From 8e4bff4cab0ddac6060645b0715210484d02ff40 Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Wed, 30 Aug 2017 08:25:59 +0000 Subject: Initial file dump from open source release --- drivers/usb/Kconfig | 141 + drivers/usb/Makefile | 58 + drivers/usb/README | 54 + drivers/usb/atm/Kconfig | 68 + drivers/usb/atm/Makefile | 11 + drivers/usb/atm/cxacru.c | 1381 +++++ drivers/usb/atm/speedtch.c | 961 ++++ drivers/usb/atm/ueagle-atm.c | 2808 ++++++++++ drivers/usb/atm/usbatm.c | 1386 +++++ drivers/usb/atm/usbatm.h | 222 + drivers/usb/atm/xusbatm.c | 229 + drivers/usb/c67x00/Makefile | 9 + drivers/usb/c67x00/c67x00-drv.c | 234 + drivers/usb/c67x00/c67x00-hcd.c | 411 ++ drivers/usb/c67x00/c67x00-hcd.h | 133 + drivers/usb/c67x00/c67x00-ll-hpi.c | 481 ++ drivers/usb/c67x00/c67x00-sched.c | 1171 ++++ drivers/usb/c67x00/c67x00.h | 294 + drivers/usb/chipidea/Kconfig | 34 + drivers/usb/chipidea/Makefile | 21 + drivers/usb/chipidea/bits.h | 90 + drivers/usb/chipidea/ci.h | 305 + drivers/usb/chipidea/ci13xxx_imx.c | 274 + drivers/usb/chipidea/ci13xxx_imx.h | 31 + drivers/usb/chipidea/ci13xxx_msm.c | 99 + drivers/usb/chipidea/ci13xxx_pci.c | 158 + drivers/usb/chipidea/core.c | 495 ++ drivers/usb/chipidea/debug.c | 301 + drivers/usb/chipidea/debug.h | 30 + drivers/usb/chipidea/host.c | 107 + drivers/usb/chipidea/host.h | 17 + drivers/usb/chipidea/udc.c | 1786 ++++++ drivers/usb/chipidea/udc.h | 93 + drivers/usb/chipidea/usbmisc_imx.c | 261 + drivers/usb/class/Kconfig | 48 + drivers/usb/class/Makefile | 9 + drivers/usb/class/cdc-acm.c | 1843 ++++++ drivers/usb/class/cdc-acm.h | 131 + drivers/usb/class/cdc-wdm.c | 1073 ++++ drivers/usb/class/usblp.c | 1433 +++++ drivers/usb/class/usbtmc.c | 1103 ++++ drivers/usb/core/Kconfig | 91 + drivers/usb/core/Makefile | 15 + drivers/usb/core/buffer.c | 151 + drivers/usb/core/config.c | 860 +++ drivers/usb/core/devices.c | 692 +++ drivers/usb/core/devio.c | 2291 ++++++++ drivers/usb/core/driver.c | 1812 ++++++ drivers/usb/core/endpoint.c | 218 + drivers/usb/core/file.c | 253 + drivers/usb/core/generic.c | 246 + drivers/usb/core/hcd-pci.c | 651 +++ drivers/usb/core/hcd.c | 2733 +++++++++ drivers/usb/core/hub.c | 5510 ++++++++++++++++++ drivers/usb/core/hub.h | 125 + drivers/usb/core/message.c | 1994 +++++++ drivers/usb/core/notify.c | 69 + drivers/usb/core/otg_whitelist.h | 112 + drivers/usb/core/port.c | 198 + drivers/usb/core/quirks.c | 251 + drivers/usb/core/sysfs.c | 971 ++++ drivers/usb/core/urb.c | 912 +++ drivers/usb/core/usb-acpi.c | 238 + drivers/usb/core/usb.c | 1074 ++++ drivers/usb/core/usb.h | 197 + drivers/usb/dwc3/Kconfig | 54 + drivers/usb/dwc3/Makefile | 41 + drivers/usb/dwc3/core.c | 780 +++ drivers/usb/dwc3/core.h | 939 ++++ drivers/usb/dwc3/debug.h | 50 + drivers/usb/dwc3/debugfs.c | 710 +++ drivers/usb/dwc3/dwc3-exynos.c | 232 + drivers/usb/dwc3/dwc3-omap.c | 481 ++ drivers/usb/dwc3/dwc3-pci.c | 268 + drivers/usb/dwc3/ep0.c | 1066 ++++ drivers/usb/dwc3/gadget.c | 2776 +++++++++ drivers/usb/dwc3/gadget.h | 194 + drivers/usb/dwc3/host.c | 87 + drivers/usb/dwc3/io.h | 66 + drivers/usb/early/Makefile | 5 + drivers/usb/early/ehci-dbgp.c | 1101 ++++ drivers/usb/gadget/Kconfig | 1036 ++++ drivers/usb/gadget/Makefile | 90 + drivers/usb/gadget/acm_ms.c | 233 + drivers/usb/gadget/amd5536udc.c | 3366 +++++++++++ drivers/usb/gadget/amd5536udc.h | 617 ++ drivers/usb/gadget/android.c | 1761 ++++++ drivers/usb/gadget/at91_udc.c | 1967 +++++++ drivers/usb/gadget/at91_udc.h | 169 + drivers/usb/gadget/atmel_usba_udc.c | 2040 +++++++ drivers/usb/gadget/atmel_usba_udc.h | 352 ++ drivers/usb/gadget/audio.c | 190 + drivers/usb/gadget/bcm63xx_udc.c | 2437 ++++++++ drivers/usb/gadget/cdc2.c | 230 + drivers/usb/gadget/composite.c | 1909 +++++++ drivers/usb/gadget/config.c | 197 + drivers/usb/gadget/configfs.c | 1007 ++++ drivers/usb/gadget/dbgp.c | 434 ++ drivers/usb/gadget/dummy_hcd.c | 2766 +++++++++ drivers/usb/gadget/dwc_otg/Makefile | 34 + drivers/usb/gadget/dwc_otg/dwc_common_linux.c | 1417 +++++ drivers/usb/gadget/dwc_otg/dwc_list.h | 616 ++ drivers/usb/gadget/dwc_otg/dwc_os.h | 1237 +++++ drivers/usb/gadget/dwc_otg/dwc_otg_adp.c | 854 +++ drivers/usb/gadget/dwc_otg/dwc_otg_adp.h | 80 + drivers/usb/gadget/dwc_otg/dwc_otg_attr.c | 1156 ++++ drivers/usb/gadget/dwc_otg/dwc_otg_attr.h | 76 + drivers/usb/gadget/dwc_otg/dwc_otg_cil.c | 7101 ++++++++++++++++++++++++ drivers/usb/gadget/dwc_otg/dwc_otg_cil.h | 1498 +++++ drivers/usb/gadget/dwc_otg/dwc_otg_cil_intr.c | 857 +++ drivers/usb/gadget/dwc_otg/dwc_otg_core_if.h | 706 +++ drivers/usb/gadget/dwc_otg/dwc_otg_dbg.h | 113 + drivers/usb/gadget/dwc_otg/dwc_otg_driver.c | 1518 +++++ drivers/usb/gadget/dwc_otg/dwc_otg_driver.h | 97 + drivers/usb/gadget/dwc_otg/dwc_otg_hcd.c | 3352 +++++++++++ drivers/usb/gadget/dwc_otg/dwc_otg_hcd.h | 803 +++ drivers/usb/gadget/dwc_otg/dwc_otg_hcd_ddma.c | 1125 ++++ drivers/usb/gadget/dwc_otg/dwc_otg_hcd_if.h | 412 ++ drivers/usb/gadget/dwc_otg/dwc_otg_hcd_intr.c | 2089 +++++++ drivers/usb/gadget/dwc_otg/dwc_otg_hcd_linux.c | 988 ++++ drivers/usb/gadget/dwc_otg/dwc_otg_hcd_queue.c | 727 +++ drivers/usb/gadget/dwc_otg/dwc_otg_os_dep.h | 116 + drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c | 2693 +++++++++ drivers/usb/gadget/dwc_otg/dwc_otg_pcd.h | 244 + drivers/usb/gadget/dwc_otg/dwc_otg_pcd_if.h | 357 ++ drivers/usb/gadget/dwc_otg/dwc_otg_pcd_intr.c | 4679 ++++++++++++++++ drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c | 1675 ++++++ drivers/usb/gadget/dwc_otg/dwc_otg_regs.h | 2550 +++++++++ drivers/usb/gadget/dwc_otg/usb.h | 850 +++ drivers/usb/gadget/dwc_otg/usb_hw.c | 430 ++ drivers/usb/gadget/dwc_otg/usb_hw.h | 32 + drivers/usb/gadget/epautoconf.c | 380 ++ drivers/usb/gadget/ether.c | 374 ++ drivers/usb/gadget/f_accessory.c | 1211 ++++ drivers/usb/gadget/f_acm.c | 849 +++ drivers/usb/gadget/f_adb.c | 619 +++ drivers/usb/gadget/f_audio_source.c | 839 +++ drivers/usb/gadget/f_ecm.c | 874 +++ drivers/usb/gadget/f_eem.c | 572 ++ drivers/usb/gadget/f_fs.c | 2548 +++++++++ drivers/usb/gadget/f_hid.c | 771 +++ drivers/usb/gadget/f_loopback.c | 433 ++ drivers/usb/gadget/f_mass_storage.c | 3173 +++++++++++ drivers/usb/gadget/f_midi.c | 995 ++++ drivers/usb/gadget/f_mtp.c | 1287 +++++ drivers/usb/gadget/f_ncm.c | 1345 +++++ drivers/usb/gadget/f_obex.c | 592 ++ drivers/usb/gadget/f_phonet.c | 634 +++ drivers/usb/gadget/f_rndis.c | 896 +++ drivers/usb/gadget/f_serial.c | 427 ++ drivers/usb/gadget/f_sourcesink.c | 942 ++++ drivers/usb/gadget/f_subset.c | 425 ++ drivers/usb/gadget/f_uac1.c | 768 +++ drivers/usb/gadget/f_uac2.c | 1370 +++++ drivers/usb/gadget/f_uvc.c | 836 +++ drivers/usb/gadget/f_uvc.h | 27 + drivers/usb/gadget/f_vserial.c | 894 +++ drivers/usb/gadget/fsl_mxc_udc.c | 123 + drivers/usb/gadget/fsl_qe_udc.c | 2736 +++++++++ drivers/usb/gadget/fsl_qe_udc.h | 421 ++ drivers/usb/gadget/fsl_udc_core.c | 2682 +++++++++ drivers/usb/gadget/fsl_usb2_udc.h | 611 ++ drivers/usb/gadget/functions.c | 116 + drivers/usb/gadget/fusb300_udc.c | 1505 +++++ drivers/usb/gadget/fusb300_udc.h | 678 +++ drivers/usb/gadget/g_ffs.c | 486 ++ drivers/usb/gadget/g_zero.h | 45 + drivers/usb/gadget/gadget_chips.h | 66 + drivers/usb/gadget/gmidi.c | 177 + drivers/usb/gadget/goku_udc.c | 1829 ++++++ drivers/usb/gadget/goku_udc.h | 292 + drivers/usb/gadget/hid.c | 266 + drivers/usb/gadget/imx_udc.c | 1544 ++++++ drivers/usb/gadget/imx_udc.h | 351 ++ drivers/usb/gadget/inode.c | 2148 +++++++ drivers/usb/gadget/lpc32xx_udc.c | 3424 ++++++++++++ drivers/usb/gadget/m66592-udc.c | 1709 ++++++ drivers/usb/gadget/m66592-udc.h | 606 ++ drivers/usb/gadget/mass_storage.c | 199 + drivers/usb/gadget/multi.c | 374 ++ drivers/usb/gadget/mv_u3d.h | 320 ++ drivers/usb/gadget/mv_u3d_core.c | 2079 +++++++ drivers/usb/gadget/mv_udc.h | 313 ++ drivers/usb/gadget/mv_udc_core.c | 2424 ++++++++ drivers/usb/gadget/ncm.c | 205 + drivers/usb/gadget/ndis.h | 47 + drivers/usb/gadget/net2272.c | 2710 +++++++++ drivers/usb/gadget/net2272.h | 601 ++ drivers/usb/gadget/net2280.c | 2904 ++++++++++ drivers/usb/gadget/net2280.h | 308 + drivers/usb/gadget/nokia.c | 279 + drivers/usb/gadget/omap_udc.c | 3041 ++++++++++ drivers/usb/gadget/omap_udc.h | 206 + drivers/usb/gadget/pch_udc.c | 3249 +++++++++++ drivers/usb/gadget/printer.c | 1299 +++++ drivers/usb/gadget/pxa25x_udc.c | 2311 ++++++++ drivers/usb/gadget/pxa25x_udc.h | 252 + drivers/usb/gadget/pxa27x_udc.c | 2624 +++++++++ drivers/usb/gadget/pxa27x_udc.h | 497 ++ drivers/usb/gadget/r8a66597-udc.c | 2030 +++++++ drivers/usb/gadget/r8a66597-udc.h | 290 + drivers/usb/gadget/rndis.c | 1237 +++++ drivers/usb/gadget/rndis.h | 222 + drivers/usb/gadget/s3c-hsotg.c | 3667 ++++++++++++ drivers/usb/gadget/s3c-hsotg.h | 377 ++ drivers/usb/gadget/s3c-hsudc.c | 1370 +++++ drivers/usb/gadget/s3c2410_udc.c | 2043 +++++++ drivers/usb/gadget/s3c2410_udc.h | 100 + drivers/usb/gadget/serial.c | 276 + drivers/usb/gadget/storage_common.c | 704 +++ drivers/usb/gadget/tcm_usb_gadget.c | 2476 +++++++++ drivers/usb/gadget/tcm_usb_gadget.h | 145 + drivers/usb/gadget/u_ether.c | 1581 ++++++ drivers/usb/gadget/u_ether.h | 161 + drivers/usb/gadget/u_phonet.h | 21 + drivers/usb/gadget/u_serial.c | 1360 +++++ drivers/usb/gadget/u_serial.h | 71 + drivers/usb/gadget/u_uac1.c | 330 ++ drivers/usb/gadget/u_uac1.h | 56 + drivers/usb/gadget/udc-core.c | 579 ++ drivers/usb/gadget/usbstring.c | 72 + drivers/usb/gadget/uvc.h | 200 + drivers/usb/gadget/uvc_queue.c | 393 ++ drivers/usb/gadget/uvc_queue.h | 63 + drivers/usb/gadget/uvc_v4l2.c | 365 ++ drivers/usb/gadget/uvc_video.c | 394 ++ drivers/usb/gadget/webcam.c | 412 ++ drivers/usb/gadget/zero.c | 406 ++ drivers/usb/host/Kconfig | 708 +++ drivers/usb/host/Makefile | 55 + drivers/usb/host/bcma-hcd.c | 334 ++ drivers/usb/host/ehci-atmel.c | 209 + drivers/usb/host/ehci-dbg.c | 980 ++++ drivers/usb/host/ehci-fsl.c | 732 +++ drivers/usb/host/ehci-fsl.h | 65 + drivers/usb/host/ehci-grlib.c | 202 + drivers/usb/host/ehci-h20ahb.c | 341 ++ drivers/usb/host/ehci-hcd.c | 1396 +++++ drivers/usb/host/ehci-hub.c | 1139 ++++ drivers/usb/host/ehci-mem.c | 245 + drivers/usb/host/ehci-msm.c | 221 + drivers/usb/host/ehci-mv.c | 341 ++ drivers/usb/host/ehci-mxc.c | 240 + drivers/usb/host/ehci-octeon.c | 200 + drivers/usb/host/ehci-omap.c | 334 ++ drivers/usb/host/ehci-orion.c | 331 ++ drivers/usb/host/ehci-pci.c | 464 ++ drivers/usb/host/ehci-platform.c | 252 + drivers/usb/host/ehci-pmcmsp.c | 369 ++ drivers/usb/host/ehci-ppc-of.c | 247 + drivers/usb/host/ehci-ps3.c | 265 + drivers/usb/host/ehci-q.c | 1368 +++++ drivers/usb/host/ehci-s5p.c | 321 ++ drivers/usb/host/ehci-sched.c | 2327 ++++++++ drivers/usb/host/ehci-sead3.c | 187 + drivers/usb/host/ehci-sh.c | 206 + drivers/usb/host/ehci-spear.c | 206 + drivers/usb/host/ehci-sysfs.c | 190 + drivers/usb/host/ehci-tegra.c | 859 +++ drivers/usb/host/ehci-tilegx.c | 217 + drivers/usb/host/ehci-timer.c | 401 ++ drivers/usb/host/ehci-w90x900.c | 179 + drivers/usb/host/ehci-xilinx-of.c | 254 + drivers/usb/host/ehci.h | 825 +++ drivers/usb/host/fhci-dbg.c | 139 + drivers/usb/host/fhci-hcd.c | 833 +++ drivers/usb/host/fhci-hub.c | 345 ++ drivers/usb/host/fhci-mem.c | 114 + drivers/usb/host/fhci-q.c | 285 + drivers/usb/host/fhci-sched.c | 894 +++ drivers/usb/host/fhci-tds.c | 626 +++ drivers/usb/host/fhci.h | 594 ++ drivers/usb/host/fsl-mph-dr-of.c | 349 ++ drivers/usb/host/hwa-hc.c | 843 +++ drivers/usb/host/imx21-dbg.c | 527 ++ drivers/usb/host/imx21-hcd.c | 1933 +++++++ drivers/usb/host/imx21-hcd.h | 440 ++ drivers/usb/host/isp116x-hcd.c | 1713 ++++++ drivers/usb/host/isp116x.h | 606 ++ drivers/usb/host/isp1362-hcd.c | 2866 ++++++++++ drivers/usb/host/isp1362.h | 1057 ++++ drivers/usb/host/isp1760-hcd.c | 2267 ++++++++ drivers/usb/host/isp1760-hcd.h | 208 + drivers/usb/host/isp1760-if.c | 480 ++ drivers/usb/host/octeon2-common.c | 200 + drivers/usb/host/ohci-at91.c | 729 +++ drivers/usb/host/ohci-da8xx.c | 458 ++ drivers/usb/host/ohci-dbg.c | 877 +++ drivers/usb/host/ohci-ep93xx.c | 217 + drivers/usb/host/ohci-exynos.c | 336 ++ drivers/usb/host/ohci-hcd.c | 1449 +++++ drivers/usb/host/ohci-hub.c | 795 +++ drivers/usb/host/ohci-jz4740.c | 276 + drivers/usb/host/ohci-mem.c | 138 + drivers/usb/host/ohci-nxp.c | 388 ++ drivers/usb/host/ohci-octeon.c | 214 + drivers/usb/host/ohci-omap.c | 557 ++ drivers/usb/host/ohci-omap3.c | 260 + drivers/usb/host/ohci-pci.c | 380 ++ drivers/usb/host/ohci-platform.c | 225 + drivers/usb/host/ohci-ppc-of.c | 254 + drivers/usb/host/ohci-ps3.c | 250 + drivers/usb/host/ohci-pxa27x.c | 620 +++ drivers/usb/host/ohci-q.c | 1184 ++++ drivers/usb/host/ohci-s3c2410.c | 535 ++ drivers/usb/host/ohci-sa1111.c | 257 + drivers/usb/host/ohci-sm501.c | 265 + drivers/usb/host/ohci-spear.c | 239 + drivers/usb/host/ohci-tilegx.c | 206 + drivers/usb/host/ohci-tmio.c | 375 ++ drivers/usb/host/ohci.h | 722 +++ drivers/usb/host/oxu210hp-hcd.c | 3958 +++++++++++++ drivers/usb/host/oxu210hp.h | 447 ++ drivers/usb/host/pci-quirks.c | 989 ++++ drivers/usb/host/pci-quirks.h | 21 + drivers/usb/host/r8a66597-hcd.c | 2543 +++++++++ drivers/usb/host/r8a66597.h | 345 ++ drivers/usb/host/sl811-hcd.c | 1822 ++++++ drivers/usb/host/sl811.h | 266 + drivers/usb/host/sl811_cs.c | 203 + drivers/usb/host/ssb-hcd.c | 279 + drivers/usb/host/u132-hcd.c | 3261 +++++++++++ drivers/usb/host/uhci-debug.c | 655 +++ drivers/usb/host/uhci-grlib.c | 208 + drivers/usb/host/uhci-hcd.c | 939 ++++ drivers/usb/host/uhci-hcd.h | 660 +++ drivers/usb/host/uhci-hub.c | 418 ++ drivers/usb/host/uhci-pci.c | 301 + drivers/usb/host/uhci-platform.c | 166 + drivers/usb/host/uhci-q.c | 1794 ++++++ drivers/usb/host/whci/Kbuild | 12 + drivers/usb/host/whci/asl.c | 389 ++ drivers/usb/host/whci/debug.c | 203 + drivers/usb/host/whci/hcd.c | 368 ++ drivers/usb/host/whci/hw.c | 104 + drivers/usb/host/whci/init.c | 189 + drivers/usb/host/whci/int.c | 94 + drivers/usb/host/whci/pzl.c | 417 ++ drivers/usb/host/whci/qset.c | 835 +++ drivers/usb/host/whci/whcd.h | 215 + drivers/usb/host/whci/whci-hc.h | 414 ++ drivers/usb/host/whci/wusb.c | 222 + drivers/usb/host/xhci-dbg.c | 574 ++ drivers/usb/host/xhci-ext-caps.h | 155 + drivers/usb/host/xhci-hub.c | 1218 ++++ drivers/usb/host/xhci-mem.c | 2475 +++++++++ drivers/usb/host/xhci-pci.c | 379 ++ drivers/usb/host/xhci-plat.c | 206 + drivers/usb/host/xhci-ring.c | 4031 ++++++++++++++ drivers/usb/host/xhci.c | 4813 ++++++++++++++++ drivers/usb/host/xhci.h | 1861 +++++++ drivers/usb/image/Kconfig | 28 + drivers/usb/image/Makefile | 6 + drivers/usb/image/mdc800.c | 1086 ++++ drivers/usb/image/microtek.c | 816 +++ drivers/usb/image/microtek.h | 54 + drivers/usb/misc/Kconfig | 237 + drivers/usb/misc/Makefile | 31 + drivers/usb/misc/adutux.c | 892 +++ drivers/usb/misc/appledisplay.c | 382 ++ drivers/usb/misc/cypress_cy7c63.c | 279 + drivers/usb/misc/cytherm.c | 424 ++ drivers/usb/misc/emi26.c | 263 + drivers/usb/misc/emi62.c | 276 + drivers/usb/misc/ezusb.c | 167 + drivers/usb/misc/ftdi-elan.c | 2946 ++++++++++ drivers/usb/misc/idmouse.c | 437 ++ drivers/usb/misc/iowarrior.c | 930 ++++ drivers/usb/misc/isight_firmware.c | 134 + drivers/usb/misc/ldusb.c | 828 +++ drivers/usb/misc/legousbtower.c | 1054 ++++ drivers/usb/misc/rio500.c | 554 ++ drivers/usb/misc/rio500_usb.h | 37 + drivers/usb/misc/sisusbvga/Kconfig | 46 + drivers/usb/misc/sisusbvga/Makefile | 7 + drivers/usb/misc/sisusbvga/sisusb.c | 3287 +++++++++++ drivers/usb/misc/sisusbvga/sisusb.h | 311 ++ drivers/usb/misc/sisusbvga/sisusb_con.c | 1564 ++++++ drivers/usb/misc/sisusbvga/sisusb_init.c | 959 ++++ drivers/usb/misc/sisusbvga/sisusb_init.h | 841 +++ drivers/usb/misc/sisusbvga/sisusb_struct.h | 161 + drivers/usb/misc/trancevibrator.c | 144 + drivers/usb/misc/usb3503.c | 314 ++ drivers/usb/misc/usb_u132.h | 101 + drivers/usb/misc/usblcd.c | 461 ++ drivers/usb/misc/usbled.c | 240 + drivers/usb/misc/usbsevseg.c | 444 ++ drivers/usb/misc/usbtest.c | 2576 +++++++++ drivers/usb/misc/uss720.c | 832 +++ drivers/usb/misc/yurex.c | 548 ++ drivers/usb/mon/Kconfig | 12 + drivers/usb/mon/Makefile | 7 + drivers/usb/mon/mon_bin.c | 1389 +++++ drivers/usb/mon/mon_main.c | 431 ++ drivers/usb/mon/mon_stat.c | 69 + drivers/usb/mon/mon_text.c | 761 +++ drivers/usb/mon/usb_mon.h | 75 + drivers/usb/musb/Kconfig | 119 + drivers/usb/musb/Makefile | 31 + drivers/usb/musb/am35x.c | 641 +++ drivers/usb/musb/blackfin.c | 573 ++ drivers/usb/musb/blackfin.h | 87 + drivers/usb/musb/cppi_dma.c | 1553 ++++++ drivers/usb/musb/cppi_dma.h | 134 + drivers/usb/musb/da8xx.c | 584 ++ drivers/usb/musb/davinci.c | 616 ++ drivers/usb/musb/davinci.h | 107 + drivers/usb/musb/musb_core.c | 2300 ++++++++ drivers/usb/musb/musb_core.h | 592 ++ drivers/usb/musb/musb_debug.h | 58 + drivers/usb/musb/musb_debugfs.c | 276 + drivers/usb/musb/musb_dma.h | 185 + drivers/usb/musb/musb_dsps.c | 797 +++ drivers/usb/musb/musb_gadget.c | 2118 +++++++ drivers/usb/musb/musb_gadget.h | 121 + drivers/usb/musb/musb_gadget_ep0.c | 1083 ++++ drivers/usb/musb/musb_host.c | 2608 +++++++++ drivers/usb/musb/musb_host.h | 112 + drivers/usb/musb/musb_io.h | 122 + drivers/usb/musb/musb_regs.h | 645 +++ drivers/usb/musb/musb_virthub.c | 432 ++ drivers/usb/musb/musbhsdma.c | 421 ++ drivers/usb/musb/musbhsdma.h | 161 + drivers/usb/musb/omap2430.c | 691 +++ drivers/usb/musb/omap2430.h | 52 + drivers/usb/musb/tusb6010.c | 1240 +++++ drivers/usb/musb/tusb6010.h | 232 + drivers/usb/musb/tusb6010_omap.c | 732 +++ drivers/usb/musb/ux500.c | 335 ++ drivers/usb/musb/ux500_dma.c | 404 ++ drivers/usb/phy/Kconfig | 221 + drivers/usb/phy/Makefile | 34 + drivers/usb/phy/otg-wakelock.c | 173 + drivers/usb/phy/phy-ab8500-usb.c | 922 +++ drivers/usb/phy/phy-fsl-usb.c | 1174 ++++ drivers/usb/phy/phy-fsl-usb.h | 406 ++ drivers/usb/phy/phy-fsm-usb.c | 348 ++ drivers/usb/phy/phy-fsm-usb.h | 154 + drivers/usb/phy/phy-gpio-vbus-usb.c | 420 ++ drivers/usb/phy/phy-isp1301-omap.c | 1656 ++++++ drivers/usb/phy/phy-isp1301.c | 163 + drivers/usb/phy/phy-msm-usb.c | 1762 ++++++ drivers/usb/phy/phy-mv-u3d-usb.c | 338 ++ drivers/usb/phy/phy-mv-u3d-usb.h | 105 + drivers/usb/phy/phy-mv-usb.c | 906 +++ drivers/usb/phy/phy-mv-usb.h | 164 + drivers/usb/phy/phy-mxs-usb.c | 214 + drivers/usb/phy/phy-nop.c | 292 + drivers/usb/phy/phy-omap-control.c | 289 + drivers/usb/phy/phy-omap-usb2.c | 273 + drivers/usb/phy/phy-omap-usb3.c | 353 ++ drivers/usb/phy/phy-rcar-usb.c | 220 + drivers/usb/phy/phy-samsung-usb.c | 236 + drivers/usb/phy/phy-samsung-usb.h | 327 ++ drivers/usb/phy/phy-samsung-usb2.c | 504 ++ drivers/usb/phy/phy-samsung-usb3.c | 342 ++ drivers/usb/phy/phy-tegra-usb.c | 803 +++ drivers/usb/phy/phy-twl4030-usb.c | 794 +++ drivers/usb/phy/phy-twl6030-usb.c | 453 ++ drivers/usb/phy/phy-ulpi-viewport.c | 80 + drivers/usb/phy/phy-ulpi.c | 286 + drivers/usb/phy/phy.c | 438 ++ drivers/usb/renesas_usbhs/Kconfig | 15 + drivers/usb/renesas_usbhs/Makefile | 15 + drivers/usb/renesas_usbhs/common.c | 645 +++ drivers/usb/renesas_usbhs/common.h | 325 ++ drivers/usb/renesas_usbhs/fifo.c | 1189 ++++ drivers/usb/renesas_usbhs/fifo.h | 102 + drivers/usb/renesas_usbhs/mod.c | 387 ++ drivers/usb/renesas_usbhs/mod.h | 172 + drivers/usb/renesas_usbhs/mod_gadget.c | 1029 ++++ drivers/usb/renesas_usbhs/mod_host.c | 1585 ++++++ drivers/usb/renesas_usbhs/pipe.c | 826 +++ drivers/usb/renesas_usbhs/pipe.h | 116 + drivers/usb/serial/Kconfig | 723 +++ drivers/usb/serial/Makefile | 67 + drivers/usb/serial/Makefile-keyspan_pda_fw | 16 + drivers/usb/serial/aircable.c | 184 + drivers/usb/serial/ark3116.c | 792 +++ drivers/usb/serial/belkin_sa.c | 502 ++ drivers/usb/serial/belkin_sa.h | 124 + drivers/usb/serial/bus.c | 196 + drivers/usb/serial/ch341.c | 611 ++ drivers/usb/serial/console.c | 313 ++ drivers/usb/serial/cp210x.c | 898 +++ drivers/usb/serial/cyberjack.c | 422 ++ drivers/usb/serial/cypress_m8.c | 1307 +++++ drivers/usb/serial/cypress_m8.h | 74 + drivers/usb/serial/digi_acceleport.c | 1550 ++++++ drivers/usb/serial/empeg.c | 130 + drivers/usb/serial/ezusb_convert.pl | 50 + drivers/usb/serial/f81232.c | 373 ++ drivers/usb/serial/ftdi_sio.c | 2555 +++++++++ drivers/usb/serial/ftdi_sio.h | 564 ++ drivers/usb/serial/ftdi_sio_ids.h | 1434 +++++ drivers/usb/serial/funsoft.c | 40 + drivers/usb/serial/garmin_gps.c | 1462 +++++ drivers/usb/serial/generic.c | 608 ++ drivers/usb/serial/hp4x.c | 51 + drivers/usb/serial/io_16654.h | 195 + drivers/usb/serial/io_edgeport.c | 3050 ++++++++++ drivers/usb/serial/io_edgeport.h | 134 + drivers/usb/serial/io_ionsp.h | 455 ++ drivers/usb/serial/io_tables.h | 232 + drivers/usb/serial/io_ti.c | 2639 +++++++++ drivers/usb/serial/io_ti.h | 186 + drivers/usb/serial/io_usbvend.h | 683 +++ drivers/usb/serial/ipaq.c | 619 +++ drivers/usb/serial/ipw.c | 318 ++ drivers/usb/serial/ir-usb.c | 447 ++ drivers/usb/serial/iuu_phoenix.c | 1233 ++++ drivers/usb/serial/iuu_phoenix.h | 122 + drivers/usb/serial/keyspan.c | 2492 +++++++++ drivers/usb/serial/keyspan.h | 629 +++ drivers/usb/serial/keyspan_pda.c | 796 +++ drivers/usb/serial/keyspan_usa26msg.h | 260 + drivers/usb/serial/keyspan_usa28msg.h | 201 + drivers/usb/serial/keyspan_usa49msg.h | 282 + drivers/usb/serial/keyspan_usa67msg.h | 254 + drivers/usb/serial/keyspan_usa90msg.h | 198 + drivers/usb/serial/kl5kusb105.c | 643 +++ drivers/usb/serial/kl5kusb105.h | 68 + drivers/usb/serial/kobil_sct.c | 573 ++ drivers/usb/serial/kobil_sct.h | 77 + drivers/usb/serial/mct_u232.c | 772 +++ drivers/usb/serial/mct_u232.h | 467 ++ drivers/usb/serial/metro-usb.c | 400 ++ drivers/usb/serial/mos7720.c | 2080 +++++++ drivers/usb/serial/mos7840.c | 2553 +++++++++ drivers/usb/serial/moto_modem.c | 48 + drivers/usb/serial/navman.c | 119 + drivers/usb/serial/omninet.c | 259 + drivers/usb/serial/opticon.c | 444 ++ drivers/usb/serial/option.c | 2003 +++++++ drivers/usb/serial/oti6858.c | 882 +++ drivers/usb/serial/oti6858.h | 15 + drivers/usb/serial/pl2303.c | 867 +++ drivers/usb/serial/pl2303.h | 155 + drivers/usb/serial/qcaux.c | 91 + drivers/usb/serial/qcserial.c | 365 ++ drivers/usb/serial/quatech2.c | 1023 ++++ drivers/usb/serial/safe_serial.c | 329 ++ drivers/usb/serial/siemens_mpi.c | 47 + drivers/usb/serial/sierra.c | 1099 ++++ drivers/usb/serial/spcp8x5.c | 493 ++ drivers/usb/serial/ssu100.c | 571 ++ drivers/usb/serial/symbolserial.c | 212 + drivers/usb/serial/ti_usb_3410_5052.c | 1607 ++++++ drivers/usb/serial/ti_usb_3410_5052.h | 247 + drivers/usb/serial/usb-serial.c | 1490 +++++ drivers/usb/serial/usb-wwan.h | 68 + drivers/usb/serial/usb_debug.c | 80 + drivers/usb/serial/usb_wwan.c | 710 +++ drivers/usb/serial/visor.c | 626 +++ drivers/usb/serial/visor.h | 161 + drivers/usb/serial/vivopay-serial.c | 43 + drivers/usb/serial/whiteheat.c | 847 +++ drivers/usb/serial/whiteheat.h | 302 + drivers/usb/serial/wishbone-serial.c | 95 + drivers/usb/serial/xsens_mt.c | 86 + drivers/usb/serial/zio.c | 39 + drivers/usb/serial/zte_ev.c | 306 + drivers/usb/storage/Kconfig | 216 + drivers/usb/storage/Makefile | 44 + drivers/usb/storage/alauda.c | 1271 +++++ drivers/usb/storage/cypress_atacb.c | 290 + drivers/usb/storage/datafab.c | 758 +++ drivers/usb/storage/debug.c | 195 + drivers/usb/storage/debug.h | 69 + drivers/usb/storage/ene_ub6250.c | 2421 ++++++++ drivers/usb/storage/freecom.c | 560 ++ drivers/usb/storage/initializers.c | 106 + drivers/usb/storage/initializers.h | 50 + drivers/usb/storage/isd200.c | 1572 ++++++ drivers/usb/storage/jumpshot.c | 684 +++ drivers/usb/storage/karma.c | 236 + drivers/usb/storage/onetouch.c | 318 ++ drivers/usb/storage/option_ms.c | 171 + drivers/usb/storage/option_ms.h | 4 + drivers/usb/storage/protocol.c | 220 + drivers/usb/storage/protocol.h | 57 + drivers/usb/storage/realtek_cr.c | 1071 ++++ drivers/usb/storage/scsiglue.c | 600 ++ drivers/usb/storage/scsiglue.h | 48 + drivers/usb/storage/sddr09.c | 1787 ++++++ drivers/usb/storage/sddr55.c | 1006 ++++ drivers/usb/storage/shuttle_usbat.c | 1874 +++++++ drivers/usb/storage/sierra_ms.c | 199 + drivers/usb/storage/sierra_ms.h | 4 + drivers/usb/storage/transport.c | 1374 +++++ drivers/usb/storage/transport.h | 103 + drivers/usb/storage/uas.c | 1073 ++++ drivers/usb/storage/unusual_alauda.h | 31 + drivers/usb/storage/unusual_cypress.h | 39 + drivers/usb/storage/unusual_datafab.h | 98 + drivers/usb/storage/unusual_devs.h | 2163 ++++++++ drivers/usb/storage/unusual_ene_ub6250.h | 26 + drivers/usb/storage/unusual_freecom.h | 26 + drivers/usb/storage/unusual_isd200.h | 57 + drivers/usb/storage/unusual_jumpshot.h | 27 + drivers/usb/storage/unusual_karma.h | 26 + drivers/usb/storage/unusual_onetouch.h | 36 + drivers/usb/storage/unusual_realtek.h | 41 + drivers/usb/storage/unusual_sddr09.h | 56 + drivers/usb/storage/unusual_sddr55.h | 44 + drivers/usb/storage/unusual_usbat.h | 43 + drivers/usb/storage/usb.c | 1088 ++++ drivers/usb/storage/usb.h | 204 + drivers/usb/storage/usual-tables.c | 123 + drivers/usb/usb-common.c | 82 + drivers/usb/usb-skeleton.c | 666 +++ drivers/usb/wusbcore/Kconfig | 39 + drivers/usb/wusbcore/Makefile | 25 + drivers/usb/wusbcore/cbaf.c | 662 +++ drivers/usb/wusbcore/crypto.c | 518 ++ drivers/usb/wusbcore/dev-sysfs.c | 139 + drivers/usb/wusbcore/devconnect.c | 1120 ++++ drivers/usb/wusbcore/mmc.c | 287 + drivers/usb/wusbcore/pal.c | 54 + drivers/usb/wusbcore/reservation.c | 118 + drivers/usb/wusbcore/rh.c | 452 ++ drivers/usb/wusbcore/security.c | 578 ++ drivers/usb/wusbcore/wa-hc.c | 99 + drivers/usb/wusbcore/wa-hc.h | 417 ++ drivers/usb/wusbcore/wa-nep.c | 305 + drivers/usb/wusbcore/wa-rpipe.c | 530 ++ drivers/usb/wusbcore/wa-xfer.c | 1621 ++++++ drivers/usb/wusbcore/wusbhc.c | 450 ++ drivers/usb/wusbcore/wusbhc.h | 496 ++ 629 files changed, 444173 insertions(+) create mode 100644 drivers/usb/Kconfig create mode 100644 drivers/usb/Makefile create mode 100644 drivers/usb/README create mode 100644 drivers/usb/atm/Kconfig create mode 100644 drivers/usb/atm/Makefile create mode 100644 drivers/usb/atm/cxacru.c create mode 100644 drivers/usb/atm/speedtch.c create mode 100644 drivers/usb/atm/ueagle-atm.c create mode 100644 drivers/usb/atm/usbatm.c create mode 100644 drivers/usb/atm/usbatm.h create mode 100644 drivers/usb/atm/xusbatm.c create mode 100644 drivers/usb/c67x00/Makefile create mode 100644 drivers/usb/c67x00/c67x00-drv.c create mode 100644 drivers/usb/c67x00/c67x00-hcd.c create mode 100644 drivers/usb/c67x00/c67x00-hcd.h create mode 100644 drivers/usb/c67x00/c67x00-ll-hpi.c create mode 100644 drivers/usb/c67x00/c67x00-sched.c create mode 100644 drivers/usb/c67x00/c67x00.h create mode 100644 drivers/usb/chipidea/Kconfig create mode 100644 drivers/usb/chipidea/Makefile create mode 100644 drivers/usb/chipidea/bits.h create mode 100644 drivers/usb/chipidea/ci.h create mode 100644 drivers/usb/chipidea/ci13xxx_imx.c create mode 100644 drivers/usb/chipidea/ci13xxx_imx.h create mode 100644 drivers/usb/chipidea/ci13xxx_msm.c create mode 100644 drivers/usb/chipidea/ci13xxx_pci.c create mode 100644 drivers/usb/chipidea/core.c create mode 100644 drivers/usb/chipidea/debug.c create mode 100644 drivers/usb/chipidea/debug.h create mode 100644 drivers/usb/chipidea/host.c create mode 100644 drivers/usb/chipidea/host.h create mode 100644 drivers/usb/chipidea/udc.c create mode 100644 drivers/usb/chipidea/udc.h create mode 100644 drivers/usb/chipidea/usbmisc_imx.c create mode 100644 drivers/usb/class/Kconfig create mode 100644 drivers/usb/class/Makefile create mode 100644 drivers/usb/class/cdc-acm.c create mode 100644 drivers/usb/class/cdc-acm.h create mode 100644 drivers/usb/class/cdc-wdm.c create mode 100644 drivers/usb/class/usblp.c create mode 100644 drivers/usb/class/usbtmc.c create mode 100644 drivers/usb/core/Kconfig create mode 100644 drivers/usb/core/Makefile create mode 100644 drivers/usb/core/buffer.c create mode 100644 drivers/usb/core/config.c create mode 100644 drivers/usb/core/devices.c create mode 100644 drivers/usb/core/devio.c create mode 100644 drivers/usb/core/driver.c create mode 100644 drivers/usb/core/endpoint.c create mode 100644 drivers/usb/core/file.c create mode 100644 drivers/usb/core/generic.c create mode 100644 drivers/usb/core/hcd-pci.c create mode 100644 drivers/usb/core/hcd.c create mode 100644 drivers/usb/core/hub.c create mode 100644 drivers/usb/core/hub.h create mode 100644 drivers/usb/core/message.c create mode 100644 drivers/usb/core/notify.c create mode 100644 drivers/usb/core/otg_whitelist.h create mode 100644 drivers/usb/core/port.c create mode 100644 drivers/usb/core/quirks.c create mode 100644 drivers/usb/core/sysfs.c create mode 100644 drivers/usb/core/urb.c create mode 100644 drivers/usb/core/usb-acpi.c create mode 100644 drivers/usb/core/usb.c create mode 100644 drivers/usb/core/usb.h create mode 100644 drivers/usb/dwc3/Kconfig create mode 100644 drivers/usb/dwc3/Makefile create mode 100644 drivers/usb/dwc3/core.c create mode 100644 drivers/usb/dwc3/core.h create mode 100644 drivers/usb/dwc3/debug.h create mode 100644 drivers/usb/dwc3/debugfs.c create mode 100644 drivers/usb/dwc3/dwc3-exynos.c create mode 100644 drivers/usb/dwc3/dwc3-omap.c create mode 100644 drivers/usb/dwc3/dwc3-pci.c create mode 100644 drivers/usb/dwc3/ep0.c create mode 100644 drivers/usb/dwc3/gadget.c create mode 100644 drivers/usb/dwc3/gadget.h create mode 100644 drivers/usb/dwc3/host.c create mode 100644 drivers/usb/dwc3/io.h create mode 100644 drivers/usb/early/Makefile create mode 100644 drivers/usb/early/ehci-dbgp.c create mode 100755 drivers/usb/gadget/Kconfig create mode 100644 drivers/usb/gadget/Makefile create mode 100644 drivers/usb/gadget/acm_ms.c create mode 100644 drivers/usb/gadget/amd5536udc.c create mode 100644 drivers/usb/gadget/amd5536udc.h create mode 100644 drivers/usb/gadget/android.c create mode 100644 drivers/usb/gadget/at91_udc.c create mode 100644 drivers/usb/gadget/at91_udc.h create mode 100644 drivers/usb/gadget/atmel_usba_udc.c create mode 100644 drivers/usb/gadget/atmel_usba_udc.h create mode 100644 drivers/usb/gadget/audio.c create mode 100644 drivers/usb/gadget/bcm63xx_udc.c create mode 100644 drivers/usb/gadget/cdc2.c create mode 100644 drivers/usb/gadget/composite.c create mode 100644 drivers/usb/gadget/config.c create mode 100644 drivers/usb/gadget/configfs.c create mode 100644 drivers/usb/gadget/dbgp.c create mode 100644 drivers/usb/gadget/dummy_hcd.c create mode 100644 drivers/usb/gadget/dwc_otg/Makefile create mode 100644 drivers/usb/gadget/dwc_otg/dwc_common_linux.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_list.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_os.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_adp.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_adp.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_attr.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_attr.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_cil.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_cil.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_cil_intr.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_core_if.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_dbg.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_driver.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_driver.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_hcd.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_hcd.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_hcd_ddma.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_hcd_if.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_hcd_intr.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_hcd_linux.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_hcd_queue.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_os_dep.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_pcd.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_pcd_if.h create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_pcd_intr.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c create mode 100644 drivers/usb/gadget/dwc_otg/dwc_otg_regs.h create mode 100644 drivers/usb/gadget/dwc_otg/usb.h create mode 100644 drivers/usb/gadget/dwc_otg/usb_hw.c create mode 100644 drivers/usb/gadget/dwc_otg/usb_hw.h create mode 100644 drivers/usb/gadget/epautoconf.c create mode 100644 drivers/usb/gadget/ether.c create mode 100644 drivers/usb/gadget/f_accessory.c create mode 100644 drivers/usb/gadget/f_acm.c create mode 100644 drivers/usb/gadget/f_adb.c create mode 100644 drivers/usb/gadget/f_audio_source.c create mode 100644 drivers/usb/gadget/f_ecm.c create mode 100644 drivers/usb/gadget/f_eem.c create mode 100644 drivers/usb/gadget/f_fs.c create mode 100644 drivers/usb/gadget/f_hid.c create mode 100644 drivers/usb/gadget/f_loopback.c create mode 100644 drivers/usb/gadget/f_mass_storage.c create mode 100644 drivers/usb/gadget/f_midi.c create mode 100644 drivers/usb/gadget/f_mtp.c create mode 100644 drivers/usb/gadget/f_ncm.c create mode 100644 drivers/usb/gadget/f_obex.c create mode 100644 drivers/usb/gadget/f_phonet.c create mode 100644 drivers/usb/gadget/f_rndis.c create mode 100644 drivers/usb/gadget/f_serial.c create mode 100644 drivers/usb/gadget/f_sourcesink.c create mode 100644 drivers/usb/gadget/f_subset.c create mode 100644 drivers/usb/gadget/f_uac1.c create mode 100644 drivers/usb/gadget/f_uac2.c create mode 100644 drivers/usb/gadget/f_uvc.c create mode 100644 drivers/usb/gadget/f_uvc.h create mode 100755 drivers/usb/gadget/f_vserial.c create mode 100644 drivers/usb/gadget/fsl_mxc_udc.c create mode 100644 drivers/usb/gadget/fsl_qe_udc.c create mode 100644 drivers/usb/gadget/fsl_qe_udc.h create mode 100644 drivers/usb/gadget/fsl_udc_core.c create mode 100644 drivers/usb/gadget/fsl_usb2_udc.h create mode 100644 drivers/usb/gadget/functions.c create mode 100644 drivers/usb/gadget/fusb300_udc.c create mode 100644 drivers/usb/gadget/fusb300_udc.h create mode 100644 drivers/usb/gadget/g_ffs.c create mode 100644 drivers/usb/gadget/g_zero.h create mode 100644 drivers/usb/gadget/gadget_chips.h create mode 100644 drivers/usb/gadget/gmidi.c create mode 100644 drivers/usb/gadget/goku_udc.c create mode 100644 drivers/usb/gadget/goku_udc.h create mode 100644 drivers/usb/gadget/hid.c create mode 100644 drivers/usb/gadget/imx_udc.c create mode 100644 drivers/usb/gadget/imx_udc.h create mode 100644 drivers/usb/gadget/inode.c create mode 100644 drivers/usb/gadget/lpc32xx_udc.c create mode 100644 drivers/usb/gadget/m66592-udc.c create mode 100644 drivers/usb/gadget/m66592-udc.h create mode 100644 drivers/usb/gadget/mass_storage.c create mode 100644 drivers/usb/gadget/multi.c create mode 100644 drivers/usb/gadget/mv_u3d.h create mode 100644 drivers/usb/gadget/mv_u3d_core.c create mode 100644 drivers/usb/gadget/mv_udc.h create mode 100644 drivers/usb/gadget/mv_udc_core.c create mode 100644 drivers/usb/gadget/ncm.c create mode 100644 drivers/usb/gadget/ndis.h create mode 100644 drivers/usb/gadget/net2272.c create mode 100644 drivers/usb/gadget/net2272.h create mode 100644 drivers/usb/gadget/net2280.c create mode 100644 drivers/usb/gadget/net2280.h create mode 100644 drivers/usb/gadget/nokia.c create mode 100644 drivers/usb/gadget/omap_udc.c create mode 100644 drivers/usb/gadget/omap_udc.h create mode 100644 drivers/usb/gadget/pch_udc.c create mode 100644 drivers/usb/gadget/printer.c create mode 100644 drivers/usb/gadget/pxa25x_udc.c create mode 100644 drivers/usb/gadget/pxa25x_udc.h create mode 100644 drivers/usb/gadget/pxa27x_udc.c create mode 100644 drivers/usb/gadget/pxa27x_udc.h create mode 100644 drivers/usb/gadget/r8a66597-udc.c create mode 100644 drivers/usb/gadget/r8a66597-udc.h create mode 100644 drivers/usb/gadget/rndis.c create mode 100644 drivers/usb/gadget/rndis.h create mode 100644 drivers/usb/gadget/s3c-hsotg.c create mode 100644 drivers/usb/gadget/s3c-hsotg.h create mode 100644 drivers/usb/gadget/s3c-hsudc.c create mode 100644 drivers/usb/gadget/s3c2410_udc.c create mode 100644 drivers/usb/gadget/s3c2410_udc.h create mode 100644 drivers/usb/gadget/serial.c create mode 100644 drivers/usb/gadget/storage_common.c create mode 100644 drivers/usb/gadget/tcm_usb_gadget.c create mode 100644 drivers/usb/gadget/tcm_usb_gadget.h create mode 100644 drivers/usb/gadget/u_ether.c create mode 100644 drivers/usb/gadget/u_ether.h create mode 100644 drivers/usb/gadget/u_phonet.h create mode 100644 drivers/usb/gadget/u_serial.c create mode 100644 drivers/usb/gadget/u_serial.h create mode 100644 drivers/usb/gadget/u_uac1.c create mode 100644 drivers/usb/gadget/u_uac1.h create mode 100644 drivers/usb/gadget/udc-core.c create mode 100644 drivers/usb/gadget/usbstring.c create mode 100644 drivers/usb/gadget/uvc.h create mode 100644 drivers/usb/gadget/uvc_queue.c create mode 100644 drivers/usb/gadget/uvc_queue.h create mode 100644 drivers/usb/gadget/uvc_v4l2.c create mode 100644 drivers/usb/gadget/uvc_video.c create mode 100644 drivers/usb/gadget/webcam.c create mode 100644 drivers/usb/gadget/zero.c create mode 100644 drivers/usb/host/Kconfig create mode 100644 drivers/usb/host/Makefile create mode 100644 drivers/usb/host/bcma-hcd.c create mode 100644 drivers/usb/host/ehci-atmel.c create mode 100644 drivers/usb/host/ehci-dbg.c create mode 100644 drivers/usb/host/ehci-fsl.c create mode 100644 drivers/usb/host/ehci-fsl.h create mode 100644 drivers/usb/host/ehci-grlib.c create mode 100644 drivers/usb/host/ehci-h20ahb.c create mode 100644 drivers/usb/host/ehci-hcd.c create mode 100644 drivers/usb/host/ehci-hub.c create mode 100644 drivers/usb/host/ehci-mem.c create mode 100644 drivers/usb/host/ehci-msm.c create mode 100644 drivers/usb/host/ehci-mv.c create mode 100644 drivers/usb/host/ehci-mxc.c create mode 100644 drivers/usb/host/ehci-octeon.c create mode 100644 drivers/usb/host/ehci-omap.c create mode 100644 drivers/usb/host/ehci-orion.c create mode 100644 drivers/usb/host/ehci-pci.c create mode 100644 drivers/usb/host/ehci-platform.c create mode 100644 drivers/usb/host/ehci-pmcmsp.c create mode 100644 drivers/usb/host/ehci-ppc-of.c create mode 100644 drivers/usb/host/ehci-ps3.c create mode 100644 drivers/usb/host/ehci-q.c create mode 100644 drivers/usb/host/ehci-s5p.c create mode 100644 drivers/usb/host/ehci-sched.c create mode 100644 drivers/usb/host/ehci-sead3.c create mode 100644 drivers/usb/host/ehci-sh.c create mode 100644 drivers/usb/host/ehci-spear.c create mode 100644 drivers/usb/host/ehci-sysfs.c create mode 100644 drivers/usb/host/ehci-tegra.c create mode 100644 drivers/usb/host/ehci-tilegx.c create mode 100644 drivers/usb/host/ehci-timer.c create mode 100644 drivers/usb/host/ehci-w90x900.c create mode 100644 drivers/usb/host/ehci-xilinx-of.c create mode 100644 drivers/usb/host/ehci.h create mode 100644 drivers/usb/host/fhci-dbg.c create mode 100644 drivers/usb/host/fhci-hcd.c create mode 100644 drivers/usb/host/fhci-hub.c create mode 100644 drivers/usb/host/fhci-mem.c create mode 100644 drivers/usb/host/fhci-q.c create mode 100644 drivers/usb/host/fhci-sched.c create mode 100644 drivers/usb/host/fhci-tds.c create mode 100644 drivers/usb/host/fhci.h create mode 100644 drivers/usb/host/fsl-mph-dr-of.c create mode 100644 drivers/usb/host/hwa-hc.c create mode 100644 drivers/usb/host/imx21-dbg.c create mode 100644 drivers/usb/host/imx21-hcd.c create mode 100644 drivers/usb/host/imx21-hcd.h create mode 100644 drivers/usb/host/isp116x-hcd.c create mode 100644 drivers/usb/host/isp116x.h create mode 100644 drivers/usb/host/isp1362-hcd.c create mode 100644 drivers/usb/host/isp1362.h create mode 100644 drivers/usb/host/isp1760-hcd.c create mode 100644 drivers/usb/host/isp1760-hcd.h create mode 100644 drivers/usb/host/isp1760-if.c create mode 100644 drivers/usb/host/octeon2-common.c create mode 100644 drivers/usb/host/ohci-at91.c create mode 100644 drivers/usb/host/ohci-da8xx.c create mode 100644 drivers/usb/host/ohci-dbg.c create mode 100644 drivers/usb/host/ohci-ep93xx.c create mode 100644 drivers/usb/host/ohci-exynos.c create mode 100644 drivers/usb/host/ohci-hcd.c create mode 100644 drivers/usb/host/ohci-hub.c create mode 100644 drivers/usb/host/ohci-jz4740.c create mode 100644 drivers/usb/host/ohci-mem.c create mode 100644 drivers/usb/host/ohci-nxp.c create mode 100644 drivers/usb/host/ohci-octeon.c create mode 100644 drivers/usb/host/ohci-omap.c create mode 100644 drivers/usb/host/ohci-omap3.c create mode 100644 drivers/usb/host/ohci-pci.c create mode 100644 drivers/usb/host/ohci-platform.c create mode 100644 drivers/usb/host/ohci-ppc-of.c create mode 100644 drivers/usb/host/ohci-ps3.c create mode 100644 drivers/usb/host/ohci-pxa27x.c create mode 100644 drivers/usb/host/ohci-q.c create mode 100644 drivers/usb/host/ohci-s3c2410.c create mode 100644 drivers/usb/host/ohci-sa1111.c create mode 100644 drivers/usb/host/ohci-sm501.c create mode 100644 drivers/usb/host/ohci-spear.c create mode 100644 drivers/usb/host/ohci-tilegx.c create mode 100644 drivers/usb/host/ohci-tmio.c create mode 100644 drivers/usb/host/ohci.h create mode 100644 drivers/usb/host/oxu210hp-hcd.c create mode 100644 drivers/usb/host/oxu210hp.h create mode 100644 drivers/usb/host/pci-quirks.c create mode 100644 drivers/usb/host/pci-quirks.h create mode 100644 drivers/usb/host/r8a66597-hcd.c create mode 100644 drivers/usb/host/r8a66597.h create mode 100644 drivers/usb/host/sl811-hcd.c create mode 100644 drivers/usb/host/sl811.h create mode 100644 drivers/usb/host/sl811_cs.c create mode 100644 drivers/usb/host/ssb-hcd.c create mode 100644 drivers/usb/host/u132-hcd.c create mode 100644 drivers/usb/host/uhci-debug.c create mode 100644 drivers/usb/host/uhci-grlib.c create mode 100644 drivers/usb/host/uhci-hcd.c create mode 100644 drivers/usb/host/uhci-hcd.h create mode 100644 drivers/usb/host/uhci-hub.c create mode 100644 drivers/usb/host/uhci-pci.c create mode 100644 drivers/usb/host/uhci-platform.c create mode 100644 drivers/usb/host/uhci-q.c create mode 100644 drivers/usb/host/whci/Kbuild create mode 100644 drivers/usb/host/whci/asl.c create mode 100644 drivers/usb/host/whci/debug.c create mode 100644 drivers/usb/host/whci/hcd.c create mode 100644 drivers/usb/host/whci/hw.c create mode 100644 drivers/usb/host/whci/init.c create mode 100644 drivers/usb/host/whci/int.c create mode 100644 drivers/usb/host/whci/pzl.c create mode 100644 drivers/usb/host/whci/qset.c create mode 100644 drivers/usb/host/whci/whcd.h create mode 100644 drivers/usb/host/whci/whci-hc.h create mode 100644 drivers/usb/host/whci/wusb.c create mode 100644 drivers/usb/host/xhci-dbg.c create mode 100644 drivers/usb/host/xhci-ext-caps.h create mode 100644 drivers/usb/host/xhci-hub.c create mode 100644 drivers/usb/host/xhci-mem.c create mode 100644 drivers/usb/host/xhci-pci.c create mode 100644 drivers/usb/host/xhci-plat.c create mode 100644 drivers/usb/host/xhci-ring.c create mode 100644 drivers/usb/host/xhci.c create mode 100644 drivers/usb/host/xhci.h create mode 100644 drivers/usb/image/Kconfig create mode 100644 drivers/usb/image/Makefile create mode 100644 drivers/usb/image/mdc800.c create mode 100644 drivers/usb/image/microtek.c create mode 100644 drivers/usb/image/microtek.h create mode 100644 drivers/usb/misc/Kconfig create mode 100644 drivers/usb/misc/Makefile create mode 100644 drivers/usb/misc/adutux.c create mode 100644 drivers/usb/misc/appledisplay.c create mode 100644 drivers/usb/misc/cypress_cy7c63.c create mode 100644 drivers/usb/misc/cytherm.c create mode 100644 drivers/usb/misc/emi26.c create mode 100644 drivers/usb/misc/emi62.c create mode 100644 drivers/usb/misc/ezusb.c create mode 100644 drivers/usb/misc/ftdi-elan.c create mode 100644 drivers/usb/misc/idmouse.c create mode 100644 drivers/usb/misc/iowarrior.c create mode 100644 drivers/usb/misc/isight_firmware.c create mode 100644 drivers/usb/misc/ldusb.c create mode 100644 drivers/usb/misc/legousbtower.c create mode 100644 drivers/usb/misc/rio500.c create mode 100644 drivers/usb/misc/rio500_usb.h create mode 100644 drivers/usb/misc/sisusbvga/Kconfig create mode 100644 drivers/usb/misc/sisusbvga/Makefile create mode 100644 drivers/usb/misc/sisusbvga/sisusb.c create mode 100644 drivers/usb/misc/sisusbvga/sisusb.h create mode 100644 drivers/usb/misc/sisusbvga/sisusb_con.c create mode 100644 drivers/usb/misc/sisusbvga/sisusb_init.c create mode 100644 drivers/usb/misc/sisusbvga/sisusb_init.h create mode 100644 drivers/usb/misc/sisusbvga/sisusb_struct.h create mode 100644 drivers/usb/misc/trancevibrator.c create mode 100644 drivers/usb/misc/usb3503.c create mode 100644 drivers/usb/misc/usb_u132.h create mode 100644 drivers/usb/misc/usblcd.c create mode 100644 drivers/usb/misc/usbled.c create mode 100644 drivers/usb/misc/usbsevseg.c create mode 100644 drivers/usb/misc/usbtest.c create mode 100644 drivers/usb/misc/uss720.c create mode 100644 drivers/usb/misc/yurex.c create mode 100644 drivers/usb/mon/Kconfig create mode 100644 drivers/usb/mon/Makefile create mode 100644 drivers/usb/mon/mon_bin.c create mode 100644 drivers/usb/mon/mon_main.c create mode 100644 drivers/usb/mon/mon_stat.c create mode 100644 drivers/usb/mon/mon_text.c create mode 100644 drivers/usb/mon/usb_mon.h create mode 100644 drivers/usb/musb/Kconfig create mode 100644 drivers/usb/musb/Makefile create mode 100644 drivers/usb/musb/am35x.c create mode 100644 drivers/usb/musb/blackfin.c create mode 100644 drivers/usb/musb/blackfin.h create mode 100644 drivers/usb/musb/cppi_dma.c create mode 100644 drivers/usb/musb/cppi_dma.h create mode 100644 drivers/usb/musb/da8xx.c create mode 100644 drivers/usb/musb/davinci.c create mode 100644 drivers/usb/musb/davinci.h create mode 100644 drivers/usb/musb/musb_core.c create mode 100644 drivers/usb/musb/musb_core.h create mode 100644 drivers/usb/musb/musb_debug.h create mode 100644 drivers/usb/musb/musb_debugfs.c create mode 100644 drivers/usb/musb/musb_dma.h create mode 100644 drivers/usb/musb/musb_dsps.c create mode 100644 drivers/usb/musb/musb_gadget.c create mode 100644 drivers/usb/musb/musb_gadget.h create mode 100644 drivers/usb/musb/musb_gadget_ep0.c create mode 100644 drivers/usb/musb/musb_host.c create mode 100644 drivers/usb/musb/musb_host.h create mode 100644 drivers/usb/musb/musb_io.h create mode 100644 drivers/usb/musb/musb_regs.h create mode 100644 drivers/usb/musb/musb_virthub.c create mode 100644 drivers/usb/musb/musbhsdma.c create mode 100644 drivers/usb/musb/musbhsdma.h create mode 100644 drivers/usb/musb/omap2430.c create mode 100644 drivers/usb/musb/omap2430.h create mode 100644 drivers/usb/musb/tusb6010.c create mode 100644 drivers/usb/musb/tusb6010.h create mode 100644 drivers/usb/musb/tusb6010_omap.c create mode 100644 drivers/usb/musb/ux500.c create mode 100644 drivers/usb/musb/ux500_dma.c create mode 100644 drivers/usb/phy/Kconfig create mode 100644 drivers/usb/phy/Makefile create mode 100644 drivers/usb/phy/otg-wakelock.c create mode 100644 drivers/usb/phy/phy-ab8500-usb.c create mode 100644 drivers/usb/phy/phy-fsl-usb.c create mode 100644 drivers/usb/phy/phy-fsl-usb.h create mode 100644 drivers/usb/phy/phy-fsm-usb.c create mode 100644 drivers/usb/phy/phy-fsm-usb.h create mode 100644 drivers/usb/phy/phy-gpio-vbus-usb.c create mode 100644 drivers/usb/phy/phy-isp1301-omap.c create mode 100644 drivers/usb/phy/phy-isp1301.c create mode 100644 drivers/usb/phy/phy-msm-usb.c create mode 100644 drivers/usb/phy/phy-mv-u3d-usb.c create mode 100644 drivers/usb/phy/phy-mv-u3d-usb.h create mode 100644 drivers/usb/phy/phy-mv-usb.c create mode 100644 drivers/usb/phy/phy-mv-usb.h create mode 100644 drivers/usb/phy/phy-mxs-usb.c create mode 100644 drivers/usb/phy/phy-nop.c create mode 100644 drivers/usb/phy/phy-omap-control.c create mode 100644 drivers/usb/phy/phy-omap-usb2.c create mode 100644 drivers/usb/phy/phy-omap-usb3.c create mode 100644 drivers/usb/phy/phy-rcar-usb.c create mode 100644 drivers/usb/phy/phy-samsung-usb.c create mode 100644 drivers/usb/phy/phy-samsung-usb.h create mode 100644 drivers/usb/phy/phy-samsung-usb2.c create mode 100644 drivers/usb/phy/phy-samsung-usb3.c create mode 100644 drivers/usb/phy/phy-tegra-usb.c create mode 100644 drivers/usb/phy/phy-twl4030-usb.c create mode 100644 drivers/usb/phy/phy-twl6030-usb.c create mode 100644 drivers/usb/phy/phy-ulpi-viewport.c create mode 100644 drivers/usb/phy/phy-ulpi.c create mode 100644 drivers/usb/phy/phy.c create mode 100644 drivers/usb/renesas_usbhs/Kconfig create mode 100644 drivers/usb/renesas_usbhs/Makefile create mode 100644 drivers/usb/renesas_usbhs/common.c create mode 100644 drivers/usb/renesas_usbhs/common.h create mode 100644 drivers/usb/renesas_usbhs/fifo.c create mode 100644 drivers/usb/renesas_usbhs/fifo.h create mode 100644 drivers/usb/renesas_usbhs/mod.c create mode 100644 drivers/usb/renesas_usbhs/mod.h create mode 100644 drivers/usb/renesas_usbhs/mod_gadget.c create mode 100644 drivers/usb/renesas_usbhs/mod_host.c create mode 100644 drivers/usb/renesas_usbhs/pipe.c create mode 100644 drivers/usb/renesas_usbhs/pipe.h create mode 100644 drivers/usb/serial/Kconfig create mode 100644 drivers/usb/serial/Makefile create mode 100644 drivers/usb/serial/Makefile-keyspan_pda_fw create mode 100644 drivers/usb/serial/aircable.c create mode 100644 drivers/usb/serial/ark3116.c create mode 100644 drivers/usb/serial/belkin_sa.c create mode 100644 drivers/usb/serial/belkin_sa.h create mode 100644 drivers/usb/serial/bus.c create mode 100644 drivers/usb/serial/ch341.c create mode 100644 drivers/usb/serial/console.c create mode 100644 drivers/usb/serial/cp210x.c create mode 100644 drivers/usb/serial/cyberjack.c create mode 100644 drivers/usb/serial/cypress_m8.c create mode 100644 drivers/usb/serial/cypress_m8.h create mode 100644 drivers/usb/serial/digi_acceleport.c create mode 100644 drivers/usb/serial/empeg.c create mode 100644 drivers/usb/serial/ezusb_convert.pl create mode 100644 drivers/usb/serial/f81232.c create mode 100644 drivers/usb/serial/ftdi_sio.c create mode 100644 drivers/usb/serial/ftdi_sio.h create mode 100644 drivers/usb/serial/ftdi_sio_ids.h create mode 100644 drivers/usb/serial/funsoft.c create mode 100644 drivers/usb/serial/garmin_gps.c create mode 100644 drivers/usb/serial/generic.c create mode 100644 drivers/usb/serial/hp4x.c create mode 100644 drivers/usb/serial/io_16654.h create mode 100644 drivers/usb/serial/io_edgeport.c create mode 100644 drivers/usb/serial/io_edgeport.h create mode 100644 drivers/usb/serial/io_ionsp.h create mode 100644 drivers/usb/serial/io_tables.h create mode 100644 drivers/usb/serial/io_ti.c create mode 100644 drivers/usb/serial/io_ti.h create mode 100644 drivers/usb/serial/io_usbvend.h create mode 100644 drivers/usb/serial/ipaq.c create mode 100644 drivers/usb/serial/ipw.c create mode 100644 drivers/usb/serial/ir-usb.c create mode 100644 drivers/usb/serial/iuu_phoenix.c create mode 100644 drivers/usb/serial/iuu_phoenix.h create mode 100644 drivers/usb/serial/keyspan.c create mode 100644 drivers/usb/serial/keyspan.h create mode 100644 drivers/usb/serial/keyspan_pda.c create mode 100644 drivers/usb/serial/keyspan_usa26msg.h create mode 100644 drivers/usb/serial/keyspan_usa28msg.h create mode 100644 drivers/usb/serial/keyspan_usa49msg.h create mode 100644 drivers/usb/serial/keyspan_usa67msg.h create mode 100644 drivers/usb/serial/keyspan_usa90msg.h create mode 100644 drivers/usb/serial/kl5kusb105.c create mode 100644 drivers/usb/serial/kl5kusb105.h create mode 100644 drivers/usb/serial/kobil_sct.c create mode 100644 drivers/usb/serial/kobil_sct.h create mode 100644 drivers/usb/serial/mct_u232.c create mode 100644 drivers/usb/serial/mct_u232.h create mode 100644 drivers/usb/serial/metro-usb.c create mode 100644 drivers/usb/serial/mos7720.c create mode 100644 drivers/usb/serial/mos7840.c create mode 100644 drivers/usb/serial/moto_modem.c create mode 100644 drivers/usb/serial/navman.c create mode 100644 drivers/usb/serial/omninet.c create mode 100644 drivers/usb/serial/opticon.c create mode 100644 drivers/usb/serial/option.c create mode 100644 drivers/usb/serial/oti6858.c create mode 100644 drivers/usb/serial/oti6858.h create mode 100644 drivers/usb/serial/pl2303.c create mode 100644 drivers/usb/serial/pl2303.h create mode 100644 drivers/usb/serial/qcaux.c create mode 100644 drivers/usb/serial/qcserial.c create mode 100644 drivers/usb/serial/quatech2.c create mode 100644 drivers/usb/serial/safe_serial.c create mode 100644 drivers/usb/serial/siemens_mpi.c create mode 100644 drivers/usb/serial/sierra.c create mode 100644 drivers/usb/serial/spcp8x5.c create mode 100644 drivers/usb/serial/ssu100.c create mode 100644 drivers/usb/serial/symbolserial.c create mode 100644 drivers/usb/serial/ti_usb_3410_5052.c create mode 100644 drivers/usb/serial/ti_usb_3410_5052.h create mode 100644 drivers/usb/serial/usb-serial.c create mode 100644 drivers/usb/serial/usb-wwan.h create mode 100644 drivers/usb/serial/usb_debug.c create mode 100644 drivers/usb/serial/usb_wwan.c create mode 100644 drivers/usb/serial/visor.c create mode 100644 drivers/usb/serial/visor.h create mode 100644 drivers/usb/serial/vivopay-serial.c create mode 100644 drivers/usb/serial/whiteheat.c create mode 100644 drivers/usb/serial/whiteheat.h create mode 100644 drivers/usb/serial/wishbone-serial.c create mode 100644 drivers/usb/serial/xsens_mt.c create mode 100644 drivers/usb/serial/zio.c create mode 100644 drivers/usb/serial/zte_ev.c create mode 100644 drivers/usb/storage/Kconfig create mode 100644 drivers/usb/storage/Makefile create mode 100644 drivers/usb/storage/alauda.c create mode 100644 drivers/usb/storage/cypress_atacb.c create mode 100644 drivers/usb/storage/datafab.c create mode 100644 drivers/usb/storage/debug.c create mode 100644 drivers/usb/storage/debug.h create mode 100644 drivers/usb/storage/ene_ub6250.c create mode 100644 drivers/usb/storage/freecom.c create mode 100644 drivers/usb/storage/initializers.c create mode 100644 drivers/usb/storage/initializers.h create mode 100644 drivers/usb/storage/isd200.c create mode 100644 drivers/usb/storage/jumpshot.c create mode 100644 drivers/usb/storage/karma.c create mode 100644 drivers/usb/storage/onetouch.c create mode 100644 drivers/usb/storage/option_ms.c create mode 100644 drivers/usb/storage/option_ms.h create mode 100644 drivers/usb/storage/protocol.c create mode 100644 drivers/usb/storage/protocol.h create mode 100644 drivers/usb/storage/realtek_cr.c create mode 100644 drivers/usb/storage/scsiglue.c create mode 100644 drivers/usb/storage/scsiglue.h create mode 100644 drivers/usb/storage/sddr09.c create mode 100644 drivers/usb/storage/sddr55.c create mode 100644 drivers/usb/storage/shuttle_usbat.c create mode 100644 drivers/usb/storage/sierra_ms.c create mode 100644 drivers/usb/storage/sierra_ms.h create mode 100644 drivers/usb/storage/transport.c create mode 100644 drivers/usb/storage/transport.h create mode 100644 drivers/usb/storage/uas.c create mode 100644 drivers/usb/storage/unusual_alauda.h create mode 100644 drivers/usb/storage/unusual_cypress.h create mode 100644 drivers/usb/storage/unusual_datafab.h create mode 100644 drivers/usb/storage/unusual_devs.h create mode 100644 drivers/usb/storage/unusual_ene_ub6250.h create mode 100644 drivers/usb/storage/unusual_freecom.h create mode 100644 drivers/usb/storage/unusual_isd200.h create mode 100644 drivers/usb/storage/unusual_jumpshot.h create mode 100644 drivers/usb/storage/unusual_karma.h create mode 100644 drivers/usb/storage/unusual_onetouch.h create mode 100644 drivers/usb/storage/unusual_realtek.h create mode 100644 drivers/usb/storage/unusual_sddr09.h create mode 100644 drivers/usb/storage/unusual_sddr55.h create mode 100644 drivers/usb/storage/unusual_usbat.h create mode 100644 drivers/usb/storage/usb.c create mode 100644 drivers/usb/storage/usb.h create mode 100644 drivers/usb/storage/usual-tables.c create mode 100644 drivers/usb/usb-common.c create mode 100644 drivers/usb/usb-skeleton.c create mode 100644 drivers/usb/wusbcore/Kconfig create mode 100644 drivers/usb/wusbcore/Makefile create mode 100644 drivers/usb/wusbcore/cbaf.c create mode 100644 drivers/usb/wusbcore/crypto.c create mode 100644 drivers/usb/wusbcore/dev-sysfs.c create mode 100644 drivers/usb/wusbcore/devconnect.c create mode 100644 drivers/usb/wusbcore/mmc.c create mode 100644 drivers/usb/wusbcore/pal.c create mode 100644 drivers/usb/wusbcore/reservation.c create mode 100644 drivers/usb/wusbcore/rh.c create mode 100644 drivers/usb/wusbcore/security.c create mode 100644 drivers/usb/wusbcore/wa-hc.c create mode 100644 drivers/usb/wusbcore/wa-hc.h create mode 100644 drivers/usb/wusbcore/wa-nep.c create mode 100644 drivers/usb/wusbcore/wa-rpipe.c create mode 100644 drivers/usb/wusbcore/wa-xfer.c create mode 100644 drivers/usb/wusbcore/wusbhc.c create mode 100644 drivers/usb/wusbcore/wusbhc.h (limited to 'drivers/usb') diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig new file mode 100644 index 00000000..73f62caa --- /dev/null +++ b/drivers/usb/Kconfig @@ -0,0 +1,141 @@ +# +# USB device configuration +# + +# These are unused now, remove them once they are no longer selected +config USB_ARCH_HAS_OHCI + bool + +config USB_ARCH_HAS_EHCI + bool + +config USB_ARCH_HAS_XHCI + bool + +menuconfig USB_SUPPORT + bool "USB support" + depends on HAS_IOMEM + default y + ---help--- + This option adds core support for Universal Serial Bus (USB). + You will also need drivers from the following menu to make use of it. + +if USB_SUPPORT + +config USB_COMMON + tristate + default y + depends on USB || USB_GADGET + +config USB_ARCH_HAS_HCD + def_bool y + +# ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface. +config USB + tristate "Support for Host-side USB" + depends on USB_ARCH_HAS_HCD + select NLS # for UTF-8 strings + ---help--- + Universal Serial Bus (USB) is a specification for a serial bus + subsystem which offers higher speeds and more features than the + traditional PC serial port. The bus supplies power to peripherals + and allows for hot swapping. Up to 127 USB peripherals can be + connected to a single USB host in a tree structure. + + The USB host is the root of the tree, the peripherals are the + leaves and the inner nodes are special USB devices called hubs. + Most PCs now have USB host ports, used to connect peripherals + such as scanners, keyboards, mice, modems, cameras, disks, + flash memory, network links, and printers to the PC. + + Say Y here if your computer has a host-side USB port and you want + to use USB devices. You then need to say Y to at least one of the + Host Controller Driver (HCD) options below. Choose a USB 1.1 + controller, such as "UHCI HCD support" or "OHCI HCD support", + and "EHCI HCD (USB 2.0) support" except for older systems that + do not have USB 2.0 support. It doesn't normally hurt to select + them all if you are not certain. + + If your system has a device-side USB port, used in the peripheral + side of the USB protocol, see the "USB Gadget" framework instead. + + After choosing your HCD, then select drivers for the USB peripherals + you'll be using. You may want to check out the information provided + in and especially the links given in + . + + To compile this driver as a module, choose M here: the + module will be called usbcore. + +if USB + +source "drivers/usb/core/Kconfig" + +source "drivers/usb/mon/Kconfig" + +source "drivers/usb/wusbcore/Kconfig" + +source "drivers/usb/host/Kconfig" + +source "drivers/usb/musb/Kconfig" + +source "drivers/usb/renesas_usbhs/Kconfig" + +source "drivers/usb/class/Kconfig" + +source "drivers/usb/storage/Kconfig" + +source "drivers/usb/image/Kconfig" + +endif + +source "drivers/usb/dwc3/Kconfig" + +source "drivers/usb/chipidea/Kconfig" + +comment "USB port drivers" + +if USB + +config USB_USS720 + tristate "USS720 parport driver" + depends on PARPORT + select PARPORT_NOT_PC + ---help--- + This driver is for USB parallel port adapters that use the Lucent + Technologies USS-720 chip. These cables are plugged into your USB + port and provide USB compatibility to peripherals designed with + parallel port interfaces. + + The chip has two modes: automatic mode and manual mode. In automatic + mode, it looks to the computer like a standard USB printer. Only + printers may be connected to the USS-720 in this mode. The generic + USB printer driver ("USB Printer support", above) may be used in + that mode, and you can say N here if you want to use the chip only + in this mode. + + Manual mode is not limited to printers, any parallel port + device should work. This driver utilizes manual mode. + Note however that some operations are three orders of magnitude + slower than on a PCI/ISA Parallel Port, so timing critical + applications might not work. + + Say Y here if you own an USS-720 USB->Parport cable and intend to + connect anything other than a printer to it. + + To compile this driver as a module, choose M here: the + module will be called uss720. + +source "drivers/usb/serial/Kconfig" + +source "drivers/usb/misc/Kconfig" + +source "drivers/usb/atm/Kconfig" + +endif # USB + +source "drivers/usb/phy/Kconfig" + +source "drivers/usb/gadget/Kconfig" + +endif # USB_SUPPORT diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile new file mode 100644 index 00000000..c41feba8 --- /dev/null +++ b/drivers/usb/Makefile @@ -0,0 +1,58 @@ +# +# Makefile for the kernel USB device drivers. +# + +# Object files in subdirectories + +obj-$(CONFIG_USB) += core/ + +obj-$(CONFIG_USB_DWC3) += dwc3/ + +obj-$(CONFIG_USB_MON) += mon/ + +obj-$(CONFIG_PCI) += host/ +obj-$(CONFIG_USB_EHCI_HCD) += host/ +obj-$(CONFIG_USB_ISP116X_HCD) += host/ +obj-$(CONFIG_USB_OHCI_HCD) += host/ +obj-$(CONFIG_USB_UHCI_HCD) += host/ +obj-$(CONFIG_USB_FHCI_HCD) += host/ +obj-$(CONFIG_USB_XHCI_HCD) += host/ +obj-$(CONFIG_USB_SL811_HCD) += host/ +obj-$(CONFIG_USB_ISP1362_HCD) += host/ +obj-$(CONFIG_USB_U132_HCD) += host/ +obj-$(CONFIG_USB_R8A66597_HCD) += host/ +obj-$(CONFIG_USB_HWA_HCD) += host/ +obj-$(CONFIG_USB_ISP1760_HCD) += host/ +obj-$(CONFIG_USB_IMX21_HCD) += host/ +obj-$(CONFIG_USB_FSL_MPH_DR_OF) += host/ + +obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ + +obj-$(CONFIG_USB_WUSB) += wusbcore/ + +obj-$(CONFIG_USB_ACM) += class/ +obj-$(CONFIG_USB_PRINTER) += class/ +obj-$(CONFIG_USB_WDM) += class/ +obj-$(CONFIG_USB_TMC) += class/ + +obj-$(CONFIG_USB_STORAGE) += storage/ +obj-$(CONFIG_USB) += storage/ + +obj-$(CONFIG_USB_MDC800) += image/ +obj-$(CONFIG_USB_MICROTEK) += image/ + +obj-$(CONFIG_USB_SERIAL) += serial/ + +obj-$(CONFIG_USB) += misc/ +obj-$(CONFIG_USB_PHY) += phy/ +obj-$(CONFIG_EARLY_PRINTK_DBGP) += early/ + +obj-$(CONFIG_USB_ATM) += atm/ +obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ + +obj-$(CONFIG_USB_MUSB_HDRC) += musb/ +obj-$(CONFIG_USB_CHIPIDEA) += chipidea/ +obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs/ +obj-$(CONFIG_USB_GADGET) += gadget/ + +obj-$(CONFIG_USB_COMMON) += usb-common.o diff --git a/drivers/usb/README b/drivers/usb/README new file mode 100644 index 00000000..284f46b3 --- /dev/null +++ b/drivers/usb/README @@ -0,0 +1,54 @@ +To understand all the Linux-USB framework, you'll use these resources: + + * This source code. This is necessarily an evolving work, and + includes kerneldoc that should help you get a current overview. + ("make pdfdocs", and then look at "usb.pdf" for host side and + "gadget.pdf" for peripheral side.) Also, Documentation/usb has + more information. + + * The USB 2.0 specification (from www.usb.org), with supplements + such as those for USB OTG and the various device classes. + The USB specification has a good overview chapter, and USB + peripherals conform to the widely known "Chapter 9". + + * Chip specifications for USB controllers. Examples include + host controllers (on PCs, servers, and more); peripheral + controllers (in devices with Linux firmware, like printers or + cell phones); and hard-wired peripherals like Ethernet adapters. + + * Specifications for other protocols implemented by USB peripheral + functions. Some are vendor-specific; others are vendor-neutral + but just standardized outside of the www.usb.org team. + +Here is a list of what each subdirectory here is, and what is contained in +them. + +core/ - This is for the core USB host code, including the + usbfs files and the hub class driver ("khubd"). + +host/ - This is for USB host controller drivers. This + includes UHCI, OHCI, EHCI, and others that might + be used with more specialized "embedded" systems. + +gadget/ - This is for USB peripheral controller drivers and + the various gadget drivers which talk to them. + + +Individual USB driver directories. A new driver should be added to the +first subdirectory in the list below that it fits into. + +image/ - This is for still image drivers, like scanners or + digital cameras. +../input/ - This is for any driver that uses the input subsystem, + like keyboard, mice, touchscreens, tablets, etc. +../media/ - This is for multimedia drivers, like video cameras, + radios, and any other drivers that talk to the v4l + subsystem. +../net/ - This is for network drivers. +serial/ - This is for USB to serial drivers. +storage/ - This is for USB mass-storage drivers. +class/ - This is for all USB device drivers that do not fit + into any of the above categories, and work for a range + of USB Class specified devices. +misc/ - This is for all USB device drivers that do not fit + into any of the above categories. diff --git a/drivers/usb/atm/Kconfig b/drivers/usb/atm/Kconfig new file mode 100644 index 00000000..0f922942 --- /dev/null +++ b/drivers/usb/atm/Kconfig @@ -0,0 +1,68 @@ +# +# USB/ATM DSL configuration +# + +menuconfig USB_ATM + tristate "USB DSL modem support" + depends on ATM + select CRC32 + default n + help + Say Y here if you want to connect a USB Digital Subscriber Line (DSL) + modem to your computer's USB port. You will then need to choose your + modem from the list below. + + To compile this driver as a module, choose M here: the + module will be called usbatm. + +if USB_ATM + +config USB_SPEEDTOUCH + tristate "Speedtouch USB support" + select FW_LOADER + help + Say Y here if you have an SpeedTouch USB or SpeedTouch 330 + modem. In order to use your modem you will need to install the + two parts of the firmware, extracted by the user space tools; see + for details. + + To compile this driver as a module, choose M here: the + module will be called speedtch. + +config USB_CXACRU + tristate "Conexant AccessRunner USB support" + select FW_LOADER + help + Say Y here if you have an ADSL USB modem based on the Conexant + AccessRunner chipset. In order to use your modem you will need to + install the firmware, extracted by the user space tools; see + for details. + + To compile this driver as a module, choose M here: the + module will be called cxacru. + +config USB_UEAGLEATM + tristate "ADI 930 and eagle USB DSL modem" + select FW_LOADER + help + Say Y here if you have an ADSL USB modem based on the ADI 930 + or eagle chipset. In order to use your modem you will need to + install firmwares and CMV (Command Management Variables); see + for details. + + To compile this driver as a module, choose M here: the + module will be called ueagle-atm. + +config USB_XUSBATM + tristate "Other USB DSL modem support" + help + Say Y here if you have a DSL USB modem not explicitly supported by + another USB DSL drivers. In order to use your modem you will need to + pass the vendor ID, product ID, and endpoint numbers for transmission + and reception as module parameters. You may need to initialize + the modem using a user space utility (a firmware loader for example). + + To compile this driver as a module, choose M here: the + module will be called xusbatm. + +endif # USB_ATM diff --git a/drivers/usb/atm/Makefile b/drivers/usb/atm/Makefile new file mode 100644 index 00000000..a5d792ec --- /dev/null +++ b/drivers/usb/atm/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for USB ATM/xDSL drivers +# + +ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG + +obj-$(CONFIG_USB_CXACRU) += cxacru.o +obj-$(CONFIG_USB_SPEEDTOUCH) += speedtch.o +obj-$(CONFIG_USB_UEAGLEATM) += ueagle-atm.o +obj-$(CONFIG_USB_ATM) += usbatm.o +obj-$(CONFIG_USB_XUSBATM) += xusbatm.o diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c new file mode 100644 index 00000000..8a7eb772 --- /dev/null +++ b/drivers/usb/atm/cxacru.c @@ -0,0 +1,1381 @@ +/****************************************************************************** + * cxacru.c - driver for USB ADSL modems based on + * Conexant AccessRunner chipset + * + * Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan + * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) + * Copyright (C) 2007 Simon Arlott + * Copyright (C) 2009 Simon Arlott + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +/* + * Credit is due for Josep Comas, who created the original patch to speedtch.c + * to support the different padding used by the AccessRunner (now generalized + * into usbatm), and the userspace firmware loading utility. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbatm.h" + +#define DRIVER_AUTHOR "Roman Kagan, David Woodhouse, Duncan Sands, Simon Arlott" +#define DRIVER_VERSION "0.4" +#define DRIVER_DESC "Conexant AccessRunner ADSL USB modem driver" + +static const char cxacru_driver_name[] = "cxacru"; + +#define CXACRU_EP_CMD 0x01 /* Bulk/interrupt in/out */ +#define CXACRU_EP_DATA 0x02 /* Bulk in/out */ + +#define CMD_PACKET_SIZE 64 /* Should be maxpacket(ep)? */ +#define CMD_MAX_CONFIG ((CMD_PACKET_SIZE / 4 - 1) / 2) + +/* Addresses */ +#define PLLFCLK_ADDR 0x00350068 +#define PLLBCLK_ADDR 0x0035006c +#define SDRAMEN_ADDR 0x00350010 +#define FW_ADDR 0x00801000 +#define BR_ADDR 0x00180600 +#define SIG_ADDR 0x00180500 +#define BR_STACK_ADDR 0x00187f10 + +/* Values */ +#define SDRAM_ENA 0x1 + +#define CMD_TIMEOUT 2000 /* msecs */ +#define POLL_INTERVAL 1 /* secs */ + +/* commands for interaction with the modem through the control channel before + * firmware is loaded */ +enum cxacru_fw_request { + FW_CMD_ERR, + FW_GET_VER, + FW_READ_MEM, + FW_WRITE_MEM, + FW_RMW_MEM, + FW_CHECKSUM_MEM, + FW_GOTO_MEM, +}; + +/* commands for interaction with the modem through the control channel once + * firmware is loaded */ +enum cxacru_cm_request { + CM_REQUEST_UNDEFINED = 0x80, + CM_REQUEST_TEST, + CM_REQUEST_CHIP_GET_MAC_ADDRESS, + CM_REQUEST_CHIP_GET_DP_VERSIONS, + CM_REQUEST_CHIP_ADSL_LINE_START, + CM_REQUEST_CHIP_ADSL_LINE_STOP, + CM_REQUEST_CHIP_ADSL_LINE_GET_STATUS, + CM_REQUEST_CHIP_ADSL_LINE_GET_SPEED, + CM_REQUEST_CARD_INFO_GET, + CM_REQUEST_CARD_DATA_GET, + CM_REQUEST_CARD_DATA_SET, + CM_REQUEST_COMMAND_HW_IO, + CM_REQUEST_INTERFACE_HW_IO, + CM_REQUEST_CARD_SERIAL_DATA_PATH_GET, + CM_REQUEST_CARD_SERIAL_DATA_PATH_SET, + CM_REQUEST_CARD_CONTROLLER_VERSION_GET, + CM_REQUEST_CARD_GET_STATUS, + CM_REQUEST_CARD_GET_MAC_ADDRESS, + CM_REQUEST_CARD_GET_DATA_LINK_STATUS, + CM_REQUEST_MAX, +}; + +/* commands for interaction with the flash memory + * + * read: response is the contents of the first 60 bytes of flash memory + * write: request contains the 60 bytes of data to write to flash memory + * response is the contents of the first 60 bytes of flash memory + * + * layout: PP PP VV VV MM MM MM MM MM MM ?? ?? SS SS SS SS SS SS SS SS + * SS SS SS SS SS SS SS SS 00 00 00 00 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * + * P: le16 USB Product ID + * V: le16 USB Vendor ID + * M: be48 MAC Address + * S: le16 ASCII Serial Number + */ +enum cxacru_cm_flash { + CM_FLASH_READ = 0xa1, + CM_FLASH_WRITE = 0xa2 +}; + +/* reply codes to the commands above */ +enum cxacru_cm_status { + CM_STATUS_UNDEFINED, + CM_STATUS_SUCCESS, + CM_STATUS_ERROR, + CM_STATUS_UNSUPPORTED, + CM_STATUS_UNIMPLEMENTED, + CM_STATUS_PARAMETER_ERROR, + CM_STATUS_DBG_LOOPBACK, + CM_STATUS_MAX, +}; + +/* indices into CARD_INFO_GET return array */ +enum cxacru_info_idx { + CXINF_DOWNSTREAM_RATE, + CXINF_UPSTREAM_RATE, + CXINF_LINK_STATUS, + CXINF_LINE_STATUS, + CXINF_MAC_ADDRESS_HIGH, + CXINF_MAC_ADDRESS_LOW, + CXINF_UPSTREAM_SNR_MARGIN, + CXINF_DOWNSTREAM_SNR_MARGIN, + CXINF_UPSTREAM_ATTENUATION, + CXINF_DOWNSTREAM_ATTENUATION, + CXINF_TRANSMITTER_POWER, + CXINF_UPSTREAM_BITS_PER_FRAME, + CXINF_DOWNSTREAM_BITS_PER_FRAME, + CXINF_STARTUP_ATTEMPTS, + CXINF_UPSTREAM_CRC_ERRORS, + CXINF_DOWNSTREAM_CRC_ERRORS, + CXINF_UPSTREAM_FEC_ERRORS, + CXINF_DOWNSTREAM_FEC_ERRORS, + CXINF_UPSTREAM_HEC_ERRORS, + CXINF_DOWNSTREAM_HEC_ERRORS, + CXINF_LINE_STARTABLE, + CXINF_MODULATION, + CXINF_ADSL_HEADEND, + CXINF_ADSL_HEADEND_ENVIRONMENT, + CXINF_CONTROLLER_VERSION, + /* dunno what the missing two mean */ + CXINF_MAX = 0x1c, +}; + +enum cxacru_poll_state { + CXPOLL_STOPPING, + CXPOLL_STOPPED, + CXPOLL_POLLING, + CXPOLL_SHUTDOWN +}; + +struct cxacru_modem_type { + u32 pll_f_clk; + u32 pll_b_clk; + int boot_rom_patch; +}; + +struct cxacru_data { + struct usbatm_data *usbatm; + + const struct cxacru_modem_type *modem_type; + + int line_status; + struct mutex adsl_state_serialize; + int adsl_status; + struct delayed_work poll_work; + u32 card_info[CXINF_MAX]; + struct mutex poll_state_serialize; + enum cxacru_poll_state poll_state; + + /* contol handles */ + struct mutex cm_serialize; + u8 *rcv_buf; + u8 *snd_buf; + struct urb *rcv_urb; + struct urb *snd_urb; + struct completion rcv_done; + struct completion snd_done; +}; + +static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, + u8 *wdata, int wsize, u8 *rdata, int rsize); +static void cxacru_poll_status(struct work_struct *work); + +/* Card info exported through sysfs */ +#define CXACRU__ATTR_INIT(_name) \ +static DEVICE_ATTR(_name, S_IRUGO, cxacru_sysfs_show_##_name, NULL) + +#define CXACRU_CMD_INIT(_name) \ +static DEVICE_ATTR(_name, S_IWUSR | S_IRUGO, \ + cxacru_sysfs_show_##_name, cxacru_sysfs_store_##_name) + +#define CXACRU_SET_INIT(_name) \ +static DEVICE_ATTR(_name, S_IWUSR, \ + NULL, cxacru_sysfs_store_##_name) + +#define CXACRU_ATTR_INIT(_value, _type, _name) \ +static ssize_t cxacru_sysfs_show_##_name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct cxacru_data *instance = to_usbatm_driver_data(\ + to_usb_interface(dev)); \ +\ + if (instance == NULL) \ + return -ENODEV; \ +\ + return cxacru_sysfs_showattr_##_type(instance->card_info[_value], buf); \ +} \ +CXACRU__ATTR_INIT(_name) + +#define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name) +#define CXACRU_CMD_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) +#define CXACRU_SET_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) +#define CXACRU__ATTR_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) + +#define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name) +#define CXACRU_CMD_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) +#define CXACRU_SET_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) +#define CXACRU__ATTR_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) + +static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", value); +} + +static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +static ssize_t cxacru_sysfs_showattr_dB(s16 value, char *buf) +{ + if (likely(value >= 0)) { + return snprintf(buf, PAGE_SIZE, "%u.%02u\n", + value / 100, value % 100); + } else { + value = -value; + return snprintf(buf, PAGE_SIZE, "-%u.%02u\n", + value / 100, value % 100); + } +} + +static ssize_t cxacru_sysfs_showattr_bool(u32 value, char *buf) +{ + static char *str[] = { "no", "yes" }; + if (unlikely(value >= ARRAY_SIZE(str))) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf) +{ + static char *str[] = { NULL, "not connected", "connected", "lost" }; + if (unlikely(value >= ARRAY_SIZE(str) || str[value] == NULL)) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf) +{ + static char *str[] = { "down", "attempting to activate", + "training", "channel analysis", "exchange", "up", + "waiting", "initialising" + }; + if (unlikely(value >= ARRAY_SIZE(str))) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf) +{ + static char *str[] = { + "", + "ANSI T1.413", + "ITU-T G.992.1 (G.DMT)", + "ITU-T G.992.2 (G.LITE)" + }; + if (unlikely(value >= ARRAY_SIZE(str))) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +/* + * This could use MAC_ADDRESS_HIGH and MAC_ADDRESS_LOW, but since + * this data is already in atm_dev there's no point. + * + * MAC_ADDRESS_HIGH = 0x????5544 + * MAC_ADDRESS_LOW = 0x33221100 + * Where 00-55 are bytes 0-5 of the MAC. + */ +static ssize_t cxacru_sysfs_show_mac_address(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cxacru_data *instance = to_usbatm_driver_data( + to_usb_interface(dev)); + + if (instance == NULL || instance->usbatm->atm_dev == NULL) + return -ENODEV; + + return snprintf(buf, PAGE_SIZE, "%pM\n", + instance->usbatm->atm_dev->esi); +} + +static ssize_t cxacru_sysfs_show_adsl_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + static char *str[] = { "running", "stopped" }; + struct cxacru_data *instance = to_usbatm_driver_data( + to_usb_interface(dev)); + u32 value; + + if (instance == NULL) + return -ENODEV; + + value = instance->card_info[CXINF_LINE_STARTABLE]; + if (unlikely(value >= ARRAY_SIZE(str))) + return snprintf(buf, PAGE_SIZE, "%u\n", value); + return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); +} + +static ssize_t cxacru_sysfs_store_adsl_state(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cxacru_data *instance = to_usbatm_driver_data( + to_usb_interface(dev)); + int ret; + int poll = -1; + char str_cmd[8]; + int len = strlen(buf); + + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + ret = sscanf(buf, "%7s", str_cmd); + if (ret != 1) + return -EINVAL; + ret = 0; + + if (instance == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&instance->adsl_state_serialize)) + return -ERESTARTSYS; + + if (!strcmp(str_cmd, "stop") || !strcmp(str_cmd, "restart")) { + ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0, NULL, 0); + if (ret < 0) { + atm_err(instance->usbatm, "change adsl state:" + " CHIP_ADSL_LINE_STOP returned %d\n", ret); + + ret = -EIO; + } else { + ret = len; + poll = CXPOLL_STOPPED; + } + } + + /* Line status is only updated every second + * and the device appears to only react to + * START/STOP every second too. Wait 1.5s to + * be sure that restart will have an effect. */ + if (!strcmp(str_cmd, "restart")) + msleep(1500); + + if (!strcmp(str_cmd, "start") || !strcmp(str_cmd, "restart")) { + ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); + if (ret < 0) { + atm_err(instance->usbatm, "change adsl state:" + " CHIP_ADSL_LINE_START returned %d\n", ret); + + ret = -EIO; + } else { + ret = len; + poll = CXPOLL_POLLING; + } + } + + if (!strcmp(str_cmd, "poll")) { + ret = len; + poll = CXPOLL_POLLING; + } + + if (ret == 0) { + ret = -EINVAL; + poll = -1; + } + + if (poll == CXPOLL_POLLING) { + mutex_lock(&instance->poll_state_serialize); + switch (instance->poll_state) { + case CXPOLL_STOPPED: + /* start polling */ + instance->poll_state = CXPOLL_POLLING; + break; + + case CXPOLL_STOPPING: + /* abort stop request */ + instance->poll_state = CXPOLL_POLLING; + case CXPOLL_POLLING: + case CXPOLL_SHUTDOWN: + /* don't start polling */ + poll = -1; + } + mutex_unlock(&instance->poll_state_serialize); + } else if (poll == CXPOLL_STOPPED) { + mutex_lock(&instance->poll_state_serialize); + /* request stop */ + if (instance->poll_state == CXPOLL_POLLING) + instance->poll_state = CXPOLL_STOPPING; + mutex_unlock(&instance->poll_state_serialize); + } + + mutex_unlock(&instance->adsl_state_serialize); + + if (poll == CXPOLL_POLLING) + cxacru_poll_status(&instance->poll_work.work); + + return ret; +} + +/* CM_REQUEST_CARD_DATA_GET times out, so no show attribute */ + +static ssize_t cxacru_sysfs_store_adsl_config(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cxacru_data *instance = to_usbatm_driver_data( + to_usb_interface(dev)); + int len = strlen(buf); + int ret, pos, num; + __le32 data[CMD_PACKET_SIZE / 4]; + + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (instance == NULL) + return -ENODEV; + + pos = 0; + num = 0; + while (pos < len) { + int tmp; + u32 index; + u32 value; + + ret = sscanf(buf + pos, "%x=%x%n", &index, &value, &tmp); + if (ret < 2) + return -EINVAL; + if (index < 0 || index > 0x7f) + return -EINVAL; + pos += tmp; + + /* skip trailing newline */ + if (buf[pos] == '\n' && pos == len-1) + pos++; + + data[num * 2 + 1] = cpu_to_le32(index); + data[num * 2 + 2] = cpu_to_le32(value); + num++; + + /* send config values when data buffer is full + * or no more data + */ + if (pos >= len || num >= CMD_MAX_CONFIG) { + char log[CMD_MAX_CONFIG * 12 + 1]; /* %02x=%08x */ + + data[0] = cpu_to_le32(num); + ret = cxacru_cm(instance, CM_REQUEST_CARD_DATA_SET, + (u8 *) data, 4 + num * 8, NULL, 0); + if (ret < 0) { + atm_err(instance->usbatm, + "set card data returned %d\n", ret); + return -EIO; + } + + for (tmp = 0; tmp < num; tmp++) + snprintf(log + tmp*12, 13, " %02x=%08x", + le32_to_cpu(data[tmp * 2 + 1]), + le32_to_cpu(data[tmp * 2 + 2])); + atm_info(instance->usbatm, "config%s\n", log); + num = 0; + } + } + + return len; +} + +/* + * All device attributes are included in CXACRU_ALL_FILES + * so that the same list can be used multiple times: + * INIT (define the device attributes) + * CREATE (create all the device files) + * REMOVE (remove all the device files) + * + * With the last two being defined as needed in the functions + * they are used in before calling CXACRU_ALL_FILES() + */ +#define CXACRU_ALL_FILES(_action) \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_RATE, u32, downstream_rate); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_RATE, u32, upstream_rate); \ +CXACRU_ATTR_##_action(CXINF_LINK_STATUS, LINK, link_status); \ +CXACRU_ATTR_##_action(CXINF_LINE_STATUS, LINE, line_status); \ +CXACRU__ATTR_##_action( mac_address); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_SNR_MARGIN, dB, upstream_snr_margin); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_SNR_MARGIN, dB, downstream_snr_margin); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_ATTENUATION, dB, upstream_attenuation); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_ATTENUATION, dB, downstream_attenuation); \ +CXACRU_ATTR_##_action(CXINF_TRANSMITTER_POWER, s8, transmitter_power); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_BITS_PER_FRAME, u32, upstream_bits_per_frame); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_BITS_PER_FRAME, u32, downstream_bits_per_frame); \ +CXACRU_ATTR_##_action(CXINF_STARTUP_ATTEMPTS, u32, startup_attempts); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_CRC_ERRORS, u32, upstream_crc_errors); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_CRC_ERRORS, u32, downstream_crc_errors); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_FEC_ERRORS, u32, upstream_fec_errors); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_FEC_ERRORS, u32, downstream_fec_errors); \ +CXACRU_ATTR_##_action(CXINF_UPSTREAM_HEC_ERRORS, u32, upstream_hec_errors); \ +CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_HEC_ERRORS, u32, downstream_hec_errors); \ +CXACRU_ATTR_##_action(CXINF_LINE_STARTABLE, bool, line_startable); \ +CXACRU_ATTR_##_action(CXINF_MODULATION, MODU, modulation); \ +CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND, u32, adsl_headend); \ +CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT, u32, adsl_headend_environment); \ +CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION, u32, adsl_controller_version); \ +CXACRU_CMD_##_action( adsl_state); \ +CXACRU_SET_##_action( adsl_config); + +CXACRU_ALL_FILES(INIT); + +/* the following three functions are stolen from drivers/usb/core/message.c */ +static void cxacru_blocking_completion(struct urb *urb) +{ + complete(urb->context); +} + +static void cxacru_timeout_kill(unsigned long data) +{ + usb_unlink_urb((struct urb *) data); +} + +static int cxacru_start_wait_urb(struct urb *urb, struct completion *done, + int *actual_length) +{ + struct timer_list timer; + + init_timer(&timer); + timer.expires = jiffies + msecs_to_jiffies(CMD_TIMEOUT); + timer.data = (unsigned long) urb; + timer.function = cxacru_timeout_kill; + add_timer(&timer); + wait_for_completion(done); + del_timer_sync(&timer); + + if (actual_length) + *actual_length = urb->actual_length; + return urb->status; /* must read status after completion */ +} + +static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, + u8 *wdata, int wsize, u8 *rdata, int rsize) +{ + int ret, actlen; + int offb, offd; + const int stride = CMD_PACKET_SIZE - 4; + u8 *wbuf = instance->snd_buf; + u8 *rbuf = instance->rcv_buf; + int wbuflen = ((wsize - 1) / stride + 1) * CMD_PACKET_SIZE; + int rbuflen = ((rsize - 1) / stride + 1) * CMD_PACKET_SIZE; + + if (wbuflen > PAGE_SIZE || rbuflen > PAGE_SIZE) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "requested transfer size too large (%d, %d)\n", + wbuflen, rbuflen); + ret = -ENOMEM; + goto err; + } + + mutex_lock(&instance->cm_serialize); + + /* submit reading urb before the writing one */ + init_completion(&instance->rcv_done); + ret = usb_submit_urb(instance->rcv_urb, GFP_KERNEL); + if (ret < 0) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "submit of read urb for cm %#x failed (%d)\n", + cm, ret); + goto fail; + } + + memset(wbuf, 0, wbuflen); + /* handle wsize == 0 */ + wbuf[0] = cm; + for (offb = offd = 0; offd < wsize; offd += stride, offb += CMD_PACKET_SIZE) { + wbuf[offb] = cm; + memcpy(wbuf + offb + 4, wdata + offd, min_t(int, stride, wsize - offd)); + } + + instance->snd_urb->transfer_buffer_length = wbuflen; + init_completion(&instance->snd_done); + ret = usb_submit_urb(instance->snd_urb, GFP_KERNEL); + if (ret < 0) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "submit of write urb for cm %#x failed (%d)\n", + cm, ret); + goto fail; + } + + ret = cxacru_start_wait_urb(instance->snd_urb, &instance->snd_done, NULL); + if (ret < 0) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "send of cm %#x failed (%d)\n", cm, ret); + goto fail; + } + + ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done, &actlen); + if (ret < 0) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "receive of cm %#x failed (%d)\n", cm, ret); + goto fail; + } + if (actlen % CMD_PACKET_SIZE || !actlen) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "invalid response length to cm %#x: %d\n", + cm, actlen); + ret = -EIO; + goto fail; + } + + /* check the return status and copy the data to the output buffer, if needed */ + for (offb = offd = 0; offd < rsize && offb < actlen; offb += CMD_PACKET_SIZE) { + if (rbuf[offb] != cm) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "wrong cm %#x in response to cm %#x\n", + rbuf[offb], cm); + ret = -EIO; + goto fail; + } + if (rbuf[offb + 1] != CM_STATUS_SUCCESS) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "response to cm %#x failed: %#x\n", + cm, rbuf[offb + 1]); + ret = -EIO; + goto fail; + } + if (offd >= rsize) + break; + memcpy(rdata + offd, rbuf + offb + 4, min_t(int, stride, rsize - offd)); + offd += stride; + } + + ret = offd; + usb_dbg(instance->usbatm, "cm %#x\n", cm); +fail: + mutex_unlock(&instance->cm_serialize); +err: + return ret; +} + +static int cxacru_cm_get_array(struct cxacru_data *instance, enum cxacru_cm_request cm, + u32 *data, int size) +{ + int ret, len; + __le32 *buf; + int offb; + unsigned int offd; + const int stride = CMD_PACKET_SIZE / (4 * 2) - 1; + int buflen = ((size - 1) / stride + 1 + size * 2) * 4; + + buf = kmalloc(buflen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = cxacru_cm(instance, cm, NULL, 0, (u8 *) buf, buflen); + if (ret < 0) + goto cleanup; + + /* len > 0 && len % 4 == 0 guaranteed by cxacru_cm() */ + len = ret / 4; + for (offb = 0; offb < len; ) { + int l = le32_to_cpu(buf[offb++]); + if (l < 0 || l > stride || l > (len - offb) / 2) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "invalid data length from cm %#x: %d\n", + cm, l); + ret = -EIO; + goto cleanup; + } + while (l--) { + offd = le32_to_cpu(buf[offb++]); + if (offd >= size) { + if (printk_ratelimit()) + usb_err(instance->usbatm, "wrong index %#x in response to cm %#x\n", + offd, cm); + ret = -EIO; + goto cleanup; + } + data[offd] = le32_to_cpu(buf[offb++]); + } + } + + ret = 0; + +cleanup: + kfree(buf); + return ret; +} + +static int cxacru_card_status(struct cxacru_data *instance) +{ + int ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0); + if (ret < 0) { /* firmware not loaded */ + usb_dbg(instance->usbatm, "cxacru_adsl_start: CARD_GET_STATUS returned %d\n", ret); + return ret; + } + return 0; +} + +static void cxacru_remove_device_files(struct usbatm_data *usbatm_instance, + struct atm_dev *atm_dev) +{ + struct usb_interface *intf = usbatm_instance->usb_intf; + + #define CXACRU_DEVICE_REMOVE_FILE(_name) \ + device_remove_file(&intf->dev, &dev_attr_##_name); + CXACRU_ALL_FILES(REMOVE); + #undef CXACRU_DEVICE_REMOVE_FILE +} + +static int cxacru_atm_start(struct usbatm_data *usbatm_instance, + struct atm_dev *atm_dev) +{ + struct cxacru_data *instance = usbatm_instance->driver_data; + struct usb_interface *intf = usbatm_instance->usb_intf; + int ret; + int start_polling = 1; + + dev_dbg(&intf->dev, "%s\n", __func__); + + /* Read MAC address */ + ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_MAC_ADDRESS, NULL, 0, + atm_dev->esi, sizeof(atm_dev->esi)); + if (ret < 0) { + atm_err(usbatm_instance, "cxacru_atm_start: CARD_GET_MAC_ADDRESS returned %d\n", ret); + return ret; + } + + #define CXACRU_DEVICE_CREATE_FILE(_name) \ + ret = device_create_file(&intf->dev, &dev_attr_##_name); \ + if (unlikely(ret)) \ + goto fail_sysfs; + CXACRU_ALL_FILES(CREATE); + #undef CXACRU_DEVICE_CREATE_FILE + + /* start ADSL */ + mutex_lock(&instance->adsl_state_serialize); + ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); + if (ret < 0) + atm_err(usbatm_instance, "cxacru_atm_start: CHIP_ADSL_LINE_START returned %d\n", ret); + + /* Start status polling */ + mutex_lock(&instance->poll_state_serialize); + switch (instance->poll_state) { + case CXPOLL_STOPPED: + /* start polling */ + instance->poll_state = CXPOLL_POLLING; + break; + + case CXPOLL_STOPPING: + /* abort stop request */ + instance->poll_state = CXPOLL_POLLING; + case CXPOLL_POLLING: + case CXPOLL_SHUTDOWN: + /* don't start polling */ + start_polling = 0; + } + mutex_unlock(&instance->poll_state_serialize); + mutex_unlock(&instance->adsl_state_serialize); + + printk(KERN_INFO "%s%d: %s %pM\n", atm_dev->type, atm_dev->number, + usbatm_instance->description, atm_dev->esi); + + if (start_polling) + cxacru_poll_status(&instance->poll_work.work); + return 0; + +fail_sysfs: + usb_err(usbatm_instance, "cxacru_atm_start: device_create_file failed (%d)\n", ret); + cxacru_remove_device_files(usbatm_instance, atm_dev); + return ret; +} + +static void cxacru_poll_status(struct work_struct *work) +{ + struct cxacru_data *instance = + container_of(work, struct cxacru_data, poll_work.work); + u32 buf[CXINF_MAX] = {}; + struct usbatm_data *usbatm = instance->usbatm; + struct atm_dev *atm_dev = usbatm->atm_dev; + int keep_polling = 1; + int ret; + + ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX); + if (ret < 0) { + if (ret != -ESHUTDOWN) + atm_warn(usbatm, "poll status: error %d\n", ret); + + mutex_lock(&instance->poll_state_serialize); + if (instance->poll_state != CXPOLL_SHUTDOWN) { + instance->poll_state = CXPOLL_STOPPED; + + if (ret != -ESHUTDOWN) + atm_warn(usbatm, "polling disabled, set adsl_state" + " to 'start' or 'poll' to resume\n"); + } + mutex_unlock(&instance->poll_state_serialize); + goto reschedule; + } + + memcpy(instance->card_info, buf, sizeof(instance->card_info)); + + if (instance->adsl_status != buf[CXINF_LINE_STARTABLE]) { + instance->adsl_status = buf[CXINF_LINE_STARTABLE]; + + switch (instance->adsl_status) { + case 0: + atm_printk(KERN_INFO, usbatm, "ADSL state: running\n"); + break; + + case 1: + atm_printk(KERN_INFO, usbatm, "ADSL state: stopped\n"); + break; + + default: + atm_printk(KERN_INFO, usbatm, "Unknown adsl status %02x\n", instance->adsl_status); + break; + } + } + + if (instance->line_status == buf[CXINF_LINE_STATUS]) + goto reschedule; + + instance->line_status = buf[CXINF_LINE_STATUS]; + switch (instance->line_status) { + case 0: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: down\n"); + break; + + case 1: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: attempting to activate\n"); + break; + + case 2: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: training\n"); + break; + + case 3: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: channel analysis\n"); + break; + + case 4: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: exchange\n"); + break; + + case 5: + atm_dev->link_rate = buf[CXINF_DOWNSTREAM_RATE] * 1000 / 424; + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_FOUND); + + atm_info(usbatm, "ADSL line: up (%d kb/s down | %d kb/s up)\n", + buf[CXINF_DOWNSTREAM_RATE], buf[CXINF_UPSTREAM_RATE]); + break; + + case 6: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: waiting\n"); + break; + + case 7: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line: initializing\n"); + break; + + default: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN); + atm_info(usbatm, "Unknown line state %02x\n", instance->line_status); + break; + } +reschedule: + + mutex_lock(&instance->poll_state_serialize); + if (instance->poll_state == CXPOLL_STOPPING && + instance->adsl_status == 1 && /* stopped */ + instance->line_status == 0) /* down */ + instance->poll_state = CXPOLL_STOPPED; + + if (instance->poll_state == CXPOLL_STOPPED) + keep_polling = 0; + mutex_unlock(&instance->poll_state_serialize); + + if (keep_polling) + schedule_delayed_work(&instance->poll_work, + round_jiffies_relative(POLL_INTERVAL*HZ)); +} + +static int cxacru_fw(struct usb_device *usb_dev, enum cxacru_fw_request fw, + u8 code1, u8 code2, u32 addr, const u8 *data, int size) +{ + int ret; + u8 *buf; + int offd, offb; + const int stride = CMD_PACKET_SIZE - 8; + + buf = (u8 *) __get_free_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + offb = offd = 0; + do { + int l = min_t(int, stride, size - offd); + buf[offb++] = fw; + buf[offb++] = l; + buf[offb++] = code1; + buf[offb++] = code2; + put_unaligned(cpu_to_le32(addr), (__le32 *)(buf + offb)); + offb += 4; + addr += l; + if (l) + memcpy(buf + offb, data + offd, l); + if (l < stride) + memset(buf + offb + l, 0, stride - l); + offb += stride; + offd += stride; + if ((offb >= PAGE_SIZE) || (offd >= size)) { + ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD), + buf, offb, NULL, CMD_TIMEOUT); + if (ret < 0) { + dev_dbg(&usb_dev->dev, "sending fw %#x failed\n", fw); + goto cleanup; + } + offb = 0; + } + } while (offd < size); + dev_dbg(&usb_dev->dev, "sent fw %#x\n", fw); + + ret = 0; + +cleanup: + free_page((unsigned long) buf); + return ret; +} + +static void cxacru_upload_firmware(struct cxacru_data *instance, + const struct firmware *fw, + const struct firmware *bp) +{ + int ret; + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + __le16 signature[] = { usb_dev->descriptor.idVendor, + usb_dev->descriptor.idProduct }; + __le32 val; + + usb_dbg(usbatm, "%s\n", __func__); + + /* FirmwarePllFClkValue */ + val = cpu_to_le32(instance->modem_type->pll_f_clk); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLFCLK_ADDR, (u8 *) &val, 4); + if (ret) { + usb_err(usbatm, "FirmwarePllFClkValue failed: %d\n", ret); + return; + } + + /* FirmwarePllBClkValue */ + val = cpu_to_le32(instance->modem_type->pll_b_clk); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLBCLK_ADDR, (u8 *) &val, 4); + if (ret) { + usb_err(usbatm, "FirmwarePllBClkValue failed: %d\n", ret); + return; + } + + /* Enable SDRAM */ + val = cpu_to_le32(SDRAM_ENA); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SDRAMEN_ADDR, (u8 *) &val, 4); + if (ret) { + usb_err(usbatm, "Enable SDRAM failed: %d\n", ret); + return; + } + + /* Firmware */ + usb_info(usbatm, "loading firmware\n"); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, FW_ADDR, fw->data, fw->size); + if (ret) { + usb_err(usbatm, "Firmware upload failed: %d\n", ret); + return; + } + + /* Boot ROM patch */ + if (instance->modem_type->boot_rom_patch) { + usb_info(usbatm, "loading boot ROM patch\n"); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_ADDR, bp->data, bp->size); + if (ret) { + usb_err(usbatm, "Boot ROM patching failed: %d\n", ret); + return; + } + } + + /* Signature */ + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SIG_ADDR, (u8 *) signature, 4); + if (ret) { + usb_err(usbatm, "Signature storing failed: %d\n", ret); + return; + } + + usb_info(usbatm, "starting device\n"); + if (instance->modem_type->boot_rom_patch) { + val = cpu_to_le32(BR_ADDR); + ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_STACK_ADDR, (u8 *) &val, 4); + } else { + ret = cxacru_fw(usb_dev, FW_GOTO_MEM, 0x0, 0x0, FW_ADDR, NULL, 0); + } + if (ret) { + usb_err(usbatm, "Passing control to firmware failed: %d\n", ret); + return; + } + + /* Delay to allow firmware to start up. */ + msleep_interruptible(1000); + + usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD)); + usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD)); + usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_DATA)); + usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_DATA)); + + ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0); + if (ret < 0) { + usb_err(usbatm, "modem failed to initialize: %d\n", ret); + return; + } +} + +static int cxacru_find_firmware(struct cxacru_data *instance, + char *phase, const struct firmware **fw_p) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct device *dev = &usbatm->usb_intf->dev; + char buf[16]; + + sprintf(buf, "cxacru-%s.bin", phase); + usb_dbg(usbatm, "cxacru_find_firmware: looking for %s\n", buf); + + if (request_firmware(fw_p, buf, dev)) { + usb_dbg(usbatm, "no stage %s firmware found\n", phase); + return -ENOENT; + } + + usb_info(usbatm, "found firmware %s\n", buf); + + return 0; +} + +static int cxacru_heavy_init(struct usbatm_data *usbatm_instance, + struct usb_interface *usb_intf) +{ + const struct firmware *fw, *bp; + struct cxacru_data *instance = usbatm_instance->driver_data; + + int ret = cxacru_find_firmware(instance, "fw", &fw); + if (ret) { + usb_warn(usbatm_instance, "firmware (cxacru-fw.bin) unavailable (system misconfigured?)\n"); + return ret; + } + + if (instance->modem_type->boot_rom_patch) { + ret = cxacru_find_firmware(instance, "bp", &bp); + if (ret) { + usb_warn(usbatm_instance, "boot ROM patch (cxacru-bp.bin) unavailable (system misconfigured?)\n"); + release_firmware(fw); + return ret; + } + } + + cxacru_upload_firmware(instance, fw, bp); + + if (instance->modem_type->boot_rom_patch) + release_firmware(bp); + release_firmware(fw); + + ret = cxacru_card_status(instance); + if (ret) + usb_dbg(usbatm_instance, "modem initialisation failed\n"); + else + usb_dbg(usbatm_instance, "done setting up the modem\n"); + + return ret; +} + +static int cxacru_bind(struct usbatm_data *usbatm_instance, + struct usb_interface *intf, const struct usb_device_id *id) +{ + struct cxacru_data *instance; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct usb_host_endpoint *cmd_ep = usb_dev->ep_in[CXACRU_EP_CMD]; + int ret; + + /* instance init */ + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + if (!instance) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for instance data\n"); + return -ENOMEM; + } + + instance->usbatm = usbatm_instance; + instance->modem_type = (struct cxacru_modem_type *) id->driver_info; + + mutex_init(&instance->poll_state_serialize); + instance->poll_state = CXPOLL_STOPPED; + instance->line_status = -1; + instance->adsl_status = -1; + + mutex_init(&instance->adsl_state_serialize); + + instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL); + if (!instance->rcv_buf) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for rcv_buf\n"); + ret = -ENOMEM; + goto fail; + } + instance->snd_buf = (u8 *) __get_free_page(GFP_KERNEL); + if (!instance->snd_buf) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for snd_buf\n"); + ret = -ENOMEM; + goto fail; + } + instance->rcv_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!instance->rcv_urb) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for rcv_urb\n"); + ret = -ENOMEM; + goto fail; + } + instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!instance->snd_urb) { + usb_dbg(usbatm_instance, "cxacru_bind: no memory for snd_urb\n"); + ret = -ENOMEM; + goto fail; + } + + if (!cmd_ep) { + usb_dbg(usbatm_instance, "cxacru_bind: no command endpoint\n"); + ret = -ENODEV; + goto fail; + } + + if ((cmd_ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_INT) { + usb_fill_int_urb(instance->rcv_urb, + usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD), + instance->rcv_buf, PAGE_SIZE, + cxacru_blocking_completion, &instance->rcv_done, 1); + + usb_fill_int_urb(instance->snd_urb, + usb_dev, usb_sndintpipe(usb_dev, CXACRU_EP_CMD), + instance->snd_buf, PAGE_SIZE, + cxacru_blocking_completion, &instance->snd_done, 4); + } else { + usb_fill_bulk_urb(instance->rcv_urb, + usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD), + instance->rcv_buf, PAGE_SIZE, + cxacru_blocking_completion, &instance->rcv_done); + + usb_fill_bulk_urb(instance->snd_urb, + usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD), + instance->snd_buf, PAGE_SIZE, + cxacru_blocking_completion, &instance->snd_done); + } + + mutex_init(&instance->cm_serialize); + + INIT_DELAYED_WORK(&instance->poll_work, cxacru_poll_status); + + usbatm_instance->driver_data = instance; + + usbatm_instance->flags = (cxacru_card_status(instance) ? 0 : UDSL_SKIP_HEAVY_INIT); + + return 0; + + fail: + free_page((unsigned long) instance->snd_buf); + free_page((unsigned long) instance->rcv_buf); + usb_free_urb(instance->snd_urb); + usb_free_urb(instance->rcv_urb); + kfree(instance); + + return ret; +} + +static void cxacru_unbind(struct usbatm_data *usbatm_instance, + struct usb_interface *intf) +{ + struct cxacru_data *instance = usbatm_instance->driver_data; + int is_polling = 1; + + usb_dbg(usbatm_instance, "cxacru_unbind entered\n"); + + if (!instance) { + usb_dbg(usbatm_instance, "cxacru_unbind: NULL instance!\n"); + return; + } + + mutex_lock(&instance->poll_state_serialize); + BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN); + + /* ensure that status polling continues unless + * it has already stopped */ + if (instance->poll_state == CXPOLL_STOPPED) + is_polling = 0; + + /* stop polling from being stopped or started */ + instance->poll_state = CXPOLL_SHUTDOWN; + mutex_unlock(&instance->poll_state_serialize); + + if (is_polling) + cancel_delayed_work_sync(&instance->poll_work); + + usb_kill_urb(instance->snd_urb); + usb_kill_urb(instance->rcv_urb); + usb_free_urb(instance->snd_urb); + usb_free_urb(instance->rcv_urb); + + free_page((unsigned long) instance->snd_buf); + free_page((unsigned long) instance->rcv_buf); + + kfree(instance); + + usbatm_instance->driver_data = NULL; +} + +static const struct cxacru_modem_type cxacru_cafe = { + .pll_f_clk = 0x02d874df, + .pll_b_clk = 0x0196a51a, + .boot_rom_patch = 1, +}; + +static const struct cxacru_modem_type cxacru_cb00 = { + .pll_f_clk = 0x5, + .pll_b_clk = 0x3, + .boot_rom_patch = 0, +}; + +static const struct usb_device_id cxacru_usb_ids[] = { + { /* V = Conexant P = ADSL modem (Euphrates project) */ + USB_DEVICE(0x0572, 0xcafe), .driver_info = (unsigned long) &cxacru_cafe + }, + { /* V = Conexant P = ADSL modem (Hasbani project) */ + USB_DEVICE(0x0572, 0xcb00), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Conexant P = ADSL modem */ + USB_DEVICE(0x0572, 0xcb01), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Conexant P = ADSL modem (Well PTI-800) */ + USB_DEVICE(0x0572, 0xcb02), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Conexant P = ADSL modem */ + USB_DEVICE(0x0572, 0xcb06), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Conexant P = ADSL modem (ZTE ZXDSL 852) */ + USB_DEVICE(0x0572, 0xcb07), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Olitec P = ADSL modem version 2 */ + USB_DEVICE(0x08e3, 0x0100), .driver_info = (unsigned long) &cxacru_cafe + }, + { /* V = Olitec P = ADSL modem version 3 */ + USB_DEVICE(0x08e3, 0x0102), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Trust/Amigo Technology Co. P = AMX-CA86U */ + USB_DEVICE(0x0eb0, 0x3457), .driver_info = (unsigned long) &cxacru_cafe + }, + { /* V = Zoom P = 5510 */ + USB_DEVICE(0x1803, 0x5510), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Draytek P = Vigor 318 */ + USB_DEVICE(0x0675, 0x0200), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Zyxel P = 630-C1 aka OMNI ADSL USB (Annex A) */ + USB_DEVICE(0x0586, 0x330a), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Zyxel P = 630-C3 aka OMNI ADSL USB (Annex B) */ + USB_DEVICE(0x0586, 0x330b), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Aethra P = Starmodem UM1020 */ + USB_DEVICE(0x0659, 0x0020), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Aztech Systems P = ? AKA Pirelli AUA-010 */ + USB_DEVICE(0x0509, 0x0812), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Netopia P = Cayman 3341(Annex A)/3351(Annex B) */ + USB_DEVICE(0x100d, 0xcb01), .driver_info = (unsigned long) &cxacru_cb00 + }, + { /* V = Netopia P = Cayman 3342(Annex A)/3352(Annex B) */ + USB_DEVICE(0x100d, 0x3342), .driver_info = (unsigned long) &cxacru_cb00 + }, + {} +}; + +MODULE_DEVICE_TABLE(usb, cxacru_usb_ids); + +static struct usbatm_driver cxacru_driver = { + .driver_name = cxacru_driver_name, + .bind = cxacru_bind, + .heavy_init = cxacru_heavy_init, + .unbind = cxacru_unbind, + .atm_start = cxacru_atm_start, + .atm_stop = cxacru_remove_device_files, + .bulk_in = CXACRU_EP_DATA, + .bulk_out = CXACRU_EP_DATA, + .rx_padding = 3, + .tx_padding = 11, +}; + +static int cxacru_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + char buf[15]; + + /* Avoid ADSL routers (cx82310_eth). + * Abort if bDeviceClass is 0xff and iProduct is "USB NET CARD". + */ + if (usb_dev->descriptor.bDeviceClass == USB_CLASS_VENDOR_SPEC + && usb_string(usb_dev, usb_dev->descriptor.iProduct, + buf, sizeof(buf)) > 0) { + if (!strcmp(buf, "USB NET CARD")) { + dev_info(&intf->dev, "ignoring cx82310_eth device\n"); + return -ENODEV; + } + } + + return usbatm_usb_probe(intf, id, &cxacru_driver); +} + +static struct usb_driver cxacru_usb_driver = { + .name = cxacru_driver_name, + .probe = cxacru_usb_probe, + .disconnect = usbatm_usb_disconnect, + .id_table = cxacru_usb_ids +}; + +module_usb_driver(cxacru_usb_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c new file mode 100644 index 00000000..807627b3 --- /dev/null +++ b/drivers/usb/atm/speedtch.c @@ -0,0 +1,961 @@ +/****************************************************************************** + * speedtch.c - Alcatel SpeedTouch USB xDSL modem driver + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands + * Copyright (C) 2004, David Woodhouse + * + * Based on "modem_run.c", copyright (C) 2001, Benoit Papillault + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbatm.h" + +#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands " +#define DRIVER_VERSION "1.10" +#define DRIVER_DESC "Alcatel SpeedTouch USB driver version " DRIVER_VERSION + +static const char speedtch_driver_name[] = "speedtch"; + +#define CTRL_TIMEOUT 2000 /* milliseconds */ +#define DATA_TIMEOUT 2000 /* milliseconds */ + +#define OFFSET_7 0 /* size 1 */ +#define OFFSET_b 1 /* size 8 */ +#define OFFSET_d 9 /* size 4 */ +#define OFFSET_e 13 /* size 1 */ +#define OFFSET_f 14 /* size 1 */ + +#define SIZE_7 1 +#define SIZE_b 8 +#define SIZE_d 4 +#define SIZE_e 1 +#define SIZE_f 1 + +#define MIN_POLL_DELAY 5000 /* milliseconds */ +#define MAX_POLL_DELAY 60000 /* milliseconds */ + +#define RESUBMIT_DELAY 1000 /* milliseconds */ + +#define DEFAULT_BULK_ALTSETTING 1 +#define DEFAULT_ISOC_ALTSETTING 3 +#define DEFAULT_DL_512_FIRST 0 +#define DEFAULT_ENABLE_ISOC 0 +#define DEFAULT_SW_BUFFERING 0 + +static unsigned int altsetting = 0; /* zero means: use the default */ +static bool dl_512_first = DEFAULT_DL_512_FIRST; +static bool enable_isoc = DEFAULT_ENABLE_ISOC; +static bool sw_buffering = DEFAULT_SW_BUFFERING; + +#define DEFAULT_B_MAX_DSL 8128 +#define DEFAULT_MODEM_MODE 11 +#define MODEM_OPTION_LENGTH 16 +static const unsigned char DEFAULT_MODEM_OPTION[MODEM_OPTION_LENGTH] = { + 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static unsigned int BMaxDSL = DEFAULT_B_MAX_DSL; +static unsigned char ModemMode = DEFAULT_MODEM_MODE; +static unsigned char ModemOption[MODEM_OPTION_LENGTH]; +static unsigned int num_ModemOption; + +module_param(altsetting, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(altsetting, + "Alternative setting for data interface (bulk_default: " + __MODULE_STRING(DEFAULT_BULK_ALTSETTING) "; isoc_default: " + __MODULE_STRING(DEFAULT_ISOC_ALTSETTING) ")"); + +module_param(dl_512_first, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(dl_512_first, + "Read 512 bytes before sending firmware (default: " + __MODULE_STRING(DEFAULT_DL_512_FIRST) ")"); + +module_param(enable_isoc, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_isoc, + "Use isochronous transfers if available (default: " + __MODULE_STRING(DEFAULT_ENABLE_ISOC) ")"); + +module_param(sw_buffering, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(sw_buffering, + "Enable software buffering (default: " + __MODULE_STRING(DEFAULT_SW_BUFFERING) ")"); + +module_param(BMaxDSL, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(BMaxDSL, + "default: " __MODULE_STRING(DEFAULT_B_MAX_DSL)); + +module_param(ModemMode, byte, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ModemMode, + "default: " __MODULE_STRING(DEFAULT_MODEM_MODE)); + +module_param_array(ModemOption, byte, &num_ModemOption, S_IRUGO); +MODULE_PARM_DESC(ModemOption, "default: 0x10,0x00,0x00,0x00,0x20"); + +#define INTERFACE_DATA 1 +#define ENDPOINT_INT 0x81 +#define ENDPOINT_BULK_DATA 0x07 +#define ENDPOINT_ISOC_DATA 0x07 +#define ENDPOINT_FIRMWARE 0x05 + +struct speedtch_params { + unsigned int altsetting; + unsigned int BMaxDSL; + unsigned char ModemMode; + unsigned char ModemOption[MODEM_OPTION_LENGTH]; +}; + +struct speedtch_instance_data { + struct usbatm_data *usbatm; + + struct speedtch_params params; /* set in probe, constant afterwards */ + + struct timer_list status_check_timer; + struct work_struct status_check_work; + + unsigned char last_status; + + int poll_delay; /* milliseconds */ + + struct timer_list resubmit_timer; + struct urb *int_urb; + unsigned char int_data[16]; + + unsigned char scratch_buffer[16]; +}; + +/*************** +** firmware ** +***************/ + +static void speedtch_set_swbuff(struct speedtch_instance_data *instance, int state) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + int ret; + + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x32, 0x40, state ? 0x01 : 0x00, 0x00, NULL, 0, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, + "%sabling SW buffering: usb_control_msg returned %d\n", + state ? "En" : "Dis", ret); + else + usb_dbg(usbatm, "speedtch_set_swbuff: %sbled SW buffering\n", state ? "En" : "Dis"); +} + +static void speedtch_test_sequence(struct speedtch_instance_data *instance) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + unsigned char *buf = instance->scratch_buffer; + int ret; + + /* URB 147 */ + buf[0] = 0x1c; + buf[1] = 0x50; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x0b, 0x00, buf, 2, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URB147: %d\n", __func__, ret); + + /* URB 148 */ + buf[0] = 0x32; + buf[1] = 0x00; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x02, 0x00, buf, 2, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URB148: %d\n", __func__, ret); + + /* URB 149 */ + buf[0] = 0x01; + buf[1] = 0x00; + buf[2] = 0x01; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x03, 0x00, buf, 3, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URB149: %d\n", __func__, ret); + + /* URB 150 */ + buf[0] = 0x01; + buf[1] = 0x00; + buf[2] = 0x01; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x04, 0x00, buf, 3, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URB150: %d\n", __func__, ret); + + /* Extra initialisation in recent drivers - gives higher speeds */ + + /* URBext1 */ + buf[0] = instance->params.ModemMode; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x11, 0x00, buf, 1, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URBext1: %d\n", __func__, ret); + + /* URBext2 */ + /* This seems to be the one which actually triggers the higher sync + rate -- it does require the new firmware too, although it works OK + with older firmware */ + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x14, 0x00, + instance->params.ModemOption, + MODEM_OPTION_LENGTH, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URBext2: %d\n", __func__, ret); + + /* URBext3 */ + buf[0] = instance->params.BMaxDSL & 0xff; + buf[1] = instance->params.BMaxDSL >> 8; + ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + 0x01, 0x40, 0x12, 0x00, buf, 2, CTRL_TIMEOUT); + if (ret < 0) + usb_warn(usbatm, "%s failed on URBext3: %d\n", __func__, ret); +} + +static int speedtch_upload_firmware(struct speedtch_instance_data *instance, + const struct firmware *fw1, + const struct firmware *fw2) +{ + unsigned char *buffer; + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + int actual_length; + int ret = 0; + int offset; + + usb_dbg(usbatm, "%s entered\n", __func__); + + if (!(buffer = (unsigned char *)__get_free_page(GFP_KERNEL))) { + ret = -ENOMEM; + usb_dbg(usbatm, "%s: no memory for buffer!\n", __func__); + goto out; + } + + if (!usb_ifnum_to_if(usb_dev, 2)) { + ret = -ENODEV; + usb_dbg(usbatm, "%s: interface not found!\n", __func__); + goto out_free; + } + + /* URB 7 */ + if (dl_512_first) { /* some modems need a read before writing the firmware */ + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, 2000); + + if (ret < 0 && ret != -ETIMEDOUT) + usb_warn(usbatm, "%s: read BLOCK0 from modem failed (%d)!\n", __func__, ret); + else + usb_dbg(usbatm, "%s: BLOCK0 downloaded (%d bytes)\n", __func__, ret); + } + + /* URB 8 : both leds are static green */ + for (offset = 0; offset < fw1->size; offset += PAGE_SIZE) { + int thislen = min_t(int, PAGE_SIZE, fw1->size - offset); + memcpy(buffer, fw1->data + offset, thislen); + + ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, thislen, &actual_length, DATA_TIMEOUT); + + if (ret < 0) { + usb_err(usbatm, "%s: write BLOCK1 to modem failed (%d)!\n", __func__, ret); + goto out_free; + } + usb_dbg(usbatm, "%s: BLOCK1 uploaded (%zu bytes)\n", __func__, fw1->size); + } + + /* USB led blinking green, ADSL led off */ + + /* URB 11 */ + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, DATA_TIMEOUT); + + if (ret < 0) { + usb_err(usbatm, "%s: read BLOCK2 from modem failed (%d)!\n", __func__, ret); + goto out_free; + } + usb_dbg(usbatm, "%s: BLOCK2 downloaded (%d bytes)\n", __func__, actual_length); + + /* URBs 12 to 139 - USB led blinking green, ADSL led off */ + for (offset = 0; offset < fw2->size; offset += PAGE_SIZE) { + int thislen = min_t(int, PAGE_SIZE, fw2->size - offset); + memcpy(buffer, fw2->data + offset, thislen); + + ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, thislen, &actual_length, DATA_TIMEOUT); + + if (ret < 0) { + usb_err(usbatm, "%s: write BLOCK3 to modem failed (%d)!\n", __func__, ret); + goto out_free; + } + } + usb_dbg(usbatm, "%s: BLOCK3 uploaded (%zu bytes)\n", __func__, fw2->size); + + /* USB led static green, ADSL led static red */ + + /* URB 142 */ + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, DATA_TIMEOUT); + + if (ret < 0) { + usb_err(usbatm, "%s: read BLOCK4 from modem failed (%d)!\n", __func__, ret); + goto out_free; + } + + /* success */ + usb_dbg(usbatm, "%s: BLOCK4 downloaded (%d bytes)\n", __func__, actual_length); + + /* Delay to allow firmware to start up. We can do this here + because we're in our own kernel thread anyway. */ + msleep_interruptible(1000); + + if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) { + usb_err(usbatm, "%s: setting interface to %d failed (%d)!\n", __func__, instance->params.altsetting, ret); + goto out_free; + } + + /* Enable software buffering, if requested */ + if (sw_buffering) + speedtch_set_swbuff(instance, 1); + + /* Magic spell; don't ask us what this does */ + speedtch_test_sequence(instance); + + ret = 0; + +out_free: + free_page((unsigned long)buffer); +out: + return ret; +} + +static int speedtch_find_firmware(struct usbatm_data *usbatm, struct usb_interface *intf, + int phase, const struct firmware **fw_p) +{ + struct device *dev = &intf->dev; + const u16 bcdDevice = le16_to_cpu(interface_to_usbdev(intf)->descriptor.bcdDevice); + const u8 major_revision = bcdDevice >> 8; + const u8 minor_revision = bcdDevice & 0xff; + char buf[24]; + + sprintf(buf, "speedtch-%d.bin.%x.%02x", phase, major_revision, minor_revision); + usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); + + if (request_firmware(fw_p, buf, dev)) { + sprintf(buf, "speedtch-%d.bin.%x", phase, major_revision); + usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); + + if (request_firmware(fw_p, buf, dev)) { + sprintf(buf, "speedtch-%d.bin", phase); + usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); + + if (request_firmware(fw_p, buf, dev)) { + usb_err(usbatm, "%s: no stage %d firmware found!\n", __func__, phase); + return -ENOENT; + } + } + } + + usb_info(usbatm, "found stage %d firmware %s\n", phase, buf); + + return 0; +} + +static int speedtch_heavy_init(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + const struct firmware *fw1, *fw2; + struct speedtch_instance_data *instance = usbatm->driver_data; + int ret; + + if ((ret = speedtch_find_firmware(usbatm, intf, 1, &fw1)) < 0) + return ret; + + if ((ret = speedtch_find_firmware(usbatm, intf, 2, &fw2)) < 0) { + release_firmware(fw1); + return ret; + } + + if ((ret = speedtch_upload_firmware(instance, fw1, fw2)) < 0) + usb_err(usbatm, "%s: firmware upload failed (%d)!\n", __func__, ret); + + release_firmware(fw2); + release_firmware(fw1); + + return ret; +} + + +/********** +** ATM ** +**********/ + +static int speedtch_read_status(struct speedtch_instance_data *instance) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + unsigned char *buf = instance->scratch_buffer; + int ret; + + memset(buf, 0, 16); + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x07, 0x00, buf + OFFSET_7, SIZE_7, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG 7 failed\n", __func__); + return ret; + } + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x0b, 0x00, buf + OFFSET_b, SIZE_b, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG B failed\n", __func__); + return ret; + } + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x0d, 0x00, buf + OFFSET_d, SIZE_d, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG D failed\n", __func__); + return ret; + } + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x01, 0xc0, 0x0e, 0x00, buf + OFFSET_e, SIZE_e, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG E failed\n", __func__); + return ret; + } + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x01, 0xc0, 0x0f, 0x00, buf + OFFSET_f, SIZE_f, + CTRL_TIMEOUT); + if (ret < 0) { + atm_dbg(usbatm, "%s: MSG F failed\n", __func__); + return ret; + } + + return 0; +} + +static int speedtch_start_synchro(struct speedtch_instance_data *instance) +{ + struct usbatm_data *usbatm = instance->usbatm; + struct usb_device *usb_dev = usbatm->usb_dev; + unsigned char *buf = instance->scratch_buffer; + int ret; + + atm_dbg(usbatm, "%s entered\n", __func__); + + memset(buf, 0, 2); + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x04, 0x00, + buf, 2, CTRL_TIMEOUT); + + if (ret < 0) + atm_warn(usbatm, "failed to start ADSL synchronisation: %d\n", ret); + else + atm_dbg(usbatm, "%s: modem prodded. %d bytes returned: %02x %02x\n", + __func__, ret, buf[0], buf[1]); + + return ret; +} + +static void speedtch_check_status(struct work_struct *work) +{ + struct speedtch_instance_data *instance = + container_of(work, struct speedtch_instance_data, + status_check_work); + struct usbatm_data *usbatm = instance->usbatm; + struct atm_dev *atm_dev = usbatm->atm_dev; + unsigned char *buf = instance->scratch_buffer; + int down_speed, up_speed, ret; + unsigned char status; + +#ifdef VERBOSE_DEBUG + atm_dbg(usbatm, "%s entered\n", __func__); +#endif + + ret = speedtch_read_status(instance); + if (ret < 0) { + atm_warn(usbatm, "error %d fetching device status\n", ret); + instance->poll_delay = min(2 * instance->poll_delay, MAX_POLL_DELAY); + return; + } + + instance->poll_delay = max(instance->poll_delay / 2, MIN_POLL_DELAY); + + status = buf[OFFSET_7]; + + if ((status != instance->last_status) || !status) { + atm_dbg(usbatm, "%s: line state 0x%02x\n", __func__, status); + + switch (status) { + case 0: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + if (instance->last_status) + atm_info(usbatm, "ADSL line is down\n"); + /* It may never resync again unless we ask it to... */ + ret = speedtch_start_synchro(instance); + break; + + case 0x08: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN); + atm_info(usbatm, "ADSL line is blocked?\n"); + break; + + case 0x10: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST); + atm_info(usbatm, "ADSL line is synchronising\n"); + break; + + case 0x20: + down_speed = buf[OFFSET_b] | (buf[OFFSET_b + 1] << 8) + | (buf[OFFSET_b + 2] << 16) | (buf[OFFSET_b + 3] << 24); + up_speed = buf[OFFSET_b + 4] | (buf[OFFSET_b + 5] << 8) + | (buf[OFFSET_b + 6] << 16) | (buf[OFFSET_b + 7] << 24); + + if (!(down_speed & 0x0000ffff) && !(up_speed & 0x0000ffff)) { + down_speed >>= 16; + up_speed >>= 16; + } + + atm_dev->link_rate = down_speed * 1000 / 424; + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_FOUND); + + atm_info(usbatm, + "ADSL line is up (%d kb/s down | %d kb/s up)\n", + down_speed, up_speed); + break; + + default: + atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN); + atm_info(usbatm, "unknown line state %02x\n", status); + break; + } + + instance->last_status = status; + } +} + +static void speedtch_status_poll(unsigned long data) +{ + struct speedtch_instance_data *instance = (void *)data; + + schedule_work(&instance->status_check_work); + + /* The following check is racy, but the race is harmless */ + if (instance->poll_delay < MAX_POLL_DELAY) + mod_timer(&instance->status_check_timer, jiffies + msecs_to_jiffies(instance->poll_delay)); + else + atm_warn(instance->usbatm, "Too many failures - disabling line status polling\n"); +} + +static void speedtch_resubmit_int(unsigned long data) +{ + struct speedtch_instance_data *instance = (void *)data; + struct urb *int_urb = instance->int_urb; + int ret; + + atm_dbg(instance->usbatm, "%s entered\n", __func__); + + if (int_urb) { + ret = usb_submit_urb(int_urb, GFP_ATOMIC); + if (!ret) + schedule_work(&instance->status_check_work); + else { + atm_dbg(instance->usbatm, "%s: usb_submit_urb failed with result %d\n", __func__, ret); + mod_timer(&instance->resubmit_timer, jiffies + msecs_to_jiffies(RESUBMIT_DELAY)); + } + } +} + +static void speedtch_handle_int(struct urb *int_urb) +{ + struct speedtch_instance_data *instance = int_urb->context; + struct usbatm_data *usbatm = instance->usbatm; + unsigned int count = int_urb->actual_length; + int status = int_urb->status; + int ret; + + /* The magic interrupt for "up state" */ + static const unsigned char up_int[6] = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00 }; + /* The magic interrupt for "down state" */ + static const unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + atm_dbg(usbatm, "%s entered\n", __func__); + + if (status < 0) { + atm_dbg(usbatm, "%s: nonzero urb status %d!\n", __func__, status); + goto fail; + } + + if ((count == 6) && !memcmp(up_int, instance->int_data, 6)) { + del_timer(&instance->status_check_timer); + atm_info(usbatm, "DSL line goes up\n"); + } else if ((count == 6) && !memcmp(down_int, instance->int_data, 6)) { + atm_info(usbatm, "DSL line goes down\n"); + } else { + int i; + + atm_dbg(usbatm, "%s: unknown interrupt packet of length %d:", __func__, count); + for (i = 0; i < count; i++) + printk(" %02x", instance->int_data[i]); + printk("\n"); + goto fail; + } + + if ((int_urb = instance->int_urb)) { + ret = usb_submit_urb(int_urb, GFP_ATOMIC); + schedule_work(&instance->status_check_work); + if (ret < 0) { + atm_dbg(usbatm, "%s: usb_submit_urb failed with result %d\n", __func__, ret); + goto fail; + } + } + + return; + +fail: + if ((int_urb = instance->int_urb)) + mod_timer(&instance->resubmit_timer, jiffies + msecs_to_jiffies(RESUBMIT_DELAY)); +} + +static int speedtch_atm_start(struct usbatm_data *usbatm, struct atm_dev *atm_dev) +{ + struct usb_device *usb_dev = usbatm->usb_dev; + struct speedtch_instance_data *instance = usbatm->driver_data; + int i, ret; + unsigned char mac_str[13]; + + atm_dbg(usbatm, "%s entered\n", __func__); + + /* Set MAC address, it is stored in the serial number */ + memset(atm_dev->esi, 0, sizeof(atm_dev->esi)); + if (usb_string(usb_dev, usb_dev->descriptor.iSerialNumber, mac_str, sizeof(mac_str)) == 12) { + for (i = 0; i < 6; i++) + atm_dev->esi[i] = (hex_to_bin(mac_str[i * 2]) << 4) + + hex_to_bin(mac_str[i * 2 + 1]); + } + + /* Start modem synchronisation */ + ret = speedtch_start_synchro(instance); + + /* Set up interrupt endpoint */ + if (instance->int_urb) { + ret = usb_submit_urb(instance->int_urb, GFP_KERNEL); + if (ret < 0) { + /* Doesn't matter; we'll poll anyway */ + atm_dbg(usbatm, "%s: submission of interrupt URB failed (%d)!\n", __func__, ret); + usb_free_urb(instance->int_urb); + instance->int_urb = NULL; + } + } + + /* Start status polling */ + mod_timer(&instance->status_check_timer, jiffies + msecs_to_jiffies(1000)); + + return 0; +} + +static void speedtch_atm_stop(struct usbatm_data *usbatm, struct atm_dev *atm_dev) +{ + struct speedtch_instance_data *instance = usbatm->driver_data; + struct urb *int_urb = instance->int_urb; + + atm_dbg(usbatm, "%s entered\n", __func__); + + del_timer_sync(&instance->status_check_timer); + + /* + * Since resubmit_timer and int_urb can schedule themselves and + * each other, shutting them down correctly takes some care + */ + instance->int_urb = NULL; /* signal shutdown */ + mb(); + usb_kill_urb(int_urb); + del_timer_sync(&instance->resubmit_timer); + /* + * At this point, speedtch_handle_int and speedtch_resubmit_int + * can run or be running, but instance->int_urb == NULL means that + * they will not reschedule + */ + usb_kill_urb(int_urb); + del_timer_sync(&instance->resubmit_timer); + usb_free_urb(int_urb); + + flush_work(&instance->status_check_work); +} + +static int speedtch_pre_reset(struct usb_interface *intf) +{ + return 0; +} + +static int speedtch_post_reset(struct usb_interface *intf) +{ + return 0; +} + + +/********** +** USB ** +**********/ + +static struct usb_device_id speedtch_usb_ids[] = { + {USB_DEVICE(0x06b9, 0x4061)}, + {} +}; + +MODULE_DEVICE_TABLE(usb, speedtch_usb_ids); + +static int speedtch_usb_probe(struct usb_interface *, const struct usb_device_id *); + +static struct usb_driver speedtch_usb_driver = { + .name = speedtch_driver_name, + .probe = speedtch_usb_probe, + .disconnect = usbatm_usb_disconnect, + .pre_reset = speedtch_pre_reset, + .post_reset = speedtch_post_reset, + .id_table = speedtch_usb_ids +}; + +static void speedtch_release_interfaces(struct usb_device *usb_dev, + int num_interfaces) +{ + struct usb_interface *cur_intf; + int i; + + for (i = 0; i < num_interfaces; i++) + if ((cur_intf = usb_ifnum_to_if(usb_dev, i))) { + usb_set_intfdata(cur_intf, NULL); + usb_driver_release_interface(&speedtch_usb_driver, cur_intf); + } +} + +static int speedtch_bind(struct usbatm_data *usbatm, + struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct usb_interface *cur_intf, *data_intf; + struct speedtch_instance_data *instance; + int ifnum = intf->altsetting->desc.bInterfaceNumber; + int num_interfaces = usb_dev->actconfig->desc.bNumInterfaces; + int i, ret; + int use_isoc; + + usb_dbg(usbatm, "%s entered\n", __func__); + + /* sanity checks */ + + if (usb_dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) { + usb_err(usbatm, "%s: wrong device class %d\n", __func__, usb_dev->descriptor.bDeviceClass); + return -ENODEV; + } + + if (!(data_intf = usb_ifnum_to_if(usb_dev, INTERFACE_DATA))) { + usb_err(usbatm, "%s: data interface not found!\n", __func__); + return -ENODEV; + } + + /* claim all interfaces */ + + for (i = 0; i < num_interfaces; i++) { + cur_intf = usb_ifnum_to_if(usb_dev, i); + + if ((i != ifnum) && cur_intf) { + ret = usb_driver_claim_interface(&speedtch_usb_driver, cur_intf, usbatm); + + if (ret < 0) { + usb_err(usbatm, "%s: failed to claim interface %2d (%d)!\n", __func__, i, ret); + speedtch_release_interfaces(usb_dev, i); + return ret; + } + } + } + + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + + if (!instance) { + usb_err(usbatm, "%s: no memory for instance data!\n", __func__); + ret = -ENOMEM; + goto fail_release; + } + + instance->usbatm = usbatm; + + /* module parameters may change at any moment, so take a snapshot */ + instance->params.altsetting = altsetting; + instance->params.BMaxDSL = BMaxDSL; + instance->params.ModemMode = ModemMode; + memcpy(instance->params.ModemOption, DEFAULT_MODEM_OPTION, MODEM_OPTION_LENGTH); + memcpy(instance->params.ModemOption, ModemOption, num_ModemOption); + use_isoc = enable_isoc; + + if (instance->params.altsetting) + if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) { + usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, instance->params.altsetting, ret); + instance->params.altsetting = 0; /* fall back to default */ + } + + if (!instance->params.altsetting && use_isoc) + if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_ISOC_ALTSETTING)) < 0) { + usb_dbg(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_ISOC_ALTSETTING, ret); + use_isoc = 0; /* fall back to bulk */ + } + + if (use_isoc) { + const struct usb_host_interface *desc = data_intf->cur_altsetting; + const __u8 target_address = USB_DIR_IN | usbatm->driver->isoc_in; + + use_isoc = 0; /* fall back to bulk if endpoint not found */ + + for (i = 0; i < desc->desc.bNumEndpoints; i++) { + const struct usb_endpoint_descriptor *endpoint_desc = &desc->endpoint[i].desc; + + if ((endpoint_desc->bEndpointAddress == target_address)) { + use_isoc = + usb_endpoint_xfer_isoc(endpoint_desc); + break; + } + } + + if (!use_isoc) + usb_info(usbatm, "isochronous transfer not supported - using bulk\n"); + } + + if (!use_isoc && !instance->params.altsetting) + if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_BULK_ALTSETTING)) < 0) { + usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_BULK_ALTSETTING, ret); + goto fail_free; + } + + if (!instance->params.altsetting) + instance->params.altsetting = use_isoc ? DEFAULT_ISOC_ALTSETTING : DEFAULT_BULK_ALTSETTING; + + usbatm->flags |= (use_isoc ? UDSL_USE_ISOC : 0); + + INIT_WORK(&instance->status_check_work, speedtch_check_status); + init_timer(&instance->status_check_timer); + + instance->status_check_timer.function = speedtch_status_poll; + instance->status_check_timer.data = (unsigned long)instance; + instance->last_status = 0xff; + instance->poll_delay = MIN_POLL_DELAY; + + init_timer(&instance->resubmit_timer); + instance->resubmit_timer.function = speedtch_resubmit_int; + instance->resubmit_timer.data = (unsigned long)instance; + + instance->int_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (instance->int_urb) + usb_fill_int_urb(instance->int_urb, usb_dev, + usb_rcvintpipe(usb_dev, ENDPOINT_INT), + instance->int_data, sizeof(instance->int_data), + speedtch_handle_int, instance, 50); + else + usb_dbg(usbatm, "%s: no memory for interrupt urb!\n", __func__); + + /* check whether the modem already seems to be alive */ + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + 0x12, 0xc0, 0x07, 0x00, + instance->scratch_buffer + OFFSET_7, SIZE_7, 500); + + usbatm->flags |= (ret == SIZE_7 ? UDSL_SKIP_HEAVY_INIT : 0); + + usb_dbg(usbatm, "%s: firmware %s loaded\n", __func__, usbatm->flags & UDSL_SKIP_HEAVY_INIT ? "already" : "not"); + + if (!(usbatm->flags & UDSL_SKIP_HEAVY_INIT)) + if ((ret = usb_reset_device(usb_dev)) < 0) { + usb_err(usbatm, "%s: device reset failed (%d)!\n", __func__, ret); + goto fail_free; + } + + usbatm->driver_data = instance; + + return 0; + +fail_free: + usb_free_urb(instance->int_urb); + kfree(instance); +fail_release: + speedtch_release_interfaces(usb_dev, num_interfaces); + return ret; +} + +static void speedtch_unbind(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct speedtch_instance_data *instance = usbatm->driver_data; + + usb_dbg(usbatm, "%s entered\n", __func__); + + speedtch_release_interfaces(usb_dev, usb_dev->actconfig->desc.bNumInterfaces); + usb_free_urb(instance->int_urb); + kfree(instance); +} + + +/*********** +** init ** +***********/ + +static struct usbatm_driver speedtch_usbatm_driver = { + .driver_name = speedtch_driver_name, + .bind = speedtch_bind, + .heavy_init = speedtch_heavy_init, + .unbind = speedtch_unbind, + .atm_start = speedtch_atm_start, + .atm_stop = speedtch_atm_stop, + .bulk_in = ENDPOINT_BULK_DATA, + .bulk_out = ENDPOINT_BULK_DATA, + .isoc_in = ENDPOINT_ISOC_DATA +}; + +static int speedtch_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + return usbatm_usb_probe(intf, id, &speedtch_usbatm_driver); +} + +module_usb_driver(speedtch_usb_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c new file mode 100644 index 00000000..defff439 --- /dev/null +++ b/drivers/usb/atm/ueagle-atm.c @@ -0,0 +1,2808 @@ +/*- + * Copyright (c) 2003, 2004 + * Damien Bergamini . All rights reserved. + * + * Copyright (c) 2005-2007 Matthieu Castet + * Copyright (c) 2005-2007 Stanislaw Gruszka + * + * This software is available to you under a choice of one of two + * licenses. You may choose to be licensed under the terms of the GNU + * General Public License (GPL) Version 2, available from the file + * COPYING in the main directory of this source tree, or the + * BSD license below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * GPL license : + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * HISTORY : some part of the code was base on ueagle 1.3 BSD driver, + * Damien Bergamini agree to put his code under a DUAL GPL/BSD license. + * + * The rest of the code was was rewritten from scratch. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "usbatm.h" + +#define EAGLEUSBVERSION "ueagle 1.4" + + +/* + * Debug macros + */ +#define uea_dbg(usb_dev, format, args...) \ + do { \ + if (debug >= 1) \ + dev_dbg(&(usb_dev)->dev, \ + "[ueagle-atm dbg] %s: " format, \ + __func__, ##args); \ + } while (0) + +#define uea_vdbg(usb_dev, format, args...) \ + do { \ + if (debug >= 2) \ + dev_dbg(&(usb_dev)->dev, \ + "[ueagle-atm vdbg] " format, ##args); \ + } while (0) + +#define uea_enters(usb_dev) \ + uea_vdbg(usb_dev, "entering %s\n" , __func__) + +#define uea_leaves(usb_dev) \ + uea_vdbg(usb_dev, "leaving %s\n" , __func__) + +#define uea_err(usb_dev, format, args...) \ + dev_err(&(usb_dev)->dev , "[UEAGLE-ATM] " format , ##args) + +#define uea_warn(usb_dev, format, args...) \ + dev_warn(&(usb_dev)->dev , "[Ueagle-atm] " format, ##args) + +#define uea_info(usb_dev, format, args...) \ + dev_info(&(usb_dev)->dev , "[ueagle-atm] " format, ##args) + +struct intr_pkt; + +/* cmv's from firmware */ +struct uea_cmvs_v1 { + u32 address; + u16 offset; + u32 data; +} __packed; + +struct uea_cmvs_v2 { + u32 group; + u32 address; + u32 offset; + u32 data; +} __packed; + +/* information about currently processed cmv */ +struct cmv_dsc_e1 { + u8 function; + u16 idx; + u32 address; + u16 offset; +}; + +struct cmv_dsc_e4 { + u16 function; + u16 offset; + u16 address; + u16 group; +}; + +union cmv_dsc { + struct cmv_dsc_e1 e1; + struct cmv_dsc_e4 e4; +}; + +struct uea_softc { + struct usb_device *usb_dev; + struct usbatm_data *usbatm; + + int modem_index; + unsigned int driver_info; + int annex; +#define ANNEXA 0 +#define ANNEXB 1 + + int booting; + int reset; + + wait_queue_head_t sync_q; + + struct task_struct *kthread; + u32 data; + u32 data1; + + int cmv_ack; + union cmv_dsc cmv_dsc; + + struct work_struct task; + u16 pageno; + u16 ovl; + + const struct firmware *dsp_firm; + struct urb *urb_int; + + void (*dispatch_cmv) (struct uea_softc *, struct intr_pkt *); + void (*schedule_load_page) (struct uea_softc *, struct intr_pkt *); + int (*stat) (struct uea_softc *); + int (*send_cmvs) (struct uea_softc *); + + /* keep in sync with eaglectl */ + struct uea_stats { + struct { + u32 state; + u32 flags; + u32 mflags; + u32 vidcpe; + u32 vidco; + u32 dsrate; + u32 usrate; + u32 dsunc; + u32 usunc; + u32 dscorr; + u32 uscorr; + u32 txflow; + u32 rxflow; + u32 usattenuation; + u32 dsattenuation; + u32 dsmargin; + u32 usmargin; + u32 firmid; + } phy; + } stats; +}; + +/* + * Elsa IDs + */ +#define ELSA_VID 0x05CC +#define ELSA_PID_PSTFIRM 0x3350 +#define ELSA_PID_PREFIRM 0x3351 + +#define ELSA_PID_A_PREFIRM 0x3352 +#define ELSA_PID_A_PSTFIRM 0x3353 +#define ELSA_PID_B_PREFIRM 0x3362 +#define ELSA_PID_B_PSTFIRM 0x3363 + +/* + * Devolo IDs : pots if (pid & 0x10) + */ +#define DEVOLO_VID 0x1039 +#define DEVOLO_EAGLE_I_A_PID_PSTFIRM 0x2110 +#define DEVOLO_EAGLE_I_A_PID_PREFIRM 0x2111 + +#define DEVOLO_EAGLE_I_B_PID_PSTFIRM 0x2100 +#define DEVOLO_EAGLE_I_B_PID_PREFIRM 0x2101 + +#define DEVOLO_EAGLE_II_A_PID_PSTFIRM 0x2130 +#define DEVOLO_EAGLE_II_A_PID_PREFIRM 0x2131 + +#define DEVOLO_EAGLE_II_B_PID_PSTFIRM 0x2120 +#define DEVOLO_EAGLE_II_B_PID_PREFIRM 0x2121 + +/* + * Reference design USB IDs + */ +#define ANALOG_VID 0x1110 +#define ADI930_PID_PREFIRM 0x9001 +#define ADI930_PID_PSTFIRM 0x9000 + +#define EAGLE_I_PID_PREFIRM 0x9010 /* Eagle I */ +#define EAGLE_I_PID_PSTFIRM 0x900F /* Eagle I */ + +#define EAGLE_IIC_PID_PREFIRM 0x9024 /* Eagle IIC */ +#define EAGLE_IIC_PID_PSTFIRM 0x9023 /* Eagle IIC */ + +#define EAGLE_II_PID_PREFIRM 0x9022 /* Eagle II */ +#define EAGLE_II_PID_PSTFIRM 0x9021 /* Eagle II */ + +#define EAGLE_III_PID_PREFIRM 0x9032 /* Eagle III */ +#define EAGLE_III_PID_PSTFIRM 0x9031 /* Eagle III */ + +#define EAGLE_IV_PID_PREFIRM 0x9042 /* Eagle IV */ +#define EAGLE_IV_PID_PSTFIRM 0x9041 /* Eagle IV */ + +/* + * USR USB IDs + */ +#define USR_VID 0x0BAF +#define MILLER_A_PID_PREFIRM 0x00F2 +#define MILLER_A_PID_PSTFIRM 0x00F1 +#define MILLER_B_PID_PREFIRM 0x00FA +#define MILLER_B_PID_PSTFIRM 0x00F9 +#define HEINEKEN_A_PID_PREFIRM 0x00F6 +#define HEINEKEN_A_PID_PSTFIRM 0x00F5 +#define HEINEKEN_B_PID_PREFIRM 0x00F8 +#define HEINEKEN_B_PID_PSTFIRM 0x00F7 + +#define PREFIRM 0 +#define PSTFIRM (1<<7) +#define AUTO_ANNEX_A (1<<8) +#define AUTO_ANNEX_B (1<<9) + +enum { + ADI930 = 0, + EAGLE_I, + EAGLE_II, + EAGLE_III, + EAGLE_IV +}; + +/* macros for both struct usb_device_id and struct uea_softc */ +#define UEA_IS_PREFIRM(x) \ + (!((x)->driver_info & PSTFIRM)) +#define UEA_CHIP_VERSION(x) \ + ((x)->driver_info & 0xf) + +#define IS_ISDN(x) \ + ((x)->annex & ANNEXB) + +#define INS_TO_USBDEV(ins) (ins->usb_dev) + +#define GET_STATUS(data) \ + ((data >> 8) & 0xf) + +#define IS_OPERATIONAL(sc) \ + ((UEA_CHIP_VERSION(sc) != EAGLE_IV) ? \ + (GET_STATUS(sc->stats.phy.state) == 2) : \ + (sc->stats.phy.state == 7)) + +/* + * Set of macros to handle unaligned data in the firmware blob. + * The FW_GET_BYTE() macro is provided only for consistency. + */ + +#define FW_GET_BYTE(p) (*((__u8 *) (p))) + +#define FW_DIR "ueagle-atm/" +#define EAGLE_FIRMWARE FW_DIR "eagle.fw" +#define ADI930_FIRMWARE FW_DIR "adi930.fw" +#define EAGLE_I_FIRMWARE FW_DIR "eagleI.fw" +#define EAGLE_II_FIRMWARE FW_DIR "eagleII.fw" +#define EAGLE_III_FIRMWARE FW_DIR "eagleIII.fw" +#define EAGLE_IV_FIRMWARE FW_DIR "eagleIV.fw" + +#define DSP4I_FIRMWARE FW_DIR "DSP4i.bin" +#define DSP4P_FIRMWARE FW_DIR "DSP4p.bin" +#define DSP9I_FIRMWARE FW_DIR "DSP9i.bin" +#define DSP9P_FIRMWARE FW_DIR "DSP9p.bin" +#define DSPEI_FIRMWARE FW_DIR "DSPei.bin" +#define DSPEP_FIRMWARE FW_DIR "DSPep.bin" +#define FPGA930_FIRMWARE FW_DIR "930-fpga.bin" + +#define CMV4P_FIRMWARE FW_DIR "CMV4p.bin" +#define CMV4PV2_FIRMWARE FW_DIR "CMV4p.bin.v2" +#define CMV4I_FIRMWARE FW_DIR "CMV4i.bin" +#define CMV4IV2_FIRMWARE FW_DIR "CMV4i.bin.v2" +#define CMV9P_FIRMWARE FW_DIR "CMV9p.bin" +#define CMV9PV2_FIRMWARE FW_DIR "CMV9p.bin.v2" +#define CMV9I_FIRMWARE FW_DIR "CMV9i.bin" +#define CMV9IV2_FIRMWARE FW_DIR "CMV9i.bin.v2" +#define CMVEP_FIRMWARE FW_DIR "CMVep.bin" +#define CMVEPV2_FIRMWARE FW_DIR "CMVep.bin.v2" +#define CMVEI_FIRMWARE FW_DIR "CMVei.bin" +#define CMVEIV2_FIRMWARE FW_DIR "CMVei.bin.v2" + +#define UEA_FW_NAME_MAX 30 +#define NB_MODEM 4 + +#define BULK_TIMEOUT 300 +#define CTRL_TIMEOUT 1000 + +#define ACK_TIMEOUT msecs_to_jiffies(3000) + +#define UEA_INTR_IFACE_NO 0 +#define UEA_US_IFACE_NO 1 +#define UEA_DS_IFACE_NO 2 + +#define FASTEST_ISO_INTF 8 + +#define UEA_BULK_DATA_PIPE 0x02 +#define UEA_IDMA_PIPE 0x04 +#define UEA_INTR_PIPE 0x04 +#define UEA_ISO_DATA_PIPE 0x08 + +#define UEA_E1_SET_BLOCK 0x0001 +#define UEA_E4_SET_BLOCK 0x002c +#define UEA_SET_MODE 0x0003 +#define UEA_SET_2183_DATA 0x0004 +#define UEA_SET_TIMEOUT 0x0011 + +#define UEA_LOOPBACK_OFF 0x0002 +#define UEA_LOOPBACK_ON 0x0003 +#define UEA_BOOT_IDMA 0x0006 +#define UEA_START_RESET 0x0007 +#define UEA_END_RESET 0x0008 + +#define UEA_SWAP_MAILBOX (0x3fcd | 0x4000) +#define UEA_MPTX_START (0x3fce | 0x4000) +#define UEA_MPTX_MAILBOX (0x3fd6 | 0x4000) +#define UEA_MPRX_MAILBOX (0x3fdf | 0x4000) + +/* block information in eagle4 dsp firmware */ +struct block_index { + __le32 PageOffset; + __le32 NotLastBlock; + __le32 dummy; + __le32 PageSize; + __le32 PageAddress; + __le16 dummy1; + __le16 PageNumber; +} __packed; + +#define E4_IS_BOOT_PAGE(PageSize) ((le32_to_cpu(PageSize)) & 0x80000000) +#define E4_PAGE_BYTES(PageSize) ((le32_to_cpu(PageSize) & 0x7fffffff) * 4) + +#define E4_L1_STRING_HEADER 0x10 +#define E4_MAX_PAGE_NUMBER 0x58 +#define E4_NO_SWAPPAGE_HEADERS 0x31 + +/* l1_code is eagle4 dsp firmware format */ +struct l1_code { + u8 string_header[E4_L1_STRING_HEADER]; + u8 page_number_to_block_index[E4_MAX_PAGE_NUMBER]; + struct block_index page_header[E4_NO_SWAPPAGE_HEADERS]; + u8 code[0]; +} __packed; + +/* structures describing a block within a DSP page */ +struct block_info_e1 { + __le16 wHdr; + __le16 wAddress; + __le16 wSize; + __le16 wOvlOffset; + __le16 wOvl; /* overlay */ + __le16 wLast; +} __packed; +#define E1_BLOCK_INFO_SIZE 12 + +struct block_info_e4 { + __be16 wHdr; + __u8 bBootPage; + __u8 bPageNumber; + __be32 dwSize; + __be32 dwAddress; + __be16 wReserved; +} __packed; +#define E4_BLOCK_INFO_SIZE 14 + +#define UEA_BIHDR 0xabcd +#define UEA_RESERVED 0xffff + +/* constants describing cmv type */ +#define E1_PREAMBLE 0x535c +#define E1_MODEMTOHOST 0x01 +#define E1_HOSTTOMODEM 0x10 + +#define E1_MEMACCESS 0x1 +#define E1_ADSLDIRECTIVE 0x7 +#define E1_FUNCTION_TYPE(f) ((f) >> 4) +#define E1_FUNCTION_SUBTYPE(f) ((f) & 0x0f) + +#define E4_MEMACCESS 0 +#define E4_ADSLDIRECTIVE 0xf +#define E4_FUNCTION_TYPE(f) ((f) >> 8) +#define E4_FUNCTION_SIZE(f) ((f) & 0x0f) +#define E4_FUNCTION_SUBTYPE(f) (((f) >> 4) & 0x0f) + +/* for MEMACCESS */ +#define E1_REQUESTREAD 0x0 +#define E1_REQUESTWRITE 0x1 +#define E1_REPLYREAD 0x2 +#define E1_REPLYWRITE 0x3 + +#define E4_REQUESTREAD 0x0 +#define E4_REQUESTWRITE 0x4 +#define E4_REPLYREAD (E4_REQUESTREAD | 1) +#define E4_REPLYWRITE (E4_REQUESTWRITE | 1) + +/* for ADSLDIRECTIVE */ +#define E1_KERNELREADY 0x0 +#define E1_MODEMREADY 0x1 + +#define E4_KERNELREADY 0x0 +#define E4_MODEMREADY 0x1 + +#define E1_MAKEFUNCTION(t, s) (((t) & 0xf) << 4 | ((s) & 0xf)) +#define E4_MAKEFUNCTION(t, st, s) (((t) & 0xf) << 8 | \ + ((st) & 0xf) << 4 | ((s) & 0xf)) + +#define E1_MAKESA(a, b, c, d) \ + (((c) & 0xff) << 24 | \ + ((d) & 0xff) << 16 | \ + ((a) & 0xff) << 8 | \ + ((b) & 0xff)) + +#define E1_GETSA1(a) ((a >> 8) & 0xff) +#define E1_GETSA2(a) (a & 0xff) +#define E1_GETSA3(a) ((a >> 24) & 0xff) +#define E1_GETSA4(a) ((a >> 16) & 0xff) + +#define E1_SA_CNTL E1_MAKESA('C', 'N', 'T', 'L') +#define E1_SA_DIAG E1_MAKESA('D', 'I', 'A', 'G') +#define E1_SA_INFO E1_MAKESA('I', 'N', 'F', 'O') +#define E1_SA_OPTN E1_MAKESA('O', 'P', 'T', 'N') +#define E1_SA_RATE E1_MAKESA('R', 'A', 'T', 'E') +#define E1_SA_STAT E1_MAKESA('S', 'T', 'A', 'T') + +#define E4_SA_CNTL 1 +#define E4_SA_STAT 2 +#define E4_SA_INFO 3 +#define E4_SA_TEST 4 +#define E4_SA_OPTN 5 +#define E4_SA_RATE 6 +#define E4_SA_DIAG 7 +#define E4_SA_CNFG 8 + +/* structures representing a CMV (Configuration and Management Variable) */ +struct cmv_e1 { + __le16 wPreamble; + __u8 bDirection; + __u8 bFunction; + __le16 wIndex; + __le32 dwSymbolicAddress; + __le16 wOffsetAddress; + __le32 dwData; +} __packed; + +struct cmv_e4 { + __be16 wGroup; + __be16 wFunction; + __be16 wOffset; + __be16 wAddress; + __be32 dwData[6]; +} __packed; + +/* structures representing swap information */ +struct swap_info_e1 { + __u8 bSwapPageNo; + __u8 bOvl; /* overlay */ +} __packed; + +struct swap_info_e4 { + __u8 bSwapPageNo; +} __packed; + +/* structures representing interrupt data */ +#define e1_bSwapPageNo u.e1.s1.swapinfo.bSwapPageNo +#define e1_bOvl u.e1.s1.swapinfo.bOvl +#define e4_bSwapPageNo u.e4.s1.swapinfo.bSwapPageNo + +#define INT_LOADSWAPPAGE 0x0001 +#define INT_INCOMINGCMV 0x0002 + +union intr_data_e1 { + struct { + struct swap_info_e1 swapinfo; + __le16 wDataSize; + } __packed s1; + struct { + struct cmv_e1 cmv; + __le16 wDataSize; + } __packed s2; +} __packed; + +union intr_data_e4 { + struct { + struct swap_info_e4 swapinfo; + __le16 wDataSize; + } __packed s1; + struct { + struct cmv_e4 cmv; + __le16 wDataSize; + } __packed s2; +} __packed; + +struct intr_pkt { + __u8 bType; + __u8 bNotification; + __le16 wValue; + __le16 wIndex; + __le16 wLength; + __le16 wInterrupt; + union { + union intr_data_e1 e1; + union intr_data_e4 e4; + } u; +} __packed; + +#define E1_INTR_PKT_SIZE 28 +#define E4_INTR_PKT_SIZE 64 + +static struct usb_driver uea_driver; +static DEFINE_MUTEX(uea_mutex); +static const char * const chip_name[] = { + "ADI930", "Eagle I", "Eagle II", "Eagle III", "Eagle IV"}; + +static int modem_index; +static unsigned int debug; +static unsigned int altsetting[NB_MODEM] = { + [0 ... (NB_MODEM - 1)] = FASTEST_ISO_INTF}; +static bool sync_wait[NB_MODEM]; +static char *cmv_file[NB_MODEM]; +static int annex[NB_MODEM]; + +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "module debug level (0=off,1=on,2=verbose)"); +module_param_array(altsetting, uint, NULL, 0644); +MODULE_PARM_DESC(altsetting, "alternate setting for incoming traffic: 0=bulk, " + "1=isoc slowest, ... , 8=isoc fastest (default)"); +module_param_array(sync_wait, bool, NULL, 0644); +MODULE_PARM_DESC(sync_wait, "wait the synchronisation before starting ATM"); +module_param_array(cmv_file, charp, NULL, 0644); +MODULE_PARM_DESC(cmv_file, + "file name with configuration and management variables"); +module_param_array(annex, uint, NULL, 0644); +MODULE_PARM_DESC(annex, + "manually set annex a/b (0=auto, 1=annex a, 2=annex b)"); + +#define uea_wait(sc, cond, timeo) \ +({ \ + int _r = wait_event_interruptible_timeout(sc->sync_q, \ + (cond) || kthread_should_stop(), timeo); \ + if (kthread_should_stop()) \ + _r = -ENODEV; \ + _r; \ +}) + +#define UPDATE_ATM_STAT(type, val) \ + do { \ + if (sc->usbatm->atm_dev) \ + sc->usbatm->atm_dev->type = val; \ + } while (0) + +#define UPDATE_ATM_SIGNAL(val) \ + do { \ + if (sc->usbatm->atm_dev) \ + atm_dev_signal_change(sc->usbatm->atm_dev, val); \ + } while (0) + + +/* Firmware loading */ +#define LOAD_INTERNAL 0xA0 +#define F8051_USBCS 0x7f92 + +/** + * uea_send_modem_cmd - Send a command for pre-firmware devices. + */ +static int uea_send_modem_cmd(struct usb_device *usb, + u16 addr, u16 size, const u8 *buff) +{ + int ret = -ENOMEM; + u8 *xfer_buff; + + xfer_buff = kmemdup(buff, size, GFP_KERNEL); + if (xfer_buff) { + ret = usb_control_msg(usb, + usb_sndctrlpipe(usb, 0), + LOAD_INTERNAL, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, addr, 0, xfer_buff, + size, CTRL_TIMEOUT); + kfree(xfer_buff); + } + + if (ret < 0) + return ret; + + return (ret == size) ? 0 : -EIO; +} + +static void uea_upload_pre_firmware(const struct firmware *fw_entry, + void *context) +{ + struct usb_device *usb = context; + const u8 *pfw; + u8 value; + u32 crc = 0; + int ret, size; + + uea_enters(usb); + if (!fw_entry) { + uea_err(usb, "firmware is not available\n"); + goto err; + } + + pfw = fw_entry->data; + size = fw_entry->size; + if (size < 4) + goto err_fw_corrupted; + + crc = get_unaligned_le32(pfw); + pfw += 4; + size -= 4; + if (crc32_be(0, pfw, size) != crc) + goto err_fw_corrupted; + + /* + * Start to upload firmware : send reset + */ + value = 1; + ret = uea_send_modem_cmd(usb, F8051_USBCS, sizeof(value), &value); + + if (ret < 0) { + uea_err(usb, "modem reset failed with error %d\n", ret); + goto err; + } + + while (size > 3) { + u8 len = FW_GET_BYTE(pfw); + u16 add = get_unaligned_le16(pfw + 1); + + size -= len + 3; + if (size < 0) + goto err_fw_corrupted; + + ret = uea_send_modem_cmd(usb, add, len, pfw + 3); + if (ret < 0) { + uea_err(usb, "uploading firmware data failed " + "with error %d\n", ret); + goto err; + } + pfw += len + 3; + } + + if (size != 0) + goto err_fw_corrupted; + + /* + * Tell the modem we finish : de-assert reset + */ + value = 0; + ret = uea_send_modem_cmd(usb, F8051_USBCS, 1, &value); + if (ret < 0) + uea_err(usb, "modem de-assert failed with error %d\n", ret); + else + uea_info(usb, "firmware uploaded\n"); + + goto err; + +err_fw_corrupted: + uea_err(usb, "firmware is corrupted\n"); +err: + release_firmware(fw_entry); + uea_leaves(usb); +} + +/** + * uea_load_firmware - Load usb firmware for pre-firmware devices. + */ +static int uea_load_firmware(struct usb_device *usb, unsigned int ver) +{ + int ret; + char *fw_name = EAGLE_FIRMWARE; + + uea_enters(usb); + uea_info(usb, "pre-firmware device, uploading firmware\n"); + + switch (ver) { + case ADI930: + fw_name = ADI930_FIRMWARE; + break; + case EAGLE_I: + fw_name = EAGLE_I_FIRMWARE; + break; + case EAGLE_II: + fw_name = EAGLE_II_FIRMWARE; + break; + case EAGLE_III: + fw_name = EAGLE_III_FIRMWARE; + break; + case EAGLE_IV: + fw_name = EAGLE_IV_FIRMWARE; + break; + } + + ret = request_firmware_nowait(THIS_MODULE, 1, fw_name, &usb->dev, + GFP_KERNEL, usb, + uea_upload_pre_firmware); + if (ret) + uea_err(usb, "firmware %s is not available\n", fw_name); + else + uea_info(usb, "loading firmware %s\n", fw_name); + + uea_leaves(usb); + return ret; +} + +/* modem management : dsp firmware, send/read CMV, monitoring statistic + */ + +/* + * Make sure that the DSP code provided is safe to use. + */ +static int check_dsp_e1(const u8 *dsp, unsigned int len) +{ + u8 pagecount, blockcount; + u16 blocksize; + u32 pageoffset; + unsigned int i, j, p, pp; + + pagecount = FW_GET_BYTE(dsp); + p = 1; + + /* enough space for page offsets? */ + if (p + 4 * pagecount > len) + return 1; + + for (i = 0; i < pagecount; i++) { + + pageoffset = get_unaligned_le32(dsp + p); + p += 4; + + if (pageoffset == 0) + continue; + + /* enough space for blockcount? */ + if (pageoffset >= len) + return 1; + + pp = pageoffset; + blockcount = FW_GET_BYTE(dsp + pp); + pp += 1; + + for (j = 0; j < blockcount; j++) { + + /* enough space for block header? */ + if (pp + 4 > len) + return 1; + + pp += 2; /* skip blockaddr */ + blocksize = get_unaligned_le16(dsp + pp); + pp += 2; + + /* enough space for block data? */ + if (pp + blocksize > len) + return 1; + + pp += blocksize; + } + } + + return 0; +} + +static int check_dsp_e4(const u8 *dsp, int len) +{ + int i; + struct l1_code *p = (struct l1_code *) dsp; + unsigned int sum = p->code - dsp; + + if (len < sum) + return 1; + + if (strcmp("STRATIPHY ANEXA", p->string_header) != 0 && + strcmp("STRATIPHY ANEXB", p->string_header) != 0) + return 1; + + for (i = 0; i < E4_MAX_PAGE_NUMBER; i++) { + struct block_index *blockidx; + u8 blockno = p->page_number_to_block_index[i]; + if (blockno >= E4_NO_SWAPPAGE_HEADERS) + continue; + + do { + u64 l; + + if (blockno >= E4_NO_SWAPPAGE_HEADERS) + return 1; + + blockidx = &p->page_header[blockno++]; + if ((u8 *)(blockidx + 1) - dsp >= len) + return 1; + + if (le16_to_cpu(blockidx->PageNumber) != i) + return 1; + + l = E4_PAGE_BYTES(blockidx->PageSize); + sum += l; + l += le32_to_cpu(blockidx->PageOffset); + if (l > len) + return 1; + + /* zero is zero regardless endianes */ + } while (blockidx->NotLastBlock); + } + + return (sum == len) ? 0 : 1; +} + +/* + * send data to the idma pipe + * */ +static int uea_idma_write(struct uea_softc *sc, const void *data, u32 size) +{ + int ret = -ENOMEM; + u8 *xfer_buff; + int bytes_read; + + xfer_buff = kmemdup(data, size, GFP_KERNEL); + if (!xfer_buff) { + uea_err(INS_TO_USBDEV(sc), "can't allocate xfer_buff\n"); + return ret; + } + + ret = usb_bulk_msg(sc->usb_dev, + usb_sndbulkpipe(sc->usb_dev, UEA_IDMA_PIPE), + xfer_buff, size, &bytes_read, BULK_TIMEOUT); + + kfree(xfer_buff); + if (ret < 0) + return ret; + if (size != bytes_read) { + uea_err(INS_TO_USBDEV(sc), "size != bytes_read %d %d\n", size, + bytes_read); + return -EIO; + } + + return 0; +} + +static int request_dsp(struct uea_softc *sc) +{ + int ret; + char *dsp_name; + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + if (IS_ISDN(sc)) + dsp_name = DSP4I_FIRMWARE; + else + dsp_name = DSP4P_FIRMWARE; + } else if (UEA_CHIP_VERSION(sc) == ADI930) { + if (IS_ISDN(sc)) + dsp_name = DSP9I_FIRMWARE; + else + dsp_name = DSP9P_FIRMWARE; + } else { + if (IS_ISDN(sc)) + dsp_name = DSPEI_FIRMWARE; + else + dsp_name = DSPEP_FIRMWARE; + } + + ret = request_firmware(&sc->dsp_firm, dsp_name, &sc->usb_dev->dev); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "requesting firmware %s failed with error %d\n", + dsp_name, ret); + return ret; + } + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) + ret = check_dsp_e4(sc->dsp_firm->data, sc->dsp_firm->size); + else + ret = check_dsp_e1(sc->dsp_firm->data, sc->dsp_firm->size); + + if (ret) { + uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", + dsp_name); + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + return -EILSEQ; + } + + return 0; +} + +/* + * The uea_load_page() function must be called within a process context + */ +static void uea_load_page_e1(struct work_struct *work) +{ + struct uea_softc *sc = container_of(work, struct uea_softc, task); + u16 pageno = sc->pageno; + u16 ovl = sc->ovl; + struct block_info_e1 bi; + + const u8 *p; + u8 pagecount, blockcount; + u16 blockaddr, blocksize; + u32 pageoffset; + int i; + + /* reload firmware when reboot start and it's loaded already */ + if (ovl == 0 && pageno == 0 && sc->dsp_firm) { + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + } + + if (sc->dsp_firm == NULL && request_dsp(sc) < 0) + return; + + p = sc->dsp_firm->data; + pagecount = FW_GET_BYTE(p); + p += 1; + + if (pageno >= pagecount) + goto bad1; + + p += 4 * pageno; + pageoffset = get_unaligned_le32(p); + + if (pageoffset == 0) + goto bad1; + + p = sc->dsp_firm->data + pageoffset; + blockcount = FW_GET_BYTE(p); + p += 1; + + uea_dbg(INS_TO_USBDEV(sc), + "sending %u blocks for DSP page %u\n", blockcount, pageno); + + bi.wHdr = cpu_to_le16(UEA_BIHDR); + bi.wOvl = cpu_to_le16(ovl); + bi.wOvlOffset = cpu_to_le16(ovl | 0x8000); + + for (i = 0; i < blockcount; i++) { + blockaddr = get_unaligned_le16(p); + p += 2; + + blocksize = get_unaligned_le16(p); + p += 2; + + bi.wSize = cpu_to_le16(blocksize); + bi.wAddress = cpu_to_le16(blockaddr); + bi.wLast = cpu_to_le16((i == blockcount - 1) ? 1 : 0); + + /* send block info through the IDMA pipe */ + if (uea_idma_write(sc, &bi, E1_BLOCK_INFO_SIZE)) + goto bad2; + + /* send block data through the IDMA pipe */ + if (uea_idma_write(sc, p, blocksize)) + goto bad2; + + p += blocksize; + } + + return; + +bad2: + uea_err(INS_TO_USBDEV(sc), "sending DSP block %u failed\n", i); + return; +bad1: + uea_err(INS_TO_USBDEV(sc), "invalid DSP page %u requested\n", pageno); +} + +static void __uea_load_page_e4(struct uea_softc *sc, u8 pageno, int boot) +{ + struct block_info_e4 bi; + struct block_index *blockidx; + struct l1_code *p = (struct l1_code *) sc->dsp_firm->data; + u8 blockno = p->page_number_to_block_index[pageno]; + + bi.wHdr = cpu_to_be16(UEA_BIHDR); + bi.bBootPage = boot; + bi.bPageNumber = pageno; + bi.wReserved = cpu_to_be16(UEA_RESERVED); + + do { + const u8 *blockoffset; + unsigned int blocksize; + + blockidx = &p->page_header[blockno]; + blocksize = E4_PAGE_BYTES(blockidx->PageSize); + blockoffset = sc->dsp_firm->data + le32_to_cpu( + blockidx->PageOffset); + + bi.dwSize = cpu_to_be32(blocksize); + bi.dwAddress = cpu_to_be32(le32_to_cpu(blockidx->PageAddress)); + + uea_dbg(INS_TO_USBDEV(sc), + "sending block %u for DSP page " + "%u size %u address %x\n", + blockno, pageno, blocksize, + le32_to_cpu(blockidx->PageAddress)); + + /* send block info through the IDMA pipe */ + if (uea_idma_write(sc, &bi, E4_BLOCK_INFO_SIZE)) + goto bad; + + /* send block data through the IDMA pipe */ + if (uea_idma_write(sc, blockoffset, blocksize)) + goto bad; + + blockno++; + } while (blockidx->NotLastBlock); + + return; + +bad: + uea_err(INS_TO_USBDEV(sc), "sending DSP block %u failed\n", blockno); + return; +} + +static void uea_load_page_e4(struct work_struct *work) +{ + struct uea_softc *sc = container_of(work, struct uea_softc, task); + u8 pageno = sc->pageno; + int i; + struct block_info_e4 bi; + struct l1_code *p; + + uea_dbg(INS_TO_USBDEV(sc), "sending DSP page %u\n", pageno); + + /* reload firmware when reboot start and it's loaded already */ + if (pageno == 0 && sc->dsp_firm) { + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + } + + if (sc->dsp_firm == NULL && request_dsp(sc) < 0) + return; + + p = (struct l1_code *) sc->dsp_firm->data; + if (pageno >= le16_to_cpu(p->page_header[0].PageNumber)) { + uea_err(INS_TO_USBDEV(sc), "invalid DSP " + "page %u requested\n", pageno); + return; + } + + if (pageno != 0) { + __uea_load_page_e4(sc, pageno, 0); + return; + } + + uea_dbg(INS_TO_USBDEV(sc), + "sending Main DSP page %u\n", p->page_header[0].PageNumber); + + for (i = 0; i < le16_to_cpu(p->page_header[0].PageNumber); i++) { + if (E4_IS_BOOT_PAGE(p->page_header[i].PageSize)) + __uea_load_page_e4(sc, i, 1); + } + + uea_dbg(INS_TO_USBDEV(sc) , "sending start bi\n"); + + bi.wHdr = cpu_to_be16(UEA_BIHDR); + bi.bBootPage = 0; + bi.bPageNumber = 0xff; + bi.wReserved = cpu_to_be16(UEA_RESERVED); + bi.dwSize = cpu_to_be32(E4_PAGE_BYTES(p->page_header[0].PageSize)); + bi.dwAddress = cpu_to_be32(le32_to_cpu(p->page_header[0].PageAddress)); + + /* send block info through the IDMA pipe */ + if (uea_idma_write(sc, &bi, E4_BLOCK_INFO_SIZE)) + uea_err(INS_TO_USBDEV(sc), "sending DSP start bi failed\n"); +} + +static inline void wake_up_cmv_ack(struct uea_softc *sc) +{ + BUG_ON(sc->cmv_ack); + sc->cmv_ack = 1; + wake_up(&sc->sync_q); +} + +static inline int wait_cmv_ack(struct uea_softc *sc) +{ + int ret = uea_wait(sc, sc->cmv_ack , ACK_TIMEOUT); + + sc->cmv_ack = 0; + + uea_dbg(INS_TO_USBDEV(sc), "wait_event_timeout : %d ms\n", + jiffies_to_msecs(ret)); + + if (ret < 0) + return ret; + + return (ret == 0) ? -ETIMEDOUT : 0; +} + +#define UCDC_SEND_ENCAPSULATED_COMMAND 0x00 + +static int uea_request(struct uea_softc *sc, + u16 value, u16 index, u16 size, const void *data) +{ + u8 *xfer_buff; + int ret = -ENOMEM; + + xfer_buff = kmemdup(data, size, GFP_KERNEL); + if (!xfer_buff) { + uea_err(INS_TO_USBDEV(sc), "can't allocate xfer_buff\n"); + return ret; + } + + ret = usb_control_msg(sc->usb_dev, usb_sndctrlpipe(sc->usb_dev, 0), + UCDC_SEND_ENCAPSULATED_COMMAND, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, xfer_buff, size, CTRL_TIMEOUT); + + kfree(xfer_buff); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), "usb_control_msg error %d\n", ret); + return ret; + } + + if (ret != size) { + uea_err(INS_TO_USBDEV(sc), + "usb_control_msg send only %d bytes (instead of %d)\n", + ret, size); + return -EIO; + } + + return 0; +} + +static int uea_cmv_e1(struct uea_softc *sc, + u8 function, u32 address, u16 offset, u32 data) +{ + struct cmv_e1 cmv; + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Address : %c%c%c%c, " + "offset : 0x%04x, data : 0x%08x\n", + E1_FUNCTION_TYPE(function), + E1_FUNCTION_SUBTYPE(function), + E1_GETSA1(address), E1_GETSA2(address), + E1_GETSA3(address), + E1_GETSA4(address), offset, data); + + /* we send a request, but we expect a reply */ + sc->cmv_dsc.e1.function = function | 0x2; + sc->cmv_dsc.e1.idx++; + sc->cmv_dsc.e1.address = address; + sc->cmv_dsc.e1.offset = offset; + + cmv.wPreamble = cpu_to_le16(E1_PREAMBLE); + cmv.bDirection = E1_HOSTTOMODEM; + cmv.bFunction = function; + cmv.wIndex = cpu_to_le16(sc->cmv_dsc.e1.idx); + put_unaligned_le32(address, &cmv.dwSymbolicAddress); + cmv.wOffsetAddress = cpu_to_le16(offset); + put_unaligned_le32(data >> 16 | data << 16, &cmv.dwData); + + ret = uea_request(sc, UEA_E1_SET_BLOCK, UEA_MPTX_START, + sizeof(cmv), &cmv); + if (ret < 0) + return ret; + ret = wait_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +static int uea_cmv_e4(struct uea_softc *sc, + u16 function, u16 group, u16 address, u16 offset, u32 data) +{ + struct cmv_e4 cmv; + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + memset(&cmv, 0, sizeof(cmv)); + + uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Group : 0x%04x, " + "Address : 0x%04x, offset : 0x%04x, data : 0x%08x\n", + E4_FUNCTION_TYPE(function), E4_FUNCTION_SUBTYPE(function), + group, address, offset, data); + + /* we send a request, but we expect a reply */ + sc->cmv_dsc.e4.function = function | (0x1 << 4); + sc->cmv_dsc.e4.offset = offset; + sc->cmv_dsc.e4.address = address; + sc->cmv_dsc.e4.group = group; + + cmv.wFunction = cpu_to_be16(function); + cmv.wGroup = cpu_to_be16(group); + cmv.wAddress = cpu_to_be16(address); + cmv.wOffset = cpu_to_be16(offset); + cmv.dwData[0] = cpu_to_be32(data); + + ret = uea_request(sc, UEA_E4_SET_BLOCK, UEA_MPTX_START, + sizeof(cmv), &cmv); + if (ret < 0) + return ret; + ret = wait_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +static inline int uea_read_cmv_e1(struct uea_softc *sc, + u32 address, u16 offset, u32 *data) +{ + int ret = uea_cmv_e1(sc, E1_MAKEFUNCTION(E1_MEMACCESS, E1_REQUESTREAD), + address, offset, 0); + if (ret < 0) + uea_err(INS_TO_USBDEV(sc), + "reading cmv failed with error %d\n", ret); + else + *data = sc->data; + + return ret; +} + +static inline int uea_read_cmv_e4(struct uea_softc *sc, + u8 size, u16 group, u16 address, u16 offset, u32 *data) +{ + int ret = uea_cmv_e4(sc, E4_MAKEFUNCTION(E4_MEMACCESS, + E4_REQUESTREAD, size), + group, address, offset, 0); + if (ret < 0) + uea_err(INS_TO_USBDEV(sc), + "reading cmv failed with error %d\n", ret); + else { + *data = sc->data; + /* size is in 16-bit word quantities */ + if (size > 2) + *(data + 1) = sc->data1; + } + return ret; +} + +static inline int uea_write_cmv_e1(struct uea_softc *sc, + u32 address, u16 offset, u32 data) +{ + int ret = uea_cmv_e1(sc, E1_MAKEFUNCTION(E1_MEMACCESS, E1_REQUESTWRITE), + address, offset, data); + if (ret < 0) + uea_err(INS_TO_USBDEV(sc), + "writing cmv failed with error %d\n", ret); + + return ret; +} + +static inline int uea_write_cmv_e4(struct uea_softc *sc, + u8 size, u16 group, u16 address, u16 offset, u32 data) +{ + int ret = uea_cmv_e4(sc, E4_MAKEFUNCTION(E4_MEMACCESS, + E4_REQUESTWRITE, size), + group, address, offset, data); + if (ret < 0) + uea_err(INS_TO_USBDEV(sc), + "writing cmv failed with error %d\n", ret); + + return ret; +} + +static void uea_set_bulk_timeout(struct uea_softc *sc, u32 dsrate) +{ + int ret; + u16 timeout; + + /* in bulk mode the modem have problem with high rate + * changing internal timing could improve things, but the + * value is mysterious. + * ADI930 don't support it (-EPIPE error). + */ + + if (UEA_CHIP_VERSION(sc) == ADI930 || + altsetting[sc->modem_index] > 0 || + sc->stats.phy.dsrate == dsrate) + return; + + /* Original timming (1Mbit/s) from ADI (used in windows driver) */ + timeout = (dsrate <= 1024*1024) ? 0 : 1; + ret = uea_request(sc, UEA_SET_TIMEOUT, timeout, 0, NULL); + uea_info(INS_TO_USBDEV(sc), "setting new timeout %d%s\n", + timeout, ret < 0 ? " failed" : ""); + +} + +/* + * Monitor the modem and update the stat + * return 0 if everything is ok + * return < 0 if an error occurs (-EAGAIN reboot needed) + */ +static int uea_stat_e1(struct uea_softc *sc) +{ + u32 data; + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + data = sc->stats.phy.state; + + ret = uea_read_cmv_e1(sc, E1_SA_STAT, 0, &sc->stats.phy.state); + if (ret < 0) + return ret; + + switch (GET_STATUS(sc->stats.phy.state)) { + case 0: /* not yet synchronized */ + uea_dbg(INS_TO_USBDEV(sc), + "modem not yet synchronized\n"); + return 0; + + case 1: /* initialization */ + uea_dbg(INS_TO_USBDEV(sc), "modem initializing\n"); + return 0; + + case 2: /* operational */ + uea_vdbg(INS_TO_USBDEV(sc), "modem operational\n"); + break; + + case 3: /* fail ... */ + uea_info(INS_TO_USBDEV(sc), "modem synchronization failed" + " (may be try other cmv/dsp)\n"); + return -EAGAIN; + + case 4 ... 6: /* test state */ + uea_warn(INS_TO_USBDEV(sc), + "modem in test mode - not supported\n"); + return -EAGAIN; + + case 7: /* fast-retain ... */ + uea_info(INS_TO_USBDEV(sc), "modem in fast-retain mode\n"); + return 0; + default: + uea_err(INS_TO_USBDEV(sc), "modem invalid SW mode %d\n", + GET_STATUS(sc->stats.phy.state)); + return -EAGAIN; + } + + if (GET_STATUS(data) != 2) { + uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_OFF, 0, NULL); + uea_info(INS_TO_USBDEV(sc), "modem operational\n"); + + /* release the dsp firmware as it is not needed until + * the next failure + */ + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + } + + /* always update it as atm layer could not be init when we switch to + * operational state + */ + UPDATE_ATM_SIGNAL(ATM_PHY_SIG_FOUND); + + /* wake up processes waiting for synchronization */ + wake_up(&sc->sync_q); + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 2, &sc->stats.phy.flags); + if (ret < 0) + return ret; + sc->stats.phy.mflags |= sc->stats.phy.flags; + + /* in case of a flags ( for example delineation LOSS (& 0x10)), + * we check the status again in order to detect the failure earlier + */ + if (sc->stats.phy.flags) { + uea_dbg(INS_TO_USBDEV(sc), "Stat flag = 0x%x\n", + sc->stats.phy.flags); + return 0; + } + + ret = uea_read_cmv_e1(sc, E1_SA_RATE, 0, &data); + if (ret < 0) + return ret; + + uea_set_bulk_timeout(sc, (data >> 16) * 32); + sc->stats.phy.dsrate = (data >> 16) * 32; + sc->stats.phy.usrate = (data & 0xffff) * 32; + UPDATE_ATM_STAT(link_rate, sc->stats.phy.dsrate * 1000 / 424); + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 23, &data); + if (ret < 0) + return ret; + sc->stats.phy.dsattenuation = (data & 0xff) / 2; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 47, &data); + if (ret < 0) + return ret; + sc->stats.phy.usattenuation = (data & 0xff) / 2; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 25, &sc->stats.phy.dsmargin); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 49, &sc->stats.phy.usmargin); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 51, &sc->stats.phy.rxflow); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 52, &sc->stats.phy.txflow); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 54, &sc->stats.phy.dsunc); + if (ret < 0) + return ret; + + /* only for atu-c */ + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 58, &sc->stats.phy.usunc); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 53, &sc->stats.phy.dscorr); + if (ret < 0) + return ret; + + /* only for atu-c */ + ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 57, &sc->stats.phy.uscorr); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_INFO, 8, &sc->stats.phy.vidco); + if (ret < 0) + return ret; + + ret = uea_read_cmv_e1(sc, E1_SA_INFO, 13, &sc->stats.phy.vidcpe); + if (ret < 0) + return ret; + + return 0; +} + +static int uea_stat_e4(struct uea_softc *sc) +{ + u32 data; + u32 tmp_arr[2]; + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + data = sc->stats.phy.state; + + /* XXX only need to be done before operationnal... */ + ret = uea_read_cmv_e4(sc, 1, E4_SA_STAT, 0, 0, &sc->stats.phy.state); + if (ret < 0) + return ret; + + switch (sc->stats.phy.state) { + case 0x0: /* not yet synchronized */ + case 0x1: + case 0x3: + case 0x4: + uea_dbg(INS_TO_USBDEV(sc), "modem not yet " + "synchronized\n"); + return 0; + case 0x5: /* initialization */ + case 0x6: + case 0x9: + case 0xa: + uea_dbg(INS_TO_USBDEV(sc), "modem initializing\n"); + return 0; + case 0x2: /* fail ... */ + uea_info(INS_TO_USBDEV(sc), "modem synchronization " + "failed (may be try other cmv/dsp)\n"); + return -EAGAIN; + case 0x7: /* operational */ + break; + default: + uea_warn(INS_TO_USBDEV(sc), "unknown state: %x\n", + sc->stats.phy.state); + return 0; + } + + if (data != 7) { + uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_OFF, 0, NULL); + uea_info(INS_TO_USBDEV(sc), "modem operational\n"); + + /* release the dsp firmware as it is not needed until + * the next failure + */ + release_firmware(sc->dsp_firm); + sc->dsp_firm = NULL; + } + + /* always update it as atm layer could not be init when we switch to + * operational state + */ + UPDATE_ATM_SIGNAL(ATM_PHY_SIG_FOUND); + + /* wake up processes waiting for synchronization */ + wake_up(&sc->sync_q); + + /* TODO improve this state machine : + * we need some CMV info : what they do and their unit + * we should find the equivalent of eagle3- CMV + */ + /* check flags */ + ret = uea_read_cmv_e4(sc, 1, E4_SA_DIAG, 0, 0, &sc->stats.phy.flags); + if (ret < 0) + return ret; + sc->stats.phy.mflags |= sc->stats.phy.flags; + + /* in case of a flags ( for example delineation LOSS (& 0x10)), + * we check the status again in order to detect the failure earlier + */ + if (sc->stats.phy.flags) { + uea_dbg(INS_TO_USBDEV(sc), "Stat flag = 0x%x\n", + sc->stats.phy.flags); + if (sc->stats.phy.flags & 1) /* delineation LOSS */ + return -EAGAIN; + if (sc->stats.phy.flags & 0x4000) /* Reset Flag */ + return -EAGAIN; + return 0; + } + + /* rate data may be in upper or lower half of 64 bit word, strange */ + ret = uea_read_cmv_e4(sc, 4, E4_SA_RATE, 0, 0, tmp_arr); + if (ret < 0) + return ret; + data = (tmp_arr[0]) ? tmp_arr[0] : tmp_arr[1]; + sc->stats.phy.usrate = data / 1000; + + ret = uea_read_cmv_e4(sc, 4, E4_SA_RATE, 1, 0, tmp_arr); + if (ret < 0) + return ret; + data = (tmp_arr[0]) ? tmp_arr[0] : tmp_arr[1]; + uea_set_bulk_timeout(sc, data / 1000); + sc->stats.phy.dsrate = data / 1000; + UPDATE_ATM_STAT(link_rate, sc->stats.phy.dsrate * 1000 / 424); + + ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 68, 1, &data); + if (ret < 0) + return ret; + sc->stats.phy.dsattenuation = data / 10; + + ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 69, 1, &data); + if (ret < 0) + return ret; + sc->stats.phy.usattenuation = data / 10; + + ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 68, 3, &data); + if (ret < 0) + return ret; + sc->stats.phy.dsmargin = data / 2; + + ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 69, 3, &data); + if (ret < 0) + return ret; + sc->stats.phy.usmargin = data / 10; + + return 0; +} + +static void cmvs_file_name(struct uea_softc *sc, char *const cmv_name, int ver) +{ + char file_arr[] = "CMVxy.bin"; + char *file; + + kparam_block_sysfs_write(cmv_file); + /* set proper name corresponding modem version and line type */ + if (cmv_file[sc->modem_index] == NULL) { + if (UEA_CHIP_VERSION(sc) == ADI930) + file_arr[3] = '9'; + else if (UEA_CHIP_VERSION(sc) == EAGLE_IV) + file_arr[3] = '4'; + else + file_arr[3] = 'e'; + + file_arr[4] = IS_ISDN(sc) ? 'i' : 'p'; + file = file_arr; + } else + file = cmv_file[sc->modem_index]; + + strcpy(cmv_name, FW_DIR); + strlcat(cmv_name, file, UEA_FW_NAME_MAX); + if (ver == 2) + strlcat(cmv_name, ".v2", UEA_FW_NAME_MAX); + kparam_unblock_sysfs_write(cmv_file); +} + +static int request_cmvs_old(struct uea_softc *sc, + void **cmvs, const struct firmware **fw) +{ + int ret, size; + u8 *data; + char cmv_name[UEA_FW_NAME_MAX]; /* 30 bytes stack variable */ + + cmvs_file_name(sc, cmv_name, 1); + ret = request_firmware(fw, cmv_name, &sc->usb_dev->dev); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "requesting firmware %s failed with error %d\n", + cmv_name, ret); + return ret; + } + + data = (u8 *) (*fw)->data; + size = (*fw)->size; + if (size < 1) + goto err_fw_corrupted; + + if (size != *data * sizeof(struct uea_cmvs_v1) + 1) + goto err_fw_corrupted; + + *cmvs = (void *)(data + 1); + return *data; + +err_fw_corrupted: + uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", cmv_name); + release_firmware(*fw); + return -EILSEQ; +} + +static int request_cmvs(struct uea_softc *sc, + void **cmvs, const struct firmware **fw, int *ver) +{ + int ret, size; + u32 crc; + u8 *data; + char cmv_name[UEA_FW_NAME_MAX]; /* 30 bytes stack variable */ + + cmvs_file_name(sc, cmv_name, 2); + ret = request_firmware(fw, cmv_name, &sc->usb_dev->dev); + if (ret < 0) { + /* if caller can handle old version, try to provide it */ + if (*ver == 1) { + uea_warn(INS_TO_USBDEV(sc), "requesting " + "firmware %s failed, " + "try to get older cmvs\n", cmv_name); + return request_cmvs_old(sc, cmvs, fw); + } + uea_err(INS_TO_USBDEV(sc), + "requesting firmware %s failed with error %d\n", + cmv_name, ret); + return ret; + } + + size = (*fw)->size; + data = (u8 *) (*fw)->data; + if (size < 4 || strncmp(data, "cmv2", 4) != 0) { + if (*ver == 1) { + uea_warn(INS_TO_USBDEV(sc), "firmware %s is corrupted," + " try to get older cmvs\n", cmv_name); + release_firmware(*fw); + return request_cmvs_old(sc, cmvs, fw); + } + goto err_fw_corrupted; + } + + *ver = 2; + + data += 4; + size -= 4; + if (size < 5) + goto err_fw_corrupted; + + crc = get_unaligned_le32(data); + data += 4; + size -= 4; + if (crc32_be(0, data, size) != crc) + goto err_fw_corrupted; + + if (size != *data * sizeof(struct uea_cmvs_v2) + 1) + goto err_fw_corrupted; + + *cmvs = (void *) (data + 1); + return *data; + +err_fw_corrupted: + uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", cmv_name); + release_firmware(*fw); + return -EILSEQ; +} + +static int uea_send_cmvs_e1(struct uea_softc *sc) +{ + int i, ret, len; + void *cmvs_ptr; + const struct firmware *cmvs_fw; + int ver = 1; /* we can handle v1 cmv firmware version; */ + + /* Enter in R-IDLE (cmv) until instructed otherwise */ + ret = uea_write_cmv_e1(sc, E1_SA_CNTL, 0, 1); + if (ret < 0) + return ret; + + /* Dump firmware version */ + ret = uea_read_cmv_e1(sc, E1_SA_INFO, 10, &sc->stats.phy.firmid); + if (ret < 0) + return ret; + uea_info(INS_TO_USBDEV(sc), "ATU-R firmware version : %x\n", + sc->stats.phy.firmid); + + /* get options */ + ret = len = request_cmvs(sc, &cmvs_ptr, &cmvs_fw, &ver); + if (ret < 0) + return ret; + + /* send options */ + if (ver == 1) { + struct uea_cmvs_v1 *cmvs_v1 = cmvs_ptr; + + uea_warn(INS_TO_USBDEV(sc), "use deprecated cmvs version, " + "please update your firmware\n"); + + for (i = 0; i < len; i++) { + ret = uea_write_cmv_e1(sc, + get_unaligned_le32(&cmvs_v1[i].address), + get_unaligned_le16(&cmvs_v1[i].offset), + get_unaligned_le32(&cmvs_v1[i].data)); + if (ret < 0) + goto out; + } + } else if (ver == 2) { + struct uea_cmvs_v2 *cmvs_v2 = cmvs_ptr; + + for (i = 0; i < len; i++) { + ret = uea_write_cmv_e1(sc, + get_unaligned_le32(&cmvs_v2[i].address), + (u16) get_unaligned_le32(&cmvs_v2[i].offset), + get_unaligned_le32(&cmvs_v2[i].data)); + if (ret < 0) + goto out; + } + } else { + /* This really should not happen */ + uea_err(INS_TO_USBDEV(sc), "bad cmvs version %d\n", ver); + goto out; + } + + /* Enter in R-ACT-REQ */ + ret = uea_write_cmv_e1(sc, E1_SA_CNTL, 0, 2); + uea_vdbg(INS_TO_USBDEV(sc), "Entering in R-ACT-REQ state\n"); + uea_info(INS_TO_USBDEV(sc), "modem started, waiting " + "synchronization...\n"); +out: + release_firmware(cmvs_fw); + return ret; +} + +static int uea_send_cmvs_e4(struct uea_softc *sc) +{ + int i, ret, len; + void *cmvs_ptr; + const struct firmware *cmvs_fw; + int ver = 2; /* we can only handle v2 cmv firmware version; */ + + /* Enter in R-IDLE (cmv) until instructed otherwise */ + ret = uea_write_cmv_e4(sc, 1, E4_SA_CNTL, 0, 0, 1); + if (ret < 0) + return ret; + + /* Dump firmware version */ + /* XXX don't read the 3th byte as it is always 6 */ + ret = uea_read_cmv_e4(sc, 2, E4_SA_INFO, 55, 0, &sc->stats.phy.firmid); + if (ret < 0) + return ret; + uea_info(INS_TO_USBDEV(sc), "ATU-R firmware version : %x\n", + sc->stats.phy.firmid); + + + /* get options */ + ret = len = request_cmvs(sc, &cmvs_ptr, &cmvs_fw, &ver); + if (ret < 0) + return ret; + + /* send options */ + if (ver == 2) { + struct uea_cmvs_v2 *cmvs_v2 = cmvs_ptr; + + for (i = 0; i < len; i++) { + ret = uea_write_cmv_e4(sc, 1, + get_unaligned_le32(&cmvs_v2[i].group), + get_unaligned_le32(&cmvs_v2[i].address), + get_unaligned_le32(&cmvs_v2[i].offset), + get_unaligned_le32(&cmvs_v2[i].data)); + if (ret < 0) + goto out; + } + } else { + /* This really should not happen */ + uea_err(INS_TO_USBDEV(sc), "bad cmvs version %d\n", ver); + goto out; + } + + /* Enter in R-ACT-REQ */ + ret = uea_write_cmv_e4(sc, 1, E4_SA_CNTL, 0, 0, 2); + uea_vdbg(INS_TO_USBDEV(sc), "Entering in R-ACT-REQ state\n"); + uea_info(INS_TO_USBDEV(sc), "modem started, waiting " + "synchronization...\n"); +out: + release_firmware(cmvs_fw); + return ret; +} + +/* Start boot post firmware modem: + * - send reset commands through usb control pipe + * - start workqueue for DSP loading + * - send CMV options to modem + */ + +static int uea_start_reset(struct uea_softc *sc) +{ + u16 zero = 0; /* ;-) */ + int ret; + + uea_enters(INS_TO_USBDEV(sc)); + uea_info(INS_TO_USBDEV(sc), "(re)booting started\n"); + + /* mask interrupt */ + sc->booting = 1; + /* We need to set this here because, a ack timeout could have occurred, + * but before we start the reboot, the ack occurs and set this to 1. + * So we will failed to wait Ready CMV. + */ + sc->cmv_ack = 0; + UPDATE_ATM_SIGNAL(ATM_PHY_SIG_LOST); + + /* reset statistics */ + memset(&sc->stats, 0, sizeof(struct uea_stats)); + + /* tell the modem that we want to boot in IDMA mode */ + uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_ON, 0, NULL); + uea_request(sc, UEA_SET_MODE, UEA_BOOT_IDMA, 0, NULL); + + /* enter reset mode */ + uea_request(sc, UEA_SET_MODE, UEA_START_RESET, 0, NULL); + + /* original driver use 200ms, but windows driver use 100ms */ + ret = uea_wait(sc, 0, msecs_to_jiffies(100)); + if (ret < 0) + return ret; + + /* leave reset mode */ + uea_request(sc, UEA_SET_MODE, UEA_END_RESET, 0, NULL); + + if (UEA_CHIP_VERSION(sc) != EAGLE_IV) { + /* clear tx and rx mailboxes */ + uea_request(sc, UEA_SET_2183_DATA, UEA_MPTX_MAILBOX, 2, &zero); + uea_request(sc, UEA_SET_2183_DATA, UEA_MPRX_MAILBOX, 2, &zero); + uea_request(sc, UEA_SET_2183_DATA, UEA_SWAP_MAILBOX, 2, &zero); + } + + ret = uea_wait(sc, 0, msecs_to_jiffies(1000)); + if (ret < 0) + return ret; + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) + sc->cmv_dsc.e4.function = E4_MAKEFUNCTION(E4_ADSLDIRECTIVE, + E4_MODEMREADY, 1); + else + sc->cmv_dsc.e1.function = E1_MAKEFUNCTION(E1_ADSLDIRECTIVE, + E1_MODEMREADY); + + /* demask interrupt */ + sc->booting = 0; + + /* start loading DSP */ + sc->pageno = 0; + sc->ovl = 0; + schedule_work(&sc->task); + + /* wait for modem ready CMV */ + ret = wait_cmv_ack(sc); + if (ret < 0) + return ret; + + uea_vdbg(INS_TO_USBDEV(sc), "Ready CMV received\n"); + + ret = sc->send_cmvs(sc); + if (ret < 0) + return ret; + + sc->reset = 0; + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +/* + * In case of an error wait 1s before rebooting the modem + * if the modem don't request reboot (-EAGAIN). + * Monitor the modem every 1s. + */ + +static int uea_kthread(void *data) +{ + struct uea_softc *sc = data; + int ret = -EAGAIN; + + set_freezable(); + uea_enters(INS_TO_USBDEV(sc)); + while (!kthread_should_stop()) { + if (ret < 0 || sc->reset) + ret = uea_start_reset(sc); + if (!ret) + ret = sc->stat(sc); + if (ret != -EAGAIN) + uea_wait(sc, 0, msecs_to_jiffies(1000)); + try_to_freeze(); + } + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +/* Load second usb firmware for ADI930 chip */ +static int load_XILINX_firmware(struct uea_softc *sc) +{ + const struct firmware *fw_entry; + int ret, size, u, ln; + const u8 *pfw; + u8 value; + char *fw_name = FPGA930_FIRMWARE; + + uea_enters(INS_TO_USBDEV(sc)); + + ret = request_firmware(&fw_entry, fw_name, &sc->usb_dev->dev); + if (ret) { + uea_err(INS_TO_USBDEV(sc), "firmware %s is not available\n", + fw_name); + goto err0; + } + + pfw = fw_entry->data; + size = fw_entry->size; + if (size != 0x577B) { + uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", + fw_name); + ret = -EILSEQ; + goto err1; + } + for (u = 0; u < size; u += ln) { + ln = min(size - u, 64); + ret = uea_request(sc, 0xe, 0, ln, pfw + u); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "elsa download data failed (%d)\n", ret); + goto err1; + } + } + + /* finish to send the fpga */ + ret = uea_request(sc, 0xe, 1, 0, NULL); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "elsa download data failed (%d)\n", ret); + goto err1; + } + + /* Tell the modem we finish : de-assert reset */ + value = 0; + ret = uea_send_modem_cmd(sc->usb_dev, 0xe, 1, &value); + if (ret < 0) + uea_err(sc->usb_dev, "elsa de-assert failed with error" + " %d\n", ret); + +err1: + release_firmware(fw_entry); +err0: + uea_leaves(INS_TO_USBDEV(sc)); + return ret; +} + +/* The modem send us an ack. First with check if it right */ +static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr) +{ + struct cmv_dsc_e1 *dsc = &sc->cmv_dsc.e1; + struct cmv_e1 *cmv = &intr->u.e1.s2.cmv; + + uea_enters(INS_TO_USBDEV(sc)); + if (le16_to_cpu(cmv->wPreamble) != E1_PREAMBLE) + goto bad1; + + if (cmv->bDirection != E1_MODEMTOHOST) + goto bad1; + + /* FIXME : ADI930 reply wrong preambule (func = 2, sub = 2) to + * the first MEMACCESS cmv. Ignore it... + */ + if (cmv->bFunction != dsc->function) { + if (UEA_CHIP_VERSION(sc) == ADI930 + && cmv->bFunction == E1_MAKEFUNCTION(2, 2)) { + cmv->wIndex = cpu_to_le16(dsc->idx); + put_unaligned_le32(dsc->address, + &cmv->dwSymbolicAddress); + cmv->wOffsetAddress = cpu_to_le16(dsc->offset); + } else + goto bad2; + } + + if (cmv->bFunction == E1_MAKEFUNCTION(E1_ADSLDIRECTIVE, + E1_MODEMREADY)) { + wake_up_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return; + } + + /* in case of MEMACCESS */ + if (le16_to_cpu(cmv->wIndex) != dsc->idx || + get_unaligned_le32(&cmv->dwSymbolicAddress) != dsc->address || + le16_to_cpu(cmv->wOffsetAddress) != dsc->offset) + goto bad2; + + sc->data = get_unaligned_le32(&cmv->dwData); + sc->data = sc->data << 16 | sc->data >> 16; + + wake_up_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return; + +bad2: + uea_err(INS_TO_USBDEV(sc), "unexpected cmv received, " + "Function : %d, Subfunction : %d\n", + E1_FUNCTION_TYPE(cmv->bFunction), + E1_FUNCTION_SUBTYPE(cmv->bFunction)); + uea_leaves(INS_TO_USBDEV(sc)); + return; + +bad1: + uea_err(INS_TO_USBDEV(sc), "invalid cmv received, " + "wPreamble %d, bDirection %d\n", + le16_to_cpu(cmv->wPreamble), cmv->bDirection); + uea_leaves(INS_TO_USBDEV(sc)); +} + +/* The modem send us an ack. First with check if it right */ +static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr) +{ + struct cmv_dsc_e4 *dsc = &sc->cmv_dsc.e4; + struct cmv_e4 *cmv = &intr->u.e4.s2.cmv; + + uea_enters(INS_TO_USBDEV(sc)); + uea_dbg(INS_TO_USBDEV(sc), "cmv %x %x %x %x %x %x\n", + be16_to_cpu(cmv->wGroup), be16_to_cpu(cmv->wFunction), + be16_to_cpu(cmv->wOffset), be16_to_cpu(cmv->wAddress), + be32_to_cpu(cmv->dwData[0]), be32_to_cpu(cmv->dwData[1])); + + if (be16_to_cpu(cmv->wFunction) != dsc->function) + goto bad2; + + if (be16_to_cpu(cmv->wFunction) == E4_MAKEFUNCTION(E4_ADSLDIRECTIVE, + E4_MODEMREADY, 1)) { + wake_up_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return; + } + + /* in case of MEMACCESS */ + if (be16_to_cpu(cmv->wOffset) != dsc->offset || + be16_to_cpu(cmv->wGroup) != dsc->group || + be16_to_cpu(cmv->wAddress) != dsc->address) + goto bad2; + + sc->data = be32_to_cpu(cmv->dwData[0]); + sc->data1 = be32_to_cpu(cmv->dwData[1]); + wake_up_cmv_ack(sc); + uea_leaves(INS_TO_USBDEV(sc)); + return; + +bad2: + uea_err(INS_TO_USBDEV(sc), "unexpected cmv received, " + "Function : %d, Subfunction : %d\n", + E4_FUNCTION_TYPE(cmv->wFunction), + E4_FUNCTION_SUBTYPE(cmv->wFunction)); + uea_leaves(INS_TO_USBDEV(sc)); + return; +} + +static void uea_schedule_load_page_e1(struct uea_softc *sc, + struct intr_pkt *intr) +{ + sc->pageno = intr->e1_bSwapPageNo; + sc->ovl = intr->e1_bOvl >> 4 | intr->e1_bOvl << 4; + schedule_work(&sc->task); +} + +static void uea_schedule_load_page_e4(struct uea_softc *sc, + struct intr_pkt *intr) +{ + sc->pageno = intr->e4_bSwapPageNo; + schedule_work(&sc->task); +} + +/* + * interrupt handler + */ +static void uea_intr(struct urb *urb) +{ + struct uea_softc *sc = urb->context; + struct intr_pkt *intr = urb->transfer_buffer; + int status = urb->status; + + uea_enters(INS_TO_USBDEV(sc)); + + if (unlikely(status < 0)) { + uea_err(INS_TO_USBDEV(sc), "uea_intr() failed with %d\n", + status); + return; + } + + /* device-to-host interrupt */ + if (intr->bType != 0x08 || sc->booting) { + uea_err(INS_TO_USBDEV(sc), "wrong interrupt\n"); + goto resubmit; + } + + switch (le16_to_cpu(intr->wInterrupt)) { + case INT_LOADSWAPPAGE: + sc->schedule_load_page(sc, intr); + break; + + case INT_INCOMINGCMV: + sc->dispatch_cmv(sc, intr); + break; + + default: + uea_err(INS_TO_USBDEV(sc), "unknown interrupt %u\n", + le16_to_cpu(intr->wInterrupt)); + } + +resubmit: + usb_submit_urb(sc->urb_int, GFP_ATOMIC); +} + +/* + * Start the modem : init the data and start kernel thread + */ +static int uea_boot(struct uea_softc *sc) +{ + int ret, size; + struct intr_pkt *intr; + + uea_enters(INS_TO_USBDEV(sc)); + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + size = E4_INTR_PKT_SIZE; + sc->dispatch_cmv = uea_dispatch_cmv_e4; + sc->schedule_load_page = uea_schedule_load_page_e4; + sc->stat = uea_stat_e4; + sc->send_cmvs = uea_send_cmvs_e4; + INIT_WORK(&sc->task, uea_load_page_e4); + } else { + size = E1_INTR_PKT_SIZE; + sc->dispatch_cmv = uea_dispatch_cmv_e1; + sc->schedule_load_page = uea_schedule_load_page_e1; + sc->stat = uea_stat_e1; + sc->send_cmvs = uea_send_cmvs_e1; + INIT_WORK(&sc->task, uea_load_page_e1); + } + + init_waitqueue_head(&sc->sync_q); + + if (UEA_CHIP_VERSION(sc) == ADI930) + load_XILINX_firmware(sc); + + intr = kmalloc(size, GFP_KERNEL); + if (!intr) { + uea_err(INS_TO_USBDEV(sc), + "cannot allocate interrupt package\n"); + goto err0; + } + + sc->urb_int = usb_alloc_urb(0, GFP_KERNEL); + if (!sc->urb_int) { + uea_err(INS_TO_USBDEV(sc), "cannot allocate interrupt URB\n"); + goto err1; + } + + usb_fill_int_urb(sc->urb_int, sc->usb_dev, + usb_rcvintpipe(sc->usb_dev, UEA_INTR_PIPE), + intr, size, uea_intr, sc, + sc->usb_dev->actconfig->interface[0]->altsetting[0]. + endpoint[0].desc.bInterval); + + ret = usb_submit_urb(sc->urb_int, GFP_KERNEL); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "urb submition failed with error %d\n", ret); + goto err1; + } + + /* Create worker thread, but don't start it here. Start it after + * all usbatm generic initialization is done. + */ + sc->kthread = kthread_create(uea_kthread, sc, "ueagle-atm"); + if (IS_ERR(sc->kthread)) { + uea_err(INS_TO_USBDEV(sc), "failed to create thread\n"); + goto err2; + } + + uea_leaves(INS_TO_USBDEV(sc)); + return 0; + +err2: + usb_kill_urb(sc->urb_int); +err1: + usb_free_urb(sc->urb_int); + sc->urb_int = NULL; + kfree(intr); +err0: + uea_leaves(INS_TO_USBDEV(sc)); + return -ENOMEM; +} + +/* + * Stop the modem : kill kernel thread and free data + */ +static void uea_stop(struct uea_softc *sc) +{ + int ret; + uea_enters(INS_TO_USBDEV(sc)); + ret = kthread_stop(sc->kthread); + uea_dbg(INS_TO_USBDEV(sc), "kthread finish with status %d\n", ret); + + uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_ON, 0, NULL); + + usb_kill_urb(sc->urb_int); + kfree(sc->urb_int->transfer_buffer); + usb_free_urb(sc->urb_int); + + /* flush the work item, when no one can schedule it */ + flush_work(&sc->task); + + release_firmware(sc->dsp_firm); + uea_leaves(INS_TO_USBDEV(sc)); +} + +/* syfs interface */ +static struct uea_softc *dev_to_uea(struct device *dev) +{ + struct usb_interface *intf; + struct usbatm_data *usbatm; + + intf = to_usb_interface(dev); + if (!intf) + return NULL; + + usbatm = usb_get_intfdata(intf); + if (!usbatm) + return NULL; + + return usbatm->driver_data; +} + +static ssize_t read_status(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret = -ENODEV; + struct uea_softc *sc; + + mutex_lock(&uea_mutex); + sc = dev_to_uea(dev); + if (!sc) + goto out; + ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.state); +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static ssize_t reboot(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = -ENODEV; + struct uea_softc *sc; + + mutex_lock(&uea_mutex); + sc = dev_to_uea(dev); + if (!sc) + goto out; + sc->reset = 1; + ret = count; +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static DEVICE_ATTR(stat_status, S_IWUSR | S_IRUGO, read_status, reboot); + +static ssize_t read_human_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = -ENODEV; + int modem_state; + struct uea_softc *sc; + + mutex_lock(&uea_mutex); + sc = dev_to_uea(dev); + if (!sc) + goto out; + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + switch (sc->stats.phy.state) { + case 0x0: /* not yet synchronized */ + case 0x1: + case 0x3: + case 0x4: + modem_state = 0; + break; + case 0x5: /* initialization */ + case 0x6: + case 0x9: + case 0xa: + modem_state = 1; + break; + case 0x7: /* operational */ + modem_state = 2; + break; + case 0x2: /* fail ... */ + modem_state = 3; + break; + default: /* unknown */ + modem_state = 4; + break; + } + } else + modem_state = GET_STATUS(sc->stats.phy.state); + + switch (modem_state) { + case 0: + ret = sprintf(buf, "Modem is booting\n"); + break; + case 1: + ret = sprintf(buf, "Modem is initializing\n"); + break; + case 2: + ret = sprintf(buf, "Modem is operational\n"); + break; + case 3: + ret = sprintf(buf, "Modem synchronization failed\n"); + break; + default: + ret = sprintf(buf, "Modem state is unknown\n"); + break; + } +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static DEVICE_ATTR(stat_human_status, S_IRUGO, read_human_status, NULL); + +static ssize_t read_delin(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret = -ENODEV; + struct uea_softc *sc; + char *delin = "GOOD"; + + mutex_lock(&uea_mutex); + sc = dev_to_uea(dev); + if (!sc) + goto out; + + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + if (sc->stats.phy.flags & 0x4000) + delin = "RESET"; + else if (sc->stats.phy.flags & 0x0001) + delin = "LOSS"; + } else { + if (sc->stats.phy.flags & 0x0C00) + delin = "ERROR"; + else if (sc->stats.phy.flags & 0x0030) + delin = "LOSS"; + } + + ret = sprintf(buf, "%s\n", delin); +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static DEVICE_ATTR(stat_delin, S_IRUGO, read_delin, NULL); + +#define UEA_ATTR(name, reset) \ + \ +static ssize_t read_##name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int ret = -ENODEV; \ + struct uea_softc *sc; \ + \ + mutex_lock(&uea_mutex); \ + sc = dev_to_uea(dev); \ + if (!sc) \ + goto out; \ + ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.name); \ + if (reset) \ + sc->stats.phy.name = 0; \ +out: \ + mutex_unlock(&uea_mutex); \ + return ret; \ +} \ + \ +static DEVICE_ATTR(stat_##name, S_IRUGO, read_##name, NULL) + +UEA_ATTR(mflags, 1); +UEA_ATTR(vidcpe, 0); +UEA_ATTR(usrate, 0); +UEA_ATTR(dsrate, 0); +UEA_ATTR(usattenuation, 0); +UEA_ATTR(dsattenuation, 0); +UEA_ATTR(usmargin, 0); +UEA_ATTR(dsmargin, 0); +UEA_ATTR(txflow, 0); +UEA_ATTR(rxflow, 0); +UEA_ATTR(uscorr, 0); +UEA_ATTR(dscorr, 0); +UEA_ATTR(usunc, 0); +UEA_ATTR(dsunc, 0); +UEA_ATTR(firmid, 0); + +/* Retrieve the device End System Identifier (MAC) */ + +static int uea_getesi(struct uea_softc *sc, u_char * esi) +{ + unsigned char mac_str[2 * ETH_ALEN + 1]; + int i; + if (usb_string + (sc->usb_dev, sc->usb_dev->descriptor.iSerialNumber, mac_str, + sizeof(mac_str)) != 2 * ETH_ALEN) + return 1; + + for (i = 0; i < ETH_ALEN; i++) + esi[i] = hex_to_bin(mac_str[2 * i]) * 16 + + hex_to_bin(mac_str[2 * i + 1]); + + return 0; +} + +/* ATM stuff */ +static int uea_atm_open(struct usbatm_data *usbatm, struct atm_dev *atm_dev) +{ + struct uea_softc *sc = usbatm->driver_data; + + return uea_getesi(sc, atm_dev->esi); +} + +static int uea_heavy(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + struct uea_softc *sc = usbatm->driver_data; + + wait_event_interruptible(sc->sync_q, IS_OPERATIONAL(sc)); + + return 0; + +} + +static int claim_interface(struct usb_device *usb_dev, + struct usbatm_data *usbatm, int ifnum) +{ + int ret; + struct usb_interface *intf = usb_ifnum_to_if(usb_dev, ifnum); + + if (!intf) { + uea_err(usb_dev, "interface %d not found\n", ifnum); + return -ENODEV; + } + + ret = usb_driver_claim_interface(&uea_driver, intf, usbatm); + if (ret != 0) + uea_err(usb_dev, "can't claim interface %d, error %d\n", ifnum, + ret); + return ret; +} + +static struct attribute *attrs[] = { + &dev_attr_stat_status.attr, + &dev_attr_stat_mflags.attr, + &dev_attr_stat_human_status.attr, + &dev_attr_stat_delin.attr, + &dev_attr_stat_vidcpe.attr, + &dev_attr_stat_usrate.attr, + &dev_attr_stat_dsrate.attr, + &dev_attr_stat_usattenuation.attr, + &dev_attr_stat_dsattenuation.attr, + &dev_attr_stat_usmargin.attr, + &dev_attr_stat_dsmargin.attr, + &dev_attr_stat_txflow.attr, + &dev_attr_stat_rxflow.attr, + &dev_attr_stat_uscorr.attr, + &dev_attr_stat_dscorr.attr, + &dev_attr_stat_usunc.attr, + &dev_attr_stat_dsunc.attr, + &dev_attr_stat_firmid.attr, + NULL, +}; +static struct attribute_group attr_grp = { + .attrs = attrs, +}; + +static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usb = interface_to_usbdev(intf); + struct uea_softc *sc; + int ret, ifnum = intf->altsetting->desc.bInterfaceNumber; + unsigned int alt; + + uea_enters(usb); + + /* interface 0 is for firmware/monitoring */ + if (ifnum != UEA_INTR_IFACE_NO) + return -ENODEV; + + usbatm->flags = (sync_wait[modem_index] ? 0 : UDSL_SKIP_HEAVY_INIT); + + /* interface 1 is for outbound traffic */ + ret = claim_interface(usb, usbatm, UEA_US_IFACE_NO); + if (ret < 0) + return ret; + + /* ADI930 has only 2 interfaces and inbound traffic is on interface 1 */ + if (UEA_CHIP_VERSION(id) != ADI930) { + /* interface 2 is for inbound traffic */ + ret = claim_interface(usb, usbatm, UEA_DS_IFACE_NO); + if (ret < 0) + return ret; + } + + sc = kzalloc(sizeof(struct uea_softc), GFP_KERNEL); + if (!sc) { + uea_err(usb, "uea_init: not enough memory !\n"); + return -ENOMEM; + } + + sc->usb_dev = usb; + usbatm->driver_data = sc; + sc->usbatm = usbatm; + sc->modem_index = (modem_index < NB_MODEM) ? modem_index++ : 0; + sc->driver_info = id->driver_info; + + /* first try to use module parameter */ + if (annex[sc->modem_index] == 1) + sc->annex = ANNEXA; + else if (annex[sc->modem_index] == 2) + sc->annex = ANNEXB; + /* try to autodetect annex */ + else if (sc->driver_info & AUTO_ANNEX_A) + sc->annex = ANNEXA; + else if (sc->driver_info & AUTO_ANNEX_B) + sc->annex = ANNEXB; + else + sc->annex = (le16_to_cpu + (sc->usb_dev->descriptor.bcdDevice) & 0x80) ? ANNEXB : ANNEXA; + + alt = altsetting[sc->modem_index]; + /* ADI930 don't support iso */ + if (UEA_CHIP_VERSION(id) != ADI930 && alt > 0) { + if (alt <= 8 && + usb_set_interface(usb, UEA_DS_IFACE_NO, alt) == 0) { + uea_dbg(usb, "set alternate %u for 2 interface\n", alt); + uea_info(usb, "using iso mode\n"); + usbatm->flags |= UDSL_USE_ISOC | UDSL_IGNORE_EILSEQ; + } else { + uea_err(usb, "setting alternate %u failed for " + "2 interface, using bulk mode\n", alt); + } + } + + ret = sysfs_create_group(&intf->dev.kobj, &attr_grp); + if (ret < 0) + goto error; + + ret = uea_boot(sc); + if (ret < 0) + goto error_rm_grp; + + return 0; + +error_rm_grp: + sysfs_remove_group(&intf->dev.kobj, &attr_grp); +error: + kfree(sc); + return ret; +} + +static void uea_unbind(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + struct uea_softc *sc = usbatm->driver_data; + + sysfs_remove_group(&intf->dev.kobj, &attr_grp); + uea_stop(sc); + kfree(sc); +} + +static struct usbatm_driver uea_usbatm_driver = { + .driver_name = "ueagle-atm", + .bind = uea_bind, + .atm_start = uea_atm_open, + .unbind = uea_unbind, + .heavy_init = uea_heavy, + .bulk_in = UEA_BULK_DATA_PIPE, + .bulk_out = UEA_BULK_DATA_PIPE, + .isoc_in = UEA_ISO_DATA_PIPE, +}; + +static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *usb = interface_to_usbdev(intf); + int ret; + + uea_enters(usb); + uea_info(usb, "ADSL device founded vid (%#X) pid (%#X) Rev (%#X): %s\n", + le16_to_cpu(usb->descriptor.idVendor), + le16_to_cpu(usb->descriptor.idProduct), + le16_to_cpu(usb->descriptor.bcdDevice), + chip_name[UEA_CHIP_VERSION(id)]); + + usb_reset_device(usb); + + if (UEA_IS_PREFIRM(id)) + return uea_load_firmware(usb, UEA_CHIP_VERSION(id)); + + ret = usbatm_usb_probe(intf, id, &uea_usbatm_driver); + if (ret == 0) { + struct usbatm_data *usbatm = usb_get_intfdata(intf); + struct uea_softc *sc = usbatm->driver_data; + + /* Ensure carrier is initialized to off as early as possible */ + UPDATE_ATM_SIGNAL(ATM_PHY_SIG_LOST); + + /* Only start the worker thread when all init is done */ + wake_up_process(sc->kthread); + } + + return ret; +} + +static void uea_disconnect(struct usb_interface *intf) +{ + struct usb_device *usb = interface_to_usbdev(intf); + int ifnum = intf->altsetting->desc.bInterfaceNumber; + uea_enters(usb); + + /* ADI930 has 2 interfaces and eagle 3 interfaces. + * Pre-firmware device has one interface + */ + if (usb->config->desc.bNumInterfaces != 1 && ifnum == 0) { + mutex_lock(&uea_mutex); + usbatm_usb_disconnect(intf); + mutex_unlock(&uea_mutex); + uea_info(usb, "ADSL device removed\n"); + } + + uea_leaves(usb); +} + +/* + * List of supported VID/PID + */ +static const struct usb_device_id uea_ids[] = { + {USB_DEVICE(ANALOG_VID, ADI930_PID_PREFIRM), + .driver_info = ADI930 | PREFIRM}, + {USB_DEVICE(ANALOG_VID, ADI930_PID_PSTFIRM), + .driver_info = ADI930 | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_I_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_I_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_II_PID_PREFIRM), + .driver_info = EAGLE_II | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_II_PID_PSTFIRM), + .driver_info = EAGLE_II | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_IIC_PID_PREFIRM), + .driver_info = EAGLE_II | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_IIC_PID_PSTFIRM), + .driver_info = EAGLE_II | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_III_PID_PREFIRM), + .driver_info = EAGLE_III | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_III_PID_PSTFIRM), + .driver_info = EAGLE_III | PSTFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_IV_PID_PREFIRM), + .driver_info = EAGLE_IV | PREFIRM}, + {USB_DEVICE(ANALOG_VID, EAGLE_IV_PID_PSTFIRM), + .driver_info = EAGLE_IV | PSTFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_I_A_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_I_A_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_I_B_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_I_B_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_B}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_II_A_PID_PREFIRM), + .driver_info = EAGLE_II | PREFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_II_A_PID_PSTFIRM), + .driver_info = EAGLE_II | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_II_B_PID_PREFIRM), + .driver_info = EAGLE_II | PREFIRM}, + {USB_DEVICE(DEVOLO_VID, DEVOLO_EAGLE_II_B_PID_PSTFIRM), + .driver_info = EAGLE_II | PSTFIRM | AUTO_ANNEX_B}, + {USB_DEVICE(ELSA_VID, ELSA_PID_PREFIRM), + .driver_info = ADI930 | PREFIRM}, + {USB_DEVICE(ELSA_VID, ELSA_PID_PSTFIRM), + .driver_info = ADI930 | PSTFIRM}, + {USB_DEVICE(ELSA_VID, ELSA_PID_A_PREFIRM), + .driver_info = ADI930 | PREFIRM}, + {USB_DEVICE(ELSA_VID, ELSA_PID_A_PSTFIRM), + .driver_info = ADI930 | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(ELSA_VID, ELSA_PID_B_PREFIRM), + .driver_info = ADI930 | PREFIRM}, + {USB_DEVICE(ELSA_VID, ELSA_PID_B_PSTFIRM), + .driver_info = ADI930 | PSTFIRM | AUTO_ANNEX_B}, + {USB_DEVICE(USR_VID, MILLER_A_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(USR_VID, MILLER_A_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(USR_VID, MILLER_B_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(USR_VID, MILLER_B_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_B}, + {USB_DEVICE(USR_VID, HEINEKEN_A_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(USR_VID, HEINEKEN_A_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_A}, + {USB_DEVICE(USR_VID, HEINEKEN_B_PID_PREFIRM), + .driver_info = EAGLE_I | PREFIRM}, + {USB_DEVICE(USR_VID, HEINEKEN_B_PID_PSTFIRM), + .driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_B}, + {} +}; + +/* + * USB driver descriptor + */ +static struct usb_driver uea_driver = { + .name = "ueagle-atm", + .id_table = uea_ids, + .probe = uea_probe, + .disconnect = uea_disconnect, +}; + +MODULE_DEVICE_TABLE(usb, uea_ids); + +module_usb_driver(uea_driver); + +MODULE_AUTHOR("Damien Bergamini/Matthieu Castet/Stanislaw W. Gruszka"); +MODULE_DESCRIPTION("ADI 930/Eagle USB ADSL Modem driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_FIRMWARE(EAGLE_FIRMWARE); +MODULE_FIRMWARE(ADI930_FIRMWARE); +MODULE_FIRMWARE(EAGLE_I_FIRMWARE); +MODULE_FIRMWARE(EAGLE_II_FIRMWARE); +MODULE_FIRMWARE(EAGLE_III_FIRMWARE); +MODULE_FIRMWARE(EAGLE_IV_FIRMWARE); +MODULE_FIRMWARE(DSP4I_FIRMWARE); +MODULE_FIRMWARE(DSP4P_FIRMWARE); +MODULE_FIRMWARE(DSP9I_FIRMWARE); +MODULE_FIRMWARE(DSP9P_FIRMWARE); +MODULE_FIRMWARE(DSPEI_FIRMWARE); +MODULE_FIRMWARE(DSPEP_FIRMWARE); +MODULE_FIRMWARE(FPGA930_FIRMWARE); +MODULE_FIRMWARE(CMV4P_FIRMWARE); +MODULE_FIRMWARE(CMV4PV2_FIRMWARE); +MODULE_FIRMWARE(CMV4I_FIRMWARE); +MODULE_FIRMWARE(CMV4IV2_FIRMWARE); +MODULE_FIRMWARE(CMV9P_FIRMWARE); +MODULE_FIRMWARE(CMV9PV2_FIRMWARE); +MODULE_FIRMWARE(CMV9I_FIRMWARE); +MODULE_FIRMWARE(CMV9IV2_FIRMWARE); +MODULE_FIRMWARE(CMVEP_FIRMWARE); +MODULE_FIRMWARE(CMVEPV2_FIRMWARE); +MODULE_FIRMWARE(CMVEI_FIRMWARE); +MODULE_FIRMWARE(CMVEIV2_FIRMWARE); diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c new file mode 100644 index 00000000..d3527dd8 --- /dev/null +++ b/drivers/usb/atm/usbatm.c @@ -0,0 +1,1386 @@ +/****************************************************************************** + * usbatm.c - Generic USB xDSL driver core + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas + * Copyright (C) 2004, David Woodhouse, Roman Kagan + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +/* + * Written by Johan Verrept, Duncan Sands (duncan.sands@free.fr) and David Woodhouse + * + * 1.7+: - See the check-in logs + * + * 1.6: - No longer opens a connection if the firmware is not loaded + * - Added support for the speedtouch 330 + * - Removed the limit on the number of devices + * - Module now autoloads on device plugin + * - Merged relevant parts of sarlib + * - Replaced the kernel thread with a tasklet + * - New packet transmission code + * - Changed proc file contents + * - Fixed all known SMP races + * - Many fixes and cleanups + * - Various fixes by Oliver Neukum (oliver@neukum.name) + * + * 1.5A: - Version for inclusion in 2.5 series kernel + * - Modifications by Richard Purdie (rpurdie@rpsys.net) + * - made compatible with kernel 2.5.6 onwards by changing + * usbatm_usb_send_data_context->urb to a pointer and adding code + * to alloc and free it + * - remove_wait_queue() added to usbatm_atm_processqueue_thread() + * + * 1.5: - fixed memory leak when atmsar_decode_aal5 returned NULL. + * (reported by stephen.robinson@zen.co.uk) + * + * 1.4: - changed the spin_lock() under interrupt to spin_lock_irqsave() + * - unlink all active send urbs of a vcc that is being closed. + * + * 1.3.1: - added the version number + * + * 1.3: - Added multiple send urb support + * - fixed memory leak and vcc->tx_inuse starvation bug + * when not enough memory left in vcc. + * + * 1.2: - Fixed race condition in usbatm_usb_send_data() + * 1.1: - Turned off packet debugging + * + */ + +#include "usbatm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef VERBOSE_DEBUG +static int usbatm_print_packet(struct usbatm_data *instance, const unsigned char *data, int len); +#define PACKETDEBUG(arg...) usbatm_print_packet(arg) +#define vdbg(arg...) dev_dbg(arg) +#else +#define PACKETDEBUG(arg...) +#define vdbg(arg...) +#endif + +#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands " +#define DRIVER_VERSION "1.10" +#define DRIVER_DESC "Generic USB ATM/DSL I/O, version " DRIVER_VERSION + +static const char usbatm_driver_name[] = "usbatm"; + +#define UDSL_MAX_RCV_URBS 16 +#define UDSL_MAX_SND_URBS 16 +#define UDSL_MAX_BUF_SIZE 65536 +#define UDSL_DEFAULT_RCV_URBS 4 +#define UDSL_DEFAULT_SND_URBS 4 +#define UDSL_DEFAULT_RCV_BUF_SIZE 3392 /* 64 * ATM_CELL_SIZE */ +#define UDSL_DEFAULT_SND_BUF_SIZE 3392 /* 64 * ATM_CELL_SIZE */ + +#define ATM_CELL_HEADER (ATM_CELL_SIZE - ATM_CELL_PAYLOAD) + +#define THROTTLE_MSECS 100 /* delay to recover processing after urb submission fails */ + +static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS; +static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS; +static unsigned int rcv_buf_bytes = UDSL_DEFAULT_RCV_BUF_SIZE; +static unsigned int snd_buf_bytes = UDSL_DEFAULT_SND_BUF_SIZE; + +module_param(num_rcv_urbs, uint, S_IRUGO); +MODULE_PARM_DESC(num_rcv_urbs, + "Number of urbs used for reception (range: 0-" + __MODULE_STRING(UDSL_MAX_RCV_URBS) ", default: " + __MODULE_STRING(UDSL_DEFAULT_RCV_URBS) ")"); + +module_param(num_snd_urbs, uint, S_IRUGO); +MODULE_PARM_DESC(num_snd_urbs, + "Number of urbs used for transmission (range: 0-" + __MODULE_STRING(UDSL_MAX_SND_URBS) ", default: " + __MODULE_STRING(UDSL_DEFAULT_SND_URBS) ")"); + +module_param(rcv_buf_bytes, uint, S_IRUGO); +MODULE_PARM_DESC(rcv_buf_bytes, + "Size of the buffers used for reception, in bytes (range: 1-" + __MODULE_STRING(UDSL_MAX_BUF_SIZE) ", default: " + __MODULE_STRING(UDSL_DEFAULT_RCV_BUF_SIZE) ")"); + +module_param(snd_buf_bytes, uint, S_IRUGO); +MODULE_PARM_DESC(snd_buf_bytes, + "Size of the buffers used for transmission, in bytes (range: 1-" + __MODULE_STRING(UDSL_MAX_BUF_SIZE) ", default: " + __MODULE_STRING(UDSL_DEFAULT_SND_BUF_SIZE) ")"); + + +/* receive */ + +struct usbatm_vcc_data { + /* vpi/vci lookup */ + struct list_head list; + short vpi; + int vci; + struct atm_vcc *vcc; + + /* raw cell reassembly */ + struct sk_buff *sarb; +}; + + +/* send */ + +struct usbatm_control { + struct atm_skb_data atm; + u32 len; + u32 crc; +}; + +#define UDSL_SKB(x) ((struct usbatm_control *)(x)->cb) + + +/* ATM */ + +static void usbatm_atm_dev_close(struct atm_dev *atm_dev); +static int usbatm_atm_open(struct atm_vcc *vcc); +static void usbatm_atm_close(struct atm_vcc *vcc); +static int usbatm_atm_ioctl(struct atm_dev *atm_dev, unsigned int cmd, void __user * arg); +static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb); +static int usbatm_atm_proc_read(struct atm_dev *atm_dev, loff_t * pos, char *page); + +static struct atmdev_ops usbatm_atm_devops = { + .dev_close = usbatm_atm_dev_close, + .open = usbatm_atm_open, + .close = usbatm_atm_close, + .ioctl = usbatm_atm_ioctl, + .send = usbatm_atm_send, + .proc_read = usbatm_atm_proc_read, + .owner = THIS_MODULE, +}; + + +/*********** +** misc ** +***********/ + +static inline unsigned int usbatm_pdu_length(unsigned int length) +{ + length += ATM_CELL_PAYLOAD - 1 + ATM_AAL5_TRAILER; + return length - length % ATM_CELL_PAYLOAD; +} + +static inline void usbatm_pop(struct atm_vcc *vcc, struct sk_buff *skb) +{ + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); +} + + +/*********** +** urbs ** +************/ + +static struct urb *usbatm_pop_urb(struct usbatm_channel *channel) +{ + struct urb *urb; + + spin_lock_irq(&channel->lock); + if (list_empty(&channel->list)) { + spin_unlock_irq(&channel->lock); + return NULL; + } + + urb = list_entry(channel->list.next, struct urb, urb_list); + list_del(&urb->urb_list); + spin_unlock_irq(&channel->lock); + + return urb; +} + +static int usbatm_submit_urb(struct urb *urb) +{ + struct usbatm_channel *channel = urb->context; + int ret; + + /* vdbg("%s: submitting urb 0x%p, size %u", + __func__, urb, urb->transfer_buffer_length); */ + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + if (printk_ratelimit()) + atm_warn(channel->usbatm, "%s: urb 0x%p submission failed (%d)!\n", + __func__, urb, ret); + + /* consider all errors transient and return the buffer back to the queue */ + urb->status = -EAGAIN; + spin_lock_irq(&channel->lock); + + /* must add to the front when sending; doesn't matter when receiving */ + list_add(&urb->urb_list, &channel->list); + + spin_unlock_irq(&channel->lock); + + /* make sure the channel doesn't stall */ + mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS)); + } + + return ret; +} + +static void usbatm_complete(struct urb *urb) +{ + struct usbatm_channel *channel = urb->context; + unsigned long flags; + int status = urb->status; + + /* vdbg("%s: urb 0x%p, status %d, actual_length %d", + __func__, urb, status, urb->actual_length); */ + + /* usually in_interrupt(), but not always */ + spin_lock_irqsave(&channel->lock, flags); + + /* must add to the back when receiving; doesn't matter when sending */ + list_add_tail(&urb->urb_list, &channel->list); + + spin_unlock_irqrestore(&channel->lock, flags); + + if (unlikely(status) && + (!(channel->usbatm->flags & UDSL_IGNORE_EILSEQ) || + status != -EILSEQ)) { + if (status == -ESHUTDOWN) + return; + + if (printk_ratelimit()) + atm_warn(channel->usbatm, "%s: urb 0x%p failed (%d)!\n", + __func__, urb, status); + /* throttle processing in case of an error */ + mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS)); + } else + tasklet_schedule(&channel->tasklet); +} + + +/************* +** decode ** +*************/ + +static inline struct usbatm_vcc_data *usbatm_find_vcc(struct usbatm_data *instance, + short vpi, int vci) +{ + struct usbatm_vcc_data *vcc_data; + + list_for_each_entry(vcc_data, &instance->vcc_list, list) + if ((vcc_data->vci == vci) && (vcc_data->vpi == vpi)) + return vcc_data; + return NULL; +} + +static void usbatm_extract_one_cell(struct usbatm_data *instance, unsigned char *source) +{ + struct atm_vcc *vcc; + struct sk_buff *sarb; + short vpi = ((source[0] & 0x0f) << 4) | (source[1] >> 4); + int vci = ((source[1] & 0x0f) << 12) | (source[2] << 4) | (source[3] >> 4); + u8 pti = ((source[3] & 0xe) >> 1); + + vdbg(&instance->usb_intf->dev, "%s: vpi %hd, vci %d, pti %d", __func__, vpi, vci, pti); + + if ((vci != instance->cached_vci) || (vpi != instance->cached_vpi)) { + instance->cached_vpi = vpi; + instance->cached_vci = vci; + + instance->cached_vcc = usbatm_find_vcc(instance, vpi, vci); + + if (!instance->cached_vcc) + atm_rldbg(instance, "%s: unknown vpi/vci (%hd/%d)!\n", __func__, vpi, vci); + } + + if (!instance->cached_vcc) + return; + + vcc = instance->cached_vcc->vcc; + + /* OAM F5 end-to-end */ + if (pti == ATM_PTI_E2EF5) { + if (printk_ratelimit()) + atm_warn(instance, "%s: OAM not supported (vpi %d, vci %d)!\n", + __func__, vpi, vci); + atomic_inc(&vcc->stats->rx_err); + return; + } + + sarb = instance->cached_vcc->sarb; + + if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) { + atm_rldbg(instance, "%s: buffer overrun (sarb->len %u, vcc: 0x%p)!\n", + __func__, sarb->len, vcc); + /* discard cells already received */ + skb_trim(sarb, 0); + UDSL_ASSERT(instance, sarb->tail + ATM_CELL_PAYLOAD <= sarb->end); + } + + memcpy(skb_tail_pointer(sarb), source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); + __skb_put(sarb, ATM_CELL_PAYLOAD); + + if (pti & 1) { + struct sk_buff *skb; + unsigned int length; + unsigned int pdu_length; + + length = (source[ATM_CELL_SIZE - 6] << 8) + source[ATM_CELL_SIZE - 5]; + + /* guard against overflow */ + if (length > ATM_MAX_AAL5_PDU) { + atm_rldbg(instance, "%s: bogus length %u (vcc: 0x%p)!\n", + __func__, length, vcc); + atomic_inc(&vcc->stats->rx_err); + goto out; + } + + pdu_length = usbatm_pdu_length(length); + + if (sarb->len < pdu_length) { + atm_rldbg(instance, "%s: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!\n", + __func__, pdu_length, sarb->len, vcc); + atomic_inc(&vcc->stats->rx_err); + goto out; + } + + if (crc32_be(~0, skb_tail_pointer(sarb) - pdu_length, pdu_length) != 0xc704dd7b) { + atm_rldbg(instance, "%s: packet failed crc check (vcc: 0x%p)!\n", + __func__, vcc); + atomic_inc(&vcc->stats->rx_err); + goto out; + } + + vdbg(&instance->usb_intf->dev, + "%s: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", + __func__, length, pdu_length, vcc); + + if (!(skb = dev_alloc_skb(length))) { + if (printk_ratelimit()) + atm_err(instance, "%s: no memory for skb (length: %u)!\n", + __func__, length); + atomic_inc(&vcc->stats->rx_drop); + goto out; + } + + vdbg(&instance->usb_intf->dev, + "%s: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", + __func__, skb, skb->truesize); + + if (!atm_charge(vcc, skb->truesize)) { + atm_rldbg(instance, "%s: failed atm_charge (skb->truesize: %u)!\n", + __func__, skb->truesize); + dev_kfree_skb_any(skb); + goto out; /* atm_charge increments rx_drop */ + } + + skb_copy_to_linear_data(skb, + skb_tail_pointer(sarb) - pdu_length, + length); + __skb_put(skb, length); + + vdbg(&instance->usb_intf->dev, + "%s: sending skb 0x%p, skb->len %u, skb->truesize %u", + __func__, skb, skb->len, skb->truesize); + + PACKETDEBUG(instance, skb->data, skb->len); + + vcc->push(vcc, skb); + + atomic_inc(&vcc->stats->rx); + out: + skb_trim(sarb, 0); + } +} + +static void usbatm_extract_cells(struct usbatm_data *instance, + unsigned char *source, unsigned int avail_data) +{ + unsigned int stride = instance->rx_channel.stride; + unsigned int buf_usage = instance->buf_usage; + + /* extract cells from incoming data, taking into account that + * the length of avail data may not be a multiple of stride */ + + if (buf_usage > 0) { + /* we have a partially received atm cell */ + unsigned char *cell_buf = instance->cell_buf; + unsigned int space_left = stride - buf_usage; + + UDSL_ASSERT(instance, buf_usage <= stride); + + if (avail_data >= space_left) { + /* add new data and process cell */ + memcpy(cell_buf + buf_usage, source, space_left); + source += space_left; + avail_data -= space_left; + usbatm_extract_one_cell(instance, cell_buf); + instance->buf_usage = 0; + } else { + /* not enough data to fill the cell */ + memcpy(cell_buf + buf_usage, source, avail_data); + instance->buf_usage = buf_usage + avail_data; + return; + } + } + + for (; avail_data >= stride; avail_data -= stride, source += stride) + usbatm_extract_one_cell(instance, source); + + if (avail_data > 0) { + /* length was not a multiple of stride - + * save remaining data for next call */ + memcpy(instance->cell_buf, source, avail_data); + instance->buf_usage = avail_data; + } +} + + +/************* +** encode ** +*************/ + +static unsigned int usbatm_write_cells(struct usbatm_data *instance, + struct sk_buff *skb, + u8 *target, unsigned int avail_space) +{ + struct usbatm_control *ctrl = UDSL_SKB(skb); + struct atm_vcc *vcc = ctrl->atm.vcc; + unsigned int bytes_written; + unsigned int stride = instance->tx_channel.stride; + + vdbg(&instance->usb_intf->dev, "%s: skb->len=%d, avail_space=%u", + __func__, skb->len, avail_space); + UDSL_ASSERT(instance, !(avail_space % stride)); + + for (bytes_written = 0; bytes_written < avail_space && ctrl->len; + bytes_written += stride, target += stride) { + unsigned int data_len = min_t(unsigned int, skb->len, ATM_CELL_PAYLOAD); + unsigned int left = ATM_CELL_PAYLOAD - data_len; + u8 *ptr = target; + + ptr[0] = vcc->vpi >> 4; + ptr[1] = (vcc->vpi << 4) | (vcc->vci >> 12); + ptr[2] = vcc->vci >> 4; + ptr[3] = vcc->vci << 4; + ptr[4] = 0xec; + ptr += ATM_CELL_HEADER; + + skb_copy_from_linear_data(skb, ptr, data_len); + ptr += data_len; + __skb_pull(skb, data_len); + + if (!left) + continue; + + memset(ptr, 0, left); + + if (left >= ATM_AAL5_TRAILER) { /* trailer will go in this cell */ + u8 *trailer = target + ATM_CELL_SIZE - ATM_AAL5_TRAILER; + /* trailer[0] = 0; UU = 0 */ + /* trailer[1] = 0; CPI = 0 */ + trailer[2] = ctrl->len >> 8; + trailer[3] = ctrl->len; + + ctrl->crc = ~crc32_be(ctrl->crc, ptr, left - 4); + + trailer[4] = ctrl->crc >> 24; + trailer[5] = ctrl->crc >> 16; + trailer[6] = ctrl->crc >> 8; + trailer[7] = ctrl->crc; + + target[3] |= 0x2; /* adjust PTI */ + + ctrl->len = 0; /* tag this skb finished */ + } else + ctrl->crc = crc32_be(ctrl->crc, ptr, left); + } + + return bytes_written; +} + + +/************** +** receive ** +**************/ + +static void usbatm_rx_process(unsigned long data) +{ + struct usbatm_data *instance = (struct usbatm_data *)data; + struct urb *urb; + + while ((urb = usbatm_pop_urb(&instance->rx_channel))) { + vdbg(&instance->usb_intf->dev, + "%s: processing urb 0x%p", __func__, urb); + + if (usb_pipeisoc(urb->pipe)) { + unsigned char *merge_start = NULL; + unsigned int merge_length = 0; + const unsigned int packet_size = instance->rx_channel.packet_size; + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + if (!urb->iso_frame_desc[i].status) { + unsigned int actual_length = urb->iso_frame_desc[i].actual_length; + + UDSL_ASSERT(instance, actual_length <= packet_size); + + if (!merge_length) + merge_start = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; + merge_length += actual_length; + if (merge_length && (actual_length < packet_size)) { + usbatm_extract_cells(instance, merge_start, merge_length); + merge_length = 0; + } + } else { + atm_rldbg(instance, "%s: status %d in frame %d!\n", __func__, urb->status, i); + if (merge_length) + usbatm_extract_cells(instance, merge_start, merge_length); + merge_length = 0; + instance->buf_usage = 0; + } + } + + if (merge_length) + usbatm_extract_cells(instance, merge_start, merge_length); + } else + if (!urb->status) + usbatm_extract_cells(instance, urb->transfer_buffer, urb->actual_length); + else + instance->buf_usage = 0; + + if (usbatm_submit_urb(urb)) + return; + } +} + + +/*********** +** send ** +***********/ + +static void usbatm_tx_process(unsigned long data) +{ + struct usbatm_data *instance = (struct usbatm_data *)data; + struct sk_buff *skb = instance->current_skb; + struct urb *urb = NULL; + const unsigned int buf_size = instance->tx_channel.buf_size; + unsigned int bytes_written = 0; + u8 *buffer = NULL; + + if (!skb) + skb = skb_dequeue(&instance->sndqueue); + + while (skb) { + if (!urb) { + urb = usbatm_pop_urb(&instance->tx_channel); + if (!urb) + break; /* no more senders */ + buffer = urb->transfer_buffer; + bytes_written = (urb->status == -EAGAIN) ? + urb->transfer_buffer_length : 0; + } + + bytes_written += usbatm_write_cells(instance, skb, + buffer + bytes_written, + buf_size - bytes_written); + + vdbg(&instance->usb_intf->dev, + "%s: wrote %u bytes from skb 0x%p to urb 0x%p", + __func__, bytes_written, skb, urb); + + if (!UDSL_SKB(skb)->len) { + struct atm_vcc *vcc = UDSL_SKB(skb)->atm.vcc; + + usbatm_pop(vcc, skb); + atomic_inc(&vcc->stats->tx); + + skb = skb_dequeue(&instance->sndqueue); + } + + if (bytes_written == buf_size || (!skb && bytes_written)) { + urb->transfer_buffer_length = bytes_written; + + if (usbatm_submit_urb(urb)) + break; + urb = NULL; + } + } + + instance->current_skb = skb; +} + +static void usbatm_cancel_send(struct usbatm_data *instance, + struct atm_vcc *vcc) +{ + struct sk_buff *skb, *n; + + atm_dbg(instance, "%s entered\n", __func__); + spin_lock_irq(&instance->sndqueue.lock); + skb_queue_walk_safe(&instance->sndqueue, skb, n) { + if (UDSL_SKB(skb)->atm.vcc == vcc) { + atm_dbg(instance, "%s: popping skb 0x%p\n", __func__, skb); + __skb_unlink(skb, &instance->sndqueue); + usbatm_pop(vcc, skb); + } + } + spin_unlock_irq(&instance->sndqueue.lock); + + tasklet_disable(&instance->tx_channel.tasklet); + if ((skb = instance->current_skb) && (UDSL_SKB(skb)->atm.vcc == vcc)) { + atm_dbg(instance, "%s: popping current skb (0x%p)\n", __func__, skb); + instance->current_skb = NULL; + usbatm_pop(vcc, skb); + } + tasklet_enable(&instance->tx_channel.tasklet); + atm_dbg(instance, "%s done\n", __func__); +} + +static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb) +{ + struct usbatm_data *instance = vcc->dev->dev_data; + struct usbatm_control *ctrl = UDSL_SKB(skb); + int err; + + /* racy disconnection check - fine */ + if (!instance || instance->disconnected) { +#ifdef DEBUG + printk_ratelimited(KERN_DEBUG "%s: %s!\n", __func__, instance ? "disconnected" : "NULL instance"); +#endif + err = -ENODEV; + goto fail; + } + + vdbg(&instance->usb_intf->dev, "%s called (skb 0x%p, len %u)", __func__, + skb, skb->len); + + if (vcc->qos.aal != ATM_AAL5) { + atm_rldbg(instance, "%s: unsupported ATM type %d!\n", __func__, vcc->qos.aal); + err = -EINVAL; + goto fail; + } + + if (skb->len > ATM_MAX_AAL5_PDU) { + atm_rldbg(instance, "%s: packet too long (%d vs %d)!\n", + __func__, skb->len, ATM_MAX_AAL5_PDU); + err = -EINVAL; + goto fail; + } + + PACKETDEBUG(instance, skb->data, skb->len); + + /* initialize the control block */ + ctrl->atm.vcc = vcc; + ctrl->len = skb->len; + ctrl->crc = crc32_be(~0, skb->data, skb->len); + + skb_queue_tail(&instance->sndqueue, skb); + tasklet_schedule(&instance->tx_channel.tasklet); + + return 0; + + fail: + usbatm_pop(vcc, skb); + return err; +} + + +/******************** +** bean counting ** +********************/ + +static void usbatm_destroy_instance(struct kref *kref) +{ + struct usbatm_data *instance = container_of(kref, struct usbatm_data, refcount); + + usb_dbg(instance, "%s\n", __func__); + + tasklet_kill(&instance->rx_channel.tasklet); + tasklet_kill(&instance->tx_channel.tasklet); + usb_put_dev(instance->usb_dev); + kfree(instance); +} + +static void usbatm_get_instance(struct usbatm_data *instance) +{ + usb_dbg(instance, "%s\n", __func__); + + kref_get(&instance->refcount); +} + +static void usbatm_put_instance(struct usbatm_data *instance) +{ + usb_dbg(instance, "%s\n", __func__); + + kref_put(&instance->refcount, usbatm_destroy_instance); +} + + +/********** +** ATM ** +**********/ + +static void usbatm_atm_dev_close(struct atm_dev *atm_dev) +{ + struct usbatm_data *instance = atm_dev->dev_data; + + if (!instance) + return; + + usb_dbg(instance, "%s\n", __func__); + atm_dev->dev_data = NULL; /* catch bugs */ + usbatm_put_instance(instance); /* taken in usbatm_atm_init */ +} + +static int usbatm_atm_proc_read(struct atm_dev *atm_dev, loff_t * pos, char *page) +{ + struct usbatm_data *instance = atm_dev->dev_data; + int left = *pos; + + if (!instance) + return -ENODEV; + + if (!left--) + return sprintf(page, "%s\n", instance->description); + + if (!left--) + return sprintf(page, "MAC: %pM\n", atm_dev->esi); + + if (!left--) + return sprintf(page, + "AAL5: tx %d ( %d err ), rx %d ( %d err, %d drop )\n", + atomic_read(&atm_dev->stats.aal5.tx), + atomic_read(&atm_dev->stats.aal5.tx_err), + atomic_read(&atm_dev->stats.aal5.rx), + atomic_read(&atm_dev->stats.aal5.rx_err), + atomic_read(&atm_dev->stats.aal5.rx_drop)); + + if (!left--) { + if (instance->disconnected) + return sprintf(page, "Disconnected\n"); + else + switch (atm_dev->signal) { + case ATM_PHY_SIG_FOUND: + return sprintf(page, "Line up\n"); + case ATM_PHY_SIG_LOST: + return sprintf(page, "Line down\n"); + default: + return sprintf(page, "Line state unknown\n"); + } + } + + return 0; +} + +static int usbatm_atm_open(struct atm_vcc *vcc) +{ + struct usbatm_data *instance = vcc->dev->dev_data; + struct usbatm_vcc_data *new = NULL; + int ret; + int vci = vcc->vci; + short vpi = vcc->vpi; + + if (!instance) + return -ENODEV; + + atm_dbg(instance, "%s: vpi %hd, vci %d\n", __func__, vpi, vci); + + /* only support AAL5 */ + if ((vcc->qos.aal != ATM_AAL5)) { + atm_warn(instance, "%s: unsupported ATM type %d!\n", __func__, vcc->qos.aal); + return -EINVAL; + } + + /* sanity checks */ + if ((vcc->qos.rxtp.max_sdu < 0) || (vcc->qos.rxtp.max_sdu > ATM_MAX_AAL5_PDU)) { + atm_dbg(instance, "%s: max_sdu %d out of range!\n", __func__, vcc->qos.rxtp.max_sdu); + return -EINVAL; + } + + mutex_lock(&instance->serialize); /* vs self, usbatm_atm_close, usbatm_usb_disconnect */ + + if (instance->disconnected) { + atm_dbg(instance, "%s: disconnected!\n", __func__); + ret = -ENODEV; + goto fail; + } + + if (usbatm_find_vcc(instance, vpi, vci)) { + atm_dbg(instance, "%s: %hd/%d already in use!\n", __func__, vpi, vci); + ret = -EADDRINUSE; + goto fail; + } + + if (!(new = kzalloc(sizeof(struct usbatm_vcc_data), GFP_KERNEL))) { + atm_err(instance, "%s: no memory for vcc_data!\n", __func__); + ret = -ENOMEM; + goto fail; + } + + new->vcc = vcc; + new->vpi = vpi; + new->vci = vci; + + new->sarb = alloc_skb(usbatm_pdu_length(vcc->qos.rxtp.max_sdu), GFP_KERNEL); + if (!new->sarb) { + atm_err(instance, "%s: no memory for SAR buffer!\n", __func__); + ret = -ENOMEM; + goto fail; + } + + vcc->dev_data = new; + + tasklet_disable(&instance->rx_channel.tasklet); + instance->cached_vcc = new; + instance->cached_vpi = vpi; + instance->cached_vci = vci; + list_add(&new->list, &instance->vcc_list); + tasklet_enable(&instance->rx_channel.tasklet); + + set_bit(ATM_VF_ADDR, &vcc->flags); + set_bit(ATM_VF_PARTIAL, &vcc->flags); + set_bit(ATM_VF_READY, &vcc->flags); + + mutex_unlock(&instance->serialize); + + atm_dbg(instance, "%s: allocated vcc data 0x%p\n", __func__, new); + + return 0; + +fail: + kfree(new); + mutex_unlock(&instance->serialize); + return ret; +} + +static void usbatm_atm_close(struct atm_vcc *vcc) +{ + struct usbatm_data *instance = vcc->dev->dev_data; + struct usbatm_vcc_data *vcc_data = vcc->dev_data; + + if (!instance || !vcc_data) + return; + + atm_dbg(instance, "%s entered\n", __func__); + + atm_dbg(instance, "%s: deallocating vcc 0x%p with vpi %d vci %d\n", + __func__, vcc_data, vcc_data->vpi, vcc_data->vci); + + usbatm_cancel_send(instance, vcc); + + mutex_lock(&instance->serialize); /* vs self, usbatm_atm_open, usbatm_usb_disconnect */ + + tasklet_disable(&instance->rx_channel.tasklet); + if (instance->cached_vcc == vcc_data) { + instance->cached_vcc = NULL; + instance->cached_vpi = ATM_VPI_UNSPEC; + instance->cached_vci = ATM_VCI_UNSPEC; + } + list_del(&vcc_data->list); + tasklet_enable(&instance->rx_channel.tasklet); + + kfree_skb(vcc_data->sarb); + vcc_data->sarb = NULL; + + kfree(vcc_data); + vcc->dev_data = NULL; + + vcc->vpi = ATM_VPI_UNSPEC; + vcc->vci = ATM_VCI_UNSPEC; + clear_bit(ATM_VF_READY, &vcc->flags); + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + + mutex_unlock(&instance->serialize); + + atm_dbg(instance, "%s successful\n", __func__); +} + +static int usbatm_atm_ioctl(struct atm_dev *atm_dev, unsigned int cmd, + void __user * arg) +{ + struct usbatm_data *instance = atm_dev->dev_data; + + if (!instance || instance->disconnected) + return -ENODEV; + + switch (cmd) { + case ATM_QUERYLOOP: + return put_user(ATM_LM_NONE, (int __user *)arg) ? -EFAULT : 0; + default: + return -ENOIOCTLCMD; + } +} + +static int usbatm_atm_init(struct usbatm_data *instance) +{ + struct atm_dev *atm_dev; + int ret, i; + + /* ATM init. The ATM initialization scheme suffers from an intrinsic race + * condition: callbacks we register can be executed at once, before we have + * initialized the struct atm_dev. To protect against this, all callbacks + * abort if atm_dev->dev_data is NULL. */ + atm_dev = atm_dev_register(instance->driver_name, + &instance->usb_intf->dev, &usbatm_atm_devops, + -1, NULL); + if (!atm_dev) { + usb_err(instance, "%s: failed to register ATM device!\n", __func__); + return -1; + } + + instance->atm_dev = atm_dev; + + atm_dev->ci_range.vpi_bits = ATM_CI_MAX; + atm_dev->ci_range.vci_bits = ATM_CI_MAX; + atm_dev->signal = ATM_PHY_SIG_UNKNOWN; + + /* temp init ATM device, set to 128kbit */ + atm_dev->link_rate = 128 * 1000 / 424; + + if (instance->driver->atm_start && ((ret = instance->driver->atm_start(instance, atm_dev)) < 0)) { + atm_err(instance, "%s: atm_start failed: %d!\n", __func__, ret); + goto fail; + } + + usbatm_get_instance(instance); /* dropped in usbatm_atm_dev_close */ + + /* ready for ATM callbacks */ + mb(); + atm_dev->dev_data = instance; + + /* submit all rx URBs */ + for (i = 0; i < num_rcv_urbs; i++) + usbatm_submit_urb(instance->urbs[i]); + + return 0; + + fail: + instance->atm_dev = NULL; + atm_dev_deregister(atm_dev); /* usbatm_atm_dev_close will eventually be called */ + return ret; +} + + +/********** +** USB ** +**********/ + +static int usbatm_do_heavy_init(void *arg) +{ + struct usbatm_data *instance = arg; + int ret; + + allow_signal(SIGTERM); + complete(&instance->thread_started); + + ret = instance->driver->heavy_init(instance, instance->usb_intf); + + if (!ret) + ret = usbatm_atm_init(instance); + + mutex_lock(&instance->serialize); + instance->thread = NULL; + mutex_unlock(&instance->serialize); + + complete_and_exit(&instance->thread_exited, ret); +} + +static int usbatm_heavy_init(struct usbatm_data *instance) +{ + struct task_struct *t; + + t = kthread_create(usbatm_do_heavy_init, instance, + instance->driver->driver_name); + if (IS_ERR(t)) { + usb_err(instance, "%s: failed to create kernel_thread (%ld)!\n", + __func__, PTR_ERR(t)); + return PTR_ERR(t); + } + + instance->thread = t; + wake_up_process(t); + wait_for_completion(&instance->thread_started); + + return 0; +} + +static void usbatm_tasklet_schedule(unsigned long data) +{ + tasklet_schedule((struct tasklet_struct *) data); +} + +static void usbatm_init_channel(struct usbatm_channel *channel) +{ + spin_lock_init(&channel->lock); + INIT_LIST_HEAD(&channel->list); + channel->delay.function = usbatm_tasklet_schedule; + channel->delay.data = (unsigned long) &channel->tasklet; + init_timer(&channel->delay); +} + +int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id, + struct usbatm_driver *driver) +{ + struct device *dev = &intf->dev; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct usbatm_data *instance; + char *buf; + int error = -ENOMEM; + int i, length; + unsigned int maxpacket, num_packets; + + dev_dbg(dev, "%s: trying driver %s with vendor=%04x, product=%04x, ifnum %2d\n", + __func__, driver->driver_name, + le16_to_cpu(usb_dev->descriptor.idVendor), + le16_to_cpu(usb_dev->descriptor.idProduct), + intf->altsetting->desc.bInterfaceNumber); + + /* instance init */ + instance = kzalloc(sizeof(*instance) + sizeof(struct urb *) * (num_rcv_urbs + num_snd_urbs), GFP_KERNEL); + if (!instance) { + dev_err(dev, "%s: no memory for instance data!\n", __func__); + return -ENOMEM; + } + + /* public fields */ + + instance->driver = driver; + snprintf(instance->driver_name, sizeof(instance->driver_name), driver->driver_name); + + instance->usb_dev = usb_dev; + instance->usb_intf = intf; + + buf = instance->description; + length = sizeof(instance->description); + + if ((i = usb_string(usb_dev, usb_dev->descriptor.iProduct, buf, length)) < 0) + goto bind; + + buf += i; + length -= i; + + i = scnprintf(buf, length, " ("); + buf += i; + length -= i; + + if (length <= 0 || (i = usb_make_path(usb_dev, buf, length)) < 0) + goto bind; + + buf += i; + length -= i; + + snprintf(buf, length, ")"); + + bind: + if (driver->bind && (error = driver->bind(instance, intf, id)) < 0) { + dev_err(dev, "%s: bind failed: %d!\n", __func__, error); + goto fail_free; + } + + /* private fields */ + + kref_init(&instance->refcount); /* dropped in usbatm_usb_disconnect */ + mutex_init(&instance->serialize); + + instance->thread = NULL; + init_completion(&instance->thread_started); + init_completion(&instance->thread_exited); + + INIT_LIST_HEAD(&instance->vcc_list); + skb_queue_head_init(&instance->sndqueue); + + usbatm_init_channel(&instance->rx_channel); + usbatm_init_channel(&instance->tx_channel); + tasklet_init(&instance->rx_channel.tasklet, usbatm_rx_process, (unsigned long)instance); + tasklet_init(&instance->tx_channel.tasklet, usbatm_tx_process, (unsigned long)instance); + instance->rx_channel.stride = ATM_CELL_SIZE + driver->rx_padding; + instance->tx_channel.stride = ATM_CELL_SIZE + driver->tx_padding; + instance->rx_channel.usbatm = instance->tx_channel.usbatm = instance; + + if ((instance->flags & UDSL_USE_ISOC) && driver->isoc_in) + instance->rx_channel.endpoint = usb_rcvisocpipe(usb_dev, driver->isoc_in); + else + instance->rx_channel.endpoint = usb_rcvbulkpipe(usb_dev, driver->bulk_in); + + instance->tx_channel.endpoint = usb_sndbulkpipe(usb_dev, driver->bulk_out); + + /* tx buffer size must be a positive multiple of the stride */ + instance->tx_channel.buf_size = max(instance->tx_channel.stride, + snd_buf_bytes - (snd_buf_bytes % instance->tx_channel.stride)); + + /* rx buffer size must be a positive multiple of the endpoint maxpacket */ + maxpacket = usb_maxpacket(usb_dev, instance->rx_channel.endpoint, 0); + + if ((maxpacket < 1) || (maxpacket > UDSL_MAX_BUF_SIZE)) { + dev_err(dev, "%s: invalid endpoint %02x!\n", __func__, + usb_pipeendpoint(instance->rx_channel.endpoint)); + error = -EINVAL; + goto fail_unbind; + } + + num_packets = max(1U, (rcv_buf_bytes + maxpacket / 2) / maxpacket); /* round */ + + if (num_packets * maxpacket > UDSL_MAX_BUF_SIZE) + num_packets--; + + instance->rx_channel.buf_size = num_packets * maxpacket; + instance->rx_channel.packet_size = maxpacket; + +#ifdef DEBUG + for (i = 0; i < 2; i++) { + struct usbatm_channel *channel = i ? + &instance->tx_channel : &instance->rx_channel; + + dev_dbg(dev, "%s: using %d byte buffer for %s channel 0x%p\n", __func__, channel->buf_size, i ? "tx" : "rx", channel); + } +#endif + + /* initialize urbs */ + + for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) { + u8 *buffer; + struct usbatm_channel *channel = i < num_rcv_urbs ? + &instance->rx_channel : &instance->tx_channel; + struct urb *urb; + unsigned int iso_packets = usb_pipeisoc(channel->endpoint) ? channel->buf_size / channel->packet_size : 0; + + UDSL_ASSERT(instance, !usb_pipeisoc(channel->endpoint) || usb_pipein(channel->endpoint)); + + urb = usb_alloc_urb(iso_packets, GFP_KERNEL); + if (!urb) { + dev_err(dev, "%s: no memory for urb %d!\n", __func__, i); + error = -ENOMEM; + goto fail_unbind; + } + + instance->urbs[i] = urb; + + /* zero the tx padding to avoid leaking information */ + buffer = kzalloc(channel->buf_size, GFP_KERNEL); + if (!buffer) { + dev_err(dev, "%s: no memory for buffer %d!\n", __func__, i); + error = -ENOMEM; + goto fail_unbind; + } + + usb_fill_bulk_urb(urb, instance->usb_dev, channel->endpoint, + buffer, channel->buf_size, usbatm_complete, channel); + if (iso_packets) { + int j; + urb->interval = 1; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = iso_packets; + for (j = 0; j < iso_packets; j++) { + urb->iso_frame_desc[j].offset = channel->packet_size * j; + urb->iso_frame_desc[j].length = channel->packet_size; + } + } + + /* put all tx URBs on the list of spares */ + if (i >= num_rcv_urbs) + list_add_tail(&urb->urb_list, &channel->list); + + vdbg(&intf->dev, "%s: alloced buffer 0x%p buf size %u urb 0x%p", + __func__, urb->transfer_buffer, urb->transfer_buffer_length, urb); + } + + instance->cached_vpi = ATM_VPI_UNSPEC; + instance->cached_vci = ATM_VCI_UNSPEC; + instance->cell_buf = kmalloc(instance->rx_channel.stride, GFP_KERNEL); + + if (!instance->cell_buf) { + dev_err(dev, "%s: no memory for cell buffer!\n", __func__); + error = -ENOMEM; + goto fail_unbind; + } + + if (!(instance->flags & UDSL_SKIP_HEAVY_INIT) && driver->heavy_init) { + error = usbatm_heavy_init(instance); + } else { + complete(&instance->thread_exited); /* pretend that heavy_init was run */ + error = usbatm_atm_init(instance); + } + + if (error < 0) + goto fail_unbind; + + usb_get_dev(usb_dev); + usb_set_intfdata(intf, instance); + + return 0; + + fail_unbind: + if (instance->driver->unbind) + instance->driver->unbind(instance, intf); + fail_free: + kfree(instance->cell_buf); + + for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) { + if (instance->urbs[i]) + kfree(instance->urbs[i]->transfer_buffer); + usb_free_urb(instance->urbs[i]); + } + + kfree(instance); + + return error; +} +EXPORT_SYMBOL_GPL(usbatm_usb_probe); + +void usbatm_usb_disconnect(struct usb_interface *intf) +{ + struct device *dev = &intf->dev; + struct usbatm_data *instance = usb_get_intfdata(intf); + struct usbatm_vcc_data *vcc_data; + int i; + + dev_dbg(dev, "%s entered\n", __func__); + + if (!instance) { + dev_dbg(dev, "%s: NULL instance!\n", __func__); + return; + } + + usb_set_intfdata(intf, NULL); + + mutex_lock(&instance->serialize); + instance->disconnected = 1; + if (instance->thread != NULL) + send_sig(SIGTERM, instance->thread, 1); + mutex_unlock(&instance->serialize); + + wait_for_completion(&instance->thread_exited); + + mutex_lock(&instance->serialize); + list_for_each_entry(vcc_data, &instance->vcc_list, list) + vcc_release_async(vcc_data->vcc, -EPIPE); + mutex_unlock(&instance->serialize); + + tasklet_disable(&instance->rx_channel.tasklet); + tasklet_disable(&instance->tx_channel.tasklet); + + for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) + usb_kill_urb(instance->urbs[i]); + + del_timer_sync(&instance->rx_channel.delay); + del_timer_sync(&instance->tx_channel.delay); + + /* turn usbatm_[rt]x_process into something close to a no-op */ + /* no need to take the spinlock */ + INIT_LIST_HEAD(&instance->rx_channel.list); + INIT_LIST_HEAD(&instance->tx_channel.list); + + tasklet_enable(&instance->rx_channel.tasklet); + tasklet_enable(&instance->tx_channel.tasklet); + + if (instance->atm_dev && instance->driver->atm_stop) + instance->driver->atm_stop(instance, instance->atm_dev); + + if (instance->driver->unbind) + instance->driver->unbind(instance, intf); + + instance->driver_data = NULL; + + for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) { + kfree(instance->urbs[i]->transfer_buffer); + usb_free_urb(instance->urbs[i]); + } + + kfree(instance->cell_buf); + + /* ATM finalize */ + if (instance->atm_dev) { + atm_dev_deregister(instance->atm_dev); + instance->atm_dev = NULL; + } + + usbatm_put_instance(instance); /* taken in usbatm_usb_probe */ +} +EXPORT_SYMBOL_GPL(usbatm_usb_disconnect); + + +/*********** +** init ** +***********/ + +static int __init usbatm_usb_init(void) +{ + if (sizeof(struct usbatm_control) > FIELD_SIZEOF(struct sk_buff, cb)) { + printk(KERN_ERR "%s unusable with this kernel!\n", usbatm_driver_name); + return -EIO; + } + + if ((num_rcv_urbs > UDSL_MAX_RCV_URBS) + || (num_snd_urbs > UDSL_MAX_SND_URBS) + || (rcv_buf_bytes < 1) + || (rcv_buf_bytes > UDSL_MAX_BUF_SIZE) + || (snd_buf_bytes < 1) + || (snd_buf_bytes > UDSL_MAX_BUF_SIZE)) + return -EINVAL; + + return 0; +} +module_init(usbatm_usb_init); + +static void __exit usbatm_usb_exit(void) +{ +} +module_exit(usbatm_usb_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +/************ +** debug ** +************/ + +#ifdef VERBOSE_DEBUG +static int usbatm_print_packet(struct usbatm_data *instance, + const unsigned char *data, int len) +{ + unsigned char buffer[256]; + int i = 0, j = 0; + + for (i = 0; i < len;) { + buffer[0] = '\0'; + sprintf(buffer, "%.3d :", i); + for (j = 0; (j < 16) && (i < len); j++, i++) + sprintf(buffer, "%s %2.2x", buffer, data[i]); + dev_dbg(&instance->usb_intf->dev, "%s", buffer); + } + return i; +} +#endif diff --git a/drivers/usb/atm/usbatm.h b/drivers/usb/atm/usbatm.h new file mode 100644 index 00000000..5fc48940 --- /dev/null +++ b/drivers/usb/atm/usbatm.h @@ -0,0 +1,222 @@ +/****************************************************************************** + * usbatm.h - Generic USB xDSL driver core + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas + * Copyright (C) 2004, David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#ifndef _USBATM_H_ +#define _USBATM_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +#define VERBOSE_DEBUG +*/ + +#ifdef DEBUG +#define UDSL_ASSERT(instance, x) BUG_ON(!(x)) +#else +#define UDSL_ASSERT(instance, x) \ + do { \ + if (!(x)) \ + dev_warn(&(instance)->usb_intf->dev, \ + "failed assertion '%s' at line %d", \ + __stringify(x), __LINE__); \ + } while (0) +#endif + +#define usb_err(instance, format, arg...) \ + dev_err(&(instance)->usb_intf->dev , format , ## arg) +#define usb_info(instance, format, arg...) \ + dev_info(&(instance)->usb_intf->dev , format , ## arg) +#define usb_warn(instance, format, arg...) \ + dev_warn(&(instance)->usb_intf->dev , format , ## arg) +#ifdef DEBUG +#define usb_dbg(instance, format, arg...) \ + dev_printk(KERN_DEBUG , &(instance)->usb_intf->dev , format , ## arg) +#else +#define usb_dbg(instance, format, arg...) \ + do {} while (0) +#endif + +/* FIXME: move to dev_* once ATM is driver model aware */ +#define atm_printk(level, instance, format, arg...) \ + printk(level "ATM dev %d: " format , \ + (instance)->atm_dev->number , ## arg) + +#define atm_err(instance, format, arg...) \ + atm_printk(KERN_ERR, instance , format , ## arg) +#define atm_info(instance, format, arg...) \ + atm_printk(KERN_INFO, instance , format , ## arg) +#define atm_warn(instance, format, arg...) \ + atm_printk(KERN_WARNING, instance , format , ## arg) +#ifdef DEBUG +#define atm_dbg(instance, format, arg...) \ + atm_printk(KERN_DEBUG, instance , format , ## arg) +#define atm_rldbg(instance, format, arg...) \ + if (printk_ratelimit()) \ + atm_printk(KERN_DEBUG, instance , format , ## arg) +#else +#define atm_dbg(instance, format, arg...) \ + do {} while (0) +#define atm_rldbg(instance, format, arg...) \ + do {} while (0) +#endif + + +/* flags, set by mini-driver in bind() */ + +#define UDSL_SKIP_HEAVY_INIT (1<<0) +#define UDSL_USE_ISOC (1<<1) +#define UDSL_IGNORE_EILSEQ (1<<2) + + +/* mini driver */ + +struct usbatm_data; + +/* +* Assuming all methods exist and succeed, they are called in this order: +* +* bind, heavy_init, atm_start, ..., atm_stop, unbind +*/ + +struct usbatm_driver { + const char *driver_name; + + /* init device ... can sleep, or cause probe() failure */ + int (*bind) (struct usbatm_data *, struct usb_interface *, + const struct usb_device_id *id); + + /* additional device initialization that is too slow to be done in probe() */ + int (*heavy_init) (struct usbatm_data *, struct usb_interface *); + + /* cleanup device ... can sleep, but can't fail */ + void (*unbind) (struct usbatm_data *, struct usb_interface *); + + /* init ATM device ... can sleep, or cause ATM initialization failure */ + int (*atm_start) (struct usbatm_data *, struct atm_dev *); + + /* cleanup ATM device ... can sleep, but can't fail */ + void (*atm_stop) (struct usbatm_data *, struct atm_dev *); + + int bulk_in; /* bulk rx endpoint */ + int isoc_in; /* isochronous rx endpoint */ + int bulk_out; /* bulk tx endpoint */ + + unsigned rx_padding; + unsigned tx_padding; +}; + +extern int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id, + struct usbatm_driver *driver); +extern void usbatm_usb_disconnect(struct usb_interface *intf); + + +struct usbatm_channel { + int endpoint; /* usb pipe */ + unsigned int stride; /* ATM cell size + padding */ + unsigned int buf_size; /* urb buffer size */ + unsigned int packet_size; /* endpoint maxpacket */ + spinlock_t lock; + struct list_head list; + struct tasklet_struct tasklet; + struct timer_list delay; + struct usbatm_data *usbatm; +}; + +/* main driver data */ + +struct usbatm_data { + /****************** + * public fields * + ******************/ + + /* mini driver */ + struct usbatm_driver *driver; + void *driver_data; + char driver_name[16]; + unsigned int flags; /* set by mini-driver in bind() */ + + /* USB device */ + struct usb_device *usb_dev; + struct usb_interface *usb_intf; + char description[64]; + + /* ATM device */ + struct atm_dev *atm_dev; + + /******************************** + * private fields - do not use * + ********************************/ + + struct kref refcount; + struct mutex serialize; + int disconnected; + + /* heavy init */ + struct task_struct *thread; + struct completion thread_started; + struct completion thread_exited; + + /* ATM device */ + struct list_head vcc_list; + + struct usbatm_channel rx_channel; + struct usbatm_channel tx_channel; + + struct sk_buff_head sndqueue; + struct sk_buff *current_skb; /* being emptied */ + + struct usbatm_vcc_data *cached_vcc; + int cached_vci; + short cached_vpi; + + unsigned char *cell_buf; /* holds partial rx cell */ + unsigned int buf_usage; + + struct urb *urbs[0]; +}; + +static inline void *to_usbatm_driver_data(struct usb_interface *intf) +{ + struct usbatm_data *usbatm_instance; + + if (intf == NULL) + return NULL; + + usbatm_instance = usb_get_intfdata(intf); + + if (usbatm_instance == NULL) /* set NULL before unbind() */ + return NULL; + + return usbatm_instance->driver_data; /* set NULL after unbind() */ +} + +#endif /* _USBATM_H_ */ diff --git a/drivers/usb/atm/xusbatm.c b/drivers/usb/atm/xusbatm.c new file mode 100644 index 00000000..b3b1bb78 --- /dev/null +++ b/drivers/usb/atm/xusbatm.c @@ -0,0 +1,229 @@ +/****************************************************************************** + * xusbatm.c - dumb usbatm-based driver for modems initialized in userspace + * + * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#include +#include /* for eth_random_addr() */ + +#include "usbatm.h" + + +#define XUSBATM_DRIVERS_MAX 8 + +#define XUSBATM_PARM(name, type, parmtype, desc) \ + static type name[XUSBATM_DRIVERS_MAX]; \ + static unsigned int num_##name; \ + module_param_array(name, parmtype, &num_##name, 0444); \ + MODULE_PARM_DESC(name, desc) + +XUSBATM_PARM(vendor, unsigned short, ushort, "USB device vendor"); +XUSBATM_PARM(product, unsigned short, ushort, "USB device product"); + +XUSBATM_PARM(rx_endpoint, unsigned char, byte, "rx endpoint number"); +XUSBATM_PARM(tx_endpoint, unsigned char, byte, "tx endpoint number"); +XUSBATM_PARM(rx_padding, unsigned char, byte, "rx padding (default 0)"); +XUSBATM_PARM(tx_padding, unsigned char, byte, "tx padding (default 0)"); +XUSBATM_PARM(rx_altsetting, unsigned char, byte, "rx altsetting (default 0)"); +XUSBATM_PARM(tx_altsetting, unsigned char, byte, "rx altsetting (default 0)"); + +static const char xusbatm_driver_name[] = "xusbatm"; + +static struct usbatm_driver xusbatm_drivers[XUSBATM_DRIVERS_MAX]; +static struct usb_device_id xusbatm_usb_ids[XUSBATM_DRIVERS_MAX + 1]; +static struct usb_driver xusbatm_usb_driver; + +static struct usb_interface *xusbatm_find_intf(struct usb_device *usb_dev, int altsetting, u8 ep) +{ + struct usb_host_interface *alt; + struct usb_interface *intf; + int i, j; + + for (i = 0; i < usb_dev->actconfig->desc.bNumInterfaces; i++) + if ((intf = usb_dev->actconfig->interface[i]) && (alt = usb_altnum_to_altsetting(intf, altsetting))) + for (j = 0; j < alt->desc.bNumEndpoints; j++) + if (alt->endpoint[j].desc.bEndpointAddress == ep) + return intf; + return NULL; +} + +static int xusbatm_capture_intf(struct usbatm_data *usbatm, struct usb_device *usb_dev, + struct usb_interface *intf, int altsetting, int claim) +{ + int ifnum = intf->altsetting->desc.bInterfaceNumber; + int ret; + + if (claim && (ret = usb_driver_claim_interface(&xusbatm_usb_driver, intf, usbatm))) { + usb_err(usbatm, "%s: failed to claim interface %2d (%d)!\n", __func__, ifnum, ret); + return ret; + } + if ((ret = usb_set_interface(usb_dev, ifnum, altsetting))) { + usb_err(usbatm, "%s: altsetting %2d for interface %2d failed (%d)!\n", __func__, altsetting, ifnum, ret); + return ret; + } + return 0; +} + +static void xusbatm_release_intf(struct usb_device *usb_dev, struct usb_interface *intf, int claimed) +{ + if (claimed) { + usb_set_intfdata(intf, NULL); + usb_driver_release_interface(&xusbatm_usb_driver, intf); + } +} + +static int xusbatm_bind(struct usbatm_data *usbatm, + struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + int drv_ix = id - xusbatm_usb_ids; + int rx_alt = rx_altsetting[drv_ix]; + int tx_alt = tx_altsetting[drv_ix]; + struct usb_interface *rx_intf = xusbatm_find_intf(usb_dev, rx_alt, rx_endpoint[drv_ix]); + struct usb_interface *tx_intf = xusbatm_find_intf(usb_dev, tx_alt, tx_endpoint[drv_ix]); + int ret; + + usb_dbg(usbatm, "%s: binding driver %d: vendor %04x product %04x" + " rx: ep %02x padd %d alt %2d tx: ep %02x padd %d alt %2d\n", + __func__, drv_ix, vendor[drv_ix], product[drv_ix], + rx_endpoint[drv_ix], rx_padding[drv_ix], rx_alt, + tx_endpoint[drv_ix], tx_padding[drv_ix], tx_alt); + + if (!rx_intf || !tx_intf) { + if (!rx_intf) + usb_dbg(usbatm, "%s: no interface contains endpoint %02x in altsetting %2d\n", + __func__, rx_endpoint[drv_ix], rx_alt); + if (!tx_intf) + usb_dbg(usbatm, "%s: no interface contains endpoint %02x in altsetting %2d\n", + __func__, tx_endpoint[drv_ix], tx_alt); + return -ENODEV; + } + + if ((rx_intf != intf) && (tx_intf != intf)) + return -ENODEV; + + if ((rx_intf == tx_intf) && (rx_alt != tx_alt)) { + usb_err(usbatm, "%s: altsettings clash on interface %2d (%2d vs %2d)!\n", __func__, + rx_intf->altsetting->desc.bInterfaceNumber, rx_alt, tx_alt); + return -EINVAL; + } + + usb_dbg(usbatm, "%s: rx If#=%2d; tx If#=%2d\n", __func__, + rx_intf->altsetting->desc.bInterfaceNumber, + tx_intf->altsetting->desc.bInterfaceNumber); + + if ((ret = xusbatm_capture_intf(usbatm, usb_dev, rx_intf, rx_alt, rx_intf != intf))) + return ret; + + if ((tx_intf != rx_intf) && (ret = xusbatm_capture_intf(usbatm, usb_dev, tx_intf, tx_alt, tx_intf != intf))) { + xusbatm_release_intf(usb_dev, rx_intf, rx_intf != intf); + return ret; + } + + return 0; +} + +static void xusbatm_unbind(struct usbatm_data *usbatm, + struct usb_interface *intf) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + int i; + + usb_dbg(usbatm, "%s entered\n", __func__); + + for (i = 0; i < usb_dev->actconfig->desc.bNumInterfaces; i++) { + struct usb_interface *cur_intf = usb_dev->actconfig->interface[i]; + + if (cur_intf && (usb_get_intfdata(cur_intf) == usbatm)) { + usb_set_intfdata(cur_intf, NULL); + usb_driver_release_interface(&xusbatm_usb_driver, cur_intf); + } + } +} + +static int xusbatm_atm_start(struct usbatm_data *usbatm, + struct atm_dev *atm_dev) +{ + atm_dbg(usbatm, "%s entered\n", __func__); + + /* use random MAC as we've no way to get it from the device */ + eth_random_addr(atm_dev->esi); + + return 0; +} + + +static int xusbatm_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return usbatm_usb_probe(intf, id, + xusbatm_drivers + (id - xusbatm_usb_ids)); +} + +static struct usb_driver xusbatm_usb_driver = { + .name = xusbatm_driver_name, + .probe = xusbatm_usb_probe, + .disconnect = usbatm_usb_disconnect, + .id_table = xusbatm_usb_ids +}; + +static int __init xusbatm_init(void) +{ + int i; + + if (!num_vendor || + num_vendor != num_product || + num_vendor != num_rx_endpoint || + num_vendor != num_tx_endpoint) { + printk(KERN_WARNING "xusbatm: malformed module parameters\n"); + return -EINVAL; + } + + for (i = 0; i < num_vendor; i++) { + rx_endpoint[i] |= USB_DIR_IN; + tx_endpoint[i] &= USB_ENDPOINT_NUMBER_MASK; + + xusbatm_usb_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + xusbatm_usb_ids[i].idVendor = vendor[i]; + xusbatm_usb_ids[i].idProduct = product[i]; + + xusbatm_drivers[i].driver_name = xusbatm_driver_name; + xusbatm_drivers[i].bind = xusbatm_bind; + xusbatm_drivers[i].unbind = xusbatm_unbind; + xusbatm_drivers[i].atm_start = xusbatm_atm_start; + xusbatm_drivers[i].bulk_in = rx_endpoint[i]; + xusbatm_drivers[i].bulk_out = tx_endpoint[i]; + xusbatm_drivers[i].rx_padding = rx_padding[i]; + xusbatm_drivers[i].tx_padding = tx_padding[i]; + } + + return usb_register(&xusbatm_usb_driver); +} +module_init(xusbatm_init); + +static void __exit xusbatm_exit(void) +{ + usb_deregister(&xusbatm_usb_driver); +} +module_exit(xusbatm_exit); + +MODULE_AUTHOR("Roman Kagan, Duncan Sands"); +MODULE_DESCRIPTION("Driver for USB ADSL modems initialized in userspace"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); diff --git a/drivers/usb/c67x00/Makefile b/drivers/usb/c67x00/Makefile new file mode 100644 index 00000000..b1218683 --- /dev/null +++ b/drivers/usb/c67x00/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for Cypress C67X00 USB Controller +# + +ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG + +obj-$(CONFIG_USB_C67X00_HCD) += c67x00.o + +c67x00-y := c67x00-drv.o c67x00-ll-hpi.o c67x00-hcd.o c67x00-sched.o diff --git a/drivers/usb/c67x00/c67x00-drv.c b/drivers/usb/c67x00/c67x00-drv.c new file mode 100644 index 00000000..fe815ecd --- /dev/null +++ b/drivers/usb/c67x00/c67x00-drv.c @@ -0,0 +1,234 @@ +/* + * c67x00-drv.c: Cypress C67X00 USB Common infrastructure + * + * Copyright (C) 2006-2008 Barco N.V. + * Derived from the Cypress cy7c67200/300 ezusb linux driver and + * based on multiple host controller drivers inside the linux kernel. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + */ + +/* + * This file implements the common infrastructure for using the c67x00. + * It is both the link between the platform configuration and subdrivers and + * the link between the common hardware parts and the subdrivers (e.g. + * interrupt handling). + * + * The c67x00 has 2 SIE's (serial interface engine) which can be configured + * to be host, device or OTG (with some limitations, E.G. only SIE1 can be OTG). + * + * Depending on the platform configuration, the SIE's are created and + * the corresponding subdriver is initialized (c67x00_probe_sie). + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "c67x00.h" +#include "c67x00-hcd.h" + +static void c67x00_probe_sie(struct c67x00_sie *sie, + struct c67x00_device *dev, int sie_num) +{ + spin_lock_init(&sie->lock); + sie->dev = dev; + sie->sie_num = sie_num; + sie->mode = c67x00_sie_config(dev->pdata->sie_config, sie_num); + + switch (sie->mode) { + case C67X00_SIE_HOST: + c67x00_hcd_probe(sie); + break; + + case C67X00_SIE_UNUSED: + dev_info(sie_dev(sie), + "Not using SIE %d as requested\n", sie->sie_num); + break; + + default: + dev_err(sie_dev(sie), + "Unsupported configuration: 0x%x for SIE %d\n", + sie->mode, sie->sie_num); + break; + } +} + +static void c67x00_remove_sie(struct c67x00_sie *sie) +{ + switch (sie->mode) { + case C67X00_SIE_HOST: + c67x00_hcd_remove(sie); + break; + + default: + break; + } +} + +static irqreturn_t c67x00_irq(int irq, void *__dev) +{ + struct c67x00_device *c67x00 = __dev; + struct c67x00_sie *sie; + u16 msg, int_status; + int i, count = 8; + + int_status = c67x00_ll_hpi_status(c67x00); + if (!int_status) + return IRQ_NONE; + + while (int_status != 0 && (count-- >= 0)) { + c67x00_ll_irq(c67x00, int_status); + for (i = 0; i < C67X00_SIES; i++) { + sie = &c67x00->sie[i]; + msg = 0; + if (int_status & SIEMSG_FLG(i)) + msg = c67x00_ll_fetch_siemsg(c67x00, i); + if (sie->irq) + sie->irq(sie, int_status, msg); + } + int_status = c67x00_ll_hpi_status(c67x00); + } + + if (int_status) + dev_warn(&c67x00->pdev->dev, "Not all interrupts handled! " + "status = 0x%04x\n", int_status); + + return IRQ_HANDLED; +} + +/* ------------------------------------------------------------------------- */ + +static int c67x00_drv_probe(struct platform_device *pdev) +{ + struct c67x00_device *c67x00; + struct c67x00_platform_data *pdata; + struct resource *res, *res2; + int ret, i; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + res2 = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res2) + return -ENODEV; + + pdata = pdev->dev.platform_data; + if (!pdata) + return -ENODEV; + + c67x00 = kzalloc(sizeof(*c67x00), GFP_KERNEL); + if (!c67x00) + return -ENOMEM; + + if (!request_mem_region(res->start, resource_size(res), + pdev->name)) { + dev_err(&pdev->dev, "Memory region busy\n"); + ret = -EBUSY; + goto request_mem_failed; + } + c67x00->hpi.base = ioremap(res->start, resource_size(res)); + if (!c67x00->hpi.base) { + dev_err(&pdev->dev, "Unable to map HPI registers\n"); + ret = -EIO; + goto map_failed; + } + + spin_lock_init(&c67x00->hpi.lock); + c67x00->hpi.regstep = pdata->hpi_regstep; + c67x00->pdata = pdev->dev.platform_data; + c67x00->pdev = pdev; + + c67x00_ll_init(c67x00); + c67x00_ll_hpi_reg_init(c67x00); + + ret = request_irq(res2->start, c67x00_irq, 0, pdev->name, c67x00); + if (ret) { + dev_err(&pdev->dev, "Cannot claim IRQ\n"); + goto request_irq_failed; + } + + ret = c67x00_ll_reset(c67x00); + if (ret) { + dev_err(&pdev->dev, "Device reset failed\n"); + goto reset_failed; + } + + for (i = 0; i < C67X00_SIES; i++) + c67x00_probe_sie(&c67x00->sie[i], c67x00, i); + + platform_set_drvdata(pdev, c67x00); + + return 0; + + reset_failed: + free_irq(res2->start, c67x00); + request_irq_failed: + iounmap(c67x00->hpi.base); + map_failed: + release_mem_region(res->start, resource_size(res)); + request_mem_failed: + kfree(c67x00); + + return ret; +} + +static int c67x00_drv_remove(struct platform_device *pdev) +{ + struct c67x00_device *c67x00 = platform_get_drvdata(pdev); + struct resource *res; + int i; + + for (i = 0; i < C67X00_SIES; i++) + c67x00_remove_sie(&c67x00->sie[i]); + + c67x00_ll_release(c67x00); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) + free_irq(res->start, c67x00); + + iounmap(c67x00->hpi.base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + + kfree(c67x00); + + return 0; +} + +static struct platform_driver c67x00_driver = { + .probe = c67x00_drv_probe, + .remove = c67x00_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = "c67x00", + }, +}; + +module_platform_driver(c67x00_driver); + +MODULE_AUTHOR("Peter Korsgaard, Jan Veldeman, Grant Likely"); +MODULE_DESCRIPTION("Cypress C67X00 USB Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:c67x00"); diff --git a/drivers/usb/c67x00/c67x00-hcd.c b/drivers/usb/c67x00/c67x00-hcd.c new file mode 100644 index 00000000..75e47b86 --- /dev/null +++ b/drivers/usb/c67x00/c67x00-hcd.c @@ -0,0 +1,411 @@ +/* + * c67x00-hcd.c: Cypress C67X00 USB Host Controller Driver + * + * Copyright (C) 2006-2008 Barco N.V. + * Derived from the Cypress cy7c67200/300 ezusb linux driver and + * based on multiple host controller drivers inside the linux kernel. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + */ + +#include +#include +#include + +#include "c67x00.h" +#include "c67x00-hcd.h" + +/* -------------------------------------------------------------------------- + * Root Hub Support + */ + +static __u8 c67x00_hub_des[] = { + 0x09, /* __u8 bLength; */ + 0x29, /* __u8 bDescriptorType; Hub-descriptor */ + 0x02, /* __u8 bNbrPorts; */ + 0x00, /* __u16 wHubCharacteristics; */ + 0x00, /* (per-port OC, no power switching) */ + 0x32, /* __u8 bPwrOn2pwrGood; 2ms */ + 0x00, /* __u8 bHubContrCurrent; 0 mA */ + 0x00, /* __u8 DeviceRemovable; ** 7 Ports max ** */ + 0xff, /* __u8 PortPwrCtrlMask; ** 7 ports max ** */ +}; + +static void c67x00_hub_reset_host_port(struct c67x00_sie *sie, int port) +{ + struct c67x00_hcd *c67x00 = sie->private_data; + unsigned long flags; + + c67x00_ll_husb_reset(sie, port); + + spin_lock_irqsave(&c67x00->lock, flags); + c67x00_ll_husb_reset_port(sie, port); + spin_unlock_irqrestore(&c67x00->lock, flags); + + c67x00_ll_set_husb_eot(sie->dev, DEFAULT_EOT); +} + +static int c67x00_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); + struct c67x00_sie *sie = c67x00->sie; + u16 status; + int i; + + *buf = 0; + status = c67x00_ll_usb_get_status(sie); + for (i = 0; i < C67X00_PORTS; i++) + if (status & PORT_CONNECT_CHANGE(i)) + *buf |= (1 << i); + + /* bit 0 denotes hub change, b1..n port change */ + *buf <<= 1; + + return !!*buf; +} + +static int c67x00_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); + struct c67x00_sie *sie = c67x00->sie; + u16 status, usb_status; + int len = 0; + unsigned int port = wIndex-1; + u16 wPortChange, wPortStatus; + + switch (typeReq) { + + case GetHubStatus: + *(__le32 *) buf = cpu_to_le32(0); + len = 4; /* hub power */ + break; + + case GetPortStatus: + if (wIndex > C67X00_PORTS) + return -EPIPE; + + status = c67x00_ll_usb_get_status(sie); + usb_status = c67x00_ll_get_usb_ctl(sie); + + wPortChange = 0; + if (status & PORT_CONNECT_CHANGE(port)) + wPortChange |= USB_PORT_STAT_C_CONNECTION; + + wPortStatus = USB_PORT_STAT_POWER; + if (!(status & PORT_SE0_STATUS(port))) + wPortStatus |= USB_PORT_STAT_CONNECTION; + if (usb_status & LOW_SPEED_PORT(port)) { + wPortStatus |= USB_PORT_STAT_LOW_SPEED; + c67x00->low_speed_ports |= (1 << port); + } else + c67x00->low_speed_ports &= ~(1 << port); + + if (usb_status & SOF_EOP_EN(port)) + wPortStatus |= USB_PORT_STAT_ENABLE; + + *(__le16 *) buf = cpu_to_le16(wPortStatus); + *(__le16 *) (buf + 2) = cpu_to_le16(wPortChange); + len = 4; + break; + + case SetHubFeature: /* We don't implement these */ + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + len = 0; + break; + + default: + return -EPIPE; + } + break; + + case SetPortFeature: + if (wIndex > C67X00_PORTS) + return -EPIPE; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + dev_dbg(c67x00_hcd_dev(c67x00), + "SetPortFeature %d (SUSPEND)\n", port); + len = 0; + break; + + case USB_PORT_FEAT_RESET: + c67x00_hub_reset_host_port(sie, port); + len = 0; + break; + + case USB_PORT_FEAT_POWER: + /* Power always enabled */ + len = 0; + break; + + default: + dev_dbg(c67x00_hcd_dev(c67x00), + "%s: SetPortFeature %d (0x%04x) Error!\n", + __func__, port, wValue); + return -EPIPE; + } + break; + + case ClearPortFeature: + if (wIndex > C67X00_PORTS) + return -EPIPE; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + /* Reset the port so that the c67x00 also notices the + * disconnect */ + c67x00_hub_reset_host_port(sie, port); + len = 0; + break; + + case USB_PORT_FEAT_C_ENABLE: + dev_dbg(c67x00_hcd_dev(c67x00), + "ClearPortFeature (%d): C_ENABLE\n", port); + len = 0; + break; + + case USB_PORT_FEAT_SUSPEND: + dev_dbg(c67x00_hcd_dev(c67x00), + "ClearPortFeature (%d): SUSPEND\n", port); + len = 0; + break; + + case USB_PORT_FEAT_C_SUSPEND: + dev_dbg(c67x00_hcd_dev(c67x00), + "ClearPortFeature (%d): C_SUSPEND\n", port); + len = 0; + break; + + case USB_PORT_FEAT_POWER: + dev_dbg(c67x00_hcd_dev(c67x00), + "ClearPortFeature (%d): POWER\n", port); + return -EPIPE; + + case USB_PORT_FEAT_C_CONNECTION: + c67x00_ll_usb_clear_status(sie, + PORT_CONNECT_CHANGE(port)); + len = 0; + break; + + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(c67x00_hcd_dev(c67x00), + "ClearPortFeature (%d): OVER_CURRENT\n", port); + len = 0; + break; + + case USB_PORT_FEAT_C_RESET: + dev_dbg(c67x00_hcd_dev(c67x00), + "ClearPortFeature (%d): C_RESET\n", port); + len = 0; + break; + + default: + dev_dbg(c67x00_hcd_dev(c67x00), + "%s: ClearPortFeature %d (0x%04x) Error!\n", + __func__, port, wValue); + return -EPIPE; + } + break; + + case GetHubDescriptor: + len = min_t(unsigned int, sizeof(c67x00_hub_des), wLength); + memcpy(buf, c67x00_hub_des, len); + break; + + default: + dev_dbg(c67x00_hcd_dev(c67x00), "%s: unknown\n", __func__); + return -EPIPE; + } + + return 0; +} + +/* --------------------------------------------------------------------- + * Main part of host controller driver + */ + +/** + * c67x00_hcd_irq + * + * This function is called from the interrupt handler in c67x00-drv.c + */ +static void c67x00_hcd_irq(struct c67x00_sie *sie, u16 int_status, u16 msg) +{ + struct c67x00_hcd *c67x00 = sie->private_data; + struct usb_hcd *hcd = c67x00_hcd_to_hcd(c67x00); + + /* Handle sie message flags */ + if (msg) { + if (msg & HUSB_TDListDone) + c67x00_sched_kick(c67x00); + else + dev_warn(c67x00_hcd_dev(c67x00), + "Unknown SIE msg flag(s): 0x%04x\n", msg); + } + + if (unlikely(hcd->state == HC_STATE_HALT)) + return; + + if (!HCD_HW_ACCESSIBLE(hcd)) + return; + + /* Handle Start of frame events */ + if (int_status & SOFEOP_FLG(sie->sie_num)) { + c67x00_ll_usb_clear_status(sie, SOF_EOP_IRQ_FLG); + c67x00_sched_kick(c67x00); + } +} + +/** + * c67x00_hcd_start: Host controller start hook + */ +static int c67x00_hcd_start(struct usb_hcd *hcd) +{ + hcd->uses_new_polling = 1; + hcd->state = HC_STATE_RUNNING; + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + + return 0; +} + +/** + * c67x00_hcd_stop: Host controller stop hook + */ +static void c67x00_hcd_stop(struct usb_hcd *hcd) +{ + /* Nothing to do */ +} + +static int c67x00_hcd_get_frame(struct usb_hcd *hcd) +{ + struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); + u16 temp_val; + + dev_dbg(c67x00_hcd_dev(c67x00), "%s\n", __func__); + temp_val = c67x00_ll_husb_get_frame(c67x00->sie); + temp_val &= HOST_FRAME_MASK; + return temp_val ? (temp_val - 1) : HOST_FRAME_MASK; +} + +static struct hc_driver c67x00_hc_driver = { + .description = "c67x00-hcd", + .product_desc = "Cypress C67X00 Host Controller", + .hcd_priv_size = sizeof(struct c67x00_hcd), + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .start = c67x00_hcd_start, + .stop = c67x00_hcd_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = c67x00_urb_enqueue, + .urb_dequeue = c67x00_urb_dequeue, + .endpoint_disable = c67x00_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = c67x00_hcd_get_frame, + + /* + * root hub support + */ + .hub_status_data = c67x00_hub_status_data, + .hub_control = c67x00_hub_control, +}; + +/* --------------------------------------------------------------------- + * Setup/Teardown routines + */ + +int c67x00_hcd_probe(struct c67x00_sie *sie) +{ + struct c67x00_hcd *c67x00; + struct usb_hcd *hcd; + unsigned long flags; + int retval; + + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&c67x00_hc_driver, sie_dev(sie), "c67x00_sie"); + if (!hcd) { + retval = -ENOMEM; + goto err0; + } + c67x00 = hcd_to_c67x00_hcd(hcd); + + spin_lock_init(&c67x00->lock); + c67x00->sie = sie; + + INIT_LIST_HEAD(&c67x00->list[PIPE_ISOCHRONOUS]); + INIT_LIST_HEAD(&c67x00->list[PIPE_INTERRUPT]); + INIT_LIST_HEAD(&c67x00->list[PIPE_CONTROL]); + INIT_LIST_HEAD(&c67x00->list[PIPE_BULK]); + c67x00->urb_count = 0; + INIT_LIST_HEAD(&c67x00->td_list); + c67x00->td_base_addr = CY_HCD_BUF_ADDR + SIE_TD_OFFSET(sie->sie_num); + c67x00->buf_base_addr = CY_HCD_BUF_ADDR + SIE_BUF_OFFSET(sie->sie_num); + c67x00->max_frame_bw = MAX_FRAME_BW_STD; + + c67x00_ll_husb_init_host_port(sie); + + init_completion(&c67x00->endpoint_disable); + retval = c67x00_sched_start_scheduler(c67x00); + if (retval) + goto err1; + + retval = usb_add_hcd(hcd, 0, 0); + if (retval) { + dev_dbg(sie_dev(sie), "%s: usb_add_hcd returned %d\n", + __func__, retval); + goto err2; + } + + spin_lock_irqsave(&sie->lock, flags); + sie->private_data = c67x00; + sie->irq = c67x00_hcd_irq; + spin_unlock_irqrestore(&sie->lock, flags); + + return retval; + + err2: + c67x00_sched_stop_scheduler(c67x00); + err1: + usb_put_hcd(hcd); + err0: + return retval; +} + +/* may be called with controller, bus, and devices active */ +void c67x00_hcd_remove(struct c67x00_sie *sie) +{ + struct c67x00_hcd *c67x00 = sie->private_data; + struct usb_hcd *hcd = c67x00_hcd_to_hcd(c67x00); + + c67x00_sched_stop_scheduler(c67x00); + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} diff --git a/drivers/usb/c67x00/c67x00-hcd.h b/drivers/usb/c67x00/c67x00-hcd.h new file mode 100644 index 00000000..e3d493d4 --- /dev/null +++ b/drivers/usb/c67x00/c67x00-hcd.h @@ -0,0 +1,133 @@ +/* + * c67x00-hcd.h: Cypress C67X00 USB HCD + * + * Copyright (C) 2006-2008 Barco N.V. + * Derived from the Cypress cy7c67200/300 ezusb linux driver and + * based on multiple host controller drivers inside the linux kernel. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + */ + +#ifndef _USB_C67X00_HCD_H +#define _USB_C67X00_HCD_H + +#include +#include +#include +#include +#include +#include "c67x00.h" + +/* + * The following parameters depend on the CPU speed, bus speed, ... + * These can be tuned for specific use cases, e.g. if isochronous transfers + * are very important, bandwidth can be sacrificed to guarantee that the + * 1ms deadline will be met. + * If bulk transfers are important, the MAX_FRAME_BW can be increased, + * but some (or many) isochronous deadlines might not be met. + * + * The values are specified in bittime. + */ + +/* + * The current implementation switches between _STD (default) and _ISO (when + * isochronous transfers are scheduled), in order to optimize the throughput + * in normal cicrumstances, but also provide good isochronous behaviour. + * + * Bandwidth is described in bit time so with a 12MHz USB clock and 1ms + * frames; there are 12000 bit times per frame. + */ + +#define TOTAL_FRAME_BW 12000 +#define DEFAULT_EOT 2250 + +#define MAX_FRAME_BW_STD (TOTAL_FRAME_BW - DEFAULT_EOT) +#define MAX_FRAME_BW_ISO 2400 + +/* + * Periodic transfers may only use 90% of the full frame, but as + * we currently don't even use 90% of the full frame, we may + * use the full usable time for periodic transfers. + */ +#define MAX_PERIODIC_BW(full_bw) full_bw + +/* -------------------------------------------------------------------------- */ + +struct c67x00_hcd { + spinlock_t lock; + struct c67x00_sie *sie; + unsigned int low_speed_ports; /* bitmask of low speed ports */ + unsigned int urb_count; + unsigned int urb_iso_count; + + struct list_head list[4]; /* iso, int, ctrl, bulk */ +#if PIPE_BULK != 3 +#error "Sanity check failed, this code presumes PIPE_... to range from 0 to 3" +#endif + + /* USB bandwidth allocated to td_list */ + int bandwidth_allocated; + /* USB bandwidth allocated for isoc/int transfer */ + int periodic_bw_allocated; + struct list_head td_list; + int max_frame_bw; + + u16 td_base_addr; + u16 buf_base_addr; + u16 next_td_addr; + u16 next_buf_addr; + + struct tasklet_struct tasklet; + + struct completion endpoint_disable; + + u16 current_frame; + u16 last_frame; +}; + +static inline struct c67x00_hcd *hcd_to_c67x00_hcd(struct usb_hcd *hcd) +{ + return (struct c67x00_hcd *)(hcd->hcd_priv); +} + +static inline struct usb_hcd *c67x00_hcd_to_hcd(struct c67x00_hcd *c67x00) +{ + return container_of((void *)c67x00, struct usb_hcd, hcd_priv); +} + +/* --------------------------------------------------------------------- + * Functions used by c67x00-drv + */ + +int c67x00_hcd_probe(struct c67x00_sie *sie); +void c67x00_hcd_remove(struct c67x00_sie *sie); + +/* --------------------------------------------------------------------- + * Transfer Descriptor scheduling functions + */ +int c67x00_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); +int c67x00_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); +void c67x00_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep); + +void c67x00_hcd_msg_received(struct c67x00_sie *sie, u16 msg); +void c67x00_sched_kick(struct c67x00_hcd *c67x00); +int c67x00_sched_start_scheduler(struct c67x00_hcd *c67x00); +void c67x00_sched_stop_scheduler(struct c67x00_hcd *c67x00); + +#define c67x00_hcd_dev(x) (c67x00_hcd_to_hcd(x)->self.controller) + +#endif /* _USB_C67X00_HCD_H */ diff --git a/drivers/usb/c67x00/c67x00-ll-hpi.c b/drivers/usb/c67x00/c67x00-ll-hpi.c new file mode 100644 index 00000000..3a1ca4df --- /dev/null +++ b/drivers/usb/c67x00/c67x00-ll-hpi.c @@ -0,0 +1,481 @@ +/* + * c67x00-ll-hpi.c: Cypress C67X00 USB Low level interface using HPI + * + * Copyright (C) 2006-2008 Barco N.V. + * Derived from the Cypress cy7c67200/300 ezusb linux driver and + * based on multiple host controller drivers inside the linux kernel. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include "c67x00.h" + +#define COMM_REGS 14 + +struct c67x00_lcp_int_data { + u16 regs[COMM_REGS]; +}; + +/* -------------------------------------------------------------------------- */ +/* Interface definitions */ + +#define COMM_ACK 0x0FED +#define COMM_NAK 0xDEAD + +#define COMM_RESET 0xFA50 +#define COMM_EXEC_INT 0xCE01 +#define COMM_INT_NUM 0x01C2 + +/* Registers 0 to COMM_REGS-1 */ +#define COMM_R(x) (0x01C4 + 2 * (x)) + +#define HUSB_SIE_pCurrentTDPtr(x) ((x) ? 0x01B2 : 0x01B0) +#define HUSB_SIE_pTDListDone_Sem(x) ((x) ? 0x01B8 : 0x01B6) +#define HUSB_pEOT 0x01B4 + +/* Software interrupts */ +/* 114, 115: */ +#define HUSB_SIE_INIT_INT(x) ((x) ? 0x0073 : 0x0072) +#define HUSB_RESET_INT 0x0074 + +#define SUSB_INIT_INT 0x0071 +#define SUSB_INIT_INT_LOC (SUSB_INIT_INT * 2) + +/* ----------------------------------------------------------------------- + * HPI implementation + * + * The c67x00 chip also support control via SPI or HSS serial + * interfaces. However, this driver assumes that register access can + * be performed from IRQ context. While this is a safe assuption with + * the HPI interface, it is not true for the serial interfaces. + */ + +/* HPI registers */ +#define HPI_DATA 0 +#define HPI_MAILBOX 1 +#define HPI_ADDR 2 +#define HPI_STATUS 3 + +static inline u16 hpi_read_reg(struct c67x00_device *dev, int reg) +{ + return __raw_readw(dev->hpi.base + reg * dev->hpi.regstep); +} + +static inline void hpi_write_reg(struct c67x00_device *dev, int reg, u16 value) +{ + __raw_writew(value, dev->hpi.base + reg * dev->hpi.regstep); +} + +static inline u16 hpi_read_word_nolock(struct c67x00_device *dev, u16 reg) +{ + hpi_write_reg(dev, HPI_ADDR, reg); + return hpi_read_reg(dev, HPI_DATA); +} + +static u16 hpi_read_word(struct c67x00_device *dev, u16 reg) +{ + u16 value; + unsigned long flags; + + spin_lock_irqsave(&dev->hpi.lock, flags); + value = hpi_read_word_nolock(dev, reg); + spin_unlock_irqrestore(&dev->hpi.lock, flags); + + return value; +} + +static void hpi_write_word_nolock(struct c67x00_device *dev, u16 reg, u16 value) +{ + hpi_write_reg(dev, HPI_ADDR, reg); + hpi_write_reg(dev, HPI_DATA, value); +} + +static void hpi_write_word(struct c67x00_device *dev, u16 reg, u16 value) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->hpi.lock, flags); + hpi_write_word_nolock(dev, reg, value); + spin_unlock_irqrestore(&dev->hpi.lock, flags); +} + +/* + * Only data is little endian, addr has cpu endianess + */ +static void hpi_write_words_le16(struct c67x00_device *dev, u16 addr, + __le16 *data, u16 count) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&dev->hpi.lock, flags); + + hpi_write_reg(dev, HPI_ADDR, addr); + for (i = 0; i < count; i++) + hpi_write_reg(dev, HPI_DATA, le16_to_cpu(*data++)); + + spin_unlock_irqrestore(&dev->hpi.lock, flags); +} + +/* + * Only data is little endian, addr has cpu endianess + */ +static void hpi_read_words_le16(struct c67x00_device *dev, u16 addr, + __le16 *data, u16 count) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&dev->hpi.lock, flags); + hpi_write_reg(dev, HPI_ADDR, addr); + for (i = 0; i < count; i++) + *data++ = cpu_to_le16(hpi_read_reg(dev, HPI_DATA)); + + spin_unlock_irqrestore(&dev->hpi.lock, flags); +} + +static void hpi_set_bits(struct c67x00_device *dev, u16 reg, u16 mask) +{ + u16 value; + unsigned long flags; + + spin_lock_irqsave(&dev->hpi.lock, flags); + value = hpi_read_word_nolock(dev, reg); + hpi_write_word_nolock(dev, reg, value | mask); + spin_unlock_irqrestore(&dev->hpi.lock, flags); +} + +static void hpi_clear_bits(struct c67x00_device *dev, u16 reg, u16 mask) +{ + u16 value; + unsigned long flags; + + spin_lock_irqsave(&dev->hpi.lock, flags); + value = hpi_read_word_nolock(dev, reg); + hpi_write_word_nolock(dev, reg, value & ~mask); + spin_unlock_irqrestore(&dev->hpi.lock, flags); +} + +static u16 hpi_recv_mbox(struct c67x00_device *dev) +{ + u16 value; + unsigned long flags; + + spin_lock_irqsave(&dev->hpi.lock, flags); + value = hpi_read_reg(dev, HPI_MAILBOX); + spin_unlock_irqrestore(&dev->hpi.lock, flags); + + return value; +} + +static u16 hpi_send_mbox(struct c67x00_device *dev, u16 value) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->hpi.lock, flags); + hpi_write_reg(dev, HPI_MAILBOX, value); + spin_unlock_irqrestore(&dev->hpi.lock, flags); + + return value; +} + +u16 c67x00_ll_hpi_status(struct c67x00_device *dev) +{ + u16 value; + unsigned long flags; + + spin_lock_irqsave(&dev->hpi.lock, flags); + value = hpi_read_reg(dev, HPI_STATUS); + spin_unlock_irqrestore(&dev->hpi.lock, flags); + + return value; +} + +void c67x00_ll_hpi_reg_init(struct c67x00_device *dev) +{ + int i; + + hpi_recv_mbox(dev); + c67x00_ll_hpi_status(dev); + hpi_write_word(dev, HPI_IRQ_ROUTING_REG, 0); + + for (i = 0; i < C67X00_SIES; i++) { + hpi_write_word(dev, SIEMSG_REG(i), 0); + hpi_read_word(dev, SIEMSG_REG(i)); + } +} + +void c67x00_ll_hpi_enable_sofeop(struct c67x00_sie *sie) +{ + hpi_set_bits(sie->dev, HPI_IRQ_ROUTING_REG, + SOFEOP_TO_HPI_EN(sie->sie_num)); +} + +void c67x00_ll_hpi_disable_sofeop(struct c67x00_sie *sie) +{ + hpi_clear_bits(sie->dev, HPI_IRQ_ROUTING_REG, + SOFEOP_TO_HPI_EN(sie->sie_num)); +} + +/* -------------------------------------------------------------------------- */ +/* Transactions */ + +static inline int ll_recv_msg(struct c67x00_device *dev) +{ + u16 res; + + res = wait_for_completion_timeout(&dev->hpi.lcp.msg_received, 5 * HZ); + WARN_ON(!res); + + return (res == 0) ? -EIO : 0; +} + +/* -------------------------------------------------------------------------- */ +/* General functions */ + +u16 c67x00_ll_fetch_siemsg(struct c67x00_device *dev, int sie_num) +{ + u16 val; + + val = hpi_read_word(dev, SIEMSG_REG(sie_num)); + /* clear register to allow next message */ + hpi_write_word(dev, SIEMSG_REG(sie_num), 0); + + return val; +} + +u16 c67x00_ll_get_usb_ctl(struct c67x00_sie *sie) +{ + return hpi_read_word(sie->dev, USB_CTL_REG(sie->sie_num)); +} + +/** + * c67x00_ll_usb_clear_status - clear the USB status bits + */ +void c67x00_ll_usb_clear_status(struct c67x00_sie *sie, u16 bits) +{ + hpi_write_word(sie->dev, USB_STAT_REG(sie->sie_num), bits); +} + +u16 c67x00_ll_usb_get_status(struct c67x00_sie *sie) +{ + return hpi_read_word(sie->dev, USB_STAT_REG(sie->sie_num)); +} + +/* -------------------------------------------------------------------------- */ + +static int c67x00_comm_exec_int(struct c67x00_device *dev, u16 nr, + struct c67x00_lcp_int_data *data) +{ + int i, rc; + + mutex_lock(&dev->hpi.lcp.mutex); + hpi_write_word(dev, COMM_INT_NUM, nr); + for (i = 0; i < COMM_REGS; i++) + hpi_write_word(dev, COMM_R(i), data->regs[i]); + hpi_send_mbox(dev, COMM_EXEC_INT); + rc = ll_recv_msg(dev); + mutex_unlock(&dev->hpi.lcp.mutex); + + return rc; +} + +/* -------------------------------------------------------------------------- */ +/* Host specific functions */ + +void c67x00_ll_set_husb_eot(struct c67x00_device *dev, u16 value) +{ + mutex_lock(&dev->hpi.lcp.mutex); + hpi_write_word(dev, HUSB_pEOT, value); + mutex_unlock(&dev->hpi.lcp.mutex); +} + +static inline void c67x00_ll_husb_sie_init(struct c67x00_sie *sie) +{ + struct c67x00_device *dev = sie->dev; + struct c67x00_lcp_int_data data; + int rc; + + rc = c67x00_comm_exec_int(dev, HUSB_SIE_INIT_INT(sie->sie_num), &data); + BUG_ON(rc); /* No return path for error code; crash spectacularly */ +} + +void c67x00_ll_husb_reset(struct c67x00_sie *sie, int port) +{ + struct c67x00_device *dev = sie->dev; + struct c67x00_lcp_int_data data; + int rc; + + data.regs[0] = 50; /* Reset USB port for 50ms */ + data.regs[1] = port | (sie->sie_num << 1); + rc = c67x00_comm_exec_int(dev, HUSB_RESET_INT, &data); + BUG_ON(rc); /* No return path for error code; crash spectacularly */ +} + +void c67x00_ll_husb_set_current_td(struct c67x00_sie *sie, u16 addr) +{ + hpi_write_word(sie->dev, HUSB_SIE_pCurrentTDPtr(sie->sie_num), addr); +} + +u16 c67x00_ll_husb_get_current_td(struct c67x00_sie *sie) +{ + return hpi_read_word(sie->dev, HUSB_SIE_pCurrentTDPtr(sie->sie_num)); +} + +u16 c67x00_ll_husb_get_frame(struct c67x00_sie *sie) +{ + return hpi_read_word(sie->dev, HOST_FRAME_REG(sie->sie_num)); +} + +void c67x00_ll_husb_init_host_port(struct c67x00_sie *sie) +{ + /* Set port into host mode */ + hpi_set_bits(sie->dev, USB_CTL_REG(sie->sie_num), HOST_MODE); + c67x00_ll_husb_sie_init(sie); + /* Clear interrupts */ + c67x00_ll_usb_clear_status(sie, HOST_STAT_MASK); + /* Check */ + if (!(hpi_read_word(sie->dev, USB_CTL_REG(sie->sie_num)) & HOST_MODE)) + dev_warn(sie_dev(sie), + "SIE %d not set to host mode\n", sie->sie_num); +} + +void c67x00_ll_husb_reset_port(struct c67x00_sie *sie, int port) +{ + /* Clear connect change */ + c67x00_ll_usb_clear_status(sie, PORT_CONNECT_CHANGE(port)); + + /* Enable interrupts */ + hpi_set_bits(sie->dev, HPI_IRQ_ROUTING_REG, + SOFEOP_TO_CPU_EN(sie->sie_num)); + hpi_set_bits(sie->dev, HOST_IRQ_EN_REG(sie->sie_num), + SOF_EOP_IRQ_EN | DONE_IRQ_EN); + + /* Enable pull down transistors */ + hpi_set_bits(sie->dev, USB_CTL_REG(sie->sie_num), PORT_RES_EN(port)); +} + +/* -------------------------------------------------------------------------- */ + +void c67x00_ll_irq(struct c67x00_device *dev, u16 int_status) +{ + if ((int_status & MBX_OUT_FLG) == 0) + return; + + dev->hpi.lcp.last_msg = hpi_recv_mbox(dev); + complete(&dev->hpi.lcp.msg_received); +} + +/* -------------------------------------------------------------------------- */ + +int c67x00_ll_reset(struct c67x00_device *dev) +{ + int rc; + + mutex_lock(&dev->hpi.lcp.mutex); + hpi_send_mbox(dev, COMM_RESET); + rc = ll_recv_msg(dev); + mutex_unlock(&dev->hpi.lcp.mutex); + + return rc; +} + +/* -------------------------------------------------------------------------- */ + +/** + * c67x00_ll_write_mem_le16 - write into c67x00 memory + * Only data is little endian, addr has cpu endianess. + */ +void c67x00_ll_write_mem_le16(struct c67x00_device *dev, u16 addr, + void *data, int len) +{ + u8 *buf = data; + + /* Sanity check */ + if (addr + len > 0xffff) { + dev_err(&dev->pdev->dev, + "Trying to write beyond writable region!\n"); + return; + } + + if (addr & 0x01) { + /* unaligned access */ + u16 tmp; + tmp = hpi_read_word(dev, addr - 1); + tmp = (tmp & 0x00ff) | (*buf++ << 8); + hpi_write_word(dev, addr - 1, tmp); + addr++; + len--; + } + + hpi_write_words_le16(dev, addr, (__le16 *)buf, len / 2); + buf += len & ~0x01; + addr += len & ~0x01; + len &= 0x01; + + if (len) { + u16 tmp; + tmp = hpi_read_word(dev, addr); + tmp = (tmp & 0xff00) | *buf; + hpi_write_word(dev, addr, tmp); + } +} + +/** + * c67x00_ll_read_mem_le16 - read from c67x00 memory + * Only data is little endian, addr has cpu endianess. + */ +void c67x00_ll_read_mem_le16(struct c67x00_device *dev, u16 addr, + void *data, int len) +{ + u8 *buf = data; + + if (addr & 0x01) { + /* unaligned access */ + u16 tmp; + tmp = hpi_read_word(dev, addr - 1); + *buf++ = (tmp >> 8) & 0x00ff; + addr++; + len--; + } + + hpi_read_words_le16(dev, addr, (__le16 *)buf, len / 2); + buf += len & ~0x01; + addr += len & ~0x01; + len &= 0x01; + + if (len) { + u16 tmp; + tmp = hpi_read_word(dev, addr); + *buf = tmp & 0x00ff; + } +} + +/* -------------------------------------------------------------------------- */ + +void c67x00_ll_init(struct c67x00_device *dev) +{ + mutex_init(&dev->hpi.lcp.mutex); + init_completion(&dev->hpi.lcp.msg_received); +} + +void c67x00_ll_release(struct c67x00_device *dev) +{ +} diff --git a/drivers/usb/c67x00/c67x00-sched.c b/drivers/usb/c67x00/c67x00-sched.c new file mode 100644 index 00000000..aa491627 --- /dev/null +++ b/drivers/usb/c67x00/c67x00-sched.c @@ -0,0 +1,1171 @@ +/* + * c67x00-sched.c: Cypress C67X00 USB Host Controller Driver - TD scheduling + * + * Copyright (C) 2006-2008 Barco N.V. + * Derived from the Cypress cy7c67200/300 ezusb linux driver and + * based on multiple host controller drivers inside the linux kernel. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + */ + +#include +#include + +#include "c67x00.h" +#include "c67x00-hcd.h" + +/* + * These are the stages for a control urb, they are kept + * in both urb->interval and td->privdata. + */ +#define SETUP_STAGE 0 +#define DATA_STAGE 1 +#define STATUS_STAGE 2 + +/* -------------------------------------------------------------------------- */ + +/** + * struct c67x00_ep_data: Host endpoint data structure + */ +struct c67x00_ep_data { + struct list_head queue; + struct list_head node; + struct usb_host_endpoint *hep; + struct usb_device *dev; + u16 next_frame; /* For int/isoc transactions */ +}; + +/** + * struct c67x00_td + * + * Hardware parts are little endiannes, SW in CPU endianess. + */ +struct c67x00_td { + /* HW specific part */ + __le16 ly_base_addr; /* Bytes 0-1 */ + __le16 port_length; /* Bytes 2-3 */ + u8 pid_ep; /* Byte 4 */ + u8 dev_addr; /* Byte 5 */ + u8 ctrl_reg; /* Byte 6 */ + u8 status; /* Byte 7 */ + u8 retry_cnt; /* Byte 8 */ +#define TT_OFFSET 2 +#define TT_CONTROL 0 +#define TT_ISOCHRONOUS 1 +#define TT_BULK 2 +#define TT_INTERRUPT 3 + u8 residue; /* Byte 9 */ + __le16 next_td_addr; /* Bytes 10-11 */ + /* SW part */ + struct list_head td_list; + u16 td_addr; + void *data; + struct urb *urb; + unsigned long privdata; + + /* These are needed for handling the toggle bits: + * an urb can be dequeued while a td is in progress + * after checking the td, the toggle bit might need to + * be fixed */ + struct c67x00_ep_data *ep_data; + unsigned int pipe; +}; + +struct c67x00_urb_priv { + struct list_head hep_node; + struct urb *urb; + int port; + int cnt; /* packet number for isoc */ + int status; + struct c67x00_ep_data *ep_data; +}; + +#define td_udev(td) ((td)->ep_data->dev) + +#define CY_TD_SIZE 12 + +#define TD_PIDEP_OFFSET 0x04 +#define TD_PIDEPMASK_PID 0xF0 +#define TD_PIDEPMASK_EP 0x0F +#define TD_PORTLENMASK_DL 0x03FF +#define TD_PORTLENMASK_PN 0xC000 + +#define TD_STATUS_OFFSET 0x07 +#define TD_STATUSMASK_ACK 0x01 +#define TD_STATUSMASK_ERR 0x02 +#define TD_STATUSMASK_TMOUT 0x04 +#define TD_STATUSMASK_SEQ 0x08 +#define TD_STATUSMASK_SETUP 0x10 +#define TD_STATUSMASK_OVF 0x20 +#define TD_STATUSMASK_NAK 0x40 +#define TD_STATUSMASK_STALL 0x80 + +#define TD_ERROR_MASK (TD_STATUSMASK_ERR | TD_STATUSMASK_TMOUT | \ + TD_STATUSMASK_STALL) + +#define TD_RETRYCNT_OFFSET 0x08 +#define TD_RETRYCNTMASK_ACT_FLG 0x10 +#define TD_RETRYCNTMASK_TX_TYPE 0x0C +#define TD_RETRYCNTMASK_RTY_CNT 0x03 + +#define TD_RESIDUE_OVERFLOW 0x80 + +#define TD_PID_IN 0x90 + +/* Residue: signed 8bits, neg -> OVERFLOW, pos -> UNDERFLOW */ +#define td_residue(td) ((__s8)(td->residue)) +#define td_ly_base_addr(td) (__le16_to_cpu((td)->ly_base_addr)) +#define td_port_length(td) (__le16_to_cpu((td)->port_length)) +#define td_next_td_addr(td) (__le16_to_cpu((td)->next_td_addr)) + +#define td_active(td) ((td)->retry_cnt & TD_RETRYCNTMASK_ACT_FLG) +#define td_length(td) (td_port_length(td) & TD_PORTLENMASK_DL) + +#define td_sequence_ok(td) (!td->status || \ + (!(td->status & TD_STATUSMASK_SEQ) == \ + !(td->ctrl_reg & SEQ_SEL))) + +#define td_acked(td) (!td->status || \ + (td->status & TD_STATUSMASK_ACK)) +#define td_actual_bytes(td) (td_length(td) - td_residue(td)) + +/* -------------------------------------------------------------------------- */ + +#ifdef DEBUG + +/** + * dbg_td - Dump the contents of the TD + */ +static void dbg_td(struct c67x00_hcd *c67x00, struct c67x00_td *td, char *msg) +{ + struct device *dev = c67x00_hcd_dev(c67x00); + + dev_dbg(dev, "### %s at 0x%04x\n", msg, td->td_addr); + dev_dbg(dev, "urb: 0x%p\n", td->urb); + dev_dbg(dev, "endpoint: %4d\n", usb_pipeendpoint(td->pipe)); + dev_dbg(dev, "pipeout: %4d\n", usb_pipeout(td->pipe)); + dev_dbg(dev, "ly_base_addr: 0x%04x\n", td_ly_base_addr(td)); + dev_dbg(dev, "port_length: 0x%04x\n", td_port_length(td)); + dev_dbg(dev, "pid_ep: 0x%02x\n", td->pid_ep); + dev_dbg(dev, "dev_addr: 0x%02x\n", td->dev_addr); + dev_dbg(dev, "ctrl_reg: 0x%02x\n", td->ctrl_reg); + dev_dbg(dev, "status: 0x%02x\n", td->status); + dev_dbg(dev, "retry_cnt: 0x%02x\n", td->retry_cnt); + dev_dbg(dev, "residue: 0x%02x\n", td->residue); + dev_dbg(dev, "next_td_addr: 0x%04x\n", td_next_td_addr(td)); + dev_dbg(dev, "data:"); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 16, 1, + td->data, td_length(td), 1); +} +#else /* DEBUG */ + +static inline void +dbg_td(struct c67x00_hcd *c67x00, struct c67x00_td *td, char *msg) { } + +#endif /* DEBUG */ + +/* -------------------------------------------------------------------------- */ +/* Helper functions */ + +static inline u16 c67x00_get_current_frame_number(struct c67x00_hcd *c67x00) +{ + return c67x00_ll_husb_get_frame(c67x00->sie) & HOST_FRAME_MASK; +} + +/** + * frame_add + * Software wraparound for framenumbers. + */ +static inline u16 frame_add(u16 a, u16 b) +{ + return (a + b) & HOST_FRAME_MASK; +} + +/** + * frame_after - is frame a after frame b + */ +static inline int frame_after(u16 a, u16 b) +{ + return ((HOST_FRAME_MASK + a - b) & HOST_FRAME_MASK) < + (HOST_FRAME_MASK / 2); +} + +/** + * frame_after_eq - is frame a after or equal to frame b + */ +static inline int frame_after_eq(u16 a, u16 b) +{ + return ((HOST_FRAME_MASK + 1 + a - b) & HOST_FRAME_MASK) < + (HOST_FRAME_MASK / 2); +} + +/* -------------------------------------------------------------------------- */ + +/** + * c67x00_release_urb - remove link from all tds to this urb + * Disconnects the urb from it's tds, so that it can be given back. + * pre: urb->hcpriv != NULL + */ +static void c67x00_release_urb(struct c67x00_hcd *c67x00, struct urb *urb) +{ + struct c67x00_td *td; + struct c67x00_urb_priv *urbp; + + BUG_ON(!urb); + + c67x00->urb_count--; + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + c67x00->urb_iso_count--; + if (c67x00->urb_iso_count == 0) + c67x00->max_frame_bw = MAX_FRAME_BW_STD; + } + + /* TODO this might be not so efficient when we've got many urbs! + * Alternatives: + * * only clear when needed + * * keep a list of tds with each urbp + */ + list_for_each_entry(td, &c67x00->td_list, td_list) + if (urb == td->urb) + td->urb = NULL; + + urbp = urb->hcpriv; + urb->hcpriv = NULL; + list_del(&urbp->hep_node); + kfree(urbp); +} + +/* -------------------------------------------------------------------------- */ + +static struct c67x00_ep_data * +c67x00_ep_data_alloc(struct c67x00_hcd *c67x00, struct urb *urb) +{ + struct usb_host_endpoint *hep = urb->ep; + struct c67x00_ep_data *ep_data; + int type; + + c67x00->current_frame = c67x00_get_current_frame_number(c67x00); + + /* Check if endpoint already has a c67x00_ep_data struct allocated */ + if (hep->hcpriv) { + ep_data = hep->hcpriv; + if (frame_after(c67x00->current_frame, ep_data->next_frame)) + ep_data->next_frame = + frame_add(c67x00->current_frame, 1); + return hep->hcpriv; + } + + /* Allocate and initialize a new c67x00 endpoint data structure */ + ep_data = kzalloc(sizeof(*ep_data), GFP_ATOMIC); + if (!ep_data) + return NULL; + + INIT_LIST_HEAD(&ep_data->queue); + INIT_LIST_HEAD(&ep_data->node); + ep_data->hep = hep; + + /* hold a reference to udev as long as this endpoint lives, + * this is needed to possibly fix the data toggle */ + ep_data->dev = usb_get_dev(urb->dev); + hep->hcpriv = ep_data; + + /* For ISOC and INT endpoints, start ASAP: */ + ep_data->next_frame = frame_add(c67x00->current_frame, 1); + + /* Add the endpoint data to one of the pipe lists; must be added + in order of endpoint address */ + type = usb_pipetype(urb->pipe); + if (list_empty(&ep_data->node)) { + list_add(&ep_data->node, &c67x00->list[type]); + } else { + struct c67x00_ep_data *prev; + + list_for_each_entry(prev, &c67x00->list[type], node) { + if (prev->hep->desc.bEndpointAddress > + hep->desc.bEndpointAddress) { + list_add(&ep_data->node, prev->node.prev); + break; + } + } + } + + return ep_data; +} + +static int c67x00_ep_data_free(struct usb_host_endpoint *hep) +{ + struct c67x00_ep_data *ep_data = hep->hcpriv; + + if (!ep_data) + return 0; + + if (!list_empty(&ep_data->queue)) + return -EBUSY; + + usb_put_dev(ep_data->dev); + list_del(&ep_data->queue); + list_del(&ep_data->node); + + kfree(ep_data); + hep->hcpriv = NULL; + + return 0; +} + +void c67x00_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); + unsigned long flags; + + if (!list_empty(&ep->urb_list)) + dev_warn(c67x00_hcd_dev(c67x00), "error: urb list not empty\n"); + + spin_lock_irqsave(&c67x00->lock, flags); + + /* loop waiting for all transfers in the endpoint queue to complete */ + while (c67x00_ep_data_free(ep)) { + /* Drop the lock so we can sleep waiting for the hardware */ + spin_unlock_irqrestore(&c67x00->lock, flags); + + /* it could happen that we reinitialize this completion, while + * somebody was waiting for that completion. The timeout and + * while loop handle such cases, but this might be improved */ + INIT_COMPLETION(c67x00->endpoint_disable); + c67x00_sched_kick(c67x00); + wait_for_completion_timeout(&c67x00->endpoint_disable, 1 * HZ); + + spin_lock_irqsave(&c67x00->lock, flags); + } + + spin_unlock_irqrestore(&c67x00->lock, flags); +} + +/* -------------------------------------------------------------------------- */ + +static inline int get_root_port(struct usb_device *dev) +{ + while (dev->parent->parent) + dev = dev->parent; + return dev->portnum; +} + +int c67x00_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, gfp_t mem_flags) +{ + int ret; + unsigned long flags; + struct c67x00_urb_priv *urbp; + struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); + int port = get_root_port(urb->dev)-1; + + spin_lock_irqsave(&c67x00->lock, flags); + + /* Make sure host controller is running */ + if (!HC_IS_RUNNING(hcd->state)) { + ret = -ENODEV; + goto err_not_linked; + } + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto err_not_linked; + + /* Allocate and initialize urb private data */ + urbp = kzalloc(sizeof(*urbp), mem_flags); + if (!urbp) { + ret = -ENOMEM; + goto err_urbp; + } + + INIT_LIST_HEAD(&urbp->hep_node); + urbp->urb = urb; + urbp->port = port; + + urbp->ep_data = c67x00_ep_data_alloc(c67x00, urb); + + if (!urbp->ep_data) { + ret = -ENOMEM; + goto err_epdata; + } + + /* TODO claim bandwidth with usb_claim_bandwidth? + * also release it somewhere! */ + + urb->hcpriv = urbp; + + urb->actual_length = 0; /* Nothing received/transmitted yet */ + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + urb->interval = SETUP_STAGE; + break; + case PIPE_INTERRUPT: + break; + case PIPE_BULK: + break; + case PIPE_ISOCHRONOUS: + if (c67x00->urb_iso_count == 0) + c67x00->max_frame_bw = MAX_FRAME_BW_ISO; + c67x00->urb_iso_count++; + /* Assume always URB_ISO_ASAP, FIXME */ + if (list_empty(&urbp->ep_data->queue)) + urb->start_frame = urbp->ep_data->next_frame; + else { + /* Go right after the last one */ + struct urb *last_urb; + + last_urb = list_entry(urbp->ep_data->queue.prev, + struct c67x00_urb_priv, + hep_node)->urb; + urb->start_frame = + frame_add(last_urb->start_frame, + last_urb->number_of_packets * + last_urb->interval); + } + urbp->cnt = 0; + break; + } + + /* Add the URB to the endpoint queue */ + list_add_tail(&urbp->hep_node, &urbp->ep_data->queue); + + /* If this is the only URB, kick start the controller */ + if (!c67x00->urb_count++) + c67x00_ll_hpi_enable_sofeop(c67x00->sie); + + c67x00_sched_kick(c67x00); + spin_unlock_irqrestore(&c67x00->lock, flags); + + return 0; + +err_epdata: + kfree(urbp); +err_urbp: + usb_hcd_unlink_urb_from_ep(hcd, urb); +err_not_linked: + spin_unlock_irqrestore(&c67x00->lock, flags); + + return ret; +} + +int c67x00_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd); + unsigned long flags; + int rc; + + spin_lock_irqsave(&c67x00->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + c67x00_release_urb(c67x00, urb); + usb_hcd_unlink_urb_from_ep(hcd, urb); + + spin_unlock(&c67x00->lock); + usb_hcd_giveback_urb(hcd, urb, status); + spin_lock(&c67x00->lock); + + spin_unlock_irqrestore(&c67x00->lock, flags); + + return 0; + + done: + spin_unlock_irqrestore(&c67x00->lock, flags); + return rc; +} + +/* -------------------------------------------------------------------------- */ + +/* + * pre: c67x00 locked, urb unlocked + */ +static void +c67x00_giveback_urb(struct c67x00_hcd *c67x00, struct urb *urb, int status) +{ + struct c67x00_urb_priv *urbp; + + if (!urb) + return; + + urbp = urb->hcpriv; + urbp->status = status; + + list_del_init(&urbp->hep_node); + + c67x00_release_urb(c67x00, urb); + usb_hcd_unlink_urb_from_ep(c67x00_hcd_to_hcd(c67x00), urb); + spin_unlock(&c67x00->lock); + usb_hcd_giveback_urb(c67x00_hcd_to_hcd(c67x00), urb, urbp->status); + spin_lock(&c67x00->lock); +} + +/* -------------------------------------------------------------------------- */ + +static int c67x00_claim_frame_bw(struct c67x00_hcd *c67x00, struct urb *urb, + int len, int periodic) +{ + struct c67x00_urb_priv *urbp = urb->hcpriv; + int bit_time; + + /* According to the C67x00 BIOS user manual, page 3-18,19, the + * following calculations provide the full speed bit times for + * a transaction. + * + * FS(in) = 112.5 + 9.36*BC + HOST_DELAY + * FS(in,iso) = 90.5 + 9.36*BC + HOST_DELAY + * FS(out) = 112.5 + 9.36*BC + HOST_DELAY + * FS(out,iso) = 78.4 + 9.36*BC + HOST_DELAY + * LS(in) = 802.4 + 75.78*BC + HOST_DELAY + * LS(out) = 802.6 + 74.67*BC + HOST_DELAY + * + * HOST_DELAY == 106 for the c67200 and c67300. + */ + + /* make calculations in 1/100 bit times to maintain resolution */ + if (urbp->ep_data->dev->speed == USB_SPEED_LOW) { + /* Low speed pipe */ + if (usb_pipein(urb->pipe)) + bit_time = 80240 + 7578*len; + else + bit_time = 80260 + 7467*len; + } else { + /* FS pipes */ + if (usb_pipeisoc(urb->pipe)) + bit_time = usb_pipein(urb->pipe) ? 9050 : 7840; + else + bit_time = 11250; + bit_time += 936*len; + } + + /* Scale back down to integer bit times. Use a host delay of 106. + * (this is the only place it is used) */ + bit_time = ((bit_time+50) / 100) + 106; + + if (unlikely(bit_time + c67x00->bandwidth_allocated >= + c67x00->max_frame_bw)) + return -EMSGSIZE; + + if (unlikely(c67x00->next_td_addr + CY_TD_SIZE >= + c67x00->td_base_addr + SIE_TD_SIZE)) + return -EMSGSIZE; + + if (unlikely(c67x00->next_buf_addr + len >= + c67x00->buf_base_addr + SIE_TD_BUF_SIZE)) + return -EMSGSIZE; + + if (periodic) { + if (unlikely(bit_time + c67x00->periodic_bw_allocated >= + MAX_PERIODIC_BW(c67x00->max_frame_bw))) + return -EMSGSIZE; + c67x00->periodic_bw_allocated += bit_time; + } + + c67x00->bandwidth_allocated += bit_time; + return 0; +} + +/* -------------------------------------------------------------------------- */ + +/** + * td_addr and buf_addr must be word aligned + */ +static int c67x00_create_td(struct c67x00_hcd *c67x00, struct urb *urb, + void *data, int len, int pid, int toggle, + unsigned long privdata) +{ + struct c67x00_td *td; + struct c67x00_urb_priv *urbp = urb->hcpriv; + const __u8 active_flag = 1, retry_cnt = 3; + __u8 cmd = 0; + int tt = 0; + + if (c67x00_claim_frame_bw(c67x00, urb, len, usb_pipeisoc(urb->pipe) + || usb_pipeint(urb->pipe))) + return -EMSGSIZE; /* Not really an error, but expected */ + + td = kzalloc(sizeof(*td), GFP_ATOMIC); + if (!td) + return -ENOMEM; + + td->pipe = urb->pipe; + td->ep_data = urbp->ep_data; + + if ((td_udev(td)->speed == USB_SPEED_LOW) && + !(c67x00->low_speed_ports & (1 << urbp->port))) + cmd |= PREAMBLE_EN; + + switch (usb_pipetype(td->pipe)) { + case PIPE_ISOCHRONOUS: + tt = TT_ISOCHRONOUS; + cmd |= ISO_EN; + break; + case PIPE_CONTROL: + tt = TT_CONTROL; + break; + case PIPE_BULK: + tt = TT_BULK; + break; + case PIPE_INTERRUPT: + tt = TT_INTERRUPT; + break; + } + + if (toggle) + cmd |= SEQ_SEL; + + cmd |= ARM_EN; + + /* SW part */ + td->td_addr = c67x00->next_td_addr; + c67x00->next_td_addr = c67x00->next_td_addr + CY_TD_SIZE; + + /* HW part */ + td->ly_base_addr = __cpu_to_le16(c67x00->next_buf_addr); + td->port_length = __cpu_to_le16((c67x00->sie->sie_num << 15) | + (urbp->port << 14) | (len & 0x3FF)); + td->pid_ep = ((pid & 0xF) << TD_PIDEP_OFFSET) | + (usb_pipeendpoint(td->pipe) & 0xF); + td->dev_addr = usb_pipedevice(td->pipe) & 0x7F; + td->ctrl_reg = cmd; + td->status = 0; + td->retry_cnt = (tt << TT_OFFSET) | (active_flag << 4) | retry_cnt; + td->residue = 0; + td->next_td_addr = __cpu_to_le16(c67x00->next_td_addr); + + /* SW part */ + td->data = data; + td->urb = urb; + td->privdata = privdata; + + c67x00->next_buf_addr += (len + 1) & ~0x01; /* properly align */ + + list_add_tail(&td->td_list, &c67x00->td_list); + return 0; +} + +static inline void c67x00_release_td(struct c67x00_td *td) +{ + list_del_init(&td->td_list); + kfree(td); +} + +/* -------------------------------------------------------------------------- */ + +static int c67x00_add_data_urb(struct c67x00_hcd *c67x00, struct urb *urb) +{ + int remaining; + int toggle; + int pid; + int ret = 0; + int maxps; + int need_empty; + + toggle = usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe)); + remaining = urb->transfer_buffer_length - urb->actual_length; + + maxps = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + + need_empty = (urb->transfer_flags & URB_ZERO_PACKET) && + usb_pipeout(urb->pipe) && !(remaining % maxps); + + while (remaining || need_empty) { + int len; + char *td_buf; + + len = (remaining > maxps) ? maxps : remaining; + if (!len) + need_empty = 0; + + pid = usb_pipeout(urb->pipe) ? USB_PID_OUT : USB_PID_IN; + td_buf = urb->transfer_buffer + urb->transfer_buffer_length - + remaining; + ret = c67x00_create_td(c67x00, urb, td_buf, len, pid, toggle, + DATA_STAGE); + if (ret) + return ret; /* td wasn't created */ + + toggle ^= 1; + remaining -= len; + if (usb_pipecontrol(urb->pipe)) + break; + } + + return 0; +} + +/** + * return 0 in case more bandwidth is available, else errorcode + */ +static int c67x00_add_ctrl_urb(struct c67x00_hcd *c67x00, struct urb *urb) +{ + int ret; + int pid; + + switch (urb->interval) { + default: + case SETUP_STAGE: + ret = c67x00_create_td(c67x00, urb, urb->setup_packet, + 8, USB_PID_SETUP, 0, SETUP_STAGE); + if (ret) + return ret; + urb->interval = SETUP_STAGE; + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), 1); + break; + case DATA_STAGE: + if (urb->transfer_buffer_length) { + ret = c67x00_add_data_urb(c67x00, urb); + if (ret) + return ret; + break; + } /* else fallthrough */ + case STATUS_STAGE: + pid = !usb_pipeout(urb->pipe) ? USB_PID_OUT : USB_PID_IN; + ret = c67x00_create_td(c67x00, urb, NULL, 0, pid, 1, + STATUS_STAGE); + if (ret) + return ret; + break; + } + + return 0; +} + +/* + * return 0 in case more bandwidth is available, else errorcode + */ +static int c67x00_add_int_urb(struct c67x00_hcd *c67x00, struct urb *urb) +{ + struct c67x00_urb_priv *urbp = urb->hcpriv; + + if (frame_after_eq(c67x00->current_frame, urbp->ep_data->next_frame)) { + urbp->ep_data->next_frame = + frame_add(urbp->ep_data->next_frame, urb->interval); + return c67x00_add_data_urb(c67x00, urb); + } + return 0; +} + +static int c67x00_add_iso_urb(struct c67x00_hcd *c67x00, struct urb *urb) +{ + struct c67x00_urb_priv *urbp = urb->hcpriv; + + if (frame_after_eq(c67x00->current_frame, urbp->ep_data->next_frame)) { + char *td_buf; + int len, pid, ret; + + BUG_ON(urbp->cnt >= urb->number_of_packets); + + td_buf = urb->transfer_buffer + + urb->iso_frame_desc[urbp->cnt].offset; + len = urb->iso_frame_desc[urbp->cnt].length; + pid = usb_pipeout(urb->pipe) ? USB_PID_OUT : USB_PID_IN; + + ret = c67x00_create_td(c67x00, urb, td_buf, len, pid, 0, + urbp->cnt); + if (ret) { + printk(KERN_DEBUG "create failed: %d\n", ret); + urb->iso_frame_desc[urbp->cnt].actual_length = 0; + urb->iso_frame_desc[urbp->cnt].status = ret; + if (urbp->cnt + 1 == urb->number_of_packets) + c67x00_giveback_urb(c67x00, urb, 0); + } + + urbp->ep_data->next_frame = + frame_add(urbp->ep_data->next_frame, urb->interval); + urbp->cnt++; + } + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static void c67x00_fill_from_list(struct c67x00_hcd *c67x00, int type, + int (*add)(struct c67x00_hcd *, struct urb *)) +{ + struct c67x00_ep_data *ep_data; + struct urb *urb; + + /* traverse every endpoint on the list */ + list_for_each_entry(ep_data, &c67x00->list[type], node) { + if (!list_empty(&ep_data->queue)) { + /* and add the first urb */ + /* isochronous transfer rely on this */ + urb = list_entry(ep_data->queue.next, + struct c67x00_urb_priv, + hep_node)->urb; + add(c67x00, urb); + } + } +} + +static void c67x00_fill_frame(struct c67x00_hcd *c67x00) +{ + struct c67x00_td *td, *ttd; + + /* Check if we can proceed */ + if (!list_empty(&c67x00->td_list)) { + dev_warn(c67x00_hcd_dev(c67x00), + "TD list not empty! This should not happen!\n"); + list_for_each_entry_safe(td, ttd, &c67x00->td_list, td_list) { + dbg_td(c67x00, td, "Unprocessed td"); + c67x00_release_td(td); + } + } + + /* Reinitialize variables */ + c67x00->bandwidth_allocated = 0; + c67x00->periodic_bw_allocated = 0; + + c67x00->next_td_addr = c67x00->td_base_addr; + c67x00->next_buf_addr = c67x00->buf_base_addr; + + /* Fill the list */ + c67x00_fill_from_list(c67x00, PIPE_ISOCHRONOUS, c67x00_add_iso_urb); + c67x00_fill_from_list(c67x00, PIPE_INTERRUPT, c67x00_add_int_urb); + c67x00_fill_from_list(c67x00, PIPE_CONTROL, c67x00_add_ctrl_urb); + c67x00_fill_from_list(c67x00, PIPE_BULK, c67x00_add_data_urb); +} + +/* -------------------------------------------------------------------------- */ + +/** + * Get TD from C67X00 + */ +static inline void +c67x00_parse_td(struct c67x00_hcd *c67x00, struct c67x00_td *td) +{ + c67x00_ll_read_mem_le16(c67x00->sie->dev, + td->td_addr, td, CY_TD_SIZE); + + if (usb_pipein(td->pipe) && td_actual_bytes(td)) + c67x00_ll_read_mem_le16(c67x00->sie->dev, td_ly_base_addr(td), + td->data, td_actual_bytes(td)); +} + +static int c67x00_td_to_error(struct c67x00_hcd *c67x00, struct c67x00_td *td) +{ + if (td->status & TD_STATUSMASK_ERR) { + dbg_td(c67x00, td, "ERROR_FLAG"); + return -EILSEQ; + } + if (td->status & TD_STATUSMASK_STALL) { + /* dbg_td(c67x00, td, "STALL"); */ + return -EPIPE; + } + if (td->status & TD_STATUSMASK_TMOUT) { + dbg_td(c67x00, td, "TIMEOUT"); + return -ETIMEDOUT; + } + + return 0; +} + +static inline int c67x00_end_of_data(struct c67x00_td *td) +{ + int maxps, need_empty, remaining; + struct urb *urb = td->urb; + int act_bytes; + + act_bytes = td_actual_bytes(td); + + if (unlikely(!act_bytes)) + return 1; /* This was an empty packet */ + + maxps = usb_maxpacket(td_udev(td), td->pipe, usb_pipeout(td->pipe)); + + if (unlikely(act_bytes < maxps)) + return 1; /* Smaller then full packet */ + + remaining = urb->transfer_buffer_length - urb->actual_length; + need_empty = (urb->transfer_flags & URB_ZERO_PACKET) && + usb_pipeout(urb->pipe) && !(remaining % maxps); + + if (unlikely(!remaining && !need_empty)) + return 1; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +/* Remove all td's from the list which come + * after last_td and are meant for the same pipe. + * This is used when a short packet has occurred */ +static inline void c67x00_clear_pipe(struct c67x00_hcd *c67x00, + struct c67x00_td *last_td) +{ + struct c67x00_td *td, *tmp; + td = last_td; + tmp = last_td; + while (td->td_list.next != &c67x00->td_list) { + td = list_entry(td->td_list.next, struct c67x00_td, td_list); + if (td->pipe == last_td->pipe) { + c67x00_release_td(td); + td = tmp; + } + tmp = td; + } +} + +/* -------------------------------------------------------------------------- */ + +static void c67x00_handle_successful_td(struct c67x00_hcd *c67x00, + struct c67x00_td *td) +{ + struct urb *urb = td->urb; + + if (!urb) + return; + + urb->actual_length += td_actual_bytes(td); + + switch (usb_pipetype(td->pipe)) { + /* isochronous tds are handled separately */ + case PIPE_CONTROL: + switch (td->privdata) { + case SETUP_STAGE: + urb->interval = + urb->transfer_buffer_length ? + DATA_STAGE : STATUS_STAGE; + /* Don't count setup_packet with normal data: */ + urb->actual_length = 0; + break; + + case DATA_STAGE: + if (c67x00_end_of_data(td)) { + urb->interval = STATUS_STAGE; + c67x00_clear_pipe(c67x00, td); + } + break; + + case STATUS_STAGE: + urb->interval = 0; + c67x00_giveback_urb(c67x00, urb, 0); + break; + } + break; + + case PIPE_INTERRUPT: + case PIPE_BULK: + if (unlikely(c67x00_end_of_data(td))) { + c67x00_clear_pipe(c67x00, td); + c67x00_giveback_urb(c67x00, urb, 0); + } + break; + } +} + +static void c67x00_handle_isoc(struct c67x00_hcd *c67x00, struct c67x00_td *td) +{ + struct urb *urb = td->urb; + struct c67x00_urb_priv *urbp; + int cnt; + + if (!urb) + return; + + urbp = urb->hcpriv; + cnt = td->privdata; + + if (td->status & TD_ERROR_MASK) + urb->error_count++; + + urb->iso_frame_desc[cnt].actual_length = td_actual_bytes(td); + urb->iso_frame_desc[cnt].status = c67x00_td_to_error(c67x00, td); + if (cnt + 1 == urb->number_of_packets) /* Last packet */ + c67x00_giveback_urb(c67x00, urb, 0); +} + +/* -------------------------------------------------------------------------- */ + +/** + * c67x00_check_td_list - handle tds which have been processed by the c67x00 + * pre: current_td == 0 + */ +static inline void c67x00_check_td_list(struct c67x00_hcd *c67x00) +{ + struct c67x00_td *td, *tmp; + struct urb *urb; + int ack_ok; + int clear_endpoint; + + list_for_each_entry_safe(td, tmp, &c67x00->td_list, td_list) { + /* get the TD */ + c67x00_parse_td(c67x00, td); + urb = td->urb; /* urb can be NULL! */ + ack_ok = 0; + clear_endpoint = 1; + + /* Handle isochronous transfers separately */ + if (usb_pipeisoc(td->pipe)) { + clear_endpoint = 0; + c67x00_handle_isoc(c67x00, td); + goto cont; + } + + /* When an error occurs, all td's for that pipe go into an + * inactive state. This state matches successful transfers so + * we must make sure not to service them. */ + if (td->status & TD_ERROR_MASK) { + c67x00_giveback_urb(c67x00, urb, + c67x00_td_to_error(c67x00, td)); + goto cont; + } + + if ((td->status & TD_STATUSMASK_NAK) || !td_sequence_ok(td) || + !td_acked(td)) + goto cont; + + /* Sequence ok and acked, don't need to fix toggle */ + ack_ok = 1; + + if (unlikely(td->status & TD_STATUSMASK_OVF)) { + if (td_residue(td) & TD_RESIDUE_OVERFLOW) { + /* Overflow */ + c67x00_giveback_urb(c67x00, urb, -EOVERFLOW); + goto cont; + } + } + + clear_endpoint = 0; + c67x00_handle_successful_td(c67x00, td); + +cont: + if (clear_endpoint) + c67x00_clear_pipe(c67x00, td); + if (ack_ok) + usb_settoggle(td_udev(td), usb_pipeendpoint(td->pipe), + usb_pipeout(td->pipe), + !(td->ctrl_reg & SEQ_SEL)); + /* next in list could have been removed, due to clear_pipe! */ + tmp = list_entry(td->td_list.next, typeof(*td), td_list); + c67x00_release_td(td); + } +} + +/* -------------------------------------------------------------------------- */ + +static inline int c67x00_all_tds_processed(struct c67x00_hcd *c67x00) +{ + /* If all tds are processed, we can check the previous frame (if + * there was any) and start our next frame. + */ + return !c67x00_ll_husb_get_current_td(c67x00->sie); +} + +/** + * Send td to C67X00 + */ +static void c67x00_send_td(struct c67x00_hcd *c67x00, struct c67x00_td *td) +{ + int len = td_length(td); + + if (len && ((td->pid_ep & TD_PIDEPMASK_PID) != TD_PID_IN)) + c67x00_ll_write_mem_le16(c67x00->sie->dev, td_ly_base_addr(td), + td->data, len); + + c67x00_ll_write_mem_le16(c67x00->sie->dev, + td->td_addr, td, CY_TD_SIZE); +} + +static void c67x00_send_frame(struct c67x00_hcd *c67x00) +{ + struct c67x00_td *td; + + if (list_empty(&c67x00->td_list)) + dev_warn(c67x00_hcd_dev(c67x00), + "%s: td list should not be empty here!\n", + __func__); + + list_for_each_entry(td, &c67x00->td_list, td_list) { + if (td->td_list.next == &c67x00->td_list) + td->next_td_addr = 0; /* Last td in list */ + + c67x00_send_td(c67x00, td); + } + + c67x00_ll_husb_set_current_td(c67x00->sie, c67x00->td_base_addr); +} + +/* -------------------------------------------------------------------------- */ + +/** + * c67x00_do_work - Schedulers state machine + */ +static void c67x00_do_work(struct c67x00_hcd *c67x00) +{ + spin_lock(&c67x00->lock); + /* Make sure all tds are processed */ + if (!c67x00_all_tds_processed(c67x00)) + goto out; + + c67x00_check_td_list(c67x00); + + /* no td's are being processed (current == 0) + * and all have been "checked" */ + complete(&c67x00->endpoint_disable); + + if (!list_empty(&c67x00->td_list)) + goto out; + + c67x00->current_frame = c67x00_get_current_frame_number(c67x00); + if (c67x00->current_frame == c67x00->last_frame) + goto out; /* Don't send tds in same frame */ + c67x00->last_frame = c67x00->current_frame; + + /* If no urbs are scheduled, our work is done */ + if (!c67x00->urb_count) { + c67x00_ll_hpi_disable_sofeop(c67x00->sie); + goto out; + } + + c67x00_fill_frame(c67x00); + if (!list_empty(&c67x00->td_list)) + /* TD's have been added to the frame */ + c67x00_send_frame(c67x00); + + out: + spin_unlock(&c67x00->lock); +} + +/* -------------------------------------------------------------------------- */ + +static void c67x00_sched_tasklet(unsigned long __c67x00) +{ + struct c67x00_hcd *c67x00 = (struct c67x00_hcd *)__c67x00; + c67x00_do_work(c67x00); +} + +void c67x00_sched_kick(struct c67x00_hcd *c67x00) +{ + tasklet_hi_schedule(&c67x00->tasklet); +} + +int c67x00_sched_start_scheduler(struct c67x00_hcd *c67x00) +{ + tasklet_init(&c67x00->tasklet, c67x00_sched_tasklet, + (unsigned long)c67x00); + return 0; +} + +void c67x00_sched_stop_scheduler(struct c67x00_hcd *c67x00) +{ + tasklet_kill(&c67x00->tasklet); +} diff --git a/drivers/usb/c67x00/c67x00.h b/drivers/usb/c67x00/c67x00.h new file mode 100644 index 00000000..a26e9ded --- /dev/null +++ b/drivers/usb/c67x00/c67x00.h @@ -0,0 +1,294 @@ +/* + * c67x00.h: Cypress C67X00 USB register and field definitions + * + * Copyright (C) 2006-2008 Barco N.V. + * Derived from the Cypress cy7c67200/300 ezusb linux driver and + * based on multiple host controller drivers inside the linux kernel. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + */ + +#ifndef _USB_C67X00_H +#define _USB_C67X00_H + +#include +#include +#include +#include + +/* --------------------------------------------------------------------- + * Cypress C67x00 register definitions + */ + +/* Hardware Revision Register */ +#define HW_REV_REG 0xC004 + +/* General USB registers */ +/* ===================== */ + +/* USB Control Register */ +#define USB_CTL_REG(x) ((x) ? 0xC0AA : 0xC08A) + +#define LOW_SPEED_PORT(x) ((x) ? 0x0800 : 0x0400) +#define HOST_MODE 0x0200 +#define PORT_RES_EN(x) ((x) ? 0x0100 : 0x0080) +#define SOF_EOP_EN(x) ((x) ? 0x0002 : 0x0001) + +/* USB status register - Notice it has different content in hcd/udc mode */ +#define USB_STAT_REG(x) ((x) ? 0xC0B0 : 0xC090) + +#define EP0_IRQ_FLG 0x0001 +#define EP1_IRQ_FLG 0x0002 +#define EP2_IRQ_FLG 0x0004 +#define EP3_IRQ_FLG 0x0008 +#define EP4_IRQ_FLG 0x0010 +#define EP5_IRQ_FLG 0x0020 +#define EP6_IRQ_FLG 0x0040 +#define EP7_IRQ_FLG 0x0080 +#define RESET_IRQ_FLG 0x0100 +#define SOF_EOP_IRQ_FLG 0x0200 +#define ID_IRQ_FLG 0x4000 +#define VBUS_IRQ_FLG 0x8000 + +/* USB Host only registers */ +/* ======================= */ + +/* Host n Control Register */ +#define HOST_CTL_REG(x) ((x) ? 0xC0A0 : 0xC080) + +#define PREAMBLE_EN 0x0080 /* Preamble enable */ +#define SEQ_SEL 0x0040 /* Data Toggle Sequence Bit Select */ +#define ISO_EN 0x0010 /* Isochronous enable */ +#define ARM_EN 0x0001 /* Arm operation */ + +/* Host n Interrupt Enable Register */ +#define HOST_IRQ_EN_REG(x) ((x) ? 0xC0AC : 0xC08C) + +#define SOF_EOP_IRQ_EN 0x0200 /* SOF/EOP Interrupt Enable */ +#define SOF_EOP_TMOUT_IRQ_EN 0x0800 /* SOF/EOP Timeout Interrupt Enable */ +#define ID_IRQ_EN 0x4000 /* ID interrupt enable */ +#define VBUS_IRQ_EN 0x8000 /* VBUS interrupt enable */ +#define DONE_IRQ_EN 0x0001 /* Done Interrupt Enable */ + +/* USB status register */ +#define HOST_STAT_MASK 0x02FD +#define PORT_CONNECT_CHANGE(x) ((x) ? 0x0020 : 0x0010) +#define PORT_SE0_STATUS(x) ((x) ? 0x0008 : 0x0004) + +/* Host Frame Register */ +#define HOST_FRAME_REG(x) ((x) ? 0xC0B6 : 0xC096) + +#define HOST_FRAME_MASK 0x07FF + +/* USB Peripheral only registers */ +/* ============================= */ + +/* Device n Port Sel reg */ +#define DEVICE_N_PORT_SEL(x) ((x) ? 0xC0A4 : 0xC084) + +/* Device n Interrupt Enable Register */ +#define DEVICE_N_IRQ_EN_REG(x) ((x) ? 0xC0AC : 0xC08C) + +#define DEVICE_N_ENDPOINT_N_CTL_REG(dev, ep) ((dev) \ + ? (0x0280 + (ep << 4)) \ + : (0x0200 + (ep << 4))) +#define DEVICE_N_ENDPOINT_N_STAT_REG(dev, ep) ((dev) \ + ? (0x0286 + (ep << 4)) \ + : (0x0206 + (ep << 4))) + +#define DEVICE_N_ADDRESS(dev) ((dev) ? (0xC0AE) : (0xC08E)) + +/* HPI registers */ +/* ============= */ + +/* HPI Status register */ +#define SOFEOP_FLG(x) (1 << ((x) ? 12 : 10)) +#define SIEMSG_FLG(x) (1 << (4 + (x))) +#define RESET_FLG(x) ((x) ? 0x0200 : 0x0002) +#define DONE_FLG(x) (1 << (2 + (x))) +#define RESUME_FLG(x) (1 << (6 + (x))) +#define MBX_OUT_FLG 0x0001 /* Message out available */ +#define MBX_IN_FLG 0x0100 +#define ID_FLG 0x4000 +#define VBUS_FLG 0x8000 + +/* Interrupt routing register */ +#define HPI_IRQ_ROUTING_REG 0x0142 + +#define HPI_SWAP_ENABLE(x) ((x) ? 0x0100 : 0x0001) +#define RESET_TO_HPI_ENABLE(x) ((x) ? 0x0200 : 0x0002) +#define DONE_TO_HPI_ENABLE(x) ((x) ? 0x0008 : 0x0004) +#define RESUME_TO_HPI_ENABLE(x) ((x) ? 0x0080 : 0x0040) +#define SOFEOP_TO_HPI_EN(x) ((x) ? 0x2000 : 0x0800) +#define SOFEOP_TO_CPU_EN(x) ((x) ? 0x1000 : 0x0400) +#define ID_TO_HPI_ENABLE 0x4000 +#define VBUS_TO_HPI_ENABLE 0x8000 + +/* SIE msg registers */ +#define SIEMSG_REG(x) ((x) ? 0x0148 : 0x0144) + +#define HUSB_TDListDone 0x1000 + +#define SUSB_EP0_MSG 0x0001 +#define SUSB_EP1_MSG 0x0002 +#define SUSB_EP2_MSG 0x0004 +#define SUSB_EP3_MSG 0x0008 +#define SUSB_EP4_MSG 0x0010 +#define SUSB_EP5_MSG 0x0020 +#define SUSB_EP6_MSG 0x0040 +#define SUSB_EP7_MSG 0x0080 +#define SUSB_RST_MSG 0x0100 +#define SUSB_SOF_MSG 0x0200 +#define SUSB_CFG_MSG 0x0400 +#define SUSB_SUS_MSG 0x0800 +#define SUSB_ID_MSG 0x4000 +#define SUSB_VBUS_MSG 0x8000 + +/* BIOS interrupt routines */ + +#define SUSBx_RECEIVE_INT(x) ((x) ? 97 : 81) +#define SUSBx_SEND_INT(x) ((x) ? 96 : 80) + +#define SUSBx_DEV_DESC_VEC(x) ((x) ? 0x00D4 : 0x00B4) +#define SUSBx_CONF_DESC_VEC(x) ((x) ? 0x00D6 : 0x00B6) +#define SUSBx_STRING_DESC_VEC(x) ((x) ? 0x00D8 : 0x00B8) + +#define CY_HCD_BUF_ADDR 0x500 /* Base address for host */ +#define SIE_TD_SIZE 0x200 /* size of the td list */ +#define SIE_TD_BUF_SIZE 0x400 /* size of the data buffer */ + +#define SIE_TD_OFFSET(host) ((host) ? (SIE_TD_SIZE+SIE_TD_BUF_SIZE) : 0) +#define SIE_BUF_OFFSET(host) (SIE_TD_OFFSET(host) + SIE_TD_SIZE) + +/* Base address of HCD + 2 x TD_SIZE + 2 x TD_BUF_SIZE */ +#define CY_UDC_REQ_HEADER_BASE 0x1100 +/* 8- byte request headers for IN/OUT transfers */ +#define CY_UDC_REQ_HEADER_SIZE 8 + +#define CY_UDC_REQ_HEADER_ADDR(ep_num) (CY_UDC_REQ_HEADER_BASE + \ + ((ep_num) * CY_UDC_REQ_HEADER_SIZE)) +#define CY_UDC_DESC_BASE_ADDRESS (CY_UDC_REQ_HEADER_ADDR(8)) + +#define CY_UDC_BIOS_REPLACE_BASE 0x1800 +#define CY_UDC_REQ_BUFFER_BASE 0x2000 +#define CY_UDC_REQ_BUFFER_SIZE 0x0400 +#define CY_UDC_REQ_BUFFER_ADDR(ep_num) (CY_UDC_REQ_BUFFER_BASE + \ + ((ep_num) * CY_UDC_REQ_BUFFER_SIZE)) + +/* --------------------------------------------------------------------- + * Driver data structures + */ + +struct c67x00_device; + +/** + * struct c67x00_sie - Common data associated with a SIE + * @lock: lock to protect this struct and the associated chip registers + * @private_data: subdriver dependent data + * @irq: subdriver dependent irq handler, set NULL when not used + * @dev: link to common driver structure + * @sie_num: SIE number on chip, starting from 0 + * @mode: SIE mode (host/peripheral/otg/not used) + */ +struct c67x00_sie { + /* Entries to be used by the subdrivers */ + spinlock_t lock; /* protect this structure */ + void *private_data; + void (*irq) (struct c67x00_sie *sie, u16 int_status, u16 msg); + + /* Read only: */ + struct c67x00_device *dev; + int sie_num; + int mode; +}; + +#define sie_dev(s) (&(s)->dev->pdev->dev) + +/** + * struct c67x00_lcp + */ +struct c67x00_lcp { + /* Internal use only */ + struct mutex mutex; + struct completion msg_received; + u16 last_msg; +}; + +/* + * struct c67x00_hpi + */ +struct c67x00_hpi { + void __iomem *base; + int regstep; + spinlock_t lock; + struct c67x00_lcp lcp; +}; + +#define C67X00_SIES 2 +#define C67X00_PORTS 2 + +/** + * struct c67x00_device - Common data associated with a c67x00 instance + * @hpi: hpi addresses + * @sie: array of sie's on this chip + * @pdev: platform device of instance + * @pdata: configuration provided by the platform + */ +struct c67x00_device { + struct c67x00_hpi hpi; + struct c67x00_sie sie[C67X00_SIES]; + struct platform_device *pdev; + struct c67x00_platform_data *pdata; +}; + +/* --------------------------------------------------------------------- + * Low level interface functions + */ + +/* Host Port Interface (HPI) functions */ +u16 c67x00_ll_hpi_status(struct c67x00_device *dev); +void c67x00_ll_hpi_reg_init(struct c67x00_device *dev); +void c67x00_ll_hpi_enable_sofeop(struct c67x00_sie *sie); +void c67x00_ll_hpi_disable_sofeop(struct c67x00_sie *sie); + +/* General functions */ +u16 c67x00_ll_fetch_siemsg(struct c67x00_device *dev, int sie_num); +u16 c67x00_ll_get_usb_ctl(struct c67x00_sie *sie); +void c67x00_ll_usb_clear_status(struct c67x00_sie *sie, u16 bits); +u16 c67x00_ll_usb_get_status(struct c67x00_sie *sie); +void c67x00_ll_write_mem_le16(struct c67x00_device *dev, u16 addr, + void *data, int len); +void c67x00_ll_read_mem_le16(struct c67x00_device *dev, u16 addr, + void *data, int len); + +/* Host specific functions */ +void c67x00_ll_set_husb_eot(struct c67x00_device *dev, u16 value); +void c67x00_ll_husb_reset(struct c67x00_sie *sie, int port); +void c67x00_ll_husb_set_current_td(struct c67x00_sie *sie, u16 addr); +u16 c67x00_ll_husb_get_current_td(struct c67x00_sie *sie); +u16 c67x00_ll_husb_get_frame(struct c67x00_sie *sie); +void c67x00_ll_husb_init_host_port(struct c67x00_sie *sie); +void c67x00_ll_husb_reset_port(struct c67x00_sie *sie, int port); + +/* Called by c67x00_irq to handle lcp interrupts */ +void c67x00_ll_irq(struct c67x00_device *dev, u16 int_status); + +/* Setup and teardown */ +void c67x00_ll_init(struct c67x00_device *dev); +void c67x00_ll_release(struct c67x00_device *dev); +int c67x00_ll_reset(struct c67x00_device *dev); + +#endif /* _USB_C67X00_H */ diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig new file mode 100644 index 00000000..b2df442e --- /dev/null +++ b/drivers/usb/chipidea/Kconfig @@ -0,0 +1,34 @@ +config USB_CHIPIDEA + tristate "ChipIdea Highspeed Dual Role Controller" + depends on USB || USB_GADGET + help + Say Y here if your system has a dual role high speed USB + controller based on ChipIdea silicon IP. Currently, only the + peripheral mode is supported. + + When compiled dynamically, the module will be called ci-hdrc.ko. + +if USB_CHIPIDEA + +config USB_CHIPIDEA_UDC + bool "ChipIdea device controller" + depends on USB_GADGET=y || USB_GADGET=USB_CHIPIDEA + help + Say Y here to enable device controller functionality of the + ChipIdea driver. + +config USB_CHIPIDEA_HOST + bool "ChipIdea host controller" + depends on USB=y || USB=USB_CHIPIDEA + depends on USB_EHCI_HCD=y + select USB_EHCI_ROOT_HUB_TT + help + Say Y here to enable host controller functionality of the + ChipIdea driver. + +config USB_CHIPIDEA_DEBUG + bool "ChipIdea driver debug" + help + Say Y here to enable debugging output of the ChipIdea driver. + +endif diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile new file mode 100644 index 00000000..4ab83e98 --- /dev/null +++ b/drivers/usb/chipidea/Makefile @@ -0,0 +1,21 @@ +ccflags-$(CONFIG_USB_CHIPIDEA_DEBUG) := -DDEBUG + +obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o + +ci_hdrc-y := core.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o + +# Glue/Bridge layers go here + +obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_msm.o + +# PCI doesn't provide stubs, need to check +ifneq ($(CONFIG_PCI),) + obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_pci.o +endif + +ifneq ($(CONFIG_OF_DEVICE),) + obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_imx.o usbmisc_imx.o +endif diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h new file mode 100644 index 00000000..050de856 --- /dev/null +++ b/drivers/usb/chipidea/bits.h @@ -0,0 +1,90 @@ +/* + * bits.h - register bits of the ChipIdea USB IP core + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_BITS_H +#define __DRIVERS_USB_CHIPIDEA_BITS_H + +#include + +/* HCCPARAMS */ +#define HCCPARAMS_LEN BIT(17) + +/* DCCPARAMS */ +#define DCCPARAMS_DEN (0x1F << 0) +#define DCCPARAMS_DC BIT(7) +#define DCCPARAMS_HC BIT(8) + +/* TESTMODE */ +#define TESTMODE_FORCE BIT(0) + +/* USBCMD */ +#define USBCMD_RS BIT(0) +#define USBCMD_RST BIT(1) +#define USBCMD_SUTW BIT(13) +#define USBCMD_ATDTW BIT(14) + +/* USBSTS & USBINTR */ +#define USBi_UI BIT(0) +#define USBi_UEI BIT(1) +#define USBi_PCI BIT(2) +#define USBi_URI BIT(6) +#define USBi_SLI BIT(8) + +/* DEVICEADDR */ +#define DEVICEADDR_USBADRA BIT(24) +#define DEVICEADDR_USBADR (0x7FUL << 25) + +/* PORTSC */ +#define PORTSC_FPR BIT(6) +#define PORTSC_SUSP BIT(7) +#define PORTSC_HSP BIT(9) +#define PORTSC_PTC (0x0FUL << 16) + +/* DEVLC */ +#define DEVLC_PSPD (0x03UL << 25) +#define DEVLC_PSPD_HS (0x02UL << 25) + +/* OTGSC */ +#define OTGSC_IDPU BIT(5) +#define OTGSC_ID BIT(8) +#define OTGSC_AVV BIT(9) +#define OTGSC_ASV BIT(10) +#define OTGSC_BSV BIT(11) +#define OTGSC_BSE BIT(12) +#define OTGSC_IDIS BIT(16) +#define OTGSC_AVVIS BIT(17) +#define OTGSC_ASVIS BIT(18) +#define OTGSC_BSVIS BIT(19) +#define OTGSC_BSEIS BIT(20) +#define OTGSC_IDIE BIT(24) +#define OTGSC_AVVIE BIT(25) +#define OTGSC_ASVIE BIT(26) +#define OTGSC_BSVIE BIT(27) +#define OTGSC_BSEIE BIT(28) + +/* USBMODE */ +#define USBMODE_CM (0x03UL << 0) +#define USBMODE_CM_DC (0x02UL << 0) +#define USBMODE_SLOM BIT(3) +#define USBMODE_CI_SDIS BIT(4) + +/* ENDPTCTRL */ +#define ENDPTCTRL_RXS BIT(0) +#define ENDPTCTRL_RXT (0x03UL << 2) +#define ENDPTCTRL_RXR BIT(6) /* reserved for port 0 */ +#define ENDPTCTRL_RXE BIT(7) +#define ENDPTCTRL_TXS BIT(16) +#define ENDPTCTRL_TXT (0x03UL << 18) +#define ENDPTCTRL_TXR BIT(22) /* reserved for port 0 */ +#define ENDPTCTRL_TXE BIT(23) + +#endif /* __DRIVERS_USB_CHIPIDEA_BITS_H */ diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h new file mode 100644 index 00000000..b0a6bce0 --- /dev/null +++ b/drivers/usb/chipidea/ci.h @@ -0,0 +1,305 @@ +/* + * ci.h - common structures, functions, and macros of the ChipIdea driver + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_CI_H +#define __DRIVERS_USB_CHIPIDEA_CI_H + +#include +#include +#include +#include + +/****************************************************************************** + * DEFINE + *****************************************************************************/ +#define TD_PAGE_COUNT 5 +#define CI13XXX_PAGE_SIZE 4096ul /* page size for TD's */ +#define ENDPT_MAX 32 + +/****************************************************************************** + * STRUCTURES + *****************************************************************************/ +/** + * struct ci13xxx_ep - endpoint representation + * @ep: endpoint structure for gadget drivers + * @dir: endpoint direction (TX/RX) + * @num: endpoint number + * @type: endpoint type + * @name: string description of the endpoint + * @qh: queue head for this endpoint + * @wedge: is the endpoint wedged + * @ci: pointer to the controller + * @lock: pointer to controller's spinlock + * @td_pool: pointer to controller's TD pool + */ +struct ci13xxx_ep { + struct usb_ep ep; + u8 dir; + u8 num; + u8 type; + char name[16]; + struct { + struct list_head queue; + struct ci13xxx_qh *ptr; + dma_addr_t dma; + } qh; + int wedge; + + /* global resources */ + struct ci13xxx *ci; + spinlock_t *lock; + struct dma_pool *td_pool; +}; + +enum ci_role { + CI_ROLE_HOST = 0, + CI_ROLE_GADGET, + CI_ROLE_END, +}; + +/** + * struct ci_role_driver - host/gadget role driver + * start: start this role + * stop: stop this role + * irq: irq handler for this role + * name: role name string (host/gadget) + */ +struct ci_role_driver { + int (*start)(struct ci13xxx *); + void (*stop)(struct ci13xxx *); + irqreturn_t (*irq)(struct ci13xxx *); + const char *name; +}; + +/** + * struct hw_bank - hardware register mapping representation + * @lpm: set if the device is LPM capable + * @phys: physical address of the controller's registers + * @abs: absolute address of the beginning of register window + * @cap: capability registers + * @op: operational registers + * @size: size of the register window + * @regmap: register lookup table + */ +struct hw_bank { + unsigned lpm; + resource_size_t phys; + void __iomem *abs; + void __iomem *cap; + void __iomem *op; + size_t size; + void __iomem **regmap; +}; + +/** + * struct ci13xxx - chipidea device representation + * @dev: pointer to parent device + * @lock: access synchronization + * @hw_bank: hardware register mapping + * @irq: IRQ number + * @roles: array of supported roles for this controller + * @role: current role + * @is_otg: if the device is otg-capable + * @work: work for role changing + * @wq: workqueue thread + * @qh_pool: allocation pool for queue heads + * @td_pool: allocation pool for transfer descriptors + * @gadget: device side representation for peripheral controller + * @driver: gadget driver + * @hw_ep_max: total number of endpoints supported by hardware + * @ci13xxx_ep: array of endpoints + * @ep0_dir: ep0 direction + * @ep0out: pointer to ep0 OUT endpoint + * @ep0in: pointer to ep0 IN endpoint + * @status: ep0 status request + * @setaddr: if we should set the address on status completion + * @address: usb address received from the host + * @remote_wakeup: host-enabled remote wakeup + * @suspended: suspended by host + * @test_mode: the selected test mode + * @platdata: platform specific information supplied by parent device + * @vbus_active: is VBUS active + * @transceiver: pointer to USB PHY, if any + * @hcd: pointer to usb_hcd for ehci host driver + * @debugfs: root dentry for this controller in debugfs + */ +struct ci13xxx { + struct device *dev; + spinlock_t lock; + struct hw_bank hw_bank; + int irq; + struct ci_role_driver *roles[CI_ROLE_END]; + enum ci_role role; + bool is_otg; + struct work_struct work; + struct workqueue_struct *wq; + + struct dma_pool *qh_pool; + struct dma_pool *td_pool; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + unsigned hw_ep_max; + struct ci13xxx_ep ci13xxx_ep[ENDPT_MAX]; + u32 ep0_dir; + struct ci13xxx_ep *ep0out, *ep0in; + + struct usb_request *status; + bool setaddr; + u8 address; + u8 remote_wakeup; + u8 suspended; + u8 test_mode; + + struct ci13xxx_platform_data *platdata; + int vbus_active; + /* FIXME: some day, we'll not use global phy */ + bool global_phy; + struct usb_phy *transceiver; + struct usb_hcd *hcd; + struct dentry *debugfs; +}; + +static inline struct ci_role_driver *ci_role(struct ci13xxx *ci) +{ + BUG_ON(ci->role >= CI_ROLE_END || !ci->roles[ci->role]); + return ci->roles[ci->role]; +} + +static inline int ci_role_start(struct ci13xxx *ci, enum ci_role role) +{ + int ret; + + if (role >= CI_ROLE_END) + return -EINVAL; + + if (!ci->roles[role]) + return -ENXIO; + + ret = ci->roles[role]->start(ci); + if (!ret) + ci->role = role; + return ret; +} + +static inline void ci_role_stop(struct ci13xxx *ci) +{ + enum ci_role role = ci->role; + + if (role == CI_ROLE_END) + return; + + ci->role = CI_ROLE_END; + + ci->roles[role]->stop(ci); +} + +/****************************************************************************** + * REGISTERS + *****************************************************************************/ +/* register size */ +#define REG_BITS (32) + +/* register indices */ +enum ci13xxx_regs { + CAP_CAPLENGTH, + CAP_HCCPARAMS, + CAP_DCCPARAMS, + CAP_TESTMODE, + CAP_LAST = CAP_TESTMODE, + OP_USBCMD, + OP_USBSTS, + OP_USBINTR, + OP_DEVICEADDR, + OP_ENDPTLISTADDR, + OP_PORTSC, + OP_DEVLC, + OP_OTGSC, + OP_USBMODE, + OP_ENDPTSETUPSTAT, + OP_ENDPTPRIME, + OP_ENDPTFLUSH, + OP_ENDPTSTAT, + OP_ENDPTCOMPLETE, + OP_ENDPTCTRL, + /* endptctrl1..15 follow */ + OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2, +}; + +/** + * hw_read: reads from a hw register + * @reg: register index + * @mask: bitfield mask + * + * This function returns register contents + */ +static inline u32 hw_read(struct ci13xxx *ci, enum ci13xxx_regs reg, u32 mask) +{ + return ioread32(ci->hw_bank.regmap[reg]) & mask; +} + +/** + * hw_write: writes to a hw register + * @reg: register index + * @mask: bitfield mask + * @data: new value + */ +static inline void hw_write(struct ci13xxx *ci, enum ci13xxx_regs reg, + u32 mask, u32 data) +{ + if (~mask) + data = (ioread32(ci->hw_bank.regmap[reg]) & ~mask) + | (data & mask); + + iowrite32(data, ci->hw_bank.regmap[reg]); +} + +/** + * hw_test_and_clear: tests & clears a hw register + * @reg: register index + * @mask: bitfield mask + * + * This function returns register contents + */ +static inline u32 hw_test_and_clear(struct ci13xxx *ci, enum ci13xxx_regs reg, + u32 mask) +{ + u32 val = ioread32(ci->hw_bank.regmap[reg]) & mask; + + iowrite32(val, ci->hw_bank.regmap[reg]); + return val; +} + +/** + * hw_test_and_write: tests & writes a hw register + * @reg: register index + * @mask: bitfield mask + * @data: new value + * + * This function returns register contents + */ +static inline u32 hw_test_and_write(struct ci13xxx *ci, enum ci13xxx_regs reg, + u32 mask, u32 data) +{ + u32 val = hw_read(ci, reg, ~0); + + hw_write(ci, reg, mask, data); + return (val & mask) >> __ffs(mask); +} + +int hw_device_reset(struct ci13xxx *ci, u32 mode); + +int hw_port_test_set(struct ci13xxx *ci, u8 mode); + +u8 hw_port_test_get(struct ci13xxx *ci); + +#endif /* __DRIVERS_USB_CHIPIDEA_CI_H */ diff --git a/drivers/usb/chipidea/ci13xxx_imx.c b/drivers/usb/chipidea/ci13xxx_imx.c new file mode 100644 index 00000000..73f9d5f1 --- /dev/null +++ b/drivers/usb/chipidea/ci13xxx_imx.c @@ -0,0 +1,274 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Marek Vasut + * on behalf of DENX Software Engineering GmbH + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "ci13xxx_imx.h" + +#define pdev_to_phy(pdev) \ + ((struct usb_phy *)platform_get_drvdata(pdev)) + +struct ci13xxx_imx_data { + struct device_node *phy_np; + struct usb_phy *phy; + struct platform_device *ci_pdev; + struct clk *clk; + struct regulator *reg_vbus; +}; + +static const struct usbmisc_ops *usbmisc_ops; + +/* Common functions shared by usbmisc drivers */ + +int usbmisc_set_ops(const struct usbmisc_ops *ops) +{ + if (usbmisc_ops) + return -EBUSY; + + usbmisc_ops = ops; + + return 0; +} +EXPORT_SYMBOL_GPL(usbmisc_set_ops); + +void usbmisc_unset_ops(const struct usbmisc_ops *ops) +{ + usbmisc_ops = NULL; +} +EXPORT_SYMBOL_GPL(usbmisc_unset_ops); + +int usbmisc_get_init_data(struct device *dev, struct usbmisc_usb_device *usbdev) +{ + struct device_node *np = dev->of_node; + struct of_phandle_args args; + int ret; + + usbdev->dev = dev; + + ret = of_parse_phandle_with_args(np, "fsl,usbmisc", "#index-cells", + 0, &args); + if (ret) { + dev_err(dev, "Failed to parse property fsl,usbmisc, errno %d\n", + ret); + memset(usbdev, 0, sizeof(*usbdev)); + return ret; + } + usbdev->index = args.args[0]; + of_node_put(args.np); + + if (of_find_property(np, "disable-over-current", NULL)) + usbdev->disable_oc = 1; + + if (of_find_property(np, "external-vbus-divider", NULL)) + usbdev->evdo = 1; + + return 0; +} +EXPORT_SYMBOL_GPL(usbmisc_get_init_data); + +/* End of common functions shared by usbmisc drivers*/ + +static struct ci13xxx_platform_data ci13xxx_imx_platdata = { + .name = "ci13xxx_imx", + .flags = CI13XXX_REQUIRE_TRANSCEIVER | + CI13XXX_PULLUP_ON_VBUS | + CI13XXX_DISABLE_STREAMING, + .capoffset = DEF_CAPOFFSET, +}; + +static int ci13xxx_imx_probe(struct platform_device *pdev) +{ + struct ci13xxx_imx_data *data; + struct platform_device *plat_ci, *phy_pdev; + struct device_node *phy_np; + struct resource *res; + struct regulator *reg_vbus; + struct pinctrl *pinctrl; + int ret; + + if (of_find_property(pdev->dev.of_node, "fsl,usbmisc", NULL) + && !usbmisc_ops) + return -EPROBE_DEFER; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "Failed to allocate CI13xxx-IMX data!\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Can't get device resources!\n"); + return -ENOENT; + } + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + dev_warn(&pdev->dev, "pinctrl get/select failed, err=%ld\n", + PTR_ERR(pinctrl)); + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, + "Failed to get clock, err=%ld\n", PTR_ERR(data->clk)); + return PTR_ERR(data->clk); + } + + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(&pdev->dev, + "Failed to prepare or enable clock, err=%d\n", ret); + return ret; + } + + phy_np = of_parse_phandle(pdev->dev.of_node, "fsl,usbphy", 0); + if (phy_np) { + data->phy_np = phy_np; + phy_pdev = of_find_device_by_node(phy_np); + if (phy_pdev) { + struct usb_phy *phy; + phy = pdev_to_phy(phy_pdev); + if (phy && + try_module_get(phy_pdev->dev.driver->owner)) { + usb_phy_init(phy); + data->phy = phy; + } + } + } + + /* we only support host now, so enable vbus here */ + reg_vbus = devm_regulator_get(&pdev->dev, "vbus"); + if (!IS_ERR(reg_vbus)) { + ret = regulator_enable(reg_vbus); + if (ret) { + dev_err(&pdev->dev, + "Failed to enable vbus regulator, err=%d\n", + ret); + goto put_np; + } + data->reg_vbus = reg_vbus; + } else { + reg_vbus = NULL; + } + + ci13xxx_imx_platdata.phy = data->phy; + + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + if (usbmisc_ops && usbmisc_ops->init) { + ret = usbmisc_ops->init(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, + "usbmisc init failed, ret=%d\n", ret); + goto err; + } + } + + plat_ci = ci13xxx_add_device(&pdev->dev, + pdev->resource, pdev->num_resources, + &ci13xxx_imx_platdata); + if (IS_ERR(plat_ci)) { + ret = PTR_ERR(plat_ci); + dev_err(&pdev->dev, + "Can't register ci_hdrc platform device, err=%d\n", + ret); + goto err; + } + + if (usbmisc_ops && usbmisc_ops->post) { + ret = usbmisc_ops->post(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, + "usbmisc post failed, ret=%d\n", ret); + goto put_np; + } + } + + data->ci_pdev = plat_ci; + platform_set_drvdata(pdev, data); + + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; + +err: + if (reg_vbus) + regulator_disable(reg_vbus); +put_np: + if (phy_np) + of_node_put(phy_np); + clk_disable_unprepare(data->clk); + return ret; +} + +static int ci13xxx_imx_remove(struct platform_device *pdev) +{ + struct ci13xxx_imx_data *data = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + ci13xxx_remove_device(data->ci_pdev); + + if (data->reg_vbus) + regulator_disable(data->reg_vbus); + + if (data->phy) { + usb_phy_shutdown(data->phy); + module_put(data->phy->dev->driver->owner); + } + + of_node_put(data->phy_np); + + clk_disable_unprepare(data->clk); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id ci13xxx_imx_dt_ids[] = { + { .compatible = "fsl,imx27-usb", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ci13xxx_imx_dt_ids); + +static struct platform_driver ci13xxx_imx_driver = { + .probe = ci13xxx_imx_probe, + .remove = ci13xxx_imx_remove, + .driver = { + .name = "imx_usb", + .owner = THIS_MODULE, + .of_match_table = ci13xxx_imx_dt_ids, + }, +}; + +module_platform_driver(ci13xxx_imx_driver); + +MODULE_ALIAS("platform:imx-usb"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CI13xxx i.MX USB binding"); +MODULE_AUTHOR("Marek Vasut "); +MODULE_AUTHOR("Richard Zhao "); diff --git a/drivers/usb/chipidea/ci13xxx_imx.h b/drivers/usb/chipidea/ci13xxx_imx.h new file mode 100644 index 00000000..550bfa45 --- /dev/null +++ b/drivers/usb/chipidea/ci13xxx_imx.h @@ -0,0 +1,31 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Used to set SoC specific callbacks */ +struct usbmisc_ops { + /* It's called once when probe a usb device */ + int (*init)(struct device *dev); + /* It's called once after adding a usb device */ + int (*post)(struct device *dev); +}; + +struct usbmisc_usb_device { + struct device *dev; /* usb controller device */ + int index; + + unsigned int disable_oc:1; /* over current detect disabled */ + unsigned int evdo:1; /* set external vbus divider option */ +}; + +int usbmisc_set_ops(const struct usbmisc_ops *ops); +void usbmisc_unset_ops(const struct usbmisc_ops *ops); +int +usbmisc_get_init_data(struct device *dev, struct usbmisc_usb_device *usbdev); diff --git a/drivers/usb/chipidea/ci13xxx_msm.c b/drivers/usb/chipidea/ci13xxx_msm.c new file mode 100644 index 00000000..7d16681f --- /dev/null +++ b/drivers/usb/chipidea/ci13xxx_msm.c @@ -0,0 +1,99 @@ +/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" + +#define MSM_USB_BASE (ci->hw_bank.abs) + +static void ci13xxx_msm_notify_event(struct ci13xxx *ci, unsigned event) +{ + struct device *dev = ci->gadget.dev.parent; + int val; + + switch (event) { + case CI13XXX_CONTROLLER_RESET_EVENT: + dev_dbg(dev, "CI13XXX_CONTROLLER_RESET_EVENT received\n"); + writel(0, USB_AHBBURST); + writel(0, USB_AHBMODE); + break; + case CI13XXX_CONTROLLER_STOPPED_EVENT: + dev_dbg(dev, "CI13XXX_CONTROLLER_STOPPED_EVENT received\n"); + /* + * Put the transceiver in non-driving mode. Otherwise host + * may not detect soft-disconnection. + */ + val = usb_phy_io_read(ci->transceiver, ULPI_FUNC_CTRL); + val &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + val |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; + usb_phy_io_write(ci->transceiver, val, ULPI_FUNC_CTRL); + break; + default: + dev_dbg(dev, "unknown ci13xxx event\n"); + break; + } +} + +static struct ci13xxx_platform_data ci13xxx_msm_platdata = { + .name = "ci13xxx_msm", + .flags = CI13XXX_REGS_SHARED | + CI13XXX_REQUIRE_TRANSCEIVER | + CI13XXX_PULLUP_ON_VBUS | + CI13XXX_DISABLE_STREAMING, + + .notify_event = ci13xxx_msm_notify_event, +}; + +static int ci13xxx_msm_probe(struct platform_device *pdev) +{ + struct platform_device *plat_ci; + + dev_dbg(&pdev->dev, "ci13xxx_msm_probe\n"); + + plat_ci = ci13xxx_add_device(&pdev->dev, + pdev->resource, pdev->num_resources, + &ci13xxx_msm_platdata); + if (IS_ERR(plat_ci)) { + dev_err(&pdev->dev, "ci13xxx_add_device failed!\n"); + return PTR_ERR(plat_ci); + } + + platform_set_drvdata(pdev, plat_ci); + + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int ci13xxx_msm_remove(struct platform_device *pdev) +{ + struct platform_device *plat_ci = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + ci13xxx_remove_device(plat_ci); + + return 0; +} + +static struct platform_driver ci13xxx_msm_driver = { + .probe = ci13xxx_msm_probe, + .remove = ci13xxx_msm_remove, + .driver = { .name = "msm_hsusb", }, +}; + +module_platform_driver(ci13xxx_msm_driver); + +MODULE_ALIAS("platform:msm_hsusb"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/ci13xxx_pci.c b/drivers/usb/chipidea/ci13xxx_pci.c new file mode 100644 index 00000000..4e1fc61b --- /dev/null +++ b/drivers/usb/chipidea/ci13xxx_pci.c @@ -0,0 +1,158 @@ +/* + * ci13xxx_pci.c - MIPS USB IP core family device controller + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +/* driver name */ +#define UDC_DRIVER_NAME "ci13xxx_pci" + +/****************************************************************************** + * PCI block + *****************************************************************************/ +static struct ci13xxx_platform_data pci_platdata = { + .name = UDC_DRIVER_NAME, + .capoffset = DEF_CAPOFFSET, +}; + +static struct ci13xxx_platform_data langwell_pci_platdata = { + .name = UDC_DRIVER_NAME, + .capoffset = 0, +}; + +static struct ci13xxx_platform_data penwell_pci_platdata = { + .name = UDC_DRIVER_NAME, + .capoffset = 0, + .power_budget = 200, +}; + +/** + * ci13xxx_pci_probe: PCI probe + * @pdev: USB device controller being probed + * @id: PCI hotplug ID connecting controller to UDC framework + * + * This function returns an error code + * Allocates basic PCI resources for this USB device controller, and then + * invokes the udc_probe() method to start the UDC associated with it + */ +static int ci13xxx_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct ci13xxx_platform_data *platdata = (void *)id->driver_data; + struct platform_device *plat_ci; + struct resource res[3]; + int retval = 0, nres = 2; + + if (!platdata) { + dev_err(&pdev->dev, "device doesn't provide driver data\n"); + return -ENODEV; + } + + retval = pci_enable_device(pdev); + if (retval) + goto done; + + if (!pdev->irq) { + dev_err(&pdev->dev, "No IRQ, check BIOS/PCI setup!"); + retval = -ENODEV; + goto disable_device; + } + + pci_set_power_state(pdev, PCI_D0); + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + memset(res, 0, sizeof(res)); + res[0].start = pci_resource_start(pdev, 0); + res[0].end = pci_resource_end(pdev, 0); + res[0].flags = IORESOURCE_MEM; + res[1].start = pdev->irq; + res[1].flags = IORESOURCE_IRQ; + + plat_ci = ci13xxx_add_device(&pdev->dev, res, nres, platdata); + if (IS_ERR(plat_ci)) { + dev_err(&pdev->dev, "ci13xxx_add_device failed!\n"); + retval = PTR_ERR(plat_ci); + goto disable_device; + } + + pci_set_drvdata(pdev, plat_ci); + + return 0; + + disable_device: + pci_disable_device(pdev); + done: + return retval; +} + +/** + * ci13xxx_pci_remove: PCI remove + * @pdev: USB Device Controller being removed + * + * Reverses the effect of ci13xxx_pci_probe(), + * first invoking the udc_remove() and then releases + * all PCI resources allocated for this USB device controller + */ +static void ci13xxx_pci_remove(struct pci_dev *pdev) +{ + struct platform_device *plat_ci = pci_get_drvdata(pdev); + + ci13xxx_remove_device(plat_ci); + pci_set_drvdata(pdev, NULL); + pci_disable_device(pdev); +} + +/** + * PCI device table + * PCI device structure + * + * Check "pci.h" for details + */ +static DEFINE_PCI_DEVICE_TABLE(ci13xxx_pci_id_table) = { + { + PCI_DEVICE(0x153F, 0x1004), + .driver_data = (kernel_ulong_t)&pci_platdata, + }, + { + PCI_DEVICE(0x153F, 0x1006), + .driver_data = (kernel_ulong_t)&pci_platdata, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0811), + .driver_data = (kernel_ulong_t)&langwell_pci_platdata, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0829), + .driver_data = (kernel_ulong_t)&penwell_pci_platdata, + }, + { 0, 0, 0, 0, 0, 0, 0 /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, ci13xxx_pci_id_table); + +static struct pci_driver ci13xxx_pci_driver = { + .name = UDC_DRIVER_NAME, + .id_table = ci13xxx_pci_id_table, + .probe = ci13xxx_pci_probe, + .remove = ci13xxx_pci_remove, +}; + +module_pci_driver(ci13xxx_pci_driver); + +MODULE_AUTHOR("MIPS - David Lopo "); +MODULE_DESCRIPTION("MIPS CI13XXX USB Peripheral Controller"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("June 2008"); diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c new file mode 100644 index 00000000..475c9c11 --- /dev/null +++ b/drivers/usb/chipidea/core.c @@ -0,0 +1,495 @@ +/* + * core.c - ChipIdea USB IP core family device controller + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * Description: ChipIdea USB IP core family device controller + * + * This driver is composed of several blocks: + * - HW: hardware interface + * - DBG: debug facilities (optional) + * - UTIL: utilities + * - ISR: interrupts handling + * - ENDPT: endpoint operations (Gadget API) + * - GADGET: gadget operations (Gadget API) + * - BUS: bus glue code, bus abstraction layer + * + * Compile Options + * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities + * - STALL_IN: non-empty bulk-in pipes cannot be halted + * if defined mass storage compliance succeeds but with warnings + * => case 4: Hi > Dn + * => case 5: Hi > Di + * => case 8: Hi <> Do + * if undefined usbtest 13 fails + * - TRACE: enable function tracing (depends on DEBUG) + * + * Main Features + * - Chapter 9 & Mass Storage Compliance with Gadget File Storage + * - Chapter 9 Compliance with Gadget Zero (STALL_IN undefined) + * - Normal & LPM support + * + * USBTEST Report + * - OK: 0-12, 13 (STALL_IN defined) & 14 + * - Not Supported: 15 & 16 (ISO) + * + * TODO List + * - OTG + * - Isochronous & Interrupt Traffic + * - Handle requests which spawns into several TDs + * - GET_STATUS(device) - always reports 0 + * - Gadget API (majority of optional features) + * - Suspend & Remote Wakeup + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "host.h" +#include "debug.h" + +/* Controller register map */ +static uintptr_t ci_regs_nolpm[] = { + [CAP_CAPLENGTH] = 0x000UL, + [CAP_HCCPARAMS] = 0x008UL, + [CAP_DCCPARAMS] = 0x024UL, + [CAP_TESTMODE] = 0x038UL, + [OP_USBCMD] = 0x000UL, + [OP_USBSTS] = 0x004UL, + [OP_USBINTR] = 0x008UL, + [OP_DEVICEADDR] = 0x014UL, + [OP_ENDPTLISTADDR] = 0x018UL, + [OP_PORTSC] = 0x044UL, + [OP_DEVLC] = 0x084UL, + [OP_OTGSC] = 0x064UL, + [OP_USBMODE] = 0x068UL, + [OP_ENDPTSETUPSTAT] = 0x06CUL, + [OP_ENDPTPRIME] = 0x070UL, + [OP_ENDPTFLUSH] = 0x074UL, + [OP_ENDPTSTAT] = 0x078UL, + [OP_ENDPTCOMPLETE] = 0x07CUL, + [OP_ENDPTCTRL] = 0x080UL, +}; + +static uintptr_t ci_regs_lpm[] = { + [CAP_CAPLENGTH] = 0x000UL, + [CAP_HCCPARAMS] = 0x008UL, + [CAP_DCCPARAMS] = 0x024UL, + [CAP_TESTMODE] = 0x0FCUL, + [OP_USBCMD] = 0x000UL, + [OP_USBSTS] = 0x004UL, + [OP_USBINTR] = 0x008UL, + [OP_DEVICEADDR] = 0x014UL, + [OP_ENDPTLISTADDR] = 0x018UL, + [OP_PORTSC] = 0x044UL, + [OP_DEVLC] = 0x084UL, + [OP_OTGSC] = 0x0C4UL, + [OP_USBMODE] = 0x0C8UL, + [OP_ENDPTSETUPSTAT] = 0x0D8UL, + [OP_ENDPTPRIME] = 0x0DCUL, + [OP_ENDPTFLUSH] = 0x0E0UL, + [OP_ENDPTSTAT] = 0x0E4UL, + [OP_ENDPTCOMPLETE] = 0x0E8UL, + [OP_ENDPTCTRL] = 0x0ECUL, +}; + +static int hw_alloc_regmap(struct ci13xxx *ci, bool is_lpm) +{ + int i; + + kfree(ci->hw_bank.regmap); + + ci->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *), + GFP_KERNEL); + if (!ci->hw_bank.regmap) + return -ENOMEM; + + for (i = 0; i < OP_ENDPTCTRL; i++) + ci->hw_bank.regmap[i] = + (i <= CAP_LAST ? ci->hw_bank.cap : ci->hw_bank.op) + + (is_lpm ? ci_regs_lpm[i] : ci_regs_nolpm[i]); + + for (; i <= OP_LAST; i++) + ci->hw_bank.regmap[i] = ci->hw_bank.op + + 4 * (i - OP_ENDPTCTRL) + + (is_lpm + ? ci_regs_lpm[OP_ENDPTCTRL] + : ci_regs_nolpm[OP_ENDPTCTRL]); + + return 0; +} + +/** + * hw_port_test_set: writes port test mode (execute without interruption) + * @mode: new value + * + * This function returns an error code + */ +int hw_port_test_set(struct ci13xxx *ci, u8 mode) +{ + const u8 TEST_MODE_MAX = 7; + + if (mode > TEST_MODE_MAX) + return -EINVAL; + + hw_write(ci, OP_PORTSC, PORTSC_PTC, mode << __ffs(PORTSC_PTC)); + return 0; +} + +/** + * hw_port_test_get: reads port test mode value + * + * This function returns port test mode value + */ +u8 hw_port_test_get(struct ci13xxx *ci) +{ + return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> __ffs(PORTSC_PTC); +} + +static int hw_device_init(struct ci13xxx *ci, void __iomem *base) +{ + u32 reg; + + /* bank is a module variable */ + ci->hw_bank.abs = base; + + ci->hw_bank.cap = ci->hw_bank.abs; + ci->hw_bank.cap += ci->platdata->capoffset; + ci->hw_bank.op = ci->hw_bank.cap + (ioread32(ci->hw_bank.cap) & 0xff); + + hw_alloc_regmap(ci, false); + reg = hw_read(ci, CAP_HCCPARAMS, HCCPARAMS_LEN) >> + __ffs(HCCPARAMS_LEN); + ci->hw_bank.lpm = reg; + hw_alloc_regmap(ci, !!reg); + ci->hw_bank.size = ci->hw_bank.op - ci->hw_bank.abs; + ci->hw_bank.size += OP_LAST; + ci->hw_bank.size /= sizeof(u32); + + reg = hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DEN) >> + __ffs(DCCPARAMS_DEN); + ci->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ + + if (ci->hw_ep_max > ENDPT_MAX) + return -ENODEV; + + dev_dbg(ci->dev, "ChipIdea HDRC found, lpm: %d; cap: %p op: %p\n", + ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op); + + /* setup lock mode ? */ + + /* ENDPTSETUPSTAT is '0' by default */ + + /* HCSPARAMS.bf.ppc SHOULD BE zero for device */ + + return 0; +} + +/** + * hw_device_reset: resets chip (execute without interruption) + * @ci: the controller + * + * This function returns an error code + */ +int hw_device_reset(struct ci13xxx *ci, u32 mode) +{ + /* should flush & stop before reset */ + hw_write(ci, OP_ENDPTFLUSH, ~0, ~0); + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + hw_write(ci, OP_USBCMD, USBCMD_RST, USBCMD_RST); + while (hw_read(ci, OP_USBCMD, USBCMD_RST)) + udelay(10); /* not RTOS friendly */ + + + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI13XXX_CONTROLLER_RESET_EVENT); + + if (ci->platdata->flags & CI13XXX_DISABLE_STREAMING) + hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); + + /* USBMODE should be configured step by step */ + hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); + hw_write(ci, OP_USBMODE, USBMODE_CM, mode); + /* HW >= 2.3 */ + hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); + + if (hw_read(ci, OP_USBMODE, USBMODE_CM) != mode) { + pr_err("cannot enter in %s mode", ci_role(ci)->name); + pr_err("lpm = %i", ci->hw_bank.lpm); + return -ENODEV; + } + + return 0; +} + +/** + * ci_otg_role - pick role based on ID pin state + * @ci: the controller + */ +static enum ci_role ci_otg_role(struct ci13xxx *ci) +{ + u32 sts = hw_read(ci, OP_OTGSC, ~0); + enum ci_role role = sts & OTGSC_ID + ? CI_ROLE_GADGET + : CI_ROLE_HOST; + + return role; +} + +/** + * ci_role_work - perform role changing based on ID pin + * @work: work struct + */ +static void ci_role_work(struct work_struct *work) +{ + struct ci13xxx *ci = container_of(work, struct ci13xxx, work); + enum ci_role role = ci_otg_role(ci); + + if (role != ci->role) { + dev_dbg(ci->dev, "switching from %s to %s\n", + ci_role(ci)->name, ci->roles[role]->name); + + ci_role_stop(ci); + ci_role_start(ci, role); + } + + enable_irq(ci->irq); +} + +static irqreturn_t ci_irq(int irq, void *data) +{ + struct ci13xxx *ci = data; + irqreturn_t ret = IRQ_NONE; + u32 otgsc = 0; + + if (ci->is_otg) + otgsc = hw_read(ci, OP_OTGSC, ~0); + + if (ci->role != CI_ROLE_END) + ret = ci_role(ci)->irq(ci); + + if (ci->is_otg && (otgsc & OTGSC_IDIS)) { + hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS); + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + ret = IRQ_HANDLED; + } + + return ret; +} + +static DEFINE_IDA(ci_ida); + +struct platform_device *ci13xxx_add_device(struct device *dev, + struct resource *res, int nres, + struct ci13xxx_platform_data *platdata) +{ + struct platform_device *pdev; + int id, ret; + + id = ida_simple_get(&ci_ida, 0, 0, GFP_KERNEL); + if (id < 0) + return ERR_PTR(id); + + pdev = platform_device_alloc("ci_hdrc", id); + if (!pdev) { + ret = -ENOMEM; + goto put_id; + } + + pdev->dev.parent = dev; + pdev->dev.dma_mask = dev->dma_mask; + pdev->dev.dma_parms = dev->dma_parms; + dma_set_coherent_mask(&pdev->dev, dev->coherent_dma_mask); + + ret = platform_device_add_resources(pdev, res, nres); + if (ret) + goto err; + + ret = platform_device_add_data(pdev, platdata, sizeof(*platdata)); + if (ret) + goto err; + + ret = platform_device_add(pdev); + if (ret) + goto err; + + return pdev; + +err: + platform_device_put(pdev); +put_id: + ida_simple_remove(&ci_ida, id); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(ci13xxx_add_device); + +void ci13xxx_remove_device(struct platform_device *pdev) +{ + int id = pdev->id; + platform_device_unregister(pdev); + ida_simple_remove(&ci_ida, id); +} +EXPORT_SYMBOL_GPL(ci13xxx_remove_device); + +static int ci_hdrc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ci13xxx *ci; + struct resource *res; + void __iomem *base; + int ret; + + if (!dev->platform_data) { + dev_err(dev, "platform data missing\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + ci = devm_kzalloc(dev, sizeof(*ci), GFP_KERNEL); + if (!ci) { + dev_err(dev, "can't allocate device\n"); + return -ENOMEM; + } + + ci->dev = dev; + ci->platdata = dev->platform_data; + if (ci->platdata->phy) + ci->transceiver = ci->platdata->phy; + else + ci->global_phy = true; + + ret = hw_device_init(ci, base); + if (ret < 0) { + dev_err(dev, "can't initialize hardware\n"); + return -ENODEV; + } + + ci->hw_bank.phys = res->start; + + ci->irq = platform_get_irq(pdev, 0); + if (ci->irq < 0) { + dev_err(dev, "missing IRQ\n"); + return -ENODEV; + } + + INIT_WORK(&ci->work, ci_role_work); + ci->wq = create_singlethread_workqueue("ci_otg"); + if (!ci->wq) { + dev_err(dev, "can't create workqueue\n"); + return -ENODEV; + } + + /* initialize role(s) before the interrupt is requested */ + ret = ci_hdrc_host_init(ci); + if (ret) + dev_info(dev, "doesn't support host\n"); + + ret = ci_hdrc_gadget_init(ci); + if (ret) + dev_info(dev, "doesn't support gadget\n"); + + if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) { + dev_err(dev, "no supported roles\n"); + ret = -ENODEV; + goto rm_wq; + } + + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { + ci->is_otg = true; + /* ID pin needs 1ms debouce time, we delay 2ms for safe */ + mdelay(2); + ci->role = ci_otg_role(ci); + } else { + ci->role = ci->roles[CI_ROLE_HOST] + ? CI_ROLE_HOST + : CI_ROLE_GADGET; + } + + ret = ci_role_start(ci, ci->role); + if (ret) { + dev_err(dev, "can't start %s role\n", ci_role(ci)->name); + ret = -ENODEV; + goto rm_wq; + } + + platform_set_drvdata(pdev, ci); + ret = request_irq(ci->irq, ci_irq, IRQF_SHARED, ci->platdata->name, + ci); + if (ret) + goto stop; + + if (ci->is_otg) + hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE); + + ret = dbg_create_files(ci); + if (!ret) + return 0; + + free_irq(ci->irq, ci); +stop: + ci_role_stop(ci); +rm_wq: + flush_workqueue(ci->wq); + destroy_workqueue(ci->wq); + + return ret; +} + +static int ci_hdrc_remove(struct platform_device *pdev) +{ + struct ci13xxx *ci = platform_get_drvdata(pdev); + + dbg_remove_files(ci); + flush_workqueue(ci->wq); + destroy_workqueue(ci->wq); + free_irq(ci->irq, ci); + ci_role_stop(ci); + + return 0; +} + +static struct platform_driver ci_hdrc_driver = { + .probe = ci_hdrc_probe, + .remove = ci_hdrc_remove, + .driver = { + .name = "ci_hdrc", + }, +}; + +module_platform_driver(ci_hdrc_driver); + +MODULE_ALIAS("platform:ci_hdrc"); +MODULE_ALIAS("platform:ci13xxx"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("David Lopo "); +MODULE_DESCRIPTION("ChipIdea HDRC Driver"); diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c new file mode 100644 index 00000000..36a7063a --- /dev/null +++ b/drivers/usb/chipidea/debug.c @@ -0,0 +1,301 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "debug.h" + +/** + * ci_device_show: prints information about device capabilities and status + */ +static int ci_device_show(struct seq_file *s, void *data) +{ + struct ci13xxx *ci = s->private; + struct usb_gadget *gadget = &ci->gadget; + + seq_printf(s, "speed = %d\n", gadget->speed); + seq_printf(s, "max_speed = %d\n", gadget->max_speed); + seq_printf(s, "is_otg = %d\n", gadget->is_otg); + seq_printf(s, "is_a_peripheral = %d\n", gadget->is_a_peripheral); + seq_printf(s, "b_hnp_enable = %d\n", gadget->b_hnp_enable); + seq_printf(s, "a_hnp_support = %d\n", gadget->a_hnp_support); + seq_printf(s, "a_alt_hnp_support = %d\n", gadget->a_alt_hnp_support); + seq_printf(s, "name = %s\n", + (gadget->name ? gadget->name : "")); + + if (!ci->driver) + return 0; + + seq_printf(s, "gadget function = %s\n", + (ci->driver->function ? ci->driver->function : "")); + seq_printf(s, "gadget max speed = %d\n", ci->driver->max_speed); + + return 0; +} + +static int ci_device_open(struct inode *inode, struct file *file) +{ + return single_open(file, ci_device_show, inode->i_private); +} + +static const struct file_operations ci_device_fops = { + .open = ci_device_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * ci_port_test_show: reads port test mode + */ +static int ci_port_test_show(struct seq_file *s, void *data) +{ + struct ci13xxx *ci = s->private; + unsigned long flags; + unsigned mode; + + spin_lock_irqsave(&ci->lock, flags); + mode = hw_port_test_get(ci); + spin_unlock_irqrestore(&ci->lock, flags); + + seq_printf(s, "mode = %u\n", mode); + + return 0; +} + +/** + * ci_port_test_write: writes port test mode + */ +static ssize_t ci_port_test_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct ci13xxx *ci = s->private; + unsigned long flags; + unsigned mode; + char buf[32]; + int ret; + + if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (sscanf(buf, "%u", &mode) != 1) + return -EINVAL; + + spin_lock_irqsave(&ci->lock, flags); + ret = hw_port_test_set(ci, mode); + spin_unlock_irqrestore(&ci->lock, flags); + + return ret ? ret : count; +} + +static int ci_port_test_open(struct inode *inode, struct file *file) +{ + return single_open(file, ci_port_test_show, inode->i_private); +} + +static const struct file_operations ci_port_test_fops = { + .open = ci_port_test_open, + .write = ci_port_test_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * ci_qheads_show: DMA contents of all queue heads + */ +static int ci_qheads_show(struct seq_file *s, void *data) +{ + struct ci13xxx *ci = s->private; + unsigned long flags; + unsigned i, j; + + if (ci->role != CI_ROLE_GADGET) { + seq_printf(s, "not in gadget mode\n"); + return 0; + } + + spin_lock_irqsave(&ci->lock, flags); + for (i = 0; i < ci->hw_ep_max/2; i++) { + struct ci13xxx_ep *mEpRx = &ci->ci13xxx_ep[i]; + struct ci13xxx_ep *mEpTx = + &ci->ci13xxx_ep[i + ci->hw_ep_max/2]; + seq_printf(s, "EP=%02i: RX=%08X TX=%08X\n", + i, (u32)mEpRx->qh.dma, (u32)mEpTx->qh.dma); + for (j = 0; j < (sizeof(struct ci13xxx_qh)/sizeof(u32)); j++) + seq_printf(s, " %04X: %08X %08X\n", j, + *((u32 *)mEpRx->qh.ptr + j), + *((u32 *)mEpTx->qh.ptr + j)); + } + spin_unlock_irqrestore(&ci->lock, flags); + + return 0; +} + +static int ci_qheads_open(struct inode *inode, struct file *file) +{ + return single_open(file, ci_qheads_show, inode->i_private); +} + +static const struct file_operations ci_qheads_fops = { + .open = ci_qheads_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * ci_requests_show: DMA contents of all requests currently queued (all endpts) + */ +static int ci_requests_show(struct seq_file *s, void *data) +{ + struct ci13xxx *ci = s->private; + unsigned long flags; + struct list_head *ptr = NULL; + struct ci13xxx_req *req = NULL; + unsigned i, j, qsize = sizeof(struct ci13xxx_td)/sizeof(u32); + + if (ci->role != CI_ROLE_GADGET) { + seq_printf(s, "not in gadget mode\n"); + return 0; + } + + spin_lock_irqsave(&ci->lock, flags); + for (i = 0; i < ci->hw_ep_max; i++) + list_for_each(ptr, &ci->ci13xxx_ep[i].qh.queue) { + req = list_entry(ptr, struct ci13xxx_req, queue); + + seq_printf(s, "EP=%02i: TD=%08X %s\n", + i % (ci->hw_ep_max / 2), (u32)req->dma, + ((i < ci->hw_ep_max/2) ? "RX" : "TX")); + + for (j = 0; j < qsize; j++) + seq_printf(s, " %04X: %08X\n", j, + *((u32 *)req->ptr + j)); + } + spin_unlock_irqrestore(&ci->lock, flags); + + return 0; +} + +static int ci_requests_open(struct inode *inode, struct file *file) +{ + return single_open(file, ci_requests_show, inode->i_private); +} + +static const struct file_operations ci_requests_fops = { + .open = ci_requests_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int ci_role_show(struct seq_file *s, void *data) +{ + struct ci13xxx *ci = s->private; + + seq_printf(s, "%s\n", ci_role(ci)->name); + + return 0; +} + +static ssize_t ci_role_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct ci13xxx *ci = s->private; + enum ci_role role; + char buf[8]; + int ret; + + if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++) + if (ci->roles[role] && + !strncmp(buf, ci->roles[role]->name, + strlen(ci->roles[role]->name))) + break; + + if (role == CI_ROLE_END || role == ci->role) + return -EINVAL; + + ci_role_stop(ci); + ret = ci_role_start(ci, role); + + return ret ? ret : count; +} + +static int ci_role_open(struct inode *inode, struct file *file) +{ + return single_open(file, ci_role_show, inode->i_private); +} + +static const struct file_operations ci_role_fops = { + .open = ci_role_open, + .write = ci_role_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * dbg_create_files: initializes the attribute interface + * @ci: device + * + * This function returns an error code + */ +int dbg_create_files(struct ci13xxx *ci) +{ + struct dentry *dent; + + ci->debugfs = debugfs_create_dir(dev_name(ci->dev), NULL); + if (!ci->debugfs) + return -ENOMEM; + + dent = debugfs_create_file("device", S_IRUGO, ci->debugfs, ci, + &ci_device_fops); + if (!dent) + goto err; + + dent = debugfs_create_file("port_test", S_IRUGO | S_IWUSR, ci->debugfs, + ci, &ci_port_test_fops); + if (!dent) + goto err; + + dent = debugfs_create_file("qheads", S_IRUGO, ci->debugfs, ci, + &ci_qheads_fops); + if (!dent) + goto err; + + dent = debugfs_create_file("requests", S_IRUGO, ci->debugfs, ci, + &ci_requests_fops); + if (!dent) + goto err; + + dent = debugfs_create_file("role", S_IRUGO | S_IWUSR, ci->debugfs, ci, + &ci_role_fops); + if (dent) + return 0; +err: + debugfs_remove_recursive(ci->debugfs); + return -ENOMEM; +} + +/** + * dbg_remove_files: destroys the attribute interface + * @ci: device + */ +void dbg_remove_files(struct ci13xxx *ci) +{ + debugfs_remove_recursive(ci->debugfs); +} diff --git a/drivers/usb/chipidea/debug.h b/drivers/usb/chipidea/debug.h new file mode 100644 index 00000000..7ca6ca0a --- /dev/null +++ b/drivers/usb/chipidea/debug.h @@ -0,0 +1,30 @@ +/* + * debug.h - ChipIdea USB driver debug interfaces + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_DEBUG_H +#define __DRIVERS_USB_CHIPIDEA_DEBUG_H + +#ifdef CONFIG_USB_CHIPIDEA_DEBUG +int dbg_create_files(struct ci13xxx *ci); +void dbg_remove_files(struct ci13xxx *ci); +#else +static inline int dbg_create_files(struct ci13xxx *ci) +{ + return 0; +} + +static inline void dbg_remove_files(struct ci13xxx *ci) +{ +} +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_DEBUG_H */ diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c new file mode 100644 index 00000000..8e9d3127 --- /dev/null +++ b/drivers/usb/chipidea/host.c @@ -0,0 +1,107 @@ +/* + * host.c - ChipIdea USB host controller driver + * + * Copyright (c) 2012 Intel Corporation + * + * Author: Alexander Shishkin + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "../host/ehci.h" + +#include "ci.h" +#include "bits.h" +#include "host.h" + +static struct hc_driver __read_mostly ci_ehci_hc_driver; + +static irqreturn_t host_irq(struct ci13xxx *ci) +{ + return usb_hcd_irq(ci->irq, ci->hcd); +} + +static int host_start(struct ci13xxx *ci) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + int ret; + + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev)); + if (!hcd) + return -ENOMEM; + + dev_set_drvdata(ci->dev, ci); + hcd->rsrc_start = ci->hw_bank.phys; + hcd->rsrc_len = ci->hw_bank.size; + hcd->regs = ci->hw_bank.abs; + hcd->has_tt = 1; + + hcd->power_budget = ci->platdata->power_budget; + hcd->phy = ci->transceiver; + + ehci = hcd_to_ehci(hcd); + ehci->caps = ci->hw_bank.cap; + ehci->has_hostpc = ci->hw_bank.lpm; + + ret = usb_add_hcd(hcd, 0, 0); + if (ret) + usb_put_hcd(hcd); + else + ci->hcd = hcd; + + if (ci->platdata->flags & CI13XXX_DISABLE_STREAMING) + hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); + + return ret; +} + +static void host_stop(struct ci13xxx *ci) +{ + struct usb_hcd *hcd = ci->hcd; + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +int ci_hdrc_host_init(struct ci13xxx *ci) +{ + struct ci_role_driver *rdrv; + + if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_HC)) + return -ENXIO; + + rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = host_start; + rdrv->stop = host_stop; + rdrv->irq = host_irq; + rdrv->name = "host"; + ci->roles[CI_ROLE_HOST] = rdrv; + + ehci_init_driver(&ci_ehci_hc_driver, NULL); + + return 0; +} diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h new file mode 100644 index 00000000..761fb1fd --- /dev/null +++ b/drivers/usb/chipidea/host.h @@ -0,0 +1,17 @@ +#ifndef __DRIVERS_USB_CHIPIDEA_HOST_H +#define __DRIVERS_USB_CHIPIDEA_HOST_H + +#ifdef CONFIG_USB_CHIPIDEA_HOST + +int ci_hdrc_host_init(struct ci13xxx *ci); + +#else + +static inline int ci_hdrc_host_init(struct ci13xxx *ci) +{ + return -ENXIO; +} + +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c new file mode 100644 index 00000000..f1cab425 --- /dev/null +++ b/drivers/usb/chipidea/udc.c @@ -0,0 +1,1786 @@ +/* + * udc.c - ChipIdea UDC driver + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "debug.h" + +/* control endpoint description */ +static const struct usb_endpoint_descriptor +ctrl_endpt_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), +}; + +static const struct usb_endpoint_descriptor +ctrl_endpt_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), +}; + +/** + * hw_ep_bit: calculates the bit number + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns bit number + */ +static inline int hw_ep_bit(int num, int dir) +{ + return num + (dir ? 16 : 0); +} + +static inline int ep_to_bit(struct ci13xxx *ci, int n) +{ + int fill = 16 - ci->hw_ep_max / 2; + + if (n >= ci->hw_ep_max / 2) + n += fill; + + return n; +} + +/** + * hw_device_state: enables/disables interrupts (execute without interruption) + * @dma: 0 => disable, !0 => enable and set dma engine + * + * This function returns an error code + */ +static int hw_device_state(struct ci13xxx *ci, u32 dma) +{ + if (dma) { + hw_write(ci, OP_ENDPTLISTADDR, ~0, dma); + /* interrupt, error, port change, reset, sleep/suspend */ + hw_write(ci, OP_USBINTR, ~0, + USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); + } else { + hw_write(ci, OP_USBINTR, ~0, 0); + } + return 0; +} + +/** + * hw_ep_flush: flush endpoint fifo (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_flush(struct ci13xxx *ci, int num, int dir) +{ + int n = hw_ep_bit(num, dir); + + do { + /* flush any pending transfer */ + hw_write(ci, OP_ENDPTFLUSH, ~0, BIT(n)); + while (hw_read(ci, OP_ENDPTFLUSH, BIT(n))) + cpu_relax(); + } while (hw_read(ci, OP_ENDPTSTAT, BIT(n))); + + return 0; +} + +/** + * hw_ep_disable: disables endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_disable(struct ci13xxx *ci, int num, int dir) +{ + hw_ep_flush(ci, num, dir); + hw_write(ci, OP_ENDPTCTRL + num, + dir ? ENDPTCTRL_TXE : ENDPTCTRL_RXE, 0); + return 0; +} + +/** + * hw_ep_enable: enables endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @type: endpoint type + * + * This function returns an error code + */ +static int hw_ep_enable(struct ci13xxx *ci, int num, int dir, int type) +{ + u32 mask, data; + + if (dir) { + mask = ENDPTCTRL_TXT; /* type */ + data = type << __ffs(mask); + + mask |= ENDPTCTRL_TXS; /* unstall */ + mask |= ENDPTCTRL_TXR; /* reset data toggle */ + data |= ENDPTCTRL_TXR; + mask |= ENDPTCTRL_TXE; /* enable */ + data |= ENDPTCTRL_TXE; + } else { + mask = ENDPTCTRL_RXT; /* type */ + data = type << __ffs(mask); + + mask |= ENDPTCTRL_RXS; /* unstall */ + mask |= ENDPTCTRL_RXR; /* reset data toggle */ + data |= ENDPTCTRL_RXR; + mask |= ENDPTCTRL_RXE; /* enable */ + data |= ENDPTCTRL_RXE; + } + hw_write(ci, OP_ENDPTCTRL + num, mask, data); + return 0; +} + +/** + * hw_ep_get_halt: return endpoint halt status + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns 1 if endpoint halted + */ +static int hw_ep_get_halt(struct ci13xxx *ci, int num, int dir) +{ + u32 mask = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + + return hw_read(ci, OP_ENDPTCTRL + num, mask) ? 1 : 0; +} + +/** + * hw_test_and_clear_setup_status: test & clear setup status (execute without + * interruption) + * @n: endpoint number + * + * This function returns setup status + */ +static int hw_test_and_clear_setup_status(struct ci13xxx *ci, int n) +{ + n = ep_to_bit(ci, n); + return hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(n)); +} + +/** + * hw_ep_prime: primes endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @is_ctrl: true if control endpoint + * + * This function returns an error code + */ +static int hw_ep_prime(struct ci13xxx *ci, int num, int dir, int is_ctrl) +{ + int n = hw_ep_bit(num, dir); + + if (is_ctrl && dir == RX && hw_read(ci, OP_ENDPTSETUPSTAT, BIT(num))) + return -EAGAIN; + + hw_write(ci, OP_ENDPTPRIME, ~0, BIT(n)); + + while (hw_read(ci, OP_ENDPTPRIME, BIT(n))) + cpu_relax(); + if (is_ctrl && dir == RX && hw_read(ci, OP_ENDPTSETUPSTAT, BIT(num))) + return -EAGAIN; + + /* status shoult be tested according with manual but it doesn't work */ + return 0; +} + +/** + * hw_ep_set_halt: configures ep halt & resets data toggle after clear (execute + * without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @value: true => stall, false => unstall + * + * This function returns an error code + */ +static int hw_ep_set_halt(struct ci13xxx *ci, int num, int dir, int value) +{ + if (value != 0 && value != 1) + return -EINVAL; + + do { + enum ci13xxx_regs reg = OP_ENDPTCTRL + num; + u32 mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + u32 mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; + + /* data toggle - reserved for EP0 but it's in ESS */ + hw_write(ci, reg, mask_xs|mask_xr, + value ? mask_xs : mask_xr); + } while (value != hw_ep_get_halt(ci, num, dir)); + + return 0; +} + +/** + * hw_is_port_high_speed: test if port is high speed + * + * This function returns true if high speed port + */ +static int hw_port_is_high_speed(struct ci13xxx *ci) +{ + return ci->hw_bank.lpm ? hw_read(ci, OP_DEVLC, DEVLC_PSPD) : + hw_read(ci, OP_PORTSC, PORTSC_HSP); +} + +/** + * hw_read_intr_enable: returns interrupt enable register + * + * This function returns register data + */ +static u32 hw_read_intr_enable(struct ci13xxx *ci) +{ + return hw_read(ci, OP_USBINTR, ~0); +} + +/** + * hw_read_intr_status: returns interrupt status register + * + * This function returns register data + */ +static u32 hw_read_intr_status(struct ci13xxx *ci) +{ + return hw_read(ci, OP_USBSTS, ~0); +} + +/** + * hw_test_and_clear_complete: test & clear complete status (execute without + * interruption) + * @n: endpoint number + * + * This function returns complete status + */ +static int hw_test_and_clear_complete(struct ci13xxx *ci, int n) +{ + n = ep_to_bit(ci, n); + return hw_test_and_clear(ci, OP_ENDPTCOMPLETE, BIT(n)); +} + +/** + * hw_test_and_clear_intr_active: test & clear active interrupts (execute + * without interruption) + * + * This function returns active interrutps + */ +static u32 hw_test_and_clear_intr_active(struct ci13xxx *ci) +{ + u32 reg = hw_read_intr_status(ci) & hw_read_intr_enable(ci); + + hw_write(ci, OP_USBSTS, ~0, reg); + return reg; +} + +/** + * hw_test_and_clear_setup_guard: test & clear setup guard (execute without + * interruption) + * + * This function returns guard value + */ +static int hw_test_and_clear_setup_guard(struct ci13xxx *ci) +{ + return hw_test_and_write(ci, OP_USBCMD, USBCMD_SUTW, 0); +} + +/** + * hw_test_and_set_setup_guard: test & set setup guard (execute without + * interruption) + * + * This function returns guard value + */ +static int hw_test_and_set_setup_guard(struct ci13xxx *ci) +{ + return hw_test_and_write(ci, OP_USBCMD, USBCMD_SUTW, USBCMD_SUTW); +} + +/** + * hw_usb_set_address: configures USB address (execute without interruption) + * @value: new USB address + * + * This function explicitly sets the address, without the "USBADRA" (advance) + * feature, which is not supported by older versions of the controller. + */ +static void hw_usb_set_address(struct ci13xxx *ci, u8 value) +{ + hw_write(ci, OP_DEVICEADDR, DEVICEADDR_USBADR, + value << __ffs(DEVICEADDR_USBADR)); +} + +/** + * hw_usb_reset: restart device after a bus reset (execute without + * interruption) + * + * This function returns an error code + */ +static int hw_usb_reset(struct ci13xxx *ci) +{ + hw_usb_set_address(ci, 0); + + /* ESS flushes only at end?!? */ + hw_write(ci, OP_ENDPTFLUSH, ~0, ~0); + + /* clear setup token semaphores */ + hw_write(ci, OP_ENDPTSETUPSTAT, 0, 0); + + /* clear complete status */ + hw_write(ci, OP_ENDPTCOMPLETE, 0, 0); + + /* wait until all bits cleared */ + while (hw_read(ci, OP_ENDPTPRIME, ~0)) + udelay(10); /* not RTOS friendly */ + + /* reset all endpoints ? */ + + /* reset internal status and wait for further instructions + no need to verify the port reset status (ESS does it) */ + + return 0; +} + +/****************************************************************************** + * UTIL block + *****************************************************************************/ +/** + * _usb_addr: calculates endpoint address from direction & number + * @ep: endpoint + */ +static inline u8 _usb_addr(struct ci13xxx_ep *ep) +{ + return ((ep->dir == TX) ? USB_ENDPOINT_DIR_MASK : 0) | ep->num; +} + +/** + * _hardware_queue: configures a request at hardware level + * @gadget: gadget + * @mEp: endpoint + * + * This function returns an error code + */ +static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +{ + struct ci13xxx *ci = mEp->ci; + unsigned i; + int ret = 0; + unsigned length = mReq->req.length; + + /* don't queue twice */ + if (mReq->req.status == -EALREADY) + return -EALREADY; + + mReq->req.status = -EALREADY; + + if (mReq->req.zero && length && (length % mEp->ep.maxpacket == 0)) { + mReq->zptr = dma_pool_alloc(mEp->td_pool, GFP_ATOMIC, + &mReq->zdma); + if (mReq->zptr == NULL) + return -ENOMEM; + + memset(mReq->zptr, 0, sizeof(*mReq->zptr)); + mReq->zptr->next = cpu_to_le32(TD_TERMINATE); + mReq->zptr->token = cpu_to_le32(TD_STATUS_ACTIVE); + if (!mReq->req.no_interrupt) + mReq->zptr->token |= cpu_to_le32(TD_IOC); + } + ret = usb_gadget_map_request(&ci->gadget, &mReq->req, mEp->dir); + if (ret) + return ret; + + /* + * TD configuration + * TODO - handle requests which spawns into several TDs + */ + memset(mReq->ptr, 0, sizeof(*mReq->ptr)); + mReq->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES)); + mReq->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES); + mReq->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE); + if (mReq->zptr) { + mReq->ptr->next = cpu_to_le32(mReq->zdma); + } else { + mReq->ptr->next = cpu_to_le32(TD_TERMINATE); + if (!mReq->req.no_interrupt) + mReq->ptr->token |= cpu_to_le32(TD_IOC); + } + mReq->ptr->page[0] = cpu_to_le32(mReq->req.dma); + for (i = 1; i < TD_PAGE_COUNT; i++) { + u32 page = mReq->req.dma + i * CI13XXX_PAGE_SIZE; + page &= ~TD_RESERVED_MASK; + mReq->ptr->page[i] = cpu_to_le32(page); + } + + wmb(); + + if (!list_empty(&mEp->qh.queue)) { + struct ci13xxx_req *mReqPrev; + int n = hw_ep_bit(mEp->num, mEp->dir); + int tmp_stat; + u32 next = mReq->dma & TD_ADDR_MASK; + + mReqPrev = list_entry(mEp->qh.queue.prev, + struct ci13xxx_req, queue); + if (mReqPrev->zptr) + mReqPrev->zptr->next = cpu_to_le32(next); + else + mReqPrev->ptr->next = cpu_to_le32(next); + wmb(); + if (hw_read(ci, OP_ENDPTPRIME, BIT(n))) + goto done; + do { + hw_write(ci, OP_USBCMD, USBCMD_ATDTW, USBCMD_ATDTW); + tmp_stat = hw_read(ci, OP_ENDPTSTAT, BIT(n)); + } while (!hw_read(ci, OP_USBCMD, USBCMD_ATDTW)); + hw_write(ci, OP_USBCMD, USBCMD_ATDTW, 0); + if (tmp_stat) + goto done; + } + + /* QH configuration */ + mEp->qh.ptr->td.next = cpu_to_le32(mReq->dma); /* TERMINATE = 0 */ + mEp->qh.ptr->td.token &= + cpu_to_le32(~(TD_STATUS_HALTED|TD_STATUS_ACTIVE)); + + wmb(); /* synchronize before ep prime */ + + ret = hw_ep_prime(ci, mEp->num, mEp->dir, + mEp->type == USB_ENDPOINT_XFER_CONTROL); +done: + return ret; +} + +/** + * _hardware_dequeue: handles a request at hardware level + * @gadget: gadget + * @mEp: endpoint + * + * This function returns an error code + */ +static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +{ + u32 tmptoken = le32_to_cpu(mReq->ptr->token); + + if (mReq->req.status != -EALREADY) + return -EINVAL; + + if ((TD_STATUS_ACTIVE & tmptoken) != 0) + return -EBUSY; + + if (mReq->zptr) { + if ((cpu_to_le32(TD_STATUS_ACTIVE) & mReq->zptr->token) != 0) + return -EBUSY; + dma_pool_free(mEp->td_pool, mReq->zptr, mReq->zdma); + mReq->zptr = NULL; + } + + mReq->req.status = 0; + + usb_gadget_unmap_request(&mEp->ci->gadget, &mReq->req, mEp->dir); + + mReq->req.status = tmptoken & TD_STATUS; + if ((TD_STATUS_HALTED & mReq->req.status) != 0) + mReq->req.status = -1; + else if ((TD_STATUS_DT_ERR & mReq->req.status) != 0) + mReq->req.status = -1; + else if ((TD_STATUS_TR_ERR & mReq->req.status) != 0) + mReq->req.status = -1; + + mReq->req.actual = tmptoken & TD_TOTAL_BYTES; + mReq->req.actual >>= __ffs(TD_TOTAL_BYTES); + mReq->req.actual = mReq->req.length - mReq->req.actual; + mReq->req.actual = mReq->req.status ? 0 : mReq->req.actual; + + return mReq->req.actual; +} + +/** + * _ep_nuke: dequeues all endpoint requests + * @mEp: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int _ep_nuke(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + if (mEp == NULL) + return -EINVAL; + + hw_ep_flush(mEp->ci, mEp->num, mEp->dir); + + while (!list_empty(&mEp->qh.queue)) { + + /* pop oldest request */ + struct ci13xxx_req *mReq = \ + list_entry(mEp->qh.queue.next, + struct ci13xxx_req, queue); + + if (mReq->zptr) { + dma_pool_free(mEp->td_pool, mReq->zptr, mReq->zdma); + mReq->zptr = NULL; + } + + list_del_init(&mReq->queue); + mReq->req.status = -ESHUTDOWN; + + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + mReq->req.complete(&mEp->ep, &mReq->req); + spin_lock(mEp->lock); + } + } + return 0; +} + +/** + * _gadget_stop_activity: stops all USB activity, flushes & disables all endpts + * @gadget: gadget + * + * This function returns an error code + */ +static int _gadget_stop_activity(struct usb_gadget *gadget) +{ + struct usb_ep *ep; + struct ci13xxx *ci = container_of(gadget, struct ci13xxx, gadget); + unsigned long flags; + + spin_lock_irqsave(&ci->lock, flags); + ci->gadget.speed = USB_SPEED_UNKNOWN; + ci->remote_wakeup = 0; + ci->suspended = 0; + spin_unlock_irqrestore(&ci->lock, flags); + + /* flush all endpoints */ + gadget_for_each_ep(ep, gadget) { + usb_ep_fifo_flush(ep); + } + usb_ep_fifo_flush(&ci->ep0out->ep); + usb_ep_fifo_flush(&ci->ep0in->ep); + + if (ci->driver) + ci->driver->disconnect(gadget); + + /* make sure to disable all endpoints */ + gadget_for_each_ep(ep, gadget) { + usb_ep_disable(ep); + } + + if (ci->status != NULL) { + usb_ep_free_request(&ci->ep0in->ep, ci->status); + ci->status = NULL; + } + + return 0; +} + +/****************************************************************************** + * ISR block + *****************************************************************************/ +/** + * isr_reset_handler: USB reset interrupt handler + * @ci: UDC device + * + * This function resets USB engine after a bus reset occurred + */ +static void isr_reset_handler(struct ci13xxx *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ + int retval; + + spin_unlock(&ci->lock); + retval = _gadget_stop_activity(&ci->gadget); + if (retval) + goto done; + + retval = hw_usb_reset(ci); + if (retval) + goto done; + + ci->status = usb_ep_alloc_request(&ci->ep0in->ep, GFP_ATOMIC); + if (ci->status == NULL) + retval = -ENOMEM; + +done: + spin_lock(&ci->lock); + + if (retval) + dev_err(ci->dev, "error: %i\n", retval); +} + +/** + * isr_get_status_complete: get_status request complete function + * @ep: endpoint + * @req: request handled + * + * Caller must release lock + */ +static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (ep == NULL || req == NULL) + return; + + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +/** + * _ep_queue: queues (submits) an I/O request to an endpoint + * + * Caller must hold lock + */ +static int _ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t __maybe_unused gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + struct ci13xxx *ci = mEp->ci; + int retval = 0; + + if (ep == NULL || req == NULL || mEp->ep.desc == NULL) + return -EINVAL; + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + if (req->length) + mEp = (ci->ep0_dir == RX) ? + ci->ep0out : ci->ep0in; + if (!list_empty(&mEp->qh.queue)) { + _ep_nuke(mEp); + retval = -EOVERFLOW; + dev_warn(mEp->ci->dev, "endpoint ctrl %X nuked\n", + _usb_addr(mEp)); + } + } + + /* first nuke then test link, e.g. previous status has not sent */ + if (!list_empty(&mReq->queue)) { + dev_err(mEp->ci->dev, "request already in queue\n"); + return -EBUSY; + } + + if (req->length > (TD_PAGE_COUNT - 1) * CI13XXX_PAGE_SIZE) { + dev_err(mEp->ci->dev, "request bigger than one td\n"); + return -EMSGSIZE; + } + + /* push request */ + mReq->req.status = -EINPROGRESS; + mReq->req.actual = 0; + + retval = _hardware_enqueue(mEp, mReq); + + if (retval == -EALREADY) + retval = 0; + if (!retval) + list_add_tail(&mReq->queue, &mEp->qh.queue); + + return retval; +} + +/** + * isr_get_status_response: get_status request response + * @ci: ci struct + * @setup: setup request packet + * + * This function returns an error code + */ +static int isr_get_status_response(struct ci13xxx *ci, + struct usb_ctrlrequest *setup) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + struct ci13xxx_ep *mEp = ci->ep0in; + struct usb_request *req = NULL; + gfp_t gfp_flags = GFP_ATOMIC; + int dir, num, retval; + + if (mEp == NULL || setup == NULL) + return -EINVAL; + + spin_unlock(mEp->lock); + req = usb_ep_alloc_request(&mEp->ep, gfp_flags); + spin_lock(mEp->lock); + if (req == NULL) + return -ENOMEM; + + req->complete = isr_get_status_complete; + req->length = 2; + req->buf = kzalloc(req->length, gfp_flags); + if (req->buf == NULL) { + retval = -ENOMEM; + goto err_free_req; + } + + if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* Assume that device is bus powered for now. */ + *(u16 *)req->buf = ci->remote_wakeup << 1; + retval = 0; + } else if ((setup->bRequestType & USB_RECIP_MASK) \ + == USB_RECIP_ENDPOINT) { + dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? + TX : RX; + num = le16_to_cpu(setup->wIndex) & USB_ENDPOINT_NUMBER_MASK; + *(u16 *)req->buf = hw_ep_get_halt(ci, num, dir); + } + /* else do nothing; reserved for future use */ + + retval = _ep_queue(&mEp->ep, req, gfp_flags); + if (retval) + goto err_free_buf; + + return 0; + + err_free_buf: + kfree(req->buf); + err_free_req: + spin_unlock(mEp->lock); + usb_ep_free_request(&mEp->ep, req); + spin_lock(mEp->lock); + return retval; +} + +/** + * isr_setup_status_complete: setup_status request complete function + * @ep: endpoint + * @req: request handled + * + * Caller must release lock. Put the port in test mode if test mode + * feature is selected. + */ +static void +isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx *ci = req->context; + unsigned long flags; + + if (ci->setaddr) { + hw_usb_set_address(ci, ci->address); + ci->setaddr = false; + } + + spin_lock_irqsave(&ci->lock, flags); + if (ci->test_mode) + hw_port_test_set(ci, ci->test_mode); + spin_unlock_irqrestore(&ci->lock, flags); +} + +/** + * isr_setup_status_phase: queues the status phase of a setup transation + * @ci: ci struct + * + * This function returns an error code + */ +static int isr_setup_status_phase(struct ci13xxx *ci) +{ + int retval; + struct ci13xxx_ep *mEp; + + mEp = (ci->ep0_dir == TX) ? ci->ep0out : ci->ep0in; + ci->status->context = ci; + ci->status->complete = isr_setup_status_complete; + + retval = _ep_queue(&mEp->ep, ci->status, GFP_ATOMIC); + + return retval; +} + +/** + * isr_tr_complete_low: transaction complete low level handler + * @mEp: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int isr_tr_complete_low(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + struct ci13xxx_req *mReq, *mReqTemp; + struct ci13xxx_ep *mEpTemp = mEp; + int retval = 0; + + list_for_each_entry_safe(mReq, mReqTemp, &mEp->qh.queue, + queue) { + retval = _hardware_dequeue(mEp, mReq); + if (retval < 0) + break; + list_del_init(&mReq->queue); + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && + mReq->req.length) + mEpTemp = mEp->ci->ep0in; + mReq->req.complete(&mEpTemp->ep, &mReq->req); + spin_lock(mEp->lock); + } + } + + if (retval == -EBUSY) + retval = 0; + + return retval; +} + +/** + * isr_tr_complete_handler: transaction complete interrupt handler + * @ci: UDC descriptor + * + * This function handles traffic events + */ +static void isr_tr_complete_handler(struct ci13xxx *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ + unsigned i; + u8 tmode = 0; + + for (i = 0; i < ci->hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &ci->ci13xxx_ep[i]; + int type, num, dir, err = -EINVAL; + struct usb_ctrlrequest req; + + if (mEp->ep.desc == NULL) + continue; /* not configured */ + + if (hw_test_and_clear_complete(ci, i)) { + err = isr_tr_complete_low(mEp); + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + if (err > 0) /* needs status phase */ + err = isr_setup_status_phase(ci); + if (err < 0) { + spin_unlock(&ci->lock); + if (usb_ep_set_halt(&mEp->ep)) + dev_err(ci->dev, + "error: ep_set_halt\n"); + spin_lock(&ci->lock); + } + } + } + + if (mEp->type != USB_ENDPOINT_XFER_CONTROL || + !hw_test_and_clear_setup_status(ci, i)) + continue; + + if (i != 0) { + dev_warn(ci->dev, "ctrl traffic at endpoint %d\n", i); + continue; + } + + /* + * Flush data and handshake transactions of previous + * setup packet. + */ + _ep_nuke(ci->ep0out); + _ep_nuke(ci->ep0in); + + /* read_setup_packet */ + do { + hw_test_and_set_setup_guard(ci); + memcpy(&req, &mEp->qh.ptr->setup, sizeof(req)); + } while (!hw_test_and_clear_setup_guard(ci)); + + type = req.bRequestType; + + ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; + + switch (req.bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += ci->hw_ep_max/2; + if (!ci->ci13xxx_ep[num].wedge) { + spin_unlock(&ci->lock); + err = usb_ep_clear_halt( + &ci->ci13xxx_ep[num].ep); + spin_lock(&ci->lock); + if (err) + break; + } + err = isr_setup_status_phase(ci); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && + le16_to_cpu(req.wValue) == + USB_DEVICE_REMOTE_WAKEUP) { + if (req.wLength != 0) + break; + ci->remote_wakeup = 0; + err = isr_setup_status_phase(ci); + } else { + goto delegate; + } + break; + case USB_REQ_GET_STATUS: + if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && + type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && + type != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 2 || + le16_to_cpu(req.wValue) != 0) + break; + err = isr_get_status_response(ci, &req); + break; + case USB_REQ_SET_ADDRESS: + if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 0 || + le16_to_cpu(req.wIndex) != 0) + break; + ci->address = (u8)le16_to_cpu(req.wValue); + ci->setaddr = true; + err = isr_setup_status_phase(ci); + break; + case USB_REQ_SET_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += ci->hw_ep_max/2; + + spin_unlock(&ci->lock); + err = usb_ep_set_halt(&ci->ci13xxx_ep[num].ep); + spin_lock(&ci->lock); + if (!err) + isr_setup_status_phase(ci); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { + if (req.wLength != 0) + break; + switch (le16_to_cpu(req.wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + ci->remote_wakeup = 1; + err = isr_setup_status_phase(ci); + break; + case USB_DEVICE_TEST_MODE: + tmode = le16_to_cpu(req.wIndex) >> 8; + switch (tmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + ci->test_mode = tmode; + err = isr_setup_status_phase( + ci); + break; + default: + break; + } + default: + goto delegate; + } + } else { + goto delegate; + } + break; + default: +delegate: + if (req.wLength == 0) /* no data phase */ + ci->ep0_dir = TX; + + spin_unlock(&ci->lock); + err = ci->driver->setup(&ci->gadget, &req); + spin_lock(&ci->lock); + break; + } + + if (err < 0) { + spin_unlock(&ci->lock); + if (usb_ep_set_halt(&mEp->ep)) + dev_err(ci->dev, "error: ep_set_halt\n"); + spin_lock(&ci->lock); + } + } +} + +/****************************************************************************** + * ENDPT block + *****************************************************************************/ +/** + * ep_enable: configure endpoint, making it usable + * + * Check usb_ep_enable() at "usb_gadget.h" for details + */ +static int ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int retval = 0; + unsigned long flags; + u32 cap = 0; + + if (ep == NULL || desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + /* only internal SW should enable ctrl endpts */ + + mEp->ep.desc = desc; + + if (!list_empty(&mEp->qh.queue)) + dev_warn(mEp->ci->dev, "enabling a non-empty endpoint!\n"); + + mEp->dir = usb_endpoint_dir_in(desc) ? TX : RX; + mEp->num = usb_endpoint_num(desc); + mEp->type = usb_endpoint_type(desc); + + mEp->ep.maxpacket = usb_endpoint_maxp(desc); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + cap |= QH_IOS; + if (mEp->num) + cap |= QH_ZLT; + cap |= (mEp->ep.maxpacket << __ffs(QH_MAX_PKT)) & QH_MAX_PKT; + + mEp->qh.ptr->cap = cpu_to_le32(cap); + + mEp->qh.ptr->td.next |= cpu_to_le32(TD_TERMINATE); /* needed? */ + + /* + * Enable endpoints in the HW other than ep0 as ep0 + * is always enabled + */ + if (mEp->num) + retval |= hw_ep_enable(mEp->ci, mEp->num, mEp->dir, mEp->type); + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_disable: endpoint is no longer usable + * + * Check usb_ep_disable() at "usb_gadget.h" for details + */ +static int ep_disable(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int direction, retval = 0; + unsigned long flags; + + if (ep == NULL) + return -EINVAL; + else if (mEp->ep.desc == NULL) + return -EBUSY; + + spin_lock_irqsave(mEp->lock, flags); + + /* only internal SW should disable ctrl endpts */ + + direction = mEp->dir; + do { + retval |= _ep_nuke(mEp); + retval |= hw_ep_disable(mEp->ci, mEp->num, mEp->dir); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + mEp->ep.desc = NULL; + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_alloc_request: allocate a request object to use with this endpoint + * + * Check usb_ep_alloc_request() at "usb_gadget.h" for details + */ +static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = NULL; + + if (ep == NULL) + return NULL; + + mReq = kzalloc(sizeof(struct ci13xxx_req), gfp_flags); + if (mReq != NULL) { + INIT_LIST_HEAD(&mReq->queue); + + mReq->ptr = dma_pool_alloc(mEp->td_pool, gfp_flags, + &mReq->dma); + if (mReq->ptr == NULL) { + kfree(mReq); + mReq = NULL; + } + } + + return (mReq == NULL) ? NULL : &mReq->req; +} + +/** + * ep_free_request: frees a request object + * + * Check usb_ep_free_request() at "usb_gadget.h" for details + */ +static void ep_free_request(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + unsigned long flags; + + if (ep == NULL || req == NULL) { + return; + } else if (!list_empty(&mReq->queue)) { + dev_err(mEp->ci->dev, "freeing queued request\n"); + return; + } + + spin_lock_irqsave(mEp->lock, flags); + + if (mReq->ptr) + dma_pool_free(mEp->td_pool, mReq->ptr, mReq->dma); + kfree(mReq); + + spin_unlock_irqrestore(mEp->lock, flags); +} + +/** + * ep_queue: queues (submits) an I/O request to an endpoint + * + * Check usb_ep_queue()* at usb_gadget.h" for details + */ +static int ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t __maybe_unused gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int retval = 0; + unsigned long flags; + + if (ep == NULL || req == NULL || mEp->ep.desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + retval = _ep_queue(ep, req, gfp_flags); + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_dequeue: dequeues (cancels, unlinks) an I/O request from an endpoint + * + * Check usb_ep_dequeue() at "usb_gadget.h" for details + */ +static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + unsigned long flags; + + if (ep == NULL || req == NULL || mReq->req.status != -EALREADY || + mEp->ep.desc == NULL || list_empty(&mReq->queue) || + list_empty(&mEp->qh.queue)) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + hw_ep_flush(mEp->ci, mEp->num, mEp->dir); + + /* pop request */ + list_del_init(&mReq->queue); + + usb_gadget_unmap_request(&mEp->ci->gadget, req, mEp->dir); + + req->status = -ECONNRESET; + + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + mReq->req.complete(&mEp->ep, &mReq->req); + spin_lock(mEp->lock); + } + + spin_unlock_irqrestore(mEp->lock, flags); + return 0; +} + +/** + * ep_set_halt: sets the endpoint halt feature + * + * Check usb_ep_set_halt() at "usb_gadget.h" for details + */ +static int ep_set_halt(struct usb_ep *ep, int value) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int direction, retval = 0; + unsigned long flags; + + if (ep == NULL || mEp->ep.desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + +#ifndef STALL_IN + /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */ + if (value && mEp->type == USB_ENDPOINT_XFER_BULK && mEp->dir == TX && + !list_empty(&mEp->qh.queue)) { + spin_unlock_irqrestore(mEp->lock, flags); + return -EAGAIN; + } +#endif + + direction = mEp->dir; + do { + retval |= hw_ep_set_halt(mEp->ci, mEp->num, mEp->dir, value); + + if (!value) + mEp->wedge = 0; + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_set_wedge: sets the halt feature and ignores clear requests + * + * Check usb_ep_set_wedge() at "usb_gadget.h" for details + */ +static int ep_set_wedge(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + unsigned long flags; + + if (ep == NULL || mEp->ep.desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + mEp->wedge = 1; + spin_unlock_irqrestore(mEp->lock, flags); + + return usb_ep_set_halt(ep); +} + +/** + * ep_fifo_flush: flushes contents of a fifo + * + * Check usb_ep_fifo_flush() at "usb_gadget.h" for details + */ +static void ep_fifo_flush(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + unsigned long flags; + + if (ep == NULL) { + dev_err(mEp->ci->dev, "%02X: -EINVAL\n", _usb_addr(mEp)); + return; + } + + spin_lock_irqsave(mEp->lock, flags); + + hw_ep_flush(mEp->ci, mEp->num, mEp->dir); + + spin_unlock_irqrestore(mEp->lock, flags); +} + +/** + * Endpoint-specific part of the API to the USB controller hardware + * Check "usb_gadget.h" for details + */ +static const struct usb_ep_ops usb_ep_ops = { + .enable = ep_enable, + .disable = ep_disable, + .alloc_request = ep_alloc_request, + .free_request = ep_free_request, + .queue = ep_queue, + .dequeue = ep_dequeue, + .set_halt = ep_set_halt, + .set_wedge = ep_set_wedge, + .fifo_flush = ep_fifo_flush, +}; + +/****************************************************************************** + * GADGET block + *****************************************************************************/ +static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct ci13xxx *ci = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + int gadget_ready = 0; + + if (!(ci->platdata->flags & CI13XXX_PULLUP_ON_VBUS)) + return -EOPNOTSUPP; + + spin_lock_irqsave(&ci->lock, flags); + ci->vbus_active = is_active; + if (ci->driver) + gadget_ready = 1; + spin_unlock_irqrestore(&ci->lock, flags); + + if (gadget_ready) { + if (is_active) { + pm_runtime_get_sync(&_gadget->dev); + hw_device_reset(ci, USBMODE_CM_DC); + hw_device_state(ci, ci->ep0out->qh.dma); + } else { + hw_device_state(ci, 0); + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI13XXX_CONTROLLER_STOPPED_EVENT); + _gadget_stop_activity(&ci->gadget); + pm_runtime_put_sync(&_gadget->dev); + } + } + + return 0; +} + +static int ci13xxx_wakeup(struct usb_gadget *_gadget) +{ + struct ci13xxx *ci = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ci->lock, flags); + if (!ci->remote_wakeup) { + ret = -EOPNOTSUPP; + goto out; + } + if (!hw_read(ci, OP_PORTSC, PORTSC_SUSP)) { + ret = -EINVAL; + goto out; + } + hw_write(ci, OP_PORTSC, PORTSC_FPR, PORTSC_FPR); +out: + spin_unlock_irqrestore(&ci->lock, flags); + return ret; +} + +static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +{ + struct ci13xxx *ci = container_of(_gadget, struct ci13xxx, gadget); + + if (ci->transceiver) + return usb_phy_set_power(ci->transceiver, mA); + return -ENOTSUPP; +} + +/* Change Data+ pullup status + * this func is used by usb_gadget_connect/disconnet + */ +static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct ci13xxx *ci = container_of(_gadget, struct ci13xxx, gadget); + + if (is_on) + hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); + else + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + return 0; +} + +static int ci13xxx_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int ci13xxx_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +/** + * Device operations part of the API to the USB controller hardware, + * which don't involve endpoints (or i/o) + * Check "usb_gadget.h" for details + */ +static const struct usb_gadget_ops usb_gadget_ops = { + .vbus_session = ci13xxx_vbus_session, + .wakeup = ci13xxx_wakeup, + .pullup = ci13xxx_pullup, + .vbus_draw = ci13xxx_vbus_draw, + .udc_start = ci13xxx_start, + .udc_stop = ci13xxx_stop, +}; + +static int init_eps(struct ci13xxx *ci) +{ + int retval = 0, i, j; + + for (i = 0; i < ci->hw_ep_max/2; i++) + for (j = RX; j <= TX; j++) { + int k = i + j * ci->hw_ep_max/2; + struct ci13xxx_ep *mEp = &ci->ci13xxx_ep[k]; + + scnprintf(mEp->name, sizeof(mEp->name), "ep%i%s", i, + (j == TX) ? "in" : "out"); + + mEp->ci = ci; + mEp->lock = &ci->lock; + mEp->td_pool = ci->td_pool; + + mEp->ep.name = mEp->name; + mEp->ep.ops = &usb_ep_ops; + /* + * for ep0: maxP defined in desc, for other + * eps, maxP is set by epautoconfig() called + * by gadget layer + */ + mEp->ep.maxpacket = (unsigned short)~0; + + INIT_LIST_HEAD(&mEp->qh.queue); + mEp->qh.ptr = dma_pool_alloc(ci->qh_pool, GFP_KERNEL, + &mEp->qh.dma); + if (mEp->qh.ptr == NULL) + retval = -ENOMEM; + else + memset(mEp->qh.ptr, 0, sizeof(*mEp->qh.ptr)); + + /* + * set up shorthands for ep0 out and in endpoints, + * don't add to gadget's ep_list + */ + if (i == 0) { + if (j == RX) + ci->ep0out = mEp; + else + ci->ep0in = mEp; + + mEp->ep.maxpacket = CTRL_PAYLOAD_MAX; + continue; + } + + list_add_tail(&mEp->ep.ep_list, &ci->gadget.ep_list); + } + + return retval; +} + +static void destroy_eps(struct ci13xxx *ci) +{ + int i; + + for (i = 0; i < ci->hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &ci->ci13xxx_ep[i]; + + dma_pool_free(ci->qh_pool, mEp->qh.ptr, mEp->qh.dma); + } +} + +/** + * ci13xxx_start: register a gadget driver + * @gadget: our gadget + * @driver: the driver being registered + * + * Interrupts are enabled here. + */ +static int ci13xxx_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ci13xxx *ci = container_of(gadget, struct ci13xxx, gadget); + unsigned long flags; + int retval = -ENOMEM; + + if (driver->disconnect == NULL) + return -EINVAL; + + + ci->ep0out->ep.desc = &ctrl_endpt_out_desc; + retval = usb_ep_enable(&ci->ep0out->ep); + if (retval) + return retval; + + ci->ep0in->ep.desc = &ctrl_endpt_in_desc; + retval = usb_ep_enable(&ci->ep0in->ep); + if (retval) + return retval; + spin_lock_irqsave(&ci->lock, flags); + + ci->driver = driver; + pm_runtime_get_sync(&ci->gadget.dev); + if (ci->platdata->flags & CI13XXX_PULLUP_ON_VBUS) { + if (ci->vbus_active) { + if (ci->platdata->flags & CI13XXX_REGS_SHARED) + hw_device_reset(ci, USBMODE_CM_DC); + } else { + pm_runtime_put_sync(&ci->gadget.dev); + goto done; + } + } + + retval = hw_device_state(ci, ci->ep0out->qh.dma); + if (retval) + pm_runtime_put_sync(&ci->gadget.dev); + + done: + spin_unlock_irqrestore(&ci->lock, flags); + return retval; +} + +/** + * ci13xxx_stop: unregister a gadget driver + */ +static int ci13xxx_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ci13xxx *ci = container_of(gadget, struct ci13xxx, gadget); + unsigned long flags; + + spin_lock_irqsave(&ci->lock, flags); + + if (!(ci->platdata->flags & CI13XXX_PULLUP_ON_VBUS) || + ci->vbus_active) { + hw_device_state(ci, 0); + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI13XXX_CONTROLLER_STOPPED_EVENT); + ci->driver = NULL; + spin_unlock_irqrestore(&ci->lock, flags); + _gadget_stop_activity(&ci->gadget); + spin_lock_irqsave(&ci->lock, flags); + pm_runtime_put(&ci->gadget.dev); + } + + spin_unlock_irqrestore(&ci->lock, flags); + + return 0; +} + +/****************************************************************************** + * BUS block + *****************************************************************************/ +/** + * udc_irq: ci interrupt handler + * + * This function returns IRQ_HANDLED if the IRQ has been handled + * It locks access to registers + */ +static irqreturn_t udc_irq(struct ci13xxx *ci) +{ + irqreturn_t retval; + u32 intr; + + if (ci == NULL) + return IRQ_HANDLED; + + spin_lock(&ci->lock); + + if (ci->platdata->flags & CI13XXX_REGS_SHARED) { + if (hw_read(ci, OP_USBMODE, USBMODE_CM) != + USBMODE_CM_DC) { + spin_unlock(&ci->lock); + return IRQ_NONE; + } + } + intr = hw_test_and_clear_intr_active(ci); + + if (intr) { + /* order defines priority - do NOT change it */ + if (USBi_URI & intr) + isr_reset_handler(ci); + + if (USBi_PCI & intr) { + ci->gadget.speed = hw_port_is_high_speed(ci) ? + USB_SPEED_HIGH : USB_SPEED_FULL; + if (ci->suspended && ci->driver->resume) { + spin_unlock(&ci->lock); + ci->driver->resume(&ci->gadget); + spin_lock(&ci->lock); + ci->suspended = 0; + } + } + + if (USBi_UI & intr) + isr_tr_complete_handler(ci); + + if (USBi_SLI & intr) { + if (ci->gadget.speed != USB_SPEED_UNKNOWN && + ci->driver->suspend) { + ci->suspended = 1; + spin_unlock(&ci->lock); + ci->driver->suspend(&ci->gadget); + spin_lock(&ci->lock); + } + } + retval = IRQ_HANDLED; + } else { + retval = IRQ_NONE; + } + spin_unlock(&ci->lock); + + return retval; +} + +/** + * udc_start: initialize gadget role + * @ci: chipidea controller + */ +static int udc_start(struct ci13xxx *ci) +{ + struct device *dev = ci->dev; + int retval = 0; + + spin_lock_init(&ci->lock); + + ci->gadget.ops = &usb_gadget_ops; + ci->gadget.speed = USB_SPEED_UNKNOWN; + ci->gadget.max_speed = USB_SPEED_HIGH; + ci->gadget.is_otg = 0; + ci->gadget.name = ci->platdata->name; + + INIT_LIST_HEAD(&ci->gadget.ep_list); + + /* alloc resources */ + ci->qh_pool = dma_pool_create("ci13xxx_qh", dev, + sizeof(struct ci13xxx_qh), + 64, CI13XXX_PAGE_SIZE); + if (ci->qh_pool == NULL) + return -ENOMEM; + + ci->td_pool = dma_pool_create("ci13xxx_td", dev, + sizeof(struct ci13xxx_td), + 64, CI13XXX_PAGE_SIZE); + if (ci->td_pool == NULL) { + retval = -ENOMEM; + goto free_qh_pool; + } + + retval = init_eps(ci); + if (retval) + goto free_pools; + + ci->gadget.ep0 = &ci->ep0in->ep; + + if (ci->global_phy) { + ci->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR(ci->transceiver)) + ci->transceiver = NULL; + } + + if (ci->platdata->flags & CI13XXX_REQUIRE_TRANSCEIVER) { + if (ci->transceiver == NULL) { + retval = -ENODEV; + goto destroy_eps; + } + } + + if (!(ci->platdata->flags & CI13XXX_REGS_SHARED)) { + retval = hw_device_reset(ci, USBMODE_CM_DC); + if (retval) + goto put_transceiver; + } + + if (ci->transceiver) { + retval = otg_set_peripheral(ci->transceiver->otg, + &ci->gadget); + if (retval) + goto put_transceiver; + } + + retval = usb_add_gadget_udc(dev, &ci->gadget); + if (retval) + goto remove_trans; + + pm_runtime_no_callbacks(&ci->gadget.dev); + pm_runtime_enable(&ci->gadget.dev); + + return retval; + +remove_trans: + if (ci->transceiver) { + otg_set_peripheral(ci->transceiver->otg, NULL); + if (ci->global_phy) + usb_put_phy(ci->transceiver); + } + + dev_err(dev, "error = %i\n", retval); +put_transceiver: + if (ci->transceiver && ci->global_phy) + usb_put_phy(ci->transceiver); +destroy_eps: + destroy_eps(ci); +free_pools: + dma_pool_destroy(ci->td_pool); +free_qh_pool: + dma_pool_destroy(ci->qh_pool); + return retval; +} + +/** + * udc_remove: parent remove must call this to remove UDC + * + * No interrupts active, the IRQ has been released + */ +static void udc_stop(struct ci13xxx *ci) +{ + if (ci == NULL) + return; + + usb_del_gadget_udc(&ci->gadget); + + destroy_eps(ci); + + dma_pool_destroy(ci->td_pool); + dma_pool_destroy(ci->qh_pool); + + if (ci->transceiver) { + otg_set_peripheral(ci->transceiver->otg, NULL); + if (ci->global_phy) + usb_put_phy(ci->transceiver); + } + /* my kobject is dynamic, I swear! */ + memset(&ci->gadget, 0, sizeof(ci->gadget)); +} + +/** + * ci_hdrc_gadget_init - initialize device related bits + * ci: the controller + * + * This function enables the gadget role, if the device is "device capable". + */ +int ci_hdrc_gadget_init(struct ci13xxx *ci) +{ + struct ci_role_driver *rdrv; + + if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC)) + return -ENXIO; + + rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = udc_start; + rdrv->stop = udc_stop; + rdrv->irq = udc_irq; + rdrv->name = "gadget"; + ci->roles[CI_ROLE_GADGET] = rdrv; + + return 0; +} diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h new file mode 100644 index 00000000..d12e8b59 --- /dev/null +++ b/drivers/usb/chipidea/udc.h @@ -0,0 +1,93 @@ +/* + * udc.h - ChipIdea UDC structures + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_UDC_H +#define __DRIVERS_USB_CHIPIDEA_UDC_H + +#include + +#define CTRL_PAYLOAD_MAX 64 +#define RX 0 /* similar to USB_DIR_OUT but can be used as an index */ +#define TX 1 /* similar to USB_DIR_IN but can be used as an index */ + +/* DMA layout of transfer descriptors */ +struct ci13xxx_td { + /* 0 */ + u32 next; +#define TD_TERMINATE BIT(0) +#define TD_ADDR_MASK (0xFFFFFFEUL << 5) + /* 1 */ + u32 token; +#define TD_STATUS (0x00FFUL << 0) +#define TD_STATUS_TR_ERR BIT(3) +#define TD_STATUS_DT_ERR BIT(5) +#define TD_STATUS_HALTED BIT(6) +#define TD_STATUS_ACTIVE BIT(7) +#define TD_MULTO (0x0003UL << 10) +#define TD_IOC BIT(15) +#define TD_TOTAL_BYTES (0x7FFFUL << 16) + /* 2 */ + u32 page[5]; +#define TD_CURR_OFFSET (0x0FFFUL << 0) +#define TD_FRAME_NUM (0x07FFUL << 0) +#define TD_RESERVED_MASK (0x0FFFUL << 0) +} __attribute__ ((packed, aligned(4))); + +/* DMA layout of queue heads */ +struct ci13xxx_qh { + /* 0 */ + u32 cap; +#define QH_IOS BIT(15) +#define QH_MAX_PKT (0x07FFUL << 16) +#define QH_ZLT BIT(29) +#define QH_MULT (0x0003UL << 30) + /* 1 */ + u32 curr; + /* 2 - 8 */ + struct ci13xxx_td td; + /* 9 */ + u32 RESERVED; + struct usb_ctrlrequest setup; +} __attribute__ ((packed, aligned(4))); + +/** + * struct ci13xxx_req - usb request representation + * @req: request structure for gadget drivers + * @queue: link to QH list + * @ptr: transfer descriptor for this request + * @dma: dma address for the transfer descriptor + * @zptr: transfer descriptor for the zero packet + * @zdma: dma address of the zero packet's transfer descriptor + */ +struct ci13xxx_req { + struct usb_request req; + struct list_head queue; + struct ci13xxx_td *ptr; + dma_addr_t dma; + struct ci13xxx_td *zptr; + dma_addr_t zdma; +}; + +#ifdef CONFIG_USB_CHIPIDEA_UDC + +int ci_hdrc_gadget_init(struct ci13xxx *ci); + +#else + +static inline int ci_hdrc_gadget_init(struct ci13xxx *ci) +{ + return -ENXIO; +} + +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */ diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c new file mode 100644 index 00000000..714a6bd8 --- /dev/null +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -0,0 +1,261 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include + +#include "ci13xxx_imx.h" + +#define USB_DEV_MAX 4 + +#define MX25_USB_PHY_CTRL_OFFSET 0x08 +#define MX25_BM_EXTERNAL_VBUS_DIVIDER BIT(23) + +#define MX53_USB_OTG_PHY_CTRL_0_OFFSET 0x08 +#define MX53_USB_UH2_CTRL_OFFSET 0x14 +#define MX53_USB_UH3_CTRL_OFFSET 0x18 +#define MX53_BM_OVER_CUR_DIS_H1 BIT(5) +#define MX53_BM_OVER_CUR_DIS_OTG BIT(8) +#define MX53_BM_OVER_CUR_DIS_UHx BIT(30) + +#define MX6_BM_OVER_CUR_DIS BIT(7) + +struct imx_usbmisc { + void __iomem *base; + spinlock_t lock; + struct clk *clk; + struct usbmisc_usb_device usbdev[USB_DEV_MAX]; + const struct usbmisc_ops *ops; +}; + +static struct imx_usbmisc *usbmisc; + +static struct usbmisc_usb_device *get_usbdev(struct device *dev) +{ + int i, ret; + + for (i = 0; i < USB_DEV_MAX; i++) { + if (usbmisc->usbdev[i].dev == dev) + return &usbmisc->usbdev[i]; + else if (!usbmisc->usbdev[i].dev) + break; + } + + if (i >= USB_DEV_MAX) + return ERR_PTR(-EBUSY); + + ret = usbmisc_get_init_data(dev, &usbmisc->usbdev[i]); + if (ret) + return ERR_PTR(ret); + + return &usbmisc->usbdev[i]; +} + +static int usbmisc_imx25_post(struct device *dev) +{ + struct usbmisc_usb_device *usbdev; + void __iomem *reg; + unsigned long flags; + u32 val; + + usbdev = get_usbdev(dev); + if (IS_ERR(usbdev)) + return PTR_ERR(usbdev); + + reg = usbmisc->base + MX25_USB_PHY_CTRL_OFFSET; + + if (usbdev->evdo) { + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(reg); + writel(val | MX25_BM_EXTERNAL_VBUS_DIVIDER, reg); + spin_unlock_irqrestore(&usbmisc->lock, flags); + usleep_range(5000, 10000); /* needed to stabilize voltage */ + } + + return 0; +} + +static int usbmisc_imx53_init(struct device *dev) +{ + struct usbmisc_usb_device *usbdev; + void __iomem *reg = NULL; + unsigned long flags; + u32 val = 0; + + usbdev = get_usbdev(dev); + if (IS_ERR(usbdev)) + return PTR_ERR(usbdev); + + if (usbdev->disable_oc) { + spin_lock_irqsave(&usbmisc->lock, flags); + switch (usbdev->index) { + case 0: + reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_OTG; + break; + case 1: + reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_H1; + break; + case 2: + reg = usbmisc->base + MX53_USB_UH2_CTRL_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_UHx; + break; + case 3: + reg = usbmisc->base + MX53_USB_UH3_CTRL_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_UHx; + break; + } + if (reg && val) + writel(val, reg); + spin_unlock_irqrestore(&usbmisc->lock, flags); + } + + return 0; +} + +static int usbmisc_imx6q_init(struct device *dev) +{ + + struct usbmisc_usb_device *usbdev; + unsigned long flags; + u32 reg; + + usbdev = get_usbdev(dev); + if (IS_ERR(usbdev)) + return PTR_ERR(usbdev); + + if (usbdev->disable_oc) { + spin_lock_irqsave(&usbmisc->lock, flags); + reg = readl(usbmisc->base + usbdev->index * 4); + writel(reg | MX6_BM_OVER_CUR_DIS, + usbmisc->base + usbdev->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + } + + return 0; +} + +static const struct usbmisc_ops imx25_usbmisc_ops = { + .post = usbmisc_imx25_post, +}; + +static const struct usbmisc_ops imx53_usbmisc_ops = { + .init = usbmisc_imx53_init, +}; + +static const struct usbmisc_ops imx6q_usbmisc_ops = { + .init = usbmisc_imx6q_init, +}; + +static const struct of_device_id usbmisc_imx_dt_ids[] = { + { + .compatible = "fsl,imx25-usbmisc", + .data = &imx25_usbmisc_ops, + }, + { + .compatible = "fsl,imx53-usbmisc", + .data = &imx53_usbmisc_ops, + }, + { + .compatible = "fsl,imx6q-usbmisc", + .data = &imx6q_usbmisc_ops, + }, + { /* sentinel */ } +}; + +static int usbmisc_imx_probe(struct platform_device *pdev) +{ + struct resource *res; + struct imx_usbmisc *data; + int ret; + struct of_device_id *tmp_dev; + + if (usbmisc) + return -EBUSY; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, + "failed to get clock, err=%ld\n", PTR_ERR(data->clk)); + return PTR_ERR(data->clk); + } + + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(&pdev->dev, + "clk_prepare_enable failed, err=%d\n", ret); + return ret; + } + + tmp_dev = (struct of_device_id *) + of_match_device(usbmisc_imx_dt_ids, &pdev->dev); + data->ops = (const struct usbmisc_ops *)tmp_dev->data; + usbmisc = data; + ret = usbmisc_set_ops(data->ops); + if (ret) { + usbmisc = NULL; + clk_disable_unprepare(data->clk); + return ret; + } + + return 0; +} + +static int usbmisc_imx_remove(struct platform_device *pdev) +{ + usbmisc_unset_ops(usbmisc->ops); + clk_disable_unprepare(usbmisc->clk); + usbmisc = NULL; + return 0; +} + +static struct platform_driver usbmisc_imx_driver = { + .probe = usbmisc_imx_probe, + .remove = usbmisc_imx_remove, + .driver = { + .name = "usbmisc_imx", + .owner = THIS_MODULE, + .of_match_table = usbmisc_imx_dt_ids, + }, +}; + +int usbmisc_imx_drv_init(void) +{ + return platform_driver_register(&usbmisc_imx_driver); +} +subsys_initcall(usbmisc_imx_drv_init); + +void usbmisc_imx_drv_exit(void) +{ + platform_driver_unregister(&usbmisc_imx_driver); +} +module_exit(usbmisc_imx_drv_exit); + +MODULE_ALIAS("platform:usbmisc-imx"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("driver for imx usb non-core registers"); +MODULE_AUTHOR("Richard Zhao "); diff --git a/drivers/usb/class/Kconfig b/drivers/usb/class/Kconfig new file mode 100644 index 00000000..bb8b7368 --- /dev/null +++ b/drivers/usb/class/Kconfig @@ -0,0 +1,48 @@ +# +# USB Class driver configuration +# +comment "USB Device Class drivers" + +config USB_ACM + tristate "USB Modem (CDC ACM) support" + depends on TTY + ---help--- + This driver supports USB modems and ISDN adapters which support the + Communication Device Class Abstract Control Model interface. + Please read for details. + + If your modem only reports "Cls=ff(vend.)" in the descriptors in + /proc/bus/usb/devices, then your modem will not work with this + driver. + + To compile this driver as a module, choose M here: the + module will be called cdc-acm. + +config USB_PRINTER + tristate "USB Printer support" + help + Say Y here if you want to connect a USB printer to your computer's + USB port. + + To compile this driver as a module, choose M here: the + module will be called usblp. + +config USB_WDM + tristate "USB Wireless Device Management support" + ---help--- + This driver supports the WMC Device Management functionality + of cell phones compliant to the CDC WMC specification. You can use + AT commands over this device. + + To compile this driver as a module, choose M here: the + module will be called cdc-wdm. + +config USB_TMC + tristate "USB Test and Measurement Class support" + help + Say Y here if you want to connect a USB device that follows + the USB.org specification for USB Test and Measurement devices + to your computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called usbtmc. diff --git a/drivers/usb/class/Makefile b/drivers/usb/class/Makefile new file mode 100644 index 00000000..32e85277 --- /dev/null +++ b/drivers/usb/class/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for USB Class drivers +# (one step up from the misc category) +# + +obj-$(CONFIG_USB_ACM) += cdc-acm.o +obj-$(CONFIG_USB_PRINTER) += usblp.o +obj-$(CONFIG_USB_WDM) += cdc-wdm.o +obj-$(CONFIG_USB_TMC) += usbtmc.o diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c new file mode 100644 index 00000000..2800776b --- /dev/null +++ b/drivers/usb/class/cdc-acm.c @@ -0,0 +1,1843 @@ +/* + * cdc-acm.c + * + * Copyright (c) 1999 Armin Fuerst + * Copyright (c) 1999 Pavel Machek + * Copyright (c) 1999 Johannes Erdfelt + * Copyright (c) 2000 Vojtech Pavlik + * Copyright (c) 2004 Oliver Neukum + * Copyright (c) 2005 David Kubicek + * Copyright (c) 2011 Johan Hovold + * + * USB Abstract Control Model driver for USB modems and ISDN adapters + * + * Sponsored by SuSE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cdc-acm.h" + + +#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek, Johan Hovold" +#define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters" + +static struct usb_driver acm_driver; +static struct tty_driver *acm_tty_driver; +static struct acm *acm_table[ACM_TTY_MINORS]; + +static DEFINE_MUTEX(acm_table_lock); + +/* + * acm_table accessors + */ + +/* + * Look up an ACM structure by index. If found and not disconnected, increment + * its refcount and return it with its mutex held. + */ +static struct acm *acm_get_by_index(unsigned index) +{ + struct acm *acm; + + mutex_lock(&acm_table_lock); + acm = acm_table[index]; + if (acm) { + mutex_lock(&acm->mutex); + if (acm->disconnected) { + mutex_unlock(&acm->mutex); + acm = NULL; + } else { + tty_port_get(&acm->port); + mutex_unlock(&acm->mutex); + } + } + mutex_unlock(&acm_table_lock); + return acm; +} + +/* + * Try to find an available minor number and if found, associate it with 'acm'. + */ +static int acm_alloc_minor(struct acm *acm) +{ + int minor; + + mutex_lock(&acm_table_lock); + for (minor = 0; minor < ACM_TTY_MINORS; minor++) { + if (!acm_table[minor]) { + acm_table[minor] = acm; + break; + } + } + mutex_unlock(&acm_table_lock); + + return minor; +} + +/* Release the minor number associated with 'acm'. */ +static void acm_release_minor(struct acm *acm) +{ + mutex_lock(&acm_table_lock); + acm_table[acm->minor] = NULL; + mutex_unlock(&acm_table_lock); +} + +/* + * Functions for ACM control messages. + */ + +static int acm_ctrl_msg(struct acm *acm, int request, int value, + void *buf, int len) +{ + int retval; + + retval = usb_autopm_get_interface(acm->control); + if (retval) + return retval; + + retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), + request, USB_RT_ACM, value, + acm->control->altsetting[0].desc.bInterfaceNumber, + buf, len, 5000); + + dev_dbg(&acm->control->dev, + "%s - rq 0x%02x, val %#x, len %#x, result %d\n", + __func__, request, value, len, retval); + + usb_autopm_put_interface(acm->control); + + return retval < 0 ? retval : 0; +} + +/* devices aren't required to support these requests. + * the cdc acm descriptor tells whether they do... + */ +#define acm_set_control(acm, control) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SET_CONTROL_LINE_STATE, control, NULL, 0) +#define acm_set_line(acm, line) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SET_LINE_CODING, 0, line, sizeof *(line)) +#define acm_send_break(acm, ms) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0) + +/* + * Write buffer management. + * All of these assume proper locks taken by the caller. + */ + +static int acm_wb_alloc(struct acm *acm) +{ + int i, wbn; + struct acm_wb *wb; + + wbn = 0; + i = 0; + for (;;) { + wb = &acm->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; + } + wbn = (wbn + 1) % ACM_NW; + if (++i >= ACM_NW) + return -1; + } +} + +static int acm_wb_is_avail(struct acm *acm) +{ + int i, n; + unsigned long flags; + + n = ACM_NW; + spin_lock_irqsave(&acm->write_lock, flags); + for (i = 0; i < ACM_NW; i++) + n -= acm->wb[i].use; + spin_unlock_irqrestore(&acm->write_lock, flags); + return n; +} + +/* + * Finish write. Caller must hold acm->write_lock + */ +static void acm_write_done(struct acm *acm, struct acm_wb *wb) +{ + wb->use = 0; + acm->transmitting--; + usb_autopm_put_interface_async(acm->control); +} + +/* + * Poke write. + * + * the caller is responsible for locking + */ + +static int acm_start_wb(struct acm *acm, struct acm_wb *wb) +{ + int rc; + + acm->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = acm->dev; + + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&acm->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + acm_write_done(acm, wb); + } + return rc; +} + +static int acm_write_start(struct acm *acm, int wbn) +{ + unsigned long flags; + struct acm_wb *wb = &acm->wb[wbn]; + int rc; + + spin_lock_irqsave(&acm->write_lock, flags); + if (!acm->dev) { + wb->use = 0; + spin_unlock_irqrestore(&acm->write_lock, flags); + return -ENODEV; + } + + dev_vdbg(&acm->data->dev, "%s - susp_count %d\n", __func__, + acm->susp_count); + usb_autopm_get_interface_async(acm->control); + if (acm->susp_count) { + usb_anchor_urb(wb->urb, &acm->delayed); + spin_unlock_irqrestore(&acm->write_lock, flags); + return 0; + } + usb_mark_last_busy(acm->dev); + + rc = acm_start_wb(acm, wb); + spin_unlock_irqrestore(&acm->write_lock, flags); + + return rc; + +} +/* + * attributes exported through sysfs + */ +static ssize_t show_caps +(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct acm *acm = usb_get_intfdata(intf); + + return sprintf(buf, "%d", acm->ctrl_caps); +} +static DEVICE_ATTR(bmCapabilities, S_IRUGO, show_caps, NULL); + +static ssize_t show_country_codes +(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct acm *acm = usb_get_intfdata(intf); + + memcpy(buf, acm->country_codes, acm->country_code_size); + return acm->country_code_size; +} + +static DEVICE_ATTR(wCountryCodes, S_IRUGO, show_country_codes, NULL); + +static ssize_t show_country_rel_date +(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct acm *acm = usb_get_intfdata(intf); + + return sprintf(buf, "%d", acm->country_rel_date); +} + +static DEVICE_ATTR(iCountryCodeRelDate, S_IRUGO, show_country_rel_date, NULL); +/* + * Interrupt handlers for various ACM device responses + */ + +/* control interface reports status changes with "interrupt" transfers */ +static void acm_ctrl_irq(struct urb *urb) +{ + struct acm *acm = urb->context; + struct usb_cdc_notification *dr = urb->transfer_buffer; + unsigned char *data; + int newctrl; + int retval; + int status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&acm->control->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&acm->control->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_mark_last_busy(acm->dev); + + data = (unsigned char *)(dr + 1); + switch (dr->bNotificationType) { + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + dev_dbg(&acm->control->dev, "%s - network connection: %d\n", + __func__, dr->wValue); + break; + + case USB_CDC_NOTIFY_SERIAL_STATE: + newctrl = get_unaligned_le16(data); + + if (!acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { + dev_dbg(&acm->control->dev, "%s - calling hangup\n", + __func__); + tty_port_tty_hangup(&acm->port, false); + } + + acm->ctrlin = newctrl; + + dev_dbg(&acm->control->dev, + "%s - input control lines: dcd%c dsr%c break%c " + "ring%c framing%c parity%c overrun%c\n", + __func__, + acm->ctrlin & ACM_CTRL_DCD ? '+' : '-', + acm->ctrlin & ACM_CTRL_DSR ? '+' : '-', + acm->ctrlin & ACM_CTRL_BRK ? '+' : '-', + acm->ctrlin & ACM_CTRL_RI ? '+' : '-', + acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-', + acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-', + acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-'); + break; + + default: + dev_dbg(&acm->control->dev, + "%s - unknown notification %d received: index %d " + "len %d data0 %d data1 %d\n", + __func__, + dr->bNotificationType, dr->wIndex, + dr->wLength, data[0], data[1]); + break; + } +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&acm->control->dev, "%s - usb_submit_urb failed: %d\n", + __func__, retval); +} + +static int acm_submit_read_urb(struct acm *acm, int index, gfp_t mem_flags) +{ + int res; + + if (!test_and_clear_bit(index, &acm->read_urbs_free)) + return 0; + + dev_vdbg(&acm->data->dev, "%s - urb %d\n", __func__, index); + + res = usb_submit_urb(acm->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&acm->data->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &acm->read_urbs_free); + return res; + } + + return 0; +} + +static int acm_submit_read_urbs(struct acm *acm, gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < acm->rx_buflimit; ++i) { + res = acm_submit_read_urb(acm, i, mem_flags); + if (res) + return res; + } + + return 0; +} + +static void acm_process_read_urb(struct acm *acm, struct urb *urb) +{ + if (!urb->actual_length) + return; + + tty_insert_flip_string(&acm->port, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(&acm->port); +} + +static void acm_read_bulk_callback(struct urb *urb) +{ + struct acm_rb *rb = urb->context; + struct acm *acm = rb->instance; + unsigned long flags; + + dev_vdbg(&acm->data->dev, "%s - urb %d, len %d\n", __func__, + rb->index, urb->actual_length); + set_bit(rb->index, &acm->read_urbs_free); + + if (!acm->dev) { + dev_dbg(&acm->data->dev, "%s - disconnected\n", __func__); + return; + } + usb_mark_last_busy(acm->dev); + + if (urb->status) { + dev_dbg(&acm->data->dev, "%s - non-zero urb status: %d\n", + __func__, urb->status); + return; + } + acm_process_read_urb(acm, urb); + + /* throttle device if requested by tty */ + spin_lock_irqsave(&acm->read_lock, flags); + acm->throttled = acm->throttle_req; + if (!acm->throttled && !acm->susp_count) { + spin_unlock_irqrestore(&acm->read_lock, flags); + acm_submit_read_urb(acm, rb->index, GFP_ATOMIC); + } else { + spin_unlock_irqrestore(&acm->read_lock, flags); + } +} + +/* data interface wrote those outgoing bytes */ +static void acm_write_bulk(struct urb *urb) +{ + struct acm_wb *wb = urb->context; + struct acm *acm = wb->instance; + unsigned long flags; + + if (urb->status || (urb->actual_length != urb->transfer_buffer_length)) + dev_vdbg(&acm->data->dev, "%s - len %d/%d, status %d\n", + __func__, + urb->actual_length, + urb->transfer_buffer_length, + urb->status); + + spin_lock_irqsave(&acm->write_lock, flags); + acm_write_done(acm, wb); + spin_unlock_irqrestore(&acm->write_lock, flags); + schedule_work(&acm->work); +} + +static void acm_softint(struct work_struct *work) +{ + struct acm *acm = container_of(work, struct acm, work); + + dev_vdbg(&acm->data->dev, "%s\n", __func__); + + tty_port_tty_wakeup(&acm->port); +} + +/* + * TTY handlers + */ + +static int acm_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct acm *acm; + int retval; + + dev_dbg(tty->dev, "%s\n", __func__); + + acm = acm_get_by_index(tty->index); + if (!acm) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; + + tty->driver_data = acm; + + return 0; + +error_init_termios: + tty_port_put(&acm->port); + return retval; +} + +static int acm_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct acm *acm = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + return tty_port_open(&acm->port, tty, filp); +} + +static int acm_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct acm *acm = container_of(port, struct acm, port); + int retval = -ENODEV; + int i; + + dev_dbg(&acm->control->dev, "%s\n", __func__); + + mutex_lock(&acm->mutex); + if (acm->disconnected) + goto disconnected; + + retval = usb_autopm_get_interface(acm->control); + if (retval) + goto error_get_interface; + + /* + * FIXME: Why do we need this? Allocating 64K of physically contiguous + * memory is really nasty... + */ + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + acm->control->needs_remote_wakeup = 1; + + acm->ctrlurb->dev = acm->dev; + if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) { + dev_err(&acm->control->dev, + "%s - usb_submit_urb(ctrl irq) failed\n", __func__); + goto error_submit_urb; + } + + acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS; + if (acm_set_control(acm, acm->ctrlout) < 0 && + (acm->ctrl_caps & USB_CDC_CAP_LINE)) + goto error_set_control; + + usb_autopm_put_interface(acm->control); + + /* + * Unthrottle device in case the TTY was closed while throttled. + */ + spin_lock_irq(&acm->read_lock); + acm->throttled = 0; + acm->throttle_req = 0; + spin_unlock_irq(&acm->read_lock); + + if (acm_submit_read_urbs(acm, GFP_KERNEL)) + goto error_submit_read_urbs; + + mutex_unlock(&acm->mutex); + + return 0; + +error_submit_read_urbs: + for (i = 0; i < acm->rx_buflimit; i++) + usb_kill_urb(acm->read_urbs[i]); + acm->ctrlout = 0; + acm_set_control(acm, acm->ctrlout); +error_set_control: + usb_kill_urb(acm->ctrlurb); +error_submit_urb: + usb_autopm_put_interface(acm->control); +error_get_interface: +disconnected: + mutex_unlock(&acm->mutex); + return retval; +} + +static void acm_port_destruct(struct tty_port *port) +{ + struct acm *acm = container_of(port, struct acm, port); + + dev_dbg(&acm->control->dev, "%s\n", __func__); + + acm_release_minor(acm); + usb_put_intf(acm->control); + kfree(acm->country_codes); + kfree(acm); +} + +static void acm_port_shutdown(struct tty_port *port) +{ + struct acm *acm = container_of(port, struct acm, port); + struct urb *urb; + struct acm_wb *wb; + int i; + int pm_err; + + dev_dbg(&acm->control->dev, "%s\n", __func__); + + mutex_lock(&acm->mutex); + if (!acm->disconnected) { + pm_err = usb_autopm_get_interface(acm->control); + acm_set_control(acm, acm->ctrlout = 0); + + for (;;) { + urb = usb_get_from_anchor(&acm->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = 0; + usb_autopm_put_interface_async(acm->control); + } + + usb_kill_urb(acm->ctrlurb); + for (i = 0; i < ACM_NW; i++) + usb_kill_urb(acm->wb[i].urb); + for (i = 0; i < acm->rx_buflimit; i++) + usb_kill_urb(acm->read_urbs[i]); + acm->control->needs_remote_wakeup = 0; + if (!pm_err) + usb_autopm_put_interface(acm->control); + } + mutex_unlock(&acm->mutex); +} + +static void acm_tty_cleanup(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + dev_dbg(&acm->control->dev, "%s\n", __func__); + tty_port_put(&acm->port); +} + +static void acm_tty_hangup(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + dev_dbg(&acm->control->dev, "%s\n", __func__); + tty_port_hangup(&acm->port); +} + +static void acm_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct acm *acm = tty->driver_data; + dev_dbg(&acm->control->dev, "%s\n", __func__); + tty_port_close(&acm->port, tty, filp); +} + +static int acm_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct acm *acm = tty->driver_data; + int stat; + unsigned long flags; + int wbn; + struct acm_wb *wb; + + if (!count) + return 0; + + dev_vdbg(&acm->data->dev, "%s - count %d\n", __func__, count); + + spin_lock_irqsave(&acm->write_lock, flags); + wbn = acm_wb_alloc(acm); + if (wbn < 0) { + spin_unlock_irqrestore(&acm->write_lock, flags); + return 0; + } + wb = &acm->wb[wbn]; + + count = (count > acm->writesize) ? acm->writesize : count; + dev_vdbg(&acm->data->dev, "%s - write %d\n", __func__, count); + memcpy(wb->buf, buf, count); + wb->len = count; + spin_unlock_irqrestore(&acm->write_lock, flags); + + stat = acm_write_start(acm, wbn); + if (stat < 0) + return stat; + return count; +} + +static int acm_tty_write_room(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + /* + * Do not let the line discipline to know that we have a reserve, + * or it might get too enthusiastic. + */ + return acm_wb_is_avail(acm) ? acm->writesize : 0; +} + +static int acm_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + /* + * if the device was unplugged then any remaining characters fell out + * of the connector ;) + */ + if (acm->disconnected) + return 0; + /* + * This is inaccurate (overcounts), but it works. + */ + return (ACM_NW - acm_wb_is_avail(acm)) * acm->writesize; +} + +static void acm_tty_throttle(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + + spin_lock_irq(&acm->read_lock); + acm->throttle_req = 1; + spin_unlock_irq(&acm->read_lock); +} + +static void acm_tty_unthrottle(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + unsigned int was_throttled; + + spin_lock_irq(&acm->read_lock); + was_throttled = acm->throttled; + acm->throttled = 0; + acm->throttle_req = 0; + spin_unlock_irq(&acm->read_lock); + + if (was_throttled) + acm_submit_read_urbs(acm, GFP_KERNEL); +} + +static int acm_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct acm *acm = tty->driver_data; + int retval; + + retval = acm_send_break(acm, state ? 0xffff : 0); + if (retval < 0) + dev_dbg(&acm->control->dev, "%s - send break failed\n", + __func__); + return retval; +} + +static int acm_tty_tiocmget(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + + return (acm->ctrlout & ACM_CTRL_DTR ? TIOCM_DTR : 0) | + (acm->ctrlout & ACM_CTRL_RTS ? TIOCM_RTS : 0) | + (acm->ctrlin & ACM_CTRL_DSR ? TIOCM_DSR : 0) | + (acm->ctrlin & ACM_CTRL_RI ? TIOCM_RI : 0) | + (acm->ctrlin & ACM_CTRL_DCD ? TIOCM_CD : 0) | + TIOCM_CTS; +} + +static int acm_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct acm *acm = tty->driver_data; + unsigned int newctrl; + + newctrl = acm->ctrlout; + set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | + (set & TIOCM_RTS ? ACM_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | + (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0); + + newctrl = (newctrl & ~clear) | set; + + if (acm->ctrlout == newctrl) + return 0; + return acm_set_control(acm, acm->ctrlout = newctrl); +} + +static int get_serial_info(struct acm *acm, struct serial_struct __user *info) +{ + struct serial_struct tmp; + + if (!info) + return -EINVAL; + + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = ASYNC_LOW_LATENCY; + tmp.xmit_fifo_size = acm->writesize; + tmp.baud_base = le32_to_cpu(acm->line.dwDTERate); + tmp.close_delay = acm->port.close_delay / 10; + tmp.closing_wait = acm->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + acm->port.closing_wait / 10; + + if (copy_to_user(info, &tmp, sizeof(tmp))) + return -EFAULT; + else + return 0; +} + +static int set_serial_info(struct acm *acm, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&acm->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != acm->port.close_delay) || + (closing_wait != acm->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + acm->port.close_delay = close_delay; + acm->port.closing_wait = closing_wait; + } + + mutex_unlock(&acm->port.mutex); + return retval; +} + +static int acm_tty_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct acm *acm = tty->driver_data; + int rv = -ENOIOCTLCMD; + + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + rv = get_serial_info(acm, (struct serial_struct __user *) arg); + break; + case TIOCSSERIAL: + rv = set_serial_info(acm, (struct serial_struct __user *) arg); + break; + } + + return rv; +} + +static void acm_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +{ + struct acm *acm = tty->driver_data; + struct ktermios *termios = &tty->termios; + struct usb_cdc_line_coding newline; + int newctrl = acm->ctrlout; + + newline.dwDTERate = cpu_to_le32(tty_get_baud_rate(tty)); + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 0; + newline.bParityType = termios->c_cflag & PARENB ? + (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + break; + case CS6: + newline.bDataBits = 6; + break; + case CS7: + newline.bDataBits = 7; + break; + case CS8: + default: + newline.bDataBits = 8; + break; + } + /* FIXME: Needs to clear unsupported bits in the termios */ + acm->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (C_BAUD(tty) == B0) { + newline.dwDTERate = acm->line.dwDTERate; + newctrl &= ~ACM_CTRL_DTR; + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= ACM_CTRL_DTR; + } + + if (newctrl != acm->ctrlout) + acm_set_control(acm, acm->ctrlout = newctrl); + + if (memcmp(&acm->line, &newline, sizeof newline)) { + memcpy(&acm->line, &newline, sizeof newline); + dev_dbg(&acm->control->dev, "%s - set line: %d %d %d %d\n", + __func__, + le32_to_cpu(newline.dwDTERate), + newline.bCharFormat, newline.bParityType, + newline.bDataBits); + acm_set_line(acm, &acm->line); + } +} + +static const struct tty_port_operations acm_port_ops = { + .shutdown = acm_port_shutdown, + .activate = acm_port_activate, + .destruct = acm_port_destruct, +}; + +/* + * USB probe and disconnect routines. + */ + +/* Little helpers: write/read buffers free */ +static void acm_write_buffers_free(struct acm *acm) +{ + int i; + struct acm_wb *wb; + struct usb_device *usb_dev = interface_to_usbdev(acm->control); + + for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) + usb_free_coherent(usb_dev, acm->writesize, wb->buf, wb->dmah); +} + +static void acm_read_buffers_free(struct acm *acm) +{ + struct usb_device *usb_dev = interface_to_usbdev(acm->control); + int i; + + for (i = 0; i < acm->rx_buflimit; i++) + usb_free_coherent(usb_dev, acm->readsize, + acm->read_buffers[i].base, acm->read_buffers[i].dma); +} + +/* Little helper: write buffers allocate */ +static int acm_write_buffers_alloc(struct acm *acm) +{ + int i; + struct acm_wb *wb; + + for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(acm->dev, acm->writesize, GFP_KERNEL, + &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(acm->dev, acm->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +static int acm_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_cdc_union_desc *union_header = NULL; + struct usb_cdc_country_functional_desc *cfd = NULL; + unsigned char *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + struct usb_interface *control_interface; + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct acm *acm; + int minor; + int ctrlsize, readsize; + u8 *buf; + u8 ac_management_function = 0; + u8 call_management_function = 0; + int call_interface_num = -1; + int data_interface_num = -1; + unsigned long quirks; + int num_rx_buf; + int i; + int combined_interfaces = 0; + struct device *tty_dev; + int rv = -ENOMEM; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + + if (quirks == IGNORE_DEVICE) + return -ENODEV; + + num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR; + + /* handle quirks deadly to normal probing*/ + if (quirks == NO_UNION_NORMAL) { + data_interface = usb_ifnum_to_if(usb_dev, 1); + control_interface = usb_ifnum_to_if(usb_dev, 0); + goto skip_normal_probe; + } + + /* normal probing*/ + if (!buffer) { + dev_err(&intf->dev, "Weird descriptor references\n"); + return -EINVAL; + } + + if (!buflen) { + if (intf->cur_altsetting->endpoint && + intf->cur_altsetting->endpoint->extralen && + intf->cur_altsetting->endpoint->extra) { + dev_dbg(&intf->dev, + "Seeking extra descriptors on endpoint\n"); + buflen = intf->cur_altsetting->endpoint->extralen; + buffer = intf->cur_altsetting->endpoint->extra; + } else { + dev_err(&intf->dev, + "Zero length descriptor references\n"); + return -EINVAL; + } + } + + while (buflen > 0) { + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); + goto next_desc; + } + + switch (buffer[2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (union_header) { + dev_err(&intf->dev, "More than one " + "union descriptor, skipping ...\n"); + goto next_desc; + } + union_header = (struct usb_cdc_union_desc *)buffer; + break; + case USB_CDC_COUNTRY_TYPE: /* export through sysfs*/ + cfd = (struct usb_cdc_country_functional_desc *)buffer; + break; + case USB_CDC_HEADER_TYPE: /* maybe check version */ + break; /* for now we ignore it */ + case USB_CDC_ACM_TYPE: + ac_management_function = buffer[3]; + break; + case USB_CDC_CALL_MANAGEMENT_TYPE: + call_management_function = buffer[3]; + call_interface_num = buffer[4]; + if ((quirks & NOT_A_MODEM) == 0 && (call_management_function & 3) != 3) + dev_err(&intf->dev, "This device cannot do calls on its own. It is not a modem.\n"); + break; + default: + /* there are LOTS more CDC descriptors that + * could legitimately be found here. + */ + dev_dbg(&intf->dev, "Ignoring descriptor: " + "type %02x, length %d\n", + buffer[2], buffer[0]); + break; + } +next_desc: + buflen -= buffer[0]; + buffer += buffer[0]; + } + + if (!union_header) { + if (call_interface_num > 0) { + dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n"); + /* quirks for Droids MuIn LCD */ + if (quirks & NO_DATA_INTERFACE) + data_interface = usb_ifnum_to_if(usb_dev, 0); + else + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num)); + control_interface = intf; + } else { + if (intf->cur_altsetting->desc.bNumEndpoints != 3) { + dev_dbg(&intf->dev,"No union descriptor, giving up\n"); + return -ENODEV; + } else { + dev_warn(&intf->dev,"No union descriptor, testing for castrated device\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } + } + } else { + control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = union_header->bSlaveInterface0)); + } + + if (!control_interface || !data_interface) { + dev_dbg(&intf->dev, "no interfaces\n"); + return -ENODEV; + } + + if (data_interface_num != call_interface_num) + dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n"); + + if (control_interface == data_interface) { + /* some broken devices designed for windows work this way */ + dev_warn(&intf->dev,"Control and data interfaces are not separated!\n"); + combined_interfaces = 1; + /* a popular other OS doesn't use it */ + quirks |= NO_CAP_LINE; + if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) { + dev_err(&intf->dev, "This needs exactly 3 endpoints\n"); + return -EINVAL; + } +look_for_collapsed_interface: + for (i = 0; i < 3; i++) { + struct usb_endpoint_descriptor *ep; + ep = &data_interface->cur_altsetting->endpoint[i].desc; + + if (usb_endpoint_is_int_in(ep)) + epctrl = ep; + else if (usb_endpoint_is_bulk_out(ep)) + epwrite = ep; + else if (usb_endpoint_is_bulk_in(ep)) + epread = ep; + else + return -EINVAL; + } + if (!epctrl || !epread || !epwrite) + return -ENODEV; + else + goto made_compressed_probe; + } + +skip_normal_probe: + + /*workaround for switched interfaces */ + if (data_interface->cur_altsetting->desc.bInterfaceClass + != CDC_DATA_INTERFACE_TYPE) { + if (control_interface->cur_altsetting->desc.bInterfaceClass + == CDC_DATA_INTERFACE_TYPE) { + struct usb_interface *t; + dev_dbg(&intf->dev, + "Your device has switched interfaces.\n"); + t = control_interface; + control_interface = data_interface; + data_interface = t; + } else { + return -EINVAL; + } + } + + /* Accept probe requests only for the control interface */ + if (!combined_interfaces && intf != control_interface) + return -ENODEV; + + if (!combined_interfaces && usb_interface_claimed(data_interface)) { + /* valid in this context */ + dev_dbg(&intf->dev, "The data interface isn't available\n"); + return -EBUSY; + } + + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + control_interface->cur_altsetting->desc.bNumEndpoints == 0) + return -EINVAL; + + epctrl = &control_interface->cur_altsetting->endpoint[0].desc; + epread = &data_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[1].desc; + + + /* workaround for switched endpoints */ + if (!usb_endpoint_dir_in(epread)) { + /* descriptors are swapped */ + struct usb_endpoint_descriptor *t; + dev_dbg(&intf->dev, + "The data interface has switched endpoints\n"); + t = epread; + epread = epwrite; + epwrite = t; + } +made_compressed_probe: + dev_dbg(&intf->dev, "interfaces are valid\n"); + + acm = kzalloc(sizeof(struct acm), GFP_KERNEL); + if (acm == NULL) { + dev_err(&intf->dev, "out of memory (acm kzalloc)\n"); + goto alloc_fail; + } + + minor = acm_alloc_minor(acm); + if (minor == ACM_TTY_MINORS) { + dev_err(&intf->dev, "no more free acm devices\n"); + kfree(acm); + return -ENODEV; + } + + ctrlsize = usb_endpoint_maxp(epctrl); + readsize = usb_endpoint_maxp(epread) * + (quirks == SINGLE_RX_URB ? 1 : 2); + acm->combined_interfaces = combined_interfaces; + acm->writesize = usb_endpoint_maxp(epwrite) * 20; + acm->control = control_interface; + acm->data = data_interface; + acm->minor = minor; + acm->dev = usb_dev; + acm->ctrl_caps = ac_management_function; + if (quirks & NO_CAP_LINE) + acm->ctrl_caps &= ~USB_CDC_CAP_LINE; + acm->ctrlsize = ctrlsize; + acm->readsize = readsize; + acm->rx_buflimit = num_rx_buf; + INIT_WORK(&acm->work, acm_softint); + spin_lock_init(&acm->write_lock); + spin_lock_init(&acm->read_lock); + mutex_init(&acm->mutex); + acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + acm->is_int_ep = usb_endpoint_xfer_int(epread); + if (acm->is_int_ep) + acm->bInterval = epread->bInterval; + tty_port_init(&acm->port); + acm->port.ops = &acm_port_ops; + init_usb_anchor(&acm->delayed); + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma); + if (!buf) { + dev_err(&intf->dev, "out of memory (ctrl buffer alloc)\n"); + goto alloc_fail2; + } + acm->ctrl_buffer = buf; + + if (acm_write_buffers_alloc(acm) < 0) { + dev_err(&intf->dev, "out of memory (write buffer alloc)\n"); + goto alloc_fail4; + } + + acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!acm->ctrlurb) { + dev_err(&intf->dev, "out of memory (ctrlurb kmalloc)\n"); + goto alloc_fail5; + } + for (i = 0; i < num_rx_buf; i++) { + struct acm_rb *rb = &(acm->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL, + &rb->dma); + if (!rb->base) { + dev_err(&intf->dev, "out of memory " + "(read bufs usb_alloc_coherent)\n"); + goto alloc_fail6; + } + rb->index = i; + rb->instance = acm; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&intf->dev, + "out of memory (read urbs usb_alloc_urb)\n"); + goto alloc_fail6; + } + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + if (acm->is_int_ep) { + usb_fill_int_urb(urb, acm->dev, + acm->rx_endpoint, + rb->base, + acm->readsize, + acm_read_bulk_callback, rb, + acm->bInterval); + } else { + usb_fill_bulk_urb(urb, acm->dev, + acm->rx_endpoint, + rb->base, + acm->readsize, + acm_read_bulk_callback, rb); + } + + acm->read_urbs[i] = urb; + __set_bit(i, &acm->read_urbs_free); + } + for (i = 0; i < ACM_NW; i++) { + struct acm_wb *snd = &(acm->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) { + dev_err(&intf->dev, + "out of memory (write urbs usb_alloc_urb)\n"); + goto alloc_fail7; + } + + if (usb_endpoint_xfer_int(epwrite)) + usb_fill_int_urb(snd->urb, usb_dev, + usb_sndintpipe(usb_dev, epwrite->bEndpointAddress), + NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval); + else + usb_fill_bulk_urb(snd->urb, usb_dev, + usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + NULL, acm->writesize, acm_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = acm; + } + + usb_set_intfdata(intf, acm); + + i = device_create_file(&intf->dev, &dev_attr_bmCapabilities); + if (i < 0) + goto alloc_fail7; + + if (cfd) { /* export the country data */ + acm->country_codes = kmalloc(cfd->bLength - 4, GFP_KERNEL); + if (!acm->country_codes) + goto skip_countries; + acm->country_code_size = cfd->bLength - 4; + memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0, + cfd->bLength - 4); + acm->country_rel_date = cfd->iCountryCodeRelDate; + + i = device_create_file(&intf->dev, &dev_attr_wCountryCodes); + if (i < 0) { + kfree(acm->country_codes); + acm->country_codes = NULL; + acm->country_code_size = 0; + goto skip_countries; + } + + i = device_create_file(&intf->dev, + &dev_attr_iCountryCodeRelDate); + if (i < 0) { + device_remove_file(&intf->dev, &dev_attr_wCountryCodes); + kfree(acm->country_codes); + acm->country_codes = NULL; + acm->country_code_size = 0; + goto skip_countries; + } + } + +skip_countries: + usb_fill_int_urb(acm->ctrlurb, usb_dev, + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, + /* works around buggy devices */ + epctrl->bInterval ? epctrl->bInterval : 0xff); + acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + acm->ctrlurb->transfer_dma = acm->ctrl_dma; + + dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); + + acm_set_control(acm, acm->ctrlout); + + acm->line.dwDTERate = cpu_to_le32(9600); + acm->line.bDataBits = 8; + acm_set_line(acm, &acm->line); + + usb_driver_claim_interface(&acm_driver, data_interface, acm); + usb_set_intfdata(data_interface, acm); + + usb_get_intf(control_interface); + tty_dev = tty_port_register_device(&acm->port, acm_tty_driver, minor, + &control_interface->dev); + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail8; + } + + return 0; +alloc_fail8: + if (acm->country_codes) { + device_remove_file(&acm->control->dev, + &dev_attr_wCountryCodes); + device_remove_file(&acm->control->dev, + &dev_attr_iCountryCodeRelDate); + kfree(acm->country_codes); + } + device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities); +alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < ACM_NW; i++) + usb_free_urb(acm->wb[i].urb); +alloc_fail6: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(acm->read_urbs[i]); + acm_read_buffers_free(acm); + usb_free_urb(acm->ctrlurb); +alloc_fail5: + acm_write_buffers_free(acm); +alloc_fail4: + usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); +alloc_fail2: + acm_release_minor(acm); + kfree(acm); +alloc_fail: + return rv; +} + +static void stop_data_traffic(struct acm *acm) +{ + int i; + + dev_dbg(&acm->control->dev, "%s\n", __func__); + + usb_kill_urb(acm->ctrlurb); + for (i = 0; i < ACM_NW; i++) + usb_kill_urb(acm->wb[i].urb); + for (i = 0; i < acm->rx_buflimit; i++) + usb_kill_urb(acm->read_urbs[i]); + + cancel_work_sync(&acm->work); +} + +static void acm_disconnect(struct usb_interface *intf) +{ + struct acm *acm = usb_get_intfdata(intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct tty_struct *tty; + int i; + + dev_dbg(&intf->dev, "%s\n", __func__); + + /* sibling interface is already cleaning up */ + if (!acm) + return; + + mutex_lock(&acm->mutex); + acm->disconnected = true; + if (acm->country_codes) { + device_remove_file(&acm->control->dev, + &dev_attr_wCountryCodes); + device_remove_file(&acm->control->dev, + &dev_attr_iCountryCodeRelDate); + } + device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities); + usb_set_intfdata(acm->control, NULL); + usb_set_intfdata(acm->data, NULL); + mutex_unlock(&acm->mutex); + + tty = tty_port_tty_get(&acm->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + + stop_data_traffic(acm); + + tty_unregister_device(acm_tty_driver, acm->minor); + + usb_free_urb(acm->ctrlurb); + for (i = 0; i < ACM_NW; i++) + usb_free_urb(acm->wb[i].urb); + for (i = 0; i < acm->rx_buflimit; i++) + usb_free_urb(acm->read_urbs[i]); + acm_write_buffers_free(acm); + usb_free_coherent(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); + acm_read_buffers_free(acm); + + if (!acm->combined_interfaces) + usb_driver_release_interface(&acm_driver, intf == acm->control ? + acm->data : acm->control); + + tty_port_put(&acm->port); +} + +#ifdef CONFIG_PM +static int acm_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct acm *acm = usb_get_intfdata(intf); + int cnt; + + spin_lock_irq(&acm->read_lock); + spin_lock(&acm->write_lock); + if (PMSG_IS_AUTO(message)) { + if (acm->transmitting) { + spin_unlock(&acm->write_lock); + spin_unlock_irq(&acm->read_lock); + return -EBUSY; + } + } + cnt = acm->susp_count++; + spin_unlock(&acm->write_lock); + spin_unlock_irq(&acm->read_lock); + + if (cnt) + return 0; + + stop_data_traffic(acm); + + return 0; +} + +static int acm_resume(struct usb_interface *intf) +{ + struct acm *acm = usb_get_intfdata(intf); + struct urb *urb; + int rv = 0; + + spin_lock_irq(&acm->read_lock); + spin_lock(&acm->write_lock); + + if (--acm->susp_count) + goto out; + + if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags)) { + rv = usb_submit_urb(acm->ctrlurb, GFP_ATOMIC); + + for (;;) { + urb = usb_get_from_anchor(&acm->delayed); + if (!urb) + break; + + acm_start_wb(acm, urb->context); + } + + /* + * delayed error checking because we must + * do the write path at all cost + */ + if (rv < 0) + goto out; + + rv = acm_submit_read_urbs(acm, GFP_ATOMIC); + } +out: + spin_unlock(&acm->write_lock); + spin_unlock_irq(&acm->read_lock); + + return rv; +} + +static int acm_reset_resume(struct usb_interface *intf) +{ + struct acm *acm = usb_get_intfdata(intf); + + if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags)) + tty_port_tty_hangup(&acm->port, false); + + return acm_resume(intf); +} + +#endif /* CONFIG_PM */ + +#define NOKIA_PCSUITE_ACM_INFO(x) \ + USB_DEVICE_AND_INTERFACE_INFO(0x0421, x, \ + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, \ + USB_CDC_ACM_PROTO_VENDOR) + +#define SAMSUNG_PCSUITE_ACM_INFO(x) \ + USB_DEVICE_AND_INTERFACE_INFO(0x04e7, x, \ + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, \ + USB_CDC_ACM_PROTO_VENDOR) + +/* + * USB driver structure. + */ + +static const struct usb_device_id acm_ids[] = { + /* quirky and broken devices */ + { USB_DEVICE(0x17ef, 0x7000), /* Lenovo USB modem */ + .driver_info = NO_UNION_NORMAL, },/* has no union descriptor */ + { USB_DEVICE(0x0870, 0x0001), /* Metricom GS Modem */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0e8d, 0x0003), /* FIREFLY, MediaTek Inc; andrey.arapov@gmail.com */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0e8d, 0x3329), /* MediaTek Inc GPS */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0482, 0x0203), /* KYOCERA AH-K3001V */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x079b, 0x000f), /* BT On-Air USB MODEM */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0ace, 0x1602), /* ZyDAS 56K USB MODEM */ + .driver_info = SINGLE_RX_URB, + }, + { USB_DEVICE(0x0ace, 0x1608), /* ZyDAS 56K USB MODEM */ + .driver_info = SINGLE_RX_URB, /* firmware bug */ + }, + { USB_DEVICE(0x0ace, 0x1611), /* ZyDAS 56K USB MODEM - new version */ + .driver_info = SINGLE_RX_URB, /* firmware bug */ + }, + { USB_DEVICE(0x22b8, 0x7000), /* Motorola Q Phone */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0803, 0x3095), /* Zoom Telephonics Model 3095F USB MODEM */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0572, 0x1321), /* Conexant USB MODEM CX93010 */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0572, 0x1324), /* Conexant USB MODEM RD02-D400 */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0572, 0x1328), /* Shiro / Aztech USB MODEM UM-3100 */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x2184, 0x001c) }, /* GW Instek AFG-2225 */ + { USB_DEVICE(0x22b8, 0x6425), /* Motorola MOTOMAGX phones */ + }, + /* Motorola H24 HSPA module: */ + { USB_DEVICE(0x22b8, 0x2d91) }, /* modem */ + { USB_DEVICE(0x22b8, 0x2d92), /* modem + diagnostics */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d93), /* modem + AT port */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d95), /* modem + AT port + diagnostics */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d96), /* modem + NMEA */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d97), /* modem + diagnostics + NMEA */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d99), /* modem + AT port + NMEA */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d9a), /* modem + AT port + diagnostics + NMEA */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + + { USB_DEVICE(0x0572, 0x1329), /* Hummingbird huc56s (Conexant) */ + .driver_info = NO_UNION_NORMAL, /* union descriptor misplaced on + data interface instead of + communications interface. + Maybe we should define a new + quirk for this. */ + }, + { USB_DEVICE(0x0572, 0x1340), /* Conexant CX93010-2x UCMxx */ + .driver_info = NO_UNION_NORMAL, + }, + { USB_DEVICE(0x05f9, 0x4002), /* PSC Scanning, Magellan 800i */ + .driver_info = NO_UNION_NORMAL, + }, + { USB_DEVICE(0x1bbb, 0x0003), /* Alcatel OT-I650 */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, + { USB_DEVICE(0x1576, 0x03b1), /* Maretron USB100 */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, + + /* Nokia S60 phones expose two ACM channels. The first is + * a modem and is picked up by the standard AT-command + * information below. The second is 'vendor-specific' but + * is treated as a serial device at the S60 end, so we want + * to expose it on Linux too. */ + { NOKIA_PCSUITE_ACM_INFO(0x042D), }, /* Nokia 3250 */ + { NOKIA_PCSUITE_ACM_INFO(0x04D8), }, /* Nokia 5500 Sport */ + { NOKIA_PCSUITE_ACM_INFO(0x04C9), }, /* Nokia E50 */ + { NOKIA_PCSUITE_ACM_INFO(0x0419), }, /* Nokia E60 */ + { NOKIA_PCSUITE_ACM_INFO(0x044D), }, /* Nokia E61 */ + { NOKIA_PCSUITE_ACM_INFO(0x0001), }, /* Nokia E61i */ + { NOKIA_PCSUITE_ACM_INFO(0x0475), }, /* Nokia E62 */ + { NOKIA_PCSUITE_ACM_INFO(0x0508), }, /* Nokia E65 */ + { NOKIA_PCSUITE_ACM_INFO(0x0418), }, /* Nokia E70 */ + { NOKIA_PCSUITE_ACM_INFO(0x0425), }, /* Nokia N71 */ + { NOKIA_PCSUITE_ACM_INFO(0x0486), }, /* Nokia N73 */ + { NOKIA_PCSUITE_ACM_INFO(0x04DF), }, /* Nokia N75 */ + { NOKIA_PCSUITE_ACM_INFO(0x000e), }, /* Nokia N77 */ + { NOKIA_PCSUITE_ACM_INFO(0x0445), }, /* Nokia N80 */ + { NOKIA_PCSUITE_ACM_INFO(0x042F), }, /* Nokia N91 & N91 8GB */ + { NOKIA_PCSUITE_ACM_INFO(0x048E), }, /* Nokia N92 */ + { NOKIA_PCSUITE_ACM_INFO(0x0420), }, /* Nokia N93 */ + { NOKIA_PCSUITE_ACM_INFO(0x04E6), }, /* Nokia N93i */ + { NOKIA_PCSUITE_ACM_INFO(0x04B2), }, /* Nokia 5700 XpressMusic */ + { NOKIA_PCSUITE_ACM_INFO(0x0134), }, /* Nokia 6110 Navigator (China) */ + { NOKIA_PCSUITE_ACM_INFO(0x046E), }, /* Nokia 6110 Navigator */ + { NOKIA_PCSUITE_ACM_INFO(0x002f), }, /* Nokia 6120 classic & */ + { NOKIA_PCSUITE_ACM_INFO(0x0088), }, /* Nokia 6121 classic */ + { NOKIA_PCSUITE_ACM_INFO(0x00fc), }, /* Nokia 6124 classic */ + { NOKIA_PCSUITE_ACM_INFO(0x0042), }, /* Nokia E51 */ + { NOKIA_PCSUITE_ACM_INFO(0x00b0), }, /* Nokia E66 */ + { NOKIA_PCSUITE_ACM_INFO(0x00ab), }, /* Nokia E71 */ + { NOKIA_PCSUITE_ACM_INFO(0x0481), }, /* Nokia N76 */ + { NOKIA_PCSUITE_ACM_INFO(0x0007), }, /* Nokia N81 & N81 8GB */ + { NOKIA_PCSUITE_ACM_INFO(0x0071), }, /* Nokia N82 */ + { NOKIA_PCSUITE_ACM_INFO(0x04F0), }, /* Nokia N95 & N95-3 NAM */ + { NOKIA_PCSUITE_ACM_INFO(0x0070), }, /* Nokia N95 8GB */ + { NOKIA_PCSUITE_ACM_INFO(0x00e9), }, /* Nokia 5320 XpressMusic */ + { NOKIA_PCSUITE_ACM_INFO(0x0099), }, /* Nokia 6210 Navigator, RM-367 */ + { NOKIA_PCSUITE_ACM_INFO(0x0128), }, /* Nokia 6210 Navigator, RM-419 */ + { NOKIA_PCSUITE_ACM_INFO(0x008f), }, /* Nokia 6220 Classic */ + { NOKIA_PCSUITE_ACM_INFO(0x00a0), }, /* Nokia 6650 */ + { NOKIA_PCSUITE_ACM_INFO(0x007b), }, /* Nokia N78 */ + { NOKIA_PCSUITE_ACM_INFO(0x0094), }, /* Nokia N85 */ + { NOKIA_PCSUITE_ACM_INFO(0x003a), }, /* Nokia N96 & N96-3 */ + { NOKIA_PCSUITE_ACM_INFO(0x00e9), }, /* Nokia 5320 XpressMusic */ + { NOKIA_PCSUITE_ACM_INFO(0x0108), }, /* Nokia 5320 XpressMusic 2G */ + { NOKIA_PCSUITE_ACM_INFO(0x01f5), }, /* Nokia N97, RM-505 */ + { NOKIA_PCSUITE_ACM_INFO(0x02e3), }, /* Nokia 5230, RM-588 */ + { NOKIA_PCSUITE_ACM_INFO(0x0178), }, /* Nokia E63 */ + { NOKIA_PCSUITE_ACM_INFO(0x010e), }, /* Nokia E75 */ + { NOKIA_PCSUITE_ACM_INFO(0x02d9), }, /* Nokia 6760 Slide */ + { NOKIA_PCSUITE_ACM_INFO(0x01d0), }, /* Nokia E52 */ + { NOKIA_PCSUITE_ACM_INFO(0x0223), }, /* Nokia E72 */ + { NOKIA_PCSUITE_ACM_INFO(0x0275), }, /* Nokia X6 */ + { NOKIA_PCSUITE_ACM_INFO(0x026c), }, /* Nokia N97 Mini */ + { NOKIA_PCSUITE_ACM_INFO(0x0154), }, /* Nokia 5800 XpressMusic */ + { NOKIA_PCSUITE_ACM_INFO(0x04ce), }, /* Nokia E90 */ + { NOKIA_PCSUITE_ACM_INFO(0x01d4), }, /* Nokia E55 */ + { NOKIA_PCSUITE_ACM_INFO(0x0302), }, /* Nokia N8 */ + { NOKIA_PCSUITE_ACM_INFO(0x0335), }, /* Nokia E7 */ + { NOKIA_PCSUITE_ACM_INFO(0x03cd), }, /* Nokia C7 */ + { SAMSUNG_PCSUITE_ACM_INFO(0x6651), }, /* Samsung GTi8510 (INNOV8) */ + + /* Support for Owen devices */ + { USB_DEVICE(0x03eb, 0x0030), }, /* Owen SI30 */ + + /* NOTE: non-Nokia COMM/ACM/0xff is likely MSFT RNDIS... NOT a modem! */ + + /* Support Lego NXT using pbLua firmware */ + { USB_DEVICE(0x0694, 0xff00), + .driver_info = NOT_A_MODEM, + }, + + /* Support for Droids MuIn LCD */ + { USB_DEVICE(0x04d8, 0x000b), + .driver_info = NO_DATA_INTERFACE, + }, + +#if IS_ENABLED(CONFIG_INPUT_IMS_PCU) + { USB_DEVICE(0x04d8, 0x0082), /* Application mode */ + .driver_info = IGNORE_DEVICE, + }, + { USB_DEVICE(0x04d8, 0x0083), /* Bootloader mode */ + .driver_info = IGNORE_DEVICE, + }, +#endif + + /* control interfaces without any protocol set */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_PROTO_NONE) }, + + /* control interfaces with various AT-command sets */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_V25TER) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_PCCA101) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_PCCA101_WAKE) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_GSM) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_3G) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_CDMA) }, + + { } +}; + +MODULE_DEVICE_TABLE(usb, acm_ids); + +static struct usb_driver acm_driver = { + .name = "cdc_acm", + .probe = acm_probe, + .disconnect = acm_disconnect, +#ifdef CONFIG_PM + .suspend = acm_suspend, + .resume = acm_resume, + .reset_resume = acm_reset_resume, +#endif + .id_table = acm_ids, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif + .disable_hub_initiated_lpm = 1, +}; + +/* + * TTY driver structures. + */ + +static const struct tty_operations acm_ops = { + .install = acm_tty_install, + .open = acm_tty_open, + .close = acm_tty_close, + .cleanup = acm_tty_cleanup, + .hangup = acm_tty_hangup, + .write = acm_tty_write, + .write_room = acm_tty_write_room, + .ioctl = acm_tty_ioctl, + .throttle = acm_tty_throttle, + .unthrottle = acm_tty_unthrottle, + .chars_in_buffer = acm_tty_chars_in_buffer, + .break_ctl = acm_tty_break_ctl, + .set_termios = acm_tty_set_termios, + .tiocmget = acm_tty_tiocmget, + .tiocmset = acm_tty_tiocmset, +}; + +/* + * Init / exit. + */ + +static int __init acm_init(void) +{ + int retval; + acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS); + if (!acm_tty_driver) + return -ENOMEM; + acm_tty_driver->driver_name = "acm", + acm_tty_driver->name = "ttyACM", + acm_tty_driver->major = ACM_TTY_MAJOR, + acm_tty_driver->minor_start = 0, + acm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + acm_tty_driver->subtype = SERIAL_TYPE_NORMAL, + acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + acm_tty_driver->init_termios = tty_std_termios; + acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | + HUPCL | CLOCAL; + tty_set_operations(acm_tty_driver, &acm_ops); + + retval = tty_register_driver(acm_tty_driver); + if (retval) { + put_tty_driver(acm_tty_driver); + return retval; + } + + retval = usb_register(&acm_driver); + if (retval) { + tty_unregister_driver(acm_tty_driver); + put_tty_driver(acm_tty_driver); + return retval; + } + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + + return 0; +} + +static void __exit acm_exit(void) +{ + usb_deregister(&acm_driver); + tty_unregister_driver(acm_tty_driver); + put_tty_driver(acm_tty_driver); +} + +module_init(acm_init); +module_exit(acm_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(ACM_TTY_MAJOR); diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h new file mode 100644 index 00000000..1683ac16 --- /dev/null +++ b/drivers/usb/class/cdc-acm.h @@ -0,0 +1,131 @@ +/* + * + * Includes for cdc-acm.c + * + * Mainly take from usbnet's cdc-ether part + * + */ + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ + +#define ACM_TTY_MAJOR 166 +#define ACM_TTY_MINORS 32 + +/* + * Requests. + */ + +#define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +/* + * Output control lines. + */ + +#define ACM_CTRL_DTR 0x01 +#define ACM_CTRL_RTS 0x02 + +/* + * Input control lines and line errors. + */ + +#define ACM_CTRL_DCD 0x01 +#define ACM_CTRL_DSR 0x02 +#define ACM_CTRL_BRK 0x04 +#define ACM_CTRL_RI 0x08 + +#define ACM_CTRL_FRAMING 0x10 +#define ACM_CTRL_PARITY 0x20 +#define ACM_CTRL_OVERRUN 0x40 + +/* + * Internal driver structures. + */ + +/* + * The only reason to have several buffers is to accommodate assumptions + * in line disciplines. They ask for empty space amount, receive our URB size, + * and proceed to issue several 1-character writes, assuming they will fit. + * The very first write takes a complete URB. Fortunately, this only happens + * when processing onlcr, so we only need 2 buffers. These values must be + * powers of 2. + */ +#define ACM_NW 16 +#define ACM_NR 16 + +struct acm_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct acm *instance; +}; + +struct acm_rb { + int size; + unsigned char *base; + dma_addr_t dma; + int index; + struct acm *instance; +}; + +struct acm { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *control; /* control interface */ + struct usb_interface *data; /* data interface */ + struct tty_port port; /* our tty port data */ + struct urb *ctrlurb; /* urbs */ + u8 *ctrl_buffer; /* buffers of urbs */ + dma_addr_t ctrl_dma; /* dma handles of buffers */ + u8 *country_codes; /* country codes from device */ + unsigned int country_code_size; /* size of this buffer */ + unsigned int country_rel_date; /* release date of version */ + struct acm_wb wb[ACM_NW]; + unsigned long read_urbs_free; + struct urb *read_urbs[ACM_NR]; + struct acm_rb read_buffers[ACM_NR]; + int rx_buflimit; + int rx_endpoint; + spinlock_t read_lock; + int write_used; /* number of non-empty write buffers */ + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + struct usb_cdc_line_coding line; /* bits, stop, parity */ + struct work_struct work; /* work queue entry for line discipline waking up */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ + unsigned int minor; /* acm minor number */ + unsigned char clocal; /* termios CLOCAL */ + unsigned int ctrl_caps; /* control capabilities from the class specific header */ + unsigned int susp_count; /* number of suspended interfaces */ + unsigned int combined_interfaces:1; /* control and data collapsed */ + unsigned int is_int_ep:1; /* interrupt endpoints contrary to spec used */ + unsigned int throttled:1; /* actually throttled */ + unsigned int throttle_req:1; /* throttle requested */ + u8 bInterval; + struct usb_anchor delayed; /* writes queued for a device about to be woken */ +}; + +#define CDC_DATA_INTERFACE_TYPE 0x0a + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL 1 +#define SINGLE_RX_URB 2 +#define NO_CAP_LINE 4 +#define NOT_A_MODEM 8 +#define NO_DATA_INTERFACE 16 +#define IGNORE_DEVICE 32 diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c new file mode 100644 index 00000000..6463ca3b --- /dev/null +++ b/drivers/usb/class/cdc-wdm.c @@ -0,0 +1,1073 @@ +/* + * cdc-wdm.c + * + * This driver supports USB CDC WCM Device Management. + * + * Copyright (c) 2007-2009 Oliver Neukum + * + * Some code taken from cdc-acm.c + * + * Released under the GPLv2. + * + * Many thanks to Carl Nordbeck + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.03" +#define DRIVER_AUTHOR "Oliver Neukum" +#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management" + +static const struct usb_device_id wdm_ids[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_DMM + }, + { } +}; + +MODULE_DEVICE_TABLE (usb, wdm_ids); + +#define WDM_MINOR_BASE 176 + + +#define WDM_IN_USE 1 +#define WDM_DISCONNECTING 2 +#define WDM_RESULT 3 +#define WDM_READ 4 +#define WDM_INT_STALL 5 +#define WDM_POLL_RUNNING 6 +#define WDM_RESPONDING 7 +#define WDM_SUSPENDING 8 +#define WDM_RESETTING 9 +#define WDM_OVERFLOW 10 + +#define WDM_MAX 16 + +/* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */ +#define WDM_DEFAULT_BUFSIZE 256 + +static DEFINE_MUTEX(wdm_mutex); +static DEFINE_SPINLOCK(wdm_device_list_lock); +static LIST_HEAD(wdm_device_list); + +/* --- method tables --- */ + +struct wdm_device { + u8 *inbuf; /* buffer for response */ + u8 *outbuf; /* buffer for command */ + u8 *sbuf; /* buffer for status */ + u8 *ubuf; /* buffer for copy to user space */ + + struct urb *command; + struct urb *response; + struct urb *validity; + struct usb_interface *intf; + struct usb_ctrlrequest *orq; + struct usb_ctrlrequest *irq; + spinlock_t iuspin; + + unsigned long flags; + u16 bufsize; + u16 wMaxCommand; + u16 wMaxPacketSize; + __le16 inum; + int reslength; + int length; + int read; + int count; + dma_addr_t shandle; + dma_addr_t ihandle; + struct mutex wlock; + struct mutex rlock; + wait_queue_head_t wait; + struct work_struct rxwork; + int werr; + int rerr; + + struct list_head device_list; + int (*manage_power)(struct usb_interface *, int); +}; + +static struct usb_driver wdm_driver; + +/* return intfdata if we own the interface, else look up intf in the list */ +static struct wdm_device *wdm_find_device(struct usb_interface *intf) +{ + struct wdm_device *desc; + + spin_lock(&wdm_device_list_lock); + list_for_each_entry(desc, &wdm_device_list, device_list) + if (desc->intf == intf) + goto found; + desc = NULL; +found: + spin_unlock(&wdm_device_list_lock); + + return desc; +} + +static struct wdm_device *wdm_find_device_by_minor(int minor) +{ + struct wdm_device *desc; + + spin_lock(&wdm_device_list_lock); + list_for_each_entry(desc, &wdm_device_list, device_list) + if (desc->intf->minor == minor) + goto found; + desc = NULL; +found: + spin_unlock(&wdm_device_list_lock); + + return desc; +} + +/* --- callbacks --- */ +static void wdm_out_callback(struct urb *urb) +{ + struct wdm_device *desc; + desc = urb->context; + spin_lock(&desc->iuspin); + desc->werr = urb->status; + spin_unlock(&desc->iuspin); + kfree(desc->outbuf); + desc->outbuf = NULL; + clear_bit(WDM_IN_USE, &desc->flags); + wake_up(&desc->wait); +} + +static void wdm_in_callback(struct urb *urb) +{ + struct wdm_device *desc = urb->context; + int status = urb->status; + int length = urb->actual_length; + + spin_lock(&desc->iuspin); + clear_bit(WDM_RESPONDING, &desc->flags); + + if (status) { + switch (status) { + case -ENOENT: + dev_dbg(&desc->intf->dev, + "nonzero urb status received: -ENOENT"); + goto skip_error; + case -ECONNRESET: + dev_dbg(&desc->intf->dev, + "nonzero urb status received: -ECONNRESET"); + goto skip_error; + case -ESHUTDOWN: + dev_dbg(&desc->intf->dev, + "nonzero urb status received: -ESHUTDOWN"); + goto skip_error; + case -EPIPE: + dev_err(&desc->intf->dev, + "nonzero urb status received: -EPIPE\n"); + break; + default: + dev_err(&desc->intf->dev, + "Unexpected error %d\n", status); + break; + } + } + + desc->rerr = status; + if (length + desc->length > desc->wMaxCommand) { + /* The buffer would overflow */ + set_bit(WDM_OVERFLOW, &desc->flags); + } else { + /* we may already be in overflow */ + if (!test_bit(WDM_OVERFLOW, &desc->flags)) { + memmove(desc->ubuf + desc->length, desc->inbuf, length); + desc->length += length; + desc->reslength = length; + } + } +skip_error: + wake_up(&desc->wait); + + set_bit(WDM_READ, &desc->flags); + spin_unlock(&desc->iuspin); +} + +static void wdm_int_callback(struct urb *urb) +{ + int rv = 0; + int responding; + int status = urb->status; + struct wdm_device *desc; + struct usb_cdc_notification *dr; + + desc = urb->context; + dr = (struct usb_cdc_notification *)desc->sbuf; + + if (status) { + switch (status) { + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + return; /* unplug */ + case -EPIPE: + set_bit(WDM_INT_STALL, &desc->flags); + dev_err(&desc->intf->dev, "Stall on int endpoint\n"); + goto sw; /* halt is cleared in work */ + default: + dev_err(&desc->intf->dev, + "nonzero urb status received: %d\n", status); + break; + } + } + + if (urb->actual_length < sizeof(struct usb_cdc_notification)) { + dev_err(&desc->intf->dev, "wdm_int_callback - %d bytes\n", + urb->actual_length); + goto exit; + } + + switch (dr->bNotificationType) { + case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: + dev_dbg(&desc->intf->dev, + "NOTIFY_RESPONSE_AVAILABLE received: index %d len %d", + dr->wIndex, dr->wLength); + break; + + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + + dev_dbg(&desc->intf->dev, + "NOTIFY_NETWORK_CONNECTION %s network", + dr->wValue ? "connected to" : "disconnected from"); + goto exit; + default: + clear_bit(WDM_POLL_RUNNING, &desc->flags); + dev_err(&desc->intf->dev, + "unknown notification %d received: index %d len %d\n", + dr->bNotificationType, dr->wIndex, dr->wLength); + goto exit; + } + + spin_lock(&desc->iuspin); + clear_bit(WDM_READ, &desc->flags); + responding = test_and_set_bit(WDM_RESPONDING, &desc->flags); + if (!responding && !test_bit(WDM_DISCONNECTING, &desc->flags) + && !test_bit(WDM_SUSPENDING, &desc->flags)) { + rv = usb_submit_urb(desc->response, GFP_ATOMIC); + dev_dbg(&desc->intf->dev, "%s: usb_submit_urb %d", + __func__, rv); + } + spin_unlock(&desc->iuspin); + if (rv < 0) { + clear_bit(WDM_RESPONDING, &desc->flags); + if (rv == -EPERM) + return; + if (rv == -ENOMEM) { +sw: + rv = schedule_work(&desc->rxwork); + if (rv) + dev_err(&desc->intf->dev, + "Cannot schedule work\n"); + } + } +exit: + rv = usb_submit_urb(urb, GFP_ATOMIC); + if (rv) + dev_err(&desc->intf->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, rv); + +} + +static void kill_urbs(struct wdm_device *desc) +{ + /* the order here is essential */ + usb_kill_urb(desc->command); + usb_kill_urb(desc->validity); + usb_kill_urb(desc->response); +} + +static void free_urbs(struct wdm_device *desc) +{ + usb_free_urb(desc->validity); + usb_free_urb(desc->response); + usb_free_urb(desc->command); +} + +static void cleanup(struct wdm_device *desc) +{ + kfree(desc->sbuf); + kfree(desc->inbuf); + kfree(desc->orq); + kfree(desc->irq); + kfree(desc->ubuf); + free_urbs(desc); + kfree(desc); +} + +static ssize_t wdm_write +(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + u8 *buf; + int rv = -EMSGSIZE, r, we; + struct wdm_device *desc = file->private_data; + struct usb_ctrlrequest *req; + + if (count > desc->wMaxCommand) + count = desc->wMaxCommand; + + spin_lock_irq(&desc->iuspin); + we = desc->werr; + desc->werr = 0; + spin_unlock_irq(&desc->iuspin); + if (we < 0) + return -EIO; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) { + rv = -ENOMEM; + goto outnl; + } + + r = copy_from_user(buf, buffer, count); + if (r > 0) { + kfree(buf); + rv = -EFAULT; + goto outnl; + } + + /* concurrent writes and disconnect */ + r = mutex_lock_interruptible(&desc->wlock); + rv = -ERESTARTSYS; + if (r) { + kfree(buf); + goto outnl; + } + + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + kfree(buf); + rv = -ENODEV; + goto outnp; + } + + r = usb_autopm_get_interface(desc->intf); + if (r < 0) { + kfree(buf); + rv = usb_translate_errors(r); + goto outnp; + } + + if (!(file->f_flags & O_NONBLOCK)) + r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE, + &desc->flags)); + else + if (test_bit(WDM_IN_USE, &desc->flags)) + r = -EAGAIN; + + if (test_bit(WDM_RESETTING, &desc->flags)) + r = -EIO; + + if (r < 0) { + kfree(buf); + rv = r; + goto out; + } + + req = desc->orq; + usb_fill_control_urb( + desc->command, + interface_to_usbdev(desc->intf), + /* using common endpoint 0 */ + usb_sndctrlpipe(interface_to_usbdev(desc->intf), 0), + (unsigned char *)req, + buf, + count, + wdm_out_callback, + desc + ); + + req->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE); + req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND; + req->wValue = 0; + req->wIndex = desc->inum; + req->wLength = cpu_to_le16(count); + set_bit(WDM_IN_USE, &desc->flags); + desc->outbuf = buf; + + rv = usb_submit_urb(desc->command, GFP_KERNEL); + if (rv < 0) { + kfree(buf); + desc->outbuf = NULL; + clear_bit(WDM_IN_USE, &desc->flags); + dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv); + rv = usb_translate_errors(rv); + } else { + dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d", + req->wIndex); + } +out: + usb_autopm_put_interface(desc->intf); +outnp: + mutex_unlock(&desc->wlock); +outnl: + return rv < 0 ? rv : count; +} + +static ssize_t wdm_read +(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + int rv, cntr; + int i = 0; + struct wdm_device *desc = file->private_data; + + + rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */ + if (rv < 0) + return -ERESTARTSYS; + + cntr = ACCESS_ONCE(desc->length); + if (cntr == 0) { + desc->read = 0; +retry: + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + rv = -ENODEV; + goto err; + } + if (test_bit(WDM_OVERFLOW, &desc->flags)) { + clear_bit(WDM_OVERFLOW, &desc->flags); + rv = -ENOBUFS; + goto err; + } + i++; + if (file->f_flags & O_NONBLOCK) { + if (!test_bit(WDM_READ, &desc->flags)) { + rv = cntr ? cntr : -EAGAIN; + goto err; + } + rv = 0; + } else { + rv = wait_event_interruptible(desc->wait, + test_bit(WDM_READ, &desc->flags)); + } + + /* may have happened while we slept */ + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + rv = -ENODEV; + goto err; + } + if (test_bit(WDM_RESETTING, &desc->flags)) { + rv = -EIO; + goto err; + } + usb_mark_last_busy(interface_to_usbdev(desc->intf)); + if (rv < 0) { + rv = -ERESTARTSYS; + goto err; + } + + spin_lock_irq(&desc->iuspin); + + if (desc->rerr) { /* read completed, error happened */ + desc->rerr = 0; + spin_unlock_irq(&desc->iuspin); + rv = -EIO; + goto err; + } + /* + * recheck whether we've lost the race + * against the completion handler + */ + if (!test_bit(WDM_READ, &desc->flags)) { /* lost race */ + spin_unlock_irq(&desc->iuspin); + goto retry; + } + + if (!desc->reslength) { /* zero length read */ + dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); + clear_bit(WDM_READ, &desc->flags); + spin_unlock_irq(&desc->iuspin); + goto retry; + } + cntr = desc->length; + spin_unlock_irq(&desc->iuspin); + } + + if (cntr > count) + cntr = count; + rv = copy_to_user(buffer, desc->ubuf, cntr); + if (rv > 0) { + rv = -EFAULT; + goto err; + } + + spin_lock_irq(&desc->iuspin); + + for (i = 0; i < desc->length - cntr; i++) + desc->ubuf[i] = desc->ubuf[i + cntr]; + + desc->length -= cntr; + /* in case we had outstanding data */ + if (!desc->length) + clear_bit(WDM_READ, &desc->flags); + + spin_unlock_irq(&desc->iuspin); + + rv = cntr; + +err: + mutex_unlock(&desc->rlock); + return rv; +} + +static int wdm_flush(struct file *file, fl_owner_t id) +{ + struct wdm_device *desc = file->private_data; + + wait_event(desc->wait, !test_bit(WDM_IN_USE, &desc->flags)); + + /* cannot dereference desc->intf if WDM_DISCONNECTING */ + if (desc->werr < 0 && !test_bit(WDM_DISCONNECTING, &desc->flags)) + dev_err(&desc->intf->dev, "Error in flush path: %d\n", + desc->werr); + + return usb_translate_errors(desc->werr); +} + +static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait) +{ + struct wdm_device *desc = file->private_data; + unsigned long flags; + unsigned int mask = 0; + + spin_lock_irqsave(&desc->iuspin, flags); + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + mask = POLLHUP | POLLERR; + spin_unlock_irqrestore(&desc->iuspin, flags); + goto desc_out; + } + if (test_bit(WDM_READ, &desc->flags)) + mask = POLLIN | POLLRDNORM; + if (desc->rerr || desc->werr) + mask |= POLLERR; + if (!test_bit(WDM_IN_USE, &desc->flags)) + mask |= POLLOUT | POLLWRNORM; + spin_unlock_irqrestore(&desc->iuspin, flags); + + poll_wait(file, &desc->wait, wait); + +desc_out: + return mask; +} + +static int wdm_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + int rv = -ENODEV; + struct usb_interface *intf; + struct wdm_device *desc; + + mutex_lock(&wdm_mutex); + desc = wdm_find_device_by_minor(minor); + if (!desc) + goto out; + + intf = desc->intf; + if (test_bit(WDM_DISCONNECTING, &desc->flags)) + goto out; + file->private_data = desc; + + rv = usb_autopm_get_interface(desc->intf); + if (rv < 0) { + dev_err(&desc->intf->dev, "Error autopm - %d\n", rv); + goto out; + } + + /* using write lock to protect desc->count */ + mutex_lock(&desc->wlock); + if (!desc->count++) { + desc->werr = 0; + desc->rerr = 0; + rv = usb_submit_urb(desc->validity, GFP_KERNEL); + if (rv < 0) { + desc->count--; + dev_err(&desc->intf->dev, + "Error submitting int urb - %d\n", rv); + rv = usb_translate_errors(rv); + } + } else { + rv = 0; + } + mutex_unlock(&desc->wlock); + if (desc->count == 1) + desc->manage_power(intf, 1); + usb_autopm_put_interface(desc->intf); +out: + mutex_unlock(&wdm_mutex); + return rv; +} + +static int wdm_release(struct inode *inode, struct file *file) +{ + struct wdm_device *desc = file->private_data; + + mutex_lock(&wdm_mutex); + + /* using write lock to protect desc->count */ + mutex_lock(&desc->wlock); + desc->count--; + mutex_unlock(&desc->wlock); + + if (!desc->count) { + if (!test_bit(WDM_DISCONNECTING, &desc->flags)) { + dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); + kill_urbs(desc); + desc->manage_power(desc->intf, 0); + } else { + /* must avoid dev_printk here as desc->intf is invalid */ + pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__); + cleanup(desc); + } + } + mutex_unlock(&wdm_mutex); + return 0; +} + +static long wdm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct wdm_device *desc = file->private_data; + int rv = 0; + + switch (cmd) { + case IOCTL_WDM_MAX_COMMAND: + if (copy_to_user((void __user *)arg, &desc->wMaxCommand, sizeof(desc->wMaxCommand))) + rv = -EFAULT; + break; + default: + rv = -ENOTTY; + } + return rv; +} + +static const struct file_operations wdm_fops = { + .owner = THIS_MODULE, + .read = wdm_read, + .write = wdm_write, + .open = wdm_open, + .flush = wdm_flush, + .release = wdm_release, + .poll = wdm_poll, + .unlocked_ioctl = wdm_ioctl, + .compat_ioctl = wdm_ioctl, + .llseek = noop_llseek, +}; + +static struct usb_class_driver wdm_class = { + .name = "cdc-wdm%d", + .fops = &wdm_fops, + .minor_base = WDM_MINOR_BASE, +}; + +/* --- error handling --- */ +static void wdm_rxwork(struct work_struct *work) +{ + struct wdm_device *desc = container_of(work, struct wdm_device, rxwork); + unsigned long flags; + int rv = 0; + int responding; + + spin_lock_irqsave(&desc->iuspin, flags); + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + spin_unlock_irqrestore(&desc->iuspin, flags); + } else { + responding = test_and_set_bit(WDM_RESPONDING, &desc->flags); + spin_unlock_irqrestore(&desc->iuspin, flags); + if (!responding) + rv = usb_submit_urb(desc->response, GFP_KERNEL); + if (rv < 0 && rv != -EPERM) { + spin_lock_irqsave(&desc->iuspin, flags); + clear_bit(WDM_RESPONDING, &desc->flags); + if (!test_bit(WDM_DISCONNECTING, &desc->flags)) + schedule_work(&desc->rxwork); + spin_unlock_irqrestore(&desc->iuspin, flags); + } + } +} + +/* --- hotplug --- */ + +static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, + u16 bufsize, int (*manage_power)(struct usb_interface *, int)) +{ + int rv = -ENOMEM; + struct wdm_device *desc; + + desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL); + if (!desc) + goto out; + INIT_LIST_HEAD(&desc->device_list); + mutex_init(&desc->rlock); + mutex_init(&desc->wlock); + spin_lock_init(&desc->iuspin); + init_waitqueue_head(&desc->wait); + desc->wMaxCommand = bufsize; + /* this will be expanded and needed in hardware endianness */ + desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber); + desc->intf = intf; + INIT_WORK(&desc->rxwork, wdm_rxwork); + + rv = -EINVAL; + if (!usb_endpoint_is_int_in(ep)) + goto err; + + desc->wMaxPacketSize = usb_endpoint_maxp(ep); + + desc->orq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!desc->orq) + goto err; + desc->irq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!desc->irq) + goto err; + + desc->validity = usb_alloc_urb(0, GFP_KERNEL); + if (!desc->validity) + goto err; + + desc->response = usb_alloc_urb(0, GFP_KERNEL); + if (!desc->response) + goto err; + + desc->command = usb_alloc_urb(0, GFP_KERNEL); + if (!desc->command) + goto err; + + desc->ubuf = kmalloc(desc->wMaxCommand, GFP_KERNEL); + if (!desc->ubuf) + goto err; + + desc->sbuf = kmalloc(desc->wMaxPacketSize, GFP_KERNEL); + if (!desc->sbuf) + goto err; + + desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL); + if (!desc->inbuf) + goto err; + + usb_fill_int_urb( + desc->validity, + interface_to_usbdev(intf), + usb_rcvintpipe(interface_to_usbdev(intf), ep->bEndpointAddress), + desc->sbuf, + desc->wMaxPacketSize, + wdm_int_callback, + desc, + ep->bInterval + ); + + desc->irq->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); + desc->irq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; + desc->irq->wValue = 0; + desc->irq->wIndex = desc->inum; + desc->irq->wLength = cpu_to_le16(desc->wMaxCommand); + + usb_fill_control_urb( + desc->response, + interface_to_usbdev(intf), + /* using common endpoint 0 */ + usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0), + (unsigned char *)desc->irq, + desc->inbuf, + desc->wMaxCommand, + wdm_in_callback, + desc + ); + + desc->manage_power = manage_power; + + spin_lock(&wdm_device_list_lock); + list_add(&desc->device_list, &wdm_device_list); + spin_unlock(&wdm_device_list_lock); + + rv = usb_register_dev(intf, &wdm_class); + if (rv < 0) + goto err; + else + dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev)); +out: + return rv; +err: + spin_lock(&wdm_device_list_lock); + list_del(&desc->device_list); + spin_unlock(&wdm_device_list_lock); + cleanup(desc); + return rv; +} + +static int wdm_manage_power(struct usb_interface *intf, int on) +{ + /* need autopm_get/put here to ensure the usbcore sees the new value */ + int rv = usb_autopm_get_interface(intf); + + intf->needs_remote_wakeup = on; + if (!rv) + usb_autopm_put_interface(intf); + return 0; +} + +static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + int rv = -EINVAL; + struct usb_host_interface *iface; + struct usb_endpoint_descriptor *ep; + struct usb_cdc_dmm_desc *dmhd; + u8 *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + u16 maxcom = WDM_DEFAULT_BUFSIZE; + + if (!buffer) + goto err; + while (buflen > 2) { + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); + goto next_desc; + } + + switch (buffer[2]) { + case USB_CDC_HEADER_TYPE: + break; + case USB_CDC_DMM_TYPE: + dmhd = (struct usb_cdc_dmm_desc *)buffer; + maxcom = le16_to_cpu(dmhd->wMaxCommand); + dev_dbg(&intf->dev, + "Finding maximum buffer length: %d", maxcom); + break; + default: + dev_err(&intf->dev, + "Ignoring extra header, type %d, length %d\n", + buffer[2], buffer[0]); + break; + } +next_desc: + buflen -= buffer[0]; + buffer += buffer[0]; + } + + iface = intf->cur_altsetting; + if (iface->desc.bNumEndpoints != 1) + goto err; + ep = &iface->endpoint[0].desc; + + rv = wdm_create(intf, ep, maxcom, &wdm_manage_power); + +err: + return rv; +} + +/** + * usb_cdc_wdm_register - register a WDM subdriver + * @intf: usb interface the subdriver will associate with + * @ep: interrupt endpoint to monitor for notifications + * @bufsize: maximum message size to support for read/write + * + * Create WDM usb class character device and associate it with intf + * without binding, allowing another driver to manage the interface. + * + * The subdriver will manage the given interrupt endpoint exclusively + * and will issue control requests referring to the given intf. It + * will otherwise avoid interferring, and in particular not do + * usb_set_intfdata/usb_get_intfdata on intf. + * + * The return value is a pointer to the subdriver's struct usb_driver. + * The registering driver is responsible for calling this subdriver's + * disconnect, suspend, resume, pre_reset and post_reset methods from + * its own. + */ +struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf, + struct usb_endpoint_descriptor *ep, + int bufsize, + int (*manage_power)(struct usb_interface *, int)) +{ + int rv = -EINVAL; + + rv = wdm_create(intf, ep, bufsize, manage_power); + if (rv < 0) + goto err; + + return &wdm_driver; +err: + return ERR_PTR(rv); +} +EXPORT_SYMBOL(usb_cdc_wdm_register); + +static void wdm_disconnect(struct usb_interface *intf) +{ + struct wdm_device *desc; + unsigned long flags; + + usb_deregister_dev(intf, &wdm_class); + desc = wdm_find_device(intf); + mutex_lock(&wdm_mutex); + + /* the spinlock makes sure no new urbs are generated in the callbacks */ + spin_lock_irqsave(&desc->iuspin, flags); + set_bit(WDM_DISCONNECTING, &desc->flags); + set_bit(WDM_READ, &desc->flags); + /* to terminate pending flushes */ + clear_bit(WDM_IN_USE, &desc->flags); + spin_unlock_irqrestore(&desc->iuspin, flags); + wake_up_all(&desc->wait); + mutex_lock(&desc->rlock); + mutex_lock(&desc->wlock); + kill_urbs(desc); + cancel_work_sync(&desc->rxwork); + mutex_unlock(&desc->wlock); + mutex_unlock(&desc->rlock); + + /* the desc->intf pointer used as list key is now invalid */ + spin_lock(&wdm_device_list_lock); + list_del(&desc->device_list); + spin_unlock(&wdm_device_list_lock); + + if (!desc->count) + cleanup(desc); + else + dev_dbg(&intf->dev, "%s: %d open files - postponing cleanup\n", __func__, desc->count); + mutex_unlock(&wdm_mutex); +} + +#ifdef CONFIG_PM +static int wdm_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct wdm_device *desc = wdm_find_device(intf); + int rv = 0; + + dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor); + + /* if this is an autosuspend the caller does the locking */ + if (!PMSG_IS_AUTO(message)) { + mutex_lock(&desc->rlock); + mutex_lock(&desc->wlock); + } + spin_lock_irq(&desc->iuspin); + + if (PMSG_IS_AUTO(message) && + (test_bit(WDM_IN_USE, &desc->flags) + || test_bit(WDM_RESPONDING, &desc->flags))) { + spin_unlock_irq(&desc->iuspin); + rv = -EBUSY; + } else { + + set_bit(WDM_SUSPENDING, &desc->flags); + spin_unlock_irq(&desc->iuspin); + /* callback submits work - order is essential */ + kill_urbs(desc); + cancel_work_sync(&desc->rxwork); + } + if (!PMSG_IS_AUTO(message)) { + mutex_unlock(&desc->wlock); + mutex_unlock(&desc->rlock); + } + + return rv; +} +#endif + +static int recover_from_urb_loss(struct wdm_device *desc) +{ + int rv = 0; + + if (desc->count) { + rv = usb_submit_urb(desc->validity, GFP_NOIO); + if (rv < 0) + dev_err(&desc->intf->dev, + "Error resume submitting int urb - %d\n", rv); + } + return rv; +} + +#ifdef CONFIG_PM +static int wdm_resume(struct usb_interface *intf) +{ + struct wdm_device *desc = wdm_find_device(intf); + int rv; + + dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); + + clear_bit(WDM_SUSPENDING, &desc->flags); + rv = recover_from_urb_loss(desc); + + return rv; +} +#endif + +static int wdm_pre_reset(struct usb_interface *intf) +{ + struct wdm_device *desc = wdm_find_device(intf); + + /* + * we notify everybody using poll of + * an exceptional situation + * must be done before recovery lest a spontaneous + * message from the device is lost + */ + spin_lock_irq(&desc->iuspin); + set_bit(WDM_RESETTING, &desc->flags); /* inform read/write */ + set_bit(WDM_READ, &desc->flags); /* unblock read */ + clear_bit(WDM_IN_USE, &desc->flags); /* unblock write */ + desc->rerr = -EINTR; + spin_unlock_irq(&desc->iuspin); + wake_up_all(&desc->wait); + mutex_lock(&desc->rlock); + mutex_lock(&desc->wlock); + kill_urbs(desc); + cancel_work_sync(&desc->rxwork); + return 0; +} + +static int wdm_post_reset(struct usb_interface *intf) +{ + struct wdm_device *desc = wdm_find_device(intf); + int rv; + + clear_bit(WDM_OVERFLOW, &desc->flags); + clear_bit(WDM_RESETTING, &desc->flags); + rv = recover_from_urb_loss(desc); + mutex_unlock(&desc->wlock); + mutex_unlock(&desc->rlock); + return 0; +} + +static struct usb_driver wdm_driver = { + .name = "cdc_wdm", + .probe = wdm_probe, + .disconnect = wdm_disconnect, +#ifdef CONFIG_PM + .suspend = wdm_suspend, + .resume = wdm_resume, + .reset_resume = wdm_resume, +#endif + .pre_reset = wdm_pre_reset, + .post_reset = wdm_post_reset, + .id_table = wdm_ids, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(wdm_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c new file mode 100644 index 00000000..d4c47d5d --- /dev/null +++ b/drivers/usb/class/usblp.c @@ -0,0 +1,1433 @@ +/* + * usblp.c + * + * Copyright (c) 1999 Michael Gee + * Copyright (c) 1999 Pavel Machek + * Copyright (c) 2000 Randy Dunlap + * Copyright (c) 2000 Vojtech Pavlik + # Copyright (c) 2001 Pete Zaitcev + # Copyright (c) 2001 David Paschal + * Copyright (c) 2006 Oliver Neukum + * + * USB Printer Device Class driver for USB printers and printer cables + * + * Sponsored by SuSE + * + * ChangeLog: + * v0.1 - thorough cleaning, URBification, almost a rewrite + * v0.2 - some more cleanups + * v0.3 - cleaner again, waitqueue fixes + * v0.4 - fixes in unidirectional mode + * v0.5 - add DEVICE_ID string support + * v0.6 - never time out + * v0.7 - fixed bulk-IN read and poll (David Paschal) + * v0.8 - add devfs support + * v0.9 - fix unplug-while-open paths + * v0.10- remove sleep_on, fix error on oom (oliver@neukum.org) + * v0.11 - add proto_bias option (Pete Zaitcev) + * v0.12 - add hpoj.sourceforge.net ioctls (David Paschal) + * v0.13 - alloc space for statusbuf ( not on stack); + * use usb_alloc_coherent() for read buf & write buf; + * none - Maintained in Linux kernel after v0.13 + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#undef DEBUG +#include +#include + +/* + * Version Information + */ +#define DRIVER_AUTHOR "Michael Gee, Pavel Machek, Vojtech Pavlik, Randy Dunlap, Pete Zaitcev, David Paschal" +#define DRIVER_DESC "USB Printer Device Class driver" + +#define USBLP_BUF_SIZE 8192 +#define USBLP_BUF_SIZE_IN 1024 +#define USBLP_DEVICE_ID_SIZE 1024 + +/* ioctls: */ +#define IOCNR_GET_DEVICE_ID 1 +#define IOCNR_GET_PROTOCOLS 2 +#define IOCNR_SET_PROTOCOL 3 +#define IOCNR_HP_SET_CHANNEL 4 +#define IOCNR_GET_BUS_ADDRESS 5 +#define IOCNR_GET_VID_PID 6 +#define IOCNR_SOFT_RESET 7 +/* Get device_id string: */ +#define LPIOC_GET_DEVICE_ID(len) _IOC(_IOC_READ, 'P', IOCNR_GET_DEVICE_ID, len) +/* The following ioctls were added for http://hpoj.sourceforge.net: */ +/* Get two-int array: + * [0]=current protocol (1=7/1/1, 2=7/1/2, 3=7/1/3), + * [1]=supported protocol mask (mask&(1<wmut locks wstatus. + * ->mut locks the whole usblp, except [rw]complete, and thus, by indirection, + * [rw]status. We only touch status when we know the side idle. + * ->lock locks what interrupt accesses. + */ +struct usblp { + struct usb_device *dev; /* USB device */ + struct mutex wmut; + struct mutex mut; + spinlock_t lock; /* locks rcomplete, wcomplete */ + char *readbuf; /* read transfer_buffer */ + char *statusbuf; /* status transfer_buffer */ + struct usb_anchor urbs; + wait_queue_head_t rwait, wwait; + int readcount; /* Counter for reads */ + int ifnum; /* Interface number */ + struct usb_interface *intf; /* The interface */ + /* Alternate-setting numbers and endpoints for each protocol + * (7/1/{index=1,2,3}) that the device supports: */ + struct { + int alt_setting; + struct usb_endpoint_descriptor *epwrite; + struct usb_endpoint_descriptor *epread; + } protocol[USBLP_MAX_PROTOCOLS]; + int current_protocol; + int minor; /* minor number of device */ + int wcomplete, rcomplete; + int wstatus; /* bytes written or error */ + int rstatus; /* bytes ready or error */ + unsigned int quirks; /* quirks flags */ + unsigned int flags; /* mode flags */ + unsigned char used; /* True if open */ + unsigned char present; /* True if not disconnected */ + unsigned char bidir; /* interface is bidirectional */ + unsigned char no_paper; /* Paper Out happened */ + unsigned char *device_id_string; /* IEEE 1284 DEVICE ID string (ptr) */ + /* first 2 bytes are (big-endian) length */ +}; + +#ifdef DEBUG +static void usblp_dump(struct usblp *usblp) +{ + struct device *dev = &usblp->intf->dev; + int p; + + dev_dbg(dev, "usblp=0x%p\n", usblp); + dev_dbg(dev, "dev=0x%p\n", usblp->dev); + dev_dbg(dev, "present=%d\n", usblp->present); + dev_dbg(dev, "readbuf=0x%p\n", usblp->readbuf); + dev_dbg(dev, "readcount=%d\n", usblp->readcount); + dev_dbg(dev, "ifnum=%d\n", usblp->ifnum); + for (p = USBLP_FIRST_PROTOCOL; p <= USBLP_LAST_PROTOCOL; p++) { + dev_dbg(dev, "protocol[%d].alt_setting=%d\n", p, + usblp->protocol[p].alt_setting); + dev_dbg(dev, "protocol[%d].epwrite=%p\n", p, + usblp->protocol[p].epwrite); + dev_dbg(dev, "protocol[%d].epread=%p\n", p, + usblp->protocol[p].epread); + } + dev_dbg(dev, "current_protocol=%d\n", usblp->current_protocol); + dev_dbg(dev, "minor=%d\n", usblp->minor); + dev_dbg(dev, "wstatus=%d\n", usblp->wstatus); + dev_dbg(dev, "rstatus=%d\n", usblp->rstatus); + dev_dbg(dev, "quirks=%d\n", usblp->quirks); + dev_dbg(dev, "used=%d\n", usblp->used); + dev_dbg(dev, "bidir=%d\n", usblp->bidir); + dev_dbg(dev, "device_id_string=\"%s\"\n", + usblp->device_id_string ? + usblp->device_id_string + 2 : + (unsigned char *)"(null)"); +} +#endif + +/* Quirks: various printer quirks are handled by this table & its flags. */ + +struct quirk_printer_struct { + __u16 vendorId; + __u16 productId; + unsigned int quirks; +}; + +#define USBLP_QUIRK_BIDIR 0x1 /* reports bidir but requires unidirectional mode (no INs/reads) */ +#define USBLP_QUIRK_USB_INIT 0x2 /* needs vendor USB init string */ +#define USBLP_QUIRK_BAD_CLASS 0x4 /* descriptor uses vendor-specific Class or SubClass */ + +static const struct quirk_printer_struct quirk_printers[] = { + { 0x03f0, 0x0004, USBLP_QUIRK_BIDIR }, /* HP DeskJet 895C */ + { 0x03f0, 0x0104, USBLP_QUIRK_BIDIR }, /* HP DeskJet 880C */ + { 0x03f0, 0x0204, USBLP_QUIRK_BIDIR }, /* HP DeskJet 815C */ + { 0x03f0, 0x0304, USBLP_QUIRK_BIDIR }, /* HP DeskJet 810C/812C */ + { 0x03f0, 0x0404, USBLP_QUIRK_BIDIR }, /* HP DeskJet 830C */ + { 0x03f0, 0x0504, USBLP_QUIRK_BIDIR }, /* HP DeskJet 885C */ + { 0x03f0, 0x0604, USBLP_QUIRK_BIDIR }, /* HP DeskJet 840C */ + { 0x03f0, 0x0804, USBLP_QUIRK_BIDIR }, /* HP DeskJet 816C */ + { 0x03f0, 0x1104, USBLP_QUIRK_BIDIR }, /* HP Deskjet 959C */ + { 0x0409, 0xefbe, USBLP_QUIRK_BIDIR }, /* NEC Picty900 (HP OEM) */ + { 0x0409, 0xbef4, USBLP_QUIRK_BIDIR }, /* NEC Picty760 (HP OEM) */ + { 0x0409, 0xf0be, USBLP_QUIRK_BIDIR }, /* NEC Picty920 (HP OEM) */ + { 0x0409, 0xf1be, USBLP_QUIRK_BIDIR }, /* NEC Picty800 (HP OEM) */ + { 0x0482, 0x0010, USBLP_QUIRK_BIDIR }, /* Kyocera Mita FS 820, by zut */ + { 0x04f9, 0x000d, USBLP_QUIRK_BIDIR }, /* Brother Industries, Ltd HL-1440 Laser Printer */ + { 0x04b8, 0x0202, USBLP_QUIRK_BAD_CLASS }, /* Seiko Epson Receipt Printer M129C */ + { 0, 0 } +}; + +static int usblp_wwait(struct usblp *usblp, int nonblock); +static int usblp_wtest(struct usblp *usblp, int nonblock); +static int usblp_rwait_and_lock(struct usblp *usblp, int nonblock); +static int usblp_rtest(struct usblp *usblp, int nonblock); +static int usblp_submit_read(struct usblp *usblp); +static int usblp_select_alts(struct usblp *usblp); +static int usblp_set_protocol(struct usblp *usblp, int protocol); +static int usblp_cache_device_id_string(struct usblp *usblp); + +/* forward reference to make our lives easier */ +static struct usb_driver usblp_driver; +static DEFINE_MUTEX(usblp_mutex); /* locks the existence of usblp's */ + +/* + * Functions for usblp control messages. + */ + +static int usblp_ctrl_msg(struct usblp *usblp, int request, int type, int dir, int recip, int value, void *buf, int len) +{ + int retval; + int index = usblp->ifnum; + + /* High byte has the interface index. + Low byte has the alternate setting. + */ + if ((request == USBLP_REQ_GET_ID) && (type == USB_TYPE_CLASS)) + index = (usblp->ifnum<<8)|usblp->protocol[usblp->current_protocol].alt_setting; + + retval = usb_control_msg(usblp->dev, + dir ? usb_rcvctrlpipe(usblp->dev, 0) : usb_sndctrlpipe(usblp->dev, 0), + request, type | dir | recip, value, index, buf, len, USBLP_CTL_TIMEOUT); + dev_dbg(&usblp->intf->dev, + "usblp_control_msg: rq: 0x%02x dir: %d recip: %d value: %d idx: %d len: %#x result: %d\n", + request, !!dir, recip, value, index, len, retval); + return retval < 0 ? retval : 0; +} + +#define usblp_read_status(usblp, status)\ + usblp_ctrl_msg(usblp, USBLP_REQ_GET_STATUS, USB_TYPE_CLASS, USB_DIR_IN, USB_RECIP_INTERFACE, 0, status, 1) +#define usblp_get_id(usblp, config, id, maxlen)\ + usblp_ctrl_msg(usblp, USBLP_REQ_GET_ID, USB_TYPE_CLASS, USB_DIR_IN, USB_RECIP_INTERFACE, config, id, maxlen) +#define usblp_reset(usblp)\ + usblp_ctrl_msg(usblp, USBLP_REQ_RESET, USB_TYPE_CLASS, USB_DIR_OUT, USB_RECIP_OTHER, 0, NULL, 0) + +#define usblp_hp_channel_change_request(usblp, channel, buffer) \ + usblp_ctrl_msg(usblp, USBLP_REQ_HP_CHANNEL_CHANGE_REQUEST, USB_TYPE_VENDOR, USB_DIR_IN, USB_RECIP_INTERFACE, channel, buffer, 1) + +/* + * See the description for usblp_select_alts() below for the usage + * explanation. Look into your /proc/bus/usb/devices and dmesg in + * case of any trouble. + */ +static int proto_bias = -1; + +/* + * URB callback. + */ + +static void usblp_bulk_read(struct urb *urb) +{ + struct usblp *usblp = urb->context; + int status = urb->status; + + if (usblp->present && usblp->used) { + if (status) + printk(KERN_WARNING "usblp%d: " + "nonzero read bulk status received: %d\n", + usblp->minor, status); + } + spin_lock(&usblp->lock); + if (status < 0) + usblp->rstatus = status; + else + usblp->rstatus = urb->actual_length; + usblp->rcomplete = 1; + wake_up(&usblp->rwait); + spin_unlock(&usblp->lock); + + usb_free_urb(urb); +} + +static void usblp_bulk_write(struct urb *urb) +{ + struct usblp *usblp = urb->context; + int status = urb->status; + + if (usblp->present && usblp->used) { + if (status) + printk(KERN_WARNING "usblp%d: " + "nonzero write bulk status received: %d\n", + usblp->minor, status); + } + spin_lock(&usblp->lock); + if (status < 0) + usblp->wstatus = status; + else + usblp->wstatus = urb->actual_length; + usblp->no_paper = 0; + usblp->wcomplete = 1; + wake_up(&usblp->wwait); + spin_unlock(&usblp->lock); + + usb_free_urb(urb); +} + +/* + * Get and print printer errors. + */ + +static const char *usblp_messages[] = { "ok", "out of paper", "off-line", "on fire" }; + +static int usblp_check_status(struct usblp *usblp, int err) +{ + unsigned char status, newerr = 0; + int error; + + mutex_lock(&usblp->mut); + if ((error = usblp_read_status(usblp, usblp->statusbuf)) < 0) { + mutex_unlock(&usblp->mut); + printk_ratelimited(KERN_ERR + "usblp%d: error %d reading printer status\n", + usblp->minor, error); + return 0; + } + status = *usblp->statusbuf; + mutex_unlock(&usblp->mut); + + if (~status & LP_PERRORP) + newerr = 3; + if (status & LP_POUTPA) + newerr = 1; + if (~status & LP_PSELECD) + newerr = 2; + + if (newerr != err) { + printk(KERN_INFO "usblp%d: %s\n", + usblp->minor, usblp_messages[newerr]); + } + + return newerr; +} + +static int handle_bidir(struct usblp *usblp) +{ + if (usblp->bidir && usblp->used) { + if (usblp_submit_read(usblp) < 0) + return -EIO; + } + return 0; +} + +/* + * File op functions. + */ + +static int usblp_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct usblp *usblp; + struct usb_interface *intf; + int retval; + + if (minor < 0) + return -ENODEV; + + mutex_lock(&usblp_mutex); + + retval = -ENODEV; + intf = usb_find_interface(&usblp_driver, minor); + if (!intf) + goto out; + usblp = usb_get_intfdata(intf); + if (!usblp || !usblp->dev || !usblp->present) + goto out; + + retval = -EBUSY; + if (usblp->used) + goto out; + + /* + * We do not implement LP_ABORTOPEN/LPABORTOPEN for two reasons: + * - We do not want persistent state which close(2) does not clear + * - It is not used anyway, according to CUPS people + */ + + retval = usb_autopm_get_interface(intf); + if (retval < 0) + goto out; + usblp->used = 1; + file->private_data = usblp; + + usblp->wcomplete = 1; /* we begin writeable */ + usblp->wstatus = 0; + usblp->rcomplete = 0; + + if (handle_bidir(usblp) < 0) { + usb_autopm_put_interface(intf); + usblp->used = 0; + file->private_data = NULL; + retval = -EIO; + } +out: + mutex_unlock(&usblp_mutex); + return retval; +} + +static void usblp_cleanup(struct usblp *usblp) +{ + printk(KERN_INFO "usblp%d: removed\n", usblp->minor); + + kfree(usblp->readbuf); + kfree(usblp->device_id_string); + kfree(usblp->statusbuf); + kfree(usblp); +} + +static void usblp_unlink_urbs(struct usblp *usblp) +{ + usb_kill_anchored_urbs(&usblp->urbs); +} + +static int usblp_release(struct inode *inode, struct file *file) +{ + struct usblp *usblp = file->private_data; + + usblp->flags &= ~LP_ABORT; + + mutex_lock(&usblp_mutex); + usblp->used = 0; + if (usblp->present) { + usblp_unlink_urbs(usblp); + usb_autopm_put_interface(usblp->intf); + } else /* finish cleanup from disconnect */ + usblp_cleanup(usblp); + mutex_unlock(&usblp_mutex); + return 0; +} + +/* No kernel lock - fine */ +static unsigned int usblp_poll(struct file *file, struct poll_table_struct *wait) +{ + int ret; + unsigned long flags; + + struct usblp *usblp = file->private_data; + /* Should we check file->f_mode & FMODE_WRITE before poll_wait()? */ + poll_wait(file, &usblp->rwait, wait); + poll_wait(file, &usblp->wwait, wait); + spin_lock_irqsave(&usblp->lock, flags); + ret = ((usblp->bidir && usblp->rcomplete) ? POLLIN | POLLRDNORM : 0) | + ((usblp->no_paper || usblp->wcomplete) ? POLLOUT | POLLWRNORM : 0); + spin_unlock_irqrestore(&usblp->lock, flags); + return ret; +} + +static long usblp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usblp *usblp = file->private_data; + int length, err, i; + unsigned char newChannel; + int status; + int twoints[2]; + int retval = 0; + + mutex_lock(&usblp->mut); + if (!usblp->present) { + retval = -ENODEV; + goto done; + } + + dev_dbg(&usblp->intf->dev, + "usblp_ioctl: cmd=0x%x (%c nr=%d len=%d dir=%d)\n", cmd, + _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd), _IOC_DIR(cmd)); + + if (_IOC_TYPE(cmd) == 'P') /* new-style ioctl number */ + + switch (_IOC_NR(cmd)) { + + case IOCNR_GET_DEVICE_ID: /* get the DEVICE_ID string */ + if (_IOC_DIR(cmd) != _IOC_READ) { + retval = -EINVAL; + goto done; + } + + length = usblp_cache_device_id_string(usblp); + if (length < 0) { + retval = length; + goto done; + } + if (length > _IOC_SIZE(cmd)) + length = _IOC_SIZE(cmd); /* truncate */ + + if (copy_to_user((void __user *) arg, + usblp->device_id_string, + (unsigned long) length)) { + retval = -EFAULT; + goto done; + } + + break; + + case IOCNR_GET_PROTOCOLS: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } + + twoints[0] = usblp->current_protocol; + twoints[1] = 0; + for (i = USBLP_FIRST_PROTOCOL; + i <= USBLP_LAST_PROTOCOL; i++) { + if (usblp->protocol[i].alt_setting >= 0) + twoints[1] |= (1<current_protocol); + } + break; + + case IOCNR_HP_SET_CHANNEL: + if (_IOC_DIR(cmd) != _IOC_WRITE || + le16_to_cpu(usblp->dev->descriptor.idVendor) != 0x03F0 || + usblp->quirks & USBLP_QUIRK_BIDIR) { + retval = -EINVAL; + goto done; + } + + err = usblp_hp_channel_change_request(usblp, + arg, &newChannel); + if (err < 0) { + dev_err(&usblp->dev->dev, + "usblp%d: error = %d setting " + "HP channel\n", + usblp->minor, err); + retval = -EIO; + goto done; + } + + dev_dbg(&usblp->intf->dev, + "usblp%d requested/got HP channel %ld/%d\n", + usblp->minor, arg, newChannel); + break; + + case IOCNR_GET_BUS_ADDRESS: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } + + twoints[0] = usblp->dev->bus->busnum; + twoints[1] = usblp->dev->devnum; + if (copy_to_user((void __user *)arg, + (unsigned char *)twoints, + sizeof(twoints))) { + retval = -EFAULT; + goto done; + } + + dev_dbg(&usblp->intf->dev, + "usblp%d is bus=%d, device=%d\n", + usblp->minor, twoints[0], twoints[1]); + break; + + case IOCNR_GET_VID_PID: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } + + twoints[0] = le16_to_cpu(usblp->dev->descriptor.idVendor); + twoints[1] = le16_to_cpu(usblp->dev->descriptor.idProduct); + if (copy_to_user((void __user *)arg, + (unsigned char *)twoints, + sizeof(twoints))) { + retval = -EFAULT; + goto done; + } + + dev_dbg(&usblp->intf->dev, + "usblp%d is VID=0x%4.4X, PID=0x%4.4X\n", + usblp->minor, twoints[0], twoints[1]); + break; + + case IOCNR_SOFT_RESET: + if (_IOC_DIR(cmd) != _IOC_NONE) { + retval = -EINVAL; + goto done; + } + retval = usblp_reset(usblp); + break; + default: + retval = -ENOTTY; + } + else /* old-style ioctl value */ + switch (cmd) { + + case LPGETSTATUS: + if ((retval = usblp_read_status(usblp, usblp->statusbuf))) { + printk_ratelimited(KERN_ERR "usblp%d:" + "failed reading printer status (%d)\n", + usblp->minor, retval); + retval = -EIO; + goto done; + } + status = *usblp->statusbuf; + if (copy_to_user((void __user *)arg, &status, sizeof(int))) + retval = -EFAULT; + break; + + case LPABORT: + if (arg) + usblp->flags |= LP_ABORT; + else + usblp->flags &= ~LP_ABORT; + break; + + default: + retval = -ENOTTY; + } + +done: + mutex_unlock(&usblp->mut); + return retval; +} + +static struct urb *usblp_new_writeurb(struct usblp *usblp, int transfer_length) +{ + struct urb *urb; + char *writebuf; + + if ((writebuf = kmalloc(transfer_length, GFP_KERNEL)) == NULL) + return NULL; + if ((urb = usb_alloc_urb(0, GFP_KERNEL)) == NULL) { + kfree(writebuf); + return NULL; + } + + usb_fill_bulk_urb(urb, usblp->dev, + usb_sndbulkpipe(usblp->dev, + usblp->protocol[usblp->current_protocol].epwrite->bEndpointAddress), + writebuf, transfer_length, usblp_bulk_write, usblp); + urb->transfer_flags |= URB_FREE_BUFFER; + + return urb; +} + +static ssize_t usblp_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct usblp *usblp = file->private_data; + struct urb *writeurb; + int rv; + int transfer_length; + ssize_t writecount = 0; + + if (mutex_lock_interruptible(&usblp->wmut)) { + rv = -EINTR; + goto raise_biglock; + } + if ((rv = usblp_wwait(usblp, !!(file->f_flags & O_NONBLOCK))) < 0) + goto raise_wait; + + while (writecount < count) { + /* + * Step 1: Submit next block. + */ + if ((transfer_length = count - writecount) > USBLP_BUF_SIZE) + transfer_length = USBLP_BUF_SIZE; + + rv = -ENOMEM; + if ((writeurb = usblp_new_writeurb(usblp, transfer_length)) == NULL) + goto raise_urb; + usb_anchor_urb(writeurb, &usblp->urbs); + + if (copy_from_user(writeurb->transfer_buffer, + buffer + writecount, transfer_length)) { + rv = -EFAULT; + goto raise_badaddr; + } + + spin_lock_irq(&usblp->lock); + usblp->wcomplete = 0; + spin_unlock_irq(&usblp->lock); + if ((rv = usb_submit_urb(writeurb, GFP_KERNEL)) < 0) { + usblp->wstatus = 0; + spin_lock_irq(&usblp->lock); + usblp->no_paper = 0; + usblp->wcomplete = 1; + wake_up(&usblp->wwait); + spin_unlock_irq(&usblp->lock); + if (rv != -ENOMEM) + rv = -EIO; + goto raise_submit; + } + + /* + * Step 2: Wait for transfer to end, collect results. + */ + rv = usblp_wwait(usblp, !!(file->f_flags&O_NONBLOCK)); + if (rv < 0) { + if (rv == -EAGAIN) { + /* Presume that it's going to complete well. */ + writecount += transfer_length; + } + if (rv == -ENOSPC) { + spin_lock_irq(&usblp->lock); + usblp->no_paper = 1; /* Mark for poll(2) */ + spin_unlock_irq(&usblp->lock); + writecount += transfer_length; + } + /* Leave URB dangling, to be cleaned on close. */ + goto collect_error; + } + + if (usblp->wstatus < 0) { + rv = -EIO; + goto collect_error; + } + /* + * This is critical: it must be our URB, not other writer's. + * The wmut exists mainly to cover us here. + */ + writecount += usblp->wstatus; + } + + mutex_unlock(&usblp->wmut); + return writecount; + +raise_submit: +raise_badaddr: + usb_unanchor_urb(writeurb); + usb_free_urb(writeurb); +raise_urb: +raise_wait: +collect_error: /* Out of raise sequence */ + mutex_unlock(&usblp->wmut); +raise_biglock: + return writecount ? writecount : rv; +} + +/* + * Notice that we fail to restart in a few cases: on EFAULT, on restart + * error, etc. This is the historical behaviour. In all such cases we return + * EIO, and applications loop in order to get the new read going. + */ +static ssize_t usblp_read(struct file *file, char __user *buffer, size_t len, loff_t *ppos) +{ + struct usblp *usblp = file->private_data; + ssize_t count; + ssize_t avail; + int rv; + + if (!usblp->bidir) + return -EINVAL; + + rv = usblp_rwait_and_lock(usblp, !!(file->f_flags & O_NONBLOCK)); + if (rv < 0) + return rv; + + if ((avail = usblp->rstatus) < 0) { + printk(KERN_ERR "usblp%d: error %d reading from printer\n", + usblp->minor, (int)avail); + usblp_submit_read(usblp); + count = -EIO; + goto done; + } + + count = len < avail - usblp->readcount ? len : avail - usblp->readcount; + if (count != 0 && + copy_to_user(buffer, usblp->readbuf + usblp->readcount, count)) { + count = -EFAULT; + goto done; + } + + if ((usblp->readcount += count) == avail) { + if (usblp_submit_read(usblp) < 0) { + /* We don't want to leak USB return codes into errno. */ + if (count == 0) + count = -EIO; + goto done; + } + } + +done: + mutex_unlock(&usblp->mut); + return count; +} + +/* + * Wait for the write path to come idle. + * This is called under the ->wmut, so the idle path stays idle. + * + * Our write path has a peculiar property: it does not buffer like a tty, + * but waits for the write to succeed. This allows our ->release to bug out + * without waiting for writes to drain. But it obviously does not work + * when O_NONBLOCK is set. So, applications setting O_NONBLOCK must use + * select(2) or poll(2) to wait for the buffer to drain before closing. + * Alternatively, set blocking mode with fcntl and issue a zero-size write. + */ +static int usblp_wwait(struct usblp *usblp, int nonblock) +{ + DECLARE_WAITQUEUE(waita, current); + int rc; + int err = 0; + + add_wait_queue(&usblp->wwait, &waita); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (mutex_lock_interruptible(&usblp->mut)) { + rc = -EINTR; + break; + } + rc = usblp_wtest(usblp, nonblock); + mutex_unlock(&usblp->mut); + if (rc <= 0) + break; + + if (schedule_timeout(msecs_to_jiffies(1500)) == 0) { + if (usblp->flags & LP_ABORT) { + err = usblp_check_status(usblp, err); + if (err == 1) { /* Paper out */ + rc = -ENOSPC; + break; + } + } else { + /* Prod the printer, Gentoo#251237. */ + mutex_lock(&usblp->mut); + usblp_read_status(usblp, usblp->statusbuf); + mutex_unlock(&usblp->mut); + } + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&usblp->wwait, &waita); + return rc; +} + +static int usblp_wtest(struct usblp *usblp, int nonblock) +{ + unsigned long flags; + + if (!usblp->present) + return -ENODEV; + if (signal_pending(current)) + return -EINTR; + spin_lock_irqsave(&usblp->lock, flags); + if (usblp->wcomplete) { + spin_unlock_irqrestore(&usblp->lock, flags); + return 0; + } + spin_unlock_irqrestore(&usblp->lock, flags); + if (nonblock) + return -EAGAIN; + return 1; +} + +/* + * Wait for read bytes to become available. This probably should have been + * called usblp_r_lock_and_wait(), because we lock first. But it's a traditional + * name for functions which lock and return. + * + * We do not use wait_event_interruptible because it makes locking iffy. + */ +static int usblp_rwait_and_lock(struct usblp *usblp, int nonblock) +{ + DECLARE_WAITQUEUE(waita, current); + int rc; + + add_wait_queue(&usblp->rwait, &waita); + for (;;) { + if (mutex_lock_interruptible(&usblp->mut)) { + rc = -EINTR; + break; + } + set_current_state(TASK_INTERRUPTIBLE); + if ((rc = usblp_rtest(usblp, nonblock)) < 0) { + mutex_unlock(&usblp->mut); + break; + } + if (rc == 0) /* Keep it locked */ + break; + mutex_unlock(&usblp->mut); + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&usblp->rwait, &waita); + return rc; +} + +static int usblp_rtest(struct usblp *usblp, int nonblock) +{ + unsigned long flags; + + if (!usblp->present) + return -ENODEV; + if (signal_pending(current)) + return -EINTR; + spin_lock_irqsave(&usblp->lock, flags); + if (usblp->rcomplete) { + spin_unlock_irqrestore(&usblp->lock, flags); + return 0; + } + spin_unlock_irqrestore(&usblp->lock, flags); + if (nonblock) + return -EAGAIN; + return 1; +} + +/* + * Please check ->bidir and other such things outside for now. + */ +static int usblp_submit_read(struct usblp *usblp) +{ + struct urb *urb; + unsigned long flags; + int rc; + + rc = -ENOMEM; + if ((urb = usb_alloc_urb(0, GFP_KERNEL)) == NULL) + goto raise_urb; + + usb_fill_bulk_urb(urb, usblp->dev, + usb_rcvbulkpipe(usblp->dev, + usblp->protocol[usblp->current_protocol].epread->bEndpointAddress), + usblp->readbuf, USBLP_BUF_SIZE_IN, + usblp_bulk_read, usblp); + usb_anchor_urb(urb, &usblp->urbs); + + spin_lock_irqsave(&usblp->lock, flags); + usblp->readcount = 0; /* XXX Why here? */ + usblp->rcomplete = 0; + spin_unlock_irqrestore(&usblp->lock, flags); + if ((rc = usb_submit_urb(urb, GFP_KERNEL)) < 0) { + dev_dbg(&usblp->intf->dev, "error submitting urb (%d)\n", rc); + spin_lock_irqsave(&usblp->lock, flags); + usblp->rstatus = rc; + usblp->rcomplete = 1; + spin_unlock_irqrestore(&usblp->lock, flags); + goto raise_submit; + } + + return 0; + +raise_submit: + usb_unanchor_urb(urb); + usb_free_urb(urb); +raise_urb: + return rc; +} + +/* + * Checks for printers that have quirks, such as requiring unidirectional + * communication but reporting bidirectional; currently some HP printers + * have this flaw (HP 810, 880, 895, etc.), or needing an init string + * sent at each open (like some Epsons). + * Returns 1 if found, 0 if not found. + * + * HP recommended that we use the bidirectional interface but + * don't attempt any bulk IN transfers from the IN endpoint. + * Here's some more detail on the problem: + * The problem is not that it isn't bidirectional though. The problem + * is that if you request a device ID, or status information, while + * the buffers are full, the return data will end up in the print data + * buffer. For example if you make sure you never request the device ID + * while you are sending print data, and you don't try to query the + * printer status every couple of milliseconds, you will probably be OK. + */ +static unsigned int usblp_quirks(__u16 vendor, __u16 product) +{ + int i; + + for (i = 0; quirk_printers[i].vendorId; i++) { + if (vendor == quirk_printers[i].vendorId && + product == quirk_printers[i].productId) + return quirk_printers[i].quirks; + } + return 0; +} + +static const struct file_operations usblp_fops = { + .owner = THIS_MODULE, + .read = usblp_read, + .write = usblp_write, + .poll = usblp_poll, + .unlocked_ioctl = usblp_ioctl, + .compat_ioctl = usblp_ioctl, + .open = usblp_open, + .release = usblp_release, + .llseek = noop_llseek, +}; + +static char *usblp_devnode(struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev)); +} + +static struct usb_class_driver usblp_class = { + .name = "lp%d", + .devnode = usblp_devnode, + .fops = &usblp_fops, + .minor_base = USBLP_MINOR_BASE, +}; + +static ssize_t usblp_show_ieee1284_id(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usblp *usblp = usb_get_intfdata(intf); + + if (usblp->device_id_string[0] == 0 && + usblp->device_id_string[1] == 0) + return 0; + + return sprintf(buf, "%s", usblp->device_id_string+2); +} + +static DEVICE_ATTR(ieee1284_id, S_IRUGO, usblp_show_ieee1284_id, NULL); + +static int usblp_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usblp *usblp; + int protocol; + int retval; + + /* Malloc and start initializing usblp structure so we can use it + * directly. */ + usblp = kzalloc(sizeof(struct usblp), GFP_KERNEL); + if (!usblp) { + retval = -ENOMEM; + goto abort_ret; + } + usblp->dev = dev; + mutex_init(&usblp->wmut); + mutex_init(&usblp->mut); + spin_lock_init(&usblp->lock); + init_waitqueue_head(&usblp->rwait); + init_waitqueue_head(&usblp->wwait); + init_usb_anchor(&usblp->urbs); + usblp->ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + usblp->intf = intf; + + /* Malloc device ID string buffer to the largest expected length, + * since we can re-query it on an ioctl and a dynamic string + * could change in length. */ + if (!(usblp->device_id_string = kmalloc(USBLP_DEVICE_ID_SIZE, GFP_KERNEL))) { + retval = -ENOMEM; + goto abort; + } + + /* + * Allocate read buffer. We somewhat wastefully + * malloc both regardless of bidirectionality, because the + * alternate setting can be changed later via an ioctl. + */ + if (!(usblp->readbuf = kmalloc(USBLP_BUF_SIZE_IN, GFP_KERNEL))) { + retval = -ENOMEM; + goto abort; + } + + /* Allocate buffer for printer status */ + usblp->statusbuf = kmalloc(STATUS_BUF_SIZE, GFP_KERNEL); + if (!usblp->statusbuf) { + retval = -ENOMEM; + goto abort; + } + + /* Lookup quirks for this printer. */ + usblp->quirks = usblp_quirks( + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + + /* Analyze and pick initial alternate settings and endpoints. */ + protocol = usblp_select_alts(usblp); + if (protocol < 0) { + dev_dbg(&intf->dev, + "incompatible printer-class device 0x%4.4X/0x%4.4X\n", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + retval = -ENODEV; + goto abort; + } + + /* Setup the selected alternate setting and endpoints. */ + if (usblp_set_protocol(usblp, protocol) < 0) { + retval = -ENODEV; /* ->probe isn't ->ioctl */ + goto abort; + } + + /* Retrieve and store the device ID string. */ + usblp_cache_device_id_string(usblp); + retval = device_create_file(&intf->dev, &dev_attr_ieee1284_id); + if (retval) + goto abort_intfdata; + +#ifdef DEBUG + usblp_check_status(usblp, 0); +#endif + + usb_set_intfdata(intf, usblp); + + usblp->present = 1; + + retval = usb_register_dev(intf, &usblp_class); + if (retval) { + dev_err(&intf->dev, + "usblp: Not able to get a minor (base %u, slice default): %d\n", + USBLP_MINOR_BASE, retval); + goto abort_intfdata; + } + usblp->minor = intf->minor; + dev_info(&intf->dev, + "usblp%d: USB %sdirectional printer dev %d if %d alt %d proto %d vid 0x%4.4X pid 0x%4.4X\n", + usblp->minor, usblp->bidir ? "Bi" : "Uni", dev->devnum, + usblp->ifnum, + usblp->protocol[usblp->current_protocol].alt_setting, + usblp->current_protocol, + le16_to_cpu(usblp->dev->descriptor.idVendor), + le16_to_cpu(usblp->dev->descriptor.idProduct)); + + return 0; + +abort_intfdata: + usb_set_intfdata(intf, NULL); + device_remove_file(&intf->dev, &dev_attr_ieee1284_id); +abort: + kfree(usblp->readbuf); + kfree(usblp->statusbuf); + kfree(usblp->device_id_string); + kfree(usblp); +abort_ret: + return retval; +} + +/* + * We are a "new" style driver with usb_device_id table, + * but our requirements are too intricate for simple match to handle. + * + * The "proto_bias" option may be used to specify the preferred protocol + * for all USB printers (1=7/1/1, 2=7/1/2, 3=7/1/3). If the device + * supports the preferred protocol, then we bind to it. + * + * The best interface for us is 7/1/2, because it is compatible + * with a stream of characters. If we find it, we bind to it. + * + * Note that the people from hpoj.sourceforge.net need to be able to + * bind to 7/1/3 (MLC/1284.4), so we provide them ioctls for this purpose. + * + * Failing 7/1/2, we look for 7/1/3, even though it's probably not + * stream-compatible, because this matches the behaviour of the old code. + * + * If nothing else, we bind to 7/1/1 - the unidirectional interface. + */ +static int usblp_select_alts(struct usblp *usblp) +{ + struct usb_interface *if_alt; + struct usb_host_interface *ifd; + struct usb_endpoint_descriptor *epd, *epwrite, *epread; + int p, i, e; + + if_alt = usblp->intf; + + for (p = 0; p < USBLP_MAX_PROTOCOLS; p++) + usblp->protocol[p].alt_setting = -1; + + /* Find out what we have. */ + for (i = 0; i < if_alt->num_altsetting; i++) { + ifd = &if_alt->altsetting[i]; + + if (ifd->desc.bInterfaceClass != 7 || ifd->desc.bInterfaceSubClass != 1) + if (!(usblp->quirks & USBLP_QUIRK_BAD_CLASS)) + continue; + + if (ifd->desc.bInterfaceProtocol < USBLP_FIRST_PROTOCOL || + ifd->desc.bInterfaceProtocol > USBLP_LAST_PROTOCOL) + continue; + + /* Look for bulk OUT and IN endpoints. */ + epwrite = epread = NULL; + for (e = 0; e < ifd->desc.bNumEndpoints; e++) { + epd = &ifd->endpoint[e].desc; + + if (usb_endpoint_is_bulk_out(epd)) + if (!epwrite) + epwrite = epd; + + if (usb_endpoint_is_bulk_in(epd)) + if (!epread) + epread = epd; + } + + /* Ignore buggy hardware without the right endpoints. */ + if (!epwrite || (ifd->desc.bInterfaceProtocol > 1 && !epread)) + continue; + + /* Turn off reads for 7/1/1 (unidirectional) interfaces + * and buggy bidirectional printers. */ + if (ifd->desc.bInterfaceProtocol == 1) { + epread = NULL; + } else if (usblp->quirks & USBLP_QUIRK_BIDIR) { + printk(KERN_INFO "usblp%d: Disabling reads from " + "problematic bidirectional printer\n", + usblp->minor); + epread = NULL; + } + + usblp->protocol[ifd->desc.bInterfaceProtocol].alt_setting = + ifd->desc.bAlternateSetting; + usblp->protocol[ifd->desc.bInterfaceProtocol].epwrite = epwrite; + usblp->protocol[ifd->desc.bInterfaceProtocol].epread = epread; + } + + /* If our requested protocol is supported, then use it. */ + if (proto_bias >= USBLP_FIRST_PROTOCOL && + proto_bias <= USBLP_LAST_PROTOCOL && + usblp->protocol[proto_bias].alt_setting != -1) + return proto_bias; + + /* Ordering is important here. */ + if (usblp->protocol[2].alt_setting != -1) + return 2; + if (usblp->protocol[1].alt_setting != -1) + return 1; + if (usblp->protocol[3].alt_setting != -1) + return 3; + + /* If nothing is available, then don't bind to this device. */ + return -1; +} + +static int usblp_set_protocol(struct usblp *usblp, int protocol) +{ + int r, alts; + + if (protocol < USBLP_FIRST_PROTOCOL || protocol > USBLP_LAST_PROTOCOL) + return -EINVAL; + + alts = usblp->protocol[protocol].alt_setting; + if (alts < 0) + return -EINVAL; + r = usb_set_interface(usblp->dev, usblp->ifnum, alts); + if (r < 0) { + printk(KERN_ERR "usblp: can't set desired altsetting %d on interface %d\n", + alts, usblp->ifnum); + return r; + } + + usblp->bidir = (usblp->protocol[protocol].epread != NULL); + usblp->current_protocol = protocol; + dev_dbg(&usblp->intf->dev, "usblp%d set protocol %d\n", + usblp->minor, protocol); + return 0; +} + +/* Retrieves and caches device ID string. + * Returns length, including length bytes but not null terminator. + * On error, returns a negative errno value. */ +static int usblp_cache_device_id_string(struct usblp *usblp) +{ + int err, length; + + err = usblp_get_id(usblp, 0, usblp->device_id_string, USBLP_DEVICE_ID_SIZE - 1); + if (err < 0) { + dev_dbg(&usblp->intf->dev, + "usblp%d: error = %d reading IEEE-1284 Device ID string\n", + usblp->minor, err); + usblp->device_id_string[0] = usblp->device_id_string[1] = '\0'; + return -EIO; + } + + /* First two bytes are length in big-endian. + * They count themselves, and we copy them into + * the user's buffer. */ + length = be16_to_cpu(*((__be16 *)usblp->device_id_string)); + if (length < 2) + length = 2; + else if (length >= USBLP_DEVICE_ID_SIZE) + length = USBLP_DEVICE_ID_SIZE - 1; + usblp->device_id_string[length] = '\0'; + + dev_dbg(&usblp->intf->dev, "usblp%d Device ID string [len=%d]=\"%s\"\n", + usblp->minor, length, &usblp->device_id_string[2]); + + return length; +} + +static void usblp_disconnect(struct usb_interface *intf) +{ + struct usblp *usblp = usb_get_intfdata(intf); + + usb_deregister_dev(intf, &usblp_class); + + if (!usblp || !usblp->dev) { + dev_err(&intf->dev, "bogus disconnect\n"); + BUG(); + } + + device_remove_file(&intf->dev, &dev_attr_ieee1284_id); + + mutex_lock(&usblp_mutex); + mutex_lock(&usblp->mut); + usblp->present = 0; + wake_up(&usblp->wwait); + wake_up(&usblp->rwait); + usb_set_intfdata(intf, NULL); + + usblp_unlink_urbs(usblp); + mutex_unlock(&usblp->mut); + + if (!usblp->used) + usblp_cleanup(usblp); + mutex_unlock(&usblp_mutex); +} + +static int usblp_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usblp *usblp = usb_get_intfdata(intf); + + usblp_unlink_urbs(usblp); +#if 0 /* XXX Do we want this? What if someone is reading, should we fail? */ + /* not strictly necessary, but just in case */ + wake_up(&usblp->wwait); + wake_up(&usblp->rwait); +#endif + + return 0; +} + +static int usblp_resume(struct usb_interface *intf) +{ + struct usblp *usblp = usb_get_intfdata(intf); + int r; + + r = handle_bidir(usblp); + + return r; +} + +static const struct usb_device_id usblp_ids[] = { + { USB_DEVICE_INFO(7, 1, 1) }, + { USB_DEVICE_INFO(7, 1, 2) }, + { USB_DEVICE_INFO(7, 1, 3) }, + { USB_INTERFACE_INFO(7, 1, 1) }, + { USB_INTERFACE_INFO(7, 1, 2) }, + { USB_INTERFACE_INFO(7, 1, 3) }, + { USB_DEVICE(0x04b8, 0x0202) }, /* Seiko Epson Receipt Printer M129C */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usblp_ids); + +static struct usb_driver usblp_driver = { + .name = "usblp", + .probe = usblp_probe, + .disconnect = usblp_disconnect, + .suspend = usblp_suspend, + .resume = usblp_resume, + .id_table = usblp_ids, + .supports_autosuspend = 1, +}; + +module_usb_driver(usblp_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +module_param(proto_bias, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(proto_bias, "Favourite protocol number"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c new file mode 100644 index 00000000..4c5506ae --- /dev/null +++ b/drivers/usb/class/usbtmc.c @@ -0,0 +1,1103 @@ +/** + * drivers/usb/class/usbtmc.c - USB Test & Measurement class driver + * + * Copyright (C) 2007 Stefan Kopp, Gechingen, Germany + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * The GNU General Public License is available at + * http://www.gnu.org/copyleft/gpl.html. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define USBTMC_MINOR_BASE 176 + +/* + * Size of driver internal IO buffer. Must be multiple of 4 and at least as + * large as wMaxPacketSize (which is usually 512 bytes). + */ +#define USBTMC_SIZE_IOBUFFER 2048 + +/* Default USB timeout (in milliseconds) */ +#define USBTMC_TIMEOUT 5000 + +/* + * Maximum number of read cycles to empty bulk in endpoint during CLEAR and + * ABORT_BULK_IN requests. Ends the loop if (for whatever reason) a short + * packet is never read. + */ +#define USBTMC_MAX_READS_TO_CLEAR_BULK_IN 100 + +static const struct usb_device_id usbtmc_devices[] = { + { USB_INTERFACE_INFO(USB_CLASS_APP_SPEC, 3, 0), }, + { USB_INTERFACE_INFO(USB_CLASS_APP_SPEC, 3, 1), }, + { 0, } /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usbtmc_devices); + +/* + * This structure is the capabilities for the device + * See section 4.2.1.8 of the USBTMC specification, + * and section 4.2.2 of the USBTMC usb488 subclass + * specification for details. + */ +struct usbtmc_dev_capabilities { + __u8 interface_capabilities; + __u8 device_capabilities; + __u8 usb488_interface_capabilities; + __u8 usb488_device_capabilities; +}; + +/* This structure holds private data for each USBTMC device. One copy is + * allocated for each USBTMC device in the driver's probe function. + */ +struct usbtmc_device_data { + const struct usb_device_id *id; + struct usb_device *usb_dev; + struct usb_interface *intf; + + unsigned int bulk_in; + unsigned int bulk_out; + + u8 bTag; + u8 bTag_last_write; /* needed for abort */ + u8 bTag_last_read; /* needed for abort */ + + /* attributes from the USB TMC spec for this device */ + u8 TermChar; + bool TermCharEnabled; + bool auto_abort; + + bool zombie; /* fd of disconnected device */ + + struct usbtmc_dev_capabilities capabilities; + struct kref kref; + struct mutex io_mutex; /* only one i/o function running at a time */ +}; +#define to_usbtmc_data(d) container_of(d, struct usbtmc_device_data, kref) + +/* Forward declarations */ +static struct usb_driver usbtmc_driver; + +static void usbtmc_delete(struct kref *kref) +{ + struct usbtmc_device_data *data = to_usbtmc_data(kref); + + usb_put_dev(data->usb_dev); + kfree(data); +} + +static int usbtmc_open(struct inode *inode, struct file *filp) +{ + struct usb_interface *intf; + struct usbtmc_device_data *data; + int retval = 0; + + intf = usb_find_interface(&usbtmc_driver, iminor(inode)); + if (!intf) { + printk(KERN_ERR KBUILD_MODNAME + ": can not find device for minor %d", iminor(inode)); + retval = -ENODEV; + goto exit; + } + + data = usb_get_intfdata(intf); + kref_get(&data->kref); + + /* Store pointer in file structure's private data field */ + filp->private_data = data; + +exit: + return retval; +} + +static int usbtmc_release(struct inode *inode, struct file *file) +{ + struct usbtmc_device_data *data = file->private_data; + + kref_put(&data->kref, usbtmc_delete); + return 0; +} + +static int usbtmc_ioctl_abort_bulk_in(struct usbtmc_device_data *data) +{ + u8 *buffer; + struct device *dev; + int rv; + int n; + int actual; + struct usb_host_interface *current_setting; + int max_size; + + dev = &data->intf->dev; + buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_INITIATE_ABORT_BULK_IN, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + data->bTag_last_read, data->bulk_in, + buffer, 2, USBTMC_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + + if (buffer[0] == USBTMC_STATUS_FAILED) { + rv = 0; + goto exit; + } + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INITIATE_ABORT_BULK_IN returned %x\n", + buffer[0]); + rv = -EPERM; + goto exit; + } + + max_size = 0; + current_setting = data->intf->cur_altsetting; + for (n = 0; n < current_setting->desc.bNumEndpoints; n++) + if (current_setting->endpoint[n].desc.bEndpointAddress == + data->bulk_in) + max_size = usb_endpoint_maxp(¤t_setting->endpoint[n].desc); + + if (max_size == 0) { + dev_err(dev, "Couldn't get wMaxPacketSize\n"); + rv = -EPERM; + goto exit; + } + + dev_dbg(&data->intf->dev, "wMaxPacketSize is %d\n", max_size); + + n = 0; + + do { + dev_dbg(dev, "Reading from bulk in EP\n"); + + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_SIZE_IOBUFFER, + &actual, USBTMC_TIMEOUT); + + n++; + + if (rv < 0) { + dev_err(dev, "usb_bulk_msg returned %d\n", rv); + goto exit; + } + } while ((actual == max_size) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + + if (actual == max_size) { + dev_err(dev, "Couldn't clear device buffer within %d cycles\n", + USBTMC_MAX_READS_TO_CLEAR_BULK_IN); + rv = -EPERM; + goto exit; + } + + n = 0; + +usbtmc_abort_bulk_in_status: + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_CHECK_ABORT_BULK_IN_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + 0, data->bulk_in, buffer, 0x08, + USBTMC_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + + if (buffer[0] == USBTMC_STATUS_SUCCESS) { + rv = 0; + goto exit; + } + + if (buffer[0] != USBTMC_STATUS_PENDING) { + dev_err(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + + if (buffer[1] == 1) + do { + dev_dbg(dev, "Reading from bulk in EP\n"); + + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_SIZE_IOBUFFER, + &actual, USBTMC_TIMEOUT); + + n++; + + if (rv < 0) { + dev_err(dev, "usb_bulk_msg returned %d\n", rv); + goto exit; + } + } while ((actual == max_size) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + + if (actual == max_size) { + dev_err(dev, "Couldn't clear device buffer within %d cycles\n", + USBTMC_MAX_READS_TO_CLEAR_BULK_IN); + rv = -EPERM; + goto exit; + } + + goto usbtmc_abort_bulk_in_status; + +exit: + kfree(buffer); + return rv; + +} + +static int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) +{ + struct device *dev; + u8 *buffer; + int rv; + int n; + + dev = &data->intf->dev; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_INITIATE_ABORT_BULK_OUT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + data->bTag_last_write, data->bulk_out, + buffer, 2, USBTMC_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INITIATE_ABORT_BULK_OUT returned %x\n", buffer[0]); + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INITIATE_ABORT_BULK_OUT returned %x\n", + buffer[0]); + rv = -EPERM; + goto exit; + } + + n = 0; + +usbtmc_abort_bulk_out_check_status: + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_CHECK_ABORT_BULK_OUT_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + 0, data->bulk_out, buffer, 0x08, + USBTMC_TIMEOUT); + n++; + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "CHECK_ABORT_BULK_OUT returned %x\n", buffer[0]); + + if (buffer[0] == USBTMC_STATUS_SUCCESS) + goto usbtmc_abort_bulk_out_clear_halt; + + if ((buffer[0] == USBTMC_STATUS_PENDING) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)) + goto usbtmc_abort_bulk_out_check_status; + + rv = -EPERM; + goto exit; + +usbtmc_abort_bulk_out_clear_halt: + rv = usb_clear_halt(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out)); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + rv = 0; + +exit: + kfree(buffer); + return rv; +} + +static ssize_t usbtmc_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + struct usbtmc_device_data *data; + struct device *dev; + u32 n_characters; + u8 *buffer; + int actual; + size_t done; + size_t remaining; + int retval; + size_t this_part; + + /* Get pointer to private data structure */ + data = filp->private_data; + dev = &data->intf->dev; + + buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + mutex_lock(&data->io_mutex); + if (data->zombie) { + retval = -ENODEV; + goto exit; + } + + remaining = count; + done = 0; + + while (remaining > 0) { + if (remaining > USBTMC_SIZE_IOBUFFER - 12 - 3) + this_part = USBTMC_SIZE_IOBUFFER - 12 - 3; + else + this_part = remaining; + + /* Setup IO buffer for DEV_DEP_MSG_IN message + * Refer to class specs for details + */ + buffer[0] = 2; + buffer[1] = data->bTag; + buffer[2] = ~(data->bTag); + buffer[3] = 0; /* Reserved */ + buffer[4] = (this_part) & 255; + buffer[5] = ((this_part) >> 8) & 255; + buffer[6] = ((this_part) >> 16) & 255; + buffer[7] = ((this_part) >> 24) & 255; + buffer[8] = data->TermCharEnabled * 2; + /* Use term character? */ + buffer[9] = data->TermChar; + buffer[10] = 0; /* Reserved */ + buffer[11] = 0; /* Reserved */ + + /* Send bulk URB */ + retval = usb_bulk_msg(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, + data->bulk_out), + buffer, 12, &actual, USBTMC_TIMEOUT); + + /* Store bTag (in case we need to abort) */ + data->bTag_last_write = data->bTag; + + /* Increment bTag -- and increment again if zero */ + data->bTag++; + if (!data->bTag) + (data->bTag)++; + + if (retval < 0) { + dev_err(dev, "usb_bulk_msg returned %d\n", retval); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } + + /* Send bulk URB */ + retval = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_SIZE_IOBUFFER, &actual, + USBTMC_TIMEOUT); + + /* Store bTag (in case we need to abort) */ + data->bTag_last_read = data->bTag; + + if (retval < 0) { + dev_err(dev, "Unable to read data, error %d\n", retval); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + /* How many characters did the instrument send? */ + n_characters = buffer[4] + + (buffer[5] << 8) + + (buffer[6] << 16) + + (buffer[7] << 24); + + /* Ensure the instrument doesn't lie about it */ + if(n_characters > actual - 12) { + dev_err(dev, "Device lies about message size: %u > %d\n", n_characters, actual - 12); + n_characters = actual - 12; + } + + /* Ensure the instrument doesn't send more back than requested */ + if(n_characters > this_part) { + dev_err(dev, "Device returns more than requested: %zu > %zu\n", done + n_characters, done + this_part); + n_characters = this_part; + } + + /* Bound amount of data received by amount of data requested */ + if (n_characters > this_part) + n_characters = this_part; + + /* Copy buffer to user space */ + if (copy_to_user(buf + done, &buffer[12], n_characters)) { + /* There must have been an addressing problem */ + retval = -EFAULT; + goto exit; + } + + done += n_characters; + /* Terminate if end-of-message bit received from device */ + if ((buffer[8] & 0x01) && (actual >= n_characters + 12)) + remaining = 0; + else + remaining -= n_characters; + } + + /* Update file position value */ + *f_pos = *f_pos + done; + retval = done; + +exit: + mutex_unlock(&data->io_mutex); + kfree(buffer); + return retval; +} + +static ssize_t usbtmc_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct usbtmc_device_data *data; + u8 *buffer; + int retval; + int actual; + unsigned long int n_bytes; + int remaining; + int done; + int this_part; + + data = filp->private_data; + + buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + mutex_lock(&data->io_mutex); + if (data->zombie) { + retval = -ENODEV; + goto exit; + } + + remaining = count; + done = 0; + + while (remaining > 0) { + if (remaining > USBTMC_SIZE_IOBUFFER - 12) { + this_part = USBTMC_SIZE_IOBUFFER - 12; + buffer[8] = 0; + } else { + this_part = remaining; + buffer[8] = 1; + } + + /* Setup IO buffer for DEV_DEP_MSG_OUT message */ + buffer[0] = 1; + buffer[1] = data->bTag; + buffer[2] = ~(data->bTag); + buffer[3] = 0; /* Reserved */ + buffer[4] = this_part & 255; + buffer[5] = (this_part >> 8) & 255; + buffer[6] = (this_part >> 16) & 255; + buffer[7] = (this_part >> 24) & 255; + /* buffer[8] is set above... */ + buffer[9] = 0; /* Reserved */ + buffer[10] = 0; /* Reserved */ + buffer[11] = 0; /* Reserved */ + + if (copy_from_user(&buffer[12], buf + done, this_part)) { + retval = -EFAULT; + goto exit; + } + + n_bytes = roundup(12 + this_part, 4); + memset(buffer + 12 + this_part, 0, n_bytes - (12 + this_part)); + + do { + retval = usb_bulk_msg(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, + data->bulk_out), + buffer, n_bytes, + &actual, USBTMC_TIMEOUT); + if (retval != 0) + break; + n_bytes -= actual; + } while (n_bytes); + + data->bTag_last_write = data->bTag; + data->bTag++; + + if (!data->bTag) + data->bTag++; + + if (retval < 0) { + dev_err(&data->intf->dev, + "Unable to send data, error %d\n", retval); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } + + remaining -= this_part; + done += this_part; + } + + retval = count; +exit: + mutex_unlock(&data->io_mutex); + kfree(buffer); + return retval; +} + +static int usbtmc_ioctl_clear(struct usbtmc_device_data *data) +{ + struct usb_host_interface *current_setting; + struct usb_endpoint_descriptor *desc; + struct device *dev; + u8 *buffer; + int rv; + int n; + int actual; + int max_size; + + dev = &data->intf->dev; + + dev_dbg(dev, "Sending INITIATE_CLEAR request\n"); + + buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_INITIATE_CLEAR, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, 0, buffer, 1, USBTMC_TIMEOUT); + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INITIATE_CLEAR returned %x\n", buffer[0]); + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INITIATE_CLEAR returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + + max_size = 0; + current_setting = data->intf->cur_altsetting; + for (n = 0; n < current_setting->desc.bNumEndpoints; n++) { + desc = ¤t_setting->endpoint[n].desc; + if (desc->bEndpointAddress == data->bulk_in) + max_size = usb_endpoint_maxp(desc); + } + + if (max_size == 0) { + dev_err(dev, "Couldn't get wMaxPacketSize\n"); + rv = -EPERM; + goto exit; + } + + dev_dbg(dev, "wMaxPacketSize is %d\n", max_size); + + n = 0; + +usbtmc_clear_check_status: + + dev_dbg(dev, "Sending CHECK_CLEAR_STATUS request\n"); + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_CHECK_CLEAR_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, 0, buffer, 2, USBTMC_TIMEOUT); + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "CHECK_CLEAR_STATUS returned %x\n", buffer[0]); + + if (buffer[0] == USBTMC_STATUS_SUCCESS) + goto usbtmc_clear_bulk_out_halt; + + if (buffer[0] != USBTMC_STATUS_PENDING) { + dev_err(dev, "CHECK_CLEAR_STATUS returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + + if (buffer[1] == 1) + do { + dev_dbg(dev, "Reading from bulk in EP\n"); + + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_SIZE_IOBUFFER, + &actual, USBTMC_TIMEOUT); + n++; + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", + rv); + goto exit; + } + } while ((actual == max_size) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + + if (actual == max_size) { + dev_err(dev, "Couldn't clear device buffer within %d cycles\n", + USBTMC_MAX_READS_TO_CLEAR_BULK_IN); + rv = -EPERM; + goto exit; + } + + goto usbtmc_clear_check_status; + +usbtmc_clear_bulk_out_halt: + + rv = usb_clear_halt(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out)); + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + rv = 0; + +exit: + kfree(buffer); + return rv; +} + +static int usbtmc_ioctl_clear_out_halt(struct usbtmc_device_data *data) +{ + int rv; + + rv = usb_clear_halt(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out)); + + if (rv < 0) { + dev_err(&data->usb_dev->dev, "usb_control_msg returned %d\n", + rv); + return rv; + } + return 0; +} + +static int usbtmc_ioctl_clear_in_halt(struct usbtmc_device_data *data) +{ + int rv; + + rv = usb_clear_halt(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, data->bulk_in)); + + if (rv < 0) { + dev_err(&data->usb_dev->dev, "usb_control_msg returned %d\n", + rv); + return rv; + } + return 0; +} + +static int get_capabilities(struct usbtmc_device_data *data) +{ + struct device *dev = &data->usb_dev->dev; + char *buffer; + int rv = 0; + + buffer = kmalloc(0x18, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_GET_CAPABILITIES, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, 0, buffer, 0x18, USBTMC_TIMEOUT); + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto err_out; + } + + dev_dbg(dev, "GET_CAPABILITIES returned %x\n", buffer[0]); + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "GET_CAPABILITIES returned %x\n", buffer[0]); + rv = -EPERM; + goto err_out; + } + dev_dbg(dev, "Interface capabilities are %x\n", buffer[4]); + dev_dbg(dev, "Device capabilities are %x\n", buffer[5]); + dev_dbg(dev, "USB488 interface capabilities are %x\n", buffer[14]); + dev_dbg(dev, "USB488 device capabilities are %x\n", buffer[15]); + + data->capabilities.interface_capabilities = buffer[4]; + data->capabilities.device_capabilities = buffer[5]; + data->capabilities.usb488_interface_capabilities = buffer[14]; + data->capabilities.usb488_device_capabilities = buffer[15]; + rv = 0; + +err_out: + kfree(buffer); + return rv; +} + +#define capability_attribute(name) \ +static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usbtmc_device_data *data = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%d\n", data->capabilities.name); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL) + +capability_attribute(interface_capabilities); +capability_attribute(device_capabilities); +capability_attribute(usb488_interface_capabilities); +capability_attribute(usb488_device_capabilities); + +static struct attribute *capability_attrs[] = { + &dev_attr_interface_capabilities.attr, + &dev_attr_device_capabilities.attr, + &dev_attr_usb488_interface_capabilities.attr, + &dev_attr_usb488_device_capabilities.attr, + NULL, +}; + +static struct attribute_group capability_attr_grp = { + .attrs = capability_attrs, +}; + +static ssize_t show_TermChar(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usbtmc_device_data *data = usb_get_intfdata(intf); + + return sprintf(buf, "%c\n", data->TermChar); +} + +static ssize_t store_TermChar(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usbtmc_device_data *data = usb_get_intfdata(intf); + + if (count < 1) + return -EINVAL; + data->TermChar = buf[0]; + return count; +} +static DEVICE_ATTR(TermChar, S_IRUGO, show_TermChar, store_TermChar); + +#define data_attribute(name) \ +static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usbtmc_device_data *data = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%d\n", data->name); \ +} \ +static ssize_t store_##name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usbtmc_device_data *data = usb_get_intfdata(intf); \ + ssize_t result; \ + unsigned val; \ + \ + result = sscanf(buf, "%u\n", &val); \ + if (result != 1) \ + result = -EINVAL; \ + data->name = val; \ + if (result < 0) \ + return result; \ + else \ + return count; \ +} \ +static DEVICE_ATTR(name, S_IRUGO, show_##name, store_##name) + +data_attribute(TermCharEnabled); +data_attribute(auto_abort); + +static struct attribute *data_attrs[] = { + &dev_attr_TermChar.attr, + &dev_attr_TermCharEnabled.attr, + &dev_attr_auto_abort.attr, + NULL, +}; + +static struct attribute_group data_attr_grp = { + .attrs = data_attrs, +}; + +static int usbtmc_ioctl_indicator_pulse(struct usbtmc_device_data *data) +{ + struct device *dev; + u8 *buffer; + int rv; + + dev = &data->intf->dev; + + buffer = kmalloc(2, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_INDICATOR_PULSE, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, 0, buffer, 0x01, USBTMC_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INDICATOR_PULSE returned %x\n", buffer[0]); + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INDICATOR_PULSE returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + rv = 0; + +exit: + kfree(buffer); + return rv; +} + +static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usbtmc_device_data *data; + int retval = -EBADRQC; + + data = file->private_data; + mutex_lock(&data->io_mutex); + if (data->zombie) { + retval = -ENODEV; + goto skip_io_on_zombie; + } + + switch (cmd) { + case USBTMC_IOCTL_CLEAR_OUT_HALT: + retval = usbtmc_ioctl_clear_out_halt(data); + break; + + case USBTMC_IOCTL_CLEAR_IN_HALT: + retval = usbtmc_ioctl_clear_in_halt(data); + break; + + case USBTMC_IOCTL_INDICATOR_PULSE: + retval = usbtmc_ioctl_indicator_pulse(data); + break; + + case USBTMC_IOCTL_CLEAR: + retval = usbtmc_ioctl_clear(data); + break; + + case USBTMC_IOCTL_ABORT_BULK_OUT: + retval = usbtmc_ioctl_abort_bulk_out(data); + break; + + case USBTMC_IOCTL_ABORT_BULK_IN: + retval = usbtmc_ioctl_abort_bulk_in(data); + break; + } + +skip_io_on_zombie: + mutex_unlock(&data->io_mutex); + return retval; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = usbtmc_read, + .write = usbtmc_write, + .open = usbtmc_open, + .release = usbtmc_release, + .unlocked_ioctl = usbtmc_ioctl, + .llseek = default_llseek, +}; + +static struct usb_class_driver usbtmc_class = { + .name = "usbtmc%d", + .fops = &fops, + .minor_base = USBTMC_MINOR_BASE, +}; + + +static int usbtmc_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usbtmc_device_data *data; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int n; + int retcode; + + dev_dbg(&intf->dev, "%s called\n", __func__); + + data = kmalloc(sizeof(struct usbtmc_device_data), GFP_KERNEL); + if (!data) { + dev_err(&intf->dev, "Unable to allocate kernel memory\n"); + return -ENOMEM; + } + + data->intf = intf; + data->id = id; + data->usb_dev = usb_get_dev(interface_to_usbdev(intf)); + usb_set_intfdata(intf, data); + kref_init(&data->kref); + mutex_init(&data->io_mutex); + data->zombie = 0; + + /* Initialize USBTMC bTag and other fields */ + data->bTag = 1; + data->TermCharEnabled = 0; + data->TermChar = '\n'; + + /* USBTMC devices have only one setting, so use that */ + iface_desc = data->intf->cur_altsetting; + + /* Find bulk in endpoint */ + for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) { + endpoint = &iface_desc->endpoint[n].desc; + + if (usb_endpoint_is_bulk_in(endpoint)) { + data->bulk_in = endpoint->bEndpointAddress; + dev_dbg(&intf->dev, "Found bulk in endpoint at %u\n", + data->bulk_in); + break; + } + } + + /* Find bulk out endpoint */ + for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) { + endpoint = &iface_desc->endpoint[n].desc; + + if (usb_endpoint_is_bulk_out(endpoint)) { + data->bulk_out = endpoint->bEndpointAddress; + dev_dbg(&intf->dev, "Found Bulk out endpoint at %u\n", + data->bulk_out); + break; + } + } + + retcode = get_capabilities(data); + if (retcode) + dev_err(&intf->dev, "can't read capabilities\n"); + else + retcode = sysfs_create_group(&intf->dev.kobj, + &capability_attr_grp); + + retcode = sysfs_create_group(&intf->dev.kobj, &data_attr_grp); + + retcode = usb_register_dev(intf, &usbtmc_class); + if (retcode) { + dev_err(&intf->dev, "Not able to get a minor" + " (base %u, slice default): %d\n", USBTMC_MINOR_BASE, + retcode); + goto error_register; + } + dev_dbg(&intf->dev, "Using minor number %d\n", intf->minor); + + return 0; + +error_register: + sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp); + sysfs_remove_group(&intf->dev.kobj, &data_attr_grp); + kref_put(&data->kref, usbtmc_delete); + return retcode; +} + +static void usbtmc_disconnect(struct usb_interface *intf) +{ + struct usbtmc_device_data *data; + + dev_dbg(&intf->dev, "usbtmc_disconnect called\n"); + + data = usb_get_intfdata(intf); + usb_deregister_dev(intf, &usbtmc_class); + sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp); + sysfs_remove_group(&intf->dev.kobj, &data_attr_grp); + mutex_lock(&data->io_mutex); + data->zombie = 1; + mutex_unlock(&data->io_mutex); + kref_put(&data->kref, usbtmc_delete); +} + +static int usbtmc_suspend(struct usb_interface *intf, pm_message_t message) +{ + /* this driver does not have pending URBs */ + return 0; +} + +static int usbtmc_resume(struct usb_interface *intf) +{ + return 0; +} + +static struct usb_driver usbtmc_driver = { + .name = "usbtmc", + .id_table = usbtmc_devices, + .probe = usbtmc_probe, + .disconnect = usbtmc_disconnect, + .suspend = usbtmc_suspend, + .resume = usbtmc_resume, +}; + +module_usb_driver(usbtmc_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig new file mode 100644 index 00000000..db535b0a --- /dev/null +++ b/drivers/usb/core/Kconfig @@ -0,0 +1,91 @@ +# +# USB Core configuration +# +config USB_DEBUG + bool "USB verbose debug messages" + help + Say Y here if you want the USB core & hub drivers to produce a bunch + of debug messages to the system log. Select this if you are having a + problem with USB support and want to see more of what is going on. + +config USB_ANNOUNCE_NEW_DEVICES + bool "USB announce new devices" + default N + help + Say Y here if you want the USB core to always announce the + idVendor, idProduct, Manufacturer, Product, and SerialNumber + strings for every new USB device to the syslog. This option is + usually used by distro vendors to help with debugging and to + let users know what specific device was added to the machine + in what location. + + If you do not want this kind of information sent to the system + log, or have any doubts about this, say N here. + +comment "Miscellaneous USB options" + +config USB_DEFAULT_PERSIST + bool "Enable USB persist by default" + default y + help + Say N here if you don't want USB power session persistance + enabled by default. If you say N it will make suspended USB + devices that lose power get reenumerated as if they had been + unplugged, causing any mounted filesystems to be lost. The + persist feature can still be enabled for individual devices + through the power/persist sysfs node. See + Documentation/usb/persist.txt for more info. + + If you have any questions about this, say Y here, only say N + if you know exactly what you are doing. + +config USB_DYNAMIC_MINORS + bool "Dynamic USB minor allocation" + help + If you say Y here, the USB subsystem will use dynamic minor + allocation for any device that uses the USB major number. + This means that you can have more than 16 of a single type + of device (like USB printers). + + If you are unsure about this, say N here. + +config USB_OTG + bool "OTG support" + depends on PM_RUNTIME + default n + help + The most notable feature of USB OTG is support for a + "Dual-Role" device, which can act as either a device + or a host. The initial role is decided by the type of + plug inserted and can be changed later when two dual + role devices talk to each other. + + Select this only if your board has Mini-AB/Micro-AB + connector. + +config USB_OTG_WHITELIST + bool "Rely on OTG Targeted Peripherals List" + depends on USB_OTG || EXPERT + default y if USB_OTG + help + If you say Y here, the "otg_whitelist.h" file will be used as a + product whitelist, so USB peripherals not listed there will be + rejected during enumeration. This behavior is required by the + USB OTG specification for all devices not on your product's + "Targeted Peripherals List". "Embedded Hosts" are likewise + allowed to support only a limited number of peripherals. + + Otherwise, peripherals not listed there will only generate a + warning and enumeration will continue. That's more like what + normal Linux-USB hosts do (other than the warning), and is + convenient for many stages of product development. + +config USB_OTG_BLACKLIST_HUB + bool "Disable external hubs" + depends on USB_OTG || EXPERT + help + If you say Y here, then Linux will refuse to enumerate + external hubs. OTG hosts are allowed to reduce hardware + and software costs by not supporting external hubs. So + are "Embedded Hosts" that don't offer OTG support. + diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile new file mode 100644 index 00000000..5e847ad2 --- /dev/null +++ b/drivers/usb/core/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for USB Core files and filesystem +# + +ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG + +usbcore-y := usb.o hub.o hcd.o urb.o message.o driver.o +usbcore-y += config.o file.o buffer.o sysfs.o endpoint.o +usbcore-y += devio.o notify.o generic.o quirks.o devices.o +usbcore-y += port.o + +usbcore-$(CONFIG_PCI) += hcd-pci.o +usbcore-$(CONFIG_ACPI) += usb-acpi.o + +obj-$(CONFIG_USB) += usbcore.o diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c new file mode 100644 index 00000000..b0585e62 --- /dev/null +++ b/drivers/usb/core/buffer.c @@ -0,0 +1,151 @@ +/* + * DMA memory management for framework level HCD code (hc_driver) + * + * This implementation plugs in through generic "usb_bus" level methods, + * and should work with all USB controllers, regardles of bus type. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * DMA-Coherent Buffers + */ + +/* FIXME tune these based on pool statistics ... */ +static const size_t pool_max[HCD_BUFFER_POOLS] = { + /* platforms without dma-friendly caches might need to + * prevent cacheline sharing... + */ + 32, + 128, + 512, + PAGE_SIZE / 2 + /* bigger --> allocate pages */ +}; + + +/* SETUP primitives */ + +/** + * hcd_buffer_create - initialize buffer pools + * @hcd: the bus whose buffer pools are to be initialized + * Context: !in_interrupt() + * + * Call this as part of initializing a host controller that uses the dma + * memory allocators. It initializes some pools of dma-coherent memory that + * will be shared by all drivers using that controller, or returns a negative + * errno value on error. + * + * Call hcd_buffer_destroy() to clean up after using those pools. + */ +int hcd_buffer_create(struct usb_hcd *hcd) +{ + char name[16]; + int i, size; + + if (!hcd->self.controller->dma_mask && + !(hcd->driver->flags & HCD_LOCAL_MEM)) + return 0; + + for (i = 0; i < HCD_BUFFER_POOLS; i++) { + size = pool_max[i]; + if (!size) + continue; + snprintf(name, sizeof name, "buffer-%d", size); + hcd->pool[i] = dma_pool_create(name, hcd->self.controller, + size, size, 0); + if (!hcd->pool[i]) { + hcd_buffer_destroy(hcd); + return -ENOMEM; + } + } + return 0; +} + + +/** + * hcd_buffer_destroy - deallocate buffer pools + * @hcd: the bus whose buffer pools are to be destroyed + * Context: !in_interrupt() + * + * This frees the buffer pools created by hcd_buffer_create(). + */ +void hcd_buffer_destroy(struct usb_hcd *hcd) +{ + int i; + + for (i = 0; i < HCD_BUFFER_POOLS; i++) { + struct dma_pool *pool = hcd->pool[i]; + if (pool) { + dma_pool_destroy(pool); + hcd->pool[i] = NULL; + } + } +} + + +/* sometimes alloc/free could use kmalloc with GFP_DMA, for + * better sharing and to leverage mm/slab.c intelligence. + */ + +void *hcd_buffer_alloc( + struct usb_bus *bus, + size_t size, + gfp_t mem_flags, + dma_addr_t *dma +) +{ + struct usb_hcd *hcd = bus_to_hcd(bus); + int i; + + /* some USB hosts just use PIO */ + if (!bus->controller->dma_mask && + !(hcd->driver->flags & HCD_LOCAL_MEM)) { + *dma = ~(dma_addr_t) 0; + return kmalloc(size, mem_flags); + } + + for (i = 0; i < HCD_BUFFER_POOLS; i++) { + if (size <= pool_max[i]) + return dma_pool_alloc(hcd->pool[i], mem_flags, dma); + } + return dma_alloc_coherent(hcd->self.controller, size, dma, mem_flags); +} + +void hcd_buffer_free( + struct usb_bus *bus, + size_t size, + void *addr, + dma_addr_t dma +) +{ + struct usb_hcd *hcd = bus_to_hcd(bus); + int i; + + if (!addr) + return; + + if (!bus->controller->dma_mask && + !(hcd->driver->flags & HCD_LOCAL_MEM)) { + kfree(addr); + return; + } + + for (i = 0; i < HCD_BUFFER_POOLS; i++) { + if (size <= pool_max[i]) { + dma_pool_free(hcd->pool[i], addr, dma); + return; + } + } + dma_free_coherent(hcd->self.controller, size, addr, dma); +} diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c new file mode 100644 index 00000000..65243832 --- /dev/null +++ b/drivers/usb/core/config.c @@ -0,0 +1,860 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb.h" + + +#define USB_MAXALTSETTING 128 /* Hard limit */ +#define USB_MAXENDPOINTS 30 /* Hard limit */ + +#define USB_MAXCONFIG 8 /* Arbitrary limit */ + + +static inline const char *plural(int n) +{ + return (n == 1 ? "" : "s"); +} + +static int find_next_descriptor(unsigned char *buffer, int size, + int dt1, int dt2, int *num_skipped) +{ + struct usb_descriptor_header *h; + int n = 0; + unsigned char *buffer0 = buffer; + + /* Find the next descriptor of type dt1 or dt2 */ + while (size > 0) { + h = (struct usb_descriptor_header *) buffer; + if (h->bDescriptorType == dt1 || h->bDescriptorType == dt2) + break; + buffer += h->bLength; + size -= h->bLength; + ++n; + } + + /* Store the number of descriptors skipped and return the + * number of bytes skipped */ + if (num_skipped) + *num_skipped = n; + return buffer - buffer0; +} + +static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, + int inum, int asnum, struct usb_host_endpoint *ep, + unsigned char *buffer, int size) +{ + struct usb_ss_ep_comp_descriptor *desc; + int max_tx; + + /* The SuperSpeed endpoint companion descriptor is supposed to + * be the first thing immediately following the endpoint descriptor. + */ + desc = (struct usb_ss_ep_comp_descriptor *) buffer; + if (desc->bDescriptorType != USB_DT_SS_ENDPOINT_COMP || + size < USB_DT_SS_EP_COMP_SIZE) { + dev_warn(ddev, "No SuperSpeed endpoint companion for config %d " + " interface %d altsetting %d ep %d: " + "using minimum values\n", + cfgno, inum, asnum, ep->desc.bEndpointAddress); + + /* Fill in some default values. + * Leave bmAttributes as zero, which will mean no streams for + * bulk, and isoc won't support multiple bursts of packets. + * With bursts of only one packet, and a Mult of 1, the max + * amount of data moved per endpoint service interval is one + * packet. + */ + ep->ss_ep_comp.bLength = USB_DT_SS_EP_COMP_SIZE; + ep->ss_ep_comp.bDescriptorType = USB_DT_SS_ENDPOINT_COMP; + if (usb_endpoint_xfer_isoc(&ep->desc) || + usb_endpoint_xfer_int(&ep->desc)) + ep->ss_ep_comp.wBytesPerInterval = + ep->desc.wMaxPacketSize; + return; + } + + memcpy(&ep->ss_ep_comp, desc, USB_DT_SS_EP_COMP_SIZE); + + /* Check the various values */ + if (usb_endpoint_xfer_control(&ep->desc) && desc->bMaxBurst != 0) { + dev_warn(ddev, "Control endpoint with bMaxBurst = %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to zero\n", desc->bMaxBurst, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bMaxBurst = 0; + } else if (desc->bMaxBurst > 15) { + dev_warn(ddev, "Endpoint with bMaxBurst = %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to 15\n", desc->bMaxBurst, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bMaxBurst = 15; + } + + if ((usb_endpoint_xfer_control(&ep->desc) || + usb_endpoint_xfer_int(&ep->desc)) && + desc->bmAttributes != 0) { + dev_warn(ddev, "%s endpoint with bmAttributes = %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to zero\n", + usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk", + desc->bmAttributes, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 0; + } else if (usb_endpoint_xfer_bulk(&ep->desc) && + desc->bmAttributes > 16) { + dev_warn(ddev, "Bulk endpoint with more than 65536 streams in " + "config %d interface %d altsetting %d ep %d: " + "setting to max\n", + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 16; + } else if (usb_endpoint_xfer_isoc(&ep->desc) && + desc->bmAttributes > 2) { + dev_warn(ddev, "Isoc endpoint has Mult of %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to 3\n", desc->bmAttributes + 1, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 2; + } + + if (usb_endpoint_xfer_isoc(&ep->desc)) + max_tx = (desc->bMaxBurst + 1) * (desc->bmAttributes + 1) * + usb_endpoint_maxp(&ep->desc); + else if (usb_endpoint_xfer_int(&ep->desc)) + max_tx = usb_endpoint_maxp(&ep->desc) * + (desc->bMaxBurst + 1); + else + max_tx = 999999; + if (le16_to_cpu(desc->wBytesPerInterval) > max_tx) { + dev_warn(ddev, "%s endpoint with wBytesPerInterval of %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to %d\n", + usb_endpoint_xfer_isoc(&ep->desc) ? "Isoc" : "Int", + le16_to_cpu(desc->wBytesPerInterval), + cfgno, inum, asnum, ep->desc.bEndpointAddress, + max_tx); + ep->ss_ep_comp.wBytesPerInterval = cpu_to_le16(max_tx); + } +} + +static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, + int asnum, struct usb_host_interface *ifp, int num_ep, + unsigned char *buffer, int size) +{ + unsigned char *buffer0 = buffer; + struct usb_endpoint_descriptor *d; + struct usb_host_endpoint *endpoint; + int n, i, j, retval; + + d = (struct usb_endpoint_descriptor *) buffer; + buffer += d->bLength; + size -= d->bLength; + + if (d->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE) + n = USB_DT_ENDPOINT_AUDIO_SIZE; + else if (d->bLength >= USB_DT_ENDPOINT_SIZE) + n = USB_DT_ENDPOINT_SIZE; + else { + dev_warn(ddev, "config %d interface %d altsetting %d has an " + "invalid endpoint descriptor of length %d, skipping\n", + cfgno, inum, asnum, d->bLength); + goto skip_to_next_endpoint_or_interface_descriptor; + } + + i = d->bEndpointAddress & ~USB_ENDPOINT_DIR_MASK; + if (i >= 16 || i == 0) { + dev_warn(ddev, "config %d interface %d altsetting %d has an " + "invalid endpoint with address 0x%X, skipping\n", + cfgno, inum, asnum, d->bEndpointAddress); + goto skip_to_next_endpoint_or_interface_descriptor; + } + + /* Only store as many endpoints as we have room for */ + if (ifp->desc.bNumEndpoints >= num_ep) + goto skip_to_next_endpoint_or_interface_descriptor; + + endpoint = &ifp->endpoint[ifp->desc.bNumEndpoints]; + ++ifp->desc.bNumEndpoints; + + memcpy(&endpoint->desc, d, n); + INIT_LIST_HEAD(&endpoint->urb_list); + + /* Fix up bInterval values outside the legal range. Use 32 ms if no + * proper value can be guessed. */ + i = 0; /* i = min, j = max, n = default */ + j = 255; + if (usb_endpoint_xfer_int(d)) { + i = 1; + switch (to_usb_device(ddev)->speed) { + case USB_SPEED_SUPER: + case USB_SPEED_HIGH: + /* Many device manufacturers are using full-speed + * bInterval values in high-speed interrupt endpoint + * descriptors. Try to fix those and fall back to a + * 32 ms default value otherwise. */ + n = fls(d->bInterval*8); + if (n == 0) + n = 9; /* 32 ms = 2^(9-1) uframes */ + j = 16; + break; + default: /* USB_SPEED_FULL or _LOW */ + /* For low-speed, 10 ms is the official minimum. + * But some "overclocked" devices might want faster + * polling so we'll allow it. */ + n = 32; + break; + } + } else if (usb_endpoint_xfer_isoc(d)) { + i = 1; + j = 16; + switch (to_usb_device(ddev)->speed) { + case USB_SPEED_HIGH: + n = 9; /* 32 ms = 2^(9-1) uframes */ + break; + default: /* USB_SPEED_FULL */ + n = 6; /* 32 ms = 2^(6-1) frames */ + break; + } + } + if (d->bInterval < i || d->bInterval > j) { + dev_warn(ddev, "config %d interface %d altsetting %d " + "endpoint 0x%X has an invalid bInterval %d, " + "changing to %d\n", + cfgno, inum, asnum, + d->bEndpointAddress, d->bInterval, n); + endpoint->desc.bInterval = n; + } + + /* Some buggy low-speed devices have Bulk endpoints, which is + * explicitly forbidden by the USB spec. In an attempt to make + * them usable, we will try treating them as Interrupt endpoints. + */ + if (to_usb_device(ddev)->speed == USB_SPEED_LOW && + usb_endpoint_xfer_bulk(d)) { + dev_warn(ddev, "config %d interface %d altsetting %d " + "endpoint 0x%X is Bulk; changing to Interrupt\n", + cfgno, inum, asnum, d->bEndpointAddress); + endpoint->desc.bmAttributes = USB_ENDPOINT_XFER_INT; + endpoint->desc.bInterval = 1; + if (usb_endpoint_maxp(&endpoint->desc) > 8) + endpoint->desc.wMaxPacketSize = cpu_to_le16(8); + } + + /* + * Some buggy high speed devices have bulk endpoints using + * maxpacket sizes other than 512. High speed HCDs may not + * be able to handle that particular bug, so let's warn... + */ + if (to_usb_device(ddev)->speed == USB_SPEED_HIGH + && usb_endpoint_xfer_bulk(d)) { + unsigned maxp; + + maxp = usb_endpoint_maxp(&endpoint->desc) & 0x07ff; + if (maxp != 512) + dev_warn(ddev, "config %d interface %d altsetting %d " + "bulk endpoint 0x%X has invalid maxpacket %d\n", + cfgno, inum, asnum, d->bEndpointAddress, + maxp); + } + + /* Parse a possible SuperSpeed endpoint companion descriptor */ + if (to_usb_device(ddev)->speed == USB_SPEED_SUPER) + usb_parse_ss_endpoint_companion(ddev, cfgno, + inum, asnum, endpoint, buffer, size); + + /* Skip over any Class Specific or Vendor Specific descriptors; + * find the next endpoint or interface descriptor */ + endpoint->extra = buffer; + i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, + USB_DT_INTERFACE, &n); + endpoint->extralen = i; + retval = buffer - buffer0 + i; + if (n > 0) + dev_dbg(ddev, "skipped %d descriptor%s after %s\n", + n, plural(n), "endpoint"); + return retval; + +skip_to_next_endpoint_or_interface_descriptor: + i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, + USB_DT_INTERFACE, NULL); + return buffer - buffer0 + i; +} + +void usb_release_interface_cache(struct kref *ref) +{ + struct usb_interface_cache *intfc = ref_to_usb_interface_cache(ref); + int j; + + for (j = 0; j < intfc->num_altsetting; j++) { + struct usb_host_interface *alt = &intfc->altsetting[j]; + + kfree(alt->endpoint); + kfree(alt->string); + } + kfree(intfc); +} + +static int usb_parse_interface(struct device *ddev, int cfgno, + struct usb_host_config *config, unsigned char *buffer, int size, + u8 inums[], u8 nalts[]) +{ + unsigned char *buffer0 = buffer; + struct usb_interface_descriptor *d; + int inum, asnum; + struct usb_interface_cache *intfc; + struct usb_host_interface *alt; + int i, n; + int len, retval; + int num_ep, num_ep_orig; + + d = (struct usb_interface_descriptor *) buffer; + buffer += d->bLength; + size -= d->bLength; + + if (d->bLength < USB_DT_INTERFACE_SIZE) + goto skip_to_next_interface_descriptor; + + /* Which interface entry is this? */ + intfc = NULL; + inum = d->bInterfaceNumber; + for (i = 0; i < config->desc.bNumInterfaces; ++i) { + if (inums[i] == inum) { + intfc = config->intf_cache[i]; + break; + } + } + if (!intfc || intfc->num_altsetting >= nalts[i]) + goto skip_to_next_interface_descriptor; + + /* Check for duplicate altsetting entries */ + asnum = d->bAlternateSetting; + for ((i = 0, alt = &intfc->altsetting[0]); + i < intfc->num_altsetting; + (++i, ++alt)) { + if (alt->desc.bAlternateSetting == asnum) { + dev_warn(ddev, "Duplicate descriptor for config %d " + "interface %d altsetting %d, skipping\n", + cfgno, inum, asnum); + goto skip_to_next_interface_descriptor; + } + } + + ++intfc->num_altsetting; + memcpy(&alt->desc, d, USB_DT_INTERFACE_SIZE); + + /* Skip over any Class Specific or Vendor Specific descriptors; + * find the first endpoint or interface descriptor */ + alt->extra = buffer; + i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, + USB_DT_INTERFACE, &n); + alt->extralen = i; + if (n > 0) + dev_dbg(ddev, "skipped %d descriptor%s after %s\n", + n, plural(n), "interface"); + buffer += i; + size -= i; + + /* Allocate space for the right(?) number of endpoints */ + num_ep = num_ep_orig = alt->desc.bNumEndpoints; + alt->desc.bNumEndpoints = 0; /* Use as a counter */ + if (num_ep > USB_MAXENDPOINTS) { + dev_warn(ddev, "too many endpoints for config %d interface %d " + "altsetting %d: %d, using maximum allowed: %d\n", + cfgno, inum, asnum, num_ep, USB_MAXENDPOINTS); + num_ep = USB_MAXENDPOINTS; + } + + if (num_ep > 0) { + /* Can't allocate 0 bytes */ + len = sizeof(struct usb_host_endpoint) * num_ep; + alt->endpoint = kzalloc(len, GFP_KERNEL); + if (!alt->endpoint) + return -ENOMEM; + } + + /* Parse all the endpoint descriptors */ + n = 0; + while (size > 0) { + if (((struct usb_descriptor_header *) buffer)->bDescriptorType + == USB_DT_INTERFACE) + break; + retval = usb_parse_endpoint(ddev, cfgno, inum, asnum, alt, + num_ep, buffer, size); + if (retval < 0) + return retval; + ++n; + + buffer += retval; + size -= retval; + } + + if (n != num_ep_orig) + dev_warn(ddev, "config %d interface %d altsetting %d has %d " + "endpoint descriptor%s, different from the interface " + "descriptor's value: %d\n", + cfgno, inum, asnum, n, plural(n), num_ep_orig); + return buffer - buffer0; + +skip_to_next_interface_descriptor: + i = find_next_descriptor(buffer, size, USB_DT_INTERFACE, + USB_DT_INTERFACE, NULL); + return buffer - buffer0 + i; +} + +static int usb_parse_configuration(struct usb_device *dev, int cfgidx, + struct usb_host_config *config, unsigned char *buffer, int size) +{ + struct device *ddev = &dev->dev; + unsigned char *buffer0 = buffer; + int cfgno; + int nintf, nintf_orig; + int i, j, n; + struct usb_interface_cache *intfc; + unsigned char *buffer2; + int size2; + struct usb_descriptor_header *header; + int len, retval; + u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES]; + unsigned iad_num = 0; + + memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE); + if (config->desc.bDescriptorType != USB_DT_CONFIG || + config->desc.bLength < USB_DT_CONFIG_SIZE || + config->desc.bLength > size) { + dev_err(ddev, "invalid descriptor for config index %d: " + "type = 0x%X, length = %d\n", cfgidx, + config->desc.bDescriptorType, config->desc.bLength); + return -EINVAL; + } + cfgno = config->desc.bConfigurationValue; + + buffer += config->desc.bLength; + size -= config->desc.bLength; + + nintf = nintf_orig = config->desc.bNumInterfaces; + if (nintf > USB_MAXINTERFACES) { + dev_warn(ddev, "config %d has too many interfaces: %d, " + "using maximum allowed: %d\n", + cfgno, nintf, USB_MAXINTERFACES); + nintf = USB_MAXINTERFACES; + } + + /* Go through the descriptors, checking their length and counting the + * number of altsettings for each interface */ + n = 0; + for ((buffer2 = buffer, size2 = size); + size2 > 0; + (buffer2 += header->bLength, size2 -= header->bLength)) { + + if (size2 < sizeof(struct usb_descriptor_header)) { + dev_warn(ddev, "config %d descriptor has %d excess " + "byte%s, ignoring\n", + cfgno, size2, plural(size2)); + break; + } + + header = (struct usb_descriptor_header *) buffer2; + if ((header->bLength > size2) || (header->bLength < 2)) { + dev_warn(ddev, "config %d has an invalid descriptor " + "of length %d, skipping remainder of the config\n", + cfgno, header->bLength); + break; + } + + if (header->bDescriptorType == USB_DT_INTERFACE) { + struct usb_interface_descriptor *d; + int inum; + + d = (struct usb_interface_descriptor *) header; + if (d->bLength < USB_DT_INTERFACE_SIZE) { + dev_warn(ddev, "config %d has an invalid " + "interface descriptor of length %d, " + "skipping\n", cfgno, d->bLength); + continue; + } + + inum = d->bInterfaceNumber; + + if ((dev->quirks & USB_QUIRK_HONOR_BNUMINTERFACES) && + n >= nintf_orig) { + dev_warn(ddev, "config %d has more interface " + "descriptors, than it declares in " + "bNumInterfaces, ignoring interface " + "number: %d\n", cfgno, inum); + continue; + } + + if (inum >= nintf_orig) + dev_warn(ddev, "config %d has an invalid " + "interface number: %d but max is %d\n", + cfgno, inum, nintf_orig - 1); + + /* Have we already encountered this interface? + * Count its altsettings */ + for (i = 0; i < n; ++i) { + if (inums[i] == inum) + break; + } + if (i < n) { + if (nalts[i] < 255) + ++nalts[i]; + } else if (n < USB_MAXINTERFACES) { + inums[n] = inum; + nalts[n] = 1; + ++n; + } + + } else if (header->bDescriptorType == + USB_DT_INTERFACE_ASSOCIATION) { + if (iad_num == USB_MAXIADS) { + dev_warn(ddev, "found more Interface " + "Association Descriptors " + "than allocated for in " + "configuration %d\n", cfgno); + } else { + config->intf_assoc[iad_num] = + (struct usb_interface_assoc_descriptor + *)header; + iad_num++; + } + + } else if (header->bDescriptorType == USB_DT_DEVICE || + header->bDescriptorType == USB_DT_CONFIG) + dev_warn(ddev, "config %d contains an unexpected " + "descriptor of type 0x%X, skipping\n", + cfgno, header->bDescriptorType); + + } /* for ((buffer2 = buffer, size2 = size); ...) */ + size = buffer2 - buffer; + config->desc.wTotalLength = cpu_to_le16(buffer2 - buffer0); + + if (n != nintf) + dev_warn(ddev, "config %d has %d interface%s, different from " + "the descriptor's value: %d\n", + cfgno, n, plural(n), nintf_orig); + else if (n == 0) + dev_warn(ddev, "config %d has no interfaces?\n", cfgno); + config->desc.bNumInterfaces = nintf = n; + + /* Check for missing interface numbers */ + for (i = 0; i < nintf; ++i) { + for (j = 0; j < nintf; ++j) { + if (inums[j] == i) + break; + } + if (j >= nintf) + dev_warn(ddev, "config %d has no interface number " + "%d\n", cfgno, i); + } + + /* Allocate the usb_interface_caches and altsetting arrays */ + for (i = 0; i < nintf; ++i) { + j = nalts[i]; + if (j > USB_MAXALTSETTING) { + dev_warn(ddev, "too many alternate settings for " + "config %d interface %d: %d, " + "using maximum allowed: %d\n", + cfgno, inums[i], j, USB_MAXALTSETTING); + nalts[i] = j = USB_MAXALTSETTING; + } + + len = sizeof(*intfc) + sizeof(struct usb_host_interface) * j; + config->intf_cache[i] = intfc = kzalloc(len, GFP_KERNEL); + if (!intfc) + return -ENOMEM; + kref_init(&intfc->ref); + } + + /* FIXME: parse the BOS descriptor */ + + /* Skip over any Class Specific or Vendor Specific descriptors; + * find the first interface descriptor */ + config->extra = buffer; + i = find_next_descriptor(buffer, size, USB_DT_INTERFACE, + USB_DT_INTERFACE, &n); + config->extralen = i; + if (n > 0) + dev_dbg(ddev, "skipped %d descriptor%s after %s\n", + n, plural(n), "configuration"); + buffer += i; + size -= i; + + /* Parse all the interface/altsetting descriptors */ + while (size > 0) { + retval = usb_parse_interface(ddev, cfgno, config, + buffer, size, inums, nalts); + if (retval < 0) + return retval; + + buffer += retval; + size -= retval; + } + + /* Check for missing altsettings */ + for (i = 0; i < nintf; ++i) { + intfc = config->intf_cache[i]; + for (j = 0; j < intfc->num_altsetting; ++j) { + for (n = 0; n < intfc->num_altsetting; ++n) { + if (intfc->altsetting[n].desc. + bAlternateSetting == j) + break; + } + if (n >= intfc->num_altsetting) + dev_warn(ddev, "config %d interface %d has no " + "altsetting %d\n", cfgno, inums[i], j); + } + } + + return 0; +} + +/* hub-only!! ... and only exported for reset/reinit path. + * otherwise used internally on disconnect/destroy path + */ +void usb_destroy_configuration(struct usb_device *dev) +{ + int c, i; + + if (!dev->config) + return; + + if (dev->rawdescriptors) { + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) + kfree(dev->rawdescriptors[i]); + + kfree(dev->rawdescriptors); + dev->rawdescriptors = NULL; + } + + for (c = 0; c < dev->descriptor.bNumConfigurations; c++) { + struct usb_host_config *cf = &dev->config[c]; + + kfree(cf->string); + for (i = 0; i < cf->desc.bNumInterfaces; i++) { + if (cf->intf_cache[i]) + kref_put(&cf->intf_cache[i]->ref, + usb_release_interface_cache); + } + } + kfree(dev->config); + dev->config = NULL; +} + + +/* + * Get the USB config descriptors, cache and parse'em + * + * hub-only!! ... and only in reset path, or usb_new_device() + * (used by real hubs and virtual root hubs) + */ +int usb_get_configuration(struct usb_device *dev) +{ + struct device *ddev = &dev->dev; + int ncfg = dev->descriptor.bNumConfigurations; + int result = 0; + unsigned int cfgno, length; + unsigned char *bigbuffer; + struct usb_config_descriptor *desc; + + cfgno = 0; + result = -ENOMEM; + if (ncfg > USB_MAXCONFIG) { + dev_warn(ddev, "too many configurations: %d, " + "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG); + dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG; + } + + if (ncfg < 1) { + dev_err(ddev, "no configurations\n"); + return -EINVAL; + } + + length = ncfg * sizeof(struct usb_host_config); + dev->config = kzalloc(length, GFP_KERNEL); + if (!dev->config) + goto err2; + + length = ncfg * sizeof(char *); + dev->rawdescriptors = kzalloc(length, GFP_KERNEL); + if (!dev->rawdescriptors) + goto err2; + + desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL); + if (!desc) + goto err2; + + result = 0; + for (; cfgno < ncfg; cfgno++) { + /* We grab just the first descriptor so we know how long + * the whole configuration is */ + result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, + desc, USB_DT_CONFIG_SIZE); + if (result < 0) { + dev_err(ddev, "unable to read config index %d " + "descriptor/%s: %d\n", cfgno, "start", result); + if (result != -EPIPE) + goto err; + dev_err(ddev, "chopping to %d config(s)\n", cfgno); + dev->descriptor.bNumConfigurations = cfgno; + break; + } else if (result < 4) { + dev_err(ddev, "config index %d descriptor too short " + "(expected %i, got %i)\n", cfgno, + USB_DT_CONFIG_SIZE, result); + result = -EINVAL; + goto err; + } + length = max((int) le16_to_cpu(desc->wTotalLength), + USB_DT_CONFIG_SIZE); + + /* Now that we know the length, get the whole thing */ + bigbuffer = kmalloc(length, GFP_KERNEL); + if (!bigbuffer) { + result = -ENOMEM; + goto err; + } + + if (dev->quirks & USB_QUIRK_DELAY_INIT) + msleep(100); + + result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, + bigbuffer, length); + if (result < 0) { + dev_err(ddev, "unable to read config index %d " + "descriptor/%s\n", cfgno, "all"); + kfree(bigbuffer); + goto err; + } + if (result < length) { + dev_warn(ddev, "config index %d descriptor too short " + "(expected %i, got %i)\n", cfgno, length, result); + length = result; + } + + dev->rawdescriptors[cfgno] = bigbuffer; + + result = usb_parse_configuration(dev, cfgno, + &dev->config[cfgno], bigbuffer, length); + if (result < 0) { + ++cfgno; + goto err; + } + } + result = 0; + +err: + kfree(desc); + dev->descriptor.bNumConfigurations = cfgno; +err2: + if (result == -ENOMEM) + dev_err(ddev, "out of memory\n"); + return result; +} + +void usb_release_bos_descriptor(struct usb_device *dev) +{ + if (dev->bos) { + kfree(dev->bos->desc); + kfree(dev->bos); + dev->bos = NULL; + } +} + +/* Get BOS descriptor set */ +int usb_get_bos_descriptor(struct usb_device *dev) +{ + struct device *ddev = &dev->dev; + struct usb_bos_descriptor *bos; + struct usb_dev_cap_header *cap; + unsigned char *buffer; + int length, total_len, num, i; + int ret; + + bos = kzalloc(sizeof(struct usb_bos_descriptor), GFP_KERNEL); + if (!bos) + return -ENOMEM; + + /* Get BOS descriptor */ + ret = usb_get_descriptor(dev, USB_DT_BOS, 0, bos, USB_DT_BOS_SIZE); + if (ret < USB_DT_BOS_SIZE) { + dev_err(ddev, "unable to get BOS descriptor\n"); + if (ret >= 0) + ret = -ENOMSG; + kfree(bos); + return ret; + } + + length = bos->bLength; + total_len = le16_to_cpu(bos->wTotalLength); + num = bos->bNumDeviceCaps; + kfree(bos); + if (total_len < length) + return -EINVAL; + + dev->bos = kzalloc(sizeof(struct usb_host_bos), GFP_KERNEL); + if (!dev->bos) + return -ENOMEM; + + /* Now let's get the whole BOS descriptor set */ + buffer = kzalloc(total_len, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto err; + } + dev->bos->desc = (struct usb_bos_descriptor *)buffer; + + ret = usb_get_descriptor(dev, USB_DT_BOS, 0, buffer, total_len); + if (ret < total_len) { + dev_err(ddev, "unable to get BOS descriptor set\n"); + if (ret >= 0) + ret = -ENOMSG; + goto err; + } + total_len -= length; + + for (i = 0; i < num; i++) { + buffer += length; + cap = (struct usb_dev_cap_header *)buffer; + length = cap->bLength; + + if (total_len < length) + break; + total_len -= length; + + if (cap->bDescriptorType != USB_DT_DEVICE_CAPABILITY) { + dev_warn(ddev, "descriptor type invalid, skip\n"); + continue; + } + + switch (cap->bDevCapabilityType) { + case USB_CAP_TYPE_WIRELESS_USB: + /* Wireless USB cap descriptor is handled by wusb */ + break; + case USB_CAP_TYPE_EXT: + dev->bos->ext_cap = + (struct usb_ext_cap_descriptor *)buffer; + break; + case USB_SS_CAP_TYPE: + dev->bos->ss_cap = + (struct usb_ss_cap_descriptor *)buffer; + break; + case CONTAINER_ID_TYPE: + dev->bos->ss_id = + (struct usb_ss_container_id_descriptor *)buffer; + break; + default: + break; + } + } + + return 0; + +err: + usb_release_bos_descriptor(dev); + return ret; +} diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c new file mode 100644 index 00000000..2a3bbdf7 --- /dev/null +++ b/drivers/usb/core/devices.c @@ -0,0 +1,692 @@ +/* + * devices.c + * (C) Copyright 1999 Randy Dunlap. + * (C) Copyright 1999,2000 Thomas Sailer . + * (proc file per device) + * (C) Copyright 1999 Deti Fliegl (new USB architecture) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ************************************************************* + * + * /devices contains USB topology, device, config, class, + * interface, & endpoint data. + * + * I considered using /proc/bus/usb/devices/device# for each device + * as it is attached or detached, but I didn't like this for some + * reason -- maybe it's just too deep of a directory structure. + * I also don't like looking in multiple places to gather and view + * the data. Having only one file for ./devices also prevents race + * conditions that could arise if a program was reading device info + * for devices that are being removed (unplugged). (That is, the + * program may find a directory for devnum_12 then try to open it, + * but it was just unplugged, so the directory is now deleted. + * But programs would just have to be prepared for situations like + * this in any plug-and-play environment.) + * + * 1999-12-16: Thomas Sailer + * Converted the whole proc stuff to real + * read methods. Now not the whole device list needs to fit + * into one page, only the device list for one bus. + * Added a poll method to /proc/bus/usb/devices, to wake + * up an eventual usbd + * 2000-01-04: Thomas Sailer + * Turned into its own filesystem + * 2000-07-05: Ashley Montanaro + * Converted file reading routine to dump to buffer once + * per device, not per bus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb.h" + +/* Define ALLOW_SERIAL_NUMBER if you want to see the serial number of devices */ +#define ALLOW_SERIAL_NUMBER + +static const char format_topo[] = +/* T: Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=dddd MxCh=dd */ +"\nT: Bus=%2.2d Lev=%2.2d Prnt=%2.2d Port=%2.2d Cnt=%2.2d Dev#=%3d Spd=%-4s MxCh=%2d\n"; + +static const char format_string_manufacturer[] = +/* S: Manufacturer=xxxx */ + "S: Manufacturer=%.100s\n"; + +static const char format_string_product[] = +/* S: Product=xxxx */ + "S: Product=%.100s\n"; + +#ifdef ALLOW_SERIAL_NUMBER +static const char format_string_serialnumber[] = +/* S: SerialNumber=xxxx */ + "S: SerialNumber=%.100s\n"; +#endif + +static const char format_bandwidth[] = +/* B: Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd */ + "B: Alloc=%3d/%3d us (%2d%%), #Int=%3d, #Iso=%3d\n"; + +static const char format_device1[] = +/* D: Ver=xx.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd */ + "D: Ver=%2x.%02x Cls=%02x(%-5s) Sub=%02x Prot=%02x MxPS=%2d #Cfgs=%3d\n"; + +static const char format_device2[] = +/* P: Vendor=xxxx ProdID=xxxx Rev=xx.xx */ + "P: Vendor=%04x ProdID=%04x Rev=%2x.%02x\n"; + +static const char format_config[] = +/* C: #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA */ + "C:%c #Ifs=%2d Cfg#=%2d Atr=%02x MxPwr=%3dmA\n"; + +static const char format_iad[] = +/* A: FirstIf#=dd IfCount=dd Cls=xx(sssss) Sub=xx Prot=xx */ + "A: FirstIf#=%2d IfCount=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x\n"; + +static const char format_iface[] = +/* I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=xxxx*/ + "I:%c If#=%2d Alt=%2d #EPs=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x Driver=%s\n"; + +static const char format_endpt[] = +/* E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=D?s */ + "E: Ad=%02x(%c) Atr=%02x(%-4s) MxPS=%4d Ivl=%d%cs\n"; + + +/* + * Need access to the driver and USB bus lists. + * extern struct list_head usb_bus_list; + * However, these will come from functions that return ptrs to each of them. + */ + +/* + * Wait for an connect/disconnect event to happen. We initialize + * the event counter with an odd number, and each event will increment + * the event counter by two, so it will always _stay_ odd. That means + * that it will never be zero, so "event 0" will never match a current + * event, and thus 'poll' will always trigger as readable for the first + * time it gets called. + */ +static struct device_connect_event { + atomic_t count; + wait_queue_head_t wait; +} device_event = { + .count = ATOMIC_INIT(1), + .wait = __WAIT_QUEUE_HEAD_INITIALIZER(device_event.wait) +}; + +struct class_info { + int class; + char *class_name; +}; + +static const struct class_info clas_info[] = { + /* max. 5 chars. per name string */ + {USB_CLASS_PER_INTERFACE, ">ifc"}, + {USB_CLASS_AUDIO, "audio"}, + {USB_CLASS_COMM, "comm."}, + {USB_CLASS_HID, "HID"}, + {USB_CLASS_PHYSICAL, "PID"}, + {USB_CLASS_STILL_IMAGE, "still"}, + {USB_CLASS_PRINTER, "print"}, + {USB_CLASS_MASS_STORAGE, "stor."}, + {USB_CLASS_HUB, "hub"}, + {USB_CLASS_CDC_DATA, "data"}, + {USB_CLASS_CSCID, "scard"}, + {USB_CLASS_CONTENT_SEC, "c-sec"}, + {USB_CLASS_VIDEO, "video"}, + {USB_CLASS_WIRELESS_CONTROLLER, "wlcon"}, + {USB_CLASS_MISC, "misc"}, + {USB_CLASS_APP_SPEC, "app."}, + {USB_CLASS_VENDOR_SPEC, "vend."}, + {-1, "unk."} /* leave as last */ +}; + +/*****************************************************************/ + +void usbfs_conn_disc_event(void) +{ + atomic_add(2, &device_event.count); + wake_up(&device_event.wait); +} + +static const char *class_decode(const int class) +{ + int ix; + + for (ix = 0; clas_info[ix].class != -1; ix++) + if (clas_info[ix].class == class) + break; + return clas_info[ix].class_name; +} + +static char *usb_dump_endpoint_descriptor(int speed, char *start, char *end, + const struct usb_endpoint_descriptor *desc) +{ + char dir, unit, *type; + unsigned interval, bandwidth = 1; + + if (start > end) + return start; + + dir = usb_endpoint_dir_in(desc) ? 'I' : 'O'; + + if (speed == USB_SPEED_HIGH) { + switch (usb_endpoint_maxp(desc) & (0x03 << 11)) { + case 1 << 11: + bandwidth = 2; break; + case 2 << 11: + bandwidth = 3; break; + } + } + + /* this isn't checking for illegal values */ + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + type = "Ctrl"; + if (speed == USB_SPEED_HIGH) /* uframes per NAK */ + interval = desc->bInterval; + else + interval = 0; + dir = 'B'; /* ctrl is bidirectional */ + break; + case USB_ENDPOINT_XFER_ISOC: + type = "Isoc"; + interval = 1 << (desc->bInterval - 1); + break; + case USB_ENDPOINT_XFER_BULK: + type = "Bulk"; + if (speed == USB_SPEED_HIGH && dir == 'O') /* uframes per NAK */ + interval = desc->bInterval; + else + interval = 0; + break; + case USB_ENDPOINT_XFER_INT: + type = "Int."; + if (speed == USB_SPEED_HIGH || speed == USB_SPEED_SUPER) + interval = 1 << (desc->bInterval - 1); + else + interval = desc->bInterval; + break; + default: /* "can't happen" */ + return start; + } + interval *= (speed == USB_SPEED_HIGH || + speed == USB_SPEED_SUPER) ? 125 : 1000; + if (interval % 1000) + unit = 'u'; + else { + unit = 'm'; + interval /= 1000; + } + + start += sprintf(start, format_endpt, desc->bEndpointAddress, dir, + desc->bmAttributes, type, + (usb_endpoint_maxp(desc) & 0x07ff) * + bandwidth, + interval, unit); + return start; +} + +static char *usb_dump_interface_descriptor(char *start, char *end, + const struct usb_interface_cache *intfc, + const struct usb_interface *iface, + int setno) +{ + const struct usb_interface_descriptor *desc; + const char *driver_name = ""; + int active = 0; + + if (start > end) + return start; + desc = &intfc->altsetting[setno].desc; + if (iface) { + driver_name = (iface->dev.driver + ? iface->dev.driver->name + : "(none)"); + active = (desc == &iface->cur_altsetting->desc); + } + start += sprintf(start, format_iface, + active ? '*' : ' ', /* mark active altsetting */ + desc->bInterfaceNumber, + desc->bAlternateSetting, + desc->bNumEndpoints, + desc->bInterfaceClass, + class_decode(desc->bInterfaceClass), + desc->bInterfaceSubClass, + desc->bInterfaceProtocol, + driver_name); + return start; +} + +static char *usb_dump_interface(int speed, char *start, char *end, + const struct usb_interface_cache *intfc, + const struct usb_interface *iface, int setno) +{ + const struct usb_host_interface *desc = &intfc->altsetting[setno]; + int i; + + start = usb_dump_interface_descriptor(start, end, intfc, iface, setno); + for (i = 0; i < desc->desc.bNumEndpoints; i++) { + if (start > end) + return start; + start = usb_dump_endpoint_descriptor(speed, + start, end, &desc->endpoint[i].desc); + } + return start; +} + +static char *usb_dump_iad_descriptor(char *start, char *end, + const struct usb_interface_assoc_descriptor *iad) +{ + if (start > end) + return start; + start += sprintf(start, format_iad, + iad->bFirstInterface, + iad->bInterfaceCount, + iad->bFunctionClass, + class_decode(iad->bFunctionClass), + iad->bFunctionSubClass, + iad->bFunctionProtocol); + return start; +} + +/* TBD: + * 0. TBDs + * 1. marking active interface altsettings (code lists all, but should mark + * which ones are active, if any) + */ +static char *usb_dump_config_descriptor(char *start, char *end, + const struct usb_config_descriptor *desc, + int active, int speed) +{ + int mul; + + if (start > end) + return start; + if (speed == USB_SPEED_SUPER) + mul = 8; + else + mul = 2; + start += sprintf(start, format_config, + /* mark active/actual/current cfg. */ + active ? '*' : ' ', + desc->bNumInterfaces, + desc->bConfigurationValue, + desc->bmAttributes, + desc->bMaxPower * mul); + return start; +} + +static char *usb_dump_config(int speed, char *start, char *end, + const struct usb_host_config *config, int active) +{ + int i, j; + struct usb_interface_cache *intfc; + struct usb_interface *interface; + + if (start > end) + return start; + if (!config) + /* getting these some in 2.3.7; none in 2.3.6 */ + return start + sprintf(start, "(null Cfg. desc.)\n"); + start = usb_dump_config_descriptor(start, end, &config->desc, active, + speed); + for (i = 0; i < USB_MAXIADS; i++) { + if (config->intf_assoc[i] == NULL) + break; + start = usb_dump_iad_descriptor(start, end, + config->intf_assoc[i]); + } + for (i = 0; i < config->desc.bNumInterfaces; i++) { + intfc = config->intf_cache[i]; + interface = config->interface[i]; + for (j = 0; j < intfc->num_altsetting; j++) { + if (start > end) + return start; + start = usb_dump_interface(speed, + start, end, intfc, interface, j); + } + } + return start; +} + +/* + * Dump the different USB descriptors. + */ +static char *usb_dump_device_descriptor(char *start, char *end, + const struct usb_device_descriptor *desc) +{ + u16 bcdUSB = le16_to_cpu(desc->bcdUSB); + u16 bcdDevice = le16_to_cpu(desc->bcdDevice); + + if (start > end) + return start; + start += sprintf(start, format_device1, + bcdUSB >> 8, bcdUSB & 0xff, + desc->bDeviceClass, + class_decode(desc->bDeviceClass), + desc->bDeviceSubClass, + desc->bDeviceProtocol, + desc->bMaxPacketSize0, + desc->bNumConfigurations); + if (start > end) + return start; + start += sprintf(start, format_device2, + le16_to_cpu(desc->idVendor), + le16_to_cpu(desc->idProduct), + bcdDevice >> 8, bcdDevice & 0xff); + return start; +} + +/* + * Dump the different strings that this device holds. + */ +static char *usb_dump_device_strings(char *start, char *end, + struct usb_device *dev) +{ + if (start > end) + return start; + if (dev->manufacturer) + start += sprintf(start, format_string_manufacturer, + dev->manufacturer); + if (start > end) + goto out; + if (dev->product) + start += sprintf(start, format_string_product, dev->product); + if (start > end) + goto out; +#ifdef ALLOW_SERIAL_NUMBER + if (dev->serial) + start += sprintf(start, format_string_serialnumber, + dev->serial); +#endif + out: + return start; +} + +static char *usb_dump_desc(char *start, char *end, struct usb_device *dev) +{ + int i; + + if (start > end) + return start; + + start = usb_dump_device_descriptor(start, end, &dev->descriptor); + + if (start > end) + return start; + + start = usb_dump_device_strings(start, end, dev); + + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { + if (start > end) + return start; + start = usb_dump_config(dev->speed, + start, end, dev->config + i, + /* active ? */ + (dev->config + i) == dev->actconfig); + } + return start; +} + + +#ifdef PROC_EXTRA /* TBD: may want to add this code later */ + +static char *usb_dump_hub_descriptor(char *start, char *end, + const struct usb_hub_descriptor *desc) +{ + int leng = USB_DT_HUB_NONVAR_SIZE; + unsigned char *ptr = (unsigned char *)desc; + + if (start > end) + return start; + start += sprintf(start, "Interface:"); + while (leng && start <= end) { + start += sprintf(start, " %02x", *ptr); + ptr++; leng--; + } + *start++ = '\n'; + return start; +} + +static char *usb_dump_string(char *start, char *end, + const struct usb_device *dev, char *id, int index) +{ + if (start > end) + return start; + start += sprintf(start, "Interface:"); + if (index <= dev->maxstring && dev->stringindex && + dev->stringindex[index]) + start += sprintf(start, "%s: %.100s ", id, + dev->stringindex[index]); + return start; +} + +#endif /* PROC_EXTRA */ + +/*****************************************************************/ + +/* This is a recursive function. Parameters: + * buffer - the user-space buffer to write data into + * nbytes - the maximum number of bytes to write + * skip_bytes - the number of bytes to skip before writing anything + * file_offset - the offset into the devices file on completion + * The caller must own the device lock. + */ +static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + loff_t *skip_bytes, loff_t *file_offset, + struct usb_device *usbdev, struct usb_bus *bus, + int level, int index, int count) +{ + int chix; + int ret, cnt = 0; + int parent_devnum = 0; + char *pages_start, *data_end, *speed; + unsigned int length; + ssize_t total_written = 0; + struct usb_device *childdev = NULL; + + /* don't bother with anything else if we're not writing any data */ + if (*nbytes <= 0) + return 0; + + if (level > MAX_TOPO_LEVEL) + return 0; + /* allocate 2^1 pages = 8K (on i386); + * should be more than enough for one device */ + pages_start = (char *)__get_free_pages(GFP_NOIO, 1); + if (!pages_start) + return -ENOMEM; + + if (usbdev->parent && usbdev->parent->devnum != -1) + parent_devnum = usbdev->parent->devnum; + /* + * So the root hub's parent is 0 and any device that is + * plugged into the root hub has a parent of 0. + */ + switch (usbdev->speed) { + case USB_SPEED_LOW: + speed = "1.5"; break; + case USB_SPEED_UNKNOWN: /* usb 1.1 root hub code */ + case USB_SPEED_FULL: + speed = "12"; break; + case USB_SPEED_WIRELESS: /* Wireless has no real fixed speed */ + case USB_SPEED_HIGH: + speed = "480"; break; + case USB_SPEED_SUPER: + speed = "5000"; break; + default: + speed = "??"; + } + data_end = pages_start + sprintf(pages_start, format_topo, + bus->busnum, level, parent_devnum, + index, count, usbdev->devnum, + speed, usbdev->maxchild); + /* + * level = topology-tier level; + * parent_devnum = parent device number; + * index = parent's connector number; + * count = device count at this level + */ + /* If this is the root hub, display the bandwidth information */ + if (level == 0) { + int max; + + /* super/high speed reserves 80%, full/low reserves 90% */ + if (usbdev->speed == USB_SPEED_HIGH || + usbdev->speed == USB_SPEED_SUPER) + max = 800; + else + max = FRAME_TIME_MAX_USECS_ALLOC; + + /* report "average" periodic allocation over a microsecond. + * the schedules are actually bursty, HCDs need to deal with + * that and just compute/report this average. + */ + data_end += sprintf(data_end, format_bandwidth, + bus->bandwidth_allocated, max, + (100 * bus->bandwidth_allocated + max / 2) + / max, + bus->bandwidth_int_reqs, + bus->bandwidth_isoc_reqs); + + } + data_end = usb_dump_desc(data_end, pages_start + (2 * PAGE_SIZE) - 256, + usbdev); + + if (data_end > (pages_start + (2 * PAGE_SIZE) - 256)) + data_end += sprintf(data_end, "(truncated)\n"); + + length = data_end - pages_start; + /* if we can start copying some data to the user */ + if (length > *skip_bytes) { + length -= *skip_bytes; + if (length > *nbytes) + length = *nbytes; + if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { + free_pages((unsigned long)pages_start, 1); + return -EFAULT; + } + *nbytes -= length; + *file_offset += length; + total_written += length; + *buffer += length; + *skip_bytes = 0; + } else + *skip_bytes -= length; + + free_pages((unsigned long)pages_start, 1); + + /* Now look at all of this device's children. */ + usb_hub_for_each_child(usbdev, chix, childdev) { + usb_lock_device(childdev); + ret = usb_device_dump(buffer, nbytes, skip_bytes, + file_offset, childdev, bus, + level + 1, chix - 1, ++cnt); + usb_unlock_device(childdev); + if (ret == -EFAULT) + return total_written; + total_written += ret; + } + return total_written; +} + +static ssize_t usb_device_read(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct usb_bus *bus; + ssize_t ret, total_written = 0; + loff_t skip_bytes = *ppos; + + if (*ppos < 0) + return -EINVAL; + if (nbytes <= 0) + return 0; + if (!access_ok(VERIFY_WRITE, buf, nbytes)) + return -EFAULT; + + mutex_lock(&usb_bus_list_lock); + /* print devices for all busses */ + list_for_each_entry(bus, &usb_bus_list, bus_list) { + /* recurse through all children of the root hub */ + if (!bus_to_hcd(bus)->rh_registered) + continue; + usb_lock_device(bus->root_hub); + ret = usb_device_dump(&buf, &nbytes, &skip_bytes, ppos, + bus->root_hub, bus, 0, 0, 0); + usb_unlock_device(bus->root_hub); + if (ret < 0) { + mutex_unlock(&usb_bus_list_lock); + return ret; + } + total_written += ret; + } + mutex_unlock(&usb_bus_list_lock); + return total_written; +} + +/* Kernel lock for "lastev" protection */ +static unsigned int usb_device_poll(struct file *file, + struct poll_table_struct *wait) +{ + unsigned int event_count; + + poll_wait(file, &device_event.wait, wait); + + event_count = atomic_read(&device_event.count); + if (file->f_version != event_count) { + file->f_version = event_count; + return POLLIN | POLLRDNORM; + } + + return 0; +} + +static loff_t usb_device_lseek(struct file *file, loff_t offset, int orig) +{ + loff_t ret; + + mutex_lock(&file_inode(file)->i_mutex); + + switch (orig) { + case 0: + file->f_pos = offset; + ret = file->f_pos; + break; + case 1: + file->f_pos += offset; + ret = file->f_pos; + break; + case 2: + default: + ret = -EINVAL; + } + + mutex_unlock(&file_inode(file)->i_mutex); + return ret; +} + +const struct file_operations usbfs_devices_fops = { + .llseek = usb_device_lseek, + .read = usb_device_read, + .poll = usb_device_poll, +}; diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c new file mode 100644 index 00000000..ce773cca --- /dev/null +++ b/drivers/usb/core/devio.c @@ -0,0 +1,2291 @@ +/*****************************************************************************/ + +/* + * devio.c -- User space communication with USB devices. + * + * Copyright (C) 1999-2000 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This file implements the usbfs/x/y files, where + * x is the bus number and y the device number. + * + * It allows user space programs/"drivers" to communicate directly + * with USB devices without intervening kernel driver. + * + * Revision history + * 22.12.1999 0.1 Initial release (split from proc_usb.c) + * 04.01.2000 0.2 Turned into its own filesystem + * 30.09.2005 0.3 Fix user-triggerable oops in async URB delivery + * (CAN-2005-3055) + */ + +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for usbcore internals */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb.h" + +#define USB_MAXBUS 64 +#define USB_DEVICE_MAX USB_MAXBUS * 128 +#define USB_SG_SIZE 16384 /* split-size for large txs */ + +/* Mutual exclusion for removal, open, and release */ +DEFINE_MUTEX(usbfs_mutex); + +struct dev_state { + struct list_head list; /* state list */ + struct usb_device *dev; + struct file *file; + spinlock_t lock; /* protects the async urb lists */ + struct list_head async_pending; + struct list_head async_completed; + wait_queue_head_t wait; /* wake up if a request completed */ + unsigned int discsignr; + struct pid *disc_pid; + const struct cred *cred; + void __user *disccontext; + unsigned long ifclaimed; + u32 secid; + u32 disabled_bulk_eps; +}; + +struct async { + struct list_head asynclist; + struct dev_state *ps; + struct pid *pid; + const struct cred *cred; + unsigned int signr; + unsigned int ifnum; + void __user *userbuffer; + void __user *userurb; + struct urb *urb; + unsigned int mem_usage; + int status; + u32 secid; + u8 bulk_addr; + u8 bulk_status; +}; + +static bool usbfs_snoop; +module_param(usbfs_snoop, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(usbfs_snoop, "true to log all usbfs traffic"); + +#define snoop(dev, format, arg...) \ + do { \ + if (usbfs_snoop) \ + dev_info(dev , format , ## arg); \ + } while (0) + +enum snoop_when { + SUBMIT, COMPLETE +}; + +#define USB_DEVICE_DEV MKDEV(USB_DEVICE_MAJOR, 0) + +/* Limit on the total amount of memory we can allocate for transfers */ +static unsigned usbfs_memory_mb = 16; +module_param(usbfs_memory_mb, uint, 0644); +MODULE_PARM_DESC(usbfs_memory_mb, + "maximum MB allowed for usbfs buffers (0 = no limit)"); + +/* Hard limit, necessary to avoid aithmetic overflow */ +#define USBFS_XFER_MAX (UINT_MAX / 2 - 1000000) + +static atomic_t usbfs_memory_usage; /* Total memory currently allocated */ + +/* Check whether it's okay to allocate more memory for a transfer */ +static int usbfs_increase_memory_usage(unsigned amount) +{ + unsigned lim; + + /* + * Convert usbfs_memory_mb to bytes, avoiding overflows. + * 0 means use the hard limit (effectively unlimited). + */ + lim = ACCESS_ONCE(usbfs_memory_mb); + if (lim == 0 || lim > (USBFS_XFER_MAX >> 20)) + lim = USBFS_XFER_MAX; + else + lim <<= 20; + + atomic_add(amount, &usbfs_memory_usage); + if (atomic_read(&usbfs_memory_usage) <= lim) + return 0; + atomic_sub(amount, &usbfs_memory_usage); + return -ENOMEM; +} + +/* Memory for a transfer is being deallocated */ +static void usbfs_decrease_memory_usage(unsigned amount) +{ + atomic_sub(amount, &usbfs_memory_usage); +} + +static int connected(struct dev_state *ps) +{ + return (!list_empty(&ps->list) && + ps->dev->state != USB_STATE_NOTATTACHED); +} + +static loff_t usbdev_lseek(struct file *file, loff_t offset, int orig) +{ + loff_t ret; + + mutex_lock(&file_inode(file)->i_mutex); + + switch (orig) { + case 0: + file->f_pos = offset; + ret = file->f_pos; + break; + case 1: + file->f_pos += offset; + ret = file->f_pos; + break; + case 2: + default: + ret = -EINVAL; + } + + mutex_unlock(&file_inode(file)->i_mutex); + return ret; +} + +static ssize_t usbdev_read(struct file *file, char __user *buf, size_t nbytes, + loff_t *ppos) +{ + struct dev_state *ps = file->private_data; + struct usb_device *dev = ps->dev; + ssize_t ret = 0; + unsigned len; + loff_t pos; + int i; + + pos = *ppos; + usb_lock_device(dev); + if (!connected(ps)) { + ret = -ENODEV; + goto err; + } else if (pos < 0) { + ret = -EINVAL; + goto err; + } + + if (pos < sizeof(struct usb_device_descriptor)) { + /* 18 bytes - fits on the stack */ + struct usb_device_descriptor temp_desc; + + memcpy(&temp_desc, &dev->descriptor, sizeof(dev->descriptor)); + le16_to_cpus(&temp_desc.bcdUSB); + le16_to_cpus(&temp_desc.idVendor); + le16_to_cpus(&temp_desc.idProduct); + le16_to_cpus(&temp_desc.bcdDevice); + + len = sizeof(struct usb_device_descriptor) - pos; + if (len > nbytes) + len = nbytes; + if (copy_to_user(buf, ((char *)&temp_desc) + pos, len)) { + ret = -EFAULT; + goto err; + } + + *ppos += len; + buf += len; + nbytes -= len; + ret += len; + } + + pos = sizeof(struct usb_device_descriptor); + for (i = 0; nbytes && i < dev->descriptor.bNumConfigurations; i++) { + struct usb_config_descriptor *config = + (struct usb_config_descriptor *)dev->rawdescriptors[i]; + unsigned int length = le16_to_cpu(config->wTotalLength); + + if (*ppos < pos + length) { + + /* The descriptor may claim to be longer than it + * really is. Here is the actual allocated length. */ + unsigned alloclen = + le16_to_cpu(dev->config[i].desc.wTotalLength); + + len = length - (*ppos - pos); + if (len > nbytes) + len = nbytes; + + /* Simply don't write (skip over) unallocated parts */ + if (alloclen > (*ppos - pos)) { + alloclen -= (*ppos - pos); + if (copy_to_user(buf, + dev->rawdescriptors[i] + (*ppos - pos), + min(len, alloclen))) { + ret = -EFAULT; + goto err; + } + } + + *ppos += len; + buf += len; + nbytes -= len; + ret += len; + } + + pos += length; + } + +err: + usb_unlock_device(dev); + return ret; +} + +/* + * async list handling + */ + +static struct async *alloc_async(unsigned int numisoframes) +{ + struct async *as; + + as = kzalloc(sizeof(struct async), GFP_KERNEL); + if (!as) + return NULL; + as->urb = usb_alloc_urb(numisoframes, GFP_KERNEL); + if (!as->urb) { + kfree(as); + return NULL; + } + return as; +} + +static void free_async(struct async *as) +{ + int i; + + put_pid(as->pid); + if (as->cred) + put_cred(as->cred); + for (i = 0; i < as->urb->num_sgs; i++) { + if (sg_page(&as->urb->sg[i])) + kfree(sg_virt(&as->urb->sg[i])); + } + kfree(as->urb->sg); + kfree(as->urb->transfer_buffer); + kfree(as->urb->setup_packet); + usb_free_urb(as->urb); + usbfs_decrease_memory_usage(as->mem_usage); + kfree(as); +} + +static void async_newpending(struct async *as) +{ + struct dev_state *ps = as->ps; + unsigned long flags; + + spin_lock_irqsave(&ps->lock, flags); + list_add_tail(&as->asynclist, &ps->async_pending); + spin_unlock_irqrestore(&ps->lock, flags); +} + +static void async_removepending(struct async *as) +{ + struct dev_state *ps = as->ps; + unsigned long flags; + + spin_lock_irqsave(&ps->lock, flags); + list_del_init(&as->asynclist); + spin_unlock_irqrestore(&ps->lock, flags); +} + +static struct async *async_getcompleted(struct dev_state *ps) +{ + unsigned long flags; + struct async *as = NULL; + + spin_lock_irqsave(&ps->lock, flags); + if (!list_empty(&ps->async_completed)) { + as = list_entry(ps->async_completed.next, struct async, + asynclist); + list_del_init(&as->asynclist); + } + spin_unlock_irqrestore(&ps->lock, flags); + return as; +} + +static struct async *async_getpending(struct dev_state *ps, + void __user *userurb) +{ + struct async *as; + + list_for_each_entry(as, &ps->async_pending, asynclist) + if (as->userurb == userurb) { + list_del_init(&as->asynclist); + return as; + } + + return NULL; +} + +static void snoop_urb(struct usb_device *udev, + void __user *userurb, int pipe, unsigned length, + int timeout_or_status, enum snoop_when when, + unsigned char *data, unsigned data_len) +{ + static const char *types[] = {"isoc", "int", "ctrl", "bulk"}; + static const char *dirs[] = {"out", "in"}; + int ep; + const char *t, *d; + + if (!usbfs_snoop) + return; + + ep = usb_pipeendpoint(pipe); + t = types[usb_pipetype(pipe)]; + d = dirs[!!usb_pipein(pipe)]; + + if (userurb) { /* Async */ + if (when == SUBMIT) + dev_info(&udev->dev, "userurb %p, ep%d %s-%s, " + "length %u\n", + userurb, ep, t, d, length); + else + dev_info(&udev->dev, "userurb %p, ep%d %s-%s, " + "actual_length %u status %d\n", + userurb, ep, t, d, length, + timeout_or_status); + } else { + if (when == SUBMIT) + dev_info(&udev->dev, "ep%d %s-%s, length %u, " + "timeout %d\n", + ep, t, d, length, timeout_or_status); + else + dev_info(&udev->dev, "ep%d %s-%s, actual_length %u, " + "status %d\n", + ep, t, d, length, timeout_or_status); + } + + if (data && data_len > 0) { + print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_NONE, 32, 1, + data, data_len, 1); + } +} + +static void snoop_urb_data(struct urb *urb, unsigned len) +{ + int i, size; + + if (!usbfs_snoop) + return; + + if (urb->num_sgs == 0) { + print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_NONE, 32, 1, + urb->transfer_buffer, len, 1); + return; + } + + for (i = 0; i < urb->num_sgs && len; i++) { + size = (len > USB_SG_SIZE) ? USB_SG_SIZE : len; + print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_NONE, 32, 1, + sg_virt(&urb->sg[i]), size, 1); + len -= size; + } +} + +static int copy_urb_data_to_user(u8 __user *userbuffer, struct urb *urb) +{ + unsigned i, len, size; + + if (urb->number_of_packets > 0) /* Isochronous */ + len = urb->transfer_buffer_length; + else /* Non-Isoc */ + len = urb->actual_length; + + if (urb->num_sgs == 0) { + if (copy_to_user(userbuffer, urb->transfer_buffer, len)) + return -EFAULT; + return 0; + } + + for (i = 0; i < urb->num_sgs && len; i++) { + size = (len > USB_SG_SIZE) ? USB_SG_SIZE : len; + if (copy_to_user(userbuffer, sg_virt(&urb->sg[i]), size)) + return -EFAULT; + userbuffer += size; + len -= size; + } + + return 0; +} + +#define AS_CONTINUATION 1 +#define AS_UNLINK 2 + +static void cancel_bulk_urbs(struct dev_state *ps, unsigned bulk_addr) +__releases(ps->lock) +__acquires(ps->lock) +{ + struct urb *urb; + struct async *as; + + /* Mark all the pending URBs that match bulk_addr, up to but not + * including the first one without AS_CONTINUATION. If such an + * URB is encountered then a new transfer has already started so + * the endpoint doesn't need to be disabled; otherwise it does. + */ + list_for_each_entry(as, &ps->async_pending, asynclist) { + if (as->bulk_addr == bulk_addr) { + if (as->bulk_status != AS_CONTINUATION) + goto rescan; + as->bulk_status = AS_UNLINK; + as->bulk_addr = 0; + } + } + ps->disabled_bulk_eps |= (1 << bulk_addr); + + /* Now carefully unlink all the marked pending URBs */ + rescan: + list_for_each_entry(as, &ps->async_pending, asynclist) { + if (as->bulk_status == AS_UNLINK) { + as->bulk_status = 0; /* Only once */ + urb = as->urb; + usb_get_urb(urb); + spin_unlock(&ps->lock); /* Allow completions */ + usb_unlink_urb(urb); + usb_put_urb(urb); + spin_lock(&ps->lock); + goto rescan; + } + } +} + +static void async_completed(struct urb *urb) +{ + struct async *as = urb->context; + struct dev_state *ps = as->ps; + struct siginfo sinfo; + struct pid *pid = NULL; + u32 secid = 0; + const struct cred *cred = NULL; + int signr; + + spin_lock(&ps->lock); + list_move_tail(&as->asynclist, &ps->async_completed); + as->status = urb->status; + signr = as->signr; + if (signr) { + sinfo.si_signo = as->signr; + sinfo.si_errno = as->status; + sinfo.si_code = SI_ASYNCIO; + sinfo.si_addr = as->userurb; + pid = get_pid(as->pid); + cred = get_cred(as->cred); + secid = as->secid; + } + snoop(&urb->dev->dev, "urb complete\n"); + snoop_urb(urb->dev, as->userurb, urb->pipe, urb->actual_length, + as->status, COMPLETE, NULL, 0); + if ((urb->transfer_flags & URB_DIR_MASK) == USB_DIR_IN) + snoop_urb_data(urb, urb->actual_length); + + if (as->status < 0 && as->bulk_addr && as->status != -ECONNRESET && + as->status != -ENOENT) + cancel_bulk_urbs(ps, as->bulk_addr); + spin_unlock(&ps->lock); + + if (signr) { + kill_pid_info_as_cred(sinfo.si_signo, &sinfo, pid, cred, secid); + put_pid(pid); + put_cred(cred); + } + + wake_up(&ps->wait); +} + +static void destroy_async(struct dev_state *ps, struct list_head *list) +{ + struct urb *urb; + struct async *as; + unsigned long flags; + + spin_lock_irqsave(&ps->lock, flags); + while (!list_empty(list)) { + as = list_entry(list->next, struct async, asynclist); + list_del_init(&as->asynclist); + urb = as->urb; + usb_get_urb(urb); + + /* drop the spinlock so the completion handler can run */ + spin_unlock_irqrestore(&ps->lock, flags); + usb_kill_urb(urb); + usb_put_urb(urb); + spin_lock_irqsave(&ps->lock, flags); + } + spin_unlock_irqrestore(&ps->lock, flags); +} + +static void destroy_async_on_interface(struct dev_state *ps, + unsigned int ifnum) +{ + struct list_head *p, *q, hitlist; + unsigned long flags; + + INIT_LIST_HEAD(&hitlist); + spin_lock_irqsave(&ps->lock, flags); + list_for_each_safe(p, q, &ps->async_pending) + if (ifnum == list_entry(p, struct async, asynclist)->ifnum) + list_move_tail(p, &hitlist); + spin_unlock_irqrestore(&ps->lock, flags); + destroy_async(ps, &hitlist); +} + +static void destroy_all_async(struct dev_state *ps) +{ + destroy_async(ps, &ps->async_pending); +} + +/* + * interface claims are made only at the request of user level code, + * which can also release them (explicitly or by closing files). + * they're also undone when devices disconnect. + */ + +static int driver_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return -ENODEV; +} + +static void driver_disconnect(struct usb_interface *intf) +{ + struct dev_state *ps = usb_get_intfdata(intf); + unsigned int ifnum = intf->altsetting->desc.bInterfaceNumber; + + if (!ps) + return; + + /* NOTE: this relies on usbcore having canceled and completed + * all pending I/O requests; 2.6 does that. + */ + + if (likely(ifnum < 8*sizeof(ps->ifclaimed))) + clear_bit(ifnum, &ps->ifclaimed); + else + dev_warn(&intf->dev, "interface number %u out of range\n", + ifnum); + + usb_set_intfdata(intf, NULL); + + /* force async requests to complete */ + destroy_async_on_interface(ps, ifnum); +} + +/* The following routines are merely placeholders. There is no way + * to inform a user task about suspend or resumes. + */ +static int driver_suspend(struct usb_interface *intf, pm_message_t msg) +{ + return 0; +} + +static int driver_resume(struct usb_interface *intf) +{ + return 0; +} + +struct usb_driver usbfs_driver = { + .name = "usbfs", + .probe = driver_probe, + .disconnect = driver_disconnect, + .suspend = driver_suspend, + .resume = driver_resume, +}; + +static int claimintf(struct dev_state *ps, unsigned int ifnum) +{ + struct usb_device *dev = ps->dev; + struct usb_interface *intf; + int err; + + if (ifnum >= 8*sizeof(ps->ifclaimed)) + return -EINVAL; + /* already claimed */ + if (test_bit(ifnum, &ps->ifclaimed)) + return 0; + + intf = usb_ifnum_to_if(dev, ifnum); + if (!intf) + err = -ENOENT; + else + err = usb_driver_claim_interface(&usbfs_driver, intf, ps); + if (err == 0) + set_bit(ifnum, &ps->ifclaimed); + return err; +} + +static int releaseintf(struct dev_state *ps, unsigned int ifnum) +{ + struct usb_device *dev; + struct usb_interface *intf; + int err; + + err = -EINVAL; + if (ifnum >= 8*sizeof(ps->ifclaimed)) + return err; + dev = ps->dev; + intf = usb_ifnum_to_if(dev, ifnum); + if (!intf) + err = -ENOENT; + else if (test_and_clear_bit(ifnum, &ps->ifclaimed)) { + usb_driver_release_interface(&usbfs_driver, intf); + err = 0; + } + return err; +} + +static int checkintf(struct dev_state *ps, unsigned int ifnum) +{ + if (ps->dev->state != USB_STATE_CONFIGURED) + return -EHOSTUNREACH; + if (ifnum >= 8*sizeof(ps->ifclaimed)) + return -EINVAL; + if (test_bit(ifnum, &ps->ifclaimed)) + return 0; + /* if not yet claimed, claim it for the driver */ + dev_warn(&ps->dev->dev, "usbfs: process %d (%s) did not claim " + "interface %u before use\n", task_pid_nr(current), + current->comm, ifnum); + return claimintf(ps, ifnum); +} + +static int findintfep(struct usb_device *dev, unsigned int ep) +{ + unsigned int i, j, e; + struct usb_interface *intf; + struct usb_host_interface *alts; + struct usb_endpoint_descriptor *endpt; + + if (ep & ~(USB_DIR_IN|0xf)) + return -EINVAL; + if (!dev->actconfig) + return -ESRCH; + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf = dev->actconfig->interface[i]; + for (j = 0; j < intf->num_altsetting; j++) { + alts = &intf->altsetting[j]; + for (e = 0; e < alts->desc.bNumEndpoints; e++) { + endpt = &alts->endpoint[e].desc; + if (endpt->bEndpointAddress == ep) + return alts->desc.bInterfaceNumber; + } + } + } + return -ENOENT; +} + +static int check_ctrlrecip(struct dev_state *ps, unsigned int requesttype, + unsigned int request, unsigned int index) +{ + int ret = 0; + struct usb_host_interface *alt_setting; + + if (ps->dev->state != USB_STATE_UNAUTHENTICATED + && ps->dev->state != USB_STATE_ADDRESS + && ps->dev->state != USB_STATE_CONFIGURED) + return -EHOSTUNREACH; + if (USB_TYPE_VENDOR == (USB_TYPE_MASK & requesttype)) + return 0; + + /* + * check for the special corner case 'get_device_id' in the printer + * class specification, where wIndex is (interface << 8 | altsetting) + * instead of just interface + */ + if (requesttype == 0xa1 && request == 0) { + alt_setting = usb_find_alt_setting(ps->dev->actconfig, + index >> 8, index & 0xff); + if (alt_setting + && alt_setting->desc.bInterfaceClass == USB_CLASS_PRINTER) + index >>= 8; + } + + index &= 0xff; + switch (requesttype & USB_RECIP_MASK) { + case USB_RECIP_ENDPOINT: + if ((index & ~USB_DIR_IN) == 0) + return 0; + ret = findintfep(ps->dev, index); + if (ret < 0) { + /* + * Some not fully compliant Win apps seem to get + * index wrong and have the endpoint number here + * rather than the endpoint address (with the + * correct direction). Win does let this through, + * so we'll not reject it here but leave it to + * the device to not break KVM. But we warn. + */ + ret = findintfep(ps->dev, index ^ 0x80); + if (ret >= 0) + dev_info(&ps->dev->dev, + "%s: process %i (%s) requesting ep %02x but needs %02x\n", + __func__, task_pid_nr(current), + current->comm, index, index ^ 0x80); + } + if (ret >= 0) + ret = checkintf(ps, ret); + break; + + case USB_RECIP_INTERFACE: + ret = checkintf(ps, index); + break; + } + return ret; +} + +static int match_devt(struct device *dev, void *data) +{ + return dev->devt == (dev_t) (unsigned long) data; +} + +static struct usb_device *usbdev_lookup_by_devt(dev_t devt) +{ + struct device *dev; + + dev = bus_find_device(&usb_bus_type, NULL, + (void *) (unsigned long) devt, match_devt); + if (!dev) + return NULL; + return container_of(dev, struct usb_device, dev); +} + +/* + * file operations + */ +static int usbdev_open(struct inode *inode, struct file *file) +{ + struct usb_device *dev = NULL; + struct dev_state *ps; + int ret; + + ret = -ENOMEM; + ps = kmalloc(sizeof(struct dev_state), GFP_KERNEL); + if (!ps) + goto out_free_ps; + + ret = -ENODEV; + + /* Protect against simultaneous removal or release */ + mutex_lock(&usbfs_mutex); + + /* usbdev device-node */ + if (imajor(inode) == USB_DEVICE_MAJOR) + dev = usbdev_lookup_by_devt(inode->i_rdev); + + mutex_unlock(&usbfs_mutex); + + if (!dev) + goto out_free_ps; + + usb_lock_device(dev); + if (dev->state == USB_STATE_NOTATTACHED) + goto out_unlock_device; + + ret = usb_autoresume_device(dev); + if (ret) + goto out_unlock_device; + + ps->dev = dev; + ps->file = file; + spin_lock_init(&ps->lock); + INIT_LIST_HEAD(&ps->list); + INIT_LIST_HEAD(&ps->async_pending); + INIT_LIST_HEAD(&ps->async_completed); + init_waitqueue_head(&ps->wait); + ps->discsignr = 0; + ps->disc_pid = get_pid(task_pid(current)); + ps->cred = get_current_cred(); + ps->disccontext = NULL; + ps->ifclaimed = 0; + security_task_getsecid(current, &ps->secid); + smp_wmb(); + list_add_tail(&ps->list, &dev->filelist); + file->private_data = ps; + usb_unlock_device(dev); + snoop(&dev->dev, "opened by process %d: %s\n", task_pid_nr(current), + current->comm); + return ret; + + out_unlock_device: + usb_unlock_device(dev); + usb_put_dev(dev); + out_free_ps: + kfree(ps); + return ret; +} + +static int usbdev_release(struct inode *inode, struct file *file) +{ + struct dev_state *ps = file->private_data; + struct usb_device *dev = ps->dev; + unsigned int ifnum; + struct async *as; + + usb_lock_device(dev); + usb_hub_release_all_ports(dev, ps); + + list_del_init(&ps->list); + + for (ifnum = 0; ps->ifclaimed && ifnum < 8*sizeof(ps->ifclaimed); + ifnum++) { + if (test_bit(ifnum, &ps->ifclaimed)) + releaseintf(ps, ifnum); + } + destroy_all_async(ps); + usb_autosuspend_device(dev); + usb_unlock_device(dev); + usb_put_dev(dev); + put_pid(ps->disc_pid); + put_cred(ps->cred); + + as = async_getcompleted(ps); + while (as) { + free_async(as); + as = async_getcompleted(ps); + } + kfree(ps); + return 0; +} + +static int proc_control(struct dev_state *ps, void __user *arg) +{ + struct usb_device *dev = ps->dev; + struct usbdevfs_ctrltransfer ctrl; + unsigned int tmo; + unsigned char *tbuf; + unsigned wLength; + int i, pipe, ret; + + if (copy_from_user(&ctrl, arg, sizeof(ctrl))) + return -EFAULT; + ret = check_ctrlrecip(ps, ctrl.bRequestType, ctrl.bRequest, + ctrl.wIndex); + if (ret) + return ret; + wLength = ctrl.wLength; /* To suppress 64k PAGE_SIZE warning */ + if (wLength > PAGE_SIZE) + return -EINVAL; + ret = usbfs_increase_memory_usage(PAGE_SIZE + sizeof(struct urb) + + sizeof(struct usb_ctrlrequest)); + if (ret) + return ret; + tbuf = (unsigned char *)__get_free_page(GFP_KERNEL); + if (!tbuf) { + ret = -ENOMEM; + goto done; + } + tmo = ctrl.timeout; + snoop(&dev->dev, "control urb: bRequestType=%02x " + "bRequest=%02x wValue=%04x " + "wIndex=%04x wLength=%04x\n", + ctrl.bRequestType, ctrl.bRequest, + __le16_to_cpup(&ctrl.wValue), + __le16_to_cpup(&ctrl.wIndex), + __le16_to_cpup(&ctrl.wLength)); + if (ctrl.bRequestType & 0x80) { + if (ctrl.wLength && !access_ok(VERIFY_WRITE, ctrl.data, + ctrl.wLength)) { + ret = -EINVAL; + goto done; + } + pipe = usb_rcvctrlpipe(dev, 0); + snoop_urb(dev, NULL, pipe, ctrl.wLength, tmo, SUBMIT, NULL, 0); + + usb_unlock_device(dev); + i = usb_control_msg(dev, pipe, ctrl.bRequest, + ctrl.bRequestType, ctrl.wValue, ctrl.wIndex, + tbuf, ctrl.wLength, tmo); + usb_lock_device(dev); + snoop_urb(dev, NULL, pipe, max(i, 0), min(i, 0), COMPLETE, + tbuf, max(i, 0)); + if ((i > 0) && ctrl.wLength) { + if (copy_to_user(ctrl.data, tbuf, i)) { + ret = -EFAULT; + goto done; + } + } + } else { + if (ctrl.wLength) { + if (copy_from_user(tbuf, ctrl.data, ctrl.wLength)) { + ret = -EFAULT; + goto done; + } + } + pipe = usb_sndctrlpipe(dev, 0); + snoop_urb(dev, NULL, pipe, ctrl.wLength, tmo, SUBMIT, + tbuf, ctrl.wLength); + + usb_unlock_device(dev); + i = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), ctrl.bRequest, + ctrl.bRequestType, ctrl.wValue, ctrl.wIndex, + tbuf, ctrl.wLength, tmo); + usb_lock_device(dev); + snoop_urb(dev, NULL, pipe, max(i, 0), min(i, 0), COMPLETE, NULL, 0); + } + if (i < 0 && i != -EPIPE) { + dev_printk(KERN_DEBUG, &dev->dev, "usbfs: USBDEVFS_CONTROL " + "failed cmd %s rqt %u rq %u len %u ret %d\n", + current->comm, ctrl.bRequestType, ctrl.bRequest, + ctrl.wLength, i); + } + ret = i; + done: + free_page((unsigned long) tbuf); + usbfs_decrease_memory_usage(PAGE_SIZE + sizeof(struct urb) + + sizeof(struct usb_ctrlrequest)); + return ret; +} + +static int proc_bulk(struct dev_state *ps, void __user *arg) +{ + struct usb_device *dev = ps->dev; + struct usbdevfs_bulktransfer bulk; + unsigned int tmo, len1, pipe; + int len2; + unsigned char *tbuf; + int i, ret; + + if (copy_from_user(&bulk, arg, sizeof(bulk))) + return -EFAULT; + ret = findintfep(ps->dev, bulk.ep); + if (ret < 0) + return ret; + ret = checkintf(ps, ret); + if (ret) + return ret; + if (bulk.ep & USB_DIR_IN) + pipe = usb_rcvbulkpipe(dev, bulk.ep & 0x7f); + else + pipe = usb_sndbulkpipe(dev, bulk.ep & 0x7f); + if (!usb_maxpacket(dev, pipe, !(bulk.ep & USB_DIR_IN))) + return -EINVAL; + len1 = bulk.len; + if (len1 >= USBFS_XFER_MAX) + return -EINVAL; + ret = usbfs_increase_memory_usage(len1 + sizeof(struct urb)); + if (ret) + return ret; + if (!(tbuf = kmalloc(len1, GFP_KERNEL))) { + ret = -ENOMEM; + goto done; + } + tmo = bulk.timeout; + if (bulk.ep & 0x80) { + if (len1 && !access_ok(VERIFY_WRITE, bulk.data, len1)) { + ret = -EINVAL; + goto done; + } + snoop_urb(dev, NULL, pipe, len1, tmo, SUBMIT, NULL, 0); + + usb_unlock_device(dev); + i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); + usb_lock_device(dev); + snoop_urb(dev, NULL, pipe, len2, i, COMPLETE, tbuf, len2); + + if (!i && len2) { + if (copy_to_user(bulk.data, tbuf, len2)) { + ret = -EFAULT; + goto done; + } + } + } else { + if (len1) { + if (copy_from_user(tbuf, bulk.data, len1)) { + ret = -EFAULT; + goto done; + } + } + snoop_urb(dev, NULL, pipe, len1, tmo, SUBMIT, tbuf, len1); + + usb_unlock_device(dev); + i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); + usb_lock_device(dev); + snoop_urb(dev, NULL, pipe, len2, i, COMPLETE, NULL, 0); + } + ret = (i < 0 ? i : len2); + done: + kfree(tbuf); + usbfs_decrease_memory_usage(len1 + sizeof(struct urb)); + return ret; +} + +static int proc_resetep(struct dev_state *ps, void __user *arg) +{ + unsigned int ep; + int ret; + + if (get_user(ep, (unsigned int __user *)arg)) + return -EFAULT; + ret = findintfep(ps->dev, ep); + if (ret < 0) + return ret; + ret = checkintf(ps, ret); + if (ret) + return ret; + usb_reset_endpoint(ps->dev, ep); + return 0; +} + +static int proc_clearhalt(struct dev_state *ps, void __user *arg) +{ + unsigned int ep; + int pipe; + int ret; + + if (get_user(ep, (unsigned int __user *)arg)) + return -EFAULT; + ret = findintfep(ps->dev, ep); + if (ret < 0) + return ret; + ret = checkintf(ps, ret); + if (ret) + return ret; + if (ep & USB_DIR_IN) + pipe = usb_rcvbulkpipe(ps->dev, ep & 0x7f); + else + pipe = usb_sndbulkpipe(ps->dev, ep & 0x7f); + + return usb_clear_halt(ps->dev, pipe); +} + +static int proc_getdriver(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_getdriver gd; + struct usb_interface *intf; + int ret; + + if (copy_from_user(&gd, arg, sizeof(gd))) + return -EFAULT; + intf = usb_ifnum_to_if(ps->dev, gd.interface); + if (!intf || !intf->dev.driver) + ret = -ENODATA; + else { + strlcpy(gd.driver, intf->dev.driver->name, + sizeof(gd.driver)); + ret = (copy_to_user(arg, &gd, sizeof(gd)) ? -EFAULT : 0); + } + return ret; +} + +static int proc_connectinfo(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_connectinfo ci = { + .devnum = ps->dev->devnum, + .slow = ps->dev->speed == USB_SPEED_LOW + }; + + if (copy_to_user(arg, &ci, sizeof(ci))) + return -EFAULT; + return 0; +} + +static int proc_resetdevice(struct dev_state *ps) +{ + return usb_reset_device(ps->dev); +} + +static int proc_setintf(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_setinterface setintf; + int ret; + + if (copy_from_user(&setintf, arg, sizeof(setintf))) + return -EFAULT; + if ((ret = checkintf(ps, setintf.interface))) + return ret; + return usb_set_interface(ps->dev, setintf.interface, + setintf.altsetting); +} + +static int proc_setconfig(struct dev_state *ps, void __user *arg) +{ + int u; + int status = 0; + struct usb_host_config *actconfig; + + if (get_user(u, (int __user *)arg)) + return -EFAULT; + + actconfig = ps->dev->actconfig; + + /* Don't touch the device if any interfaces are claimed. + * It could interfere with other drivers' operations, and if + * an interface is claimed by usbfs it could easily deadlock. + */ + if (actconfig) { + int i; + + for (i = 0; i < actconfig->desc.bNumInterfaces; ++i) { + if (usb_interface_claimed(actconfig->interface[i])) { + dev_warn(&ps->dev->dev, + "usbfs: interface %d claimed by %s " + "while '%s' sets config #%d\n", + actconfig->interface[i] + ->cur_altsetting + ->desc.bInterfaceNumber, + actconfig->interface[i] + ->dev.driver->name, + current->comm, u); + status = -EBUSY; + break; + } + } + } + + /* SET_CONFIGURATION is often abused as a "cheap" driver reset, + * so avoid usb_set_configuration()'s kick to sysfs + */ + if (status == 0) { + if (actconfig && actconfig->desc.bConfigurationValue == u) + status = usb_reset_configuration(ps->dev); + else + status = usb_set_configuration(ps->dev, u); + } + + return status; +} + +static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, + struct usbdevfs_iso_packet_desc __user *iso_frame_desc, + void __user *arg) +{ + struct usbdevfs_iso_packet_desc *isopkt = NULL; + struct usb_host_endpoint *ep; + struct async *as = NULL; + struct usb_ctrlrequest *dr = NULL; + unsigned int u, totlen, isofrmlen; + int i, ret, is_in, num_sgs = 0, ifnum = -1; + void *buf; + + if (uurb->flags & ~(USBDEVFS_URB_ISO_ASAP | + USBDEVFS_URB_SHORT_NOT_OK | + USBDEVFS_URB_BULK_CONTINUATION | + USBDEVFS_URB_NO_FSBR | + USBDEVFS_URB_ZERO_PACKET | + USBDEVFS_URB_NO_INTERRUPT)) + return -EINVAL; + if (uurb->buffer_length > 0 && !uurb->buffer) + return -EINVAL; + if (!(uurb->type == USBDEVFS_URB_TYPE_CONTROL && + (uurb->endpoint & ~USB_ENDPOINT_DIR_MASK) == 0)) { + ifnum = findintfep(ps->dev, uurb->endpoint); + if (ifnum < 0) + return ifnum; + ret = checkintf(ps, ifnum); + if (ret) + return ret; + } + if ((uurb->endpoint & USB_ENDPOINT_DIR_MASK) != 0) { + is_in = 1; + ep = ps->dev->ep_in[uurb->endpoint & USB_ENDPOINT_NUMBER_MASK]; + } else { + is_in = 0; + ep = ps->dev->ep_out[uurb->endpoint & USB_ENDPOINT_NUMBER_MASK]; + } + if (!ep) + return -ENOENT; + + u = 0; + switch(uurb->type) { + case USBDEVFS_URB_TYPE_CONTROL: + if (!usb_endpoint_xfer_control(&ep->desc)) + return -EINVAL; + /* min 8 byte setup packet */ + if (uurb->buffer_length < 8) + return -EINVAL; + dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!dr) + return -ENOMEM; + if (copy_from_user(dr, uurb->buffer, 8)) { + ret = -EFAULT; + goto error; + } + if (uurb->buffer_length < (le16_to_cpup(&dr->wLength) + 8)) { + ret = -EINVAL; + goto error; + } + ret = check_ctrlrecip(ps, dr->bRequestType, dr->bRequest, + le16_to_cpup(&dr->wIndex)); + if (ret) + goto error; + uurb->number_of_packets = 0; + uurb->buffer_length = le16_to_cpup(&dr->wLength); + uurb->buffer += 8; + if ((dr->bRequestType & USB_DIR_IN) && uurb->buffer_length) { + is_in = 1; + uurb->endpoint |= USB_DIR_IN; + } else { + is_in = 0; + uurb->endpoint &= ~USB_DIR_IN; + } + snoop(&ps->dev->dev, "control urb: bRequestType=%02x " + "bRequest=%02x wValue=%04x " + "wIndex=%04x wLength=%04x\n", + dr->bRequestType, dr->bRequest, + __le16_to_cpup(&dr->wValue), + __le16_to_cpup(&dr->wIndex), + __le16_to_cpup(&dr->wLength)); + u = sizeof(struct usb_ctrlrequest); + break; + + case USBDEVFS_URB_TYPE_BULK: + switch (usb_endpoint_type(&ep->desc)) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_ISOC: + return -EINVAL; + case USB_ENDPOINT_XFER_INT: + /* allow single-shot interrupt transfers */ + uurb->type = USBDEVFS_URB_TYPE_INTERRUPT; + goto interrupt_urb; + } + uurb->number_of_packets = 0; + num_sgs = DIV_ROUND_UP(uurb->buffer_length, USB_SG_SIZE); + if (num_sgs == 1 || num_sgs > ps->dev->bus->sg_tablesize) + num_sgs = 0; + break; + + case USBDEVFS_URB_TYPE_INTERRUPT: + if (!usb_endpoint_xfer_int(&ep->desc)) + return -EINVAL; + interrupt_urb: + uurb->number_of_packets = 0; + break; + + case USBDEVFS_URB_TYPE_ISO: + /* arbitrary limit */ + if (uurb->number_of_packets < 1 || + uurb->number_of_packets > 128) + return -EINVAL; + if (!usb_endpoint_xfer_isoc(&ep->desc)) + return -EINVAL; + isofrmlen = sizeof(struct usbdevfs_iso_packet_desc) * + uurb->number_of_packets; + if (!(isopkt = kmalloc(isofrmlen, GFP_KERNEL))) + return -ENOMEM; + if (copy_from_user(isopkt, iso_frame_desc, isofrmlen)) { + ret = -EFAULT; + goto error; + } + for (totlen = u = 0; u < uurb->number_of_packets; u++) { + /* + * arbitrary limit need for USB 3.0 + * bMaxBurst (0~15 allowed, 1~16 packets) + * bmAttributes (bit 1:0, mult 0~2, 1~3 packets) + * sizemax: 1024 * 16 * 3 = 49152 + */ + if (isopkt[u].length > 49152) { + ret = -EINVAL; + goto error; + } + totlen += isopkt[u].length; + } + u *= sizeof(struct usb_iso_packet_descriptor); + uurb->buffer_length = totlen; + break; + + default: + return -EINVAL; + } + + if (uurb->buffer_length >= USBFS_XFER_MAX) { + ret = -EINVAL; + goto error; + } + if (uurb->buffer_length > 0 && + !access_ok(is_in ? VERIFY_WRITE : VERIFY_READ, + uurb->buffer, uurb->buffer_length)) { + ret = -EFAULT; + goto error; + } + as = alloc_async(uurb->number_of_packets); + if (!as) { + ret = -ENOMEM; + goto error; + } + + u += sizeof(struct async) + sizeof(struct urb) + uurb->buffer_length + + num_sgs * sizeof(struct scatterlist); + ret = usbfs_increase_memory_usage(u); + if (ret) + goto error; + as->mem_usage = u; + + if (num_sgs) { + as->urb->sg = kmalloc(num_sgs * sizeof(struct scatterlist), + GFP_KERNEL); + if (!as->urb->sg) { + ret = -ENOMEM; + goto error; + } + as->urb->num_sgs = num_sgs; + sg_init_table(as->urb->sg, as->urb->num_sgs); + + totlen = uurb->buffer_length; + for (i = 0; i < as->urb->num_sgs; i++) { + u = (totlen > USB_SG_SIZE) ? USB_SG_SIZE : totlen; + buf = kmalloc(u, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto error; + } + sg_set_buf(&as->urb->sg[i], buf, u); + + if (!is_in) { + if (copy_from_user(buf, uurb->buffer, u)) { + ret = -EFAULT; + goto error; + } + uurb->buffer += u; + } + totlen -= u; + } + } else if (uurb->buffer_length > 0) { + as->urb->transfer_buffer = kmalloc(uurb->buffer_length, + GFP_KERNEL); + if (!as->urb->transfer_buffer) { + ret = -ENOMEM; + goto error; + } + + if (!is_in) { + if (copy_from_user(as->urb->transfer_buffer, + uurb->buffer, + uurb->buffer_length)) { + ret = -EFAULT; + goto error; + } + } else if (uurb->type == USBDEVFS_URB_TYPE_ISO) { + /* + * Isochronous input data may end up being + * discontiguous if some of the packets are short. + * Clear the buffer so that the gaps don't leak + * kernel data to userspace. + */ + memset(as->urb->transfer_buffer, 0, + uurb->buffer_length); + } + } + as->urb->dev = ps->dev; + as->urb->pipe = (uurb->type << 30) | + __create_pipe(ps->dev, uurb->endpoint & 0xf) | + (uurb->endpoint & USB_DIR_IN); + + /* This tedious sequence is necessary because the URB_* flags + * are internal to the kernel and subject to change, whereas + * the USBDEVFS_URB_* flags are a user API and must not be changed. + */ + u = (is_in ? URB_DIR_IN : URB_DIR_OUT); + if (uurb->flags & USBDEVFS_URB_ISO_ASAP) + u |= URB_ISO_ASAP; + if (uurb->flags & USBDEVFS_URB_SHORT_NOT_OK) + u |= URB_SHORT_NOT_OK; + if (uurb->flags & USBDEVFS_URB_NO_FSBR) + u |= URB_NO_FSBR; + if (uurb->flags & USBDEVFS_URB_ZERO_PACKET) + u |= URB_ZERO_PACKET; + if (uurb->flags & USBDEVFS_URB_NO_INTERRUPT) + u |= URB_NO_INTERRUPT; + as->urb->transfer_flags = u; + + as->urb->transfer_buffer_length = uurb->buffer_length; + as->urb->setup_packet = (unsigned char *)dr; + dr = NULL; + as->urb->start_frame = uurb->start_frame; + as->urb->number_of_packets = uurb->number_of_packets; + if (uurb->type == USBDEVFS_URB_TYPE_ISO || + ps->dev->speed == USB_SPEED_HIGH) + as->urb->interval = 1 << min(15, ep->desc.bInterval - 1); + else + as->urb->interval = ep->desc.bInterval; + as->urb->context = as; + as->urb->complete = async_completed; + for (totlen = u = 0; u < uurb->number_of_packets; u++) { + as->urb->iso_frame_desc[u].offset = totlen; + as->urb->iso_frame_desc[u].length = isopkt[u].length; + totlen += isopkt[u].length; + } + kfree(isopkt); + isopkt = NULL; + as->ps = ps; + as->userurb = arg; + if (is_in && uurb->buffer_length > 0) + as->userbuffer = uurb->buffer; + else + as->userbuffer = NULL; + as->signr = uurb->signr; + as->ifnum = ifnum; + as->pid = get_pid(task_pid(current)); + as->cred = get_current_cred(); + security_task_getsecid(current, &as->secid); + snoop_urb(ps->dev, as->userurb, as->urb->pipe, + as->urb->transfer_buffer_length, 0, SUBMIT, + NULL, 0); + if (!is_in) + snoop_urb_data(as->urb, as->urb->transfer_buffer_length); + + async_newpending(as); + + if (usb_endpoint_xfer_bulk(&ep->desc)) { + spin_lock_irq(&ps->lock); + + /* Not exactly the endpoint address; the direction bit is + * shifted to the 0x10 position so that the value will be + * between 0 and 31. + */ + as->bulk_addr = usb_endpoint_num(&ep->desc) | + ((ep->desc.bEndpointAddress & USB_ENDPOINT_DIR_MASK) + >> 3); + + /* If this bulk URB is the start of a new transfer, re-enable + * the endpoint. Otherwise mark it as a continuation URB. + */ + if (uurb->flags & USBDEVFS_URB_BULK_CONTINUATION) + as->bulk_status = AS_CONTINUATION; + else + ps->disabled_bulk_eps &= ~(1 << as->bulk_addr); + + /* Don't accept continuation URBs if the endpoint is + * disabled because of an earlier error. + */ + if (ps->disabled_bulk_eps & (1 << as->bulk_addr)) + ret = -EREMOTEIO; + else + ret = usb_submit_urb(as->urb, GFP_ATOMIC); + spin_unlock_irq(&ps->lock); + } else { + ret = usb_submit_urb(as->urb, GFP_KERNEL); + } + + if (ret) { + dev_printk(KERN_DEBUG, &ps->dev->dev, + "usbfs: usb_submit_urb returned %d\n", ret); + snoop_urb(ps->dev, as->userurb, as->urb->pipe, + 0, ret, COMPLETE, NULL, 0); + async_removepending(as); + goto error; + } + return 0; + + error: + kfree(isopkt); + kfree(dr); + if (as) + free_async(as); + return ret; +} + +static int proc_submiturb(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_urb uurb; + + if (copy_from_user(&uurb, arg, sizeof(uurb))) + return -EFAULT; + + return proc_do_submiturb(ps, &uurb, + (((struct usbdevfs_urb __user *)arg)->iso_frame_desc), + arg); +} + +static int proc_unlinkurb(struct dev_state *ps, void __user *arg) +{ + struct urb *urb; + struct async *as; + unsigned long flags; + + spin_lock_irqsave(&ps->lock, flags); + as = async_getpending(ps, arg); + if (!as) { + spin_unlock_irqrestore(&ps->lock, flags); + return -EINVAL; + } + + urb = as->urb; + usb_get_urb(urb); + spin_unlock_irqrestore(&ps->lock, flags); + + usb_kill_urb(urb); + usb_put_urb(urb); + + return 0; +} + +static int processcompl(struct async *as, void __user * __user *arg) +{ + struct urb *urb = as->urb; + struct usbdevfs_urb __user *userurb = as->userurb; + void __user *addr = as->userurb; + unsigned int i; + + if (as->userbuffer && urb->actual_length) { + if (copy_urb_data_to_user(as->userbuffer, urb)) + goto err_out; + } + if (put_user(as->status, &userurb->status)) + goto err_out; + if (put_user(urb->actual_length, &userurb->actual_length)) + goto err_out; + if (put_user(urb->error_count, &userurb->error_count)) + goto err_out; + + if (usb_endpoint_xfer_isoc(&urb->ep->desc)) { + for (i = 0; i < urb->number_of_packets; i++) { + if (put_user(urb->iso_frame_desc[i].actual_length, + &userurb->iso_frame_desc[i].actual_length)) + goto err_out; + if (put_user(urb->iso_frame_desc[i].status, + &userurb->iso_frame_desc[i].status)) + goto err_out; + } + } + + if (put_user(addr, (void __user * __user *)arg)) + return -EFAULT; + return 0; + +err_out: + return -EFAULT; +} + +static struct async *reap_as(struct dev_state *ps) +{ + DECLARE_WAITQUEUE(wait, current); + struct async *as = NULL; + struct usb_device *dev = ps->dev; + + add_wait_queue(&ps->wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + as = async_getcompleted(ps); + if (as) + break; + if (signal_pending(current)) + break; + usb_unlock_device(dev); + schedule(); + usb_lock_device(dev); + } + remove_wait_queue(&ps->wait, &wait); + set_current_state(TASK_RUNNING); + return as; +} + +static int proc_reapurb(struct dev_state *ps, void __user *arg) +{ + struct async *as = reap_as(ps); + if (as) { + int retval = processcompl(as, (void __user * __user *)arg); + free_async(as); + return retval; + } + if (signal_pending(current)) + return -EINTR; + return -EIO; +} + +static int proc_reapurbnonblock(struct dev_state *ps, void __user *arg) +{ + int retval; + struct async *as; + + as = async_getcompleted(ps); + retval = -EAGAIN; + if (as) { + retval = processcompl(as, (void __user * __user *)arg); + free_async(as); + } + return retval; +} + +#ifdef CONFIG_COMPAT +static int proc_control_compat(struct dev_state *ps, + struct usbdevfs_ctrltransfer32 __user *p32) +{ + struct usbdevfs_ctrltransfer __user *p; + __u32 udata; + p = compat_alloc_user_space(sizeof(*p)); + if (copy_in_user(p, p32, (sizeof(*p32) - sizeof(compat_caddr_t))) || + get_user(udata, &p32->data) || + put_user(compat_ptr(udata), &p->data)) + return -EFAULT; + return proc_control(ps, p); +} + +static int proc_bulk_compat(struct dev_state *ps, + struct usbdevfs_bulktransfer32 __user *p32) +{ + struct usbdevfs_bulktransfer __user *p; + compat_uint_t n; + compat_caddr_t addr; + + p = compat_alloc_user_space(sizeof(*p)); + + if (get_user(n, &p32->ep) || put_user(n, &p->ep) || + get_user(n, &p32->len) || put_user(n, &p->len) || + get_user(n, &p32->timeout) || put_user(n, &p->timeout) || + get_user(addr, &p32->data) || put_user(compat_ptr(addr), &p->data)) + return -EFAULT; + + return proc_bulk(ps, p); +} +static int proc_disconnectsignal_compat(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_disconnectsignal32 ds; + + if (copy_from_user(&ds, arg, sizeof(ds))) + return -EFAULT; + ps->discsignr = ds.signr; + ps->disccontext = compat_ptr(ds.context); + return 0; +} + +static int get_urb32(struct usbdevfs_urb *kurb, + struct usbdevfs_urb32 __user *uurb) +{ + __u32 uptr; + if (!access_ok(VERIFY_READ, uurb, sizeof(*uurb)) || + __get_user(kurb->type, &uurb->type) || + __get_user(kurb->endpoint, &uurb->endpoint) || + __get_user(kurb->status, &uurb->status) || + __get_user(kurb->flags, &uurb->flags) || + __get_user(kurb->buffer_length, &uurb->buffer_length) || + __get_user(kurb->actual_length, &uurb->actual_length) || + __get_user(kurb->start_frame, &uurb->start_frame) || + __get_user(kurb->number_of_packets, &uurb->number_of_packets) || + __get_user(kurb->error_count, &uurb->error_count) || + __get_user(kurb->signr, &uurb->signr)) + return -EFAULT; + + if (__get_user(uptr, &uurb->buffer)) + return -EFAULT; + kurb->buffer = compat_ptr(uptr); + if (__get_user(uptr, &uurb->usercontext)) + return -EFAULT; + kurb->usercontext = compat_ptr(uptr); + + return 0; +} + +static int proc_submiturb_compat(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_urb uurb; + + if (get_urb32(&uurb, (struct usbdevfs_urb32 __user *)arg)) + return -EFAULT; + + return proc_do_submiturb(ps, &uurb, + ((struct usbdevfs_urb32 __user *)arg)->iso_frame_desc, + arg); +} + +static int processcompl_compat(struct async *as, void __user * __user *arg) +{ + struct urb *urb = as->urb; + struct usbdevfs_urb32 __user *userurb = as->userurb; + void __user *addr = as->userurb; + unsigned int i; + + if (as->userbuffer && urb->actual_length) { + if (copy_urb_data_to_user(as->userbuffer, urb)) + return -EFAULT; + } + if (put_user(as->status, &userurb->status)) + return -EFAULT; + if (put_user(urb->actual_length, &userurb->actual_length)) + return -EFAULT; + if (put_user(urb->error_count, &userurb->error_count)) + return -EFAULT; + + if (usb_endpoint_xfer_isoc(&urb->ep->desc)) { + for (i = 0; i < urb->number_of_packets; i++) { + if (put_user(urb->iso_frame_desc[i].actual_length, + &userurb->iso_frame_desc[i].actual_length)) + return -EFAULT; + if (put_user(urb->iso_frame_desc[i].status, + &userurb->iso_frame_desc[i].status)) + return -EFAULT; + } + } + + if (put_user(ptr_to_compat(addr), (u32 __user *)arg)) + return -EFAULT; + return 0; +} + +static int proc_reapurb_compat(struct dev_state *ps, void __user *arg) +{ + struct async *as = reap_as(ps); + if (as) { + int retval = processcompl_compat(as, (void __user * __user *)arg); + free_async(as); + return retval; + } + if (signal_pending(current)) + return -EINTR; + return -EIO; +} + +static int proc_reapurbnonblock_compat(struct dev_state *ps, void __user *arg) +{ + int retval; + struct async *as; + + retval = -EAGAIN; + as = async_getcompleted(ps); + if (as) { + retval = processcompl_compat(as, (void __user * __user *)arg); + free_async(as); + } + return retval; +} + + +#endif + +static int proc_disconnectsignal(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_disconnectsignal ds; + + if (copy_from_user(&ds, arg, sizeof(ds))) + return -EFAULT; + ps->discsignr = ds.signr; + ps->disccontext = ds.context; + return 0; +} + +static int proc_claiminterface(struct dev_state *ps, void __user *arg) +{ + unsigned int ifnum; + + if (get_user(ifnum, (unsigned int __user *)arg)) + return -EFAULT; + return claimintf(ps, ifnum); +} + +static int proc_releaseinterface(struct dev_state *ps, void __user *arg) +{ + unsigned int ifnum; + int ret; + + if (get_user(ifnum, (unsigned int __user *)arg)) + return -EFAULT; + if ((ret = releaseintf(ps, ifnum)) < 0) + return ret; + destroy_async_on_interface (ps, ifnum); + return 0; +} + +static int proc_ioctl(struct dev_state *ps, struct usbdevfs_ioctl *ctl) +{ + int size; + void *buf = NULL; + int retval = 0; + struct usb_interface *intf = NULL; + struct usb_driver *driver = NULL; + + /* alloc buffer */ + if ((size = _IOC_SIZE(ctl->ioctl_code)) > 0) { + if ((buf = kmalloc(size, GFP_KERNEL)) == NULL) + return -ENOMEM; + if ((_IOC_DIR(ctl->ioctl_code) & _IOC_WRITE)) { + if (copy_from_user(buf, ctl->data, size)) { + kfree(buf); + return -EFAULT; + } + } else { + memset(buf, 0, size); + } + } + + if (!connected(ps)) { + kfree(buf); + return -ENODEV; + } + + if (ps->dev->state != USB_STATE_CONFIGURED) + retval = -EHOSTUNREACH; + else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno))) + retval = -EINVAL; + else switch (ctl->ioctl_code) { + + /* disconnect kernel driver from interface */ + case USBDEVFS_DISCONNECT: + if (intf->dev.driver) { + driver = to_usb_driver(intf->dev.driver); + dev_dbg(&intf->dev, "disconnect by usbfs\n"); + usb_driver_release_interface(driver, intf); + } else + retval = -ENODATA; + break; + + /* let kernel drivers try to (re)bind to the interface */ + case USBDEVFS_CONNECT: + if (!intf->dev.driver) + retval = device_attach(&intf->dev); + else + retval = -EBUSY; + break; + + /* talk directly to the interface's driver */ + default: + if (intf->dev.driver) + driver = to_usb_driver(intf->dev.driver); + if (driver == NULL || driver->unlocked_ioctl == NULL) { + retval = -ENOTTY; + } else { + retval = driver->unlocked_ioctl(intf, ctl->ioctl_code, buf); + if (retval == -ENOIOCTLCMD) + retval = -ENOTTY; + } + } + + /* cleanup and return */ + if (retval >= 0 + && (_IOC_DIR(ctl->ioctl_code) & _IOC_READ) != 0 + && size > 0 + && copy_to_user(ctl->data, buf, size) != 0) + retval = -EFAULT; + + kfree(buf); + return retval; +} + +static int proc_ioctl_default(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_ioctl ctrl; + + if (copy_from_user(&ctrl, arg, sizeof(ctrl))) + return -EFAULT; + return proc_ioctl(ps, &ctrl); +} + +#ifdef CONFIG_COMPAT +static int proc_ioctl_compat(struct dev_state *ps, compat_uptr_t arg) +{ + struct usbdevfs_ioctl32 __user *uioc; + struct usbdevfs_ioctl ctrl; + u32 udata; + + uioc = compat_ptr((long)arg); + if (!access_ok(VERIFY_READ, uioc, sizeof(*uioc)) || + __get_user(ctrl.ifno, &uioc->ifno) || + __get_user(ctrl.ioctl_code, &uioc->ioctl_code) || + __get_user(udata, &uioc->data)) + return -EFAULT; + ctrl.data = compat_ptr(udata); + + return proc_ioctl(ps, &ctrl); +} +#endif + +static int proc_claim_port(struct dev_state *ps, void __user *arg) +{ + unsigned portnum; + int rc; + + if (get_user(portnum, (unsigned __user *) arg)) + return -EFAULT; + rc = usb_hub_claim_port(ps->dev, portnum, ps); + if (rc == 0) + snoop(&ps->dev->dev, "port %d claimed by process %d: %s\n", + portnum, task_pid_nr(current), current->comm); + return rc; +} + +static int proc_release_port(struct dev_state *ps, void __user *arg) +{ + unsigned portnum; + + if (get_user(portnum, (unsigned __user *) arg)) + return -EFAULT; + return usb_hub_release_port(ps->dev, portnum, ps); +} + +static int proc_get_capabilities(struct dev_state *ps, void __user *arg) +{ + __u32 caps; + + caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM; + if (!ps->dev->bus->no_stop_on_short) + caps |= USBDEVFS_CAP_BULK_CONTINUATION; + if (ps->dev->bus->sg_tablesize) + caps |= USBDEVFS_CAP_BULK_SCATTER_GATHER; + + if (put_user(caps, (__u32 __user *)arg)) + return -EFAULT; + + return 0; +} + +static int proc_disconnect_claim(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_disconnect_claim dc; + struct usb_interface *intf; + + if (copy_from_user(&dc, arg, sizeof(dc))) + return -EFAULT; + + intf = usb_ifnum_to_if(ps->dev, dc.interface); + if (!intf) + return -EINVAL; + + if (intf->dev.driver) { + struct usb_driver *driver = to_usb_driver(intf->dev.driver); + + if ((dc.flags & USBDEVFS_DISCONNECT_CLAIM_IF_DRIVER) && + strncmp(dc.driver, intf->dev.driver->name, + sizeof(dc.driver)) != 0) + return -EBUSY; + + if ((dc.flags & USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER) && + strncmp(dc.driver, intf->dev.driver->name, + sizeof(dc.driver)) == 0) + return -EBUSY; + + dev_dbg(&intf->dev, "disconnect by usbfs\n"); + usb_driver_release_interface(driver, intf); + } + + return claimintf(ps, dc.interface); +} + +/* + * NOTE: All requests here that have interface numbers as parameters + * are assuming that somehow the configuration has been prevented from + * changing. But there's no mechanism to ensure that... + */ +static long usbdev_do_ioctl(struct file *file, unsigned int cmd, + void __user *p) +{ + struct dev_state *ps = file->private_data; + struct inode *inode = file_inode(file); + struct usb_device *dev = ps->dev; + int ret = -ENOTTY; + + if (!(file->f_mode & FMODE_WRITE)) + return -EPERM; + + usb_lock_device(dev); + if (!connected(ps)) { + usb_unlock_device(dev); + return -ENODEV; + } + + switch (cmd) { + case USBDEVFS_CONTROL: + snoop(&dev->dev, "%s: CONTROL\n", __func__); + ret = proc_control(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_BULK: + snoop(&dev->dev, "%s: BULK\n", __func__); + ret = proc_bulk(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_RESETEP: + snoop(&dev->dev, "%s: RESETEP\n", __func__); + ret = proc_resetep(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_RESET: + snoop(&dev->dev, "%s: RESET\n", __func__); + ret = proc_resetdevice(ps); + break; + + case USBDEVFS_CLEAR_HALT: + snoop(&dev->dev, "%s: CLEAR_HALT\n", __func__); + ret = proc_clearhalt(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_GETDRIVER: + snoop(&dev->dev, "%s: GETDRIVER\n", __func__); + ret = proc_getdriver(ps, p); + break; + + case USBDEVFS_CONNECTINFO: + snoop(&dev->dev, "%s: CONNECTINFO\n", __func__); + ret = proc_connectinfo(ps, p); + break; + + case USBDEVFS_SETINTERFACE: + snoop(&dev->dev, "%s: SETINTERFACE\n", __func__); + ret = proc_setintf(ps, p); + break; + + case USBDEVFS_SETCONFIGURATION: + snoop(&dev->dev, "%s: SETCONFIGURATION\n", __func__); + ret = proc_setconfig(ps, p); + break; + + case USBDEVFS_SUBMITURB: + snoop(&dev->dev, "%s: SUBMITURB\n", __func__); + ret = proc_submiturb(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + +#ifdef CONFIG_COMPAT + case USBDEVFS_CONTROL32: + snoop(&dev->dev, "%s: CONTROL32\n", __func__); + ret = proc_control_compat(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_BULK32: + snoop(&dev->dev, "%s: BULK32\n", __func__); + ret = proc_bulk_compat(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_DISCSIGNAL32: + snoop(&dev->dev, "%s: DISCSIGNAL32\n", __func__); + ret = proc_disconnectsignal_compat(ps, p); + break; + + case USBDEVFS_SUBMITURB32: + snoop(&dev->dev, "%s: SUBMITURB32\n", __func__); + ret = proc_submiturb_compat(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_REAPURB32: + snoop(&dev->dev, "%s: REAPURB32\n", __func__); + ret = proc_reapurb_compat(ps, p); + break; + + case USBDEVFS_REAPURBNDELAY32: + snoop(&dev->dev, "%s: REAPURBNDELAY32\n", __func__); + ret = proc_reapurbnonblock_compat(ps, p); + break; + + case USBDEVFS_IOCTL32: + snoop(&dev->dev, "%s: IOCTL32\n", __func__); + ret = proc_ioctl_compat(ps, ptr_to_compat(p)); + break; +#endif + + case USBDEVFS_DISCARDURB: + snoop(&dev->dev, "%s: DISCARDURB\n", __func__); + ret = proc_unlinkurb(ps, p); + break; + + case USBDEVFS_REAPURB: + snoop(&dev->dev, "%s: REAPURB\n", __func__); + ret = proc_reapurb(ps, p); + break; + + case USBDEVFS_REAPURBNDELAY: + snoop(&dev->dev, "%s: REAPURBNDELAY\n", __func__); + ret = proc_reapurbnonblock(ps, p); + break; + + case USBDEVFS_DISCSIGNAL: + snoop(&dev->dev, "%s: DISCSIGNAL\n", __func__); + ret = proc_disconnectsignal(ps, p); + break; + + case USBDEVFS_CLAIMINTERFACE: + snoop(&dev->dev, "%s: CLAIMINTERFACE\n", __func__); + ret = proc_claiminterface(ps, p); + break; + + case USBDEVFS_RELEASEINTERFACE: + snoop(&dev->dev, "%s: RELEASEINTERFACE\n", __func__); + ret = proc_releaseinterface(ps, p); + break; + + case USBDEVFS_IOCTL: + snoop(&dev->dev, "%s: IOCTL\n", __func__); + ret = proc_ioctl_default(ps, p); + break; + + case USBDEVFS_CLAIM_PORT: + snoop(&dev->dev, "%s: CLAIM_PORT\n", __func__); + ret = proc_claim_port(ps, p); + break; + + case USBDEVFS_RELEASE_PORT: + snoop(&dev->dev, "%s: RELEASE_PORT\n", __func__); + ret = proc_release_port(ps, p); + break; + case USBDEVFS_GET_CAPABILITIES: + ret = proc_get_capabilities(ps, p); + break; + case USBDEVFS_DISCONNECT_CLAIM: + ret = proc_disconnect_claim(ps, p); + break; + } + usb_unlock_device(dev); + if (ret >= 0) + inode->i_atime = CURRENT_TIME; + return ret; +} + +static long usbdev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + + ret = usbdev_do_ioctl(file, cmd, (void __user *)arg); + + return ret; +} + +#ifdef CONFIG_COMPAT +static long usbdev_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + + ret = usbdev_do_ioctl(file, cmd, compat_ptr(arg)); + + return ret; +} +#endif + +/* No kernel lock - fine */ +static unsigned int usbdev_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct dev_state *ps = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &ps->wait, wait); + if (file->f_mode & FMODE_WRITE && !list_empty(&ps->async_completed)) + mask |= POLLOUT | POLLWRNORM; + if (!connected(ps)) + mask |= POLLERR | POLLHUP; + return mask; +} + +const struct file_operations usbdev_file_operations = { + .owner = THIS_MODULE, + .llseek = usbdev_lseek, + .read = usbdev_read, + .poll = usbdev_poll, + .unlocked_ioctl = usbdev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = usbdev_compat_ioctl, +#endif + .open = usbdev_open, + .release = usbdev_release, +}; + +static void usbdev_remove(struct usb_device *udev) +{ + struct dev_state *ps; + struct siginfo sinfo; + + while (!list_empty(&udev->filelist)) { + ps = list_entry(udev->filelist.next, struct dev_state, list); + destroy_all_async(ps); + wake_up_all(&ps->wait); + list_del_init(&ps->list); + if (ps->discsignr) { + sinfo.si_signo = ps->discsignr; + sinfo.si_errno = EPIPE; + sinfo.si_code = SI_ASYNCIO; + sinfo.si_addr = ps->disccontext; + kill_pid_info_as_cred(ps->discsignr, &sinfo, + ps->disc_pid, ps->cred, ps->secid); + } + } +} + +static int usbdev_notify(struct notifier_block *self, + unsigned long action, void *dev) +{ + switch (action) { + case USB_DEVICE_ADD: + break; + case USB_DEVICE_REMOVE: + usbdev_remove(dev); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block usbdev_nb = { + .notifier_call = usbdev_notify, +}; + +static struct cdev usb_device_cdev; + +int __init usb_devio_init(void) +{ + int retval; + + retval = register_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX, + "usb_device"); + if (retval) { + printk(KERN_ERR "Unable to register minors for usb_device\n"); + goto out; + } + cdev_init(&usb_device_cdev, &usbdev_file_operations); + retval = cdev_add(&usb_device_cdev, USB_DEVICE_DEV, USB_DEVICE_MAX); + if (retval) { + printk(KERN_ERR "Unable to get usb_device major %d\n", + USB_DEVICE_MAJOR); + goto error_cdev; + } + usb_register_notify(&usbdev_nb); +out: + return retval; + +error_cdev: + unregister_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX); + goto out; +} + +void usb_devio_cleanup(void) +{ + usb_unregister_notify(&usbdev_nb); + cdev_del(&usb_device_cdev); + unregister_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX); +} diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c new file mode 100644 index 00000000..2cdd5079 --- /dev/null +++ b/drivers/usb/core/driver.c @@ -0,0 +1,1812 @@ +/* + * drivers/usb/driver.c - most of the driver model stuff for usb + * + * (C) Copyright 2005 Greg Kroah-Hartman + * + * based on drivers/usb/usb.c which had the following copyrights: + * (C) Copyright Linus Torvalds 1999 + * (C) Copyright Johannes Erdfelt 1999-2001 + * (C) Copyright Andreas Gal 1999 + * (C) Copyright Gregory P. Smith 1999 + * (C) Copyright Deti Fliegl 1999 (new USB architecture) + * (C) Copyright Randy Dunlap 2000 + * (C) Copyright David Brownell 2000-2004 + * (C) Copyright Yggdrasil Computing, Inc. 2000 + * (usb_device_id matching changes by Adam J. Richter) + * (C) Copyright Greg Kroah-Hartman 2002-2003 + * + * NOTE! This is not actually a driver at all, rather this is + * just a collection of helper routines that implement the + * matching, probing, releasing, suspending and resuming for + * real drivers. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "usb.h" + + +/* + * Adds a new dynamic USBdevice ID to this driver, + * and cause the driver to probe for all devices again. + */ +ssize_t usb_store_new_id(struct usb_dynids *dynids, + struct device_driver *driver, + const char *buf, size_t count) +{ + struct usb_dynid *dynid; + u32 idVendor = 0; + u32 idProduct = 0; + unsigned int bInterfaceClass = 0; + int fields = 0; + int retval = 0; + + fields = sscanf(buf, "%x %x %x", &idVendor, &idProduct, + &bInterfaceClass); + if (fields < 2) + return -EINVAL; + + dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); + if (!dynid) + return -ENOMEM; + + INIT_LIST_HEAD(&dynid->node); + dynid->id.idVendor = idVendor; + dynid->id.idProduct = idProduct; + dynid->id.match_flags = USB_DEVICE_ID_MATCH_DEVICE; + if (fields == 3) { + dynid->id.bInterfaceClass = (u8)bInterfaceClass; + dynid->id.match_flags |= USB_DEVICE_ID_MATCH_INT_CLASS; + } + + spin_lock(&dynids->lock); + list_add_tail(&dynid->node, &dynids->list); + spin_unlock(&dynids->lock); + + retval = driver_attach(driver); + + if (retval) + return retval; + return count; +} +EXPORT_SYMBOL_GPL(usb_store_new_id); + +ssize_t usb_show_dynids(struct usb_dynids *dynids, char *buf) +{ + struct usb_dynid *dynid; + size_t count = 0; + + list_for_each_entry(dynid, &dynids->list, node) + if (dynid->id.bInterfaceClass != 0) + count += scnprintf(&buf[count], PAGE_SIZE - count, "%04x %04x %02x\n", + dynid->id.idVendor, dynid->id.idProduct, + dynid->id.bInterfaceClass); + else + count += scnprintf(&buf[count], PAGE_SIZE - count, "%04x %04x\n", + dynid->id.idVendor, dynid->id.idProduct); + return count; +} +EXPORT_SYMBOL_GPL(usb_show_dynids); + +static ssize_t show_dynids(struct device_driver *driver, char *buf) +{ + struct usb_driver *usb_drv = to_usb_driver(driver); + + return usb_show_dynids(&usb_drv->dynids, buf); +} + +static ssize_t store_new_id(struct device_driver *driver, + const char *buf, size_t count) +{ + struct usb_driver *usb_drv = to_usb_driver(driver); + + return usb_store_new_id(&usb_drv->dynids, driver, buf, count); +} +static DRIVER_ATTR(new_id, S_IRUGO | S_IWUSR, show_dynids, store_new_id); + +/** + * store_remove_id - remove a USB device ID from this driver + * @driver: target device driver + * @buf: buffer for scanning device ID data + * @count: input size + * + * Removes a dynamic usb device ID from this driver. + */ +static ssize_t +store_remove_id(struct device_driver *driver, const char *buf, size_t count) +{ + struct usb_dynid *dynid, *n; + struct usb_driver *usb_driver = to_usb_driver(driver); + u32 idVendor; + u32 idProduct; + int fields; + + fields = sscanf(buf, "%x %x", &idVendor, &idProduct); + if (fields < 2) + return -EINVAL; + + spin_lock(&usb_driver->dynids.lock); + list_for_each_entry_safe(dynid, n, &usb_driver->dynids.list, node) { + struct usb_device_id *id = &dynid->id; + if ((id->idVendor == idVendor) && + (id->idProduct == idProduct)) { + list_del(&dynid->node); + kfree(dynid); + break; + } + } + spin_unlock(&usb_driver->dynids.lock); + return count; +} +static DRIVER_ATTR(remove_id, S_IRUGO | S_IWUSR, show_dynids, store_remove_id); + +static int usb_create_newid_files(struct usb_driver *usb_drv) +{ + int error = 0; + + if (usb_drv->no_dynamic_id) + goto exit; + + if (usb_drv->probe != NULL) { + error = driver_create_file(&usb_drv->drvwrap.driver, + &driver_attr_new_id); + if (error == 0) { + error = driver_create_file(&usb_drv->drvwrap.driver, + &driver_attr_remove_id); + if (error) + driver_remove_file(&usb_drv->drvwrap.driver, + &driver_attr_new_id); + } + } +exit: + return error; +} + +static void usb_remove_newid_files(struct usb_driver *usb_drv) +{ + if (usb_drv->no_dynamic_id) + return; + + if (usb_drv->probe != NULL) { + driver_remove_file(&usb_drv->drvwrap.driver, + &driver_attr_remove_id); + driver_remove_file(&usb_drv->drvwrap.driver, + &driver_attr_new_id); + } +} + +static void usb_free_dynids(struct usb_driver *usb_drv) +{ + struct usb_dynid *dynid, *n; + + spin_lock(&usb_drv->dynids.lock); + list_for_each_entry_safe(dynid, n, &usb_drv->dynids.list, node) { + list_del(&dynid->node); + kfree(dynid); + } + spin_unlock(&usb_drv->dynids.lock); +} + +static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *intf, + struct usb_driver *drv) +{ + struct usb_dynid *dynid; + + spin_lock(&drv->dynids.lock); + list_for_each_entry(dynid, &drv->dynids.list, node) { + if (usb_match_one_id(intf, &dynid->id)) { + spin_unlock(&drv->dynids.lock); + return &dynid->id; + } + } + spin_unlock(&drv->dynids.lock); + return NULL; +} + + +/* called from driver core with dev locked */ +static int usb_probe_device(struct device *dev) +{ + struct usb_device_driver *udriver = to_usb_device_driver(dev->driver); + struct usb_device *udev = to_usb_device(dev); + int error = 0; + + dev_dbg(dev, "%s\n", __func__); + + /* TODO: Add real matching code */ + + /* The device should always appear to be in use + * unless the driver supports autosuspend. + */ + if (!udriver->supports_autosuspend) + error = usb_autoresume_device(udev); + + if (!error) + error = udriver->probe(udev); + return error; +} + +/* called from driver core with dev locked */ +static int usb_unbind_device(struct device *dev) +{ + struct usb_device *udev = to_usb_device(dev); + struct usb_device_driver *udriver = to_usb_device_driver(dev->driver); + + udriver->disconnect(udev); + if (!udriver->supports_autosuspend) + usb_autosuspend_device(udev); + return 0; +} + +/* + * Cancel any pending scheduled resets + * + * [see usb_queue_reset_device()] + * + * Called after unconfiguring / when releasing interfaces. See + * comments in __usb_queue_reset_device() regarding + * udev->reset_running. + */ +static void usb_cancel_queued_reset(struct usb_interface *iface) +{ + if (iface->reset_running == 0) + cancel_work_sync(&iface->reset_ws); +} + +/* called from driver core with dev locked */ +static int usb_probe_interface(struct device *dev) +{ + struct usb_driver *driver = to_usb_driver(dev->driver); + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + const struct usb_device_id *id; + int error = -ENODEV; + int lpm_disable_error; + + dev_dbg(dev, "%s\n", __func__); + + intf->needs_binding = 0; + + if (usb_device_is_owned(udev)) + return error; + + if (udev->authorized == 0) { + dev_err(&intf->dev, "Device is not authorized for usage\n"); + return error; + } + + id = usb_match_id(intf, driver->id_table); + if (!id) + id = usb_match_dynamic_id(intf, driver); + if (!id) + return error; + + dev_dbg(dev, "%s - got id\n", __func__); + + error = usb_autoresume_device(udev); + if (error) + return error; + + intf->condition = USB_INTERFACE_BINDING; + + /* Probed interfaces are initially active. They are + * runtime-PM-enabled only if the driver has autosuspend support. + * They are sensitive to their children's power states. + */ + pm_runtime_set_active(dev); + pm_suspend_ignore_children(dev, false); + if (driver->supports_autosuspend) + pm_runtime_enable(dev); + + /* If the new driver doesn't allow hub-initiated LPM, and we can't + * disable hub-initiated LPM, then fail the probe. + * + * Otherwise, leaving LPM enabled should be harmless, because the + * endpoint intervals should remain the same, and the U1/U2 timeouts + * should remain the same. + * + * If we need to install alt setting 0 before probe, or another alt + * setting during probe, that should also be fine. usb_set_interface() + * will attempt to disable LPM, and fail if it can't disable it. + */ + lpm_disable_error = usb_unlocked_disable_lpm(udev); + if (lpm_disable_error && driver->disable_hub_initiated_lpm) { + dev_err(&intf->dev, "%s Failed to disable LPM for driver %s\n.", + __func__, driver->name); + error = lpm_disable_error; + goto err; + } + + /* Carry out a deferred switch to altsetting 0 */ + if (intf->needs_altsetting0) { + error = usb_set_interface(udev, intf->altsetting[0]. + desc.bInterfaceNumber, 0); + if (error < 0) + goto err; + intf->needs_altsetting0 = 0; + } + + error = driver->probe(intf, id); + if (error) + goto err; + + intf->condition = USB_INTERFACE_BOUND; + + /* If the LPM disable succeeded, balance the ref counts. */ + if (!lpm_disable_error) + usb_unlocked_enable_lpm(udev); + + usb_autosuspend_device(udev); + return error; + + err: + usb_set_intfdata(intf, NULL); + intf->needs_remote_wakeup = 0; + intf->condition = USB_INTERFACE_UNBOUND; + usb_cancel_queued_reset(intf); + + /* If the LPM disable succeeded, balance the ref counts. */ + if (!lpm_disable_error) + usb_unlocked_enable_lpm(udev); + + /* Unbound interfaces are always runtime-PM-disabled and -suspended */ + if (driver->supports_autosuspend) + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + + usb_autosuspend_device(udev); + return error; +} + +/* called from driver core with dev locked */ +static int usb_unbind_interface(struct device *dev) +{ + struct usb_driver *driver = to_usb_driver(dev->driver); + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev; + int error, r, lpm_disable_error; + + intf->condition = USB_INTERFACE_UNBINDING; + + /* Autoresume for set_interface call below */ + udev = interface_to_usbdev(intf); + error = usb_autoresume_device(udev); + + /* Hub-initiated LPM policy may change, so attempt to disable LPM until + * the driver is unbound. If LPM isn't disabled, that's fine because it + * wouldn't be enabled unless all the bound interfaces supported + * hub-initiated LPM. + */ + lpm_disable_error = usb_unlocked_disable_lpm(udev); + + /* Terminate all URBs for this interface unless the driver + * supports "soft" unbinding. + */ + if (!driver->soft_unbind) + usb_disable_interface(udev, intf, false); + + driver->disconnect(intf); + usb_cancel_queued_reset(intf); + + /* Reset other interface state. + * We cannot do a Set-Interface if the device is suspended or + * if it is prepared for a system sleep (since installing a new + * altsetting means creating new endpoint device entries). + * When either of these happens, defer the Set-Interface. + */ + if (intf->cur_altsetting->desc.bAlternateSetting == 0) { + /* Already in altsetting 0 so skip Set-Interface. + * Just re-enable it without affecting the endpoint toggles. + */ + usb_enable_interface(udev, intf, false); + } else if (!error && !intf->dev.power.is_prepared) { + r = usb_set_interface(udev, intf->altsetting[0]. + desc.bInterfaceNumber, 0); + if (r < 0) + intf->needs_altsetting0 = 1; + } else { + intf->needs_altsetting0 = 1; + } + usb_set_intfdata(intf, NULL); + + intf->condition = USB_INTERFACE_UNBOUND; + intf->needs_remote_wakeup = 0; + + /* Attempt to re-enable USB3 LPM, if the disable succeeded. */ + if (!lpm_disable_error) + usb_unlocked_enable_lpm(udev); + + /* Unbound interfaces are always runtime-PM-disabled and -suspended */ + if (driver->supports_autosuspend) + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + + /* Undo any residual pm_autopm_get_interface_* calls */ + for (r = atomic_read(&intf->pm_usage_cnt); r > 0; --r) + usb_autopm_put_interface_no_suspend(intf); + atomic_set(&intf->pm_usage_cnt, 0); + + if (!error) + usb_autosuspend_device(udev); + + return 0; +} + +/** + * usb_driver_claim_interface - bind a driver to an interface + * @driver: the driver to be bound + * @iface: the interface to which it will be bound; must be in the + * usb device's active configuration + * @priv: driver data associated with that interface + * + * This is used by usb device drivers that need to claim more than one + * interface on a device when probing (audio and acm are current examples). + * No device driver should directly modify internal usb_interface or + * usb_device structure members. + * + * Few drivers should need to use this routine, since the most natural + * way to bind to an interface is to return the private data from + * the driver's probe() method. + * + * Callers must own the device lock, so driver probe() entries don't need + * extra locking, but other call contexts may need to explicitly claim that + * lock. + */ +int usb_driver_claim_interface(struct usb_driver *driver, + struct usb_interface *iface, void *priv) +{ + struct device *dev = &iface->dev; + struct usb_device *udev; + int retval = 0; + int lpm_disable_error; + + if (dev->driver) + return -EBUSY; + + udev = interface_to_usbdev(iface); + + dev->driver = &driver->drvwrap.driver; + usb_set_intfdata(iface, priv); + iface->needs_binding = 0; + + iface->condition = USB_INTERFACE_BOUND; + + /* Disable LPM until this driver is bound. */ + lpm_disable_error = usb_unlocked_disable_lpm(udev); + if (lpm_disable_error && driver->disable_hub_initiated_lpm) { + dev_err(&iface->dev, "%s Failed to disable LPM for driver %s\n.", + __func__, driver->name); + return -ENOMEM; + } + + /* Claimed interfaces are initially inactive (suspended) and + * runtime-PM-enabled, but only if the driver has autosuspend + * support. Otherwise they are marked active, to prevent the + * device from being autosuspended, but left disabled. In either + * case they are sensitive to their children's power states. + */ + pm_suspend_ignore_children(dev, false); + if (driver->supports_autosuspend) + pm_runtime_enable(dev); + else + pm_runtime_set_active(dev); + + /* if interface was already added, bind now; else let + * the future device_add() bind it, bypassing probe() + */ + if (device_is_registered(dev)) + retval = device_bind_driver(dev); + + /* Attempt to re-enable USB3 LPM, if the disable was successful. */ + if (!lpm_disable_error) + usb_unlocked_enable_lpm(udev); + + return retval; +} +EXPORT_SYMBOL_GPL(usb_driver_claim_interface); + +/** + * usb_driver_release_interface - unbind a driver from an interface + * @driver: the driver to be unbound + * @iface: the interface from which it will be unbound + * + * This can be used by drivers to release an interface without waiting + * for their disconnect() methods to be called. In typical cases this + * also causes the driver disconnect() method to be called. + * + * This call is synchronous, and may not be used in an interrupt context. + * Callers must own the device lock, so driver disconnect() entries don't + * need extra locking, but other call contexts may need to explicitly claim + * that lock. + */ +void usb_driver_release_interface(struct usb_driver *driver, + struct usb_interface *iface) +{ + struct device *dev = &iface->dev; + + /* this should never happen, don't release something that's not ours */ + if (!dev->driver || dev->driver != &driver->drvwrap.driver) + return; + + /* don't release from within disconnect() */ + if (iface->condition != USB_INTERFACE_BOUND) + return; + iface->condition = USB_INTERFACE_UNBINDING; + + /* Release via the driver core only if the interface + * has already been registered + */ + if (device_is_registered(dev)) { + device_release_driver(dev); + } else { + device_lock(dev); + usb_unbind_interface(dev); + dev->driver = NULL; + device_unlock(dev); + } +} +EXPORT_SYMBOL_GPL(usb_driver_release_interface); + +/* returns 0 if no match, 1 if match */ +int usb_match_device(struct usb_device *dev, const struct usb_device_id *id) +{ + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + id->idVendor != le16_to_cpu(dev->descriptor.idVendor)) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->idProduct != le16_to_cpu(dev->descriptor.idProduct)) + return 0; + + /* No need to test id->bcdDevice_lo != 0, since 0 is never + greater than any unsigned number. */ + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) && + (id->bcdDevice_lo > le16_to_cpu(dev->descriptor.bcdDevice))) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) && + (id->bcdDevice_hi < le16_to_cpu(dev->descriptor.bcdDevice))) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && + (id->bDeviceClass != dev->descriptor.bDeviceClass)) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) && + (id->bDeviceSubClass != dev->descriptor.bDeviceSubClass)) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && + (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol)) + return 0; + + return 1; +} + +/* returns 0 if no match, 1 if match */ +int usb_match_one_id_intf(struct usb_device *dev, + struct usb_host_interface *intf, + const struct usb_device_id *id) +{ + /* The interface class, subclass, protocol and number should never be + * checked for a match if the device class is Vendor Specific, + * unless the match record specifies the Vendor ID. */ + if (dev->descriptor.bDeviceClass == USB_CLASS_VENDOR_SPEC && + !(id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->match_flags & (USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS | + USB_DEVICE_ID_MATCH_INT_PROTOCOL | + USB_DEVICE_ID_MATCH_INT_NUMBER))) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) && + (id->bInterfaceClass != intf->desc.bInterfaceClass)) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) && + (id->bInterfaceSubClass != intf->desc.bInterfaceSubClass)) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) && + (id->bInterfaceProtocol != intf->desc.bInterfaceProtocol)) + return 0; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_NUMBER) && + (id->bInterfaceNumber != intf->desc.bInterfaceNumber)) + return 0; + + return 1; +} + +/* returns 0 if no match, 1 if match */ +int usb_match_one_id(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_host_interface *intf; + struct usb_device *dev; + + /* proc_connectinfo in devio.c may call us with id == NULL. */ + if (id == NULL) + return 0; + + intf = interface->cur_altsetting; + dev = interface_to_usbdev(interface); + + if (!usb_match_device(dev, id)) + return 0; + + return usb_match_one_id_intf(dev, intf, id); +} +EXPORT_SYMBOL_GPL(usb_match_one_id); + +/** + * usb_match_id - find first usb_device_id matching device or interface + * @interface: the interface of interest + * @id: array of usb_device_id structures, terminated by zero entry + * + * usb_match_id searches an array of usb_device_id's and returns + * the first one matching the device or interface, or null. + * This is used when binding (or rebinding) a driver to an interface. + * Most USB device drivers will use this indirectly, through the usb core, + * but some layered driver frameworks use it directly. + * These device tables are exported with MODULE_DEVICE_TABLE, through + * modutils, to support the driver loading functionality of USB hotplugging. + * + * What Matches: + * + * The "match_flags" element in a usb_device_id controls which + * members are used. If the corresponding bit is set, the + * value in the device_id must match its corresponding member + * in the device or interface descriptor, or else the device_id + * does not match. + * + * "driver_info" is normally used only by device drivers, + * but you can create a wildcard "matches anything" usb_device_id + * as a driver's "modules.usbmap" entry if you provide an id with + * only a nonzero "driver_info" field. If you do this, the USB device + * driver's probe() routine should use additional intelligence to + * decide whether to bind to the specified interface. + * + * What Makes Good usb_device_id Tables: + * + * The match algorithm is very simple, so that intelligence in + * driver selection must come from smart driver id records. + * Unless you have good reasons to use another selection policy, + * provide match elements only in related groups, and order match + * specifiers from specific to general. Use the macros provided + * for that purpose if you can. + * + * The most specific match specifiers use device descriptor + * data. These are commonly used with product-specific matches; + * the USB_DEVICE macro lets you provide vendor and product IDs, + * and you can also match against ranges of product revisions. + * These are widely used for devices with application or vendor + * specific bDeviceClass values. + * + * Matches based on device class/subclass/protocol specifications + * are slightly more general; use the USB_DEVICE_INFO macro, or + * its siblings. These are used with single-function devices + * where bDeviceClass doesn't specify that each interface has + * its own class. + * + * Matches based on interface class/subclass/protocol are the + * most general; they let drivers bind to any interface on a + * multiple-function device. Use the USB_INTERFACE_INFO + * macro, or its siblings, to match class-per-interface style + * devices (as recorded in bInterfaceClass). + * + * Note that an entry created by USB_INTERFACE_INFO won't match + * any interface if the device class is set to Vendor-Specific. + * This is deliberate; according to the USB spec the meanings of + * the interface class/subclass/protocol for these devices are also + * vendor-specific, and hence matching against a standard product + * class wouldn't work anyway. If you really want to use an + * interface-based match for such a device, create a match record + * that also specifies the vendor ID. (Unforunately there isn't a + * standard macro for creating records like this.) + * + * Within those groups, remember that not all combinations are + * meaningful. For example, don't give a product version range + * without vendor and product IDs; or specify a protocol without + * its associated class and subclass. + */ +const struct usb_device_id *usb_match_id(struct usb_interface *interface, + const struct usb_device_id *id) +{ + /* proc_connectinfo in devio.c may call us with id == NULL. */ + if (id == NULL) + return NULL; + + /* It is important to check that id->driver_info is nonzero, + since an entry that is all zeroes except for a nonzero + id->driver_info is the way to create an entry that + indicates that the driver want to examine every + device and interface. */ + for (; id->idVendor || id->idProduct || id->bDeviceClass || + id->bInterfaceClass || id->driver_info; id++) { + if (usb_match_one_id(interface, id)) + return id; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_match_id); + +static int usb_device_match(struct device *dev, struct device_driver *drv) +{ + /* devices and interfaces are handled separately */ + if (is_usb_device(dev)) { + + /* interface drivers never match devices */ + if (!is_usb_device_driver(drv)) + return 0; + + /* TODO: Add real matching code */ + return 1; + + } else if (is_usb_interface(dev)) { + struct usb_interface *intf; + struct usb_driver *usb_drv; + const struct usb_device_id *id; + + /* device drivers never match interfaces */ + if (is_usb_device_driver(drv)) + return 0; + + intf = to_usb_interface(dev); + usb_drv = to_usb_driver(drv); + + id = usb_match_id(intf, usb_drv->id_table); + if (id) + return 1; + + id = usb_match_dynamic_id(intf, usb_drv); + if (id) + return 1; + } + + return 0; +} + +static int usb_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct usb_device *usb_dev; + + if (is_usb_device(dev)) { + usb_dev = to_usb_device(dev); + } else if (is_usb_interface(dev)) { + struct usb_interface *intf = to_usb_interface(dev); + + usb_dev = interface_to_usbdev(intf); + } else { + return 0; + } + + if (usb_dev->devnum < 0) { + /* driver is often null here; dev_dbg() would oops */ + pr_debug("usb %s: already deleted?\n", dev_name(dev)); + return -ENODEV; + } + if (!usb_dev->bus) { + pr_debug("usb %s: bus removed?\n", dev_name(dev)); + return -ENODEV; + } + + /* per-device configurations are common */ + if (add_uevent_var(env, "PRODUCT=%x/%x/%x", + le16_to_cpu(usb_dev->descriptor.idVendor), + le16_to_cpu(usb_dev->descriptor.idProduct), + le16_to_cpu(usb_dev->descriptor.bcdDevice))) + return -ENOMEM; + + /* class-based driver binding models */ + if (add_uevent_var(env, "TYPE=%d/%d/%d", + usb_dev->descriptor.bDeviceClass, + usb_dev->descriptor.bDeviceSubClass, + usb_dev->descriptor.bDeviceProtocol)) + return -ENOMEM; + + return 0; +} + +/** + * usb_register_device_driver - register a USB device (not interface) driver + * @new_udriver: USB operations for the device driver + * @owner: module owner of this driver. + * + * Registers a USB device driver with the USB core. The list of + * unattached devices will be rescanned whenever a new driver is + * added, allowing the new driver to attach to any recognized devices. + * Returns a negative error code on failure and 0 on success. + */ +int usb_register_device_driver(struct usb_device_driver *new_udriver, + struct module *owner) +{ + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + new_udriver->drvwrap.for_devices = 1; + new_udriver->drvwrap.driver.name = (char *) new_udriver->name; + new_udriver->drvwrap.driver.bus = &usb_bus_type; + new_udriver->drvwrap.driver.probe = usb_probe_device; + new_udriver->drvwrap.driver.remove = usb_unbind_device; + new_udriver->drvwrap.driver.owner = owner; + + retval = driver_register(&new_udriver->drvwrap.driver); + + if (!retval) + pr_info("%s: registered new device driver %s\n", + usbcore_name, new_udriver->name); + else + printk(KERN_ERR "%s: error %d registering device " + " driver %s\n", + usbcore_name, retval, new_udriver->name); + + return retval; +} +EXPORT_SYMBOL_GPL(usb_register_device_driver); + +/** + * usb_deregister_device_driver - unregister a USB device (not interface) driver + * @udriver: USB operations of the device driver to unregister + * Context: must be able to sleep + * + * Unlinks the specified driver from the internal USB driver list. + */ +void usb_deregister_device_driver(struct usb_device_driver *udriver) +{ + pr_info("%s: deregistering device driver %s\n", + usbcore_name, udriver->name); + + driver_unregister(&udriver->drvwrap.driver); +} +EXPORT_SYMBOL_GPL(usb_deregister_device_driver); + +/** + * usb_register_driver - register a USB interface driver + * @new_driver: USB operations for the interface driver + * @owner: module owner of this driver. + * @mod_name: module name string + * + * Registers a USB interface driver with the USB core. The list of + * unattached interfaces will be rescanned whenever a new driver is + * added, allowing the new driver to attach to any recognized interfaces. + * Returns a negative error code on failure and 0 on success. + * + * NOTE: if you want your driver to use the USB major number, you must call + * usb_register_dev() to enable that functionality. This function no longer + * takes care of that. + */ +int usb_register_driver(struct usb_driver *new_driver, struct module *owner, + const char *mod_name) +{ + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + new_driver->drvwrap.for_devices = 0; + new_driver->drvwrap.driver.name = (char *) new_driver->name; + new_driver->drvwrap.driver.bus = &usb_bus_type; + new_driver->drvwrap.driver.probe = usb_probe_interface; + new_driver->drvwrap.driver.remove = usb_unbind_interface; + new_driver->drvwrap.driver.owner = owner; + new_driver->drvwrap.driver.mod_name = mod_name; + spin_lock_init(&new_driver->dynids.lock); + INIT_LIST_HEAD(&new_driver->dynids.list); + + retval = driver_register(&new_driver->drvwrap.driver); + if (retval) + goto out; + + retval = usb_create_newid_files(new_driver); + if (retval) + goto out_newid; + + pr_info("%s: registered new interface driver %s\n", + usbcore_name, new_driver->name); + +out: + return retval; + +out_newid: + driver_unregister(&new_driver->drvwrap.driver); + + printk(KERN_ERR "%s: error %d registering interface " + " driver %s\n", + usbcore_name, retval, new_driver->name); + goto out; +} +EXPORT_SYMBOL_GPL(usb_register_driver); + +/** + * usb_deregister - unregister a USB interface driver + * @driver: USB operations of the interface driver to unregister + * Context: must be able to sleep + * + * Unlinks the specified driver from the internal USB driver list. + * + * NOTE: If you called usb_register_dev(), you still need to call + * usb_deregister_dev() to clean up your driver's allocated minor numbers, + * this * call will no longer do it for you. + */ +void usb_deregister(struct usb_driver *driver) +{ + pr_info("%s: deregistering interface driver %s\n", + usbcore_name, driver->name); + + usb_remove_newid_files(driver); + driver_unregister(&driver->drvwrap.driver); + usb_free_dynids(driver); +} +EXPORT_SYMBOL_GPL(usb_deregister); + +/* Forced unbinding of a USB interface driver, either because + * it doesn't support pre_reset/post_reset/reset_resume or + * because it doesn't support suspend/resume. + * + * The caller must hold @intf's device's lock, but not @intf's lock. + */ +void usb_forced_unbind_intf(struct usb_interface *intf) +{ + struct usb_driver *driver = to_usb_driver(intf->dev.driver); + + dev_dbg(&intf->dev, "forced unbind\n"); + usb_driver_release_interface(driver, intf); + + /* Mark the interface for later rebinding */ + intf->needs_binding = 1; +} + +/* + * Unbind drivers for @udev's marked interfaces. These interfaces have + * the needs_binding flag set, for example by usb_resume_interface(). + * + * The caller must hold @udev's device lock. + */ +static void unbind_marked_interfaces(struct usb_device *udev) +{ + struct usb_host_config *config; + int i; + struct usb_interface *intf; + + config = udev->actconfig; + if (config) { + for (i = 0; i < config->desc.bNumInterfaces; ++i) { + intf = config->interface[i]; + if (intf->dev.driver && intf->needs_binding) + usb_forced_unbind_intf(intf); + } + } +} + +/* Delayed forced unbinding of a USB interface driver and scan + * for rebinding. + * + * The caller must hold @intf's device's lock, but not @intf's lock. + * + * Note: Rebinds will be skipped if a system sleep transition is in + * progress and the PM "complete" callback hasn't occurred yet. + */ +static void usb_rebind_intf(struct usb_interface *intf) +{ + int rc; + + /* Delayed unbind of an existing driver */ + if (intf->dev.driver) + usb_forced_unbind_intf(intf); + + /* Try to rebind the interface */ + if (!intf->dev.power.is_prepared) { + intf->needs_binding = 0; + rc = device_attach(&intf->dev); + if (rc < 0) + dev_warn(&intf->dev, "rebind failed: %d\n", rc); + } +} + +/* + * Rebind drivers to @udev's marked interfaces. These interfaces have + * the needs_binding flag set. + * + * The caller must hold @udev's device lock. + */ +static void rebind_marked_interfaces(struct usb_device *udev) +{ + struct usb_host_config *config; + int i; + struct usb_interface *intf; + + config = udev->actconfig; + if (config) { + for (i = 0; i < config->desc.bNumInterfaces; ++i) { + intf = config->interface[i]; + if (intf->needs_binding) + usb_rebind_intf(intf); + } + } +} + +/* + * Unbind all of @udev's marked interfaces and then rebind all of them. + * This ordering is necessary because some drivers claim several interfaces + * when they are first probed. + * + * The caller must hold @udev's device lock. + */ +void usb_unbind_and_rebind_marked_interfaces(struct usb_device *udev) +{ + unbind_marked_interfaces(udev); + rebind_marked_interfaces(udev); +} + +#ifdef CONFIG_PM + +/* Unbind drivers for @udev's interfaces that don't support suspend/resume + * There is no check for reset_resume here because it can be determined + * only during resume whether reset_resume is needed. + * + * The caller must hold @udev's device lock. + */ +static void unbind_no_pm_drivers_interfaces(struct usb_device *udev) +{ + struct usb_host_config *config; + int i; + struct usb_interface *intf; + struct usb_driver *drv; + + config = udev->actconfig; + if (config) { + for (i = 0; i < config->desc.bNumInterfaces; ++i) { + intf = config->interface[i]; + + if (intf->dev.driver) { + drv = to_usb_driver(intf->dev.driver); + if (!drv->suspend || !drv->resume) + usb_forced_unbind_intf(intf); + } + } + } +} + +static int usb_suspend_device(struct usb_device *udev, pm_message_t msg) +{ + struct usb_device_driver *udriver; + int status = 0; + + if (udev->state == USB_STATE_NOTATTACHED || + udev->state == USB_STATE_SUSPENDED) + goto done; + + /* For devices that don't have a driver, we do a generic suspend. */ + if (udev->dev.driver) + udriver = to_usb_device_driver(udev->dev.driver); + else { + udev->do_remote_wakeup = 0; + udriver = &usb_generic_driver; + } + status = udriver->suspend(udev, msg); + + done: + dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); + return status; +} + +static int usb_resume_device(struct usb_device *udev, pm_message_t msg) +{ + struct usb_device_driver *udriver; + int status = 0; + + if (udev->state == USB_STATE_NOTATTACHED) + goto done; + + /* Can't resume it if it doesn't have a driver. */ + if (udev->dev.driver == NULL) { + status = -ENOTCONN; + goto done; + } + + /* Non-root devices on a full/low-speed bus must wait for their + * companion high-speed root hub, in case a handoff is needed. + */ + if (!PMSG_IS_AUTO(msg) && udev->parent && udev->bus->hs_companion) + device_pm_wait_for_dev(&udev->dev, + &udev->bus->hs_companion->root_hub->dev); + + if (udev->quirks & USB_QUIRK_RESET_RESUME) + udev->reset_resume = 1; + + udriver = to_usb_device_driver(udev->dev.driver); + status = udriver->resume(udev, msg); + + done: + dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); + return status; +} + +static int usb_suspend_interface(struct usb_device *udev, + struct usb_interface *intf, pm_message_t msg) +{ + struct usb_driver *driver; + int status = 0; + + if (udev->state == USB_STATE_NOTATTACHED || + intf->condition == USB_INTERFACE_UNBOUND) + goto done; + driver = to_usb_driver(intf->dev.driver); + + /* at this time we know the driver supports suspend */ + status = driver->suspend(intf, msg); + if (status && !PMSG_IS_AUTO(msg)) + dev_err(&intf->dev, "suspend error %d\n", status); + + done: + dev_vdbg(&intf->dev, "%s: status %d\n", __func__, status); + return status; +} + +static int usb_resume_interface(struct usb_device *udev, + struct usb_interface *intf, pm_message_t msg, int reset_resume) +{ + struct usb_driver *driver; + int status = 0; + + if (udev->state == USB_STATE_NOTATTACHED) + goto done; + + /* Don't let autoresume interfere with unbinding */ + if (intf->condition == USB_INTERFACE_UNBINDING) + goto done; + + /* Can't resume it if it doesn't have a driver. */ + if (intf->condition == USB_INTERFACE_UNBOUND) { + + /* Carry out a deferred switch to altsetting 0 */ + if (intf->needs_altsetting0 && !intf->dev.power.is_prepared) { + usb_set_interface(udev, intf->altsetting[0]. + desc.bInterfaceNumber, 0); + intf->needs_altsetting0 = 0; + } + goto done; + } + + /* Don't resume if the interface is marked for rebinding */ + if (intf->needs_binding) + goto done; + driver = to_usb_driver(intf->dev.driver); + + if (reset_resume) { + if (driver->reset_resume) { + status = driver->reset_resume(intf); + if (status) + dev_err(&intf->dev, "%s error %d\n", + "reset_resume", status); + } else { + intf->needs_binding = 1; + dev_warn(&intf->dev, "no %s for driver %s?\n", + "reset_resume", driver->name); + } + } else { + status = driver->resume(intf); + if (status) + dev_err(&intf->dev, "resume error %d\n", status); + } + +done: + dev_vdbg(&intf->dev, "%s: status %d\n", __func__, status); + + /* Later we will unbind the driver and/or reprobe, if necessary */ + return status; +} + +/** + * usb_suspend_both - suspend a USB device and its interfaces + * @udev: the usb_device to suspend + * @msg: Power Management message describing this state transition + * + * This is the central routine for suspending USB devices. It calls the + * suspend methods for all the interface drivers in @udev and then calls + * the suspend method for @udev itself. When the routine is called in + * autosuspend, if an error occurs at any stage, all the interfaces + * which were suspended are resumed so that they remain in the same + * state as the device, but when called from system sleep, all error + * from suspend methods of interfaces and the non-root-hub device itself + * are simply ignored, so all suspended interfaces are only resumed + * to the device's state when @udev is root-hub and its suspend method + * returns failure. + * + * Autosuspend requests originating from a child device or an interface + * driver may be made without the protection of @udev's device lock, but + * all other suspend calls will hold the lock. Usbcore will insure that + * method calls do not arrive during bind, unbind, or reset operations. + * However drivers must be prepared to handle suspend calls arriving at + * unpredictable times. + * + * This routine can run only in process context. + */ +static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) +{ + int status = 0; + int i = 0, n = 0; + struct usb_interface *intf; + + if (udev->state == USB_STATE_NOTATTACHED || + udev->state == USB_STATE_SUSPENDED) + goto done; + + /* Suspend all the interfaces and then udev itself */ + if (udev->actconfig) { + n = udev->actconfig->desc.bNumInterfaces; + for (i = n - 1; i >= 0; --i) { + intf = udev->actconfig->interface[i]; + status = usb_suspend_interface(udev, intf, msg); + + /* Ignore errors during system sleep transitions */ + if (!PMSG_IS_AUTO(msg)) + status = 0; + if (status != 0) + break; + } + } + if (status == 0) { + status = usb_suspend_device(udev, msg); + + /* + * Ignore errors from non-root-hub devices during + * system sleep transitions. For the most part, + * these devices should go to low power anyway when + * the entire bus is suspended. + */ + if (udev->parent && !PMSG_IS_AUTO(msg)) + status = 0; + } + + /* If the suspend failed, resume interfaces that did get suspended */ + if (status != 0) { + if (udev->actconfig) { + msg.event ^= (PM_EVENT_SUSPEND | PM_EVENT_RESUME); + while (++i < n) { + intf = udev->actconfig->interface[i]; + usb_resume_interface(udev, intf, msg, 0); + } + } + + /* If the suspend succeeded then prevent any more URB submissions + * and flush any outstanding URBs. + */ + } else { + udev->can_submit = 0; + for (i = 0; i < 16; ++i) { + usb_hcd_flush_endpoint(udev, udev->ep_out[i]); + usb_hcd_flush_endpoint(udev, udev->ep_in[i]); + } + } + + done: + dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); + return status; +} + +/** + * usb_resume_both - resume a USB device and its interfaces + * @udev: the usb_device to resume + * @msg: Power Management message describing this state transition + * + * This is the central routine for resuming USB devices. It calls the + * the resume method for @udev and then calls the resume methods for all + * the interface drivers in @udev. + * + * Autoresume requests originating from a child device or an interface + * driver may be made without the protection of @udev's device lock, but + * all other resume calls will hold the lock. Usbcore will insure that + * method calls do not arrive during bind, unbind, or reset operations. + * However drivers must be prepared to handle resume calls arriving at + * unpredictable times. + * + * This routine can run only in process context. + */ +static int usb_resume_both(struct usb_device *udev, pm_message_t msg) +{ + int status = 0; + int i; + struct usb_interface *intf; + + if (udev->state == USB_STATE_NOTATTACHED) { + status = -ENODEV; + goto done; + } + udev->can_submit = 1; + + /* Resume the device */ + if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) + status = usb_resume_device(udev, msg); + + /* Resume the interfaces */ + if (status == 0 && udev->actconfig) { + for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { + intf = udev->actconfig->interface[i]; + usb_resume_interface(udev, intf, msg, + udev->reset_resume); + } + } + usb_mark_last_busy(udev); + + done: + dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); + if (!status) + udev->reset_resume = 0; + return status; +} + +static void choose_wakeup(struct usb_device *udev, pm_message_t msg) +{ + int w; + + /* Remote wakeup is needed only when we actually go to sleep. + * For things like FREEZE and QUIESCE, if the device is already + * autosuspended then its current wakeup setting is okay. + */ + if (msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_QUIESCE) { + if (udev->state != USB_STATE_SUSPENDED) + udev->do_remote_wakeup = 0; + return; + } + + /* Enable remote wakeup if it is allowed, even if no interface drivers + * actually want it. + */ + w = device_may_wakeup(&udev->dev); + + /* If the device is autosuspended with the wrong wakeup setting, + * autoresume now so the setting can be changed. + */ + if (udev->state == USB_STATE_SUSPENDED && w != udev->do_remote_wakeup) + pm_runtime_resume(&udev->dev); + udev->do_remote_wakeup = w; +} + +/* The device lock is held by the PM core */ +int usb_suspend(struct device *dev, pm_message_t msg) +{ + struct usb_device *udev = to_usb_device(dev); + + unbind_no_pm_drivers_interfaces(udev); + + /* From now on we are sure all drivers support suspend/resume + * but not necessarily reset_resume() + * so we may still need to unbind and rebind upon resume + */ + choose_wakeup(udev, msg); + return usb_suspend_both(udev, msg); +} + +/* The device lock is held by the PM core */ +int usb_resume_complete(struct device *dev) +{ + struct usb_device *udev = to_usb_device(dev); + + /* For PM complete calls, all we do is rebind interfaces + * whose needs_binding flag is set + */ + if (udev->state != USB_STATE_NOTATTACHED) + rebind_marked_interfaces(udev); + return 0; +} + +/* The device lock is held by the PM core */ +int usb_resume(struct device *dev, pm_message_t msg) +{ + struct usb_device *udev = to_usb_device(dev); + int status; + + /* For all calls, take the device back to full power and + * tell the PM core in case it was autosuspended previously. + * Unbind the interfaces that will need rebinding later, + * because they fail to support reset_resume. + * (This can't be done in usb_resume_interface() + * above because it doesn't own the right set of locks.) + */ + status = usb_resume_both(udev, msg); + if (status == 0) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + unbind_marked_interfaces(udev); + } + + /* Avoid PM error messages for devices disconnected while suspended + * as we'll display regular disconnect messages just a bit later. + */ + if (status == -ENODEV || status == -ESHUTDOWN) + status = 0; + return status; +} + +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME + +/** + * usb_enable_autosuspend - allow a USB device to be autosuspended + * @udev: the USB device which may be autosuspended + * + * This routine allows @udev to be autosuspended. An autosuspend won't + * take place until the autosuspend_delay has elapsed and all the other + * necessary conditions are satisfied. + * + * The caller must hold @udev's device lock. + */ +void usb_enable_autosuspend(struct usb_device *udev) +{ + pm_runtime_allow(&udev->dev); +} +EXPORT_SYMBOL_GPL(usb_enable_autosuspend); + +/** + * usb_disable_autosuspend - prevent a USB device from being autosuspended + * @udev: the USB device which may not be autosuspended + * + * This routine prevents @udev from being autosuspended and wakes it up + * if it is already autosuspended. + * + * The caller must hold @udev's device lock. + */ +void usb_disable_autosuspend(struct usb_device *udev) +{ + pm_runtime_forbid(&udev->dev); +} +EXPORT_SYMBOL_GPL(usb_disable_autosuspend); + +/** + * usb_autosuspend_device - delayed autosuspend of a USB device and its interfaces + * @udev: the usb_device to autosuspend + * + * This routine should be called when a core subsystem is finished using + * @udev and wants to allow it to autosuspend. Examples would be when + * @udev's device file in usbfs is closed or after a configuration change. + * + * @udev's usage counter is decremented; if it drops to 0 and all the + * interfaces are inactive then a delayed autosuspend will be attempted. + * The attempt may fail (see autosuspend_check()). + * + * The caller must hold @udev's device lock. + * + * This routine can run only in process context. + */ +void usb_autosuspend_device(struct usb_device *udev) +{ + int status; + + usb_mark_last_busy(udev); + status = pm_runtime_put_sync_autosuspend(&udev->dev); + dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n", + __func__, atomic_read(&udev->dev.power.usage_count), + status); +} + +/** + * usb_autoresume_device - immediately autoresume a USB device and its interfaces + * @udev: the usb_device to autoresume + * + * This routine should be called when a core subsystem wants to use @udev + * and needs to guarantee that it is not suspended. No autosuspend will + * occur until usb_autosuspend_device() is called. (Note that this will + * not prevent suspend events originating in the PM core.) Examples would + * be when @udev's device file in usbfs is opened or when a remote-wakeup + * request is received. + * + * @udev's usage counter is incremented to prevent subsequent autosuspends. + * However if the autoresume fails then the usage counter is re-decremented. + * + * The caller must hold @udev's device lock. + * + * This routine can run only in process context. + */ +int usb_autoresume_device(struct usb_device *udev) +{ + int status; + + status = pm_runtime_get_sync(&udev->dev); + if (status < 0) + pm_runtime_put_sync(&udev->dev); + dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n", + __func__, atomic_read(&udev->dev.power.usage_count), + status); + if (status > 0) + status = 0; + return status; +} + +/** + * usb_autopm_put_interface - decrement a USB interface's PM-usage counter + * @intf: the usb_interface whose counter should be decremented + * + * This routine should be called by an interface driver when it is + * finished using @intf and wants to allow it to autosuspend. A typical + * example would be a character-device driver when its device file is + * closed. + * + * The routine decrements @intf's usage counter. When the counter reaches + * 0, a delayed autosuspend request for @intf's device is attempted. The + * attempt may fail (see autosuspend_check()). + * + * This routine can run only in process context. + */ +void usb_autopm_put_interface(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + int status; + + usb_mark_last_busy(udev); + atomic_dec(&intf->pm_usage_cnt); + status = pm_runtime_put_sync(&intf->dev); + dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", + __func__, atomic_read(&intf->dev.power.usage_count), + status); +} +EXPORT_SYMBOL_GPL(usb_autopm_put_interface); + +/** + * usb_autopm_put_interface_async - decrement a USB interface's PM-usage counter + * @intf: the usb_interface whose counter should be decremented + * + * This routine does much the same thing as usb_autopm_put_interface(): + * It decrements @intf's usage counter and schedules a delayed + * autosuspend request if the counter is <= 0. The difference is that it + * does not perform any synchronization; callers should hold a private + * lock and handle all synchronization issues themselves. + * + * Typically a driver would call this routine during an URB's completion + * handler, if no more URBs were pending. + * + * This routine can run in atomic context. + */ +void usb_autopm_put_interface_async(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + int status; + + usb_mark_last_busy(udev); + atomic_dec(&intf->pm_usage_cnt); + status = pm_runtime_put(&intf->dev); + dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", + __func__, atomic_read(&intf->dev.power.usage_count), + status); +} +EXPORT_SYMBOL_GPL(usb_autopm_put_interface_async); + +/** + * usb_autopm_put_interface_no_suspend - decrement a USB interface's PM-usage counter + * @intf: the usb_interface whose counter should be decremented + * + * This routine decrements @intf's usage counter but does not carry out an + * autosuspend. + * + * This routine can run in atomic context. + */ +void usb_autopm_put_interface_no_suspend(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + + usb_mark_last_busy(udev); + atomic_dec(&intf->pm_usage_cnt); + pm_runtime_put_noidle(&intf->dev); +} +EXPORT_SYMBOL_GPL(usb_autopm_put_interface_no_suspend); + +/** + * usb_autopm_get_interface - increment a USB interface's PM-usage counter + * @intf: the usb_interface whose counter should be incremented + * + * This routine should be called by an interface driver when it wants to + * use @intf and needs to guarantee that it is not suspended. In addition, + * the routine prevents @intf from being autosuspended subsequently. (Note + * that this will not prevent suspend events originating in the PM core.) + * This prevention will persist until usb_autopm_put_interface() is called + * or @intf is unbound. A typical example would be a character-device + * driver when its device file is opened. + * + * @intf's usage counter is incremented to prevent subsequent autosuspends. + * However if the autoresume fails then the counter is re-decremented. + * + * This routine can run only in process context. + */ +int usb_autopm_get_interface(struct usb_interface *intf) +{ + int status; + + status = pm_runtime_get_sync(&intf->dev); + if (status < 0) + pm_runtime_put_sync(&intf->dev); + else + atomic_inc(&intf->pm_usage_cnt); + dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", + __func__, atomic_read(&intf->dev.power.usage_count), + status); + if (status > 0) + status = 0; + return status; +} +EXPORT_SYMBOL_GPL(usb_autopm_get_interface); + +/** + * usb_autopm_get_interface_async - increment a USB interface's PM-usage counter + * @intf: the usb_interface whose counter should be incremented + * + * This routine does much the same thing as + * usb_autopm_get_interface(): It increments @intf's usage counter and + * queues an autoresume request if the device is suspended. The + * differences are that it does not perform any synchronization (callers + * should hold a private lock and handle all synchronization issues + * themselves), and it does not autoresume the device directly (it only + * queues a request). After a successful call, the device may not yet be + * resumed. + * + * This routine can run in atomic context. + */ +int usb_autopm_get_interface_async(struct usb_interface *intf) +{ + int status; + + status = pm_runtime_get(&intf->dev); + if (status < 0 && status != -EINPROGRESS) + pm_runtime_put_noidle(&intf->dev); + else + atomic_inc(&intf->pm_usage_cnt); + dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", + __func__, atomic_read(&intf->dev.power.usage_count), + status); + if (status > 0 || status == -EINPROGRESS) + status = 0; + return status; +} +EXPORT_SYMBOL_GPL(usb_autopm_get_interface_async); + +/** + * usb_autopm_get_interface_no_resume - increment a USB interface's PM-usage counter + * @intf: the usb_interface whose counter should be incremented + * + * This routine increments @intf's usage counter but does not carry out an + * autoresume. + * + * This routine can run in atomic context. + */ +void usb_autopm_get_interface_no_resume(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + + usb_mark_last_busy(udev); + atomic_inc(&intf->pm_usage_cnt); + pm_runtime_get_noresume(&intf->dev); +} +EXPORT_SYMBOL_GPL(usb_autopm_get_interface_no_resume); + +/* Internal routine to check whether we may autosuspend a device. */ +static int autosuspend_check(struct usb_device *udev) +{ + int w, i; + struct usb_interface *intf; + + /* Fail if autosuspend is disabled, or any interfaces are in use, or + * any interface drivers require remote wakeup but it isn't available. + */ + w = 0; + if (udev->actconfig) { + for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { + intf = udev->actconfig->interface[i]; + + /* We don't need to check interfaces that are + * disabled for runtime PM. Either they are unbound + * or else their drivers don't support autosuspend + * and so they are permanently active. + */ + if (intf->dev.power.disable_depth) + continue; + if (atomic_read(&intf->dev.power.usage_count) > 0) + return -EBUSY; + w |= intf->needs_remote_wakeup; + + /* Don't allow autosuspend if the device will need + * a reset-resume and any of its interface drivers + * doesn't include support or needs remote wakeup. + */ + if (udev->quirks & USB_QUIRK_RESET_RESUME) { + struct usb_driver *driver; + + driver = to_usb_driver(intf->dev.driver); + if (!driver->reset_resume || + intf->needs_remote_wakeup) + return -EOPNOTSUPP; + } + } + } + if (w && !device_can_wakeup(&udev->dev)) { + dev_dbg(&udev->dev, "remote wakeup needed for autosuspend\n"); + return -EOPNOTSUPP; + } + udev->do_remote_wakeup = w; + return 0; +} + +int usb_runtime_suspend(struct device *dev) +{ + struct usb_device *udev = to_usb_device(dev); + int status; + + /* A USB device can be suspended if it passes the various autosuspend + * checks. Runtime suspend for a USB device means suspending all the + * interfaces and then the device itself. + */ + if (autosuspend_check(udev) != 0) + return -EAGAIN; + + status = usb_suspend_both(udev, PMSG_AUTO_SUSPEND); + + /* Allow a retry if autosuspend failed temporarily */ + if (status == -EAGAIN || status == -EBUSY) + usb_mark_last_busy(udev); + + /* + * The PM core reacts badly unless the return code is 0, + * -EAGAIN, or -EBUSY, so always return -EBUSY on an error + * (except for root hubs, because they don't suspend through + * an upstream port like other USB devices). + */ + if (status != 0 && udev->parent) + return -EBUSY; + return status; +} + +int usb_runtime_resume(struct device *dev) +{ + struct usb_device *udev = to_usb_device(dev); + int status; + + /* Runtime resume for a USB device means resuming both the device + * and all its interfaces. + */ + status = usb_resume_both(udev, PMSG_AUTO_RESUME); + return status; +} + +int usb_runtime_idle(struct device *dev) +{ + struct usb_device *udev = to_usb_device(dev); + + /* An idle USB device can be suspended if it passes the various + * autosuspend checks. + */ + if (autosuspend_check(udev) == 0) + pm_runtime_autosuspend(dev); + return 0; +} + +int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + int ret = -EPERM; + + if (hcd->driver->set_usb2_hw_lpm) { + ret = hcd->driver->set_usb2_hw_lpm(hcd, udev, enable); + if (!ret) + udev->usb2_hw_lpm_enabled = enable; + } + + return ret; +} + +#endif /* CONFIG_PM_RUNTIME */ + +struct bus_type usb_bus_type = { + .name = "usb", + .match = usb_device_match, + .uevent = usb_uevent, +}; diff --git a/drivers/usb/core/endpoint.c b/drivers/usb/core/endpoint.c new file mode 100644 index 00000000..68cc6532 --- /dev/null +++ b/drivers/usb/core/endpoint.c @@ -0,0 +1,218 @@ +/* + * drivers/usb/core/endpoint.c + * + * (C) Copyright 2002,2004,2006 Greg Kroah-Hartman + * (C) Copyright 2002,2004 IBM Corp. + * (C) Copyright 2006 Novell Inc. + * + * Endpoint sysfs stuff + * + */ + +#include +#include +#include +#include +#include +#include "usb.h" + +struct ep_device { + struct usb_endpoint_descriptor *desc; + struct usb_device *udev; + struct device dev; +}; +#define to_ep_device(_dev) \ + container_of(_dev, struct ep_device, dev) + +struct ep_attribute { + struct attribute attr; + ssize_t (*show)(struct usb_device *, + struct usb_endpoint_descriptor *, char *); +}; +#define to_ep_attribute(_attr) \ + container_of(_attr, struct ep_attribute, attr) + +#define usb_ep_attr(field, format_string) \ +static ssize_t show_ep_##field(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ep_device *ep = to_ep_device(dev); \ + return sprintf(buf, format_string, ep->desc->field); \ +} \ +static DEVICE_ATTR(field, S_IRUGO, show_ep_##field, NULL); + +usb_ep_attr(bLength, "%02x\n") +usb_ep_attr(bEndpointAddress, "%02x\n") +usb_ep_attr(bmAttributes, "%02x\n") +usb_ep_attr(bInterval, "%02x\n") + +static ssize_t show_ep_wMaxPacketSize(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ep_device *ep = to_ep_device(dev); + return sprintf(buf, "%04x\n", + usb_endpoint_maxp(ep->desc) & 0x07ff); +} +static DEVICE_ATTR(wMaxPacketSize, S_IRUGO, show_ep_wMaxPacketSize, NULL); + +static ssize_t show_ep_type(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ep_device *ep = to_ep_device(dev); + char *type = "unknown"; + + switch (usb_endpoint_type(ep->desc)) { + case USB_ENDPOINT_XFER_CONTROL: + type = "Control"; + break; + case USB_ENDPOINT_XFER_ISOC: + type = "Isoc"; + break; + case USB_ENDPOINT_XFER_BULK: + type = "Bulk"; + break; + case USB_ENDPOINT_XFER_INT: + type = "Interrupt"; + break; + } + return sprintf(buf, "%s\n", type); +} +static DEVICE_ATTR(type, S_IRUGO, show_ep_type, NULL); + +static ssize_t show_ep_interval(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ep_device *ep = to_ep_device(dev); + char unit; + unsigned interval = 0; + unsigned in; + + in = (ep->desc->bEndpointAddress & USB_DIR_IN); + + switch (usb_endpoint_type(ep->desc)) { + case USB_ENDPOINT_XFER_CONTROL: + if (ep->udev->speed == USB_SPEED_HIGH) + /* uframes per NAK */ + interval = ep->desc->bInterval; + break; + + case USB_ENDPOINT_XFER_ISOC: + interval = 1 << (ep->desc->bInterval - 1); + break; + + case USB_ENDPOINT_XFER_BULK: + if (ep->udev->speed == USB_SPEED_HIGH && !in) + /* uframes per NAK */ + interval = ep->desc->bInterval; + break; + + case USB_ENDPOINT_XFER_INT: + if (ep->udev->speed == USB_SPEED_HIGH) + interval = 1 << (ep->desc->bInterval - 1); + else + interval = ep->desc->bInterval; + break; + } + interval *= (ep->udev->speed == USB_SPEED_HIGH) ? 125 : 1000; + if (interval % 1000) + unit = 'u'; + else { + unit = 'm'; + interval /= 1000; + } + + return sprintf(buf, "%d%cs\n", interval, unit); +} +static DEVICE_ATTR(interval, S_IRUGO, show_ep_interval, NULL); + +static ssize_t show_ep_direction(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ep_device *ep = to_ep_device(dev); + char *direction; + + if (usb_endpoint_xfer_control(ep->desc)) + direction = "both"; + else if (usb_endpoint_dir_in(ep->desc)) + direction = "in"; + else + direction = "out"; + return sprintf(buf, "%s\n", direction); +} +static DEVICE_ATTR(direction, S_IRUGO, show_ep_direction, NULL); + +static struct attribute *ep_dev_attrs[] = { + &dev_attr_bLength.attr, + &dev_attr_bEndpointAddress.attr, + &dev_attr_bmAttributes.attr, + &dev_attr_bInterval.attr, + &dev_attr_wMaxPacketSize.attr, + &dev_attr_interval.attr, + &dev_attr_type.attr, + &dev_attr_direction.attr, + NULL, +}; +static struct attribute_group ep_dev_attr_grp = { + .attrs = ep_dev_attrs, +}; +static const struct attribute_group *ep_dev_groups[] = { + &ep_dev_attr_grp, + NULL +}; + +static void ep_device_release(struct device *dev) +{ + struct ep_device *ep_dev = to_ep_device(dev); + + kfree(ep_dev); +} + +struct device_type usb_ep_device_type = { + .name = "usb_endpoint", + .release = ep_device_release, +}; + +int usb_create_ep_devs(struct device *parent, + struct usb_host_endpoint *endpoint, + struct usb_device *udev) +{ + struct ep_device *ep_dev; + int retval; + + ep_dev = kzalloc(sizeof(*ep_dev), GFP_KERNEL); + if (!ep_dev) { + retval = -ENOMEM; + goto exit; + } + + ep_dev->desc = &endpoint->desc; + ep_dev->udev = udev; + ep_dev->dev.groups = ep_dev_groups; + ep_dev->dev.type = &usb_ep_device_type; + ep_dev->dev.parent = parent; + dev_set_name(&ep_dev->dev, "ep_%02x", endpoint->desc.bEndpointAddress); + + retval = device_register(&ep_dev->dev); + if (retval) + goto error_register; + + device_enable_async_suspend(&ep_dev->dev); + endpoint->ep_dev = ep_dev; + return retval; + +error_register: + put_device(&ep_dev->dev); +exit: + return retval; +} + +void usb_remove_ep_devs(struct usb_host_endpoint *endpoint) +{ + struct ep_device *ep_dev = endpoint->ep_dev; + + if (ep_dev) { + device_unregister(&ep_dev->dev); + endpoint->ep_dev = NULL; + } +} diff --git a/drivers/usb/core/file.c b/drivers/usb/core/file.c new file mode 100644 index 00000000..e5387a47 --- /dev/null +++ b/drivers/usb/core/file.c @@ -0,0 +1,253 @@ +/* + * drivers/usb/core/file.c + * + * (C) Copyright Linus Torvalds 1999 + * (C) Copyright Johannes Erdfelt 1999-2001 + * (C) Copyright Andreas Gal 1999 + * (C) Copyright Gregory P. Smith 1999 + * (C) Copyright Deti Fliegl 1999 (new USB architecture) + * (C) Copyright Randy Dunlap 2000 + * (C) Copyright David Brownell 2000-2001 (kernel hotplug, usb_device_id, + more docs, etc) + * (C) Copyright Yggdrasil Computing, Inc. 2000 + * (usb_device_id matching changes by Adam J. Richter) + * (C) Copyright Greg Kroah-Hartman 2002-2003 + * + */ + +#include +#include +#include +#include +#include + +#include "usb.h" + +#define MAX_USB_MINORS 256 +static const struct file_operations *usb_minors[MAX_USB_MINORS]; +static DECLARE_RWSEM(minor_rwsem); + +static int usb_open(struct inode * inode, struct file * file) +{ + int minor = iminor(inode); + const struct file_operations *c; + int err = -ENODEV; + const struct file_operations *old_fops, *new_fops = NULL; + + down_read(&minor_rwsem); + c = usb_minors[minor]; + + if (!c || !(new_fops = fops_get(c))) + goto done; + + old_fops = file->f_op; + file->f_op = new_fops; + /* Curiouser and curiouser... NULL ->open() as "no device" ? */ + if (file->f_op->open) + err = file->f_op->open(inode,file); + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } + fops_put(old_fops); + done: + up_read(&minor_rwsem); + return err; +} + +static const struct file_operations usb_fops = { + .owner = THIS_MODULE, + .open = usb_open, + .llseek = noop_llseek, +}; + +static struct usb_class { + struct kref kref; + struct class *class; +} *usb_class; + +static char *usb_devnode(struct device *dev, umode_t *mode) +{ + struct usb_class_driver *drv; + + drv = dev_get_drvdata(dev); + if (!drv || !drv->devnode) + return NULL; + return drv->devnode(dev, mode); +} + +static int init_usb_class(void) +{ + int result = 0; + + if (usb_class != NULL) { + kref_get(&usb_class->kref); + goto exit; + } + + usb_class = kmalloc(sizeof(*usb_class), GFP_KERNEL); + if (!usb_class) { + result = -ENOMEM; + goto exit; + } + + kref_init(&usb_class->kref); + usb_class->class = class_create(THIS_MODULE, "usbmisc"); + if (IS_ERR(usb_class->class)) { + result = IS_ERR(usb_class->class); + printk(KERN_ERR "class_create failed for usb devices\n"); + kfree(usb_class); + usb_class = NULL; + goto exit; + } + usb_class->class->devnode = usb_devnode; + +exit: + return result; +} + +static void release_usb_class(struct kref *kref) +{ + /* Ok, we cheat as we know we only have one usb_class */ + class_destroy(usb_class->class); + kfree(usb_class); + usb_class = NULL; +} + +static void destroy_usb_class(void) +{ + if (usb_class) + kref_put(&usb_class->kref, release_usb_class); +} + +int usb_major_init(void) +{ + int error; + + error = register_chrdev(USB_MAJOR, "usb", &usb_fops); + if (error) + printk(KERN_ERR "Unable to get major %d for usb devices\n", + USB_MAJOR); + + return error; +} + +void usb_major_cleanup(void) +{ + unregister_chrdev(USB_MAJOR, "usb"); +} + +/** + * usb_register_dev - register a USB device, and ask for a minor number + * @intf: pointer to the usb_interface that is being registered + * @class_driver: pointer to the usb_class_driver for this device + * + * This should be called by all USB drivers that use the USB major number. + * If CONFIG_USB_DYNAMIC_MINORS is enabled, the minor number will be + * dynamically allocated out of the list of available ones. If it is not + * enabled, the minor number will be based on the next available free minor, + * starting at the class_driver->minor_base. + * + * This function also creates a usb class device in the sysfs tree. + * + * usb_deregister_dev() must be called when the driver is done with + * the minor numbers given out by this function. + * + * Returns -EINVAL if something bad happens with trying to register a + * device, and 0 on success. + */ +int usb_register_dev(struct usb_interface *intf, + struct usb_class_driver *class_driver) +{ + int retval; + int minor_base = class_driver->minor_base; + int minor; + char name[20]; + char *temp; + +#ifdef CONFIG_USB_DYNAMIC_MINORS + /* + * We don't care what the device tries to start at, we want to start + * at zero to pack the devices into the smallest available space with + * no holes in the minor range. + */ + minor_base = 0; +#endif + + if (class_driver->fops == NULL) + return -EINVAL; + if (intf->minor >= 0) + return -EADDRINUSE; + + retval = init_usb_class(); + if (retval) + return retval; + + dev_dbg(&intf->dev, "looking for a minor, starting at %d\n", minor_base); + + down_write(&minor_rwsem); + for (minor = minor_base; minor < MAX_USB_MINORS; ++minor) { + if (usb_minors[minor]) + continue; + + usb_minors[minor] = class_driver->fops; + intf->minor = minor; + break; + } + up_write(&minor_rwsem); + if (intf->minor < 0) + return -EXFULL; + + /* create a usb class device for this usb interface */ + snprintf(name, sizeof(name), class_driver->name, minor - minor_base); + temp = strrchr(name, '/'); + if (temp && (temp[1] != '\0')) + ++temp; + else + temp = name; + intf->usb_dev = device_create(usb_class->class, &intf->dev, + MKDEV(USB_MAJOR, minor), class_driver, + "%s", temp); + if (IS_ERR(intf->usb_dev)) { + down_write(&minor_rwsem); + usb_minors[minor] = NULL; + intf->minor = -1; + up_write(&minor_rwsem); + retval = PTR_ERR(intf->usb_dev); + } + return retval; +} +EXPORT_SYMBOL_GPL(usb_register_dev); + +/** + * usb_deregister_dev - deregister a USB device's dynamic minor. + * @intf: pointer to the usb_interface that is being deregistered + * @class_driver: pointer to the usb_class_driver for this device + * + * Used in conjunction with usb_register_dev(). This function is called + * when the USB driver is finished with the minor numbers gotten from a + * call to usb_register_dev() (usually when the device is disconnected + * from the system.) + * + * This function also removes the usb class device from the sysfs tree. + * + * This should be called by all drivers that use the USB major number. + */ +void usb_deregister_dev(struct usb_interface *intf, + struct usb_class_driver *class_driver) +{ + if (intf->minor == -1) + return; + + dev_dbg(&intf->dev, "removing %d minor\n", intf->minor); + + down_write(&minor_rwsem); + usb_minors[intf->minor] = NULL; + up_write(&minor_rwsem); + + device_destroy(usb_class->class, MKDEV(USB_MAJOR, intf->minor)); + intf->usb_dev = NULL; + intf->minor = -1; + destroy_usb_class(); +} +EXPORT_SYMBOL_GPL(usb_deregister_dev); diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c new file mode 100644 index 00000000..acbfeb0a --- /dev/null +++ b/drivers/usb/core/generic.c @@ -0,0 +1,246 @@ +/* + * drivers/usb/generic.c - generic driver for USB devices (not interfaces) + * + * (C) Copyright 2005 Greg Kroah-Hartman + * + * based on drivers/usb/usb.c which had the following copyrights: + * (C) Copyright Linus Torvalds 1999 + * (C) Copyright Johannes Erdfelt 1999-2001 + * (C) Copyright Andreas Gal 1999 + * (C) Copyright Gregory P. Smith 1999 + * (C) Copyright Deti Fliegl 1999 (new USB architecture) + * (C) Copyright Randy Dunlap 2000 + * (C) Copyright David Brownell 2000-2004 + * (C) Copyright Yggdrasil Computing, Inc. 2000 + * (usb_device_id matching changes by Adam J. Richter) + * (C) Copyright Greg Kroah-Hartman 2002-2003 + * + */ + +#include +#include +#include "usb.h" + +static inline const char *plural(int n) +{ + return (n == 1 ? "" : "s"); +} + +static int is_rndis(struct usb_interface_descriptor *desc) +{ + return desc->bInterfaceClass == USB_CLASS_COMM + && desc->bInterfaceSubClass == 2 + && desc->bInterfaceProtocol == 0xff; +} + +static int is_activesync(struct usb_interface_descriptor *desc) +{ + return desc->bInterfaceClass == USB_CLASS_MISC + && desc->bInterfaceSubClass == 1 + && desc->bInterfaceProtocol == 1; +} + +int usb_choose_configuration(struct usb_device *udev) +{ + int i; + int num_configs; + int insufficient_power = 0; + struct usb_host_config *c, *best; + + if (usb_device_is_owned(udev)) + return 0; + + best = NULL; + c = udev->config; + num_configs = udev->descriptor.bNumConfigurations; + for (i = 0; i < num_configs; (i++, c++)) { + struct usb_interface_descriptor *desc = NULL; + + /* It's possible that a config has no interfaces! */ + if (c->desc.bNumInterfaces > 0) + desc = &c->intf_cache[0]->altsetting->desc; + + /* + * HP's USB bus-powered keyboard has only one configuration + * and it claims to be self-powered; other devices may have + * similar errors in their descriptors. If the next test + * were allowed to execute, such configurations would always + * be rejected and the devices would not work as expected. + * In the meantime, we run the risk of selecting a config + * that requires external power at a time when that power + * isn't available. It seems to be the lesser of two evils. + * + * Bugzilla #6448 reports a device that appears to crash + * when it receives a GET_DEVICE_STATUS request! We don't + * have any other way to tell whether a device is self-powered, + * but since we don't use that information anywhere but here, + * the call has been removed. + * + * Maybe the GET_DEVICE_STATUS call and the test below can + * be reinstated when device firmwares become more reliable. + * Don't hold your breath. + */ +#if 0 + /* Rule out self-powered configs for a bus-powered device */ + if (bus_powered && (c->desc.bmAttributes & + USB_CONFIG_ATT_SELFPOWER)) + continue; +#endif + + /* + * The next test may not be as effective as it should be. + * Some hubs have errors in their descriptor, claiming + * to be self-powered when they are really bus-powered. + * We will overestimate the amount of current such hubs + * make available for each port. + * + * This is a fairly benign sort of failure. It won't + * cause us to reject configurations that we should have + * accepted. + */ + + /* Rule out configs that draw too much bus current */ + if (usb_get_max_power(udev, c) > udev->bus_mA) { + insufficient_power++; + continue; + } + + /* When the first config's first interface is one of Microsoft's + * pet nonstandard Ethernet-over-USB protocols, ignore it unless + * this kernel has enabled the necessary host side driver. + * But: Don't ignore it if it's the only config. + */ + if (i == 0 && num_configs > 1 && desc && + (is_rndis(desc) || is_activesync(desc))) { +#if !defined(CONFIG_USB_NET_RNDIS_HOST) && !defined(CONFIG_USB_NET_RNDIS_HOST_MODULE) + continue; +#else + best = c; +#endif + } + + /* From the remaining configs, choose the first one whose + * first interface is for a non-vendor-specific class. + * Reason: Linux is more likely to have a class driver + * than a vendor-specific driver. */ + else if (udev->descriptor.bDeviceClass != + USB_CLASS_VENDOR_SPEC && + (desc && desc->bInterfaceClass != + USB_CLASS_VENDOR_SPEC)) { + best = c; + break; + } + + /* If all the remaining configs are vendor-specific, + * choose the first one. */ + else if (!best) + best = c; + } + + if (insufficient_power > 0) + dev_info(&udev->dev, "rejected %d configuration%s " + "due to insufficient available bus power\n", + insufficient_power, plural(insufficient_power)); + + if (best) { + i = best->desc.bConfigurationValue; + dev_dbg(&udev->dev, + "configuration #%d chosen from %d choice%s\n", + i, num_configs, plural(num_configs)); + } else { + i = -1; + dev_warn(&udev->dev, + "no configuration chosen from %d choice%s\n", + num_configs, plural(num_configs)); + } + return i; +} + +static int generic_probe(struct usb_device *udev) +{ + int err, c; + + /* Choose and set the configuration. This registers the interfaces + * with the driver core and lets interface drivers bind to them. + */ + if (udev->authorized == 0) + dev_err(&udev->dev, "Device is not authorized for usage\n"); + else { + c = usb_choose_configuration(udev); + if (c >= 0) { + err = usb_set_configuration(udev, c); + if (err && err != -ENODEV) { + dev_err(&udev->dev, "can't set config #%d, error %d\n", + c, err); + /* This need not be fatal. The user can try to + * set other configurations. */ + } + } + } + /* USB device state == configured ... usable */ + usb_notify_add_device(udev); + + return 0; +} + +static void generic_disconnect(struct usb_device *udev) +{ + usb_notify_remove_device(udev); + + /* if this is only an unbind, not a physical disconnect, then + * unconfigure the device */ + if (udev->actconfig) + usb_set_configuration(udev, -1); +} + +#ifdef CONFIG_PM + +static int generic_suspend(struct usb_device *udev, pm_message_t msg) +{ + int rc; + + /* Normal USB devices suspend through their upstream port. + * Root hubs don't have upstream ports to suspend, + * so we have to shut down their downstream HC-to-USB + * interfaces manually by doing a bus (or "global") suspend. + */ + if (!udev->parent) + rc = hcd_bus_suspend(udev, msg); + + /* Non-root devices don't need to do anything for FREEZE or PRETHAW */ + else if (msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_PRETHAW) + rc = 0; + else + rc = usb_port_suspend(udev, msg); + + return rc; +} + +static int generic_resume(struct usb_device *udev, pm_message_t msg) +{ + int rc; + + /* Normal USB devices resume/reset through their upstream port. + * Root hubs don't have upstream ports to resume or reset, + * so we have to start up their downstream HC-to-USB + * interfaces manually by doing a bus (or "global") resume. + */ + if (!udev->parent) + rc = hcd_bus_resume(udev, msg); + else + rc = usb_port_resume(udev, msg); + return rc; +} + +#endif /* CONFIG_PM */ + +struct usb_device_driver usb_generic_driver = { + .name = "usb", + .probe = generic_probe, + .disconnect = generic_disconnect, +#ifdef CONFIG_PM + .suspend = generic_suspend, + .resume = generic_resume, +#endif + .supports_autosuspend = 1, +}; diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c new file mode 100644 index 00000000..4676917e --- /dev/null +++ b/drivers/usb/core/hcd-pci.c @@ -0,0 +1,651 @@ +/* + * (C) Copyright David Brownell 2000-2002 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#ifdef CONFIG_PPC_PMAC +#include +#include +#include +#include +#endif + +#include "usb.h" + + +/* PCI-based HCs are common, but plenty of non-PCI HCs are used too */ + +/* + * Coordinate handoffs between EHCI and companion controllers + * during EHCI probing and system resume. + */ + +static DECLARE_RWSEM(companions_rwsem); + +#define CL_UHCI PCI_CLASS_SERIAL_USB_UHCI +#define CL_OHCI PCI_CLASS_SERIAL_USB_OHCI +#define CL_EHCI PCI_CLASS_SERIAL_USB_EHCI + +static inline int is_ohci_or_uhci(struct pci_dev *pdev) +{ + return pdev->class == CL_OHCI || pdev->class == CL_UHCI; +} + +typedef void (*companion_fn)(struct pci_dev *pdev, struct usb_hcd *hcd, + struct pci_dev *companion, struct usb_hcd *companion_hcd); + +/* Iterate over PCI devices in the same slot as pdev and call fn for each */ +static void for_each_companion(struct pci_dev *pdev, struct usb_hcd *hcd, + companion_fn fn) +{ + struct pci_dev *companion; + struct usb_hcd *companion_hcd; + unsigned int slot = PCI_SLOT(pdev->devfn); + + /* + * Iterate through other PCI functions in the same slot. + * If the function's drvdata isn't set then it isn't bound to + * a USB host controller driver, so skip it. + */ + companion = NULL; + for_each_pci_dev(companion) { + if (companion->bus != pdev->bus || + PCI_SLOT(companion->devfn) != slot) + continue; + companion_hcd = pci_get_drvdata(companion); + if (!companion_hcd || !companion_hcd->self.root_hub) + continue; + fn(pdev, hcd, companion, companion_hcd); + } +} + +/* + * We're about to add an EHCI controller, which will unceremoniously grab + * all the port connections away from its companions. To prevent annoying + * error messages, lock the companion's root hub and gracefully unconfigure + * it beforehand. Leave it locked until the EHCI controller is all set. + */ +static void ehci_pre_add(struct pci_dev *pdev, struct usb_hcd *hcd, + struct pci_dev *companion, struct usb_hcd *companion_hcd) +{ + struct usb_device *udev; + + if (is_ohci_or_uhci(companion)) { + udev = companion_hcd->self.root_hub; + usb_lock_device(udev); + usb_set_configuration(udev, 0); + } +} + +/* + * Adding the EHCI controller has either succeeded or failed. Set the + * companion pointer accordingly, and in either case, reconfigure and + * unlock the root hub. + */ +static void ehci_post_add(struct pci_dev *pdev, struct usb_hcd *hcd, + struct pci_dev *companion, struct usb_hcd *companion_hcd) +{ + struct usb_device *udev; + + if (is_ohci_or_uhci(companion)) { + if (dev_get_drvdata(&pdev->dev)) { /* Succeeded */ + dev_dbg(&pdev->dev, "HS companion for %s\n", + dev_name(&companion->dev)); + companion_hcd->self.hs_companion = &hcd->self; + } + udev = companion_hcd->self.root_hub; + usb_set_configuration(udev, 1); + usb_unlock_device(udev); + } +} + +/* + * We just added a non-EHCI controller. Find the EHCI controller to + * which it is a companion, and store a pointer to the bus structure. + */ +static void non_ehci_add(struct pci_dev *pdev, struct usb_hcd *hcd, + struct pci_dev *companion, struct usb_hcd *companion_hcd) +{ + if (is_ohci_or_uhci(pdev) && companion->class == CL_EHCI) { + dev_dbg(&pdev->dev, "FS/LS companion for %s\n", + dev_name(&companion->dev)); + hcd->self.hs_companion = &companion_hcd->self; + } +} + +/* We are removing an EHCI controller. Clear the companions' pointers. */ +static void ehci_remove(struct pci_dev *pdev, struct usb_hcd *hcd, + struct pci_dev *companion, struct usb_hcd *companion_hcd) +{ + if (is_ohci_or_uhci(companion)) + companion_hcd->self.hs_companion = NULL; +} + +#ifdef CONFIG_PM + +/* An EHCI controller must wait for its companions before resuming. */ +static void ehci_wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd, + struct pci_dev *companion, struct usb_hcd *companion_hcd) +{ + if (is_ohci_or_uhci(companion)) + device_pm_wait_for_dev(&pdev->dev, &companion->dev); +} + +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * usb_hcd_pci_probe - initialize PCI-based HCDs + * @dev: USB Host Controller being probed + * @id: pci hotplug id connecting controller to HCD framework + * Context: !in_interrupt() + * + * Allocates basic PCI 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. + * + * Store this function in the HCD's struct pci_driver as probe(). + */ +int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct hc_driver *driver; + struct usb_hcd *hcd; + int retval; + int hcd_irq = 0; + + if (usb_disabled()) + return -ENODEV; + + if (!id) + return -EINVAL; + driver = (struct hc_driver *)id->driver_data; + if (!driver) + return -EINVAL; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + dev->current_state = PCI_D0; + + /* + * The xHCI driver has its own irq management + * make sure irq setup is not touched for xhci in generic hcd code + */ + if ((driver->flags & HCD_MASK) != HCD_USB3) { + if (!dev->irq) { + dev_err(&dev->dev, + "Found HC with no IRQ. Check BIOS/PCI %s setup!\n", + pci_name(dev)); + retval = -ENODEV; + goto disable_pci; + } + hcd_irq = dev->irq; + } + + hcd = usb_create_hcd(driver, &dev->dev, pci_name(dev)); + if (!hcd) { + retval = -ENOMEM; + goto disable_pci; + } + + if (driver->flags & HCD_MEMORY) { + /* EHCI, OHCI */ + hcd->rsrc_start = pci_resource_start(dev, 0); + hcd->rsrc_len = pci_resource_len(dev, 0); + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + driver->description)) { + dev_dbg(&dev->dev, "controller already in use\n"); + retval = -EBUSY; + goto put_hcd; + } + hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len); + if (hcd->regs == NULL) { + dev_dbg(&dev->dev, "error mapping memory\n"); + retval = -EFAULT; + goto release_mem_region; + } + + } else { + /* UHCI */ + int region; + + for (region = 0; region < PCI_ROM_RESOURCE; region++) { + if (!(pci_resource_flags(dev, region) & + IORESOURCE_IO)) + continue; + + hcd->rsrc_start = pci_resource_start(dev, region); + hcd->rsrc_len = pci_resource_len(dev, region); + if (request_region(hcd->rsrc_start, hcd->rsrc_len, + driver->description)) + break; + } + if (region == PCI_ROM_RESOURCE) { + dev_dbg(&dev->dev, "no i/o regions available\n"); + retval = -EBUSY; + goto put_hcd; + } + } + + pci_set_master(dev); + + /* Note: dev_set_drvdata must be called while holding the rwsem */ + if (dev->class == CL_EHCI) { + down_write(&companions_rwsem); + dev_set_drvdata(&dev->dev, hcd); + for_each_companion(dev, hcd, ehci_pre_add); + retval = usb_add_hcd(hcd, hcd_irq, IRQF_SHARED); + if (retval != 0) + dev_set_drvdata(&dev->dev, NULL); + for_each_companion(dev, hcd, ehci_post_add); + up_write(&companions_rwsem); + } else { + down_read(&companions_rwsem); + dev_set_drvdata(&dev->dev, hcd); + retval = usb_add_hcd(hcd, hcd_irq, IRQF_SHARED); + if (retval != 0) + dev_set_drvdata(&dev->dev, NULL); + else + for_each_companion(dev, hcd, non_ehci_add); + up_read(&companions_rwsem); + } + + if (retval != 0) + goto unmap_registers; + + if (pci_dev_run_wake(dev)) + pm_runtime_put_noidle(&dev->dev); + return retval; + +unmap_registers: + if (driver->flags & HCD_MEMORY) { + iounmap(hcd->regs); +release_mem_region: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + } else + release_region(hcd->rsrc_start, hcd->rsrc_len); +put_hcd: + usb_put_hcd(hcd); +disable_pci: + pci_disable_device(dev); + dev_err(&dev->dev, "init %s fail, %d\n", pci_name(dev), retval); + return retval; +} +EXPORT_SYMBOL_GPL(usb_hcd_pci_probe); + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_pci_remove - shutdown processing for PCI-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_pci_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + * Store this function in the HCD's struct pci_driver as remove(). + */ +void usb_hcd_pci_remove(struct pci_dev *dev) +{ + struct usb_hcd *hcd; + + hcd = pci_get_drvdata(dev); + if (!hcd) + return; + + if (pci_dev_run_wake(dev)) + pm_runtime_get_noresume(&dev->dev); + + /* Fake an interrupt request in order to give the driver a chance + * to test whether the controller hardware has been removed (e.g., + * cardbus physical eject). + */ + local_irq_disable(); + usb_hcd_irq(0, hcd); + local_irq_enable(); + + /* Note: dev_set_drvdata must be called while holding the rwsem */ + if (dev->class == CL_EHCI) { + down_write(&companions_rwsem); + for_each_companion(dev, hcd, ehci_remove); + usb_remove_hcd(hcd); + dev_set_drvdata(&dev->dev, NULL); + up_write(&companions_rwsem); + } else { + /* Not EHCI; just clear the companion pointer */ + down_read(&companions_rwsem); + hcd->self.hs_companion = NULL; + usb_remove_hcd(hcd); + dev_set_drvdata(&dev->dev, NULL); + up_read(&companions_rwsem); + } + + if (hcd->driver->flags & HCD_MEMORY) { + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + } else { + release_region(hcd->rsrc_start, hcd->rsrc_len); + } + + usb_put_hcd(hcd); + pci_disable_device(dev); +} +EXPORT_SYMBOL_GPL(usb_hcd_pci_remove); + +/** + * usb_hcd_pci_shutdown - shutdown host controller + * @dev: USB Host Controller being shutdown + */ +void usb_hcd_pci_shutdown(struct pci_dev *dev) +{ + struct usb_hcd *hcd; + + hcd = pci_get_drvdata(dev); + if (!hcd) + return; + + if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) && + hcd->driver->shutdown) { + hcd->driver->shutdown(hcd); + pci_disable_device(dev); + } +} +EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown); + +#ifdef CONFIG_PM + +#ifdef CONFIG_PPC_PMAC +static void powermac_set_asic(struct pci_dev *pci_dev, int enable) +{ + /* Enanble or disable ASIC clocks for USB */ + if (machine_is(powermac)) { + struct device_node *of_node; + + of_node = pci_device_to_OF_node(pci_dev); + if (of_node) + pmac_call_feature(PMAC_FTR_USB_ENABLE, + of_node, 0, enable); + } +} + +#else + +static inline void powermac_set_asic(struct pci_dev *pci_dev, int enable) +{} + +#endif /* CONFIG_PPC_PMAC */ + +static int check_root_hub_suspended(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + + if (HCD_RH_RUNNING(hcd)) { + dev_warn(dev, "Root hub is not suspended\n"); + return -EBUSY; + } + if (hcd->shared_hcd) { + hcd = hcd->shared_hcd; + if (HCD_RH_RUNNING(hcd)) { + dev_warn(dev, "Secondary root hub is not suspended\n"); + return -EBUSY; + } + } + return 0; +} + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME) +static int suspend_common(struct device *dev, bool do_wakeup) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + int retval; + + /* Root hub suspend should have stopped all downstream traffic, + * and all bus master traffic. And done so for both the interface + * and the stub usb_device (which we check here). But maybe it + * didn't; writing sysfs power/state files ignores such rules... + */ + retval = check_root_hub_suspended(dev); + if (retval) + return retval; + + if (hcd->driver->pci_suspend && !HCD_DEAD(hcd)) { + /* Optimization: Don't suspend if a root-hub wakeup is + * pending and it would cause the HCD to wake up anyway. + */ + if (do_wakeup && HCD_WAKEUP_PENDING(hcd)) + return -EBUSY; + if (do_wakeup && hcd->shared_hcd && + HCD_WAKEUP_PENDING(hcd->shared_hcd)) + return -EBUSY; + retval = hcd->driver->pci_suspend(hcd, do_wakeup); + suspend_report_result(hcd->driver->pci_suspend, retval); + + /* Check again in case wakeup raced with pci_suspend */ + if ((retval == 0 && do_wakeup && HCD_WAKEUP_PENDING(hcd)) || + (retval == 0 && do_wakeup && hcd->shared_hcd && + HCD_WAKEUP_PENDING(hcd->shared_hcd))) { + if (hcd->driver->pci_resume) + hcd->driver->pci_resume(hcd, false); + retval = -EBUSY; + } + if (retval) + return retval; + } + + /* If MSI-X is enabled, the driver will have synchronized all vectors + * in pci_suspend(). If MSI or legacy PCI is enabled, that will be + * synchronized here. + */ + if (!hcd->msix_enabled) + synchronize_irq(pci_dev->irq); + + /* Downstream ports from this root hub should already be quiesced, so + * there will be no DMA activity. Now we can shut down the upstream + * link (except maybe for PME# resume signaling). We'll enter a + * low power state during suspend_noirq, if the hardware allows. + */ + pci_disable_device(pci_dev); + return retval; +} + +static int resume_common(struct device *dev, int event) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + int retval; + + if (HCD_RH_RUNNING(hcd) || + (hcd->shared_hcd && + HCD_RH_RUNNING(hcd->shared_hcd))) { + dev_dbg(dev, "can't resume, not suspended!\n"); + return 0; + } + + retval = pci_enable_device(pci_dev); + if (retval < 0) { + dev_err(dev, "can't re-enable after resume, %d!\n", retval); + return retval; + } + + pci_set_master(pci_dev); + + if (hcd->driver->pci_resume && !HCD_DEAD(hcd)) { + + /* + * Only EHCI controllers have to wait for their companions. + * No locking is needed because PCI controller drivers do not + * get unbound during system resume. + */ + if (pci_dev->class == CL_EHCI && event != PM_EVENT_AUTO_RESUME) + for_each_companion(pci_dev, hcd, + ehci_wait_for_companions); + + retval = hcd->driver->pci_resume(hcd, + event == PM_EVENT_RESTORE); + if (retval) { + dev_err(dev, "PCI post-resume error %d!\n", retval); + if (hcd->shared_hcd) + usb_hc_died(hcd->shared_hcd); + usb_hc_died(hcd); + } + } + return retval; +} +#endif /* SLEEP || RUNTIME */ + +#ifdef CONFIG_PM_SLEEP + +static int hcd_pci_suspend(struct device *dev) +{ + return suspend_common(dev, device_may_wakeup(dev)); +} + +static int hcd_pci_suspend_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + int retval; + + retval = check_root_hub_suspended(dev); + if (retval) + return retval; + + pci_save_state(pci_dev); + + /* If the root hub is dead rather than suspended, disallow remote + * wakeup. usb_hc_died() should ensure that both hosts are marked as + * dying, so we only need to check the primary roothub. + */ + if (HCD_DEAD(hcd)) + device_set_wakeup_enable(dev, 0); + dev_dbg(dev, "wakeup: %d\n", device_may_wakeup(dev)); + + /* Possibly enable remote wakeup, + * choose the appropriate low-power state, and go to that state. + */ + retval = pci_prepare_to_sleep(pci_dev); + if (retval == -EIO) { /* Low-power not supported */ + dev_dbg(dev, "--> PCI D0 legacy\n"); + retval = 0; + } else if (retval == 0) { + dev_dbg(dev, "--> PCI %s\n", + pci_power_name(pci_dev->current_state)); + } else { + suspend_report_result(pci_prepare_to_sleep, retval); + return retval; + } + + powermac_set_asic(pci_dev, 0); + return retval; +} + +static int hcd_pci_resume_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + + powermac_set_asic(pci_dev, 1); + + /* Go back to D0 and disable remote wakeup */ + pci_back_from_sleep(pci_dev); + return 0; +} + +static int hcd_pci_resume(struct device *dev) +{ + return resume_common(dev, PM_EVENT_RESUME); +} + +static int hcd_pci_restore(struct device *dev) +{ + return resume_common(dev, PM_EVENT_RESTORE); +} + +#else + +#define hcd_pci_suspend NULL +#define hcd_pci_suspend_noirq NULL +#define hcd_pci_resume_noirq NULL +#define hcd_pci_resume NULL +#define hcd_pci_restore NULL + +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME + +static int hcd_pci_runtime_suspend(struct device *dev) +{ + int retval; + + retval = suspend_common(dev, true); + if (retval == 0) + powermac_set_asic(to_pci_dev(dev), 0); + dev_dbg(dev, "hcd_pci_runtime_suspend: %d\n", retval); + return retval; +} + +static int hcd_pci_runtime_resume(struct device *dev) +{ + int retval; + + powermac_set_asic(to_pci_dev(dev), 1); + retval = resume_common(dev, PM_EVENT_AUTO_RESUME); + dev_dbg(dev, "hcd_pci_runtime_resume: %d\n", retval); + return retval; +} + +#else + +#define hcd_pci_runtime_suspend NULL +#define hcd_pci_runtime_resume NULL + +#endif /* CONFIG_PM_RUNTIME */ + +const struct dev_pm_ops usb_hcd_pci_pm_ops = { + .suspend = hcd_pci_suspend, + .suspend_noirq = hcd_pci_suspend_noirq, + .resume_noirq = hcd_pci_resume_noirq, + .resume = hcd_pci_resume, + .freeze = check_root_hub_suspended, + .freeze_noirq = check_root_hub_suspended, + .thaw_noirq = NULL, + .thaw = NULL, + .poweroff = hcd_pci_suspend, + .poweroff_noirq = hcd_pci_suspend_noirq, + .restore_noirq = hcd_pci_resume_noirq, + .restore = hcd_pci_restore, + .runtime_suspend = hcd_pci_runtime_suspend, + .runtime_resume = hcd_pci_runtime_resume, +}; +EXPORT_SYMBOL_GPL(usb_hcd_pci_pm_ops); + +#endif /* CONFIG_PM */ diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c new file mode 100644 index 00000000..f6e5ceb0 --- /dev/null +++ b/drivers/usb/core/hcd.c @@ -0,0 +1,2733 @@ +/* + * (C) Copyright Linus Torvalds 1999 + * (C) Copyright Johannes Erdfelt 1999-2001 + * (C) Copyright Andreas Gal 1999 + * (C) Copyright Gregory P. Smith 1999 + * (C) Copyright Deti Fliegl 1999 + * (C) Copyright Randy Dunlap 2000 + * (C) Copyright David Brownell 2000-2002 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "usb.h" + + +/*-------------------------------------------------------------------------*/ + +/* + * USB Host Controller Driver framework + * + * Plugs into usbcore (usb_bus) and lets HCDs share code, minimizing + * HCD-specific behaviors/bugs. + * + * This does error checks, tracks devices and urbs, and delegates to a + * "hc_driver" only for code (and data) that really needs to know about + * hardware differences. That includes root hub registers, i/o queues, + * and so on ... but as little else as possible. + * + * Shared code includes most of the "root hub" code (these are emulated, + * though each HC's hardware works differently) and PCI glue, plus request + * tracking overhead. The HCD code should only block on spinlocks or on + * hardware handshaking; blocking on software events (such as other kernel + * threads releasing resources, or completing actions) is all generic. + * + * Happens the USB 2.0 spec says this would be invisible inside the "USBD", + * and includes mostly a "HCDI" (HCD Interface) along with some APIs used + * only by the hub driver ... and that neither should be seen or used by + * usb client device drivers. + * + * Contributors of ideas or unattributed patches include: David Brownell, + * Roman Weissgaerber, Rory Bolt, Greg Kroah-Hartman, ... + * + * HISTORY: + * 2002-02-21 Pull in most of the usb_bus support from usb.c; some + * associated cleanup. "usb_hcd" still != "usb_bus". + * 2001-12-12 Initial patch version for Linux 2.5.1 kernel. + */ + +/*-------------------------------------------------------------------------*/ + +/* Keep track of which host controller drivers are loaded */ +unsigned long usb_hcds_loaded; +EXPORT_SYMBOL_GPL(usb_hcds_loaded); + +/* host controllers we manage */ +LIST_HEAD (usb_bus_list); +EXPORT_SYMBOL_GPL (usb_bus_list); + +/* used when allocating bus numbers */ +#define USB_MAXBUS 64 +struct usb_busmap { + unsigned long busmap [USB_MAXBUS / (8*sizeof (unsigned long))]; +}; +static struct usb_busmap busmap; + +/* used when updating list of hcds */ +DEFINE_MUTEX(usb_bus_list_lock); /* exported only for usbfs */ +EXPORT_SYMBOL_GPL (usb_bus_list_lock); + +/* used for controlling access to virtual root hubs */ +static DEFINE_SPINLOCK(hcd_root_hub_lock); + +/* used when updating an endpoint's URB list */ +static DEFINE_SPINLOCK(hcd_urb_list_lock); + +/* used to protect against unlinking URBs after the device is gone */ +static DEFINE_SPINLOCK(hcd_urb_unlink_lock); + +/* wait queue for synchronous unlinks */ +DECLARE_WAIT_QUEUE_HEAD(usb_kill_urb_queue); + +static inline int is_root_hub(struct usb_device *udev) +{ + return (udev->parent == NULL); +} + +/*-------------------------------------------------------------------------*/ + +/* + * Sharable chunks of root hub code. + */ + +/*-------------------------------------------------------------------------*/ +#define KERNEL_REL bin2bcd(((LINUX_VERSION_CODE >> 16) & 0x0ff)) +#define KERNEL_VER bin2bcd(((LINUX_VERSION_CODE >> 8) & 0x0ff)) + +/* usb 3.0 root hub device descriptor */ +static const u8 usb3_rh_dev_descriptor[18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x00, 0x03, /* __le16 bcdUSB; v3.0 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x03, /* __u8 bDeviceProtocol; USB 3.0 hub */ + 0x09, /* __u8 bMaxPacketSize0; 2^9 = 512 Bytes */ + + 0x6b, 0x1d, /* __le16 idVendor; Linux Foundation 0x1d6b */ + 0x03, 0x00, /* __le16 idProduct; device 0x0003 */ + KERNEL_VER, KERNEL_REL, /* __le16 bcdDevice */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + +/* usb 2.0 root hub device descriptor */ +static const u8 usb2_rh_dev_descriptor [18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x00, 0x02, /* __le16 bcdUSB; v2.0 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x00, /* __u8 bDeviceProtocol; [ usb 2.0 no TT ] */ + 0x40, /* __u8 bMaxPacketSize0; 64 Bytes */ + + 0x6b, 0x1d, /* __le16 idVendor; Linux Foundation 0x1d6b */ + 0x02, 0x00, /* __le16 idProduct; device 0x0002 */ + KERNEL_VER, KERNEL_REL, /* __le16 bcdDevice */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + +/* no usb 2.0 root hub "device qualifier" descriptor: one speed only */ + +/* usb 1.1 root hub device descriptor */ +static const u8 usb11_rh_dev_descriptor [18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x10, 0x01, /* __le16 bcdUSB; v1.1 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x00, /* __u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x40, /* __u8 bMaxPacketSize0; 64 Bytes */ + + 0x6b, 0x1d, /* __le16 idVendor; Linux Foundation 0x1d6b */ + 0x01, 0x00, /* __le16 idProduct; device 0x0001 */ + KERNEL_VER, KERNEL_REL, /* __le16 bcdDevice */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + + +/*-------------------------------------------------------------------------*/ + +/* Configuration descriptors for our root hubs */ + +static const u8 fs_rh_config_descriptor [] = { + + /* one configuration */ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x19, 0x00, /* __le16 wTotalLength; */ + 0x01, /* __u8 bNumInterfaces; (1) */ + 0x01, /* __u8 bConfigurationValue; */ + 0x00, /* __u8 iConfiguration; */ + 0xc0, /* __u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x00, /* __u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* __u8 if_bLength; */ + 0x04, /* __u8 if_bDescriptorType; Interface */ + 0x00, /* __u8 if_bInterfaceNumber; */ + 0x00, /* __u8 if_bAlternateSetting; */ + 0x01, /* __u8 if_bNumEndpoints; */ + 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* __u8 if_bInterfaceSubClass; */ + 0x00, /* __u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x00, /* __u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + 0x02, 0x00, /* __le16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ + 0xff /* __u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +static const u8 hs_rh_config_descriptor [] = { + + /* one configuration */ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x19, 0x00, /* __le16 wTotalLength; */ + 0x01, /* __u8 bNumInterfaces; (1) */ + 0x01, /* __u8 bConfigurationValue; */ + 0x00, /* __u8 iConfiguration; */ + 0xc0, /* __u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x00, /* __u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* __u8 if_bLength; */ + 0x04, /* __u8 if_bDescriptorType; Interface */ + 0x00, /* __u8 if_bInterfaceNumber; */ + 0x00, /* __u8 if_bAlternateSetting; */ + 0x01, /* __u8 if_bNumEndpoints; */ + 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* __u8 if_bInterfaceSubClass; */ + 0x00, /* __u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x00, /* __u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + /* __le16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) + * see hub.c:hub_configure() for details. */ + (USB_MAXCHILDREN + 1 + 7) / 8, 0x00, + 0x0c /* __u8 ep_bInterval; (256ms -- usb 2.0 spec) */ +}; + +static const u8 ss_rh_config_descriptor[] = { + /* one configuration */ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x1f, 0x00, /* __le16 wTotalLength; */ + 0x01, /* __u8 bNumInterfaces; (1) */ + 0x01, /* __u8 bConfigurationValue; */ + 0x00, /* __u8 iConfiguration; */ + 0xc0, /* __u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x00, /* __u8 MaxPower; */ + + /* one interface */ + 0x09, /* __u8 if_bLength; */ + 0x04, /* __u8 if_bDescriptorType; Interface */ + 0x00, /* __u8 if_bInterfaceNumber; */ + 0x00, /* __u8 if_bAlternateSetting; */ + 0x01, /* __u8 if_bNumEndpoints; */ + 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* __u8 if_bInterfaceSubClass; */ + 0x00, /* __u8 if_bInterfaceProtocol; */ + 0x00, /* __u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + /* __le16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) + * see hub.c:hub_configure() for details. */ + (USB_MAXCHILDREN + 1 + 7) / 8, 0x00, + 0x0c, /* __u8 ep_bInterval; (256ms -- usb 2.0 spec) */ + + /* one SuperSpeed endpoint companion descriptor */ + 0x06, /* __u8 ss_bLength */ + 0x30, /* __u8 ss_bDescriptorType; SuperSpeed EP Companion */ + 0x00, /* __u8 ss_bMaxBurst; allows 1 TX between ACKs */ + 0x00, /* __u8 ss_bmAttributes; 1 packet per service interval */ + 0x02, 0x00 /* __le16 ss_wBytesPerInterval; 15 bits for max 15 ports */ +}; + +/* authorized_default behaviour: + * -1 is authorized for all devices except wireless (old behaviour) + * 0 is unauthorized for all devices + * 1 is authorized for all devices + */ +static int authorized_default = -1; +module_param(authorized_default, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(authorized_default, + "Default USB device authorization: 0 is not authorized, 1 is " + "authorized, -1 is authorized except for wireless USB (default, " + "old behaviour"); +/*-------------------------------------------------------------------------*/ + +/** + * ascii2desc() - Helper routine for producing UTF-16LE string descriptors + * @s: Null-terminated ASCII (actually ISO-8859-1) string + * @buf: Buffer for USB string descriptor (header + UTF-16LE) + * @len: Length (in bytes; may be odd) of descriptor buffer. + * + * The return value is the number of bytes filled in: 2 + 2*strlen(s) or + * buflen, whichever is less. + * + * USB String descriptors can contain at most 126 characters; input + * strings longer than that are truncated. + */ +static unsigned +ascii2desc(char const *s, u8 *buf, unsigned len) +{ + unsigned n, t = 2 + 2*strlen(s); + + if (t > 254) + t = 254; /* Longest possible UTF string descriptor */ + if (len > t) + len = t; + + t += USB_DT_STRING << 8; /* Now t is first 16 bits to store */ + + n = len; + while (n--) { + *buf++ = t; + if (!n--) + break; + *buf++ = t >> 8; + t = (unsigned char)*s++; + } + return len; +} + +/** + * rh_string() - provides string descriptors for root hub + * @id: the string ID number (0: langids, 1: serial #, 2: product, 3: vendor) + * @hcd: the host controller for this root hub + * @data: buffer for output packet + * @len: length of the provided buffer + * + * Produces either a manufacturer, product or serial number string for the + * virtual root hub device. + * Returns the number of bytes filled in: the length of the descriptor or + * of the provided buffer, whichever is less. + */ +static unsigned +rh_string(int id, struct usb_hcd const *hcd, u8 *data, unsigned len) +{ + char buf[100]; + char const *s; + static char const langids[4] = {4, USB_DT_STRING, 0x09, 0x04}; + + // language ids + switch (id) { + case 0: + /* Array of LANGID codes (0x0409 is MSFT-speak for "en-us") */ + /* See http://www.usb.org/developers/docs/USB_LANGIDs.pdf */ + if (len > 4) + len = 4; + memcpy(data, langids, len); + return len; + case 1: + /* Serial number */ + s = hcd->self.bus_name; + break; + case 2: + /* Product name */ + s = hcd->product_desc; + break; + case 3: + /* Manufacturer */ + snprintf (buf, sizeof buf, "%s %s %s", init_utsname()->sysname, + init_utsname()->release, hcd->driver->description); + s = buf; + break; + default: + /* Can't happen; caller guarantees it */ + return 0; + } + + return ascii2desc(s, data, len); +} + + +/* Root hub control transfers execute synchronously */ +static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) +{ + struct usb_ctrlrequest *cmd; + u16 typeReq, wValue, wIndex, wLength; + u8 *ubuf = urb->transfer_buffer; + /* + * tbuf should be as big as the BOS descriptor and + * the USB hub descriptor. + */ + u8 tbuf[USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE] + __attribute__((aligned(4))); + const u8 *bufp = tbuf; + unsigned len = 0; + int status; + u8 patch_wakeup = 0; + u8 patch_protocol = 0; + + might_sleep(); + + spin_lock_irq(&hcd_root_hub_lock); + status = usb_hcd_link_urb_to_ep(hcd, urb); + spin_unlock_irq(&hcd_root_hub_lock); + if (status) + return status; + urb->hcpriv = hcd; /* Indicate it's queued */ + + cmd = (struct usb_ctrlrequest *) urb->setup_packet; + typeReq = (cmd->bRequestType << 8) | cmd->bRequest; + wValue = le16_to_cpu (cmd->wValue); + wIndex = le16_to_cpu (cmd->wIndex); + wLength = le16_to_cpu (cmd->wLength); + + if (wLength > urb->transfer_buffer_length) + goto error; + + urb->actual_length = 0; + switch (typeReq) { + + /* DEVICE REQUESTS */ + + /* The root hub's remote wakeup enable bit is implemented using + * driver model wakeup flags. If this system supports wakeup + * through USB, userspace may change the default "allow wakeup" + * policy through sysfs or these calls. + * + * Most root hubs support wakeup from downstream devices, for + * runtime power management (disabling USB clocks and reducing + * VBUS power usage). However, not all of them do so; silicon, + * board, and BIOS bugs here are not uncommon, so these can't + * be treated quite like external hubs. + * + * Likewise, not all root hubs will pass wakeup events upstream, + * to wake up the whole system. So don't assume root hub and + * controller capabilities are identical. + */ + + case DeviceRequest | USB_REQ_GET_STATUS: + tbuf [0] = (device_may_wakeup(&hcd->self.root_hub->dev) + << USB_DEVICE_REMOTE_WAKEUP) + | (1 << USB_DEVICE_SELF_POWERED); + tbuf [1] = 0; + len = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (wValue == USB_DEVICE_REMOTE_WAKEUP) + device_set_wakeup_enable(&hcd->self.root_hub->dev, 0); + else + goto error; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (device_can_wakeup(&hcd->self.root_hub->dev) + && wValue == USB_DEVICE_REMOTE_WAKEUP) + device_set_wakeup_enable(&hcd->self.root_hub->dev, 1); + else + goto error; + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + tbuf [0] = 1; + len = 1; + /* FALLTHROUGH */ + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch (wValue & 0xff00) { + case USB_DT_DEVICE << 8: + switch (hcd->speed) { + case HCD_USB3: + bufp = usb3_rh_dev_descriptor; + break; + case HCD_USB2: + bufp = usb2_rh_dev_descriptor; + break; + case HCD_USB11: + bufp = usb11_rh_dev_descriptor; + break; + default: + goto error; + } + len = 18; + if (hcd->has_tt) + patch_protocol = 1; + break; + case USB_DT_CONFIG << 8: + switch (hcd->speed) { + case HCD_USB3: + bufp = ss_rh_config_descriptor; + len = sizeof ss_rh_config_descriptor; + break; + case HCD_USB2: + bufp = hs_rh_config_descriptor; + len = sizeof hs_rh_config_descriptor; + break; + case HCD_USB11: + bufp = fs_rh_config_descriptor; + len = sizeof fs_rh_config_descriptor; + break; + default: + goto error; + } + if (device_can_wakeup(&hcd->self.root_hub->dev)) + patch_wakeup = 1; + break; + case USB_DT_STRING << 8: + if ((wValue & 0xff) < 4) + urb->actual_length = rh_string(wValue & 0xff, + hcd, ubuf, wLength); + else /* unsupported IDs --> "protocol stall" */ + goto error; + break; + case USB_DT_BOS << 8: + goto nongeneric; + default: + goto error; + } + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + tbuf [0] = 0; + len = 1; + /* FALLTHROUGH */ + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + // wValue == urb->dev->devaddr + dev_dbg (hcd->self.controller, "root hub device address %d\n", + wValue); + break; + + /* INTERFACE REQUESTS (no defined feature/status flags) */ + + /* ENDPOINT REQUESTS */ + + case EndpointRequest | USB_REQ_GET_STATUS: + // ENDPOINT_HALT flag + tbuf [0] = 0; + tbuf [1] = 0; + len = 2; + /* FALLTHROUGH */ + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + case EndpointOutRequest | USB_REQ_SET_FEATURE: + dev_dbg (hcd->self.controller, "no endpoint features yet\n"); + break; + + /* CLASS REQUESTS (and errors) */ + + default: +nongeneric: + /* non-generic request */ + switch (typeReq) { + case GetHubStatus: + case GetPortStatus: + len = 4; + break; + case GetHubDescriptor: + len = sizeof (struct usb_hub_descriptor); + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + /* len is returned by hub_control */ + break; + } + status = hcd->driver->hub_control (hcd, + typeReq, wValue, wIndex, + tbuf, wLength); + + if (typeReq == GetHubDescriptor) + usb_hub_adjust_deviceremovable(hcd->self.root_hub, + (struct usb_hub_descriptor *)tbuf); + break; +error: + /* "protocol stall" on error */ + status = -EPIPE; + } + + if (status < 0) { + len = 0; + if (status != -EPIPE) { + dev_dbg (hcd->self.controller, + "CTRL: TypeReq=0x%x val=0x%x " + "idx=0x%x len=%d ==> %d\n", + typeReq, wValue, wIndex, + wLength, status); + } + } else if (status > 0) { + /* hub_control may return the length of data copied. */ + len = status; + status = 0; + } + if (len) { + if (urb->transfer_buffer_length < len) + len = urb->transfer_buffer_length; + urb->actual_length = len; + // always USB_DIR_IN, toward host + memcpy (ubuf, bufp, len); + + /* report whether RH hardware supports remote wakeup */ + if (patch_wakeup && + len > offsetof (struct usb_config_descriptor, + bmAttributes)) + ((struct usb_config_descriptor *)ubuf)->bmAttributes + |= USB_CONFIG_ATT_WAKEUP; + + /* report whether RH hardware has an integrated TT */ + if (patch_protocol && + len > offsetof(struct usb_device_descriptor, + bDeviceProtocol)) + ((struct usb_device_descriptor *) ubuf)-> + bDeviceProtocol = USB_HUB_PR_HS_SINGLE_TT; + } + + /* any errors get returned through the urb completion */ + spin_lock_irq(&hcd_root_hub_lock); + usb_hcd_unlink_urb_from_ep(hcd, urb); + + /* This peculiar use of spinlocks echoes what real HC drivers do. + * Avoiding calls to local_irq_disable/enable makes the code + * RT-friendly. + */ + spin_unlock(&hcd_root_hub_lock); + usb_hcd_giveback_urb(hcd, urb, status); + spin_lock(&hcd_root_hub_lock); + + spin_unlock_irq(&hcd_root_hub_lock); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Root Hub interrupt transfers are polled using a timer if the + * driver requests it; otherwise the driver is responsible for + * calling usb_hcd_poll_rh_status() when an event occurs. + * + * Completions are called in_interrupt(), but they may or may not + * be in_irq(). + */ +void usb_hcd_poll_rh_status(struct usb_hcd *hcd) +{ + struct urb *urb; + int length; + unsigned long flags; + char buffer[6]; /* Any root hubs with > 31 ports? */ + + if (unlikely(!hcd->rh_pollable)) + return; + if (!hcd->uses_new_polling && !hcd->status_urb) + return; + + length = hcd->driver->hub_status_data(hcd, buffer); + if (length > 0) { + + /* try to complete the status urb */ + spin_lock_irqsave(&hcd_root_hub_lock, flags); + urb = hcd->status_urb; + if (urb) { + clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags); + hcd->status_urb = NULL; + urb->actual_length = length; + memcpy(urb->transfer_buffer, buffer, length); + + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock(&hcd_root_hub_lock); + usb_hcd_giveback_urb(hcd, urb, 0); + spin_lock(&hcd_root_hub_lock); + } else { + length = 0; + set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags); + } + spin_unlock_irqrestore(&hcd_root_hub_lock, flags); + } + + /* The USB 2.0 spec says 256 ms. This is close enough and won't + * exceed that limit if HZ is 100. The math is more clunky than + * maybe expected, this is to make sure that all timers for USB devices + * fire at the same time to give the CPU a break in between */ + if (hcd->uses_new_polling ? HCD_POLL_RH(hcd) : + (length == 0 && hcd->status_urb != NULL)) + mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4)); +} +EXPORT_SYMBOL_GPL(usb_hcd_poll_rh_status); + +/* timer callback */ +static void rh_timer_func (unsigned long _hcd) +{ + usb_hcd_poll_rh_status((struct usb_hcd *) _hcd); +} + +/*-------------------------------------------------------------------------*/ + +static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb) +{ + int retval; + unsigned long flags; + unsigned len = 1 + (urb->dev->maxchild / 8); + + spin_lock_irqsave (&hcd_root_hub_lock, flags); + if (hcd->status_urb || urb->transfer_buffer_length < len) { + dev_dbg (hcd->self.controller, "not queuing rh status urb\n"); + retval = -EINVAL; + goto done; + } + + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) + goto done; + + hcd->status_urb = urb; + urb->hcpriv = hcd; /* indicate it's queued */ + if (!hcd->uses_new_polling) + mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4)); + + /* If a status change has already occurred, report it ASAP */ + else if (HCD_POLL_PENDING(hcd)) + mod_timer(&hcd->rh_timer, jiffies); + retval = 0; + done: + spin_unlock_irqrestore (&hcd_root_hub_lock, flags); + return retval; +} + +static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb) +{ + if (usb_endpoint_xfer_int(&urb->ep->desc)) + return rh_queue_status (hcd, urb); + if (usb_endpoint_xfer_control(&urb->ep->desc)) + return rh_call_control (hcd, urb); + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ + +/* Unlinks of root-hub control URBs are legal, but they don't do anything + * since these URBs always execute synchronously. + */ +static int usb_rh_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + unsigned long flags; + int rc; + + spin_lock_irqsave(&hcd_root_hub_lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + if (usb_endpoint_num(&urb->ep->desc) == 0) { /* Control URB */ + ; /* Do nothing */ + + } else { /* Status URB */ + if (!hcd->uses_new_polling) + del_timer (&hcd->rh_timer); + if (urb == hcd->status_urb) { + hcd->status_urb = NULL; + usb_hcd_unlink_urb_from_ep(hcd, urb); + + spin_unlock(&hcd_root_hub_lock); + usb_hcd_giveback_urb(hcd, urb, status); + spin_lock(&hcd_root_hub_lock); + } + } + done: + spin_unlock_irqrestore(&hcd_root_hub_lock, flags); + return rc; +} + + + +/* + * Show & store the current value of authorized_default + */ +static ssize_t usb_host_authorized_default_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_device *rh_usb_dev = to_usb_device(dev); + struct usb_bus *usb_bus = rh_usb_dev->bus; + struct usb_hcd *usb_hcd; + + if (usb_bus == NULL) /* FIXME: not sure if this case is possible */ + return -ENODEV; + usb_hcd = bus_to_hcd(usb_bus); + return snprintf(buf, PAGE_SIZE, "%u\n", usb_hcd->authorized_default); +} + +static ssize_t usb_host_authorized_default_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + unsigned val; + struct usb_device *rh_usb_dev = to_usb_device(dev); + struct usb_bus *usb_bus = rh_usb_dev->bus; + struct usb_hcd *usb_hcd; + + if (usb_bus == NULL) /* FIXME: not sure if this case is possible */ + return -ENODEV; + usb_hcd = bus_to_hcd(usb_bus); + result = sscanf(buf, "%u\n", &val); + if (result == 1) { + usb_hcd->authorized_default = val? 1 : 0; + result = size; + } + else + result = -EINVAL; + return result; +} + +static DEVICE_ATTR(authorized_default, 0644, + usb_host_authorized_default_show, + usb_host_authorized_default_store); + + +/* Group all the USB bus attributes */ +static struct attribute *usb_bus_attrs[] = { + &dev_attr_authorized_default.attr, + NULL, +}; + +static struct attribute_group usb_bus_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = usb_bus_attrs, +}; + + + +/*-------------------------------------------------------------------------*/ + +/** + * usb_bus_init - shared initialization code + * @bus: the bus structure being initialized + * + * This code is used to initialize a usb_bus structure, memory for which is + * separately managed. + */ +static void usb_bus_init (struct usb_bus *bus) +{ + memset (&bus->devmap, 0, sizeof(struct usb_devmap)); + + bus->devnum_next = 1; + + bus->root_hub = NULL; + bus->busnum = -1; + bus->bandwidth_allocated = 0; + bus->bandwidth_int_reqs = 0; + bus->bandwidth_isoc_reqs = 0; + + INIT_LIST_HEAD (&bus->bus_list); +} + +/*-------------------------------------------------------------------------*/ + +/** + * usb_register_bus - registers the USB host controller with the usb core + * @bus: pointer to the bus to register + * Context: !in_interrupt() + * + * Assigns a bus number, and links the controller into usbcore data + * structures so that it can be seen by scanning the bus list. + */ +static int usb_register_bus(struct usb_bus *bus) +{ + int result = -E2BIG; + int busnum; + + mutex_lock(&usb_bus_list_lock); + busnum = find_next_zero_bit (busmap.busmap, USB_MAXBUS, 1); + if (busnum >= USB_MAXBUS) { + printk (KERN_ERR "%s: too many buses\n", usbcore_name); + goto error_find_busnum; + } + set_bit (busnum, busmap.busmap); + bus->busnum = busnum; + + /* Add it to the local list of buses */ + list_add (&bus->bus_list, &usb_bus_list); + mutex_unlock(&usb_bus_list_lock); + + usb_notify_add_bus(bus); + + dev_info (bus->controller, "new USB bus registered, assigned bus " + "number %d\n", bus->busnum); + return 0; + +error_find_busnum: + mutex_unlock(&usb_bus_list_lock); + return result; +} + +/** + * usb_deregister_bus - deregisters the USB host controller + * @bus: pointer to the bus to deregister + * Context: !in_interrupt() + * + * Recycles the bus number, and unlinks the controller from usbcore data + * structures so that it won't be seen by scanning the bus list. + */ +static void usb_deregister_bus (struct usb_bus *bus) +{ + dev_info (bus->controller, "USB bus %d deregistered\n", bus->busnum); + + /* + * NOTE: make sure that all the devices are removed by the + * controller code, as well as having it call this when cleaning + * itself up + */ + mutex_lock(&usb_bus_list_lock); + list_del (&bus->bus_list); + mutex_unlock(&usb_bus_list_lock); + + usb_notify_remove_bus(bus); + + clear_bit (bus->busnum, busmap.busmap); +} + +/** + * register_root_hub - called by usb_add_hcd() to register a root hub + * @hcd: host controller for this root hub + * + * This function registers the root hub with the USB subsystem. It sets up + * the device properly in the device tree and then calls usb_new_device() + * to register the usb device. It also assigns the root hub's USB address + * (always 1). + */ +static int register_root_hub(struct usb_hcd *hcd) +{ + struct device *parent_dev = hcd->self.controller; + struct usb_device *usb_dev = hcd->self.root_hub; + const int devnum = 1; + int retval; + + usb_dev->devnum = devnum; + usb_dev->bus->devnum_next = devnum + 1; + memset (&usb_dev->bus->devmap.devicemap, 0, + sizeof usb_dev->bus->devmap.devicemap); + set_bit (devnum, usb_dev->bus->devmap.devicemap); + usb_set_device_state(usb_dev, USB_STATE_ADDRESS); + + mutex_lock(&usb_bus_list_lock); + + usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); + retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE); + if (retval != sizeof usb_dev->descriptor) { + mutex_unlock(&usb_bus_list_lock); + dev_dbg (parent_dev, "can't read %s device descriptor %d\n", + dev_name(&usb_dev->dev), retval); + return (retval < 0) ? retval : -EMSGSIZE; + } + if (usb_dev->speed == USB_SPEED_SUPER) { + retval = usb_get_bos_descriptor(usb_dev); + if (retval < 0) { + mutex_unlock(&usb_bus_list_lock); + dev_dbg(parent_dev, "can't read %s bos descriptor %d\n", + dev_name(&usb_dev->dev), retval); + return retval; + } + } + + retval = usb_new_device (usb_dev); + if (retval) { + dev_err (parent_dev, "can't register root hub for %s, %d\n", + dev_name(&usb_dev->dev), retval); + } else { + spin_lock_irq (&hcd_root_hub_lock); + hcd->rh_registered = 1; + spin_unlock_irq (&hcd_root_hub_lock); + + /* Did the HC die before the root hub was registered? */ + if (HCD_DEAD(hcd)) + usb_hc_died (hcd); /* This time clean up */ + } + mutex_unlock(&usb_bus_list_lock); + + return retval; +} + +/* + * usb_hcd_start_port_resume - a root-hub port is sending a resume signal + * @bus: the bus which the root hub belongs to + * @portnum: the port which is being resumed + * + * HCDs should call this function when they know that a resume signal is + * being sent to a root-hub port. The root hub will be prevented from + * going into autosuspend until usb_hcd_end_port_resume() is called. + * + * The bus's private lock must be held by the caller. + */ +void usb_hcd_start_port_resume(struct usb_bus *bus, int portnum) +{ + unsigned bit = 1 << portnum; + + if (!(bus->resuming_ports & bit)) { + bus->resuming_ports |= bit; + pm_runtime_get_noresume(&bus->root_hub->dev); + } +} +EXPORT_SYMBOL_GPL(usb_hcd_start_port_resume); + +/* + * usb_hcd_end_port_resume - a root-hub port has stopped sending a resume signal + * @bus: the bus which the root hub belongs to + * @portnum: the port which is being resumed + * + * HCDs should call this function when they know that a resume signal has + * stopped being sent to a root-hub port. The root hub will be allowed to + * autosuspend again. + * + * The bus's private lock must be held by the caller. + */ +void usb_hcd_end_port_resume(struct usb_bus *bus, int portnum) +{ + unsigned bit = 1 << portnum; + + if (bus->resuming_ports & bit) { + bus->resuming_ports &= ~bit; + pm_runtime_put_noidle(&bus->root_hub->dev); + } +} +EXPORT_SYMBOL_GPL(usb_hcd_end_port_resume); + +/*-------------------------------------------------------------------------*/ + +/** + * usb_calc_bus_time - approximate periodic transaction time in nanoseconds + * @speed: from dev->speed; USB_SPEED_{LOW,FULL,HIGH} + * @is_input: true iff the transaction sends data to the host + * @isoc: true for isochronous transactions, false for interrupt ones + * @bytecount: how many bytes in the transaction. + * + * Returns approximate bus time in nanoseconds for a periodic transaction. + * See USB 2.0 spec section 5.11.3; only periodic transfers need to be + * scheduled in software, this function is only used for such scheduling. + */ +long usb_calc_bus_time (int speed, int is_input, int isoc, int bytecount) +{ + unsigned long tmp; + + switch (speed) { + case USB_SPEED_LOW: /* INTR only */ + if (is_input) { + tmp = (67667L * (31L + 10L * BitTime (bytecount))) / 1000L; + return (64060L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp); + } else { + tmp = (66700L * (31L + 10L * BitTime (bytecount))) / 1000L; + return (64107L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp); + } + case USB_SPEED_FULL: /* ISOC or INTR */ + if (isoc) { + tmp = (8354L * (31L + 10L * BitTime (bytecount))) / 1000L; + return (((is_input) ? 7268L : 6265L) + BW_HOST_DELAY + tmp); + } else { + tmp = (8354L * (31L + 10L * BitTime (bytecount))) / 1000L; + return (9107L + BW_HOST_DELAY + tmp); + } + case USB_SPEED_HIGH: /* ISOC or INTR */ + // FIXME adjust for input vs output + if (isoc) + tmp = HS_NSECS_ISO (bytecount); + else + tmp = HS_NSECS (bytecount); + return tmp; + default: + pr_debug ("%s: bogus device speed!\n", usbcore_name); + return -1; + } +} +EXPORT_SYMBOL_GPL(usb_calc_bus_time); + + +/*-------------------------------------------------------------------------*/ + +/* + * Generic HC operations. + */ + +/*-------------------------------------------------------------------------*/ + +/** + * usb_hcd_link_urb_to_ep - add an URB to its endpoint queue + * @hcd: host controller to which @urb was submitted + * @urb: URB being submitted + * + * Host controller drivers should call this routine in their enqueue() + * method. The HCD's private spinlock must be held and interrupts must + * be disabled. The actions carried out here are required for URB + * submission, as well as for endpoint shutdown and for usb_kill_urb. + * + * Returns 0 for no error, otherwise a negative error code (in which case + * the enqueue() method must fail). If no error occurs but enqueue() fails + * anyway, it must call usb_hcd_unlink_urb_from_ep() before releasing + * the private spinlock and returning. + */ +int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb) +{ + int rc = 0; + + spin_lock(&hcd_urb_list_lock); + + /* Check that the URB isn't being killed */ + if (unlikely(atomic_read(&urb->reject))) { + rc = -EPERM; + goto done; + } + + if (unlikely(!urb->ep->enabled)) { + rc = -ENOENT; + goto done; + } + + if (unlikely(!urb->dev->can_submit)) { + rc = -EHOSTUNREACH; + goto done; + } + + /* + * Check the host controller's state and add the URB to the + * endpoint's queue. + */ + if (HCD_RH_RUNNING(hcd)) { + urb->unlinked = 0; + list_add_tail(&urb->urb_list, &urb->ep->urb_list); + } else { + rc = -ESHUTDOWN; + goto done; + } + done: + spin_unlock(&hcd_urb_list_lock); + return rc; +} +EXPORT_SYMBOL_GPL(usb_hcd_link_urb_to_ep); + +/** + * usb_hcd_check_unlink_urb - check whether an URB may be unlinked + * @hcd: host controller to which @urb was submitted + * @urb: URB being checked for unlinkability + * @status: error code to store in @urb if the unlink succeeds + * + * Host controller drivers should call this routine in their dequeue() + * method. The HCD's private spinlock must be held and interrupts must + * be disabled. The actions carried out here are required for making + * sure than an unlink is valid. + * + * Returns 0 for no error, otherwise a negative error code (in which case + * the dequeue() method must fail). The possible error codes are: + * + * -EIDRM: @urb was not submitted or has already completed. + * The completion function may not have been called yet. + * + * -EBUSY: @urb has already been unlinked. + */ +int usb_hcd_check_unlink_urb(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + struct list_head *tmp; + + /* insist the urb is still queued */ + list_for_each(tmp, &urb->ep->urb_list) { + if (tmp == &urb->urb_list) + break; + } + if (tmp != &urb->urb_list) + return -EIDRM; + + /* Any status except -EINPROGRESS means something already started to + * unlink this URB from the hardware. So there's no more work to do. + */ + if (urb->unlinked) + return -EBUSY; + urb->unlinked = status; + return 0; +} +EXPORT_SYMBOL_GPL(usb_hcd_check_unlink_urb); + +/** + * usb_hcd_unlink_urb_from_ep - remove an URB from its endpoint queue + * @hcd: host controller to which @urb was submitted + * @urb: URB being unlinked + * + * Host controller drivers should call this routine before calling + * usb_hcd_giveback_urb(). The HCD's private spinlock must be held and + * interrupts must be disabled. The actions carried out here are required + * for URB completion. + */ +void usb_hcd_unlink_urb_from_ep(struct usb_hcd *hcd, struct urb *urb) +{ + /* clear all state linking urb to this dev (and hcd) */ + spin_lock(&hcd_urb_list_lock); + list_del_init(&urb->urb_list); + spin_unlock(&hcd_urb_list_lock); +} +EXPORT_SYMBOL_GPL(usb_hcd_unlink_urb_from_ep); + +/* + * Some usb host controllers can only perform dma using a small SRAM area. + * The usb core itself is however optimized for host controllers that can dma + * using regular system memory - like pci devices doing bus mastering. + * + * To support host controllers with limited dma capabilites we provide dma + * bounce buffers. This feature can be enabled using the HCD_LOCAL_MEM flag. + * For this to work properly the host controller code must first use the + * function dma_declare_coherent_memory() to point out which memory area + * that should be used for dma allocations. + * + * The HCD_LOCAL_MEM flag then tells the usb code to allocate all data for + * dma using dma_alloc_coherent() which in turn allocates from the memory + * area pointed out with dma_declare_coherent_memory(). + * + * So, to summarize... + * + * - We need "local" memory, canonical example being + * a small SRAM on a discrete controller being the + * only memory that the controller can read ... + * (a) "normal" kernel memory is no good, and + * (b) there's not enough to share + * + * - The only *portable* hook for such stuff in the + * DMA framework is dma_declare_coherent_memory() + * + * - So we use that, even though the primary requirement + * is that the memory be "local" (hence addressible + * by that device), not "coherent". + * + */ + +static int hcd_alloc_coherent(struct usb_bus *bus, + gfp_t mem_flags, dma_addr_t *dma_handle, + void **vaddr_handle, size_t size, + enum dma_data_direction dir) +{ + unsigned char *vaddr; + + if (*vaddr_handle == NULL) { + WARN_ON_ONCE(1); + return -EFAULT; + } + + vaddr = hcd_buffer_alloc(bus, size + sizeof(vaddr), + mem_flags, dma_handle); + if (!vaddr) + return -ENOMEM; + + /* + * Store the virtual address of the buffer at the end + * of the allocated dma buffer. The size of the buffer + * may be uneven so use unaligned functions instead + * of just rounding up. It makes sense to optimize for + * memory footprint over access speed since the amount + * of memory available for dma may be limited. + */ + put_unaligned((unsigned long)*vaddr_handle, + (unsigned long *)(vaddr + size)); + + if (dir == DMA_TO_DEVICE) + memcpy(vaddr, *vaddr_handle, size); + + *vaddr_handle = vaddr; + return 0; +} + +static void hcd_free_coherent(struct usb_bus *bus, dma_addr_t *dma_handle, + void **vaddr_handle, size_t size, + enum dma_data_direction dir) +{ + unsigned char *vaddr = *vaddr_handle; + + vaddr = (void *)get_unaligned((unsigned long *)(vaddr + size)); + + if (dir == DMA_FROM_DEVICE) + memcpy(vaddr, *vaddr_handle, size); + + hcd_buffer_free(bus, size + sizeof(vaddr), *vaddr_handle, *dma_handle); + + *vaddr_handle = vaddr; + *dma_handle = 0; +} + +void usb_hcd_unmap_urb_setup_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + if (urb->transfer_flags & URB_SETUP_MAP_SINGLE) + dma_unmap_single(hcd->self.controller, + urb->setup_dma, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + else if (urb->transfer_flags & URB_SETUP_MAP_LOCAL) + hcd_free_coherent(urb->dev->bus, + &urb->setup_dma, + (void **) &urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + + /* Make it safe to call this routine more than once */ + urb->transfer_flags &= ~(URB_SETUP_MAP_SINGLE | URB_SETUP_MAP_LOCAL); +} +EXPORT_SYMBOL_GPL(usb_hcd_unmap_urb_setup_for_dma); + +static void unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + if (hcd->driver->unmap_urb_for_dma) + hcd->driver->unmap_urb_for_dma(hcd, urb); + else + usb_hcd_unmap_urb_for_dma(hcd, urb); +} + +void usb_hcd_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + enum dma_data_direction dir; + + usb_hcd_unmap_urb_setup_for_dma(hcd, urb); + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + if (urb->transfer_flags & URB_DMA_MAP_SG) + dma_unmap_sg(hcd->self.controller, + urb->sg, + urb->num_sgs, + dir); + else if (urb->transfer_flags & URB_DMA_MAP_PAGE) + dma_unmap_page(hcd->self.controller, + urb->transfer_dma, + urb->transfer_buffer_length, + dir); + else if (urb->transfer_flags & URB_DMA_MAP_SINGLE) + dma_unmap_single(hcd->self.controller, + urb->transfer_dma, + urb->transfer_buffer_length, + dir); + else if (urb->transfer_flags & URB_MAP_LOCAL) + hcd_free_coherent(urb->dev->bus, + &urb->transfer_dma, + &urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + + /* Make it safe to call this routine more than once */ + urb->transfer_flags &= ~(URB_DMA_MAP_SG | URB_DMA_MAP_PAGE | + URB_DMA_MAP_SINGLE | URB_MAP_LOCAL); +} +EXPORT_SYMBOL_GPL(usb_hcd_unmap_urb_for_dma); + +static int map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + if (hcd->driver->map_urb_for_dma) + return hcd->driver->map_urb_for_dma(hcd, urb, mem_flags); + else + return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); +} + +int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + enum dma_data_direction dir; + int ret = 0; + + /* Map the URB's buffers for DMA access. + * Lower level HCD code should use *_dma exclusively, + * unless it uses pio or talks to another transport, + * or uses the provided scatter gather list for bulk. + */ + + if (usb_endpoint_xfer_control(&urb->ep->desc)) { + if (hcd->self.uses_pio_for_control) + return ret; + if (hcd->self.uses_dma) { + urb->setup_dma = dma_map_single( + hcd->self.controller, + urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + if (dma_mapping_error(hcd->self.controller, + urb->setup_dma)) + return -EAGAIN; + urb->transfer_flags |= URB_SETUP_MAP_SINGLE; + } else if (hcd->driver->flags & HCD_LOCAL_MEM) { + ret = hcd_alloc_coherent( + urb->dev->bus, mem_flags, + &urb->setup_dma, + (void **)&urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + if (ret) + return ret; + urb->transfer_flags |= URB_SETUP_MAP_LOCAL; + } + } + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + if (urb->transfer_buffer_length != 0 + && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { + if (hcd->self.uses_dma) { + if (urb->num_sgs) { + int n; + + /* We don't support sg for isoc transfers ! */ + if (usb_endpoint_xfer_isoc(&urb->ep->desc)) { + WARN_ON(1); + return -EINVAL; + } + + n = dma_map_sg( + hcd->self.controller, + urb->sg, + urb->num_sgs, + dir); + if (n <= 0) + ret = -EAGAIN; + else + urb->transfer_flags |= URB_DMA_MAP_SG; + urb->num_mapped_sgs = n; + if (n != urb->num_sgs) + urb->transfer_flags |= + URB_DMA_SG_COMBINED; + } else if (urb->sg) { + struct scatterlist *sg = urb->sg; + urb->transfer_dma = dma_map_page( + hcd->self.controller, + sg_page(sg), + sg->offset, + urb->transfer_buffer_length, + dir); + if (dma_mapping_error(hcd->self.controller, + urb->transfer_dma)) + ret = -EAGAIN; + else + urb->transfer_flags |= URB_DMA_MAP_PAGE; + } else { + urb->transfer_dma = dma_map_single( + hcd->self.controller, + urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + if (dma_mapping_error(hcd->self.controller, + urb->transfer_dma)) + ret = -EAGAIN; + else + urb->transfer_flags |= URB_DMA_MAP_SINGLE; + } + } else if (hcd->driver->flags & HCD_LOCAL_MEM) { + ret = hcd_alloc_coherent( + urb->dev->bus, mem_flags, + &urb->transfer_dma, + &urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + if (ret == 0) + urb->transfer_flags |= URB_MAP_LOCAL; + } + if (ret && (urb->transfer_flags & (URB_SETUP_MAP_SINGLE | + URB_SETUP_MAP_LOCAL))) + usb_hcd_unmap_urb_for_dma(hcd, urb); + } + return ret; +} +EXPORT_SYMBOL_GPL(usb_hcd_map_urb_for_dma); + +/*-------------------------------------------------------------------------*/ + +/* may be called in any context with a valid urb->dev usecount + * caller surrenders "ownership" of urb + * expects usb_submit_urb() to have sanity checked and conditioned all + * inputs in the urb + */ +int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags) +{ + int status; + struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus); + + /* increment urb's reference count as part of giving it to the HCD + * (which will control it). HCD guarantees that it either returns + * an error or calls giveback(), but not both. + */ + usb_get_urb(urb); + atomic_inc(&urb->use_count); + atomic_inc(&urb->dev->urbnum); + usbmon_urb_submit(&hcd->self, urb); + + /* NOTE requirements on root-hub callers (usbfs and the hub + * driver, for now): URBs' urb->transfer_buffer must be + * valid and usb_buffer_{sync,unmap}() not be needed, since + * they could clobber root hub response data. Also, control + * URBs must be submitted in process context with interrupts + * enabled. + */ + + if (is_root_hub(urb->dev)) { + status = rh_urb_enqueue(hcd, urb); + } else { + status = map_urb_for_dma(hcd, urb, mem_flags); + if (likely(status == 0)) { + status = hcd->driver->urb_enqueue(hcd, urb, mem_flags); + if (unlikely(status)) + unmap_urb_for_dma(hcd, urb); + } + } + + if (unlikely(status)) { + usbmon_urb_submit_error(&hcd->self, urb, status); + urb->hcpriv = NULL; + INIT_LIST_HEAD(&urb->urb_list); + atomic_dec(&urb->use_count); + atomic_dec(&urb->dev->urbnum); + if (atomic_read(&urb->reject)) + wake_up(&usb_kill_urb_queue); + usb_put_urb(urb); + } + return status; +} + +/*-------------------------------------------------------------------------*/ + +/* this makes the hcd giveback() the urb more quickly, by kicking it + * off hardware queues (which may take a while) and returning it as + * soon as practical. we've already set up the urb's return status, + * but we can't know if the callback completed already. + */ +static int unlink1(struct usb_hcd *hcd, struct urb *urb, int status) +{ + int value; + + if (is_root_hub(urb->dev)) + value = usb_rh_urb_dequeue(hcd, urb, status); + else { + + /* The only reason an HCD might fail this call is if + * it has not yet fully queued the urb to begin with. + * Such failures should be harmless. */ + value = hcd->driver->urb_dequeue(hcd, urb, status); + } + return value; +} + +/* + * called in any context + * + * caller guarantees urb won't be recycled till both unlink() + * and the urb's completion function return + */ +int usb_hcd_unlink_urb (struct urb *urb, int status) +{ + struct usb_hcd *hcd; + int retval = -EIDRM; + unsigned long flags; + + /* Prevent the device and bus from going away while + * the unlink is carried out. If they are already gone + * then urb->use_count must be 0, since disconnected + * devices can't have any active URBs. + */ + spin_lock_irqsave(&hcd_urb_unlink_lock, flags); + if (atomic_read(&urb->use_count) > 0) { + retval = 0; + usb_get_dev(urb->dev); + } + spin_unlock_irqrestore(&hcd_urb_unlink_lock, flags); + if (retval == 0) { + hcd = bus_to_hcd(urb->dev->bus); + retval = unlink1(hcd, urb, status); + usb_put_dev(urb->dev); + } + + if (retval == 0) + retval = -EINPROGRESS; + else if (retval != -EIDRM && retval != -EBUSY) + dev_dbg(&urb->dev->dev, "hcd_unlink_urb %p fail %d\n", + urb, retval); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/** + * usb_hcd_giveback_urb - return URB from HCD to device driver + * @hcd: host controller returning the URB + * @urb: urb being returned to the USB device driver. + * @status: completion status code for the URB. + * Context: in_interrupt() + * + * This hands the URB from HCD to its USB device driver, using its + * completion function. The HCD has freed all per-urb resources + * (and is done using urb->hcpriv). It also released all HCD locks; + * the device driver won't cause problems if it frees, modifies, + * or resubmits this URB. + * + * If @urb was unlinked, the value of @status will be overridden by + * @urb->unlinked. Erroneous short transfers are detected in case + * the HCD hasn't checked for them. + */ +void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status) +{ + urb->hcpriv = NULL; + if (unlikely(urb->unlinked)) + status = urb->unlinked; + else if (unlikely((urb->transfer_flags & URB_SHORT_NOT_OK) && + urb->actual_length < urb->transfer_buffer_length && + !status)) + status = -EREMOTEIO; + + unmap_urb_for_dma(hcd, urb); + usbmon_urb_complete(&hcd->self, urb, status); + usb_unanchor_urb(urb); + + /* pass ownership to the completion handler */ + urb->status = status; + urb->complete (urb); + atomic_dec (&urb->use_count); + if (unlikely(atomic_read(&urb->reject))) + wake_up (&usb_kill_urb_queue); + usb_put_urb (urb); +} +EXPORT_SYMBOL_GPL(usb_hcd_giveback_urb); + +/*-------------------------------------------------------------------------*/ + +/* Cancel all URBs pending on this endpoint and wait for the endpoint's + * queue to drain completely. The caller must first insure that no more + * URBs can be submitted for this endpoint. + */ +void usb_hcd_flush_endpoint(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct usb_hcd *hcd; + struct urb *urb; + + if (!ep) + return; + might_sleep(); + hcd = bus_to_hcd(udev->bus); + + /* No more submits can occur */ + spin_lock_irq(&hcd_urb_list_lock); +rescan: + list_for_each_entry (urb, &ep->urb_list, urb_list) { + int is_in; + + if (urb->unlinked) + continue; + usb_get_urb (urb); + is_in = usb_urb_dir_in(urb); + spin_unlock(&hcd_urb_list_lock); + + /* kick hcd */ + unlink1(hcd, urb, -ESHUTDOWN); + dev_dbg (hcd->self.controller, + "shutdown urb %p ep%d%s%s\n", + urb, usb_endpoint_num(&ep->desc), + is_in ? "in" : "out", + ({ char *s; + + switch (usb_endpoint_type(&ep->desc)) { + case USB_ENDPOINT_XFER_CONTROL: + s = ""; break; + case USB_ENDPOINT_XFER_BULK: + s = "-bulk"; break; + case USB_ENDPOINT_XFER_INT: + s = "-intr"; break; + default: + s = "-iso"; break; + }; + s; + })); + usb_put_urb (urb); + + /* list contents may have changed */ + spin_lock(&hcd_urb_list_lock); + goto rescan; + } + spin_unlock_irq(&hcd_urb_list_lock); + + /* Wait until the endpoint queue is completely empty */ + while (!list_empty (&ep->urb_list)) { + spin_lock_irq(&hcd_urb_list_lock); + + /* The list may have changed while we acquired the spinlock */ + urb = NULL; + if (!list_empty (&ep->urb_list)) { + urb = list_entry (ep->urb_list.prev, struct urb, + urb_list); + usb_get_urb (urb); + } + spin_unlock_irq(&hcd_urb_list_lock); + + if (urb) { + usb_kill_urb (urb); + usb_put_urb (urb); + } + } +} + +/** + * usb_hcd_alloc_bandwidth - check whether a new bandwidth setting exceeds + * the bus bandwidth + * @udev: target &usb_device + * @new_config: new configuration to install + * @cur_alt: the current alternate interface setting + * @new_alt: alternate interface setting that is being installed + * + * To change configurations, pass in the new configuration in new_config, + * and pass NULL for cur_alt and new_alt. + * + * To reset a device's configuration (put the device in the ADDRESSED state), + * pass in NULL for new_config, cur_alt, and new_alt. + * + * To change alternate interface settings, pass in NULL for new_config, + * pass in the current alternate interface setting in cur_alt, + * and pass in the new alternate interface setting in new_alt. + * + * Returns an error if the requested bandwidth change exceeds the + * bus bandwidth or host controller internal resources. + */ +int usb_hcd_alloc_bandwidth(struct usb_device *udev, + struct usb_host_config *new_config, + struct usb_host_interface *cur_alt, + struct usb_host_interface *new_alt) +{ + int num_intfs, i, j; + struct usb_host_interface *alt = NULL; + int ret = 0; + struct usb_hcd *hcd; + struct usb_host_endpoint *ep; + + hcd = bus_to_hcd(udev->bus); + if (!hcd->driver->check_bandwidth) + return 0; + + /* Configuration is being removed - set configuration 0 */ + if (!new_config && !cur_alt) { + for (i = 1; i < 16; ++i) { + ep = udev->ep_out[i]; + if (ep) + hcd->driver->drop_endpoint(hcd, udev, ep); + ep = udev->ep_in[i]; + if (ep) + hcd->driver->drop_endpoint(hcd, udev, ep); + } + hcd->driver->check_bandwidth(hcd, udev); + return 0; + } + /* Check if the HCD says there's enough bandwidth. Enable all endpoints + * each interface's alt setting 0 and ask the HCD to check the bandwidth + * of the bus. There will always be bandwidth for endpoint 0, so it's + * ok to exclude it. + */ + if (new_config) { + num_intfs = new_config->desc.bNumInterfaces; + /* Remove endpoints (except endpoint 0, which is always on the + * schedule) from the old config from the schedule + */ + for (i = 1; i < 16; ++i) { + ep = udev->ep_out[i]; + if (ep) { + ret = hcd->driver->drop_endpoint(hcd, udev, ep); + if (ret < 0) + goto reset; + } + ep = udev->ep_in[i]; + if (ep) { + ret = hcd->driver->drop_endpoint(hcd, udev, ep); + if (ret < 0) + goto reset; + } + } + for (i = 0; i < num_intfs; ++i) { + struct usb_host_interface *first_alt; + int iface_num; + + first_alt = &new_config->intf_cache[i]->altsetting[0]; + iface_num = first_alt->desc.bInterfaceNumber; + /* Set up endpoints for alternate interface setting 0 */ + alt = usb_find_alt_setting(new_config, iface_num, 0); + if (!alt) + /* No alt setting 0? Pick the first setting. */ + alt = first_alt; + + for (j = 0; j < alt->desc.bNumEndpoints; j++) { + ret = hcd->driver->add_endpoint(hcd, udev, &alt->endpoint[j]); + if (ret < 0) + goto reset; + } + } + } + if (cur_alt && new_alt) { + struct usb_interface *iface = usb_ifnum_to_if(udev, + cur_alt->desc.bInterfaceNumber); + + if (!iface) + return -EINVAL; + if (iface->resetting_device) { + /* + * The USB core just reset the device, so the xHCI host + * and the device will think alt setting 0 is installed. + * However, the USB core will pass in the alternate + * setting installed before the reset as cur_alt. Dig + * out the alternate setting 0 structure, or the first + * alternate setting if a broken device doesn't have alt + * setting 0. + */ + cur_alt = usb_altnum_to_altsetting(iface, 0); + if (!cur_alt) + cur_alt = &iface->altsetting[0]; + } + + /* Drop all the endpoints in the current alt setting */ + for (i = 0; i < cur_alt->desc.bNumEndpoints; i++) { + ret = hcd->driver->drop_endpoint(hcd, udev, + &cur_alt->endpoint[i]); + if (ret < 0) + goto reset; + } + /* Add all the endpoints in the new alt setting */ + for (i = 0; i < new_alt->desc.bNumEndpoints; i++) { + ret = hcd->driver->add_endpoint(hcd, udev, + &new_alt->endpoint[i]); + if (ret < 0) + goto reset; + } + } + ret = hcd->driver->check_bandwidth(hcd, udev); +reset: + if (ret < 0) + hcd->driver->reset_bandwidth(hcd, udev); + return ret; +} + +/* Disables the endpoint: synchronizes with the hcd to make sure all + * endpoint state is gone from hardware. usb_hcd_flush_endpoint() must + * have been called previously. Use for set_configuration, set_interface, + * driver removal, physical disconnect. + * + * example: a qh stored in ep->hcpriv, holding state related to endpoint + * type, maxpacket size, toggle, halt status, and scheduling. + */ +void usb_hcd_disable_endpoint(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct usb_hcd *hcd; + + might_sleep(); + hcd = bus_to_hcd(udev->bus); + if (hcd->driver->endpoint_disable) + hcd->driver->endpoint_disable(hcd, ep); +} + +/** + * usb_hcd_reset_endpoint - reset host endpoint state + * @udev: USB device. + * @ep: the endpoint to reset. + * + * Resets any host endpoint state such as the toggle bit, sequence + * number and current window. + */ +void usb_hcd_reset_endpoint(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + if (hcd->driver->endpoint_reset) + hcd->driver->endpoint_reset(hcd, ep); + else { + int epnum = usb_endpoint_num(&ep->desc); + int is_out = usb_endpoint_dir_out(&ep->desc); + int is_control = usb_endpoint_xfer_control(&ep->desc); + + usb_settoggle(udev, epnum, is_out, 0); + if (is_control) + usb_settoggle(udev, epnum, !is_out, 0); + } +} + +/** + * usb_alloc_streams - allocate bulk endpoint stream IDs. + * @interface: alternate setting that includes all endpoints. + * @eps: array of endpoints that need streams. + * @num_eps: number of endpoints in the array. + * @num_streams: number of streams to allocate. + * @mem_flags: flags hcd should use to allocate memory. + * + * Sets up a group of bulk endpoints to have num_streams stream IDs available. + * Drivers may queue multiple transfers to different stream IDs, which may + * complete in a different order than they were queued. + */ +int usb_alloc_streams(struct usb_interface *interface, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int num_streams, gfp_t mem_flags) +{ + struct usb_hcd *hcd; + struct usb_device *dev; + int i; + + dev = interface_to_usbdev(interface); + hcd = bus_to_hcd(dev->bus); + if (!hcd->driver->alloc_streams || !hcd->driver->free_streams) + return -EINVAL; + if (dev->speed != USB_SPEED_SUPER) + return -EINVAL; + if (dev->state < USB_STATE_CONFIGURED) + return -ENODEV; + + /* Streams only apply to bulk endpoints. */ + for (i = 0; i < num_eps; i++) + if (!usb_endpoint_xfer_bulk(&eps[i]->desc)) + return -EINVAL; + + return hcd->driver->alloc_streams(hcd, dev, eps, num_eps, + num_streams, mem_flags); +} +EXPORT_SYMBOL_GPL(usb_alloc_streams); + +/** + * usb_free_streams - free bulk endpoint stream IDs. + * @interface: alternate setting that includes all endpoints. + * @eps: array of endpoints to remove streams from. + * @num_eps: number of endpoints in the array. + * @mem_flags: flags hcd should use to allocate memory. + * + * Reverts a group of bulk endpoints back to not using stream IDs. + * Can fail if we are given bad arguments, or HCD is broken. + */ +void usb_free_streams(struct usb_interface *interface, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags) +{ + struct usb_hcd *hcd; + struct usb_device *dev; + int i; + + dev = interface_to_usbdev(interface); + hcd = bus_to_hcd(dev->bus); + if (dev->speed != USB_SPEED_SUPER) + return; + + /* Streams only apply to bulk endpoints. */ + for (i = 0; i < num_eps; i++) + if (!eps[i] || !usb_endpoint_xfer_bulk(&eps[i]->desc)) + return; + + hcd->driver->free_streams(hcd, dev, eps, num_eps, mem_flags); +} +EXPORT_SYMBOL_GPL(usb_free_streams); + +/* Protect against drivers that try to unlink URBs after the device + * is gone, by waiting until all unlinks for @udev are finished. + * Since we don't currently track URBs by device, simply wait until + * nothing is running in the locked region of usb_hcd_unlink_urb(). + */ +void usb_hcd_synchronize_unlinks(struct usb_device *udev) +{ + spin_lock_irq(&hcd_urb_unlink_lock); + spin_unlock_irq(&hcd_urb_unlink_lock); +} + +/*-------------------------------------------------------------------------*/ + +/* called in any context */ +int usb_hcd_get_frame_number (struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + if (!HCD_RH_RUNNING(hcd)) + return -ESHUTDOWN; + return hcd->driver->get_frame_number (hcd); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) +{ + struct usb_hcd *hcd = container_of(rhdev->bus, struct usb_hcd, self); + int status; + int old_state = hcd->state; + + dev_dbg(&rhdev->dev, "bus %ssuspend, wakeup %d\n", + (PMSG_IS_AUTO(msg) ? "auto-" : ""), + rhdev->do_remote_wakeup); + if (HCD_DEAD(hcd)) { + dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "suspend"); + return 0; + } + + if (!hcd->driver->bus_suspend) { + status = -ENOENT; + } else { + clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags); + hcd->state = HC_STATE_QUIESCING; + status = hcd->driver->bus_suspend(hcd); + } + if (status == 0) { + usb_set_device_state(rhdev, USB_STATE_SUSPENDED); + hcd->state = HC_STATE_SUSPENDED; + + /* Did we race with a root-hub wakeup event? */ + if (rhdev->do_remote_wakeup) { + char buffer[6]; + + status = hcd->driver->hub_status_data(hcd, buffer); + if (status != 0) { + dev_dbg(&rhdev->dev, "suspend raced with wakeup event\n"); + hcd_bus_resume(rhdev, PMSG_AUTO_RESUME); + status = -EBUSY; + } + } + } else { + spin_lock_irq(&hcd_root_hub_lock); + if (!HCD_DEAD(hcd)) { + set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags); + hcd->state = old_state; + } + spin_unlock_irq(&hcd_root_hub_lock); + dev_dbg(&rhdev->dev, "bus %s fail, err %d\n", + "suspend", status); + } + return status; +} + +int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) +{ + struct usb_hcd *hcd = container_of(rhdev->bus, struct usb_hcd, self); + int status; + int old_state = hcd->state; + + dev_dbg(&rhdev->dev, "usb %sresume\n", + (PMSG_IS_AUTO(msg) ? "auto-" : "")); + if (HCD_DEAD(hcd)) { + dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "resume"); + return 0; + } + if (!hcd->driver->bus_resume) + return -ENOENT; + if (HCD_RH_RUNNING(hcd)) + return 0; + + hcd->state = HC_STATE_RESUMING; + status = hcd->driver->bus_resume(hcd); + clear_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags); + if (status == 0) { + struct usb_device *udev; + int port1; + + spin_lock_irq(&hcd_root_hub_lock); + if (!HCD_DEAD(hcd)) { + usb_set_device_state(rhdev, rhdev->actconfig + ? USB_STATE_CONFIGURED + : USB_STATE_ADDRESS); + set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags); + hcd->state = HC_STATE_RUNNING; + } + spin_unlock_irq(&hcd_root_hub_lock); + + /* + * Check whether any of the enabled ports on the root hub are + * unsuspended. If they are then a TRSMRCY delay is needed + * (this is what the USB-2 spec calls a "global resume"). + * Otherwise we can skip the delay. + */ + usb_hub_for_each_child(rhdev, port1, udev) { + if (udev->state != USB_STATE_NOTATTACHED && + !udev->port_is_suspended) { + usleep_range(10000, 11000); /* TRSMRCY */ + break; + } + } + } else { + hcd->state = old_state; + dev_dbg(&rhdev->dev, "bus %s fail, err %d\n", + "resume", status); + if (status != -ESHUTDOWN) + usb_hc_died(hcd); + } + return status; +} + +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME + +/* Workqueue routine for root-hub remote wakeup */ +static void hcd_resume_work(struct work_struct *work) +{ + struct usb_hcd *hcd = container_of(work, struct usb_hcd, wakeup_work); + struct usb_device *udev = hcd->self.root_hub; + + usb_lock_device(udev); + usb_remote_wakeup(udev); + usb_unlock_device(udev); +} + +/** + * usb_hcd_resume_root_hub - called by HCD to resume its root hub + * @hcd: host controller for this root hub + * + * The USB host controller calls this function when its root hub is + * suspended (with the remote wakeup feature enabled) and a remote + * wakeup request is received. The routine submits a workqueue request + * to resume the root hub (that is, manage its downstream ports again). + */ +void usb_hcd_resume_root_hub (struct usb_hcd *hcd) +{ + unsigned long flags; + + spin_lock_irqsave (&hcd_root_hub_lock, flags); + if (hcd->rh_registered) { + set_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags); + queue_work(pm_wq, &hcd->wakeup_work); + } + spin_unlock_irqrestore (&hcd_root_hub_lock, flags); +} +EXPORT_SYMBOL_GPL(usb_hcd_resume_root_hub); + +#endif /* CONFIG_PM_RUNTIME */ + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_OTG + +/** + * usb_bus_start_enum - start immediate enumeration (for OTG) + * @bus: the bus (must use hcd framework) + * @port_num: 1-based number of port; usually bus->otg_port + * Context: in_interrupt() + * + * Starts enumeration, with an immediate reset followed later by + * khubd identifying and possibly configuring the device. + * This is needed by OTG controller drivers, where it helps meet + * HNP protocol timing requirements for starting a port reset. + */ +int usb_bus_start_enum(struct usb_bus *bus, unsigned port_num) +{ + struct usb_hcd *hcd; + int status = -EOPNOTSUPP; + + /* NOTE: since HNP can't start by grabbing the bus's address0_sem, + * boards with root hubs hooked up to internal devices (instead of + * just the OTG port) may need more attention to resetting... + */ + hcd = container_of (bus, struct usb_hcd, self); + if (port_num && hcd->driver->start_port_reset) + status = hcd->driver->start_port_reset(hcd, port_num); + + /* run khubd shortly after (first) root port reset finishes; + * it may issue others, until at least 50 msecs have passed. + */ + if (status == 0) + mod_timer(&hcd->rh_timer, jiffies + msecs_to_jiffies(10)); + return status; +} +EXPORT_SYMBOL_GPL(usb_bus_start_enum); + +#endif + +/*-------------------------------------------------------------------------*/ + +/** + * usb_hcd_irq - hook IRQs to HCD framework (bus glue) + * @irq: the IRQ being raised + * @__hcd: pointer to the HCD whose IRQ is being signaled + * + * If the controller isn't HALTed, calls the driver's irq handler. + * Checks whether the controller is now dead. + */ +irqreturn_t usb_hcd_irq (int irq, void *__hcd) +{ + struct usb_hcd *hcd = __hcd; + unsigned long flags; + irqreturn_t rc; + + /* IRQF_DISABLED doesn't work correctly with shared IRQs + * when the first handler doesn't use it. So let's just + * assume it's never used. + */ + local_irq_save(flags); + + if (unlikely(HCD_DEAD(hcd) || !HCD_HW_ACCESSIBLE(hcd))) + rc = IRQ_NONE; + else if (hcd->driver->irq(hcd) == IRQ_NONE) + rc = IRQ_NONE; + else + rc = IRQ_HANDLED; + + local_irq_restore(flags); + return rc; +} +EXPORT_SYMBOL_GPL(usb_hcd_irq); + +/*-------------------------------------------------------------------------*/ + +/** + * usb_hc_died - report abnormal shutdown of a host controller (bus glue) + * @hcd: pointer to the HCD representing the controller + * + * This is called by bus glue to report a USB host controller that died + * while operations may still have been pending. It's called automatically + * by the PCI glue, so only glue for non-PCI busses should need to call it. + * + * Only call this function with the primary HCD. + */ +void usb_hc_died (struct usb_hcd *hcd) +{ + unsigned long flags; + + dev_err (hcd->self.controller, "HC died; cleaning up\n"); + + spin_lock_irqsave (&hcd_root_hub_lock, flags); + clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags); + set_bit(HCD_FLAG_DEAD, &hcd->flags); + if (hcd->rh_registered) { + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + + /* make khubd clean up old urbs and devices */ + usb_set_device_state (hcd->self.root_hub, + USB_STATE_NOTATTACHED); + usb_kick_khubd (hcd->self.root_hub); + } + if (usb_hcd_is_primary_hcd(hcd) && hcd->shared_hcd) { + hcd = hcd->shared_hcd; + if (hcd->rh_registered) { + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + + /* make khubd clean up old urbs and devices */ + usb_set_device_state(hcd->self.root_hub, + USB_STATE_NOTATTACHED); + usb_kick_khubd(hcd->self.root_hub); + } + } + spin_unlock_irqrestore (&hcd_root_hub_lock, flags); + /* Make sure that the other roothub is also deallocated. */ +} +EXPORT_SYMBOL_GPL (usb_hc_died); + +/*-------------------------------------------------------------------------*/ + +/** + * usb_create_shared_hcd - create and initialize an HCD structure + * @driver: HC driver that will use this hcd + * @dev: device for this HC, stored in hcd->self.controller + * @bus_name: value to store in hcd->self.bus_name + * @primary_hcd: a pointer to the usb_hcd structure that is sharing the + * PCI device. Only allocate certain resources for the primary HCD + * Context: !in_interrupt() + * + * Allocate a struct usb_hcd, with extra space at the end for the + * HC driver's private data. Initialize the generic members of the + * hcd structure. + * + * If memory is unavailable, returns NULL. + */ +struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, + struct device *dev, const char *bus_name, + struct usb_hcd *primary_hcd) +{ + struct usb_hcd *hcd; + + hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL); + if (!hcd) { + dev_dbg (dev, "hcd alloc failed\n"); + return NULL; + } + if (primary_hcd == NULL) { + hcd->bandwidth_mutex = kmalloc(sizeof(*hcd->bandwidth_mutex), + GFP_KERNEL); + if (!hcd->bandwidth_mutex) { + kfree(hcd); + dev_dbg(dev, "hcd bandwidth mutex alloc failed\n"); + return NULL; + } + mutex_init(hcd->bandwidth_mutex); + dev_set_drvdata(dev, hcd); + } else { + hcd->bandwidth_mutex = primary_hcd->bandwidth_mutex; + hcd->primary_hcd = primary_hcd; + primary_hcd->primary_hcd = primary_hcd; + hcd->shared_hcd = primary_hcd; + primary_hcd->shared_hcd = hcd; + } + + kref_init(&hcd->kref); + + usb_bus_init(&hcd->self); + hcd->self.controller = dev; + hcd->self.bus_name = bus_name; + hcd->self.uses_dma = (dev->dma_mask != NULL); + + init_timer(&hcd->rh_timer); + hcd->rh_timer.function = rh_timer_func; + hcd->rh_timer.data = (unsigned long) hcd; +#ifdef CONFIG_PM_RUNTIME + INIT_WORK(&hcd->wakeup_work, hcd_resume_work); +#endif + + hcd->driver = driver; + hcd->speed = driver->flags & HCD_MASK; + hcd->product_desc = (driver->product_desc) ? driver->product_desc : + "USB Host Controller"; + return hcd; +} +EXPORT_SYMBOL_GPL(usb_create_shared_hcd); + +/** + * usb_create_hcd - create and initialize an HCD structure + * @driver: HC driver that will use this hcd + * @dev: device for this HC, stored in hcd->self.controller + * @bus_name: value to store in hcd->self.bus_name + * Context: !in_interrupt() + * + * Allocate a struct usb_hcd, with extra space at the end for the + * HC driver's private data. Initialize the generic members of the + * hcd structure. + * + * If memory is unavailable, returns NULL. + */ +struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, + struct device *dev, const char *bus_name) +{ + return usb_create_shared_hcd(driver, dev, bus_name, NULL); +} +EXPORT_SYMBOL_GPL(usb_create_hcd); + +/* + * Roothubs that share one PCI device must also share the bandwidth mutex. + * Don't deallocate the bandwidth_mutex until the last shared usb_hcd is + * deallocated. + * + * Make sure to only deallocate the bandwidth_mutex when the primary HCD is + * freed. When hcd_release() is called for the non-primary HCD, set the + * primary_hcd's shared_hcd pointer to null (since the non-primary HCD will be + * freed shortly). + */ +static void hcd_release (struct kref *kref) +{ + struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref); + + if (usb_hcd_is_primary_hcd(hcd)) + kfree(hcd->bandwidth_mutex); + else + hcd->shared_hcd->shared_hcd = NULL; + kfree(hcd); +} + +struct usb_hcd *usb_get_hcd (struct usb_hcd *hcd) +{ + if (hcd) + kref_get (&hcd->kref); + return hcd; +} +EXPORT_SYMBOL_GPL(usb_get_hcd); + +void usb_put_hcd (struct usb_hcd *hcd) +{ + if (hcd) + kref_put (&hcd->kref, hcd_release); +} +EXPORT_SYMBOL_GPL(usb_put_hcd); + +int usb_hcd_is_primary_hcd(struct usb_hcd *hcd) +{ + if (!hcd->primary_hcd) + return 1; + return hcd == hcd->primary_hcd; +} +EXPORT_SYMBOL_GPL(usb_hcd_is_primary_hcd); + +int usb_hcd_find_raw_port_number(struct usb_hcd *hcd, int port1) +{ + if (!hcd->driver->find_raw_port_number) + return port1; + + return hcd->driver->find_raw_port_number(hcd, port1); +} + +static int usb_hcd_request_irqs(struct usb_hcd *hcd, + unsigned int irqnum, unsigned long irqflags) +{ + int retval; + + if (hcd->driver->irq) { + + /* IRQF_DISABLED doesn't work as advertised when used together + * with IRQF_SHARED. As usb_hcd_irq() will always disable + * interrupts we can remove it here. + */ + if (irqflags & IRQF_SHARED) + irqflags &= ~IRQF_DISABLED; + + snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d", + hcd->driver->description, hcd->self.busnum); + retval = request_irq(irqnum, &usb_hcd_irq, irqflags, + hcd->irq_descr, hcd); + if (retval != 0) { + dev_err(hcd->self.controller, + "request interrupt %d failed\n", + irqnum); + return retval; + } + hcd->irq = irqnum; + dev_info(hcd->self.controller, "irq %d, %s 0x%08llx\n", irqnum, + (hcd->driver->flags & HCD_MEMORY) ? + "io mem" : "io base", + (unsigned long long)hcd->rsrc_start); + } else { + hcd->irq = 0; + if (hcd->rsrc_start) + dev_info(hcd->self.controller, "%s 0x%08llx\n", + (hcd->driver->flags & HCD_MEMORY) ? + "io mem" : "io base", + (unsigned long long)hcd->rsrc_start); + } + return 0; +} + +/** + * usb_add_hcd - finish generic HCD structure initialization and register + * @hcd: the usb_hcd structure to initialize + * @irqnum: Interrupt line to allocate + * @irqflags: Interrupt type flags + * + * Finish the remaining parts of generic HCD initialization: allocate the + * buffers of consistent memory, register the bus, request the IRQ line, + * and call the driver's reset() and start() routines. + */ +int usb_add_hcd(struct usb_hcd *hcd, + unsigned int irqnum, unsigned long irqflags) +{ + int retval; + struct usb_device *rhdev; + + dev_info(hcd->self.controller, "%s\n", hcd->product_desc); + + /* Keep old behaviour if authorized_default is not in [0, 1]. */ + if (authorized_default < 0 || authorized_default > 1) + hcd->authorized_default = hcd->wireless? 0 : 1; + else + hcd->authorized_default = authorized_default; + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + /* HC is in reset state, but accessible. Now do the one-time init, + * bottom up so that hcds can customize the root hubs before khubd + * starts talking to them. (Note, bus id is assigned early too.) + */ + if ((retval = hcd_buffer_create(hcd)) != 0) { + dev_dbg(hcd->self.controller, "pool alloc failed\n"); + return retval; + } + + if ((retval = usb_register_bus(&hcd->self)) < 0) + goto err_register_bus; + + if ((rhdev = usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) { + dev_err(hcd->self.controller, "unable to allocate root hub\n"); + retval = -ENOMEM; + goto err_allocate_root_hub; + } + hcd->self.root_hub = rhdev; + + switch (hcd->speed) { + case HCD_USB11: + rhdev->speed = USB_SPEED_FULL; + break; + case HCD_USB2: + rhdev->speed = USB_SPEED_HIGH; + break; + case HCD_USB3: + rhdev->speed = USB_SPEED_SUPER; + break; + default: + retval = -EINVAL; + goto err_set_rh_speed; + } + + /* wakeup flag init defaults to "everything works" for root hubs, + * but drivers can override it in reset() if needed, along with + * recording the overall controller's system wakeup capability. + */ + device_set_wakeup_capable(&rhdev->dev, 1); + + /* HCD_FLAG_RH_RUNNING doesn't matter until the root hub is + * registered. But since the controller can die at any time, + * let's initialize the flag before touching the hardware. + */ + set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags); + + /* "reset" is misnamed; its role is now one-time init. the controller + * should already have been reset (and boot firmware kicked off etc). + */ + if (hcd->driver->reset && (retval = hcd->driver->reset(hcd)) < 0) { + dev_err(hcd->self.controller, "can't setup\n"); + goto err_hcd_driver_setup; + } + hcd->rh_pollable = 1; + + /* NOTE: root hub and controller capabilities may not be the same */ + if (device_can_wakeup(hcd->self.controller) + && device_can_wakeup(&hcd->self.root_hub->dev)) + dev_dbg(hcd->self.controller, "supports USB remote wakeup\n"); + + /* enable irqs just before we start the controller, + * if the BIOS provides legacy PCI irqs. + */ + if (usb_hcd_is_primary_hcd(hcd) && irqnum) { + retval = usb_hcd_request_irqs(hcd, irqnum, irqflags); + if (retval) + goto err_request_irq; + } + + hcd->state = HC_STATE_RUNNING; + retval = hcd->driver->start(hcd); + if (retval < 0) { + dev_err(hcd->self.controller, "startup error %d\n", retval); + goto err_hcd_driver_start; + } + + /* starting here, usbcore will pay attention to this root hub */ + if ((retval = register_root_hub(hcd)) != 0) + goto err_register_root_hub; + + retval = sysfs_create_group(&rhdev->dev.kobj, &usb_bus_attr_group); + if (retval < 0) { + printk(KERN_ERR "Cannot register USB bus sysfs attributes: %d\n", + retval); + goto error_create_attr_group; + } + if (hcd->uses_new_polling && HCD_POLL_RH(hcd)) + usb_hcd_poll_rh_status(hcd); + + /* + * Host controllers don't generate their own wakeup requests; + * they only forward requests from the root hub. Therefore + * controllers should always be enabled for remote wakeup. + */ + device_wakeup_enable(hcd->self.controller); + return retval; + +error_create_attr_group: + clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags); + if (HC_IS_RUNNING(hcd->state)) + hcd->state = HC_STATE_QUIESCING; + spin_lock_irq(&hcd_root_hub_lock); + hcd->rh_registered = 0; + spin_unlock_irq(&hcd_root_hub_lock); + +#ifdef CONFIG_PM_RUNTIME + cancel_work_sync(&hcd->wakeup_work); +#endif + mutex_lock(&usb_bus_list_lock); + usb_disconnect(&rhdev); /* Sets rhdev to NULL */ + mutex_unlock(&usb_bus_list_lock); +err_register_root_hub: + hcd->rh_pollable = 0; + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + del_timer_sync(&hcd->rh_timer); + hcd->driver->stop(hcd); + hcd->state = HC_STATE_HALT; + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + del_timer_sync(&hcd->rh_timer); +err_hcd_driver_start: + if (usb_hcd_is_primary_hcd(hcd) && hcd->irq > 0) + free_irq(irqnum, hcd); +err_request_irq: +err_hcd_driver_setup: +err_set_rh_speed: + usb_put_dev(hcd->self.root_hub); +err_allocate_root_hub: + usb_deregister_bus(&hcd->self); +err_register_bus: + hcd_buffer_destroy(hcd); + return retval; +} +EXPORT_SYMBOL_GPL(usb_add_hcd); + +/** + * usb_remove_hcd - shutdown processing for generic HCDs + * @hcd: the usb_hcd structure to remove + * Context: !in_interrupt() + * + * Disconnects the root hub, then reverses the effects of usb_add_hcd(), + * invoking the HCD's stop() method. + */ +void usb_remove_hcd(struct usb_hcd *hcd) +{ + struct usb_device *rhdev = hcd->self.root_hub; + + dev_info(hcd->self.controller, "remove, state %x\n", hcd->state); + + usb_get_dev(rhdev); + sysfs_remove_group(&rhdev->dev.kobj, &usb_bus_attr_group); + + clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags); + if (HC_IS_RUNNING (hcd->state)) + hcd->state = HC_STATE_QUIESCING; + + dev_dbg(hcd->self.controller, "roothub graceful disconnect\n"); + spin_lock_irq (&hcd_root_hub_lock); + hcd->rh_registered = 0; + spin_unlock_irq (&hcd_root_hub_lock); + +#ifdef CONFIG_PM_RUNTIME + cancel_work_sync(&hcd->wakeup_work); +#endif + + mutex_lock(&usb_bus_list_lock); + usb_disconnect(&rhdev); /* Sets rhdev to NULL */ + mutex_unlock(&usb_bus_list_lock); + + /* Prevent any more root-hub status calls from the timer. + * The HCD might still restart the timer (if a port status change + * interrupt occurs), but usb_hcd_poll_rh_status() won't invoke + * the hub_status_data() callback. + */ + hcd->rh_pollable = 0; + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + del_timer_sync(&hcd->rh_timer); + + hcd->driver->stop(hcd); + hcd->state = HC_STATE_HALT; + + /* In case the HCD restarted the timer, stop it again. */ + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + del_timer_sync(&hcd->rh_timer); + + if (usb_hcd_is_primary_hcd(hcd)) { + if (hcd->irq > 0) + free_irq(hcd->irq, hcd); + } + + usb_put_dev(hcd->self.root_hub); + usb_deregister_bus(&hcd->self); + hcd_buffer_destroy(hcd); +} +EXPORT_SYMBOL_GPL(usb_remove_hcd); + +void +usb_hcd_platform_shutdown(struct platform_device* dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} +EXPORT_SYMBOL_GPL(usb_hcd_platform_shutdown); + +/*-------------------------------------------------------------------------*/ + +#if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE) + +struct usb_mon_operations *mon_ops; + +/* + * The registration is unlocked. + * We do it this way because we do not want to lock in hot paths. + * + * Notice that the code is minimally error-proof. Because usbmon needs + * symbols from usbcore, usbcore gets referenced and cannot be unloaded first. + */ + +int usb_mon_register (struct usb_mon_operations *ops) +{ + + if (mon_ops) + return -EBUSY; + + mon_ops = ops; + mb(); + return 0; +} +EXPORT_SYMBOL_GPL (usb_mon_register); + +void usb_mon_deregister (void) +{ + + if (mon_ops == NULL) { + printk(KERN_ERR "USB: monitor was not registered\n"); + return; + } + mon_ops = NULL; + mb(); +} +EXPORT_SYMBOL_GPL (usb_mon_deregister); + +#endif /* CONFIG_USB_MON || CONFIG_USB_MON_MODULE */ diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c new file mode 100644 index 00000000..aa970337 --- /dev/null +++ b/drivers/usb/core/hub.c @@ -0,0 +1,5510 @@ +/* + * USB hub driver. + * + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 1999 Johannes Erdfelt + * (C) Copyright 1999 Gregory P. Smith + * (C) Copyright 2001 Brad Hards (bhards@bigpond.net.au) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "hub.h" + +/* if we are in debug mode, always announce new devices */ +#ifdef DEBUG +#ifndef CONFIG_USB_ANNOUNCE_NEW_DEVICES +#define CONFIG_USB_ANNOUNCE_NEW_DEVICES +#endif +#endif + +#define USB_VENDOR_GENESYS_LOGIC 0x05e3 +#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01 + +static inline int hub_is_superspeed(struct usb_device *hdev) +{ + return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS); +} + +/* Protect struct usb_device->state and ->children members + * Note: Both are also protected by ->dev.sem, except that ->state can + * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */ +static DEFINE_SPINLOCK(device_state_lock); + +/* khubd's worklist and its lock */ +static DEFINE_SPINLOCK(hub_event_lock); +static LIST_HEAD(hub_event_list); /* List of hubs needing servicing */ + +/* Wakes up khubd */ +static DECLARE_WAIT_QUEUE_HEAD(khubd_wait); + +static struct task_struct *khubd_task; + +/* cycle leds on hubs that aren't blinking for attention */ +static bool blinkenlights = 0; +module_param (blinkenlights, bool, S_IRUGO); +MODULE_PARM_DESC (blinkenlights, "true to cycle leds on hubs"); + +/* + * Device SATA8000 FW1.0 from DATAST0R Technology Corp requires about + * 10 seconds to send reply for the initial 64-byte descriptor request. + */ +/* define initial 64-byte descriptor request timeout in milliseconds */ +static int initial_descriptor_timeout = USB_CTRL_GET_TIMEOUT; +module_param(initial_descriptor_timeout, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(initial_descriptor_timeout, + "initial 64-byte descriptor request timeout in milliseconds " + "(default 5000 - 5.0 seconds)"); + +/* + * As of 2.6.10 we introduce a new USB device initialization scheme which + * closely resembles the way Windows works. Hopefully it will be compatible + * with a wider range of devices than the old scheme. However some previously + * working devices may start giving rise to "device not accepting address" + * errors; if that happens the user can try the old scheme by adjusting the + * following module parameters. + * + * For maximum flexibility there are two boolean parameters to control the + * hub driver's behavior. On the first initialization attempt, if the + * "old_scheme_first" parameter is set then the old scheme will be used, + * otherwise the new scheme is used. If that fails and "use_both_schemes" + * is set, then the driver will make another attempt, using the other scheme. + */ +static bool old_scheme_first = 0; +module_param(old_scheme_first, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(old_scheme_first, + "start with the old device initialization scheme"); + +static bool use_both_schemes = 1; +module_param(use_both_schemes, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(use_both_schemes, + "try the other device initialization scheme if the " + "first one fails"); + +/* Mutual exclusion for EHCI CF initialization. This interferes with + * port reset on some companion controllers. + */ +DECLARE_RWSEM(ehci_cf_port_reset_rwsem); +EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem); + +#define HUB_DEBOUNCE_TIMEOUT 2000 +#define HUB_DEBOUNCE_STEP 25 +#define HUB_DEBOUNCE_STABLE 100 + +static int usb_reset_and_verify_device(struct usb_device *udev); + +static inline char *portspeed(struct usb_hub *hub, int portstatus) +{ + if (hub_is_superspeed(hub->hdev)) + return "5.0 Gb/s"; + if (portstatus & USB_PORT_STAT_HIGH_SPEED) + return "480 Mb/s"; + else if (portstatus & USB_PORT_STAT_LOW_SPEED) + return "1.5 Mb/s"; + else + return "12 Mb/s"; +} + +/* Note that hdev or one of its children must be locked! */ +struct usb_hub *usb_hub_to_struct_hub(struct usb_device *hdev) +{ + if (!hdev || !hdev->actconfig || !hdev->maxchild) + return NULL; + return usb_get_intfdata(hdev->actconfig->interface[0]); +} + +static int usb_device_supports_lpm(struct usb_device *udev) +{ + /* USB 2.1 (and greater) devices indicate LPM support through + * their USB 2.0 Extended Capabilities BOS descriptor. + */ + if (udev->speed == USB_SPEED_HIGH) { + if (udev->bos->ext_cap && + (USB_LPM_SUPPORT & + le32_to_cpu(udev->bos->ext_cap->bmAttributes))) + return 1; + return 0; + } + + /* All USB 3.0 must support LPM, but we need their max exit latency + * information from the SuperSpeed Extended Capabilities BOS descriptor. + */ + if (!udev->bos->ss_cap) { + dev_warn(&udev->dev, "No LPM exit latency info found. " + "Power management will be impacted.\n"); + return 0; + } + if (udev->parent->lpm_capable) + return 1; + + dev_warn(&udev->dev, "Parent hub missing LPM exit latency info. " + "Power management will be impacted.\n"); + return 0; +} + +/* + * Set the Maximum Exit Latency (MEL) for the host to initiate a transition from + * either U1 or U2. + */ +static void usb_set_lpm_mel(struct usb_device *udev, + struct usb3_lpm_parameters *udev_lpm_params, + unsigned int udev_exit_latency, + struct usb_hub *hub, + struct usb3_lpm_parameters *hub_lpm_params, + unsigned int hub_exit_latency) +{ + unsigned int total_mel; + unsigned int device_mel; + unsigned int hub_mel; + + /* + * Calculate the time it takes to transition all links from the roothub + * to the parent hub into U0. The parent hub must then decode the + * packet (hub header decode latency) to figure out which port it was + * bound for. + * + * The Hub Header decode latency is expressed in 0.1us intervals (0x1 + * means 0.1us). Multiply that by 100 to get nanoseconds. + */ + total_mel = hub_lpm_params->mel + + (hub->descriptor->u.ss.bHubHdrDecLat * 100); + + /* + * How long will it take to transition the downstream hub's port into + * U0? The greater of either the hub exit latency or the device exit + * latency. + * + * The BOS U1/U2 exit latencies are expressed in 1us intervals. + * Multiply that by 1000 to get nanoseconds. + */ + device_mel = udev_exit_latency * 1000; + hub_mel = hub_exit_latency * 1000; + if (device_mel > hub_mel) + total_mel += device_mel; + else + total_mel += hub_mel; + + udev_lpm_params->mel = total_mel; +} + +/* + * Set the maximum Device to Host Exit Latency (PEL) for the device to initiate + * a transition from either U1 or U2. + */ +static void usb_set_lpm_pel(struct usb_device *udev, + struct usb3_lpm_parameters *udev_lpm_params, + unsigned int udev_exit_latency, + struct usb_hub *hub, + struct usb3_lpm_parameters *hub_lpm_params, + unsigned int hub_exit_latency, + unsigned int port_to_port_exit_latency) +{ + unsigned int first_link_pel; + unsigned int hub_pel; + + /* + * First, the device sends an LFPS to transition the link between the + * device and the parent hub into U0. The exit latency is the bigger of + * the device exit latency or the hub exit latency. + */ + if (udev_exit_latency > hub_exit_latency) + first_link_pel = udev_exit_latency * 1000; + else + first_link_pel = hub_exit_latency * 1000; + + /* + * When the hub starts to receive the LFPS, there is a slight delay for + * it to figure out that one of the ports is sending an LFPS. Then it + * will forward the LFPS to its upstream link. The exit latency is the + * delay, plus the PEL that we calculated for this hub. + */ + hub_pel = port_to_port_exit_latency * 1000 + hub_lpm_params->pel; + + /* + * According to figure C-7 in the USB 3.0 spec, the PEL for this device + * is the greater of the two exit latencies. + */ + if (first_link_pel > hub_pel) + udev_lpm_params->pel = first_link_pel; + else + udev_lpm_params->pel = hub_pel; +} + +/* + * Set the System Exit Latency (SEL) to indicate the total worst-case time from + * when a device initiates a transition to U0, until when it will receive the + * first packet from the host controller. + * + * Section C.1.5.1 describes the four components to this: + * - t1: device PEL + * - t2: time for the ERDY to make it from the device to the host. + * - t3: a host-specific delay to process the ERDY. + * - t4: time for the packet to make it from the host to the device. + * + * t3 is specific to both the xHCI host and the platform the host is integrated + * into. The Intel HW folks have said it's negligible, FIXME if a different + * vendor says otherwise. + */ +static void usb_set_lpm_sel(struct usb_device *udev, + struct usb3_lpm_parameters *udev_lpm_params) +{ + struct usb_device *parent; + unsigned int num_hubs; + unsigned int total_sel; + + /* t1 = device PEL */ + total_sel = udev_lpm_params->pel; + /* How many external hubs are in between the device & the root port. */ + for (parent = udev->parent, num_hubs = 0; parent->parent; + parent = parent->parent) + num_hubs++; + /* t2 = 2.1us + 250ns * (num_hubs - 1) */ + if (num_hubs > 0) + total_sel += 2100 + 250 * (num_hubs - 1); + + /* t4 = 250ns * num_hubs */ + total_sel += 250 * num_hubs; + + udev_lpm_params->sel = total_sel; +} + +static void usb_set_lpm_parameters(struct usb_device *udev) +{ + struct usb_hub *hub; + unsigned int port_to_port_delay; + unsigned int udev_u1_del; + unsigned int udev_u2_del; + unsigned int hub_u1_del; + unsigned int hub_u2_del; + + if (!udev->lpm_capable || udev->speed != USB_SPEED_SUPER) + return; + + hub = usb_hub_to_struct_hub(udev->parent); + /* It doesn't take time to transition the roothub into U0, since it + * doesn't have an upstream link. + */ + if (!hub) + return; + + udev_u1_del = udev->bos->ss_cap->bU1devExitLat; + udev_u2_del = udev->bos->ss_cap->bU2DevExitLat; + hub_u1_del = udev->parent->bos->ss_cap->bU1devExitLat; + hub_u2_del = udev->parent->bos->ss_cap->bU2DevExitLat; + + usb_set_lpm_mel(udev, &udev->u1_params, udev_u1_del, + hub, &udev->parent->u1_params, hub_u1_del); + + usb_set_lpm_mel(udev, &udev->u2_params, udev_u2_del, + hub, &udev->parent->u2_params, hub_u2_del); + + /* + * Appendix C, section C.2.2.2, says that there is a slight delay from + * when the parent hub notices the downstream port is trying to + * transition to U0 to when the hub initiates a U0 transition on its + * upstream port. The section says the delays are tPort2PortU1EL and + * tPort2PortU2EL, but it doesn't define what they are. + * + * The hub chapter, sections 10.4.2.4 and 10.4.2.5 seem to be talking + * about the same delays. Use the maximum delay calculations from those + * sections. For U1, it's tHubPort2PortExitLat, which is 1us max. For + * U2, it's tHubPort2PortExitLat + U2DevExitLat - U1DevExitLat. I + * assume the device exit latencies they are talking about are the hub + * exit latencies. + * + * What do we do if the U2 exit latency is less than the U1 exit + * latency? It's possible, although not likely... + */ + port_to_port_delay = 1; + + usb_set_lpm_pel(udev, &udev->u1_params, udev_u1_del, + hub, &udev->parent->u1_params, hub_u1_del, + port_to_port_delay); + + if (hub_u2_del > hub_u1_del) + port_to_port_delay = 1 + hub_u2_del - hub_u1_del; + else + port_to_port_delay = 1 + hub_u1_del; + + usb_set_lpm_pel(udev, &udev->u2_params, udev_u2_del, + hub, &udev->parent->u2_params, hub_u2_del, + port_to_port_delay); + + /* Now that we've got PEL, calculate SEL. */ + usb_set_lpm_sel(udev, &udev->u1_params); + usb_set_lpm_sel(udev, &udev->u2_params); +} + +/* USB 2.0 spec Section 11.24.4.5 */ +static int get_hub_descriptor(struct usb_device *hdev, void *data) +{ + int i, ret, size; + unsigned dtype; + + if (hub_is_superspeed(hdev)) { + dtype = USB_DT_SS_HUB; + size = USB_DT_SS_HUB_SIZE; + } else { + dtype = USB_DT_HUB; + size = sizeof(struct usb_hub_descriptor); + } + + for (i = 0; i < 3; i++) { + ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, + dtype << 8, 0, data, size, + USB_CTRL_GET_TIMEOUT); + if (ret >= (USB_DT_HUB_NONVAR_SIZE + 2)) + return ret; + } + return -EINVAL; +} + +/* + * USB 2.0 spec Section 11.24.2.1 + */ +static int clear_hub_feature(struct usb_device *hdev, int feature) +{ + return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_CLEAR_FEATURE, USB_RT_HUB, feature, 0, NULL, 0, 1000); +} + +/* + * USB 2.0 spec Section 11.24.2.2 + */ +int usb_clear_port_feature(struct usb_device *hdev, int port1, int feature) +{ + return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1, + NULL, 0, 1000); +} + +/* + * USB 2.0 spec Section 11.24.2.13 + */ +static int set_port_feature(struct usb_device *hdev, int port1, int feature) +{ + return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, + NULL, 0, 1000); +} + +/* + * USB 2.0 spec Section 11.24.2.7.1.10 and table 11-7 + * for info about using port indicators + */ +static void set_port_led( + struct usb_hub *hub, + int port1, + int selector +) +{ + int status = set_port_feature(hub->hdev, (selector << 8) | port1, + USB_PORT_FEAT_INDICATOR); + if (status < 0) + dev_dbg (hub->intfdev, + "port %d indicator %s status %d\n", + port1, + ({ char *s; switch (selector) { + case HUB_LED_AMBER: s = "amber"; break; + case HUB_LED_GREEN: s = "green"; break; + case HUB_LED_OFF: s = "off"; break; + case HUB_LED_AUTO: s = "auto"; break; + default: s = "??"; break; + }; s; }), + status); +} + +#define LED_CYCLE_PERIOD ((2*HZ)/3) + +static void led_work (struct work_struct *work) +{ + struct usb_hub *hub = + container_of(work, struct usb_hub, leds.work); + struct usb_device *hdev = hub->hdev; + unsigned i; + unsigned changed = 0; + int cursor = -1; + + if (hdev->state != USB_STATE_CONFIGURED || hub->quiescing) + return; + + for (i = 0; i < hub->descriptor->bNbrPorts; i++) { + unsigned selector, mode; + + /* 30%-50% duty cycle */ + + switch (hub->indicator[i]) { + /* cycle marker */ + case INDICATOR_CYCLE: + cursor = i; + selector = HUB_LED_AUTO; + mode = INDICATOR_AUTO; + break; + /* blinking green = sw attention */ + case INDICATOR_GREEN_BLINK: + selector = HUB_LED_GREEN; + mode = INDICATOR_GREEN_BLINK_OFF; + break; + case INDICATOR_GREEN_BLINK_OFF: + selector = HUB_LED_OFF; + mode = INDICATOR_GREEN_BLINK; + break; + /* blinking amber = hw attention */ + case INDICATOR_AMBER_BLINK: + selector = HUB_LED_AMBER; + mode = INDICATOR_AMBER_BLINK_OFF; + break; + case INDICATOR_AMBER_BLINK_OFF: + selector = HUB_LED_OFF; + mode = INDICATOR_AMBER_BLINK; + break; + /* blink green/amber = reserved */ + case INDICATOR_ALT_BLINK: + selector = HUB_LED_GREEN; + mode = INDICATOR_ALT_BLINK_OFF; + break; + case INDICATOR_ALT_BLINK_OFF: + selector = HUB_LED_AMBER; + mode = INDICATOR_ALT_BLINK; + break; + default: + continue; + } + if (selector != HUB_LED_AUTO) + changed = 1; + set_port_led(hub, i + 1, selector); + hub->indicator[i] = mode; + } + if (!changed && blinkenlights) { + cursor++; + cursor %= hub->descriptor->bNbrPorts; + set_port_led(hub, cursor + 1, HUB_LED_GREEN); + hub->indicator[cursor] = INDICATOR_CYCLE; + changed++; + } + if (changed) + schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); +} + +/* use a short timeout for hub/port status fetches */ +#define USB_STS_TIMEOUT 1000 +#define USB_STS_RETRIES 5 + +/* + * USB 2.0 spec Section 11.24.2.6 + */ +static int get_hub_status(struct usb_device *hdev, + struct usb_hub_status *data) +{ + int i, status = -ETIMEDOUT; + + for (i = 0; i < USB_STS_RETRIES && + (status == -ETIMEDOUT || status == -EPIPE); i++) { + status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0, + data, sizeof(*data), USB_STS_TIMEOUT); + } + return status; +} + +/* + * USB 2.0 spec Section 11.24.2.7 + */ +static int get_port_status(struct usb_device *hdev, int port1, + struct usb_port_status *data) +{ + int i, status = -ETIMEDOUT; + + for (i = 0; i < USB_STS_RETRIES && + (status == -ETIMEDOUT || status == -EPIPE); i++) { + status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1, + data, sizeof(*data), USB_STS_TIMEOUT); + } + return status; +} + +static int hub_port_status(struct usb_hub *hub, int port1, + u16 *status, u16 *change) +{ + int ret; + + mutex_lock(&hub->status_mutex); + ret = get_port_status(hub->hdev, port1, &hub->status->port); + if (ret < 4) { + if (ret != -ENODEV) + dev_err(hub->intfdev, + "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; + } else { + *status = le16_to_cpu(hub->status->port.wPortStatus); + *change = le16_to_cpu(hub->status->port.wPortChange); + + ret = 0; + } + mutex_unlock(&hub->status_mutex); + return ret; +} + +static void kick_khubd(struct usb_hub *hub) +{ + unsigned long flags; + + spin_lock_irqsave(&hub_event_lock, flags); + if (!hub->disconnected && list_empty(&hub->event_list)) { + list_add_tail(&hub->event_list, &hub_event_list); + + /* Suppress autosuspend until khubd runs */ + usb_autopm_get_interface_no_resume( + to_usb_interface(hub->intfdev)); + wake_up(&khubd_wait); + } + spin_unlock_irqrestore(&hub_event_lock, flags); +} + +void usb_kick_khubd(struct usb_device *hdev) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + if (hub) + kick_khubd(hub); +} + +/* + * Let the USB core know that a USB 3.0 device has sent a Function Wake Device + * Notification, which indicates it had initiated remote wakeup. + * + * USB 3.0 hubs do not report the port link state change from U3 to U0 when the + * device initiates resume, so the USB core will not receive notice of the + * resume through the normal hub interrupt URB. + */ +void usb_wakeup_notification(struct usb_device *hdev, + unsigned int portnum) +{ + struct usb_hub *hub; + + if (!hdev) + return; + + hub = usb_hub_to_struct_hub(hdev); + if (hub) { + set_bit(portnum, hub->wakeup_bits); + kick_khubd(hub); + } +} +EXPORT_SYMBOL_GPL(usb_wakeup_notification); + +/* completion function, fires on port status changes and various faults */ +static void hub_irq(struct urb *urb) +{ + struct usb_hub *hub = urb->context; + int status = urb->status; + unsigned i; + unsigned long bits; + + switch (status) { + case -ENOENT: /* synchronous unlink */ + case -ECONNRESET: /* async unlink */ + case -ESHUTDOWN: /* hardware going away */ + return; + + default: /* presumably an error */ + /* Cause a hub reset after 10 consecutive errors */ + dev_dbg (hub->intfdev, "transfer --> %d\n", status); + if ((++hub->nerrors < 10) || hub->error) + goto resubmit; + hub->error = status; + /* FALL THROUGH */ + + /* let khubd handle things */ + case 0: /* we got data: port status changed */ + bits = 0; + for (i = 0; i < urb->actual_length; ++i) + bits |= ((unsigned long) ((*hub->buffer)[i])) + << (i*8); + hub->event_bits[0] = bits; + break; + } + + hub->nerrors = 0; + + /* Something happened, let khubd figure it out */ + kick_khubd(hub); + +resubmit: + if (hub->quiescing) + return; + + if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0 + && status != -ENODEV && status != -EPERM) + dev_err (hub->intfdev, "resubmit --> %d\n", status); +} + +/* USB 2.0 spec Section 11.24.2.3 */ +static inline int +hub_clear_tt_buffer (struct usb_device *hdev, u16 devinfo, u16 tt) +{ + /* Need to clear both directions for control ep */ + if (((devinfo >> 11) & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_CONTROL) { + int status = usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + HUB_CLEAR_TT_BUFFER, USB_RT_PORT, + devinfo ^ 0x8000, tt, NULL, 0, 1000); + if (status) + return status; + } + return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + HUB_CLEAR_TT_BUFFER, USB_RT_PORT, devinfo, + tt, NULL, 0, 1000); +} + +/* + * enumeration blocks khubd for a long time. we use keventd instead, since + * long blocking there is the exception, not the rule. accordingly, HCDs + * talking to TTs must queue control transfers (not just bulk and iso), so + * both can talk to the same hub concurrently. + */ +static void hub_tt_work(struct work_struct *work) +{ + struct usb_hub *hub = + container_of(work, struct usb_hub, tt.clear_work); + unsigned long flags; + + spin_lock_irqsave (&hub->tt.lock, flags); + while (!list_empty(&hub->tt.clear_list)) { + struct list_head *next; + struct usb_tt_clear *clear; + struct usb_device *hdev = hub->hdev; + const struct hc_driver *drv; + int status; + + next = hub->tt.clear_list.next; + clear = list_entry (next, struct usb_tt_clear, clear_list); + list_del (&clear->clear_list); + + /* drop lock so HCD can concurrently report other TT errors */ + spin_unlock_irqrestore (&hub->tt.lock, flags); + status = hub_clear_tt_buffer (hdev, clear->devinfo, clear->tt); + if (status && status != -ENODEV) + dev_err (&hdev->dev, + "clear tt %d (%04x) error %d\n", + clear->tt, clear->devinfo, status); + + /* Tell the HCD, even if the operation failed */ + drv = clear->hcd->driver; + if (drv->clear_tt_buffer_complete) + (drv->clear_tt_buffer_complete)(clear->hcd, clear->ep); + + kfree(clear); + spin_lock_irqsave(&hub->tt.lock, flags); + } + spin_unlock_irqrestore (&hub->tt.lock, flags); +} + +/** + * usb_hub_set_port_power - control hub port's power state + * @hdev: target hub + * @port1: port index + * @set: expected status + * + * call this function to control port's power via setting or + * clearing the port's PORT_POWER feature. + */ +int usb_hub_set_port_power(struct usb_device *hdev, int port1, + bool set) +{ + int ret; + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_port *port_dev = hub->ports[port1 - 1]; + + if (set) + ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); + else + ret = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER); + + if (!ret) + port_dev->power_is_on = set; + return ret; +} + +/** + * usb_hub_clear_tt_buffer - clear control/bulk TT state in high speed hub + * @urb: an URB associated with the failed or incomplete split transaction + * + * High speed HCDs use this to tell the hub driver that some split control or + * bulk transaction failed in a way that requires clearing internal state of + * a transaction translator. This is normally detected (and reported) from + * interrupt context. + * + * It may not be possible for that hub to handle additional full (or low) + * speed transactions until that state is fully cleared out. + */ +int usb_hub_clear_tt_buffer(struct urb *urb) +{ + struct usb_device *udev = urb->dev; + int pipe = urb->pipe; + struct usb_tt *tt = udev->tt; + unsigned long flags; + struct usb_tt_clear *clear; + + /* we've got to cope with an arbitrary number of pending TT clears, + * since each TT has "at least two" buffers that can need it (and + * there can be many TTs per hub). even if they're uncommon. + */ + if ((clear = kmalloc (sizeof *clear, GFP_ATOMIC)) == NULL) { + dev_err (&udev->dev, "can't save CLEAR_TT_BUFFER state\n"); + /* FIXME recover somehow ... RESET_TT? */ + return -ENOMEM; + } + + /* info that CLEAR_TT_BUFFER needs */ + clear->tt = tt->multi ? udev->ttport : 1; + clear->devinfo = usb_pipeendpoint (pipe); + clear->devinfo |= udev->devnum << 4; + clear->devinfo |= usb_pipecontrol (pipe) + ? (USB_ENDPOINT_XFER_CONTROL << 11) + : (USB_ENDPOINT_XFER_BULK << 11); + if (usb_pipein (pipe)) + clear->devinfo |= 1 << 15; + + /* info for completion callback */ + clear->hcd = bus_to_hcd(udev->bus); + clear->ep = urb->ep; + + /* tell keventd to clear state for this TT */ + spin_lock_irqsave (&tt->lock, flags); + list_add_tail (&clear->clear_list, &tt->clear_list); + schedule_work(&tt->clear_work); + spin_unlock_irqrestore (&tt->lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(usb_hub_clear_tt_buffer); + +/* If do_delay is false, return the number of milliseconds the caller + * needs to delay. + */ +static unsigned hub_power_on(struct usb_hub *hub, bool do_delay) +{ + int port1; + unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2; + unsigned delay; + u16 wHubCharacteristics = + le16_to_cpu(hub->descriptor->wHubCharacteristics); + + /* Enable power on each port. Some hubs have reserved values + * of LPSM (> 2) in their descriptors, even though they are + * USB 2.0 hubs. Some hubs do not implement port-power switching + * but only emulate it. In all cases, the ports won't work + * unless we send these messages to the hub. + */ + if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2) + dev_dbg(hub->intfdev, "enabling power on all ports\n"); + else + dev_dbg(hub->intfdev, "trying to enable port power on " + "non-switchable hub\n"); + for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++) + if (hub->ports[port1 - 1]->power_is_on) + set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); + else + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_POWER); + + /* Wait at least 100 msec for power to become stable */ + delay = max(pgood_delay, (unsigned) 100); + if (do_delay) + msleep(delay); + return delay; +} + +static int hub_hub_status(struct usb_hub *hub, + u16 *status, u16 *change) +{ + int ret; + + mutex_lock(&hub->status_mutex); + ret = get_hub_status(hub->hdev, &hub->status->hub); + if (ret < 0) { + if (ret != -ENODEV) + dev_err(hub->intfdev, + "%s failed (err = %d)\n", __func__, ret); + } else { + *status = le16_to_cpu(hub->status->hub.wHubStatus); + *change = le16_to_cpu(hub->status->hub.wHubChange); + ret = 0; + } + mutex_unlock(&hub->status_mutex); + return ret; +} + +static int hub_set_port_link_state(struct usb_hub *hub, int port1, + unsigned int link_status) +{ + return set_port_feature(hub->hdev, + port1 | (link_status << 3), + USB_PORT_FEAT_LINK_STATE); +} + +/* + * If USB 3.0 ports are placed into the Disabled state, they will no longer + * detect any device connects or disconnects. This is generally not what the + * USB core wants, since it expects a disabled port to produce a port status + * change event when a new device connects. + * + * Instead, set the link state to Disabled, wait for the link to settle into + * that state, clear any change bits, and then put the port into the RxDetect + * state. + */ +static int hub_usb3_port_disable(struct usb_hub *hub, int port1) +{ + int ret; + int total_time; + u16 portchange, portstatus; + + if (!hub_is_superspeed(hub->hdev)) + return -EINVAL; + + ret = hub_port_status(hub, port1, &portstatus, &portchange); + if (ret < 0) + return ret; + + /* + * USB controller Advanced Micro Devices, Inc. [AMD] FCH USB XHCI + * Controller [1022:7814] will have spurious result making the following + * usb 3.0 device hotplugging route to the 2.0 root hub and recognized + * as high-speed device if we set the usb 3.0 port link state to + * Disabled. Since it's already in USB_SS_PORT_LS_RX_DETECT state, we + * check the state here to avoid the bug. + */ + if ((portstatus & USB_PORT_STAT_LINK_STATE) == + USB_SS_PORT_LS_RX_DETECT) { + dev_dbg(&hub->ports[port1 - 1]->dev, + "Not disabling port; link state is RxDetect\n"); + return ret; + } + + ret = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_SS_DISABLED); + if (ret) + return ret; + + /* Wait for the link to enter the disabled state. */ + for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) { + ret = hub_port_status(hub, port1, &portstatus, &portchange); + if (ret < 0) + return ret; + + if ((portstatus & USB_PORT_STAT_LINK_STATE) == + USB_SS_PORT_LS_SS_DISABLED) + break; + if (total_time >= HUB_DEBOUNCE_TIMEOUT) + break; + msleep(HUB_DEBOUNCE_STEP); + } + if (total_time >= HUB_DEBOUNCE_TIMEOUT) + dev_warn(hub->intfdev, "Could not disable port %d after %d ms\n", + port1, total_time); + + return hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_RX_DETECT); +} + +static int hub_port_disable(struct usb_hub *hub, int port1, int set_state) +{ + struct usb_device *hdev = hub->hdev; + int ret = 0; + + if (hub->ports[port1 - 1]->child && set_state) + usb_set_device_state(hub->ports[port1 - 1]->child, + USB_STATE_NOTATTACHED); + if (!hub->error) { + if (hub_is_superspeed(hub->hdev)) + ret = hub_usb3_port_disable(hub, port1); + else + ret = usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_ENABLE); + } + if (ret && ret != -ENODEV) + dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n", + port1, ret); + return ret; +} + +/* + * Disable a port and mark a logical connect-change event, so that some + * time later khubd will disconnect() any existing usb_device on the port + * and will re-enumerate if there actually is a device attached. + */ +static void hub_port_logical_disconnect(struct usb_hub *hub, int port1) +{ + dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1); + hub_port_disable(hub, port1, 1); + + /* FIXME let caller ask to power down the port: + * - some devices won't enumerate without a VBUS power cycle + * - SRP saves power that way + * - ... new call, TBD ... + * That's easy if this hub can switch power per-port, and + * khubd reactivates the port later (timer, SRP, etc). + * Powerdown must be optional, because of reset/DFU. + */ + + set_bit(port1, hub->change_bits); + kick_khubd(hub); +} + +/** + * usb_remove_device - disable a device's port on its parent hub + * @udev: device to be disabled and removed + * Context: @udev locked, must be able to sleep. + * + * After @udev's port has been disabled, khubd is notified and it will + * see that the device has been disconnected. When the device is + * physically unplugged and something is plugged in, the events will + * be received and processed normally. + */ +int usb_remove_device(struct usb_device *udev) +{ + struct usb_hub *hub; + struct usb_interface *intf; + + if (!udev->parent) /* Can't remove a root hub */ + return -EINVAL; + hub = usb_hub_to_struct_hub(udev->parent); + intf = to_usb_interface(hub->intfdev); + + usb_autopm_get_interface(intf); + set_bit(udev->portnum, hub->removed_bits); + hub_port_logical_disconnect(hub, udev->portnum); + usb_autopm_put_interface(intf); + return 0; +} + +enum hub_activation_type { + HUB_INIT, HUB_INIT2, HUB_INIT3, /* INITs must come first */ + HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME, +}; + +static void hub_init_func2(struct work_struct *ws); +static void hub_init_func3(struct work_struct *ws); + +static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) +{ + struct usb_device *hdev = hub->hdev; + struct usb_hcd *hcd; + int ret; + int port1; + int status; + bool need_debounce_delay = false; + unsigned delay; + + /* Continue a partial initialization */ + if (type == HUB_INIT2) + goto init2; + if (type == HUB_INIT3) + goto init3; + + /* The superspeed hub except for root hub has to use Hub Depth + * value as an offset into the route string to locate the bits + * it uses to determine the downstream port number. So hub driver + * should send a set hub depth request to superspeed hub after + * the superspeed hub is set configuration in initialization or + * reset procedure. + * + * After a resume, port power should still be on. + * For any other type of activation, turn it on. + */ + if (type != HUB_RESUME) { + if (hdev->parent && hub_is_superspeed(hdev)) { + ret = usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + HUB_SET_DEPTH, USB_RT_HUB, + hdev->level - 1, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (ret < 0) + dev_err(hub->intfdev, + "set hub depth failed\n"); + } + + /* Speed up system boot by using a delayed_work for the + * hub's initial power-up delays. This is pretty awkward + * and the implementation looks like a home-brewed sort of + * setjmp/longjmp, but it saves at least 100 ms for each + * root hub (assuming usbcore is compiled into the kernel + * rather than as a module). It adds up. + * + * This can't be done for HUB_RESUME or HUB_RESET_RESUME + * because for those activation types the ports have to be + * operational when we return. In theory this could be done + * for HUB_POST_RESET, but it's easier not to. + */ + if (type == HUB_INIT) { + delay = hub_power_on(hub, false); + PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func2); + schedule_delayed_work(&hub->init_work, + msecs_to_jiffies(delay)); + + /* Suppress autosuspend until init is done */ + usb_autopm_get_interface_no_resume( + to_usb_interface(hub->intfdev)); + return; /* Continues at init2: below */ + } else if (type == HUB_RESET_RESUME) { + /* The internal host controller state for the hub device + * may be gone after a host power loss on system resume. + * Update the device's info so the HW knows it's a hub. + */ + hcd = bus_to_hcd(hdev->bus); + if (hcd->driver->update_hub_device) { + ret = hcd->driver->update_hub_device(hcd, hdev, + &hub->tt, GFP_NOIO); + if (ret < 0) { + dev_err(hub->intfdev, "Host not " + "accepting hub info " + "update.\n"); + dev_err(hub->intfdev, "LS/FS devices " + "and hubs may not work " + "under this hub\n."); + } + } + hub_power_on(hub, true); + } else { + hub_power_on(hub, true); + } + } + init2: + + /* Check each port and set hub->change_bits to let khubd know + * which ports need attention. + */ + for (port1 = 1; port1 <= hdev->maxchild; ++port1) { + struct usb_device *udev = hub->ports[port1 - 1]->child; + u16 portstatus, portchange; + + portstatus = portchange = 0; + status = hub_port_status(hub, port1, &portstatus, &portchange); + if (udev || (portstatus & USB_PORT_STAT_CONNECTION)) + dev_dbg(hub->intfdev, + "port %d: status %04x change %04x\n", + port1, portstatus, portchange); + + /* After anything other than HUB_RESUME (i.e., initialization + * or any sort of reset), every port should be disabled. + * Unconnected ports should likewise be disabled (paranoia), + * and so should ports for which we have no usb_device. + */ + if ((portstatus & USB_PORT_STAT_ENABLE) && ( + type != HUB_RESUME || + !(portstatus & USB_PORT_STAT_CONNECTION) || + !udev || + udev->state == USB_STATE_NOTATTACHED)) { + /* + * USB3 protocol ports will automatically transition + * to Enabled state when detect an USB3.0 device attach. + * Do not disable USB3 protocol ports. + */ + if (!hub_is_superspeed(hdev)) { + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_ENABLE); + portstatus &= ~USB_PORT_STAT_ENABLE; + } else { + /* Pretend that power was lost for USB3 devs */ + portstatus &= ~USB_PORT_STAT_ENABLE; + } + } + + /* Clear status-change flags; we'll debounce later */ + if (portchange & USB_PORT_STAT_C_CONNECTION) { + need_debounce_delay = true; + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_CONNECTION); + } + if (portchange & USB_PORT_STAT_C_ENABLE) { + need_debounce_delay = true; + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_ENABLE); + } + if (portchange & USB_PORT_STAT_C_RESET) { + need_debounce_delay = true; + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_RESET); + } + if ((portchange & USB_PORT_STAT_C_BH_RESET) && + hub_is_superspeed(hub->hdev)) { + need_debounce_delay = true; + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_BH_PORT_RESET); + } + /* We can forget about a "removed" device when there's a + * physical disconnect or the connect status changes. + */ + if (!(portstatus & USB_PORT_STAT_CONNECTION) || + (portchange & USB_PORT_STAT_C_CONNECTION)) + clear_bit(port1, hub->removed_bits); + + if (!udev || udev->state == USB_STATE_NOTATTACHED) { + /* Tell khubd to disconnect the device or + * check for a new connection + */ + if (udev || (portstatus & USB_PORT_STAT_CONNECTION) || + (portstatus & USB_PORT_STAT_OVERCURRENT)) + set_bit(port1, hub->change_bits); + + } else if (portstatus & USB_PORT_STAT_ENABLE) { + bool port_resumed = (portstatus & + USB_PORT_STAT_LINK_STATE) == + USB_SS_PORT_LS_U0; + /* The power session apparently survived the resume. + * If there was an overcurrent or suspend change + * (i.e., remote wakeup request), have khubd + * take care of it. Look at the port link state + * for USB 3.0 hubs, since they don't have a suspend + * change bit, and they don't set the port link change + * bit on device-initiated resume. + */ + if (portchange || (hub_is_superspeed(hub->hdev) && + port_resumed)) + set_bit(port1, hub->change_bits); + + } else if (udev->persist_enabled) { + struct usb_port *port_dev = hub->ports[port1 - 1]; + +#ifdef CONFIG_PM + udev->reset_resume = 1; +#endif + /* Don't set the change_bits when the device + * was powered off. + */ + if (port_dev->power_is_on) + set_bit(port1, hub->change_bits); + + } else { + /* The power session is gone; tell khubd */ + usb_set_device_state(udev, USB_STATE_NOTATTACHED); + set_bit(port1, hub->change_bits); + } + } + + /* If no port-status-change flags were set, we don't need any + * debouncing. If flags were set we can try to debounce the + * ports all at once right now, instead of letting khubd do them + * one at a time later on. + * + * If any port-status changes do occur during this delay, khubd + * will see them later and handle them normally. + */ + if (need_debounce_delay) { + delay = HUB_DEBOUNCE_STABLE; + + /* Don't do a long sleep inside a workqueue routine */ + if (type == HUB_INIT2) { + PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func3); + schedule_delayed_work(&hub->init_work, + msecs_to_jiffies(delay)); + return; /* Continues at init3: below */ + } else { + msleep(delay); + } + } + init3: + hub->quiescing = 0; + + status = usb_submit_urb(hub->urb, GFP_NOIO); + if (status < 0) + dev_err(hub->intfdev, "activate --> %d\n", status); + if (hub->has_indicators && blinkenlights) + schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); + + /* Scan all ports that need attention */ + kick_khubd(hub); + + /* Allow autosuspend if it was suppressed */ + if (type <= HUB_INIT3) + usb_autopm_put_interface_async(to_usb_interface(hub->intfdev)); +} + +/* Implement the continuations for the delays above */ +static void hub_init_func2(struct work_struct *ws) +{ + struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work); + + hub_activate(hub, HUB_INIT2); +} + +static void hub_init_func3(struct work_struct *ws) +{ + struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work); + + hub_activate(hub, HUB_INIT3); +} + +enum hub_quiescing_type { + HUB_DISCONNECT, HUB_PRE_RESET, HUB_SUSPEND +}; + +static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type) +{ + struct usb_device *hdev = hub->hdev; + int i; + + cancel_delayed_work_sync(&hub->init_work); + + /* khubd and related activity won't re-trigger */ + hub->quiescing = 1; + + if (type != HUB_SUSPEND) { + /* Disconnect all the children */ + for (i = 0; i < hdev->maxchild; ++i) { + if (hub->ports[i]->child) + usb_disconnect(&hub->ports[i]->child); + } + } + + /* Stop khubd and related activity */ + usb_kill_urb(hub->urb); + if (hub->has_indicators) + cancel_delayed_work_sync(&hub->leds); + if (hub->tt.hub) + flush_work(&hub->tt.clear_work); +} + +/* caller has locked the hub device */ +static int hub_pre_reset(struct usb_interface *intf) +{ + struct usb_hub *hub = usb_get_intfdata(intf); + + hub_quiesce(hub, HUB_PRE_RESET); + return 0; +} + +/* caller has locked the hub device */ +static int hub_post_reset(struct usb_interface *intf) +{ + struct usb_hub *hub = usb_get_intfdata(intf); + + hub_activate(hub, HUB_POST_RESET); + return 0; +} + +static int hub_configure(struct usb_hub *hub, + struct usb_endpoint_descriptor *endpoint) +{ + struct usb_hcd *hcd; + struct usb_device *hdev = hub->hdev; + struct device *hub_dev = hub->intfdev; + u16 hubstatus, hubchange; + u16 wHubCharacteristics; + unsigned int pipe; + int maxp, ret, i; + char *message = "out of memory"; + unsigned unit_load; + unsigned full_load; + + hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL); + if (!hub->buffer) { + ret = -ENOMEM; + goto fail; + } + + hub->status = kmalloc(sizeof(*hub->status), GFP_KERNEL); + if (!hub->status) { + ret = -ENOMEM; + goto fail; + } + mutex_init(&hub->status_mutex); + + hub->descriptor = kmalloc(sizeof(*hub->descriptor), GFP_KERNEL); + if (!hub->descriptor) { + ret = -ENOMEM; + goto fail; + } + + /* Request the entire hub descriptor. + * hub->descriptor can handle USB_MAXCHILDREN ports, + * but the hub can/will return fewer bytes here. + */ + ret = get_hub_descriptor(hdev, hub->descriptor); + if (ret < 0) { + message = "can't read hub descriptor"; + goto fail; + } else if (hub->descriptor->bNbrPorts > USB_MAXCHILDREN) { + message = "hub has too many ports!"; + ret = -ENODEV; + goto fail; + } else if (hub->descriptor->bNbrPorts == 0) { + message = "hub doesn't have any ports!"; + ret = -ENODEV; + goto fail; + } + + hdev->maxchild = hub->descriptor->bNbrPorts; + dev_info (hub_dev, "%d port%s detected\n", hdev->maxchild, + (hdev->maxchild == 1) ? "" : "s"); + + hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *), + GFP_KERNEL); + if (!hub->ports) { + ret = -ENOMEM; + goto fail; + } + + wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics); + if (hub_is_superspeed(hdev)) { + unit_load = 150; + full_load = 900; + } else { + unit_load = 100; + full_load = 500; + } + + /* FIXME for USB 3.0, skip for now */ + if ((wHubCharacteristics & HUB_CHAR_COMPOUND) && + !(hub_is_superspeed(hdev))) { + int i; + char portstr [USB_MAXCHILDREN + 1]; + + for (i = 0; i < hdev->maxchild; i++) + portstr[i] = hub->descriptor->u.hs.DeviceRemovable + [((i + 1) / 8)] & (1 << ((i + 1) % 8)) + ? 'F' : 'R'; + portstr[hdev->maxchild] = 0; + dev_dbg(hub_dev, "compound device; port removable status: %s\n", portstr); + } else + dev_dbg(hub_dev, "standalone hub\n"); + + switch (wHubCharacteristics & HUB_CHAR_LPSM) { + case HUB_CHAR_COMMON_LPSM: + dev_dbg(hub_dev, "ganged power switching\n"); + break; + case HUB_CHAR_INDV_PORT_LPSM: + dev_dbg(hub_dev, "individual port power switching\n"); + break; + case HUB_CHAR_NO_LPSM: + case HUB_CHAR_LPSM: + dev_dbg(hub_dev, "no power switching (usb 1.0)\n"); + break; + } + + switch (wHubCharacteristics & HUB_CHAR_OCPM) { + case HUB_CHAR_COMMON_OCPM: + dev_dbg(hub_dev, "global over-current protection\n"); + break; + case HUB_CHAR_INDV_PORT_OCPM: + dev_dbg(hub_dev, "individual port over-current protection\n"); + break; + case HUB_CHAR_NO_OCPM: + case HUB_CHAR_OCPM: + dev_dbg(hub_dev, "no over-current protection\n"); + break; + } + + spin_lock_init (&hub->tt.lock); + INIT_LIST_HEAD (&hub->tt.clear_list); + INIT_WORK(&hub->tt.clear_work, hub_tt_work); + switch (hdev->descriptor.bDeviceProtocol) { + case USB_HUB_PR_FS: + break; + case USB_HUB_PR_HS_SINGLE_TT: + dev_dbg(hub_dev, "Single TT\n"); + hub->tt.hub = hdev; + break; + case USB_HUB_PR_HS_MULTI_TT: + ret = usb_set_interface(hdev, 0, 1); + if (ret == 0) { + dev_dbg(hub_dev, "TT per port\n"); + hub->tt.multi = 1; + } else + dev_err(hub_dev, "Using single TT (err %d)\n", + ret); + hub->tt.hub = hdev; + break; + case USB_HUB_PR_SS: + /* USB 3.0 hubs don't have a TT */ + break; + default: + dev_dbg(hub_dev, "Unrecognized hub protocol %d\n", + hdev->descriptor.bDeviceProtocol); + break; + } + + /* Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */ + switch (wHubCharacteristics & HUB_CHAR_TTTT) { + case HUB_TTTT_8_BITS: + if (hdev->descriptor.bDeviceProtocol != 0) { + hub->tt.think_time = 666; + dev_dbg(hub_dev, "TT requires at most %d " + "FS bit times (%d ns)\n", + 8, hub->tt.think_time); + } + break; + case HUB_TTTT_16_BITS: + hub->tt.think_time = 666 * 2; + dev_dbg(hub_dev, "TT requires at most %d " + "FS bit times (%d ns)\n", + 16, hub->tt.think_time); + break; + case HUB_TTTT_24_BITS: + hub->tt.think_time = 666 * 3; + dev_dbg(hub_dev, "TT requires at most %d " + "FS bit times (%d ns)\n", + 24, hub->tt.think_time); + break; + case HUB_TTTT_32_BITS: + hub->tt.think_time = 666 * 4; + dev_dbg(hub_dev, "TT requires at most %d " + "FS bit times (%d ns)\n", + 32, hub->tt.think_time); + break; + } + + /* probe() zeroes hub->indicator[] */ + if (wHubCharacteristics & HUB_CHAR_PORTIND) { + hub->has_indicators = 1; + dev_dbg(hub_dev, "Port indicators are supported\n"); + } + + dev_dbg(hub_dev, "power on to power good time: %dms\n", + hub->descriptor->bPwrOn2PwrGood * 2); + + /* power budgeting mostly matters with bus-powered hubs, + * and battery-powered root hubs (may provide just 8 mA). + */ + ret = usb_get_status(hdev, USB_RECIP_DEVICE, 0, &hubstatus); + if (ret < 2) { + message = "can't get hub status"; + goto fail; + } + le16_to_cpus(&hubstatus); + hcd = bus_to_hcd(hdev->bus); + if (hdev == hdev->bus->root_hub) { + if (hcd->power_budget > 0) + hdev->bus_mA = hcd->power_budget; + else + hdev->bus_mA = full_load * hdev->maxchild; + if (hdev->bus_mA >= full_load) + hub->mA_per_port = full_load; + else { + hub->mA_per_port = hdev->bus_mA; + hub->limited_power = 1; + } + } else if ((hubstatus & (1 << USB_DEVICE_SELF_POWERED)) == 0) { + int remaining = hdev->bus_mA - + hub->descriptor->bHubContrCurrent; + + dev_dbg(hub_dev, "hub controller current requirement: %dmA\n", + hub->descriptor->bHubContrCurrent); + hub->limited_power = 1; + + if (remaining < hdev->maxchild * unit_load) + dev_warn(hub_dev, + "insufficient power available " + "to use all downstream ports\n"); + hub->mA_per_port = unit_load; /* 7.2.1 */ + + } else { /* Self-powered external hub */ + /* FIXME: What about battery-powered external hubs that + * provide less current per port? */ + hub->mA_per_port = full_load; + } + if (hub->mA_per_port < full_load) + dev_dbg(hub_dev, "%umA bus power budget for each child\n", + hub->mA_per_port); + + /* Update the HCD's internal representation of this hub before khubd + * starts getting port status changes for devices under the hub. + */ + if (hcd->driver->update_hub_device) { + ret = hcd->driver->update_hub_device(hcd, hdev, + &hub->tt, GFP_KERNEL); + if (ret < 0) { + message = "can't update HCD hub info"; + goto fail; + } + } + + ret = hub_hub_status(hub, &hubstatus, &hubchange); + if (ret < 0) { + message = "can't get hub status"; + goto fail; + } + + /* local power status reports aren't always correct */ + if (hdev->actconfig->desc.bmAttributes & USB_CONFIG_ATT_SELFPOWER) + dev_dbg(hub_dev, "local power source is %s\n", + (hubstatus & HUB_STATUS_LOCAL_POWER) + ? "lost (inactive)" : "good"); + + if ((wHubCharacteristics & HUB_CHAR_OCPM) == 0) + dev_dbg(hub_dev, "%sover-current condition exists\n", + (hubstatus & HUB_STATUS_OVERCURRENT) ? "" : "no "); + + /* set up the interrupt endpoint + * We use the EP's maxpacket size instead of (PORTS+1+7)/8 + * bytes as USB2.0[11.12.3] says because some hubs are known + * to send more data (and thus cause overflow). For root hubs, + * maxpktsize is defined in hcd.c's fake endpoint descriptors + * to be big enough for at least USB_MAXCHILDREN ports. */ + pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe)); + + if (maxp > sizeof(*hub->buffer)) + maxp = sizeof(*hub->buffer); + + hub->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!hub->urb) { + ret = -ENOMEM; + goto fail; + } + + usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, + hub, endpoint->bInterval); + + /* maybe cycle the hub leds */ + if (hub->has_indicators && blinkenlights) + hub->indicator [0] = INDICATOR_CYCLE; + + for (i = 0; i < hdev->maxchild; i++) { + ret = usb_hub_create_port_device(hub, i + 1); + if (ret < 0) { + dev_err(hub->intfdev, + "couldn't create port%d device.\n", i + 1); + hdev->maxchild = i; + goto fail_keep_maxchild; + } + } + + usb_hub_adjust_deviceremovable(hdev, hub->descriptor); + + hub_activate(hub, HUB_INIT); + return 0; + +fail: + hdev->maxchild = 0; +fail_keep_maxchild: + dev_err (hub_dev, "config failed, %s (err %d)\n", + message, ret); + /* hub_disconnect() frees urb and descriptor */ + return ret; +} + +static void hub_release(struct kref *kref) +{ + struct usb_hub *hub = container_of(kref, struct usb_hub, kref); + + usb_put_intf(to_usb_interface(hub->intfdev)); + kfree(hub); +} + +static unsigned highspeed_hubs; + +static void hub_disconnect(struct usb_interface *intf) +{ + struct usb_hub *hub = usb_get_intfdata(intf); + struct usb_device *hdev = interface_to_usbdev(intf); + int port1; + + /* Take the hub off the event list and don't let it be added again */ + spin_lock_irq(&hub_event_lock); + if (!list_empty(&hub->event_list)) { + list_del_init(&hub->event_list); + usb_autopm_put_interface_no_suspend(intf); + } + hub->disconnected = 1; + spin_unlock_irq(&hub_event_lock); + + /* Disconnect all children and quiesce the hub */ + hub->error = 0; + hub_quiesce(hub, HUB_DISCONNECT); + + /* Avoid races with recursively_mark_NOTATTACHED() */ + spin_lock_irq(&device_state_lock); + port1 = hdev->maxchild; + hdev->maxchild = 0; + usb_set_intfdata(intf, NULL); + spin_unlock_irq(&device_state_lock); + + for (; port1 > 0; --port1) + usb_hub_remove_port_device(hub, port1); + + if (hub->hdev->speed == USB_SPEED_HIGH) + highspeed_hubs--; + + usb_free_urb(hub->urb); + kfree(hub->ports); + kfree(hub->descriptor); + kfree(hub->status); + kfree(hub->buffer); + + pm_suspend_ignore_children(&intf->dev, false); + kref_put(&hub->kref, hub_release); +} + +static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_host_interface *desc; + struct usb_endpoint_descriptor *endpoint; + struct usb_device *hdev; + struct usb_hub *hub; + + desc = intf->cur_altsetting; + hdev = interface_to_usbdev(intf); + + /* + * Set default autosuspend delay as 0 to speedup bus suspend, + * based on the below considerations: + * + * - Unlike other drivers, the hub driver does not rely on the + * autosuspend delay to provide enough time to handle a wakeup + * event, and the submitted status URB is just to check future + * change on hub downstream ports, so it is safe to do it. + * + * - The patch might cause one or more auto supend/resume for + * below very rare devices when they are plugged into hub + * first time: + * + * devices having trouble initializing, and disconnect + * themselves from the bus and then reconnect a second + * or so later + * + * devices just for downloading firmware, and disconnects + * themselves after completing it + * + * For these quite rare devices, their drivers may change the + * autosuspend delay of their parent hub in the probe() to one + * appropriate value to avoid the subtle problem if someone + * does care it. + * + * - The patch may cause one or more auto suspend/resume on + * hub during running 'lsusb', but it is probably too + * infrequent to worry about. + * + * - Change autosuspend delay of hub can avoid unnecessary auto + * suspend timer for hub, also may decrease power consumption + * of USB bus. + * + * - If user has indicated to prevent autosuspend by passing + * usbcore.autosuspend = -1 then keep autosuspend disabled. + */ +#ifdef CONFIG_PM_RUNTIME + if (hdev->dev.power.autosuspend_delay >= 0) + pm_runtime_set_autosuspend_delay(&hdev->dev, 0); +#endif + + /* + * Hubs have proper suspend/resume support, except for root hubs + * where the controller driver doesn't have bus_suspend and + * bus_resume methods. + */ + if (hdev->parent) { /* normal device */ + usb_enable_autosuspend(hdev); + } else { /* root hub */ + const struct hc_driver *drv = bus_to_hcd(hdev->bus)->driver; + + if (drv->bus_suspend && drv->bus_resume) + usb_enable_autosuspend(hdev); + } + + if (hdev->level == MAX_TOPO_LEVEL) { + dev_err(&intf->dev, + "Unsupported bus topology: hub nested too deep\n"); + return -E2BIG; + } + +#ifdef CONFIG_USB_OTG_BLACKLIST_HUB + if (hdev->parent) { + dev_warn(&intf->dev, "ignoring external hub\n"); + return -ENODEV; + } +#endif + + /* Some hubs have a subclass of 1, which AFAICT according to the */ + /* specs is not defined, but it works */ + if ((desc->desc.bInterfaceSubClass != 0) && + (desc->desc.bInterfaceSubClass != 1)) { +descriptor_error: + dev_err (&intf->dev, "bad descriptor, ignoring hub\n"); + return -EIO; + } + + /* Multiple endpoints? What kind of mutant ninja-hub is this? */ + if (desc->desc.bNumEndpoints != 1) + goto descriptor_error; + + endpoint = &desc->endpoint[0].desc; + + /* If it's not an interrupt in endpoint, we'd better punt! */ + if (!usb_endpoint_is_int_in(endpoint)) + goto descriptor_error; + + /* We found a hub */ + dev_info (&intf->dev, "USB hub found\n"); + + hub = kzalloc(sizeof(*hub), GFP_KERNEL); + if (!hub) { + dev_dbg (&intf->dev, "couldn't kmalloc hub struct\n"); + return -ENOMEM; + } + + kref_init(&hub->kref); + INIT_LIST_HEAD(&hub->event_list); + hub->intfdev = &intf->dev; + hub->hdev = hdev; + INIT_DELAYED_WORK(&hub->leds, led_work); + INIT_DELAYED_WORK(&hub->init_work, NULL); + usb_get_intf(intf); + + usb_set_intfdata (intf, hub); + intf->needs_remote_wakeup = 1; + pm_suspend_ignore_children(&intf->dev, true); + + if (hdev->speed == USB_SPEED_HIGH) + highspeed_hubs++; + + if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND) + hub->quirk_check_port_auto_suspend = 1; + + if (hub_configure(hub, endpoint) >= 0) + return 0; + + hub_disconnect (intf); + return -ENODEV; +} + +static int +hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data) +{ + struct usb_device *hdev = interface_to_usbdev (intf); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + /* assert ifno == 0 (part of hub spec) */ + switch (code) { + case USBDEVFS_HUB_PORTINFO: { + struct usbdevfs_hub_portinfo *info = user_data; + int i; + + spin_lock_irq(&device_state_lock); + if (hdev->devnum <= 0) + info->nports = 0; + else { + info->nports = hdev->maxchild; + for (i = 0; i < info->nports; i++) { + if (hub->ports[i]->child == NULL) + info->port[i] = 0; + else + info->port[i] = + hub->ports[i]->child->devnum; + } + } + spin_unlock_irq(&device_state_lock); + + return info->nports + 1; + } + + default: + return -ENOSYS; + } +} + +/* + * Allow user programs to claim ports on a hub. When a device is attached + * to one of these "claimed" ports, the program will "own" the device. + */ +static int find_port_owner(struct usb_device *hdev, unsigned port1, + struct dev_state ***ppowner) +{ + if (hdev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + if (port1 == 0 || port1 > hdev->maxchild) + return -EINVAL; + + /* This assumes that devices not managed by the hub driver + * will always have maxchild equal to 0. + */ + *ppowner = &(usb_hub_to_struct_hub(hdev)->ports[port1 - 1]->port_owner); + return 0; +} + +/* In the following three functions, the caller must hold hdev's lock */ +int usb_hub_claim_port(struct usb_device *hdev, unsigned port1, + struct dev_state *owner) +{ + int rc; + struct dev_state **powner; + + rc = find_port_owner(hdev, port1, &powner); + if (rc) + return rc; + if (*powner) + return -EBUSY; + *powner = owner; + return rc; +} + +int usb_hub_release_port(struct usb_device *hdev, unsigned port1, + struct dev_state *owner) +{ + int rc; + struct dev_state **powner; + + rc = find_port_owner(hdev, port1, &powner); + if (rc) + return rc; + if (*powner != owner) + return -ENOENT; + *powner = NULL; + return rc; +} + +void usb_hub_release_all_ports(struct usb_device *hdev, struct dev_state *owner) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + int n; + + for (n = 0; n < hdev->maxchild; n++) { + if (hub->ports[n]->port_owner == owner) + hub->ports[n]->port_owner = NULL; + } + +} + +/* The caller must hold udev's lock */ +bool usb_device_is_owned(struct usb_device *udev) +{ + struct usb_hub *hub; + + if (udev->state == USB_STATE_NOTATTACHED || !udev->parent) + return false; + hub = usb_hub_to_struct_hub(udev->parent); + return !!hub->ports[udev->portnum - 1]->port_owner; +} + +static void recursively_mark_NOTATTACHED(struct usb_device *udev) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(udev); + int i; + + for (i = 0; i < udev->maxchild; ++i) { + if (hub->ports[i]->child) + recursively_mark_NOTATTACHED(hub->ports[i]->child); + } + if (udev->state == USB_STATE_SUSPENDED) + udev->active_duration -= jiffies; + udev->state = USB_STATE_NOTATTACHED; +} + +/** + * usb_set_device_state - change a device's current state (usbcore, hcds) + * @udev: pointer to device whose state should be changed + * @new_state: new state value to be stored + * + * udev->state is _not_ fully protected by the device lock. Although + * most transitions are made only while holding the lock, the state can + * can change to USB_STATE_NOTATTACHED at almost any time. This + * is so that devices can be marked as disconnected as soon as possible, + * without having to wait for any semaphores to be released. As a result, + * all changes to any device's state must be protected by the + * device_state_lock spinlock. + * + * Once a device has been added to the device tree, all changes to its state + * should be made using this routine. The state should _not_ be set directly. + * + * If udev->state is already USB_STATE_NOTATTACHED then no change is made. + * Otherwise udev->state is set to new_state, and if new_state is + * USB_STATE_NOTATTACHED then all of udev's descendants' states are also set + * to USB_STATE_NOTATTACHED. + */ +void usb_set_device_state(struct usb_device *udev, + enum usb_device_state new_state) +{ + unsigned long flags; + int wakeup = -1; + + spin_lock_irqsave(&device_state_lock, flags); + if (udev->state == USB_STATE_NOTATTACHED) + ; /* do nothing */ + else if (new_state != USB_STATE_NOTATTACHED) { + + /* root hub wakeup capabilities are managed out-of-band + * and may involve silicon errata ... ignore them here. + */ + if (udev->parent) { + if (udev->state == USB_STATE_SUSPENDED + || new_state == USB_STATE_SUSPENDED) + ; /* No change to wakeup settings */ + else if (new_state == USB_STATE_CONFIGURED) + wakeup = (udev->quirks & + USB_QUIRK_IGNORE_REMOTE_WAKEUP) ? 0 : + udev->actconfig->desc.bmAttributes & + USB_CONFIG_ATT_WAKEUP; + else + wakeup = 0; + } + if (udev->state == USB_STATE_SUSPENDED && + new_state != USB_STATE_SUSPENDED) + udev->active_duration -= jiffies; + else if (new_state == USB_STATE_SUSPENDED && + udev->state != USB_STATE_SUSPENDED) + udev->active_duration += jiffies; + udev->state = new_state; + } else + recursively_mark_NOTATTACHED(udev); + spin_unlock_irqrestore(&device_state_lock, flags); + if (wakeup >= 0) + device_set_wakeup_capable(&udev->dev, wakeup); +} +EXPORT_SYMBOL_GPL(usb_set_device_state); + +/* + * Choose a device number. + * + * Device numbers are used as filenames in usbfs. On USB-1.1 and + * USB-2.0 buses they are also used as device addresses, however on + * USB-3.0 buses the address is assigned by the controller hardware + * and it usually is not the same as the device number. + * + * WUSB devices are simple: they have no hubs behind, so the mapping + * device <-> virtual port number becomes 1:1. Why? to simplify the + * life of the device connection logic in + * drivers/usb/wusbcore/devconnect.c. When we do the initial secret + * handshake we need to assign a temporary address in the unauthorized + * space. For simplicity we use the first virtual port number found to + * be free [drivers/usb/wusbcore/devconnect.c:wusbhc_devconnect_ack()] + * and that becomes it's address [X < 128] or its unauthorized address + * [X | 0x80]. + * + * We add 1 as an offset to the one-based USB-stack port number + * (zero-based wusb virtual port index) for two reasons: (a) dev addr + * 0 is reserved by USB for default address; (b) Linux's USB stack + * uses always #1 for the root hub of the controller. So USB stack's + * port #1, which is wusb virtual-port #0 has address #2. + * + * Devices connected under xHCI are not as simple. The host controller + * supports virtualization, so the hardware assigns device addresses and + * the HCD must setup data structures before issuing a set address + * command to the hardware. + */ +static void choose_devnum(struct usb_device *udev) +{ + int devnum; + struct usb_bus *bus = udev->bus; + + /* If khubd ever becomes multithreaded, this will need a lock */ + if (udev->wusb) { + devnum = udev->portnum + 1; + BUG_ON(test_bit(devnum, bus->devmap.devicemap)); + } else { + /* Try to allocate the next devnum beginning at + * bus->devnum_next. */ + devnum = find_next_zero_bit(bus->devmap.devicemap, 128, + bus->devnum_next); + if (devnum >= 128) + devnum = find_next_zero_bit(bus->devmap.devicemap, + 128, 1); + bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); + } + if (devnum < 128) { + set_bit(devnum, bus->devmap.devicemap); + udev->devnum = devnum; + } +} + +static void release_devnum(struct usb_device *udev) +{ + if (udev->devnum > 0) { + clear_bit(udev->devnum, udev->bus->devmap.devicemap); + udev->devnum = -1; + } +} + +static void update_devnum(struct usb_device *udev, int devnum) +{ + /* The address for a WUSB device is managed by wusbcore. */ + if (!udev->wusb) + udev->devnum = devnum; +} + +static void hub_free_dev(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + /* Root hubs aren't real devices, so don't free HCD resources */ + if (hcd->driver->free_dev && udev->parent) + hcd->driver->free_dev(hcd, udev); +} + +/** + * usb_disconnect - disconnect a device (usbcore-internal) + * @pdev: pointer to device being disconnected + * Context: !in_interrupt () + * + * Something got disconnected. Get rid of it and all of its children. + * + * If *pdev is a normal device then the parent hub must already be locked. + * If *pdev is a root hub then this routine will acquire the + * usb_bus_list_lock on behalf of the caller. + * + * Only hub drivers (including virtual root hub drivers for host + * controllers) should ever call this. + * + * This call is synchronous, and may not be used in an interrupt context. + */ +void usb_disconnect(struct usb_device **pdev) +{ + struct usb_device *udev = *pdev; + struct usb_hub *hub = usb_hub_to_struct_hub(udev); + int i; + + /* mark the device as inactive, so any further urb submissions for + * this device (and any of its children) will fail immediately. + * this quiesces everything except pending urbs. + */ + usb_set_device_state(udev, USB_STATE_NOTATTACHED); + dev_info(&udev->dev, "USB disconnect, device number %d\n", + udev->devnum); + + usb_lock_device(udev); + + /* Free up all the children before we remove this device */ + for (i = 0; i < udev->maxchild; i++) { + if (hub->ports[i]->child) + usb_disconnect(&hub->ports[i]->child); + } + + /* deallocate hcd/hardware state ... nuking all pending urbs and + * cleaning up all state associated with the current configuration + * so that the hardware is now fully quiesced. + */ + dev_dbg (&udev->dev, "unregistering device\n"); + usb_disable_device(udev, 0); + usb_hcd_synchronize_unlinks(udev); + + if (udev->parent) { + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + struct usb_port *port_dev = hub->ports[udev->portnum - 1]; + + sysfs_remove_link(&udev->dev.kobj, "port"); + sysfs_remove_link(&port_dev->dev.kobj, "device"); + + if (!port_dev->did_runtime_put) + pm_runtime_put(&port_dev->dev); + else + port_dev->did_runtime_put = false; + } + + usb_remove_ep_devs(&udev->ep0); + usb_unlock_device(udev); + + /* Unregister the device. The device driver is responsible + * for de-configuring the device and invoking the remove-device + * notifier chain (used by usbfs and possibly others). + */ + device_del(&udev->dev); + + /* Free the device number and delete the parent's children[] + * (or root_hub) pointer. + */ + release_devnum(udev); + + /* Avoid races with recursively_mark_NOTATTACHED() */ + spin_lock_irq(&device_state_lock); + *pdev = NULL; + spin_unlock_irq(&device_state_lock); + + hub_free_dev(udev); + + put_device(&udev->dev); +} + +#ifdef CONFIG_USB_ANNOUNCE_NEW_DEVICES +static void show_string(struct usb_device *udev, char *id, char *string) +{ + if (!string) + return; + dev_info(&udev->dev, "%s: %s\n", id, string); +} + +static void announce_device(struct usb_device *udev) +{ + dev_info(&udev->dev, "New USB device found, idVendor=%04x, idProduct=%04x\n", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + dev_info(&udev->dev, + "New USB device strings: Mfr=%d, Product=%d, SerialNumber=%d\n", + udev->descriptor.iManufacturer, + udev->descriptor.iProduct, + udev->descriptor.iSerialNumber); + show_string(udev, "Product", udev->product); + show_string(udev, "Manufacturer", udev->manufacturer); + show_string(udev, "SerialNumber", udev->serial); +} +#else +static inline void announce_device(struct usb_device *udev) { } +#endif + +#ifdef CONFIG_USB_OTG +#include "otg_whitelist.h" +#endif + +/** + * usb_enumerate_device_otg - FIXME (usbcore-internal) + * @udev: newly addressed device (in ADDRESS state) + * + * Finish enumeration for On-The-Go devices + */ +static int usb_enumerate_device_otg(struct usb_device *udev) +{ + int err = 0; + +#ifdef CONFIG_USB_OTG + /* + * OTG-aware devices on OTG-capable root hubs may be able to use SRP, + * to wake us after we've powered off VBUS; and HNP, switching roles + * "host" to "peripheral". The OTG descriptor helps figure this out. + */ + if (!udev->bus->is_b_host + && udev->config + && udev->parent == udev->bus->root_hub) { + struct usb_otg_descriptor *desc = NULL; + struct usb_bus *bus = udev->bus; + + /* descriptor may appear anywhere in config */ + if (__usb_get_extra_descriptor (udev->rawdescriptors[0], + le16_to_cpu(udev->config[0].desc.wTotalLength), + USB_DT_OTG, (void **) &desc) == 0) { + if (desc->bmAttributes & USB_OTG_HNP) { + unsigned port1 = udev->portnum; + + dev_info(&udev->dev, + "Dual-Role OTG device on %sHNP port\n", + (port1 == bus->otg_port) + ? "" : "non-"); + + /* enable HNP before suspend, it's simpler */ + if (port1 == bus->otg_port) + bus->b_hnp_enable = 1; + err = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, 0, + bus->b_hnp_enable + ? USB_DEVICE_B_HNP_ENABLE + : USB_DEVICE_A_ALT_HNP_SUPPORT, + 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + if (err < 0) { + /* OTG MESSAGE: report errors here, + * customize to match your product. + */ + dev_info(&udev->dev, + "can't set HNP mode: %d\n", + err); + bus->b_hnp_enable = 0; + } + } + } + } + + if (!is_targeted(udev)) { + + /* Maybe it can talk to us, though we can't talk to it. + * (Includes HNP test device.) + */ + if (udev->bus->b_hnp_enable || udev->bus->is_b_host) { + err = usb_port_suspend(udev, PMSG_SUSPEND); + if (err < 0) + dev_dbg(&udev->dev, "HNP fail, %d\n", err); + } + err = -ENOTSUPP; + goto fail; + } +fail: +#endif + return err; +} + + +/** + * usb_enumerate_device - Read device configs/intfs/otg (usbcore-internal) + * @udev: newly addressed device (in ADDRESS state) + * + * This is only called by usb_new_device() and usb_authorize_device() + * and FIXME -- all comments that apply to them apply here wrt to + * environment. + * + * If the device is WUSB and not authorized, we don't attempt to read + * the string descriptors, as they will be errored out by the device + * until it has been authorized. + */ +static int usb_enumerate_device(struct usb_device *udev) +{ + int err; + + if (udev->config == NULL) { + err = usb_get_configuration(udev); + if (err < 0) { + if (err != -ENODEV) + dev_err(&udev->dev, "can't read configurations, error %d\n", + err); + return err; + } + } + + /* read the standard strings and cache them if present */ + udev->product = usb_cache_string(udev, udev->descriptor.iProduct); + udev->manufacturer = usb_cache_string(udev, + udev->descriptor.iManufacturer); + udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber); + + err = usb_enumerate_device_otg(udev); + if (err < 0) + return err; + + usb_detect_interface_quirks(udev); + + return 0; +} + +static void set_usb_port_removable(struct usb_device *udev) +{ + struct usb_device *hdev = udev->parent; + struct usb_hub *hub; + u8 port = udev->portnum; + u16 wHubCharacteristics; + bool removable = true; + + if (!hdev) + return; + + hub = usb_hub_to_struct_hub(udev->parent); + + wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics); + + if (!(wHubCharacteristics & HUB_CHAR_COMPOUND)) + return; + + if (hub_is_superspeed(hdev)) { + if (le16_to_cpu(hub->descriptor->u.ss.DeviceRemovable) + & (1 << port)) + removable = false; + } else { + if (hub->descriptor->u.hs.DeviceRemovable[port / 8] & (1 << (port % 8))) + removable = false; + } + + if (removable) + udev->removable = USB_DEVICE_REMOVABLE; + else + udev->removable = USB_DEVICE_FIXED; +} + +/** + * usb_new_device - perform initial device setup (usbcore-internal) + * @udev: newly addressed device (in ADDRESS state) + * + * This is called with devices which have been detected but not fully + * enumerated. The device descriptor is available, but not descriptors + * for any device configuration. The caller must have locked either + * the parent hub (if udev is a normal device) or else the + * usb_bus_list_lock (if udev is a root hub). The parent's pointer to + * udev has already been installed, but udev is not yet visible through + * sysfs or other filesystem code. + * + * It will return if the device is configured properly or not. Zero if + * the interface was registered with the driver core; else a negative + * errno value. + * + * This call is synchronous, and may not be used in an interrupt context. + * + * Only the hub driver or root-hub registrar should ever call this. + */ +int usb_new_device(struct usb_device *udev) +{ + int err; + + if (udev->parent) { + /* Initialize non-root-hub device wakeup to disabled; + * device (un)configuration controls wakeup capable + * sysfs power/wakeup controls wakeup enabled/disabled + */ + device_init_wakeup(&udev->dev, 0); + } + + /* Tell the runtime-PM framework the device is active */ + pm_runtime_set_active(&udev->dev); + pm_runtime_get_noresume(&udev->dev); + pm_runtime_use_autosuspend(&udev->dev); + pm_runtime_enable(&udev->dev); + + /* By default, forbid autosuspend for all devices. It will be + * allowed for hubs during binding. + */ + usb_disable_autosuspend(udev); + + err = usb_enumerate_device(udev); /* Read descriptors */ + if (err < 0) + goto fail; + dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n", + udev->devnum, udev->bus->busnum, + (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); + /* export the usbdev device-node for libusb */ + udev->dev.devt = MKDEV(USB_DEVICE_MAJOR, + (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); + + /* Tell the world! */ + announce_device(udev); + + if (udev->serial) + add_device_randomness(udev->serial, strlen(udev->serial)); + if (udev->product) + add_device_randomness(udev->product, strlen(udev->product)); + if (udev->manufacturer) + add_device_randomness(udev->manufacturer, + strlen(udev->manufacturer)); + + device_enable_async_suspend(&udev->dev); + + /* + * check whether the hub marks this port as non-removable. Do it + * now so that platform-specific data can override it in + * device_add() + */ + if (udev->parent) + set_usb_port_removable(udev); + + /* Register the device. The device driver is responsible + * for configuring the device and invoking the add-device + * notifier chain (used by usbfs and possibly others). + */ + err = device_add(&udev->dev); + if (err) { + dev_err(&udev->dev, "can't device_add, error %d\n", err); + goto fail; + } + + /* Create link files between child device and usb port device. */ + if (udev->parent) { + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + struct usb_port *port_dev = hub->ports[udev->portnum - 1]; + + err = sysfs_create_link(&udev->dev.kobj, + &port_dev->dev.kobj, "port"); + if (err) + goto fail; + + err = sysfs_create_link(&port_dev->dev.kobj, + &udev->dev.kobj, "device"); + if (err) { + sysfs_remove_link(&udev->dev.kobj, "port"); + goto fail; + } + + pm_runtime_get_sync(&port_dev->dev); + } + + (void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev); + usb_mark_last_busy(udev); + pm_runtime_put_sync_autosuspend(&udev->dev); + return err; + +fail: + usb_set_device_state(udev, USB_STATE_NOTATTACHED); + pm_runtime_disable(&udev->dev); + pm_runtime_set_suspended(&udev->dev); + return err; +} + + +/** + * usb_deauthorize_device - deauthorize a device (usbcore-internal) + * @usb_dev: USB device + * + * Move the USB device to a very basic state where interfaces are disabled + * and the device is in fact unconfigured and unusable. + * + * We share a lock (that we have) with device_del(), so we need to + * defer its call. + */ +int usb_deauthorize_device(struct usb_device *usb_dev) +{ + usb_lock_device(usb_dev); + if (usb_dev->authorized == 0) + goto out_unauthorized; + + usb_dev->authorized = 0; + usb_set_configuration(usb_dev, -1); + +out_unauthorized: + usb_unlock_device(usb_dev); + return 0; +} + + +int usb_authorize_device(struct usb_device *usb_dev) +{ + int result = 0, c; + + usb_lock_device(usb_dev); + if (usb_dev->authorized == 1) + goto out_authorized; + + result = usb_autoresume_device(usb_dev); + if (result < 0) { + dev_err(&usb_dev->dev, + "can't autoresume for authorization: %d\n", result); + goto error_autoresume; + } + result = usb_get_device_descriptor(usb_dev, sizeof(usb_dev->descriptor)); + if (result < 0) { + dev_err(&usb_dev->dev, "can't re-read device descriptor for " + "authorization: %d\n", result); + goto error_device_descriptor; + } + + usb_dev->authorized = 1; + /* Choose and set the configuration. This registers the interfaces + * with the driver core and lets interface drivers bind to them. + */ + c = usb_choose_configuration(usb_dev); + if (c >= 0) { + result = usb_set_configuration(usb_dev, c); + if (result) { + dev_err(&usb_dev->dev, + "can't set config #%d, error %d\n", c, result); + /* This need not be fatal. The user can try to + * set other configurations. */ + } + } + dev_info(&usb_dev->dev, "authorized to connect\n"); + +error_device_descriptor: + usb_autosuspend_device(usb_dev); +error_autoresume: +out_authorized: + usb_unlock_device(usb_dev); // complements locktree + return result; +} + + +/* Returns 1 if @hub is a WUSB root hub, 0 otherwise */ +static unsigned hub_is_wusb(struct usb_hub *hub) +{ + struct usb_hcd *hcd; + if (hub->hdev->parent != NULL) /* not a root hub? */ + return 0; + hcd = container_of(hub->hdev->bus, struct usb_hcd, self); + return hcd->wireless; +} + + +#define PORT_RESET_TRIES 5 +#define SET_ADDRESS_TRIES 2 +#define GET_DESCRIPTOR_TRIES 2 +#define SET_CONFIG_TRIES (2 * (use_both_schemes + 1)) +#define USE_NEW_SCHEME(i) ((i) / 2 == (int)old_scheme_first) + +#define HUB_ROOT_RESET_TIME 50 /* times are in msec */ +#define HUB_SHORT_RESET_TIME 10 +#define HUB_BH_RESET_TIME 50 +#define HUB_LONG_RESET_TIME 200 +#define HUB_RESET_TIMEOUT 800 + +static int hub_port_reset(struct usb_hub *hub, int port1, + struct usb_device *udev, unsigned int delay, bool warm); + +/* Is a USB 3.0 port in the Inactive or Complinance Mode state? + * Port worm reset is required to recover + */ +static bool hub_port_warm_reset_required(struct usb_hub *hub, u16 portstatus) +{ + return hub_is_superspeed(hub->hdev) && + (((portstatus & USB_PORT_STAT_LINK_STATE) == + USB_SS_PORT_LS_SS_INACTIVE) || + ((portstatus & USB_PORT_STAT_LINK_STATE) == + USB_SS_PORT_LS_COMP_MOD)) ; +} + +static int hub_port_wait_reset(struct usb_hub *hub, int port1, + struct usb_device *udev, unsigned int delay, bool warm) +{ + int delay_time, ret; + u16 portstatus; + u16 portchange; + + for (delay_time = 0; + delay_time < HUB_RESET_TIMEOUT; + delay_time += delay) { + /* wait to give the device a chance to reset */ + msleep(delay); + + /* read and decode port status */ + ret = hub_port_status(hub, port1, &portstatus, &portchange); + if (ret < 0) + return ret; + + /* The port state is unknown until the reset completes. */ + if (!(portstatus & USB_PORT_STAT_RESET)) + break; + + /* switch to the long delay after two short delay failures */ + if (delay_time >= 2 * HUB_SHORT_RESET_TIME) + delay = HUB_LONG_RESET_TIME; + + dev_dbg (hub->intfdev, + "port %d not %sreset yet, waiting %dms\n", + port1, warm ? "warm " : "", delay); + } + + if ((portstatus & USB_PORT_STAT_RESET)) + return -EBUSY; + + if (hub_port_warm_reset_required(hub, portstatus)) + return -ENOTCONN; + + /* Device went away? */ + if (!(portstatus & USB_PORT_STAT_CONNECTION)) + return -ENOTCONN; + + /* bomb out completely if the connection bounced. A USB 3.0 + * connection may bounce if multiple warm resets were issued, + * but the device may have successfully re-connected. Ignore it. + */ + if (!hub_is_superspeed(hub->hdev) && + (portchange & USB_PORT_STAT_C_CONNECTION)) + return -ENOTCONN; + + if (!(portstatus & USB_PORT_STAT_ENABLE)) + return -EBUSY; + + if (!udev) + return 0; + + if (hub_is_wusb(hub)) + udev->speed = USB_SPEED_WIRELESS; + else if (hub_is_superspeed(hub->hdev)) + udev->speed = USB_SPEED_SUPER; + else if (portstatus & USB_PORT_STAT_HIGH_SPEED) + udev->speed = USB_SPEED_HIGH; + else if (portstatus & USB_PORT_STAT_LOW_SPEED) + udev->speed = USB_SPEED_LOW; + else + udev->speed = USB_SPEED_FULL; + return 0; +} + +static void hub_port_finish_reset(struct usb_hub *hub, int port1, + struct usb_device *udev, int *status) +{ + switch (*status) { + case 0: + /* TRSTRCY = 10 ms; plus some extra */ + msleep(10 + 40); + if (udev) { + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + update_devnum(udev, 0); + /* The xHC may think the device is already reset, + * so ignore the status. + */ + if (hcd->driver->reset_device) + hcd->driver->reset_device(hcd, udev); + } + /* FALL THROUGH */ + case -ENOTCONN: + case -ENODEV: + usb_clear_port_feature(hub->hdev, + port1, USB_PORT_FEAT_C_RESET); + if (hub_is_superspeed(hub->hdev)) { + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_BH_PORT_RESET); + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_PORT_LINK_STATE); + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_CONNECTION); + } + if (udev) + usb_set_device_state(udev, *status + ? USB_STATE_NOTATTACHED + : USB_STATE_DEFAULT); + break; + } +} + +/* Handle port reset and port warm(BH) reset (for USB3 protocol ports) */ +static int hub_port_reset(struct usb_hub *hub, int port1, + struct usb_device *udev, unsigned int delay, bool warm) +{ + int i, status; + u16 portchange, portstatus; + + if (!hub_is_superspeed(hub->hdev)) { + if (warm) { + dev_err(hub->intfdev, "only USB3 hub support " + "warm reset\n"); + return -EINVAL; + } + /* Block EHCI CF initialization during the port reset. + * Some companion controllers don't like it when they mix. + */ + down_read(&ehci_cf_port_reset_rwsem); + } else if (!warm) { + /* + * If the caller hasn't explicitly requested a warm reset, + * double check and see if one is needed. + */ + status = hub_port_status(hub, port1, + &portstatus, &portchange); + if (status < 0) + goto done; + + if (hub_port_warm_reset_required(hub, portstatus)) + warm = true; + } + + /* Reset the port */ + for (i = 0; i < PORT_RESET_TRIES; i++) { + status = set_port_feature(hub->hdev, port1, (warm ? + USB_PORT_FEAT_BH_PORT_RESET : + USB_PORT_FEAT_RESET)); + if (status == -ENODEV) { + ; /* The hub is gone */ + } else if (status) { + dev_err(hub->intfdev, + "cannot %sreset port %d (err = %d)\n", + warm ? "warm " : "", port1, status); + } else { + status = hub_port_wait_reset(hub, port1, udev, delay, + warm); + if (status && status != -ENOTCONN && status != -ENODEV) + dev_dbg(hub->intfdev, + "port_wait_reset: err = %d\n", + status); + } + + /* Check for disconnect or reset */ + if (status == 0 || status == -ENOTCONN || status == -ENODEV) { + hub_port_finish_reset(hub, port1, udev, &status); + + if (!hub_is_superspeed(hub->hdev)) + goto done; + + /* + * If a USB 3.0 device migrates from reset to an error + * state, re-issue the warm reset. + */ + if (hub_port_status(hub, port1, + &portstatus, &portchange) < 0) + goto done; + + if (!hub_port_warm_reset_required(hub, portstatus)) + goto done; + + /* + * If the port is in SS.Inactive or Compliance Mode, the + * hot or warm reset failed. Try another warm reset. + */ + if (!warm) { + dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n", + port1); + warm = true; + } + } + + dev_dbg (hub->intfdev, + "port %d not enabled, trying %sreset again...\n", + port1, warm ? "warm " : ""); + delay = HUB_LONG_RESET_TIME; + } + + dev_err (hub->intfdev, + "Cannot enable port %i. Maybe the USB cable is bad?\n", + port1); + +done: + if (!hub_is_superspeed(hub->hdev)) + up_read(&ehci_cf_port_reset_rwsem); + + return status; +} + +/* Check if a port is power on */ +static int port_is_power_on(struct usb_hub *hub, unsigned portstatus) +{ + int ret = 0; + + if (hub_is_superspeed(hub->hdev)) { + if (portstatus & USB_SS_PORT_STAT_POWER) + ret = 1; + } else { + if (portstatus & USB_PORT_STAT_POWER) + ret = 1; + } + + return ret; +} + +#ifdef CONFIG_PM + +/* Check if a port is suspended(USB2.0 port) or in U3 state(USB3.0 port) */ +static int port_is_suspended(struct usb_hub *hub, unsigned portstatus) +{ + int ret = 0; + + if (hub_is_superspeed(hub->hdev)) { + if ((portstatus & USB_PORT_STAT_LINK_STATE) + == USB_SS_PORT_LS_U3) + ret = 1; + } else { + if (portstatus & USB_PORT_STAT_SUSPEND) + ret = 1; + } + + return ret; +} + +/* Determine whether the device on a port is ready for a normal resume, + * is ready for a reset-resume, or should be disconnected. + */ +static int check_port_resume_type(struct usb_device *udev, + struct usb_hub *hub, int port1, + int status, unsigned portchange, unsigned portstatus) +{ + /* Is the device still present? */ + if (status || port_is_suspended(hub, portstatus) || + !port_is_power_on(hub, portstatus) || + !(portstatus & USB_PORT_STAT_CONNECTION)) { + if (status >= 0) + status = -ENODEV; + } + + /* Can't do a normal resume if the port isn't enabled, + * so try a reset-resume instead. + */ + else if (!(portstatus & USB_PORT_STAT_ENABLE) && !udev->reset_resume) { + if (udev->persist_enabled) + udev->reset_resume = 1; + else + status = -ENODEV; + } + + if (status) { + dev_dbg(hub->intfdev, + "port %d status %04x.%04x after resume, %d\n", + port1, portchange, portstatus, status); + } else if (udev->reset_resume) { + + /* Late port handoff can set status-change bits */ + if (portchange & USB_PORT_STAT_C_CONNECTION) + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_CONNECTION); + if (portchange & USB_PORT_STAT_C_ENABLE) + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_ENABLE); + } + + return status; +} + +int usb_disable_ltm(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + /* Check if the roothub and device supports LTM. */ + if (!usb_device_supports_ltm(hcd->self.root_hub) || + !usb_device_supports_ltm(udev)) + return 0; + + /* Clear Feature LTM Enable can only be sent if the device is + * configured. + */ + if (!udev->actconfig) + return 0; + + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE, + USB_DEVICE_LTM_ENABLE, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); +} +EXPORT_SYMBOL_GPL(usb_disable_ltm); + +void usb_enable_ltm(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + /* Check if the roothub and device supports LTM. */ + if (!usb_device_supports_ltm(hcd->self.root_hub) || + !usb_device_supports_ltm(udev)) + return; + + /* Set Feature LTM Enable can only be sent if the device is + * configured. + */ + if (!udev->actconfig) + return; + + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, USB_RECIP_DEVICE, + USB_DEVICE_LTM_ENABLE, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); +} +EXPORT_SYMBOL_GPL(usb_enable_ltm); + +#ifdef CONFIG_PM +/* + * usb_disable_function_remotewakeup - disable usb3.0 + * device's function remote wakeup + * @udev: target device + * + * Assume there's only one function on the USB 3.0 + * device and disable remote wake for the first + * interface. FIXME if the interface association + * descriptor shows there's more than one function. + */ +static int usb_disable_function_remotewakeup(struct usb_device *udev) +{ + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_CLEAR_FEATURE, USB_RECIP_INTERFACE, + USB_INTRF_FUNC_SUSPEND, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); +} + +/* Count of wakeup-enabled devices at or below udev */ +static unsigned wakeup_enabled_descendants(struct usb_device *udev) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(udev); + + return udev->do_remote_wakeup + + (hub ? hub->wakeup_enabled_descendants : 0); +} + +/* + * usb_port_suspend - suspend a usb device's upstream port + * @udev: device that's no longer in active use, not a root hub + * Context: must be able to sleep; device not locked; pm locks held + * + * Suspends a USB device that isn't in active use, conserving power. + * Devices may wake out of a suspend, if anything important happens, + * using the remote wakeup mechanism. They may also be taken out of + * suspend by the host, using usb_port_resume(). It's also routine + * to disconnect devices while they are suspended. + * + * This only affects the USB hardware for a device; its interfaces + * (and, for hubs, child devices) must already have been suspended. + * + * Selective port suspend reduces power; most suspended devices draw + * less than 500 uA. It's also used in OTG, along with remote wakeup. + * All devices below the suspended port are also suspended. + * + * Devices leave suspend state when the host wakes them up. Some devices + * also support "remote wakeup", where the device can activate the USB + * tree above them to deliver data, such as a keypress or packet. In + * some cases, this wakes the USB host. + * + * Suspending OTG devices may trigger HNP, if that's been enabled + * between a pair of dual-role devices. That will change roles, such + * as from A-Host to A-Peripheral or from B-Host back to B-Peripheral. + * + * Devices on USB hub ports have only one "suspend" state, corresponding + * to ACPI D2, "may cause the device to lose some context". + * State transitions include: + * + * - suspend, resume ... when the VBUS power link stays live + * - suspend, disconnect ... VBUS lost + * + * Once VBUS drop breaks the circuit, the port it's using has to go through + * normal re-enumeration procedures, starting with enabling VBUS power. + * Other than re-initializing the hub (plug/unplug, except for root hubs), + * Linux (2.6) currently has NO mechanisms to initiate that: no khubd + * timer, no SRP, no requests through sysfs. + * + * If Runtime PM isn't enabled or used, non-SuperSpeed devices may not get + * suspended until their bus goes into global suspend (i.e., the root + * hub is suspended). Nevertheless, we change @udev->state to + * USB_STATE_SUSPENDED as this is the device's "logical" state. The actual + * upstream port setting is stored in @udev->port_is_suspended. + * + * Returns 0 on success, else negative errno. + */ +int usb_port_suspend(struct usb_device *udev, pm_message_t msg) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + struct usb_port *port_dev = hub->ports[udev->portnum - 1]; + int port1 = udev->portnum; + int status; + bool really_suspend = true; + + /* enable remote wakeup when appropriate; this lets the device + * wake up the upstream hub (including maybe the root hub). + * + * NOTE: OTG devices may issue remote wakeup (or SRP) even when + * we don't explicitly enable it here. + */ + if (udev->do_remote_wakeup) { + if (!hub_is_superspeed(hub->hdev)) { + status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, 0, + NULL, 0, + USB_CTRL_SET_TIMEOUT); + } else { + /* Assume there's only one function on the USB 3.0 + * device and enable remote wake for the first + * interface. FIXME if the interface association + * descriptor shows there's more than one function. + */ + status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, + USB_RECIP_INTERFACE, + USB_INTRF_FUNC_SUSPEND, + USB_INTRF_FUNC_SUSPEND_RW | + USB_INTRF_FUNC_SUSPEND_LP, + NULL, 0, + USB_CTRL_SET_TIMEOUT); + } + if (status) { + dev_dbg(&udev->dev, "won't remote wakeup, status %d\n", + status); + /* bail if autosuspend is requested */ + if (PMSG_IS_AUTO(msg)) + goto err_wakeup; + } + } + + /* disable USB2 hardware LPM */ + if (udev->usb2_hw_lpm_enabled == 1) + usb_set_usb2_hardware_lpm(udev, 0); + + if (usb_disable_ltm(udev)) { + dev_err(&udev->dev, "Failed to disable LTM before suspend\n."); + status = -ENOMEM; + if (PMSG_IS_AUTO(msg)) + goto err_ltm; + } + if (usb_unlocked_disable_lpm(udev)) { + dev_err(&udev->dev, "Failed to disable LPM before suspend\n."); + status = -ENOMEM; + if (PMSG_IS_AUTO(msg)) + goto err_lpm3; + } + + /* see 7.1.7.6 */ + if (hub_is_superspeed(hub->hdev)) + status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U3); + + /* + * For system suspend, we do not need to enable the suspend feature + * on individual USB-2 ports. The devices will automatically go + * into suspend a few ms after the root hub stops sending packets. + * The USB 2.0 spec calls this "global suspend". + * + * However, many USB hubs have a bug: They don't relay wakeup requests + * from a downstream port if the port's suspend feature isn't on. + * Therefore we will turn on the suspend feature if udev or any of its + * descendants is enabled for remote wakeup. + */ + else if (PMSG_IS_AUTO(msg) || wakeup_enabled_descendants(udev) > 0) + status = set_port_feature(hub->hdev, port1, + USB_PORT_FEAT_SUSPEND); + else { + really_suspend = false; + status = 0; + } + if (status) { + dev_dbg(hub->intfdev, "can't suspend port %d, status %d\n", + port1, status); + + /* Try to enable USB3 LPM and LTM again */ + usb_unlocked_enable_lpm(udev); + err_lpm3: + usb_enable_ltm(udev); + err_ltm: + /* Try to enable USB2 hardware LPM again */ + if (udev->usb2_hw_lpm_capable == 1) + usb_set_usb2_hardware_lpm(udev, 1); + + if (udev->do_remote_wakeup) { + if (udev->speed < USB_SPEED_SUPER) + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_CLEAR_FEATURE, + USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); + else + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_CLEAR_FEATURE, + USB_RECIP_INTERFACE, + USB_INTRF_FUNC_SUSPEND, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); + } + err_wakeup: + + /* System sleep transitions should never fail */ + if (!PMSG_IS_AUTO(msg)) + status = 0; + } else { + dev_dbg(&udev->dev, "usb %ssuspend, wakeup %d\n", + (PMSG_IS_AUTO(msg) ? "auto-" : ""), + udev->do_remote_wakeup); + if (really_suspend) { + udev->port_is_suspended = 1; + + /* device has up to 10 msec to fully suspend */ + msleep(10); + } + usb_set_device_state(udev, USB_STATE_SUSPENDED); + } + + if (status == 0 && !udev->do_remote_wakeup && udev->persist_enabled) { + pm_runtime_put_sync(&port_dev->dev); + port_dev->did_runtime_put = true; + } + + usb_mark_last_busy(hub->hdev); + return status; +} + +/* + * If the USB "suspend" state is in use (rather than "global suspend"), + * many devices will be individually taken out of suspend state using + * special "resume" signaling. This routine kicks in shortly after + * hardware resume signaling is finished, either because of selective + * resume (by host) or remote wakeup (by device) ... now see what changed + * in the tree that's rooted at this device. + * + * If @udev->reset_resume is set then the device is reset before the + * status check is done. + */ +static int finish_port_resume(struct usb_device *udev) +{ + int status = 0; + u16 devstatus = 0; + + /* caller owns the udev device lock */ + dev_dbg(&udev->dev, "%s\n", + udev->reset_resume ? "finish reset-resume" : "finish resume"); + + /* usb ch9 identifies four variants of SUSPENDED, based on what + * state the device resumes to. Linux currently won't see the + * first two on the host side; they'd be inside hub_port_init() + * during many timeouts, but khubd can't suspend until later. + */ + usb_set_device_state(udev, udev->actconfig + ? USB_STATE_CONFIGURED + : USB_STATE_ADDRESS); + + /* 10.5.4.5 says not to reset a suspended port if the attached + * device is enabled for remote wakeup. Hence the reset + * operation is carried out here, after the port has been + * resumed. + */ + if (udev->reset_resume) + retry_reset_resume: + status = usb_reset_and_verify_device(udev); + + /* 10.5.4.5 says be sure devices in the tree are still there. + * For now let's assume the device didn't go crazy on resume, + * and device drivers will know about any resume quirks. + */ + if (status == 0) { + devstatus = 0; + status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus); + if (status >= 0) + status = (status > 0 ? 0 : -ENODEV); + + /* If a normal resume failed, try doing a reset-resume */ + if (status && !udev->reset_resume && udev->persist_enabled) { + dev_dbg(&udev->dev, "retry with reset-resume\n"); + udev->reset_resume = 1; + goto retry_reset_resume; + } + } + + if (status) { + dev_dbg(&udev->dev, "gone after usb resume? status %d\n", + status); + /* + * There are a few quirky devices which violate the standard + * by claiming to have remote wakeup enabled after a reset, + * which crash if the feature is cleared, hence check for + * udev->reset_resume + */ + } else if (udev->actconfig && !udev->reset_resume) { + if (!hub_is_superspeed(udev->parent)) { + le16_to_cpus(&devstatus); + if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) + status = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + USB_REQ_CLEAR_FEATURE, + USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, 0, + NULL, 0, + USB_CTRL_SET_TIMEOUT); + } else { + status = usb_get_status(udev, USB_RECIP_INTERFACE, 0, + &devstatus); + le16_to_cpus(&devstatus); + if (!status && devstatus & (USB_INTRF_STAT_FUNC_RW_CAP + | USB_INTRF_STAT_FUNC_RW)) + status = + usb_disable_function_remotewakeup(udev); + } + + if (status) + dev_dbg(&udev->dev, + "disable remote wakeup, status %d\n", + status); + status = 0; + } + return status; +} + +/* + * There are some SS USB devices which take longer time for link training. + * XHCI specs 4.19.4 says that when Link training is successful, port + * sets CSC bit to 1. So if SW reads port status before successful link + * training, then it will not find device to be present. + * USB Analyzer log with such buggy devices show that in some cases + * device switch on the RX termination after long delay of host enabling + * the VBUS. In few other cases it has been seen that device fails to + * negotiate link training in first attempt. It has been + * reported till now that few devices take as long as 2000 ms to train + * the link after host enabling its VBUS and termination. Following + * routine implements a 2000 ms timeout for link training. If in a case + * link trains before timeout, loop will exit earlier. + * + * FIXME: If a device was connected before suspend, but was removed + * while system was asleep, then the loop in the following routine will + * only exit at timeout. + * + * This routine should only be called when persist is enabled for a SS + * device. + */ +static int wait_for_ss_port_enable(struct usb_device *udev, + struct usb_hub *hub, int *port1, + u16 *portchange, u16 *portstatus) +{ + int status = 0, delay_ms = 0; + + while (delay_ms < 2000) { + if (status || *portstatus & USB_PORT_STAT_CONNECTION) + break; + msleep(20); + delay_ms += 20; + status = hub_port_status(hub, *port1, portstatus, portchange); + } + return status; +} + +/* + * usb_port_resume - re-activate a suspended usb device's upstream port + * @udev: device to re-activate, not a root hub + * Context: must be able to sleep; device not locked; pm locks held + * + * This will re-activate the suspended device, increasing power usage + * while letting drivers communicate again with its endpoints. + * USB resume explicitly guarantees that the power session between + * the host and the device is the same as it was when the device + * suspended. + * + * If @udev->reset_resume is set then this routine won't check that the + * port is still enabled. Furthermore, finish_port_resume() above will + * reset @udev. The end result is that a broken power session can be + * recovered and @udev will appear to persist across a loss of VBUS power. + * + * For example, if a host controller doesn't maintain VBUS suspend current + * during a system sleep or is reset when the system wakes up, all the USB + * power sessions below it will be broken. This is especially troublesome + * for mass-storage devices containing mounted filesystems, since the + * device will appear to have disconnected and all the memory mappings + * to it will be lost. Using the USB_PERSIST facility, the device can be + * made to appear as if it had not disconnected. + * + * This facility can be dangerous. Although usb_reset_and_verify_device() makes + * every effort to insure that the same device is present after the + * reset as before, it cannot provide a 100% guarantee. Furthermore it's + * quite possible for a device to remain unaltered but its media to be + * changed. If the user replaces a flash memory card while the system is + * asleep, he will have only himself to blame when the filesystem on the + * new card is corrupted and the system crashes. + * + * Returns 0 on success, else negative errno. + */ +int usb_port_resume(struct usb_device *udev, pm_message_t msg) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + struct usb_port *port_dev = hub->ports[udev->portnum - 1]; + int port1 = udev->portnum; + int status; + u16 portchange, portstatus; + + if (port_dev->did_runtime_put) { + status = pm_runtime_get_sync(&port_dev->dev); + port_dev->did_runtime_put = false; + if (status < 0) { + dev_dbg(&udev->dev, "can't resume usb port, status %d\n", + status); + return status; + } + } + + /* Skip the initial Clear-Suspend step for a remote wakeup */ + status = hub_port_status(hub, port1, &portstatus, &portchange); + if (status == 0 && !port_is_suspended(hub, portstatus)) + goto SuspendCleared; + + // dev_dbg(hub->intfdev, "resume port %d\n", port1); + + set_bit(port1, hub->busy_bits); + + /* see 7.1.7.7; affects power usage, but not budgeting */ + if (hub_is_superspeed(hub->hdev)) + status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0); + else + status = usb_clear_port_feature(hub->hdev, + port1, USB_PORT_FEAT_SUSPEND); + if (status) { + dev_dbg(hub->intfdev, "can't resume port %d, status %d\n", + port1, status); + } else { + /* drive resume for at least 20 msec */ + dev_dbg(&udev->dev, "usb %sresume\n", + (PMSG_IS_AUTO(msg) ? "auto-" : "")); + msleep(25); + + /* Virtual root hubs can trigger on GET_PORT_STATUS to + * stop resume signaling. Then finish the resume + * sequence. + */ + status = hub_port_status(hub, port1, &portstatus, &portchange); + + /* TRSMRCY = 10 msec */ + msleep(10); + } + + SuspendCleared: + if (status == 0) { + udev->port_is_suspended = 0; + if (hub_is_superspeed(hub->hdev)) { + if (portchange & USB_PORT_STAT_C_LINK_STATE) + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_PORT_LINK_STATE); + } else { + if (portchange & USB_PORT_STAT_C_SUSPEND) + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_SUSPEND); + } + } + + clear_bit(port1, hub->busy_bits); + + if (udev->persist_enabled && hub_is_superspeed(hub->hdev)) + status = wait_for_ss_port_enable(udev, hub, &port1, &portchange, + &portstatus); + + status = check_port_resume_type(udev, + hub, port1, status, portchange, portstatus); + if (status == 0) + status = finish_port_resume(udev); + if (status < 0) { + dev_dbg(&udev->dev, "can't resume, status %d\n", status); + hub_port_logical_disconnect(hub, port1); + } else { + /* Try to enable USB2 hardware LPM */ + if (udev->usb2_hw_lpm_capable == 1) + usb_set_usb2_hardware_lpm(udev, 1); + + /* Try to enable USB3 LTM and LPM */ + usb_enable_ltm(udev); + usb_unlocked_enable_lpm(udev); + } + + return status; +} + +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME + +/* caller has locked udev */ +int usb_remote_wakeup(struct usb_device *udev) +{ + int status = 0; + + if (udev->state == USB_STATE_SUSPENDED) { + dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-"); + status = usb_autoresume_device(udev); + if (status == 0) { + /* Let the drivers do their thing, then... */ + usb_autosuspend_device(udev); + } + } + return status; +} + +#endif + +static int check_ports_changed(struct usb_hub *hub) +{ + int port1; + + for (port1 = 1; port1 <= hub->hdev->maxchild; ++port1) { + u16 portstatus, portchange; + int status; + + status = hub_port_status(hub, port1, &portstatus, &portchange); + if (!status && portchange) + return 1; + } + return 0; +} + +static int hub_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct usb_hub *hub = usb_get_intfdata (intf); + struct usb_device *hdev = hub->hdev; + unsigned port1; + int status; + + /* + * Warn if children aren't already suspended. + * Also, add up the number of wakeup-enabled descendants. + */ + hub->wakeup_enabled_descendants = 0; + for (port1 = 1; port1 <= hdev->maxchild; port1++) { + struct usb_device *udev; + + udev = hub->ports[port1 - 1]->child; + if (udev && udev->can_submit) { + dev_warn(&intf->dev, "port %d nyet suspended\n", port1); + if (PMSG_IS_AUTO(msg)) + return -EBUSY; + } + if (udev) + hub->wakeup_enabled_descendants += + wakeup_enabled_descendants(udev); + } + + if (hdev->do_remote_wakeup && hub->quirk_check_port_auto_suspend) { + /* check if there are changes pending on hub ports */ + if (check_ports_changed(hub)) { + if (PMSG_IS_AUTO(msg)) + return -EBUSY; + pm_wakeup_event(&hdev->dev, 2000); + } + } + + if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) { + /* Enable hub to send remote wakeup for all ports. */ + for (port1 = 1; port1 <= hdev->maxchild; port1++) { + status = set_port_feature(hdev, + port1 | + USB_PORT_FEAT_REMOTE_WAKE_CONNECT | + USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT | + USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT, + USB_PORT_FEAT_REMOTE_WAKE_MASK); + } + } + + dev_dbg(&intf->dev, "%s\n", __func__); + + /* stop khubd and related activity */ + hub_quiesce(hub, HUB_SUSPEND); + return 0; +} + +static int hub_resume(struct usb_interface *intf) +{ + struct usb_hub *hub = usb_get_intfdata(intf); + + dev_dbg(&intf->dev, "%s\n", __func__); + hub_activate(hub, HUB_RESUME); + return 0; +} + +static int hub_reset_resume(struct usb_interface *intf) +{ + struct usb_hub *hub = usb_get_intfdata(intf); + + dev_dbg(&intf->dev, "%s\n", __func__); + hub_activate(hub, HUB_RESET_RESUME); + return 0; +} + +/** + * usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power + * @rhdev: struct usb_device for the root hub + * + * The USB host controller driver calls this function when its root hub + * is resumed and Vbus power has been interrupted or the controller + * has been reset. The routine marks @rhdev as having lost power. + * When the hub driver is resumed it will take notice and carry out + * power-session recovery for all the "USB-PERSIST"-enabled child devices; + * the others will be disconnected. + */ +void usb_root_hub_lost_power(struct usb_device *rhdev) +{ + dev_warn(&rhdev->dev, "root hub lost power or was reset\n"); + rhdev->reset_resume = 1; +} +EXPORT_SYMBOL_GPL(usb_root_hub_lost_power); + +static const char * const usb3_lpm_names[] = { + "U0", + "U1", + "U2", + "U3", +}; + +/* + * Send a Set SEL control transfer to the device, prior to enabling + * device-initiated U1 or U2. This lets the device know the exit latencies from + * the time the device initiates a U1 or U2 exit, to the time it will receive a + * packet from the host. + * + * This function will fail if the SEL or PEL values for udev are greater than + * the maximum allowed values for the link state to be enabled. + */ +static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) +{ + struct usb_set_sel_req *sel_values; + unsigned long long u1_sel; + unsigned long long u1_pel; + unsigned long long u2_sel; + unsigned long long u2_pel; + int ret; + + /* Convert SEL and PEL stored in ns to us */ + u1_sel = DIV_ROUND_UP(udev->u1_params.sel, 1000); + u1_pel = DIV_ROUND_UP(udev->u1_params.pel, 1000); + u2_sel = DIV_ROUND_UP(udev->u2_params.sel, 1000); + u2_pel = DIV_ROUND_UP(udev->u2_params.pel, 1000); + + /* + * Make sure that the calculated SEL and PEL values for the link + * state we're enabling aren't bigger than the max SEL/PEL + * value that will fit in the SET SEL control transfer. + * Otherwise the device would get an incorrect idea of the exit + * latency for the link state, and could start a device-initiated + * U1/U2 when the exit latencies are too high. + */ + if ((state == USB3_LPM_U1 && + (u1_sel > USB3_LPM_MAX_U1_SEL_PEL || + u1_pel > USB3_LPM_MAX_U1_SEL_PEL)) || + (state == USB3_LPM_U2 && + (u2_sel > USB3_LPM_MAX_U2_SEL_PEL || + u2_pel > USB3_LPM_MAX_U2_SEL_PEL))) { + dev_dbg(&udev->dev, "Device-initiated %s disabled due to long SEL %llu us or PEL %llu us\n", + usb3_lpm_names[state], u1_sel, u1_pel); + return -EINVAL; + } + + /* + * If we're enabling device-initiated LPM for one link state, + * but the other link state has a too high SEL or PEL value, + * just set those values to the max in the Set SEL request. + */ + if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL) + u1_sel = USB3_LPM_MAX_U1_SEL_PEL; + + if (u1_pel > USB3_LPM_MAX_U1_SEL_PEL) + u1_pel = USB3_LPM_MAX_U1_SEL_PEL; + + if (u2_sel > USB3_LPM_MAX_U2_SEL_PEL) + u2_sel = USB3_LPM_MAX_U2_SEL_PEL; + + if (u2_pel > USB3_LPM_MAX_U2_SEL_PEL) + u2_pel = USB3_LPM_MAX_U2_SEL_PEL; + + /* + * usb_enable_lpm() can be called as part of a failed device reset, + * which may be initiated by an error path of a mass storage driver. + * Therefore, use GFP_NOIO. + */ + sel_values = kmalloc(sizeof *(sel_values), GFP_NOIO); + if (!sel_values) + return -ENOMEM; + + sel_values->u1_sel = u1_sel; + sel_values->u1_pel = u1_pel; + sel_values->u2_sel = cpu_to_le16(u2_sel); + sel_values->u2_pel = cpu_to_le16(u2_pel); + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_SEL, + USB_RECIP_DEVICE, + 0, 0, + sel_values, sizeof *(sel_values), + USB_CTRL_SET_TIMEOUT); + kfree(sel_values); + return ret; +} + +/* + * Enable or disable device-initiated U1 or U2 transitions. + */ +static int usb_set_device_initiated_lpm(struct usb_device *udev, + enum usb3_link_state state, bool enable) +{ + int ret; + int feature; + + switch (state) { + case USB3_LPM_U1: + feature = USB_DEVICE_U1_ENABLE; + break; + case USB3_LPM_U2: + feature = USB_DEVICE_U2_ENABLE; + break; + default: + dev_warn(&udev->dev, "%s: Can't %s non-U1 or U2 state.\n", + __func__, enable ? "enable" : "disable"); + return -EINVAL; + } + + if (udev->state != USB_STATE_CONFIGURED) { + dev_dbg(&udev->dev, "%s: Can't %s %s state " + "for unconfigured device.\n", + __func__, enable ? "enable" : "disable", + usb3_lpm_names[state]); + return 0; + } + + if (enable) { + /* + * Now send the control transfer to enable device-initiated LPM + * for either U1 or U2. + */ + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, + USB_RECIP_DEVICE, + feature, + 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + } else { + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_CLEAR_FEATURE, + USB_RECIP_DEVICE, + feature, + 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + } + if (ret < 0) { + dev_warn(&udev->dev, "%s of device-initiated %s failed.\n", + enable ? "Enable" : "Disable", + usb3_lpm_names[state]); + return -EBUSY; + } + return 0; +} + +static int usb_set_lpm_timeout(struct usb_device *udev, + enum usb3_link_state state, int timeout) +{ + int ret; + int feature; + + switch (state) { + case USB3_LPM_U1: + feature = USB_PORT_FEAT_U1_TIMEOUT; + break; + case USB3_LPM_U2: + feature = USB_PORT_FEAT_U2_TIMEOUT; + break; + default: + dev_warn(&udev->dev, "%s: Can't set timeout for non-U1 or U2 state.\n", + __func__); + return -EINVAL; + } + + if (state == USB3_LPM_U1 && timeout > USB3_LPM_U1_MAX_TIMEOUT && + timeout != USB3_LPM_DEVICE_INITIATED) { + dev_warn(&udev->dev, "Failed to set %s timeout to 0x%x, " + "which is a reserved value.\n", + usb3_lpm_names[state], timeout); + return -EINVAL; + } + + ret = set_port_feature(udev->parent, + USB_PORT_LPM_TIMEOUT(timeout) | udev->portnum, + feature); + if (ret < 0) { + dev_warn(&udev->dev, "Failed to set %s timeout to 0x%x," + "error code %i\n", usb3_lpm_names[state], + timeout, ret); + return -EBUSY; + } + if (state == USB3_LPM_U1) + udev->u1_params.timeout = timeout; + else + udev->u2_params.timeout = timeout; + return 0; +} + +/* + * Enable the hub-initiated U1/U2 idle timeouts, and enable device-initiated + * U1/U2 entry. + * + * We will attempt to enable U1 or U2, but there are no guarantees that the + * control transfers to set the hub timeout or enable device-initiated U1/U2 + * will be successful. + * + * If we cannot set the parent hub U1/U2 timeout, we attempt to let the xHCI + * driver know about it. If that call fails, it should be harmless, and just + * take up more slightly more bus bandwidth for unnecessary U1/U2 exit latency. + */ +static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, + enum usb3_link_state state) +{ + int timeout, ret; + __u8 u1_mel = udev->bos->ss_cap->bU1devExitLat; + __le16 u2_mel = udev->bos->ss_cap->bU2DevExitLat; + + /* If the device says it doesn't have *any* exit latency to come out of + * U1 or U2, it's probably lying. Assume it doesn't implement that link + * state. + */ + if ((state == USB3_LPM_U1 && u1_mel == 0) || + (state == USB3_LPM_U2 && u2_mel == 0)) + return; + + /* + * First, let the device know about the exit latencies + * associated with the link state we're about to enable. + */ + ret = usb_req_set_sel(udev, state); + if (ret < 0) { + dev_warn(&udev->dev, "Set SEL for device-initiated %s failed.\n", + usb3_lpm_names[state]); + return; + } + + /* We allow the host controller to set the U1/U2 timeout internally + * first, so that it can change its schedule to account for the + * additional latency to send data to a device in a lower power + * link state. + */ + timeout = hcd->driver->enable_usb3_lpm_timeout(hcd, udev, state); + + /* xHCI host controller doesn't want to enable this LPM state. */ + if (timeout == 0) + return; + + if (timeout < 0) { + dev_warn(&udev->dev, "Could not enable %s link state, " + "xHCI error %i.\n", usb3_lpm_names[state], + timeout); + return; + } + + if (usb_set_lpm_timeout(udev, state, timeout)) + /* If we can't set the parent hub U1/U2 timeout, + * device-initiated LPM won't be allowed either, so let the xHCI + * host know that this link state won't be enabled. + */ + hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state); + + /* Only a configured device will accept the Set Feature U1/U2_ENABLE */ + else if (udev->actconfig) + usb_set_device_initiated_lpm(udev, state, true); + +} + +/* + * Disable the hub-initiated U1/U2 idle timeouts, and disable device-initiated + * U1/U2 entry. + * + * If this function returns -EBUSY, the parent hub will still allow U1/U2 entry. + * If zero is returned, the parent will not allow the link to go into U1/U2. + * + * If zero is returned, device-initiated U1/U2 entry may still be enabled, but + * it won't have an effect on the bus link state because the parent hub will + * still disallow device-initiated U1/U2 entry. + * + * If zero is returned, the xHCI host controller may still think U1/U2 entry is + * possible. The result will be slightly more bus bandwidth will be taken up + * (to account for U1/U2 exit latency), but it should be harmless. + */ +static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev, + enum usb3_link_state state) +{ + int feature; + + switch (state) { + case USB3_LPM_U1: + feature = USB_PORT_FEAT_U1_TIMEOUT; + break; + case USB3_LPM_U2: + feature = USB_PORT_FEAT_U2_TIMEOUT; + break; + default: + dev_warn(&udev->dev, "%s: Can't disable non-U1 or U2 state.\n", + __func__); + return -EINVAL; + } + + if (usb_set_lpm_timeout(udev, state, 0)) + return -EBUSY; + + usb_set_device_initiated_lpm(udev, state, false); + + if (hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state)) + dev_warn(&udev->dev, "Could not disable xHCI %s timeout, " + "bus schedule bandwidth may be impacted.\n", + usb3_lpm_names[state]); + return 0; +} + +/* + * Disable hub-initiated and device-initiated U1 and U2 entry. + * Caller must own the bandwidth_mutex. + * + * This will call usb_enable_lpm() on failure, which will decrement + * lpm_disable_count, and will re-enable LPM if lpm_disable_count reaches zero. + */ +int usb_disable_lpm(struct usb_device *udev) +{ + struct usb_hcd *hcd; + + if (!udev || !udev->parent || + udev->speed != USB_SPEED_SUPER || + !udev->lpm_capable) + return 0; + + hcd = bus_to_hcd(udev->bus); + if (!hcd || !hcd->driver->disable_usb3_lpm_timeout) + return 0; + + udev->lpm_disable_count++; + if ((udev->u1_params.timeout == 0 && udev->u2_params.timeout == 0)) + return 0; + + /* If LPM is enabled, attempt to disable it. */ + if (usb_disable_link_state(hcd, udev, USB3_LPM_U1)) + goto enable_lpm; + if (usb_disable_link_state(hcd, udev, USB3_LPM_U2)) + goto enable_lpm; + + return 0; + +enable_lpm: + usb_enable_lpm(udev); + return -EBUSY; +} +EXPORT_SYMBOL_GPL(usb_disable_lpm); + +/* Grab the bandwidth_mutex before calling usb_disable_lpm() */ +int usb_unlocked_disable_lpm(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + int ret; + + if (!hcd) + return -EINVAL; + + mutex_lock(hcd->bandwidth_mutex); + ret = usb_disable_lpm(udev); + mutex_unlock(hcd->bandwidth_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm); + +/* + * Attempt to enable device-initiated and hub-initiated U1 and U2 entry. The + * xHCI host policy may prevent U1 or U2 from being enabled. + * + * Other callers may have disabled link PM, so U1 and U2 entry will be disabled + * until the lpm_disable_count drops to zero. Caller must own the + * bandwidth_mutex. + */ +void usb_enable_lpm(struct usb_device *udev) +{ + struct usb_hcd *hcd; + + if (!udev || !udev->parent || + udev->speed != USB_SPEED_SUPER || + !udev->lpm_capable) + return; + + udev->lpm_disable_count--; + hcd = bus_to_hcd(udev->bus); + /* Double check that we can both enable and disable LPM. + * Device must be configured to accept set feature U1/U2 timeout. + */ + if (!hcd || !hcd->driver->enable_usb3_lpm_timeout || + !hcd->driver->disable_usb3_lpm_timeout) + return; + + if (udev->lpm_disable_count > 0) + return; + + usb_enable_link_state(hcd, udev, USB3_LPM_U1); + usb_enable_link_state(hcd, udev, USB3_LPM_U2); +} +EXPORT_SYMBOL_GPL(usb_enable_lpm); + +/* Grab the bandwidth_mutex before calling usb_enable_lpm() */ +void usb_unlocked_enable_lpm(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + if (!hcd) + return; + + mutex_lock(hcd->bandwidth_mutex); + usb_enable_lpm(udev); + mutex_unlock(hcd->bandwidth_mutex); +} +EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm); + + +#else /* CONFIG_PM */ + +#define hub_suspend NULL +#define hub_resume NULL +#define hub_reset_resume NULL + +int usb_disable_lpm(struct usb_device *udev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(usb_disable_lpm); + +void usb_enable_lpm(struct usb_device *udev) { } +EXPORT_SYMBOL_GPL(usb_enable_lpm); + +int usb_unlocked_disable_lpm(struct usb_device *udev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm); + +void usb_unlocked_enable_lpm(struct usb_device *udev) { } +EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm); + +int usb_disable_ltm(struct usb_device *udev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(usb_disable_ltm); + +void usb_enable_ltm(struct usb_device *udev) { } +EXPORT_SYMBOL_GPL(usb_enable_ltm); +#endif + + +/* USB 2.0 spec, 7.1.7.3 / fig 7-29: + * + * Between connect detection and reset signaling there must be a delay + * of 100ms at least for debounce and power-settling. The corresponding + * timer shall restart whenever the downstream port detects a disconnect. + * + * Apparently there are some bluetooth and irda-dongles and a number of + * low-speed devices for which this debounce period may last over a second. + * Not covered by the spec - but easy to deal with. + * + * This implementation uses a 1500ms total debounce timeout; if the + * connection isn't stable by then it returns -ETIMEDOUT. It checks + * every 25ms for transient disconnects. When the port status has been + * unchanged for 100ms it returns the port status. + */ +int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected) +{ + int ret; + int total_time, stable_time = 0; + u16 portchange, portstatus; + unsigned connection = 0xffff; + + for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) { + ret = hub_port_status(hub, port1, &portstatus, &portchange); + if (ret < 0) + return ret; + + if (!(portchange & USB_PORT_STAT_C_CONNECTION) && + (portstatus & USB_PORT_STAT_CONNECTION) == connection) { + if (!must_be_connected || + (connection == USB_PORT_STAT_CONNECTION)) + stable_time += HUB_DEBOUNCE_STEP; + if (stable_time >= HUB_DEBOUNCE_STABLE) + break; + } else { + stable_time = 0; + connection = portstatus & USB_PORT_STAT_CONNECTION; + } + + if (portchange & USB_PORT_STAT_C_CONNECTION) { + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_CONNECTION); + } + + if (total_time >= HUB_DEBOUNCE_TIMEOUT) + break; + msleep(HUB_DEBOUNCE_STEP); + } + + dev_dbg (hub->intfdev, + "debounce: port %d: total %dms stable %dms status 0x%x\n", + port1, total_time, stable_time, portstatus); + + if (stable_time < HUB_DEBOUNCE_STABLE) + return -ETIMEDOUT; + return portstatus; +} + +void usb_ep0_reinit(struct usb_device *udev) +{ + usb_disable_endpoint(udev, 0 + USB_DIR_IN, true); + usb_disable_endpoint(udev, 0 + USB_DIR_OUT, true); + usb_enable_endpoint(udev, &udev->ep0, true); +} +EXPORT_SYMBOL_GPL(usb_ep0_reinit); + +#define usb_sndaddr0pipe() (PIPE_CONTROL << 30) +#define usb_rcvaddr0pipe() ((PIPE_CONTROL << 30) | USB_DIR_IN) + +static int hub_set_address(struct usb_device *udev, int devnum) +{ + int retval; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + /* + * The host controller will choose the device address, + * instead of the core having chosen it earlier + */ + if (!hcd->driver->address_device && devnum <= 1) + return -EINVAL; + if (udev->state == USB_STATE_ADDRESS) + return 0; + if (udev->state != USB_STATE_DEFAULT) + return -EINVAL; + if (hcd->driver->address_device) + retval = hcd->driver->address_device(hcd, udev); + else + retval = usb_control_msg(udev, usb_sndaddr0pipe(), + USB_REQ_SET_ADDRESS, 0, devnum, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); + if (retval == 0) { + update_devnum(udev, devnum); + /* Device now using proper address. */ + usb_set_device_state(udev, USB_STATE_ADDRESS); + usb_ep0_reinit(udev); + } + return retval; +} + +/* Reset device, (re)assign address, get device descriptor. + * Device connection must be stable, no more debouncing needed. + * Returns device in USB_STATE_ADDRESS, except on error. + * + * If this is called for an already-existing device (as part of + * usb_reset_and_verify_device), the caller must own the device lock. For a + * newly detected device that is not accessible through any global + * pointers, it's not necessary to lock the device. + */ +static int +hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, + int retry_counter) +{ + static DEFINE_MUTEX(usb_address0_mutex); + + struct usb_device *hdev = hub->hdev; + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + int i, j, retval; + unsigned delay = HUB_SHORT_RESET_TIME; + enum usb_device_speed oldspeed = udev->speed; + const char *speed; + int devnum = udev->devnum; + + /* root hub ports have a slightly longer reset period + * (from USB 2.0 spec, section 7.1.7.5) + */ + if (!hdev->parent) { + delay = HUB_ROOT_RESET_TIME; + if (port1 == hdev->bus->otg_port) + hdev->bus->b_hnp_enable = 0; + } + + /* Some low speed devices have problems with the quick delay, so */ + /* be a bit pessimistic with those devices. RHbug #23670 */ + if (oldspeed == USB_SPEED_LOW) + delay = HUB_LONG_RESET_TIME; + + mutex_lock(&usb_address0_mutex); + + /* Reset the device; full speed may morph to high speed */ + /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */ + retval = hub_port_reset(hub, port1, udev, delay, false); + if (retval < 0) /* error or disconnect */ + goto fail; + /* success, speed is known */ + + retval = -ENODEV; + + if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed) { + dev_dbg(&udev->dev, "device reset changed speed!\n"); + goto fail; + } + oldspeed = udev->speed; + + /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ... + * it's fixed size except for full speed devices. + * For Wireless USB devices, ep0 max packet is always 512 (tho + * reported as 0xff in the device descriptor). WUSB1.0[4.8.1]. + */ + switch (udev->speed) { + case USB_SPEED_SUPER: + case USB_SPEED_WIRELESS: /* fixed at 512 */ + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); + break; + case USB_SPEED_HIGH: /* fixed at 64 */ + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); + break; + case USB_SPEED_FULL: /* 8, 16, 32, or 64 */ + /* to determine the ep0 maxpacket size, try to read + * the device descriptor to get bMaxPacketSize0 and + * then correct our initial guess. + */ + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); + break; + case USB_SPEED_LOW: /* fixed at 8 */ + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8); + break; + default: + goto fail; + } + + if (udev->speed == USB_SPEED_WIRELESS) + speed = "variable speed Wireless"; + else + speed = usb_speed_string(udev->speed); + + if (udev->speed != USB_SPEED_SUPER) + dev_info(&udev->dev, + "%s %s USB device number %d using %s\n", + (udev->config) ? "reset" : "new", speed, + devnum, udev->bus->controller->driver->name); + + /* Set up TT records, if needed */ + if (hdev->tt) { + udev->tt = hdev->tt; + udev->ttport = hdev->ttport; + } else if (udev->speed != USB_SPEED_HIGH + && hdev->speed == USB_SPEED_HIGH) { + if (!hub->tt.hub) { + dev_err(&udev->dev, "parent hub has no TT\n"); + retval = -EINVAL; + goto fail; + } + udev->tt = &hub->tt; + udev->ttport = port1; + } + + /* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way? + * Because device hardware and firmware is sometimes buggy in + * this area, and this is how Linux has done it for ages. + * Change it cautiously. + * + * NOTE: If USE_NEW_SCHEME() is true we will start by issuing + * a 64-byte GET_DESCRIPTOR request. This is what Windows does, + * so it may help with some non-standards-compliant devices. + * Otherwise we start with SET_ADDRESS and then try to read the + * first 8 bytes of the device descriptor to get the ep0 maxpacket + * value. + */ + for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) { + if (USE_NEW_SCHEME(retry_counter) && !(hcd->driver->flags & HCD_USB3)) { + struct usb_device_descriptor *buf; + int r = 0; + +#define GET_DESCRIPTOR_BUFSIZE 64 + buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO); + if (!buf) { + retval = -ENOMEM; + continue; + } + + /* Retry on all errors; some devices are flakey. + * 255 is for WUSB devices, we actually need to use + * 512 (WUSB1.0[4.8.1]). + */ + for (j = 0; j < 3; ++j) { + buf->bMaxPacketSize0 = 0; + r = usb_control_msg(udev, usb_rcvaddr0pipe(), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + USB_DT_DEVICE << 8, 0, + buf, GET_DESCRIPTOR_BUFSIZE, + initial_descriptor_timeout); + switch (buf->bMaxPacketSize0) { + case 8: case 16: case 32: case 64: case 255: + if (buf->bDescriptorType == + USB_DT_DEVICE) { + r = 0; + break; + } + /* FALL THROUGH */ + default: + if (r == 0) + r = -EPROTO; + break; + } + if (r == 0) + break; + } + udev->descriptor.bMaxPacketSize0 = + buf->bMaxPacketSize0; + kfree(buf); + + retval = hub_port_reset(hub, port1, udev, delay, false); + if (retval < 0) /* error or disconnect */ + goto fail; + if (oldspeed != udev->speed) { + dev_dbg(&udev->dev, + "device reset changed speed!\n"); + retval = -ENODEV; + goto fail; + } + if (r) { + if (r != -ENODEV) + dev_err(&udev->dev, "device descriptor read/64, error %d\n", + r); + retval = -EMSGSIZE; + continue; + } +#undef GET_DESCRIPTOR_BUFSIZE + } + + /* + * If device is WUSB, we already assigned an + * unauthorized address in the Connect Ack sequence; + * authorization will assign the final address. + */ + if (udev->wusb == 0) { + for (j = 0; j < SET_ADDRESS_TRIES; ++j) { + retval = hub_set_address(udev, devnum); + if (retval >= 0) + break; + msleep(200); + } + if (retval < 0) { + if (retval != -ENODEV) + dev_err(&udev->dev, "device not accepting address %d, error %d\n", + devnum, retval); + goto fail; + } + if (udev->speed == USB_SPEED_SUPER) { + devnum = udev->devnum; + dev_info(&udev->dev, + "%s SuperSpeed USB device number %d using %s\n", + (udev->config) ? "reset" : "new", + devnum, udev->bus->controller->driver->name); + } + + /* cope with hardware quirkiness: + * - let SET_ADDRESS settle, some device hardware wants it + * - read ep0 maxpacket even for high and low speed, + */ + msleep(10); + if (USE_NEW_SCHEME(retry_counter) && !(hcd->driver->flags & HCD_USB3)) + break; + } + + retval = usb_get_device_descriptor(udev, 8); + if (retval < 8) { + if (retval != -ENODEV) + dev_err(&udev->dev, + "device descriptor read/8, error %d\n", + retval); + if (retval >= 0) + retval = -EMSGSIZE; + } else { + retval = 0; + break; + } + } + if (retval) + goto fail; + + if (hcd->phy && !hdev->parent) + usb_phy_notify_connect(hcd->phy, udev->speed); + + /* + * Some superspeed devices have finished the link training process + * and attached to a superspeed hub port, but the device descriptor + * got from those devices show they aren't superspeed devices. Warm + * reset the port attached by the devices can fix them. + */ + if ((udev->speed == USB_SPEED_SUPER) && + (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) { + dev_err(&udev->dev, "got a wrong device descriptor, " + "warm reset device\n"); + hub_port_reset(hub, port1, udev, + HUB_BH_RESET_TIME, true); + retval = -EINVAL; + goto fail; + } + + if (udev->descriptor.bMaxPacketSize0 == 0xff || + udev->speed == USB_SPEED_SUPER) + i = 512; + else + i = udev->descriptor.bMaxPacketSize0; + if (usb_endpoint_maxp(&udev->ep0.desc) != i) { + if (udev->speed == USB_SPEED_LOW || + !(i == 8 || i == 16 || i == 32 || i == 64)) { + dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i); + retval = -EMSGSIZE; + goto fail; + } + if (udev->speed == USB_SPEED_FULL) + dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i); + else + dev_warn(&udev->dev, "Using ep0 maxpacket: %d\n", i); + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i); + usb_ep0_reinit(udev); + } + + retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); + if (retval < (signed)sizeof(udev->descriptor)) { + if (retval != -ENODEV) + dev_err(&udev->dev, "device descriptor read/all, error %d\n", + retval); + if (retval >= 0) + retval = -ENOMSG; + goto fail; + } + + if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) { + retval = usb_get_bos_descriptor(udev); + if (!retval) { + udev->lpm_capable = usb_device_supports_lpm(udev); + usb_set_lpm_parameters(udev); + } + } + + retval = 0; + /* notify HCD that we have a device connected and addressed */ + if (hcd->driver->update_device) + hcd->driver->update_device(hcd, udev); +fail: + if (retval) { + hub_port_disable(hub, port1, 0); + update_devnum(udev, devnum); /* for disconnect processing */ + } + mutex_unlock(&usb_address0_mutex); + return retval; +} + +static void +check_highspeed (struct usb_hub *hub, struct usb_device *udev, int port1) +{ + struct usb_qualifier_descriptor *qual; + int status; + + qual = kmalloc (sizeof *qual, GFP_KERNEL); + if (qual == NULL) + return; + + status = usb_get_descriptor (udev, USB_DT_DEVICE_QUALIFIER, 0, + qual, sizeof *qual); + if (status == sizeof *qual) { + dev_info(&udev->dev, "not running at top speed; " + "connect to a high speed hub\n"); + /* hub LEDs are probably harder to miss than syslog */ + if (hub->has_indicators) { + hub->indicator[port1-1] = INDICATOR_GREEN_BLINK; + schedule_delayed_work (&hub->leds, 0); + } + } + kfree(qual); +} + +static unsigned +hub_power_remaining (struct usb_hub *hub) +{ + struct usb_device *hdev = hub->hdev; + int remaining; + int port1; + + if (!hub->limited_power) + return 0; + + remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent; + for (port1 = 1; port1 <= hdev->maxchild; ++port1) { + struct usb_device *udev = hub->ports[port1 - 1]->child; + int delta; + unsigned unit_load; + + if (!udev) + continue; + if (hub_is_superspeed(udev)) + unit_load = 150; + else + unit_load = 100; + + /* + * Unconfigured devices may not use more than one unit load, + * or 8mA for OTG ports + */ + if (udev->actconfig) + delta = usb_get_max_power(udev, udev->actconfig); + else if (port1 != udev->bus->otg_port || hdev->parent) + delta = unit_load; + else + delta = 8; + if (delta > hub->mA_per_port) + dev_warn(&udev->dev, + "%dmA is over %umA budget for port %d!\n", + delta, hub->mA_per_port, port1); + remaining -= delta; + } + if (remaining < 0) { + dev_warn(hub->intfdev, "%dmA over power budget!\n", + - remaining); + remaining = 0; + } + return remaining; +} + +/* Handle physical or logical connection change events. + * This routine is called when: + * a port connection-change occurs; + * a port enable-change occurs (often caused by EMI); + * usb_reset_and_verify_device() encounters changed descriptors (as from + * a firmware download) + * caller already locked the hub + */ +static void hub_port_connect_change(struct usb_hub *hub, int port1, + u16 portstatus, u16 portchange) +{ + struct usb_device *hdev = hub->hdev; + struct device *hub_dev = hub->intfdev; + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + unsigned wHubCharacteristics = + le16_to_cpu(hub->descriptor->wHubCharacteristics); + struct usb_device *udev; + int status, i; + unsigned unit_load; + + dev_dbg (hub_dev, + "port %d, status %04x, change %04x, %s\n", + port1, portstatus, portchange, portspeed(hub, portstatus)); + + if (hub->has_indicators) { + set_port_led(hub, port1, HUB_LED_AUTO); + hub->indicator[port1-1] = INDICATOR_AUTO; + } + +#ifdef CONFIG_USB_OTG + /* during HNP, don't repeat the debounce */ + if (hdev->bus->is_b_host) + portchange &= ~(USB_PORT_STAT_C_CONNECTION | + USB_PORT_STAT_C_ENABLE); +#endif + + /* Try to resuscitate an existing device */ + udev = hub->ports[port1 - 1]->child; + if ((portstatus & USB_PORT_STAT_CONNECTION) && udev && + udev->state != USB_STATE_NOTATTACHED) { + usb_lock_device(udev); + if (portstatus & USB_PORT_STAT_ENABLE) { + status = 0; /* Nothing to do */ + +#ifdef CONFIG_PM_RUNTIME + } else if (udev->state == USB_STATE_SUSPENDED && + udev->persist_enabled) { + /* For a suspended device, treat this as a + * remote wakeup event. + */ + status = usb_remote_wakeup(udev); +#endif + + } else { + status = -ENODEV; /* Don't resuscitate */ + } + usb_unlock_device(udev); + + if (status == 0) { + clear_bit(port1, hub->change_bits); + return; + } + } + + /* Disconnect any existing devices under this port */ + if (udev) { + if (hcd->phy && !hdev->parent && + !(portstatus & USB_PORT_STAT_CONNECTION)) + usb_phy_notify_disconnect(hcd->phy, udev->speed); + usb_disconnect(&hub->ports[port1 - 1]->child); + } + clear_bit(port1, hub->change_bits); + + /* We can forget about a "removed" device when there's a physical + * disconnect or the connect status changes. + */ + if (!(portstatus & USB_PORT_STAT_CONNECTION) || + (portchange & USB_PORT_STAT_C_CONNECTION)) + clear_bit(port1, hub->removed_bits); + + if (portchange & (USB_PORT_STAT_C_CONNECTION | + USB_PORT_STAT_C_ENABLE)) { + status = hub_port_debounce_be_stable(hub, port1); + if (status < 0) { + if (status != -ENODEV && printk_ratelimit()) + dev_err(hub_dev, "connect-debounce failed, " + "port %d disabled\n", port1); + portstatus &= ~USB_PORT_STAT_CONNECTION; + } else { + portstatus = status; + } + } + + /* Return now if debouncing failed or nothing is connected or + * the device was "removed". + */ + if (!(portstatus & USB_PORT_STAT_CONNECTION) || + test_bit(port1, hub->removed_bits)) { + + /* maybe switch power back on (e.g. root hub was reset) */ + if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2 + && !port_is_power_on(hub, portstatus)) + set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); + + if (portstatus & USB_PORT_STAT_ENABLE) + goto done; + return; + } + if (hub_is_superspeed(hub->hdev)) + unit_load = 150; + else + unit_load = 100; + + status = 0; + for (i = 0; i < SET_CONFIG_TRIES; i++) { + + /* reallocate for each attempt, since references + * to the previous one can escape in various ways + */ + udev = usb_alloc_dev(hdev, hdev->bus, port1); + if (!udev) { + dev_err (hub_dev, + "couldn't allocate port %d usb_device\n", + port1); + goto done; + } + + usb_set_device_state(udev, USB_STATE_POWERED); + udev->bus_mA = hub->mA_per_port; + udev->level = hdev->level + 1; + udev->wusb = hub_is_wusb(hub); + + /* Only USB 3.0 devices are connected to SuperSpeed hubs. */ + if (hub_is_superspeed(hub->hdev)) + udev->speed = USB_SPEED_SUPER; + else + udev->speed = USB_SPEED_UNKNOWN; + + choose_devnum(udev); + if (udev->devnum <= 0) { + status = -ENOTCONN; /* Don't retry */ + goto loop; + } + + /* reset (non-USB 3.0 devices) and get descriptor */ + status = hub_port_init(hub, udev, port1, i); + if (status < 0) + goto loop; + + usb_detect_quirks(udev); + if (udev->quirks & USB_QUIRK_DELAY_INIT) + msleep(1000); + + /* consecutive bus-powered hubs aren't reliable; they can + * violate the voltage drop budget. if the new child has + * a "powered" LED, users should notice we didn't enable it + * (without reading syslog), even without per-port LEDs + * on the parent. + */ + if (udev->descriptor.bDeviceClass == USB_CLASS_HUB + && udev->bus_mA <= unit_load) { + u16 devstat; + + status = usb_get_status(udev, USB_RECIP_DEVICE, 0, + &devstat); + if (status < 2) { + dev_dbg(&udev->dev, "get status %d ?\n", status); + goto loop_disable; + } + le16_to_cpus(&devstat); + if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) { + dev_err(&udev->dev, + "can't connect bus-powered hub " + "to this port\n"); + if (hub->has_indicators) { + hub->indicator[port1-1] = + INDICATOR_AMBER_BLINK; + schedule_delayed_work (&hub->leds, 0); + } + status = -ENOTCONN; /* Don't retry */ + goto loop_disable; + } + } + + /* check for devices running slower than they could */ + if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200 + && udev->speed == USB_SPEED_FULL + && highspeed_hubs != 0) + check_highspeed (hub, udev, port1); + + /* Store the parent's children[] pointer. At this point + * udev becomes globally accessible, although presumably + * no one will look at it until hdev is unlocked. + */ + status = 0; + + /* We mustn't add new devices if the parent hub has + * been disconnected; we would race with the + * recursively_mark_NOTATTACHED() routine. + */ + spin_lock_irq(&device_state_lock); + if (hdev->state == USB_STATE_NOTATTACHED) + status = -ENOTCONN; + else + hub->ports[port1 - 1]->child = udev; + spin_unlock_irq(&device_state_lock); + + /* Run it through the hoops (find a driver, etc) */ + if (!status) { + status = usb_new_device(udev); + if (status) { + spin_lock_irq(&device_state_lock); + hub->ports[port1 - 1]->child = NULL; + spin_unlock_irq(&device_state_lock); + } + } + + if (status) + goto loop_disable; + + status = hub_power_remaining(hub); + if (status) + dev_dbg(hub_dev, "%dmA power budget left\n", status); + + return; + +loop_disable: + hub_port_disable(hub, port1, 1); +loop: + usb_ep0_reinit(udev); + release_devnum(udev); + hub_free_dev(udev); + usb_put_dev(udev); + if ((status == -ENOTCONN) || (status == -ENOTSUPP)) + break; + } + if (hub->hdev->parent || + !hcd->driver->port_handed_over || + !(hcd->driver->port_handed_over)(hcd, port1)) { + if (status != -ENOTCONN && status != -ENODEV) + dev_err(hub_dev, "unable to enumerate USB device on port %d\n", + port1); + } + +done: + hub_port_disable(hub, port1, 1); + if (hcd->driver->relinquish_port && !hub->hdev->parent) + hcd->driver->relinquish_port(hcd, port1); +} + +/* Returns 1 if there was a remote wakeup and a connect status change. */ +static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, + u16 portstatus, u16 portchange) +{ + struct usb_device *hdev; + struct usb_device *udev; + int connect_change = 0; + int ret; + + hdev = hub->hdev; + udev = hub->ports[port - 1]->child; + if (!hub_is_superspeed(hdev)) { + if (!(portchange & USB_PORT_STAT_C_SUSPEND)) + return 0; + usb_clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND); + } else { + if (!udev || udev->state != USB_STATE_SUSPENDED || + (portstatus & USB_PORT_STAT_LINK_STATE) != + USB_SS_PORT_LS_U0) + return 0; + } + + if (udev) { + /* TRSMRCY = 10 msec */ + msleep(10); + + usb_lock_device(udev); + ret = usb_remote_wakeup(udev); + usb_unlock_device(udev); + if (ret < 0) + connect_change = 1; + } else { + ret = -ENODEV; + hub_port_disable(hub, port, 1); + } + dev_dbg(hub->intfdev, "resume on port %d, status %d\n", + port, ret); + return connect_change; +} + +static void hub_events(void) +{ + struct list_head *tmp; + struct usb_device *hdev; + struct usb_interface *intf; + struct usb_hub *hub; + struct device *hub_dev; + u16 hubstatus; + u16 hubchange; + u16 portstatus; + u16 portchange; + int i, ret; + int connect_change, wakeup_change; + + /* + * We restart the list every time to avoid a deadlock with + * deleting hubs downstream from this one. This should be + * safe since we delete the hub from the event list. + * Not the most efficient, but avoids deadlocks. + */ + while (1) { + + /* Grab the first entry at the beginning of the list */ + spin_lock_irq(&hub_event_lock); + if (list_empty(&hub_event_list)) { + spin_unlock_irq(&hub_event_lock); + break; + } + + tmp = hub_event_list.next; + list_del_init(tmp); + + hub = list_entry(tmp, struct usb_hub, event_list); + kref_get(&hub->kref); + hdev = hub->hdev; + usb_get_dev(hdev); + spin_unlock_irq(&hub_event_lock); + + hub_dev = hub->intfdev; + intf = to_usb_interface(hub_dev); + dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n", + hdev->state, hub->descriptor + ? hub->descriptor->bNbrPorts + : 0, + /* NOTE: expects max 15 ports... */ + (u16) hub->change_bits[0], + (u16) hub->event_bits[0]); + + /* Lock the device, then check to see if we were + * disconnected while waiting for the lock to succeed. */ + usb_lock_device(hdev); + if (unlikely(hub->disconnected)) + goto loop_disconnected; + + /* If the hub has died, clean up after it */ + if (hdev->state == USB_STATE_NOTATTACHED) { + hub->error = -ENODEV; + hub_quiesce(hub, HUB_DISCONNECT); + goto loop; + } + + /* Autoresume */ + ret = usb_autopm_get_interface(intf); + if (ret) { + dev_dbg(hub_dev, "Can't autoresume: %d\n", ret); + goto loop; + } + + /* If this is an inactive hub, do nothing */ + if (hub->quiescing) + goto loop_autopm; + + if (hub->error) { + dev_dbg (hub_dev, "resetting for error %d\n", + hub->error); + + ret = usb_reset_device(hdev); + if (ret) { + dev_dbg (hub_dev, + "error resetting hub: %d\n", ret); + goto loop_autopm; + } + + hub->nerrors = 0; + hub->error = 0; + } + + /* deal with port status changes */ + for (i = 1; i <= hub->descriptor->bNbrPorts; i++) { + if (test_bit(i, hub->busy_bits)) + continue; + connect_change = test_bit(i, hub->change_bits); + wakeup_change = test_and_clear_bit(i, hub->wakeup_bits); + if (!test_and_clear_bit(i, hub->event_bits) && + !connect_change && !wakeup_change) + continue; + + ret = hub_port_status(hub, i, + &portstatus, &portchange); + if (ret < 0) + continue; + + if (portchange & USB_PORT_STAT_C_CONNECTION) { + usb_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_CONNECTION); + connect_change = 1; + } + + if (portchange & USB_PORT_STAT_C_ENABLE) { + if (!connect_change) + dev_dbg (hub_dev, + "port %d enable change, " + "status %08x\n", + i, portstatus); + usb_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_ENABLE); + + /* + * EM interference sometimes causes badly + * shielded USB devices to be shutdown by + * the hub, this hack enables them again. + * Works at least with mouse driver. + */ + if (!(portstatus & USB_PORT_STAT_ENABLE) + && !connect_change + && hub->ports[i - 1]->child) { + dev_err (hub_dev, + "port %i " + "disabled by hub (EMI?), " + "re-enabling...\n", + i); + connect_change = 1; + } + } + + if (hub_handle_remote_wakeup(hub, i, + portstatus, portchange)) + connect_change = 1; + + if (portchange & USB_PORT_STAT_C_OVERCURRENT) { + u16 status = 0; + u16 unused; + + dev_dbg(hub_dev, "over-current change on port " + "%d\n", i); + usb_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_OVER_CURRENT); + msleep(100); /* Cool down */ + hub_power_on(hub, true); + hub_port_status(hub, i, &status, &unused); + if (status & USB_PORT_STAT_OVERCURRENT) + dev_err(hub_dev, "over-current " + "condition on port %d\n", i); + } + + if (portchange & USB_PORT_STAT_C_RESET) { + dev_dbg (hub_dev, + "reset change on port %d\n", + i); + usb_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_RESET); + } + if ((portchange & USB_PORT_STAT_C_BH_RESET) && + hub_is_superspeed(hub->hdev)) { + dev_dbg(hub_dev, + "warm reset change on port %d\n", + i); + usb_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_BH_PORT_RESET); + } + if (portchange & USB_PORT_STAT_C_LINK_STATE) { + usb_clear_port_feature(hub->hdev, i, + USB_PORT_FEAT_C_PORT_LINK_STATE); + } + if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) { + dev_warn(hub_dev, + "config error on port %d\n", + i); + usb_clear_port_feature(hub->hdev, i, + USB_PORT_FEAT_C_PORT_CONFIG_ERROR); + } + + /* Warm reset a USB3 protocol port if it's in + * SS.Inactive state. + */ + if (hub_port_warm_reset_required(hub, portstatus)) { + int status; + struct usb_device *udev = + hub->ports[i - 1]->child; + + dev_dbg(hub_dev, "warm reset port %d\n", i); + if (!udev || + !(portstatus & USB_PORT_STAT_CONNECTION) || + udev->state == USB_STATE_NOTATTACHED) { + status = hub_port_reset(hub, i, + NULL, HUB_BH_RESET_TIME, + true); + if (status < 0) + hub_port_disable(hub, i, 1); + } else { + usb_lock_device(udev); + status = usb_reset_device(udev); + usb_unlock_device(udev); + connect_change = 0; + } + } + + if (connect_change) + hub_port_connect_change(hub, i, + portstatus, portchange); + } /* end for i */ + + /* deal with hub status changes */ + if (test_and_clear_bit(0, hub->event_bits) == 0) + ; /* do nothing */ + else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0) + dev_err (hub_dev, "get_hub_status failed\n"); + else { + if (hubchange & HUB_CHANGE_LOCAL_POWER) { + dev_dbg (hub_dev, "power change\n"); + clear_hub_feature(hdev, C_HUB_LOCAL_POWER); + if (hubstatus & HUB_STATUS_LOCAL_POWER) + /* FIXME: Is this always true? */ + hub->limited_power = 1; + else + hub->limited_power = 0; + } + if (hubchange & HUB_CHANGE_OVERCURRENT) { + u16 status = 0; + u16 unused; + + dev_dbg(hub_dev, "over-current change\n"); + clear_hub_feature(hdev, C_HUB_OVER_CURRENT); + msleep(500); /* Cool down */ + hub_power_on(hub, true); + hub_hub_status(hub, &status, &unused); + if (status & HUB_STATUS_OVERCURRENT) + dev_err(hub_dev, "over-current " + "condition\n"); + } + } + + loop_autopm: + /* Balance the usb_autopm_get_interface() above */ + usb_autopm_put_interface_no_suspend(intf); + loop: + /* Balance the usb_autopm_get_interface_no_resume() in + * kick_khubd() and allow autosuspend. + */ + usb_autopm_put_interface(intf); + loop_disconnected: + usb_unlock_device(hdev); + usb_put_dev(hdev); + kref_put(&hub->kref, hub_release); + + } /* end while (1) */ +} + +static int hub_thread(void *__unused) +{ + /* khubd needs to be freezable to avoid intefering with USB-PERSIST + * port handover. Otherwise it might see that a full-speed device + * was gone before the EHCI controller had handed its port over to + * the companion full-speed controller. + */ + set_freezable(); + + do { + hub_events(); + wait_event_freezable(khubd_wait, + !list_empty(&hub_event_list) || + kthread_should_stop()); + } while (!kthread_should_stop() || !list_empty(&hub_event_list)); + + pr_debug("%s: khubd exiting\n", usbcore_name); + return 0; +} + +static const struct usb_device_id hub_id_table[] = { + { .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_INT_CLASS, + .idVendor = USB_VENDOR_GENESYS_LOGIC, + .bInterfaceClass = USB_CLASS_HUB, + .driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND}, + { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, + .bDeviceClass = USB_CLASS_HUB}, + { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, + .bInterfaceClass = USB_CLASS_HUB}, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, hub_id_table); + +static struct usb_driver hub_driver = { + .name = "hub", + .probe = hub_probe, + .disconnect = hub_disconnect, + .suspend = hub_suspend, + .resume = hub_resume, + .reset_resume = hub_reset_resume, + .pre_reset = hub_pre_reset, + .post_reset = hub_post_reset, + .unlocked_ioctl = hub_ioctl, + .id_table = hub_id_table, + .supports_autosuspend = 1, +}; + +int usb_hub_init(void) +{ + if (usb_register(&hub_driver) < 0) { + printk(KERN_ERR "%s: can't register hub driver\n", + usbcore_name); + return -1; + } + + khubd_task = kthread_run(hub_thread, NULL, "khubd"); + if (!IS_ERR(khubd_task)) + return 0; + + /* Fall through if kernel_thread failed */ + usb_deregister(&hub_driver); + printk(KERN_ERR "%s: can't start khubd\n", usbcore_name); + + return -1; +} + +void usb_hub_cleanup(void) +{ + kthread_stop(khubd_task); + + /* + * Hub resources are freed for us by usb_deregister. It calls + * usb_driver_purge on every device which in turn calls that + * devices disconnect function if it is using this driver. + * The hub_disconnect function takes care of releasing the + * individual hub resources. -greg + */ + usb_deregister(&hub_driver); +} /* usb_hub_cleanup() */ + +static int descriptors_changed(struct usb_device *udev, + struct usb_device_descriptor *old_device_descriptor) +{ + int changed = 0; + unsigned index; + unsigned serial_len = 0; + unsigned len; + unsigned old_length; + int length; + char *buf; + + if (memcmp(&udev->descriptor, old_device_descriptor, + sizeof(*old_device_descriptor)) != 0) + return 1; + + /* Since the idVendor, idProduct, and bcdDevice values in the + * device descriptor haven't changed, we will assume the + * Manufacturer and Product strings haven't changed either. + * But the SerialNumber string could be different (e.g., a + * different flash card of the same brand). + */ + if (udev->serial) + serial_len = strlen(udev->serial) + 1; + + len = serial_len; + for (index = 0; index < udev->descriptor.bNumConfigurations; index++) { + old_length = le16_to_cpu(udev->config[index].desc.wTotalLength); + len = max(len, old_length); + } + + buf = kmalloc(len, GFP_NOIO); + if (buf == NULL) { + dev_err(&udev->dev, "no mem to re-read configs after reset\n"); + /* assume the worst */ + return 1; + } + for (index = 0; index < udev->descriptor.bNumConfigurations; index++) { + old_length = le16_to_cpu(udev->config[index].desc.wTotalLength); + length = usb_get_descriptor(udev, USB_DT_CONFIG, index, buf, + old_length); + if (length != old_length) { + dev_dbg(&udev->dev, "config index %d, error %d\n", + index, length); + changed = 1; + break; + } + if (memcmp (buf, udev->rawdescriptors[index], old_length) + != 0) { + dev_dbg(&udev->dev, "config index %d changed (#%d)\n", + index, + ((struct usb_config_descriptor *) buf)-> + bConfigurationValue); + changed = 1; + break; + } + } + + if (!changed && serial_len) { + length = usb_string(udev, udev->descriptor.iSerialNumber, + buf, serial_len); + if(length < 0) + { + dev_err(&udev->dev, "usb_string return error\n"); + kfree(buf); + return 1; + } + if (length + 1 != serial_len) { + dev_dbg(&udev->dev, "serial string error %d\n", + length); + changed = 1; + } else if (memcmp(buf, udev->serial, length) != 0) { + dev_dbg(&udev->dev, "serial string changed\n"); + changed = 1; + } + } + + kfree(buf); + return changed; +} + +/** + * usb_reset_and_verify_device - perform a USB port reset to reinitialize a device + * @udev: device to reset (not in SUSPENDED or NOTATTACHED state) + * + * WARNING - don't use this routine to reset a composite device + * (one with multiple interfaces owned by separate drivers)! + * Use usb_reset_device() instead. + * + * Do a port reset, reassign the device's address, and establish its + * former operating configuration. If the reset fails, or the device's + * descriptors change from their values before the reset, or the original + * configuration and altsettings cannot be restored, a flag will be set + * telling khubd to pretend the device has been disconnected and then + * re-connected. All drivers will be unbound, and the device will be + * re-enumerated and probed all over again. + * + * Returns 0 if the reset succeeded, -ENODEV if the device has been + * flagged for logical disconnection, or some other negative error code + * if the reset wasn't even attempted. + * + * The caller must own the device lock. For example, it's safe to use + * this from a driver probe() routine after downloading new firmware. + * For calls that might not occur during probe(), drivers should lock + * the device using usb_lock_device_for_reset(). + * + * Locking exception: This routine may also be called from within an + * autoresume handler. Such usage won't conflict with other tasks + * holding the device lock because these tasks should always call + * usb_autopm_resume_device(), thereby preventing any unwanted autoresume. + */ +static int usb_reset_and_verify_device(struct usb_device *udev) +{ + struct usb_device *parent_hdev = udev->parent; + struct usb_hub *parent_hub; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct usb_device_descriptor descriptor = udev->descriptor; + int i, ret = 0; + int port1 = udev->portnum; + + if (udev->state == USB_STATE_NOTATTACHED || + udev->state == USB_STATE_SUSPENDED) { + dev_dbg(&udev->dev, "device reset not allowed in state %d\n", + udev->state); + return -EINVAL; + } + + if (!parent_hdev) { + /* this requires hcd-specific logic; see ohci_restart() */ + dev_dbg(&udev->dev, "%s for root hub!\n", __func__); + return -EISDIR; + } + parent_hub = usb_hub_to_struct_hub(parent_hdev); + + /* Disable LPM and LTM while we reset the device and reinstall the alt + * settings. Device-initiated LPM settings, and system exit latency + * settings are cleared when the device is reset, so we have to set + * them up again. + */ + ret = usb_unlocked_disable_lpm(udev); + if (ret) { + dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__); + goto re_enumerate; + } + ret = usb_disable_ltm(udev); + if (ret) { + dev_err(&udev->dev, "%s Failed to disable LTM\n.", + __func__); + goto re_enumerate; + } + + set_bit(port1, parent_hub->busy_bits); + for (i = 0; i < SET_CONFIG_TRIES; ++i) { + + /* ep0 maxpacket size may change; let the HCD know about it. + * Other endpoints will be handled by re-enumeration. */ + usb_ep0_reinit(udev); + ret = hub_port_init(parent_hub, udev, port1, i); + if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV) + break; + } + clear_bit(port1, parent_hub->busy_bits); + + if (ret < 0) + goto re_enumerate; + + /* Device might have changed firmware (DFU or similar) */ + if (descriptors_changed(udev, &descriptor)) { + dev_info(&udev->dev, "device firmware changed\n"); + udev->descriptor = descriptor; /* for disconnect() calls */ + goto re_enumerate; + } + + /* Restore the device's previous configuration */ + if (!udev->actconfig) + goto done; + + mutex_lock(hcd->bandwidth_mutex); + ret = usb_hcd_alloc_bandwidth(udev, udev->actconfig, NULL, NULL); + if (ret < 0) { + dev_warn(&udev->dev, + "Busted HC? Not enough HCD resources for " + "old configuration.\n"); + mutex_unlock(hcd->bandwidth_mutex); + goto re_enumerate; + } + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_CONFIGURATION, 0, + udev->actconfig->desc.bConfigurationValue, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); + if (ret < 0) { + dev_err(&udev->dev, + "can't restore configuration #%d (error=%d)\n", + udev->actconfig->desc.bConfigurationValue, ret); + mutex_unlock(hcd->bandwidth_mutex); + goto re_enumerate; + } + mutex_unlock(hcd->bandwidth_mutex); + usb_set_device_state(udev, USB_STATE_CONFIGURED); + + /* Put interfaces back into the same altsettings as before. + * Don't bother to send the Set-Interface request for interfaces + * that were already in altsetting 0; besides being unnecessary, + * many devices can't handle it. Instead just reset the host-side + * endpoint state. + */ + for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { + struct usb_host_config *config = udev->actconfig; + struct usb_interface *intf = config->interface[i]; + struct usb_interface_descriptor *desc; + + desc = &intf->cur_altsetting->desc; + if (desc->bAlternateSetting == 0) { + usb_disable_interface(udev, intf, true); + usb_enable_interface(udev, intf, true); + ret = 0; + } else { + /* Let the bandwidth allocation function know that this + * device has been reset, and it will have to use + * alternate setting 0 as the current alternate setting. + */ + intf->resetting_device = 1; + ret = usb_set_interface(udev, desc->bInterfaceNumber, + desc->bAlternateSetting); + intf->resetting_device = 0; + } + if (ret < 0) { + dev_err(&udev->dev, "failed to restore interface %d " + "altsetting %d (error=%d)\n", + desc->bInterfaceNumber, + desc->bAlternateSetting, + ret); + goto re_enumerate; + } + } + +done: + /* Now that the alt settings are re-installed, enable LTM and LPM. */ + usb_unlocked_enable_lpm(udev); + usb_enable_ltm(udev); + return 0; + +re_enumerate: + /* LPM state doesn't matter when we're about to destroy the device. */ + hub_port_logical_disconnect(parent_hub, port1); + return -ENODEV; +} + +/** + * usb_reset_device - warn interface drivers and perform a USB port reset + * @udev: device to reset (not in SUSPENDED or NOTATTACHED state) + * + * Warns all drivers bound to registered interfaces (using their pre_reset + * method), performs the port reset, and then lets the drivers know that + * the reset is over (using their post_reset method). + * + * Return value is the same as for usb_reset_and_verify_device(). + * + * The caller must own the device lock. For example, it's safe to use + * this from a driver probe() routine after downloading new firmware. + * For calls that might not occur during probe(), drivers should lock + * the device using usb_lock_device_for_reset(). + * + * If an interface is currently being probed or disconnected, we assume + * its driver knows how to handle resets. For all other interfaces, + * if the driver doesn't have pre_reset and post_reset methods then + * we attempt to unbind it and rebind afterward. + */ +int usb_reset_device(struct usb_device *udev) +{ + int ret; + int i; + unsigned int noio_flag; + struct usb_host_config *config = udev->actconfig; + + if (udev->state == USB_STATE_NOTATTACHED || + udev->state == USB_STATE_SUSPENDED) { + dev_dbg(&udev->dev, "device reset not allowed in state %d\n", + udev->state); + return -EINVAL; + } + + /* + * Don't allocate memory with GFP_KERNEL in current + * context to avoid possible deadlock if usb mass + * storage interface or usbnet interface(iSCSI case) + * is included in current configuration. The easist + * approach is to do it for every device reset, + * because the device 'memalloc_noio' flag may have + * not been set before reseting the usb device. + */ + noio_flag = memalloc_noio_save(); + + /* Prevent autosuspend during the reset */ + usb_autoresume_device(udev); + + if (config) { + for (i = 0; i < config->desc.bNumInterfaces; ++i) { + struct usb_interface *cintf = config->interface[i]; + struct usb_driver *drv; + int unbind = 0; + + if (cintf->dev.driver) { + drv = to_usb_driver(cintf->dev.driver); + if (drv->pre_reset && drv->post_reset) + unbind = (drv->pre_reset)(cintf); + else if (cintf->condition == + USB_INTERFACE_BOUND) + unbind = 1; + if (unbind) + usb_forced_unbind_intf(cintf); + } + } + } + + ret = usb_reset_and_verify_device(udev); + + if (config) { + for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) { + struct usb_interface *cintf = config->interface[i]; + struct usb_driver *drv; + int rebind = cintf->needs_binding; + + if (!rebind && cintf->dev.driver) { + drv = to_usb_driver(cintf->dev.driver); + if (drv->post_reset) + rebind = (drv->post_reset)(cintf); + else if (cintf->condition == + USB_INTERFACE_BOUND) + rebind = 1; + if (rebind) + cintf->needs_binding = 1; + } + } + usb_unbind_and_rebind_marked_interfaces(udev); + } + + usb_autosuspend_device(udev); + memalloc_noio_restore(noio_flag); + return ret; +} +EXPORT_SYMBOL_GPL(usb_reset_device); + + +/** + * usb_queue_reset_device - Reset a USB device from an atomic context + * @iface: USB interface belonging to the device to reset + * + * This function can be used to reset a USB device from an atomic + * context, where usb_reset_device() won't work (as it blocks). + * + * Doing a reset via this method is functionally equivalent to calling + * usb_reset_device(), except for the fact that it is delayed to a + * workqueue. This means that any drivers bound to other interfaces + * might be unbound, as well as users from usbfs in user space. + * + * Corner cases: + * + * - Scheduling two resets at the same time from two different drivers + * attached to two different interfaces of the same device is + * possible; depending on how the driver attached to each interface + * handles ->pre_reset(), the second reset might happen or not. + * + * - If a driver is unbound and it had a pending reset, the reset will + * be cancelled. + * + * - This function can be called during .probe() or .disconnect() + * times. On return from .disconnect(), any pending resets will be + * cancelled. + * + * There is no no need to lock/unlock the @reset_ws as schedule_work() + * does its own. + * + * NOTE: We don't do any reference count tracking because it is not + * needed. The lifecycle of the work_struct is tied to the + * usb_interface. Before destroying the interface we cancel the + * work_struct, so the fact that work_struct is queued and or + * running means the interface (and thus, the device) exist and + * are referenced. + */ +void usb_queue_reset_device(struct usb_interface *iface) +{ + schedule_work(&iface->reset_ws); +} +EXPORT_SYMBOL_GPL(usb_queue_reset_device); + +/** + * usb_hub_find_child - Get the pointer of child device + * attached to the port which is specified by @port1. + * @hdev: USB device belonging to the usb hub + * @port1: port num to indicate which port the child device + * is attached to. + * + * USB drivers call this function to get hub's child device + * pointer. + * + * Return NULL if input param is invalid and + * child's usb_device pointer if non-NULL. + */ +struct usb_device *usb_hub_find_child(struct usb_device *hdev, + int port1) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + if (port1 < 1 || port1 > hdev->maxchild) + return NULL; + return hub->ports[port1 - 1]->child; +} +EXPORT_SYMBOL_GPL(usb_hub_find_child); + +/** + * usb_set_hub_port_connect_type - set hub port connect type. + * @hdev: USB device belonging to the usb hub + * @port1: port num of the port + * @type: connect type of the port + */ +void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1, + enum usb_port_connect_type type) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + hub->ports[port1 - 1]->connect_type = type; +} + +/** + * usb_get_hub_port_connect_type - Get the port's connect type + * @hdev: USB device belonging to the usb hub + * @port1: port num of the port + * + * Return connect type of the port and if input params are + * invalid, return USB_PORT_CONNECT_TYPE_UNKNOWN. + */ +enum usb_port_connect_type +usb_get_hub_port_connect_type(struct usb_device *hdev, int port1) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + return hub->ports[port1 - 1]->connect_type; +} + +void usb_hub_adjust_deviceremovable(struct usb_device *hdev, + struct usb_hub_descriptor *desc) +{ + enum usb_port_connect_type connect_type; + int i; + + if (!hub_is_superspeed(hdev)) { + for (i = 1; i <= hdev->maxchild; i++) { + connect_type = usb_get_hub_port_connect_type(hdev, i); + + if (connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) { + u8 mask = 1 << (i%8); + + if (!(desc->u.hs.DeviceRemovable[i/8] & mask)) { + dev_dbg(&hdev->dev, "usb port%d's DeviceRemovable is changed to 1 according to platform information.\n", + i); + desc->u.hs.DeviceRemovable[i/8] |= mask; + } + } + } + } else { + u16 port_removable = le16_to_cpu(desc->u.ss.DeviceRemovable); + + for (i = 1; i <= hdev->maxchild; i++) { + connect_type = usb_get_hub_port_connect_type(hdev, i); + + if (connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) { + u16 mask = 1 << i; + + if (!(port_removable & mask)) { + dev_dbg(&hdev->dev, "usb port%d's DeviceRemovable is changed to 1 according to platform information.\n", + i); + port_removable |= mask; + } + } + } + + desc->u.ss.DeviceRemovable = cpu_to_le16(port_removable); + } +} + +#ifdef CONFIG_ACPI +/** + * usb_get_hub_port_acpi_handle - Get the usb port's acpi handle + * @hdev: USB device belonging to the usb hub + * @port1: port num of the port + * + * Return port's acpi handle if successful, NULL if params are + * invaild. + */ +acpi_handle usb_get_hub_port_acpi_handle(struct usb_device *hdev, + int port1) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + return DEVICE_ACPI_HANDLE(&hub->ports[port1 - 1]->dev); +} +#endif diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h new file mode 100644 index 00000000..f608b39b --- /dev/null +++ b/drivers/usb/core/hub.h @@ -0,0 +1,125 @@ +/* + * usb hub driver head file + * + * Copyright (C) 1999 Linus Torvalds + * Copyright (C) 1999 Johannes Erdfelt + * Copyright (C) 1999 Gregory P. Smith + * Copyright (C) 2001 Brad Hards (bhards@bigpond.net.au) + * Copyright (C) 2012 Intel Corp (tianyu.lan@intel.com) + * + * move struct usb_hub to this file. + * + * This program is free software; you can redistribute 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 that 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 +#include +#include +#include "usb.h" + +struct usb_hub { + struct device *intfdev; /* the "interface" device */ + struct usb_device *hdev; + struct kref kref; + struct urb *urb; /* for interrupt polling pipe */ + + /* buffer for urb ... with extra space in case of babble */ + u8 (*buffer)[8]; + union { + struct usb_hub_status hub; + struct usb_port_status port; + } *status; /* buffer for status reports */ + struct mutex status_mutex; /* for the status buffer */ + + int error; /* last reported error */ + int nerrors; /* track consecutive errors */ + + struct list_head event_list; /* hubs w/data or errs ready */ + unsigned long event_bits[1]; /* status change bitmask */ + unsigned long change_bits[1]; /* ports with logical connect + status change */ + unsigned long busy_bits[1]; /* ports being reset or + resumed */ + unsigned long removed_bits[1]; /* ports with a "removed" + device present */ + unsigned long wakeup_bits[1]; /* ports that have signaled + remote wakeup */ +#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ +#error event_bits[] is too short! +#endif + + struct usb_hub_descriptor *descriptor; /* class descriptor */ + struct usb_tt tt; /* Transaction Translator */ + + unsigned mA_per_port; /* current for each child */ +#ifdef CONFIG_PM + unsigned wakeup_enabled_descendants; +#endif + + unsigned limited_power:1; + unsigned quiescing:1; + unsigned disconnected:1; + + unsigned quirk_check_port_auto_suspend:1; + + unsigned has_indicators:1; + u8 indicator[USB_MAXCHILDREN]; + struct delayed_work leds; + struct delayed_work init_work; + struct usb_port **ports; +}; + +/** + * struct usb port - kernel's representation of a usb port + * @child: usb device attatched to the port + * @dev: generic device interface + * @port_owner: port's owner + * @connect_type: port's connect type + * @portnum: port index num based one + * @power_is_on: port's power state + * @did_runtime_put: port has done pm_runtime_put(). + */ +struct usb_port { + struct usb_device *child; + struct device dev; + struct dev_state *port_owner; + enum usb_port_connect_type connect_type; + u8 portnum; + unsigned power_is_on:1; + unsigned did_runtime_put:1; +}; + +#define to_usb_port(_dev) \ + container_of(_dev, struct usb_port, dev) + +extern int usb_hub_create_port_device(struct usb_hub *hub, + int port1); +extern void usb_hub_remove_port_device(struct usb_hub *hub, + int port1); +extern int usb_hub_set_port_power(struct usb_device *hdev, + int port1, bool set); +extern struct usb_hub *usb_hub_to_struct_hub(struct usb_device *hdev); +extern int hub_port_debounce(struct usb_hub *hub, int port1, + bool must_be_connected); +extern int usb_clear_port_feature(struct usb_device *hdev, + int port1, int feature); + +static inline int hub_port_debounce_be_connected(struct usb_hub *hub, + int port1) +{ + return hub_port_debounce(hub, port1, true); +} + +static inline int hub_port_debounce_be_stable(struct usb_hub *hub, + int port1) +{ + return hub_port_debounce(hub, port1, false); +} + diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c new file mode 100644 index 00000000..444d30e3 --- /dev/null +++ b/drivers/usb/core/message.c @@ -0,0 +1,1994 @@ +/* + * message.c - synchronous message handling + */ + +#include /* for scatterlist macros */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for usbcore internals */ +#include + +#include "usb.h" + +static void cancel_async_set_config(struct usb_device *udev); + +struct api_context { + struct completion done; + int status; +}; + +static void usb_api_blocking_completion(struct urb *urb) +{ + struct api_context *ctx = urb->context; + + ctx->status = urb->status; + complete(&ctx->done); +} + + +/* + * Starts urb and waits for completion or timeout. Note that this call + * is NOT interruptible. Many device driver i/o requests should be + * interruptible and therefore these drivers should implement their + * own interruptible routines. + */ +static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length) +{ + struct api_context ctx; + unsigned long expire; + int retval; + + init_completion(&ctx.done); + urb->context = &ctx; + urb->actual_length = 0; + retval = usb_submit_urb(urb, GFP_NOIO); + if (unlikely(retval)) + goto out; + + expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT; + if (!wait_for_completion_timeout(&ctx.done, expire)) { + usb_kill_urb(urb); + retval = (ctx.status == -ENOENT ? -ETIMEDOUT : ctx.status); + + dev_dbg(&urb->dev->dev, + "%s timed out on ep%d%s len=%u/%u\n", + current->comm, + usb_endpoint_num(&urb->ep->desc), + usb_urb_dir_in(urb) ? "in" : "out", + urb->actual_length, + urb->transfer_buffer_length); + } else + retval = ctx.status; +out: + if (actual_length) + *actual_length = urb->actual_length; + + usb_free_urb(urb); + return retval; +} + +/*-------------------------------------------------------------------*/ +/* returns status (negative) or length (positive) */ +static int usb_internal_control_msg(struct usb_device *usb_dev, + unsigned int pipe, + struct usb_ctrlrequest *cmd, + void *data, int len, int timeout) +{ + struct urb *urb; + int retv; + int length; + + urb = usb_alloc_urb(0, GFP_NOIO); + if (!urb) + return -ENOMEM; + + usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data, + len, usb_api_blocking_completion, NULL); + + retv = usb_start_wait_urb(urb, timeout, &length); + if (retv < 0) + return retv; + else + return length; +} + +/** + * usb_control_msg - Builds a control urb, sends it off and waits for completion + * @dev: pointer to the usb device to send the message to + * @pipe: endpoint "pipe" to send the message to + * @request: USB message request value + * @requesttype: USB message request type value + * @value: USB message value + * @index: USB message index value + * @data: pointer to the data to send + * @size: length in bytes of the data to send + * @timeout: time in msecs to wait for the message to complete before timing + * out (if 0 the wait is forever) + * + * Context: !in_interrupt () + * + * This function sends a simple control message to a specified endpoint and + * waits for the message to complete, or timeout. + * + * If successful, it returns the number of bytes transferred, otherwise a + * negative error number. + * + * Don't use this function from within an interrupt context, like a bottom half + * handler. If you need an asynchronous message, or need to send a message + * from within interrupt context, use usb_submit_urb(). + * If a thread in your driver uses this call, make sure your disconnect() + * method can wait for it to complete. Since you don't have a handle on the + * URB used, you can't cancel the request. + */ +int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, + __u8 requesttype, __u16 value, __u16 index, void *data, + __u16 size, int timeout) +{ + struct usb_ctrlrequest *dr; + int ret; + + dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO); + if (!dr) + return -ENOMEM; + + dr->bRequestType = requesttype; + dr->bRequest = request; + dr->wValue = cpu_to_le16(value); + dr->wIndex = cpu_to_le16(index); + dr->wLength = cpu_to_le16(size); + + ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout); + + kfree(dr); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_control_msg); + +/** + * usb_interrupt_msg - Builds an interrupt urb, sends it off and waits for completion + * @usb_dev: pointer to the usb device to send the message to + * @pipe: endpoint "pipe" to send the message to + * @data: pointer to the data to send + * @len: length in bytes of the data to send + * @actual_length: pointer to a location to put the actual length transferred + * in bytes + * @timeout: time in msecs to wait for the message to complete before + * timing out (if 0 the wait is forever) + * + * Context: !in_interrupt () + * + * This function sends a simple interrupt message to a specified endpoint and + * waits for the message to complete, or timeout. + * + * If successful, it returns 0, otherwise a negative error number. The number + * of actual bytes transferred will be stored in the actual_length paramater. + * + * Don't use this function from within an interrupt context, like a bottom half + * handler. If you need an asynchronous message, or need to send a message + * from within interrupt context, use usb_submit_urb() If a thread in your + * driver uses this call, make sure your disconnect() method can wait for it to + * complete. Since you don't have a handle on the URB used, you can't cancel + * the request. + */ +int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, + void *data, int len, int *actual_length, int timeout) +{ + return usb_bulk_msg(usb_dev, pipe, data, len, actual_length, timeout); +} +EXPORT_SYMBOL_GPL(usb_interrupt_msg); + +/** + * usb_bulk_msg - Builds a bulk urb, sends it off and waits for completion + * @usb_dev: pointer to the usb device to send the message to + * @pipe: endpoint "pipe" to send the message to + * @data: pointer to the data to send + * @len: length in bytes of the data to send + * @actual_length: pointer to a location to put the actual length transferred + * in bytes + * @timeout: time in msecs to wait for the message to complete before + * timing out (if 0 the wait is forever) + * + * Context: !in_interrupt () + * + * This function sends a simple bulk message to a specified endpoint + * and waits for the message to complete, or timeout. + * + * If successful, it returns 0, otherwise a negative error number. The number + * of actual bytes transferred will be stored in the actual_length paramater. + * + * Don't use this function from within an interrupt context, like a bottom half + * handler. If you need an asynchronous message, or need to send a message + * from within interrupt context, use usb_submit_urb() If a thread in your + * driver uses this call, make sure your disconnect() method can wait for it to + * complete. Since you don't have a handle on the URB used, you can't cancel + * the request. + * + * Because there is no usb_interrupt_msg() and no USBDEVFS_INTERRUPT ioctl, + * users are forced to abuse this routine by using it to submit URBs for + * interrupt endpoints. We will take the liberty of creating an interrupt URB + * (with the default interval) if the target is an interrupt endpoint. + */ +int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, + void *data, int len, int *actual_length, int timeout) +{ + struct urb *urb; + struct usb_host_endpoint *ep; + + ep = usb_pipe_endpoint(usb_dev, pipe); + if (!ep || len < 0) + return -EINVAL; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT) { + pipe = (pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30); + usb_fill_int_urb(urb, usb_dev, pipe, data, len, + usb_api_blocking_completion, NULL, + ep->desc.bInterval); + } else + usb_fill_bulk_urb(urb, usb_dev, pipe, data, len, + usb_api_blocking_completion, NULL); + + return usb_start_wait_urb(urb, timeout, actual_length); +} +EXPORT_SYMBOL_GPL(usb_bulk_msg); + +/*-------------------------------------------------------------------*/ + +static void sg_clean(struct usb_sg_request *io) +{ + if (io->urbs) { + while (io->entries--) + usb_free_urb(io->urbs [io->entries]); + kfree(io->urbs); + io->urbs = NULL; + } + io->dev = NULL; +} + +static void sg_complete(struct urb *urb) +{ + struct usb_sg_request *io = urb->context; + int status = urb->status; + + spin_lock(&io->lock); + + /* In 2.5 we require hcds' endpoint queues not to progress after fault + * reports, until the completion callback (this!) returns. That lets + * device driver code (like this routine) unlink queued urbs first, + * if it needs to, since the HC won't work on them at all. So it's + * not possible for page N+1 to overwrite page N, and so on. + * + * That's only for "hard" faults; "soft" faults (unlinks) sometimes + * complete before the HCD can get requests away from hardware, + * though never during cleanup after a hard fault. + */ + if (io->status + && (io->status != -ECONNRESET + || status != -ECONNRESET) + && urb->actual_length) { + dev_err(io->dev->bus->controller, + "dev %s ep%d%s scatterlist error %d/%d\n", + io->dev->devpath, + usb_endpoint_num(&urb->ep->desc), + usb_urb_dir_in(urb) ? "in" : "out", + status, io->status); + /* BUG (); */ + } + + if (io->status == 0 && status && status != -ECONNRESET) { + int i, found, retval; + + io->status = status; + + /* the previous urbs, and this one, completed already. + * unlink pending urbs so they won't rx/tx bad data. + * careful: unlink can sometimes be synchronous... + */ + spin_unlock(&io->lock); + for (i = 0, found = 0; i < io->entries; i++) { + if (!io->urbs [i] || !io->urbs [i]->dev) + continue; + if (found) { + retval = usb_unlink_urb(io->urbs [i]); + if (retval != -EINPROGRESS && + retval != -ENODEV && + retval != -EBUSY && + retval != -EIDRM) + dev_err(&io->dev->dev, + "%s, unlink --> %d\n", + __func__, retval); + } else if (urb == io->urbs [i]) + found = 1; + } + spin_lock(&io->lock); + } + + /* on the last completion, signal usb_sg_wait() */ + io->bytes += urb->actual_length; + io->count--; + if (!io->count) + complete(&io->complete); + + spin_unlock(&io->lock); +} + + +/** + * usb_sg_init - initializes scatterlist-based bulk/interrupt I/O request + * @io: request block being initialized. until usb_sg_wait() returns, + * treat this as a pointer to an opaque block of memory, + * @dev: the usb device that will send or receive the data + * @pipe: endpoint "pipe" used to transfer the data + * @period: polling rate for interrupt endpoints, in frames or + * (for high speed endpoints) microframes; ignored for bulk + * @sg: scatterlist entries + * @nents: how many entries in the scatterlist + * @length: how many bytes to send from the scatterlist, or zero to + * send every byte identified in the list. + * @mem_flags: SLAB_* flags affecting memory allocations in this call + * + * Returns zero for success, else a negative errno value. This initializes a + * scatter/gather request, allocating resources such as I/O mappings and urb + * memory (except maybe memory used by USB controller drivers). + * + * The request must be issued using usb_sg_wait(), which waits for the I/O to + * complete (or to be canceled) and then cleans up all resources allocated by + * usb_sg_init(). + * + * The request may be canceled with usb_sg_cancel(), either before or after + * usb_sg_wait() is called. + */ +int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev, + unsigned pipe, unsigned period, struct scatterlist *sg, + int nents, size_t length, gfp_t mem_flags) +{ + int i; + int urb_flags; + int use_sg; + + if (!io || !dev || !sg + || usb_pipecontrol(pipe) + || usb_pipeisoc(pipe) + || nents <= 0) + return -EINVAL; + + spin_lock_init(&io->lock); + io->dev = dev; + io->pipe = pipe; + + if (dev->bus->sg_tablesize > 0) { + use_sg = true; + io->entries = 1; + } else { + use_sg = false; + io->entries = nents; + } + + /* initialize all the urbs we'll use */ + io->urbs = kmalloc(io->entries * sizeof *io->urbs, mem_flags); + if (!io->urbs) + goto nomem; + + urb_flags = URB_NO_INTERRUPT; + if (usb_pipein(pipe)) + urb_flags |= URB_SHORT_NOT_OK; + + for_each_sg(sg, sg, io->entries, i) { + struct urb *urb; + unsigned len; + + urb = usb_alloc_urb(0, mem_flags); + if (!urb) { + io->entries = i; + goto nomem; + } + io->urbs[i] = urb; + + urb->dev = NULL; + urb->pipe = pipe; + urb->interval = period; + urb->transfer_flags = urb_flags; + urb->complete = sg_complete; + urb->context = io; + urb->sg = sg; + + if (use_sg) { + /* There is no single transfer buffer */ + urb->transfer_buffer = NULL; + urb->num_sgs = nents; + + /* A length of zero means transfer the whole sg list */ + len = length; + if (len == 0) { + struct scatterlist *sg2; + int j; + + for_each_sg(sg, sg2, nents, j) + len += sg2->length; + } + } else { + /* + * Some systems can't use DMA; they use PIO instead. + * For their sakes, transfer_buffer is set whenever + * possible. + */ + if (!PageHighMem(sg_page(sg))) + urb->transfer_buffer = sg_virt(sg); + else + urb->transfer_buffer = NULL; + + len = sg->length; + if (length) { + len = min_t(size_t, len, length); + length -= len; + if (length == 0) + io->entries = i + 1; + } + } + urb->transfer_buffer_length = len; + } + io->urbs[--i]->transfer_flags &= ~URB_NO_INTERRUPT; + + /* transaction state */ + io->count = io->entries; + io->status = 0; + io->bytes = 0; + init_completion(&io->complete); + return 0; + +nomem: + sg_clean(io); + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(usb_sg_init); + +/** + * usb_sg_wait - synchronously execute scatter/gather request + * @io: request block handle, as initialized with usb_sg_init(). + * some fields become accessible when this call returns. + * Context: !in_interrupt () + * + * This function blocks until the specified I/O operation completes. It + * leverages the grouping of the related I/O requests to get good transfer + * rates, by queueing the requests. At higher speeds, such queuing can + * significantly improve USB throughput. + * + * There are three kinds of completion for this function. + * (1) success, where io->status is zero. The number of io->bytes + * transferred is as requested. + * (2) error, where io->status is a negative errno value. The number + * of io->bytes transferred before the error is usually less + * than requested, and can be nonzero. + * (3) cancellation, a type of error with status -ECONNRESET that + * is initiated by usb_sg_cancel(). + * + * When this function returns, all memory allocated through usb_sg_init() or + * this call will have been freed. The request block parameter may still be + * passed to usb_sg_cancel(), or it may be freed. It could also be + * reinitialized and then reused. + * + * Data Transfer Rates: + * + * Bulk transfers are valid for full or high speed endpoints. + * The best full speed data rate is 19 packets of 64 bytes each + * per frame, or 1216 bytes per millisecond. + * The best high speed data rate is 13 packets of 512 bytes each + * per microframe, or 52 KBytes per millisecond. + * + * The reason to use interrupt transfers through this API would most likely + * be to reserve high speed bandwidth, where up to 24 KBytes per millisecond + * could be transferred. That capability is less useful for low or full + * speed interrupt endpoints, which allow at most one packet per millisecond, + * of at most 8 or 64 bytes (respectively). + * + * It is not necessary to call this function to reserve bandwidth for devices + * under an xHCI host controller, as the bandwidth is reserved when the + * configuration or interface alt setting is selected. + */ +void usb_sg_wait(struct usb_sg_request *io) +{ + int i; + int entries = io->entries; + + /* queue the urbs. */ + spin_lock_irq(&io->lock); + i = 0; + while (i < entries && !io->status) { + int retval; + + io->urbs[i]->dev = io->dev; + retval = usb_submit_urb(io->urbs [i], GFP_ATOMIC); + + /* after we submit, let completions or cancelations fire; + * we handshake using io->status. + */ + spin_unlock_irq(&io->lock); + switch (retval) { + /* maybe we retrying will recover */ + case -ENXIO: /* hc didn't queue this one */ + case -EAGAIN: + case -ENOMEM: + retval = 0; + yield(); + break; + + /* no error? continue immediately. + * + * NOTE: to work better with UHCI (4K I/O buffer may + * need 3K of TDs) it may be good to limit how many + * URBs are queued at once; N milliseconds? + */ + case 0: + ++i; + cpu_relax(); + break; + + /* fail any uncompleted urbs */ + default: + io->urbs[i]->status = retval; + dev_dbg(&io->dev->dev, "%s, submit --> %d\n", + __func__, retval); + usb_sg_cancel(io); + } + spin_lock_irq(&io->lock); + if (retval && (io->status == 0 || io->status == -ECONNRESET)) + io->status = retval; + } + io->count -= entries - i; + if (io->count == 0) + complete(&io->complete); + spin_unlock_irq(&io->lock); + + /* OK, yes, this could be packaged as non-blocking. + * So could the submit loop above ... but it's easier to + * solve neither problem than to solve both! + */ + wait_for_completion(&io->complete); + + sg_clean(io); +} +EXPORT_SYMBOL_GPL(usb_sg_wait); + +/** + * usb_sg_cancel - stop scatter/gather i/o issued by usb_sg_wait() + * @io: request block, initialized with usb_sg_init() + * + * This stops a request after it has been started by usb_sg_wait(). + * It can also prevents one initialized by usb_sg_init() from starting, + * so that call just frees resources allocated to the request. + */ +void usb_sg_cancel(struct usb_sg_request *io) +{ + unsigned long flags; + + spin_lock_irqsave(&io->lock, flags); + + /* shut everything down, if it didn't already */ + if (!io->status) { + int i; + + io->status = -ECONNRESET; + spin_unlock(&io->lock); + for (i = 0; i < io->entries; i++) { + int retval; + + if (!io->urbs [i]->dev) + continue; + retval = usb_unlink_urb(io->urbs [i]); + if (retval != -EINPROGRESS + && retval != -ENODEV + && retval != -EBUSY + && retval != -EIDRM) + dev_warn(&io->dev->dev, "%s, unlink --> %d\n", + __func__, retval); + } + spin_lock(&io->lock); + } + spin_unlock_irqrestore(&io->lock, flags); +} +EXPORT_SYMBOL_GPL(usb_sg_cancel); + +/*-------------------------------------------------------------------*/ + +/** + * usb_get_descriptor - issues a generic GET_DESCRIPTOR request + * @dev: the device whose descriptor is being retrieved + * @type: the descriptor type (USB_DT_*) + * @index: the number of the descriptor + * @buf: where to put the descriptor + * @size: how big is "buf"? + * Context: !in_interrupt () + * + * Gets a USB descriptor. Convenience functions exist to simplify + * getting some types of descriptors. Use + * usb_get_string() or usb_string() for USB_DT_STRING. + * Device (USB_DT_DEVICE) and configuration descriptors (USB_DT_CONFIG) + * are part of the device structure. + * In addition to a number of USB-standard descriptors, some + * devices also use class-specific or vendor-specific descriptors. + * + * This call is synchronous, and may not be used in an interrupt context. + * + * Returns the number of bytes received on success, or else the status code + * returned by the underlying usb_control_msg() call. + */ +int usb_get_descriptor(struct usb_device *dev, unsigned char type, + unsigned char index, void *buf, int size) +{ + int i; + int result; + + memset(buf, 0, size); /* Make sure we parse really received data */ + + for (i = 0; i < 3; ++i) { + /* retry on length 0 or error; some devices are flakey */ + result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (type << 8) + index, 0, buf, size, + USB_CTRL_GET_TIMEOUT); + if (result <= 0 && result != -ETIMEDOUT) + continue; + if (result > 1 && ((u8 *)buf)[1] != type) { + result = -ENODATA; + continue; + } + break; + } + return result; +} +EXPORT_SYMBOL_GPL(usb_get_descriptor); + +/** + * usb_get_string - gets a string descriptor + * @dev: the device whose string descriptor is being retrieved + * @langid: code for language chosen (from string descriptor zero) + * @index: the number of the descriptor + * @buf: where to put the string + * @size: how big is "buf"? + * Context: !in_interrupt () + * + * Retrieves a string, encoded using UTF-16LE (Unicode, 16 bits per character, + * in little-endian byte order). + * The usb_string() function will often be a convenient way to turn + * these strings into kernel-printable form. + * + * Strings may be referenced in device, configuration, interface, or other + * descriptors, and could also be used in vendor-specific ways. + * + * This call is synchronous, and may not be used in an interrupt context. + * + * Returns the number of bytes received on success, or else the status code + * returned by the underlying usb_control_msg() call. + */ +static int usb_get_string(struct usb_device *dev, unsigned short langid, + unsigned char index, void *buf, int size) +{ + int i; + int result; + + for (i = 0; i < 3; ++i) { + /* retry on length 0 or stall; some devices are flakey */ + result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (USB_DT_STRING << 8) + index, langid, buf, size, + USB_CTRL_GET_TIMEOUT); + if (result == 0 || result == -EPIPE) + continue; + if (result > 1 && ((u8 *) buf)[1] != USB_DT_STRING) { + result = -ENODATA; + continue; + } + break; + } + return result; +} + +static void usb_try_string_workarounds(unsigned char *buf, int *length) +{ + int newlength, oldlength = *length; + + for (newlength = 2; newlength + 1 < oldlength; newlength += 2) + if (!isprint(buf[newlength]) || buf[newlength + 1]) + break; + + if (newlength > 2) { + buf[0] = newlength; + *length = newlength; + } +} + +static int usb_string_sub(struct usb_device *dev, unsigned int langid, + unsigned int index, unsigned char *buf) +{ + int rc; + + /* Try to read the string descriptor by asking for the maximum + * possible number of bytes */ + if (dev->quirks & USB_QUIRK_STRING_FETCH_255) + rc = -EIO; + else + rc = usb_get_string(dev, langid, index, buf, 255); + + /* If that failed try to read the descriptor length, then + * ask for just that many bytes */ + if (rc < 2) { + rc = usb_get_string(dev, langid, index, buf, 2); + if (rc == 2) + rc = usb_get_string(dev, langid, index, buf, buf[0]); + } + + if (rc >= 2) { + if (!buf[0] && !buf[1]) + usb_try_string_workarounds(buf, &rc); + + /* There might be extra junk at the end of the descriptor */ + if (buf[0] < rc) + rc = buf[0]; + + rc = rc - (rc & 1); /* force a multiple of two */ + } + + if (rc < 2) + rc = (rc < 0 ? rc : -EINVAL); + + return rc; +} + +static int usb_get_langid(struct usb_device *dev, unsigned char *tbuf) +{ + int err; + + if (dev->have_langid) + return 0; + + if (dev->string_langid < 0) + return -EPIPE; + + err = usb_string_sub(dev, 0, 0, tbuf); + + /* If the string was reported but is malformed, default to english + * (0x0409) */ + if (err == -ENODATA || (err > 0 && err < 4)) { + dev->string_langid = 0x0409; + dev->have_langid = 1; + dev_err(&dev->dev, + "string descriptor 0 malformed (err = %d), " + "defaulting to 0x%04x\n", + err, dev->string_langid); + return 0; + } + + /* In case of all other errors, we assume the device is not able to + * deal with strings at all. Set string_langid to -1 in order to + * prevent any string to be retrieved from the device */ + if (err < 0) { + dev_err(&dev->dev, "string descriptor 0 read error: %d\n", + err); + dev->string_langid = -1; + return -EPIPE; + } + + /* always use the first langid listed */ + dev->string_langid = tbuf[2] | (tbuf[3] << 8); + dev->have_langid = 1; + dev_dbg(&dev->dev, "default language 0x%04x\n", + dev->string_langid); + return 0; +} + +/** + * usb_string - returns UTF-8 version of a string descriptor + * @dev: the device whose string descriptor is being retrieved + * @index: the number of the descriptor + * @buf: where to put the string + * @size: how big is "buf"? + * Context: !in_interrupt () + * + * This converts the UTF-16LE encoded strings returned by devices, from + * usb_get_string_descriptor(), to null-terminated UTF-8 encoded ones + * that are more usable in most kernel contexts. Note that this function + * chooses strings in the first language supported by the device. + * + * This call is synchronous, and may not be used in an interrupt context. + * + * Returns length of the string (>= 0) or usb_control_msg status (< 0). + */ +int usb_string(struct usb_device *dev, int index, char *buf, size_t size) +{ + unsigned char *tbuf; + int err; + + if (dev->state == USB_STATE_SUSPENDED) + return -EHOSTUNREACH; + if (size <= 0 || !buf || !index) + return -EINVAL; + buf[0] = 0; + tbuf = kmalloc(256, GFP_NOIO); + if (!tbuf) + return -ENOMEM; + + err = usb_get_langid(dev, tbuf); + if (err < 0) + goto errout; + + err = usb_string_sub(dev, dev->string_langid, index, tbuf); + if (err < 0) + goto errout; + + size--; /* leave room for trailing NULL char in output buffer */ + err = utf16s_to_utf8s((wchar_t *) &tbuf[2], (err - 2) / 2, + UTF16_LITTLE_ENDIAN, buf, size); + buf[err] = 0; + + if (tbuf[1] != USB_DT_STRING) + dev_dbg(&dev->dev, + "wrong descriptor type %02x for string %d (\"%s\")\n", + tbuf[1], index, buf); + + errout: + kfree(tbuf); + return err; +} +EXPORT_SYMBOL_GPL(usb_string); + +/* one UTF-8-encoded 16-bit character has at most three bytes */ +#define MAX_USB_STRING_SIZE (127 * 3 + 1) + +/** + * usb_cache_string - read a string descriptor and cache it for later use + * @udev: the device whose string descriptor is being read + * @index: the descriptor index + * + * Returns a pointer to a kmalloc'ed buffer containing the descriptor string, + * or NULL if the index is 0 or the string could not be read. + */ +char *usb_cache_string(struct usb_device *udev, int index) +{ + char *buf; + char *smallbuf = NULL; + int len; + + if (index <= 0) + return NULL; + + buf = kmalloc(MAX_USB_STRING_SIZE, GFP_NOIO); + if (buf) { + len = usb_string(udev, index, buf, MAX_USB_STRING_SIZE); + if (len > 0) { + smallbuf = kmalloc(++len, GFP_NOIO); + if (!smallbuf) + return buf; + memcpy(smallbuf, buf, len); + } + kfree(buf); + } + return smallbuf; +} + +/* + * usb_get_device_descriptor - (re)reads the device descriptor (usbcore) + * @dev: the device whose device descriptor is being updated + * @size: how much of the descriptor to read + * Context: !in_interrupt () + * + * Updates the copy of the device descriptor stored in the device structure, + * which dedicates space for this purpose. + * + * Not exported, only for use by the core. If drivers really want to read + * the device descriptor directly, they can call usb_get_descriptor() with + * type = USB_DT_DEVICE and index = 0. + * + * This call is synchronous, and may not be used in an interrupt context. + * + * Returns the number of bytes received on success, or else the status code + * returned by the underlying usb_control_msg() call. + */ +int usb_get_device_descriptor(struct usb_device *dev, unsigned int size) +{ + struct usb_device_descriptor *desc; + int ret; + + if (size > sizeof(*desc)) + return -EINVAL; + desc = kmalloc(sizeof(*desc), GFP_NOIO); + if (!desc) + return -ENOMEM; + + ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size); + if (ret >= 0) + memcpy(&dev->descriptor, desc, size); + kfree(desc); + return ret; +} + +/** + * usb_get_status - issues a GET_STATUS call + * @dev: the device whose status is being checked + * @type: USB_RECIP_*; for device, interface, or endpoint + * @target: zero (for device), else interface or endpoint number + * @data: pointer to two bytes of bitmap data + * Context: !in_interrupt () + * + * Returns device, interface, or endpoint status. Normally only of + * interest to see if the device is self powered, or has enabled the + * remote wakeup facility; or whether a bulk or interrupt endpoint + * is halted ("stalled"). + * + * Bits in these status bitmaps are set using the SET_FEATURE request, + * and cleared using the CLEAR_FEATURE request. The usb_clear_halt() + * function should be used to clear halt ("stall") status. + * + * This call is synchronous, and may not be used in an interrupt context. + * + * Returns the number of bytes received on success, or else the status code + * returned by the underlying usb_control_msg() call. + */ +int usb_get_status(struct usb_device *dev, int type, int target, void *data) +{ + int ret; + u16 *status = kmalloc(sizeof(*status), GFP_KERNEL); + + if (!status) + return -ENOMEM; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | type, 0, target, status, + sizeof(*status), USB_CTRL_GET_TIMEOUT); + + *(u16 *)data = *status; + kfree(status); + return ret; +} +EXPORT_SYMBOL_GPL(usb_get_status); + +/** + * usb_clear_halt - tells device to clear endpoint halt/stall condition + * @dev: device whose endpoint is halted + * @pipe: endpoint "pipe" being cleared + * Context: !in_interrupt () + * + * This is used to clear halt conditions for bulk and interrupt endpoints, + * as reported by URB completion status. Endpoints that are halted are + * sometimes referred to as being "stalled". Such endpoints are unable + * to transmit or receive data until the halt status is cleared. Any URBs + * queued for such an endpoint should normally be unlinked by the driver + * before clearing the halt condition, as described in sections 5.7.5 + * and 5.8.5 of the USB 2.0 spec. + * + * Note that control and isochronous endpoints don't halt, although control + * endpoints report "protocol stall" (for unsupported requests) using the + * same status code used to report a true stall. + * + * This call is synchronous, and may not be used in an interrupt context. + * + * Returns zero on success, or else the status code returned by the + * underlying usb_control_msg() call. + */ +int usb_clear_halt(struct usb_device *dev, int pipe) +{ + int result; + int endp = usb_pipeendpoint(pipe); + + if (usb_pipein(pipe)) + endp |= USB_DIR_IN; + + /* we don't care if it wasn't halted first. in fact some devices + * (like some ibmcam model 1 units) seem to expect hosts to make + * this request for iso endpoints, which can't halt! + */ + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, endp, NULL, 0, + USB_CTRL_SET_TIMEOUT); + + /* don't un-halt or force to DATA0 except on success */ + if (result < 0) + return result; + + /* NOTE: seems like Microsoft and Apple don't bother verifying + * the clear "took", so some devices could lock up if you check... + * such as the Hagiwara FlashGate DUAL. So we won't bother. + * + * NOTE: make sure the logic here doesn't diverge much from + * the copy in usb-storage, for as long as we need two copies. + */ + + usb_reset_endpoint(dev, endp); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_clear_halt); + +static int create_intf_ep_devs(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *alt = intf->cur_altsetting; + int i; + + if (intf->ep_devs_created || intf->unregistering) + return 0; + + for (i = 0; i < alt->desc.bNumEndpoints; ++i) + (void) usb_create_ep_devs(&intf->dev, &alt->endpoint[i], udev); + intf->ep_devs_created = 1; + return 0; +} + +static void remove_intf_ep_devs(struct usb_interface *intf) +{ + struct usb_host_interface *alt = intf->cur_altsetting; + int i; + + if (!intf->ep_devs_created) + return; + + for (i = 0; i < alt->desc.bNumEndpoints; ++i) + usb_remove_ep_devs(&alt->endpoint[i]); + intf->ep_devs_created = 0; +} + +/** + * usb_disable_endpoint -- Disable an endpoint by address + * @dev: the device whose endpoint is being disabled + * @epaddr: the endpoint's address. Endpoint number for output, + * endpoint number + USB_DIR_IN for input + * @reset_hardware: flag to erase any endpoint state stored in the + * controller hardware + * + * Disables the endpoint for URB submission and nukes all pending URBs. + * If @reset_hardware is set then also deallocates hcd/hardware state + * for the endpoint. + */ +void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + bool reset_hardware) +{ + unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK; + struct usb_host_endpoint *ep; + + if (!dev) + return; + + if (usb_endpoint_out(epaddr)) { + ep = dev->ep_out[epnum]; + if (reset_hardware) + dev->ep_out[epnum] = NULL; + } else { + ep = dev->ep_in[epnum]; + if (reset_hardware) + dev->ep_in[epnum] = NULL; + } + if (ep) { + ep->enabled = 0; + usb_hcd_flush_endpoint(dev, ep); + if (reset_hardware) + usb_hcd_disable_endpoint(dev, ep); + } +} + +/** + * usb_reset_endpoint - Reset an endpoint's state. + * @dev: the device whose endpoint is to be reset + * @epaddr: the endpoint's address. Endpoint number for output, + * endpoint number + USB_DIR_IN for input + * + * Resets any host-side endpoint state such as the toggle bit, + * sequence number or current window. + */ +void usb_reset_endpoint(struct usb_device *dev, unsigned int epaddr) +{ + unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK; + struct usb_host_endpoint *ep; + + if (usb_endpoint_out(epaddr)) + ep = dev->ep_out[epnum]; + else + ep = dev->ep_in[epnum]; + if (ep) + usb_hcd_reset_endpoint(dev, ep); +} +EXPORT_SYMBOL_GPL(usb_reset_endpoint); + + +/** + * usb_disable_interface -- Disable all endpoints for an interface + * @dev: the device whose interface is being disabled + * @intf: pointer to the interface descriptor + * @reset_hardware: flag to erase any endpoint state stored in the + * controller hardware + * + * Disables all the endpoints for the interface's current altsetting. + */ +void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf, + bool reset_hardware) +{ + struct usb_host_interface *alt = intf->cur_altsetting; + int i; + + for (i = 0; i < alt->desc.bNumEndpoints; ++i) { + usb_disable_endpoint(dev, + alt->endpoint[i].desc.bEndpointAddress, + reset_hardware); + } +} + +/** + * usb_disable_device - Disable all the endpoints for a USB device + * @dev: the device whose endpoints are being disabled + * @skip_ep0: 0 to disable endpoint 0, 1 to skip it. + * + * Disables all the device's endpoints, potentially including endpoint 0. + * Deallocates hcd/hardware state for the endpoints (nuking all or most + * pending urbs) and usbcore state for the interfaces, so that usbcore + * must usb_set_configuration() before any interfaces could be used. + */ +void usb_disable_device(struct usb_device *dev, int skip_ep0) +{ + int i; + struct usb_hcd *hcd = bus_to_hcd(dev->bus); + + /* getting rid of interfaces will disconnect + * any drivers bound to them (a key side effect) + */ + if (dev->actconfig) { + /* + * FIXME: In order to avoid self-deadlock involving the + * bandwidth_mutex, we have to mark all the interfaces + * before unregistering any of them. + */ + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) + dev->actconfig->interface[i]->unregistering = 1; + + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + struct usb_interface *interface; + + /* remove this interface if it has been registered */ + interface = dev->actconfig->interface[i]; + if (!device_is_registered(&interface->dev)) + continue; + dev_dbg(&dev->dev, "unregistering interface %s\n", + dev_name(&interface->dev)); + remove_intf_ep_devs(interface); + device_del(&interface->dev); + } + + /* Now that the interfaces are unbound, nobody should + * try to access them. + */ + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + put_device(&dev->actconfig->interface[i]->dev); + dev->actconfig->interface[i] = NULL; + } + usb_unlocked_disable_lpm(dev); + usb_disable_ltm(dev); + dev->actconfig = NULL; + if (dev->state == USB_STATE_CONFIGURED) + usb_set_device_state(dev, USB_STATE_ADDRESS); + } + + dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__, + skip_ep0 ? "non-ep0" : "all"); + if (hcd->driver->check_bandwidth) { + /* First pass: Cancel URBs, leave endpoint pointers intact. */ + for (i = skip_ep0; i < 16; ++i) { + usb_disable_endpoint(dev, i, false); + usb_disable_endpoint(dev, i + USB_DIR_IN, false); + } + /* Remove endpoints from the host controller internal state */ + mutex_lock(hcd->bandwidth_mutex); + usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); + mutex_unlock(hcd->bandwidth_mutex); + /* Second pass: remove endpoint pointers */ + } + for (i = skip_ep0; i < 16; ++i) { + usb_disable_endpoint(dev, i, true); + usb_disable_endpoint(dev, i + USB_DIR_IN, true); + } +} + +/** + * usb_enable_endpoint - Enable an endpoint for USB communications + * @dev: the device whose interface is being enabled + * @ep: the endpoint + * @reset_ep: flag to reset the endpoint state + * + * Resets the endpoint state if asked, and sets dev->ep_{in,out} pointers. + * For control endpoints, both the input and output sides are handled. + */ +void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep, + bool reset_ep) +{ + int epnum = usb_endpoint_num(&ep->desc); + int is_out = usb_endpoint_dir_out(&ep->desc); + int is_control = usb_endpoint_xfer_control(&ep->desc); + + if (reset_ep) + usb_hcd_reset_endpoint(dev, ep); + if (is_out || is_control) + dev->ep_out[epnum] = ep; + if (!is_out || is_control) + dev->ep_in[epnum] = ep; + ep->enabled = 1; +} + +/** + * usb_enable_interface - Enable all the endpoints for an interface + * @dev: the device whose interface is being enabled + * @intf: pointer to the interface descriptor + * @reset_eps: flag to reset the endpoints' state + * + * Enables all the endpoints for the interface's current altsetting. + */ +void usb_enable_interface(struct usb_device *dev, + struct usb_interface *intf, bool reset_eps) +{ + struct usb_host_interface *alt = intf->cur_altsetting; + int i; + + for (i = 0; i < alt->desc.bNumEndpoints; ++i) + usb_enable_endpoint(dev, &alt->endpoint[i], reset_eps); +} + +/** + * usb_set_interface - Makes a particular alternate setting be current + * @dev: the device whose interface is being updated + * @interface: the interface being updated + * @alternate: the setting being chosen. + * Context: !in_interrupt () + * + * This is used to enable data transfers on interfaces that may not + * be enabled by default. Not all devices support such configurability. + * Only the driver bound to an interface may change its setting. + * + * Within any given configuration, each interface may have several + * alternative settings. These are often used to control levels of + * bandwidth consumption. For example, the default setting for a high + * speed interrupt endpoint may not send more than 64 bytes per microframe, + * while interrupt transfers of up to 3KBytes per microframe are legal. + * Also, isochronous endpoints may never be part of an + * interface's default setting. To access such bandwidth, alternate + * interface settings must be made current. + * + * Note that in the Linux USB subsystem, bandwidth associated with + * an endpoint in a given alternate setting is not reserved until an URB + * is submitted that needs that bandwidth. Some other operating systems + * allocate bandwidth early, when a configuration is chosen. + * + * This call is synchronous, and may not be used in an interrupt context. + * Also, drivers must not change altsettings while urbs are scheduled for + * endpoints in that interface; all such urbs must first be completed + * (perhaps forced by unlinking). + * + * Returns zero on success, or else the status code returned by the + * underlying usb_control_msg() call. + */ +int usb_set_interface(struct usb_device *dev, int interface, int alternate) +{ + struct usb_interface *iface; + struct usb_host_interface *alt; + struct usb_hcd *hcd = bus_to_hcd(dev->bus); + int ret; + int manual = 0; + unsigned int epaddr; + unsigned int pipe; + + if (dev->state == USB_STATE_SUSPENDED) + return -EHOSTUNREACH; + + iface = usb_ifnum_to_if(dev, interface); + if (!iface) { + dev_dbg(&dev->dev, "selecting invalid interface %d\n", + interface); + return -EINVAL; + } + if (iface->unregistering) + return -ENODEV; + + alt = usb_altnum_to_altsetting(iface, alternate); + if (!alt) { + dev_warn(&dev->dev, "selecting invalid altsetting %d\n", + alternate); + return -EINVAL; + } + + /* Make sure we have enough bandwidth for this alternate interface. + * Remove the current alt setting and add the new alt setting. + */ + mutex_lock(hcd->bandwidth_mutex); + /* Disable LPM, and re-enable it once the new alt setting is installed, + * so that the xHCI driver can recalculate the U1/U2 timeouts. + */ + if (usb_disable_lpm(dev)) { + dev_err(&iface->dev, "%s Failed to disable LPM\n.", __func__); + mutex_unlock(hcd->bandwidth_mutex); + return -ENOMEM; + } + ret = usb_hcd_alloc_bandwidth(dev, NULL, iface->cur_altsetting, alt); + if (ret < 0) { + dev_info(&dev->dev, "Not enough bandwidth for altsetting %d\n", + alternate); + usb_enable_lpm(dev); + mutex_unlock(hcd->bandwidth_mutex); + return ret; + } + + if (dev->quirks & USB_QUIRK_NO_SET_INTF) + ret = -EPIPE; + else + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE, + alternate, interface, NULL, 0, 5000); + + /* 9.4.10 says devices don't need this and are free to STALL the + * request if the interface only has one alternate setting. + */ + if (ret == -EPIPE && iface->num_altsetting == 1) { + dev_dbg(&dev->dev, + "manual set_interface for iface %d, alt %d\n", + interface, alternate); + manual = 1; + } else if (ret < 0) { + /* Re-instate the old alt setting */ + usb_hcd_alloc_bandwidth(dev, NULL, alt, iface->cur_altsetting); + usb_enable_lpm(dev); + mutex_unlock(hcd->bandwidth_mutex); + return ret; + } + mutex_unlock(hcd->bandwidth_mutex); + + /* FIXME drivers shouldn't need to replicate/bugfix the logic here + * when they implement async or easily-killable versions of this or + * other "should-be-internal" functions (like clear_halt). + * should hcd+usbcore postprocess control requests? + */ + + /* prevent submissions using previous endpoint settings */ + if (iface->cur_altsetting != alt) { + remove_intf_ep_devs(iface); + usb_remove_sysfs_intf_files(iface); + } + usb_disable_interface(dev, iface, true); + + iface->cur_altsetting = alt; + + /* Now that the interface is installed, re-enable LPM. */ + usb_unlocked_enable_lpm(dev); + + /* If the interface only has one altsetting and the device didn't + * accept the request, we attempt to carry out the equivalent action + * by manually clearing the HALT feature for each endpoint in the + * new altsetting. + */ + if (manual) { + int i; + + for (i = 0; i < alt->desc.bNumEndpoints; i++) { + epaddr = alt->endpoint[i].desc.bEndpointAddress; + pipe = __create_pipe(dev, + USB_ENDPOINT_NUMBER_MASK & epaddr) | + (usb_endpoint_out(epaddr) ? + USB_DIR_OUT : USB_DIR_IN); + + usb_clear_halt(dev, pipe); + } + } + + /* 9.1.1.5: reset toggles for all endpoints in the new altsetting + * + * Note: + * Despite EP0 is always present in all interfaces/AS, the list of + * endpoints from the descriptor does not contain EP0. Due to its + * omnipresence one might expect EP0 being considered "affected" by + * any SetInterface request and hence assume toggles need to be reset. + * However, EP0 toggles are re-synced for every individual transfer + * during the SETUP stage - hence EP0 toggles are "don't care" here. + * (Likewise, EP0 never "halts" on well designed devices.) + */ + usb_enable_interface(dev, iface, true); + if (device_is_registered(&iface->dev)) { + usb_create_sysfs_intf_files(iface); + create_intf_ep_devs(iface); + } + return 0; +} +EXPORT_SYMBOL_GPL(usb_set_interface); + +/** + * usb_reset_configuration - lightweight device reset + * @dev: the device whose configuration is being reset + * + * This issues a standard SET_CONFIGURATION request to the device using + * the current configuration. The effect is to reset most USB-related + * state in the device, including interface altsettings (reset to zero), + * endpoint halts (cleared), and endpoint state (only for bulk and interrupt + * endpoints). Other usbcore state is unchanged, including bindings of + * usb device drivers to interfaces. + * + * Because this affects multiple interfaces, avoid using this with composite + * (multi-interface) devices. Instead, the driver for each interface may + * use usb_set_interface() on the interfaces it claims. Be careful though; + * some devices don't support the SET_INTERFACE request, and others won't + * reset all the interface state (notably endpoint state). Resetting the whole + * configuration would affect other drivers' interfaces. + * + * The caller must own the device lock. + * + * Returns zero on success, else a negative error code. + */ +int usb_reset_configuration(struct usb_device *dev) +{ + int i, retval; + struct usb_host_config *config; + struct usb_hcd *hcd = bus_to_hcd(dev->bus); + + if (dev->state == USB_STATE_SUSPENDED) + return -EHOSTUNREACH; + + /* caller must have locked the device and must own + * the usb bus readlock (so driver bindings are stable); + * calls during probe() are fine + */ + + for (i = 1; i < 16; ++i) { + usb_disable_endpoint(dev, i, true); + usb_disable_endpoint(dev, i + USB_DIR_IN, true); + } + + config = dev->actconfig; + retval = 0; + mutex_lock(hcd->bandwidth_mutex); + /* Disable LPM, and re-enable it once the configuration is reset, so + * that the xHCI driver can recalculate the U1/U2 timeouts. + */ + if (usb_disable_lpm(dev)) { + dev_err(&dev->dev, "%s Failed to disable LPM\n.", __func__); + mutex_unlock(hcd->bandwidth_mutex); + return -ENOMEM; + } + /* Make sure we have enough bandwidth for each alternate setting 0 */ + for (i = 0; i < config->desc.bNumInterfaces; i++) { + struct usb_interface *intf = config->interface[i]; + struct usb_host_interface *alt; + + alt = usb_altnum_to_altsetting(intf, 0); + if (!alt) + alt = &intf->altsetting[0]; + if (alt != intf->cur_altsetting) + retval = usb_hcd_alloc_bandwidth(dev, NULL, + intf->cur_altsetting, alt); + if (retval < 0) + break; + } + /* If not, reinstate the old alternate settings */ + if (retval < 0) { +reset_old_alts: + for (i--; i >= 0; i--) { + struct usb_interface *intf = config->interface[i]; + struct usb_host_interface *alt; + + alt = usb_altnum_to_altsetting(intf, 0); + if (!alt) + alt = &intf->altsetting[0]; + if (alt != intf->cur_altsetting) + usb_hcd_alloc_bandwidth(dev, NULL, + alt, intf->cur_altsetting); + } + usb_enable_lpm(dev); + mutex_unlock(hcd->bandwidth_mutex); + return retval; + } + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_CONFIGURATION, 0, + config->desc.bConfigurationValue, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); + if (retval < 0) + goto reset_old_alts; + mutex_unlock(hcd->bandwidth_mutex); + + /* re-init hc/hcd interface/endpoint state */ + for (i = 0; i < config->desc.bNumInterfaces; i++) { + struct usb_interface *intf = config->interface[i]; + struct usb_host_interface *alt; + + alt = usb_altnum_to_altsetting(intf, 0); + + /* No altsetting 0? We'll assume the first altsetting. + * We could use a GetInterface call, but if a device is + * so non-compliant that it doesn't have altsetting 0 + * then I wouldn't trust its reply anyway. + */ + if (!alt) + alt = &intf->altsetting[0]; + + if (alt != intf->cur_altsetting) { + remove_intf_ep_devs(intf); + usb_remove_sysfs_intf_files(intf); + } + intf->cur_altsetting = alt; + usb_enable_interface(dev, intf, true); + if (device_is_registered(&intf->dev)) { + usb_create_sysfs_intf_files(intf); + create_intf_ep_devs(intf); + } + } + /* Now that the interfaces are installed, re-enable LPM. */ + usb_unlocked_enable_lpm(dev); + return 0; +} +EXPORT_SYMBOL_GPL(usb_reset_configuration); + +static void usb_release_interface(struct device *dev) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_interface_cache *intfc = + altsetting_to_usb_interface_cache(intf->altsetting); + + kref_put(&intfc->ref, usb_release_interface_cache); + kfree(intf); +} + +static int usb_if_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct usb_device *usb_dev; + struct usb_interface *intf; + struct usb_host_interface *alt; + + intf = to_usb_interface(dev); + usb_dev = interface_to_usbdev(intf); + alt = intf->cur_altsetting; + + if (add_uevent_var(env, "INTERFACE=%d/%d/%d", + alt->desc.bInterfaceClass, + alt->desc.bInterfaceSubClass, + alt->desc.bInterfaceProtocol)) + return -ENOMEM; + + if (add_uevent_var(env, + "MODALIAS=usb:" + "v%04Xp%04Xd%04Xdc%02Xdsc%02Xdp%02Xic%02Xisc%02Xip%02Xin%02X", + le16_to_cpu(usb_dev->descriptor.idVendor), + le16_to_cpu(usb_dev->descriptor.idProduct), + le16_to_cpu(usb_dev->descriptor.bcdDevice), + usb_dev->descriptor.bDeviceClass, + usb_dev->descriptor.bDeviceSubClass, + usb_dev->descriptor.bDeviceProtocol, + alt->desc.bInterfaceClass, + alt->desc.bInterfaceSubClass, + alt->desc.bInterfaceProtocol, + alt->desc.bInterfaceNumber)) + return -ENOMEM; + + return 0; +} + +struct device_type usb_if_device_type = { + .name = "usb_interface", + .release = usb_release_interface, + .uevent = usb_if_uevent, +}; + +static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev, + struct usb_host_config *config, + u8 inum) +{ + struct usb_interface_assoc_descriptor *retval = NULL; + struct usb_interface_assoc_descriptor *intf_assoc; + int first_intf; + int last_intf; + int i; + + for (i = 0; (i < USB_MAXIADS && config->intf_assoc[i]); i++) { + intf_assoc = config->intf_assoc[i]; + if (intf_assoc->bInterfaceCount == 0) + continue; + + first_intf = intf_assoc->bFirstInterface; + last_intf = first_intf + (intf_assoc->bInterfaceCount - 1); + if (inum >= first_intf && inum <= last_intf) { + if (!retval) + retval = intf_assoc; + else + dev_err(&dev->dev, "Interface #%d referenced" + " by multiple IADs\n", inum); + } + } + + return retval; +} + + +/* + * Internal function to queue a device reset + * + * This is initialized into the workstruct in 'struct + * usb_device->reset_ws' that is launched by + * message.c:usb_set_configuration() when initializing each 'struct + * usb_interface'. + * + * It is safe to get the USB device without reference counts because + * the life cycle of @iface is bound to the life cycle of @udev. Then, + * this function will be ran only if @iface is alive (and before + * freeing it any scheduled instances of it will have been cancelled). + * + * We need to set a flag (usb_dev->reset_running) because when we call + * the reset, the interfaces might be unbound. The current interface + * cannot try to remove the queued work as it would cause a deadlock + * (you cannot remove your work from within your executing + * workqueue). This flag lets it know, so that + * usb_cancel_queued_reset() doesn't try to do it. + * + * See usb_queue_reset_device() for more details + */ +static void __usb_queue_reset_device(struct work_struct *ws) +{ + int rc; + struct usb_interface *iface = + container_of(ws, struct usb_interface, reset_ws); + struct usb_device *udev = interface_to_usbdev(iface); + + rc = usb_lock_device_for_reset(udev, iface); + if (rc >= 0) { + iface->reset_running = 1; + usb_reset_device(udev); + iface->reset_running = 0; + usb_unlock_device(udev); + } +} + + +/* + * usb_set_configuration - Makes a particular device setting be current + * @dev: the device whose configuration is being updated + * @configuration: the configuration being chosen. + * Context: !in_interrupt(), caller owns the device lock + * + * This is used to enable non-default device modes. Not all devices + * use this kind of configurability; many devices only have one + * configuration. + * + * @configuration is the value of the configuration to be installed. + * According to the USB spec (e.g. section 9.1.1.5), configuration values + * must be non-zero; a value of zero indicates that the device in + * unconfigured. However some devices erroneously use 0 as one of their + * configuration values. To help manage such devices, this routine will + * accept @configuration = -1 as indicating the device should be put in + * an unconfigured state. + * + * USB device configurations may affect Linux interoperability, + * power consumption and the functionality available. For example, + * the default configuration is limited to using 100mA of bus power, + * so that when certain device functionality requires more power, + * and the device is bus powered, that functionality should be in some + * non-default device configuration. Other device modes may also be + * reflected as configuration options, such as whether two ISDN + * channels are available independently; and choosing between open + * standard device protocols (like CDC) or proprietary ones. + * + * Note that a non-authorized device (dev->authorized == 0) will only + * be put in unconfigured mode. + * + * Note that USB has an additional level of device configurability, + * associated with interfaces. That configurability is accessed using + * usb_set_interface(). + * + * This call is synchronous. The calling context must be able to sleep, + * must own the device lock, and must not hold the driver model's USB + * bus mutex; usb interface driver probe() methods cannot use this routine. + * + * Returns zero on success, or else the status code returned by the + * underlying call that failed. On successful completion, each interface + * in the original device configuration has been destroyed, and each one + * in the new configuration has been probed by all relevant usb device + * drivers currently known to the kernel. + */ +int usb_set_configuration(struct usb_device *dev, int configuration) +{ + int i, ret; + struct usb_host_config *cp = NULL; + struct usb_interface **new_interfaces = NULL; + struct usb_hcd *hcd = bus_to_hcd(dev->bus); + int n, nintf; + + if (dev->authorized == 0 || configuration == -1) + configuration = 0; + else { + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { + if (dev->config[i].desc.bConfigurationValue == + configuration) { + cp = &dev->config[i]; + break; + } + } + } + if ((!cp && configuration != 0)) + return -EINVAL; + + /* The USB spec says configuration 0 means unconfigured. + * But if a device includes a configuration numbered 0, + * we will accept it as a correctly configured state. + * Use -1 if you really want to unconfigure the device. + */ + if (cp && configuration == 0) + dev_warn(&dev->dev, "config 0 descriptor??\n"); + + /* Allocate memory for new interfaces before doing anything else, + * so that if we run out then nothing will have changed. */ + n = nintf = 0; + if (cp) { + nintf = cp->desc.bNumInterfaces; + new_interfaces = kmalloc(nintf * sizeof(*new_interfaces), + GFP_NOIO); + if (!new_interfaces) { + dev_err(&dev->dev, "Out of memory\n"); + return -ENOMEM; + } + + for (; n < nintf; ++n) { + new_interfaces[n] = kzalloc( + sizeof(struct usb_interface), + GFP_NOIO); + if (!new_interfaces[n]) { + dev_err(&dev->dev, "Out of memory\n"); + ret = -ENOMEM; +free_interfaces: + while (--n >= 0) + kfree(new_interfaces[n]); + kfree(new_interfaces); + return ret; + } + } + + i = dev->bus_mA - usb_get_max_power(dev, cp); + if (i < 0) + dev_warn(&dev->dev, "new config #%d exceeds power " + "limit by %dmA\n", + configuration, -i); + } + + /* Wake up the device so we can send it the Set-Config request */ + ret = usb_autoresume_device(dev); + if (ret) + goto free_interfaces; + + /* if it's already configured, clear out old state first. + * getting rid of old interfaces means unbinding their drivers. + */ + if (dev->state != USB_STATE_ADDRESS) + usb_disable_device(dev, 1); /* Skip ep0 */ + + /* Get rid of pending async Set-Config requests for this device */ + cancel_async_set_config(dev); + + /* Make sure we have bandwidth (and available HCD resources) for this + * configuration. Remove endpoints from the schedule if we're dropping + * this configuration to set configuration 0. After this point, the + * host controller will not allow submissions to dropped endpoints. If + * this call fails, the device state is unchanged. + */ + mutex_lock(hcd->bandwidth_mutex); + /* Disable LPM, and re-enable it once the new configuration is + * installed, so that the xHCI driver can recalculate the U1/U2 + * timeouts. + */ + if (dev->actconfig && usb_disable_lpm(dev)) { + dev_err(&dev->dev, "%s Failed to disable LPM\n.", __func__); + mutex_unlock(hcd->bandwidth_mutex); + ret = -ENOMEM; + goto free_interfaces; + } + ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL); + if (ret < 0) { + if (dev->actconfig) + usb_enable_lpm(dev); + mutex_unlock(hcd->bandwidth_mutex); + usb_autosuspend_device(dev); + goto free_interfaces; + } + + /* + * Initialize the new interface structures and the + * hc/hcd/usbcore interface/endpoint state. + */ + for (i = 0; i < nintf; ++i) { + struct usb_interface_cache *intfc; + struct usb_interface *intf; + struct usb_host_interface *alt; + + cp->interface[i] = intf = new_interfaces[i]; + intfc = cp->intf_cache[i]; + intf->altsetting = intfc->altsetting; + intf->num_altsetting = intfc->num_altsetting; + kref_get(&intfc->ref); + + alt = usb_altnum_to_altsetting(intf, 0); + + /* No altsetting 0? We'll assume the first altsetting. + * We could use a GetInterface call, but if a device is + * so non-compliant that it doesn't have altsetting 0 + * then I wouldn't trust its reply anyway. + */ + if (!alt) + alt = &intf->altsetting[0]; + + intf->intf_assoc = + find_iad(dev, cp, alt->desc.bInterfaceNumber); + intf->cur_altsetting = alt; + usb_enable_interface(dev, intf, true); + intf->dev.parent = &dev->dev; + intf->dev.driver = NULL; + intf->dev.bus = &usb_bus_type; + intf->dev.type = &usb_if_device_type; + intf->dev.groups = usb_interface_groups; + intf->dev.dma_mask = dev->dev.dma_mask; + INIT_WORK(&intf->reset_ws, __usb_queue_reset_device); + intf->minor = -1; + device_initialize(&intf->dev); + pm_runtime_no_callbacks(&intf->dev); + dev_set_name(&intf->dev, "%d-%s:%d.%d", + dev->bus->busnum, dev->devpath, + configuration, alt->desc.bInterfaceNumber); + } + kfree(new_interfaces); + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_CONFIGURATION, 0, configuration, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); + if (ret < 0 && cp) { + /* + * All the old state is gone, so what else can we do? + * The device is probably useless now anyway. + */ + usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); + for (i = 0; i < nintf; ++i) { + usb_disable_interface(dev, cp->interface[i], true); + put_device(&cp->interface[i]->dev); + cp->interface[i] = NULL; + } + cp = NULL; + } + + dev->actconfig = cp; + mutex_unlock(hcd->bandwidth_mutex); + + if (!cp) { + usb_set_device_state(dev, USB_STATE_ADDRESS); + + /* Leave LPM disabled while the device is unconfigured. */ + usb_autosuspend_device(dev); + return ret; + } + usb_set_device_state(dev, USB_STATE_CONFIGURED); + + if (cp->string == NULL && + !(dev->quirks & USB_QUIRK_CONFIG_INTF_STRINGS)) + cp->string = usb_cache_string(dev, cp->desc.iConfiguration); + + /* Now that the interfaces are installed, re-enable LPM. */ + usb_unlocked_enable_lpm(dev); + /* Enable LTM if it was turned off by usb_disable_device. */ + usb_enable_ltm(dev); + + /* Now that all the interfaces are set up, register them + * to trigger binding of drivers to interfaces. probe() + * routines may install different altsettings and may + * claim() any interfaces not yet bound. Many class drivers + * need that: CDC, audio, video, etc. + */ + for (i = 0; i < nintf; ++i) { + struct usb_interface *intf = cp->interface[i]; + + dev_dbg(&dev->dev, + "adding %s (config #%d, interface %d)\n", + dev_name(&intf->dev), configuration, + intf->cur_altsetting->desc.bInterfaceNumber); + device_enable_async_suspend(&intf->dev); + ret = device_add(&intf->dev); + if (ret != 0) { + dev_err(&dev->dev, "device_add(%s) --> %d\n", + dev_name(&intf->dev), ret); + continue; + } + create_intf_ep_devs(intf); + } + + usb_autosuspend_device(dev); + return 0; +} + +static LIST_HEAD(set_config_list); +static DEFINE_SPINLOCK(set_config_lock); + +struct set_config_request { + struct usb_device *udev; + int config; + struct work_struct work; + struct list_head node; +}; + +/* Worker routine for usb_driver_set_configuration() */ +static void driver_set_config_work(struct work_struct *work) +{ + struct set_config_request *req = + container_of(work, struct set_config_request, work); + struct usb_device *udev = req->udev; + + usb_lock_device(udev); + spin_lock(&set_config_lock); + list_del(&req->node); + spin_unlock(&set_config_lock); + + if (req->config >= -1) /* Is req still valid? */ + usb_set_configuration(udev, req->config); + usb_unlock_device(udev); + usb_put_dev(udev); + kfree(req); +} + +/* Cancel pending Set-Config requests for a device whose configuration + * was just changed + */ +static void cancel_async_set_config(struct usb_device *udev) +{ + struct set_config_request *req; + + spin_lock(&set_config_lock); + list_for_each_entry(req, &set_config_list, node) { + if (req->udev == udev) + req->config = -999; /* Mark as cancelled */ + } + spin_unlock(&set_config_lock); +} + +/** + * usb_driver_set_configuration - Provide a way for drivers to change device configurations + * @udev: the device whose configuration is being updated + * @config: the configuration being chosen. + * Context: In process context, must be able to sleep + * + * Device interface drivers are not allowed to change device configurations. + * This is because changing configurations will destroy the interface the + * driver is bound to and create new ones; it would be like a floppy-disk + * driver telling the computer to replace the floppy-disk drive with a + * tape drive! + * + * Still, in certain specialized circumstances the need may arise. This + * routine gets around the normal restrictions by using a work thread to + * submit the change-config request. + * + * Returns 0 if the request was successfully queued, error code otherwise. + * The caller has no way to know whether the queued request will eventually + * succeed. + */ +int usb_driver_set_configuration(struct usb_device *udev, int config) +{ + struct set_config_request *req; + + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + req->udev = udev; + req->config = config; + INIT_WORK(&req->work, driver_set_config_work); + + spin_lock(&set_config_lock); + list_add(&req->node, &set_config_list); + spin_unlock(&set_config_lock); + + usb_get_dev(udev); + schedule_work(&req->work); + return 0; +} +EXPORT_SYMBOL_GPL(usb_driver_set_configuration); diff --git a/drivers/usb/core/notify.c b/drivers/usb/core/notify.c new file mode 100644 index 00000000..7728c91d --- /dev/null +++ b/drivers/usb/core/notify.c @@ -0,0 +1,69 @@ +/* + * All the USB notify logic + * + * (C) Copyright 2005 Greg Kroah-Hartman + * + * notifier functions originally based on those in kernel/sys.c + * but fixed up to not be so broken. + * + */ + + +#include +#include +#include +#include +#include +#include "usb.h" + +static BLOCKING_NOTIFIER_HEAD(usb_notifier_list); + +/** + * usb_register_notify - register a notifier callback whenever a usb change happens + * @nb: pointer to the notifier block for the callback events. + * + * These changes are either USB devices or busses being added or removed. + */ +void usb_register_notify(struct notifier_block *nb) +{ + blocking_notifier_chain_register(&usb_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(usb_register_notify); + +/** + * usb_unregister_notify - unregister a notifier callback + * @nb: pointer to the notifier block for the callback events. + * + * usb_register_notify() must have been previously called for this function + * to work properly. + */ +void usb_unregister_notify(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&usb_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(usb_unregister_notify); + + +void usb_notify_add_device(struct usb_device *udev) +{ + blocking_notifier_call_chain(&usb_notifier_list, USB_DEVICE_ADD, udev); +} + +void usb_notify_remove_device(struct usb_device *udev) +{ + /* Protect against simultaneous usbfs open */ + mutex_lock(&usbfs_mutex); + blocking_notifier_call_chain(&usb_notifier_list, + USB_DEVICE_REMOVE, udev); + mutex_unlock(&usbfs_mutex); +} + +void usb_notify_add_bus(struct usb_bus *ubus) +{ + blocking_notifier_call_chain(&usb_notifier_list, USB_BUS_ADD, ubus); +} + +void usb_notify_remove_bus(struct usb_bus *ubus) +{ + blocking_notifier_call_chain(&usb_notifier_list, USB_BUS_REMOVE, ubus); +} diff --git a/drivers/usb/core/otg_whitelist.h b/drivers/usb/core/otg_whitelist.h new file mode 100644 index 00000000..e8cdce57 --- /dev/null +++ b/drivers/usb/core/otg_whitelist.h @@ -0,0 +1,112 @@ +/* + * drivers/usb/core/otg_whitelist.h + * + * Copyright (C) 2004 Texas Instruments + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * This OTG Whitelist is the OTG "Targeted Peripheral List". It should + * mostly use of USB_DEVICE() or USB_DEVICE_VER() entries.. + * + * YOU _SHOULD_ CHANGE THIS LIST TO MATCH YOUR PRODUCT AND ITS TESTING! + */ + +static struct usb_device_id whitelist_table [] = { + +/* hubs are optional in OTG, but very handy ... */ +{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 0), }, +{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 1), }, + +#ifdef CONFIG_USB_PRINTER /* ignoring nonstatic linkage! */ +/* FIXME actually, printers are NOT supposed to use device classes; + * they're supposed to use interface classes... + */ +{ USB_DEVICE_INFO(7, 1, 1) }, +{ USB_DEVICE_INFO(7, 1, 2) }, +{ USB_DEVICE_INFO(7, 1, 3) }, +#endif + +#ifdef CONFIG_USB_NET_CDCETHER +/* Linux-USB CDC Ethernet gadget */ +{ USB_DEVICE(0x0525, 0xa4a1), }, +/* Linux-USB CDC Ethernet + RNDIS gadget */ +{ USB_DEVICE(0x0525, 0xa4a2), }, +#endif + +#if defined(CONFIG_USB_TEST) || defined(CONFIG_USB_TEST_MODULE) +/* gadget zero, for testing */ +{ USB_DEVICE(0x0525, 0xa4a0), }, +#endif + +{ } /* Terminating entry */ +}; + +static int is_targeted(struct usb_device *dev) +{ + struct usb_device_id *id = whitelist_table; + + /* possible in developer configs only! */ + if (!dev->bus->otg_port) + return 1; + + /* HNP test device is _never_ targeted (see OTG spec 6.6.6) */ + if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a0a && + le16_to_cpu(dev->descriptor.idProduct) == 0xbadd)) + return 0; + + /* NOTE: can't use usb_match_id() since interface caches + * aren't set up yet. this is cut/paste from that code. + */ + for (id = whitelist_table; id->match_flags; id++) { + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + id->idVendor != le16_to_cpu(dev->descriptor.idVendor)) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->idProduct != le16_to_cpu(dev->descriptor.idProduct)) + continue; + + /* No need to test id->bcdDevice_lo != 0, since 0 is never + greater than any unsigned number. */ + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) && + (id->bcdDevice_lo > le16_to_cpu(dev->descriptor.bcdDevice))) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) && + (id->bcdDevice_hi < le16_to_cpu(dev->descriptor.bcdDevice))) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && + (id->bDeviceClass != dev->descriptor.bDeviceClass)) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) && + (id->bDeviceSubClass != dev->descriptor.bDeviceSubClass)) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && + (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol)) + continue; + + return 1; + } + + /* add other match criteria here ... */ + + + /* OTG MESSAGE: report errors here, customize to match your product */ + dev_err(&dev->dev, "device v%04x p%04x is not supported\n", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); +#ifdef CONFIG_USB_OTG_WHITELIST + return 0; +#else + return 1; +#endif +} + diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c new file mode 100644 index 00000000..ef07b359 --- /dev/null +++ b/drivers/usb/core/port.c @@ -0,0 +1,198 @@ +/* + * usb port device code + * + * Copyright (C) 2012 Intel Corp + * + * Author: Lan Tianyu + * + * This program is free software; you can redistribute 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 that 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 +#include + +#include "hub.h" + +static const struct attribute_group *port_dev_group[]; + +static ssize_t show_port_connect_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + char *result; + + switch (port_dev->connect_type) { + case USB_PORT_CONNECT_TYPE_HOT_PLUG: + result = "hotplug"; + break; + case USB_PORT_CONNECT_TYPE_HARD_WIRED: + result = "hardwired"; + break; + case USB_PORT_NOT_USED: + result = "not used"; + break; + default: + result = "unknown"; + break; + } + + return sprintf(buf, "%s\n", result); +} +static DEVICE_ATTR(connect_type, S_IRUGO, show_port_connect_type, + NULL); + +static struct attribute *port_dev_attrs[] = { + &dev_attr_connect_type.attr, + NULL, +}; + +static struct attribute_group port_dev_attr_grp = { + .attrs = port_dev_attrs, +}; + +static const struct attribute_group *port_dev_group[] = { + &port_dev_attr_grp, + NULL, +}; + +static void usb_port_device_release(struct device *dev) +{ + struct usb_port *port_dev = to_usb_port(dev); + + kfree(port_dev); +} + +#ifdef CONFIG_PM_RUNTIME +static int usb_port_runtime_resume(struct device *dev) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_interface *intf = to_usb_interface(dev->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + int port1 = port_dev->portnum; + int retval; + + if (!hub) + return -EINVAL; + + usb_autopm_get_interface(intf); + set_bit(port1, hub->busy_bits); + + retval = usb_hub_set_port_power(hdev, port1, true); + if (port_dev->child && !retval) { + /* + * Attempt to wait for usb hub port to be reconnected in order + * to make the resume procedure successful. The device may have + * disconnected while the port was powered off, so ignore the + * return status. + */ + retval = hub_port_debounce_be_connected(hub, port1); + if (retval < 0) + dev_dbg(&port_dev->dev, "can't get reconnection after setting port power on, status %d\n", + retval); + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); + retval = 0; + } + + clear_bit(port1, hub->busy_bits); + usb_autopm_put_interface(intf); + return retval; +} + +static int usb_port_runtime_suspend(struct device *dev) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_interface *intf = to_usb_interface(dev->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + int port1 = port_dev->portnum; + int retval; + + if (!hub) + return -EINVAL; + + if (dev_pm_qos_flags(&port_dev->dev, PM_QOS_FLAG_NO_POWER_OFF) + == PM_QOS_FLAGS_ALL) + return -EAGAIN; + + usb_autopm_get_interface(intf); + set_bit(port1, hub->busy_bits); + retval = usb_hub_set_port_power(hdev, port1, false); + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); + clear_bit(port1, hub->busy_bits); + usb_autopm_put_interface(intf); + return retval; +} +#endif + +static const struct dev_pm_ops usb_port_pm_ops = { +#ifdef CONFIG_PM_RUNTIME + .runtime_suspend = usb_port_runtime_suspend, + .runtime_resume = usb_port_runtime_resume, + .runtime_idle = pm_generic_runtime_idle, +#endif +}; + +struct device_type usb_port_device_type = { + .name = "usb_port", + .release = usb_port_device_release, + .pm = &usb_port_pm_ops, +}; + +int usb_hub_create_port_device(struct usb_hub *hub, int port1) +{ + struct usb_port *port_dev = NULL; + int retval; + + port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); + if (!port_dev) { + retval = -ENOMEM; + goto exit; + } + + hub->ports[port1 - 1] = port_dev; + port_dev->portnum = port1; + port_dev->power_is_on = true; + port_dev->dev.parent = hub->intfdev; + port_dev->dev.groups = port_dev_group; + port_dev->dev.type = &usb_port_device_type; + dev_set_name(&port_dev->dev, "port%d", port1); + + retval = device_register(&port_dev->dev); + if (retval) + goto error_register; + + pm_runtime_set_active(&port_dev->dev); + + /* It would be dangerous if user space couldn't + * prevent usb device from being powered off. So don't + * enable port runtime pm if failed to expose port's pm qos. + */ + if (!dev_pm_qos_expose_flags(&port_dev->dev, + PM_QOS_FLAG_NO_POWER_OFF)) + pm_runtime_enable(&port_dev->dev); + + device_enable_async_suspend(&port_dev->dev); + return 0; + +error_register: + put_device(&port_dev->dev); +exit: + return retval; +} + +void usb_hub_remove_port_device(struct usb_hub *hub, + int port1) +{ + device_unregister(&hub->ports[port1 - 1]->dev); +} + diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c new file mode 100644 index 00000000..b73f3031 --- /dev/null +++ b/drivers/usb/core/quirks.c @@ -0,0 +1,251 @@ +/* + * USB device quirk handling logic and table + * + * Copyright (c) 2007 Oliver Neukum + * Copyright (c) 2007 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 2. + * + * + */ + +#include +#include +#include "usb.h" + +/* Lists of quirky USB devices, split in device quirks and interface quirks. + * Device quirks are applied at the very beginning of the enumeration process, + * right after reading the device descriptor. They can thus only match on device + * information. + * + * Interface quirks are applied after reading all the configuration descriptors. + * They can match on both device and interface information. + * + * Note that the DELAY_INIT and HONOR_BNUMINTERFACES quirks do not make sense as + * interface quirks, as they only influence the enumeration process which is run + * before processing the interface quirks. + * + * Please keep the lists ordered by: + * 1) Vendor ID + * 2) Product ID + * 3) Class ID + */ +static const struct usb_device_id usb_quirk_list[] = { + /* CBM - Flash disk */ + { USB_DEVICE(0x0204, 0x6025), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* HP 5300/5370C scanner */ + { USB_DEVICE(0x03f0, 0x0701), .driver_info = + USB_QUIRK_STRING_FETCH_255 }, + + /* Creative SB Audigy 2 NX */ + { USB_DEVICE(0x041e, 0x3020), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Microsoft Wireless Laser Mouse 6000 Receiver */ + { USB_DEVICE(0x045e, 0x00e1), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Microsoft LifeCam-VX700 v2.0 */ + { USB_DEVICE(0x045e, 0x0770), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech HD Pro Webcams C920 and C930e */ + { USB_DEVICE(0x046d, 0x082d), .driver_info = USB_QUIRK_DELAY_INIT }, + { USB_DEVICE(0x046d, 0x0843), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Logitech Quickcam Fusion */ + { USB_DEVICE(0x046d, 0x08c1), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech Quickcam Orbit MP */ + { USB_DEVICE(0x046d, 0x08c2), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech Quickcam Pro for Notebook */ + { USB_DEVICE(0x046d, 0x08c3), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech Quickcam Pro 5000 */ + { USB_DEVICE(0x046d, 0x08c5), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech Quickcam OEM Dell Notebook */ + { USB_DEVICE(0x046d, 0x08c6), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech Quickcam OEM Cisco VT Camera II */ + { USB_DEVICE(0x046d, 0x08c7), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech Harmony 700-series */ + { USB_DEVICE(0x046d, 0xc122), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Philips PSC805 audio device */ + { USB_DEVICE(0x0471, 0x0155), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Artisman Watchdog Dongle */ + { USB_DEVICE(0x04b4, 0x0526), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Microchip Joss Optical infrared touchboard device */ + { USB_DEVICE(0x04d8, 0x000c), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* CarrolTouch 4000U */ + { USB_DEVICE(0x04e7, 0x0009), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* CarrolTouch 4500U */ + { USB_DEVICE(0x04e7, 0x0030), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Samsung Android phone modem - ID conflict with SPH-I500 */ + { USB_DEVICE(0x04e8, 0x6601), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Roland SC-8820 */ + { USB_DEVICE(0x0582, 0x0007), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Edirol SD-20 */ + { USB_DEVICE(0x0582, 0x0027), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Alcor Micro Corp. Hub */ + { USB_DEVICE(0x058f, 0x9254), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* MicroTouch Systems touchscreen */ + { USB_DEVICE(0x0596, 0x051e), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* appletouch */ + { USB_DEVICE(0x05ac, 0x021a), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Avision AV600U */ + { USB_DEVICE(0x0638, 0x0a13), .driver_info = + USB_QUIRK_STRING_FETCH_255 }, + + /* Saitek Cyborg Gold Joystick */ + { USB_DEVICE(0x06a3, 0x0006), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Guillemot Webcam Hercules Dualpix Exchange (2nd ID) */ + { USB_DEVICE(0x06f8, 0x0804), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Guillemot Webcam Hercules Dualpix Exchange*/ + { USB_DEVICE(0x06f8, 0x3005), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Midiman M-Audio Keystation 88es */ + { USB_DEVICE(0x0763, 0x0192), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* M-Systems Flash Disk Pioneers */ + { USB_DEVICE(0x08ec, 0x1000), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Keytouch QWERTY Panel keyboard */ + { USB_DEVICE(0x0926, 0x3333), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* X-Rite/Gretag-Macbeth Eye-One Pro display colorimeter */ + { USB_DEVICE(0x0971, 0x2000), .driver_info = USB_QUIRK_NO_SET_INTF }, + + /* Broadcom BCM92035DGROM BT dongle */ + { USB_DEVICE(0x0a5c, 0x2021), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* MAYA44USB sound device */ + { USB_DEVICE(0x0a92, 0x0091), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Action Semiconductor flash disk */ + { USB_DEVICE(0x10d6, 0x2200), .driver_info = + USB_QUIRK_STRING_FETCH_255 }, + + /* SKYMEDI USB_DRIVE */ + { USB_DEVICE(0x1516, 0x8628), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* BUILDWIN Photo Frame */ + { USB_DEVICE(0x1908, 0x1315), .driver_info = + USB_QUIRK_HONOR_BNUMINTERFACES }, + + /* INTEL VALUE SSD */ + { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME }, + + { } /* terminating entry must be last */ +}; + +static const struct usb_device_id usb_interface_quirk_list[] = { + /* Logitech UVC Cameras */ + { USB_VENDOR_AND_INTERFACE_INFO(0x046d, USB_CLASS_VIDEO, 1, 0), + .driver_info = USB_QUIRK_RESET_RESUME }, + + /* ASUS Base Station(T100) */ + { USB_DEVICE(0x0b05, 0x17e0), .driver_info = + USB_QUIRK_IGNORE_REMOTE_WAKEUP }, + + { } /* terminating entry must be last */ +}; + +static bool usb_match_any_interface(struct usb_device *udev, + const struct usb_device_id *id) +{ + unsigned int i; + + for (i = 0; i < udev->descriptor.bNumConfigurations; ++i) { + struct usb_host_config *cfg = &udev->config[i]; + unsigned int j; + + for (j = 0; j < cfg->desc.bNumInterfaces; ++j) { + struct usb_interface_cache *cache; + struct usb_host_interface *intf; + + cache = cfg->intf_cache[j]; + if (cache->num_altsetting == 0) + continue; + + intf = &cache->altsetting[0]; + if (usb_match_one_id_intf(udev, intf, id)) + return true; + } + } + + return false; +} + +static u32 __usb_detect_quirks(struct usb_device *udev, + const struct usb_device_id *id) +{ + u32 quirks = 0; + + for (; id->match_flags; id++) { + if (!usb_match_device(udev, id)) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_INFO) && + !usb_match_any_interface(udev, id)) + continue; + + quirks |= (u32)(id->driver_info); + } + + return quirks; +} + +/* + * Detect any quirks the device has, and do any housekeeping for it if needed. + */ +void usb_detect_quirks(struct usb_device *udev) +{ + udev->quirks = __usb_detect_quirks(udev, usb_quirk_list); + if (udev->quirks) + dev_dbg(&udev->dev, "USB quirks for this device: %x\n", + udev->quirks); + +#ifdef CONFIG_USB_DEFAULT_PERSIST + if (!(udev->quirks & USB_QUIRK_RESET)) + udev->persist_enabled = 1; +#else + /* Hubs are automatically enabled for USB-PERSIST */ + if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) + udev->persist_enabled = 1; +#endif /* CONFIG_USB_DEFAULT_PERSIST */ +} + +void usb_detect_interface_quirks(struct usb_device *udev) +{ + u32 quirks; + + quirks = __usb_detect_quirks(udev, usb_interface_quirk_list); + if (quirks == 0) + return; + + dev_dbg(&udev->dev, "USB interface quirks for this device: %x\n", + quirks); + udev->quirks |= quirks; +} diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c new file mode 100644 index 00000000..aa38db44 --- /dev/null +++ b/drivers/usb/core/sysfs.c @@ -0,0 +1,971 @@ +/* + * drivers/usb/core/sysfs.c + * + * (C) Copyright 2002 David Brownell + * (C) Copyright 2002,2004 Greg Kroah-Hartman + * (C) Copyright 2002,2004 IBM Corp. + * + * All of the sysfs file attributes for usb devices and interfaces. + * + */ + + +#include +#include +#include +#include +#include "usb.h" + +/* Active configuration fields */ +#define usb_actconfig_show(field, format_string) \ +static ssize_t show_##field(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_device *udev; \ + struct usb_host_config *actconfig; \ + \ + udev = to_usb_device(dev); \ + actconfig = udev->actconfig; \ + if (actconfig) \ + return sprintf(buf, format_string, \ + actconfig->desc.field); \ + else \ + return 0; \ +} \ + +#define usb_actconfig_attr(field, format_string) \ + usb_actconfig_show(field, format_string) \ + static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); + +usb_actconfig_attr(bNumInterfaces, "%2d\n") +usb_actconfig_attr(bmAttributes, "%2x\n") + +static ssize_t show_bMaxPower(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + struct usb_host_config *actconfig; + + udev = to_usb_device(dev); + actconfig = udev->actconfig; + if (!actconfig) + return 0; + return sprintf(buf, "%dmA\n", usb_get_max_power(udev, actconfig)); +} +static DEVICE_ATTR(bMaxPower, S_IRUGO, show_bMaxPower, NULL); + +static ssize_t show_configuration_string(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + struct usb_host_config *actconfig; + + udev = to_usb_device(dev); + actconfig = udev->actconfig; + if ((!actconfig) || (!actconfig->string)) + return 0; + return sprintf(buf, "%s\n", actconfig->string); +} +static DEVICE_ATTR(configuration, S_IRUGO, show_configuration_string, NULL); + +/* configuration value is always present, and r/w */ +usb_actconfig_show(bConfigurationValue, "%u\n"); + +static ssize_t +set_bConfigurationValue(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + int config, value; + + if (sscanf(buf, "%d", &config) != 1 || config < -1 || config > 255) + return -EINVAL; + usb_lock_device(udev); + value = usb_set_configuration(udev, config); + usb_unlock_device(udev); + return (value < 0) ? value : count; +} + +static DEVICE_ATTR_IGNORE_LOCKDEP(bConfigurationValue, S_IRUGO | S_IWUSR, + show_bConfigurationValue, set_bConfigurationValue); + +/* String fields */ +#define usb_string_attr(name) \ +static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_device *udev; \ + int retval; \ + \ + udev = to_usb_device(dev); \ + usb_lock_device(udev); \ + retval = sprintf(buf, "%s\n", udev->name); \ + usb_unlock_device(udev); \ + return retval; \ +} \ +static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); + +usb_string_attr(product); +usb_string_attr(manufacturer); +usb_string_attr(serial); + +static ssize_t +show_speed(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + char *speed; + + udev = to_usb_device(dev); + + switch (udev->speed) { + case USB_SPEED_LOW: + speed = "1.5"; + break; + case USB_SPEED_UNKNOWN: + case USB_SPEED_FULL: + speed = "12"; + break; + case USB_SPEED_HIGH: + speed = "480"; + break; + case USB_SPEED_WIRELESS: + speed = "480"; + break; + case USB_SPEED_SUPER: + speed = "5000"; + break; + default: + speed = "unknown"; + } + return sprintf(buf, "%s\n", speed); +} +static DEVICE_ATTR(speed, S_IRUGO, show_speed, NULL); + +static ssize_t +show_busnum(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->bus->busnum); +} +static DEVICE_ATTR(busnum, S_IRUGO, show_busnum, NULL); + +static ssize_t +show_devnum(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->devnum); +} +static DEVICE_ATTR(devnum, S_IRUGO, show_devnum, NULL); + +static ssize_t +show_devpath(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%s\n", udev->devpath); +} +static DEVICE_ATTR(devpath, S_IRUGO, show_devpath, NULL); + +static ssize_t +show_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + u16 bcdUSB; + + udev = to_usb_device(dev); + bcdUSB = le16_to_cpu(udev->descriptor.bcdUSB); + return sprintf(buf, "%2x.%02x\n", bcdUSB >> 8, bcdUSB & 0xff); +} +static DEVICE_ATTR(version, S_IRUGO, show_version, NULL); + +static ssize_t +show_maxchild(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->maxchild); +} +static DEVICE_ATTR(maxchild, S_IRUGO, show_maxchild, NULL); + +static ssize_t +show_quirks(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "0x%x\n", udev->quirks); +} +static DEVICE_ATTR(quirks, S_IRUGO, show_quirks, NULL); + +static ssize_t +show_avoid_reset_quirk(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", !!(udev->quirks & USB_QUIRK_RESET)); +} + +static ssize_t +set_avoid_reset_quirk(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + int val; + + if (sscanf(buf, "%d", &val) != 1 || val < 0 || val > 1) + return -EINVAL; + usb_lock_device(udev); + if (val) + udev->quirks |= USB_QUIRK_RESET; + else + udev->quirks &= ~USB_QUIRK_RESET; + usb_unlock_device(udev); + return count; +} + +static DEVICE_ATTR(avoid_reset_quirk, S_IRUGO | S_IWUSR, + show_avoid_reset_quirk, set_avoid_reset_quirk); + +static ssize_t +show_urbnum(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", atomic_read(&udev->urbnum)); +} +static DEVICE_ATTR(urbnum, S_IRUGO, show_urbnum, NULL); + +static ssize_t +show_removable(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + char *state; + + udev = to_usb_device(dev); + + switch (udev->removable) { + case USB_DEVICE_REMOVABLE: + state = "removable"; + break; + case USB_DEVICE_FIXED: + state = "fixed"; + break; + default: + state = "unknown"; + } + + return sprintf(buf, "%s\n", state); +} +static DEVICE_ATTR(removable, S_IRUGO, show_removable, NULL); + +static ssize_t +show_ltm_capable(struct device *dev, struct device_attribute *attr, char *buf) +{ + if (usb_device_supports_ltm(to_usb_device(dev))) + return sprintf(buf, "%s\n", "yes"); + return sprintf(buf, "%s\n", "no"); +} +static DEVICE_ATTR(ltm_capable, S_IRUGO, show_ltm_capable, NULL); + +#ifdef CONFIG_PM + +static ssize_t +show_persist(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + + return sprintf(buf, "%d\n", udev->persist_enabled); +} + +static ssize_t +set_persist(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + int value; + + /* Hubs are always enabled for USB_PERSIST */ + if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) + return -EPERM; + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + + usb_lock_device(udev); + udev->persist_enabled = !!value; + usb_unlock_device(udev); + return count; +} + +static DEVICE_ATTR(persist, S_IRUGO | S_IWUSR, show_persist, set_persist); + +static int add_persist_attributes(struct device *dev) +{ + int rc = 0; + + if (is_usb_device(dev)) { + struct usb_device *udev = to_usb_device(dev); + + /* Hubs are automatically enabled for USB_PERSIST, + * no point in creating the attribute file. + */ + if (udev->descriptor.bDeviceClass != USB_CLASS_HUB) + rc = sysfs_add_file_to_group(&dev->kobj, + &dev_attr_persist.attr, + power_group_name); + } + return rc; +} + +static void remove_persist_attributes(struct device *dev) +{ + sysfs_remove_file_from_group(&dev->kobj, + &dev_attr_persist.attr, + power_group_name); +} +#else + +#define add_persist_attributes(dev) 0 +#define remove_persist_attributes(dev) do {} while (0) + +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME + +static ssize_t +show_connected_duration(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + + return sprintf(buf, "%u\n", + jiffies_to_msecs(jiffies - udev->connect_time)); +} + +static DEVICE_ATTR(connected_duration, S_IRUGO, show_connected_duration, NULL); + +/* + * If the device is resumed, the last time the device was suspended has + * been pre-subtracted from active_duration. We add the current time to + * get the duration that the device was actually active. + * + * If the device is suspended, the active_duration is up-to-date. + */ +static ssize_t +show_active_duration(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + int duration; + + if (udev->state != USB_STATE_SUSPENDED) + duration = jiffies_to_msecs(jiffies + udev->active_duration); + else + duration = jiffies_to_msecs(udev->active_duration); + return sprintf(buf, "%u\n", duration); +} + +static DEVICE_ATTR(active_duration, S_IRUGO, show_active_duration, NULL); + +static ssize_t +show_autosuspend(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", dev->power.autosuspend_delay / 1000); +} + +static ssize_t +set_autosuspend(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int value; + + if (sscanf(buf, "%d", &value) != 1 || value >= INT_MAX/1000 || + value <= -INT_MAX/1000) + return -EINVAL; + + pm_runtime_set_autosuspend_delay(dev, value * 1000); + return count; +} + +static DEVICE_ATTR(autosuspend, S_IRUGO | S_IWUSR, + show_autosuspend, set_autosuspend); + +static const char on_string[] = "on"; +static const char auto_string[] = "auto"; + +static void warn_level(void) { + static int level_warned; + + if (!level_warned) { + level_warned = 1; + printk(KERN_WARNING "WARNING! power/level is deprecated; " + "use power/control instead\n"); + } +} + +static ssize_t +show_level(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + const char *p = auto_string; + + warn_level(); + if (udev->state != USB_STATE_SUSPENDED && !udev->dev.power.runtime_auto) + p = on_string; + return sprintf(buf, "%s\n", p); +} + +static ssize_t +set_level(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + int len = count; + char *cp; + int rc = count; + + warn_level(); + cp = memchr(buf, '\n', count); + if (cp) + len = cp - buf; + + usb_lock_device(udev); + + if (len == sizeof on_string - 1 && + strncmp(buf, on_string, len) == 0) + usb_disable_autosuspend(udev); + + else if (len == sizeof auto_string - 1 && + strncmp(buf, auto_string, len) == 0) + usb_enable_autosuspend(udev); + + else + rc = -EINVAL; + + usb_unlock_device(udev); + return rc; +} + +static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level); + +static ssize_t +show_usb2_hardware_lpm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + const char *p; + + if (udev->usb2_hw_lpm_enabled == 1) + p = "enabled"; + else + p = "disabled"; + + return sprintf(buf, "%s\n", p); +} + +static ssize_t +set_usb2_hardware_lpm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + bool value; + int ret; + + usb_lock_device(udev); + + ret = strtobool(buf, &value); + + if (!ret) + ret = usb_set_usb2_hardware_lpm(udev, value); + + usb_unlock_device(udev); + + if (!ret) + return count; + + return ret; +} + +static DEVICE_ATTR(usb2_hardware_lpm, S_IRUGO | S_IWUSR, show_usb2_hardware_lpm, + set_usb2_hardware_lpm); + +static struct attribute *usb2_hardware_lpm_attr[] = { + &dev_attr_usb2_hardware_lpm.attr, + NULL, +}; +static struct attribute_group usb2_hardware_lpm_attr_group = { + .name = power_group_name, + .attrs = usb2_hardware_lpm_attr, +}; + +static struct attribute *power_attrs[] = { + &dev_attr_autosuspend.attr, + &dev_attr_level.attr, + &dev_attr_connected_duration.attr, + &dev_attr_active_duration.attr, + NULL, +}; +static struct attribute_group power_attr_group = { + .name = power_group_name, + .attrs = power_attrs, +}; + +static int add_power_attributes(struct device *dev) +{ + int rc = 0; + + if (is_usb_device(dev)) { + struct usb_device *udev = to_usb_device(dev); + rc = sysfs_merge_group(&dev->kobj, &power_attr_group); + if (udev->usb2_hw_lpm_capable == 1) + rc = sysfs_merge_group(&dev->kobj, + &usb2_hardware_lpm_attr_group); + } + + return rc; +} + +static void remove_power_attributes(struct device *dev) +{ + sysfs_unmerge_group(&dev->kobj, &usb2_hardware_lpm_attr_group); + sysfs_unmerge_group(&dev->kobj, &power_attr_group); +} + +#else + +#define add_power_attributes(dev) 0 +#define remove_power_attributes(dev) do {} while (0) + +#endif /* CONFIG_PM_RUNTIME */ + + +/* Descriptor fields */ +#define usb_descriptor_attr_le16(field, format_string) \ +static ssize_t \ +show_##field(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_device *udev; \ + \ + udev = to_usb_device(dev); \ + return sprintf(buf, format_string, \ + le16_to_cpu(udev->descriptor.field)); \ +} \ +static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); + +usb_descriptor_attr_le16(idVendor, "%04x\n") +usb_descriptor_attr_le16(idProduct, "%04x\n") +usb_descriptor_attr_le16(bcdDevice, "%04x\n") + +#define usb_descriptor_attr(field, format_string) \ +static ssize_t \ +show_##field(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_device *udev; \ + \ + udev = to_usb_device(dev); \ + return sprintf(buf, format_string, udev->descriptor.field); \ +} \ +static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); + +usb_descriptor_attr(bDeviceClass, "%02x\n") +usb_descriptor_attr(bDeviceSubClass, "%02x\n") +usb_descriptor_attr(bDeviceProtocol, "%02x\n") +usb_descriptor_attr(bNumConfigurations, "%d\n") +usb_descriptor_attr(bMaxPacketSize0, "%d\n") + + + +/* show if the device is authorized (1) or not (0) */ +static ssize_t usb_dev_authorized_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_device *usb_dev = to_usb_device(dev); + return snprintf(buf, PAGE_SIZE, "%u\n", usb_dev->authorized); +} + + +/* + * Authorize a device to be used in the system + * + * Writing a 0 deauthorizes the device, writing a 1 authorizes it. + */ +static ssize_t usb_dev_authorized_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_device *usb_dev = to_usb_device(dev); + unsigned val; + result = sscanf(buf, "%u\n", &val); + if (result != 1) + result = -EINVAL; + else if (val == 0) + result = usb_deauthorize_device(usb_dev); + else + result = usb_authorize_device(usb_dev); + return result < 0? result : size; +} + +static DEVICE_ATTR_IGNORE_LOCKDEP(authorized, 0644, + usb_dev_authorized_show, usb_dev_authorized_store); + +/* "Safely remove a device" */ +static ssize_t usb_remove_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + int rc = 0; + + usb_lock_device(udev); + if (udev->state != USB_STATE_NOTATTACHED) { + + /* To avoid races, first unconfigure and then remove */ + usb_set_configuration(udev, -1); + rc = usb_remove_device(udev); + } + if (rc == 0) + rc = count; + usb_unlock_device(udev); + return rc; +} +static DEVICE_ATTR_IGNORE_LOCKDEP(remove, 0200, NULL, usb_remove_store); + + +static struct attribute *dev_attrs[] = { + /* current configuration's attributes */ + &dev_attr_configuration.attr, + &dev_attr_bNumInterfaces.attr, + &dev_attr_bConfigurationValue.attr, + &dev_attr_bmAttributes.attr, + &dev_attr_bMaxPower.attr, + /* device attributes */ + &dev_attr_urbnum.attr, + &dev_attr_idVendor.attr, + &dev_attr_idProduct.attr, + &dev_attr_bcdDevice.attr, + &dev_attr_bDeviceClass.attr, + &dev_attr_bDeviceSubClass.attr, + &dev_attr_bDeviceProtocol.attr, + &dev_attr_bNumConfigurations.attr, + &dev_attr_bMaxPacketSize0.attr, + &dev_attr_speed.attr, + &dev_attr_busnum.attr, + &dev_attr_devnum.attr, + &dev_attr_devpath.attr, + &dev_attr_version.attr, + &dev_attr_maxchild.attr, + &dev_attr_quirks.attr, + &dev_attr_avoid_reset_quirk.attr, + &dev_attr_authorized.attr, + &dev_attr_remove.attr, + &dev_attr_removable.attr, + &dev_attr_ltm_capable.attr, + NULL, +}; +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; + +/* When modifying this list, be sure to modify dev_string_attrs_are_visible() + * accordingly. + */ +static struct attribute *dev_string_attrs[] = { + &dev_attr_manufacturer.attr, + &dev_attr_product.attr, + &dev_attr_serial.attr, + NULL +}; + +static umode_t dev_string_attrs_are_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct usb_device *udev = to_usb_device(dev); + + if (a == &dev_attr_manufacturer.attr) { + if (udev->manufacturer == NULL) + return 0; + } else if (a == &dev_attr_product.attr) { + if (udev->product == NULL) + return 0; + } else if (a == &dev_attr_serial.attr) { + if (udev->serial == NULL) + return 0; + } + return a->mode; +} + +static struct attribute_group dev_string_attr_grp = { + .attrs = dev_string_attrs, + .is_visible = dev_string_attrs_are_visible, +}; + +const struct attribute_group *usb_device_groups[] = { + &dev_attr_grp, + &dev_string_attr_grp, + NULL +}; + +/* Binary descriptors */ + +static ssize_t +read_descriptors(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct usb_device *udev = to_usb_device(dev); + size_t nleft = count; + size_t srclen, n; + int cfgno; + void *src; + + /* The binary attribute begins with the device descriptor. + * Following that are the raw descriptor entries for all the + * configurations (config plus subsidiary descriptors). + */ + for (cfgno = -1; cfgno < udev->descriptor.bNumConfigurations && + nleft > 0; ++cfgno) { + if (cfgno < 0) { + src = &udev->descriptor; + srclen = sizeof(struct usb_device_descriptor); + } else { + src = udev->rawdescriptors[cfgno]; + srclen = __le16_to_cpu(udev->config[cfgno].desc. + wTotalLength); + } + if (off < srclen) { + n = min(nleft, srclen - (size_t) off); + memcpy(buf, src + off, n); + nleft -= n; + buf += n; + off = 0; + } else { + off -= srclen; + } + } + return count - nleft; +} + +static struct bin_attribute dev_bin_attr_descriptors = { + .attr = {.name = "descriptors", .mode = 0444}, + .read = read_descriptors, + .size = 18 + 65535, /* dev descr + max-size raw descriptor */ +}; + +int usb_create_sysfs_dev_files(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + int retval; + + retval = device_create_bin_file(dev, &dev_bin_attr_descriptors); + if (retval) + goto error; + + retval = add_persist_attributes(dev); + if (retval) + goto error; + + retval = add_power_attributes(dev); + if (retval) + goto error; + return retval; +error: + usb_remove_sysfs_dev_files(udev); + return retval; +} + +void usb_remove_sysfs_dev_files(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + + remove_power_attributes(dev); + remove_persist_attributes(dev); + device_remove_bin_file(dev, &dev_bin_attr_descriptors); +} + +/* Interface Accociation Descriptor fields */ +#define usb_intf_assoc_attr(field, format_string) \ +static ssize_t \ +show_iad_##field(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + \ + return sprintf(buf, format_string, \ + intf->intf_assoc->field); \ +} \ +static DEVICE_ATTR(iad_##field, S_IRUGO, show_iad_##field, NULL); + +usb_intf_assoc_attr(bFirstInterface, "%02x\n") +usb_intf_assoc_attr(bInterfaceCount, "%02d\n") +usb_intf_assoc_attr(bFunctionClass, "%02x\n") +usb_intf_assoc_attr(bFunctionSubClass, "%02x\n") +usb_intf_assoc_attr(bFunctionProtocol, "%02x\n") + +/* Interface fields */ +#define usb_intf_attr(field, format_string) \ +static ssize_t \ +show_##field(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + \ + return sprintf(buf, format_string, \ + intf->cur_altsetting->desc.field); \ +} \ +static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); + +usb_intf_attr(bInterfaceNumber, "%02x\n") +usb_intf_attr(bAlternateSetting, "%2d\n") +usb_intf_attr(bNumEndpoints, "%02x\n") +usb_intf_attr(bInterfaceClass, "%02x\n") +usb_intf_attr(bInterfaceSubClass, "%02x\n") +usb_intf_attr(bInterfaceProtocol, "%02x\n") + +static ssize_t show_interface_string(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf; + char *string; + + intf = to_usb_interface(dev); + string = intf->cur_altsetting->string; + barrier(); /* The altsetting might change! */ + + if (!string) + return 0; + return sprintf(buf, "%s\n", string); +} +static DEVICE_ATTR(interface, S_IRUGO, show_interface_string, NULL); + +static ssize_t show_modalias(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf; + struct usb_device *udev; + struct usb_host_interface *alt; + + intf = to_usb_interface(dev); + udev = interface_to_usbdev(intf); + alt = intf->cur_altsetting; + + return sprintf(buf, "usb:v%04Xp%04Xd%04Xdc%02Xdsc%02Xdp%02X" + "ic%02Xisc%02Xip%02Xin%02X\n", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct), + le16_to_cpu(udev->descriptor.bcdDevice), + udev->descriptor.bDeviceClass, + udev->descriptor.bDeviceSubClass, + udev->descriptor.bDeviceProtocol, + alt->desc.bInterfaceClass, + alt->desc.bInterfaceSubClass, + alt->desc.bInterfaceProtocol, + alt->desc.bInterfaceNumber); +} +static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL); + +static ssize_t show_supports_autosuspend(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf; + struct usb_device *udev; + int ret; + + intf = to_usb_interface(dev); + udev = interface_to_usbdev(intf); + + usb_lock_device(udev); + /* Devices will be autosuspended even when an interface isn't claimed */ + if (!intf->dev.driver || + to_usb_driver(intf->dev.driver)->supports_autosuspend) + ret = sprintf(buf, "%u\n", 1); + else + ret = sprintf(buf, "%u\n", 0); + usb_unlock_device(udev); + + return ret; +} +static DEVICE_ATTR(supports_autosuspend, S_IRUGO, show_supports_autosuspend, NULL); + +static struct attribute *intf_attrs[] = { + &dev_attr_bInterfaceNumber.attr, + &dev_attr_bAlternateSetting.attr, + &dev_attr_bNumEndpoints.attr, + &dev_attr_bInterfaceClass.attr, + &dev_attr_bInterfaceSubClass.attr, + &dev_attr_bInterfaceProtocol.attr, + &dev_attr_modalias.attr, + &dev_attr_supports_autosuspend.attr, + NULL, +}; +static struct attribute_group intf_attr_grp = { + .attrs = intf_attrs, +}; + +static struct attribute *intf_assoc_attrs[] = { + &dev_attr_iad_bFirstInterface.attr, + &dev_attr_iad_bInterfaceCount.attr, + &dev_attr_iad_bFunctionClass.attr, + &dev_attr_iad_bFunctionSubClass.attr, + &dev_attr_iad_bFunctionProtocol.attr, + NULL, +}; + +static umode_t intf_assoc_attrs_are_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct usb_interface *intf = to_usb_interface(dev); + + if (intf->intf_assoc == NULL) + return 0; + return a->mode; +} + +static struct attribute_group intf_assoc_attr_grp = { + .attrs = intf_assoc_attrs, + .is_visible = intf_assoc_attrs_are_visible, +}; + +const struct attribute_group *usb_interface_groups[] = { + &intf_attr_grp, + &intf_assoc_attr_grp, + NULL +}; + +void usb_create_sysfs_intf_files(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *alt = intf->cur_altsetting; + + if (intf->sysfs_files_created || intf->unregistering) + return; + + if (!alt->string && !(udev->quirks & USB_QUIRK_CONFIG_INTF_STRINGS)) + alt->string = usb_cache_string(udev, alt->desc.iInterface); + if (alt->string && device_create_file(&intf->dev, &dev_attr_interface)) + ; /* We don't actually care if the function fails. */ + intf->sysfs_files_created = 1; +} + +void usb_remove_sysfs_intf_files(struct usb_interface *intf) +{ + if (!intf->sysfs_files_created) + return; + + device_remove_file(&intf->dev, &dev_attr_interface); + intf->sysfs_files_created = 0; +} diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c new file mode 100644 index 00000000..16927fa8 --- /dev/null +++ b/drivers/usb/core/urb.c @@ -0,0 +1,912 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define to_urb(d) container_of(d, struct urb, kref) + + +static void urb_destroy(struct kref *kref) +{ + struct urb *urb = to_urb(kref); + + if (urb->transfer_flags & URB_FREE_BUFFER) + kfree(urb->transfer_buffer); + + kfree(urb); +} + +/** + * usb_init_urb - initializes a urb so that it can be used by a USB driver + * @urb: pointer to the urb to initialize + * + * Initializes a urb so that the USB subsystem can use it properly. + * + * If a urb is created with a call to usb_alloc_urb() it is not + * necessary to call this function. Only use this if you allocate the + * space for a struct urb on your own. If you call this function, be + * careful when freeing the memory for your urb that it is no longer in + * use by the USB core. + * + * Only use this function if you _really_ understand what you are doing. + */ +void usb_init_urb(struct urb *urb) +{ + if (urb) { + memset(urb, 0, sizeof(*urb)); + kref_init(&urb->kref); + INIT_LIST_HEAD(&urb->anchor_list); + } +} +EXPORT_SYMBOL_GPL(usb_init_urb); + +/** + * usb_alloc_urb - creates a new urb for a USB driver to use + * @iso_packets: number of iso packets for this urb + * @mem_flags: the type of memory to allocate, see kmalloc() for a list of + * valid options for this. + * + * Creates an urb for the USB driver to use, initializes a few internal + * structures, incrementes the usage counter, and returns a pointer to it. + * + * If no memory is available, NULL is returned. + * + * If the driver want to use this urb for interrupt, control, or bulk + * endpoints, pass '0' as the number of iso packets. + * + * The driver must call usb_free_urb() when it is finished with the urb. + */ +struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags) +{ + struct urb *urb; + + urb = kmalloc(sizeof(struct urb) + + iso_packets * sizeof(struct usb_iso_packet_descriptor), + mem_flags); + if (!urb) { + printk(KERN_ERR "alloc_urb: kmalloc failed\n"); + return NULL; + } + usb_init_urb(urb); + return urb; +} +EXPORT_SYMBOL_GPL(usb_alloc_urb); + +/** + * usb_free_urb - frees the memory used by a urb when all users of it are finished + * @urb: pointer to the urb to free, may be NULL + * + * Must be called when a user of a urb is finished with it. When the last user + * of the urb calls this function, the memory of the urb is freed. + * + * Note: The transfer buffer associated with the urb is not freed unless the + * URB_FREE_BUFFER transfer flag is set. + */ +void usb_free_urb(struct urb *urb) +{ + if (urb) + kref_put(&urb->kref, urb_destroy); +} +EXPORT_SYMBOL_GPL(usb_free_urb); + +/** + * usb_get_urb - increments the reference count of the urb + * @urb: pointer to the urb to modify, may be NULL + * + * This must be called whenever a urb is transferred from a device driver to a + * host controller driver. This allows proper reference counting to happen + * for urbs. + * + * A pointer to the urb with the incremented reference counter is returned. + */ +struct urb *usb_get_urb(struct urb *urb) +{ + if (urb) + kref_get(&urb->kref); + return urb; +} +EXPORT_SYMBOL_GPL(usb_get_urb); + +/** + * usb_anchor_urb - anchors an URB while it is processed + * @urb: pointer to the urb to anchor + * @anchor: pointer to the anchor + * + * This can be called to have access to URBs which are to be executed + * without bothering to track them + */ +void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor) +{ + unsigned long flags; + + spin_lock_irqsave(&anchor->lock, flags); + usb_get_urb(urb); + list_add_tail(&urb->anchor_list, &anchor->urb_list); + urb->anchor = anchor; + + if (unlikely(anchor->poisoned)) { + atomic_inc(&urb->reject); + } + + spin_unlock_irqrestore(&anchor->lock, flags); +} +EXPORT_SYMBOL_GPL(usb_anchor_urb); + +/* Callers must hold anchor->lock */ +static void __usb_unanchor_urb(struct urb *urb, struct usb_anchor *anchor) +{ + urb->anchor = NULL; + list_del(&urb->anchor_list); + usb_put_urb(urb); + if (list_empty(&anchor->urb_list)) + wake_up(&anchor->wait); +} + +/** + * usb_unanchor_urb - unanchors an URB + * @urb: pointer to the urb to anchor + * + * Call this to stop the system keeping track of this URB + */ +void usb_unanchor_urb(struct urb *urb) +{ + unsigned long flags; + struct usb_anchor *anchor; + + if (!urb) + return; + + anchor = urb->anchor; + if (!anchor) + return; + + spin_lock_irqsave(&anchor->lock, flags); + /* + * At this point, we could be competing with another thread which + * has the same intention. To protect the urb from being unanchored + * twice, only the winner of the race gets the job. + */ + if (likely(anchor == urb->anchor)) + __usb_unanchor_urb(urb, anchor); + spin_unlock_irqrestore(&anchor->lock, flags); +} +EXPORT_SYMBOL_GPL(usb_unanchor_urb); + +/*-------------------------------------------------------------------*/ + +/** + * usb_submit_urb - issue an asynchronous transfer request for an endpoint + * @urb: pointer to the urb describing the request + * @mem_flags: the type of memory to allocate, see kmalloc() for a list + * of valid options for this. + * + * This submits a transfer request, and transfers control of the URB + * describing that request to the USB subsystem. Request completion will + * be indicated later, asynchronously, by calling the completion handler. + * The three types of completion are success, error, and unlink + * (a software-induced fault, also called "request cancellation"). + * + * URBs may be submitted in interrupt context. + * + * The caller must have correctly initialized the URB before submitting + * it. Functions such as usb_fill_bulk_urb() and usb_fill_control_urb() are + * available to ensure that most fields are correctly initialized, for + * the particular kind of transfer, although they will not initialize + * any transfer flags. + * + * Successful submissions return 0; otherwise this routine returns a + * negative error number. If the submission is successful, the complete() + * callback from the URB will be called exactly once, when the USB core and + * Host Controller Driver (HCD) are finished with the URB. When the completion + * function is called, control of the URB is returned to the device + * driver which issued the request. The completion handler may then + * immediately free or reuse that URB. + * + * With few exceptions, USB device drivers should never access URB fields + * provided by usbcore or the HCD until its complete() is called. + * The exceptions relate to periodic transfer scheduling. For both + * interrupt and isochronous urbs, as part of successful URB submission + * urb->interval is modified to reflect the actual transfer period used + * (normally some power of two units). And for isochronous urbs, + * urb->start_frame is modified to reflect when the URB's transfers were + * scheduled to start. + * + * Not all isochronous transfer scheduling policies will work, but most + * host controller drivers should easily handle ISO queues going from now + * until 10-200 msec into the future. Drivers should try to keep at + * least one or two msec of data in the queue; many controllers require + * that new transfers start at least 1 msec in the future when they are + * added. If the driver is unable to keep up and the queue empties out, + * the behavior for new submissions is governed by the URB_ISO_ASAP flag. + * If the flag is set, or if the queue is idle, then the URB is always + * assigned to the first available (and not yet expired) slot in the + * endpoint's schedule. If the flag is not set and the queue is active + * then the URB is always assigned to the next slot in the schedule + * following the end of the endpoint's previous URB, even if that slot is + * in the past. When a packet is assigned in this way to a slot that has + * already expired, the packet is not transmitted and the corresponding + * usb_iso_packet_descriptor's status field will return -EXDEV. If this + * would happen to all the packets in the URB, submission fails with a + * -EXDEV error code. + * + * For control endpoints, the synchronous usb_control_msg() call is + * often used (in non-interrupt context) instead of this call. + * That is often used through convenience wrappers, for the requests + * that are standardized in the USB 2.0 specification. For bulk + * endpoints, a synchronous usb_bulk_msg() call is available. + * + * Request Queuing: + * + * URBs may be submitted to endpoints before previous ones complete, to + * minimize the impact of interrupt latencies and system overhead on data + * throughput. With that queuing policy, an endpoint's queue would never + * be empty. This is required for continuous isochronous data streams, + * and may also be required for some kinds of interrupt transfers. Such + * queuing also maximizes bandwidth utilization by letting USB controllers + * start work on later requests before driver software has finished the + * completion processing for earlier (successful) requests. + * + * As of Linux 2.6, all USB endpoint transfer queues support depths greater + * than one. This was previously a HCD-specific behavior, except for ISO + * transfers. Non-isochronous endpoint queues are inactive during cleanup + * after faults (transfer errors or cancellation). + * + * Reserved Bandwidth Transfers: + * + * Periodic transfers (interrupt or isochronous) are performed repeatedly, + * using the interval specified in the urb. Submitting the first urb to + * the endpoint reserves the bandwidth necessary to make those transfers. + * If the USB subsystem can't allocate sufficient bandwidth to perform + * the periodic request, submitting such a periodic request should fail. + * + * For devices under xHCI, the bandwidth is reserved at configuration time, or + * when the alt setting is selected. If there is not enough bus bandwidth, the + * configuration/alt setting request will fail. Therefore, submissions to + * periodic endpoints on devices under xHCI should never fail due to bandwidth + * constraints. + * + * Device drivers must explicitly request that repetition, by ensuring that + * some URB is always on the endpoint's queue (except possibly for short + * periods during completion callacks). When there is no longer an urb + * queued, the endpoint's bandwidth reservation is canceled. This means + * drivers can use their completion handlers to ensure they keep bandwidth + * they need, by reinitializing and resubmitting the just-completed urb + * until the driver longer needs that periodic bandwidth. + * + * Memory Flags: + * + * The general rules for how to decide which mem_flags to use + * are the same as for kmalloc. There are four + * different possible values; GFP_KERNEL, GFP_NOFS, GFP_NOIO and + * GFP_ATOMIC. + * + * GFP_NOFS is not ever used, as it has not been implemented yet. + * + * GFP_ATOMIC is used when + * (a) you are inside a completion handler, an interrupt, bottom half, + * tasklet or timer, or + * (b) you are holding a spinlock or rwlock (does not apply to + * semaphores), or + * (c) current->state != TASK_RUNNING, this is the case only after + * you've changed it. + * + * GFP_NOIO is used in the block io path and error handling of storage + * devices. + * + * All other situations use GFP_KERNEL. + * + * Some more specific rules for mem_flags can be inferred, such as + * (1) start_xmit, timeout, and receive methods of network drivers must + * use GFP_ATOMIC (they are called with a spinlock held); + * (2) queuecommand methods of scsi drivers must use GFP_ATOMIC (also + * called with a spinlock held); + * (3) If you use a kernel thread with a network driver you must use + * GFP_NOIO, unless (b) or (c) apply; + * (4) after you have done a down() you can use GFP_KERNEL, unless (b) or (c) + * apply or your are in a storage driver's block io path; + * (5) USB probe and disconnect can use GFP_KERNEL unless (b) or (c) apply; and + * (6) changing firmware on a running storage or net device uses + * GFP_NOIO, unless b) or c) apply + * + */ +int usb_submit_urb(struct urb *urb, gfp_t mem_flags) +{ + int xfertype, max; + struct usb_device *dev; + struct usb_host_endpoint *ep; + int is_out; + + if (!urb || !urb->complete) + return -EINVAL; + if (urb->hcpriv) { + WARN_ONCE(1, "URB %p submitted while active\n", urb); + return -EBUSY; + } + + dev = urb->dev; + if ((!dev) || (dev->state < USB_STATE_UNAUTHENTICATED)) + return -ENODEV; + + /* For now, get the endpoint from the pipe. Eventually drivers + * will be required to set urb->ep directly and we will eliminate + * urb->pipe. + */ + ep = usb_pipe_endpoint(dev, urb->pipe); + if (!ep) + return -ENOENT; + + urb->ep = ep; + urb->status = -EINPROGRESS; + urb->actual_length = 0; + + /* Lots of sanity checks, so HCDs can rely on clean data + * and don't need to duplicate tests + */ + xfertype = usb_endpoint_type(&ep->desc); + if (xfertype == USB_ENDPOINT_XFER_CONTROL) { + struct usb_ctrlrequest *setup = + (struct usb_ctrlrequest *) urb->setup_packet; + + if (!setup) + return -ENOEXEC; + is_out = !(setup->bRequestType & USB_DIR_IN) || + !setup->wLength; + } else { + is_out = usb_endpoint_dir_out(&ep->desc); + } + + /* Clear the internal flags and cache the direction for later use */ + urb->transfer_flags &= ~(URB_DIR_MASK | URB_DMA_MAP_SINGLE | + URB_DMA_MAP_PAGE | URB_DMA_MAP_SG | URB_MAP_LOCAL | + URB_SETUP_MAP_SINGLE | URB_SETUP_MAP_LOCAL | + URB_DMA_SG_COMBINED); + urb->transfer_flags |= (is_out ? URB_DIR_OUT : URB_DIR_IN); + + if (xfertype != USB_ENDPOINT_XFER_CONTROL && + dev->state < USB_STATE_CONFIGURED) + return -ENODEV; + + max = usb_endpoint_maxp(&ep->desc); + if (max <= 0) { + dev_dbg(&dev->dev, + "bogus endpoint ep%d%s in %s (bad maxpacket %d)\n", + usb_endpoint_num(&ep->desc), is_out ? "out" : "in", + __func__, max); + return -EMSGSIZE; + } + + /* periodic transfers limit size per frame/uframe, + * but drivers only control those sizes for ISO. + * while we're checking, initialize return status. + */ + if (xfertype == USB_ENDPOINT_XFER_ISOC) { + int n, len; + + /* SuperSpeed isoc endpoints have up to 16 bursts of up to + * 3 packets each + */ + if (dev->speed == USB_SPEED_SUPER) { + int burst = 1 + ep->ss_ep_comp.bMaxBurst; + int mult = USB_SS_MULT(ep->ss_ep_comp.bmAttributes); + max *= burst; + max *= mult; + } + + /* "high bandwidth" mode, 1-3 packets/uframe? */ + if (dev->speed == USB_SPEED_HIGH) { + int mult = 1 + ((max >> 11) & 0x03); + max &= 0x07ff; + max *= mult; + } + + if (urb->number_of_packets <= 0) + return -EINVAL; + for (n = 0; n < urb->number_of_packets; n++) { + len = urb->iso_frame_desc[n].length; + if (len < 0 || len > max) + return -EMSGSIZE; + urb->iso_frame_desc[n].status = -EXDEV; + urb->iso_frame_desc[n].actual_length = 0; + } + } + + /* the I/O buffer must be mapped/unmapped, except when length=0 */ + if (urb->transfer_buffer_length > INT_MAX) + return -EMSGSIZE; + +#ifdef DEBUG + /* stuff that drivers shouldn't do, but which shouldn't + * cause problems in HCDs if they get it wrong. + */ + { + unsigned int allowed; + static int pipetypes[4] = { + PIPE_CONTROL, PIPE_ISOCHRONOUS, PIPE_BULK, PIPE_INTERRUPT + }; + + /* Check that the pipe's type matches the endpoint's type */ + if (usb_pipetype(urb->pipe) != pipetypes[xfertype]) + dev_WARN(&dev->dev, "BOGUS urb xfer, pipe %x != type %x\n", + usb_pipetype(urb->pipe), pipetypes[xfertype]); + + /* Check against a simple/standard policy */ + allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_INTERRUPT | URB_DIR_MASK | + URB_FREE_BUFFER); + switch (xfertype) { + case USB_ENDPOINT_XFER_BULK: + if (is_out) + allowed |= URB_ZERO_PACKET; + /* FALLTHROUGH */ + case USB_ENDPOINT_XFER_CONTROL: + allowed |= URB_NO_FSBR; /* only affects UHCI */ + /* FALLTHROUGH */ + default: /* all non-iso endpoints */ + if (!is_out) + allowed |= URB_SHORT_NOT_OK; + break; + case USB_ENDPOINT_XFER_ISOC: + allowed |= URB_ISO_ASAP; + break; + } + allowed &= urb->transfer_flags; + + /* warn if submitter gave bogus flags */ + if (allowed != urb->transfer_flags) + dev_WARN(&dev->dev, "BOGUS urb flags, %x --> %x\n", + urb->transfer_flags, allowed); + } +#endif + /* + * Force periodic transfer intervals to be legal values that are + * a power of two (so HCDs don't need to). + * + * FIXME want bus->{intr,iso}_sched_horizon values here. Each HC + * supports different values... this uses EHCI/UHCI defaults (and + * EHCI can use smaller non-default values). + */ + switch (xfertype) { + case USB_ENDPOINT_XFER_ISOC: + case USB_ENDPOINT_XFER_INT: + /* too small? */ + switch (dev->speed) { + case USB_SPEED_WIRELESS: + if (urb->interval < 6) + return -EINVAL; + break; + default: + if (urb->interval <= 0) + return -EINVAL; + break; + } + /* too big? */ + switch (dev->speed) { + case USB_SPEED_SUPER: /* units are 125us */ + /* Handle up to 2^(16-1) microframes */ + if (urb->interval > (1 << 15)) + return -EINVAL; + max = 1 << 15; + break; + case USB_SPEED_WIRELESS: + if (urb->interval > 16) + return -EINVAL; + break; + case USB_SPEED_HIGH: /* units are microframes */ + /* NOTE usb handles 2^15 */ + if (urb->interval > (1024 * 8)) + urb->interval = 1024 * 8; + max = 1024 * 8; + break; + case USB_SPEED_FULL: /* units are frames/msec */ + case USB_SPEED_LOW: + if (xfertype == USB_ENDPOINT_XFER_INT) { + if (urb->interval > 255) + return -EINVAL; + /* NOTE ohci only handles up to 32 */ + max = 128; + } else { + if (urb->interval > 1024) + urb->interval = 1024; + /* NOTE usb and ohci handle up to 2^15 */ + max = 1024; + } + break; + default: + return -EINVAL; + } + if (dev->speed != USB_SPEED_WIRELESS) { + /* Round down to a power of 2, no more than max */ + urb->interval = min(max, 1 << ilog2(urb->interval)); + } + } + + return usb_hcd_submit_urb(urb, mem_flags); +} +EXPORT_SYMBOL_GPL(usb_submit_urb); + +/*-------------------------------------------------------------------*/ + +/** + * usb_unlink_urb - abort/cancel a transfer request for an endpoint + * @urb: pointer to urb describing a previously submitted request, + * may be NULL + * + * This routine cancels an in-progress request. URBs complete only once + * per submission, and may be canceled only once per submission. + * Successful cancellation means termination of @urb will be expedited + * and the completion handler will be called with a status code + * indicating that the request has been canceled (rather than any other + * code). + * + * Drivers should not call this routine or related routines, such as + * usb_kill_urb() or usb_unlink_anchored_urbs(), after their disconnect + * method has returned. The disconnect function should synchronize with + * a driver's I/O routines to insure that all URB-related activity has + * completed before it returns. + * + * This request is asynchronous, however the HCD might call the ->complete() + * callback during unlink. Therefore when drivers call usb_unlink_urb(), they + * must not hold any locks that may be taken by the completion function. + * Success is indicated by returning -EINPROGRESS, at which time the URB will + * probably not yet have been given back to the device driver. When it is + * eventually called, the completion function will see @urb->status == + * -ECONNRESET. + * Failure is indicated by usb_unlink_urb() returning any other value. + * Unlinking will fail when @urb is not currently "linked" (i.e., it was + * never submitted, or it was unlinked before, or the hardware is already + * finished with it), even if the completion handler has not yet run. + * + * The URB must not be deallocated while this routine is running. In + * particular, when a driver calls this routine, it must insure that the + * completion handler cannot deallocate the URB. + * + * Unlinking and Endpoint Queues: + * + * [The behaviors and guarantees described below do not apply to virtual + * root hubs but only to endpoint queues for physical USB devices.] + * + * Host Controller Drivers (HCDs) place all the URBs for a particular + * endpoint in a queue. Normally the queue advances as the controller + * hardware processes each request. But when an URB terminates with an + * error its queue generally stops (see below), at least until that URB's + * completion routine returns. It is guaranteed that a stopped queue + * will not restart until all its unlinked URBs have been fully retired, + * with their completion routines run, even if that's not until some time + * after the original completion handler returns. The same behavior and + * guarantee apply when an URB terminates because it was unlinked. + * + * Bulk and interrupt endpoint queues are guaranteed to stop whenever an + * URB terminates with any sort of error, including -ECONNRESET, -ENOENT, + * and -EREMOTEIO. Control endpoint queues behave the same way except + * that they are not guaranteed to stop for -EREMOTEIO errors. Queues + * for isochronous endpoints are treated differently, because they must + * advance at fixed rates. Such queues do not stop when an URB + * encounters an error or is unlinked. An unlinked isochronous URB may + * leave a gap in the stream of packets; it is undefined whether such + * gaps can be filled in. + * + * Note that early termination of an URB because a short packet was + * received will generate a -EREMOTEIO error if and only if the + * URB_SHORT_NOT_OK flag is set. By setting this flag, USB device + * drivers can build deep queues for large or complex bulk transfers + * and clean them up reliably after any sort of aborted transfer by + * unlinking all pending URBs at the first fault. + * + * When a control URB terminates with an error other than -EREMOTEIO, it + * is quite likely that the status stage of the transfer will not take + * place. + */ +int usb_unlink_urb(struct urb *urb) +{ + if (!urb) + return -EINVAL; + if (!urb->dev) + return -ENODEV; + if (!urb->ep) + return -EIDRM; + return usb_hcd_unlink_urb(urb, -ECONNRESET); +} +EXPORT_SYMBOL_GPL(usb_unlink_urb); + +/** + * usb_kill_urb - cancel a transfer request and wait for it to finish + * @urb: pointer to URB describing a previously submitted request, + * may be NULL + * + * This routine cancels an in-progress request. It is guaranteed that + * upon return all completion handlers will have finished and the URB + * will be totally idle and available for reuse. These features make + * this an ideal way to stop I/O in a disconnect() callback or close() + * function. If the request has not already finished or been unlinked + * the completion handler will see urb->status == -ENOENT. + * + * While the routine is running, attempts to resubmit the URB will fail + * with error -EPERM. Thus even if the URB's completion handler always + * tries to resubmit, it will not succeed and the URB will become idle. + * + * The URB must not be deallocated while this routine is running. In + * particular, when a driver calls this routine, it must insure that the + * completion handler cannot deallocate the URB. + * + * This routine may not be used in an interrupt context (such as a bottom + * half or a completion handler), or when holding a spinlock, or in other + * situations where the caller can't schedule(). + * + * This routine should not be called by a driver after its disconnect + * method has returned. + */ +void usb_kill_urb(struct urb *urb) +{ + might_sleep(); + if (!(urb && urb->dev && urb->ep)) + return; + atomic_inc(&urb->reject); + + usb_hcd_unlink_urb(urb, -ENOENT); + wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); + + atomic_dec(&urb->reject); +} +EXPORT_SYMBOL_GPL(usb_kill_urb); + +/** + * usb_poison_urb - reliably kill a transfer and prevent further use of an URB + * @urb: pointer to URB describing a previously submitted request, + * may be NULL + * + * This routine cancels an in-progress request. It is guaranteed that + * upon return all completion handlers will have finished and the URB + * will be totally idle and cannot be reused. These features make + * this an ideal way to stop I/O in a disconnect() callback. + * If the request has not already finished or been unlinked + * the completion handler will see urb->status == -ENOENT. + * + * After and while the routine runs, attempts to resubmit the URB will fail + * with error -EPERM. Thus even if the URB's completion handler always + * tries to resubmit, it will not succeed and the URB will become idle. + * + * The URB must not be deallocated while this routine is running. In + * particular, when a driver calls this routine, it must insure that the + * completion handler cannot deallocate the URB. + * + * This routine may not be used in an interrupt context (such as a bottom + * half or a completion handler), or when holding a spinlock, or in other + * situations where the caller can't schedule(). + * + * This routine should not be called by a driver after its disconnect + * method has returned. + */ +void usb_poison_urb(struct urb *urb) +{ + might_sleep(); + if (!urb) + return; + atomic_inc(&urb->reject); + + if (!urb->dev || !urb->ep) + return; + + usb_hcd_unlink_urb(urb, -ENOENT); + wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); +} +EXPORT_SYMBOL_GPL(usb_poison_urb); + +void usb_unpoison_urb(struct urb *urb) +{ + if (!urb) + return; + + atomic_dec(&urb->reject); +} +EXPORT_SYMBOL_GPL(usb_unpoison_urb); + +/** + * usb_block_urb - reliably prevent further use of an URB + * @urb: pointer to URB to be blocked, may be NULL + * + * After the routine has run, attempts to resubmit the URB will fail + * with error -EPERM. Thus even if the URB's completion handler always + * tries to resubmit, it will not succeed and the URB will become idle. + * + * The URB must not be deallocated while this routine is running. In + * particular, when a driver calls this routine, it must insure that the + * completion handler cannot deallocate the URB. + */ +void usb_block_urb(struct urb *urb) +{ + if (!urb) + return; + + atomic_inc(&urb->reject); +} +EXPORT_SYMBOL_GPL(usb_block_urb); + +/** + * usb_kill_anchored_urbs - cancel transfer requests en masse + * @anchor: anchor the requests are bound to + * + * this allows all outstanding URBs to be killed starting + * from the back of the queue + * + * This routine should not be called by a driver after its disconnect + * method has returned. + */ +void usb_kill_anchored_urbs(struct usb_anchor *anchor) +{ + struct urb *victim; + + spin_lock_irq(&anchor->lock); + while (!list_empty(&anchor->urb_list)) { + victim = list_entry(anchor->urb_list.prev, struct urb, + anchor_list); + /* we must make sure the URB isn't freed before we kill it*/ + usb_get_urb(victim); + spin_unlock_irq(&anchor->lock); + /* this will unanchor the URB */ + usb_kill_urb(victim); + usb_put_urb(victim); + spin_lock_irq(&anchor->lock); + } + spin_unlock_irq(&anchor->lock); +} +EXPORT_SYMBOL_GPL(usb_kill_anchored_urbs); + + +/** + * usb_poison_anchored_urbs - cease all traffic from an anchor + * @anchor: anchor the requests are bound to + * + * this allows all outstanding URBs to be poisoned starting + * from the back of the queue. Newly added URBs will also be + * poisoned + * + * This routine should not be called by a driver after its disconnect + * method has returned. + */ +void usb_poison_anchored_urbs(struct usb_anchor *anchor) +{ + struct urb *victim; + + spin_lock_irq(&anchor->lock); + anchor->poisoned = 1; + while (!list_empty(&anchor->urb_list)) { + victim = list_entry(anchor->urb_list.prev, struct urb, + anchor_list); + /* we must make sure the URB isn't freed before we kill it*/ + usb_get_urb(victim); + spin_unlock_irq(&anchor->lock); + /* this will unanchor the URB */ + usb_poison_urb(victim); + usb_put_urb(victim); + spin_lock_irq(&anchor->lock); + } + spin_unlock_irq(&anchor->lock); +} +EXPORT_SYMBOL_GPL(usb_poison_anchored_urbs); + +/** + * usb_unpoison_anchored_urbs - let an anchor be used successfully again + * @anchor: anchor the requests are bound to + * + * Reverses the effect of usb_poison_anchored_urbs + * the anchor can be used normally after it returns + */ +void usb_unpoison_anchored_urbs(struct usb_anchor *anchor) +{ + unsigned long flags; + struct urb *lazarus; + + spin_lock_irqsave(&anchor->lock, flags); + list_for_each_entry(lazarus, &anchor->urb_list, anchor_list) { + usb_unpoison_urb(lazarus); + } + anchor->poisoned = 0; + spin_unlock_irqrestore(&anchor->lock, flags); +} +EXPORT_SYMBOL_GPL(usb_unpoison_anchored_urbs); +/** + * usb_unlink_anchored_urbs - asynchronously cancel transfer requests en masse + * @anchor: anchor the requests are bound to + * + * this allows all outstanding URBs to be unlinked starting + * from the back of the queue. This function is asynchronous. + * The unlinking is just tiggered. It may happen after this + * function has returned. + * + * This routine should not be called by a driver after its disconnect + * method has returned. + */ +void usb_unlink_anchored_urbs(struct usb_anchor *anchor) +{ + struct urb *victim; + + while ((victim = usb_get_from_anchor(anchor)) != NULL) { + usb_unlink_urb(victim); + usb_put_urb(victim); + } +} +EXPORT_SYMBOL_GPL(usb_unlink_anchored_urbs); + +/** + * usb_wait_anchor_empty_timeout - wait for an anchor to be unused + * @anchor: the anchor you want to become unused + * @timeout: how long you are willing to wait in milliseconds + * + * Call this is you want to be sure all an anchor's + * URBs have finished + */ +int usb_wait_anchor_empty_timeout(struct usb_anchor *anchor, + unsigned int timeout) +{ + return wait_event_timeout(anchor->wait, list_empty(&anchor->urb_list), + msecs_to_jiffies(timeout)); +} +EXPORT_SYMBOL_GPL(usb_wait_anchor_empty_timeout); + +/** + * usb_get_from_anchor - get an anchor's oldest urb + * @anchor: the anchor whose urb you want + * + * this will take the oldest urb from an anchor, + * unanchor and return it + */ +struct urb *usb_get_from_anchor(struct usb_anchor *anchor) +{ + struct urb *victim; + unsigned long flags; + + spin_lock_irqsave(&anchor->lock, flags); + if (!list_empty(&anchor->urb_list)) { + victim = list_entry(anchor->urb_list.next, struct urb, + anchor_list); + usb_get_urb(victim); + __usb_unanchor_urb(victim, anchor); + } else { + victim = NULL; + } + spin_unlock_irqrestore(&anchor->lock, flags); + + return victim; +} + +EXPORT_SYMBOL_GPL(usb_get_from_anchor); + +/** + * usb_scuttle_anchored_urbs - unanchor all an anchor's urbs + * @anchor: the anchor whose urbs you want to unanchor + * + * use this to get rid of all an anchor's urbs + */ +void usb_scuttle_anchored_urbs(struct usb_anchor *anchor) +{ + struct urb *victim; + unsigned long flags; + + spin_lock_irqsave(&anchor->lock, flags); + while (!list_empty(&anchor->urb_list)) { + victim = list_entry(anchor->urb_list.prev, struct urb, + anchor_list); + __usb_unanchor_urb(victim, anchor); + } + spin_unlock_irqrestore(&anchor->lock, flags); +} + +EXPORT_SYMBOL_GPL(usb_scuttle_anchored_urbs); + +/** + * usb_anchor_empty - is an anchor empty + * @anchor: the anchor you want to query + * + * returns 1 if the anchor has no urbs associated with it + */ +int usb_anchor_empty(struct usb_anchor *anchor) +{ + return list_empty(&anchor->urb_list); +} + +EXPORT_SYMBOL_GPL(usb_anchor_empty); + diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c new file mode 100644 index 00000000..255c1446 --- /dev/null +++ b/drivers/usb/core/usb-acpi.c @@ -0,0 +1,238 @@ +/* + * USB-ACPI glue code + * + * Copyright 2012 Red Hat + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 2. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb.h" + +/** + * usb_acpi_power_manageable - check whether usb port has + * acpi power resource. + * @hdev: USB device belonging to the usb hub + * @index: port index based zero + * + * Return true if the port has acpi power resource and false if no. + */ +bool usb_acpi_power_manageable(struct usb_device *hdev, int index) +{ + acpi_handle port_handle; + int port1 = index + 1; + + port_handle = usb_get_hub_port_acpi_handle(hdev, + port1); + if (port_handle) + return acpi_bus_power_manageable(port_handle); + else + return false; +} +EXPORT_SYMBOL_GPL(usb_acpi_power_manageable); + +/** + * usb_acpi_set_power_state - control usb port's power via acpi power + * resource + * @hdev: USB device belonging to the usb hub + * @index: port index based zero + * @enable: power state expected to be set + * + * Notice to use usb_acpi_power_manageable() to check whether the usb port + * has acpi power resource before invoking this function. + * + * Returns 0 on success, else negative errno. + */ +int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable) +{ + acpi_handle port_handle; + unsigned char state; + int port1 = index + 1; + int error = -EINVAL; + + port_handle = (acpi_handle)usb_get_hub_port_acpi_handle(hdev, + port1); + if (!port_handle) + return error; + + if (enable) + state = ACPI_STATE_D0; + else + state = ACPI_STATE_D3_COLD; + + error = acpi_bus_set_power(port_handle, state); + if (!error) + dev_dbg(&hdev->dev, "The power of hub port %d was set to %d\n", + port1, enable); + else + dev_dbg(&hdev->dev, "The power of hub port failed to be set\n"); + + return error; +} +EXPORT_SYMBOL_GPL(usb_acpi_set_power_state); + +static int usb_acpi_check_port_connect_type(struct usb_device *hdev, + acpi_handle handle, int port1) +{ + acpi_status status; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *upc; + struct acpi_pld_info *pld; + int ret = 0; + + /* + * Accoding to ACPI Spec 9.13. PLD indicates whether usb port is + * user visible and _UPC indicates whether it is connectable. If + * the port was visible and connectable, it could be freely connected + * and disconnected with USB devices. If no visible and connectable, + * a usb device is directly hard-wired to the port. If no visible and + * no connectable, the port would be not used. + */ + status = acpi_get_physical_device_location(handle, &pld); + if (ACPI_FAILURE(status)) + return -ENODEV; + + status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer); + upc = buffer.pointer; + if (!upc || (upc->type != ACPI_TYPE_PACKAGE) + || upc->package.count != 4) { + ret = -EINVAL; + goto out; + } + + if (upc->package.elements[0].integer.value) + if (pld->user_visible) + usb_set_hub_port_connect_type(hdev, port1, + USB_PORT_CONNECT_TYPE_HOT_PLUG); + else + usb_set_hub_port_connect_type(hdev, port1, + USB_PORT_CONNECT_TYPE_HARD_WIRED); + else if (!pld->user_visible) + usb_set_hub_port_connect_type(hdev, port1, USB_PORT_NOT_USED); + +out: + ACPI_FREE(pld); + kfree(upc); + return ret; +} + +static int usb_acpi_find_device(struct device *dev, acpi_handle *handle) +{ + struct usb_device *udev; + acpi_handle *parent_handle; + int port_num; + + /* + * In the ACPI DSDT table, only usb root hub and usb ports are + * acpi device nodes. The hierarchy like following. + * Device (EHC1) + * Device (HUBN) + * Device (PR01) + * Device (PR11) + * Device (PR12) + * Device (PR13) + * ... + * So all binding process is divided into two parts. binding + * root hub and usb ports. + */ + if (is_usb_device(dev)) { + udev = to_usb_device(dev); + if (udev->parent) { + enum usb_port_connect_type type; + + /* + * According usb port's connect type to set usb device's + * removability. + */ + type = usb_get_hub_port_connect_type(udev->parent, + udev->portnum); + switch (type) { + case USB_PORT_CONNECT_TYPE_HOT_PLUG: + udev->removable = USB_DEVICE_REMOVABLE; + break; + case USB_PORT_CONNECT_TYPE_HARD_WIRED: + udev->removable = USB_DEVICE_FIXED; + break; + default: + udev->removable = USB_DEVICE_REMOVABLE_UNKNOWN; + break; + } + + return -ENODEV; + } + + /* root hub's parent is the usb hcd. */ + parent_handle = DEVICE_ACPI_HANDLE(dev->parent); + *handle = acpi_get_child(parent_handle, udev->portnum); + if (!*handle) + return -ENODEV; + return 0; + } else if (is_usb_port(dev)) { + sscanf(dev_name(dev), "port%d", &port_num); + /* Get the struct usb_device point of port's hub */ + udev = to_usb_device(dev->parent->parent); + + /* + * The root hub ports' parent is the root hub. The non-root-hub + * ports' parent is the parent hub port which the hub is + * connected to. + */ + if (!udev->parent) { + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + int raw_port_num; + + raw_port_num = usb_hcd_find_raw_port_number(hcd, + port_num); + *handle = acpi_get_child(DEVICE_ACPI_HANDLE(&udev->dev), + raw_port_num); + if (!*handle) + return -ENODEV; + } else { + parent_handle = + usb_get_hub_port_acpi_handle(udev->parent, + udev->portnum); + if (!parent_handle) + return -ENODEV; + + *handle = acpi_get_child(parent_handle, port_num); + if (!*handle) + return -ENODEV; + } + usb_acpi_check_port_connect_type(udev, *handle, port_num); + } else + return -ENODEV; + + return 0; +} + +static bool usb_acpi_bus_match(struct device *dev) +{ + return is_usb_device(dev) || is_usb_port(dev); +} + +static struct acpi_bus_type usb_acpi_bus = { + .name = "USB", + .match = usb_acpi_bus_match, + .find_device = usb_acpi_find_device, +}; + +int usb_acpi_register(void) +{ + return register_acpi_bus_type(&usb_acpi_bus); +} + +void usb_acpi_unregister(void) +{ + unregister_acpi_bus_type(&usb_acpi_bus); +} diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c new file mode 100644 index 00000000..b10da720 --- /dev/null +++ b/drivers/usb/core/usb.c @@ -0,0 +1,1074 @@ +/* + * drivers/usb/core/usb.c + * + * (C) Copyright Linus Torvalds 1999 + * (C) Copyright Johannes Erdfelt 1999-2001 + * (C) Copyright Andreas Gal 1999 + * (C) Copyright Gregory P. Smith 1999 + * (C) Copyright Deti Fliegl 1999 (new USB architecture) + * (C) Copyright Randy Dunlap 2000 + * (C) Copyright David Brownell 2000-2004 + * (C) Copyright Yggdrasil Computing, Inc. 2000 + * (usb_device_id matching changes by Adam J. Richter) + * (C) Copyright Greg Kroah-Hartman 2002-2003 + * + * NOTE! This is not actually a driver at all, rather this is + * just a collection of helper routines that implement the + * generic USB things that the real drivers can use.. + * + * Think of this as a "USB library" rather than anything else. + * It should be considered a slave, with no callbacks. Callbacks + * are evil. + */ + +#include +#include +#include +#include +#include +#include /* for in_interrupt() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "usb.h" + + +const char *usbcore_name = "usbcore"; + +static bool nousb; /* Disable USB when built into kernel image */ + +#ifdef CONFIG_PM_RUNTIME +static int usb_autosuspend_delay = 2; /* Default delay value, + * in seconds */ +module_param_named(autosuspend, usb_autosuspend_delay, int, 0644); +MODULE_PARM_DESC(autosuspend, "default autosuspend delay"); + +#else +#define usb_autosuspend_delay 0 +#endif + + +/** + * usb_find_alt_setting() - Given a configuration, find the alternate setting + * for the given interface. + * @config: the configuration to search (not necessarily the current config). + * @iface_num: interface number to search in + * @alt_num: alternate interface setting number to search for. + * + * Search the configuration's interface cache for the given alt setting. + */ +struct usb_host_interface *usb_find_alt_setting( + struct usb_host_config *config, + unsigned int iface_num, + unsigned int alt_num) +{ + struct usb_interface_cache *intf_cache = NULL; + int i; + + for (i = 0; i < config->desc.bNumInterfaces; i++) { + if (config->intf_cache[i]->altsetting[0].desc.bInterfaceNumber + == iface_num) { + intf_cache = config->intf_cache[i]; + break; + } + } + if (!intf_cache) + return NULL; + for (i = 0; i < intf_cache->num_altsetting; i++) + if (intf_cache->altsetting[i].desc.bAlternateSetting == alt_num) + return &intf_cache->altsetting[i]; + + printk(KERN_DEBUG "Did not find alt setting %u for intf %u, " + "config %u\n", alt_num, iface_num, + config->desc.bConfigurationValue); + return NULL; +} +EXPORT_SYMBOL_GPL(usb_find_alt_setting); + +/** + * usb_ifnum_to_if - get the interface object with a given interface number + * @dev: the device whose current configuration is considered + * @ifnum: the desired interface + * + * This walks the device descriptor for the currently active configuration + * and returns a pointer to the interface with that particular interface + * number, or null. + * + * Note that configuration descriptors are not required to assign interface + * numbers sequentially, so that it would be incorrect to assume that + * the first interface in that descriptor corresponds to interface zero. + * This routine helps device drivers avoid such mistakes. + * However, you should make sure that you do the right thing with any + * alternate settings available for this interfaces. + * + * Don't call this function unless you are bound to one of the interfaces + * on this device or you have locked the device! + */ +struct usb_interface *usb_ifnum_to_if(const struct usb_device *dev, + unsigned ifnum) +{ + struct usb_host_config *config = dev->actconfig; + int i; + + if (!config) + return NULL; + for (i = 0; i < config->desc.bNumInterfaces; i++) + if (config->interface[i]->altsetting[0] + .desc.bInterfaceNumber == ifnum) + return config->interface[i]; + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_ifnum_to_if); + +/** + * usb_altnum_to_altsetting - get the altsetting structure with a given alternate setting number. + * @intf: the interface containing the altsetting in question + * @altnum: the desired alternate setting number + * + * This searches the altsetting array of the specified interface for + * an entry with the correct bAlternateSetting value and returns a pointer + * to that entry, or null. + * + * Note that altsettings need not be stored sequentially by number, so + * it would be incorrect to assume that the first altsetting entry in + * the array corresponds to altsetting zero. This routine helps device + * drivers avoid such mistakes. + * + * Don't call this function unless you are bound to the intf interface + * or you have locked the device! + */ +struct usb_host_interface *usb_altnum_to_altsetting( + const struct usb_interface *intf, + unsigned int altnum) +{ + int i; + + for (i = 0; i < intf->num_altsetting; i++) { + if (intf->altsetting[i].desc.bAlternateSetting == altnum) + return &intf->altsetting[i]; + } + return NULL; +} +EXPORT_SYMBOL_GPL(usb_altnum_to_altsetting); + +struct find_interface_arg { + int minor; + struct device_driver *drv; +}; + +static int __find_interface(struct device *dev, void *data) +{ + struct find_interface_arg *arg = data; + struct usb_interface *intf; + + if (!is_usb_interface(dev)) + return 0; + + if (dev->driver != arg->drv) + return 0; + intf = to_usb_interface(dev); + return intf->minor == arg->minor; +} + +/** + * usb_find_interface - find usb_interface pointer for driver and device + * @drv: the driver whose current configuration is considered + * @minor: the minor number of the desired device + * + * This walks the bus device list and returns a pointer to the interface + * with the matching minor and driver. Note, this only works for devices + * that share the USB major number. + */ +struct usb_interface *usb_find_interface(struct usb_driver *drv, int minor) +{ + struct find_interface_arg argb; + struct device *dev; + + argb.minor = minor; + argb.drv = &drv->drvwrap.driver; + + dev = bus_find_device(&usb_bus_type, NULL, &argb, __find_interface); + + /* Drop reference count from bus_find_device */ + put_device(dev); + + return dev ? to_usb_interface(dev) : NULL; +} +EXPORT_SYMBOL_GPL(usb_find_interface); + +/** + * usb_release_dev - free a usb device structure when all users of it are finished. + * @dev: device that's been disconnected + * + * Will be called only by the device core when all users of this usb device are + * done. + */ +static void usb_release_dev(struct device *dev) +{ + struct usb_device *udev; + struct usb_hcd *hcd; + + udev = to_usb_device(dev); + hcd = bus_to_hcd(udev->bus); + + usb_destroy_configuration(udev); + usb_release_bos_descriptor(udev); + usb_put_hcd(hcd); + kfree(udev->product); + kfree(udev->manufacturer); + kfree(udev->serial); + kfree(udev); +} + +static int usb_dev_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct usb_device *usb_dev; + + usb_dev = to_usb_device(dev); + + if (add_uevent_var(env, "BUSNUM=%03d", usb_dev->bus->busnum)) + return -ENOMEM; + + if (add_uevent_var(env, "DEVNUM=%03d", usb_dev->devnum)) + return -ENOMEM; + + return 0; +} + +#ifdef CONFIG_PM + +/* USB device Power-Management thunks. + * There's no need to distinguish here between quiescing a USB device + * and powering it down; the generic_suspend() routine takes care of + * it by skipping the usb_port_suspend() call for a quiesce. And for + * USB interfaces there's no difference at all. + */ + +static int usb_dev_prepare(struct device *dev) +{ + return 0; /* Implement eventually? */ +} + +static void usb_dev_complete(struct device *dev) +{ + /* Currently used only for rebinding interfaces */ + usb_resume_complete(dev); +} + +static int usb_dev_suspend(struct device *dev) +{ + return usb_suspend(dev, PMSG_SUSPEND); +} + +static int usb_dev_resume(struct device *dev) +{ + return usb_resume(dev, PMSG_RESUME); +} + +static int usb_dev_freeze(struct device *dev) +{ + return usb_suspend(dev, PMSG_FREEZE); +} + +static int usb_dev_thaw(struct device *dev) +{ + return usb_resume(dev, PMSG_THAW); +} + +static int usb_dev_poweroff(struct device *dev) +{ + return usb_suspend(dev, PMSG_HIBERNATE); +} + +static int usb_dev_restore(struct device *dev) +{ + return usb_resume(dev, PMSG_RESTORE); +} + +static const struct dev_pm_ops usb_device_pm_ops = { + .prepare = usb_dev_prepare, + .complete = usb_dev_complete, + .suspend = usb_dev_suspend, + .resume = usb_dev_resume, + .freeze = usb_dev_freeze, + .thaw = usb_dev_thaw, + .poweroff = usb_dev_poweroff, + .restore = usb_dev_restore, +#ifdef CONFIG_PM_RUNTIME + .runtime_suspend = usb_runtime_suspend, + .runtime_resume = usb_runtime_resume, + .runtime_idle = usb_runtime_idle, +#endif +}; + +#endif /* CONFIG_PM */ + + +static char *usb_devnode(struct device *dev, + umode_t *mode, kuid_t *uid, kgid_t *gid) +{ + struct usb_device *usb_dev; + + usb_dev = to_usb_device(dev); + return kasprintf(GFP_KERNEL, "bus/usb/%03d/%03d", + usb_dev->bus->busnum, usb_dev->devnum); +} + +struct device_type usb_device_type = { + .name = "usb_device", + .release = usb_release_dev, + .uevent = usb_dev_uevent, + .devnode = usb_devnode, +#ifdef CONFIG_PM + .pm = &usb_device_pm_ops, +#endif +}; + + +/* Returns 1 if @usb_bus is WUSB, 0 otherwise */ +static unsigned usb_bus_is_wusb(struct usb_bus *bus) +{ + struct usb_hcd *hcd = container_of(bus, struct usb_hcd, self); + return hcd->wireless; +} + + +/** + * usb_alloc_dev - usb device constructor (usbcore-internal) + * @parent: hub to which device is connected; null to allocate a root hub + * @bus: bus used to access the device + * @port1: one-based index of port; ignored for root hubs + * Context: !in_interrupt() + * + * Only hub drivers (including virtual root hub drivers for host + * controllers) should ever call this. + * + * This call may not be used in a non-sleeping context. + */ +struct usb_device *usb_alloc_dev(struct usb_device *parent, + struct usb_bus *bus, unsigned port1) +{ + struct usb_device *dev; + struct usb_hcd *usb_hcd = bus_to_hcd(bus); + unsigned root_hub = 0; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + + if (!usb_get_hcd(usb_hcd)) { + kfree(dev); + return NULL; + } + /* Root hubs aren't true devices, so don't allocate HCD resources */ + if (usb_hcd->driver->alloc_dev && parent && + !usb_hcd->driver->alloc_dev(usb_hcd, dev)) { + usb_put_hcd(bus_to_hcd(bus)); + kfree(dev); + return NULL; + } + + device_initialize(&dev->dev); + dev->dev.bus = &usb_bus_type; + dev->dev.type = &usb_device_type; + dev->dev.groups = usb_device_groups; + dev->dev.dma_mask = bus->controller->dma_mask; + set_dev_node(&dev->dev, dev_to_node(bus->controller)); + dev->state = USB_STATE_ATTACHED; + dev->lpm_disable_count = 1; + atomic_set(&dev->urbnum, 0); + + INIT_LIST_HEAD(&dev->ep0.urb_list); + dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE; + dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT; + /* ep0 maxpacket comes later, from device descriptor */ + usb_enable_endpoint(dev, &dev->ep0, false); + dev->can_submit = 1; + + /* Save readable and stable topology id, distinguishing devices + * by location for diagnostics, tools, driver model, etc. The + * string is a path along hub ports, from the root. Each device's + * dev->devpath will be stable until USB is re-cabled, and hubs + * are often labeled with these port numbers. The name isn't + * as stable: bus->busnum changes easily from modprobe order, + * cardbus or pci hotplugging, and so on. + */ + if (unlikely(!parent)) { + dev->devpath[0] = '0'; + dev->route = 0; + + dev->dev.parent = bus->controller; + dev_set_name(&dev->dev, "usb%d", bus->busnum); + root_hub = 1; + } else { + /* match any labeling on the hubs; it's one-based */ + if (parent->devpath[0] == '0') { + snprintf(dev->devpath, sizeof dev->devpath, + "%d", port1); + /* Root ports are not counted in route string */ + dev->route = 0; + } else { + snprintf(dev->devpath, sizeof dev->devpath, + "%s.%d", parent->devpath, port1); + /* Route string assumes hubs have less than 16 ports */ + if (port1 < 15) + dev->route = parent->route + + (port1 << ((parent->level - 1)*4)); + else + dev->route = parent->route + + (15 << ((parent->level - 1)*4)); + } + + dev->dev.parent = &parent->dev; + dev_set_name(&dev->dev, "%d-%s", bus->busnum, dev->devpath); + + /* hub driver sets up TT records */ + } + + dev->portnum = port1; + dev->bus = bus; + dev->parent = parent; + INIT_LIST_HEAD(&dev->filelist); + +#ifdef CONFIG_PM + pm_runtime_set_autosuspend_delay(&dev->dev, + usb_autosuspend_delay * 1000); + dev->connect_time = jiffies; + dev->active_duration = -jiffies; +#endif + if (root_hub) /* Root hub always ok [and always wired] */ + dev->authorized = 1; + else { + dev->authorized = usb_hcd->authorized_default; + dev->wusb = usb_bus_is_wusb(bus)? 1 : 0; + } + return dev; +} + +/** + * usb_get_dev - increments the reference count of the usb device structure + * @dev: the device being referenced + * + * Each live reference to a device should be refcounted. + * + * Drivers for USB interfaces should normally record such references in + * their probe() methods, when they bind to an interface, and release + * them by calling usb_put_dev(), in their disconnect() methods. + * + * A pointer to the device with the incremented reference counter is returned. + */ +struct usb_device *usb_get_dev(struct usb_device *dev) +{ + if (dev) + get_device(&dev->dev); + return dev; +} +EXPORT_SYMBOL_GPL(usb_get_dev); + +/** + * usb_put_dev - release a use of the usb device structure + * @dev: device that's been disconnected + * + * Must be called when a user of a device is finished with it. When the last + * user of the device calls this function, the memory of the device is freed. + */ +void usb_put_dev(struct usb_device *dev) +{ + if (dev) + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(usb_put_dev); + +/** + * usb_get_intf - increments the reference count of the usb interface structure + * @intf: the interface being referenced + * + * Each live reference to a interface must be refcounted. + * + * Drivers for USB interfaces should normally record such references in + * their probe() methods, when they bind to an interface, and release + * them by calling usb_put_intf(), in their disconnect() methods. + * + * A pointer to the interface with the incremented reference counter is + * returned. + */ +struct usb_interface *usb_get_intf(struct usb_interface *intf) +{ + if (intf) + get_device(&intf->dev); + return intf; +} +EXPORT_SYMBOL_GPL(usb_get_intf); + +/** + * usb_put_intf - release a use of the usb interface structure + * @intf: interface that's been decremented + * + * Must be called when a user of an interface is finished with it. When the + * last user of the interface calls this function, the memory of the interface + * is freed. + */ +void usb_put_intf(struct usb_interface *intf) +{ + if (intf) + put_device(&intf->dev); +} +EXPORT_SYMBOL_GPL(usb_put_intf); + +/* USB device locking + * + * USB devices and interfaces are locked using the semaphore in their + * embedded struct device. The hub driver guarantees that whenever a + * device is connected or disconnected, drivers are called with the + * USB device locked as well as their particular interface. + * + * Complications arise when several devices are to be locked at the same + * time. Only hub-aware drivers that are part of usbcore ever have to + * do this; nobody else needs to worry about it. The rule for locking + * is simple: + * + * When locking both a device and its parent, always lock the + * the parent first. + */ + +/** + * usb_lock_device_for_reset - cautiously acquire the lock for a usb device structure + * @udev: device that's being locked + * @iface: interface bound to the driver making the request (optional) + * + * Attempts to acquire the device lock, but fails if the device is + * NOTATTACHED or SUSPENDED, or if iface is specified and the interface + * is neither BINDING nor BOUND. Rather than sleeping to wait for the + * lock, the routine polls repeatedly. This is to prevent deadlock with + * disconnect; in some drivers (such as usb-storage) the disconnect() + * or suspend() method will block waiting for a device reset to complete. + * + * Returns a negative error code for failure, otherwise 0. + */ +int usb_lock_device_for_reset(struct usb_device *udev, + const struct usb_interface *iface) +{ + unsigned long jiffies_expire = jiffies + HZ; + + if (udev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + if (udev->state == USB_STATE_SUSPENDED) + return -EHOSTUNREACH; + if (iface && (iface->condition == USB_INTERFACE_UNBINDING || + iface->condition == USB_INTERFACE_UNBOUND)) + return -EINTR; + + while (!usb_trylock_device(udev)) { + + /* If we can't acquire the lock after waiting one second, + * we're probably deadlocked */ + if (time_after(jiffies, jiffies_expire)) + return -EBUSY; + + msleep(15); + if (udev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + if (udev->state == USB_STATE_SUSPENDED) + return -EHOSTUNREACH; + if (iface && (iface->condition == USB_INTERFACE_UNBINDING || + iface->condition == USB_INTERFACE_UNBOUND)) + return -EINTR; + } + return 0; +} +EXPORT_SYMBOL_GPL(usb_lock_device_for_reset); + +/** + * usb_get_current_frame_number - return current bus frame number + * @dev: the device whose bus is being queried + * + * Returns the current frame number for the USB host controller + * used with the given USB device. This can be used when scheduling + * isochronous requests. + * + * Note that different kinds of host controller have different + * "scheduling horizons". While one type might support scheduling only + * 32 frames into the future, others could support scheduling up to + * 1024 frames into the future. + */ +int usb_get_current_frame_number(struct usb_device *dev) +{ + return usb_hcd_get_frame_number(dev); +} +EXPORT_SYMBOL_GPL(usb_get_current_frame_number); + +/*-------------------------------------------------------------------*/ +/* + * __usb_get_extra_descriptor() finds a descriptor of specific type in the + * extra field of the interface and endpoint descriptor structs. + */ + +int __usb_get_extra_descriptor(char *buffer, unsigned size, + unsigned char type, void **ptr) +{ + struct usb_descriptor_header *header; + + while (size >= sizeof(struct usb_descriptor_header)) { + header = (struct usb_descriptor_header *)buffer; + + if (header->bLength < 2) { + printk(KERN_ERR + "%s: bogus descriptor, type %d length %d\n", + usbcore_name, + header->bDescriptorType, + header->bLength); + return -1; + } + + if (header->bDescriptorType == type) { + *ptr = header; + return 0; + } + + buffer += header->bLength; + size -= header->bLength; + } + return -1; +} +EXPORT_SYMBOL_GPL(__usb_get_extra_descriptor); + +/** + * usb_alloc_coherent - allocate dma-consistent buffer for URB_NO_xxx_DMA_MAP + * @dev: device the buffer will be used with + * @size: requested buffer size + * @mem_flags: affect whether allocation may block + * @dma: used to return DMA address of buffer + * + * Return value is either null (indicating no buffer could be allocated), or + * the cpu-space pointer to a buffer that may be used to perform DMA to the + * specified device. Such cpu-space buffers are returned along with the DMA + * address (through the pointer provided). + * + * These buffers are used with URB_NO_xxx_DMA_MAP set in urb->transfer_flags + * to avoid behaviors like using "DMA bounce buffers", or thrashing IOMMU + * hardware during URB completion/resubmit. The implementation varies between + * platforms, depending on details of how DMA will work to this device. + * Using these buffers also eliminates cacheline sharing problems on + * architectures where CPU caches are not DMA-coherent. On systems without + * bus-snooping caches, these buffers are uncached. + * + * When the buffer is no longer used, free it with usb_free_coherent(). + */ +void *usb_alloc_coherent(struct usb_device *dev, size_t size, gfp_t mem_flags, + dma_addr_t *dma) +{ + if (!dev || !dev->bus) + return NULL; + return hcd_buffer_alloc(dev->bus, size, mem_flags, dma); +} +EXPORT_SYMBOL_GPL(usb_alloc_coherent); + +/** + * usb_free_coherent - free memory allocated with usb_alloc_coherent() + * @dev: device the buffer was used with + * @size: requested buffer size + * @addr: CPU address of buffer + * @dma: DMA address of buffer + * + * This reclaims an I/O buffer, letting it be reused. The memory must have + * been allocated using usb_alloc_coherent(), and the parameters must match + * those provided in that allocation request. + */ +void usb_free_coherent(struct usb_device *dev, size_t size, void *addr, + dma_addr_t dma) +{ + if (!dev || !dev->bus) + return; + if (!addr) + return; + hcd_buffer_free(dev->bus, size, addr, dma); +} +EXPORT_SYMBOL_GPL(usb_free_coherent); + +/** + * usb_buffer_map - create DMA mapping(s) for an urb + * @urb: urb whose transfer_buffer/setup_packet will be mapped + * + * Return value is either null (indicating no buffer could be mapped), or + * the parameter. URB_NO_TRANSFER_DMA_MAP is + * added to urb->transfer_flags if the operation succeeds. If the device + * is connected to this system through a non-DMA controller, this operation + * always succeeds. + * + * This call would normally be used for an urb which is reused, perhaps + * as the target of a large periodic transfer, with usb_buffer_dmasync() + * calls to synchronize memory and dma state. + * + * Reverse the effect of this call with usb_buffer_unmap(). + */ +#if 0 +struct urb *usb_buffer_map(struct urb *urb) +{ + struct usb_bus *bus; + struct device *controller; + + if (!urb + || !urb->dev + || !(bus = urb->dev->bus) + || !(controller = bus->controller)) + return NULL; + + if (controller->dma_mask) { + urb->transfer_dma = dma_map_single(controller, + urb->transfer_buffer, urb->transfer_buffer_length, + usb_pipein(urb->pipe) + ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + /* FIXME generic api broken like pci, can't report errors */ + /* if (urb->transfer_dma == DMA_ADDR_INVALID) return 0; */ + } else + urb->transfer_dma = ~0; + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + return urb; +} +EXPORT_SYMBOL_GPL(usb_buffer_map); +#endif /* 0 */ + +/* XXX DISABLED, no users currently. If you wish to re-enable this + * XXX please determine whether the sync is to transfer ownership of + * XXX the buffer from device to cpu or vice verse, and thusly use the + * XXX appropriate _for_{cpu,device}() method. -DaveM + */ +#if 0 + +/** + * usb_buffer_dmasync - synchronize DMA and CPU view of buffer(s) + * @urb: urb whose transfer_buffer/setup_packet will be synchronized + */ +void usb_buffer_dmasync(struct urb *urb) +{ + struct usb_bus *bus; + struct device *controller; + + if (!urb + || !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + || !urb->dev + || !(bus = urb->dev->bus) + || !(controller = bus->controller)) + return; + + if (controller->dma_mask) { + dma_sync_single_for_cpu(controller, + urb->transfer_dma, urb->transfer_buffer_length, + usb_pipein(urb->pipe) + ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + if (usb_pipecontrol(urb->pipe)) + dma_sync_single_for_cpu(controller, + urb->setup_dma, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + } +} +EXPORT_SYMBOL_GPL(usb_buffer_dmasync); +#endif + +/** + * usb_buffer_unmap - free DMA mapping(s) for an urb + * @urb: urb whose transfer_buffer will be unmapped + * + * Reverses the effect of usb_buffer_map(). + */ +#if 0 +void usb_buffer_unmap(struct urb *urb) +{ + struct usb_bus *bus; + struct device *controller; + + if (!urb + || !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + || !urb->dev + || !(bus = urb->dev->bus) + || !(controller = bus->controller)) + return; + + if (controller->dma_mask) { + dma_unmap_single(controller, + urb->transfer_dma, urb->transfer_buffer_length, + usb_pipein(urb->pipe) + ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + } + urb->transfer_flags &= ~URB_NO_TRANSFER_DMA_MAP; +} +EXPORT_SYMBOL_GPL(usb_buffer_unmap); +#endif /* 0 */ + +#if 0 +/** + * usb_buffer_map_sg - create scatterlist DMA mapping(s) for an endpoint + * @dev: device to which the scatterlist will be mapped + * @is_in: mapping transfer direction + * @sg: the scatterlist to map + * @nents: the number of entries in the scatterlist + * + * Return value is either < 0 (indicating no buffers could be mapped), or + * the number of DMA mapping array entries in the scatterlist. + * + * The caller is responsible for placing the resulting DMA addresses from + * the scatterlist into URB transfer buffer pointers, and for setting the + * URB_NO_TRANSFER_DMA_MAP transfer flag in each of those URBs. + * + * Top I/O rates come from queuing URBs, instead of waiting for each one + * to complete before starting the next I/O. This is particularly easy + * to do with scatterlists. Just allocate and submit one URB for each DMA + * mapping entry returned, stopping on the first error or when all succeed. + * Better yet, use the usb_sg_*() calls, which do that (and more) for you. + * + * This call would normally be used when translating scatterlist requests, + * rather than usb_buffer_map(), since on some hardware (with IOMMUs) it + * may be able to coalesce mappings for improved I/O efficiency. + * + * Reverse the effect of this call with usb_buffer_unmap_sg(). + */ +int usb_buffer_map_sg(const struct usb_device *dev, int is_in, + struct scatterlist *sg, int nents) +{ + struct usb_bus *bus; + struct device *controller; + + if (!dev + || !(bus = dev->bus) + || !(controller = bus->controller) + || !controller->dma_mask) + return -EINVAL; + + /* FIXME generic api broken like pci, can't report errors */ + return dma_map_sg(controller, sg, nents, + is_in ? DMA_FROM_DEVICE : DMA_TO_DEVICE) ? : -ENOMEM; +} +EXPORT_SYMBOL_GPL(usb_buffer_map_sg); +#endif + +/* XXX DISABLED, no users currently. If you wish to re-enable this + * XXX please determine whether the sync is to transfer ownership of + * XXX the buffer from device to cpu or vice verse, and thusly use the + * XXX appropriate _for_{cpu,device}() method. -DaveM + */ +#if 0 + +/** + * usb_buffer_dmasync_sg - synchronize DMA and CPU view of scatterlist buffer(s) + * @dev: device to which the scatterlist will be mapped + * @is_in: mapping transfer direction + * @sg: the scatterlist to synchronize + * @n_hw_ents: the positive return value from usb_buffer_map_sg + * + * Use this when you are re-using a scatterlist's data buffers for + * another USB request. + */ +void usb_buffer_dmasync_sg(const struct usb_device *dev, int is_in, + struct scatterlist *sg, int n_hw_ents) +{ + struct usb_bus *bus; + struct device *controller; + + if (!dev + || !(bus = dev->bus) + || !(controller = bus->controller) + || !controller->dma_mask) + return; + + dma_sync_sg_for_cpu(controller, sg, n_hw_ents, + is_in ? DMA_FROM_DEVICE : DMA_TO_DEVICE); +} +EXPORT_SYMBOL_GPL(usb_buffer_dmasync_sg); +#endif + +#if 0 +/** + * usb_buffer_unmap_sg - free DMA mapping(s) for a scatterlist + * @dev: device to which the scatterlist will be mapped + * @is_in: mapping transfer direction + * @sg: the scatterlist to unmap + * @n_hw_ents: the positive return value from usb_buffer_map_sg + * + * Reverses the effect of usb_buffer_map_sg(). + */ +void usb_buffer_unmap_sg(const struct usb_device *dev, int is_in, + struct scatterlist *sg, int n_hw_ents) +{ + struct usb_bus *bus; + struct device *controller; + + if (!dev + || !(bus = dev->bus) + || !(controller = bus->controller) + || !controller->dma_mask) + return; + + dma_unmap_sg(controller, sg, n_hw_ents, + is_in ? DMA_FROM_DEVICE : DMA_TO_DEVICE); +} +EXPORT_SYMBOL_GPL(usb_buffer_unmap_sg); +#endif + +/* To disable USB, kernel command line is 'nousb' not 'usbcore.nousb' */ +#ifdef MODULE +module_param(nousb, bool, 0444); +#else +core_param(nousb, nousb, bool, 0444); +#endif + +/* + * for external read access to + */ +int usb_disabled(void) +{ + return nousb; +} +EXPORT_SYMBOL_GPL(usb_disabled); + +/* + * Notifications of device and interface registration + */ +static int usb_bus_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + if (dev->type == &usb_device_type) + (void) usb_create_sysfs_dev_files(to_usb_device(dev)); + else if (dev->type == &usb_if_device_type) + usb_create_sysfs_intf_files(to_usb_interface(dev)); + break; + + case BUS_NOTIFY_DEL_DEVICE: + if (dev->type == &usb_device_type) + usb_remove_sysfs_dev_files(to_usb_device(dev)); + else if (dev->type == &usb_if_device_type) + usb_remove_sysfs_intf_files(to_usb_interface(dev)); + break; + } + return 0; +} + +static struct notifier_block usb_bus_nb = { + .notifier_call = usb_bus_notify, +}; + +struct dentry *usb_debug_root; +EXPORT_SYMBOL_GPL(usb_debug_root); + +static struct dentry *usb_debug_devices; + +static int usb_debugfs_init(void) +{ + usb_debug_root = debugfs_create_dir("usb", NULL); + if (!usb_debug_root) + return -ENOENT; + + usb_debug_devices = debugfs_create_file("devices", 0444, + usb_debug_root, NULL, + &usbfs_devices_fops); + if (!usb_debug_devices) { + debugfs_remove(usb_debug_root); + usb_debug_root = NULL; + return -ENOENT; + } + + return 0; +} + +static void usb_debugfs_cleanup(void) +{ + debugfs_remove(usb_debug_devices); + debugfs_remove(usb_debug_root); +} + +/* + * Init + */ +static int __init usb_init(void) +{ + int retval; + if (nousb) { + pr_info("%s: USB support disabled\n", usbcore_name); + return 0; + } + + retval = usb_debugfs_init(); + if (retval) + goto out; + + usb_acpi_register(); + retval = bus_register(&usb_bus_type); + if (retval) + goto bus_register_failed; + retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb); + if (retval) + goto bus_notifier_failed; + retval = usb_major_init(); + if (retval) + goto major_init_failed; + retval = usb_register(&usbfs_driver); + if (retval) + goto driver_register_failed; + retval = usb_devio_init(); + if (retval) + goto usb_devio_init_failed; + retval = usb_hub_init(); + if (retval) + goto hub_init_failed; + retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE); + if (!retval) + goto out; + + usb_hub_cleanup(); +hub_init_failed: + usb_devio_cleanup(); +usb_devio_init_failed: + usb_deregister(&usbfs_driver); +driver_register_failed: + usb_major_cleanup(); +major_init_failed: + bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); +bus_notifier_failed: + bus_unregister(&usb_bus_type); +bus_register_failed: + usb_acpi_unregister(); + usb_debugfs_cleanup(); +out: + return retval; +} + +/* + * Cleanup + */ +static void __exit usb_exit(void) +{ + /* This will matter if shutdown/reboot does exitcalls. */ + if (nousb) + return; + + usb_deregister_device_driver(&usb_generic_driver); + usb_major_cleanup(); + usb_deregister(&usbfs_driver); + usb_devio_cleanup(); + usb_hub_cleanup(); + bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); + bus_unregister(&usb_bus_type); + usb_acpi_unregister(); + usb_debugfs_cleanup(); +} + +subsys_initcall(usb_init); +module_exit(usb_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h new file mode 100644 index 00000000..0923add7 --- /dev/null +++ b/drivers/usb/core/usb.h @@ -0,0 +1,197 @@ +#include +#include + +struct usb_hub_descriptor; +struct dev_state; + +/* Functions local to drivers/usb/core/ */ + +extern int usb_create_sysfs_dev_files(struct usb_device *dev); +extern void usb_remove_sysfs_dev_files(struct usb_device *dev); +extern void usb_create_sysfs_intf_files(struct usb_interface *intf); +extern void usb_remove_sysfs_intf_files(struct usb_interface *intf); +extern int usb_create_ep_devs(struct device *parent, + struct usb_host_endpoint *endpoint, + struct usb_device *udev); +extern void usb_remove_ep_devs(struct usb_host_endpoint *endpoint); + +extern void usb_enable_endpoint(struct usb_device *dev, + struct usb_host_endpoint *ep, bool reset_toggle); +extern void usb_enable_interface(struct usb_device *dev, + struct usb_interface *intf, bool reset_toggles); +extern void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + bool reset_hardware); +extern void usb_disable_interface(struct usb_device *dev, + struct usb_interface *intf, bool reset_hardware); +extern void usb_release_interface_cache(struct kref *ref); +extern void usb_disable_device(struct usb_device *dev, int skip_ep0); +extern int usb_deauthorize_device(struct usb_device *); +extern int usb_authorize_device(struct usb_device *); +extern void usb_detect_quirks(struct usb_device *udev); +extern void usb_detect_interface_quirks(struct usb_device *udev); +extern int usb_remove_device(struct usb_device *udev); + +extern int usb_get_device_descriptor(struct usb_device *dev, + unsigned int size); +extern int usb_get_bos_descriptor(struct usb_device *dev); +extern void usb_release_bos_descriptor(struct usb_device *dev); +extern char *usb_cache_string(struct usb_device *udev, int index); +extern int usb_set_configuration(struct usb_device *dev, int configuration); +extern int usb_choose_configuration(struct usb_device *udev); + +static inline unsigned usb_get_max_power(struct usb_device *udev, + struct usb_host_config *c) +{ + /* SuperSpeed power is in 8 mA units; others are in 2 mA units */ + unsigned mul = (udev->speed == USB_SPEED_SUPER ? 8 : 2); + + return c->desc.bMaxPower * mul; +} + +extern void usb_kick_khubd(struct usb_device *dev); +extern int usb_match_one_id_intf(struct usb_device *dev, + struct usb_host_interface *intf, + const struct usb_device_id *id); +extern int usb_match_device(struct usb_device *dev, + const struct usb_device_id *id); +extern void usb_forced_unbind_intf(struct usb_interface *intf); +extern void usb_unbind_and_rebind_marked_interfaces(struct usb_device *udev); + +extern int usb_hub_claim_port(struct usb_device *hdev, unsigned port, + struct dev_state *owner); +extern int usb_hub_release_port(struct usb_device *hdev, unsigned port, + struct dev_state *owner); +extern void usb_hub_release_all_ports(struct usb_device *hdev, + struct dev_state *owner); +extern bool usb_device_is_owned(struct usb_device *udev); + +extern int usb_hub_init(void); +extern void usb_hub_cleanup(void); +extern int usb_major_init(void); +extern void usb_major_cleanup(void); + +#ifdef CONFIG_PM + +extern int usb_suspend(struct device *dev, pm_message_t msg); +extern int usb_resume(struct device *dev, pm_message_t msg); +extern int usb_resume_complete(struct device *dev); + +extern int usb_port_suspend(struct usb_device *dev, pm_message_t msg); +extern int usb_port_resume(struct usb_device *dev, pm_message_t msg); + +#else + +static inline int usb_port_suspend(struct usb_device *udev, pm_message_t msg) +{ + return 0; +} + +static inline int usb_port_resume(struct usb_device *udev, pm_message_t msg) +{ + return 0; +} + +#endif + +#ifdef CONFIG_PM_RUNTIME + +extern void usb_autosuspend_device(struct usb_device *udev); +extern int usb_autoresume_device(struct usb_device *udev); +extern int usb_remote_wakeup(struct usb_device *dev); +extern int usb_runtime_suspend(struct device *dev); +extern int usb_runtime_resume(struct device *dev); +extern int usb_runtime_idle(struct device *dev); +extern int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable); + +#else + +#define usb_autosuspend_device(udev) do {} while (0) +static inline int usb_autoresume_device(struct usb_device *udev) +{ + return 0; +} + +static inline int usb_remote_wakeup(struct usb_device *udev) +{ + return 0; +} + +static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) +{ + return 0; +} +#endif + +extern struct bus_type usb_bus_type; +extern struct device_type usb_device_type; +extern struct device_type usb_if_device_type; +extern struct device_type usb_ep_device_type; +extern struct device_type usb_port_device_type; +extern struct usb_device_driver usb_generic_driver; + +static inline int is_usb_device(const struct device *dev) +{ + return dev->type == &usb_device_type; +} + +static inline int is_usb_interface(const struct device *dev) +{ + return dev->type == &usb_if_device_type; +} + +static inline int is_usb_endpoint(const struct device *dev) +{ + return dev->type == &usb_ep_device_type; +} + +static inline int is_usb_port(const struct device *dev) +{ + return dev->type == &usb_port_device_type; +} + +/* Do the same for device drivers and interface drivers. */ + +static inline int is_usb_device_driver(struct device_driver *drv) +{ + return container_of(drv, struct usbdrv_wrap, driver)-> + for_devices; +} + +/* for labeling diagnostics */ +extern const char *usbcore_name; + +/* sysfs stuff */ +extern const struct attribute_group *usb_device_groups[]; +extern const struct attribute_group *usb_interface_groups[]; + +/* usbfs stuff */ +extern struct mutex usbfs_mutex; +extern struct usb_driver usbfs_driver; +extern const struct file_operations usbfs_devices_fops; +extern const struct file_operations usbdev_file_operations; +extern void usbfs_conn_disc_event(void); + +extern int usb_devio_init(void); +extern void usb_devio_cleanup(void); + +/* internal notify stuff */ +extern void usb_notify_add_device(struct usb_device *udev); +extern void usb_notify_remove_device(struct usb_device *udev); +extern void usb_notify_add_bus(struct usb_bus *ubus); +extern void usb_notify_remove_bus(struct usb_bus *ubus); +extern enum usb_port_connect_type + usb_get_hub_port_connect_type(struct usb_device *hdev, int port1); +extern void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1, + enum usb_port_connect_type type); +extern void usb_hub_adjust_deviceremovable(struct usb_device *hdev, + struct usb_hub_descriptor *desc); + +#ifdef CONFIG_ACPI +extern int usb_acpi_register(void); +extern void usb_acpi_unregister(void); +extern acpi_handle usb_get_hub_port_acpi_handle(struct usb_device *hdev, + int port1); +#else +static inline int usb_acpi_register(void) { return 0; }; +static inline void usb_acpi_unregister(void) { }; +#endif diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig new file mode 100644 index 00000000..757aa180 --- /dev/null +++ b/drivers/usb/dwc3/Kconfig @@ -0,0 +1,54 @@ +config USB_DWC3 + tristate "DesignWare USB3 DRD Core Support" + depends on (USB || USB_GADGET) && GENERIC_HARDIRQS + select USB_XHCI_PLATFORM if USB_SUPPORT && USB_XHCI_HCD + help + Say Y or M here if your system has a Dual Role SuperSpeed + USB controller based on the DesignWare USB3 IP Core. + + If you choose to build this driver is a dynamically linked + module, the module will be called dwc3.ko. + +if USB_DWC3 + +choice + bool "DWC3 Mode Selection" + default USB_DWC3_DUAL_ROLE if (USB && USB_GADGET) + default USB_DWC3_HOST if (USB && !USB_GADGET) + default USB_DWC3_GADGET if (!USB && USB_GADGET) + +config USB_DWC3_HOST + bool "Host only mode" + depends on USB=y || USB=USB_DWC3 + help + Select this when you want to use DWC3 in host mode only, + thereby the gadget feature will be regressed. + +config USB_DWC3_GADGET + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_DWC3 + help + Select this when you want to use DWC3 in gadget mode only, + thereby the host feature will be regressed. + +config USB_DWC3_DUAL_ROLE + bool "Dual Role mode" + depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3)) + help + This is the default mode of working of DWC3 controller where + both host and gadget features are enabled. + +endchoice + +config USB_DWC3_DEBUG + bool "Enable Debugging Messages" + help + Say Y here to enable debugging messages on DWC3 Driver. + +config USB_DWC3_VERBOSE + bool "Enable Verbose Debugging Messages" + depends on USB_DWC3_DEBUG + help + Say Y here to enable verbose debugging messages on DWC3 Driver. + +endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile new file mode 100644 index 00000000..0c7ac925 --- /dev/null +++ b/drivers/usb/dwc3/Makefile @@ -0,0 +1,41 @@ +ccflags-$(CONFIG_USB_DWC3_DEBUG) := -DDEBUG +ccflags-$(CONFIG_USB_DWC3_VERBOSE) += -DVERBOSE_DEBUG + +obj-$(CONFIG_USB_DWC3) += dwc3.o + +dwc3-y := core.o + +ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE)),) + dwc3-y += host.o +endif + +ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),) + dwc3-y += gadget.o ep0.o +endif + +ifneq ($(CONFIG_DEBUG_FS),) + dwc3-y += debugfs.o +endif + +## +# Platform-specific glue layers go here +# +# NOTICE: Make sure your glue layer doesn't depend on anything +# which is arch-specific and that it compiles on all situations. +# +# We want to keep this requirement in order to be able to compile +# the entire driver (with all its glue layers) on several architectures +# and make sure it compiles fine. This will also help with allmodconfig +# and allyesconfig builds. +# +# The only exception is the PCI glue layer, but that's only because +# PCI doesn't provide nops if CONFIG_PCI isn't enabled. +## + +obj-$(CONFIG_USB_DWC3) += dwc3-omap.o +obj-$(CONFIG_USB_DWC3) += dwc3-exynos.o + +ifneq ($(CONFIG_PCI),) + obj-$(CONFIG_USB_DWC3) += dwc3-pci.o +endif + diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c new file mode 100644 index 00000000..1d386030 --- /dev/null +++ b/drivers/usb/dwc3/core.c @@ -0,0 +1,780 @@ +/** + * core.c - DesignWare USB3 DRD Controller Core file + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "core.h" +#include "gadget.h" +#include "io.h" + +#include "debug.h" + +static char *maximum_speed = "super"; +module_param(maximum_speed, charp, 0); +MODULE_PARM_DESC(maximum_speed, "Maximum supported speed."); + +/* -------------------------------------------------------------------------- */ + +void dwc3_set_mode(struct dwc3 *dwc, u32 mode) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG)); + reg |= DWC3_GCTL_PRTCAPDIR(mode); + dwc3_writel(dwc->regs, DWC3_GCTL, reg); +} + +/** + * dwc3_core_soft_reset - Issues core soft reset and PHY reset + * @dwc: pointer to our context structure + */ +static void dwc3_core_soft_reset(struct dwc3 *dwc) +{ + u32 reg; + + /* Before Resetting PHY, put Core in Reset */ + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg |= DWC3_GCTL_CORESOFTRESET; + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + /* Assert USB3 PHY reset */ + reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); + reg |= DWC3_GUSB3PIPECTL_PHYSOFTRST; + dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); + + /* Assert USB2 PHY reset */ + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_PHYSOFTRST; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + + usb_phy_init(dwc->usb2_phy); + usb_phy_init(dwc->usb3_phy); + mdelay(100); + + /* Clear USB3 PHY reset */ + reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); + reg &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST; + dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); + + /* Clear USB2 PHY reset */ + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg &= ~DWC3_GUSB2PHYCFG_PHYSOFTRST; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + + mdelay(100); + + /* After PHYs are stable we can take Core out of reset state */ + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~DWC3_GCTL_CORESOFTRESET; + dwc3_writel(dwc->regs, DWC3_GCTL, reg); +} + +/** + * dwc3_free_one_event_buffer - Frees one event buffer + * @dwc: Pointer to our controller context structure + * @evt: Pointer to event buffer to be freed + */ +static void dwc3_free_one_event_buffer(struct dwc3 *dwc, + struct dwc3_event_buffer *evt) +{ + dma_free_coherent(dwc->dev, evt->length, evt->buf, evt->dma); +} + +/** + * dwc3_alloc_one_event_buffer - Allocates one event buffer structure + * @dwc: Pointer to our controller context structure + * @length: size of the event buffer + * + * Returns a pointer to the allocated event buffer structure on success + * otherwise ERR_PTR(errno). + */ +static struct dwc3_event_buffer *dwc3_alloc_one_event_buffer(struct dwc3 *dwc, + unsigned length) +{ + struct dwc3_event_buffer *evt; + + evt = devm_kzalloc(dwc->dev, sizeof(*evt), GFP_KERNEL); + if (!evt) + return ERR_PTR(-ENOMEM); + + evt->dwc = dwc; + evt->length = length; + evt->buf = dma_alloc_coherent(dwc->dev, length, + &evt->dma, GFP_KERNEL); + if (!evt->buf) + return ERR_PTR(-ENOMEM); + + return evt; +} + +/** + * dwc3_free_event_buffers - frees all allocated event buffers + * @dwc: Pointer to our controller context structure + */ +static void dwc3_free_event_buffers(struct dwc3 *dwc) +{ + struct dwc3_event_buffer *evt; + int i; + + for (i = 0; i < dwc->num_event_buffers; i++) { + evt = dwc->ev_buffs[i]; + if (evt) + dwc3_free_one_event_buffer(dwc, evt); + } +} + +/** + * dwc3_alloc_event_buffers - Allocates @num event buffers of size @length + * @dwc: pointer to our controller context structure + * @length: size of event buffer + * + * Returns 0 on success otherwise negative errno. In the error case, dwc + * may contain some buffers allocated but not all which were requested. + */ +static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length) +{ + int num; + int i; + + num = DWC3_NUM_INT(dwc->hwparams.hwparams1); + dwc->num_event_buffers = num; + + dwc->ev_buffs = devm_kzalloc(dwc->dev, sizeof(*dwc->ev_buffs) * num, + GFP_KERNEL); + if (!dwc->ev_buffs) { + dev_err(dwc->dev, "can't allocate event buffers array\n"); + return -ENOMEM; + } + + for (i = 0; i < num; i++) { + struct dwc3_event_buffer *evt; + + evt = dwc3_alloc_one_event_buffer(dwc, length); + if (IS_ERR(evt)) { + dev_err(dwc->dev, "can't allocate event buffer\n"); + return PTR_ERR(evt); + } + dwc->ev_buffs[i] = evt; + } + + return 0; +} + +/** + * dwc3_event_buffers_setup - setup our allocated event buffers + * @dwc: pointer to our controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_event_buffers_setup(struct dwc3 *dwc) +{ + struct dwc3_event_buffer *evt; + int n; + + for (n = 0; n < dwc->num_event_buffers; n++) { + evt = dwc->ev_buffs[n]; + dev_dbg(dwc->dev, "Event buf %p dma %08llx length %d\n", + evt->buf, (unsigned long long) evt->dma, + evt->length); + + evt->lpos = 0; + + dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(n), + lower_32_bits(evt->dma)); + dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n), + upper_32_bits(evt->dma)); + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n), + evt->length & 0xffff); + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(n), 0); + } + + return 0; +} + +static void dwc3_event_buffers_cleanup(struct dwc3 *dwc) +{ + struct dwc3_event_buffer *evt; + int n; + + for (n = 0; n < dwc->num_event_buffers; n++) { + evt = dwc->ev_buffs[n]; + + evt->lpos = 0; + + dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(n), 0); + dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n), 0); + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n), 0); + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(n), 0); + } +} + +static void dwc3_core_num_eps(struct dwc3 *dwc) +{ + struct dwc3_hwparams *parms = &dwc->hwparams; + + dwc->num_in_eps = DWC3_NUM_IN_EPS(parms); + dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps; + + dev_vdbg(dwc->dev, "found %d IN and %d OUT endpoints\n", + dwc->num_in_eps, dwc->num_out_eps); +} + +static void dwc3_cache_hwparams(struct dwc3 *dwc) +{ + struct dwc3_hwparams *parms = &dwc->hwparams; + + parms->hwparams0 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS0); + parms->hwparams1 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS1); + parms->hwparams2 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS2); + parms->hwparams3 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS3); + parms->hwparams4 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS4); + parms->hwparams5 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS5); + parms->hwparams6 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6); + parms->hwparams7 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS7); + parms->hwparams8 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS8); +} + +/** + * dwc3_core_init - Low-level initialization of DWC3 Core + * @dwc: Pointer to our controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_core_init(struct dwc3 *dwc) +{ + unsigned long timeout; + u32 reg; + int ret; + + reg = dwc3_readl(dwc->regs, DWC3_GSNPSID); + /* This should read as U3 followed by revision number */ + if ((reg & DWC3_GSNPSID_MASK) != 0x55330000) { + dev_err(dwc->dev, "this is not a DesignWare USB3 DRD Core\n"); + ret = -ENODEV; + goto err0; + } + dwc->revision = reg; + + /* issue device SoftReset too */ + timeout = jiffies + msecs_to_jiffies(500); + dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST); + do { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (!(reg & DWC3_DCTL_CSFTRST)) + break; + + if (time_after(jiffies, timeout)) { + dev_err(dwc->dev, "Reset Timed Out\n"); + ret = -ETIMEDOUT; + goto err0; + } + + cpu_relax(); + } while (true); + + dwc3_core_soft_reset(dwc); + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~DWC3_GCTL_SCALEDOWN_MASK; + reg &= ~DWC3_GCTL_DISSCRAMBLE; + + switch (DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1)) { + case DWC3_GHWPARAMS1_EN_PWROPT_CLK: + reg &= ~DWC3_GCTL_DSBLCLKGTNG; + break; + default: + dev_dbg(dwc->dev, "No power optimization available\n"); + } + + /* + * WORKAROUND: DWC3 revisions <1.90a have a bug + * where the device can fail to connect at SuperSpeed + * and falls back to high-speed mode which causes + * the device to enter a Connect/Disconnect loop + */ + if (dwc->revision < DWC3_REVISION_190A) + reg |= DWC3_GCTL_U2RSTECN; + + dwc3_core_num_eps(dwc); + + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + return 0; + +err0: + return ret; +} + +static void dwc3_core_exit(struct dwc3 *dwc) +{ + usb_phy_shutdown(dwc->usb2_phy); + usb_phy_shutdown(dwc->usb3_phy); +} + +#define DWC3_ALIGN_MASK (16 - 1) + +static int dwc3_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct resource *res; + struct dwc3 *dwc; + struct device *dev = &pdev->dev; + + int ret = -ENOMEM; + + void __iomem *regs; + void *mem; + + u8 mode; + + mem = devm_kzalloc(dev, sizeof(*dwc) + DWC3_ALIGN_MASK, GFP_KERNEL); + if (!mem) { + dev_err(dev, "not enough memory\n"); + return -ENOMEM; + } + dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1); + dwc->mem = mem; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "missing IRQ\n"); + return -ENODEV; + } + dwc->xhci_resources[1].start = res->start; + dwc->xhci_resources[1].end = res->end; + dwc->xhci_resources[1].flags = res->flags; + dwc->xhci_resources[1].name = res->name; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing memory resource\n"); + return -ENODEV; + } + dwc->xhci_resources[0].start = res->start; + dwc->xhci_resources[0].end = dwc->xhci_resources[0].start + + DWC3_XHCI_REGS_END; + dwc->xhci_resources[0].flags = res->flags; + dwc->xhci_resources[0].name = res->name; + + /* + * Request memory region but exclude xHCI regs, + * since it will be requested by the xhci-plat driver. + */ + res = devm_request_mem_region(dev, res->start + DWC3_GLOBALS_REGS_START, + resource_size(res) - DWC3_GLOBALS_REGS_START, + dev_name(dev)); + if (!res) { + dev_err(dev, "can't request mem region\n"); + return -ENOMEM; + } + + regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (!regs) { + dev_err(dev, "ioremap failed\n"); + return -ENOMEM; + } + + if (node) { + dwc->usb2_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0); + dwc->usb3_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 1); + } else { + dwc->usb2_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + dwc->usb3_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB3); + } + + if (IS_ERR(dwc->usb2_phy)) { + ret = PTR_ERR(dwc->usb2_phy); + + /* + * if -ENXIO is returned, it means PHY layer wasn't + * enabled, so it makes no sense to return -EPROBE_DEFER + * in that case, since no PHY driver will ever probe. + */ + if (ret == -ENXIO) + return ret; + + dev_err(dev, "no usb2 phy configured\n"); + return -EPROBE_DEFER; + } + + if (IS_ERR(dwc->usb3_phy)) { + ret = PTR_ERR(dwc->usb3_phy); + + /* + * if -ENXIO is returned, it means PHY layer wasn't + * enabled, so it makes no sense to return -EPROBE_DEFER + * in that case, since no PHY driver will ever probe. + */ + if (ret == -ENXIO) + return ret; + + dev_err(dev, "no usb3 phy configured\n"); + return -EPROBE_DEFER; + } + + usb_phy_set_suspend(dwc->usb2_phy, 0); + usb_phy_set_suspend(dwc->usb3_phy, 0); + + spin_lock_init(&dwc->lock); + platform_set_drvdata(pdev, dwc); + + dwc->regs = regs; + dwc->regs_size = resource_size(res); + dwc->dev = dev; + + dev->dma_mask = dev->parent->dma_mask; + dev->dma_parms = dev->parent->dma_parms; + dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask); + + if (!strncmp("super", maximum_speed, 5)) + dwc->maximum_speed = DWC3_DCFG_SUPERSPEED; + else if (!strncmp("high", maximum_speed, 4)) + dwc->maximum_speed = DWC3_DCFG_HIGHSPEED; + else if (!strncmp("full", maximum_speed, 4)) + dwc->maximum_speed = DWC3_DCFG_FULLSPEED1; + else if (!strncmp("low", maximum_speed, 3)) + dwc->maximum_speed = DWC3_DCFG_LOWSPEED; + else + dwc->maximum_speed = DWC3_DCFG_SUPERSPEED; + + dwc->needs_fifo_resize = of_property_read_bool(node, "tx-fifo-resize"); + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + pm_runtime_forbid(dev); + + dwc3_cache_hwparams(dwc); + + ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE); + if (ret) { + dev_err(dwc->dev, "failed to allocate event buffers\n"); + ret = -ENOMEM; + goto err0; + } + + ret = dwc3_core_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize core\n"); + goto err0; + } + + ret = dwc3_event_buffers_setup(dwc); + if (ret) { + dev_err(dwc->dev, "failed to setup event buffers\n"); + goto err1; + } + + if (IS_ENABLED(CONFIG_USB_DWC3_HOST)) + mode = DWC3_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET)) + mode = DWC3_MODE_DEVICE; + else + mode = DWC3_MODE_DRD; + + switch (mode) { + case DWC3_MODE_DEVICE: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + ret = dwc3_gadget_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize gadget\n"); + goto err2; + } + break; + case DWC3_MODE_HOST: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + goto err2; + } + break; + case DWC3_MODE_DRD: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + goto err2; + } + + ret = dwc3_gadget_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize gadget\n"); + goto err2; + } + break; + default: + dev_err(dev, "Unsupported mode of operation %d\n", mode); + goto err2; + } + dwc->mode = mode; + + ret = dwc3_debugfs_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize debugfs\n"); + goto err3; + } + + pm_runtime_allow(dev); + + return 0; + +err3: + switch (mode) { + case DWC3_MODE_DEVICE: + dwc3_gadget_exit(dwc); + break; + case DWC3_MODE_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_MODE_DRD: + dwc3_host_exit(dwc); + dwc3_gadget_exit(dwc); + break; + default: + /* do nothing */ + break; + } + +err2: + dwc3_event_buffers_cleanup(dwc); + +err1: + dwc3_core_exit(dwc); + +err0: + dwc3_free_event_buffers(dwc); + + return ret; +} + +static int dwc3_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + + dwc3_debugfs_exit(dwc); + + switch (dwc->mode) { + case DWC3_MODE_DEVICE: + dwc3_gadget_exit(dwc); + break; + case DWC3_MODE_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_MODE_DRD: + dwc3_host_exit(dwc); + dwc3_gadget_exit(dwc); + break; + default: + /* do nothing */ + break; + } + + dwc3_event_buffers_cleanup(dwc); + dwc3_free_event_buffers(dwc); + + usb_phy_set_suspend(dwc->usb2_phy, 1); + usb_phy_set_suspend(dwc->usb3_phy, 1); + + dwc3_core_exit(dwc); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dwc3_prepare(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + + switch (dwc->mode) { + case DWC3_MODE_DEVICE: + case DWC3_MODE_DRD: + dwc3_gadget_prepare(dwc); + /* FALLTHROUGH */ + case DWC3_MODE_HOST: + default: + dwc3_event_buffers_cleanup(dwc); + break; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static void dwc3_complete(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + + switch (dwc->mode) { + case DWC3_MODE_DEVICE: + case DWC3_MODE_DRD: + dwc3_gadget_complete(dwc); + /* FALLTHROUGH */ + case DWC3_MODE_HOST: + default: + dwc3_event_buffers_setup(dwc); + break; + } + + spin_unlock_irqrestore(&dwc->lock, flags); +} + +static int dwc3_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + + switch (dwc->mode) { + case DWC3_MODE_DEVICE: + case DWC3_MODE_DRD: + dwc3_gadget_suspend(dwc); + /* FALLTHROUGH */ + case DWC3_MODE_HOST: + default: + /* do nothing */ + break; + } + + dwc->gctl = dwc3_readl(dwc->regs, DWC3_GCTL); + spin_unlock_irqrestore(&dwc->lock, flags); + + usb_phy_shutdown(dwc->usb3_phy); + usb_phy_shutdown(dwc->usb2_phy); + + return 0; +} + +static int dwc3_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + + usb_phy_init(dwc->usb3_phy); + usb_phy_init(dwc->usb2_phy); + msleep(100); + + spin_lock_irqsave(&dwc->lock, flags); + + dwc3_writel(dwc->regs, DWC3_GCTL, dwc->gctl); + + switch (dwc->mode) { + case DWC3_MODE_DEVICE: + case DWC3_MODE_DRD: + dwc3_gadget_resume(dwc); + /* FALLTHROUGH */ + case DWC3_MODE_HOST: + default: + /* do nothing */ + break; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static const struct dev_pm_ops dwc3_dev_pm_ops = { + .prepare = dwc3_prepare, + .complete = dwc3_complete, + + SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume) +}; + +#define DWC3_PM_OPS &(dwc3_dev_pm_ops) +#else +#define DWC3_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id of_dwc3_match[] = { + { + .compatible = "synopsys,dwc3" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_match); +#endif + +static struct platform_driver dwc3_driver = { + .probe = dwc3_probe, + .remove = dwc3_remove, + .driver = { + .name = "dwc3", + .of_match_table = of_match_ptr(of_dwc3_match), + .pm = DWC3_PM_OPS, + }, +}; + +module_platform_driver(dwc3_driver); + +MODULE_ALIAS("platform:dwc3"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("DesignWare USB3 DRD Controller Driver"); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h new file mode 100644 index 00000000..7ab3c998 --- /dev/null +++ b/drivers/usb/dwc3/core.h @@ -0,0 +1,939 @@ +/** + * core.h - DesignWare USB3 DRD Core Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __DRIVERS_USB_DWC3_CORE_H +#define __DRIVERS_USB_DWC3_CORE_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Global constants */ +#define DWC3_EP0_BOUNCE_SIZE 512 +#define DWC3_ENDPOINTS_NUM 32 +#define DWC3_XHCI_RESOURCES_NUM 2 + +#define DWC3_EVENT_SIZE 4 /* bytes */ +#define DWC3_EVENT_MAX_NUM 64 /* 2 events/endpoint */ +#define DWC3_EVENT_BUFFERS_SIZE (DWC3_EVENT_SIZE * DWC3_EVENT_MAX_NUM) +#define DWC3_EVENT_TYPE_MASK 0xfe + +#define DWC3_EVENT_TYPE_DEV 0 +#define DWC3_EVENT_TYPE_CARKIT 3 +#define DWC3_EVENT_TYPE_I2C 4 + +#define DWC3_DEVICE_EVENT_DISCONNECT 0 +#define DWC3_DEVICE_EVENT_RESET 1 +#define DWC3_DEVICE_EVENT_CONNECT_DONE 2 +#define DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE 3 +#define DWC3_DEVICE_EVENT_WAKEUP 4 +#define DWC3_DEVICE_EVENT_HIBER_REQ 5 +#define DWC3_DEVICE_EVENT_EOPF 6 +#define DWC3_DEVICE_EVENT_SOF 7 +#define DWC3_DEVICE_EVENT_ERRATIC_ERROR 9 +#define DWC3_DEVICE_EVENT_CMD_CMPL 10 +#define DWC3_DEVICE_EVENT_OVERFLOW 11 + +#define DWC3_GEVNTCOUNT_MASK 0xfffc +#define DWC3_GSNPSID_MASK 0xffff0000 +#define DWC3_GSNPSREV_MASK 0xffff + +/* DWC3 registers memory space boundries */ +#define DWC3_XHCI_REGS_START 0x0 +#define DWC3_XHCI_REGS_END 0x7fff +#define DWC3_GLOBALS_REGS_START 0xc100 +#define DWC3_GLOBALS_REGS_END 0xc6ff +#define DWC3_DEVICE_REGS_START 0xc700 +#define DWC3_DEVICE_REGS_END 0xcbff +#define DWC3_OTG_REGS_START 0xcc00 +#define DWC3_OTG_REGS_END 0xccff + +/* Global Registers */ +#define DWC3_GSBUSCFG0 0xc100 +#define DWC3_GSBUSCFG1 0xc104 +#define DWC3_GTXTHRCFG 0xc108 +#define DWC3_GRXTHRCFG 0xc10c +#define DWC3_GCTL 0xc110 +#define DWC3_GEVTEN 0xc114 +#define DWC3_GSTS 0xc118 +#define DWC3_GSNPSID 0xc120 +#define DWC3_GGPIO 0xc124 +#define DWC3_GUID 0xc128 +#define DWC3_GUCTL 0xc12c +#define DWC3_GBUSERRADDR0 0xc130 +#define DWC3_GBUSERRADDR1 0xc134 +#define DWC3_GPRTBIMAP0 0xc138 +#define DWC3_GPRTBIMAP1 0xc13c +#define DWC3_GHWPARAMS0 0xc140 +#define DWC3_GHWPARAMS1 0xc144 +#define DWC3_GHWPARAMS2 0xc148 +#define DWC3_GHWPARAMS3 0xc14c +#define DWC3_GHWPARAMS4 0xc150 +#define DWC3_GHWPARAMS5 0xc154 +#define DWC3_GHWPARAMS6 0xc158 +#define DWC3_GHWPARAMS7 0xc15c +#define DWC3_GDBGFIFOSPACE 0xc160 +#define DWC3_GDBGLTSSM 0xc164 +#define DWC3_GPRTBIMAP_HS0 0xc180 +#define DWC3_GPRTBIMAP_HS1 0xc184 +#define DWC3_GPRTBIMAP_FS0 0xc188 +#define DWC3_GPRTBIMAP_FS1 0xc18c + +#define DWC3_GUSB2PHYCFG(n) (0xc200 + (n * 0x04)) +#define DWC3_GUSB2I2CCTL(n) (0xc240 + (n * 0x04)) + +#define DWC3_GUSB2PHYACC(n) (0xc280 + (n * 0x04)) + +#define DWC3_GUSB3PIPECTL(n) (0xc2c0 + (n * 0x04)) + +#define DWC3_GTXFIFOSIZ(n) (0xc300 + (n * 0x04)) +#define DWC3_GRXFIFOSIZ(n) (0xc380 + (n * 0x04)) + +#define DWC3_GEVNTADRLO(n) (0xc400 + (n * 0x10)) +#define DWC3_GEVNTADRHI(n) (0xc404 + (n * 0x10)) +#define DWC3_GEVNTSIZ(n) (0xc408 + (n * 0x10)) +#define DWC3_GEVNTCOUNT(n) (0xc40c + (n * 0x10)) + +#define DWC3_GHWPARAMS8 0xc600 + +/* Device Registers */ +#define DWC3_DCFG 0xc700 +#define DWC3_DCTL 0xc704 +#define DWC3_DEVTEN 0xc708 +#define DWC3_DSTS 0xc70c +#define DWC3_DGCMDPAR 0xc710 +#define DWC3_DGCMD 0xc714 +#define DWC3_DALEPENA 0xc720 +#define DWC3_DEPCMDPAR2(n) (0xc800 + (n * 0x10)) +#define DWC3_DEPCMDPAR1(n) (0xc804 + (n * 0x10)) +#define DWC3_DEPCMDPAR0(n) (0xc808 + (n * 0x10)) +#define DWC3_DEPCMD(n) (0xc80c + (n * 0x10)) + +/* OTG Registers */ +#define DWC3_OCFG 0xcc00 +#define DWC3_OCTL 0xcc04 +#define DWC3_OEVT 0xcc08 +#define DWC3_OEVTEN 0xcc0C +#define DWC3_OSTS 0xcc10 + +/* Bit fields */ + +/* Global Configuration Register */ +#define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) +#define DWC3_GCTL_U2RSTECN (1 << 16) +#define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6) +#define DWC3_GCTL_CLK_BUS (0) +#define DWC3_GCTL_CLK_PIPE (1) +#define DWC3_GCTL_CLK_PIPEHALF (2) +#define DWC3_GCTL_CLK_MASK (3) + +#define DWC3_GCTL_PRTCAP(n) (((n) & (3 << 12)) >> 12) +#define DWC3_GCTL_PRTCAPDIR(n) ((n) << 12) +#define DWC3_GCTL_PRTCAP_HOST 1 +#define DWC3_GCTL_PRTCAP_DEVICE 2 +#define DWC3_GCTL_PRTCAP_OTG 3 + +#define DWC3_GCTL_CORESOFTRESET (1 << 11) +#define DWC3_GCTL_SCALEDOWN(n) ((n) << 4) +#define DWC3_GCTL_SCALEDOWN_MASK DWC3_GCTL_SCALEDOWN(3) +#define DWC3_GCTL_DISSCRAMBLE (1 << 3) +#define DWC3_GCTL_GBLHIBERNATIONEN (1 << 1) +#define DWC3_GCTL_DSBLCLKGTNG (1 << 0) + +/* Global USB2 PHY Configuration Register */ +#define DWC3_GUSB2PHYCFG_PHYSOFTRST (1 << 31) +#define DWC3_GUSB2PHYCFG_SUSPHY (1 << 6) + +/* Global USB3 PIPE Control Register */ +#define DWC3_GUSB3PIPECTL_PHYSOFTRST (1 << 31) +#define DWC3_GUSB3PIPECTL_SUSPHY (1 << 17) + +/* Global TX Fifo Size Register */ +#define DWC3_GTXFIFOSIZ_TXFDEF(n) ((n) & 0xffff) +#define DWC3_GTXFIFOSIZ_TXFSTADDR(n) ((n) & 0xffff0000) + +/* Global HWPARAMS1 Register */ +#define DWC3_GHWPARAMS1_EN_PWROPT(n) (((n) & (3 << 24)) >> 24) +#define DWC3_GHWPARAMS1_EN_PWROPT_NO 0 +#define DWC3_GHWPARAMS1_EN_PWROPT_CLK 1 +#define DWC3_GHWPARAMS1_EN_PWROPT_HIB 2 +#define DWC3_GHWPARAMS1_PWROPT(n) ((n) << 24) +#define DWC3_GHWPARAMS1_PWROPT_MASK DWC3_GHWPARAMS1_PWROPT(3) + +/* Global HWPARAMS4 Register */ +#define DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(n) (((n) & (0x0f << 13)) >> 13) +#define DWC3_MAX_HIBER_SCRATCHBUFS 15 + +/* Device Configuration Register */ +#define DWC3_DCFG_LPM_CAP (1 << 22) +#define DWC3_DCFG_DEVADDR(addr) ((addr) << 3) +#define DWC3_DCFG_DEVADDR_MASK DWC3_DCFG_DEVADDR(0x7f) + +#define DWC3_DCFG_SPEED_MASK (7 << 0) +#define DWC3_DCFG_SUPERSPEED (4 << 0) +#define DWC3_DCFG_HIGHSPEED (0 << 0) +#define DWC3_DCFG_FULLSPEED2 (1 << 0) +#define DWC3_DCFG_LOWSPEED (2 << 0) +#define DWC3_DCFG_FULLSPEED1 (3 << 0) + +#define DWC3_DCFG_LPM_CAP (1 << 22) + +/* Device Control Register */ +#define DWC3_DCTL_RUN_STOP (1 << 31) +#define DWC3_DCTL_CSFTRST (1 << 30) +#define DWC3_DCTL_LSFTRST (1 << 29) + +#define DWC3_DCTL_HIRD_THRES_MASK (0x1f << 24) +#define DWC3_DCTL_HIRD_THRES(n) ((n) << 24) + +#define DWC3_DCTL_APPL1RES (1 << 23) + +/* These apply for core versions 1.87a and earlier */ +#define DWC3_DCTL_TRGTULST_MASK (0x0f << 17) +#define DWC3_DCTL_TRGTULST(n) ((n) << 17) +#define DWC3_DCTL_TRGTULST_U2 (DWC3_DCTL_TRGTULST(2)) +#define DWC3_DCTL_TRGTULST_U3 (DWC3_DCTL_TRGTULST(3)) +#define DWC3_DCTL_TRGTULST_SS_DIS (DWC3_DCTL_TRGTULST(4)) +#define DWC3_DCTL_TRGTULST_RX_DET (DWC3_DCTL_TRGTULST(5)) +#define DWC3_DCTL_TRGTULST_SS_INACT (DWC3_DCTL_TRGTULST(6)) + +/* These apply for core versions 1.94a and later */ +#define DWC3_DCTL_KEEP_CONNECT (1 << 19) +#define DWC3_DCTL_L1_HIBER_EN (1 << 18) +#define DWC3_DCTL_CRS (1 << 17) +#define DWC3_DCTL_CSS (1 << 16) + +#define DWC3_DCTL_INITU2ENA (1 << 12) +#define DWC3_DCTL_ACCEPTU2ENA (1 << 11) +#define DWC3_DCTL_INITU1ENA (1 << 10) +#define DWC3_DCTL_ACCEPTU1ENA (1 << 9) +#define DWC3_DCTL_TSTCTRL_MASK (0xf << 1) + +#define DWC3_DCTL_ULSTCHNGREQ_MASK (0x0f << 5) +#define DWC3_DCTL_ULSTCHNGREQ(n) (((n) << 5) & DWC3_DCTL_ULSTCHNGREQ_MASK) + +#define DWC3_DCTL_ULSTCHNG_NO_ACTION (DWC3_DCTL_ULSTCHNGREQ(0)) +#define DWC3_DCTL_ULSTCHNG_SS_DISABLED (DWC3_DCTL_ULSTCHNGREQ(4)) +#define DWC3_DCTL_ULSTCHNG_RX_DETECT (DWC3_DCTL_ULSTCHNGREQ(5)) +#define DWC3_DCTL_ULSTCHNG_SS_INACTIVE (DWC3_DCTL_ULSTCHNGREQ(6)) +#define DWC3_DCTL_ULSTCHNG_RECOVERY (DWC3_DCTL_ULSTCHNGREQ(8)) +#define DWC3_DCTL_ULSTCHNG_COMPLIANCE (DWC3_DCTL_ULSTCHNGREQ(10)) +#define DWC3_DCTL_ULSTCHNG_LOOPBACK (DWC3_DCTL_ULSTCHNGREQ(11)) + +/* Device Event Enable Register */ +#define DWC3_DEVTEN_VNDRDEVTSTRCVEDEN (1 << 12) +#define DWC3_DEVTEN_EVNTOVERFLOWEN (1 << 11) +#define DWC3_DEVTEN_CMDCMPLTEN (1 << 10) +#define DWC3_DEVTEN_ERRTICERREN (1 << 9) +#define DWC3_DEVTEN_SOFEN (1 << 7) +#define DWC3_DEVTEN_EOPFEN (1 << 6) +#define DWC3_DEVTEN_HIBERNATIONREQEVTEN (1 << 5) +#define DWC3_DEVTEN_WKUPEVTEN (1 << 4) +#define DWC3_DEVTEN_ULSTCNGEN (1 << 3) +#define DWC3_DEVTEN_CONNECTDONEEN (1 << 2) +#define DWC3_DEVTEN_USBRSTEN (1 << 1) +#define DWC3_DEVTEN_DISCONNEVTEN (1 << 0) + +/* Device Status Register */ +#define DWC3_DSTS_DCNRD (1 << 29) + +/* This applies for core versions 1.87a and earlier */ +#define DWC3_DSTS_PWRUPREQ (1 << 24) + +/* These apply for core versions 1.94a and later */ +#define DWC3_DSTS_RSS (1 << 25) +#define DWC3_DSTS_SSS (1 << 24) + +#define DWC3_DSTS_COREIDLE (1 << 23) +#define DWC3_DSTS_DEVCTRLHLT (1 << 22) + +#define DWC3_DSTS_USBLNKST_MASK (0x0f << 18) +#define DWC3_DSTS_USBLNKST(n) (((n) & DWC3_DSTS_USBLNKST_MASK) >> 18) + +#define DWC3_DSTS_RXFIFOEMPTY (1 << 17) + +#define DWC3_DSTS_SOFFN_MASK (0x3fff << 3) +#define DWC3_DSTS_SOFFN(n) (((n) & DWC3_DSTS_SOFFN_MASK) >> 3) + +#define DWC3_DSTS_CONNECTSPD (7 << 0) + +#define DWC3_DSTS_SUPERSPEED (4 << 0) +#define DWC3_DSTS_HIGHSPEED (0 << 0) +#define DWC3_DSTS_FULLSPEED2 (1 << 0) +#define DWC3_DSTS_LOWSPEED (2 << 0) +#define DWC3_DSTS_FULLSPEED1 (3 << 0) + +/* Device Generic Command Register */ +#define DWC3_DGCMD_SET_LMP 0x01 +#define DWC3_DGCMD_SET_PERIODIC_PAR 0x02 +#define DWC3_DGCMD_XMIT_FUNCTION 0x03 + +/* These apply for core versions 1.94a and later */ +#define DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO 0x04 +#define DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI 0x05 + +#define DWC3_DGCMD_SELECTED_FIFO_FLUSH 0x09 +#define DWC3_DGCMD_ALL_FIFO_FLUSH 0x0a +#define DWC3_DGCMD_SET_ENDPOINT_NRDY 0x0c +#define DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK 0x10 + +#define DWC3_DGCMD_STATUS(n) (((n) >> 15) & 1) +#define DWC3_DGCMD_CMDACT (1 << 10) +#define DWC3_DGCMD_CMDIOC (1 << 8) + +/* Device Generic Command Parameter Register */ +#define DWC3_DGCMDPAR_FORCE_LINKPM_ACCEPT (1 << 0) +#define DWC3_DGCMDPAR_FIFO_NUM(n) ((n) << 0) +#define DWC3_DGCMDPAR_RX_FIFO (0 << 5) +#define DWC3_DGCMDPAR_TX_FIFO (1 << 5) +#define DWC3_DGCMDPAR_LOOPBACK_DIS (0 << 0) +#define DWC3_DGCMDPAR_LOOPBACK_ENA (1 << 0) + +/* Device Endpoint Command Register */ +#define DWC3_DEPCMD_PARAM_SHIFT 16 +#define DWC3_DEPCMD_PARAM(x) ((x) << DWC3_DEPCMD_PARAM_SHIFT) +#define DWC3_DEPCMD_GET_RSC_IDX(x) (((x) >> DWC3_DEPCMD_PARAM_SHIFT) & 0x7f) +#define DWC3_DEPCMD_STATUS(x) (((x) >> 15) & 1) +#define DWC3_DEPCMD_HIPRI_FORCERM (1 << 11) +#define DWC3_DEPCMD_CMDACT (1 << 10) +#define DWC3_DEPCMD_CMDIOC (1 << 8) + +#define DWC3_DEPCMD_DEPSTARTCFG (0x09 << 0) +#define DWC3_DEPCMD_ENDTRANSFER (0x08 << 0) +#define DWC3_DEPCMD_UPDATETRANSFER (0x07 << 0) +#define DWC3_DEPCMD_STARTTRANSFER (0x06 << 0) +#define DWC3_DEPCMD_CLEARSTALL (0x05 << 0) +#define DWC3_DEPCMD_SETSTALL (0x04 << 0) +/* This applies for core versions 1.90a and earlier */ +#define DWC3_DEPCMD_GETSEQNUMBER (0x03 << 0) +/* This applies for core versions 1.94a and later */ +#define DWC3_DEPCMD_GETEPSTATE (0x03 << 0) +#define DWC3_DEPCMD_SETTRANSFRESOURCE (0x02 << 0) +#define DWC3_DEPCMD_SETEPCONFIG (0x01 << 0) + +/* The EP number goes 0..31 so ep0 is always out and ep1 is always in */ +#define DWC3_DALEPENA_EP(n) (1 << n) + +#define DWC3_DEPCMD_TYPE_CONTROL 0 +#define DWC3_DEPCMD_TYPE_ISOC 1 +#define DWC3_DEPCMD_TYPE_BULK 2 +#define DWC3_DEPCMD_TYPE_INTR 3 + +/* Structures */ + +struct dwc3_trb; + +/** + * struct dwc3_event_buffer - Software event buffer representation + * @list: a list of event buffers + * @buf: _THE_ buffer + * @length: size of this buffer + * @lpos: event offset + * @count: cache of last read event count register + * @flags: flags related to this event buffer + * @dma: dma_addr_t + * @dwc: pointer to DWC controller + */ +struct dwc3_event_buffer { + void *buf; + unsigned length; + unsigned int lpos; + unsigned int count; + unsigned int flags; + +#define DWC3_EVENT_PENDING BIT(0) + + dma_addr_t dma; + + struct dwc3 *dwc; +}; + +#define DWC3_EP_FLAG_STALLED (1 << 0) +#define DWC3_EP_FLAG_WEDGED (1 << 1) + +#define DWC3_EP_DIRECTION_TX true +#define DWC3_EP_DIRECTION_RX false + +#define DWC3_TRB_NUM 32 +#define DWC3_TRB_MASK (DWC3_TRB_NUM - 1) + +/** + * struct dwc3_ep - device side endpoint representation + * @endpoint: usb endpoint + * @request_list: list of requests for this endpoint + * @req_queued: list of requests on this ep which have TRBs setup + * @trb_pool: array of transaction buffers + * @trb_pool_dma: dma address of @trb_pool + * @free_slot: next slot which is going to be used + * @busy_slot: first slot which is owned by HW + * @desc: usb_endpoint_descriptor pointer + * @dwc: pointer to DWC controller + * @flags: endpoint flags (wedged, stalled, ...) + * @current_trb: index of current used trb + * @number: endpoint number (1 - 15) + * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK + * @resource_index: Resource transfer index + * @interval: the intervall on which the ISOC transfer is started + * @name: a human readable name e.g. ep1out-bulk + * @direction: true for TX, false for RX + * @stream_capable: true when streams are enabled + */ +struct dwc3_ep { + struct usb_ep endpoint; + struct list_head request_list; + struct list_head req_queued; + + struct dwc3_trb *trb_pool; + dma_addr_t trb_pool_dma; + u32 free_slot; + u32 busy_slot; + const struct usb_ss_ep_comp_descriptor *comp_desc; + struct dwc3 *dwc; + + unsigned flags; +#define DWC3_EP_ENABLED (1 << 0) +#define DWC3_EP_STALL (1 << 1) +#define DWC3_EP_WEDGE (1 << 2) +#define DWC3_EP_BUSY (1 << 4) +#define DWC3_EP_PENDING_REQUEST (1 << 5) +#define DWC3_EP_MISSED_ISOC (1 << 6) + + /* This last one is specific to EP0 */ +#define DWC3_EP0_DIR_IN (1 << 31) + + unsigned current_trb; + + u8 number; + u8 type; + u8 resource_index; + u32 interval; + + char name[20]; + + unsigned direction:1; + unsigned stream_capable:1; +}; + +enum dwc3_phy { + DWC3_PHY_UNKNOWN = 0, + DWC3_PHY_USB3, + DWC3_PHY_USB2, +}; + +enum dwc3_ep0_next { + DWC3_EP0_UNKNOWN = 0, + DWC3_EP0_COMPLETE, + DWC3_EP0_NRDY_DATA, + DWC3_EP0_NRDY_STATUS, +}; + +enum dwc3_ep0_state { + EP0_UNCONNECTED = 0, + EP0_SETUP_PHASE, + EP0_DATA_PHASE, + EP0_STATUS_PHASE, +}; + +enum dwc3_link_state { + /* In SuperSpeed */ + DWC3_LINK_STATE_U0 = 0x00, /* in HS, means ON */ + DWC3_LINK_STATE_U1 = 0x01, + DWC3_LINK_STATE_U2 = 0x02, /* in HS, means SLEEP */ + DWC3_LINK_STATE_U3 = 0x03, /* in HS, means SUSPEND */ + DWC3_LINK_STATE_SS_DIS = 0x04, + DWC3_LINK_STATE_RX_DET = 0x05, /* in HS, means Early Suspend */ + DWC3_LINK_STATE_SS_INACT = 0x06, + DWC3_LINK_STATE_POLL = 0x07, + DWC3_LINK_STATE_RECOV = 0x08, + DWC3_LINK_STATE_HRESET = 0x09, + DWC3_LINK_STATE_CMPLY = 0x0a, + DWC3_LINK_STATE_LPBK = 0x0b, + DWC3_LINK_STATE_RESET = 0x0e, + DWC3_LINK_STATE_RESUME = 0x0f, + DWC3_LINK_STATE_MASK = 0x0f, +}; + +/* TRB Length, PCM and Status */ +#define DWC3_TRB_SIZE_MASK (0x00ffffff) +#define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) +#define DWC3_TRB_SIZE_PCM1(n) (((n) & 0x03) << 24) +#define DWC3_TRB_SIZE_TRBSTS(n) (((n) & (0x0f << 28)) >> 28) + +#define DWC3_TRBSTS_OK 0 +#define DWC3_TRBSTS_MISSED_ISOC 1 +#define DWC3_TRBSTS_SETUP_PENDING 2 +#define DWC3_TRB_STS_XFER_IN_PROG 4 + +/* TRB Control */ +#define DWC3_TRB_CTRL_HWO (1 << 0) +#define DWC3_TRB_CTRL_LST (1 << 1) +#define DWC3_TRB_CTRL_CHN (1 << 2) +#define DWC3_TRB_CTRL_CSP (1 << 3) +#define DWC3_TRB_CTRL_TRBCTL(n) (((n) & 0x3f) << 4) +#define DWC3_TRB_CTRL_ISP_IMI (1 << 10) +#define DWC3_TRB_CTRL_IOC (1 << 11) +#define DWC3_TRB_CTRL_SID_SOFN(n) (((n) & 0xffff) << 14) + +#define DWC3_TRBCTL_NORMAL DWC3_TRB_CTRL_TRBCTL(1) +#define DWC3_TRBCTL_CONTROL_SETUP DWC3_TRB_CTRL_TRBCTL(2) +#define DWC3_TRBCTL_CONTROL_STATUS2 DWC3_TRB_CTRL_TRBCTL(3) +#define DWC3_TRBCTL_CONTROL_STATUS3 DWC3_TRB_CTRL_TRBCTL(4) +#define DWC3_TRBCTL_CONTROL_DATA DWC3_TRB_CTRL_TRBCTL(5) +#define DWC3_TRBCTL_ISOCHRONOUS_FIRST DWC3_TRB_CTRL_TRBCTL(6) +#define DWC3_TRBCTL_ISOCHRONOUS DWC3_TRB_CTRL_TRBCTL(7) +#define DWC3_TRBCTL_LINK_TRB DWC3_TRB_CTRL_TRBCTL(8) + +/** + * struct dwc3_trb - transfer request block (hw format) + * @bpl: DW0-3 + * @bph: DW4-7 + * @size: DW8-B + * @trl: DWC-F + */ +struct dwc3_trb { + u32 bpl; + u32 bph; + u32 size; + u32 ctrl; +} __packed; + +/** + * dwc3_hwparams - copy of HWPARAMS registers + * @hwparams0 - GHWPARAMS0 + * @hwparams1 - GHWPARAMS1 + * @hwparams2 - GHWPARAMS2 + * @hwparams3 - GHWPARAMS3 + * @hwparams4 - GHWPARAMS4 + * @hwparams5 - GHWPARAMS5 + * @hwparams6 - GHWPARAMS6 + * @hwparams7 - GHWPARAMS7 + * @hwparams8 - GHWPARAMS8 + */ +struct dwc3_hwparams { + u32 hwparams0; + u32 hwparams1; + u32 hwparams2; + u32 hwparams3; + u32 hwparams4; + u32 hwparams5; + u32 hwparams6; + u32 hwparams7; + u32 hwparams8; +}; + +/* HWPARAMS0 */ +#define DWC3_MODE(n) ((n) & 0x7) + +#define DWC3_MODE_DEVICE 0 +#define DWC3_MODE_HOST 1 +#define DWC3_MODE_DRD 2 +#define DWC3_MODE_HUB 3 + +#define DWC3_MDWIDTH(n) (((n) & 0xff00) >> 8) + +/* HWPARAMS1 */ +#define DWC3_NUM_INT(n) (((n) & (0x3f << 15)) >> 15) + +/* HWPARAMS3 */ +#define DWC3_NUM_IN_EPS_MASK (0x1f << 18) +#define DWC3_NUM_EPS_MASK (0x3f << 12) +#define DWC3_NUM_EPS(p) (((p)->hwparams3 & \ + (DWC3_NUM_EPS_MASK)) >> 12) +#define DWC3_NUM_IN_EPS(p) (((p)->hwparams3 & \ + (DWC3_NUM_IN_EPS_MASK)) >> 18) + +/* HWPARAMS7 */ +#define DWC3_RAM1_DEPTH(n) ((n) & 0xffff) + +struct dwc3_request { + struct usb_request request; + struct list_head list; + struct dwc3_ep *dep; + u32 start_slot; + + u8 epnum; + struct dwc3_trb *trb; + dma_addr_t trb_dma; + + unsigned direction:1; + unsigned mapped:1; + unsigned queued:1; +}; + +/* + * struct dwc3_scratchpad_array - hibernation scratchpad array + * (format defined by hw) + */ +struct dwc3_scratchpad_array { + __le64 dma_adr[DWC3_MAX_HIBER_SCRATCHBUFS]; +}; + +/** + * struct dwc3 - representation of our controller + * @ctrl_req: usb control request which is used for ep0 + * @ep0_trb: trb which is used for the ctrl_req + * @ep0_bounce: bounce buffer for ep0 + * @setup_buf: used while precessing STD USB requests + * @ctrl_req_addr: dma address of ctrl_req + * @ep0_trb: dma address of ep0_trb + * @ep0_usb_req: dummy req used while handling STD USB requests + * @ep0_bounce_addr: dma address of ep0_bounce + * @lock: for synchronizing + * @dev: pointer to our struct device + * @xhci: pointer to our xHCI child + * @event_buffer_list: a list of event buffers + * @gadget: device side representation of the peripheral controller + * @gadget_driver: pointer to the gadget driver + * @regs: base address for our registers + * @regs_size: address space size + * @num_event_buffers: calculated number of event buffers + * @u1u2: only used on revisions <1.83a for workaround + * @maximum_speed: maximum speed requested (mainly for testing purposes) + * @revision: revision register contents + * @mode: mode of operation + * @usb2_phy: pointer to USB2 PHY + * @usb3_phy: pointer to USB3 PHY + * @dcfg: saved contents of DCFG register + * @gctl: saved contents of GCTL register + * @is_selfpowered: true when we are selfpowered + * @three_stage_setup: set if we perform a three phase setup + * @ep0_bounced: true when we used bounce buffer + * @ep0_expect_in: true when we expect a DATA IN transfer + * @start_config_issued: true when StartConfig command has been issued + * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround + * @needs_fifo_resize: not all users might want fifo resizing, flag it + * @resize_fifos: tells us it's ok to reconfigure our TxFIFO sizes. + * @isoch_delay: wValue from Set Isochronous Delay request; + * @u2sel: parameter from Set SEL request. + * @u2pel: parameter from Set SEL request. + * @u1sel: parameter from Set SEL request. + * @u1pel: parameter from Set SEL request. + * @num_out_eps: number of out endpoints + * @num_in_eps: number of in endpoints + * @ep0_next_event: hold the next expected event + * @ep0state: state of endpoint zero + * @link_state: link state + * @speed: device speed (super, high, full, low) + * @mem: points to start of memory which is used for this struct. + * @hwparams: copy of hwparams registers + * @root: debugfs root folder pointer + */ +struct dwc3 { + struct usb_ctrlrequest *ctrl_req; + struct dwc3_trb *ep0_trb; + void *ep0_bounce; + u8 *setup_buf; + dma_addr_t ctrl_req_addr; + dma_addr_t ep0_trb_addr; + dma_addr_t ep0_bounce_addr; + struct dwc3_request ep0_usb_req; + + /* device lock */ + spinlock_t lock; + + struct device *dev; + + struct platform_device *xhci; + struct resource xhci_resources[DWC3_XHCI_RESOURCES_NUM]; + + struct dwc3_event_buffer **ev_buffs; + struct dwc3_ep *eps[DWC3_ENDPOINTS_NUM]; + + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + + struct usb_phy *usb2_phy; + struct usb_phy *usb3_phy; + + void __iomem *regs; + size_t regs_size; + + /* used for suspend/resume */ + u32 dcfg; + u32 gctl; + + u32 num_event_buffers; + u32 u1u2; + u32 maximum_speed; + u32 revision; + u32 mode; + +#define DWC3_REVISION_173A 0x5533173a +#define DWC3_REVISION_175A 0x5533175a +#define DWC3_REVISION_180A 0x5533180a +#define DWC3_REVISION_183A 0x5533183a +#define DWC3_REVISION_185A 0x5533185a +#define DWC3_REVISION_187A 0x5533187a +#define DWC3_REVISION_188A 0x5533188a +#define DWC3_REVISION_190A 0x5533190a +#define DWC3_REVISION_194A 0x5533194a +#define DWC3_REVISION_200A 0x5533200a +#define DWC3_REVISION_202A 0x5533202a +#define DWC3_REVISION_210A 0x5533210a +#define DWC3_REVISION_220A 0x5533220a +#define DWC3_REVISION_230A 0x5533230a +#define DWC3_REVISION_240A 0x5533240a +#define DWC3_REVISION_250A 0x5533250a + + unsigned is_selfpowered:1; + unsigned three_stage_setup:1; + unsigned ep0_bounced:1; + unsigned ep0_expect_in:1; + unsigned start_config_issued:1; + unsigned setup_packet_pending:1; + unsigned delayed_status:1; + unsigned needs_fifo_resize:1; + unsigned resize_fifos:1; + unsigned pullups_connected:1; + + enum dwc3_ep0_next ep0_next_event; + enum dwc3_ep0_state ep0state; + enum dwc3_link_state link_state; + + u16 isoch_delay; + u16 u2sel; + u16 u2pel; + u8 u1sel; + u8 u1pel; + + u8 speed; + + u8 num_out_eps; + u8 num_in_eps; + + void *mem; + + struct dwc3_hwparams hwparams; + struct dentry *root; + struct debugfs_regset32 *regset; + + u8 test_mode; + u8 test_mode_nr; +}; + +/* -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ + +struct dwc3_event_type { + u32 is_devspec:1; + u32 type:7; + u32 reserved8_31:24; +} __packed; + +#define DWC3_DEPEVT_XFERCOMPLETE 0x01 +#define DWC3_DEPEVT_XFERINPROGRESS 0x02 +#define DWC3_DEPEVT_XFERNOTREADY 0x03 +#define DWC3_DEPEVT_RXTXFIFOEVT 0x04 +#define DWC3_DEPEVT_STREAMEVT 0x06 +#define DWC3_DEPEVT_EPCMDCMPLT 0x07 + +/** + * struct dwc3_event_depvt - Device Endpoint Events + * @one_bit: indicates this is an endpoint event (not used) + * @endpoint_number: number of the endpoint + * @endpoint_event: The event we have: + * 0x00 - Reserved + * 0x01 - XferComplete + * 0x02 - XferInProgress + * 0x03 - XferNotReady + * 0x04 - RxTxFifoEvt (IN->Underrun, OUT->Overrun) + * 0x05 - Reserved + * 0x06 - StreamEvt + * 0x07 - EPCmdCmplt + * @reserved11_10: Reserved, don't use. + * @status: Indicates the status of the event. Refer to databook for + * more information. + * @parameters: Parameters of the current event. Refer to databook for + * more information. + */ +struct dwc3_event_depevt { + u32 one_bit:1; + u32 endpoint_number:5; + u32 endpoint_event:4; + u32 reserved11_10:2; + u32 status:4; + +/* Within XferNotReady */ +#define DEPEVT_STATUS_TRANSFER_ACTIVE (1 << 3) + +/* Within XferComplete */ +#define DEPEVT_STATUS_BUSERR (1 << 0) +#define DEPEVT_STATUS_SHORT (1 << 1) +#define DEPEVT_STATUS_IOC (1 << 2) +#define DEPEVT_STATUS_LST (1 << 3) + +/* Stream event only */ +#define DEPEVT_STREAMEVT_FOUND 1 +#define DEPEVT_STREAMEVT_NOTFOUND 2 + +/* Control-only Status */ +#define DEPEVT_STATUS_CONTROL_DATA 1 +#define DEPEVT_STATUS_CONTROL_STATUS 2 + + u32 parameters:16; +} __packed; + +/** + * struct dwc3_event_devt - Device Events + * @one_bit: indicates this is a non-endpoint event (not used) + * @device_event: indicates it's a device event. Should read as 0x00 + * @type: indicates the type of device event. + * 0 - DisconnEvt + * 1 - USBRst + * 2 - ConnectDone + * 3 - ULStChng + * 4 - WkUpEvt + * 5 - Reserved + * 6 - EOPF + * 7 - SOF + * 8 - Reserved + * 9 - ErrticErr + * 10 - CmdCmplt + * 11 - EvntOverflow + * 12 - VndrDevTstRcved + * @reserved15_12: Reserved, not used + * @event_info: Information about this event + * @reserved31_25: Reserved, not used + */ +struct dwc3_event_devt { + u32 one_bit:1; + u32 device_event:7; + u32 type:4; + u32 reserved15_12:4; + u32 event_info:9; + u32 reserved31_25:7; +} __packed; + +/** + * struct dwc3_event_gevt - Other Core Events + * @one_bit: indicates this is a non-endpoint event (not used) + * @device_event: indicates it's (0x03) Carkit or (0x04) I2C event. + * @phy_port_number: self-explanatory + * @reserved31_12: Reserved, not used. + */ +struct dwc3_event_gevt { + u32 one_bit:1; + u32 device_event:7; + u32 phy_port_number:4; + u32 reserved31_12:20; +} __packed; + +/** + * union dwc3_event - representation of Event Buffer contents + * @raw: raw 32-bit event + * @type: the type of the event + * @depevt: Device Endpoint Event + * @devt: Device Event + * @gevt: Global Event + */ +union dwc3_event { + u32 raw; + struct dwc3_event_type type; + struct dwc3_event_depevt depevt; + struct dwc3_event_devt devt; + struct dwc3_event_gevt gevt; +}; + +/* + * DWC3 Features to be used as Driver Data + */ + +#define DWC3_HAS_PERIPHERAL BIT(0) +#define DWC3_HAS_XHCI BIT(1) +#define DWC3_HAS_OTG BIT(3) + +/* prototypes */ +void dwc3_set_mode(struct dwc3 *dwc, u32 mode); +int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc); + +#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_host_init(struct dwc3 *dwc); +void dwc3_host_exit(struct dwc3 *dwc); +#else +static inline int dwc3_host_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_host_exit(struct dwc3 *dwc) +{ } +#endif + +#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_gadget_init(struct dwc3 *dwc); +void dwc3_gadget_exit(struct dwc3 *dwc); +#else +static inline int dwc3_gadget_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_gadget_exit(struct dwc3 *dwc) +{ } +#endif + +/* power management interface */ +#if !IS_ENABLED(CONFIG_USB_DWC3_HOST) +int dwc3_gadget_prepare(struct dwc3 *dwc); +void dwc3_gadget_complete(struct dwc3 *dwc); +int dwc3_gadget_suspend(struct dwc3 *dwc); +int dwc3_gadget_resume(struct dwc3 *dwc); +#else +static inline int dwc3_gadget_prepare(struct dwc3 *dwc) +{ + return 0; +} + +static inline void dwc3_gadget_complete(struct dwc3 *dwc) +{ +} + +static inline int dwc3_gadget_suspend(struct dwc3 *dwc) +{ + return 0; +} + +static inline int dwc3_gadget_resume(struct dwc3 *dwc) +{ + return 0; +} +#endif /* !IS_ENABLED(CONFIG_USB_DWC3_HOST) */ + +#endif /* __DRIVERS_USB_DWC3_CORE_H */ diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h new file mode 100644 index 00000000..5894ee82 --- /dev/null +++ b/drivers/usb/dwc3/debug.h @@ -0,0 +1,50 @@ +/** + * debug.h - DesignWare USB3 DRD Controller Debug Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "core.h" + +#ifdef CONFIG_DEBUG_FS +extern int dwc3_debugfs_init(struct dwc3 *); +extern void dwc3_debugfs_exit(struct dwc3 *); +#else +static inline int dwc3_debugfs_init(struct dwc3 *d) +{ return 0; } +static inline void dwc3_debugfs_exit(struct dwc3 *d) +{ } +#endif + diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c new file mode 100644 index 00000000..9e9f1221 --- /dev/null +++ b/drivers/usb/dwc3/debugfs.c @@ -0,0 +1,710 @@ +/** + * debugfs.c - DesignWare USB3 DRD Controller DebugFS file + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core.h" +#include "gadget.h" +#include "io.h" +#include "debug.h" + +#define dump_register(nm) \ +{ \ + .name = __stringify(nm), \ + .offset = DWC3_ ##nm - DWC3_GLOBALS_REGS_START, \ +} + +static const struct debugfs_reg32 dwc3_regs[] = { + dump_register(GSBUSCFG0), + dump_register(GSBUSCFG1), + dump_register(GTXTHRCFG), + dump_register(GRXTHRCFG), + dump_register(GCTL), + dump_register(GEVTEN), + dump_register(GSTS), + dump_register(GSNPSID), + dump_register(GGPIO), + dump_register(GUID), + dump_register(GUCTL), + dump_register(GBUSERRADDR0), + dump_register(GBUSERRADDR1), + dump_register(GPRTBIMAP0), + dump_register(GPRTBIMAP1), + dump_register(GHWPARAMS0), + dump_register(GHWPARAMS1), + dump_register(GHWPARAMS2), + dump_register(GHWPARAMS3), + dump_register(GHWPARAMS4), + dump_register(GHWPARAMS5), + dump_register(GHWPARAMS6), + dump_register(GHWPARAMS7), + dump_register(GDBGFIFOSPACE), + dump_register(GDBGLTSSM), + dump_register(GPRTBIMAP_HS0), + dump_register(GPRTBIMAP_HS1), + dump_register(GPRTBIMAP_FS0), + dump_register(GPRTBIMAP_FS1), + + dump_register(GUSB2PHYCFG(0)), + dump_register(GUSB2PHYCFG(1)), + dump_register(GUSB2PHYCFG(2)), + dump_register(GUSB2PHYCFG(3)), + dump_register(GUSB2PHYCFG(4)), + dump_register(GUSB2PHYCFG(5)), + dump_register(GUSB2PHYCFG(6)), + dump_register(GUSB2PHYCFG(7)), + dump_register(GUSB2PHYCFG(8)), + dump_register(GUSB2PHYCFG(9)), + dump_register(GUSB2PHYCFG(10)), + dump_register(GUSB2PHYCFG(11)), + dump_register(GUSB2PHYCFG(12)), + dump_register(GUSB2PHYCFG(13)), + dump_register(GUSB2PHYCFG(14)), + dump_register(GUSB2PHYCFG(15)), + + dump_register(GUSB2I2CCTL(0)), + dump_register(GUSB2I2CCTL(1)), + dump_register(GUSB2I2CCTL(2)), + dump_register(GUSB2I2CCTL(3)), + dump_register(GUSB2I2CCTL(4)), + dump_register(GUSB2I2CCTL(5)), + dump_register(GUSB2I2CCTL(6)), + dump_register(GUSB2I2CCTL(7)), + dump_register(GUSB2I2CCTL(8)), + dump_register(GUSB2I2CCTL(9)), + dump_register(GUSB2I2CCTL(10)), + dump_register(GUSB2I2CCTL(11)), + dump_register(GUSB2I2CCTL(12)), + dump_register(GUSB2I2CCTL(13)), + dump_register(GUSB2I2CCTL(14)), + dump_register(GUSB2I2CCTL(15)), + + dump_register(GUSB2PHYACC(0)), + dump_register(GUSB2PHYACC(1)), + dump_register(GUSB2PHYACC(2)), + dump_register(GUSB2PHYACC(3)), + dump_register(GUSB2PHYACC(4)), + dump_register(GUSB2PHYACC(5)), + dump_register(GUSB2PHYACC(6)), + dump_register(GUSB2PHYACC(7)), + dump_register(GUSB2PHYACC(8)), + dump_register(GUSB2PHYACC(9)), + dump_register(GUSB2PHYACC(10)), + dump_register(GUSB2PHYACC(11)), + dump_register(GUSB2PHYACC(12)), + dump_register(GUSB2PHYACC(13)), + dump_register(GUSB2PHYACC(14)), + dump_register(GUSB2PHYACC(15)), + + dump_register(GUSB3PIPECTL(0)), + dump_register(GUSB3PIPECTL(1)), + dump_register(GUSB3PIPECTL(2)), + dump_register(GUSB3PIPECTL(3)), + dump_register(GUSB3PIPECTL(4)), + dump_register(GUSB3PIPECTL(5)), + dump_register(GUSB3PIPECTL(6)), + dump_register(GUSB3PIPECTL(7)), + dump_register(GUSB3PIPECTL(8)), + dump_register(GUSB3PIPECTL(9)), + dump_register(GUSB3PIPECTL(10)), + dump_register(GUSB3PIPECTL(11)), + dump_register(GUSB3PIPECTL(12)), + dump_register(GUSB3PIPECTL(13)), + dump_register(GUSB3PIPECTL(14)), + dump_register(GUSB3PIPECTL(15)), + + dump_register(GTXFIFOSIZ(0)), + dump_register(GTXFIFOSIZ(1)), + dump_register(GTXFIFOSIZ(2)), + dump_register(GTXFIFOSIZ(3)), + dump_register(GTXFIFOSIZ(4)), + dump_register(GTXFIFOSIZ(5)), + dump_register(GTXFIFOSIZ(6)), + dump_register(GTXFIFOSIZ(7)), + dump_register(GTXFIFOSIZ(8)), + dump_register(GTXFIFOSIZ(9)), + dump_register(GTXFIFOSIZ(10)), + dump_register(GTXFIFOSIZ(11)), + dump_register(GTXFIFOSIZ(12)), + dump_register(GTXFIFOSIZ(13)), + dump_register(GTXFIFOSIZ(14)), + dump_register(GTXFIFOSIZ(15)), + dump_register(GTXFIFOSIZ(16)), + dump_register(GTXFIFOSIZ(17)), + dump_register(GTXFIFOSIZ(18)), + dump_register(GTXFIFOSIZ(19)), + dump_register(GTXFIFOSIZ(20)), + dump_register(GTXFIFOSIZ(21)), + dump_register(GTXFIFOSIZ(22)), + dump_register(GTXFIFOSIZ(23)), + dump_register(GTXFIFOSIZ(24)), + dump_register(GTXFIFOSIZ(25)), + dump_register(GTXFIFOSIZ(26)), + dump_register(GTXFIFOSIZ(27)), + dump_register(GTXFIFOSIZ(28)), + dump_register(GTXFIFOSIZ(29)), + dump_register(GTXFIFOSIZ(30)), + dump_register(GTXFIFOSIZ(31)), + + dump_register(GRXFIFOSIZ(0)), + dump_register(GRXFIFOSIZ(1)), + dump_register(GRXFIFOSIZ(2)), + dump_register(GRXFIFOSIZ(3)), + dump_register(GRXFIFOSIZ(4)), + dump_register(GRXFIFOSIZ(5)), + dump_register(GRXFIFOSIZ(6)), + dump_register(GRXFIFOSIZ(7)), + dump_register(GRXFIFOSIZ(8)), + dump_register(GRXFIFOSIZ(9)), + dump_register(GRXFIFOSIZ(10)), + dump_register(GRXFIFOSIZ(11)), + dump_register(GRXFIFOSIZ(12)), + dump_register(GRXFIFOSIZ(13)), + dump_register(GRXFIFOSIZ(14)), + dump_register(GRXFIFOSIZ(15)), + dump_register(GRXFIFOSIZ(16)), + dump_register(GRXFIFOSIZ(17)), + dump_register(GRXFIFOSIZ(18)), + dump_register(GRXFIFOSIZ(19)), + dump_register(GRXFIFOSIZ(20)), + dump_register(GRXFIFOSIZ(21)), + dump_register(GRXFIFOSIZ(22)), + dump_register(GRXFIFOSIZ(23)), + dump_register(GRXFIFOSIZ(24)), + dump_register(GRXFIFOSIZ(25)), + dump_register(GRXFIFOSIZ(26)), + dump_register(GRXFIFOSIZ(27)), + dump_register(GRXFIFOSIZ(28)), + dump_register(GRXFIFOSIZ(29)), + dump_register(GRXFIFOSIZ(30)), + dump_register(GRXFIFOSIZ(31)), + + dump_register(GEVNTADRLO(0)), + dump_register(GEVNTADRHI(0)), + dump_register(GEVNTSIZ(0)), + dump_register(GEVNTCOUNT(0)), + + dump_register(GHWPARAMS8), + dump_register(DCFG), + dump_register(DCTL), + dump_register(DEVTEN), + dump_register(DSTS), + dump_register(DGCMDPAR), + dump_register(DGCMD), + dump_register(DALEPENA), + + dump_register(DEPCMDPAR2(0)), + dump_register(DEPCMDPAR2(1)), + dump_register(DEPCMDPAR2(2)), + dump_register(DEPCMDPAR2(3)), + dump_register(DEPCMDPAR2(4)), + dump_register(DEPCMDPAR2(5)), + dump_register(DEPCMDPAR2(6)), + dump_register(DEPCMDPAR2(7)), + dump_register(DEPCMDPAR2(8)), + dump_register(DEPCMDPAR2(9)), + dump_register(DEPCMDPAR2(10)), + dump_register(DEPCMDPAR2(11)), + dump_register(DEPCMDPAR2(12)), + dump_register(DEPCMDPAR2(13)), + dump_register(DEPCMDPAR2(14)), + dump_register(DEPCMDPAR2(15)), + dump_register(DEPCMDPAR2(16)), + dump_register(DEPCMDPAR2(17)), + dump_register(DEPCMDPAR2(18)), + dump_register(DEPCMDPAR2(19)), + dump_register(DEPCMDPAR2(20)), + dump_register(DEPCMDPAR2(21)), + dump_register(DEPCMDPAR2(22)), + dump_register(DEPCMDPAR2(23)), + dump_register(DEPCMDPAR2(24)), + dump_register(DEPCMDPAR2(25)), + dump_register(DEPCMDPAR2(26)), + dump_register(DEPCMDPAR2(27)), + dump_register(DEPCMDPAR2(28)), + dump_register(DEPCMDPAR2(29)), + dump_register(DEPCMDPAR2(30)), + dump_register(DEPCMDPAR2(31)), + + dump_register(DEPCMDPAR1(0)), + dump_register(DEPCMDPAR1(1)), + dump_register(DEPCMDPAR1(2)), + dump_register(DEPCMDPAR1(3)), + dump_register(DEPCMDPAR1(4)), + dump_register(DEPCMDPAR1(5)), + dump_register(DEPCMDPAR1(6)), + dump_register(DEPCMDPAR1(7)), + dump_register(DEPCMDPAR1(8)), + dump_register(DEPCMDPAR1(9)), + dump_register(DEPCMDPAR1(10)), + dump_register(DEPCMDPAR1(11)), + dump_register(DEPCMDPAR1(12)), + dump_register(DEPCMDPAR1(13)), + dump_register(DEPCMDPAR1(14)), + dump_register(DEPCMDPAR1(15)), + dump_register(DEPCMDPAR1(16)), + dump_register(DEPCMDPAR1(17)), + dump_register(DEPCMDPAR1(18)), + dump_register(DEPCMDPAR1(19)), + dump_register(DEPCMDPAR1(20)), + dump_register(DEPCMDPAR1(21)), + dump_register(DEPCMDPAR1(22)), + dump_register(DEPCMDPAR1(23)), + dump_register(DEPCMDPAR1(24)), + dump_register(DEPCMDPAR1(25)), + dump_register(DEPCMDPAR1(26)), + dump_register(DEPCMDPAR1(27)), + dump_register(DEPCMDPAR1(28)), + dump_register(DEPCMDPAR1(29)), + dump_register(DEPCMDPAR1(30)), + dump_register(DEPCMDPAR1(31)), + + dump_register(DEPCMDPAR0(0)), + dump_register(DEPCMDPAR0(1)), + dump_register(DEPCMDPAR0(2)), + dump_register(DEPCMDPAR0(3)), + dump_register(DEPCMDPAR0(4)), + dump_register(DEPCMDPAR0(5)), + dump_register(DEPCMDPAR0(6)), + dump_register(DEPCMDPAR0(7)), + dump_register(DEPCMDPAR0(8)), + dump_register(DEPCMDPAR0(9)), + dump_register(DEPCMDPAR0(10)), + dump_register(DEPCMDPAR0(11)), + dump_register(DEPCMDPAR0(12)), + dump_register(DEPCMDPAR0(13)), + dump_register(DEPCMDPAR0(14)), + dump_register(DEPCMDPAR0(15)), + dump_register(DEPCMDPAR0(16)), + dump_register(DEPCMDPAR0(17)), + dump_register(DEPCMDPAR0(18)), + dump_register(DEPCMDPAR0(19)), + dump_register(DEPCMDPAR0(20)), + dump_register(DEPCMDPAR0(21)), + dump_register(DEPCMDPAR0(22)), + dump_register(DEPCMDPAR0(23)), + dump_register(DEPCMDPAR0(24)), + dump_register(DEPCMDPAR0(25)), + dump_register(DEPCMDPAR0(26)), + dump_register(DEPCMDPAR0(27)), + dump_register(DEPCMDPAR0(28)), + dump_register(DEPCMDPAR0(29)), + dump_register(DEPCMDPAR0(30)), + dump_register(DEPCMDPAR0(31)), + + dump_register(DEPCMD(0)), + dump_register(DEPCMD(1)), + dump_register(DEPCMD(2)), + dump_register(DEPCMD(3)), + dump_register(DEPCMD(4)), + dump_register(DEPCMD(5)), + dump_register(DEPCMD(6)), + dump_register(DEPCMD(7)), + dump_register(DEPCMD(8)), + dump_register(DEPCMD(9)), + dump_register(DEPCMD(10)), + dump_register(DEPCMD(11)), + dump_register(DEPCMD(12)), + dump_register(DEPCMD(13)), + dump_register(DEPCMD(14)), + dump_register(DEPCMD(15)), + dump_register(DEPCMD(16)), + dump_register(DEPCMD(17)), + dump_register(DEPCMD(18)), + dump_register(DEPCMD(19)), + dump_register(DEPCMD(20)), + dump_register(DEPCMD(21)), + dump_register(DEPCMD(22)), + dump_register(DEPCMD(23)), + dump_register(DEPCMD(24)), + dump_register(DEPCMD(25)), + dump_register(DEPCMD(26)), + dump_register(DEPCMD(27)), + dump_register(DEPCMD(28)), + dump_register(DEPCMD(29)), + dump_register(DEPCMD(30)), + dump_register(DEPCMD(31)), + + dump_register(OCFG), + dump_register(OCTL), + dump_register(OEVT), + dump_register(OEVTEN), + dump_register(OSTS), +}; + +static int dwc3_mode_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (DWC3_GCTL_PRTCAP(reg)) { + case DWC3_GCTL_PRTCAP_HOST: + seq_printf(s, "host\n"); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + seq_printf(s, "device\n"); + break; + case DWC3_GCTL_PRTCAP_OTG: + seq_printf(s, "OTG\n"); + break; + default: + seq_printf(s, "UNKNOWN %08x\n", DWC3_GCTL_PRTCAP(reg)); + } + + return 0; +} + +static int dwc3_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_mode_show, inode->i_private); +} + +static ssize_t dwc3_mode_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct dwc3 *dwc = s->private; + unsigned long flags; + u32 mode = 0; + char buf[32]; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "host", 4)) + mode |= DWC3_GCTL_PRTCAP_HOST; + + if (!strncmp(buf, "device", 6)) + mode |= DWC3_GCTL_PRTCAP_DEVICE; + + if (!strncmp(buf, "otg", 3)) + mode |= DWC3_GCTL_PRTCAP_OTG; + + if (mode) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_set_mode(dwc, mode); + spin_unlock_irqrestore(&dwc->lock, flags); + } + return count; +} + +static const struct file_operations dwc3_mode_fops = { + .open = dwc3_mode_open, + .write = dwc3_mode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dwc3_testmode_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= DWC3_DCTL_TSTCTRL_MASK; + reg >>= 1; + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (reg) { + case 0: + seq_printf(s, "no test\n"); + break; + case TEST_J: + seq_printf(s, "test_j\n"); + break; + case TEST_K: + seq_printf(s, "test_k\n"); + break; + case TEST_SE0_NAK: + seq_printf(s, "test_se0_nak\n"); + break; + case TEST_PACKET: + seq_printf(s, "test_packet\n"); + break; + case TEST_FORCE_EN: + seq_printf(s, "test_force_enable\n"); + break; + default: + seq_printf(s, "UNKNOWN %d\n", reg); + } + + return 0; +} + +static int dwc3_testmode_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_testmode_show, inode->i_private); +} + +static ssize_t dwc3_testmode_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct dwc3 *dwc = s->private; + unsigned long flags; + u32 testmode = 0; + char buf[32]; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "test_j", 6)) + testmode = TEST_J; + else if (!strncmp(buf, "test_k", 6)) + testmode = TEST_K; + else if (!strncmp(buf, "test_se0_nak", 12)) + testmode = TEST_SE0_NAK; + else if (!strncmp(buf, "test_packet", 11)) + testmode = TEST_PACKET; + else if (!strncmp(buf, "test_force_enable", 17)) + testmode = TEST_FORCE_EN; + else + testmode = 0; + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_set_test_mode(dwc, testmode); + spin_unlock_irqrestore(&dwc->lock, flags); + + return count; +} + +static const struct file_operations dwc3_testmode_fops = { + .open = dwc3_testmode_open, + .write = dwc3_testmode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dwc3_link_state_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned long flags; + enum dwc3_link_state state; + u32 reg; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + state = DWC3_DSTS_USBLNKST(reg); + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (state) { + case DWC3_LINK_STATE_U0: + seq_printf(s, "U0\n"); + break; + case DWC3_LINK_STATE_U1: + seq_printf(s, "U1\n"); + break; + case DWC3_LINK_STATE_U2: + seq_printf(s, "U2\n"); + break; + case DWC3_LINK_STATE_U3: + seq_printf(s, "U3\n"); + break; + case DWC3_LINK_STATE_SS_DIS: + seq_printf(s, "SS.Disabled\n"); + break; + case DWC3_LINK_STATE_RX_DET: + seq_printf(s, "Rx.Detect\n"); + break; + case DWC3_LINK_STATE_SS_INACT: + seq_printf(s, "SS.Inactive\n"); + break; + case DWC3_LINK_STATE_POLL: + seq_printf(s, "Poll\n"); + break; + case DWC3_LINK_STATE_RECOV: + seq_printf(s, "Recovery\n"); + break; + case DWC3_LINK_STATE_HRESET: + seq_printf(s, "HRESET\n"); + break; + case DWC3_LINK_STATE_CMPLY: + seq_printf(s, "Compliance\n"); + break; + case DWC3_LINK_STATE_LPBK: + seq_printf(s, "Loopback\n"); + break; + case DWC3_LINK_STATE_RESET: + seq_printf(s, "Reset\n"); + break; + case DWC3_LINK_STATE_RESUME: + seq_printf(s, "Resume\n"); + break; + default: + seq_printf(s, "UNKNOWN %d\n", state); + } + + return 0; +} + +static int dwc3_link_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_link_state_show, inode->i_private); +} + +static ssize_t dwc3_link_state_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct dwc3 *dwc = s->private; + unsigned long flags; + enum dwc3_link_state state = 0; + char buf[32]; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "SS.Disabled", 11)) + state = DWC3_LINK_STATE_SS_DIS; + else if (!strncmp(buf, "Rx.Detect", 9)) + state = DWC3_LINK_STATE_RX_DET; + else if (!strncmp(buf, "SS.Inactive", 11)) + state = DWC3_LINK_STATE_SS_INACT; + else if (!strncmp(buf, "Recovery", 8)) + state = DWC3_LINK_STATE_RECOV; + else if (!strncmp(buf, "Compliance", 10)) + state = DWC3_LINK_STATE_CMPLY; + else if (!strncmp(buf, "Loopback", 8)) + state = DWC3_LINK_STATE_LPBK; + else + return -EINVAL; + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_set_link_state(dwc, state); + spin_unlock_irqrestore(&dwc->lock, flags); + + return count; +} + +static const struct file_operations dwc3_link_state_fops = { + .open = dwc3_link_state_open, + .write = dwc3_link_state_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int dwc3_debugfs_init(struct dwc3 *dwc) +{ + struct dentry *root; + struct dentry *file; + int ret; + + root = debugfs_create_dir(dev_name(dwc->dev), NULL); + if (!root) { + ret = -ENOMEM; + goto err0; + } + + dwc->root = root; + + dwc->regset = kzalloc(sizeof(*dwc->regset), GFP_KERNEL); + if (!dwc->regset) { + ret = -ENOMEM; + goto err1; + } + + dwc->regset->regs = dwc3_regs; + dwc->regset->nregs = ARRAY_SIZE(dwc3_regs); + dwc->regset->base = dwc->regs; + + file = debugfs_create_regset32("regdump", S_IRUGO, root, dwc->regset); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) { + file = debugfs_create_file("mode", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_mode_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + } + + if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) || + IS_ENABLED(CONFIG_USB_DWC3_GADGET)) { + file = debugfs_create_file("testmode", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_testmode_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + file = debugfs_create_file("link_state", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_link_state_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + } + + return 0; + +err1: + debugfs_remove_recursive(root); + +err0: + return ret; +} + +void dwc3_debugfs_exit(struct dwc3 *dwc) +{ + debugfs_remove_recursive(dwc->root); + dwc->root = NULL; +} diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c new file mode 100644 index 00000000..8ce9d7fd --- /dev/null +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -0,0 +1,232 @@ +/** + * dwc3-exynos.c - Samsung EXYNOS DWC3 Specific Glue layer + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Anton Tikhomirov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dwc3_exynos { + struct platform_device *usb2_phy; + struct platform_device *usb3_phy; + struct device *dev; + + struct clk *clk; +}; + +static int dwc3_exynos_register_phys(struct dwc3_exynos *exynos) +{ + struct nop_usb_xceiv_platform_data pdata; + struct platform_device *pdev; + int ret; + + memset(&pdata, 0x00, sizeof(pdata)); + + pdev = platform_device_alloc("nop_usb_xceiv", PLATFORM_DEVID_AUTO); + if (!pdev) + return -ENOMEM; + + exynos->usb2_phy = pdev; + pdata.type = USB_PHY_TYPE_USB2; + + ret = platform_device_add_data(exynos->usb2_phy, &pdata, sizeof(pdata)); + if (ret) + goto err1; + + pdev = platform_device_alloc("nop_usb_xceiv", PLATFORM_DEVID_AUTO); + if (!pdev) { + ret = -ENOMEM; + goto err1; + } + + exynos->usb3_phy = pdev; + pdata.type = USB_PHY_TYPE_USB3; + + ret = platform_device_add_data(exynos->usb3_phy, &pdata, sizeof(pdata)); + if (ret) + goto err2; + + ret = platform_device_add(exynos->usb2_phy); + if (ret) + goto err2; + + ret = platform_device_add(exynos->usb3_phy); + if (ret) + goto err3; + + return 0; + +err3: + platform_device_del(exynos->usb2_phy); + +err2: + platform_device_put(exynos->usb3_phy); + +err1: + platform_device_put(exynos->usb2_phy); + + return ret; +} + +static int dwc3_exynos_remove_child(struct device *dev, void *unused) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static int dwc3_exynos_probe(struct platform_device *pdev) +{ + struct dwc3_exynos *exynos; + struct clk *clk; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + + int ret = -ENOMEM; + + exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL); + if (!exynos) { + dev_err(dev, "not enough memory\n"); + goto err1; + } + + /* + * 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 move to full device tree support this will vanish off. + */ + if (!dev->dma_mask) + dev->dma_mask = &dev->coherent_dma_mask; + if (!dev->coherent_dma_mask) + dev->coherent_dma_mask = DMA_BIT_MASK(32); + + platform_set_drvdata(pdev, exynos); + + ret = dwc3_exynos_register_phys(exynos); + if (ret) { + dev_err(dev, "couldn't register PHYs\n"); + goto err1; + } + + clk = devm_clk_get(dev, "usbdrd30"); + if (IS_ERR(clk)) { + dev_err(dev, "couldn't get clock\n"); + ret = -EINVAL; + goto err1; + } + + exynos->dev = dev; + exynos->clk = clk; + + clk_prepare_enable(exynos->clk); + + if (node) { + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to add dwc3 core\n"); + goto err2; + } + } else { + dev_err(dev, "no device node, failed to add dwc3 core\n"); + ret = -ENODEV; + goto err2; + } + + return 0; + +err2: + clk_disable_unprepare(clk); +err1: + return ret; +} + +static int dwc3_exynos_remove(struct platform_device *pdev) +{ + struct dwc3_exynos *exynos = platform_get_drvdata(pdev); + + device_for_each_child(&pdev->dev, NULL, dwc3_exynos_remove_child); + platform_device_unregister(exynos->usb2_phy); + platform_device_unregister(exynos->usb3_phy); + + clk_disable_unprepare(exynos->clk); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id exynos_dwc3_match[] = { + { .compatible = "samsung,exynos5250-dwusb3" }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_dwc3_match); +#endif + +#ifdef CONFIG_PM_SLEEP +static int dwc3_exynos_suspend(struct device *dev) +{ + struct dwc3_exynos *exynos = dev_get_drvdata(dev); + + clk_disable(exynos->clk); + + return 0; +} + +static int dwc3_exynos_resume(struct device *dev) +{ + struct dwc3_exynos *exynos = dev_get_drvdata(dev); + + clk_enable(exynos->clk); + + /* runtime set active to reflect active state. */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static const struct dev_pm_ops dwc3_exynos_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_exynos_suspend, dwc3_exynos_resume) +}; + +#define DEV_PM_OPS (&dwc3_exynos_dev_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct platform_driver dwc3_exynos_driver = { + .probe = dwc3_exynos_probe, + .remove = dwc3_exynos_remove, + .driver = { + .name = "exynos-dwc3", + .of_match_table = of_match_ptr(exynos_dwc3_match), + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver(dwc3_exynos_driver); + +MODULE_ALIAS("platform:exynos-dwc3"); +MODULE_AUTHOR("Anton Tikhomirov "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 EXYNOS Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c new file mode 100644 index 00000000..cb5f8c44 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -0,0 +1,481 @@ +/** + * dwc3-omap.c - OMAP Specific Glue layer + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * All these registers belong to OMAP's Wrapper around the + * DesignWare USB3 Core. + */ + +#define USBOTGSS_REVISION 0x0000 +#define USBOTGSS_SYSCONFIG 0x0010 +#define USBOTGSS_IRQ_EOI 0x0020 +#define USBOTGSS_IRQSTATUS_RAW_0 0x0024 +#define USBOTGSS_IRQSTATUS_0 0x0028 +#define USBOTGSS_IRQENABLE_SET_0 0x002c +#define USBOTGSS_IRQENABLE_CLR_0 0x0030 +#define USBOTGSS_IRQSTATUS_RAW_1 0x0034 +#define USBOTGSS_IRQSTATUS_1 0x0038 +#define USBOTGSS_IRQENABLE_SET_1 0x003c +#define USBOTGSS_IRQENABLE_CLR_1 0x0040 +#define USBOTGSS_UTMI_OTG_CTRL 0x0080 +#define USBOTGSS_UTMI_OTG_STATUS 0x0084 +#define USBOTGSS_MMRAM_OFFSET 0x0100 +#define USBOTGSS_FLADJ 0x0104 +#define USBOTGSS_DEBUG_CFG 0x0108 +#define USBOTGSS_DEBUG_DATA 0x010c + +/* SYSCONFIG REGISTER */ +#define USBOTGSS_SYSCONFIG_DMADISABLE (1 << 16) + +/* IRQ_EOI REGISTER */ +#define USBOTGSS_IRQ_EOI_LINE_NUMBER (1 << 0) + +/* IRQS0 BITS */ +#define USBOTGSS_IRQO_COREIRQ_ST (1 << 0) + +/* IRQ1 BITS */ +#define USBOTGSS_IRQ1_DMADISABLECLR (1 << 17) +#define USBOTGSS_IRQ1_OEVT (1 << 16) +#define USBOTGSS_IRQ1_DRVVBUS_RISE (1 << 13) +#define USBOTGSS_IRQ1_CHRGVBUS_RISE (1 << 12) +#define USBOTGSS_IRQ1_DISCHRGVBUS_RISE (1 << 11) +#define USBOTGSS_IRQ1_IDPULLUP_RISE (1 << 8) +#define USBOTGSS_IRQ1_DRVVBUS_FALL (1 << 5) +#define USBOTGSS_IRQ1_CHRGVBUS_FALL (1 << 4) +#define USBOTGSS_IRQ1_DISCHRGVBUS_FALL (1 << 3) +#define USBOTGSS_IRQ1_IDPULLUP_FALL (1 << 0) + +/* UTMI_OTG_CTRL REGISTER */ +#define USBOTGSS_UTMI_OTG_CTRL_DRVVBUS (1 << 5) +#define USBOTGSS_UTMI_OTG_CTRL_CHRGVBUS (1 << 4) +#define USBOTGSS_UTMI_OTG_CTRL_DISCHRGVBUS (1 << 3) +#define USBOTGSS_UTMI_OTG_CTRL_IDPULLUP (1 << 0) + +/* UTMI_OTG_STATUS REGISTER */ +#define USBOTGSS_UTMI_OTG_STATUS_SW_MODE (1 << 31) +#define USBOTGSS_UTMI_OTG_STATUS_POWERPRESENT (1 << 9) +#define USBOTGSS_UTMI_OTG_STATUS_TXBITSTUFFENABLE (1 << 8) +#define USBOTGSS_UTMI_OTG_STATUS_IDDIG (1 << 4) +#define USBOTGSS_UTMI_OTG_STATUS_SESSEND (1 << 3) +#define USBOTGSS_UTMI_OTG_STATUS_SESSVALID (1 << 2) +#define USBOTGSS_UTMI_OTG_STATUS_VBUSVALID (1 << 1) + +struct dwc3_omap { + /* device lock */ + spinlock_t lock; + + struct device *dev; + + int irq; + void __iomem *base; + + u32 utmi_otg_status; + + u32 dma_status:1; +}; + +static struct dwc3_omap *_omap; + +static inline u32 dwc3_omap_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static inline void dwc3_omap_writel(void __iomem *base, u32 offset, u32 value) +{ + writel(value, base + offset); +} + +int dwc3_omap_mailbox(enum omap_dwc3_vbus_id_status status) +{ + u32 val; + struct dwc3_omap *omap = _omap; + + if (!omap) + return -EPROBE_DEFER; + + switch (status) { + case OMAP_DWC3_ID_GROUND: + dev_dbg(omap->dev, "ID GND\n"); + + val = dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS); + val &= ~(USBOTGSS_UTMI_OTG_STATUS_IDDIG + | USBOTGSS_UTMI_OTG_STATUS_VBUSVALID + | USBOTGSS_UTMI_OTG_STATUS_SESSEND); + val |= USBOTGSS_UTMI_OTG_STATUS_SESSVALID + | USBOTGSS_UTMI_OTG_STATUS_POWERPRESENT; + dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, val); + break; + + case OMAP_DWC3_VBUS_VALID: + dev_dbg(omap->dev, "VBUS Connect\n"); + + val = dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS); + val &= ~USBOTGSS_UTMI_OTG_STATUS_SESSEND; + val |= USBOTGSS_UTMI_OTG_STATUS_IDDIG + | USBOTGSS_UTMI_OTG_STATUS_VBUSVALID + | USBOTGSS_UTMI_OTG_STATUS_SESSVALID + | USBOTGSS_UTMI_OTG_STATUS_POWERPRESENT; + dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, val); + break; + + case OMAP_DWC3_ID_FLOAT: + case OMAP_DWC3_VBUS_OFF: + dev_dbg(omap->dev, "VBUS Disconnect\n"); + + val = dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS); + val &= ~(USBOTGSS_UTMI_OTG_STATUS_SESSVALID + | USBOTGSS_UTMI_OTG_STATUS_VBUSVALID + | USBOTGSS_UTMI_OTG_STATUS_POWERPRESENT); + val |= USBOTGSS_UTMI_OTG_STATUS_SESSEND + | USBOTGSS_UTMI_OTG_STATUS_IDDIG; + dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, val); + break; + + default: + dev_dbg(omap->dev, "ID float\n"); + } + + return 0; +} +EXPORT_SYMBOL_GPL(dwc3_omap_mailbox); + +static irqreturn_t dwc3_omap_interrupt(int irq, void *_omap) +{ + struct dwc3_omap *omap = _omap; + u32 reg; + + spin_lock(&omap->lock); + + reg = dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_1); + + if (reg & USBOTGSS_IRQ1_DMADISABLECLR) { + dev_dbg(omap->dev, "DMA Disable was Cleared\n"); + omap->dma_status = false; + } + + if (reg & USBOTGSS_IRQ1_OEVT) + dev_dbg(omap->dev, "OTG Event\n"); + + if (reg & USBOTGSS_IRQ1_DRVVBUS_RISE) + dev_dbg(omap->dev, "DRVVBUS Rise\n"); + + if (reg & USBOTGSS_IRQ1_CHRGVBUS_RISE) + dev_dbg(omap->dev, "CHRGVBUS Rise\n"); + + if (reg & USBOTGSS_IRQ1_DISCHRGVBUS_RISE) + dev_dbg(omap->dev, "DISCHRGVBUS Rise\n"); + + if (reg & USBOTGSS_IRQ1_IDPULLUP_RISE) + dev_dbg(omap->dev, "IDPULLUP Rise\n"); + + if (reg & USBOTGSS_IRQ1_DRVVBUS_FALL) + dev_dbg(omap->dev, "DRVVBUS Fall\n"); + + if (reg & USBOTGSS_IRQ1_CHRGVBUS_FALL) + dev_dbg(omap->dev, "CHRGVBUS Fall\n"); + + if (reg & USBOTGSS_IRQ1_DISCHRGVBUS_FALL) + dev_dbg(omap->dev, "DISCHRGVBUS Fall\n"); + + if (reg & USBOTGSS_IRQ1_IDPULLUP_FALL) + dev_dbg(omap->dev, "IDPULLUP Fall\n"); + + dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_1, reg); + + reg = dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_0); + dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_0, reg); + + spin_unlock(&omap->lock); + + return IRQ_HANDLED; +} + +static int dwc3_omap_remove_core(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static void dwc3_omap_enable_irqs(struct dwc3_omap *omap) +{ + u32 reg; + + /* enable all IRQs */ + reg = USBOTGSS_IRQO_COREIRQ_ST; + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_0, reg); + + reg = (USBOTGSS_IRQ1_OEVT | + USBOTGSS_IRQ1_DRVVBUS_RISE | + USBOTGSS_IRQ1_CHRGVBUS_RISE | + USBOTGSS_IRQ1_DISCHRGVBUS_RISE | + USBOTGSS_IRQ1_IDPULLUP_RISE | + USBOTGSS_IRQ1_DRVVBUS_FALL | + USBOTGSS_IRQ1_CHRGVBUS_FALL | + USBOTGSS_IRQ1_DISCHRGVBUS_FALL | + USBOTGSS_IRQ1_IDPULLUP_FALL); + + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_1, reg); +} + +static void dwc3_omap_disable_irqs(struct dwc3_omap *omap) +{ + /* disable all IRQs */ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_1, 0x00); + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_0, 0x00); +} + +static u64 dwc3_omap_dma_mask = DMA_BIT_MASK(32); + +static int dwc3_omap_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + + struct dwc3_omap *omap; + struct resource *res; + struct device *dev = &pdev->dev; + + int ret = -ENOMEM; + int irq; + + int utmi_mode = 0; + + u32 reg; + + void __iomem *base; + + if (!node) { + dev_err(dev, "device node not found\n"); + return -EINVAL; + } + + omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL); + if (!omap) { + dev_err(dev, "not enough memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, omap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "missing IRQ resource\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing memory base resource\n"); + return -EINVAL; + } + + base = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (!base) { + dev_err(dev, "ioremap failed\n"); + return -ENOMEM; + } + + spin_lock_init(&omap->lock); + + omap->dev = dev; + omap->irq = irq; + omap->base = base; + dev->dma_mask = &dwc3_omap_dma_mask; + + /* + * REVISIT if we ever have two instances of the wrapper, we will be + * in big trouble + */ + _omap = omap; + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "get_sync failed with err %d\n", ret); + return ret; + } + + reg = dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS); + + of_property_read_u32(node, "utmi-mode", &utmi_mode); + + switch (utmi_mode) { + case DWC3_OMAP_UTMI_MODE_SW: + reg |= USBOTGSS_UTMI_OTG_STATUS_SW_MODE; + break; + case DWC3_OMAP_UTMI_MODE_HW: + reg &= ~USBOTGSS_UTMI_OTG_STATUS_SW_MODE; + break; + default: + dev_dbg(dev, "UNKNOWN utmi mode %d\n", utmi_mode); + } + + dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, reg); + + /* check the DMA Status */ + reg = dwc3_omap_readl(omap->base, USBOTGSS_SYSCONFIG); + omap->dma_status = !!(reg & USBOTGSS_SYSCONFIG_DMADISABLE); + + ret = devm_request_irq(dev, omap->irq, dwc3_omap_interrupt, 0, + "dwc3-omap", omap); + if (ret) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", + omap->irq, ret); + return ret; + } + + dwc3_omap_enable_irqs(omap); + + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(&pdev->dev, "failed to create dwc3 core\n"); + return ret; + } + + return 0; +} + +static int dwc3_omap_remove(struct platform_device *pdev) +{ + struct dwc3_omap *omap = platform_get_drvdata(pdev); + + dwc3_omap_disable_irqs(omap); + device_for_each_child(&pdev->dev, NULL, dwc3_omap_remove_core); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id of_dwc3_match[] = { + { + .compatible = "ti,dwc3" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_match); + +#ifdef CONFIG_PM_SLEEP +static int dwc3_omap_prepare(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + dwc3_omap_disable_irqs(omap); + + return 0; +} + +static void dwc3_omap_complete(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + dwc3_omap_enable_irqs(omap); +} + +static int dwc3_omap_suspend(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + omap->utmi_otg_status = dwc3_omap_readl(omap->base, + USBOTGSS_UTMI_OTG_STATUS); + + return 0; +} + +static int dwc3_omap_resume(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, + omap->utmi_otg_status); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static const struct dev_pm_ops dwc3_omap_dev_pm_ops = { + .prepare = dwc3_omap_prepare, + .complete = dwc3_omap_complete, + + SET_SYSTEM_SLEEP_PM_OPS(dwc3_omap_suspend, dwc3_omap_resume) +}; + +#define DEV_PM_OPS (&dwc3_omap_dev_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct platform_driver dwc3_omap_driver = { + .probe = dwc3_omap_probe, + .remove = dwc3_omap_remove, + .driver = { + .name = "omap-dwc3", + .of_match_table = of_dwc3_match, + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver(dwc3_omap_driver); + +MODULE_ALIAS("platform:omap-dwc3"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("DesignWare USB3 OMAP Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c new file mode 100644 index 00000000..2357c4e5 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -0,0 +1,268 @@ +/** + * dwc3-pci.c - PCI Specific glue layer + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include + +/* FIXME define these in */ +#define PCI_VENDOR_ID_SYNOPSYS 0x16c3 +#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3 0xabcd +#define PCI_DEVICE_ID_INTEL_BYT 0x0f37 +#define PCI_DEVICE_ID_INTEL_MRFLD 0x119e + +struct dwc3_pci { + struct device *dev; + struct platform_device *dwc3; + struct platform_device *usb2_phy; + struct platform_device *usb3_phy; +}; + +static int dwc3_pci_register_phys(struct dwc3_pci *glue) +{ + struct nop_usb_xceiv_platform_data pdata; + struct platform_device *pdev; + int ret; + + memset(&pdata, 0x00, sizeof(pdata)); + + pdev = platform_device_alloc("nop_usb_xceiv", 0); + if (!pdev) + return -ENOMEM; + + glue->usb2_phy = pdev; + pdata.type = USB_PHY_TYPE_USB2; + + ret = platform_device_add_data(glue->usb2_phy, &pdata, sizeof(pdata)); + if (ret) + goto err1; + + pdev = platform_device_alloc("nop_usb_xceiv", 1); + if (!pdev) { + ret = -ENOMEM; + goto err1; + } + + glue->usb3_phy = pdev; + pdata.type = USB_PHY_TYPE_USB3; + + ret = platform_device_add_data(glue->usb3_phy, &pdata, sizeof(pdata)); + if (ret) + goto err2; + + ret = platform_device_add(glue->usb2_phy); + if (ret) + goto err2; + + ret = platform_device_add(glue->usb3_phy); + if (ret) + goto err3; + + return 0; + +err3: + platform_device_del(glue->usb2_phy); + +err2: + platform_device_put(glue->usb3_phy); + +err1: + platform_device_put(glue->usb2_phy); + + return ret; +} + +static int dwc3_pci_probe(struct pci_dev *pci, + const struct pci_device_id *id) +{ + struct resource res[2]; + struct platform_device *dwc3; + struct dwc3_pci *glue; + int ret = -ENOMEM; + struct device *dev = &pci->dev; + + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(dev, "not enough memory\n"); + return -ENOMEM; + } + + glue->dev = dev; + + ret = pci_enable_device(pci); + if (ret) { + dev_err(dev, "failed to enable pci device\n"); + return -ENODEV; + } + + pci_set_power_state(pci, PCI_D0); + pci_set_master(pci); + + ret = dwc3_pci_register_phys(glue); + if (ret) { + dev_err(dev, "couldn't register PHYs\n"); + return ret; + } + + dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO); + if (!dwc3) { + dev_err(dev, "couldn't allocate dwc3 device\n"); + ret = -ENOMEM; + goto err1; + } + + memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res)); + + res[0].start = pci_resource_start(pci, 0); + res[0].end = pci_resource_end(pci, 0); + res[0].name = "dwc_usb3"; + res[0].flags = IORESOURCE_MEM; + + res[1].start = pci->irq; + res[1].name = "dwc_usb3"; + res[1].flags = IORESOURCE_IRQ; + + ret = platform_device_add_resources(dwc3, res, ARRAY_SIZE(res)); + if (ret) { + dev_err(dev, "couldn't add resources to dwc3 device\n"); + goto err1; + } + + pci_set_drvdata(pci, glue); + + dma_set_coherent_mask(&dwc3->dev, dev->coherent_dma_mask); + + dwc3->dev.dma_mask = dev->dma_mask; + dwc3->dev.dma_parms = dev->dma_parms; + dwc3->dev.parent = dev; + glue->dwc3 = dwc3; + + ret = platform_device_add(dwc3); + if (ret) { + dev_err(dev, "failed to register dwc3 device\n"); + goto err3; + } + + return 0; + +err3: + pci_set_drvdata(pci, NULL); + platform_device_put(dwc3); +err1: + pci_disable_device(pci); + + return ret; +} + +static void dwc3_pci_remove(struct pci_dev *pci) +{ + struct dwc3_pci *glue = pci_get_drvdata(pci); + + platform_device_unregister(glue->dwc3); + platform_device_unregister(glue->usb2_phy); + platform_device_unregister(glue->usb3_phy); + pci_set_drvdata(pci, NULL); + pci_disable_device(pci); +} + +static DEFINE_PCI_DEVICE_TABLE(dwc3_pci_id_table) = { + { + PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, + PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3), + }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_BYT), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_MRFLD), }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table); + +#ifdef CONFIG_PM +static int dwc3_pci_suspend(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + + pci_disable_device(pci); + + return 0; +} + +static int dwc3_pci_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + int ret; + + ret = pci_enable_device(pci); + if (ret) { + dev_err(dev, "can't re-enable device --> %d\n", ret); + return ret; + } + + pci_set_master(pci); + + return 0; +} + +static const struct dev_pm_ops dwc3_pci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_pci_suspend, dwc3_pci_resume) +}; + +#define DEV_PM_OPS (&dwc3_pci_dev_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct pci_driver dwc3_pci_driver = { + .name = "dwc3-pci", + .id_table = dwc3_pci_id_table, + .probe = dwc3_pci_probe, + .remove = dwc3_pci_remove, + .driver = { + .pm = DEV_PM_OPS, + }, +}; + +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("DesignWare USB3 PCI Glue Layer"); + +module_pci_driver(dwc3_pci_driver); diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c new file mode 100644 index 00000000..6cd418f6 --- /dev/null +++ b/drivers/usb/dwc3/ep0.c @@ -0,0 +1,1066 @@ +/** + * ep0.c - DesignWare USB3 DRD Controller Endpoint 0 Handling + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "core.h" +#include "gadget.h" +#include "io.h" + +static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep); +static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, + struct dwc3_ep *dep, struct dwc3_request *req); + +static const char *dwc3_ep0_state_string(enum dwc3_ep0_state state) +{ + switch (state) { + case EP0_UNCONNECTED: + return "Unconnected"; + case EP0_SETUP_PHASE: + return "Setup Phase"; + case EP0_DATA_PHASE: + return "Data Phase"; + case EP0_STATUS_PHASE: + return "Status Phase"; + default: + return "UNKNOWN"; + } +} + +static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum, dma_addr_t buf_dma, + u32 len, u32 type) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_trb *trb; + struct dwc3_ep *dep; + + int ret; + + dep = dwc->eps[epnum]; + if (dep->flags & DWC3_EP_BUSY) { + dev_vdbg(dwc->dev, "%s: still busy\n", dep->name); + return 0; + } + + trb = dwc->ep0_trb; + + trb->bpl = lower_32_bits(buf_dma); + trb->bph = upper_32_bits(buf_dma); + trb->size = len; + trb->ctrl = type; + + trb->ctrl |= (DWC3_TRB_CTRL_HWO + | DWC3_TRB_CTRL_LST + | DWC3_TRB_CTRL_IOC + | DWC3_TRB_CTRL_ISP_IMI); + + memset(¶ms, 0, sizeof(params)); + params.param0 = upper_32_bits(dwc->ep0_trb_addr); + params.param1 = lower_32_bits(dwc->ep0_trb_addr); + + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_STARTTRANSFER, ¶ms); + if (ret < 0) { + dev_dbg(dwc->dev, "failed to send STARTTRANSFER command\n"); + return ret; + } + + dep->flags |= DWC3_EP_BUSY; + dep->resource_index = dwc3_gadget_ep_get_transfer_index(dwc, + dep->number); + + dwc->ep0_next_event = DWC3_EP0_COMPLETE; + + return 0; +} + +static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep, + struct dwc3_request *req) +{ + struct dwc3 *dwc = dep->dwc; + + req->request.actual = 0; + req->request.status = -EINPROGRESS; + req->epnum = dep->number; + + list_add_tail(&req->list, &dep->request_list); + + /* + * Gadget driver might not be quick enough to queue a request + * before we get a Transfer Not Ready event on this endpoint. + * + * In that case, we will set DWC3_EP_PENDING_REQUEST. When that + * flag is set, it's telling us that as soon as Gadget queues the + * required request, we should kick the transfer here because the + * IRQ we were waiting for is long gone. + */ + if (dep->flags & DWC3_EP_PENDING_REQUEST) { + unsigned direction; + + direction = !!(dep->flags & DWC3_EP0_DIR_IN); + + if (dwc->ep0state != EP0_DATA_PHASE) { + dev_WARN(dwc->dev, "Unexpected pending request\n"); + return 0; + } + + __dwc3_ep0_do_control_data(dwc, dwc->eps[direction], req); + + dep->flags &= ~(DWC3_EP_PENDING_REQUEST | + DWC3_EP0_DIR_IN); + + return 0; + } + + /* + * In case gadget driver asked us to delay the STATUS phase, + * handle it here. + */ + if (dwc->delayed_status) { + unsigned direction; + + direction = !dwc->ep0_expect_in; + dwc->delayed_status = false; + + if (dwc->ep0state == EP0_STATUS_PHASE) + __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); + else + dev_dbg(dwc->dev, "too early for delayed status\n"); + + return 0; + } + + /* + * Unfortunately we have uncovered a limitation wrt the Data Phase. + * + * Section 9.4 says we can wait for the XferNotReady(DATA) event to + * come before issueing Start Transfer command, but if we do, we will + * miss situations where the host starts another SETUP phase instead of + * the DATA phase. Such cases happen at least on TD.7.6 of the Link + * Layer Compliance Suite. + * + * The problem surfaces due to the fact that in case of back-to-back + * SETUP packets there will be no XferNotReady(DATA) generated and we + * will be stuck waiting for XferNotReady(DATA) forever. + * + * By looking at tables 9-13 and 9-14 of the Databook, we can see that + * it tells us to start Data Phase right away. It also mentions that if + * we receive a SETUP phase instead of the DATA phase, core will issue + * XferComplete for the DATA phase, before actually initiating it in + * the wire, with the TRB's status set to "SETUP_PENDING". Such status + * can only be used to print some debugging logs, as the core expects + * us to go through to the STATUS phase and start a CONTROL_STATUS TRB, + * just so it completes right away, without transferring anything and, + * only then, we can go back to the SETUP phase. + * + * Because of this scenario, SNPS decided to change the programming + * model of control transfers and support on-demand transfers only for + * the STATUS phase. To fix the issue we have now, we will always wait + * for gadget driver to queue the DATA phase's struct usb_request, then + * start it right away. + * + * If we're actually in a 2-stage transfer, we will wait for + * XferNotReady(STATUS). + */ + if (dwc->three_stage_setup) { + unsigned direction; + + direction = dwc->ep0_expect_in; + dwc->ep0state = EP0_DATA_PHASE; + + __dwc3_ep0_do_control_data(dwc, dwc->eps[direction], req); + + dep->flags &= ~DWC3_EP0_DIR_IN; + } + + return 0; +} + +int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + unsigned long flags; + + int ret; + + spin_lock_irqsave(&dwc->lock, flags); + if (!dep->endpoint.desc) { + dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n", + request, dep->name); + ret = -ESHUTDOWN; + goto out; + } + + /* we share one TRB for ep0/1 */ + if (!list_empty(&dep->request_list)) { + ret = -EBUSY; + goto out; + } + + dev_vdbg(dwc->dev, "queueing request %p to %s length %d, state '%s'\n", + request, dep->name, request->length, + dwc3_ep0_state_string(dwc->ep0state)); + + ret = __dwc3_gadget_ep0_queue(dep, req); + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static void dwc3_ep0_stall_and_restart(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + + /* reinitialize physical ep1 */ + dep = dwc->eps[1]; + dep->flags = DWC3_EP_ENABLED; + + /* stall is always issued on EP0 */ + dep = dwc->eps[0]; + __dwc3_gadget_ep_set_halt(dep, 1, false); + dep->flags = DWC3_EP_ENABLED; + dwc->delayed_status = false; + + if (!list_empty(&dep->request_list)) { + struct dwc3_request *req; + + req = next_request(&dep->request_list); + dwc3_gadget_giveback(dep, req, -ECONNRESET); + } + + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); +} + +int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + dwc3_ep0_stall_and_restart(dwc); + + return 0; +} + +void dwc3_ep0_out_start(struct dwc3 *dwc) +{ + int ret; + + ret = dwc3_ep0_start_trans(dwc, 0, dwc->ctrl_req_addr, 8, + DWC3_TRBCTL_CONTROL_SETUP); + WARN_ON(ret < 0); +} + +static struct dwc3_ep *dwc3_wIndex_to_dep(struct dwc3 *dwc, __le16 wIndex_le) +{ + struct dwc3_ep *dep; + u32 windex = le16_to_cpu(wIndex_le); + u32 epnum; + + epnum = (windex & USB_ENDPOINT_NUMBER_MASK) << 1; + if ((windex & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) + epnum |= 1; + + dep = dwc->eps[epnum]; + if (dep->flags & DWC3_EP_ENABLED) + return dep; + + return NULL; +} + +static void dwc3_ep0_status_cmpl(struct usb_ep *ep, struct usb_request *req) +{ +} +/* + * ch 9.4.5 + */ +static int dwc3_ep0_handle_status(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl) +{ + struct dwc3_ep *dep; + u32 recip; + u32 reg; + u16 usb_status = 0; + __le16 *response_pkt; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + switch (recip) { + case USB_RECIP_DEVICE: + /* + * LTM will be set once we know how to set this in HW. + */ + usb_status |= dwc->is_selfpowered << USB_DEVICE_SELF_POWERED; + + if (dwc->speed == DWC3_DSTS_SUPERSPEED) { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (reg & DWC3_DCTL_INITU1ENA) + usb_status |= 1 << USB_DEV_STAT_U1_ENABLED; + if (reg & DWC3_DCTL_INITU2ENA) + usb_status |= 1 << USB_DEV_STAT_U2_ENABLED; + } + + break; + + case USB_RECIP_INTERFACE: + /* + * Function Remote Wake Capable D0 + * Function Remote Wakeup D1 + */ + break; + + case USB_RECIP_ENDPOINT: + dep = dwc3_wIndex_to_dep(dwc, ctrl->wIndex); + if (!dep) + return -EINVAL; + + if (dep->flags & DWC3_EP_STALL) + usb_status = 1 << USB_ENDPOINT_HALT; + break; + default: + return -EINVAL; + }; + + response_pkt = (__le16 *) dwc->setup_buf; + *response_pkt = cpu_to_le16(usb_status); + + dep = dwc->eps[0]; + dwc->ep0_usb_req.dep = dep; + dwc->ep0_usb_req.request.length = sizeof(*response_pkt); + dwc->ep0_usb_req.request.buf = dwc->setup_buf; + dwc->ep0_usb_req.request.complete = dwc3_ep0_status_cmpl; + + return __dwc3_gadget_ep0_queue(dep, &dwc->ep0_usb_req); +} + +static int dwc3_ep0_handle_feature(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl, int set) +{ + struct dwc3_ep *dep; + u32 recip; + u32 wValue; + u32 wIndex; + u32 reg; + int ret; + enum usb_device_state state; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + state = dwc->gadget.state; + + switch (recip) { + case USB_RECIP_DEVICE: + + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + break; + /* + * 9.4.1 says only only for SS, in AddressState only for + * default control pipe + */ + case USB_DEVICE_U1_ENABLE: + if (state != USB_STATE_CONFIGURED) + return -EINVAL; + if (dwc->speed != DWC3_DSTS_SUPERSPEED) + return -EINVAL; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (set) + reg |= DWC3_DCTL_INITU1ENA; + else + reg &= ~DWC3_DCTL_INITU1ENA; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + break; + + case USB_DEVICE_U2_ENABLE: + if (state != USB_STATE_CONFIGURED) + return -EINVAL; + if (dwc->speed != DWC3_DSTS_SUPERSPEED) + return -EINVAL; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (set) + reg |= DWC3_DCTL_INITU2ENA; + else + reg &= ~DWC3_DCTL_INITU2ENA; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + break; + + case USB_DEVICE_LTM_ENABLE: + return -EINVAL; + break; + + case USB_DEVICE_TEST_MODE: + if ((wIndex & 0xff) != 0) + return -EINVAL; + if (!set) + return -EINVAL; + + dwc->test_mode_nr = wIndex >> 8; + dwc->test_mode = true; + break; + default: + return -EINVAL; + } + break; + + case USB_RECIP_INTERFACE: + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + if (wIndex & USB_INTRF_FUNC_SUSPEND_LP) + /* XXX enable Low power suspend */ + ; + if (wIndex & USB_INTRF_FUNC_SUSPEND_RW) + /* XXX enable remote wakeup */ + ; + break; + default: + return -EINVAL; + } + break; + + case USB_RECIP_ENDPOINT: + switch (wValue) { + case USB_ENDPOINT_HALT: + dep = dwc3_wIndex_to_dep(dwc, wIndex); + if (!dep) + return -EINVAL; + if (set == 0 && (dep->flags & DWC3_EP_WEDGE)) + break; + ret = __dwc3_gadget_ep_set_halt(dep, set, true); + if (ret) + return -EINVAL; + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + }; + + return 0; +} + +static int dwc3_ep0_set_address(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = dwc->gadget.state; + u32 addr; + u32 reg; + + addr = le16_to_cpu(ctrl->wValue); + if (addr > 127) { + dev_dbg(dwc->dev, "invalid device address %d\n", addr); + return -EINVAL; + } + + if (state == USB_STATE_CONFIGURED) { + dev_dbg(dwc->dev, "trying to set address when configured\n"); + return -EINVAL; + } + + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_DEVADDR_MASK); + reg |= DWC3_DCFG_DEVADDR(addr); + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + if (addr) + usb_gadget_set_state(&dwc->gadget, USB_STATE_ADDRESS); + else + usb_gadget_set_state(&dwc->gadget, USB_STATE_DEFAULT); + + return 0; +} + +static int dwc3_ep0_delegate_req(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + int ret; + + spin_unlock(&dwc->lock); + ret = dwc->gadget_driver->setup(&dwc->gadget, ctrl); + spin_lock(&dwc->lock); + return ret; +} + +static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = dwc->gadget.state; + u32 cfg; + int ret; + u32 reg; + + dwc->start_config_issued = false; + cfg = le16_to_cpu(ctrl->wValue); + + switch (state) { + case USB_STATE_DEFAULT: + return -EINVAL; + break; + + case USB_STATE_ADDRESS: + ret = dwc3_ep0_delegate_req(dwc, ctrl); + /* if the cfg matches and the cfg is non zero */ + if (cfg && (!ret || (ret == USB_GADGET_DELAYED_STATUS))) { + usb_gadget_set_state(&dwc->gadget, + USB_STATE_CONFIGURED); + + /* + * Enable transition to U1/U2 state when + * nothing is pending from application. + */ + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg |= (DWC3_DCTL_ACCEPTU1ENA | DWC3_DCTL_ACCEPTU2ENA); + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + dwc->resize_fifos = true; + dev_dbg(dwc->dev, "resize fifos flag SET\n"); + } + break; + + case USB_STATE_CONFIGURED: + ret = dwc3_ep0_delegate_req(dwc, ctrl); + if (!cfg) + usb_gadget_set_state(&dwc->gadget, + USB_STATE_ADDRESS); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static void dwc3_ep0_set_sel_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + u32 param = 0; + u32 reg; + + struct timing { + u8 u1sel; + u8 u1pel; + u16 u2sel; + u16 u2pel; + } __packed timing; + + int ret; + + memcpy(&timing, req->buf, sizeof(timing)); + + dwc->u1sel = timing.u1sel; + dwc->u1pel = timing.u1pel; + dwc->u2sel = le16_to_cpu(timing.u2sel); + dwc->u2pel = le16_to_cpu(timing.u2pel); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (reg & DWC3_DCTL_INITU2ENA) + param = dwc->u2pel; + if (reg & DWC3_DCTL_INITU1ENA) + param = dwc->u1pel; + + /* + * According to Synopsys Databook, if parameter is + * greater than 125, a value of zero should be + * programmed in the register. + */ + if (param > 125) + param = 0; + + /* now that we have the time, issue DGCMD Set Sel */ + ret = dwc3_send_gadget_generic_command(dwc, + DWC3_DGCMD_SET_PERIODIC_PAR, param); + WARN_ON(ret < 0); +} + +static int dwc3_ep0_set_sel(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + struct dwc3_ep *dep; + enum usb_device_state state = dwc->gadget.state; + u16 wLength; + u16 wValue; + + if (state == USB_STATE_DEFAULT) + return -EINVAL; + + wValue = le16_to_cpu(ctrl->wValue); + wLength = le16_to_cpu(ctrl->wLength); + + if (wLength != 6) { + dev_err(dwc->dev, "Set SEL should be 6 bytes, got %d\n", + wLength); + return -EINVAL; + } + + /* + * To handle Set SEL we need to receive 6 bytes from Host. So let's + * queue a usb_request for 6 bytes. + * + * Remember, though, this controller can't handle non-wMaxPacketSize + * aligned transfers on the OUT direction, so we queue a request for + * wMaxPacketSize instead. + */ + dep = dwc->eps[0]; + dwc->ep0_usb_req.dep = dep; + dwc->ep0_usb_req.request.length = dep->endpoint.maxpacket; + dwc->ep0_usb_req.request.buf = dwc->setup_buf; + dwc->ep0_usb_req.request.complete = dwc3_ep0_set_sel_cmpl; + + return __dwc3_gadget_ep0_queue(dep, &dwc->ep0_usb_req); +} + +static int dwc3_ep0_set_isoch_delay(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + u16 wLength; + u16 wValue; + u16 wIndex; + + wValue = le16_to_cpu(ctrl->wValue); + wLength = le16_to_cpu(ctrl->wLength); + wIndex = le16_to_cpu(ctrl->wIndex); + + if (wIndex || wLength) + return -EINVAL; + + /* + * REVISIT It's unclear from Databook what to do with this + * value. For now, just cache it. + */ + dwc->isoch_delay = wValue; + + return 0; +} + +static int dwc3_ep0_std_request(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + int ret; + + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + dev_vdbg(dwc->dev, "USB_REQ_GET_STATUS\n"); + ret = dwc3_ep0_handle_status(dwc, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + dev_vdbg(dwc->dev, "USB_REQ_CLEAR_FEATURE\n"); + ret = dwc3_ep0_handle_feature(dwc, ctrl, 0); + break; + case USB_REQ_SET_FEATURE: + dev_vdbg(dwc->dev, "USB_REQ_SET_FEATURE\n"); + ret = dwc3_ep0_handle_feature(dwc, ctrl, 1); + break; + case USB_REQ_SET_ADDRESS: + dev_vdbg(dwc->dev, "USB_REQ_SET_ADDRESS\n"); + ret = dwc3_ep0_set_address(dwc, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + dev_vdbg(dwc->dev, "USB_REQ_SET_CONFIGURATION\n"); + ret = dwc3_ep0_set_config(dwc, ctrl); + break; + case USB_REQ_SET_SEL: + dev_vdbg(dwc->dev, "USB_REQ_SET_SEL\n"); + ret = dwc3_ep0_set_sel(dwc, ctrl); + break; + case USB_REQ_SET_ISOCH_DELAY: + dev_vdbg(dwc->dev, "USB_REQ_SET_ISOCH_DELAY\n"); + ret = dwc3_ep0_set_isoch_delay(dwc, ctrl); + break; + default: + dev_vdbg(dwc->dev, "Forwarding to gadget driver\n"); + ret = dwc3_ep0_delegate_req(dwc, ctrl); + break; + }; + + return ret; +} + +static void dwc3_ep0_inspect_setup(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct usb_ctrlrequest *ctrl = dwc->ctrl_req; + int ret = -EINVAL; + u32 len; + + if (!dwc->gadget_driver) + goto out; + + len = le16_to_cpu(ctrl->wLength); + if (!len) { + dwc->three_stage_setup = false; + dwc->ep0_expect_in = false; + dwc->ep0_next_event = DWC3_EP0_NRDY_STATUS; + } else { + dwc->three_stage_setup = true; + dwc->ep0_expect_in = !!(ctrl->bRequestType & USB_DIR_IN); + dwc->ep0_next_event = DWC3_EP0_NRDY_DATA; + } + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + ret = dwc3_ep0_std_request(dwc, ctrl); + else + ret = dwc3_ep0_delegate_req(dwc, ctrl); + + if (ret == USB_GADGET_DELAYED_STATUS) + dwc->delayed_status = true; + +out: + if (ret < 0) + dwc3_ep0_stall_and_restart(dwc); +} + +static void dwc3_ep0_complete_data(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_request *r = NULL; + struct usb_request *ur; + struct dwc3_trb *trb; + struct dwc3_ep *ep0; + u32 transferred; + u32 status; + u32 length; + u8 epnum; + + epnum = event->endpoint_number; + ep0 = dwc->eps[0]; + + dwc->ep0_next_event = DWC3_EP0_NRDY_STATUS; + + r = next_request(&ep0->request_list); + ur = &r->request; + + trb = dwc->ep0_trb; + + status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (status == DWC3_TRBSTS_SETUP_PENDING) { + dev_dbg(dwc->dev, "Setup Pending received\n"); + + if (r) + dwc3_gadget_giveback(ep0, r, -ECONNRESET); + + return; + } + + length = trb->size & DWC3_TRB_SIZE_MASK; + + if (dwc->ep0_bounced) { + unsigned transfer_size = ur->length; + unsigned maxp = ep0->endpoint.maxpacket; + + transfer_size += (maxp - (transfer_size % maxp)); + transferred = min_t(u32, ur->length, + transfer_size - length); + memcpy(ur->buf, dwc->ep0_bounce, transferred); + } else { + transferred = ur->length - length; + } + + ur->actual += transferred; + + if ((epnum & 1) && ur->actual < ur->length) { + /* for some reason we did not get everything out */ + + dwc3_ep0_stall_and_restart(dwc); + } else { + /* + * handle the case where we have to send a zero packet. This + * seems to be case when req.length > maxpacket. Could it be? + */ + if (r) + dwc3_gadget_giveback(ep0, r, 0); + } +} + +static void dwc3_ep0_complete_status(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_request *r; + struct dwc3_ep *dep; + struct dwc3_trb *trb; + u32 status; + + dep = dwc->eps[0]; + trb = dwc->ep0_trb; + + if (!list_empty(&dep->request_list)) { + r = next_request(&dep->request_list); + + dwc3_gadget_giveback(dep, r, 0); + } + + if (dwc->test_mode) { + int ret; + + ret = dwc3_gadget_set_test_mode(dwc, dwc->test_mode_nr); + if (ret < 0) { + dev_dbg(dwc->dev, "Invalid Test #%d\n", + dwc->test_mode_nr); + dwc3_ep0_stall_and_restart(dwc); + return; + } + } + + status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (status == DWC3_TRBSTS_SETUP_PENDING) + dev_dbg(dwc->dev, "Setup Pending received\n"); + + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); +} + +static void dwc3_ep0_xfer_complete(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_ep *dep = dwc->eps[event->endpoint_number]; + + dep->flags &= ~DWC3_EP_BUSY; + dep->resource_index = 0; + dwc->setup_packet_pending = false; + + switch (dwc->ep0state) { + case EP0_SETUP_PHASE: + dev_vdbg(dwc->dev, "Inspecting Setup Bytes\n"); + dwc3_ep0_inspect_setup(dwc, event); + break; + + case EP0_DATA_PHASE: + dev_vdbg(dwc->dev, "Data Phase\n"); + dwc3_ep0_complete_data(dwc, event); + break; + + case EP0_STATUS_PHASE: + dev_vdbg(dwc->dev, "Status Phase\n"); + dwc3_ep0_complete_status(dwc, event); + break; + default: + WARN(true, "UNKNOWN ep0state %d\n", dwc->ep0state); + } +} + +static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, + struct dwc3_ep *dep, struct dwc3_request *req) +{ + int ret; + + req->direction = !!dep->number; + + if (req->request.length == 0) { + ret = dwc3_ep0_start_trans(dwc, dep->number, + dwc->ctrl_req_addr, 0, + DWC3_TRBCTL_CONTROL_DATA); + } else if (!IS_ALIGNED(req->request.length, dep->endpoint.maxpacket) + && (dep->number == 0)) { + u32 transfer_size; + u32 maxpacket; + + ret = usb_gadget_map_request(&dwc->gadget, &req->request, + dep->number); + if (ret) { + dev_dbg(dwc->dev, "failed to map request\n"); + return; + } + + WARN_ON(req->request.length > DWC3_EP0_BOUNCE_SIZE); + + maxpacket = dep->endpoint.maxpacket; + transfer_size = roundup(req->request.length, maxpacket); + + dwc->ep0_bounced = true; + + /* + * REVISIT in case request length is bigger than + * DWC3_EP0_BOUNCE_SIZE we will need two chained + * TRBs to handle the transfer. + */ + ret = dwc3_ep0_start_trans(dwc, dep->number, + dwc->ep0_bounce_addr, transfer_size, + DWC3_TRBCTL_CONTROL_DATA); + } else { + ret = usb_gadget_map_request(&dwc->gadget, &req->request, + dep->number); + if (ret) { + dev_dbg(dwc->dev, "failed to map request\n"); + return; + } + + ret = dwc3_ep0_start_trans(dwc, dep->number, req->request.dma, + req->request.length, DWC3_TRBCTL_CONTROL_DATA); + } + + WARN_ON(ret < 0); +} + +static int dwc3_ep0_start_control_status(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + u32 type; + + type = dwc->three_stage_setup ? DWC3_TRBCTL_CONTROL_STATUS3 + : DWC3_TRBCTL_CONTROL_STATUS2; + + return dwc3_ep0_start_trans(dwc, dep->number, + dwc->ctrl_req_addr, 0, type); +} + +static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + if (dwc->resize_fifos) { + dev_dbg(dwc->dev, "starting to resize fifos\n"); + dwc3_gadget_resize_tx_fifos(dwc); + dwc->resize_fifos = 0; + } + + WARN_ON(dwc3_ep0_start_control_status(dep)); +} + +static void dwc3_ep0_do_control_status(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_ep *dep = dwc->eps[event->endpoint_number]; + + __dwc3_ep0_do_control_status(dwc, dep); +} + +static void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret; + + if (!dep->resource_index) + return; + + cmd = DWC3_DEPCMD_ENDTRANSFER; + cmd |= DWC3_DEPCMD_CMDIOC; + cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); + memset(¶ms, 0, sizeof(params)); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + WARN_ON_ONCE(ret); + dep->resource_index = 0; +} + +static void dwc3_ep0_xfernotready(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + dwc->setup_packet_pending = true; + + switch (event->status) { + case DEPEVT_STATUS_CONTROL_DATA: + dev_vdbg(dwc->dev, "Control Data\n"); + + /* + * We already have a DATA transfer in the controller's cache, + * if we receive a XferNotReady(DATA) we will ignore it, unless + * it's for the wrong direction. + * + * In that case, we must issue END_TRANSFER command to the Data + * Phase we already have started and issue SetStall on the + * control endpoint. + */ + if (dwc->ep0_expect_in != event->endpoint_number) { + struct dwc3_ep *dep = dwc->eps[dwc->ep0_expect_in]; + + dev_vdbg(dwc->dev, "Wrong direction for Data phase\n"); + dwc3_ep0_end_control_data(dwc, dep); + dwc3_ep0_stall_and_restart(dwc); + return; + } + + break; + + case DEPEVT_STATUS_CONTROL_STATUS: + if (dwc->ep0_next_event != DWC3_EP0_NRDY_STATUS) + return; + + dev_vdbg(dwc->dev, "Control Status\n"); + + dwc->ep0state = EP0_STATUS_PHASE; + + if (dwc->delayed_status) { + WARN_ON_ONCE(event->endpoint_number != 1); + dev_vdbg(dwc->dev, "Mass Storage delayed status\n"); + return; + } + + dwc3_ep0_do_control_status(dwc, event); + } +} + +void dwc3_ep0_interrupt(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + u8 epnum = event->endpoint_number; + + dev_dbg(dwc->dev, "%s while ep%d%s in state '%s'\n", + dwc3_ep_event_string(event->endpoint_event), + epnum >> 1, (epnum & 1) ? "in" : "out", + dwc3_ep0_state_string(dwc->ep0state)); + + switch (event->endpoint_event) { + case DWC3_DEPEVT_XFERCOMPLETE: + dwc3_ep0_xfer_complete(dwc, event); + break; + + case DWC3_DEPEVT_XFERNOTREADY: + dwc3_ep0_xfernotready(dwc, event); + break; + + case DWC3_DEPEVT_XFERINPROGRESS: + case DWC3_DEPEVT_RXTXFIFOEVT: + case DWC3_DEPEVT_STREAMEVT: + case DWC3_DEPEVT_EPCMDCMPLT: + break; + } +} diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c new file mode 100644 index 00000000..8f8e75e3 --- /dev/null +++ b/drivers/usb/dwc3/gadget.c @@ -0,0 +1,2776 @@ +/** + * gadget.c - DesignWare USB3 DRD Controller Gadget Framework Link + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "core.h" +#include "gadget.h" +#include "io.h" + +/** + * dwc3_gadget_set_test_mode - Enables USB2 Test Modes + * @dwc: pointer to our context structure + * @mode: the mode to set (J, K SE0 NAK, Force Enable) + * + * Caller should take care of locking. This function will + * return 0 on success or -EINVAL if wrong Test Selector + * is passed + */ +int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_TSTCTRL_MASK; + + switch (mode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + reg |= mode << 1; + break; + default: + return -EINVAL; + } + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + return 0; +} + +/** + * dwc3_gadget_set_link_state - Sets USB Link to a particular State + * @dwc: pointer to our context structure + * @state: the state to put link into + * + * Caller should take care of locking. This function will + * return 0 on success or -ETIMEDOUT. + */ +int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state) +{ + int retries = 10000; + u32 reg; + + /* + * Wait until device controller is ready. Only applies to 1.94a and + * later RTL. + */ + if (dwc->revision >= DWC3_REVISION_194A) { + while (--retries) { + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + if (reg & DWC3_DSTS_DCNRD) + udelay(5); + else + break; + } + + if (retries <= 0) + return -ETIMEDOUT; + } + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK; + + /* set requested state */ + reg |= DWC3_DCTL_ULSTCHNGREQ(state); + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + /* + * The following code is racy when called from dwc3_gadget_wakeup, + * and is not needed, at least on newer versions + */ + if (dwc->revision >= DWC3_REVISION_194A) + return 0; + + /* wait for a change in DSTS */ + retries = 10000; + while (--retries) { + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + + if (DWC3_DSTS_USBLNKST(reg) == state) + return 0; + + udelay(5); + } + + dev_vdbg(dwc->dev, "link state change request timed out\n"); + + return -ETIMEDOUT; +} + +/** + * dwc3_gadget_resize_tx_fifos - reallocate fifo spaces for current use-case + * @dwc: pointer to our context structure + * + * This function will a best effort FIFO allocation in order + * to improve FIFO usage and throughput, while still allowing + * us to enable as many endpoints as possible. + * + * Keep in mind that this operation will be highly dependent + * on the configured size for RAM1 - which contains TxFifo -, + * the amount of endpoints enabled on coreConsultant tool, and + * the width of the Master Bus. + * + * In the ideal world, we would always be able to satisfy the + * following equation: + * + * ((512 + 2 * MDWIDTH-Bytes) + (Number of IN Endpoints - 1) * \ + * (3 * (1024 + MDWIDTH-Bytes) + MDWIDTH-Bytes)) / MDWIDTH-Bytes + * + * Unfortunately, due to many variables that's not always the case. + */ +int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc) +{ + int last_fifo_depth = 0; + int ram1_depth; + int fifo_size; + int mdwidth; + int num; + + if (!dwc->needs_fifo_resize) + return 0; + + ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7); + mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0); + + /* MDWIDTH is represented in bits, we need it in bytes */ + mdwidth >>= 3; + + /* + * FIXME For now we will only allocate 1 wMaxPacketSize space + * for each enabled endpoint, later patches will come to + * improve this algorithm so that we better use the internal + * FIFO space + */ + for (num = 0; num < DWC3_ENDPOINTS_NUM; num++) { + struct dwc3_ep *dep = dwc->eps[num]; + int fifo_number = dep->number >> 1; + int mult = 1; + int tmp; + + if (!(dep->number & 1)) + continue; + + if (!(dep->flags & DWC3_EP_ENABLED)) + continue; + + if (usb_endpoint_xfer_bulk(dep->endpoint.desc) + || usb_endpoint_xfer_isoc(dep->endpoint.desc)) + mult = 3; + + /* + * REVISIT: the following assumes we will always have enough + * space available on the FIFO RAM for all possible use cases. + * Make sure that's true somehow and change FIFO allocation + * accordingly. + * + * If we have Bulk or Isochronous endpoints, we want + * them to be able to be very, very fast. So we're giving + * those endpoints a fifo_size which is enough for 3 full + * packets + */ + tmp = mult * (dep->endpoint.maxpacket + mdwidth); + tmp += mdwidth; + + fifo_size = DIV_ROUND_UP(tmp, mdwidth); + + fifo_size |= (last_fifo_depth << 16); + + dev_vdbg(dwc->dev, "%s: Fifo Addr %04x Size %d\n", + dep->name, last_fifo_depth, fifo_size & 0xffff); + + dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(fifo_number), + fifo_size); + + last_fifo_depth += (fifo_size & 0xffff); + } + + return 0; +} + +void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, + int status) +{ + struct dwc3 *dwc = dep->dwc; + int i; + + if (req->queued) { + i = 0; + do { + dep->busy_slot++; + /* + * Skip LINK TRB. We can't use req->trb and check for + * DWC3_TRBCTL_LINK_TRB because it points the TRB we + * just completed (not the LINK TRB). + */ + if (((dep->busy_slot & DWC3_TRB_MASK) == + DWC3_TRB_NUM- 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + dep->busy_slot++; + } while(++i < req->request.num_mapped_sgs); + req->queued = false; + } + list_del(&req->list); + req->trb = NULL; + + if (req->request.status == -EINPROGRESS) + req->request.status = status; + + if (dwc->ep0_bounced && dep->number == 0) + dwc->ep0_bounced = false; + else + usb_gadget_unmap_request(&dwc->gadget, &req->request, + req->direction); + + dev_dbg(dwc->dev, "request %p from %s completed %d/%d ===> %d\n", + req, dep->name, req->request.actual, + req->request.length, status); + + spin_unlock(&dwc->lock); + req->request.complete(&dep->endpoint, &req->request); + spin_lock(&dwc->lock); +} + +static const char *dwc3_gadget_ep_cmd_string(u8 cmd) +{ + switch (cmd) { + case DWC3_DEPCMD_DEPSTARTCFG: + return "Start New Configuration"; + case DWC3_DEPCMD_ENDTRANSFER: + return "End Transfer"; + case DWC3_DEPCMD_UPDATETRANSFER: + return "Update Transfer"; + case DWC3_DEPCMD_STARTTRANSFER: + return "Start Transfer"; + case DWC3_DEPCMD_CLEARSTALL: + return "Clear Stall"; + case DWC3_DEPCMD_SETSTALL: + return "Set Stall"; + case DWC3_DEPCMD_GETEPSTATE: + return "Get Endpoint State"; + case DWC3_DEPCMD_SETTRANSFRESOURCE: + return "Set Endpoint Transfer Resource"; + case DWC3_DEPCMD_SETEPCONFIG: + return "Set Endpoint Configuration"; + default: + return "UNKNOWN command"; + } +} + +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param) +{ + u32 timeout = 500; + u32 reg; + + dwc3_writel(dwc->regs, DWC3_DGCMDPAR, param); + dwc3_writel(dwc->regs, DWC3_DGCMD, cmd | DWC3_DGCMD_CMDACT); + + do { + reg = dwc3_readl(dwc->regs, DWC3_DGCMD); + if (!(reg & DWC3_DGCMD_CMDACT)) { + dev_vdbg(dwc->dev, "Command Complete --> %d\n", + DWC3_DGCMD_STATUS(reg)); + return 0; + } + + /* + * We can't sleep here, because it's also called from + * interrupt context. + */ + timeout--; + if (!timeout) + return -ETIMEDOUT; + udelay(1); + } while (1); +} + +int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, + unsigned cmd, struct dwc3_gadget_ep_cmd_params *params) +{ + struct dwc3_ep *dep = dwc->eps[ep]; + u32 timeout = 500; + u32 reg; + + dev_vdbg(dwc->dev, "%s: cmd '%s' params %08x %08x %08x\n", + dep->name, + dwc3_gadget_ep_cmd_string(cmd), params->param0, + params->param1, params->param2); + + dwc3_writel(dwc->regs, DWC3_DEPCMDPAR0(ep), params->param0); + dwc3_writel(dwc->regs, DWC3_DEPCMDPAR1(ep), params->param1); + dwc3_writel(dwc->regs, DWC3_DEPCMDPAR2(ep), params->param2); + + dwc3_writel(dwc->regs, DWC3_DEPCMD(ep), cmd | DWC3_DEPCMD_CMDACT); + do { + reg = dwc3_readl(dwc->regs, DWC3_DEPCMD(ep)); + if (!(reg & DWC3_DEPCMD_CMDACT)) { + dev_vdbg(dwc->dev, "Command Complete --> %d\n", + DWC3_DEPCMD_STATUS(reg)); + return 0; + } + + /* + * We can't sleep here, because it is also called from + * interrupt context. + */ + timeout--; + if (!timeout) + return -ETIMEDOUT; + + udelay(1); + } while (1); +} + +static dma_addr_t dwc3_trb_dma_offset(struct dwc3_ep *dep, + struct dwc3_trb *trb) +{ + u32 offset = (char *) trb - (char *) dep->trb_pool; + + return dep->trb_pool_dma + offset; +} + +static int dwc3_alloc_trb_pool(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + + if (dep->trb_pool) + return 0; + + if (dep->number == 0 || dep->number == 1) + return 0; + + dep->trb_pool = dma_alloc_coherent(dwc->dev, + sizeof(struct dwc3_trb) * DWC3_TRB_NUM, + &dep->trb_pool_dma, GFP_KERNEL); + if (!dep->trb_pool) { + dev_err(dep->dwc->dev, "failed to allocate trb pool for %s\n", + dep->name); + return -ENOMEM; + } + + return 0; +} + +static void dwc3_free_trb_pool(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + + dma_free_coherent(dwc->dev, sizeof(struct dwc3_trb) * DWC3_TRB_NUM, + dep->trb_pool, dep->trb_pool_dma); + + dep->trb_pool = NULL; + dep->trb_pool_dma = 0; +} + +static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + + memset(¶ms, 0x00, sizeof(params)); + + if (dep->number != 1) { + cmd = DWC3_DEPCMD_DEPSTARTCFG; + /* XferRscIdx == 0 for ep0 and 2 for the remaining */ + if (dep->number > 1) { + if (dwc->start_config_issued) + return 0; + dwc->start_config_issued = true; + cmd |= DWC3_DEPCMD_PARAM(2); + } + + return dwc3_send_gadget_ep_cmd(dwc, 0, cmd, ¶ms); + } + + return 0; +} + +static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, + const struct usb_endpoint_descriptor *desc, + const struct usb_ss_ep_comp_descriptor *comp_desc, + bool ignore) +{ + struct dwc3_gadget_ep_cmd_params params; + + memset(¶ms, 0x00, sizeof(params)); + + params.param0 = DWC3_DEPCFG_EP_TYPE(usb_endpoint_type(desc)) + | DWC3_DEPCFG_MAX_PACKET_SIZE(usb_endpoint_maxp(desc)); + + /* Burst size is only needed in SuperSpeed mode */ + if (dwc->gadget.speed == USB_SPEED_SUPER) { + u32 burst = dep->endpoint.maxburst - 1; + + params.param0 |= DWC3_DEPCFG_BURST_SIZE(burst); + } + + if (ignore) + params.param0 |= DWC3_DEPCFG_IGN_SEQ_NUM; + + params.param1 = DWC3_DEPCFG_XFER_COMPLETE_EN + | DWC3_DEPCFG_XFER_NOT_READY_EN; + + if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) { + params.param1 |= DWC3_DEPCFG_STREAM_CAPABLE + | DWC3_DEPCFG_STREAM_EVENT_EN; + dep->stream_capable = true; + } + + if (usb_endpoint_xfer_isoc(desc)) + params.param1 |= DWC3_DEPCFG_XFER_IN_PROGRESS_EN; + + /* + * We are doing 1:1 mapping for endpoints, meaning + * Physical Endpoints 2 maps to Logical Endpoint 2 and + * so on. We consider the direction bit as part of the physical + * endpoint number. So USB endpoint 0x81 is 0x03. + */ + params.param1 |= DWC3_DEPCFG_EP_NUMBER(dep->number); + + /* + * We must use the lower 16 TX FIFOs even though + * HW might have more + */ + if (dep->direction) + params.param0 |= DWC3_DEPCFG_FIFO_NUMBER(dep->number >> 1); + + if (desc->bInterval) { + params.param1 |= DWC3_DEPCFG_BINTERVAL_M1(desc->bInterval - 1); + dep->interval = 1 << (desc->bInterval - 1); + } + + return dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_SETEPCONFIG, ¶ms); +} + +static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + + memset(¶ms, 0x00, sizeof(params)); + + params.param0 = DWC3_DEPXFERCFG_NUM_XFER_RES(1); + + return dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_SETTRANSFRESOURCE, ¶ms); +} + +/** + * __dwc3_gadget_ep_enable - Initializes a HW endpoint + * @dep: endpoint to be initialized + * @desc: USB Endpoint Descriptor + * + * Caller should take care of locking + */ +static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, + const struct usb_endpoint_descriptor *desc, + const struct usb_ss_ep_comp_descriptor *comp_desc, + bool ignore) +{ + struct dwc3 *dwc = dep->dwc; + u32 reg; + int ret = -ENOMEM; + + if (!(dep->flags & DWC3_EP_ENABLED)) { + ret = dwc3_gadget_start_config(dwc, dep); + if (ret) + return ret; + } + + ret = dwc3_gadget_set_ep_config(dwc, dep, desc, comp_desc, ignore); + if (ret) + return ret; + + if (!(dep->flags & DWC3_EP_ENABLED)) { + struct dwc3_trb *trb_st_hw; + struct dwc3_trb *trb_link; + + ret = dwc3_gadget_set_xfer_resource(dwc, dep); + if (ret) + return ret; + + dep->endpoint.desc = desc; + dep->comp_desc = comp_desc; + dep->type = usb_endpoint_type(desc); + dep->flags |= DWC3_EP_ENABLED; + + reg = dwc3_readl(dwc->regs, DWC3_DALEPENA); + reg |= DWC3_DALEPENA_EP(dep->number); + dwc3_writel(dwc->regs, DWC3_DALEPENA, reg); + + if (!usb_endpoint_xfer_isoc(desc)) + return 0; + + /* Link TRB for ISOC. The HWO bit is never reset */ + trb_st_hw = &dep->trb_pool[0]; + + trb_link = &dep->trb_pool[DWC3_TRB_NUM - 1]; + memset(trb_link, 0, sizeof(*trb_link)); + + trb_link->bpl = lower_32_bits(dwc3_trb_dma_offset(dep, trb_st_hw)); + trb_link->bph = upper_32_bits(dwc3_trb_dma_offset(dep, trb_st_hw)); + trb_link->ctrl |= DWC3_TRBCTL_LINK_TRB; + trb_link->ctrl |= DWC3_TRB_CTRL_HWO; + } + + return 0; +} + +static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum); +static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_request *req; + + if (!list_empty(&dep->req_queued)) { + dwc3_stop_active_transfer(dwc, dep->number); + + /* - giveback all requests to gadget driver */ + while (!list_empty(&dep->req_queued)) { + req = next_request(&dep->req_queued); + + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + } + } + + while (!list_empty(&dep->request_list)) { + req = next_request(&dep->request_list); + + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + } +} + +/** + * __dwc3_gadget_ep_disable - Disables a HW endpoint + * @dep: the endpoint to disable + * + * This function also removes requests which are currently processed ny the + * hardware and those which are not yet scheduled. + * Caller should take care of locking. + */ +static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + u32 reg; + + dwc3_remove_requests(dwc, dep); + + /* make sure HW endpoint isn't stalled */ + if (dep->flags & DWC3_EP_STALL) + __dwc3_gadget_ep_set_halt(dep, 0, false); + + reg = dwc3_readl(dwc->regs, DWC3_DALEPENA); + reg &= ~DWC3_DALEPENA_EP(dep->number); + dwc3_writel(dwc->regs, DWC3_DALEPENA, reg); + + dep->stream_capable = false; + dep->endpoint.desc = NULL; + dep->comp_desc = NULL; + dep->type = 0; + dep->flags = 0; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +static int dwc3_gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct dwc3_ep *dep; + struct dwc3 *dwc; + unsigned long flags; + int ret; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + pr_debug("dwc3: invalid parameters\n"); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + pr_debug("dwc3: missing wMaxPacketSize\n"); + return -EINVAL; + } + + dep = to_dwc3_ep(ep); + dwc = dep->dwc; + + if (dep->flags & DWC3_EP_ENABLED) { + dev_WARN_ONCE(dwc->dev, true, "%s is already enabled\n", + dep->name); + return 0; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + strlcat(dep->name, "-control", sizeof(dep->name)); + break; + case USB_ENDPOINT_XFER_ISOC: + strlcat(dep->name, "-isoc", sizeof(dep->name)); + break; + case USB_ENDPOINT_XFER_BULK: + strlcat(dep->name, "-bulk", sizeof(dep->name)); + break; + case USB_ENDPOINT_XFER_INT: + strlcat(dep->name, "-int", sizeof(dep->name)); + break; + default: + dev_err(dwc->dev, "invalid endpoint transfer type\n"); + } + + dev_vdbg(dwc->dev, "Enabling %s\n", dep->name); + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static int dwc3_gadget_ep_disable(struct usb_ep *ep) +{ + struct dwc3_ep *dep; + struct dwc3 *dwc; + unsigned long flags; + int ret; + + if (!ep) { + pr_debug("dwc3: invalid parameters\n"); + return -EINVAL; + } + + dep = to_dwc3_ep(ep); + dwc = dep->dwc; + + if (!(dep->flags & DWC3_EP_ENABLED)) { + dev_WARN_ONCE(dwc->dev, true, "%s is already disabled\n", + dep->name); + return 0; + } + + snprintf(dep->name, sizeof(dep->name), "ep%d%s", + dep->number >> 1, + (dep->number & 1) ? "in" : "out"); + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep_disable(dep); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static struct usb_request *dwc3_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct dwc3_request *req; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) { + dev_err(dwc->dev, "not enough memory\n"); + return NULL; + } + + req->epnum = dep->number; + req->dep = dep; + + return &req->request; +} + +static void dwc3_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + struct dwc3_request *req = to_dwc3_request(request); + + kfree(req); +} + +/** + * dwc3_prepare_one_trb - setup one TRB from one request + * @dep: endpoint for which this request is prepared + * @req: dwc3_request pointer + */ +static void dwc3_prepare_one_trb(struct dwc3_ep *dep, + struct dwc3_request *req, dma_addr_t dma, + unsigned length, unsigned last, unsigned chain, unsigned node) +{ + struct dwc3 *dwc = dep->dwc; + struct dwc3_trb *trb; + + dev_vdbg(dwc->dev, "%s: req %p dma %08llx length %d%s%s\n", + dep->name, req, (unsigned long long) dma, + length, last ? " last" : "", + chain ? " chain" : ""); + + /* Skip the LINK-TRB on ISOC */ + if (((dep->free_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + dep->free_slot++; + + trb = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; + + if (!req->trb) { + dwc3_gadget_move_request_queued(req); + req->trb = trb; + req->trb_dma = dwc3_trb_dma_offset(dep, trb); + req->start_slot = dep->free_slot & DWC3_TRB_MASK; + } + + dep->free_slot++; + + trb->size = DWC3_TRB_SIZE_LENGTH(length); + trb->bpl = lower_32_bits(dma); + trb->bph = upper_32_bits(dma); + + switch (usb_endpoint_type(dep->endpoint.desc)) { + case USB_ENDPOINT_XFER_CONTROL: + trb->ctrl = DWC3_TRBCTL_CONTROL_SETUP; + break; + + case USB_ENDPOINT_XFER_ISOC: + if (!node) + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST; + else + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS; + + if (!req->request.no_interrupt && !chain) + trb->ctrl |= DWC3_TRB_CTRL_IOC; + break; + + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + trb->ctrl = DWC3_TRBCTL_NORMAL; + break; + default: + /* + * This is only possible with faulty memory because we + * checked it already :) + */ + BUG(); + } + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; + trb->ctrl |= DWC3_TRB_CTRL_CSP; + } else if (last) { + trb->ctrl |= DWC3_TRB_CTRL_LST; + } + + if (chain) + trb->ctrl |= DWC3_TRB_CTRL_CHN; + + if (usb_endpoint_xfer_bulk(dep->endpoint.desc) && dep->stream_capable) + trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(req->request.stream_id); + + trb->ctrl |= DWC3_TRB_CTRL_HWO; +} + +/* + * dwc3_prepare_trbs - setup TRBs from requests + * @dep: endpoint for which requests are being prepared + * @starting: true if the endpoint is idle and no requests are queued. + * + * The function goes through the requests list and sets up TRBs for the + * transfers. The function returns once there are no more TRBs available or + * it runs out of requests. + */ +static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) +{ + struct dwc3_request *req, *n; + u32 trbs_left; + u32 max; + unsigned int last_one = 0; + + BUILD_BUG_ON_NOT_POWER_OF_2(DWC3_TRB_NUM); + + /* the first request must not be queued */ + trbs_left = (dep->busy_slot - dep->free_slot) & DWC3_TRB_MASK; + + /* Can't wrap around on a non-isoc EP since there's no link TRB */ + if (!usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + max = DWC3_TRB_NUM - (dep->free_slot & DWC3_TRB_MASK); + if (trbs_left > max) + trbs_left = max; + } + + /* + * If busy & slot are equal than it is either full or empty. If we are + * starting to process requests then we are empty. Otherwise we are + * full and don't do anything + */ + if (!trbs_left) { + if (!starting) + return; + trbs_left = DWC3_TRB_NUM; + /* + * In case we start from scratch, we queue the ISOC requests + * starting from slot 1. This is done because we use ring + * buffer and have no LST bit to stop us. Instead, we place + * IOC bit every TRB_NUM/4. We try to avoid having an interrupt + * after the first request so we start at slot 1 and have + * 7 requests proceed before we hit the first IOC. + * Other transfer types don't use the ring buffer and are + * processed from the first TRB until the last one. Since we + * don't wrap around we have to start at the beginning. + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dep->busy_slot = 1; + dep->free_slot = 1; + } else { + dep->busy_slot = 0; + dep->free_slot = 0; + } + } + + /* The last TRB is a link TRB, not used for xfer */ + if ((trbs_left <= 1) && usb_endpoint_xfer_isoc(dep->endpoint.desc)) + return; + + list_for_each_entry_safe(req, n, &dep->request_list, list) { + unsigned length; + dma_addr_t dma; + last_one = false; + + if (req->request.num_mapped_sgs > 0) { + struct usb_request *request = &req->request; + struct scatterlist *sg = request->sg; + struct scatterlist *s; + int i; + + for_each_sg(sg, s, request->num_mapped_sgs, i) { + unsigned chain = true; + + length = sg_dma_len(s); + dma = sg_dma_address(s); + + if (i == (request->num_mapped_sgs - 1) || + sg_is_last(s)) { + if (list_is_last(&req->list, + &dep->request_list)) + last_one = true; + chain = false; + } + + trbs_left--; + if (!trbs_left) + last_one = true; + + if (last_one) + chain = false; + + dwc3_prepare_one_trb(dep, req, dma, length, + last_one, chain, i); + + if (last_one) + break; + } + } else { + dma = req->request.dma; + length = req->request.length; + trbs_left--; + + if (!trbs_left) + last_one = 1; + + /* Is this the last request? */ + if (list_is_last(&req->list, &dep->request_list)) + last_one = 1; + + dwc3_prepare_one_trb(dep, req, dma, length, + last_one, false, 0); + + if (last_one) + break; + } + } +} + +static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, + int start_new) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_request *req; + struct dwc3 *dwc = dep->dwc; + int ret; + u32 cmd; + + if (start_new && (dep->flags & DWC3_EP_BUSY)) { + dev_vdbg(dwc->dev, "%s: endpoint busy\n", dep->name); + return -EBUSY; + } + dep->flags &= ~DWC3_EP_PENDING_REQUEST; + + /* + * If we are getting here after a short-out-packet we don't enqueue any + * new requests as we try to set the IOC bit only on the last request. + */ + if (start_new) { + if (list_empty(&dep->req_queued)) + dwc3_prepare_trbs(dep, start_new); + + /* req points to the first request which will be sent */ + req = next_request(&dep->req_queued); + } else { + dwc3_prepare_trbs(dep, start_new); + + /* + * req points to the first request where HWO changed from 0 to 1 + */ + req = next_request(&dep->req_queued); + } + if (!req) { + dep->flags |= DWC3_EP_PENDING_REQUEST; + return 0; + } + + memset(¶ms, 0, sizeof(params)); + + if (start_new) { + params.param0 = upper_32_bits(req->trb_dma); + params.param1 = lower_32_bits(req->trb_dma); + cmd = DWC3_DEPCMD_STARTTRANSFER; + } else { + cmd = DWC3_DEPCMD_UPDATETRANSFER; + } + + cmd |= DWC3_DEPCMD_PARAM(cmd_param); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + if (ret < 0) { + dev_dbg(dwc->dev, "failed to send STARTTRANSFER command\n"); + + /* + * FIXME we need to iterate over the list of requests + * here and stop, unmap, free and del each of the linked + * requests instead of what we do now. + */ + usb_gadget_unmap_request(&dwc->gadget, &req->request, + req->direction); + list_del(&req->list); + return ret; + } + + dep->flags |= DWC3_EP_BUSY; + + if (start_new) { + dep->resource_index = dwc3_gadget_ep_get_transfer_index(dwc, + dep->number); + WARN_ON_ONCE(!dep->resource_index); + } + + return 0; +} + +static void __dwc3_gadget_start_isoc(struct dwc3 *dwc, + struct dwc3_ep *dep, u32 cur_uf) +{ + u32 uf; + + if (list_empty(&dep->request_list)) { + dev_vdbg(dwc->dev, "ISOC ep %s run out for requests.\n", + dep->name); + dep->flags |= DWC3_EP_PENDING_REQUEST; + return; + } + + /* 4 micro frames in the future */ + uf = cur_uf + dep->interval * 4; + + __dwc3_gadget_kick_transfer(dep, uf, 1); +} + +static void dwc3_gadget_start_isoc(struct dwc3 *dwc, + struct dwc3_ep *dep, const struct dwc3_event_depevt *event) +{ + u32 cur_uf, mask; + + mask = ~(dep->interval - 1); + cur_uf = event->parameters & mask; + + __dwc3_gadget_start_isoc(dwc, dep, cur_uf); +} + +static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) +{ + struct dwc3 *dwc = dep->dwc; + int ret; + + req->request.actual = 0; + req->request.status = -EINPROGRESS; + req->direction = dep->direction; + req->epnum = dep->number; + + /* + * We only add to our list of requests now and + * start consuming the list once we get XferNotReady + * IRQ. + * + * That way, we avoid doing anything that we don't need + * to do now and defer it until the point we receive a + * particular token from the Host side. + * + * This will also avoid Host cancelling URBs due to too + * many NAKs. + */ + ret = usb_gadget_map_request(&dwc->gadget, &req->request, + dep->direction); + if (ret) + return ret; + + list_add_tail(&req->list, &dep->request_list); + + /* + * There are a few special cases: + * + * 1. XferNotReady with empty list of requests. We need to kick the + * transfer here in that situation, otherwise we will be NAKing + * forever. If we get XferNotReady before gadget driver has a + * chance to queue a request, we will ACK the IRQ but won't be + * able to receive the data until the next request is queued. + * The following code is handling exactly that. + * + */ + if (dep->flags & DWC3_EP_PENDING_REQUEST) { + /* + * If xfernotready is already elapsed and it is a case + * of isoc transfer, then issue END TRANSFER, so that + * you can receive xfernotready again and can have + * notion of current microframe. + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + if (list_empty(&dep->req_queued)) { + dwc3_stop_active_transfer(dwc, dep->number); + dep->flags = DWC3_EP_ENABLED; + } + return 0; + } + + ret = __dwc3_gadget_kick_transfer(dep, 0, true); + if (ret && ret != -EBUSY) + dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dep->name); + return ret; + } + + /* + * 2. XferInProgress on Isoc EP with an active transfer. We need to + * kick the transfer here after queuing a request, otherwise the + * core may not see the modified TRB(s). + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && + (dep->flags & DWC3_EP_BUSY) && + !(dep->flags & DWC3_EP_MISSED_ISOC)) { + WARN_ON_ONCE(!dep->resource_index); + ret = __dwc3_gadget_kick_transfer(dep, dep->resource_index, + false); + if (ret && ret != -EBUSY) + dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dep->name); + return ret; + } + + return 0; +} + +static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + unsigned long flags; + + int ret; + + if (!dep->endpoint.desc) { + dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n", + request, ep->name); + return -ESHUTDOWN; + } + + dev_vdbg(dwc->dev, "queing request %p to %s length %d\n", + request, ep->name, request->length); + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep_queue(dep, req); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_request *r = NULL; + + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&dwc->lock, flags); + + list_for_each_entry(r, &dep->request_list, list) { + if (r == req) + break; + } + + if (r != req) { + list_for_each_entry(r, &dep->req_queued, list) { + if (r == req) + break; + } + if (r == req) { + /* wait until it is processed */ + dwc3_stop_active_transfer(dwc, dep->number); + goto out1; + } + dev_err(dwc->dev, "request %p was not queued to %s\n", + request, ep->name); + ret = -EINVAL; + goto out0; + } + +out1: + /* giveback the request */ + dwc3_gadget_giveback(dep, req, -ECONNRESET); + +out0: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3 *dwc = dep->dwc; + int ret; + + memset(¶ms, 0x00, sizeof(params)); + + if (value) { + if (!protocol && ((dep->direction && dep->flags & DWC3_EP_BUSY) || + (!list_empty(&dep->req_queued) || + !list_empty(&dep->request_list)))) { + dev_dbg(dwc->dev, "%s: pending request, cannot halt\n", + dep->name); + return -EAGAIN; + } + + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_SETSTALL, ¶ms); + if (ret) + dev_err(dwc->dev, "failed to %s STALL on %s\n", + value ? "set" : "clear", + dep->name); + else + dep->flags |= DWC3_EP_STALL; + } else { + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_CLEARSTALL, ¶ms); + if (ret) + dev_err(dwc->dev, "failed to %s STALL on %s\n", + value ? "set" : "clear", + dep->name); + else + dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); + } + + return ret; +} + +static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + unsigned long flags; + + int ret; + + spin_lock_irqsave(&dwc->lock, flags); + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dev_err(dwc->dev, "%s is of Isochronous type\n", dep->name); + ret = -EINVAL; + goto out; + } + + ret = __dwc3_gadget_ep_set_halt(dep, value, false); +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dep->flags |= DWC3_EP_WEDGE; + spin_unlock_irqrestore(&dwc->lock, flags); + + if (dep->number == 0 || dep->number == 1) + return dwc3_gadget_ep0_set_halt(ep, 1); + else + return __dwc3_gadget_ep_set_halt(dep, 1, false); +} + +/* -------------------------------------------------------------------------- */ + +static struct usb_endpoint_descriptor dwc3_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static const struct usb_ep_ops dwc3_gadget_ep0_ops = { + .enable = dwc3_gadget_ep0_enable, + .disable = dwc3_gadget_ep0_disable, + .alloc_request = dwc3_gadget_ep_alloc_request, + .free_request = dwc3_gadget_ep_free_request, + .queue = dwc3_gadget_ep0_queue, + .dequeue = dwc3_gadget_ep_dequeue, + .set_halt = dwc3_gadget_ep0_set_halt, + .set_wedge = dwc3_gadget_ep_set_wedge, +}; + +static const struct usb_ep_ops dwc3_gadget_ep_ops = { + .enable = dwc3_gadget_ep_enable, + .disable = dwc3_gadget_ep_disable, + .alloc_request = dwc3_gadget_ep_alloc_request, + .free_request = dwc3_gadget_ep_free_request, + .queue = dwc3_gadget_ep_queue, + .dequeue = dwc3_gadget_ep_dequeue, + .set_halt = dwc3_gadget_ep_set_halt, + .set_wedge = dwc3_gadget_ep_set_wedge, +}; + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_get_frame(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + return DWC3_DSTS_SOFFN(reg); +} + +static int dwc3_gadget_wakeup(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + + unsigned long timeout; + unsigned long flags; + + u32 reg; + + int ret = 0; + + u8 link_state; + u8 speed; + + spin_lock_irqsave(&dwc->lock, flags); + + /* + * According to the Databook Remote wakeup request should + * be issued only when the device is in early suspend state. + * + * We can check that via USB Link State bits in DSTS register. + */ + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + + speed = reg & DWC3_DSTS_CONNECTSPD; + if (speed == DWC3_DSTS_SUPERSPEED) { + dev_dbg(dwc->dev, "no wakeup on SuperSpeed\n"); + ret = -EINVAL; + goto out; + } + + link_state = DWC3_DSTS_USBLNKST(reg); + + switch (link_state) { + case DWC3_LINK_STATE_RX_DET: /* in HS, means Early Suspend */ + case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */ + break; + default: + dev_dbg(dwc->dev, "can't wakeup from link state %d\n", + link_state); + ret = -EINVAL; + goto out; + } + + ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV); + if (ret < 0) { + dev_err(dwc->dev, "failed to put link in Recovery\n"); + goto out; + } + + /* Recent versions do this automatically */ + if (dwc->revision < DWC3_REVISION_194A) { + /* write zeroes to Link Change Request */ + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } + + /* poll until Link State changes to ON */ + timeout = jiffies + msecs_to_jiffies(100); + + while (!time_after(jiffies, timeout)) { + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + + /* in HS, means ON */ + if (DWC3_DSTS_USBLNKST(reg) == DWC3_LINK_STATE_U0) + break; + } + + if (DWC3_DSTS_USBLNKST(reg) != DWC3_LINK_STATE_U0) { + dev_err(dwc->dev, "failed to send remote wakeup\n"); + ret = -EINVAL; + } + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, + int is_selfpowered) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) +{ + u32 reg; + u32 timeout = 500; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (is_on) { + if (dwc->revision <= DWC3_REVISION_187A) { + reg &= ~DWC3_DCTL_TRGTULST_MASK; + reg |= DWC3_DCTL_TRGTULST_RX_DET; + } + + if (dwc->revision >= DWC3_REVISION_194A) + reg &= ~DWC3_DCTL_KEEP_CONNECT; + reg |= DWC3_DCTL_RUN_STOP; + dwc->pullups_connected = true; + } else { + reg &= ~DWC3_DCTL_RUN_STOP; + dwc->pullups_connected = false; + } + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + do { + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + if (is_on) { + if (!(reg & DWC3_DSTS_DEVCTRLHLT)) + break; + } else { + if (reg & DWC3_DSTS_DEVCTRLHLT) + break; + } + timeout--; + if (!timeout) + return -ETIMEDOUT; + udelay(1); + } while (1); + + dev_vdbg(dwc->dev, "gadget %s data soft-%s\n", + dwc->gadget_driver + ? dwc->gadget_driver->function : "no-function", + is_on ? "connect" : "disconnect"); + + return 0; +} + +static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + int ret; + + is_on = !!is_on; + + spin_lock_irqsave(&dwc->lock, flags); + ret = dwc3_gadget_run_stop(dwc, is_on); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static void dwc3_gadget_enable_irq(struct dwc3 *dwc) +{ + u32 reg; + + /* Enable all but Start and End of Frame IRQs */ + reg = (DWC3_DEVTEN_VNDRDEVTSTRCVEDEN | + DWC3_DEVTEN_EVNTOVERFLOWEN | + DWC3_DEVTEN_CMDCMPLTEN | + DWC3_DEVTEN_ERRTICERREN | + DWC3_DEVTEN_WKUPEVTEN | + DWC3_DEVTEN_ULSTCNGEN | + DWC3_DEVTEN_CONNECTDONEEN | + DWC3_DEVTEN_USBRSTEN | + DWC3_DEVTEN_DISCONNEVTEN); + + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); +} + +static void dwc3_gadget_disable_irq(struct dwc3 *dwc) +{ + /* mask all interrupts */ + dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00); +} + +static irqreturn_t dwc3_interrupt(int irq, void *_dwc); +static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc); + +static int dwc3_gadget_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + struct dwc3_ep *dep; + unsigned long flags; + int ret = 0; + int irq; + u32 reg; + + irq = platform_get_irq(to_platform_device(dwc->dev), 0); + ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt, + IRQF_SHARED | IRQF_ONESHOT, "dwc3", dwc); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + irq, ret); + goto err0; + } + + spin_lock_irqsave(&dwc->lock, flags); + + if (dwc->gadget_driver) { + dev_err(dwc->dev, "%s is already bound to %s\n", + dwc->gadget.name, + dwc->gadget_driver->driver.name); + ret = -EBUSY; + goto err1; + } + + dwc->gadget_driver = driver; + + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_SPEED_MASK); + + /** + * WORKAROUND: DWC3 revision < 2.20a have an issue + * which would cause metastability state on Run/Stop + * bit if we try to force the IP to USB2-only mode. + * + * Because of that, we cannot configure the IP to any + * speed other than the SuperSpeed + * + * Refers to: + * + * STAR#9000525659: Clock Domain Crossing on DCTL in + * USB 2.0 Mode + */ + if (dwc->revision < DWC3_REVISION_220A) + reg |= DWC3_DCFG_SUPERSPEED; + else + reg |= dwc->maximum_speed; + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + dwc->start_config_issued = false; + + /* Start with SuperSpeed Default */ + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + + dep = dwc->eps[0]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + goto err2; + } + + dep = dwc->eps[1]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + goto err3; + } + + /* begin to receive SETUP packets */ + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); + + dwc3_gadget_enable_irq(dwc); + + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; + +err3: + __dwc3_gadget_ep_disable(dwc->eps[0]); + +err2: + dwc->gadget_driver = NULL; + +err1: + spin_unlock_irqrestore(&dwc->lock, flags); + + free_irq(irq, dwc); + +err0: + return ret; +} + +static int dwc3_gadget_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + int irq; + + spin_lock_irqsave(&dwc->lock, flags); + + dwc3_gadget_disable_irq(dwc); + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); + + dwc->gadget_driver = NULL; + + spin_unlock_irqrestore(&dwc->lock, flags); + + irq = platform_get_irq(to_platform_device(dwc->dev), 0); + free_irq(irq, dwc); + + return 0; +} + +static const struct usb_gadget_ops dwc3_gadget_ops = { + .get_frame = dwc3_gadget_get_frame, + .wakeup = dwc3_gadget_wakeup, + .set_selfpowered = dwc3_gadget_set_selfpowered, + .pullup = dwc3_gadget_pullup, + .udc_start = dwc3_gadget_start, + .udc_stop = dwc3_gadget_stop, +}; + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, + u8 num, u32 direction) +{ + struct dwc3_ep *dep; + u8 i; + + for (i = 0; i < num; i++) { + u8 epnum = (i << 1) | (!!direction); + + dep = kzalloc(sizeof(*dep), GFP_KERNEL); + if (!dep) { + dev_err(dwc->dev, "can't allocate endpoint %d\n", + epnum); + return -ENOMEM; + } + + dep->dwc = dwc; + dep->number = epnum; + dwc->eps[epnum] = dep; + + snprintf(dep->name, sizeof(dep->name), "ep%d%s", epnum >> 1, + (epnum & 1) ? "in" : "out"); + + dep->endpoint.name = dep->name; + dep->direction = (epnum & 1); + + if (epnum == 0 || epnum == 1) { + dep->endpoint.maxpacket = 512; + dep->endpoint.maxburst = 1; + dep->endpoint.ops = &dwc3_gadget_ep0_ops; + if (!epnum) + dwc->gadget.ep0 = &dep->endpoint; + } else { + int ret; + + dep->endpoint.maxpacket = 1024; + dep->endpoint.max_streams = 15; + dep->endpoint.ops = &dwc3_gadget_ep_ops; + list_add_tail(&dep->endpoint.ep_list, + &dwc->gadget.ep_list); + + ret = dwc3_alloc_trb_pool(dep); + if (ret) + return ret; + } + + INIT_LIST_HEAD(&dep->request_list); + INIT_LIST_HEAD(&dep->req_queued); + } + + return 0; +} + +static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) +{ + int ret; + + INIT_LIST_HEAD(&dwc->gadget.ep_list); + + ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_out_eps, 0); + if (ret < 0) { + dev_vdbg(dwc->dev, "failed to allocate OUT endpoints\n"); + return ret; + } + + ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_in_eps, 1); + if (ret < 0) { + dev_vdbg(dwc->dev, "failed to allocate IN endpoints\n"); + return ret; + } + + return 0; +} + +static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + u8 epnum; + + for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) { + dep = dwc->eps[epnum]; + if (!dep) + continue; + /* + * Physical endpoints 0 and 1 are special; they form the + * bi-directional USB endpoint 0. + * + * For those two physical endpoints, we don't allocate a TRB + * pool nor do we add them the endpoints list. Due to that, we + * shouldn't do these two operations otherwise we would end up + * with all sorts of bugs when removing dwc3.ko. + */ + if (epnum != 0 && epnum != 1) { + dwc3_free_trb_pool(dep); + list_del(&dep->endpoint.ep_list); + } + + kfree(dep); + } +} + +/* -------------------------------------------------------------------------- */ + +static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, + struct dwc3_request *req, struct dwc3_trb *trb, + const struct dwc3_event_depevt *event, int status) +{ + unsigned int count; + unsigned int s_pkt = 0; + unsigned int trb_status; + + if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) + /* + * We continue despite the error. There is not much we + * can do. If we don't clean it up we loop forever. If + * we skip the TRB then it gets overwritten after a + * while since we use them in a ring buffer. A BUG() + * would help. Lets hope that if this occurs, someone + * fixes the root cause instead of looking away :) + */ + dev_err(dwc->dev, "%s's TRB (%p) still owned by HW\n", + dep->name, trb); + count = trb->size & DWC3_TRB_SIZE_MASK; + + if (dep->direction) { + if (count) { + trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { + dev_dbg(dwc->dev, "incomplete IN transfer %s\n", + dep->name); + /* + * If missed isoc occurred and there is + * no request queued then issue END + * TRANSFER, so that core generates + * next xfernotready and we will issue + * a fresh START TRANSFER. + * If there are still queued request + * then wait, do not issue either END + * or UPDATE TRANSFER, just attach next + * request in request_list during + * giveback.If any future queued request + * is successfully transferred then we + * will issue UPDATE TRANSFER for all + * request in the request_list. + */ + dep->flags |= DWC3_EP_MISSED_ISOC; + } else { + dev_err(dwc->dev, "incomplete IN transfer %s\n", + dep->name); + status = -ECONNRESET; + } + } else { + dep->flags &= ~DWC3_EP_MISSED_ISOC; + } + } else { + if (count && (event->status & DEPEVT_STATUS_SHORT)) + s_pkt = 1; + } + + /* + * We assume here we will always receive the entire data block + * which we should receive. Meaning, if we program RX to + * receive 4K but we receive only 2K, we assume that's all we + * should receive and we simply bounce the request back to the + * gadget driver for further processing. + */ + req->request.actual += req->request.length - count; + if (s_pkt) + return 1; + if ((event->status & DEPEVT_STATUS_LST) && + (trb->ctrl & (DWC3_TRB_CTRL_LST | + DWC3_TRB_CTRL_HWO))) + return 1; + if ((event->status & DEPEVT_STATUS_IOC) && + (trb->ctrl & DWC3_TRB_CTRL_IOC)) + return 1; + return 0; +} + +static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, + const struct dwc3_event_depevt *event, int status) +{ + struct dwc3_request *req; + struct dwc3_trb *trb; + unsigned int slot; + unsigned int i; + int ret; + + do { + req = next_request(&dep->req_queued); + if (!req) { + WARN_ON_ONCE(1); + return 1; + } + i = 0; + do { + slot = req->start_slot + i; + if ((slot == DWC3_TRB_NUM - 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + slot++; + slot %= DWC3_TRB_NUM; + trb = &dep->trb_pool[slot]; + + ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb, + event, status); + if (ret) + break; + }while (++i < req->request.num_mapped_sgs); + + dwc3_gadget_giveback(dep, req, status); + + if (ret) + break; + } while (1); + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && + list_empty(&dep->req_queued)) { + if (list_empty(&dep->request_list)) { + /* + * If there is no entry in request list then do + * not issue END TRANSFER now. Just set PENDING + * flag, so that END TRANSFER is issued when an + * entry is added into request list. + */ + dep->flags = DWC3_EP_PENDING_REQUEST; + } else { + dwc3_stop_active_transfer(dwc, dep->number); + dep->flags = DWC3_EP_ENABLED; + } + return 1; + } + + if ((event->status & DEPEVT_STATUS_IOC) && + (trb->ctrl & DWC3_TRB_CTRL_IOC)) + return 0; + return 1; +} + +static void dwc3_endpoint_transfer_complete(struct dwc3 *dwc, + struct dwc3_ep *dep, const struct dwc3_event_depevt *event, + int start_new) +{ + unsigned status = 0; + int clean_busy; + + if (event->status & DEPEVT_STATUS_BUSERR) + status = -ECONNRESET; + + clean_busy = dwc3_cleanup_done_reqs(dwc, dep, event, status); + if (clean_busy) + dep->flags &= ~DWC3_EP_BUSY; + + /* + * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround. + * See dwc3_gadget_linksts_change_interrupt() for 1st half. + */ + if (dwc->revision < DWC3_REVISION_183A) { + u32 reg; + int i; + + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { + dep = dwc->eps[i]; + + if (!(dep->flags & DWC3_EP_ENABLED)) + continue; + + if (!list_empty(&dep->req_queued)) + return; + } + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg |= dwc->u1u2; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + dwc->u1u2 = 0; + } +} + +static void dwc3_endpoint_interrupt(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_ep *dep; + u8 epnum = event->endpoint_number; + + dep = dwc->eps[epnum]; + + if (!(dep->flags & DWC3_EP_ENABLED)) + return; + + dev_vdbg(dwc->dev, "%s: %s\n", dep->name, + dwc3_ep_event_string(event->endpoint_event)); + + if (epnum == 0 || epnum == 1) { + dwc3_ep0_interrupt(dwc, event); + return; + } + + switch (event->endpoint_event) { + case DWC3_DEPEVT_XFERCOMPLETE: + dep->resource_index = 0; + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dev_dbg(dwc->dev, "%s is an Isochronous endpoint\n", + dep->name); + return; + } + + dwc3_endpoint_transfer_complete(dwc, dep, event, 1); + break; + case DWC3_DEPEVT_XFERINPROGRESS: + if (!usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dev_dbg(dwc->dev, "%s is not an Isochronous endpoint\n", + dep->name); + return; + } + + dwc3_endpoint_transfer_complete(dwc, dep, event, 0); + break; + case DWC3_DEPEVT_XFERNOTREADY: + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dwc3_gadget_start_isoc(dwc, dep, event); + } else { + int ret; + + dev_vdbg(dwc->dev, "%s: reason %s\n", + dep->name, event->status & + DEPEVT_STATUS_TRANSFER_ACTIVE + ? "Transfer Active" + : "Transfer Not Active"); + + ret = __dwc3_gadget_kick_transfer(dep, 0, 1); + if (!ret || ret == -EBUSY) + return; + + dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dep->name); + } + + break; + case DWC3_DEPEVT_STREAMEVT: + if (!usb_endpoint_xfer_bulk(dep->endpoint.desc)) { + dev_err(dwc->dev, "Stream event for non-Bulk %s\n", + dep->name); + return; + } + + switch (event->status) { + case DEPEVT_STREAMEVT_FOUND: + dev_vdbg(dwc->dev, "Stream %d found and started\n", + event->parameters); + + break; + case DEPEVT_STREAMEVT_NOTFOUND: + /* FALLTHROUGH */ + default: + dev_dbg(dwc->dev, "Couldn't find suitable stream\n"); + } + break; + case DWC3_DEPEVT_RXTXFIFOEVT: + dev_dbg(dwc->dev, "%s FIFO Overrun\n", dep->name); + break; + case DWC3_DEPEVT_EPCMDCMPLT: + dev_vdbg(dwc->dev, "Endpoint Command Complete\n"); + break; + } +} + +static void dwc3_disconnect_gadget(struct dwc3 *dwc) +{ + if (dwc->gadget_driver && dwc->gadget_driver->disconnect) { + spin_unlock(&dwc->lock); + dwc->gadget_driver->disconnect(&dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum) +{ + struct dwc3_ep *dep; + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret; + + dep = dwc->eps[epnum]; + + if (!dep->resource_index) + return; + + /* + * NOTICE: We are violating what the Databook says about the + * EndTransfer command. Ideally we would _always_ wait for the + * EndTransfer Command Completion IRQ, but that's causing too + * much trouble synchronizing between us and gadget driver. + * + * We have discussed this with the IP Provider and it was + * suggested to giveback all requests here, but give HW some + * extra time to synchronize with the interconnect. We're using + * an arbitraty 100us delay for that. + * + * Note also that a similar handling was tested by Synopsys + * (thanks a lot Paul) and nothing bad has come out of it. + * In short, what we're doing is: + * + * - Issue EndTransfer WITH CMDIOC bit set + * - Wait 100us + */ + + cmd = DWC3_DEPCMD_ENDTRANSFER; + cmd |= DWC3_DEPCMD_HIPRI_FORCERM | DWC3_DEPCMD_CMDIOC; + cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); + memset(¶ms, 0, sizeof(params)); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + WARN_ON_ONCE(ret); + dep->resource_index = 0; + dep->flags &= ~DWC3_EP_BUSY; + udelay(100); +} + +static void dwc3_stop_active_transfers(struct dwc3 *dwc) +{ + u32 epnum; + + for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) { + struct dwc3_ep *dep; + + dep = dwc->eps[epnum]; + if (!dep) + continue; + + if (!(dep->flags & DWC3_EP_ENABLED)) + continue; + + dwc3_remove_requests(dwc, dep); + } +} + +static void dwc3_clear_stall_all_ep(struct dwc3 *dwc) +{ + u32 epnum; + + for (epnum = 1; epnum < DWC3_ENDPOINTS_NUM; epnum++) { + struct dwc3_ep *dep; + struct dwc3_gadget_ep_cmd_params params; + int ret; + + dep = dwc->eps[epnum]; + if (!dep) + continue; + + if (!(dep->flags & DWC3_EP_STALL)) + continue; + + dep->flags &= ~DWC3_EP_STALL; + + memset(¶ms, 0, sizeof(params)); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_CLEARSTALL, ¶ms); + WARN_ON_ONCE(ret); + } +} + +static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) +{ + int reg; + + dev_vdbg(dwc->dev, "%s\n", __func__); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_INITU1ENA; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + reg &= ~DWC3_DCTL_INITU2ENA; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + dwc3_disconnect_gadget(dwc); + dwc->start_config_issued = false; + + dwc->gadget.speed = USB_SPEED_UNKNOWN; + dwc->setup_packet_pending = false; +} + +static void dwc3_gadget_usb3_phy_suspend(struct dwc3 *dwc, int suspend) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); + + if (suspend) + reg |= DWC3_GUSB3PIPECTL_SUSPHY; + else + reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; + + dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); +} + +static void dwc3_gadget_usb2_phy_suspend(struct dwc3 *dwc, int suspend) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + + if (suspend) + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + else + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); +} + +static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) +{ + u32 reg; + + dev_vdbg(dwc->dev, "%s\n", __func__); + + /* + * WORKAROUND: DWC3 revisions <1.88a have an issue which + * would cause a missing Disconnect Event if there's a + * pending Setup Packet in the FIFO. + * + * There's no suggested workaround on the official Bug + * report, which states that "unless the driver/application + * is doing any special handling of a disconnect event, + * there is no functional issue". + * + * Unfortunately, it turns out that we _do_ some special + * handling of a disconnect event, namely complete all + * pending transfers, notify gadget driver of the + * disconnection, and so on. + * + * Our suggested workaround is to follow the Disconnect + * Event steps here, instead, based on a setup_packet_pending + * flag. Such flag gets set whenever we have a XferNotReady + * event on EP0 and gets cleared on XferComplete for the + * same endpoint. + * + * Refers to: + * + * STAR#9000466709: RTL: Device : Disconnect event not + * generated if setup packet pending in FIFO + */ + if (dwc->revision < DWC3_REVISION_188A) { + if (dwc->setup_packet_pending) + dwc3_gadget_disconnect_interrupt(dwc); + } + + /* after reset -> Default State */ + usb_gadget_set_state(&dwc->gadget, USB_STATE_DEFAULT); + + /* Recent versions support automatic phy suspend and don't need this */ + if (dwc->revision < DWC3_REVISION_194A) { + /* Resume PHYs */ + dwc3_gadget_usb2_phy_suspend(dwc, false); + dwc3_gadget_usb3_phy_suspend(dwc, false); + } + + if (dwc->gadget.speed != USB_SPEED_UNKNOWN) + dwc3_disconnect_gadget(dwc); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_TSTCTRL_MASK; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + dwc->test_mode = false; + + dwc3_stop_active_transfers(dwc); + dwc3_clear_stall_all_ep(dwc); + dwc->start_config_issued = false; + + /* Reset device address to zero */ + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_DEVADDR_MASK); + dwc3_writel(dwc->regs, DWC3_DCFG, reg); +} + +static void dwc3_update_ram_clk_sel(struct dwc3 *dwc, u32 speed) +{ + u32 reg; + u32 usb30_clock = DWC3_GCTL_CLK_BUS; + + /* + * We change the clock only at SS but I dunno why I would want to do + * this. Maybe it becomes part of the power saving plan. + */ + + if (speed != DWC3_DSTS_SUPERSPEED) + return; + + /* + * RAMClkSel is reset to 0 after USB reset, so it must be reprogrammed + * each time on Connect Done. + */ + if (!usb30_clock) + return; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg |= DWC3_GCTL_RAMCLKSEL(usb30_clock); + dwc3_writel(dwc->regs, DWC3_GCTL, reg); +} + +static void dwc3_gadget_phy_suspend(struct dwc3 *dwc, u8 speed) +{ + switch (speed) { + case USB_SPEED_SUPER: + dwc3_gadget_usb2_phy_suspend(dwc, true); + break; + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + case USB_SPEED_LOW: + dwc3_gadget_usb3_phy_suspend(dwc, true); + break; + } +} + +static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret; + u32 reg; + u8 speed; + + dev_vdbg(dwc->dev, "%s\n", __func__); + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + speed = reg & DWC3_DSTS_CONNECTSPD; + dwc->speed = speed; + + dwc3_update_ram_clk_sel(dwc, speed); + + switch (speed) { + case DWC3_DCFG_SUPERSPEED: + /* + * WORKAROUND: DWC3 revisions <1.90a have an issue which + * would cause a missing USB3 Reset event. + * + * In such situations, we should force a USB3 Reset + * event by calling our dwc3_gadget_reset_interrupt() + * routine. + * + * Refers to: + * + * STAR#9000483510: RTL: SS : USB3 reset event may + * not be generated always when the link enters poll + */ + if (dwc->revision < DWC3_REVISION_190A) + dwc3_gadget_reset_interrupt(dwc); + + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + dwc->gadget.ep0->maxpacket = 512; + dwc->gadget.speed = USB_SPEED_SUPER; + break; + case DWC3_DCFG_HIGHSPEED: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + dwc->gadget.ep0->maxpacket = 64; + dwc->gadget.speed = USB_SPEED_HIGH; + break; + case DWC3_DCFG_FULLSPEED2: + case DWC3_DCFG_FULLSPEED1: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + dwc->gadget.ep0->maxpacket = 64; + dwc->gadget.speed = USB_SPEED_FULL; + break; + case DWC3_DCFG_LOWSPEED: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8); + dwc->gadget.ep0->maxpacket = 8; + dwc->gadget.speed = USB_SPEED_LOW; + break; + } + + /* Enable USB2 LPM Capability */ + + if ((dwc->revision > DWC3_REVISION_194A) + && (speed != DWC3_DCFG_SUPERSPEED)) { + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg |= DWC3_DCFG_LPM_CAP; + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN); + + /* + * TODO: This should be configurable. For now using + * maximum allowed HIRD threshold value of 0b1100 + */ + reg |= DWC3_DCTL_HIRD_THRES(12); + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } + + /* Recent versions support automatic phy suspend and don't need this */ + if (dwc->revision < DWC3_REVISION_194A) { + /* Suspend unneeded PHY */ + dwc3_gadget_phy_suspend(dwc, dwc->gadget.speed); + } + + dep = dwc->eps[0]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + return; + } + + dep = dwc->eps[1]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + return; + } + + /* + * Configure PHY via GUSB3PIPECTLn if required. + * + * Update GTXFIFOSIZn + * + * In both cases reset values should be sufficient. + */ +} + +static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc) +{ + dev_vdbg(dwc->dev, "%s\n", __func__); + + /* + * TODO take core out of low power mode when that's + * implemented. + */ + + dwc->gadget_driver->resume(&dwc->gadget); +} + +static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, + unsigned int evtinfo) +{ + enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK; + unsigned int pwropt; + + /* + * WORKAROUND: DWC3 < 2.50a have an issue when configured without + * Hibernation mode enabled which would show up when device detects + * host-initiated U3 exit. + * + * In that case, device will generate a Link State Change Interrupt + * from U3 to RESUME which is only necessary if Hibernation is + * configured in. + * + * There are no functional changes due to such spurious event and we + * just need to ignore it. + * + * Refers to: + * + * STAR#9000570034 RTL: SS Resume event generated in non-Hibernation + * operational mode + */ + pwropt = DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1); + if ((dwc->revision < DWC3_REVISION_250A) && + (pwropt != DWC3_GHWPARAMS1_EN_PWROPT_HIB)) { + if ((dwc->link_state == DWC3_LINK_STATE_U3) && + (next == DWC3_LINK_STATE_RESUME)) { + dev_vdbg(dwc->dev, "ignoring transition U3 -> Resume\n"); + return; + } + } + + /* + * WORKAROUND: DWC3 Revisions <1.83a have an issue which, depending + * on the link partner, the USB session might do multiple entry/exit + * of low power states before a transfer takes place. + * + * Due to this problem, we might experience lower throughput. The + * suggested workaround is to disable DCTL[12:9] bits if we're + * transitioning from U1/U2 to U0 and enable those bits again + * after a transfer completes and there are no pending transfers + * on any of the enabled endpoints. + * + * This is the first half of that workaround. + * + * Refers to: + * + * STAR#9000446952: RTL: Device SS : if U1/U2 ->U0 takes >128us + * core send LGO_Ux entering U0 + */ + if (dwc->revision < DWC3_REVISION_183A) { + if (next == DWC3_LINK_STATE_U0) { + u32 u1u2; + u32 reg; + + switch (dwc->link_state) { + case DWC3_LINK_STATE_U1: + case DWC3_LINK_STATE_U2: + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + u1u2 = reg & (DWC3_DCTL_INITU2ENA + | DWC3_DCTL_ACCEPTU2ENA + | DWC3_DCTL_INITU1ENA + | DWC3_DCTL_ACCEPTU1ENA); + + if (!dwc->u1u2) + dwc->u1u2 = reg & u1u2; + + reg &= ~u1u2; + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + break; + default: + /* do nothing */ + break; + } + } + } + + dwc->link_state = next; + + dev_vdbg(dwc->dev, "%s link %d\n", __func__, dwc->link_state); +} + +static void dwc3_gadget_interrupt(struct dwc3 *dwc, + const struct dwc3_event_devt *event) +{ + switch (event->type) { + case DWC3_DEVICE_EVENT_DISCONNECT: + dwc3_gadget_disconnect_interrupt(dwc); + break; + case DWC3_DEVICE_EVENT_RESET: + dwc3_gadget_reset_interrupt(dwc); + break; + case DWC3_DEVICE_EVENT_CONNECT_DONE: + dwc3_gadget_conndone_interrupt(dwc); + break; + case DWC3_DEVICE_EVENT_WAKEUP: + dwc3_gadget_wakeup_interrupt(dwc); + break; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + dwc3_gadget_linksts_change_interrupt(dwc, event->event_info); + break; + case DWC3_DEVICE_EVENT_EOPF: + dev_vdbg(dwc->dev, "End of Periodic Frame\n"); + break; + case DWC3_DEVICE_EVENT_SOF: + dev_vdbg(dwc->dev, "Start of Periodic Frame\n"); + break; + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + dev_vdbg(dwc->dev, "Erratic Error\n"); + break; + case DWC3_DEVICE_EVENT_CMD_CMPL: + dev_vdbg(dwc->dev, "Command Complete\n"); + break; + case DWC3_DEVICE_EVENT_OVERFLOW: + dev_vdbg(dwc->dev, "Overflow\n"); + break; + default: + dev_dbg(dwc->dev, "UNKNOWN IRQ %d\n", event->type); + } +} + +static void dwc3_process_event_entry(struct dwc3 *dwc, + const union dwc3_event *event) +{ + /* Endpoint IRQ, handle it and return early */ + if (event->type.is_devspec == 0) { + /* depevt */ + return dwc3_endpoint_interrupt(dwc, &event->depevt); + } + + switch (event->type.type) { + case DWC3_EVENT_TYPE_DEV: + dwc3_gadget_interrupt(dwc, &event->devt); + break; + /* REVISIT what to do with Carkit and I2C events ? */ + default: + dev_err(dwc->dev, "UNKNOWN IRQ type %d\n", event->raw); + } +} + +static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + int i; + + spin_lock_irqsave(&dwc->lock, flags); + + for (i = 0; i < dwc->num_event_buffers; i++) { + struct dwc3_event_buffer *evt; + int left; + + evt = dwc->ev_buffs[i]; + left = evt->count; + + if (!(evt->flags & DWC3_EVENT_PENDING)) + continue; + + while (left > 0) { + union dwc3_event event; + + event.raw = *(u32 *) (evt->buf + evt->lpos); + + dwc3_process_event_entry(dwc, &event); + + /* + * FIXME we wrap around correctly to the next entry as + * almost all entries are 4 bytes in size. There is one + * entry which has 12 bytes which is a regular entry + * followed by 8 bytes data. ATM I don't know how + * things are organized if we get next to the a + * boundary so I worry about that once we try to handle + * that. + */ + evt->lpos = (evt->lpos + 4) % DWC3_EVENT_BUFFERS_SIZE; + left -= 4; + + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(i), 4); + } + + evt->count = 0; + evt->flags &= ~DWC3_EVENT_PENDING; + ret = IRQ_HANDLED; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static irqreturn_t dwc3_process_event_buf(struct dwc3 *dwc, u32 buf) +{ + struct dwc3_event_buffer *evt; + u32 count; + + evt = dwc->ev_buffs[buf]; + + count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(buf)); + count &= DWC3_GEVNTCOUNT_MASK; + if (!count) + return IRQ_NONE; + + evt->count = count; + evt->flags |= DWC3_EVENT_PENDING; + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t dwc3_interrupt(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + int i; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&dwc->lock); + + for (i = 0; i < dwc->num_event_buffers; i++) { + irqreturn_t status; + + status = dwc3_process_event_buf(dwc, i); + if (status == IRQ_WAKE_THREAD) + ret = status; + } + + spin_unlock(&dwc->lock); + + return ret; +} + +/** + * dwc3_gadget_init - Initializes gadget related registers + * @dwc: pointer to our controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +int dwc3_gadget_init(struct dwc3 *dwc) +{ + u32 reg; + int ret; + + dwc->ctrl_req = dma_alloc_coherent(dwc->dev, sizeof(*dwc->ctrl_req), + &dwc->ctrl_req_addr, GFP_KERNEL); + if (!dwc->ctrl_req) { + dev_err(dwc->dev, "failed to allocate ctrl request\n"); + ret = -ENOMEM; + goto err0; + } + + dwc->ep0_trb = dma_alloc_coherent(dwc->dev, sizeof(*dwc->ep0_trb), + &dwc->ep0_trb_addr, GFP_KERNEL); + if (!dwc->ep0_trb) { + dev_err(dwc->dev, "failed to allocate ep0 trb\n"); + ret = -ENOMEM; + goto err1; + } + + dwc->setup_buf = kzalloc(DWC3_EP0_BOUNCE_SIZE, GFP_KERNEL); + if (!dwc->setup_buf) { + dev_err(dwc->dev, "failed to allocate setup buffer\n"); + ret = -ENOMEM; + goto err2; + } + + dwc->ep0_bounce = dma_alloc_coherent(dwc->dev, + DWC3_EP0_BOUNCE_SIZE, &dwc->ep0_bounce_addr, + GFP_KERNEL); + if (!dwc->ep0_bounce) { + dev_err(dwc->dev, "failed to allocate ep0 bounce buffer\n"); + ret = -ENOMEM; + goto err3; + } + + dwc->gadget.ops = &dwc3_gadget_ops; + dwc->gadget.max_speed = USB_SPEED_SUPER; + dwc->gadget.speed = USB_SPEED_UNKNOWN; + dwc->gadget.sg_supported = true; + dwc->gadget.name = "dwc3-gadget"; + + /* + * REVISIT: Here we should clear all pending IRQs to be + * sure we're starting from a well known location. + */ + + ret = dwc3_gadget_init_endpoints(dwc); + if (ret) + goto err4; + + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg |= DWC3_DCFG_LPM_CAP; + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + /* Enable USB2 LPM and automatic phy suspend only on recent versions */ + if (dwc->revision >= DWC3_REVISION_194A) { + dwc3_gadget_usb2_phy_suspend(dwc, false); + dwc3_gadget_usb3_phy_suspend(dwc, false); + } + + ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); + if (ret) { + dev_err(dwc->dev, "failed to register udc\n"); + goto err5; + } + + return 0; + +err5: + dwc3_gadget_free_endpoints(dwc); + +err4: + dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE, + dwc->ep0_bounce, dwc->ep0_bounce_addr); + +err3: + kfree(dwc->setup_buf); + +err2: + dma_free_coherent(dwc->dev, sizeof(*dwc->ep0_trb), + dwc->ep0_trb, dwc->ep0_trb_addr); + +err1: + dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req), + dwc->ctrl_req, dwc->ctrl_req_addr); + +err0: + return ret; +} + +/* -------------------------------------------------------------------------- */ + +void dwc3_gadget_exit(struct dwc3 *dwc) +{ + usb_del_gadget_udc(&dwc->gadget); + + dwc3_gadget_free_endpoints(dwc); + + dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE, + dwc->ep0_bounce, dwc->ep0_bounce_addr); + + kfree(dwc->setup_buf); + + dma_free_coherent(dwc->dev, sizeof(*dwc->ep0_trb), + dwc->ep0_trb, dwc->ep0_trb_addr); + + dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req), + dwc->ctrl_req, dwc->ctrl_req_addr); +} + +int dwc3_gadget_prepare(struct dwc3 *dwc) +{ + if (dwc->pullups_connected) + dwc3_gadget_disable_irq(dwc); + + return 0; +} + +void dwc3_gadget_complete(struct dwc3 *dwc) +{ + if (dwc->pullups_connected) { + dwc3_gadget_enable_irq(dwc); + dwc3_gadget_run_stop(dwc, true); + } +} + +int dwc3_gadget_suspend(struct dwc3 *dwc) +{ + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); + + dwc->dcfg = dwc3_readl(dwc->regs, DWC3_DCFG); + + return 0; +} + +int dwc3_gadget_resume(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret; + + /* Start with SuperSpeed Default */ + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + + dep = dwc->eps[0]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + if (ret) + goto err0; + + dep = dwc->eps[1]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + if (ret) + goto err1; + + /* begin to receive SETUP packets */ + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); + + dwc3_writel(dwc->regs, DWC3_DCFG, dwc->dcfg); + + return 0; + +err1: + __dwc3_gadget_ep_disable(dwc->eps[0]); + +err0: + return ret; +} diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h new file mode 100644 index 00000000..b3f25c30 --- /dev/null +++ b/drivers/usb/dwc3/gadget.h @@ -0,0 +1,194 @@ +/** + * gadget.h - DesignWare USB3 DRD Gadget Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __DRIVERS_USB_DWC3_GADGET_H +#define __DRIVERS_USB_DWC3_GADGET_H + +#include +#include +#include "io.h" + +struct dwc3; +#define to_dwc3_ep(ep) (container_of(ep, struct dwc3_ep, endpoint)) +#define gadget_to_dwc(g) (container_of(g, struct dwc3, gadget)) + +/* DEPCFG parameter 1 */ +#define DWC3_DEPCFG_INT_NUM(n) ((n) << 0) +#define DWC3_DEPCFG_XFER_COMPLETE_EN (1 << 8) +#define DWC3_DEPCFG_XFER_IN_PROGRESS_EN (1 << 9) +#define DWC3_DEPCFG_XFER_NOT_READY_EN (1 << 10) +#define DWC3_DEPCFG_FIFO_ERROR_EN (1 << 11) +#define DWC3_DEPCFG_STREAM_EVENT_EN (1 << 13) +#define DWC3_DEPCFG_BINTERVAL_M1(n) ((n) << 16) +#define DWC3_DEPCFG_STREAM_CAPABLE (1 << 24) +#define DWC3_DEPCFG_EP_NUMBER(n) ((n) << 25) +#define DWC3_DEPCFG_BULK_BASED (1 << 30) +#define DWC3_DEPCFG_FIFO_BASED (1 << 31) + +/* DEPCFG parameter 0 */ +#define DWC3_DEPCFG_EP_TYPE(n) ((n) << 1) +#define DWC3_DEPCFG_MAX_PACKET_SIZE(n) ((n) << 3) +#define DWC3_DEPCFG_FIFO_NUMBER(n) ((n) << 17) +#define DWC3_DEPCFG_BURST_SIZE(n) ((n) << 22) +#define DWC3_DEPCFG_DATA_SEQ_NUM(n) ((n) << 26) +/* This applies for core versions earlier than 1.94a */ +#define DWC3_DEPCFG_IGN_SEQ_NUM (1 << 31) +/* These apply for core versions 1.94a and later */ +#define DWC3_DEPCFG_ACTION_INIT (0 << 30) +#define DWC3_DEPCFG_ACTION_RESTORE (1 << 30) +#define DWC3_DEPCFG_ACTION_MODIFY (2 << 30) + +/* DEPXFERCFG parameter 0 */ +#define DWC3_DEPXFERCFG_NUM_XFER_RES(n) ((n) & 0xffff) + +struct dwc3_gadget_ep_cmd_params { + u32 param2; + u32 param1; + u32 param0; +}; + +/* -------------------------------------------------------------------------- */ + +#define to_dwc3_request(r) (container_of(r, struct dwc3_request, request)) + +static inline struct dwc3_request *next_request(struct list_head *list) +{ + if (list_empty(list)) + return NULL; + + return list_first_entry(list, struct dwc3_request, list); +} + +static inline void dwc3_gadget_move_request_queued(struct dwc3_request *req) +{ + struct dwc3_ep *dep = req->dep; + + req->queued = true; + list_move_tail(&req->list, &dep->req_queued); +} + +void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, + int status); + +int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode); +int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); + +void dwc3_ep0_interrupt(struct dwc3 *dwc, + const struct dwc3_event_depevt *event); +void dwc3_ep0_out_start(struct dwc3 *dwc); +int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value); +int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags); +int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol); +int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, + unsigned cmd, struct dwc3_gadget_ep_cmd_params *params); +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param); + +/** + * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW + * @dwc: DesignWare USB3 Pointer + * @number: DWC endpoint number + * + * Caller should take care of locking + */ +static inline u32 dwc3_gadget_ep_get_transfer_index(struct dwc3 *dwc, u8 number) +{ + u32 res_id; + + res_id = dwc3_readl(dwc->regs, DWC3_DEPCMD(number)); + + return DWC3_DEPCMD_GET_RSC_IDX(res_id); +} + +/** + * dwc3_gadget_event_string - returns event name + * @event: the event code + */ +static inline const char *dwc3_gadget_event_string(u8 event) +{ + switch (event) { + case DWC3_DEVICE_EVENT_DISCONNECT: + return "Disconnect"; + case DWC3_DEVICE_EVENT_RESET: + return "Reset"; + case DWC3_DEVICE_EVENT_CONNECT_DONE: + return "Connection Done"; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + return "Link Status Change"; + case DWC3_DEVICE_EVENT_WAKEUP: + return "WakeUp"; + case DWC3_DEVICE_EVENT_EOPF: + return "End-Of-Frame"; + case DWC3_DEVICE_EVENT_SOF: + return "Start-Of-Frame"; + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + return "Erratic Error"; + case DWC3_DEVICE_EVENT_CMD_CMPL: + return "Command Complete"; + case DWC3_DEVICE_EVENT_OVERFLOW: + return "Overflow"; + } + + return "UNKNOWN"; +} + +/** + * dwc3_ep_event_string - returns event name + * @event: then event code + */ +static inline const char *dwc3_ep_event_string(u8 event) +{ + switch (event) { + case DWC3_DEPEVT_XFERCOMPLETE: + return "Transfer Complete"; + case DWC3_DEPEVT_XFERINPROGRESS: + return "Transfer In-Progress"; + case DWC3_DEPEVT_XFERNOTREADY: + return "Transfer Not Ready"; + case DWC3_DEPEVT_RXTXFIFOEVT: + return "FIFO"; + case DWC3_DEPEVT_STREAMEVT: + return "Stream"; + case DWC3_DEPEVT_EPCMDCMPLT: + return "Endpoint Command Complete"; + } + + return "UNKNOWN"; +} + +#endif /* __DRIVERS_USB_DWC3_GADGET_H */ diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c new file mode 100644 index 00000000..0fa1846e --- /dev/null +++ b/drivers/usb/dwc3/host.c @@ -0,0 +1,87 @@ +/** + * host.c - DesignWare USB3 DRD Controller Host Glue + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "core.h" + +int dwc3_host_init(struct dwc3 *dwc) +{ + struct platform_device *xhci; + int ret; + + xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); + if (!xhci) { + dev_err(dwc->dev, "couldn't allocate xHCI device\n"); + ret = -ENOMEM; + goto err0; + } + + dma_set_coherent_mask(&xhci->dev, dwc->dev->coherent_dma_mask); + + xhci->dev.parent = dwc->dev; + xhci->dev.dma_mask = dwc->dev->dma_mask; + xhci->dev.dma_parms = dwc->dev->dma_parms; + + dwc->xhci = xhci; + + ret = platform_device_add_resources(xhci, dwc->xhci_resources, + DWC3_XHCI_RESOURCES_NUM); + if (ret) { + dev_err(dwc->dev, "couldn't add resources to xHCI device\n"); + goto err1; + } + + ret = platform_device_add(xhci); + if (ret) { + dev_err(dwc->dev, "failed to register xHCI device\n"); + goto err1; + } + + return 0; + +err1: + platform_device_put(xhci); + +err0: + return ret; +} + +void dwc3_host_exit(struct dwc3 *dwc) +{ + platform_device_unregister(dwc->xhci); +} diff --git a/drivers/usb/dwc3/io.h b/drivers/usb/dwc3/io.h new file mode 100644 index 00000000..a50f76b9 --- /dev/null +++ b/drivers/usb/dwc3/io.h @@ -0,0 +1,66 @@ +/** + * io.h - DesignWare USB3 DRD IO Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __DRIVERS_USB_DWC3_IO_H +#define __DRIVERS_USB_DWC3_IO_H + +#include + +#include "core.h" + +static inline u32 dwc3_readl(void __iomem *base, u32 offset) +{ + /* + * We requested the mem region starting from the Globals address + * space, see dwc3_probe in core.c. + * However, the offsets are given starting from xHCI address space. + */ + return readl(base + (offset - DWC3_GLOBALS_REGS_START)); +} + +static inline void dwc3_writel(void __iomem *base, u32 offset, u32 value) +{ + /* + * We requested the mem region starting from the Globals address + * space, see dwc3_probe in core.c. + * However, the offsets are given starting from xHCI address space. + */ + writel(value, base + (offset - DWC3_GLOBALS_REGS_START)); +} + +#endif /* __DRIVERS_USB_DWC3_IO_H */ diff --git a/drivers/usb/early/Makefile b/drivers/usb/early/Makefile new file mode 100644 index 00000000..24bbe519 --- /dev/null +++ b/drivers/usb/early/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for early USB devices +# + +obj-$(CONFIG_EARLY_PRINTK_DBGP) += ehci-dbgp.o diff --git a/drivers/usb/early/ehci-dbgp.c b/drivers/usb/early/ehci-dbgp.c new file mode 100644 index 00000000..5e29ddeb --- /dev/null +++ b/drivers/usb/early/ehci-dbgp.c @@ -0,0 +1,1101 @@ +/* + * Standalone EHCI usb debug driver + * + * Originally written by: + * Eric W. Biederman" and + * Yinghai Lu + * + * Changes for early/late printk and HW errata: + * Jason Wessel + * Copyright (C) 2009 Wind River Systems, Inc. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The code here is intended to talk directly to the EHCI debug port + * and does not require that you have any kind of USB host controller + * drivers or USB device drivers compiled into the kernel. + * + * If you make a change to anything in here, the following test cases + * need to pass where a USB debug device works in the following + * configurations. + * + * 1. boot args: earlyprintk=dbgp + * o kernel compiled with # CONFIG_USB_EHCI_HCD is not set + * o kernel compiled with CONFIG_USB_EHCI_HCD=y + * 2. boot args: earlyprintk=dbgp,keep + * o kernel compiled with # CONFIG_USB_EHCI_HCD is not set + * o kernel compiled with CONFIG_USB_EHCI_HCD=y + * 3. boot args: earlyprintk=dbgp console=ttyUSB0 + * o kernel has CONFIG_USB_EHCI_HCD=y and + * CONFIG_USB_SERIAL_DEBUG=y + * 4. boot args: earlyprintk=vga,dbgp + * o kernel compiled with # CONFIG_USB_EHCI_HCD is not set + * o kernel compiled with CONFIG_USB_EHCI_HCD=y + * + * For the 4th configuration you can turn on or off the DBGP_DEBUG + * such that you can debug the dbgp device's driver code. + */ + +static int dbgp_phys_port = 1; + +static struct ehci_caps __iomem *ehci_caps; +static struct ehci_regs __iomem *ehci_regs; +static struct ehci_dbg_port __iomem *ehci_debug; +static int dbgp_not_safe; /* Cannot use debug device during ehci reset */ +static unsigned int dbgp_endpoint_out; +static unsigned int dbgp_endpoint_in; + +struct ehci_dev { + u32 bus; + u32 slot; + u32 func; +}; + +static struct ehci_dev ehci_dev; + +#define USB_DEBUG_DEVNUM 127 + +#ifdef DBGP_DEBUG +#define dbgp_printk printk +static void dbgp_ehci_status(char *str) +{ + if (!ehci_debug) + return; + dbgp_printk("dbgp: %s\n", str); + dbgp_printk(" Debug control: %08x", readl(&ehci_debug->control)); + dbgp_printk(" ehci cmd : %08x", readl(&ehci_regs->command)); + dbgp_printk(" ehci conf flg: %08x\n", + readl(&ehci_regs->configured_flag)); + dbgp_printk(" ehci status : %08x", readl(&ehci_regs->status)); + dbgp_printk(" ehci portsc : %08x\n", + readl(&ehci_regs->port_status[dbgp_phys_port - 1])); +} +#else +static inline void dbgp_ehci_status(char *str) { } +static inline void dbgp_printk(const char *fmt, ...) { } +#endif + +static inline u32 dbgp_len_update(u32 x, u32 len) +{ + return (x & ~0x0f) | (len & 0x0f); +} + +#ifdef CONFIG_KGDB +static struct kgdb_io kgdbdbgp_io_ops; +#define dbgp_kgdb_mode (dbg_io_ops == &kgdbdbgp_io_ops) +#else +#define dbgp_kgdb_mode (0) +#endif + +/* Local version of HC_LENGTH macro as ehci struct is not available here */ +#define EARLY_HC_LENGTH(p) (0x00ff & (p)) /* bits 7 : 0 */ + +/* + * USB Packet IDs (PIDs) + */ + +/* token */ +#define USB_PID_OUT 0xe1 +#define USB_PID_IN 0x69 +#define USB_PID_SOF 0xa5 +#define USB_PID_SETUP 0x2d +/* handshake */ +#define USB_PID_ACK 0xd2 +#define USB_PID_NAK 0x5a +#define USB_PID_STALL 0x1e +#define USB_PID_NYET 0x96 +/* data */ +#define USB_PID_DATA0 0xc3 +#define USB_PID_DATA1 0x4b +#define USB_PID_DATA2 0x87 +#define USB_PID_MDATA 0x0f +/* Special */ +#define USB_PID_PREAMBLE 0x3c +#define USB_PID_ERR 0x3c +#define USB_PID_SPLIT 0x78 +#define USB_PID_PING 0xb4 +#define USB_PID_UNDEF_0 0xf0 + +#define USB_PID_DATA_TOGGLE 0x88 +#define DBGP_CLAIM (DBGP_OWNER | DBGP_ENABLED | DBGP_INUSE) + +#define PCI_CAP_ID_EHCI_DEBUG 0xa + +#define HUB_ROOT_RESET_TIME 50 /* times are in msec */ +#define HUB_SHORT_RESET_TIME 10 +#define HUB_LONG_RESET_TIME 200 +#define HUB_RESET_TIMEOUT 500 + +#define DBGP_MAX_PACKET 8 +#define DBGP_TIMEOUT (250 * 1000) +#define DBGP_LOOPS 1000 + +static inline u32 dbgp_pid_write_update(u32 x, u32 tok) +{ + static int data0 = USB_PID_DATA1; + data0 ^= USB_PID_DATA_TOGGLE; + return (x & 0xffff0000) | (data0 << 8) | (tok & 0xff); +} + +static inline u32 dbgp_pid_read_update(u32 x, u32 tok) +{ + return (x & 0xffff0000) | (USB_PID_DATA0 << 8) | (tok & 0xff); +} + +static int dbgp_wait_until_complete(void) +{ + u32 ctrl; + int loop = DBGP_TIMEOUT; + + do { + ctrl = readl(&ehci_debug->control); + /* Stop when the transaction is finished */ + if (ctrl & DBGP_DONE) + break; + udelay(1); + } while (--loop > 0); + + if (!loop) + return -DBGP_TIMEOUT; + + /* + * Now that we have observed the completed transaction, + * clear the done bit. + */ + writel(ctrl | DBGP_DONE, &ehci_debug->control); + return (ctrl & DBGP_ERROR) ? -DBGP_ERRCODE(ctrl) : DBGP_LEN(ctrl); +} + +static inline void dbgp_mdelay(int ms) +{ + int i; + + while (ms--) { + for (i = 0; i < 1000; i++) + outb(0x1, 0x80); + } +} + +static void dbgp_breath(void) +{ + /* Sleep to give the debug port a chance to breathe */ +} + +static int dbgp_wait_until_done(unsigned ctrl, int loop) +{ + u32 pids, lpid; + int ret; + +retry: + writel(ctrl | DBGP_GO, &ehci_debug->control); + ret = dbgp_wait_until_complete(); + pids = readl(&ehci_debug->pids); + lpid = DBGP_PID_GET(pids); + + if (ret < 0) { + /* A -DBGP_TIMEOUT failure here means the device has + * failed, perhaps because it was unplugged, in which + * case we do not want to hang the system so the dbgp + * will be marked as unsafe to use. EHCI reset is the + * only way to recover if you unplug the dbgp device. + */ + if (ret == -DBGP_TIMEOUT && !dbgp_not_safe) + dbgp_not_safe = 1; + if (ret == -DBGP_ERR_BAD && --loop > 0) + goto retry; + return ret; + } + + /* + * If the port is getting full or it has dropped data + * start pacing ourselves, not necessary but it's friendly. + */ + if ((lpid == USB_PID_NAK) || (lpid == USB_PID_NYET)) + dbgp_breath(); + + /* If I get a NACK reissue the transmission */ + if (lpid == USB_PID_NAK) { + if (--loop > 0) + goto retry; + } + + return ret; +} + +static inline void dbgp_set_data(const void *buf, int size) +{ + const unsigned char *bytes = buf; + u32 lo, hi; + int i; + + lo = hi = 0; + for (i = 0; i < 4 && i < size; i++) + lo |= bytes[i] << (8*i); + for (; i < 8 && i < size; i++) + hi |= bytes[i] << (8*(i - 4)); + writel(lo, &ehci_debug->data03); + writel(hi, &ehci_debug->data47); +} + +static inline void dbgp_get_data(void *buf, int size) +{ + unsigned char *bytes = buf; + u32 lo, hi; + int i; + + lo = readl(&ehci_debug->data03); + hi = readl(&ehci_debug->data47); + for (i = 0; i < 4 && i < size; i++) + bytes[i] = (lo >> (8*i)) & 0xff; + for (; i < 8 && i < size; i++) + bytes[i] = (hi >> (8*(i - 4))) & 0xff; +} + +static int dbgp_bulk_write(unsigned devnum, unsigned endpoint, + const char *bytes, int size) +{ + int ret; + u32 addr; + u32 pids, ctrl; + + if (size > DBGP_MAX_PACKET) + return -1; + + addr = DBGP_EPADDR(devnum, endpoint); + + pids = readl(&ehci_debug->pids); + pids = dbgp_pid_write_update(pids, USB_PID_OUT); + + ctrl = readl(&ehci_debug->control); + ctrl = dbgp_len_update(ctrl, size); + ctrl |= DBGP_OUT; + ctrl |= DBGP_GO; + + dbgp_set_data(bytes, size); + writel(addr, &ehci_debug->address); + writel(pids, &ehci_debug->pids); + ret = dbgp_wait_until_done(ctrl, DBGP_LOOPS); + + return ret; +} + +static int dbgp_bulk_read(unsigned devnum, unsigned endpoint, void *data, + int size, int loops) +{ + u32 pids, addr, ctrl; + int ret; + + if (size > DBGP_MAX_PACKET) + return -1; + + addr = DBGP_EPADDR(devnum, endpoint); + + pids = readl(&ehci_debug->pids); + pids = dbgp_pid_read_update(pids, USB_PID_IN); + + ctrl = readl(&ehci_debug->control); + ctrl = dbgp_len_update(ctrl, size); + ctrl &= ~DBGP_OUT; + ctrl |= DBGP_GO; + + writel(addr, &ehci_debug->address); + writel(pids, &ehci_debug->pids); + ret = dbgp_wait_until_done(ctrl, loops); + if (ret < 0) + return ret; + + if (size > ret) + size = ret; + dbgp_get_data(data, size); + return ret; +} + +static int dbgp_control_msg(unsigned devnum, int requesttype, + int request, int value, int index, void *data, int size) +{ + u32 pids, addr, ctrl; + struct usb_ctrlrequest req; + int read; + int ret; + + read = (requesttype & USB_DIR_IN) != 0; + if (size > (read ? DBGP_MAX_PACKET : 0)) + return -1; + + /* Compute the control message */ + req.bRequestType = requesttype; + req.bRequest = request; + req.wValue = cpu_to_le16(value); + req.wIndex = cpu_to_le16(index); + req.wLength = cpu_to_le16(size); + + pids = DBGP_PID_SET(USB_PID_DATA0, USB_PID_SETUP); + addr = DBGP_EPADDR(devnum, 0); + + ctrl = readl(&ehci_debug->control); + ctrl = dbgp_len_update(ctrl, sizeof(req)); + ctrl |= DBGP_OUT; + ctrl |= DBGP_GO; + + /* Send the setup message */ + dbgp_set_data(&req, sizeof(req)); + writel(addr, &ehci_debug->address); + writel(pids, &ehci_debug->pids); + ret = dbgp_wait_until_done(ctrl, DBGP_LOOPS); + if (ret < 0) + return ret; + + /* Read the result */ + return dbgp_bulk_read(devnum, 0, data, size, DBGP_LOOPS); +} + +/* Find a PCI capability */ +static u32 __init find_cap(u32 num, u32 slot, u32 func, int cap) +{ + u8 pos; + int bytes; + + if (!(read_pci_config_16(num, slot, func, PCI_STATUS) & + PCI_STATUS_CAP_LIST)) + return 0; + + pos = read_pci_config_byte(num, slot, func, PCI_CAPABILITY_LIST); + for (bytes = 0; bytes < 48 && pos >= 0x40; bytes++) { + u8 id; + + pos &= ~3; + id = read_pci_config_byte(num, slot, func, pos+PCI_CAP_LIST_ID); + if (id == 0xff) + break; + if (id == cap) + return pos; + + pos = read_pci_config_byte(num, slot, func, + pos+PCI_CAP_LIST_NEXT); + } + return 0; +} + +static u32 __init __find_dbgp(u32 bus, u32 slot, u32 func) +{ + u32 class; + + class = read_pci_config(bus, slot, func, PCI_CLASS_REVISION); + if ((class >> 8) != PCI_CLASS_SERIAL_USB_EHCI) + return 0; + + return find_cap(bus, slot, func, PCI_CAP_ID_EHCI_DEBUG); +} + +static u32 __init find_dbgp(int ehci_num, u32 *rbus, u32 *rslot, u32 *rfunc) +{ + u32 bus, slot, func; + + for (bus = 0; bus < 256; bus++) { + for (slot = 0; slot < 32; slot++) { + for (func = 0; func < 8; func++) { + unsigned cap; + + cap = __find_dbgp(bus, slot, func); + + if (!cap) + continue; + if (ehci_num-- != 0) + continue; + *rbus = bus; + *rslot = slot; + *rfunc = func; + return cap; + } + } + } + return 0; +} + +static int dbgp_ehci_startup(void) +{ + u32 ctrl, cmd, status; + int loop; + + /* Claim ownership, but do not enable yet */ + ctrl = readl(&ehci_debug->control); + ctrl |= DBGP_OWNER; + ctrl &= ~(DBGP_ENABLED | DBGP_INUSE); + writel(ctrl, &ehci_debug->control); + udelay(1); + + dbgp_ehci_status("EHCI startup"); + /* Start the ehci running */ + cmd = readl(&ehci_regs->command); + cmd &= ~(CMD_LRESET | CMD_IAAD | CMD_PSE | CMD_ASE | CMD_RESET); + cmd |= CMD_RUN; + writel(cmd, &ehci_regs->command); + + /* Ensure everything is routed to the EHCI */ + writel(FLAG_CF, &ehci_regs->configured_flag); + + /* Wait until the controller is no longer halted */ + loop = 1000; + do { + status = readl(&ehci_regs->status); + if (!(status & STS_HALT)) + break; + udelay(1); + } while (--loop > 0); + + if (!loop) { + dbgp_printk("ehci can not be started\n"); + return -ENODEV; + } + dbgp_printk("ehci started\n"); + return 0; +} + +static int dbgp_ehci_controller_reset(void) +{ + int loop = 250 * 1000; + u32 cmd; + + /* Reset the EHCI controller */ + cmd = readl(&ehci_regs->command); + cmd |= CMD_RESET; + writel(cmd, &ehci_regs->command); + do { + cmd = readl(&ehci_regs->command); + } while ((cmd & CMD_RESET) && (--loop > 0)); + + if (!loop) { + dbgp_printk("can not reset ehci\n"); + return -1; + } + dbgp_ehci_status("ehci reset done"); + return 0; +} +static int ehci_wait_for_port(int port); +/* Return 0 on success + * Return -ENODEV for any general failure + * Return -EIO if wait for port fails + */ +static int _dbgp_external_startup(void) +{ + int devnum; + struct usb_debug_descriptor dbgp_desc; + int ret; + u32 ctrl, portsc, cmd; + int dbg_port = dbgp_phys_port; + int tries = 3; + int reset_port_tries = 1; + int try_hard_once = 1; + +try_port_reset_again: + ret = dbgp_ehci_startup(); + if (ret) + return ret; + + /* Wait for a device to show up in the debug port */ + ret = ehci_wait_for_port(dbg_port); + if (ret < 0) { + portsc = readl(&ehci_regs->port_status[dbg_port - 1]); + if (!(portsc & PORT_CONNECT) && try_hard_once) { + /* Last ditch effort to try to force enable + * the debug device by using the packet test + * ehci command to try and wake it up. */ + try_hard_once = 0; + cmd = readl(&ehci_regs->command); + cmd &= ~CMD_RUN; + writel(cmd, &ehci_regs->command); + portsc = readl(&ehci_regs->port_status[dbg_port - 1]); + portsc |= PORT_TEST_PKT; + writel(portsc, &ehci_regs->port_status[dbg_port - 1]); + dbgp_ehci_status("Trying to force debug port online"); + mdelay(50); + dbgp_ehci_controller_reset(); + goto try_port_reset_again; + } else if (reset_port_tries--) { + goto try_port_reset_again; + } + dbgp_printk("No device found in debug port\n"); + return -EIO; + } + dbgp_ehci_status("wait for port done"); + + /* Enable the debug port */ + ctrl = readl(&ehci_debug->control); + ctrl |= DBGP_CLAIM; + writel(ctrl, &ehci_debug->control); + ctrl = readl(&ehci_debug->control); + if ((ctrl & DBGP_CLAIM) != DBGP_CLAIM) { + dbgp_printk("No device in debug port\n"); + writel(ctrl & ~DBGP_CLAIM, &ehci_debug->control); + return -ENODEV; + } + dbgp_ehci_status("debug ported enabled"); + + /* Completely transfer the debug device to the debug controller */ + portsc = readl(&ehci_regs->port_status[dbg_port - 1]); + portsc &= ~PORT_PE; + writel(portsc, &ehci_regs->port_status[dbg_port - 1]); + + dbgp_mdelay(100); + +try_again: + /* Find the debug device and make it device number 127 */ + for (devnum = 0; devnum <= 127; devnum++) { + ret = dbgp_control_msg(devnum, + USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_REQ_GET_DESCRIPTOR, (USB_DT_DEBUG << 8), 0, + &dbgp_desc, sizeof(dbgp_desc)); + if (ret > 0) + break; + } + if (devnum > 127) { + dbgp_printk("Could not find attached debug device\n"); + goto err; + } + if (ret < 0) { + dbgp_printk("Attached device is not a debug device\n"); + goto err; + } + dbgp_endpoint_out = dbgp_desc.bDebugOutEndpoint; + dbgp_endpoint_in = dbgp_desc.bDebugInEndpoint; + + /* Move the device to 127 if it isn't already there */ + if (devnum != USB_DEBUG_DEVNUM) { + ret = dbgp_control_msg(devnum, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_REQ_SET_ADDRESS, USB_DEBUG_DEVNUM, 0, NULL, 0); + if (ret < 0) { + dbgp_printk("Could not move attached device to %d\n", + USB_DEBUG_DEVNUM); + goto err; + } + devnum = USB_DEBUG_DEVNUM; + dbgp_printk("debug device renamed to 127\n"); + } + + /* Enable the debug interface */ + ret = dbgp_control_msg(USB_DEBUG_DEVNUM, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_REQ_SET_FEATURE, USB_DEVICE_DEBUG_MODE, 0, NULL, 0); + if (ret < 0) { + dbgp_printk(" Could not enable the debug device\n"); + goto err; + } + dbgp_printk("debug interface enabled\n"); + /* Perform a small write to get the even/odd data state in sync + */ + ret = dbgp_bulk_write(USB_DEBUG_DEVNUM, dbgp_endpoint_out, " ", 1); + if (ret < 0) { + dbgp_printk("dbgp_bulk_write failed: %d\n", ret); + goto err; + } + dbgp_printk("small write done\n"); + dbgp_not_safe = 0; + + return 0; +err: + if (tries--) + goto try_again; + return -ENODEV; +} + +static int ehci_reset_port(int port) +{ + u32 portsc; + u32 delay_time, delay; + int loop; + + dbgp_ehci_status("reset port"); + /* Reset the usb debug port */ + portsc = readl(&ehci_regs->port_status[port - 1]); + portsc &= ~PORT_PE; + portsc |= PORT_RESET; + writel(portsc, &ehci_regs->port_status[port - 1]); + + delay = HUB_ROOT_RESET_TIME; + for (delay_time = 0; delay_time < HUB_RESET_TIMEOUT; + delay_time += delay) { + dbgp_mdelay(delay); + portsc = readl(&ehci_regs->port_status[port - 1]); + if (!(portsc & PORT_RESET)) + break; + } + if (portsc & PORT_RESET) { + /* force reset to complete */ + loop = 100 * 1000; + writel(portsc & ~(PORT_RWC_BITS | PORT_RESET), + &ehci_regs->port_status[port - 1]); + do { + udelay(1); + portsc = readl(&ehci_regs->port_status[port-1]); + } while ((portsc & PORT_RESET) && (--loop > 0)); + } + + /* Device went away? */ + if (!(portsc & PORT_CONNECT)) + return -ENOTCONN; + + /* bomb out completely if something weird happened */ + if ((portsc & PORT_CSC)) + return -EINVAL; + + /* If we've finished resetting, then break out of the loop */ + if (!(portsc & PORT_RESET) && (portsc & PORT_PE)) + return 0; + return -EBUSY; +} + +static int ehci_wait_for_port(int port) +{ + u32 status; + int ret, reps; + + for (reps = 0; reps < 300; reps++) { + status = readl(&ehci_regs->status); + if (status & STS_PCD) + break; + dbgp_mdelay(1); + } + ret = ehci_reset_port(port); + if (ret == 0) + return 0; + return -ENOTCONN; +} + +typedef void (*set_debug_port_t)(int port); + +static void __init default_set_debug_port(int port) +{ +} + +static set_debug_port_t __initdata set_debug_port = default_set_debug_port; + +static void __init nvidia_set_debug_port(int port) +{ + u32 dword; + dword = read_pci_config(ehci_dev.bus, ehci_dev.slot, ehci_dev.func, + 0x74); + dword &= ~(0x0f<<12); + dword |= ((port & 0x0f)<<12); + write_pci_config(ehci_dev.bus, ehci_dev.slot, ehci_dev.func, 0x74, + dword); + dbgp_printk("set debug port to %d\n", port); +} + +static void __init detect_set_debug_port(void) +{ + u32 vendorid; + + vendorid = read_pci_config(ehci_dev.bus, ehci_dev.slot, ehci_dev.func, + 0x00); + + if ((vendorid & 0xffff) == 0x10de) { + dbgp_printk("using nvidia set_debug_port\n"); + set_debug_port = nvidia_set_debug_port; + } +} + +/* The code in early_ehci_bios_handoff() is derived from the usb pci + * quirk initialization, but altered so as to use the early PCI + * routines. */ +#define EHCI_USBLEGSUP_BIOS (1 << 16) /* BIOS semaphore */ +#define EHCI_USBLEGCTLSTS 4 /* legacy control/status */ +static void __init early_ehci_bios_handoff(void) +{ + u32 hcc_params = readl(&ehci_caps->hcc_params); + int offset = (hcc_params >> 8) & 0xff; + u32 cap; + int msec; + + if (!offset) + return; + + cap = read_pci_config(ehci_dev.bus, ehci_dev.slot, + ehci_dev.func, offset); + dbgp_printk("dbgp: ehci BIOS state %08x\n", cap); + + if ((cap & 0xff) == 1 && (cap & EHCI_USBLEGSUP_BIOS)) { + dbgp_printk("dbgp: BIOS handoff\n"); + write_pci_config_byte(ehci_dev.bus, ehci_dev.slot, + ehci_dev.func, offset + 3, 1); + } + + /* if boot firmware now owns EHCI, spin till it hands it over. */ + msec = 1000; + while ((cap & EHCI_USBLEGSUP_BIOS) && (msec > 0)) { + mdelay(10); + msec -= 10; + cap = read_pci_config(ehci_dev.bus, ehci_dev.slot, + ehci_dev.func, offset); + } + + if (cap & EHCI_USBLEGSUP_BIOS) { + /* well, possibly buggy BIOS... try to shut it down, + * and hope nothing goes too wrong */ + dbgp_printk("dbgp: BIOS handoff failed: %08x\n", cap); + write_pci_config_byte(ehci_dev.bus, ehci_dev.slot, + ehci_dev.func, offset + 2, 0); + } + + /* just in case, always disable EHCI SMIs */ + write_pci_config_byte(ehci_dev.bus, ehci_dev.slot, ehci_dev.func, + offset + EHCI_USBLEGCTLSTS, 0); +} + +static int __init ehci_setup(void) +{ + u32 ctrl, portsc, hcs_params; + u32 debug_port, new_debug_port = 0, n_ports; + int ret, i; + int port_map_tried; + int playtimes = 3; + + early_ehci_bios_handoff(); + +try_next_time: + port_map_tried = 0; + +try_next_port: + + hcs_params = readl(&ehci_caps->hcs_params); + debug_port = HCS_DEBUG_PORT(hcs_params); + dbgp_phys_port = debug_port; + n_ports = HCS_N_PORTS(hcs_params); + + dbgp_printk("debug_port: %d\n", debug_port); + dbgp_printk("n_ports: %d\n", n_ports); + dbgp_ehci_status(""); + + for (i = 1; i <= n_ports; i++) { + portsc = readl(&ehci_regs->port_status[i-1]); + dbgp_printk("portstatus%d: %08x\n", i, portsc); + } + + if (port_map_tried && (new_debug_port != debug_port)) { + if (--playtimes) { + set_debug_port(new_debug_port); + goto try_next_time; + } + return -1; + } + + /* Only reset the controller if it is not already in the + * configured state */ + if (!(readl(&ehci_regs->configured_flag) & FLAG_CF)) { + if (dbgp_ehci_controller_reset() != 0) + return -1; + } else { + dbgp_ehci_status("ehci skip - already configured"); + } + + ret = _dbgp_external_startup(); + if (ret == -EIO) + goto next_debug_port; + + if (ret < 0) { + /* Things didn't work so remove my claim */ + ctrl = readl(&ehci_debug->control); + ctrl &= ~(DBGP_CLAIM | DBGP_OUT); + writel(ctrl, &ehci_debug->control); + return -1; + } + return 0; + +next_debug_port: + port_map_tried |= (1<<(debug_port - 1)); + new_debug_port = ((debug_port-1+1)%n_ports) + 1; + if (port_map_tried != ((1<> 29) & 0x7; + bar = (bar * 4) + 0xc; + offset = (debug_port >> 16) & 0xfff; + dbgp_printk("bar: %02x offset: %03x\n", bar, offset); + if (bar != PCI_BASE_ADDRESS_0) { + dbgp_printk("only debug ports on bar 1 handled.\n"); + + return -1; + } + + bar_val = read_pci_config(bus, slot, func, PCI_BASE_ADDRESS_0); + dbgp_printk("bar_val: %02x offset: %03x\n", bar_val, offset); + if (bar_val & ~PCI_BASE_ADDRESS_MEM_MASK) { + dbgp_printk("only simple 32bit mmio bars supported\n"); + + return -1; + } + + /* double check if the mem space is enabled */ + byte = read_pci_config_byte(bus, slot, func, 0x04); + if (!(byte & 0x2)) { + byte |= 0x02; + write_pci_config_byte(bus, slot, func, 0x04, byte); + dbgp_printk("mmio for ehci enabled\n"); + } + + /* + * FIXME I don't have the bar size so just guess PAGE_SIZE is more + * than enough. 1K is the biggest I have seen. + */ + set_fixmap_nocache(FIX_DBGP_BASE, bar_val & PAGE_MASK); + ehci_bar = (void __iomem *)__fix_to_virt(FIX_DBGP_BASE); + ehci_bar += bar_val & ~PAGE_MASK; + dbgp_printk("ehci_bar: %p\n", ehci_bar); + + ehci_caps = ehci_bar; + ehci_regs = ehci_bar + EARLY_HC_LENGTH(readl(&ehci_caps->hc_capbase)); + ehci_debug = ehci_bar + offset; + ehci_dev.bus = bus; + ehci_dev.slot = slot; + ehci_dev.func = func; + + detect_set_debug_port(); + + ret = ehci_setup(); + if (ret < 0) { + dbgp_printk("ehci_setup failed\n"); + ehci_debug = NULL; + + return -1; + } + dbgp_ehci_status("early_init_complete"); + + return 0; +} + +static void early_dbgp_write(struct console *con, const char *str, u32 n) +{ + int chunk, ret; + char buf[DBGP_MAX_PACKET]; + int use_cr = 0; + u32 cmd, ctrl; + int reset_run = 0; + + if (!ehci_debug || dbgp_not_safe) + return; + + cmd = readl(&ehci_regs->command); + if (unlikely(!(cmd & CMD_RUN))) { + /* If the ehci controller is not in the run state do extended + * checks to see if the acpi or some other initialization also + * reset the ehci debug port */ + ctrl = readl(&ehci_debug->control); + if (!(ctrl & DBGP_ENABLED)) { + dbgp_not_safe = 1; + _dbgp_external_startup(); + } else { + cmd |= CMD_RUN; + writel(cmd, &ehci_regs->command); + reset_run = 1; + } + } + while (n > 0) { + for (chunk = 0; chunk < DBGP_MAX_PACKET && n > 0; + str++, chunk++, n--) { + if (!use_cr && *str == '\n') { + use_cr = 1; + buf[chunk] = '\r'; + str--; + n++; + continue; + } + if (use_cr) + use_cr = 0; + buf[chunk] = *str; + } + if (chunk > 0) { + ret = dbgp_bulk_write(USB_DEBUG_DEVNUM, + dbgp_endpoint_out, buf, chunk); + } + } + if (unlikely(reset_run)) { + cmd = readl(&ehci_regs->command); + cmd &= ~CMD_RUN; + writel(cmd, &ehci_regs->command); + } +} + +struct console early_dbgp_console = { + .name = "earlydbg", + .write = early_dbgp_write, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +#if IS_ENABLED(CONFIG_USB) +int dbgp_reset_prep(struct usb_hcd *hcd) +{ + int ret = xen_dbgp_reset_prep(hcd); + u32 ctrl; + + if (ret) + return ret; + + dbgp_not_safe = 1; + if (!ehci_debug) + return 0; + + if ((early_dbgp_console.index != -1 && + !(early_dbgp_console.flags & CON_BOOT)) || + dbgp_kgdb_mode) + return 1; + /* This means the console is not initialized, or should get + * shutdown so as to allow for reuse of the usb device, which + * means it is time to shutdown the usb debug port. */ + ctrl = readl(&ehci_debug->control); + if (ctrl & DBGP_ENABLED) { + ctrl &= ~(DBGP_CLAIM); + writel(ctrl, &ehci_debug->control); + } + return 0; +} +EXPORT_SYMBOL_GPL(dbgp_reset_prep); + +int dbgp_external_startup(struct usb_hcd *hcd) +{ + return xen_dbgp_external_startup(hcd) ?: _dbgp_external_startup(); +} +EXPORT_SYMBOL_GPL(dbgp_external_startup); +#endif /* USB */ + +#ifdef CONFIG_KGDB + +static char kgdbdbgp_buf[DBGP_MAX_PACKET]; +static int kgdbdbgp_buf_sz; +static int kgdbdbgp_buf_idx; +static int kgdbdbgp_loop_cnt = DBGP_LOOPS; + +static int kgdbdbgp_read_char(void) +{ + int ret; + + if (kgdbdbgp_buf_idx < kgdbdbgp_buf_sz) { + char ch = kgdbdbgp_buf[kgdbdbgp_buf_idx++]; + return ch; + } + + ret = dbgp_bulk_read(USB_DEBUG_DEVNUM, dbgp_endpoint_in, + &kgdbdbgp_buf, DBGP_MAX_PACKET, + kgdbdbgp_loop_cnt); + if (ret <= 0) + return NO_POLL_CHAR; + kgdbdbgp_buf_sz = ret; + kgdbdbgp_buf_idx = 1; + return kgdbdbgp_buf[0]; +} + +static void kgdbdbgp_write_char(u8 chr) +{ + early_dbgp_write(NULL, &chr, 1); +} + +static struct kgdb_io kgdbdbgp_io_ops = { + .name = "kgdbdbgp", + .read_char = kgdbdbgp_read_char, + .write_char = kgdbdbgp_write_char, +}; + +static int kgdbdbgp_wait_time; + +static int __init kgdbdbgp_parse_config(char *str) +{ + char *ptr; + + if (!ehci_debug) { + if (early_dbgp_init(str)) + return -1; + } + ptr = strchr(str, ','); + if (ptr) { + ptr++; + kgdbdbgp_wait_time = simple_strtoul(ptr, &ptr, 10); + } + kgdb_register_io_module(&kgdbdbgp_io_ops); + kgdbdbgp_io_ops.is_console = early_dbgp_console.index != -1; + + return 0; +} +early_param("kgdbdbgp", kgdbdbgp_parse_config); + +static int kgdbdbgp_reader_thread(void *ptr) +{ + int ret; + + while (readl(&ehci_debug->control) & DBGP_ENABLED) { + kgdbdbgp_loop_cnt = 1; + ret = kgdbdbgp_read_char(); + kgdbdbgp_loop_cnt = DBGP_LOOPS; + if (ret != NO_POLL_CHAR) { + if (ret == 0x3 || ret == '$') { + if (ret == '$') + kgdbdbgp_buf_idx--; + kgdb_breakpoint(); + } + continue; + } + schedule_timeout_interruptible(kgdbdbgp_wait_time * HZ); + } + return 0; +} + +static int __init kgdbdbgp_start_thread(void) +{ + if (dbgp_kgdb_mode && kgdbdbgp_wait_time) + kthread_run(kgdbdbgp_reader_thread, NULL, "%s", "dbgp"); + + return 0; +} +module_init(kgdbdbgp_start_thread); +#endif /* CONFIG_KGDB */ diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig new file mode 100755 index 00000000..6d474c06 --- /dev/null +++ b/drivers/usb/gadget/Kconfig @@ -0,0 +1,1036 @@ +# +# USB Gadget support on a system involves +# (a) a peripheral controller, and +# (b) the gadget driver using it. +# +# NOTE: Gadget support ** DOES NOT ** depend on host-side CONFIG_USB !! +# +# - Host systems (like PCs) need CONFIG_USB (with "A" jacks). +# - Peripherals (like PDAs) need CONFIG_USB_GADGET (with "B" jacks). +# - Some systems have both kinds of controllers. +# +# With help from a special transceiver and a "Mini-AB" jack, systems with +# both kinds of controller can also support "USB On-the-Go" (CONFIG_USB_OTG). +# + +menuconfig USB_GADGET + tristate "USB Gadget Support" + select NLS + help + USB is a master/slave protocol, organized with one master + host (such as a PC) controlling up to 127 peripheral devices. + The USB hardware is asymmetric, which makes it easier to set up: + you can't connect a "to-the-host" connector to a peripheral. + + Linux can run in the host, or in the peripheral. In both cases + you need a low level bus controller driver, and some software + talking to it. Peripheral controllers are often discrete silicon, + or are integrated with the CPU in a microcontroller. The more + familiar host side controllers have names like "EHCI", "OHCI", + or "UHCI", and are usually integrated into southbridges on PC + motherboards. + + Enable this configuration option if you want to run Linux inside + a USB peripheral device. Configure one hardware driver for your + peripheral/device side bus controller, and a "gadget driver" for + your peripheral protocol. (If you use modular gadget drivers, + you may configure more than one.) + + If in doubt, say "N" and don't enable these drivers; most people + don't have this kind of hardware (except maybe inside Linux PDAs). + + For more information, see and + the kernel DocBook documentation for this API. + +if USB_GADGET + +config USB_GADGET_DEBUG + boolean "Debugging messages (DEVELOPMENT)" + depends on DEBUG_KERNEL + help + Many controller and gadget drivers will print some debugging + messages if you use this option to ask for those messages. + + Avoid enabling these messages, even if you're actively + debugging such a driver. Many drivers will emit so many + messages that the driver timings are affected, which will + either create new failure modes or remove the one you're + trying to track down. Never enable these messages for a + production build. + +config USB_GADGET_DEBUG_FILES + boolean "Debugging information files (DEVELOPMENT)" + depends on PROC_FS + help + Some of the drivers in the "gadget" framework can expose + debugging information in files such as /proc/driver/udc + (for a peripheral controller). The information in these + files may help when you're troubleshooting or bringing up a + driver on a new board. Enable these files by choosing "Y" + here. If in doubt, or to conserve kernel memory, say "N". + +config USB_GADGET_DEBUG_FS + boolean "Debugging information files in debugfs (DEVELOPMENT)" + depends on DEBUG_FS + help + Some of the drivers in the "gadget" framework can expose + debugging information in files under /sys/kernel/debug/. + The information in these files may help when you're + troubleshooting or bringing up a driver on a new board. + Enable these files by choosing "Y" here. If in doubt, or + to conserve kernel memory, say "N". + +config USB_GADGET_VBUS_DRAW + int "Maximum VBUS Power usage (2-500 mA)" + range 2 500 + default 2 + help + Some devices need to draw power from USB when they are + configured, perhaps to operate circuitry or to recharge + batteries. This is in addition to any local power supply, + such as an AC adapter or batteries. + + Enter the maximum power your device draws through USB, in + milliAmperes. The permitted range of values is 2 - 500 mA; + 0 mA would be legal, but can make some hosts misbehave. + + This value will be used except for system-specific gadget + drivers that have more specific information. + +config USB_GADGET_STORAGE_NUM_BUFFERS + int "Number of storage pipeline buffers" + range 2 4 + default 2 + help + Usually 2 buffers are enough to establish a good buffering + pipeline. The number may be increased in order to compensate + for a bursty VFS behaviour. For instance there may be CPU wake up + latencies that makes the VFS to appear bursty in a system with + an CPU on-demand governor. Especially if DMA is doing IO to + offload the CPU. In this case the CPU will go into power + save often and spin up occasionally to move data within VFS. + If selecting USB_GADGET_DEBUG_FILES this value may be set by + a module parameter as well. + If unsure, say 2. + +# +# USB Peripheral Controller Support +# +# The order here is alphabetical, except that integrated controllers go +# before discrete ones so they will be the initial/default value: +# - integrated/SOC controllers first +# - licensed IP used in both SOC and discrete versions +# - discrete ones (including all PCI-only controllers) +# - debug/dummy gadget+hcd is last. +# +menu "USB Peripheral Controller" + +# +# Integrated controllers +# + +config USB_AT91 + tristate "Atmel AT91 USB Device Port" + depends on ARCH_AT91 + help + Many Atmel AT91 processors (such as the AT91RM2000) have a + full speed USB Device Port with support for five configurable + endpoints (plus endpoint zero). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "at91_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_LPC32XX + tristate "LPC32XX USB Peripheral Controller" + depends on ARCH_LPC32XX + depends on USB_PHY + select USB_ISP1301 + help + This option selects the USB device controller in the LPC32xx SoC. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "lpc32xx_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_ATMEL_USBA + tristate "Atmel USBA" + depends on AVR32 || ARCH_AT91SAM9RL || ARCH_AT91SAM9G45 + help + USBA is the integrated high-speed USB Device controller on + the AT32AP700x, some AT91SAM9 and AT91CAP9 processors from Atmel. + +config USB_BCM63XX_UDC + tristate "Broadcom BCM63xx Peripheral Controller" + depends on BCM63XX + help + Many Broadcom BCM63xx chipsets (such as the BCM6328) have a + high speed USB Device Port with support for four fixed endpoints + (plus endpoint zero). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "bcm63xx_udc". + +config USB_FSL_USB2 + tristate "Freescale Highspeed USB DR Peripheral Controller" + depends on FSL_SOC || ARCH_MXC + select USB_FSL_MPH_DR_OF if OF + help + Some of Freescale PowerPC and i.MX processors have a High Speed + Dual-Role(DR) USB controller, which supports device mode. + + The number of programmable endpoints is different through + SOC revisions. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "fsl_usb2_udc" and force + all gadget drivers to also be dynamically linked. + +config USB_FUSB300 + tristate "Faraday FUSB300 USB Peripheral Controller" + depends on !PHYS_ADDR_T_64BIT + help + Faraday usb device controller FUSB300 driver + +config USB_OMAP + tristate "OMAP USB Device Controller" + depends on ARCH_OMAP1 + depends on USB_PHY + select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_H4_OTG + help + Many Texas Instruments OMAP processors have flexible full + speed USB device controllers, with support for up to 30 + endpoints (plus endpoint zero). This driver supports the + controller in the OMAP 1611, and should work with controllers + in other OMAP processors too, given minor tweaks. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "omap_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_PXA25X + tristate "PXA 25x or IXP 4xx" + depends on (ARCH_PXA && PXA25x) || ARCH_IXP4XX + help + Intel's PXA 25x series XScale ARM-5TE processors include + an integrated full speed USB 1.1 device controller. The + controller in the IXP 4xx series is register-compatible. + + It has fifteen fixed-function endpoints, as well as endpoint + zero (for control transfers). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "pxa25x_udc" and force all + gadget drivers to also be dynamically linked. + +# if there's only one gadget driver, using only two bulk endpoints, +# don't waste memory for the other endpoints +config USB_PXA25X_SMALL + depends on USB_PXA25X + bool + default n if USB_ETH_RNDIS + default y if USB_ZERO + default y if USB_ETH + default y if USB_G_SERIAL + +config USB_R8A66597 + tristate "Renesas R8A66597 USB Peripheral Controller" + help + R8A66597 is a discrete USB host and peripheral controller chip that + supports both full and high speed USB 2.0 data transfers. + It has nine configurable endpoints, and endpoint zero. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "r8a66597_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_RENESAS_USBHS_UDC + tristate 'Renesas USBHS controller' + depends on USB_RENESAS_USBHS + help + Renesas USBHS is a discrete USB host and peripheral controller chip + that supports both full and high speed USB 2.0 data transfers. + It has nine or more configurable endpoints, and endpoint zero. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "renesas_usbhs" and force all + gadget drivers to also be dynamically linked. + +config USB_PXA27X + tristate "PXA 27x" + help + Intel's PXA 27x series XScale ARM v5TE processors include + an integrated full speed USB 1.1 device controller. + + It has up to 23 endpoints, as well as endpoint zero (for + control transfers). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "pxa27x_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_S3C_HSOTG + tristate "S3C HS/OtG USB Device controller" + depends on S3C_DEV_USB_HSOTG + help + The Samsung S3C64XX USB2.0 high-speed gadget controller + integrated into the S3C64XX series SoC. + +config USB_IMX + tristate "Freescale i.MX1 USB Peripheral Controller" + depends on ARCH_MXC + depends on BROKEN + help + Freescale's i.MX1 includes an integrated full speed + USB 1.1 device controller. + + It has Six fixed-function endpoints, as well as endpoint + zero (for control transfers). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "imx_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_S3C2410 + tristate "S3C2410 USB Device Controller" + depends on ARCH_S3C24XX + help + Samsung's S3C2410 is an ARM-4 processor with an integrated + full speed USB 1.1 device controller. It has 4 configurable + endpoints, as well as endpoint zero (for control transfers). + + This driver has been tested on the S3C2410, S3C2412, and + S3C2440 processors. + +config USB_S3C2410_DEBUG + boolean "S3C2410 udc debug messages" + depends on USB_S3C2410 + +config USB_S3C_HSUDC + tristate "S3C2416, S3C2443 and S3C2450 USB Device Controller" + depends on ARCH_S3C24XX + help + Samsung's S3C2416, S3C2443 and S3C2450 is an ARM9 based SoC + integrated with dual speed USB 2.0 device controller. It has + 8 endpoints, as well as endpoint zero. + + This driver has been tested on S3C2416 and S3C2450 processors. + +config USB_MV_UDC + tristate "Marvell USB2.0 Device Controller" + depends on GENERIC_HARDIRQS + help + Marvell Socs (including PXA and MMP series) include a high speed + USB2.0 OTG controller, which can be configured as high speed or + full speed USB peripheral. + +config USB_MV_U3D + tristate "MARVELL PXA2128 USB 3.0 controller" + help + MARVELL PXA2128 Processor series include a super speed USB3.0 device + controller, which support super speed USB peripheral. + +# +# Controllers available in both integrated and discrete versions +# + +# musb builds in ../musb along with host support +config USB_GADGET_MUSB_HDRC + tristate "Inventra HDRC USB Peripheral (TI, ADI, ...)" + depends on USB_MUSB_HDRC + help + This OTG-capable silicon IP is used in dual designs including + the TI DaVinci, OMAP 243x, OMAP 343x, TUSB 6010, and ADI Blackfin + +config USB_M66592 + tristate "Renesas M66592 USB Peripheral Controller" + help + M66592 is a discrete USB peripheral controller chip that + supports both full and high speed USB 2.0 data transfers. + It has seven configurable endpoints, and endpoint zero. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "m66592_udc" and force all + gadget drivers to also be dynamically linked. + +# +# Controllers available only in discrete form (and all PCI controllers) +# + +config USB_AMD5536UDC + tristate "AMD5536 UDC" + depends on PCI + help + The AMD5536 UDC is part of the AMD Geode CS5536, an x86 southbridge. + It is a USB Highspeed DMA capable USB device controller. Beside ep0 + it provides 4 IN and 4 OUT endpoints (bulk or interrupt type). + The UDC port supports OTG operation, and may be used as a host port + if it's not being used to implement peripheral or OTG roles. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "amd5536udc" and force all + gadget drivers to also be dynamically linked. + +config USB_FSL_QE + tristate "Freescale QE/CPM USB Device Controller" + depends on FSL_SOC && (QUICC_ENGINE || CPM) + help + Some of Freescale PowerPC processors have a Full Speed + QE/CPM2 USB controller, which support device mode with 4 + programmable endpoints. This driver supports the + controller in the MPC8360 and MPC8272, and should work with + controllers having QE or CPM2, given minor tweaks. + + Set CONFIG_USB_GADGET to "m" to build this driver as a + dynamically linked module called "fsl_qe_udc". + +config USB_NET2272 + tristate "PLX NET2272" + help + PLX NET2272 is a USB peripheral controller which supports + both full and high speed USB 2.0 data transfers. + + It has three configurable endpoints, as well as endpoint zero + (for control transfer). + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "net2272" and force all + gadget drivers to also be dynamically linked. + +config USB_NET2272_DMA + boolean "Support external DMA controller" + depends on USB_NET2272 + help + The NET2272 part can optionally support an external DMA + controller, but your board has to have support in the + driver itself. + + If unsure, say "N" here. The driver works fine in PIO mode. + +config USB_NET2280 + tristate "NetChip 228x" + depends on PCI + help + NetChip 2280 / 2282 is a PCI based USB peripheral controller which + supports both full and high speed USB 2.0 data transfers. + + It has six configurable endpoints, as well as endpoint zero + (for control transfers) and several endpoints with dedicated + functions. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "net2280" and force all + gadget drivers to also be dynamically linked. + +config USB_GOKU + tristate "Toshiba TC86C001 'Goku-S'" + depends on PCI + help + The Toshiba TC86C001 is a PCI device which includes controllers + for full speed USB devices, IDE, I2C, SIO, plus a USB host (OHCI). + + The device controller has three configurable (bulk or interrupt) + endpoints, plus endpoint zero (for control transfers). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "goku_udc" and to force all + gadget drivers to also be dynamically linked. + +config USB_EG20T + tristate "Intel EG20T PCH/LAPIS Semiconductor IOH(ML7213/ML7831) UDC" + depends on PCI && GENERIC_HARDIRQS + help + This is a USB device driver for EG20T PCH. + EG20T PCH is the platform controller hub that is used in Intel's + general embedded platform. EG20T PCH has USB device interface. + Using this interface, it is able to access system devices connected + to USB device. + This driver enables USB device function. + USB device is a USB peripheral controller which + supports both full and high speed USB 2.0 data transfers. + This driver supports both control transfer and bulk transfer modes. + This driver dose not support interrupt transfer or isochronous + transfer modes. + + This driver also can be used for LAPIS Semiconductor's ML7213 which is + for IVI(In-Vehicle Infotainment) use. + ML7831 is for general purpose use. + ML7213/ML7831 is companion chip for Intel Atom E6xx series. + ML7213/ML7831 is completely compatible for Intel EG20T PCH. + +# +# LAST -- dummy/emulated controller +# + +config USB_DUMMY_HCD + tristate "Dummy HCD (DEVELOPMENT)" + depends on USB=y || (USB=m && USB_GADGET=m) + help + This host controller driver emulates USB, looping all data transfer + requests back to a USB "gadget driver" in the same host. The host + side is the master; the gadget side is the slave. Gadget drivers + can be high, full, or low speed; and they have access to endpoints + like those from NET2280, PXA2xx, or SA1100 hardware. + + This may help in some stages of creating a driver to embed in a + Linux device, since it lets you debug several parts of the gadget + driver without its hardware or drivers being involved. + + Since such a gadget side driver needs to interoperate with a host + side Linux-USB device driver, this may help to debug both sides + of a USB protocol stack. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "dummy_hcd" and force all + gadget drivers to also be dynamically linked. + +# NOTE: Please keep dummy_hcd LAST so that "real hardware" appears +# first and will be selected by default. + +endmenu + +config USB_GADGET_SPRD_DWC + boolean "SPREADTRUM USB2.0 Controller" + help + Spreadtrum's usb controller is an integrated high speed + USB 2.0 device controller(with dwc core). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "dwc_otg" and force all + gadget drivers to also be dynamically linked. + +config USB_SPRD_DWC + tristate + depends on USB_GADGET_SPRD_DWC + default USB_GADGET + select USB_GADGET_SELECTED + select USB_GADGET_DUALSPEED + +# +# USB Gadget Drivers +# + +# composite based drivers +config USB_LIBCOMPOSITE + tristate + select CONFIGFS_FS + depends on USB_GADGET + +config USB_F_ACM + tristate + +config USB_F_SS_LB + tristate + +config USB_U_SERIAL + tristate + +config USB_F_SERIAL + tristate + +config USB_F_OBEX + tristate + +choice + tristate "USB Gadget Drivers" + default USB_ETH + help + A Linux "Gadget Driver" talks to the USB Peripheral Controller + driver through the abstract "gadget" API. Some other operating + systems call these "client" drivers, of which "class drivers" + are a subset (implementing a USB device class specification). + A gadget driver implements one or more USB functions using + the peripheral hardware. + + Gadget drivers are hardware-neutral, or "platform independent", + except that they sometimes must understand quirks or limitations + of the particular controllers they work with. For example, when + a controller doesn't support alternate configurations or provide + enough of the right types of endpoints, the gadget driver might + not be able work with that controller, or might need to implement + a less common variant of a device class protocol. + +# this first set of drivers all depend on bulk-capable hardware. + +config USB_ZERO + tristate "Gadget Zero (DEVELOPMENT)" + select USB_LIBCOMPOSITE + select USB_F_SS_LB + help + Gadget Zero is a two-configuration device. It either sinks and + sources bulk data; or it loops back a configurable number of + transfers. It also implements control requests, for "chapter 9" + conformance. The driver needs only two bulk-capable endpoints, so + it can work on top of most device-side usb controllers. It's + useful for testing, and is also a working example showing how + USB "gadget drivers" can be written. + + Make this be the first driver you try using on top of any new + USB peripheral controller driver. Then you can use host-side + test software, like the "usbtest" driver, to put your hardware + and its driver through a basic set of functional tests. + + Gadget Zero also works with the host-side "usb-skeleton" driver, + and with many kinds of host-side test software. You may need + to tweak product and vendor IDs before host software knows about + this device, and arrange to select an appropriate configuration. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_zero". + +config USB_ZERO_HNPTEST + boolean "HNP Test Device" + depends on USB_ZERO && USB_OTG + help + You can configure this device to enumerate using the device + identifiers of the USB-OTG test device. That means that when + this gadget connects to another OTG device, with this one using + the "B-Peripheral" role, that device will use HNP to let this + one serve as the USB host instead (in the "B-Host" role). + +config USB_AUDIO + tristate "Audio Gadget" + depends on SND + select USB_LIBCOMPOSITE + select SND_PCM + help + This Gadget Audio driver is compatible with USB Audio Class + specification 2.0. It implements 1 AudioControl interface, + 1 AudioStreaming Interface each for USB-OUT and USB-IN. + Number of channels, sample rate and sample size can be + specified as module parameters. + This driver doesn't expect any real Audio codec to be present + on the device - the audio streams are simply sinked to and + sourced from a virtual ALSA sound card created. The user-space + application may choose to do whatever it wants with the data + received from the USB Host and choose to provide whatever it + wants as audio data to the USB Host. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_audio". + +config GADGET_UAC1 + bool "UAC 1.0 (Legacy)" + depends on USB_AUDIO + help + If you instead want older UAC Spec-1.0 driver that also has audio + paths hardwired to the Audio codec chip on-board and doesn't work + without one. + +config USB_ETH + tristate "Ethernet Gadget (with CDC Ethernet support)" + depends on NET + select USB_LIBCOMPOSITE + select CRC32 + help + This driver implements Ethernet style communication, in one of + several ways: + + - The "Communication Device Class" (CDC) Ethernet Control Model. + That protocol is often avoided with pure Ethernet adapters, in + favor of simpler vendor-specific hardware, but is widely + supported by firmware for smart network devices. + + - On hardware can't implement that protocol, a simple CDC subset + is used, placing fewer demands on USB. + + - CDC Ethernet Emulation Model (EEM) is a newer standard that has + a simpler interface that can be used by more USB hardware. + + RNDIS support is an additional option, more demanding than than + subset. + + Within the USB device, this gadget driver exposes a network device + "usbX", where X depends on what other networking devices you have. + Treat it like a two-node Ethernet link: host, and gadget. + + The Linux-USB host-side "usbnet" driver interoperates with this + driver, so that deep I/O queues can be supported. On 2.4 kernels, + use "CDCEther" instead, if you're using the CDC option. That CDC + mode should also interoperate with standard CDC Ethernet class + drivers on other host operating systems. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_ether". + +config USB_ETH_RNDIS + bool "RNDIS support" + depends on USB_ETH + select USB_LIBCOMPOSITE + default y + help + Microsoft Windows XP bundles the "Remote NDIS" (RNDIS) protocol, + and Microsoft provides redistributable binary RNDIS drivers for + older versions of Windows. + + If you say "y" here, the Ethernet gadget driver will try to provide + a second device configuration, supporting RNDIS to talk to such + Microsoft USB hosts. + + To make MS-Windows work with this, use Documentation/usb/linux.inf + as the "driver info file". For versions of MS-Windows older than + XP, you'll need to download drivers from Microsoft's website; a URL + is given in comments found in that info file. + +config USB_ETH_EEM + bool "Ethernet Emulation Model (EEM) support" + depends on USB_ETH + select USB_LIBCOMPOSITE + default n + help + CDC EEM is a newer USB standard that is somewhat simpler than CDC ECM + and therefore can be supported by more hardware. Technically ECM and + EEM are designed for different applications. The ECM model extends + the network interface to the target (e.g. a USB cable modem), and the + EEM model is for mobile devices to communicate with hosts using + ethernet over USB. For Linux gadgets, however, the interface with + the host is the same (a usbX device), so the differences are minimal. + + If you say "y" here, the Ethernet gadget driver will use the EEM + protocol rather than ECM. If unsure, say "n". + +config USB_G_NCM + tristate "Network Control Model (NCM) support" + depends on NET + select USB_LIBCOMPOSITE + select CRC32 + help + This driver implements USB CDC NCM subclass standard. NCM is + an advanced protocol for Ethernet encapsulation, allows grouping + of several ethernet frames into one USB transfer and different + alignment possibilities. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_ncm". + +config USB_GADGETFS + tristate "Gadget Filesystem" + help + This driver provides a filesystem based API that lets user mode + programs implement a single-configuration USB device, including + endpoint I/O and control requests that don't relate to enumeration. + All endpoints, transfer speeds, and transfer types supported by + the hardware are available, through read() and write() calls. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "gadgetfs". + +config USB_FUNCTIONFS + tristate "Function Filesystem" + select USB_LIBCOMPOSITE + select USB_FUNCTIONFS_GENERIC if !(USB_FUNCTIONFS_ETH || USB_FUNCTIONFS_RNDIS) + help + The Function Filesystem (FunctionFS) lets one create USB + composite functions in user space in the same way GadgetFS + lets one create USB gadgets in user space. This allows creation + of composite gadgets such that some of the functions are + implemented in kernel space (for instance Ethernet, serial or + mass storage) and other are implemented in user space. + + If you say "y" or "m" here you will be able what kind of + configurations the gadget will provide. + + Say "y" to link the driver statically, or "m" to build + a dynamically linked module called "g_ffs". + +config USB_FUNCTIONFS_ETH + bool "Include configuration with CDC ECM (Ethernet)" + depends on USB_FUNCTIONFS && NET + help + Include a configuration with CDC ECM function (Ethernet) and the + Function Filesystem. + +config USB_FUNCTIONFS_RNDIS + bool "Include configuration with RNDIS (Ethernet)" + depends on USB_FUNCTIONFS && NET + help + Include a configuration with RNDIS function (Ethernet) and the Filesystem. + +config USB_FUNCTIONFS_GENERIC + bool "Include 'pure' configuration" + depends on USB_FUNCTIONFS + help + Include a configuration with the Function Filesystem alone with + no Ethernet interface. + +config USB_MASS_STORAGE + tristate "Mass Storage Gadget" + depends on BLOCK + select USB_LIBCOMPOSITE + help + The Mass Storage Gadget acts as a USB Mass Storage disk drive. + As its storage repository it can use a regular file or a block + device (in much the same way as the "loop" device driver), + specified as a module parameter or sysfs option. + + This driver is a replacement for now removed File-backed + Storage Gadget (g_file_storage). + + Say "y" to link the driver statically, or "m" to build + a dynamically linked module called "g_mass_storage". + +config USB_GADGET_TARGET + tristate "USB Gadget Target Fabric Module" + depends on TARGET_CORE + select USB_LIBCOMPOSITE + help + This fabric is an USB gadget. Two USB protocols are supported that is + BBB or BOT (Bulk Only Transport) and UAS (USB Attached SCSI). BOT is + advertised on alternative interface 0 (primary) and UAS is on + alternative interface 1. Both protocols can work on USB2.0 and USB3.0. + UAS utilizes the USB 3.0 feature called streams support. + +config USB_G_SERIAL + tristate "Serial Gadget (with CDC ACM and CDC OBEX support)" + depends on TTY + select USB_U_SERIAL + select USB_F_ACM + select USB_F_SERIAL + select USB_F_OBEX + select USB_LIBCOMPOSITE + help + The Serial Gadget talks to the Linux-USB generic serial driver. + This driver supports a CDC-ACM module option, which can be used + to interoperate with MS-Windows hosts or with the Linux-USB + "cdc-acm" driver. + + This driver also supports a CDC-OBEX option. You will need a + user space OBEX server talking to /dev/ttyGS*, since the kernel + itself doesn't implement the OBEX protocol. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_serial". + + For more information, see Documentation/usb/gadget_serial.txt + which includes instructions and a "driver info file" needed to + make MS-Windows work with CDC ACM. + +config USB_MIDI_GADGET + tristate "MIDI Gadget" + depends on SND + select USB_LIBCOMPOSITE + select SND_RAWMIDI + help + The MIDI Gadget acts as a USB Audio device, with one MIDI + input and one MIDI output. These MIDI jacks appear as + a sound "card" in the ALSA sound system. Other MIDI + connections can then be made on the gadget system, using + ALSA's aconnect utility etc. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_midi". + +config USB_G_PRINTER + tristate "Printer Gadget" + select USB_LIBCOMPOSITE + help + The Printer Gadget channels data between the USB host and a + userspace program driving the print engine. The user space + program reads and writes the device file /dev/g_printer to + receive or send printer data. It can use ioctl calls to + the device file to get or set printer status. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_printer". + + For more information, see Documentation/usb/gadget_printer.txt + which includes sample code for accessing the device file. + +config USB_G_ANDROID + boolean "Android Composite Gadget" + select USB_F_ACM + select USB_LIBCOMPOSITE + select USB_U_SERIAL + help + The Android Composite Gadget supports multiple USB + functions: adb, acm, mass storage, mtp, accessory + and rndis. + Each function can be configured and enabled/disabled + dynamically from userspace through a sysfs interface. + +config USB_ANDROID_RNDIS_DWORD_ALIGNED + boolean "Use double word aligned" + depends on USB_G_ANDROID + help + Provides dword aligned for DMA controller. + +if TTY + +config USB_CDC_COMPOSITE + tristate "CDC Composite Device (Ethernet and ACM)" + depends on NET + select USB_LIBCOMPOSITE + select USB_U_SERIAL + select USB_F_ACM + help + This driver provides two functions in one configuration: + a CDC Ethernet (ECM) link, and a CDC ACM (serial port) link. + + This driver requires four bulk and two interrupt endpoints, + plus the ability to handle altsettings. Not all peripheral + controllers are that capable. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module. + +config USB_G_NOKIA + tristate "Nokia composite gadget" + depends on PHONET + select USB_LIBCOMPOSITE + select USB_U_SERIAL + select USB_F_ACM + help + The Nokia composite gadget provides support for acm, obex + and phonet in only one composite gadget driver. + + It's only really useful for N900 hardware. If you're building + a kernel for N900, say Y or M here. If unsure, say N. + +config USB_G_ACM_MS + tristate "CDC Composite Device (ACM and mass storage)" + depends on BLOCK + select USB_LIBCOMPOSITE + select USB_U_SERIAL + select USB_F_ACM + help + This driver provides two functions in one configuration: + a mass storage, and a CDC ACM (serial port) link. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_acm_ms". + +config USB_G_MULTI + tristate "Multifunction Composite Gadget" + depends on BLOCK && NET + select USB_G_MULTI_CDC if !USB_G_MULTI_RNDIS + select USB_LIBCOMPOSITE + select USB_U_SERIAL + select USB_F_ACM + help + The Multifunction Composite Gadget provides Ethernet (RNDIS + and/or CDC Ethernet), mass storage and ACM serial link + interfaces. + + You will be asked to choose which of the two configurations is + to be available in the gadget. At least one configuration must + be chosen to make the gadget usable. Selecting more than one + configuration will prevent Windows from automatically detecting + the gadget as a composite gadget, so an INF file will be needed to + use the gadget. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_multi". + +config USB_G_MULTI_RNDIS + bool "RNDIS + CDC Serial + Storage configuration" + depends on USB_G_MULTI + default y + help + This option enables a configuration with RNDIS, CDC Serial and + Mass Storage functions available in the Multifunction Composite + Gadget. This is the configuration dedicated for Windows since RNDIS + is Microsoft's protocol. + + If unsure, say "y". + +config USB_G_MULTI_CDC + bool "CDC Ethernet + CDC Serial + Storage configuration" + depends on USB_G_MULTI + default n + help + This option enables a configuration with CDC Ethernet (ECM), CDC + Serial and Mass Storage functions available in the Multifunction + Composite Gadget. + + If unsure, say "y". + +endif # TTY + +config USB_G_HID + tristate "HID Gadget" + select USB_LIBCOMPOSITE + help + The HID gadget driver provides generic emulation of USB + Human Interface Devices (HID). + + For more information, see Documentation/usb/gadget_hid.txt which + includes sample code for accessing the device files. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_hid". + +# Standalone / single function gadgets +config USB_G_DBGP + tristate "EHCI Debug Device Gadget" + depends on TTY + select USB_LIBCOMPOSITE + help + This gadget emulates an EHCI Debug device. This is useful when you want + to interact with an EHCI Debug Port. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_dbgp". + +if USB_G_DBGP +choice + prompt "EHCI Debug Device mode" + default USB_G_DBGP_SERIAL + +config USB_G_DBGP_PRINTK + depends on USB_G_DBGP + bool "printk" + help + Directly printk() received data. No interaction. + +config USB_G_DBGP_SERIAL + depends on USB_G_DBGP + select USB_U_SERIAL + bool "serial" + help + Userland can interact using /dev/ttyGSxxx. +endchoice +endif + +# put drivers that need isochronous transfer support (for audio +# or video class gadget drivers), or specific hardware, here. +config USB_G_WEBCAM + tristate "USB Webcam Gadget" + depends on VIDEO_DEV + select USB_LIBCOMPOSITE + select VIDEOBUF2_VMALLOC + help + The Webcam Gadget acts as a composite USB Audio and Video Class + device. It provides a userspace API to process UVC control requests + and stream video data to the host. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_webcam". + +endchoice + +config USB_CORE_IP_293A + boolean "usb core ip version (synophsys)" + depends on USB_GADGET_SPRD_DWC + + help + the new ip version 293a, should write 0 to softdiscon bit. + +config SPRD_USB_DEVICE_ONLY + boolean "whether support USB otg" + depends on USB_GADGET_SPRD_DWC + + help + the chip can only support device mode, do not support otg. + +config SHARK_PAD_HW_V102 + boolean "usb pad hardware version" + depends on USB_GADGET_SPRD_DWC + + help + usb pad hardware version. + +config USB_INTERRUPT_BY_MUIC + boolean "Interrupt according to MUIC" + depends on USB_GADGET_SPRD_DWC + help + Support MUIC +endif # USB_GADGET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile new file mode 100644 index 00000000..c3e2255c --- /dev/null +++ b/drivers/usb/gadget/Makefile @@ -0,0 +1,90 @@ +# +# USB peripheral controller drivers +# +ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG + +obj-$(CONFIG_USB_GADGET) += udc-core.o +obj-$(CONFIG_USB_LIBCOMPOSITE) += libcomposite.o +libcomposite-y := usbstring.o config.o epautoconf.o +libcomposite-y += composite.o functions.o configfs.o +obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o +obj-$(CONFIG_USB_NET2272) += net2272.o +obj-$(CONFIG_USB_NET2280) += net2280.o +obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc.o +obj-$(CONFIG_USB_PXA25X) += pxa25x_udc.o +obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o +obj-$(CONFIG_USB_IMX) += imx_udc.o +obj-$(CONFIG_USB_GOKU) += goku_udc.o +obj-$(CONFIG_USB_OMAP) += omap_udc.o +obj-$(CONFIG_USB_S3C2410) += s3c2410_udc.o +obj-$(CONFIG_USB_AT91) += at91_udc.o +obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o +obj-$(CONFIG_USB_BCM63XX_UDC) += bcm63xx_udc.o +obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o +fsl_usb2_udc-y := fsl_udc_core.o +fsl_usb2_udc-$(CONFIG_ARCH_MXC) += fsl_mxc_udc.o +obj-$(CONFIG_USB_M66592) += m66592-udc.o +obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o +obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o +obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o +obj-$(CONFIG_USB_S3C_HSUDC) += s3c-hsudc.o +obj-$(CONFIG_USB_LPC32XX) += lpc32xx_udc.o +obj-$(CONFIG_USB_EG20T) += pch_udc.o +obj-$(CONFIG_USB_MV_UDC) += mv_udc.o +mv_udc-y := mv_udc_core.o +obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o +obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o +obj-$(CONFIG_USB_SPRD_DWC) += dwc_otg/ + +# USB Functions +usb_f_acm-y := f_acm.o +obj-$(CONFIG_USB_F_ACM) += usb_f_acm.o +usb_f_ss_lb-y := f_loopback.o f_sourcesink.o +obj-$(CONFIG_USB_F_SS_LB) += usb_f_ss_lb.o +obj-$(CONFIG_USB_U_SERIAL) += u_serial.o +usb_f_serial-y := f_serial.o +obj-$(CONFIG_USB_F_SERIAL) += usb_f_serial.o +usb_f_obex-y := f_obex.o +obj-$(CONFIG_USB_F_OBEX) += usb_f_obex.o + +# +# USB gadget drivers +# +g_zero-y := zero.o +g_audio-y := audio.o +g_ether-y := ether.o +g_serial-y := serial.o +g_midi-y := gmidi.o +gadgetfs-y := inode.o +g_mass_storage-y := mass_storage.o +g_printer-y := printer.o +g_cdc-y := cdc2.o +g_multi-y := multi.o +g_hid-y := hid.o +g_dbgp-y := dbgp.o +g_nokia-y := nokia.o +g_webcam-y := webcam.o +g_ncm-y := ncm.o +g_acm_ms-y := acm_ms.o +g_android-y := android.o +g_tcm_usb_gadget-y := tcm_usb_gadget.o + +obj-$(CONFIG_USB_ZERO) += g_zero.o +obj-$(CONFIG_USB_AUDIO) += g_audio.o +obj-$(CONFIG_USB_ETH) += g_ether.o +obj-$(CONFIG_USB_GADGETFS) += gadgetfs.o +obj-$(CONFIG_USB_FUNCTIONFS) += g_ffs.o +obj-$(CONFIG_USB_MASS_STORAGE) += g_mass_storage.o +obj-$(CONFIG_USB_G_SERIAL) += g_serial.o +obj-$(CONFIG_USB_G_PRINTER) += g_printer.o +obj-$(CONFIG_USB_MIDI_GADGET) += g_midi.o +obj-$(CONFIG_USB_CDC_COMPOSITE) += g_cdc.o +obj-$(CONFIG_USB_G_HID) += g_hid.o +obj-$(CONFIG_USB_G_DBGP) += g_dbgp.o +obj-$(CONFIG_USB_G_MULTI) += g_multi.o +obj-$(CONFIG_USB_G_NOKIA) += g_nokia.o +obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o +obj-$(CONFIG_USB_G_NCM) += g_ncm.o +obj-$(CONFIG_USB_G_ACM_MS) += g_acm_ms.o +obj-$(CONFIG_USB_G_ANDROID) += g_android.o +obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o diff --git a/drivers/usb/gadget/acm_ms.c b/drivers/usb/gadget/acm_ms.c new file mode 100644 index 00000000..4b947bb5 --- /dev/null +++ b/drivers/usb/gadget/acm_ms.c @@ -0,0 +1,233 @@ +/* + * acm_ms.c -- Composite driver, with ACM and mass storage support + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * Author: David Brownell + * Modified: Klaus Schwarzkopf + * + * Heavily based on multi.c and cdc2.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include + +#include "u_serial.h" + +#define DRIVER_DESC "Composite Gadget (ACM + MS)" +#define DRIVER_VERSION "2011/10/10" + +/*-------------------------------------------------------------------------*/ + +/* + * DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ +#define ACM_MS_VENDOR_NUM 0x1d6b /* Linux Foundation */ +#define ACM_MS_PRODUCT_NUM 0x0106 /* Composite Gadget: ACM + MS*/ + +/*-------------------------------------------------------------------------*/ + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "f_mass_storage.c" + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + + .bDeviceClass = USB_CLASS_MISC /* 0xEF */, + .bDeviceSubClass = 2, + .bDeviceProtocol = 1, + + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id can be overridden by module parameters. */ + .idVendor = cpu_to_le16(ACM_MS_VENDOR_NUM), + .idProduct = cpu_to_le16(ACM_MS_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + /*.bNumConfigurations = DYNAMIC*/ +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* + * REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +/* string IDs are assigned dynamically */ +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +/****************************** Configurations ******************************/ + +static struct fsg_module_parameters fsg_mod_data = { .stall = 1 }; +FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +static struct fsg_common fsg_common; + +/*-------------------------------------------------------------------------*/ +static struct usb_function *f_acm; +static struct usb_function_instance *f_acm_inst; +/* + * We _always_ have both ACM and mass storage functions. + */ +static int __init acm_ms_do_config(struct usb_configuration *c) +{ + int status; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + f_acm_inst = usb_get_function_instance("acm"); + if (IS_ERR(f_acm_inst)) + return PTR_ERR(f_acm_inst); + + f_acm = usb_get_function(f_acm_inst); + if (IS_ERR(f_acm)) { + status = PTR_ERR(f_acm); + goto err_func; + } + + status = usb_add_function(c, f_acm); + if (status < 0) + goto err_conf; + + status = fsg_bind_config(c->cdev, c, &fsg_common); + if (status < 0) + goto err_fsg; + + return 0; +err_fsg: + usb_remove_function(c, f_acm); +err_conf: + usb_put_function(f_acm); +err_func: + usb_put_function_instance(f_acm_inst); + return status; +} + +static struct usb_configuration acm_ms_config_driver = { + .label = DRIVER_DESC, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init acm_ms_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int status; + void *retp; + + /* set up mass storage function */ + retp = fsg_common_from_params(&fsg_common, cdev, &fsg_mod_data); + if (IS_ERR(retp)) { + status = PTR_ERR(retp); + return PTR_ERR(retp); + } + + /* + * Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto fail1; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + /* register our configuration */ + status = usb_add_config(cdev, &acm_ms_config_driver, acm_ms_do_config); + if (status < 0) + goto fail1; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n", + DRIVER_DESC); + fsg_common_put(&fsg_common); + return 0; + + /* error recovery */ +fail1: + fsg_common_put(&fsg_common); + return status; +} + +static int __exit acm_ms_unbind(struct usb_composite_dev *cdev) +{ + usb_put_function(f_acm); + usb_put_function_instance(f_acm_inst); + return 0; +} + +static __refdata struct usb_composite_driver acm_ms_driver = { + .name = "g_acm_ms", + .dev = &device_desc, + .max_speed = USB_SPEED_SUPER, + .strings = dev_strings, + .bind = acm_ms_bind, + .unbind = __exit_p(acm_ms_unbind), +}; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Klaus Schwarzkopf "); +MODULE_LICENSE("GPL v2"); + +static int __init init(void) +{ + return usb_composite_probe(&acm_ms_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&acm_ms_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/amd5536udc.c b/drivers/usb/gadget/amd5536udc.c new file mode 100644 index 00000000..f52dcfe8 --- /dev/null +++ b/drivers/usb/gadget/amd5536udc.c @@ -0,0 +1,3366 @@ +/* + * amd5536.c -- AMD 5536 UDC high/full speed USB device controller + * + * Copyright (C) 2005-2007 AMD (http://www.amd.com) + * Author: Thomas Dahlmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * The AMD5536 UDC is part of the x86 southbridge AMD Geode CS5536. + * It is a USB Highspeed DMA capable USB device controller. Beside ep0 it + * provides 4 IN and 4 OUT endpoints (bulk or interrupt type). + * + * Make sure that UDC is assigned to port 4 by BIOS settings (port can also + * be used as host port) and UOC bits PAD_EN and APU are set (should be done + * by BIOS init). + * + * UDC DMA requires 32-bit aligned buffers so DMA with gadget ether does not + * work without updating NET_IP_ALIGN. Or PIO mode (module param "use_dma=0") + * can be used with gadget ether. + */ + +/* debug control */ +/* #define UDC_VERBOSE */ + +/* Driver strings */ +#define UDC_MOD_DESCRIPTION "AMD 5536 UDC - USB Device Controller" +#define UDC_DRIVER_VERSION_STRING "01.00.0206" + +/* system */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* gadget stack */ +#include +#include + +/* udc specific */ +#include "amd5536udc.h" + + +static void udc_tasklet_disconnect(unsigned long); +static void empty_req_queue(struct udc_ep *); +static int udc_probe(struct udc *dev); +static void udc_basic_init(struct udc *dev); +static void udc_setup_endpoints(struct udc *dev); +static void udc_soft_reset(struct udc *dev); +static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep); +static void udc_free_request(struct usb_ep *usbep, struct usb_request *usbreq); +static int udc_free_dma_chain(struct udc *dev, struct udc_request *req); +static int udc_create_dma_chain(struct udc_ep *ep, struct udc_request *req, + unsigned long buf_len, gfp_t gfp_flags); +static int udc_remote_wakeup(struct udc *dev); +static int udc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id); +static void udc_pci_remove(struct pci_dev *pdev); + +/* description */ +static const char mod_desc[] = UDC_MOD_DESCRIPTION; +static const char name[] = "amd5536udc"; + +/* structure to hold endpoint function pointers */ +static const struct usb_ep_ops udc_ep_ops; + +/* received setup data */ +static union udc_setup_data setup_data; + +/* pointer to device object */ +static struct udc *udc; + +/* irq spin lock for soft reset */ +static DEFINE_SPINLOCK(udc_irq_spinlock); +/* stall spin lock */ +static DEFINE_SPINLOCK(udc_stall_spinlock); + +/* +* slave mode: pending bytes in rx fifo after nyet, +* used if EPIN irq came but no req was available +*/ +static unsigned int udc_rxfifo_pending; + +/* count soft resets after suspend to avoid loop */ +static int soft_reset_occured; +static int soft_reset_after_usbreset_occured; + +/* timer */ +static struct timer_list udc_timer; +static int stop_timer; + +/* set_rde -- Is used to control enabling of RX DMA. Problem is + * that UDC has only one bit (RDE) to enable/disable RX DMA for + * all OUT endpoints. So we have to handle race conditions like + * when OUT data reaches the fifo but no request was queued yet. + * This cannot be solved by letting the RX DMA disabled until a + * request gets queued because there may be other OUT packets + * in the FIFO (important for not blocking control traffic). + * The value of set_rde controls the correspondig timer. + * + * set_rde -1 == not used, means it is alloed to be set to 0 or 1 + * set_rde 0 == do not touch RDE, do no start the RDE timer + * set_rde 1 == timer function will look whether FIFO has data + * set_rde 2 == set by timer function to enable RX DMA on next call + */ +static int set_rde = -1; + +static DECLARE_COMPLETION(on_exit); +static struct timer_list udc_pollstall_timer; +static int stop_pollstall_timer; +static DECLARE_COMPLETION(on_pollstall_exit); + +/* tasklet for usb disconnect */ +static DECLARE_TASKLET(disconnect_tasklet, udc_tasklet_disconnect, + (unsigned long) &udc); + + +/* endpoint names used for print */ +static const char ep0_string[] = "ep0in"; +static const char *const ep_string[] = { + ep0_string, + "ep1in-int", "ep2in-bulk", "ep3in-bulk", "ep4in-bulk", "ep5in-bulk", + "ep6in-bulk", "ep7in-bulk", "ep8in-bulk", "ep9in-bulk", "ep10in-bulk", + "ep11in-bulk", "ep12in-bulk", "ep13in-bulk", "ep14in-bulk", + "ep15in-bulk", "ep0out", "ep1out-bulk", "ep2out-bulk", "ep3out-bulk", + "ep4out-bulk", "ep5out-bulk", "ep6out-bulk", "ep7out-bulk", + "ep8out-bulk", "ep9out-bulk", "ep10out-bulk", "ep11out-bulk", + "ep12out-bulk", "ep13out-bulk", "ep14out-bulk", "ep15out-bulk" +}; + +/* DMA usage flag */ +static bool use_dma = 1; +/* packet per buffer dma */ +static bool use_dma_ppb = 1; +/* with per descr. update */ +static bool use_dma_ppb_du; +/* buffer fill mode */ +static int use_dma_bufferfill_mode; +/* full speed only mode */ +static bool use_fullspeed; +/* tx buffer size for high speed */ +static unsigned long hs_tx_buf = UDC_EPIN_BUFF_SIZE; + +/* module parameters */ +module_param(use_dma, bool, S_IRUGO); +MODULE_PARM_DESC(use_dma, "true for DMA"); +module_param(use_dma_ppb, bool, S_IRUGO); +MODULE_PARM_DESC(use_dma_ppb, "true for DMA in packet per buffer mode"); +module_param(use_dma_ppb_du, bool, S_IRUGO); +MODULE_PARM_DESC(use_dma_ppb_du, + "true for DMA in packet per buffer mode with descriptor update"); +module_param(use_fullspeed, bool, S_IRUGO); +MODULE_PARM_DESC(use_fullspeed, "true for fullspeed only"); + +/*---------------------------------------------------------------------------*/ +/* Prints UDC device registers and endpoint irq registers */ +static void print_regs(struct udc *dev) +{ + DBG(dev, "------- Device registers -------\n"); + DBG(dev, "dev config = %08x\n", readl(&dev->regs->cfg)); + DBG(dev, "dev control = %08x\n", readl(&dev->regs->ctl)); + DBG(dev, "dev status = %08x\n", readl(&dev->regs->sts)); + DBG(dev, "\n"); + DBG(dev, "dev int's = %08x\n", readl(&dev->regs->irqsts)); + DBG(dev, "dev intmask = %08x\n", readl(&dev->regs->irqmsk)); + DBG(dev, "\n"); + DBG(dev, "dev ep int's = %08x\n", readl(&dev->regs->ep_irqsts)); + DBG(dev, "dev ep intmask = %08x\n", readl(&dev->regs->ep_irqmsk)); + DBG(dev, "\n"); + DBG(dev, "USE DMA = %d\n", use_dma); + if (use_dma && use_dma_ppb && !use_dma_ppb_du) { + DBG(dev, "DMA mode = PPBNDU (packet per buffer " + "WITHOUT desc. update)\n"); + dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBNDU"); + } else if (use_dma && use_dma_ppb && use_dma_ppb_du) { + DBG(dev, "DMA mode = PPBDU (packet per buffer " + "WITH desc. update)\n"); + dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBDU"); + } + if (use_dma && use_dma_bufferfill_mode) { + DBG(dev, "DMA mode = BF (buffer fill mode)\n"); + dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "BF"); + } + if (!use_dma) + dev_info(&dev->pdev->dev, "FIFO mode\n"); + DBG(dev, "-------------------------------------------------------\n"); +} + +/* Masks unused interrupts */ +static int udc_mask_unused_interrupts(struct udc *dev) +{ + u32 tmp; + + /* mask all dev interrupts */ + tmp = AMD_BIT(UDC_DEVINT_SVC) | + AMD_BIT(UDC_DEVINT_ENUM) | + AMD_BIT(UDC_DEVINT_US) | + AMD_BIT(UDC_DEVINT_UR) | + AMD_BIT(UDC_DEVINT_ES) | + AMD_BIT(UDC_DEVINT_SI) | + AMD_BIT(UDC_DEVINT_SOF)| + AMD_BIT(UDC_DEVINT_SC); + writel(tmp, &dev->regs->irqmsk); + + /* mask all ep interrupts */ + writel(UDC_EPINT_MSK_DISABLE_ALL, &dev->regs->ep_irqmsk); + + return 0; +} + +/* Enables endpoint 0 interrupts */ +static int udc_enable_ep0_interrupts(struct udc *dev) +{ + u32 tmp; + + DBG(dev, "udc_enable_ep0_interrupts()\n"); + + /* read irq mask */ + tmp = readl(&dev->regs->ep_irqmsk); + /* enable ep0 irq's */ + tmp &= AMD_UNMASK_BIT(UDC_EPINT_IN_EP0) + & AMD_UNMASK_BIT(UDC_EPINT_OUT_EP0); + writel(tmp, &dev->regs->ep_irqmsk); + + return 0; +} + +/* Enables device interrupts for SET_INTF and SET_CONFIG */ +static int udc_enable_dev_setup_interrupts(struct udc *dev) +{ + u32 tmp; + + DBG(dev, "enable device interrupts for setup data\n"); + + /* read irq mask */ + tmp = readl(&dev->regs->irqmsk); + + /* enable SET_INTERFACE, SET_CONFIG and other needed irq's */ + tmp &= AMD_UNMASK_BIT(UDC_DEVINT_SI) + & AMD_UNMASK_BIT(UDC_DEVINT_SC) + & AMD_UNMASK_BIT(UDC_DEVINT_UR) + & AMD_UNMASK_BIT(UDC_DEVINT_SVC) + & AMD_UNMASK_BIT(UDC_DEVINT_ENUM); + writel(tmp, &dev->regs->irqmsk); + + return 0; +} + +/* Calculates fifo start of endpoint based on preceding endpoints */ +static int udc_set_txfifo_addr(struct udc_ep *ep) +{ + struct udc *dev; + u32 tmp; + int i; + + if (!ep || !(ep->in)) + return -EINVAL; + + dev = ep->dev; + ep->txfifo = dev->txfifo; + + /* traverse ep's */ + for (i = 0; i < ep->num; i++) { + if (dev->ep[i].regs) { + /* read fifo size */ + tmp = readl(&dev->ep[i].regs->bufin_framenum); + tmp = AMD_GETBITS(tmp, UDC_EPIN_BUFF_SIZE); + ep->txfifo += tmp; + } + } + return 0; +} + +/* CNAK pending field: bit0 = ep0in, bit16 = ep0out */ +static u32 cnak_pending; + +static void UDC_QUEUE_CNAK(struct udc_ep *ep, unsigned num) +{ + if (readl(&ep->regs->ctl) & AMD_BIT(UDC_EPCTL_NAK)) { + DBG(ep->dev, "NAK could not be cleared for ep%d\n", num); + cnak_pending |= 1 << (num); + ep->naking = 1; + } else + cnak_pending = cnak_pending & (~(1 << (num))); +} + + +/* Enables endpoint, is called by gadget driver */ +static int +udc_ep_enable(struct usb_ep *usbep, const struct usb_endpoint_descriptor *desc) +{ + struct udc_ep *ep; + struct udc *dev; + u32 tmp; + unsigned long iflags; + u8 udc_csr_epix; + unsigned maxpacket; + + if (!usbep + || usbep->name == ep0_string + || !desc + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + ep = container_of(usbep, struct udc_ep, ep); + dev = ep->dev; + + DBG(dev, "udc_ep_enable() ep %d\n", ep->num); + + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&dev->lock, iflags); + ep->ep.desc = desc; + + ep->halted = 0; + + /* set traffic type */ + tmp = readl(&dev->ep[ep->num].regs->ctl); + tmp = AMD_ADDBITS(tmp, desc->bmAttributes, UDC_EPCTL_ET); + writel(tmp, &dev->ep[ep->num].regs->ctl); + + /* set max packet size */ + maxpacket = usb_endpoint_maxp(desc); + tmp = readl(&dev->ep[ep->num].regs->bufout_maxpkt); + tmp = AMD_ADDBITS(tmp, maxpacket, UDC_EP_MAX_PKT_SIZE); + ep->ep.maxpacket = maxpacket; + writel(tmp, &dev->ep[ep->num].regs->bufout_maxpkt); + + /* IN ep */ + if (ep->in) { + + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num; + + /* set buffer size (tx fifo entries) */ + tmp = readl(&dev->ep[ep->num].regs->bufin_framenum); + /* double buffering: fifo size = 2 x max packet size */ + tmp = AMD_ADDBITS( + tmp, + maxpacket * UDC_EPIN_BUFF_SIZE_MULT + / UDC_DWORD_BYTES, + UDC_EPIN_BUFF_SIZE); + writel(tmp, &dev->ep[ep->num].regs->bufin_framenum); + + /* calc. tx fifo base addr */ + udc_set_txfifo_addr(ep); + + /* flush fifo */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_F); + writel(tmp, &ep->regs->ctl); + + /* OUT ep */ + } else { + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; + + /* set max packet size UDC CSR */ + tmp = readl(&dev->csr->ne[ep->num - UDC_CSR_EP_OUT_IX_OFS]); + tmp = AMD_ADDBITS(tmp, maxpacket, + UDC_CSR_NE_MAX_PKT); + writel(tmp, &dev->csr->ne[ep->num - UDC_CSR_EP_OUT_IX_OFS]); + + if (use_dma && !ep->in) { + /* alloc and init BNA dummy request */ + ep->bna_dummy_req = udc_alloc_bna_dummy(ep); + ep->bna_occurred = 0; + } + + if (ep->num != UDC_EP0OUT_IX) + dev->data_ep_enabled = 1; + } + + /* set ep values */ + tmp = readl(&dev->csr->ne[udc_csr_epix]); + /* max packet */ + tmp = AMD_ADDBITS(tmp, maxpacket, UDC_CSR_NE_MAX_PKT); + /* ep number */ + tmp = AMD_ADDBITS(tmp, desc->bEndpointAddress, UDC_CSR_NE_NUM); + /* ep direction */ + tmp = AMD_ADDBITS(tmp, ep->in, UDC_CSR_NE_DIR); + /* ep type */ + tmp = AMD_ADDBITS(tmp, desc->bmAttributes, UDC_CSR_NE_TYPE); + /* ep config */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_config, UDC_CSR_NE_CFG); + /* ep interface */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_intf, UDC_CSR_NE_INTF); + /* ep alt */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_alt, UDC_CSR_NE_ALT); + /* write reg */ + writel(tmp, &dev->csr->ne[udc_csr_epix]); + + /* enable ep irq */ + tmp = readl(&dev->regs->ep_irqmsk); + tmp &= AMD_UNMASK_BIT(ep->num); + writel(tmp, &dev->regs->ep_irqmsk); + + /* + * clear NAK by writing CNAK + * avoid BNA for OUT DMA, don't clear NAK until DMA desc. written + */ + if (!use_dma || ep->in) { + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->naking = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + tmp = desc->bEndpointAddress; + DBG(dev, "%s enabled\n", usbep->name); + + spin_unlock_irqrestore(&dev->lock, iflags); + return 0; +} + +/* Resets endpoint */ +static void ep_init(struct udc_regs __iomem *regs, struct udc_ep *ep) +{ + u32 tmp; + + VDBG(ep->dev, "ep-%d reset\n", ep->num); + ep->ep.desc = NULL; + ep->ep.ops = &udc_ep_ops; + INIT_LIST_HEAD(&ep->queue); + + ep->ep.maxpacket = (u16) ~0; + /* set NAK */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_SNAK); + writel(tmp, &ep->regs->ctl); + ep->naking = 1; + + /* disable interrupt */ + tmp = readl(®s->ep_irqmsk); + tmp |= AMD_BIT(ep->num); + writel(tmp, ®s->ep_irqmsk); + + if (ep->in) { + /* unset P and IN bit of potential former DMA */ + tmp = readl(&ep->regs->ctl); + tmp &= AMD_UNMASK_BIT(UDC_EPCTL_P); + writel(tmp, &ep->regs->ctl); + + tmp = readl(&ep->regs->sts); + tmp |= AMD_BIT(UDC_EPSTS_IN); + writel(tmp, &ep->regs->sts); + + /* flush the fifo */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_F); + writel(tmp, &ep->regs->ctl); + + } + /* reset desc pointer */ + writel(0, &ep->regs->desptr); +} + +/* Disables endpoint, is called by gadget driver */ +static int udc_ep_disable(struct usb_ep *usbep) +{ + struct udc_ep *ep = NULL; + unsigned long iflags; + + if (!usbep) + return -EINVAL; + + ep = container_of(usbep, struct udc_ep, ep); + if (usbep->name == ep0_string || !ep->ep.desc) + return -EINVAL; + + DBG(ep->dev, "Disable ep-%d\n", ep->num); + + spin_lock_irqsave(&ep->dev->lock, iflags); + udc_free_request(&ep->ep, &ep->bna_dummy_req->req); + empty_req_queue(ep); + ep_init(ep->dev->regs, ep); + spin_unlock_irqrestore(&ep->dev->lock, iflags); + + return 0; +} + +/* Allocates request packet, called by gadget driver */ +static struct usb_request * +udc_alloc_request(struct usb_ep *usbep, gfp_t gfp) +{ + struct udc_request *req; + struct udc_data_dma *dma_desc; + struct udc_ep *ep; + + if (!usbep) + return NULL; + + ep = container_of(usbep, struct udc_ep, ep); + + VDBG(ep->dev, "udc_alloc_req(): ep%d\n", ep->num); + req = kzalloc(sizeof(struct udc_request), gfp); + if (!req) + return NULL; + + req->req.dma = DMA_DONT_USE; + INIT_LIST_HEAD(&req->queue); + + if (ep->dma) { + /* ep0 in requests are allocated from data pool here */ + dma_desc = pci_pool_alloc(ep->dev->data_requests, gfp, + &req->td_phys); + if (!dma_desc) { + kfree(req); + return NULL; + } + + VDBG(ep->dev, "udc_alloc_req: req = %p dma_desc = %p, " + "td_phys = %lx\n", + req, dma_desc, + (unsigned long)req->td_phys); + /* prevent from using desc. - set HOST BUSY */ + dma_desc->status = AMD_ADDBITS(dma_desc->status, + UDC_DMA_STP_STS_BS_HOST_BUSY, + UDC_DMA_STP_STS_BS); + dma_desc->bufptr = cpu_to_le32(DMA_DONT_USE); + req->td_data = dma_desc; + req->td_data_last = NULL; + req->chain_len = 1; + } + + return &req->req; +} + +/* Frees request packet, called by gadget driver */ +static void +udc_free_request(struct usb_ep *usbep, struct usb_request *usbreq) +{ + struct udc_ep *ep; + struct udc_request *req; + + if (!usbep || !usbreq) + return; + + ep = container_of(usbep, struct udc_ep, ep); + req = container_of(usbreq, struct udc_request, req); + VDBG(ep->dev, "free_req req=%p\n", req); + BUG_ON(!list_empty(&req->queue)); + if (req->td_data) { + VDBG(ep->dev, "req->td_data=%p\n", req->td_data); + + /* free dma chain if created */ + if (req->chain_len > 1) + udc_free_dma_chain(ep->dev, req); + + pci_pool_free(ep->dev->data_requests, req->td_data, + req->td_phys); + } + kfree(req); +} + +/* Init BNA dummy descriptor for HOST BUSY and pointing to itself */ +static void udc_init_bna_dummy(struct udc_request *req) +{ + if (req) { + /* set last bit */ + req->td_data->status |= AMD_BIT(UDC_DMA_IN_STS_L); + /* set next pointer to itself */ + req->td_data->next = req->td_phys; + /* set HOST BUSY */ + req->td_data->status + = AMD_ADDBITS(req->td_data->status, + UDC_DMA_STP_STS_BS_DMA_DONE, + UDC_DMA_STP_STS_BS); +#ifdef UDC_VERBOSE + pr_debug("bna desc = %p, sts = %08x\n", + req->td_data, req->td_data->status); +#endif + } +} + +/* Allocate BNA dummy descriptor */ +static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep) +{ + struct udc_request *req = NULL; + struct usb_request *_req = NULL; + + /* alloc the dummy request */ + _req = udc_alloc_request(&ep->ep, GFP_ATOMIC); + if (_req) { + req = container_of(_req, struct udc_request, req); + ep->bna_dummy_req = req; + udc_init_bna_dummy(req); + } + return req; +} + +/* Write data to TX fifo for IN packets */ +static void +udc_txfifo_write(struct udc_ep *ep, struct usb_request *req) +{ + u8 *req_buf; + u32 *buf; + int i, j; + unsigned bytes = 0; + unsigned remaining = 0; + + if (!req || !ep) + return; + + req_buf = req->buf + req->actual; + prefetch(req_buf); + remaining = req->length - req->actual; + + buf = (u32 *) req_buf; + + bytes = ep->ep.maxpacket; + if (bytes > remaining) + bytes = remaining; + + /* dwords first */ + for (i = 0; i < bytes / UDC_DWORD_BYTES; i++) + writel(*(buf + i), ep->txfifo); + + /* remaining bytes must be written by byte access */ + for (j = 0; j < bytes % UDC_DWORD_BYTES; j++) { + writeb((u8)(*(buf + i) >> (j << UDC_BITS_PER_BYTE_SHIFT)), + ep->txfifo); + } + + /* dummy write confirm */ + writel(0, &ep->regs->confirm); +} + +/* Read dwords from RX fifo for OUT transfers */ +static int udc_rxfifo_read_dwords(struct udc *dev, u32 *buf, int dwords) +{ + int i; + + VDBG(dev, "udc_read_dwords(): %d dwords\n", dwords); + + for (i = 0; i < dwords; i++) + *(buf + i) = readl(dev->rxfifo); + return 0; +} + +/* Read bytes from RX fifo for OUT transfers */ +static int udc_rxfifo_read_bytes(struct udc *dev, u8 *buf, int bytes) +{ + int i, j; + u32 tmp; + + VDBG(dev, "udc_read_bytes(): %d bytes\n", bytes); + + /* dwords first */ + for (i = 0; i < bytes / UDC_DWORD_BYTES; i++) + *((u32 *)(buf + (i<<2))) = readl(dev->rxfifo); + + /* remaining bytes must be read by byte access */ + if (bytes % UDC_DWORD_BYTES) { + tmp = readl(dev->rxfifo); + for (j = 0; j < bytes % UDC_DWORD_BYTES; j++) { + *(buf + (i<<2) + j) = (u8)(tmp & UDC_BYTE_MASK); + tmp = tmp >> UDC_BITS_PER_BYTE; + } + } + + return 0; +} + +/* Read data from RX fifo for OUT transfers */ +static int +udc_rxfifo_read(struct udc_ep *ep, struct udc_request *req) +{ + u8 *buf; + unsigned buf_space; + unsigned bytes = 0; + unsigned finished = 0; + + /* received number bytes */ + bytes = readl(&ep->regs->sts); + bytes = AMD_GETBITS(bytes, UDC_EPSTS_RX_PKT_SIZE); + + buf_space = req->req.length - req->req.actual; + buf = req->req.buf + req->req.actual; + if (bytes > buf_space) { + if ((buf_space % ep->ep.maxpacket) != 0) { + DBG(ep->dev, + "%s: rx %d bytes, rx-buf space = %d bytesn\n", + ep->ep.name, bytes, buf_space); + req->req.status = -EOVERFLOW; + } + bytes = buf_space; + } + req->req.actual += bytes; + + /* last packet ? */ + if (((bytes % ep->ep.maxpacket) != 0) || (!bytes) + || ((req->req.actual == req->req.length) && !req->req.zero)) + finished = 1; + + /* read rx fifo bytes */ + VDBG(ep->dev, "ep %s: rxfifo read %d bytes\n", ep->ep.name, bytes); + udc_rxfifo_read_bytes(ep->dev, buf, bytes); + + return finished; +} + +/* create/re-init a DMA descriptor or a DMA descriptor chain */ +static int prep_dma(struct udc_ep *ep, struct udc_request *req, gfp_t gfp) +{ + int retval = 0; + u32 tmp; + + VDBG(ep->dev, "prep_dma\n"); + VDBG(ep->dev, "prep_dma ep%d req->td_data=%p\n", + ep->num, req->td_data); + + /* set buffer pointer */ + req->td_data->bufptr = req->req.dma; + + /* set last bit */ + req->td_data->status |= AMD_BIT(UDC_DMA_IN_STS_L); + + /* build/re-init dma chain if maxpkt scatter mode, not for EP0 */ + if (use_dma_ppb) { + + retval = udc_create_dma_chain(ep, req, ep->ep.maxpacket, gfp); + if (retval != 0) { + if (retval == -ENOMEM) + DBG(ep->dev, "Out of DMA memory\n"); + return retval; + } + if (ep->in) { + if (req->req.length == ep->ep.maxpacket) { + /* write tx bytes */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + ep->ep.maxpacket, + UDC_DMA_IN_STS_TXBYTES); + + } + } + + } + + if (ep->in) { + VDBG(ep->dev, "IN: use_dma_ppb=%d req->req.len=%d " + "maxpacket=%d ep%d\n", + use_dma_ppb, req->req.length, + ep->ep.maxpacket, ep->num); + /* + * if bytes < max packet then tx bytes must + * be written in packet per buffer mode + */ + if (!use_dma_ppb || req->req.length < ep->ep.maxpacket + || ep->num == UDC_EP0OUT_IX + || ep->num == UDC_EP0IN_IX) { + /* write tx bytes */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + req->req.length, + UDC_DMA_IN_STS_TXBYTES); + /* reset frame num */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + 0, + UDC_DMA_IN_STS_FRAMENUM); + } + /* set HOST BUSY */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + UDC_DMA_STP_STS_BS_HOST_BUSY, + UDC_DMA_STP_STS_BS); + } else { + VDBG(ep->dev, "OUT set host ready\n"); + /* set HOST READY */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + UDC_DMA_STP_STS_BS_HOST_READY, + UDC_DMA_STP_STS_BS); + + + /* clear NAK by writing CNAK */ + if (ep->naking) { + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->naking = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + + } + + return retval; +} + +/* Completes request packet ... caller MUST hold lock */ +static void +complete_req(struct udc_ep *ep, struct udc_request *req, int sts) +__releases(ep->dev->lock) +__acquires(ep->dev->lock) +{ + struct udc *dev; + unsigned halted; + + VDBG(ep->dev, "complete_req(): ep%d\n", ep->num); + + dev = ep->dev; + /* unmap DMA */ + if (ep->dma) + usb_gadget_unmap_request(&dev->gadget, &req->req, ep->in); + + halted = ep->halted; + ep->halted = 1; + + /* set new status if pending */ + if (req->req.status == -EINPROGRESS) + req->req.status = sts; + + /* remove from ep queue */ + list_del_init(&req->queue); + + VDBG(ep->dev, "req %p => complete %d bytes at %s with sts %d\n", + &req->req, req->req.length, ep->ep.name, sts); + + spin_unlock(&dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&dev->lock); + ep->halted = halted; +} + +/* frees pci pool descriptors of a DMA chain */ +static int udc_free_dma_chain(struct udc *dev, struct udc_request *req) +{ + + int ret_val = 0; + struct udc_data_dma *td; + struct udc_data_dma *td_last = NULL; + unsigned int i; + + DBG(dev, "free chain req = %p\n", req); + + /* do not free first desc., will be done by free for request */ + td_last = req->td_data; + td = phys_to_virt(td_last->next); + + for (i = 1; i < req->chain_len; i++) { + + pci_pool_free(dev->data_requests, td, + (dma_addr_t) td_last->next); + td_last = td; + td = phys_to_virt(td_last->next); + } + + return ret_val; +} + +/* Iterates to the end of a DMA chain and returns last descriptor */ +static struct udc_data_dma *udc_get_last_dma_desc(struct udc_request *req) +{ + struct udc_data_dma *td; + + td = req->td_data; + while (td && !(td->status & AMD_BIT(UDC_DMA_IN_STS_L))) + td = phys_to_virt(td->next); + + return td; + +} + +/* Iterates to the end of a DMA chain and counts bytes received */ +static u32 udc_get_ppbdu_rxbytes(struct udc_request *req) +{ + struct udc_data_dma *td; + u32 count; + + td = req->td_data; + /* received number bytes */ + count = AMD_GETBITS(td->status, UDC_DMA_OUT_STS_RXBYTES); + + while (td && !(td->status & AMD_BIT(UDC_DMA_IN_STS_L))) { + td = phys_to_virt(td->next); + /* received number bytes */ + if (td) { + count += AMD_GETBITS(td->status, + UDC_DMA_OUT_STS_RXBYTES); + } + } + + return count; + +} + +/* Creates or re-inits a DMA chain */ +static int udc_create_dma_chain( + struct udc_ep *ep, + struct udc_request *req, + unsigned long buf_len, gfp_t gfp_flags +) +{ + unsigned long bytes = req->req.length; + unsigned int i; + dma_addr_t dma_addr; + struct udc_data_dma *td = NULL; + struct udc_data_dma *last = NULL; + unsigned long txbytes; + unsigned create_new_chain = 0; + unsigned len; + + VDBG(ep->dev, "udc_create_dma_chain: bytes=%ld buf_len=%ld\n", + bytes, buf_len); + dma_addr = DMA_DONT_USE; + + /* unset L bit in first desc for OUT */ + if (!ep->in) + req->td_data->status &= AMD_CLEAR_BIT(UDC_DMA_IN_STS_L); + + /* alloc only new desc's if not already available */ + len = req->req.length / ep->ep.maxpacket; + if (req->req.length % ep->ep.maxpacket) + len++; + + if (len > req->chain_len) { + /* shorter chain already allocated before */ + if (req->chain_len > 1) + udc_free_dma_chain(ep->dev, req); + req->chain_len = len; + create_new_chain = 1; + } + + td = req->td_data; + /* gen. required number of descriptors and buffers */ + for (i = buf_len; i < bytes; i += buf_len) { + /* create or determine next desc. */ + if (create_new_chain) { + + td = pci_pool_alloc(ep->dev->data_requests, + gfp_flags, &dma_addr); + if (!td) + return -ENOMEM; + + td->status = 0; + } else if (i == buf_len) { + /* first td */ + td = (struct udc_data_dma *) phys_to_virt( + req->td_data->next); + td->status = 0; + } else { + td = (struct udc_data_dma *) phys_to_virt(last->next); + td->status = 0; + } + + + if (td) + td->bufptr = req->req.dma + i; /* assign buffer */ + else + break; + + /* short packet ? */ + if ((bytes - i) >= buf_len) { + txbytes = buf_len; + } else { + /* short packet */ + txbytes = bytes - i; + } + + /* link td and assign tx bytes */ + if (i == buf_len) { + if (create_new_chain) + req->td_data->next = dma_addr; + /* + else + req->td_data->next = virt_to_phys(td); + */ + /* write tx bytes */ + if (ep->in) { + /* first desc */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + ep->ep.maxpacket, + UDC_DMA_IN_STS_TXBYTES); + /* second desc */ + td->status = AMD_ADDBITS(td->status, + txbytes, + UDC_DMA_IN_STS_TXBYTES); + } + } else { + if (create_new_chain) + last->next = dma_addr; + /* + else + last->next = virt_to_phys(td); + */ + if (ep->in) { + /* write tx bytes */ + td->status = AMD_ADDBITS(td->status, + txbytes, + UDC_DMA_IN_STS_TXBYTES); + } + } + last = td; + } + /* set last bit */ + if (td) { + td->status |= AMD_BIT(UDC_DMA_IN_STS_L); + /* last desc. points to itself */ + req->td_data_last = td; + } + + return 0; +} + +/* Enabling RX DMA */ +static void udc_set_rde(struct udc *dev) +{ + u32 tmp; + + VDBG(dev, "udc_set_rde()\n"); + /* stop RDE timer */ + if (timer_pending(&udc_timer)) { + set_rde = 0; + mod_timer(&udc_timer, jiffies - 1); + } + /* set RDE */ + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_RDE); + writel(tmp, &dev->regs->ctl); +} + +/* Queues a request packet, called by gadget driver */ +static int +udc_queue(struct usb_ep *usbep, struct usb_request *usbreq, gfp_t gfp) +{ + int retval = 0; + u8 open_rxfifo = 0; + unsigned long iflags; + struct udc_ep *ep; + struct udc_request *req; + struct udc *dev; + u32 tmp; + + /* check the inputs */ + req = container_of(usbreq, struct udc_request, req); + + if (!usbep || !usbreq || !usbreq->complete || !usbreq->buf + || !list_empty(&req->queue)) + return -EINVAL; + + ep = container_of(usbep, struct udc_ep, ep); + if (!ep->ep.desc && (ep->num != 0 && ep->num != UDC_EP0OUT_IX)) + return -EINVAL; + + VDBG(ep->dev, "udc_queue(): ep%d-in=%d\n", ep->num, ep->in); + dev = ep->dev; + + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + /* map dma (usually done before) */ + if (ep->dma) { + VDBG(dev, "DMA map req %p\n", req); + retval = usb_gadget_map_request(&udc->gadget, usbreq, ep->in); + if (retval) + return retval; + } + + VDBG(dev, "%s queue req %p, len %d req->td_data=%p buf %p\n", + usbep->name, usbreq, usbreq->length, + req->td_data, usbreq->buf); + + spin_lock_irqsave(&dev->lock, iflags); + usbreq->actual = 0; + usbreq->status = -EINPROGRESS; + req->dma_done = 0; + + /* on empty queue just do first transfer */ + if (list_empty(&ep->queue)) { + /* zlp */ + if (usbreq->length == 0) { + /* IN zlp's are handled by hardware */ + complete_req(ep, req, 0); + VDBG(dev, "%s: zlp\n", ep->ep.name); + /* + * if set_config or set_intf is waiting for ack by zlp + * then set CSR_DONE + */ + if (dev->set_cfg_not_acked) { + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_CSR_DONE); + writel(tmp, &dev->regs->ctl); + dev->set_cfg_not_acked = 0; + } + /* setup command is ACK'ed now by zlp */ + if (dev->waiting_zlp_ack_ep0in) { + /* clear NAK by writing CNAK in EP0_IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + dev->ep[UDC_EP0IN_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], + UDC_EP0IN_IX); + dev->waiting_zlp_ack_ep0in = 0; + } + goto finished; + } + if (ep->dma) { + retval = prep_dma(ep, req, gfp); + if (retval != 0) + goto finished; + /* write desc pointer to enable DMA */ + if (ep->in) { + /* set HOST READY */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + UDC_DMA_IN_STS_BS_HOST_READY, + UDC_DMA_IN_STS_BS); + } + + /* disabled rx dma while descriptor update */ + if (!ep->in) { + /* stop RDE timer */ + if (timer_pending(&udc_timer)) { + set_rde = 0; + mod_timer(&udc_timer, jiffies - 1); + } + /* clear RDE */ + tmp = readl(&dev->regs->ctl); + tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_RDE); + writel(tmp, &dev->regs->ctl); + open_rxfifo = 1; + + /* + * if BNA occurred then let BNA dummy desc. + * point to current desc. + */ + if (ep->bna_occurred) { + VDBG(dev, "copy to BNA dummy desc.\n"); + memcpy(ep->bna_dummy_req->td_data, + req->td_data, + sizeof(struct udc_data_dma)); + } + } + /* write desc pointer */ + writel(req->td_phys, &ep->regs->desptr); + + /* clear NAK by writing CNAK */ + if (ep->naking) { + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->naking = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + + if (ep->in) { + /* enable ep irq */ + tmp = readl(&dev->regs->ep_irqmsk); + tmp &= AMD_UNMASK_BIT(ep->num); + writel(tmp, &dev->regs->ep_irqmsk); + } + } else if (ep->in) { + /* enable ep irq */ + tmp = readl(&dev->regs->ep_irqmsk); + tmp &= AMD_UNMASK_BIT(ep->num); + writel(tmp, &dev->regs->ep_irqmsk); + } + + } else if (ep->dma) { + + /* + * prep_dma not used for OUT ep's, this is not possible + * for PPB modes, because of chain creation reasons + */ + if (ep->in) { + retval = prep_dma(ep, req, gfp); + if (retval != 0) + goto finished; + } + } + VDBG(dev, "list_add\n"); + /* add request to ep queue */ + if (req) { + + list_add_tail(&req->queue, &ep->queue); + + /* open rxfifo if out data queued */ + if (open_rxfifo) { + /* enable DMA */ + req->dma_going = 1; + udc_set_rde(dev); + if (ep->num != UDC_EP0OUT_IX) + dev->data_ep_queued = 1; + } + /* stop OUT naking */ + if (!ep->in) { + if (!use_dma && udc_rxfifo_pending) { + DBG(dev, "udc_queue(): pending bytes in " + "rxfifo after nyet\n"); + /* + * read pending bytes afer nyet: + * referring to isr + */ + if (udc_rxfifo_read(ep, req)) { + /* finish */ + complete_req(ep, req, 0); + } + udc_rxfifo_pending = 0; + + } + } + } + +finished: + spin_unlock_irqrestore(&dev->lock, iflags); + return retval; +} + +/* Empty request queue of an endpoint; caller holds spinlock */ +static void empty_req_queue(struct udc_ep *ep) +{ + struct udc_request *req; + + ep->halted = 1; + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct udc_request, + queue); + complete_req(ep, req, -ESHUTDOWN); + } +} + +/* Dequeues a request packet, called by gadget driver */ +static int udc_dequeue(struct usb_ep *usbep, struct usb_request *usbreq) +{ + struct udc_ep *ep; + struct udc_request *req; + unsigned halted; + unsigned long iflags; + + ep = container_of(usbep, struct udc_ep, ep); + if (!usbep || !usbreq || (!ep->ep.desc && (ep->num != 0 + && ep->num != UDC_EP0OUT_IX))) + return -EINVAL; + + req = container_of(usbreq, struct udc_request, req); + + spin_lock_irqsave(&ep->dev->lock, iflags); + halted = ep->halted; + ep->halted = 1; + /* request in processing or next one */ + if (ep->queue.next == &req->queue) { + if (ep->dma && req->dma_going) { + if (ep->in) + ep->cancel_transfer = 1; + else { + u32 tmp; + u32 dma_sts; + /* stop potential receive DMA */ + tmp = readl(&udc->regs->ctl); + writel(tmp & AMD_UNMASK_BIT(UDC_DEVCTL_RDE), + &udc->regs->ctl); + /* + * Cancel transfer later in ISR + * if descriptor was touched. + */ + dma_sts = AMD_GETBITS(req->td_data->status, + UDC_DMA_OUT_STS_BS); + if (dma_sts != UDC_DMA_OUT_STS_BS_HOST_READY) + ep->cancel_transfer = 1; + else { + udc_init_bna_dummy(ep->req); + writel(ep->bna_dummy_req->td_phys, + &ep->regs->desptr); + } + writel(tmp, &udc->regs->ctl); + } + } + } + complete_req(ep, req, -ECONNRESET); + ep->halted = halted; + + spin_unlock_irqrestore(&ep->dev->lock, iflags); + return 0; +} + +/* Halt or clear halt of endpoint */ +static int +udc_set_halt(struct usb_ep *usbep, int halt) +{ + struct udc_ep *ep; + u32 tmp; + unsigned long iflags; + int retval = 0; + + if (!usbep) + return -EINVAL; + + pr_debug("set_halt %s: halt=%d\n", usbep->name, halt); + + ep = container_of(usbep, struct udc_ep, ep); + if (!ep->ep.desc && (ep->num != 0 && ep->num != UDC_EP0OUT_IX)) + return -EINVAL; + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc_stall_spinlock, iflags); + /* halt or clear halt */ + if (halt) { + if (ep->num == 0) + ep->dev->stall_ep0in = 1; + else { + /* + * set STALL + * rxfifo empty not taken into acount + */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl); + ep->halted = 1; + + /* setup poll timer */ + if (!timer_pending(&udc_pollstall_timer)) { + udc_pollstall_timer.expires = jiffies + + HZ * UDC_POLLSTALL_TIMER_USECONDS + / (1000 * 1000); + if (!stop_pollstall_timer) { + DBG(ep->dev, "start polltimer\n"); + add_timer(&udc_pollstall_timer); + } + } + } + } else { + /* ep is halted by set_halt() before */ + if (ep->halted) { + tmp = readl(&ep->regs->ctl); + /* clear stall bit */ + tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); + /* clear NAK by writing CNAK */ + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->halted = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + } + spin_unlock_irqrestore(&udc_stall_spinlock, iflags); + return retval; +} + +/* gadget interface */ +static const struct usb_ep_ops udc_ep_ops = { + .enable = udc_ep_enable, + .disable = udc_ep_disable, + + .alloc_request = udc_alloc_request, + .free_request = udc_free_request, + + .queue = udc_queue, + .dequeue = udc_dequeue, + + .set_halt = udc_set_halt, + /* fifo ops not implemented */ +}; + +/*-------------------------------------------------------------------------*/ + +/* Get frame counter (not implemented) */ +static int udc_get_frame(struct usb_gadget *gadget) +{ + return -EOPNOTSUPP; +} + +/* Remote wakeup gadget interface */ +static int udc_wakeup(struct usb_gadget *gadget) +{ + struct udc *dev; + + if (!gadget) + return -EINVAL; + dev = container_of(gadget, struct udc, gadget); + udc_remote_wakeup(dev); + + return 0; +} + +static int amd5536_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int amd5536_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); +/* gadget operations */ +static const struct usb_gadget_ops udc_ops = { + .wakeup = udc_wakeup, + .get_frame = udc_get_frame, + .udc_start = amd5536_udc_start, + .udc_stop = amd5536_udc_stop, +}; + +/* Setups endpoint parameters, adds endpoints to linked list */ +static void make_ep_lists(struct udc *dev) +{ + /* make gadget ep lists */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + list_add_tail(&dev->ep[UDC_EPIN_STATUS_IX].ep.ep_list, + &dev->gadget.ep_list); + list_add_tail(&dev->ep[UDC_EPIN_IX].ep.ep_list, + &dev->gadget.ep_list); + list_add_tail(&dev->ep[UDC_EPOUT_IX].ep.ep_list, + &dev->gadget.ep_list); + + /* fifo config */ + dev->ep[UDC_EPIN_STATUS_IX].fifo_depth = UDC_EPIN_SMALLINT_BUFF_SIZE; + if (dev->gadget.speed == USB_SPEED_FULL) + dev->ep[UDC_EPIN_IX].fifo_depth = UDC_FS_EPIN_BUFF_SIZE; + else if (dev->gadget.speed == USB_SPEED_HIGH) + dev->ep[UDC_EPIN_IX].fifo_depth = hs_tx_buf; + dev->ep[UDC_EPOUT_IX].fifo_depth = UDC_RXFIFO_SIZE; +} + +/* init registers at driver load time */ +static int startup_registers(struct udc *dev) +{ + u32 tmp; + + /* init controller by soft reset */ + udc_soft_reset(dev); + + /* mask not needed interrupts */ + udc_mask_unused_interrupts(dev); + + /* put into initial config */ + udc_basic_init(dev); + /* link up all endpoints */ + udc_setup_endpoints(dev); + + /* program speed */ + tmp = readl(&dev->regs->cfg); + if (use_fullspeed) + tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_FS, UDC_DEVCFG_SPD); + else + tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_HS, UDC_DEVCFG_SPD); + writel(tmp, &dev->regs->cfg); + + return 0; +} + +/* Inits UDC context */ +static void udc_basic_init(struct udc *dev) +{ + u32 tmp; + + DBG(dev, "udc_basic_init()\n"); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* stop RDE timer */ + if (timer_pending(&udc_timer)) { + set_rde = 0; + mod_timer(&udc_timer, jiffies - 1); + } + /* stop poll stall timer */ + if (timer_pending(&udc_pollstall_timer)) + mod_timer(&udc_pollstall_timer, jiffies - 1); + /* disable DMA */ + tmp = readl(&dev->regs->ctl); + tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_RDE); + tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_TDE); + writel(tmp, &dev->regs->ctl); + + /* enable dynamic CSR programming */ + tmp = readl(&dev->regs->cfg); + tmp |= AMD_BIT(UDC_DEVCFG_CSR_PRG); + /* set self powered */ + tmp |= AMD_BIT(UDC_DEVCFG_SP); + /* set remote wakeupable */ + tmp |= AMD_BIT(UDC_DEVCFG_RWKP); + writel(tmp, &dev->regs->cfg); + + make_ep_lists(dev); + + dev->data_ep_enabled = 0; + dev->data_ep_queued = 0; +} + +/* Sets initial endpoint parameters */ +static void udc_setup_endpoints(struct udc *dev) +{ + struct udc_ep *ep; + u32 tmp; + u32 reg; + + DBG(dev, "udc_setup_endpoints()\n"); + + /* read enum speed */ + tmp = readl(&dev->regs->sts); + tmp = AMD_GETBITS(tmp, UDC_DEVSTS_ENUM_SPEED); + if (tmp == UDC_DEVSTS_ENUM_SPEED_HIGH) + dev->gadget.speed = USB_SPEED_HIGH; + else if (tmp == UDC_DEVSTS_ENUM_SPEED_FULL) + dev->gadget.speed = USB_SPEED_FULL; + + /* set basic ep parameters */ + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) { + ep = &dev->ep[tmp]; + ep->dev = dev; + ep->ep.name = ep_string[tmp]; + ep->num = tmp; + /* txfifo size is calculated at enable time */ + ep->txfifo = dev->txfifo; + + /* fifo size */ + if (tmp < UDC_EPIN_NUM) { + ep->fifo_depth = UDC_TXFIFO_SIZE; + ep->in = 1; + } else { + ep->fifo_depth = UDC_RXFIFO_SIZE; + ep->in = 0; + + } + ep->regs = &dev->ep_regs[tmp]; + /* + * ep will be reset only if ep was not enabled before to avoid + * disabling ep interrupts when ENUM interrupt occurs but ep is + * not enabled by gadget driver + */ + if (!ep->ep.desc) + ep_init(dev->regs, ep); + + if (use_dma) { + /* + * ep->dma is not really used, just to indicate that + * DMA is active: remove this + * dma regs = dev control regs + */ + ep->dma = &dev->regs->ctl; + + /* nak OUT endpoints until enable - not for ep0 */ + if (tmp != UDC_EP0IN_IX && tmp != UDC_EP0OUT_IX + && tmp > UDC_EPIN_NUM) { + /* set NAK */ + reg = readl(&dev->ep[tmp].regs->ctl); + reg |= AMD_BIT(UDC_EPCTL_SNAK); + writel(reg, &dev->ep[tmp].regs->ctl); + dev->ep[tmp].naking = 1; + + } + } + } + /* EP0 max packet */ + if (dev->gadget.speed == USB_SPEED_FULL) { + dev->ep[UDC_EP0IN_IX].ep.maxpacket = UDC_FS_EP0IN_MAX_PKT_SIZE; + dev->ep[UDC_EP0OUT_IX].ep.maxpacket = + UDC_FS_EP0OUT_MAX_PKT_SIZE; + } else if (dev->gadget.speed == USB_SPEED_HIGH) { + dev->ep[UDC_EP0IN_IX].ep.maxpacket = UDC_EP0IN_MAX_PKT_SIZE; + dev->ep[UDC_EP0OUT_IX].ep.maxpacket = UDC_EP0OUT_MAX_PKT_SIZE; + } + + /* + * with suspend bug workaround, ep0 params for gadget driver + * are set at gadget driver bind() call + */ + dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IX].ep; + dev->ep[UDC_EP0IN_IX].halted = 0; + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + + /* init cfg/alt/int */ + dev->cur_config = 0; + dev->cur_intf = 0; + dev->cur_alt = 0; +} + +/* Bringup after Connect event, initial bringup to be ready for ep0 events */ +static void usb_connect(struct udc *dev) +{ + + dev_info(&dev->pdev->dev, "USB Connect\n"); + + dev->connected = 1; + + /* put into initial config */ + udc_basic_init(dev); + + /* enable device setup interrupts */ + udc_enable_dev_setup_interrupts(dev); +} + +/* + * Calls gadget with disconnect event and resets the UDC and makes + * initial bringup to be ready for ep0 events + */ +static void usb_disconnect(struct udc *dev) +{ + + dev_info(&dev->pdev->dev, "USB Disconnect\n"); + + dev->connected = 0; + + /* mask interrupts */ + udc_mask_unused_interrupts(dev); + + /* REVISIT there doesn't seem to be a point to having this + * talk to a tasklet ... do it directly, we already hold + * the spinlock needed to process the disconnect. + */ + + tasklet_schedule(&disconnect_tasklet); +} + +/* Tasklet for disconnect to be outside of interrupt context */ +static void udc_tasklet_disconnect(unsigned long par) +{ + struct udc *dev = (struct udc *)(*((struct udc **) par)); + u32 tmp; + + DBG(dev, "Tasklet disconnect\n"); + spin_lock_irq(&dev->lock); + + if (dev->driver) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + + /* empty queues */ + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) + empty_req_queue(&dev->ep[tmp]); + + } + + /* disable ep0 */ + ep_init(dev->regs, + &dev->ep[UDC_EP0IN_IX]); + + + if (!soft_reset_occured) { + /* init controller by soft reset */ + udc_soft_reset(dev); + soft_reset_occured++; + } + + /* re-enable dev interrupts */ + udc_enable_dev_setup_interrupts(dev); + /* back to full speed ? */ + if (use_fullspeed) { + tmp = readl(&dev->regs->cfg); + tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_FS, UDC_DEVCFG_SPD); + writel(tmp, &dev->regs->cfg); + } + + spin_unlock_irq(&dev->lock); +} + +/* Reset the UDC core */ +static void udc_soft_reset(struct udc *dev) +{ + unsigned long flags; + + DBG(dev, "Soft reset\n"); + /* + * reset possible waiting interrupts, because int. + * status is lost after soft reset, + * ep int. status reset + */ + writel(UDC_EPINT_MSK_DISABLE_ALL, &dev->regs->ep_irqsts); + /* device int. status reset */ + writel(UDC_DEV_MSK_DISABLE, &dev->regs->irqsts); + + spin_lock_irqsave(&udc_irq_spinlock, flags); + writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); + readl(&dev->regs->cfg); + spin_unlock_irqrestore(&udc_irq_spinlock, flags); + +} + +/* RDE timer callback to set RDE bit */ +static void udc_timer_function(unsigned long v) +{ + u32 tmp; + + spin_lock_irq(&udc_irq_spinlock); + + if (set_rde > 0) { + /* + * open the fifo if fifo was filled on last timer call + * conditionally + */ + if (set_rde > 1) { + /* set RDE to receive setup data */ + tmp = readl(&udc->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_RDE); + writel(tmp, &udc->regs->ctl); + set_rde = -1; + } else if (readl(&udc->regs->sts) + & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) { + /* + * if fifo empty setup polling, do not just + * open the fifo + */ + udc_timer.expires = jiffies + HZ/UDC_RDE_TIMER_DIV; + if (!stop_timer) + add_timer(&udc_timer); + } else { + /* + * fifo contains data now, setup timer for opening + * the fifo when timer expires to be able to receive + * setup packets, when data packets gets queued by + * gadget layer then timer will forced to expire with + * set_rde=0 (RDE is set in udc_queue()) + */ + set_rde++; + /* debug: lhadmot_timer_start = 221070 */ + udc_timer.expires = jiffies + HZ*UDC_RDE_TIMER_SECONDS; + if (!stop_timer) + add_timer(&udc_timer); + } + + } else + set_rde = -1; /* RDE was set by udc_queue() */ + spin_unlock_irq(&udc_irq_spinlock); + if (stop_timer) + complete(&on_exit); + +} + +/* Handle halt state, used in stall poll timer */ +static void udc_handle_halt_state(struct udc_ep *ep) +{ + u32 tmp; + /* set stall as long not halted */ + if (ep->halted == 1) { + tmp = readl(&ep->regs->ctl); + /* STALL cleared ? */ + if (!(tmp & AMD_BIT(UDC_EPCTL_S))) { + /* + * FIXME: MSC spec requires that stall remains + * even on receivng of CLEAR_FEATURE HALT. So + * we would set STALL again here to be compliant. + * But with current mass storage drivers this does + * not work (would produce endless host retries). + * So we clear halt on CLEAR_FEATURE. + * + DBG(ep->dev, "ep %d: set STALL again\n", ep->num); + tmp |= AMD_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl);*/ + + /* clear NAK by writing CNAK */ + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->halted = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + } +} + +/* Stall timer callback to poll S bit and set it again after */ +static void udc_pollstall_timer_function(unsigned long v) +{ + struct udc_ep *ep; + int halted = 0; + + spin_lock_irq(&udc_stall_spinlock); + /* + * only one IN and OUT endpoints are handled + * IN poll stall + */ + ep = &udc->ep[UDC_EPIN_IX]; + udc_handle_halt_state(ep); + if (ep->halted) + halted = 1; + /* OUT poll stall */ + ep = &udc->ep[UDC_EPOUT_IX]; + udc_handle_halt_state(ep); + if (ep->halted) + halted = 1; + + /* setup timer again when still halted */ + if (!stop_pollstall_timer && halted) { + udc_pollstall_timer.expires = jiffies + + HZ * UDC_POLLSTALL_TIMER_USECONDS + / (1000 * 1000); + add_timer(&udc_pollstall_timer); + } + spin_unlock_irq(&udc_stall_spinlock); + + if (stop_pollstall_timer) + complete(&on_pollstall_exit); +} + +/* Inits endpoint 0 so that SETUP packets are processed */ +static void activate_control_endpoints(struct udc *dev) +{ + u32 tmp; + + DBG(dev, "activate_control_endpoints\n"); + + /* flush fifo */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_F); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + + /* set ep0 directions */ + dev->ep[UDC_EP0IN_IX].in = 1; + dev->ep[UDC_EP0OUT_IX].in = 0; + + /* set buffer size (tx fifo entries) of EP0_IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->bufin_framenum); + if (dev->gadget.speed == USB_SPEED_FULL) + tmp = AMD_ADDBITS(tmp, UDC_FS_EPIN0_BUFF_SIZE, + UDC_EPIN_BUFF_SIZE); + else if (dev->gadget.speed == USB_SPEED_HIGH) + tmp = AMD_ADDBITS(tmp, UDC_EPIN0_BUFF_SIZE, + UDC_EPIN_BUFF_SIZE); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->bufin_framenum); + + /* set max packet size of EP0_IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->bufout_maxpkt); + if (dev->gadget.speed == USB_SPEED_FULL) + tmp = AMD_ADDBITS(tmp, UDC_FS_EP0IN_MAX_PKT_SIZE, + UDC_EP_MAX_PKT_SIZE); + else if (dev->gadget.speed == USB_SPEED_HIGH) + tmp = AMD_ADDBITS(tmp, UDC_EP0IN_MAX_PKT_SIZE, + UDC_EP_MAX_PKT_SIZE); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->bufout_maxpkt); + + /* set max packet size of EP0_OUT */ + tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->bufout_maxpkt); + if (dev->gadget.speed == USB_SPEED_FULL) + tmp = AMD_ADDBITS(tmp, UDC_FS_EP0OUT_MAX_PKT_SIZE, + UDC_EP_MAX_PKT_SIZE); + else if (dev->gadget.speed == USB_SPEED_HIGH) + tmp = AMD_ADDBITS(tmp, UDC_EP0OUT_MAX_PKT_SIZE, + UDC_EP_MAX_PKT_SIZE); + writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->bufout_maxpkt); + + /* set max packet size of EP0 in UDC CSR */ + tmp = readl(&dev->csr->ne[0]); + if (dev->gadget.speed == USB_SPEED_FULL) + tmp = AMD_ADDBITS(tmp, UDC_FS_EP0OUT_MAX_PKT_SIZE, + UDC_CSR_NE_MAX_PKT); + else if (dev->gadget.speed == USB_SPEED_HIGH) + tmp = AMD_ADDBITS(tmp, UDC_EP0OUT_MAX_PKT_SIZE, + UDC_CSR_NE_MAX_PKT); + writel(tmp, &dev->csr->ne[0]); + + if (use_dma) { + dev->ep[UDC_EP0OUT_IX].td->status |= + AMD_BIT(UDC_DMA_OUT_STS_L); + /* write dma desc address */ + writel(dev->ep[UDC_EP0OUT_IX].td_stp_dma, + &dev->ep[UDC_EP0OUT_IX].regs->subptr); + writel(dev->ep[UDC_EP0OUT_IX].td_phys, + &dev->ep[UDC_EP0OUT_IX].regs->desptr); + /* stop RDE timer */ + if (timer_pending(&udc_timer)) { + set_rde = 0; + mod_timer(&udc_timer, jiffies - 1); + } + /* stop pollstall timer */ + if (timer_pending(&udc_pollstall_timer)) + mod_timer(&udc_pollstall_timer, jiffies - 1); + /* enable DMA */ + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_MODE) + | AMD_BIT(UDC_DEVCTL_RDE) + | AMD_BIT(UDC_DEVCTL_TDE); + if (use_dma_bufferfill_mode) + tmp |= AMD_BIT(UDC_DEVCTL_BF); + else if (use_dma_ppb_du) + tmp |= AMD_BIT(UDC_DEVCTL_DU); + writel(tmp, &dev->regs->ctl); + } + + /* clear NAK by writing CNAK for EP0IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + dev->ep[UDC_EP0IN_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], UDC_EP0IN_IX); + + /* clear NAK by writing CNAK for EP0OUT */ + tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->ctl); + dev->ep[UDC_EP0OUT_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], UDC_EP0OUT_IX); +} + +/* Make endpoint 0 ready for control traffic */ +static int setup_ep0(struct udc *dev) +{ + activate_control_endpoints(dev); + /* enable ep0 interrupts */ + udc_enable_ep0_interrupts(dev); + /* enable device setup interrupts */ + udc_enable_dev_setup_interrupts(dev); + + return 0; +} + +/* Called by gadget driver to register itself */ +static int amd5536_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct udc *dev = to_amd5536_udc(g); + u32 tmp; + + driver->driver.bus = NULL; + dev->driver = driver; + + /* Some gadget drivers use both ep0 directions. + * NOTE: to gadget driver, ep0 is just one endpoint... + */ + dev->ep[UDC_EP0OUT_IX].ep.driver_data = + dev->ep[UDC_EP0IN_IX].ep.driver_data; + + /* get ready for ep0 traffic */ + setup_ep0(dev); + + /* clear SD */ + tmp = readl(&dev->regs->ctl); + tmp = tmp & AMD_CLEAR_BIT(UDC_DEVCTL_SD); + writel(tmp, &dev->regs->ctl); + + usb_connect(dev); + + return 0; +} + +/* shutdown requests and disconnect from gadget */ +static void +shutdown(struct udc *dev, struct usb_gadget_driver *driver) +__releases(dev->lock) +__acquires(dev->lock) +{ + int tmp; + + /* empty queues and init hardware */ + udc_basic_init(dev); + + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) + empty_req_queue(&dev->ep[tmp]); + + udc_setup_endpoints(dev); +} + +/* Called by gadget driver to unregister itself */ +static int amd5536_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct udc *dev = to_amd5536_udc(g); + unsigned long flags; + u32 tmp; + + spin_lock_irqsave(&dev->lock, flags); + udc_mask_unused_interrupts(dev); + shutdown(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + dev->driver = NULL; + + /* set SD */ + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_SD); + writel(tmp, &dev->regs->ctl); + + return 0; +} + +/* Clear pending NAK bits */ +static void udc_process_cnak_queue(struct udc *dev) +{ + u32 tmp; + u32 reg; + + /* check epin's */ + DBG(dev, "CNAK pending queue processing\n"); + for (tmp = 0; tmp < UDC_EPIN_NUM_USED; tmp++) { + if (cnak_pending & (1 << tmp)) { + DBG(dev, "CNAK pending for ep%d\n", tmp); + /* clear NAK by writing CNAK */ + reg = readl(&dev->ep[tmp].regs->ctl); + reg |= AMD_BIT(UDC_EPCTL_CNAK); + writel(reg, &dev->ep[tmp].regs->ctl); + dev->ep[tmp].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[tmp], dev->ep[tmp].num); + } + } + /* ... and ep0out */ + if (cnak_pending & (1 << UDC_EP0OUT_IX)) { + DBG(dev, "CNAK pending for ep%d\n", UDC_EP0OUT_IX); + /* clear NAK by writing CNAK */ + reg = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); + reg |= AMD_BIT(UDC_EPCTL_CNAK); + writel(reg, &dev->ep[UDC_EP0OUT_IX].regs->ctl); + dev->ep[UDC_EP0OUT_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], + dev->ep[UDC_EP0OUT_IX].num); + } +} + +/* Enabling RX DMA after setup packet */ +static void udc_ep0_set_rde(struct udc *dev) +{ + if (use_dma) { + /* + * only enable RXDMA when no data endpoint enabled + * or data is queued + */ + if (!dev->data_ep_enabled || dev->data_ep_queued) { + udc_set_rde(dev); + } else { + /* + * setup timer for enabling RDE (to not enable + * RXFIFO DMA for data endpoints to early) + */ + if (set_rde != 0 && !timer_pending(&udc_timer)) { + udc_timer.expires = + jiffies + HZ/UDC_RDE_TIMER_DIV; + set_rde = 1; + if (!stop_timer) + add_timer(&udc_timer); + } + } + } +} + + +/* Interrupt handler for data OUT traffic */ +static irqreturn_t udc_data_out_isr(struct udc *dev, int ep_ix) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + struct udc_ep *ep; + struct udc_request *req; + unsigned int count; + struct udc_data_dma *td = NULL; + unsigned dma_done; + + VDBG(dev, "ep%d irq\n", ep_ix); + ep = &dev->ep[ep_ix]; + + tmp = readl(&ep->regs->sts); + if (use_dma) { + /* BNA event ? */ + if (tmp & AMD_BIT(UDC_EPSTS_BNA)) { + DBG(dev, "BNA ep%dout occurred - DESPTR = %x\n", + ep->num, readl(&ep->regs->desptr)); + /* clear BNA */ + writel(tmp | AMD_BIT(UDC_EPSTS_BNA), &ep->regs->sts); + if (!ep->cancel_transfer) + ep->bna_occurred = 1; + else + ep->cancel_transfer = 0; + ret_val = IRQ_HANDLED; + goto finished; + } + } + /* HE event ? */ + if (tmp & AMD_BIT(UDC_EPSTS_HE)) { + dev_err(&dev->pdev->dev, "HE ep%dout occurred\n", ep->num); + + /* clear HE */ + writel(tmp | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); + ret_val = IRQ_HANDLED; + goto finished; + } + + if (!list_empty(&ep->queue)) { + + /* next request */ + req = list_entry(ep->queue.next, + struct udc_request, queue); + } else { + req = NULL; + udc_rxfifo_pending = 1; + } + VDBG(dev, "req = %p\n", req); + /* fifo mode */ + if (!use_dma) { + + /* read fifo */ + if (req && udc_rxfifo_read(ep, req)) { + ret_val = IRQ_HANDLED; + + /* finish */ + complete_req(ep, req, 0); + /* next request */ + if (!list_empty(&ep->queue) && !ep->halted) { + req = list_entry(ep->queue.next, + struct udc_request, queue); + } else + req = NULL; + } + + /* DMA */ + } else if (!ep->cancel_transfer && req != NULL) { + ret_val = IRQ_HANDLED; + + /* check for DMA done */ + if (!use_dma_ppb) { + dma_done = AMD_GETBITS(req->td_data->status, + UDC_DMA_OUT_STS_BS); + /* packet per buffer mode - rx bytes */ + } else { + /* + * if BNA occurred then recover desc. from + * BNA dummy desc. + */ + if (ep->bna_occurred) { + VDBG(dev, "Recover desc. from BNA dummy\n"); + memcpy(req->td_data, ep->bna_dummy_req->td_data, + sizeof(struct udc_data_dma)); + ep->bna_occurred = 0; + udc_init_bna_dummy(ep->req); + } + td = udc_get_last_dma_desc(req); + dma_done = AMD_GETBITS(td->status, UDC_DMA_OUT_STS_BS); + } + if (dma_done == UDC_DMA_OUT_STS_BS_DMA_DONE) { + /* buffer fill mode - rx bytes */ + if (!use_dma_ppb) { + /* received number bytes */ + count = AMD_GETBITS(req->td_data->status, + UDC_DMA_OUT_STS_RXBYTES); + VDBG(dev, "rx bytes=%u\n", count); + /* packet per buffer mode - rx bytes */ + } else { + VDBG(dev, "req->td_data=%p\n", req->td_data); + VDBG(dev, "last desc = %p\n", td); + /* received number bytes */ + if (use_dma_ppb_du) { + /* every desc. counts bytes */ + count = udc_get_ppbdu_rxbytes(req); + } else { + /* last desc. counts bytes */ + count = AMD_GETBITS(td->status, + UDC_DMA_OUT_STS_RXBYTES); + if (!count && req->req.length + == UDC_DMA_MAXPACKET) { + /* + * on 64k packets the RXBYTES + * field is zero + */ + count = UDC_DMA_MAXPACKET; + } + } + VDBG(dev, "last desc rx bytes=%u\n", count); + } + + tmp = req->req.length - req->req.actual; + if (count > tmp) { + if ((tmp % ep->ep.maxpacket) != 0) { + DBG(dev, "%s: rx %db, space=%db\n", + ep->ep.name, count, tmp); + req->req.status = -EOVERFLOW; + } + count = tmp; + } + req->req.actual += count; + req->dma_going = 0; + /* complete request */ + complete_req(ep, req, 0); + + /* next request */ + if (!list_empty(&ep->queue) && !ep->halted) { + req = list_entry(ep->queue.next, + struct udc_request, + queue); + /* + * DMA may be already started by udc_queue() + * called by gadget drivers completion + * routine. This happens when queue + * holds one request only. + */ + if (req->dma_going == 0) { + /* next dma */ + if (prep_dma(ep, req, GFP_ATOMIC) != 0) + goto finished; + /* write desc pointer */ + writel(req->td_phys, + &ep->regs->desptr); + req->dma_going = 1; + /* enable DMA */ + udc_set_rde(dev); + } + } else { + /* + * implant BNA dummy descriptor to allow + * RXFIFO opening by RDE + */ + if (ep->bna_dummy_req) { + /* write desc pointer */ + writel(ep->bna_dummy_req->td_phys, + &ep->regs->desptr); + ep->bna_occurred = 0; + } + + /* + * schedule timer for setting RDE if queue + * remains empty to allow ep0 packets pass + * through + */ + if (set_rde != 0 + && !timer_pending(&udc_timer)) { + udc_timer.expires = + jiffies + + HZ*UDC_RDE_TIMER_SECONDS; + set_rde = 1; + if (!stop_timer) + add_timer(&udc_timer); + } + if (ep->num != UDC_EP0OUT_IX) + dev->data_ep_queued = 0; + } + + } else { + /* + * RX DMA must be reenabled for each desc in PPBDU mode + * and must be enabled for PPBNDU mode in case of BNA + */ + udc_set_rde(dev); + } + + } else if (ep->cancel_transfer) { + ret_val = IRQ_HANDLED; + ep->cancel_transfer = 0; + } + + /* check pending CNAKS */ + if (cnak_pending) { + /* CNAk processing when rxfifo empty only */ + if (readl(&dev->regs->sts) & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) + udc_process_cnak_queue(dev); + } + + /* clear OUT bits in ep status */ + writel(UDC_EPSTS_OUT_CLEAR, &ep->regs->sts); +finished: + return ret_val; +} + +/* Interrupt handler for data IN traffic */ +static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + u32 epsts; + struct udc_ep *ep; + struct udc_request *req; + struct udc_data_dma *td; + unsigned dma_done; + unsigned len; + + ep = &dev->ep[ep_ix]; + + epsts = readl(&ep->regs->sts); + if (use_dma) { + /* BNA ? */ + if (epsts & AMD_BIT(UDC_EPSTS_BNA)) { + dev_err(&dev->pdev->dev, + "BNA ep%din occurred - DESPTR = %08lx\n", + ep->num, + (unsigned long) readl(&ep->regs->desptr)); + + /* clear BNA */ + writel(epsts, &ep->regs->sts); + ret_val = IRQ_HANDLED; + goto finished; + } + } + /* HE event ? */ + if (epsts & AMD_BIT(UDC_EPSTS_HE)) { + dev_err(&dev->pdev->dev, + "HE ep%dn occurred - DESPTR = %08lx\n", + ep->num, (unsigned long) readl(&ep->regs->desptr)); + + /* clear HE */ + writel(epsts | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); + ret_val = IRQ_HANDLED; + goto finished; + } + + /* DMA completion */ + if (epsts & AMD_BIT(UDC_EPSTS_TDC)) { + VDBG(dev, "TDC set- completion\n"); + ret_val = IRQ_HANDLED; + if (!ep->cancel_transfer && !list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct udc_request, queue); + /* + * length bytes transferred + * check dma done of last desc. in PPBDU mode + */ + if (use_dma_ppb_du) { + td = udc_get_last_dma_desc(req); + if (td) { + dma_done = + AMD_GETBITS(td->status, + UDC_DMA_IN_STS_BS); + /* don't care DMA done */ + req->req.actual = req->req.length; + } + } else { + /* assume all bytes transferred */ + req->req.actual = req->req.length; + } + + if (req->req.actual == req->req.length) { + /* complete req */ + complete_req(ep, req, 0); + req->dma_going = 0; + /* further request available ? */ + if (list_empty(&ep->queue)) { + /* disable interrupt */ + tmp = readl(&dev->regs->ep_irqmsk); + tmp |= AMD_BIT(ep->num); + writel(tmp, &dev->regs->ep_irqmsk); + } + } + } + ep->cancel_transfer = 0; + + } + /* + * status reg has IN bit set and TDC not set (if TDC was handled, + * IN must not be handled (UDC defect) ? + */ + if ((epsts & AMD_BIT(UDC_EPSTS_IN)) + && !(epsts & AMD_BIT(UDC_EPSTS_TDC))) { + ret_val = IRQ_HANDLED; + if (!list_empty(&ep->queue)) { + /* next request */ + req = list_entry(ep->queue.next, + struct udc_request, queue); + /* FIFO mode */ + if (!use_dma) { + /* write fifo */ + udc_txfifo_write(ep, &req->req); + len = req->req.length - req->req.actual; + if (len > ep->ep.maxpacket) + len = ep->ep.maxpacket; + req->req.actual += len; + if (req->req.actual == req->req.length + || (len != ep->ep.maxpacket)) { + /* complete req */ + complete_req(ep, req, 0); + } + /* DMA */ + } else if (req && !req->dma_going) { + VDBG(dev, "IN DMA : req=%p req->td_data=%p\n", + req, req->td_data); + if (req->td_data) { + + req->dma_going = 1; + + /* + * unset L bit of first desc. + * for chain + */ + if (use_dma_ppb && req->req.length > + ep->ep.maxpacket) { + req->td_data->status &= + AMD_CLEAR_BIT( + UDC_DMA_IN_STS_L); + } + + /* write desc pointer */ + writel(req->td_phys, &ep->regs->desptr); + + /* set HOST READY */ + req->td_data->status = + AMD_ADDBITS( + req->td_data->status, + UDC_DMA_IN_STS_BS_HOST_READY, + UDC_DMA_IN_STS_BS); + + /* set poll demand bit */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_P); + writel(tmp, &ep->regs->ctl); + } + } + + } else if (!use_dma && ep->in) { + /* disable interrupt */ + tmp = readl( + &dev->regs->ep_irqmsk); + tmp |= AMD_BIT(ep->num); + writel(tmp, + &dev->regs->ep_irqmsk); + } + } + /* clear status bits */ + writel(epsts, &ep->regs->sts); + +finished: + return ret_val; + +} + +/* Interrupt handler for Control OUT traffic */ +static irqreturn_t udc_control_out_isr(struct udc *dev) +__releases(dev->lock) +__acquires(dev->lock) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + int setup_supported; + u32 count; + int set = 0; + struct udc_ep *ep; + struct udc_ep *ep_tmp; + + ep = &dev->ep[UDC_EP0OUT_IX]; + + /* clear irq */ + writel(AMD_BIT(UDC_EPINT_OUT_EP0), &dev->regs->ep_irqsts); + + tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->sts); + /* check BNA and clear if set */ + if (tmp & AMD_BIT(UDC_EPSTS_BNA)) { + VDBG(dev, "ep0: BNA set\n"); + writel(AMD_BIT(UDC_EPSTS_BNA), + &dev->ep[UDC_EP0OUT_IX].regs->sts); + ep->bna_occurred = 1; + ret_val = IRQ_HANDLED; + goto finished; + } + + /* type of data: SETUP or DATA 0 bytes */ + tmp = AMD_GETBITS(tmp, UDC_EPSTS_OUT); + VDBG(dev, "data_typ = %x\n", tmp); + + /* setup data */ + if (tmp == UDC_EPSTS_OUT_SETUP) { + ret_val = IRQ_HANDLED; + + ep->dev->stall_ep0in = 0; + dev->waiting_zlp_ack_ep0in = 0; + + /* set NAK for EP0_IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_SNAK); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + dev->ep[UDC_EP0IN_IX].naking = 1; + /* get setup data */ + if (use_dma) { + + /* clear OUT bits in ep status */ + writel(UDC_EPSTS_OUT_CLEAR, + &dev->ep[UDC_EP0OUT_IX].regs->sts); + + setup_data.data[0] = + dev->ep[UDC_EP0OUT_IX].td_stp->data12; + setup_data.data[1] = + dev->ep[UDC_EP0OUT_IX].td_stp->data34; + /* set HOST READY */ + dev->ep[UDC_EP0OUT_IX].td_stp->status = + UDC_DMA_STP_STS_BS_HOST_READY; + } else { + /* read fifo */ + udc_rxfifo_read_dwords(dev, setup_data.data, 2); + } + + /* determine direction of control data */ + if ((setup_data.request.bRequestType & USB_DIR_IN) != 0) { + dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IX].ep; + /* enable RDE */ + udc_ep0_set_rde(dev); + set = 0; + } else { + dev->gadget.ep0 = &dev->ep[UDC_EP0OUT_IX].ep; + /* + * implant BNA dummy descriptor to allow RXFIFO opening + * by RDE + */ + if (ep->bna_dummy_req) { + /* write desc pointer */ + writel(ep->bna_dummy_req->td_phys, + &dev->ep[UDC_EP0OUT_IX].regs->desptr); + ep->bna_occurred = 0; + } + + set = 1; + dev->ep[UDC_EP0OUT_IX].naking = 1; + /* + * setup timer for enabling RDE (to not enable + * RXFIFO DMA for data to early) + */ + set_rde = 1; + if (!timer_pending(&udc_timer)) { + udc_timer.expires = jiffies + + HZ/UDC_RDE_TIMER_DIV; + if (!stop_timer) + add_timer(&udc_timer); + } + } + + /* + * mass storage reset must be processed here because + * next packet may be a CLEAR_FEATURE HALT which would not + * clear the stall bit when no STALL handshake was received + * before (autostall can cause this) + */ + if (setup_data.data[0] == UDC_MSCRES_DWORD0 + && setup_data.data[1] == UDC_MSCRES_DWORD1) { + DBG(dev, "MSC Reset\n"); + /* + * clear stall bits + * only one IN and OUT endpoints are handled + */ + ep_tmp = &udc->ep[UDC_EPIN_IX]; + udc_set_halt(&ep_tmp->ep, 0); + ep_tmp = &udc->ep[UDC_EPOUT_IX]; + udc_set_halt(&ep_tmp->ep, 0); + } + + /* call gadget with setup data received */ + spin_unlock(&dev->lock); + setup_supported = dev->driver->setup(&dev->gadget, + &setup_data.request); + spin_lock(&dev->lock); + + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + /* ep0 in returns data (not zlp) on IN phase */ + if (setup_supported >= 0 && setup_supported < + UDC_EP0IN_MAXPACKET) { + /* clear NAK by writing CNAK in EP0_IN */ + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + dev->ep[UDC_EP0IN_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], UDC_EP0IN_IX); + + /* if unsupported request then stall */ + } else if (setup_supported < 0) { + tmp |= AMD_BIT(UDC_EPCTL_S); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + } else + dev->waiting_zlp_ack_ep0in = 1; + + + /* clear NAK by writing CNAK in EP0_OUT */ + if (!set) { + tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->ctl); + dev->ep[UDC_EP0OUT_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], UDC_EP0OUT_IX); + } + + if (!use_dma) { + /* clear OUT bits in ep status */ + writel(UDC_EPSTS_OUT_CLEAR, + &dev->ep[UDC_EP0OUT_IX].regs->sts); + } + + /* data packet 0 bytes */ + } else if (tmp == UDC_EPSTS_OUT_DATA) { + /* clear OUT bits in ep status */ + writel(UDC_EPSTS_OUT_CLEAR, &dev->ep[UDC_EP0OUT_IX].regs->sts); + + /* get setup data: only 0 packet */ + if (use_dma) { + /* no req if 0 packet, just reactivate */ + if (list_empty(&dev->ep[UDC_EP0OUT_IX].queue)) { + VDBG(dev, "ZLP\n"); + + /* set HOST READY */ + dev->ep[UDC_EP0OUT_IX].td->status = + AMD_ADDBITS( + dev->ep[UDC_EP0OUT_IX].td->status, + UDC_DMA_OUT_STS_BS_HOST_READY, + UDC_DMA_OUT_STS_BS); + /* enable RDE */ + udc_ep0_set_rde(dev); + ret_val = IRQ_HANDLED; + + } else { + /* control write */ + ret_val |= udc_data_out_isr(dev, UDC_EP0OUT_IX); + /* re-program desc. pointer for possible ZLPs */ + writel(dev->ep[UDC_EP0OUT_IX].td_phys, + &dev->ep[UDC_EP0OUT_IX].regs->desptr); + /* enable RDE */ + udc_ep0_set_rde(dev); + } + } else { + + /* received number bytes */ + count = readl(&dev->ep[UDC_EP0OUT_IX].regs->sts); + count = AMD_GETBITS(count, UDC_EPSTS_RX_PKT_SIZE); + /* out data for fifo mode not working */ + count = 0; + + /* 0 packet or real data ? */ + if (count != 0) { + ret_val |= udc_data_out_isr(dev, UDC_EP0OUT_IX); + } else { + /* dummy read confirm */ + readl(&dev->ep[UDC_EP0OUT_IX].regs->confirm); + ret_val = IRQ_HANDLED; + } + } + } + + /* check pending CNAKS */ + if (cnak_pending) { + /* CNAk processing when rxfifo empty only */ + if (readl(&dev->regs->sts) & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) + udc_process_cnak_queue(dev); + } + +finished: + return ret_val; +} + +/* Interrupt handler for Control IN traffic */ +static irqreturn_t udc_control_in_isr(struct udc *dev) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + struct udc_ep *ep; + struct udc_request *req; + unsigned len; + + ep = &dev->ep[UDC_EP0IN_IX]; + + /* clear irq */ + writel(AMD_BIT(UDC_EPINT_IN_EP0), &dev->regs->ep_irqsts); + + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->sts); + /* DMA completion */ + if (tmp & AMD_BIT(UDC_EPSTS_TDC)) { + VDBG(dev, "isr: TDC clear\n"); + ret_val = IRQ_HANDLED; + + /* clear TDC bit */ + writel(AMD_BIT(UDC_EPSTS_TDC), + &dev->ep[UDC_EP0IN_IX].regs->sts); + + /* status reg has IN bit set ? */ + } else if (tmp & AMD_BIT(UDC_EPSTS_IN)) { + ret_val = IRQ_HANDLED; + + if (ep->dma) { + /* clear IN bit */ + writel(AMD_BIT(UDC_EPSTS_IN), + &dev->ep[UDC_EP0IN_IX].regs->sts); + } + if (dev->stall_ep0in) { + DBG(dev, "stall ep0in\n"); + /* halt ep0in */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl); + } else { + if (!list_empty(&ep->queue)) { + /* next request */ + req = list_entry(ep->queue.next, + struct udc_request, queue); + + if (ep->dma) { + /* write desc pointer */ + writel(req->td_phys, &ep->regs->desptr); + /* set HOST READY */ + req->td_data->status = + AMD_ADDBITS( + req->td_data->status, + UDC_DMA_STP_STS_BS_HOST_READY, + UDC_DMA_STP_STS_BS); + + /* set poll demand bit */ + tmp = + readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_P); + writel(tmp, + &dev->ep[UDC_EP0IN_IX].regs->ctl); + + /* all bytes will be transferred */ + req->req.actual = req->req.length; + + /* complete req */ + complete_req(ep, req, 0); + + } else { + /* write fifo */ + udc_txfifo_write(ep, &req->req); + + /* lengh bytes transferred */ + len = req->req.length - req->req.actual; + if (len > ep->ep.maxpacket) + len = ep->ep.maxpacket; + + req->req.actual += len; + if (req->req.actual == req->req.length + || (len != ep->ep.maxpacket)) { + /* complete req */ + complete_req(ep, req, 0); + } + } + + } + } + ep->halted = 0; + dev->stall_ep0in = 0; + if (!ep->dma) { + /* clear IN bit */ + writel(AMD_BIT(UDC_EPSTS_IN), + &dev->ep[UDC_EP0IN_IX].regs->sts); + } + } + + return ret_val; +} + + +/* Interrupt handler for global device events */ +static irqreturn_t udc_dev_isr(struct udc *dev, u32 dev_irq) +__releases(dev->lock) +__acquires(dev->lock) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + u32 cfg; + struct udc_ep *ep; + u16 i; + u8 udc_csr_epix; + + /* SET_CONFIG irq ? */ + if (dev_irq & AMD_BIT(UDC_DEVINT_SC)) { + ret_val = IRQ_HANDLED; + + /* read config value */ + tmp = readl(&dev->regs->sts); + cfg = AMD_GETBITS(tmp, UDC_DEVSTS_CFG); + DBG(dev, "SET_CONFIG interrupt: config=%d\n", cfg); + dev->cur_config = cfg; + dev->set_cfg_not_acked = 1; + + /* make usb request for gadget driver */ + memset(&setup_data, 0 , sizeof(union udc_setup_data)); + setup_data.request.bRequest = USB_REQ_SET_CONFIGURATION; + setup_data.request.wValue = cpu_to_le16(dev->cur_config); + + /* programm the NE registers */ + for (i = 0; i < UDC_EP_NUM; i++) { + ep = &dev->ep[i]; + if (ep->in) { + + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num; + + + /* OUT ep */ + } else { + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; + } + + tmp = readl(&dev->csr->ne[udc_csr_epix]); + /* ep cfg */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_config, + UDC_CSR_NE_CFG); + /* write reg */ + writel(tmp, &dev->csr->ne[udc_csr_epix]); + + /* clear stall bits */ + ep->halted = 0; + tmp = readl(&ep->regs->ctl); + tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl); + } + /* call gadget zero with setup data received */ + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &setup_data.request); + spin_lock(&dev->lock); + + } /* SET_INTERFACE ? */ + if (dev_irq & AMD_BIT(UDC_DEVINT_SI)) { + ret_val = IRQ_HANDLED; + + dev->set_cfg_not_acked = 1; + /* read interface and alt setting values */ + tmp = readl(&dev->regs->sts); + dev->cur_alt = AMD_GETBITS(tmp, UDC_DEVSTS_ALT); + dev->cur_intf = AMD_GETBITS(tmp, UDC_DEVSTS_INTF); + + /* make usb request for gadget driver */ + memset(&setup_data, 0 , sizeof(union udc_setup_data)); + setup_data.request.bRequest = USB_REQ_SET_INTERFACE; + setup_data.request.bRequestType = USB_RECIP_INTERFACE; + setup_data.request.wValue = cpu_to_le16(dev->cur_alt); + setup_data.request.wIndex = cpu_to_le16(dev->cur_intf); + + DBG(dev, "SET_INTERFACE interrupt: alt=%d intf=%d\n", + dev->cur_alt, dev->cur_intf); + + /* programm the NE registers */ + for (i = 0; i < UDC_EP_NUM; i++) { + ep = &dev->ep[i]; + if (ep->in) { + + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num; + + + /* OUT ep */ + } else { + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; + } + + /* UDC CSR reg */ + /* set ep values */ + tmp = readl(&dev->csr->ne[udc_csr_epix]); + /* ep interface */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_intf, + UDC_CSR_NE_INTF); + /* tmp = AMD_ADDBITS(tmp, 2, UDC_CSR_NE_INTF); */ + /* ep alt */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_alt, + UDC_CSR_NE_ALT); + /* write reg */ + writel(tmp, &dev->csr->ne[udc_csr_epix]); + + /* clear stall bits */ + ep->halted = 0; + tmp = readl(&ep->regs->ctl); + tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl); + } + + /* call gadget zero with setup data received */ + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &setup_data.request); + spin_lock(&dev->lock); + + } /* USB reset */ + if (dev_irq & AMD_BIT(UDC_DEVINT_UR)) { + DBG(dev, "USB Reset interrupt\n"); + ret_val = IRQ_HANDLED; + + /* allow soft reset when suspend occurs */ + soft_reset_occured = 0; + + dev->waiting_zlp_ack_ep0in = 0; + dev->set_cfg_not_acked = 0; + + /* mask not needed interrupts */ + udc_mask_unused_interrupts(dev); + + /* call gadget to resume and reset configs etc. */ + spin_unlock(&dev->lock); + if (dev->sys_suspended && dev->driver->resume) { + dev->driver->resume(&dev->gadget); + dev->sys_suspended = 0; + } + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + + /* disable ep0 to empty req queue */ + empty_req_queue(&dev->ep[UDC_EP0IN_IX]); + ep_init(dev->regs, &dev->ep[UDC_EP0IN_IX]); + + /* soft reset when rxfifo not empty */ + tmp = readl(&dev->regs->sts); + if (!(tmp & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) + && !soft_reset_after_usbreset_occured) { + udc_soft_reset(dev); + soft_reset_after_usbreset_occured++; + } + + /* + * DMA reset to kill potential old DMA hw hang, + * POLL bit is already reset by ep_init() through + * disconnect() + */ + DBG(dev, "DMA machine reset\n"); + tmp = readl(&dev->regs->cfg); + writel(tmp | AMD_BIT(UDC_DEVCFG_DMARST), &dev->regs->cfg); + writel(tmp, &dev->regs->cfg); + + /* put into initial config */ + udc_basic_init(dev); + + /* enable device setup interrupts */ + udc_enable_dev_setup_interrupts(dev); + + /* enable suspend interrupt */ + tmp = readl(&dev->regs->irqmsk); + tmp &= AMD_UNMASK_BIT(UDC_DEVINT_US); + writel(tmp, &dev->regs->irqmsk); + + } /* USB suspend */ + if (dev_irq & AMD_BIT(UDC_DEVINT_US)) { + DBG(dev, "USB Suspend interrupt\n"); + ret_val = IRQ_HANDLED; + if (dev->driver->suspend) { + spin_unlock(&dev->lock); + dev->sys_suspended = 1; + dev->driver->suspend(&dev->gadget); + spin_lock(&dev->lock); + } + } /* new speed ? */ + if (dev_irq & AMD_BIT(UDC_DEVINT_ENUM)) { + DBG(dev, "ENUM interrupt\n"); + ret_val = IRQ_HANDLED; + soft_reset_after_usbreset_occured = 0; + + /* disable ep0 to empty req queue */ + empty_req_queue(&dev->ep[UDC_EP0IN_IX]); + ep_init(dev->regs, &dev->ep[UDC_EP0IN_IX]); + + /* link up all endpoints */ + udc_setup_endpoints(dev); + dev_info(&dev->pdev->dev, "Connect: %s\n", + usb_speed_string(dev->gadget.speed)); + + /* init ep 0 */ + activate_control_endpoints(dev); + + /* enable ep0 interrupts */ + udc_enable_ep0_interrupts(dev); + } + /* session valid change interrupt */ + if (dev_irq & AMD_BIT(UDC_DEVINT_SVC)) { + DBG(dev, "USB SVC interrupt\n"); + ret_val = IRQ_HANDLED; + + /* check that session is not valid to detect disconnect */ + tmp = readl(&dev->regs->sts); + if (!(tmp & AMD_BIT(UDC_DEVSTS_SESSVLD))) { + /* disable suspend interrupt */ + tmp = readl(&dev->regs->irqmsk); + tmp |= AMD_BIT(UDC_DEVINT_US); + writel(tmp, &dev->regs->irqmsk); + DBG(dev, "USB Disconnect (session valid low)\n"); + /* cleanup on disconnect */ + usb_disconnect(udc); + } + + } + + return ret_val; +} + +/* Interrupt Service Routine, see Linux Kernel Doc for parameters */ +static irqreturn_t udc_irq(int irq, void *pdev) +{ + struct udc *dev = pdev; + u32 reg; + u16 i; + u32 ep_irq; + irqreturn_t ret_val = IRQ_NONE; + + spin_lock(&dev->lock); + + /* check for ep irq */ + reg = readl(&dev->regs->ep_irqsts); + if (reg) { + if (reg & AMD_BIT(UDC_EPINT_OUT_EP0)) + ret_val |= udc_control_out_isr(dev); + if (reg & AMD_BIT(UDC_EPINT_IN_EP0)) + ret_val |= udc_control_in_isr(dev); + + /* + * data endpoint + * iterate ep's + */ + for (i = 1; i < UDC_EP_NUM; i++) { + ep_irq = 1 << i; + if (!(reg & ep_irq) || i == UDC_EPINT_OUT_EP0) + continue; + + /* clear irq status */ + writel(ep_irq, &dev->regs->ep_irqsts); + + /* irq for out ep ? */ + if (i > UDC_EPIN_NUM) + ret_val |= udc_data_out_isr(dev, i); + else + ret_val |= udc_data_in_isr(dev, i); + } + + } + + + /* check for dev irq */ + reg = readl(&dev->regs->irqsts); + if (reg) { + /* clear irq */ + writel(reg, &dev->regs->irqsts); + ret_val |= udc_dev_isr(dev, reg); + } + + + spin_unlock(&dev->lock); + return ret_val; +} + +/* Tears down device */ +static void gadget_release(struct device *pdev) +{ + struct amd5536udc *dev = dev_get_drvdata(pdev); + kfree(dev); +} + +/* Cleanup on device remove */ +static void udc_remove(struct udc *dev) +{ + /* remove timer */ + stop_timer++; + if (timer_pending(&udc_timer)) + wait_for_completion(&on_exit); + if (udc_timer.data) + del_timer_sync(&udc_timer); + /* remove pollstall timer */ + stop_pollstall_timer++; + if (timer_pending(&udc_pollstall_timer)) + wait_for_completion(&on_pollstall_exit); + if (udc_pollstall_timer.data) + del_timer_sync(&udc_pollstall_timer); + udc = NULL; +} + +/* Reset all pci context */ +static void udc_pci_remove(struct pci_dev *pdev) +{ + struct udc *dev; + + dev = pci_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + /* gadget driver must not be registered */ + BUG_ON(dev->driver != NULL); + + /* dma pool cleanup */ + if (dev->data_requests) + pci_pool_destroy(dev->data_requests); + + if (dev->stp_requests) { + /* cleanup DMA desc's for ep0in */ + pci_pool_free(dev->stp_requests, + dev->ep[UDC_EP0OUT_IX].td_stp, + dev->ep[UDC_EP0OUT_IX].td_stp_dma); + pci_pool_free(dev->stp_requests, + dev->ep[UDC_EP0OUT_IX].td, + dev->ep[UDC_EP0OUT_IX].td_phys); + + pci_pool_destroy(dev->stp_requests); + } + + /* reset controller */ + writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); + if (dev->irq_registered) + free_irq(pdev->irq, dev); + if (dev->regs) + iounmap(dev->regs); + if (dev->mem_region) + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + if (dev->active) + pci_disable_device(pdev); + + pci_set_drvdata(pdev, NULL); + + udc_remove(dev); +} + +/* create dma pools on init */ +static int init_dma_pools(struct udc *dev) +{ + struct udc_stp_dma *td_stp; + struct udc_data_dma *td_data; + int retval; + + /* consistent DMA mode setting ? */ + if (use_dma_ppb) { + use_dma_bufferfill_mode = 0; + } else { + use_dma_ppb_du = 0; + use_dma_bufferfill_mode = 1; + } + + /* DMA setup */ + dev->data_requests = dma_pool_create("data_requests", NULL, + sizeof(struct udc_data_dma), 0, 0); + if (!dev->data_requests) { + DBG(dev, "can't get request data pool\n"); + retval = -ENOMEM; + goto finished; + } + + /* EP0 in dma regs = dev control regs */ + dev->ep[UDC_EP0IN_IX].dma = &dev->regs->ctl; + + /* dma desc for setup data */ + dev->stp_requests = dma_pool_create("setup requests", NULL, + sizeof(struct udc_stp_dma), 0, 0); + if (!dev->stp_requests) { + DBG(dev, "can't get stp request pool\n"); + retval = -ENOMEM; + goto finished; + } + /* setup */ + td_stp = dma_pool_alloc(dev->stp_requests, GFP_KERNEL, + &dev->ep[UDC_EP0OUT_IX].td_stp_dma); + if (td_stp == NULL) { + retval = -ENOMEM; + goto finished; + } + dev->ep[UDC_EP0OUT_IX].td_stp = td_stp; + + /* data: 0 packets !? */ + td_data = dma_pool_alloc(dev->stp_requests, GFP_KERNEL, + &dev->ep[UDC_EP0OUT_IX].td_phys); + if (td_data == NULL) { + retval = -ENOMEM; + goto finished; + } + dev->ep[UDC_EP0OUT_IX].td = td_data; + return 0; + +finished: + return retval; +} + +/* Called by pci bus driver to init pci context */ +static int udc_pci_probe( + struct pci_dev *pdev, + const struct pci_device_id *id +) +{ + struct udc *dev; + unsigned long resource; + unsigned long len; + int retval = 0; + + /* one udc only */ + if (udc) { + dev_dbg(&pdev->dev, "already probed\n"); + return -EBUSY; + } + + /* init */ + dev = kzalloc(sizeof(struct udc), GFP_KERNEL); + if (!dev) { + retval = -ENOMEM; + goto finished; + } + + /* pci setup */ + if (pci_enable_device(pdev) < 0) { + kfree(dev); + dev = NULL; + retval = -ENODEV; + goto finished; + } + dev->active = 1; + + /* PCI resource allocation */ + resource = pci_resource_start(pdev, 0); + len = pci_resource_len(pdev, 0); + + if (!request_mem_region(resource, len, name)) { + dev_dbg(&pdev->dev, "pci device used already\n"); + kfree(dev); + dev = NULL; + retval = -EBUSY; + goto finished; + } + dev->mem_region = 1; + + dev->virt_addr = ioremap_nocache(resource, len); + if (dev->virt_addr == NULL) { + dev_dbg(&pdev->dev, "start address cannot be mapped\n"); + kfree(dev); + dev = NULL; + retval = -EFAULT; + goto finished; + } + + if (!pdev->irq) { + dev_err(&pdev->dev, "irq not set\n"); + kfree(dev); + dev = NULL; + retval = -ENODEV; + goto finished; + } + + spin_lock_init(&dev->lock); + /* udc csr registers base */ + dev->csr = dev->virt_addr + UDC_CSR_ADDR; + /* dev registers base */ + dev->regs = dev->virt_addr + UDC_DEVCFG_ADDR; + /* ep registers base */ + dev->ep_regs = dev->virt_addr + UDC_EPREGS_ADDR; + /* fifo's base */ + dev->rxfifo = (u32 __iomem *)(dev->virt_addr + UDC_RXFIFO_ADDR); + dev->txfifo = (u32 __iomem *)(dev->virt_addr + UDC_TXFIFO_ADDR); + + if (request_irq(pdev->irq, udc_irq, IRQF_SHARED, name, dev) != 0) { + dev_dbg(&pdev->dev, "request_irq(%d) fail\n", pdev->irq); + kfree(dev); + dev = NULL; + retval = -EBUSY; + goto finished; + } + dev->irq_registered = 1; + + pci_set_drvdata(pdev, dev); + + /* chip revision for Hs AMD5536 */ + dev->chiprev = pdev->revision; + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + /* init dma pools */ + if (use_dma) { + retval = init_dma_pools(dev); + if (retval != 0) + goto finished; + } + + dev->phys_addr = resource; + dev->irq = pdev->irq; + dev->pdev = pdev; + + /* general probing */ + if (udc_probe(dev) == 0) + return 0; + +finished: + if (dev) + udc_pci_remove(pdev); + return retval; +} + +/* general probe */ +static int udc_probe(struct udc *dev) +{ + char tmp[128]; + u32 reg; + int retval; + + /* mark timer as not initialized */ + udc_timer.data = 0; + udc_pollstall_timer.data = 0; + + /* device struct setup */ + dev->gadget.ops = &udc_ops; + + dev_set_name(&dev->gadget.dev, "gadget"); + dev->gadget.name = name; + dev->gadget.max_speed = USB_SPEED_HIGH; + + /* init registers, interrupts, ... */ + startup_registers(dev); + + dev_info(&dev->pdev->dev, "%s\n", mod_desc); + + snprintf(tmp, sizeof tmp, "%d", dev->irq); + dev_info(&dev->pdev->dev, + "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", + tmp, dev->phys_addr, dev->chiprev, + (dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1"); + strcpy(tmp, UDC_DRIVER_VERSION_STRING); + if (dev->chiprev == UDC_HSA0_REV) { + dev_err(&dev->pdev->dev, "chip revision is A0; too old\n"); + retval = -ENODEV; + goto finished; + } + dev_info(&dev->pdev->dev, + "driver version: %s(for Geode5536 B1)\n", tmp); + udc = dev; + + retval = usb_add_gadget_udc_release(&udc->pdev->dev, &dev->gadget, + gadget_release); + if (retval) + goto finished; + + /* timer init */ + init_timer(&udc_timer); + udc_timer.function = udc_timer_function; + udc_timer.data = 1; + /* timer pollstall init */ + init_timer(&udc_pollstall_timer); + udc_pollstall_timer.function = udc_pollstall_timer_function; + udc_pollstall_timer.data = 1; + + /* set SD */ + reg = readl(&dev->regs->ctl); + reg |= AMD_BIT(UDC_DEVCTL_SD); + writel(reg, &dev->regs->ctl); + + /* print dev register info */ + print_regs(dev); + + return 0; + +finished: + return retval; +} + +/* Initiates a remote wakeup */ +static int udc_remote_wakeup(struct udc *dev) +{ + unsigned long flags; + u32 tmp; + + DBG(dev, "UDC initiates remote wakeup\n"); + + spin_lock_irqsave(&dev->lock, flags); + + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_RES); + writel(tmp, &dev->regs->ctl); + tmp &= AMD_CLEAR_BIT(UDC_DEVCTL_RES); + writel(tmp, &dev->regs->ctl); + + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} + +/* PCI device parameters */ +static DEFINE_PCI_DEVICE_TABLE(pci_id) = { + { + PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x2096), + .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class_mask = 0xffffffff, + }, + {}, +}; +MODULE_DEVICE_TABLE(pci, pci_id); + +/* PCI functions */ +static struct pci_driver udc_pci_driver = { + .name = (char *) name, + .id_table = pci_id, + .probe = udc_pci_probe, + .remove = udc_pci_remove, +}; + +module_pci_driver(udc_pci_driver); + +MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); +MODULE_AUTHOR("Thomas Dahlmann"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/gadget/amd5536udc.h b/drivers/usb/gadget/amd5536udc.h new file mode 100644 index 00000000..6744d3b8 --- /dev/null +++ b/drivers/usb/gadget/amd5536udc.h @@ -0,0 +1,617 @@ +/* + * amd5536.h -- header for AMD 5536 UDC high/full speed USB device controller + * + * Copyright (C) 2007 AMD (http://www.amd.com) + * Author: Thomas Dahlmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef AMD5536UDC_H +#define AMD5536UDC_H + +/* various constants */ +#define UDC_RDE_TIMER_SECONDS 1 +#define UDC_RDE_TIMER_DIV 10 +#define UDC_POLLSTALL_TIMER_USECONDS 500 + +/* Hs AMD5536 chip rev. */ +#define UDC_HSA0_REV 1 +#define UDC_HSB1_REV 2 + +/* + * SETUP usb commands + * needed, because some SETUP's are handled in hw, but must be passed to + * gadget driver above + * SET_CONFIG + */ +#define UDC_SETCONFIG_DWORD0 0x00000900 +#define UDC_SETCONFIG_DWORD0_VALUE_MASK 0xffff0000 +#define UDC_SETCONFIG_DWORD0_VALUE_OFS 16 + +#define UDC_SETCONFIG_DWORD1 0x00000000 + +/* SET_INTERFACE */ +#define UDC_SETINTF_DWORD0 0x00000b00 +#define UDC_SETINTF_DWORD0_ALT_MASK 0xffff0000 +#define UDC_SETINTF_DWORD0_ALT_OFS 16 + +#define UDC_SETINTF_DWORD1 0x00000000 +#define UDC_SETINTF_DWORD1_INTF_MASK 0x0000ffff +#define UDC_SETINTF_DWORD1_INTF_OFS 0 + +/* Mass storage reset */ +#define UDC_MSCRES_DWORD0 0x0000ff21 +#define UDC_MSCRES_DWORD1 0x00000000 + +/* Global CSR's -------------------------------------------------------------*/ +#define UDC_CSR_ADDR 0x500 + +/* EP NE bits */ +/* EP number */ +#define UDC_CSR_NE_NUM_MASK 0x0000000f +#define UDC_CSR_NE_NUM_OFS 0 +/* EP direction */ +#define UDC_CSR_NE_DIR_MASK 0x00000010 +#define UDC_CSR_NE_DIR_OFS 4 +/* EP type */ +#define UDC_CSR_NE_TYPE_MASK 0x00000060 +#define UDC_CSR_NE_TYPE_OFS 5 +/* EP config number */ +#define UDC_CSR_NE_CFG_MASK 0x00000780 +#define UDC_CSR_NE_CFG_OFS 7 +/* EP interface number */ +#define UDC_CSR_NE_INTF_MASK 0x00007800 +#define UDC_CSR_NE_INTF_OFS 11 +/* EP alt setting */ +#define UDC_CSR_NE_ALT_MASK 0x00078000 +#define UDC_CSR_NE_ALT_OFS 15 + +/* max pkt */ +#define UDC_CSR_NE_MAX_PKT_MASK 0x3ff80000 +#define UDC_CSR_NE_MAX_PKT_OFS 19 + +/* Device Config Register ---------------------------------------------------*/ +#define UDC_DEVCFG_ADDR 0x400 + +#define UDC_DEVCFG_SOFTRESET 31 +#define UDC_DEVCFG_HNPSFEN 30 +#define UDC_DEVCFG_DMARST 29 +#define UDC_DEVCFG_SET_DESC 18 +#define UDC_DEVCFG_CSR_PRG 17 +#define UDC_DEVCFG_STATUS 7 +#define UDC_DEVCFG_DIR 6 +#define UDC_DEVCFG_PI 5 +#define UDC_DEVCFG_SS 4 +#define UDC_DEVCFG_SP 3 +#define UDC_DEVCFG_RWKP 2 + +#define UDC_DEVCFG_SPD_MASK 0x3 +#define UDC_DEVCFG_SPD_OFS 0 +#define UDC_DEVCFG_SPD_HS 0x0 +#define UDC_DEVCFG_SPD_FS 0x1 +#define UDC_DEVCFG_SPD_LS 0x2 +/*#define UDC_DEVCFG_SPD_FS 0x3*/ + + +/* Device Control Register --------------------------------------------------*/ +#define UDC_DEVCTL_ADDR 0x404 + +#define UDC_DEVCTL_THLEN_MASK 0xff000000 +#define UDC_DEVCTL_THLEN_OFS 24 + +#define UDC_DEVCTL_BRLEN_MASK 0x00ff0000 +#define UDC_DEVCTL_BRLEN_OFS 16 + +#define UDC_DEVCTL_CSR_DONE 13 +#define UDC_DEVCTL_DEVNAK 12 +#define UDC_DEVCTL_SD 10 +#define UDC_DEVCTL_MODE 9 +#define UDC_DEVCTL_BREN 8 +#define UDC_DEVCTL_THE 7 +#define UDC_DEVCTL_BF 6 +#define UDC_DEVCTL_BE 5 +#define UDC_DEVCTL_DU 4 +#define UDC_DEVCTL_TDE 3 +#define UDC_DEVCTL_RDE 2 +#define UDC_DEVCTL_RES 0 + + +/* Device Status Register ---------------------------------------------------*/ +#define UDC_DEVSTS_ADDR 0x408 + +#define UDC_DEVSTS_TS_MASK 0xfffc0000 +#define UDC_DEVSTS_TS_OFS 18 + +#define UDC_DEVSTS_SESSVLD 17 +#define UDC_DEVSTS_PHY_ERROR 16 +#define UDC_DEVSTS_RXFIFO_EMPTY 15 + +#define UDC_DEVSTS_ENUM_SPEED_MASK 0x00006000 +#define UDC_DEVSTS_ENUM_SPEED_OFS 13 +#define UDC_DEVSTS_ENUM_SPEED_FULL 1 +#define UDC_DEVSTS_ENUM_SPEED_HIGH 0 + +#define UDC_DEVSTS_SUSP 12 + +#define UDC_DEVSTS_ALT_MASK 0x00000f00 +#define UDC_DEVSTS_ALT_OFS 8 + +#define UDC_DEVSTS_INTF_MASK 0x000000f0 +#define UDC_DEVSTS_INTF_OFS 4 + +#define UDC_DEVSTS_CFG_MASK 0x0000000f +#define UDC_DEVSTS_CFG_OFS 0 + + +/* Device Interrupt Register ------------------------------------------------*/ +#define UDC_DEVINT_ADDR 0x40c + +#define UDC_DEVINT_SVC 7 +#define UDC_DEVINT_ENUM 6 +#define UDC_DEVINT_SOF 5 +#define UDC_DEVINT_US 4 +#define UDC_DEVINT_UR 3 +#define UDC_DEVINT_ES 2 +#define UDC_DEVINT_SI 1 +#define UDC_DEVINT_SC 0 + +/* Device Interrupt Mask Register -------------------------------------------*/ +#define UDC_DEVINT_MSK_ADDR 0x410 + +#define UDC_DEVINT_MSK 0x7f + +/* Endpoint Interrupt Register ----------------------------------------------*/ +#define UDC_EPINT_ADDR 0x414 + +#define UDC_EPINT_OUT_MASK 0xffff0000 +#define UDC_EPINT_OUT_OFS 16 +#define UDC_EPINT_IN_MASK 0x0000ffff +#define UDC_EPINT_IN_OFS 0 + +#define UDC_EPINT_IN_EP0 0 +#define UDC_EPINT_IN_EP1 1 +#define UDC_EPINT_IN_EP2 2 +#define UDC_EPINT_IN_EP3 3 +#define UDC_EPINT_OUT_EP0 16 +#define UDC_EPINT_OUT_EP1 17 +#define UDC_EPINT_OUT_EP2 18 +#define UDC_EPINT_OUT_EP3 19 + +#define UDC_EPINT_EP0_ENABLE_MSK 0x001e001e + +/* Endpoint Interrupt Mask Register -----------------------------------------*/ +#define UDC_EPINT_MSK_ADDR 0x418 + +#define UDC_EPINT_OUT_MSK_MASK 0xffff0000 +#define UDC_EPINT_OUT_MSK_OFS 16 +#define UDC_EPINT_IN_MSK_MASK 0x0000ffff +#define UDC_EPINT_IN_MSK_OFS 0 + +#define UDC_EPINT_MSK_DISABLE_ALL 0xffffffff +/* mask non-EP0 endpoints */ +#define UDC_EPDATAINT_MSK_DISABLE 0xfffefffe +/* mask all dev interrupts */ +#define UDC_DEV_MSK_DISABLE 0x7f + +/* Endpoint-specific CSR's --------------------------------------------------*/ +#define UDC_EPREGS_ADDR 0x0 +#define UDC_EPIN_REGS_ADDR 0x0 +#define UDC_EPOUT_REGS_ADDR 0x200 + +#define UDC_EPCTL_ADDR 0x0 + +#define UDC_EPCTL_RRDY 9 +#define UDC_EPCTL_CNAK 8 +#define UDC_EPCTL_SNAK 7 +#define UDC_EPCTL_NAK 6 + +#define UDC_EPCTL_ET_MASK 0x00000030 +#define UDC_EPCTL_ET_OFS 4 +#define UDC_EPCTL_ET_CONTROL 0 +#define UDC_EPCTL_ET_ISO 1 +#define UDC_EPCTL_ET_BULK 2 +#define UDC_EPCTL_ET_INTERRUPT 3 + +#define UDC_EPCTL_P 3 +#define UDC_EPCTL_SN 2 +#define UDC_EPCTL_F 1 +#define UDC_EPCTL_S 0 + +/* Endpoint Status Registers ------------------------------------------------*/ +#define UDC_EPSTS_ADDR 0x4 + +#define UDC_EPSTS_RX_PKT_SIZE_MASK 0x007ff800 +#define UDC_EPSTS_RX_PKT_SIZE_OFS 11 + +#define UDC_EPSTS_TDC 10 +#define UDC_EPSTS_HE 9 +#define UDC_EPSTS_BNA 7 +#define UDC_EPSTS_IN 6 + +#define UDC_EPSTS_OUT_MASK 0x00000030 +#define UDC_EPSTS_OUT_OFS 4 +#define UDC_EPSTS_OUT_DATA 1 +#define UDC_EPSTS_OUT_DATA_CLEAR 0x10 +#define UDC_EPSTS_OUT_SETUP 2 +#define UDC_EPSTS_OUT_SETUP_CLEAR 0x20 +#define UDC_EPSTS_OUT_CLEAR 0x30 + +/* Endpoint Buffer Size IN/ Receive Packet Frame Number OUT Registers ------*/ +#define UDC_EPIN_BUFF_SIZE_ADDR 0x8 +#define UDC_EPOUT_FRAME_NUMBER_ADDR 0x8 + +#define UDC_EPIN_BUFF_SIZE_MASK 0x0000ffff +#define UDC_EPIN_BUFF_SIZE_OFS 0 +/* EP0in txfifo = 128 bytes*/ +#define UDC_EPIN0_BUFF_SIZE 32 +/* EP0in fullspeed txfifo = 128 bytes*/ +#define UDC_FS_EPIN0_BUFF_SIZE 32 + +/* fifo size mult = fifo size / max packet */ +#define UDC_EPIN_BUFF_SIZE_MULT 2 + +/* EPin data fifo size = 1024 bytes DOUBLE BUFFERING */ +#define UDC_EPIN_BUFF_SIZE 256 +/* EPin small INT data fifo size = 128 bytes */ +#define UDC_EPIN_SMALLINT_BUFF_SIZE 32 + +/* EPin fullspeed data fifo size = 128 bytes DOUBLE BUFFERING */ +#define UDC_FS_EPIN_BUFF_SIZE 32 + +#define UDC_EPOUT_FRAME_NUMBER_MASK 0x0000ffff +#define UDC_EPOUT_FRAME_NUMBER_OFS 0 + +/* Endpoint Buffer Size OUT/Max Packet Size Registers -----------------------*/ +#define UDC_EPOUT_BUFF_SIZE_ADDR 0x0c +#define UDC_EP_MAX_PKT_SIZE_ADDR 0x0c + +#define UDC_EPOUT_BUFF_SIZE_MASK 0xffff0000 +#define UDC_EPOUT_BUFF_SIZE_OFS 16 +#define UDC_EP_MAX_PKT_SIZE_MASK 0x0000ffff +#define UDC_EP_MAX_PKT_SIZE_OFS 0 +/* EP0in max packet size = 64 bytes */ +#define UDC_EP0IN_MAX_PKT_SIZE 64 +/* EP0out max packet size = 64 bytes */ +#define UDC_EP0OUT_MAX_PKT_SIZE 64 +/* EP0in fullspeed max packet size = 64 bytes */ +#define UDC_FS_EP0IN_MAX_PKT_SIZE 64 +/* EP0out fullspeed max packet size = 64 bytes */ +#define UDC_FS_EP0OUT_MAX_PKT_SIZE 64 + +/* + * Endpoint dma descriptors ------------------------------------------------ + * + * Setup data, Status dword + */ +#define UDC_DMA_STP_STS_CFG_MASK 0x0fff0000 +#define UDC_DMA_STP_STS_CFG_OFS 16 +#define UDC_DMA_STP_STS_CFG_ALT_MASK 0x000f0000 +#define UDC_DMA_STP_STS_CFG_ALT_OFS 16 +#define UDC_DMA_STP_STS_CFG_INTF_MASK 0x00f00000 +#define UDC_DMA_STP_STS_CFG_INTF_OFS 20 +#define UDC_DMA_STP_STS_CFG_NUM_MASK 0x0f000000 +#define UDC_DMA_STP_STS_CFG_NUM_OFS 24 +#define UDC_DMA_STP_STS_RX_MASK 0x30000000 +#define UDC_DMA_STP_STS_RX_OFS 28 +#define UDC_DMA_STP_STS_BS_MASK 0xc0000000 +#define UDC_DMA_STP_STS_BS_OFS 30 +#define UDC_DMA_STP_STS_BS_HOST_READY 0 +#define UDC_DMA_STP_STS_BS_DMA_BUSY 1 +#define UDC_DMA_STP_STS_BS_DMA_DONE 2 +#define UDC_DMA_STP_STS_BS_HOST_BUSY 3 +/* IN data, Status dword */ +#define UDC_DMA_IN_STS_TXBYTES_MASK 0x0000ffff +#define UDC_DMA_IN_STS_TXBYTES_OFS 0 +#define UDC_DMA_IN_STS_FRAMENUM_MASK 0x07ff0000 +#define UDC_DMA_IN_STS_FRAMENUM_OFS 0 +#define UDC_DMA_IN_STS_L 27 +#define UDC_DMA_IN_STS_TX_MASK 0x30000000 +#define UDC_DMA_IN_STS_TX_OFS 28 +#define UDC_DMA_IN_STS_BS_MASK 0xc0000000 +#define UDC_DMA_IN_STS_BS_OFS 30 +#define UDC_DMA_IN_STS_BS_HOST_READY 0 +#define UDC_DMA_IN_STS_BS_DMA_BUSY 1 +#define UDC_DMA_IN_STS_BS_DMA_DONE 2 +#define UDC_DMA_IN_STS_BS_HOST_BUSY 3 +/* OUT data, Status dword */ +#define UDC_DMA_OUT_STS_RXBYTES_MASK 0x0000ffff +#define UDC_DMA_OUT_STS_RXBYTES_OFS 0 +#define UDC_DMA_OUT_STS_FRAMENUM_MASK 0x07ff0000 +#define UDC_DMA_OUT_STS_FRAMENUM_OFS 0 +#define UDC_DMA_OUT_STS_L 27 +#define UDC_DMA_OUT_STS_RX_MASK 0x30000000 +#define UDC_DMA_OUT_STS_RX_OFS 28 +#define UDC_DMA_OUT_STS_BS_MASK 0xc0000000 +#define UDC_DMA_OUT_STS_BS_OFS 30 +#define UDC_DMA_OUT_STS_BS_HOST_READY 0 +#define UDC_DMA_OUT_STS_BS_DMA_BUSY 1 +#define UDC_DMA_OUT_STS_BS_DMA_DONE 2 +#define UDC_DMA_OUT_STS_BS_HOST_BUSY 3 +/* max ep0in packet */ +#define UDC_EP0IN_MAXPACKET 1000 +/* max dma packet */ +#define UDC_DMA_MAXPACKET 65536 + +/* un-usable DMA address */ +#define DMA_DONT_USE (~(dma_addr_t) 0 ) + +/* other Endpoint register addresses and values-----------------------------*/ +#define UDC_EP_SUBPTR_ADDR 0x10 +#define UDC_EP_DESPTR_ADDR 0x14 +#define UDC_EP_WRITE_CONFIRM_ADDR 0x1c + +/* EP number as layouted in AHB space */ +#define UDC_EP_NUM 32 +#define UDC_EPIN_NUM 16 +#define UDC_EPIN_NUM_USED 5 +#define UDC_EPOUT_NUM 16 +/* EP number of EP's really used = EP0 + 8 data EP's */ +#define UDC_USED_EP_NUM 9 +/* UDC CSR regs are aligned but AHB regs not - offset for OUT EP's */ +#define UDC_CSR_EP_OUT_IX_OFS 12 + +#define UDC_EP0OUT_IX 16 +#define UDC_EP0IN_IX 0 + +/* Rx fifo address and size = 1k -------------------------------------------*/ +#define UDC_RXFIFO_ADDR 0x800 +#define UDC_RXFIFO_SIZE 0x400 + +/* Tx fifo address and size = 1.5k -----------------------------------------*/ +#define UDC_TXFIFO_ADDR 0xc00 +#define UDC_TXFIFO_SIZE 0x600 + +/* default data endpoints --------------------------------------------------*/ +#define UDC_EPIN_STATUS_IX 1 +#define UDC_EPIN_IX 2 +#define UDC_EPOUT_IX 18 + +/* general constants -------------------------------------------------------*/ +#define UDC_DWORD_BYTES 4 +#define UDC_BITS_PER_BYTE_SHIFT 3 +#define UDC_BYTE_MASK 0xff +#define UDC_BITS_PER_BYTE 8 + +/*---------------------------------------------------------------------------*/ +/* UDC CSR's */ +struct udc_csrs { + + /* sca - setup command address */ + u32 sca; + + /* ep ne's */ + u32 ne[UDC_USED_EP_NUM]; +} __attribute__ ((packed)); + +/* AHB subsystem CSR registers */ +struct udc_regs { + + /* device configuration */ + u32 cfg; + + /* device control */ + u32 ctl; + + /* device status */ + u32 sts; + + /* device interrupt */ + u32 irqsts; + + /* device interrupt mask */ + u32 irqmsk; + + /* endpoint interrupt */ + u32 ep_irqsts; + + /* endpoint interrupt mask */ + u32 ep_irqmsk; +} __attribute__ ((packed)); + +/* endpoint specific registers */ +struct udc_ep_regs { + + /* endpoint control */ + u32 ctl; + + /* endpoint status */ + u32 sts; + + /* endpoint buffer size in/ receive packet frame number out */ + u32 bufin_framenum; + + /* endpoint buffer size out/max packet size */ + u32 bufout_maxpkt; + + /* endpoint setup buffer pointer */ + u32 subptr; + + /* endpoint data descriptor pointer */ + u32 desptr; + + /* reserverd */ + u32 reserved; + + /* write/read confirmation */ + u32 confirm; + +} __attribute__ ((packed)); + +/* control data DMA desc */ +struct udc_stp_dma { + /* status quadlet */ + u32 status; + /* reserved */ + u32 _reserved; + /* first setup word */ + u32 data12; + /* second setup word */ + u32 data34; +} __attribute__ ((aligned (16))); + +/* normal data DMA desc */ +struct udc_data_dma { + /* status quadlet */ + u32 status; + /* reserved */ + u32 _reserved; + /* buffer pointer */ + u32 bufptr; + /* next descriptor pointer */ + u32 next; +} __attribute__ ((aligned (16))); + +/* request packet */ +struct udc_request { + /* embedded gadget ep */ + struct usb_request req; + + /* flags */ + unsigned dma_going : 1, + dma_done : 1; + /* phys. address */ + dma_addr_t td_phys; + /* first dma desc. of chain */ + struct udc_data_dma *td_data; + /* last dma desc. of chain */ + struct udc_data_dma *td_data_last; + struct list_head queue; + + /* chain length */ + unsigned chain_len; + +}; + +/* UDC specific endpoint parameters */ +struct udc_ep { + struct usb_ep ep; + struct udc_ep_regs __iomem *regs; + u32 __iomem *txfifo; + u32 __iomem *dma; + dma_addr_t td_phys; + dma_addr_t td_stp_dma; + struct udc_stp_dma *td_stp; + struct udc_data_dma *td; + /* temp request */ + struct udc_request *req; + unsigned req_used; + unsigned req_completed; + /* dummy DMA desc for BNA dummy */ + struct udc_request *bna_dummy_req; + unsigned bna_occurred; + + /* NAK state */ + unsigned naking; + + struct udc *dev; + + /* queue for requests */ + struct list_head queue; + unsigned halted; + unsigned cancel_transfer; + unsigned num : 5, + fifo_depth : 14, + in : 1; +}; + +/* device struct */ +struct udc { + struct usb_gadget gadget; + spinlock_t lock; /* protects all state */ + /* all endpoints */ + struct udc_ep ep[UDC_EP_NUM]; + struct usb_gadget_driver *driver; + /* operational flags */ + unsigned active : 1, + stall_ep0in : 1, + waiting_zlp_ack_ep0in : 1, + set_cfg_not_acked : 1, + irq_registered : 1, + data_ep_enabled : 1, + data_ep_queued : 1, + mem_region : 1, + sys_suspended : 1, + connected; + + u16 chiprev; + + /* registers */ + struct pci_dev *pdev; + struct udc_csrs __iomem *csr; + struct udc_regs __iomem *regs; + struct udc_ep_regs __iomem *ep_regs; + u32 __iomem *rxfifo; + u32 __iomem *txfifo; + + /* DMA desc pools */ + struct pci_pool *data_requests; + struct pci_pool *stp_requests; + + /* device data */ + unsigned long phys_addr; + void __iomem *virt_addr; + unsigned irq; + + /* states */ + u16 cur_config; + u16 cur_intf; + u16 cur_alt; +}; + +#define to_amd5536_udc(g) (container_of((g), struct udc, gadget)) + +/* setup request data */ +union udc_setup_data { + u32 data[2]; + struct usb_ctrlrequest request; +}; + +/* + *--------------------------------------------------------------------------- + * SET and GET bitfields in u32 values + * via constants for mask/offset: + * is the text between + * UDC_ and _MASK|_OFS of appropriate + * constant + * + * set bitfield value in u32 u32Val + */ +#define AMD_ADDBITS(u32Val, bitfield_val, bitfield_stub_name) \ + (((u32Val) & (((u32) ~((u32) bitfield_stub_name##_MASK)))) \ + | (((bitfield_val) << ((u32) bitfield_stub_name##_OFS)) \ + & ((u32) bitfield_stub_name##_MASK))) + +/* + * set bitfield value in zero-initialized u32 u32Val + * => bitfield bits in u32Val are all zero + */ +#define AMD_INIT_SETBITS(u32Val, bitfield_val, bitfield_stub_name) \ + ((u32Val) \ + | (((bitfield_val) << ((u32) bitfield_stub_name##_OFS)) \ + & ((u32) bitfield_stub_name##_MASK))) + +/* get bitfield value from u32 u32Val */ +#define AMD_GETBITS(u32Val, bitfield_stub_name) \ + ((u32Val & ((u32) bitfield_stub_name##_MASK)) \ + >> ((u32) bitfield_stub_name##_OFS)) + +/* SET and GET bits in u32 values ------------------------------------------*/ +#define AMD_BIT(bit_stub_name) (1 << bit_stub_name) +#define AMD_UNMASK_BIT(bit_stub_name) (~AMD_BIT(bit_stub_name)) +#define AMD_CLEAR_BIT(bit_stub_name) (~AMD_BIT(bit_stub_name)) + +/* debug macros ------------------------------------------------------------*/ + +#define DBG(udc , args...) dev_dbg(&(udc)->pdev->dev, args) + +#ifdef UDC_VERBOSE +#define VDBG DBG +#else +#define VDBG(udc , args...) do {} while (0) +#endif + +#endif /* #ifdef AMD5536UDC_H */ diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c new file mode 100644 index 00000000..48aa72c9 --- /dev/null +++ b/drivers/usb/gadget/android.c @@ -0,0 +1,1761 @@ +/* + * Gadget Driver for Android + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * Benoit Goby + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "gadget_chips.h" + +#include "f_fs.c" +#ifdef CONFIG_SOUND +#include "f_audio_source.c" +#endif +#include "f_mass_storage.c" +#include "f_mtp.c" +#include "f_accessory.c" +#ifdef CONFIG_USB_SPRD_DWC +#include "f_vserial.c" +#include "f_serial.c" +#endif +#define USB_ETH_RNDIS y +#include "f_rndis.c" +#include "rndis.c" +#include "u_ether.c" + +MODULE_AUTHOR("Mike Lockwood"); +MODULE_DESCRIPTION("Android Composite USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +#define GSER_PORT_MAX_COUNT MAX_U_SERIAL_PORTS + +static const char longname[] = "Gadget Android"; +extern int in_calibration(void); +extern int in_autotest(void); +/* Default vendor and product IDs, overridden by userspace */ +#define VENDOR_ID 0x18D1 +#define PRODUCT_ID 0x0001 + +struct android_usb_function { + char *name; + void *config; + + struct device *dev; + char *dev_name; + struct device_attribute **attributes; + + /* for android_dev.enabled_functions */ + struct list_head enabled_list; + + /* Optional: initialization during gadget bind */ + int (*init)(struct android_usb_function *, struct usb_composite_dev *); + /* Optional: cleanup during gadget unbind */ + void (*cleanup)(struct android_usb_function *); + /* Optional: called when the function is added the list of + * enabled functions */ + void (*enable)(struct android_usb_function *); + /* Optional: called when it is removed */ + void (*disable)(struct android_usb_function *); + + int (*bind_config)(struct android_usb_function *, + struct usb_configuration *); + + /* Optional: called when the configuration is removed */ + void (*unbind_config)(struct android_usb_function *, + struct usb_configuration *); + /* Optional: handle ctrl requests before the device is configured */ + int (*ctrlrequest)(struct android_usb_function *, + struct usb_composite_dev *, + const struct usb_ctrlrequest *); +}; + +struct android_dev { + struct android_usb_function **functions; + struct list_head enabled_functions; + struct usb_composite_dev *cdev; + struct device *dev; + + bool enabled; + int disable_depth; + struct mutex mutex; + bool connected; + bool sw_connected; + struct work_struct work; + char ffs_aliases[256]; +}; + +static struct class *android_class; +static struct android_dev *_android_dev; +static int android_bind_config(struct usb_configuration *c); +static void android_unbind_config(struct usb_configuration *c); + +/* string IDs are assigned dynamically */ +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +static char manufacturer_string[256]; +static char product_string[256]; +static char serial_string[256]; + +/* String Table */ +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer_string, + [STRING_PRODUCT_IDX].s = product_string, + [STRING_SERIAL_IDX].s = serial_string, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof(device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = __constant_cpu_to_le16(VENDOR_ID), + .idProduct = __constant_cpu_to_le16(PRODUCT_ID), + .bcdDevice = __constant_cpu_to_le16(0xffff), + .bNumConfigurations = 1, +}; + +static struct usb_configuration android_config_driver = { + .label = "android", + .unbind = android_unbind_config, + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .MaxPower = 500, /* 500ma */ +}; + +static void android_work(struct work_struct *data) +{ + struct android_dev *dev = container_of(data, struct android_dev, work); + struct usb_composite_dev *cdev = dev->cdev; + char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL }; + char *connected[2] = { "USB_STATE=CONNECTED", NULL }; + char *configured[2] = { "USB_STATE=CONFIGURED", NULL }; + char **uevent_envp = NULL; + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + uevent_envp = configured; + else if (dev->connected != dev->sw_connected) + uevent_envp = dev->connected ? connected : disconnected; + dev->sw_connected = dev->connected; + spin_unlock_irqrestore(&cdev->lock, flags); + + if (uevent_envp) { + kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp); + pr_info("%s: sent uevent %s\n", __func__, uevent_envp[0]); + } else { + pr_info("%s: did not send uevent (%d %d %p)\n", __func__, + dev->connected, dev->sw_connected, cdev->config); + } +} + +static void android_enable(struct android_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + + if (WARN_ON(!dev->disable_depth)) + return; + + if (--dev->disable_depth == 0) { + usb_add_config(cdev, &android_config_driver, + android_bind_config); + mdelay(30); + usb_gadget_connect(cdev->gadget); + } +} + +static void android_disable(struct android_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + + if (dev->disable_depth++ == 0) { + usb_gadget_disconnect(cdev->gadget); + /* Cancel pending control requests */ + usb_ep_dequeue(cdev->gadget->ep0, cdev->req); + usb_remove_config(cdev, &android_config_driver); + cdev->next_string_id = 7; + } +} + +/*-------------------------------------------------------------------------*/ +/* Supported functions initialization */ + +struct functionfs_config { + bool opened; + bool enabled; + struct ffs_data *data; +}; + +static int ffs_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct functionfs_config), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + + return functionfs_init(); +} + +static void ffs_function_cleanup(struct android_usb_function *f) +{ + functionfs_cleanup(); + kfree(f->config); +} + +static void ffs_function_enable(struct android_usb_function *f) +{ + struct android_dev *dev = _android_dev; + struct functionfs_config *config = f->config; + + config->enabled = true; + + /* Disable the gadget until the function is ready */ + if (!config->opened) + android_disable(dev); +} + +static void ffs_function_disable(struct android_usb_function *f) +{ + struct android_dev *dev = _android_dev; + struct functionfs_config *config = f->config; + + config->enabled = false; + + /* Balance the disable that was called in closed_callback */ + if (!config->opened) + android_enable(dev); +} + +static int ffs_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct functionfs_config *config = f->config; + return functionfs_bind_config(c->cdev, c, config->data); +} + +static ssize_t +ffs_aliases_show(struct device *pdev, struct device_attribute *attr, char *buf) +{ + struct android_dev *dev = _android_dev; + int ret; + + mutex_lock(&dev->mutex); + ret = sprintf(buf, "%s\n", dev->ffs_aliases); + mutex_unlock(&dev->mutex); + + return ret; +} + +static ssize_t +ffs_aliases_store(struct device *pdev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct android_dev *dev = _android_dev; + char buff[256]; + + mutex_lock(&dev->mutex); + + if (dev->enabled) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + + strlcpy(buff, buf, sizeof(buff)); + strlcpy(dev->ffs_aliases, strim(buff), sizeof(dev->ffs_aliases)); + + mutex_unlock(&dev->mutex); + + return size; +} + +static DEVICE_ATTR(aliases, S_IRUGO | S_IWUSR, ffs_aliases_show, + ffs_aliases_store); +static struct device_attribute *ffs_function_attributes[] = { + &dev_attr_aliases, + NULL +}; + +static struct android_usb_function ffs_function = { + .name = "ffs", + .init = ffs_function_init, + .enable = ffs_function_enable, + .disable = ffs_function_disable, + .cleanup = ffs_function_cleanup, + .bind_config = ffs_function_bind_config, + .attributes = ffs_function_attributes, +}; + +static int functionfs_ready_callback(struct ffs_data *ffs) +{ + struct android_dev *dev = _android_dev; + struct functionfs_config *config = ffs_function.config; + int ret = 0; + + mutex_lock(&dev->mutex); + + ret = functionfs_bind(ffs, dev->cdev); + if (ret) + goto err; + + config->data = ffs; + config->opened = true; + + if (config->enabled) + android_enable(dev); + +err: + mutex_unlock(&dev->mutex); + return ret; +} + +static void functionfs_closed_callback(struct ffs_data *ffs) +{ + struct android_dev *dev = _android_dev; + struct functionfs_config *config = ffs_function.config; + + mutex_lock(&dev->mutex); + + if (config->enabled) + android_disable(dev); + + config->opened = false; + config->data = NULL; + + functionfs_unbind(ffs); + + mutex_unlock(&dev->mutex); +} + +static void *functionfs_acquire_dev_callback(const char *dev_name) +{ + return 0; +} + +static void functionfs_release_dev_callback(struct ffs_data *ffs_data) +{ +} + +#define MAX_ACM_INSTANCES 4 +struct acm_function_config { + int instances; + int instances_on; + struct usb_function *f_acm[MAX_ACM_INSTANCES]; + struct usb_function_instance *f_acm_inst[MAX_ACM_INSTANCES]; +}; + +static int +acm_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + int i; + int ret; + struct acm_function_config *config; + + config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + f->config = config; + + for (i = 0; i < MAX_ACM_INSTANCES; i++) { + config->f_acm_inst[i] = usb_get_function_instance("acm"); + if (IS_ERR(config->f_acm_inst[i])) { + ret = PTR_ERR(config->f_acm_inst[i]); + goto err_usb_get_function_instance; + } + config->f_acm[i] = usb_get_function(config->f_acm_inst[i]); + if (IS_ERR(config->f_acm[i])) { + ret = PTR_ERR(config->f_acm[i]); + goto err_usb_get_function; + } + } + return 0; +err_usb_get_function_instance: + while (i-- > 0) { + usb_put_function(config->f_acm[i]); +err_usb_get_function: + usb_put_function_instance(config->f_acm_inst[i]); + } + return ret; +} + +static void acm_function_cleanup(struct android_usb_function *f) +{ + int i; + struct acm_function_config *config = f->config; + + for (i = 0; i < MAX_ACM_INSTANCES; i++) { + usb_put_function(config->f_acm[i]); + usb_put_function_instance(config->f_acm_inst[i]); + } + kfree(f->config); + f->config = NULL; +} + +static int +acm_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int i; + int ret = 0; + struct acm_function_config *config = f->config; + + config->instances_on = config->instances; + for (i = 0; i < config->instances_on; i++) { + ret = usb_add_function(c, config->f_acm[i]); + if (ret) { + pr_err("Could not bind acm%u config\n", i); + goto err_usb_add_function; + } + } + + return 0; + +err_usb_add_function: + while (i-- > 0) + usb_remove_function(c, config->f_acm[i]); + return ret; +} + +static void acm_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int i; + struct acm_function_config *config = f->config; + + for (i = 0; i < config->instances_on; i++) + usb_remove_function(c, config->f_acm[i]); +} + +static ssize_t acm_instances_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + return sprintf(buf, "%d\n", config->instances); +} + +static ssize_t acm_instances_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + int value; + + sscanf(buf, "%d", &value); + if (value > MAX_ACM_INSTANCES) + value = MAX_ACM_INSTANCES; + config->instances = value; + return size; +} + +static DEVICE_ATTR(instances, S_IRUGO | S_IWUSR, acm_instances_show, + acm_instances_store); +static struct device_attribute *acm_function_attributes[] = { + &dev_attr_instances, + NULL +}; + +static struct android_usb_function acm_function = { + .name = "acm", + .init = acm_function_init, + .cleanup = acm_function_cleanup, + .bind_config = acm_function_bind_config, + .unbind_config = acm_function_unbind_config, + .attributes = acm_function_attributes, +}; + + +static int +mtp_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return mtp_setup(); +} + +static void mtp_function_cleanup(struct android_usb_function *f) +{ + mtp_cleanup(); +} + +static int +mtp_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return mtp_bind_config(c, false); +} + +static int +ptp_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + /* nothing to do - initialization is handled by mtp_function_init */ + return 0; +} + +static void ptp_function_cleanup(struct android_usb_function *f) +{ + /* nothing to do - cleanup is handled by mtp_function_cleanup */ +} + +static int +ptp_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return mtp_bind_config(c, true); +} + +static int mtp_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return mtp_ctrlrequest(cdev, c); +} + +static struct android_usb_function mtp_function = { + .name = "mtp", + .init = mtp_function_init, + .cleanup = mtp_function_cleanup, + .bind_config = mtp_function_bind_config, + .ctrlrequest = mtp_function_ctrlrequest, +}; + +/* PTP function is same as MTP with slightly different interface descriptor */ +static struct android_usb_function ptp_function = { + .name = "ptp", + .init = ptp_function_init, + .cleanup = ptp_function_cleanup, + .bind_config = ptp_function_bind_config, +}; + + +struct rndis_function_config { + u8 ethaddr[ETH_ALEN]; + u32 vendorID; + char manufacturer[256]; + /* "Wireless" RNDIS; auto-detected by Windows */ + bool wceis; + struct eth_dev *dev; +}; + +static int +rndis_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct rndis_function_config), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + return 0; +} + +static void rndis_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); + f->config = NULL; +} + +static int +rndis_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int ret; + struct eth_dev *dev; + struct rndis_function_config *rndis = f->config; + + if (!rndis) { + pr_err("%s: rndis_pdata\n", __func__); + return -1; + } + + pr_info("%s MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", __func__, + rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], + rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); + + dev = gether_setup_name(c->cdev->gadget, rndis->ethaddr, "rndis"); + if (IS_ERR(dev)) { + ret = PTR_ERR(dev); + pr_err("%s: gether_setup failed\n", __func__); + return ret; + } + rndis->dev = dev; + + if (rndis->wceis) { + /* "Wireless" RNDIS; auto-detected by Windows */ + rndis_iad_descriptor.bFunctionClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_iad_descriptor.bFunctionSubClass = 0x01; + rndis_iad_descriptor.bFunctionProtocol = 0x03; + rndis_control_intf.bInterfaceClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_control_intf.bInterfaceSubClass = 0x01; + rndis_control_intf.bInterfaceProtocol = 0x03; + } + + return rndis_bind_config_vendor(c, rndis->ethaddr, rndis->vendorID, + rndis->manufacturer, rndis->dev); +} + +static void rndis_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct rndis_function_config *rndis = f->config; + gether_cleanup(rndis->dev); +} + +static ssize_t rndis_manufacturer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%s\n", config->manufacturer); +} + +static ssize_t rndis_manufacturer_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + + if (size >= sizeof(config->manufacturer)) + return -EINVAL; + if (sscanf(buf, "%s", config->manufacturer) == 1) + return size; + return -1; +} + +static DEVICE_ATTR(manufacturer, S_IRUGO | S_IWUSR, rndis_manufacturer_show, + rndis_manufacturer_store); + +static ssize_t rndis_wceis_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%d\n", config->wceis); +} + +static ssize_t rndis_wceis_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%d", &value) == 1) { + config->wceis = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(wceis, S_IRUGO | S_IWUSR, rndis_wceis_show, + rndis_wceis_store); + +static ssize_t rndis_ethaddr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], + rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); +} + +static ssize_t rndis_ethaddr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + + if (sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + (int *)&rndis->ethaddr[0], (int *)&rndis->ethaddr[1], + (int *)&rndis->ethaddr[2], (int *)&rndis->ethaddr[3], + (int *)&rndis->ethaddr[4], (int *)&rndis->ethaddr[5]) == 6) + return size; + return -EINVAL; +} + +static DEVICE_ATTR(ethaddr, S_IRUGO | S_IWUSR, rndis_ethaddr_show, + rndis_ethaddr_store); + +static ssize_t rndis_vendorID_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%04x\n", config->vendorID); +} + +static ssize_t rndis_vendorID_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%04x", &value) == 1) { + config->vendorID = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(vendorID, S_IRUGO | S_IWUSR, rndis_vendorID_show, + rndis_vendorID_store); + +static struct device_attribute *rndis_function_attributes[] = { + &dev_attr_manufacturer, + &dev_attr_wceis, + &dev_attr_ethaddr, + &dev_attr_vendorID, + NULL +}; + +static struct android_usb_function rndis_function = { + .name = "rndis", + .init = rndis_function_init, + .cleanup = rndis_function_cleanup, + .bind_config = rndis_function_bind_config, + .unbind_config = rndis_function_unbind_config, + .attributes = rndis_function_attributes, +}; + + +struct mass_storage_function_config { + struct fsg_config fsg; + struct fsg_common *common; +}; + +static int mass_storage_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct mass_storage_function_config *config; + struct fsg_common *common; + int err; + int i; + + config = kzalloc(sizeof(struct mass_storage_function_config), + GFP_KERNEL); + if (!config) + return -ENOMEM; + + config->fsg.nluns = 2; + + for(i = 0; i < config->fsg.nluns; i++) { + config->fsg.luns[i].removable = 1; + config->fsg.luns[i].nofua = 1; + } + + common = fsg_common_init(NULL, cdev, &config->fsg); + if (IS_ERR(common)) { + kfree(config); + return PTR_ERR(common); + } + + err = sysfs_create_link(&f->dev->kobj, + &common->luns[0].dev.kobj, + "lun"); + if (err) { + kfree(config); + return err; + } + + err = sysfs_create_link(&f->dev->kobj, + &common->luns[1].dev.kobj, + "lun1"); + if (err) { + kfree(config); + return err; + } + + config->common = common; + f->config = config; + return 0; +} + +static void mass_storage_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); + f->config = NULL; +} + +static int mass_storage_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct mass_storage_function_config *config = f->config; + return fsg_bind_config(c->cdev, c, config->common); +} + +static ssize_t mass_storage_inquiry_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + return sprintf(buf, "%s\n", config->common->inquiry_string); +} + +static ssize_t mass_storage_inquiry_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + if (size >= sizeof(config->common->inquiry_string)) + return -EINVAL; + if (sscanf(buf, "%s", config->common->inquiry_string) != 1) + return -EINVAL; + return size; +} + +static DEVICE_ATTR(inquiry_string, S_IRUGO | S_IWUSR, + mass_storage_inquiry_show, + mass_storage_inquiry_store); + +static ssize_t mass_storage_support_luns_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + return sprintf(buf, "%d\n", config->common->board_support_luns); +} + +static ssize_t mass_storage_support_luns_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + int value; + + sscanf(buf, "%d", &value); + if (value > (config->common->luns)||value <= 0) + return size; + config->common->board_support_luns = value; + return size; +} + +static DEVICE_ATTR(board_support_luns, S_IRUGO | S_IWUSR, + mass_storage_support_luns_show, + mass_storage_support_luns_store); + +static struct device_attribute *mass_storage_function_attributes[] = { + &dev_attr_inquiry_string, + &dev_attr_board_support_luns, + NULL +}; + +static struct android_usb_function mass_storage_function = { + .name = "mass_storage", + .init = mass_storage_function_init, + .cleanup = mass_storage_function_cleanup, + .bind_config = mass_storage_function_bind_config, + .attributes = mass_storage_function_attributes, +}; + + +static int accessory_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return acc_setup(); +} + +static void accessory_function_cleanup(struct android_usb_function *f) +{ + acc_cleanup(); +} + +static int accessory_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return acc_bind_config(c); +} + +static int accessory_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return acc_ctrlrequest(cdev, c); +} + +static struct android_usb_function accessory_function = { + .name = "accessory", + .init = accessory_function_init, + .cleanup = accessory_function_cleanup, + .bind_config = accessory_function_bind_config, + .ctrlrequest = accessory_function_ctrlrequest, +}; +#ifdef CONFIG_USB_SPRD_DWC +static int vser_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return vser_init(); +} + +static void vser_function_cleanup(struct android_usb_function *f) +{ + vser_cleanup(); +} + +static int vser_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return vser_bind_config(c); +} + +static int vser_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return vser_setup(cdev, c); +} +static struct android_usb_function vser_function = { + .name = "vser", + .init = vser_function_init, + .cleanup = vser_function_cleanup, + .bind_config = vser_function_bind_config, + .ctrlrequest = vser_function_ctrlrequest, +}; + +struct gser_function_config { + int instances_on; + struct usb_function *f_gser[GSER_PORT_MAX_COUNT]; + struct usb_function_instance *f_gser_inst[GSER_PORT_MAX_COUNT]; +}; +static int gser_port_count=1; +static int gser_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + int i; + int ret; + struct gser_function_config *config; + + config = kzalloc(sizeof(struct gser_function_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + f->config = config; + + for (i = 0; i < GSER_PORT_MAX_COUNT; i++) { + config->f_gser_inst[i] = usb_get_function_instance("gser"); + if (IS_ERR(config->f_gser_inst[i])) { + ret = PTR_ERR(config->f_gser_inst[i]); + goto err_usb_get_function_instance; + } + config->f_gser[i] = usb_get_function(config->f_gser_inst[i]); + if (IS_ERR(config->f_gser[i])) { + ret = PTR_ERR(config->f_gser[i]); + goto err_usb_get_function; + } + } + return 0; +err_usb_get_function_instance: + while (i-- > 0) { + usb_put_function(config->f_gser[i]); +err_usb_get_function: + usb_put_function_instance(config->f_gser_inst[i]); + } + return ret; +} + +static void gser_function_cleanup(struct android_usb_function *f) +{ + int i; + struct gser_function_config *config = f->config; + + for (i = 0; i < GSER_PORT_MAX_COUNT; i++) { + usb_put_function(config->f_gser[i]); + usb_put_function_instance(config->f_gser_inst[i]); + } + kfree(f->config); + f->config = NULL; +} + +static int gser_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int i; + int ret = 0; + struct gser_function_config *config = f->config; + + config->instances_on = gser_port_count; + for (i = 0; i < config->instances_on; i++) { + ret = usb_add_function(c, config->f_gser[i]); + if (ret) { + pr_err("Could not bind gser%u config\n", i); + goto err_usb_add_function; + } + } + + return 0; + +err_usb_add_function: + while (i-- > 0) + usb_remove_function(c, config->f_gser[i]); + return ret; +} +/** + *should notice that when adb disable/enable, it will call usb_remove_config/usb_add_config + *and in usb_remove_config it will call unbind_config , it will also delete function list and unbind + *the gser function; + *in kernel 3.4 no acm_function_unbind_config, so now we don't use gser_function_unbind_config + *to avoid kernel data abort. because in current s/w architecture it will delete function list two times. + */ +static void gser_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int i; + struct gser_function_config *config = f->config; + + for (i = 0; i < config->instances_on; i++) + usb_remove_function(c, config->f_gser[i]); +} + +static int gser_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return gser_setup(cdev, c); +} +static ssize_t gser_port_store(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count; + + printk("%s %s\n",__func__,buf); + sscanf(buf, "%d", &count); + if(count > GSER_PORT_MAX_COUNT) + return -1; + gser_port_count = count; + return count; +} +static ssize_t gser_port_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + /* print gser instance numbers */ + return sprintf(buf, "%d \n",gser_port_count ); +} +static DEVICE_ATTR(port_count, S_IRUGO | S_IWUSR, gser_port_show, gser_port_store); + +static struct device_attribute *gser_function_attributes[] = { + &dev_attr_port_count, + NULL +}; +static struct android_usb_function gser_function = { + .name = "gser", + .init = gser_function_init, + .cleanup = gser_function_cleanup, + .bind_config = gser_function_bind_config, + //.unbind_config = gser_function_unbind_config, + .ctrlrequest = gser_function_ctrlrequest, + .attributes = gser_function_attributes, +}; +#endif + +#ifdef CONFIG_SOUND +static int audio_source_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct audio_source_config *config; + + config = kzalloc(sizeof(struct audio_source_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + config->card = -1; + config->device = -1; + f->config = config; + return 0; +} + +static void audio_source_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); +} + +static int audio_source_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + return audio_source_bind_config(c, config); +} + +static void audio_source_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + config->card = -1; + config->device = -1; +} + +static ssize_t audio_source_pcm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct audio_source_config *config = f->config; + + /* print PCM card and device numbers */ + return sprintf(buf, "%d %d\n", config->card, config->device); +} + +static DEVICE_ATTR(pcm, S_IRUGO, audio_source_pcm_show, NULL); + +static struct device_attribute *audio_source_function_attributes[] = { + &dev_attr_pcm, + NULL +}; + +static struct android_usb_function audio_source_function = { + .name = "audio_source", + .init = audio_source_function_init, + .cleanup = audio_source_function_cleanup, + .bind_config = audio_source_function_bind_config, + .unbind_config = audio_source_function_unbind_config, + .attributes = audio_source_function_attributes, +}; +#endif +static struct android_usb_function *supported_functions[] = { + &ffs_function, + &mtp_function, + &ptp_function, + &rndis_function, + &mass_storage_function, + &accessory_function, +#ifdef CONFIG_SOUND + &audio_source_function, +#endif +#ifdef CONFIG_USB_SPRD_DWC + &vser_function, + &gser_function, +#else + &acm_function, +#endif + + NULL +}; + + +static int android_init_functions(struct android_usb_function **functions, + struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct android_usb_function *f; + struct device_attribute **attrs; + struct device_attribute *attr; + int err=0; + int index = 0; + + for (; (f = *functions++); index++) { + f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name); + f->dev = device_create(android_class, dev->dev, + MKDEV(0, index), f, f->dev_name); + if (IS_ERR(f->dev)) { + pr_err("%s: Failed to create dev %s", __func__, + f->dev_name); + err = PTR_ERR(f->dev); + goto err_create; + } + + if (f->init) { + err = f->init(f, cdev); + if (err) { + pr_err("%s: Failed to init %s", __func__, + f->name); + goto err_out; + } + } + + attrs = f->attributes; + if (attrs) { + while ((attr = *attrs++) && !err) + err = device_create_file(f->dev, attr); + } + if (err) { + pr_err("%s: Failed to create function %s attributes", + __func__, f->name); + goto err_out; + } + } + return 0; + +err_out: + device_destroy(android_class, f->dev->devt); +err_create: + kfree(f->dev_name); + return err; +} + +static void android_cleanup_functions(struct android_usb_function **functions) +{ + struct android_usb_function *f; + + while (*functions) { + f = *functions++; + + if (f->dev) { + device_destroy(android_class, f->dev->devt); + kfree(f->dev_name); + } + + if (f->cleanup) + f->cleanup(f); + } +} + +static int +android_bind_enabled_functions(struct android_dev *dev, + struct usb_configuration *c) +{ + struct android_usb_function *f; + int ret; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + ret = f->bind_config(f, c); + if (ret) { + pr_err("%s: %s failed", __func__, f->name); + return ret; + } + } + return 0; +} + +static void +android_unbind_enabled_functions(struct android_dev *dev, + struct usb_configuration *c) +{ + struct android_usb_function *f; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->unbind_config) + f->unbind_config(f, c); + } +} + +static int android_enable_function(struct android_dev *dev, char *name) +{ + struct android_usb_function **functions = dev->functions; + struct android_usb_function *f; + while ((f = *functions++)) { + if (!strcmp(name, f->name)) { + list_add_tail(&f->enabled_list, + &dev->enabled_functions); + return 0; + } + } + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ +/* /sys/class/android_usb/android%d/ interface */ + +static ssize_t +functions_show(struct device *pdev, struct device_attribute *attr, char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct android_usb_function *f; + char *buff = buf; + + mutex_lock(&dev->mutex); + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) + buff += sprintf(buff, "%s,", f->name); + + mutex_unlock(&dev->mutex); + + if (buff != buf) + *(buff-1) = '\n'; + return buff - buf; +} + +static ssize_t +functions_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + char *name; + char buf[256], *b; + char aliases[256], *a; + int err; + int is_ffs; + int ffs_enabled = 0; + + printk("%s(%d) %s(%s) \n",current->comm,current->pid,__func__,buff); + mutex_lock(&dev->mutex); + if (dev->enabled) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + + INIT_LIST_HEAD(&dev->enabled_functions); + + if((strcmp(buff,"mass_storage,adb,gser6") == 0) || + (strcmp(buff,"mass_storage,adb,gser4") == 0) || + (strcmp(buff,"mass_storage,adb,gser2") == 0)) + strlcpy(buf, "mass_storage,adb,gser", sizeof(buf)); + else + strlcpy(buf, buff, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + if (!name) + continue; + + is_ffs = 0; + strlcpy(aliases, dev->ffs_aliases, sizeof(aliases)); + a = aliases; + + while (a) { + char *alias = strsep(&a, ","); + if (alias && !strcmp(name, alias)) { + is_ffs = 1; + break; + } + } + + if (is_ffs) { + if (ffs_enabled) + continue; + err = android_enable_function(dev, "ffs"); + if (err) + pr_err("android_usb: Cannot enable ffs (%d)", + err); + else + ffs_enabled = 1; + continue; + } + + err = android_enable_function(dev, name); + if (err) + pr_err("android_usb: Cannot enable '%s' (%d)", + name, err); + } + + mutex_unlock(&dev->mutex); + + return size; +} + +static ssize_t enable_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + return sprintf(buf, "%d\n", dev->enabled); +} +extern void usb_first_enable_store_flag(); +static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + struct android_usb_function *f; + int enabled = 0; + + +#if 0 + if (!cdev) + return -ENODEV; +#else + /* To resolve the panic in case of calibration mode. */ + if (!cdev) + return 0; +#endif + + mutex_lock(&dev->mutex); + usb_first_enable_store_flag(); + sscanf(buff, "%d", &enabled); + printk("%s(%d) call %s(%d) \n",current->comm,current->pid,__func__,enabled); + if (enabled && !dev->enabled) { + /* + * Update values in composite driver's copy of + * device descriptor. + */ + cdev->desc.idVendor = device_desc.idVendor; + cdev->desc.idProduct = device_desc.idProduct; + cdev->desc.bcdDevice = device_desc.bcdDevice; + cdev->desc.bDeviceClass = device_desc.bDeviceClass; + cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass; + cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol; + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->enable) + f->enable(f); + } + android_enable(dev); + dev->enabled = true; + } else if (!enabled && dev->enabled) { + android_disable(dev); + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->disable) + f->disable(f); + } + dev->enabled = false; + } else { + pr_err("android_usb: already %s\n", + dev->enabled ? "enabled" : "disabled"); + } + + mutex_unlock(&dev->mutex); + return size; +} + +static ssize_t state_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + char *state = "DISCONNECTED"; + unsigned long flags; + + if (!cdev) + goto out; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + state = "CONFIGURED"; + else if (dev->connected) + state = "CONNECTED"; + spin_unlock_irqrestore(&cdev->lock, flags); +out: + return sprintf(buf, "%s\n", state); +} + +#define DESCRIPTOR_ATTR(field, format_string) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, format_string, device_desc.field); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + int value; \ + if (sscanf(buf, format_string, &value) == 1) { \ + device_desc.field = value; \ + return size; \ + } \ + return -1; \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + +#define DESCRIPTOR_STRING_ATTR(field, buffer) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, "%s", buffer); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + if (size >= sizeof(buffer)) \ + return -EINVAL; \ + return strlcpy(buffer, buf, sizeof(buffer)); \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + + +DESCRIPTOR_ATTR(idVendor, "%04x\n") +DESCRIPTOR_ATTR(idProduct, "%04x\n") +DESCRIPTOR_ATTR(bcdDevice, "%04x\n") +DESCRIPTOR_ATTR(bDeviceClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceSubClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceProtocol, "%d\n") +DESCRIPTOR_STRING_ATTR(iManufacturer, manufacturer_string) +DESCRIPTOR_STRING_ATTR(iProduct, product_string) +DESCRIPTOR_STRING_ATTR(iSerial, serial_string) + +static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show, + functions_store); +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); +static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); + +static struct device_attribute *android_usb_attributes[] = { + &dev_attr_idVendor, + &dev_attr_idProduct, + &dev_attr_bcdDevice, + &dev_attr_bDeviceClass, + &dev_attr_bDeviceSubClass, + &dev_attr_bDeviceProtocol, + &dev_attr_iManufacturer, + &dev_attr_iProduct, + &dev_attr_iSerial, + &dev_attr_functions, + &dev_attr_enable, + &dev_attr_state, + NULL +}; + +/*-------------------------------------------------------------------------*/ +/* Composite driver */ + +static int android_bind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + int ret = 0; + + ret = android_bind_enabled_functions(dev, c); + if (ret) + return ret; + + return 0; +} + +static void android_unbind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + + android_unbind_enabled_functions(dev, c); +} + +static int android_bind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct usb_gadget *gadget = cdev->gadget; + int id, ret; + + /* + * Start disconnected. Userspace will connect the gadget once + * it is done configuring the functions. + */ + usb_gadget_disconnect(gadget); + + ret = android_init_functions(dev->functions, cdev); + if (ret) + return ret; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_MANUFACTURER_IDX].id = id; + device_desc.iManufacturer = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_PRODUCT_IDX].id = id; + device_desc.iProduct = id; + + /* Default strings - should be updated by userspace */ + strncpy(manufacturer_string, "Android", sizeof(manufacturer_string)-1); + strncpy(product_string, "Android", sizeof(product_string) - 1); + strncpy(serial_string, "0123456789ABCDEF", sizeof(serial_string) - 1); + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_SERIAL_IDX].id = id; + if(in_calibration() || in_autotest()){ + device_desc.iSerialNumber = 0; + cdev->desc.bDeviceClass = 0xff; + }else + device_desc.iSerialNumber = id; + + usb_gadget_set_selfpowered(gadget); + dev->cdev = cdev; + + return 0; +} + +static int android_usb_unbind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + + cancel_work_sync(&dev->work); + android_cleanup_functions(dev->functions); + return 0; +} + +/* HACK: android needs to override setup for accessory to work */ +static int (*composite_setup_func)(struct usb_gadget *gadget, const struct usb_ctrlrequest *c); + +static int +android_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c) +{ + struct android_dev *dev = _android_dev; + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_request *req = cdev->req; + struct android_usb_function *f; + int value = -EOPNOTSUPP; + unsigned long flags; + + req->zero = 0; + req->length = 0; + gadget->ep0->driver_data = cdev; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->ctrlrequest) { + value = f->ctrlrequest(f, cdev, c); + if (value >= 0) + break; + } + } + + /* Special case the accessory function. + * It needs to handle control requests before it is enabled. + */ + if (value < 0) + value = acc_ctrlrequest(cdev, c); + + if (value < 0) + value = composite_setup_func(gadget, c); + + spin_lock_irqsave(&cdev->lock, flags); + if (!dev->connected) { + dev->connected = 1; + schedule_work(&dev->work); + } else if (c->bRequest == USB_REQ_SET_CONFIGURATION && + cdev->config) { + schedule_work(&dev->work); + } + spin_unlock_irqrestore(&cdev->lock, flags); + + return value; +} + +static void android_disconnect(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + + /* accessory HID support can be active while the + accessory function is not actually enabled, + so we need to inform it when we are disconnected. + */ + acc_disconnect(); + + dev->connected = 0; + schedule_work(&dev->work); +} + +static struct usb_composite_driver android_usb_driver = { + .name = "android_usb", + .dev = &device_desc, + .strings = dev_strings, + .bind = android_bind, + .unbind = android_usb_unbind, + .disconnect = android_disconnect, + .max_speed = USB_SPEED_HIGH, +}; + +static int android_create_device(struct android_dev *dev) +{ + struct device_attribute **attrs = android_usb_attributes; + struct device_attribute *attr; + int err; + + dev->dev = device_create(android_class, NULL, + MKDEV(0, 0), NULL, "android0"); + if (IS_ERR(dev->dev)) + return PTR_ERR(dev->dev); + + dev_set_drvdata(dev->dev, dev); + + while ((attr = *attrs++)) { + err = device_create_file(dev->dev, attr); + if (err) { + device_destroy(android_class, dev->dev->devt); + return err; + } + } + return 0; +} + + +static int __init init(void) +{ + struct android_dev *dev; + int err; + + android_class = class_create(THIS_MODULE, "android_usb"); + if (IS_ERR(android_class)) + return PTR_ERR(android_class); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + err = -ENOMEM; + goto err_dev; + } + + dev->disable_depth = 1; + dev->functions = supported_functions; + INIT_LIST_HEAD(&dev->enabled_functions); + INIT_WORK(&dev->work, android_work); + mutex_init(&dev->mutex); + + err = android_create_device(dev); + if (err) { + pr_err("%s: failed to create android device %d", __func__, err); + goto err_create; + } + + _android_dev = dev; + + err = usb_composite_probe(&android_usb_driver); + if (err) { + pr_err("%s: failed to probe driver %d", __func__, err); + goto err_probe; + } + + /* HACK: exchange composite's setup with ours */ + composite_setup_func = android_usb_driver.gadget_driver.setup; + android_usb_driver.gadget_driver.setup = android_setup; + + return 0; + +err_probe: + device_destroy(android_class, dev->dev->devt); +err_create: + kfree(dev); +err_dev: + class_destroy(android_class); + return err; +} +late_initcall(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&android_usb_driver); + class_destroy(android_class); + kfree(_android_dev); + _android_dev = NULL; +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/at91_udc.c b/drivers/usb/gadget/at91_udc.c new file mode 100644 index 00000000..55e96131 --- /dev/null +++ b/drivers/usb/gadget/at91_udc.c @@ -0,0 +1,1967 @@ +/* + * at91_udc -- driver for at91-series USB peripheral controller + * + * Copyright (C) 2004 by Thomas Rathbone + * Copyright (C) 2005 by HP Labs + * Copyright (C) 2005 by David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#undef VERBOSE_DEBUG +#undef PACKET_TRACE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "at91_udc.h" + + +/* + * This controller is simple and PIO-only. It's used in many AT91-series + * full speed USB controllers, including the at91rm9200 (arm920T, with MMU), + * at91sam926x (arm926ejs, with MMU), and several no-mmu versions. + * + * This driver expects the board has been wired with two GPIOs supporting + * a VBUS sensing IRQ, and a D+ pullup. (They may be omitted, but the + * testing hasn't covered such cases.) + * + * The pullup is most important (so it's integrated on sam926x parts). It + * provides software control over whether the host enumerates the device. + * + * The VBUS sensing helps during enumeration, and allows both USB clocks + * (and the transceiver) to stay gated off until they're necessary, saving + * power. During USB suspend, the 48 MHz clock is gated off in hardware; + * it may also be gated off by software during some Linux sleep states. + */ + +#define DRIVER_VERSION "3 May 2006" + +static const char driver_name [] = "at91_udc"; +static const char ep0name[] = "ep0"; + +#define VBUS_POLL_TIMEOUT msecs_to_jiffies(1000) + +#define at91_udp_read(udc, reg) \ + __raw_readl((udc)->udp_baseaddr + (reg)) +#define at91_udp_write(udc, reg, val) \ + __raw_writel((val), (udc)->udp_baseaddr + (reg)) + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#include + +static const char debug_filename[] = "driver/udc"; + +#define FOURBITS "%s%s%s%s" +#define EIGHTBITS FOURBITS FOURBITS + +static void proc_ep_show(struct seq_file *s, struct at91_ep *ep) +{ + static char *types[] = { + "control", "out-iso", "out-bulk", "out-int", + "BOGUS", "in-iso", "in-bulk", "in-int"}; + + u32 csr; + struct at91_request *req; + unsigned long flags; + struct at91_udc *udc = ep->udc; + + spin_lock_irqsave(&udc->lock, flags); + + csr = __raw_readl(ep->creg); + + /* NOTE: not collecting per-endpoint irq statistics... */ + + seq_printf(s, "\n"); + seq_printf(s, "%s, maxpacket %d %s%s %s%s\n", + ep->ep.name, ep->ep.maxpacket, + ep->is_in ? "in" : "out", + ep->is_iso ? " iso" : "", + ep->is_pingpong + ? (ep->fifo_bank ? "pong" : "ping") + : "", + ep->stopped ? " stopped" : ""); + seq_printf(s, "csr %08x rxbytes=%d %s %s %s" EIGHTBITS "\n", + csr, + (csr & 0x07ff0000) >> 16, + (csr & (1 << 15)) ? "enabled" : "disabled", + (csr & (1 << 11)) ? "DATA1" : "DATA0", + types[(csr & 0x700) >> 8], + + /* iff type is control then print current direction */ + (!(csr & 0x700)) + ? ((csr & (1 << 7)) ? " IN" : " OUT") + : "", + (csr & (1 << 6)) ? " rxdatabk1" : "", + (csr & (1 << 5)) ? " forcestall" : "", + (csr & (1 << 4)) ? " txpktrdy" : "", + + (csr & (1 << 3)) ? " stallsent" : "", + (csr & (1 << 2)) ? " rxsetup" : "", + (csr & (1 << 1)) ? " rxdatabk0" : "", + (csr & (1 << 0)) ? " txcomp" : ""); + if (list_empty (&ep->queue)) + seq_printf(s, "\t(queue empty)\n"); + + else list_for_each_entry (req, &ep->queue, queue) { + unsigned length = req->req.actual; + + seq_printf(s, "\treq %p len %d/%d buf %p\n", + &req->req, length, + req->req.length, req->req.buf); + } + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void proc_irq_show(struct seq_file *s, const char *label, u32 mask) +{ + int i; + + seq_printf(s, "%s %04x:%s%s" FOURBITS, label, mask, + (mask & (1 << 13)) ? " wakeup" : "", + (mask & (1 << 12)) ? " endbusres" : "", + + (mask & (1 << 11)) ? " sofint" : "", + (mask & (1 << 10)) ? " extrsm" : "", + (mask & (1 << 9)) ? " rxrsm" : "", + (mask & (1 << 8)) ? " rxsusp" : ""); + for (i = 0; i < 8; i++) { + if (mask & (1 << i)) + seq_printf(s, " ep%d", i); + } + seq_printf(s, "\n"); +} + +static int proc_udc_show(struct seq_file *s, void *unused) +{ + struct at91_udc *udc = s->private; + struct at91_ep *ep; + u32 tmp; + + seq_printf(s, "%s: version %s\n", driver_name, DRIVER_VERSION); + + seq_printf(s, "vbus %s, pullup %s, %s powered%s, gadget %s\n\n", + udc->vbus ? "present" : "off", + udc->enabled + ? (udc->vbus ? "active" : "enabled") + : "disabled", + udc->selfpowered ? "self" : "VBUS", + udc->suspended ? ", suspended" : "", + udc->driver ? udc->driver->driver.name : "(none)"); + + /* don't access registers when interface isn't clocked */ + if (!udc->clocked) { + seq_printf(s, "(not clocked)\n"); + return 0; + } + + tmp = at91_udp_read(udc, AT91_UDP_FRM_NUM); + seq_printf(s, "frame %05x:%s%s frame=%d\n", tmp, + (tmp & AT91_UDP_FRM_OK) ? " ok" : "", + (tmp & AT91_UDP_FRM_ERR) ? " err" : "", + (tmp & AT91_UDP_NUM)); + + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + seq_printf(s, "glbstate %02x:%s" FOURBITS "\n", tmp, + (tmp & AT91_UDP_RMWUPE) ? " rmwupe" : "", + (tmp & AT91_UDP_RSMINPR) ? " rsminpr" : "", + (tmp & AT91_UDP_ESR) ? " esr" : "", + (tmp & AT91_UDP_CONFG) ? " confg" : "", + (tmp & AT91_UDP_FADDEN) ? " fadden" : ""); + + tmp = at91_udp_read(udc, AT91_UDP_FADDR); + seq_printf(s, "faddr %03x:%s fadd=%d\n", tmp, + (tmp & AT91_UDP_FEN) ? " fen" : "", + (tmp & AT91_UDP_FADD)); + + proc_irq_show(s, "imr ", at91_udp_read(udc, AT91_UDP_IMR)); + proc_irq_show(s, "isr ", at91_udp_read(udc, AT91_UDP_ISR)); + + if (udc->enabled && udc->vbus) { + proc_ep_show(s, &udc->ep[0]); + list_for_each_entry (ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->ep.desc) + proc_ep_show(s, ep); + } + } + return 0; +} + +static int proc_udc_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_udc_show, PDE_DATA(inode)); +} + +static const struct file_operations proc_ops = { + .owner = THIS_MODULE, + .open = proc_udc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void create_debug_file(struct at91_udc *udc) +{ + udc->pde = proc_create_data(debug_filename, 0, NULL, &proc_ops, udc); +} + +static void remove_debug_file(struct at91_udc *udc) +{ + if (udc->pde) + remove_proc_entry(debug_filename, NULL); +} + +#else + +static inline void create_debug_file(struct at91_udc *udc) {} +static inline void remove_debug_file(struct at91_udc *udc) {} + +#endif + + +/*-------------------------------------------------------------------------*/ + +static void done(struct at91_ep *ep, struct at91_request *req, int status) +{ + unsigned stopped = ep->stopped; + struct at91_udc *udc = ep->udc; + + list_del_init(&req->queue); + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + if (status && status != -ESHUTDOWN) + VDBG("%s done %p, status %d\n", ep->ep.name, req, status); + + ep->stopped = 1; + spin_unlock(&udc->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&udc->lock); + ep->stopped = stopped; + + /* ep0 is always ready; other endpoints need a non-empty queue */ + if (list_empty(&ep->queue) && ep->int_mask != (1 << 0)) + at91_udp_write(udc, AT91_UDP_IDR, ep->int_mask); +} + +/*-------------------------------------------------------------------------*/ + +/* bits indicating OUT fifo has data ready */ +#define RX_DATA_READY (AT91_UDP_RX_DATA_BK0 | AT91_UDP_RX_DATA_BK1) + +/* + * Endpoint FIFO CSR bits have a mix of bits, making it unsafe to just write + * back most of the value you just read (because of side effects, including + * bits that may change after reading and before writing). + * + * Except when changing a specific bit, always write values which: + * - clear SET_FX bits (setting them could change something) + * - set CLR_FX bits (clearing them could change something) + * + * There are also state bits like FORCESTALL, EPEDS, DIR, and EPTYPE + * that shouldn't normally be changed. + * + * NOTE at91sam9260 docs mention synch between UDPCK and MCK clock domains, + * implying a need to wait for one write to complete (test relevant bits) + * before starting the next write. This shouldn't be an issue given how + * infrequently we write, except maybe for write-then-read idioms. + */ +#define SET_FX (AT91_UDP_TXPKTRDY) +#define CLR_FX (RX_DATA_READY | AT91_UDP_RXSETUP \ + | AT91_UDP_STALLSENT | AT91_UDP_TXCOMP) + +/* pull OUT packet data from the endpoint's fifo */ +static int read_fifo (struct at91_ep *ep, struct at91_request *req) +{ + u32 __iomem *creg = ep->creg; + u8 __iomem *dreg = ep->creg + (AT91_UDP_FDR(0) - AT91_UDP_CSR(0)); + u32 csr; + u8 *buf; + unsigned int count, bufferspace, is_done; + + buf = req->req.buf + req->req.actual; + bufferspace = req->req.length - req->req.actual; + + /* + * there might be nothing to read if ep_queue() calls us, + * or if we already emptied both pingpong buffers + */ +rescan: + csr = __raw_readl(creg); + if ((csr & RX_DATA_READY) == 0) + return 0; + + count = (csr & AT91_UDP_RXBYTECNT) >> 16; + if (count > ep->ep.maxpacket) + count = ep->ep.maxpacket; + if (count > bufferspace) { + DBG("%s buffer overflow\n", ep->ep.name); + req->req.status = -EOVERFLOW; + count = bufferspace; + } + __raw_readsb(dreg, buf, count); + + /* release and swap pingpong mem bank */ + csr |= CLR_FX; + if (ep->is_pingpong) { + if (ep->fifo_bank == 0) { + csr &= ~(SET_FX | AT91_UDP_RX_DATA_BK0); + ep->fifo_bank = 1; + } else { + csr &= ~(SET_FX | AT91_UDP_RX_DATA_BK1); + ep->fifo_bank = 0; + } + } else + csr &= ~(SET_FX | AT91_UDP_RX_DATA_BK0); + __raw_writel(csr, creg); + + req->req.actual += count; + is_done = (count < ep->ep.maxpacket); + if (count == bufferspace) + is_done = 1; + + PACKET("%s %p out/%d%s\n", ep->ep.name, &req->req, count, + is_done ? " (done)" : ""); + + /* + * avoid extra trips through IRQ logic for packets already in + * the fifo ... maybe preventing an extra (expensive) OUT-NAK + */ + if (is_done) + done(ep, req, 0); + else if (ep->is_pingpong) { + /* + * One dummy read to delay the code because of a HW glitch: + * CSR returns bad RXCOUNT when read too soon after updating + * RX_DATA_BK flags. + */ + csr = __raw_readl(creg); + + bufferspace -= count; + buf += count; + goto rescan; + } + + return is_done; +} + +/* load fifo for an IN packet */ +static int write_fifo(struct at91_ep *ep, struct at91_request *req) +{ + u32 __iomem *creg = ep->creg; + u32 csr = __raw_readl(creg); + u8 __iomem *dreg = ep->creg + (AT91_UDP_FDR(0) - AT91_UDP_CSR(0)); + unsigned total, count, is_last; + u8 *buf; + + /* + * TODO: allow for writing two packets to the fifo ... that'll + * reduce the amount of IN-NAKing, but probably won't affect + * throughput much. (Unlike preventing OUT-NAKing!) + */ + + /* + * If ep_queue() calls us, the queue is empty and possibly in + * odd states like TXCOMP not yet cleared (we do it, saving at + * least one IRQ) or the fifo not yet being free. Those aren't + * issues normally (IRQ handler fast path). + */ + if (unlikely(csr & (AT91_UDP_TXCOMP | AT91_UDP_TXPKTRDY))) { + if (csr & AT91_UDP_TXCOMP) { + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_TXCOMP); + __raw_writel(csr, creg); + csr = __raw_readl(creg); + } + if (csr & AT91_UDP_TXPKTRDY) + return 0; + } + + buf = req->req.buf + req->req.actual; + prefetch(buf); + total = req->req.length - req->req.actual; + if (ep->ep.maxpacket < total) { + count = ep->ep.maxpacket; + is_last = 0; + } else { + count = total; + is_last = (count < ep->ep.maxpacket) || !req->req.zero; + } + + /* + * Write the packet, maybe it's a ZLP. + * + * NOTE: incrementing req->actual before we receive the ACK means + * gadget driver IN bytecounts can be wrong in fault cases. That's + * fixable with PIO drivers like this one (save "count" here, and + * do the increment later on TX irq), but not for most DMA hardware. + * + * So all gadget drivers must accept that potential error. Some + * hardware supports precise fifo status reporting, letting them + * recover when the actual bytecount matters (e.g. for USB Test + * and Measurement Class devices). + */ + __raw_writesb(dreg, buf, count); + csr &= ~SET_FX; + csr |= CLR_FX | AT91_UDP_TXPKTRDY; + __raw_writel(csr, creg); + req->req.actual += count; + + PACKET("%s %p in/%d%s\n", ep->ep.name, &req->req, count, + is_last ? " (done)" : ""); + if (is_last) + done(ep, req, 0); + return is_last; +} + +static void nuke(struct at91_ep *ep, int status) +{ + struct at91_request *req; + + /* terminate any request in the queue */ + ep->stopped = 1; + if (list_empty(&ep->queue)) + return; + + VDBG("%s %s\n", __func__, ep->ep.name); + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct at91_request, queue); + done(ep, req, status); + } +} + +/*-------------------------------------------------------------------------*/ + +static int at91_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct at91_ep *ep = container_of(_ep, struct at91_ep, ep); + struct at91_udc *udc; + u16 maxpacket; + u32 tmp; + unsigned long flags; + + if (!_ep || !ep + || !desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || (maxpacket = usb_endpoint_maxp(desc)) == 0 + || maxpacket > ep->maxpacket) { + DBG("bad ep or descriptor\n"); + return -EINVAL; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + DBG("bogus device state\n"); + return -ESHUTDOWN; + } + + tmp = usb_endpoint_type(desc); + switch (tmp) { + case USB_ENDPOINT_XFER_CONTROL: + DBG("only one control endpoint\n"); + return -EINVAL; + case USB_ENDPOINT_XFER_INT: + if (maxpacket > 64) + goto bogus_max; + break; + case USB_ENDPOINT_XFER_BULK: + switch (maxpacket) { + case 8: + case 16: + case 32: + case 64: + goto ok; + } +bogus_max: + DBG("bogus maxpacket %d\n", maxpacket); + return -EINVAL; + case USB_ENDPOINT_XFER_ISOC: + if (!ep->is_pingpong) { + DBG("iso requires double buffering\n"); + return -EINVAL; + } + break; + } + +ok: + spin_lock_irqsave(&udc->lock, flags); + + /* initialize endpoint to match this descriptor */ + ep->is_in = usb_endpoint_dir_in(desc); + ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC); + ep->stopped = 0; + if (ep->is_in) + tmp |= 0x04; + tmp <<= 8; + tmp |= AT91_UDP_EPEDS; + __raw_writel(tmp, ep->creg); + + ep->ep.maxpacket = maxpacket; + + /* + * reset/init endpoint fifo. NOTE: leaves fifo_bank alone, + * since endpoint resets don't reset hw pingpong state. + */ + at91_udp_write(udc, AT91_UDP_RST_EP, ep->int_mask); + at91_udp_write(udc, AT91_UDP_RST_EP, 0); + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_ep_disable (struct usb_ep * _ep) +{ + struct at91_ep *ep = container_of(_ep, struct at91_ep, ep); + struct at91_udc *udc = ep->udc; + unsigned long flags; + + if (ep == &ep->udc->ep[0]) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + + nuke(ep, -ESHUTDOWN); + + /* restore the endpoint's pristine config */ + ep->ep.desc = NULL; + ep->ep.maxpacket = ep->maxpacket; + + /* reset fifos and endpoint */ + if (ep->udc->clocked) { + at91_udp_write(udc, AT91_UDP_RST_EP, ep->int_mask); + at91_udp_write(udc, AT91_UDP_RST_EP, 0); + __raw_writel(0, ep->creg); + } + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/* + * this is a PIO-only driver, so there's nothing + * interesting for request or buffer allocation. + */ + +static struct usb_request * +at91_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct at91_request *req; + + req = kzalloc(sizeof (struct at91_request), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +static void at91_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct at91_request *req; + + req = container_of(_req, struct at91_request, req); + BUG_ON(!list_empty(&req->queue)); + kfree(req); +} + +static int at91_ep_queue(struct usb_ep *_ep, + struct usb_request *_req, gfp_t gfp_flags) +{ + struct at91_request *req; + struct at91_ep *ep; + struct at91_udc *udc; + int status; + unsigned long flags; + + req = container_of(_req, struct at91_request, req); + ep = container_of(_ep, struct at91_ep, ep); + + if (!_req || !_req->complete + || !_req->buf || !list_empty(&req->queue)) { + DBG("invalid request\n"); + return -EINVAL; + } + + if (!_ep || (!ep->ep.desc && ep->ep.name != ep0name)) { + DBG("invalid ep\n"); + return -EINVAL; + } + + udc = ep->udc; + + if (!udc || !udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + DBG("invalid device\n"); + return -EINVAL; + } + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* try to kickstart any empty and idle queue */ + if (list_empty(&ep->queue) && !ep->stopped) { + int is_ep0; + + /* + * If this control request has a non-empty DATA stage, this + * will start that stage. It works just like a non-control + * request (until the status stage starts, maybe early). + * + * If the data stage is empty, then this starts a successful + * IN/STATUS stage. (Unsuccessful ones use set_halt.) + */ + is_ep0 = (ep->ep.name == ep0name); + if (is_ep0) { + u32 tmp; + + if (!udc->req_pending) { + status = -EINVAL; + goto done; + } + + /* + * defer changing CONFG until after the gadget driver + * reconfigures the endpoints. + */ + if (udc->wait_for_config_ack) { + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + tmp ^= AT91_UDP_CONFG; + VDBG("toggle config\n"); + at91_udp_write(udc, AT91_UDP_GLB_STAT, tmp); + } + if (req->req.length == 0) { +ep0_in_status: + PACKET("ep0 in/status\n"); + status = 0; + tmp = __raw_readl(ep->creg); + tmp &= ~SET_FX; + tmp |= CLR_FX | AT91_UDP_TXPKTRDY; + __raw_writel(tmp, ep->creg); + udc->req_pending = 0; + goto done; + } + } + + if (ep->is_in) + status = write_fifo(ep, req); + else { + status = read_fifo(ep, req); + + /* IN/STATUS stage is otherwise triggered by irq */ + if (status && is_ep0) + goto ep0_in_status; + } + } else + status = 0; + + if (req && !status) { + list_add_tail (&req->queue, &ep->queue); + at91_udp_write(udc, AT91_UDP_IER, ep->int_mask); + } +done: + spin_unlock_irqrestore(&udc->lock, flags); + return (status < 0) ? status : 0; +} + +static int at91_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct at91_ep *ep; + struct at91_request *req; + unsigned long flags; + struct at91_udc *udc; + + ep = container_of(_ep, struct at91_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + udc = ep->udc; + + spin_lock_irqsave(&udc->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry (req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct at91_ep *ep = container_of(_ep, struct at91_ep, ep); + struct at91_udc *udc = ep->udc; + u32 __iomem *creg; + u32 csr; + unsigned long flags; + int status = 0; + + if (!_ep || ep->is_iso || !ep->udc->clocked) + return -EINVAL; + + creg = ep->creg; + spin_lock_irqsave(&udc->lock, flags); + + csr = __raw_readl(creg); + + /* + * fail with still-busy IN endpoints, ensuring correct sequencing + * of data tx then stall. note that the fifo rx bytecount isn't + * completely accurate as a tx bytecount. + */ + if (ep->is_in && (!list_empty(&ep->queue) || (csr >> 16) != 0)) + status = -EAGAIN; + else { + csr |= CLR_FX; + csr &= ~SET_FX; + if (value) { + csr |= AT91_UDP_FORCESTALL; + VDBG("halt %s\n", ep->ep.name); + } else { + at91_udp_write(udc, AT91_UDP_RST_EP, ep->int_mask); + at91_udp_write(udc, AT91_UDP_RST_EP, 0); + csr &= ~AT91_UDP_FORCESTALL; + } + __raw_writel(csr, creg); + } + + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +static const struct usb_ep_ops at91_ep_ops = { + .enable = at91_ep_enable, + .disable = at91_ep_disable, + .alloc_request = at91_ep_alloc_request, + .free_request = at91_ep_free_request, + .queue = at91_ep_queue, + .dequeue = at91_ep_dequeue, + .set_halt = at91_ep_set_halt, + /* there's only imprecise fifo status reporting */ +}; + +/*-------------------------------------------------------------------------*/ + +static int at91_get_frame(struct usb_gadget *gadget) +{ + struct at91_udc *udc = to_udc(gadget); + + if (!to_udc(gadget)->clocked) + return -EINVAL; + return at91_udp_read(udc, AT91_UDP_FRM_NUM) & AT91_UDP_NUM; +} + +static int at91_wakeup(struct usb_gadget *gadget) +{ + struct at91_udc *udc = to_udc(gadget); + u32 glbstate; + int status = -EINVAL; + unsigned long flags; + + DBG("%s\n", __func__ ); + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->clocked || !udc->suspended) + goto done; + + /* NOTE: some "early versions" handle ESR differently ... */ + + glbstate = at91_udp_read(udc, AT91_UDP_GLB_STAT); + if (!(glbstate & AT91_UDP_ESR)) + goto done; + glbstate |= AT91_UDP_ESR; + at91_udp_write(udc, AT91_UDP_GLB_STAT, glbstate); + +done: + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +/* reinit == restore initial software state */ +static void udc_reinit(struct at91_udc *udc) +{ + u32 i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->gadget.ep0->ep_list); + + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct at91_ep *ep = &udc->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->ep.desc = NULL; + ep->stopped = 0; + ep->fifo_bank = 0; + ep->ep.maxpacket = ep->maxpacket; + ep->creg = (void __iomem *) udc->udp_baseaddr + AT91_UDP_CSR(i); + /* initialize one queue per endpoint */ + INIT_LIST_HEAD(&ep->queue); + } +} + +static void stop_activity(struct at91_udc *udc) +{ + struct usb_gadget_driver *driver = udc->driver; + int i; + + if (udc->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->suspended = 0; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct at91_ep *ep = &udc->ep[i]; + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + if (driver) { + spin_unlock(&udc->lock); + driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + + udc_reinit(udc); +} + +static void clk_on(struct at91_udc *udc) +{ + if (udc->clocked) + return; + udc->clocked = 1; + clk_enable(udc->iclk); + clk_enable(udc->fclk); +} + +static void clk_off(struct at91_udc *udc) +{ + if (!udc->clocked) + return; + udc->clocked = 0; + udc->gadget.speed = USB_SPEED_UNKNOWN; + clk_disable(udc->fclk); + clk_disable(udc->iclk); +} + +/* + * activate/deactivate link with host; minimize power usage for + * inactive links by cutting clocks and transceiver power. + */ +static void pullup(struct at91_udc *udc, int is_on) +{ + int active = !udc->board.pullup_active_low; + + if (!udc->enabled || !udc->vbus) + is_on = 0; + DBG("%sactive\n", is_on ? "" : "in"); + + if (is_on) { + clk_on(udc); + at91_udp_write(udc, AT91_UDP_ICR, AT91_UDP_RXRSM); + at91_udp_write(udc, AT91_UDP_TXVC, 0); + if (cpu_is_at91rm9200()) + gpio_set_value(udc->board.pullup_pin, active); + else if (cpu_is_at91sam9260() || cpu_is_at91sam9263() || cpu_is_at91sam9g20()) { + u32 txvc = at91_udp_read(udc, AT91_UDP_TXVC); + + txvc |= AT91_UDP_TXVC_PUON; + at91_udp_write(udc, AT91_UDP_TXVC, txvc); + } else if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) { + u32 usbpucr; + + usbpucr = at91_matrix_read(AT91_MATRIX_USBPUCR); + usbpucr |= AT91_MATRIX_USBPUCR_PUON; + at91_matrix_write(AT91_MATRIX_USBPUCR, usbpucr); + } + } else { + stop_activity(udc); + at91_udp_write(udc, AT91_UDP_IDR, AT91_UDP_RXRSM); + at91_udp_write(udc, AT91_UDP_TXVC, AT91_UDP_TXVC_TXVDIS); + if (cpu_is_at91rm9200()) + gpio_set_value(udc->board.pullup_pin, !active); + else if (cpu_is_at91sam9260() || cpu_is_at91sam9263() || cpu_is_at91sam9g20()) { + u32 txvc = at91_udp_read(udc, AT91_UDP_TXVC); + + txvc &= ~AT91_UDP_TXVC_PUON; + at91_udp_write(udc, AT91_UDP_TXVC, txvc); + } else if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) { + u32 usbpucr; + + usbpucr = at91_matrix_read(AT91_MATRIX_USBPUCR); + usbpucr &= ~AT91_MATRIX_USBPUCR_PUON; + at91_matrix_write(AT91_MATRIX_USBPUCR, usbpucr); + } + clk_off(udc); + } +} + +/* vbus is here! turn everything on that's ready */ +static int at91_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct at91_udc *udc = to_udc(gadget); + unsigned long flags; + + /* VDBG("vbus %s\n", is_active ? "on" : "off"); */ + spin_lock_irqsave(&udc->lock, flags); + udc->vbus = (is_active != 0); + if (udc->driver) + pullup(udc, is_active); + else + pullup(udc, 0); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_pullup(struct usb_gadget *gadget, int is_on) +{ + struct at91_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->enabled = is_on = !!is_on; + pullup(udc, is_on); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_set_selfpowered(struct usb_gadget *gadget, int is_on) +{ + struct at91_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->selfpowered = (is_on != 0); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int at91_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int at91_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static const struct usb_gadget_ops at91_udc_ops = { + .get_frame = at91_get_frame, + .wakeup = at91_wakeup, + .set_selfpowered = at91_set_selfpowered, + .vbus_session = at91_vbus_session, + .pullup = at91_pullup, + .udc_start = at91_start, + .udc_stop = at91_stop, + + /* + * VBUS-powered devices may also also want to support bigger + * power budgets after an appropriate SET_CONFIGURATION. + */ + /* .vbus_power = at91_vbus_power, */ +}; + +/*-------------------------------------------------------------------------*/ + +static int handle_ep(struct at91_ep *ep) +{ + struct at91_request *req; + u32 __iomem *creg = ep->creg; + u32 csr = __raw_readl(creg); + + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, + struct at91_request, queue); + else + req = NULL; + + if (ep->is_in) { + if (csr & (AT91_UDP_STALLSENT | AT91_UDP_TXCOMP)) { + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_STALLSENT | AT91_UDP_TXCOMP); + __raw_writel(csr, creg); + } + if (req) + return write_fifo(ep, req); + + } else { + if (csr & AT91_UDP_STALLSENT) { + /* STALLSENT bit == ISOERR */ + if (ep->is_iso && req) + req->req.status = -EILSEQ; + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_STALLSENT); + __raw_writel(csr, creg); + csr = __raw_readl(creg); + } + if (req && (csr & RX_DATA_READY)) + return read_fifo(ep, req); + } + return 0; +} + +union setup { + u8 raw[8]; + struct usb_ctrlrequest r; +}; + +static void handle_setup(struct at91_udc *udc, struct at91_ep *ep, u32 csr) +{ + u32 __iomem *creg = ep->creg; + u8 __iomem *dreg = ep->creg + (AT91_UDP_FDR(0) - AT91_UDP_CSR(0)); + unsigned rxcount, i = 0; + u32 tmp; + union setup pkt; + int status = 0; + + /* read and ack SETUP; hard-fail for bogus packets */ + rxcount = (csr & AT91_UDP_RXBYTECNT) >> 16; + if (likely(rxcount == 8)) { + while (rxcount--) + pkt.raw[i++] = __raw_readb(dreg); + if (pkt.r.bRequestType & USB_DIR_IN) { + csr |= AT91_UDP_DIR; + ep->is_in = 1; + } else { + csr &= ~AT91_UDP_DIR; + ep->is_in = 0; + } + } else { + /* REVISIT this happens sometimes under load; why?? */ + ERR("SETUP len %d, csr %08x\n", rxcount, csr); + status = -EINVAL; + } + csr |= CLR_FX; + csr &= ~(SET_FX | AT91_UDP_RXSETUP); + __raw_writel(csr, creg); + udc->wait_for_addr_ack = 0; + udc->wait_for_config_ack = 0; + ep->stopped = 0; + if (unlikely(status != 0)) + goto stall; + +#define w_index le16_to_cpu(pkt.r.wIndex) +#define w_value le16_to_cpu(pkt.r.wValue) +#define w_length le16_to_cpu(pkt.r.wLength) + + VDBG("SETUP %02x.%02x v%04x i%04x l%04x\n", + pkt.r.bRequestType, pkt.r.bRequest, + w_value, w_index, w_length); + + /* + * A few standard requests get handled here, ones that touch + * hardware ... notably for device and endpoint features. + */ + udc->req_pending = 1; + csr = __raw_readl(creg); + csr |= CLR_FX; + csr &= ~SET_FX; + switch ((pkt.r.bRequestType << 8) | pkt.r.bRequest) { + + case ((USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_SET_ADDRESS: + __raw_writel(csr | AT91_UDP_TXPKTRDY, creg); + udc->addr = w_value; + udc->wait_for_addr_ack = 1; + udc->req_pending = 0; + /* FADDR is set later, when we ack host STATUS */ + return; + + case ((USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_SET_CONFIGURATION: + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT) & AT91_UDP_CONFG; + if (pkt.r.wValue) + udc->wait_for_config_ack = (tmp == 0); + else + udc->wait_for_config_ack = (tmp != 0); + if (udc->wait_for_config_ack) + VDBG("wait for config\n"); + /* CONFG is toggled later, if gadget driver succeeds */ + break; + + /* + * Hosts may set or clear remote wakeup status, and + * devices may report they're VBUS powered. + */ + case ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_GET_STATUS: + tmp = (udc->selfpowered << USB_DEVICE_SELF_POWERED); + if (at91_udp_read(udc, AT91_UDP_GLB_STAT) & AT91_UDP_ESR) + tmp |= (1 << USB_DEVICE_REMOTE_WAKEUP); + PACKET("get device status\n"); + __raw_writeb(tmp, dreg); + __raw_writeb(0, dreg); + goto write_in; + /* then STATUS starts later, automatically */ + case ((USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_SET_FEATURE: + if (w_value != USB_DEVICE_REMOTE_WAKEUP) + goto stall; + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + tmp |= AT91_UDP_ESR; + at91_udp_write(udc, AT91_UDP_GLB_STAT, tmp); + goto succeed; + case ((USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) + | USB_REQ_CLEAR_FEATURE: + if (w_value != USB_DEVICE_REMOTE_WAKEUP) + goto stall; + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + tmp &= ~AT91_UDP_ESR; + at91_udp_write(udc, AT91_UDP_GLB_STAT, tmp); + goto succeed; + + /* + * Interfaces have no feature settings; this is pretty useless. + * we won't even insist the interface exists... + */ + case ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE) << 8) + | USB_REQ_GET_STATUS: + PACKET("get interface status\n"); + __raw_writeb(0, dreg); + __raw_writeb(0, dreg); + goto write_in; + /* then STATUS starts later, automatically */ + case ((USB_TYPE_STANDARD|USB_RECIP_INTERFACE) << 8) + | USB_REQ_SET_FEATURE: + case ((USB_TYPE_STANDARD|USB_RECIP_INTERFACE) << 8) + | USB_REQ_CLEAR_FEATURE: + goto stall; + + /* + * Hosts may clear bulk/intr endpoint halt after the gadget + * driver sets it (not widely used); or set it (for testing) + */ + case ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT) << 8) + | USB_REQ_GET_STATUS: + tmp = w_index & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[tmp]; + if (tmp >= NUM_ENDPOINTS || (tmp && !ep->ep.desc)) + goto stall; + + if (tmp) { + if ((w_index & USB_DIR_IN)) { + if (!ep->is_in) + goto stall; + } else if (ep->is_in) + goto stall; + } + PACKET("get %s status\n", ep->ep.name); + if (__raw_readl(ep->creg) & AT91_UDP_FORCESTALL) + tmp = (1 << USB_ENDPOINT_HALT); + else + tmp = 0; + __raw_writeb(tmp, dreg); + __raw_writeb(0, dreg); + goto write_in; + /* then STATUS starts later, automatically */ + case ((USB_TYPE_STANDARD|USB_RECIP_ENDPOINT) << 8) + | USB_REQ_SET_FEATURE: + tmp = w_index & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[tmp]; + if (w_value != USB_ENDPOINT_HALT || tmp >= NUM_ENDPOINTS) + goto stall; + if (!ep->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; + + tmp = __raw_readl(ep->creg); + tmp &= ~SET_FX; + tmp |= CLR_FX | AT91_UDP_FORCESTALL; + __raw_writel(tmp, ep->creg); + goto succeed; + case ((USB_TYPE_STANDARD|USB_RECIP_ENDPOINT) << 8) + | 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->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(udc, AT91_UDP_RST_EP, ep->int_mask); + at91_udp_write(udc, 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 */ + if (udc->driver) { + spin_unlock(&udc->lock); + status = udc->driver->setup(&udc->gadget, &pkt.r); + spin_lock(&udc->lock); + } + else + status = -ENODEV; + 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; +} + +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(udc, AT91_UDP_FADDR, + AT91_UDP_FEN | udc->addr); + tmp = at91_udp_read(udc, AT91_UDP_GLB_STAT); + tmp &= ~AT91_UDP_FADDEN; + if (udc->addr) + tmp |= AT91_UDP_FADDEN; + at91_udp_write(udc, 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 at91_udc *udc = _udc; + u32 rescans = 5; + int disable_clock = 0; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->clocked) { + clk_on(udc); + disable_clock = 1; + } + + while (rescans--) { + u32 status; + + status = at91_udp_read(udc, AT91_UDP_ISR) + & at91_udp_read(udc, AT91_UDP_IMR); + if (!status) + break; + + /* USB reset irq: not maskable */ + if (status & AT91_UDP_ENDBUSRES) { + at91_udp_write(udc, AT91_UDP_IDR, ~MINIMUS_INTERRUPTUS); + at91_udp_write(udc, AT91_UDP_IER, MINIMUS_INTERRUPTUS); + /* Atmel code clears this irq twice */ + at91_udp_write(udc, AT91_UDP_ICR, AT91_UDP_ENDBUSRES); + at91_udp_write(udc, AT91_UDP_ICR, AT91_UDP_ENDBUSRES); + VDBG("end bus reset\n"); + udc->addr = 0; + stop_activity(udc); + + /* enable ep0 */ + at91_udp_write(udc, AT91_UDP_CSR(0), + AT91_UDP_EPEDS | AT91_UDP_EPTYPE_CTRL); + udc->gadget.speed = USB_SPEED_FULL; + udc->suspended = 0; + at91_udp_write(udc, AT91_UDP_IER, AT91_UDP_EP(0)); + + /* + * NOTE: this driver keeps clocks off unless the + * USB host is present. That saves power, but for + * boards that don't support VBUS detection, both + * clocks need to be active most of the time. + */ + + /* host initiated suspend (3+ms bus idle) */ + } else if (status & AT91_UDP_RXSUSP) { + at91_udp_write(udc, AT91_UDP_IDR, AT91_UDP_RXSUSP); + at91_udp_write(udc, AT91_UDP_IER, AT91_UDP_RXRSM); + at91_udp_write(udc, 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) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + + /* host initiated resume */ + } else if (status & AT91_UDP_RXRSM) { + at91_udp_write(udc, AT91_UDP_IDR, AT91_UDP_RXRSM); + at91_udp_write(udc, AT91_UDP_IER, AT91_UDP_RXSUSP); + at91_udp_write(udc, 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) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + + /* 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++; + } + } + } + + if (disable_clock) + clk_off(udc); + + spin_unlock_irqrestore(&udc->lock, flags); + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static void nop_release(struct device *dev) +{ + /* nothing to free */ +} + +static struct at91_udc controller = { + .gadget = { + .ops = &at91_udc_ops, + .ep0 = &controller.ep[0].ep, + .name = driver_name, + .dev = { + .init_name = "gadget", + .release = nop_release, + } + }, + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &at91_ep_ops, + }, + .udc = &controller, + .maxpacket = 8, + .int_mask = 1 << 0, + }, + .ep[1] = { + .ep = { + .name = "ep1", + .ops = &at91_ep_ops, + }, + .udc = &controller, + .is_pingpong = 1, + .maxpacket = 64, + .int_mask = 1 << 1, + }, + .ep[2] = { + .ep = { + .name = "ep2", + .ops = &at91_ep_ops, + }, + .udc = &controller, + .is_pingpong = 1, + .maxpacket = 64, + .int_mask = 1 << 2, + }, + .ep[3] = { + .ep = { + /* could actually do bulk too */ + .name = "ep3-int", + .ops = &at91_ep_ops, + }, + .udc = &controller, + .maxpacket = 8, + .int_mask = 1 << 3, + }, + .ep[4] = { + .ep = { + .name = "ep4", + .ops = &at91_ep_ops, + }, + .udc = &controller, + .is_pingpong = 1, + .maxpacket = 256, + .int_mask = 1 << 4, + }, + .ep[5] = { + .ep = { + .name = "ep5", + .ops = &at91_ep_ops, + }, + .udc = &controller, + .is_pingpong = 1, + .maxpacket = 256, + .int_mask = 1 << 5, + }, + /* ep6 and ep7 are also reserved (custom silicon might use them) */ +}; + +static void at91_vbus_update(struct at91_udc *udc, unsigned value) +{ + value ^= udc->board.vbus_active_low; + if (value != udc->vbus) + at91_vbus_session(&udc->gadget, value); +} + +static irqreturn_t at91_vbus_irq(int irq, void *_udc) +{ + struct at91_udc *udc = _udc; + + /* vbus needs at least brief debouncing */ + udelay(10); + at91_vbus_update(udc, gpio_get_value(udc->board.vbus_pin)); + + return IRQ_HANDLED; +} + +static void at91_vbus_timer_work(struct work_struct *work) +{ + struct at91_udc *udc = container_of(work, struct at91_udc, + vbus_timer_work); + + at91_vbus_update(udc, gpio_get_value_cansleep(udc->board.vbus_pin)); + + if (!timer_pending(&udc->vbus_timer)) + mod_timer(&udc->vbus_timer, jiffies + VBUS_POLL_TIMEOUT); +} + +static void at91_vbus_timer(unsigned long data) +{ + struct at91_udc *udc = (struct at91_udc *)data; + + /* + * If we are polling vbus it is likely that the gpio is on an + * bus such as i2c or spi which may sleep, so schedule some work + * to read the vbus gpio + */ + schedule_work(&udc->vbus_timer_work); +} + +static int at91_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct at91_udc *udc; + + udc = container_of(gadget, struct at91_udc, gadget); + udc->driver = driver; + udc->gadget.dev.of_node = udc->pdev->dev.of_node; + udc->enabled = 1; + udc->selfpowered = 1; + + DBG("bound to %s\n", driver->driver.name); + return 0; +} + +static int at91_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct at91_udc *udc; + unsigned long flags; + + udc = container_of(gadget, struct at91_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + udc->enabled = 0; + at91_udp_write(udc, AT91_UDP_IDR, ~0); + spin_unlock_irqrestore(&udc->lock, flags); + + udc->driver = NULL; + + DBG("unbound from %s\n", driver->driver.name); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static void at91udc_shutdown(struct platform_device *dev) +{ + struct at91_udc *udc = platform_get_drvdata(dev); + unsigned long flags; + + /* force disconnect on reboot */ + spin_lock_irqsave(&udc->lock, flags); + pullup(platform_get_drvdata(dev), 0); + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void at91udc_of_init(struct at91_udc *udc, + struct device_node *np) +{ + struct at91_udc_data *board = &udc->board; + u32 val; + enum of_gpio_flags flags; + + if (of_property_read_u32(np, "atmel,vbus-polled", &val) == 0) + board->vbus_polled = 1; + + board->vbus_pin = of_get_named_gpio_flags(np, "atmel,vbus-gpio", 0, + &flags); + board->vbus_active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0; + + board->pullup_pin = of_get_named_gpio_flags(np, "atmel,pullup-gpio", 0, + &flags); + + board->pullup_active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0; +} + +static int at91udc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct at91_udc *udc; + int retval; + struct resource *res; + + if (!dev->platform_data && !pdev->dev.of_node) { + /* small (so we copy it) but critical! */ + DBG("missing platform_data\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + if (!request_mem_region(res->start, resource_size(res), driver_name)) { + DBG("someone's using UDC memory\n"); + return -EBUSY; + } + + /* init software state */ + udc = &controller; + udc->gadget.dev.parent = dev; + if (pdev->dev.of_node) + at91udc_of_init(udc, pdev->dev.of_node); + else + memcpy(&udc->board, dev->platform_data, + sizeof(struct at91_udc_data)); + udc->pdev = pdev; + udc->enabled = 0; + spin_lock_init(&udc->lock); + + /* rm9200 needs manual D+ pullup; off by default */ + if (cpu_is_at91rm9200()) { + if (!gpio_is_valid(udc->board.pullup_pin)) { + DBG("no D+ pullup?\n"); + retval = -ENODEV; + goto fail0; + } + retval = gpio_request(udc->board.pullup_pin, "udc_pullup"); + if (retval) { + DBG("D+ pullup is busy\n"); + goto fail0; + } + gpio_direction_output(udc->board.pullup_pin, + udc->board.pullup_active_low); + } + + /* newer chips have more FIFO memory than rm9200 */ + if (cpu_is_at91sam9260() || cpu_is_at91sam9g20()) { + udc->ep[0].maxpacket = 64; + udc->ep[3].maxpacket = 64; + udc->ep[4].maxpacket = 512; + udc->ep[5].maxpacket = 512; + } else if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) { + udc->ep[3].maxpacket = 64; + } else if (cpu_is_at91sam9263()) { + udc->ep[0].maxpacket = 64; + udc->ep[3].maxpacket = 64; + } + + udc->udp_baseaddr = ioremap(res->start, resource_size(res)); + if (!udc->udp_baseaddr) { + retval = -ENOMEM; + goto fail0a; + } + + udc_reinit(udc); + + /* 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"); + retval = -ENODEV; + /* NOTE: we "know" here that refcounts on these are NOPs */ + goto fail1; + } + + /* don't do anything until we have both gadget driver and VBUS */ + clk_enable(udc->iclk); + at91_udp_write(udc, AT91_UDP_TXVC, AT91_UDP_TXVC_TXVDIS); + at91_udp_write(udc, AT91_UDP_IDR, 0xffffffff); + /* Clear all pending interrupts - UDP may be used by bootloader. */ + at91_udp_write(udc, AT91_UDP_ICR, 0xffffffff); + clk_disable(udc->iclk); + + /* request UDC and maybe VBUS irqs */ + udc->udp_irq = platform_get_irq(pdev, 0); + retval = request_irq(udc->udp_irq, at91_udc_irq, + 0, driver_name, udc); + if (retval < 0) { + DBG("request irq %d failed\n", udc->udp_irq); + goto fail1; + } + if (gpio_is_valid(udc->board.vbus_pin)) { + retval = gpio_request(udc->board.vbus_pin, "udc_vbus"); + if (retval < 0) { + DBG("request vbus pin failed\n"); + goto fail2; + } + gpio_direction_input(udc->board.vbus_pin); + + /* + * Get the initial state of VBUS - we cannot expect + * a pending interrupt. + */ + udc->vbus = gpio_get_value_cansleep(udc->board.vbus_pin) ^ + udc->board.vbus_active_low; + + if (udc->board.vbus_polled) { + INIT_WORK(&udc->vbus_timer_work, at91_vbus_timer_work); + setup_timer(&udc->vbus_timer, at91_vbus_timer, + (unsigned long)udc); + mod_timer(&udc->vbus_timer, + jiffies + VBUS_POLL_TIMEOUT); + } else { + if (request_irq(gpio_to_irq(udc->board.vbus_pin), + at91_vbus_irq, 0, driver_name, udc)) { + DBG("request vbus irq %d failed\n", + udc->board.vbus_pin); + retval = -EBUSY; + goto fail3; + } + } + } else { + DBG("no VBUS detection, assuming always-on\n"); + udc->vbus = 1; + } + retval = usb_add_gadget_udc(dev, &udc->gadget); + if (retval) + goto fail4; + dev_set_drvdata(dev, udc); + device_init_wakeup(dev, 1); + create_debug_file(udc); + + INFO("%s version %s\n", driver_name, DRIVER_VERSION); + return 0; +fail4: + if (gpio_is_valid(udc->board.vbus_pin) && !udc->board.vbus_polled) + free_irq(gpio_to_irq(udc->board.vbus_pin), udc); +fail3: + if (gpio_is_valid(udc->board.vbus_pin)) + gpio_free(udc->board.vbus_pin); +fail2: + free_irq(udc->udp_irq, udc); +fail1: + iounmap(udc->udp_baseaddr); +fail0a: + if (cpu_is_at91rm9200()) + gpio_free(udc->board.pullup_pin); +fail0: + release_mem_region(res->start, resource_size(res)); + DBG("%s probe failed, %d\n", driver_name, retval); + return retval; +} + +static int __exit at91udc_remove(struct platform_device *pdev) +{ + struct at91_udc *udc = platform_get_drvdata(pdev); + struct resource *res; + unsigned long flags; + + DBG("remove\n"); + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) + return -EBUSY; + + spin_lock_irqsave(&udc->lock, flags); + pullup(udc, 0); + spin_unlock_irqrestore(&udc->lock, flags); + + device_init_wakeup(&pdev->dev, 0); + remove_debug_file(udc); + if (gpio_is_valid(udc->board.vbus_pin)) { + free_irq(gpio_to_irq(udc->board.vbus_pin), udc); + gpio_free(udc->board.vbus_pin); + } + free_irq(udc->udp_irq, udc); + iounmap(udc->udp_baseaddr); + + if (cpu_is_at91rm9200()) + gpio_free(udc->board.pullup_pin); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + clk_put(udc->iclk); + clk_put(udc->fclk); + + return 0; +} + +#ifdef CONFIG_PM +static int at91udc_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct at91_udc *udc = platform_get_drvdata(pdev); + int wake = udc->driver && device_may_wakeup(&pdev->dev); + unsigned long flags; + + /* Unless we can act normally to the host (letting it wake us up + * whenever it has work for us) force disconnect. Wakeup requires + * PLLB for USB events (signaling for reset, wakeup, or incoming + * tokens) and VBUS irqs (on systems which support them). + */ + if ((!udc->suspended && udc->addr) + || !wake + || at91_suspend_entering_slow_clock()) { + spin_lock_irqsave(&udc->lock, flags); + pullup(udc, 0); + wake = 0; + spin_unlock_irqrestore(&udc->lock, flags); + } else + enable_irq_wake(udc->udp_irq); + + udc->active_suspend = wake; + if (gpio_is_valid(udc->board.vbus_pin) && !udc->board.vbus_polled && wake) + enable_irq_wake(udc->board.vbus_pin); + return 0; +} + +static int at91udc_resume(struct platform_device *pdev) +{ + struct at91_udc *udc = platform_get_drvdata(pdev); + unsigned long flags; + + if (gpio_is_valid(udc->board.vbus_pin) && !udc->board.vbus_polled && + udc->active_suspend) + disable_irq_wake(udc->board.vbus_pin); + + /* maybe reconnect to host; if so, clocks on */ + if (udc->active_suspend) + disable_irq_wake(udc->udp_irq); + else { + spin_lock_irqsave(&udc->lock, flags); + pullup(udc, 1); + spin_unlock_irqrestore(&udc->lock, flags); + } + return 0; +} +#else +#define at91udc_suspend NULL +#define at91udc_resume NULL +#endif + +#if defined(CONFIG_OF) +static const struct of_device_id at91_udc_dt_ids[] = { + { .compatible = "atmel,at91rm9200-udc" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, at91_udc_dt_ids); +#endif + +static struct platform_driver at91_udc_driver = { + .remove = __exit_p(at91udc_remove), + .shutdown = at91udc_shutdown, + .suspend = at91udc_suspend, + .resume = at91udc_resume, + .driver = { + .name = (char *) driver_name, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(at91_udc_dt_ids), + }, +}; + +module_platform_driver_probe(at91_udc_driver, at91udc_probe); + +MODULE_DESCRIPTION("AT91 udc driver"); +MODULE_AUTHOR("Thomas Rathbone, David Brownell"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:at91_udc"); diff --git a/drivers/usb/gadget/at91_udc.h b/drivers/usb/gadget/at91_udc.h new file mode 100644 index 00000000..e647d1c2 --- /dev/null +++ b/drivers/usb/gadget/at91_udc.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2004 by Thomas Rathbone, HP Labs + * Copyright (C) 2005 by Ivan Kokshaysky + * Copyright (C) 2006 by SAN People + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef AT91_UDC_H +#define AT91_UDC_H + +/* + * USB Device Port (UDP) registers. + * Based on AT91RM9200 datasheet revision E. + */ + +#define AT91_UDP_FRM_NUM 0x00 /* Frame Number Register */ +#define AT91_UDP_NUM (0x7ff << 0) /* Frame Number */ +#define AT91_UDP_FRM_ERR (1 << 16) /* Frame Error */ +#define AT91_UDP_FRM_OK (1 << 17) /* Frame OK */ + +#define AT91_UDP_GLB_STAT 0x04 /* Global State Register */ +#define AT91_UDP_FADDEN (1 << 0) /* Function Address Enable */ +#define AT91_UDP_CONFG (1 << 1) /* Configured */ +#define AT91_UDP_ESR (1 << 2) /* Enable Send Resume */ +#define AT91_UDP_RSMINPR (1 << 3) /* Resume has been sent */ +#define AT91_UDP_RMWUPE (1 << 4) /* Remote Wake Up Enable */ + +#define AT91_UDP_FADDR 0x08 /* Function Address Register */ +#define AT91_UDP_FADD (0x7f << 0) /* Function Address Value */ +#define AT91_UDP_FEN (1 << 8) /* Function Enable */ + +#define AT91_UDP_IER 0x10 /* Interrupt Enable Register */ +#define AT91_UDP_IDR 0x14 /* Interrupt Disable Register */ +#define AT91_UDP_IMR 0x18 /* Interrupt Mask Register */ + +#define AT91_UDP_ISR 0x1c /* Interrupt Status Register */ +#define AT91_UDP_EP(n) (1 << (n)) /* Endpoint Interrupt Status */ +#define AT91_UDP_RXSUSP (1 << 8) /* USB Suspend Interrupt Status */ +#define AT91_UDP_RXRSM (1 << 9) /* USB Resume Interrupt Status */ +#define AT91_UDP_EXTRSM (1 << 10) /* External Resume Interrupt Status [AT91RM9200 only] */ +#define AT91_UDP_SOFINT (1 << 11) /* Start of Frame Interrupt Status */ +#define AT91_UDP_ENDBUSRES (1 << 12) /* End of Bus Reset Interrupt Status */ +#define AT91_UDP_WAKEUP (1 << 13) /* USB Wakeup Interrupt Status [AT91RM9200 only] */ + +#define AT91_UDP_ICR 0x20 /* Interrupt Clear Register */ +#define AT91_UDP_RST_EP 0x28 /* Reset Endpoint Register */ + +#define AT91_UDP_CSR(n) (0x30+((n)*4)) /* Endpoint Control/Status Registers 0-7 */ +#define AT91_UDP_TXCOMP (1 << 0) /* Generates IN packet with data previously written in DPR */ +#define AT91_UDP_RX_DATA_BK0 (1 << 1) /* Receive Data Bank 0 */ +#define AT91_UDP_RXSETUP (1 << 2) /* Send STALL to the host */ +#define AT91_UDP_STALLSENT (1 << 3) /* Stall Sent / Isochronous error (Isochronous endpoints) */ +#define AT91_UDP_TXPKTRDY (1 << 4) /* Transmit Packet Ready */ +#define AT91_UDP_FORCESTALL (1 << 5) /* Force Stall */ +#define AT91_UDP_RX_DATA_BK1 (1 << 6) /* Receive Data Bank 1 */ +#define AT91_UDP_DIR (1 << 7) /* Transfer Direction */ +#define AT91_UDP_EPTYPE (7 << 8) /* Endpoint Type */ +#define AT91_UDP_EPTYPE_CTRL (0 << 8) +#define AT91_UDP_EPTYPE_ISO_OUT (1 << 8) +#define AT91_UDP_EPTYPE_BULK_OUT (2 << 8) +#define AT91_UDP_EPTYPE_INT_OUT (3 << 8) +#define AT91_UDP_EPTYPE_ISO_IN (5 << 8) +#define AT91_UDP_EPTYPE_BULK_IN (6 << 8) +#define AT91_UDP_EPTYPE_INT_IN (7 << 8) +#define AT91_UDP_DTGLE (1 << 11) /* Data Toggle */ +#define AT91_UDP_EPEDS (1 << 15) /* Endpoint Enable/Disable */ +#define AT91_UDP_RXBYTECNT (0x7ff << 16) /* Number of bytes in FIFO */ + +#define AT91_UDP_FDR(n) (0x50+((n)*4)) /* Endpoint FIFO Data Registers 0-7 */ + +#define AT91_UDP_TXVC 0x74 /* Transceiver Control Register */ +#define AT91_UDP_TXVC_TXVDIS (1 << 8) /* Transceiver Disable */ +#define AT91_UDP_TXVC_PUON (1 << 9) /* PullUp On [AT91SAM9260 only] */ + +/*-------------------------------------------------------------------------*/ + +/* + * controller driver data structures + */ + +#define NUM_ENDPOINTS 6 + +/* + * hardware won't disable bus reset, or resume while the controller + * is suspended ... watching suspend helps keep the logic symmetric. + */ +#define MINIMUS_INTERRUPTUS \ + (AT91_UDP_ENDBUSRES | AT91_UDP_RXRSM | AT91_UDP_RXSUSP) + +struct at91_ep { + struct usb_ep ep; + struct list_head queue; + struct at91_udc *udc; + void __iomem *creg; + + unsigned maxpacket:16; + u8 int_mask; + unsigned is_pingpong:1; + + unsigned stopped:1; + unsigned is_in:1; + unsigned is_iso:1; + unsigned fifo_bank:1; +}; + +/* + * driver is non-SMP, and just blocks IRQs whenever it needs + * access protection for chip registers or driver state + */ +struct at91_udc { + struct usb_gadget gadget; + struct at91_ep ep[NUM_ENDPOINTS]; + struct usb_gadget_driver *driver; + unsigned vbus:1; + unsigned enabled:1; + unsigned clocked:1; + unsigned suspended:1; + unsigned req_pending:1; + unsigned wait_for_addr_ack:1; + unsigned wait_for_config_ack:1; + unsigned selfpowered:1; + unsigned active_suspend:1; + u8 addr; + struct at91_udc_data board; + struct clk *iclk, *fclk; + struct platform_device *pdev; + struct proc_dir_entry *pde; + void __iomem *udp_baseaddr; + int udp_irq; + spinlock_t lock; + struct timer_list vbus_timer; + struct work_struct vbus_timer_work; +}; + +static inline struct at91_udc *to_udc(struct usb_gadget *g) +{ + return container_of(g, struct at91_udc, gadget); +} + +struct at91_request { + struct usb_request req; + struct list_head queue; +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef VERBOSE_DEBUG +# define VDBG DBG +#else +# define VDBG(stuff...) do{}while(0) +#endif + +#ifdef PACKET_TRACE +# define PACKET VDBG +#else +# define PACKET(stuff...) do{}while(0) +#endif + +#define ERR(stuff...) pr_err("udc: " stuff) +#define WARNING(stuff...) pr_warning("udc: " stuff) +#define INFO(stuff...) pr_info("udc: " stuff) +#define DBG(stuff...) pr_debug("udc: " stuff) + +#endif + diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c new file mode 100644 index 00000000..5a5128a2 --- /dev/null +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -0,0 +1,2040 @@ +/* + * Driver for the Atmel USBA high speed USB device controller + * + * Copyright (C) 2005-2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "atmel_usba_udc.h" + + +static struct usba_udc the_udc; +static struct usba_ep *usba_ep; + +#ifdef CONFIG_USB_GADGET_DEBUG_FS +#include +#include + +static int queue_dbg_open(struct inode *inode, struct file *file) +{ + struct usba_ep *ep = inode->i_private; + struct usba_request *req, *req_copy; + struct list_head *queue_data; + + queue_data = kmalloc(sizeof(*queue_data), GFP_KERNEL); + if (!queue_data) + return -ENOMEM; + INIT_LIST_HEAD(queue_data); + + spin_lock_irq(&ep->udc->lock); + list_for_each_entry(req, &ep->queue, queue) { + req_copy = kmemdup(req, sizeof(*req_copy), GFP_ATOMIC); + if (!req_copy) + goto fail; + list_add_tail(&req_copy->queue, queue_data); + } + spin_unlock_irq(&ep->udc->lock); + + file->private_data = queue_data; + return 0; + +fail: + spin_unlock_irq(&ep->udc->lock); + list_for_each_entry_safe(req, req_copy, queue_data, queue) { + list_del(&req->queue); + kfree(req); + } + kfree(queue_data); + return -ENOMEM; +} + +/* + * bbbbbbbb llllllll IZS sssss nnnn FDL\n\0 + * + * b: buffer address + * l: buffer length + * I/i: interrupt/no interrupt + * Z/z: zero/no zero + * S/s: short ok/short not ok + * s: status + * n: nr_packets + * F/f: submitted/not submitted to FIFO + * D/d: using/not using DMA + * L/l: last transaction/not last transaction + */ +static ssize_t queue_dbg_read(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct list_head *queue = file->private_data; + struct usba_request *req, *tmp_req; + size_t len, remaining, actual = 0; + char tmpbuf[38]; + + if (!access_ok(VERIFY_WRITE, buf, nbytes)) + return -EFAULT; + + mutex_lock(&file_inode(file)->i_mutex); + list_for_each_entry_safe(req, tmp_req, queue, queue) { + len = snprintf(tmpbuf, sizeof(tmpbuf), + "%8p %08x %c%c%c %5d %c%c%c\n", + req->req.buf, req->req.length, + req->req.no_interrupt ? 'i' : 'I', + req->req.zero ? 'Z' : 'z', + req->req.short_not_ok ? 's' : 'S', + req->req.status, + req->submitted ? 'F' : 'f', + req->using_dma ? 'D' : 'd', + req->last_transaction ? 'L' : 'l'); + len = min(len, sizeof(tmpbuf)); + if (len > nbytes) + break; + + list_del(&req->queue); + kfree(req); + + remaining = __copy_to_user(buf, tmpbuf, len); + actual += len - remaining; + if (remaining) + break; + + nbytes -= len; + buf += len; + } + mutex_unlock(&file_inode(file)->i_mutex); + + return actual; +} + +static int queue_dbg_release(struct inode *inode, struct file *file) +{ + struct list_head *queue_data = file->private_data; + struct usba_request *req, *tmp_req; + + list_for_each_entry_safe(req, tmp_req, queue_data, queue) { + list_del(&req->queue); + kfree(req); + } + kfree(queue_data); + return 0; +} + +static int regs_dbg_open(struct inode *inode, struct file *file) +{ + struct usba_udc *udc; + unsigned int i; + u32 *data; + int ret = -ENOMEM; + + mutex_lock(&inode->i_mutex); + udc = inode->i_private; + data = kmalloc(inode->i_size, GFP_KERNEL); + if (!data) + goto out; + + spin_lock_irq(&udc->lock); + for (i = 0; i < inode->i_size / 4; i++) + data[i] = __raw_readl(udc->regs + i * 4); + spin_unlock_irq(&udc->lock); + + file->private_data = data; + ret = 0; + +out: + mutex_unlock(&inode->i_mutex); + + return ret; +} + +static ssize_t regs_dbg_read(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct inode *inode = file_inode(file); + int ret; + + mutex_lock(&inode->i_mutex); + ret = simple_read_from_buffer(buf, nbytes, ppos, + file->private_data, + file_inode(file)->i_size); + mutex_unlock(&inode->i_mutex); + + return ret; +} + +static int regs_dbg_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + return 0; +} + +const struct file_operations queue_dbg_fops = { + .owner = THIS_MODULE, + .open = queue_dbg_open, + .llseek = no_llseek, + .read = queue_dbg_read, + .release = queue_dbg_release, +}; + +const struct file_operations regs_dbg_fops = { + .owner = THIS_MODULE, + .open = regs_dbg_open, + .llseek = generic_file_llseek, + .read = regs_dbg_read, + .release = regs_dbg_release, +}; + +static void usba_ep_init_debugfs(struct usba_udc *udc, + struct usba_ep *ep) +{ + struct dentry *ep_root; + + ep_root = debugfs_create_dir(ep->ep.name, udc->debugfs_root); + if (!ep_root) + goto err_root; + ep->debugfs_dir = ep_root; + + ep->debugfs_queue = debugfs_create_file("queue", 0400, ep_root, + ep, &queue_dbg_fops); + if (!ep->debugfs_queue) + goto err_queue; + + if (ep->can_dma) { + ep->debugfs_dma_status + = debugfs_create_u32("dma_status", 0400, ep_root, + &ep->last_dma_status); + if (!ep->debugfs_dma_status) + goto err_dma_status; + } + if (ep_is_control(ep)) { + ep->debugfs_state + = debugfs_create_u32("state", 0400, ep_root, + &ep->state); + if (!ep->debugfs_state) + goto err_state; + } + + return; + +err_state: + if (ep->can_dma) + debugfs_remove(ep->debugfs_dma_status); +err_dma_status: + debugfs_remove(ep->debugfs_queue); +err_queue: + debugfs_remove(ep_root); +err_root: + dev_err(&ep->udc->pdev->dev, + "failed to create debugfs directory for %s\n", ep->ep.name); +} + +static void usba_ep_cleanup_debugfs(struct usba_ep *ep) +{ + debugfs_remove(ep->debugfs_queue); + debugfs_remove(ep->debugfs_dma_status); + debugfs_remove(ep->debugfs_state); + debugfs_remove(ep->debugfs_dir); + ep->debugfs_dma_status = NULL; + ep->debugfs_dir = NULL; +} + +static void usba_init_debugfs(struct usba_udc *udc) +{ + struct dentry *root, *regs; + struct resource *regs_resource; + + root = debugfs_create_dir(udc->gadget.name, NULL); + if (IS_ERR(root) || !root) + goto err_root; + udc->debugfs_root = root; + + regs = debugfs_create_file("regs", 0400, root, udc, ®s_dbg_fops); + if (!regs) + goto err_regs; + + regs_resource = platform_get_resource(udc->pdev, IORESOURCE_MEM, + CTRL_IOMEM_ID); + regs->d_inode->i_size = resource_size(regs_resource); + udc->debugfs_regs = regs; + + usba_ep_init_debugfs(udc, to_usba_ep(udc->gadget.ep0)); + + return; + +err_regs: + debugfs_remove(root); +err_root: + udc->debugfs_root = NULL; + dev_err(&udc->pdev->dev, "debugfs is not available\n"); +} + +static void usba_cleanup_debugfs(struct usba_udc *udc) +{ + usba_ep_cleanup_debugfs(to_usba_ep(udc->gadget.ep0)); + debugfs_remove(udc->debugfs_regs); + debugfs_remove(udc->debugfs_root); + udc->debugfs_regs = NULL; + udc->debugfs_root = NULL; +} +#else +static inline void usba_ep_init_debugfs(struct usba_udc *udc, + struct usba_ep *ep) +{ + +} + +static inline void usba_ep_cleanup_debugfs(struct usba_ep *ep) +{ + +} + +static inline void usba_init_debugfs(struct usba_udc *udc) +{ + +} + +static inline void usba_cleanup_debugfs(struct usba_udc *udc) +{ + +} +#endif + +static int vbus_is_present(struct usba_udc *udc) +{ + if (gpio_is_valid(udc->vbus_pin)) + return gpio_get_value(udc->vbus_pin) ^ udc->vbus_pin_inverted; + + /* No Vbus detection: Assume always present */ + return 1; +} + +#if defined(CONFIG_ARCH_AT91SAM9RL) + +#include + +static void toggle_bias(int is_on) +{ + unsigned int uckr = at91_pmc_read(AT91_CKGR_UCKR); + + if (is_on) + at91_pmc_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); + else + at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); +} + +#else + +static void toggle_bias(int is_on) +{ +} + +#endif /* CONFIG_ARCH_AT91SAM9RL */ + +static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) +{ + unsigned int transaction_len; + + transaction_len = req->req.length - req->req.actual; + req->last_transaction = 1; + if (transaction_len > ep->ep.maxpacket) { + transaction_len = ep->ep.maxpacket; + req->last_transaction = 0; + } else if (transaction_len == ep->ep.maxpacket && req->req.zero) + req->last_transaction = 0; + + DBG(DBG_QUEUE, "%s: submit_transaction, req %p (length %d)%s\n", + ep->ep.name, req, transaction_len, + req->last_transaction ? ", done" : ""); + + memcpy_toio(ep->fifo, req->req.buf + req->req.actual, transaction_len); + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + req->req.actual += transaction_len; +} + +static void submit_request(struct usba_ep *ep, struct usba_request *req) +{ + DBG(DBG_QUEUE, "%s: submit_request: req %p (length %d)\n", + ep->ep.name, req, req->req.length); + + req->req.actual = 0; + req->submitted = 1; + + if (req->using_dma) { + if (req->req.length == 0) { + usba_ep_writel(ep, CTL_ENB, USBA_TX_PK_RDY); + return; + } + + if (req->req.zero) + usba_ep_writel(ep, CTL_ENB, USBA_SHORT_PACKET); + else + usba_ep_writel(ep, CTL_DIS, USBA_SHORT_PACKET); + + usba_dma_writel(ep, ADDRESS, req->req.dma); + usba_dma_writel(ep, CONTROL, req->ctrl); + } else { + next_fifo_transaction(ep, req); + if (req->last_transaction) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); + } else { + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + usba_ep_writel(ep, CTL_ENB, USBA_TX_PK_RDY); + } + } +} + +static void submit_next_request(struct usba_ep *ep) +{ + struct usba_request *req; + + if (list_empty(&ep->queue)) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY | USBA_RX_BK_RDY); + return; + } + + req = list_entry(ep->queue.next, struct usba_request, queue); + if (!req->submitted) + submit_request(ep, req); +} + +static void send_status(struct usba_udc *udc, struct usba_ep *ep) +{ + ep->state = STATUS_STAGE_IN; + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); +} + +static void receive_data(struct usba_ep *ep) +{ + struct usba_udc *udc = ep->udc; + struct usba_request *req; + unsigned long status; + unsigned int bytecount, nr_busy; + int is_complete = 0; + + status = usba_ep_readl(ep, STA); + nr_busy = USBA_BFEXT(BUSY_BANKS, status); + + DBG(DBG_QUEUE, "receive data: nr_busy=%u\n", nr_busy); + + while (nr_busy > 0) { + if (list_empty(&ep->queue)) { + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + break; + } + req = list_entry(ep->queue.next, + struct usba_request, queue); + + bytecount = USBA_BFEXT(BYTE_COUNT, status); + + if (status & (1 << 31)) + is_complete = 1; + if (req->req.actual + bytecount >= req->req.length) { + is_complete = 1; + bytecount = req->req.length - req->req.actual; + } + + memcpy_fromio(req->req.buf + req->req.actual, + ep->fifo, bytecount); + req->req.actual += bytecount; + + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + + if (is_complete) { + DBG(DBG_QUEUE, "%s: request done\n", ep->ep.name); + req->req.status = 0; + list_del_init(&req->queue); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + spin_unlock(&udc->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&udc->lock); + } + + status = usba_ep_readl(ep, STA); + nr_busy = USBA_BFEXT(BUSY_BANKS, status); + + if (is_complete && ep_is_control(ep)) { + send_status(udc, ep); + break; + } + } +} + +static void +request_complete(struct usba_ep *ep, struct usba_request *req, int status) +{ + struct usba_udc *udc = ep->udc; + + WARN_ON(!list_empty(&req->queue)); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + + if (req->using_dma) + usb_gadget_unmap_request(&udc->gadget, &req->req, ep->is_in); + + DBG(DBG_GADGET | DBG_REQ, + "%s: req %p complete: status %d, actual %u\n", + ep->ep.name, req, req->req.status, req->req.actual); + + spin_unlock(&udc->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&udc->lock); +} + +static void +request_complete_list(struct usba_ep *ep, struct list_head *list, int status) +{ + struct usba_request *req, *tmp_req; + + list_for_each_entry_safe(req, tmp_req, list, queue) { + list_del_init(&req->queue); + request_complete(ep, req, status); + } +} + +static int +usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + unsigned long flags, ept_cfg, maxpacket; + unsigned int nr_trans; + + DBG(DBG_GADGET, "%s: ep_enable: desc=%p\n", ep->ep.name, desc); + + maxpacket = usb_endpoint_maxp(desc) & 0x7ff; + + if (((desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) != ep->index) + || ep->index == 0 + || desc->bDescriptorType != USB_DT_ENDPOINT + || maxpacket == 0 + || maxpacket > ep->fifo_size) { + DBG(DBG_ERR, "ep_enable: Invalid argument"); + return -EINVAL; + } + + ep->is_isoc = 0; + ep->is_in = 0; + + if (maxpacket <= 8) + ept_cfg = USBA_BF(EPT_SIZE, USBA_EPT_SIZE_8); + else + /* LSB is bit 1, not 0 */ + ept_cfg = USBA_BF(EPT_SIZE, fls(maxpacket - 1) - 3); + + DBG(DBG_HW, "%s: EPT_SIZE = %lu (maxpacket = %lu)\n", + ep->ep.name, ept_cfg, maxpacket); + + if (usb_endpoint_dir_in(desc)) { + ep->is_in = 1; + ept_cfg |= USBA_EPT_DIR_IN; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); + break; + case USB_ENDPOINT_XFER_ISOC: + if (!ep->can_isoc) { + DBG(DBG_ERR, "ep_enable: %s is not isoc capable\n", + ep->ep.name); + return -EINVAL; + } + + /* + * Bits 11:12 specify number of _additional_ + * transactions per microframe. + */ + nr_trans = ((usb_endpoint_maxp(desc) >> 11) & 3) + 1; + if (nr_trans > 3) + return -EINVAL; + + ep->is_isoc = 1; + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_ISO); + + /* + * Do triple-buffering on high-bandwidth iso endpoints. + */ + if (nr_trans > 1 && ep->nr_banks == 3) + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_TRIPLE); + else + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_DOUBLE); + ept_cfg |= USBA_BF(NB_TRANS, nr_trans); + break; + case USB_ENDPOINT_XFER_BULK: + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_DOUBLE); + break; + case USB_ENDPOINT_XFER_INT: + ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_INT); + ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_DOUBLE); + break; + } + + spin_lock_irqsave(&ep->udc->lock, flags); + + ep->ep.desc = desc; + ep->ep.maxpacket = maxpacket; + + usba_ep_writel(ep, CFG, ept_cfg); + usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE); + + if (ep->can_dma) { + u32 ctrl; + + usba_writel(udc, INT_ENB, + (usba_readl(udc, INT_ENB) + | USBA_BF(EPT_INT, 1 << ep->index) + | USBA_BF(DMA_INT, 1 << ep->index))); + ctrl = USBA_AUTO_VALID | USBA_INTDIS_DMA; + usba_ep_writel(ep, CTL_ENB, ctrl); + } else { + usba_writel(udc, INT_ENB, + (usba_readl(udc, INT_ENB) + | USBA_BF(EPT_INT, 1 << ep->index))); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + DBG(DBG_HW, "EPT_CFG%d after init: %#08lx\n", ep->index, + (unsigned long)usba_ep_readl(ep, CFG)); + DBG(DBG_HW, "INT_ENB after init: %#08lx\n", + (unsigned long)usba_readl(udc, INT_ENB)); + + return 0; +} + +static int usba_ep_disable(struct usb_ep *_ep) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + LIST_HEAD(req_list); + unsigned long flags; + + DBG(DBG_GADGET, "ep_disable: %s\n", ep->ep.name); + + spin_lock_irqsave(&udc->lock, flags); + + if (!ep->ep.desc) { + spin_unlock_irqrestore(&udc->lock, flags); + /* REVISIT because this driver disables endpoints in + * reset_all_endpoints() before calling disconnect(), + * most gadget drivers would trigger this non-error ... + */ + if (udc->gadget.speed != USB_SPEED_UNKNOWN) + DBG(DBG_ERR, "ep_disable: %s not enabled\n", + ep->ep.name); + return -EINVAL; + } + ep->ep.desc = NULL; + + list_splice_init(&ep->queue, &req_list); + if (ep->can_dma) { + usba_dma_writel(ep, CONTROL, 0); + usba_dma_writel(ep, ADDRESS, 0); + usba_dma_readl(ep, STATUS); + } + usba_ep_writel(ep, CTL_DIS, USBA_EPT_ENABLE); + usba_writel(udc, INT_ENB, + usba_readl(udc, INT_ENB) + & ~USBA_BF(EPT_INT, 1 << ep->index)); + + request_complete_list(ep, &req_list, -ESHUTDOWN); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request * +usba_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct usba_request *req; + + DBG(DBG_GADGET, "ep_alloc_request: %p, 0x%x\n", _ep, gfp_flags); + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void +usba_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usba_request *req = to_usba_req(_req); + + DBG(DBG_GADGET, "ep_free_request: %p, %p\n", _ep, _req); + + kfree(req); +} + +static int queue_dma(struct usba_udc *udc, struct usba_ep *ep, + struct usba_request *req, gfp_t gfp_flags) +{ + unsigned long flags; + int ret; + + DBG(DBG_DMA, "%s: req l/%u d/%08x %c%c%c\n", + ep->ep.name, req->req.length, req->req.dma, + req->req.zero ? 'Z' : 'z', + req->req.short_not_ok ? 'S' : 's', + req->req.no_interrupt ? 'I' : 'i'); + + if (req->req.length > 0x10000) { + /* Lengths from 0 to 65536 (inclusive) are supported */ + DBG(DBG_ERR, "invalid request length %u\n", req->req.length); + return -EINVAL; + } + + ret = usb_gadget_map_request(&udc->gadget, &req->req, ep->is_in); + if (ret) + return ret; + + req->using_dma = 1; + req->ctrl = USBA_BF(DMA_BUF_LEN, req->req.length) + | USBA_DMA_CH_EN | USBA_DMA_END_BUF_IE + | USBA_DMA_END_TR_EN | USBA_DMA_END_TR_IE; + + if (ep->is_in) + req->ctrl |= USBA_DMA_END_BUF_EN; + + /* + * Add this request to the queue and submit for DMA if + * possible. Check if we're still alive first -- we may have + * received a reset since last time we checked. + */ + ret = -ESHUTDOWN; + spin_lock_irqsave(&udc->lock, flags); + if (ep->ep.desc) { + if (list_empty(&ep->queue)) + submit_request(ep, req); + + list_add_tail(&req->queue, &ep->queue); + ret = 0; + } + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +static int +usba_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct usba_request *req = to_usba_req(_req); + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + unsigned long flags; + int ret; + + DBG(DBG_GADGET | DBG_QUEUE | DBG_REQ, "%s: queue req %p, len %u\n", + ep->ep.name, req, _req->length); + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN || + !ep->ep.desc) + return -ESHUTDOWN; + + req->submitted = 0; + req->using_dma = 0; + req->last_transaction = 0; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + if (ep->can_dma) + return queue_dma(udc, ep, req, gfp_flags); + + /* May have received a reset since last time we checked */ + ret = -ESHUTDOWN; + spin_lock_irqsave(&udc->lock, flags); + if (ep->ep.desc) { + list_add_tail(&req->queue, &ep->queue); + + if ((!ep_is_control(ep) && ep->is_in) || + (ep_is_control(ep) + && (ep->state == DATA_STAGE_IN + || ep->state == STATUS_STAGE_IN))) + usba_ep_writel(ep, CTL_ENB, USBA_TX_PK_RDY); + else + usba_ep_writel(ep, CTL_ENB, USBA_RX_BK_RDY); + ret = 0; + } + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +static void +usba_update_req(struct usba_ep *ep, struct usba_request *req, u32 status) +{ + req->req.actual = req->req.length - USBA_BFEXT(DMA_BUF_LEN, status); +} + +static int stop_dma(struct usba_ep *ep, u32 *pstatus) +{ + unsigned int timeout; + u32 status; + + /* + * Stop the DMA controller. When writing both CH_EN + * and LINK to 0, the other bits are not affected. + */ + usba_dma_writel(ep, CONTROL, 0); + + /* Wait for the FIFO to empty */ + for (timeout = 40; timeout; --timeout) { + status = usba_dma_readl(ep, STATUS); + if (!(status & USBA_DMA_CH_EN)) + break; + udelay(1); + } + + if (pstatus) + *pstatus = status; + + if (timeout == 0) { + dev_err(&ep->udc->pdev->dev, + "%s: timed out waiting for DMA FIFO to empty\n", + ep->ep.name); + return -ETIMEDOUT; + } + + return 0; +} + +static int usba_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + struct usba_request *req = to_usba_req(_req); + unsigned long flags; + u32 status; + + DBG(DBG_GADGET | DBG_QUEUE, "ep_dequeue: %s, req %p\n", + ep->ep.name, req); + + spin_lock_irqsave(&udc->lock, flags); + + if (req->using_dma) { + /* + * If this request is currently being transferred, + * stop the DMA controller and reset the FIFO. + */ + if (ep->queue.next == &req->queue) { + status = usba_dma_readl(ep, STATUS); + if (status & USBA_DMA_CH_EN) + stop_dma(ep, &status); + +#ifdef CONFIG_USB_GADGET_DEBUG_FS + ep->last_dma_status = status; +#endif + + usba_writel(udc, EPT_RST, 1 << ep->index); + + usba_update_req(ep, req, status); + } + } + + /* + * Errors should stop the queue from advancing until the + * completion function returns. + */ + list_del_init(&req->queue); + + request_complete(ep, req, -ECONNRESET); + + /* Process the next request if any */ + submit_next_request(ep); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int usba_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + unsigned long flags; + int ret = 0; + + DBG(DBG_GADGET, "endpoint %s: %s HALT\n", ep->ep.name, + value ? "set" : "clear"); + + if (!ep->ep.desc) { + DBG(DBG_ERR, "Attempted to halt uninitialized ep %s\n", + ep->ep.name); + return -ENODEV; + } + if (ep->is_isoc) { + DBG(DBG_ERR, "Attempted to halt isochronous ep %s\n", + ep->ep.name); + return -ENOTTY; + } + + spin_lock_irqsave(&udc->lock, flags); + + /* + * We can't halt IN endpoints while there are still data to be + * transferred + */ + if (!list_empty(&ep->queue) + || ((value && ep->is_in && (usba_ep_readl(ep, STA) + & USBA_BF(BUSY_BANKS, -1L))))) { + ret = -EAGAIN; + } else { + if (value) + usba_ep_writel(ep, SET_STA, USBA_FORCE_STALL); + else + usba_ep_writel(ep, CLR_STA, + USBA_FORCE_STALL | USBA_TOGGLE_CLR); + usba_ep_readl(ep, STA); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +static int usba_ep_fifo_status(struct usb_ep *_ep) +{ + struct usba_ep *ep = to_usba_ep(_ep); + + return USBA_BFEXT(BYTE_COUNT, usba_ep_readl(ep, STA)); +} + +static void usba_ep_fifo_flush(struct usb_ep *_ep) +{ + struct usba_ep *ep = to_usba_ep(_ep); + struct usba_udc *udc = ep->udc; + + usba_writel(udc, EPT_RST, 1 << ep->index); +} + +static const struct usb_ep_ops usba_ep_ops = { + .enable = usba_ep_enable, + .disable = usba_ep_disable, + .alloc_request = usba_ep_alloc_request, + .free_request = usba_ep_free_request, + .queue = usba_ep_queue, + .dequeue = usba_ep_dequeue, + .set_halt = usba_ep_set_halt, + .fifo_status = usba_ep_fifo_status, + .fifo_flush = usba_ep_fifo_flush, +}; + +static int usba_udc_get_frame(struct usb_gadget *gadget) +{ + struct usba_udc *udc = to_usba_udc(gadget); + + return USBA_BFEXT(FRAME_NUMBER, usba_readl(udc, FNUM)); +} + +static int usba_udc_wakeup(struct usb_gadget *gadget) +{ + struct usba_udc *udc = to_usba_udc(gadget); + unsigned long flags; + u32 ctrl; + int ret = -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + if (udc->devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) { + ctrl = usba_readl(udc, CTRL); + usba_writel(udc, CTRL, ctrl | USBA_REMOTE_WAKE_UP); + ret = 0; + } + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +static int +usba_udc_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) +{ + struct usba_udc *udc = to_usba_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + if (is_selfpowered) + udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int atmel_usba_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int atmel_usba_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static const struct usb_gadget_ops usba_udc_ops = { + .get_frame = usba_udc_get_frame, + .wakeup = usba_udc_wakeup, + .set_selfpowered = usba_udc_set_selfpowered, + .udc_start = atmel_usba_start, + .udc_stop = atmel_usba_stop, +}; + +static struct usb_endpoint_descriptor usba_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(64), + /* FIXME: I have no idea what to put here */ + .bInterval = 1, +}; + +static void nop_release(struct device *dev) +{ + +} + +static struct usba_udc the_udc = { + .gadget = { + .ops = &usba_udc_ops, + .ep_list = LIST_HEAD_INIT(the_udc.gadget.ep_list), + .max_speed = USB_SPEED_HIGH, + .name = "atmel_usba_udc", + .dev = { + .init_name = "gadget", + .release = nop_release, + }, + }, +}; + +/* + * Called with interrupts disabled and udc->lock held. + */ +static void reset_all_endpoints(struct usba_udc *udc) +{ + struct usba_ep *ep; + struct usba_request *req, *tmp_req; + + usba_writel(udc, EPT_RST, ~0UL); + + ep = to_usba_ep(udc->gadget.ep0); + list_for_each_entry_safe(req, tmp_req, &ep->queue, queue) { + list_del_init(&req->queue); + request_complete(ep, req, -ECONNRESET); + } + + /* NOTE: normally, the next call to the gadget driver is in + * charge of disabling endpoints... usually disconnect(). + * The exception would be entering a high speed test mode. + * + * FIXME remove this code ... and retest thoroughly. + */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->ep.desc) { + spin_unlock(&udc->lock); + usba_ep_disable(&ep->ep); + spin_lock(&udc->lock); + } + } +} + +static struct usba_ep *get_ep_by_addr(struct usba_udc *udc, u16 wIndex) +{ + struct usba_ep *ep; + + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) + return to_usba_ep(udc->gadget.ep0); + + list_for_each_entry (ep, &udc->gadget.ep_list, ep.ep_list) { + u8 bEndpointAddress; + + if (!ep->ep.desc) + continue; + bEndpointAddress = ep->ep.desc->bEndpointAddress; + if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) + continue; + if ((bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) + == (wIndex & USB_ENDPOINT_NUMBER_MASK)) + return ep; + } + + return NULL; +} + +/* Called with interrupts disabled and udc->lock held */ +static inline void set_protocol_stall(struct usba_udc *udc, struct usba_ep *ep) +{ + usba_ep_writel(ep, SET_STA, USBA_FORCE_STALL); + ep->state = WAIT_FOR_SETUP; +} + +static inline int is_stalled(struct usba_udc *udc, struct usba_ep *ep) +{ + if (usba_ep_readl(ep, STA) & USBA_FORCE_STALL) + return 1; + return 0; +} + +static inline void set_address(struct usba_udc *udc, unsigned int addr) +{ + u32 regval; + + DBG(DBG_BUS, "setting address %u...\n", addr); + regval = usba_readl(udc, CTRL); + regval = USBA_BFINS(DEV_ADDR, addr, regval); + usba_writel(udc, CTRL, regval); +} + +static int do_test_mode(struct usba_udc *udc) +{ + static const char test_packet_buffer[] = { + /* JKJKJKJK * 9 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* JJKKJJKK * 8 */ + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + /* JJKKJJKK * 8 */ + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, + /* JJJJJJJKKKKKKK * 8 */ + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* JJJJJJJK * 8 */ + 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, + /* {JKKKKKKK * 10}, JK */ + 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E + }; + struct usba_ep *ep; + struct device *dev = &udc->pdev->dev; + int test_mode; + + test_mode = udc->test_mode; + + /* Start from a clean slate */ + reset_all_endpoints(udc); + + switch (test_mode) { + case 0x0100: + /* Test_J */ + usba_writel(udc, TST, USBA_TST_J_MODE); + dev_info(dev, "Entering Test_J mode...\n"); + break; + case 0x0200: + /* Test_K */ + usba_writel(udc, TST, USBA_TST_K_MODE); + dev_info(dev, "Entering Test_K mode...\n"); + break; + case 0x0300: + /* + * Test_SE0_NAK: Force high-speed mode and set up ep0 + * for Bulk IN transfers + */ + ep = &usba_ep[0]; + usba_writel(udc, TST, + USBA_BF(SPEED_CFG, USBA_SPEED_CFG_FORCE_HIGH)); + usba_ep_writel(ep, CFG, + USBA_BF(EPT_SIZE, USBA_EPT_SIZE_64) + | USBA_EPT_DIR_IN + | USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK) + | USBA_BF(BK_NUMBER, 1)); + if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED)) { + set_protocol_stall(udc, ep); + dev_err(dev, "Test_SE0_NAK: ep0 not mapped\n"); + } else { + usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE); + dev_info(dev, "Entering Test_SE0_NAK mode...\n"); + } + break; + case 0x0400: + /* Test_Packet */ + ep = &usba_ep[0]; + usba_ep_writel(ep, CFG, + USBA_BF(EPT_SIZE, USBA_EPT_SIZE_64) + | USBA_EPT_DIR_IN + | USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK) + | USBA_BF(BK_NUMBER, 1)); + if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED)) { + set_protocol_stall(udc, ep); + dev_err(dev, "Test_Packet: ep0 not mapped\n"); + } else { + usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE); + usba_writel(udc, TST, USBA_TST_PKT_MODE); + memcpy_toio(ep->fifo, test_packet_buffer, + sizeof(test_packet_buffer)); + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + dev_info(dev, "Entering Test_Packet mode...\n"); + } + break; + default: + dev_err(dev, "Invalid test mode: 0x%04x\n", test_mode); + return -EINVAL; + } + + return 0; +} + +/* Avoid overly long expressions */ +static inline bool feature_is_dev_remote_wakeup(struct usb_ctrlrequest *crq) +{ + if (crq->wValue == cpu_to_le16(USB_DEVICE_REMOTE_WAKEUP)) + return true; + return false; +} + +static inline bool feature_is_dev_test_mode(struct usb_ctrlrequest *crq) +{ + if (crq->wValue == cpu_to_le16(USB_DEVICE_TEST_MODE)) + return true; + return false; +} + +static inline bool feature_is_ep_halt(struct usb_ctrlrequest *crq) +{ + if (crq->wValue == cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + return false; +} + +static int handle_ep0_setup(struct usba_udc *udc, struct usba_ep *ep, + struct usb_ctrlrequest *crq) +{ + int retval = 0; + + switch (crq->bRequest) { + case USB_REQ_GET_STATUS: { + u16 status; + + if (crq->bRequestType == (USB_DIR_IN | USB_RECIP_DEVICE)) { + status = cpu_to_le16(udc->devstatus); + } else if (crq->bRequestType + == (USB_DIR_IN | USB_RECIP_INTERFACE)) { + status = cpu_to_le16(0); + } else if (crq->bRequestType + == (USB_DIR_IN | USB_RECIP_ENDPOINT)) { + struct usba_ep *target; + + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + status = 0; + if (is_stalled(udc, target)) + status |= cpu_to_le16(1); + } else + goto delegate; + + /* Write directly to the FIFO. No queueing is done. */ + if (crq->wLength != cpu_to_le16(sizeof(status))) + goto stall; + ep->state = DATA_STAGE_IN; + __raw_writew(status, ep->fifo); + usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); + break; + } + + case USB_REQ_CLEAR_FEATURE: { + if (crq->bRequestType == USB_RECIP_DEVICE) { + if (feature_is_dev_remote_wakeup(crq)) + udc->devstatus + &= ~(1 << USB_DEVICE_REMOTE_WAKEUP); + else + /* Can't CLEAR_FEATURE TEST_MODE */ + goto stall; + } else if (crq->bRequestType == USB_RECIP_ENDPOINT) { + struct usba_ep *target; + + if (crq->wLength != cpu_to_le16(0) + || !feature_is_ep_halt(crq)) + goto stall; + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + usba_ep_writel(target, CLR_STA, USBA_FORCE_STALL); + if (target->index != 0) + usba_ep_writel(target, CLR_STA, + USBA_TOGGLE_CLR); + } else { + goto delegate; + } + + send_status(udc, ep); + break; + } + + case USB_REQ_SET_FEATURE: { + if (crq->bRequestType == USB_RECIP_DEVICE) { + if (feature_is_dev_test_mode(crq)) { + send_status(udc, ep); + ep->state = STATUS_STAGE_TEST; + udc->test_mode = le16_to_cpu(crq->wIndex); + return 0; + } else if (feature_is_dev_remote_wakeup(crq)) { + udc->devstatus |= 1 << USB_DEVICE_REMOTE_WAKEUP; + } else { + goto stall; + } + } else if (crq->bRequestType == USB_RECIP_ENDPOINT) { + struct usba_ep *target; + + if (crq->wLength != cpu_to_le16(0) + || !feature_is_ep_halt(crq)) + goto stall; + + target = get_ep_by_addr(udc, le16_to_cpu(crq->wIndex)); + if (!target) + goto stall; + + usba_ep_writel(target, SET_STA, USBA_FORCE_STALL); + } else + goto delegate; + + send_status(udc, ep); + break; + } + + case USB_REQ_SET_ADDRESS: + if (crq->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + goto delegate; + + set_address(udc, le16_to_cpu(crq->wValue)); + send_status(udc, ep); + ep->state = STATUS_STAGE_ADDR; + break; + + default: +delegate: + spin_unlock(&udc->lock); + retval = udc->driver->setup(&udc->gadget, crq); + spin_lock(&udc->lock); + } + + return retval; + +stall: + pr_err("udc: %s: Invalid setup request: %02x.%02x v%04x i%04x l%d, " + "halting endpoint...\n", + ep->ep.name, crq->bRequestType, crq->bRequest, + le16_to_cpu(crq->wValue), le16_to_cpu(crq->wIndex), + le16_to_cpu(crq->wLength)); + set_protocol_stall(udc, ep); + return -1; +} + +static void usba_control_irq(struct usba_udc *udc, struct usba_ep *ep) +{ + struct usba_request *req; + u32 epstatus; + u32 epctrl; + +restart: + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + + DBG(DBG_INT, "%s [%d]: s/%08x c/%08x\n", + ep->ep.name, ep->state, epstatus, epctrl); + + req = NULL; + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, + struct usba_request, queue); + + if ((epctrl & USBA_TX_PK_RDY) && !(epstatus & USBA_TX_PK_RDY)) { + if (req->submitted) + next_fifo_transaction(ep, req); + else + submit_request(ep, req); + + if (req->last_transaction) { + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); + } + goto restart; + } + if ((epstatus & epctrl) & USBA_TX_COMPLETE) { + usba_ep_writel(ep, CLR_STA, USBA_TX_COMPLETE); + + switch (ep->state) { + case DATA_STAGE_IN: + usba_ep_writel(ep, CTL_ENB, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = STATUS_STAGE_OUT; + break; + case STATUS_STAGE_ADDR: + /* Activate our new address */ + usba_writel(udc, CTRL, (usba_readl(udc, CTRL) + | USBA_FADDR_EN)); + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = WAIT_FOR_SETUP; + break; + case STATUS_STAGE_IN: + if (req) { + list_del_init(&req->queue); + request_complete(ep, req, 0); + submit_next_request(ep); + } + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = WAIT_FOR_SETUP; + break; + case STATUS_STAGE_TEST: + usba_ep_writel(ep, CTL_DIS, USBA_TX_COMPLETE); + ep->state = WAIT_FOR_SETUP; + if (do_test_mode(udc)) + set_protocol_stall(udc, ep); + break; + default: + pr_err("udc: %s: TXCOMP: Invalid endpoint state %d, " + "halting endpoint...\n", + ep->ep.name, ep->state); + set_protocol_stall(udc, ep); + break; + } + + goto restart; + } + if ((epstatus & epctrl) & USBA_RX_BK_RDY) { + switch (ep->state) { + case STATUS_STAGE_OUT: + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + + if (req) { + list_del_init(&req->queue); + request_complete(ep, req, 0); + } + ep->state = WAIT_FOR_SETUP; + break; + + case DATA_STAGE_OUT: + receive_data(ep); + break; + + default: + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + pr_err("udc: %s: RXRDY: Invalid endpoint state %d, " + "halting endpoint...\n", + ep->ep.name, ep->state); + set_protocol_stall(udc, ep); + break; + } + + goto restart; + } + if (epstatus & USBA_RX_SETUP) { + union { + struct usb_ctrlrequest crq; + unsigned long data[2]; + } crq; + unsigned int pkt_len; + int ret; + + if (ep->state != WAIT_FOR_SETUP) { + /* + * Didn't expect a SETUP packet at this + * point. Clean up any pending requests (which + * may be successful). + */ + int status = -EPROTO; + + /* + * RXRDY and TXCOMP are dropped when SETUP + * packets arrive. Just pretend we received + * the status packet. + */ + if (ep->state == STATUS_STAGE_OUT + || ep->state == STATUS_STAGE_IN) { + usba_ep_writel(ep, CTL_DIS, USBA_RX_BK_RDY); + status = 0; + } + + if (req) { + list_del_init(&req->queue); + request_complete(ep, req, status); + } + } + + pkt_len = USBA_BFEXT(BYTE_COUNT, usba_ep_readl(ep, STA)); + DBG(DBG_HW, "Packet length: %u\n", pkt_len); + if (pkt_len != sizeof(crq)) { + pr_warning("udc: Invalid packet length %u " + "(expected %zu)\n", pkt_len, sizeof(crq)); + set_protocol_stall(udc, ep); + return; + } + + DBG(DBG_FIFO, "Copying ctrl request from 0x%p:\n", ep->fifo); + memcpy_fromio(crq.data, ep->fifo, sizeof(crq)); + + /* Free up one bank in the FIFO so that we can + * generate or receive a reply right away. */ + usba_ep_writel(ep, CLR_STA, USBA_RX_SETUP); + + /* printk(KERN_DEBUG "setup: %d: %02x.%02x\n", + ep->state, crq.crq.bRequestType, + crq.crq.bRequest); */ + + if (crq.crq.bRequestType & USB_DIR_IN) { + /* + * The USB 2.0 spec states that "if wLength is + * zero, there is no data transfer phase." + * However, testusb #14 seems to actually + * expect a data phase even if wLength = 0... + */ + ep->state = DATA_STAGE_IN; + } else { + if (crq.crq.wLength != cpu_to_le16(0)) + ep->state = DATA_STAGE_OUT; + else + ep->state = STATUS_STAGE_IN; + } + + ret = -1; + if (ep->index == 0) + ret = handle_ep0_setup(udc, ep, &crq.crq); + else { + spin_unlock(&udc->lock); + ret = udc->driver->setup(&udc->gadget, &crq.crq); + spin_lock(&udc->lock); + } + + DBG(DBG_BUS, "req %02x.%02x, length %d, state %d, ret %d\n", + crq.crq.bRequestType, crq.crq.bRequest, + le16_to_cpu(crq.crq.wLength), ep->state, ret); + + if (ret < 0) { + /* Let the host know that we failed */ + set_protocol_stall(udc, ep); + } + } +} + +static void usba_ep_irq(struct usba_udc *udc, struct usba_ep *ep) +{ + struct usba_request *req; + u32 epstatus; + u32 epctrl; + + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + + DBG(DBG_INT, "%s: interrupt, status: 0x%08x\n", ep->ep.name, epstatus); + + while ((epctrl & USBA_TX_PK_RDY) && !(epstatus & USBA_TX_PK_RDY)) { + DBG(DBG_BUS, "%s: TX PK ready\n", ep->ep.name); + + if (list_empty(&ep->queue)) { + dev_warn(&udc->pdev->dev, "ep_irq: queue empty\n"); + usba_ep_writel(ep, CTL_DIS, USBA_TX_PK_RDY); + return; + } + + req = list_entry(ep->queue.next, struct usba_request, queue); + + if (req->using_dma) { + /* Send a zero-length packet */ + usba_ep_writel(ep, SET_STA, + USBA_TX_PK_RDY); + usba_ep_writel(ep, CTL_DIS, + USBA_TX_PK_RDY); + list_del_init(&req->queue); + submit_next_request(ep); + request_complete(ep, req, 0); + } else { + if (req->submitted) + next_fifo_transaction(ep, req); + else + submit_request(ep, req); + + if (req->last_transaction) { + list_del_init(&req->queue); + submit_next_request(ep); + request_complete(ep, req, 0); + } + } + + epstatus = usba_ep_readl(ep, STA); + epctrl = usba_ep_readl(ep, CTL); + } + if ((epstatus & epctrl) & USBA_RX_BK_RDY) { + DBG(DBG_BUS, "%s: RX data ready\n", ep->ep.name); + receive_data(ep); + usba_ep_writel(ep, CLR_STA, USBA_RX_BK_RDY); + } +} + +static void usba_dma_irq(struct usba_udc *udc, struct usba_ep *ep) +{ + struct usba_request *req; + u32 status, control, pending; + + status = usba_dma_readl(ep, STATUS); + control = usba_dma_readl(ep, CONTROL); +#ifdef CONFIG_USB_GADGET_DEBUG_FS + ep->last_dma_status = status; +#endif + pending = status & control; + DBG(DBG_INT | DBG_DMA, "dma irq, s/%#08x, c/%#08x\n", status, control); + + if (status & USBA_DMA_CH_EN) { + dev_err(&udc->pdev->dev, + "DMA_CH_EN is set after transfer is finished!\n"); + dev_err(&udc->pdev->dev, + "status=%#08x, pending=%#08x, control=%#08x\n", + status, pending, control); + + /* + * try to pretend nothing happened. We might have to + * do something here... + */ + } + + if (list_empty(&ep->queue)) + /* Might happen if a reset comes along at the right moment */ + return; + + if (pending & (USBA_DMA_END_TR_ST | USBA_DMA_END_BUF_ST)) { + req = list_entry(ep->queue.next, struct usba_request, queue); + usba_update_req(ep, req, status); + + list_del_init(&req->queue); + submit_next_request(ep); + request_complete(ep, req, 0); + } +} + +static irqreturn_t usba_udc_irq(int irq, void *devid) +{ + struct usba_udc *udc = devid; + u32 status; + u32 dma_status; + u32 ep_status; + + spin_lock(&udc->lock); + + status = usba_readl(udc, INT_STA); + DBG(DBG_INT, "irq, status=%#08x\n", status); + + if (status & USBA_DET_SUSPEND) { + toggle_bias(0); + usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); + DBG(DBG_BUS, "Suspend detected\n"); + if (udc->gadget.speed != USB_SPEED_UNKNOWN + && udc->driver && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (status & USBA_WAKE_UP) { + toggle_bias(1); + usba_writel(udc, INT_CLR, USBA_WAKE_UP); + DBG(DBG_BUS, "Wake Up CPU detected\n"); + } + + if (status & USBA_END_OF_RESUME) { + usba_writel(udc, INT_CLR, USBA_END_OF_RESUME); + DBG(DBG_BUS, "Resume detected\n"); + if (udc->gadget.speed != USB_SPEED_UNKNOWN + && udc->driver && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + } + + dma_status = USBA_BFEXT(DMA_INT, status); + if (dma_status) { + int i; + + for (i = 1; i < USBA_NR_ENDPOINTS; i++) + if (dma_status & (1 << i)) + usba_dma_irq(udc, &usba_ep[i]); + } + + ep_status = USBA_BFEXT(EPT_INT, status); + if (ep_status) { + int i; + + for (i = 0; i < USBA_NR_ENDPOINTS; i++) + if (ep_status & (1 << i)) { + if (ep_is_control(&usba_ep[i])) + usba_control_irq(udc, &usba_ep[i]); + else + usba_ep_irq(udc, &usba_ep[i]); + } + } + + if (status & USBA_END_OF_RESET) { + struct usba_ep *ep0; + + usba_writel(udc, INT_CLR, USBA_END_OF_RESET); + reset_all_endpoints(udc); + + if (udc->gadget.speed != USB_SPEED_UNKNOWN + && udc->driver->disconnect) { + udc->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + + if (status & USBA_HIGH_SPEED) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + DBG(DBG_BUS, "%s bus reset detected\n", + usb_speed_string(udc->gadget.speed)); + + ep0 = &usba_ep[0]; + ep0->ep.desc = &usba_ep0_desc; + ep0->state = WAIT_FOR_SETUP; + usba_ep_writel(ep0, CFG, + (USBA_BF(EPT_SIZE, EP0_EPT_SIZE) + | USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL) + | USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE))); + usba_ep_writel(ep0, CTL_ENB, + USBA_EPT_ENABLE | USBA_RX_SETUP); + usba_writel(udc, INT_ENB, + (usba_readl(udc, INT_ENB) + | USBA_BF(EPT_INT, 1) + | USBA_DET_SUSPEND + | USBA_END_OF_RESUME)); + + /* + * Unclear why we hit this irregularly, e.g. in usbtest, + * but it's clearly harmless... + */ + if (!(usba_ep_readl(ep0, CFG) & USBA_EPT_MAPPED)) + dev_dbg(&udc->pdev->dev, + "ODD: EP0 configuration is invalid!\n"); + } + + spin_unlock(&udc->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t usba_vbus_irq(int irq, void *devid) +{ + struct usba_udc *udc = devid; + int vbus; + + /* debounce */ + udelay(10); + + spin_lock(&udc->lock); + + /* May happen if Vbus pin toggles during probe() */ + if (!udc->driver) + goto out; + + vbus = vbus_is_present(udc); + if (vbus != udc->vbus_prev) { + if (vbus) { + toggle_bias(1); + usba_writel(udc, CTRL, USBA_ENABLE_MASK); + usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + } else { + udc->gadget.speed = USB_SPEED_UNKNOWN; + reset_all_endpoints(udc); + toggle_bias(0); + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + if (udc->driver->disconnect) { + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + } + udc->vbus_prev = vbus; + } + +out: + spin_unlock(&udc->lock); + + return IRQ_HANDLED; +} + +static int atmel_usba_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + udc->devstatus = 1 << USB_DEVICE_SELF_POWERED; + udc->driver = driver; + spin_unlock_irqrestore(&udc->lock, flags); + + clk_enable(udc->pclk); + clk_enable(udc->hclk); + + DBG(DBG_GADGET, "registered driver `%s'\n", driver->driver.name); + + udc->vbus_prev = 0; + if (gpio_is_valid(udc->vbus_pin)) + enable_irq(gpio_to_irq(udc->vbus_pin)); + + /* If Vbus is present, enable the controller and wait for reset */ + spin_lock_irqsave(&udc->lock, flags); + if (vbus_is_present(udc) && udc->vbus_prev == 0) { + toggle_bias(1); + usba_writel(udc, CTRL, USBA_ENABLE_MASK); + usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + } + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int atmel_usba_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget); + unsigned long flags; + + if (gpio_is_valid(udc->vbus_pin)) + disable_irq(gpio_to_irq(udc->vbus_pin)); + + spin_lock_irqsave(&udc->lock, flags); + udc->gadget.speed = USB_SPEED_UNKNOWN; + reset_all_endpoints(udc); + spin_unlock_irqrestore(&udc->lock, flags); + + /* This will also disable the DP pullup */ + toggle_bias(0); + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + + udc->driver = NULL; + + clk_disable(udc->hclk); + clk_disable(udc->pclk); + + DBG(DBG_GADGET, "unregistered driver `%s'\n", driver->driver.name); + + return 0; +} + +static int __init usba_udc_probe(struct platform_device *pdev) +{ + struct usba_platform_data *pdata = pdev->dev.platform_data; + struct resource *regs, *fifo; + struct clk *pclk, *hclk; + struct usba_udc *udc = &the_udc; + int irq, ret, i; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, CTRL_IOMEM_ID); + fifo = platform_get_resource(pdev, IORESOURCE_MEM, FIFO_IOMEM_ID); + if (!regs || !fifo || !pdata) + return -ENXIO; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + pclk = clk_get(&pdev->dev, "pclk"); + if (IS_ERR(pclk)) + return PTR_ERR(pclk); + hclk = clk_get(&pdev->dev, "hclk"); + if (IS_ERR(hclk)) { + ret = PTR_ERR(hclk); + goto err_get_hclk; + } + + spin_lock_init(&udc->lock); + udc->pdev = pdev; + udc->pclk = pclk; + udc->hclk = hclk; + udc->vbus_pin = -ENODEV; + + ret = -ENOMEM; + udc->regs = ioremap(regs->start, resource_size(regs)); + if (!udc->regs) { + dev_err(&pdev->dev, "Unable to map I/O memory, aborting.\n"); + goto err_map_regs; + } + dev_info(&pdev->dev, "MMIO registers at 0x%08lx mapped at %p\n", + (unsigned long)regs->start, udc->regs); + udc->fifo = ioremap(fifo->start, resource_size(fifo)); + if (!udc->fifo) { + dev_err(&pdev->dev, "Unable to map FIFO, aborting.\n"); + goto err_map_fifo; + } + dev_info(&pdev->dev, "FIFO at 0x%08lx mapped at %p\n", + (unsigned long)fifo->start, udc->fifo); + + platform_set_drvdata(pdev, udc); + + /* Make sure we start from a clean slate */ + clk_enable(pclk); + toggle_bias(0); + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + clk_disable(pclk); + + usba_ep = kzalloc(sizeof(struct usba_ep) * pdata->num_ep, + GFP_KERNEL); + if (!usba_ep) + goto err_alloc_ep; + + the_udc.gadget.ep0 = &usba_ep[0].ep; + + INIT_LIST_HEAD(&usba_ep[0].ep.ep_list); + usba_ep[0].ep_regs = udc->regs + USBA_EPT_BASE(0); + usba_ep[0].dma_regs = udc->regs + USBA_DMA_BASE(0); + usba_ep[0].fifo = udc->fifo + USBA_FIFO_BASE(0); + usba_ep[0].ep.ops = &usba_ep_ops; + usba_ep[0].ep.name = pdata->ep[0].name; + usba_ep[0].ep.maxpacket = pdata->ep[0].fifo_size; + usba_ep[0].udc = &the_udc; + INIT_LIST_HEAD(&usba_ep[0].queue); + usba_ep[0].fifo_size = pdata->ep[0].fifo_size; + usba_ep[0].nr_banks = pdata->ep[0].nr_banks; + usba_ep[0].index = pdata->ep[0].index; + usba_ep[0].can_dma = pdata->ep[0].can_dma; + usba_ep[0].can_isoc = pdata->ep[0].can_isoc; + + for (i = 1; i < pdata->num_ep; i++) { + struct usba_ep *ep = &usba_ep[i]; + + ep->ep_regs = udc->regs + USBA_EPT_BASE(i); + ep->dma_regs = udc->regs + USBA_DMA_BASE(i); + ep->fifo = udc->fifo + USBA_FIFO_BASE(i); + ep->ep.ops = &usba_ep_ops; + ep->ep.name = pdata->ep[i].name; + ep->ep.maxpacket = pdata->ep[i].fifo_size; + ep->udc = &the_udc; + INIT_LIST_HEAD(&ep->queue); + ep->fifo_size = pdata->ep[i].fifo_size; + ep->nr_banks = pdata->ep[i].nr_banks; + ep->index = pdata->ep[i].index; + ep->can_dma = pdata->ep[i].can_dma; + ep->can_isoc = pdata->ep[i].can_isoc; + + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + + ret = request_irq(irq, usba_udc_irq, 0, "atmel_usba_udc", udc); + if (ret) { + dev_err(&pdev->dev, "Cannot request irq %d (error %d)\n", + irq, ret); + goto err_request_irq; + } + udc->irq = irq; + + if (gpio_is_valid(pdata->vbus_pin)) { + if (!gpio_request(pdata->vbus_pin, "atmel_usba_udc")) { + udc->vbus_pin = pdata->vbus_pin; + udc->vbus_pin_inverted = pdata->vbus_pin_inverted; + + ret = request_irq(gpio_to_irq(udc->vbus_pin), + usba_vbus_irq, 0, + "atmel_usba_udc", udc); + if (ret) { + gpio_free(udc->vbus_pin); + udc->vbus_pin = -ENODEV; + dev_warn(&udc->pdev->dev, + "failed to request vbus irq; " + "assuming always on\n"); + } else { + disable_irq(gpio_to_irq(udc->vbus_pin)); + } + } else { + /* gpio_request fail so use -EINVAL for gpio_is_valid */ + udc->vbus_pin = -EINVAL; + } + } + + ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (ret) + goto err_add_udc; + + usba_init_debugfs(udc); + for (i = 1; i < pdata->num_ep; i++) + usba_ep_init_debugfs(udc, &usba_ep[i]); + + return 0; + +err_add_udc: + if (gpio_is_valid(pdata->vbus_pin)) { + free_irq(gpio_to_irq(udc->vbus_pin), udc); + gpio_free(udc->vbus_pin); + } + + free_irq(irq, udc); +err_request_irq: + kfree(usba_ep); +err_alloc_ep: + iounmap(udc->fifo); +err_map_fifo: + iounmap(udc->regs); +err_map_regs: + clk_put(hclk); +err_get_hclk: + clk_put(pclk); + + return ret; +} + +static int __exit usba_udc_remove(struct platform_device *pdev) +{ + struct usba_udc *udc; + int i; + struct usba_platform_data *pdata = pdev->dev.platform_data; + + udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + + for (i = 1; i < pdata->num_ep; i++) + usba_ep_cleanup_debugfs(&usba_ep[i]); + usba_cleanup_debugfs(udc); + + if (gpio_is_valid(udc->vbus_pin)) { + free_irq(gpio_to_irq(udc->vbus_pin), udc); + gpio_free(udc->vbus_pin); + } + + free_irq(udc->irq, udc); + kfree(usba_ep); + iounmap(udc->fifo); + iounmap(udc->regs); + clk_put(udc->hclk); + clk_put(udc->pclk); + + return 0; +} + +static struct platform_driver udc_driver = { + .remove = __exit_p(usba_udc_remove), + .driver = { + .name = "atmel_usba_udc", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver_probe(udc_driver, usba_udc_probe); + +MODULE_DESCRIPTION("Atmel USBA UDC driver"); +MODULE_AUTHOR("Haavard Skinnemoen (Atmel)"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:atmel_usba_udc"); diff --git a/drivers/usb/gadget/atmel_usba_udc.h b/drivers/usb/gadget/atmel_usba_udc.h new file mode 100644 index 00000000..d65a6185 --- /dev/null +++ b/drivers/usb/gadget/atmel_usba_udc.h @@ -0,0 +1,352 @@ +/* + * Driver for the Atmel USBA high speed USB device controller + * + * Copyright (C) 2005-2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __LINUX_USB_GADGET_USBA_UDC_H__ +#define __LINUX_USB_GADGET_USBA_UDC_H__ + +/* USB register offsets */ +#define USBA_CTRL 0x0000 +#define USBA_FNUM 0x0004 +#define USBA_INT_ENB 0x0010 +#define USBA_INT_STA 0x0014 +#define USBA_INT_CLR 0x0018 +#define USBA_EPT_RST 0x001c +#define USBA_TST 0x00e0 + +/* USB endpoint register offsets */ +#define USBA_EPT_CFG 0x0000 +#define USBA_EPT_CTL_ENB 0x0004 +#define USBA_EPT_CTL_DIS 0x0008 +#define USBA_EPT_CTL 0x000c +#define USBA_EPT_SET_STA 0x0014 +#define USBA_EPT_CLR_STA 0x0018 +#define USBA_EPT_STA 0x001c + +/* USB DMA register offsets */ +#define USBA_DMA_NXT_DSC 0x0000 +#define USBA_DMA_ADDRESS 0x0004 +#define USBA_DMA_CONTROL 0x0008 +#define USBA_DMA_STATUS 0x000c + +/* Bitfields in CTRL */ +#define USBA_DEV_ADDR_OFFSET 0 +#define USBA_DEV_ADDR_SIZE 7 +#define USBA_FADDR_EN (1 << 7) +#define USBA_EN_USBA (1 << 8) +#define USBA_DETACH (1 << 9) +#define USBA_REMOTE_WAKE_UP (1 << 10) +#define USBA_PULLD_DIS (1 << 11) + +#if defined(CONFIG_AVR32) +#define USBA_ENABLE_MASK USBA_EN_USBA +#define USBA_DISABLE_MASK 0 +#elif defined(CONFIG_ARCH_AT91) +#define USBA_ENABLE_MASK (USBA_EN_USBA | USBA_PULLD_DIS) +#define USBA_DISABLE_MASK USBA_DETACH +#endif /* CONFIG_ARCH_AT91 */ + +/* Bitfields in FNUM */ +#define USBA_MICRO_FRAME_NUM_OFFSET 0 +#define USBA_MICRO_FRAME_NUM_SIZE 3 +#define USBA_FRAME_NUMBER_OFFSET 3 +#define USBA_FRAME_NUMBER_SIZE 11 +#define USBA_FRAME_NUM_ERROR (1 << 31) + +/* Bitfields in INT_ENB/INT_STA/INT_CLR */ +#define USBA_HIGH_SPEED (1 << 0) +#define USBA_DET_SUSPEND (1 << 1) +#define USBA_MICRO_SOF (1 << 2) +#define USBA_SOF (1 << 3) +#define USBA_END_OF_RESET (1 << 4) +#define USBA_WAKE_UP (1 << 5) +#define USBA_END_OF_RESUME (1 << 6) +#define USBA_UPSTREAM_RESUME (1 << 7) +#define USBA_EPT_INT_OFFSET 8 +#define USBA_EPT_INT_SIZE 16 +#define USBA_DMA_INT_OFFSET 24 +#define USBA_DMA_INT_SIZE 8 + +/* Bitfields in EPT_RST */ +#define USBA_RST_OFFSET 0 +#define USBA_RST_SIZE 16 + +/* Bitfields in USBA_TST */ +#define USBA_SPEED_CFG_OFFSET 0 +#define USBA_SPEED_CFG_SIZE 2 +#define USBA_TST_J_MODE (1 << 2) +#define USBA_TST_K_MODE (1 << 3) +#define USBA_TST_PKT_MODE (1 << 4) +#define USBA_OPMODE2 (1 << 5) + +/* Bitfields in EPT_CFG */ +#define USBA_EPT_SIZE_OFFSET 0 +#define USBA_EPT_SIZE_SIZE 3 +#define USBA_EPT_DIR_IN (1 << 3) +#define USBA_EPT_TYPE_OFFSET 4 +#define USBA_EPT_TYPE_SIZE 2 +#define USBA_BK_NUMBER_OFFSET 6 +#define USBA_BK_NUMBER_SIZE 2 +#define USBA_NB_TRANS_OFFSET 8 +#define USBA_NB_TRANS_SIZE 2 +#define USBA_EPT_MAPPED (1 << 31) + +/* Bitfields in EPT_CTL/EPT_CTL_ENB/EPT_CTL_DIS */ +#define USBA_EPT_ENABLE (1 << 0) +#define USBA_AUTO_VALID (1 << 1) +#define USBA_INTDIS_DMA (1 << 3) +#define USBA_NYET_DIS (1 << 4) +#define USBA_DATAX_RX (1 << 6) +#define USBA_MDATA_RX (1 << 7) +/* Bits 8-15 and 31 enable interrupts for respective bits in EPT_STA */ +#define USBA_BUSY_BANK_IE (1 << 18) + +/* Bitfields in EPT_SET_STA/EPT_CLR_STA/EPT_STA */ +#define USBA_FORCE_STALL (1 << 5) +#define USBA_TOGGLE_CLR (1 << 6) +#define USBA_TOGGLE_SEQ_OFFSET 6 +#define USBA_TOGGLE_SEQ_SIZE 2 +#define USBA_ERR_OVFLW (1 << 8) +#define USBA_RX_BK_RDY (1 << 9) +#define USBA_KILL_BANK (1 << 9) +#define USBA_TX_COMPLETE (1 << 10) +#define USBA_TX_PK_RDY (1 << 11) +#define USBA_ISO_ERR_TRANS (1 << 11) +#define USBA_RX_SETUP (1 << 12) +#define USBA_ISO_ERR_FLOW (1 << 12) +#define USBA_STALL_SENT (1 << 13) +#define USBA_ISO_ERR_CRC (1 << 13) +#define USBA_ISO_ERR_NBTRANS (1 << 13) +#define USBA_NAK_IN (1 << 14) +#define USBA_ISO_ERR_FLUSH (1 << 14) +#define USBA_NAK_OUT (1 << 15) +#define USBA_CURRENT_BANK_OFFSET 16 +#define USBA_CURRENT_BANK_SIZE 2 +#define USBA_BUSY_BANKS_OFFSET 18 +#define USBA_BUSY_BANKS_SIZE 2 +#define USBA_BYTE_COUNT_OFFSET 20 +#define USBA_BYTE_COUNT_SIZE 11 +#define USBA_SHORT_PACKET (1 << 31) + +/* Bitfields in DMA_CONTROL */ +#define USBA_DMA_CH_EN (1 << 0) +#define USBA_DMA_LINK (1 << 1) +#define USBA_DMA_END_TR_EN (1 << 2) +#define USBA_DMA_END_BUF_EN (1 << 3) +#define USBA_DMA_END_TR_IE (1 << 4) +#define USBA_DMA_END_BUF_IE (1 << 5) +#define USBA_DMA_DESC_LOAD_IE (1 << 6) +#define USBA_DMA_BURST_LOCK (1 << 7) +#define USBA_DMA_BUF_LEN_OFFSET 16 +#define USBA_DMA_BUF_LEN_SIZE 16 + +/* Bitfields in DMA_STATUS */ +#define USBA_DMA_CH_ACTIVE (1 << 1) +#define USBA_DMA_END_TR_ST (1 << 4) +#define USBA_DMA_END_BUF_ST (1 << 5) +#define USBA_DMA_DESC_LOAD_ST (1 << 6) + +/* Constants for SPEED_CFG */ +#define USBA_SPEED_CFG_NORMAL 0 +#define USBA_SPEED_CFG_FORCE_HIGH 2 +#define USBA_SPEED_CFG_FORCE_FULL 3 + +/* Constants for EPT_SIZE */ +#define USBA_EPT_SIZE_8 0 +#define USBA_EPT_SIZE_16 1 +#define USBA_EPT_SIZE_32 2 +#define USBA_EPT_SIZE_64 3 +#define USBA_EPT_SIZE_128 4 +#define USBA_EPT_SIZE_256 5 +#define USBA_EPT_SIZE_512 6 +#define USBA_EPT_SIZE_1024 7 + +/* Constants for EPT_TYPE */ +#define USBA_EPT_TYPE_CONTROL 0 +#define USBA_EPT_TYPE_ISO 1 +#define USBA_EPT_TYPE_BULK 2 +#define USBA_EPT_TYPE_INT 3 + +/* Constants for BK_NUMBER */ +#define USBA_BK_NUMBER_ZERO 0 +#define USBA_BK_NUMBER_ONE 1 +#define USBA_BK_NUMBER_DOUBLE 2 +#define USBA_BK_NUMBER_TRIPLE 3 + +/* Bit manipulation macros */ +#define USBA_BF(name, value) \ + (((value) & ((1 << USBA_##name##_SIZE) - 1)) \ + << USBA_##name##_OFFSET) +#define USBA_BFEXT(name, value) \ + (((value) >> USBA_##name##_OFFSET) \ + & ((1 << USBA_##name##_SIZE) - 1)) +#define USBA_BFINS(name, value, old) \ + (((old) & ~(((1 << USBA_##name##_SIZE) - 1) \ + << USBA_##name##_OFFSET)) \ + | USBA_BF(name, value)) + +/* Register access macros */ +#define usba_readl(udc, reg) \ + __raw_readl((udc)->regs + USBA_##reg) +#define usba_writel(udc, reg, value) \ + __raw_writel((value), (udc)->regs + USBA_##reg) +#define usba_ep_readl(ep, reg) \ + __raw_readl((ep)->ep_regs + USBA_EPT_##reg) +#define usba_ep_writel(ep, reg, value) \ + __raw_writel((value), (ep)->ep_regs + USBA_EPT_##reg) +#define usba_dma_readl(ep, reg) \ + __raw_readl((ep)->dma_regs + USBA_DMA_##reg) +#define usba_dma_writel(ep, reg, value) \ + __raw_writel((value), (ep)->dma_regs + USBA_DMA_##reg) + +/* Calculate base address for a given endpoint or DMA controller */ +#define USBA_EPT_BASE(x) (0x100 + (x) * 0x20) +#define USBA_DMA_BASE(x) (0x300 + (x) * 0x10) +#define USBA_FIFO_BASE(x) ((x) << 16) + +/* Synth parameters */ +#define USBA_NR_ENDPOINTS 7 + +#define EP0_FIFO_SIZE 64 +#define EP0_EPT_SIZE USBA_EPT_SIZE_64 +#define EP0_NR_BANKS 1 + +#define FIFO_IOMEM_ID 0 +#define CTRL_IOMEM_ID 1 + +#define DBG_ERR 0x0001 /* report all error returns */ +#define DBG_HW 0x0002 /* debug hardware initialization */ +#define DBG_GADGET 0x0004 /* calls to/from gadget driver */ +#define DBG_INT 0x0008 /* interrupts */ +#define DBG_BUS 0x0010 /* report changes in bus state */ +#define DBG_QUEUE 0x0020 /* debug request queue processing */ +#define DBG_FIFO 0x0040 /* debug FIFO contents */ +#define DBG_DMA 0x0080 /* debug DMA handling */ +#define DBG_REQ 0x0100 /* print out queued request length */ +#define DBG_ALL 0xffff +#define DBG_NONE 0x0000 + +#define DEBUG_LEVEL (DBG_ERR) + +#define DBG(level, fmt, ...) \ + do { \ + if ((level) & DEBUG_LEVEL) \ + pr_debug("udc: " fmt, ## __VA_ARGS__); \ + } while (0) + +enum usba_ctrl_state { + WAIT_FOR_SETUP, + DATA_STAGE_IN, + DATA_STAGE_OUT, + STATUS_STAGE_IN, + STATUS_STAGE_OUT, + STATUS_STAGE_ADDR, + STATUS_STAGE_TEST, +}; +/* + EP_STATE_IDLE, + EP_STATE_SETUP, + EP_STATE_IN_DATA, + EP_STATE_OUT_DATA, + EP_STATE_SET_ADDR_STATUS, + EP_STATE_RX_STATUS, + EP_STATE_TX_STATUS, + EP_STATE_HALT, +*/ + +struct usba_dma_desc { + dma_addr_t next; + dma_addr_t addr; + u32 ctrl; +}; + +struct usba_ep { + int state; + void __iomem *ep_regs; + void __iomem *dma_regs; + void __iomem *fifo; + struct usb_ep ep; + struct usba_udc *udc; + + struct list_head queue; + + u16 fifo_size; + u8 nr_banks; + u8 index; + unsigned int can_dma:1; + unsigned int can_isoc:1; + unsigned int is_isoc:1; + unsigned int is_in:1; + +#ifdef CONFIG_USB_GADGET_DEBUG_FS + u32 last_dma_status; + struct dentry *debugfs_dir; + struct dentry *debugfs_queue; + struct dentry *debugfs_dma_status; + struct dentry *debugfs_state; +#endif +}; + +struct usba_request { + struct usb_request req; + struct list_head queue; + + u32 ctrl; + + unsigned int submitted:1; + unsigned int last_transaction:1; + unsigned int using_dma:1; + unsigned int mapped:1; +}; + +struct usba_udc { + /* Protect hw registers from concurrent modifications */ + spinlock_t lock; + + void __iomem *regs; + void __iomem *fifo; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + int irq; + int vbus_pin; + int vbus_pin_inverted; + struct clk *pclk; + struct clk *hclk; + + u16 devstatus; + + u16 test_mode; + int vbus_prev; + +#ifdef CONFIG_USB_GADGET_DEBUG_FS + struct dentry *debugfs_root; + struct dentry *debugfs_regs; +#endif +}; + +static inline struct usba_ep *to_usba_ep(struct usb_ep *ep) +{ + return container_of(ep, struct usba_ep, ep); +} + +static inline struct usba_request *to_usba_req(struct usb_request *req) +{ + return container_of(req, struct usba_request, req); +} + +static inline struct usba_udc *to_usba_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct usba_udc, gadget); +} + +#define ep_is_control(ep) ((ep)->index == 0) +#define ep_is_idle(ep) ((ep)->state == EP_STATE_IDLE) + +#endif /* __LINUX_USB_GADGET_USBA_UDC_H */ diff --git a/drivers/usb/gadget/audio.c b/drivers/usb/gadget/audio.c new file mode 100644 index 00000000..231b0efe --- /dev/null +++ b/drivers/usb/gadget/audio.c @@ -0,0 +1,190 @@ +/* + * audio.c -- Audio gadget driver + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include + +#include "gadget_chips.h" +#define DRIVER_DESC "Linux USB Audio Gadget" +#define DRIVER_VERSION "Feb 2, 2012" + +USB_GADGET_COMPOSITE_OPTIONS(); + +/* string IDs are assigned dynamically */ + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *audio_strings[] = { + &stringtab_dev, + NULL, +}; + +#ifdef CONFIG_GADGET_UAC1 +#include "u_uac1.h" +#include "u_uac1.c" +#include "f_uac1.c" +#else +#include "f_uac2.c" +#endif + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to Linux Foundation for donating this product ID. */ +#define AUDIO_VENDOR_NUM 0x1d6b /* Linux Foundation */ +#define AUDIO_PRODUCT_NUM 0x0101 /* Linux-USB Audio Gadget */ + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = __constant_cpu_to_le16(0x200), + +#ifdef CONFIG_GADGET_UAC1 + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, +#else + .bDeviceClass = USB_CLASS_MISC, + .bDeviceSubClass = 0x02, + .bDeviceProtocol = 0x01, +#endif + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id defaults change according to what configs + * we support. (As does bNumConfigurations.) These values can + * also be overridden by module parameters. + */ + .idVendor = __constant_cpu_to_le16(AUDIO_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16(AUDIO_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init audio_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + audio_bind_config(c); + + return 0; +} + +static struct usb_configuration audio_config_driver = { + .label = DRIVER_DESC, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +#ifndef CONFIG_GADGET_UAC1 + .unbind = uac2_unbind_config, +#endif +}; + +/*-------------------------------------------------------------------------*/ + +static int __init audio_bind(struct usb_composite_dev *cdev) +{ + int status; + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto fail; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + status = usb_add_config(cdev, &audio_config_driver, audio_do_config); + if (status < 0) + goto fail; + usb_composite_overwrite_options(cdev, &coverwrite); + + INFO(cdev, "%s, version: %s\n", DRIVER_DESC, DRIVER_VERSION); + return 0; + +fail: + return status; +} + +static int __exit audio_unbind(struct usb_composite_dev *cdev) +{ +#ifdef CONFIG_GADGET_UAC1 + gaudio_cleanup(); +#endif + return 0; +} + +static __refdata struct usb_composite_driver audio_driver = { + .name = "g_audio", + .dev = &device_desc, + .strings = audio_strings, + .max_speed = USB_SPEED_HIGH, + .bind = audio_bind, + .unbind = __exit_p(audio_unbind), +}; + +static int __init init(void) +{ + return usb_composite_probe(&audio_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&audio_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Bryan Wu "); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/gadget/bcm63xx_udc.c b/drivers/usb/gadget/bcm63xx_udc.c new file mode 100644 index 00000000..5396709c --- /dev/null +++ b/drivers/usb/gadget/bcm63xx_udc.c @@ -0,0 +1,2437 @@ +/* + * bcm63xx_udc.c -- BCM63xx UDC high/full speed USB device controller + * + * Copyright (C) 2012 Kevin Cernekee + * Copyright (C) 2012 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DRV_MODULE_NAME "bcm63xx_udc" + +static const char bcm63xx_ep0name[] = "ep0"; +static const char *const bcm63xx_ep_name[] = { + bcm63xx_ep0name, + "ep1in-bulk", "ep2out-bulk", "ep3in-int", "ep4out-int", +}; + +static bool use_fullspeed; +module_param(use_fullspeed, bool, S_IRUGO); +MODULE_PARM_DESC(use_fullspeed, "true for fullspeed only"); + +/* + * RX IRQ coalescing options: + * + * false (default) - one IRQ per DATAx packet. Slow but reliable. The + * driver is able to pass the "testusb" suite and recover from conditions like: + * + * 1) Device queues up a 2048-byte RX IUDMA transaction on an OUT bulk ep + * 2) Host sends 512 bytes of data + * 3) Host decides to reconfigure the device and sends SET_INTERFACE + * 4) Device shuts down the endpoint and cancels the RX transaction + * + * true - one IRQ per transfer, for transfers <= 2048B. Generates + * considerably fewer IRQs, but error recovery is less robust. Does not + * reliably pass "testusb". + * + * TX always uses coalescing, because we can cancel partially complete TX + * transfers by repeatedly flushing the FIFO. The hardware doesn't allow + * this on RX. + */ +static bool irq_coalesce; +module_param(irq_coalesce, bool, S_IRUGO); +MODULE_PARM_DESC(irq_coalesce, "take one IRQ per RX transfer"); + +#define BCM63XX_NUM_EP 5 +#define BCM63XX_NUM_IUDMA 6 +#define BCM63XX_NUM_FIFO_PAIRS 3 + +#define IUDMA_RESET_TIMEOUT_US 10000 + +#define IUDMA_EP0_RXCHAN 0 +#define IUDMA_EP0_TXCHAN 1 + +#define IUDMA_MAX_FRAGMENT 2048 +#define BCM63XX_MAX_CTRL_PKT 64 + +#define BCMEP_CTRL 0x00 +#define BCMEP_ISOC 0x01 +#define BCMEP_BULK 0x02 +#define BCMEP_INTR 0x03 + +#define BCMEP_OUT 0x00 +#define BCMEP_IN 0x01 + +#define BCM63XX_SPD_FULL 1 +#define BCM63XX_SPD_HIGH 0 + +#define IUDMA_DMAC_OFFSET 0x200 +#define IUDMA_DMAS_OFFSET 0x400 + +enum bcm63xx_ep0_state { + EP0_REQUEUE, + EP0_IDLE, + EP0_IN_DATA_PHASE_SETUP, + EP0_IN_DATA_PHASE_COMPLETE, + EP0_OUT_DATA_PHASE_SETUP, + EP0_OUT_DATA_PHASE_COMPLETE, + EP0_OUT_STATUS_PHASE, + EP0_IN_FAKE_STATUS_PHASE, + EP0_SHUTDOWN, +}; + +static const char __maybe_unused bcm63xx_ep0_state_names[][32] = { + "REQUEUE", + "IDLE", + "IN_DATA_PHASE_SETUP", + "IN_DATA_PHASE_COMPLETE", + "OUT_DATA_PHASE_SETUP", + "OUT_DATA_PHASE_COMPLETE", + "OUT_STATUS_PHASE", + "IN_FAKE_STATUS_PHASE", + "SHUTDOWN", +}; + +/** + * struct iudma_ch_cfg - Static configuration for an IUDMA channel. + * @ep_num: USB endpoint number. + * @n_bds: Number of buffer descriptors in the ring. + * @ep_type: Endpoint type (control, bulk, interrupt). + * @dir: Direction (in, out). + * @n_fifo_slots: Number of FIFO entries to allocate for this channel. + * @max_pkt_hs: Maximum packet size in high speed mode. + * @max_pkt_fs: Maximum packet size in full speed mode. + */ +struct iudma_ch_cfg { + int ep_num; + int n_bds; + int ep_type; + int dir; + int n_fifo_slots; + int max_pkt_hs; + int max_pkt_fs; +}; + +static const struct iudma_ch_cfg iudma_defaults[] = { + + /* This controller was designed to support a CDC/RNDIS application. + It may be possible to reconfigure some of the endpoints, but + the hardware limitations (FIFO sizing and number of DMA channels) + may significantly impact flexibility and/or stability. Change + these values at your own risk. + + ep_num ep_type n_fifo_slots max_pkt_fs + idx | n_bds | dir | max_pkt_hs | + | | | | | | | | */ + [0] = { -1, 4, BCMEP_CTRL, BCMEP_OUT, 32, 64, 64 }, + [1] = { 0, 4, BCMEP_CTRL, BCMEP_OUT, 32, 64, 64 }, + [2] = { 2, 16, BCMEP_BULK, BCMEP_OUT, 128, 512, 64 }, + [3] = { 1, 16, BCMEP_BULK, BCMEP_IN, 128, 512, 64 }, + [4] = { 4, 4, BCMEP_INTR, BCMEP_OUT, 32, 64, 64 }, + [5] = { 3, 4, BCMEP_INTR, BCMEP_IN, 32, 64, 64 }, +}; + +struct bcm63xx_udc; + +/** + * struct iudma_ch - Represents the current state of a single IUDMA channel. + * @ch_idx: IUDMA channel index (0 to BCM63XX_NUM_IUDMA-1). + * @ep_num: USB endpoint number. -1 for ep0 RX. + * @enabled: Whether bcm63xx_ep_enable() has been called. + * @max_pkt: "Chunk size" on the USB interface. Based on interface speed. + * @is_tx: true for TX, false for RX. + * @bep: Pointer to the associated endpoint. NULL for ep0 RX. + * @udc: Reference to the device controller. + * @read_bd: Next buffer descriptor to reap from the hardware. + * @write_bd: Next BD available for a new packet. + * @end_bd: Points to the final BD in the ring. + * @n_bds_used: Number of BD entries currently occupied. + * @bd_ring: Base pointer to the BD ring. + * @bd_ring_dma: Physical (DMA) address of bd_ring. + * @n_bds: Total number of BDs in the ring. + * + * ep0 has two IUDMA channels (IUDMA_EP0_RXCHAN and IUDMA_EP0_TXCHAN), as it is + * bidirectional. The "struct usb_ep" associated with ep0 is for TX (IN) + * only. + * + * Each bulk/intr endpoint has a single IUDMA channel and a single + * struct usb_ep. + */ +struct iudma_ch { + unsigned int ch_idx; + int ep_num; + bool enabled; + int max_pkt; + bool is_tx; + struct bcm63xx_ep *bep; + struct bcm63xx_udc *udc; + + struct bcm_enet_desc *read_bd; + struct bcm_enet_desc *write_bd; + struct bcm_enet_desc *end_bd; + int n_bds_used; + + struct bcm_enet_desc *bd_ring; + dma_addr_t bd_ring_dma; + unsigned int n_bds; +}; + +/** + * struct bcm63xx_ep - Internal (driver) state of a single endpoint. + * @ep_num: USB endpoint number. + * @iudma: Pointer to IUDMA channel state. + * @ep: USB gadget layer representation of the EP. + * @udc: Reference to the device controller. + * @queue: Linked list of outstanding requests for this EP. + * @halted: 1 if the EP is stalled; 0 otherwise. + */ +struct bcm63xx_ep { + unsigned int ep_num; + struct iudma_ch *iudma; + struct usb_ep ep; + struct bcm63xx_udc *udc; + struct list_head queue; + unsigned halted:1; +}; + +/** + * struct bcm63xx_req - Internal (driver) state of a single request. + * @queue: Links back to the EP's request list. + * @req: USB gadget layer representation of the request. + * @offset: Current byte offset into the data buffer (next byte to queue). + * @bd_bytes: Number of data bytes in outstanding BD entries. + * @iudma: IUDMA channel used for the request. + */ +struct bcm63xx_req { + struct list_head queue; /* ep's requests */ + struct usb_request req; + unsigned int offset; + unsigned int bd_bytes; + struct iudma_ch *iudma; +}; + +/** + * struct bcm63xx_udc - Driver/hardware private context. + * @lock: Spinlock to mediate access to this struct, and (most) HW regs. + * @dev: Generic Linux device structure. + * @pd: Platform data (board/port info). + * @usbd_clk: Clock descriptor for the USB device block. + * @usbh_clk: Clock descriptor for the USB host block. + * @gadget: USB slave device. + * @driver: Driver for USB slave devices. + * @usbd_regs: Base address of the USBD/USB20D block. + * @iudma_regs: Base address of the USBD's associated IUDMA block. + * @bep: Array of endpoints, including ep0. + * @iudma: Array of all IUDMA channels used by this controller. + * @cfg: USB configuration number, from SET_CONFIGURATION wValue. + * @iface: USB interface number, from SET_INTERFACE wIndex. + * @alt_iface: USB alt interface number, from SET_INTERFACE wValue. + * @ep0_ctrl_req: Request object for bcm63xx_udc-initiated ep0 transactions. + * @ep0_ctrl_buf: Data buffer for ep0_ctrl_req. + * @ep0state: Current state of the ep0 state machine. + * @ep0_wq: Workqueue struct used to wake up the ep0 state machine. + * @wedgemap: Bitmap of wedged endpoints. + * @ep0_req_reset: USB reset is pending. + * @ep0_req_set_cfg: Need to spoof a SET_CONFIGURATION packet. + * @ep0_req_set_iface: Need to spoof a SET_INTERFACE packet. + * @ep0_req_shutdown: Driver is shutting down; requesting ep0 to halt activity. + * @ep0_req_completed: ep0 request has completed; worker has not seen it yet. + * @ep0_reply: Pending reply from gadget driver. + * @ep0_request: Outstanding ep0 request. + * @debugfs_root: debugfs directory: /sys/kernel/debug/. + * @debugfs_usbd: debugfs file "usbd" for controller state. + * @debugfs_iudma: debugfs file "usbd" for IUDMA state. + */ +struct bcm63xx_udc { + spinlock_t lock; + + struct device *dev; + struct bcm63xx_usbd_platform_data *pd; + struct clk *usbd_clk; + struct clk *usbh_clk; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + void __iomem *usbd_regs; + void __iomem *iudma_regs; + + struct bcm63xx_ep bep[BCM63XX_NUM_EP]; + struct iudma_ch iudma[BCM63XX_NUM_IUDMA]; + + int cfg; + int iface; + int alt_iface; + + struct bcm63xx_req ep0_ctrl_req; + u8 *ep0_ctrl_buf; + + int ep0state; + struct work_struct ep0_wq; + + unsigned long wedgemap; + + unsigned ep0_req_reset:1; + unsigned ep0_req_set_cfg:1; + unsigned ep0_req_set_iface:1; + unsigned ep0_req_shutdown:1; + + unsigned ep0_req_completed:1; + struct usb_request *ep0_reply; + struct usb_request *ep0_request; + + struct dentry *debugfs_root; + struct dentry *debugfs_usbd; + struct dentry *debugfs_iudma; +}; + +static const struct usb_ep_ops bcm63xx_udc_ep_ops; + +/*********************************************************************** + * Convenience functions + ***********************************************************************/ + +static inline struct bcm63xx_udc *gadget_to_udc(struct usb_gadget *g) +{ + return container_of(g, struct bcm63xx_udc, gadget); +} + +static inline struct bcm63xx_ep *our_ep(struct usb_ep *ep) +{ + return container_of(ep, struct bcm63xx_ep, ep); +} + +static inline struct bcm63xx_req *our_req(struct usb_request *req) +{ + return container_of(req, struct bcm63xx_req, req); +} + +static inline u32 usbd_readl(struct bcm63xx_udc *udc, u32 off) +{ + return bcm_readl(udc->usbd_regs + off); +} + +static inline void usbd_writel(struct bcm63xx_udc *udc, u32 val, u32 off) +{ + bcm_writel(val, udc->usbd_regs + off); +} + +static inline u32 usb_dma_readl(struct bcm63xx_udc *udc, u32 off) +{ + return bcm_readl(udc->iudma_regs + off); +} + +static inline void usb_dma_writel(struct bcm63xx_udc *udc, u32 val, u32 off) +{ + bcm_writel(val, udc->iudma_regs + off); +} + +static inline u32 usb_dmac_readl(struct bcm63xx_udc *udc, u32 off, int chan) +{ + return bcm_readl(udc->iudma_regs + IUDMA_DMAC_OFFSET + off + + (ENETDMA_CHAN_WIDTH * chan)); +} + +static inline void usb_dmac_writel(struct bcm63xx_udc *udc, u32 val, u32 off, + int chan) +{ + bcm_writel(val, udc->iudma_regs + IUDMA_DMAC_OFFSET + off + + (ENETDMA_CHAN_WIDTH * chan)); +} + +static inline u32 usb_dmas_readl(struct bcm63xx_udc *udc, u32 off, int chan) +{ + return bcm_readl(udc->iudma_regs + IUDMA_DMAS_OFFSET + off + + (ENETDMA_CHAN_WIDTH * chan)); +} + +static inline void usb_dmas_writel(struct bcm63xx_udc *udc, u32 val, u32 off, + int chan) +{ + bcm_writel(val, udc->iudma_regs + IUDMA_DMAS_OFFSET + off + + (ENETDMA_CHAN_WIDTH * chan)); +} + +static inline void set_clocks(struct bcm63xx_udc *udc, bool is_enabled) +{ + if (is_enabled) { + clk_enable(udc->usbh_clk); + clk_enable(udc->usbd_clk); + udelay(10); + } else { + clk_disable(udc->usbd_clk); + clk_disable(udc->usbh_clk); + } +} + +/*********************************************************************** + * Low-level IUDMA / FIFO operations + ***********************************************************************/ + +/** + * bcm63xx_ep_dma_select - Helper function to set up the init_sel signal. + * @udc: Reference to the device controller. + * @idx: Desired init_sel value. + * + * The "init_sel" signal is used as a selection index for both endpoints + * and IUDMA channels. Since these do not map 1:1, the use of this signal + * depends on the context. + */ +static void bcm63xx_ep_dma_select(struct bcm63xx_udc *udc, int idx) +{ + u32 val = usbd_readl(udc, USBD_CONTROL_REG); + + val &= ~USBD_CONTROL_INIT_SEL_MASK; + val |= idx << USBD_CONTROL_INIT_SEL_SHIFT; + usbd_writel(udc, val, USBD_CONTROL_REG); +} + +/** + * bcm63xx_set_stall - Enable/disable stall on one endpoint. + * @udc: Reference to the device controller. + * @bep: Endpoint on which to operate. + * @is_stalled: true to enable stall, false to disable. + * + * See notes in bcm63xx_update_wedge() regarding automatic clearing of + * halt/stall conditions. + */ +static void bcm63xx_set_stall(struct bcm63xx_udc *udc, struct bcm63xx_ep *bep, + bool is_stalled) +{ + u32 val; + + val = USBD_STALL_UPDATE_MASK | + (is_stalled ? USBD_STALL_ENABLE_MASK : 0) | + (bep->ep_num << USBD_STALL_EPNUM_SHIFT); + usbd_writel(udc, val, USBD_STALL_REG); +} + +/** + * bcm63xx_fifo_setup - (Re)initialize FIFO boundaries and settings. + * @udc: Reference to the device controller. + * + * These parameters depend on the USB link speed. Settings are + * per-IUDMA-channel-pair. + */ +static void bcm63xx_fifo_setup(struct bcm63xx_udc *udc) +{ + int is_hs = udc->gadget.speed == USB_SPEED_HIGH; + u32 i, val, rx_fifo_slot, tx_fifo_slot; + + /* set up FIFO boundaries and packet sizes; this is done in pairs */ + rx_fifo_slot = tx_fifo_slot = 0; + for (i = 0; i < BCM63XX_NUM_IUDMA; i += 2) { + const struct iudma_ch_cfg *rx_cfg = &iudma_defaults[i]; + const struct iudma_ch_cfg *tx_cfg = &iudma_defaults[i + 1]; + + bcm63xx_ep_dma_select(udc, i >> 1); + + val = (rx_fifo_slot << USBD_RXFIFO_CONFIG_START_SHIFT) | + ((rx_fifo_slot + rx_cfg->n_fifo_slots - 1) << + USBD_RXFIFO_CONFIG_END_SHIFT); + rx_fifo_slot += rx_cfg->n_fifo_slots; + usbd_writel(udc, val, USBD_RXFIFO_CONFIG_REG); + usbd_writel(udc, + is_hs ? rx_cfg->max_pkt_hs : rx_cfg->max_pkt_fs, + USBD_RXFIFO_EPSIZE_REG); + + val = (tx_fifo_slot << USBD_TXFIFO_CONFIG_START_SHIFT) | + ((tx_fifo_slot + tx_cfg->n_fifo_slots - 1) << + USBD_TXFIFO_CONFIG_END_SHIFT); + tx_fifo_slot += tx_cfg->n_fifo_slots; + usbd_writel(udc, val, USBD_TXFIFO_CONFIG_REG); + usbd_writel(udc, + is_hs ? tx_cfg->max_pkt_hs : tx_cfg->max_pkt_fs, + USBD_TXFIFO_EPSIZE_REG); + + usbd_readl(udc, USBD_TXFIFO_EPSIZE_REG); + } +} + +/** + * bcm63xx_fifo_reset_ep - Flush a single endpoint's FIFO. + * @udc: Reference to the device controller. + * @ep_num: Endpoint number. + */ +static void bcm63xx_fifo_reset_ep(struct bcm63xx_udc *udc, int ep_num) +{ + u32 val; + + bcm63xx_ep_dma_select(udc, ep_num); + + val = usbd_readl(udc, USBD_CONTROL_REG); + val |= USBD_CONTROL_FIFO_RESET_MASK; + usbd_writel(udc, val, USBD_CONTROL_REG); + usbd_readl(udc, USBD_CONTROL_REG); +} + +/** + * bcm63xx_fifo_reset - Flush all hardware FIFOs. + * @udc: Reference to the device controller. + */ +static void bcm63xx_fifo_reset(struct bcm63xx_udc *udc) +{ + int i; + + for (i = 0; i < BCM63XX_NUM_FIFO_PAIRS; i++) + bcm63xx_fifo_reset_ep(udc, i); +} + +/** + * bcm63xx_ep_init - Initial (one-time) endpoint initialization. + * @udc: Reference to the device controller. + */ +static void bcm63xx_ep_init(struct bcm63xx_udc *udc) +{ + u32 i, val; + + for (i = 0; i < BCM63XX_NUM_IUDMA; i++) { + const struct iudma_ch_cfg *cfg = &iudma_defaults[i]; + + if (cfg->ep_num < 0) + continue; + + bcm63xx_ep_dma_select(udc, cfg->ep_num); + val = (cfg->ep_type << USBD_EPNUM_TYPEMAP_TYPE_SHIFT) | + ((i >> 1) << USBD_EPNUM_TYPEMAP_DMA_CH_SHIFT); + usbd_writel(udc, val, USBD_EPNUM_TYPEMAP_REG); + } +} + +/** + * bcm63xx_ep_setup - Configure per-endpoint settings. + * @udc: Reference to the device controller. + * + * This needs to be rerun if the speed/cfg/intf/altintf changes. + */ +static void bcm63xx_ep_setup(struct bcm63xx_udc *udc) +{ + u32 val, i; + + usbd_writel(udc, USBD_CSR_SETUPADDR_DEF, USBD_CSR_SETUPADDR_REG); + + for (i = 0; i < BCM63XX_NUM_IUDMA; i++) { + const struct iudma_ch_cfg *cfg = &iudma_defaults[i]; + int max_pkt = udc->gadget.speed == USB_SPEED_HIGH ? + cfg->max_pkt_hs : cfg->max_pkt_fs; + int idx = cfg->ep_num; + + udc->iudma[i].max_pkt = max_pkt; + + if (idx < 0) + continue; + udc->bep[idx].ep.maxpacket = max_pkt; + + val = (idx << USBD_CSR_EP_LOG_SHIFT) | + (cfg->dir << USBD_CSR_EP_DIR_SHIFT) | + (cfg->ep_type << USBD_CSR_EP_TYPE_SHIFT) | + (udc->cfg << USBD_CSR_EP_CFG_SHIFT) | + (udc->iface << USBD_CSR_EP_IFACE_SHIFT) | + (udc->alt_iface << USBD_CSR_EP_ALTIFACE_SHIFT) | + (max_pkt << USBD_CSR_EP_MAXPKT_SHIFT); + usbd_writel(udc, val, USBD_CSR_EP_REG(idx)); + } +} + +/** + * iudma_write - Queue a single IUDMA transaction. + * @udc: Reference to the device controller. + * @iudma: IUDMA channel to use. + * @breq: Request containing the transaction data. + * + * For RX IUDMA, this will queue a single buffer descriptor, as RX IUDMA + * does not honor SOP/EOP so the handling of multiple buffers is ambiguous. + * So iudma_write() may be called several times to fulfill a single + * usb_request. + * + * For TX IUDMA, this can queue multiple buffer descriptors if needed. + */ +static void iudma_write(struct bcm63xx_udc *udc, struct iudma_ch *iudma, + struct bcm63xx_req *breq) +{ + int first_bd = 1, last_bd = 0, extra_zero_pkt = 0; + unsigned int bytes_left = breq->req.length - breq->offset; + const int max_bd_bytes = !irq_coalesce && !iudma->is_tx ? + iudma->max_pkt : IUDMA_MAX_FRAGMENT; + + iudma->n_bds_used = 0; + breq->bd_bytes = 0; + breq->iudma = iudma; + + if ((bytes_left % iudma->max_pkt == 0) && bytes_left && breq->req.zero) + extra_zero_pkt = 1; + + do { + struct bcm_enet_desc *d = iudma->write_bd; + u32 dmaflags = 0; + unsigned int n_bytes; + + if (d == iudma->end_bd) { + dmaflags |= DMADESC_WRAP_MASK; + iudma->write_bd = iudma->bd_ring; + } else { + iudma->write_bd++; + } + iudma->n_bds_used++; + + n_bytes = min_t(int, bytes_left, max_bd_bytes); + if (n_bytes) + dmaflags |= n_bytes << DMADESC_LENGTH_SHIFT; + else + dmaflags |= (1 << DMADESC_LENGTH_SHIFT) | + DMADESC_USB_ZERO_MASK; + + dmaflags |= DMADESC_OWNER_MASK; + if (first_bd) { + dmaflags |= DMADESC_SOP_MASK; + first_bd = 0; + } + + /* + * extra_zero_pkt forces one more iteration through the loop + * after all data is queued up, to send the zero packet + */ + if (extra_zero_pkt && !bytes_left) + extra_zero_pkt = 0; + + if (!iudma->is_tx || iudma->n_bds_used == iudma->n_bds || + (n_bytes == bytes_left && !extra_zero_pkt)) { + last_bd = 1; + dmaflags |= DMADESC_EOP_MASK; + } + + d->address = breq->req.dma + breq->offset; + mb(); + d->len_stat = dmaflags; + + breq->offset += n_bytes; + breq->bd_bytes += n_bytes; + bytes_left -= n_bytes; + } while (!last_bd); + + usb_dmac_writel(udc, ENETDMAC_CHANCFG_EN_MASK, + ENETDMAC_CHANCFG_REG, iudma->ch_idx); +} + +/** + * iudma_read - Check for IUDMA buffer completion. + * @udc: Reference to the device controller. + * @iudma: IUDMA channel to use. + * + * This checks to see if ALL of the outstanding BDs on the DMA channel + * have been filled. If so, it returns the actual transfer length; + * otherwise it returns -EBUSY. + */ +static int iudma_read(struct bcm63xx_udc *udc, struct iudma_ch *iudma) +{ + int i, actual_len = 0; + struct bcm_enet_desc *d = iudma->read_bd; + + if (!iudma->n_bds_used) + return -EINVAL; + + for (i = 0; i < iudma->n_bds_used; i++) { + u32 dmaflags; + + dmaflags = d->len_stat; + + if (dmaflags & DMADESC_OWNER_MASK) + return -EBUSY; + + actual_len += (dmaflags & DMADESC_LENGTH_MASK) >> + DMADESC_LENGTH_SHIFT; + if (d == iudma->end_bd) + d = iudma->bd_ring; + else + d++; + } + + iudma->read_bd = d; + iudma->n_bds_used = 0; + return actual_len; +} + +/** + * iudma_reset_channel - Stop DMA on a single channel. + * @udc: Reference to the device controller. + * @iudma: IUDMA channel to reset. + */ +static void iudma_reset_channel(struct bcm63xx_udc *udc, struct iudma_ch *iudma) +{ + int timeout = IUDMA_RESET_TIMEOUT_US; + struct bcm_enet_desc *d; + int ch_idx = iudma->ch_idx; + + if (!iudma->is_tx) + bcm63xx_fifo_reset_ep(udc, max(0, iudma->ep_num)); + + /* stop DMA, then wait for the hardware to wrap up */ + usb_dmac_writel(udc, 0, ENETDMAC_CHANCFG_REG, ch_idx); + + while (usb_dmac_readl(udc, ENETDMAC_CHANCFG_REG, ch_idx) & + ENETDMAC_CHANCFG_EN_MASK) { + udelay(1); + + /* repeatedly flush the FIFO data until the BD completes */ + if (iudma->is_tx && iudma->ep_num >= 0) + bcm63xx_fifo_reset_ep(udc, iudma->ep_num); + + if (!timeout--) { + dev_err(udc->dev, "can't reset IUDMA channel %d\n", + ch_idx); + break; + } + if (timeout == IUDMA_RESET_TIMEOUT_US / 2) { + dev_warn(udc->dev, "forcibly halting IUDMA channel %d\n", + ch_idx); + usb_dmac_writel(udc, ENETDMAC_CHANCFG_BUFHALT_MASK, + ENETDMAC_CHANCFG_REG, ch_idx); + } + } + usb_dmac_writel(udc, ~0, ENETDMAC_IR_REG, ch_idx); + + /* don't leave "live" HW-owned entries for the next guy to step on */ + for (d = iudma->bd_ring; d <= iudma->end_bd; d++) + d->len_stat = 0; + mb(); + + iudma->read_bd = iudma->write_bd = iudma->bd_ring; + iudma->n_bds_used = 0; + + /* set up IRQs, UBUS burst size, and BD base for this channel */ + usb_dmac_writel(udc, ENETDMAC_IR_BUFDONE_MASK, + ENETDMAC_IRMASK_REG, ch_idx); + usb_dmac_writel(udc, 8, ENETDMAC_MAXBURST_REG, ch_idx); + + usb_dmas_writel(udc, iudma->bd_ring_dma, ENETDMAS_RSTART_REG, ch_idx); + usb_dmas_writel(udc, 0, ENETDMAS_SRAM2_REG, ch_idx); +} + +/** + * iudma_init_channel - One-time IUDMA channel initialization. + * @udc: Reference to the device controller. + * @ch_idx: Channel to initialize. + */ +static int iudma_init_channel(struct bcm63xx_udc *udc, unsigned int ch_idx) +{ + struct iudma_ch *iudma = &udc->iudma[ch_idx]; + const struct iudma_ch_cfg *cfg = &iudma_defaults[ch_idx]; + unsigned int n_bds = cfg->n_bds; + struct bcm63xx_ep *bep = NULL; + + iudma->ep_num = cfg->ep_num; + iudma->ch_idx = ch_idx; + iudma->is_tx = !!(ch_idx & 0x01); + if (iudma->ep_num >= 0) { + bep = &udc->bep[iudma->ep_num]; + bep->iudma = iudma; + INIT_LIST_HEAD(&bep->queue); + } + + iudma->bep = bep; + iudma->udc = udc; + + /* ep0 is always active; others are controlled by the gadget driver */ + if (iudma->ep_num <= 0) + iudma->enabled = true; + + iudma->n_bds = n_bds; + iudma->bd_ring = dmam_alloc_coherent(udc->dev, + n_bds * sizeof(struct bcm_enet_desc), + &iudma->bd_ring_dma, GFP_KERNEL); + if (!iudma->bd_ring) + return -ENOMEM; + iudma->end_bd = &iudma->bd_ring[n_bds - 1]; + + return 0; +} + +/** + * iudma_init - One-time initialization of all IUDMA channels. + * @udc: Reference to the device controller. + * + * Enable DMA, flush channels, and enable global IUDMA IRQs. + */ +static int iudma_init(struct bcm63xx_udc *udc) +{ + int i, rc; + + usb_dma_writel(udc, ENETDMA_CFG_EN_MASK, ENETDMA_CFG_REG); + + for (i = 0; i < BCM63XX_NUM_IUDMA; i++) { + rc = iudma_init_channel(udc, i); + if (rc) + return rc; + iudma_reset_channel(udc, &udc->iudma[i]); + } + + usb_dma_writel(udc, BIT(BCM63XX_NUM_IUDMA)-1, ENETDMA_GLB_IRQMASK_REG); + return 0; +} + +/** + * iudma_uninit - Uninitialize IUDMA channels. + * @udc: Reference to the device controller. + * + * Kill global IUDMA IRQs, flush channels, and kill DMA. + */ +static void iudma_uninit(struct bcm63xx_udc *udc) +{ + int i; + + usb_dma_writel(udc, 0, ENETDMA_GLB_IRQMASK_REG); + + for (i = 0; i < BCM63XX_NUM_IUDMA; i++) + iudma_reset_channel(udc, &udc->iudma[i]); + + usb_dma_writel(udc, 0, ENETDMA_CFG_REG); +} + +/*********************************************************************** + * Other low-level USBD operations + ***********************************************************************/ + +/** + * bcm63xx_set_ctrl_irqs - Mask/unmask control path interrupts. + * @udc: Reference to the device controller. + * @enable_irqs: true to enable, false to disable. + */ +static void bcm63xx_set_ctrl_irqs(struct bcm63xx_udc *udc, bool enable_irqs) +{ + u32 val; + + usbd_writel(udc, 0, USBD_STATUS_REG); + + val = BIT(USBD_EVENT_IRQ_USB_RESET) | + BIT(USBD_EVENT_IRQ_SETUP) | + BIT(USBD_EVENT_IRQ_SETCFG) | + BIT(USBD_EVENT_IRQ_SETINTF) | + BIT(USBD_EVENT_IRQ_USB_LINK); + usbd_writel(udc, enable_irqs ? val : 0, USBD_EVENT_IRQ_MASK_REG); + usbd_writel(udc, val, USBD_EVENT_IRQ_STATUS_REG); +} + +/** + * bcm63xx_select_phy_mode - Select between USB device and host mode. + * @udc: Reference to the device controller. + * @is_device: true for device, false for host. + * + * This should probably be reworked to use the drivers/usb/otg + * infrastructure. + * + * By default, the AFE/pullups are disabled in device mode, until + * bcm63xx_select_pullup() is called. + */ +static void bcm63xx_select_phy_mode(struct bcm63xx_udc *udc, bool is_device) +{ + u32 val, portmask = BIT(udc->pd->port_no); + + if (BCMCPU_IS_6328()) { + /* configure pinmux to sense VBUS signal */ + val = bcm_gpio_readl(GPIO_PINMUX_OTHR_REG); + val &= ~GPIO_PINMUX_OTHR_6328_USB_MASK; + val |= is_device ? GPIO_PINMUX_OTHR_6328_USB_DEV : + GPIO_PINMUX_OTHR_6328_USB_HOST; + bcm_gpio_writel(val, GPIO_PINMUX_OTHR_REG); + } + + val = bcm_rset_readl(RSET_USBH_PRIV, USBH_PRIV_UTMI_CTL_6368_REG); + if (is_device) { + val |= (portmask << USBH_PRIV_UTMI_CTL_HOSTB_SHIFT); + val |= (portmask << USBH_PRIV_UTMI_CTL_NODRIV_SHIFT); + } else { + val &= ~(portmask << USBH_PRIV_UTMI_CTL_HOSTB_SHIFT); + val &= ~(portmask << USBH_PRIV_UTMI_CTL_NODRIV_SHIFT); + } + bcm_rset_writel(RSET_USBH_PRIV, val, USBH_PRIV_UTMI_CTL_6368_REG); + + val = bcm_rset_readl(RSET_USBH_PRIV, USBH_PRIV_SWAP_6368_REG); + if (is_device) + val |= USBH_PRIV_SWAP_USBD_MASK; + else + val &= ~USBH_PRIV_SWAP_USBD_MASK; + bcm_rset_writel(RSET_USBH_PRIV, val, USBH_PRIV_SWAP_6368_REG); +} + +/** + * bcm63xx_select_pullup - Enable/disable the pullup on D+ + * @udc: Reference to the device controller. + * @is_on: true to enable the pullup, false to disable. + * + * If the pullup is active, the host will sense a FS/HS device connected to + * the port. If the pullup is inactive, the host will think the USB + * device has been disconnected. + */ +static void bcm63xx_select_pullup(struct bcm63xx_udc *udc, bool is_on) +{ + u32 val, portmask = BIT(udc->pd->port_no); + + val = bcm_rset_readl(RSET_USBH_PRIV, USBH_PRIV_UTMI_CTL_6368_REG); + if (is_on) + val &= ~(portmask << USBH_PRIV_UTMI_CTL_NODRIV_SHIFT); + else + val |= (portmask << USBH_PRIV_UTMI_CTL_NODRIV_SHIFT); + bcm_rset_writel(RSET_USBH_PRIV, val, USBH_PRIV_UTMI_CTL_6368_REG); +} + +/** + * bcm63xx_uninit_udc_hw - Shut down the hardware prior to driver removal. + * @udc: Reference to the device controller. + * + * This just masks the IUDMA IRQs and releases the clocks. It is assumed + * that bcm63xx_udc_stop() has already run, and the clocks are stopped. + */ +static void bcm63xx_uninit_udc_hw(struct bcm63xx_udc *udc) +{ + set_clocks(udc, true); + iudma_uninit(udc); + set_clocks(udc, false); + + clk_put(udc->usbd_clk); + clk_put(udc->usbh_clk); +} + +/** + * bcm63xx_init_udc_hw - Initialize the controller hardware and data structures. + * @udc: Reference to the device controller. + */ +static int bcm63xx_init_udc_hw(struct bcm63xx_udc *udc) +{ + int i, rc = 0; + u32 val; + + udc->ep0_ctrl_buf = devm_kzalloc(udc->dev, BCM63XX_MAX_CTRL_PKT, + GFP_KERNEL); + if (!udc->ep0_ctrl_buf) + return -ENOMEM; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + for (i = 0; i < BCM63XX_NUM_EP; i++) { + struct bcm63xx_ep *bep = &udc->bep[i]; + + bep->ep.name = bcm63xx_ep_name[i]; + bep->ep_num = i; + bep->ep.ops = &bcm63xx_udc_ep_ops; + list_add_tail(&bep->ep.ep_list, &udc->gadget.ep_list); + bep->halted = 0; + bep->ep.maxpacket = BCM63XX_MAX_CTRL_PKT; + bep->udc = udc; + bep->ep.desc = NULL; + INIT_LIST_HEAD(&bep->queue); + } + + udc->gadget.ep0 = &udc->bep[0].ep; + list_del(&udc->bep[0].ep.ep_list); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->ep0state = EP0_SHUTDOWN; + + udc->usbh_clk = clk_get(udc->dev, "usbh"); + if (IS_ERR(udc->usbh_clk)) + return -EIO; + + udc->usbd_clk = clk_get(udc->dev, "usbd"); + if (IS_ERR(udc->usbd_clk)) { + clk_put(udc->usbh_clk); + return -EIO; + } + + set_clocks(udc, true); + + val = USBD_CONTROL_AUTO_CSRS_MASK | + USBD_CONTROL_DONE_CSRS_MASK | + (irq_coalesce ? USBD_CONTROL_RXZSCFG_MASK : 0); + usbd_writel(udc, val, USBD_CONTROL_REG); + + val = USBD_STRAPS_APP_SELF_PWR_MASK | + USBD_STRAPS_APP_RAM_IF_MASK | + USBD_STRAPS_APP_CSRPRGSUP_MASK | + USBD_STRAPS_APP_8BITPHY_MASK | + USBD_STRAPS_APP_RMTWKUP_MASK; + + if (udc->gadget.max_speed == USB_SPEED_HIGH) + val |= (BCM63XX_SPD_HIGH << USBD_STRAPS_SPEED_SHIFT); + else + val |= (BCM63XX_SPD_FULL << USBD_STRAPS_SPEED_SHIFT); + usbd_writel(udc, val, USBD_STRAPS_REG); + + bcm63xx_set_ctrl_irqs(udc, false); + + usbd_writel(udc, 0, USBD_EVENT_IRQ_CFG_LO_REG); + + val = USBD_EVENT_IRQ_CFG_FALLING(USBD_EVENT_IRQ_ENUM_ON) | + USBD_EVENT_IRQ_CFG_FALLING(USBD_EVENT_IRQ_SET_CSRS); + usbd_writel(udc, val, USBD_EVENT_IRQ_CFG_HI_REG); + + rc = iudma_init(udc); + set_clocks(udc, false); + if (rc) + bcm63xx_uninit_udc_hw(udc); + + return 0; +} + +/*********************************************************************** + * Standard EP gadget operations + ***********************************************************************/ + +/** + * bcm63xx_ep_enable - Enable one endpoint. + * @ep: Endpoint to enable. + * @desc: Contains max packet, direction, etc. + * + * Most of the endpoint parameters are fixed in this controller, so there + * isn't much for this function to do. + */ +static int bcm63xx_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct bcm63xx_ep *bep = our_ep(ep); + struct bcm63xx_udc *udc = bep->udc; + struct iudma_ch *iudma = bep->iudma; + unsigned long flags; + + if (!ep || !desc || ep->name == bcm63xx_ep0name) + return -EINVAL; + + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + if (iudma->enabled) { + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + iudma->enabled = true; + BUG_ON(!list_empty(&bep->queue)); + + iudma_reset_channel(udc, iudma); + + bep->halted = 0; + bcm63xx_set_stall(udc, bep, false); + clear_bit(bep->ep_num, &udc->wedgemap); + + ep->desc = desc; + ep->maxpacket = usb_endpoint_maxp(desc); + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/** + * bcm63xx_ep_disable - Disable one endpoint. + * @ep: Endpoint to disable. + */ +static int bcm63xx_ep_disable(struct usb_ep *ep) +{ + struct bcm63xx_ep *bep = our_ep(ep); + struct bcm63xx_udc *udc = bep->udc; + struct iudma_ch *iudma = bep->iudma; + struct list_head *pos, *n; + unsigned long flags; + + if (!ep || !ep->desc) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + if (!iudma->enabled) { + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + iudma->enabled = false; + + iudma_reset_channel(udc, iudma); + + if (!list_empty(&bep->queue)) { + list_for_each_safe(pos, n, &bep->queue) { + struct bcm63xx_req *breq = + list_entry(pos, struct bcm63xx_req, queue); + + usb_gadget_unmap_request(&udc->gadget, &breq->req, + iudma->is_tx); + list_del(&breq->queue); + breq->req.status = -ESHUTDOWN; + + spin_unlock_irqrestore(&udc->lock, flags); + breq->req.complete(&iudma->bep->ep, &breq->req); + spin_lock_irqsave(&udc->lock, flags); + } + } + ep->desc = NULL; + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/** + * bcm63xx_udc_alloc_request - Allocate a new request. + * @ep: Endpoint associated with the request. + * @mem_flags: Flags to pass to kzalloc(). + */ +static struct usb_request *bcm63xx_udc_alloc_request(struct usb_ep *ep, + gfp_t mem_flags) +{ + struct bcm63xx_req *breq; + + breq = kzalloc(sizeof(*breq), mem_flags); + if (!breq) + return NULL; + return &breq->req; +} + +/** + * bcm63xx_udc_free_request - Free a request. + * @ep: Endpoint associated with the request. + * @req: Request to free. + */ +static void bcm63xx_udc_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + struct bcm63xx_req *breq = our_req(req); + kfree(breq); +} + +/** + * bcm63xx_udc_queue - Queue up a new request. + * @ep: Endpoint associated with the request. + * @req: Request to add. + * @mem_flags: Unused. + * + * If the queue is empty, start this request immediately. Otherwise, add + * it to the list. + * + * ep0 replies are sent through this function from the gadget driver, but + * they are treated differently because they need to be handled by the ep0 + * state machine. (Sometimes they are replies to control requests that + * were spoofed by this driver, and so they shouldn't be transmitted at all.) + */ +static int bcm63xx_udc_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t mem_flags) +{ + struct bcm63xx_ep *bep = our_ep(ep); + struct bcm63xx_udc *udc = bep->udc; + struct bcm63xx_req *breq = our_req(req); + unsigned long flags; + int rc = 0; + + if (unlikely(!req || !req->complete || !req->buf || !ep)) + return -EINVAL; + + req->actual = 0; + req->status = 0; + breq->offset = 0; + + if (bep == &udc->bep[0]) { + /* only one reply per request, please */ + if (udc->ep0_reply) + return -EINVAL; + + udc->ep0_reply = req; + schedule_work(&udc->ep0_wq); + return 0; + } + + spin_lock_irqsave(&udc->lock, flags); + if (!bep->iudma->enabled) { + rc = -ESHUTDOWN; + goto out; + } + + rc = usb_gadget_map_request(&udc->gadget, req, bep->iudma->is_tx); + if (rc == 0) { + list_add_tail(&breq->queue, &bep->queue); + if (list_is_singular(&bep->queue)) + iudma_write(udc, bep->iudma, breq); + } + +out: + spin_unlock_irqrestore(&udc->lock, flags); + return rc; +} + +/** + * bcm63xx_udc_dequeue - Remove a pending request from the queue. + * @ep: Endpoint associated with the request. + * @req: Request to remove. + * + * If the request is not at the head of the queue, this is easy - just nuke + * it. If the request is at the head of the queue, we'll need to stop the + * DMA transaction and then queue up the successor. + */ +static int bcm63xx_udc_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct bcm63xx_ep *bep = our_ep(ep); + struct bcm63xx_udc *udc = bep->udc; + struct bcm63xx_req *breq = our_req(req), *cur; + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&udc->lock, flags); + if (list_empty(&bep->queue)) { + rc = -EINVAL; + goto out; + } + + cur = list_first_entry(&bep->queue, struct bcm63xx_req, queue); + usb_gadget_unmap_request(&udc->gadget, &breq->req, bep->iudma->is_tx); + + if (breq == cur) { + iudma_reset_channel(udc, bep->iudma); + list_del(&breq->queue); + + if (!list_empty(&bep->queue)) { + struct bcm63xx_req *next; + + next = list_first_entry(&bep->queue, + struct bcm63xx_req, queue); + iudma_write(udc, bep->iudma, next); + } + } else { + list_del(&breq->queue); + } + +out: + spin_unlock_irqrestore(&udc->lock, flags); + + req->status = -ESHUTDOWN; + req->complete(ep, req); + + return rc; +} + +/** + * bcm63xx_udc_set_halt - Enable/disable STALL flag in the hardware. + * @ep: Endpoint to halt. + * @value: Zero to clear halt; nonzero to set halt. + * + * See comments in bcm63xx_update_wedge(). + */ +static int bcm63xx_udc_set_halt(struct usb_ep *ep, int value) +{ + struct bcm63xx_ep *bep = our_ep(ep); + struct bcm63xx_udc *udc = bep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + bcm63xx_set_stall(udc, bep, !!value); + bep->halted = value; + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/** + * bcm63xx_udc_set_wedge - Stall the endpoint until the next reset. + * @ep: Endpoint to wedge. + * + * See comments in bcm63xx_update_wedge(). + */ +static int bcm63xx_udc_set_wedge(struct usb_ep *ep) +{ + struct bcm63xx_ep *bep = our_ep(ep); + struct bcm63xx_udc *udc = bep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + set_bit(bep->ep_num, &udc->wedgemap); + bcm63xx_set_stall(udc, bep, true); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_ep_ops bcm63xx_udc_ep_ops = { + .enable = bcm63xx_ep_enable, + .disable = bcm63xx_ep_disable, + + .alloc_request = bcm63xx_udc_alloc_request, + .free_request = bcm63xx_udc_free_request, + + .queue = bcm63xx_udc_queue, + .dequeue = bcm63xx_udc_dequeue, + + .set_halt = bcm63xx_udc_set_halt, + .set_wedge = bcm63xx_udc_set_wedge, +}; + +/*********************************************************************** + * EP0 handling + ***********************************************************************/ + +/** + * bcm63xx_ep0_setup_callback - Drop spinlock to invoke ->setup callback. + * @udc: Reference to the device controller. + * @ctrl: 8-byte SETUP request. + */ +static int bcm63xx_ep0_setup_callback(struct bcm63xx_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + int rc; + + spin_unlock_irq(&udc->lock); + rc = udc->driver->setup(&udc->gadget, ctrl); + spin_lock_irq(&udc->lock); + return rc; +} + +/** + * bcm63xx_ep0_spoof_set_cfg - Synthesize a SET_CONFIGURATION request. + * @udc: Reference to the device controller. + * + * Many standard requests are handled automatically in the hardware, but + * we still need to pass them to the gadget driver so that it can + * reconfigure the interfaces/endpoints if necessary. + * + * Unfortunately we are not able to send a STALL response if the host + * requests an invalid configuration. If this happens, we'll have to be + * content with printing a warning. + */ +static int bcm63xx_ep0_spoof_set_cfg(struct bcm63xx_udc *udc) +{ + struct usb_ctrlrequest ctrl; + int rc; + + ctrl.bRequestType = USB_DIR_OUT | USB_RECIP_DEVICE; + ctrl.bRequest = USB_REQ_SET_CONFIGURATION; + ctrl.wValue = cpu_to_le16(udc->cfg); + ctrl.wIndex = 0; + ctrl.wLength = 0; + + rc = bcm63xx_ep0_setup_callback(udc, &ctrl); + if (rc < 0) { + dev_warn_ratelimited(udc->dev, + "hardware auto-acked bad SET_CONFIGURATION(%d) request\n", + udc->cfg); + } + return rc; +} + +/** + * bcm63xx_ep0_spoof_set_iface - Synthesize a SET_INTERFACE request. + * @udc: Reference to the device controller. + */ +static int bcm63xx_ep0_spoof_set_iface(struct bcm63xx_udc *udc) +{ + struct usb_ctrlrequest ctrl; + int rc; + + ctrl.bRequestType = USB_DIR_OUT | USB_RECIP_INTERFACE; + ctrl.bRequest = USB_REQ_SET_INTERFACE; + ctrl.wValue = cpu_to_le16(udc->alt_iface); + ctrl.wIndex = cpu_to_le16(udc->iface); + ctrl.wLength = 0; + + rc = bcm63xx_ep0_setup_callback(udc, &ctrl); + if (rc < 0) { + dev_warn_ratelimited(udc->dev, + "hardware auto-acked bad SET_INTERFACE(%d,%d) request\n", + udc->iface, udc->alt_iface); + } + return rc; +} + +/** + * bcm63xx_ep0_map_write - dma_map and iudma_write a single request. + * @udc: Reference to the device controller. + * @ch_idx: IUDMA channel number. + * @req: USB gadget layer representation of the request. + */ +static void bcm63xx_ep0_map_write(struct bcm63xx_udc *udc, int ch_idx, + struct usb_request *req) +{ + struct bcm63xx_req *breq = our_req(req); + struct iudma_ch *iudma = &udc->iudma[ch_idx]; + + BUG_ON(udc->ep0_request); + udc->ep0_request = req; + + req->actual = 0; + breq->offset = 0; + usb_gadget_map_request(&udc->gadget, req, iudma->is_tx); + iudma_write(udc, iudma, breq); +} + +/** + * bcm63xx_ep0_complete - Set completion status and "stage" the callback. + * @udc: Reference to the device controller. + * @req: USB gadget layer representation of the request. + * @status: Status to return to the gadget driver. + */ +static void bcm63xx_ep0_complete(struct bcm63xx_udc *udc, + struct usb_request *req, int status) +{ + req->status = status; + if (status) + req->actual = 0; + if (req->complete) { + spin_unlock_irq(&udc->lock); + req->complete(&udc->bep[0].ep, req); + spin_lock_irq(&udc->lock); + } +} + +/** + * bcm63xx_ep0_nuke_reply - Abort request from the gadget driver due to + * reset/shutdown. + * @udc: Reference to the device controller. + * @is_tx: Nonzero for TX (IN), zero for RX (OUT). + */ +static void bcm63xx_ep0_nuke_reply(struct bcm63xx_udc *udc, int is_tx) +{ + struct usb_request *req = udc->ep0_reply; + + udc->ep0_reply = NULL; + usb_gadget_unmap_request(&udc->gadget, req, is_tx); + if (udc->ep0_request == req) { + udc->ep0_req_completed = 0; + udc->ep0_request = NULL; + } + bcm63xx_ep0_complete(udc, req, -ESHUTDOWN); +} + +/** + * bcm63xx_ep0_read_complete - Close out the pending ep0 request; return + * transfer len. + * @udc: Reference to the device controller. + */ +static int bcm63xx_ep0_read_complete(struct bcm63xx_udc *udc) +{ + struct usb_request *req = udc->ep0_request; + + udc->ep0_req_completed = 0; + udc->ep0_request = NULL; + + return req->actual; +} + +/** + * bcm63xx_ep0_internal_request - Helper function to submit an ep0 request. + * @udc: Reference to the device controller. + * @ch_idx: IUDMA channel number. + * @length: Number of bytes to TX/RX. + * + * Used for simple transfers performed by the ep0 worker. This will always + * use ep0_ctrl_req / ep0_ctrl_buf. + */ +static void bcm63xx_ep0_internal_request(struct bcm63xx_udc *udc, int ch_idx, + int length) +{ + struct usb_request *req = &udc->ep0_ctrl_req.req; + + req->buf = udc->ep0_ctrl_buf; + req->length = length; + req->complete = NULL; + + bcm63xx_ep0_map_write(udc, ch_idx, req); +} + +/** + * bcm63xx_ep0_do_setup - Parse new SETUP packet and decide how to handle it. + * @udc: Reference to the device controller. + * + * EP0_IDLE probably shouldn't ever happen. EP0_REQUEUE means we're ready + * for the next packet. Anything else means the transaction requires multiple + * stages of handling. + */ +static enum bcm63xx_ep0_state bcm63xx_ep0_do_setup(struct bcm63xx_udc *udc) +{ + int rc; + struct usb_ctrlrequest *ctrl = (void *)udc->ep0_ctrl_buf; + + rc = bcm63xx_ep0_read_complete(udc); + + if (rc < 0) { + dev_err(udc->dev, "missing SETUP packet\n"); + return EP0_IDLE; + } + + /* + * Handle 0-byte IN STATUS acknowledgement. The hardware doesn't + * ALWAYS deliver these 100% of the time, so if we happen to see one, + * just throw it away. + */ + if (rc == 0) + return EP0_REQUEUE; + + /* Drop malformed SETUP packets */ + if (rc != sizeof(*ctrl)) { + dev_warn_ratelimited(udc->dev, + "malformed SETUP packet (%d bytes)\n", rc); + return EP0_REQUEUE; + } + + /* Process new SETUP packet arriving on ep0 */ + rc = bcm63xx_ep0_setup_callback(udc, ctrl); + if (rc < 0) { + bcm63xx_set_stall(udc, &udc->bep[0], true); + return EP0_REQUEUE; + } + + if (!ctrl->wLength) + return EP0_REQUEUE; + else if (ctrl->bRequestType & USB_DIR_IN) + return EP0_IN_DATA_PHASE_SETUP; + else + return EP0_OUT_DATA_PHASE_SETUP; +} + +/** + * bcm63xx_ep0_do_idle - Check for outstanding requests if ep0 is idle. + * @udc: Reference to the device controller. + * + * In state EP0_IDLE, the RX descriptor is either pending, or has been + * filled with a SETUP packet from the host. This function handles new + * SETUP packets, control IRQ events (which can generate fake SETUP packets), + * and reset/shutdown events. + * + * Returns 0 if work was done; -EAGAIN if nothing to do. + */ +static int bcm63xx_ep0_do_idle(struct bcm63xx_udc *udc) +{ + if (udc->ep0_req_reset) { + udc->ep0_req_reset = 0; + } else if (udc->ep0_req_set_cfg) { + udc->ep0_req_set_cfg = 0; + if (bcm63xx_ep0_spoof_set_cfg(udc) >= 0) + udc->ep0state = EP0_IN_FAKE_STATUS_PHASE; + } else if (udc->ep0_req_set_iface) { + udc->ep0_req_set_iface = 0; + if (bcm63xx_ep0_spoof_set_iface(udc) >= 0) + udc->ep0state = EP0_IN_FAKE_STATUS_PHASE; + } else if (udc->ep0_req_completed) { + udc->ep0state = bcm63xx_ep0_do_setup(udc); + return udc->ep0state == EP0_IDLE ? -EAGAIN : 0; + } else if (udc->ep0_req_shutdown) { + udc->ep0_req_shutdown = 0; + udc->ep0_req_completed = 0; + udc->ep0_request = NULL; + iudma_reset_channel(udc, &udc->iudma[IUDMA_EP0_RXCHAN]); + usb_gadget_unmap_request(&udc->gadget, + &udc->ep0_ctrl_req.req, 0); + + /* bcm63xx_udc_pullup() is waiting for this */ + mb(); + udc->ep0state = EP0_SHUTDOWN; + } else if (udc->ep0_reply) { + /* + * This could happen if a USB RESET shows up during an ep0 + * transaction (especially if a laggy driver like gadgetfs + * is in use). + */ + dev_warn(udc->dev, "nuking unexpected reply\n"); + bcm63xx_ep0_nuke_reply(udc, 0); + } else { + return -EAGAIN; + } + + return 0; +} + +/** + * bcm63xx_ep0_one_round - Handle the current ep0 state. + * @udc: Reference to the device controller. + * + * Returns 0 if work was done; -EAGAIN if nothing to do. + */ +static int bcm63xx_ep0_one_round(struct bcm63xx_udc *udc) +{ + enum bcm63xx_ep0_state ep0state = udc->ep0state; + bool shutdown = udc->ep0_req_reset || udc->ep0_req_shutdown; + + switch (udc->ep0state) { + case EP0_REQUEUE: + /* set up descriptor to receive SETUP packet */ + bcm63xx_ep0_internal_request(udc, IUDMA_EP0_RXCHAN, + BCM63XX_MAX_CTRL_PKT); + ep0state = EP0_IDLE; + break; + case EP0_IDLE: + return bcm63xx_ep0_do_idle(udc); + case EP0_IN_DATA_PHASE_SETUP: + /* + * Normal case: TX request is in ep0_reply (queued by the + * callback), or will be queued shortly. When it's here, + * send it to the HW and go to EP0_IN_DATA_PHASE_COMPLETE. + * + * Shutdown case: Stop waiting for the reply. Just + * REQUEUE->IDLE. The gadget driver is NOT expected to + * queue anything else now. + */ + if (udc->ep0_reply) { + bcm63xx_ep0_map_write(udc, IUDMA_EP0_TXCHAN, + udc->ep0_reply); + ep0state = EP0_IN_DATA_PHASE_COMPLETE; + } else if (shutdown) { + ep0state = EP0_REQUEUE; + } + break; + case EP0_IN_DATA_PHASE_COMPLETE: { + /* + * Normal case: TX packet (ep0_reply) is in flight; wait for + * it to finish, then go back to REQUEUE->IDLE. + * + * Shutdown case: Reset the TX channel, send -ESHUTDOWN + * completion to the gadget driver, then REQUEUE->IDLE. + */ + if (udc->ep0_req_completed) { + udc->ep0_reply = NULL; + bcm63xx_ep0_read_complete(udc); + /* + * the "ack" sometimes gets eaten (see + * bcm63xx_ep0_do_idle) + */ + ep0state = EP0_REQUEUE; + } else if (shutdown) { + iudma_reset_channel(udc, &udc->iudma[IUDMA_EP0_TXCHAN]); + bcm63xx_ep0_nuke_reply(udc, 1); + ep0state = EP0_REQUEUE; + } + break; + } + case EP0_OUT_DATA_PHASE_SETUP: + /* Similar behavior to EP0_IN_DATA_PHASE_SETUP */ + if (udc->ep0_reply) { + bcm63xx_ep0_map_write(udc, IUDMA_EP0_RXCHAN, + udc->ep0_reply); + ep0state = EP0_OUT_DATA_PHASE_COMPLETE; + } else if (shutdown) { + ep0state = EP0_REQUEUE; + } + break; + case EP0_OUT_DATA_PHASE_COMPLETE: { + /* Similar behavior to EP0_IN_DATA_PHASE_COMPLETE */ + if (udc->ep0_req_completed) { + udc->ep0_reply = NULL; + bcm63xx_ep0_read_complete(udc); + + /* send 0-byte ack to host */ + bcm63xx_ep0_internal_request(udc, IUDMA_EP0_TXCHAN, 0); + ep0state = EP0_OUT_STATUS_PHASE; + } else if (shutdown) { + iudma_reset_channel(udc, &udc->iudma[IUDMA_EP0_RXCHAN]); + bcm63xx_ep0_nuke_reply(udc, 0); + ep0state = EP0_REQUEUE; + } + break; + } + case EP0_OUT_STATUS_PHASE: + /* + * Normal case: 0-byte OUT ack packet is in flight; wait + * for it to finish, then go back to REQUEUE->IDLE. + * + * Shutdown case: just cancel the transmission. Don't bother + * calling the completion, because it originated from this + * function anyway. Then go back to REQUEUE->IDLE. + */ + if (udc->ep0_req_completed) { + bcm63xx_ep0_read_complete(udc); + ep0state = EP0_REQUEUE; + } else if (shutdown) { + iudma_reset_channel(udc, &udc->iudma[IUDMA_EP0_TXCHAN]); + udc->ep0_request = NULL; + ep0state = EP0_REQUEUE; + } + break; + case EP0_IN_FAKE_STATUS_PHASE: { + /* + * Normal case: we spoofed a SETUP packet and are now + * waiting for the gadget driver to send a 0-byte reply. + * This doesn't actually get sent to the HW because the + * HW has already sent its own reply. Once we get the + * response, return to IDLE. + * + * Shutdown case: return to IDLE immediately. + * + * Note that the ep0 RX descriptor has remained queued + * (and possibly unfilled) during this entire transaction. + * The HW datapath (IUDMA) never even sees SET_CONFIGURATION + * or SET_INTERFACE transactions. + */ + struct usb_request *r = udc->ep0_reply; + + if (!r) { + if (shutdown) + ep0state = EP0_IDLE; + break; + } + + bcm63xx_ep0_complete(udc, r, 0); + udc->ep0_reply = NULL; + ep0state = EP0_IDLE; + break; + } + case EP0_SHUTDOWN: + break; + } + + if (udc->ep0state == ep0state) + return -EAGAIN; + + udc->ep0state = ep0state; + return 0; +} + +/** + * bcm63xx_ep0_process - ep0 worker thread / state machine. + * @w: Workqueue struct. + * + * bcm63xx_ep0_process is triggered any time an event occurs on ep0. It + * is used to synchronize ep0 events and ensure that both HW and SW events + * occur in a well-defined order. When the ep0 IUDMA queues are idle, it may + * synthesize SET_CONFIGURATION / SET_INTERFACE requests that were consumed + * by the USBD hardware. + * + * The worker function will continue iterating around the state machine + * until there is nothing left to do. Usually "nothing left to do" means + * that we're waiting for a new event from the hardware. + */ +static void bcm63xx_ep0_process(struct work_struct *w) +{ + struct bcm63xx_udc *udc = container_of(w, struct bcm63xx_udc, ep0_wq); + spin_lock_irq(&udc->lock); + while (bcm63xx_ep0_one_round(udc) == 0) + ; + spin_unlock_irq(&udc->lock); +} + +/*********************************************************************** + * Standard UDC gadget operations + ***********************************************************************/ + +/** + * bcm63xx_udc_get_frame - Read current SOF frame number from the HW. + * @gadget: USB slave device. + */ +static int bcm63xx_udc_get_frame(struct usb_gadget *gadget) +{ + struct bcm63xx_udc *udc = gadget_to_udc(gadget); + + return (usbd_readl(udc, USBD_STATUS_REG) & + USBD_STATUS_SOF_MASK) >> USBD_STATUS_SOF_SHIFT; +} + +/** + * bcm63xx_udc_pullup - Enable/disable pullup on D+ line. + * @gadget: USB slave device. + * @is_on: 0 to disable pullup, 1 to enable. + * + * See notes in bcm63xx_select_pullup(). + */ +static int bcm63xx_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct bcm63xx_udc *udc = gadget_to_udc(gadget); + unsigned long flags; + int i, rc = -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + if (is_on && udc->ep0state == EP0_SHUTDOWN) { + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->ep0state = EP0_REQUEUE; + bcm63xx_fifo_setup(udc); + bcm63xx_fifo_reset(udc); + bcm63xx_ep_setup(udc); + + bitmap_zero(&udc->wedgemap, BCM63XX_NUM_EP); + for (i = 0; i < BCM63XX_NUM_EP; i++) + bcm63xx_set_stall(udc, &udc->bep[i], false); + + bcm63xx_set_ctrl_irqs(udc, true); + bcm63xx_select_pullup(gadget_to_udc(gadget), true); + rc = 0; + } else if (!is_on && udc->ep0state != EP0_SHUTDOWN) { + bcm63xx_select_pullup(gadget_to_udc(gadget), false); + + udc->ep0_req_shutdown = 1; + spin_unlock_irqrestore(&udc->lock, flags); + + while (1) { + schedule_work(&udc->ep0_wq); + if (udc->ep0state == EP0_SHUTDOWN) + break; + msleep(50); + } + bcm63xx_set_ctrl_irqs(udc, false); + cancel_work_sync(&udc->ep0_wq); + return 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + return rc; +} + +/** + * bcm63xx_udc_start - Start the controller. + * @gadget: USB slave device. + * @driver: Driver for USB slave devices. + */ +static int bcm63xx_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct bcm63xx_udc *udc = gadget_to_udc(gadget); + unsigned long flags; + + if (!driver || driver->max_speed < USB_SPEED_HIGH || + !driver->setup) + return -EINVAL; + if (!udc) + return -ENODEV; + if (udc->driver) + return -EBUSY; + + spin_lock_irqsave(&udc->lock, flags); + + set_clocks(udc, true); + bcm63xx_fifo_setup(udc); + bcm63xx_ep_init(udc); + bcm63xx_ep_setup(udc); + bcm63xx_fifo_reset(udc); + bcm63xx_select_phy_mode(udc, true); + + udc->driver = driver; + driver->driver.bus = NULL; + udc->gadget.dev.of_node = udc->dev->of_node; + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/** + * bcm63xx_udc_stop - Shut down the controller. + * @gadget: USB slave device. + * @driver: Driver for USB slave devices. + */ +static int bcm63xx_udc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct bcm63xx_udc *udc = gadget_to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + udc->driver = NULL; + + /* + * If we switch the PHY too abruptly after dropping D+, the host + * will often complain: + * + * hub 1-0:1.0: port 1 disabled by hub (EMI?), re-enabling... + */ + msleep(100); + + bcm63xx_select_phy_mode(udc, false); + set_clocks(udc, false); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops bcm63xx_udc_ops = { + .get_frame = bcm63xx_udc_get_frame, + .pullup = bcm63xx_udc_pullup, + .udc_start = bcm63xx_udc_start, + .udc_stop = bcm63xx_udc_stop, +}; + +/*********************************************************************** + * IRQ handling + ***********************************************************************/ + +/** + * bcm63xx_update_cfg_iface - Read current configuration/interface settings. + * @udc: Reference to the device controller. + * + * This controller intercepts SET_CONFIGURATION and SET_INTERFACE messages. + * The driver never sees the raw control packets coming in on the ep0 + * IUDMA channel, but at least we get an interrupt event to tell us that + * new values are waiting in the USBD_STATUS register. + */ +static void bcm63xx_update_cfg_iface(struct bcm63xx_udc *udc) +{ + u32 reg = usbd_readl(udc, USBD_STATUS_REG); + + udc->cfg = (reg & USBD_STATUS_CFG_MASK) >> USBD_STATUS_CFG_SHIFT; + udc->iface = (reg & USBD_STATUS_INTF_MASK) >> USBD_STATUS_INTF_SHIFT; + udc->alt_iface = (reg & USBD_STATUS_ALTINTF_MASK) >> + USBD_STATUS_ALTINTF_SHIFT; + bcm63xx_ep_setup(udc); +} + +/** + * bcm63xx_update_link_speed - Check to see if the link speed has changed. + * @udc: Reference to the device controller. + * + * The link speed update coincides with a SETUP IRQ. Returns 1 if the + * speed has changed, so that the caller can update the endpoint settings. + */ +static int bcm63xx_update_link_speed(struct bcm63xx_udc *udc) +{ + u32 reg = usbd_readl(udc, USBD_STATUS_REG); + enum usb_device_speed oldspeed = udc->gadget.speed; + + switch ((reg & USBD_STATUS_SPD_MASK) >> USBD_STATUS_SPD_SHIFT) { + case BCM63XX_SPD_HIGH: + udc->gadget.speed = USB_SPEED_HIGH; + break; + case BCM63XX_SPD_FULL: + udc->gadget.speed = USB_SPEED_FULL; + break; + default: + /* this should never happen */ + udc->gadget.speed = USB_SPEED_UNKNOWN; + dev_err(udc->dev, + "received SETUP packet with invalid link speed\n"); + return 0; + } + + if (udc->gadget.speed != oldspeed) { + dev_info(udc->dev, "link up, %s-speed mode\n", + udc->gadget.speed == USB_SPEED_HIGH ? "high" : "full"); + return 1; + } else { + return 0; + } +} + +/** + * bcm63xx_update_wedge - Iterate through wedged endpoints. + * @udc: Reference to the device controller. + * @new_status: true to "refresh" wedge status; false to clear it. + * + * On a SETUP interrupt, we need to manually "refresh" the wedge status + * because the controller hardware is designed to automatically clear + * stalls in response to a CLEAR_FEATURE request from the host. + * + * On a RESET interrupt, we do want to restore all wedged endpoints. + */ +static void bcm63xx_update_wedge(struct bcm63xx_udc *udc, bool new_status) +{ + int i; + + for_each_set_bit(i, &udc->wedgemap, BCM63XX_NUM_EP) { + bcm63xx_set_stall(udc, &udc->bep[i], new_status); + if (!new_status) + clear_bit(i, &udc->wedgemap); + } +} + +/** + * bcm63xx_udc_ctrl_isr - ISR for control path events (USBD). + * @irq: IRQ number (unused). + * @dev_id: Reference to the device controller. + * + * This is where we handle link (VBUS) down, USB reset, speed changes, + * SET_CONFIGURATION, and SET_INTERFACE events. + */ +static irqreturn_t bcm63xx_udc_ctrl_isr(int irq, void *dev_id) +{ + struct bcm63xx_udc *udc = dev_id; + u32 stat; + bool disconnected = false; + + stat = usbd_readl(udc, USBD_EVENT_IRQ_STATUS_REG) & + usbd_readl(udc, USBD_EVENT_IRQ_MASK_REG); + + usbd_writel(udc, stat, USBD_EVENT_IRQ_STATUS_REG); + + spin_lock(&udc->lock); + if (stat & BIT(USBD_EVENT_IRQ_USB_LINK)) { + /* VBUS toggled */ + + if (!(usbd_readl(udc, USBD_EVENTS_REG) & + USBD_EVENTS_USB_LINK_MASK) && + udc->gadget.speed != USB_SPEED_UNKNOWN) + dev_info(udc->dev, "link down\n"); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + disconnected = true; + } + if (stat & BIT(USBD_EVENT_IRQ_USB_RESET)) { + bcm63xx_fifo_setup(udc); + bcm63xx_fifo_reset(udc); + bcm63xx_ep_setup(udc); + + bcm63xx_update_wedge(udc, false); + + udc->ep0_req_reset = 1; + schedule_work(&udc->ep0_wq); + disconnected = true; + } + if (stat & BIT(USBD_EVENT_IRQ_SETUP)) { + if (bcm63xx_update_link_speed(udc)) { + bcm63xx_fifo_setup(udc); + bcm63xx_ep_setup(udc); + } + bcm63xx_update_wedge(udc, true); + } + if (stat & BIT(USBD_EVENT_IRQ_SETCFG)) { + bcm63xx_update_cfg_iface(udc); + udc->ep0_req_set_cfg = 1; + schedule_work(&udc->ep0_wq); + } + if (stat & BIT(USBD_EVENT_IRQ_SETINTF)) { + bcm63xx_update_cfg_iface(udc); + udc->ep0_req_set_iface = 1; + schedule_work(&udc->ep0_wq); + } + spin_unlock(&udc->lock); + + if (disconnected && udc->driver) + udc->driver->disconnect(&udc->gadget); + + return IRQ_HANDLED; +} + +/** + * bcm63xx_udc_data_isr - ISR for data path events (IUDMA). + * @irq: IRQ number (unused). + * @dev_id: Reference to the IUDMA channel that generated the interrupt. + * + * For the two ep0 channels, we have special handling that triggers the + * ep0 worker thread. For normal bulk/intr channels, either queue up + * the next buffer descriptor for the transaction (incomplete transaction), + * or invoke the completion callback (complete transactions). + */ +static irqreturn_t bcm63xx_udc_data_isr(int irq, void *dev_id) +{ + struct iudma_ch *iudma = dev_id; + struct bcm63xx_udc *udc = iudma->udc; + struct bcm63xx_ep *bep; + struct usb_request *req = NULL; + struct bcm63xx_req *breq = NULL; + int rc; + bool is_done = false; + + spin_lock(&udc->lock); + + usb_dmac_writel(udc, ENETDMAC_IR_BUFDONE_MASK, + ENETDMAC_IR_REG, iudma->ch_idx); + bep = iudma->bep; + rc = iudma_read(udc, iudma); + + /* special handling for EP0 RX (0) and TX (1) */ + if (iudma->ch_idx == IUDMA_EP0_RXCHAN || + iudma->ch_idx == IUDMA_EP0_TXCHAN) { + req = udc->ep0_request; + breq = our_req(req); + + /* a single request could require multiple submissions */ + if (rc >= 0) { + req->actual += rc; + + if (req->actual >= req->length || breq->bd_bytes > rc) { + udc->ep0_req_completed = 1; + is_done = true; + schedule_work(&udc->ep0_wq); + + /* "actual" on a ZLP is 1 byte */ + req->actual = min(req->actual, req->length); + } else { + /* queue up the next BD (same request) */ + iudma_write(udc, iudma, breq); + } + } + } else if (!list_empty(&bep->queue)) { + breq = list_first_entry(&bep->queue, struct bcm63xx_req, queue); + req = &breq->req; + + if (rc >= 0) { + req->actual += rc; + + if (req->actual >= req->length || breq->bd_bytes > rc) { + is_done = true; + list_del(&breq->queue); + + req->actual = min(req->actual, req->length); + + if (!list_empty(&bep->queue)) { + struct bcm63xx_req *next; + + next = list_first_entry(&bep->queue, + struct bcm63xx_req, queue); + iudma_write(udc, iudma, next); + } + } else { + iudma_write(udc, iudma, breq); + } + } + } + spin_unlock(&udc->lock); + + if (is_done) { + usb_gadget_unmap_request(&udc->gadget, req, iudma->is_tx); + if (req->complete) + req->complete(&bep->ep, req); + } + + return IRQ_HANDLED; +} + +/*********************************************************************** + * Debug filesystem + ***********************************************************************/ + +/* + * bcm63xx_usbd_dbg_show - Show USBD controller state. + * @s: seq_file to which the information will be written. + * @p: Unused. + * + * This file nominally shows up as /sys/kernel/debug/bcm63xx_udc/usbd + */ +static int bcm63xx_usbd_dbg_show(struct seq_file *s, void *p) +{ + struct bcm63xx_udc *udc = s->private; + + if (!udc->driver) + return -ENODEV; + + seq_printf(s, "ep0 state: %s\n", + bcm63xx_ep0_state_names[udc->ep0state]); + seq_printf(s, " pending requests: %s%s%s%s%s%s%s\n", + udc->ep0_req_reset ? "reset " : "", + udc->ep0_req_set_cfg ? "set_cfg " : "", + udc->ep0_req_set_iface ? "set_iface " : "", + udc->ep0_req_shutdown ? "shutdown " : "", + udc->ep0_request ? "pending " : "", + udc->ep0_req_completed ? "completed " : "", + udc->ep0_reply ? "reply " : ""); + seq_printf(s, "cfg: %d; iface: %d; alt_iface: %d\n", + udc->cfg, udc->iface, udc->alt_iface); + seq_printf(s, "regs:\n"); + seq_printf(s, " control: %08x; straps: %08x; status: %08x\n", + usbd_readl(udc, USBD_CONTROL_REG), + usbd_readl(udc, USBD_STRAPS_REG), + usbd_readl(udc, USBD_STATUS_REG)); + seq_printf(s, " events: %08x; stall: %08x\n", + usbd_readl(udc, USBD_EVENTS_REG), + usbd_readl(udc, USBD_STALL_REG)); + + return 0; +} + +/* + * bcm63xx_iudma_dbg_show - Show IUDMA status and descriptors. + * @s: seq_file to which the information will be written. + * @p: Unused. + * + * This file nominally shows up as /sys/kernel/debug/bcm63xx_udc/iudma + */ +static int bcm63xx_iudma_dbg_show(struct seq_file *s, void *p) +{ + struct bcm63xx_udc *udc = s->private; + int ch_idx, i; + u32 sram2, sram3; + + if (!udc->driver) + return -ENODEV; + + for (ch_idx = 0; ch_idx < BCM63XX_NUM_IUDMA; ch_idx++) { + struct iudma_ch *iudma = &udc->iudma[ch_idx]; + struct list_head *pos; + + seq_printf(s, "IUDMA channel %d -- ", ch_idx); + switch (iudma_defaults[ch_idx].ep_type) { + case BCMEP_CTRL: + seq_printf(s, "control"); + break; + case BCMEP_BULK: + seq_printf(s, "bulk"); + break; + case BCMEP_INTR: + seq_printf(s, "interrupt"); + break; + } + seq_printf(s, ch_idx & 0x01 ? " tx" : " rx"); + seq_printf(s, " [ep%d]:\n", + max_t(int, iudma_defaults[ch_idx].ep_num, 0)); + seq_printf(s, " cfg: %08x; irqstat: %08x; irqmask: %08x; maxburst: %08x\n", + usb_dmac_readl(udc, ENETDMAC_CHANCFG_REG, ch_idx), + usb_dmac_readl(udc, ENETDMAC_IR_REG, ch_idx), + usb_dmac_readl(udc, ENETDMAC_IRMASK_REG, ch_idx), + usb_dmac_readl(udc, ENETDMAC_MAXBURST_REG, ch_idx)); + + sram2 = usb_dmas_readl(udc, ENETDMAS_SRAM2_REG, ch_idx); + sram3 = usb_dmas_readl(udc, ENETDMAS_SRAM3_REG, ch_idx); + seq_printf(s, " base: %08x; index: %04x_%04x; desc: %04x_%04x %08x\n", + usb_dmas_readl(udc, ENETDMAS_RSTART_REG, ch_idx), + sram2 >> 16, sram2 & 0xffff, + sram3 >> 16, sram3 & 0xffff, + usb_dmas_readl(udc, ENETDMAS_SRAM4_REG, ch_idx)); + seq_printf(s, " desc: %d/%d used", iudma->n_bds_used, + iudma->n_bds); + + if (iudma->bep) { + i = 0; + list_for_each(pos, &iudma->bep->queue) + i++; + seq_printf(s, "; %d queued\n", i); + } else { + seq_printf(s, "\n"); + } + + for (i = 0; i < iudma->n_bds; i++) { + struct bcm_enet_desc *d = &iudma->bd_ring[i]; + + seq_printf(s, " %03x (%02x): len_stat: %04x_%04x; pa %08x", + i * sizeof(*d), i, + d->len_stat >> 16, d->len_stat & 0xffff, + d->address); + if (d == iudma->read_bd) + seq_printf(s, " <write_bd) + seq_printf(s, " <i_private); +} + +static int bcm63xx_iudma_dbg_open(struct inode *inode, struct file *file) +{ + return single_open(file, bcm63xx_iudma_dbg_show, inode->i_private); +} + +static const struct file_operations usbd_dbg_fops = { + .owner = THIS_MODULE, + .open = bcm63xx_usbd_dbg_open, + .llseek = seq_lseek, + .read = seq_read, + .release = single_release, +}; + +static const struct file_operations iudma_dbg_fops = { + .owner = THIS_MODULE, + .open = bcm63xx_iudma_dbg_open, + .llseek = seq_lseek, + .read = seq_read, + .release = single_release, +}; + + +/** + * bcm63xx_udc_init_debugfs - Create debugfs entries. + * @udc: Reference to the device controller. + */ +static void bcm63xx_udc_init_debugfs(struct bcm63xx_udc *udc) +{ + struct dentry *root, *usbd, *iudma; + + if (!IS_ENABLED(CONFIG_USB_GADGET_DEBUG_FS)) + return; + + root = debugfs_create_dir(udc->gadget.name, NULL); + if (IS_ERR(root) || !root) + goto err_root; + + usbd = debugfs_create_file("usbd", 0400, root, udc, + &usbd_dbg_fops); + if (!usbd) + goto err_usbd; + iudma = debugfs_create_file("iudma", 0400, root, udc, + &iudma_dbg_fops); + if (!iudma) + goto err_iudma; + + udc->debugfs_root = root; + udc->debugfs_usbd = usbd; + udc->debugfs_iudma = iudma; + return; +err_iudma: + debugfs_remove(usbd); +err_usbd: + debugfs_remove(root); +err_root: + dev_err(udc->dev, "debugfs is not available\n"); +} + +/** + * bcm63xx_udc_cleanup_debugfs - Remove debugfs entries. + * @udc: Reference to the device controller. + * + * debugfs_remove() is safe to call with a NULL argument. + */ +static void bcm63xx_udc_cleanup_debugfs(struct bcm63xx_udc *udc) +{ + debugfs_remove(udc->debugfs_iudma); + debugfs_remove(udc->debugfs_usbd); + debugfs_remove(udc->debugfs_root); + udc->debugfs_iudma = NULL; + udc->debugfs_usbd = NULL; + udc->debugfs_root = NULL; +} + +/*********************************************************************** + * Driver init/exit + ***********************************************************************/ + +/** + * bcm63xx_udc_probe - Initialize a new instance of the UDC. + * @pdev: Platform device struct from the bcm63xx BSP code. + * + * Note that platform data is required, because pd.port_no varies from chip + * to chip and is used to switch the correct USB port to device mode. + */ +static int bcm63xx_udc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm63xx_usbd_platform_data *pd = dev->platform_data; + struct bcm63xx_udc *udc; + struct resource *res; + int rc = -ENOMEM, i, irq; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) { + dev_err(dev, "cannot allocate memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, udc); + udc->dev = dev; + udc->pd = pd; + + if (!pd) { + dev_err(dev, "missing platform data\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + udc->usbd_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(udc->usbd_regs)) + return PTR_ERR(udc->usbd_regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + udc->iudma_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(udc->iudma_regs)) + return PTR_ERR(udc->iudma_regs); + + spin_lock_init(&udc->lock); + INIT_WORK(&udc->ep0_wq, bcm63xx_ep0_process); + + udc->gadget.ops = &bcm63xx_udc_ops; + udc->gadget.name = dev_name(dev); + + if (!pd->use_fullspeed && !use_fullspeed) + udc->gadget.max_speed = USB_SPEED_HIGH; + else + udc->gadget.max_speed = USB_SPEED_FULL; + + /* request clocks, allocate buffers, and clear any pending IRQs */ + rc = bcm63xx_init_udc_hw(udc); + if (rc) + return rc; + + rc = -ENXIO; + + /* IRQ resource #0: control interrupt (VBUS, speed, etc.) */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "missing IRQ resource #0\n"); + goto out_uninit; + } + if (devm_request_irq(dev, irq, &bcm63xx_udc_ctrl_isr, 0, + dev_name(dev), udc) < 0) { + dev_err(dev, "error requesting IRQ #%d\n", irq); + goto out_uninit; + } + + /* IRQ resources #1-6: data interrupts for IUDMA channels 0-5 */ + for (i = 0; i < BCM63XX_NUM_IUDMA; i++) { + irq = platform_get_irq(pdev, i + 1); + if (irq < 0) { + dev_err(dev, "missing IRQ resource #%d\n", i + 1); + goto out_uninit; + } + if (devm_request_irq(dev, irq, &bcm63xx_udc_data_isr, 0, + dev_name(dev), &udc->iudma[i]) < 0) { + dev_err(dev, "error requesting IRQ #%d\n", irq); + goto out_uninit; + } + } + + bcm63xx_udc_init_debugfs(udc); + rc = usb_add_gadget_udc(dev, &udc->gadget); + if (!rc) + return 0; + + bcm63xx_udc_cleanup_debugfs(udc); +out_uninit: + bcm63xx_uninit_udc_hw(udc); + return rc; +} + +/** + * bcm63xx_udc_remove - Remove the device from the system. + * @pdev: Platform device struct from the bcm63xx BSP code. + */ +static int bcm63xx_udc_remove(struct platform_device *pdev) +{ + struct bcm63xx_udc *udc = platform_get_drvdata(pdev); + + bcm63xx_udc_cleanup_debugfs(udc); + usb_del_gadget_udc(&udc->gadget); + BUG_ON(udc->driver); + + bcm63xx_uninit_udc_hw(udc); + + return 0; +} + +static struct platform_driver bcm63xx_udc_driver = { + .probe = bcm63xx_udc_probe, + .remove = bcm63xx_udc_remove, + .driver = { + .name = DRV_MODULE_NAME, + .owner = THIS_MODULE, + }, +}; +module_platform_driver(bcm63xx_udc_driver); + +MODULE_DESCRIPTION("BCM63xx USB Peripheral Controller"); +MODULE_AUTHOR("Kevin Cernekee "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_MODULE_NAME); diff --git a/drivers/usb/gadget/cdc2.c b/drivers/usb/gadget/cdc2.c new file mode 100644 index 00000000..2c525518 --- /dev/null +++ b/drivers/usb/gadget/cdc2.c @@ -0,0 +1,230 @@ +/* + * cdc2.c -- CDC Composite driver, with ECM and ACM support + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include + +#include "u_ether.h" +#include "u_serial.h" + + +#define DRIVER_DESC "CDC Composite Gadget" +#define DRIVER_VERSION "King Kamehameha Day 2008" + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. + * It's for devices with only this composite CDC configuration. + */ +#define CDC_VENDOR_NUM 0x0525 /* NetChip */ +#define CDC_PRODUCT_NUM 0xa4aa /* CDC Composite: ECM + ACM */ + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "f_ecm.c" +#include "u_ether.c" + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + + .bDeviceClass = USB_CLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id can be overridden by module parameters. */ + .idVendor = cpu_to_le16(CDC_VENDOR_NUM), + .idProduct = cpu_to_le16(CDC_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + + +/* string IDs are assigned dynamically */ +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static u8 hostaddr[ETH_ALEN]; +static struct eth_dev *the_dev; +/*-------------------------------------------------------------------------*/ +static struct usb_function *f_acm; +static struct usb_function_instance *fi_serial; + +/* + * We _always_ have both CDC ECM and CDC ACM functions. + */ +static int __init cdc_do_config(struct usb_configuration *c) +{ + int status; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + status = ecm_bind_config(c, hostaddr, the_dev); + if (status < 0) + return status; + + fi_serial = usb_get_function_instance("acm"); + if (IS_ERR(fi_serial)) + return PTR_ERR(fi_serial); + + f_acm = usb_get_function(fi_serial); + if (IS_ERR(f_acm)) { + status = PTR_ERR(f_acm); + goto err_func_acm; + } + + status = usb_add_function(c, f_acm); + if (status) + goto err_conf; + return 0; +err_conf: + usb_put_function(f_acm); +err_func_acm: + usb_put_function_instance(fi_serial); + return status; +} + +static struct usb_configuration cdc_config_driver = { + .label = "CDC Composite (ECM + ACM)", + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init cdc_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int status; + + if (!can_support_ecm(cdev->gadget)) { + dev_err(&gadget->dev, "controller '%s' not usable\n", + gadget->name); + return -EINVAL; + } + + /* set up network link layer */ + the_dev = gether_setup(cdev->gadget, hostaddr); + if (IS_ERR(the_dev)) + return PTR_ERR(the_dev); + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto fail1; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + /* register our configuration */ + status = usb_add_config(cdev, &cdc_config_driver, cdc_do_config); + if (status < 0) + goto fail1; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n", + DRIVER_DESC); + + return 0; + +fail1: + gether_cleanup(the_dev); + return status; +} + +static int __exit cdc_unbind(struct usb_composite_dev *cdev) +{ + usb_put_function(f_acm); + usb_put_function_instance(fi_serial); + gether_cleanup(the_dev); + return 0; +} + +static __refdata struct usb_composite_driver cdc_driver = { + .name = "g_cdc", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = cdc_bind, + .unbind = __exit_p(cdc_unbind), +}; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); + +static int __init init(void) +{ + return usb_composite_probe(&cdc_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&cdc_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c new file mode 100644 index 00000000..19bd306d --- /dev/null +++ b/drivers/usb/gadget/composite.c @@ -0,0 +1,1909 @@ +/* + * composite.c - infrastructure for Composite USB Gadgets + * + * Copyright (C) 2006-2008 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +/* + * The code in this file is utility code, used to build a gadget driver + * from one or more "function" drivers, one or more "configuration" + * objects, and a "usb_composite_driver" by gluing them together along + * with the relevant device-wide data. + */ + +static struct usb_gadget_strings **get_containers_gs( + struct usb_gadget_string_container *uc) +{ + return (struct usb_gadget_strings **)uc->stash; +} + +/** + * next_ep_desc() - advance to the next EP descriptor + * @t: currect pointer within descriptor array + * + * Return: next EP descriptor or NULL + * + * Iterate over @t until either EP descriptor found or + * NULL (that indicates end of list) encountered + */ +static struct usb_descriptor_header** +next_ep_desc(struct usb_descriptor_header **t) +{ + for (; *t; t++) { + if ((*t)->bDescriptorType == USB_DT_ENDPOINT) + return t; + } + return NULL; +} + +/* + * for_each_ep_desc()- iterate over endpoint descriptors in the + * descriptors list + * @start: pointer within descriptor array. + * @ep_desc: endpoint descriptor to use as the loop cursor + */ +#define for_each_ep_desc(start, ep_desc) \ + for (ep_desc = next_ep_desc(start); \ + ep_desc; ep_desc = next_ep_desc(ep_desc+1)) + +/** + * config_ep_by_speed() - configures the given endpoint + * according to gadget speed. + * @g: pointer to the gadget + * @f: usb function + * @_ep: the endpoint to configure + * + * Return: error code, 0 on success + * + * This function chooses the right descriptors for a given + * endpoint according to gadget speed and saves it in the + * endpoint desc field. If the endpoint already has a descriptor + * assigned to it - overwrites it with currently corresponding + * descriptor. The endpoint maxpacket field is updated according + * to the chosen descriptor. + * Note: the supplied function should hold all the descriptors + * for supported speeds + */ +int config_ep_by_speed(struct usb_gadget *g, + struct usb_function *f, + struct usb_ep *_ep) +{ + struct usb_composite_dev *cdev = get_gadget_data(g); + struct usb_endpoint_descriptor *chosen_desc = NULL; + struct usb_descriptor_header **speed_desc = NULL; + + struct usb_ss_ep_comp_descriptor *comp_desc = NULL; + int want_comp_desc = 0; + + struct usb_descriptor_header **d_spd; /* cursor for speed desc */ + + if (!g || !f || !_ep) + return -EIO; + + /* select desired speed */ + switch (g->speed) { + case USB_SPEED_SUPER: + if (gadget_is_superspeed(g)) { + speed_desc = f->ss_descriptors; + want_comp_desc = 1; + break; + } + /* else: Fall trough */ + case USB_SPEED_HIGH: + if (gadget_is_dualspeed(g)) { + speed_desc = f->hs_descriptors; + break; + } + /* else: fall through */ + default: + speed_desc = f->fs_descriptors; + } + /* find descriptors */ + for_each_ep_desc(speed_desc, d_spd) { + chosen_desc = (struct usb_endpoint_descriptor *)*d_spd; + if (chosen_desc->bEndpointAddress == _ep->address) + goto ep_found; + } + return -EIO; + +ep_found: + /* commit results */ + _ep->maxpacket = usb_endpoint_maxp(chosen_desc); + _ep->desc = chosen_desc; + _ep->comp_desc = NULL; + _ep->maxburst = 0; + _ep->mult = 0; + if (!want_comp_desc) + return 0; + + /* + * Companion descriptor should follow EP descriptor + * USB 3.0 spec, #9.6.7 + */ + comp_desc = (struct usb_ss_ep_comp_descriptor *)*(++d_spd); + if (!comp_desc || + (comp_desc->bDescriptorType != USB_DT_SS_ENDPOINT_COMP)) + return -EIO; + _ep->comp_desc = comp_desc; + if (g->speed == USB_SPEED_SUPER) { + switch (usb_endpoint_type(_ep->desc)) { + case USB_ENDPOINT_XFER_ISOC: + /* mult: bits 1:0 of bmAttributes */ + _ep->mult = comp_desc->bmAttributes & 0x3; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + _ep->maxburst = comp_desc->bMaxBurst + 1; + break; + default: + if (comp_desc->bMaxBurst != 0) + ERROR(cdev, "ep0 bMaxBurst must be 0\n"); + _ep->maxburst = 1; + break; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(config_ep_by_speed); + +/** + * usb_add_function() - add a function to a configuration + * @config: the configuration + * @function: the function being added + * Context: single threaded during gadget setup + * + * After initialization, each configuration must have one or more + * functions added to it. Adding a function involves calling its @bind() + * method to allocate resources such as interface and string identifiers + * and endpoints. + * + * This function returns the value of the function's bind(), which is + * zero for success else a negative errno value. + */ +int usb_add_function(struct usb_configuration *config, + struct usb_function *function) +{ + int value = -EINVAL; + + DBG(config->cdev, "adding '%s'/%p to config '%s'/%p\n", + function->name, function, + config->label, config); + + if (!function->set_alt || !function->disable) + goto done; + + function->config = config; + list_add_tail(&function->list, &config->functions); + + /* REVISIT *require* function->bind? */ + if (function->bind) { + value = function->bind(config, function); + if (value < 0) { + list_del(&function->list); + function->config = NULL; + } + } else + value = 0; + + /* We allow configurations that don't work at both speeds. + * If we run into a lowspeed Linux system, treat it the same + * as full speed ... it's the function drivers that will need + * to avoid bulk and ISO transfers. + */ + if (!config->fullspeed && function->fs_descriptors) + config->fullspeed = true; + if (!config->highspeed && function->hs_descriptors) + config->highspeed = true; + if (!config->superspeed && function->ss_descriptors) + config->superspeed = true; + +done: + if (value) + DBG(config->cdev, "adding '%s'/%p --> %d\n", + function->name, function, value); + return value; +} +EXPORT_SYMBOL_GPL(usb_add_function); + +void usb_remove_function(struct usb_configuration *c, struct usb_function *f) +{ + if (f->disable) + f->disable(f); + + bitmap_zero(f->endpoints, 32); + list_del(&f->list); + if (f->unbind) + f->unbind(c, f); +} +EXPORT_SYMBOL_GPL(usb_remove_function); + +/** + * usb_function_deactivate - prevent function and gadget enumeration + * @function: the function that isn't yet ready to respond + * + * Blocks response of the gadget driver to host enumeration by + * preventing the data line pullup from being activated. This is + * normally called during @bind() processing to change from the + * initial "ready to respond" state, or when a required resource + * becomes available. + * + * For example, drivers that serve as a passthrough to a userspace + * daemon can block enumeration unless that daemon (such as an OBEX, + * MTP, or print server) is ready to handle host requests. + * + * Not all systems support software control of their USB peripheral + * data pullups. + * + * Returns zero on success, else negative errno. + */ +int usb_function_deactivate(struct usb_function *function) +{ + struct usb_composite_dev *cdev = function->config->cdev; + unsigned long flags; + int status = 0; + + spin_lock_irqsave(&cdev->lock, flags); + + if (cdev->deactivations == 0) + status = usb_gadget_disconnect(cdev->gadget); + if (status == 0) + cdev->deactivations++; + + spin_unlock_irqrestore(&cdev->lock, flags); + return status; +} +EXPORT_SYMBOL_GPL(usb_function_deactivate); + +/** + * usb_function_activate - allow function and gadget enumeration + * @function: function on which usb_function_activate() was called + * + * Reverses effect of usb_function_deactivate(). If no more functions + * are delaying their activation, the gadget driver will respond to + * host enumeration procedures. + * + * Returns zero on success, else negative errno. + */ +int usb_function_activate(struct usb_function *function) +{ + struct usb_composite_dev *cdev = function->config->cdev; + unsigned long flags; + int status = 0; + + spin_lock_irqsave(&cdev->lock, flags); + + if (WARN_ON(cdev->deactivations == 0)) + status = -EINVAL; + else { + cdev->deactivations--; + if (cdev->deactivations == 0) + status = usb_gadget_connect(cdev->gadget); + } + + spin_unlock_irqrestore(&cdev->lock, flags); + return status; +} +EXPORT_SYMBOL_GPL(usb_function_activate); + +/** + * usb_interface_id() - allocate an unused interface ID + * @config: configuration associated with the interface + * @function: function handling the interface + * Context: single threaded during gadget setup + * + * usb_interface_id() is called from usb_function.bind() callbacks to + * allocate new interface IDs. The function driver will then store that + * ID in interface, association, CDC union, and other descriptors. It + * will also handle any control requests targeted at that interface, + * particularly changing its altsetting via set_alt(). There may + * also be class-specific or vendor-specific requests to handle. + * + * All interface identifier should be allocated using this routine, to + * ensure that for example different functions don't wrongly assign + * different meanings to the same identifier. Note that since interface + * identifiers are configuration-specific, functions used in more than + * one configuration (or more than once in a given configuration) need + * multiple versions of the relevant descriptors. + * + * Returns the interface ID which was allocated; or -ENODEV if no + * more interface IDs can be allocated. + */ +int usb_interface_id(struct usb_configuration *config, + struct usb_function *function) +{ + unsigned id = config->next_interface_id; + + if (id < MAX_CONFIG_INTERFACES) { + config->interface[id] = function; + config->next_interface_id = id + 1; + return id; + } + return -ENODEV; +} +EXPORT_SYMBOL_GPL(usb_interface_id); + +static u8 encode_bMaxPower(enum usb_device_speed speed, + struct usb_configuration *c) +{ + unsigned val; + + if (c->MaxPower) + val = c->MaxPower; + else + val = CONFIG_USB_GADGET_VBUS_DRAW; + if (!val) + return 0; + switch (speed) { + case USB_SPEED_SUPER: + return DIV_ROUND_UP(val, 8); + default: + return DIV_ROUND_UP(val, 2); + }; +} + +static int config_buf(struct usb_configuration *config, + enum usb_device_speed speed, void *buf, u8 type) +{ + struct usb_config_descriptor *c = buf; + void *next = buf + USB_DT_CONFIG_SIZE; + int len; + struct usb_function *f; + int status; + + len = USB_COMP_EP0_BUFSIZ - USB_DT_CONFIG_SIZE; + /* write the config descriptor */ + c = buf; + c->bLength = USB_DT_CONFIG_SIZE; + c->bDescriptorType = type; + /* wTotalLength is written later */ + c->bNumInterfaces = config->next_interface_id; + c->bConfigurationValue = config->bConfigurationValue; + c->iConfiguration = config->iConfiguration; + c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes; + c->bMaxPower = encode_bMaxPower(speed, config); + + /* There may be e.g. OTG descriptors */ + if (config->descriptors) { + status = usb_descriptor_fillbuf(next, len, + config->descriptors); + if (status < 0) + return status; + len -= status; + next += status; + } + + /* add each function's descriptors */ + list_for_each_entry(f, &config->functions, list) { + struct usb_descriptor_header **descriptors; + + switch (speed) { + case USB_SPEED_SUPER: + descriptors = f->ss_descriptors; + break; + case USB_SPEED_HIGH: + descriptors = f->hs_descriptors; + break; + default: + descriptors = f->fs_descriptors; + } + + if (!descriptors) + continue; + status = usb_descriptor_fillbuf(next, len, + (const struct usb_descriptor_header **) descriptors); + if (status < 0) + return status; + len -= status; + next += status; + } + + len = next - buf; + c->wTotalLength = cpu_to_le16(len); + return len; +} + +static int config_desc(struct usb_composite_dev *cdev, unsigned w_value) +{ + struct usb_gadget *gadget = cdev->gadget; + struct usb_configuration *c; + u8 type = w_value >> 8; + enum usb_device_speed speed = USB_SPEED_UNKNOWN; + + if (gadget->speed == USB_SPEED_SUPER) + speed = gadget->speed; + else if (gadget_is_dualspeed(gadget)) { + int hs = 0; + if (gadget->speed == USB_SPEED_HIGH) + hs = 1; + if (type == USB_DT_OTHER_SPEED_CONFIG) + hs = !hs; + if (hs) + speed = USB_SPEED_HIGH; + + } + + /* This is a lookup by config *INDEX* */ + w_value &= 0xff; + list_for_each_entry(c, &cdev->configs, list) { + /* ignore configs that won't work at this speed */ + switch (speed) { + case USB_SPEED_SUPER: + if (!c->superspeed) + continue; + break; + case USB_SPEED_HIGH: + if (!c->highspeed) + continue; + break; + default: + if (!c->fullspeed) + continue; + } + + if (w_value == 0) + return config_buf(c, speed, cdev->req->buf, type); + w_value--; + } + return -EINVAL; +} + +static int count_configs(struct usb_composite_dev *cdev, unsigned type) +{ + struct usb_gadget *gadget = cdev->gadget; + struct usb_configuration *c; + unsigned count = 0; + int hs = 0; + int ss = 0; + + if (gadget_is_dualspeed(gadget)) { + if (gadget->speed == USB_SPEED_HIGH) + hs = 1; + if (gadget->speed == USB_SPEED_SUPER) + ss = 1; + if (type == USB_DT_DEVICE_QUALIFIER) + hs = !hs; + } + list_for_each_entry(c, &cdev->configs, list) { + /* ignore configs that won't work at this speed */ + if (ss) { + if (!c->superspeed) + continue; + } else if (hs) { + if (!c->highspeed) + continue; + } else { + if (!c->fullspeed) + continue; + } + count++; + } + return count; +} + +/** + * bos_desc() - prepares the BOS descriptor. + * @cdev: pointer to usb_composite device to generate the bos + * descriptor for + * + * This function generates the BOS (Binary Device Object) + * descriptor and its device capabilities descriptors. The BOS + * descriptor should be supported by a SuperSpeed device. + */ +static int bos_desc(struct usb_composite_dev *cdev) +{ + struct usb_ext_cap_descriptor *usb_ext; + struct usb_ss_cap_descriptor *ss_cap; + struct usb_dcd_config_params dcd_config_params; + struct usb_bos_descriptor *bos = cdev->req->buf; + + bos->bLength = USB_DT_BOS_SIZE; + bos->bDescriptorType = USB_DT_BOS; + + bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE); + bos->bNumDeviceCaps = 0; + + /* + * A SuperSpeed device shall include the USB2.0 extension descriptor + * and shall support LPM when operating in USB2.0 HS mode. + */ + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE); + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE; + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT; + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT); + + /* + * The Superspeed USB Capability descriptor shall be implemented by all + * SuperSpeed devices. + */ + ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE); + ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; + ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; + ss_cap->bmAttributes = 0; /* LTM is not supported yet */ + ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION | + USB_FULL_SPEED_OPERATION | + USB_HIGH_SPEED_OPERATION | + USB_5GBPS_OPERATION); + ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; + + /* Get Controller configuration */ + if (cdev->gadget->ops->get_config_params) + cdev->gadget->ops->get_config_params(&dcd_config_params); + else { + dcd_config_params.bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT; + dcd_config_params.bU2DevExitLat = + cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + } + ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; + ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; + + return le16_to_cpu(bos->wTotalLength); +} + +static void device_qual(struct usb_composite_dev *cdev) +{ + struct usb_qualifier_descriptor *qual = cdev->req->buf; + + qual->bLength = sizeof(*qual); + qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER; + /* POLICY: same bcdUSB and device type info at both speeds */ + qual->bcdUSB = cdev->desc.bcdUSB; + qual->bDeviceClass = cdev->desc.bDeviceClass; + qual->bDeviceSubClass = cdev->desc.bDeviceSubClass; + qual->bDeviceProtocol = cdev->desc.bDeviceProtocol; + /* ASSUME same EP0 fifo size at both speeds */ + qual->bMaxPacketSize0 = cdev->gadget->ep0->maxpacket; + qual->bNumConfigurations = count_configs(cdev, USB_DT_DEVICE_QUALIFIER); + qual->bRESERVED = 0; +} + +/*-------------------------------------------------------------------------*/ + +static void reset_config(struct usb_composite_dev *cdev) +{ + struct usb_function *f; + + DBG(cdev, "reset config\n"); + + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->disable) + f->disable(f); + + bitmap_zero(f->endpoints, 32); + } + cdev->config = NULL; + cdev->delayed_status = 0; +} + +static int set_config(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl, unsigned number) +{ + struct usb_gadget *gadget = cdev->gadget; + struct usb_configuration *c = NULL; + int result = -EINVAL; + unsigned power = gadget_is_otg(gadget) ? 8 : 100; + int tmp; + + if (number) { + list_for_each_entry(c, &cdev->configs, list) { + if (c->bConfigurationValue == number) { + /* + * We disable the FDs of the previous + * configuration only if the new configuration + * is a valid one + */ + if (cdev->config) + reset_config(cdev); + result = 0; + break; + } + } + if (result < 0) + goto done; + } else { /* Zero configuration value - need to reset the config */ + if (cdev->config) + reset_config(cdev); + result = 0; + } + + INFO(cdev, "%s config #%d: %s\n", + usb_speed_string(gadget->speed), + number, c ? c->label : "unconfigured"); + + if (!c) + goto done; + + cdev->config = c; + + /* Initialize all interfaces by setting them to altsetting zero. */ + for (tmp = 0; tmp < MAX_CONFIG_INTERFACES; tmp++) { + struct usb_function *f = c->interface[tmp]; + struct usb_descriptor_header **descriptors; + + if (!f) + break; + + /* + * Record which endpoints are used by the function. This is used + * to dispatch control requests targeted at that endpoint to the + * function's setup callback instead of the current + * configuration's setup callback. + */ + switch (gadget->speed) { + case USB_SPEED_SUPER: + descriptors = f->ss_descriptors; + break; + case USB_SPEED_HIGH: + descriptors = f->hs_descriptors; + break; + default: + descriptors = f->fs_descriptors; + } + + for (; *descriptors; ++descriptors) { + struct usb_endpoint_descriptor *ep; + int addr; + + if ((*descriptors)->bDescriptorType != USB_DT_ENDPOINT) + continue; + + ep = (struct usb_endpoint_descriptor *)*descriptors; + addr = ((ep->bEndpointAddress & 0x80) >> 3) + | (ep->bEndpointAddress & 0x0f); + set_bit(addr, f->endpoints); + } + + result = f->set_alt(f, tmp, 0); + if (result < 0) { + DBG(cdev, "interface %d (%s/%p) alt 0 --> %d\n", + tmp, f->name, f, result); + + reset_config(cdev); + goto done; + } + + if (result == USB_GADGET_DELAYED_STATUS) { + DBG(cdev, + "%s: interface %d (%s) requested delayed status\n", + __func__, tmp, f->name); + cdev->delayed_status++; + DBG(cdev, "delayed_status count %d\n", + cdev->delayed_status); + } + } + + /* when we return, be sure our power usage is valid */ + power = c->MaxPower ? c->MaxPower : CONFIG_USB_GADGET_VBUS_DRAW; +done: + usb_gadget_vbus_draw(gadget, power); + if (result >= 0 && cdev->delayed_status) + result = USB_GADGET_DELAYED_STATUS; + return result; +} + +int usb_add_config_only(struct usb_composite_dev *cdev, + struct usb_configuration *config) +{ + struct usb_configuration *c; + + if (!config->bConfigurationValue) + return -EINVAL; + + /* Prevent duplicate configuration identifiers */ + list_for_each_entry(c, &cdev->configs, list) { + if (c->bConfigurationValue == config->bConfigurationValue) + return -EBUSY; + } + + config->cdev = cdev; + list_add_tail(&config->list, &cdev->configs); + + INIT_LIST_HEAD(&config->functions); + config->next_interface_id = 0; + memset(config->interface, 0, sizeof(config->interface)); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_add_config_only); + +/** + * usb_add_config() - add a configuration to a device. + * @cdev: wraps the USB gadget + * @config: the configuration, with bConfigurationValue assigned + * @bind: the configuration's bind function + * Context: single threaded during gadget setup + * + * One of the main tasks of a composite @bind() routine is to + * add each of the configurations it supports, using this routine. + * + * This function returns the value of the configuration's @bind(), which + * is zero for success else a negative errno value. Binding configurations + * assigns global resources including string IDs, and per-configuration + * resources such as interface IDs and endpoints. + */ +int usb_add_config(struct usb_composite_dev *cdev, + struct usb_configuration *config, + int (*bind)(struct usb_configuration *)) +{ + int status = -EINVAL; + + if (!bind) + goto done; + + DBG(cdev, "adding config #%u '%s'/%p\n", + config->bConfigurationValue, + config->label, config); + + printk("%s(%d) %s \n",current->comm, current->pid,__func__); + status = usb_add_config_only(cdev, config); + if (status) + goto done; + + status = bind(config); + if (status < 0) { + while (!list_empty(&config->functions)) { + struct usb_function *f; + + f = list_first_entry(&config->functions, + struct usb_function, list); + list_del(&f->list); + if (f->unbind) { + DBG(cdev, "unbind function '%s'/%p\n", + f->name, f); + f->unbind(config, f); + /* may free memory for "f" */ + } + } + __list_del_entry(&config->list); + config->cdev = NULL; + } else { + unsigned i; + + DBG(cdev, "cfg %d/%p speeds:%s%s%s\n", + config->bConfigurationValue, config, + config->superspeed ? " super" : "", + config->highspeed ? " high" : "", + config->fullspeed + ? (gadget_is_dualspeed(cdev->gadget) + ? " full" + : " full/low") + : ""); + + for (i = 0; i < MAX_CONFIG_INTERFACES; i++) { + struct usb_function *f = config->interface[i]; + + if (!f) + continue; + DBG(cdev, " interface %d = %s/%p\n", + i, f->name, f); + } + } + + /* set_alt(), or next bind(), sets up + * ep->driver_data as needed. + */ + usb_ep_autoconfig_reset(cdev->gadget); + +done: + if (status) + DBG(cdev, "added config '%s'/%u --> %d\n", config->label, + config->bConfigurationValue, status); + return status; +} +EXPORT_SYMBOL_GPL(usb_add_config); + +static void unbind_config(struct usb_composite_dev *cdev, + struct usb_configuration *config) +{ + while (!list_empty(&config->functions)) { + struct usb_function *f; + + f = list_first_entry(&config->functions, + struct usb_function, list); + list_del(&f->list); + if (f->unbind) { + DBG(cdev, "unbind function '%s'/%p\n", f->name, f); + f->unbind(config, f); + /* may free memory for "f" */ + } + } + if (config->unbind) { + DBG(cdev, "unbind config '%s'/%p\n", config->label, config); + config->unbind(config); + /* may free memory for "c" */ + } +} + +/** + * usb_remove_config() - remove a configuration from a device. + * @cdev: wraps the USB gadget + * @config: the configuration + * + * Drivers must call usb_gadget_disconnect before calling this function + * to disconnect the device from the host and make sure the host will not + * try to enumerate the device while we are changing the config list. + */ +void usb_remove_config(struct usb_composite_dev *cdev, + struct usb_configuration *config) +{ + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + + if (cdev->config == config) + reset_config(cdev); + + __list_del_entry(&config->list); + printk("%s(%d) %s \n",current->comm,current->pid,__func__); + spin_unlock_irqrestore(&cdev->lock, flags); + + unbind_config(cdev, config); +} + +/*-------------------------------------------------------------------------*/ + +/* We support strings in multiple languages ... string descriptor zero + * says which languages are supported. The typical case will be that + * only one language (probably English) is used, with I18N handled on + * the host side. + */ + +static void collect_langs(struct usb_gadget_strings **sp, __le16 *buf) +{ + const struct usb_gadget_strings *s; + __le16 language; + __le16 *tmp; + + while (*sp) { + s = *sp; + language = cpu_to_le16(s->language); + for (tmp = buf; *tmp && tmp < &buf[126]; tmp++) { + if (*tmp == language) + goto repeat; + } + *tmp++ = language; +repeat: + sp++; + } +} + +static int lookup_string( + struct usb_gadget_strings **sp, + void *buf, + u16 language, + int id +) +{ + struct usb_gadget_strings *s; + int value; + + while (*sp) { + s = *sp++; + if (s->language != language) + continue; + value = usb_gadget_get_string(s, id, buf); + if (value > 0) + return value; + } + return -EINVAL; +} + +static int get_string(struct usb_composite_dev *cdev, + void *buf, u16 language, int id) +{ + struct usb_composite_driver *composite = cdev->driver; + struct usb_gadget_string_container *uc; + struct usb_configuration *c; + struct usb_function *f; + int len; + + /* Yes, not only is USB's I18N support probably more than most + * folk will ever care about ... also, it's all supported here. + * (Except for UTF8 support for Unicode's "Astral Planes".) + */ + + /* 0 == report all available language codes */ + if (id == 0) { + struct usb_string_descriptor *s = buf; + struct usb_gadget_strings **sp; + + memset(s, 0, 256); + s->bDescriptorType = USB_DT_STRING; + + sp = composite->strings; + if (sp) + collect_langs(sp, s->wData); + + list_for_each_entry(c, &cdev->configs, list) { + sp = c->strings; + if (sp) + collect_langs(sp, s->wData); + + list_for_each_entry(f, &c->functions, list) { + sp = f->strings; + if (sp) + collect_langs(sp, s->wData); + } + } + list_for_each_entry(uc, &cdev->gstrings, list) { + struct usb_gadget_strings **sp; + + sp = get_containers_gs(uc); + collect_langs(sp, s->wData); + } + + for (len = 0; len <= 126 && s->wData[len]; len++) + continue; + if (!len) + return -EINVAL; + + s->bLength = 2 * (len + 1); + return s->bLength; + } + + list_for_each_entry(uc, &cdev->gstrings, list) { + struct usb_gadget_strings **sp; + + sp = get_containers_gs(uc); + len = lookup_string(sp, buf, language, id); + if (len > 0) + return len; + } + + /* String IDs are device-scoped, so we look up each string + * table we're told about. These lookups are infrequent; + * simpler-is-better here. + */ + if (composite->strings) { + len = lookup_string(composite->strings, buf, language, id); + if (len > 0) + return len; + } + list_for_each_entry(c, &cdev->configs, list) { + if (c->strings) { + len = lookup_string(c->strings, buf, language, id); + if (len > 0) + return len; + } + list_for_each_entry(f, &c->functions, list) { + if (!f->strings) + continue; + len = lookup_string(f->strings, buf, language, id); + if (len > 0) + return len; + } + } + return -EINVAL; +} + +/** + * usb_string_id() - allocate an unused string ID + * @cdev: the device whose string descriptor IDs are being allocated + * Context: single threaded during gadget setup + * + * @usb_string_id() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then store that ID in the appropriate descriptors and string table. + * + * All string identifier should be allocated using this, + * @usb_string_ids_tab() or @usb_string_ids_n() routine, to ensure + * that for example different functions don't wrongly assign different + * meanings to the same identifier. + */ +int usb_string_id(struct usb_composite_dev *cdev) +{ + if (cdev->next_string_id < 254) { + /* string id 0 is reserved by USB spec for list of + * supported languages */ + /* 255 reserved as well? -- mina86 */ + cdev->next_string_id++; + return cdev->next_string_id; + } + return -ENODEV; +} +EXPORT_SYMBOL_GPL(usb_string_id); + +/** + * usb_string_ids() - allocate unused string IDs in batch + * @cdev: the device whose string descriptor IDs are being allocated + * @str: an array of usb_string objects to assign numbers to + * Context: single threaded during gadget setup + * + * @usb_string_ids() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then copy IDs from the string table to the appropriate descriptors + * and string table for other languages. + * + * All string identifier should be allocated using this, + * @usb_string_id() or @usb_string_ids_n() routine, to ensure that for + * example different functions don't wrongly assign different meanings + * to the same identifier. + */ +int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str) +{ + int next = cdev->next_string_id; + + for (; str->s; ++str) { + if (unlikely(next >= 254)) + return -ENODEV; + str->id = ++next; + } + + cdev->next_string_id = next; + + return 0; +} +EXPORT_SYMBOL_GPL(usb_string_ids_tab); + +static struct usb_gadget_string_container *copy_gadget_strings( + struct usb_gadget_strings **sp, unsigned n_gstrings, + unsigned n_strings) +{ + struct usb_gadget_string_container *uc; + struct usb_gadget_strings **gs_array; + struct usb_gadget_strings *gs; + struct usb_string *s; + unsigned mem; + unsigned n_gs; + unsigned n_s; + void *stash; + + mem = sizeof(*uc); + mem += sizeof(void *) * (n_gstrings + 1); + mem += sizeof(struct usb_gadget_strings) * n_gstrings; + mem += sizeof(struct usb_string) * (n_strings + 1) * (n_gstrings); + uc = kmalloc(mem, GFP_KERNEL); + if (!uc) + return ERR_PTR(-ENOMEM); + gs_array = get_containers_gs(uc); + stash = uc->stash; + stash += sizeof(void *) * (n_gstrings + 1); + for (n_gs = 0; n_gs < n_gstrings; n_gs++) { + struct usb_string *org_s; + + gs_array[n_gs] = stash; + gs = gs_array[n_gs]; + stash += sizeof(struct usb_gadget_strings); + gs->language = sp[n_gs]->language; + gs->strings = stash; + org_s = sp[n_gs]->strings; + + for (n_s = 0; n_s < n_strings; n_s++) { + s = stash; + stash += sizeof(struct usb_string); + if (org_s->s) + s->s = org_s->s; + else + s->s = ""; + org_s++; + } + s = stash; + s->s = NULL; + stash += sizeof(struct usb_string); + + } + gs_array[n_gs] = NULL; + return uc; +} + +/** + * usb_gstrings_attach() - attach gadget strings to a cdev and assign ids + * @cdev: the device whose string descriptor IDs are being allocated + * and attached. + * @sp: an array of usb_gadget_strings to attach. + * @n_strings: number of entries in each usb_strings array (sp[]->strings) + * + * This function will create a deep copy of usb_gadget_strings and usb_string + * and attach it to the cdev. The actual string (usb_string.s) will not be + * copied but only a referenced will be made. The struct usb_gadget_strings + * array may contain multiple languges and should be NULL terminated. + * The ->language pointer of each struct usb_gadget_strings has to contain the + * same amount of entries. + * For instance: sp[0] is en-US, sp[1] is es-ES. It is expected that the first + * usb_string entry of es-ES containts the translation of the first usb_string + * entry of en-US. Therefore both entries become the same id assign. + */ +struct usb_string *usb_gstrings_attach(struct usb_composite_dev *cdev, + struct usb_gadget_strings **sp, unsigned n_strings) +{ + struct usb_gadget_string_container *uc; + struct usb_gadget_strings **n_gs; + unsigned n_gstrings = 0; + unsigned i; + int ret; + + for (i = 0; sp[i]; i++) + n_gstrings++; + + if (!n_gstrings) + return ERR_PTR(-EINVAL); + + uc = copy_gadget_strings(sp, n_gstrings, n_strings); + if (IS_ERR(uc)) + return ERR_PTR(PTR_ERR(uc)); + + n_gs = get_containers_gs(uc); + ret = usb_string_ids_tab(cdev, n_gs[0]->strings); + if (ret) + goto err; + + for (i = 1; i < n_gstrings; i++) { + struct usb_string *m_s; + struct usb_string *s; + unsigned n; + + m_s = n_gs[0]->strings; + s = n_gs[i]->strings; + for (n = 0; n < n_strings; n++) { + s->id = m_s->id; + s++; + m_s++; + } + } + list_add_tail(&uc->list, &cdev->gstrings); + return n_gs[0]->strings; +err: + kfree(uc); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(usb_gstrings_attach); + +/** + * usb_string_ids_n() - allocate unused string IDs in batch + * @c: the device whose string descriptor IDs are being allocated + * @n: number of string IDs to allocate + * Context: single threaded during gadget setup + * + * Returns the first requested ID. This ID and next @n-1 IDs are now + * valid IDs. At least provided that @n is non-zero because if it + * is, returns last requested ID which is now very useful information. + * + * @usb_string_ids_n() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then store that ID in the appropriate descriptors and string table. + * + * All string identifier should be allocated using this, + * @usb_string_id() or @usb_string_ids_n() routine, to ensure that for + * example different functions don't wrongly assign different meanings + * to the same identifier. + */ +int usb_string_ids_n(struct usb_composite_dev *c, unsigned n) +{ + unsigned next = c->next_string_id; + if (unlikely(n > 254 || (unsigned)next + n > 254)) + return -ENODEV; + c->next_string_id += n; + return next + 1; +} +EXPORT_SYMBOL_GPL(usb_string_ids_n); + +/*-------------------------------------------------------------------------*/ + +static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (req->status || req->actual != req->length) + DBG((struct usb_composite_dev *) ep->driver_data, + "setup complete --> %d, %d/%d\n", + req->status, req->actual, req->length); +} + +/* + * The setup() callback implements all the ep0 functionality that's + * not handled lower down, in hardware or the hardware driver(like + * device and endpoint feature flags, and their status). It's all + * housekeeping for the gadget function we're implementing. Most of + * the work is in config and function specific setup. + */ +int +composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + int status = 0; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u8 intf = w_index & 0xFF; + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + struct usb_function *f = NULL; + u8 endp; + + /* partial re-init of the response message; the function or the + * gadget might need to intercept e.g. a control-OUT completion + * when we delegate to it. + */ + req->zero = 0; + req->complete = composite_setup_complete; + req->length = 0; + gadget->ep0->driver_data = cdev; + + switch (ctrl->bRequest) { + + /* we handle all standard USB descriptors */ + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + cdev->desc.bNumConfigurations = + count_configs(cdev, USB_DT_DEVICE); + cdev->desc.bMaxPacketSize0 = + cdev->gadget->ep0->maxpacket; + if (gadget_is_superspeed(gadget)) { + if (gadget->speed >= USB_SPEED_SUPER) { + cdev->desc.bcdUSB = cpu_to_le16(0x0300); + cdev->desc.bMaxPacketSize0 = 9; + } else { + cdev->desc.bcdUSB = cpu_to_le16(0x0210); + } + } + + value = min(w_length, (u16) sizeof cdev->desc); + memcpy(req->buf, &cdev->desc, value); + break; + case USB_DT_DEVICE_QUALIFIER: + if (!gadget_is_dualspeed(gadget) || + gadget->speed >= USB_SPEED_SUPER) + break; + device_qual(cdev); + value = min_t(int, w_length, + sizeof(struct usb_qualifier_descriptor)); + break; + case USB_DT_OTHER_SPEED_CONFIG: + if (!gadget_is_dualspeed(gadget) || + gadget->speed >= USB_SPEED_SUPER) + break; + /* FALLTHROUGH */ + case USB_DT_CONFIG: + value = config_desc(cdev, w_value); + if (value >= 0) + value = min(w_length, (u16) value); + break; + case USB_DT_STRING: + value = get_string(cdev, req->buf, + w_index, w_value & 0xff); + if (value >= 0) + value = min(w_length, (u16) value); + break; + case USB_DT_BOS: + if (gadget_is_superspeed(gadget)) { + value = bos_desc(cdev); + value = min(w_length, (u16) value); + } + break; + } + break; + + /* any number of configs can work */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != 0) + goto unknown; + if (gadget_is_otg(gadget)) { + if (gadget->a_hnp_support) + DBG(cdev, "HNP available\n"); + else if (gadget->a_alt_hnp_support) + DBG(cdev, "HNP on another port\n"); + else + VDBG(cdev, "HNP inactive\n"); + } + spin_lock(&cdev->lock); + value = set_config(cdev, ctrl, w_value); + spin_unlock(&cdev->lock); + break; + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + if (cdev->config) + *(u8 *)req->buf = cdev->config->bConfigurationValue; + else + *(u8 *)req->buf = 0; + value = min(w_length, (u16) 1); + break; + + /* function drivers must handle get/set altsetting; if there's + * no get() method, we know only altsetting zero works. + */ + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType != USB_RECIP_INTERFACE) + goto unknown; + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + if (w_value && !f->set_alt) + break; + value = f->set_alt(f, w_index, w_value); + if (value == USB_GADGET_DELAYED_STATUS) { + DBG(cdev, + "%s: interface %d (%s) requested delayed status\n", + __func__, intf, f->name); + cdev->delayed_status++; + DBG(cdev, "delayed_status count %d\n", + cdev->delayed_status); + } + break; + case USB_REQ_GET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto unknown; + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + /* lots of interfaces only need altsetting zero... */ + value = f->get_alt ? f->get_alt(f, w_index) : 0; + if (value < 0) + break; + *((u8 *)req->buf) = value; + value = min(w_length, (u16) 1); + break; + + /* + * USB 3.0 additions: + * Function driver should handle get_status request. If such cb + * wasn't supplied we respond with default value = 0 + * Note: function driver should supply such cb only for the first + * interface of the function + */ + case USB_REQ_GET_STATUS: + if (!gadget_is_superspeed(gadget)) + goto unknown; + if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE)) + goto unknown; + value = 2; /* This is the length of the get_status reply */ + put_unaligned_le16(0, req->buf); + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + status = f->get_status ? f->get_status(f) : 0; + if (status < 0) + break; + put_unaligned_le16(status & 0x0000ffff, req->buf); + break; + /* + * Function drivers should handle SetFeature/ClearFeature + * (FUNCTION_SUSPEND) request. function_suspend cb should be supplied + * only for the first interface of the function + */ + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + if (!gadget_is_superspeed(gadget)) + goto unknown; + if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE)) + goto unknown; + switch (w_value) { + case USB_INTRF_FUNC_SUSPEND: + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + value = 0; + if (f->func_suspend) + value = f->func_suspend(f, w_index >> 8); + if (value < 0) { + ERROR(cdev, + "func_suspend() returned error %d\n", + value); + value = 0; + } + break; + } + break; + default: +unknown: + VDBG(cdev, + "non-core control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* functions always handle their interfaces and endpoints... + * punt other recipients (other, WUSB, ...) to the current + * configuration code. + * + * REVISIT it could make sense to let the composite device + * take such requests too, if that's ever needed: to work + * in config 0, etc. + */ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + break; + + case USB_RECIP_ENDPOINT: + endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f); + list_for_each_entry(f, &cdev->config->functions, list) { + if (test_bit(endp, f->endpoints)) + break; + } + if (&f->list == &cdev->config->functions) + f = NULL; + break; + } + + if (f && f->setup) + value = f->setup(f, ctrl); + else { + struct usb_configuration *c; + + c = cdev->config; + if (c && c->setup) + value = c->setup(c, ctrl); + } + + goto done; + } + + /* respond with data transfer before status phase? */ + if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) { + req->length = value; + req->zero = value < w_length; + value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + DBG(cdev, "ep_queue --> %d\n", value); + req->status = 0; + composite_setup_complete(gadget->ep0, req); + } + } else if (value == USB_GADGET_DELAYED_STATUS && w_length != 0) { + WARN(cdev, + "%s: Delayed status not supported for w_length != 0", + __func__); + } + +done: + /* device either stalls (value < 0) or reports success */ + return value; +} + +void composite_disconnect(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + unsigned long flags; + + /* REVISIT: should we have config and device level + * disconnect callbacks? + */ + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + reset_config(cdev); + if (cdev->driver->disconnect) + cdev->driver->disconnect(cdev); + if(cdev->delayed_status !=0) + { + INFO(cdev,"delayed status mismatch .. resetting\n"); + cdev->delayed_status = 0; + } + + spin_unlock_irqrestore(&cdev->lock, flags); +} + +/*-------------------------------------------------------------------------*/ + +static ssize_t composite_show_suspended(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_composite_dev *cdev = get_gadget_data(gadget); + + return sprintf(buf, "%d\n", cdev->suspended); +} + +static DEVICE_ATTR(suspended, 0444, composite_show_suspended, NULL); + +static void __composite_unbind(struct usb_gadget *gadget, bool unbind_driver) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + + /* composite_disconnect() must already have been called + * by the underlying peripheral controller driver! + * so there's no i/o concurrency that could affect the + * state protected by cdev->lock. + */ + WARN_ON(cdev->config); + + while (!list_empty(&cdev->configs)) { + struct usb_configuration *c; + c = list_first_entry(&cdev->configs, + struct usb_configuration, list); + list_del(&c->list); + unbind_config(cdev, c); + } + if (cdev->driver->unbind && unbind_driver) + cdev->driver->unbind(cdev); + + composite_dev_cleanup(cdev); + + kfree(cdev->def_manufacturer); + kfree(cdev); + set_gadget_data(gadget, NULL); +} + +static void composite_unbind(struct usb_gadget *gadget) +{ + __composite_unbind(gadget, true); +} + +static void update_unchanged_dev_desc(struct usb_device_descriptor *new, + const struct usb_device_descriptor *old) +{ + __le16 idVendor; + __le16 idProduct; + __le16 bcdDevice; + u8 iSerialNumber; + u8 iManufacturer; + u8 iProduct; + + /* + * these variables may have been set in + * usb_composite_overwrite_options() + */ + idVendor = new->idVendor; + idProduct = new->idProduct; + bcdDevice = new->bcdDevice; + iSerialNumber = new->iSerialNumber; + iManufacturer = new->iManufacturer; + iProduct = new->iProduct; + + *new = *old; + if (idVendor) + new->idVendor = idVendor; + if (idProduct) + new->idProduct = idProduct; + if (bcdDevice) + new->bcdDevice = bcdDevice; + else + new->bcdDevice = cpu_to_le16(get_default_bcdDevice()); + if (iSerialNumber) + new->iSerialNumber = iSerialNumber; + if (iManufacturer) + new->iManufacturer = iManufacturer; + if (iProduct) + new->iProduct = iProduct; +} + +int composite_dev_prepare(struct usb_composite_driver *composite, + struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int ret = -ENOMEM; + + /* preallocate control response and buffer */ + cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!cdev->req) + return -ENOMEM; + + cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL); + if (!cdev->req->buf) + goto fail; + + ret = device_create_file(&gadget->dev, &dev_attr_suspended); + if (ret) + goto fail_dev; + + cdev->req->complete = composite_setup_complete; + gadget->ep0->driver_data = cdev; + + cdev->driver = composite; + + /* + * As per USB compliance update, a device that is actively drawing + * more than 100mA from USB must report itself as bus-powered in + * the GetStatus(DEVICE) call. + */ + if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW) + usb_gadget_set_selfpowered(gadget); + + /* interface and string IDs start at zero via kzalloc. + * we force endpoints to start unassigned; few controller + * drivers will zero ep->driver_data. + */ + usb_ep_autoconfig_reset(gadget); + return 0; +fail_dev: + kfree(cdev->req->buf); +fail: + usb_ep_free_request(gadget->ep0, cdev->req); + cdev->req = NULL; + return ret; +} + +void composite_dev_cleanup(struct usb_composite_dev *cdev) +{ + struct usb_gadget_string_container *uc, *tmp; + + list_for_each_entry_safe(uc, tmp, &cdev->gstrings, list) { + list_del(&uc->list); + kfree(uc); + } + if (cdev->req) { + kfree(cdev->req->buf); + usb_ep_free_request(cdev->gadget->ep0, cdev->req); + } + cdev->next_string_id = 0; + device_remove_file(&cdev->gadget->dev, &dev_attr_suspended); +} + +static int composite_bind(struct usb_gadget *gadget, + struct usb_gadget_driver *gdriver) +{ + struct usb_composite_dev *cdev; + struct usb_composite_driver *composite = to_cdriver(gdriver); + int status = -ENOMEM; + + cdev = kzalloc(sizeof *cdev, GFP_KERNEL); + if (!cdev) + return status; + + spin_lock_init(&cdev->lock); + cdev->gadget = gadget; + set_gadget_data(gadget, cdev); + INIT_LIST_HEAD(&cdev->configs); + INIT_LIST_HEAD(&cdev->gstrings); + + status = composite_dev_prepare(composite, cdev); + if (status) + goto fail; + + /* composite gadget needs to assign strings for whole device (like + * serial number), register function drivers, potentially update + * power state and consumption, etc + */ + status = composite->bind(cdev); + if (status < 0) + goto fail; + + update_unchanged_dev_desc(&cdev->desc, composite->dev); + + /* has userspace failed to provide a serial number? */ + if (composite->needs_serial && !cdev->desc.iSerialNumber) + WARNING(cdev, "userspace failed to provide iSerialNumber\n"); + + INFO(cdev, "%s ready\n", composite->name); + return 0; + +fail: + __composite_unbind(gadget, false); + return status; +} + +/*-------------------------------------------------------------------------*/ + +static void +composite_suspend(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_function *f; + + /* REVISIT: should we have config level + * suspend/resume callbacks? + */ + DBG(cdev, "suspend\n"); + if (cdev->config) { + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->suspend) + f->suspend(f); + } + } + if (cdev->driver->suspend) + cdev->driver->suspend(cdev); + + cdev->suspended = 1; + + usb_gadget_vbus_draw(gadget, 2); +} + +static void +composite_resume(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_function *f; + u8 maxpower; + + /* REVISIT: should we have config level + * suspend/resume callbacks? + */ + DBG(cdev, "resume\n"); + if (cdev->driver->resume) + cdev->driver->resume(cdev); + if (cdev->config) { + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->resume) + f->resume(f); + } + + maxpower = cdev->config->MaxPower; + + usb_gadget_vbus_draw(gadget, maxpower ? + maxpower : CONFIG_USB_GADGET_VBUS_DRAW); + } + + cdev->suspended = 0; +} + +/*-------------------------------------------------------------------------*/ + +static const struct usb_gadget_driver composite_driver_template = { + .bind = composite_bind, + .unbind = composite_unbind, + + .setup = composite_setup, + .disconnect = composite_disconnect, + + .suspend = composite_suspend, + .resume = composite_resume, + + .driver = { + .owner = THIS_MODULE, + }, +}; + +/** + * usb_composite_probe() - register a composite driver + * @driver: the driver to register + * + * Context: single threaded during gadget setup + * + * This function is used to register drivers using the composite driver + * framework. The return value is zero, or a negative errno value. + * Those values normally come from the driver's @bind method, which does + * all the work of setting up the driver to match the hardware. + * + * On successful return, the gadget is ready to respond to requests from + * the host, unless one of its components invokes usb_gadget_disconnect() + * while it was binding. That would usually be done in order to wait for + * some userspace participation. + */ +int usb_composite_probe(struct usb_composite_driver *driver) +{ + struct usb_gadget_driver *gadget_driver; + + if (!driver || !driver->dev || !driver->bind) + return -EINVAL; + + if (!driver->name) + driver->name = "composite"; + + driver->gadget_driver = composite_driver_template; + gadget_driver = &driver->gadget_driver; + + gadget_driver->function = (char *) driver->name; + gadget_driver->driver.name = driver->name; + gadget_driver->max_speed = driver->max_speed; + + return usb_gadget_probe_driver(gadget_driver); +} +EXPORT_SYMBOL_GPL(usb_composite_probe); + +/** + * usb_composite_unregister() - unregister a composite driver + * @driver: the driver to unregister + * + * This function is used to unregister drivers using the composite + * driver framework. + */ +void usb_composite_unregister(struct usb_composite_driver *driver) +{ + usb_gadget_unregister_driver(&driver->gadget_driver); +} +EXPORT_SYMBOL_GPL(usb_composite_unregister); + +/** + * usb_composite_setup_continue() - Continue with the control transfer + * @cdev: the composite device who's control transfer was kept waiting + * + * This function must be called by the USB function driver to continue + * with the control transfer's data/status stage in case it had requested to + * delay the data/status stages. A USB function's setup handler (e.g. set_alt()) + * can request the composite framework to delay the setup request's data/status + * stages by returning USB_GADGET_DELAYED_STATUS. + */ +void usb_composite_setup_continue(struct usb_composite_dev *cdev) +{ + int value; + struct usb_request *req = cdev->req; + unsigned long flags; + + DBG(cdev, "%s\n", __func__); + spin_lock_irqsave(&cdev->lock, flags); + + if (cdev->delayed_status == 0) { + WARN(cdev, "%s: Unexpected call\n", __func__); + + } else if (--cdev->delayed_status == 0) { + DBG(cdev, "%s: Completing delayed status\n", __func__); + req->length = 0; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + DBG(cdev, "ep_queue --> %d\n", value); + req->status = 0; + composite_setup_complete(cdev->gadget->ep0, req); + } + } + + spin_unlock_irqrestore(&cdev->lock, flags); +} +EXPORT_SYMBOL_GPL(usb_composite_setup_continue); + +static char *composite_default_mfr(struct usb_gadget *gadget) +{ + char *mfr; + int len; + + len = snprintf(NULL, 0, "%s %s with %s", init_utsname()->sysname, + init_utsname()->release, gadget->name); + len++; + mfr = kmalloc(len, GFP_KERNEL); + if (!mfr) + return NULL; + snprintf(mfr, len, "%s %s with %s", init_utsname()->sysname, + init_utsname()->release, gadget->name); + return mfr; +} + +void usb_composite_overwrite_options(struct usb_composite_dev *cdev, + struct usb_composite_overwrite *covr) +{ + struct usb_device_descriptor *desc = &cdev->desc; + struct usb_gadget_strings *gstr = cdev->driver->strings[0]; + struct usb_string *dev_str = gstr->strings; + + if (covr->idVendor) + desc->idVendor = cpu_to_le16(covr->idVendor); + + if (covr->idProduct) + desc->idProduct = cpu_to_le16(covr->idProduct); + + if (covr->bcdDevice) + desc->bcdDevice = cpu_to_le16(covr->bcdDevice); + + if (covr->serial_number) { + desc->iSerialNumber = dev_str[USB_GADGET_SERIAL_IDX].id; + dev_str[USB_GADGET_SERIAL_IDX].s = covr->serial_number; + } + if (covr->manufacturer) { + desc->iManufacturer = dev_str[USB_GADGET_MANUFACTURER_IDX].id; + dev_str[USB_GADGET_MANUFACTURER_IDX].s = covr->manufacturer; + + } else if (!strlen(dev_str[USB_GADGET_MANUFACTURER_IDX].s)) { + desc->iManufacturer = dev_str[USB_GADGET_MANUFACTURER_IDX].id; + cdev->def_manufacturer = composite_default_mfr(cdev->gadget); + dev_str[USB_GADGET_MANUFACTURER_IDX].s = cdev->def_manufacturer; + } + + if (covr->product) { + desc->iProduct = dev_str[USB_GADGET_PRODUCT_IDX].id; + dev_str[USB_GADGET_PRODUCT_IDX].s = covr->product; + } +} +EXPORT_SYMBOL_GPL(usb_composite_overwrite_options); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/config.c b/drivers/usb/gadget/config.c new file mode 100644 index 00000000..34e12fc5 --- /dev/null +++ b/drivers/usb/gadget/config.c @@ -0,0 +1,197 @@ +/* + * usb/gadget/config.c -- simplify building config descriptors + * + * Copyright (C) 2003 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * usb_descriptor_fillbuf - fill buffer with descriptors + * @buf: Buffer to be filled + * @buflen: Size of buf + * @src: Array of descriptor pointers, terminated by null pointer. + * + * Copies descriptors into the buffer, returning the length or a + * negative error code if they can't all be copied. Useful when + * assembling descriptors for an associated set of interfaces used + * as part of configuring a composite device; or in other cases where + * sets of descriptors need to be marshaled. + */ +int +usb_descriptor_fillbuf(void *buf, unsigned buflen, + const struct usb_descriptor_header **src) +{ + u8 *dest = buf; + + if (!src) + return -EINVAL; + + /* fill buffer from src[] until null descriptor ptr */ + for (; NULL != *src; src++) { + unsigned len = (*src)->bLength; + + if (len > buflen) + return -EINVAL; + memcpy(dest, *src, len); + buflen -= len; + dest += len; + } + return dest - (u8 *)buf; +} +EXPORT_SYMBOL_GPL(usb_descriptor_fillbuf); + +/** + * usb_gadget_config_buf - builts a complete configuration descriptor + * @config: Header for the descriptor, including characteristics such + * as power requirements and number of interfaces. + * @desc: Null-terminated vector of pointers to the descriptors (interface, + * endpoint, etc) defining all functions in this device configuration. + * @buf: Buffer for the resulting configuration descriptor. + * @length: Length of buffer. If this is not big enough to hold the + * entire configuration descriptor, an error code will be returned. + * + * This copies descriptors into the response buffer, building a descriptor + * for that configuration. It returns the buffer length or a negative + * status code. The config.wTotalLength field is set to match the length + * of the result, but other descriptor fields (including power usage and + * interface count) must be set by the caller. + * + * Gadget drivers could use this when constructing a config descriptor + * in response to USB_REQ_GET_DESCRIPTOR. They will need to patch the + * resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed. + */ +int usb_gadget_config_buf( + const struct usb_config_descriptor *config, + void *buf, + unsigned length, + const struct usb_descriptor_header **desc +) +{ + struct usb_config_descriptor *cp = buf; + int len; + + /* config descriptor first */ + if (length < USB_DT_CONFIG_SIZE || !desc) + return -EINVAL; + *cp = *config; + + /* then interface/endpoint/class/vendor/... */ + len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8*)buf, + length - USB_DT_CONFIG_SIZE, desc); + if (len < 0) + return len; + len += USB_DT_CONFIG_SIZE; + if (len > 0xffff) + return -EINVAL; + + /* patch up the config descriptor */ + cp->bLength = USB_DT_CONFIG_SIZE; + cp->bDescriptorType = USB_DT_CONFIG; + cp->wTotalLength = cpu_to_le16(len); + cp->bmAttributes |= USB_CONFIG_ATT_ONE; + return len; +} +EXPORT_SYMBOL_GPL(usb_gadget_config_buf); + +/** + * usb_copy_descriptors - copy a vector of USB descriptors + * @src: null-terminated vector to copy + * Context: initialization code, which may sleep + * + * This makes a copy of a vector of USB descriptors. Its primary use + * is to support usb_function objects which can have multiple copies, + * each needing different descriptors. Functions may have static + * tables of descriptors, which are used as templates and customized + * with identifiers (for interfaces, strings, endpoints, and more) + * as needed by a given function instance. + */ +struct usb_descriptor_header ** +usb_copy_descriptors(struct usb_descriptor_header **src) +{ + struct usb_descriptor_header **tmp; + unsigned bytes; + unsigned n_desc; + void *mem; + struct usb_descriptor_header **ret; + + /* count descriptors and their sizes; then add vector size */ + for (bytes = 0, n_desc = 0, tmp = src; *tmp; tmp++, n_desc++) + bytes += (*tmp)->bLength; + bytes += (n_desc + 1) * sizeof(*tmp); + + mem = kmalloc(bytes, GFP_KERNEL); + if (!mem) + return NULL; + + /* fill in pointers starting at "tmp", + * to descriptors copied starting at "mem"; + * and return "ret" + */ + tmp = mem; + ret = mem; + mem += (n_desc + 1) * sizeof(*tmp); + while (*src) { + memcpy(mem, *src, (*src)->bLength); + *tmp = mem; + tmp++; + mem += (*src)->bLength; + src++; + } + *tmp = NULL; + + return ret; +} +EXPORT_SYMBOL_GPL(usb_copy_descriptors); + +int usb_assign_descriptors(struct usb_function *f, + struct usb_descriptor_header **fs, + struct usb_descriptor_header **hs, + struct usb_descriptor_header **ss) +{ + struct usb_gadget *g = f->config->cdev->gadget; + + if (fs) { + f->fs_descriptors = usb_copy_descriptors(fs); + if (!f->fs_descriptors) + goto err; + } + if (hs && gadget_is_dualspeed(g)) { + f->hs_descriptors = usb_copy_descriptors(hs); + if (!f->hs_descriptors) + goto err; + } + if (ss && gadget_is_superspeed(g)) { + f->ss_descriptors = usb_copy_descriptors(ss); + if (!f->ss_descriptors) + goto err; + } + return 0; +err: + usb_free_all_descriptors(f); + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(usb_assign_descriptors); + +void usb_free_all_descriptors(struct usb_function *f) +{ + usb_free_descriptors(f->fs_descriptors); + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->ss_descriptors); +} +EXPORT_SYMBOL_GPL(usb_free_all_descriptors); diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c new file mode 100644 index 00000000..80e7f75a --- /dev/null +++ b/drivers/usb/gadget/configfs.c @@ -0,0 +1,1007 @@ +#include +#include +#include +#include +#include +#include + +int check_user_usb_string(const char *name, + struct usb_gadget_strings *stringtab_dev) +{ + unsigned primary_lang; + unsigned sub_lang; + u16 num; + int ret; + + ret = kstrtou16(name, 0, &num); + if (ret) + return ret; + + primary_lang = num & 0x3ff; + sub_lang = num >> 10; + + /* simple sanity check for valid langid */ + switch (primary_lang) { + case 0: + case 0x62 ... 0xfe: + case 0x100 ... 0x3ff: + return -EINVAL; + } + if (!sub_lang) + return -EINVAL; + + stringtab_dev->language = num; + return 0; +} + +#define MAX_NAME_LEN 40 +#define MAX_USB_STRING_LANGS 2 + +struct gadget_info { + struct config_group group; + struct config_group functions_group; + struct config_group configs_group; + struct config_group strings_group; + struct config_group *default_groups[4]; + + struct mutex lock; + struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1]; + struct list_head string_list; + struct list_head available_func; + + const char *udc_name; +#ifdef CONFIG_USB_OTG + struct usb_otg_descriptor otg; +#endif + struct usb_composite_driver composite; + struct usb_composite_dev cdev; +}; + +struct config_usb_cfg { + struct config_group group; + struct config_group strings_group; + struct config_group *default_groups[2]; + struct list_head string_list; + struct usb_configuration c; + struct list_head func_list; + struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1]; +}; + +struct gadget_strings { + struct usb_gadget_strings stringtab_dev; + struct usb_string strings[USB_GADGET_FIRST_AVAIL_IDX]; + char *manufacturer; + char *product; + char *serialnumber; + + struct config_group group; + struct list_head list; +}; + +struct gadget_config_name { + struct usb_gadget_strings stringtab_dev; + struct usb_string strings; + char *configuration; + + struct config_group group; + struct list_head list; +}; + +static int usb_string_copy(const char *s, char **s_copy) +{ + int ret; + char *str; + char *copy = *s_copy; + ret = strlen(s); + if (ret > 126) + return -EOVERFLOW; + + str = kstrdup(s, GFP_KERNEL); + if (!str) + return -ENOMEM; + if (str[ret - 1] == '\n') + str[ret - 1] = '\0'; + kfree(copy); + *s_copy = str; + return 0; +} + +CONFIGFS_ATTR_STRUCT(gadget_info); +CONFIGFS_ATTR_STRUCT(config_usb_cfg); + +#define GI_DEVICE_DESC_ITEM_ATTR(name) \ + static struct gadget_info_attribute gadget_cdev_desc_##name = \ + __CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, \ + gadget_dev_desc_##name##_show, \ + gadget_dev_desc_##name##_store) + +#define GI_DEVICE_DESC_SIMPLE_R_u8(__name) \ + static ssize_t gadget_dev_desc_##__name##_show(struct gadget_info *gi, \ + char *page) \ +{ \ + return sprintf(page, "0x%02x\n", gi->cdev.desc.__name); \ +} + +#define GI_DEVICE_DESC_SIMPLE_R_u16(__name) \ + static ssize_t gadget_dev_desc_##__name##_show(struct gadget_info *gi, \ + char *page) \ +{ \ + return sprintf(page, "0x%04x\n", le16_to_cpup(&gi->cdev.desc.__name)); \ +} + + +#define GI_DEVICE_DESC_SIMPLE_W_u8(_name) \ + static ssize_t gadget_dev_desc_##_name##_store(struct gadget_info *gi, \ + const char *page, size_t len) \ +{ \ + u8 val; \ + int ret; \ + ret = kstrtou8(page, 0, &val); \ + if (ret) \ + return ret; \ + gi->cdev.desc._name = val; \ + return len; \ +} + +#define GI_DEVICE_DESC_SIMPLE_W_u16(_name) \ + static ssize_t gadget_dev_desc_##_name##_store(struct gadget_info *gi, \ + const char *page, size_t len) \ +{ \ + u16 val; \ + int ret; \ + ret = kstrtou16(page, 0, &val); \ + if (ret) \ + return ret; \ + gi->cdev.desc._name = cpu_to_le16p(&val); \ + return len; \ +} + +#define GI_DEVICE_DESC_SIMPLE_RW(_name, _type) \ + GI_DEVICE_DESC_SIMPLE_R_##_type(_name) \ + GI_DEVICE_DESC_SIMPLE_W_##_type(_name) + +GI_DEVICE_DESC_SIMPLE_R_u16(bcdUSB); +GI_DEVICE_DESC_SIMPLE_RW(bDeviceClass, u8); +GI_DEVICE_DESC_SIMPLE_RW(bDeviceSubClass, u8); +GI_DEVICE_DESC_SIMPLE_RW(bDeviceProtocol, u8); +GI_DEVICE_DESC_SIMPLE_RW(bMaxPacketSize0, u8); +GI_DEVICE_DESC_SIMPLE_RW(idVendor, u16); +GI_DEVICE_DESC_SIMPLE_RW(idProduct, u16); +GI_DEVICE_DESC_SIMPLE_R_u16(bcdDevice); + +static ssize_t is_valid_bcd(u16 bcd_val) +{ + if ((bcd_val & 0xf) > 9) + return -EINVAL; + if (((bcd_val >> 4) & 0xf) > 9) + return -EINVAL; + if (((bcd_val >> 8) & 0xf) > 9) + return -EINVAL; + if (((bcd_val >> 12) & 0xf) > 9) + return -EINVAL; + return 0; +} + +static ssize_t gadget_dev_desc_bcdDevice_store(struct gadget_info *gi, + const char *page, size_t len) +{ + u16 bcdDevice; + int ret; + + ret = kstrtou16(page, 0, &bcdDevice); + if (ret) + return ret; + ret = is_valid_bcd(bcdDevice); + if (ret) + return ret; + + gi->cdev.desc.bcdDevice = cpu_to_le16(bcdDevice); + return len; +} + +static ssize_t gadget_dev_desc_bcdUSB_store(struct gadget_info *gi, + const char *page, size_t len) +{ + u16 bcdUSB; + int ret; + + ret = kstrtou16(page, 0, &bcdUSB); + if (ret) + return ret; + ret = is_valid_bcd(bcdUSB); + if (ret) + return ret; + + gi->cdev.desc.bcdUSB = cpu_to_le16(bcdUSB); + return len; +} + +static ssize_t gadget_dev_desc_UDC_show(struct gadget_info *gi, char *page) +{ + return sprintf(page, "%s\n", gi->udc_name ?: ""); +} + +static int unregister_gadget(struct gadget_info *gi) +{ + int ret; + + if (!gi->udc_name) + return -ENODEV; + + ret = usb_gadget_unregister_driver(&gi->composite.gadget_driver); + if (ret) + return ret; + kfree(gi->udc_name); + gi->udc_name = NULL; + return 0; +} + +static ssize_t gadget_dev_desc_UDC_store(struct gadget_info *gi, + const char *page, size_t len) +{ + char *name; + int ret; + + name = kstrdup(page, GFP_KERNEL); + if (!name) + return -ENOMEM; + if (name[len - 1] == '\n') + name[len - 1] = '\0'; + + mutex_lock(&gi->lock); + + if (!strlen(name)) { + ret = unregister_gadget(gi); + if (ret) + goto err; + } else { + if (gi->udc_name) { + ret = -EBUSY; + goto err; + } + ret = udc_attach_driver(name, &gi->composite.gadget_driver); + if (ret) + goto err; + gi->udc_name = name; + } + mutex_unlock(&gi->lock); + return len; +err: + kfree(name); + mutex_unlock(&gi->lock); + return ret; +} + +GI_DEVICE_DESC_ITEM_ATTR(bDeviceClass); +GI_DEVICE_DESC_ITEM_ATTR(bDeviceSubClass); +GI_DEVICE_DESC_ITEM_ATTR(bDeviceProtocol); +GI_DEVICE_DESC_ITEM_ATTR(bMaxPacketSize0); +GI_DEVICE_DESC_ITEM_ATTR(idVendor); +GI_DEVICE_DESC_ITEM_ATTR(idProduct); +GI_DEVICE_DESC_ITEM_ATTR(bcdDevice); +GI_DEVICE_DESC_ITEM_ATTR(bcdUSB); +GI_DEVICE_DESC_ITEM_ATTR(UDC); + +static struct configfs_attribute *gadget_root_attrs[] = { + &gadget_cdev_desc_bDeviceClass.attr, + &gadget_cdev_desc_bDeviceSubClass.attr, + &gadget_cdev_desc_bDeviceProtocol.attr, + &gadget_cdev_desc_bMaxPacketSize0.attr, + &gadget_cdev_desc_idVendor.attr, + &gadget_cdev_desc_idProduct.attr, + &gadget_cdev_desc_bcdDevice.attr, + &gadget_cdev_desc_bcdUSB.attr, + &gadget_cdev_desc_UDC.attr, + NULL, +}; + +static inline struct gadget_info *to_gadget_info(struct config_item *item) +{ + return container_of(to_config_group(item), struct gadget_info, group); +} + +static inline struct gadget_strings *to_gadget_strings(struct config_item *item) +{ + return container_of(to_config_group(item), struct gadget_strings, + group); +} + +static inline struct gadget_config_name *to_gadget_config_name( + struct config_item *item) +{ + return container_of(to_config_group(item), struct gadget_config_name, + group); +} + +static inline struct config_usb_cfg *to_config_usb_cfg(struct config_item *item) +{ + return container_of(to_config_group(item), struct config_usb_cfg, + group); +} + +static inline struct usb_function_instance *to_usb_function_instance( + struct config_item *item) +{ + return container_of(to_config_group(item), + struct usb_function_instance, group); +} + +static void gadget_info_attr_release(struct config_item *item) +{ + struct gadget_info *gi = to_gadget_info(item); + + WARN_ON(!list_empty(&gi->cdev.configs)); + WARN_ON(!list_empty(&gi->string_list)); + WARN_ON(!list_empty(&gi->available_func)); + kfree(gi->composite.gadget_driver.function); + kfree(gi); +} + +CONFIGFS_ATTR_OPS(gadget_info); + +static struct configfs_item_operations gadget_root_item_ops = { + .release = gadget_info_attr_release, + .show_attribute = gadget_info_attr_show, + .store_attribute = gadget_info_attr_store, +}; + +static void gadget_config_attr_release(struct config_item *item) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(item); + + WARN_ON(!list_empty(&cfg->c.functions)); + list_del(&cfg->c.list); + kfree(cfg->c.label); + kfree(cfg); +} + +static int config_usb_cfg_link( + struct config_item *usb_cfg_ci, + struct config_item *usb_func_ci) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci); + struct usb_composite_dev *cdev = cfg->c.cdev; + struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev); + + struct config_group *group = to_config_group(usb_func_ci); + struct usb_function_instance *fi = container_of(group, + struct usb_function_instance, group); + struct usb_function_instance *a_fi; + struct usb_function *f; + int ret; + + mutex_lock(&gi->lock); + /* + * Make sure this function is from within our _this_ gadget and not + * from another gadget or a random directory. + * Also a function instance can only be linked once. + */ + list_for_each_entry(a_fi, &gi->available_func, cfs_list) { + if (a_fi == fi) + break; + } + if (a_fi != fi) { + ret = -EINVAL; + goto out; + } + + list_for_each_entry(f, &cfg->func_list, list) { + if (f->fi == fi) { + ret = -EEXIST; + goto out; + } + } + + f = usb_get_function(fi); + if (IS_ERR(f)) { + ret = PTR_ERR(f); + goto out; + } + + /* stash the function until we bind it to the gadget */ + list_add_tail(&f->list, &cfg->func_list); + ret = 0; +out: + mutex_unlock(&gi->lock); + return ret; +} + +static int config_usb_cfg_unlink( + struct config_item *usb_cfg_ci, + struct config_item *usb_func_ci) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci); + struct usb_composite_dev *cdev = cfg->c.cdev; + struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev); + + struct config_group *group = to_config_group(usb_func_ci); + struct usb_function_instance *fi = container_of(group, + struct usb_function_instance, group); + struct usb_function *f; + + /* + * ideally I would like to forbid to unlink functions while a gadget is + * bound to an UDC. Since this isn't possible at the moment, we simply + * force an unbind, the function is available here and then we can + * remove the function. + */ + mutex_lock(&gi->lock); + if (gi->udc_name) + unregister_gadget(gi); + WARN_ON(gi->udc_name); + + list_for_each_entry(f, &cfg->func_list, list) { + if (f->fi == fi) { + list_del(&f->list); + usb_put_function(f); + mutex_unlock(&gi->lock); + return 0; + } + } + mutex_unlock(&gi->lock); + WARN(1, "Unable to locate function to unbind\n"); + return 0; +} + +CONFIGFS_ATTR_OPS(config_usb_cfg); + +static struct configfs_item_operations gadget_config_item_ops = { + .release = gadget_config_attr_release, + .show_attribute = config_usb_cfg_attr_show, + .store_attribute = config_usb_cfg_attr_store, + .allow_link = config_usb_cfg_link, + .drop_link = config_usb_cfg_unlink, +}; + + +static ssize_t gadget_config_desc_MaxPower_show(struct config_usb_cfg *cfg, + char *page) +{ + return sprintf(page, "%u\n", cfg->c.MaxPower); +} + +static ssize_t gadget_config_desc_MaxPower_store(struct config_usb_cfg *cfg, + const char *page, size_t len) +{ + u16 val; + int ret; + ret = kstrtou16(page, 0, &val); + if (ret) + return ret; + if (DIV_ROUND_UP(val, 8) > 0xff) + return -ERANGE; + cfg->c.MaxPower = val; + return len; +} + +static ssize_t gadget_config_desc_bmAttributes_show(struct config_usb_cfg *cfg, + char *page) +{ + return sprintf(page, "0x%02x\n", cfg->c.bmAttributes); +} + +static ssize_t gadget_config_desc_bmAttributes_store(struct config_usb_cfg *cfg, + const char *page, size_t len) +{ + u8 val; + int ret; + ret = kstrtou8(page, 0, &val); + if (ret) + return ret; + if (!(val & USB_CONFIG_ATT_ONE)) + return -EINVAL; + if (val & ~(USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER | + USB_CONFIG_ATT_WAKEUP)) + return -EINVAL; + cfg->c.bmAttributes = val; + return len; +} + +#define CFG_CONFIG_DESC_ITEM_ATTR(name) \ + static struct config_usb_cfg_attribute gadget_usb_cfg_##name = \ + __CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, \ + gadget_config_desc_##name##_show, \ + gadget_config_desc_##name##_store) + +CFG_CONFIG_DESC_ITEM_ATTR(MaxPower); +CFG_CONFIG_DESC_ITEM_ATTR(bmAttributes); + +static struct configfs_attribute *gadget_config_attrs[] = { + &gadget_usb_cfg_MaxPower.attr, + &gadget_usb_cfg_bmAttributes.attr, + NULL, +}; + +static struct config_item_type gadget_config_type = { + .ct_item_ops = &gadget_config_item_ops, + .ct_attrs = gadget_config_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item_type gadget_root_type = { + .ct_item_ops = &gadget_root_item_ops, + .ct_attrs = gadget_root_attrs, + .ct_owner = THIS_MODULE, +}; + +static void composite_init_dev(struct usb_composite_dev *cdev) +{ + spin_lock_init(&cdev->lock); + INIT_LIST_HEAD(&cdev->configs); + INIT_LIST_HEAD(&cdev->gstrings); +} + +static struct config_group *function_make( + struct config_group *group, + const char *name) +{ + struct gadget_info *gi; + struct usb_function_instance *fi; + char buf[MAX_NAME_LEN]; + char *func_name; + char *instance_name; + int ret; + + ret = snprintf(buf, MAX_NAME_LEN, "%s", name); + if (ret >= MAX_NAME_LEN) + return ERR_PTR(-ENAMETOOLONG); + + func_name = buf; + instance_name = strchr(func_name, '.'); + if (!instance_name) { + pr_err("Unable to locate . in FUNC.INSTANCE\n"); + return ERR_PTR(-EINVAL); + } + *instance_name = '\0'; + instance_name++; + + fi = usb_get_function_instance(func_name); + if (IS_ERR(fi)) + return ERR_PTR(PTR_ERR(fi)); + + ret = config_item_set_name(&fi->group.cg_item, name); + if (ret) { + usb_put_function_instance(fi); + return ERR_PTR(ret); + } + + gi = container_of(group, struct gadget_info, functions_group); + + mutex_lock(&gi->lock); + list_add_tail(&fi->cfs_list, &gi->available_func); + mutex_unlock(&gi->lock); + return &fi->group; +} + +static void function_drop( + struct config_group *group, + struct config_item *item) +{ + struct usb_function_instance *fi = to_usb_function_instance(item); + struct gadget_info *gi; + + gi = container_of(group, struct gadget_info, functions_group); + + mutex_lock(&gi->lock); + list_del(&fi->cfs_list); + mutex_unlock(&gi->lock); + config_item_put(item); +} + +static struct configfs_group_operations functions_ops = { + .make_group = &function_make, + .drop_item = &function_drop, +}; + +static struct config_item_type functions_type = { + .ct_group_ops = &functions_ops, + .ct_owner = THIS_MODULE, +}; + +CONFIGFS_ATTR_STRUCT(gadget_config_name); +GS_STRINGS_RW(gadget_config_name, configuration); + +static struct configfs_attribute *gadget_config_name_langid_attrs[] = { + &gadget_config_name_configuration.attr, + NULL, +}; + +static void gadget_config_name_attr_release(struct config_item *item) +{ + struct gadget_config_name *cn = to_gadget_config_name(item); + + kfree(cn->configuration); + + list_del(&cn->list); + kfree(cn); +} + +USB_CONFIG_STRING_RW_OPS(gadget_config_name); +USB_CONFIG_STRINGS_LANG(gadget_config_name, config_usb_cfg); + +static struct config_group *config_desc_make( + struct config_group *group, + const char *name) +{ + struct gadget_info *gi; + struct config_usb_cfg *cfg; + char buf[MAX_NAME_LEN]; + char *num_str; + u8 num; + int ret; + + gi = container_of(group, struct gadget_info, configs_group); + ret = snprintf(buf, MAX_NAME_LEN, "%s", name); + if (ret >= MAX_NAME_LEN) + return ERR_PTR(-ENAMETOOLONG); + + num_str = strchr(buf, '.'); + if (!num_str) { + pr_err("Unable to locate . in name.bConfigurationValue\n"); + return ERR_PTR(-EINVAL); + } + + *num_str = '\0'; + num_str++; + + if (!strlen(buf)) + return ERR_PTR(-EINVAL); + + ret = kstrtou8(num_str, 0, &num); + if (ret) + return ERR_PTR(ret); + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return ERR_PTR(-ENOMEM); + cfg->c.label = kstrdup(buf, GFP_KERNEL); + if (!cfg->c.label) { + ret = -ENOMEM; + goto err; + } + cfg->c.bConfigurationValue = num; + cfg->c.MaxPower = CONFIG_USB_GADGET_VBUS_DRAW; + cfg->c.bmAttributes = USB_CONFIG_ATT_ONE; + INIT_LIST_HEAD(&cfg->string_list); + INIT_LIST_HEAD(&cfg->func_list); + + cfg->group.default_groups = cfg->default_groups; + cfg->default_groups[0] = &cfg->strings_group; + + config_group_init_type_name(&cfg->group, name, + &gadget_config_type); + config_group_init_type_name(&cfg->strings_group, "strings", + &gadget_config_name_strings_type); + + ret = usb_add_config_only(&gi->cdev, &cfg->c); + if (ret) + goto err; + + return &cfg->group; +err: + kfree(cfg->c.label); + kfree(cfg); + return ERR_PTR(ret); +} + +static void config_desc_drop( + struct config_group *group, + struct config_item *item) +{ + config_item_put(item); +} + +static struct configfs_group_operations config_desc_ops = { + .make_group = &config_desc_make, + .drop_item = &config_desc_drop, +}; + +static struct config_item_type config_desc_type = { + .ct_group_ops = &config_desc_ops, + .ct_owner = THIS_MODULE, +}; + +CONFIGFS_ATTR_STRUCT(gadget_strings); +GS_STRINGS_RW(gadget_strings, manufacturer); +GS_STRINGS_RW(gadget_strings, product); +GS_STRINGS_RW(gadget_strings, serialnumber); + +static struct configfs_attribute *gadget_strings_langid_attrs[] = { + &gadget_strings_manufacturer.attr, + &gadget_strings_product.attr, + &gadget_strings_serialnumber.attr, + NULL, +}; + +static void gadget_strings_attr_release(struct config_item *item) +{ + struct gadget_strings *gs = to_gadget_strings(item); + + kfree(gs->manufacturer); + kfree(gs->product); + kfree(gs->serialnumber); + + list_del(&gs->list); + kfree(gs); +} + +USB_CONFIG_STRING_RW_OPS(gadget_strings); +USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info); + +static int configfs_do_nothing(struct usb_composite_dev *cdev) +{ + WARN_ON(1); + return -EINVAL; +} + +int composite_dev_prepare(struct usb_composite_driver *composite, + struct usb_composite_dev *dev); + +static void purge_configs_funcs(struct gadget_info *gi) +{ + struct usb_configuration *c; + + list_for_each_entry(c, &gi->cdev.configs, list) { + struct usb_function *f, *tmp; + struct config_usb_cfg *cfg; + + cfg = container_of(c, struct config_usb_cfg, c); + + list_for_each_entry_safe(f, tmp, &c->functions, list) { + + list_move_tail(&f->list, &cfg->func_list); + if (f->unbind) { + dev_err(&gi->cdev.gadget->dev, "unbind function" + " '%s'/%p\n", f->name, f); + f->unbind(c, f); + } + } + c->next_interface_id = 0; + c->superspeed = 0; + c->highspeed = 0; + c->fullspeed = 0; + } +} + +static int configfs_composite_bind(struct usb_gadget *gadget, + struct usb_gadget_driver *gdriver) +{ + struct usb_composite_driver *composite = to_cdriver(gdriver); + struct gadget_info *gi = container_of(composite, + struct gadget_info, composite); + struct usb_composite_dev *cdev = &gi->cdev; + struct usb_configuration *c; + struct usb_string *s; + unsigned i; + int ret; + + /* the gi->lock is hold by the caller */ + cdev->gadget = gadget; + set_gadget_data(gadget, cdev); + ret = composite_dev_prepare(composite, cdev); + if (ret) + return ret; + /* and now the gadget bind */ + ret = -EINVAL; + + if (list_empty(&gi->cdev.configs)) { + pr_err("Need atleast one configuration in %s.\n", + gi->composite.name); + goto err_comp_cleanup; + } + + + list_for_each_entry(c, &gi->cdev.configs, list) { + struct config_usb_cfg *cfg; + + cfg = container_of(c, struct config_usb_cfg, c); + if (list_empty(&cfg->func_list)) { + pr_err("Config %s/%d of %s needs atleast one function.\n", + c->label, c->bConfigurationValue, + gi->composite.name); + goto err_comp_cleanup; + } + } + + /* init all strings */ + if (!list_empty(&gi->string_list)) { + struct gadget_strings *gs; + + i = 0; + list_for_each_entry(gs, &gi->string_list, list) { + + gi->gstrings[i] = &gs->stringtab_dev; + gs->stringtab_dev.strings = gs->strings; + gs->strings[USB_GADGET_MANUFACTURER_IDX].s = + gs->manufacturer; + gs->strings[USB_GADGET_PRODUCT_IDX].s = gs->product; + gs->strings[USB_GADGET_SERIAL_IDX].s = gs->serialnumber; + i++; + } + gi->gstrings[i] = NULL; + s = usb_gstrings_attach(&gi->cdev, gi->gstrings, + USB_GADGET_FIRST_AVAIL_IDX); + if (IS_ERR(s)) { + ret = PTR_ERR(s); + goto err_comp_cleanup; + } + + gi->cdev.desc.iManufacturer = s[USB_GADGET_MANUFACTURER_IDX].id; + gi->cdev.desc.iProduct = s[USB_GADGET_PRODUCT_IDX].id; + gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id; + } + + /* Go through all configs, attach all functions */ + list_for_each_entry(c, &gi->cdev.configs, list) { + struct config_usb_cfg *cfg; + struct usb_function *f; + struct usb_function *tmp; + struct gadget_config_name *cn; + + cfg = container_of(c, struct config_usb_cfg, c); + if (!list_empty(&cfg->string_list)) { + i = 0; + list_for_each_entry(cn, &cfg->string_list, list) { + cfg->gstrings[i] = &cn->stringtab_dev; + cn->stringtab_dev.strings = &cn->strings; + cn->strings.s = cn->configuration; + i++; + } + cfg->gstrings[i] = NULL; + s = usb_gstrings_attach(&gi->cdev, cfg->gstrings, 1); + if (IS_ERR(s)) { + ret = PTR_ERR(s); + goto err_comp_cleanup; + } + c->iConfiguration = s[0].id; + } + + list_for_each_entry_safe(f, tmp, &cfg->func_list, list) { + list_del(&f->list); + ret = usb_add_function(c, f); + if (ret) + goto err_purge_funcs; + } + usb_ep_autoconfig_reset(cdev->gadget); + } + usb_ep_autoconfig_reset(cdev->gadget); + return 0; + +err_purge_funcs: + purge_configs_funcs(gi); +err_comp_cleanup: + composite_dev_cleanup(cdev); + return ret; +} + +static void configfs_composite_unbind(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev; + struct gadget_info *gi; + + /* the gi->lock is hold by the caller */ + + cdev = get_gadget_data(gadget); + gi = container_of(cdev, struct gadget_info, cdev); + + purge_configs_funcs(gi); + composite_dev_cleanup(cdev); + usb_ep_autoconfig_reset(cdev->gadget); + cdev->gadget = NULL; + set_gadget_data(gadget, NULL); +} + +static const struct usb_gadget_driver configfs_driver_template = { + .bind = configfs_composite_bind, + .unbind = configfs_composite_unbind, + + .setup = composite_setup, + .disconnect = composite_disconnect, + + .max_speed = USB_SPEED_SUPER, + .driver = { + .owner = THIS_MODULE, + .name = "configfs-gadget", + }, +}; + +static struct config_group *gadgets_make( + struct config_group *group, + const char *name) +{ + struct gadget_info *gi; + + gi = kzalloc(sizeof(*gi), GFP_KERNEL); + if (!gi) + return ERR_PTR(-ENOMEM); + + gi->group.default_groups = gi->default_groups; + gi->group.default_groups[0] = &gi->functions_group; + gi->group.default_groups[1] = &gi->configs_group; + gi->group.default_groups[2] = &gi->strings_group; + + config_group_init_type_name(&gi->functions_group, "functions", + &functions_type); + config_group_init_type_name(&gi->configs_group, "configs", + &config_desc_type); + config_group_init_type_name(&gi->strings_group, "strings", + &gadget_strings_strings_type); + + gi->composite.bind = configfs_do_nothing; + gi->composite.unbind = configfs_do_nothing; + gi->composite.suspend = NULL; + gi->composite.resume = NULL; + gi->composite.max_speed = USB_SPEED_SUPER; + + mutex_init(&gi->lock); + INIT_LIST_HEAD(&gi->string_list); + INIT_LIST_HEAD(&gi->available_func); + + composite_init_dev(&gi->cdev); + gi->cdev.desc.bLength = USB_DT_DEVICE_SIZE; + gi->cdev.desc.bDescriptorType = USB_DT_DEVICE; + gi->cdev.desc.bcdDevice = cpu_to_le16(get_default_bcdDevice()); + + gi->composite.gadget_driver = configfs_driver_template; + + gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL); + gi->composite.name = gi->composite.gadget_driver.function; + + if (!gi->composite.gadget_driver.function) + goto err; + +#ifdef CONFIG_USB_OTG + gi->otg.bLength = sizeof(struct usb_otg_descriptor); + gi->otg.bDescriptorType = USB_DT_OTG; + gi->otg.bmAttributes = USB_OTG_SRP | USB_OTG_HNP; +#endif + + config_group_init_type_name(&gi->group, name, + &gadget_root_type); + return &gi->group; +err: + kfree(gi); + return ERR_PTR(-ENOMEM); +} + +static void gadgets_drop(struct config_group *group, struct config_item *item) +{ + config_item_put(item); +} + +static struct configfs_group_operations gadgets_ops = { + .make_group = &gadgets_make, + .drop_item = &gadgets_drop, +}; + +static struct config_item_type gadgets_type = { + .ct_group_ops = &gadgets_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem gadget_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "usb_gadget", + .ci_type = &gadgets_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex), +}; + +static int __init gadget_cfs_init(void) +{ + int ret; + + config_group_init(&gadget_subsys.su_group); + + ret = configfs_register_subsystem(&gadget_subsys); + return ret; +} +module_init(gadget_cfs_init); + +static void __exit gadget_cfs_exit(void) +{ + configfs_unregister_subsystem(&gadget_subsys); +} +module_exit(gadget_cfs_exit); diff --git a/drivers/usb/gadget/dbgp.c b/drivers/usb/gadget/dbgp.c new file mode 100644 index 00000000..986fc511 --- /dev/null +++ b/drivers/usb/gadget/dbgp.c @@ -0,0 +1,434 @@ +/* + * dbgp.c -- EHCI Debug Port device gadget + * + * Copyright (C) 2010 Stephane Duverger + * + * Released under the GPLv2. + */ + +/* verbose messages */ +#include +#include +#include +#include +#include + +#include "u_serial.h" + +#define DRIVER_VENDOR_ID 0x0525 /* NetChip */ +#define DRIVER_PRODUCT_ID 0xc0de /* undefined */ + +#define USB_DEBUG_MAX_PACKET_SIZE 8 +#define DBGP_REQ_EP0_LEN 128 +#define DBGP_REQ_LEN 512 + +static struct dbgp { + struct usb_gadget *gadget; + struct usb_request *req; + struct usb_ep *i_ep; + struct usb_ep *o_ep; +#ifdef CONFIG_USB_G_DBGP_SERIAL + struct gserial *serial; +#endif +} dbgp; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + .idVendor = __constant_cpu_to_le16(DRIVER_VENDOR_ID), + .idProduct = __constant_cpu_to_le16(DRIVER_PRODUCT_ID), + .bNumConfigurations = 1, +}; + +static struct usb_debug_descriptor dbg_desc = { + .bLength = sizeof dbg_desc, + .bDescriptorType = USB_DT_DEBUG, +}; + +static struct usb_endpoint_descriptor i_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .bEndpointAddress = USB_DIR_IN, +}; + +static struct usb_endpoint_descriptor o_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .bEndpointAddress = USB_DIR_OUT, +}; + +#ifdef CONFIG_USB_G_DBGP_PRINTK +static int dbgp_consume(char *buf, unsigned len) +{ + char c; + + if (!len) + return 0; + + c = buf[len-1]; + if (c != 0) + buf[len-1] = 0; + + printk(KERN_NOTICE "%s%c", buf, c); + return 0; +} + +static void __disable_ep(struct usb_ep *ep) +{ + if (ep && ep->driver_data == dbgp.gadget) { + usb_ep_disable(ep); + ep->driver_data = NULL; + } +} + +static void dbgp_disable_ep(void) +{ + __disable_ep(dbgp.i_ep); + __disable_ep(dbgp.o_ep); +} + +static void dbgp_complete(struct usb_ep *ep, struct usb_request *req) +{ + int stp; + int err = 0; + int status = req->status; + + if (ep == dbgp.i_ep) { + stp = 1; + goto fail; + } + + if (status != 0) { + stp = 2; + goto release_req; + } + + dbgp_consume(req->buf, req->actual); + + req->length = DBGP_REQ_LEN; + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err < 0) { + stp = 3; + goto release_req; + } + + return; + +release_req: + kfree(req->buf); + usb_ep_free_request(dbgp.o_ep, req); + dbgp_disable_ep(); +fail: + dev_dbg(&dbgp.gadget->dev, + "complete: failure (%d:%d) ==> %d\n", stp, err, status); +} + +static int dbgp_enable_ep_req(struct usb_ep *ep) +{ + int err, stp; + struct usb_request *req; + + req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) { + err = -ENOMEM; + stp = 1; + goto fail_1; + } + + req->buf = kmalloc(DBGP_REQ_LEN, GFP_KERNEL); + if (!req->buf) { + err = -ENOMEM; + stp = 2; + goto fail_2; + } + + req->complete = dbgp_complete; + req->length = DBGP_REQ_LEN; + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err < 0) { + stp = 3; + goto fail_3; + } + + return 0; + +fail_3: + kfree(req->buf); +fail_2: + usb_ep_free_request(dbgp.o_ep, req); +fail_1: + dev_dbg(&dbgp.gadget->dev, + "enable ep req: failure (%d:%d)\n", stp, err); + return err; +} + +static int __enable_ep(struct usb_ep *ep, struct usb_endpoint_descriptor *desc) +{ + int err; + ep->desc = desc; + err = usb_ep_enable(ep); + ep->driver_data = dbgp.gadget; + return err; +} + +static int dbgp_enable_ep(void) +{ + int err, stp; + + err = __enable_ep(dbgp.i_ep, &i_desc); + if (err < 0) { + stp = 1; + goto fail_1; + } + + err = __enable_ep(dbgp.o_ep, &o_desc); + if (err < 0) { + stp = 2; + goto fail_2; + } + + err = dbgp_enable_ep_req(dbgp.o_ep); + if (err < 0) { + stp = 3; + goto fail_3; + } + + return 0; + +fail_3: + __disable_ep(dbgp.o_ep); +fail_2: + __disable_ep(dbgp.i_ep); +fail_1: + dev_dbg(&dbgp.gadget->dev, "enable ep: failure (%d:%d)\n", stp, err); + return err; +} +#endif + +static void dbgp_disconnect(struct usb_gadget *gadget) +{ +#ifdef CONFIG_USB_G_DBGP_PRINTK + dbgp_disable_ep(); +#else + gserial_disconnect(dbgp.serial); +#endif +} + +static void dbgp_unbind(struct usb_gadget *gadget) +{ +#ifdef CONFIG_USB_G_DBGP_SERIAL + kfree(dbgp.serial); +#endif + if (dbgp.req) { + kfree(dbgp.req->buf); + usb_ep_free_request(gadget->ep0, dbgp.req); + } + + gadget->ep0->driver_data = NULL; +} + +#ifdef CONFIG_USB_G_DBGP_SERIAL +static unsigned char tty_line; +#endif + +static int __init dbgp_configure_endpoints(struct usb_gadget *gadget) +{ + int stp; + + usb_ep_autoconfig_reset(gadget); + + dbgp.i_ep = usb_ep_autoconfig(gadget, &i_desc); + if (!dbgp.i_ep) { + stp = 1; + goto fail_1; + } + + dbgp.i_ep->driver_data = gadget; + i_desc.wMaxPacketSize = + __constant_cpu_to_le16(USB_DEBUG_MAX_PACKET_SIZE); + + dbgp.o_ep = usb_ep_autoconfig(gadget, &o_desc); + if (!dbgp.o_ep) { + dbgp.i_ep->driver_data = NULL; + stp = 2; + goto fail_2; + } + + dbgp.o_ep->driver_data = gadget; + o_desc.wMaxPacketSize = + __constant_cpu_to_le16(USB_DEBUG_MAX_PACKET_SIZE); + + dbg_desc.bDebugInEndpoint = i_desc.bEndpointAddress; + dbg_desc.bDebugOutEndpoint = o_desc.bEndpointAddress; + +#ifdef CONFIG_USB_G_DBGP_SERIAL + dbgp.serial->in = dbgp.i_ep; + dbgp.serial->out = dbgp.o_ep; + + dbgp.serial->in->desc = &i_desc; + dbgp.serial->out->desc = &o_desc; + + if (gserial_alloc_line(&tty_line)) { + stp = 3; + goto fail_3; + } + + return 0; + +fail_3: + dbgp.o_ep->driver_data = NULL; +#else + return 0; +#endif +fail_2: + dbgp.i_ep->driver_data = NULL; +fail_1: + dev_dbg(&dbgp.gadget->dev, "ep config: failure (%d)\n", stp); + return -ENODEV; +} + +static int __init dbgp_bind(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + int err, stp; + + dbgp.gadget = gadget; + + dbgp.req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!dbgp.req) { + err = -ENOMEM; + stp = 1; + goto fail; + } + + dbgp.req->buf = kmalloc(DBGP_REQ_EP0_LEN, GFP_KERNEL); + if (!dbgp.req->buf) { + err = -ENOMEM; + stp = 2; + goto fail; + } + + dbgp.req->length = DBGP_REQ_EP0_LEN; + gadget->ep0->driver_data = gadget; + +#ifdef CONFIG_USB_G_DBGP_SERIAL + dbgp.serial = kzalloc(sizeof(struct gserial), GFP_KERNEL); + if (!dbgp.serial) { + stp = 3; + err = -ENOMEM; + goto fail; + } +#endif + err = dbgp_configure_endpoints(gadget); + if (err < 0) { + stp = 4; + goto fail; + } + + dev_dbg(&dbgp.gadget->dev, "bind: success\n"); + return 0; + +fail: + dev_dbg(&gadget->dev, "bind: failure (%d:%d)\n", stp, err); + dbgp_unbind(gadget); + return err; +} + +static void dbgp_setup_complete(struct usb_ep *ep, + struct usb_request *req) +{ + dev_dbg(&dbgp.gadget->dev, "setup complete: %d, %d/%d\n", + req->status, req->actual, req->length); +} + +static int dbgp_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_request *req = dbgp.req; + u8 request = ctrl->bRequest; + u16 value = le16_to_cpu(ctrl->wValue); + u16 length = le16_to_cpu(ctrl->wLength); + int err = -EOPNOTSUPP; + void *data = NULL; + u16 len = 0; + + gadget->ep0->driver_data = gadget; + + if (request == USB_REQ_GET_DESCRIPTOR) { + switch (value>>8) { + case USB_DT_DEVICE: + dev_dbg(&dbgp.gadget->dev, "setup: desc device\n"); + len = sizeof device_desc; + data = &device_desc; + device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket; + break; + case USB_DT_DEBUG: + dev_dbg(&dbgp.gadget->dev, "setup: desc debug\n"); + len = sizeof dbg_desc; + data = &dbg_desc; + break; + default: + goto fail; + } + err = 0; + } else if (request == USB_REQ_SET_FEATURE && + value == USB_DEVICE_DEBUG_MODE) { + dev_dbg(&dbgp.gadget->dev, "setup: feat debug\n"); +#ifdef CONFIG_USB_G_DBGP_PRINTK + err = dbgp_enable_ep(); +#else + err = gserial_connect(dbgp.serial, tty_line); +#endif + if (err < 0) + goto fail; + } else + goto fail; + + req->length = min(length, len); + req->zero = len < req->length; + if (data && req->length) + memcpy(req->buf, data, req->length); + + req->complete = dbgp_setup_complete; + return usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); + +fail: + dev_dbg(&dbgp.gadget->dev, + "setup: failure req %x v %x\n", request, value); + return err; +} + +static __refdata struct usb_gadget_driver dbgp_driver = { + .function = "dbgp", + .max_speed = USB_SPEED_HIGH, + .bind = dbgp_bind, + .unbind = dbgp_unbind, + .setup = dbgp_setup, + .disconnect = dbgp_disconnect, + .driver = { + .owner = THIS_MODULE, + .name = "dbgp" + }, +}; + +static int __init dbgp_init(void) +{ + return usb_gadget_probe_driver(&dbgp_driver); +} + +static void __exit dbgp_exit(void) +{ + usb_gadget_unregister_driver(&dbgp_driver); +#ifdef CONFIG_USB_G_DBGP_SERIAL + gserial_free_line(tty_line); +#endif +} + +MODULE_AUTHOR("Stephane Duverger"); +MODULE_LICENSE("GPL"); +module_init(dbgp_init); +module_exit(dbgp_exit); diff --git a/drivers/usb/gadget/dummy_hcd.c b/drivers/usb/gadget/dummy_hcd.c new file mode 100644 index 00000000..ac0e79e2 --- /dev/null +++ b/drivers/usb/gadget/dummy_hcd.c @@ -0,0 +1,2766 @@ +/* + * dummy_hcd.c -- Dummy/Loopback USB host and device emulator driver. + * + * Maintainer: Alan Stern + * + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003-2005 Alan Stern + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +/* + * This exposes a device side "USB gadget" API, driven by requests to a + * Linux-USB host controller driver. USB traffic is simulated; there's + * no need for USB hardware. Use this with two other drivers: + * + * - Gadget driver, responding to requests (slave); + * - Host-side device driver, as already familiar in Linux. + * + * Having this all in one kernel can help some stages of development, + * bypassing some hardware (and driver) issues. UML could help too. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define DRIVER_DESC "USB Host+Gadget Emulator" +#define DRIVER_VERSION "02 May 2005" + +#define POWER_BUDGET 500 /* in mA; use 8 for low-power port testing */ + +static const char driver_name[] = "dummy_hcd"; +static const char driver_desc[] = "USB Host+Gadget Emulator"; + +static const char gadget_name[] = "dummy_udc"; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); + +struct dummy_hcd_module_parameters { + bool is_super_speed; + bool is_high_speed; + unsigned int num; +}; + +static struct dummy_hcd_module_parameters mod_data = { + .is_super_speed = false, + .is_high_speed = true, + .num = 1, +}; +module_param_named(is_super_speed, mod_data.is_super_speed, bool, S_IRUGO); +MODULE_PARM_DESC(is_super_speed, "true to simulate SuperSpeed connection"); +module_param_named(is_high_speed, mod_data.is_high_speed, bool, S_IRUGO); +MODULE_PARM_DESC(is_high_speed, "true to simulate HighSpeed connection"); +module_param_named(num, mod_data.num, uint, S_IRUGO); +MODULE_PARM_DESC(num, "number of emulated controllers"); +/*-------------------------------------------------------------------------*/ + +/* gadget side driver data structres */ +struct dummy_ep { + struct list_head queue; + unsigned long last_io; /* jiffies timestamp */ + struct usb_gadget *gadget; + const struct usb_endpoint_descriptor *desc; + struct usb_ep ep; + unsigned halted:1; + unsigned wedged:1; + unsigned already_seen:1; + unsigned setup_stage:1; + unsigned stream_en:1; +}; + +struct dummy_request { + struct list_head queue; /* ep's requests */ + struct usb_request req; +}; + +static inline struct dummy_ep *usb_ep_to_dummy_ep(struct usb_ep *_ep) +{ + return container_of(_ep, struct dummy_ep, ep); +} + +static inline struct dummy_request *usb_request_to_dummy_request + (struct usb_request *_req) +{ + return container_of(_req, struct dummy_request, req); +} + +/*-------------------------------------------------------------------------*/ + +/* + * Every device has ep0 for control requests, plus up to 30 more endpoints, + * in one of two types: + * + * - Configurable: direction (in/out), type (bulk, iso, etc), and endpoint + * number can be changed. Names like "ep-a" are used for this type. + * + * - Fixed Function: in other cases. some characteristics may be mutable; + * that'd be hardware-specific. Names like "ep12out-bulk" are used. + * + * Gadget drivers are responsible for not setting up conflicting endpoint + * configurations, illegal or unsupported packet lengths, and so on. + */ + +static const char ep0name[] = "ep0"; + +static const char *const ep_name[] = { + ep0name, /* everyone has ep0 */ + + /* act like a pxa250: fifteen fixed function endpoints */ + "ep1in-bulk", "ep2out-bulk", "ep3in-iso", "ep4out-iso", "ep5in-int", + "ep6in-bulk", "ep7out-bulk", "ep8in-iso", "ep9out-iso", "ep10in-int", + "ep11in-bulk", "ep12out-bulk", "ep13in-iso", "ep14out-iso", + "ep15in-int", + + /* or like sa1100: two fixed function endpoints */ + "ep1out-bulk", "ep2in-bulk", + + /* and now some generic EPs so we have enough in multi config */ + "ep3out", "ep4in", "ep5out", "ep6out", "ep7in", "ep8out", "ep9in", + "ep10out", "ep11out", "ep12in", "ep13out", "ep14in", "ep15out", +}; +#define DUMMY_ENDPOINTS ARRAY_SIZE(ep_name) + +/*-------------------------------------------------------------------------*/ + +#define FIFO_SIZE 64 + +struct urbp { + struct urb *urb; + struct list_head urbp_list; + struct sg_mapping_iter miter; + u32 miter_started; +}; + + +enum dummy_rh_state { + DUMMY_RH_RESET, + DUMMY_RH_SUSPENDED, + DUMMY_RH_RUNNING +}; + +struct dummy_hcd { + struct dummy *dum; + enum dummy_rh_state rh_state; + struct timer_list timer; + u32 port_status; + u32 old_status; + unsigned long re_timeout; + + struct usb_device *udev; + struct list_head urbp_list; + u32 stream_en_ep; + u8 num_stream[30 / 2]; + + unsigned active:1; + unsigned old_active:1; + unsigned resuming:1; +}; + +struct dummy { + spinlock_t lock; + + /* + * SLAVE/GADGET side support + */ + struct dummy_ep ep[DUMMY_ENDPOINTS]; + int address; + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct dummy_request fifo_req; + u8 fifo_buf[FIFO_SIZE]; + u16 devstatus; + unsigned udc_suspended:1; + unsigned pullup:1; + + /* + * MASTER/HOST side support + */ + struct dummy_hcd *hs_hcd; + struct dummy_hcd *ss_hcd; +}; + +static inline struct dummy_hcd *hcd_to_dummy_hcd(struct usb_hcd *hcd) +{ + return (struct dummy_hcd *) (hcd->hcd_priv); +} + +static inline struct usb_hcd *dummy_hcd_to_hcd(struct dummy_hcd *dum) +{ + return container_of((void *) dum, struct usb_hcd, hcd_priv); +} + +static inline struct device *dummy_dev(struct dummy_hcd *dum) +{ + return dummy_hcd_to_hcd(dum)->self.controller; +} + +static inline struct device *udc_dev(struct dummy *dum) +{ + return dum->gadget.dev.parent; +} + +static inline struct dummy *ep_to_dummy(struct dummy_ep *ep) +{ + return container_of(ep->gadget, struct dummy, gadget); +} + +static inline struct dummy_hcd *gadget_to_dummy_hcd(struct usb_gadget *gadget) +{ + struct dummy *dum = container_of(gadget, struct dummy, gadget); + if (dum->gadget.speed == USB_SPEED_SUPER) + return dum->ss_hcd; + else + return dum->hs_hcd; +} + +static inline struct dummy *gadget_dev_to_dummy(struct device *dev) +{ + return container_of(dev, struct dummy, gadget.dev); +} + +/*-------------------------------------------------------------------------*/ + +/* SLAVE/GADGET SIDE UTILITY ROUTINES */ + +/* called with spinlock held */ +static void nuke(struct dummy *dum, struct dummy_ep *ep) +{ + while (!list_empty(&ep->queue)) { + struct dummy_request *req; + + req = list_entry(ep->queue.next, struct dummy_request, queue); + list_del_init(&req->queue); + req->req.status = -ESHUTDOWN; + + spin_unlock(&dum->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&dum->lock); + } +} + +/* caller must hold lock */ +static void stop_activity(struct dummy *dum) +{ + struct dummy_ep *ep; + + /* prevent any more requests */ + dum->address = 0; + + /* The timer is left running so that outstanding URBs can fail */ + + /* nuke any pending requests first, so driver i/o is quiesced */ + list_for_each_entry(ep, &dum->gadget.ep_list, ep.ep_list) + nuke(dum, ep); + + /* driver now does any non-usb quiescing necessary */ +} + +/** + * set_link_state_by_speed() - Sets the current state of the link according to + * the hcd speed + * @dum_hcd: pointer to the dummy_hcd structure to update the link state for + * + * This function updates the port_status according to the link state and the + * speed of the hcd. + */ +static void set_link_state_by_speed(struct dummy_hcd *dum_hcd) +{ + struct dummy *dum = dum_hcd->dum; + + if (dummy_hcd_to_hcd(dum_hcd)->speed == HCD_USB3) { + if ((dum_hcd->port_status & USB_SS_PORT_STAT_POWER) == 0) { + dum_hcd->port_status = 0; + } else if (!dum->pullup || dum->udc_suspended) { + /* UDC suspend must cause a disconnect */ + dum_hcd->port_status &= ~(USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_ENABLE); + if ((dum_hcd->old_status & + USB_PORT_STAT_CONNECTION) != 0) + dum_hcd->port_status |= + (USB_PORT_STAT_C_CONNECTION << 16); + } else { + /* device is connected and not suspended */ + dum_hcd->port_status |= (USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_SPEED_5GBPS) ; + if ((dum_hcd->old_status & + USB_PORT_STAT_CONNECTION) == 0) + dum_hcd->port_status |= + (USB_PORT_STAT_C_CONNECTION << 16); + if ((dum_hcd->port_status & + USB_PORT_STAT_ENABLE) == 1 && + (dum_hcd->port_status & + USB_SS_PORT_LS_U0) == 1 && + dum_hcd->rh_state != DUMMY_RH_SUSPENDED) + dum_hcd->active = 1; + } + } else { + if ((dum_hcd->port_status & USB_PORT_STAT_POWER) == 0) { + dum_hcd->port_status = 0; + } else if (!dum->pullup || dum->udc_suspended) { + /* UDC suspend must cause a disconnect */ + dum_hcd->port_status &= ~(USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_ENABLE | + USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_SUSPEND); + if ((dum_hcd->old_status & + USB_PORT_STAT_CONNECTION) != 0) + dum_hcd->port_status |= + (USB_PORT_STAT_C_CONNECTION << 16); + } else { + dum_hcd->port_status |= USB_PORT_STAT_CONNECTION; + if ((dum_hcd->old_status & + USB_PORT_STAT_CONNECTION) == 0) + dum_hcd->port_status |= + (USB_PORT_STAT_C_CONNECTION << 16); + if ((dum_hcd->port_status & USB_PORT_STAT_ENABLE) == 0) + dum_hcd->port_status &= ~USB_PORT_STAT_SUSPEND; + else if ((dum_hcd->port_status & + USB_PORT_STAT_SUSPEND) == 0 && + dum_hcd->rh_state != DUMMY_RH_SUSPENDED) + dum_hcd->active = 1; + } + } +} + +/* caller must hold lock */ +static void set_link_state(struct dummy_hcd *dum_hcd) +{ + struct dummy *dum = dum_hcd->dum; + + dum_hcd->active = 0; + if (dum->pullup) + if ((dummy_hcd_to_hcd(dum_hcd)->speed == HCD_USB3 && + dum->gadget.speed != USB_SPEED_SUPER) || + (dummy_hcd_to_hcd(dum_hcd)->speed != HCD_USB3 && + dum->gadget.speed == USB_SPEED_SUPER)) + return; + + set_link_state_by_speed(dum_hcd); + + if ((dum_hcd->port_status & USB_PORT_STAT_ENABLE) == 0 || + dum_hcd->active) + dum_hcd->resuming = 0; + + /* if !connected or reset */ + if ((dum_hcd->port_status & USB_PORT_STAT_CONNECTION) == 0 || + (dum_hcd->port_status & USB_PORT_STAT_RESET) != 0) { + /* + * We're connected and not reset (reset occurred now), + * and driver attached - disconnect! + */ + if ((dum_hcd->old_status & USB_PORT_STAT_CONNECTION) != 0 && + (dum_hcd->old_status & USB_PORT_STAT_RESET) == 0 && + dum->driver) { + stop_activity(dum); + spin_unlock(&dum->lock); + dum->driver->disconnect(&dum->gadget); + spin_lock(&dum->lock); + } + } else if (dum_hcd->active != dum_hcd->old_active) { + if (dum_hcd->old_active && dum->driver->suspend) { + spin_unlock(&dum->lock); + dum->driver->suspend(&dum->gadget); + spin_lock(&dum->lock); + } else if (!dum_hcd->old_active && dum->driver->resume) { + spin_unlock(&dum->lock); + dum->driver->resume(&dum->gadget); + spin_lock(&dum->lock); + } + } + + dum_hcd->old_status = dum_hcd->port_status; + dum_hcd->old_active = dum_hcd->active; +} + +/*-------------------------------------------------------------------------*/ + +/* SLAVE/GADGET SIDE DRIVER + * + * This only tracks gadget state. All the work is done when the host + * side tries some (emulated) i/o operation. Real device controller + * drivers would do real i/o using dma, fifos, irqs, timers, etc. + */ + +#define is_enabled(dum) \ + (dum->port_status & USB_PORT_STAT_ENABLE) + +static int dummy_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct dummy *dum; + struct dummy_hcd *dum_hcd; + struct dummy_ep *ep; + unsigned max; + int retval; + + ep = usb_ep_to_dummy_ep(_ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + dum = ep_to_dummy(ep); + if (!dum->driver) + return -ESHUTDOWN; + + dum_hcd = gadget_to_dummy_hcd(&dum->gadget); + if (!is_enabled(dum_hcd)) + return -ESHUTDOWN; + + /* + * For HS/FS devices only bits 0..10 of the wMaxPacketSize represent the + * maximum packet size. + * For SS devices the wMaxPacketSize is limited by 1024. + */ + max = usb_endpoint_maxp(desc) & 0x7ff; + + /* drivers must not request bad settings, since lower levels + * (hardware or its drivers) may not check. some endpoints + * can't do iso, many have maxpacket limitations, etc. + * + * since this "hardware" driver is here to help debugging, we + * have some extra sanity checks. (there could be more though, + * especially for "ep9out" style fixed function ones.) + */ + retval = -EINVAL; + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_BULK: + if (strstr(ep->ep.name, "-iso") + || strstr(ep->ep.name, "-int")) { + goto done; + } + switch (dum->gadget.speed) { + case USB_SPEED_SUPER: + if (max == 1024) + break; + goto done; + case USB_SPEED_HIGH: + if (max == 512) + break; + goto done; + case USB_SPEED_FULL: + if (max == 8 || max == 16 || max == 32 || max == 64) + /* we'll fake any legal size */ + break; + /* save a return statement */ + default: + goto done; + } + break; + case USB_ENDPOINT_XFER_INT: + if (strstr(ep->ep.name, "-iso")) /* bulk is ok */ + goto done; + /* real hardware might not handle all packet sizes */ + switch (dum->gadget.speed) { + case USB_SPEED_SUPER: + case USB_SPEED_HIGH: + if (max <= 1024) + break; + /* save a return statement */ + case USB_SPEED_FULL: + if (max <= 64) + break; + /* save a return statement */ + default: + if (max <= 8) + break; + goto done; + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (strstr(ep->ep.name, "-bulk") + || strstr(ep->ep.name, "-int")) + goto done; + /* real hardware might not handle all packet sizes */ + switch (dum->gadget.speed) { + case USB_SPEED_SUPER: + case USB_SPEED_HIGH: + if (max <= 1024) + break; + /* save a return statement */ + case USB_SPEED_FULL: + if (max <= 1023) + break; + /* save a return statement */ + default: + goto done; + } + break; + default: + /* few chips support control except on ep0 */ + goto done; + } + + _ep->maxpacket = max; + if (usb_ss_max_streams(_ep->comp_desc)) { + if (!usb_endpoint_xfer_bulk(desc)) { + dev_err(udc_dev(dum), "Can't enable stream support on " + "non-bulk ep %s\n", _ep->name); + return -EINVAL; + } + ep->stream_en = 1; + } + ep->desc = desc; + + dev_dbg(udc_dev(dum), "enabled %s (ep%d%s-%s) maxpacket %d stream %s\n", + _ep->name, + desc->bEndpointAddress & 0x0f, + (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + ({ char *val; + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_BULK: + val = "bulk"; + break; + case USB_ENDPOINT_XFER_ISOC: + val = "iso"; + break; + case USB_ENDPOINT_XFER_INT: + val = "intr"; + break; + default: + val = "ctrl"; + break; + }; val; }), + max, ep->stream_en ? "enabled" : "disabled"); + + /* at this point real hardware should be NAKing transfers + * to that endpoint, until a buffer is queued to it. + */ + ep->halted = ep->wedged = 0; + retval = 0; +done: + return retval; +} + +static int dummy_disable(struct usb_ep *_ep) +{ + struct dummy_ep *ep; + struct dummy *dum; + unsigned long flags; + int retval; + + ep = usb_ep_to_dummy_ep(_ep); + if (!_ep || !ep->desc || _ep->name == ep0name) + return -EINVAL; + dum = ep_to_dummy(ep); + + spin_lock_irqsave(&dum->lock, flags); + ep->desc = NULL; + ep->stream_en = 0; + retval = 0; + nuke(dum, ep); + spin_unlock_irqrestore(&dum->lock, flags); + + dev_dbg(udc_dev(dum), "disabled %s\n", _ep->name); + return retval; +} + +static struct usb_request *dummy_alloc_request(struct usb_ep *_ep, + gfp_t mem_flags) +{ + struct dummy_ep *ep; + struct dummy_request *req; + + if (!_ep) + return NULL; + ep = usb_ep_to_dummy_ep(_ep); + + req = kzalloc(sizeof(*req), mem_flags); + if (!req) + return NULL; + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +static void dummy_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct dummy_request *req; + + if (!_ep || !_req) { + WARN_ON(1); + return; + } + + req = usb_request_to_dummy_request(_req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +static void fifo_complete(struct usb_ep *ep, struct usb_request *req) +{ +} + +static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t mem_flags) +{ + struct dummy_ep *ep; + struct dummy_request *req; + struct dummy *dum; + struct dummy_hcd *dum_hcd; + unsigned long flags; + + req = usb_request_to_dummy_request(_req); + if (!_req || !list_empty(&req->queue) || !_req->complete) + return -EINVAL; + + ep = usb_ep_to_dummy_ep(_ep); + if (!_ep || (!ep->desc && _ep->name != ep0name)) + return -EINVAL; + + dum = ep_to_dummy(ep); + dum_hcd = gadget_to_dummy_hcd(&dum->gadget); + if (!dum->driver || !is_enabled(dum_hcd)) + return -ESHUTDOWN; + +#if 0 + dev_dbg(udc_dev(dum), "ep %p queue req %p to %s, len %d buf %p\n", + ep, _req, _ep->name, _req->length, _req->buf); +#endif + _req->status = -EINPROGRESS; + _req->actual = 0; + spin_lock_irqsave(&dum->lock, flags); + + /* implement an emulated single-request FIFO */ + if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && + list_empty(&dum->fifo_req.queue) && + list_empty(&ep->queue) && + _req->length <= FIFO_SIZE) { + req = &dum->fifo_req; + req->req = *_req; + req->req.buf = dum->fifo_buf; + memcpy(dum->fifo_buf, _req->buf, _req->length); + req->req.context = dum; + req->req.complete = fifo_complete; + + list_add_tail(&req->queue, &ep->queue); + spin_unlock(&dum->lock); + _req->actual = _req->length; + _req->status = 0; + _req->complete(_ep, _req); + spin_lock(&dum->lock); + } else + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&dum->lock, flags); + + /* real hardware would likely enable transfers here, in case + * it'd been left NAKing. + */ + return 0; +} + +static int dummy_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct dummy_ep *ep; + struct dummy *dum; + int retval = -EINVAL; + unsigned long flags; + struct dummy_request *req = NULL; + + if (!_ep || !_req) + return retval; + ep = usb_ep_to_dummy_ep(_ep); + dum = ep_to_dummy(ep); + + if (!dum->driver) + return -ESHUTDOWN; + + local_irq_save(flags); + spin_lock(&dum->lock); + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) { + list_del_init(&req->queue); + _req->status = -ECONNRESET; + retval = 0; + break; + } + } + spin_unlock(&dum->lock); + + if (retval == 0) { + dev_dbg(udc_dev(dum), + "dequeued req %p from %s, len %d buf %p\n", + req, _ep->name, _req->length, _req->buf); + _req->complete(_ep, _req); + } + local_irq_restore(flags); + return retval; +} + +static int +dummy_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) +{ + struct dummy_ep *ep; + struct dummy *dum; + + if (!_ep) + return -EINVAL; + ep = usb_ep_to_dummy_ep(_ep); + dum = ep_to_dummy(ep); + if (!dum->driver) + return -ESHUTDOWN; + if (!value) + ep->halted = ep->wedged = 0; + else if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && + !list_empty(&ep->queue)) + return -EAGAIN; + else { + ep->halted = 1; + if (wedged) + ep->wedged = 1; + } + /* FIXME clear emulated data toggle too */ + return 0; +} + +static int +dummy_set_halt(struct usb_ep *_ep, int value) +{ + return dummy_set_halt_and_wedge(_ep, value, 0); +} + +static int dummy_set_wedge(struct usb_ep *_ep) +{ + if (!_ep || _ep->name == ep0name) + return -EINVAL; + return dummy_set_halt_and_wedge(_ep, 1, 1); +} + +static const struct usb_ep_ops dummy_ep_ops = { + .enable = dummy_enable, + .disable = dummy_disable, + + .alloc_request = dummy_alloc_request, + .free_request = dummy_free_request, + + .queue = dummy_queue, + .dequeue = dummy_dequeue, + + .set_halt = dummy_set_halt, + .set_wedge = dummy_set_wedge, +}; + +/*-------------------------------------------------------------------------*/ + +/* there are both host and device side versions of this call ... */ +static int dummy_g_get_frame(struct usb_gadget *_gadget) +{ + struct timeval tv; + + do_gettimeofday(&tv); + return tv.tv_usec / 1000; +} + +static int dummy_wakeup(struct usb_gadget *_gadget) +{ + struct dummy_hcd *dum_hcd; + + dum_hcd = gadget_to_dummy_hcd(_gadget); + if (!(dum_hcd->dum->devstatus & ((1 << USB_DEVICE_B_HNP_ENABLE) + | (1 << USB_DEVICE_REMOTE_WAKEUP)))) + return -EINVAL; + if ((dum_hcd->port_status & USB_PORT_STAT_CONNECTION) == 0) + return -ENOLINK; + if ((dum_hcd->port_status & USB_PORT_STAT_SUSPEND) == 0 && + dum_hcd->rh_state != DUMMY_RH_SUSPENDED) + return -EIO; + + /* FIXME: What if the root hub is suspended but the port isn't? */ + + /* hub notices our request, issues downstream resume, etc */ + dum_hcd->resuming = 1; + dum_hcd->re_timeout = jiffies + msecs_to_jiffies(20); + mod_timer(&dummy_hcd_to_hcd(dum_hcd)->rh_timer, dum_hcd->re_timeout); + return 0; +} + +static int dummy_set_selfpowered(struct usb_gadget *_gadget, int value) +{ + struct dummy *dum; + + dum = gadget_to_dummy_hcd(_gadget)->dum; + if (value) + dum->devstatus |= (1 << USB_DEVICE_SELF_POWERED); + else + dum->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + return 0; +} + +static void dummy_udc_update_ep0(struct dummy *dum) +{ + if (dum->gadget.speed == USB_SPEED_SUPER) + dum->ep[0].ep.maxpacket = 9; + else + dum->ep[0].ep.maxpacket = 64; +} + +static int dummy_pullup(struct usb_gadget *_gadget, int value) +{ + struct dummy_hcd *dum_hcd; + struct dummy *dum; + unsigned long flags; + + dum = gadget_dev_to_dummy(&_gadget->dev); + + if (value && dum->driver) { + if (mod_data.is_super_speed) + dum->gadget.speed = dum->driver->max_speed; + else if (mod_data.is_high_speed) + dum->gadget.speed = min_t(u8, USB_SPEED_HIGH, + dum->driver->max_speed); + else + dum->gadget.speed = USB_SPEED_FULL; + dummy_udc_update_ep0(dum); + + if (dum->gadget.speed < dum->driver->max_speed) + dev_dbg(udc_dev(dum), "This device can perform faster" + " if you connect it to a %s port...\n", + usb_speed_string(dum->driver->max_speed)); + } + dum_hcd = gadget_to_dummy_hcd(_gadget); + + spin_lock_irqsave(&dum->lock, flags); + dum->pullup = (value != 0); + set_link_state(dum_hcd); + spin_unlock_irqrestore(&dum->lock, flags); + + usb_hcd_poll_rh_status(dummy_hcd_to_hcd(dum_hcd)); + return 0; +} + +static int dummy_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int dummy_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops dummy_ops = { + .get_frame = dummy_g_get_frame, + .wakeup = dummy_wakeup, + .set_selfpowered = dummy_set_selfpowered, + .pullup = dummy_pullup, + .udc_start = dummy_udc_start, + .udc_stop = dummy_udc_stop, +}; + +/*-------------------------------------------------------------------------*/ + +/* "function" sysfs attribute */ +static ssize_t show_function(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dummy *dum = gadget_dev_to_dummy(dev); + + if (!dum->driver || !dum->driver->function) + return 0; + return scnprintf(buf, PAGE_SIZE, "%s\n", dum->driver->function); +} +static DEVICE_ATTR(function, S_IRUGO, show_function, NULL); + +/*-------------------------------------------------------------------------*/ + +/* + * Driver registration/unregistration. + * + * This is basically hardware-specific; there's usually only one real USB + * device (not host) controller since that's how USB devices are intended + * to work. So most implementations of these api calls will rely on the + * fact that only one driver will ever bind to the hardware. But curious + * hardware can be built with discrete components, so the gadget API doesn't + * require that assumption. + * + * For this emulator, it might be convenient to create a usb slave device + * for each driver that registers: just add to a big root hub. + */ + +static int dummy_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct dummy_hcd *dum_hcd = gadget_to_dummy_hcd(g); + struct dummy *dum = dum_hcd->dum; + + if (driver->max_speed == USB_SPEED_UNKNOWN) + return -EINVAL; + + /* + * SLAVE side init ... the layer above hardware, which + * can't enumerate without help from the driver we're binding. + */ + + dum->devstatus = 0; + + dum->driver = driver; + dev_dbg(udc_dev(dum), "binding gadget driver '%s'\n", + driver->driver.name); + return 0; +} + +static int dummy_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct dummy_hcd *dum_hcd = gadget_to_dummy_hcd(g); + struct dummy *dum = dum_hcd->dum; + + if (driver) + dev_dbg(udc_dev(dum), "unregister gadget driver '%s'\n", + driver->driver.name); + + dum->driver = NULL; + + return 0; +} + +#undef is_enabled + +/* The gadget structure is stored inside the hcd structure and will be + * released along with it. */ +static void init_dummy_udc_hw(struct dummy *dum) +{ + int i; + + INIT_LIST_HEAD(&dum->gadget.ep_list); + for (i = 0; i < DUMMY_ENDPOINTS; i++) { + struct dummy_ep *ep = &dum->ep[i]; + + if (!ep_name[i]) + break; + ep->ep.name = ep_name[i]; + ep->ep.ops = &dummy_ep_ops; + list_add_tail(&ep->ep.ep_list, &dum->gadget.ep_list); + ep->halted = ep->wedged = ep->already_seen = + ep->setup_stage = 0; + ep->ep.maxpacket = ~0; + ep->ep.max_streams = 16; + ep->last_io = jiffies; + ep->gadget = &dum->gadget; + ep->desc = NULL; + INIT_LIST_HEAD(&ep->queue); + } + + dum->gadget.ep0 = &dum->ep[0].ep; + list_del_init(&dum->ep[0].ep.ep_list); + INIT_LIST_HEAD(&dum->fifo_req.queue); + +#ifdef CONFIG_USB_OTG + dum->gadget.is_otg = 1; +#endif +} + +static int dummy_udc_probe(struct platform_device *pdev) +{ + struct dummy *dum; + int rc; + + dum = *((void **)dev_get_platdata(&pdev->dev)); + dum->gadget.name = gadget_name; + dum->gadget.ops = &dummy_ops; + dum->gadget.max_speed = USB_SPEED_SUPER; + + dum->gadget.dev.parent = &pdev->dev; + init_dummy_udc_hw(dum); + + rc = usb_add_gadget_udc(&pdev->dev, &dum->gadget); + if (rc < 0) + goto err_udc; + + rc = device_create_file(&dum->gadget.dev, &dev_attr_function); + if (rc < 0) + goto err_dev; + platform_set_drvdata(pdev, dum); + return rc; + +err_dev: + usb_del_gadget_udc(&dum->gadget); +err_udc: + return rc; +} + +static int dummy_udc_remove(struct platform_device *pdev) +{ + struct dummy *dum = platform_get_drvdata(pdev); + + device_remove_file(&dum->gadget.dev, &dev_attr_function); + usb_del_gadget_udc(&dum->gadget); + return 0; +} + +static void dummy_udc_pm(struct dummy *dum, struct dummy_hcd *dum_hcd, + int suspend) +{ + spin_lock_irq(&dum->lock); + dum->udc_suspended = suspend; + set_link_state(dum_hcd); + spin_unlock_irq(&dum->lock); +} + +static int dummy_udc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct dummy *dum = platform_get_drvdata(pdev); + struct dummy_hcd *dum_hcd = gadget_to_dummy_hcd(&dum->gadget); + + dev_dbg(&pdev->dev, "%s\n", __func__); + dummy_udc_pm(dum, dum_hcd, 1); + usb_hcd_poll_rh_status(dummy_hcd_to_hcd(dum_hcd)); + return 0; +} + +static int dummy_udc_resume(struct platform_device *pdev) +{ + struct dummy *dum = platform_get_drvdata(pdev); + struct dummy_hcd *dum_hcd = gadget_to_dummy_hcd(&dum->gadget); + + dev_dbg(&pdev->dev, "%s\n", __func__); + dummy_udc_pm(dum, dum_hcd, 0); + usb_hcd_poll_rh_status(dummy_hcd_to_hcd(dum_hcd)); + return 0; +} + +static struct platform_driver dummy_udc_driver = { + .probe = dummy_udc_probe, + .remove = dummy_udc_remove, + .suspend = dummy_udc_suspend, + .resume = dummy_udc_resume, + .driver = { + .name = (char *) gadget_name, + .owner = THIS_MODULE, + }, +}; + +/*-------------------------------------------------------------------------*/ + +static unsigned int dummy_get_ep_idx(const struct usb_endpoint_descriptor *desc) +{ + unsigned int index; + + index = usb_endpoint_num(desc) << 1; + if (usb_endpoint_dir_in(desc)) + index |= 1; + return index; +} + +/* MASTER/HOST SIDE DRIVER + * + * this uses the hcd framework to hook up to host side drivers. + * its root hub will only have one device, otherwise it acts like + * a normal host controller. + * + * when urbs are queued, they're just stuck on a list that we + * scan in a timer callback. that callback connects writes from + * the host with reads from the device, and so on, based on the + * usb 2.0 rules. + */ + +static int dummy_ep_stream_en(struct dummy_hcd *dum_hcd, struct urb *urb) +{ + const struct usb_endpoint_descriptor *desc = &urb->ep->desc; + u32 index; + + if (!usb_endpoint_xfer_bulk(desc)) + return 0; + + index = dummy_get_ep_idx(desc); + return (1 << index) & dum_hcd->stream_en_ep; +} + +/* + * The max stream number is saved as a nibble so for the 30 possible endpoints + * we only 15 bytes of memory. Therefore we are limited to max 16 streams (0 + * means we use only 1 stream). The maximum according to the spec is 16bit so + * if the 16 stream limit is about to go, the array size should be incremented + * to 30 elements of type u16. + */ +static int get_max_streams_for_pipe(struct dummy_hcd *dum_hcd, + unsigned int pipe) +{ + int max_streams; + + max_streams = dum_hcd->num_stream[usb_pipeendpoint(pipe)]; + if (usb_pipeout(pipe)) + max_streams >>= 4; + else + max_streams &= 0xf; + max_streams++; + return max_streams; +} + +static void set_max_streams_for_pipe(struct dummy_hcd *dum_hcd, + unsigned int pipe, unsigned int streams) +{ + int max_streams; + + streams--; + max_streams = dum_hcd->num_stream[usb_pipeendpoint(pipe)]; + if (usb_pipeout(pipe)) { + streams <<= 4; + max_streams &= 0xf; + } else { + max_streams &= 0xf0; + } + max_streams |= streams; + dum_hcd->num_stream[usb_pipeendpoint(pipe)] = max_streams; +} + +static int dummy_validate_stream(struct dummy_hcd *dum_hcd, struct urb *urb) +{ + unsigned int max_streams; + int enabled; + + enabled = dummy_ep_stream_en(dum_hcd, urb); + if (!urb->stream_id) { + if (enabled) + return -EINVAL; + return 0; + } + if (!enabled) + return -EINVAL; + + max_streams = get_max_streams_for_pipe(dum_hcd, + usb_pipeendpoint(urb->pipe)); + if (urb->stream_id > max_streams) { + dev_err(dummy_dev(dum_hcd), "Stream id %d is out of range.\n", + urb->stream_id); + BUG(); + return -EINVAL; + } + return 0; +} + +static int dummy_urb_enqueue( + struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags +) { + struct dummy_hcd *dum_hcd; + struct urbp *urbp; + unsigned long flags; + int rc; + + urbp = kmalloc(sizeof *urbp, mem_flags); + if (!urbp) + return -ENOMEM; + urbp->urb = urb; + urbp->miter_started = 0; + + dum_hcd = hcd_to_dummy_hcd(hcd); + spin_lock_irqsave(&dum_hcd->dum->lock, flags); + + rc = dummy_validate_stream(dum_hcd, urb); + if (rc) { + kfree(urbp); + goto done; + } + + rc = usb_hcd_link_urb_to_ep(hcd, urb); + if (rc) { + kfree(urbp); + goto done; + } + + if (!dum_hcd->udev) { + dum_hcd->udev = urb->dev; + usb_get_dev(dum_hcd->udev); + } else if (unlikely(dum_hcd->udev != urb->dev)) + dev_err(dummy_dev(dum_hcd), "usb_device address has changed!\n"); + + list_add_tail(&urbp->urbp_list, &dum_hcd->urbp_list); + urb->hcpriv = urbp; + if (usb_pipetype(urb->pipe) == PIPE_CONTROL) + urb->error_count = 1; /* mark as a new urb */ + + /* kick the scheduler, it'll do the rest */ + if (!timer_pending(&dum_hcd->timer)) + mod_timer(&dum_hcd->timer, jiffies + 1); + + done: + spin_unlock_irqrestore(&dum_hcd->dum->lock, flags); + return rc; +} + +static int dummy_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct dummy_hcd *dum_hcd; + unsigned long flags; + int rc; + + /* giveback happens automatically in timer callback, + * so make sure the callback happens */ + dum_hcd = hcd_to_dummy_hcd(hcd); + spin_lock_irqsave(&dum_hcd->dum->lock, flags); + + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (!rc && dum_hcd->rh_state != DUMMY_RH_RUNNING && + !list_empty(&dum_hcd->urbp_list)) + mod_timer(&dum_hcd->timer, jiffies); + + spin_unlock_irqrestore(&dum_hcd->dum->lock, flags); + return rc; +} + +static int dummy_perform_transfer(struct urb *urb, struct dummy_request *req, + u32 len) +{ + void *ubuf, *rbuf; + struct urbp *urbp = urb->hcpriv; + int to_host; + struct sg_mapping_iter *miter = &urbp->miter; + u32 trans = 0; + u32 this_sg; + bool next_sg; + + to_host = usb_pipein(urb->pipe); + rbuf = req->req.buf + req->req.actual; + + if (!urb->num_sgs) { + ubuf = urb->transfer_buffer + urb->actual_length; + if (to_host) + memcpy(ubuf, rbuf, len); + else + memcpy(rbuf, ubuf, len); + return len; + } + + if (!urbp->miter_started) { + u32 flags = SG_MITER_ATOMIC; + + if (to_host) + flags |= SG_MITER_TO_SG; + else + flags |= SG_MITER_FROM_SG; + + sg_miter_start(miter, urb->sg, urb->num_sgs, flags); + urbp->miter_started = 1; + } + next_sg = sg_miter_next(miter); + if (next_sg == false) { + WARN_ON_ONCE(1); + return -EINVAL; + } + do { + ubuf = miter->addr; + this_sg = min_t(u32, len, miter->length); + miter->consumed = this_sg; + trans += this_sg; + + if (to_host) + memcpy(ubuf, rbuf, this_sg); + else + memcpy(rbuf, ubuf, this_sg); + len -= this_sg; + + if (!len) + break; + next_sg = sg_miter_next(miter); + if (next_sg == false) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + rbuf += this_sg; + } while (1); + + sg_miter_stop(miter); + return trans; +} + +/* transfer up to a frame's worth; caller must own lock */ +static int transfer(struct dummy_hcd *dum_hcd, struct urb *urb, + struct dummy_ep *ep, int limit, int *status) +{ + struct dummy *dum = dum_hcd->dum; + struct dummy_request *req; + +top: + /* if there's no request queued, the device is NAKing; return */ + list_for_each_entry(req, &ep->queue, queue) { + unsigned host_len, dev_len, len; + int is_short, to_host; + int rescan = 0; + + if (dummy_ep_stream_en(dum_hcd, urb)) { + if ((urb->stream_id != req->req.stream_id)) + continue; + } + + /* 1..N packets of ep->ep.maxpacket each ... the last one + * may be short (including zero length). + * + * writer can send a zlp explicitly (length 0) or implicitly + * (length mod maxpacket zero, and 'zero' flag); they always + * terminate reads. + */ + host_len = urb->transfer_buffer_length - urb->actual_length; + dev_len = req->req.length - req->req.actual; + len = min(host_len, dev_len); + + /* FIXME update emulated data toggle too */ + + to_host = usb_pipein(urb->pipe); + if (unlikely(len == 0)) + is_short = 1; + else { + /* not enough bandwidth left? */ + if (limit < ep->ep.maxpacket && limit < len) + break; + len = min_t(unsigned, len, limit); + if (len == 0) + break; + + /* use an extra pass for the final short packet */ + if (len > ep->ep.maxpacket) { + rescan = 1; + len -= (len % ep->ep.maxpacket); + } + is_short = (len % ep->ep.maxpacket) != 0; + + len = dummy_perform_transfer(urb, req, len); + + ep->last_io = jiffies; + if ((int)len < 0) { + req->req.status = len; + } else { + limit -= len; + urb->actual_length += len; + req->req.actual += len; + } + } + + /* short packets terminate, maybe with overflow/underflow. + * it's only really an error to write too much. + * + * partially filling a buffer optionally blocks queue advances + * (so completion handlers can clean up the queue) but we don't + * need to emulate such data-in-flight. + */ + if (is_short) { + if (host_len == dev_len) { + req->req.status = 0; + *status = 0; + } else if (to_host) { + req->req.status = 0; + if (dev_len > host_len) + *status = -EOVERFLOW; + else + *status = 0; + } else if (!to_host) { + *status = 0; + if (host_len > dev_len) + req->req.status = -EOVERFLOW; + else + req->req.status = 0; + } + + /* many requests terminate without a short packet */ + } else { + if (req->req.length == req->req.actual + && !req->req.zero) + req->req.status = 0; + if (urb->transfer_buffer_length == urb->actual_length + && !(urb->transfer_flags + & URB_ZERO_PACKET)) + *status = 0; + } + + /* device side completion --> continuable */ + if (req->req.status != -EINPROGRESS) { + list_del_init(&req->queue); + + spin_unlock(&dum->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&dum->lock); + + /* requests might have been unlinked... */ + rescan = 1; + } + + /* host side completion --> terminate */ + if (*status != -EINPROGRESS) + break; + + /* rescan to continue with any other queued i/o */ + if (rescan) + goto top; + } + return limit; +} + +static int periodic_bytes(struct dummy *dum, struct dummy_ep *ep) +{ + int limit = ep->ep.maxpacket; + + if (dum->gadget.speed == USB_SPEED_HIGH) { + int tmp; + + /* high bandwidth mode */ + tmp = usb_endpoint_maxp(ep->desc); + tmp = (tmp >> 11) & 0x03; + tmp *= 8 /* applies to entire frame */; + limit += limit * tmp; + } + if (dum->gadget.speed == USB_SPEED_SUPER) { + switch (usb_endpoint_type(ep->desc)) { + case USB_ENDPOINT_XFER_ISOC: + /* Sec. 4.4.8.2 USB3.0 Spec */ + limit = 3 * 16 * 1024 * 8; + break; + case USB_ENDPOINT_XFER_INT: + /* Sec. 4.4.7.2 USB3.0 Spec */ + limit = 3 * 1024 * 8; + break; + case USB_ENDPOINT_XFER_BULK: + default: + break; + } + } + return limit; +} + +#define is_active(dum_hcd) ((dum_hcd->port_status & \ + (USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE | \ + USB_PORT_STAT_SUSPEND)) \ + == (USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE)) + +static struct dummy_ep *find_endpoint(struct dummy *dum, u8 address) +{ + int i; + + if (!is_active((dum->gadget.speed == USB_SPEED_SUPER ? + dum->ss_hcd : dum->hs_hcd))) + return NULL; + if ((address & ~USB_DIR_IN) == 0) + return &dum->ep[0]; + for (i = 1; i < DUMMY_ENDPOINTS; i++) { + struct dummy_ep *ep = &dum->ep[i]; + + if (!ep->desc) + continue; + if (ep->desc->bEndpointAddress == address) + return ep; + } + return NULL; +} + +#undef is_active + +#define Dev_Request (USB_TYPE_STANDARD | USB_RECIP_DEVICE) +#define Dev_InRequest (Dev_Request | USB_DIR_IN) +#define Intf_Request (USB_TYPE_STANDARD | USB_RECIP_INTERFACE) +#define Intf_InRequest (Intf_Request | USB_DIR_IN) +#define Ep_Request (USB_TYPE_STANDARD | USB_RECIP_ENDPOINT) +#define Ep_InRequest (Ep_Request | USB_DIR_IN) + + +/** + * handle_control_request() - handles all control transfers + * @dum: pointer to dummy (the_controller) + * @urb: the urb request to handle + * @setup: pointer to the setup data for a USB device control + * request + * @status: pointer to request handling status + * + * Return 0 - if the request was handled + * 1 - if the request wasn't handles + * error code on error + */ +static int handle_control_request(struct dummy_hcd *dum_hcd, struct urb *urb, + struct usb_ctrlrequest *setup, + int *status) +{ + struct dummy_ep *ep2; + struct dummy *dum = dum_hcd->dum; + int ret_val = 1; + unsigned w_index; + unsigned w_value; + + w_index = le16_to_cpu(setup->wIndex); + w_value = le16_to_cpu(setup->wValue); + switch (setup->bRequest) { + case USB_REQ_SET_ADDRESS: + if (setup->bRequestType != Dev_Request) + break; + dum->address = w_value; + *status = 0; + dev_dbg(udc_dev(dum), "set_address = %d\n", + w_value); + ret_val = 0; + break; + case USB_REQ_SET_FEATURE: + if (setup->bRequestType == Dev_Request) { + ret_val = 0; + switch (w_value) { + case USB_DEVICE_REMOTE_WAKEUP: + break; + case USB_DEVICE_B_HNP_ENABLE: + dum->gadget.b_hnp_enable = 1; + break; + case USB_DEVICE_A_HNP_SUPPORT: + dum->gadget.a_hnp_support = 1; + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + dum->gadget.a_alt_hnp_support = 1; + break; + case USB_DEVICE_U1_ENABLE: + if (dummy_hcd_to_hcd(dum_hcd)->speed == + HCD_USB3) + w_value = USB_DEV_STAT_U1_ENABLED; + else + ret_val = -EOPNOTSUPP; + break; + case USB_DEVICE_U2_ENABLE: + if (dummy_hcd_to_hcd(dum_hcd)->speed == + HCD_USB3) + w_value = USB_DEV_STAT_U2_ENABLED; + else + ret_val = -EOPNOTSUPP; + break; + case USB_DEVICE_LTM_ENABLE: + if (dummy_hcd_to_hcd(dum_hcd)->speed == + HCD_USB3) + w_value = USB_DEV_STAT_LTM_ENABLED; + else + ret_val = -EOPNOTSUPP; + break; + default: + ret_val = -EOPNOTSUPP; + } + if (ret_val == 0) { + dum->devstatus |= (1 << w_value); + *status = 0; + } + } else if (setup->bRequestType == Ep_Request) { + /* endpoint halt */ + ep2 = find_endpoint(dum, w_index); + if (!ep2 || ep2->ep.name == ep0name) { + ret_val = -EOPNOTSUPP; + break; + } + ep2->halted = 1; + ret_val = 0; + *status = 0; + } + break; + case USB_REQ_CLEAR_FEATURE: + if (setup->bRequestType == Dev_Request) { + ret_val = 0; + switch (w_value) { + case USB_DEVICE_REMOTE_WAKEUP: + w_value = USB_DEVICE_REMOTE_WAKEUP; + break; + case USB_DEVICE_U1_ENABLE: + if (dummy_hcd_to_hcd(dum_hcd)->speed == + HCD_USB3) + w_value = USB_DEV_STAT_U1_ENABLED; + else + ret_val = -EOPNOTSUPP; + break; + case USB_DEVICE_U2_ENABLE: + if (dummy_hcd_to_hcd(dum_hcd)->speed == + HCD_USB3) + w_value = USB_DEV_STAT_U2_ENABLED; + else + ret_val = -EOPNOTSUPP; + break; + case USB_DEVICE_LTM_ENABLE: + if (dummy_hcd_to_hcd(dum_hcd)->speed == + HCD_USB3) + w_value = USB_DEV_STAT_LTM_ENABLED; + else + ret_val = -EOPNOTSUPP; + break; + default: + ret_val = -EOPNOTSUPP; + break; + } + if (ret_val == 0) { + dum->devstatus &= ~(1 << w_value); + *status = 0; + } + } else if (setup->bRequestType == Ep_Request) { + /* endpoint halt */ + ep2 = find_endpoint(dum, w_index); + if (!ep2) { + ret_val = -EOPNOTSUPP; + break; + } + if (!ep2->wedged) + ep2->halted = 0; + ret_val = 0; + *status = 0; + } + break; + case USB_REQ_GET_STATUS: + if (setup->bRequestType == Dev_InRequest + || setup->bRequestType == Intf_InRequest + || setup->bRequestType == Ep_InRequest) { + char *buf; + /* + * device: remote wakeup, selfpowered + * interface: nothing + * endpoint: halt + */ + buf = (char *)urb->transfer_buffer; + if (urb->transfer_buffer_length > 0) { + if (setup->bRequestType == Ep_InRequest) { + ep2 = find_endpoint(dum, w_index); + if (!ep2) { + ret_val = -EOPNOTSUPP; + break; + } + buf[0] = ep2->halted; + } else if (setup->bRequestType == + Dev_InRequest) { + buf[0] = (u8)dum->devstatus; + } else + buf[0] = 0; + } + if (urb->transfer_buffer_length > 1) + buf[1] = 0; + urb->actual_length = min_t(u32, 2, + urb->transfer_buffer_length); + ret_val = 0; + *status = 0; + } + break; + } + return ret_val; +} + +/* drive both sides of the transfers; looks like irq handlers to + * both drivers except the callbacks aren't in_irq(). + */ +static void dummy_timer(unsigned long _dum_hcd) +{ + struct dummy_hcd *dum_hcd = (struct dummy_hcd *) _dum_hcd; + struct dummy *dum = dum_hcd->dum; + struct urbp *urbp, *tmp; + unsigned long flags; + int limit, total; + int i; + + /* simplistic model for one frame's bandwidth */ + switch (dum->gadget.speed) { + case USB_SPEED_LOW: + total = 8/*bytes*/ * 12/*packets*/; + break; + case USB_SPEED_FULL: + total = 64/*bytes*/ * 19/*packets*/; + break; + case USB_SPEED_HIGH: + total = 512/*bytes*/ * 13/*packets*/ * 8/*uframes*/; + break; + case USB_SPEED_SUPER: + /* Bus speed is 500000 bytes/ms, so use a little less */ + total = 490000; + break; + default: + dev_err(dummy_dev(dum_hcd), "bogus device speed\n"); + return; + } + + /* FIXME if HZ != 1000 this will probably misbehave ... */ + + /* look at each urb queued by the host side driver */ + spin_lock_irqsave(&dum->lock, flags); + + if (!dum_hcd->udev) { + dev_err(dummy_dev(dum_hcd), + "timer fired with no URBs pending?\n"); + spin_unlock_irqrestore(&dum->lock, flags); + return; + } + + for (i = 0; i < DUMMY_ENDPOINTS; i++) { + if (!ep_name[i]) + break; + dum->ep[i].already_seen = 0; + } + +restart: + list_for_each_entry_safe(urbp, tmp, &dum_hcd->urbp_list, urbp_list) { + struct urb *urb; + struct dummy_request *req; + u8 address; + struct dummy_ep *ep = NULL; + int type; + int status = -EINPROGRESS; + + urb = urbp->urb; + if (urb->unlinked) + goto return_urb; + else if (dum_hcd->rh_state != DUMMY_RH_RUNNING) + continue; + type = usb_pipetype(urb->pipe); + + /* used up this frame's non-periodic bandwidth? + * FIXME there's infinite bandwidth for control and + * periodic transfers ... unrealistic. + */ + if (total <= 0 && type == PIPE_BULK) + continue; + + /* find the gadget's ep for this request (if configured) */ + address = usb_pipeendpoint (urb->pipe); + if (usb_pipein(urb->pipe)) + address |= USB_DIR_IN; + ep = find_endpoint(dum, address); + if (!ep) { + /* set_configuration() disagreement */ + dev_dbg(dummy_dev(dum_hcd), + "no ep configured for urb %p\n", + urb); + status = -EPROTO; + goto return_urb; + } + + if (ep->already_seen) + continue; + ep->already_seen = 1; + if (ep == &dum->ep[0] && urb->error_count) { + ep->setup_stage = 1; /* a new urb */ + urb->error_count = 0; + } + if (ep->halted && !ep->setup_stage) { + /* NOTE: must not be iso! */ + dev_dbg(dummy_dev(dum_hcd), "ep %s halted, urb %p\n", + ep->ep.name, urb); + status = -EPIPE; + goto return_urb; + } + /* FIXME make sure both ends agree on maxpacket */ + + /* handle control requests */ + if (ep == &dum->ep[0] && ep->setup_stage) { + struct usb_ctrlrequest setup; + int value = 1; + + setup = *(struct usb_ctrlrequest *) urb->setup_packet; + /* paranoia, in case of stale queued data */ + list_for_each_entry(req, &ep->queue, queue) { + list_del_init(&req->queue); + req->req.status = -EOVERFLOW; + dev_dbg(udc_dev(dum), "stale req = %p\n", + req); + + spin_unlock(&dum->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&dum->lock); + ep->already_seen = 0; + goto restart; + } + + /* gadget driver never sees set_address or operations + * on standard feature flags. some hardware doesn't + * even expose them. + */ + ep->last_io = jiffies; + ep->setup_stage = 0; + ep->halted = 0; + + value = handle_control_request(dum_hcd, urb, &setup, + &status); + + /* gadget driver handles all other requests. block + * until setup() returns; no reentrancy issues etc. + */ + if (value > 0) { + spin_unlock(&dum->lock); + value = dum->driver->setup(&dum->gadget, + &setup); + spin_lock(&dum->lock); + + if (value >= 0) { + /* no delays (max 64KB data stage) */ + limit = 64*1024; + goto treat_control_like_bulk; + } + /* error, see below */ + } + + if (value < 0) { + if (value != -EOPNOTSUPP) + dev_dbg(udc_dev(dum), + "setup --> %d\n", + value); + status = -EPIPE; + urb->actual_length = 0; + } + + goto return_urb; + } + + /* non-control requests */ + limit = total; + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + /* FIXME is it urb->interval since the last xfer? + * use urb->iso_frame_desc[i]. + * complete whether or not ep has requests queued. + * report random errors, to debug drivers. + */ + limit = max(limit, periodic_bytes(dum, ep)); + status = -ENOSYS; + break; + + case PIPE_INTERRUPT: + /* FIXME is it urb->interval since the last xfer? + * this almost certainly polls too fast. + */ + limit = max(limit, periodic_bytes(dum, ep)); + /* FALLTHROUGH */ + + default: +treat_control_like_bulk: + ep->last_io = jiffies; + total = transfer(dum_hcd, urb, ep, limit, &status); + break; + } + + /* incomplete transfer? */ + if (status == -EINPROGRESS) + continue; + +return_urb: + list_del(&urbp->urbp_list); + kfree(urbp); + if (ep) + ep->already_seen = ep->setup_stage = 0; + + usb_hcd_unlink_urb_from_ep(dummy_hcd_to_hcd(dum_hcd), urb); + spin_unlock(&dum->lock); + usb_hcd_giveback_urb(dummy_hcd_to_hcd(dum_hcd), urb, status); + spin_lock(&dum->lock); + + goto restart; + } + + if (list_empty(&dum_hcd->urbp_list)) { + usb_put_dev(dum_hcd->udev); + dum_hcd->udev = NULL; + } else if (dum_hcd->rh_state == DUMMY_RH_RUNNING) { + /* want a 1 msec delay here */ + mod_timer(&dum_hcd->timer, jiffies + msecs_to_jiffies(1)); + } + + spin_unlock_irqrestore(&dum->lock, flags); +} + +/*-------------------------------------------------------------------------*/ + +#define PORT_C_MASK \ + ((USB_PORT_STAT_C_CONNECTION \ + | USB_PORT_STAT_C_ENABLE \ + | USB_PORT_STAT_C_SUSPEND \ + | USB_PORT_STAT_C_OVERCURRENT \ + | USB_PORT_STAT_C_RESET) << 16) + +static int dummy_hub_status(struct usb_hcd *hcd, char *buf) +{ + struct dummy_hcd *dum_hcd; + unsigned long flags; + int retval = 0; + + dum_hcd = hcd_to_dummy_hcd(hcd); + + spin_lock_irqsave(&dum_hcd->dum->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) + goto done; + + if (dum_hcd->resuming && time_after_eq(jiffies, dum_hcd->re_timeout)) { + dum_hcd->port_status |= (USB_PORT_STAT_C_SUSPEND << 16); + dum_hcd->port_status &= ~USB_PORT_STAT_SUSPEND; + set_link_state(dum_hcd); + } + + if ((dum_hcd->port_status & PORT_C_MASK) != 0) { + *buf = (1 << 1); + dev_dbg(dummy_dev(dum_hcd), "port status 0x%08x has changes\n", + dum_hcd->port_status); + retval = 1; + if (dum_hcd->rh_state == DUMMY_RH_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + } +done: + spin_unlock_irqrestore(&dum_hcd->dum->lock, flags); + return retval; +} + +/* usb 3.0 root hub device descriptor */ +static struct { + struct usb_bos_descriptor bos; + struct usb_ss_cap_descriptor ss_cap; +} __packed usb3_bos_desc = { + + .bos = { + .bLength = USB_DT_BOS_SIZE, + .bDescriptorType = USB_DT_BOS, + .wTotalLength = cpu_to_le16(sizeof(usb3_bos_desc)), + .bNumDeviceCaps = 1, + }, + .ss_cap = { + .bLength = USB_DT_USB_SS_CAP_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_SS_CAP_TYPE, + .wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION), + .bFunctionalitySupport = ilog2(USB_5GBPS_OPERATION), + }, +}; + +static inline void +ss_hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof *desc); + desc->bDescriptorType = 0x2a; + desc->bDescLength = 12; + desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->bNbrPorts = 1; + desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ + desc->u.ss.DeviceRemovable = 0xffff; +} + +static inline void hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof *desc); + desc->bDescriptorType = 0x29; + desc->bDescLength = 9; + desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->bNbrPorts = 1; + desc->u.hs.DeviceRemovable[0] = 0xff; + desc->u.hs.DeviceRemovable[1] = 0xff; +} + +static int dummy_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct dummy_hcd *dum_hcd; + int retval = 0; + unsigned long flags; + + if (!HCD_HW_ACCESSIBLE(hcd)) + return -ETIMEDOUT; + + dum_hcd = hcd_to_dummy_hcd(hcd); + + spin_lock_irqsave(&dum_hcd->dum->lock, flags); + switch (typeReq) { + case ClearHubFeature: + break; + case ClearPortFeature: + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if (hcd->speed == HCD_USB3) { + dev_dbg(dummy_dev(dum_hcd), + "USB_PORT_FEAT_SUSPEND req not " + "supported for USB 3.0 roothub\n"); + goto error; + } + if (dum_hcd->port_status & USB_PORT_STAT_SUSPEND) { + /* 20msec resume signaling */ + dum_hcd->resuming = 1; + dum_hcd->re_timeout = jiffies + + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_POWER: + if (hcd->speed == HCD_USB3) { + if (dum_hcd->port_status & USB_PORT_STAT_POWER) + dev_dbg(dummy_dev(dum_hcd), + "power-off\n"); + } else + if (dum_hcd->port_status & + USB_SS_PORT_STAT_POWER) + dev_dbg(dummy_dev(dum_hcd), + "power-off\n"); + /* FALLS THROUGH */ + default: + dum_hcd->port_status &= ~(1 << wValue); + set_link_state(dum_hcd); + } + break; + case GetHubDescriptor: + if (hcd->speed == HCD_USB3 && + (wLength < USB_DT_SS_HUB_SIZE || + wValue != (USB_DT_SS_HUB << 8))) { + dev_dbg(dummy_dev(dum_hcd), + "Wrong hub descriptor type for " + "USB 3.0 roothub.\n"); + goto error; + } + if (hcd->speed == HCD_USB3) + ss_hub_descriptor((struct usb_hub_descriptor *) buf); + else + hub_descriptor((struct usb_hub_descriptor *) buf); + break; + + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + if (hcd->speed != HCD_USB3) + goto error; + + if ((wValue >> 8) != USB_DT_BOS) + goto error; + + memcpy(buf, &usb3_bos_desc, sizeof(usb3_bos_desc)); + retval = sizeof(usb3_bos_desc); + break; + + case GetHubStatus: + *(__le32 *) buf = cpu_to_le32(0); + break; + case GetPortStatus: + if (wIndex != 1) + retval = -EPIPE; + + /* whoever resets or resumes must GetPortStatus to + * complete it!! + */ + if (dum_hcd->resuming && + time_after_eq(jiffies, dum_hcd->re_timeout)) { + dum_hcd->port_status |= (USB_PORT_STAT_C_SUSPEND << 16); + dum_hcd->port_status &= ~USB_PORT_STAT_SUSPEND; + } + if ((dum_hcd->port_status & USB_PORT_STAT_RESET) != 0 && + time_after_eq(jiffies, dum_hcd->re_timeout)) { + dum_hcd->port_status |= (USB_PORT_STAT_C_RESET << 16); + dum_hcd->port_status &= ~USB_PORT_STAT_RESET; + if (dum_hcd->dum->pullup) { + dum_hcd->port_status |= USB_PORT_STAT_ENABLE; + + if (hcd->speed < HCD_USB3) { + switch (dum_hcd->dum->gadget.speed) { + case USB_SPEED_HIGH: + dum_hcd->port_status |= + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + dum_hcd->dum->gadget.ep0-> + maxpacket = 8; + dum_hcd->port_status |= + USB_PORT_STAT_LOW_SPEED; + break; + default: + dum_hcd->dum->gadget.speed = + USB_SPEED_FULL; + break; + } + } + } + } + set_link_state(dum_hcd); + ((__le16 *) buf)[0] = cpu_to_le16(dum_hcd->port_status); + ((__le16 *) buf)[1] = cpu_to_le16(dum_hcd->port_status >> 16); + break; + case SetHubFeature: + retval = -EPIPE; + break; + case SetPortFeature: + switch (wValue) { + case USB_PORT_FEAT_LINK_STATE: + if (hcd->speed != HCD_USB3) { + dev_dbg(dummy_dev(dum_hcd), + "USB_PORT_FEAT_LINK_STATE req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* + * Since this is dummy we don't have an actual link so + * there is nothing to do for the SET_LINK_STATE cmd + */ + break; + case USB_PORT_FEAT_U1_TIMEOUT: + case USB_PORT_FEAT_U2_TIMEOUT: + /* TODO: add suspend/resume support! */ + if (hcd->speed != HCD_USB3) { + dev_dbg(dummy_dev(dum_hcd), + "USB_PORT_FEAT_U1/2_TIMEOUT req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + break; + case USB_PORT_FEAT_SUSPEND: + /* Applicable only for USB2.0 hub */ + if (hcd->speed == HCD_USB3) { + dev_dbg(dummy_dev(dum_hcd), + "USB_PORT_FEAT_SUSPEND req not " + "supported for USB 3.0 roothub\n"); + goto error; + } + if (dum_hcd->active) { + dum_hcd->port_status |= USB_PORT_STAT_SUSPEND; + + /* HNP would happen here; for now we + * assume b_bus_req is always true. + */ + set_link_state(dum_hcd); + if (((1 << USB_DEVICE_B_HNP_ENABLE) + & dum_hcd->dum->devstatus) != 0) + dev_dbg(dummy_dev(dum_hcd), + "no HNP yet!\n"); + } + break; + case USB_PORT_FEAT_POWER: + if (hcd->speed == HCD_USB3) + dum_hcd->port_status |= USB_SS_PORT_STAT_POWER; + else + dum_hcd->port_status |= USB_PORT_STAT_POWER; + set_link_state(dum_hcd); + break; + case USB_PORT_FEAT_BH_PORT_RESET: + /* Applicable only for USB3.0 hub */ + if (hcd->speed != HCD_USB3) { + dev_dbg(dummy_dev(dum_hcd), + "USB_PORT_FEAT_BH_PORT_RESET req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* FALLS THROUGH */ + case USB_PORT_FEAT_RESET: + /* if it's already enabled, disable */ + if (hcd->speed == HCD_USB3) { + dum_hcd->port_status = 0; + dum_hcd->port_status = + (USB_SS_PORT_STAT_POWER | + USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_RESET); + } else + dum_hcd->port_status &= ~(USB_PORT_STAT_ENABLE + | USB_PORT_STAT_LOW_SPEED + | USB_PORT_STAT_HIGH_SPEED); + /* + * We want to reset device status. All but the + * Self powered feature + */ + dum_hcd->dum->devstatus &= + (1 << USB_DEVICE_SELF_POWERED); + /* + * FIXME USB3.0: what is the correct reset signaling + * interval? Is it still 50msec as for HS? + */ + dum_hcd->re_timeout = jiffies + msecs_to_jiffies(50); + /* FALLS THROUGH */ + default: + if (hcd->speed == HCD_USB3) { + if ((dum_hcd->port_status & + USB_SS_PORT_STAT_POWER) != 0) { + dum_hcd->port_status |= (1 << wValue); + set_link_state(dum_hcd); + } + } else + if ((dum_hcd->port_status & + USB_PORT_STAT_POWER) != 0) { + dum_hcd->port_status |= (1 << wValue); + set_link_state(dum_hcd); + } + } + break; + case GetPortErrorCount: + if (hcd->speed != HCD_USB3) { + dev_dbg(dummy_dev(dum_hcd), + "GetPortErrorCount req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* We'll always return 0 since this is a dummy hub */ + *(__le32 *) buf = cpu_to_le32(0); + break; + case SetHubDepth: + if (hcd->speed != HCD_USB3) { + dev_dbg(dummy_dev(dum_hcd), + "SetHubDepth req not supported for " + "USB 2.0 roothub\n"); + goto error; + } + break; + default: + dev_dbg(dummy_dev(dum_hcd), + "hub control req%04x v%04x i%04x l%d\n", + typeReq, wValue, wIndex, wLength); +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&dum_hcd->dum->lock, flags); + + if ((dum_hcd->port_status & PORT_C_MASK) != 0) + usb_hcd_poll_rh_status(hcd); + return retval; +} + +static int dummy_bus_suspend(struct usb_hcd *hcd) +{ + struct dummy_hcd *dum_hcd = hcd_to_dummy_hcd(hcd); + + dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); + + spin_lock_irq(&dum_hcd->dum->lock); + dum_hcd->rh_state = DUMMY_RH_SUSPENDED; + set_link_state(dum_hcd); + hcd->state = HC_STATE_SUSPENDED; + spin_unlock_irq(&dum_hcd->dum->lock); + return 0; +} + +static int dummy_bus_resume(struct usb_hcd *hcd) +{ + struct dummy_hcd *dum_hcd = hcd_to_dummy_hcd(hcd); + int rc = 0; + + dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); + + spin_lock_irq(&dum_hcd->dum->lock); + if (!HCD_HW_ACCESSIBLE(hcd)) { + rc = -ESHUTDOWN; + } else { + dum_hcd->rh_state = DUMMY_RH_RUNNING; + set_link_state(dum_hcd); + if (!list_empty(&dum_hcd->urbp_list)) + mod_timer(&dum_hcd->timer, jiffies); + hcd->state = HC_STATE_RUNNING; + } + spin_unlock_irq(&dum_hcd->dum->lock); + return rc; +} + +/*-------------------------------------------------------------------------*/ + +static inline ssize_t show_urb(char *buf, size_t size, struct urb *urb) +{ + int ep = usb_pipeendpoint(urb->pipe); + + return snprintf(buf, size, + "urb/%p %s ep%d%s%s len %d/%d\n", + urb, + ({ char *s; + switch (urb->dev->speed) { + case USB_SPEED_LOW: + s = "ls"; + break; + case USB_SPEED_FULL: + s = "fs"; + break; + case USB_SPEED_HIGH: + s = "hs"; + break; + case USB_SPEED_SUPER: + s = "ss"; + break; + default: + s = "?"; + break; + }; s; }), + ep, ep ? (usb_pipein(urb->pipe) ? "in" : "out") : "", + ({ char *s; \ + switch (usb_pipetype(urb->pipe)) { \ + case PIPE_CONTROL: \ + s = ""; \ + break; \ + case PIPE_BULK: \ + s = "-bulk"; \ + break; \ + case PIPE_INTERRUPT: \ + s = "-int"; \ + break; \ + default: \ + s = "-iso"; \ + break; \ + }; s; }), + urb->actual_length, urb->transfer_buffer_length); +} + +static ssize_t show_urbs(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct dummy_hcd *dum_hcd = hcd_to_dummy_hcd(hcd); + struct urbp *urbp; + size_t size = 0; + unsigned long flags; + + spin_lock_irqsave(&dum_hcd->dum->lock, flags); + list_for_each_entry(urbp, &dum_hcd->urbp_list, urbp_list) { + size_t temp; + + temp = show_urb(buf, PAGE_SIZE - size, urbp->urb); + buf += temp; + size += temp; + } + spin_unlock_irqrestore(&dum_hcd->dum->lock, flags); + + return size; +} +static DEVICE_ATTR(urbs, S_IRUGO, show_urbs, NULL); + +static int dummy_start_ss(struct dummy_hcd *dum_hcd) +{ + init_timer(&dum_hcd->timer); + dum_hcd->timer.function = dummy_timer; + dum_hcd->timer.data = (unsigned long)dum_hcd; + dum_hcd->rh_state = DUMMY_RH_RUNNING; + dum_hcd->stream_en_ep = 0; + INIT_LIST_HEAD(&dum_hcd->urbp_list); + dummy_hcd_to_hcd(dum_hcd)->power_budget = POWER_BUDGET; + dummy_hcd_to_hcd(dum_hcd)->state = HC_STATE_RUNNING; + dummy_hcd_to_hcd(dum_hcd)->uses_new_polling = 1; +#ifdef CONFIG_USB_OTG + dummy_hcd_to_hcd(dum_hcd)->self.otg_port = 1; +#endif + return 0; + + /* FIXME 'urbs' should be a per-device thing, maybe in usbcore */ + return device_create_file(dummy_dev(dum_hcd), &dev_attr_urbs); +} + +static int dummy_start(struct usb_hcd *hcd) +{ + struct dummy_hcd *dum_hcd = hcd_to_dummy_hcd(hcd); + + /* + * MASTER side init ... we emulate a root hub that'll only ever + * talk to one device (the slave side). Also appears in sysfs, + * just like more familiar pci-based HCDs. + */ + if (!usb_hcd_is_primary_hcd(hcd)) + return dummy_start_ss(dum_hcd); + + spin_lock_init(&dum_hcd->dum->lock); + init_timer(&dum_hcd->timer); + dum_hcd->timer.function = dummy_timer; + dum_hcd->timer.data = (unsigned long)dum_hcd; + dum_hcd->rh_state = DUMMY_RH_RUNNING; + + INIT_LIST_HEAD(&dum_hcd->urbp_list); + + hcd->power_budget = POWER_BUDGET; + hcd->state = HC_STATE_RUNNING; + hcd->uses_new_polling = 1; + +#ifdef CONFIG_USB_OTG + hcd->self.otg_port = 1; +#endif + + /* FIXME 'urbs' should be a per-device thing, maybe in usbcore */ + return device_create_file(dummy_dev(dum_hcd), &dev_attr_urbs); +} + +static void dummy_stop(struct usb_hcd *hcd) +{ + struct dummy *dum; + + dum = hcd_to_dummy_hcd(hcd)->dum; + device_remove_file(dummy_dev(hcd_to_dummy_hcd(hcd)), &dev_attr_urbs); + usb_gadget_unregister_driver(dum->driver); + dev_info(dummy_dev(hcd_to_dummy_hcd(hcd)), "stopped\n"); +} + +/*-------------------------------------------------------------------------*/ + +static int dummy_h_get_frame(struct usb_hcd *hcd) +{ + return dummy_g_get_frame(NULL); +} + +static int dummy_setup(struct usb_hcd *hcd) +{ + struct dummy *dum; + + dum = *((void **)dev_get_platdata(hcd->self.controller)); + hcd->self.sg_tablesize = ~0; + if (usb_hcd_is_primary_hcd(hcd)) { + dum->hs_hcd = hcd_to_dummy_hcd(hcd); + dum->hs_hcd->dum = dum; + /* + * Mark the first roothub as being USB 2.0. + * The USB 3.0 roothub will be registered later by + * dummy_hcd_probe() + */ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + } else { + dum->ss_hcd = hcd_to_dummy_hcd(hcd); + dum->ss_hcd->dum = dum; + hcd->speed = HCD_USB3; + hcd->self.root_hub->speed = USB_SPEED_SUPER; + } + return 0; +} + +/* Change a group of bulk endpoints to support multiple stream IDs */ +static int dummy_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int num_streams, gfp_t mem_flags) +{ + struct dummy_hcd *dum_hcd = hcd_to_dummy_hcd(hcd); + unsigned long flags; + int max_stream; + int ret_streams = num_streams; + unsigned int index; + unsigned int i; + + if (!num_eps) + return -EINVAL; + + spin_lock_irqsave(&dum_hcd->dum->lock, flags); + for (i = 0; i < num_eps; i++) { + index = dummy_get_ep_idx(&eps[i]->desc); + if ((1 << index) & dum_hcd->stream_en_ep) { + ret_streams = -EINVAL; + goto out; + } + max_stream = usb_ss_max_streams(&eps[i]->ss_ep_comp); + if (!max_stream) { + ret_streams = -EINVAL; + goto out; + } + if (max_stream < ret_streams) { + dev_dbg(dummy_dev(dum_hcd), "Ep 0x%x only supports %u " + "stream IDs.\n", + eps[i]->desc.bEndpointAddress, + max_stream); + ret_streams = max_stream; + } + } + + for (i = 0; i < num_eps; i++) { + index = dummy_get_ep_idx(&eps[i]->desc); + dum_hcd->stream_en_ep |= 1 << index; + set_max_streams_for_pipe(dum_hcd, + usb_endpoint_num(&eps[i]->desc), ret_streams); + } +out: + spin_unlock_irqrestore(&dum_hcd->dum->lock, flags); + return ret_streams; +} + +/* Reverts a group of bulk endpoints back to not using stream IDs. */ +static int dummy_free_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags) +{ + struct dummy_hcd *dum_hcd = hcd_to_dummy_hcd(hcd); + unsigned long flags; + int ret; + unsigned int index; + unsigned int i; + + spin_lock_irqsave(&dum_hcd->dum->lock, flags); + for (i = 0; i < num_eps; i++) { + index = dummy_get_ep_idx(&eps[i]->desc); + if (!((1 << index) & dum_hcd->stream_en_ep)) { + ret = -EINVAL; + goto out; + } + } + + for (i = 0; i < num_eps; i++) { + index = dummy_get_ep_idx(&eps[i]->desc); + dum_hcd->stream_en_ep &= ~(1 << index); + set_max_streams_for_pipe(dum_hcd, + usb_endpoint_num(&eps[i]->desc), 0); + } + ret = 0; +out: + spin_unlock_irqrestore(&dum_hcd->dum->lock, flags); + return ret; +} + +static struct hc_driver dummy_hcd = { + .description = (char *) driver_name, + .product_desc = "Dummy host controller", + .hcd_priv_size = sizeof(struct dummy_hcd), + + .flags = HCD_USB3 | HCD_SHARED, + + .reset = dummy_setup, + .start = dummy_start, + .stop = dummy_stop, + + .urb_enqueue = dummy_urb_enqueue, + .urb_dequeue = dummy_urb_dequeue, + + .get_frame_number = dummy_h_get_frame, + + .hub_status_data = dummy_hub_status, + .hub_control = dummy_hub_control, + .bus_suspend = dummy_bus_suspend, + .bus_resume = dummy_bus_resume, + + .alloc_streams = dummy_alloc_streams, + .free_streams = dummy_free_streams, +}; + +static int dummy_hcd_probe(struct platform_device *pdev) +{ + struct dummy *dum; + struct usb_hcd *hs_hcd; + struct usb_hcd *ss_hcd; + int retval; + + dev_info(&pdev->dev, "%s, driver " DRIVER_VERSION "\n", driver_desc); + dum = *((void **)dev_get_platdata(&pdev->dev)); + + if (!mod_data.is_super_speed) + dummy_hcd.flags = HCD_USB2; + hs_hcd = usb_create_hcd(&dummy_hcd, &pdev->dev, dev_name(&pdev->dev)); + if (!hs_hcd) + return -ENOMEM; + hs_hcd->has_tt = 1; + + retval = usb_add_hcd(hs_hcd, 0, 0); + if (retval) + goto put_usb2_hcd; + + if (mod_data.is_super_speed) { + ss_hcd = usb_create_shared_hcd(&dummy_hcd, &pdev->dev, + dev_name(&pdev->dev), hs_hcd); + if (!ss_hcd) { + retval = -ENOMEM; + goto dealloc_usb2_hcd; + } + + retval = usb_add_hcd(ss_hcd, 0, 0); + if (retval) + goto put_usb3_hcd; + } + return 0; + +put_usb3_hcd: + usb_put_hcd(ss_hcd); +dealloc_usb2_hcd: + usb_remove_hcd(hs_hcd); +put_usb2_hcd: + usb_put_hcd(hs_hcd); + dum->hs_hcd = dum->ss_hcd = NULL; + return retval; +} + +static int dummy_hcd_remove(struct platform_device *pdev) +{ + struct dummy *dum; + + dum = hcd_to_dummy_hcd(platform_get_drvdata(pdev))->dum; + + if (dum->ss_hcd) { + usb_remove_hcd(dummy_hcd_to_hcd(dum->ss_hcd)); + usb_put_hcd(dummy_hcd_to_hcd(dum->ss_hcd)); + } + + usb_remove_hcd(dummy_hcd_to_hcd(dum->hs_hcd)); + usb_put_hcd(dummy_hcd_to_hcd(dum->hs_hcd)); + + dum->hs_hcd = NULL; + dum->ss_hcd = NULL; + + return 0; +} + +static int dummy_hcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd; + struct dummy_hcd *dum_hcd; + int rc = 0; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + hcd = platform_get_drvdata(pdev); + dum_hcd = hcd_to_dummy_hcd(hcd); + if (dum_hcd->rh_state == DUMMY_RH_RUNNING) { + dev_warn(&pdev->dev, "Root hub isn't suspended!\n"); + rc = -EBUSY; + } else + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + return rc; +} + +static int dummy_hcd_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + hcd = platform_get_drvdata(pdev); + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + usb_hcd_poll_rh_status(hcd); + return 0; +} + +static struct platform_driver dummy_hcd_driver = { + .probe = dummy_hcd_probe, + .remove = dummy_hcd_remove, + .suspend = dummy_hcd_suspend, + .resume = dummy_hcd_resume, + .driver = { + .name = (char *) driver_name, + .owner = THIS_MODULE, + }, +}; + +/*-------------------------------------------------------------------------*/ +#define MAX_NUM_UDC 2 +static struct platform_device *the_udc_pdev[MAX_NUM_UDC]; +static struct platform_device *the_hcd_pdev[MAX_NUM_UDC]; + +static int __init init(void) +{ + int retval = -ENOMEM; + int i; + struct dummy *dum[MAX_NUM_UDC]; + + if (usb_disabled()) + return -ENODEV; + + if (!mod_data.is_high_speed && mod_data.is_super_speed) + return -EINVAL; + + if (mod_data.num < 1 || mod_data.num > MAX_NUM_UDC) { + pr_err("Number of emulated UDC must be in range of 1…%d\n", + MAX_NUM_UDC); + return -EINVAL; + } + + for (i = 0; i < mod_data.num; i++) { + the_hcd_pdev[i] = platform_device_alloc(driver_name, i); + if (!the_hcd_pdev[i]) { + i--; + while (i >= 0) + platform_device_put(the_hcd_pdev[i--]); + return retval; + } + } + for (i = 0; i < mod_data.num; i++) { + the_udc_pdev[i] = platform_device_alloc(gadget_name, i); + if (!the_udc_pdev[i]) { + i--; + while (i >= 0) + platform_device_put(the_udc_pdev[i--]); + goto err_alloc_udc; + } + } + for (i = 0; i < mod_data.num; i++) { + dum[i] = kzalloc(sizeof(struct dummy), GFP_KERNEL); + if (!dum[i]) { + retval = -ENOMEM; + goto err_add_pdata; + } + retval = platform_device_add_data(the_hcd_pdev[i], &dum[i], + sizeof(void *)); + if (retval) + goto err_add_pdata; + retval = platform_device_add_data(the_udc_pdev[i], &dum[i], + sizeof(void *)); + if (retval) + goto err_add_pdata; + } + + retval = platform_driver_register(&dummy_hcd_driver); + if (retval < 0) + goto err_add_pdata; + retval = platform_driver_register(&dummy_udc_driver); + if (retval < 0) + goto err_register_udc_driver; + + for (i = 0; i < mod_data.num; i++) { + retval = platform_device_add(the_hcd_pdev[i]); + if (retval < 0) { + i--; + while (i >= 0) + platform_device_del(the_hcd_pdev[i--]); + goto err_add_hcd; + } + } + for (i = 0; i < mod_data.num; i++) { + if (!dum[i]->hs_hcd || + (!dum[i]->ss_hcd && mod_data.is_super_speed)) { + /* + * The hcd was added successfully but its probe + * function failed for some reason. + */ + retval = -EINVAL; + goto err_add_udc; + } + } + + for (i = 0; i < mod_data.num; i++) { + retval = platform_device_add(the_udc_pdev[i]); + if (retval < 0) { + i--; + while (i >= 0) + platform_device_del(the_udc_pdev[i]); + goto err_add_udc; + } + } + + for (i = 0; i < mod_data.num; i++) { + if (!platform_get_drvdata(the_udc_pdev[i])) { + /* + * The udc was added successfully but its probe + * function failed for some reason. + */ + retval = -EINVAL; + goto err_probe_udc; + } + } + return retval; + +err_probe_udc: + for (i = 0; i < mod_data.num; i++) + platform_device_del(the_udc_pdev[i]); +err_add_udc: + for (i = 0; i < mod_data.num; i++) + platform_device_del(the_hcd_pdev[i]); +err_add_hcd: + platform_driver_unregister(&dummy_udc_driver); +err_register_udc_driver: + platform_driver_unregister(&dummy_hcd_driver); +err_add_pdata: + for (i = 0; i < mod_data.num; i++) + kfree(dum[i]); + for (i = 0; i < mod_data.num; i++) + platform_device_put(the_udc_pdev[i]); +err_alloc_udc: + for (i = 0; i < mod_data.num; i++) + platform_device_put(the_hcd_pdev[i]); + return retval; +} +module_init(init); + +static void __exit cleanup(void) +{ + int i; + + for (i = 0; i < mod_data.num; i++) { + struct dummy *dum; + + dum = *((void **)dev_get_platdata(&the_udc_pdev[i]->dev)); + + platform_device_unregister(the_udc_pdev[i]); + platform_device_unregister(the_hcd_pdev[i]); + kfree(dum); + } + platform_driver_unregister(&dummy_udc_driver); + platform_driver_unregister(&dummy_hcd_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/dwc_otg/Makefile b/drivers/usb/gadget/dwc_otg/Makefile new file mode 100644 index 00000000..9502328d --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/Makefile @@ -0,0 +1,34 @@ +# +# Makefile for DWC_otg Highspeed USB controller driver +# + +#PWD := $(shell pwd) +#PORTLIB := $(PWD)/../dwc_common_port + +ifeq ($(CONFIG_USB_GADGET_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + +# Use one of the following flags to compile the software in host-only or +# device-only mode. +#EXTRA_CFLAGS += -DDWC_HOST_ONLY +ifeq ($(CONFIG_SPRD_USB_DEVICE_ONLY),y) +EXTRA_CFLAGS += -DDWC_DEVICE_ONLY +endif + +#EXTRA_CFLAGS += -Dlinux -DDWC_HS_ELECT_TST +#EXTRA_CFLAGS += -DDWC_EN_ISOC +EXTRA_CFLAGS += -DDWC_LINUX +#EXTRA_CFLAGS += $(CFI) +obj-y += dwc_otg.o usb_hw.o + +dwc_otg-objs := dwc_otg_driver.o dwc_otg_attr.o +dwc_otg-objs += dwc_otg_cil.o dwc_otg_cil_intr.o +dwc_otg-objs += dwc_otg_pcd_linux.o dwc_otg_pcd.o dwc_otg_pcd_intr.o +dwc_otg-objs += dwc_common_linux.o dwc_otg_adp.o +ifndef CONFIG_SPRD_USB_DEVICE_ONLY +#host related +dwc_otg-objs += dwc_otg_hcd.o dwc_otg_hcd_intr.o +dwc_otg-objs += dwc_otg_hcd_linux.o dwc_otg_hcd_queue.o +dwc_otg-objs += dwc_otg_hcd_ddma.o +endif diff --git a/drivers/usb/gadget/dwc_otg/dwc_common_linux.c b/drivers/usb/gadget/dwc_otg/dwc_common_linux.c new file mode 100644 index 00000000..e2df2ca8 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_common_linux.c @@ -0,0 +1,1417 @@ +#include +#include +#include +#include + +#ifdef DWC_CCLIB +# include "dwc_cc.h" +#endif + +#ifdef DWC_CRYPTOLIB +# include "dwc_modpow.h" +# include "dwc_dh.h" +# include "dwc_crypto.h" +#endif + +#ifdef DWC_NOTIFYLIB +# include "dwc_notifier.h" +#endif + +/* OS-Level Implementations */ + +/* This is the Linux kernel implementation of the DWC platform library. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +# include +#else +# include +#endif + +#include +#include +#include +#include + +#include "dwc_os.h" +#include "dwc_list.h" + + +/* MISC */ + +void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size) +{ + return memset(dest, byte, size); +} + +void *DWC_MEMCPY(void *dest, void const *src, uint32_t size) +{ + return memcpy(dest, src, size); +} + +void *DWC_MEMMOVE(void *dest, void *src, uint32_t size) +{ + return memmove(dest, src, size); +} + +int DWC_MEMCMP(void *m1, void *m2, uint32_t size) +{ + return memcmp(m1, m2, size); +} + +int DWC_STRNCMP(void *s1, void *s2, uint32_t size) +{ + return strncmp(s1, s2, size); +} + +int DWC_STRCMP(void *s1, void *s2) +{ + return strcmp(s1, s2); +} + +int DWC_STRLEN(char const *str) +{ + return strlen(str); +} + +char *DWC_STRCPY(char *to, char const *from) +{ + return strcpy(to, from); +} + +char *DWC_STRDUP(char const *str) +{ + int len = DWC_STRLEN(str) + 1; + char *new = DWC_ALLOC_ATOMIC(len); + + if (!new) { + return NULL; + } + + DWC_MEMCPY(new, str, len); + return new; +} + +int DWC_ATOI(const char *str, int32_t *value) +{ + char *end = NULL; + + *value = simple_strtol(str, &end, 0); + if (*end == '\0') { + return 0; + } + + return -1; +} + +int DWC_ATOUI(const char *str, uint32_t *value) +{ + char *end = NULL; + + *value = simple_strtoul(str, &end, 0); + if (*end == '\0') { + return 0; + } + + return -1; +} + + +#ifdef DWC_UTFLIB +/* From usbstring.c */ + +int DWC_UTF8_TO_UTF16LE(uint8_t const *s, uint16_t *cp, unsigned len) +{ + int count = 0; + u8 c; + u16 uchar; + + /* this insists on correct encodings, though not minimal ones. + * BUT it currently rejects legit 4-byte UTF-8 code points, + * which need surrogate pairs. (Unicode 3.1 can use them.) + */ + while (len != 0 && (c = (u8) *s++) != 0) { + if (unlikely(c & 0x80)) { + // 2-byte sequence: + // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx + if ((c & 0xe0) == 0xc0) { + uchar = (c & 0x1f) << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + // 3-byte sequence (most CJKV characters): + // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx + } else if ((c & 0xf0) == 0xe0) { + uchar = (c & 0x0f) << 12; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + /* no bogus surrogates */ + if (0xd800 <= uchar && uchar <= 0xdfff) + goto fail; + + // 4-byte sequence (surrogate pairs, currently rare): + // 11101110wwwwzzzzyy + 110111yyyyxxxxxx + // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx + // (uuuuu = wwww + 1) + // FIXME accept the surrogate code points (only) + } else + goto fail; + } else + uchar = c; + put_unaligned (cpu_to_le16 (uchar), cp++); + count++; + len--; + } + return count; +fail: + return -1; +} +#endif /* DWC_UTFLIB */ + + +/* dwc_debug.h */ + +dwc_bool_t DWC_IN_IRQ(void) +{ + return in_irq(); +} + +dwc_bool_t DWC_IN_BH(void) +{ + return in_softirq(); +} + +void DWC_VPRINTF(char *format, va_list args) +{ + vprintk(format, args); +} + +int DWC_VSNPRINTF(char *str, int size, char *format, va_list args) +{ + return vsnprintf(str, size, format, args); +} + +void DWC_PRINTF(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} + +int DWC_SPRINTF(char *buffer, char *format, ...) +{ + int retval; + va_list args; + + va_start(args, format); + retval = vsprintf(buffer, format, args); + va_end(args); + return retval; +} + +int DWC_SNPRINTF(char *buffer, int size, char *format, ...) +{ + int retval; + va_list args; + + va_start(args, format); + retval = vsnprintf(buffer, size, format, args); + va_end(args); + return retval; +} + +void __DWC_WARN(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_PRINTF(KERN_WARNING); + DWC_VPRINTF(format, args); + va_end(args); +} + +void __DWC_ERROR(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_PRINTF(KERN_ERR); + DWC_VPRINTF(format, args); + va_end(args); +} + +void DWC_EXCEPTION(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_PRINTF(KERN_ERR); + DWC_VPRINTF(format, args); + va_end(args); + BUG_ON(1); +} + +#ifdef DEBUG +void __DWC_DEBUG(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_PRINTF(KERN_DEBUG); + DWC_VPRINTF(format, args); + va_end(args); +} +#endif + + +/* dwc_mem.h */ + +#if 0 +dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size, + uint32_t align, + uint32_t alloc) +{ + struct dma_pool *pool = dma_pool_create("Pool", NULL, + size, align, alloc); + return (dwc_pool_t *)pool; +} + +void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool) +{ + dma_pool_destroy((struct dma_pool *)pool); +} + +void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr) +{ + return dma_pool_alloc((struct dma_pool *)pool, GFP_KERNEL, dma_addr); +} + +void *DWC_DMA_POOL_ZALLOC(dwc_pool_t *pool, uint64_t *dma_addr) +{ + void *vaddr = DWC_DMA_POOL_ALLOC(pool, dma_addr); + memset(..); +} + +void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr) +{ + dma_pool_free(pool, vaddr, daddr); +} +#endif + +void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr) +{ + void *buf = dma_alloc_coherent(dma_ctx, (size_t)size, dma_addr, GFP_KERNEL); + if (!buf) { + return NULL; + } + + memset(buf, 0, (size_t)size); + return buf; +} + +void *__DWC_DMA_ALLOC_ATOMIC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr) +{ + void *buf = dma_alloc_coherent(NULL, (size_t)size, dma_addr, GFP_ATOMIC); + if (!buf) { + return NULL; + } + memset(buf, 0, (size_t)size); + return buf; +} + +void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr) +{ + dma_free_coherent(dma_ctx, size, virt_addr, dma_addr); +} + +void *__DWC_ALLOC(void *mem_ctx, uint32_t size) +{ + return kzalloc(size, GFP_KERNEL); +} + +void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size) +{ + return kzalloc(size, GFP_ATOMIC); +} + +void __DWC_FREE(void *mem_ctx, void *addr) +{ + kfree(addr); +} + + +#ifdef DWC_CRYPTOLIB +/* dwc_crypto.h */ + +void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length) +{ + get_random_bytes(buffer, length); +} + +int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out) +{ + struct crypto_blkcipher *tfm; + struct blkcipher_desc desc; + struct scatterlist sgd; + struct scatterlist sgs; + + tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC); + if (tfm == NULL) { + printk("failed to load transform for aes CBC\n"); + return -1; + } + + crypto_blkcipher_setkey(tfm, key, keylen); + crypto_blkcipher_set_iv(tfm, iv, 16); + + sg_init_one(&sgd, out, messagelen); + sg_init_one(&sgs, message, messagelen); + + desc.tfm = tfm; + desc.flags = 0; + + if (crypto_blkcipher_encrypt(&desc, &sgd, &sgs, messagelen)) { + crypto_free_blkcipher(tfm); + DWC_ERROR("AES CBC encryption failed"); + return -1; + } + + crypto_free_blkcipher(tfm); + return 0; +} + +int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out) +{ + struct crypto_hash *tfm; + struct hash_desc desc; + struct scatterlist sg; + + tfm = crypto_alloc_hash("sha256", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + DWC_ERROR("Failed to load transform for sha256: %ld\n", PTR_ERR(tfm)); + return 0; + } + desc.tfm = tfm; + desc.flags = 0; + + sg_init_one(&sg, message, len); + crypto_hash_digest(&desc, &sg, len, out); + crypto_free_hash(tfm); + + return 1; +} + +int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen, + uint8_t *key, uint32_t keylen, uint8_t *out) +{ + struct crypto_hash *tfm; + struct hash_desc desc; + struct scatterlist sg; + + tfm = crypto_alloc_hash("hmac(sha256)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + DWC_ERROR("Failed to load transform for hmac(sha256): %ld\n", PTR_ERR(tfm)); + return 0; + } + desc.tfm = tfm; + desc.flags = 0; + + sg_init_one(&sg, message, messagelen); + crypto_hash_setkey(tfm, key, keylen); + crypto_hash_digest(&desc, &sg, messagelen, out); + crypto_free_hash(tfm); + + return 1; +} +#endif /* DWC_CRYPTOLIB */ + + +/* Byte Ordering Conversions */ + +uint32_t DWC_CPU_TO_LE32(uint32_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_CPU_TO_BE32(uint32_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_LE32_TO_CPU(uint32_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_BE32_TO_CPU(uint32_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint16_t DWC_CPU_TO_LE16(uint16_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_CPU_TO_BE16(uint16_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_LE16_TO_CPU(uint16_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_BE16_TO_CPU(uint16_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + + +/* Registers */ + +uint32_t DWC_READ_REG32(uint32_t volatile *reg) +{ + return readl(reg); +} + +#if 0 +uint64_t DWC_READ_REG64(uint64_t volatile *reg) +{ +} +#endif + +void DWC_WRITE_REG32(uint32_t volatile *reg, uint32_t value) +{ + writel(value, reg); +} + +#if 0 +void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value) +{ +} +#endif + +void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask) +{ + writel((readl(reg) & ~clear_mask) | set_mask, reg); +} + +#if 0 +void DWC_MODIFY_REG64(uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask) +{ +} +#endif + + +/* Locking */ + +dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void) +{ + spinlock_t *sl = (spinlock_t *)1; + +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + sl = DWC_ALLOC(sizeof(*sl)); + if (!sl) { + DWC_ERROR("Cannot allocate memory for spinlock\n"); + return NULL; + } + + spin_lock_init(sl); +#endif + return (dwc_spinlock_t *)sl; +} + +void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock) +{ +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + DWC_FREE(lock); +#endif +} + +void DWC_SPINLOCK(dwc_spinlock_t *lock) +{ +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + spin_lock((spinlock_t *)lock); +#endif +} + +void DWC_SPINUNLOCK(dwc_spinlock_t *lock) +{ +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + spin_unlock((spinlock_t *)lock); +#endif +} + +void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags) +{ + dwc_irqflags_t f; + +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + spin_lock_irqsave((spinlock_t *)lock, f); +#else + local_irq_save(f); +#endif + *flags = f; +} + +void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags) +{ +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + spin_unlock_irqrestore((spinlock_t *)lock, flags); +#else + local_irq_restore(flags); +#endif +} + +dwc_mutex_t *DWC_MUTEX_ALLOC(void) +{ + struct mutex *m; + dwc_mutex_t *mutex = (dwc_mutex_t *)DWC_ALLOC(sizeof(struct mutex)); + + if (!mutex) { + DWC_ERROR("Cannot allocate memory for mutex\n"); + return NULL; + } + + m = (struct mutex *)mutex; + mutex_init(m); + return mutex; +} + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES)) +#else +void DWC_MUTEX_FREE(dwc_mutex_t *mutex) +{ + mutex_destroy((struct mutex *)mutex); + DWC_FREE(mutex); +} +#endif + +void DWC_MUTEX_LOCK(dwc_mutex_t *mutex) +{ + struct mutex *m = (struct mutex *)mutex; + mutex_lock(m); +} + +int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex) +{ + struct mutex *m = (struct mutex *)mutex; + return mutex_trylock(m); +} + +void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex) +{ + struct mutex *m = (struct mutex *)mutex; + mutex_unlock(m); +} + + +/* Timing */ + +void DWC_UDELAY(uint32_t usecs) +{ + udelay(usecs); +} + +void DWC_MDELAY(uint32_t msecs) +{ + mdelay(msecs); +} + +void DWC_MSLEEP(uint32_t msecs) +{ + msleep(msecs); +} + +uint32_t DWC_TIME(void) +{ + return jiffies_to_msecs(jiffies); +} + + +/* Timers */ + +struct dwc_timer { + struct timer_list *t; + char *name; + dwc_timer_callback_t cb; + void *data; + uint8_t scheduled; + dwc_spinlock_t *lock; +}; + +static void timer_callback(unsigned long data) +{ + dwc_timer_t *timer = (dwc_timer_t *)data; + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(timer->lock, &flags); + timer->scheduled = 0; + DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags); + DWC_DEBUG("Timer %s callback", timer->name); + timer->cb(timer->data); +} + +dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data) +{ + dwc_timer_t *t = DWC_ALLOC(sizeof(*t)); + + if (!t) { + DWC_ERROR("Cannot allocate memory for timer"); + return NULL; + } + + t->t = DWC_ALLOC(sizeof(*t->t)); + if (!t->t) { + DWC_ERROR("Cannot allocate memory for timer->t"); + goto no_timer; + } + + t->name = DWC_STRDUP(name); + if (!t->name) { + DWC_ERROR("Cannot allocate memory for timer->name"); + goto no_name; + } + + t->lock = DWC_SPINLOCK_ALLOC(); + if (!t->lock) { + DWC_ERROR("Cannot allocate memory for lock"); + goto no_lock; + } + + t->scheduled = 0; + t->t->base = &boot_tvec_bases; + t->t->expires = jiffies; + setup_timer(t->t, timer_callback, (unsigned long)t); + + t->cb = cb; + t->data = data; + + return t; + + no_lock: + DWC_FREE(t->name); + no_name: + DWC_FREE(t->t); + no_timer: + DWC_FREE(t); + return NULL; +} + +void DWC_TIMER_FREE(dwc_timer_t *timer) +{ + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(timer->lock, &flags); + + if (timer->scheduled) { + del_timer(timer->t); + timer->scheduled = 0; + } + + DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags); + DWC_SPINLOCK_FREE(timer->lock); + DWC_FREE(timer->t); + DWC_FREE(timer->name); + DWC_FREE(timer); +} + +void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time) +{ + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(timer->lock, &flags); + + if (!timer->scheduled) { + timer->scheduled = 1; + DWC_DEBUG("Scheduling timer %s to expire in +%d msec", timer->name, time); + timer->t->expires = jiffies + msecs_to_jiffies(time); + add_timer(timer->t); + } else { + DWC_DEBUG("Modifying timer %s to expire in +%d msec", timer->name, time); + mod_timer(timer->t, jiffies + msecs_to_jiffies(time)); + } + + DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags); +} + +void DWC_TIMER_CANCEL(dwc_timer_t *timer) +{ + del_timer(timer->t); +} + + +/* Wait Queues */ + +struct dwc_waitq { + wait_queue_head_t queue; + int abort; +}; + +dwc_waitq_t *DWC_WAITQ_ALLOC(void) +{ + dwc_waitq_t *wq = DWC_ALLOC(sizeof(*wq)); + + if (!wq) { + DWC_ERROR("Cannot allocate memory for waitqueue\n"); + return NULL; + } + + init_waitqueue_head(&wq->queue); + wq->abort = 0; + return wq; +} + +void DWC_WAITQ_FREE(dwc_waitq_t *wq) +{ + DWC_FREE(wq); +} + +int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data) +{ + int result = wait_event_interruptible(wq->queue, + cond(data) || wq->abort); + if (result == -ERESTARTSYS) { + wq->abort = 0; + return -DWC_E_RESTART; + } + + if (wq->abort == 1) { + wq->abort = 0; + return -DWC_E_ABORT; + } + + wq->abort = 0; + + if (result == 0) { + return 0; + } + + return -DWC_E_UNKNOWN; +} + +int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, + void *data, int32_t msecs) +{ + int32_t tmsecs; + int result = wait_event_interruptible_timeout(wq->queue, + cond(data) || wq->abort, + msecs_to_jiffies(msecs)); + if (result == -ERESTARTSYS) { + wq->abort = 0; + return -DWC_E_RESTART; + } + + if (wq->abort == 1) { + wq->abort = 0; + return -DWC_E_ABORT; + } + + wq->abort = 0; + + if (result > 0) { + tmsecs = jiffies_to_msecs(result); + if (!tmsecs) { + return 1; + } + + return tmsecs; + } + + if (result == 0) { + return -DWC_E_TIMEOUT; + } + + return -DWC_E_UNKNOWN; +} + +void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq) +{ + wq->abort = 0; + wake_up_interruptible(&wq->queue); +} + +void DWC_WAITQ_ABORT(dwc_waitq_t *wq) +{ + wq->abort = 1; + wake_up_interruptible(&wq->queue); +} + + +/* Threading */ + +dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data) +{ + struct task_struct *thread = kthread_run(func, data, name); + + if (thread == ERR_PTR(-ENOMEM)) { + return NULL; + } + + return (dwc_thread_t *)thread; +} + +int DWC_THREAD_STOP(dwc_thread_t *thread) +{ + return kthread_stop((struct task_struct *)thread); +} + +dwc_bool_t DWC_THREAD_SHOULD_STOP(void) +{ + return kthread_should_stop(); +} + + +/* tasklets + - run in interrupt context (cannot sleep) + - each tasklet runs on a single CPU + - different tasklets can be running simultaneously on different CPUs + */ +struct dwc_tasklet { + struct tasklet_struct t; + dwc_tasklet_callback_t cb; + void *data; +}; + +static void tasklet_callback(unsigned long data) +{ + dwc_tasklet_t *t = (dwc_tasklet_t *)data; + t->cb(t->data); +} + +dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data) +{ + dwc_tasklet_t *t = DWC_ALLOC(sizeof(*t)); + + if (t) { + t->cb = cb; + t->data = data; + tasklet_init(&t->t, tasklet_callback, (unsigned long)t); + } else { + DWC_ERROR("Cannot allocate memory for tasklet\n"); + } + + return t; +} + +void DWC_TASK_FREE(dwc_tasklet_t *task) +{ + DWC_FREE(task); +} + +void DWC_TASK_SCHEDULE(dwc_tasklet_t *task) +{ + tasklet_schedule(&task->t); +} + + +/* workqueues + - run in process context (can sleep) + */ +typedef struct work_container { + dwc_work_callback_t cb; + void *data; + dwc_workq_t *wq; + char *name; + +#ifdef DEBUG + DWC_CIRCLEQ_ENTRY(work_container) entry; +#endif + struct delayed_work work; +} work_container_t; + +#ifdef DEBUG +DWC_CIRCLEQ_HEAD(work_container_queue, work_container); +#endif + +struct dwc_workq { + struct workqueue_struct *wq; + dwc_spinlock_t *lock; + dwc_waitq_t *waitq; + int pending; + +#ifdef DEBUG + struct work_container_queue entries; +#endif +}; + +static void do_work(struct work_struct *work) +{ + dwc_irqflags_t flags; + struct delayed_work *dw = container_of(work, struct delayed_work, work); + work_container_t *container = container_of(dw, struct work_container, work); + dwc_workq_t *wq = container->wq; + + container->cb(container->data); + +#ifdef DEBUG + DWC_CIRCLEQ_REMOVE(&wq->entries, container, entry); +#endif + DWC_DEBUG("Work done: %s, container=%p", container->name, container); + if (container->name) { + DWC_FREE(container->name); + } + DWC_FREE(container); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending--; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); +} + +static int work_done(void *data) +{ + dwc_workq_t *workq = (dwc_workq_t *)data; + return workq->pending == 0; +} + +int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout) +{ + return DWC_WAITQ_WAIT_TIMEOUT(workq->waitq, work_done, workq, timeout); +} + +dwc_workq_t *DWC_WORKQ_ALLOC(char *name) +{ + dwc_workq_t *wq = DWC_ALLOC(sizeof(*wq)); + + if (!wq) { + return NULL; + } + + wq->wq = create_singlethread_workqueue(name); + if (!wq->wq) { + goto no_wq; + } + + wq->pending = 0; + + wq->lock = DWC_SPINLOCK_ALLOC(); + if (!wq->lock) { + goto no_lock; + } + + wq->waitq = DWC_WAITQ_ALLOC(); + if (!wq->waitq) { + goto no_waitq; + } + +#ifdef DEBUG + DWC_CIRCLEQ_INIT(&wq->entries); +#endif + return wq; + + no_waitq: + DWC_SPINLOCK_FREE(wq->lock); + no_lock: + destroy_workqueue(wq->wq); + no_wq: + DWC_FREE(wq); + + return NULL; +} + +void DWC_WORKQ_FREE(dwc_workq_t *wq) +{ +#ifdef DEBUG + if (wq->pending != 0) { + struct work_container *wc; + DWC_ERROR("Destroying work queue with pending work"); + DWC_CIRCLEQ_FOREACH(wc, &wq->entries, entry) { + DWC_ERROR("Work %s still pending", wc->name); + } + } +#endif + destroy_workqueue(wq->wq); + DWC_SPINLOCK_FREE(wq->lock); + DWC_WAITQ_FREE(wq->waitq); + DWC_FREE(wq); +} + +void DWC_WORKQ_SCHEDULE(dwc_workq_t *wq, dwc_work_callback_t cb, void *data, + char *format, ...) +{ + dwc_irqflags_t flags; + work_container_t *container; + static char name[128]; + va_list args; + + va_start(args, format); + DWC_VSNPRINTF(name, 128, format, args); + va_end(args); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending++; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); + + container = DWC_ALLOC_ATOMIC(sizeof(*container)); + if (!container) { + DWC_ERROR("Cannot allocate memory for container\n"); + return; + } + + container->name = DWC_STRDUP(name); + if (!container->name) { + DWC_ERROR("Cannot allocate memory for container->name\n"); + DWC_FREE(container); + return; + } + + container->cb = cb; + container->data = data; + container->wq = wq; + DWC_DEBUG("Queueing work: %s, container=%p", container->name, container); + INIT_WORK(&container->work.work, do_work); + +#ifdef DEBUG + DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry); +#endif + queue_work(wq->wq, &container->work.work); +} + +void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *wq, dwc_work_callback_t cb, + void *data, uint32_t time, char *format, ...) +{ + dwc_irqflags_t flags; + work_container_t *container; + static char name[128]; + va_list args; + + va_start(args, format); + DWC_VSNPRINTF(name, 128, format, args); + va_end(args); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending++; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); + + container = DWC_ALLOC_ATOMIC(sizeof(*container)); + if (!container) { + DWC_ERROR("Cannot allocate memory for container\n"); + return; + } + + container->name = DWC_STRDUP(name); + if (!container->name) { + DWC_ERROR("Cannot allocate memory for container->name\n"); + DWC_FREE(container); + return; + } + + container->cb = cb; + container->data = data; + container->wq = wq; + DWC_DEBUG("Queueing work: %s, container=%p", container->name, container); + INIT_DELAYED_WORK(&container->work, do_work); + +#ifdef DEBUG + DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry); +#endif + queue_delayed_work(wq->wq, &container->work, msecs_to_jiffies(time)); +} + +int DWC_WORKQ_PENDING(dwc_workq_t *wq) +{ + return wq->pending; +} + + +#ifdef DWC_LIBMODULE + +#ifdef DWC_CCLIB +/* CC */ +EXPORT_SYMBOL(dwc_cc_if_alloc); +EXPORT_SYMBOL(dwc_cc_if_free); +EXPORT_SYMBOL(dwc_cc_clear); +EXPORT_SYMBOL(dwc_cc_add); +EXPORT_SYMBOL(dwc_cc_remove); +EXPORT_SYMBOL(dwc_cc_change); +EXPORT_SYMBOL(dwc_cc_data_for_save); +EXPORT_SYMBOL(dwc_cc_restore_from_data); +EXPORT_SYMBOL(dwc_cc_match_chid); +EXPORT_SYMBOL(dwc_cc_match_cdid); +EXPORT_SYMBOL(dwc_cc_ck); +EXPORT_SYMBOL(dwc_cc_chid); +EXPORT_SYMBOL(dwc_cc_cdid); +EXPORT_SYMBOL(dwc_cc_name); +#endif /* DWC_CCLIB */ + +#ifdef DWC_CRYPTOLIB +# ifndef CONFIG_MACH_IPMATE +/* Modpow */ +EXPORT_SYMBOL(dwc_modpow); + +/* DH */ +EXPORT_SYMBOL(dwc_dh_modpow); +EXPORT_SYMBOL(dwc_dh_derive_keys); +EXPORT_SYMBOL(dwc_dh_pk); +# endif /* CONFIG_MACH_IPMATE */ + +/* Crypto */ +EXPORT_SYMBOL(dwc_wusb_aes_encrypt); +EXPORT_SYMBOL(dwc_wusb_cmf); +EXPORT_SYMBOL(dwc_wusb_prf); +EXPORT_SYMBOL(dwc_wusb_fill_ccm_nonce); +EXPORT_SYMBOL(dwc_wusb_gen_nonce); +EXPORT_SYMBOL(dwc_wusb_gen_key); +EXPORT_SYMBOL(dwc_wusb_gen_mic); +#endif /* DWC_CRYPTOLIB */ + +/* Notification */ +#ifdef DWC_NOTIFYLIB +EXPORT_SYMBOL(dwc_alloc_notification_manager); +EXPORT_SYMBOL(dwc_free_notification_manager); +EXPORT_SYMBOL(dwc_register_notifier); +EXPORT_SYMBOL(dwc_unregister_notifier); +EXPORT_SYMBOL(dwc_add_observer); +EXPORT_SYMBOL(dwc_remove_observer); +EXPORT_SYMBOL(dwc_notify); +#endif + +/* Memory Debugging Routines */ +#ifdef DWC_DEBUG_MEMORY +EXPORT_SYMBOL(dwc_alloc_debug); +EXPORT_SYMBOL(dwc_alloc_atomic_debug); +EXPORT_SYMBOL(dwc_free_debug); +EXPORT_SYMBOL(dwc_dma_alloc_debug); +EXPORT_SYMBOL(dwc_dma_free_debug); +#endif + +EXPORT_SYMBOL(DWC_MEMSET); +EXPORT_SYMBOL(DWC_MEMCPY); +EXPORT_SYMBOL(DWC_MEMMOVE); +EXPORT_SYMBOL(DWC_MEMCMP); +EXPORT_SYMBOL(DWC_STRNCMP); +EXPORT_SYMBOL(DWC_STRCMP); +EXPORT_SYMBOL(DWC_STRLEN); +EXPORT_SYMBOL(DWC_STRCPY); +EXPORT_SYMBOL(DWC_STRDUP); +EXPORT_SYMBOL(DWC_ATOI); +EXPORT_SYMBOL(DWC_ATOUI); + +#ifdef DWC_UTFLIB +EXPORT_SYMBOL(DWC_UTF8_TO_UTF16LE); +#endif /* DWC_UTFLIB */ + +EXPORT_SYMBOL(DWC_IN_IRQ); +EXPORT_SYMBOL(DWC_IN_BH); +EXPORT_SYMBOL(DWC_VPRINTF); +EXPORT_SYMBOL(DWC_VSNPRINTF); +EXPORT_SYMBOL(DWC_PRINTF); +EXPORT_SYMBOL(DWC_SPRINTF); +EXPORT_SYMBOL(DWC_SNPRINTF); +EXPORT_SYMBOL(__DWC_WARN); +EXPORT_SYMBOL(__DWC_ERROR); +EXPORT_SYMBOL(DWC_EXCEPTION); + +#ifdef DEBUG +EXPORT_SYMBOL(__DWC_DEBUG); +#endif + +EXPORT_SYMBOL(__DWC_DMA_ALLOC); +EXPORT_SYMBOL(__DWC_DMA_ALLOC_ATOMIC); +EXPORT_SYMBOL(__DWC_DMA_FREE); +EXPORT_SYMBOL(__DWC_ALLOC); +EXPORT_SYMBOL(__DWC_ALLOC_ATOMIC); +EXPORT_SYMBOL(__DWC_FREE); + +#ifdef DWC_CRYPTOLIB +EXPORT_SYMBOL(DWC_RANDOM_BYTES); +EXPORT_SYMBOL(DWC_AES_CBC); +EXPORT_SYMBOL(DWC_SHA256); +EXPORT_SYMBOL(DWC_HMAC_SHA256); +#endif + +EXPORT_SYMBOL(DWC_CPU_TO_LE32); +EXPORT_SYMBOL(DWC_CPU_TO_BE32); +EXPORT_SYMBOL(DWC_LE32_TO_CPU); +EXPORT_SYMBOL(DWC_BE32_TO_CPU); +EXPORT_SYMBOL(DWC_CPU_TO_LE16); +EXPORT_SYMBOL(DWC_CPU_TO_BE16); +EXPORT_SYMBOL(DWC_LE16_TO_CPU); +EXPORT_SYMBOL(DWC_BE16_TO_CPU); +EXPORT_SYMBOL(DWC_READ_REG32); +EXPORT_SYMBOL(DWC_WRITE_REG32); +EXPORT_SYMBOL(DWC_MODIFY_REG32); + +#if 0 +EXPORT_SYMBOL(DWC_READ_REG64); +EXPORT_SYMBOL(DWC_WRITE_REG64); +EXPORT_SYMBOL(DWC_MODIFY_REG64); +#endif + +EXPORT_SYMBOL(DWC_SPINLOCK_ALLOC); +EXPORT_SYMBOL(DWC_SPINLOCK_FREE); +EXPORT_SYMBOL(DWC_SPINLOCK); +EXPORT_SYMBOL(DWC_SPINUNLOCK); +EXPORT_SYMBOL(DWC_SPINLOCK_IRQSAVE); +EXPORT_SYMBOL(DWC_SPINUNLOCK_IRQRESTORE); +EXPORT_SYMBOL(DWC_MUTEX_ALLOC); + +#if (!defined(DWC_LINUX) || !defined(CONFIG_DEBUG_MUTEXES)) +EXPORT_SYMBOL(DWC_MUTEX_FREE); +#endif + +EXPORT_SYMBOL(DWC_MUTEX_LOCK); +EXPORT_SYMBOL(DWC_MUTEX_TRYLOCK); +EXPORT_SYMBOL(DWC_MUTEX_UNLOCK); +EXPORT_SYMBOL(DWC_UDELAY); +EXPORT_SYMBOL(DWC_MDELAY); +EXPORT_SYMBOL(DWC_MSLEEP); +EXPORT_SYMBOL(DWC_TIME); +EXPORT_SYMBOL(DWC_TIMER_ALLOC); +EXPORT_SYMBOL(DWC_TIMER_FREE); +EXPORT_SYMBOL(DWC_TIMER_SCHEDULE); +EXPORT_SYMBOL(DWC_TIMER_CANCEL); +EXPORT_SYMBOL(DWC_WAITQ_ALLOC); +EXPORT_SYMBOL(DWC_WAITQ_FREE); +EXPORT_SYMBOL(DWC_WAITQ_WAIT); +EXPORT_SYMBOL(DWC_WAITQ_WAIT_TIMEOUT); +EXPORT_SYMBOL(DWC_WAITQ_TRIGGER); +EXPORT_SYMBOL(DWC_WAITQ_ABORT); +EXPORT_SYMBOL(DWC_THREAD_RUN); +EXPORT_SYMBOL(DWC_THREAD_STOP); +EXPORT_SYMBOL(DWC_THREAD_SHOULD_STOP); +EXPORT_SYMBOL(DWC_TASK_ALLOC); +EXPORT_SYMBOL(DWC_TASK_FREE); +EXPORT_SYMBOL(DWC_TASK_SCHEDULE); +EXPORT_SYMBOL(DWC_WORKQ_WAIT_WORK_DONE); +EXPORT_SYMBOL(DWC_WORKQ_ALLOC); +EXPORT_SYMBOL(DWC_WORKQ_FREE); +EXPORT_SYMBOL(DWC_WORKQ_SCHEDULE); +EXPORT_SYMBOL(DWC_WORKQ_SCHEDULE_DELAYED); +EXPORT_SYMBOL(DWC_WORKQ_PENDING); + +static int dwc_common_port_init_module(void) +{ + int result = 0; + + printk(KERN_DEBUG "Module dwc_common_port init\n" ); + +#ifdef DWC_DEBUG_MEMORY + result = dwc_memory_debug_start(NULL); + if (result) { + printk(KERN_ERR + "dwc_memory_debug_start() failed with error %d\n", + result); + return result; + } +#endif + +#ifdef DWC_NOTIFYLIB + result = dwc_alloc_notification_manager(NULL, NULL); + if (result) { + printk(KERN_ERR + "dwc_alloc_notification_manager() failed with error %d\n", + result); + return result; + } +#endif + return result; +} + +static void dwc_common_port_exit_module(void) +{ + printk(KERN_DEBUG "Module dwc_common_port exit\n" ); + +#ifdef DWC_NOTIFYLIB + dwc_free_notification_manager(); +#endif + +#ifdef DWC_DEBUG_MEMORY + dwc_memory_debug_stop(); +#endif +} + +module_init(dwc_common_port_init_module); +module_exit(dwc_common_port_exit_module); + +MODULE_DESCRIPTION("DWC Common Library - Portable version"); +MODULE_AUTHOR("Synopsys Inc."); +MODULE_LICENSE ("GPL"); + +#endif /* DWC_LIBMODULE */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_list.h b/drivers/usb/gadget/dwc_otg/dwc_list.h new file mode 100644 index 00000000..b29afe00 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_list.h @@ -0,0 +1,616 @@ +/* $OpenBSD: queue.h,v 1.26 2004/05/04 16:59:32 grange Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + + +/** @file + * + * This file defines linked list operations. It is derived from BSD with + * only the MACRO names being prefixed with DWC_. This is because a few of + * these names conflict with those on Linux. For documentation on use, see the + * inline comments in the source code. The original license for this source + * code applies and is preserved in the dwc_list.h source file. + */ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * Double-linked List. + */ + +typedef struct dwc_list_link { + struct dwc_list_link *next; + struct dwc_list_link *prev; +} dwc_list_link_t; + +#define DWC_LIST_INIT(link) do{ \ + (link)->next = (link); \ + (link)->prev = (link); \ +} while(0) + +#define DWC_LIST_FIRST(link) ((link)->next) +#define DWC_LIST_LAST(link) ((link)->prev) +#define DWC_LIST_END(link) (link) +#define DWC_LIST_NEXT(link) ((link)->next) +#define DWC_LIST_PREV(link) ((link)->prev) +#define DWC_LIST_EMPTY(link) \ + (DWC_LIST_FIRST(link) == DWC_LIST_END(link)) +#define DWC_LIST_ENTRY(link, type, field) (type *) \ + ((uint8_t *)(link) - (size_t)(&((type *)0)->field)) + +#define DWC_LIST_INSERT_HEAD(list, link) do { \ + (link)->next = (list)->next; \ + (link)->prev = (list); \ + (list)->next->prev = link; \ + (list)->next = link; \ +} while(0) + +#define DWC_LIST_INSERT_TAIL(list, link) do { \ + (link)->next = list; \ + (link)->prev = (list)->prev; \ + (list)->prev->next = link; \ + (list)->prev = link; \ +} while(0) + +#define DWC_LIST_REMOVE(link) do { \ + (link)->next->prev = (link)->prev; \ + (link)->prev->next = (link)->next; \ +} while(0) + +#define DWC_LIST_REMOVE_INIT(link) do { \ + DWC_LIST_REMOVE(link); \ + DWC_LIST_INIT(link); \ +} while(0) + +#define DWC_LIST_MOVE_HEAD(list, link) do { \ + DWC_LIST_REMOVE(link); \ + DWC_LIST_INSERT_HEAD(list, link); \ +} while(0) + +#define DWC_LIST_MOVE_TAIL(list, link) do { \ + DWC_LIST_REMOVE(link); \ + DWC_LIST_INSERT_TAIL(list, link); \ +} while(0) + +#define DWC_LIST_FOREACH(var, list) \ + for((var) = DWC_LIST_FIRST(list); \ + (var) != DWC_LIST_END(list); \ + (var) = DWC_LIST_NEXT(var)) + +#define DWC_LIST_FOREACH_SAFE(var, var2, list) \ + for((var) = DWC_LIST_FIRST(list), var2 = DWC_LIST_NEXT(var); \ + (var) != DWC_LIST_END(list); \ + (var) = (var2), var2 = DWC_LIST_NEXT(var2)) + +#define DWC_LIST_FOREACH_REVERSE(var, list) \ + for((var) = DWC_LIST_LAST(list); \ + (var) != DWC_LIST_END(list); \ + (var) = DWC_LIST_PREV(var)) + +/* + * Singly-linked List definitions. + */ +#define DWC_SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define DWC_SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define DWC_SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define DWC_SLIST_FIRST(head) ((head)->slh_first) +#define DWC_SLIST_END(head) NULL +#define DWC_SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define DWC_SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define DWC_SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define DWC_SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for ((varp) = &SLIST_FIRST((head)); \ + ((var) = *(varp)) != SLIST_END(head); \ + (varp) = &SLIST_NEXT((var), field)) + +/* + * Singly-linked List functions. + */ +#define DWC_SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define DWC_SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define DWC_SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define DWC_SLIST_REMOVE_NEXT(head, elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define DWC_SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define DWC_SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = (head)->slh_first; \ + while( curelm->field.sle_next != (elm) ) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ +} while (0) + +#if 0 + +/* + * List definitions. + */ +#define DWC_LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define DWC_LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define DWC_LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define DWC_LIST_FIRST(head) ((head)->lh_first) +#define DWC_LIST_END(head) NULL +#define DWC_LIST_EMPTY(head) (DWC_LIST_FIRST(head) == DWC_LIST_END(head)) +#define DWC_LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define DWC_LIST_FOREACH(var, head, field) \ + for((var) = DWC_LIST_FIRST(head); \ + (var)!= DWC_LIST_END(head); \ + (var) = DWC_LIST_NEXT(var, field)) +#define DWC_LIST_FOREACH_SAFE(var, var2, head, field) \ + for((var) = DWC_LIST_FIRST(head), var2 = DWC_LIST_NEXT(var, field); \ + (var) != DWC_LIST_END(head); \ + (var) = var2, var2 = DWC_LIST_NEXT(var, field)) + +/* + * List functions. + */ +#define DWC_LIST_INIT(head) do { \ + DWC_LIST_FIRST(head) = DWC_LIST_END(head); \ +} while (0) + +#define DWC_LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define DWC_LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define DWC_LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define DWC_LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ +} while (0) + +#define DWC_LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ +} while (0) + +#endif + +/* + * Simple queue definitions. + */ +#define DWC_SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define DWC_SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define DWC_SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define DWC_SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define DWC_SIMPLEQ_END(head) NULL +#define DWC_SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define DWC_SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define DWC_SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +/* + * Simple queue functions. + */ +#define DWC_SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define DWC_SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define DWC_SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define DWC_SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define DWC_SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +/* + * Tail queue definitions. + */ +#define DWC_TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define DWC_TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define DWC_TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define DWC_TAILQ_FIRST(head) ((head)->tqh_first) +#define DWC_TAILQ_END(head) NULL +#define DWC_TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define DWC_TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define DWC_TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define DWC_TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define DWC_TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define DWC_TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +/* + * Tail queue functions. + */ +#define DWC_TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define DWC_TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define DWC_TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define DWC_TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define DWC_TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define DWC_TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (0) + +#define DWC_TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define DWC_CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define DWC_CIRCLEQ_HEAD_INITIALIZER(head) \ + { DWC_CIRCLEQ_END(&head), DWC_CIRCLEQ_END(&head) } + +#define DWC_CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define DWC_CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define DWC_CIRCLEQ_LAST(head) ((head)->cqh_last) +#define DWC_CIRCLEQ_END(head) ((void *)(head)) +#define DWC_CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define DWC_CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define DWC_CIRCLEQ_EMPTY(head) \ + (DWC_CIRCLEQ_FIRST(head) == DWC_CIRCLEQ_END(head)) + +#define DWC_CIRCLEQ_EMPTY_ENTRY(elm, field) (((elm)->field.cqe_next == NULL) && ((elm)->field.cqe_prev == NULL)) + +#define DWC_CIRCLEQ_FOREACH(var, head, field) \ + for((var) = DWC_CIRCLEQ_FIRST(head); \ + (var) != DWC_CIRCLEQ_END(head); \ + (var) = DWC_CIRCLEQ_NEXT(var, field)) + +#define DWC_CIRCLEQ_FOREACH_SAFE(var, var2, head, field) \ + for((var) = DWC_CIRCLEQ_FIRST(head), var2 = DWC_CIRCLEQ_NEXT(var, field); \ + (var) != DWC_CIRCLEQ_END(head); \ + (var) = var2, var2 = DWC_CIRCLEQ_NEXT(var, field)) + +#define DWC_CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = DWC_CIRCLEQ_LAST(head); \ + (var) != DWC_CIRCLEQ_END(head); \ + (var) = DWC_CIRCLEQ_PREV(var, field)) + +/* + * Circular queue functions. + */ +#define DWC_CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = DWC_CIRCLEQ_END(head); \ + (head)->cqh_last = DWC_CIRCLEQ_END(head); \ +} while (0) + +#define DWC_CIRCLEQ_INIT_ENTRY(elm, field) do { \ + (elm)->field.cqe_next = NULL; \ + (elm)->field.cqe_prev = NULL; \ +} while (0) + +#define DWC_CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define DWC_CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define DWC_CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = DWC_CIRCLEQ_END(head); \ + if ((head)->cqh_last == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define DWC_CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = DWC_CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define DWC_CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ +} while (0) + +#define DWC_CIRCLEQ_REMOVE_INIT(head, elm, field) do { \ + DWC_CIRCLEQ_REMOVE(head, elm, field); \ + DWC_CIRCLEQ_INIT_ENTRY(elm, field); \ +} while (0) + +#define DWC_CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + DWC_CIRCLEQ_END(head)) \ + (head).cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + DWC_CIRCLEQ_END(head)) \ + (head).cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_os.h b/drivers/usb/gadget/dwc_otg/dwc_os.h new file mode 100644 index 00000000..8c9be4ff --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_os.h @@ -0,0 +1,1237 @@ +/* ========================================================================= + * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_os.h $ + * $Revision: #14 $ + * $Date: 2010/11/04 $ + * $Change: 1621695 $ + * + * Synopsys Portability Library Software and documentation + * (hereinafter, "Software") is an Unsupported proprietary work of + * Synopsys, Inc. unless otherwise expressly agreed to in writing + * between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product + * under any End User Software License Agreement or Agreement for + * Licensed Product with Synopsys or any supplement thereto. You are + * permitted to use and redistribute this Software in source and binary + * forms, with or without modification, provided that redistributions + * of source code must retain this notice. You may not view, use, + * disclose, copy or distribute this file or any information contained + * herein except pursuant to this license grant from Synopsys. If you + * do not agree with this notice, including the disclaimer below, then + * you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" + * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL + * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================= */ +#ifndef _DWC_OS_H_ +#define _DWC_OS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file + * + * DWC portability library, low level os-wrapper functions + * + */ + +/* These basic types need to be defined by some OS header file or custom header + * file for your specific target architecture. + * + * uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t + * + * Any custom or alternate header file must be added and enabled here. + */ + +#ifdef DWC_LINUX +# include +# ifdef CONFIG_DEBUG_MUTEXES +# include +# endif +# include +# include +#endif + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +# include +#endif + + +/** @name Primitive Types and Values */ + +/** We define a boolean type for consistency. Can be either YES or NO */ +typedef uint8_t dwc_bool_t; +#define YES 1 +#define NO 0 + +#ifdef DWC_LINUX + +/** @name Error Codes */ +#define DWC_E_INVALID EINVAL +#define DWC_E_NO_MEMORY ENOMEM +#define DWC_E_NO_DEVICE ENODEV +#define DWC_E_NOT_SUPPORTED EOPNOTSUPP +#define DWC_E_TIMEOUT ETIMEDOUT +#define DWC_E_BUSY EBUSY +#define DWC_E_AGAIN EAGAIN +#define DWC_E_RESTART ERESTART +#define DWC_E_ABORT ECONNABORTED +#define DWC_E_SHUTDOWN ESHUTDOWN +#define DWC_E_NO_DATA ENODATA +#define DWC_E_DISCONNECT ECONNRESET +#define DWC_E_UNKNOWN EINVAL +#define DWC_E_NO_STREAM_RES ENOSR +#define DWC_E_COMMUNICATION ECOMM +#define DWC_E_OVERFLOW EOVERFLOW +#define DWC_E_PROTOCOL EPROTO +#define DWC_E_IN_PROGRESS EINPROGRESS +#define DWC_E_PIPE EPIPE +#define DWC_E_IO EIO +#define DWC_E_NO_SPACE ENOSPC + +#else + +/** @name Error Codes */ +#define DWC_E_INVALID 1001 +#define DWC_E_NO_MEMORY 1002 +#define DWC_E_NO_DEVICE 1003 +#define DWC_E_NOT_SUPPORTED 1004 +#define DWC_E_TIMEOUT 1005 +#define DWC_E_BUSY 1006 +#define DWC_E_AGAIN 1007 +#define DWC_E_RESTART 1008 +#define DWC_E_ABORT 1009 +#define DWC_E_SHUTDOWN 1010 +#define DWC_E_NO_DATA 1011 +#define DWC_E_DISCONNECT 2000 +#define DWC_E_UNKNOWN 3000 +#define DWC_E_NO_STREAM_RES 4001 +#define DWC_E_COMMUNICATION 4002 +#define DWC_E_OVERFLOW 4003 +#define DWC_E_PROTOCOL 4004 +#define DWC_E_IN_PROGRESS 4005 +#define DWC_E_PIPE 4006 +#define DWC_E_IO 4007 +#define DWC_E_NO_SPACE 4008 + +#endif + + +/** @name Tracing/Logging Functions + * + * These function provide the capability to add tracing, debugging, and error + * messages, as well exceptions as assertions. The WUDEV uses these + * extensively. These could be logged to the main console, the serial port, an + * internal buffer, etc. These functions could also be no-op if they are too + * expensive on your system. By default undefining the DEBUG macro already + * no-ops some of these functions. */ + +/** Returns non-zero if in interrupt context. */ +extern dwc_bool_t DWC_IN_IRQ(void); +#define dwc_in_irq DWC_IN_IRQ + +/** Returns "IRQ" if DWC_IN_IRQ is true. */ +static inline char *dwc_irq(void) { + return DWC_IN_IRQ() ? "IRQ" : ""; +} + +/** Returns non-zero if in bottom-half context. */ +extern dwc_bool_t DWC_IN_BH(void); +#define dwc_in_bh DWC_IN_BH + +/** Returns "BH" if DWC_IN_BH is true. */ +static inline char *dwc_bh(void) { + return DWC_IN_BH() ? "BH" : ""; +} + +/** + * A vprintf() clone. Just call vprintf if you've got it. + */ +extern void DWC_VPRINTF(char *format, va_list args); +#define dwc_vprintf DWC_VPRINTF + +/** + * A vsnprintf() clone. Just call vprintf if you've got it. + */ +extern int DWC_VSNPRINTF(char *str, int size, char *format, va_list args); +#define dwc_vsnprintf DWC_VSNPRINTF + +/** + * printf() clone. Just call printf if you've go it. + */ +extern void DWC_PRINTF(char *format, ...) +/* This provides compiler level static checking of the parameters if you're + * using GCC. */ +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif +#define dwc_printf DWC_PRINTF + +/** + * sprintf() clone. Just call sprintf if you've got it. + */ +extern int DWC_SPRINTF(char *string, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 2, 3))); +#else + ; +#endif +#define dwc_sprintf DWC_SPRINTF + +/** + * snprintf() clone. Just call snprintf if you've got it. + */ +extern int DWC_SNPRINTF(char *string, int size, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 3, 4))); +#else + ; +#endif +#define dwc_snprintf DWC_SNPRINTF + +/** + * Prints a WARNING message. On systems that don't differentiate between + * warnings and regular log messages, just print it. Indicates that something + * may be wrong with the driver. Works like printf(). + * + * Use the DWC_WARN macro to call this function. + */ +extern void __DWC_WARN(char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif + +/** + * Prints an error message. On systems that don't differentiate between errors + * and regular log messages, just print it. Indicates that something went wrong + * with the driver. Works like printf(). + * + * Use the DWC_ERROR macro to call this function. + */ +extern void __DWC_ERROR(char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif + +/** + * Prints an exception error message and takes some user-defined action such as + * print out a backtrace or trigger a breakpoint. Indicates that something went + * abnormally wrong with the driver such as programmer error, or other + * exceptional condition. It should not be ignored so even on systems without + * printing capability, some action should be taken to notify the developer of + * it. Works like printf(). + */ +extern void DWC_EXCEPTION(char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif +#define dwc_exception DWC_EXCEPTION + +#ifdef DEBUG +/** + * Prints out a debug message. Used for logging/trace messages. + * + * Use the DWC_DEBUG macro to call this function + */ +extern void __DWC_DEBUG(char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif +#else +#define __DWC_DEBUG(...) +#endif + +/** + * Prints out a Debug message. + */ +#define DWC_DEBUG(_format, _args...) __DWC_DEBUG("DEBUG:%s:%s: " _format "\n", \ + __func__, dwc_irq(), ## _args) +#define dwc_debug DWC_DEBUG +/** + * Prints out an informative message. + */ +#define DWC_INFO(_format, _args...) DWC_PRINTF("INFO:%s: " _format "\n", \ + dwc_irq(), ## _args) +#define dwc_info DWC_INFO +/** + * Prints out a warning message. + */ +#define DWC_WARN(_format, _args...) __DWC_WARN("WARN:%s:%s:%d: " _format "\n", \ + dwc_irq(), __func__, __LINE__, ## _args) +#define dwc_warn DWC_WARN +/** + * Prints out an error message. + */ +#define DWC_ERROR(_format, _args...) __DWC_ERROR("ERROR:%s:%s:%d: " _format "\n", \ + dwc_irq(), __func__, __LINE__, ## _args) +#define dwc_error DWC_ERROR + +#define DWC_PROTO_ERROR(_format, _args...) __DWC_WARN("ERROR:%s:%s:%d: " _format "\n", \ + dwc_irq(), __func__, __LINE__, ## _args) +#define dwc_proto_error DWC_PROTO_ERROR + +#ifdef DEBUG +/** Prints out a exception error message if the _expr expression fails. Disabled + * if DEBUG is not enabled. */ +#define DWC_ASSERT(_expr, _format, _args...) do { \ + if (!(_expr)) { DWC_EXCEPTION("%s:%s:%d: " _format "\n", dwc_irq(), \ + __FILE__, __LINE__, ## _args); } \ + } while (0) +#else +#define DWC_ASSERT(_x...) +#endif +#define dwc_assert DWC_ASSERT + + +/** @name Byte Ordering + * The following functions are for conversions between processor's byte ordering + * and specific ordering you want. + */ + +/** Converts 32 bit data in CPU byte ordering to little endian. */ +extern uint32_t DWC_CPU_TO_LE32(uint32_t *p); +#define dwc_cpu_to_le32 DWC_CPU_TO_LE32 + +/** Converts 32 bit data in CPU byte orderint to big endian. */ +extern uint32_t DWC_CPU_TO_BE32(uint32_t *p); +#define dwc_cpu_to_be32 DWC_CPU_TO_BE32 + +/** Converts 32 bit little endian data to CPU byte ordering. */ +extern uint32_t DWC_LE32_TO_CPU(uint32_t *p); +#define dwc_le32_to_cpu DWC_LE32_TO_CPU + +/** Converts 32 bit big endian data to CPU byte ordering. */ +extern uint32_t DWC_BE32_TO_CPU(uint32_t *p); +#define dwc_be32_to_cpu DWC_BE32_TO_CPU + +/** Converts 16 bit data in CPU byte ordering to little endian. */ +extern uint16_t DWC_CPU_TO_LE16(uint16_t *p); +#define dwc_cpu_to_le16 DWC_CPU_TO_LE16 + +/** Converts 16 bit data in CPU byte orderint to big endian. */ +extern uint16_t DWC_CPU_TO_BE16(uint16_t *p); +#define dwc_cpu_to_be16 DWC_CPU_TO_BE16 + +/** Converts 16 bit little endian data to CPU byte ordering. */ +extern uint16_t DWC_LE16_TO_CPU(uint16_t *p); +#define dwc_le16_to_cpu DWC_LE16_TO_CPU + +/** Converts 16 bit bi endian data to CPU byte ordering. */ +extern uint16_t DWC_BE16_TO_CPU(uint16_t *p); +#define dwc_be16_to_cpu DWC_BE16_TO_CPU + + +/** @name Register Read/Write + * + * The following six functions should be implemented to read/write registers of + * 32-bit and 64-bit sizes. All modules use this to read/write register values. + * The reg value is a pointer to the register calculated from the void *base + * variable passed into the driver when it is started. */ + +#ifdef DWC_LINUX +/* Linux doesn't need any extra parameters for register read/write, so we + * just throw away the IO context parameter. + */ +/** Reads the content of a 32-bit register. */ +extern uint32_t DWC_READ_REG32(uint32_t volatile *reg); +#define dwc_read_reg32(_reg_) DWC_READ_REG32(_reg_) + +/** Reads the content of a 64-bit register. */ +extern uint64_t DWC_READ_REG64(uint64_t volatile *reg); +#define dwc_read_reg64(_reg_) DWC_READ_REG64(_reg_) + +/** Writes to a 32-bit register. */ +extern void DWC_WRITE_REG32(uint32_t volatile *reg, uint32_t value); +#define dwc_write_reg32(_reg_,_val_) DWC_WRITE_REG32(_reg_, _val_) + +/** Writes to a 64-bit register. */ +extern void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value); +#define dwc_write_reg64(_reg_,_val_) DWC_WRITE_REG64(_reg_, _val_) + +/** + * Modify bit values in a register. Using the + * algorithm: (reg_contents & ~clear_mask) | set_mask. + */ +extern void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask); +#define dwc_modify_reg32(_reg_,_cmsk_,_smsk_) DWC_MODIFY_REG32(_reg_,_cmsk_,_smsk_) +extern void DWC_MODIFY_REG64(uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask); +#define dwc_modify_reg64(_reg_,_cmsk_,_smsk_) DWC_MODIFY_REG64(_reg_,_cmsk_,_smsk_) + +#endif /* DWC_LINUX */ + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +typedef struct dwc_ioctx { + struct device *dev; + bus_space_tag_t iot; + bus_space_handle_t ioh; +} dwc_ioctx_t; + +/** BSD needs two extra parameters for register read/write, so we pass + * them in using the IO context parameter. + */ +/** Reads the content of a 32-bit register. */ +extern uint32_t DWC_READ_REG32(void *io_ctx, uint32_t volatile *reg); +#define dwc_read_reg32 DWC_READ_REG32 + +/** Reads the content of a 64-bit register. */ +extern uint64_t DWC_READ_REG64(void *io_ctx, uint64_t volatile *reg); +#define dwc_read_reg64 DWC_READ_REG64 + +/** Writes to a 32-bit register. */ +extern void DWC_WRITE_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t value); +#define dwc_write_reg32 DWC_WRITE_REG32 + +/** Writes to a 64-bit register. */ +extern void DWC_WRITE_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t value); +#define dwc_write_reg64 DWC_WRITE_REG64 + +/** + * Modify bit values in a register. Using the + * algorithm: (reg_contents & ~clear_mask) | set_mask. + */ +extern void DWC_MODIFY_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask); +#define dwc_modify_reg32 DWC_MODIFY_REG32 +extern void DWC_MODIFY_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask); +#define dwc_modify_reg64 DWC_MODIFY_REG64 + +#endif /* DWC_FREEBSD || DWC_NETBSD */ + +/** @cond */ + +/** @name Some convenience MACROS used internally. Define DWC_DEBUG_REGS to log the + * register writes. */ + +#ifdef DWC_LINUX + +# ifdef DWC_DEBUG_REGS + +#define dwc_define_read_write_reg_n(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg##_n(_container_type *container, int num) { \ + return DWC_READ_REG32(&container->regs->_reg[num]); \ +} \ +static inline void dwc_write_##_reg##_n(_container_type *container, int num, uint32_t data) { \ + DWC_DEBUG("WRITING %8s[%d]: %p: %08x", #_reg, num, \ + &(((uint32_t*)container->regs->_reg)[num]), data); \ + DWC_WRITE_REG32(&(((uint32_t*)container->regs->_reg)[num]), data); \ +} + +#define dwc_define_read_write_reg(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg(_container_type *container) { \ + return DWC_READ_REG32(&container->regs->_reg); \ +} \ +static inline void dwc_write_##_reg(_container_type *container, uint32_t data) { \ + DWC_DEBUG("WRITING %11s: %p: %08x", #_reg, &container->regs->_reg, data); \ + DWC_WRITE_REG32(&container->regs->_reg, data); \ +} + +# else /* DWC_DEBUG_REGS */ + +#define dwc_define_read_write_reg_n(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg##_n(_container_type *container, int num) { \ + return DWC_READ_REG32(&container->regs->_reg[num]); \ +} \ +static inline void dwc_write_##_reg##_n(_container_type *container, int num, uint32_t data) { \ + DWC_WRITE_REG32(&(((uint32_t*)container->regs->_reg)[num]), data); \ +} + +#define dwc_define_read_write_reg(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg(_container_type *container) { \ + return DWC_READ_REG32(&container->regs->_reg); \ +} \ +static inline void dwc_write_##_reg(_container_type *container, uint32_t data) { \ + DWC_WRITE_REG32(&container->regs->_reg, data); \ +} + +# endif /* DWC_DEBUG_REGS */ + +#endif /* DWC_LINUX */ + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) + +# ifdef DWC_DEBUG_REGS + +#define dwc_define_read_write_reg_n(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg##_n(void *io_ctx, _container_type *container, int num) { \ + return DWC_READ_REG32(io_ctx, &container->regs->_reg[num]); \ +} \ +static inline void dwc_write_##_reg##_n(void *io_ctx, _container_type *container, int num, uint32_t data) { \ + DWC_DEBUG("WRITING %8s[%d]: %p: %08x", #_reg, num, \ + &(((uint32_t*)container->regs->_reg)[num]), data); \ + DWC_WRITE_REG32(io_ctx, &(((uint32_t*)container->regs->_reg)[num]), data); \ +} + +#define dwc_define_read_write_reg(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg(void *io_ctx, _container_type *container) { \ + return DWC_READ_REG32(io_ctx, &container->regs->_reg); \ +} \ +static inline void dwc_write_##_reg(void *io_ctx, _container_type *container, uint32_t data) { \ + DWC_DEBUG("WRITING %11s: %p: %08x", #_reg, &container->regs->_reg, data); \ + DWC_WRITE_REG32(io_ctx, &container->regs->_reg, data); \ +} + +# else /* DWC_DEBUG_REGS */ + +#define dwc_define_read_write_reg_n(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg##_n(void *io_ctx, _container_type *container, int num) { \ + return DWC_READ_REG32(io_ctx, &container->regs->_reg[num]); \ +} \ +static inline void dwc_write_##_reg##_n(void *io_ctx, _container_type *container, int num, uint32_t data) { \ + DWC_WRITE_REG32(io_ctx, &(((uint32_t*)container->regs->_reg)[num]), data); \ +} + +#define dwc_define_read_write_reg(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg(void *io_ctx, _container_type *container) { \ + return DWC_READ_REG32(io_ctx, &container->regs->_reg); \ +} \ +static inline void dwc_write_##_reg(void *io_ctx, _container_type *container, uint32_t data) { \ + DWC_WRITE_REG32(io_ctx, &container->regs->_reg, data); \ +} + +# endif /* DWC_DEBUG_REGS */ + +#endif /* DWC_FREEBSD || DWC_NETBSD */ + +/** @endcond */ + + +#ifdef DWC_CRYPTOLIB +/** @name Crypto Functions + * + * These are the low-level cryptographic functions used by the driver. */ + +/** Perform AES CBC */ +extern int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out); +#define dwc_aes_cbc DWC_AES_CBC + +/** Fill the provided buffer with random bytes. These should be cryptographic grade random numbers. */ +extern void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length); +#define dwc_random_bytes DWC_RANDOM_BYTES + +/** Perform the SHA-256 hash function */ +extern int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out); +#define dwc_sha256 DWC_SHA256 + +/** Calculated the HMAC-SHA256 */ +extern int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t *out); +#define dwc_hmac_sha256 DWC_HMAC_SHA256 + +#endif /* DWC_CRYPTOLIB */ + + +/** @name Memory Allocation + * + * These function provide access to memory allocation. There are only 2 DMA + * functions and 3 Regular memory functions that need to be implemented. None + * of the memory debugging routines need to be implemented. The allocation + * routines all ZERO the contents of the memory. + * + * Defining DWC_DEBUG_MEMORY turns on memory debugging and statistic gathering. + * This checks for memory leaks, keeping track of alloc/free pairs. It also + * keeps track of how much memory the driver is using at any given time. */ + +#define DWC_PAGE_SIZE 4096 +#define DWC_PAGE_OFFSET(addr) (((uint32_t)addr) & 0xfff) +#define DWC_PAGE_ALIGNED(addr) ((((uint32_t)addr) & 0xfff) == 0) + +#define DWC_INVALID_DMA_ADDR 0x0 + +#ifdef DWC_LINUX +/** Type for a DMA address */ +typedef dma_addr_t dwc_dma_t; +#endif + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +typedef bus_addr_t dwc_dma_t; +#endif + +#ifdef DWC_FREEBSD +typedef struct dwc_dmactx { + struct device *dev; + bus_dma_tag_t dma_tag; + bus_dmamap_t dma_map; + bus_addr_t dma_paddr; + void *dma_vaddr; +} dwc_dmactx_t; +#endif + +#ifdef DWC_NETBSD +typedef struct dwc_dmactx { + struct device *dev; + bus_dma_tag_t dma_tag; + bus_dmamap_t dma_map; + bus_dma_segment_t segs[1]; + int nsegs; + bus_addr_t dma_paddr; + void *dma_vaddr; +} dwc_dmactx_t; +#endif + +/* @todo these functions will be added in the future */ +#if 0 +/** + * Creates a DMA pool from which you can allocate DMA buffers. Buffers + * allocated from this pool will be guaranteed to meet the size, alignment, and + * boundary requirements specified. + * + * @param[in] size Specifies the size of the buffers that will be allocated from + * this pool. + * @param[in] align Specifies the byte alignment requirements of the buffers + * allocated from this pool. Must be a power of 2. + * @param[in] boundary Specifies the N-byte boundary that buffers allocated from + * this pool must not cross. + * + * @returns A pointer to an internal opaque structure which is not to be + * accessed outside of these library functions. Use this handle to specify + * which pools to allocate/free DMA buffers from and also to destroy the pool, + * when you are done with it. + */ +extern dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size, uint32_t align, uint32_t boundary); + +/** + * Destroy a DMA pool. All buffers allocated from that pool must be freed first. + */ +extern void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool); + +/** + * Allocate a buffer from the specified DMA pool and zeros its contents. + */ +extern void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr); + +/** + * Free a previously allocated buffer from the DMA pool. + */ +extern void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr); +#endif + +/** Allocates a DMA capable buffer and zeroes its contents. */ +extern void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr); + +/** Allocates a DMA capable buffer and zeroes its contents in atomic contest */ +extern void *__DWC_DMA_ALLOC_ATOMIC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr); + +/** Frees a previously allocated buffer. */ +extern void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr); + +/** Allocates a block of memory and zeroes its contents. */ +extern void *__DWC_ALLOC(void *mem_ctx, uint32_t size); + +/** Allocates a block of memory and zeroes its contents, in an atomic manner + * which can be used inside interrupt context. The size should be sufficiently + * small, a few KB at most, such that failures are not likely to occur. Can just call + * __DWC_ALLOC if it is atomic. */ +extern void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size); + +/** Frees a previously allocated buffer. */ +extern void __DWC_FREE(void *mem_ctx, void *addr); + +#ifndef DWC_DEBUG_MEMORY + +#define DWC_ALLOC(_size_) __DWC_ALLOC(NULL, _size_) +#define DWC_ALLOC_ATOMIC(_size_) __DWC_ALLOC_ATOMIC(NULL, _size_) +#define DWC_FREE(_addr_) __DWC_FREE(NULL, _addr_) + +# ifdef DWC_LINUX +#define DWC_DMA_ALLOC __DWC_DMA_ALLOC +#define DWC_DMA_ALLOC_ATOMIC __DWC_DMA_ALLOC_ATOMIC +#define DWC_DMA_FREE __DWC_DMA_FREE +# endif + +# if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +#define DWC_DMA_ALLOC __DWC_DMA_ALLOC +#define DWC_DMA_FREE __DWC_DMA_FREE +# endif + +#else /* DWC_DEBUG_MEMORY */ + +extern void *dwc_alloc_debug(void *mem_ctx, uint32_t size, char const *func, int line); +extern void *dwc_alloc_atomic_debug(void *mem_ctx, uint32_t size, char const *func, int line); +extern void dwc_free_debug(void *mem_ctx, void *addr, char const *func, int line); +extern void *dwc_dma_alloc_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr, + char const *func, int line); +extern void *dwc_dma_alloc_atomic_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr, + char const *func, int line); +extern void dwc_dma_free_debug(void *dma_ctx, uint32_t size, void *virt_addr, + dwc_dma_t dma_addr, char const *func, int line); + +extern int dwc_memory_debug_start(void *mem_ctx); +extern void dwc_memory_debug_stop(void); +extern void dwc_memory_debug_report(void); + +#define DWC_ALLOC(_size_) dwc_alloc_debug(NULL, _size_, __func__, __LINE__) +#define DWC_ALLOC_ATOMIC(_size_) dwc_alloc_atomic_debug(NULL, _size_, \ + __func__, __LINE__) +#define DWC_FREE(_addr_) dwc_free_debug(NULL, _addr_, __func__, __LINE__) + +# ifdef DWC_LINUX +#define DWC_DMA_ALLOC(_size_,_dma_) dwc_dma_alloc_debug(NULL, _size_, \ + _dma_, __func__, __LINE__) +#define DWC_DMA_ALLOC_ATOMIC(_size_,_dma_) dwc_dma_alloc_atomic_debug(NULL, _size_, \ + _dma_, __func__, __LINE__) +#define DWC_DMA_FREE(_size_,_virt_,_dma_) dwc_dma_free_debug(NULL, _size_, \ + _virt_, _dma_, __func__, __LINE__) +# endif + +# if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +#define DWC_DMA_ALLOC(_ctx_,_size_,_dma_) dwc_dma_alloc_debug(_ctx_, _size_, \ + _dma_, __func__, __LINE__) +#define DWC_DMA_FREE(_ctx_,_size_,_virt_,_dma_) dwc_dma_free_debug(_ctx_, _size_, \ + _virt_, _dma_, __func__, __LINE__) +# endif + +#endif /* DWC_DEBUG_MEMORY */ + +#define dwc_alloc(_size_) DWC_ALLOC(_size_) +#define dwc_alloc_atomic(_size_) DWC_ALLOC_ATOMIC(_size_) +#define dwc_free(_addr_) DWC_FREE(_addr_) + +#ifdef DWC_LINUX +/* Linux doesn't need any extra parameters for DMA buffer allocation, so we + * just throw away the DMA context parameter. + */ +#define dwc_dma_alloc(_size_,_dma_) DWC_DMA_ALLOC(_size_, _dma_) +#define dwc_dma_alloc_atomic(_size_,_dma_) DWC_DMA_ALLOC_ATOMIC(_size_, _dma_) +#define dwc_dma_free(_size_,_virt_,_dma_) DWC_DMA_FREE(_size_, _virt_, _dma_) +#endif + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +/** BSD needs several extra parameters for DMA buffer allocation, so we pass + * them in using the DMA context parameter. + */ +#define dwc_dma_alloc DWC_DMA_ALLOC +#define dwc_dma_free DWC_DMA_FREE +#endif + + +/** @name Memory and String Processing */ + +/** memset() clone */ +extern void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size); +#define dwc_memset DWC_MEMSET + +/** memcpy() clone */ +extern void *DWC_MEMCPY(void *dest, void const *src, uint32_t size); +#define dwc_memcpy DWC_MEMCPY + +/** memmove() clone */ +extern void *DWC_MEMMOVE(void *dest, void *src, uint32_t size); +#define dwc_memmove DWC_MEMMOVE + +/** memcmp() clone */ +extern int DWC_MEMCMP(void *m1, void *m2, uint32_t size); +#define dwc_memcmp DWC_MEMCMP + +/** strcmp() clone */ +extern int DWC_STRCMP(void *s1, void *s2); +#define dwc_strcmp DWC_STRCMP + +/** strncmp() clone */ +extern int DWC_STRNCMP(void *s1, void *s2, uint32_t size); +#define dwc_strncmp DWC_STRNCMP + +/** strlen() clone, for NULL terminated ASCII strings */ +extern int DWC_STRLEN(char const *str); +#define dwc_strlen DWC_STRLEN + +/** strcpy() clone, for NULL terminated ASCII strings */ +extern char *DWC_STRCPY(char *to, const char *from); +#define dwc_strcpy DWC_STRCPY + +/** strdup() clone. If you wish to use memory allocation debugging, this + * implementation of strdup should use the DWC_* memory routines instead of + * calling a predefined strdup. Otherwise the memory allocated by this routine + * will not be seen by the debugging routines. */ +extern char *DWC_STRDUP(char const *str); +#define dwc_strdup(_ctx_,_str_) DWC_STRDUP(_str_) + +/** NOT an atoi() clone. Read the description carefully. Returns an integer + * converted from the string str in base 10 unless the string begins with a "0x" + * in which case it is base 16. String must be a NULL terminated sequence of + * ASCII characters and may optionally begin with whitespace, a + or -, and a + * "0x" prefix if base 16. The remaining characters must be valid digits for + * the number and end with a NULL character. If any invalid characters are + * encountered or it returns with a negative error code and the results of the + * conversion are undefined. On sucess it returns 0. Overflow conditions are + * undefined. An example implementation using atoi() can be referenced from the + * Linux implementation. */ +extern int DWC_ATOI(const char *str, int32_t *value); +#define dwc_atoi DWC_ATOI + +/** Same as above but for unsigned. */ +extern int DWC_ATOUI(const char *str, uint32_t *value); +#define dwc_atoui DWC_ATOUI + +#ifdef DWC_UTFLIB +/** This routine returns a UTF16LE unicode encoded string from a UTF8 string. */ +extern int DWC_UTF8_TO_UTF16LE(uint8_t const *utf8string, uint16_t *utf16string, unsigned len); +#define dwc_utf8_to_utf16le DWC_UTF8_TO_UTF16LE +#endif + + +/** @name Wait queues + * + * Wait queues provide a means of synchronizing between threads or processes. A + * process can block on a waitq if some condition is not true, waiting for it to + * become true. When the waitq is triggered all waiting process will get + * unblocked and the condition will be check again. Waitqs should be triggered + * every time a condition can potentially change.*/ +struct dwc_waitq; + +/** Type for a waitq */ +typedef struct dwc_waitq dwc_waitq_t; + +/** The type of waitq condition callback function. This is called every time + * condition is evaluated. */ +typedef int (*dwc_waitq_condition_t)(void *data); + +/** Allocate a waitq */ +extern dwc_waitq_t *DWC_WAITQ_ALLOC(void); +#define dwc_waitq_alloc(_ctx_) DWC_WAITQ_ALLOC() + +/** Free a waitq */ +extern void DWC_WAITQ_FREE(dwc_waitq_t *wq); +#define dwc_waitq_free DWC_WAITQ_FREE + +/** Check the condition and if it is false, block on the waitq. When unblocked, check the + * condition again. The function returns when the condition becomes true. The return value + * is 0 on condition true, DWC_WAITQ_ABORTED on abort or killed, or DWC_WAITQ_UNKNOWN on error. */ +extern int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data); +#define dwc_waitq_wait DWC_WAITQ_WAIT + +/** Check the condition and if it is false, block on the waitq. When unblocked, + * check the condition again. The function returns when the condition become + * true or the timeout has passed. The return value is 0 on condition true or + * DWC_TIMED_OUT on timeout, or DWC_WAITQ_ABORTED, or DWC_WAITQ_UNKNOWN on + * error. */ +extern int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, + void *data, int32_t msecs); +#define dwc_waitq_wait_timeout DWC_WAITQ_WAIT_TIMEOUT + +/** Trigger a waitq, unblocking all processes. This should be called whenever a condition + * has potentially changed. */ +extern void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq); +#define dwc_waitq_trigger DWC_WAITQ_TRIGGER + +/** Unblock all processes waiting on the waitq with an ABORTED result. */ +extern void DWC_WAITQ_ABORT(dwc_waitq_t *wq); +#define dwc_waitq_abort DWC_WAITQ_ABORT + + +/** @name Threads + * + * A thread must be explicitly stopped. It must check DWC_THREAD_SHOULD_STOP + * whenever it is woken up, and then return. The DWC_THREAD_STOP function + * returns the value from the thread. + */ + +struct dwc_thread; + +/** Type for a thread */ +typedef struct dwc_thread dwc_thread_t; + +/** The thread function */ +typedef int (*dwc_thread_function_t)(void *data); + +/** Create a thread and start it running the thread_function. Returns a handle + * to the thread */ +extern dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data); +#define dwc_thread_run(_ctx_,_func_,_name_,_data_) DWC_THREAD_RUN(_func_, _name_, _data_) + +/** Stops a thread. Return the value returned by the thread. Or will return + * DWC_ABORT if the thread never started. */ +extern int DWC_THREAD_STOP(dwc_thread_t *thread); +#define dwc_thread_stop DWC_THREAD_STOP + +/** Signifies to the thread that it must stop. */ +#ifdef DWC_LINUX +/* Linux doesn't need any parameters for kthread_should_stop() */ +extern dwc_bool_t DWC_THREAD_SHOULD_STOP(void); +#define dwc_thread_should_stop(_thrd_) DWC_THREAD_SHOULD_STOP() + +/* No thread_exit function in Linux */ +#define dwc_thread_exit(_thrd_) +#endif + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +/** BSD needs the thread pointer for kthread_suspend_check() */ +extern dwc_bool_t DWC_THREAD_SHOULD_STOP(dwc_thread_t *thread); +#define dwc_thread_should_stop DWC_THREAD_SHOULD_STOP + +/** The thread must call this to exit. */ +extern void DWC_THREAD_EXIT(dwc_thread_t *thread); +#define dwc_thread_exit DWC_THREAD_EXIT +#endif + + +/** @name Work queues + * + * Workqs are used to queue a callback function to be called at some later time, + * in another thread. */ +struct dwc_workq; + +/** Type for a workq */ +typedef struct dwc_workq dwc_workq_t; + +/** The type of the callback function to be called. */ +typedef void (*dwc_work_callback_t)(void *data); + +/** Allocate a workq */ +extern dwc_workq_t *DWC_WORKQ_ALLOC(char *name); +#define dwc_workq_alloc(_ctx_,_name_) DWC_WORKQ_ALLOC(_name_) + +/** Free a workq. All work must be completed before being freed. */ +extern void DWC_WORKQ_FREE(dwc_workq_t *workq); +#define dwc_workq_free DWC_WORKQ_FREE + +/** Schedule a callback on the workq, passing in data. The function will be + * scheduled at some later time. */ +extern void DWC_WORKQ_SCHEDULE(dwc_workq_t *workq, dwc_work_callback_t cb, + void *data, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 4, 5))); +#else + ; +#endif +#define dwc_workq_schedule DWC_WORKQ_SCHEDULE + +/** Schedule a callback on the workq, that will be called until at least + * given number miliseconds have passed. */ +extern void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *workq, dwc_work_callback_t cb, + void *data, uint32_t time, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 5, 6))); +#else + ; +#endif +#define dwc_workq_schedule_delayed DWC_WORKQ_SCHEDULE_DELAYED + +/** The number of processes in the workq */ +extern int DWC_WORKQ_PENDING(dwc_workq_t *workq); +#define dwc_workq_pending DWC_WORKQ_PENDING + +/** Blocks until all the work in the workq is complete or timed out. Returns < + * 0 on timeout. */ +extern int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout); +#define dwc_workq_wait_work_done DWC_WORKQ_WAIT_WORK_DONE + + +/** @name Tasklets + * + */ +struct dwc_tasklet; + +/** Type for a tasklet */ +typedef struct dwc_tasklet dwc_tasklet_t; + +/** The type of the callback function to be called */ +typedef void (*dwc_tasklet_callback_t)(void *data); + +/** Allocates a tasklet */ +extern dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data); +#define dwc_task_alloc(_ctx_,_name_,_cb_,_data_) DWC_TASK_ALLOC(_name_, _cb_, _data_) + +/** Frees a tasklet */ +extern void DWC_TASK_FREE(dwc_tasklet_t *task); +#define dwc_task_free DWC_TASK_FREE + +/** Schedules a tasklet to run */ +extern void DWC_TASK_SCHEDULE(dwc_tasklet_t *task); +#define dwc_task_schedule DWC_TASK_SCHEDULE + + +/** @name Timer + * + * Callbacks must be small and atomic. + */ +struct dwc_timer; + +/** Type for a timer */ +typedef struct dwc_timer dwc_timer_t; + +/** The type of the callback function to be called */ +typedef void (*dwc_timer_callback_t)(void *data); + +/** Allocates a timer */ +extern dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data); +#define dwc_timer_alloc(_ctx_,_name_,_cb_,_data_) DWC_TIMER_ALLOC(_name_,_cb_,_data_) + +/** Frees a timer */ +extern void DWC_TIMER_FREE(dwc_timer_t *timer); +#define dwc_timer_free DWC_TIMER_FREE + +/** Schedules the timer to run at time ms from now. And will repeat at every + * repeat_interval msec therafter + * + * Modifies a timer that is still awaiting execution to a new expiration time. + * The mod_time is added to the old time. */ +extern void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time); +#define dwc_timer_schedule DWC_TIMER_SCHEDULE + +/** Disables the timer from execution. */ +extern void DWC_TIMER_CANCEL(dwc_timer_t *timer); +#define dwc_timer_cancel DWC_TIMER_CANCEL + + +/** @name Spinlocks + * + * These locks are used when the work between the lock/unlock is atomic and + * short. Interrupts are also disabled during the lock/unlock and thus they are + * suitable to lock between interrupt/non-interrupt context. They also lock + * between processes if you have multiple CPUs or Preemption. If you don't have + * multiple CPUS or Preemption, then the you can simply implement the + * DWC_SPINLOCK and DWC_SPINUNLOCK to disable and enable interrupts. Because + * the work between the lock/unlock is atomic, the process context will never + * change, and so you never have to lock between processes. */ + +struct dwc_spinlock; + +/** Type for a spinlock */ +typedef struct dwc_spinlock dwc_spinlock_t; + +/** Type for the 'flags' argument to spinlock funtions */ +typedef unsigned long dwc_irqflags_t; + +/** Returns an initialized lock variable. This function should allocate and + * initialize the OS-specific data structure used for locking. This data + * structure is to be used for the DWC_LOCK and DWC_UNLOCK functions and should + * be freed by the DWC_FREE_LOCK when it is no longer used. */ +extern dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void); +#define dwc_spinlock_alloc(_ctx_) DWC_SPINLOCK_ALLOC() + +/** Frees an initialized lock variable. */ +extern void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock); +#define dwc_spinlock_free(_ctx_,_lock_) DWC_SPINLOCK_FREE(_lock_) + +/** Disables interrupts and blocks until it acquires the lock. + * + * @param lock Pointer to the spinlock. + * @param flags Unsigned long for irq flags storage. + */ +extern void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags); +#define dwc_spinlock_irqsave DWC_SPINLOCK_IRQSAVE + +/** Re-enables the interrupt and releases the lock. + * + * @param lock Pointer to the spinlock. + * @param flags Unsigned long for irq flags storage. Must be the same as was + * passed into DWC_LOCK. + */ +extern void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags); +#define dwc_spinunlock_irqrestore DWC_SPINUNLOCK_IRQRESTORE + +/** Blocks until it acquires the lock. + * + * @param lock Pointer to the spinlock. + */ +extern void DWC_SPINLOCK(dwc_spinlock_t *lock); +#define dwc_spinlock DWC_SPINLOCK + +/** Releases the lock. + * + * @param lock Pointer to the spinlock. + */ +extern void DWC_SPINUNLOCK(dwc_spinlock_t *lock); +#define dwc_spinunlock DWC_SPINUNLOCK + + +/** @name Mutexes + * + * Unlike spinlocks Mutexes lock only between processes and the work between the + * lock/unlock CAN block, therefore it CANNOT be called from interrupt context. + */ + +struct dwc_mutex; + +/** Type for a mutex */ +typedef struct dwc_mutex dwc_mutex_t; + +/* For Linux Mutex Debugging make it inline because the debugging routines use + * the symbol to determine recursive locking. This makes it falsely think + * recursive locking occurs. */ +#if defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES) +#define DWC_MUTEX_ALLOC_LINUX_DEBUG(__mutexp) ({ \ + __mutexp = (dwc_mutex_t *)DWC_ALLOC(sizeof(struct mutex)); \ + mutex_init((struct mutex *)__mutexp); \ +}) +#endif + +/** Allocate a mutex */ +extern dwc_mutex_t *DWC_MUTEX_ALLOC(void); +#define dwc_mutex_alloc(_ctx_) DWC_MUTEX_ALLOC() + +/* For memory leak debugging when using Linux Mutex Debugging */ +#if defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES) +#define DWC_MUTEX_FREE(__mutexp) do { \ + mutex_destroy((struct mutex *)__mutexp); \ + DWC_FREE(__mutexp); \ +} while(0) +#else +/** Free a mutex */ +extern void DWC_MUTEX_FREE(dwc_mutex_t *mutex); +#define dwc_mutex_free(_ctx_,_mutex_) DWC_MUTEX_FREE(_mutex_) +#endif + +/** Lock a mutex */ +extern void DWC_MUTEX_LOCK(dwc_mutex_t *mutex); +#define dwc_mutex_lock DWC_MUTEX_LOCK + +/** Non-blocking lock returns 1 on successful lock. */ +extern int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex); +#define dwc_mutex_trylock DWC_MUTEX_TRYLOCK + +/** Unlock a mutex */ +extern void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex); +#define dwc_mutex_unlock DWC_MUTEX_UNLOCK + + +/** @name Time */ + +/** Microsecond delay. + * + * @param usecs Microseconds to delay. + */ +extern void DWC_UDELAY(uint32_t usecs); +#define dwc_udelay DWC_UDELAY + +/** Millisecond delay. + * + * @param msecs Milliseconds to delay. + */ +extern void DWC_MDELAY(uint32_t msecs); +#define dwc_mdelay DWC_MDELAY + +/** Non-busy waiting. + * Sleeps for specified number of milliseconds. + * + * @param msecs Milliseconds to sleep. + */ +extern void DWC_MSLEEP(uint32_t msecs); +#define dwc_msleep DWC_MSLEEP + +/** + * Returns number of milliseconds since boot. + */ +extern uint32_t DWC_TIME(void); +#define dwc_time DWC_TIME + + + + +/* @mainpage DWC Portability and Common Library + * + * This is the documentation for the DWC Portability and Common Library. + * + * @section intro Introduction + * + * The DWC Portability library consists of wrapper calls and data structures to + * all low-level functions which are typically provided by the OS. The WUDEV + * driver uses only these functions. In order to port the WUDEV driver, only + * the functions in this library need to be re-implemented, with the same + * behavior as documented here. + * + * The Common library consists of higher level functions, which rely only on + * calling the functions from the DWC Portability library. These common + * routines are shared across modules. Some of the common libraries need to be + * used directly by the driver programmer when porting WUDEV. Such as the + * parameter and notification libraries. + * + * @section low Portability Library OS Wrapper Functions + * + * Any function starting with DWC and in all CAPS is a low-level OS-wrapper that + * needs to be implemented when porting, for example DWC_MUTEX_ALLOC(). All of + * these functions are included in the dwc_os.h file. + * + * There are many functions here covering a wide array of OS services. Please + * see dwc_os.h for details, and implementation notes for each function. + * + * @section common Common Library Functions + * + * Any function starting with dwc and in all lowercase is a common library + * routine. These functions have a portable implementation and do not need to + * be reimplemented when porting. The common routines can be used by any + * driver, and some must be used by the end user to control the drivers. For + * example, you must use the Parameter common library in order to set the + * parameters in the WUDEV module. + * + * The common libraries consist of the following: + * + * - Connection Contexts - Used internally and can be used by end-user. See dwc_cc.h + * - Parameters - Used internally and can be used by end-user. See dwc_params.h + * - Notifications - Used internally and can be used by end-user. See dwc_notifier.h + * - Lists - Used internally and can be used by end-user. See dwc_list.h + * - Memory Debugging - Used internally and can be used by end-user. See dwc_os.h + * - Modpow - Used internally only. See dwc_modpow.h + * - DH - Used internally only. See dwc_dh.h + * - Crypto - Used internally only. See dwc_crypto.h + * + * + * @section prereq Prerequistes For dwc_os.h + * @subsection types Data Types + * + * The dwc_os.h file assumes that several low-level data types are pre defined for the + * compilation environment. These data types are: + * + * - uint8_t - unsigned 8-bit data type + * - int8_t - signed 8-bit data type + * - uint16_t - unsigned 16-bit data type + * - int16_t - signed 16-bit data type + * - uint32_t - unsigned 32-bit data type + * - int32_t - signed 32-bit data type + * - uint64_t - unsigned 64-bit data type + * - int64_t - signed 64-bit data type + * + * Ensure that these are defined before using dwc_os.h. The easiest way to do + * that is to modify the top of the file to include the appropriate header. + * This is already done for the Linux environment. If the DWC_LINUX macro is + * defined, the correct header will be added. A standard header is + * also used for environments where standard C headers are available. + * + * @subsection stdarg Variable Arguments + * + * Variable arguments are provided by a standard C header . it is + * available in Both the Linux and ANSI C enviornment. An equivalent must be + * provided in your enviornment in order to use dwc_os.h with the debug and + * tracing message functionality. + * + * @subsection thread Threading + * + * WUDEV Core must be run on an operating system that provides for multiple + * threads/processes. Threading can be implemented in many ways, even in + * embedded systems without an operating system. At the bare minimum, the + * system should be able to start any number of processes at any time to handle + * special work. It need not be a pre-emptive system. Process context can + * change upon a call to a blocking function. The hardware interrupt context + * that calls the module's ISR() function must be differentiable from process + * context, even if your processes are impemented via a hardware interrupt. + * Further locking mechanism between process must exist (or be implemented), and + * process context must have a way to disable interrupts for a period of time to + * lock them out. If all of this exists, the functions in dwc_os.h related to + * threading should be able to be implemented with the defined behavior. + * + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _DWC_OS_H_ */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_adp.c b/drivers/usb/gadget/dwc_otg/dwc_otg_adp.c new file mode 100644 index 00000000..0877472c --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_adp.c @@ -0,0 +1,854 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_adp.c $ + * $Revision: #12 $ + * $Date: 2011/10/26 $ + * $Change: 1873028 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#include "dwc_os.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_adp.h" + +/** @file + * + * This file contains the most of the Attach Detect Protocol implementation for + * the driver to support OTG Rev2.0. + * + */ + +void dwc_otg_adp_write_reg(dwc_otg_core_if_t * core_if, uint32_t value) +{ + adpctl_data_t adpctl; + + adpctl.d32 = value; + adpctl.b.ar = 0x2; + + DWC_WRITE_REG32(&core_if->core_global_regs->adpctl, adpctl.d32); + + while (adpctl.b.ar) { + adpctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->adpctl); + } + +} + +/** + * Function is called to read ADP registers + */ +uint32_t dwc_otg_adp_read_reg(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + + adpctl.d32 = 0; + adpctl.b.ar = 0x1; + + DWC_WRITE_REG32(&core_if->core_global_regs->adpctl, adpctl.d32); + + while (adpctl.b.ar) { + adpctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->adpctl); + } + + return adpctl.d32; +} + +/** + * Function is called to read ADPCTL register and filter Write-clear bits + */ +uint32_t dwc_otg_adp_read_reg_filter(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + adpctl.b.adp_tmout_int = 0; + adpctl.b.adp_prb_int = 0; + adpctl.b.adp_tmout_int = 0; + + return adpctl.d32; +} + +/** + * Function is called to write ADP registers + */ +void dwc_otg_adp_modify_reg(dwc_otg_core_if_t * core_if, uint32_t clr, + uint32_t set) +{ + dwc_otg_adp_write_reg(core_if, + (dwc_otg_adp_read_reg(core_if) & (~clr)) | set); +} + +static void adp_sense_timeout(void *ptr) +{ + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr; + core_if->adp.sense_timer_started = 0; + DWC_PRINTF("ADP SENSE TIMEOUT\n"); + if (core_if->adp_enable) { + dwc_otg_adp_sense_stop(core_if); + dwc_otg_adp_probe_start(core_if); + } +} + +/** + * This function is called when the ADP vbus timer expires. Timeout is 1.1s. + */ +static void adp_vbuson_timeout(void *ptr) +{ + gpwrdn_data_t gpwrdn; + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr; + hprt0_data_t hprt0 = {.d32 = 0 }; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + DWC_PRINTF("%s: 1.1 seconds expire after turning on VBUS\n",__FUNCTION__); + if (core_if) { + core_if->adp.vbuson_timer_started = 0; + /* Turn off vbus */ + hprt0.b.prtpwr = 1; + DWC_MODIFY_REG32(core_if->host_if->hprt0, hprt0.d32, 0); + gpwrdn.d32 = 0; + + /* Power off the core */ + if (core_if->power_down == 2) { + /* Enable Wakeup Logic */ +// gpwrdn.b.wkupactiv = 1; + gpwrdn.b.pmuactv = 0; + gpwrdn.b.pwrdnrstn = 1; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, + gpwrdn.d32); + + /* Suspend the Phy Clock */ + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32); + + /* Switch on VDD */ +// gpwrdn.b.wkupactiv = 1; + gpwrdn.b.pmuactv = 1; + gpwrdn.b.pwrdnrstn = 1; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, + gpwrdn.d32); + } else { + /* Enable Power Down Logic */ + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + } + + /* Power off the core */ + if (core_if->power_down == 2) { + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, + gpwrdn.d32, 0); + } + + /* Unmask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + + dwc_otg_adp_probe_start(core_if); + dwc_otg_dump_global_registers(core_if); + dwc_otg_dump_host_registers(core_if); + } + +} + +/** + * Start the ADP Initial Probe timer to detect if Port Connected interrupt is + * not asserted within 1.1 seconds. + * + * @param core_if the pointer to core_if strucure. + */ +void dwc_otg_adp_vbuson_timer_start(dwc_otg_core_if_t * core_if) +{ + core_if->adp.vbuson_timer_started = 1; + if (core_if->adp.vbuson_timer) + { + DWC_PRINTF("SCHEDULING VBUSON TIMER\n"); + /* 1.1 secs + 60ms necessary for cil_hcd_start*/ + DWC_TIMER_SCHEDULE(core_if->adp.vbuson_timer, 1160); + } else { + DWC_WARN("VBUSON_TIMER = %p\n",core_if->adp.vbuson_timer); + } +} + +#if 0 +/** + * Masks all DWC OTG core interrupts + * + */ +static void mask_all_interrupts(dwc_otg_core_if_t * core_if) +{ + int i; + gahbcfg_data_t ahbcfg = {.d32 = 0 }; + + /* Mask Host Interrupts */ + + /* Clear and disable HCINTs */ + for (i = 0; i < core_if->core_params->host_channels; i++) { + DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcintmsk, 0); + DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcint, 0xFFFFFFFF); + + } + + /* Clear and disable HAINT */ + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haintmsk, 0x0000); + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haint, 0xFFFFFFFF); + + /* Mask Device Interrupts */ + if (!core_if->multiproc_int_enable) { + /* Clear and disable IN Endpoint interrupts */ + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->diepmsk, 0); + for (i = 0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]-> + diepint, 0xFFFFFFFF); + } + + /* Clear and disable OUT Endpoint interrupts */ + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->doepmsk, 0); + for (i = 0; i <= core_if->dev_if->num_out_eps; i++) { + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]-> + doepint, 0xFFFFFFFF); + } + + /* Clear and disable DAINT */ + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daint, + 0xFFFFFFFF); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daintmsk, 0); + } else { + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs-> + diepeachintmsk[i], 0); + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]-> + diepint, 0xFFFFFFFF); + } + + for (i = 0; i < core_if->dev_if->num_out_eps; ++i) { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[i], 0); + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]-> + doepint, 0xFFFFFFFF); + } + + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->deachintmsk, + 0); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->deachint, + 0xFFFFFFFF); + + } + + /* Disable interrupts */ + ahbcfg.b.glblintrmsk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, ahbcfg.d32, 0); + + /* Disable all interrupts. */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, 0); + + /* Clear any pending interrupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* Clear any pending OTG Interrupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gotgint, 0xFFFFFFFF); +} + +/** + * Unmask Port Connection Detected interrupt + * + */ +static void unmask_conn_det_intr(dwc_otg_core_if_t * core_if) +{ + gintmsk_data_t gintmsk = {.d32 = 0,.b.portintr = 1 }; + + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32); +} +#endif + +/** + * Starts the ADP Probing + * + * @param core_if the pointer to core_if structure. + */ +uint32_t dwc_otg_adp_probe_start(dwc_otg_core_if_t * core_if) +{ + + adpctl_data_t adpctl = {.d32 = 0}; + gpwrdn_data_t gpwrdn; +#if 0 + adpctl_data_t adpctl_int = {.d32 = 0, .b.adp_prb_int = 1, + .b.adp_sns_int = 1, b.adp_tmout_int}; +#endif + dwc_otg_disable_global_interrupts(core_if); + DWC_PRINTF("ADP Probe Start\n"); + core_if->adp.probe_enabled = 1; + + adpctl.b.adpres = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + while (adpctl.b.adpres) { + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + } + + adpctl.d32 = 0; + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + + /* In Host mode unmask SRP detected interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.sts_chngint_msk = 1; + if (!gpwrdn.b.idsts) { + gpwrdn.b.srp_det_msk = 1; + } + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + + adpctl.b.adp_tmout_int_msk = 1; + adpctl.b.adp_prb_int_msk = 1; + adpctl.b.prb_dschg = 1; + adpctl.b.prb_delta = 1; + adpctl.b.prb_per = 1; + adpctl.b.adpen = 1; + adpctl.b.enaprb = 1; + + dwc_otg_adp_write_reg(core_if, adpctl.d32); + DWC_PRINTF("ADP Probe Finish\n"); + return 0; +} + +/** + * Starts the ADP Sense timer to detect if ADP Sense interrupt is not asserted + * within 3 seconds. + * + * @param core_if the pointer to core_if strucure. + */ +void dwc_otg_adp_sense_timer_start(dwc_otg_core_if_t * core_if) +{ + core_if->adp.sense_timer_started = 1; + DWC_TIMER_SCHEDULE(core_if->adp.sense_timer, 3000 /* 3 secs */ ); +} + +/** + * Starts the ADP Sense + * + * @param core_if the pointer to core_if strucure. + */ +uint32_t dwc_otg_adp_sense_start(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + + DWC_PRINTF("ADP Sense Start\n"); + + /* Unmask ADP sense interrupt and mask all other from the core */ + adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if); + adpctl.b.adp_sns_int_msk = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + dwc_otg_disable_global_interrupts(core_if); // vahrama + + /* Set ADP reset bit*/ + adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if); + adpctl.b.adpres = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + while (adpctl.b.adpres) { + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + } + + adpctl.b.adpres = 0; + adpctl.b.adpen = 1; + adpctl.b.enasns = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + dwc_otg_adp_sense_timer_start(core_if); + + return 0; +} + +/** + * Stops the ADP Probing + * + * @param core_if the pointer to core_if strucure. + */ +uint32_t dwc_otg_adp_probe_stop(dwc_otg_core_if_t * core_if) +{ + + adpctl_data_t adpctl; + DWC_PRINTF("Stop ADP probe\n"); + core_if->adp.probe_enabled = 0; + core_if->adp.probe_counter = 0; + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + + adpctl.b.adpen = 0; + adpctl.b.adp_prb_int = 1; + adpctl.b.adp_tmout_int = 1; + adpctl.b.adp_sns_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * Stops the ADP Sensing + * + * @param core_if the pointer to core_if strucure. + */ +uint32_t dwc_otg_adp_sense_stop(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + + core_if->adp.sense_enabled = 0; + + adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if); + adpctl.b.enasns = 0; + adpctl.b.adp_sns_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * Called to turn on the VBUS after initial ADP probe in host mode. + * If port power was already enabled in cil_hcd_start function then + * only schedule a timer. + * + * @param core_if the pointer to core_if structure. + */ +void dwc_otg_adp_turnon_vbus(dwc_otg_core_if_t * core_if) +{ + hprt0_data_t hprt0 = {.d32 = 0 }; + hprt0.d32 = dwc_otg_read_hprt0(core_if); + DWC_PRINTF("Turn on VBUS for 1.1s, port power is %d\n", hprt0.b.prtpwr); + + if (hprt0.b.prtpwr == 0) { + hprt0.b.prtpwr = 1; + //DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + } + + dwc_otg_adp_vbuson_timer_start(core_if); +} + +/** + * Called right after driver is loaded + * to perform initial actions for ADP + * + * @param core_if the pointer to core_if structure. + * @param is_host - flag for current mode of operation either from GINTSTS or GPWRDN + */ +void dwc_otg_adp_start(dwc_otg_core_if_t * core_if, uint8_t is_host) +{ + gpwrdn_data_t gpwrdn; + + DWC_PRINTF("ADP Initial Start\n"); + core_if->adp.adp_started = 1; + + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + dwc_otg_disable_global_interrupts(core_if); + if (is_host) { + DWC_PRINTF("HOST MODE\n"); + /* Enable Power Down Logic Interrupt*/ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + /* Initialize first ADP probe to obtain Ramp Time value */ + core_if->adp.initial_probe = 1; + dwc_otg_adp_probe_start(core_if); + } else { + gotgctl_data_t gotgctl; + gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + DWC_PRINTF("DEVICE MODE\n"); + if (gotgctl.b.bsesvld == 0) { + /* Enable Power Down Logic Interrupt*/ + gpwrdn.d32 = 0; + DWC_PRINTF("VBUS is not valid - start ADP probe\n"); + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + core_if->adp.initial_probe = 1; + dwc_otg_adp_probe_start(core_if); + } else { + DWC_PRINTF("VBUS is valid - initialize core as a Device\n"); + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + dwc_otg_dump_global_registers(core_if); + dwc_otg_dump_dev_registers(core_if); + } + } +} + +void dwc_otg_adp_init(dwc_otg_core_if_t * core_if) +{ + core_if->adp.adp_started = 0; + core_if->adp.initial_probe = 0; + core_if->adp.probe_timer_values[0] = -1; + core_if->adp.probe_timer_values[1] = -1; + core_if->adp.probe_enabled = 0; + core_if->adp.sense_enabled = 0; + core_if->adp.sense_timer_started = 0; + core_if->adp.vbuson_timer_started = 0; + core_if->adp.probe_counter = 0; + core_if->adp.gpwrdn = 0; + core_if->adp.attached = DWC_OTG_ADP_UNKOWN; + /* Initialize timers */ + core_if->adp.sense_timer = + DWC_TIMER_ALLOC("ADP SENSE TIMER", adp_sense_timeout, core_if); + core_if->adp.vbuson_timer = + DWC_TIMER_ALLOC("ADP VBUS ON TIMER", adp_vbuson_timeout, core_if); + if (!core_if->adp.sense_timer || !core_if->adp.vbuson_timer) + { + DWC_ERROR("Could not allocate memory for ADP timers\n"); + } +} + +void dwc_otg_adp_remove(dwc_otg_core_if_t * core_if) +{ + gpwrdn_data_t gpwrdn = { .d32 = 0 }; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + if (core_if->adp.probe_enabled) + dwc_otg_adp_probe_stop(core_if); + if (core_if->adp.sense_enabled) + dwc_otg_adp_sense_stop(core_if); + if (core_if->adp.sense_timer_started) + DWC_TIMER_CANCEL(core_if->adp.sense_timer); + if (core_if->adp.vbuson_timer_started) + DWC_TIMER_CANCEL(core_if->adp.vbuson_timer); + DWC_TIMER_FREE(core_if->adp.sense_timer); + DWC_TIMER_FREE(core_if->adp.vbuson_timer); +} + +///////////////////////////////////////////////////////////////////// +////////////// ADP Interrupt Handlers /////////////////////////////// +///////////////////////////////////////////////////////////////////// +/** + * This function sets Ramp Timer values + */ +static uint32_t set_timer_value(dwc_otg_core_if_t * core_if, uint32_t val) +{ + if (core_if->adp.probe_timer_values[0] == -1) { + core_if->adp.probe_timer_values[0] = val; + core_if->adp.probe_timer_values[1] = -1; + return 1; + } else { + core_if->adp.probe_timer_values[1] = + core_if->adp.probe_timer_values[0]; + core_if->adp.probe_timer_values[0] = val; + return 0; + } +} + +/** + * This function compares Ramp Timer values + */ +static uint32_t compare_timer_values(dwc_otg_core_if_t * core_if) +{ + uint32_t diff; + if (core_if->adp.probe_timer_values[0]>=core_if->adp.probe_timer_values[1]) + diff = core_if->adp.probe_timer_values[0]-core_if->adp.probe_timer_values[1]; + else + diff = core_if->adp.probe_timer_values[1]-core_if->adp.probe_timer_values[0]; + if(diff < 2) { + return 0; + } else { + return 1; + } +} + +/** + * This function handles ADP Probe Interrupts + */ +static int32_t dwc_otg_adp_handle_prb_intr(dwc_otg_core_if_t * core_if, + uint32_t val) +{ + adpctl_data_t adpctl = {.d32 = 0 }; + gpwrdn_data_t gpwrdn, temp; + adpctl.d32 = val; + + temp.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + core_if->adp.probe_counter++; + core_if->adp.gpwrdn = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + if (adpctl.b.rtim == 0 && !temp.b.idsts){ + DWC_PRINTF("RTIM value is 0\n"); + goto exit; + } + if (set_timer_value(core_if, adpctl.b.rtim) && + core_if->adp.initial_probe) { + core_if->adp.initial_probe = 0; + dwc_otg_adp_probe_stop(core_if); + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* check which value is for device mode and which for Host mode */ + if (!temp.b.idsts) { /* considered host mode value is 0 */ + /* + * Turn on VBUS after initial ADP probe. + */ + core_if->op_state = A_HOST; + dwc_otg_enable_global_interrupts(core_if); + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_start(core_if); + dwc_otg_adp_turnon_vbus(core_if); + DWC_SPINLOCK(core_if->lock); + } else { + /* + * Initiate SRP after initial ADP probe. + */ + dwc_otg_enable_global_interrupts(core_if); + dwc_otg_initiate_srp(core_if); + } + } else if (core_if->adp.probe_counter > 2){ + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + if (compare_timer_values(core_if)) { + DWC_PRINTF("Difference in timer values !!! \n"); +// core_if->adp.attached = DWC_OTG_ADP_ATTACHED; + dwc_otg_adp_probe_stop(core_if); + + /* Power on the core */ + if (core_if->power_down == 2) { + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } + + /* check which value is for device mode and which for Host mode */ + if (!temp.b.idsts) { /* considered host mode value is 0 */ + /* Disable Interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, gpwrdn.d32, 0); + + /* + * Initialize the Core for Host mode. + */ + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + } else { + gotgctl_data_t gotgctl; + /* Mask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, gpwrdn.d32, 0); + + /* Disable Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, gpwrdn.d32, 0); + + /* + * Initialize the Core for Device mode. + */ + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + + gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + if (!gotgctl.b.bsesvld) { + dwc_otg_initiate_srp(core_if); + } + } + } + if (core_if->power_down == 2) { + if (gpwrdn.b.bsessvld) { + /* Mask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Disable Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* + * Initialize the Core for Device mode. + */ + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } + } + } +exit: + /* Clear interrupt */ + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + adpctl.b.adp_prb_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * This function hadles ADP Sense Interrupt + */ +static int32_t dwc_otg_adp_handle_sns_intr(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + /* Stop ADP Sense timer */ + DWC_TIMER_CANCEL(core_if->adp.sense_timer); + + /* Restart ADP Sense timer */ + dwc_otg_adp_sense_timer_start(core_if); + + /* Clear interrupt */ + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + adpctl.b.adp_sns_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * This function handles ADP Probe Interrupts + */ +static int32_t dwc_otg_adp_handle_prb_tmout_intr(dwc_otg_core_if_t * core_if, + uint32_t val) +{ + adpctl_data_t adpctl = {.d32 = 0 }; + adpctl.d32 = val; + set_timer_value(core_if, adpctl.b.rtim); + + /* Clear interrupt */ + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + adpctl.b.adp_tmout_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * ADP Interrupt handler. + * + */ +int32_t dwc_otg_adp_handle_intr(dwc_otg_core_if_t * core_if) +{ + int retval = 0; + adpctl_data_t adpctl = {.d32 = 0}; + + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + DWC_PRINTF("ADPCTL = %08x\n",adpctl.d32); + + if (adpctl.b.adp_sns_int & adpctl.b.adp_sns_int_msk) { + DWC_PRINTF("ADP Sense interrupt\n"); + retval |= dwc_otg_adp_handle_sns_intr(core_if); + } + if (adpctl.b.adp_tmout_int & adpctl.b.adp_tmout_int_msk) { + DWC_PRINTF("ADP timeout interrupt\n"); + retval |= dwc_otg_adp_handle_prb_tmout_intr(core_if, adpctl.d32); + } + if (adpctl.b.adp_prb_int & adpctl.b.adp_prb_int_msk) { + DWC_PRINTF("ADP Probe interrupt\n"); + adpctl.b.adp_prb_int = 1; + retval |= dwc_otg_adp_handle_prb_intr(core_if, adpctl.d32); + } + +// dwc_otg_adp_modify_reg(core_if, adpctl.d32, 0); + //dwc_otg_adp_write_reg(core_if, adpctl.d32); + DWC_PRINTF("RETURN FROM ADP ISR\n"); + + return retval; +} + +/** + * + * @param core_if Programming view of DWC_otg controller. + */ +int32_t dwc_otg_adp_handle_srp_intr(dwc_otg_core_if_t * core_if) +{ + +#ifndef DWC_HOST_ONLY + hprt0_data_t hprt0; + gpwrdn_data_t gpwrdn; + DWC_DEBUGPL(DBG_ANY, "++ Power Down Logic Session Request Interrupt++\n"); + + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + /* check which value is for device mode and which for Host mode */ + if (!gpwrdn.b.idsts) { /* considered host mode value is 0 */ + DWC_PRINTF("SRP: Host mode\n"); + + if (core_if->adp_enable) { + dwc_otg_adp_probe_stop(core_if); + + /* Power on the core */ + if (core_if->power_down == 2) { + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } + + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + } + + /* Turn on the port power bit. */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* Start the Connection timer. So a message can be displayed + * if connect does not occur within 10 seconds. */ + cil_hcd_session_start(core_if); + } else { + DWC_PRINTF("SRP: Device mode %s\n", __FUNCTION__); + if (core_if->adp_enable) { + dwc_otg_adp_probe_stop(core_if); + + /* Power on the core */ + if (core_if->power_down == 2) { + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } + + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 0; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, + gpwrdn.d32); + + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } + } +#endif + return 1; +} diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_adp.h b/drivers/usb/gadget/dwc_otg/dwc_otg_adp.h new file mode 100644 index 00000000..d8c3f851 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_adp.h @@ -0,0 +1,80 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_adp.h $ + * $Revision: #7 $ + * $Date: 2011/10/24 $ + * $Change: 1871159 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#ifndef __DWC_OTG_ADP_H__ +#define __DWC_OTG_ADP_H__ + +/** + * @file + * + * This file contains the Attach Detect Protocol interfaces and defines + * (functions) and structures for Linux. + * + */ + +#define DWC_OTG_ADP_UNATTACHED 0 +#define DWC_OTG_ADP_ATTACHED 1 +#define DWC_OTG_ADP_UNKOWN 2 + +typedef struct dwc_otg_adp { + uint32_t adp_started; + uint32_t initial_probe; + int32_t probe_timer_values[2]; + uint32_t probe_enabled; + uint32_t sense_enabled; + dwc_timer_t *sense_timer; + uint32_t sense_timer_started; + dwc_timer_t *vbuson_timer; + uint32_t vbuson_timer_started; + uint32_t attached; + uint32_t probe_counter; + uint32_t gpwrdn; +} dwc_otg_adp_t; + +/** + * Attach Detect Protocol functions + */ + +extern void dwc_otg_adp_write_reg(dwc_otg_core_if_t * core_if, uint32_t value); +extern uint32_t dwc_otg_adp_read_reg(dwc_otg_core_if_t * core_if); +extern uint32_t dwc_otg_adp_probe_start(dwc_otg_core_if_t * core_if); +extern uint32_t dwc_otg_adp_sense_start(dwc_otg_core_if_t * core_if); +extern uint32_t dwc_otg_adp_probe_stop(dwc_otg_core_if_t * core_if); +extern uint32_t dwc_otg_adp_sense_stop(dwc_otg_core_if_t * core_if); +extern void dwc_otg_adp_start(dwc_otg_core_if_t * core_if, uint8_t is_host); +extern void dwc_otg_adp_init(dwc_otg_core_if_t * core_if); +extern void dwc_otg_adp_remove(dwc_otg_core_if_t * core_if); +extern int32_t dwc_otg_adp_handle_intr(dwc_otg_core_if_t * core_if); +extern int32_t dwc_otg_adp_handle_srp_intr(dwc_otg_core_if_t * core_if); + +#endif //__DWC_OTG_ADP_H__ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_attr.c b/drivers/usb/gadget/dwc_otg/dwc_otg_attr.c new file mode 100644 index 00000000..3b552ffa --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_attr.c @@ -0,0 +1,1156 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_attr.c $ + * $Revision: #34 $ + * $Date: 2008/12/24 $ + * $Change: 1158473 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * + * The diagnostic interface will provide access to the controller for + * bringing up the hardware and testing. The Linux driver attributes + * feature will be used to provide the Linux Diagnostic + * Interface. These attributes are accessed through sysfs. + */ + +/** @page "Linux Module Attributes" + * + * The Linux module attributes feature is used to provide the Linux + * Diagnostic Interface. These attributes are accessed through sysfs. + * The diagnostic interface will provide access to the controller for + * bringing up the hardware and testing. + + The following table shows the attributes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name Description Access
mode Returns the current mode: 0 for device mode, 1 for host mode Read
hnpcapable Gets or sets the "HNP-capable" bit in the Core USB Configuraton Register. + Read returns the current value. Read/Write
srpcapable Gets or sets the "SRP-capable" bit in the Core USB Configuraton Register. + Read returns the current value. Read/Write
hsic_connect Gets or sets the "HSIC-Connect" bit in the GLPMCFG Register. + Read returns the current value. Read/Write
inv_sel_hsic Gets or sets the "Invert Select HSIC" bit in the GLPMFG Register. + Read returns the current value. Read/Write
hnp Initiates the Host Negotiation Protocol. Read returns the status. Read/Write
srp Initiates the Session Request Protocol. Read returns the status. Read/Write
buspower Gets or sets the Power State of the bus (0 - Off or 1 - On) Read/Write
bussuspend Suspends the USB bus. Read/Write
busconnected Gets the connection status of the bus Read
gotgctl Gets or sets the Core Control Status Register. Read/Write
gusbcfg Gets or sets the Core USB Configuration Register Read/Write
grxfsiz Gets or sets the Receive FIFO Size Register Read/Write
gnptxfsiz Gets or sets the non-periodic Transmit Size Register Read/Write
gpvndctl Gets or sets the PHY Vendor Control Register Read/Write
ggpio Gets the value in the lower 16-bits of the General Purpose IO Register + or sets the upper 16 bits. Read/Write
guid Gets or sets the value of the User ID Register Read/Write
gsnpsid Gets the value of the Synopsys ID Regester Read
devspeed Gets or sets the device speed setting in the DCFG register Read/Write
enumspeed Gets the device enumeration Speed. Read
hptxfsiz Gets the value of the Host Periodic Transmit FIFO Read
hprt0 Gets or sets the value in the Host Port Control and Status Register Read/Write
regoffset Sets the register offset for the next Register Access Read/Write
regvalue Gets or sets the value of the register at the offset in the regoffset attribute. Read/Write
remote_wakeup On read, shows the status of Remote Wakeup. On write, initiates a remote + wakeup of the host. When bit 0 is 1 and Remote Wakeup is enabled, the Remote + Wakeup signalling bit in the Device Control Register is set for 1 + milli-second. Read/Write
regdump Dumps the contents of core registers. Read
spramdump Dumps the contents of core registers. Read
hcddump Dumps the current HCD state. Read
hcd_frrem Shows the average value of the Frame Remaining + field in the Host Frame Number/Frame Remaining register when an SOF interrupt + occurs. This can be used to determine the average interrupt latency. Also + shows the average Frame Remaining value for start_transfer and the "a" and + "b" sample points. The "a" and "b" sample points may be used during debugging + bto determine how long it takes to execute a section of the HCD code. Read
rd_reg_test Displays the time required to read the GNPTXFSIZ register many times + (the output shows the number of times the register is read). + Read
wr_reg_test Displays the time required to write the GNPTXFSIZ register many times + (the output shows the number of times the register is written). + Read
lpm_response Gets or sets lpm_response mode. Applicable only in device mode. + Write
sleep_local_dev Generetates sleep signaling. Applicable only in host mode. + Write
sleep_status Shows sleep status of device. + Read
+ + Example usage: + To get the current mode: + cat /sys/devices/lm0/mode + + To power down the USB: + echo 0 > /sys/devices/lm0/buspower + */ +#include +#include +#include +#include +#include +#include +#include +#include /* permission constants */ +#include +#include +#include +#include +#include +#ifdef CONFIG_OF +#include +#include +#include +#endif + +#include "dwc_otg_os_dep.h" +#include "dwc_os.h" +#include "dwc_otg_attr.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_core_if.h" +#include "dwc_otg_pcd_if.h" +#include "dwc_otg_hcd_if.h" +#include "usb_hw.h" + + +//#include +/* + * MACROs for defining sysfs attribute + */ + +#define DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \ +{ \ + struct platform_device *lm_dev = to_platform_device(_dev);\ + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev);\ + uint32_t val; \ + val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \ + return sprintf (buf, "%s = 0x%x\n", _string_, val); \ +} +#define DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct platform_device *lm_dev = to_platform_device(_dev);\ + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev);\ + uint32_t set = simple_strtoul(buf, NULL, 16); \ + dwc_otg_set_##_otg_attr_name_(otg_dev->core_if, set);\ + return count; \ +} + + +/* + * MACROs for defining sysfs attribute for 32-bit registers + */ +#define DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \ +{ \ + struct platform_device *lm_dev = to_platform_device(_dev);\ + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev);\ + uint32_t val; \ + val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \ + return sprintf (buf, "%s = 0x%08x\n", _string_, val); \ +} +#define DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct platform_device *lm_dev = to_platform_device(_dev);\ + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev);\ + uint32_t val = simple_strtoul(buf, NULL, 16); \ + dwc_otg_set_##_otg_attr_name_ (otg_dev->core_if, val); \ + return count; \ +} + +#define DWC_OTG_DEVICE_ATTR_BITFIELD_RW(_otg_attr_name_,_string_) \ +DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \ +DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \ +DEVICE_ATTR(_otg_attr_name_,0644,_otg_attr_name_##_show,_otg_attr_name_##_store); + +#define DWC_OTG_DEVICE_ATTR_BITFIELD_RO(_otg_attr_name_,_string_) \ +DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \ +DEVICE_ATTR(_otg_attr_name_,0444,_otg_attr_name_##_show,NULL); + +#define DWC_OTG_DEVICE_ATTR_REG32_RW(_otg_attr_name_,_addr_,_string_) \ +DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \ +DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \ +DEVICE_ATTR(_otg_attr_name_,0644,_otg_attr_name_##_show,_otg_attr_name_##_store); + +#define DWC_OTG_DEVICE_ATTR_REG32_RO(_otg_attr_name_,_addr_,_string_) \ +DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \ +DEVICE_ATTR(_otg_attr_name_,0444,_otg_attr_name_##_show,NULL); + +/** @name Functions for Show/Store of Attributes */ +/**@{*/ + +/** + * Show the register offset of the Register Access. + */ +static ssize_t regoffset_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + return snprintf(buf, sizeof("0xFFFFFFFF\n") + 1, "0x%08x\n", + otg_dev->os_dep.reg_offset); +} + +/** + * Set the register offset for the next Register Access Read/Write + */ +static ssize_t regoffset_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + uint32_t offset = simple_strtoul(buf, NULL, 16); + if (offset < SZ_256K) { + otg_dev->os_dep.reg_offset = offset; + } else { + dev_err(_dev, "invalid offset\n"); + } + + return count; +} + +DEVICE_ATTR(regoffset, S_IRUGO | S_IWUSR, regoffset_show, regoffset_store); + +/** + * Show the value of the register at the offset in the reg_offset + * attribute. + */ +static ssize_t regvalue_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + uint32_t val; + volatile uint32_t *addr; + + if (otg_dev->os_dep.reg_offset != 0xFFFFFFFF && 0 != otg_dev->os_dep.base) { + /* Calculate the address */ + addr = (uint32_t *) (otg_dev->os_dep.reg_offset + + (uint8_t *) otg_dev->os_dep.base); + val = DWC_READ_REG32(addr); + return snprintf(buf, + sizeof("Reg@0xFFFFFFFF = 0xFFFFFFFF\n") + 1, + "Reg@0x%06x = 0x%08x\n", otg_dev->os_dep.reg_offset, + val); + } else { + dev_err(_dev, "Invalid offset (0x%0x)\n", otg_dev->os_dep.reg_offset); + return sprintf(buf, "invalid offset\n"); + } +} + +/** + * Store the value in the register at the offset in the reg_offset + * attribute. + * + */ +static ssize_t regvalue_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + volatile uint32_t *addr; + uint32_t val = simple_strtoul(buf, NULL, 16); + //dev_dbg(_dev, "Offset=0x%08x Val=0x%08x\n", otg_dev->os_dep.reg_offset, val); + if (otg_dev->os_dep.reg_offset != 0xFFFFFFFF && 0 != otg_dev->os_dep.base) { + /* Calculate the address */ + addr = (uint32_t *) (otg_dev->os_dep.reg_offset + + (uint8_t *) otg_dev->os_dep.base); + DWC_WRITE_REG32(addr, val); + } else { + dev_err(_dev, "Invalid Register Offset (0x%08x)\n", + otg_dev->os_dep.reg_offset); + } + return count; +} + +DEVICE_ATTR(regvalue, S_IRUGO | S_IWUSR, regvalue_show, regvalue_store); + +/* + * Attributes + */ +DWC_OTG_DEVICE_ATTR_BITFIELD_RO(mode, "Mode"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(hnpcapable, "HNPCapable"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(srpcapable, "Mode"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(hsic_connect, "HSIC Connect"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(inv_sel_hsic, "Invert Select HSIC"); + +//DWC_OTG_DEVICE_ATTR_BITFIELD_RW(buspower,&(otg_dev->core_if->core_global_regs->gotgctl),(1<<8),8,"Mode"); +//DWC_OTG_DEVICE_ATTR_BITFIELD_RW(bussuspend,&(otg_dev->core_if->core_global_regs->gotgctl),(1<<8),8,"Mode"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RO(busconnected, "Bus Connected"); + +DWC_OTG_DEVICE_ATTR_REG32_RW(gotgctl, 0, "GOTGCTL"); +DWC_OTG_DEVICE_ATTR_REG32_RW(gusbcfg, + &(otg_dev->core_if->core_global_regs->gusbcfg), + "GUSBCFG"); +DWC_OTG_DEVICE_ATTR_REG32_RW(grxfsiz, + &(otg_dev->core_if->core_global_regs->grxfsiz), + "GRXFSIZ"); +DWC_OTG_DEVICE_ATTR_REG32_RW(gnptxfsiz, + &(otg_dev->core_if->core_global_regs->gnptxfsiz), + "GNPTXFSIZ"); +DWC_OTG_DEVICE_ATTR_REG32_RW(gpvndctl, + &(otg_dev->core_if->core_global_regs->gpvndctl), + "GPVNDCTL"); +DWC_OTG_DEVICE_ATTR_REG32_RW(ggpio, + &(otg_dev->core_if->core_global_regs->ggpio), + "GGPIO"); +DWC_OTG_DEVICE_ATTR_REG32_RW(guid, &(otg_dev->core_if->core_global_regs->guid), + "GUID"); +DWC_OTG_DEVICE_ATTR_REG32_RO(gsnpsid, + &(otg_dev->core_if->core_global_regs->gsnpsid), + "GSNPSID"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(devspeed, "Device Speed"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RO(enumspeed, "Device Enumeration Speed"); + +DWC_OTG_DEVICE_ATTR_REG32_RO(hptxfsiz, + &(otg_dev->core_if->core_global_regs->hptxfsiz), + "HPTXFSIZ"); +DWC_OTG_DEVICE_ATTR_REG32_RW(hprt0, otg_dev->core_if->host_if->hprt0, "HPRT0"); + +/** + * @todo Add code to initiate the HNP. + */ +/** + * Show the HNP status bit + */ +static ssize_t hnp_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + return sprintf(buf, "HstNegScs = 0x%x\n", + dwc_otg_get_hnpstatus(otg_dev->core_if)); +} + +/** + * Set the HNP Request bit + */ +static ssize_t hnp_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + uint32_t in = simple_strtoul(buf, NULL, 16); + dwc_otg_set_hnpreq(otg_dev->core_if, in); + return count; +} + +DEVICE_ATTR(hnp, 0644, hnp_show, hnp_store); + +/** + * @todo Add code to initiate the SRP. + */ +/** + * Show the SRP status bit + */ +static ssize_t srp_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_HOST_ONLY + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + return sprintf(buf, "SesReqScs = 0x%x\n", + dwc_otg_get_srpstatus(otg_dev->core_if)); +#else + return sprintf(buf, "Host Only Mode!\n"); +#endif +} + +/** + * Set the SRP Request bit + */ +static ssize_t srp_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifndef DWC_HOST_ONLY + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + dwc_otg_pcd_initiate_srp(otg_dev->pcd); +#endif + return count; +} + +DEVICE_ATTR(srp, 0644, srp_show, srp_store); + +/** + * @todo Need to do more for power on/off? + */ +/** + * Show the Bus Power status + */ +static ssize_t buspower_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + return sprintf(buf, "Bus Power = 0x%x\n", + dwc_otg_get_prtpower(otg_dev->core_if)); +} + +/** + * Set the Bus Power status + */ +static ssize_t buspower_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + uint32_t on = simple_strtoul(buf, NULL, 16); + dwc_otg_set_prtpower(otg_dev->core_if, on); + return count; +} + +DEVICE_ATTR(buspower, 0644, buspower_show, buspower_store); + +/** + * @todo Need to do more for suspend? + */ +/** + * Show the Bus Suspend status + */ +static ssize_t bussuspend_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + return sprintf(buf, "Bus Suspend = 0x%x\n", + dwc_otg_get_prtsuspend(otg_dev->core_if)); +} + +/** + * Set the Bus Suspend status + */ +static ssize_t bussuspend_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + uint32_t in = simple_strtoul(buf, NULL, 16); + dwc_otg_set_prtsuspend(otg_dev->core_if, in); + return count; +} + +DEVICE_ATTR(bussuspend, 0644, bussuspend_show, bussuspend_store); + +/** + * Show the status of Remote Wakeup. + */ +static ssize_t remote_wakeup_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_HOST_ONLY + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + return sprintf(buf, + "Remote Wakeup Sig = %d Enabled = %d LPM Remote Wakeup = %d\n", + dwc_otg_get_remotewakesig(otg_dev->core_if), + dwc_otg_pcd_get_rmwkup_enable(otg_dev->pcd), + dwc_otg_get_lpm_remotewakeenabled(otg_dev->core_if)); +#else + return sprintf(buf, "Host Only Mode!\n"); +#endif /* DWC_HOST_ONLY */ +} + +/** + * Initiate a remote wakeup of the host. The Device control register + * Remote Wakeup Signal bit is written if the PCD Remote wakeup enable + * flag is set. + * + */ +static ssize_t remote_wakeup_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifndef DWC_HOST_ONLY + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + uint32_t val = simple_strtoul(buf, NULL, 16); + + if (val & 1) { + dwc_otg_pcd_remote_wakeup(otg_dev->pcd, 1); + } else { + dwc_otg_pcd_remote_wakeup(otg_dev->pcd, 0); + } +#endif /* DWC_HOST_ONLY */ + return count; +} + +DEVICE_ATTR(remote_wakeup, S_IRUGO | S_IWUSR, remote_wakeup_show, + remote_wakeup_store); + +/** + * Dump global registers and either host or device registers (depending on the + * current mode of the core). + */ +static ssize_t regdump_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + dwc_otg_dump_global_registers(otg_dev->core_if); + if (dwc_otg_is_host_mode(otg_dev->core_if)) { + dwc_otg_dump_host_registers(otg_dev->core_if); + } else { + dwc_otg_dump_dev_registers(otg_dev->core_if); + + } + return sprintf(buf, "Register Dump\n"); +} + +DEVICE_ATTR(regdump, S_IRUGO, regdump_show, 0); + +/** + * Dump global registers and either host or device registers (depending on the + * current mode of the core). + */ +static ssize_t spramdump_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + struct resource *res_ptr = NULL; + +#ifndef CONFIG_OF + res_ptr = platform_get_resource(lm_dev, IORESOURCE_MEM, 0); +#else + int ret; + struct device_node *np = lm_dev->dev.of_node; + struct resource res; + + ret = of_address_to_resource(np, 0, &res); + if (ret < 0) { + dev_err(&lm_dev->dev, "no reg of property specified\n"); + goto failed; + } + res_ptr = &res; +#endif + + if (res_ptr && (SPRAM_END_ADDR < res_ptr->end - res_ptr->start) + && (SPRAM_START_ADDR < res_ptr->end - res_ptr->start)) { + dwc_otg_dump_spram(otg_dev->core_if); + + return sprintf(buf, "SPRAM Dump\n"); + } +failed: + return sprintf(buf, "SPRAM Dump failed\n"); +} + +DEVICE_ATTR(spramdump, S_IRUGO, spramdump_show, 0); + +/** + * Dump the current hcd state. + */ +static ssize_t hcddump_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_DEVICE_ONLY + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + dwc_otg_hcd_dump_state(otg_dev->hcd); +#endif /* DWC_DEVICE_ONLY */ + return sprintf(buf, "HCD Dump\n"); +} + +DEVICE_ATTR(hcddump, S_IRUGO, hcddump_show, 0); + +/** + * Dump the average frame remaining at SOF. This can be used to + * determine average interrupt latency. Frame remaining is also shown for + * start transfer and two additional sample points. + */ +static ssize_t hcd_frrem_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_DEVICE_ONLY + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + + dwc_otg_hcd_dump_frrem(otg_dev->hcd); +#endif /* DWC_DEVICE_ONLY */ + return sprintf(buf, "HCD Dump Frame Remaining\n"); +} + +DEVICE_ATTR(hcd_frrem, S_IRUGO, hcd_frrem_show, 0); + +/** + * Displays the time required to read the GNPTXFSIZ register many times (the + * output shows the number of times the register is read). + */ +#define RW_REG_COUNT 10000000 +#define MSEC_PER_JIFFIE 1000/HZ +static ssize_t rd_reg_test_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + + int i; + int time; + int start_jiffies; + + DWC_PRINTF("HZ %d, MSEC_PER_JIFFIE %d, loops_per_jiffy %lu\n", + HZ, MSEC_PER_JIFFIE, loops_per_jiffy); + start_jiffies = jiffies; + for (i = 0; i < RW_REG_COUNT; i++) { + dwc_otg_get_gnptxfsiz(otg_dev->core_if); + } + time = jiffies - start_jiffies; + return sprintf(buf, + "Time to read GNPTXFSIZ reg %d times: %d msecs (%d jiffies)\n", + RW_REG_COUNT, time * MSEC_PER_JIFFIE, time); +} + +DEVICE_ATTR(rd_reg_test, S_IRUGO, rd_reg_test_show, 0); + +/** + * Displays the time required to write the GNPTXFSIZ register many times (the + * output shows the number of times the register is written). + */ +static ssize_t wr_reg_test_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + uint32_t reg_val; + int i; + int time; + int start_jiffies; + + DWC_PRINTF("HZ %d, MSEC_PER_JIFFIE %d, loops_per_jiffy %lu\n", + HZ, MSEC_PER_JIFFIE, loops_per_jiffy); + reg_val = dwc_otg_get_gnptxfsiz(otg_dev->core_if); + start_jiffies = jiffies; + for (i = 0; i < RW_REG_COUNT; i++) { + dwc_otg_set_gnptxfsiz(otg_dev->core_if, reg_val); + } + time = jiffies - start_jiffies; + return sprintf(buf, + "Time to write GNPTXFSIZ reg %d times: %d msecs (%d jiffies)\n", + RW_REG_COUNT, time * MSEC_PER_JIFFIE, time); +} + +DEVICE_ATTR(wr_reg_test, S_IRUGO, wr_reg_test_show, 0); + +#ifdef CONFIG_USB_DWC_OTG_LPM + +/** +* Show the lpm_response attribute. +*/ +static ssize_t lpmresp_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + if (!dwc_otg_get_param_lpm_enable(otg_dev->core_if)) + return sprintf(buf, "** LPM is DISABLED **\n"); + + if (!dwc_otg_is_device_mode(otg_dev->core_if)) { + return sprintf(buf, "** Current mode is not device mode\n"); + } + return sprintf(buf, "lpm_response = %d\n", + dwc_otg_get_lpmresponse(otg_dev->core_if)); +} + +/** +* Store the lpm_response attribute. +*/ +static ssize_t lpmresp_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + + uint32_t val = simple_strtoul(buf, NULL, 16); + + if (!dwc_otg_get_param_lpm_enable(otg_dev->core_if)) { + return 0; + } + + if (!dwc_otg_is_device_mode(otg_dev->core_if)) { + return 0; + } + + dwc_otg_set_lpmresponse(otg_dev->core_if, val); + return count; +} + +DEVICE_ATTR(lpm_response, S_IRUGO | S_IWUSR, lpmresp_show, lpmresp_store); + +/** +* Show the sleep_status attribute. +*/ +static ssize_t sleepstatus_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + + + return sprintf(buf, "Sleep Status = %d\n", + dwc_otg_get_lpm_portsleepstatus(otg_dev->core_if)); +} + +/** + * Store the sleep_status attribure. + */ +static ssize_t sleepstatus_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *lm_dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(lm_dev); + + dwc_otg_core_if_t *core_if = otg_dev->core_if; + + if (dwc_otg_get_lpm_portsleepstatus(otg_dev->core_if)) { + if (dwc_otg_is_host_mode(core_if)) { + + DWC_PRINTF("Host initiated resume\n"); + dwc_otg_set_prtresume(otg_dev->core_if, 1); + } + } + + return count; +} + +DEVICE_ATTR(sleep_status, S_IRUGO | S_IWUSR, sleepstatus_show, + sleepstatus_store); + +#endif /* CONFIG_USB_DWC_OTG_LPM_ENABLE */ +#ifndef DWC_DEVICE_ONLY +static ssize_t hostenable_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *dev = to_platform_device(_dev); + struct sprd_usb_platform_data *pdata = dev->dev.platform_data; + dwc_otg_device_t *otg_dev = platform_get_drvdata(dev); + + if (pdata->gpio_otgdet == 0xFFFFFFFF) { + DWC_PRINTF("Don't support host mode!!!\n"); + return sprintf(buf, "Not support"); + } + + return sprintf(buf, "%s\n", + ((otg_dev->host_disabled&0x01) ? "disabled" : "enabled")); +} + +/** + * Store the sleep_status attribure. + */ +static ssize_t hostenable_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(dev); + struct sprd_usb_platform_data *pdata = dev->dev.platform_data; + int otg_cable_irq; + + if (pdata->gpio_otgdet == 0xFFFFFFFF) { + DWC_PRINTF("Don't support host mode!!!\n"); + return count; + } + otg_cable_irq = usb_alloc_id_irq(pdata->gpio_otgdet); + + if (strncmp(buf, "disable", 7) == 0) { + otg_dev->host_disabled |= 1; + disable_irq(otg_cable_irq); + DWC_PRINTF("gpio_otgdet = %d irq = %d disabled\n", + pdata->gpio_otgdet, otg_cable_irq); + } else if (strncmp(buf, "enable", 6) == 0) { + otg_dev->host_disabled &= ~(0x01); + enable_irq(otg_cable_irq); + DWC_PRINTF("gpio_otgdet = %d irq = %d enabled\n", + pdata->gpio_otgdet, otg_cable_irq); + } else { + DWC_PRINTF("gpio_otgdet = %d irq = %d case3!!!!!!!!!!!!!!\n", + pdata->gpio_otgdet, otg_cable_irq, buf); + return 0; + } + + return count; +} + +DEVICE_ATTR(host_enable, S_IRUGO | S_IWUSR, hostenable_show, + hostenable_store); + +static ssize_t otgstatus_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(dev); + struct sprd_usb_platform_data *pdata = dev->dev.platform_data; + int otg_status; + + if (pdata->gpio_otgdet == 0xFFFFFFFF) { + DWC_PRINTF("Don't support host mode!!!\n"); + return sprintf(buf, "Not support"); + } + otg_status = usb_get_id_state(); + return sprintf(buf, "%s\n", (otg_status ? "hign" : "low")); +} + +/** + * Store the sleep_status attribure. + */ + +DEVICE_ATTR(otg_status, S_IRUGO, otgstatus_show, NULL); + + +/** + * Store the sleep_status attribure. + */ +static ssize_t hostvbus_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *dev = to_platform_device(_dev); + dwc_otg_device_t *otg_dev = platform_get_drvdata(dev); + struct sprd_usb_platform_data *pdata = dev->dev.platform_data; + int otg_cable_irq; + + + if (strncmp(buf, "off", 3) == 0) { + otg_dev->host_disabled &= ~(0x02); + charge_pump_set(pdata->gpio_boost, 0); + DWC_PRINTF("gpio_boost = %d off\n", pdata->gpio_boost); + } else if (strncmp(buf, "on", 2) == 0) { + otg_dev->host_disabled |= 0x02; + charge_pump_set(pdata->gpio_boost, 1); + DWC_PRINTF("gpio_boost = %d on\n", pdata->gpio_boost); + } + + return count; +} + +DEVICE_ATTR(vbus_control, S_IWUSR, NULL, hostvbus_store); +#endif + +/**@}*/ + +/** + * Create the device files + */ +void dwc_otg_attr_create( + struct platform_device *dev + ) + +{ + int error; + + error = device_create_file(&dev->dev, &dev_attr_regoffset); + error = device_create_file(&dev->dev, &dev_attr_regvalue); + error = device_create_file(&dev->dev, &dev_attr_mode); + error = device_create_file(&dev->dev, &dev_attr_hnpcapable); + error = device_create_file(&dev->dev, &dev_attr_srpcapable); + error = device_create_file(&dev->dev, &dev_attr_hsic_connect); + error = device_create_file(&dev->dev, &dev_attr_inv_sel_hsic); + error = device_create_file(&dev->dev, &dev_attr_hnp); + error = device_create_file(&dev->dev, &dev_attr_srp); + error = device_create_file(&dev->dev, &dev_attr_buspower); + error = device_create_file(&dev->dev, &dev_attr_bussuspend); + error = device_create_file(&dev->dev, &dev_attr_busconnected); + error = device_create_file(&dev->dev, &dev_attr_gotgctl); + error = device_create_file(&dev->dev, &dev_attr_gusbcfg); + error = device_create_file(&dev->dev, &dev_attr_grxfsiz); + error = device_create_file(&dev->dev, &dev_attr_gnptxfsiz); + error = device_create_file(&dev->dev, &dev_attr_gpvndctl); + error = device_create_file(&dev->dev, &dev_attr_ggpio); + error = device_create_file(&dev->dev, &dev_attr_guid); + error = device_create_file(&dev->dev, &dev_attr_gsnpsid); + error = device_create_file(&dev->dev, &dev_attr_devspeed); + error = device_create_file(&dev->dev, &dev_attr_enumspeed); + error = device_create_file(&dev->dev, &dev_attr_hptxfsiz); + error = device_create_file(&dev->dev, &dev_attr_hprt0); + error = device_create_file(&dev->dev, &dev_attr_remote_wakeup); + error = device_create_file(&dev->dev, &dev_attr_regdump); + error = device_create_file(&dev->dev, &dev_attr_spramdump); + error = device_create_file(&dev->dev, &dev_attr_hcddump); + error = device_create_file(&dev->dev, &dev_attr_hcd_frrem); + error = device_create_file(&dev->dev, &dev_attr_rd_reg_test); + error = device_create_file(&dev->dev, &dev_attr_wr_reg_test); +#ifdef CONFIG_USB_DWC_OTG_LPM + error = device_create_file(&dev->dev, &dev_attr_lpm_response); + error = device_create_file(&dev->dev, &dev_attr_sleep_status); +#endif +#ifndef DWC_DEVICE_ONLY + error = device_create_file(&dev->dev, &dev_attr_host_enable); + error = device_create_file(&dev->dev, &dev_attr_otg_status); + error = device_create_file(&dev->dev, &dev_attr_vbus_control); +#endif + +} + +/** + * Remove the device files + */ +void dwc_otg_attr_remove( + struct platform_device *dev + ) + +{ + device_remove_file(&dev->dev, &dev_attr_regoffset); + device_remove_file(&dev->dev, &dev_attr_regvalue); + device_remove_file(&dev->dev, &dev_attr_mode); + device_remove_file(&dev->dev, &dev_attr_hnpcapable); + device_remove_file(&dev->dev, &dev_attr_srpcapable); + device_remove_file(&dev->dev, &dev_attr_hsic_connect); + device_remove_file(&dev->dev, &dev_attr_inv_sel_hsic); + device_remove_file(&dev->dev, &dev_attr_hnp); + device_remove_file(&dev->dev, &dev_attr_srp); + device_remove_file(&dev->dev, &dev_attr_buspower); + device_remove_file(&dev->dev, &dev_attr_bussuspend); + device_remove_file(&dev->dev, &dev_attr_busconnected); + device_remove_file(&dev->dev, &dev_attr_gotgctl); + device_remove_file(&dev->dev, &dev_attr_gusbcfg); + device_remove_file(&dev->dev, &dev_attr_grxfsiz); + device_remove_file(&dev->dev, &dev_attr_gnptxfsiz); + device_remove_file(&dev->dev, &dev_attr_gpvndctl); + device_remove_file(&dev->dev, &dev_attr_ggpio); + device_remove_file(&dev->dev, &dev_attr_guid); + device_remove_file(&dev->dev, &dev_attr_gsnpsid); + device_remove_file(&dev->dev, &dev_attr_devspeed); + device_remove_file(&dev->dev, &dev_attr_enumspeed); + device_remove_file(&dev->dev, &dev_attr_hptxfsiz); + device_remove_file(&dev->dev, &dev_attr_hprt0); + device_remove_file(&dev->dev, &dev_attr_remote_wakeup); + device_remove_file(&dev->dev, &dev_attr_regdump); + device_remove_file(&dev->dev, &dev_attr_spramdump); + device_remove_file(&dev->dev, &dev_attr_hcddump); + device_remove_file(&dev->dev, &dev_attr_hcd_frrem); + device_remove_file(&dev->dev, &dev_attr_rd_reg_test); + device_remove_file(&dev->dev, &dev_attr_wr_reg_test); +#ifdef CONFIG_USB_DWC_OTG_LPM + device_remove_file(&dev->dev, &dev_attr_lpm_response); + device_remove_file(&dev->dev, &dev_attr_sleep_status); +#endif +#ifndef DWC_DEVICE_ONLY + device_remove_file(&dev->dev, &dev_attr_host_enable); + device_remove_file(&dev->dev, &dev_attr_otg_status); + device_remove_file(&dev->dev, &dev_attr_vbus_control); +#endif +} diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_attr.h b/drivers/usb/gadget/dwc_otg/dwc_otg_attr.h new file mode 100644 index 00000000..2fa40189 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_attr.h @@ -0,0 +1,76 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_attr.h $ + * $Revision: #10 $ + * $Date: 2008/12/24 $ + * $Change: 1158473 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#if !defined(__DWC_OTG_ATTR_H__) +#define __DWC_OTG_ATTR_H__ + +/** @file + * This file contains the interface to the Linux device attributes. + */ +extern struct device_attribute dev_attr_regoffset; +extern struct device_attribute dev_attr_regvalue; + +extern struct device_attribute dev_attr_mode; +extern struct device_attribute dev_attr_hnpcapable; +extern struct device_attribute dev_attr_srpcapable; +extern struct device_attribute dev_attr_hnp; +extern struct device_attribute dev_attr_srp; +extern struct device_attribute dev_attr_buspower; +extern struct device_attribute dev_attr_bussuspend; +extern struct device_attribute dev_attr_busconnected; +extern struct device_attribute dev_attr_gotgctl; +extern struct device_attribute dev_attr_gusbcfg; +extern struct device_attribute dev_attr_grxfsiz; +extern struct device_attribute dev_attr_gnptxfsiz; +extern struct device_attribute dev_attr_gpvndctl; +extern struct device_attribute dev_attr_ggpio; +extern struct device_attribute dev_attr_guid; +extern struct device_attribute dev_attr_gsnpsid; +extern struct device_attribute dev_attr_devspeed; +extern struct device_attribute dev_attr_enumspeed; +extern struct device_attribute dev_attr_hptxfsiz; +extern struct device_attribute dev_attr_hprt0; +#ifdef CONFIG_USB_DWC_OTG_LPM +extern struct device_attribute dev_attr_lpm_response; +extern struct device_attribute dev_attr_sleep_local_dev; +extern struct device_attribute devi_attr_sleep_status; +#endif + +void dwc_otg_attr_create( + struct platform_device *dev + ); + +void dwc_otg_attr_remove( + struct platform_device *dev + ); +#endif diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_cil.c b/drivers/usb/gadget/dwc_otg/dwc_otg_cil.c new file mode 100644 index 00000000..532d25bb --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_cil.c @@ -0,0 +1,7101 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil.c $ + * $Revision: #191 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * + * The Core Interface Layer provides basic services for accessing and + * managing the DWC_otg hardware. These services are used by both the + * Host Controller Driver and the Peripheral Controller Driver. + * + * The CIL manages the memory map for the core so that the HCD and PCD + * don't have to do this separately. It also handles basic tasks like + * reading/writing the registers and data FIFOs in the controller. + * Some of the data access functions provide encapsulation of several + * operations required to perform a task, such as writing multiple + * registers to start a transfer. Finally, the CIL performs basic + * services that are not specific to either the host or device modes + * of operation. These services include management of the OTG Host + * Negotiation Protocol (HNP) and Session Request Protocol (SRP). A + * Diagnostic API is also provided to allow testing of the controller + * hardware. + * + * The Core Interface Layer has the following requirements: + * - Provides basic controller operations. + * - Minimal use of OS services. + * - The OS services used will be abstracted by using inline functions + * or macros. + * + */ +#include //for debug +#include "dwc_os.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_cil.h" + +extern int in_calibration(void); +extern int in_autotest(void); +extern void usb_phy_ahb_rst(void); +static int dwc_otg_setup_params(dwc_otg_core_if_t * core_if); + +/** + * This function is called to initialize the DWC_otg CSR data + * structures. The register addresses in the device and host + * structures are initialized from the base address supplied by the + * caller. The calling function must make the OS calls to get the + * base address of the DWC_otg controller registers. The core_params + * argument holds the parameters that specify how the core should be + * configured. + * + * @param reg_base_addr Base address of DWC_otg core registers + * + */ +dwc_otg_core_if_t *dwc_otg_cil_init(const uint32_t * reg_base_addr) +{ + dwc_otg_core_if_t *core_if = 0; + dwc_otg_dev_if_t *dev_if = 0; + dwc_otg_host_if_t *host_if = 0; + uint8_t *reg_base = (uint8_t *) reg_base_addr; + int i = 0; + + DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", __func__, reg_base_addr); + + core_if = DWC_ALLOC(sizeof(dwc_otg_core_if_t)); + + if (core_if == NULL) { + DWC_DEBUGPL(DBG_CIL, + "Allocation of dwc_otg_core_if_t failed\n"); + return 0; + } + core_if->core_global_regs = (dwc_otg_core_global_regs_t *) reg_base; + + /* + * Allocate the Device Mode structures. + */ + dev_if = DWC_ALLOC(sizeof(dwc_otg_dev_if_t)); + + if (dev_if == NULL) { + DWC_DEBUGPL(DBG_CIL, "Allocation of dwc_otg_dev_if_t failed\n"); + DWC_FREE(core_if); + return 0; + } + + dev_if->dev_global_regs = + (dwc_otg_device_global_regs_t *) (reg_base + + DWC_DEV_GLOBAL_REG_OFFSET); + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + dev_if->in_ep_regs[i] = (dwc_otg_dev_in_ep_regs_t *) + (reg_base + DWC_DEV_IN_EP_REG_OFFSET + + (i * DWC_EP_REG_OFFSET)); + + dev_if->out_ep_regs[i] = (dwc_otg_dev_out_ep_regs_t *) + (reg_base + DWC_DEV_OUT_EP_REG_OFFSET + + (i * DWC_EP_REG_OFFSET)); + DWC_DEBUGPL(DBG_CILV, "in_ep_regs[%d]->diepctl=%p\n", + i, &dev_if->in_ep_regs[i]->diepctl); + DWC_DEBUGPL(DBG_CILV, "out_ep_regs[%d]->doepctl=%p\n", + i, &dev_if->out_ep_regs[i]->doepctl); + } + + dev_if->speed = 0; // unknown + + core_if->dev_if = dev_if; + + /* + * Allocate the Host Mode structures. + */ + host_if = DWC_ALLOC(sizeof(dwc_otg_host_if_t)); + + if (host_if == NULL) { + DWC_DEBUGPL(DBG_CIL, + "Allocation of dwc_otg_host_if_t failed\n"); + DWC_FREE(dev_if); + DWC_FREE(core_if); + return 0; + } + + host_if->host_global_regs = (dwc_otg_host_global_regs_t *) + (reg_base + DWC_OTG_HOST_GLOBAL_REG_OFFSET); + + host_if->hprt0 = + (uint32_t *) (reg_base + DWC_OTG_HOST_PORT_REGS_OFFSET); + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + host_if->hc_regs[i] = (dwc_otg_hc_regs_t *) + (reg_base + DWC_OTG_HOST_CHAN_REGS_OFFSET + + (i * DWC_OTG_CHAN_REGS_OFFSET)); + DWC_DEBUGPL(DBG_CILV, "hc_reg[%d]->hcchar=%p\n", + i, &host_if->hc_regs[i]->hcchar); + } + + host_if->num_host_channels = MAX_EPS_CHANNELS; + core_if->host_if = host_if; + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + core_if->data_fifo[i] = + (uint32_t *) (reg_base + DWC_OTG_DATA_FIFO_OFFSET + + (i * DWC_OTG_DATA_FIFO_SIZE)); + DWC_DEBUGPL(DBG_CILV, "data_fifo[%d]=0x%08lx\n", + i, (unsigned long)core_if->data_fifo[i]); + } + + core_if->pcgcctl = (uint32_t *) (reg_base + DWC_OTG_PCGCCTL_OFFSET); + + /* Initiate lx_state to L3 disconnected state */ + core_if->lx_state = DWC_OTG_L3; + /* + * Store the contents of the hardware configuration registers here for + * easy access later. + */ + core_if->hwcfg1.d32 = + DWC_READ_REG32(&core_if->core_global_regs->ghwcfg1); + core_if->hwcfg2.d32 = + DWC_READ_REG32(&core_if->core_global_regs->ghwcfg2); + core_if->hwcfg3.d32 = + DWC_READ_REG32(&core_if->core_global_regs->ghwcfg3); + core_if->hwcfg4.d32 = + DWC_READ_REG32(&core_if->core_global_regs->ghwcfg4); + + /* Force host mode to get HPTXFSIZ exact power on value */ + { + gusbcfg_data_t gusbcfg = {.d32 = 0 }; + gusbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + gusbcfg.b.force_host_mode = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32); + dwc_mdelay(100); + core_if->hptxfsiz.d32 = + DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz); + gusbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + gusbcfg.b.force_host_mode = 0; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32); + dwc_mdelay(100); + } + + DWC_DEBUGPL(DBG_CILV, "hwcfg1=%08x\n", core_if->hwcfg1.d32); + DWC_DEBUGPL(DBG_CILV, "hwcfg2=%08x\n", core_if->hwcfg2.d32); + DWC_DEBUGPL(DBG_CILV, "hwcfg3=%08x\n", core_if->hwcfg3.d32); + DWC_DEBUGPL(DBG_CILV, "hwcfg4=%08x\n", core_if->hwcfg4.d32); + + core_if->hcfg.d32 = + DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + core_if->dcfg.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + + DWC_DEBUGPL(DBG_CILV, "hcfg=%08x\n", core_if->hcfg.d32); + DWC_DEBUGPL(DBG_CILV, "dcfg=%08x\n", core_if->dcfg.d32); + + DWC_DEBUGPL(DBG_CILV, "op_mode=%0x\n", core_if->hwcfg2.b.op_mode); + DWC_DEBUGPL(DBG_CILV, "arch=%0x\n", core_if->hwcfg2.b.architecture); + DWC_DEBUGPL(DBG_CILV, "num_dev_ep=%d\n", core_if->hwcfg2.b.num_dev_ep); + DWC_DEBUGPL(DBG_CILV, "num_host_chan=%d\n", + core_if->hwcfg2.b.num_host_chan); + DWC_DEBUGPL(DBG_CILV, "nonperio_tx_q_depth=0x%0x\n", + core_if->hwcfg2.b.nonperio_tx_q_depth); + DWC_DEBUGPL(DBG_CILV, "host_perio_tx_q_depth=0x%0x\n", + core_if->hwcfg2.b.host_perio_tx_q_depth); + DWC_DEBUGPL(DBG_CILV, "dev_token_q_depth=0x%0x\n", + core_if->hwcfg2.b.dev_token_q_depth); + + DWC_DEBUGPL(DBG_CILV, "Total FIFO SZ=%d\n", + core_if->hwcfg3.b.dfifo_depth); + DWC_DEBUGPL(DBG_CILV, "xfer_size_cntr_width=%0x\n", + core_if->hwcfg3.b.xfer_size_cntr_width); + + /* + * Set the SRP sucess bit for FS-I2c + */ + core_if->srp_success = 0; + core_if->srp_timer_started = 0; + + /* + * Create new workqueue and init works + */ + core_if->wq_otg = DWC_WORKQ_ALLOC("dwc_otg"); + if (core_if->wq_otg == 0) { + DWC_WARN("DWC_WORKQ_ALLOC failed\n"); + DWC_FREE(host_if); + DWC_FREE(dev_if); + DWC_FREE(core_if); + return 0; + } + + core_if->snpsid = DWC_READ_REG32(&core_if->core_global_regs->gsnpsid); + + DWC_PRINTF("Core Release: %x.%x%x%x\n", + (core_if->snpsid >> 12 & 0xF), + (core_if->snpsid >> 8 & 0xF), + (core_if->snpsid >> 4 & 0xF), (core_if->snpsid & 0xF)); + + core_if->wkp_timer = DWC_TIMER_ALLOC("Wake Up Timer", + w_wakeup_detected, core_if); + if (core_if->wkp_timer == 0) { + DWC_WARN("DWC_TIMER_ALLOC failed\n"); + DWC_FREE(host_if); + DWC_FREE(dev_if); + DWC_WORKQ_FREE(core_if->wq_otg); + DWC_FREE(core_if); + return 0; + } + + if (dwc_otg_setup_params(core_if)) { + DWC_WARN("Error while setting core params\n"); + } + + core_if->hibernation_suspend = 0; + + /** ADP initialization */ + dwc_otg_adp_init(core_if); + + return core_if; +} + +/** + * This function frees the structures allocated by dwc_otg_cil_init(). + * + * @param core_if The core interface pointer returned from + * dwc_otg_cil_init(). + * + */ +void dwc_otg_cil_remove(dwc_otg_core_if_t * core_if) +{ + dctl_data_t dctl = {.d32 = 0 }; + /* Disable all interrupts */ + DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, 1, 0); + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, 0); + + dctl.b.sftdiscon = 1; + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, + dctl.d32); + } + + if (core_if->wq_otg) { + DWC_WORKQ_WAIT_WORK_DONE(core_if->wq_otg, 500); + DWC_WORKQ_FREE(core_if->wq_otg); + } + if (core_if->dev_if) { + DWC_FREE(core_if->dev_if); + } + if (core_if->host_if) { + DWC_FREE(core_if->host_if); + } + + /** Remove ADP Stuff */ + dwc_otg_adp_remove(core_if); + if (core_if->core_params) { + DWC_FREE(core_if->core_params); + } + if (core_if->wkp_timer) { + DWC_TIMER_FREE(core_if->wkp_timer); + } + if (core_if->srp_timer) { + DWC_TIMER_FREE(core_if->srp_timer); + } + DWC_FREE(core_if); +} + +/** + * This function enables the controller's Global Interrupt in the AHB Config + * register. + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_enable_global_interrupts(dwc_otg_core_if_t * core_if) +{ + gahbcfg_data_t ahbcfg = {.d32 = 0 }; + ahbcfg.b.glblintrmsk = 1; /* Enable interrupts */ + DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, 0, ahbcfg.d32); +} + +/** + * This function disables the controller's Global Interrupt in the AHB Config + * register. + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_disable_global_interrupts(dwc_otg_core_if_t * core_if) +{ + gahbcfg_data_t ahbcfg = {.d32 = 0 }; + ahbcfg.b.glblintrmsk = 1; /* Disable interrupts */ + DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, ahbcfg.d32, 0); +} + +/** + * This function initializes the commmon interrupts, used in both + * device and host modes. + * + * @param core_if Programming view of the DWC_otg controller + * + */ +static void dwc_otg_enable_common_interrupts(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + /* Clear any pending OTG Interrupts */ + DWC_WRITE_REG32(&global_regs->gotgint, 0xFFFFFFFF); + + /* Clear any pending interrupts */ + DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF); + + /* + * Enable the interrupts in the GINTMSK. + */ + intr_mask.b.modemismatch = 1; + intr_mask.b.otgintr = 1; + + if (!core_if->dma_enable) { + intr_mask.b.rxstsqlvl = 1; + } + + intr_mask.b.conidstschng = 1; + intr_mask.b.wkupintr = 1; + intr_mask.b.disconnect = 0; + intr_mask.b.usbsuspend = 1; + intr_mask.b.sessreqintr = 1; +#ifdef CONFIG_USB_DWC_OTG_LPM + if (core_if->core_params->lpm_enable) { + intr_mask.b.lpmtranrcvd = 1; + } +#endif + DWC_WRITE_REG32(&global_regs->gintmsk, intr_mask.d32); +} + +/* + * The restore operation is modified to support Synopsys Emulated Powerdown and + * Hibernation. This function is for exiting from Device mode hibernation by + * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup. + * @param core_if Programming view of DWC_otg controller. + * @param rem_wakeup - indicates whether resume is initiated by Device or Host. + * @param reset - indicates whether resume is initiated by Reset. + */ +int dwc_otg_device_hibernation_restore(dwc_otg_core_if_t * core_if, + int rem_wakeup, int reset) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + + int timeout = 2000; + + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return 1; + } + + DWC_DEBUGPL(DBG_PCD, "%s called\n", __FUNCTION__); + /* Switch-on voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Assert Restore signal */ + gpwrdn.d32 = 0; + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + if (rem_wakeup) { + dwc_udelay(70); + } + + /* Deassert Reset core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Mask interrupts from gpwrdn */ + gpwrdn.d32 = 0; + gpwrdn.b.connect_det_msk = 1; + gpwrdn.b.srp_det_msk = 1; + gpwrdn.b.disconn_det_msk = 1; + gpwrdn.b.rst_det_msk = 1; + gpwrdn.b.lnstchng_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Indicates that we are going out from hibernation */ + core_if->hibernation_suspend = 0; + + /* + * Set Restore Essential Regs bit in PCGCCTL register, restore_mode = 1 + * indicates restore from remote_wakeup + */ + restore_essential_regs(core_if, rem_wakeup, 0); + + /* + * Wait a little for seeing new value of variable hibernation_suspend if + * Restore done interrupt received before polling + */ + dwc_udelay(10); + + if (core_if->hibernation_suspend == 0) { + /* + * Wait For Restore_done Interrupt. This mechanism of polling the + * interrupt is introduced to avoid any possible race conditions + */ + do { + gintsts_data_t gintsts; + gintsts.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gintsts); + if (gintsts.b.restoredone) { + gintsts.d32 = 0; + gintsts.b.restoredone = 1; + DWC_WRITE_REG32(&core_if->core_global_regs-> + gintsts, gintsts.d32); + DWC_PRINTF("Restore Done Interrupt seen\n"); + break; + } + dwc_udelay(10); + } while (--timeout); + if (!timeout) { + DWC_PRINTF("Restore Done interrupt wasn't generated here\n"); + } + } + /* Clear all pending interupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* De-assert Restore */ + gpwrdn.d32 = 0; + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + if (!rem_wakeup) { + pcgcctl.d32 = 0; + pcgcctl.b.rstpdwnmodule = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + } + + /* Restore GUSBCFG and DCFG */ + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, + core_if->gr_backup->gusbcfg_local); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, + core_if->dr_backup->dcfg); + + /* De-assert Wakeup Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + if (!rem_wakeup) { + /* Set Device programming done bit */ + dctl.b.pwronprgdone = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + } else { + /* Start Remote Wakeup Signaling */ + dctl.d32 = core_if->dr_backup->dctl; + dctl.b.rmtwkupsig = 1; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); + } + + dwc_mdelay(2); + /* Clear all pending interupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* Restore global registers */ + dwc_otg_restore_global_regs(core_if); + /* Restore device global registers */ + dwc_otg_restore_dev_regs(core_if, rem_wakeup); + + if (rem_wakeup) { + dwc_mdelay(7); + dctl.d32 = 0; + dctl.b.rmtwkupsig = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0); + } + + core_if->hibernation_suspend = 0; + /* The core will be in ON STATE */ + core_if->lx_state = DWC_OTG_L0; + DWC_PRINTF("Hibernation recovery completes here\n"); + + return 1; +} + +/* + * The restore operation is modified to support Synopsys Emulated Powerdown and + * Hibernation. This function is for exiting from Host mode hibernation by + * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup. + * @param core_if Programming view of DWC_otg controller. + * @param rem_wakeup - indicates whether resume is initiated by Device or Host. + * @param reset - indicates whether resume is initiated by Reset. + */ +int dwc_otg_host_hibernation_restore(dwc_otg_core_if_t * core_if, + int rem_wakeup, int reset) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + hprt0_data_t hprt0 = {.d32 = 0 }; + + int timeout = 2000; + + DWC_DEBUGPL(DBG_HCD, "%s called\n", __FUNCTION__); + /* Switch-on voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Assert Restore signal */ + gpwrdn.d32 = 0; + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + if (!rem_wakeup) { + dwc_udelay(50); + } + + /* Deassert Reset core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + gpwrdn.d32 = 0; + gpwrdn.b.connect_det_msk = 1; + gpwrdn.b.srp_det_msk = 1; + gpwrdn.b.disconn_det_msk = 1; + gpwrdn.b.rst_det_msk = 1; + gpwrdn.b.lnstchng_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Indicates that we are going out from hibernation */ + core_if->hibernation_suspend = 0; + + /* Set Restore Essential Regs bit in PCGCCTL register */ + restore_essential_regs(core_if, rem_wakeup, 1); + + /* Wait a little for seeing new value of variable hibernation_suspend if + * Restore done interrupt received before polling */ + dwc_udelay(10); + + if (core_if->hibernation_suspend == 0) { + /* Wait For Restore_done Interrupt. This mechanism of polling the + * interrupt is introduced to avoid any possible race conditions + */ + do { + gintsts_data_t gintsts; + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + if (gintsts.b.restoredone) { + gintsts.d32 = 0; + gintsts.b.restoredone = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + DWC_DEBUGPL(DBG_HCD,"Restore Done Interrupt seen\n"); + break; + } + dwc_udelay(10); + } while (--timeout); + if (!timeout) { + DWC_WARN("Restore Done interrupt wasn't generated\n"); + } + } + + /* Set the flag's value to 0 again after receiving restore done interrupt */ + core_if->hibernation_suspend = 0; + + /* This step is not described in functional spec but if not wait for this + * delay, mismatch interrupts occurred because just after restore core is + * in Device mode(gintsts.curmode == 0) */ + dwc_mdelay(100); + + /* Clear all pending interrupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* De-assert Restore */ + gpwrdn.d32 = 0; + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Restore GUSBCFG and HCFG */ + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, + core_if->gr_backup->gusbcfg_local); + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, + core_if->hr_backup->hcfg_local); + + /* De-assert Wakeup Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Start the Resume operation by programming HPRT0 */ + hprt0.d32 = core_if->hr_backup->hprt0_local; + hprt0.b.prtpwr = 1; + hprt0.b.prtena = 0; + hprt0.b.prtsusp = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + DWC_PRINTF("Resume Starts Now\n"); + if (!reset) { // Indicates it is Resume Operation + hprt0.d32 = core_if->hr_backup->hprt0_local; + hprt0.b.prtres = 1; + hprt0.b.prtpwr = 1; + hprt0.b.prtena = 0; + hprt0.b.prtsusp = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + if (!rem_wakeup) + hprt0.b.prtres = 0; + /* Wait for Resume time and then program HPRT again */ + dwc_mdelay(100); + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + } else { // Indicates it is Reset Operation + hprt0.d32 = core_if->hr_backup->hprt0_local; + hprt0.b.prtrst = 1; + hprt0.b.prtpwr = 1; + hprt0.b.prtena = 0; + hprt0.b.prtsusp = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + /* Wait for Reset time and then program HPRT again */ + dwc_mdelay(60); + hprt0.b.prtrst = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + } + /* Clear all interrupt status */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtconndet = 1; + hprt0.b.prtenchng = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* Clear all pending interupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* Restore global registers */ + dwc_otg_restore_global_regs(core_if); + /* Restore host global registers */ + dwc_otg_restore_host_regs(core_if, reset); + + /* The core will be in ON STATE */ + core_if->lx_state = DWC_OTG_L0; + DWC_PRINTF("Hibernation recovery is complete here\n"); + return 0; +} + +/** Saves some register values into system memory. */ +int dwc_otg_save_global_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_global_regs_backup *gr; + int i; + + gr = core_if->gr_backup; + if (!gr) { + gr = DWC_ALLOC(sizeof(*gr)); + if (!gr) { + return -DWC_E_NO_MEMORY; + } + core_if->gr_backup = gr; + } + + gr->gotgctl_local = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + gr->gintmsk_local = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + gr->gahbcfg_local = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg); + gr->gusbcfg_local = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + gr->grxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); + gr->gnptxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz); + gr->hptxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz); +#ifdef CONFIG_USB_DWC_OTG_LPM + gr->glpmcfg_local = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); +#endif + gr->gi2cctl_local = DWC_READ_REG32(&core_if->core_global_regs->gi2cctl); + gr->pcgcctl_local = DWC_READ_REG32(core_if->pcgcctl); + gr->gdfifocfg_local = + DWC_READ_REG32(&core_if->core_global_regs->gdfifocfg); + for (i = 0; i < MAX_EPS_CHANNELS - 1; i++) { + gr->dtxfsiz_local[i] = + DWC_READ_REG32(&(core_if->core_global_regs->dtxfsiz[i])); + } + + DWC_DEBUGPL(DBG_ANY, "===========Backing Global registers==========\n"); + DWC_DEBUGPL(DBG_ANY, "Backed up gotgctl = %08x\n", gr->gotgctl_local); + DWC_DEBUGPL(DBG_ANY, "Backed up gintmsk = %08x\n", gr->gintmsk_local); + DWC_DEBUGPL(DBG_ANY, "Backed up gahbcfg = %08x\n", gr->gahbcfg_local); + DWC_DEBUGPL(DBG_ANY, "Backed up gusbcfg = %08x\n", gr->gusbcfg_local); + DWC_DEBUGPL(DBG_ANY, "Backed up grxfsiz = %08x\n", gr->grxfsiz_local); + DWC_DEBUGPL(DBG_ANY, "Backed up gnptxfsiz = %08x\n", + gr->gnptxfsiz_local); + DWC_DEBUGPL(DBG_ANY, "Backed up hptxfsiz = %08x\n", + gr->hptxfsiz_local); +#ifdef CONFIG_USB_DWC_OTG_LPM + DWC_DEBUGPL(DBG_ANY, "Backed up glpmcfg = %08x\n", gr->glpmcfg_local); +#endif + DWC_DEBUGPL(DBG_ANY, "Backed up gi2cctl = %08x\n", gr->gi2cctl_local); + DWC_DEBUGPL(DBG_ANY, "Backed up pcgcctl = %08x\n", gr->pcgcctl_local); + DWC_DEBUGPL(DBG_ANY,"Backed up gdfifocfg = %08x\n",gr->gdfifocfg_local); + + return 0; +} + +/** Saves GINTMSK register before setting the msk bits. */ +int dwc_otg_save_gintmsk_reg(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_global_regs_backup *gr; + + gr = core_if->gr_backup; + if (!gr) { + gr = DWC_ALLOC(sizeof(*gr)); + if (!gr) { + return -DWC_E_NO_MEMORY; + } + core_if->gr_backup = gr; + } + + gr->gintmsk_local = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + + DWC_DEBUGPL(DBG_ANY,"=============Backing GINTMSK registers============\n"); + DWC_DEBUGPL(DBG_ANY, "Backed up gintmsk = %08x\n", gr->gintmsk_local); + + return 0; +} + +int dwc_otg_save_dev_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_dev_regs_backup *dr; + int i; + + dr = core_if->dr_backup; + if (!dr) { + dr = DWC_ALLOC(sizeof(*dr)); + if (!dr) { + return -DWC_E_NO_MEMORY; + } + core_if->dr_backup = dr; + } + + dr->dcfg = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + dr->dctl = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + dr->daintmsk = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk); + dr->diepmsk = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->diepmsk); + dr->doepmsk = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->doepmsk); + + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + dr->diepctl[i] = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl); + dr->dieptsiz[i] = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->dieptsiz); + dr->diepdma[i] = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepdma); + } + + DWC_DEBUGPL(DBG_ANY, + "=============Backing Host registers==============\n"); + DWC_DEBUGPL(DBG_ANY, "Backed up dcfg = %08x\n", dr->dcfg); + DWC_DEBUGPL(DBG_ANY, "Backed up dctl = %08x\n", dr->dctl); + DWC_DEBUGPL(DBG_ANY, "Backed up daintmsk = %08x\n", + dr->daintmsk); + DWC_DEBUGPL(DBG_ANY, "Backed up diepmsk = %08x\n", dr->diepmsk); + DWC_DEBUGPL(DBG_ANY, "Backed up doepmsk = %08x\n", dr->doepmsk); + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + DWC_DEBUGPL(DBG_ANY, "Backed up diepctl[%d] = %08x\n", i, + dr->diepctl[i]); + DWC_DEBUGPL(DBG_ANY, "Backed up dieptsiz[%d] = %08x\n", + i, dr->dieptsiz[i]); + DWC_DEBUGPL(DBG_ANY, "Backed up diepdma[%d] = %08x\n", i, + dr->diepdma[i]); + } + + return 0; +} + +int dwc_otg_save_host_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_host_regs_backup *hr; + int i; + + hr = core_if->hr_backup; + if (!hr) { + hr = DWC_ALLOC(sizeof(*hr)); + if (!hr) { + return -DWC_E_NO_MEMORY; + } + core_if->hr_backup = hr; + } + + hr->hcfg_local = + DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + hr->haintmsk_local = + DWC_READ_REG32(&core_if->host_if->host_global_regs->haintmsk); + for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) { + hr->hcintmsk_local[i] = + DWC_READ_REG32(&core_if->host_if->hc_regs[i]->hcintmsk); + } + hr->hprt0_local = DWC_READ_REG32(core_if->host_if->hprt0); + hr->hfir_local = + DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir); + + DWC_DEBUGPL(DBG_ANY, + "=============Backing Host registers===============\n"); + DWC_DEBUGPL(DBG_ANY, "Backed up hcfg = %08x\n", + hr->hcfg_local); + DWC_DEBUGPL(DBG_ANY, "Backed up haintmsk = %08x\n", hr->haintmsk_local); + for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) { + DWC_DEBUGPL(DBG_ANY, "Backed up hcintmsk[%02d]=%08x\n", i, + hr->hcintmsk_local[i]); + } + DWC_DEBUGPL(DBG_ANY, "Backed up hprt0 = %08x\n", + hr->hprt0_local); + DWC_DEBUGPL(DBG_ANY, "Backed up hfir = %08x\n", + hr->hfir_local); + + return 0; +} + +int dwc_otg_restore_global_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_global_regs_backup *gr; + int i; + + gr = core_if->gr_backup; + if (!gr) { + return -DWC_E_INVALID; + } + + DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, gr->gotgctl_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gr->gintmsk_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gr->gusbcfg_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, gr->gahbcfg_local); + DWC_WRITE_REG32(&core_if->core_global_regs->grxfsiz, gr->grxfsiz_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gnptxfsiz, + gr->gnptxfsiz_local); + DWC_WRITE_REG32(&core_if->core_global_regs->hptxfsiz, + gr->hptxfsiz_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gdfifocfg, + gr->gdfifocfg_local); + for (i = 0; i < MAX_EPS_CHANNELS-1; i++) { + DWC_WRITE_REG32(&core_if->core_global_regs->dtxfsiz[i], + gr->dtxfsiz_local[i]); + } + + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + DWC_WRITE_REG32(core_if->host_if->hprt0, 0x0000100A); + DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, + (gr->gahbcfg_local)); + return 0; +} + +int dwc_otg_restore_dev_regs(dwc_otg_core_if_t * core_if, int rem_wakeup) +{ + struct dwc_otg_dev_regs_backup *dr; + int i; + + dr = core_if->dr_backup; + + if (!dr) { + return -DWC_E_INVALID; + } + + if (!rem_wakeup) { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, + dr->dctl); + } + + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daintmsk, dr->daintmsk); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->diepmsk, dr->diepmsk); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->doepmsk, dr->doepmsk); + + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->dieptsiz, dr->dieptsiz[i]); + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->diepdma, dr->diepdma[i]); + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl, dr->diepctl[i]); + } + + return 0; +} + +int dwc_otg_restore_host_regs(dwc_otg_core_if_t * core_if, int reset) +{ + struct dwc_otg_host_regs_backup *hr; + int i; + hr = core_if->hr_backup; + + if (!hr) { + return -DWC_E_INVALID; + } + + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hr->hcfg_local); + //if (!reset) + //{ + // DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hfir, hr->hfir_local); + //} + + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haintmsk, + hr->haintmsk_local); + for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) { + DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcintmsk, + hr->hcintmsk_local[i]); + } + + return 0; +} + +int restore_lpm_i2c_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_global_regs_backup *gr; + + gr = core_if->gr_backup; + + /* Restore values for LPM and I2C */ +#ifdef CONFIG_USB_DWC_OTG_LPM + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, gr->glpmcfg_local); +#endif + DWC_WRITE_REG32(&core_if->core_global_regs->gi2cctl, gr->gi2cctl_local); + + return 0; +} + +int restore_essential_regs(dwc_otg_core_if_t * core_if, int rmode, int is_host) +{ + struct dwc_otg_global_regs_backup *gr; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + gahbcfg_data_t gahbcfg = {.d32 = 0 }; + gusbcfg_data_t gusbcfg = {.d32 = 0 }; + gintmsk_data_t gintmsk = {.d32 = 0 }; + + /* Restore LPM and I2C registers */ + restore_lpm_i2c_regs(core_if); + + /* Set PCGCCTL to 0 */ + DWC_WRITE_REG32(core_if->pcgcctl, 0x00000000); + + gr = core_if->gr_backup; + /* Load restore values for [31:14] bits */ + DWC_WRITE_REG32(core_if->pcgcctl, + ((gr->pcgcctl_local & 0xffffc000) | 0x00020000)); + + /* Umnask global Interrupt in GAHBCFG and restore it */ + gahbcfg.d32 = gr->gahbcfg_local; + gahbcfg.b.glblintrmsk = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, gahbcfg.d32); + + /* Clear all pending interupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* Unmask restore done interrupt */ + gintmsk.b.restoredone = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32); + + /* Restore GUSBCFG and HCFG/DCFG */ + gusbcfg.d32 = core_if->gr_backup->gusbcfg_local; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32); + + if (is_host) { + hcfg_data_t hcfg = {.d32 = 0 }; + hcfg.d32 = core_if->hr_backup->hcfg_local; + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, + hcfg.d32); + + /* Load restore values for [31:14] bits */ + pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000; + pcgcctl.d32 = gr->pcgcctl_local | 0x00020000; + + if (rmode) + pcgcctl.b.restoremode = 1; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + dwc_udelay(10); + + /* Load restore values for [31:14] bits and set EssRegRestored bit */ + pcgcctl.d32 = gr->pcgcctl_local | 0xffffc000; + pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000; + pcgcctl.b.ess_reg_restored = 1; + if (rmode) + pcgcctl.b.restoremode = 1; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + } else { + dcfg_data_t dcfg = {.d32 = 0 }; + dcfg.d32 = core_if->dr_backup->dcfg; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32); + + /* Load restore values for [31:14] bits */ + pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000; + pcgcctl.d32 = gr->pcgcctl_local | 0x00020000; + if (!rmode) { + pcgcctl.d32 |= 0x208; + } + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + dwc_udelay(10); + + /* Load restore values for [31:14] bits */ + pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000; + pcgcctl.d32 = gr->pcgcctl_local | 0x00020000; + pcgcctl.b.ess_reg_restored = 1; + if (!rmode) + pcgcctl.d32 |= 0x208; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + } + + return 0; +} + +/** + * Initializes the FSLSPClkSel field of the HCFG register depending on the PHY + * type. + */ +static void init_fslspclksel(dwc_otg_core_if_t * core_if) +{ + uint32_t val; + hcfg_data_t hcfg; + + if (((core_if->hwcfg2.b.hs_phy_type == 2) && + (core_if->hwcfg2.b.fs_phy_type == 1) && + (core_if->core_params->ulpi_fs_ls)) || + (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) { + /* Full speed PHY */ + val = DWC_HCFG_48_MHZ; + } else { + /* High speed PHY running at full speed or high speed */ + val = DWC_HCFG_30_60_MHZ; + } + + DWC_DEBUGPL(DBG_CIL, "Initializing HCFG.FSLSPClkSel to 0x%1x\n", val); + hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + hcfg.b.fslspclksel = val; + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hcfg.d32); +} + +/** + * Initializes the DevSpd field of the DCFG register depending on the PHY type + * and the enumeration speed of the device. + */ +static void init_devspd(dwc_otg_core_if_t * core_if) +{ + uint32_t val; + dcfg_data_t dcfg; + + if (((core_if->hwcfg2.b.hs_phy_type == 2) && + (core_if->hwcfg2.b.fs_phy_type == 1) && + (core_if->core_params->ulpi_fs_ls)) || + (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) { + /* Full speed PHY */ + val = 0x3; + } else if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) { + /* High speed PHY running at full speed */ + val = 0x1; + } else { + /* High speed PHY running at high speed */ + val = 0x0; + } + + if (in_calibration() || in_autotest()) + val = 1; + DWC_DEBUGPL(DBG_CIL, "Initializing DCFG.DevSpd to 0x%1x\n", val); + + dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + dcfg.b.devspd = val; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32); +} + +/** + * This function calculates the number of IN EPS + * using GHWCFG1 and GHWCFG2 registers values + * + * @param core_if Programming view of the DWC_otg controller + */ +static uint32_t calc_num_in_eps(dwc_otg_core_if_t * core_if) +{ + uint32_t num_in_eps = 0; + uint32_t num_eps = core_if->hwcfg2.b.num_dev_ep; + uint32_t hwcfg1 = core_if->hwcfg1.d32 >> 3; + //uint32_t num_tx_fifos = core_if->hwcfg4.b.num_in_eps; + int i; + + for (i = 0; i < num_eps; ++i) { + if (!(hwcfg1 & 0x1)) + num_in_eps++; + + hwcfg1 >>= 2; + } + + /* + * the dedicate fifo maybe less than number of IN eps, but our + * composite usb function normally won't use all dedicated fifo, + * so, just let allocate more the IN eps. Sword, + */ + #if 0 + if (core_if->hwcfg4.b.ded_fifo_en) { + num_in_eps = + (num_in_eps > num_tx_fifos) ? num_tx_fifos : num_in_eps; + } + #endif + + return num_in_eps; +} + +/** + * This function calculates the number of OUT EPS + * using GHWCFG1 and GHWCFG2 registers values + * + * @param core_if Programming view of the DWC_otg controller + */ +static uint32_t calc_num_out_eps(dwc_otg_core_if_t * core_if) +{ + uint32_t num_out_eps = 0; + uint32_t num_eps = core_if->hwcfg2.b.num_dev_ep; + uint32_t hwcfg1 = core_if->hwcfg1.d32 >> 2; + int i; + + for (i = 0; i < num_eps; ++i) { + if (!(hwcfg1 & 0x1)) + num_out_eps++; + + hwcfg1 >>= 2; + } + return num_out_eps; +} + +#ifndef DWC_DEVICE_ONLY + +void dwc_otg_core_fore_host(dwc_otg_core_if_t * core_if) +{ + //int i = 0; + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; +// dwc_otg_dev_if_t *dev_if = core_if->dev_if; + gusbcfg_data_t usbcfg = {.d32 = 0 }; + + /* Common Initialization */ + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + + /* Program the force_host_mod */ + usbcfg.b.force_host_mode = 1; + + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); +} +#endif +/** + * This function initializes the DWC_otg controller registers and + * prepares the core for device mode or host mode operation. + * + * @param core_if Programming view of the DWC_otg controller + * + */ +void dwc_otg_core_init(dwc_otg_core_if_t * core_if) +{ + int i = 0; + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + gahbcfg_data_t ahbcfg = {.d32 = 0 }; + gusbcfg_data_t usbcfg = {.d32 = 0 }; + gi2cctl_data_t i2cctl = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_CILV, "dwc_otg_core_init(%p)\n", core_if); + + /* Common Initialization */ + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + + /* Program the ULPI External VBUS bit if needed */ + usbcfg.b.ulpi_ext_vbus_drv = + (core_if->core_params->phy_ulpi_ext_vbus == + DWC_PHY_ULPI_EXTERNAL_VBUS) ? 1 : 0; + + /* Set external TS Dline pulsing */ + usbcfg.b.term_sel_dl_pulse = + (core_if->core_params->ts_dline == 1) ? 1 : 0; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + + /* Reset the Controller */ + dwc_otg_core_reset(core_if); + + core_if->adp_enable = core_if->core_params->adp_supp_enable; + core_if->power_down = core_if->core_params->power_down; + core_if->otg_sts = 0; + + /* Initialize parameters from Hardware configuration registers. */ + dev_if->num_in_eps = calc_num_in_eps(core_if); + dev_if->num_out_eps = calc_num_out_eps(core_if); + + DWC_DEBUGPL(DBG_CIL, "num_dev_perio_in_ep=%d\n", + core_if->hwcfg4.b.num_dev_perio_in_ep); + + for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) { + dev_if->perio_tx_fifo_size[i] = + DWC_READ_REG32(&global_regs->dtxfsiz[i]) >> 16; + DWC_DEBUGPL(DBG_CIL, "Periodic Tx FIFO SZ #%d=0x%0x\n", + i, dev_if->perio_tx_fifo_size[i]); + } + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + dev_if->tx_fifo_size[i] = + DWC_READ_REG32(&global_regs->dtxfsiz[i]) >> 16; + DWC_DEBUGPL(DBG_CIL, "Tx FIFO SZ #%d=0x%0x\n", + i, dev_if->tx_fifo_size[i]); + } + + core_if->total_fifo_size = core_if->hwcfg3.b.dfifo_depth; + core_if->rx_fifo_size = DWC_READ_REG32(&global_regs->grxfsiz); + core_if->nperio_tx_fifo_size = + DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16; + + DWC_DEBUGPL(DBG_CIL, "Total FIFO SZ=%d\n", core_if->total_fifo_size); + DWC_DEBUGPL(DBG_CIL, "Rx FIFO SZ=%d\n", core_if->rx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO SZ=%d\n", + core_if->nperio_tx_fifo_size); + + /* This programming sequence needs to happen in FS mode before any other + * programming occurs */ + if ((core_if->core_params->speed == DWC_SPEED_PARAM_FULL) && + (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) { + /* If FS mode with FS PHY */ + + /* core_init() is now called on every switch so only call the + * following for the first time through. */ + //if (!core_if->phy_init_done) { + // core_if->phy_init_done = 1; + DWC_DEBUGPL(DBG_CIL, "FS_PHY detected\n"); + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + usbcfg.b.physel = 1; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + + /* Reset after a PHY select */ + dwc_otg_core_reset(core_if); + //} + + /* Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS. Also + * do this on HNP Dev/Host mode switches (done in dev_init and + * host_init). */ + if (dwc_otg_is_host_mode(core_if)) { + init_fslspclksel(core_if); + } else { + init_devspd(core_if); + } + + if (core_if->core_params->i2c_enable) { + DWC_DEBUGPL(DBG_CIL, "FS_PHY Enabling I2c\n"); + /* Program GUSBCFG.OtgUtmifsSel to I2C */ + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + usbcfg.b.otgutmifssel = 1; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + + /* Program GI2CCTL.I2CEn */ + i2cctl.d32 = DWC_READ_REG32(&global_regs->gi2cctl); + i2cctl.b.i2cdevaddr = 1; + i2cctl.b.i2cen = 0; + DWC_WRITE_REG32(&global_regs->gi2cctl, i2cctl.d32); + i2cctl.b.i2cen = 1; + DWC_WRITE_REG32(&global_regs->gi2cctl, i2cctl.d32); + } + + } /* endif speed == DWC_SPEED_PARAM_FULL */ + else { + /* High speed PHY. */ + //if (!core_if->phy_init_done) { + // core_if->phy_init_done = 1; + /* HS PHY parameters. These parameters are preserved + * during soft reset so only program the first time. Do + * a soft reset immediately after setting phyif. */ + usbcfg.b.ulpi_utmi_sel = core_if->core_params->phy_type; + //if (usbcfg.b.ulpi_utmi_sel == 1) { //sword BUG + if (core_if->core_params->phy_utmi_width == 16) { + usbcfg.b.phyif = 1; + + } else { + usbcfg.b.phyif = 0; + } + usbcfg.b.ulpi_utmi_sel = 0; + + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + /* Reset after setting the PHY parameters */ + dwc_otg_core_reset(core_if); + //} + } + + if ((core_if->hwcfg2.b.hs_phy_type == 2) && + (core_if->hwcfg2.b.fs_phy_type == 1) && + (core_if->core_params->ulpi_fs_ls)) { + DWC_DEBUGPL(DBG_CIL, "Setting ULPI FSLS\n"); + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + usbcfg.b.ulpi_fsls = 1; + usbcfg.b.ulpi_clk_sus_m = 1; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + } else { + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + usbcfg.b.ulpi_fsls = 0; + usbcfg.b.ulpi_clk_sus_m = 0; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + } + + /* Program the GAHBCFG Register. */ + switch (core_if->hwcfg2.b.architecture) { + + case DWC_SLAVE_ONLY_ARCH: + DWC_DEBUGPL(DBG_CIL, "Slave Only Mode\n"); + ahbcfg.b.nptxfemplvl_txfemplvl = + DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY; + ahbcfg.b.ptxfemplvl = DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY; + core_if->dma_enable = 0; + core_if->dma_desc_enable = 0; + break; + + case DWC_EXT_DMA_ARCH: + DWC_DEBUGPL(DBG_CIL, "External DMA Mode\n"); + { + uint8_t brst_sz = core_if->core_params->dma_burst_size; + ahbcfg.b.hburstlen = 0; + while (brst_sz > 1) { + ahbcfg.b.hburstlen++; + brst_sz >>= 1; + } + } + core_if->dma_enable = (core_if->core_params->dma_enable != 0); + core_if->dma_desc_enable = + (core_if->core_params->dma_desc_enable != 0); + break; + + case DWC_INT_DMA_ARCH: + DWC_DEBUGPL(DBG_CIL, "Internal DMA Mode\n"); + /* Old value was DWC_GAHBCFG_INT_DMA_BURST_INCR - done for + Host mode ISOC in issue fix - vahrama */ + ahbcfg.b.hburstlen = DWC_GAHBCFG_INT_DMA_BURST_INCR4; + core_if->dma_enable = (core_if->core_params->dma_enable != 0); + core_if->dma_desc_enable = + (core_if->core_params->dma_desc_enable != 0); + break; + + } + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + DWC_DEBUGPL(DBG_CIL, "Using Descriptor DMA mode\n"); + } else { + DWC_DEBUGPL(DBG_CIL, "Using Buffer DMA mode\n"); + } + } else { + DWC_PRINTF("Using Slave mode\n"); + core_if->dma_desc_enable = 0; + } + + if (core_if->core_params->ahb_single) { + ahbcfg.b.ahbsingle = 1; + } + + ahbcfg.b.dmaenable = core_if->dma_enable; + DWC_WRITE_REG32(&global_regs->gahbcfg, ahbcfg.d32); + + core_if->en_multiple_tx_fifo = core_if->hwcfg4.b.ded_fifo_en; + + core_if->pti_enh_enable = core_if->core_params->pti_enable != 0; + core_if->multiproc_int_enable = core_if->core_params->mpi_enable; + DWC_DEBUGPL(DBG_CIL, "Periodic Transfer Interrupt Enhancement - %s\n", + ((core_if->pti_enh_enable) ? "enabled" : "disabled")); + DWC_DEBUGPL(DBG_CIL, "Multiprocessor Interrupt Enhancement - %s\n", + ((core_if->multiproc_int_enable) ? "enabled" : "disabled")); + + /* + * Program the GUSBCFG register. + */ + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + + switch (core_if->hwcfg2.b.op_mode) { + case DWC_MODE_HNP_SRP_CAPABLE: + usbcfg.b.hnpcap = (core_if->core_params->otg_cap == + DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE); + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + + case DWC_MODE_SRP_ONLY_CAPABLE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + + case DWC_MODE_NO_HNP_SRP_CAPABLE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + + case DWC_MODE_SRP_CAPABLE_DEVICE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + + case DWC_MODE_NO_SRP_CAPABLE_DEVICE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + + case DWC_MODE_SRP_CAPABLE_HOST: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + + case DWC_MODE_NO_SRP_CAPABLE_HOST: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + } + + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + +#ifdef CONFIG_USB_DWC_OTG_LPM + if (core_if->core_params->lpm_enable) { + glpmcfg_data_t lpmcfg = {.d32 = 0 }; + + /* To enable LPM support set lpm_cap_en bit */ + lpmcfg.b.lpm_cap_en = 1; + + /* Make AppL1Res ACK */ + lpmcfg.b.appl_resp = 1; + + /* Retry 3 times */ + lpmcfg.b.retry_count = 3; + + DWC_MODIFY_REG32(&core_if->core_global_regs->glpmcfg, + 0, lpmcfg.d32); + + } +#endif + if (core_if->core_params->ic_usb_cap) { + gusbcfg_data_t gusbcfg = {.d32 = 0 }; + gusbcfg.b.ic_usb_cap = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gusbcfg, + 0, gusbcfg.d32); + } + { + gotgctl_data_t gotgctl = {.d32 = 0 }; + gotgctl.b.otgver = core_if->core_params->otg_ver; + DWC_MODIFY_REG32(&core_if->core_global_regs->gotgctl, 0, + gotgctl.d32); + /* Set OTG version supported */ + core_if->otg_ver = core_if->core_params->otg_ver; + DWC_DEBUGPL(DBG_CIL, "OTG VER PARAM: %d, OTG VER FLAG: %d\n", + core_if->core_params->otg_ver, core_if->otg_ver); + + } + + /* Enable common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* Do device or host intialization based on mode during PCD + * and HCD initialization */ + if (dwc_otg_is_host_mode(core_if)) { + DWC_DEBUGPL(DBG_ANY, "Host Mode\n"); + core_if->op_state = A_HOST; + } else { + DWC_DEBUGPL(DBG_ANY, "Device Mode\n"); + core_if->op_state = B_PERIPHERAL; + } +} + +/** + * This function enables the Device mode interrupts. + * + * @param core_if Programming view of DWC_otg controller + */ +void dwc_otg_enable_device_interrupts(dwc_otg_core_if_t * core_if) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + + DWC_DEBUGPL(DBG_CIL, "%s()\n", __func__); + + /* Disable all interrupts. */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* Clear any pending interrupts */ + DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF); + + /* Enable the common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* Enable interrupts */ + intr_mask.b.usbreset = 1; + intr_mask.b.enumdone = 1; + /* Disable Disconnect interrupt in Device mode */ + intr_mask.b.disconnect = 0; + + if (!core_if->multiproc_int_enable) { + intr_mask.b.inepintr = 1; + intr_mask.b.outepintr = 1; + } + + intr_mask.b.erlysuspend = 1; + + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.epmismatch = 1; + } + + //intr_mask.b.incomplisoout = 1; + intr_mask.b.incomplisoin = 1; + +/* Enable the ignore frame number for ISOC xfers - MAS */ +/* Disable to support high bandwith ISOC transfers - manukz */ +#if 0 +#ifdef DWC_UTE_PER_IO + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + dctl_data_t dctl1 = {.d32 = 0 }; + dctl1.b.ifrmnum = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, 0, dctl1.d32); + DWC_DEBUG("----Enabled Ignore frame number (0x%08x)", + DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dctl)); + } + } +#endif +#endif +#ifdef DWC_EN_ISOC + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + if (core_if->pti_enh_enable) { + dctl_data_t dctl = {.d32 = 0 }; + dctl.b.ifrmnum = 1; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dctl, + 0, dctl.d32); + } else { + intr_mask.b.incomplisoin = 1; + intr_mask.b.incomplisoout = 1; + } + } + } else { + intr_mask.b.incomplisoin = 1; + intr_mask.b.incomplisoout = 1; + } +#endif /* DWC_EN_ISOC */ + + /** @todo NGS: Should this be a module parameter? */ +#ifdef USE_PERIODIC_EP + intr_mask.b.isooutdrop = 1; + intr_mask.b.eopframe = 1; + intr_mask.b.incomplisoin = 1; + intr_mask.b.incomplisoout = 1; +#endif + + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32); + + DWC_DEBUGPL(DBG_CIL, "%s() gintmsk=%0x\n", __func__, + DWC_READ_REG32(&global_regs->gintmsk)); +} + +/** + * This function initializes the DWC_otg controller registers for + * device mode. + * + * @param core_if Programming view of DWC_otg controller + * + */ +void dwc_otg_core_dev_init(dwc_otg_core_if_t * core_if) +{ + int i; + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_core_params_t *params = core_if->core_params; + dcfg_data_t dcfg = {.d32 = 0 }; + //depctl_data_t diepctl = {.d32 = 0 }; + grstctl_t resetctl = {.d32 = 0 }; + uint32_t rx_fifo_size; + fifosize_data_t nptxfifosize; + fifosize_data_t txfifosize; + dthrctl_data_t dthrctl; + fifosize_data_t ptxfifosize; + gotgctl_data_t gotgctl = {.d32 = 0 }; + + /* Restart the Phy Clock */ + DWC_WRITE_REG32(core_if->pcgcctl, 0); + + /* Device configuration register */ + init_devspd(core_if); + dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg); + dcfg.b.descdma = (core_if->dma_desc_enable) ? 1 : 0; + dcfg.b.perfrint = DWC_DCFG_FRAME_INTERVAL_80; + /* Enable Device OUT NAK in case of DDMA mode */ + if (core_if->core_params->dev_out_nak) { + dcfg.b.endevoutnak = 1; + } + + if (core_if->core_params->cont_on_bna) { + dctl_data_t dctl = {.d32 = 0 }; + dctl.b.encontonbna = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, 0, dctl.d32); + } + + + DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32); + /**the reset irq won't come if we don't set bvalidoven | bvalidovval + *when connecting with google ADk + */ + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + gotgctl.b.bvalidoven = 1; + gotgctl.b.bvalidovval = 1; + DWC_WRITE_REG32(&global_regs->gotgctl, gotgctl.d32); + + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + gotgctl.b.bvalidoven = 1; + gotgctl.b.bvalidovval = 1; + DWC_WRITE_REG32(&global_regs->gotgctl, gotgctl.d32); + + /* Configure data FIFO sizes */ + if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) { + DWC_DEBUGPL(DBG_CIL, "Total FIFO Size=%d\n", + core_if->total_fifo_size); + DWC_DEBUGPL(DBG_CIL, "Rx FIFO Size=%d\n", + params->dev_rx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO Size=%d\n", + params->dev_nperio_tx_fifo_size); + + /* Rx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial grxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->grxfsiz)); + +#ifdef DWC_UTE_CFI + core_if->pwron_rxfsiz = DWC_READ_REG32(&global_regs->grxfsiz); + core_if->init_rxfsiz = params->dev_rx_fifo_size; +#endif + rx_fifo_size = params->dev_rx_fifo_size; + DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fifo_size); + + DWC_DEBUGPL(DBG_CIL, "new grxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->grxfsiz)); + + /** Set Periodic Tx FIFO Mask all bits 0 */ + core_if->p_tx_msk = 0; + +#if defined(CONFIG_ARCH_SCX35) + /**fisrt 3 txfifos are 0x40 in shark, tooo small, don't use them*/ + core_if->tx_msk = 0x7; +#else + /** Set Tx FIFO Mask all bits 0 */ + core_if->tx_msk = 0; +#endif + + if (core_if->en_multiple_tx_fifo == 0) { + /* Non-periodic Tx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + + nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size; + nptxfifosize.b.startaddr = params->dev_rx_fifo_size; + + DWC_WRITE_REG32(&global_regs->gnptxfsiz, + nptxfifosize.d32); + + DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + + /**@todo NGS: Fix Periodic FIFO Sizing! */ + /* + * Periodic Tx FIFOs These FIFOs are numbered from 1 to 15. + * Indexes of the FIFO size module parameters in the + * dev_perio_tx_fifo_size array and the FIFO size registers in + * the dptxfsiz array run from 0 to 14. + */ + /** @todo Finish debug of this */ + ptxfifosize.b.startaddr = + nptxfifosize.b.startaddr + nptxfifosize.b.depth; + for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) { + ptxfifosize.b.depth = + params->dev_perio_tx_fifo_size[i]; + DWC_DEBUGPL(DBG_CIL, + "initial dtxfsiz[%d]=%08x\n", i, + DWC_READ_REG32(&global_regs->dtxfsiz + [i])); + DWC_WRITE_REG32(&global_regs->dtxfsiz[i], + ptxfifosize.d32); + DWC_DEBUGPL(DBG_CIL, "new dtxfsiz[%d]=%08x\n", + i, + DWC_READ_REG32(&global_regs->dtxfsiz + [i])); + ptxfifosize.b.startaddr += ptxfifosize.b.depth; + } + } else { + /* + * Tx FIFOs These FIFOs are numbered from 1 to 15. + * Indexes of the FIFO size module parameters in the + * dev_tx_fifo_size array and the FIFO size registers in + * the dtxfsiz array run from 0 to 14. + */ + + /* Non-periodic Tx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + +#ifdef DWC_UTE_CFI + core_if->pwron_gnptxfsiz = + (DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16); + core_if->init_gnptxfsiz = + params->dev_nperio_tx_fifo_size; +#endif + nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size; + nptxfifosize.b.startaddr = params->dev_rx_fifo_size; + + DWC_WRITE_REG32(&global_regs->gnptxfsiz, + nptxfifosize.d32); + + DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + + txfifosize.b.startaddr = + nptxfifosize.b.startaddr + nptxfifosize.b.depth; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + + txfifosize.b.depth = + params->dev_tx_fifo_size[i]; + + DWC_DEBUGPL(DBG_CIL, + "initial dtxfsiz[%d]=%08x\n", + i, + DWC_READ_REG32(&global_regs->dtxfsiz + [i])); + +#ifdef DWC_UTE_CFI + core_if->pwron_txfsiz[i] = + (DWC_READ_REG32 + (&global_regs->dtxfsiz[i]) >> 16); + core_if->init_txfsiz[i] = + params->dev_tx_fifo_size[i]; +#endif + DWC_WRITE_REG32(&global_regs->dtxfsiz[i], + txfifosize.d32); + + DWC_DEBUGPL(DBG_CIL, + "new dtxfsiz[%d]=%08x\n", + i, + DWC_READ_REG32(&global_regs->dtxfsiz + [i])); + + txfifosize.b.startaddr += txfifosize.b.depth; + } + } + } + + /* Flush the FIFOs */ + dwc_otg_flush_tx_fifo(core_if, 0x10); /* all Tx FIFOs */ + dwc_otg_flush_rx_fifo(core_if); + + /* Flush the Learning Queue. */ + resetctl.b.intknqflsh = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->grstctl, resetctl.d32); + + /* Clear all pending Device Interrupts */ + /** @todo - if the condition needed to be checked + * or in any case all pending interrutps should be cleared? + */ + if (core_if->multiproc_int_enable) { + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + DWC_WRITE_REG32(&dev_if->dev_global_regs-> + diepeachintmsk[i], 0); + } + + for (i = 0; i < core_if->dev_if->num_out_eps; ++i) { + DWC_WRITE_REG32(&dev_if->dev_global_regs-> + doepeachintmsk[i], 0); + } + + DWC_WRITE_REG32(&dev_if->dev_global_regs->deachint, 0xFFFFFFFF); + DWC_WRITE_REG32(&dev_if->dev_global_regs->deachintmsk, 0); + } else { + DWC_WRITE_REG32(&dev_if->dev_global_regs->diepmsk, 0); + DWC_WRITE_REG32(&dev_if->dev_global_regs->doepmsk, 0); + DWC_WRITE_REG32(&dev_if->dev_global_regs->daint, 0xFFFFFFFF); + DWC_WRITE_REG32(&dev_if->dev_global_regs->daintmsk, 0); + } + + for (i = 0; i <= dev_if->num_in_eps; i++) { + depctl_data_t depctl; + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (depctl.b.epena) { + depctl.d32 = 0; + depctl.b.epdis = 1; + depctl.b.snak = 1; + } else { + depctl.d32 = 0; + } + + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32); + + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->dieptsiz, 0); + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepdma, 0); + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepint, 0xFF); + } + + for (i = 0; i <= dev_if->num_out_eps; i++) { + depctl_data_t depctl; + depctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl); + if (depctl.b.epena) { + int time_out = 0; + dctl_data_t dctl = {.d32 = 0 }; + gintmsk_data_t gintsts = {.d32 = 0 }; + doepint_data_t doepint = {.d32 = 0 }; + dctl.b.sgoutnak = 1; + do { + if(time_out++ > 10) + break; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + dwc_udelay(10); + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + } while (!gintsts.b.goutnakeff); + gintsts.d32 = 0; + gintsts.b.goutnakeff = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + depctl.d32 = 0; + depctl.b.epdis = 1; + depctl.b.snak = 1; + time_out = 0; + do { + if(time_out++ > 10) + break; + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]->doepctl, depctl.d32); + dwc_udelay(10); + doepint.d32 = DWC_READ_REG32(&core_if->dev_if->out_ep_regs[i]->doepint); + } while (!doepint.b.epdisabled); + + doepint.b.epdisabled = 1; + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]->doepint, doepint.d32); + + dctl.d32 = 0; + dctl.b.cgoutnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + } else { + depctl.d32 = 0; + } + + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, depctl.d32); + + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doeptsiz, 0); + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepdma, 0); + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepint, 0xFF); + } + + if (core_if->en_multiple_tx_fifo && core_if->dma_enable) { + dev_if->non_iso_tx_thr_en = params->thr_ctl & 0x1; + dev_if->iso_tx_thr_en = (params->thr_ctl >> 1) & 0x1; + dev_if->rx_thr_en = (params->thr_ctl >> 2) & 0x1; + + dev_if->rx_thr_length = params->rx_thr_length; + dev_if->tx_thr_length = params->tx_thr_length; + + dev_if->setup_desc_index = 0; + + dthrctl.d32 = 0; + dthrctl.b.non_iso_thr_en = dev_if->non_iso_tx_thr_en; + dthrctl.b.iso_thr_en = dev_if->iso_tx_thr_en; + dthrctl.b.tx_thr_len = dev_if->tx_thr_length; + dthrctl.b.rx_thr_en = dev_if->rx_thr_en; + dthrctl.b.rx_thr_len = dev_if->rx_thr_length; + dthrctl.b.ahb_thr_ratio = params->ahb_thr_ratio; + + DWC_WRITE_REG32(&dev_if->dev_global_regs->dtknqr3_dthrctl, + dthrctl.d32); + + DWC_DEBUGPL(DBG_CIL, + "Non ISO Tx Thr - %d\nISO Tx Thr - %d\nRx Thr - %d\nTx Thr Len - %d\nRx Thr Len - %d\n", + dthrctl.b.non_iso_thr_en, dthrctl.b.iso_thr_en, + dthrctl.b.rx_thr_en, dthrctl.b.tx_thr_len, + dthrctl.b.rx_thr_len); + + } + + dwc_otg_enable_device_interrupts(core_if); + + { + diepmsk_data_t msk = {.d32 = 0 }; + msk.b.txfifoundrn = 1; + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&dev_if->dev_global_regs-> + diepeachintmsk[0], msk.d32, msk.d32); + } else { + DWC_MODIFY_REG32(&dev_if->dev_global_regs->diepmsk, + msk.d32, msk.d32); + } + } + + if (core_if->multiproc_int_enable) { + /* Set NAK on Babble */ + dctl_data_t dctl = {.d32 = 0 }; + dctl.b.nakonbble = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, 0, dctl.d32); + } + + if (core_if->snpsid >= OTG_CORE_REV_2_94a) { + static uint32_t first_entrance = 1; + dctl_data_t dctl = {.d32 = 0 }; + dctl.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dctl); + if(in_calibration() && first_entrance){ + first_entrance = 0; + dctl.b.sftdiscon = 1; + }else + dctl.b.sftdiscon = 0; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dctl, dctl.d32); + } +} + +/** + * This function enables the Host mode interrupts. + * + * @param core_if Programming view of DWC_otg controller + */ +void dwc_otg_enable_host_interrupts(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_CIL, "%s()\n", __func__); + + /* Disable all interrupts. */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* Clear any pending interrupts. */ + DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF); + + /* Enable the common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* + * Enable host mode interrupts without disturbing common + * interrupts. + */ + + intr_mask.b.disconnect = 1; + intr_mask.b.portintr = 1; + intr_mask.b.hcintr = 1; + + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32); +} + +/** + * This function disables the Host Mode interrupts. + * + * @param core_if Programming view of DWC_otg controller + */ +void dwc_otg_disable_host_interrupts(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_CILV, "%s()\n", __func__); + + /* + * Disable host mode interrupts without disturbing common + * interrupts. + */ + intr_mask.b.sofintr = 1; + intr_mask.b.portintr = 1; + intr_mask.b.hcintr = 1; + intr_mask.b.ptxfempty = 1; + intr_mask.b.nptxfempty = 1; + + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, 0); +} + +/** + * This function initializes the DWC_otg controller registers for + * host mode. + * + * This function flushes the Tx and Rx FIFOs and it flushes any entries in the + * request queues. Host channels are reset to ensure that they are ready for + * performing transfers. + * + * @param core_if Programming view of DWC_otg controller + * + */ +void dwc_otg_core_host_init(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_host_if_t *host_if = core_if->host_if; + dwc_otg_core_params_t *params = core_if->core_params; + hprt0_data_t hprt0 = {.d32 = 0 }; + fifosize_data_t nptxfifosize; + fifosize_data_t ptxfifosize; + int i; + hcchar_data_t hcchar; + hcfg_data_t hcfg; + hfir_data_t hfir; + dwc_otg_hc_regs_t *hc_regs; + int num_channels; + gotgctl_data_t gotgctl = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", __func__, core_if); + + /* Restart the Phy Clock */ + DWC_WRITE_REG32(core_if->pcgcctl, 0); + + /* Initialize Host Configuration Register */ + init_fslspclksel(core_if); + if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) { + hcfg.d32 = DWC_READ_REG32(&host_if->host_global_regs->hcfg); + hcfg.b.fslssupp = 1; + DWC_WRITE_REG32(&host_if->host_global_regs->hcfg, hcfg.d32); + + } + + /* This bit allows dynamic reloading of the HFIR register + * during runtime. This bit needs to be programmed during + * initial configuration and its value must not be changed + * during runtime.*/ + if (core_if->core_params->reload_ctl == 1) { + hfir.d32 = DWC_READ_REG32(&host_if->host_global_regs->hfir); + hfir.b.hfirrldctrl = 1; + DWC_WRITE_REG32(&host_if->host_global_regs->hfir, hfir.d32); + } + + if (core_if->core_params->dma_desc_enable) { + uint8_t op_mode = core_if->hwcfg2.b.op_mode; + if (! + (core_if->hwcfg4.b.desc_dma + && (core_if->snpsid >= OTG_CORE_REV_2_90a) + && ((op_mode == DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + || (op_mode == DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG) + || (op_mode == + DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG) + || (op_mode == DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST) + || (op_mode == + DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST)))) { + + DWC_ERROR("Host can't operate in Descriptor DMA mode.\n" + "Either core version is below 2.90a or " + "GHWCFG2, GHWCFG4 registers' values do not allow Descriptor DMA in host mode.\n" + "To run the driver in Buffer DMA host mode set dma_desc_enable " + "module parameter to 0.\n"); + return; + } + hcfg.d32 = DWC_READ_REG32(&host_if->host_global_regs->hcfg); + hcfg.b.descdma = 1; + DWC_WRITE_REG32(&host_if->host_global_regs->hcfg, hcfg.d32); + } + + /* Configure data FIFO sizes */ + if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) { + DWC_DEBUGPL(DBG_CIL, "Total FIFO Size=%d\n", + core_if->total_fifo_size); + DWC_DEBUGPL(DBG_CIL, "Rx FIFO Size=%d\n", + params->host_rx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO Size=%d\n", + params->host_nperio_tx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "P Tx FIFO Size=%d\n", + params->host_perio_tx_fifo_size); + + /* Rx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial grxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->grxfsiz)); + DWC_WRITE_REG32(&global_regs->grxfsiz, + params->host_rx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "new grxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->grxfsiz)); + + /* Non-periodic Tx FIFO */ + /*set the reg with XX_fifo_size_default,don't use the value initialized when kernel bootup*/ + DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + nptxfifosize.b.depth = dwc_param_host_nperio_tx_fifo_size_default; + nptxfifosize.b.startaddr = params->host_rx_fifo_size; + DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfifosize.d32); + DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + + /* Periodic Tx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial hptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->hptxfsiz)); + ptxfifosize.b.depth = params->host_perio_tx_fifo_size; + ptxfifosize.b.startaddr = + nptxfifosize.b.startaddr + nptxfifosize.b.depth; + DWC_WRITE_REG32(&global_regs->hptxfsiz, ptxfifosize.d32); + DWC_DEBUGPL(DBG_CIL, "new hptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->hptxfsiz)); + + } + + /* TODO - check this */ + /* Clear Host Set HNP Enable in the OTG Control Register */ + gotgctl.b.hstsethnpen = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0); + /* Make sure the FIFOs are flushed. */ + dwc_otg_flush_tx_fifo(core_if, 0x10 /* all TX FIFOs */ ); + dwc_otg_flush_rx_fifo(core_if); + + /* Clear Host Set HNP Enable in the OTG Control Register */ + gotgctl.b.hstsethnpen = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0); + + if (!core_if->core_params->dma_desc_enable) { + /* Flush out any leftover queued requests. */ + num_channels = core_if->core_params->host_channels; + + for (i = 0; i < num_channels; i++) { + hc_regs = core_if->host_if->hc_regs[i]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.chen = 0; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + } + + /* Halt all channels to put them into a known state. */ + for (i = 0; i < num_channels; i++) { + int count = 0; + hc_regs = core_if->host_if->hc_regs[i]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + DWC_DEBUGPL(DBG_HCDV, "%s: Halt channel %d\n", __func__, i); + do { + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (++count > 1000) { + DWC_ERROR + ("%s: Unable to clear halt on channel %d\n", + __func__, i); + break; + } + dwc_udelay(1); + } while (hcchar.b.chen); + } + } + + /* Turn on the vbus power. */ + DWC_PRINTF("Init: Port Power? op_state=%d\n", core_if->op_state); + if (core_if->op_state == A_HOST) { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + DWC_PRINTF("Init: Power Port (%d)\n", hprt0.b.prtpwr); + if (hprt0.b.prtpwr == 0) { + hprt0.b.prtpwr = 1; + DWC_WRITE_REG32(host_if->hprt0, hprt0.d32); + } + } + + dwc_otg_enable_host_interrupts(core_if); +} + +/** + * Prepares a host channel for transferring packets to/from a specific + * endpoint. The HCCHARn register is set up with the characteristics specified + * in _hc. Host channel interrupts that may need to be serviced while this + * transfer is in progress are enabled. + * + * @param core_if Programming view of DWC_otg controller + * @param hc Information needed to initialize the host channel + */ +void dwc_otg_hc_init(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + uint32_t intr_enable; + hcintmsk_data_t hc_intr_mask; + gintmsk_data_t gintmsk = {.d32 = 0 }; + hcchar_data_t hcchar; + hcsplt_data_t hcsplt; + + uint8_t hc_num = hc->hc_num; + dwc_otg_host_if_t *host_if = core_if->host_if; + dwc_otg_hc_regs_t *hc_regs = host_if->hc_regs[hc_num]; + + /* Clear old interrupt conditions for this host channel. */ + hc_intr_mask.d32 = 0xFFFFFFFF; + hc_intr_mask.b.reserved14_31 = 0; + DWC_WRITE_REG32(&hc_regs->hcint, hc_intr_mask.d32); + + /* Enable channel interrupts required for this transfer. */ + hc_intr_mask.d32 = 0; + hc_intr_mask.b.chhltd = 1; + if (core_if->dma_enable) { + /* For Descriptor DMA mode core halts the channel on AHB error. Interrupt is not required */ + if (!core_if->dma_desc_enable) + hc_intr_mask.b.ahberr = 1; + else { + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + hc_intr_mask.b.xfercompl = 1; + } + + if (hc->error_state && !hc->do_split && + hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + hc_intr_mask.b.ack = 1; + if (hc->ep_is_in) { + hc_intr_mask.b.datatglerr = 1; + if (hc->ep_type != DWC_OTG_EP_TYPE_INTR) { + hc_intr_mask.b.nak = 1; + } + } + } + } else { + switch (hc->ep_type) { + case DWC_OTG_EP_TYPE_CONTROL: + case DWC_OTG_EP_TYPE_BULK: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.stall = 1; + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.datatglerr = 1; + if (hc->ep_is_in) { + hc_intr_mask.b.bblerr = 1; + } else { + hc_intr_mask.b.nak = 1; + hc_intr_mask.b.nyet = 1; + if (hc->do_ping) { + hc_intr_mask.b.ack = 1; + } + } + + if (hc->do_split) { + hc_intr_mask.b.nak = 1; + if (hc->complete_split) { + hc_intr_mask.b.nyet = 1; + } else { + hc_intr_mask.b.ack = 1; + } + } + + if (hc->error_state) { + hc_intr_mask.b.ack = 1; + } + break; + case DWC_OTG_EP_TYPE_INTR: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.nak = 1; + hc_intr_mask.b.stall = 1; + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.datatglerr = 1; + hc_intr_mask.b.frmovrun = 1; + + if (hc->ep_is_in) { + hc_intr_mask.b.bblerr = 1; + } + if (hc->error_state) { + hc_intr_mask.b.ack = 1; + } + if (hc->do_split) { + if (hc->complete_split) { + hc_intr_mask.b.nyet = 1; + } else { + hc_intr_mask.b.ack = 1; + } + } + break; + case DWC_OTG_EP_TYPE_ISOC: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.frmovrun = 1; + hc_intr_mask.b.ack = 1; + + if (hc->ep_is_in) { + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.bblerr = 1; + } + break; + } + } + DWC_WRITE_REG32(&hc_regs->hcintmsk, hc_intr_mask.d32); + + /* Enable the top level host channel interrupt. */ + intr_enable = (1 << hc_num); + DWC_MODIFY_REG32(&host_if->host_global_regs->haintmsk, 0, intr_enable); + + /* Make sure host channel interrupts are enabled. */ + gintmsk.b.hcintr = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, gintmsk.d32); + + /* + * Program the HCCHARn register with the endpoint characteristics for + * the current transfer. + */ + hcchar.d32 = 0; + hcchar.b.devaddr = hc->dev_addr; + hcchar.b.epnum = hc->ep_num; + hcchar.b.epdir = hc->ep_is_in; + hcchar.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW); + hcchar.b.eptype = hc->ep_type; + hcchar.b.mps = hc->max_packet; + + DWC_WRITE_REG32(&host_if->hc_regs[hc_num]->hcchar, hcchar.d32); + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " Dev Addr: %d\n", hcchar.b.devaddr); + DWC_DEBUGPL(DBG_HCDV, " Ep Num: %d\n", hcchar.b.epnum); + DWC_DEBUGPL(DBG_HCDV, " Is In: %d\n", hcchar.b.epdir); + DWC_DEBUGPL(DBG_HCDV, " Is Low Speed: %d\n", hcchar.b.lspddev); + DWC_DEBUGPL(DBG_HCDV, " Ep Type: %d\n", hcchar.b.eptype); + DWC_DEBUGPL(DBG_HCDV, " Max Pkt: %d\n", hcchar.b.mps); + DWC_DEBUGPL(DBG_HCDV, " Multi Cnt: %d\n", hcchar.b.multicnt); + + /* + * Program the HCSPLIT register for SPLITs + */ + hcsplt.d32 = 0; + if (hc->do_split) { + DWC_DEBUGPL(DBG_HCDV, "Programming HC %d with split --> %s\n", + hc->hc_num, + hc->complete_split ? "CSPLIT" : "SSPLIT"); + hcsplt.b.compsplt = hc->complete_split; + hcsplt.b.xactpos = hc->xact_pos; + hcsplt.b.hubaddr = hc->hub_addr; + hcsplt.b.prtaddr = hc->port_addr; + DWC_DEBUGPL(DBG_HCDV, " comp split %d\n", hc->complete_split); + DWC_DEBUGPL(DBG_HCDV, " xact pos %d\n", hc->xact_pos); + DWC_DEBUGPL(DBG_HCDV, " hub addr %d\n", hc->hub_addr); + DWC_DEBUGPL(DBG_HCDV, " port addr %d\n", hc->port_addr); + DWC_DEBUGPL(DBG_HCDV, " is_in %d\n", hc->ep_is_in); + DWC_DEBUGPL(DBG_HCDV, " Max Pkt: %d\n", hcchar.b.mps); + DWC_DEBUGPL(DBG_HCDV, " xferlen: %d\n", hc->xfer_len); + } + DWC_WRITE_REG32(&host_if->hc_regs[hc_num]->hcsplt, hcsplt.d32); + +} + +/** + * Attempts to halt a host channel. This function should only be called in + * Slave mode or to abort a transfer in either Slave mode or DMA mode. Under + * normal circumstances in DMA mode, the controller halts the channel when the + * transfer is complete or a condition occurs that requires application + * intervention. + * + * In slave mode, checks for a free request queue entry, then sets the Channel + * Enable and Channel Disable bits of the Host Channel Characteristics + * register of the specified channel to intiate the halt. If there is no free + * request queue entry, sets only the Channel Disable bit of the HCCHARn + * register to flush requests for this channel. In the latter case, sets a + * flag to indicate that the host channel needs to be halted when a request + * queue slot is open. + * + * In DMA mode, always sets the Channel Enable and Channel Disable bits of the + * HCCHARn register. The controller ensures there is space in the request + * queue before submitting the halt request. + * + * Some time may elapse before the core flushes any posted requests for this + * host channel and halts. The Channel Halted interrupt handler completes the + * deactivation of the host channel. + * + * @param core_if Controller register interface. + * @param hc Host channel to halt. + * @param halt_status Reason for halting the channel. + */ +void dwc_otg_hc_halt(dwc_otg_core_if_t * core_if, + dwc_hc_t * hc, dwc_otg_halt_status_e halt_status) +{ + gnptxsts_data_t nptxsts; + hptxsts_data_t hptxsts; + hcchar_data_t hcchar; + dwc_otg_hc_regs_t *hc_regs; + dwc_otg_core_global_regs_t *global_regs; + dwc_otg_host_global_regs_t *host_global_regs; + + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + global_regs = core_if->core_global_regs; + host_global_regs = core_if->host_if->host_global_regs; + + DWC_ASSERT(!(halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS), + "halt_status = %d\n", halt_status); + + if (halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE || + halt_status == DWC_OTG_HC_XFER_AHB_ERR) { + /* + * Disable all channel interrupts except Ch Halted. The QTD + * and QH state associated with this transfer has been cleared + * (in the case of URB_DEQUEUE), so the channel needs to be + * shut down carefully to prevent crashes. + */ + hcintmsk_data_t hcintmsk; + hcintmsk.d32 = 0; + hcintmsk.b.chhltd = 1; + DWC_WRITE_REG32(&hc_regs->hcintmsk, hcintmsk.d32); + + /* + * Make sure no other interrupts besides halt are currently + * pending. Handling another interrupt could cause a crash due + * to the QTD and QH state. + */ + DWC_WRITE_REG32(&hc_regs->hcint, ~hcintmsk.d32); + + /* + * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR + * even if the channel was already halted for some other + * reason. + */ + hc->halt_status = halt_status; + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen == 0) { + /* + * The channel is either already halted or it hasn't + * started yet. In DMA mode, the transfer may halt if + * it finishes normally or a condition occurs that + * requires driver intervention. Don't want to halt + * the channel again. In either Slave or DMA mode, + * it's possible that the transfer has been assigned + * to a channel, but not started yet when an URB is + * dequeued. Don't want to halt a channel that hasn't + * started yet. + */ + return; + } + } + if (hc->halt_pending) { + /* + * A halt has already been issued for this channel. This might + * happen when a transfer is aborted by a higher level in + * the stack. + */ +#ifdef DEBUG + DWC_PRINTF + ("*** %s: Channel %d, _hc->halt_pending already set ***\n", + __func__, hc->hc_num); + +#endif + return; + } + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* No need to set the bit in DDMA for disabling the channel */ + //TODO check it everywhere channel is disabled + if (!core_if->core_params->dma_desc_enable) + hcchar.b.chen = 1; + hcchar.b.chdis = 1; + + if (!core_if->dma_enable) { + /* Check for space in the request queue to issue the halt. */ + if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || + hc->ep_type == DWC_OTG_EP_TYPE_BULK) { + nptxsts.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + if (nptxsts.b.nptxqspcavail == 0) { + hcchar.b.chen = 0; + } + } else { + hptxsts.d32 = + DWC_READ_REG32(&host_global_regs->hptxsts); + if ((hptxsts.b.ptxqspcavail == 0) + || (core_if->queuing_high_bandwidth)) { + hcchar.b.chen = 0; + } + } + } + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + hc->halt_status = halt_status; + + if (hcchar.b.chen) { + hc->halt_pending = 1; + hc->halt_on_queue = 0; + } else { + hc->halt_on_queue = 1; + } + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " hcchar: 0x%08x\n", hcchar.d32); + DWC_DEBUGPL(DBG_HCDV, " halt_pending: %d\n", hc->halt_pending); + DWC_DEBUGPL(DBG_HCDV, " halt_on_queue: %d\n", hc->halt_on_queue); + DWC_DEBUGPL(DBG_HCDV, " halt_status: %d\n", hc->halt_status); + + return; +} + +/** + * Clears the transfer state for a host channel. This function is normally + * called after a transfer is done and the host channel is being released. + * + * @param core_if Programming view of DWC_otg controller. + * @param hc Identifies the host channel to clean up. + */ +void dwc_otg_hc_cleanup(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + dwc_otg_hc_regs_t *hc_regs; + + hc->xfer_started = 0; + + /* + * Clear channel interrupt enables and any unhandled channel interrupt + * conditions. + */ + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0); + DWC_WRITE_REG32(&hc_regs->hcint, 0xFFFFFFFF); +#ifdef DEBUG + DWC_TIMER_CANCEL(core_if->hc_xfer_timer[hc->hc_num]); +#endif +} + +/** + * Sets the channel property that indicates in which frame a periodic transfer + * should occur. This is always set to the _next_ frame. This function has no + * effect on non-periodic transfers. + * + * @param core_if Programming view of DWC_otg controller. + * @param hc Identifies the host channel to set up and its properties. + * @param hcchar Current value of the HCCHAR register for the specified host + * channel. + */ +static inline void hc_set_even_odd_frame(dwc_otg_core_if_t * core_if, + dwc_hc_t * hc, hcchar_data_t * hcchar) +{ + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + hfnum_data_t hfnum; + hfnum.d32 = + DWC_READ_REG32(&core_if->host_if->host_global_regs->hfnum); + + /* 1 if _next_ frame is odd, 0 if it's even */ + hcchar->b.oddfrm = (hfnum.b.frnum & 0x1) ? 0 : 1; +#ifdef DEBUG + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR && hc->do_split + && !hc->complete_split) { + switch (hfnum.b.frnum & 0x7) { + case 7: + core_if->hfnum_7_samples++; + core_if->hfnum_7_frrem_accum += hfnum.b.frrem; + break; + case 0: + core_if->hfnum_0_samples++; + core_if->hfnum_0_frrem_accum += hfnum.b.frrem; + break; + default: + core_if->hfnum_other_samples++; + core_if->hfnum_other_frrem_accum += + hfnum.b.frrem; + break; + } + } +#endif + } +} + +#ifdef DEBUG +void hc_xfer_timeout(void *ptr) +{ + hc_xfer_info_t *xfer_info = NULL; + int hc_num = 0; + + if (ptr) + xfer_info = (hc_xfer_info_t *) ptr; + + if (!xfer_info || !xfer_info->hc) { + DWC_ERROR("xfer_info->hc = %p\n", xfer_info->hc); + return; + } + + hc_num = xfer_info->hc->hc_num; + DWC_WARN("%s: timeout on channel %d\n", __func__, hc_num); + DWC_WARN(" start_hcchar_val 0x%08x\n", + xfer_info->core_if->start_hcchar_val[hc_num]); +} +#endif + +void ep_xfer_timeout(void *ptr) +{ + ep_xfer_info_t *xfer_info = NULL; + int ep_num = 0; + dctl_data_t dctl = {.d32 = 0 }; + gintsts_data_t gintsts = {.d32 = 0 }; + gintmsk_data_t gintmsk = {.d32 = 0 }; + + if (ptr) + xfer_info = (ep_xfer_info_t *) ptr; + + if (!xfer_info->ep) { + DWC_ERROR("xfer_info->ep = %p\n", xfer_info->ep); + return; + } + + ep_num = xfer_info->ep->num; + + /* Put the sate to 2 as it was time outed */ + xfer_info->state = 2; + + dctl.d32 = + DWC_READ_REG32(&xfer_info->core_if->dev_if->dev_global_regs->dctl); + gintsts.d32 = + DWC_READ_REG32(&xfer_info->core_if->core_global_regs->gintsts); + gintmsk.d32 = + DWC_READ_REG32(&xfer_info->core_if->core_global_regs->gintmsk); + + if (!gintmsk.b.goutnakeff) { + /* Unmask it */ + gintmsk.b.goutnakeff = 1; + DWC_WRITE_REG32(&xfer_info->core_if->core_global_regs->gintmsk, + gintmsk.d32); + + } + + if (!gintsts.b.goutnakeff) { + dctl.b.sgoutnak = 1; + } + DWC_WRITE_REG32(&xfer_info->core_if->dev_if->dev_global_regs->dctl, + dctl.d32); + +} + +void set_pid_isoc(dwc_hc_t * hc) +{ + /* Set up the initial PID for the transfer. */ + if (hc->speed == DWC_OTG_EP_SPEED_HIGH) { + if (hc->ep_is_in) { + if (hc->multi_count == 1) { + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + } else if (hc->multi_count == 2) { + hc->data_pid_start = DWC_OTG_HC_PID_DATA1; + } else { + hc->data_pid_start = DWC_OTG_HC_PID_DATA2; + } + } else { + if (hc->multi_count == 1) { + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + } else { + hc->data_pid_start = DWC_OTG_HC_PID_MDATA; + } + } + } else { + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + } +} + +/** + * This function does the setup for a data transfer for a host channel and + * starts the transfer. May be called in either Slave mode or DMA mode. In + * Slave mode, the caller must ensure that there is sufficient space in the + * request queue and Tx Data FIFO. + * + * For an OUT transfer in Slave mode, it loads a data packet into the + * appropriate FIFO. If necessary, additional data packets will be loaded in + * the Host ISR. + * + * For an IN transfer in Slave mode, a data packet is requested. The data + * packets are unloaded from the Rx FIFO in the Host ISR. If necessary, + * additional data packets are requested in the Host ISR. + * + * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ + * register along with a packet count of 1 and the channel is enabled. This + * causes a single PING transaction to occur. Other fields in HCTSIZ are + * simply set to 0 since no data transfer occurs in this case. + * + * For a PING transfer in DMA mode, the HCTSIZ register is initialized with + * all the information required to perform the subsequent data transfer. In + * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the + * controller performs the entire PING protocol, then starts the data + * transfer. + * + * @param core_if Programming view of DWC_otg controller. + * @param hc Information needed to initialize the host channel. The xfer_len + * value may be reduced to accommodate the max widths of the XferSize and + * PktCnt fields in the HCTSIZn register. The multi_count value may be changed + * to reflect the final xfer_len value. + */ +void dwc_otg_hc_start_transfer(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + uint16_t num_packets; + uint32_t max_hc_xfer_size = core_if->core_params->max_transfer_size; + uint16_t max_hc_pkt_count = core_if->core_params->max_packet_count; + dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + + hctsiz.d32 = 0; + + if (hc->do_ping) { + if (!core_if->dma_enable) { + dwc_otg_hc_do_ping(core_if, hc); + hc->xfer_started = 1; + return; + } else { + hctsiz.b.dopng = 1; + } + } + + if (hc->do_split) { + num_packets = 1; + + if (hc->complete_split && !hc->ep_is_in) { + /* For CSPLIT OUT Transfer, set the size to 0 so the + * core doesn't expect any data written to the FIFO */ + hc->xfer_len = 0; + } else if (hc->ep_is_in || (hc->xfer_len > hc->max_packet)) { + hc->xfer_len = hc->max_packet; + } else if (!hc->ep_is_in && (hc->xfer_len > 188)) { + hc->xfer_len = 188; + } + + hctsiz.b.xfersize = hc->xfer_len; + } else { + /* + * Ensure that the transfer length and packet count will fit + * in the widths allocated for them in the HCTSIZn register. + */ + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * Make sure the transfer size is no larger than one + * (micro)frame's worth of data. (A check was done + * when the periodic transfer was accepted to ensure + * that a (micro)frame's worth of data can be + * programmed into a channel.) + */ + uint32_t max_periodic_len = + hc->multi_count * hc->max_packet; + if (hc->xfer_len > max_periodic_len) { + hc->xfer_len = max_periodic_len; + } else { + } + } else if (hc->xfer_len > max_hc_xfer_size) { + /* Make sure that xfer_len is a multiple of max packet size. */ + hc->xfer_len = max_hc_xfer_size - hc->max_packet + 1; + } + + if (hc->xfer_len > 0) { + num_packets = + (hc->xfer_len + hc->max_packet - + 1) / hc->max_packet; + if (num_packets > max_hc_pkt_count) { + num_packets = max_hc_pkt_count; + hc->xfer_len = num_packets * hc->max_packet; + } + } else { + /* Need 1 packet for transfer length of 0. */ + num_packets = 1; + } + + if (hc->ep_is_in) { + /* Always program an integral # of max packets for IN transfers. */ + hc->xfer_len = num_packets * hc->max_packet; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * Make sure that the multi_count field matches the + * actual transfer length. + */ + hc->multi_count = num_packets; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + set_pid_isoc(hc); + + hctsiz.b.xfersize = hc->xfer_len; + } + + hc->start_pkt_count = num_packets; + hctsiz.b.pktcnt = num_packets; + hctsiz.b.pid = hc->data_pid_start; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " Xfer Size: %d\n", hctsiz.b.xfersize); + DWC_DEBUGPL(DBG_HCDV, " Num Pkts: %d\n", hctsiz.b.pktcnt); + DWC_DEBUGPL(DBG_HCDV, " Start PID: %d\n", hctsiz.b.pid); + + if (core_if->dma_enable) { + dwc_dma_t dma_addr; + if (hc->align_buff) { + dma_addr = hc->align_buff; + } else { + dma_addr = ((unsigned long)hc->xfer_buff & 0xffffffff); + } + DWC_WRITE_REG32(&hc_regs->hcdma, dma_addr); + } + + /* Start the split */ + if (hc->do_split) { + hcsplt_data_t hcsplt; + hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt); + hcsplt.b.spltena = 1; + DWC_WRITE_REG32(&hc_regs->hcsplt, hcsplt.d32); + } + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.multicnt = hc->multi_count; + hc_set_even_odd_frame(core_if, hc, &hcchar); +#ifdef DEBUG + core_if->start_hcchar_val[hc->hc_num] = hcchar.d32; + if (hcchar.b.chdis) { + DWC_WARN("%s: chdis set, channel %d, hcchar 0x%08x\n", + __func__, hc->hc_num, hcchar.d32); + } +#endif + + /* Set host channel enable after all other setup is complete. */ + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + hc->xfer_started = 1; + hc->requests++; + + if (!core_if->dma_enable && !hc->ep_is_in && hc->xfer_len > 0) { + /* Load OUT packet into the appropriate Tx FIFO. */ + dwc_otg_hc_write_packet(core_if, hc); + } +#ifdef DEBUG + if (hc->ep_type != DWC_OTG_EP_TYPE_INTR) { + core_if->hc_xfer_info[hc->hc_num].core_if = core_if; + core_if->hc_xfer_info[hc->hc_num].hc = hc; + + /* Start a timer for this transfer. */ + DWC_TIMER_SCHEDULE(core_if->hc_xfer_timer[hc->hc_num], 10000); + } +#endif +} + +/** + * This function does the setup for a data transfer for a host channel + * and starts the transfer in Descriptor DMA mode. + * + * Initializes HCTSIZ register. For a PING transfer the Do Ping bit is set. + * Sets PID and NTD values. For periodic transfers + * initializes SCHED_INFO field with micro-frame bitmap. + * + * Initializes HCDMA register with descriptor list address and CTD value + * then starts the transfer via enabling the channel. + * + * @param core_if Programming view of DWC_otg controller. + * @param hc Information needed to initialize the host channel. + */ +void dwc_otg_hc_start_transfer_ddma(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + hcdma_data_t hcdma; + + hctsiz.d32 = 0; + + if (hc->do_ping) + hctsiz.b_ddma.dopng = 1; + + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + set_pid_isoc(hc); + + /* Packet Count and Xfer Size are not used in Descriptor DMA mode */ + hctsiz.b_ddma.pid = hc->data_pid_start; + hctsiz.b_ddma.ntd = hc->ntd - 1; /* 0 - 1 descriptor, 1 - 2 descriptors, etc. */ + hctsiz.b_ddma.schinfo = hc->schinfo; /* Non-zero only for high-speed interrupt endpoints */ + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " Start PID: %d\n", hctsiz.b.pid); + DWC_DEBUGPL(DBG_HCDV, " NTD: %d\n", hctsiz.b_ddma.ntd); + + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + hcdma.d32 = 0; + hcdma.b.dma_addr = ((uint32_t) hc->desc_list_addr) >> 11; + + /* Always start from first descriptor. */ + hcdma.b.ctd = 0; + DWC_WRITE_REG32(&hc_regs->hcdma, hcdma.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.multicnt = hc->multi_count; + +#ifdef DEBUG + core_if->start_hcchar_val[hc->hc_num] = hcchar.d32; + if (hcchar.b.chdis) { + DWC_WARN("%s: chdis set, channel %d, hcchar 0x%08x\n", + __func__, hc->hc_num, hcchar.d32); + } +#endif + + /* Set host channel enable after all other setup is complete. */ + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + hc->xfer_started = 1; + hc->requests++; + +#ifdef DEBUG + if ((hc->ep_type != DWC_OTG_EP_TYPE_INTR) + && (hc->ep_type != DWC_OTG_EP_TYPE_ISOC)) { + core_if->hc_xfer_info[hc->hc_num].core_if = core_if; + core_if->hc_xfer_info[hc->hc_num].hc = hc; + /* Start a timer for this transfer. */ + DWC_TIMER_SCHEDULE(core_if->hc_xfer_timer[hc->hc_num], 10000); + } +#endif + +} + +/** + * This function continues a data transfer that was started by previous call + * to dwc_otg_hc_start_transfer. The caller must ensure there is + * sufficient space in the request queue and Tx Data FIFO. This function + * should only be called in Slave mode. In DMA mode, the controller acts + * autonomously to complete transfers programmed to a host channel. + * + * For an OUT transfer, a new data packet is loaded into the appropriate FIFO + * if there is any data remaining to be queued. For an IN transfer, another + * data packet is always requested. For the SETUP phase of a control transfer, + * this function does nothing. + * + * @return 1 if a new request is queued, 0 if no more requests are required + * for this transfer. + */ +int dwc_otg_hc_continue_transfer(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + + if (hc->do_split) { + /* SPLITs always queue just once per channel */ + return 0; + } else if (hc->data_pid_start == DWC_OTG_HC_PID_SETUP) { + /* SETUPs are queued only once since they can't be NAKed. */ + return 0; + } else if (hc->ep_is_in) { + /* + * Always queue another request for other IN transfers. If + * back-to-back INs are issued and NAKs are received for both, + * the driver may still be processing the first NAK when the + * second NAK is received. When the interrupt handler clears + * the NAK interrupt for the first NAK, the second NAK will + * not be seen. So we can't depend on the NAK interrupt + * handler to requeue a NAKed request. Instead, IN requests + * are issued each time this function is called. When the + * transfer completes, the extra requests for the channel will + * be flushed. + */ + hcchar_data_t hcchar; + dwc_otg_hc_regs_t *hc_regs = + core_if->host_if->hc_regs[hc->hc_num]; + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hc_set_even_odd_frame(core_if, hc, &hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + DWC_DEBUGPL(DBG_HCDV, " IN xfer: hcchar = 0x%08x\n", + hcchar.d32); + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + hc->requests++; + return 1; + } else { + /* OUT transfers. */ + if (hc->xfer_count < hc->xfer_len) { + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + hcchar_data_t hcchar; + dwc_otg_hc_regs_t *hc_regs; + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hc_set_even_odd_frame(core_if, hc, &hcchar); + } + + /* Load OUT packet into the appropriate Tx FIFO. */ + dwc_otg_hc_write_packet(core_if, hc); + hc->requests++; + return 1; + } else { + return 0; + } + } +} + +/** + * Starts a PING transfer. This function should only be called in Slave mode. + * The Do Ping bit is set in the HCTSIZ register, then the channel is enabled. + */ +void dwc_otg_hc_do_ping(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + + hctsiz.d32 = 0; + hctsiz.b.dopng = 1; + hctsiz.b.pktcnt = 1; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); +} + +/* + * This function writes a packet into the Tx FIFO associated with the Host + * Channel. For a channel associated with a non-periodic EP, the non-periodic + * Tx FIFO is written. For a channel associated with a periodic EP, the + * periodic Tx FIFO is written. This function should only be called in Slave + * mode. + * + * Upon return the xfer_buff and xfer_count fields in _hc are incremented by + * then number of bytes written to the Tx FIFO. + */ +void dwc_otg_hc_write_packet(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + uint32_t i; + uint32_t remaining_count; + uint32_t byte_count; + uint32_t dword_count; + + uint32_t *data_buff = (uint32_t *) (hc->xfer_buff); + uint32_t *data_fifo = core_if->data_fifo[hc->hc_num]; + + remaining_count = hc->xfer_len - hc->xfer_count; + if (remaining_count > hc->max_packet) { + byte_count = hc->max_packet; + } else { + byte_count = remaining_count; + } + + dword_count = (byte_count + 3) / 4; + + if ((((unsigned long)data_buff) & 0x3) == 0) { + /* xfer_buff is DWORD aligned. */ + for (i = 0; i < dword_count; i++, data_buff++) { + DWC_WRITE_REG32(data_fifo, *data_buff); + } + } else { + /* xfer_buff is not DWORD aligned. */ + for (i = 0; i < dword_count; i++, data_buff++) { + uint32_t data; + data = + (data_buff[0] | data_buff[1] << 8 | data_buff[2] << + 16 | data_buff[3] << 24); + DWC_WRITE_REG32(data_fifo, data); + } + } + + hc->xfer_count += byte_count; + hc->xfer_buff += byte_count; +} + +/** + * Gets the current USB frame number. This is the frame number from the last + * SOF packet. + */ +uint32_t dwc_otg_get_frame_number(dwc_otg_core_if_t * core_if) +{ + dsts_data_t dsts; + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + /* read current frame/microframe number from DSTS register */ + return dsts.b.soffn; +} + +/** + * Calculates and gets the frame Interval value of HFIR register according PHY + * type and speed.The application can modify a value of HFIR register only after + * the Port Enable bit of the Host Port Control and Status register + * (HPRT.PrtEnaPort) has been set. +*/ + +uint32_t calc_frame_interval(dwc_otg_core_if_t * core_if) +{ + gusbcfg_data_t usbcfg; + hwcfg2_data_t hwcfg2; + hprt0_data_t hprt0; + int clock = 60; // default value + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + hwcfg2.d32 = DWC_READ_REG32(&core_if->core_global_regs->ghwcfg2); + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + if (!usbcfg.b.physel && usbcfg.b.ulpi_utmi_sel && !usbcfg.b.phyif) + clock = 60; + if (usbcfg.b.physel && hwcfg2.b.fs_phy_type == 3) + clock = 48; + if (!usbcfg.b.phylpwrclksel && !usbcfg.b.physel && + !usbcfg.b.ulpi_utmi_sel && usbcfg.b.phyif) + clock = 30; + if (!usbcfg.b.phylpwrclksel && !usbcfg.b.physel && + !usbcfg.b.ulpi_utmi_sel && !usbcfg.b.phyif) + clock = 60; + if (usbcfg.b.phylpwrclksel && !usbcfg.b.physel && + !usbcfg.b.ulpi_utmi_sel && usbcfg.b.phyif) + clock = 48; + if (usbcfg.b.physel && !usbcfg.b.phyif && hwcfg2.b.fs_phy_type == 2) + clock = 48; + if (usbcfg.b.physel && hwcfg2.b.fs_phy_type == 1) + clock = 48; + if (hprt0.b.prtspd == 0) + /* High speed case */ + return 125 * clock; + else + /* FS/LS case */ + return 1000 * clock; +} + +/** + * This function reads a setup packet from the Rx FIFO into the destination + * buffer. This function is called from the Rx Status Queue Level (RxStsQLvl) + * Interrupt routine when a SETUP packet has been received in Slave mode. + * + * @param core_if Programming view of DWC_otg controller. + * @param dest Destination buffer for packet data. + */ +void dwc_otg_read_setup_packet(dwc_otg_core_if_t * core_if, uint32_t * dest) +{ + device_grxsts_data_t status; + /* Get the 8 bytes of a setup transaction data */ + + /* Pop 2 DWORDS off the receive data FIFO into memory */ + dest[0] = DWC_READ_REG32(core_if->data_fifo[0]); + dest[1] = DWC_READ_REG32(core_if->data_fifo[0]); + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + status.d32 = + DWC_READ_REG32(&core_if->core_global_regs->grxstsp); + DWC_DEBUGPL(DBG_ANY, + "EP:%d BCnt:%d " "pktsts:%x Frame:%d(0x%0x)\n", + status.b.epnum, status.b.bcnt, status.b.pktsts, + status.b.fn, status.b.fn); + } +} + +/** + * This function enables EP0 OUT to receive SETUP packets and configures EP0 + * IN for transmitting packets. It is normally called when the + * "Enumeration Done" interrupt occurs. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP0 data. + */ +void dwc_otg_ep0_activate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dsts_data_t dsts; + depctl_data_t diepctl; + depctl_data_t doepctl; + dctl_data_t dctl = {.d32 = 0 }; + + ep->stp_rollover = 0; + /* Read the Device Status and Endpoint 0 Control registers */ + dsts.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dsts); + diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl); + doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl); + + /* Set the MPS of the IN EP based on the enumeration speed */ + switch (dsts.b.enumspd) { + case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ: + diepctl.b.mps = DWC_DEP0CTL_MPS_64; + break; + case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ: + diepctl.b.mps = DWC_DEP0CTL_MPS_8; + break; + } + + DWC_WRITE_REG32(&dev_if->in_ep_regs[0]->diepctl, diepctl.d32); + + /* Enable OUT EP for receive + if (core_if->snpsid <= OTG_CORE_REV_2_94a) { + doepctl.b.epena = 1; + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepctl, doepctl.d32); + } + */ +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "doepctl0=%0x\n", + DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl)); + DWC_DEBUGPL(DBG_PCDV, "diepctl0=%0x\n", + DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl)); +#endif + dctl.b.cgnpinnak = 1; + + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + DWC_DEBUGPL(DBG_PCDV, "dctl=%0x\n", + DWC_READ_REG32(&dev_if->dev_global_regs->dctl)); + +} + +/** + * This function activates an EP. The Device EP control register for + * the EP is configured as defined in the ep structure. Note: This + * function is not used for EP0. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to activate. + */ +void dwc_otg_ep_activate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + depctl_data_t depctl; + volatile uint32_t *addr; + daint_data_t daintmsk = {.d32 = 0 }; + //dcfg_data_t dcfg; + //uint8_t i; + + DWC_DEBUGPL(DBG_PCDV, "%s() EP%d-%s\n", __func__, ep->num, + (ep->is_in ? "IN" : "OUT")); + +#ifdef DWC_UTE_PER_IO + ep->xiso_frame_num = 0xFFFFFFFF; + ep->xiso_active_xfers = 0; + ep->xiso_queued_xfers = 0; +#endif + /* Read DEPCTLn register */ + if (ep->is_in == 1) { + addr = &dev_if->in_ep_regs[ep->num]->diepctl; + daintmsk.ep.in = 1 << ep->num; + } else { + addr = &dev_if->out_ep_regs[ep->num]->doepctl; + daintmsk.ep.out = 1 << ep->num; + } + + /* If the EP is already active don't change the EP Control + * register. */ + depctl.d32 = DWC_READ_REG32(addr); + if (!depctl.b.usbactep) { + depctl.b.mps = ep->maxpacket; + depctl.b.eptype = ep->type; + depctl.b.txfnum = ep->tx_fifo_num; + + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + depctl.b.setd0pid = 1; // ??? + } else { + depctl.b.setd0pid = 1; + } + depctl.b.usbactep = 1; + + + DWC_WRITE_REG32(addr, depctl.d32); + DWC_DEBUGPL(DBG_PCDV, "DEPCTL=%08x\n", DWC_READ_REG32(addr)); + } + + /* Enable the Interrupt for this EP */ + if (core_if->multiproc_int_enable) { + if (ep->is_in == 1) { + diepmsk_data_t diepmsk = {.d32 = 0 }; + diepmsk.b.xfercompl = 1; + diepmsk.b.timeout = 1; + diepmsk.b.epdisabled = 1; + diepmsk.b.ahberr = 1; + diepmsk.b.intknepmis = 1; + if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) + diepmsk.b.intknepmis = 0; + diepmsk.b.txfifoundrn = 1; //????? + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + diepmsk.b.nak = 1; + } + +/* + if (core_if->dma_desc_enable) { + diepmsk.b.bna = 1; + } +*/ +/* + if (core_if->dma_enable) { + doepmsk.b.nak = 1; + } +*/ + DWC_WRITE_REG32(&dev_if->dev_global_regs-> + diepeachintmsk[ep->num], diepmsk.d32); + + } else { + doepmsk_data_t doepmsk = {.d32 = 0 }; + doepmsk.b.xfercompl = 1; + doepmsk.b.ahberr = 1; + doepmsk.b.epdisabled = 1; + if (ep->type == DWC_OTG_EP_TYPE_ISOC) + doepmsk.b.outtknepdis = 1; + +/* + + if (core_if->dma_desc_enable) { + doepmsk.b.bna = 1; + } +*/ +/* + doepmsk.b.babble = 1; + doepmsk.b.nyet = 1; + doepmsk.b.nak = 1; +*/ + DWC_WRITE_REG32(&dev_if->dev_global_regs-> + doepeachintmsk[ep->num], doepmsk.d32); + } + DWC_MODIFY_REG32(&dev_if->dev_global_regs->deachintmsk, + 0, daintmsk.d32); + } else { + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (ep->is_in) { + diepmsk_data_t diepmsk = {.d32 = 0 }; + diepmsk.b.nak = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->diepmsk, 0, diepmsk.d32); + } else { + doepmsk_data_t doepmsk = {.d32 = 0 }; + doepmsk.b.outtknepdis = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->doepmsk, 0, doepmsk.d32); + } + } + DWC_MODIFY_REG32(&dev_if->dev_global_regs->daintmsk, + 0, daintmsk.d32); + } + + DWC_DEBUGPL(DBG_PCDV, "DAINTMSK=%0x\n", + DWC_READ_REG32(&dev_if->dev_global_regs->daintmsk)); + + ep->stall_clear_flag = 0; + + return; +} + +/** + * This function deactivates an EP. This is done by clearing the USB Active + * EP bit in the Device EP control register. Note: This function is not used + * for EP0. EP0 cannot be deactivated. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to deactivate. + */ +void dwc_otg_ep_deactivate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl = {.d32 = 0 }; + volatile uint32_t *addr; + daint_data_t daintmsk = {.d32 = 0 }; + //dcfg_data_t dcfg; + //uint8_t i = 0; + +#ifdef DWC_UTE_PER_IO + ep->xiso_frame_num = 0xFFFFFFFF; + ep->xiso_active_xfers = 0; + ep->xiso_queued_xfers = 0; +#endif + + /* Read DEPCTLn register */ + if (ep->is_in == 1) { + addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl; + daintmsk.ep.in = 1 << ep->num; + } else { + addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl; + daintmsk.ep.out = 1 << ep->num; + } + + depctl.d32 = DWC_READ_REG32(addr); + + //depctl.b.usbactep = 0; + depctl.b.snak = 1; + if (core_if->dma_desc_enable) + depctl.b.epdis = 1; + + DWC_WRITE_REG32(addr, depctl.d32); + depctl.d32 = DWC_READ_REG32(addr); + if (core_if->dma_enable && ep->type == DWC_OTG_EP_TYPE_ISOC + && depctl.b.epena) { + depctl_data_t depctl = {.d32 = 0 }; + if (ep->is_in) { + diepint_data_t diepint = {.d32 = 0 }; + + depctl.b.snak = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + diepctl, depctl.d32); + do { + dwc_udelay(10); + diepint.d32 = + DWC_READ_REG32(&core_if-> + dev_if->in_ep_regs[ep->num]-> + diepint); + } while (!diepint.b.inepnakeff); + diepint.b.inepnakeff = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + diepint, diepint.d32); + depctl.d32 = 0; + depctl.b.epdis = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + diepctl, depctl.d32); + do { + dwc_udelay(10); + diepint.d32 = + DWC_READ_REG32(&core_if-> + dev_if->in_ep_regs[ep->num]-> + diepint); + } while (!diepint.b.epdisabled); + diepint.b.epdisabled = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + diepint, diepint.d32); + } else { + dctl_data_t dctl = {.d32 = 0}; + gintmsk_data_t gintsts = {.d32 = 0}; + doepint_data_t doepint = {.d32 = 0}; + dctl.b.sgoutnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, 0, dctl.d32); + do { + dwc_udelay(10); + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + } while (!gintsts.b.goutnakeff); + gintsts.d32 = 0; + gintsts.b.goutnakeff = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + depctl.d32 = 0; + depctl.b.epdis = 1; + depctl.b.snak = 1; + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]->doepctl, depctl.d32); + do + { + dwc_udelay(10); + doepint.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[ep->num]->doepint); + } while (!doepint.b.epdisabled); + + doepint.b.epdisabled = 1; + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]->doepint, doepint.d32); + + dctl.d32 = 0; + dctl.b.cgoutnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + } + } + + /* Disable the Interrupt for this EP */ + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->deachintmsk, + daintmsk.d32, 0); + + if (ep->is_in == 1) { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs-> + diepeachintmsk[ep->num], 0); + } else { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[ep->num], 0); + } + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->daintmsk, + daintmsk.d32, 0); + } + +} + +/** + * This function initializes dma descriptor chain. + * Auther: Miao.Zhu@spreadtrum.com + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + */ +static void init_dma_desc_chain(dwc_otg_core_if_t * core_if, dwc_ep_t * ep, + dwc_otg_pcd_request_t *req) +{ + dwc_otg_dev_dma_desc_t *dma_desc; + uint32_t offset; + uint32_t xfer_est; + int i = 0; + unsigned maxxfer_local, bytes; + struct scatterlist *sg = NULL; + dma_addr_t dma_addr; + + if (!ep->is_in && ep->type == DWC_OTG_EP_TYPE_INTR && + (ep->maxpacket % 4)) { + maxxfer_local = ep->maxpacket; + } else { + maxxfer_local = ep->maxxfer; + } + dma_desc = ep->desc_addr; + ep->desc_cnt = 0; + if (req->num_mapped_sgs) + sg = req->sg; + + do { + if (sg) { + dma_addr = sg_dma_address(sg); + xfer_est = sg_dma_len(sg); + sg++; + } else { + dma_addr = req->dma; + xfer_est = req->length; + } + offset = 0; + if (!xfer_est) { + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 0; + dma_desc->status.b.ioc = 0; + dma_desc->status.b.sp = 1; + dma_desc->status.b.bytes = 0; + dma_desc->buf = 0; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + dma_desc++; + } + /** DMA Descriptor Setup */ + while (xfer_est) { + if (xfer_est > maxxfer_local) { + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 0; + dma_desc->status.b.ioc = 0; + dma_desc->status.b.sp = 0; + dma_desc->status.b.bytes = maxxfer_local; + dma_desc->buf = dma_addr + offset; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + + xfer_est -= maxxfer_local; + offset += maxxfer_local; + } else { + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 0; + dma_desc->status.b.ioc = 0; + /** WORKAROUND: If this buffer length is not multiple of DWORD, + ** and it wants to concatenate to next buffer, DMA would behave + ** abnormally, so it is sent as a short packet. + */ + //if( core_if->snpsid <= OTG_CORE_REV_2_94a) + dma_desc->status.b.sp = + (req->short_packet && xfer_est % ep->maxpacket) ? + 1 : (xfer_est % 4 ? 1 : 0); + dma_desc->status.b.bytes = xfer_est; + dma_desc->buf = dma_addr + offset; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + + xfer_est = 0; + } + ++dma_desc; + /** No more descriptors? */ + if( ++ep->desc_cnt > MAX_DMA_DESC_CNT) + goto end; + } + i++; + }while (i < req->num_mapped_sgs); +end: + /** the Last DMA Descriptor Setup */ + --dma_desc; + bytes = dma_desc->status.b.bytes; + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + if (ep->is_in && bytes) { + dma_desc->status.b.sp = + (bytes % ep->maxpacket) ? 1 : ((ep->sent_zlp) ? 1 : 0); + } else { + if (!(maxxfer_local == ep->maxpacket)) + dma_desc->status.b.bytes = + bytes + ((4 - (bytes & 0x3)) & 0x3); + } + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; +} + +/** + * This function is called when to write ISOC data into appropriate dedicated + * periodic FIFO. + */ +static int32_t write_isoc_tx_fifo(dwc_otg_core_if_t * core_if, dwc_ep_t * dwc_ep) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_dev_in_ep_regs_t *ep_regs; + dtxfsts_data_t txstatus = {.d32 = 0 }; + uint32_t len = 0; + int epnum = dwc_ep->num; + int dwords; + + DWC_DEBUGPL(DBG_PCD, "Dedicated TxFifo Empty: %d \n", epnum); + + ep_regs = core_if->dev_if->in_ep_regs[epnum]; + + len = dwc_ep->xfer_len - dwc_ep->xfer_count; + + if (len > dwc_ep->maxpacket) { + len = dwc_ep->maxpacket; + } + + dwords = (len + 3) / 4; + + /* While there is space in the queue and space in the FIFO and + * More data to tranfer, Write packets to the Tx FIFO */ + txstatus.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, txstatus.d32); + + while (txstatus.b.txfspcavail > dwords && + dwc_ep->xfer_count < dwc_ep->xfer_len && dwc_ep->xfer_len != 0) { + /* Write the FIFO */ + dwc_otg_ep_write_packet(core_if, dwc_ep, 0); + + len = dwc_ep->xfer_len - dwc_ep->xfer_count; + if (len > dwc_ep->maxpacket) { + len = dwc_ep->maxpacket; + } + + dwords = (len + 3) / 4; + txstatus.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", epnum, + txstatus.d32); + } + + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts)); + + return 1; +} + +/** + * This function does the setup for a data transfer for an EP and + * starts the transfer. For an IN transfer, the packets will be + * loaded into the appropriate Tx FIFO in the ISR. For OUT transfers, + * the packets are unloaded from the Rx FIFO in the ISR. the ISR. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + */ + +void dwc_otg_ep_start_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep, + dwc_otg_pcd_request_t *req) +{ + depctl_data_t depctl; + deptsiz_data_t deptsiz; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s()\n", __func__); + DWC_DEBUGPL(DBG_PCD, "ep%d-%s xfer_len=%d xfer_cnt=%d " + "xfer_buff=%p start_xfer_buff=%p, total_len = %d\n", + ep->num, (ep->is_in ? "IN" : "OUT"), ep->xfer_len, + ep->xfer_count, ep->xfer_buff, ep->start_xfer_buff, + ep->total_len); + /* IN endpoint */ + if (ep->is_in == 1) { + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[ep->num]; + + gnptxsts_data_t gtxstatus; + + gtxstatus.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gnptxsts); + + if (core_if->en_multiple_tx_fifo == 0 + && gtxstatus.b.nptxqspcavail == 0 && !core_if->dma_enable) { +#ifdef DEBUG + DWC_PRINTF("TX Queue Full (0x%0x)\n", gtxstatus.d32); +#endif + return; + } + + depctl.d32 = DWC_READ_REG32(&(in_regs->diepctl)); + deptsiz.d32 = DWC_READ_REG32(&(in_regs->dieptsiz)); + + if (ep->maxpacket > ep->maxxfer / MAX_PKT_CNT) + ep->xfer_len += (ep->maxxfer < (ep->total_len - ep->xfer_len)) ? + ep->maxxfer : (ep->total_len - ep->xfer_len); + else + ep->xfer_len += (MAX_PKT_CNT * ep->maxpacket < (ep->total_len - ep->xfer_len)) ? + MAX_PKT_CNT * ep->maxpacket : (ep->total_len - ep->xfer_len); + + + /* Zero Length Packet? */ + if ((ep->xfer_len - ep->xfer_count) == 0) { + deptsiz.b.xfersize = 0; + deptsiz.b.pktcnt = 1; + } else { + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + deptsiz.b.xfersize = ep->xfer_len - ep->xfer_count; + deptsiz.b.pktcnt = + (ep->xfer_len - ep->xfer_count - 1 + + ep->maxpacket) / ep->maxpacket; + if (ep->type == DWC_OTG_EP_TYPE_ISOC) + deptsiz.b.mc = deptsiz.b.pktcnt; + } + + /* Write the DMA register */ + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + dma_addr_t buf_dma; + if (req->dw_align_buf_dma) + buf_dma = req->dw_align_buf_dma + ep->xfer_count; + else + buf_dma = req->dma + ep->xfer_count; + if (ep->type != DWC_OTG_EP_TYPE_ISOC) + deptsiz.b.mc = 1; + DWC_WRITE_REG32(&in_regs->dieptsiz, + deptsiz.d32); + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) buf_dma); + } else { +#ifdef DWC_UTE_CFI + /* The descriptor chain should be already initialized by now */ + if (ep->buff_mode != BM_STANDARD) { + DWC_WRITE_REG32(&in_regs->diepdma, + ep->descs_dma_addr); + } else { +#endif + init_dma_desc_chain(core_if, ep, req); + /** DIEPDMAn Register write */ + DWC_WRITE_REG32(&in_regs->diepdma, + ep->dma_desc_addr); +#ifdef DWC_UTE_CFI + } +#endif + } + } else { + DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32); + if (ep->type != DWC_OTG_EP_TYPE_ISOC) { + /** + * Enable the Non-Periodic Tx FIFO empty interrupt, + * or the Tx FIFO epmty interrupt in dedicated Tx FIFO mode, + * the data will be written into the fifo by the ISR. + */ + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32 + (&core_if->core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + } else { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + if (ep->xfer_len > 0) { + uint32_t fifoemptymsk = 0; + fifoemptymsk = 1 << ep->num; + DWC_MODIFY_REG32 + (&core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + 0, fifoemptymsk); + + } + } + } else { + write_isoc_tx_fifo(core_if, ep); + } + } + + + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + dsts_data_t dsts = {.d32 = 0 }; + if (ep->bInterval == 1) { + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dsts); + ep->frame_num = dsts.b.soffn + ep->bInterval; + if (ep->frame_num > 0x3FFF) { + ep->frm_overrun = 1; + ep->frame_num &= 0x3FFF; + } else + ep->frm_overrun = 0; + if (ep->frame_num & 0x1) { + depctl.b.setd1pid = 1; + } else { + depctl.b.setd0pid = 1; + } + } + } + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32); + + } else { + /* OUT endpoint */ + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[ep->num]; + + depctl.d32 = DWC_READ_REG32(&(out_regs->doepctl)); + deptsiz.d32 = DWC_READ_REG32(&(out_regs->doeptsiz)); + + if (!core_if->dma_desc_enable) { + if (ep->maxpacket > ep->maxxfer / MAX_PKT_CNT) + ep->xfer_len += (ep->maxxfer < (ep->total_len - ep->xfer_len)) ? + ep->maxxfer : (ep->total_len - ep->xfer_len); + else + ep->xfer_len += (MAX_PKT_CNT * ep->maxpacket < (ep->total_len + - ep->xfer_len)) ? MAX_PKT_CNT * ep->maxpacket : (ep->total_len - ep->xfer_len); + } + + /* Program the transfer size and packet count as follows: + * + * pktcnt = N + * xfersize = N * maxpacket + */ + if ((ep->xfer_len - ep->xfer_count) == 0) { + /* Zero Length Packet */ + deptsiz.b.xfersize = ep->maxpacket; + deptsiz.b.pktcnt = 1; + } else { + deptsiz.b.pktcnt = + (ep->xfer_len - ep->xfer_count + + (ep->maxpacket - 1)) / ep->maxpacket; + if (!core_if->dma_desc_enable) { + ep->xfer_len = + deptsiz.b.pktcnt * ep->maxpacket + ep->xfer_count; + } + deptsiz.b.xfersize = ep->xfer_len - ep->xfer_count; + } + + DWC_DEBUGPL(DBG_PCDV, "ep%d xfersize=%d pktcnt=%d\n", + ep->num, deptsiz.b.xfersize, deptsiz.b.pktcnt); + + if (core_if->dma_enable) { + if (!core_if->dma_desc_enable) { + dma_addr_t buf_dma; + if (req->dw_align_buf_dma) + buf_dma = req->dw_align_buf_dma; + else + buf_dma = req->dma; + DWC_WRITE_REG32(&out_regs->doeptsiz, + deptsiz.d32); + + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) buf_dma); + } else { +#ifdef DWC_UTE_CFI + /* The descriptor chain should be already initialized by now */ + if (ep->buff_mode != BM_STANDARD) { + DWC_WRITE_REG32(&out_regs->doepdma, + ep->descs_dma_addr); + } else { +#endif + /** This is used for interrupt out transfers*/ + if (!ep->xfer_len) + ep->xfer_len = ep->total_len; + init_dma_desc_chain(core_if, ep, req); + + if (core_if->core_params->dev_out_nak) { + if (ep->type == DWC_OTG_EP_TYPE_BULK) { + deptsiz.b.pktcnt = (ep->total_len + + (ep->maxpacket - 1)) / ep->maxpacket; + deptsiz.b.xfersize = ep->total_len; + /* Remember initial value of doeptsiz */ + core_if->start_doeptsiz_val[ep->num] = deptsiz.d32; + DWC_WRITE_REG32(&out_regs->doeptsiz, + deptsiz.d32); + } + } + /** DOEPDMAn Register write */ + DWC_WRITE_REG32(&out_regs->doepdma, + ep->dma_desc_addr); +#ifdef DWC_UTE_CFI + } +#endif + } + } else { + DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32); + } + + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + dsts_data_t dsts = {.d32 = 0 }; + if (ep->bInterval == 1) { + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dsts); + ep->frame_num = dsts.b.soffn + ep->bInterval; + if (ep->frame_num > 0x3FFF) { + ep->frm_overrun = 1; + ep->frame_num &= 0x3FFF; + } else + ep->frm_overrun = 0; + + if (ep->frame_num & 0x1) { + depctl.b.setd1pid = 1; + } else { + depctl.b.setd0pid = 1; + } + } + } + + /* EP enable */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + + DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32); + + DWC_DEBUGPL(DBG_PCD, "DOEPCTL=%08x DOEPTSIZ=%08x\n", + DWC_READ_REG32(&out_regs->doepctl), + DWC_READ_REG32(&out_regs->doeptsiz)); + DWC_DEBUGPL(DBG_PCD, "DAINTMSK=%08x GINTMSK=%08x\n", + DWC_READ_REG32(&core_if->dev_if->dev_global_regs-> + daintmsk), + DWC_READ_REG32(&core_if->core_global_regs-> + gintmsk)); + + /* Timer is scheduling only for out bulk transfers for + * "Device DDMA OUT NAK Enhancement" feature to inform user + * about received data payload in case of timeout + */ + if (core_if->core_params->dev_out_nak) { + if (ep->type == DWC_OTG_EP_TYPE_BULK) { + core_if->ep_xfer_info[ep->num].core_if = core_if; + core_if->ep_xfer_info[ep->num].ep = ep; + core_if->ep_xfer_info[ep->num].state = 1; + + /* Start a timer for this transfer. + DWC_TIMER_SCHEDULE(core_if->ep_xfer_timer[ep->num], 10000); + */ + } + } + } +} + +/** + * This function setup a zero length transfer in Buffer DMA and + * Slave modes for usb requests with zero field set + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void dwc_otg_ep_start_zl_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + + depctl_data_t depctl; + deptsiz_data_t deptsiz; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s()\n", __func__); + + /* IN endpoint */ + if (ep->is_in == 1) { + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[ep->num]; + + depctl.d32 = DWC_READ_REG32(&(in_regs->diepctl)); + deptsiz.d32 = DWC_READ_REG32(&(in_regs->dieptsiz)); + + deptsiz.b.xfersize = 0; + deptsiz.b.pktcnt = 1; + + /* Write the DMA register */ + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + deptsiz.b.mc = 1; + DWC_WRITE_REG32(&in_regs->dieptsiz, + deptsiz.d32); + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) ep->dma_addr); + } + } else { + DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32); + /** + * Enable the Non-Periodic Tx FIFO empty interrupt, + * or the Tx FIFO epmty interrupt in dedicated Tx FIFO mode, + * the data will be written into the fifo by the ISR. + */ + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + } else { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + if (ep->xfer_len > 0) { + uint32_t fifoemptymsk = 0; + fifoemptymsk = 1 << ep->num; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + 0, fifoemptymsk); + } + } + } + + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32); + + } else { + /* OUT endpoint */ + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[ep->num]; + + depctl.d32 = DWC_READ_REG32(&(out_regs->doepctl)); + deptsiz.d32 = DWC_READ_REG32(&(out_regs->doeptsiz)); + + /* Zero Length Packet */ + deptsiz.b.xfersize = ep->maxpacket; + deptsiz.b.pktcnt = 1; + + if (core_if->dma_enable) { + if (!core_if->dma_desc_enable) { + DWC_WRITE_REG32(&out_regs->doeptsiz, + deptsiz.d32); + + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) ep->dma_addr); + } + } else { + DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32); + } + + /* EP enable */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + + DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32); + + } +} + +/** + * This function does the setup for a data transfer for EP0 and starts + * the transfer. For an IN transfer, the packets will be loaded into + * the appropriate Tx FIFO in the ISR. For OUT transfers, the packets are + * unloaded from the Rx FIFO in the ISR. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP0 data. + */ +void dwc_otg_ep0_start_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + deptsiz0_data_t deptsiz; + gintmsk_data_t intr_mask = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc; + + DWC_DEBUGPL(DBG_PCD, "ep%d-%s xfer_len=%d xfer_cnt=%d " + "xfer_buff=%p start_xfer_buff=%p \n", + ep->num, (ep->is_in ? "IN" : "OUT"), ep->xfer_len, + ep->xfer_count, ep->xfer_buff, ep->start_xfer_buff); + + ep->total_len = ep->xfer_len; + + /* IN endpoint */ + if (ep->is_in == 1) { + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[0]; + + gnptxsts_data_t gtxstatus; + + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + depctl.d32 = DWC_READ_REG32(&in_regs->diepctl); + if (depctl.b.epena) + return; + } + + gtxstatus.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gnptxsts); + + /* If dedicated FIFO every time flush fifo before enable ep*/ + if (core_if->en_multiple_tx_fifo && core_if->snpsid >= OTG_CORE_REV_3_00a) + dwc_otg_flush_tx_fifo(core_if, ep->tx_fifo_num); + + if (core_if->en_multiple_tx_fifo == 0 + && gtxstatus.b.nptxqspcavail == 0 + && !core_if->dma_enable) { +#ifdef DEBUG + deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz); + DWC_DEBUGPL(DBG_PCD, "DIEPCTL0=%0x\n", + DWC_READ_REG32(&in_regs->diepctl)); + DWC_DEBUGPL(DBG_PCD, "DIEPTSIZ0=%0x (sz=%d, pcnt=%d)\n", + deptsiz.d32, + deptsiz.b.xfersize, deptsiz.b.pktcnt); + DWC_PRINTF("TX Queue or FIFO Full (0x%0x)\n", + gtxstatus.d32); +#endif + return; + } + + depctl.d32 = DWC_READ_REG32(&in_regs->diepctl); + deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz); + + /* Zero Length Packet? */ + if (ep->xfer_len == 0) { + deptsiz.b.xfersize = 0; + deptsiz.b.pktcnt = 1; + } else { + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + if (ep->xfer_len > ep->maxpacket) { + ep->xfer_len = ep->maxpacket; + deptsiz.b.xfersize = ep->maxpacket; + } else { + deptsiz.b.xfersize = ep->xfer_len; + } + deptsiz.b.pktcnt = 1; + + } + DWC_DEBUGPL(DBG_PCDV, + "IN len=%d xfersize=%d pktcnt=%d [%08x]\n", + ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt, + deptsiz.d32); + + /* Write the DMA register */ + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + DWC_WRITE_REG32(&in_regs->dieptsiz, + deptsiz.d32); + + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) ep->dma_addr); + } else { + ep->desc_cnt = 0; + dma_desc = core_if->dev_if->in_desc_addr; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.sp = + (ep->xfer_len == ep->maxpacket) ? 0 : 1; + dma_desc->status.b.bytes = ep->xfer_len; + dma_desc->buf = ep->xfer_len ? ep->dma_addr : 0; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + ep->desc_cnt = 1; + ep->desc_addr = dma_desc; + ep->dma_desc_addr = core_if->dev_if->dma_in_desc_addr; + /** DIEPDMA0 Register write */ + DWC_WRITE_REG32(&in_regs->diepdma, + core_if->dev_if->dma_in_desc_addr); + } + } else { + DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32); + } + + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32); + + /** + * Enable the Non-Periodic Tx FIFO empty interrupt, the + * data will be written into the fifo by the ISR. + */ + if (!core_if->dma_enable) { + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + } else { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + if (ep->xfer_len > 0) { + uint32_t fifoemptymsk = 0; + fifoemptymsk |= 1 << ep->num; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + 0, fifoemptymsk); + } + } + } + } else { + /* OUT endpoint */ + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[0]; + + depctl.d32 = DWC_READ_REG32(&out_regs->doepctl); + deptsiz.d32 = DWC_READ_REG32(&out_regs->doeptsiz); + + /* Program the transfer size and packet count as follows: + * xfersize = N * (maxpacket + 4 - (maxpacket % 4)) + * pktcnt = N */ + /* Zero Length Packet */ + deptsiz.b.xfersize = ep->maxpacket; + deptsiz.b.pktcnt = 1; + if (core_if->snpsid >= OTG_CORE_REV_3_00a) + deptsiz.b.supcnt = 3; + + DWC_DEBUGPL(DBG_PCDV, "len=%d xfersize=%d pktcnt=%d\n", + ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt); + + if (core_if->dma_enable) { + if (!core_if->dma_desc_enable) { + DWC_WRITE_REG32(&out_regs->doeptsiz, + deptsiz.d32); + + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) ep->dma_addr); + } else { + ep->desc_cnt = 0; + dma_desc = core_if->dev_if->out_desc_addr; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + dma_desc->status.b.mtrf = 0; + dma_desc->status.b.sr = 0; + } + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.bytes = ep->maxpacket; + dma_desc->buf = ep->dma_addr; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + ep->desc_cnt = 1; + ep->desc_addr = dma_desc; + ep->dma_desc_addr = core_if->dev_if->dma_out_desc_addr; + /** DOEPDMA0 Register write */ + DWC_WRITE_REG32(&out_regs->doepdma, + core_if->dev_if->dma_out_desc_addr); + } + } else { + DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32); + } + + /* EP enable */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&(out_regs->doepctl), depctl.d32); + } +} + +/** + * This function continues control IN transfers started by + * dwc_otg_ep0_start_transfer, when the transfer does not fit in a + * single packet. NOTE: The DIEPCTL0/DOEPCTL0 registers only have one + * bit for the packet count. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP0 data. + */ +void dwc_otg_ep0_continue_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + deptsiz0_data_t deptsiz; + gintmsk_data_t intr_mask = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc; + + if (ep->is_in == 1) { + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[0]; + gnptxsts_data_t tx_status = {.d32 = 0 }; + + tx_status.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gnptxsts); + /** @todo Should there be check for room in the Tx + * Status Queue. If not remove the code above this comment. */ + + depctl.d32 = DWC_READ_REG32(&in_regs->diepctl); + deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz); + + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + + if (core_if->dma_desc_enable == 0) { + deptsiz.b.xfersize = + (ep->total_len - ep->xfer_count) > + ep->maxpacket ? ep->maxpacket : (ep->total_len - + ep->xfer_count); + deptsiz.b.pktcnt = 1; + if (core_if->dma_enable == 0) { + ep->xfer_len += deptsiz.b.xfersize; + } else { + ep->xfer_len = deptsiz.b.xfersize; + } + DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32); + } else { + ep->xfer_len = + (ep->total_len - ep->xfer_count) > + ep->maxpacket ? ep->maxpacket : (ep->total_len - + ep->xfer_count); + ep->desc_cnt = 0; + dma_desc = core_if->dev_if->in_desc_addr; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.sp = + (ep->xfer_len == ep->maxpacket) ? 0 : 1; + dma_desc->status.b.bytes = ep->xfer_len; + dma_desc->buf = ep->dma_addr; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + ep->desc_cnt = 1; + ep->desc_addr = dma_desc; + ep->dma_desc_addr = core_if->dev_if->dma_in_desc_addr; + /** DIEPDMA0 Register write */ + DWC_WRITE_REG32(&in_regs->diepdma, + core_if->dev_if->dma_in_desc_addr); + } + + DWC_DEBUGPL(DBG_PCDV, + "IN len=%d xfersize=%d pktcnt=%d [%08x]\n", + ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt, + deptsiz.d32); + + /* Write the DMA register */ + if (core_if->hwcfg2.b.architecture == DWC_INT_DMA_ARCH) { + if (core_if->dma_desc_enable == 0) + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) ep->dma_addr); + } + + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32); + + /** + * Enable the Non-Periodic Tx FIFO empty interrupt, the + * data will be written into the fifo by the ISR. + */ + if (!core_if->dma_enable) { + if (core_if->en_multiple_tx_fifo == 0) { + /* First clear it from GINTSTS */ + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + + } else { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + if (ep->xfer_len > 0) { + uint32_t fifoemptymsk = 0; + fifoemptymsk |= 1 << ep->num; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + 0, fifoemptymsk); + } + } + } + } else { + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[0]; + + depctl.d32 = DWC_READ_REG32(&out_regs->doepctl); + deptsiz.d32 = DWC_READ_REG32(&out_regs->doeptsiz); + + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + deptsiz.b.xfersize = ep->maxpacket; + deptsiz.b.pktcnt = 1; + + if (core_if->dma_desc_enable == 0) { + DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32); + } else { + dma_desc = core_if->dev_if->out_desc_addr; + ep->desc_cnt = 0; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.bytes = ep->maxpacket; + dma_desc->buf = ep->dma_addr; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + ep->desc_cnt = 1; + ep->desc_addr = dma_desc; + ep->dma_desc_addr = core_if->dev_if->dma_out_desc_addr; + /** DOEPDMA0 Register write */ + DWC_WRITE_REG32(&out_regs->doepdma, + core_if->dev_if->dma_out_desc_addr); + } + + DWC_DEBUGPL(DBG_PCDV, + "IN len=%d xfersize=%d pktcnt=%d [%08x]\n", + ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt, + deptsiz.d32); + + /* Write the DMA register */ + if (core_if->hwcfg2.b.architecture == DWC_INT_DMA_ARCH) { + if (core_if->dma_desc_enable == 0) + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) ep->dma_addr); + + } + + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32); + + } +} + +#ifdef DEBUG +void dump_msg(const u8 * buf, unsigned int length) +{ + unsigned int start, num, i; + char line[52], *p; + + if (length >= 512) + return; + start = 0; + while (length > 0) { + num = length < 16u ? length : 16u; + p = line; + for (i = 0; i < num; ++i) { + if (i == 8) + *p++ = ' '; + DWC_SPRINTF(p, " %02x", buf[i]); + p += 3; + } + *p = 0; + DWC_PRINTF("%6x: %s\n", start, line); + buf += num; + start += num; + length -= num; + } +} +#else +static inline void dump_msg(const u8 * buf, unsigned int length) +{ +} +#endif + +/** + * This function writes a packet into the Tx FIFO associated with the + * EP. For non-periodic EPs the non-periodic Tx FIFO is written. For + * periodic EPs the periodic Tx FIFO associated with the EP is written + * with all packets for the next micro-frame. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to write packet for. + * @param dma Indicates if DMA is being used. + */ +void dwc_otg_ep_write_packet(dwc_otg_core_if_t * core_if, dwc_ep_t * ep, + int dma) +{ + /** + * The buffer is padded to DWORD on a per packet basis in + * slave/dma mode if the MPS is not DWORD aligned. The last + * packet, if short, is also padded to a multiple of DWORD. + * + * ep->xfer_buff always starts DWORD aligned in memory and is a + * multiple of DWORD in length + * + * ep->xfer_len can be any number of bytes + * + * ep->xfer_count is a multiple of ep->maxpacket until the last + * packet + * + * FIFO access is DWORD */ + + uint32_t i; + uint32_t byte_count; + uint32_t dword_count; + uint32_t *fifo; + uint32_t *data_buff = (uint32_t *) ep->xfer_buff; + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s(%p,%p)\n", __func__, core_if, + ep); + if (ep->xfer_count >= ep->xfer_len) { + DWC_WARN("%s() No data for EP%d!!!\n", __func__, ep->num); + return; + } + + /* Find the byte length of the packet either short packet or MPS */ + if ((ep->xfer_len - ep->xfer_count) < ep->maxpacket) { + byte_count = ep->xfer_len - ep->xfer_count; + } else { + byte_count = ep->maxpacket; + } + + /* Find the DWORD length, padded by extra bytes as neccessary if MPS + * is not a multiple of DWORD */ + dword_count = (byte_count + 3) / 4; + +#ifdef VERBOSE + dump_msg(ep->xfer_buff, byte_count); +#endif + + /**@todo NGS Where are the Periodic Tx FIFO addresses + * intialized? What should this be? */ + + fifo = core_if->data_fifo[ep->num]; + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "fifo=%p buff=%p *p=%08x bc=%d\n", + fifo, data_buff, *data_buff, byte_count); + + if (!dma) { + for (i = 0; i < dword_count; i++, data_buff++) { + DWC_WRITE_REG32(fifo, *data_buff); + } + } + + ep->xfer_count += byte_count; + ep->xfer_buff += byte_count; + ep->dma_addr += byte_count; +} + +/** + * Set the EP STALL. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to set the stall on. + */ +void dwc_otg_ep_set_stall(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + volatile uint32_t *depctl_addr; + + DWC_DEBUGPL(DBG_PCD, "%s ep%d-%s\n", __func__, ep->num, + (ep->is_in ? "IN" : "OUT")); + + if (ep->is_in == 1) { + depctl_addr = &(core_if->dev_if->in_ep_regs[ep->num]->diepctl); + depctl.d32 = DWC_READ_REG32(depctl_addr); + + /* set the disable and stall bits */ + if (depctl.b.epena) { + depctl.b.epdis = 1; + } + depctl.b.stall = 1; + DWC_WRITE_REG32(depctl_addr, depctl.d32); + } else { + depctl_addr = &(core_if->dev_if->out_ep_regs[ep->num]->doepctl); + depctl.d32 = DWC_READ_REG32(depctl_addr); + + /* set the stall bit */ + depctl.b.stall = 1; + DWC_WRITE_REG32(depctl_addr, depctl.d32); + } + + DWC_DEBUGPL(DBG_PCD, "DEPCTL=%0x\n", DWC_READ_REG32(depctl_addr)); + + return; +} + +/** + * Clear the EP STALL. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to clear stall from. + */ +void dwc_otg_ep_clear_stall(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + volatile uint32_t *depctl_addr; + + DWC_DEBUGPL(DBG_PCD, "%s ep%d-%s\n", __func__, ep->num, + (ep->is_in ? "IN" : "OUT")); + + if (ep->is_in == 1) { + depctl_addr = &(core_if->dev_if->in_ep_regs[ep->num]->diepctl); + } else { + depctl_addr = &(core_if->dev_if->out_ep_regs[ep->num]->doepctl); + } + + depctl.d32 = DWC_READ_REG32(depctl_addr); + + /* clear the stall bits */ + depctl.b.stall = 0; + + /* + * USB Spec 9.4.5: For endpoints using data toggle, regardless + * of whether an endpoint has the Halt feature set, a + * ClearFeature(ENDPOINT_HALT) request always results in the + * data toggle being reinitialized to DATA0. + */ + if (ep->type == DWC_OTG_EP_TYPE_INTR || + ep->type == DWC_OTG_EP_TYPE_BULK) { + depctl.b.setd0pid = 1; /* DATA0 */ + } + + DWC_WRITE_REG32(depctl_addr, depctl.d32); + DWC_DEBUGPL(DBG_PCD, "DEPCTL=%0x\n", DWC_READ_REG32(depctl_addr)); + return; +} + +/** + * This function reads a packet from the Rx FIFO into the destination + * buffer. To read SETUP data use dwc_otg_read_setup_packet. + * + * @param core_if Programming view of DWC_otg controller. + * @param dest Destination buffer for the packet. + * @param bytes Number of bytes to copy to the destination. + */ +void dwc_otg_read_packet(dwc_otg_core_if_t * core_if, + uint8_t * dest, uint16_t bytes) +{ + int i; + int word_count = (bytes + 3) / 4; + + volatile uint32_t *fifo = core_if->data_fifo[0]; + uint32_t *data_buff = (uint32_t *) dest; + + /** + * @todo Account for the case where _dest is not dword aligned. This + * requires reading data from the FIFO into a uint32_t temp buffer, + * then moving it into the data buffer. + */ + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s(%p,%p,%d)\n", __func__, + core_if, dest, bytes); + + for (i = 0; i < word_count; i++, data_buff++) { + *data_buff = DWC_READ_REG32(fifo); + } + + return; +} + +/** + * This functions reads the device registers and prints them + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_dump_dev_registers(dwc_otg_core_if_t * core_if) +{ + int i; + volatile uint32_t *addr; + + DWC_PRINTF("Device Global Registers\n"); + addr = &core_if->dev_if->dev_global_regs->dcfg; + DWC_PRINTF("DCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->dctl; + DWC_PRINTF("DCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->dsts; + DWC_PRINTF("DSTS @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->diepmsk; + DWC_PRINTF("DIEPMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->doepmsk; + DWC_PRINTF("DOEPMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->daint; + DWC_PRINTF("DAINT @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->daintmsk; + DWC_PRINTF("DAINTMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->dtknqr1; + DWC_PRINTF("DTKNQR1 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + if (core_if->hwcfg2.b.dev_token_q_depth > 6) { + addr = &core_if->dev_if->dev_global_regs->dtknqr2; + DWC_PRINTF("DTKNQR2 @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + addr = &core_if->dev_if->dev_global_regs->dvbusdis; + DWC_PRINTF("DVBUSID @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + + addr = &core_if->dev_if->dev_global_regs->dvbuspulse; + DWC_PRINTF("DVBUSPULSE @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + + addr = &core_if->dev_if->dev_global_regs->dtknqr3_dthrctl; + DWC_PRINTF("DTKNQR3_DTHRCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + + if (core_if->hwcfg2.b.dev_token_q_depth > 22) { + addr = &core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk; + DWC_PRINTF("DTKNQR4 @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + addr = &core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk; + DWC_PRINTF("FIFOEMPMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + + if (core_if->hwcfg2.b.multi_proc_int) { + + addr = &core_if->dev_if->dev_global_regs->deachint; + DWC_PRINTF("DEACHINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->deachintmsk; + DWC_PRINTF("DEACHINTMSK @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + + for (i = 0; i <= core_if->dev_if->num_in_eps; i++) { + addr = + &core_if->dev_if-> + dev_global_regs->diepeachintmsk[i]; + DWC_PRINTF("DIEPEACHINTMSK[%d] @0x%08lX : 0x%08X\n", + i, (unsigned long)addr, + DWC_READ_REG32(addr)); + } + + for (i = 0; i <= core_if->dev_if->num_out_eps; i++) { + addr = + &core_if->dev_if-> + dev_global_regs->doepeachintmsk[i]; + DWC_PRINTF("DOEPEACHINTMSK[%d] @0x%08lX : 0x%08X\n", + i, (unsigned long)addr, + DWC_READ_REG32(addr)); + } + } + + for (i = 0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_PRINTF("Device IN EP %d Registers\n", i); + addr = &core_if->dev_if->in_ep_regs[i]->diepctl; + DWC_PRINTF("DIEPCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->diepint; + DWC_PRINTF("DIEPINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->dieptsiz; + DWC_PRINTF("DIETSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->diepdma; + DWC_PRINTF("DIEPDMA @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->dtxfsts; + DWC_PRINTF("DTXFSTS @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->diepdmab; + DWC_PRINTF("DIEPDMAB @0x%08lX : 0x%08X\n", + (unsigned long)addr, 0 /*DWC_READ_REG32(addr) */ ); + } + + for (i = 0; i <= core_if->dev_if->num_out_eps; i++) { + DWC_PRINTF("Device OUT EP %d Registers\n", i); + addr = &core_if->dev_if->out_ep_regs[i]->doepctl; + DWC_PRINTF("DOEPCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->out_ep_regs[i]->doepint; + DWC_PRINTF("DOEPINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->out_ep_regs[i]->doeptsiz; + DWC_PRINTF("DOETSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->out_ep_regs[i]->doepdma; + DWC_PRINTF("DOEPDMA @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + if (core_if->dma_enable) { /* Don't access this register in SLAVE mode */ + addr = &core_if->dev_if->out_ep_regs[i]->doepdmab; + DWC_PRINTF("DOEPDMAB @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + } +} + +/** + * This functions reads the SPRAM and prints its content + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_dump_spram(dwc_otg_core_if_t * core_if) +{ + volatile uint8_t *addr, *start_addr, *end_addr; + + DWC_PRINTF("SPRAM Data:\n"); + start_addr = (void *)core_if->core_global_regs; + DWC_PRINTF("Base Address: 0x%8lX\n", (unsigned long)start_addr); + start_addr += SPRAM_START_ADDR; + end_addr = (void *)core_if->core_global_regs; + end_addr += SPRAM_END_ADDR; + + for (addr = start_addr; addr < end_addr; addr += 16) { + DWC_PRINTF + ("0x%8lX:\t%2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X\n", + (unsigned long)addr, addr[0], addr[1], addr[2], addr[3], + addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], + addr[10], addr[11], addr[12], addr[13], addr[14], addr[15] + ); + } + + return; +} + +/** + * This function reads the host registers and prints them + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_dump_host_registers(dwc_otg_core_if_t * core_if) +{ + int i; + volatile uint32_t *addr; + + DWC_PRINTF("Host Global Registers\n"); + addr = &core_if->host_if->host_global_regs->hcfg; + DWC_PRINTF("HCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->hfir; + DWC_PRINTF("HFIR @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->hfnum; + DWC_PRINTF("HFNUM @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->hptxsts; + DWC_PRINTF("HPTXSTS @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->haint; + DWC_PRINTF("HAINT @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->haintmsk; + DWC_PRINTF("HAINTMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + if (core_if->dma_desc_enable) { + addr = &core_if->host_if->host_global_regs->hflbaddr; + DWC_PRINTF("HFLBADDR @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + addr = core_if->host_if->hprt0; + DWC_PRINTF("HPRT0 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + + for (i = 0; i < core_if->core_params->host_channels; i++) { + DWC_PRINTF("Host Channel %d Specific Registers\n", i); + addr = &core_if->host_if->hc_regs[i]->hcchar; + DWC_PRINTF("HCCHAR @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hcsplt; + DWC_PRINTF("HCSPLT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hcint; + DWC_PRINTF("HCINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hcintmsk; + DWC_PRINTF("HCINTMSK @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hctsiz; + DWC_PRINTF("HCTSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hcdma; + DWC_PRINTF("HCDMA @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + if (core_if->dma_desc_enable) { + addr = &core_if->host_if->hc_regs[i]->hcdmab; + DWC_PRINTF("HCDMAB @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + } + return; +} + +/** + * This function reads the core global registers and prints them + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_dump_global_registers(dwc_otg_core_if_t * core_if) +{ + int i, ep_num; + volatile uint32_t *addr; + char *txfsiz; + + DWC_PRINTF("Core Global Registers\n"); + addr = &core_if->core_global_regs->gotgctl; + DWC_PRINTF("GOTGCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gotgint; + DWC_PRINTF("GOTGINT @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gahbcfg; + DWC_PRINTF("GAHBCFG @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gusbcfg; + DWC_PRINTF("GUSBCFG @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->grstctl; + DWC_PRINTF("GRSTCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gintsts; + DWC_PRINTF("GINTSTS @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gintmsk; + DWC_PRINTF("GINTMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->grxstsr; + DWC_PRINTF("GRXSTSR @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->grxfsiz; + DWC_PRINTF("GRXFSIZ @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gnptxfsiz; + DWC_PRINTF("GNPTXFSIZ @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gnptxsts; + DWC_PRINTF("GNPTXSTS @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gi2cctl; + DWC_PRINTF("GI2CCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gpvndctl; + DWC_PRINTF("GPVNDCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ggpio; + DWC_PRINTF("GGPIO @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->guid; + DWC_PRINTF("GUID @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gsnpsid; + DWC_PRINTF("GSNPSID @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ghwcfg1; + DWC_PRINTF("GHWCFG1 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ghwcfg2; + DWC_PRINTF("GHWCFG2 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ghwcfg3; + DWC_PRINTF("GHWCFG3 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ghwcfg4; + DWC_PRINTF("GHWCFG4 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->glpmcfg; + DWC_PRINTF("GLPMCFG @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gpwrdn; + DWC_PRINTF("GPWRDN @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gdfifocfg; + DWC_PRINTF("GDFIFOCFG @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->adpctl; + DWC_PRINTF("ADPCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + dwc_otg_adp_read_reg(core_if)); + addr = &core_if->core_global_regs->hptxfsiz; + DWC_PRINTF("HPTXFSIZ @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + + if (core_if->en_multiple_tx_fifo == 0) { + ep_num = core_if->hwcfg4.b.num_dev_perio_in_ep; + txfsiz = "DPTXFSIZ"; + } else { + ep_num = core_if->hwcfg4.b.num_in_eps; + txfsiz = "DIENPTXF"; + } + for (i = 0; i < ep_num; i++) { + addr = &core_if->core_global_regs->dtxfsiz[i]; + DWC_PRINTF("%s[%d] @0x%08lX : 0x%08X\n", txfsiz, i + 1, + (unsigned long)addr, DWC_READ_REG32(addr)); + } + addr = core_if->pcgcctl; + DWC_PRINTF("PCGCCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); +} + +/** + * Flush a Tx FIFO. + * + * @param core_if Programming view of DWC_otg controller. + * @param num Tx FIFO to flush. + */ +void dwc_otg_flush_tx_fifo(dwc_otg_core_if_t * core_if, const int num) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + volatile grstctl_t greset = {.d32 = 0 }; + int count = 0; + + DWC_DEBUGPL((DBG_CIL | DBG_PCDV), "Flush Tx FIFO %d\n", num); + + greset.b.txfflsh = 1; + greset.b.txfnum = num; + DWC_WRITE_REG32(&global_regs->grstctl, greset.d32); + + do { + greset.d32 = DWC_READ_REG32(&global_regs->grstctl); + if (++count > 10000) { + dump_stack(); + DWC_WARN("%s() HANG! GRSTCTL=%0x GNPTXSTS=0x%08x\n", + __func__, greset.d32, + DWC_READ_REG32(&global_regs->gnptxsts)); + break; + } + dwc_udelay(1); + } while (greset.b.txfflsh == 1); + + /* Wait for 3 PHY Clocks */ + dwc_udelay(1); +} + +/** + * Flush Rx FIFO. + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_flush_rx_fifo(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + volatile grstctl_t greset = {.d32 = 0 }; + int count = 0; + + DWC_DEBUGPL((DBG_CIL | DBG_PCDV), "%s\n", __func__); + /* + * + */ + greset.b.rxfflsh = 1; + DWC_WRITE_REG32(&global_regs->grstctl, greset.d32); + + do { + greset.d32 = DWC_READ_REG32(&global_regs->grstctl); + if (++count > 10000) { + DWC_WARN("%s() HANG! GRSTCTL=%0x\n", __func__, + greset.d32); + break; + } + dwc_udelay(1); + } while (greset.b.rxfflsh == 1); + + /* Wait for 3 PHY Clocks */ + dwc_udelay(1); +} + +/** + * Do core a soft reset of the core. Be careful with this because it + * resets all the internal state machines of the core. + */ +void dwc_otg_core_reset(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + volatile grstctl_t greset = {.d32 = 0 }; + int count = 0; + + DWC_DEBUGPL(DBG_CILV, "%s\n", __func__); + /* Wait for AHB master IDLE state. */ + do { + dwc_udelay(10); + greset.d32 = DWC_READ_REG32(&global_regs->grstctl); + if (++count > 100000) { + DWC_WARN("%s() HANG! AHB Idle GRSTCTL=%0x\n", __func__, + greset.d32); + return; + } + } + while (greset.b.ahbidle == 0); + + /* Core Soft Reset */ + count = 0; + greset.b.csftrst = 1; + DWC_WRITE_REG32(&global_regs->grstctl, greset.d32); + do { + greset.d32 = DWC_READ_REG32(&global_regs->grstctl); + if (++count > 10000) { + DWC_WARN("%s() HANG! Soft Reset GRSTCTL=%0x\n", + __func__, greset.d32); + usb_phy_ahb_rst(); + break; + } + dwc_udelay(1); + } + while (greset.b.csftrst == 1); + + /* Wait for 3 PHY Clocks */ + /* orignal 100ms is too long, change to 1ms, sword */ + dwc_mdelay(1); +} + +uint8_t dwc_otg_is_device_mode(dwc_otg_core_if_t * _core_if) +{ + return (dwc_otg_mode(_core_if) != DWC_HOST_MODE); +} + +uint8_t dwc_otg_is_host_mode(dwc_otg_core_if_t * _core_if) +{ + return (dwc_otg_mode(_core_if) == DWC_HOST_MODE); +} + +/** + * Register HCD callbacks. The callbacks are used to start and stop + * the HCD for interrupt processing. + * + * @param core_if Programming view of DWC_otg controller. + * @param cb the HCD callback structure. + * @param p pointer to be passed to callback function (usb_hcd*). + */ +void dwc_otg_cil_register_hcd_callbacks(dwc_otg_core_if_t * core_if, + dwc_otg_cil_callbacks_t * cb, void *p) +{ + core_if->hcd_cb = cb; + cb->p = p; +} + +/** + * Register PCD callbacks. The callbacks are used to start and stop + * the PCD for interrupt processing. + * + * @param core_if Programming view of DWC_otg controller. + * @param cb the PCD callback structure. + * @param p pointer to be passed to callback function (pcd*). + */ +void dwc_otg_cil_register_pcd_callbacks(dwc_otg_core_if_t * core_if, + dwc_otg_cil_callbacks_t * cb, void *p) +{ + core_if->pcd_cb = cb; + cb->p = p; +} + +#ifdef DWC_EN_ISOC + +/** + * This function writes isoc data per 1 (micro)frame into tx fifo + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void write_isoc_frame_data(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + dwc_otg_dev_in_ep_regs_t *ep_regs; + dtxfsts_data_t txstatus = {.d32 = 0 }; + uint32_t len = 0; + uint32_t dwords; + + ep->xfer_len = ep->data_per_frame; + ep->xfer_count = 0; + + ep_regs = core_if->dev_if->in_ep_regs[ep->num]; + + len = ep->xfer_len - ep->xfer_count; + + if (len > ep->maxpacket) { + len = ep->maxpacket; + } + + dwords = (len + 3) / 4; + + /* While there is space in the queue and space in the FIFO and + * More data to tranfer, Write packets to the Tx FIFO */ + txstatus.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", ep->num, txstatus.d32); + + while (txstatus.b.txfspcavail > dwords && + ep->xfer_count < ep->xfer_len && ep->xfer_len != 0) { + /* Write the FIFO */ + dwc_otg_ep_write_packet(core_if, ep, 0); + + len = ep->xfer_len - ep->xfer_count; + if (len > ep->maxpacket) { + len = ep->maxpacket; + } + + dwords = (len + 3) / 4; + txstatus.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", ep->num, + txstatus.d32); + } +} + +/** + * This function initializes a descriptor chain for Isochronous transfer + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void dwc_otg_iso_ep_start_frm_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep) +{ + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + dsts_data_t dsts = {.d32 = 0 }; + volatile uint32_t *addr; + + if (ep->is_in) { + addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl; + } else { + addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl; + } + + ep->xfer_len = ep->data_per_frame; + ep->xfer_count = 0; + ep->xfer_buff = ep->cur_pkt_addr; + ep->dma_addr = ep->cur_pkt_dma_addr; + + if (ep->is_in) { + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + deptsiz.b.xfersize = ep->xfer_len; + deptsiz.b.pktcnt = + (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket; + deptsiz.b.mc = deptsiz.b.pktcnt; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]->dieptsiz, + deptsiz.d32); + + /* Write the DMA register */ + if (core_if->dma_enable) { + DWC_WRITE_REG32(& + (core_if->dev_if->in_ep_regs[ep->num]-> + diepdma), (uint32_t) ep->dma_addr); + } + } else { + deptsiz.b.pktcnt = + (ep->xfer_len + (ep->maxpacket - 1)) / ep->maxpacket; + deptsiz.b.xfersize = deptsiz.b.pktcnt * ep->maxpacket; + + DWC_WRITE_REG32(&core_if->dev_if-> + out_ep_regs[ep->num]->doeptsiz, deptsiz.d32); + + if (core_if->dma_enable) { + DWC_WRITE_REG32(& + (core_if->dev_if-> + out_ep_regs[ep->num]->doepdma), + (uint32_t) ep->dma_addr); + } + } + + /** Enable endpoint, clear nak */ + + depctl.d32 = 0; + if (ep->bInterval == 1) { + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + ep->next_frame = dsts.b.soffn + ep->bInterval; + + if (ep->next_frame & 0x1) { + depctl.b.setd1pid = 1; + } else { + depctl.b.setd0pid = 1; + } + } else { + ep->next_frame += ep->bInterval; + + if (ep->next_frame & 0x1) { + depctl.b.setd1pid = 1; + } else { + depctl.b.setd0pid = 1; + } + } + depctl.b.epena = 1; + depctl.b.cnak = 1; + + DWC_MODIFY_REG32(addr, 0, depctl.d32); + depctl.d32 = DWC_READ_REG32(addr); + + if (ep->is_in && core_if->dma_enable == 0) { + write_isoc_frame_data(core_if, ep); + } + +} +#endif /* DWC_EN_ISOC */ + +static void dwc_otg_set_uninitialized(int32_t * p, int size) +{ + int i; + for (i = 0; i < size; i++) { + p[i] = -1; + } +} + +static int dwc_otg_param_initialized(int32_t val) +{ + return val != -1; +} + +static int dwc_otg_setup_params(dwc_otg_core_if_t * core_if) +{ + int i; + core_if->core_params = DWC_ALLOC(sizeof(*core_if->core_params)); + if (!core_if->core_params) { + return -DWC_E_NO_MEMORY; + } + dwc_otg_set_uninitialized((int32_t *) core_if->core_params, + sizeof(*core_if->core_params) / + sizeof(int32_t)); + DWC_PRINTF("Setting default values for core params\n"); + dwc_otg_set_param_otg_cap(core_if, dwc_param_otg_cap_default); + dwc_otg_set_param_dma_enable(core_if, dwc_param_dma_enable_default); + dwc_otg_set_param_dma_desc_enable(core_if, + dwc_param_dma_desc_enable_default); + dwc_otg_set_param_opt(core_if, dwc_param_opt_default); + dwc_otg_set_param_dma_burst_size(core_if, + dwc_param_dma_burst_size_default); + dwc_otg_set_param_host_support_fs_ls_low_power(core_if, + dwc_param_host_support_fs_ls_low_power_default); + dwc_otg_set_param_enable_dynamic_fifo(core_if, + dwc_param_enable_dynamic_fifo_default); + dwc_otg_set_param_data_fifo_size(core_if, + dwc_param_data_fifo_size_default); + dwc_otg_set_param_dev_rx_fifo_size(core_if, + dwc_param_dev_rx_fifo_size_default); + dwc_otg_set_param_dev_nperio_tx_fifo_size(core_if, + dwc_param_dev_nperio_tx_fifo_size_default); + dwc_otg_set_param_host_rx_fifo_size(core_if, + dwc_param_host_rx_fifo_size_default); + dwc_otg_set_param_host_nperio_tx_fifo_size(core_if, + dwc_param_host_nperio_tx_fifo_size_default); + dwc_otg_set_param_host_perio_tx_fifo_size(core_if, + dwc_param_host_perio_tx_fifo_size_default); + dwc_otg_set_param_max_transfer_size(core_if, + dwc_param_max_transfer_size_default); + dwc_otg_set_param_max_packet_count(core_if, + dwc_param_max_packet_count_default); + dwc_otg_set_param_host_channels(core_if, + dwc_param_host_channels_default); + dwc_otg_set_param_dev_endpoints(core_if, + dwc_param_dev_endpoints_default); + dwc_otg_set_param_phy_type(core_if, dwc_param_phy_type_default); + dwc_otg_set_param_speed(core_if, dwc_param_speed_default); + dwc_otg_set_param_host_ls_low_power_phy_clk(core_if, + dwc_param_host_ls_low_power_phy_clk_default); + dwc_otg_set_param_phy_ulpi_ddr(core_if, dwc_param_phy_ulpi_ddr_default); + dwc_otg_set_param_phy_ulpi_ext_vbus(core_if, + dwc_param_phy_ulpi_ext_vbus_default); + dwc_otg_set_param_phy_utmi_width(core_if, + dwc_param_phy_utmi_width_default); + dwc_otg_set_param_ts_dline(core_if, dwc_param_ts_dline_default); + dwc_otg_set_param_i2c_enable(core_if, dwc_param_i2c_enable_default); + dwc_otg_set_param_ulpi_fs_ls(core_if, dwc_param_ulpi_fs_ls_default); + dwc_otg_set_param_en_multiple_tx_fifo(core_if, + dwc_param_en_multiple_tx_fifo_default); + for (i = 0; i < 15; i++) { + dwc_otg_set_param_dev_perio_tx_fifo_size(core_if, + dwc_param_dev_perio_tx_fifo_size_default, + i); + } + + for (i = 0; i < 15; i++) { + dwc_otg_set_param_dev_tx_fifo_size(core_if, + dwc_param_dev_tx_fifo_size_default, + i); + } + dwc_otg_set_param_thr_ctl(core_if, dwc_param_thr_ctl_default); + dwc_otg_set_param_mpi_enable(core_if, dwc_param_mpi_enable_default); + dwc_otg_set_param_pti_enable(core_if, dwc_param_pti_enable_default); + dwc_otg_set_param_lpm_enable(core_if, dwc_param_lpm_enable_default); + dwc_otg_set_param_ic_usb_cap(core_if, dwc_param_ic_usb_cap_default); + dwc_otg_set_param_tx_thr_length(core_if, + dwc_param_tx_thr_length_default); + dwc_otg_set_param_rx_thr_length(core_if, + dwc_param_rx_thr_length_default); + dwc_otg_set_param_ahb_thr_ratio(core_if, + dwc_param_ahb_thr_ratio_default); + dwc_otg_set_param_power_down(core_if, dwc_param_power_down_default); + dwc_otg_set_param_reload_ctl(core_if, dwc_param_reload_ctl_default); + dwc_otg_set_param_dev_out_nak(core_if, dwc_param_dev_out_nak_default); + dwc_otg_set_param_cont_on_bna(core_if, dwc_param_cont_on_bna_default); + dwc_otg_set_param_ahb_single(core_if, dwc_param_ahb_single_default); + dwc_otg_set_param_otg_ver(core_if, dwc_param_otg_ver_default); + dwc_otg_set_param_adp_enable(core_if, dwc_param_adp_enable_default); + return 0; +} + +uint8_t dwc_otg_is_dma_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->dma_enable; +} + +/* Checks if the parameter is outside of its valid range of values */ +#define DWC_OTG_PARAM_TEST(_param_, _low_, _high_) \ + (((_param_) < (_low_)) || \ + ((_param_) > (_high_))) + +/* Parameter access functions */ +int dwc_otg_set_param_otg_cap(dwc_otg_core_if_t * core_if, int32_t val) +{ + int valid; + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 2)) { + DWC_WARN("Wrong value for otg_cap parameter\n"); + DWC_WARN("otg_cap parameter must be 0,1 or 2\n"); + retval = -DWC_E_INVALID; + goto out; + } + + valid = 1; + switch (val) { + case DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE: + if (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + valid = 0; + break; + case DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE: + if ((core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + && (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG) + && (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) + && (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)) { + valid = 0; + } + break; + case DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE: + /* always valid */ + break; + } + if (!valid) { + if (dwc_otg_param_initialized(core_if->core_params->otg_cap)) { + DWC_ERROR + ("%d invalid for otg_cap paremter. Check HW configuration.\n", + val); + } + val = + (((core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + || (core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG) + || (core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) + || (core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)) ? + DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE : + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + retval = -DWC_E_INVALID; + } + + core_if->core_params->otg_cap = val; +out: + return retval; +} + +int32_t dwc_otg_get_param_otg_cap(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->otg_cap; +} + +int dwc_otg_set_param_opt(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for opt parameter\n"); + return -DWC_E_INVALID; + } + core_if->core_params->opt = val; + return 0; +} + +int32_t dwc_otg_get_param_opt(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->opt; +} + +int dwc_otg_set_param_dma_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for dma enable\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && (core_if->hwcfg2.b.architecture == 0)) { + if (dwc_otg_param_initialized(core_if->core_params->dma_enable)) { + DWC_ERROR + ("%d invalid for dma_enable paremter. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + + core_if->core_params->dma_enable = val; + if (val == 0) { + dwc_otg_set_param_dma_desc_enable(core_if, 0); + } + return retval; +} + +int32_t dwc_otg_get_param_dma_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dma_enable; +} + +int dwc_otg_set_param_dma_desc_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for dma_enable\n"); + DWC_WARN("dma_desc_enable must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) + && ((dwc_otg_get_param_dma_enable(core_if) == 0) + || (core_if->hwcfg4.b.desc_dma == 0))) { + if (dwc_otg_param_initialized + (core_if->core_params->dma_desc_enable)) { + DWC_ERROR + ("%d invalid for dma_desc_enable paremter. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + core_if->core_params->dma_desc_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_dma_desc_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dma_desc_enable; +} + +int dwc_otg_set_param_host_support_fs_ls_low_power(dwc_otg_core_if_t * core_if, + int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for host_support_fs_low_power\n"); + DWC_WARN("host_support_fs_low_power must be 0 or 1\n"); + return -DWC_E_INVALID; + } + core_if->core_params->host_support_fs_ls_low_power = val; + return 0; +} + +int32_t dwc_otg_get_param_host_support_fs_ls_low_power(dwc_otg_core_if_t * + core_if) +{ + return core_if->core_params->host_support_fs_ls_low_power; +} + +int dwc_otg_set_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for enable_dynamic_fifo\n"); + DWC_WARN("enable_dynamic_fifo must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && (core_if->hwcfg2.b.dynamic_fifo == 0)) { + if (dwc_otg_param_initialized + (core_if->core_params->enable_dynamic_fifo)) { + DWC_ERROR + ("%d invalid for enable_dynamic_fifo paremter. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + core_if->core_params->enable_dynamic_fifo = val; + return retval; +} + +int32_t dwc_otg_get_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->enable_dynamic_fifo; +} + +int dwc_otg_set_param_data_fifo_size(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 32, 32768)) { + DWC_WARN("Wrong value for data_fifo_size\n"); + DWC_WARN("data_fifo_size must be 32-32768\n"); + return -DWC_E_INVALID; + } + + if (val > core_if->hwcfg3.b.dfifo_depth) { + if (dwc_otg_param_initialized + (core_if->core_params->data_fifo_size)) { + DWC_ERROR + ("%d invalid for data_fifo_size parameter. Check HW configuration.\n", + val); + } + val = core_if->hwcfg3.b.dfifo_depth; + retval = -DWC_E_INVALID; + } + + core_if->core_params->data_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_data_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->data_fifo_size; +} + +int dwc_otg_set_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for dev_rx_fifo_size\n"); + DWC_WARN("dev_rx_fifo_size must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > DWC_READ_REG32(&core_if->core_global_regs->grxfsiz)) { + if (dwc_otg_param_initialized(core_if->core_params->dev_rx_fifo_size)) { + DWC_WARN("%d invalid for dev_rx_fifo_size parameter\n", val); + } + val = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_rx_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dev_rx_fifo_size; +} + +int dwc_otg_set_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for dev_nperio_tx_fifo\n"); + DWC_WARN("dev_nperio_tx_fifo must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> 16)) { + if (dwc_otg_param_initialized + (core_if->core_params->dev_nperio_tx_fifo_size)) { + DWC_ERROR + ("%d invalid for dev_nperio_tx_fifo_size. Check HW configuration.\n", + val); + } + val = + (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> + 16); + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_nperio_tx_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dev_nperio_tx_fifo_size; +} + +int dwc_otg_set_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for host_rx_fifo_size\n"); + DWC_WARN("host_rx_fifo_size must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > DWC_READ_REG32(&core_if->core_global_regs->grxfsiz)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_rx_fifo_size)) { + DWC_ERROR + ("%d invalid for host_rx_fifo_size. Check HW configuration.\n", + val); + } + val = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_rx_fifo_size = val; + return retval; + +} + +int32_t dwc_otg_get_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_rx_fifo_size; +} + +int dwc_otg_set_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for host_nperio_tx_fifo_size\n"); + DWC_WARN("host_nperio_tx_fifo_size must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> 16)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_nperio_tx_fifo_size)) { + DWC_ERROR + ("%d invalid for host_nperio_tx_fifo_size. Check HW configuration.\n", + val); + } + val = + (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> + 16); + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_nperio_tx_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_nperio_tx_fifo_size; +} + +int dwc_otg_set_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for host_perio_tx_fifo_size\n"); + DWC_WARN("host_perio_tx_fifo_size must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > ((core_if->hptxfsiz.d32) >> 16)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_perio_tx_fifo_size)) { + DWC_ERROR + ("%d invalid for host_perio_tx_fifo_size. Check HW configuration.\n", + val); + } + val = (core_if->hptxfsiz.d32) >> 16; + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_perio_tx_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_perio_tx_fifo_size; +} + +int dwc_otg_set_param_max_transfer_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 2047, 524288)) { + DWC_WARN("Wrong value for max_transfer_size\n"); + DWC_WARN("max_transfer_size must be 2047-524288\n"); + return -DWC_E_INVALID; + } + + if (val >= (1 << (core_if->hwcfg3.b.xfer_size_cntr_width + 11))) { + if (dwc_otg_param_initialized + (core_if->core_params->max_transfer_size)) { + DWC_ERROR + ("%d invalid for max_transfer_size. Check HW configuration.\n", + val); + } + val = + ((1 << (core_if->hwcfg3.b.packet_size_cntr_width + 11)) - + 1); + retval = -DWC_E_INVALID; + } + + core_if->core_params->max_transfer_size = val; + return retval; +} + +int32_t dwc_otg_get_param_max_transfer_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->max_transfer_size; +} + +int dwc_otg_set_param_max_packet_count(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 15, 511)) { + DWC_WARN("Wrong value for max_packet_count\n"); + DWC_WARN("max_packet_count must be 15-511\n"); + return -DWC_E_INVALID; + } + + if (val > (1 << (core_if->hwcfg3.b.packet_size_cntr_width + 4))) { + if (dwc_otg_param_initialized + (core_if->core_params->max_packet_count)) { + DWC_ERROR + ("%d invalid for max_packet_count. Check HW configuration.\n", + val); + } + val = + ((1 << (core_if->hwcfg3.b.packet_size_cntr_width + 4)) - 1); + retval = -DWC_E_INVALID; + } + + core_if->core_params->max_packet_count = val; + return retval; +} + +int32_t dwc_otg_get_param_max_packet_count(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->max_packet_count; +} + +int dwc_otg_set_param_host_channels(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 1, 16)) { + DWC_WARN("Wrong value for host_channels\n"); + DWC_WARN("host_channels must be 1-16\n"); + return -DWC_E_INVALID; + } + + if (val > (core_if->hwcfg2.b.num_host_chan + 1)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_channels)) { + DWC_ERROR + ("%d invalid for host_channels. Check HW configurations.\n", + val); + } + val = (core_if->hwcfg2.b.num_host_chan + 1); + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_channels = val; + return retval; +} + +int32_t dwc_otg_get_param_host_channels(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_channels; +} + +int dwc_otg_set_param_dev_endpoints(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 1, 15)) { + DWC_WARN("Wrong value for dev_endpoints\n"); + DWC_WARN("dev_endpoints must be 1-15\n"); + return -DWC_E_INVALID; + } + + if (val > (core_if->hwcfg2.b.num_dev_ep)) { + if (dwc_otg_param_initialized + (core_if->core_params->dev_endpoints)) { + DWC_ERROR + ("%d invalid for dev_endpoints. Check HW configurations.\n", + val); + } + val = core_if->hwcfg2.b.num_dev_ep; + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_endpoints = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_endpoints(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dev_endpoints; +} + +int dwc_otg_set_param_phy_type(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 2)) { + DWC_WARN("Wrong value for phy_type\n"); + DWC_WARN("phy_type must be 0,1 or 2\n"); + return -DWC_E_INVALID; + } +#ifndef NO_FS_PHY_HW_CHECKS + if ((val == DWC_PHY_TYPE_PARAM_UTMI) && + ((core_if->hwcfg2.b.hs_phy_type == 1) || + (core_if->hwcfg2.b.hs_phy_type == 3))) { + valid = 1; + } else if ((val == DWC_PHY_TYPE_PARAM_ULPI) && + ((core_if->hwcfg2.b.hs_phy_type == 2) || + (core_if->hwcfg2.b.hs_phy_type == 3))) { + valid = 1; + } else if ((val == DWC_PHY_TYPE_PARAM_FS) && + (core_if->hwcfg2.b.fs_phy_type == 1)) { + valid = 1; + } + if (!valid) { + if (dwc_otg_param_initialized(core_if->core_params->phy_type)) { + DWC_ERROR + ("%d invalid for phy_type. Check HW configurations.\n", + val); + } + if (core_if->hwcfg2.b.hs_phy_type) { + if ((core_if->hwcfg2.b.hs_phy_type == 3) || + (core_if->hwcfg2.b.hs_phy_type == 1)) { + val = DWC_PHY_TYPE_PARAM_UTMI; + } else { + val = DWC_PHY_TYPE_PARAM_ULPI; + } + } + retval = -DWC_E_INVALID; + } +#endif + core_if->core_params->phy_type = val; + return retval; +} + +int32_t dwc_otg_get_param_phy_type(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->phy_type; +} + +int dwc_otg_set_param_speed(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for speed parameter\n"); + DWC_WARN("max_speed parameter must be 0 or 1\n"); + return -DWC_E_INVALID; + } + if ((val == 0) + && dwc_otg_get_param_phy_type(core_if) == DWC_PHY_TYPE_PARAM_FS) { + if (dwc_otg_param_initialized(core_if->core_params->speed)) { + DWC_ERROR + ("%d invalid for speed paremter. Check HW configuration.\n", + val); + } + val = + (dwc_otg_get_param_phy_type(core_if) == + DWC_PHY_TYPE_PARAM_FS ? 1 : 0); + retval = -DWC_E_INVALID; + } + core_if->core_params->speed = val; + return retval; +} + +int32_t dwc_otg_get_param_speed(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->speed; +} + +int dwc_otg_set_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN + ("Wrong value for host_ls_low_power_phy_clk parameter\n"); + DWC_WARN("host_ls_low_power_phy_clk must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ) + && (dwc_otg_get_param_phy_type(core_if) == DWC_PHY_TYPE_PARAM_FS)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_ls_low_power_phy_clk)) { + DWC_ERROR + ("%d invalid for host_ls_low_power_phy_clk. Check HW configuration.\n", + val); + } + val = + (dwc_otg_get_param_phy_type(core_if) == + DWC_PHY_TYPE_PARAM_FS) ? + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ : + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ; + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_ls_low_power_phy_clk = val; + return retval; +} + +int32_t dwc_otg_get_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_ls_low_power_phy_clk; +} + +int dwc_otg_set_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for phy_ulpi_ddr\n"); + DWC_WARN("phy_upli_ddr must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->phy_ulpi_ddr = val; + return 0; +} + +int32_t dwc_otg_get_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->phy_ulpi_ddr; +} + +int dwc_otg_set_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if, + int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for phy_ulpi_ext_vbus\n"); + DWC_WARN("phy_ulpi_ext_vbus must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->phy_ulpi_ext_vbus = val; + return 0; +} + +int32_t dwc_otg_get_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->phy_ulpi_ext_vbus; +} + +int dwc_otg_set_param_phy_utmi_width(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 8, 8) && DWC_OTG_PARAM_TEST(val, 16, 16)) { + DWC_WARN("Wrong valaue for phy_utmi_width\n"); + DWC_WARN("phy_utmi_width must be 8 or 16\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->phy_utmi_width = val; + return 0; +} + +int32_t dwc_otg_get_param_phy_utmi_width(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->phy_utmi_width; +} + +int dwc_otg_set_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for ulpi_fs_ls\n"); + DWC_WARN("ulpi_fs_ls must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->ulpi_fs_ls = val; + return 0; +} + +int32_t dwc_otg_get_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ulpi_fs_ls; +} + +int dwc_otg_set_param_ts_dline(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for ts_dline\n"); + DWC_WARN("ts_dline must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->ts_dline = val; + return 0; +} + +int32_t dwc_otg_get_param_ts_dline(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ts_dline; +} + +int dwc_otg_set_param_i2c_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for i2c_enable\n"); + DWC_WARN("i2c_enable must be 0 or 1\n"); + return -DWC_E_INVALID; + } +#ifndef NO_FS_PHY_HW_CHECK + if (val == 1 && core_if->hwcfg3.b.i2c == 0) { + if (dwc_otg_param_initialized(core_if->core_params->i2c_enable)) { + DWC_ERROR + ("%d invalid for i2c_enable. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } +#endif + + core_if->core_params->i2c_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_i2c_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->i2c_enable; +} + +int dwc_otg_set_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val, int fifo_num) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 4, 768)) { + DWC_WARN("Wrong value for dev_perio_tx_fifo_size\n"); + DWC_WARN("dev_perio_tx_fifo_size must be 4-768\n"); + return -DWC_E_INVALID; + } + + if (val > + (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num]))) { + if (dwc_otg_param_initialized + (core_if->core_params->dev_perio_tx_fifo_size[fifo_num])) { + DWC_ERROR + ("`%d' invalid for parameter `dev_perio_fifo_size_%d'. Check HW configuration.\n", + val, fifo_num); + } + val = (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num])); + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_perio_tx_fifo_size[fifo_num] = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int fifo_num) +{ + return core_if->core_params->dev_perio_tx_fifo_size[fifo_num]; +} + +int dwc_otg_set_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for en_multiple_tx_fifo,\n"); + DWC_WARN("en_multiple_tx_fifo must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if (val == 1 && core_if->hwcfg4.b.ded_fifo_en == 0) { + if (dwc_otg_param_initialized + (core_if->core_params->en_multiple_tx_fifo)) { + DWC_ERROR + ("%d invalid for parameter en_multiple_tx_fifo. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + + core_if->core_params->en_multiple_tx_fifo = val; + return retval; +} + +int32_t dwc_otg_get_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->en_multiple_tx_fifo; +} + +int dwc_otg_set_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, int32_t val, + int fifo_num) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 4, 768)) { + DWC_WARN("Wrong value for dev_tx_fifo_size\n"); + DWC_WARN("dev_tx_fifo_size must be 4-768\n"); + return -DWC_E_INVALID; + } + + if (val > + (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num]))) { + if (dwc_otg_param_initialized + (core_if->core_params->dev_tx_fifo_size[fifo_num])) { + DWC_ERROR + ("`%d' invalid for parameter `dev_tx_fifo_size_%d'. Check HW configuration.\n", + val, fifo_num); + } + val = (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num])); + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_tx_fifo_size[fifo_num] = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, + int fifo_num) +{ + return core_if->core_params->dev_tx_fifo_size[fifo_num]; +} + +int dwc_otg_set_param_thr_ctl(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 7)) { + DWC_WARN("Wrong value for thr_ctl\n"); + DWC_WARN("thr_ctl must be 0-7\n"); + return -DWC_E_INVALID; + } + + if ((val != 0) && + (!dwc_otg_get_param_dma_enable(core_if) || + !core_if->hwcfg4.b.ded_fifo_en)) { + if (dwc_otg_param_initialized(core_if->core_params->thr_ctl)) { + DWC_ERROR + ("%d invalid for parameter thr_ctl. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + + core_if->core_params->thr_ctl = val; + return retval; +} + +int32_t dwc_otg_get_param_thr_ctl(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->thr_ctl; +} + +int dwc_otg_set_param_lpm_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for lpm_enable\n"); + DWC_WARN("lpm_enable must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if (val && !core_if->hwcfg3.b.otg_lpm_en) { + if (dwc_otg_param_initialized(core_if->core_params->lpm_enable)) { + DWC_ERROR + ("%d invalid for parameter lpm_enable. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + + core_if->core_params->lpm_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_lpm_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->lpm_enable; +} + +int dwc_otg_set_param_tx_thr_length(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 8, 128)) { + DWC_WARN("Wrong valaue for tx_thr_length\n"); + DWC_WARN("tx_thr_length must be 8 - 128\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->tx_thr_length = val; + return 0; +} + +int32_t dwc_otg_get_param_tx_thr_length(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->tx_thr_length; +} + +int dwc_otg_set_param_rx_thr_length(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 8, 128)) { + DWC_WARN("Wrong valaue for rx_thr_length\n"); + DWC_WARN("rx_thr_length must be 8 - 128\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->rx_thr_length = val; + return 0; +} + +int32_t dwc_otg_get_param_rx_thr_length(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->rx_thr_length; +} + +int dwc_otg_set_param_dma_burst_size(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 1, 1) && + DWC_OTG_PARAM_TEST(val, 4, 4) && + DWC_OTG_PARAM_TEST(val, 8, 8) && + DWC_OTG_PARAM_TEST(val, 16, 16) && + DWC_OTG_PARAM_TEST(val, 32, 32) && + DWC_OTG_PARAM_TEST(val, 64, 64) && + DWC_OTG_PARAM_TEST(val, 128, 128) && + DWC_OTG_PARAM_TEST(val, 256, 256)) { + DWC_WARN("`%d' invalid for parameter `dma_burst_size'\n", val); + return -DWC_E_INVALID; + } + core_if->core_params->dma_burst_size = val; + return 0; +} + +int32_t dwc_otg_get_param_dma_burst_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dma_burst_size; +} + +int dwc_otg_set_param_pti_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `pti_enable'\n", val); + return -DWC_E_INVALID; + } + if (val && (core_if->snpsid < OTG_CORE_REV_2_72a)) { + if (dwc_otg_param_initialized(core_if->core_params->pti_enable)) { + DWC_ERROR + ("%d invalid for parameter pti_enable. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->pti_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_pti_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->pti_enable; +} + +int dwc_otg_set_param_mpi_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `mpi_enable'\n", val); + return -DWC_E_INVALID; + } + if (val && (core_if->hwcfg2.b.multi_proc_int == 0)) { + if (dwc_otg_param_initialized(core_if->core_params->mpi_enable)) { + DWC_ERROR + ("%d invalid for parameter mpi_enable. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->mpi_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_mpi_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->mpi_enable; +} + +int dwc_otg_set_param_adp_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `adp_enable'\n", val); + return -DWC_E_INVALID; + } + if (val && (core_if->hwcfg3.b.adp_supp == 0)) { + if (dwc_otg_param_initialized + (core_if->core_params->adp_supp_enable)) { + DWC_ERROR + ("%d invalid for parameter adp_enable. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->adp_supp_enable = val; + /* Set OTG version 2.0 in case of enabling ADP */ + if (val) + dwc_otg_set_param_otg_ver(core_if, 1); + + return retval; +} + +int32_t dwc_otg_get_param_adp_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->adp_supp_enable; +} + +int dwc_otg_set_param_ic_usb_cap(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `ic_usb_cap'\n", val); + DWC_WARN("ic_usb_cap must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if (val && (core_if->hwcfg2.b.otg_enable_ic_usb == 0)) { + if (dwc_otg_param_initialized(core_if->core_params->ic_usb_cap)) { + DWC_ERROR + ("%d invalid for parameter ic_usb_cap. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->ic_usb_cap = val; + return retval; +} + +int32_t dwc_otg_get_param_ic_usb_cap(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ic_usb_cap; +} + +int dwc_otg_set_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 3)) { + DWC_WARN("`%d' invalid for parameter `ahb_thr_ratio'\n", val); + DWC_WARN("ahb_thr_ratio must be 0 - 3\n"); + return -DWC_E_INVALID; + } + + if (val + && (core_if->snpsid < OTG_CORE_REV_2_81a + || !dwc_otg_get_param_thr_ctl(core_if))) { + valid = 0; + } else if (val + && ((dwc_otg_get_param_tx_thr_length(core_if) / (1 << val)) < + 4)) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized + (core_if->core_params->ahb_thr_ratio)) { + DWC_ERROR + ("%d invalid for parameter ahb_thr_ratio. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + + core_if->core_params->ahb_thr_ratio = val; + return retval; +} + +int32_t dwc_otg_get_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ahb_thr_ratio; +} + +int dwc_otg_set_param_power_down(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + hwcfg4_data_t hwcfg4 = {.d32 = 0 }; + hwcfg4.d32 = DWC_READ_REG32(&core_if->core_global_regs->ghwcfg4); + + if (DWC_OTG_PARAM_TEST(val, 0, 3)) { + DWC_WARN("`%d' invalid for parameter `power_down'\n", val); + DWC_WARN("power_down must be 0 - 2\n"); + return -DWC_E_INVALID; + } + + if ((val == 2) && (core_if->snpsid < OTG_CORE_REV_2_91a)) { + valid = 0; + } + if ((val == 3) + && ((core_if->snpsid < OTG_CORE_REV_3_00a) + || (hwcfg4.b.xhiber == 0))) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->power_down)) { + DWC_ERROR + ("%d invalid for parameter power_down. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->power_down = val; + return retval; +} + +int32_t dwc_otg_get_param_power_down(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->power_down; +} + +int dwc_otg_set_param_reload_ctl(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `reload_ctl'\n", val); + DWC_WARN("reload_ctl must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && (core_if->snpsid < OTG_CORE_REV_2_92a)) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->reload_ctl)) { + DWC_ERROR("%d invalid for parameter reload_ctl." + "Check HW configuration.\n", val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->reload_ctl = val; + return retval; +} + +int32_t dwc_otg_get_param_reload_ctl(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->reload_ctl; +} + +int dwc_otg_set_param_dev_out_nak(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `dev_out_nak'\n", val); + DWC_WARN("dev_out_nak must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && ((core_if->snpsid < OTG_CORE_REV_2_93a) || + !(core_if->core_params->dma_desc_enable))) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->dev_out_nak)) { + DWC_ERROR("%d invalid for parameter dev_out_nak." + "Check HW configuration.\n", val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->dev_out_nak = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_out_nak(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dev_out_nak; +} + +int dwc_otg_set_param_cont_on_bna(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `cont_on_bna'\n", val); + DWC_WARN("cont_on_bna must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && ((core_if->snpsid < OTG_CORE_REV_2_94a) || + !(core_if->core_params->dma_desc_enable))) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->cont_on_bna)) { + DWC_ERROR("%d invalid for parameter cont_on_bna." + "Check HW configuration.\n", val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->cont_on_bna = val; + return retval; +} + +int32_t dwc_otg_get_param_cont_on_bna(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->cont_on_bna; +} + +int dwc_otg_set_param_ahb_single(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `ahb_single'\n", val); + DWC_WARN("ahb_single must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && (core_if->snpsid < OTG_CORE_REV_2_94a)) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->ahb_single)) { + DWC_ERROR("%d invalid for parameter ahb_single." + "Check HW configuration.\n", val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->ahb_single = val; + return retval; +} + +int32_t dwc_otg_get_param_ahb_single(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ahb_single; +} + +int dwc_otg_set_param_otg_ver(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `otg_ver'\n", val); + DWC_WARN + ("otg_ver must be 0(for OTG 1.3 support) or 1(for OTG 2.0 support)\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->otg_ver = val; + return retval; +} + +int32_t dwc_otg_get_param_otg_ver(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->otg_ver; +} + +uint32_t dwc_otg_get_hnpstatus(dwc_otg_core_if_t * core_if) +{ + gotgctl_data_t otgctl; + otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + return otgctl.b.hstnegscs; +} + +uint32_t dwc_otg_get_srpstatus(dwc_otg_core_if_t * core_if) +{ + gotgctl_data_t otgctl; + otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + return otgctl.b.sesreqscs; +} + +void dwc_otg_set_hnpreq(dwc_otg_core_if_t * core_if, uint32_t val) +{ + if(core_if->otg_ver == 0) { + gotgctl_data_t otgctl; + otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + otgctl.b.hnpreq = val; + DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, otgctl.d32); + } else { + core_if->otg_sts = val; + } +} + +uint32_t dwc_otg_get_gsnpsid(dwc_otg_core_if_t * core_if) +{ + return core_if->snpsid; +} + +uint32_t dwc_otg_get_mode(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + return gintsts.b.curmode; +} + +uint32_t dwc_otg_get_hnpcapable(dwc_otg_core_if_t * core_if) +{ + gusbcfg_data_t usbcfg; + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + return usbcfg.b.hnpcap; +} + +void dwc_otg_set_hnpcapable(dwc_otg_core_if_t * core_if, uint32_t val) +{ + gusbcfg_data_t usbcfg; + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + usbcfg.b.hnpcap = val; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, usbcfg.d32); +} + +uint32_t dwc_otg_get_srpcapable(dwc_otg_core_if_t * core_if) +{ + gusbcfg_data_t usbcfg; + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + return usbcfg.b.srpcap; +} + +void dwc_otg_set_srpcapable(dwc_otg_core_if_t * core_if, uint32_t val) +{ + gusbcfg_data_t usbcfg; + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + usbcfg.b.srpcap = val; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, usbcfg.d32); +} + +uint32_t dwc_otg_get_devspeed(dwc_otg_core_if_t * core_if) +{ + dcfg_data_t dcfg; + dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + return dcfg.b.devspd; +} + +void dwc_otg_set_devspeed(dwc_otg_core_if_t * core_if, uint32_t val) +{ + dcfg_data_t dcfg; + dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + dcfg.b.devspd = val; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32); +} + +uint32_t dwc_otg_get_busconnected(dwc_otg_core_if_t * core_if) +{ + hprt0_data_t hprt0; + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + return hprt0.b.prtconnsts; +} + +uint32_t dwc_otg_get_enumspeed(dwc_otg_core_if_t * core_if) +{ + dsts_data_t dsts; + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + return dsts.b.enumspd; +} + +uint32_t dwc_otg_get_prtpower(dwc_otg_core_if_t * core_if) +{ + hprt0_data_t hprt0; + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + return hprt0.b.prtpwr; + +} + +uint32_t dwc_otg_get_core_state(dwc_otg_core_if_t * core_if) +{ + return core_if->hibernation_suspend; +} + +void dwc_otg_set_prtpower(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hprt0_data_t hprt0; + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = val; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); +} + +uint32_t dwc_otg_get_prtsuspend(dwc_otg_core_if_t * core_if) +{ + hprt0_data_t hprt0; + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + return hprt0.b.prtsusp; + +} + +void dwc_otg_set_prtsuspend(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hprt0_data_t hprt0; + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = val; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); +} + +uint32_t dwc_otg_get_fr_interval(dwc_otg_core_if_t * core_if) +{ + hfir_data_t hfir; + hfir.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir); + return hfir.b.frint; + +} + +void dwc_otg_set_fr_interval(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hfir_data_t hfir; + uint32_t fram_int; + fram_int = calc_frame_interval(core_if); + hfir.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir); + if (!core_if->core_params->reload_ctl) { + DWC_WARN("\nCannot reload HFIR register.HFIR.HFIRRldCtrl bit is" + "not set to 1.\nShould load driver with reload_ctl=1" + " module parameter\n"); + return; + } + switch (fram_int) { + case 3750: + if ((val < 3350) || (val > 4150)) { + DWC_WARN("HFIR interval for HS core and 30 MHz" + "clock freq should be from 3350 to 4150\n"); + return; + } + break; + case 30000: + if ((val < 26820) || (val > 33180)) { + DWC_WARN("HFIR interval for FS/LS core and 30 MHz" + "clock freq should be from 26820 to 33180\n"); + return; + } + break; + case 6000: + if ((val < 5360) || (val > 6640)) { + DWC_WARN("HFIR interval for HS core and 48 MHz" + "clock freq should be from 5360 to 6640\n"); + return; + } + break; + case 48000: + if ((val < 42912) || (val > 53088)) { + DWC_WARN("HFIR interval for FS/LS core and 48 MHz" + "clock freq should be from 42912 to 53088\n"); + return; + } + break; + case 7500: + if ((val < 6700) || (val > 8300)) { + DWC_WARN("HFIR interval for HS core and 60 MHz" + "clock freq should be from 6700 to 8300\n"); + return; + } + break; + case 60000: + if ((val < 53640) || (val > 65536)) { + DWC_WARN("HFIR interval for FS/LS core and 60 MHz" + "clock freq should be from 53640 to 65536\n"); + return; + } + break; + default: + DWC_WARN("Unknown frame interval\n"); + return; + break; + + } + hfir.b.frint = val; + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hfir, hfir.d32); +} + +uint32_t dwc_otg_get_mode_ch_tim(dwc_otg_core_if_t * core_if) +{ + hcfg_data_t hcfg; + hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + return hcfg.b.modechtimen; + +} + +void dwc_otg_set_mode_ch_tim(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hcfg_data_t hcfg; + hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + hcfg.b.modechtimen = val; + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hcfg.d32); +} + +void dwc_otg_set_prtresume(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hprt0_data_t hprt0; + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtres = val; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); +} + +uint32_t dwc_otg_get_remotewakesig(dwc_otg_core_if_t * core_if) +{ + dctl_data_t dctl; + dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + return dctl.b.rmtwkupsig; +} + +uint32_t dwc_otg_get_lpm_portsleepstatus(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + + DWC_ASSERT(! + ((core_if->lx_state == DWC_OTG_L1) ^ lpmcfg.b.prt_sleep_sts), + "lx_state = %d, lmpcfg.prt_sleep_sts = %d\n", + core_if->lx_state, lpmcfg.b.prt_sleep_sts); + + return lpmcfg.b.prt_sleep_sts; +} + +uint32_t dwc_otg_get_lpm_remotewakeenabled(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + return lpmcfg.b.rem_wkup_en; +} + +uint32_t dwc_otg_get_lpmresponse(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + return lpmcfg.b.appl_resp; +} + +void dwc_otg_set_lpmresponse(dwc_otg_core_if_t * core_if, uint32_t val) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.appl_resp = val; + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32); +} + +uint32_t dwc_otg_get_hsic_connect(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + return lpmcfg.b.hsic_connect; +} + +void dwc_otg_set_hsic_connect(dwc_otg_core_if_t * core_if, uint32_t val) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.hsic_connect = val; + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32); +} + +uint32_t dwc_otg_get_inv_sel_hsic(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + return lpmcfg.b.inv_sel_hsic; + +} + +void dwc_otg_set_inv_sel_hsic(dwc_otg_core_if_t * core_if, uint32_t val) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.inv_sel_hsic = val; + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32); +} + +uint32_t dwc_otg_get_gotgctl(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->gotgctl); +} + +void dwc_otg_set_gotgctl(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, val); +} + +uint32_t dwc_otg_get_gusbcfg(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); +} + +void dwc_otg_set_gusbcfg(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, val); +} + +uint32_t dwc_otg_get_grxfsiz(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); +} + +void dwc_otg_set_grxfsiz(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->grxfsiz, val); +} + +uint32_t dwc_otg_get_gnptxfsiz(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz); +} + +void dwc_otg_set_gnptxfsiz(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->gnptxfsiz, val); +} + +uint32_t dwc_otg_get_gpvndctl(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->gpvndctl); +} + +void dwc_otg_set_gpvndctl(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->gpvndctl, val); +} + +uint32_t dwc_otg_get_ggpio(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->ggpio); +} + +void dwc_otg_set_ggpio(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->ggpio, val); +} + +uint32_t dwc_otg_get_hprt0(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(core_if->host_if->hprt0); + +} + +void dwc_otg_set_hprt0(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(core_if->host_if->hprt0, val); +} + +uint32_t dwc_otg_get_guid(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->guid); +} + +void dwc_otg_set_guid(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->guid, val); +} + +uint32_t dwc_otg_get_hptxfsiz(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz); +} + +uint16_t dwc_otg_get_otg_version(dwc_otg_core_if_t * core_if) +{ + return ((core_if->otg_ver == 1) ? (uint16_t)0x0200 : (uint16_t)0x0103); +} + +/** + * Start the SRP timer to detect when the SRP does not complete within + * 6 seconds. + * + * @param core_if the pointer to core_if strucure. + */ +void dwc_otg_pcd_start_srp_timer(dwc_otg_core_if_t * core_if) +{ + core_if->srp_timer_started = 1; + DWC_TIMER_SCHEDULE(core_if->srp_timer, 6000 /* 6 secs */ ); +} + +void dwc_otg_initiate_srp(dwc_otg_core_if_t * core_if) +{ + uint32_t *addr = (uint32_t *) & (core_if->core_global_regs->gotgctl); + gotgctl_data_t mem; + gotgctl_data_t val; + + val.d32 = DWC_READ_REG32(addr); + if (val.b.sesreq) { + DWC_ERROR("Session Request Already active!\n"); + return; + } + + DWC_INFO("Session Request Initated\n"); //NOTICE + mem.d32 = DWC_READ_REG32(addr); + mem.b.sesreq = 1; + DWC_WRITE_REG32(addr, mem.d32); + + /* Start the SRP timer */ + dwc_otg_pcd_start_srp_timer(core_if); + return; +} diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_cil.h b/drivers/usb/gadget/dwc_otg/dwc_otg_cil.h new file mode 100644 index 00000000..f3dfa804 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_cil.h @@ -0,0 +1,1498 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil.h $ + * $Revision: #123 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#if !defined(__DWC_CIL_H__) +#define __DWC_CIL_H__ + +#include +#include "dwc_list.h" +#include "dwc_otg_dbg.h" +#include "dwc_otg_regs.h" + +#include "dwc_otg_core_if.h" +#include "dwc_otg_adp.h" + +/** + * @file + * This file contains the interface to the Core Interface Layer. + */ + +#ifdef DWC_UTE_CFI + +#define MAX_DMA_DESCS_PER_EP 256 + +/** + * Enumeration for the data buffer mode + */ +typedef enum _data_buffer_mode { + BM_STANDARD = 0, /* data buffer is in normal mode */ + BM_SG = 1, /* data buffer uses the scatter/gather mode */ + BM_CONCAT = 2, /* data buffer uses the concatenation mode */ + BM_CIRCULAR = 3, /* data buffer uses the circular DMA mode */ + BM_ALIGN = 4 /* data buffer is in buffer alignment mode */ +} data_buffer_mode_e; +#endif //DWC_UTE_CFI + +/** Macros defined for DWC OTG HW Release version */ + +#define OTG_CORE_REV_2_60a 0x4F54260A +#define OTG_CORE_REV_2_71a 0x4F54271A +#define OTG_CORE_REV_2_72a 0x4F54272A +#define OTG_CORE_REV_2_80a 0x4F54280A +#define OTG_CORE_REV_2_81a 0x4F54281A +#define OTG_CORE_REV_2_90a 0x4F54290A +#define OTG_CORE_REV_2_91a 0x4F54291A +#define OTG_CORE_REV_2_92a 0x4F54292A +#define OTG_CORE_REV_2_93a 0x4F54293A +#define OTG_CORE_REV_2_94a 0x4F54294A +#define OTG_CORE_REV_3_00a 0x4F54300A + +/** + * Information for each ISOC packet. + */ +typedef struct iso_pkt_info { + uint32_t offset; + uint32_t length; + int32_t status; +} iso_pkt_info_t; + +/** + * The dwc_ep structure represents the state of a single + * endpoint when acting in device mode. It contains the data items + * needed for an endpoint to be activated and transfer packets. + */ +typedef struct dwc_ep { + /** EP number used for register address lookup */ + uint8_t num; + /** EP direction 0 = OUT */ + unsigned is_in:1; + /** EP active. */ + unsigned active:1; + + /** + * Periodic Tx FIFO # for IN EPs For INTR EP set to 0 to use non-periodic + * Tx FIFO. If dedicated Tx FIFOs are enabled Tx FIFO # FOR IN EPs*/ + unsigned tx_fifo_num:4; + /** EP type: 0 - Control, 1 - ISOC, 2 - BULK, 3 - INTR */ + unsigned type:2; +#define DWC_OTG_EP_TYPE_CONTROL 0 +#define DWC_OTG_EP_TYPE_ISOC 1 +#define DWC_OTG_EP_TYPE_BULK 2 +#define DWC_OTG_EP_TYPE_INTR 3 + + /** DATA start PID for INTR and BULK EP */ + unsigned data_pid_start:1; + /** Frame (even/odd) for ISOC EP */ + unsigned even_odd_frame:1; + /** Max Packet bytes */ + unsigned maxpacket:11; + + /** Max Transfer size */ + uint32_t maxxfer; + + /** @name Transfer state */ + /** @{ */ + + /** + * Pointer to the beginning of the transfer buffer -- do not modify + * during transfer. + */ + + dwc_dma_t dma_addr; + + dwc_dma_t dma_desc_addr; + dwc_otg_dev_dma_desc_t *desc_addr; + + uint8_t *start_xfer_buff; + /** pointer to the transfer buffer */ + uint8_t *xfer_buff; + /** Number of bytes to transfer */ + unsigned xfer_len:19; + /** Number of bytes transferred. */ + unsigned xfer_count:19; + /** Sent ZLP */ + unsigned sent_zlp:1; + /** Total len for control transfer */ + unsigned total_len:19; + + /** stall clear flag */ + unsigned stall_clear_flag:1; + + /** SETUP pkt cnt rollover flag for EP0 out*/ + unsigned stp_rollover; + +#ifdef DWC_UTE_CFI + /* The buffer mode */ + data_buffer_mode_e buff_mode; + + /* The chain of DMA descriptors. + * MAX_DMA_DESCS_PER_EP will be allocated for each active EP. + */ + dwc_otg_dma_desc_t *descs; + + /* The DMA address of the descriptors chain start */ + dma_addr_t descs_dma_addr; + /** This variable stores the length of the last enqueued request */ + uint32_t cfi_req_len; +#endif //DWC_UTE_CFI + +/** Max DMA Descriptor count for any EP */ +#define MAX_DMA_DESC_CNT 256 + /** Allocated DMA Desc count */ + uint32_t desc_cnt; + + /** bInterval */ + uint32_t bInterval; + /** Next frame num to setup next ISOC transfer */ + uint32_t frame_num; + /** Indicates SOF number overrun in DSTS */ + uint8_t frm_overrun; + +#ifdef DWC_UTE_PER_IO + /** Next frame num for which will be setup DMA Desc */ + uint32_t xiso_frame_num; + /** bInterval */ + uint32_t xiso_bInterval; + /** Count of currently active transfers - shall be either 0 or 1 */ + int xiso_active_xfers; + int xiso_queued_xfers; +#endif +#ifdef DWC_EN_ISOC + /** + * Variables specific for ISOC EPs + * + */ + /** DMA addresses of ISOC buffers */ + dwc_dma_t dma_addr0; + dwc_dma_t dma_addr1; + + dwc_dma_t iso_dma_desc_addr; + dwc_otg_dev_dma_desc_t *iso_desc_addr; + + /** pointer to the transfer buffers */ + uint8_t *xfer_buff0; + uint8_t *xfer_buff1; + + /** number of ISOC Buffer is processing */ + uint32_t proc_buf_num; + /** Interval of ISOC Buffer processing */ + uint32_t buf_proc_intrvl; + /** Data size for regular frame */ + uint32_t data_per_frame; + + /* todo - pattern data support is to be implemented in the future */ + /** Data size for pattern frame */ + uint32_t data_pattern_frame; + /** Frame number of pattern data */ + uint32_t sync_frame; + + /** bInterval */ + uint32_t bInterval; + /** ISO Packet number per frame */ + uint32_t pkt_per_frm; + /** Next frame num for which will be setup DMA Desc */ + uint32_t next_frame; + /** Number of packets per buffer processing */ + uint32_t pkt_cnt; + /** Info for all isoc packets */ + iso_pkt_info_t *pkt_info; + /** current pkt number */ + uint32_t cur_pkt; + /** current pkt number */ + uint8_t *cur_pkt_addr; + /** current pkt number */ + uint32_t cur_pkt_dma_addr; +#endif /* DWC_EN_ISOC */ + +/** @} */ +} dwc_ep_t; + +/* + * Reasons for halting a host channel. + */ +typedef enum dwc_otg_halt_status { + DWC_OTG_HC_XFER_NO_HALT_STATUS, + DWC_OTG_HC_XFER_COMPLETE, + DWC_OTG_HC_XFER_URB_COMPLETE, + DWC_OTG_HC_XFER_ACK, + DWC_OTG_HC_XFER_NAK, + DWC_OTG_HC_XFER_NYET, + DWC_OTG_HC_XFER_STALL, + DWC_OTG_HC_XFER_XACT_ERR, + DWC_OTG_HC_XFER_FRAME_OVERRUN, + DWC_OTG_HC_XFER_BABBLE_ERR, + DWC_OTG_HC_XFER_DATA_TOGGLE_ERR, + DWC_OTG_HC_XFER_AHB_ERR, + DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE, + DWC_OTG_HC_XFER_URB_DEQUEUE +} dwc_otg_halt_status_e; + +/** + * Host channel descriptor. This structure represents the state of a single + * host channel when acting in host mode. It contains the data items needed to + * transfer packets to an endpoint via a host channel. + */ +typedef struct dwc_hc { + /** Host channel number used for register address lookup */ + uint8_t hc_num; + + /** Device to access */ + unsigned dev_addr:7; + + /** EP to access */ + unsigned ep_num:4; + + /** EP direction. 0: OUT, 1: IN */ + unsigned ep_is_in:1; + + /** + * EP speed. + * One of the following values: + * - DWC_OTG_EP_SPEED_LOW + * - DWC_OTG_EP_SPEED_FULL + * - DWC_OTG_EP_SPEED_HIGH + */ + unsigned speed:2; +#define DWC_OTG_EP_SPEED_LOW 0 +#define DWC_OTG_EP_SPEED_FULL 1 +#define DWC_OTG_EP_SPEED_HIGH 2 + + /** + * Endpoint type. + * One of the following values: + * - DWC_OTG_EP_TYPE_CONTROL: 0 + * - DWC_OTG_EP_TYPE_ISOC: 1 + * - DWC_OTG_EP_TYPE_BULK: 2 + * - DWC_OTG_EP_TYPE_INTR: 3 + */ + unsigned ep_type:2; + + /** Max packet size in bytes */ + unsigned max_packet:11; + + /** + * PID for initial transaction. + * 0: DATA0,
+ * 1: DATA2,
+ * 2: DATA1,
+ * 3: MDATA (non-Control EP), + * SETUP (Control EP) + */ + unsigned data_pid_start:2; +#define DWC_OTG_HC_PID_DATA0 0 +#define DWC_OTG_HC_PID_DATA2 1 +#define DWC_OTG_HC_PID_DATA1 2 +#define DWC_OTG_HC_PID_MDATA 3 +#define DWC_OTG_HC_PID_SETUP 3 + + /** Number of periodic transactions per (micro)frame */ + unsigned multi_count:2; + + /** @name Transfer State */ + /** @{ */ + + /** Pointer to the current transfer buffer position. */ + uint8_t *xfer_buff; + /** + * In Buffer DMA mode this buffer will be used + * if xfer_buff is not DWORD aligned. + */ + dwc_dma_t align_buff; + /** Total number of bytes to transfer. */ + uint32_t xfer_len; + /** Number of bytes transferred so far. */ + uint32_t xfer_count; + /** Packet count at start of transfer.*/ + uint16_t start_pkt_count; + + /** + * Flag to indicate whether the transfer has been started. Set to 1 if + * it has been started, 0 otherwise. + */ + uint8_t xfer_started; + + /** + * Set to 1 to indicate that a PING request should be issued on this + * channel. If 0, process normally. + */ + uint8_t do_ping; + + /** + * Set to 1 to indicate that the error count for this transaction is + * non-zero. Set to 0 if the error count is 0. + */ + uint8_t error_state; + + /** + * Set to 1 to indicate that this channel should be halted the next + * time a request is queued for the channel. This is necessary in + * slave mode if no request queue space is available when an attempt + * is made to halt the channel. + */ + uint8_t halt_on_queue; + + /** + * Set to 1 if the host channel has been halted, but the core is not + * finished flushing queued requests. Otherwise 0. + */ + uint8_t halt_pending; + + /** + * Reason for halting the host channel. + */ + dwc_otg_halt_status_e halt_status; + + /* + * Split settings for the host channel + */ + uint8_t do_split; /**< Enable split for the channel */ + uint8_t complete_split; /**< Enable complete split */ + uint8_t hub_addr; /**< Address of high speed hub */ + + uint8_t port_addr; /**< Port of the low/full speed device */ + /** Split transaction position + * One of the following values: + * - DWC_HCSPLIT_XACTPOS_MID + * - DWC_HCSPLIT_XACTPOS_BEGIN + * - DWC_HCSPLIT_XACTPOS_END + * - DWC_HCSPLIT_XACTPOS_ALL */ + uint8_t xact_pos; + + /** Set when the host channel does a short read. */ + uint8_t short_read; + + /** + * Number of requests issued for this channel since it was assigned to + * the current transfer (not counting PINGs). + */ + uint8_t requests; + + /** + * Queue Head for the transfer being processed by this channel. + */ + struct dwc_otg_qh *qh; + + /** @} */ + + /** Entry in list of host channels. */ + DWC_CIRCLEQ_ENTRY(dwc_hc) hc_list_entry; + + /** @name Descriptor DMA support */ + /** @{ */ + + /** Number of Transfer Descriptors */ + uint16_t ntd; + + /** Descriptor List DMA address */ + dwc_dma_t desc_list_addr; + + /** Scheduling micro-frame bitmap. */ + uint8_t schinfo; + + /** @} */ +} dwc_hc_t; + +/** + * The following parameters may be specified when starting the module. These + * parameters define how the DWC_otg controller should be configured. + */ +typedef struct dwc_otg_core_params { + int32_t opt; + + /** + * Specifies the OTG capabilities. The driver will automatically + * detect the value for this parameter if none is specified. + * 0 - HNP and SRP capable (default) + * 1 - SRP Only capable + * 2 - No HNP/SRP capable + */ + int32_t otg_cap; + + /** + * Specifies whether to use slave or DMA mode for accessing the data + * FIFOs. The driver will automatically detect the value for this + * parameter if none is specified. + * 0 - Slave + * 1 - DMA (default, if available) + */ + int32_t dma_enable; + + /** + * When DMA mode is enabled specifies whether to use address DMA or DMA + * Descriptor mode for accessing the data FIFOs in device mode. The driver + * will automatically detect the value for this if none is specified. + * 0 - address DMA + * 1 - DMA Descriptor(default, if available) + */ + int32_t dma_desc_enable; + /** The DMA Burst size (applicable only for External DMA + * Mode). 1, 4, 8 16, 32, 64, 128, 256 (default 32) + */ + int32_t dma_burst_size; /* Translate this to GAHBCFG values */ + + /** + * Specifies the maximum speed of operation in host and device mode. + * The actual speed depends on the speed of the attached device and + * the value of phy_type. The actual speed depends on the speed of the + * attached device. + * 0 - High Speed (default) + * 1 - Full Speed + */ + int32_t speed; + /** Specifies whether low power mode is supported when attached + * to a Full Speed or Low Speed device in host mode. + * 0 - Don't support low power mode (default) + * 1 - Support low power mode + */ + int32_t host_support_fs_ls_low_power; + + /** Specifies the PHY clock rate in low power mode when connected to a + * Low Speed device in host mode. This parameter is applicable only if + * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS + * then defaults to 6 MHZ otherwise 48 MHZ. + * + * 0 - 48 MHz + * 1 - 6 MHz + */ + int32_t host_ls_low_power_phy_clk; + + /** + * 0 - Use cC FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default) + */ + int32_t enable_dynamic_fifo; + + /** Total number of 4-byte words in the data FIFO memory. This + * memory includes the Rx FIFO, non-periodic Tx FIFO, and periodic + * Tx FIFOs. + * 32 to 32768 (default 8192) + * Note: The total FIFO memory depth in the FPGA configuration is 8192. + */ + int32_t data_fifo_size; + + /** Number of 4-byte words in the Rx FIFO in device mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1064) + */ + int32_t dev_rx_fifo_size; + + /** Number of 4-byte words in the non-periodic Tx FIFO in device mode + * when dynamic FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ + int32_t dev_nperio_tx_fifo_size; + + /** Number of 4-byte words in each of the periodic Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. + * 4 to 768 (default 256) + */ + uint32_t dev_perio_tx_fifo_size[MAX_PERIO_FIFOS]; + + /** Number of 4-byte words in the Rx FIFO in host mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ + int32_t host_rx_fifo_size; + + /** Number of 4-byte words in the non-periodic Tx FIFO in host mode + * when Dynamic FIFO sizing is enabled in the core. + * 16 to 32768 (default 1024) + */ + int32_t host_nperio_tx_fifo_size; + + /** Number of 4-byte words in the host periodic Tx FIFO when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ + int32_t host_perio_tx_fifo_size; + + /** The maximum transfer size supported in bytes. + * 2047 to 65,535 (default 65,535) + */ + int32_t max_transfer_size; + + /** The maximum number of packets in a transfer. + * 15 to 511 (default 511) + */ + int32_t max_packet_count; + + /** The number of host channel registers to use. + * 1 to 16 (default 12) + * Note: The FPGA configuration supports a maximum of 12 host channels. + */ + int32_t host_channels; + + /** The number of endpoints in addition to EP0 available for device + * mode operations. + * 1 to 15 (default 6 IN and OUT) + * Note: The FPGA configuration supports a maximum of 6 IN and OUT + * endpoints in addition to EP0. + */ + int32_t dev_endpoints; + + /** + * Specifies the type of PHY interface to use. By default, the driver + * will automatically detect the phy_type. + * + * 0 - Full Speed PHY + * 1 - UTMI+ (default) + * 2 - ULPI + */ + int32_t phy_type; + + /** + * Specifies the UTMI+ Data Width. This parameter is + * applicable for a PHY_TYPE of UTMI+ or ULPI. (For a ULPI + * PHY_TYPE, this parameter indicates the data width between + * the MAC and the ULPI Wrapper.) Also, this parameter is + * applicable only if the OTG_HSPHY_WIDTH cC parameter was set + * to "8 and 16 bits", meaning that the core has been + * configured to work at either data path width. + * + * 8 or 16 bits (default 16) + */ + int32_t phy_utmi_width; + + /** + * Specifies whether the ULPI operates at double or single + * data rate. This parameter is only applicable if PHY_TYPE is + * ULPI. + * + * 0 - single data rate ULPI interface with 8 bit wide data + * bus (default) + * 1 - double data rate ULPI interface with 4 bit wide data + * bus + */ + int32_t phy_ulpi_ddr; + + /** + * Specifies whether to use the internal or external supply to + * drive the vbus with a ULPI phy. + */ + int32_t phy_ulpi_ext_vbus; + + /** + * Specifies whether to use the I2Cinterface for full speed PHY. This + * parameter is only applicable if PHY_TYPE is FS. + * 0 - No (default) + * 1 - Yes + */ + int32_t i2c_enable; + + int32_t ulpi_fs_ls; + + int32_t ts_dline; + + /** + * Specifies whether dedicated transmit FIFOs are + * enabled for non periodic IN endpoints in device mode + * 0 - No + * 1 - Yes + */ + int32_t en_multiple_tx_fifo; + + /** Number of 4-byte words in each of the Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. + * 4 to 768 (default 256) + */ + uint32_t dev_tx_fifo_size[MAX_TX_FIFOS]; + + /** Thresholding enable flag- + * bit 0 - enable non-ISO Tx thresholding + * bit 1 - enable ISO Tx thresholding + * bit 2 - enable Rx thresholding + */ + uint32_t thr_ctl; + + /** Thresholding length for Tx + * FIFOs in 32 bit DWORDs + */ + uint32_t tx_thr_length; + + /** Thresholding length for Rx + * FIFOs in 32 bit DWORDs + */ + uint32_t rx_thr_length; + + /** + * Specifies whether LPM (Link Power Management) support is enabled + */ + int32_t lpm_enable; + + /** Per Transfer Interrupt + * mode enable flag + * 1 - Enabled + * 0 - Disabled + */ + int32_t pti_enable; + + /** Multi Processor Interrupt + * mode enable flag + * 1 - Enabled + * 0 - Disabled + */ + int32_t mpi_enable; + + /** IS_USB Capability + * 1 - Enabled + * 0 - Disabled + */ + int32_t ic_usb_cap; + + /** AHB Threshold Ratio + * 2'b00 AHB Threshold = MAC Threshold + * 2'b01 AHB Threshold = 1/2 MAC Threshold + * 2'b10 AHB Threshold = 1/4 MAC Threshold + * 2'b11 AHB Threshold = 1/8 MAC Threshold + */ + int32_t ahb_thr_ratio; + + /** ADP Support + * 1 - Enabled + * 0 - Disabled + */ + int32_t adp_supp_enable; + + /** HFIR Reload Control + * 0 - The HFIR cannot be reloaded dynamically. + * 1 - Allow dynamic reloading of the HFIR register during runtime. + */ + int32_t reload_ctl; + + /** DCFG: Enable device Out NAK + * 0 - The core does not set NAK after Bulk Out transfer complete. + * 1 - The core sets NAK after Bulk OUT transfer complete. + */ + int32_t dev_out_nak; + + /** DCFG: Enable Continue on BNA + * After receiving BNA interrupt the core disables the endpoint,when the + * endpoint is re-enabled by the application the core starts processing + * 0 - from the DOEPDMA descriptor + * 1 - from the descriptor which received the BNA. + */ + int32_t cont_on_bna; + + /** GAHBCFG: AHB Single Support + * This bit when programmed supports SINGLE transfers for remainder + * data in a transfer for DMA mode of operation. + * 0 - in this case the remainder data will be sent using INCR burst size. + * 1 - in this case the remainder data will be sent using SINGLE burst size. + */ + int32_t ahb_single; + + /** Core Power down mode + * 0 - No Power Down is enabled + * 1 - Reserved + * 2 - Complete Power Down (Hibernation) + */ + int32_t power_down; + + /** OTG revision supported + * 0 - OTG 1.3 revision + * 1 - OTG 2.0 revision + */ + int32_t otg_ver; + +} dwc_otg_core_params_t; + +#ifdef DEBUG +struct dwc_otg_core_if; +typedef struct hc_xfer_info { + struct dwc_otg_core_if *core_if; + dwc_hc_t *hc; +} hc_xfer_info_t; +#endif + +typedef struct ep_xfer_info { + struct dwc_otg_core_if *core_if; + dwc_ep_t *ep; + uint8_t state; +} ep_xfer_info_t; +/* + * Device States + */ +typedef enum dwc_otg_lx_state { + /** On state */ + DWC_OTG_L0, + /** LPM sleep state*/ + DWC_OTG_L1, + /** USB suspend state*/ + DWC_OTG_L2, + /** Off state*/ + DWC_OTG_L3 +} dwc_otg_lx_state_e; + +struct dwc_otg_global_regs_backup { + uint32_t gotgctl_local; + uint32_t gintmsk_local; + uint32_t gahbcfg_local; + uint32_t gusbcfg_local; + uint32_t grxfsiz_local; + uint32_t gnptxfsiz_local; +#ifdef CONFIG_USB_DWC_OTG_LPM + uint32_t glpmcfg_local; +#endif + uint32_t gi2cctl_local; + uint32_t hptxfsiz_local; + uint32_t pcgcctl_local; + uint32_t gdfifocfg_local; + uint32_t dtxfsiz_local[MAX_EPS_CHANNELS]; + uint32_t gpwrdn_local; + uint32_t xhib_pcgcctl; + uint32_t xhib_gpwrdn; +}; + +struct dwc_otg_host_regs_backup { + uint32_t hcfg_local; + uint32_t haintmsk_local; + uint32_t hcintmsk_local[MAX_EPS_CHANNELS]; + uint32_t hprt0_local; + uint32_t hfir_local; +}; + +struct dwc_otg_dev_regs_backup { + uint32_t dcfg; + uint32_t dctl; + uint32_t daintmsk; + uint32_t diepmsk; + uint32_t doepmsk; + uint32_t diepctl[MAX_EPS_CHANNELS]; + uint32_t dieptsiz[MAX_EPS_CHANNELS]; + uint32_t diepdma[MAX_EPS_CHANNELS]; +}; +/** + * The dwc_otg_core_if structure contains information needed to manage + * the DWC_otg controller acting in either host or device mode. It + * represents the programming view of the controller as a whole. + */ +struct dwc_otg_core_if { + /** Parameters that define how the core should be configured.*/ + dwc_otg_core_params_t *core_params; + + /** Core Global registers starting at offset 000h. */ + dwc_otg_core_global_regs_t *core_global_regs; + + /** Device-specific information */ + dwc_otg_dev_if_t *dev_if; + /** Host-specific information */ + dwc_otg_host_if_t *host_if; + + /** Value from SNPSID register */ + uint32_t snpsid; + + /* + * Set to 1 if the core PHY interface bits in USBCFG have been + * initialized. + */ + uint8_t phy_init_done; + + /* + * SRP Success flag, set by srp success interrupt in FS I2C mode + */ + uint8_t srp_success; + uint8_t srp_timer_started; + /** Timer for SRP. If it expires before SRP is successful + * clear the SRP. */ + dwc_timer_t *srp_timer; + +#ifdef DWC_DEV_SRPCAP + /* This timer is needed to power on the hibernated host core if SRP is not + * initiated on connected SRP capable device for limited period of time + */ + uint8_t pwron_timer_started; + dwc_timer_t *pwron_timer; +#endif + /* Common configuration information */ + /** Power and Clock Gating Control Register */ + volatile uint32_t *pcgcctl; +#define DWC_OTG_PCGCCTL_OFFSET 0xE00 + + /** Push/pop addresses for endpoints or host channels.*/ + uint32_t *data_fifo[MAX_EPS_CHANNELS]; +#define DWC_OTG_DATA_FIFO_OFFSET 0x1000 +#define DWC_OTG_DATA_FIFO_SIZE 0x1000 + + /** Total RAM for FIFOs (Bytes) */ + uint16_t total_fifo_size; + /** Size of Rx FIFO (Bytes) */ + uint16_t rx_fifo_size; + /** Size of Non-periodic Tx FIFO (Bytes) */ + uint16_t nperio_tx_fifo_size; + + /** 1 if DMA is enabled, 0 otherwise. */ + uint8_t dma_enable; + + /** 1 if DMA descriptor is enabled, 0 otherwise. */ + uint8_t dma_desc_enable; + + /** 1 if PTI Enhancement mode is enabled, 0 otherwise. */ + uint8_t pti_enh_enable; + + /** 1 if MPI Enhancement mode is enabled, 0 otherwise. */ + uint8_t multiproc_int_enable; + + /** 1 if dedicated Tx FIFOs are enabled, 0 otherwise. */ + uint8_t en_multiple_tx_fifo; + + /** Set to 1 if multiple packets of a high-bandwidth transfer is in + * process of being queued */ + uint8_t queuing_high_bandwidth; + + /** Hardware Configuration -- stored here for convenience.*/ + hwcfg1_data_t hwcfg1; + hwcfg2_data_t hwcfg2; + hwcfg3_data_t hwcfg3; + hwcfg4_data_t hwcfg4; + fifosize_data_t hptxfsiz; + + /** Host and Device Configuration -- stored here for convenience.*/ + hcfg_data_t hcfg; + dcfg_data_t dcfg; + + /** The operational State, during transations + * (a_host>>a_peripherial and b_device=>b_host) this may not + * match the core but allows the software to determine + * transitions. + */ + uint8_t op_state; + + /** + * Set to 1 if the HCD needs to be restarted on a session request + * interrupt. This is required if no connector ID status change has + * occurred since the HCD was last disconnected. + */ + uint8_t restart_hcd_on_session_req; + + /** HCD callbacks */ + /** A-Device is a_host */ +#define A_HOST (1) + /** A-Device is a_suspend */ +#define A_SUSPEND (2) + /** A-Device is a_peripherial */ +#define A_PERIPHERAL (3) + /** B-Device is operating as a Peripheral. */ +#define B_PERIPHERAL (4) + /** B-Device is operating as a Host. */ +#define B_HOST (5) + + /** HCD callbacks */ + struct dwc_otg_cil_callbacks *hcd_cb; + /** PCD callbacks */ + struct dwc_otg_cil_callbacks *pcd_cb; + + /** Device mode Periodic Tx FIFO Mask */ + uint32_t p_tx_msk; + /** Device mode Periodic Tx FIFO Mask */ + uint32_t tx_msk; + + /** Workqueue object used for handling several interrupts */ + dwc_workq_t *wq_otg; + + /** Timer object used for handling "Wakeup Detected" Interrupt */ + dwc_timer_t *wkp_timer; + /** This arrays used for debug purposes for DEV OUT NAK enhancement */ + uint32_t start_doeptsiz_val[MAX_EPS_CHANNELS]; + ep_xfer_info_t ep_xfer_info[MAX_EPS_CHANNELS]; + dwc_timer_t *ep_xfer_timer[MAX_EPS_CHANNELS]; +#ifdef DEBUG + uint32_t start_hcchar_val[MAX_EPS_CHANNELS]; + + hc_xfer_info_t hc_xfer_info[MAX_EPS_CHANNELS]; + dwc_timer_t *hc_xfer_timer[MAX_EPS_CHANNELS]; + + uint32_t hfnum_7_samples; + uint64_t hfnum_7_frrem_accum; + uint32_t hfnum_0_samples; + uint64_t hfnum_0_frrem_accum; + uint32_t hfnum_other_samples; + uint64_t hfnum_other_frrem_accum; +#endif + +#ifdef DWC_UTE_CFI + uint16_t pwron_rxfsiz; + uint16_t pwron_gnptxfsiz; + uint16_t pwron_txfsiz[15]; + + uint16_t init_rxfsiz; + uint16_t init_gnptxfsiz; + uint16_t init_txfsiz[15]; +#endif + + /** Lx state of device */ + dwc_otg_lx_state_e lx_state; + + /** Saved Core Global registers */ + struct dwc_otg_global_regs_backup *gr_backup; + /** Saved Host registers */ + struct dwc_otg_host_regs_backup *hr_backup; + /** Saved Device registers */ + struct dwc_otg_dev_regs_backup *dr_backup; + + /** Power Down Enable */ + uint32_t power_down; + + /** ADP support Enable */ + uint32_t adp_enable; + + /** ADP structure object */ + dwc_otg_adp_t adp; + + /** hibernation/suspend flag */ + int hibernation_suspend; + + /** Device mode extended hibernation flag */ + int xhib; + + /** OTG revision supported */ + uint32_t otg_ver; + + /** OTG status flag used for HNP polling */ + uint8_t otg_sts; + + /** Pointer to either hcd->lock or pcd->lock */ + dwc_spinlock_t *lock; + + /** Start predict NextEP based on Learning Queue if equal 1, + * also used as counter of disabled NP IN EP's */ + uint8_t start_predict; + + /** NextEp sequence, including EP0: nextep_seq[] = EP if non-periodic and + * active, 0xff otherwise */ + uint8_t nextep_seq[MAX_EPS_CHANNELS]; + + /** Index of fisrt EP in nextep_seq array which should be re-enabled **/ + uint8_t first_in_nextep_seq; + + /** Frame number while entering to ISR - needed for ISOCs **/ + uint32_t frame_num; + +}; + +/** DWC_otg request structure. + * This structure is a list of requests. + */ + +typedef struct dwc_otg_pcd_request { + void *priv; + void *buf; + dwc_dma_t dma; + uint32_t num_mapped_sgs; + struct scatterlist *sg; + uint32_t length; + uint32_t actual; + + unsigned short_packet:1; + unsigned sent_zlp:1; + unsigned mapped;//for DMA transfer lee + /** + * Used instead of original buffer if + * it(physical address) is not dword-aligned. + **/ + uint8_t *dw_align_buf; + dwc_dma_t dw_align_buf_dma; + + DWC_CIRCLEQ_ENTRY(dwc_otg_pcd_request) queue_entry; +#ifdef DWC_UTE_PER_IO + struct dwc_iso_xreq_port ext_req; + //void *priv_ereq_nport; /* */ +#endif +} dwc_otg_pcd_request_t; + +DWC_CIRCLEQ_HEAD(req_list, dwc_otg_pcd_request); + + +#ifdef DEBUG +/* + * This function is called when transfer is timed out. + */ +extern void hc_xfer_timeout(void *ptr); +#endif + +/* + * This function is called when transfer is timed out on endpoint. + */ +extern void ep_xfer_timeout(void *ptr); + +/* + * The following functions are functions for works + * using during handling some interrupts + */ +extern void w_conn_id_status_change(void *p); + +extern void w_wakeup_detected(void *p); + +/** Saves global register values into system memory. */ +extern int dwc_otg_save_global_regs(dwc_otg_core_if_t * core_if); +/** Saves device register values into system memory. */ +extern int dwc_otg_save_dev_regs(dwc_otg_core_if_t * core_if); +/** Saves host register values into system memory. */ +extern int dwc_otg_save_host_regs(dwc_otg_core_if_t * core_if); +/** Restore global register values. */ +extern int dwc_otg_restore_global_regs(dwc_otg_core_if_t * core_if); +/** Restore host register values. */ +extern int dwc_otg_restore_host_regs(dwc_otg_core_if_t * core_if, int reset); +/** Restore device register values. */ +extern int dwc_otg_restore_dev_regs(dwc_otg_core_if_t * core_if, + int rem_wakeup); +extern int restore_lpm_i2c_regs(dwc_otg_core_if_t * core_if); +extern int restore_essential_regs(dwc_otg_core_if_t * core_if, int rmode, + int is_host); + +extern int dwc_otg_host_hibernation_restore(dwc_otg_core_if_t * core_if, + int restore_mode, int reset); +extern int dwc_otg_device_hibernation_restore(dwc_otg_core_if_t * core_if, + int rem_wakeup, int reset); + +/* + * The following functions support initialization of the CIL driver component + * and the DWC_otg controller. + */ +extern void dwc_otg_core_host_init(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_core_dev_init(dwc_otg_core_if_t * _core_if); + +/** @name Device CIL Functions + * The following functions support managing the DWC_otg controller in device + * mode. + */ +/**@{*/ +extern void dwc_otg_wakeup(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_read_setup_packet(dwc_otg_core_if_t * _core_if, + uint32_t * _dest); +extern uint32_t dwc_otg_get_frame_number(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_ep0_activate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep); +extern void dwc_otg_ep_activate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep); +extern void dwc_otg_ep_deactivate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep); +extern void dwc_otg_ep_start_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep, + dwc_otg_pcd_request_t *req); +extern void dwc_otg_ep_start_zl_transfer(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_ep0_start_transfer(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_ep0_continue_transfer(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_ep_write_packet(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep, int _dma); +extern void dwc_otg_ep_set_stall(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep); +extern void dwc_otg_ep_clear_stall(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_enable_device_interrupts(dwc_otg_core_if_t * _core_if); + +#ifdef DWC_EN_ISOC +extern void dwc_otg_iso_ep_start_frm_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep); +extern void dwc_otg_iso_ep_start_buf_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep); +#endif /* DWC_EN_ISOC */ +/**@}*/ + +/** @name Host CIL Functions + * The following functions support managing the DWC_otg controller in host + * mode. + */ +/**@{*/ +extern void dwc_otg_hc_init(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc); +extern void dwc_otg_hc_halt(dwc_otg_core_if_t * _core_if, + dwc_hc_t * _hc, dwc_otg_halt_status_e _halt_status); +extern void dwc_otg_hc_cleanup(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc); +extern void dwc_otg_hc_start_transfer(dwc_otg_core_if_t * _core_if, + dwc_hc_t * _hc); +extern int dwc_otg_hc_continue_transfer(dwc_otg_core_if_t * _core_if, + dwc_hc_t * _hc); +extern void dwc_otg_hc_do_ping(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc); +extern void dwc_otg_hc_write_packet(dwc_otg_core_if_t * _core_if, + dwc_hc_t * _hc); +extern void dwc_otg_enable_host_interrupts(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_disable_host_interrupts(dwc_otg_core_if_t * _core_if); + +extern void dwc_otg_hc_start_transfer_ddma(dwc_otg_core_if_t * core_if, + dwc_hc_t * hc); + +extern uint32_t calc_frame_interval(dwc_otg_core_if_t * core_if); + +/* Macro used to clear one channel interrupt */ +#define clear_hc_int(_hc_regs_, _intr_) \ +do { \ + hcint_data_t hcint_clear = {.d32 = 0}; \ + hcint_clear.b._intr_ = 1; \ + DWC_WRITE_REG32(&(_hc_regs_)->hcint, hcint_clear.d32); \ +} while (0) + +/* + * Macro used to disable one channel interrupt. Channel interrupts are + * disabled when the channel is halted or released by the interrupt handler. + * There is no need to handle further interrupts of that type until the + * channel is re-assigned. In fact, subsequent handling may cause crashes + * because the channel structures are cleaned up when the channel is released. + */ +#define disable_hc_int(_hc_regs_, _intr_) \ +do { \ + hcintmsk_data_t hcintmsk = {.d32 = 0}; \ + hcintmsk.b._intr_ = 1; \ + DWC_MODIFY_REG32(&(_hc_regs_)->hcintmsk, hcintmsk.d32, 0); \ +} while (0) + +/** + * This function Reads HPRT0 in preparation to modify. It keeps the + * WC bits 0 so that if they are read as 1, they won't clear when you + * write it back + */ +static inline uint32_t dwc_otg_read_hprt0(dwc_otg_core_if_t * _core_if) +{ + hprt0_data_t hprt0; + hprt0.d32 = DWC_READ_REG32(_core_if->host_if->hprt0); + hprt0.b.prtena = 0; + hprt0.b.prtconndet = 0; + hprt0.b.prtenchng = 0; + hprt0.b.prtovrcurrchng = 0; + return hprt0.d32; +} + +/**@}*/ + +/** @name Common CIL Functions + * The following functions support managing the DWC_otg controller in either + * device or host mode. + */ +/**@{*/ + +extern void dwc_otg_read_packet(dwc_otg_core_if_t * core_if, + uint8_t * dest, uint16_t bytes); + +extern void dwc_otg_flush_tx_fifo(dwc_otg_core_if_t * _core_if, const int _num); +extern void dwc_otg_flush_rx_fifo(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_core_reset(dwc_otg_core_if_t * _core_if); + +/** + * This function returns the Core Interrupt register. + */ +static inline uint32_t dwc_otg_read_core_intr(dwc_otg_core_if_t * core_if) +{ + return (DWC_READ_REG32(&core_if->core_global_regs->gintsts) & + DWC_READ_REG32(&core_if->core_global_regs->gintmsk)); +} + +/** + * This function returns the OTG Interrupt register. + */ +static inline uint32_t dwc_otg_read_otg_intr(dwc_otg_core_if_t * core_if) +{ + return (DWC_READ_REG32(&core_if->core_global_regs->gotgint)); +} + +/** + * This function reads the Device All Endpoints Interrupt register and + * returns the IN endpoint interrupt bits. + */ +static inline uint32_t dwc_otg_read_dev_all_in_ep_intr(dwc_otg_core_if_t * + core_if) +{ + + uint32_t v; + + if (core_if->multiproc_int_enable) { + v = DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->deachint) & + DWC_READ_REG32(&core_if-> + dev_if->dev_global_regs->deachintmsk); + } else { + v = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daint) & + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk); + } + return (v & 0xffff); +} + +/** + * This function reads the Device All Endpoints Interrupt register and + * returns the OUT endpoint interrupt bits. + */ +static inline uint32_t dwc_otg_read_dev_all_out_ep_intr(dwc_otg_core_if_t * + core_if) +{ + uint32_t v; + + if (core_if->multiproc_int_enable) { + v = DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->deachint) & + DWC_READ_REG32(&core_if-> + dev_if->dev_global_regs->deachintmsk); + } else { + v = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daint) & + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk); + } + + return ((v & 0xffff0000) >> 16); +} + +/** + * This function returns the Device IN EP Interrupt register + */ +static inline uint32_t dwc_otg_read_dev_in_ep_intr(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + uint32_t v, msk, emp; + + if (core_if->multiproc_int_enable) { + msk = + DWC_READ_REG32(&dev_if-> + dev_global_regs->diepeachintmsk[ep->num]); + emp = + DWC_READ_REG32(&dev_if-> + dev_global_regs->dtknqr4_fifoemptymsk); + msk |= ((emp >> ep->num) & 0x1) << 7; + v = DWC_READ_REG32(&dev_if->in_ep_regs[ep->num]->diepint) & msk; + } else { + msk = DWC_READ_REG32(&dev_if->dev_global_regs->diepmsk); + emp = + DWC_READ_REG32(&dev_if-> + dev_global_regs->dtknqr4_fifoemptymsk); + msk |= ((emp >> ep->num) & 0x1) << 7; + v = DWC_READ_REG32(&dev_if->in_ep_regs[ep->num]->diepint) & msk; + } + + return v; +} + +/** + * This function returns the Device OUT EP Interrupt register + */ +static inline uint32_t dwc_otg_read_dev_out_ep_intr(dwc_otg_core_if_t * + _core_if, dwc_ep_t * _ep) +{ + dwc_otg_dev_if_t *dev_if = _core_if->dev_if; + uint32_t v; + doepmsk_data_t msk = {.d32 = 0 }; + + if (_core_if->multiproc_int_enable) { + msk.d32 = + DWC_READ_REG32(&dev_if-> + dev_global_regs->doepeachintmsk[_ep->num]); + if (_core_if->pti_enh_enable) { + msk.b.pktdrpsts = 1; + } + v = DWC_READ_REG32(&dev_if-> + out_ep_regs[_ep->num]->doepint) & msk.d32; + } else { + msk.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->doepmsk); + if (_core_if->pti_enh_enable) { + msk.b.pktdrpsts = 1; + } + v = DWC_READ_REG32(&dev_if-> + out_ep_regs[_ep->num]->doepint) & msk.d32; + } + return v; +} + +/** + * This function returns the Host All Channel Interrupt register + */ +static inline uint32_t dwc_otg_read_host_all_channels_intr(dwc_otg_core_if_t * + _core_if) +{ + return (DWC_READ_REG32(&_core_if->host_if->host_global_regs->haint)); +} + +static inline uint32_t dwc_otg_read_host_channel_intr(dwc_otg_core_if_t * + _core_if, dwc_hc_t * _hc) +{ + return (DWC_READ_REG32 + (&_core_if->host_if->hc_regs[_hc->hc_num]->hcint)); +} + +/** + * This function returns the mode of the operation, host or device. + * + * @return 0 - Device Mode, 1 - Host Mode + */ +static inline uint32_t dwc_otg_mode(dwc_otg_core_if_t * _core_if) +{ + return (DWC_READ_REG32(&_core_if->core_global_regs->gintsts) & 0x1); +} + +/**@}*/ + +/** + * DWC_otg CIL callback structure. This structure allows the HCD and + * PCD to register functions used for starting and stopping the PCD + * and HCD for role change on for a DRD. + */ +typedef struct dwc_otg_cil_callbacks { + /** Start function for role change */ + int (*start) (void *_p); + /** Stop Function for role change */ + int (*stop) (void *_p); + /** Disconnect Function for role change */ + int (*disconnect) (void *_p); + /** Resume/Remote wakeup Function */ + int (*resume_wakeup) (void *_p); + /** Suspend function */ + int (*suspend) (void *_p); + /** Session Start (SRP) */ + int (*session_start) (void *_p); +#ifdef CONFIG_USB_DWC_OTG_LPM + /** Sleep (switch to L0 state) */ + int (*sleep) (void *_p); +#endif + /** Pointer passed to start() and stop() */ + void *p; +} dwc_otg_cil_callbacks_t; + +extern void dwc_otg_cil_register_pcd_callbacks(dwc_otg_core_if_t * _core_if, + dwc_otg_cil_callbacks_t * _cb, + void *_p); +extern void dwc_otg_cil_register_hcd_callbacks(dwc_otg_core_if_t * _core_if, + dwc_otg_cil_callbacks_t * _cb, + void *_p); + +void dwc_otg_initiate_srp(dwc_otg_core_if_t * core_if); + +////////////////////////////////////////////////////////////////////// +/** Start the HCD. Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_start(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->start) { + core_if->hcd_cb->start(core_if->hcd_cb->p); + } +} + +/** Stop the HCD. Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_stop(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->stop) { + core_if->hcd_cb->stop(core_if->hcd_cb->p); + } +} + +/** Disconnect the HCD. Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_disconnect(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->disconnect) { + core_if->hcd_cb->disconnect(core_if->hcd_cb->p); + } +} + +/** Inform the HCD the a New Session has begun. Helper function for + * using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_session_start(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->session_start) { + core_if->hcd_cb->session_start(core_if->hcd_cb->p); + } +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** + * Inform the HCD about LPM sleep. + * Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_sleep(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->sleep) { + core_if->hcd_cb->sleep(core_if->hcd_cb->p); + } +} +#endif + +/** Resume the HCD. Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_resume(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->resume_wakeup) { + core_if->hcd_cb->resume_wakeup(core_if->hcd_cb->p); + } +} + +/** Start the PCD. Helper function for using the PCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_pcd_start(dwc_otg_core_if_t * core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->start) { + core_if->pcd_cb->start(core_if->pcd_cb->p); + } +} + +/** Stop the PCD. Helper function for using the PCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_pcd_stop(dwc_otg_core_if_t * core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->stop) { + core_if->pcd_cb->stop(core_if->pcd_cb->p); + } +} + +/** Suspend the PCD. Helper function for using the PCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_pcd_suspend(dwc_otg_core_if_t * core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->suspend) { + core_if->pcd_cb->suspend(core_if->pcd_cb->p); + } +} + +/** Resume the PCD. Helper function for using the PCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_pcd_resume(dwc_otg_core_if_t * core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } +} + +////////////////////////////////////////////////////////////////////// + +#endif diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_cil_intr.c b/drivers/usb/gadget/dwc_otg/dwc_otg_cil_intr.c new file mode 100644 index 00000000..5e2037a1 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_cil_intr.c @@ -0,0 +1,857 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil_intr.c $ + * $Revision: #32 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * + * The Core Interface Layer provides basic services for accessing and + * managing the DWC_otg hardware. These services are used by both the + * Host Controller Driver and the Peripheral Controller Driver. + * + * This file contains the Common Interrupt handlers. + */ +#include "dwc_os.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_pcd.h" +#include "dwc_otg_hcd.h" + +#ifdef DEBUG +inline const char *op_state_str(dwc_otg_core_if_t * core_if) +{ + return (core_if->op_state == A_HOST ? "a_host" : + (core_if->op_state == A_SUSPEND ? "a_suspend" : + (core_if->op_state == A_PERIPHERAL ? "a_peripheral" : + (core_if->op_state == B_PERIPHERAL ? "b_peripheral" : + (core_if->op_state == B_HOST ? "b_host" : "unknown"))))); +} +#endif + +/** This function will log a debug message + * + * @param core_if Programming view of DWC_otg controller. + */ +int32_t dwc_otg_handle_mode_mismatch_intr(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + DWC_WARN("Mode Mismatch Interrupt: currently in %s mode\n", + dwc_otg_mode(core_if) ? "Host" : "Device"); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.modemismatch = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + return 1; +} + +/** + * This function handles the OTG Interrupts. It reads the OTG + * Interrupt Register (GOTGINT) to determine what interrupt has + * occurred. + * + * @param core_if Programming view of DWC_otg controller. + */ +int32_t dwc_otg_handle_otg_intr(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gotgint_data_t gotgint; + gotgctl_data_t gotgctl; + gintmsk_data_t gintmsk; + gpwrdn_data_t gpwrdn; + + gotgint.d32 = DWC_READ_REG32(&global_regs->gotgint); + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + DWC_DEBUGPL(DBG_CIL, "++OTG Interrupt gotgint=%0x [%s]\n", gotgint.d32, + op_state_str(core_if)); + + if (gotgint.b.sesenddet) { + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " + "Session End Detected++ (%s)\n", + op_state_str(core_if)); + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + + if (core_if->op_state == B_HOST) { + cil_pcd_start(core_if); + core_if->op_state = B_PERIPHERAL; + } else { + /* If not B_HOST and Device HNP still set. HNP + * Did not succeed!*/ + if (gotgctl.b.devhnpen) { + DWC_DEBUGPL(DBG_ANY, "Session End Detected\n"); + __DWC_ERROR("Device Not Connected/Responding!\n"); + } + + /* If Session End Detected the B-Cable has + * been disconnected. */ + /* Reset PCD and Gadget driver to a + * clean state. */ + core_if->lx_state = DWC_OTG_L0; + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_stop(core_if); + DWC_SPINLOCK(core_if->lock); + + if (core_if->adp_enable) { + if (core_if->power_down == 2) { + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs-> + gpwrdn, gpwrdn.d32, 0); + } + + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + + dwc_otg_adp_sense_start(core_if); + } + } + + gotgctl.d32 = 0; + gotgctl.b.devhnpen = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0); + } + if (gotgint.b.sesreqsucstschng) { + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " + "Session Reqeust Success Status Change++\n"); + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + if (gotgctl.b.sesreqscs) { + + if ((core_if->core_params->phy_type == + DWC_PHY_TYPE_PARAM_FS) && (core_if->core_params->i2c_enable)) { + core_if->srp_success = 1; + } else { + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_resume(core_if); + DWC_SPINLOCK(core_if->lock); + /* Clear Session Request */ + gotgctl.d32 = 0; + gotgctl.b.sesreq = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, + gotgctl.d32, 0); + } + } + } + if (gotgint.b.hstnegsucstschng) { + /* Print statements during the HNP interrupt handling + * can cause it to fail.*/ + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + /* WA for 3.00a- HW is not setting cur_mode, even sometimes + * this does not help*/ + if (core_if->snpsid >= OTG_CORE_REV_3_00a) + dwc_udelay(100); + if (gotgctl.b.hstnegscs) { + if (dwc_otg_is_host_mode(core_if)) { + core_if->op_state = B_HOST; + /* + * Need to disable SOF interrupt immediately. + * When switching from device to host, the PCD + * interrupt handler won't handle the + * interrupt if host mode is already set. The + * HCD interrupt handler won't get called if + * the HCD state is HALT. This means that the + * interrupt does not get handled and Linux + * complains loudly. + */ + gintmsk.d32 = 0; + gintmsk.b.sofintr = 1; + DWC_MODIFY_REG32(&global_regs->gintmsk, + gintmsk.d32, 0); + /* Call callback function with spin lock released */ + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_stop(core_if); + /* + * Initialize the Core for Host mode. + */ + cil_hcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = B_HOST; + } + } else { + gotgctl.d32 = 0; + gotgctl.b.hnpreq = 1; + gotgctl.b.devhnpen = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0); + DWC_DEBUGPL(DBG_ANY, "HNP Failed\n"); + __DWC_ERROR("Device Not Connected/Responding\n"); + } + } + if (gotgint.b.hstnegdet) { + /* The disconnect interrupt is set at the same time as + * Host Negotiation Detected. During the mode + * switch all interrupts are cleared so the disconnect + * interrupt handler will not get executed. + */ + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " + "Host Negotiation Detected++ (%s)\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : + "Device")); + if (dwc_otg_is_device_mode(core_if)) { + DWC_DEBUGPL(DBG_ANY, "a_suspend->a_peripheral (%d)\n", + core_if->op_state); + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_disconnect(core_if); + cil_pcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = A_PERIPHERAL; + } else { + /* + * Need to disable SOF interrupt immediately. When + * switching from device to host, the PCD interrupt + * handler won't handle the interrupt if host mode is + * already set. The HCD interrupt handler won't get + * called if the HCD state is HALT. This means that + * the interrupt does not get handled and Linux + * complains loudly. + */ + gintmsk.d32 = 0; + gintmsk.b.sofintr = 1; + DWC_MODIFY_REG32(&global_regs->gintmsk, gintmsk.d32, 0); + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_stop(core_if); + cil_hcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = A_HOST; + } + } + if (gotgint.b.adevtoutchng) { + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " + "A-Device Timeout Change++\n"); + } + if (gotgint.b.debdone) { + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " "Debounce Done++\n"); + } + + /* Clear GOTGINT */ + DWC_WRITE_REG32(&core_if->core_global_regs->gotgint, gotgint.d32); + + return 1; +} + +void w_conn_id_status_change(void *p) +{ + dwc_otg_core_if_t *core_if = p; + uint32_t count = 0; + gotgctl_data_t gotgctl = {.d32 = 0 }; + + gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + DWC_DEBUGPL(DBG_CIL, "gotgctl=%0x\n", gotgctl.d32); + DWC_DEBUGPL(DBG_CIL, "gotgctl.b.conidsts=%d\n", gotgctl.b.conidsts); + + /* B-Device connector (Device Mode) */ + if (gotgctl.b.conidsts) { + /* Wait for switch to device mode. */ + while (!dwc_otg_is_device_mode(core_if)) { + DWC_PRINTF("Waiting for Peripheral Mode, Mode=%s\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : + "Peripheral")); + dwc_mdelay(100); + if (++count > 10000) + break; + } + DWC_ASSERT(++count < 10000, + "Connection id status change timed out"); + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } else { + /* A-Device connector (Host Mode) */ + while (!dwc_otg_is_host_mode(core_if)) { + DWC_PRINTF("Waiting for Host Mode, Mode=%s\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : + "Peripheral")); + dwc_mdelay(100); + if (++count > 10000) + break; + } + DWC_ASSERT(++count < 10000, + "Connection id status change timed out"); + core_if->op_state = A_HOST; + /* + * Initialize the Core for Host mode. + */ + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + } +} + +/** + * This function handles the Connector ID Status Change Interrupt. It + * reads the OTG Interrupt Register (GOTCTL) to determine whether this + * is a Device to Host Mode transition or a Host Mode to Device + * Transition. + * + * This only occurs when the cable is connected/removed from the PHY + * connector. + * + * @param core_if Programming view of DWC_otg controller. + */ +int32_t dwc_otg_handle_conn_id_status_change_intr(dwc_otg_core_if_t * core_if) +{ + + /* + * Need to disable SOF interrupt immediately. If switching from device + * to host, the PCD interrupt handler won't handle the interrupt if + * host mode is already set. The HCD interrupt handler won't get + * called if the HCD state is HALT. This means that the interrupt does + * not get handled and Linux complains loudly. + */ + gintmsk_data_t gintmsk = {.d32 = 0 }; + gintsts_data_t gintsts = {.d32 = 0 }; + + gintmsk.b.sofintr = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32, 0); + + DWC_DEBUGPL(DBG_CIL, + " ++Connector ID Status Change Interrupt++ (%s)\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : "Device")); + + DWC_SPINUNLOCK(core_if->lock); + + /* + * Need to schedule a work, as there are possible DELAY function calls + * Release lock before scheduling workq as it holds spinlock during scheduling + */ + + DWC_WORKQ_SCHEDULE(core_if->wq_otg, w_conn_id_status_change, + core_if, "connection id status change"); + DWC_SPINLOCK(core_if->lock); + + /* Set flag and clear interrupt */ + gintsts.b.conidstschng = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This interrupt indicates that a device is initiating the Session + * Request Protocol to request the host to turn on bus power so a new + * session can begin. The handler responds by turning on bus power. If + * the DWC_otg controller is in low power mode, the handler brings the + * controller out of low power mode before turning on bus power. + * + * @param core_if Programming view of DWC_otg controller. + */ +int32_t dwc_otg_handle_session_req_intr(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + +#ifndef DWC_HOST_ONLY + DWC_DEBUGPL(DBG_ANY, "++Session Request Interrupt++\n"); + + if (dwc_otg_is_device_mode(core_if)) { + DWC_PRINTF("SRP: Device mode\n"); + } else { + hprt0_data_t hprt0; + DWC_PRINTF("SRP: Host mode\n"); + + /* Turn on the port power bit. */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* Start the Connection timer. So a message can be displayed + * if connect does not occur within 10 seconds. */ + cil_hcd_session_start(core_if); + } +#endif + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.sessreqintr = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +void w_wakeup_detected(void *p) +{ + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) p; + /* + * Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms + * so that OPT tests pass with all PHYs). + */ + hprt0_data_t hprt0 = {.d32 = 0 }; +#if 0 + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + /* Restart the Phy Clock */ + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + dwc_udelay(10); +#endif //0 + hprt0.d32 = dwc_otg_read_hprt0(core_if); + DWC_DEBUGPL(DBG_ANY, "Resume: HPRT0=%0x\n", hprt0.d32); +// dwc_mdelay(70); + hprt0.b.prtres = 0; /* Resume */ + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + DWC_DEBUGPL(DBG_ANY, "Clear Resume: HPRT0=%0x\n", + DWC_READ_REG32(core_if->host_if->hprt0)); + + cil_hcd_resume(core_if); + + /** Change to L0 state*/ + core_if->lx_state = DWC_OTG_L0; +} + +/** + * This interrupt indicates that the DWC_otg controller has detected a + * resume or remote wakeup sequence. If the DWC_otg controller is in + * low power mode, the handler must brings the controller out of low + * power mode. The controller automatically begins resume + * signaling. The handler schedules a time to stop resume signaling. + */ +int32_t dwc_otg_handle_wakeup_detected_intr(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + + DWC_DEBUGPL(DBG_ANY, + "++Resume and Remote Wakeup Detected Interrupt++\n"); + + DWC_PRINTF("%s lxstate = %d\n", __func__, core_if->lx_state); + + if (dwc_otg_is_device_mode(core_if)) { + dctl_data_t dctl = {.d32 = 0 }; + DWC_DEBUGPL(DBG_PCD, "DSTS=0x%0x\n", + DWC_READ_REG32(&core_if->dev_if->dev_global_regs-> + dsts)); + if (core_if->lx_state == DWC_OTG_L2) { +#ifdef PARTIAL_POWER_DOWN + if (core_if->hwcfg4.b.power_optimiz) { + pcgcctl_data_t power = {.d32 = 0 }; + + power.d32 = DWC_READ_REG32(core_if->pcgcctl); + DWC_DEBUGPL(DBG_CIL, "PCGCCTL=%0x\n", + power.d32); + + power.b.stoppclk = 0; + DWC_WRITE_REG32(core_if->pcgcctl, power.d32); + + power.b.pwrclmp = 0; + DWC_WRITE_REG32(core_if->pcgcctl, power.d32); + + power.b.rstpdwnmodule = 0; + DWC_WRITE_REG32(core_if->pcgcctl, power.d32); + } +#endif + /* Clear the Remote Wakeup Signaling */ + dctl.b.rmtwkupsig = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, dctl.d32, 0); + + DWC_SPINUNLOCK(core_if->lock); + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } + DWC_SPINLOCK(core_if->lock); + } else { + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.hird_thres &= (~(1 << 4)); + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, + lpmcfg.d32); + } + /** Change to L0 state*/ + core_if->lx_state = DWC_OTG_L0; + } else { + if (core_if->lx_state != DWC_OTG_L1) { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + + /* Restart the Phy Clock */ + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + DWC_TIMER_SCHEDULE(core_if->wkp_timer, 71); + } else { + /** Change to L0 state*/ + core_if->lx_state = DWC_OTG_L0; + } + } + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.wkupintr = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** This interrupt indicates that restore command after Hibernation + * was completed by the core. */ +int32_t dwc_otg_handle_restore_done_intr(dwc_otg_core_if_t * core_if) +{ + pcgcctl_data_t pcgcctl; + DWC_DEBUGPL(DBG_ANY, "++Restore Done Interrupt++\n"); + + //TODO De-assert restore signal. 8.a + pcgcctl.d32 = DWC_READ_REG32(core_if->pcgcctl); + if (pcgcctl.b.restoremode == 1) { + gintmsk_data_t gintmsk = {.d32 = 0 }; + /* + * If restore mode is Remote Wakeup, + * unmask Remote Wakeup interrupt. + */ + gintmsk.b.wkupintr = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, + 0, gintmsk.d32); + } + + return 1; +} + +#ifndef DWC_DEVICE_ONLY +/** + * This interrupt indicates that a device has been disconnected from + * the root port. + */ +int32_t otg_cable_disconnect(dwc_otg_core_if_t * core_if) +{ + + cil_hcd_disconnect(core_if); + cil_hcd_stop(core_if); + return 1; +} +#endif + +/** + * This interrupt indicates that a device has been disconnected from + * the root port. + */ +int32_t dwc_otg_handle_disconnect_intr(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + + DWC_DEBUGPL(DBG_ANY, "++Disconnect Detected Interrupt++ (%s) %s\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : "Device"), + op_state_str(core_if)); + +/** @todo Consolidate this if statement. */ +#ifndef DWC_HOST_ONLY + if (core_if->op_state == B_HOST) { + /* If in device mode Disconnect and stop the HCD, then + * start the PCD. */ + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_disconnect(core_if); + cil_pcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = B_PERIPHERAL; + } else if (dwc_otg_is_device_mode(core_if)) { + gotgctl_data_t gotgctl = {.d32 = 0 }; + gotgctl.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + if (gotgctl.b.hstsethnpen == 1) { + /* Do nothing, if HNP in process the OTG + * interrupt "Host Negotiation Detected" + * interrupt will do the mode switch. + */ + } else if (gotgctl.b.devhnpen == 0) { + /* If in device mode Disconnect and stop the HCD, then + * start the PCD. */ + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_disconnect(core_if); + cil_pcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = B_PERIPHERAL; + } else { + DWC_DEBUGPL(DBG_ANY, "!a_peripheral && !devhnpen\n"); + } + } else { + if (core_if->op_state == A_HOST) { + /* A-Cable still connected but device disconnected. */ + cil_hcd_disconnect(core_if); + if (core_if->adp_enable) { + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + cil_hcd_stop(core_if); + /* Enable Power Down Logic */ + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_otg_adp_probe_start(core_if); + + /* Power off the core */ + if (core_if->power_down == 2) { + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32 + (&core_if->core_global_regs->gpwrdn, + gpwrdn.d32, 0); + } + } + } + } +#endif + /* Change to L3(OFF) state */ + core_if->lx_state = DWC_OTG_L3; + + gintsts.d32 = 0; + gintsts.b.disconnect = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + return 1; +} + +/** + * This interrupt indicates that SUSPEND state has been detected on + * the USB. + * + * For HNP the USB Suspend interrupt signals the change from + * "a_peripheral" to "a_host". + * + * When power management is enabled the core will be put in low power + * mode. + */ +int32_t dwc_otg_handle_usb_suspend_intr(dwc_otg_core_if_t * core_if) +{ + dsts_data_t dsts; + gintsts_data_t gintsts; + + DWC_DEBUGPL(DBG_ANY, "USB SUSPEND\n"); + + if (dwc_otg_is_device_mode(core_if)) { + /* Check the Device status register to determine if the Suspend + * state is active. */ + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + DWC_DEBUGPL(DBG_PCD, "DSTS=0x%0x\n", dsts.d32); + DWC_DEBUGPL(DBG_PCD, "DSTS.Suspend Status=%d " + "HWCFG4.power Optimize=%d\n", + dsts.b.suspsts, core_if->hwcfg4.b.power_optimiz); + +#ifdef PARTIAL_POWER_DOWN +/** @todo Add a module parameter for power management. */ + + if (dsts.b.suspsts && core_if->hwcfg4.b.power_optimiz) { + pcgcctl_data_t power = {.d32 = 0 }; + DWC_DEBUGPL(DBG_CIL, "suspend\n"); + + power.b.pwrclmp = 1; + DWC_WRITE_REG32(core_if->pcgcctl, power.d32); + + power.b.rstpdwnmodule = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, power.d32); + + power.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, power.d32); + + } else { + DWC_DEBUGPL(DBG_ANY, "disconnect?\n"); + } +#endif + /* PCD callback for suspend. Release the lock inside of callback function */ + cil_pcd_suspend(core_if); + } else { + if (core_if->op_state == A_PERIPHERAL) { + DWC_DEBUGPL(DBG_ANY, "a_peripheral->a_host\n"); + /* Clear the a_peripheral flag, back to a_host. */ + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_stop(core_if); + cil_hcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = A_HOST; + } + } + + /* Change to L2(suspend) state */ + core_if->lx_state = DWC_OTG_L2; + + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.usbsuspend = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** + * This function hadles LPM transaction received interrupt. + */ +static int32_t dwc_otg_handle_lpm_intr(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + gintsts_data_t gintsts; + + if (!core_if->core_params->lpm_enable) { + DWC_PRINTF("Unexpected LPM interrupt\n"); + } + + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + DWC_PRINTF("LPM config register = 0x%08x\n", lpmcfg.d32); + + if (dwc_otg_is_host_mode(core_if)) { + cil_hcd_sleep(core_if); + } else { + lpmcfg.b.hird_thres |= (1 << 4); + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, + lpmcfg.d32); + } + + /* Examine prt_sleep_sts after TL1TokenTetry period max (10 us) */ + dwc_udelay(10); + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + if (lpmcfg.b.prt_sleep_sts) { + /* Save the current state */ + core_if->lx_state = DWC_OTG_L1; + } + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.lpmtranrcvd = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + return 1; +} +#endif /* CONFIG_USB_DWC_OTG_LPM */ + +/** + * This function returns the Core Interrupt register. + */ +static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if) +{ + gahbcfg_data_t gahbcfg = {.d32 = 0 }; + gintsts_data_t gintsts; + gintmsk_data_t gintmsk; + gintmsk_data_t gintmsk_common = {.d32 = 0 }; + gintmsk_common.b.wkupintr = 1; + gintmsk_common.b.sessreqintr = 1; + gintmsk_common.b.conidstschng = 1; + gintmsk_common.b.otgintr = 1; + gintmsk_common.b.modemismatch = 1; + gintmsk_common.b.disconnect = 1; + gintmsk_common.b.usbsuspend = 1; +#ifdef CONFIG_USB_DWC_OTG_LPM + gintmsk_common.b.lpmtranrcvd = 1; +#endif + gintmsk_common.b.restoredone = 1; + /** @todo: The port interrupt occurs while in device + * mode. Added code to CIL to clear the interrupt for now! + */ + gintmsk_common.b.portintr = 1; + + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + gahbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg); + +#ifdef DEBUG + /* if any common interrupts set */ + if (gintsts.d32 & gintmsk_common.d32) { + DWC_DEBUGPL(DBG_ANY, "gintsts=%08x gintmsk=%08x\n", + gintsts.d32, gintmsk.d32); + } +#endif + if (gahbcfg.b.glblintrmsk) + return ((gintsts.d32 & gintmsk.d32) & gintmsk_common.d32); + else + return 0; + +} + +/* MACRO for clearing interupt bits in GPWRDN register */ +#define CLEAR_GPWRDN_INTR(__core_if,__intr) \ +do { \ + gpwrdn_data_t gpwrdn = {.d32=0}; \ + gpwrdn.b.__intr = 1; \ + DWC_MODIFY_REG32(&__core_if->core_global_regs->gpwrdn, \ + 0, gpwrdn.d32); \ +} while (0) + +/** + * Common interrupt handler. + * + * The common interrupts are those that occur in both Host and Device mode. + * This handler handles the following interrupts: + * - Mode Mismatch Interrupt + * - Disconnect Interrupt + * - OTG Interrupt + * - Connector ID Status Change Interrupt + * - Session Request Interrupt. + * - Resume / Remote Wakeup Detected Interrupt. + * - LPM Transaction Received Interrupt + * - ADP Transaction Received Interrupt + * + */ +int32_t dwc_otg_handle_common_intr(dwc_otg_core_if_t *core_if) +{/**should notice the param of this function**/ + int retval = 0; + gintsts_data_t gintsts; + + if (core_if->lock) + DWC_SPINLOCK(core_if->lock); + + gintsts.d32 = dwc_otg_read_common_intr(core_if); + if (gintsts.b.modemismatch) { + retval |= dwc_otg_handle_mode_mismatch_intr(core_if); + } + if (gintsts.b.otgintr) { + retval |= dwc_otg_handle_otg_intr(core_if); + } + if (gintsts.b.conidstschng) { + retval |= + dwc_otg_handle_conn_id_status_change_intr(core_if); + } + if (gintsts.b.disconnect) { + retval |= dwc_otg_handle_disconnect_intr(core_if); + } + if (gintsts.b.sessreqintr) { + retval |= dwc_otg_handle_session_req_intr(core_if); + } + if (gintsts.b.wkupintr) { + retval |= dwc_otg_handle_wakeup_detected_intr(core_if); + } + if (gintsts.b.usbsuspend) { + retval |= dwc_otg_handle_usb_suspend_intr(core_if); + } +#ifdef CONFIG_USB_DWC_OTG_LPM + if (gintsts.b.lpmtranrcvd) { + retval |= dwc_otg_handle_lpm_intr(core_if); + } +#endif + if (gintsts.b.portintr && dwc_otg_is_device_mode(core_if)) { + /* The port interrupt occurs while in device mode with HPRT0 + * Port Enable/Disable. + */ + gintsts.d32 = 0; + gintsts.b.portintr = 1; + dwc_write_reg32(&core_if->core_global_regs->gintsts, + gintsts.d32); + retval |= 1; + } + if (core_if->lock) + DWC_SPINUNLOCK(core_if->lock); + + return retval; +} diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_core_if.h b/drivers/usb/gadget/dwc_otg/dwc_otg_core_if.h new file mode 100644 index 00000000..8c49df6c --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_core_if.h @@ -0,0 +1,706 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_core_if.h $ + * $Revision: #13 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#if !defined(__DWC_CORE_IF_H__) +#define __DWC_CORE_IF_H__ + +#include "dwc_os.h" + +/** @file + * This file defines DWC_OTG Core API + */ + +struct dwc_otg_core_if; +typedef struct dwc_otg_core_if dwc_otg_core_if_t; + +/** Maximum number of Periodic FIFOs */ +#define MAX_PERIO_FIFOS 15 +/** Maximum number of Periodic FIFOs */ +#define MAX_TX_FIFOS 15 + +/** Maximum number of Endpoints/HostChannels */ +#define MAX_EPS_CHANNELS 16 + +/** SPRAM address configuration */ +#define SPRAM_START_ADDR 0x00028000 +#define SPRAM_END_ADDR 0x000280e0 + +extern dwc_otg_core_if_t *dwc_otg_cil_init(const uint32_t * _reg_base_addr); +extern void dwc_otg_core_init(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_cil_remove(dwc_otg_core_if_t * _core_if); + +extern void dwc_otg_enable_global_interrupts(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_disable_global_interrupts(dwc_otg_core_if_t * _core_if); + +extern uint8_t dwc_otg_is_device_mode(dwc_otg_core_if_t * _core_if); +extern uint8_t dwc_otg_is_host_mode(dwc_otg_core_if_t * _core_if); + +extern uint8_t dwc_otg_is_dma_enable(dwc_otg_core_if_t * core_if); + +/** This function should be called on every hardware interrupt. */ +extern int32_t dwc_otg_handle_common_intr(dwc_otg_core_if_t *core_if); + +#ifndef DWC_DEVICE_ONLY +extern void dwc_otg_core_fore_host(dwc_otg_core_if_t * core_if); +extern int32_t otg_cable_disconnect(dwc_otg_core_if_t * core_if); +#endif +/** @name OTG Core Parameters */ +/** @{ */ + +/** + * Specifies the OTG capabilities. The driver will automatically + * detect the value for this parameter if none is specified. + * 0 - HNP and SRP capable (default) + * 1 - SRP Only capable + * 2 - No HNP/SRP capable + */ +extern int dwc_otg_set_param_otg_cap(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_otg_cap(dwc_otg_core_if_t * core_if); +#define DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE 0 +#define DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE 1 +#define DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE 2 +#define dwc_param_otg_cap_default DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE + +extern int dwc_otg_set_param_opt(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_opt(dwc_otg_core_if_t * core_if); +#define dwc_param_opt_default 1 + +/** + * Specifies whether to use slave or DMA mode for accessing the data + * FIFOs. The driver will automatically detect the value for this + * parameter if none is specified. + * 0 - Slave + * 1 - DMA (default, if available) + */ +extern int dwc_otg_set_param_dma_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dma_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_dma_enable_default 1 + +/** + * When DMA mode is enabled specifies whether to use + * address DMA or DMA Descritor mode for accessing the data + * FIFOs in device mode. The driver will automatically detect + * the value for this parameter if none is specified. + * 0 - address DMA + * 1 - DMA Descriptor(default, if available) + */ +extern int dwc_otg_set_param_dma_desc_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dma_desc_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_dma_desc_enable_default 1 + +/** The DMA Burst size (applicable only for External DMA + * Mode). 1, 4, 8 16, 32, 64, 128, 256 (default 32) + */ +extern int dwc_otg_set_param_dma_burst_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dma_burst_size(dwc_otg_core_if_t * core_if); +#define dwc_param_dma_burst_size_default 32 + +/** + * Specifies the maximum speed of operation in host and device mode. + * The actual speed depends on the speed of the attached device and + * the value of phy_type. The actual speed depends on the speed of the + * attached device. + * 0 - High Speed (default) + * 1 - Full Speed + */ +extern int dwc_otg_set_param_speed(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_speed(dwc_otg_core_if_t * core_if); +#define dwc_param_speed_default 0 +#define DWC_SPEED_PARAM_HIGH 0 +#define DWC_SPEED_PARAM_FULL 1 + +/** Specifies whether low power mode is supported when attached + * to a Full Speed or Low Speed device in host mode. + * 0 - Don't support low power mode (default) + * 1 - Support low power mode + */ +extern int dwc_otg_set_param_host_support_fs_ls_low_power(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_host_support_fs_ls_low_power(dwc_otg_core_if_t + * core_if); +#define dwc_param_host_support_fs_ls_low_power_default 0 + +/** Specifies the PHY clock rate in low power mode when connected to a + * Low Speed device in host mode. This parameter is applicable only if + * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS + * then defaults to 6 MHZ otherwise 48 MHZ. + * + * 0 - 48 MHz + * 1 - 6 MHz + */ +extern int dwc_otg_set_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * + core_if); +#define dwc_param_host_ls_low_power_phy_clk_default 0 +#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ 0 +#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ 1 + +/** + * 0 - Use cC FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default) + */ +extern int dwc_otg_set_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_enable_dynamic_fifo(dwc_otg_core_if_t * + core_if); +#define dwc_param_enable_dynamic_fifo_default 1 + +/** Total number of 4-byte words in the data FIFO memory. This + * memory includes the Rx FIFO, non-periodic Tx FIFO, and periodic + * Tx FIFOs. + * 32 to 32768 (default 8192) + * Note: The total FIFO memory depth in the FPGA configuration is 8192. + */ +extern int dwc_otg_set_param_data_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_data_fifo_size(dwc_otg_core_if_t * core_if); +#define dwc_param_data_fifo_size_default 8192 + +/** Number of 4-byte words in the Rx FIFO in device mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1064) + */ +extern int dwc_otg_set_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if); +#define dwc_param_dev_rx_fifo_size_default 1064 + +/** Number of 4-byte words in the non-periodic Tx FIFO in device mode + * when dynamic FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ +extern int dwc_otg_set_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * + core_if); +#define dwc_param_dev_nperio_tx_fifo_size_default 1024 + +/** Number of 4-byte words in each of the periodic Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. + * 4 to 768 (default 256) + */ +extern int dwc_otg_set_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val, int fifo_num); +extern int32_t dwc_otg_get_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * + core_if, int fifo_num); +#define dwc_param_dev_perio_tx_fifo_size_default 256 + +/** Number of 4-byte words in the Rx FIFO in host mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ +extern int dwc_otg_set_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if); +#define dwc_param_host_rx_fifo_size_default 1024 + +/** Number of 4-byte words in the non-periodic Tx FIFO in host mode + * when Dynamic FIFO sizing is enabled in the core. + * 16 to 32768 (default 1024) + */ +extern int dwc_otg_set_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * + core_if); +#define dwc_param_host_nperio_tx_fifo_size_default 1024 + +/** Number of 4-byte words in the host periodic Tx FIFO when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ +extern int dwc_otg_set_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * + core_if); +#define dwc_param_host_perio_tx_fifo_size_default 1024 + +/** The maximum transfer size supported in bytes. + * 2047 to 65,535 (default 65,535) + */ +extern int dwc_otg_set_param_max_transfer_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_max_transfer_size(dwc_otg_core_if_t * core_if); +#define dwc_param_max_transfer_size_default 65535 + +/** The maximum number of packets in a transfer. + * 15 to 511 (default 511) + */ +extern int dwc_otg_set_param_max_packet_count(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_max_packet_count(dwc_otg_core_if_t * core_if); +#define dwc_param_max_packet_count_default 511 + +/** The number of host channel registers to use. + * 1 to 16 (default 12) + * Note: The FPGA configuration supports a maximum of 12 host channels. + */ +extern int dwc_otg_set_param_host_channels(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_host_channels(dwc_otg_core_if_t * core_if); +#define dwc_param_host_channels_default 12 + +/** The number of endpoints in addition to EP0 available for device + * mode operations. + * 1 to 15 (default 6 IN and OUT) + * Note: The FPGA configuration supports a maximum of 6 IN and OUT + * endpoints in addition to EP0. + */ +extern int dwc_otg_set_param_dev_endpoints(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dev_endpoints(dwc_otg_core_if_t * core_if); +#define dwc_param_dev_endpoints_default 6 + +/** + * Specifies the type of PHY interface to use. By default, the driver + * will automatically detect the phy_type. + * + * 0 - Full Speed PHY + * 1 - UTMI+ (default) + * 2 - ULPI + */ +extern int dwc_otg_set_param_phy_type(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_phy_type(dwc_otg_core_if_t * core_if); +#define DWC_PHY_TYPE_PARAM_FS 0 +#define DWC_PHY_TYPE_PARAM_UTMI 1 +#define DWC_PHY_TYPE_PARAM_ULPI 2 +#define dwc_param_phy_type_default DWC_PHY_TYPE_PARAM_UTMI + +/** + * Specifies the UTMI+ Data Width. This parameter is + * applicable for a PHY_TYPE of UTMI+ or ULPI. (For a ULPI + * PHY_TYPE, this parameter indicates the data width between + * the MAC and the ULPI Wrapper.) Also, this parameter is + * applicable only if the OTG_HSPHY_WIDTH cC parameter was set + * to "8 and 16 bits", meaning that the core has been + * configured to work at either data path width. + * + * 8 or 16 bits (default 16) + */ +extern int dwc_otg_set_param_phy_utmi_width(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_phy_utmi_width(dwc_otg_core_if_t * core_if); +#define dwc_param_phy_utmi_width_default 16 + +/** + * Specifies whether the ULPI operates at double or single + * data rate. This parameter is only applicable if PHY_TYPE is + * ULPI. + * + * 0 - single data rate ULPI interface with 8 bit wide data + * bus (default) + * 1 - double data rate ULPI interface with 4 bit wide data + * bus + */ +extern int dwc_otg_set_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if); +#define dwc_param_phy_ulpi_ddr_default 0 + +/** + * Specifies whether to use the internal or external supply to + * drive the vbus with a ULPI phy. + */ +extern int dwc_otg_set_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if); +#define DWC_PHY_ULPI_INTERNAL_VBUS 0 +#define DWC_PHY_ULPI_EXTERNAL_VBUS 1 +#define dwc_param_phy_ulpi_ext_vbus_default DWC_PHY_ULPI_INTERNAL_VBUS + +/** + * Specifies whether to use the I2Cinterface for full speed PHY. This + * parameter is only applicable if PHY_TYPE is FS. + * 0 - No (default) + * 1 - Yes + */ +extern int dwc_otg_set_param_i2c_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_i2c_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_i2c_enable_default 0 + +extern int dwc_otg_set_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if); +#define dwc_param_ulpi_fs_ls_default 0 + +extern int dwc_otg_set_param_ts_dline(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_ts_dline(dwc_otg_core_if_t * core_if); +#define dwc_param_ts_dline_default 0 + +/** + * Specifies whether dedicated transmit FIFOs are + * enabled for non periodic IN endpoints in device mode + * 0 - No + * 1 - Yes + */ +extern int dwc_otg_set_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_en_multiple_tx_fifo(dwc_otg_core_if_t * + core_if); +#define dwc_param_en_multiple_tx_fifo_default 1 + +/** Number of 4-byte words in each of the Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. + * 4 to 768 (default 256) + */ +extern int dwc_otg_set_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, + int fifo_num, int32_t val); +extern int32_t dwc_otg_get_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, + int fifo_num); +#define dwc_param_dev_tx_fifo_size_default 768 + +/** Thresholding enable flag- + * bit 0 - enable non-ISO Tx thresholding + * bit 1 - enable ISO Tx thresholding + * bit 2 - enable Rx thresholding + */ +extern int dwc_otg_set_param_thr_ctl(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_thr_ctl(dwc_otg_core_if_t * core_if, int fifo_num); +#define dwc_param_thr_ctl_default 0 + +/** Thresholding length for Tx + * FIFOs in 32 bit DWORDs + */ +extern int dwc_otg_set_param_tx_thr_length(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_tx_thr_length(dwc_otg_core_if_t * core_if); +#define dwc_param_tx_thr_length_default 64 + +/** Thresholding length for Rx + * FIFOs in 32 bit DWORDs + */ +extern int dwc_otg_set_param_rx_thr_length(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_rx_thr_length(dwc_otg_core_if_t * core_if); +#define dwc_param_rx_thr_length_default 64 + +/** + * Specifies whether LPM (Link Power Management) support is enabled + */ +extern int dwc_otg_set_param_lpm_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_lpm_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_lpm_enable_default 1 + +/** + * Specifies whether PTI enhancement is enabled + */ +extern int dwc_otg_set_param_pti_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_pti_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_pti_enable_default 0 + +/** + * Specifies whether MPI enhancement is enabled + */ +extern int dwc_otg_set_param_mpi_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_mpi_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_mpi_enable_default 0 + +/** + * Specifies whether ADP capability is enabled + */ +extern int dwc_otg_set_param_adp_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_adp_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_adp_enable_default 0 + +/** + * Specifies whether IC_USB capability is enabled + */ + +extern int dwc_otg_set_param_ic_usb_cap(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_ic_usb_cap(dwc_otg_core_if_t * core_if); +#define dwc_param_ic_usb_cap_default 0 + +extern int dwc_otg_set_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if); +#define dwc_param_ahb_thr_ratio_default 0 + +extern int dwc_otg_set_param_power_down(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_power_down(dwc_otg_core_if_t * core_if); +#define dwc_param_power_down_default 0 + +extern int dwc_otg_set_param_reload_ctl(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_reload_ctl(dwc_otg_core_if_t * core_if); +#define dwc_param_reload_ctl_default 0 + +extern int dwc_otg_set_param_dev_out_nak(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dev_out_nak(dwc_otg_core_if_t * core_if); +#define dwc_param_dev_out_nak_default 1 + +extern int dwc_otg_set_param_cont_on_bna(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_cont_on_bna(dwc_otg_core_if_t * core_if); +#define dwc_param_cont_on_bna_default 0 + +extern int dwc_otg_set_param_ahb_single(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_ahb_single(dwc_otg_core_if_t * core_if); +#define dwc_param_ahb_single_default 0 + +extern int dwc_otg_set_param_otg_ver(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_otg_ver(dwc_otg_core_if_t * core_if); +#define dwc_param_otg_ver_default 0 + +/** @} */ + +/** @name Access to registers and bit-fields */ + +/** + * Dump core registers and SPRAM + */ +extern void dwc_otg_dump_dev_registers(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_dump_spram(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_dump_host_registers(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_dump_global_registers(dwc_otg_core_if_t * _core_if); + +/** + * Get host negotiation status. + */ +extern uint32_t dwc_otg_get_hnpstatus(dwc_otg_core_if_t * core_if); + +/** + * Get srp status + */ +extern uint32_t dwc_otg_get_srpstatus(dwc_otg_core_if_t * core_if); + +/** + * Set hnpreq bit in the GOTGCTL register. + */ +extern void dwc_otg_set_hnpreq(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get Content of SNPSID register. + */ +extern uint32_t dwc_otg_get_gsnpsid(dwc_otg_core_if_t * core_if); + +/** + * Get current mode. + * Returns 0 if in device mode, and 1 if in host mode. + */ +extern uint32_t dwc_otg_get_mode(dwc_otg_core_if_t * core_if); + +/** + * Get value of hnpcapable field in the GUSBCFG register + */ +extern uint32_t dwc_otg_get_hnpcapable(dwc_otg_core_if_t * core_if); +/** + * Set value of hnpcapable field in the GUSBCFG register + */ +extern void dwc_otg_set_hnpcapable(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of srpcapable field in the GUSBCFG register + */ +extern uint32_t dwc_otg_get_srpcapable(dwc_otg_core_if_t * core_if); +/** + * Set value of srpcapable field in the GUSBCFG register + */ +extern void dwc_otg_set_srpcapable(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of devspeed field in the DCFG register + */ +extern uint32_t dwc_otg_get_devspeed(dwc_otg_core_if_t * core_if); +/** + * Set value of devspeed field in the DCFG register + */ +extern void dwc_otg_set_devspeed(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get the value of busconnected field from the HPRT0 register + */ +extern uint32_t dwc_otg_get_busconnected(dwc_otg_core_if_t * core_if); + +/** + * Gets the device enumeration Speed. + */ +extern uint32_t dwc_otg_get_enumspeed(dwc_otg_core_if_t * core_if); + +/** + * Get value of prtpwr field from the HPRT0 register + */ +extern uint32_t dwc_otg_get_prtpower(dwc_otg_core_if_t * core_if); + +/** + * Get value of flag indicating core state - hibernated or not + */ +extern uint32_t dwc_otg_get_core_state(dwc_otg_core_if_t * core_if); + +/** + * Set value of prtpwr field from the HPRT0 register + */ +extern void dwc_otg_set_prtpower(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of prtsusp field from the HPRT0 regsiter + */ +extern uint32_t dwc_otg_get_prtsuspend(dwc_otg_core_if_t * core_if); +/** + * Set value of prtpwr field from the HPRT0 register + */ +extern void dwc_otg_set_prtsuspend(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of ModeChTimEn field from the HCFG regsiter + */ +extern uint32_t dwc_otg_get_mode_ch_tim(dwc_otg_core_if_t * core_if); +/** + * Set value of ModeChTimEn field from the HCFG regsiter + */ +extern void dwc_otg_set_mode_ch_tim(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of Fram Interval field from the HFIR regsiter + */ +extern uint32_t dwc_otg_get_fr_interval(dwc_otg_core_if_t * core_if); +/** + * Set value of Frame Interval field from the HFIR regsiter + */ +extern void dwc_otg_set_fr_interval(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Set value of prtres field from the HPRT0 register + *FIXME Remove? + */ +extern void dwc_otg_set_prtresume(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of rmtwkupsig bit in DCTL register + */ +extern uint32_t dwc_otg_get_remotewakesig(dwc_otg_core_if_t * core_if); + +/** + * Get value of prt_sleep_sts field from the GLPMCFG register + */ +extern uint32_t dwc_otg_get_lpm_portsleepstatus(dwc_otg_core_if_t * core_if); + +/** + * Get value of rem_wkup_en field from the GLPMCFG register + */ +extern uint32_t dwc_otg_get_lpm_remotewakeenabled(dwc_otg_core_if_t * core_if); + +/** + * Get value of appl_resp field from the GLPMCFG register + */ +extern uint32_t dwc_otg_get_lpmresponse(dwc_otg_core_if_t * core_if); +/** + * Set value of appl_resp field from the GLPMCFG register + */ +extern void dwc_otg_set_lpmresponse(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of hsic_connect field from the GLPMCFG register + */ +extern uint32_t dwc_otg_get_hsic_connect(dwc_otg_core_if_t * core_if); +/** + * Set value of hsic_connect field from the GLPMCFG register + */ +extern void dwc_otg_set_hsic_connect(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of inv_sel_hsic field from the GLPMCFG register. + */ +extern uint32_t dwc_otg_get_inv_sel_hsic(dwc_otg_core_if_t * core_if); +/** + * Set value of inv_sel_hsic field from the GLPMFG register. + */ +extern void dwc_otg_set_inv_sel_hsic(dwc_otg_core_if_t * core_if, uint32_t val); + +/* + * Some functions for accessing registers + */ + +/** + * GOTGCTL register + */ +extern uint32_t dwc_otg_get_gotgctl(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_gotgctl(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GUSBCFG register + */ +extern uint32_t dwc_otg_get_gusbcfg(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_gusbcfg(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GRXFSIZ register + */ +extern uint32_t dwc_otg_get_grxfsiz(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_grxfsiz(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GNPTXFSIZ register + */ +extern uint32_t dwc_otg_get_gnptxfsiz(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_gnptxfsiz(dwc_otg_core_if_t * core_if, uint32_t val); + +extern uint32_t dwc_otg_get_gpvndctl(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_gpvndctl(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GGPIO register + */ +extern uint32_t dwc_otg_get_ggpio(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_ggpio(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GUID register + */ +extern uint32_t dwc_otg_get_guid(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_guid(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * HPRT0 register + */ +extern uint32_t dwc_otg_get_hprt0(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_hprt0(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GHPTXFSIZE + */ +extern uint32_t dwc_otg_get_hptxfsiz(dwc_otg_core_if_t * core_if); + +/** @} */ + +#endif /* __DWC_CORE_IF_H__ */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_dbg.h b/drivers/usb/gadget/dwc_otg/dwc_otg_dbg.h new file mode 100644 index 00000000..36ff44b7 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_dbg.h @@ -0,0 +1,113 @@ +/* ========================================================================== + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#ifndef __DWC_OTG_DBG_H__ +#define __DWC_OTG_DBG_H__ + +/** @file + * This file defines debug levels. + * Debugging support vanishes in non-debug builds. + */ + +/** + * The Debug Level bit-mask variable. + */ +extern uint32_t g_dbg_lvl; +/** + * Set the Debug Level variable. + */ +static inline uint32_t SET_DEBUG_LEVEL(const uint32_t new) +{ + uint32_t old = g_dbg_lvl; + g_dbg_lvl = new; + return old; +} +//#define DEBUG +/** When debug level has the DBG_CIL bit set, display CIL Debug messages. */ +#define DBG_CIL (0x2) +/** When debug level has the DBG_CILV bit set, display CIL Verbose debug + * messages */ +#define DBG_CILV (0x20) +/** When debug level has the DBG_PCD bit set, display PCD (Device) debug + * messages */ +#define DBG_PCD (0x4) +/** When debug level has the DBG_PCDV set, display PCD (Device) Verbose debug + * messages */ +#define DBG_PCDV (0x40) +/** When debug level has the DBG_HCD bit set, display Host debug messages */ +#define DBG_HCD (0x8) +/** When debug level has the DBG_HCDV bit set, display Verbose Host debug + * messages */ +#define DBG_HCDV (0x80) +/** When debug level has the DBG_HCD_URB bit set, display enqueued URBs in host + * mode. */ +#define DBG_HCD_URB (0x800) + +/** When debug level has any bit set, display debug messages */ +#define DBG_ANY (0xFF) + +/** All debug messages off */ +#define DBG_OFF 0 + +/** Prefix string for DWC_DEBUG print macros. */ +#define USB_DWC "DWC_otg: " + +/** + * Print a debug message when the Global debug level variable contains + * the bit defined in lvl. + * + * @param[in] lvl - Debug level, use one of the DBG_ constants above. + * @param[in] x - like printf + * + * Example:

+ * + * DWC_DEBUGPL( DBG_ANY, "%s(%p)\n", __func__, _reg_base_addr); + * + *
+ * results in:
+ * + * usb-DWC_otg: dwc_otg_cil_init(ca867000) + * + */ +#ifdef DEBUG + +# define DWC_DEBUGPL(lvl, x...) do{ if ((lvl)&g_dbg_lvl)__DWC_DEBUG(USB_DWC x ); }while(0) +# define DWC_DEBUGP(x...) DWC_DEBUGPL(DBG_ANY, x ) + +# define CHK_DEBUG_LEVEL(level) ((level) & g_dbg_lvl) + +#else + +# define DWC_DEBUGPL(lvl, x...) do{}while(0) +# define DWC_DEBUGP(x...) + +# define CHK_DEBUG_LEVEL(level) (0) + +#endif /*DEBUG*/ +#endif diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_driver.c b/drivers/usb/gadget/dwc_otg/dwc_otg_driver.c new file mode 100644 index 00000000..32cb263f --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_driver.c @@ -0,0 +1,1518 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_driver.c $ + * $Revision: #71 $ + * $Date: 2009/02/04 $ + * $Change: 1179630 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * The dwc_otg_driver module provides the initialization and cleanup entry + * points for the DWC_otg driver. This module will be dynamically installed + * after Linux is booted using the insmod command. When the module is + * installed, the dwc_otg_driver_init function is called. When the module is + * removed (using rmmod), the dwc_otg_driver_cleanup function is called. + * + * This module also defines a data structure for the dwc_otg_driver, which is + * used in conjunction with the standard ARM lm_device structure. These + * structures allow the OTG driver to comply with the standard Linux driver + * model in which devices and drivers are registered with a bus driver. This + * has the benefit that Linux can expose attributes of the driver and device + * in its special sysfs file system. Users can then read or write files in + * this file system to perform diagnostics on the driver components or the + * device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include /* permission constants */ +#include +#include +#include + +# include + +#include +#include +#ifdef CONFIG_OF +#include +#include +#include +#endif + +#include "dwc_os.h" +#include "dwc_otg_dbg.h" +#include "dwc_otg_attr.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_core_if.h" +#include "dwc_otg_pcd_if.h" +#include "usb_hw.h" + +#define DWC_DRIVER_VERSION "2.81a 04-FEB-2009" +#define DWC_DRIVER_DESC "HS OTG USB Controller driver" + +/* + USB_GUSBCFG for set USC28C SPRD USB PHY Width:8/16 bits +*/ +#if defined(CONFIG_ARCH_SCX20) +unsigned int USB_GUSBCFG_REG; +#define GUSBCFG_OFFSET 0x0c +#endif + +static const char dwc_driver_name[] = "dwc_otg"; + +extern int pcd_init( + struct platform_device *_dev + ); +extern int hcd_init( + struct platform_device *_dev + ); + +extern int pcd_remove( + struct platform_device *_dev + ); + +extern void hcd_remove( + struct platform_device *_dev + ); + +extern void dwc_udc_startup( + void + ); +extern void dwc_udc_shutdown( + void + ); +extern int dwc_udc_state( + void + ); +extern void usb_phy_init( + struct platform_device *_dev + ); +/*-------------------------------------------------------------------------*/ +/* Encapsulate the module parameter settings */ + +struct dwc_otg_driver_module_params { + int32_t opt; + int32_t otg_cap; + int32_t dma_enable; + int32_t dma_desc_enable; + int32_t dma_burst_size; + int32_t speed; + int32_t host_support_fs_ls_low_power; + int32_t host_ls_low_power_phy_clk; + int32_t enable_dynamic_fifo; + int32_t data_fifo_size; + int32_t dev_rx_fifo_size; + int32_t dev_nperio_tx_fifo_size; + uint32_t dev_perio_tx_fifo_size[MAX_PERIO_FIFOS]; + int32_t host_rx_fifo_size; + int32_t host_nperio_tx_fifo_size; + int32_t host_perio_tx_fifo_size; + int32_t max_transfer_size; + int32_t max_packet_count; + int32_t host_channels; + int32_t dev_endpoints; + int32_t phy_type; + int32_t phy_utmi_width; + int32_t phy_ulpi_ddr; + int32_t phy_ulpi_ext_vbus; + int32_t i2c_enable; + int32_t ulpi_fs_ls; + int32_t ts_dline; + int32_t en_multiple_tx_fifo; + uint32_t dev_tx_fifo_size[MAX_TX_FIFOS]; + uint32_t thr_ctl; + uint32_t tx_thr_length; + uint32_t rx_thr_length; + int32_t pti_enable; + int32_t mpi_enable; + int32_t lpm_enable; + int32_t ic_usb_cap; + int32_t ahb_thr_ratio; +}; + +static struct dwc_otg_driver_module_params dwc_otg_module_params = { + .opt = -1, + .otg_cap = 2, + .dma_enable = 1, + .dma_desc_enable = 1, + .dma_burst_size = -1, + .speed = -1, + .host_support_fs_ls_low_power = -1, + .host_ls_low_power_phy_clk = -1, + .enable_dynamic_fifo = -1, + .data_fifo_size = -1, + .dev_rx_fifo_size = 0x114, + .dev_nperio_tx_fifo_size = 0x10, + .dev_perio_tx_fifo_size = { + /* dev_perio_tx_fifo_size_1 */ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + /* 15 */ + }, + .host_rx_fifo_size = -1, + .host_nperio_tx_fifo_size = -1, + .host_perio_tx_fifo_size = -1, + .max_transfer_size = -1, + .max_packet_count = -1, + .host_channels = -1, + .dev_endpoints = -1, + .phy_type = -1, + .phy_utmi_width = -1, + .phy_ulpi_ddr = -1, + .phy_ulpi_ext_vbus = -1, + .i2c_enable = -1, + .ulpi_fs_ls = -1, + .ts_dline = -1, + .en_multiple_tx_fifo = -1, +#if defined(CONFIG_ARCH_SCX35) + .dev_tx_fifo_size = { + /* dev_tx_fifo_size */ + 0x40, + 0x40, + 0x100, + 0x100, + 0x100, + 0x100, + 0x100, + 0x80, + 0x80, + 0x80, + 0x80, + 0x80, + 0x80, + 0x80, + 0x200 + /* 15 */ + }, + +#else + .dev_tx_fifo_size = { + /* dev_tx_fifo_size */ + 0x100, + 0x100, + 0x80, + 0x80, + 0x80, + 0x80, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + /* 15 */ + }, +#endif + .thr_ctl = -1, + .tx_thr_length = -1, + .rx_thr_length = -1, + .pti_enable = -1, + .mpi_enable = -1, + .lpm_enable = -1, + .ic_usb_cap = -1, + .ahb_thr_ratio = -1, +}; + +/** + * This function shows the driver whether support otg. + */ +static ssize_t is_support_otg_show(struct device_driver *dev, char *buf) +{ +#ifdef DWC_DEVICE_ONLY + return sprintf(buf, "%x\n", 0); +#else + return sprintf(buf, "%x\n", 1); +#endif +} + +static DRIVER_ATTR(is_support_otg, S_IRUGO, is_support_otg_show, NULL); + +/** + * This function shows the Driver Version. + */ +static ssize_t version_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, sizeof(DWC_DRIVER_VERSION) + 2, "%s\n", + DWC_DRIVER_VERSION); +} + +static DRIVER_ATTR(version, S_IRUGO, version_show, NULL); + +/** + * Global Debug Level Mask. + */ +uint32_t g_dbg_lvl = 0x0; /* OFF */ + +/** + * This function shows the driver Debug Level. + */ +static ssize_t dbg_level_show(struct device_driver *drv, char *buf) +{ + return sprintf(buf, "0x%0x\n", g_dbg_lvl); +} + +/** + * This function stores the driver Debug Level. + */ +static ssize_t dbg_level_store(struct device_driver *drv, const char *buf, + size_t count) +{ + g_dbg_lvl = simple_strtoul(buf, NULL, 16); + return count; +} + +static DRIVER_ATTR(debuglevel, S_IRUGO | S_IWUSR, dbg_level_show, + dbg_level_store); + + +/** + * This function shows the usb controller's power state + */ +static ssize_t dwc_udc_power_show(struct device_driver *drv, char *buf) +{ + return sprintf(buf, "%s\n", dwc_udc_state() ? "on" : "off"); +} + +/** + * This function setting the usb controller's power + */ +static ssize_t dbg_udc_power_set(struct device_driver *drv, const char *buf, + size_t count) +{ + int power; + power = simple_strtoul(buf, NULL, 16); + if (power) { + dwc_udc_startup(); + } else { + dwc_udc_shutdown(); + } + return count; +} + +static DRIVER_ATTR(udcpower, S_IRUGO | S_IWUSR, dwc_udc_power_show, + dbg_udc_power_set); + + +/** + * This function is called during module intialization + * to pass module parameters to the DWC_OTG CORE. + */ +static int set_parameters(dwc_otg_core_if_t * core_if) +{ + int retval = 0; + int i; + + if (dwc_otg_module_params.otg_cap != -1) { + retval += + dwc_otg_set_param_otg_cap(core_if, + dwc_otg_module_params.otg_cap); + } + if (dwc_otg_module_params.dma_enable != -1) { + retval += + dwc_otg_set_param_dma_enable(core_if, + dwc_otg_module_params. + dma_enable); + } + if (dwc_otg_module_params.dma_desc_enable != -1) { + retval += + dwc_otg_set_param_dma_desc_enable(core_if, + dwc_otg_module_params. + dma_desc_enable); + } + if (dwc_otg_module_params.opt != -1) { + retval += + dwc_otg_set_param_opt(core_if, dwc_otg_module_params.opt); + } + if (dwc_otg_module_params.dma_burst_size != -1) { + retval += + dwc_otg_set_param_dma_burst_size(core_if, + dwc_otg_module_params. + dma_burst_size); + } + if (dwc_otg_module_params.host_support_fs_ls_low_power != -1) { + retval += + dwc_otg_set_param_host_support_fs_ls_low_power(core_if, + dwc_otg_module_params. + host_support_fs_ls_low_power); + } + if (dwc_otg_module_params.enable_dynamic_fifo != -1) { + retval += + dwc_otg_set_param_enable_dynamic_fifo(core_if, + dwc_otg_module_params. + enable_dynamic_fifo); + } + if (dwc_otg_module_params.data_fifo_size != -1) { + retval += + dwc_otg_set_param_data_fifo_size(core_if, + dwc_otg_module_params. + data_fifo_size); + } + if (dwc_otg_module_params.dev_rx_fifo_size != -1) { + retval += + dwc_otg_set_param_dev_rx_fifo_size(core_if, + dwc_otg_module_params. + dev_rx_fifo_size); + } + if (dwc_otg_module_params.dev_nperio_tx_fifo_size != -1) { + retval += + dwc_otg_set_param_dev_nperio_tx_fifo_size(core_if, + dwc_otg_module_params. + dev_nperio_tx_fifo_size); + } + if (dwc_otg_module_params.host_rx_fifo_size != -1) { + retval += + dwc_otg_set_param_host_rx_fifo_size(core_if, + dwc_otg_module_params.host_rx_fifo_size); + } + if (dwc_otg_module_params.host_nperio_tx_fifo_size != -1) { + retval += + dwc_otg_set_param_host_nperio_tx_fifo_size(core_if, + dwc_otg_module_params. + host_nperio_tx_fifo_size); + } + if (dwc_otg_module_params.host_perio_tx_fifo_size != -1) { + retval += + dwc_otg_set_param_host_perio_tx_fifo_size(core_if, + dwc_otg_module_params. + host_perio_tx_fifo_size); + } + if (dwc_otg_module_params.max_transfer_size != -1) { + retval += + dwc_otg_set_param_max_transfer_size(core_if, + dwc_otg_module_params. + max_transfer_size); + } + if (dwc_otg_module_params.max_packet_count != -1) { + retval += + dwc_otg_set_param_max_packet_count(core_if, + dwc_otg_module_params. + max_packet_count); + } + if (dwc_otg_module_params.host_channels != -1) { + retval += + dwc_otg_set_param_host_channels(core_if, + dwc_otg_module_params. + host_channels); + } + if (dwc_otg_module_params.dev_endpoints != -1) { + retval += + dwc_otg_set_param_dev_endpoints(core_if, + dwc_otg_module_params. + dev_endpoints); + } + if (dwc_otg_module_params.phy_type != -1) { + retval += + dwc_otg_set_param_phy_type(core_if, + dwc_otg_module_params.phy_type); + } + if (dwc_otg_module_params.speed != -1) { + retval += + dwc_otg_set_param_speed(core_if, + dwc_otg_module_params.speed); + } + if (dwc_otg_module_params.host_ls_low_power_phy_clk != -1) { + retval += + dwc_otg_set_param_host_ls_low_power_phy_clk(core_if, + dwc_otg_module_params. + host_ls_low_power_phy_clk); + } + if (dwc_otg_module_params.phy_ulpi_ddr != -1) { + retval += + dwc_otg_set_param_phy_ulpi_ddr(core_if, + dwc_otg_module_params. + phy_ulpi_ddr); + } + if (dwc_otg_module_params.phy_ulpi_ext_vbus != -1) { + retval += + dwc_otg_set_param_phy_ulpi_ext_vbus(core_if, + dwc_otg_module_params. + phy_ulpi_ext_vbus); + } + if (dwc_otg_module_params.phy_utmi_width != -1) { + retval += + dwc_otg_set_param_phy_utmi_width(core_if, + dwc_otg_module_params. + phy_utmi_width); + } + if (dwc_otg_module_params.ts_dline != -1) { + retval += + dwc_otg_set_param_ts_dline(core_if, + dwc_otg_module_params.ts_dline); + } + if (dwc_otg_module_params.i2c_enable != -1) { + retval += + dwc_otg_set_param_i2c_enable(core_if, + dwc_otg_module_params. + i2c_enable); + } + if (dwc_otg_module_params.en_multiple_tx_fifo != -1) { + retval += + dwc_otg_set_param_en_multiple_tx_fifo(core_if, + dwc_otg_module_params. + en_multiple_tx_fifo); + } + for (i = 0; i < 15; i++) { + if (dwc_otg_module_params.dev_perio_tx_fifo_size[i] != -1) { + retval += + dwc_otg_set_param_dev_perio_tx_fifo_size(core_if, + dwc_otg_module_params. + dev_perio_tx_fifo_size + [i], i); + } + } + + for (i = 0; i < 15; i++) { + if (dwc_otg_module_params.dev_tx_fifo_size[i] != -1) { + retval += dwc_otg_set_param_dev_tx_fifo_size(core_if, + dwc_otg_module_params. + dev_tx_fifo_size + [i], i); + } + } + if (dwc_otg_module_params.thr_ctl != -1) { + retval += + dwc_otg_set_param_thr_ctl(core_if, + dwc_otg_module_params.thr_ctl); + } + if (dwc_otg_module_params.mpi_enable != -1) { + retval += + dwc_otg_set_param_mpi_enable(core_if, + dwc_otg_module_params. + mpi_enable); + } + if (dwc_otg_module_params.pti_enable != -1) { + retval += + dwc_otg_set_param_pti_enable(core_if, + dwc_otg_module_params. + pti_enable); + } + if (dwc_otg_module_params.lpm_enable != -1) { + retval += + dwc_otg_set_param_lpm_enable(core_if, + dwc_otg_module_params. + lpm_enable); + } + if (dwc_otg_module_params.ic_usb_cap != -1) { + retval += + dwc_otg_set_param_ic_usb_cap(core_if, + dwc_otg_module_params. + ic_usb_cap); + } + if (dwc_otg_module_params.tx_thr_length != -1) { + retval += + dwc_otg_set_param_tx_thr_length(core_if, + dwc_otg_module_params.tx_thr_length); + } + if (dwc_otg_module_params.rx_thr_length != -1) { + retval += + dwc_otg_set_param_rx_thr_length(core_if, + dwc_otg_module_params. + rx_thr_length); + } + if(dwc_otg_module_params.ahb_thr_ratio != -1) { + retval += + dwc_otg_set_param_ahb_thr_ratio(core_if, dwc_otg_module_params.ahb_thr_ratio); + } + return retval; +} + +/** + * This function is the top level interrupt handler for the Common + * (Device and host modes) interrupts. + */ +/**static irqreturn_t dwc_otg_common_irq(int irq, void *dev) +* __attribute__((unused)); +*need to confirm if need comm_irq +*/ +static irqreturn_t dwc_otg_common_irq(int irq, void *dev) +{ + dwc_otg_device_t *otg_dev = dev; + int32_t retval = IRQ_NONE; + + retval = dwc_otg_handle_common_intr(otg_dev->core_if); + if (retval != 0) { + S3C2410X_CLEAR_EINTPEND(); + } + return IRQ_RETVAL(retval); +} + +/** + * This function is called when a lm_device is unregistered with the + * dwc_otg_driver. This happens, for example, when the rmmod command is + * executed. The device may or may not be electrically present. If it is + * present, the driver stops device processing. Any resources used on behalf + * of this device are freed. + * + * @param[in] _dev + */ +static int dwc_otg_driver_remove( + struct platform_device *_dev +) + +{ + dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev); + + + DWC_DEBUGPL(DBG_ANY, "%s(%p)\n", __func__, _dev); + + if (!otg_dev) { + /* Memory allocation for the dwc_otg_device failed. */ + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__); + return -1; + } +#ifndef DWC_DEVICE_ONLY + if (otg_dev->hcd) { + hcd_remove(_dev); + } else { + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__); + return -1; + } +#endif + +#ifndef DWC_HOST_ONLY + if (otg_dev->pcd) { + pcd_remove(_dev); + } +#endif + /* + * Free the IRQ + */ +#ifndef DWC_DEVICE_ONLY + /*if otg is supported*/ + if (otg_dev->common_irq_installed) { + free_irq(platform_get_irq(_dev, 0), otg_dev); + } +#endif + if (otg_dev->core_if) { + dwc_otg_cil_remove(otg_dev->core_if); + } + + /* + * Remove the device attributes + */ + dwc_otg_attr_remove(_dev); + + /* + * Return the memory. + */ + if (otg_dev->os_dep.base) { + iounmap(otg_dev->os_dep.base); + } + dwc_free(otg_dev); +#ifdef CONFIG_OF + dwc_free(_dev->dev.platform_data ); +#endif + /* + * Clear the drvdata pointer. + */ + platform_set_drvdata(_dev, 0); + return 0; +} +#ifdef CONFIG_PM +static int dwc_otg_suspend(struct platform_device *dev, pm_message_t state) +{ + pr_info("%s\n", __func__); + return 0; +} +static int dwc_otg_resume(struct platform_device *dev) +{ + pr_info("%s\n", __func__); + return 0; +} +#else +#define dwc_otg_suspend NULL +#define dwc_otg_resume NULL +#endif +/** + * This function is called when an lm_device is bound to a + * dwc_otg_driver. It creates the driver components required to + * control the device (CIL, HCD, and PCD) and it initializes the + * device. The driver components are stored in a dwc_otg_device + * structure. A reference to the dwc_otg_device is saved in the + * lm_device. This allows the driver to access the dwc_otg_device + * structure on subsequent calls to driver methods for this device. + * + * @param[in] _dev Bus device + */ +static int dwc_otg_driver_probe( + struct platform_device *_dev +) +{ + int retval = 0; + dwc_otg_device_t *dwc_otg_device; + int irq; +#ifndef CONFIG_OF + struct sprd_usb_platform_data *pdata= _dev->dev.platform_data; +#else + int ret; + struct device_node *np = _dev->dev.of_node; + struct resource res; + + if (np) + _dev->id = of_alias_get_id(np, "usb"); + + ret = of_address_to_resource(np, 0, &res); + if(ret < 0){ + dev_err(&_dev->dev, "no reg of property specified\n"); + return NULL; + } +#endif + usb_phy_init(_dev); + + dev_dbg(&_dev->dev, "dwc_otg_driver_probe(%p)\n", _dev); + + dwc_otg_device = dwc_alloc(sizeof(dwc_otg_device_t)); + + if (!dwc_otg_device) { + dev_err(&_dev->dev, "kmalloc of dwc_otg_device failed\n"); + retval = -ENOMEM; + goto fail; + } + + memset(dwc_otg_device, 0, sizeof(*dwc_otg_device)); + dwc_otg_device->os_dep.reg_offset = 0xFFFFFFFF; + + /* + * Map the DWC_otg Core memory into virtual address space. + */ +#ifdef CONFIG_OF + dwc_otg_device->os_dep.base = (unsigned long)ioremap_nocache(res.start, + resource_size(&res)); +#if defined(CONFIG_ARCH_SCX20) + USB_GUSBCFG_REG = dwc_otg_device->os_dep.base + GUSBCFG_OFFSET; +#endif +#else + dwc_otg_device->os_dep.base = (void *)platform_get_resource(_dev, + IORESOURCE_MEM, 0)->start; +#endif + if (!dwc_otg_device->os_dep.base) { + dev_err(&_dev->dev, "ioremap() failed\n"); + retval = -ENOMEM; + goto fail; + } + dev_dbg(&_dev->dev, "base=0x%08x\n", (unsigned)dwc_otg_device->os_dep.base); + + irq = platform_get_irq(_dev, 0); + dev_dbg(&_dev->dev, "base=0x%08x irq:%d\n", (unsigned)dwc_otg_device->os_dep.base, + irq); + /* + * Initialize driver data to point to the global DWC_otg + * Device structure. + */ +#ifdef CONFIG_OF + if(np){ + struct sprd_usb_platform_data *pdata; + pdata = __DWC_ALLOC(NULL,sizeof(struct sprd_usb_platform_data)); + if(!pdata){ + dev_err(&_dev->dev, "fail to malloc memory for platform_data\n"); + return -ENOMEM; + } + if (of_property_read_u32(np, "ngpios", &pdata->gpio_num)) + { + pr_info("read gpio number error\n"); + __DWC_FREE(NULL , pdata); + return -ENODEV; + } + if (1 == pdata->gpio_num) + { + pdata->gpio_chgdet = of_get_gpio(np, 0); + if(pdata->gpio_chgdet<0){ + printk(" get charge detect error ,gpio is %d\n",pdata->gpio_chgdet); + pdata->gpio_chgdet = 0xffffffff; + } + + pdata->gpio_otgdet = 0xffffffff; + pdata->gpio_boost = 0xffffffff; + }else if(2 == pdata->gpio_num){ + pdata->gpio_chgdet = of_get_gpio(np, 0); + if(pdata->gpio_chgdet<0){ + printk(" get charge detect error ,gpio is %d\n",pdata->gpio_chgdet); + pdata->gpio_chgdet = 0xffffffff; + } + + pdata->gpio_otgdet = of_get_gpio(np, 1); + if(pdata->gpio_otgdet<0){ + printk(" get otg cable detect error ,gpio is %d\n",pdata->gpio_otgdet); + pdata->gpio_otgdet = 0xffffffff; + } + + pdata->gpio_boost = 0xffffffff; + }else if(3 == pdata->gpio_num){ + pdata->gpio_chgdet = of_get_gpio(np, 0); + if(pdata->gpio_chgdet<0){ + printk(" get charge detect error ,gpio is %d\n",pdata->gpio_chgdet); + pdata->gpio_chgdet = 0xffffffff; + } + pdata->gpio_otgdet = of_get_gpio(np, 1); + if(pdata->gpio_otgdet<0){ + printk(" get otg cable detect error ,gpio is %d\n",pdata->gpio_otgdet); + pdata->gpio_otgdet = 0xffffffff; + } + + pdata->gpio_boost = of_get_gpio(np, 2); + if(pdata->gpio_boost<0){ + printk("get boost error ,gpio is %d\n",pdata->gpio_boost); + pdata->gpio_boost = 0xffffffff; + } + }else{ + printk("gpio number more than three,gpio number %d\n",pdata->gpio_num); + } + + _dev->dev.platform_data = (void *)pdata; + memcpy(&dwc_otg_device->platform_data,pdata,sizeof(pdata)); + } +#else + memcpy(&dwc_otg_device->platform_data,pdata,sizeof(pdata)); +#endif + platform_set_drvdata(_dev, dwc_otg_device); + + dev_dbg(&_dev->dev, "dwc_otg_device=0x%p\n", dwc_otg_device); + + udc_enable(); + dwc_otg_device->core_if = dwc_otg_cil_init(dwc_otg_device->os_dep.base); + if (!dwc_otg_device->core_if) { + dev_err(&_dev->dev, "CIL initialization failed!\n"); + retval = -ENOMEM; + goto fail; + } + /* + * Attempt to ensure this device is really a DWC_otg Controller. + * Read and verify the SNPSID register contents. The value should be + * 0x45F42XXX, which corresponds to "OT2", as in "OTG version 2.XX". + */ + + if ((dwc_otg_get_gsnpsid(dwc_otg_device->core_if) & 0xFFFFF000) != + 0x4F542000) { + dev_err(&_dev->dev, "Bad value for SNPSID: 0x%08x\n", + dwc_otg_get_gsnpsid(dwc_otg_device->core_if)); + dwc_otg_cil_remove(dwc_otg_device->core_if); + dwc_free(dwc_otg_device); + retval = -EINVAL; + goto fail; + } + + /* + * Validate parameter values. + */ + if (set_parameters(dwc_otg_device->core_if)) { + dwc_otg_cil_remove(dwc_otg_device->core_if); + retval = -EINVAL; + goto fail; + } + + /* + * Create Device Attributes in sysfs + */ + dwc_otg_attr_create(_dev); + + dev_dbg(&_dev->dev, "number EP=%d\n", + dwc_otg_get_param_dev_endpoints(dwc_otg_device->core_if)); + + /* + * Disable the global interrupt until all the interrupt + * handlers are installed. + */ + dwc_otg_disable_global_interrupts(dwc_otg_device->core_if); + + /* + * Install the interrupt handler for the common interrupts before + * enabling common interrupts in core_init below. + */ + DWC_DEBUGPL(DBG_CIL, "registering (common) handler for irq%d\n", + irq); + + /* + *no need to use comm_irq if the usb IP core can work only in device mode + *but if support otg, we should use common_irq + *such as device disconnect when usb cable is connected,plug out udisk + */ + retval = request_irq(irq, dwc_otg_common_irq, + IRQF_SHARED, "dwc_otg", dwc_otg_device); + //SA_SHIRQ, "dwc_otg", dwc_otg_device); + if (retval) { + DWC_ERROR("request of irq%d failed\n", irq); + retval = -EBUSY; + goto fail; + } else { + dwc_otg_device->common_irq_installed = 1; + } + +#if 0//ndef DWC_DEVICE_ONLY //no need + /* + * judge wheather otg cable is connected + */ + if(!usb_get_id_state()) + dwc_otg_core_fore_host(dwc_otg_device->core_if); +#endif + /* + * Initialize the DWC_otg core. + */ + dwc_otg_core_init(dwc_otg_device->core_if); + +#ifndef DWC_HOST_ONLY + /* + * Initialize the PCD + */ + retval = pcd_init(_dev); + if (retval != 0) { + DWC_ERROR("pcd_init failed\n"); + dwc_otg_device->pcd = NULL; + goto fail; + } +#endif +#ifndef DWC_DEVICE_ONLY + /* + * Initialize the HCD + */ + retval = hcd_init(_dev); + if (retval != 0) { + DWC_ERROR("hcd_init failed\n"); + dwc_otg_device->hcd = NULL; + goto fail; + } +#endif + + /*Recover from drvdata having been overwritten by hcd_init()*/ + platform_set_drvdata(_dev, dwc_otg_device); + dwc_otg_device->os_dep.platformdev = _dev; + + /* + * Enable the global interrupt after all the interrupt + * handlers are installed. + */ + //dwc_otg_enable_global_interrupts(dwc_otg_device->core_if); + + return 0; + +fail: + dwc_otg_driver_remove(_dev); + return retval; +} + +/** + * This structure defines the methods to be called by a bus driver + * during the lifecycle of a device on that bus. Both drivers and + * devices are registered with a bus driver. The bus driver matches + * devices to drivers based on information in the device and driver + * structures. + * + * The probe function is called when the bus driver matches a device + * to this driver. The remove function is called when a device is + * unregistered with the bus driver. + */ +#ifdef CONFIG_OF +static const struct of_device_id usb_ids [] = { + { .compatible = "sprd,usb" }, + {} +}; +#endif +static struct platform_driver dwc_otg_driver = { + .driver = { + .name = "dwc_otg", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(usb_ids), +#endif + }, + + .probe = dwc_otg_driver_probe, + .remove = dwc_otg_driver_remove, + //.suspend = dwc_otg_suspend, + //.resume = dwc_otg_resume, + +}; +/** + * This function is called when the dwc_otg_driver is installed with the + * insmod command. It registers the dwc_otg_driver structure with the + * appropriate bus driver. This will cause the dwc_otg_driver_probe function + * to be called. In addition, the bus driver will automatically expose + * attributes defined for the device and driver in the special sysfs file + * system. + * + * @return + */ +static int __init dwc_otg_driver_init(void) +{ + int retval = 0; + int error; + printk(KERN_INFO "%s: version %s\n", dwc_driver_name, + DWC_DRIVER_VERSION); + printk(KERN_INFO "Working version %s\n", "No 007 - 10/24/2007"); + + retval = platform_driver_probe(&dwc_otg_driver, dwc_otg_driver_probe); + if (retval < 0) { + printk(KERN_ERR "%s retval=%d\n", __func__, retval); + return retval; + } + + error = driver_create_file(&dwc_otg_driver.driver, &driver_attr_version); + error = driver_create_file(&dwc_otg_driver.driver, &driver_attr_debuglevel); + error = driver_create_file(&dwc_otg_driver.driver, &driver_attr_udcpower); + error = driver_create_file(&dwc_otg_driver.driver, &driver_attr_is_support_otg); + return retval; +} + +module_init(dwc_otg_driver_init); + +/** + * This function is called when the driver is removed from the kernel + * with the rmmod command. The driver unregisters itself with its bus + * driver. + * + */ +static void __exit dwc_otg_driver_cleanup(void) +{ + printk(KERN_DEBUG "dwc_otg_driver_cleanup()\n"); + + + driver_remove_file(&dwc_otg_driver.driver, &driver_attr_debuglevel); + driver_remove_file(&dwc_otg_driver.driver, &driver_attr_version); + driver_remove_file(&dwc_otg_driver.driver, &driver_attr_udcpower); + driver_remove_file(&dwc_otg_driver.driver, &driver_attr_is_support_otg); + platform_driver_unregister(&dwc_otg_driver); + + printk(KERN_INFO "%s module removed\n", dwc_driver_name); +} +module_exit(dwc_otg_driver_cleanup); + +MODULE_DESCRIPTION(DWC_DRIVER_DESC); +MODULE_AUTHOR("Synopsys Inc."); +MODULE_LICENSE("GPL"); + +module_param_named(otg_cap, dwc_otg_module_params.otg_cap, int, 0444); +MODULE_PARM_DESC(otg_cap, "OTG Capabilities 0=HNP&SRP 1=SRP Only 2=None"); +module_param_named(opt, dwc_otg_module_params.opt, int, 0444); +MODULE_PARM_DESC(opt, "OPT Mode"); +module_param_named(dma_enable, dwc_otg_module_params.dma_enable, int, 0444); +MODULE_PARM_DESC(dma_enable, "DMA Mode 0=Slave 1=DMA enabled"); + +module_param_named(dma_desc_enable, dwc_otg_module_params.dma_desc_enable, int, + 0444); +MODULE_PARM_DESC(dma_desc_enable, + "DMA Desc Mode 0=Address DMA 1=DMA Descriptor enabled"); + +module_param_named(dma_burst_size, dwc_otg_module_params.dma_burst_size, int, + 0444); +MODULE_PARM_DESC(dma_burst_size, + "DMA Burst Size 1, 4, 8, 16, 32, 64, 128, 256"); +module_param_named(speed, dwc_otg_module_params.speed, int, 0444); +MODULE_PARM_DESC(speed, "Speed 0=High Speed 1=Full Speed"); +module_param_named(host_support_fs_ls_low_power, + dwc_otg_module_params.host_support_fs_ls_low_power, int, + 0444); +MODULE_PARM_DESC(host_support_fs_ls_low_power, + "Support Low Power w/FS or LS 0=Support 1=Don't Support"); +module_param_named(host_ls_low_power_phy_clk, + dwc_otg_module_params.host_ls_low_power_phy_clk, int, 0444); +MODULE_PARM_DESC(host_ls_low_power_phy_clk, + "Low Speed Low Power Clock 0=48Mhz 1=6Mhz"); +module_param_named(enable_dynamic_fifo, + dwc_otg_module_params.enable_dynamic_fifo, int, 0444); +MODULE_PARM_DESC(enable_dynamic_fifo, "0=cC Setting 1=Allow Dynamic Sizing"); +module_param_named(data_fifo_size, dwc_otg_module_params.data_fifo_size, int, + 0444); +MODULE_PARM_DESC(data_fifo_size, + "Total number of words in the data FIFO memory 32-32768"); +module_param_named(dev_rx_fifo_size, dwc_otg_module_params.dev_rx_fifo_size, + int, 0444); +MODULE_PARM_DESC(dev_rx_fifo_size, "Number of words in the Rx FIFO 16-32768"); +module_param_named(dev_nperio_tx_fifo_size, + dwc_otg_module_params.dev_nperio_tx_fifo_size, int, 0444); +MODULE_PARM_DESC(dev_nperio_tx_fifo_size, + "Number of words in the non-periodic Tx FIFO 16-32768"); +module_param_named(dev_perio_tx_fifo_size_1, + dwc_otg_module_params.dev_perio_tx_fifo_size[0], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_1, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_2, + dwc_otg_module_params.dev_perio_tx_fifo_size[1], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_2, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_3, + dwc_otg_module_params.dev_perio_tx_fifo_size[2], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_3, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_4, + dwc_otg_module_params.dev_perio_tx_fifo_size[3], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_4, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_5, + dwc_otg_module_params.dev_perio_tx_fifo_size[4], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_5, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_6, + dwc_otg_module_params.dev_perio_tx_fifo_size[5], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_6, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_7, + dwc_otg_module_params.dev_perio_tx_fifo_size[6], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_7, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_8, + dwc_otg_module_params.dev_perio_tx_fifo_size[7], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_8, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_9, + dwc_otg_module_params.dev_perio_tx_fifo_size[8], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_9, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_10, + dwc_otg_module_params.dev_perio_tx_fifo_size[9], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_10, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_11, + dwc_otg_module_params.dev_perio_tx_fifo_size[10], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_11, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_12, + dwc_otg_module_params.dev_perio_tx_fifo_size[11], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_12, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_13, + dwc_otg_module_params.dev_perio_tx_fifo_size[12], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_13, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_14, + dwc_otg_module_params.dev_perio_tx_fifo_size[13], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_14, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_15, + dwc_otg_module_params.dev_perio_tx_fifo_size[14], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_15, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(host_rx_fifo_size, dwc_otg_module_params.host_rx_fifo_size, + int, 0444); +MODULE_PARM_DESC(host_rx_fifo_size, "Number of words in the Rx FIFO 16-32768"); +module_param_named(host_nperio_tx_fifo_size, + dwc_otg_module_params.host_nperio_tx_fifo_size, int, 0444); +MODULE_PARM_DESC(host_nperio_tx_fifo_size, + "Number of words in the non-periodic Tx FIFO 16-32768"); +module_param_named(host_perio_tx_fifo_size, + dwc_otg_module_params.host_perio_tx_fifo_size, int, 0444); +MODULE_PARM_DESC(host_perio_tx_fifo_size, + "Number of words in the host periodic Tx FIFO 16-32768"); +module_param_named(max_transfer_size, dwc_otg_module_params.max_transfer_size, + int, 0444); +/** @todo Set the max to 512K, modify checks */ +MODULE_PARM_DESC(max_transfer_size, + "The maximum transfer size supported in bytes 2047-65535"); +module_param_named(max_packet_count, dwc_otg_module_params.max_packet_count, + int, 0444); +MODULE_PARM_DESC(max_packet_count, + "The maximum number of packets in a transfer 15-511"); +module_param_named(host_channels, dwc_otg_module_params.host_channels, int, + 0444); +MODULE_PARM_DESC(host_channels, + "The number of host channel registers to use 1-16"); +module_param_named(dev_endpoints, dwc_otg_module_params.dev_endpoints, int, + 0444); +MODULE_PARM_DESC(dev_endpoints, + "The number of endpoints in addition to EP0 available for device mode 1-15"); +module_param_named(phy_type, dwc_otg_module_params.phy_type, int, 0444); +MODULE_PARM_DESC(phy_type, "0=Reserved 1=UTMI+ 2=ULPI"); +module_param_named(phy_utmi_width, dwc_otg_module_params.phy_utmi_width, int, + 0444); +MODULE_PARM_DESC(phy_utmi_width, "Specifies the UTMI+ Data Width 8 or 16 bits"); +module_param_named(phy_ulpi_ddr, dwc_otg_module_params.phy_ulpi_ddr, int, 0444); +MODULE_PARM_DESC(phy_ulpi_ddr, + "ULPI at double or single data rate 0=Single 1=Double"); +module_param_named(phy_ulpi_ext_vbus, dwc_otg_module_params.phy_ulpi_ext_vbus, + int, 0444); +MODULE_PARM_DESC(phy_ulpi_ext_vbus, + "ULPI PHY using internal or external vbus 0=Internal"); +module_param_named(i2c_enable, dwc_otg_module_params.i2c_enable, int, 0444); +MODULE_PARM_DESC(i2c_enable, "FS PHY Interface"); +module_param_named(ulpi_fs_ls, dwc_otg_module_params.ulpi_fs_ls, int, 0444); +MODULE_PARM_DESC(ulpi_fs_ls, "ULPI PHY FS/LS mode only"); +module_param_named(ts_dline, dwc_otg_module_params.ts_dline, int, 0444); +MODULE_PARM_DESC(ts_dline, "Term select Dline pulsing for all PHYs"); +module_param_named(debug, g_dbg_lvl, int, 0444); +MODULE_PARM_DESC(debug, ""); + +module_param_named(en_multiple_tx_fifo, + dwc_otg_module_params.en_multiple_tx_fifo, int, 0444); +MODULE_PARM_DESC(en_multiple_tx_fifo, + "Dedicated Non Periodic Tx FIFOs 0=disabled 1=enabled"); +module_param_named(dev_tx_fifo_size_1, + dwc_otg_module_params.dev_tx_fifo_size[0], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_1, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_2, + dwc_otg_module_params.dev_tx_fifo_size[1], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_2, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_3, + dwc_otg_module_params.dev_tx_fifo_size[2], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_3, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_4, + dwc_otg_module_params.dev_tx_fifo_size[3], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_4, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_5, + dwc_otg_module_params.dev_tx_fifo_size[4], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_5, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_6, + dwc_otg_module_params.dev_tx_fifo_size[5], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_6, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_7, + dwc_otg_module_params.dev_tx_fifo_size[6], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_7, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_8, + dwc_otg_module_params.dev_tx_fifo_size[7], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_8, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_9, + dwc_otg_module_params.dev_tx_fifo_size[8], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_9, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_10, + dwc_otg_module_params.dev_tx_fifo_size[9], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_10, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_11, + dwc_otg_module_params.dev_tx_fifo_size[10], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_11, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_12, + dwc_otg_module_params.dev_tx_fifo_size[11], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_12, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_13, + dwc_otg_module_params.dev_tx_fifo_size[12], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_13, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_14, + dwc_otg_module_params.dev_tx_fifo_size[13], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_14, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_15, + dwc_otg_module_params.dev_tx_fifo_size[14], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_15, "Number of words in the Tx FIFO 4-768"); + +module_param_named(thr_ctl, dwc_otg_module_params.thr_ctl, int, 0444); +MODULE_PARM_DESC(thr_ctl, + "Thresholding enable flag bit 0 - non ISO Tx thr., 1 - ISO Tx thr., 2 - Rx thr.- bit 0=disabled 1=enabled"); +module_param_named(tx_thr_length, dwc_otg_module_params.tx_thr_length, int, + 0444); +MODULE_PARM_DESC(tx_thr_length, "Tx Threshold length in 32 bit DWORDs"); +module_param_named(rx_thr_length, dwc_otg_module_params.rx_thr_length, int, + 0444); +MODULE_PARM_DESC(rx_thr_length, "Rx Threshold length in 32 bit DWORDs"); + +module_param_named(pti_enable, dwc_otg_module_params.pti_enable, int, 0444); +module_param_named(mpi_enable, dwc_otg_module_params.mpi_enable, int, 0444); +module_param_named(lpm_enable, dwc_otg_module_params.lpm_enable, int, 0444); +MODULE_PARM_DESC(lpm_enable, "LPM Enable 0=LPM Disabled 1=LPM Enabled"); +module_param_named(ic_usb_cap, dwc_otg_module_params.ic_usb_cap, int, 0444); +MODULE_PARM_DESC(ic_usb_cap, + "IC_USB Capability 0=IC_USB Disabled 1=IC_USB Enabled"); +module_param_named(ahb_thr_ratio, dwc_otg_module_params.ahb_thr_ratio, int, 0444); +MODULE_PARM_DESC(ahb_thr_ratio, "AHB Threshold Ratio"); + +/** @page "Module Parameters" + * + * The following parameters may be specified when starting the module. + * These parameters define how the DWC_otg controller should be + * configured. Parameter values are passed to the CIL initialization + * function dwc_otg_cil_init + * + * Example: modprobe dwc_otg speed=1 otg_cap=1 + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_driver.h b/drivers/usb/gadget/dwc_otg/dwc_otg_driver.h new file mode 100644 index 00000000..aebe9d74 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_driver.h @@ -0,0 +1,97 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_driver.h $ + * $Revision: #19 $ + * $Date: 2010/11/15 $ + * $Change: 1627671 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#ifndef __DWC_OTG_DRIVER_H__ +#define __DWC_OTG_DRIVER_H__ + +/** @file + * This file contains the interface to the Linux driver. + */ +#include "dwc_otg_os_dep.h" +#include "dwc_otg_core_if.h" +#include +#include + +/* Type declarations */ +struct dwc_otg_pcd; +struct dwc_otg_hcd; + +/** + * This structure is a wrapper that encapsulates the driver components used to + * manage a single DWC_otg controller. + */ +typedef struct dwc_otg_device { + /** Structure containing OS-dependent stuff. KEEP THIS STRUCT AT THE + * VERY BEGINNING OF THE DEVICE STRUCT. OSes such as FreeBSD and NetBSD + * require this. */ + struct os_dependent os_dep; + + /** Pointer to the core interface structure. */ + dwc_otg_core_if_t *core_if; + + /** Pointer to the PCD structure. */ + struct dwc_otg_pcd *pcd; + + /** Pointer to the HCD structure. */ + struct dwc_otg_hcd *hcd; + + /** Flag to indicate whether the common IRQ handler is installed. */ + uint8_t common_irq_installed; + + /**For host charge pump external boost mode. */ + struct sprd_usb_platform_data platform_data; + + /**For auto test mode*/ + int host_disabled; +} dwc_otg_device_t; + +/*We must clear S3C24XX_EINTPEND external interrupt register + * because after clearing in this register trigerred IRQ from + * H/W core in kernel interrupt can be occured again before OTG + * handlers clear all IRQ sources of Core registers because of + * timing latencies and Low Level IRQ Type. + */ + +struct device *get_hcd_device(); +struct device *get_gadget_wrapper_device(); + +#ifdef CONFIG_MACH_IPMATE +#define S3C2410X_CLEAR_EINTPEND() \ +do { \ + __raw_writel(1UL << 11,S3C24XX_EINTPEND); \ +} while (0) +#else +#define S3C2410X_CLEAR_EINTPEND() do { } while (0) +#endif + +#endif diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_hcd.c b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd.c new file mode 100644 index 00000000..f0ab99b4 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd.c @@ -0,0 +1,3352 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.c $ + * $Revision: #104 $ + * $Date: 2011/10/24 $ + * $Change: 1871159 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +/** @file + * This file implements HCD Core. All code in this file is portable and doesn't + * use any OS specific functions. + * Interface provided by HCD Core is defined in + * header file. + */ + +#include "usb.h" +#include +#include "dwc_os.h" +#include "dwc_otg_hcd.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_driver.h" + +dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void) +{ + return DWC_ALLOC(sizeof(dwc_otg_hcd_t)); +} + +/** + * Connection timeout function. An OTG host is required to display a + * message if the device does not connect within 10 seconds. + */ +void dwc_otg_hcd_connect_timeout(void *ptr) +{ + DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, ptr); + DWC_PRINTF("Connect Timeout\n"); + __DWC_ERROR("Device Not Connected/Responding\n"); +} + +#ifdef DEBUG +static void dump_channel_info(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + if (qh->channel != NULL) { + dwc_hc_t *hc = qh->channel; + dwc_list_link_t *item; + dwc_otg_qh_t *qh_item; + int num_channels = hcd->core_if->core_params->host_channels; + int i; + + dwc_otg_hc_regs_t *hc_regs; + hcchar_data_t hcchar; + hcsplt_data_t hcsplt; + hctsiz_data_t hctsiz; + uint32_t hcdma; + + hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt); + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + hcdma = DWC_READ_REG32(&hc_regs->hcdma); + + DWC_PRINTF(" Assigned to channel %p:\n", hc); + DWC_PRINTF(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32, + hcsplt.d32); + DWC_PRINTF(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32, + hcdma); + DWC_PRINTF(" dev_addr: %d, ep_num: %d, ep_is_in: %d\n", + hc->dev_addr, hc->ep_num, hc->ep_is_in); + DWC_PRINTF(" ep_type: %d\n", hc->ep_type); + DWC_PRINTF(" max_packet: %d\n", hc->max_packet); + DWC_PRINTF(" data_pid_start: %d\n", hc->data_pid_start); + DWC_PRINTF(" xfer_started: %d\n", hc->xfer_started); + DWC_PRINTF(" halt_status: %d\n", hc->halt_status); + DWC_PRINTF(" xfer_buff: %p\n", hc->xfer_buff); + DWC_PRINTF(" xfer_len: %d\n", hc->xfer_len); + DWC_PRINTF(" qh: %p\n", hc->qh); + DWC_PRINTF(" NP inactive sched:\n"); + DWC_LIST_FOREACH(item, &hcd->non_periodic_sched_inactive) { + qh_item = + DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry); + DWC_PRINTF(" %p\n", qh_item); + } + DWC_PRINTF(" NP active sched:\n"); + DWC_LIST_FOREACH(item, &hcd->non_periodic_sched_active) { + qh_item = + DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry); + DWC_PRINTF(" %p\n", qh_item); + } + DWC_PRINTF(" Channels: \n"); + for (i = 0; i < num_channels; i++) { + dwc_hc_t *hc = hcd->hc_ptr_array[i]; + DWC_PRINTF(" %2d: %p\n", i, hc); + } + } +} +#endif /* DEBUG */ + +/** + * Work queue function for starting the HCD when A-Cable is connected. + * The hcd_start() must be called in a process context. + */ +static void hcd_start_func(void *_vp) +{ + dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) _vp; + + DWC_DEBUGPL(DBG_HCDV, "%s() %p\n", __func__, hcd); + if (hcd) { + hcd->fops->start(hcd); + } +} + +static void del_xfer_timers(dwc_otg_hcd_t * hcd) +{ +#ifdef DEBUG + int i; + int num_channels = hcd->core_if->core_params->host_channels; + for (i = 0; i < num_channels; i++) { + DWC_TIMER_CANCEL(hcd->core_if->hc_xfer_timer[i]); + } +#endif +} + +static void del_timers(dwc_otg_hcd_t * hcd) +{ + del_xfer_timers(hcd); + DWC_TIMER_CANCEL(hcd->conn_timer); +} + +/** + * Processes all the URBs in a single list of QHs. Completes them with + * -ETIMEDOUT and frees the QTD. + */ +static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list) +{ + dwc_list_link_t *qh_item; + dwc_otg_qh_t *qh; + dwc_otg_qtd_t *qtd, *qtd_tmp; + + DWC_LIST_FOREACH(qh_item, qh_list) { + qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry); + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, + &qh->qtd_list, qtd_list_entry) { + qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + if (qtd->urb != NULL) { + hcd->fops->complete(hcd, qtd->urb->priv, + qtd->urb, -DWC_E_TIMEOUT); + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + } + + } + } +} + +/** + * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic + * and periodic schedules. The QTD associated with each URB is removed from + * the schedule and freed. This function may be called when a disconnect is + * detected or when the HCD is being stopped. + */ +static void kill_all_urbs(dwc_otg_hcd_t * hcd) +{ + kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_inactive); + kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_active); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_inactive); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_ready); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_assigned); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_queued); +} + +/** + * Start the connection timer. An OTG host is required to display a + * message if the device does not connect within 10 seconds. The + * timer is deleted if a port connect interrupt occurs before the + * timer expires. + */ +static void dwc_otg_hcd_start_connect_timer(dwc_otg_hcd_t * hcd) +{ + DWC_TIMER_SCHEDULE(hcd->conn_timer, 10000 /* 10 secs */ ); +} + +/** + * HCD Callback function for disconnect of the HCD. + * + * @param p void pointer to the struct usb_hcd + */ +static int32_t dwc_otg_hcd_session_start_cb(void *p) +{ + dwc_otg_hcd_t *dwc_otg_hcd; + DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, p); + dwc_otg_hcd = p; + dwc_otg_hcd_start_connect_timer(dwc_otg_hcd); + return 1; +} + +/** + * HCD Callback function for starting the HCD when A-Cable is + * connected. + * + * @param p void pointer to the struct usb_hcd + */ +static int32_t dwc_otg_hcd_start_cb(void *p) +{ + dwc_otg_hcd_t *dwc_otg_hcd = p; + dwc_otg_core_if_t *core_if; + hprt0_data_t hprt0; + + core_if = dwc_otg_hcd->core_if; + + if (core_if->op_state == B_HOST) { + /* + * Reset the port. During a HNP mode switch the reset + * needs to occur within 1ms and have a duration of at + * least 50ms. + */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtrst = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + } + DWC_WORKQ_SCHEDULE_DELAYED(core_if->wq_otg, + hcd_start_func, dwc_otg_hcd, 50, + "start hcd"); + + return 1; +} + +/** + * HCD Callback function for disconnect of the HCD. + * + * @param p void pointer to the struct usb_hcd + */ +static int32_t dwc_otg_hcd_disconnect_cb(void *p) +{ + gintsts_data_t intr; + dwc_otg_hcd_t *dwc_otg_hcd = p; + + /* + * Set status flags for the hub driver. + */ + dwc_otg_hcd->flags.b.port_connect_status_change = 1; + dwc_otg_hcd->flags.b.port_connect_status = 0; + + /* + * Shutdown any transfers in process by clearing the Tx FIFO Empty + * interrupt mask and status bits and disabling subsequent host + * channel interrupts. + */ + intr.d32 = 0; + intr.b.nptxfempty = 1; + intr.b.ptxfempty = 1; + intr.b.hcintr = 1; + DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk, + intr.d32, 0); + DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintsts, + intr.d32, 0); + + del_timers(dwc_otg_hcd); + + /* + * Turn off the vbus power only if the core has transitioned to device + * mode. If still in host mode, need to keep power on to detect a + * reconnection. + */ + if (dwc_otg_is_device_mode(dwc_otg_hcd->core_if)) { + if (dwc_otg_hcd->core_if->op_state != A_SUSPEND) { + hprt0_data_t hprt0 = {.d32 = 0 }; + DWC_PRINTF("Disconnect: PortPower off\n"); + hprt0.b.prtpwr = 0; + DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0, + hprt0.d32); + } + + dwc_otg_disable_host_interrupts(dwc_otg_hcd->core_if); + } + + /* Respond with an error status to all URBs in the schedule. */ + kill_all_urbs(dwc_otg_hcd); + + if (dwc_otg_is_host_mode(dwc_otg_hcd->core_if)) { + /* Clean up any host channels that were in use. */ + int num_channels; + int i; + dwc_hc_t *channel; + dwc_otg_hc_regs_t *hc_regs; + hcchar_data_t hcchar; + + num_channels = dwc_otg_hcd->core_if->core_params->host_channels; + + if (!dwc_otg_hcd->core_if->dma_enable) { + /* Flush out any channel requests in slave mode. */ + for (i = 0; i < num_channels; i++) { + channel = dwc_otg_hcd->hc_ptr_array[i]; + if (DWC_CIRCLEQ_EMPTY_ENTRY + (channel, hc_list_entry)) { + hc_regs = + dwc_otg_hcd->core_if-> + host_if->hc_regs[i]; + hcchar.d32 = + DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + hcchar.b.chen = 0; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + DWC_WRITE_REG32 + (&hc_regs->hcchar, + hcchar.d32); + } + } + } + } + + for (i = 0; i < num_channels; i++) { + channel = dwc_otg_hcd->hc_ptr_array[i]; + if (DWC_CIRCLEQ_EMPTY_ENTRY(channel, hc_list_entry)) { + hc_regs = + dwc_otg_hcd->core_if->host_if->hc_regs[i]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + /* Halt the channel. */ + hcchar.b.chdis = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, + hcchar.d32); + } + + dwc_otg_hc_cleanup(dwc_otg_hcd->core_if, + channel); + DWC_CIRCLEQ_INSERT_TAIL + (&dwc_otg_hcd->free_hc_list, channel, + hc_list_entry); + /* + * Added for Descriptor DMA to prevent channel double cleanup + * in release_channel_ddma(). Which called from ep_disable + * when device disconnect. + */ + channel->qh = NULL; + } + } + } + + if (dwc_otg_hcd->fops->disconnect) { + dwc_otg_hcd->fops->disconnect(dwc_otg_hcd); + } + + return 1; +} + +/** + * HCD Callback function for stopping the HCD. + * + * @param p void pointer to the struct usb_hcd + */ +static int32_t dwc_otg_hcd_stop_cb(void *p) +{ + dwc_otg_hcd_t *dwc_otg_hcd = p; + + DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, p); + dwc_otg_hcd_stop(dwc_otg_hcd); + return 1; +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** + * HCD Callback function for sleep of HCD. + * + * @param p void pointer to the struct usb_hcd + */ +static int dwc_otg_hcd_sleep_cb(void *p) +{ + dwc_otg_hcd_t *hcd = p; + + dwc_otg_hcd_free_hc_from_lpm(hcd); + + return 0; +} +#endif + +/** + * HCD Callback function for Remote Wakeup. + * + * @param p void pointer to the struct usb_hcd + */ +static int dwc_otg_hcd_rem_wakeup_cb(void *p) +{ + dwc_otg_hcd_t *hcd = p; + + if (hcd->core_if->lx_state == DWC_OTG_L2) { + hcd->flags.b.port_suspend_change = 1; + } +#ifdef CONFIG_USB_DWC_OTG_LPM + else { + hcd->flags.b.port_l1_change = 1; + } +#endif + return 0; +} + +/** + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + */ +void dwc_otg_hcd_stop(dwc_otg_hcd_t * hcd) +{ + hprt0_data_t hprt0 = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD STOP\n"); + + /* + * The root hub should be disconnected before this function is called. + * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue) + * and the QH lists (via ..._hcd_endpoint_disable). + */ + + /* Turn off all host-specific interrupts. */ + dwc_otg_disable_host_interrupts(hcd->core_if); + + /* Turn off the vbus power */ + DWC_PRINTF("PortPower off\n"); + hprt0.b.prtpwr = 0; + DWC_WRITE_REG32(hcd->core_if->host_if->hprt0, hprt0.d32); + dwc_mdelay(1); +} + +int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * hcd, + dwc_otg_hcd_urb_t * dwc_otg_urb, void **ep_handle, + int atomic_alloc) +{ + dwc_irqflags_t flags; + int retval = 0; + dwc_otg_qtd_t *qtd; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + if (!hcd->flags.b.port_connect_status) { + /* No longer connected. */ + DWC_ERROR("Not connected\n"); + return -DWC_E_NO_DEVICE; + } + + qtd = dwc_otg_hcd_qtd_create(dwc_otg_urb, atomic_alloc); + if (qtd == NULL) { + DWC_ERROR("DWC OTG HCD URB Enqueue failed creating QTD\n"); + return -DWC_E_NO_MEMORY; + } + + retval = + dwc_otg_hcd_qtd_add(qtd, hcd, (dwc_otg_qh_t **) ep_handle, atomic_alloc); + if (retval < 0) { + DWC_ERROR("DWC OTG HCD URB Enqueue failed adding QTD. " + "Error status %d\n", retval); + dwc_otg_hcd_qtd_free(qtd); + } else { + qtd->qh = *ep_handle; + } + intr_mask.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->gintmsk); + if (!intr_mask.b.sofintr && retval == 0) { + dwc_otg_transaction_type_e tr_type; + if ((qtd->qh->ep_type == UE_BULK) + && !(qtd->urb->flags & URB_GIVEBACK_ASAP)) { + /* Do not schedule SG transactions until qtd has URB_GIVEBACK_ASAP set */ + return 0; + } + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE) { + dwc_otg_hcd_queue_transactions(hcd, tr_type); + } + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + } + + return retval; +} + +int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd, + dwc_otg_hcd_urb_t * dwc_otg_urb) +{ + dwc_otg_qh_t *qh; + dwc_otg_qtd_t *urb_qtd; + + urb_qtd = dwc_otg_urb->qtd; + qh = urb_qtd->qh; +#ifdef DEBUG + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + if (urb_qtd->in_process) { + dump_channel_info(hcd, qh); + } + } +#endif + if (urb_qtd->in_process && qh->channel) { + /* The QTD is in process (it has been assigned to a channel). */ + if (hcd->flags.b.port_connect_status) { + /* + * If still connected (i.e. in host mode), halt the + * channel so it can be used for other transfers. If + * no longer connected, the host registers can't be + * written to halt the channel since the core is in + * device mode. + */ + dwc_otg_hc_halt(hcd->core_if, qh->channel, + DWC_OTG_HC_XFER_URB_DEQUEUE); + } + } + + /* + * Free the QTD and clean up the associated QH. Leave the QH in the + * schedule if it has any remaining QTDs. + */ + + if (!hcd->core_if->dma_desc_enable) { + uint8_t b = urb_qtd->in_process; + dwc_otg_hcd_qtd_remove_and_free(hcd, urb_qtd, qh); + if (b) { + dwc_otg_hcd_qh_deactivate(hcd, qh, 0); + qh->channel = NULL; + } else if (DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + dwc_otg_hcd_qh_remove(hcd, qh); + } + } else { + dwc_otg_hcd_qtd_remove_and_free(hcd, urb_qtd, qh); + } + return 0; +} + +int dwc_otg_hcd_endpoint_disable(dwc_otg_hcd_t * hcd, void *ep_handle, + int retry) +{ + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + int retval = 0; + dwc_irqflags_t flags; + + if (retry < 0) { + retval = -DWC_E_INVALID; + goto done; + } + + if (!qh) { + retval = -DWC_E_INVALID; + goto done; + } + + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + + while (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list) && retry) { + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + retry--; + dwc_msleep(5); + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + } + + dwc_otg_hcd_qh_remove(hcd, qh); + + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + /* + * Split dwc_otg_hcd_qh_remove_and_free() into qh_remove + * and qh_free to prevent stack dump on DWC_DMA_FREE() with + * irq_disabled (spinlock_irqsave) in dwc_otg_hcd_desc_list_free() + * and dwc_otg_hcd_frame_list_alloc(). + */ + dwc_otg_hcd_qh_free(hcd, qh); + +done: + return retval; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +int dwc_otg_hcd_endpoint_reset(dwc_otg_hcd_t * hcd, void *ep_handle) +{ + int retval = 0; + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + if (!qh) + return -DWC_E_INVALID; + + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + return retval; +} +#endif + +/** + * HCD Callback structure for handling mode switching. + */ +static dwc_otg_cil_callbacks_t hcd_cil_callbacks = { + .start = dwc_otg_hcd_start_cb, + .stop = dwc_otg_hcd_stop_cb, + .disconnect = dwc_otg_hcd_disconnect_cb, + .session_start = dwc_otg_hcd_session_start_cb, + .resume_wakeup = dwc_otg_hcd_rem_wakeup_cb, +#ifdef CONFIG_USB_DWC_OTG_LPM + .sleep = dwc_otg_hcd_sleep_cb, +#endif + .p = 0, +}; + +/** + * Reset tasklet function + */ +static void reset_tasklet_func(void *data) +{ + dwc_otg_hcd_t *dwc_otg_hcd = (dwc_otg_hcd_t *) data; + dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; + hprt0_data_t hprt0; + + DWC_DEBUGPL(DBG_HCDV, "USB RESET tasklet called\n"); + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtrst = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + dwc_mdelay(60); + + hprt0.b.prtrst = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + dwc_otg_hcd->flags.b.port_reset_change = 1; +} + +static void qh_list_free(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list) +{ + dwc_list_link_t *item; + dwc_otg_qh_t *qh; + dwc_irqflags_t flags; + + if (!qh_list->next) { + /* The list hasn't been initialized yet. */ + return; + } + /* + * Hold spinlock here. Not needed in that case if bellow + * function is being called from ISR + */ + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + /* Ensure there are no QTDs or URBs left. */ + kill_urbs_in_qh_list(hcd, qh_list); + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + + DWC_LIST_FOREACH(item, qh_list) { + qh = DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry); + dwc_otg_hcd_qh_remove_and_free(hcd, qh); + } +} + +/** + * Exit from Hibernation if Host did not detect SRP from connected SRP capable + * Device during SRP time by host power up. + */ +void dwc_otg_hcd_power_up(void *ptr) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr; + + DWC_PRINTF("%s called\n", __FUNCTION__); + + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return; + } + + /* Switch on the voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset the core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Remove reset the core signal */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + core_if->hibernation_suspend = 0; + + /* Disable PMU */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Enable VBUS */ + gpwrdn.d32 = 0; + gpwrdn.b.dis_vbus = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); +} + +/** + * Frees secondary storage associated with the dwc_otg_hcd structure contained + * in the struct usb_hcd field. + */ +static void dwc_otg_hcd_free(dwc_otg_hcd_t * dwc_otg_hcd) +{ + int i; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD FREE\n"); + + del_timers(dwc_otg_hcd); + + /* Free memory for QH/QTD lists */ + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->non_periodic_sched_inactive); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->non_periodic_sched_active); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_inactive); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_ready); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_assigned); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_queued); + + /* Free memory for the host channels. */ + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + dwc_hc_t *hc = dwc_otg_hcd->hc_ptr_array[i]; + +#ifdef DEBUG + if (dwc_otg_hcd->core_if->hc_xfer_timer[i]) { + DWC_TIMER_FREE(dwc_otg_hcd->core_if->hc_xfer_timer[i]); + } +#endif + if (hc != NULL) { + DWC_DEBUGPL(DBG_HCDV, "HCD Free channel #%i, hc=%p\n", + i, hc); + DWC_FREE(hc); + } + } + + if (dwc_otg_hcd->core_if->dma_enable) { + if (dwc_otg_hcd->status_buf_dma) { + DWC_DMA_FREE(get_hcd_device() ,DWC_OTG_HCD_STATUS_BUF_SIZE, + dwc_otg_hcd->status_buf, + dwc_otg_hcd->status_buf_dma); + } + } else if (dwc_otg_hcd->status_buf != NULL) { + DWC_FREE(dwc_otg_hcd->status_buf); + } + DWC_SPINLOCK_FREE(dwc_otg_hcd->lock); + /* Set core_if's lock pointer to NULL */ + dwc_otg_hcd->core_if->lock = NULL; + + DWC_TIMER_FREE(dwc_otg_hcd->conn_timer); + DWC_TASK_FREE(dwc_otg_hcd->reset_tasklet); + +#ifdef DWC_DEV_SRPCAP + if (dwc_otg_hcd->core_if->power_down == 2 && + dwc_otg_hcd->core_if->pwron_timer) { + DWC_TIMER_FREE(dwc_otg_hcd->core_if->pwron_timer); + } +#endif + DWC_FREE(dwc_otg_hcd); +} + +int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if) +{ + int retval = 0; + int num_channels; + int i; + dwc_hc_t *channel; + + hcd->lock = DWC_SPINLOCK_ALLOC(); + if (!hcd->lock) { + DWC_ERROR("Could not allocate lock for pcd"); + DWC_FREE(hcd); + retval = -DWC_E_NO_MEMORY; + goto out; + } + hcd->core_if = core_if; + + /* Register the HCD CIL Callbacks */ + dwc_otg_cil_register_hcd_callbacks(hcd->core_if, + &hcd_cil_callbacks, hcd); + + /* Initialize the non-periodic schedule. */ + DWC_LIST_INIT(&hcd->non_periodic_sched_inactive); + DWC_LIST_INIT(&hcd->non_periodic_sched_active); + + /* Initialize the periodic schedule. */ + DWC_LIST_INIT(&hcd->periodic_sched_inactive); + DWC_LIST_INIT(&hcd->periodic_sched_ready); + DWC_LIST_INIT(&hcd->periodic_sched_assigned); + DWC_LIST_INIT(&hcd->periodic_sched_queued); + + /* + * Create a host channel descriptor for each host channel implemented + * in the controller. Initialize the channel descriptor array. + */ + DWC_CIRCLEQ_INIT(&hcd->free_hc_list); + num_channels = hcd->core_if->core_params->host_channels; + DWC_MEMSET(hcd->hc_ptr_array, 0, sizeof(hcd->hc_ptr_array)); + for (i = 0; i < num_channels; i++) { + channel = DWC_ALLOC(sizeof(dwc_hc_t)); + if (channel == NULL) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: host channel allocation failed\n", + __func__); + dwc_otg_hcd_free(hcd); + goto out; + } + channel->hc_num = i; + hcd->hc_ptr_array[i] = channel; +#ifdef DEBUG + hcd->core_if->hc_xfer_timer[i] = + DWC_TIMER_ALLOC("hc timer", hc_xfer_timeout, + &hcd->core_if->hc_xfer_info[i]); +#endif + DWC_DEBUGPL(DBG_HCDV, "HCD Added channel #%d, hc=%p\n", i, + channel); + } + + /* Initialize the Connection timeout timer. */ + hcd->conn_timer = DWC_TIMER_ALLOC("Connection timer", + dwc_otg_hcd_connect_timeout, 0); + + /* Initialize reset tasklet. */ + hcd->reset_tasklet = DWC_TASK_ALLOC("reset_tasklet", reset_tasklet_func, hcd); +#ifdef DWC_DEV_SRPCAP + if (hcd->core_if->power_down == 2) { + /* Initialize Power on timer for Host power up in case hibernation */ + hcd->core_if->pwron_timer = DWC_TIMER_ALLOC("PWRON TIMER", + dwc_otg_hcd_power_up, core_if); + } +#endif + + /* + * Allocate space for storing data on status transactions. Normally no + * data is sent, but this space acts as a bit bucket. This must be + * done after usb_add_hcd since that function allocates the DMA buffer + * pool. + */ + if (hcd->core_if->dma_enable) { + hcd->status_buf = + DWC_DMA_ALLOC(get_hcd_device(),DWC_OTG_HCD_STATUS_BUF_SIZE, + &hcd->status_buf_dma); + } else { + hcd->status_buf = DWC_ALLOC(DWC_OTG_HCD_STATUS_BUF_SIZE); + } + if (!hcd->status_buf) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: status_buf allocation failed\n", __func__); + dwc_otg_hcd_free(hcd); + goto out; + } + + hcd->otg_port = 1; + hcd->frame_list = NULL; + hcd->frame_list_dma = 0; + hcd->periodic_qh_count = 0; +out: + return retval; +} + +void dwc_otg_hcd_remove(dwc_otg_hcd_t * hcd) +{ + /* Turn off all host-specific interrupts. */ + dwc_otg_disable_host_interrupts(hcd->core_if); + + dwc_otg_hcd_free(hcd); +} + +/** + * Initializes dynamic portions of the DWC_otg HCD state. + */ +static void dwc_otg_hcd_reinit(dwc_otg_hcd_t * hcd) +{ + int num_channels; + int i; + dwc_hc_t *channel; + dwc_hc_t *channel_tmp; + + hcd->flags.d32 = 0; + + hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active; + hcd->non_periodic_channels = 0; + hcd->periodic_channels = 0; + + /* + * Put all channels in the free channel list and clean up channel + * states. + */ + DWC_CIRCLEQ_FOREACH_SAFE(channel, channel_tmp, + &hcd->free_hc_list, hc_list_entry) { + DWC_CIRCLEQ_REMOVE(&hcd->free_hc_list, channel, hc_list_entry); + } + + num_channels = hcd->core_if->core_params->host_channels; + for (i = 0; i < num_channels; i++) { + channel = hcd->hc_ptr_array[i]; + DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, channel, + hc_list_entry); + dwc_otg_hc_cleanup(hcd->core_if, channel); + } + + /* Initialize the DWC core for host mode operation. */ + dwc_otg_core_host_init(hcd->core_if); + + /* Set core_if's lock pointer to the hcd->lock */ + hcd->core_if->lock = hcd->lock; +} + +/** + * Assigns transactions from a QTD to a free host channel and initializes the + * host channel to perform the transactions. The host channel is removed from + * the free list. + * + * @param hcd The HCD state structure. + * @param qh Transactions from the first QTD for this QH are selected and + * assigned to a free host channel. + */ +static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + dwc_hc_t *hc; + dwc_otg_qtd_t *qtd; + dwc_otg_hcd_urb_t *urb; + void* ptr = NULL; + + DWC_DEBUGPL(DBG_HCDV, "%s(%p,%p)\n", __func__, hcd, qh); + + hc = DWC_CIRCLEQ_FIRST(&hcd->free_hc_list); + + /* Remove the host channel from the free list. */ + DWC_CIRCLEQ_REMOVE_INIT(&hcd->free_hc_list, hc, hc_list_entry); + + qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + + urb = qtd->urb; + qh->channel = hc; + + qtd->in_process = 1; + + /* + * Use usb_pipedevice to determine device address. This address is + * 0 before the SET_ADDRESS command and the correct address afterward. + */ + hc->dev_addr = dwc_otg_hcd_get_dev_addr(&urb->pipe_info); + hc->ep_num = dwc_otg_hcd_get_ep_num(&urb->pipe_info); + hc->speed = qh->dev_speed; + hc->max_packet = dwc_max_packet(qh->maxp); + + hc->xfer_started = 0; + hc->halt_status = DWC_OTG_HC_XFER_NO_HALT_STATUS; + hc->error_state = (qtd->error_count > 0); + hc->halt_on_queue = 0; + hc->halt_pending = 0; + hc->requests = 0; + + /* + * The following values may be modified in the transfer type section + * below. The xfer_len value may be reduced when the transfer is + * started to accommodate the max widths of the XferSize and PktCnt + * fields in the HCTSIZn register. + */ + + hc->ep_is_in = (dwc_otg_hcd_is_pipe_in(&urb->pipe_info) != 0); + if (hc->ep_is_in) { + hc->do_ping = 0; + } else { + hc->do_ping = qh->ping_state; + } + + hc->data_pid_start = qh->data_toggle; + hc->multi_count = 1; + + if (hcd->core_if->dma_enable) { + hc->xfer_buff = (uint8_t *) urb->dma + urb->actual_length; + + /* For non-dword aligned case */ + if (((unsigned long)hc->xfer_buff & 0x3) + && !hcd->core_if->dma_desc_enable) { + ptr = (uint8_t *) urb->buf + urb->actual_length; + } + } else { + hc->xfer_buff = (uint8_t *) urb->buf + urb->actual_length; + } + hc->xfer_len = urb->length - urb->actual_length; + hc->xfer_count = 0; + + /* + * Set the split attributes + */ + hc->do_split = 0; + if (qh->do_split) { + uint32_t hub_addr, port_addr; + hc->do_split = 1; + hc->xact_pos = qtd->isoc_split_pos; + hc->complete_split = qtd->complete_split; + hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &port_addr); + hc->hub_addr = (uint8_t) hub_addr; + hc->port_addr = (uint8_t) port_addr; + } + + switch (dwc_otg_hcd_get_pipe_type(&urb->pipe_info)) { + case UE_CONTROL: + hc->ep_type = DWC_OTG_EP_TYPE_CONTROL; + switch (qtd->control_phase) { + case DWC_OTG_CONTROL_SETUP: + DWC_DEBUGPL(DBG_HCDV, " Control setup transaction\n"); + hc->do_ping = 0; + hc->ep_is_in = 0; + hc->data_pid_start = DWC_OTG_HC_PID_SETUP; + if (hcd->core_if->dma_enable) { + hc->xfer_buff = (uint8_t *) urb->setup_dma; + } else { + hc->xfer_buff = (uint8_t *) urb->setup_packet; + } + hc->xfer_len = 8; + ptr = NULL; + break; + case DWC_OTG_CONTROL_DATA: + DWC_DEBUGPL(DBG_HCDV, " Control data transaction\n"); + hc->data_pid_start = qtd->data_toggle; + break; + case DWC_OTG_CONTROL_STATUS: + /* + * Direction is opposite of data direction or IN if no + * data. + */ + DWC_DEBUGPL(DBG_HCDV, " Control status transaction\n"); + if (urb->length == 0) { + hc->ep_is_in = 1; + } else { + hc->ep_is_in = + dwc_otg_hcd_is_pipe_out(&urb->pipe_info); + } + if (hc->ep_is_in) { + hc->do_ping = 0; + } + + hc->data_pid_start = DWC_OTG_HC_PID_DATA1; + + hc->xfer_len = 0; + if (hcd->core_if->dma_enable) { + hc->xfer_buff = (uint8_t *) hcd->status_buf_dma; + } else { + hc->xfer_buff = (uint8_t *) hcd->status_buf; + } + ptr = NULL; + break; + } + break; + case UE_BULK: + hc->ep_type = DWC_OTG_EP_TYPE_BULK; + break; + case UE_INTERRUPT: + hc->ep_type = DWC_OTG_EP_TYPE_INTR; + break; + case UE_ISOCHRONOUS: + { + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + + hc->ep_type = DWC_OTG_EP_TYPE_ISOC; + + if (hcd->core_if->dma_desc_enable) + break; + + frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; + + frame_desc->status = 0; + + if (hcd->core_if->dma_enable) { + hc->xfer_buff = (uint8_t *) urb->dma; + } else { + hc->xfer_buff = (uint8_t *) urb->buf; + } + hc->xfer_buff += + frame_desc->offset + qtd->isoc_split_offset; + hc->xfer_len = + frame_desc->length - qtd->isoc_split_offset; + + /* For non-dword aligned buffers */ + if (((unsigned long)hc->xfer_buff & 0x3) + && hcd->core_if->dma_enable) { + ptr = + (uint8_t *) urb->buf + frame_desc->offset + + qtd->isoc_split_offset; + } else + ptr = NULL; + + if (hc->xact_pos == DWC_HCSPLIT_XACTPOS_ALL) { + if (hc->xfer_len <= 188) { + hc->xact_pos = DWC_HCSPLIT_XACTPOS_ALL; + } else { + hc->xact_pos = + DWC_HCSPLIT_XACTPOS_BEGIN; + } + } + } + break; + } + /* non DWORD-aligned buffer case */ + if (ptr) { + uint32_t buf_size; + if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + buf_size = hcd->core_if->core_params->max_transfer_size; + } else { + buf_size = 4096; + } + if (!qh->dw_align_buf) { + qh->dw_align_buf = DWC_DMA_ALLOC_ATOMIC(get_hcd_device(),buf_size, + &qh->dw_align_buf_dma); + if (!qh->dw_align_buf) { + DWC_ERROR + ("%s: Failed to allocate memory to handle " + "non-dword aligned buffer case\n", + __func__); + return; + } + } + if (!hc->ep_is_in) { + dwc_memcpy(qh->dw_align_buf, ptr, hc->xfer_len); + } + hc->align_buff = qh->dw_align_buf_dma; + } else { + hc->align_buff = 0; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * This value may be modified when the transfer is started to + * reflect the actual transfer length. + */ + hc->multi_count = dwc_hb_mult(qh->maxp); + } + + if (hcd->core_if->dma_desc_enable) + hc->desc_list_addr = qh->desc_list_dma; + + dwc_otg_hc_init(hcd->core_if, hc); + hc->qh = qh; +} + +/** + * This function selects transactions from the HCD transfer schedule and + * assigns them to available host channels. It is called from HCD interrupt + * handler functions. + * + * @param hcd The HCD state structure. + * + * @return The types of new transactions that were assigned to host channels. + */ +dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd) +{ + dwc_list_link_t *qh_ptr; + dwc_otg_qh_t *qh; + int num_channels; + dwc_otg_transaction_type_e ret_val = DWC_OTG_TRANSACTION_NONE; + +#ifdef DEBUG_SOF + DWC_DEBUGPL(DBG_HCD, " Select Transactions\n"); +#endif + + /* Process entries in the periodic ready list. */ + qh_ptr = DWC_LIST_FIRST(&hcd->periodic_sched_ready); + + while (qh_ptr != &hcd->periodic_sched_ready && + !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) { + + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + assign_and_init_hc(hcd, qh); + + /* + * Move the QH from the periodic ready schedule to the + * periodic assigned schedule. + */ + qh_ptr = DWC_LIST_NEXT(qh_ptr); + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned, + &qh->qh_list_entry); + + ret_val = DWC_OTG_TRANSACTION_PERIODIC; + } + + /* + * Process entries in the inactive portion of the non-periodic + * schedule. Some free host channels may not be used if they are + * reserved for periodic transfers. + */ + qh_ptr = hcd->non_periodic_sched_inactive.next; + num_channels = hcd->core_if->core_params->host_channels; + while (qh_ptr != &hcd->non_periodic_sched_inactive && + (hcd->non_periodic_channels < + num_channels - hcd->periodic_channels) && + !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) { + + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + + assign_and_init_hc(hcd, qh); + + /* + * Move the QH from the non-periodic inactive schedule to the + * non-periodic active schedule. + */ + qh_ptr = DWC_LIST_NEXT(qh_ptr); + DWC_LIST_MOVE_HEAD(&hcd->non_periodic_sched_active, + &qh->qh_list_entry); + + if (ret_val == DWC_OTG_TRANSACTION_NONE) { + ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC; + } else { + ret_val = DWC_OTG_TRANSACTION_ALL; + } + + hcd->non_periodic_channels++; + } + + return ret_val; +} + +/** + * Attempts to queue a single transaction request for a host channel + * associated with either a periodic or non-periodic transfer. This function + * assumes that there is space available in the appropriate request queue. For + * an OUT transfer or SETUP transaction in Slave mode, it checks whether space + * is available in the appropriate Tx FIFO. + * + * @param hcd The HCD state structure. + * @param hc Host channel descriptor associated with either a periodic or + * non-periodic transfer. + * @param fifo_dwords_avail Number of DWORDs available in the periodic Tx + * FIFO for periodic transfers or the non-periodic Tx FIFO for non-periodic + * transfers. + * + * @return 1 if a request is queued and more requests may be needed to + * complete the transfer, 0 if no more requests are required for this + * transfer, -1 if there is insufficient space in the Tx FIFO. + */ +static int queue_transaction(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, uint16_t fifo_dwords_avail) +{ + int retval; + + if (hcd->core_if->dma_enable) { + if (hcd->core_if->dma_desc_enable) { + if (!hc->xfer_started + || (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)) { + dwc_otg_hcd_start_xfer_ddma(hcd, hc->qh); + hc->qh->ping_state = 0; + } + } else if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + hc->qh->ping_state = 0; + } + retval = 0; + } else if (hc->halt_pending) { + /* Don't queue a request if the channel has been halted. */ + retval = 0; + } else if (hc->halt_on_queue) { + dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status); + retval = 0; + } else if (hc->do_ping) { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + } + retval = 0; + } else if (!hc->ep_is_in || hc->data_pid_start == DWC_OTG_HC_PID_SETUP) { + if ((fifo_dwords_avail * 4) >= hc->max_packet) { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + retval = 1; + } else { + retval = + dwc_otg_hc_continue_transfer(hcd->core_if, + hc); + } + } else { + retval = -1; + } + } else { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + retval = 1; + } else { + retval = dwc_otg_hc_continue_transfer(hcd->core_if, hc); + } + } + + return retval; +} + +/** + * Processes periodic channels for the next frame and queues transactions for + * these channels to the DWC_otg controller. After queueing transactions, the + * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions + * to queue as Periodic Tx FIFO or request queue space becomes available. + * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled. + */ +static void process_periodic_channels(dwc_otg_hcd_t * hcd) +{ + hptxsts_data_t tx_status; + dwc_list_link_t *qh_ptr; + dwc_otg_qh_t *qh; + int status; + int no_queue_space = 0; + int no_fifo_space = 0; + + dwc_otg_host_global_regs_t *host_regs; + host_regs = hcd->core_if->host_if->host_global_regs; + + DWC_DEBUGPL(DBG_HCDV, "Queue periodic transactions\n"); +#ifdef DEBUG + tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts); + DWC_DEBUGPL(DBG_HCDV, + " P Tx Req Queue Space Avail (before queue): %d\n", + tx_status.b.ptxqspcavail); + DWC_DEBUGPL(DBG_HCDV, " P Tx FIFO Space Avail (before queue): %d\n", + tx_status.b.ptxfspcavail); +#endif + + qh_ptr = hcd->periodic_sched_assigned.next; + while (qh_ptr != &hcd->periodic_sched_assigned) { + tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts); + if (tx_status.b.ptxqspcavail == 0) { + no_queue_space = 1; + break; + } + + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + + /* + * Set a flag if we're queuing high-bandwidth in slave mode. + * The flag prevents any halts to get into the request queue in + * the middle of multiple high-bandwidth packets getting queued. + */ + if (!hcd->core_if->dma_enable && qh->channel->multi_count > 1) { + hcd->core_if->queuing_high_bandwidth = 1; + } + status = + queue_transaction(hcd, qh->channel, + tx_status.b.ptxfspcavail); + if (status < 0) { + no_fifo_space = 1; + break; + } + + /* + * In Slave mode, stay on the current transfer until there is + * nothing more to do or the high-bandwidth request count is + * reached. In DMA mode, only need to queue one request. The + * controller automatically handles multiple packets for + * high-bandwidth transfers. + */ + if (hcd->core_if->dma_enable || status == 0 || + qh->channel->requests == qh->channel->multi_count) { + qh_ptr = qh_ptr->next; + /* + * Move the QH from the periodic assigned schedule to + * the periodic queued schedule. + */ + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_queued, + &qh->qh_list_entry); + + /* done queuing high bandwidth */ + hcd->core_if->queuing_high_bandwidth = 0; + } + } + + if (!hcd->core_if->dma_enable) { + dwc_otg_core_global_regs_t *global_regs; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + global_regs = hcd->core_if->core_global_regs; + intr_mask.b.ptxfempty = 1; +#ifdef DEBUG + tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts); + DWC_DEBUGPL(DBG_HCDV, + " P Tx Req Queue Space Avail (after queue): %d\n", + tx_status.b.ptxqspcavail); + DWC_DEBUGPL(DBG_HCDV, + " P Tx FIFO Space Avail (after queue): %d\n", + tx_status.b.ptxfspcavail); +#endif + if (!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned) || + no_queue_space || no_fifo_space) { + /* + * May need to queue more transactions as the request + * queue or Tx FIFO empties. Enable the periodic Tx + * FIFO empty interrupt. (Always use the half-empty + * level to ensure that new requests are loaded as + * soon as possible.) + */ + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, + intr_mask.d32); + } else { + /* + * Disable the Tx FIFO empty interrupt since there are + * no more transactions that need to be queued right + * now. This function is called from interrupt + * handlers to queue more transactions as transfer + * states change. + */ + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, + 0); + } + } +} + +/** + * Processes active non-periodic channels and queues transactions for these + * channels to the DWC_otg controller. After queueing transactions, the NP Tx + * FIFO Empty interrupt is enabled if there are more transactions to queue as + * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx + * FIFO Empty interrupt is disabled. + */ +static void process_non_periodic_channels(dwc_otg_hcd_t * hcd) +{ + gnptxsts_data_t tx_status; + dwc_list_link_t *orig_qh_ptr; + dwc_otg_qh_t *qh; + int status; + int no_queue_space = 0; + int no_fifo_space = 0; + int more_to_do = 0; + + dwc_otg_core_global_regs_t *global_regs = + hcd->core_if->core_global_regs; + + DWC_DEBUGPL(DBG_HCDV, "Queue non-periodic transactions\n"); +#ifdef DEBUG + tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + DWC_DEBUGPL(DBG_HCDV, + " NP Tx Req Queue Space Avail (before queue): %d\n", + tx_status.b.nptxqspcavail); + DWC_DEBUGPL(DBG_HCDV, " NP Tx FIFO Space Avail (before queue): %d\n", + tx_status.b.nptxfspcavail); +#endif + /* + * Keep track of the starting point. Skip over the start-of-list + * entry. + */ + if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active) { + hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next; + } + orig_qh_ptr = hcd->non_periodic_qh_ptr; + + /* + * Process once through the active list or until no more space is + * available in the request queue or the Tx FIFO. + */ + do { + tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + if (!hcd->core_if->dma_enable && tx_status.b.nptxqspcavail == 0) { + no_queue_space = 1; + break; + } + + qh = DWC_LIST_ENTRY(hcd->non_periodic_qh_ptr, dwc_otg_qh_t, + qh_list_entry); + status = + queue_transaction(hcd, qh->channel, + tx_status.b.nptxfspcavail); + + if (status > 0) { + more_to_do = 1; + } else if (status < 0) { + no_fifo_space = 1; + break; + } + + /* Advance to next QH, skipping start-of-list entry. */ + hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next; + if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active) { + hcd->non_periodic_qh_ptr = + hcd->non_periodic_qh_ptr->next; + } + + } while (hcd->non_periodic_qh_ptr != orig_qh_ptr); + + if (!hcd->core_if->dma_enable) { + gintmsk_data_t intr_mask = {.d32 = 0 }; + intr_mask.b.nptxfempty = 1; + +#ifdef DEBUG + tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + DWC_DEBUGPL(DBG_HCDV, + " NP Tx Req Queue Space Avail (after queue): %d\n", + tx_status.b.nptxqspcavail); + DWC_DEBUGPL(DBG_HCDV, + " NP Tx FIFO Space Avail (after queue): %d\n", + tx_status.b.nptxfspcavail); +#endif + if (more_to_do || no_queue_space || no_fifo_space) { + /* + * May need to queue more transactions as the request + * queue or Tx FIFO empties. Enable the non-periodic + * Tx FIFO empty interrupt. (Always use the half-empty + * level to ensure that new requests are loaded as + * soon as possible.) + */ + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, + intr_mask.d32); + } else { + /* + * Disable the Tx FIFO empty interrupt since there are + * no more transactions that need to be queued right + * now. This function is called from interrupt + * handlers to queue more transactions as transfer + * states change. + */ + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, + 0); + } + } +} + +/** + * This function processes the currently active host channels and queues + * transactions for these channels to the DWC_otg controller. It is called + * from HCD interrupt handler functions. + * + * @param hcd The HCD state structure. + * @param tr_type The type(s) of transactions to queue (non-periodic, + * periodic, or both). + */ +void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd, + dwc_otg_transaction_type_e tr_type) +{ +#ifdef DEBUG_SOF + DWC_DEBUGPL(DBG_HCD, "Queue Transactions\n"); +#endif + /* Process host channels associated with periodic transfers. */ + if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC || + tr_type == DWC_OTG_TRANSACTION_ALL) && + !DWC_LIST_EMPTY(&hcd->periodic_sched_assigned)) { + + process_periodic_channels(hcd); + } + + /* Process host channels associated with non-periodic transfers. */ + if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC || + tr_type == DWC_OTG_TRANSACTION_ALL) { + if (!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active)) { + process_non_periodic_channels(hcd); + } else { + /* + * Ensure NP Tx FIFO empty interrupt is disabled when + * there are no non-periodic transfers to process. + */ + gintmsk_data_t gintmsk = {.d32 = 0 }; + gintmsk.b.nptxfempty = 1; + DWC_MODIFY_REG32(&hcd->core_if-> + core_global_regs->gintmsk, gintmsk.d32, + 0); + } + } +} + +#ifdef DWC_HS_ELECT_TST +/* + * Quick and dirty hack to implement the HS Electrical Test + * SINGLE_STEP_GET_DEVICE_DESCRIPTOR feature. + * + * This code was copied from our userspace app "hset". It sends a + * Get Device Descriptor control sequence in two parts, first the + * Setup packet by itself, followed some time later by the In and + * Ack packets. Rather than trying to figure out how to add this + * functionality to the normal driver code, we just hijack the + * hardware, using these two function to drive the hardware + * directly. + */ + +static dwc_otg_core_global_regs_t *global_regs; +static dwc_otg_host_global_regs_t *hc_global_regs; +static dwc_otg_hc_regs_t *hc_regs; +static uint32_t *data_fifo; + +static void do_setup(void) +{ + gintsts_data_t gintsts; + hctsiz_data_t hctsiz; + hcchar_data_t hcchar; + haint_data_t haint; + hcint_data_t hcint; + + /* Enable HAINTs */ + DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0001); + + /* Enable HCINTs */ + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x04a3); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* + * Send Setup packet (Get Device Descriptor) + */ + + /* Make sure channel is disabled */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + hcchar.b.chdis = 1; +// hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + //sleep(1); + dwc_mdelay(1000); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + } + + /* Set HCTSIZ */ + hctsiz.d32 = 0; + hctsiz.b.xfersize = 8; + hctsiz.b.pktcnt = 1; + hctsiz.b.pid = DWC_OTG_HC_PID_SETUP; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + /* Set HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; + hcchar.b.epdir = 0; + hcchar.b.epnum = 0; + hcchar.b.mps = 8; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + /* Fill FIFO with Setup data for Get Device Descriptor */ + data_fifo = (uint32_t *) ((char *)global_regs + 0x1000); + DWC_WRITE_REG32(data_fifo++, 0x01000680); + DWC_WRITE_REG32(data_fifo++, 0x00080000); + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for host channel interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.hcintr == 0); + + /* Disable HCINTs */ + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x0000); + + /* Disable HAINTs */ + DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0000); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); +} + +static void do_in_ack(void) +{ + gintsts_data_t gintsts; + hctsiz_data_t hctsiz; + hcchar_data_t hcchar; + haint_data_t haint; + hcint_data_t hcint; + host_grxsts_data_t grxsts; + + /* Enable HAINTs */ + DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0001); + + /* Enable HCINTs */ + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x04a3); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* + * Receive Control In packet + */ + + /* Make sure channel is disabled */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + hcchar.b.chdis = 1; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + //sleep(1); + dwc_mdelay(1000); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + } + + /* Set HCTSIZ */ + hctsiz.d32 = 0; + hctsiz.b.xfersize = 8; + hctsiz.b.pktcnt = 1; + hctsiz.b.pid = DWC_OTG_HC_PID_DATA1; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + /* Set HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; + hcchar.b.epdir = 1; + hcchar.b.epnum = 0; + hcchar.b.mps = 8; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for receive status queue interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.rxstsqlvl == 0); + + /* Read RXSTS */ + grxsts.d32 = DWC_READ_REG32(&global_regs->grxstsp); + + /* Clear RXSTSQLVL in GINTSTS */ + gintsts.d32 = 0; + gintsts.b.rxstsqlvl = 1; + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + switch (grxsts.b.pktsts) { + case DWC_GRXSTS_PKTSTS_IN: + /* Read the data into the host buffer */ + if (grxsts.b.bcnt > 0) { + int i; + int word_count = (grxsts.b.bcnt + 3) / 4; + + data_fifo = (uint32_t *) ((char *)global_regs + 0x1000); + + for (i = 0; i < word_count; i++) { + (void)DWC_READ_REG32(data_fifo++); + } + } + break; + + default: + break; + } + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for receive status queue interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.rxstsqlvl == 0); + + /* Read RXSTS */ + grxsts.d32 = DWC_READ_REG32(&global_regs->grxstsp); + + /* Clear RXSTSQLVL in GINTSTS */ + gintsts.d32 = 0; + gintsts.b.rxstsqlvl = 1; + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + switch (grxsts.b.pktsts) { + case DWC_GRXSTS_PKTSTS_IN_XFER_COMP: + break; + + default: + break; + } + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for host channel interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.hcintr == 0); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + +// usleep(100000); +// mdelay(100); + dwc_mdelay(1); + + /* + * Send handshake packet + */ + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Make sure channel is disabled */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + hcchar.b.chdis = 1; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + //sleep(1); + dwc_mdelay(1000); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + } + + /* Set HCTSIZ */ + hctsiz.d32 = 0; + hctsiz.b.xfersize = 0; + hctsiz.b.pktcnt = 1; + hctsiz.b.pid = DWC_OTG_HC_PID_DATA1; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + /* Set HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; + hcchar.b.epdir = 0; + hcchar.b.epnum = 0; + hcchar.b.mps = 8; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for host channel interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.hcintr == 0); + + /* Disable HCINTs */ + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x0000); + + /* Disable HAINTs */ + DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0000); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); +} +#endif + +/** Handles hub class-specific requests. */ +int dwc_otg_hcd_hub_control(dwc_otg_hcd_t * dwc_otg_hcd, + uint16_t typeReq, + uint16_t wValue, + uint16_t wIndex, uint8_t * buf, uint16_t wLength) +{ + int retval = 0; + + dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; + usb_hub_descriptor_t *hub_desc; + hprt0_data_t hprt0 = {.d32 = 0 }; + + uint32_t port_status; + + switch (typeReq) { + case UCR_CLEAR_HUB_FEATURE: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearHubFeature 0x%x\n", wValue); + switch (wValue) { + case UHF_C_HUB_LOCAL_POWER: + case UHF_C_HUB_OVER_CURRENT: + /* Nothing required here */ + break; + default: + retval = -DWC_E_INVALID; + DWC_ERROR("DWC OTG HCD - " + "ClearHubFeature request %xh unknown\n", + wValue); + } + break; + case UCR_CLEAR_PORT_FEATURE: +#ifdef CONFIG_USB_DWC_OTG_LPM + if (wValue != UHF_PORT_L1) +#endif + if (!wIndex || wIndex > 1) + goto error; + + switch (wValue) { + case UHF_PORT_ENABLE: + DWC_DEBUGPL(DBG_ANY, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_ENABLE\n"); + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtena = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + break; + case UHF_PORT_SUSPEND: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); + + if (core_if->power_down == 2) { + dwc_otg_host_hibernation_restore(core_if, 0, 0); + } else { + DWC_WRITE_REG32(core_if->pcgcctl, 0); + dwc_mdelay(5); + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtres = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + hprt0.b.prtsusp = 0; + /* Clear Resume bit */ + dwc_mdelay(100); + hprt0.b.prtres = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + } + break; +#ifdef CONFIG_USB_DWC_OTG_LPM + case UHF_PORT_L1: + { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + glpmcfg_data_t lpmcfg = {.d32 = 0 }; + + lpmcfg.d32 = + DWC_READ_REG32(&core_if-> + core_global_regs->glpmcfg); + lpmcfg.b.en_utmi_sleep = 0; + lpmcfg.b.hird_thres &= (~(1 << 4)); + lpmcfg.b.prt_sleep_sts = 1; + DWC_WRITE_REG32(&core_if-> + core_global_regs->glpmcfg, + lpmcfg.d32); + + /* Clear Enbl_L1Gating bit. */ + pcgcctl.b.enbl_sleep_gating = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, + 0); + + dwc_mdelay(5); + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtres = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, + hprt0.d32); + /* This bit will be cleared in wakeup interrupt handle */ + break; + } +#endif + case UHF_PORT_POWER: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_POWER\n"); + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + break; + case UHF_PORT_INDICATOR: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_INDICATOR\n"); + /* Port inidicator not supported */ + break; + case UHF_C_PORT_CONNECTION: + /* Clears drivers internal connect status change + * flag */ + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n"); + dwc_otg_hcd->flags.b.port_connect_status_change = 0; + break; + case UHF_C_PORT_RESET: + /* Clears the driver's internal Port Reset Change + * flag */ + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_RESET\n"); + dwc_otg_hcd->flags.b.port_reset_change = 0; + break; + case UHF_C_PORT_ENABLE: + /* Clears the driver's internal Port + * Enable/Disable Change flag */ + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_ENABLE\n"); + dwc_otg_hcd->flags.b.port_enable_change = 0; + break; + case UHF_C_PORT_SUSPEND: + /* Clears the driver's internal Port Suspend + * Change flag, which is set when resume signaling on + * the host port is complete */ + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n"); + dwc_otg_hcd->flags.b.port_suspend_change = 0; + break; +#ifdef CONFIG_USB_DWC_OTG_LPM + case UHF_C_PORT_L1: + dwc_otg_hcd->flags.b.port_l1_change = 0; + break; +#endif + case UHF_C_PORT_OVER_CURRENT: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n"); + dwc_otg_hcd->flags.b.port_over_current_change = 0; + break; + default: + retval = -DWC_E_INVALID; + DWC_ERROR("DWC OTG HCD - " + "ClearPortFeature request %xh " + "unknown or unsupported\n", wValue); + } + break; + case UCR_GET_HUB_DESCRIPTOR: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "GetHubDescriptor\n"); + hub_desc = (usb_hub_descriptor_t *) buf; + hub_desc->bDescLength = 9; + hub_desc->bDescriptorType = 0x29; + hub_desc->bNbrPorts = 1; + USETW(hub_desc->wHubCharacteristics, 0x08); + hub_desc->bPwrOn2PwrGood = 1; + hub_desc->bHubContrCurrent = 0; + hub_desc->DeviceRemovable[0] = 0; + hub_desc->DeviceRemovable[1] = 0xff; + break; + case UCR_GET_HUB_STATUS: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "GetHubStatus\n"); + DWC_MEMSET(buf, 0, 4); + break; + case UCR_GET_PORT_STATUS: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "GetPortStatus wIndex = 0x%04x FLAGS=0x%08x\n", + wIndex, dwc_otg_hcd->flags.d32); + if (!wIndex || wIndex > 1) + goto error; + + port_status = 0; + + if (dwc_otg_hcd->flags.b.port_connect_status_change) + port_status |= (1 << UHF_C_PORT_CONNECTION); + + if (dwc_otg_hcd->flags.b.port_enable_change) + port_status |= (1 << UHF_C_PORT_ENABLE); + + if (dwc_otg_hcd->flags.b.port_suspend_change) + port_status |= (1 << UHF_C_PORT_SUSPEND); + + if (dwc_otg_hcd->flags.b.port_l1_change) + port_status |= (1 << UHF_C_PORT_L1); + + if (dwc_otg_hcd->flags.b.port_reset_change) { + port_status |= (1 << UHF_C_PORT_RESET); + } + + if (dwc_otg_hcd->flags.b.port_over_current_change) { + DWC_WARN("Overcurrent change detected\n"); + port_status |= (1 << UHF_C_PORT_OVER_CURRENT); + } + + if (!dwc_otg_hcd->flags.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return 0's for the remainder of the port status + * since the port register can't be read if the core + * is in device mode. + */ + *((__le32 *) buf) = dwc_cpu_to_le32(&port_status); + break; + } + + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + DWC_DEBUGPL(DBG_HCDV, " HPRT0: 0x%08x\n", hprt0.d32); + + if (hprt0.b.prtconnsts) + port_status |= (1 << UHF_PORT_CONNECTION); + + if (hprt0.b.prtena) + port_status |= (1 << UHF_PORT_ENABLE); + + if (hprt0.b.prtsusp) + port_status |= (1 << UHF_PORT_SUSPEND); + + if (hprt0.b.prtovrcurract) + port_status |= (1 << UHF_PORT_OVER_CURRENT); + + if (hprt0.b.prtrst) + port_status |= (1 << UHF_PORT_RESET); + + if (hprt0.b.prtpwr) + port_status |= (1 << UHF_PORT_POWER); + + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) + port_status |= (1 << UHF_PORT_HIGH_SPEED); + else if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED) + port_status |= (1 << UHF_PORT_LOW_SPEED); + + if (hprt0.b.prttstctl) + port_status |= (1 << UHF_PORT_TEST); + if (dwc_otg_get_lpm_portsleepstatus(dwc_otg_hcd->core_if)) { + port_status |= (1 << UHF_PORT_L1); + } + /* + For Synopsys HW emulation of Power down wkup_control asserts the + hreset_n and prst_n on suspned. This causes the HPRT0 to be zero. + We intentionally tell the software that port is in L2Suspend state. + Only for STE. + */ + if ((core_if->power_down == 2) + && (core_if->hibernation_suspend == 1)) { + port_status |= (1 << UHF_PORT_SUSPEND); + } + /* USB_PORT_FEAT_INDICATOR unsupported always 0 */ + + *((__le32 *) buf) = dwc_cpu_to_le32(&port_status); + + break; + case UCR_SET_HUB_FEATURE: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "SetHubFeature\n"); + /* No HUB features supported */ + break; + case UCR_SET_PORT_FEATURE: + if (wValue != UHF_PORT_TEST && (!wIndex || wIndex > 1)) + goto error; + + if (!dwc_otg_hcd->flags.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return without doing anything since the port + * register can't be written if the core is in device + * mode. + */ + break; + } + + switch (wValue) { + case UHF_PORT_SUSPEND: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_SUSPEND\n"); + if (dwc_otg_hcd_otg_port(dwc_otg_hcd) != wIndex) { + goto error; + } + if (core_if->power_down == 2) { + int timeout = 300; + dwc_irqflags_t flags; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + gusbcfg_data_t gusbcfg = {.d32 = 0 }; +#ifdef DWC_DEV_SRPCAP + int32_t otg_cap_param = core_if->core_params->otg_cap; +#endif + DWC_PRINTF("Preparing for complete power-off\n"); + + /* Save registers before hibernation */ + dwc_otg_save_global_regs(core_if); + dwc_otg_save_host_regs(core_if); + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 1; + hprt0.b.prtena = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + /* Spin hprt0.b.prtsusp to became 1 */ + do { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + if (hprt0.b.prtsusp) { + break; + } + dwc_mdelay(1); + } while (--timeout); + if (!timeout) { + DWC_WARN("Suspend wasn't genereted\n"); + } + dwc_udelay(10); + + /* + * We need to disable interrupts to prevent servicing of any IRQ + * during going to hibernation + */ + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); + core_if->lx_state = DWC_OTG_L2; +#ifdef DWC_DEV_SRPCAP + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 0; + hprt0.b.prtena = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, + hprt0.d32); +#endif + gusbcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs-> + gusbcfg); + if (gusbcfg.b.ulpi_utmi_sel == 1) { + /* ULPI interface */ + /* Suspend the Phy Clock */ + pcgcctl.d32 = 0; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, + pcgcctl.d32); + dwc_udelay(10); + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } else { + /* UTMI+ Interface */ + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32); + dwc_udelay(10); + } +#ifdef DWC_DEV_SRPCAP + gpwrdn.d32 = 0; + gpwrdn.b.dis_vbus = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); +#endif + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + gpwrdn.d32 = 0; +#ifdef DWC_DEV_SRPCAP + gpwrdn.b.srp_det_msk = 1; +#endif + gpwrdn.b.disconn_det_msk = 1; + gpwrdn.b.lnstchng_msk = 1; + gpwrdn.b.sts_chngint_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Enable Power Down Clamp and all interrupts in GPWRDN */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Switch off VDD */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + +#ifdef DWC_DEV_SRPCAP + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) + { + core_if->pwron_timer_started = 1; + DWC_TIMER_SCHEDULE(core_if->pwron_timer, 6000 /* 6 secs */ ); + } +#endif + /* Save gpwrdn register for further usage if stschng interrupt */ + core_if->gr_backup->gpwrdn_local = + DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + + /* Set flag to indicate that we are in hibernation */ + core_if->hibernation_suspend = 1; + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock,flags); + + DWC_PRINTF("Host hibernation completed\n"); + // Exit from case statement + break; + + } + if (dwc_otg_hcd_otg_port(dwc_otg_hcd) == wIndex && + dwc_otg_hcd->fops->get_b_hnp_enable(dwc_otg_hcd)) { + gotgctl_data_t gotgctl = {.d32 = 0 }; + gotgctl.b.hstsethnpen = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gotgctl, 0, gotgctl.d32); + core_if->op_state = A_SUSPEND; + } + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + { + dwc_irqflags_t flags; + /* Update lx_state */ + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); + core_if->lx_state = DWC_OTG_L2; + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); + } + /* Suspend the Phy Clock */ + { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, + pcgcctl.d32); + dwc_udelay(10); + } + + /* For HNP the bus must be suspended for at least 200ms. */ + if (dwc_otg_hcd->fops->get_b_hnp_enable(dwc_otg_hcd)) { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + dwc_mdelay(200); + } + + /** @todo - check how sw can wait for 1 sec to check asesvld??? */ +#if 0 //vahrama !!!!!!!!!!!!!!!!!! + if (core_if->adp_enable) { + gotgctl_data_t gotgctl = {.d32 = 0 }; + gpwrdn_data_t gpwrdn; + + while (gotgctl.b.asesvld == 1) { + gotgctl.d32 = + DWC_READ_REG32(&core_if-> + core_global_regs-> + gotgctl); + dwc_mdelay(100); + } + + /* Enable Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + + /* Unmask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + + dwc_otg_adp_probe_start(core_if); + } +#endif + break; + case UHF_PORT_POWER: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_POWER\n"); + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + break; + case UHF_PORT_RESET: + if ((core_if->power_down == 2) + && (core_if->hibernation_suspend == 1)) { + /* If we are going to exit from Hibernated + * state via USB RESET. + */ + dwc_otg_host_hibernation_restore(core_if, 0, 1); + } else { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + + DWC_DEBUGPL(DBG_HCD, + "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_RESET\n"); + { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + pcgcctl.b.enbl_sleep_gating = 1; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + DWC_WRITE_REG32(core_if->pcgcctl, 0); + } +#ifdef CONFIG_USB_DWC_OTG_LPM + { + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + if (lpmcfg.b.prt_sleep_sts) { + lpmcfg.b.en_utmi_sleep = 0; + lpmcfg.b.hird_thres &= (~(1 << 4)); + DWC_WRITE_REG32 + (&core_if->core_global_regs->glpmcfg, + lpmcfg.d32); + dwc_mdelay(1); + } + } +#endif + hprt0.d32 = dwc_otg_read_hprt0(core_if); + /* Clear suspend bit if resetting from suspended state. */ + hprt0.b.prtsusp = 0; + /* When B-Host the Port reset bit is set in + * the Start HCD Callback function, so that + * the reset is started within 1ms of the HNP + * success interrupt. */ + if (!dwc_otg_hcd_is_b_host(dwc_otg_hcd)) { + hprt0.b.prtpwr = 1; + hprt0.b.prtrst = 1; + DWC_PRINTF("Indeed it is in host mode hprt0 = %08x\n",hprt0.d32); + DWC_WRITE_REG32(core_if->host_if->hprt0, + hprt0.d32); + } + /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */ + dwc_mdelay(60); + hprt0.b.prtrst = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + core_if->lx_state = DWC_OTG_L0; /* Now back to the on state */ + } + break; +#ifdef DWC_HS_ELECT_TST + case UHF_PORT_TEST: + { + uint32_t t; + gintmsk_data_t gintmsk; + + t = (wIndex >> 8); /* MSB wIndex USB */ + DWC_DEBUGPL(DBG_HCD, + "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_TEST %d\n", + t); + DWC_WARN("USB_PORT_FEAT_TEST %d\n", t); + if (t < 6) { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prttstctl = t; + DWC_WRITE_REG32(core_if->host_if->hprt0, + hprt0.d32); + } else { + /* Setup global vars with reg addresses (quick and + * dirty hack, should be cleaned up) + */ + global_regs = core_if->core_global_regs; + hc_global_regs = + core_if->host_if->host_global_regs; + hc_regs = + (dwc_otg_hc_regs_t *) ((char *) + global_regs + + 0x500); + data_fifo = + (uint32_t *) ((char *)global_regs + + 0x1000); + + if (t == 6) { /* HS_HOST_PORT_SUSPEND_RESUME */ + /* Save current interrupt mask */ + gintmsk.d32 = + DWC_READ_REG32 + (&global_regs->gintmsk); + + /* Disable all interrupts while we muck with + * the hardware directly + */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* 15 second delay per the test spec */ + dwc_mdelay(15000); + + /* Drive suspend on the root port */ + hprt0.d32 = + dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 1; + hprt0.b.prtres = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* 15 second delay per the test spec */ + dwc_mdelay(15000); + + /* Drive resume on the root port */ + hprt0.d32 = + dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 0; + hprt0.b.prtres = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + dwc_mdelay(100); + + /* Clear the resume bit */ + hprt0.b.prtres = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* Restore interrupts */ + DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32); + } else if (t == 7) { /* SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup */ + /* Save current interrupt mask */ + gintmsk.d32 = + DWC_READ_REG32 + (&global_regs->gintmsk); + + /* Disable all interrupts while we muck with + * the hardware directly + */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* 15 second delay per the test spec */ + dwc_mdelay(15000); + + /* Send the Setup packet */ + do_setup(); + + /* 15 second delay so nothing else happens for awhile */ + dwc_mdelay(15000); + + /* Restore interrupts */ + DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32); + } else if (t == 8) { /* SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute */ + /* Save current interrupt mask */ + gintmsk.d32 = + DWC_READ_REG32 + (&global_regs->gintmsk); + + /* Disable all interrupts while we muck with + * the hardware directly + */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* Send the Setup packet */ + do_setup(); + + /* 15 second delay so nothing else happens for awhile */ + dwc_mdelay(15000); + + /* Send the In and Ack packets */ + do_in_ack(); + + /* 15 second delay so nothing else happens for awhile */ + dwc_mdelay(15000); + + /* Restore interrupts */ + DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32); + } + } + break; + } +#endif /* DWC_HS_ELECT_TST */ + + case UHF_PORT_INDICATOR: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_INDICATOR\n"); + /* Not supported */ + break; + default: + retval = -DWC_E_INVALID; + DWC_ERROR("DWC OTG HCD - " + "SetPortFeature request %xh " + "unknown or unsupported\n", wValue); + break; + } + break; +#ifdef CONFIG_USB_DWC_OTG_LPM + case UCR_SET_AND_TEST_PORT_FEATURE: + if (wValue != UHF_PORT_L1) { + goto error; + } + { + int portnum, hird, devaddr, remwake; + glpmcfg_data_t lpmcfg; + uint32_t time_usecs; + gintsts_data_t gintsts; + gintmsk_data_t gintmsk; + + if (!dwc_otg_get_param_lpm_enable(core_if)) { + goto error; + } + if (wValue != UHF_PORT_L1 || wLength != 1) { + goto error; + } + /* Check if the port currently is in SLEEP state */ + lpmcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + if (lpmcfg.b.prt_sleep_sts) { + DWC_INFO("Port is already in sleep mode\n"); + buf[0] = 0; /* Return success */ + break; + } + + portnum = wIndex & 0xf; + hird = (wIndex >> 4) & 0xf; + devaddr = (wIndex >> 8) & 0x7f; + remwake = (wIndex >> 15); + + if (portnum != 1) { + retval = -DWC_E_INVALID; + DWC_WARN + ("Wrong port number(%d) in SetandTestPortFeature request\n", + portnum); + break; + } + + DWC_PRINTF + ("SetandTestPortFeature request: portnum = %d, hird = %d, devaddr = %d, rewake = %d\n", + portnum, hird, devaddr, remwake); + /* Disable LPM interrupt */ + gintmsk.d32 = 0; + gintmsk.b.lpmtranrcvd = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, + gintmsk.d32, 0); + + if (dwc_otg_hcd_send_lpm + (dwc_otg_hcd, devaddr, hird, remwake)) { + retval = -DWC_E_INVALID; + break; + } + + time_usecs = 10 * (lpmcfg.b.retry_count + 1); + /* We will consider timeout if time_usecs microseconds pass, + * and we don't receive LPM transaction status. + * After receiving non-error responce(ACK/NYET/STALL) from device, + * core will set lpmtranrcvd bit. + */ + do { + gintsts.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gintsts); + if (gintsts.b.lpmtranrcvd) { + break; + } + dwc_udelay(1); + } while (--time_usecs); + /* lpm_int bit will be cleared in LPM interrupt handler */ + + /* Now fill status + * 0x00 - Success + * 0x10 - NYET + * 0x11 - Timeout + */ + if (!gintsts.b.lpmtranrcvd) { + buf[0] = 0x3; /* Completion code is Timeout */ + dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd); + } else { + lpmcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + if (lpmcfg.b.lpm_resp == 0x3) { + /* ACK responce from the device */ + buf[0] = 0x00; /* Success */ + } else if (lpmcfg.b.lpm_resp == 0x2) { + /* NYET responce from the device */ + buf[0] = 0x2; + } else { + /* Otherwise responce with Timeout */ + buf[0] = 0x3; + } + } + DWC_PRINTF("Device responce to LPM trans is %x\n", + lpmcfg.b.lpm_resp); + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, + gintmsk.d32); + + break; + } +#endif /* CONFIG_USB_DWC_OTG_LPM */ + default: +error: + retval = -DWC_E_INVALID; + DWC_WARN("DWC OTG HCD - " + "Unknown hub control request type or invalid typeReq: %xh wIndex: %xh wValue: %xh\n", + typeReq, wIndex, wValue); + break; + } + + return retval; +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** Returns index of host channel to perform LPM transaction. */ +int dwc_otg_hcd_get_hc_for_lpm_tran(dwc_otg_hcd_t * hcd, uint8_t devaddr) +{ + dwc_otg_core_if_t *core_if = hcd->core_if; + dwc_hc_t *hc; + hcchar_data_t hcchar; + gintmsk_data_t gintmsk = {.d32 = 0 }; + + if (DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) { + DWC_PRINTF("No free channel to select for LPM transaction\n"); + return -1; + } + + hc = DWC_CIRCLEQ_FIRST(&hcd->free_hc_list); + + /* Mask host channel interrupts. */ + gintmsk.b.hcintr = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32, 0); + + /* Fill fields that core needs for LPM transaction */ + hcchar.b.devaddr = devaddr; + hcchar.b.epnum = 0; + hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; + hcchar.b.mps = 64; + hcchar.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW); + hcchar.b.epdir = 0; /* OUT */ + DWC_WRITE_REG32(&core_if->host_if->hc_regs[hc->hc_num]->hcchar, + hcchar.d32); + + /* Remove the host channel from the free list. */ + DWC_CIRCLEQ_REMOVE_INIT(&hcd->free_hc_list, hc, hc_list_entry); + + DWC_PRINTF("hcnum = %d devaddr = %d\n", hc->hc_num, devaddr); + + return hc->hc_num; +} + +/** Release hc after performing LPM transaction */ +void dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd_t * hcd) +{ + dwc_hc_t *hc; + glpmcfg_data_t lpmcfg; + uint8_t hc_num; + + lpmcfg.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->glpmcfg); + hc_num = lpmcfg.b.lpm_chan_index; + + hc = hcd->hc_ptr_array[hc_num]; + + DWC_PRINTF("Freeing channel %d after LPM\n", hc_num); + /* Return host channel to free list */ + DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry); +} + +int dwc_otg_hcd_send_lpm(dwc_otg_hcd_t * hcd, uint8_t devaddr, uint8_t hird, + uint8_t bRemoteWake) +{ + glpmcfg_data_t lpmcfg; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + int channel; + + channel = dwc_otg_hcd_get_hc_for_lpm_tran(hcd, devaddr); + if (channel < 0) { + return channel; + } + + pcgcctl.b.enbl_sleep_gating = 1; + DWC_MODIFY_REG32(hcd->core_if->pcgcctl, 0, pcgcctl.d32); + + /* Read LPM config register */ + lpmcfg.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->glpmcfg); + + /* Program LPM transaction fields */ + lpmcfg.b.rem_wkup_en = bRemoteWake; + lpmcfg.b.hird = hird; + lpmcfg.b.hird_thres = 0x1c; + lpmcfg.b.lpm_chan_index = channel; + lpmcfg.b.en_utmi_sleep = 1; + /* Program LPM config register */ + DWC_WRITE_REG32(&hcd->core_if->core_global_regs->glpmcfg, lpmcfg.d32); + + /* Send LPM transaction */ + lpmcfg.b.send_lpm = 1; + DWC_WRITE_REG32(&hcd->core_if->core_global_regs->glpmcfg, lpmcfg.d32); + + return 0; +} + +#endif /* CONFIG_USB_DWC_OTG_LPM */ + +int dwc_otg_hcd_is_status_changed(dwc_otg_hcd_t * hcd, int port) +{ + int retval; + + if (port != 1) { + return -DWC_E_INVALID; + } + + retval = (hcd->flags.b.port_connect_status_change || + hcd->flags.b.port_reset_change || + hcd->flags.b.port_enable_change || + hcd->flags.b.port_suspend_change || + hcd->flags.b.port_over_current_change); +#ifdef DEBUG + if (retval) { + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB STATUS DATA:" + " Root port status changed\n"); + DWC_DEBUGPL(DBG_HCDV, " port_connect_status_change: %d\n", + hcd->flags.b.port_connect_status_change); + DWC_DEBUGPL(DBG_HCDV, " port_reset_change: %d\n", + hcd->flags.b.port_reset_change); + DWC_DEBUGPL(DBG_HCDV, " port_enable_change: %d\n", + hcd->flags.b.port_enable_change); + DWC_DEBUGPL(DBG_HCDV, " port_suspend_change: %d\n", + hcd->flags.b.port_suspend_change); + DWC_DEBUGPL(DBG_HCDV, " port_over_current_change: %d\n", + hcd->flags.b.port_over_current_change); + } +#endif + return retval; +} + +int dwc_otg_hcd_get_frame_number(dwc_otg_hcd_t * dwc_otg_hcd) +{ + hfnum_data_t hfnum; + hfnum.d32 = + DWC_READ_REG32(&dwc_otg_hcd->core_if->host_if->host_global_regs-> + hfnum); + +#ifdef DEBUG_SOF + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD GET FRAME NUMBER %d\n", + hfnum.b.frnum); +#endif + return hfnum.b.frnum; +} + +int dwc_otg_hcd_start(dwc_otg_hcd_t * hcd, + struct dwc_otg_hcd_function_ops *fops) +{ + int retval = 0; + + hcd->fops = fops; + if (!dwc_otg_is_device_mode(hcd->core_if) && + (!hcd->core_if->adp_enable || hcd->core_if->adp.adp_started)) { + dwc_otg_hcd_reinit(hcd); + } else { + retval = -DWC_E_NO_DEVICE; + } + + return retval; +} + +void *dwc_otg_hcd_get_priv_data(dwc_otg_hcd_t * hcd) +{ + return hcd->priv; +} + +void dwc_otg_hcd_set_priv_data(dwc_otg_hcd_t * hcd, void *priv_data) +{ + hcd->priv = priv_data; +} + +uint32_t dwc_otg_hcd_otg_port(dwc_otg_hcd_t * hcd) +{ + return hcd->otg_port; +} + +uint32_t dwc_otg_hcd_is_b_host(dwc_otg_hcd_t * hcd) +{ + uint32_t is_b_host; + if (hcd->core_if->op_state == B_HOST) { + is_b_host = 1; + } else { + is_b_host = 0; + } + + return is_b_host; +} + +dwc_otg_hcd_urb_t *dwc_otg_hcd_urb_alloc(dwc_otg_hcd_t * hcd, + int iso_desc_count, int atomic_alloc) +{ + dwc_otg_hcd_urb_t *dwc_otg_urb; + uint32_t size; + + size = + sizeof(*dwc_otg_urb) + + iso_desc_count * sizeof(struct dwc_otg_hcd_iso_packet_desc); + if (atomic_alloc) + dwc_otg_urb = DWC_ALLOC_ATOMIC(size); + else + dwc_otg_urb = DWC_ALLOC(size); + + dwc_otg_urb->packet_count = iso_desc_count; + + return dwc_otg_urb; +} + +void dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_hcd_urb_t * dwc_otg_urb, + uint8_t dev_addr, uint8_t ep_num, + uint8_t ep_type, uint8_t ep_dir, uint16_t mps) +{ + dwc_otg_hcd_fill_pipe(&dwc_otg_urb->pipe_info, dev_addr, ep_num, + ep_type, ep_dir, mps); +#if 0 + DWC_PRINTF + ("addr = %d, ep_num = %d, ep_dir = 0x%x, ep_type = 0x%x, mps = %d\n", + dev_addr, ep_num, ep_dir, ep_type, mps); +#endif +} + +void dwc_otg_hcd_urb_set_params(dwc_otg_hcd_urb_t * dwc_otg_urb, + void *urb_handle, void *buf, dwc_dma_t dma, + uint32_t buflen, void *setup_packet, + dwc_dma_t setup_dma, uint32_t flags, + uint16_t interval) +{ + dwc_otg_urb->priv = urb_handle; + dwc_otg_urb->buf = buf; + dwc_otg_urb->dma = dma; + dwc_otg_urb->length = buflen; + dwc_otg_urb->setup_packet = setup_packet; + dwc_otg_urb->setup_dma = setup_dma; + dwc_otg_urb->flags = flags; + dwc_otg_urb->interval = interval; + dwc_otg_urb->status = -DWC_E_IN_PROGRESS; +} + +uint32_t dwc_otg_hcd_urb_get_status(dwc_otg_hcd_urb_t * dwc_otg_urb) +{ + return dwc_otg_urb->status; +} + +uint32_t dwc_otg_hcd_urb_get_actual_length(dwc_otg_hcd_urb_t * dwc_otg_urb) +{ + return dwc_otg_urb->actual_length; +} + +uint32_t dwc_otg_hcd_urb_get_error_count(dwc_otg_hcd_urb_t * dwc_otg_urb) +{ + return dwc_otg_urb->error_count; +} + +void dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_hcd_urb_t * dwc_otg_urb, + int desc_num, uint32_t offset, + uint32_t length) +{ + dwc_otg_urb->iso_descs[desc_num].offset = offset; + dwc_otg_urb->iso_descs[desc_num].length = length; +} + +uint32_t dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_hcd_urb_t * dwc_otg_urb, + int desc_num) +{ + return dwc_otg_urb->iso_descs[desc_num].status; +} + +uint32_t dwc_otg_hcd_urb_get_iso_desc_actual_length(dwc_otg_hcd_urb_t * + dwc_otg_urb, int desc_num) +{ + return dwc_otg_urb->iso_descs[desc_num].actual_length; +} + +int dwc_otg_hcd_is_bandwidth_allocated(dwc_otg_hcd_t * hcd, void *ep_handle) +{ + int allocated = 0; + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + + if (qh) { + if (!DWC_LIST_EMPTY(&qh->qh_list_entry)) { + allocated = 1; + } + } + return allocated; +} + +int dwc_otg_hcd_is_bandwidth_freed(dwc_otg_hcd_t * hcd, void *ep_handle) +{ + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + int freed = 0; + DWC_ASSERT(qh, "qh is not allocated\n"); + + if (DWC_LIST_EMPTY(&qh->qh_list_entry)) { + freed = 1; + } + + return freed; +} + +uint8_t dwc_otg_hcd_get_ep_bandwidth(dwc_otg_hcd_t * hcd, void *ep_handle) +{ + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + DWC_ASSERT(qh, "qh is not allocated\n"); + return qh->usecs; +} + +void dwc_otg_hcd_dump_state(dwc_otg_hcd_t * hcd) +{ +#ifdef DEBUG + int num_channels; + int i; + gnptxsts_data_t np_tx_status; + hptxsts_data_t p_tx_status; + + num_channels = hcd->core_if->core_params->host_channels; + DWC_PRINTF("\n"); + DWC_PRINTF + ("************************************************************\n"); + DWC_PRINTF("HCD State:\n"); + DWC_PRINTF(" Num channels: %d\n", num_channels); + for (i = 0; i < num_channels; i++) { + dwc_hc_t *hc = hcd->hc_ptr_array[i]; + DWC_PRINTF(" Channel %d:\n", i); + DWC_PRINTF(" dev_addr: %d, ep_num: %d, ep_is_in: %d\n", + hc->dev_addr, hc->ep_num, hc->ep_is_in); + DWC_PRINTF(" speed: %d\n", hc->speed); + DWC_PRINTF(" ep_type: %d\n", hc->ep_type); + DWC_PRINTF(" max_packet: %d\n", hc->max_packet); + DWC_PRINTF(" data_pid_start: %d\n", hc->data_pid_start); + DWC_PRINTF(" multi_count: %d\n", hc->multi_count); + DWC_PRINTF(" xfer_started: %d\n", hc->xfer_started); + DWC_PRINTF(" xfer_buff: %p\n", hc->xfer_buff); + DWC_PRINTF(" xfer_len: %d\n", hc->xfer_len); + DWC_PRINTF(" xfer_count: %d\n", hc->xfer_count); + DWC_PRINTF(" halt_on_queue: %d\n", hc->halt_on_queue); + DWC_PRINTF(" halt_pending: %d\n", hc->halt_pending); + DWC_PRINTF(" halt_status: %d\n", hc->halt_status); + DWC_PRINTF(" do_split: %d\n", hc->do_split); + DWC_PRINTF(" complete_split: %d\n", hc->complete_split); + DWC_PRINTF(" hub_addr: %d\n", hc->hub_addr); + DWC_PRINTF(" port_addr: %d\n", hc->port_addr); + DWC_PRINTF(" xact_pos: %d\n", hc->xact_pos); + DWC_PRINTF(" requests: %d\n", hc->requests); + DWC_PRINTF(" qh: %p\n", hc->qh); + if (hc->xfer_started) { + hfnum_data_t hfnum; + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + hfnum.d32 = + DWC_READ_REG32(&hcd->core_if-> + host_if->host_global_regs->hfnum); + hcchar.d32 = + DWC_READ_REG32(&hcd->core_if->host_if-> + hc_regs[i]->hcchar); + hctsiz.d32 = + DWC_READ_REG32(&hcd->core_if->host_if-> + hc_regs[i]->hctsiz); + hcint.d32 = + DWC_READ_REG32(&hcd->core_if->host_if-> + hc_regs[i]->hcint); + hcintmsk.d32 = + DWC_READ_REG32(&hcd->core_if->host_if-> + hc_regs[i]->hcintmsk); + DWC_PRINTF(" hfnum: 0x%08x\n", hfnum.d32); + DWC_PRINTF(" hcchar: 0x%08x\n", hcchar.d32); + DWC_PRINTF(" hctsiz: 0x%08x\n", hctsiz.d32); + DWC_PRINTF(" hcint: 0x%08x\n", hcint.d32); + DWC_PRINTF(" hcintmsk: 0x%08x\n", hcintmsk.d32); + } + if (hc->xfer_started && hc->qh) { + dwc_otg_qtd_t *qtd; + dwc_otg_hcd_urb_t *urb; + + DWC_CIRCLEQ_FOREACH(qtd, &hc->qh->qtd_list, qtd_list_entry) { + if (!qtd->in_process) + break; + + urb = qtd->urb; + DWC_PRINTF(" URB Info:\n"); + DWC_PRINTF(" qtd: %p, urb: %p\n", qtd, urb); + if (urb) { + DWC_PRINTF(" Dev: %d, EP: %d %s\n", + dwc_otg_hcd_get_dev_addr(&urb-> + pipe_info), + dwc_otg_hcd_get_ep_num(&urb-> + pipe_info), + dwc_otg_hcd_is_pipe_in(&urb-> + pipe_info) ? + "IN" : "OUT"); + DWC_PRINTF(" Max packet size: %d\n", + dwc_otg_hcd_get_mps(&urb-> + pipe_info)); + DWC_PRINTF(" transfer_buffer: %p\n", + urb->buf); + DWC_PRINTF(" transfer_dma: %p\n", + (void *)urb->dma); + DWC_PRINTF(" transfer_buffer_length: %d\n", + urb->length); + DWC_PRINTF(" actual_length: %d\n", + urb->actual_length); + } + } + } + } + DWC_PRINTF(" non_periodic_channels: %d\n", hcd->non_periodic_channels); + DWC_PRINTF(" periodic_channels: %d\n", hcd->periodic_channels); + DWC_PRINTF(" periodic_usecs: %d\n", hcd->periodic_usecs); + np_tx_status.d32 = + DWC_READ_REG32(&hcd->core_if->core_global_regs->gnptxsts); + DWC_PRINTF(" NP Tx Req Queue Space Avail: %d\n", + np_tx_status.b.nptxqspcavail); + DWC_PRINTF(" NP Tx FIFO Space Avail: %d\n", + np_tx_status.b.nptxfspcavail); + p_tx_status.d32 = + DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hptxsts); + DWC_PRINTF(" P Tx Req Queue Space Avail: %d\n", + p_tx_status.b.ptxqspcavail); + DWC_PRINTF(" P Tx FIFO Space Avail: %d\n", p_tx_status.b.ptxfspcavail); + dwc_otg_hcd_dump_frrem(hcd); + dwc_otg_dump_global_registers(hcd->core_if); + dwc_otg_dump_host_registers(hcd->core_if); + DWC_PRINTF + ("************************************************************\n"); + DWC_PRINTF("\n"); +#endif +} + +#ifdef DEBUG +void dwc_print_setup_data(uint8_t * setup) +{ + int i; + if (CHK_DEBUG_LEVEL(DBG_HCD)) { + DWC_PRINTF("Setup Data = MSB "); + for (i = 7; i >= 0; i--) + DWC_PRINTF("%02x ", setup[i]); + DWC_PRINTF("\n"); + DWC_PRINTF(" bmRequestType Tranfer = %s\n", + (setup[0] & 0x80) ? "Device-to-Host" : + "Host-to-Device"); + DWC_PRINTF(" bmRequestType Type = "); + switch ((setup[0] & 0x60) >> 5) { + case 0: + DWC_PRINTF("Standard\n"); + break; + case 1: + DWC_PRINTF("Class\n"); + break; + case 2: + DWC_PRINTF("Vendor\n"); + break; + case 3: + DWC_PRINTF("Reserved\n"); + break; + } + DWC_PRINTF(" bmRequestType Recipient = "); + switch (setup[0] & 0x1f) { + case 0: + DWC_PRINTF("Device\n"); + break; + case 1: + DWC_PRINTF("Interface\n"); + break; + case 2: + DWC_PRINTF("Endpoint\n"); + break; + case 3: + DWC_PRINTF("Other\n"); + break; + default: + DWC_PRINTF("Reserved\n"); + break; + } + DWC_PRINTF(" bRequest = 0x%0x\n", setup[1]); + DWC_PRINTF(" wValue = 0x%0x\n", *((uint16_t *) & setup[2])); + DWC_PRINTF(" wIndex = 0x%0x\n", *((uint16_t *) & setup[4])); + DWC_PRINTF(" wLength = 0x%0x\n\n", *((uint16_t *) & setup[6])); + } +} +#endif + +void dwc_otg_hcd_dump_frrem(dwc_otg_hcd_t * hcd) +{ +#if 0 + DWC_PRINTF("Frame remaining at SOF:\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->frrem_samples, hcd->frrem_accum, + (hcd->frrem_samples > 0) ? + hcd->frrem_accum / hcd->frrem_samples : 0); + + DWC_PRINTF("\n"); + DWC_PRINTF("Frame remaining at start_transfer (uframe 7):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->core_if->hfnum_7_samples, + hcd->core_if->hfnum_7_frrem_accum, + (hcd->core_if->hfnum_7_samples > + 0) ? hcd->core_if->hfnum_7_frrem_accum / + hcd->core_if->hfnum_7_samples : 0); + DWC_PRINTF("Frame remaining at start_transfer (uframe 0):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->core_if->hfnum_0_samples, + hcd->core_if->hfnum_0_frrem_accum, + (hcd->core_if->hfnum_0_samples > + 0) ? hcd->core_if->hfnum_0_frrem_accum / + hcd->core_if->hfnum_0_samples : 0); + DWC_PRINTF("Frame remaining at start_transfer (uframe 1-6):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->core_if->hfnum_other_samples, + hcd->core_if->hfnum_other_frrem_accum, + (hcd->core_if->hfnum_other_samples > + 0) ? hcd->core_if->hfnum_other_frrem_accum / + hcd->core_if->hfnum_other_samples : 0); + + DWC_PRINTF("\n"); + DWC_PRINTF("Frame remaining at sample point A (uframe 7):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_7_samples_a, hcd->hfnum_7_frrem_accum_a, + (hcd->hfnum_7_samples_a > 0) ? + hcd->hfnum_7_frrem_accum_a / hcd->hfnum_7_samples_a : 0); + DWC_PRINTF("Frame remaining at sample point A (uframe 0):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_0_samples_a, hcd->hfnum_0_frrem_accum_a, + (hcd->hfnum_0_samples_a > 0) ? + hcd->hfnum_0_frrem_accum_a / hcd->hfnum_0_samples_a : 0); + DWC_PRINTF("Frame remaining at sample point A (uframe 1-6):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_other_samples_a, hcd->hfnum_other_frrem_accum_a, + (hcd->hfnum_other_samples_a > 0) ? + hcd->hfnum_other_frrem_accum_a / + hcd->hfnum_other_samples_a : 0); + + DWC_PRINTF("\n"); + DWC_PRINTF("Frame remaining at sample point B (uframe 7):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_7_samples_b, hcd->hfnum_7_frrem_accum_b, + (hcd->hfnum_7_samples_b > 0) ? + hcd->hfnum_7_frrem_accum_b / hcd->hfnum_7_samples_b : 0); + DWC_PRINTF("Frame remaining at sample point B (uframe 0):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_0_samples_b, hcd->hfnum_0_frrem_accum_b, + (hcd->hfnum_0_samples_b > 0) ? + hcd->hfnum_0_frrem_accum_b / hcd->hfnum_0_samples_b : 0); + DWC_PRINTF("Frame remaining at sample point B (uframe 1-6):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_other_samples_b, hcd->hfnum_other_frrem_accum_b, + (hcd->hfnum_other_samples_b > 0) ? + hcd->hfnum_other_frrem_accum_b / + hcd->hfnum_other_samples_b : 0); +#endif +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_hcd.h b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd.h new file mode 100644 index 00000000..58a12934 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd.h @@ -0,0 +1,803 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.h $ + * $Revision: #58 $ + * $Date: 2011/09/15 $ + * $Change: 1846647 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY +#ifndef __DWC_HCD_H__ +#define __DWC_HCD_H__ + +#include "dwc_otg_os_dep.h" +#include "usb.h" +#include "dwc_otg_hcd_if.h" +#include "dwc_otg_core_if.h" +#include "dwc_list.h" +#include "dwc_otg_cil.h" + +/** + * @file + * + * This file contains the structures, constants, and interfaces for + * the Host Contoller Driver (HCD). + * + * The Host Controller Driver (HCD) is responsible for translating requests + * from the USB Driver into the appropriate actions on the DWC_otg controller. + * It isolates the USBD from the specifics of the controller by providing an + * API to the USBD. + */ + +struct dwc_otg_hcd_pipe_info { + uint8_t dev_addr; + uint8_t ep_num; + uint8_t pipe_type; + uint8_t pipe_dir; + uint16_t mps; +}; + +struct dwc_otg_hcd_iso_packet_desc { + uint32_t offset; + uint32_t length; + uint32_t actual_length; + uint32_t status; +}; + +struct dwc_otg_qtd; + +struct dwc_otg_hcd_urb { + void *priv; + struct dwc_otg_qtd *qtd; + void *buf; + dwc_dma_t dma; + void *setup_packet; + dwc_dma_t setup_dma; + uint32_t length; + uint32_t actual_length; + uint32_t status; + uint32_t error_count; + uint32_t packet_count; + uint32_t flags; + uint16_t interval; + struct dwc_otg_hcd_pipe_info pipe_info; + struct dwc_otg_hcd_iso_packet_desc iso_descs[0]; +}; + +static inline uint8_t dwc_otg_hcd_get_ep_num(struct dwc_otg_hcd_pipe_info *pipe) +{ + return pipe->ep_num; +} + +static inline uint8_t dwc_otg_hcd_get_pipe_type(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return pipe->pipe_type; +} + +static inline uint16_t dwc_otg_hcd_get_mps(struct dwc_otg_hcd_pipe_info *pipe) +{ + return pipe->mps; +} + +static inline uint8_t dwc_otg_hcd_get_dev_addr(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return pipe->dev_addr; +} + +static inline uint8_t dwc_otg_hcd_is_pipe_isoc(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (pipe->pipe_type == UE_ISOCHRONOUS); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_int(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (pipe->pipe_type == UE_INTERRUPT); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_bulk(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (pipe->pipe_type == UE_BULK); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_control(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (pipe->pipe_type == UE_CONTROL); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_in(struct dwc_otg_hcd_pipe_info *pipe) +{ + return (pipe->pipe_dir == UE_DIR_IN); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_out(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (!dwc_otg_hcd_is_pipe_in(pipe)); +} + +static inline void dwc_otg_hcd_fill_pipe(struct dwc_otg_hcd_pipe_info *pipe, + uint8_t devaddr, uint8_t ep_num, + uint8_t pipe_type, uint8_t pipe_dir, + uint16_t mps) +{ + pipe->dev_addr = devaddr; + pipe->ep_num = ep_num; + pipe->pipe_type = pipe_type; + pipe->pipe_dir = pipe_dir; + pipe->mps = mps; +} + +/** + * Phases for control transfers. + */ +typedef enum dwc_otg_control_phase { + DWC_OTG_CONTROL_SETUP, + DWC_OTG_CONTROL_DATA, + DWC_OTG_CONTROL_STATUS +} dwc_otg_control_phase_e; + +/** Transaction types. */ +typedef enum dwc_otg_transaction_type { + DWC_OTG_TRANSACTION_NONE, + DWC_OTG_TRANSACTION_PERIODIC, + DWC_OTG_TRANSACTION_NON_PERIODIC, + DWC_OTG_TRANSACTION_ALL +} dwc_otg_transaction_type_e; + +struct dwc_otg_qh; + +/** + * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control, + * interrupt, or isochronous transfer. A single QTD is created for each URB + * (of one of these types) submitted to the HCD. The transfer associated with + * a QTD may require one or multiple transactions. + * + * A QTD is linked to a Queue Head, which is entered in either the + * non-periodic or periodic schedule for execution. When a QTD is chosen for + * execution, some or all of its transactions may be executed. After + * execution, the state of the QTD is updated. The QTD may be retired if all + * its transactions are complete or if an error occurred. Otherwise, it + * remains in the schedule so more transactions can be executed later. + */ +typedef struct dwc_otg_qtd { + /** + * Determines the PID of the next data packet for the data phase of + * control transfers. Ignored for other transfer types.
+ * One of the following values: + * - DWC_OTG_HC_PID_DATA0 + * - DWC_OTG_HC_PID_DATA1 + */ + uint8_t data_toggle; + + /** Current phase for control transfers (Setup, Data, or Status). */ + dwc_otg_control_phase_e control_phase; + + /** Keep track of the current split type + * for FS/LS endpoints on a HS Hub */ + uint8_t complete_split; + + /** How many bytes transferred during SSPLIT OUT */ + uint32_t ssplit_out_xfer_count; + + /** + * Holds the number of bus errors that have occurred for a transaction + * within this transfer. + */ + uint8_t error_count; + + /** + * Index of the next frame descriptor for an isochronous transfer. A + * frame descriptor describes the buffer position and length of the + * data to be transferred in the next scheduled (micro)frame of an + * isochronous transfer. It also holds status for that transaction. + * The frame index starts at 0. + */ + uint16_t isoc_frame_index; + + /** Position of the ISOC split on full/low speed */ + uint8_t isoc_split_pos; + + /** Position of the ISOC split in the buffer for the current frame */ + uint16_t isoc_split_offset; + + /** URB for this transfer */ + struct dwc_otg_hcd_urb *urb; + + struct dwc_otg_qh *qh; + + /** This list of QTDs */ + DWC_CIRCLEQ_ENTRY(dwc_otg_qtd) qtd_list_entry; + + /** Indicates if this QTD is currently processed by HW. */ + uint8_t in_process; + + /** Number of DMA descriptors for this QTD */ + uint8_t n_desc; + + /** + * Last activated frame(packet) index. + * Used in Descriptor DMA mode only. + */ + uint16_t isoc_frame_index_last; + +} dwc_otg_qtd_t; + +DWC_CIRCLEQ_HEAD(dwc_otg_qtd_list, dwc_otg_qtd); + +/** + * A Queue Head (QH) holds the static characteristics of an endpoint and + * maintains a list of transfers (QTDs) for that endpoint. A QH structure may + * be entered in either the non-periodic or periodic schedule. + */ +typedef struct dwc_otg_qh { + /** + * Endpoint type. + * One of the following values: + * - UE_CONTROL + * - UE_BULK + * - UE_INTERRUPT + * - UE_ISOCHRONOUS + */ + uint8_t ep_type; + uint8_t ep_is_in; + + /** wMaxPacketSize Field of Endpoint Descriptor. */ + uint16_t maxp; + + /** + * Device speed. + * One of the following values: + * - DWC_OTG_EP_SPEED_LOW + * - DWC_OTG_EP_SPEED_FULL + * - DWC_OTG_EP_SPEED_HIGH + */ + uint8_t dev_speed; + + /** + * Determines the PID of the next data packet for non-control + * transfers. Ignored for control transfers.
+ * One of the following values: + * - DWC_OTG_HC_PID_DATA0 + * - DWC_OTG_HC_PID_DATA1 + */ + uint8_t data_toggle; + + /** Ping state if 1. */ + uint8_t ping_state; + + /** + * List of QTDs for this QH. + */ + struct dwc_otg_qtd_list qtd_list; + + /** Host channel currently processing transfers for this QH. */ + struct dwc_hc *channel; + + /** Full/low speed endpoint on high-speed hub requires split. */ + uint8_t do_split; + + /** @name Periodic schedule information */ + /** @{ */ + + /** Bandwidth in microseconds per (micro)frame. */ + uint16_t usecs; + + /** Interval between transfers in (micro)frames. */ + uint16_t interval; + + /** + * (micro)frame to initialize a periodic transfer. The transfer + * executes in the following (micro)frame. + */ + uint16_t sched_frame; + + /** (micro)frame at which last start split was initialized. */ + uint16_t start_split_frame; + + /** @} */ + + /** + * Used instead of original buffer if + * it(physical address) is not dword-aligned. + */ + uint8_t *dw_align_buf; + dwc_dma_t dw_align_buf_dma; + + /** Entry for QH in either the periodic or non-periodic schedule. */ + dwc_list_link_t qh_list_entry; + + /** @name Descriptor DMA support */ + /** @{ */ + + /** Descriptor List. */ + dwc_otg_host_dma_desc_t *desc_list; + + /** Descriptor List physical address. */ + dwc_dma_t desc_list_dma; + + /** + * Xfer Bytes array. + * Each element corresponds to a descriptor and indicates + * original XferSize size value for the descriptor. + */ + uint32_t *n_bytes; + + /** Actual number of transfer descriptors in a list. */ + uint16_t ntd; + + /** First activated isochronous transfer descriptor index. */ + uint8_t td_first; + /** Last activated isochronous transfer descriptor index. */ + uint8_t td_last; + + /** @} */ + +} dwc_otg_qh_t; + +DWC_CIRCLEQ_HEAD(hc_list, dwc_hc); + +/** + * This structure holds the state of the HCD, including the non-periodic and + * periodic schedules. + */ +struct dwc_otg_hcd { + /** The DWC otg device pointer */ + struct dwc_otg_device *otg_dev; + /** DWC OTG Core Interface Layer */ + dwc_otg_core_if_t *core_if; + + /** Function HCD driver callbacks */ + struct dwc_otg_hcd_function_ops *fops; + + /** Internal DWC HCD Flags */ + volatile union dwc_otg_hcd_internal_flags { + uint32_t d32; + struct { + unsigned port_connect_status_change:1; + unsigned port_connect_status:1; + unsigned port_reset_change:1; + unsigned port_enable_change:1; + unsigned port_suspend_change:1; + unsigned port_over_current_change:1; + unsigned port_l1_change:1; + unsigned reserved:26; + } b; + } flags; + + /** + * Inactive items in the non-periodic schedule. This is a list of + * Queue Heads. Transfers associated with these Queue Heads are not + * currently assigned to a host channel. + */ + dwc_list_link_t non_periodic_sched_inactive; + + /** + * Active items in the non-periodic schedule. This is a list of + * Queue Heads. Transfers associated with these Queue Heads are + * currently assigned to a host channel. + */ + dwc_list_link_t non_periodic_sched_active; + + /** + * Pointer to the next Queue Head to process in the active + * non-periodic schedule. + */ + dwc_list_link_t *non_periodic_qh_ptr; + + /** + * Inactive items in the periodic schedule. This is a list of QHs for + * periodic transfers that are _not_ scheduled for the next frame. + * Each QH in the list has an interval counter that determines when it + * needs to be scheduled for execution. This scheduling mechanism + * allows only a simple calculation for periodic bandwidth used (i.e. + * must assume that all periodic transfers may need to execute in the + * same frame). However, it greatly simplifies scheduling and should + * be sufficient for the vast majority of OTG hosts, which need to + * connect to a small number of peripherals at one time. + * + * Items move from this list to periodic_sched_ready when the QH + * interval counter is 0 at SOF. + */ + dwc_list_link_t periodic_sched_inactive; + + /** + * List of periodic QHs that are ready for execution in the next + * frame, but have not yet been assigned to host channels. + * + * Items move from this list to periodic_sched_assigned as host + * channels become available during the current frame. + */ + dwc_list_link_t periodic_sched_ready; + + /** + * List of periodic QHs to be executed in the next frame that are + * assigned to host channels. + * + * Items move from this list to periodic_sched_queued as the + * transactions for the QH are queued to the DWC_otg controller. + */ + dwc_list_link_t periodic_sched_assigned; + + /** + * List of periodic QHs that have been queued for execution. + * + * Items move from this list to either periodic_sched_inactive or + * periodic_sched_ready when the channel associated with the transfer + * is released. If the interval for the QH is 1, the item moves to + * periodic_sched_ready because it must be rescheduled for the next + * frame. Otherwise, the item moves to periodic_sched_inactive. + */ + dwc_list_link_t periodic_sched_queued; + + /** + * Total bandwidth claimed so far for periodic transfers. This value + * is in microseconds per (micro)frame. The assumption is that all + * periodic transfers may occur in the same (micro)frame. + */ + uint16_t periodic_usecs; + + /** + * Frame number read from the core at SOF. The value ranges from 0 to + * DWC_HFNUM_MAX_FRNUM. + */ + uint16_t frame_number; + + /** + * Count of periodic QHs, if using several eps. For SOF enable/disable. + */ + uint16_t periodic_qh_count; + + /** + * Free host channels in the controller. This is a list of + * dwc_hc_t items. + */ + struct hc_list free_hc_list; + /** + * Number of host channels assigned to periodic transfers. Currently + * assuming that there is a dedicated host channel for each periodic + * transaction and at least one host channel available for + * non-periodic transactions. + */ + int periodic_channels; + + /** + * Number of host channels assigned to non-periodic transfers. + */ + int non_periodic_channels; + + /** + * Array of pointers to the host channel descriptors. Allows accessing + * a host channel descriptor given the host channel number. This is + * useful in interrupt handlers. + */ + struct dwc_hc *hc_ptr_array[MAX_EPS_CHANNELS]; + + /** + * Buffer to use for any data received during the status phase of a + * control transfer. Normally no data is transferred during the status + * phase. This buffer is used as a bit bucket. + */ + uint8_t *status_buf; + + /** + * DMA address for status_buf. + */ + dma_addr_t status_buf_dma; +#define DWC_OTG_HCD_STATUS_BUF_SIZE 64 + + /** + * Connection timer. An OTG host must display a message if the device + * does not connect. Started when the VBus power is turned on via + * sysfs attribute "buspower". + */ + dwc_timer_t *conn_timer; + + /* Tasket to do a reset */ + dwc_tasklet_t *reset_tasklet; + + /* */ + dwc_spinlock_t *lock; + + /** + * Private data that could be used by OS wrapper. + */ + void *priv; + + uint8_t otg_port; + + /** Frame List */ + uint32_t *frame_list; + + /** Frame List DMA address */ + dma_addr_t frame_list_dma; + +#ifdef DEBUG + uint32_t frrem_samples; + uint64_t frrem_accum; + + uint32_t hfnum_7_samples_a; + uint64_t hfnum_7_frrem_accum_a; + uint32_t hfnum_0_samples_a; + uint64_t hfnum_0_frrem_accum_a; + uint32_t hfnum_other_samples_a; + uint64_t hfnum_other_frrem_accum_a; + + uint32_t hfnum_7_samples_b; + uint64_t hfnum_7_frrem_accum_b; + uint32_t hfnum_0_samples_b; + uint64_t hfnum_0_frrem_accum_b; + uint32_t hfnum_other_samples_b; + uint64_t hfnum_other_frrem_accum_b; +#endif +}; + +/** @name Transaction Execution Functions */ +/** @{ */ +extern dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t + * hcd); +extern void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd, + dwc_otg_transaction_type_e tr_type); + +/** @} */ + +/** @name Interrupt Handler Functions */ +/** @{ */ +extern int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_incomplete_periodic_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_port_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_conn_id_status_change_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_disconnect_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, + uint32_t num); +extern int32_t dwc_otg_hcd_handle_session_req_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_wakeup_detected_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +/** @} */ + +/** @name Schedule Queue Functions */ +/** @{ */ + +/* Implemented in dwc_otg_hcd_queue.c */ +extern dwc_otg_qh_t *dwc_otg_hcd_qh_create(dwc_otg_hcd_t * hcd, + dwc_otg_hcd_urb_t * urb, int atomic_alloc); +extern void dwc_otg_hcd_qh_free(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern int dwc_otg_hcd_qh_add(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern void dwc_otg_hcd_qh_remove(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, + int sched_csplit); + +/** Remove and free a QH */ +static inline void dwc_otg_hcd_qh_remove_and_free(dwc_otg_hcd_t * hcd, + dwc_otg_qh_t * qh) +{ + dwc_irqflags_t flags; + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + dwc_otg_hcd_qh_remove(hcd, qh); + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + dwc_otg_hcd_qh_free(hcd, qh); +} + +/** Allocates memory for a QH structure. + * @return Returns the memory allocate or NULL on error. */ +static inline dwc_otg_qh_t *dwc_otg_hcd_qh_alloc(int atomic_alloc) +{ + if (atomic_alloc) + return (dwc_otg_qh_t *) DWC_ALLOC_ATOMIC(sizeof(dwc_otg_qh_t)); + else + return (dwc_otg_qh_t *) DWC_ALLOC(sizeof(dwc_otg_qh_t)); +} + +extern dwc_otg_qtd_t *dwc_otg_hcd_qtd_create(dwc_otg_hcd_urb_t * urb, + int atomic_alloc); +extern void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t * qtd, dwc_otg_hcd_urb_t * urb); +extern int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * qtd, dwc_otg_hcd_t * dwc_otg_hcd, + dwc_otg_qh_t ** qh, int atomic_alloc); + +/** Allocates memory for a QTD structure. + * @return Returns the memory allocate or NULL on error. */ +static inline dwc_otg_qtd_t *dwc_otg_hcd_qtd_alloc(int atomic_alloc) +{ + if (atomic_alloc) + return (dwc_otg_qtd_t *) DWC_ALLOC_ATOMIC(sizeof(dwc_otg_qtd_t)); + else + return (dwc_otg_qtd_t *) DWC_ALLOC(sizeof(dwc_otg_qtd_t)); +} + +/** Frees the memory for a QTD structure. QTD should already be removed from + * list. + * @param qtd QTD to free.*/ +static inline void dwc_otg_hcd_qtd_free(dwc_otg_qtd_t * qtd) +{ + DWC_FREE(qtd); +} + +/** Removes a QTD from list. + * @param hcd HCD instance. + * @param qtd QTD to remove from list. + * @param qh QTD belongs to. + */ +static inline void dwc_otg_hcd_qtd_remove(dwc_otg_hcd_t * hcd, + dwc_otg_qtd_t * qtd, + dwc_otg_qh_t * qh) +{ + DWC_CIRCLEQ_REMOVE(&qh->qtd_list, qtd, qtd_list_entry); +} + +/** Remove and free a QTD + * Need to disable IRQ and hold hcd lock while calling this function out of + * interrupt servicing chain */ +static inline void dwc_otg_hcd_qtd_remove_and_free(dwc_otg_hcd_t * hcd, + dwc_otg_qtd_t * qtd, + dwc_otg_qh_t * qh) +{ + dwc_otg_hcd_qtd_remove(hcd, qtd, qh); + dwc_otg_hcd_qtd_free(qtd); +} + +/** @} */ + +/** @name Descriptor DMA Supporting Functions */ +/** @{ */ + +extern void dwc_otg_hcd_start_xfer_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern void dwc_otg_hcd_complete_xfer_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_halt_status_e halt_status); + +extern int dwc_otg_hcd_qh_init_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern void dwc_otg_hcd_qh_free_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); + +/** @} */ + +/** @name Internal Functions */ +/** @{ */ +dwc_otg_qh_t *dwc_urb_to_qh(dwc_otg_hcd_urb_t * urb); +/** @} */ + +#ifdef CONFIG_USB_DWC_OTG_LPM +extern int dwc_otg_hcd_get_hc_for_lpm_tran(dwc_otg_hcd_t * hcd, + uint8_t devaddr); +extern void dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd_t * hcd); +#endif + +/** Gets the QH that contains the list_head */ +#define dwc_list_to_qh(_list_head_ptr_) container_of(_list_head_ptr_, dwc_otg_qh_t, qh_list_entry) + +/** Gets the QTD that contains the list_head */ +#define dwc_list_to_qtd(_list_head_ptr_) container_of(_list_head_ptr_, dwc_otg_qtd_t, qtd_list_entry) + +/** Check if QH is non-periodic */ +#define dwc_qh_is_non_per(_qh_ptr_) ((_qh_ptr_->ep_type == UE_BULK) || \ + (_qh_ptr_->ep_type == UE_CONTROL)) + +/** High bandwidth multiplier as encoded in highspeed endpoint descriptors */ +#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) + +/** Packet size for any kind of endpoint descriptor */ +#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/** + * Returns true if _frame1 is less than or equal to _frame2. The comparison is + * done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the + * frame number when the max frame number is reached. + */ +static inline int dwc_frame_num_le(uint16_t frame1, uint16_t frame2) +{ + return ((frame2 - frame1) & DWC_HFNUM_MAX_FRNUM) <= + (DWC_HFNUM_MAX_FRNUM >> 1); +} + +/** + * Returns true if _frame1 is greater than _frame2. The comparison is done + * modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame + * number when the max frame number is reached. + */ +static inline int dwc_frame_num_gt(uint16_t frame1, uint16_t frame2) +{ + return (frame1 != frame2) && + (((frame1 - frame2) & DWC_HFNUM_MAX_FRNUM) < + (DWC_HFNUM_MAX_FRNUM >> 1)); +} + +/** + * Increments _frame by the amount specified by _inc. The addition is done + * modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value. + */ +static inline uint16_t dwc_frame_num_inc(uint16_t frame, uint16_t inc) +{ + return (frame + inc) & DWC_HFNUM_MAX_FRNUM; +} + +static inline uint16_t dwc_full_frame_num(uint16_t frame) +{ + return (frame & DWC_HFNUM_MAX_FRNUM) >> 3; +} + +static inline uint16_t dwc_micro_frame_num(uint16_t frame) +{ + return frame & 0x7; +} + +void dwc_otg_hcd_save_data_toggle(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd); + +#ifdef DEBUG +/** + * Macro to sample the remaining PHY clocks left in the current frame. This + * may be used during debugging to determine the average time it takes to + * execute sections of code. There are two possible sample points, "a" and + * "b", so the _letter argument must be one of these values. + * + * To dump the average sample times, read the "hcd_frrem" sysfs attribute. For + * example, "cat /sys/devices/lm0/hcd_frrem". + */ +#define dwc_sample_frrem(_hcd, _qh, _letter) \ +{ \ + hfnum_data_t hfnum; \ + dwc_otg_qtd_t *qtd; \ + qtd = list_entry(_qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry); \ + if (usb_pipeint(qtd->urb->pipe) && _qh->start_split_frame != 0 && !qtd->complete_split) { \ + hfnum.d32 = DWC_READ_REG32(&_hcd->core_if->host_if->host_global_regs->hfnum); \ + switch (hfnum.b.frnum & 0x7) { \ + case 7: \ + _hcd->hfnum_7_samples_##_letter++; \ + _hcd->hfnum_7_frrem_accum_##_letter += hfnum.b.frrem; \ + break; \ + case 0: \ + _hcd->hfnum_0_samples_##_letter++; \ + _hcd->hfnum_0_frrem_accum_##_letter += hfnum.b.frrem; \ + break; \ + default: \ + _hcd->hfnum_other_samples_##_letter++; \ + _hcd->hfnum_other_frrem_accum_##_letter += hfnum.b.frrem; \ + break; \ + } \ + } \ +} +#else +#define dwc_sample_frrem(_hcd, _qh, _letter) +#endif +#endif +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_ddma.c b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_ddma.c new file mode 100644 index 00000000..4c867e5f --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_ddma.c @@ -0,0 +1,1125 @@ +/*========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_ddma.c $ + * $Revision: #10 $ + * $Date: 2011/10/20 $ + * $Change: 1869464 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +/** @file + * This file contains Descriptor DMA support implementation for host mode. + */ + +#include "dwc_otg_hcd.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_driver.h" + +static inline uint8_t frame_list_idx(uint16_t frame) +{ + return (frame & (MAX_FRLIST_EN_NUM - 1)); +} + +static inline uint16_t desclist_idx_inc(uint16_t idx, uint16_t inc, uint8_t speed) +{ + return (idx + inc) & + (((speed == + DWC_OTG_EP_SPEED_HIGH) ? MAX_DMA_DESC_NUM_HS_ISOC : + MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static inline uint16_t desclist_idx_dec(uint16_t idx, uint16_t inc, uint8_t speed) +{ + return (idx - inc) & + (((speed == + DWC_OTG_EP_SPEED_HIGH) ? MAX_DMA_DESC_NUM_HS_ISOC : + MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static inline uint16_t max_desc_num(dwc_otg_qh_t * qh) +{ + return (((qh->ep_type == UE_ISOCHRONOUS) + && (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH)) + ? MAX_DMA_DESC_NUM_HS_ISOC : MAX_DMA_DESC_NUM_GENERIC); +} +static inline uint16_t frame_incr_val(dwc_otg_qh_t * qh) +{ + return ((qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) + ? ((qh->interval + 8 - 1) / 8) + : qh->interval); +} + +static int desc_list_alloc(dwc_otg_qh_t * qh) +{ + int retval = 0; + + qh->desc_list = (dwc_otg_host_dma_desc_t *) + DWC_DMA_ALLOC(get_hcd_device() , sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh), + &qh->desc_list_dma); + + if (!qh->desc_list) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: DMA descriptor list allocation failed\n", __func__); + return retval; + + } + + dwc_memset(qh->desc_list, 0x00, + sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh)); + + qh->n_bytes = + (uint32_t *) DWC_ALLOC(sizeof(uint32_t) * max_desc_num(qh)); + + if (!qh->n_bytes) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR + ("%s: Failed to allocate array for descriptors' size actual values\n", + __func__); + + } + return retval; + +} + +static void desc_list_free(dwc_otg_qh_t * qh) +{ + if (qh->desc_list) { + DWC_DMA_FREE(get_hcd_device() , max_desc_num(qh), qh->desc_list, + qh->desc_list_dma); + qh->desc_list = NULL; + } + + if (qh->n_bytes) { + DWC_FREE(qh->n_bytes); + qh->n_bytes = NULL; + } +} + +static int frame_list_alloc(dwc_otg_hcd_t * hcd) +{ + int retval = 0; + if (hcd->frame_list) + return 0; + + hcd->frame_list = DWC_DMA_ALLOC(get_hcd_device() , 4 * MAX_FRLIST_EN_NUM, + &hcd->frame_list_dma); + if (!hcd->frame_list) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: Frame List allocation failed\n", __func__); + return retval; + } + + dwc_memset(hcd->frame_list, 0x00, 4 * MAX_FRLIST_EN_NUM); + + return retval; +} + +static void frame_list_free(dwc_otg_hcd_t * hcd) +{ + if (!hcd->frame_list) + return; + + DWC_DMA_FREE(get_hcd_device() , 4 * MAX_FRLIST_EN_NUM, hcd->frame_list, hcd->frame_list_dma); + hcd->frame_list = NULL; +} + +static void per_sched_enable(dwc_otg_hcd_t * hcd, uint16_t fr_list_en) +{ + + hcfg_data_t hcfg; + + hcfg.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hcfg); + + if (hcfg.b.perschedena) { + /* already enabled */ + return; + } + + DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hflbaddr, + hcd->frame_list_dma); + + switch (fr_list_en) { + case 64: + hcfg.b.frlisten = 3; + break; + case 32: + hcfg.b.frlisten = 2; + break; + case 16: + hcfg.b.frlisten = 1; + break; + case 8: + hcfg.b.frlisten = 0; + break; + default: + break; + } + + hcfg.b.perschedena = 1; + + DWC_DEBUGPL(DBG_HCD, "Enabling Periodic schedule\n"); + DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hcfg, hcfg.d32); + +} + +static void per_sched_disable(dwc_otg_hcd_t * hcd) +{ + hcfg_data_t hcfg; + + hcfg.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hcfg); + + if (!hcfg.b.perschedena) { + /* already disabled */ + return; + } + hcfg.b.perschedena = 0; + + DWC_DEBUGPL(DBG_HCD, "Disabling Periodic schedule\n"); + DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hcfg, hcfg.d32); +} + +/* + * Activates/Deactivates FrameList entries for the channel + * based on endpoint servicing period. + */ +void update_frame_list(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, uint8_t enable) +{ + uint16_t i, j, inc; + dwc_hc_t *hc = NULL; + + if (!qh->channel) { + DWC_ERROR("qh->channel = %p", qh->channel); + return; + } + + if (!hcd) { + DWC_ERROR("------hcd = %p", hcd); + return; + } + + if (!hcd->frame_list) { + DWC_ERROR("-------hcd->frame_list = %p", hcd->frame_list); + return; + } + + hc = qh->channel; + inc = frame_incr_val(qh); + if (qh->ep_type == UE_ISOCHRONOUS) + i = frame_list_idx(qh->sched_frame); + else + i = 0; + + j = i; + do { + if (enable) + hcd->frame_list[j] |= (1 << hc->hc_num); + else + hcd->frame_list[j] &= ~(1 << hc->hc_num); + j = (j + inc) & (MAX_FRLIST_EN_NUM - 1); + } + while (j != i); + if (!enable) + return; + hc->schinfo = 0; + if (qh->channel->speed == DWC_OTG_EP_SPEED_HIGH) { + j = 1; + /* TODO - check this */ + inc = (8 + qh->interval - 1) / qh->interval; + for (i = 0; i < inc; i++) { + hc->schinfo |= j; + j = j << qh->interval; + } + } else { + hc->schinfo = 0xff; + } +} + +#if 1 +void dump_frame_list(dwc_otg_hcd_t * hcd) +{ + int i = 0; + DWC_PRINTF("--FRAME LIST (hex) --\n"); + for (i = 0; i < MAX_FRLIST_EN_NUM; i++) { + DWC_PRINTF("%x\t", hcd->frame_list[i]); + if (!(i % 8) && i) + DWC_PRINTF("\n"); + } + DWC_PRINTF("\n----\n"); + +} +#endif + +static void release_channel_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + dwc_hc_t *hc = qh->channel; + if (dwc_qh_is_non_per(qh)) + hcd->non_periodic_channels--; + else + update_frame_list(hcd, qh, 0); + + /* + * The condition is added to prevent double cleanup try in case of device + * disconnect. See channel cleanup in dwc_otg_hcd_disconnect_cb(). + */ + if (hc->qh) { + dwc_otg_hc_cleanup(hcd->core_if, hc); + DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry); + hc->qh = NULL; + } + + qh->channel = NULL; + qh->ntd = 0; + + if (qh->desc_list) { + dwc_memset(qh->desc_list, 0x00, + sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh)); + } +} + +/** + * Initializes a QH structure's Descriptor DMA related members. + * Allocates memory for descriptor list. + * On first periodic QH, allocates memory for FrameList + * and enables periodic scheduling. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh The QH to init. + * + * @return 0 if successful, negative error code otherwise. + */ +int dwc_otg_hcd_qh_init_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int retval = 0; + + if (qh->do_split) { + DWC_ERROR("SPLIT Transfers are not supported in Descriptor DMA.\n"); + return -1; + } + + retval = desc_list_alloc(qh); + + if ((retval == 0) + && (qh->ep_type == UE_ISOCHRONOUS || qh->ep_type == UE_INTERRUPT)) { + if (!hcd->frame_list) { + retval = frame_list_alloc(hcd); + /* Enable periodic schedule on first periodic QH */ + if (retval == 0) + per_sched_enable(hcd, MAX_FRLIST_EN_NUM); + } + } + + qh->ntd = 0; + + return retval; +} + +/** + * Frees descriptor list memory associated with the QH. + * If QH is periodic and the last, frees FrameList memory + * and disables periodic scheduling. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh The QH to init. + */ +void dwc_otg_hcd_qh_free_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + desc_list_free(qh); + + /* + * Channel still assigned due to some reasons. + * Seen on Isoc URB dequeue. Channel halted but no subsequent + * ChHalted interrupt to release the channel. Afterwards + * when it comes here from endpoint disable routine + * channel remains assigned. + */ + if (qh->channel) + release_channel_ddma(hcd, qh); + + if ((qh->ep_type == UE_ISOCHRONOUS || qh->ep_type == UE_INTERRUPT) + && !hcd->periodic_channels && hcd->frame_list) { + + per_sched_disable(hcd); + frame_list_free(hcd); + } +} + +static uint8_t frame_to_desc_idx(dwc_otg_qh_t * qh, uint16_t frame_idx) +{ + if (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) { + /* + * Descriptor set(8 descriptors) index + * which is 8-aligned. + */ + return (frame_idx & ((MAX_DMA_DESC_NUM_HS_ISOC / 8) - 1)) * 8; + } else { + return (frame_idx & (MAX_DMA_DESC_NUM_GENERIC - 1)); + } +} + +/* + * Determine starting frame for Isochronous transfer. + * Few frames skipped to prevent race condition with HC. + */ +static uint8_t calc_starting_frame(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, + uint8_t * skip_frames) +{ + uint16_t frame = 0; + hcd->frame_number = dwc_otg_hcd_get_frame_number(hcd); + + /* sched_frame is always frame number(not uFrame) both in FS and HS !! */ + + /* + * skip_frames is used to limit activated descriptors number + * to avoid the situation when HC services the last activated + * descriptor firstly. + * Example for FS: + * Current frame is 1, scheduled frame is 3. Since HC always fetches the descriptor + * corresponding to curr_frame+1, the descriptor corresponding to frame 2 + * will be fetched. If the number of descriptors is max=64 (or greather) the + * list will be fully programmed with Active descriptors and it is possible + * case(rare) that the latest descriptor(considering rollback) corresponding + * to frame 2 will be serviced first. HS case is more probable because, in fact, + * up to 11 uframes(16 in the code) may be skipped. + */ + if (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) { + /* + * Consider uframe counter also, to start xfer asap. + * If half of the frame elapsed skip 2 frames otherwise + * just 1 frame. + * Starting descriptor index must be 8-aligned, so + * if the current frame is near to complete the next one + * is skipped as well. + */ + + if (dwc_micro_frame_num(hcd->frame_number) >= 5) { + *skip_frames = 2 * 8; + frame = dwc_frame_num_inc(hcd->frame_number, *skip_frames); + } else { + *skip_frames = 1 * 8; + frame = dwc_frame_num_inc(hcd->frame_number, *skip_frames); + } + + frame = dwc_full_frame_num(frame); + } else { + /* + * Two frames are skipped for FS - the current and the next. + * But for descriptor programming, 1 frame(descriptor) is enough, + * see example above. + */ + *skip_frames = 1; + frame = dwc_frame_num_inc(hcd->frame_number, 2); + } + + return frame; +} + +/* + * Calculate initial descriptor index for isochronous transfer + * based on scheduled frame. + */ +static uint8_t recalc_initial_desc_idx(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + uint16_t frame = 0, fr_idx, fr_idx_tmp; + uint8_t skip_frames = 0; + /* + * With current ISOC processing algorithm the channel is being + * released when no more QTDs in the list(qh->ntd == 0). + * Thus this function is called only when qh->ntd == 0 and qh->channel == 0. + * + * So qh->channel != NULL branch is not used and just not removed from the + * source file. It is required for another possible approach which is, + * do not disable and release the channel when ISOC session completed, + * just move QH to inactive schedule until new QTD arrives. + * On new QTD, the QH moved back to 'ready' schedule, + * starting frame and therefore starting desc_index are recalculated. + * In this case channel is released only on ep_disable. + */ + + /* Calculate starting descriptor index. For INTERRUPT endpoint it is always 0. */ + if (qh->channel) { + frame = calc_starting_frame(hcd, qh, &skip_frames); + /* + * Calculate initial descriptor index based on FrameList current bitmap + * and servicing period. + */ + fr_idx_tmp = frame_list_idx(frame); + fr_idx = + (MAX_FRLIST_EN_NUM + frame_list_idx(qh->sched_frame) - + fr_idx_tmp) + % frame_incr_val(qh); + fr_idx = (fr_idx + fr_idx_tmp) % MAX_FRLIST_EN_NUM; + } else { + qh->sched_frame = calc_starting_frame(hcd, qh, &skip_frames); + fr_idx = frame_list_idx(qh->sched_frame); + } + + qh->td_first = qh->td_last = frame_to_desc_idx(qh, fr_idx); + + return skip_frames; +} + +#define ISOC_URB_GIVEBACK_ASAP + +#define MAX_ISOC_XFER_SIZE_FS 1023 +#define MAX_ISOC_XFER_SIZE_HS 3072 +#define DESCNUM_THRESHOLD 4 + +static void init_isoc_dma_desc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, + uint8_t skip_frames) +{ + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + dwc_otg_qtd_t *qtd; + dwc_otg_host_dma_desc_t *dma_desc; + uint16_t idx, inc, n_desc, ntd_max, max_xfer_size; + + idx = qh->td_last; + inc = qh->interval; + n_desc = 0; + + ntd_max = (max_desc_num(qh) + qh->interval - 1) / qh->interval; + if (skip_frames && !qh->channel) + ntd_max = ntd_max - skip_frames / qh->interval; + + max_xfer_size = + (qh->dev_speed == + DWC_OTG_EP_SPEED_HIGH) ? MAX_ISOC_XFER_SIZE_HS : + MAX_ISOC_XFER_SIZE_FS; + + DWC_CIRCLEQ_FOREACH(qtd, &qh->qtd_list, qtd_list_entry) { + while ((qh->ntd < ntd_max) + && (qtd->isoc_frame_index_last < + qtd->urb->packet_count)) { + + dma_desc = &qh->desc_list[idx]; + dwc_memset(dma_desc, 0x00, sizeof(dwc_otg_host_dma_desc_t)); + + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last]; + + if (frame_desc->length > max_xfer_size) + qh->n_bytes[idx] = max_xfer_size; + else + qh->n_bytes[idx] = frame_desc->length; + dma_desc->status.b_isoc.n_bytes = qh->n_bytes[idx]; + dma_desc->status.b_isoc.a = 1; + dma_desc->status.b_isoc.sts = 0; + + dma_desc->buf = qtd->urb->dma + frame_desc->offset; + + qh->ntd++; + + qtd->isoc_frame_index_last++; + +#ifdef ISOC_URB_GIVEBACK_ASAP + /* + * Set IOC for each descriptor corresponding to the + * last frame of the URB. + */ + if (qtd->isoc_frame_index_last == + qtd->urb->packet_count) + dma_desc->status.b_isoc.ioc = 1; + +#endif + idx = desclist_idx_inc(idx, inc, qh->dev_speed); + n_desc++; + + } + qtd->in_process = 1; + } + + qh->td_last = idx; + +#ifdef ISOC_URB_GIVEBACK_ASAP + /* Set IOC for the last descriptor if descriptor list is full */ + if (qh->ntd == ntd_max) { + idx = desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + qh->desc_list[idx].status.b_isoc.ioc = 1; + } +#else + /* + * Set IOC bit only for one descriptor. + * Always try to be ahead of HW processing, + * i.e. on IOC generation driver activates next descriptors but + * core continues to process descriptors followed the one with IOC set. + */ + + if (n_desc > DESCNUM_THRESHOLD) { + /* + * Move IOC "up". Required even if there is only one QTD + * in the list, cause QTDs migth continue to be queued, + * but during the activation it was only one queued. + * Actually more than one QTD might be in the list if this function called + * from XferCompletion - QTDs was queued during HW processing of the previous + * descriptor chunk. + */ + idx = dwc_desclist_idx_dec(idx, inc * ((qh->ntd + 1) / 2), qh->dev_speed); + } else { + /* + * Set the IOC for the latest descriptor + * if either number of descriptor is not greather than threshold + * or no more new descriptors activated. + */ + idx = dwc_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + } + + qh->desc_list[idx].status.b_isoc.ioc = 1; +#endif +} + +static void init_non_isoc_dma_desc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + + dwc_hc_t *hc; + dwc_otg_host_dma_desc_t *dma_desc; + dwc_otg_qtd_t *qtd; + int num_packets, len, n_desc = 0; + + hc = qh->channel; + + /* + * Start with hc->xfer_buff initialized in + * assign_and_init_hc(), then if SG transfer consists of multiple URBs, + * this pointer re-assigned to the buffer of the currently processed QTD. + * For non-SG request there is always one QTD active. + */ + + DWC_CIRCLEQ_FOREACH(qtd, &qh->qtd_list, qtd_list_entry) { + + if (n_desc) { + /* SG request - more than 1 QTDs */ + hc->xfer_buff = (uint8_t *)qtd->urb->dma + qtd->urb->actual_length; + hc->xfer_len = qtd->urb->length - qtd->urb->actual_length; + } + + qtd->n_desc = 0; + + do { + dma_desc = &qh->desc_list[n_desc]; + len = hc->xfer_len; + + if (len > MAX_DMA_DESC_SIZE) + len = MAX_DMA_DESC_SIZE - hc->max_packet + 1; + + if (hc->ep_is_in) { + if (len > 0) { + num_packets = (len + hc->max_packet - 1) / hc->max_packet; + } else { + /* Need 1 packet for transfer length of 0. */ + num_packets = 1; + } + /* Always program an integral # of max packets for IN transfers. */ + len = num_packets * hc->max_packet; + } + + dma_desc->status.b.n_bytes = len; + + qh->n_bytes[n_desc] = len; + + if ((qh->ep_type == UE_CONTROL) + && (qtd->control_phase == DWC_OTG_CONTROL_SETUP)) + dma_desc->status.b.sup = 1; /* Setup Packet */ + + dma_desc->status.b.a = 1; /* Active descriptor */ + dma_desc->status.b.sts = 0; + + dma_desc->buf = + ((unsigned long)hc->xfer_buff & 0xffffffff); + + /* + * Last descriptor(or single) of IN transfer + * with actual size less than MaxPacket. + */ + if (len > hc->xfer_len) { + hc->xfer_len = 0; + } else { + hc->xfer_buff += len; + hc->xfer_len -= len; + } + + qtd->n_desc++; + n_desc++; + } + while ((hc->xfer_len > 0) && (n_desc != MAX_DMA_DESC_NUM_GENERIC)); + + + qtd->in_process = 1; + + if (qh->ep_type == UE_CONTROL) + break; + + if (n_desc == MAX_DMA_DESC_NUM_GENERIC) + break; + } + + if (n_desc) { + /* Request Transfer Complete interrupt for the last descriptor */ + qh->desc_list[n_desc - 1].status.b.ioc = 1; + /* End of List indicator */ + qh->desc_list[n_desc - 1].status.b.eol = 1; + + hc->ntd = n_desc; + } +} + +/** + * For Control and Bulk endpoints initializes descriptor list + * and starts the transfer. + * + * For Interrupt and Isochronous endpoints initializes descriptor list + * then updates FrameList, marking appropriate entries as active. + * In case of Isochronous, the starting descriptor index is calculated based + * on the scheduled frame, but only on the first transfer descriptor within a session. + * Then starts the transfer via enabling the channel. + * For Isochronous endpoint the channel is not halted on XferComplete + * interrupt so remains assigned to the endpoint(QH) until session is done. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh The QH to init. + * + * @return 0 if successful, negative error code otherwise. + */ +void dwc_otg_hcd_start_xfer_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + /* Channel is already assigned */ + dwc_hc_t *hc = qh->channel; + uint8_t skip_frames = 0; + + switch (hc->ep_type) { + case DWC_OTG_EP_TYPE_CONTROL: + case DWC_OTG_EP_TYPE_BULK: + init_non_isoc_dma_desc(hcd, qh); + + dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc); + break; + case DWC_OTG_EP_TYPE_INTR: + init_non_isoc_dma_desc(hcd, qh); + + update_frame_list(hcd, qh, 1); + + dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc); + break; + case DWC_OTG_EP_TYPE_ISOC: + + if (!qh->ntd) + skip_frames = recalc_initial_desc_idx(hcd, qh); + + init_isoc_dma_desc(hcd, qh, skip_frames); + + if (!hc->xfer_started) { + + update_frame_list(hcd, qh, 1); + + /* + * Always set to max, instead of actual size. + * Otherwise ntd will be changed with + * channel being enabled. Not recommended. + * + */ + hc->ntd = max_desc_num(qh); + /* Enable channel only once for ISOC */ + dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc); + } + + break; + default: + + break; + } +} + +static void complete_isoc_xfer_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_halt_status_e halt_status) +{ + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + dwc_otg_qtd_t *qtd, *qtd_tmp; + dwc_otg_qh_t *qh; + dwc_otg_host_dma_desc_t *dma_desc; + uint16_t idx, remain; + uint8_t urb_compl; + + qh = hc->qh; + idx = qh->td_first; + + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) { + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) + qtd->in_process = 0; + return; + } else if ((halt_status == DWC_OTG_HC_XFER_AHB_ERR) || + (halt_status == DWC_OTG_HC_XFER_BABBLE_ERR)) { + /* + * Channel is halted in these error cases. + * Considered as serious issues. + * Complete all URBs marking all frames as failed, + * irrespective whether some of the descriptors(frames) succeeded or no. + * Pass error code to completion routine as well, to + * update urb->status, some of class drivers might use it to stop + * queing transfer requests. + */ + int err = (halt_status == DWC_OTG_HC_XFER_AHB_ERR) + ? (-DWC_E_IO) + : (-DWC_E_OVERFLOW); + + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) { + for (idx = 0; idx < qtd->urb->packet_count; idx++) { + frame_desc = &qtd->urb->iso_descs[idx]; + frame_desc->status = err; + } + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, err); + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + } + return; + } + + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) { + + if (!qtd->in_process) + break; + + urb_compl = 0; + + do { + + dma_desc = &qh->desc_list[idx]; + + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + remain = hc->ep_is_in ? dma_desc->status.b_isoc.n_bytes : 0; + + if (dma_desc->status.b_isoc.sts == DMA_DESC_STS_PKTERR) { + /* + * XactError or, unable to complete all the transactions + * in the scheduled micro-frame/frame, + * both indicated by DMA_DESC_STS_PKTERR. + */ + qtd->urb->error_count++; + frame_desc->actual_length = qh->n_bytes[idx] - remain; + frame_desc->status = -DWC_E_PROTOCOL; + } else { + /* Success */ + + frame_desc->actual_length = qh->n_bytes[idx] - remain; + frame_desc->status = 0; + } + + if (++qtd->isoc_frame_index == qtd->urb->packet_count) { + /* + * urb->status is not used for isoc transfers here. + * The individual frame_desc status are used instead. + */ + + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + + /* + * This check is necessary because urb_dequeue can be called + * from urb complete callback(sound driver example). + * All pending URBs are dequeued there, so no need for + * further processing. + */ + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) { + return; + } + + urb_compl = 1; + + } + + qh->ntd--; + + /* Stop if IOC requested descriptor reached */ + if (dma_desc->status.b_isoc.ioc) { + idx = desclist_idx_inc(idx, qh->interval, hc->speed); + goto stop_scan; + } + + idx = desclist_idx_inc(idx, qh->interval, hc->speed); + + if (urb_compl) + break; + } + while (idx != qh->td_first); + } +stop_scan: + qh->td_first = idx; +} + +uint8_t update_non_isoc_urb_state_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_qtd_t * qtd, + dwc_otg_host_dma_desc_t * dma_desc, + dwc_otg_halt_status_e halt_status, + uint32_t n_bytes, uint8_t * xfer_done) +{ + + uint16_t remain = hc->ep_is_in ? dma_desc->status.b.n_bytes : 0; + dwc_otg_hcd_urb_t *urb = qtd->urb; + + if (halt_status == DWC_OTG_HC_XFER_AHB_ERR) { + urb->status = -DWC_E_IO; + return 1; + } + if (dma_desc->status.b.sts == DMA_DESC_STS_PKTERR) { + switch (halt_status) { + case DWC_OTG_HC_XFER_STALL: + urb->status = -DWC_E_PIPE; + break; + case DWC_OTG_HC_XFER_BABBLE_ERR: + urb->status = -DWC_E_OVERFLOW; + break; + case DWC_OTG_HC_XFER_XACT_ERR: + urb->status = -DWC_E_PROTOCOL; + break; + default: + DWC_ERROR("%s: Unhandled descriptor error status (%d)\n", __func__, + halt_status); + break; + } + return 1; + } + + if (dma_desc->status.b.a == 1) { + DWC_DEBUGPL(DBG_HCDV, + "Active descriptor encountered on channel %d\n", + hc->hc_num); + return 0; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL) { + if (qtd->control_phase == DWC_OTG_CONTROL_DATA) { + urb->actual_length += n_bytes - remain; + if (remain || urb->actual_length == urb->length) { + /* + * For Control Data stage do not set urb->status=0 to prevent + * URB callback. Set it when Status phase done. See below. + */ + *xfer_done = 1; + } + + } else if (qtd->control_phase == DWC_OTG_CONTROL_STATUS) { + urb->status = 0; + *xfer_done = 1; + } + /* No handling for SETUP stage */ + } else { + /* BULK and INTR */ + urb->actual_length += n_bytes - remain; + if (remain || urb->actual_length == urb->length) { + urb->status = 0; + *xfer_done = 1; + } + } + + return 0; +} + +static void complete_non_isoc_xfer_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_halt_status_e halt_status) +{ + dwc_otg_hcd_urb_t *urb = NULL; + dwc_otg_qtd_t *qtd, *qtd_tmp; + dwc_otg_qh_t *qh; + dwc_otg_host_dma_desc_t *dma_desc; + uint32_t n_bytes, n_desc, i; + uint8_t failed = 0, xfer_done; + + n_desc = 0; + + qh = hc->qh; + + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) { + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) { + qtd->in_process = 0; + } + return; + } + + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { + + urb = qtd->urb; + + n_bytes = 0; + xfer_done = 0; + + for (i = 0; i < qtd->n_desc; i++) { + dma_desc = &qh->desc_list[n_desc]; + + n_bytes = qh->n_bytes[n_desc]; + + failed = + update_non_isoc_urb_state_ddma(hcd, hc, qtd, + dma_desc, + halt_status, n_bytes, + &xfer_done); + + if (failed + || (xfer_done + && (urb->status != -DWC_E_IN_PROGRESS))) { + + hcd->fops->complete(hcd, urb->priv, urb, + urb->status); + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + + if (failed) + goto stop_scan; + } else if (qh->ep_type == UE_CONTROL) { + if (qtd->control_phase == DWC_OTG_CONTROL_SETUP) { + if (urb->length > 0) { + qtd->control_phase = DWC_OTG_CONTROL_DATA; + } else { + qtd->control_phase = DWC_OTG_CONTROL_STATUS; + } + DWC_DEBUGPL(DBG_HCDV, " Control setup transaction done\n"); + } else if (qtd->control_phase == DWC_OTG_CONTROL_DATA) { + if (xfer_done) { + qtd->control_phase = DWC_OTG_CONTROL_STATUS; + DWC_DEBUGPL(DBG_HCDV, " Control data transfer done\n"); + } else if (i + 1 == qtd->n_desc) { + /* + * Last descriptor for Control data stage which is + * not completed yet. + */ + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + } + } + } + + n_desc++; + } + + } + +stop_scan: + + if (qh->ep_type != UE_CONTROL) { + /* + * Resetting the data toggle for bulk + * and interrupt endpoints in case of stall. See handle_hc_stall_intr() + */ + if (halt_status == DWC_OTG_HC_XFER_STALL) + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + else + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + } + + if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { + hcint_data_t hcint; + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + if (hcint.b.nyet) { + /* + * Got a NYET on the last transaction of the transfer. It + * means that the endpoint should be in the PING state at the + * beginning of the next transfer. + */ + qh->ping_state = 1; + clear_hc_int(hc_regs, nyet); + } + + } + +} + +/** + * This function is called from interrupt handlers. + * Scans the descriptor list, updates URB's status and + * calls completion routine for the URB if it's done. + * Releases the channel to be used by other transfers. + * In case of Isochronous endpoint the channel is not halted until + * the end of the session, i.e. QTD list is empty. + * If periodic channel released the FrameList is updated accordingly. + * + * Calls transaction selection routines to activate pending transfers. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param hc Host channel, the transfer is completed on. + * @param hc_regs Host channel registers. + * @param halt_status Reason the channel is being halted, + * or just XferComplete for isochronous transfer + */ +void dwc_otg_hcd_complete_xfer_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_halt_status_e halt_status) +{ + uint8_t continue_isoc_xfer = 0; + dwc_otg_transaction_type_e tr_type; + dwc_otg_qh_t *qh = hc->qh; + + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + + complete_isoc_xfer_ddma(hcd, hc, hc_regs, halt_status); + + /* Release the channel if halted or session completed */ + if (halt_status != DWC_OTG_HC_XFER_COMPLETE || + DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + + /* Halt the channel if session completed */ + if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { + dwc_otg_hc_halt(hcd->core_if, hc, halt_status); + } + + release_channel_ddma(hcd, qh); + dwc_otg_hcd_qh_remove(hcd, qh); + } else { + /* Keep in assigned schedule to continue transfer */ + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned, + &qh->qh_list_entry); + continue_isoc_xfer = 1; + + } + /** @todo Consider the case when period exceeds FrameList size. + * Frame Rollover interrupt should be used. + */ + } else { + /* Scan descriptor list to complete the URB(s), then release the channel */ + complete_non_isoc_xfer_ddma(hcd, hc, hc_regs, halt_status); + + release_channel_ddma(hcd, qh); + dwc_otg_hcd_qh_remove(hcd, qh); + + if (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + /* Add back to inactive non-periodic schedule on normal completion */ + dwc_otg_hcd_qh_add(hcd, qh); + } + + } + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE || continue_isoc_xfer) { + if (continue_isoc_xfer) { + if (tr_type == DWC_OTG_TRANSACTION_NONE) { + tr_type = DWC_OTG_TRANSACTION_PERIODIC; + } else if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC) { + tr_type = DWC_OTG_TRANSACTION_ALL; + } + } + dwc_otg_hcd_queue_transactions(hcd, tr_type); + } +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_if.h b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_if.h new file mode 100644 index 00000000..b3dc806a --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_if.h @@ -0,0 +1,412 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_if.h $ + * $Revision: #12 $ + * $Date: 2011/10/26 $ + * $Change: 1873028 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY +#ifndef __DWC_HCD_IF_H__ +#define __DWC_HCD_IF_H__ + +#include "dwc_otg_core_if.h" + +/** @file + * This file defines DWC_OTG HCD Core API. + */ + +struct dwc_otg_hcd; +typedef struct dwc_otg_hcd dwc_otg_hcd_t; + +struct dwc_otg_hcd_urb; +typedef struct dwc_otg_hcd_urb dwc_otg_hcd_urb_t; + +/** @name HCD Function Driver Callbacks */ +/** @{ */ + +/** This function is called whenever core switches to host mode. */ +typedef int (*dwc_otg_hcd_start_cb_t) (dwc_otg_hcd_t * hcd); + +/** This function is called when device has been disconnected */ +typedef int (*dwc_otg_hcd_disconnect_cb_t) (dwc_otg_hcd_t * hcd); + +/** Wrapper provides this function to HCD to core, so it can get hub information to which device is connected */ +typedef int (*dwc_otg_hcd_hub_info_from_urb_cb_t) (dwc_otg_hcd_t * hcd, + void *urb_handle, + uint32_t * hub_addr, + uint32_t * port_addr); +/** Via this function HCD core gets device speed */ +typedef int (*dwc_otg_hcd_speed_from_urb_cb_t) (dwc_otg_hcd_t * hcd, + void *urb_handle); + +/** This function is called when urb is completed */ +typedef int (*dwc_otg_hcd_complete_urb_cb_t) (dwc_otg_hcd_t * hcd, + void *urb_handle, + dwc_otg_hcd_urb_t * dwc_otg_urb, + int32_t status); + +/** Via this function HCD core gets b_hnp_enable parameter */ +typedef int (*dwc_otg_hcd_get_b_hnp_enable) (dwc_otg_hcd_t * hcd); + +struct dwc_otg_hcd_function_ops { + dwc_otg_hcd_start_cb_t start; + dwc_otg_hcd_disconnect_cb_t disconnect; + dwc_otg_hcd_hub_info_from_urb_cb_t hub_info; + dwc_otg_hcd_speed_from_urb_cb_t speed; + dwc_otg_hcd_complete_urb_cb_t complete; + dwc_otg_hcd_get_b_hnp_enable get_b_hnp_enable; +}; +/** @} */ + +/** @name HCD Core API */ +/** @{ */ +/** This function allocates dwc_otg_hcd structure and returns pointer on it. */ +extern dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void); + +/** This function should be called to initiate HCD Core. + * + * @param hcd The HCD + * @param core_if The DWC_OTG Core + * + * Returns -DWC_E_NO_MEMORY if no enough memory. + * Returns 0 on success + */ +extern int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if); + +/** Frees HCD + * + * @param hcd The HCD + */ +extern void dwc_otg_hcd_remove(dwc_otg_hcd_t * hcd); + +/** This function should be called on every hardware interrupt. + * + * @param dwc_otg_hcd The HCD + * + * Returns non zero if interrupt is handled + * Return 0 if interrupt is not handled + */ +extern int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd); + +/** + * Returns private data set by + * dwc_otg_hcd_set_priv_data function. + * + * @param hcd The HCD + */ +extern void *dwc_otg_hcd_get_priv_data(dwc_otg_hcd_t * hcd); + +/** + * Set private data. + * + * @param hcd The HCD + * @param priv_data pointer to be stored in private data + */ +extern void dwc_otg_hcd_set_priv_data(dwc_otg_hcd_t * hcd, void *priv_data); + +/** + * This function initializes the HCD Core. + * + * @param hcd The HCD + * @param fops The Function Driver Operations data structure containing pointers to all callbacks. + * + * Returns -DWC_E_NO_DEVICE if Core is currently is in device mode. + * Returns 0 on success + */ +extern int dwc_otg_hcd_start(dwc_otg_hcd_t * hcd, + struct dwc_otg_hcd_function_ops *fops); + +/** + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + * + * @param hcd The HCD + */ +extern void dwc_otg_hcd_stop(dwc_otg_hcd_t * hcd); + +/** + * Handles hub class-specific requests. + * + * @param dwc_otg_hcd The HCD + * @param typeReq Request Type + * @param wValue wValue from control request + * @param wIndex wIndex from control request + * @param buf data buffer + * @param wLength data buffer length + * + * Returns -DWC_E_INVALID if invalid argument is passed + * Returns 0 on success + */ +extern int dwc_otg_hcd_hub_control(dwc_otg_hcd_t * dwc_otg_hcd, + uint16_t typeReq, uint16_t wValue, + uint16_t wIndex, uint8_t * buf, + uint16_t wLength); + +/** + * Returns otg port number. + * + * @param hcd The HCD + */ +extern uint32_t dwc_otg_hcd_otg_port(dwc_otg_hcd_t * hcd); + +/** + * Returns OTG version - either 1.3 or 2.0. + * + * @param core_if The core_if structure pointer + */ +extern uint16_t dwc_otg_get_otg_version(dwc_otg_core_if_t * core_if); + +/** + * Returns 1 if currently core is acting as B host, and 0 otherwise. + * + * @param hcd The HCD + */ +extern uint32_t dwc_otg_hcd_is_b_host(dwc_otg_hcd_t * hcd); + +/** + * Returns current frame number. + * + * @param hcd The HCD + */ +extern int dwc_otg_hcd_get_frame_number(dwc_otg_hcd_t * hcd); + +/** + * Dumps hcd state. + * + * @param hcd The HCD + */ +extern void dwc_otg_hcd_dump_state(dwc_otg_hcd_t * hcd); + +/** + * Dump the average frame remaining at SOF. This can be used to + * determine average interrupt latency. Frame remaining is also shown for + * start transfer and two additional sample points. + * Currently this function is not implemented. + * + * @param hcd The HCD + */ +extern void dwc_otg_hcd_dump_frrem(dwc_otg_hcd_t * hcd); + +/** + * Sends LPM transaction to the local device. + * + * @param hcd The HCD + * @param devaddr Device Address + * @param hird Host initiated resume duration + * @param bRemoteWake Value of bRemoteWake field in LPM transaction + * + * Returns negative value if sending LPM transaction was not succeeded. + * Returns 0 on success. + */ +extern int dwc_otg_hcd_send_lpm(dwc_otg_hcd_t * hcd, uint8_t devaddr, + uint8_t hird, uint8_t bRemoteWake); + +/* URB interface */ + +/** + * Allocates memory for dwc_otg_hcd_urb structure. + * Allocated memory should be freed by call of DWC_FREE. + * + * @param hcd The HCD + * @param iso_desc_count Count of ISOC descriptors + * @param atomic_alloc Specefies whether to perform atomic allocation. + */ +extern dwc_otg_hcd_urb_t *dwc_otg_hcd_urb_alloc(dwc_otg_hcd_t * hcd, + int iso_desc_count, + int atomic_alloc); + +/** + * Set pipe information in URB. + * + * @param hcd_urb DWC_OTG URB + * @param devaddr Device Address + * @param ep_num Endpoint Number + * @param ep_type Endpoint Type + * @param ep_dir Endpoint Direction + * @param mps Max Packet Size + */ +extern void dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_hcd_urb_t * hcd_urb, + uint8_t devaddr, uint8_t ep_num, + uint8_t ep_type, uint8_t ep_dir, + uint16_t mps); + +/* Transfer flags */ +#define URB_GIVEBACK_ASAP 0x1 +#define URB_SEND_ZERO_PACKET 0x2 + +/** + * Sets dwc_otg_hcd_urb parameters. + * + * @param urb DWC_OTG URB allocated by dwc_otg_hcd_urb_alloc function. + * @param urb_handle Unique handle for request, this will be passed back + * to function driver in completion callback. + * @param buf The buffer for the data + * @param dma The DMA buffer for the data + * @param buflen Transfer length + * @param sp Buffer for setup data + * @param sp_dma DMA address of setup data buffer + * @param flags Transfer flags + * @param interval Polling interval for interrupt or isochronous transfers. + */ +extern void dwc_otg_hcd_urb_set_params(dwc_otg_hcd_urb_t * urb, + void *urb_handle, void *buf, + dwc_dma_t dma, uint32_t buflen, void *sp, + dwc_dma_t sp_dma, uint32_t flags, + uint16_t interval); + +/** Gets status from dwc_otg_hcd_urb + * + * @param dwc_otg_urb DWC_OTG URB + */ +extern uint32_t dwc_otg_hcd_urb_get_status(dwc_otg_hcd_urb_t * dwc_otg_urb); + +/** Gets actual length from dwc_otg_hcd_urb + * + * @param dwc_otg_urb DWC_OTG URB + */ +extern uint32_t dwc_otg_hcd_urb_get_actual_length(dwc_otg_hcd_urb_t * + dwc_otg_urb); + +/** Gets error count from dwc_otg_hcd_urb. Only for ISOC URBs + * + * @param dwc_otg_urb DWC_OTG URB + */ +extern uint32_t dwc_otg_hcd_urb_get_error_count(dwc_otg_hcd_urb_t * + dwc_otg_urb); + +/** Set ISOC descriptor offset and length + * + * @param dwc_otg_urb DWC_OTG URB + * @param desc_num ISOC descriptor number + * @param offset Offset from beginig of buffer. + * @param length Transaction length + */ +extern void dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_hcd_urb_t * dwc_otg_urb, + int desc_num, uint32_t offset, + uint32_t length); + +/** Get status of ISOC descriptor, specified by desc_num + * + * @param dwc_otg_urb DWC_OTG URB + * @param desc_num ISOC descriptor number + */ +extern uint32_t dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_hcd_urb_t * + dwc_otg_urb, int desc_num); + +/** Get actual length of ISOC descriptor, specified by desc_num + * + * @param dwc_otg_urb DWC_OTG URB + * @param desc_num ISOC descriptor number + */ +extern uint32_t dwc_otg_hcd_urb_get_iso_desc_actual_length(dwc_otg_hcd_urb_t * + dwc_otg_urb, + int desc_num); + +/** Queue URB. After transfer is completes, the complete callback will be called with the URB status + * + * @param dwc_otg_hcd The HCD + * @param dwc_otg_urb DWC_OTG URB + * @param ep_handle Out parameter for returning endpoint handle + * @param atomic_alloc Flag to do atomic allocation if needed + * + * Returns -DWC_E_NO_DEVICE if no device is connected. + * Returns -DWC_E_NO_MEMORY if there is no enough memory. + * Returns 0 on success. + */ +extern int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * dwc_otg_hcd, + dwc_otg_hcd_urb_t * dwc_otg_urb, + void **ep_handle, int atomic_alloc); + +/** De-queue the specified URB + * + * @param dwc_otg_hcd The HCD + * @param dwc_otg_urb DWC_OTG URB + */ +extern int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * dwc_otg_hcd, + dwc_otg_hcd_urb_t * dwc_otg_urb); + +/** Frees resources in the DWC_otg controller related to a given endpoint. + * Any URBs for the endpoint must already be dequeued. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle, returned by dwc_otg_hcd_urb_enqueue function + * @param retry Number of retries if there are queued transfers. + * + * Returns -DWC_E_INVALID if invalid arguments are passed. + * Returns 0 on success + */ +extern int dwc_otg_hcd_endpoint_disable(dwc_otg_hcd_t * hcd, void *ep_handle, + int retry); + +/* Resets the data toggle in qh structure. This function can be called from + * usb_clear_halt routine. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle, returned by dwc_otg_hcd_urb_enqueue function + * + * Returns -DWC_E_INVALID if invalid arguments are passed. + * Returns 0 on success + */ +extern int dwc_otg_hcd_endpoint_reset(dwc_otg_hcd_t * hcd, void *ep_handle); + +/** Returns 1 if status of specified port is changed and 0 otherwise. + * + * @param hcd The HCD + * @param port Port number + */ +extern int dwc_otg_hcd_is_status_changed(dwc_otg_hcd_t * hcd, int port); + +/** Call this function to check if bandwidth was allocated for specified endpoint. + * Only for ISOC and INTERRUPT endpoints. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle + */ +extern int dwc_otg_hcd_is_bandwidth_allocated(dwc_otg_hcd_t * hcd, + void *ep_handle); + +/** Call this function to check if bandwidth was freed for specified endpoint. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle + */ +extern int dwc_otg_hcd_is_bandwidth_freed(dwc_otg_hcd_t * hcd, void *ep_handle); + +/** Returns bandwidth allocated for specified endpoint in microseconds. + * Only for ISOC and INTERRUPT endpoints. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle + */ +extern uint8_t dwc_otg_hcd_get_ep_bandwidth(dwc_otg_hcd_t * hcd, + void *ep_handle); + +/** @} */ + +#endif /* __DWC_HCD_IF_H__ */ +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_intr.c b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_intr.c new file mode 100644 index 00000000..c1e6a78c --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_intr.c @@ -0,0 +1,2089 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_intr.c $ + * $Revision: #89 $ + * $Date: 2011/10/20 $ + * $Change: 1869487 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +#include "usb.h" + +#include "dwc_otg_driver.h" +#include "dwc_otg_hcd.h" +#include "dwc_otg_regs.h" + +/** @file + * This file contains the implementation of the HCD Interrupt handlers. + */ + +/** This function handles interrupts for the HCD. */ +int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + int retval = 0; + + dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; + gintsts_data_t gintsts; +#ifdef DEBUG + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; +#endif + + /* Exit from ISR if core is hibernated */ + if (core_if->hibernation_suspend == 1) { + return retval; + } + DWC_SPINLOCK(dwc_otg_hcd->lock); + /* Check if HOST Mode */ + if (dwc_otg_is_host_mode(core_if)) { + gintsts.d32 = dwc_otg_read_core_intr(core_if); + if (!gintsts.d32) { + DWC_SPINUNLOCK(dwc_otg_hcd->lock); + return 0; + } +#ifdef DEBUG + /* Don't print debug message in the interrupt handler on SOF */ +#ifndef DEBUG_SOF + if (gintsts.d32 != DWC_SOF_INTR_MASK) +#endif + DWC_DEBUGPL(DBG_HCD, "\n"); +#endif + +#ifdef DEBUG +#ifndef DEBUG_SOF + if (gintsts.d32 != DWC_SOF_INTR_MASK) +#endif + DWC_DEBUGPL(DBG_HCD, + "DWC OTG HCD Interrupt Detected gintsts&gintmsk=0x%08x\n", + gintsts.d32); +#endif + + if (gintsts.b.sofintr) { + retval |= dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd); + } + if (gintsts.b.rxstsqlvl) { + retval |= + dwc_otg_hcd_handle_rx_status_q_level_intr + (dwc_otg_hcd); + } + if (gintsts.b.nptxfempty) { + retval |= + dwc_otg_hcd_handle_np_tx_fifo_empty_intr + (dwc_otg_hcd); + } + if (gintsts.b.i2cintr) { + /** @todo Implement i2cintr handler. */ + } + if (gintsts.b.portintr) { + retval |= dwc_otg_hcd_handle_port_intr(dwc_otg_hcd); + } + if (gintsts.b.hcintr) { + retval |= dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd); + } + if (gintsts.b.ptxfempty) { + retval |= + dwc_otg_hcd_handle_perio_tx_fifo_empty_intr + (dwc_otg_hcd); + } +#ifdef DEBUG +#ifndef DEBUG_SOF + if (gintsts.d32 != DWC_SOF_INTR_MASK) +#endif + { + DWC_DEBUGPL(DBG_HCD, + "DWC OTG HCD Finished Servicing Interrupts\n"); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD gintsts=0x%08x\n", + DWC_READ_REG32(&global_regs->gintsts)); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD gintmsk=0x%08x\n", + DWC_READ_REG32(&global_regs->gintmsk)); + } +#endif + +#ifdef DEBUG +#ifndef DEBUG_SOF + if (gintsts.d32 != DWC_SOF_INTR_MASK) +#endif + DWC_DEBUGPL(DBG_HCD, "\n"); +#endif + + } + DWC_SPINUNLOCK(dwc_otg_hcd->lock); + return retval; +} + +#ifdef DWC_TRACK_MISSED_SOFS +#warning Compiling code to track missed SOFs +#define FRAME_NUM_ARRAY_SIZE 1000 +/** + * This function is for debug only. + */ +static inline void track_missed_sofs(uint16_t curr_frame_number) +{ + static uint16_t frame_num_array[FRAME_NUM_ARRAY_SIZE]; + static uint16_t last_frame_num_array[FRAME_NUM_ARRAY_SIZE]; + static int frame_num_idx = 0; + static uint16_t last_frame_num = DWC_HFNUM_MAX_FRNUM; + static int dumped_frame_num_array = 0; + + if (frame_num_idx < FRAME_NUM_ARRAY_SIZE) { + if (((last_frame_num + 1) & DWC_HFNUM_MAX_FRNUM) != + curr_frame_number) { + frame_num_array[frame_num_idx] = curr_frame_number; + last_frame_num_array[frame_num_idx++] = last_frame_num; + } + } else if (!dumped_frame_num_array) { + int i; + DWC_PRINTF("Frame Last Frame\n"); + DWC_PRINTF("----- ----------\n"); + for (i = 0; i < FRAME_NUM_ARRAY_SIZE; i++) { + DWC_PRINTF("0x%04x 0x%04x\n", + frame_num_array[i], last_frame_num_array[i]); + } + dumped_frame_num_array = 1; + } + last_frame_num = curr_frame_number; +} +#endif + +/** + * Handles the start-of-frame interrupt in host mode. Non-periodic + * transactions may be queued to the DWC_otg controller for the current + * (micro)frame. Periodic transactions may be queued to the controller for the + * next (micro)frame. + */ +int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * hcd) +{ + hfnum_data_t hfnum; + dwc_list_link_t *qh_entry; + dwc_otg_qh_t *qh; + dwc_otg_transaction_type_e tr_type; + gintsts_data_t gintsts = {.d32 = 0 }; + + hfnum.d32 = + DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum); + +#ifdef DEBUG_SOF + DWC_DEBUGPL(DBG_HCD, "--Start of Frame Interrupt--\n"); +#endif + hcd->frame_number = hfnum.b.frnum; + +#ifdef DEBUG + hcd->frrem_accum += hfnum.b.frrem; + hcd->frrem_samples++; +#endif + +#ifdef DWC_TRACK_MISSED_SOFS + track_missed_sofs(hcd->frame_number); +#endif + /* Determine whether any periodic QHs should be executed. */ + qh_entry = DWC_LIST_FIRST(&hcd->periodic_sched_inactive); + while (qh_entry != &hcd->periodic_sched_inactive) { + qh = DWC_LIST_ENTRY(qh_entry, dwc_otg_qh_t, qh_list_entry); + qh_entry = qh_entry->next; + if (dwc_frame_num_le(qh->sched_frame, hcd->frame_number)) { + /* + * Move QH to the ready list to be executed next + * (micro)frame. + */ + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_ready, + &qh->qh_list_entry); + } + } + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE) { + dwc_otg_hcd_queue_transactions(hcd, tr_type); + } + + /* Clear interrupt */ + gintsts.b.sofintr = 1; + DWC_WRITE_REG32(&hcd->core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** Handles the Rx Status Queue Level Interrupt, which indicates that there is at + * least one packet in the Rx FIFO. The packets are moved from the FIFO to + * memory if the DWC_otg controller is operating in Slave mode. */ +int32_t dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + host_grxsts_data_t grxsts; + dwc_hc_t *hc = NULL; + + DWC_DEBUGPL(DBG_HCD, "--RxStsQ Level Interrupt--\n"); + + grxsts.d32 = + DWC_READ_REG32(&dwc_otg_hcd->core_if->core_global_regs->grxstsp); + + hc = dwc_otg_hcd->hc_ptr_array[grxsts.b.chnum]; + if (!hc) { + DWC_ERROR("Unable to get corresponding channel\n"); + return 0; + } + + /* Packet Status */ + DWC_DEBUGPL(DBG_HCDV, " Ch num = %d\n", grxsts.b.chnum); + DWC_DEBUGPL(DBG_HCDV, " Count = %d\n", grxsts.b.bcnt); + DWC_DEBUGPL(DBG_HCDV, " DPID = %d, hc.dpid = %d\n", grxsts.b.dpid, + hc->data_pid_start); + DWC_DEBUGPL(DBG_HCDV, " PStatus = %d\n", grxsts.b.pktsts); + + switch (grxsts.b.pktsts) { + case DWC_GRXSTS_PKTSTS_IN: + /* Read the data into the host buffer. */ + if (grxsts.b.bcnt > 0) { + dwc_otg_read_packet(dwc_otg_hcd->core_if, + hc->xfer_buff, grxsts.b.bcnt); + + /* Update the HC fields for the next packet received. */ + hc->xfer_count += grxsts.b.bcnt; + hc->xfer_buff += grxsts.b.bcnt; + } + + case DWC_GRXSTS_PKTSTS_IN_XFER_COMP: + case DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR: + case DWC_GRXSTS_PKTSTS_CH_HALTED: + /* Handled in interrupt, just ignore data */ + break; + default: + DWC_ERROR("RX_STS_Q Interrupt: Unknown status %d\n", + grxsts.b.pktsts); + break; + } + + return 1; +} + +/** This interrupt occurs when the non-periodic Tx FIFO is half-empty. More + * data packets may be written to the FIFO for OUT transfers. More requests + * may be written to the non-periodic request queue for IN transfers. This + * interrupt is enabled only in Slave mode. */ +int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + DWC_DEBUGPL(DBG_HCD, "--Non-Periodic TxFIFO Empty Interrupt--\n"); + dwc_otg_hcd_queue_transactions(dwc_otg_hcd, + DWC_OTG_TRANSACTION_NON_PERIODIC); + return 1; +} + +/** This interrupt occurs when the periodic Tx FIFO is half-empty. More data + * packets may be written to the FIFO for OUT transfers. More requests may be + * written to the periodic request queue for IN transfers. This interrupt is + * enabled only in Slave mode. */ +int32_t dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + DWC_DEBUGPL(DBG_HCD, "--Periodic TxFIFO Empty Interrupt--\n"); + dwc_otg_hcd_queue_transactions(dwc_otg_hcd, + DWC_OTG_TRANSACTION_PERIODIC); + return 1; +} + +/** There are multiple conditions that can cause a port interrupt. This function + * determines which interrupt conditions have occurred and handles them + * appropriately. */ +int32_t dwc_otg_hcd_handle_port_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + int retval = 0; + hprt0_data_t hprt0; + hprt0_data_t hprt0_modify; + + hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0); + hprt0_modify.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0); + + /* Clear appropriate bits in HPRT0 to clear the interrupt bit in + * GINTSTS */ + + hprt0_modify.b.prtena = 0; + hprt0_modify.b.prtconndet = 0; + hprt0_modify.b.prtenchng = 0; + hprt0_modify.b.prtovrcurrchng = 0; + + /* Port Connect Detected + * Set flag and clear if detected */ + if (dwc_otg_hcd->core_if->hibernation_suspend == 1) { + // Dont modify port status if we are in hibernation state + hprt0_modify.b.prtconndet = 1; + hprt0_modify.b.prtenchng = 1; + DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0, hprt0_modify.d32); + hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0); + return retval; + } + + if (hprt0.b.prtconndet) { + /** @todo - check if steps performed in 'else' block should be perfromed regardles adp */ + if (dwc_otg_hcd->core_if->adp_enable && + dwc_otg_hcd->core_if->adp.vbuson_timer_started == 1) { + DWC_PRINTF("PORT CONNECT DETECTED ----------------\n"); + DWC_TIMER_CANCEL(dwc_otg_hcd->core_if->adp.vbuson_timer); + dwc_otg_hcd->core_if->adp.vbuson_timer_started = 0; + /* TODO - check if this is required, as + * host initialization was already performed + * after initial ADP probing + */ + /*dwc_otg_hcd->core_if->adp.vbuson_timer_started = 0; + dwc_otg_core_init(dwc_otg_hcd->core_if); + dwc_otg_enable_global_interrupts(dwc_otg_hcd->core_if); + cil_hcd_start(dwc_otg_hcd->core_if);*/ + } else { + + DWC_DEBUGPL(DBG_HCD, "--Port Interrupt HPRT0=0x%08x " + "Port Connect Detected--\n", hprt0.d32); + dwc_otg_hcd->flags.b.port_connect_status_change = 1; + dwc_otg_hcd->flags.b.port_connect_status = 1; + hprt0_modify.b.prtconndet = 1; + + /* B-Device has connected, Delete the connection timer. */ + DWC_TIMER_CANCEL(dwc_otg_hcd->conn_timer); + } + /* The Hub driver asserts a reset when it sees port connect + * status change flag */ + retval |= 1; + } + + /* Port Enable Changed + * Clear if detected - Set internal flag if disabled */ + if (hprt0.b.prtenchng) { + DWC_DEBUGPL(DBG_HCD, " --Port Interrupt HPRT0=0x%08x " + "Port Enable Changed--\n", hprt0.d32); + hprt0_modify.b.prtenchng = 1; + if (hprt0.b.prtena == 1) { + hfir_data_t hfir; + int do_reset = 0; + dwc_otg_core_params_t *params = + dwc_otg_hcd->core_if->core_params; + dwc_otg_core_global_regs_t *global_regs = + dwc_otg_hcd->core_if->core_global_regs; + dwc_otg_host_if_t *host_if = + dwc_otg_hcd->core_if->host_if; + + /* Every time when port enables calculate + * HFIR.FrInterval + */ + hfir.d32 = DWC_READ_REG32(&host_if->host_global_regs->hfir); + hfir.b.frint = calc_frame_interval(dwc_otg_hcd->core_if); + DWC_WRITE_REG32(&host_if->host_global_regs->hfir, hfir.d32); + + /* Check if we need to adjust the PHY clock speed for + * low power and adjust it */ + if (params->host_support_fs_ls_low_power) { + gusbcfg_data_t usbcfg; + + usbcfg.d32 = + DWC_READ_REG32(&global_regs->gusbcfg); + + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED + || hprt0.b.prtspd == + DWC_HPRT0_PRTSPD_FULL_SPEED) { + /* + * Low power + */ + hcfg_data_t hcfg; + if (usbcfg.b.phylpwrclksel == 0) { + /* Set PHY low power clock select for FS/LS devices */ + usbcfg.b.phylpwrclksel = 1; + DWC_WRITE_REG32 + (&global_regs->gusbcfg, + usbcfg.d32); + do_reset = 1; + } + + hcfg.d32 = + DWC_READ_REG32 + (&host_if->host_global_regs->hcfg); + + if (hprt0.b.prtspd == + DWC_HPRT0_PRTSPD_LOW_SPEED + && params->host_ls_low_power_phy_clk + == + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) + { + /* 6 MHZ */ + DWC_DEBUGPL(DBG_CIL, + "FS_PHY programming HCFG to 6 MHz (Low Power)\n"); + if (hcfg.b.fslspclksel != + DWC_HCFG_6_MHZ) { + hcfg.b.fslspclksel = + DWC_HCFG_6_MHZ; + DWC_WRITE_REG32 + (&host_if->host_global_regs->hcfg, + hcfg.d32); + do_reset = 1; + } + } else { + /* 48 MHZ */ + DWC_DEBUGPL(DBG_CIL, + "FS_PHY programming HCFG to 48 MHz ()\n"); + if (hcfg.b.fslspclksel != + DWC_HCFG_48_MHZ) { + hcfg.b.fslspclksel = + DWC_HCFG_48_MHZ; + DWC_WRITE_REG32 + (&host_if->host_global_regs->hcfg, + hcfg.d32); + do_reset = 1; + } + } + } else { + /* + * Not low power + */ + if (usbcfg.b.phylpwrclksel == 1) { + usbcfg.b.phylpwrclksel = 0; + DWC_WRITE_REG32 + (&global_regs->gusbcfg, + usbcfg.d32); + do_reset = 1; + } + } + + if (do_reset) { + DWC_TASK_SCHEDULE(dwc_otg_hcd->reset_tasklet); + } + } + + if (!do_reset) { + /* Port has been enabled set the reset change flag */ + dwc_otg_hcd->flags.b.port_reset_change = 1; + } + } else { + dwc_otg_hcd->flags.b.port_enable_change = 1; + } + retval |= 1; + } + + /** Overcurrent Change Interrupt */ + if (hprt0.b.prtovrcurrchng) { + DWC_DEBUGPL(DBG_HCD, " --Port Interrupt HPRT0=0x%08x " + "Port Overcurrent Changed--\n", hprt0.d32); + dwc_otg_hcd->flags.b.port_over_current_change = 1; + hprt0_modify.b.prtovrcurrchng = 1; + retval |= 1; + } + + /* Clear Port Interrupts */ + DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0, hprt0_modify.d32); + + return retval; +} + +/** This interrupt indicates that one or more host channels has a pending + * interrupt. There are multiple conditions that can cause each host channel + * interrupt. This function determines which conditions have occurred for each + * host channel interrupt and handles them appropriately. */ +int32_t dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + int i; + int retval = 0; + haint_data_t haint; + + /* Clear appropriate bits in HCINTn to clear the interrupt bit in + * GINTSTS */ + + haint.d32 = dwc_otg_read_host_all_channels_intr(dwc_otg_hcd->core_if); + + for (i = 0; i < dwc_otg_hcd->core_if->core_params->host_channels; i++) { + if (haint.b2.chint & (1 << i)) { + retval |= dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd, i); + } + } + + return retval; +} + +/** + * Gets the actual length of a transfer after the transfer halts. _halt_status + * holds the reason for the halt. + * + * For IN transfers where halt_status is DWC_OTG_HC_XFER_COMPLETE, + * *short_read is set to 1 upon return if less than the requested + * number of bytes were transferred. Otherwise, *short_read is set to 0 upon + * return. short_read may also be NULL on entry, in which case it remains + * unchanged. + */ +static uint32_t get_actual_xfer_length(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status, + int *short_read) +{ + hctsiz_data_t hctsiz; + uint32_t length; + + if (short_read != NULL) { + *short_read = 0; + } + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + + if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { + if (hc->ep_is_in) { + length = hc->xfer_len - hctsiz.b.xfersize; + if (short_read != NULL) { + *short_read = (hctsiz.b.xfersize != 0); + } + } else if (hc->qh->do_split) { + length = qtd->ssplit_out_xfer_count; + } else { + length = hc->xfer_len; + } + } else { + /* + * Must use the hctsiz.pktcnt field to determine how much data + * has been transferred. This field reflects the number of + * packets that have been transferred via the USB. This is + * always an integral number of packets if the transfer was + * halted before its normal completion. (Can't use the + * hctsiz.xfersize field because that reflects the number of + * bytes transferred via the AHB, not the USB). + */ + length = + (hc->start_pkt_count - hctsiz.b.pktcnt) * hc->max_packet; + } + + return length; +} + +/** + * Updates the state of the URB after a Transfer Complete interrupt on the + * host channel. Updates the actual_length field of the URB based on the + * number of bytes transferred via the host channel. Sets the URB status + * if the data transfer is finished. + * + * @return 1 if the data transfer specified by the URB is completely finished, + * 0 otherwise. + */ +static int update_urb_state_xfer_comp(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_hcd_urb_t * urb, + dwc_otg_qtd_t * qtd) +{ + int xfer_done = 0; + int short_read = 0; + + int xfer_length; + + xfer_length = get_actual_xfer_length(hc, hc_regs, qtd, + DWC_OTG_HC_XFER_COMPLETE, + &short_read); + + + /* non DWORD-aligned buffer case handling. */ + if (hc->align_buff && xfer_length && hc->ep_is_in) { + dwc_memcpy(urb->buf + urb->actual_length, hc->qh->dw_align_buf, + xfer_length); + } + + urb->actual_length += xfer_length; + + if (xfer_length && (hc->ep_type == DWC_OTG_EP_TYPE_BULK) && + (urb->flags & URB_SEND_ZERO_PACKET) + && (urb->actual_length == urb->length) + && !(urb->length % hc->max_packet)) { + xfer_done = 0; + } else if (short_read || urb->actual_length == urb->length) { + xfer_done = 1; + urb->status = 0; + } + +#ifdef DEBUG + { + hctsiz_data_t hctsiz; + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + DWC_DEBUGPL(DBG_HCDV, "DWC_otg: %s: %s, channel %d\n", + __func__, (hc->ep_is_in ? "IN" : "OUT"), + hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " hc->xfer_len %d\n", hc->xfer_len); + DWC_DEBUGPL(DBG_HCDV, " hctsiz.xfersize %d\n", + hctsiz.b.xfersize); + DWC_DEBUGPL(DBG_HCDV, " urb->transfer_buffer_length %d\n", + urb->length); + DWC_DEBUGPL(DBG_HCDV, " urb->actual_length %d\n", + urb->actual_length); + DWC_DEBUGPL(DBG_HCDV, " short_read %d, xfer_done %d\n", + short_read, xfer_done); + } +#endif + + return xfer_done; +} + +/* + * Save the starting data toggle for the next transfer. The data toggle is + * saved in the QH for non-control transfers and it's saved in the QTD for + * control transfers. + */ +void dwc_otg_hcd_save_data_toggle(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, dwc_otg_qtd_t * qtd) +{ + hctsiz_data_t hctsiz; + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + + if (hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) { + dwc_otg_qh_t *qh = hc->qh; + if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + } else { + qh->data_toggle = DWC_OTG_HC_PID_DATA1; + } + } else { + if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { + qtd->data_toggle = DWC_OTG_HC_PID_DATA0; + } else { + qtd->data_toggle = DWC_OTG_HC_PID_DATA1; + } + } +} + +/** + * Updates the state of an Isochronous URB when the transfer is stopped for + * any reason. The fields of the current entry in the frame descriptor array + * are set based on the transfer state and the input _halt_status. Completes + * the Isochronous URB if all the URB frames have been completed. + * + * @return DWC_OTG_HC_XFER_COMPLETE if there are more frames remaining to be + * transferred in the URB. Otherwise return DWC_OTG_HC_XFER_URB_COMPLETE. + */ +static dwc_otg_halt_status_e +update_isoc_urb_state(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd, dwc_otg_halt_status_e halt_status) +{ + dwc_otg_hcd_urb_t *urb = qtd->urb; + dwc_otg_halt_status_e ret_val = halt_status; + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + + frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; + switch (halt_status) { + case DWC_OTG_HC_XFER_COMPLETE: + frame_desc->status = 0; + frame_desc->actual_length = + get_actual_xfer_length(hc, hc_regs, qtd, halt_status, NULL); + + /* non DWORD-aligned buffer case handling. */ + if (hc->align_buff && frame_desc->actual_length && hc->ep_is_in) { + dwc_memcpy(urb->buf + frame_desc->offset + qtd->isoc_split_offset, + hc->qh->dw_align_buf, frame_desc->actual_length); + } + + break; + case DWC_OTG_HC_XFER_FRAME_OVERRUN: + urb->error_count++; + if (hc->ep_is_in) { + frame_desc->status = -DWC_E_NO_STREAM_RES; + } else { + frame_desc->status = -DWC_E_COMMUNICATION; + } + frame_desc->actual_length = 0; + break; + case DWC_OTG_HC_XFER_BABBLE_ERR: + urb->error_count++; + frame_desc->status = -DWC_E_OVERFLOW; + /* Don't need to update actual_length in this case. */ + break; + case DWC_OTG_HC_XFER_XACT_ERR: + urb->error_count++; + frame_desc->status = -DWC_E_PROTOCOL; + frame_desc->actual_length = + get_actual_xfer_length(hc, hc_regs, qtd, halt_status, NULL); + + /* non DWORD-aligned buffer case handling. */ + if (hc->align_buff && frame_desc->actual_length && hc->ep_is_in) { + dwc_memcpy(urb->buf + frame_desc->offset + qtd->isoc_split_offset, + hc->qh->dw_align_buf, frame_desc->actual_length); + } + /* Skip whole frame */ + if (hc->qh->do_split && (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) && + hc->ep_is_in && hcd->core_if->dma_enable) { + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + } + + break; + default: + DWC_ASSERT(1, "Unhandled _halt_status (%d)\n", halt_status); + break; + } + if (++qtd->isoc_frame_index == urb->packet_count) { + /* + * urb->status is not used for isoc transfers. + * The individual frame_desc statuses are used instead. + */ + hcd->fops->complete(hcd, urb->priv, urb, 0); + ret_val = DWC_OTG_HC_XFER_URB_COMPLETE; + } else { + ret_val = DWC_OTG_HC_XFER_COMPLETE; + } + return ret_val; +} + +/** + * Frees the first QTD in the QH's list if free_qtd is 1. For non-periodic + * QHs, removes the QH from the active non-periodic schedule. If any QTDs are + * still linked to the QH, the QH is added to the end of the inactive + * non-periodic schedule. For periodic QHs, removes the QH from the periodic + * schedule if no more QTDs are linked to the QH. + */ +static void deactivate_qh(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, int free_qtd) +{ + int continue_split = 0; + dwc_otg_qtd_t *qtd; + + DWC_DEBUGPL(DBG_HCDV, " %s(%p,%p,%d)\n", __func__, hcd, qh, free_qtd); + + qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + + if (qtd->complete_split) { + continue_split = 1; + } else if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID || + qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END) { + continue_split = 1; + } + + if (free_qtd) { + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + continue_split = 0; + } + + qh->channel = NULL; + dwc_otg_hcd_qh_deactivate(hcd, qh, continue_split); +} + +/** + * Releases a host channel for use by other transfers. Attempts to select and + * queue more transactions since at least one host channel is available. + * + * @param hcd The HCD state structure. + * @param hc The host channel to release. + * @param qtd The QTD associated with the host channel. This QTD may be freed + * if the transfer is complete or an error has occurred. + * @param halt_status Reason the channel is being released. This status + * determines the actions taken by this function. + */ +static void release_channel(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status) +{ + dwc_otg_transaction_type_e tr_type; + int free_qtd; + + DWC_DEBUGPL(DBG_HCDV, " %s: channel %d, halt_status %d\n", + __func__, hc->hc_num, halt_status); + + switch (halt_status) { + case DWC_OTG_HC_XFER_URB_COMPLETE: + free_qtd = 1; + break; + case DWC_OTG_HC_XFER_AHB_ERR: + case DWC_OTG_HC_XFER_STALL: + case DWC_OTG_HC_XFER_BABBLE_ERR: + free_qtd = 1; + break; + case DWC_OTG_HC_XFER_XACT_ERR: + if (qtd->error_count >= 3) { + DWC_DEBUGPL(DBG_HCDV, + " Complete URB with transaction error\n"); + free_qtd = 1; + qtd->urb->status = -DWC_E_PROTOCOL; + hcd->fops->complete(hcd, qtd->urb->priv, + qtd->urb, -DWC_E_PROTOCOL); + } else { + free_qtd = 0; + } + break; + case DWC_OTG_HC_XFER_URB_DEQUEUE: + /* + * The QTD has already been removed and the QH has been + * deactivated. Don't want to do anything except release the + * host channel and try to queue more transfers. + */ + goto cleanup; + case DWC_OTG_HC_XFER_NO_HALT_STATUS: + free_qtd = 0; + break; + case DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE: + DWC_DEBUGPL(DBG_HCDV, + " Complete URB with I/O error\n"); + free_qtd = 1; + qtd->urb->status = -DWC_E_IO; + hcd->fops->complete(hcd, qtd->urb->priv, + qtd->urb, -DWC_E_IO); + break; + default: + free_qtd = 0; + break; + } + + deactivate_qh(hcd, hc->qh, free_qtd); + +cleanup: + /* + * Release the host channel for use by other transfers. The cleanup + * function clears the channel interrupt enables and conditions, so + * there's no need to clear the Channel Halted interrupt separately. + */ + dwc_otg_hc_cleanup(hcd->core_if, hc); + DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry); + + switch (hc->ep_type) { + case DWC_OTG_EP_TYPE_CONTROL: + case DWC_OTG_EP_TYPE_BULK: + hcd->non_periodic_channels--; + break; + + default: + /* + * Don't release reservations for periodic channels here. + * That's done when a periodic transfer is descheduled (i.e. + * when the QH is removed from the periodic schedule). + */ + break; + } + + /* Try to queue more transfers now that there's a free channel. */ + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE) { + dwc_otg_hcd_queue_transactions(hcd, tr_type); + } +} + +/** + * Halts a host channel. If the channel cannot be halted immediately because + * the request queue is full, this function ensures that the FIFO empty + * interrupt for the appropriate queue is enabled so that the halt request can + * be queued when there is space in the request queue. + * + * This function may also be called in DMA mode. In that case, the channel is + * simply released since the core always halts the channel automatically in + * DMA mode. + */ +static void halt_channel(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_qtd_t * qtd, dwc_otg_halt_status_e halt_status) +{ + if (hcd->core_if->dma_enable) { + release_channel(hcd, hc, qtd, halt_status); + return; + } + + /* Slave mode processing... */ + dwc_otg_hc_halt(hcd->core_if, hc, halt_status); + + if (hc->halt_on_queue) { + gintmsk_data_t gintmsk = {.d32 = 0 }; + dwc_otg_core_global_regs_t *global_regs; + global_regs = hcd->core_if->core_global_regs; + + if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || + hc->ep_type == DWC_OTG_EP_TYPE_BULK) { + /* + * Make sure the Non-periodic Tx FIFO empty interrupt + * is enabled so that the non-periodic schedule will + * be processed. + */ + gintmsk.b.nptxfempty = 1; + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32); + } else { + /* + * Move the QH from the periodic queued schedule to + * the periodic assigned schedule. This allows the + * halt to be queued when the periodic schedule is + * processed. + */ + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned, + &hc->qh->qh_list_entry); + + /* + * Make sure the Periodic Tx FIFO Empty interrupt is + * enabled so that the periodic schedule will be + * processed. + */ + gintmsk.b.ptxfempty = 1; + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32); + } + } +} + +/** + * Performs common cleanup for non-periodic transfers after a Transfer + * Complete interrupt. This function should be called after any endpoint type + * specific handling is finished to release the host channel. + */ +static void complete_non_periodic_xfer(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status) +{ + hcint_data_t hcint; + + qtd->error_count = 0; + + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + if (hcint.b.nyet) { + /* + * Got a NYET on the last transaction of the transfer. This + * means that the endpoint should be in the PING state at the + * beginning of the next transfer. + */ + hc->qh->ping_state = 1; + clear_hc_int(hc_regs, nyet); + } + + /* + * Always halt and release the host channel to make it available for + * more transfers. There may still be more phases for a control + * transfer or more data packets for a bulk transfer at this point, + * but the host channel is still halted. A channel will be reassigned + * to the transfer when the non-periodic schedule is processed after + * the channel is released. This allows transactions to be queued + * properly via dwc_otg_hcd_queue_transactions, which also enables the + * Tx FIFO Empty interrupt if necessary. + */ + if (hc->ep_is_in) { + /* + * IN transfers in Slave mode require an explicit disable to + * halt the channel. (In DMA mode, this call simply releases + * the channel.) + */ + halt_channel(hcd, hc, qtd, halt_status); + } else { + /* + * The channel is automatically disabled by the core for OUT + * transfers in Slave mode. + */ + release_channel(hcd, hc, qtd, halt_status); + } +} + +/** + * Performs common cleanup for periodic transfers after a Transfer Complete + * interrupt. This function should be called after any endpoint type specific + * handling is finished to release the host channel. + */ +static void complete_periodic_xfer(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status) +{ + hctsiz_data_t hctsiz; + qtd->error_count = 0; + + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + if (!hc->ep_is_in || hctsiz.b.pktcnt == 0) { + /* Core halts channel in these cases. */ + release_channel(hcd, hc, qtd, halt_status); + } else { + /* Flush any outstanding requests from the Tx queue. */ + halt_channel(hcd, hc, qtd, halt_status); + } +} + +static int32_t handle_xfercomp_isoc_split_in(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + uint32_t len; + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + + len = get_actual_xfer_length(hc, hc_regs, qtd, + DWC_OTG_HC_XFER_COMPLETE, NULL); + + if (!len) { + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + return 0; + } + frame_desc->actual_length += len; + + if (hc->align_buff && len) + dwc_memcpy(qtd->urb->buf + frame_desc->offset + + qtd->isoc_split_offset, hc->qh->dw_align_buf, len); + qtd->isoc_split_offset += len; + + if (frame_desc->length == frame_desc->actual_length) { + frame_desc->status = 0; + qtd->isoc_frame_index++; + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + } + + if (qtd->isoc_frame_index == qtd->urb->packet_count) { + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + + return 1; /* Indicates that channel released */ +} + +/** + * Handles a host channel Transfer Complete interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int32_t handle_hc_xfercomp_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + int urb_xfer_done; + dwc_otg_halt_status_e halt_status = DWC_OTG_HC_XFER_COMPLETE; + dwc_otg_hcd_urb_t *urb = qtd->urb; + int pipe_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info); + + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "Transfer Complete--\n", hc->hc_num); + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, halt_status); + if (pipe_type == UE_ISOCHRONOUS) { + /* Do not disable the interrupt, just clear it */ + clear_hc_int(hc_regs, xfercomp); + return 1; + } + goto handle_xfercomp_done; + } + + /* + * Handle xfer complete on CSPLIT. + */ + + if (hc->qh->do_split) { + if ((hc->ep_type == DWC_OTG_EP_TYPE_ISOC) && hc->ep_is_in + && hcd->core_if->dma_enable) { + if (qtd->complete_split + && handle_xfercomp_isoc_split_in(hcd, hc, hc_regs, + qtd)) + goto handle_xfercomp_done; + } else { + qtd->complete_split = 0; + } + } + + /* Update the QTD and URB states. */ + switch (pipe_type) { + case UE_CONTROL: + switch (qtd->control_phase) { + case DWC_OTG_CONTROL_SETUP: + if (urb->length > 0) { + qtd->control_phase = DWC_OTG_CONTROL_DATA; + } else { + qtd->control_phase = DWC_OTG_CONTROL_STATUS; + } + DWC_DEBUGPL(DBG_HCDV, + " Control setup transaction done\n"); + halt_status = DWC_OTG_HC_XFER_COMPLETE; + break; + case DWC_OTG_CONTROL_DATA:{ + urb_xfer_done = + update_urb_state_xfer_comp(hc, hc_regs, urb, + qtd); + if (urb_xfer_done) { + qtd->control_phase = + DWC_OTG_CONTROL_STATUS; + DWC_DEBUGPL(DBG_HCDV, + " Control data transfer done\n"); + } else { + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + } + halt_status = DWC_OTG_HC_XFER_COMPLETE; + break; + } + case DWC_OTG_CONTROL_STATUS: + DWC_DEBUGPL(DBG_HCDV, " Control transfer complete\n"); + if (urb->status == -DWC_E_IN_PROGRESS) { + urb->status = 0; + } + hcd->fops->complete(hcd, urb->priv, urb, urb->status); + halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; + break; + } + + complete_non_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); + break; + case UE_BULK: + DWC_DEBUGPL(DBG_HCDV, " Bulk transfer complete\n"); + urb_xfer_done = + update_urb_state_xfer_comp(hc, hc_regs, urb, qtd); + if (urb_xfer_done) { + hcd->fops->complete(hcd, urb->priv, urb, urb->status); + halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; + } else { + halt_status = DWC_OTG_HC_XFER_COMPLETE; + } + + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + complete_non_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); + break; + case UE_INTERRUPT: + DWC_DEBUGPL(DBG_HCDV, " Interrupt transfer complete\n"); + urb_xfer_done = + update_urb_state_xfer_comp(hc, hc_regs, urb, qtd); + + /* + * Interrupt URB is done on the first transfer complete + * interrupt. + */ + if (urb_xfer_done) { + hcd->fops->complete(hcd, urb->priv, urb, urb->status); + halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; + } else { + halt_status = DWC_OTG_HC_XFER_COMPLETE; + } + + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + complete_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); + break; + case UE_ISOCHRONOUS: + DWC_DEBUGPL(DBG_HCDV, " Isochronous transfer complete\n"); + if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_ALL) { + halt_status = + update_isoc_urb_state(hcd, hc, hc_regs, qtd, + DWC_OTG_HC_XFER_COMPLETE); + } + complete_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); + break; + } + +handle_xfercomp_done: + disable_hc_int(hc_regs, xfercompl); + + return 1; +} + +/** + * Handles a host channel STALL interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static int32_t handle_hc_stall_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + dwc_otg_hcd_urb_t *urb = qtd->urb; + int pipe_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info); + + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "STALL Received--\n", hc->hc_num); + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, DWC_OTG_HC_XFER_STALL); + goto handle_stall_done; + } + + if (pipe_type == UE_CONTROL) { + hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_PIPE); + } + + if (pipe_type == UE_BULK || pipe_type == UE_INTERRUPT) { + hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_PIPE); + /* + * USB protocol requires resetting the data toggle for bulk + * and interrupt endpoints when a CLEAR_FEATURE(ENDPOINT_HALT) + * setup command is issued to the endpoint. Anticipate the + * CLEAR_FEATURE command since a STALL has occurred and reset + * the data toggle now. + */ + hc->qh->data_toggle = 0; + } + + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_STALL); + +handle_stall_done: + disable_hc_int(hc_regs, stall); + + return 1; +} + +/* + * Updates the state of the URB when a transfer has been stopped due to an + * abnormal condition before the transfer completes. Modifies the + * actual_length field of the URB to reflect the number of bytes that have + * actually been transferred via the host channel. + */ +static void update_urb_state_xfer_intr(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_hcd_urb_t * urb, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status) +{ + uint32_t bytes_transferred = get_actual_xfer_length(hc, hc_regs, qtd, + halt_status, NULL); + /* non DWORD-aligned buffer case handling. */ + if (hc->align_buff && bytes_transferred && hc->ep_is_in) { + dwc_memcpy(urb->buf + urb->actual_length, hc->qh->dw_align_buf, + bytes_transferred); + } + + urb->actual_length += bytes_transferred; + +#ifdef DEBUG + { + hctsiz_data_t hctsiz; + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + DWC_DEBUGPL(DBG_HCDV, "DWC_otg: %s: %s, channel %d\n", + __func__, (hc->ep_is_in ? "IN" : "OUT"), + hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " hc->start_pkt_count %d\n", + hc->start_pkt_count); + DWC_DEBUGPL(DBG_HCDV, " hctsiz.pktcnt %d\n", hctsiz.b.pktcnt); + DWC_DEBUGPL(DBG_HCDV, " hc->max_packet %d\n", hc->max_packet); + DWC_DEBUGPL(DBG_HCDV, " bytes_transferred %d\n", + bytes_transferred); + DWC_DEBUGPL(DBG_HCDV, " urb->actual_length %d\n", + urb->actual_length); + DWC_DEBUGPL(DBG_HCDV, " urb->transfer_buffer_length %d\n", + urb->length); + } +#endif +} + +/** + * Handles a host channel NAK interrupt. This handler may be called in either + * DMA mode or Slave mode. + */ +static int32_t handle_hc_nak_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "NAK Received--\n", hc->hc_num); + + /* + * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and + * interrupt. Re-start the SSPLIT transfer. + */ + if (hc->do_split) { + if (hc->complete_split) { + qtd->error_count = 0; + } + qtd->complete_split = 0; + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); + goto handle_nak_done; + } + + switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case UE_CONTROL: + case UE_BULK: + if (hcd->core_if->dma_enable && hc->ep_is_in) { + /* + * NAK interrupts are enabled on bulk/control IN + * transfers in DMA mode for the sole purpose of + * resetting the error count after a transaction error + * occurs. The core will continue transferring data. + */ + qtd->error_count = 0; + goto handle_nak_done; + } + + /* + * NAK interrupts normally occur during OUT transfers in DMA + * or Slave mode. For IN transfers, more requests will be + * queued as request queue space is available. + */ + qtd->error_count = 0; + + if (!hc->qh->ping_state) { + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, + DWC_OTG_HC_XFER_NAK); + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + + if (hc->speed == DWC_OTG_EP_SPEED_HIGH) + hc->qh->ping_state = 1; + } + + /* + * Halt the channel so the transfer can be re-started from + * the appropriate point or the PING protocol will + * start/continue. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); + break; + case UE_INTERRUPT: + qtd->error_count = 0; + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); + break; + case UE_ISOCHRONOUS: + /* Should never get called for isochronous transfers. */ + DWC_ASSERT(1, "NACK interrupt for ISOC transfer\n"); + break; + } + +handle_nak_done: + disable_hc_int(hc_regs, nak); + + return 1; +} + +/** + * Handles a host channel ACK interrupt. This interrupt is enabled when + * performing the PING protocol in Slave mode, when errors occur during + * either Slave mode or DMA mode, and during Start Split transactions. + */ +static int32_t handle_hc_ack_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "ACK Received--\n", hc->hc_num); + + if (hc->do_split) { + /* + * Handle ACK on SSPLIT. + * ACK should not occur in CSPLIT. + */ + if (!hc->ep_is_in && hc->data_pid_start != DWC_OTG_HC_PID_SETUP) { + qtd->ssplit_out_xfer_count = hc->xfer_len; + } + if (!(hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)) { + /* Don't need complete for isochronous out transfers. */ + qtd->complete_split = 1; + } + + /* ISOC OUT */ + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) { + switch (hc->xact_pos) { + case DWC_HCSPLIT_XACTPOS_ALL: + break; + case DWC_HCSPLIT_XACTPOS_END: + qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL; + qtd->isoc_split_offset = 0; + break; + case DWC_HCSPLIT_XACTPOS_BEGIN: + case DWC_HCSPLIT_XACTPOS_MID: + /* + * For BEGIN or MID, calculate the length for + * the next microframe to determine the correct + * SSPLIT token, either MID or END. + */ + { + struct dwc_otg_hcd_iso_packet_desc + *frame_desc; + + frame_desc = + &qtd->urb-> + iso_descs[qtd->isoc_frame_index]; + qtd->isoc_split_offset += 188; + + if ((frame_desc->length - + qtd->isoc_split_offset) <= 188) { + qtd->isoc_split_pos = + DWC_HCSPLIT_XACTPOS_END; + } else { + qtd->isoc_split_pos = + DWC_HCSPLIT_XACTPOS_MID; + } + + } + break; + } + } else { + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK); + } + } else { + qtd->error_count = 0; + + if (hc->qh->ping_state) { + hc->qh->ping_state = 0; + /* + * Halt the channel so the transfer can be re-started + * from the appropriate point. This only happens in + * Slave mode. In DMA mode, the ping_state is cleared + * when the transfer is started because the core + * automatically executes the PING, then the transfer. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK); + } + } + + /* + * If the ACK occurred when _not_ in the PING state, let the channel + * continue transferring data after clearing the error count. + */ + + disable_hc_int(hc_regs, ack); + + return 1; +} + +/** + * Handles a host channel NYET interrupt. This interrupt should only occur on + * Bulk and Control OUT endpoints and for complete split transactions. If a + * NYET occurs at the same time as a Transfer Complete interrupt, it is + * handled in the xfercomp interrupt handler, not here. This handler may be + * called in either DMA mode or Slave mode. + */ +static int32_t handle_hc_nyet_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "NYET Received--\n", hc->hc_num); + + /* + * NYET on CSPLIT + * re-do the CSPLIT immediately on non-periodic + */ + if (hc->do_split && hc->complete_split) { + if (hc->ep_is_in && (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + && hcd->core_if->dma_enable) { + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + if (++qtd->isoc_frame_index == qtd->urb->packet_count) { + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } + else + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + goto handle_nyet_done; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + int frnum = dwc_otg_hcd_get_frame_number(hcd); + + if (dwc_full_frame_num(frnum) != + dwc_full_frame_num(hc->qh->sched_frame)) { + /* + * No longer in the same full speed frame. + * Treat this as a transaction error. + */ +#if 0 + /** @todo Fix system performance so this can + * be treated as an error. Right now complete + * splits cannot be scheduled precisely enough + * due to other system activity, so this error + * occurs regularly in Slave mode. + */ + qtd->error_count++; +#endif + qtd->complete_split = 0; + halt_channel(hcd, hc, qtd, + DWC_OTG_HC_XFER_XACT_ERR); + /** @todo add support for isoc release */ + goto handle_nyet_done; + } + } + + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET); + goto handle_nyet_done; + } + + hc->qh->ping_state = 1; + qtd->error_count = 0; + + update_urb_state_xfer_intr(hc, hc_regs, qtd->urb, qtd, + DWC_OTG_HC_XFER_NYET); + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + + /* + * Halt the channel and re-start the transfer so the PING + * protocol will start. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET); + +handle_nyet_done: + disable_hc_int(hc_regs, nyet); + return 1; +} + +/** + * Handles a host channel babble interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static int32_t handle_hc_babble_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "Babble Error--\n", hc->hc_num); + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, + DWC_OTG_HC_XFER_BABBLE_ERR); + goto handle_babble_done; + } + + if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + hcd->fops->complete(hcd, qtd->urb->priv, + qtd->urb, -DWC_E_OVERFLOW); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_BABBLE_ERR); + } else { + dwc_otg_halt_status_e halt_status; + halt_status = update_isoc_urb_state(hcd, hc, hc_regs, qtd, + DWC_OTG_HC_XFER_BABBLE_ERR); + halt_channel(hcd, hc, qtd, halt_status); + } + +handle_babble_done: + disable_hc_int(hc_regs, bblerr); + return 1; +} + +/** + * Handles a host channel AHB error interrupt. This handler is only called in + * DMA mode. + */ +static int32_t handle_hc_ahberr_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + hcchar_data_t hcchar; + hcsplt_data_t hcsplt; + hctsiz_data_t hctsiz; + uint32_t hcdma; + char *pipetype, *speed; + + dwc_otg_hcd_urb_t *urb = qtd->urb; + + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "AHB Error--\n", hc->hc_num); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt); + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + hcdma = DWC_READ_REG32(&hc_regs->hcdma); + + DWC_ERROR("AHB ERROR, Channel %d\n", hc->hc_num); + DWC_ERROR(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32, hcsplt.d32); + DWC_ERROR(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32, hcdma); + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Enqueue\n"); + DWC_ERROR(" Device address: %d\n", + dwc_otg_hcd_get_dev_addr(&urb->pipe_info)); + DWC_ERROR(" Endpoint: %d, %s\n", + dwc_otg_hcd_get_ep_num(&urb->pipe_info), + (dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT")); + + switch (dwc_otg_hcd_get_pipe_type(&urb->pipe_info)) { + case UE_CONTROL: + pipetype = "CONTROL"; + break; + case UE_BULK: + pipetype = "BULK"; + break; + case UE_INTERRUPT: + pipetype = "INTERRUPT"; + break; + case UE_ISOCHRONOUS: + pipetype = "ISOCHRONOUS"; + break; + default: + pipetype = "UNKNOWN"; + break; + } + + DWC_ERROR(" Endpoint type: %s\n", pipetype); + + switch (hc->speed) { + case DWC_OTG_EP_SPEED_HIGH: + speed = "HIGH"; + break; + case DWC_OTG_EP_SPEED_FULL: + speed = "FULL"; + break; + case DWC_OTG_EP_SPEED_LOW: + speed = "LOW"; + break; + default: + speed = "UNKNOWN"; + break; + }; + + DWC_ERROR(" Speed: %s\n", speed); + + DWC_ERROR(" Max packet size: %d\n", + dwc_otg_hcd_get_mps(&urb->pipe_info)); + DWC_ERROR(" Data buffer length: %d\n", urb->length); + DWC_ERROR(" Transfer buffer: %p, Transfer DMA: %p\n", + urb->buf, (void *)urb->dma); + DWC_ERROR(" Setup buffer: %p, Setup DMA: %p\n", + urb->setup_packet, (void *)urb->setup_dma); + DWC_ERROR(" Interval: %d\n", urb->interval); + + /* Core haltes the channel for Descriptor DMA mode */ + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, + DWC_OTG_HC_XFER_AHB_ERR); + goto handle_ahberr_done; + } + + hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_IO); + + /* + * Force a channel halt. Don't call halt_channel because that won't + * write to the HCCHARn register in DMA mode to force the halt. + */ + dwc_otg_hc_halt(hcd->core_if, hc, DWC_OTG_HC_XFER_AHB_ERR); +handle_ahberr_done: + disable_hc_int(hc_regs, ahberr); + return 1; +} + +/** + * Handles a host channel transaction error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int32_t handle_hc_xacterr_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "Transaction Error--\n", hc->hc_num); + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, + DWC_OTG_HC_XFER_XACT_ERR); + goto handle_xacterr_done; + } + + switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case UE_CONTROL: + case UE_BULK: + qtd->error_count++; + if (!hc->qh->ping_state) { + + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, + DWC_OTG_HC_XFER_XACT_ERR); + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + if (!hc->ep_is_in && hc->speed == DWC_OTG_EP_SPEED_HIGH) { + hc->qh->ping_state = 1; + } + } + + /* + * Halt the channel so the transfer can be re-started from + * the appropriate point or the PING protocol will start. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); + break; + case UE_INTERRUPT: + qtd->error_count++; + if (hc->do_split && hc->complete_split) { + qtd->complete_split = 0; + } + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); + break; + case UE_ISOCHRONOUS: + { + dwc_otg_halt_status_e halt_status; + halt_status = + update_isoc_urb_state(hcd, hc, hc_regs, qtd, + DWC_OTG_HC_XFER_XACT_ERR); + + halt_channel(hcd, hc, qtd, halt_status); + } + break; + } +handle_xacterr_done: + disable_hc_int(hc_regs, xacterr); + + return 1; +} + +/** + * Handles a host channel frame overrun interrupt. This handler may be called + * in either DMA mode or Slave mode. + */ +static int32_t handle_hc_frmovrun_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "Frame Overrun--\n", hc->hc_num); + + switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case UE_CONTROL: + case UE_BULK: + break; + case UE_INTERRUPT: + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_FRAME_OVERRUN); + break; + case UE_ISOCHRONOUS: + { + dwc_otg_halt_status_e halt_status; + halt_status = + update_isoc_urb_state(hcd, hc, hc_regs, qtd, + DWC_OTG_HC_XFER_FRAME_OVERRUN); + + halt_channel(hcd, hc, qtd, halt_status); + } + break; + } + + disable_hc_int(hc_regs, frmovrun); + + return 1; +} + +/** + * Handles a host channel data toggle error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int32_t handle_hc_datatglerr_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "Data Toggle Error--\n", hc->hc_num); + + if (hc->ep_is_in) { + qtd->error_count = 0; + } else { + DWC_ERROR("Data Toggle Error on OUT transfer," + "channel %d\n", hc->hc_num); + } + + disable_hc_int(hc_regs, datatglerr); + + return 1; +} + +#ifdef DEBUG +/** + * This function is for debug only. It checks that a valid halt status is set + * and that HCCHARn.chdis is clear. If there's a problem, corrective action is + * taken and a warning is issued. + * @return 1 if halt status is ok, 0 otherwise. + */ +static inline int halt_status_ok(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + hcsplt_data_t hcsplt; + + if (hc->halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS) { + /* + * This code is here only as a check. This condition should + * never happen. Ignore the halt if it does occur. + */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); + hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt); + DWC_WARN + ("%s: hc->halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS, " + "channel %d, hcchar 0x%08x, hctsiz 0x%08x, " + "hcint 0x%08x, hcintmsk 0x%08x, " + "hcsplt 0x%08x, qtd->complete_split %d\n", __func__, + hc->hc_num, hcchar.d32, hctsiz.d32, hcint.d32, + hcintmsk.d32, hcsplt.d32, qtd->complete_split); + + DWC_WARN("%s: no halt status, channel %d, ignoring interrupt\n", + __func__, hc->hc_num); + DWC_WARN("\n"); + clear_hc_int(hc_regs, chhltd); + return 0; + } + + /* + * This code is here only as a check. hcchar.chdis should + * never be set when the halt interrupt occurs. Halt the + * channel again if it does occur. + */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chdis) { + DWC_WARN("%s: hcchar.chdis set unexpectedly, " + "hcchar 0x%08x, trying to halt again\n", + __func__, hcchar.d32); + clear_hc_int(hc_regs, chhltd); + hc->halt_pending = 0; + halt_channel(hcd, hc, qtd, hc->halt_status); + return 0; + } + + return 1; +} +#endif + +/** + * Handles a host Channel Halted interrupt in DMA mode. This handler + * determines the reason the channel halted and proceeds accordingly. + */ +static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + int out_nak_enh = 0; + + /* For core with OUT NAK enhancement, the flow for high- + * speed CONTROL/BULK OUT is handled a little differently. + */ + if (hcd->core_if->snpsid >= OTG_CORE_REV_2_71a) { + if (hc->speed == DWC_OTG_EP_SPEED_HIGH && !hc->ep_is_in && + (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || + hc->ep_type == DWC_OTG_EP_TYPE_BULK)) { + out_nak_enh = 1; + } + } + + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE || + (hc->halt_status == DWC_OTG_HC_XFER_AHB_ERR + && !hcd->core_if->dma_desc_enable)) { + /* + * Just release the channel. A dequeue can happen on a + * transfer timeout. In the case of an AHB Error, the channel + * was forced to halt because there's no way to gracefully + * recover. + */ + if (hcd->core_if->dma_desc_enable) + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, + hc->halt_status); + else + release_channel(hcd, hc, qtd, hc->halt_status); + return; + } + + /* Read the HCINTn register to determine the cause for the halt. */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); + + if (hcint.b.xfercomp) { + /** @todo This is here because of a possible hardware bug. Spec + * says that on SPLIT-ISOC OUT transfers in DMA mode that a HALT + * interrupt w/ACK bit set should occur, but I only see the + * XFERCOMP bit, even with it masked out. This is a workaround + * for that behavior. Should fix this when hardware is fixed. + */ + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) { + handle_hc_ack_intr(hcd, hc, hc_regs, qtd); + } + handle_hc_xfercomp_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.stall) { + handle_hc_stall_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.xacterr && !hcd->core_if->dma_desc_enable) { + if (out_nak_enh) { + if (hcint.b.nyet || hcint.b.nak || hcint.b.ack) { + DWC_DEBUG("XactErr with NYET/NAK/ACK\n"); + qtd->error_count = 0; + } else { + DWC_DEBUG("XactErr without NYET/NAK/ACK\n"); + } + } + + /* + * Must handle xacterr before nak or ack. Could get a xacterr + * at the same time as either of these on a BULK/CONTROL OUT + * that started with a PING. The xacterr takes precedence. + */ + handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.xcs_xact && hcd->core_if->dma_desc_enable) { + handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.ahberr && hcd->core_if->dma_desc_enable) { + handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.bblerr) { + handle_hc_babble_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.frmovrun) { + handle_hc_frmovrun_intr(hcd, hc, hc_regs, qtd); + } else if (!out_nak_enh) { + if (hcint.b.nyet) { + /* + * Must handle nyet before nak or ack. Could get a nyet at the + * same time as either of those on a BULK/CONTROL OUT that + * started with a PING. The nyet takes precedence. + */ + handle_hc_nyet_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.nak && !hcintmsk.b.nak) { + /* + * If nak is not masked, it's because a non-split IN transfer + * is in an error state. In that case, the nak is handled by + * the nak interrupt handler, not here. Handle nak here for + * BULK/CONTROL OUT transfers, which halt on a NAK to allow + * rewinding the buffer pointer. + */ + handle_hc_nak_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.ack && !hcintmsk.b.ack) { + /* + * If ack is not masked, it's because a non-split IN transfer + * is in an error state. In that case, the ack is handled by + * the ack interrupt handler, not here. Handle ack here for + * split transfers. Start splits halt on ACK. + */ + handle_hc_ack_intr(hcd, hc, hc_regs, qtd); + } else { + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * A periodic transfer halted with no other channel + * interrupts set. Assume it was halted by the core + * because it could not be completed in its scheduled + * (micro)frame. + */ +#ifdef DEBUG + DWC_PRINTF + ("%s: Halt channel %d (assume incomplete periodic transfer)\n", + __func__, hc->hc_num); +#endif + halt_channel(hcd, hc, qtd, + DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE); + } else { + DWC_ERROR + ("%s: Channel %d, DMA Mode -- ChHltd set, but reason " + "for halting is unknown, hcint 0x%08x, intsts 0x%08x\n", + __func__, hc->hc_num, hcint.d32, + DWC_READ_REG32(&hcd-> + core_if->core_global_regs-> + gintsts)); + } + + } + } else { + DWC_PRINTF("NYET/NAK/ACK/other in non-error case, 0x%08x\n", + hcint.d32); + } +} + +/** + * Handles a host channel Channel Halted interrupt. + * + * In slave mode, this handler is called only when the driver specifically + * requests a halt. This occurs during handling other host channel interrupts + * (e.g. nak, xacterr, stall, nyet, etc.). + * + * In DMA mode, this is the interrupt that occurs when the core has finished + * processing a transfer on a channel. Other host channel interrupts (except + * ahberr) are disabled in DMA mode. + */ +static int32_t handle_hc_chhltd_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "Channel Halted--\n", hc->hc_num); + + if (hcd->core_if->dma_enable) { + handle_hc_chhltd_intr_dma(hcd, hc, hc_regs, qtd); + } else { +#ifdef DEBUG + if (!halt_status_ok(hcd, hc, hc_regs, qtd)) { + return 1; + } +#endif + release_channel(hcd, hc, qtd, hc->halt_status); + } + + return 1; +} + +/** Handles interrupt for a specific Host Channel */ +int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num) +{ + int retval = 0; + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + dwc_hc_t *hc; + dwc_otg_hc_regs_t *hc_regs; + dwc_otg_qtd_t *qtd; + + DWC_DEBUGPL(DBG_HCDV, "--Host Channel Interrupt--, Channel %d\n", num); + + hc = dwc_otg_hcd->hc_ptr_array[num]; + hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[num]; + qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list); + + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); + DWC_DEBUGPL(DBG_HCDV, + " hcint 0x%08x, hcintmsk 0x%08x, hcint&hcintmsk 0x%08x\n", + hcint.d32, hcintmsk.d32, (hcint.d32 & hcintmsk.d32)); + hcint.d32 = hcint.d32 & hcintmsk.d32; + + if (!dwc_otg_hcd->core_if->dma_enable) { + if (hcint.b.chhltd && hcint.d32 != 0x2) { + hcint.b.chhltd = 0; + } + } + + if (hcint.b.xfercomp) { + retval |= + handle_hc_xfercomp_intr(dwc_otg_hcd, hc, hc_regs, qtd); + /* + * If NYET occurred at same time as Xfer Complete, the NYET is + * handled by the Xfer Complete interrupt handler. Don't want + * to call the NYET interrupt handler in this case. + */ + hcint.b.nyet = 0; + } + if (hcint.b.chhltd) { + retval |= handle_hc_chhltd_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.ahberr) { + retval |= handle_hc_ahberr_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.stall) { + retval |= handle_hc_stall_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.nak) { + retval |= handle_hc_nak_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.ack) { + retval |= handle_hc_ack_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.nyet) { + retval |= handle_hc_nyet_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.xacterr) { + retval |= handle_hc_xacterr_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.bblerr) { + retval |= handle_hc_babble_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.frmovrun) { + retval |= + handle_hc_frmovrun_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.datatglerr) { + retval |= + handle_hc_datatglerr_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + + return retval; +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_linux.c b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_linux.c new file mode 100644 index 00000000..5ae4227f --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_linux.c @@ -0,0 +1,988 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_linux.c $ + * $Revision: #20 $ + * $Date: 2011/10/26 $ + * $Change: 1872981 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +/** + * @file + * + * This file contains the implementation of the HCD. In Linux, the HCD + * implements the hc_driver API. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_MACH_IPMATE +#include +#include +#include +#endif +#include +#include + +#include +#include +#include + +#include "dwc_otg_hcd_if.h" +#include "dwc_otg_dbg.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_hcd.h" +#include "usb_hw.h" + +static dwc_otg_device_t *_otg_dev = NULL; + +/** + * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is + * qualified with its direction (possible 32 endpoints per device). + */ +#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \ + ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4) + +static const char dwc_otg_hcd_name[] = "dwc_otg_hcd"; + +/** @name Linux HC Driver API Functions */ +/** @{ */ +static int urb_enqueue(struct usb_hcd *hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + struct usb_host_endpoint *ep, +#endif + struct urb *urb, gfp_t mem_flags); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) +static int urb_dequeue(struct usb_hcd *hcd, struct urb *urb); +#else +static int urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); +#endif + +static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep); +#endif +static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd); +extern int hcd_start(struct usb_hcd *hcd); +extern void hcd_stop(struct usb_hcd *hcd); +static int get_frame_number(struct usb_hcd *hcd); +extern int hub_status_data(struct usb_hcd *hcd, char *buf); +extern int hub_control(struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength); + +static int dwc_otg_bus_suspend(struct usb_hcd *hcd); +static int dwc_otg_bus_resume(struct usb_hcd *hcd); + +#ifdef CONFIG_SPRD_EXT_IC_POWER +extern void sprd_extic_otg_power(int enable); +#endif + +struct wrapper_priv_data { + dwc_otg_hcd_t *dwc_otg_hcd; +}; + +#define OTG_CABLE_TIMEOUT (HZ*3) +/** @} */ + +static struct hc_driver dwc_otg_hc_driver = { + + .description = dwc_otg_hcd_name, + .product_desc = "DWC OTG Controller", + .hcd_priv_size = sizeof(struct wrapper_priv_data), + + .irq = dwc_otg_hcd_irq, + + .flags = HCD_MEMORY | HCD_USB2, + + //.reset = + .start = hcd_start, + //.suspend = + //.resume = + .stop = hcd_stop, + + .urb_enqueue = urb_enqueue, + .urb_dequeue = urb_dequeue, + .endpoint_disable = endpoint_disable, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + .endpoint_reset = endpoint_reset, +#endif + .get_frame_number = get_frame_number, + + .hub_status_data = hub_status_data, + .hub_control = hub_control, + .bus_suspend = dwc_otg_bus_suspend, + .bus_resume = dwc_otg_bus_resume, +}; + +/** fake function: for bus suspend fail err -2 */ +/** when work in host mode,doesn't allow the system to enter suspend */ +static int dwc_otg_bus_suspend(struct usb_hcd *hcd) +{ + //struct dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + //printk("%s\n", __func__); + if(usb_get_id_state()) + return 0; + else + return -EBUSY; +} + +static int dwc_otg_bus_resume(struct usb_hcd *hcd) +{ + //struct dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + //printk("%s\n", __func__); + return 0; +} + +/** Gets the dwc_otg_hcd from a struct usb_hcd */ +static inline dwc_otg_hcd_t *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd) +{ + struct wrapper_priv_data *p; + p = (struct wrapper_priv_data *)(hcd->hcd_priv); + return p->dwc_otg_hcd; +} + +/** Gets the struct usb_hcd that contains a dwc_otg_hcd_t. */ +static inline struct usb_hcd *dwc_otg_hcd_to_hcd(dwc_otg_hcd_t * dwc_otg_hcd) +{ + return dwc_otg_hcd_get_priv_data(dwc_otg_hcd); +} + +/** Gets the usb_host_endpoint associated with an URB. */ +inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *urb) +{ + struct usb_device *dev = urb->dev; + int ep_num = usb_pipeendpoint(urb->pipe); + + if (usb_pipein(urb->pipe)) + return dev->ep_in[ep_num]; + else + return dev->ep_out[ep_num]; +} + +static int _disconnect(dwc_otg_hcd_t * hcd) +{ + struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); + + usb_hcd->self.is_b_host = 0; + return 0; +} + +static int _start(dwc_otg_hcd_t * hcd) +{ + struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); + + usb_hcd->self.is_b_host = dwc_otg_hcd_is_b_host(hcd); + hcd_start(usb_hcd); + + return 0; +} + +static int _hub_info(dwc_otg_hcd_t * hcd, void *urb_handle, uint32_t * hub_addr, + uint32_t * port_addr) +{ + struct urb *urb = (struct urb *)urb_handle; + if (urb->dev->tt) { + *hub_addr = urb->dev->tt->hub->devnum; + } else { + *hub_addr = 0; + } + *port_addr = urb->dev->ttport; + return 0; +} + +static int _speed(dwc_otg_hcd_t * hcd, void *urb_handle) +{ + struct urb *urb = (struct urb *)urb_handle; + return urb->dev->speed; +} + +static int _get_b_hnp_enable(dwc_otg_hcd_t * hcd) +{ + struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); + return usb_hcd->self.b_hnp_enable; +} + +static void allocate_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw, + struct urb *urb) +{ + hcd_to_bus(hcd)->bandwidth_allocated += bw / urb->interval; + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + hcd_to_bus(hcd)->bandwidth_isoc_reqs++; + } else { + hcd_to_bus(hcd)->bandwidth_int_reqs++; + } +} + +static void free_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw, + struct urb *urb) +{ + hcd_to_bus(hcd)->bandwidth_allocated -= bw / urb->interval; + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + hcd_to_bus(hcd)->bandwidth_isoc_reqs--; + } else { + hcd_to_bus(hcd)->bandwidth_int_reqs--; + } +} + +/** + * Sets the final status of an URB and returns it to the device driver. Any + * required cleanup of the URB is performed. + */ +static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, + dwc_otg_hcd_urb_t * dwc_otg_urb, int32_t status) +{ + struct urb *urb = (struct urb *)urb_handle; +#ifdef DEBUG + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + DWC_PRINTF("%s: urb %p, device %d, ep %d %s, status=%d\n", + __func__, urb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "IN" : "OUT", status); + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + for (i = 0; i < urb->number_of_packets; i++) { + DWC_PRINTF(" ISO Desc %d status: %d\n", + i, urb->iso_frame_desc[i].status); + } + } + } +#endif + + urb->actual_length = dwc_otg_hcd_urb_get_actual_length(dwc_otg_urb); + /* Convert status value. */ + switch (status) { + case -DWC_E_PROTOCOL: + status = -EPROTO; + break; + case -DWC_E_IN_PROGRESS: + status = -EINPROGRESS; + break; + case -DWC_E_PIPE: + status = -EPIPE; + break; + case -DWC_E_IO: + status = -EIO; + break; + case -DWC_E_TIMEOUT: + status = -ETIMEDOUT; + break; + case -DWC_E_OVERFLOW: + status = -EOVERFLOW; + break; + default: + if (status) { + DWC_PRINTF("Uknown urb status %d\n", status); + + } + } + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + + urb->error_count = dwc_otg_hcd_urb_get_error_count(dwc_otg_urb); + for (i = 0; i < urb->number_of_packets; ++i) { + urb->iso_frame_desc[i].actual_length = + dwc_otg_hcd_urb_get_iso_desc_actual_length + (dwc_otg_urb, i); + urb->iso_frame_desc[i].status = + dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_urb, i); + } + } + + urb->status = status; + urb->hcpriv = NULL; + if (!status) { + if ((urb->transfer_flags & URB_SHORT_NOT_OK) && + (urb->actual_length < urb->transfer_buffer_length)) { + urb->status = -EREMOTEIO; + } + } + + if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) || + (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) { + struct usb_host_endpoint *ep = dwc_urb_to_endpoint(urb); + if (ep) { + free_bus_bandwidth(dwc_otg_hcd_to_hcd(hcd), + dwc_otg_hcd_get_ep_bandwidth(hcd, + ep->hcpriv), + urb); + } + } + + DWC_FREE(dwc_otg_urb); + + DWC_SPINUNLOCK(hcd->lock); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb); +#else + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status); +#endif + DWC_SPINLOCK(hcd->lock); + + return 0; +} + +static struct dwc_otg_hcd_function_ops hcd_fops = { + .start = _start, + .disconnect = _disconnect, + .hub_info = _hub_info, + .speed = _speed, + .complete = _complete, + .get_b_hnp_enable = _get_b_hnp_enable, +}; + +#if 0 +static struct timer_list otg_cable_connect_timer; +static int32_t otg_cable_connect_fun(void *dev) +{ + + dwc_otg_device_t *otg_dev = dev; + printk("USB otg cable is not connected timeout"); + return 0; + /* + otg_cable_disconnect(otg_dev->core_if); + if(timer_pending(&otg_cable_connect_timer)) + del_timer(&otg_cable_connect_timer); + */ + +} +#endif + +void usb_otg_cable_detect_work(void *p) +{ + dwc_otg_device_t *otg_dev = p; + struct sprd_usb_platform_data *platform_data =&otg_dev->platform_data; + int value = 0; + int vbus_irq; + + vbus_irq = usb_get_vbus_irq(); + value = usb_get_id_state(); + if (value){ + pr_info("usb otg cable detect work plug out\n"); + + //otg_cable_disconnect(otg_dev->core_if); + charge_pump_set(platform_data->gpio_boost,0); + udc_disable(); + mdelay(10);//charge pump need time to turn off + enable_irq(vbus_irq); + } else { + pr_info("usb otg cable detect work plug in\n"); + + charge_pump_set(platform_data->gpio_boost,1); + udc_enable(); + dwc_otg_core_fore_host(otg_dev->core_if); + dwc_otg_core_init(otg_dev->core_if); + _start(otg_dev->hcd); + dwc_otg_enable_global_interrupts(otg_dev->core_if); + } + +} +void usb_otg_cable_detect_event(bool is_otg_cable_in) +{ + if (!is_otg_cable_in){ + udc_disable(); + } else { + if(_otg_dev!=NULL){ + udc_enable(); + dwc_otg_core_fore_host(_otg_dev->core_if); + dwc_otg_core_init(_otg_dev->core_if); + _start(_otg_dev->hcd); + dwc_otg_enable_global_interrupts(_otg_dev->core_if); + }else{ + pr_err("[%s]_otg_dev is null!\n", __func__); + } + } + +} + +static irqreturn_t usb_otg_cable_detect_handler(int irq, void *dev) +{ + dwc_otg_device_t *otg_dev = dev; + int value = 0; + int vbus_irq; + + vbus_irq = usb_get_vbus_irq(); + value = usb_get_id_state(); + if (value){ + pr_info("usb otg cable detect plug out\n"); + + usb_set_id_irq_type(irq, OTG_CABLE_PLUG_IN); +#ifdef CONFIG_SPRD_EXT_IC_POWER + sprd_extic_otg_power(0); //Turn off ext ic otg func +#endif + } else { + pr_info("usb otg cable detect plug in\n"); + disable_irq(vbus_irq); + usb_set_id_irq_type(irq, OTG_CABLE_PLUG_OUT); +#ifdef CONFIG_SPRD_EXT_IC_POWER + sprd_extic_otg_power(1); //Turn on ext ic otg func +#endif + } + /*use DWC workqueue*/ + DWC_WORKQ_SCHEDULE(otg_dev->core_if->wq_otg, usb_otg_cable_detect_work, + otg_dev, "OTG cable connect state change"); + + return IRQ_HANDLED; +} + +static struct device *device_hcd_dwc_otg; +static u64 dwc_otg_dmamask = DMA_BIT_MASK(32); +/** + * Initializes the HCD. This function allocates memory for and initializes the + * static parts of the usb_hcd and dwc_otg_hcd structures. It also registers the + * USB bus with the core and calls the hc_driver->start() function. It returns + * a negative error on failure. + */ +int hcd_init( + struct platform_device *_dev + ) +{ + struct usb_hcd *hcd = NULL; + dwc_otg_hcd_t *dwc_otg_hcd = NULL; + dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev); + struct sprd_usb_platform_data *pdata= _dev->dev.platform_data; + int irq; + int otg_cable_irq; + int otg_cable_connected; + int retval = 0; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD INIT\n"); + + device_hcd_dwc_otg = &(_dev->dev); + + /* Set device flags indicating whether the HCD supports DMA. */ + /* HCD.c will judge this flag. */ + /* and also scsi_lib.c will check this dma_mask. daniel.li */ + if (dwc_otg_is_dma_enable(otg_dev->core_if)) { + _dev->dev.dma_mask = &dwc_otg_dmamask; + _dev->dev.coherent_dma_mask = dwc_otg_dmamask; + } else { + _dev->dev.dma_mask = (void *)0; + _dev->dev.coherent_dma_mask = 0; + } + + /* + * Allocate memory for the base HCD plus the DWC OTG HCD. + * Initialize the base HCD. + */ + hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, dev_name(&_dev->dev)); + /** need hcd->has_tt = 1, because if the device is work on FS, we should support it**/ + if (!hcd) { + retval = -ENOMEM; + goto error1; + } + + hcd->has_tt = 1; + +// hcd->uses_new_polling = 1; +// hcd->poll_rh = 0; + + hcd->regs = otg_dev->os_dep.base; + + /* Initialize the DWC OTG HCD. */ + dwc_otg_hcd = dwc_otg_hcd_alloc_hcd(); + if (!dwc_otg_hcd) { + goto error2; + } + ((struct wrapper_priv_data *)(hcd->hcd_priv))->dwc_otg_hcd = + dwc_otg_hcd; + otg_dev->hcd = dwc_otg_hcd; + + if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if)) { + goto error2; + } + + otg_dev->hcd->otg_dev = otg_dev; + hcd->self.otg_port = dwc_otg_hcd_otg_port(dwc_otg_hcd); + + irq = platform_get_irq(_dev, 0); +#if 0//need to confirm LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33) //don't support for LM(with 2.6.20.1 kernel) + hcd->self.otg_version = dwc_otg_get_otg_version(otg_dev->core_if); + /* Don't support SG list at this point */ + hcd->self.sg_tablesize = 0; +#endif + /* + * Finish generic HCD initialization and start the HCD. This function + * allocates the DMA buffer pool, registers the USB bus, requests the + * IRQ line, and calls hcd_start method. + */ + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval < 0) { + goto error2; + } +#if 0 + /*doesn't need timer, just judge the GPIO ID state, if no otg cable connect ,host_init wouldn't done*/ + setup_timer(&otg_cable_connect_timer,otg_cable_connect_fun,otg_dev); + mod_timer(&otg_cable_connect_timer, jiffies + OTG_CABLE_TIMEOUT); +#endif +#ifndef CONFIG_MFD_SM5504 + /* + * setup usb OTG cable detect interrupt, using id pin. added by sprd + */ + { + otg_cable_irq = usb_alloc_id_irq(pdata->gpio_otgdet); + if (otg_cable_irq < 0) { + pr_warning("cannot alloc otg cable irq\n"); + return -EBUSY; + } + usb_set_id_irq_type(otg_cable_irq, OTG_CABLE_PLUG_IN); + otg_cable_connected = usb_get_id_state(); + pr_info("now usb id state is :%d\n", otg_cable_connected); + retval = request_irq(otg_cable_irq, usb_otg_cable_detect_handler, IRQF_NO_SUSPEND, + "usb otg cable detect", otg_dev); + } +#endif + //save to global + _otg_dev = otg_dev; + dwc_otg_hcd_set_priv_data(dwc_otg_hcd, hcd); + return 0; + + error2: + usb_put_hcd(hcd); + error1: + return retval; +} + +/** + * Removes the HCD. + * Frees memory and resources associated with the HCD and deregisters the bus. + */ +void hcd_remove( + struct platform_device *_dev + + ) +{ + dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev); + + + dwc_otg_hcd_t *dwc_otg_hcd; + struct usb_hcd *hcd; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD REMOVE\n"); + + if (!otg_dev) { + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__); + return; + } + + dwc_otg_hcd = otg_dev->hcd; + + if (!dwc_otg_hcd) { + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__); + return; + } + + hcd = dwc_otg_hcd_to_hcd(dwc_otg_hcd); + + if (!hcd) { + DWC_DEBUGPL(DBG_ANY, + "%s: dwc_otg_hcd_to_hcd(dwc_otg_hcd) NULL!\n", + __func__); + return; + } + usb_remove_hcd(hcd); + dwc_otg_hcd_set_priv_data(dwc_otg_hcd, NULL); + dwc_otg_hcd_remove(dwc_otg_hcd); + usb_put_hcd(hcd); +} + +/* ========================================================================= + * Linux HC Driver Functions + * ========================================================================= */ + +/** Initializes the DWC_otg controller and its root hub and prepares it for host + * mode operation. Activates the root port. Returns 0 on success and a negative + * error code on failure. */ +int hcd_start(struct usb_hcd *hcd) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + struct usb_bus *bus; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD START\n"); + bus = hcd_to_bus(hcd); + + hcd->state = HC_STATE_RUNNING; + if (dwc_otg_hcd_start(dwc_otg_hcd, &hcd_fops)) { + return 0; + } + + /* Initialize and connect root hub if one is not already attached */ + if (bus->root_hub) { + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD Has Root Hub\n"); + /* Inform the HUB driver to resume. */ + usb_hcd_resume_root_hub(hcd); + } + + return 0; +} + +/** + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + */ +void hcd_stop(struct usb_hcd *hcd) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + dwc_otg_hcd_stop(dwc_otg_hcd); +} + +/** Returns the current frame number. */ +static int get_frame_number(struct usb_hcd *hcd) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + return dwc_otg_hcd_get_frame_number(dwc_otg_hcd); +} + +#ifdef DEBUG +static void dump_urb_info(struct urb *urb, char *fn_name) +{ + DWC_PRINTF("%s, urb %p\n", fn_name, urb); + DWC_PRINTF(" Device address: %d\n", usb_pipedevice(urb->pipe)); + DWC_PRINTF(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe), + (usb_pipein(urb->pipe) ? "IN" : "OUT")); + DWC_PRINTF(" Endpoint type: %s\n", ( { + char *pipetype; + switch (usb_pipetype(urb->pipe)) { +case PIPE_CONTROL: +pipetype = "CONTROL"; break; case PIPE_BULK: +pipetype = "BULK"; break; case PIPE_INTERRUPT: +pipetype = "INTERRUPT"; break; case PIPE_ISOCHRONOUS: +pipetype = "ISOCHRONOUS"; break; default: + pipetype = "UNKNOWN"; break;}; + pipetype;} + )) ; + DWC_PRINTF(" Speed: %s\n", ( { + char *speed; switch (urb->dev->speed) { +case USB_SPEED_HIGH: +speed = "HIGH"; break; case USB_SPEED_FULL: +speed = "FULL"; break; case USB_SPEED_LOW: +speed = "LOW"; break; default: + speed = "UNKNOWN"; break;}; + speed;} + )) ; + DWC_PRINTF(" Max packet size: %d\n", + usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))); + DWC_PRINTF(" Data buffer length: %d\n", urb->transfer_buffer_length); + DWC_PRINTF(" Transfer buffer: %p, Transfer DMA: %p\n", + urb->transfer_buffer, (void *)urb->transfer_dma); + DWC_PRINTF(" Setup buffer: %p, Setup DMA: %p\n", + urb->setup_packet, (void *)urb->setup_dma); + DWC_PRINTF(" Interval: %d\n", urb->interval); + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + for (i = 0; i < urb->number_of_packets; i++) { + DWC_PRINTF(" ISO Desc %d:\n", i); + DWC_PRINTF(" offset: %d, length %d\n", + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].length); + } + } +} + +#endif + +/** Starts processing a USB transfer request specified by a USB Request Block + * (URB). mem_flags indicates the type of memory allocation to use while + * processing this URB. */ +static int urb_enqueue(struct usb_hcd *hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + struct usb_host_endpoint *ep, +#endif + struct urb *urb, gfp_t mem_flags) +{ + int retval = 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28) + struct usb_host_endpoint *ep = urb->ep; +#endif + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + dwc_otg_hcd_urb_t *dwc_otg_urb; + int i; + int alloc_bandwidth = 0; + uint8_t ep_type = 0; + uint32_t flags = 0; + void *buf; + +#ifdef DEBUG + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + dump_urb_info(urb, "urb_enqueue"); + } +#endif + + if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + || (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) { + if (!dwc_otg_hcd_is_bandwidth_allocated + (dwc_otg_hcd, &ep->hcpriv)) { + alloc_bandwidth = 1; + } + } + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ep_type = USB_ENDPOINT_XFER_CONTROL; + break; + case PIPE_ISOCHRONOUS: + ep_type = USB_ENDPOINT_XFER_ISOC; + break; + case PIPE_BULK: + ep_type = USB_ENDPOINT_XFER_BULK; + break; + case PIPE_INTERRUPT: + ep_type = USB_ENDPOINT_XFER_INT; + break; + default: + DWC_WARN("Wrong ep type\n"); + } + + dwc_otg_urb = dwc_otg_hcd_urb_alloc(dwc_otg_hcd, + urb->number_of_packets, + mem_flags == GFP_ATOMIC ? 1 : 0); + + dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_urb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), ep_type, + usb_pipein(urb->pipe), + usb_maxpacket(urb->dev, urb->pipe, + !(usb_pipein(urb->pipe)))); + + buf = urb->transfer_buffer; + if (hcd->self.uses_dma) { + /* + * Calculate virtual address from physical address, + * because some class driver may not fill transfer_buffer. + * In Buffer DMA mode virual address is used, + * when handling non DWORD aligned buffers. + */ + buf = phys_to_virt(urb->transfer_dma); + } + + if (!(urb->transfer_flags & URB_NO_INTERRUPT)) + flags |= URB_GIVEBACK_ASAP; + if (urb->transfer_flags & URB_ZERO_PACKET) + flags |= URB_SEND_ZERO_PACKET; + + dwc_otg_hcd_urb_set_params(dwc_otg_urb, urb, buf, + urb->transfer_dma, + urb->transfer_buffer_length, + urb->setup_packet, + urb->setup_dma, flags, urb->interval); + + for (i = 0; i < urb->number_of_packets; ++i) { + dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_urb, i, + urb-> + iso_frame_desc[i].offset, + urb-> + iso_frame_desc[i].length); + } + + urb->hcpriv = dwc_otg_urb; + retval = dwc_otg_hcd_urb_enqueue(dwc_otg_hcd, dwc_otg_urb, &ep->hcpriv, + mem_flags == GFP_ATOMIC ? 1 : 0); + if (!retval) { + if (alloc_bandwidth) { + allocate_bus_bandwidth(hcd, + dwc_otg_hcd_get_ep_bandwidth + (dwc_otg_hcd, ep->hcpriv), urb); + } + } else { + if (retval == -DWC_E_NO_DEVICE) { + retval = -ENODEV; + } + } + + return retval; +} + +/** Aborts/cancels a USB transfer request. Always returns 0 to indicate + * success. */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) +static int urb_dequeue(struct usb_hcd *hcd, struct urb *urb) +#else +static int urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +#endif +{ + dwc_irqflags_t flags; + dwc_otg_hcd_t *dwc_otg_hcd; + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue\n"); + + dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + +#ifdef DEBUG + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + dump_urb_info(urb, "urb_dequeue"); + } +#endif + + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); + if(urb->hcpriv != NULL) { + dwc_otg_hcd_urb_dequeue(dwc_otg_hcd, urb->hcpriv); + + DWC_FREE(urb->hcpriv); + urb->hcpriv = NULL; + } + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); + + /* Higher layer software sets URB status. */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + usb_hcd_giveback_urb(hcd, urb); +#else + usb_hcd_giveback_urb(hcd, urb, status); +#endif + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + DWC_PRINTF("Called usb_hcd_giveback_urb()\n"); + DWC_PRINTF(" urb->status = %d\n", urb->status); + } + + return 0; +} + +/* Frees resources in the DWC_otg controller related to a given endpoint. Also + * clears state in the HCD related to the endpoint. Any URBs for the endpoint + * must already be dequeued. */ +static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + DWC_DEBUGPL(DBG_HCD, + "DWC OTG HCD EP DISABLE: _bEndpointAddress=0x%02x, " + "endpoint=%d\n", ep->desc.bEndpointAddress, + dwc_ep_addr_to_endpoint(ep->desc.bEndpointAddress)); + dwc_otg_hcd_endpoint_disable(dwc_otg_hcd, ep->hcpriv, 250); + ep->hcpriv = NULL; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +/* Resets endpoint specific parameter values, in current version used to reset + * the data toggle(as a WA). This function can be called from usb_clear_halt routine */ +static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + dwc_irqflags_t flags; + struct usb_device *udev = NULL; + int epnum = usb_endpoint_num(&ep->desc); + int is_out = usb_endpoint_dir_out(&ep->desc); + int is_control = usb_endpoint_xfer_control(&ep->desc); + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + struct device *dev = DWC_OTG_OS_GETDEV(dwc_otg_hcd->otg_dev->os_dep); + + if (dev) + udev = to_usb_device(dev); + else + return; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD EP RESET: Endpoint Num=0x%02d\n", epnum); + + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); + usb_settoggle(udev, epnum, is_out, 0); + if (is_control) + usb_settoggle(udev, epnum, !is_out, 0); + + if (ep->hcpriv) { + dwc_otg_hcd_endpoint_reset(dwc_otg_hcd, ep->hcpriv); + } + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); +} +#endif + +/** Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if + * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid + * interrupt. + * + * This function is called by the USB core when an interrupt occurs */ +static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + int32_t retval = dwc_otg_hcd_handle_intr(dwc_otg_hcd); + if (retval != 0) { + S3C2410X_CLEAR_EINTPEND(); + } + return IRQ_RETVAL(retval); +} + +/** Creates Status Change bitmap for the root hub and root port. The bitmap is + * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1 + * is the status change indicator for the single root port. Returns 1 if either + * change indicator is 1, otherwise returns 0. */ +int hub_status_data(struct usb_hcd *hcd, char *buf) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); +//dump_stack(); + buf[0] = 0; + buf[0] |= (dwc_otg_hcd_is_status_changed(dwc_otg_hcd, 1)) << 1; + + return (buf[0] != 0); +} + +/** Handles hub class-specific requests. */ +int hub_control(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + int retval; + + retval = dwc_otg_hcd_hub_control(hcd_to_dwc_otg_hcd(hcd), + typeReq, wValue, wIndex, buf, wLength); + + switch (retval) { + case -DWC_E_INVALID: + retval = -EINVAL; + break; + } + + return retval; +} + +struct device *get_hcd_device() +{ +#ifdef CONFIG_ARM64 +if(NULL != device_hcd_dwc_otg) + { + return device_hcd_dwc_otg; + } +else + { + gadget_wrapper->gadget.dev.dma_mask = &dwc_otg_dmamask; + gadget_wrapper->gadget.dev.coherent_dma_mask = dwc_otg_dmamask; + return &(gadget_wrapper->gadget.dev); + } +#else + return NULL; +#endif +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_queue.c b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_queue.c new file mode 100644 index 00000000..bbf3406c --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_hcd_queue.c @@ -0,0 +1,727 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_queue.c $ + * $Revision: #44 $ + * $Date: 2011/10/26 $ + * $Change: 1873028 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +/** + * @file + * + * This file contains the functions to manage Queue Heads and Queue + * Transfer Descriptors. + */ + +#include "dwc_otg_driver.h" +#include "dwc_otg_hcd.h" +#include "dwc_otg_regs.h" +#include "usb.h" +/** + * Free each QTD in the QH's QTD-list then free the QH. QH should already be + * removed from a list. QTD list should already be empty if called from URB + * Dequeue. + * + * @param hcd HCD instance. + * @param qh The QH to free. + */ +void dwc_otg_hcd_qh_free(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + dwc_otg_qtd_t *qtd, *qtd_tmp; + + /* Free each QTD in the QTD list */ + DWC_SPINLOCK(hcd->lock); + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { + DWC_CIRCLEQ_REMOVE(&qh->qtd_list, qtd, qtd_list_entry); + dwc_otg_hcd_qtd_free(qtd); + } + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_qh_free_ddma(hcd, qh); + } else if (qh->dw_align_buf) { + uint32_t buf_size; + if (qh->ep_type == UE_ISOCHRONOUS) { + buf_size = 4096; + } else { + buf_size = hcd->core_if->core_params->max_transfer_size; + } + DWC_DMA_FREE(get_hcd_device(), buf_size, qh->dw_align_buf, qh->dw_align_buf_dma); + } + + DWC_FREE(qh); + DWC_SPINUNLOCK(hcd->lock); + return; +} + +#define BitStuffTime(bytecount) ((8 * 7* bytecount) / 6) +#define HS_HOST_DELAY 5 /* nanoseconds */ +#define FS_LS_HOST_DELAY 1000 /* nanoseconds */ +#define HUB_LS_SETUP 333 /* nanoseconds */ +#define NS_TO_US(ns) ((ns + 500) / 1000) + /* convert & round nanoseconds to microseconds */ + +static uint32_t calc_bus_time(int speed, int is_in, int is_isoc, int bytecount) +{ + unsigned long retval; + + switch (speed) { + case USB_SPEED_HIGH: + if (is_isoc) { + retval = + ((38 * 8 * 2083) + + (2083 * (3 + BitStuffTime(bytecount)))) / 1000 + + HS_HOST_DELAY; + } else { + retval = + ((55 * 8 * 2083) + + (2083 * (3 + BitStuffTime(bytecount)))) / 1000 + + HS_HOST_DELAY; + } + break; + case USB_SPEED_FULL: + if (is_isoc) { + retval = + (8354 * (31 + 10 * BitStuffTime(bytecount))) / 1000; + if (is_in) { + retval = 7268 + FS_LS_HOST_DELAY + retval; + } else { + retval = 6265 + FS_LS_HOST_DELAY + retval; + } + } else { + retval = + (8354 * (31 + 10 * BitStuffTime(bytecount))) / 1000; + retval = 9107 + FS_LS_HOST_DELAY + retval; + } + break; + case USB_SPEED_LOW: + if (is_in) { + retval = + (67667 * (31 + 10 * BitStuffTime(bytecount))) / + 1000; + retval = + 64060 + (2 * HUB_LS_SETUP) + FS_LS_HOST_DELAY + + retval; + } else { + retval = + (66700 * (31 + 10 * BitStuffTime(bytecount))) / + 1000; + retval = + 64107 + (2 * HUB_LS_SETUP) + FS_LS_HOST_DELAY + + retval; + } + break; + default: + DWC_WARN("Unknown device speed\n"); + retval = -1; + } + + return NS_TO_US(retval); +} + +/** + * Initializes a QH structure. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh The QH to init. + * @param urb Holds the information about the device/endpoint that we need + * to initialize the QH. + */ +#define SCHEDULE_SLOP 10 +void qh_init(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, dwc_otg_hcd_urb_t * urb) +{ + char *speed, *type; + int dev_speed; + uint32_t hub_addr, hub_port; + + dwc_memset(qh, 0, sizeof(dwc_otg_qh_t)); + + /* Initialize QH */ + qh->ep_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info); + qh->ep_is_in = dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? 1 : 0; + + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + qh->maxp = dwc_otg_hcd_get_mps(&urb->pipe_info); + DWC_CIRCLEQ_INIT(&qh->qtd_list); + DWC_LIST_INIT(&qh->qh_list_entry); + qh->channel = NULL; + + /* FS/LS Enpoint on HS Hub + * NOT virtual root hub */ + dev_speed = hcd->fops->speed(hcd, urb->priv); + + hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &hub_port); + qh->do_split = 0; + + if (((dev_speed == USB_SPEED_LOW) || + (dev_speed == USB_SPEED_FULL)) && + (hub_addr != 0 && hub_addr != 1)) { + DWC_DEBUGPL(DBG_HCD, + "QH init: EP %d: TT found at hub addr %d, for port %d\n", + dwc_otg_hcd_get_ep_num(&urb->pipe_info), hub_addr, + hub_port); + qh->do_split = 1; + } + + if (qh->ep_type == UE_INTERRUPT || qh->ep_type == UE_ISOCHRONOUS) { + /* Compute scheduling parameters once and save them. */ + hprt0_data_t hprt; + + /** @todo Account for split transfers in the bus time. */ + int bytecount = + dwc_hb_mult(qh->maxp) * dwc_max_packet(qh->maxp); + + qh->usecs = + calc_bus_time((qh->do_split ? USB_SPEED_HIGH : dev_speed), + qh->ep_is_in, (qh->ep_type == UE_ISOCHRONOUS), + bytecount); + /* Start in a slightly future (micro)frame. */ + qh->sched_frame = dwc_frame_num_inc(hcd->frame_number, + SCHEDULE_SLOP); + qh->interval = urb->interval; + +#if 0 + /* Increase interrupt polling rate for debugging. */ + if (qh->ep_type == UE_INTERRUPT) { + qh->interval = 8; + } +#endif + hprt.d32 = DWC_READ_REG32(hcd->core_if->host_if->hprt0); + if ((hprt.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) && + ((dev_speed == USB_SPEED_LOW) || + (dev_speed == USB_SPEED_FULL))) { + qh->interval *= 8; + qh->sched_frame |= 0x7; + qh->start_split_frame = qh->sched_frame; + } + + } + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD QH Initialized\n"); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - qh = %p\n", qh); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Device Address = %d\n", + dwc_otg_hcd_get_dev_addr(&urb->pipe_info)); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Endpoint %d, %s\n", + dwc_otg_hcd_get_ep_num(&urb->pipe_info), + dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT"); + switch (dev_speed) { + case USB_SPEED_LOW: + qh->dev_speed = DWC_OTG_EP_SPEED_LOW; + speed = "low"; + break; + case USB_SPEED_FULL: + qh->dev_speed = DWC_OTG_EP_SPEED_FULL; + speed = "full"; + break; + case USB_SPEED_HIGH: + qh->dev_speed = DWC_OTG_EP_SPEED_HIGH; + speed = "high"; + break; + default: + speed = "?"; + break; + } + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Speed = %s\n", speed); + + switch (qh->ep_type) { + case UE_ISOCHRONOUS: + type = "isochronous"; + break; + case UE_INTERRUPT: + type = "interrupt"; + break; + case UE_CONTROL: + type = "control"; + break; + case UE_BULK: + type = "bulk"; + break; + default: + type = "?"; + break; + } + + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Type = %s\n", type); + +#ifdef DEBUG + if (qh->ep_type == UE_INTERRUPT) { + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - usecs = %d\n", + qh->usecs); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - interval = %d\n", + qh->interval); + } +#endif + +} + +/** + * This function allocates and initializes a QH. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param urb Holds the information about the device/endpoint that we need + * to initialize the QH. + * @param atomic_alloc Flag to do atomic allocation if needed + * + * @return Returns pointer to the newly allocated QH, or NULL on error. */ +dwc_otg_qh_t *dwc_otg_hcd_qh_create(dwc_otg_hcd_t * hcd, + dwc_otg_hcd_urb_t * urb, int atomic_alloc) +{ + dwc_otg_qh_t *qh; + + /* Allocate memory */ + /** @todo add memflags argument */ + qh = dwc_otg_hcd_qh_alloc(atomic_alloc); + if (qh == NULL) { + DWC_ERROR("qh allocation failed"); + return NULL; + } + + qh_init(hcd, qh, urb); + + if (hcd->core_if->dma_desc_enable + && (dwc_otg_hcd_qh_init_ddma(hcd, qh) < 0)) { + dwc_otg_hcd_qh_free(hcd, qh); + return NULL; + } + + return qh; +} + +/** + * Checks that a channel is available for a periodic transfer. + * + * @return 0 if successful, negative error code otherise. + */ +static int periodic_channel_available(dwc_otg_hcd_t * hcd) +{ + /* + * Currently assuming that there is a dedicated host channnel for each + * periodic transaction plus at least one host channel for + * non-periodic transactions. + */ + int status; + int num_channels; + + num_channels = hcd->core_if->core_params->host_channels; + if ((hcd->periodic_channels + hcd->non_periodic_channels < num_channels) + && (hcd->periodic_channels < num_channels - 1)) { + status = 0; + } else { + DWC_INFO("%s: Total channels: %d, Periodic: %d, Non-periodic: %d\n", + __func__, num_channels, hcd->periodic_channels, hcd->non_periodic_channels); //NOTICE + status = -DWC_E_NO_SPACE; + } + + return status; +} + +/** + * Checks that there is sufficient bandwidth for the specified QH in the + * periodic schedule. For simplicity, this calculation assumes that all the + * transfers in the periodic schedule may occur in the same (micro)frame. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh QH containing periodic bandwidth required. + * + * @return 0 if successful, negative error code otherwise. + */ +static int check_periodic_bandwidth(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int status; + int16_t max_claimed_usecs; + + status = 0; + + if ((qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) || qh->do_split) { + /* + * High speed mode. + * Max periodic usecs is 80% x 125 usec = 100 usec. + */ + + max_claimed_usecs = 100 - qh->usecs; + } else { + /* + * Full speed mode. + * Max periodic usecs is 90% x 1000 usec = 900 usec. + */ + max_claimed_usecs = 900 - qh->usecs; + } + + if (hcd->periodic_usecs > max_claimed_usecs) { + DWC_INFO("%s: already claimed usecs %d, required usecs %d\n", __func__, hcd->periodic_usecs, qh->usecs); //NOTICE + status = -DWC_E_NO_SPACE; + } + + return status; +} + +/** + * Checks that the max transfer size allowed in a host channel is large enough + * to handle the maximum data transfer in a single (micro)frame for a periodic + * transfer. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh QH for a periodic endpoint. + * + * @return 0 if successful, negative error code otherwise. + */ +static int check_max_xfer_size(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int status; + uint32_t max_xfer_size; + uint32_t max_channel_xfer_size; + + status = 0; + + max_xfer_size = dwc_max_packet(qh->maxp) * dwc_hb_mult(qh->maxp); + max_channel_xfer_size = hcd->core_if->core_params->max_transfer_size; + + if (max_xfer_size > max_channel_xfer_size) { + DWC_INFO("%s: Periodic xfer length %d > " "max xfer length for channel %d\n", + __func__, max_xfer_size, max_channel_xfer_size); //NOTICE + status = -DWC_E_NO_SPACE; + } + + return status; +} + +/** + * Schedules an interrupt or isochronous transfer in the periodic schedule. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh QH for the periodic transfer. The QH should already contain the + * scheduling information. + * + * @return 0 if successful, negative error code otherwise. + */ +static int schedule_periodic(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int status = 0; + + status = periodic_channel_available(hcd); + if (status) { + DWC_INFO("%s: No host channel available for periodic " "transfer.\n", __func__); //NOTICE + return status; + } + + status = check_periodic_bandwidth(hcd, qh); + if (status) { + DWC_INFO("%s: Insufficient periodic bandwidth for " "periodic transfer.\n", __func__); //NOTICE + return status; + } + + status = check_max_xfer_size(hcd, qh); + if (status) { + DWC_INFO("%s: Channel max transfer size too small " "for periodic transfer.\n", __func__); //NOTICE + return status; + } + + if (hcd->core_if->dma_desc_enable) { + /* Don't rely on SOF and start in ready schedule */ + DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_ready, &qh->qh_list_entry); + } + else { + /* Always start in the inactive schedule. */ + DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_inactive, &qh->qh_list_entry); + } + + /* Reserve the periodic channel. */ + hcd->periodic_channels++; + + /* Update claimed usecs per (micro)frame. */ + hcd->periodic_usecs += qh->usecs; + + return status; +} + +/** + * This function adds a QH to either the non periodic or periodic schedule if + * it is not already in the schedule. If the QH is already in the schedule, no + * action is taken. + * + * @return 0 if successful, negative error code otherwise. + */ +int dwc_otg_hcd_qh_add(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int status = 0; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + if (!DWC_LIST_EMPTY(&qh->qh_list_entry)) { + /* QH already in a schedule. */ + return status; + } + + /* Add the new QH to the appropriate schedule */ + if (dwc_qh_is_non_per(qh)) { + /* Always start in the inactive schedule. */ + DWC_LIST_INSERT_TAIL(&hcd->non_periodic_sched_inactive, + &qh->qh_list_entry); + } else { + status = schedule_periodic(hcd, qh); + if ( !hcd->periodic_qh_count ) { + intr_mask.b.sofintr = 1; + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + } + hcd->periodic_qh_count++; + } + + return status; +} + +/** + * Removes an interrupt or isochronous transfer from the periodic schedule. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh QH for the periodic transfer. + */ +static void deschedule_periodic(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + DWC_LIST_REMOVE_INIT(&qh->qh_list_entry); + + /* Release the periodic channel reservation. */ + hcd->periodic_channels--; + + /* Update claimed usecs per (micro)frame. */ + hcd->periodic_usecs -= qh->usecs; +} + +/** + * Removes a QH from either the non-periodic or periodic schedule. Memory is + * not freed. + * + * @param hcd The HCD state structure. + * @param qh QH to remove from schedule. */ +void dwc_otg_hcd_qh_remove(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + + if (DWC_LIST_EMPTY(&qh->qh_list_entry)) { + /* QH is not in a schedule. */ + return; + } + + if (dwc_qh_is_non_per(qh)) { + if (hcd->non_periodic_qh_ptr == &qh->qh_list_entry) { + hcd->non_periodic_qh_ptr = + hcd->non_periodic_qh_ptr->next; + } + DWC_LIST_REMOVE_INIT(&qh->qh_list_entry); + } else { + deschedule_periodic(hcd, qh); + hcd->periodic_qh_count--; + if( !hcd->periodic_qh_count ) { + intr_mask.b.sofintr = 1; + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, + intr_mask.d32, 0); + } + } +} + +/** + * Deactivates a QH. For non-periodic QHs, removes the QH from the active + * non-periodic schedule. The QH is added to the inactive non-periodic + * schedule if any QTDs are still attached to the QH. + * + * For periodic QHs, the QH is removed from the periodic queued schedule. If + * there are any QTDs still attached to the QH, the QH is added to either the + * periodic inactive schedule or the periodic ready schedule and its next + * scheduled frame is calculated. The QH is placed in the ready schedule if + * the scheduled frame has been reached already. Otherwise it's placed in the + * inactive schedule. If there are no QTDs attached to the QH, the QH is + * completely removed from the periodic schedule. + */ +void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, + int sched_next_periodic_split) +{ + if (dwc_qh_is_non_per(qh)) { + dwc_otg_hcd_qh_remove(hcd, qh); + if (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + /* Add back to inactive non-periodic schedule. */ + dwc_otg_hcd_qh_add(hcd, qh); + } + } else { + uint16_t frame_number = dwc_otg_hcd_get_frame_number(hcd); + + if (qh->do_split) { + /* Schedule the next continuing periodic split transfer */ + if (sched_next_periodic_split) { + + qh->sched_frame = frame_number; + if (dwc_frame_num_le(frame_number, + dwc_frame_num_inc + (qh->start_split_frame, + 1))) { + /* + * Allow one frame to elapse after start + * split microframe before scheduling + * complete split, but DONT if we are + * doing the next start split in the + * same frame for an ISOC out. + */ + if ((qh->ep_type != UE_ISOCHRONOUS) || + (qh->ep_is_in != 0)) { + qh->sched_frame = + dwc_frame_num_inc(qh->sched_frame, 1); + } + } + } else { + qh->sched_frame = + dwc_frame_num_inc(qh->start_split_frame, + qh->interval); + if (dwc_frame_num_le + (qh->sched_frame, frame_number)) { + qh->sched_frame = frame_number; + } + qh->sched_frame |= 0x7; + qh->start_split_frame = qh->sched_frame; + } + } else { + qh->sched_frame = + dwc_frame_num_inc(qh->sched_frame, qh->interval); + if (dwc_frame_num_le(qh->sched_frame, frame_number)) { + qh->sched_frame = frame_number; + } + } + + if (DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + dwc_otg_hcd_qh_remove(hcd, qh); + } else { + /* + * Remove from periodic_sched_queued and move to + * appropriate queue. + */ + if (qh->sched_frame == frame_number) { + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_ready, + &qh->qh_list_entry); + } else { + DWC_LIST_MOVE_HEAD + (&hcd->periodic_sched_inactive, + &qh->qh_list_entry); + } + } + } +} + +/** + * This function allocates and initializes a QTD. + * + * @param urb The URB to create a QTD from. Each URB-QTD pair will end up + * pointing to each other so each pair should have a unique correlation. + * @param atomic_alloc Flag to do atomic alloc if needed + * + * @return Returns pointer to the newly allocated QTD, or NULL on error. */ +dwc_otg_qtd_t *dwc_otg_hcd_qtd_create(dwc_otg_hcd_urb_t * urb, int atomic_alloc) +{ + dwc_otg_qtd_t *qtd; + + qtd = dwc_otg_hcd_qtd_alloc(atomic_alloc); + if (qtd == NULL) { + return NULL; + } + + dwc_otg_hcd_qtd_init(qtd, urb); + return qtd; +} + +/** + * Initializes a QTD structure. + * + * @param qtd The QTD to initialize. + * @param urb The URB to use for initialization. */ +void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t * qtd, dwc_otg_hcd_urb_t * urb) +{ + dwc_memset(qtd, 0, sizeof(dwc_otg_qtd_t)); + qtd->urb = urb; + if (dwc_otg_hcd_get_pipe_type(&urb->pipe_info) == UE_CONTROL) { + /* + * The only time the QTD data toggle is used is on the data + * phase of control transfers. This phase always starts with + * DATA1. + */ + qtd->data_toggle = DWC_OTG_HC_PID_DATA1; + qtd->control_phase = DWC_OTG_CONTROL_SETUP; + } + + /* start split */ + qtd->complete_split = 0; + qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL; + qtd->isoc_split_offset = 0; + qtd->in_process = 0; + + /* Store the qtd ptr in the urb to reference what QTD. */ + urb->qtd = qtd; + return; +} + +/** + * This function adds a QTD to the QTD-list of a QH. It will find the correct + * QH to place the QTD into. If it does not find a QH, then it will create a + * new QH. If the QH to which the QTD is added is not currently scheduled, it + * is placed into the proper schedule based on its EP type. + * + * @param[in] qtd The QTD to add + * @param[in] hcd The DWC HCD structure + * @param[out] qh out parameter to return queue head + * @param atomic_alloc Flag to do atomic alloc if needed + * + * @return 0 if successful, negative error code otherwise. + */ +int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * qtd, + dwc_otg_hcd_t * hcd, dwc_otg_qh_t ** qh, int atomic_alloc) +{ + int retval = 0; + dwc_irqflags_t flags; + + dwc_otg_hcd_urb_t *urb = qtd->urb; + + /* + * Get the QH which holds the QTD-list to insert to. Create QH if it + * doesn't exist. + */ + if (*qh == NULL) { + *qh = dwc_otg_hcd_qh_create(hcd, urb, atomic_alloc); + if (*qh == NULL) { + retval = -1; + goto done; + } + } + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + retval = dwc_otg_hcd_qh_add(hcd, *qh); + if (retval == 0) { + DWC_CIRCLEQ_INSERT_TAIL(&((*qh)->qtd_list), qtd, + qtd_list_entry); + } + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + +done: + + return retval; +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_os_dep.h b/drivers/usb/gadget/dwc_otg/dwc_otg_os_dep.h new file mode 100644 index 00000000..5226ebe5 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_os_dep.h @@ -0,0 +1,116 @@ +#ifndef _DWC_OS_DEP_H_ +#define _DWC_OS_DEP_H_ + +/** +* @file +* +* This file contains OS dependent structures. +* +*/ + #if 0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) +# include +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21) +# include +#else +# include +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +# include +#else +# include +#endif +#endif + + +#include +#ifdef CONFIG_ARM64 +#include +#include +#else +#include +#endif + +/** The OS page size */ +#define DWC_OS_PAGE_SIZE PAGE_SIZE + + +typedef struct os_dependent { +/** Base address returned from ioremap() */ +void *base; + +/** Register offset for Diagnostic API */ +uint32_t reg_offset; + + +struct platform_device *platformdev; + + +} os_dependent_t; + +#ifdef __cplusplus +} +#endif + + +typedef struct platform_device dwc_bus_dev_t; + + +/* Helper macro to retrieve drvdata from the device on the chosen bus */ + +#define DWC_OTG_BUSDRVDATA(_dev) platform_get_drvdata(_dev) + + +/** +* Helper macro returning the otg_device structure of a given struct device +* +* c.f. static dwc_otg_device_t *dwc_otg_drvdev(struct device *_dev) +*/ + +#define DWC_OTG_GETDRVDEV(_var, _dev) do { \ +struct platform_device *platform_dev = \ +container_of(_dev, struct platform_device, dev); \ +_var = platform_get_drvdata(platform_dev); \ +} while (0) + + + +/** +* Helper macro returning the struct dev of the given struct os_dependent +* +* c.f. static struct device *dwc_otg_getdev(struct os_dependent *osdep) +*/ + +#define DWC_OTG_OS_GETDEV(_osdep) \ +((_osdep).platformdev == NULL? NULL: &(_osdep).platformdev->dev) + + + + + +#endif /* _DWC_OS_DEP_H_ */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c new file mode 100644 index 00000000..50f0b3a9 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c @@ -0,0 +1,2693 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd.c $ + * $Revision: #101 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY + +/** @file + * This file implements PCD Core. All code in this file is portable and doesn't + * use any OS specific functions. + * PCD Core provides Interface, defined in + * header file, which can be used to implement OS specific PCD interface. + * + * An important function of the PCD is managing interrupts generated + * by the DWC_otg controller. The implementation of the DWC_otg device + * mode interrupt service routines is in dwc_otg_pcd_intr.c. + * + * @todo Add Device Mode test modes (Test J mode, Test K mode, etc). + * @todo Does it work when the request size is greater than DEPTSIZ + * transfer size + * + */ + +#include "dwc_otg_pcd.h" +#include "dwc_otg_driver.h" +#include +#include + +#ifdef DWC_UTE_CFI +#include "dwc_otg_cfi.h" + +extern int init_cfi(cfiobject_t * cfiobj); +#endif + +/** + * Choose endpoint from ep arrays using usb_ep structure. + */ +static dwc_otg_pcd_ep_t *get_ep_from_handle(dwc_otg_pcd_t * pcd, void *handle) +{ + int i; + if (pcd->ep0.priv == handle) { + return &pcd->ep0; + } + for (i = 0; i < MAX_EPS_CHANNELS - 1; i++) { + if (pcd->in_ep[i].priv == handle) + return &pcd->in_ep[i]; + if (pcd->out_ep[i].priv == handle) + return &pcd->out_ep[i]; + } + + return NULL; +} +static void noinline dump_log(unsigned char * buf, int len, int direction) +{ +#ifdef DEBUG + int i = 0; + + trace_printk("**log_buf :%s len:%d\n", (direction ? "in" : "out"), len); + if (len > 64) + len = 64; + for (i = 0; i < len; i += 4) { + trace_printk("%02x %02x %02x %02x\n", + buf[i],buf[i+1],buf[i+2],buf[i+3] + ); + } +#endif +} + +/** + * This function completes a request. It call's the request call back. + */ +void dwc_otg_request_done(dwc_otg_pcd_ep_t * ep, dwc_otg_pcd_request_t * req, + int32_t status) +{ + unsigned stopped = ep->stopped; + + DWC_DEBUGPL(DBG_PCDV, "%s(ep %p req %p)\n", __func__, ep, req); + DWC_CIRCLEQ_REMOVE_INIT(&ep->queue, req, queue_entry); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + if (GET_CORE_IF(ep->pcd)->dma_enable){ + if (req->num_mapped_sgs) { + dma_unmap_sg(get_gadget_wrapper_device(), + req->sg, req->num_mapped_sgs, + (ep->dwc_ep.num == 0) ? DMA_BIDIRECTIONAL : + ((ep->dwc_ep.is_in) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE)); + } else { + if (req->mapped) { + dma_unmap_single(get_gadget_wrapper_device(), + req->dma, req->length, + (ep->dwc_ep.num == 0) ? DMA_BIDIRECTIONAL : + ((ep->dwc_ep.is_in) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE)); + req->dma = DWC_DMA_ADDR_INVALID; + req->mapped = 0; + } else { + dma_sync_single_for_cpu(get_gadget_wrapper_device(), + req->dma, req->length, + (ep->dwc_ep.num == 0) ? DMA_BIDIRECTIONAL : + ((ep->dwc_ep.is_in) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE)); + } + } + } + + if (!ep->dwc_ep.is_in) { + dump_log(req->buf, req->actual, 0); + } + /* spin_unlock/spin_lock now done in fops->complete() */ + ep->pcd->fops->complete(ep->pcd, ep->priv, req->priv, status, + req->actual); + + if (ep->pcd->request_pending > 0) { + --ep->pcd->request_pending; + } + + ep->stopped = stopped; + DWC_FREE(req); +} + +/** + * This function terminates all the requsts in the EP request queue. + */ +void dwc_otg_request_nuke(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_pcd_request_t *req; + + ep->stopped = 1; + + /* called with irqs blocked?? */ + while (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + dwc_otg_request_done(ep, req, -DWC_E_SHUTDOWN); + } +} + +void dwc_otg_pcd_start(dwc_otg_pcd_t * pcd, + const struct dwc_otg_pcd_function_ops *fops) +{ + pcd->fops = fops; +} + +/** + * PCD Callback function for initializing the PCD when switching to + * device mode. + * + * @param p void pointer to the dwc_otg_pcd_t + */ +static int32_t dwc_otg_pcd_start_cb(void *p) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + /* + * Initialized the Core for Device mode. + */ + if (dwc_otg_is_device_mode(core_if)) { + dwc_otg_core_dev_init(core_if); + /* Set core_if's lock pointer to the pcd->lock */ + core_if->lock = pcd->lock; + } + return 1; +} + +/** CFI-specific buffer allocation function for EP */ +#ifdef DWC_UTE_CFI +uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep, dwc_dma_t * addr, + size_t buflen, int flags) +{ + dwc_otg_pcd_ep_t *ep; + ep = get_ep_from_handle(pcd, pep); + if (!ep) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + + return pcd->cfi->ops.ep_alloc_buf(pcd->cfi, pcd, ep, addr, buflen, + flags); +} +#else +uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep, dwc_dma_t * addr, + size_t buflen, int flags); +#endif + +/** + * PCD Callback function for notifying the PCD when resuming from + * suspend. + * + * @param p void pointer to the dwc_otg_pcd_t + */ +static int32_t dwc_otg_pcd_resume_cb(void *p) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p; + + if (pcd->fops->resume) { + pcd->fops->resume(pcd); + } + + /* Stop the SRP timeout timer. */ + if ((GET_CORE_IF(pcd)->core_params->phy_type != DWC_PHY_TYPE_PARAM_FS) + || (!GET_CORE_IF(pcd)->core_params->i2c_enable)) { + if (GET_CORE_IF(pcd)->srp_timer_started) { + GET_CORE_IF(pcd)->srp_timer_started = 0; + DWC_TIMER_CANCEL(GET_CORE_IF(pcd)->srp_timer); + } + } + return 1; +} + +/** + * PCD Callback function for notifying the PCD device is suspended. + * + * @param p void pointer to the dwc_otg_pcd_t + */ +static int32_t dwc_otg_pcd_suspend_cb(void *p) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p; + + if (pcd->fops->suspend) { + pcd->fops->suspend(pcd); + } + + return 1; +} + +/** + * PCD Callback function for stopping the PCD when switching to Host + * mode. + * + * @param p void pointer to the dwc_otg_pcd_t + */ +static int32_t dwc_otg_pcd_stop_cb(void *p) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p; + extern void dwc_otg_pcd_stop(dwc_otg_pcd_t * _pcd); + + dwc_otg_pcd_stop(pcd); + return 1; +} + +/** + * PCD Callback structure for handling mode switching. + */ +static dwc_otg_cil_callbacks_t pcd_callbacks = { + .start = dwc_otg_pcd_start_cb, + .stop = dwc_otg_pcd_stop_cb, + .suspend = dwc_otg_pcd_suspend_cb, + .resume_wakeup = dwc_otg_pcd_resume_cb, + .p = 0, /* Set at registration */ +}; + +/** + * This function allocates a DMA Descriptor chain for the Endpoint + * buffer to be used for a transfer to/from the specified endpoint. + */ +dwc_otg_dev_dma_desc_t *dwc_otg_ep_alloc_desc_chain(dwc_dma_t * dma_desc_addr, + uint32_t count) +{ + return DWC_DMA_ALLOC(get_gadget_wrapper_device(), count * sizeof(dwc_otg_dev_dma_desc_t), + dma_desc_addr); +} + +/** + * This function frees a DMA Descriptor chain that was allocated by ep_alloc_desc. + */ +void dwc_otg_ep_free_desc_chain(dwc_otg_dev_dma_desc_t * desc_addr, + uint32_t dma_desc_addr, uint32_t count) +{ + DWC_DMA_FREE(get_gadget_wrapper_device(), count * sizeof(dwc_otg_dev_dma_desc_t), desc_addr, + dma_desc_addr); +} + +#ifdef DWC_EN_ISOC + +/** + * This function initializes a descriptor chain for Isochronous transfer + * + * @param core_if Programming view of DWC_otg controller. + * @param dwc_ep The EP to start the transfer on. + * + */ +void dwc_otg_iso_ep_start_ddma_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * dwc_ep) +{ + + dsts_data_t dsts = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + volatile uint32_t *addr; + int i, j; + uint32_t len; + + if (dwc_ep->is_in) + dwc_ep->desc_cnt = dwc_ep->buf_proc_intrvl / dwc_ep->bInterval; + else + dwc_ep->desc_cnt = + dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm / + dwc_ep->bInterval; + + /** Allocate descriptors for double buffering */ + dwc_ep->iso_desc_addr = + dwc_otg_ep_alloc_desc_chain(&dwc_ep->iso_dma_desc_addr, + dwc_ep->desc_cnt * 2); + if (dwc_ep->desc_addr) { + DWC_WARN("%s, can't allocate DMA descriptor chain\n", __func__); + return; + } + + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + /** ISO OUT EP */ + if (dwc_ep->is_in == 0) { + dev_dma_desc_sts_t sts = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc = dwc_ep->iso_desc_addr; + dma_addr_t dma_ad; + uint32_t data_per_desc; + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[dwc_ep->num]; + int offset; + + addr = &core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl; + dma_ad = (dma_addr_t) DWC_READ_REG32(&(out_regs->doepdma)); + + /** Buffer 0 descriptors setup */ + dma_ad = dwc_ep->dma_addr0; + + sts.b_iso_out.bs = BS_HOST_READY; + sts.b_iso_out.rxsts = 0; + sts.b_iso_out.l = 0; + sts.b_iso_out.sp = 0; + sts.b_iso_out.ioc = 0; + sts.b_iso_out.pid = 0; + sts.b_iso_out.framenum = 0; + + offset = 0; + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + + for (j = 0; j < dwc_ep->pkt_per_frm; ++j) { + uint32_t len = (j + 1) * dwc_ep->maxpacket; + if (len > dwc_ep->data_per_frame) + data_per_desc = + dwc_ep->data_per_frame - + j * dwc_ep->maxpacket; + else + data_per_desc = dwc_ep->maxpacket; + len = data_per_desc % 4; + if (len) + data_per_desc += 4 - len; + + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + offset += data_per_desc; + dma_desc++; + dma_ad += data_per_desc; + } + } + + for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) { + uint32_t len = (j + 1) * dwc_ep->maxpacket; + if (len > dwc_ep->data_per_frame) + data_per_desc = + dwc_ep->data_per_frame - + j * dwc_ep->maxpacket; + else + data_per_desc = dwc_ep->maxpacket; + len = data_per_desc % 4; + if (len) + data_per_desc += 4 - len; + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + offset += data_per_desc; + dma_desc++; + dma_ad += data_per_desc; + } + + sts.b_iso_out.ioc = 1; + len = (j + 1) * dwc_ep->maxpacket; + if (len > dwc_ep->data_per_frame) + data_per_desc = + dwc_ep->data_per_frame - j * dwc_ep->maxpacket; + else + data_per_desc = dwc_ep->maxpacket; + len = data_per_desc % 4; + if (len) + data_per_desc += 4 - len; + sts.b_iso_out.rxbytes = data_per_desc; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + dma_desc++; + + /** Buffer 1 descriptors setup */ + sts.b_iso_out.ioc = 0; + dma_ad = dwc_ep->dma_addr1; + + offset = 0; + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + for (j = 0; j < dwc_ep->pkt_per_frm; ++j) { + uint32_t len = (j + 1) * dwc_ep->maxpacket; + if (len > dwc_ep->data_per_frame) + data_per_desc = + dwc_ep->data_per_frame - + j * dwc_ep->maxpacket; + else + data_per_desc = dwc_ep->maxpacket; + len = data_per_desc % 4; + if (len) + data_per_desc += 4 - len; + + data_per_desc = + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + offset += data_per_desc; + dma_desc++; + dma_ad += data_per_desc; + } + } + for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) { + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + offset += data_per_desc; + dma_desc++; + dma_ad += data_per_desc; + } + + sts.b_iso_out.ioc = 1; + sts.b_iso_out.l = 1; + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dwc_ep->next_frame = 0; + + /** Write dma_ad into DOEPDMA register */ + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) dwc_ep->iso_dma_desc_addr); + + } + /** ISO IN EP */ + else { + dev_dma_desc_sts_t sts = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc = dwc_ep->iso_desc_addr; + dma_addr_t dma_ad; + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[dwc_ep->num]; + unsigned int frmnumber; + fifosize_data_t txfifosize, rxfifosize; + + txfifosize.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[dwc_ep->num]-> + dtxfsts); + rxfifosize.d32 = + DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); + + addr = &core_if->dev_if->in_ep_regs[dwc_ep->num]->diepctl; + + dma_ad = dwc_ep->dma_addr0; + + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + sts.b_iso_in.bs = BS_HOST_READY; + sts.b_iso_in.txsts = 0; + sts.b_iso_in.sp = + (dwc_ep->data_per_frame % dwc_ep->maxpacket) ? 1 : 0; + sts.b_iso_in.ioc = 0; + sts.b_iso_in.pid = dwc_ep->pkt_per_frm; + + frmnumber = dwc_ep->next_frame; + + sts.b_iso_in.framenum = frmnumber; + sts.b_iso_in.txbytes = dwc_ep->data_per_frame; + sts.b_iso_in.l = 0; + + /** Buffer 0 descriptors setup */ + for (i = 0; i < dwc_ep->desc_cnt - 1; i++) { + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + dma_desc++; + + dma_ad += dwc_ep->data_per_frame; + sts.b_iso_in.framenum += dwc_ep->bInterval; + } + + sts.b_iso_in.ioc = 1; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + ++dma_desc; + + /** Buffer 1 descriptors setup */ + sts.b_iso_in.ioc = 0; + dma_ad = dwc_ep->dma_addr1; + + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + dma_desc++; + + dma_ad += dwc_ep->data_per_frame; + sts.b_iso_in.framenum += dwc_ep->bInterval; + + sts.b_iso_in.ioc = 0; + } + sts.b_iso_in.ioc = 1; + sts.b_iso_in.l = 1; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dwc_ep->next_frame = sts.b_iso_in.framenum + dwc_ep->bInterval; + + /** Write dma_ad into diepdma register */ + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) dwc_ep->iso_dma_desc_addr); + } + /** Enable endpoint, clear nak */ + depctl.d32 = 0; + depctl.b.epena = 1; + depctl.b.usbactep = 1; + depctl.b.cnak = 1; + + DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32); + depctl.d32 = DWC_READ_REG32(addr); +} + +/** + * This function initializes a descriptor chain for Isochronous transfer + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void dwc_otg_iso_ep_start_buf_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep) +{ + depctl_data_t depctl = {.d32 = 0 }; + volatile uint32_t *addr; + + if (ep->is_in) { + addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl; + } else { + addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl; + } + + if (core_if->dma_enable == 0 || core_if->dma_desc_enable != 0) { + return; + } else { + deptsiz_data_t deptsiz = {.d32 = 0 }; + + ep->xfer_len = + ep->data_per_frame * ep->buf_proc_intrvl / ep->bInterval; + ep->pkt_cnt = + (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket; + ep->xfer_count = 0; + ep->xfer_buff = + (ep->proc_buf_num) ? ep->xfer_buff1 : ep->xfer_buff0; + ep->dma_addr = + (ep->proc_buf_num) ? ep->dma_addr1 : ep->dma_addr0; + + if (ep->is_in) { + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + deptsiz.b.mc = ep->pkt_per_frm; + deptsiz.b.xfersize = ep->xfer_len; + deptsiz.b.pktcnt = + (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + dieptsiz, deptsiz.d32); + + /* Write the DMA register */ + DWC_WRITE_REG32(& + (core_if->dev_if->in_ep_regs[ep->num]-> + diepdma), (uint32_t) ep->dma_addr); + + } else { + deptsiz.b.pktcnt = + (ep->xfer_len + (ep->maxpacket - 1)) / + ep->maxpacket; + deptsiz.b.xfersize = deptsiz.b.pktcnt * ep->maxpacket; + + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]-> + doeptsiz, deptsiz.d32); + + /* Write the DMA register */ + DWC_WRITE_REG32(& + (core_if->dev_if->out_ep_regs[ep->num]-> + doepdma), (uint32_t) ep->dma_addr); + + } + /** Enable endpoint, clear nak */ + depctl.d32 = 0; + depctl.b.epena = 1; + depctl.b.cnak = 1; + + DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32); + } +} + +/** + * This function does the setup for a data transfer for an EP and + * starts the transfer. For an IN transfer, the packets will be + * loaded into the appropriate Tx FIFO in the ISR. For OUT transfers, + * the packets are unloaded from the Rx FIFO in the ISR. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + */ + +static void dwc_otg_iso_ep_start_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep) +{ + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + if (ep->is_in) { + ep->desc_cnt = ep->pkt_cnt / ep->pkt_per_frm; + } else { + ep->desc_cnt = ep->pkt_cnt; + } + dwc_otg_iso_ep_start_ddma_transfer(core_if, ep); + } else { + if (core_if->pti_enh_enable) { + dwc_otg_iso_ep_start_buf_transfer(core_if, ep); + } else { + ep->cur_pkt_addr = + (ep->proc_buf_num) ? ep->xfer_buff1 : ep-> + xfer_buff0; + ep->cur_pkt_dma_addr = + (ep->proc_buf_num) ? ep->dma_addr1 : ep-> + dma_addr0; + dwc_otg_iso_ep_start_frm_transfer(core_if, ep); + } + } + } else { + ep->cur_pkt_addr = + (ep->proc_buf_num) ? ep->xfer_buff1 : ep->xfer_buff0; + ep->cur_pkt_dma_addr = + (ep->proc_buf_num) ? ep->dma_addr1 : ep->dma_addr0; + dwc_otg_iso_ep_start_frm_transfer(core_if, ep); + } +} + +/** + * This function stops transfer for an EP and + * resets the ep's variables. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + */ + +void dwc_otg_iso_ep_stop_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl = {.d32 = 0 }; + volatile uint32_t *addr; + + if (ep->is_in == 1) { + addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl; + } else { + addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl; + } + + /* disable the ep */ + depctl.d32 = DWC_READ_REG32(addr); + + depctl.b.epdis = 1; + depctl.b.snak = 1; + + DWC_WRITE_REG32(addr, depctl.d32); + + if (core_if->dma_desc_enable && + ep->iso_desc_addr && ep->iso_dma_desc_addr) { + dwc_otg_ep_free_desc_chain(ep->iso_desc_addr, + ep->iso_dma_desc_addr, + ep->desc_cnt * 2); + } + + /* reset varibales */ + ep->dma_addr0 = 0; + ep->dma_addr1 = 0; + ep->xfer_buff0 = 0; + ep->xfer_buff1 = 0; + ep->data_per_frame = 0; + ep->data_pattern_frame = 0; + ep->sync_frame = 0; + ep->buf_proc_intrvl = 0; + ep->bInterval = 0; + ep->proc_buf_num = 0; + ep->pkt_per_frm = 0; + ep->pkt_per_frm = 0; + ep->desc_cnt = 0; + ep->iso_desc_addr = 0; + ep->iso_dma_desc_addr = 0; +} + +int dwc_otg_pcd_iso_ep_start(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf0, uint8_t * buf1, dwc_dma_t dma0, + dwc_dma_t dma1, int sync_frame, int dp_frame, + int data_per_frame, int start_frame, + int buf_proc_intrvl, void *req_handle, + int atomic_alloc) +{ + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags = 0; + dwc_ep_t *dwc_ep; + int32_t frm_data; + dsts_data_t dsts; + dwc_otg_core_if_t *core_if; + + ep = get_ep_from_handle(pcd, ep_handle); + + if (!ep || !ep->desc || ep->dwc_ep.num == 0) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + core_if = GET_CORE_IF(pcd); + dwc_ep = &ep->dwc_ep; + + if (ep->iso_req_handle) { + DWC_WARN("ISO request in progress\n"); + } + + dwc_ep->dma_addr0 = dma0; + dwc_ep->dma_addr1 = dma1; + + dwc_ep->xfer_buff0 = buf0; + dwc_ep->xfer_buff1 = buf1; + + dwc_ep->data_per_frame = data_per_frame; + + /** @todo - pattern data support is to be implemented in the future */ + dwc_ep->data_pattern_frame = dp_frame; + dwc_ep->sync_frame = sync_frame; + + dwc_ep->buf_proc_intrvl = buf_proc_intrvl; + + dwc_ep->bInterval = 1 << (ep->desc->bInterval - 1); + + dwc_ep->proc_buf_num = 0; + + dwc_ep->pkt_per_frm = 0; + frm_data = ep->dwc_ep.data_per_frame; + while (frm_data > 0) { + dwc_ep->pkt_per_frm++; + frm_data -= ep->dwc_ep.maxpacket; + } + + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + if (start_frame == -1) { + dwc_ep->next_frame = dsts.b.soffn + 1; + if (dwc_ep->bInterval != 1) { + dwc_ep->next_frame = + dwc_ep->next_frame + (dwc_ep->bInterval - 1 - + dwc_ep->next_frame % + dwc_ep->bInterval); + } + } else { + dwc_ep->next_frame = start_frame; + } + + if (!core_if->pti_enh_enable) { + dwc_ep->pkt_cnt = + dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm / + dwc_ep->bInterval; + } else { + dwc_ep->pkt_cnt = + (dwc_ep->data_per_frame * + (dwc_ep->buf_proc_intrvl / dwc_ep->bInterval) + - 1 + dwc_ep->maxpacket) / dwc_ep->maxpacket; + } + + if (core_if->dma_desc_enable) { + dwc_ep->desc_cnt = + dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm / + dwc_ep->bInterval; + } + + if (atomic_alloc) { + dwc_ep->pkt_info = + DWC_ALLOC_ATOMIC(sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt); + } else { + dwc_ep->pkt_info = + DWC_ALLOC(sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt); + } + if (!dwc_ep->pkt_info) { + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return -DWC_E_NO_MEMORY; + } + if (core_if->pti_enh_enable) { + dwc_memset(dwc_ep->pkt_info, 0, + sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt); + } + + dwc_ep->cur_pkt = 0; + ep->iso_req_handle = req_handle; + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + dwc_otg_iso_ep_start_transfer(core_if, dwc_ep); + return 0; +} + +int dwc_otg_pcd_iso_ep_stop(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle) +{ + dwc_irqflags_t flags = 0; + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep || !ep->desc || ep->dwc_ep.num == 0) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + dwc_ep = &ep->dwc_ep; + + dwc_otg_iso_ep_stop_transfer(GET_CORE_IF(pcd), dwc_ep); + + DWC_FREE(dwc_ep->pkt_info); + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + if (ep->iso_req_handle != req_handle) { + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return -DWC_E_INVALID; + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + ep->iso_req_handle = 0; + return 0; +} + +/** + * This function is used for perodical data exchnage between PCD and gadget drivers. + * for Isochronous EPs + * + * - Every time a sync period completes this function is called to + * perform data exchange between PCD and gadget + */ +void dwc_otg_iso_buffer_done(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep, + void *req_handle) +{ + int i; + dwc_ep_t *dwc_ep; + + dwc_ep = &ep->dwc_ep; + + DWC_SPINUNLOCK(ep->pcd->lock); + pcd->fops->isoc_complete(pcd, ep->priv, ep->iso_req_handle, + dwc_ep->proc_buf_num ^ 0x1); + DWC_SPINLOCK(ep->pcd->lock); + + for (i = 0; i < dwc_ep->pkt_cnt; ++i) { + dwc_ep->pkt_info[i].status = 0; + dwc_ep->pkt_info[i].offset = 0; + dwc_ep->pkt_info[i].length = 0; + } +} + +int dwc_otg_pcd_get_iso_packet_count(dwc_otg_pcd_t * pcd, void *ep_handle, + void *iso_req_handle) +{ + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep->desc || ep->dwc_ep.num == 0) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + dwc_ep = &ep->dwc_ep; + + return dwc_ep->pkt_cnt; +} + +void dwc_otg_pcd_get_iso_packet_params(dwc_otg_pcd_t * pcd, void *ep_handle, + void *iso_req_handle, int packet, + int *status, int *actual, int *offset) +{ + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep) + DWC_WARN("bad ep\n"); + + dwc_ep = &ep->dwc_ep; + + *status = dwc_ep->pkt_info[packet].status; + *actual = dwc_ep->pkt_info[packet].length; + *offset = dwc_ep->pkt_info[packet].offset; +} + +#endif /* DWC_EN_ISOC */ + +static void dwc_otg_pcd_init_ep(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * pcd_ep, + uint32_t is_in, uint32_t ep_num) +{ + /* Init EP structure */ + pcd_ep->desc = 0; + pcd_ep->pcd = pcd; + pcd_ep->stopped = 1; + pcd_ep->queue_sof = 0; + + /* Init DWC ep structure */ + pcd_ep->dwc_ep.is_in = is_in; + pcd_ep->dwc_ep.num = ep_num; + pcd_ep->dwc_ep.active = 0; + pcd_ep->dwc_ep.tx_fifo_num = 0; + /* Control until ep is actvated */ + pcd_ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; + pcd_ep->dwc_ep.maxpacket = MAX_PACKET_SIZE; + pcd_ep->dwc_ep.dma_addr = 0; + pcd_ep->dwc_ep.start_xfer_buff = 0; + pcd_ep->dwc_ep.xfer_buff = 0; + pcd_ep->dwc_ep.xfer_len = 0; + pcd_ep->dwc_ep.xfer_count = 0; + pcd_ep->dwc_ep.sent_zlp = 0; + pcd_ep->dwc_ep.total_len = 0; + pcd_ep->dwc_ep.desc_addr = + dwc_otg_ep_alloc_desc_chain( + &pcd_ep->dwc_ep.dma_desc_addr, + MAX_DMA_DESC_CNT); + if (!pcd_ep->dwc_ep.desc_addr) + DWC_WARN("%s, can't allocate DMA descriptor\n", + __func__); + DWC_CIRCLEQ_INIT(&pcd_ep->queue); +} + +/** + * Initialize ep's + */ +static void dwc_otg_pcd_reinit(dwc_otg_pcd_t * pcd) +{ + int i; + uint32_t hwcfg1; + dwc_otg_pcd_ep_t *ep; + int in_ep_cntr, out_ep_cntr; + uint32_t num_in_eps = (GET_CORE_IF(pcd))->dev_if->num_in_eps; + uint32_t num_out_eps = (GET_CORE_IF(pcd))->dev_if->num_out_eps; + + /** + * Initialize the EP0 structure. + */ + ep = &pcd->ep0; + dwc_otg_pcd_init_ep(pcd, ep, 0, 0); + + in_ep_cntr = 0; + hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1.d32 >> 3; + for (i = 1; in_ep_cntr < num_in_eps; i++) { + if ((hwcfg1 & 0x1) == 0) { + dwc_otg_pcd_ep_t *ep = &pcd->in_ep[in_ep_cntr]; + in_ep_cntr++; + /** + * @todo NGS: Add direction to EP, based on contents + * of HWCFG1. Need a copy of HWCFG1 in pcd structure? + * sprintf(";r + */ + dwc_otg_pcd_init_ep(pcd, ep, 1 /* IN */ , i); + + DWC_CIRCLEQ_INIT(&ep->queue); + } + hwcfg1 >>= 2; + } + + out_ep_cntr = 0; + hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1.d32 >> 2; + for (i = 1; out_ep_cntr < num_out_eps; i++) { + if ((hwcfg1 & 0x1) == 0) { + dwc_otg_pcd_ep_t *ep = &pcd->out_ep[out_ep_cntr]; + out_ep_cntr++; + /** + * @todo NGS: Add direction to EP, based on contents + * of HWCFG1. Need a copy of HWCFG1 in pcd structure? + * sprintf(";r + */ + dwc_otg_pcd_init_ep(pcd, ep, 0 /* OUT */ , i); + DWC_CIRCLEQ_INIT(&ep->queue); + } + hwcfg1 >>= 2; + } + + pcd->ep0state = EP0_DISCONNECT; + pcd->ep0.dwc_ep.maxpacket = MAX_EP0_SIZE; + pcd->ep0.dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; +} + +/** + * This function is called when the SRP timer expires. The SRP should + * complete within 6 seconds. + */ +static void srp_timeout(void *ptr) +{ + gotgctl_data_t gotgctl; + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr; + volatile uint32_t *addr = &core_if->core_global_regs->gotgctl; + + gotgctl.d32 = DWC_READ_REG32(addr); + + core_if->srp_timer_started = 0; + + if (core_if->adp_enable) { + if (gotgctl.b.bsesvld == 0) { + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + DWC_PRINTF("SRP Timeout BSESSVLD = 0\n"); + /* Power off the core */ + if (core_if->power_down == 2) { + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gpwrdn, + gpwrdn.d32, 0); + } + + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, + gpwrdn.d32); + dwc_otg_adp_probe_start(core_if); + } else { + DWC_PRINTF("SRP Timeout BSESSVLD = 1\n"); + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } + } + + if ((core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) && + (core_if->core_params->i2c_enable)) { + DWC_PRINTF("SRP Timeout\n"); + + if ((core_if->srp_success) && (gotgctl.b.bsesvld)) { + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } + + /* Clear Session Request */ + gotgctl.d32 = 0; + gotgctl.b.sesreq = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gotgctl, + gotgctl.d32, 0); + + core_if->srp_success = 0; + } else { + __DWC_ERROR("Device not connected/responding\n"); + gotgctl.b.sesreq = 0; + DWC_WRITE_REG32(addr, gotgctl.d32); + } + } else if (gotgctl.b.sesreq) { + DWC_PRINTF("SRP Timeout\n"); + + __DWC_ERROR("Device not connected/responding\n"); + gotgctl.b.sesreq = 0; + DWC_WRITE_REG32(addr, gotgctl.d32); + } else { + DWC_PRINTF(" SRP GOTGCTL=%0x\n", gotgctl.d32); + } +} + +/** + * Tasklet + * + */ +extern void start_next_request(dwc_otg_pcd_ep_t * ep); + +static void start_xfer_tasklet_func(void *data) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) data; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + int i; + depctl_data_t diepctl; + + DWC_DEBUGPL(DBG_PCDV, "Start xfer tasklet\n"); + + diepctl.d32 = DWC_READ_REG32(&core_if->dev_if->in_ep_regs[0]->diepctl); + + if (pcd->ep0.queue_sof) { + pcd->ep0.queue_sof = 0; + start_next_request(&pcd->ep0); + // break; + } + + for (i = 0; i < core_if->dev_if->num_in_eps; i++) { + depctl_data_t diepctl; + diepctl.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl); + + if (pcd->in_ep[i].queue_sof) { + pcd->in_ep[i].queue_sof = 0; + start_next_request(&pcd->in_ep[i]); + // break; + } + } + + return; +} + +/** + * This function initialized the PCD portion of the driver. + * + */ +dwc_otg_pcd_t *dwc_otg_pcd_init(dwc_otg_core_if_t * core_if) +{ + dwc_otg_pcd_t *pcd = NULL; + dwc_otg_dev_if_t *dev_if; + int i; + + /* + * Allocate PCD structure + */ + pcd = DWC_ALLOC(sizeof(dwc_otg_pcd_t)); + + if (pcd == NULL) { + return NULL; + } + + pcd->lock = DWC_SPINLOCK_ALLOC(); + if (!pcd->lock) { + DWC_ERROR("Could not allocate lock for pcd"); + DWC_FREE(pcd); + return NULL; + } + /* Set core_if's lock pointer to hcd->lock */ + core_if->lock = pcd->lock; + pcd->core_if = core_if; + + dev_if = core_if->dev_if; + dev_if->isoc_ep = NULL; + + if (core_if->hwcfg4.b.ded_fifo_en) { + DWC_PRINTF("Dedicated Tx FIFOs mode\n"); + } else { + DWC_PRINTF("Shared Tx FIFO mode\n"); + } + + /* + * Initialized the Core for Device mode here if there is nod ADP support. + * Otherwise it will be done later in dwc_otg_adp_start routine. + */ + if (dwc_otg_is_device_mode(core_if) /*&& !core_if->adp_enable */ ) { + dwc_otg_core_dev_init(core_if); + } + + /* + * Register the PCD Callbacks. + */ + dwc_otg_cil_register_pcd_callbacks(core_if, &pcd_callbacks, pcd); + + /* + * Initialize the DMA buffer for SETUP packets + */ + if (GET_CORE_IF(pcd)->dma_enable) { + pcd->setup_pkt = + DWC_DMA_ALLOC(get_gadget_wrapper_device(),sizeof(*pcd->setup_pkt) * 5, + &pcd->setup_pkt_dma_handle); + if (pcd->setup_pkt == NULL) { + DWC_FREE(pcd); + return NULL; + } + + pcd->status_buf = + DWC_DMA_ALLOC(get_gadget_wrapper_device(),sizeof(uint16_t), + &pcd->status_buf_dma_handle); + if (pcd->status_buf == NULL) { + DWC_DMA_FREE(get_gadget_wrapper_device(),sizeof(*pcd->setup_pkt) * 5, + pcd->setup_pkt, pcd->setup_pkt_dma_handle); + DWC_FREE(pcd); + return NULL; + } + + if (GET_CORE_IF(pcd)->dma_desc_enable) { + dev_if->setup_desc_addr[0] = + dwc_otg_ep_alloc_desc_chain + (&dev_if->dma_setup_desc_addr[0], 1); + dev_if->setup_desc_addr[1] = + dwc_otg_ep_alloc_desc_chain + (&dev_if->dma_setup_desc_addr[1], 1); + dev_if->in_desc_addr = + dwc_otg_ep_alloc_desc_chain + (&dev_if->dma_in_desc_addr, 1); + dev_if->out_desc_addr = + dwc_otg_ep_alloc_desc_chain + (&dev_if->dma_out_desc_addr, 1); + pcd->data_terminated = 0; + + if (dev_if->setup_desc_addr[0] == 0 + || dev_if->setup_desc_addr[1] == 0 + || dev_if->in_desc_addr == 0 + || dev_if->out_desc_addr == 0) { + + if (dev_if->out_desc_addr) + dwc_otg_ep_free_desc_chain + (dev_if->out_desc_addr, + dev_if->dma_out_desc_addr, 1); + if (dev_if->in_desc_addr) + dwc_otg_ep_free_desc_chain + (dev_if->in_desc_addr, + dev_if->dma_in_desc_addr, 1); + if (dev_if->setup_desc_addr[1]) + dwc_otg_ep_free_desc_chain + (dev_if->setup_desc_addr[1], + dev_if->dma_setup_desc_addr[1], 1); + if (dev_if->setup_desc_addr[0]) + dwc_otg_ep_free_desc_chain + (dev_if->setup_desc_addr[0], + dev_if->dma_setup_desc_addr[0], 1); + + DWC_DMA_FREE(get_gadget_wrapper_device(),sizeof(*pcd->setup_pkt) * 5, + pcd->setup_pkt, + pcd->setup_pkt_dma_handle); + DWC_DMA_FREE(get_gadget_wrapper_device(),sizeof(*pcd->status_buf), + pcd->status_buf, + pcd->status_buf_dma_handle); + + DWC_FREE(pcd); + + return NULL; + } + } + } else { + pcd->setup_pkt = DWC_ALLOC(sizeof(*pcd->setup_pkt) * 5); + if (pcd->setup_pkt == NULL) { + DWC_FREE(pcd); + return NULL; + } + + pcd->status_buf = DWC_ALLOC(sizeof(uint16_t)); + if (pcd->status_buf == NULL) { + DWC_FREE(pcd->setup_pkt); + DWC_FREE(pcd); + return NULL; + } + } + + dwc_otg_pcd_reinit(pcd); + + /* Allocate the cfi object for the PCD */ +#ifdef DWC_UTE_CFI + pcd->cfi = DWC_ALLOC(sizeof(cfiobject_t)); + if (NULL == pcd->cfi) + goto fail; + if (init_cfi(pcd->cfi)) { + CFI_INFO("%s: Failed to init the CFI object\n", __func__); + goto fail; + } +#endif + + /* Initialize tasklets */ + pcd->start_xfer_tasklet = DWC_TASK_ALLOC("xfer_tasklet", + start_xfer_tasklet_func, pcd); + pcd->test_mode_tasklet = DWC_TASK_ALLOC("test_mode_tasklet", + do_test_mode, pcd); + + /* Initialize SRP timer */ + core_if->srp_timer = DWC_TIMER_ALLOC("SRP TIMER", srp_timeout, core_if); + + if (core_if->core_params->dev_out_nak) { + /** + * Initialize xfer timeout timer. Implemented for + * 2.93a feature "Device DDMA OUT NAK Enhancement" + */ + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + pcd->core_if->ep_xfer_timer[i] = + DWC_TIMER_ALLOC("ep timer", ep_xfer_timeout, + &pcd->core_if->ep_xfer_info[i]); + } + } + + return pcd; +#ifdef DWC_UTE_CFI +fail: + if (pcd->setup_pkt) + DWC_FREE(pcd->setup_pkt); + if (pcd->status_buf) + DWC_FREE(pcd->status_buf); + if (pcd->cfi) + DWC_FREE(pcd->cfi); + if (pcd) + DWC_FREE(pcd); + return NULL; +#endif + +} + +/** + * Remove PCD specific data + */ +void dwc_otg_pcd_remove(dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if; + int i; + if (pcd->core_if->core_params->dev_out_nak) { + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[i]); + pcd->core_if->ep_xfer_info[i].state = 0; + } + } + + if (GET_CORE_IF(pcd)->dma_enable) { + DWC_DMA_FREE(get_gadget_wrapper_device(),sizeof(*pcd->setup_pkt) * 5, pcd->setup_pkt, + pcd->setup_pkt_dma_handle); + DWC_DMA_FREE(get_gadget_wrapper_device(),sizeof(uint16_t), pcd->status_buf, + pcd->status_buf_dma_handle); + if (GET_CORE_IF(pcd)->dma_desc_enable) { + dwc_otg_ep_free_desc_chain(dev_if->setup_desc_addr[0], + dev_if->dma_setup_desc_addr + [0], 1); + dwc_otg_ep_free_desc_chain(dev_if->setup_desc_addr[1], + dev_if->dma_setup_desc_addr + [1], 1); + dwc_otg_ep_free_desc_chain(dev_if->in_desc_addr, + dev_if->dma_in_desc_addr, 1); + dwc_otg_ep_free_desc_chain(dev_if->out_desc_addr, + dev_if->dma_out_desc_addr, + 1); + } + } else { + DWC_FREE(pcd->setup_pkt); + DWC_FREE(pcd->status_buf); + } + DWC_SPINLOCK_FREE(pcd->lock); + /* Set core_if's lock pointer to NULL */ + pcd->core_if->lock = NULL; + + DWC_TASK_FREE(pcd->start_xfer_tasklet); + DWC_TASK_FREE(pcd->test_mode_tasklet); + if (pcd->core_if->core_params->dev_out_nak) { + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + if (pcd->core_if->ep_xfer_timer[i]) { + DWC_TIMER_FREE(pcd->core_if->ep_xfer_timer[i]); + } + } + } + +/* Release the CFI object's dynamic memory */ +#ifdef DWC_UTE_CFI + if (pcd->cfi->ops.release) { + pcd->cfi->ops.release(pcd->cfi); + } +#endif + + DWC_FREE(pcd); +} + +/** + * Returns whether registered pcd is dual speed or not + */ +uint32_t dwc_otg_pcd_is_dualspeed(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + if ((core_if->core_params->speed == DWC_SPEED_PARAM_FULL) || + ((core_if->hwcfg2.b.hs_phy_type == 2) && + (core_if->hwcfg2.b.fs_phy_type == 1) && + (core_if->core_params->ulpi_fs_ls))) { + return 0; + } + + return 1; +} + +/** + * Returns whether registered pcd is OTG capable or not + */ +uint32_t dwc_otg_pcd_is_otg(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + gusbcfg_data_t usbcfg = {.d32 = 0 }; + + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + if (!usbcfg.b.srpcap || !usbcfg.b.hnpcap) { + return 0; + } + + return 1; +} + +/** + * This function assigns periodic Tx FIFO to an periodic EP + * in shared Tx FIFO mode + */ +static uint32_t assign_tx_fifo(dwc_otg_core_if_t * core_if) +{ + uint32_t TxMsk = 1; + int i; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; ++i) { + if ((TxMsk & core_if->tx_msk) == 0) { + core_if->tx_msk |= TxMsk; + return i + 1; + } + TxMsk <<= 1; + } + return 0; +} + +/** + * This function assigns periodic Tx FIFO to an periodic EP + * in shared Tx FIFO mode + */ +static uint32_t assign_perio_tx_fifo(dwc_otg_core_if_t * core_if) +{ + uint32_t PerTxMsk = 1; + int i; + for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; ++i) { + if ((PerTxMsk & core_if->p_tx_msk) == 0) { + core_if->p_tx_msk |= PerTxMsk; + return i + 1; + } + PerTxMsk <<= 1; + } + return 0; +} + +/** + * This function releases periodic Tx FIFO + * in shared Tx FIFO mode + */ +static void release_perio_tx_fifo(dwc_otg_core_if_t * core_if, + uint32_t fifo_num) +{ + core_if->p_tx_msk = + (core_if->p_tx_msk & (1 << (fifo_num - 1))) ^ core_if->p_tx_msk; +} + +/** + * This function releases periodic Tx FIFO + * in shared Tx FIFO mode + */ +static void release_tx_fifo(dwc_otg_core_if_t * core_if, uint32_t fifo_num) +{ + core_if->tx_msk = + (core_if->tx_msk & (1 << (fifo_num - 1))) ^ core_if->tx_msk; +} + +/** + * This function is being called from gadget + * to enable PCD endpoint. + */ +int dwc_otg_pcd_ep_enable(dwc_otg_pcd_t * pcd, + const uint8_t * ep_desc, void *usb_ep) +{ + int num, dir; + dwc_otg_pcd_ep_t *ep = NULL; + const usb_endpoint_descriptor_t *desc; + dwc_irqflags_t flags; + int retval = 0; + int i, epcount; + + desc = (const usb_endpoint_descriptor_t *)ep_desc; + + if (!desc) { + pcd->ep0.priv = usb_ep; + ep = &pcd->ep0; + retval = -DWC_E_INVALID; + goto out; + } + + num = UE_GET_ADDR(desc->bEndpointAddress); + dir = UE_GET_DIR(desc->bEndpointAddress); + + if (!desc->wMaxPacketSize) { + DWC_WARN("bad maxpacketsize\n"); + retval = -DWC_E_INVALID; + goto out; + } + + if (dir == UE_DIR_IN) { + epcount = pcd->core_if->dev_if->num_in_eps; + for (i = 0; i < epcount; i++) { + if (num == pcd->in_ep[i].dwc_ep.num) { + ep = &pcd->in_ep[i]; + break; + } + } + } else { + epcount = pcd->core_if->dev_if->num_out_eps; + for (i = 0; i < epcount; i++) { + if (num == pcd->out_ep[i].dwc_ep.num) { + ep = &pcd->out_ep[i]; + break; + } + } + } + + if (!ep) { + DWC_WARN("bad address\n"); + retval = -DWC_E_INVALID; + goto out; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + ep->desc = desc; + ep->priv = usb_ep; + + /* + * Activate the EP + */ + ep->stopped = 0; + + ep->dwc_ep.is_in = (dir == UE_DIR_IN); + ep->dwc_ep.maxpacket = UGETW(desc->wMaxPacketSize); + + ep->dwc_ep.type = desc->bmAttributes & UE_XFERTYPE; + + if (ep->dwc_ep.is_in) { + if (!GET_CORE_IF(pcd)->en_multiple_tx_fifo) { + ep->dwc_ep.tx_fifo_num = 0; + + if (ep->dwc_ep.type == UE_ISOCHRONOUS) { + /* + * if ISOC EP then assign a Periodic Tx FIFO. + */ + ep->dwc_ep.tx_fifo_num = + assign_perio_tx_fifo(GET_CORE_IF(pcd)); + } + } else { + /* + * if Dedicated FIFOs mode is on then assign a Tx FIFO. + */ + ep->dwc_ep.tx_fifo_num = + assign_tx_fifo(GET_CORE_IF(pcd)); + } + } + /* Set initial data PID. */ + if (ep->dwc_ep.type == UE_BULK) { + ep->dwc_ep.data_pid_start = 0; + } + + /* Alloc DMA Descriptors */ + /* The allocation is moved to dwc_otg_pcd_init_ep + if (GET_CORE_IF(pcd)->dma_desc_enable) { +#ifndef DWC_UTE_PER_IO + if (ep->dwc_ep.type != UE_ISOCHRONOUS) { +#endif + ep->dwc_ep.desc_addr = + dwc_otg_ep_alloc_desc_chain(&ep-> + dwc_ep.dma_desc_addr, + MAX_DMA_DESC_CNT); + if (!ep->dwc_ep.desc_addr) { + DWC_WARN("%s, can't allocate DMA descriptor\n", + __func__); + retval = -DWC_E_SHUTDOWN; + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + goto out; + } +#ifndef DWC_UTE_PER_IO + } +#endif + } + */ + DWC_DEBUGPL(DBG_PCD, "Activate %s: type=%d, mps=%d desc=%p\n", + (ep->dwc_ep.is_in ? "IN" : "OUT"), + ep->dwc_ep.type, ep->dwc_ep.maxpacket, ep->desc); +#ifdef DWC_UTE_PER_IO + ep->dwc_ep.xiso_bInterval = 1 << (ep->desc->bInterval - 1); +#endif + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + ep->dwc_ep.bInterval = 1 << (ep->desc->bInterval - 1); + ep->dwc_ep.frame_num = 0xFFFFFFFF; + } + + dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep); + +#ifdef DWC_UTE_CFI + if (pcd->cfi->ops.ep_enable) { + pcd->cfi->ops.ep_enable(pcd->cfi, pcd, ep); + } +#endif + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + +out: + return retval; +} + +/** + * This function is being called from gadget + * to disable PCD endpoint. + */ +int dwc_otg_pcd_ep_disable(dwc_otg_pcd_t * pcd, void *ep_handle) +{ + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags; + /* + dwc_otg_dev_dma_desc_t *desc_addr; + dwc_dma_t dma_desc_addr; + */ + ep = get_ep_from_handle(pcd, ep_handle); + + if (!ep || !ep->desc) { + DWC_DEBUGPL(DBG_PCD, "bad ep address\n"); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + dwc_otg_request_nuke(ep); + + dwc_otg_ep_deactivate(GET_CORE_IF(pcd), &ep->dwc_ep); + if (pcd->core_if->core_params->dev_out_nak) { + DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[ep->dwc_ep.num]); + pcd->core_if->ep_xfer_info[ep->dwc_ep.num].state = 0; + } + ep->desc = NULL; + ep->stopped = 1; + + if (ep->dwc_ep.is_in) { + dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd), ep->dwc_ep.tx_fifo_num); + release_perio_tx_fifo(GET_CORE_IF(pcd), ep->dwc_ep.tx_fifo_num); + release_tx_fifo(GET_CORE_IF(pcd), ep->dwc_ep.tx_fifo_num); + } + + /* Free DMA Descriptors */ + /* desc_addr would not be freed in Kernel lifetime. + if (GET_CORE_IF(pcd)->dma_desc_enable) { + if (ep->dwc_ep.type != UE_ISOCHRONOUS) { + desc_addr = ep->dwc_ep.desc_addr; + dma_desc_addr = ep->dwc_ep.dma_desc_addr; + + // Cannot call dma_free_coherent() with IRQs disabled + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + dwc_otg_ep_free_desc_chain(desc_addr, dma_desc_addr, + MAX_DMA_DESC_CNT); + + goto out_unlocked; + } + } + */ + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); +/* +out_unlocked: +*/ + DWC_DEBUGPL(DBG_PCD, "%d %s disabled\n", ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT"); + return 0; + +} + +/******************************************************************************/ +#ifdef DWC_UTE_PER_IO + +/** + * Free the request and its extended parts + * + */ +void dwc_pcd_xiso_ereq_free(dwc_otg_pcd_ep_t * ep, dwc_otg_pcd_request_t * req) +{ + DWC_FREE(req->ext_req.per_io_frame_descs); + DWC_FREE(req); +} + +/** + * Start the next request in the endpoint's queue. + * + */ +int dwc_otg_pcd_xiso_start_next_request(dwc_otg_pcd_t * pcd, + dwc_otg_pcd_ep_t * ep) +{ + int i; + dwc_otg_pcd_request_t *req = NULL; + dwc_ep_t *dwcep = NULL; + struct dwc_iso_xreq_port *ereq = NULL; + struct dwc_iso_pkt_desc_port *ddesc_iso; + uint16_t nat; + depctl_data_t diepctl; + + dwcep = &ep->dwc_ep; + + if (dwcep->xiso_active_xfers > 0) { +#if 0 //Disable this to decrease s/w overhead that is crucial for Isoc transfers + DWC_WARN("There are currently active transfers for EP%d \ + (active=%d; queued=%d)", dwcep->num, dwcep->xiso_active_xfers, + dwcep->xiso_queued_xfers); +#endif + return 0; + } + + nat = UGETW(ep->desc->wMaxPacketSize); + nat = (nat >> 11) & 0x03; + + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + ereq = &req->ext_req; + ep->stopped = 0; + + /* Get the frame number */ + dwcep->xiso_frame_num = + dwc_otg_get_frame_number(GET_CORE_IF(pcd)); + DWC_DEBUG("FRM_NUM=%d", dwcep->xiso_frame_num); + + ddesc_iso = ereq->per_io_frame_descs; + + if (dwcep->is_in) { + /* Setup DMA Descriptor chain for IN Isoc request */ + for (i = 0; i < ereq->pio_pkt_count; i++) { + //if ((i % (nat + 1)) == 0) + if (i > 0) + dwcep->xiso_frame_num = + (dwcep->xiso_bInterval + + dwcep->xiso_frame_num) & 0x3FFF; + dwcep->desc_addr[i].buf = + req->dma + ddesc_iso[i].offset; + dwcep->desc_addr[i].status.b_iso_in.txbytes = + ddesc_iso[i].length; + dwcep->desc_addr[i].status.b_iso_in.framenum = + dwcep->xiso_frame_num; + dwcep->desc_addr[i].status.b_iso_in.bs = + BS_HOST_READY; + dwcep->desc_addr[i].status.b_iso_in.txsts = 0; + dwcep->desc_addr[i].status.b_iso_in.sp = + (ddesc_iso[i].length % + dwcep->maxpacket) ? 1 : 0; + dwcep->desc_addr[i].status.b_iso_in.ioc = 0; + dwcep->desc_addr[i].status.b_iso_in.pid = nat + 1; + dwcep->desc_addr[i].status.b_iso_in.l = 0; + + /* Process the last descriptor */ + if (i == ereq->pio_pkt_count - 1) { + dwcep->desc_addr[i].status.b_iso_in.ioc = 1; + dwcep->desc_addr[i].status.b_iso_in.l = 1; + } + } + + /* Setup and start the transfer for this endpoint */ + dwcep->xiso_active_xfers++; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->dev_if-> + in_ep_regs[dwcep->num]->diepdma, + dwcep->dma_desc_addr); + diepctl.d32 = 0; + diepctl.b.epena = 1; + diepctl.b.cnak = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->dev_if-> + in_ep_regs[dwcep->num]->diepctl, 0, + diepctl.d32); + } else { + /* Setup DMA Descriptor chain for OUT Isoc request */ + for (i = 0; i < ereq->pio_pkt_count; i++) { + //if ((i % (nat + 1)) == 0) + dwcep->xiso_frame_num = (dwcep->xiso_bInterval + + dwcep->xiso_frame_num) & 0x3FFF; + dwcep->desc_addr[i].buf = + req->dma + ddesc_iso[i].offset; + dwcep->desc_addr[i].status.b_iso_out.rxbytes = + ddesc_iso[i].length; + dwcep->desc_addr[i].status.b_iso_out.framenum = + dwcep->xiso_frame_num; + dwcep->desc_addr[i].status.b_iso_out.bs = + BS_HOST_READY; + dwcep->desc_addr[i].status.b_iso_out.rxsts = 0; + dwcep->desc_addr[i].status.b_iso_out.sp = + (ddesc_iso[i].length % + dwcep->maxpacket) ? 1 : 0; + dwcep->desc_addr[i].status.b_iso_out.ioc = 0; + dwcep->desc_addr[i].status.b_iso_out.pid = nat + 1; + dwcep->desc_addr[i].status.b_iso_out.l = 0; + + /* Process the last descriptor */ + if (i == ereq->pio_pkt_count - 1) { + dwcep->desc_addr[i].status.b_iso_out.ioc = 1; + dwcep->desc_addr[i].status.b_iso_out.l = 1; + } + } + + /* Setup and start the transfer for this endpoint */ + dwcep->xiso_active_xfers++; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)-> + dev_if->out_ep_regs[dwcep->num]-> + doepdma, dwcep->dma_desc_addr); + diepctl.d32 = 0; + diepctl.b.epena = 1; + diepctl.b.cnak = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)-> + dev_if->out_ep_regs[dwcep->num]-> + doepctl, 0, diepctl.d32); + } + + } else { + ep->stopped = 1; + } + + return 0; +} + +/** + * - Remove the request from the queue + */ +void complete_xiso_ep(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_pcd_request_t *req = NULL; + struct dwc_iso_xreq_port *ereq = NULL; + struct dwc_iso_pkt_desc_port *ddesc_iso = NULL; + dwc_ep_t *dwcep = NULL; + int i; + + //DWC_DEBUG(); + dwcep = &ep->dwc_ep; + + /* Get the first pending request from the queue */ + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (!req) { + DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep); + return; + } + dwcep->xiso_active_xfers--; + dwcep->xiso_queued_xfers--; + /* Remove this request from the queue */ + DWC_CIRCLEQ_REMOVE_INIT(&ep->queue, req, queue_entry); + } else { + DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep); + return; + } + + ep->stopped = 1; + ereq = &req->ext_req; + ddesc_iso = ereq->per_io_frame_descs; + + if (dwcep->xiso_active_xfers < 0) { + DWC_WARN("EP#%d (xiso_active_xfers=%d)", dwcep->num, + dwcep->xiso_active_xfers); + } + + /* Fill the Isoc descs of portable extended req from dma descriptors */ + for (i = 0; i < ereq->pio_pkt_count; i++) { + if (dwcep->is_in) { /* IN endpoints */ + ddesc_iso[i].actual_length = ddesc_iso[i].length - + dwcep->desc_addr[i].status.b_iso_in.txbytes; + ddesc_iso[i].status = + dwcep->desc_addr[i].status.b_iso_in.txsts; + } else { /* OUT endpoints */ + ddesc_iso[i].actual_length = ddesc_iso[i].length - + dwcep->desc_addr[i].status.b_iso_out.rxbytes; + ddesc_iso[i].status = + dwcep->desc_addr[i].status.b_iso_out.rxsts; + } + } + + DWC_SPINUNLOCK(ep->pcd->lock); + + /* Call the completion function in the non-portable logic */ + ep->pcd->fops->xisoc_complete(ep->pcd, ep->priv, req->priv, 0, + &req->ext_req); + + DWC_SPINLOCK(ep->pcd->lock); + + /* Free the request - specific freeing needed for extended request object */ + dwc_pcd_xiso_ereq_free(ep, req); + + /* Start the next request */ + dwc_otg_pcd_xiso_start_next_request(ep->pcd, ep); + + return; +} + +/** + * Create and initialize the Isoc pkt descriptors of the extended request. + * + */ +static int dwc_otg_pcd_xiso_create_pkt_descs(dwc_otg_pcd_request_t * req, + void *ereq_nonport, + int atomic_alloc) +{ + struct dwc_iso_xreq_port *ereq = NULL; + struct dwc_iso_xreq_port *req_mapped = NULL; + struct dwc_iso_pkt_desc_port *ipds = NULL; /* To be created in this function */ + uint32_t pkt_count; + int i; + + ereq = &req->ext_req; + req_mapped = (struct dwc_iso_xreq_port *)ereq_nonport; + pkt_count = req_mapped->pio_pkt_count; + + /* Create the isoc descs */ + if (atomic_alloc) { + ipds = DWC_ALLOC_ATOMIC(sizeof(*ipds) * pkt_count); + } else { + ipds = DWC_ALLOC(sizeof(*ipds) * pkt_count); + } + + if (!ipds) { + DWC_ERROR("Failed to allocate isoc descriptors"); + return -DWC_E_NO_MEMORY; + } + + /* Initialize the extended request fields */ + ereq->per_io_frame_descs = ipds; + ereq->error_count = 0; + ereq->pio_alloc_pkt_count = pkt_count; + ereq->pio_pkt_count = pkt_count; + ereq->tr_sub_flags = req_mapped->tr_sub_flags; + + /* Init the Isoc descriptors */ + for (i = 0; i < pkt_count; i++) { + ipds[i].length = req_mapped->per_io_frame_descs[i].length; + ipds[i].offset = req_mapped->per_io_frame_descs[i].offset; + ipds[i].status = req_mapped->per_io_frame_descs[i].status; /* 0 */ + ipds[i].actual_length = + req_mapped->per_io_frame_descs[i].actual_length; + } + + return 0; +} + +static void prn_ext_request(struct dwc_iso_xreq_port *ereq) +{ + struct dwc_iso_pkt_desc_port *xfd = NULL; + int i; + + DWC_DEBUG("per_io_frame_descs=%p", ereq->per_io_frame_descs); + DWC_DEBUG("tr_sub_flags=%d", ereq->tr_sub_flags); + DWC_DEBUG("error_count=%d", ereq->error_count); + DWC_DEBUG("pio_alloc_pkt_count=%d", ereq->pio_alloc_pkt_count); + DWC_DEBUG("pio_pkt_count=%d", ereq->pio_pkt_count); + DWC_DEBUG("res=%d", ereq->res); + + for (i = 0; i < ereq->pio_pkt_count; i++) { + xfd = &ereq->per_io_frame_descs[0]; + DWC_DEBUG("FD #%d", i); + + DWC_DEBUG("xfd->actual_length=%d", xfd->actual_length); + DWC_DEBUG("xfd->length=%d", xfd->length); + DWC_DEBUG("xfd->offset=%d", xfd->offset); + DWC_DEBUG("xfd->status=%d", xfd->status); + } +} + +/** + * + */ +int dwc_otg_pcd_xiso_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf, dwc_dma_t dma_buf, uint32_t buflen, + int zero, void *req_handle, int atomic_alloc, + void *ereq_nonport) +{ + dwc_otg_pcd_request_t *req = NULL; + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags; + int res; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + + /* We support this extension only for DDMA mode */ + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) + if (!GET_CORE_IF(pcd)->dma_desc_enable) + return -DWC_E_INVALID; + + /* Create a dwc_otg_pcd_request_t object */ + if (atomic_alloc) { + req = DWC_ALLOC_ATOMIC(sizeof(*req)); + } else { + req = DWC_ALLOC(sizeof(*req)); + } + + if (!req) { + return -DWC_E_NO_MEMORY; + } + + /* Create the Isoc descs for this request which shall be the exact match + * of the structure sent to us from the non-portable logic */ + res = + dwc_otg_pcd_xiso_create_pkt_descs(req, ereq_nonport, atomic_alloc); + if (res) { + DWC_WARN("Failed to init the Isoc descriptors"); + DWC_FREE(req); + return res; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + DWC_CIRCLEQ_INIT_ENTRY(req, queue_entry); + req->buf = buf; + req->dma = dma_buf; + req->length = buflen; + req->sent_zlp = zero; + req->priv = req_handle; + + //DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + ep->dwc_ep.dma_addr = dma_buf; + ep->dwc_ep.start_xfer_buff = buf; + ep->dwc_ep.xfer_buff = buf; + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = buflen; + + /* Add this request to the tail */ + DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry); + ep->dwc_ep.xiso_queued_xfers++; + +//DWC_DEBUG("CP_0"); +//DWC_DEBUG("req->ext_req.tr_sub_flags=%d", req->ext_req.tr_sub_flags); +//prn_ext_request((struct dwc_iso_xreq_port *) ereq_nonport); +//prn_ext_request(&req->ext_req); + + //DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + /* If the req->status == ASAP then check if there is any active transfer + * for this endpoint. If no active transfers, then get the first entry + * from the queue and start that transfer + */ + if (req->ext_req.tr_sub_flags == DWC_EREQ_TF_ASAP) { + res = dwc_otg_pcd_xiso_start_next_request(pcd, ep); + if (res) { + DWC_WARN("Failed to start the next Isoc transfer"); + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + DWC_FREE(req); + return res; + } + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return 0; +} + +#endif +/* END ifdef DWC_UTE_PER_IO ***************************************************/ + +/** + ** Change log: Add Scatter/Gather DMA Mode Transfer Implement + ** Author: Miao.Zhu + ** Date: 10.16th.2014 + ** It's a workaround for the purpose of backward compatibility to buffer DMA mode + ** + ** As seen the definition of struct usb_request, We use as a flag of + ** whether scatter buffer list is employed in this request. + ** If equals ZERO, will be interpreted as buffer address of + ** data to be transfered. + ** If is greater than ZERO, wil be interpreted as the base address + ** of a buffer list. + ** means buffer number. + ** denotes total length of this transfer. + **/ +int dwc_otg_pcd_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf, dwc_dma_t dma_buf, uint32_t buflen, + int zero, uint32_t num_sgs, struct scatterlist *sg, + void *req_handle, int atomic_alloc) +{ + dwc_irqflags_t flags; + dwc_otg_pcd_request_t *req; + dwc_otg_pcd_ep_t *ep; + uint32_t max_transfer; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep || (!ep->desc && ep->dwc_ep.num != 0)) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + + if (atomic_alloc) { + req = DWC_ALLOC_ATOMIC(sizeof(*req)); + } else { + req = DWC_ALLOC(sizeof(*req)); + } + + if (!req) { + return -DWC_E_NO_MEMORY; + } + DWC_CIRCLEQ_INIT_ENTRY(req, queue_entry); + if (!GET_CORE_IF(pcd)->core_params->opt) { + if (ep->dwc_ep.num != 0) { + DWC_ERROR("queue req %p, len %d buf %p\n", + req_handle, buflen, buf); + } + } + + if (num_sgs > 1 && (!GET_CORE_IF(pcd)->dma_desc_enable)) { + DWC_ERROR("scatter buffer list is not supported in buffer DMA mode, \ + queue req %p, buf %p\n", req_handle, buf); + return -DWC_E_INVALID; + } + if (num_sgs > 1 && ep->dwc_ep.num == 0) { + DWC_ERROR("more than one buffer is not supported in EP0, \ + queue req %p, buf %p\n", req_handle, buf); + return -DWC_E_INVALID; + } + if (ep->dwc_ep.is_in) { + dump_log(buf, buflen, 1); + } + + if (GET_CORE_IF(pcd)->dma_enable) { + req->mapped = 0; + if (!num_sgs) { + if (dma_buf == DWC_DMA_ADDR_INVALID) { + dma_buf = dma_map_single(get_gadget_wrapper_device(), + buf, buflen, + (ep->dwc_ep.num == 0) ? DMA_BIDIRECTIONAL : + ((ep->dwc_ep.is_in) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); + req->mapped = 1; + } else { + dma_sync_single_for_device(get_gadget_wrapper_device(), + dma_buf, buflen, + (ep->dwc_ep.num == 0) ? DMA_BIDIRECTIONAL : + ((ep->dwc_ep.is_in) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); + } + req->buf = buf; + req->dma = dma_buf; + req->length = buflen; + req->sg = 0; + req->num_mapped_sgs = 0; + } else { + int mapped; + + mapped = dma_map_sg(get_gadget_wrapper_device(), + sg, num_sgs, + (ep->dwc_ep.num == 0) ? DMA_BIDIRECTIONAL : + ((ep->dwc_ep.is_in) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); + if (mapped == 0) { + DWC_ERROR("failed to map SGs\n"); + BUG(); + return -EFAULT; + } + req->length = buflen; + req->sg = sg; + req->num_mapped_sgs = mapped; + } + } + req->sent_zlp = zero; + req->priv = req_handle; + req->dw_align_buf = NULL; + if ((dma_buf & 0x3) && GET_CORE_IF(pcd)->dma_enable + && !GET_CORE_IF(pcd)->dma_desc_enable){ + req->dw_align_buf = kmalloc(buflen, GFP_KERNEL); + req->dw_align_buf_dma = dma_map_single(get_gadget_wrapper_device(), + req->dw_align_buf, + buflen, + (ep->dwc_ep.num == 0) ? DMA_BIDIRECTIONAL : + ((ep->dwc_ep.is_in) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE)); + } + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + /* + * After adding request to the queue for IN ISOC wait for In Token Received + * when TX FIFO is empty interrupt and for OUT ISOC wait for OUT Token + * Received when EP is disabled interrupt to obtain starting microframe + * (odd/even) start transfer + */ + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + if (req != 0) { + depctl_data_t depctl = {.d32 = + DWC_READ_REG32(&pcd->core_if->dev_if-> + in_ep_regs[ep->dwc_ep.num]-> + diepctl) }; + ++pcd->request_pending; + + DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry); + if (ep->dwc_ep.is_in) { + depctl.b.cnak = 1; + DWC_WRITE_REG32(&pcd->core_if->dev_if-> + in_ep_regs[ep->dwc_ep.num]-> + diepctl, depctl.d32); + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + } + return 0; + } + + /* + * For EP0 IN without premature status, zlp is required? + */ + if (ep->dwc_ep.num == 0 && ep->dwc_ep.is_in) { + DWC_DEBUGPL(DBG_PCDV, "%d-OUT ZLP\n", ep->dwc_ep.num); + //_req->zero = 1; + } + + /* Start the transfer */ + if (DWC_CIRCLEQ_EMPTY(&ep->queue) && !ep->stopped) { + /* EP0 Transfer? */ + if (ep->dwc_ep.num == 0) { + switch (pcd->ep0state) { + case EP0_IN_DATA_PHASE: + DWC_DEBUGPL(DBG_PCD, + "%s ep0: EP0_IN_DATA_PHASE\n", + __func__); + break; + + case EP0_OUT_DATA_PHASE: + DWC_DEBUGPL(DBG_PCD, + "%s ep0: EP0_OUT_DATA_PHASE\n", + __func__); + if (pcd->request_config) { + /* Complete STATUS PHASE */ + ep->dwc_ep.is_in = 1; + pcd->ep0state = EP0_IN_STATUS_PHASE; + } + break; + + case EP0_IN_STATUS_PHASE: + DWC_DEBUGPL(DBG_PCD, + "%s ep0: EP0_IN_STATUS_PHASE\n", + __func__); + break; + + default: + DWC_DEBUGPL(DBG_ANY, "ep0: odd state %d\n", + pcd->ep0state); + if (req != 0) { + DWC_FREE(req); + } + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return -DWC_E_SHUTDOWN; + } + ep->dwc_ep.start_xfer_buff = req->buf; + ep->dwc_ep.xfer_buff = req->buf; + ep->dwc_ep.dma_addr = req->dma; + ep->dwc_ep.xfer_len = req->length; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + if (zero) { + if ((ep->dwc_ep.xfer_len % + ep->dwc_ep.maxpacket == 0) + && (ep->dwc_ep.xfer_len != 0)) { + ep->dwc_ep.sent_zlp = 1; + } + } + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), + &ep->dwc_ep); + } // non-ep0 endpoints + else { +#ifdef DWC_UTE_CFI + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + /* store the request length */ + ep->dwc_ep.cfi_req_len = buflen; + pcd->cfi->ops.build_descriptors(pcd->cfi, pcd, + ep, req); + } else { +#endif + max_transfer = + GET_CORE_IF(ep->pcd)->core_params->max_transfer_size; + ep->dwc_ep.maxxfer = max_transfer; + if (GET_CORE_IF(pcd)->dma_desc_enable) { + uint32_t out_max_xfer = + DDMA_MAX_TRANSFER_SIZE - (DDMA_MAX_TRANSFER_SIZE % 4); + if (ep->dwc_ep.is_in) { + if (ep->dwc_ep.maxxfer > DDMA_MAX_TRANSFER_SIZE) + ep->dwc_ep.maxxfer = DDMA_MAX_TRANSFER_SIZE; + } else { + if (ep->dwc_ep.maxxfer > out_max_xfer) + ep->dwc_ep.maxxfer = out_max_xfer; + } + } + if (req->dw_align_buf) { + if (ep->dwc_ep.is_in) + dwc_memcpy(req->dw_align_buf, + buf, buflen); + } + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = req->length; + if (zero) { + if ((ep->dwc_ep.total_len % ep->dwc_ep.maxpacket == 0) + && (ep->dwc_ep.total_len != 0)) { + ep->dwc_ep.sent_zlp = 1; + } + if (num_sgs) + req->short_packet = 1; + } + if (ep->dwc_ep.maxxfer < ep->dwc_ep.total_len) { + ep->dwc_ep.maxxfer -= + (ep->dwc_ep.maxxfer % ep->dwc_ep.maxpacket); + } + +#ifdef DWC_UTE_CFI + } +#endif + dwc_otg_ep_start_transfer(GET_CORE_IF(pcd), + &ep->dwc_ep, req); + } + } + + if (req != 0) { + ++pcd->request_pending; + DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry); + if (ep->dwc_ep.is_in && ep->stopped + && !(GET_CORE_IF(pcd)->dma_enable)) { + /** @todo NGS Create a function for this. */ + diepmsk_data_t diepmsk = {.d32 = 0 }; + diepmsk.b.intktxfemp = 1; + if (GET_CORE_IF(pcd)->multiproc_int_enable) { + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)-> + dev_if->dev_global_regs->diepeachintmsk + [ep->dwc_ep.num], 0, + diepmsk.d32); + } else { + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)-> + dev_if->dev_global_regs-> + diepmsk, 0, diepmsk.d32); + } + + } + } + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + return 0; +} + +int dwc_otg_pcd_ep_dequeue(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle) +{ + dwc_irqflags_t flags; + dwc_otg_pcd_request_t *req; + dwc_otg_pcd_ep_t *ep; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep || (!ep->desc && ep->dwc_ep.num != 0)) { + DWC_WARN("bad argument\n"); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + /* make sure it's actually queued on this endpoint */ + DWC_CIRCLEQ_FOREACH(req, &ep->queue, queue_entry) { + if (req->priv == (void *)req_handle) { + break; + } + } + + if (req->priv != (void *)req_handle) { + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return -DWC_E_INVALID; + } + + if (!DWC_CIRCLEQ_EMPTY_ENTRY(req, queue_entry)) { + dwc_otg_request_done(ep, req, -DWC_E_RESTART); + } else { + req = NULL; + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + return req ? 0 : -DWC_E_SHUTDOWN; + +} + +int dwc_otg_pcd_ep_halt(dwc_otg_pcd_t * pcd, void *ep_handle, int value) +{ + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags; + int retval = 0; + + ep = get_ep_from_handle(pcd, ep_handle); + + if (!ep || (!ep->desc && ep != &pcd->ep0) || + (ep->desc && (ep->desc->bmAttributes == UE_ISOCHRONOUS))) { + DWC_WARN("%s, bad ep\n", __func__); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + DWC_WARN("%d %s XFer In process\n", ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT"); + retval = -DWC_E_AGAIN; + } else if (value == 0) { + dwc_otg_ep_clear_stall(GET_CORE_IF(pcd), &ep->dwc_ep); + } else if (value == 1) { + if (ep->dwc_ep.is_in == 1 && GET_CORE_IF(pcd)->dma_desc_enable) { + dtxfsts_data_t txstatus; + fifosize_data_t txfifosize; + + txfifosize.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)-> + core_global_regs->dtxfsiz[ep->dwc_ep. + tx_fifo_num]); + txstatus.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)-> + dev_if->in_ep_regs[ep->dwc_ep.num]-> + dtxfsts); + + if (txstatus.b.txfspcavail < txfifosize.b.depth) { + DWC_WARN("%s() Data In Tx Fifo\n", __func__); + retval = -DWC_E_AGAIN; + } else { + if (ep->dwc_ep.num == 0) { + pcd->ep0state = EP0_STALL; + } + + ep->stopped = 1; + dwc_otg_ep_set_stall(GET_CORE_IF(pcd), + &ep->dwc_ep); + } + } else { + if (ep->dwc_ep.num == 0) { + pcd->ep0state = EP0_STALL; + } + + ep->stopped = 1; + dwc_otg_ep_set_stall(GET_CORE_IF(pcd), &ep->dwc_ep); + } + } else if (value == 2) { + ep->dwc_ep.stall_clear_flag = 0; + } else if (value == 3) { + ep->dwc_ep.stall_clear_flag = 1; + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + return retval; +} + +/** + * This function initiates remote wakeup of the host from suspend state. + */ +void dwc_otg_pcd_rem_wkup_from_suspend(dwc_otg_pcd_t * pcd, int set) +{ + dctl_data_t dctl = { 0 }; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dsts_data_t dsts; + + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + if (!dsts.b.suspsts) { + DWC_WARN("Remote wakeup while is not in suspend state\n"); + } + /* Check if DEVICE_REMOTE_WAKEUP feature enabled */ + if (pcd->remote_wakeup_enable) { + if (set) { + + if (core_if->adp_enable) { + gpwrdn_data_t gpwrdn; + + dwc_otg_adp_probe_stop(core_if); + + /* Mask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gpwrdn, + gpwrdn.d32, 0); + + /* Disable Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gpwrdn, + gpwrdn.d32, 0); + + /* + * Initialize the Core for Device mode. + */ + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + + dwc_otg_initiate_srp(core_if); + } + + dctl.b.rmtwkupsig = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, 0, dctl.d32); + DWC_DEBUGPL(DBG_PCD, "Set Remote Wakeup\n"); + + dwc_mdelay(2); + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, dctl.d32, 0); + DWC_DEBUGPL(DBG_PCD, "Clear Remote Wakeup\n"); + } + } else { + DWC_DEBUGPL(DBG_PCD, "Remote Wakeup is disabled\n"); + } +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** + * This function initiates remote wakeup of the host from L1 sleep state. + */ +void dwc_otg_pcd_rem_wkup_from_sleep(dwc_otg_pcd_t * pcd, int set) +{ + glpmcfg_data_t lpmcfg; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + + /* Check if we are in L1 state */ + if (!lpmcfg.b.prt_sleep_sts) { + DWC_DEBUGPL(DBG_PCD, "Device is not in sleep state\n"); + return; + } + + /* Check if host allows remote wakeup */ + if (!lpmcfg.b.rem_wkup_en) { + DWC_DEBUGPL(DBG_PCD, "Host does not allow remote wakeup\n"); + return; + } + + /* Check if Resume OK */ + if (!lpmcfg.b.sleep_state_resumeok) { + DWC_DEBUGPL(DBG_PCD, "Sleep state resume is not OK\n"); + return; + } + + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.en_utmi_sleep = 0; + lpmcfg.b.hird_thres &= (~(1 << 4)); + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32); + + if (set) { + dctl_data_t dctl = {.d32 = 0 }; + dctl.b.rmtwkupsig = 1; + /* Set RmtWkUpSig bit to start remote wakup signaling. + * Hardware will automatically clear this bit. + */ + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, + 0, dctl.d32); + DWC_DEBUGPL(DBG_PCD, "Set Remote Wakeup\n"); + } + +} +#endif + +/** + * Performs remote wakeup. + */ +void dwc_otg_pcd_remote_wakeup(dwc_otg_pcd_t * pcd, int set) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_irqflags_t flags; + if (dwc_otg_is_device_mode(core_if)) { + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); +#ifdef CONFIG_USB_DWC_OTG_LPM + if (core_if->lx_state == DWC_OTG_L1) { + dwc_otg_pcd_rem_wkup_from_sleep(pcd, set); + } else { +#endif + dwc_otg_pcd_rem_wkup_from_suspend(pcd, set); +#ifdef CONFIG_USB_DWC_OTG_LPM + } +#endif + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + } + return; +} + +void dwc_otg_pcd_disconnect_us(dwc_otg_pcd_t * pcd, int no_of_usecs) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dctl_data_t dctl = { 0 }; + + if (dwc_otg_is_device_mode(core_if)) { + dctl.b.sftdiscon = 1; + DWC_PRINTF("Soft disconnect for %d useconds\n",no_of_usecs); + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + dwc_udelay(no_of_usecs); + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32,0); + + } else{ + DWC_PRINTF("NOT SUPPORTED IN HOST MODE\n"); + } + return; + +} + +int dwc_otg_pcd_wakeup(dwc_otg_pcd_t * pcd) +{ + dsts_data_t dsts; + gotgctl_data_t gotgctl; + + /* + * This function starts the Protocol if no session is in progress. If + * a session is already in progress, but the device is suspended, + * remote wakeup signaling is started. + */ + + /* Check if valid session */ + gotgctl.d32 = + DWC_READ_REG32(&(GET_CORE_IF(pcd)->core_global_regs->gotgctl)); + if (gotgctl.b.bsesvld) { + /* Check if suspend state */ + dsts.d32 = + DWC_READ_REG32(& + (GET_CORE_IF(pcd)->dev_if-> + dev_global_regs->dsts)); + if (dsts.b.suspsts) { + dwc_otg_pcd_remote_wakeup(pcd, 1); + } + } else { + dwc_otg_pcd_initiate_srp(pcd); + } + + return 0; + +} + +/** + * Start the SRP timer to detect when the SRP does not complete within + * 6 seconds. + * + * @param pcd the pcd structure. + */ +void dwc_otg_pcd_initiate_srp(dwc_otg_pcd_t * pcd) +{ + dwc_irqflags_t flags; + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + dwc_otg_initiate_srp(GET_CORE_IF(pcd)); + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); +} + +int dwc_otg_pcd_get_frame_number(dwc_otg_pcd_t * pcd) +{ + return dwc_otg_get_frame_number(GET_CORE_IF(pcd)); +} + +int dwc_otg_pcd_is_lpm_enabled(dwc_otg_pcd_t * pcd) +{ + return GET_CORE_IF(pcd)->core_params->lpm_enable; +} + +uint32_t get_b_hnp_enable(dwc_otg_pcd_t * pcd) +{ + return pcd->b_hnp_enable; +} + +uint32_t get_a_hnp_support(dwc_otg_pcd_t * pcd) +{ + return pcd->a_hnp_support; +} + +uint32_t get_a_alt_hnp_support(dwc_otg_pcd_t * pcd) +{ + return pcd->a_alt_hnp_support; +} + +int dwc_otg_pcd_get_rmwkup_enable(dwc_otg_pcd_t * pcd) +{ + return pcd->remote_wakeup_enable; +} + +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.h b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.h new file mode 100644 index 00000000..d37d77fd --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.h @@ -0,0 +1,244 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd.h $ + * $Revision: #48 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY +#if !defined(__DWC_PCD_H__) +#define __DWC_PCD_H__ + +#include "dwc_otg_os_dep.h" +#include "usb.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_pcd_if.h" +#include +struct cfiobject; + +/** + * @file + * + * This file contains the structures, constants, and interfaces for + * the Perpherial Contoller Driver (PCD). + * + * The Peripheral Controller Driver (PCD) for Linux will implement the + * Gadget API, so that the existing Gadget drivers can be used. For + * the Mass Storage Function driver the File-backed USB Storage Gadget + * (FBS) driver will be used. The FBS driver supports the + * Control-Bulk (CB), Control-Bulk-Interrupt (CBI), and Bulk-Only + * transports. + * + */ + +/** Invalid DMA Address */ +#define DWC_DMA_ADDR_INVALID (~(dwc_dma_t)0) + +/** Max Transfer size for any EP */ +#define DDMA_MAX_TRANSFER_SIZE 65535 + +/** + * Get the pointer to the core_if from the pcd pointer. + */ +#define GET_CORE_IF( _pcd ) (_pcd->core_if) + +/** + * States of EP0. + */ +typedef enum ep0_state { + EP0_DISCONNECT, /* no host */ + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_IN_STATUS_PHASE, + EP0_OUT_STATUS_PHASE, + EP0_STALL, +} ep0state_e; + +/** Fordward declaration.*/ +struct dwc_otg_pcd; + +/** DWC_otg iso request structure. + * + */ +typedef struct usb_iso_request dwc_otg_pcd_iso_request_t; + +#ifdef DWC_UTE_PER_IO + +/** + * This shall be the exact analogy of the same type structure defined in the + * usb_gadget.h. Each descriptor contains + */ +struct dwc_iso_pkt_desc_port { + uint32_t offset; + uint32_t length; /* expected length */ + uint32_t actual_length; + uint32_t status; +}; + +struct dwc_iso_xreq_port { + /** transfer/submission flag */ + uint32_t tr_sub_flags; + /** Start the request ASAP */ +#define DWC_EREQ_TF_ASAP 0x00000002 + /** Just enqueue the request w/o initiating a transfer */ +#define DWC_EREQ_TF_ENQUEUE 0x00000004 + + /** + * count of ISO packets attached to this request - shall + * not exceed the pio_alloc_pkt_count + */ + uint32_t pio_pkt_count; + /** count of ISO packets allocated for this request */ + uint32_t pio_alloc_pkt_count; + /** number of ISO packet errors */ + uint32_t error_count; + /** reserved for future extension */ + uint32_t res; + /** Will be allocated and freed in the UTE gadget and based on the CFC value */ + struct dwc_iso_pkt_desc_port *per_io_frame_descs; +}; +#endif + +/** PCD EP structure. + * This structure describes an EP, there is an array of EPs in the PCD + * structure. + */ +typedef struct dwc_otg_pcd_ep { + /** USB EP Descriptor */ + const usb_endpoint_descriptor_t *desc; + + /** queue of dwc_otg_pcd_requests. */ + struct req_list queue; + unsigned stopped:1; + unsigned disabling:1; + unsigned dma:1; + unsigned queue_sof:1; + +#ifdef DWC_EN_ISOC + /** ISOC req handle passed */ + void *iso_req_handle; +#endif //_EN_ISOC_ + + /** DWC_otg ep data. */ + dwc_ep_t dwc_ep; + + /** Pointer to PCD */ + struct dwc_otg_pcd *pcd; + + void *priv; +} dwc_otg_pcd_ep_t; + +/** DWC_otg PCD Structure. + * This structure encapsulates the data for the dwc_otg PCD. + */ +struct dwc_otg_pcd { + const struct dwc_otg_pcd_function_ops *fops; + /** The DWC otg device pointer */ + struct dwc_otg_device *otg_dev; + /** Core Interface */ + dwc_otg_core_if_t *core_if; + /** State of EP0 */ + ep0state_e ep0state; + /** EP0 Request is pending */ + unsigned ep0_pending:1; + /** Indicates when SET CONFIGURATION Request is in process */ + unsigned request_config:1; + /** The state of the Remote Wakeup Enable. */ + unsigned remote_wakeup_enable:1; + /** The state of the B-Device HNP Enable. */ + unsigned b_hnp_enable:1; + /** The state of A-Device HNP Support. */ + unsigned a_hnp_support:1; + /** The state of the A-Device Alt HNP support. */ + unsigned a_alt_hnp_support:1; + /** Count of pending Requests */ + unsigned request_pending; + + /** SETUP packet for EP0 + * This structure is allocated as a DMA buffer on PCD initialization + * with enough space for up to 3 setup packets. + */ + union { + usb_device_request_t req; + uint32_t d32[2]; + } *setup_pkt; + + dwc_dma_t setup_pkt_dma_handle; + + /* Additional buffer and flag for CTRL_WR premature case */ + uint8_t *backup_buf; + unsigned data_terminated; + + /** 2-byte dma buffer used to return status from GET_STATUS */ + uint16_t *status_buf; + dwc_dma_t status_buf_dma_handle; + + /** EP0 */ + dwc_otg_pcd_ep_t ep0; + + /** Array of IN EPs. */ + dwc_otg_pcd_ep_t in_ep[MAX_EPS_CHANNELS - 1]; + /** Array of OUT EPs. */ + dwc_otg_pcd_ep_t out_ep[MAX_EPS_CHANNELS - 1]; + /** number of valid EPs in the above array. */ +// unsigned num_eps : 4; + dwc_spinlock_t *lock; + + /** Tasklet to defer starting of TEST mode transmissions until + * Status Phase has been completed. + */ + dwc_tasklet_t *test_mode_tasklet; + + /** Tasklet to delay starting of xfer in DMA mode */ + dwc_tasklet_t *start_xfer_tasklet; + + /** The test mode to enter when the tasklet is executed. */ + unsigned test_mode; + /** re_initialization flag when usb resetted in configured. */ + uint32_t reinit_flag; + /** The cfi_api structure that implements most of the CFI API + * and OTG specific core configuration functionality + */ +#ifdef DWC_UTE_CFI + struct cfiobject *cfi; +#endif + +}; + +//FIXME this functions should be static, and this prototypes should be removed +extern void dwc_otg_request_nuke(dwc_otg_pcd_ep_t * ep); +extern void dwc_otg_request_done(dwc_otg_pcd_ep_t * ep, + dwc_otg_pcd_request_t * req, int32_t status); + +void dwc_otg_iso_buffer_done(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep, + void *req_handle); + +extern void do_test_mode(void *data); +#endif +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_if.h b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_if.h new file mode 100644 index 00000000..10f9d096 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_if.h @@ -0,0 +1,357 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_if.h $ + * $Revision: #11 $ + * $Date: 2011/10/26 $ + * $Change: 1873028 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY + +#if !defined(__DWC_PCD_IF_H__) +#define __DWC_PCD_IF_H__ + +#include "dwc_os.h" +#include "dwc_otg_core_if.h" + +/** @file + * This file defines DWC_OTG PCD Core API. + */ + +struct dwc_otg_pcd; +typedef struct dwc_otg_pcd dwc_otg_pcd_t; + +/** Maxpacket size for EP0 */ +#define MAX_EP0_SIZE 64 +/** Maxpacket size for any EP */ +#define MAX_PACKET_SIZE 1024 + +/** @name Function Driver Callbacks */ +/** @{ */ + +/** This function will be called whenever a previously queued request has + * completed. The status value will be set to -DWC_E_SHUTDOWN to indicated a + * failed or aborted transfer, or -DWC_E_RESTART to indicate the device was reset, + * or -DWC_E_TIMEOUT to indicate it timed out, or -DWC_E_INVALID to indicate invalid + * parameters. */ +typedef int (*dwc_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int32_t status, + uint32_t actual); +/** + * This function will be called whenever a previousle queued ISOC request has + * completed. Count of ISOC packets could be read using dwc_otg_pcd_get_iso_packet_count + * function. + * The status of each ISOC packet could be read using dwc_otg_pcd_get_iso_packet_* + * functions. + */ +typedef int (*dwc_isoc_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int proc_buf_num); +/** This function should handle any SETUP request that cannot be handled by the + * PCD Core. This includes most GET_DESCRIPTORs, SET_CONFIGS, Any + * class-specific requests, etc. The function must non-blocking. + * + * Returns 0 on success. + * Returns -DWC_E_NOT_SUPPORTED if the request is not supported. + * Returns -DWC_E_INVALID if the setup request had invalid parameters or bytes. + * Returns -DWC_E_SHUTDOWN on any other error. */ +typedef int (*dwc_setup_cb_t) (dwc_otg_pcd_t * pcd, uint8_t * bytes); +/** This is called whenever the device has been disconnected. The function + * driver should take appropriate action to clean up all pending requests in the + * PCD Core, remove all endpoints (except ep0), and initialize back to reset + * state. */ +typedef int (*dwc_disconnect_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called when device has been connected. */ +typedef int (*dwc_connect_cb_t) (dwc_otg_pcd_t * pcd, int speed); +/** This function is called when device has been suspended */ +typedef int (*dwc_suspend_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called when device has received LPM tokens, i.e. + * device has been sent to sleep state. */ +typedef int (*dwc_sleep_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called when device has been resumed + * from suspend(L2) or L1 sleep state. */ +typedef int (*dwc_resume_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called whenever hnp params has been changed. + * User can call get_b_hnp_enable, get_a_hnp_support, get_a_alt_hnp_support functions + * to get hnp parameters. */ +typedef int (*dwc_hnp_params_changed_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called whenever USB RESET is detected. */ +typedef int (*dwc_reset_cb_t) (dwc_otg_pcd_t * pcd); + +typedef int (*cfi_setup_cb_t) (dwc_otg_pcd_t * pcd, void *ctrl_req_bytes); + +/** + * + * @param ep_handle Void pointer to the usb_ep structure + * @param ereq_port Pointer to the extended request structure created in the + * portable part. + */ +typedef int (*xiso_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int32_t status, + void *ereq_port); +/** Function Driver Ops Data Structure */ +struct dwc_otg_pcd_function_ops { + dwc_connect_cb_t connect; + dwc_disconnect_cb_t disconnect; + dwc_setup_cb_t setup; + dwc_completion_cb_t complete; + dwc_isoc_completion_cb_t isoc_complete; + dwc_suspend_cb_t suspend; + dwc_sleep_cb_t sleep; + dwc_resume_cb_t resume; + dwc_reset_cb_t reset; + dwc_hnp_params_changed_cb_t hnp_changed; + cfi_setup_cb_t cfi_setup; +#ifdef DWC_UTE_PER_IO + xiso_completion_cb_t xisoc_complete; +#endif +}; +/** @} */ + +/** @name Function Driver Functions */ +/** @{ */ + +/** Call this function to get pointer on dwc_otg_pcd_t, + * this pointer will be used for all PCD API functions. + * + * @param core_if The DWC_OTG Core + */ +extern dwc_otg_pcd_t *dwc_otg_pcd_init(dwc_otg_core_if_t * core_if); + +/** Frees PCD allocated by dwc_otg_pcd_init + * + * @param pcd The PCD + */ +extern void dwc_otg_pcd_remove(dwc_otg_pcd_t * pcd); + +/** Call this to bind the function driver to the PCD Core. + * + * @param pcd Pointer on dwc_otg_pcd_t returned by dwc_otg_pcd_init function. + * @param fops The Function Driver Ops data structure containing pointers to all callbacks. + */ +extern void dwc_otg_pcd_start(dwc_otg_pcd_t * pcd, + const struct dwc_otg_pcd_function_ops *fops); + +/** Enables an endpoint for use. This function enables an endpoint in + * the PCD. The endpoint is described by the ep_desc which has the + * same format as a USB ep descriptor. The ep_handle parameter is used to refer + * to the endpoint from other API functions and in callbacks. Normally this + * should be called after a SET_CONFIGURATION/SET_INTERFACE to configure the + * core for that interface. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error ocurred. + * Returns 0 on success. + * + * @param pcd The PCD + * @param ep_desc Endpoint descriptor + * @param usb_ep Handle on endpoint, that will be used to identify endpoint. + */ +extern int dwc_otg_pcd_ep_enable(dwc_otg_pcd_t * pcd, + const uint8_t * ep_desc, void *usb_ep); + +/** Disable the endpoint referenced by ep_handle. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error occurred. + * Returns 0 on success. */ +extern int dwc_otg_pcd_ep_disable(dwc_otg_pcd_t * pcd, void *ep_handle); + +/** Queue a data transfer request on the endpoint referenced by ep_handle. + * After the transfer is completes, the complete callback will be called with + * the request status. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param buf The buffer for the data + * @param dma_buf The DMA buffer for the data + * @param buflen The length of the data transfer + * @param zero Specifies whether to send zero length last packet. + * @param req_handle Set this handle to any value to use to reference this + * request in the ep_dequeue function or from the complete callback + * @param atomic_alloc If driver need to perform atomic allocations + * for internal data structures. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error ocurred. + * Returns 0 on success. */ +extern int dwc_otg_pcd_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf, dwc_dma_t dma_buf, uint32_t buflen, int zero, + uint32_t num_sgs, struct scatterlist *sg, + void *req_handle, int atomic_alloc); +#ifdef DWC_UTE_PER_IO +/** + * + * @param ereq_nonport Pointer to the extended request part of the + * usb_request structure defined in usb_gadget.h file. + */ +extern int dwc_otg_pcd_xiso_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf, dwc_dma_t dma_buf, + uint32_t buflen, int zero, + void *req_handle, int atomic_alloc, + void *ereq_nonport); + +#endif + +/** De-queue the specified data transfer that has not yet completed. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error ocurred. + * Returns 0 on success. */ +extern int dwc_otg_pcd_ep_dequeue(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle); + +/** Halt (STALL) an endpoint or clear it. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error ocurred. + * Returns -DWC_E_AGAIN if the STALL cannot be sent and must be tried again later + * Returns 0 on success. */ +extern int dwc_otg_pcd_ep_halt(dwc_otg_pcd_t * pcd, void *ep_handle, int value); + +/** This function should be called on every hardware interrupt */ +extern int32_t dwc_otg_pcd_handle_intr(dwc_otg_pcd_t * pcd); + +/** This function returns current frame number */ +extern int dwc_otg_pcd_get_frame_number(dwc_otg_pcd_t * pcd); + +/** + * Start isochronous transfers on the endpoint referenced by ep_handle. + * For isochronous transfers duble buffering is used. + * After processing each of buffers comlete callback will be called with + * status for each transaction. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param buf0 The virtual address of first data buffer + * @param buf1 The virtual address of second data buffer + * @param dma0 The DMA address of first data buffer + * @param dma1 The DMA address of second data buffer + * @param sync_frame Data pattern frame number + * @param dp_frame Data size for pattern frame + * @param data_per_frame Data size for regular frame + * @param start_frame Frame number to start transfers, if -1 then start transfers ASAP. + * @param buf_proc_intrvl Interval of ISOC Buffer processing + * @param req_handle Handle of ISOC request + * @param atomic_alloc Specefies whether to perform atomic allocation for + * internal data structures. + * + * Returns -DWC_E_NO_MEMORY if there is no enough memory. + * Returns -DWC_E_INVALID if incorrect arguments are passed to the function. + * Returns -DW_E_SHUTDOWN for any other error. + * Returns 0 on success + */ +extern int dwc_otg_pcd_iso_ep_start(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf0, uint8_t * buf1, + dwc_dma_t dma0, dwc_dma_t dma1, + int sync_frame, int dp_frame, + int data_per_frame, int start_frame, + int buf_proc_intrvl, void *req_handle, + int atomic_alloc); + +/** Stop ISOC transfers on endpoint referenced by ep_handle. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param req_handle Handle of ISOC request + * + * Returns -DWC_E_INVALID if incorrect arguments are passed to the function + * Returns 0 on success + */ +int dwc_otg_pcd_iso_ep_stop(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle); + +/** Get ISOC packet status. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param iso_req_handle Isochronoush request handle + * @param packet Number of packet + * @param status Out parameter for returning status + * @param actual Out parameter for returning actual length + * @param offset Out parameter for returning offset + * + */ +extern void dwc_otg_pcd_get_iso_packet_params(dwc_otg_pcd_t * pcd, + void *ep_handle, + void *iso_req_handle, int packet, + int *status, int *actual, + int *offset); + +/** Get ISOC packet count. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param iso_req_handle + */ +extern int dwc_otg_pcd_get_iso_packet_count(dwc_otg_pcd_t * pcd, + void *ep_handle, + void *iso_req_handle); + +/** This function starts the SRP Protocol if no session is in progress. If + * a session is already in progress, but the device is suspended, + * remote wakeup signaling is started. + */ +extern int dwc_otg_pcd_wakeup(dwc_otg_pcd_t * pcd); + +/** This function returns 1 if LPM support is enabled, and 0 otherwise. */ +extern int dwc_otg_pcd_is_lpm_enabled(dwc_otg_pcd_t * pcd); + +/** This function returns 1 if remote wakeup is allowed and 0, otherwise. */ +extern int dwc_otg_pcd_get_rmwkup_enable(dwc_otg_pcd_t * pcd); + +/** Initiate SRP */ +extern void dwc_otg_pcd_initiate_srp(dwc_otg_pcd_t * pcd); + +/** Starts remote wakeup signaling. */ +extern void dwc_otg_pcd_remote_wakeup(dwc_otg_pcd_t * pcd, int set); + +/** Starts micorsecond soft disconnect. */ +extern void dwc_otg_pcd_disconnect_us(dwc_otg_pcd_t * pcd, int no_of_usecs); +/** This function returns whether device is dualspeed.*/ +extern uint32_t dwc_otg_pcd_is_dualspeed(dwc_otg_pcd_t * pcd); + +/** This function returns whether device is otg. */ +extern uint32_t dwc_otg_pcd_is_otg(dwc_otg_pcd_t * pcd); + +/** These functions allow to get hnp parameters */ +extern uint32_t get_b_hnp_enable(dwc_otg_pcd_t * pcd); +extern uint32_t get_a_hnp_support(dwc_otg_pcd_t * pcd); +extern uint32_t get_a_alt_hnp_support(dwc_otg_pcd_t * pcd); + +/** CFI specific Interface functions */ +/** Allocate a cfi buffer */ +extern uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep, + dwc_dma_t * addr, size_t buflen, + int flags); + +/******************************************************************************/ + +/** @} */ + +#endif /* __DWC_PCD_IF_H__ */ + +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_intr.c b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_intr.c new file mode 100644 index 00000000..9a876574 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_intr.c @@ -0,0 +1,4679 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_intr.c $ + * $Revision: #116 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY +#include +#include "dwc_otg_pcd.h" +#include +#include +#include "dwc_otg_driver.h" + +#ifdef DWC_UTE_CFI +#include "dwc_otg_cfi.h" +#endif + +#ifdef DWC_UTE_PER_IO +extern void complete_xiso_ep(dwc_otg_pcd_ep_t * ep); +#endif +//#define PRINT_CFI_DMA_DESCS + +#define DEBUG_EP0 +//#define USB_DEBUG + +/** + * This function updates OTG. + */ +static void dwc_otg_pcd_update_otg(dwc_otg_pcd_t * pcd, const unsigned reset) +{ + + if (reset) { + pcd->b_hnp_enable = 0; + pcd->a_hnp_support = 0; + pcd->a_alt_hnp_support = 0; + } + + if (pcd->fops->hnp_changed) { + pcd->fops->hnp_changed(pcd); + } +} + +/** @file + * This file contains the implementation of the PCD Interrupt handlers. + * + * The PCD handles the device interrupts. Many conditions can cause a + * device interrupt. When an interrupt occurs, the device interrupt + * service routine determines the cause of the interrupt and + * dispatches handling to the appropriate function. These interrupt + * handling functions are described below. + * All interrupt registers are processed from LSB to MSB. + */ + +/** + * This function prints the ep0 state for debug purposes. + */ +static inline void print_ep0_state(dwc_otg_pcd_t * pcd) +{ +#ifdef DEBUG + char str[40]; + + switch (pcd->ep0state) { + case EP0_DISCONNECT: + dwc_strcpy(str, "EP0_DISCONNECT"); + break; + case EP0_IDLE: + dwc_strcpy(str, "EP0_IDLE"); + break; + case EP0_IN_DATA_PHASE: + dwc_strcpy(str, "EP0_IN_DATA_PHASE"); + break; + case EP0_OUT_DATA_PHASE: + dwc_strcpy(str, "EP0_OUT_DATA_PHASE"); + break; + case EP0_IN_STATUS_PHASE: + dwc_strcpy(str, "EP0_IN_STATUS_PHASE"); + break; + case EP0_OUT_STATUS_PHASE: + dwc_strcpy(str, "EP0_OUT_STATUS_PHASE"); + break; + case EP0_STALL: + dwc_strcpy(str, "EP0_STALL"); + break; + default: + dwc_strcpy(str, "EP0_INVALID"); + } + + DWC_DEBUGPL(DBG_ANY, "%s(%d)\n", str, pcd->ep0state); +#endif +} + +/** + * This function calculate the size of the payload in the memory + * for out endpoints and prints size for debug purposes(used in + * 2.93a DevOutNak feature). + */ +static inline void print_memory_payload(dwc_otg_pcd_t * pcd, dwc_ep_t * ep) +{ +#ifdef DEBUG + deptsiz_data_t deptsiz_init = {.d32 = 0 }; + deptsiz_data_t deptsiz_updt = {.d32 = 0 }; + int pack_num; + unsigned payload; + + deptsiz_init.d32 = pcd->core_if->start_doeptsiz_val[ep->num]; + deptsiz_updt.d32 = + DWC_READ_REG32(&pcd->core_if->dev_if-> + out_ep_regs[ep->num]->doeptsiz); + /* Payload will be */ + payload = deptsiz_init.b.xfersize - deptsiz_updt.b.xfersize; + /* Packet count is decremented every time a packet + * is written to the RxFIFO not in to the external memory + * So, if payload == 0, then it means no packet was sent to ext memory*/ + pack_num = (!payload) ? 0 : (deptsiz_init.b.pktcnt - deptsiz_updt.b.pktcnt); + DWC_DEBUGPL(DBG_PCDV, + "Payload for EP%d-%s\n", + ep->num, (ep->is_in ? "IN" : "OUT")); + DWC_DEBUGPL(DBG_PCDV, + "Number of transfered bytes = 0x%08x\n", payload); + DWC_DEBUGPL(DBG_PCDV, + "Number of transfered packets = %d\n", pack_num); +#endif +} + + +#ifdef DWC_UTE_CFI +static inline void print_desc(struct dwc_otg_dma_desc *ddesc, + const uint8_t * epname, int descnum) +{ + CFI_INFO + ("%s DMA_DESC(%d) buf=0x%08x bytes=0x%04x; sp=0x%x; l=0x%x; sts=0x%02x; bs=0x%02x\n", + epname, descnum, ddesc->buf, ddesc->status.b.bytes, + ddesc->status.b.sp, ddesc->status.b.l, ddesc->status.b.sts, + ddesc->status.b.bs); +} +#endif + +/** + * This function returns pointer to in ep struct with number ep_num + */ +static inline dwc_otg_pcd_ep_t *get_in_ep(dwc_otg_pcd_t * pcd, uint32_t ep_num) +{ + int i; + int num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps; + if (ep_num == 0) { + return &pcd->ep0; + } else { + for (i = 0; i < num_in_eps; ++i) { + if (pcd->in_ep[i].dwc_ep.num == ep_num) + return &pcd->in_ep[i]; + } + return 0; + } +} + +/** + * This function returns pointer to out ep struct with number ep_num + */ +static inline dwc_otg_pcd_ep_t *get_out_ep(dwc_otg_pcd_t * pcd, uint32_t ep_num) +{ + int i; + int num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps; + if (ep_num == 0) { + return &pcd->ep0; + } else { + for (i = 0; i < num_out_eps; ++i) { + if (pcd->out_ep[i].dwc_ep.num == ep_num) + return &pcd->out_ep[i]; + } + return 0; + } +} + +/** + * This functions gets a pointer to an EP from the wIndex address + * value of the control request. + */ +dwc_otg_pcd_ep_t *get_ep_by_addr(dwc_otg_pcd_t * pcd, u16 wIndex) +{ + dwc_otg_pcd_ep_t *ep; + uint32_t ep_num = UE_GET_ADDR(wIndex); + + if (ep_num == 0) { + ep = &pcd->ep0; + } else if (UE_GET_DIR(wIndex) == UE_DIR_IN) { /* in ep */ + ep = &pcd->in_ep[ep_num - 1]; + } else { + ep = &pcd->out_ep[ep_num - 1]; + } + + return ep; +} + +/** + * This function checks the EP request queue, if the queue is not + * empty the next request is started. + */ +void start_next_request(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_pcd_request_t *req = 0; + uint32_t max_transfer = + GET_CORE_IF(ep->pcd)->core_params->max_transfer_size; + +#ifdef DWC_UTE_CFI + struct dwc_otg_pcd *pcd; + pcd = ep->pcd; +#endif + + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + +#ifdef DWC_UTE_CFI + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + ep->dwc_ep.cfi_req_len = req->length; + pcd->cfi->ops.build_descriptors(pcd->cfi, pcd, ep, req); + } else { +#endif + /* Setup and start the Transfer */ + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = req->length; + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + + ep->dwc_ep.maxxfer = max_transfer; + if (GET_CORE_IF(ep->pcd)->dma_desc_enable) { + uint32_t out_max_xfer = DDMA_MAX_TRANSFER_SIZE + - (DDMA_MAX_TRANSFER_SIZE % 4); + if (ep->dwc_ep.is_in) { + if (ep->dwc_ep.maxxfer > + DDMA_MAX_TRANSFER_SIZE) { + ep->dwc_ep.maxxfer = + DDMA_MAX_TRANSFER_SIZE; + } + } else { + if (ep->dwc_ep.maxxfer > out_max_xfer) { + ep->dwc_ep.maxxfer = + out_max_xfer; + } + } + } + if (ep->dwc_ep.maxxfer < ep->dwc_ep.total_len) { + ep->dwc_ep.maxxfer -= + (ep->dwc_ep.maxxfer % ep->dwc_ep.maxpacket); + } + if (req->sent_zlp) { + if ((ep->dwc_ep.total_len % + ep->dwc_ep.maxpacket == 0) + && (ep->dwc_ep.total_len != 0)) { + ep->dwc_ep.sent_zlp = 1; + } + + } +#ifdef DWC_UTE_CFI + } +#endif + dwc_otg_ep_start_transfer(GET_CORE_IF(ep->pcd), &ep->dwc_ep, req); + } else if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + DWC_PRINTF("There are no more ISOC requests \n"); + ep->dwc_ep.frame_num = 0xFFFFFFFF; + } +} + +/** + * This function handles the SOF Interrupts. At this time the SOF + * Interrupt is disabled. + */ +int32_t dwc_otg_pcd_handle_sof_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + gintsts_data_t gintsts; + + DWC_DEBUGPL(DBG_PCD, "SOF\n"); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.sofintr = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This function handles the Rx Status Queue Level Interrupt, which + * indicates that there is a least one packet in the Rx FIFO. The + * packets are moved from the FIFO to memory, where they will be + * processed when the Endpoint Interrupt Register indicates Transfer + * Complete or SETUP Phase Done. + * + * Repeat the following until the Rx Status Queue is empty: + * -# Read the Receive Status Pop Register (GRXSTSP) to get Packet + * info + * -# If Receive FIFO is empty then skip to step Clear the interrupt + * and exit + * -# If SETUP Packet call dwc_otg_read_setup_packet to copy the + * SETUP data to the buffer + * -# If OUT Data Packet call dwc_otg_read_packet to copy the data + * to the destination buffer + */ +int32_t dwc_otg_pcd_handle_rx_status_q_level_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gintmsk_data_t gintmask = {.d32 = 0 }; + device_grxsts_data_t status; + dwc_otg_pcd_ep_t *ep; + gintsts_data_t gintsts; +#ifdef DEBUG + static char *dpid_str[] = { "D0", "D2", "D1", "MDATA" }; +#endif + + //DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _pcd); + /* Disable the Rx Status Queue Level interrupt */ + gintmask.b.rxstsqlvl = 1; + DWC_MODIFY_REG32(&global_regs->gintmsk, gintmask.d32, 0); + + /* Get the Status from the top of the FIFO */ + status.d32 = DWC_READ_REG32(&global_regs->grxstsp); + + DWC_DEBUGPL(DBG_PCD, "EP:%d BCnt:%d DPID:%s " + "pktsts:%x Frame:%d(0x%0x)\n", + status.b.epnum, status.b.bcnt, + dpid_str[status.b.dpid], + status.b.pktsts, status.b.fn, status.b.fn); + /* Get pointer to EP structure */ + ep = get_out_ep(pcd, status.b.epnum); + + switch (status.b.pktsts) { + case DWC_DSTS_GOUT_NAK: + DWC_DEBUGPL(DBG_PCDV, "Global OUT NAK\n"); + break; + case DWC_STS_DATA_UPDT: + DWC_DEBUGPL(DBG_PCDV, "OUT Data Packet\n"); + if (status.b.bcnt && ep->dwc_ep.xfer_buff) { + /** @todo NGS Check for buffer overflow? */ + dwc_otg_read_packet(core_if, + ep->dwc_ep.xfer_buff, + status.b.bcnt); + ep->dwc_ep.xfer_count += status.b.bcnt; + ep->dwc_ep.xfer_buff += status.b.bcnt; + } + break; + case DWC_STS_XFER_COMP: + DWC_DEBUGPL(DBG_PCDV, "OUT Complete\n"); + break; + case DWC_DSTS_SETUP_COMP: +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "Setup Complete\n"); +#endif + break; + case DWC_DSTS_SETUP_UPDT: + dwc_otg_read_setup_packet(core_if, pcd->setup_pkt->d32); +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, + "SETUP PKT: %02x.%02x v%04x i%04x l%04x\n", + pcd->setup_pkt->req.bmRequestType, + pcd->setup_pkt->req.bRequest, + UGETW(pcd->setup_pkt->req.wValue), + UGETW(pcd->setup_pkt->req.wIndex), + UGETW(pcd->setup_pkt->req.wLength)); +#endif + ep->dwc_ep.xfer_count += status.b.bcnt; + break; + default: + DWC_DEBUGPL(DBG_PCDV, "Invalid Packet Status (0x%0x)\n", + status.b.pktsts); + break; + } + + /* Enable the Rx Status Queue Level interrupt */ + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmask.d32); + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.rxstsqlvl = 1; + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + //DWC_DEBUGPL(DBG_PCDV, "EXIT: %s\n", __func__); + return 1; +} + +/** + * This function examines the Device IN Token Learning Queue to + * determine the EP number of the last IN token received. This + * implementation is for the Mass Storage device where there are only + * 2 IN EPs (Control-IN and BULK-IN). + * + * The EP numbers for the first six IN Tokens are in DTKNQR1 and there + * are 8 EP Numbers in each of the other possible DTKNQ Registers. + * + * @param core_if Programming view of DWC_otg controller. + * + */ +static inline int get_ep_of_last_in_token(dwc_otg_core_if_t * core_if) +{ + dwc_otg_device_global_regs_t *dev_global_regs = + core_if->dev_if->dev_global_regs; + const uint32_t TOKEN_Q_DEPTH = core_if->hwcfg2.b.dev_token_q_depth; + /* Number of Token Queue Registers */ + const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8; + dtknq1_data_t dtknqr1; + uint32_t in_tkn_epnums[4]; + int ndx = 0; + int i = 0; + volatile uint32_t *addr = &dev_global_regs->dtknqr1; + int epnum = 0; + + //DWC_DEBUGPL(DBG_PCD,"dev_token_q_depth=%d\n",TOKEN_Q_DEPTH); + + /* Read the DTKNQ Registers */ + for (i = 0; i < DTKNQ_REG_CNT; i++) { + in_tkn_epnums[i] = DWC_READ_REG32(addr); + DWC_DEBUGPL(DBG_PCDV, "DTKNQR%d=0x%08x\n", i + 1, + in_tkn_epnums[i]); + if (addr == &dev_global_regs->dvbusdis) { + addr = &dev_global_regs->dtknqr3_dthrctl; + } else { + ++addr; + } + + } + + /* Copy the DTKNQR1 data to the bit field. */ + dtknqr1.d32 = in_tkn_epnums[0]; + /* Get the EP numbers */ + in_tkn_epnums[0] = dtknqr1.b.epnums0_5; + ndx = dtknqr1.b.intknwptr - 1; + + //DWC_DEBUGPL(DBG_PCDV,"ndx=%d\n",ndx); + if (ndx == -1) { + /** @todo Find a simpler way to calculate the max + * queue position.*/ + int cnt = TOKEN_Q_DEPTH; + if (TOKEN_Q_DEPTH <= 6) { + cnt = TOKEN_Q_DEPTH - 1; + } else if (TOKEN_Q_DEPTH <= 14) { + cnt = TOKEN_Q_DEPTH - 7; + } else if (TOKEN_Q_DEPTH <= 22) { + cnt = TOKEN_Q_DEPTH - 15; + } else { + cnt = TOKEN_Q_DEPTH - 23; + } + epnum = (in_tkn_epnums[DTKNQ_REG_CNT - 1] >> (cnt * 4)) & 0xF; + } else { + if (ndx <= 5) { + epnum = (in_tkn_epnums[0] >> (ndx * 4)) & 0xF; + } else if (ndx <= 13) { + ndx -= 6; + epnum = (in_tkn_epnums[1] >> (ndx * 4)) & 0xF; + } else if (ndx <= 21) { + ndx -= 14; + epnum = (in_tkn_epnums[2] >> (ndx * 4)) & 0xF; + } else if (ndx <= 29) { + ndx -= 22; + epnum = (in_tkn_epnums[3] >> (ndx * 4)) & 0xF; + } + } + //DWC_DEBUGPL(DBG_PCD,"epnum=%d\n",epnum); + return epnum; +} + +/** + * This interrupt occurs when the non-periodic Tx FIFO is half-empty. + * The active request is checked for the next packet to be loaded into + * the non-periodic Tx FIFO. + */ +int32_t dwc_otg_pcd_handle_np_tx_fifo_empty_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_dev_in_ep_regs_t *ep_regs; + gnptxsts_data_t txstatus = {.d32 = 0 }; + gintsts_data_t gintsts; + + int epnum = 0; + dwc_otg_pcd_ep_t *ep = 0; + uint32_t len = 0; + int dwords; + + /* Get the epnum from the IN Token Learning Queue. */ + epnum = get_ep_of_last_in_token(core_if); + ep = get_in_ep(pcd, epnum); + + DWC_DEBUGPL(DBG_PCD, "NP TxFifo Empty: %d \n", epnum); + + ep_regs = core_if->dev_if->in_ep_regs[epnum]; + + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + if (len > ep->dwc_ep.maxpacket) { + len = ep->dwc_ep.maxpacket; + } + dwords = (len + 3) / 4; + + /* While there is space in the queue and space in the FIFO and + * More data to tranfer, Write packets to the Tx FIFO */ + txstatus.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + DWC_DEBUGPL(DBG_PCDV, "b4 GNPTXSTS=0x%08x\n", txstatus.d32); + + while (txstatus.b.nptxqspcavail > 0 && + txstatus.b.nptxfspcavail > dwords && + ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len) { + /* Write the FIFO */ + dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0); + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + + if (len > ep->dwc_ep.maxpacket) { + len = ep->dwc_ep.maxpacket; + } + + dwords = (len + 3) / 4; + txstatus.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + DWC_DEBUGPL(DBG_PCDV, "GNPTXSTS=0x%08x\n", txstatus.d32); + } + + DWC_DEBUGPL(DBG_PCDV, "GNPTXSTS=0x%08x\n", + DWC_READ_REG32(&global_regs->gnptxsts)); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.nptxfempty = 1; + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This function is called when dedicated Tx FIFO Empty interrupt occurs. + * The active request is checked for the next packet to be loaded into + * apropriate Tx FIFO. + */ +static int32_t write_empty_tx_fifo(dwc_otg_pcd_t * pcd, uint32_t epnum) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_dev_in_ep_regs_t *ep_regs; + dtxfsts_data_t txstatus = {.d32 = 0 }; + dwc_otg_pcd_ep_t *ep = 0; + uint32_t len = 0; + int dwords; + + ep = get_in_ep(pcd, epnum); + + DWC_DEBUGPL(DBG_PCD, "Dedicated TxFifo Empty: %d \n", epnum); + + ep_regs = core_if->dev_if->in_ep_regs[epnum]; + + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + + if (len > ep->dwc_ep.maxpacket) { + len = ep->dwc_ep.maxpacket; + } + + dwords = (len + 3) / 4; + + /* While there is space in the queue and space in the FIFO and + * More data to tranfer, Write packets to the Tx FIFO */ + txstatus.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, txstatus.d32); + + while (txstatus.b.txfspcavail > dwords && + ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len && + ep->dwc_ep.xfer_len != 0) { + /* Write the FIFO */ + dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0); + + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + if (len > ep->dwc_ep.maxpacket) { + len = ep->dwc_ep.maxpacket; + } + + dwords = (len + 3) / 4; + txstatus.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", epnum, + txstatus.d32); + } + + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts)); + + return 1; +} + +/** + * This function is called when the Device is disconnected. It stops + * any active requests and informs the Gadget driver of the + * disconnect. + */ +void dwc_otg_pcd_stop(dwc_otg_pcd_t * pcd) +{ + int i, num_in_eps, num_out_eps; + dwc_otg_pcd_ep_t *ep; + unsigned long flags; + + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps; + num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps; + + DWC_DEBUGPL(DBG_PCDV, "%s() \n", __func__); + /* don't disconnect drivers more than once */ + if (pcd->ep0state == EP0_DISCONNECT) { + DWC_DEBUGPL(DBG_ANY, "%s() Already Disconnected\n", __func__); + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return; + } + pcd->ep0state = EP0_DISCONNECT; + + /* Reset the OTG state. */ + dwc_otg_pcd_update_otg(pcd, 1); + + /* Disable the NP Tx Fifo Empty Interrupt. */ + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Flush the FIFOs */ + /**@todo NGS Flush Periodic FIFOs */ + dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd), 0x10); + dwc_otg_flush_rx_fifo(GET_CORE_IF(pcd)); + + /* prevent new request submissions, kill any outstanding requests */ + ep = &pcd->ep0; + dwc_otg_request_nuke(ep); + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < num_in_eps; i++) { + dwc_otg_pcd_ep_t *ep = &pcd->in_ep[i]; + dwc_otg_request_nuke(ep); + } + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < num_out_eps; i++) { + dwc_otg_pcd_ep_t *ep = &pcd->out_ep[i]; + dwc_otg_request_nuke(ep); + } + + /* report disconnect; the driver is already quiesced */ + if (pcd->fops->disconnect) { + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + pcd->fops->disconnect(pcd); + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + } + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); +} + +/** + * This interrupt indicates that ... + */ +int32_t dwc_otg_pcd_handle_i2c_intr(dwc_otg_pcd_t * pcd) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "i2cintr"); + intr_mask.b.i2cintr = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.i2cintr = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + return 1; +} + +/** + * This interrupt indicates that ... + */ +int32_t dwc_otg_pcd_handle_early_suspend_intr(dwc_otg_pcd_t * pcd) +{ + gintsts_data_t gintsts; +#if defined(VERBOSE) + DWC_PRINTF("Early Suspend Detected\n"); +#endif + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.erlysuspend = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + return 1; +} + +/** + * This function configures EPO to receive SETUP packets. + * + * @todo NGS: Update the comments from the HW FS. + * + * -# Program the following fields in the endpoint specific registers + * for Control OUT EP 0, in order to receive a setup packet + * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back + * setup packets) + * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back + * to back setup packets) + * - In DMA mode, DOEPDMA0 Register with a memory address to + * store any setup packets received + * + * @param core_if Programming view of DWC_otg controller. + * @param pcd Programming view of the PCD. + */ +static inline void ep0_out_start(dwc_otg_core_if_t * core_if, + dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + deptsiz0_data_t doeptsize0 = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc; + dwc_ep_t *ep = &pcd->ep0.dwc_ep; + depctl_data_t doepctl = {.d32 = 0 }; + +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "%s() doepctl0=%0x\n", __func__, + DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl)); +#endif + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl); + if (doepctl.b.epena) { + return; + } + } + + doeptsize0.b.supcnt = 3; + doeptsize0.b.pktcnt = 1; + doeptsize0.b.xfersize = 8 * 3; + + if (core_if->dma_enable) { + if (!core_if->dma_desc_enable) { + /** put here as for Hermes mode deptisz register should not be written */ + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doeptsiz, + doeptsize0.d32); + + /** @todo dma needs to handle multiple setup packets (up to 3) */ + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepdma, + pcd->setup_pkt_dma_handle); + } else { + dev_if->setup_desc_index = + (dev_if->setup_desc_index + 1) & 1; + dma_desc = + dev_if->setup_desc_addr[dev_if->setup_desc_index]; + ep->desc_cnt = 0; + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + dma_desc->status.b.sr = 0; + dma_desc->status.b.mtrf = 0; + } + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.bytes = ep->maxpacket; + dma_desc->buf = pcd->setup_pkt_dma_handle; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + ep->desc_cnt = 1; + ep->desc_addr = dma_desc; + ep->dma_desc_addr = dev_if->dma_setup_desc_addr + [dev_if->setup_desc_index]; + /** DOEPDMA0 Register write */ + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepdma, + dev_if->dma_setup_desc_addr + [dev_if->setup_desc_index]); + /** printk("ep0 dma_desc_addr[%d].dmareg(0x%x) == (0x%x , 0x%x: 0x%x,0x%x)\n", + dev_if->setup_desc_index,&dev_if->out_ep_regs[0]->doepdma, + dma_desc, dev_if->dma_setup_desc_addr[dev_if->setup_desc_index], + DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepdma),dma_desc->status.d32); + */ + } + + } else { + /** put here as for Hermes mode deptisz register should not be written */ + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doeptsiz, + doeptsize0.d32); + } + + /** DOEPCTL0 Register write */ + doepctl.b.epena = 1; + //doepctl.b.cnak = 1; + //doepctl.b.snak = 1; + dwc_write_reg32(&dev_if->out_ep_regs[0]->doepctl, doepctl.d32); + +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "doepctl0=%0x\n", + DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl)); + DWC_DEBUGPL(DBG_PCDV, "diepctl0=%0x\n", + DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl)); +#endif +} + +/** + * This interrupt occurs when a USB Reset is detected. When the USB + * Reset Interrupt occurs the device state is set to DEFAULT and the + * EP0 state is set to IDLE. + * -# Set the NAK bit for all OUT endpoints (DOEPCTLn.SNAK = 1) + * -# Unmask the following interrupt bits + * - DAINTMSK.INEP0 = 1 (Control 0 IN endpoint) + * - DAINTMSK.OUTEP0 = 1 (Control 0 OUT endpoint) + * - DOEPMSK.SETUP = 1 + * - DOEPMSK.XferCompl = 1 + * - DIEPMSK.XferCompl = 1 + * - DIEPMSK.TimeOut = 1 + * -# Program the following fields in the endpoint specific registers + * for Control OUT EP 0, in order to receive a setup packet + * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back + * setup packets) + * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back + * to back setup packets) + * - In DMA mode, DOEPDMA0 Register with a memory address to + * store any setup packets received + * At this point, all the required initialization, except for enabling + * the control 0 OUT endpoint is done, for receiving SETUP packets. + */ +int32_t dwc_otg_pcd_handle_usb_reset_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + depctl_data_t doepctl = {.d32 = 0 }; + depctl_data_t diepctl = {.d32 = 0 }; + daint_data_t daintmsk = {.d32 = 0 }; + doepmsk_data_t doepmsk = {.d32 = 0 }; + diepmsk_data_t diepmsk = {.d32 = 0 }; + dcfg_data_t dcfg = {.d32 = 0 }; + grstctl_t resetctl = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + int i = 0; + gintsts_data_t gintsts; + pcgcctl_data_t power = {.d32 = 0 }; + + power.d32 = DWC_READ_REG32(core_if->pcgcctl); + if (power.b.stoppclk) { + power.d32 = 0; + power.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0); + + power.b.pwrclmp = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0); + + power.b.rstpdwnmodule = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0); + } + + core_if->lx_state = DWC_OTG_L0; + +#ifdef DWC_EN_ISOC + for (i = 1; i < 16; ++i) { + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + ep = get_in_ep(pcd, i); + if (ep != 0) { + dwc_ep = &ep->dwc_ep; + dwc_ep->next_frame = 0xffffffff; + } + } +#endif /* DWC_EN_ISOC */ + + /* reset the HNP settings */ + dwc_otg_pcd_update_otg(pcd, 1); + + /* Clear the Remote Wakeup Signalling */ + dctl.b.rmtwkupsig = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0); + + /* Set NAK for all OUT EPs */ + doepctl.b.snak = 1; + for (i = 0; i <= dev_if->num_out_eps; i++) { + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, doepctl.d32); + } + + /* Set data pid0 for all eps except for ep0 */ + doepctl.b.setd0pid = 1; + for (i = 1; i <= dev_if->num_out_eps; i++) { + dwc_write_reg32(&dev_if->out_ep_regs[i]->doepctl, doepctl.d32); + } + + diepctl.b.setd0pid = 1; + for (i = 1; i <= dev_if->num_in_eps; i++) { + dwc_write_reg32(&dev_if->in_ep_regs[i]->diepctl, diepctl.d32); + } + /* Flush the NP Tx FIFO */ + dwc_otg_flush_tx_fifo(core_if, 0x10); + /* Flush the NP Rx FIFO */ + dwc_otg_flush_rx_fifo(core_if); + /* Flush the Learning Queue */ + resetctl.b.intknqflsh = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->grstctl, resetctl.d32); + + if (core_if->multiproc_int_enable) { + daintmsk.b.inep0 = 1; + daintmsk.b.outep0 = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->deachintmsk, + daintmsk.d32); + + doepmsk.b.setup = 1; + doepmsk.b.xfercompl = 1; + doepmsk.b.ahberr = 1; + doepmsk.b.epdisabled = 1; + + if ((core_if->dma_desc_enable) || + (core_if->dma_enable + && core_if->snpsid >= OTG_CORE_REV_3_00a)) { + doepmsk.b.stsphsercvd = 1; + } + if (core_if->dma_desc_enable) + doepmsk.b.bna = 1; +/* + doepmsk.b.babble = 1; + doepmsk.b.nyet = 1; + + if (core_if->dma_enable) { + doepmsk.b.nak = 1; + } +*/ + DWC_WRITE_REG32(&dev_if->dev_global_regs->doepeachintmsk[0], + doepmsk.d32); + + diepmsk.b.xfercompl = 1; + diepmsk.b.timeout = 1; + diepmsk.b.epdisabled = 1; + diepmsk.b.ahberr = 1; + diepmsk.b.intknepmis = 1; + if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) + diepmsk.b.intknepmis = 0; + +/* if (core_if->dma_desc_enable) { + diepmsk.b.bna = 1; + } +*/ +/* + if (core_if->dma_enable) { + diepmsk.b.nak = 1; + } +*/ + DWC_WRITE_REG32(&dev_if->dev_global_regs->diepeachintmsk[0], + diepmsk.d32); + } else { + daintmsk.b.inep0 = 1; + daintmsk.b.outep0 = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->daintmsk, + daintmsk.d32); + + doepmsk.b.setup = 1; + doepmsk.b.xfercompl = 1; + doepmsk.b.ahberr = 1; + doepmsk.b.epdisabled = 1; + + if ((core_if->dma_desc_enable) || + (core_if->dma_enable + && core_if->snpsid >= OTG_CORE_REV_3_00a)) { + doepmsk.b.stsphsercvd = 1; + } + if (core_if->dma_desc_enable) + doepmsk.b.bna = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->doepmsk, doepmsk.d32); + + diepmsk.b.xfercompl = 1; + diepmsk.b.timeout = 1; + diepmsk.b.epdisabled = 1; + diepmsk.b.ahberr = 1; + if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) + diepmsk.b.intknepmis = 0; +/* + if (core_if->dma_desc_enable) { + diepmsk.b.bna = 1; + } +*/ + + DWC_WRITE_REG32(&dev_if->dev_global_regs->diepmsk, diepmsk.d32); + } + + /* Reset Device Address */ + dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg); + if ((pcd->reinit_flag == 0) && (pcd->ep0state != EP0_DISCONNECT) + && (dcfg.b.devaddr != 0)) { + DWC_PRINTF("ep0_state = %d addr = 0x%x USB RESET\n" , + pcd->ep0state, dcfg.b.devaddr); + pcd->reinit_flag = 1; + } + dcfg.b.devaddr = 0; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32); + /* setup EP0 to receive SETUP packets */ + if (core_if->snpsid <= OTG_CORE_REV_2_94a) + ep0_out_start(core_if, pcd); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.usbreset = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * Get the device speed from the device status register and convert it + * to USB speed constant. + * + * @param core_if Programming view of DWC_otg controller. + */ +static int get_device_speed(dwc_otg_core_if_t * core_if) +{ + dsts_data_t dsts; + int speed = 0; + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + switch (dsts.b.enumspd) { + case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ: + speed = USB_SPEED_HIGH; + break; + case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ: + speed = USB_SPEED_FULL; + break; + + case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ: + speed = USB_SPEED_LOW; + break; + } + + return speed; +} + +/** + * Read the device status register and set the device speed in the + * data structure. + * Set up EP0 to receive SETUP packets by calling dwc_ep0_activate. + */ +int32_t dwc_otg_pcd_handle_enum_done_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + gintsts_data_t gintsts; + gusbcfg_data_t gusbcfg; + dwc_otg_core_global_regs_t *global_regs = + GET_CORE_IF(pcd)->core_global_regs; + uint8_t utmi16b, utmi8b; + int speed; + + DWC_PRINTF("SPEED ENUM\n"); + + if (GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_2_60a) { + utmi16b = 6; //vahrama old value was 6; + utmi8b = 9; + } else { + utmi16b = 4; + utmi8b = 8; + } + dwc_otg_ep0_activate(GET_CORE_IF(pcd), &ep0->dwc_ep); + if (GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_3_00a) { + ep0_out_start(GET_CORE_IF(pcd), pcd); + } + +#ifdef DEBUG_EP0 + print_ep0_state(pcd); +#endif + + if (pcd->ep0state == EP0_DISCONNECT) { + pcd->ep0state = EP0_IDLE; + } else if (pcd->ep0state == EP0_STALL) { + pcd->ep0state = EP0_IDLE; + } + + pcd->ep0state = EP0_IDLE; + + ep0->stopped = 0; + + speed = get_device_speed(GET_CORE_IF(pcd)); + pcd->fops->connect(pcd, speed); + + /* Set USB turnaround time based on device speed and PHY interface. */ + gusbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + if (speed == USB_SPEED_HIGH) { + if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type == + DWC_HWCFG2_HS_PHY_TYPE_ULPI) { + /* ULPI interface */ + gusbcfg.b.usbtrdtim = 9; + } + if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type == + DWC_HWCFG2_HS_PHY_TYPE_UTMI) { + /* UTMI+ interface */ + if (GET_CORE_IF(pcd)->hwcfg4.b.utmi_phy_data_width == 0) { + gusbcfg.b.usbtrdtim = utmi8b; + } else if (GET_CORE_IF(pcd)->hwcfg4. + b.utmi_phy_data_width == 1) { + gusbcfg.b.usbtrdtim = utmi16b; + } else if (GET_CORE_IF(pcd)-> + core_params->phy_utmi_width == 8) { + gusbcfg.b.usbtrdtim = utmi8b; + } else { + gusbcfg.b.usbtrdtim = utmi16b; + } + } + if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type == + DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI) { + /* UTMI+ OR ULPI interface */ + if (gusbcfg.b.ulpi_utmi_sel == 1) { + /* ULPI interface */ + gusbcfg.b.usbtrdtim = 9; + } else { + /* UTMI+ interface */ + if (GET_CORE_IF(pcd)-> + core_params->phy_utmi_width == 16) { + gusbcfg.b.usbtrdtim = utmi16b; + } else { + gusbcfg.b.usbtrdtim = utmi8b; + } + } + } + } else { + /* Full or low speed */ + gusbcfg.b.usbtrdtim = 9; + } + DWC_WRITE_REG32(&global_regs->gusbcfg, gusbcfg.d32); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.enumdone = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + return 1; +} + +/** + * This interrupt indicates that the ISO OUT Packet was dropped due to + * Rx FIFO full or Rx Status Queue Full. If this interrupt occurs + * read all the data from the Rx FIFO. + */ +int32_t dwc_otg_pcd_handle_isoc_out_packet_dropped_intr(dwc_otg_pcd_t * pcd) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + + DWC_WARN("INTERRUPT Handler not implemented for %s\n", + "ISOC Out Dropped"); + + intr_mask.b.isooutdrop = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.isooutdrop = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * This interrupt indicates the end of the portion of the micro-frame + * for periodic transactions. If there is a periodic transaction for + * the next frame, load the packets into the EP periodic Tx FIFO. + */ +int32_t dwc_otg_pcd_handle_end_periodic_frame_intr(dwc_otg_pcd_t * pcd) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "EOP"); + + intr_mask.b.eopframe = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.eopframe = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * This interrupt indicates that EP of the packet on the top of the + * non-periodic Tx FIFO does not match EP of the IN Token received. + * + * The "Device IN Token Queue" Registers are read to determine the + * order the IN Tokens have been received. The non-periodic Tx FIFO + * is flushed, so it can be reloaded in the order seen in the IN Token + * Queue. + */ +int32_t dwc_otg_pcd_handle_ep_mismatch_intr(dwc_otg_pcd_t * pcd) +{ + gintsts_data_t gintsts; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dctl_data_t dctl; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) { + core_if->start_predict = 1; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, core_if); + + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + if (!gintsts.b.ginnakeff) { + /* Disable EP Mismatch interrupt */ + intr_mask.d32 = 0; + intr_mask.b.epmismatch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, intr_mask.d32, 0); + /* Enable the Global IN NAK Effective Interrupt */ + intr_mask.d32 = 0; + intr_mask.b.ginnakeff = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, intr_mask.d32); + /* Set the global non-periodic IN NAK handshake */ + dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + dctl.b.sgnpinnak = 1; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); + } else { + DWC_PRINTF("gintsts.b.ginnakeff = 1! dctl.b.sgnpinnak not set\n"); + } + /* Disabling of all EP's will be done in dwc_otg_pcd_handle_in_nak_effective() + * handler after Global IN NAK Effective interrupt will be asserted */ + } + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.epmismatch = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This interrupt is valid only in DMA mode. This interrupt indicates that the + * core has stopped fetching data for IN endpoints due to the unavailability of + * TxFIFO space or Request Queue space. This interrupt is used by the + * application for an endpoint mismatch algorithm. + * + * @param pcd The PCD + */ +int32_t dwc_otg_pcd_handle_ep_fetsusp_intr(dwc_otg_pcd_t * pcd) +{ + gintsts_data_t gintsts; + gintmsk_data_t gintmsk_data; + dctl_data_t dctl; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, core_if); + + /* Clear the global non-periodic IN NAK handshake */ + dctl.d32 = 0; + dctl.b.cgnpinnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + + /* Mask GINTSTS.FETSUSP interrupt */ + gintmsk_data.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + gintmsk_data.b.fetsusp = 0; + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk_data.d32); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.fetsusp = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This funcion stalls EP0. + */ +static inline void ep0_do_stall(dwc_otg_pcd_t * pcd, const int err_val) +{ + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + usb_device_request_t *ctrl = &pcd->setup_pkt->req; + DWC_WARN("req: 0x%x 0x%x protocol STALL; err %d\n", + *(uint32_t*)ctrl, *((uint32_t*)ctrl + 1), err_val); + + ep0->dwc_ep.is_in = 1; + dwc_otg_ep_set_stall(GET_CORE_IF(pcd), &ep0->dwc_ep); + pcd->ep0.stopped = 1; + pcd->ep0state = EP0_IDLE; + ep0_out_start(GET_CORE_IF(pcd), pcd); +} + +/** + * This functions delegates the setup command to the gadget driver. + */ +static inline void do_gadget_setup(dwc_otg_pcd_t * pcd, + usb_device_request_t * ctrl) +{ + int ret = 0; + DWC_SPINUNLOCK(pcd->lock); + ret = pcd->fops->setup(pcd, (uint8_t *) ctrl); + DWC_SPINLOCK(pcd->lock); + if (ret < 0) { + ep0_do_stall(pcd, ret); + } + + /** @todo This is a g_file_storage gadget driver specific + * workaround: a DELAYED_STATUS result from the fsg_setup + * routine will result in the gadget queueing a EP0 IN status + * phase for a two-stage control transfer. Exactly the same as + * a SET_CONFIGURATION/SET_INTERFACE except that this is a class + * specific request. Need a generic way to know when the gadget + * driver will queue the status phase. Can we assume when we + * call the gadget driver setup() function that it will always + * queue and require the following flag? Need to look into + * this. + */ + + if (ret == 256 + 999) { + pcd->request_config = 1; + } +} + +#ifdef DWC_UTE_CFI +/** + * This functions delegates the CFI setup commands to the gadget driver. + * This function will return a negative value to indicate a failure. + */ +static inline int cfi_gadget_setup(dwc_otg_pcd_t * pcd, + struct cfi_usb_ctrlrequest *ctrl_req) +{ + int ret = 0; + + if (pcd->fops && pcd->fops->cfi_setup) { + DWC_SPINUNLOCK(pcd->lock); + ret = pcd->fops->cfi_setup(pcd, ctrl_req); + DWC_SPINLOCK(pcd->lock); + if (ret < 0) { + ep0_do_stall(pcd, ret); + return ret; + } + } + + return ret; +} +#endif + +/** + * This function starts the Zero-Length Packet for the IN status phase + * of a 2 stage control transfer. + */ +static inline void do_setup_in_status_phase(dwc_otg_pcd_t * pcd) +{ + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + if (pcd->ep0state == EP0_STALL) { + return; + } + + pcd->ep0state = EP0_IN_STATUS_PHASE; + + /* Prepare for more SETUP Packets */ + DWC_DEBUGPL(DBG_PCD, "EP0 IN ZLP\n"); + if ((GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_3_00a) + && (pcd->core_if->dma_desc_enable) + && (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len)) { + DWC_DEBUGPL(DBG_PCDV, + "Data terminated wait next packet in out_desc_addr\n"); + pcd->backup_buf = phys_to_virt(ep0->dwc_ep.dma_addr); + pcd->data_terminated = 1; + } + ep0->dwc_ep.xfer_len = 0; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.is_in = 1; + ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); + + /* Prepare for more SETUP Packets */ + //ep0_out_start(GET_CORE_IF(pcd), pcd); +} + +/** + * This function starts the Zero-Length Packet for the OUT status phase + * of a 2 stage control transfer. + */ +static inline void do_setup_out_status_phase(dwc_otg_pcd_t * pcd) +{ + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + if (pcd->ep0state == EP0_STALL) { + DWC_DEBUGPL(DBG_PCD, "EP0 STALLED\n"); + return; + } + pcd->ep0state = EP0_OUT_STATUS_PHASE; + + DWC_DEBUGPL(DBG_PCD, "EP0 OUT ZLP\n"); + ep0->dwc_ep.xfer_len = 0; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.is_in = 0; + ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); + + /* Prepare for more SETUP Packets */ + if (GET_CORE_IF(pcd)->dma_enable == 0) { + ep0_out_start(GET_CORE_IF(pcd), pcd); + } +} + +/** + * Clear the EP halt (STALL) and if pending requests start the + * transfer. + */ +static inline void pcd_clear_halt(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep) +{ + if (ep->dwc_ep.stall_clear_flag == 0) + dwc_otg_ep_clear_stall(GET_CORE_IF(pcd), &ep->dwc_ep); + + /* Reactive the EP */ + dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep); + if (ep->stopped) { + ep->stopped = 0; + /* If there is a request in the EP queue start it */ + + /** @todo FIXME: this causes an EP mismatch in DMA mode. + * epmismatch not yet implemented. */ + + /* + * Above fixme is solved by implmenting a tasklet to call the + * start_next_request(), outside of interrupt context at some + * time after the current time, after a clear-halt setup packet. + * Still need to implement ep mismatch in the future if a gadget + * ever uses more than one endpoint at once + */ + ep->queue_sof = 1; + DWC_TASK_SCHEDULE(pcd->start_xfer_tasklet); + } + /* Start Control Status Phase */ + do_setup_in_status_phase(pcd); +} + +/** + * This function is called when the SET_FEATURE TEST_MODE Setup packet + * is sent from the host. The Device Control register is written with + * the Test Mode bits set to the specified Test Mode. This is done as + * a tasklet so that the "Status" phase of the control transfer + * completes before transmitting the TEST packets. + * + * @todo This has not been tested since the tasklet struct was put + * into the PCD struct! + * + */ +void do_test_mode(void *data) +{ + dctl_data_t dctl; + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) data; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + int test_mode = pcd->test_mode; + +// DWC_WARN("%s() has not been tested since being rewritten!\n", __func__); + + dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + switch (test_mode) { + case 1: // TEST_J + dctl.b.tstctl = 1; + break; + + case 2: // TEST_K + dctl.b.tstctl = 2; + break; + + case 3: // TEST_SE0_NAK + dctl.b.tstctl = 3; + break; + + case 4: // TEST_PACKET + dctl.b.tstctl = 4; + break; + + case 5: // TEST_FORCE_ENABLE + dctl.b.tstctl = 5; + break; + } + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); +} + +/** + * This function process the GET_STATUS Setup Commands. + */ +static inline void do_get_status(dwc_otg_pcd_t * pcd) +{ + usb_device_request_t ctrl = pcd->setup_pkt->req; + dwc_otg_pcd_ep_t *ep; + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + uint16_t *status = pcd->status_buf; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, + "GET_STATUS %02x.%02x v%04x i%04x l%04x\n", + ctrl.bmRequestType, ctrl.bRequest, + UGETW(ctrl.wValue), UGETW(ctrl.wIndex), + UGETW(ctrl.wLength)); +#endif + + switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) { + case UT_DEVICE: + if (UGETW(ctrl.wIndex) == 0xF000) { /* OTG Status selector */ + DWC_PRINTF("wIndex - %d\n", UGETW(ctrl.wIndex)); + DWC_PRINTF("OTG VERSION - %d\n", core_if->otg_ver); + DWC_PRINTF("OTG CAP - %d, %d\n", + core_if->core_params->otg_cap, + DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE); + if (core_if->otg_ver == 1 + && core_if->core_params->otg_cap == + DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + uint8_t *otgsts = (uint8_t *) pcd->status_buf; + *otgsts = (core_if->otg_sts & 0x1); + pcd->ep0_pending = 1; + ep0->dwc_ep.start_xfer_buff = + (uint8_t *) otgsts; + ep0->dwc_ep.xfer_buff = (uint8_t *) otgsts; + ep0->dwc_ep.dma_addr = + pcd->status_buf_dma_handle; + ep0->dwc_ep.xfer_len = 1; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + return; + } else { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + break; + } else { + *status = 0x1; /* Self powered */ + *status |= pcd->remote_wakeup_enable << 1; + break; + } + case UT_INTERFACE: + *status = 0; + break; + + case UT_ENDPOINT: + ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex)); + if (ep == 0 || UGETW(ctrl.wLength) > 2) { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + /** @todo check for EP stall */ + *status = ep->stopped; + break; + } + pcd->ep0_pending = 1; + ep0->dwc_ep.start_xfer_buff = (uint8_t *) status; + ep0->dwc_ep.xfer_buff = (uint8_t *) status; + ep0->dwc_ep.dma_addr = pcd->status_buf_dma_handle; + ep0->dwc_ep.xfer_len = 2; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); +} + +/** + * This function process the SET_FEATURE Setup Commands. + */ +static inline void do_set_feature(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + usb_device_request_t ctrl = pcd->setup_pkt->req; + dwc_otg_pcd_ep_t *ep = 0; + int32_t otg_cap_param = core_if->core_params->otg_cap; + gotgctl_data_t gotgctl = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_PCD, "SET_FEATURE:%02x.%02x v%04x i%04x l%04x\n", + ctrl.bmRequestType, ctrl.bRequest, + UGETW(ctrl.wValue), UGETW(ctrl.wIndex), + UGETW(ctrl.wLength)); + DWC_DEBUGPL(DBG_PCD, "otg_cap=%d\n", otg_cap_param); + + switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) { + case UT_DEVICE: + switch (UGETW(ctrl.wValue)) { + case UF_DEVICE_REMOTE_WAKEUP: + pcd->remote_wakeup_enable = 1; + break; + + case UF_TEST_MODE: + /* Setup the Test Mode tasklet to do the Test + * Packet generation after the SETUP Status + * phase has completed. */ + + /** @todo This has not been tested since the + * tasklet struct was put into the PCD + * struct! */ + pcd->test_mode = UGETW(ctrl.wIndex) >> 8; + DWC_TASK_SCHEDULE(pcd->test_mode_tasklet); + break; + + case UF_DEVICE_B_HNP_ENABLE: + DWC_DEBUGPL(DBG_PCDV, + "SET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n"); + + /* dev may initiate HNP */ + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->b_hnp_enable = 1; + dwc_otg_pcd_update_otg(pcd, 0); + DWC_DEBUGPL(DBG_PCD, "Request B HNP\n"); + /**@todo Is the gotgctl.devhnpen cleared + * by a USB Reset? */ + gotgctl.b.devhnpen = 1; + gotgctl.b.hnpreq = 1; + DWC_WRITE_REG32(&global_regs->gotgctl, + gotgctl.d32); + } else { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + break; + + case UF_DEVICE_A_HNP_SUPPORT: + /* RH port supports HNP */ + DWC_DEBUGPL(DBG_PCDV, + "SET_FEATURE: USB_DEVICE_A_HNP_SUPPORT\n"); + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->a_hnp_support = 1; + dwc_otg_pcd_update_otg(pcd, 0); + } else { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + break; + + case UF_DEVICE_A_ALT_HNP_SUPPORT: + /* other RH port does */ + DWC_DEBUGPL(DBG_PCDV, + "SET_FEATURE: USB_DEVICE_A_ALT_HNP_SUPPORT\n"); + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->a_alt_hnp_support = 1; + dwc_otg_pcd_update_otg(pcd, 0); + } else { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + break; + + default: + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + + } + do_setup_in_status_phase(pcd); + break; + + case UT_INTERFACE: + do_gadget_setup(pcd, &ctrl); + break; + + case UT_ENDPOINT: + if (UGETW(ctrl.wValue) == UF_ENDPOINT_HALT) { + ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex)); + if (ep == 0) { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + ep->stopped = 1; + dwc_otg_ep_set_stall(core_if, &ep->dwc_ep); + } + do_setup_in_status_phase(pcd); + break; + } +} + +/** + * This function process the CLEAR_FEATURE Setup Commands. + */ +static inline void do_clear_feature(dwc_otg_pcd_t * pcd) +{ + usb_device_request_t ctrl = pcd->setup_pkt->req; + dwc_otg_pcd_ep_t *ep = 0; + + DWC_DEBUGPL(DBG_PCD, + "CLEAR_FEATURE:%02x.%02x v%04x i%04x l%04x\n", + ctrl.bmRequestType, ctrl.bRequest, + UGETW(ctrl.wValue), UGETW(ctrl.wIndex), + UGETW(ctrl.wLength)); + + switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) { + case UT_DEVICE: + switch (UGETW(ctrl.wValue)) { + case UF_DEVICE_REMOTE_WAKEUP: + pcd->remote_wakeup_enable = 0; + break; + + case UF_TEST_MODE: + /** @todo Add CLEAR_FEATURE for TEST modes. */ + break; + + default: + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + do_setup_in_status_phase(pcd); + break; + + case UT_ENDPOINT: + ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex)); + if (ep == 0) { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + + pcd_clear_halt(pcd, ep); + + break; + } +} + +/** + * This function process the SET_ADDRESS Setup Commands. + */ +static inline void do_set_address(dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if; + usb_device_request_t ctrl = pcd->setup_pkt->req; + + if (ctrl.bmRequestType == UT_DEVICE) { + dcfg_data_t dcfg = {.d32 = 0 }; + +#ifdef DEBUG_EP0 +// DWC_DEBUGPL(DBG_PCDV, "SET_ADDRESS:%d\n", ctrl.wValue); +#endif + dcfg.b.devaddr = UGETW(ctrl.wValue); + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dcfg, 0, dcfg.d32); + do_setup_in_status_phase(pcd); + } +} + +/** + * This function processes SETUP commands. In Linux, the USB Command + * processing is done in two places - the first being the PCD and the + * second in the Gadget Driver (for example, the File-Backed Storage + * Gadget Driver). + * + *
Parameter NameMeaning
otg_capSpecifies the OTG capabilities. The driver will automatically detect the + value for this parameter if none is specified. + - 0: HNP and SRP capable (default, if available) + - 1: SRP Only capable + - 2: No HNP/SRP capable +
dma_enableSpecifies whether to use slave or DMA mode for accessing the data FIFOs. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: Slave + - 1: DMA (default, if available) +
dma_burst_sizeThe DMA Burst size (applicable only for External DMA Mode). + - Values: 1, 4, 8 16, 32, 64, 128, 256 (default 32) +
speedSpecifies the maximum speed of operation in host and device mode. The + actual speed depends on the speed of the attached device and the value of + phy_type. + - 0: High Speed (default) + - 1: Full Speed +
host_support_fs_ls_low_powerSpecifies whether low power mode is supported when attached to a Full + Speed or Low Speed device in host mode. + - 0: Don't support low power mode (default) + - 1: Support low power mode +
host_ls_low_power_phy_clkSpecifies the PHY clock rate in low power mode when connected to a Low + Speed device in host mode. This parameter is applicable only if + HOST_SUPPORT_FS_LS_LOW_POWER is enabled. + - 0: 48 MHz (default) + - 1: 6 MHz +
enable_dynamic_fifo Specifies whether FIFOs may be resized by the driver software. + - 0: Use cC FIFO size parameters + - 1: Allow dynamic FIFO sizing (default) +
data_fifo_sizeTotal number of 4-byte words in the data FIFO memory. This memory + includes the Rx FIFO, non-periodic Tx FIFO, and periodic Tx FIFOs. + - Values: 32 to 32768 (default 8192) + + Note: The total FIFO memory depth in the FPGA configuration is 8192. +
dev_rx_fifo_sizeNumber of 4-byte words in the Rx FIFO in device mode when dynamic + FIFO sizing is enabled. + - Values: 16 to 32768 (default 1064) +
dev_nperio_tx_fifo_sizeNumber of 4-byte words in the non-periodic Tx FIFO in device mode when + dynamic FIFO sizing is enabled. + - Values: 16 to 32768 (default 1024) +
dev_perio_tx_fifo_size_n (n = 1 to 15)Number of 4-byte words in each of the periodic Tx FIFOs in device mode + when dynamic FIFO sizing is enabled. + - Values: 4 to 768 (default 256) +
host_rx_fifo_sizeNumber of 4-byte words in the Rx FIFO in host mode when dynamic FIFO + sizing is enabled. + - Values: 16 to 32768 (default 1024) +
host_nperio_tx_fifo_sizeNumber of 4-byte words in the non-periodic Tx FIFO in host mode when + dynamic FIFO sizing is enabled in the core. + - Values: 16 to 32768 (default 1024) +
host_perio_tx_fifo_sizeNumber of 4-byte words in the host periodic Tx FIFO when dynamic FIFO + sizing is enabled. + - Values: 16 to 32768 (default 1024) +
max_transfer_sizeThe maximum transfer size supported in bytes. + - Values: 2047 to 65,535 (default 65,535) +
max_packet_countThe maximum number of packets in a transfer. + - Values: 15 to 511 (default 511) +
host_channelsThe number of host channel registers to use. + - Values: 1 to 16 (default 12) + + Note: The FPGA configuration supports a maximum of 12 host channels. +
dev_endpointsThe number of endpoints in addition to EP0 available for device mode + operations. + - Values: 1 to 15 (default 6 IN and OUT) + + Note: The FPGA configuration supports a maximum of 6 IN and OUT endpoints in + addition to EP0. +
phy_typeSpecifies the type of PHY interface to use. By default, the driver will + automatically detect the phy_type. + - 0: Full Speed + - 1: UTMI+ (default, if available) + - 2: ULPI +
phy_utmi_widthSpecifies the UTMI+ Data Width. This parameter is applicable for a + phy_type of UTMI+. Also, this parameter is applicable only if the + OTG_HSPHY_WIDTH cC parameter was set to "8 and 16 bits", meaning that the + core has been configured to work at either data path width. + - Values: 8 or 16 bits (default 16) +
phy_ulpi_ddrSpecifies whether the ULPI operates at double or single data rate. This + parameter is only applicable if phy_type is ULPI. + - 0: single data rate ULPI interface with 8 bit wide data bus (default) + - 1: double data rate ULPI interface with 4 bit wide data bus +
i2c_enableSpecifies whether to use the I2C interface for full speed PHY. This + parameter is only applicable if PHY_TYPE is FS. + - 0: Disabled (default) + - 1: Enabled +
otg_en_multiple_tx_fifoSpecifies whether dedicatedto tx fifos are enabled for non periodic IN EPs. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: Disabled + - 1: Enabled (default, if available) +
dev_tx_fifo_size_n (n = 1 to 15)Number of 4-byte words in each of the Tx FIFOs in device mode + when dynamic FIFO sizing is enabled. + - Values: 4 to 768 (default 256) +
tx_thr_lengthTransmit Threshold length in 32 bit double words + - Values: 8 to 128 (default 64) +
rx_thr_lengthReceive Threshold length in 32 bit double words + - Values: 8 to 128 (default 64) +
thr_ctlSpecifies whether to enable Thresholding for Device mode. Bits 0, 1, 2 of this + parmater specifies if thresholding is enabled for non-Iso Tx, Iso Tx and Rx + transfers accordingly. + The driver will automatically detect the value for this parameter if none is + specified. + - Values: 0 to 7 (default 0) + Bit values indicate: + - 0: Thresholding disabled + - 1: Thresholding enabled +
dma_desc_enableSpecifies whether to enable Descriptor DMA mode. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: Descriptor DMA disabled + - 1: Descriptor DMA (default, if available) +
mpi_enableSpecifies whether to enable MPI enhancement mode. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: MPI disabled (default) + - 1: MPI enable +
pti_enableSpecifies whether to enable PTI enhancement support. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: PTI disabled (default) + - 1: PTI enable +
lpm_enableSpecifies whether to enable LPM support. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: LPM disabled + - 1: LPM enable (default, if available) +
ahb_thr_ratioSpecifies AHB Threshold ratio. + - Values: 0 to 3 (default 0) +
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Command Driver Description
GET_STATUS PCD Command is processed as + * defined in chapter 9 of the USB 2.0 Specification chapter 9 + *
CLEAR_FEATURE PCD The Device and Endpoint + * requests are the ENDPOINT_HALT feature is procesed, all others the + * interface requests are ignored.
SET_FEATURE PCD The Device and Endpoint + * requests are processed by the PCD. Interface requests are passed + * to the Gadget Driver.
SET_ADDRESS PCD Program the DCFG reg, + * with device address received
GET_DESCRIPTOR Gadget Driver Return the + * requested descriptor
SET_DESCRIPTOR Gadget Driver Optional - + * not implemented by any of the existing Gadget Drivers.
SET_CONFIGURATION Gadget Driver Disable + * all EPs and enable EPs for new configuration.
GET_CONFIGURATION Gadget Driver Return + * the current configuration
SET_INTERFACE Gadget Driver Disable all + * EPs and enable EPs for new configuration.
GET_INTERFACE Gadget Driver Return the + * current interface.
SYNC_FRAME PCD Display debug + * message.
+ * + * When the SETUP Phase Done interrupt occurs, the PCD SETUP commands are + * processed by pcd_setup. Calling the Function Driver's setup function from + * pcd_setup processes the gadget SETUP commands. + */ +static inline void pcd_setup(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + usb_device_request_t ctrl = pcd->setup_pkt->req; + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + + deptsiz0_data_t doeptsize0 = {.d32 = 0 }; + +#ifdef DWC_UTE_CFI + int retval = 0; + struct cfi_usb_ctrlrequest cfi_req; +#endif + + doeptsize0.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doeptsiz); + + /** In BDMA more then 1 setup packet is not supported till 3.00a */ + if (core_if->dma_enable && core_if->dma_desc_enable == 0 + && (doeptsize0.b.supcnt < 2) + && (core_if->snpsid < OTG_CORE_REV_2_94a)) { + DWC_ERROR + ("\n\n----------- CANNOT handle > 1 setup packet in DMA mode\n\n"); + } + if ((core_if->snpsid >= OTG_CORE_REV_3_00a) + && (core_if->dma_enable == 1) && (core_if->dma_desc_enable == 0)) { + ctrl = + (pcd->setup_pkt + + (3 - doeptsize0.b.supcnt - 1 + + ep0->dwc_ep.stp_rollover))->req; + } +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "SETUP %02x.%02x v%04x i%04x l%04x\n", + ctrl.bmRequestType, ctrl.bRequest, + UGETW(ctrl.wValue), UGETW(ctrl.wIndex), + UGETW(ctrl.wLength)); +#endif + + /* Clean up the request queue */ + dwc_otg_request_nuke(ep0); + ep0->stopped = 0; + + if (ctrl.bmRequestType & UE_DIR_IN) { + ep0->dwc_ep.is_in = 1; + pcd->ep0state = EP0_IN_DATA_PHASE; + } else { + ep0->dwc_ep.is_in = 0; + pcd->ep0state = EP0_OUT_DATA_PHASE; + } + + if (UGETW(ctrl.wLength) == 0) { + ep0->dwc_ep.is_in = 1; + pcd->ep0state = EP0_IN_STATUS_PHASE; + } + + if (UT_GET_TYPE(ctrl.bmRequestType) != UT_STANDARD) { + +#ifdef DWC_UTE_CFI + DWC_MEMCPY(&cfi_req, &ctrl, sizeof(usb_device_request_t)); + + //printk(KERN_ALERT "CFI: req_type=0x%02x; req=0x%02x\n", + ctrl.bRequestType, ctrl.bRequest); + if (UT_GET_TYPE(cfi_req.bRequestType) == UT_VENDOR) { + if (cfi_req.bRequest > 0xB0 && cfi_req.bRequest < 0xBF) { + retval = cfi_setup(pcd, &cfi_req); + if (retval < 0) { + ep0_do_stall(pcd, retval); + pcd->ep0_pending = 0; + return; + } + + /* if need gadget setup then call it and check the retval */ + if (pcd->cfi->need_gadget_att) { + retval = + cfi_gadget_setup(pcd, + &pcd-> + cfi->ctrl_req); + if (retval < 0) { + pcd->ep0_pending = 0; + return; + } + } + + if (pcd->cfi->need_status_in_complete) { + do_setup_in_status_phase(pcd); + } + return; + } + } +#endif + + /* handle non-standard (class/vendor) requests in the gadget driver */ + do_gadget_setup(pcd, &ctrl); + return; + } + + /** @todo NGS: Handle bad setup packet? */ + +/////////////////////////////////////////// +//// --- Standard Request handling --- //// + + switch (ctrl.bRequest) { + case UR_GET_STATUS: + do_get_status(pcd); + break; + + case UR_CLEAR_FEATURE: + do_clear_feature(pcd); + break; + + case UR_SET_FEATURE: + do_set_feature(pcd); + break; + + case UR_SET_ADDRESS: + do_set_address(pcd); + break; + + case UR_SET_INTERFACE: + case UR_SET_CONFIG: +// _pcd->request_config = 1; /* Configuration changed */ + pcd->reinit_flag = 0; + do_gadget_setup(pcd, &ctrl); + break; + + case UR_SYNCH_FRAME: + do_gadget_setup(pcd, &ctrl); + break; + + default: + /* Call the Gadget Driver's setup functions */ + do_gadget_setup(pcd, &ctrl); + break; + } +} + +/** + * This function completes the ep0 control transfer. + */ +static int32_t ep0_complete_request(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_dev_in_ep_regs_t *in_ep_regs = + dev_if->in_ep_regs[ep->dwc_ep.num]; +#ifdef DEBUG_EP0 + dwc_otg_dev_out_ep_regs_t *out_ep_regs = + dev_if->out_ep_regs[ep->dwc_ep.num]; +#endif + deptsiz0_data_t deptsiz; + dev_dma_desc_sts_t desc_sts; + dwc_otg_pcd_request_t *req; + int is_last = 0; + dwc_otg_pcd_t *pcd = ep->pcd; + +#ifdef DWC_UTE_CFI + struct cfi_usb_ctrlrequest *ctrlreq; + int retval = -DWC_E_NOT_SUPPORTED; +#endif + + if (pcd->ep0_pending && DWC_CIRCLEQ_EMPTY(&ep->queue)) { + if (ep->dwc_ep.is_in) { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "Do setup OUT status phase\n"); +#endif + do_setup_out_status_phase(pcd); + } else { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "Do setup IN status phase\n"); +#endif + +#ifdef DWC_UTE_CFI + ctrlreq = &pcd->cfi->ctrl_req; + + if (UT_GET_TYPE(ctrlreq->bRequestType) == UT_VENDOR) { + if (ctrlreq->bRequest > 0xB0 + && ctrlreq->bRequest < 0xBF) { + + /* Return if the PCD failed to handle the request */ + if ((retval = + pcd->cfi->ops. + ctrl_write_complete(pcd->cfi, + pcd)) < 0) { + CFI_INFO + ("ERROR setting a new value in the PCD(%d)\n", + retval); + ep0_do_stall(pcd, retval); + pcd->ep0_pending = 0; + return 0; + } + + /* If the gadget needs to be notified on the request */ + if (pcd->cfi->need_gadget_att == 1) { + //retval = do_gadget_setup(pcd, &pcd->cfi->ctrl_req); + retval = + cfi_gadget_setup(pcd, + &pcd->cfi-> + ctrl_req); + + /* Return from the function if the gadget failed to process + * the request properly - this should never happen !!! + */ + if (retval < 0) { + CFI_INFO + ("ERROR setting a new value in the gadget(%d)\n", + retval); + pcd->ep0_pending = 0; + return 0; + } + } + + CFI_INFO("%s: RETVAL=%d\n", __func__, + retval); + /* If we hit here then the PCD and the gadget has properly + * handled the request - so send the ZLP IN to the host. + */ + /* @todo: MAS - decide whether we need to start the setup + * stage based on the need_setup value of the cfi object + */ + do_setup_in_status_phase(pcd); + pcd->ep0_pending = 0; + return 1; + } + } +#endif + + do_setup_in_status_phase(pcd); + } + pcd->ep0_pending = 0; + return 1; + } + + if (DWC_CIRCLEQ_EMPTY(&ep->queue)) { + return 0; + } + req = DWC_CIRCLEQ_FIRST(&ep->queue); + + if (pcd->ep0state == EP0_OUT_STATUS_PHASE + || pcd->ep0state == EP0_IN_STATUS_PHASE) { + is_last = 1; + } else if (ep->dwc_ep.is_in) { + deptsiz.d32 = DWC_READ_REG32(&in_ep_regs->dieptsiz); + if (core_if->dma_desc_enable != 0) + desc_sts = dev_if->in_desc_addr->status; +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "%d len=%d xfersize=%d pktcnt=%d\n", + ep->dwc_ep.num, ep->dwc_ep.xfer_len, + deptsiz.b.xfersize, deptsiz.b.pktcnt); +#endif + + if (((core_if->dma_desc_enable == 0) + && (deptsiz.b.xfersize == 0)) + || ((core_if->dma_desc_enable != 0) + && (desc_sts.b.bytes == 0))) { + req->actual = ep->dwc_ep.xfer_count; + /* Is a Zero Len Packet needed? */ + if (req->sent_zlp) { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "Setup Rx ZLP\n"); +#endif + req->sent_zlp = 0; + } + do_setup_out_status_phase(pcd); + } + } else { + /* ep0-OUT */ +#ifdef DEBUG_EP0 + deptsiz.d32 = DWC_READ_REG32(&out_ep_regs->doeptsiz); + DWC_DEBUGPL(DBG_PCDV, "%d len=%d xsize=%d pktcnt=%d\n", + ep->dwc_ep.num, ep->dwc_ep.xfer_len, + deptsiz.b.xfersize, deptsiz.b.pktcnt); +#endif + req->actual = ep->dwc_ep.xfer_count; + + /* Is a Zero Len Packet needed? */ + if (req->sent_zlp) { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "Setup Tx ZLP\n"); +#endif + req->sent_zlp = 0; + } + /* For older cores do setup in status phase in Slave/BDMA modes, + * starting from 3.00 do that only in slave, and for DMA modes + * just re-enable ep 0 OUT here*/ + if (core_if->dma_enable == 0 + || (core_if->dma_desc_enable == 0 + && core_if->snpsid <= OTG_CORE_REV_2_94a)) { + do_setup_in_status_phase(pcd); + } else if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + DWC_DEBUGPL(DBG_PCDV, + "Enable out ep before in status phase\n"); + ep0_out_start(core_if, pcd); + } + } + + /* Complete the request */ + if (is_last) { + dwc_otg_request_done(ep, req, 0); + ep->dwc_ep.start_xfer_buff = 0; + ep->dwc_ep.xfer_buff = 0; + ep->dwc_ep.xfer_len = 0; + return 1; + } + return 0; +} + +#ifdef DWC_UTE_CFI +/** + * This function calculates traverses all the CFI DMA descriptors and + * and accumulates the bytes that are left to be transfered. + * + * @return The total bytes left to transfered, or a negative value as failure + */ +static inline int cfi_calc_desc_residue(dwc_otg_pcd_ep_t * ep) +{ + int32_t ret = 0; + int i; + struct dwc_otg_dma_desc *ddesc = NULL; + struct cfi_ep *cfiep; + + /* See if the pcd_ep has its respective cfi_ep mapped */ + cfiep = get_cfi_ep_by_pcd_ep(ep->pcd->cfi, ep); + if (!cfiep) { + CFI_INFO("%s: Failed to find ep\n", __func__); + return -1; + } + + ddesc = ep->dwc_ep.descs; + + for (i = 0; (i < cfiep->desc_count) && (i < MAX_DMA_DESCS_PER_EP); i++) { + +#if defined(PRINT_CFI_DMA_DESCS) + print_desc(ddesc, ep->ep.name, i); +#endif + ret += ddesc->status.b.bytes; + ddesc++; + } + + if (ret) + CFI_INFO("!!!!!!!!!! WARNING (%s) - residue=%d\n", __func__, + ret); + + return ret; +} +#endif + +/** + * This function completes the request for the EP. If there are + * additional requests for the EP in the queue they will be started. + */ +static void complete_ep(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_dev_in_ep_regs_t *in_ep_regs = + dev_if->in_ep_regs[ep->dwc_ep.num]; + deptsiz_data_t deptsiz; + dev_dma_desc_sts_t desc_sts; + dwc_otg_pcd_request_t *req = 0; + dwc_otg_dev_dma_desc_t *dma_desc; + uint32_t byte_count = 0; + int is_last = 0; + int i; + + DWC_DEBUGPL(DBG_PCDV, "%s() %d-%s\n", __func__, ep->dwc_ep.num, + (ep->dwc_ep.is_in ? "IN" : "OUT")); + + /* Get any pending requests */ + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (!req) { + DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep); + return; + } + } else { + DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep); + return; + } + + DWC_DEBUGPL(DBG_PCD, "Requests %d\n", ep->pcd->request_pending); + + if (ep->dwc_ep.is_in) { + deptsiz.d32 = DWC_READ_REG32(&in_ep_regs->dieptsiz); + + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + if (deptsiz.b.xfersize == 0 + && deptsiz.b.pktcnt == 0) { + byte_count = + ep->dwc_ep.xfer_len - + ep->dwc_ep.xfer_count; + + ep->dwc_ep.xfer_count += byte_count; + + DWC_DEBUGPL(DBG_PCDV, + "%d-%s len=%d xfersize=%d pktcnt=%d\n", + ep->dwc_ep.num, + (ep->dwc_ep. + is_in ? "IN" : "OUT"), + ep->dwc_ep.xfer_len, + deptsiz.b.xfersize, + deptsiz.b.pktcnt); + + if (ep->dwc_ep.xfer_len < + ep->dwc_ep.total_len) { + dwc_otg_ep_start_transfer + (core_if, &ep->dwc_ep, req); + } else if (ep->dwc_ep.sent_zlp) { + /* + * This fragment of code should initiate 0 + * length transfer in case if it is queued + * a transfer with size divisible to EPs max + * packet size and with usb_request zero field + * is set, which means that after data is transfered, + * it is also should be transfered + * a 0 length packet at the end. For Slave and + * Buffer DMA modes in this case SW has + * to initiate 2 transfers one with transfer size, + * and the second with 0 size. For Descriptor + * DMA mode SW is able to initiate a transfer, + * which will handle all the packets including + * the last 0 length. + */ + ep->dwc_ep.sent_zlp = 0; + dwc_otg_ep_start_zl_transfer + (core_if, &ep->dwc_ep); + } else { + is_last = 1; + } + } else { + if (ep->dwc_ep.type == + DWC_OTG_EP_TYPE_ISOC) { + req->actual = 0; + dwc_otg_request_done(ep, req, 0); + + ep->dwc_ep.start_xfer_buff = 0; + ep->dwc_ep.xfer_buff = 0; + ep->dwc_ep.xfer_len = 0; + + /* If there is a request in the queue start it. */ + start_next_request(ep); + } else + DWC_WARN + ("Incomplete transfer (%d - %s [siz=%d pkt=%d])\n", + ep->dwc_ep.num, + (ep->dwc_ep.is_in ? "IN" : "OUT"), + deptsiz.b.xfersize, + deptsiz.b.pktcnt); + } + } else { + dma_desc = ep->dwc_ep.desc_addr; + byte_count = 0; + ep->dwc_ep.sent_zlp = 0; + +#ifdef DWC_UTE_CFI + CFI_INFO("%s: BUFFER_MODE=%d\n", __func__, + ep->dwc_ep.buff_mode); + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + int residue; + + residue = cfi_calc_desc_residue(ep); + if (residue < 0) + return; + + byte_count = residue; + } else { +#endif + for (i = 0; i < ep->dwc_ep.desc_cnt; + ++i) { + desc_sts = dma_desc->status; + byte_count += desc_sts.b.bytes; + dma_desc++; + } +#ifdef DWC_UTE_CFI + } +#endif + if (byte_count == 0) { + ep->dwc_ep.xfer_count = + ep->dwc_ep.total_len; + is_last = 1; + } else { + DWC_WARN("Incomplete transfer\n"); + } + } + } else { + if (deptsiz.b.xfersize == 0 && deptsiz.b.pktcnt == 0) { + DWC_DEBUGPL(DBG_PCDV, + "%d-%s len=%d xfersize=%d pktcnt=%d\n", + ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT", + ep->dwc_ep.xfer_len, + deptsiz.b.xfersize, + deptsiz.b.pktcnt); + + /* Check if the whole transfer was completed, + * if no, setup transfer for next portion of data + */ + if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) { + dwc_otg_ep_start_transfer(core_if, + &ep->dwc_ep, req); + } else if (ep->dwc_ep.sent_zlp) { + /* + * This fragment of code should initiate 0 + * length trasfer in case if it is queued + * a trasfer with size divisible to EPs max + * packet size and with usb_request zero field + * is set, which means that after data is transfered, + * it is also should be transfered + * a 0 length packet at the end. For Slave and + * Buffer DMA modes in this case SW has + * to initiate 2 transfers one with transfer size, + * and the second with 0 size. For Desriptor + * DMA mode SW is able to initiate a transfer, + * which will handle all the packets including + * the last 0 legth. + */ + ep->dwc_ep.sent_zlp = 0; + dwc_otg_ep_start_zl_transfer(core_if, + &ep->dwc_ep); + } else { + is_last = 1; + } + } else { + DWC_WARN + ("Incomplete transfer (%d-%s [siz=%d pkt=%d])\n", + ep->dwc_ep.num, + (ep->dwc_ep.is_in ? "IN" : "OUT"), + deptsiz.b.xfersize, deptsiz.b.pktcnt); + } + } + } else { + dwc_otg_dev_out_ep_regs_t *out_ep_regs = + dev_if->out_ep_regs[ep->dwc_ep.num]; + desc_sts.d32 = 0; + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + dma_desc = ep->dwc_ep.desc_addr; + byte_count = 0; + ep->dwc_ep.sent_zlp = 0; + +#ifdef DWC_UTE_CFI + CFI_INFO("%s: BUFFER_MODE=%d\n", __func__, + ep->dwc_ep.buff_mode); + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + int residue; + residue = cfi_calc_desc_residue(ep); + if (residue < 0) + return; + byte_count = residue; + } else { +#endif + + for (i = 0; i < ep->dwc_ep.desc_cnt; + ++i) { + desc_sts = dma_desc->status; + byte_count += desc_sts.b.bytes; + dma_desc++; + } + +#ifdef DWC_UTE_CFI + } +#endif + /* Checking for interrupt Out transfers with not + * dword aligned mps sizes + */ + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_INTR && + (ep->dwc_ep.maxpacket % 4)) { + ep->dwc_ep.xfer_count = + ep->dwc_ep.total_len - byte_count; + if ((ep->dwc_ep.xfer_len % + ep->dwc_ep.maxpacket) + && (ep->dwc_ep.xfer_len / + ep->dwc_ep.maxpacket < + MAX_DMA_DESC_CNT)) + ep->dwc_ep.xfer_len -= + (ep->dwc_ep.desc_cnt - + 1) * ep->dwc_ep.maxpacket + + ep->dwc_ep.xfer_len % + ep->dwc_ep.maxpacket; + else + ep->dwc_ep.xfer_len -= + ep->dwc_ep.desc_cnt * + ep->dwc_ep.maxpacket; + if (ep->dwc_ep.xfer_len > 0) { + dwc_otg_ep_start_transfer + (core_if, &ep->dwc_ep, req); + } else { + is_last = 1; + } + } else { + ep->dwc_ep.xfer_count = + ep->dwc_ep.total_len - byte_count + + ((4 - (ep->dwc_ep.total_len & 0x3)) & 0x3); + is_last = 1; + } + } else { + deptsiz.d32 = 0; + deptsiz.d32 = + DWC_READ_REG32(&out_ep_regs->doeptsiz); + + byte_count = (ep->dwc_ep.xfer_len - + ep->dwc_ep.xfer_count - + deptsiz.b.xfersize); + ep->dwc_ep.xfer_buff += byte_count; + ep->dwc_ep.dma_addr += byte_count; + ep->dwc_ep.xfer_count += byte_count; + + /* Check if the whole transfer was completed, + * if no, setup transfer for next portion of data + */ + if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) { + dwc_otg_ep_start_transfer(core_if, + &ep->dwc_ep, req); + } else if (ep->dwc_ep.sent_zlp) { + /* + * This fragment of code should initiate 0 + * length trasfer in case if it is queued + * a trasfer with size divisible to EPs max + * packet size and with usb_request zero field + * is set, which means that after data is transfered, + * it is also should be transfered + * a 0 length packet at the end. For Slave and + * Buffer DMA modes in this case SW has + * to initiate 2 transfers one with transfer size, + * and the second with 0 size. For Desriptor + * DMA mode SW is able to initiate a transfer, + * which will handle all the packets including + * the last 0 legth. + */ + ep->dwc_ep.sent_zlp = 0; + dwc_otg_ep_start_zl_transfer(core_if, + &ep->dwc_ep); + } else { + is_last = 1; + } + } + } else { + /* Check if the whole transfer was completed, + * if no, setup transfer for next portion of data + */ + if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) { + dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep, req); + } else if (ep->dwc_ep.sent_zlp) { + /* + * This fragment of code should initiate 0 + * length transfer in case if it is queued + * a transfer with size divisible to EPs max + * packet size and with usb_request zero field + * is set, which means that after data is transfered, + * it is also should be transfered + * a 0 length packet at the end. For Slave and + * Buffer DMA modes in this case SW has + * to initiate 2 transfers one with transfer size, + * and the second with 0 size. For Descriptor + * DMA mode SW is able to initiate a transfer, + * which will handle all the packets including + * the last 0 length. + */ + ep->dwc_ep.sent_zlp = 0; + dwc_otg_ep_start_zl_transfer(core_if, + &ep->dwc_ep); + } else { + is_last = 1; + } + } + + DWC_DEBUGPL(DBG_PCDV, + "addr %p, %d-%s len=%d cnt=%d xsize=%d pktcnt=%d\n", + &out_ep_regs->doeptsiz, ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT", + ep->dwc_ep.xfer_len, ep->dwc_ep.xfer_count, + deptsiz.b.xfersize, deptsiz.b.pktcnt); + } + + /* Complete the request */ + if (is_last) { +#ifdef DWC_UTE_CFI + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + req->actual = ep->dwc_ep.cfi_req_len - byte_count; + } else { +#endif + req->actual = ep->dwc_ep.xfer_count; +#ifdef DWC_UTE_CFI + } +#endif + if (req->dw_align_buf) { + if (!ep->dwc_ep.is_in) + dwc_memcpy(req->buf, req->dw_align_buf, req->length); + dma_unmap_single(get_gadget_wrapper_device(), + req->dw_align_buf_dma, req->length, + (ep->dwc_ep.num == 0) ? DMA_BIDIRECTIONAL : + ((ep->dwc_ep.is_in) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE)); + req->dw_align_buf_dma = DWC_DMA_ADDR_INVALID; + kfree(req->dw_align_buf); + } + + dwc_otg_request_done(ep, req, 0); + + ep->dwc_ep.start_xfer_buff = 0; + ep->dwc_ep.xfer_buff = 0; + ep->dwc_ep.xfer_len = 0; + + /* If there is a request in the queue start it. */ + start_next_request(ep); + } +} + +#ifdef DWC_EN_ISOC + +/** + * This function BNA interrupt for Isochronous EPs + * + */ +static void dwc_otg_pcd_handle_iso_bna(dwc_otg_pcd_ep_t * ep) +{ + dwc_ep_t *dwc_ep = &ep->dwc_ep; + volatile uint32_t *addr; + depctl_data_t depctl = {.d32 = 0 }; + dwc_otg_pcd_t *pcd = ep->pcd; + dwc_otg_dev_dma_desc_t *dma_desc; + int i; + + dma_desc = + dwc_ep->iso_desc_addr + dwc_ep->desc_cnt * (dwc_ep->proc_buf_num); + + if (dwc_ep->is_in) { + dev_dma_desc_sts_t sts = {.d32 = 0 }; + for (i = 0; i < dwc_ep->desc_cnt; ++i, ++dma_desc) { + sts.d32 = dma_desc->status.d32; + sts.b_iso_in.bs = BS_HOST_READY; + dma_desc->status.d32 = sts.d32; + } + } else { + dev_dma_desc_sts_t sts = {.d32 = 0 }; + for (i = 0; i < dwc_ep->desc_cnt; ++i, ++dma_desc) { + sts.d32 = dma_desc->status.d32; + sts.b_iso_out.bs = BS_HOST_READY; + dma_desc->status.d32 = sts.d32; + } + } + + if (dwc_ep->is_in == 0) { + addr = + &GET_CORE_IF(pcd)->dev_if->out_ep_regs[dwc_ep-> + num]->doepctl; + } else { + addr = + &GET_CORE_IF(pcd)->dev_if->in_ep_regs[dwc_ep->num]->diepctl; + } + depctl.b.epena = 1; + DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32); +} + +/** + * This function sets latest iso packet information(non-PTI mode) + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void set_current_pkt_info(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + deptsiz_data_t deptsiz = {.d32 = 0 }; + dma_addr_t dma_addr; + uint32_t offset; + + if (ep->proc_buf_num) + dma_addr = ep->dma_addr1; + else + dma_addr = ep->dma_addr0; + + if (ep->is_in) { + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if-> + in_ep_regs[ep->num]->dieptsiz); + offset = ep->data_per_frame; + } else { + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[ep->num]->doeptsiz); + offset = + ep->data_per_frame + + (0x4 & (0x4 - (ep->data_per_frame & 0x3))); + } + + if (!deptsiz.b.xfersize) { + ep->pkt_info[ep->cur_pkt].length = ep->data_per_frame; + ep->pkt_info[ep->cur_pkt].offset = + ep->cur_pkt_dma_addr - dma_addr; + ep->pkt_info[ep->cur_pkt].status = 0; + } else { + ep->pkt_info[ep->cur_pkt].length = ep->data_per_frame; + ep->pkt_info[ep->cur_pkt].offset = + ep->cur_pkt_dma_addr - dma_addr; + ep->pkt_info[ep->cur_pkt].status = -DWC_E_NO_DATA; + } + ep->cur_pkt_addr += offset; + ep->cur_pkt_dma_addr += offset; + ep->cur_pkt++; +} + +/** + * This function sets latest iso packet information(DDMA mode) + * + * @param core_if Programming view of DWC_otg controller. + * @param dwc_ep The EP to start the transfer on. + * + */ +static void set_ddma_iso_pkts_info(dwc_otg_core_if_t * core_if, + dwc_ep_t * dwc_ep) +{ + dwc_otg_dev_dma_desc_t *dma_desc; + dev_dma_desc_sts_t sts = {.d32 = 0 }; + iso_pkt_info_t *iso_packet; + uint32_t data_per_desc; + uint32_t offset; + int i, j; + + iso_packet = dwc_ep->pkt_info; + + /** Reinit closed DMA Descriptors*/ + /** ISO OUT EP */ + if (dwc_ep->is_in == 0) { + dma_desc = + dwc_ep->iso_desc_addr + + dwc_ep->desc_cnt * dwc_ep->proc_buf_num; + offset = 0; + + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + for (j = 0; j < dwc_ep->pkt_per_frm; ++j) { + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep-> + data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - + data_per_desc % + 4) : 0; + + sts.d32 = dma_desc->status.d32; + + /* Write status in iso_packet_decsriptor */ + iso_packet->status = + sts.b_iso_out.rxsts + + (sts.b_iso_out.bs ^ BS_DMA_DONE); + if (iso_packet->status) { + iso_packet->status = -DWC_E_NO_DATA; + } + + /* Received data length */ + if (!sts.b_iso_out.rxbytes) { + iso_packet->length = + data_per_desc - + sts.b_iso_out.rxbytes; + } else { + iso_packet->length = + data_per_desc - + sts.b_iso_out.rxbytes + (4 - + dwc_ep->data_per_frame + % 4); + } + + iso_packet->offset = offset; + + offset += data_per_desc; + dma_desc++; + iso_packet++; + } + } + + for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) { + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + + sts.d32 = dma_desc->status.d32; + + /* Write status in iso_packet_decsriptor */ + iso_packet->status = + sts.b_iso_out.rxsts + + (sts.b_iso_out.bs ^ BS_DMA_DONE); + if (iso_packet->status) { + iso_packet->status = -DWC_E_NO_DATA; + } + + /* Received data length */ + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_out.rxbytes; + + iso_packet->offset = offset; + + offset += data_per_desc; + iso_packet++; + dma_desc++; + } + + sts.d32 = dma_desc->status.d32; + + /* Write status in iso_packet_decsriptor */ + iso_packet->status = + sts.b_iso_out.rxsts + (sts.b_iso_out.bs ^ BS_DMA_DONE); + if (iso_packet->status) { + iso_packet->status = -DWC_E_NO_DATA; + } + /* Received data length */ + if (!sts.b_iso_out.rxbytes) { + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_out.rxbytes; + } else { + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_out.rxbytes + + (4 - dwc_ep->data_per_frame % 4); + } + + iso_packet->offset = offset; + } else { +/** ISO IN EP */ + + dma_desc = + dwc_ep->iso_desc_addr + + dwc_ep->desc_cnt * dwc_ep->proc_buf_num; + + for (i = 0; i < dwc_ep->desc_cnt - 1; i++) { + sts.d32 = dma_desc->status.d32; + + /* Write status in iso packet descriptor */ + iso_packet->status = + sts.b_iso_in.txsts + + (sts.b_iso_in.bs ^ BS_DMA_DONE); + if (iso_packet->status != 0) { + iso_packet->status = -DWC_E_NO_DATA; + + } + /* Bytes has been transfered */ + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_in.txbytes; + + dma_desc++; + iso_packet++; + } + + sts.d32 = dma_desc->status.d32; + while (sts.b_iso_in.bs == BS_DMA_BUSY) { + sts.d32 = dma_desc->status.d32; + } + + /* Write status in iso packet descriptor ??? do be done with ERROR codes */ + iso_packet->status = + sts.b_iso_in.txsts + (sts.b_iso_in.bs ^ BS_DMA_DONE); + if (iso_packet->status != 0) { + iso_packet->status = -DWC_E_NO_DATA; + } + + /* Bytes has been transfered */ + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_in.txbytes; + } +} + +/** + * This function reinitialize DMA Descriptors for Isochronous transfer + * + * @param core_if Programming view of DWC_otg controller. + * @param dwc_ep The EP to start the transfer on. + * + */ +static void reinit_ddma_iso_xfer(dwc_otg_core_if_t * core_if, dwc_ep_t * dwc_ep) +{ + int i, j; + dwc_otg_dev_dma_desc_t *dma_desc; + dma_addr_t dma_ad; + volatile uint32_t *addr; + dev_dma_desc_sts_t sts = {.d32 = 0 }; + uint32_t data_per_desc; + + if (dwc_ep->is_in == 0) { + addr = &core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl; + } else { + addr = &core_if->dev_if->in_ep_regs[dwc_ep->num]->diepctl; + } + + if (dwc_ep->proc_buf_num == 0) { + /** Buffer 0 descriptors setup */ + dma_ad = dwc_ep->dma_addr0; + } else { + /** Buffer 1 descriptors setup */ + dma_ad = dwc_ep->dma_addr1; + } + + /** Reinit closed DMA Descriptors*/ + /** ISO OUT EP */ + if (dwc_ep->is_in == 0) { + dma_desc = + dwc_ep->iso_desc_addr + + dwc_ep->desc_cnt * dwc_ep->proc_buf_num; + + sts.b_iso_out.bs = BS_HOST_READY; + sts.b_iso_out.rxsts = 0; + sts.b_iso_out.l = 0; + sts.b_iso_out.sp = 0; + sts.b_iso_out.ioc = 0; + sts.b_iso_out.pid = 0; + sts.b_iso_out.framenum = 0; + + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + for (j = 0; j < dwc_ep->pkt_per_frm; ++j) { + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep-> + data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - + data_per_desc % + 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dma_ad += data_per_desc; + dma_desc++; + } + } + + for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) { + + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dma_desc++; + dma_ad += data_per_desc; + } + + sts.b_iso_out.ioc = 1; + sts.b_iso_out.l = dwc_ep->proc_buf_num; + + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + } else { +/** ISO IN EP */ + + dma_desc = + dwc_ep->iso_desc_addr + + dwc_ep->desc_cnt * dwc_ep->proc_buf_num; + + sts.b_iso_in.bs = BS_HOST_READY; + sts.b_iso_in.txsts = 0; + sts.b_iso_in.sp = 0; + sts.b_iso_in.ioc = 0; + sts.b_iso_in.pid = dwc_ep->pkt_per_frm; + sts.b_iso_in.framenum = dwc_ep->next_frame; + sts.b_iso_in.txbytes = dwc_ep->data_per_frame; + sts.b_iso_in.l = 0; + + for (i = 0; i < dwc_ep->desc_cnt - 1; i++) { + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + sts.b_iso_in.framenum += dwc_ep->bInterval; + dma_ad += dwc_ep->data_per_frame; + dma_desc++; + } + + sts.b_iso_in.ioc = 1; + sts.b_iso_in.l = dwc_ep->proc_buf_num; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dwc_ep->next_frame = + sts.b_iso_in.framenum + dwc_ep->bInterval * 1; + } + dwc_ep->proc_buf_num = (dwc_ep->proc_buf_num ^ 1) & 0x1; +} + +/** + * This function is to handle Iso EP transfer complete interrupt + * in case Iso out packet was dropped + * + * @param core_if Programming view of DWC_otg controller. + * @param dwc_ep The EP for wihich transfer complete was asserted + * + */ +static uint32_t handle_iso_out_pkt_dropped(dwc_otg_core_if_t * core_if, + dwc_ep_t * dwc_ep) +{ + uint32_t dma_addr; + uint32_t drp_pkt; + uint32_t drp_pkt_cnt; + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + int i; + + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[dwc_ep->num]->doeptsiz); + + drp_pkt = dwc_ep->pkt_cnt - deptsiz.b.pktcnt; + drp_pkt_cnt = dwc_ep->pkt_per_frm - (drp_pkt % dwc_ep->pkt_per_frm); + + /* Setting dropped packets status */ + for (i = 0; i < drp_pkt_cnt; ++i) { + dwc_ep->pkt_info[drp_pkt].status = -DWC_E_NO_DATA; + drp_pkt++; + deptsiz.b.pktcnt--; + } + + if (deptsiz.b.pktcnt > 0) { + deptsiz.b.xfersize = + dwc_ep->xfer_len - (dwc_ep->pkt_cnt - + deptsiz.b.pktcnt) * dwc_ep->maxpacket; + } else { + deptsiz.b.xfersize = 0; + deptsiz.b.pktcnt = 0; + } + + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doeptsiz, + deptsiz.d32); + + if (deptsiz.b.pktcnt > 0) { + if (dwc_ep->proc_buf_num) { + dma_addr = + dwc_ep->dma_addr1 + dwc_ep->xfer_len - + deptsiz.b.xfersize; + } else { + dma_addr = + dwc_ep->dma_addr0 + dwc_ep->xfer_len - + deptsiz.b.xfersize;; + } + + DWC_WRITE_REG32(&core_if->dev_if-> + out_ep_regs[dwc_ep->num]->doepdma, dma_addr); + + /** Re-enable endpoint, clear nak */ + depctl.d32 = 0; + depctl.b.epena = 1; + depctl.b.cnak = 1; + + DWC_MODIFY_REG32(&core_if->dev_if-> + out_ep_regs[dwc_ep->num]->doepctl, depctl.d32, + depctl.d32); + return 0; + } else { + return 1; + } +} + +/** + * This function sets iso packets information(PTI mode) + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +static uint32_t set_iso_pkts_info(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + int i, j; + dma_addr_t dma_ad; + iso_pkt_info_t *packet_info = ep->pkt_info; + uint32_t offset; + uint32_t frame_data; + deptsiz_data_t deptsiz; + + if (ep->proc_buf_num == 0) { + /** Buffer 0 descriptors setup */ + dma_ad = ep->dma_addr0; + } else { + /** Buffer 1 descriptors setup */ + dma_ad = ep->dma_addr1; + } + + if (ep->is_in) { + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + dieptsiz); + } else { + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if->out_ep_regs[ep->num]-> + doeptsiz); + } + + if (!deptsiz.b.xfersize) { + offset = 0; + for (i = 0; i < ep->pkt_cnt; i += ep->pkt_per_frm) { + frame_data = ep->data_per_frame; + for (j = 0; j < ep->pkt_per_frm; ++j) { + + /* Packet status - is not set as initially + * it is set to 0 and if packet was sent + successfully, status field will remain 0*/ + + /* Bytes has been transfered */ + packet_info->length = + (ep->maxpacket < + frame_data) ? ep->maxpacket : frame_data; + + /* Received packet offset */ + packet_info->offset = offset; + offset += packet_info->length; + frame_data -= packet_info->length; + + packet_info++; + } + } + return 1; + } else { + /* This is a workaround for in case of Transfer Complete with + * PktDrpSts interrupts merging - in this case Transfer complete + * interrupt for Isoc Out Endpoint is asserted without PktDrpSts + * set and with DOEPTSIZ register non zero. Investigations showed, + * that this happens when Out packet is dropped, but because of + * interrupts merging during first interrupt handling PktDrpSts + * bit is cleared and for next merged interrupts it is not reset. + * In this case SW hadles the interrupt as if PktDrpSts bit is set. + */ + if (ep->is_in) { + return 1; + } else { + return handle_iso_out_pkt_dropped(core_if, ep); + } + } +} + +/** + * This function is to handle Iso EP transfer complete interrupt + * + * @param pcd The PCD + * @param ep The EP for which transfer complete was asserted + * + */ +static void complete_iso_ep(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd); + dwc_ep_t *dwc_ep = &ep->dwc_ep; + uint8_t is_last = 0; + + if (ep->dwc_ep.next_frame == 0xffffffff) { + DWC_WARN("Next frame is not set!\n"); + return; + } + + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + set_ddma_iso_pkts_info(core_if, dwc_ep); + reinit_ddma_iso_xfer(core_if, dwc_ep); + is_last = 1; + } else { + if (core_if->pti_enh_enable) { + if (set_iso_pkts_info(core_if, dwc_ep)) { + dwc_ep->proc_buf_num = + (dwc_ep->proc_buf_num ^ 1) & 0x1; + dwc_otg_iso_ep_start_buf_transfer + (core_if, dwc_ep); + is_last = 1; + } + } else { + set_current_pkt_info(core_if, dwc_ep); + if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) { + is_last = 1; + dwc_ep->cur_pkt = 0; + dwc_ep->proc_buf_num = + (dwc_ep->proc_buf_num ^ 1) & 0x1; + if (dwc_ep->proc_buf_num) { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff1; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr1; + } else { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff0; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr0; + } + + } + dwc_otg_iso_ep_start_frm_transfer(core_if, + dwc_ep); + } + } + } else { + set_current_pkt_info(core_if, dwc_ep); + if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) { + is_last = 1; + dwc_ep->cur_pkt = 0; + dwc_ep->proc_buf_num = (dwc_ep->proc_buf_num ^ 1) & 0x1; + if (dwc_ep->proc_buf_num) { + dwc_ep->cur_pkt_addr = dwc_ep->xfer_buff1; + dwc_ep->cur_pkt_dma_addr = dwc_ep->dma_addr1; + } else { + dwc_ep->cur_pkt_addr = dwc_ep->xfer_buff0; + dwc_ep->cur_pkt_dma_addr = dwc_ep->dma_addr0; + } + + } + dwc_otg_iso_ep_start_frm_transfer(core_if, dwc_ep); + } + if (is_last) + dwc_otg_iso_buffer_done(pcd, ep, ep->iso_req_handle); +} +#endif /* DWC_EN_ISOC */ + +/** + * This function handle BNA interrupt for Non Isochronous EPs + * + */ +static void dwc_otg_pcd_handle_noniso_bna(dwc_otg_pcd_ep_t * ep) +{ + dwc_ep_t *dwc_ep = &ep->dwc_ep; + volatile uint32_t *addr; + volatile uint32_t *dmskaddr; + volatile uint32_t *dmaaddr; + depctl_data_t depctl = {.d32 = 0 }; + dwc_otg_pcd_t *pcd = ep->pcd; + dwc_otg_dev_dma_desc_t *dma_desc; + dev_dma_desc_sts_t sts = {.d32 = 0 }; + dwc_otg_core_if_t *core_if = ep->pcd->core_if; + doepmsk_data_t depmsk; + + int i, start; + + if (dwc_ep->is_in == 0) { + addr = + &GET_CORE_IF(pcd)->dev_if->out_ep_regs[dwc_ep->num]-> + doepctl; + dmaaddr = &GET_CORE_IF(pcd)->dev_if->out_ep_regs[dwc_ep->num]->doepdma; + dmskaddr = &GET_CORE_IF(pcd)->dev_if->dev_global_regs->doepmsk; + } else { + addr = + &GET_CORE_IF(pcd)->dev_if->in_ep_regs[dwc_ep->num]->diepctl; + dmaaddr = &GET_CORE_IF(pcd)->dev_if->in_ep_regs[dwc_ep->num]->diepdma; + dmskaddr = &GET_CORE_IF(pcd)->dev_if->dev_global_regs->diepmsk; + + } + + DWC_WARN("Ep%d %s, DescCnt = %d, depctl = 0x%x, \ + depdma = 0x%x, (0x%p).sta = 0x%x\n", + dwc_ep->num, dwc_ep->is_in ? "IN" : "OUT", + dwc_ep->desc_cnt, DWC_READ_REG32(addr), + DWC_READ_REG32(dmaaddr), dwc_ep->desc_addr, + dwc_ep->desc_addr->status.d32); + if (dwc_ep->num == 0) { + depmsk.d32 = DWC_READ_REG32(dmskaddr); + depmsk.b.bna = 0; + DWC_WRITE_REG32(dmskaddr, depmsk.d32); + } + + if (core_if->core_params->cont_on_bna && !dwc_ep->is_in + && dwc_ep->type != DWC_OTG_EP_TYPE_CONTROL) { + uint32_t doepdma; + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[dwc_ep->num]; + doepdma = DWC_READ_REG32(&(out_regs->doepdma)); + start = (doepdma - dwc_ep->dma_desc_addr)/sizeof(dwc_otg_dev_dma_desc_t); + dma_desc = &(dwc_ep->desc_addr[start]); + } else { + start = 0; + dma_desc = dwc_ep->desc_addr; + } + + for (i = start; i < dwc_ep->desc_cnt; ++i, ++dma_desc) { + sts.d32 = dma_desc->status.d32; + sts.b.bs = BS_HOST_READY; + dma_desc->status.d32 = sts.d32; + } + + depctl.b.epena = 1; + depctl.b.cnak = 1; + DWC_MODIFY_REG32(addr, 0, depctl.d32); +} + +/** + * This function handles EP0 Control transfers. + * + * The state of the control transfers are tracked in + * ep0state. + */ +static void handle_ep0(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + dev_dma_desc_sts_t desc_sts; + deptsiz0_data_t deptsiz; + uint32_t byte_count; + depctl_data_t depctl; + +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "%s()\n", __func__); + print_ep0_state(pcd); +#endif + +// DWC_PRINTF("HANDLE EP0\n"); + + switch (pcd->ep0state) { + case EP0_DISCONNECT: + break; + + case EP0_IDLE: + pcd->request_config = 0; + + pcd_setup(pcd); + break; + + case EP0_IN_DATA_PHASE: +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "DATA_IN EP%d-%s: type=%d, mps=%d\n", + ep0->dwc_ep.num, (ep0->dwc_ep.is_in ? "IN" : "OUT"), + ep0->dwc_ep.type, ep0->dwc_ep.maxpacket); +#endif + + if (core_if->dma_enable != 0) { + /* + * For EP0 we can only program 1 packet at a time so we + * need to do the make calculations after each complete. + * Call write_packet to make the calculations, as in + * slave mode, and use those values to determine if we + * can complete. + */ + if (core_if->dma_desc_enable == 0) { + deptsiz.d32 = + DWC_READ_REG32(&core_if-> + dev_if->in_ep_regs[0]-> + dieptsiz); + byte_count = + ep0->dwc_ep.xfer_len - deptsiz.b.xfersize; + } else { + desc_sts = + core_if->dev_if->in_desc_addr->status; + byte_count = + ep0->dwc_ep.xfer_len - desc_sts.b.bytes; + } + ep0->dwc_ep.xfer_count += byte_count; + ep0->dwc_ep.xfer_buff += byte_count; + ep0->dwc_ep.dma_addr += byte_count; + } + if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len) { + dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER\n"); + } else if (ep0->dwc_ep.sent_zlp) { + dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + ep0->dwc_ep.sent_zlp = 0; + DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER sent zlp\n"); + } else { + ep0_complete_request(ep0); + DWC_DEBUGPL(DBG_PCD, "COMPLETE TRANSFER\n"); + } + break; + case EP0_OUT_DATA_PHASE: +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "DATA_OUT EP%d-%s: type=%d, mps=%d\n", + ep0->dwc_ep.num, (ep0->dwc_ep.is_in ? "IN" : "OUT"), + ep0->dwc_ep.type, ep0->dwc_ep.maxpacket); +#endif + if (core_if->dma_enable != 0) { + if (core_if->dma_desc_enable == 0) { + deptsiz.d32 = + DWC_READ_REG32(&core_if-> + dev_if->out_ep_regs[0]-> + doeptsiz); + byte_count = + ep0->dwc_ep.maxpacket - deptsiz.b.xfersize; + } else { + desc_sts = + core_if->dev_if->out_desc_addr->status; + byte_count = + ep0->dwc_ep.maxpacket - desc_sts.b.bytes; + } + ep0->dwc_ep.xfer_count += byte_count; + ep0->dwc_ep.xfer_buff += byte_count; + ep0->dwc_ep.dma_addr += byte_count; + } + if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len) { + dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER\n"); + } else if (ep0->dwc_ep.sent_zlp) { + dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + ep0->dwc_ep.sent_zlp = 0; + DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER sent zlp\n"); + } else { + ep0_complete_request(ep0); + DWC_DEBUGPL(DBG_PCD, "COMPLETE TRANSFER\n"); + } + break; + + case EP0_IN_STATUS_PHASE: + case EP0_OUT_STATUS_PHASE: + DWC_DEBUGPL(DBG_PCD, "CASE: EP0_STATUS\n"); + depctl.d32 = dwc_read_reg32(&core_if->dev_if->in_ep_regs[0]->diepctl); + if(depctl.b.epena){ + depctl.b.epdis = 1; + dwc_write_reg32(&core_if->dev_if->in_ep_regs[0]->diepctl,depctl.d32); + } + ep0_complete_request(ep0); + pcd->ep0state = EP0_IDLE; + ep0->stopped = 1; + ep0->dwc_ep.is_in = 0; /* OUT for next SETUP */ + + /* Prepare for more SETUP Packets */ + if (core_if->dma_enable) { + ep0_out_start(core_if, pcd); + } + break; + + case EP0_STALL: + DWC_ERROR("EP0 STALLed, should not get here pcd_setup()\n"); + break; + } +#ifdef DEBUG_EP0 + print_ep0_state(pcd); +#endif +} + +/** + * Restart transfer + */ +static void restart_transfer(dwc_otg_pcd_t * pcd, const uint32_t epnum) +{ + dwc_otg_core_if_t *core_if; + dwc_otg_dev_if_t *dev_if; + deptsiz_data_t dieptsiz = {.d32 = 0 }; + dwc_otg_pcd_ep_t *ep; + dwc_otg_pcd_request_t *req; + + ep = get_in_ep(pcd, epnum); + +#ifdef DWC_EN_ISOC + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + return; + } +#endif /* DWC_EN_ISOC */ + /* Get any pending requests */ + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (!req) { + DWC_PRINTF("restart_transfer 0x%p, req = NULL!\n", ep); + return; + } + } else { + DWC_PRINTF("restart_transfer 0x%p, ep->queue empty!\n", ep); + return; + } + + core_if = GET_CORE_IF(pcd); + dev_if = core_if->dev_if; + + dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dieptsiz); + + DWC_DEBUGPL(DBG_PCD, "xfer_buff=%p xfer_count=%0x xfer_len=%0x" + " stopped=%d\n", ep->dwc_ep.xfer_buff, + ep->dwc_ep.xfer_count, ep->dwc_ep.xfer_len, ep->stopped); + /* + * If xfersize is 0 and pktcnt in not 0, resend the last packet. + */ + if (dieptsiz.b.pktcnt && dieptsiz.b.xfersize == 0 && + ep->dwc_ep.start_xfer_buff != 0) { + if (ep->dwc_ep.total_len <= ep->dwc_ep.maxpacket) { + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff; + ep->dwc_ep.xfer_len = ep->dwc_ep.xfer_count; + } else { + ep->dwc_ep.xfer_count -= ep->dwc_ep.maxpacket; + /* convert packet size to dwords. */ + ep->dwc_ep.xfer_buff -= ep->dwc_ep.maxpacket; + ep->dwc_ep.xfer_len = ep->dwc_ep.xfer_count; + } + ep->stopped = 0; + DWC_DEBUGPL(DBG_PCD, "xfer_buff=%p xfer_count=%0x " + "xfer_len=%0x stopped=%d\n", + ep->dwc_ep.xfer_buff, + ep->dwc_ep.xfer_count, ep->dwc_ep.xfer_len, + ep->stopped); + if (epnum == 0) { + dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep); + } else { + dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep, req); + } + } +} + +/** + * handle the IN EP disable interrupt. + */ +static inline void handle_in_ep_disable_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + deptsiz_data_t dieptsiz = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + depctl_data_t depctl; + + ep = get_in_ep(pcd, epnum); + dwc_ep = &ep->dwc_ep; + + if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num); + complete_ep(ep); + return; + } + + DWC_DEBUGPL(DBG_PCD, "diepctl%d=%0x\n", epnum, + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl)); + dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dieptsiz); + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl); + + DWC_DEBUGPL(DBG_ANY, "pktcnt=%d size=%d\n", + dieptsiz.b.pktcnt, dieptsiz.b.xfersize); + + if (ep->stopped) { + /* Flush the Tx FIFO */ + dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num); + /* Clear the Global IN NP NAK */ + dctl.d32 = 0; + dctl.b.cgnpinnak = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + + /* Restart the transaction */ + if (dieptsiz.b.pktcnt != 0 || dieptsiz.b.xfersize != 0) { + restart_transfer(pcd, epnum); + } + } else { + /* Restart the transaction */ + if (dieptsiz.b.pktcnt != 0 || dieptsiz.b.xfersize != 0) { + restart_transfer(pcd, epnum); + } + trace_printk("STOPPED!!!\n"); + } +} + +/** + * Handler for the IN EP timeout handshake interrupt. + */ +static inline void handle_in_ep_timeout_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + +#ifdef DEBUG + deptsiz_data_t dieptsiz = {.d32 = 0 }; + uint32_t num = 0; +#endif + dctl_data_t dctl = {.d32 = 0 }; + dwc_otg_pcd_ep_t *ep; + + gintmsk_data_t intr_mask = {.d32 = 0 }; + + ep = get_in_ep(pcd, epnum); + + /* Disable the NP Tx Fifo Empty Interrrupt */ + if (!core_if->dma_enable) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, + intr_mask.d32, 0); + } + /** @todo NGS Check EP type. + * Implement for Periodic EPs */ + /* + * Non-periodic EP + */ + /* Enable the Global IN NAK Effective Interrupt */ + intr_mask.b.ginnakeff = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, intr_mask.d32); + + /* Set Global IN NAK */ + dctl.b.sgnpinnak = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + + ep->stopped = 1; + +#ifdef DEBUG + dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[num]->dieptsiz); + DWC_DEBUGPL(DBG_ANY, "pktcnt=%d size=%d\n", + dieptsiz.b.pktcnt, dieptsiz.b.xfersize); +#endif + +#ifdef DISABLE_PERIODIC_EP + /* + * Set the NAK bit for this EP to + * start the disable process. + */ + diepctl.d32 = 0; + diepctl.b.snak = 1; + DWC_MODIFY_REG32(&dev_if->in_ep_regs[num]->diepctl, diepctl.d32, + diepctl.d32); + ep->disabling = 1; + ep->stopped = 1; +#endif +} + +/** + * Handler for the IN EP NAK interrupt. + */ +static inline int32_t handle_in_ep_nak_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + /** @todo implement ISR */ + dwc_otg_core_if_t *core_if; + diepmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "IN EP NAK"); + core_if = GET_CORE_IF(pcd); + intr_mask.b.nak = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + diepeachintmsk[epnum], intr_mask.d32, 0); + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->diepmsk, + intr_mask.d32, 0); + } + + return 1; +} + +/** + * Handler for the OUT EP Babble interrupt. + */ +static inline int32_t handle_out_ep_babble_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + /** @todo implement ISR */ + dwc_otg_core_if_t *core_if; + doepmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", + "OUT EP Babble"); + core_if = GET_CORE_IF(pcd); + intr_mask.b.babble = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[epnum], intr_mask.d32, 0); + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk, + intr_mask.d32, 0); + } + + return 1; +} + +/** + * Handler for the OUT EP NAK interrupt. + */ +static inline int32_t handle_out_ep_nak_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + /** @todo implement ISR */ + dwc_otg_core_if_t *core_if; + doepmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_ANY, "INTERRUPT Handler not implemented for %s\n", "OUT EP NAK"); + core_if = GET_CORE_IF(pcd); + intr_mask.b.nak = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[epnum], intr_mask.d32, 0); + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk, + intr_mask.d32, 0); + } + + return 1; +} + +/** + * Handler for the OUT EP NYET interrupt. + */ +static inline int32_t handle_out_ep_nyet_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + /** @todo implement ISR */ + dwc_otg_core_if_t *core_if; + doepmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "OUT EP NYET"); + core_if = GET_CORE_IF(pcd); + intr_mask.b.nyet = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[epnum], intr_mask.d32, 0); + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk, + intr_mask.d32, 0); + } + + return 1; +} + +/** + * This interrupt indicates that an IN EP has a pending Interrupt. + * The sequence for handling the IN EP interrupt is shown below: + * -# Read the Device All Endpoint Interrupt register + * -# Repeat the following for each IN EP interrupt bit set (from + * LSB to MSB). + * -# Read the Device Endpoint Interrupt (DIEPINTn) register + * -# If "Transfer Complete" call the request complete function + * -# If "Endpoint Disabled" complete the EP disable procedure. + * -# If "AHB Error Interrupt" log error + * -# If "Time-out Handshake" log error + * -# If "IN Token Received when TxFIFO Empty" write packet to Tx + * FIFO. + * -# If "IN Token EP Mismatch" (disable, this is handled by EP + * Mismatch Interrupt) + */ +static int32_t dwc_otg_pcd_handle_in_ep_intr(dwc_otg_pcd_t * pcd) +{ +#define CLEAR_IN_EP_INTR(__core_if,__epnum,__intr) \ +do { \ + diepint_data_t diepint = {.d32=0}; \ + diepint.b.__intr = 1; \ + DWC_WRITE_REG32(&__core_if->dev_if->in_ep_regs[__epnum]->diepint, \ + diepint.d32); \ +} while (0) + + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + diepint_data_t diepint = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + uint32_t ep_intr; + uint32_t epnum = 0; + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, pcd); + + /* Read in the device interrupt bits */ + ep_intr = dwc_otg_read_dev_all_in_ep_intr(core_if); + + /* Service the Device IN interrupts for each endpoint */ + while (ep_intr) { + if (ep_intr & 0x1) { + uint32_t empty_msk; + /* Get EP pointer */ + ep = get_in_ep(pcd, epnum); + dwc_ep = &ep->dwc_ep; + + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl); + empty_msk = + DWC_READ_REG32(&dev_if-> + dev_global_regs->dtknqr4_fifoemptymsk); + + DWC_DEBUGPL(DBG_PCDV, + "IN EP INTERRUPT - %d\nepmty_msk - %8x diepctl - %8x\n", + epnum, empty_msk, depctl.d32); + + DWC_DEBUGPL(DBG_PCD, + "EP%d-%s: type=%d, mps=%d\n", + dwc_ep->num, (dwc_ep->is_in ? "IN" : "OUT"), + dwc_ep->type, dwc_ep->maxpacket); + + diepint.d32 = + dwc_otg_read_dev_in_ep_intr(core_if, dwc_ep); + + DWC_DEBUGPL(DBG_PCDV, + "EP %d Interrupt Register - 0x%x\n", epnum, + diepint.d32); + /* Transfer complete */ + if (diepint.b.xfercompl) { + /* Disable the NP Tx FIFO Empty + * Interrupt */ + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32 + (&core_if->core_global_regs->gintmsk, + intr_mask.d32, 0); + } else { + /* Disable the Tx FIFO Empty Interrupt for this EP */ + uint32_t fifoemptymsk = + 0x1 << dwc_ep->num; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + fifoemptymsk, 0); + } + /* Clear the bit in DIEPINTn for this interrupt */ + CLEAR_IN_EP_INTR(core_if, epnum, xfercompl); + + /* Complete the transfer */ + if (epnum == 0) { + handle_ep0(pcd); + } +#ifdef DWC_EN_ISOC + else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (!ep->stopped) + complete_iso_ep(pcd, ep); + } +#endif /* DWC_EN_ISOC */ +#ifdef DWC_UTE_PER_IO + else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (!ep->stopped) + complete_xiso_ep(ep); + } +#endif /* DWC_UTE_PER_IO */ + else { + if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC && + dwc_ep->bInterval > 1) { + dwc_ep->frame_num += dwc_ep->bInterval; + if (dwc_ep->frame_num > 0x3FFF) + { + dwc_ep->frm_overrun = 1; + dwc_ep->frame_num &= 0x3FFF; + } else + dwc_ep->frm_overrun = 0; + } + complete_ep(ep); + if(diepint.b.nak) + CLEAR_IN_EP_INTR(core_if, epnum, nak); + } + } + /* Endpoint disable */ + if (diepint.b.epdisabled) { + DWC_DEBUGPL(DBG_ANY, "EP%d IN disabled\n", + epnum); + handle_in_ep_disable_intr(pcd, epnum); + + /* Clear the bit in DIEPINTn for this interrupt */ + CLEAR_IN_EP_INTR(core_if, epnum, epdisabled); + } + /* AHB Error */ + if (diepint.b.ahberr) { + DWC_ERROR("EP%d IN AHB Error\n", epnum); + /* Clear the bit in DIEPINTn for this interrupt */ + CLEAR_IN_EP_INTR(core_if, epnum, ahberr); + } + /* TimeOUT Handshake (non-ISOC IN EPs) */ + if (diepint.b.timeout) { + DWC_ERROR("EP%d IN Time-out\n", epnum); + handle_in_ep_timeout_intr(pcd, epnum); + + CLEAR_IN_EP_INTR(core_if, epnum, timeout); + } + /** IN Token received with TxF Empty */ + if (diepint.b.intktxfemp) { + DWC_DEBUGPL(DBG_ANY, + "EP%d IN TKN TxFifo Empty\n", + epnum); + if (!ep->stopped && epnum != 0) { + + diepmsk_data_t diepmsk = {.d32 = 0 }; + diepmsk.b.intktxfemp = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32 + (&dev_if->dev_global_regs->diepeachintmsk + [epnum], diepmsk.d32, 0); + } else { + DWC_MODIFY_REG32 + (&dev_if->dev_global_regs->diepmsk, + diepmsk.d32, 0); + } + } else if (core_if->dma_desc_enable + && epnum == 0 + && pcd->ep0state == + EP0_OUT_STATUS_PHASE) { + // EP0 IN set STALL + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs + [epnum]->diepctl); + + /* set the disable and stall bits */ + if (depctl.b.epena) { + depctl.b.epdis = 1; + } + depctl.b.stall = 1; + DWC_WRITE_REG32(&dev_if->in_ep_regs + [epnum]->diepctl, + depctl.d32); + } + CLEAR_IN_EP_INTR(core_if, epnum, intktxfemp); + } + /** IN Token Received with EP mismatch */ + if (diepint.b.intknepmis) { + DWC_DEBUGPL(DBG_ANY, + "EP%d IN TKN EP Mismatch\n", epnum); + CLEAR_IN_EP_INTR(core_if, epnum, intknepmis); + } + /** IN Endpoint NAK Effective */ + if (diepint.b.inepnakeff) { + DWC_DEBUGPL(DBG_ANY, + "EP%d IN EP NAK Effective\n", + epnum); + /* Periodic EP */ + if (ep->disabling) { + depctl.d32 = 0; + depctl.b.snak = 1; + depctl.b.epdis = 1; + DWC_MODIFY_REG32(&dev_if->in_ep_regs + [epnum]->diepctl, + depctl.d32, + depctl.d32); + } + CLEAR_IN_EP_INTR(core_if, epnum, inepnakeff); + + } + + /** IN EP Tx FIFO Empty Intr */ + if (diepint.b.emptyintr) { + DWC_DEBUGPL(DBG_ANY, + "EP%d Tx FIFO Empty Intr \n", + epnum); + write_empty_tx_fifo(pcd, epnum); + + CLEAR_IN_EP_INTR(core_if, epnum, emptyintr); + + } + + /** IN EP BNA Intr */ + if (diepint.b.bna) { + CLEAR_IN_EP_INTR(core_if, epnum, bna); + if (core_if->dma_desc_enable) { +#ifdef DWC_EN_ISOC + if (dwc_ep->type == + DWC_OTG_EP_TYPE_ISOC) { + /* + * This checking is performed to prevent first "false" BNA + * handling occuring right after reconnect + */ + if (dwc_ep->next_frame != + 0xffffffff) + dwc_otg_pcd_handle_iso_bna(ep); + } else +#endif /* DWC_EN_ISOC */ + { + dwc_otg_pcd_handle_noniso_bna(ep); + } + } + } + /* NAK Interrutp */ + if (diepint.b.nak) { + DWC_DEBUGPL(DBG_ANY, "EP%d IN NAK Interrupt\n", + epnum); + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + depctl_data_t depctl; + if (ep->dwc_ep.frame_num == 0xFFFFFFFF) { + ep->dwc_ep.frame_num = core_if->frame_num; + if (ep->dwc_ep.bInterval > 1) { + depctl.d32 = 0; + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl); + if (ep->dwc_ep.frame_num & 0x1) { + depctl.b.setd1pid = 1; + depctl.b.setd0pid = 0; + } else { + depctl.b.setd0pid = 1; + depctl.b.setd1pid = 0; + } + DWC_WRITE_REG32(&dev_if->in_ep_regs[epnum]->diepctl, depctl.d32); + } + start_next_request(ep); + } + ep->dwc_ep.frame_num += ep->dwc_ep.bInterval; + if (dwc_ep->frame_num > 0x3FFF) { + dwc_ep->frm_overrun = 1; + dwc_ep->frame_num &= 0x3FFF; + } else + dwc_ep->frm_overrun = 0; + } + + CLEAR_IN_EP_INTR(core_if, epnum, nak); + } + } + epnum++; + ep_intr >>= 1; + } + + return 1; +#undef CLEAR_IN_EP_INTR +} + +/** + * This interrupt indicates that an OUT EP has a pending Interrupt. + * The sequence for handling the OUT EP interrupt is shown below: + * -# Read the Device All Endpoint Interrupt register + * -# Repeat the following for each OUT EP interrupt bit set (from + * LSB to MSB). + * -# Read the Device Endpoint Interrupt (DOEPINTn) register + * -# If "Transfer Complete" call the request complete function + * -# If "Endpoint Disabled" complete the EP disable procedure. + * -# If "AHB Error Interrupt" log error + * -# If "Setup Phase Done" process Setup Packet (See Standard USB + * Command Processing) + */ +static int32_t dwc_otg_pcd_handle_out_ep_intr(dwc_otg_pcd_t * pcd) +{ +#define CLEAR_OUT_EP_INTR(__core_if,__epnum,__intr) \ +do { \ + doepint_data_t doepint = {.d32=0}; \ + doepint.b.__intr = 1; \ + DWC_WRITE_REG32(&__core_if->dev_if->out_ep_regs[__epnum]->doepint, \ + doepint.d32); \ +} while (0) + + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + uint32_t ep_intr; + doepint_data_t doepint = {.d32 = 0 }; + uint32_t epnum = 0; + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + dctl_data_t dctl = {.d32 = 0 }; + gintmsk_data_t gintmsk = {.d32 = 0 }; + + + DWC_DEBUGPL(DBG_PCDV, "%s()\n", __func__); + + /* Read in the device interrupt bits */ + ep_intr = dwc_otg_read_dev_all_out_ep_intr(core_if); + + while (ep_intr) { + if (ep_intr & 0x1) { + /* Get EP pointer */ + ep = get_out_ep(pcd, epnum); + dwc_ep = &ep->dwc_ep; + +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, + "EP%d-%s: type=%d, mps=%d\n", + dwc_ep->num, (dwc_ep->is_in ? "IN" : "OUT"), + dwc_ep->type, dwc_ep->maxpacket); +#endif + doepint.d32 = + dwc_otg_read_dev_out_ep_intr(core_if, dwc_ep); + + /* Transfer complete */ + if (doepint.b.xfercompl) { + + if (epnum == 0) { + /* Clear the bit in DOEPINTn for this interrupt */ + CLEAR_OUT_EP_INTR(core_if, epnum, + xfercompl); + if (pcd->ep0state != EP0_IDLE) + handle_ep0(pcd); +#ifdef DWC_EN_ISOC + } else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (doepint.b.pktdrpsts == 0) { + /* Clear the bit in DOEPINTn for this interrupt */ + CLEAR_OUT_EP_INTR(core_if, + epnum, + xfercompl); + complete_iso_ep(pcd, ep); + } else { + + doepint_data_t doepint = {.d32 = 0 }; + doepint.b.xfercompl = 1; + doepint.b.pktdrpsts = 1; + DWC_WRITE_REG32 + (&core_if->dev_if->out_ep_regs + [epnum]->doepint, + doepint.d32); + if (handle_iso_out_pkt_dropped + (core_if, dwc_ep)) { + complete_iso_ep(pcd, + ep); + } + } +#endif /* DWC_EN_ISOC */ +#ifdef DWC_UTE_PER_IO + } else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + CLEAR_OUT_EP_INTR(core_if, epnum, xfercompl); + if (!ep->stopped) + complete_xiso_ep(ep); +#endif /* DWC_UTE_PER_IO */ + } else { + /* Clear the bit in DOEPINTn for this interrupt */ + CLEAR_OUT_EP_INTR(core_if, epnum, + xfercompl); + + if (core_if->core_params->dev_out_nak) { + DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[epnum]); + pcd->core_if->ep_xfer_info[epnum].state = 0; +#ifdef DEBUG + print_memory_payload(pcd, dwc_ep); +#endif + } + complete_ep(ep); + } + + } + + /* Endpoint disable */ + if (doepint.b.epdisabled) { + + /* Clear the bit in DOEPINTn for this interrupt */ + CLEAR_OUT_EP_INTR(core_if, epnum, epdisabled); + if (core_if->core_params->dev_out_nak) { +#ifdef DEBUG + print_memory_payload(pcd, dwc_ep); +#endif + /* In case of timeout condition */ + if (core_if->ep_xfer_info[epnum].state == 2) { + dctl.d32 = DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dctl); + dctl.b.cgoutnak = 1; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, + dctl.d32); + /* Unmask goutnakeff interrupt which was masked + * during handle nak out interrupt */ + gintmsk.b.goutnakeff = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, + 0, gintmsk.d32); + + complete_ep(ep); + } + } + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) + { + dctl_data_t dctl; + gintmsk_data_t intr_mask = {.d32 = 0}; + dwc_otg_pcd_request_t *req = 0; + + dctl.d32 = DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dctl); + dctl.b.cgoutnak = 1; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, + dctl.d32); + + intr_mask.d32 = 0; + intr_mask.b.incomplisoout = 1; + + /* Get any pending requests */ + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (!req) { + DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep); + } else { + dwc_otg_request_done(ep, req, 0); + start_next_request(ep); + } + } else { + DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep); + } + } + } + /* Moved this interrupt upper due to core deffect of asserting + * OUT EP 0 xfercompl along with stsphsrcvd in BDMA */ + if (doepint.b.stsphsercvd) { + deptsiz0_data_t deptsiz; + + CLEAR_OUT_EP_INTR(core_if, epnum, stsphsercvd); + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[0]->doeptsiz); + if (core_if->snpsid >= OTG_CORE_REV_3_00a + && core_if->dma_enable + && core_if->dma_desc_enable == 0 + && doepint.b.xfercompl + && deptsiz.b.xfersize == 24) { + CLEAR_OUT_EP_INTR(core_if, epnum, + xfercompl); + doepint.b.xfercompl = 0; + ep0_out_start(core_if, pcd); + } + if ((core_if->dma_desc_enable) || + (core_if->dma_enable + && core_if->snpsid >= + OTG_CORE_REV_3_00a)) { + do_setup_in_status_phase(pcd); + } + } + /* AHB Error */ + if (doepint.b.ahberr) { + DWC_ERROR("EP%d OUT AHB Error\n", epnum); + DWC_ERROR("EP%d DEPDMA=0x%08x \n", + epnum, core_if->dev_if->out_ep_regs[epnum]->doepdma); + CLEAR_OUT_EP_INTR(core_if, epnum, ahberr); + } + /* Setup Phase Done (contorl EPs) */ + if (doepint.b.setup) { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "EP%d SETUP Done\n", epnum); +#endif + CLEAR_OUT_EP_INTR(core_if, epnum, setup); + + handle_ep0(pcd); + } + + /** OUT EP BNA Intr */ + if (doepint.b.bna) { + CLEAR_OUT_EP_INTR(core_if, epnum, bna); + if (core_if->dma_desc_enable) { +#ifdef DWC_EN_ISOC + if (dwc_ep->type == + DWC_OTG_EP_TYPE_ISOC) { + /* + * This checking is performed to prevent first "false" BNA + * handling occuring right after reconnect + */ + if (dwc_ep->next_frame != + 0xffffffff) + dwc_otg_pcd_handle_iso_bna(ep); + } else +#endif /* DWC_EN_ISOC */ + { + dwc_otg_pcd_handle_noniso_bna(ep); + } + } + } + /* Babble Interrupt */ + if (doepint.b.babble) { + DWC_DEBUGPL(DBG_ANY, "EP%d OUT Babble\n", + epnum); + handle_out_ep_babble_intr(pcd, epnum); + + CLEAR_OUT_EP_INTR(core_if, epnum, babble); + } + if (doepint.b.outtknepdis) { + DWC_DEBUGPL(DBG_ANY, "EP%d OUT Token received when EP is \ + disabled\n",epnum); + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + doepmsk_data_t doepmsk = {.d32 = 0}; + ep->dwc_ep.frame_num = core_if->frame_num; + if (ep->dwc_ep.bInterval > 1) { + depctl_data_t depctl; + depctl.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[epnum]->doepctl); + if (ep->dwc_ep.frame_num & 0x1) { + depctl.b.setd1pid = 1; + depctl.b.setd0pid = 0; + } else { + depctl.b.setd0pid = 1; + depctl.b.setd1pid = 0; + } + DWC_WRITE_REG32(&core_if->dev_if-> + out_ep_regs[epnum]->doepctl, depctl.d32); + } + start_next_request(ep); + doepmsk.b.outtknepdis = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk, + doepmsk.d32, 0); + } + CLEAR_OUT_EP_INTR(core_if, epnum, outtknepdis); + } + + /* NAK Interrutp */ + if (doepint.b.nak) { + DWC_DEBUGPL(DBG_ANY, "EP%d OUT NAK\n", epnum); + handle_out_ep_nak_intr(pcd, epnum); + + CLEAR_OUT_EP_INTR(core_if, epnum, nak); + } + /* NYET Interrutp */ + if (doepint.b.nyet) { + DWC_DEBUGPL(DBG_ANY, "EP%d OUT NYET\n", epnum); + handle_out_ep_nyet_intr(pcd, epnum); + + CLEAR_OUT_EP_INTR(core_if, epnum, nyet); + } + } + + epnum++; + ep_intr >>= 1; + } + + return 1; + +#undef CLEAR_OUT_EP_INTR +} +static int drop_transfer(uint32_t trgt_fr, uint32_t curr_fr, uint8_t frm_overrun) +{ + int retval = 0; + if(!frm_overrun && curr_fr >= trgt_fr) + retval = 1; + else if (frm_overrun + && (curr_fr >= trgt_fr && ((curr_fr - trgt_fr) < 0x3FFF / 2))) + retval = 1; + return retval; +} + +/** + * Incomplete ISO IN Transfer Interrupt. + * This interrupt indicates one of the following conditions occurred + * while transmitting an ISOC transaction. + * - Corrupted IN Token for ISOC EP. + * - Packet not complete in FIFO. + * The follow actions will be taken: + * -# Determine the EP + * -# Set incomplete flag in dwc_ep structure + * -# Disable EP; when "Endpoint Disabled" interrupt is received + * Flush FIFO + */ +int32_t dwc_otg_pcd_handle_incomplete_isoc_in_intr(dwc_otg_pcd_t * pcd) +{ + gintsts_data_t gintsts; + +#ifdef DWC_EN_ISOC + dwc_otg_dev_if_t *dev_if; + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + dsts_data_t dsts = {.d32 = 0 }; + dwc_ep_t *dwc_ep; + int i; + + dev_if = GET_CORE_IF(pcd)->dev_if; + + for (i = 1; i <= dev_if->num_in_eps; ++i) { + dwc_ep = &pcd->in_ep[i].dwc_ep; + if (dwc_ep->active && dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + deptsiz.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->dieptsiz); + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + + if (depctl.b.epdis && deptsiz.d32) { + set_current_pkt_info(GET_CORE_IF(pcd), dwc_ep); + if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) { + dwc_ep->cur_pkt = 0; + dwc_ep->proc_buf_num = + (dwc_ep->proc_buf_num ^ 1) & 0x1; + + if (dwc_ep->proc_buf_num) { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff1; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr1; + } else { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff0; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr0; + } + + } + + dsts.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)->dev_if-> + dev_global_regs->dsts); + dwc_ep->next_frame = dsts.b.soffn; + + dwc_otg_iso_ep_start_frm_transfer(GET_CORE_IF + (pcd), + dwc_ep); + } + } + } + +#else + depctl_data_t depctl = {.d32 = 0 }; + dwc_ep_t *dwc_ep; + dwc_otg_dev_if_t *dev_if; + int i; + dev_if = GET_CORE_IF(pcd)->dev_if; + + DWC_DEBUGPL(DBG_PCD,"Incomplete ISO IN \n"); + + for (i = 1; i <= dev_if->num_in_eps; ++i) { + dwc_ep = &pcd->in_ep[i-1].dwc_ep; + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (depctl.b.epena && dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (drop_transfer(dwc_ep->frame_num, GET_CORE_IF(pcd)->frame_num, + dwc_ep->frm_overrun)) + { + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + depctl.b.snak = 1; + depctl.b.epdis = 1; + DWC_MODIFY_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32, depctl.d32); + } + } + } + + /*intr_mask.b.incomplisoin = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); */ +#endif //DWC_EN_ISOC + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.incomplisoin = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * Incomplete ISO OUT Transfer Interrupt. + * + * This interrupt indicates that the core has dropped an ISO OUT + * packet. The following conditions can be the cause: + * - FIFO Full, the entire packet would not fit in the FIFO. + * - CRC Error + * - Corrupted Token + * The follow actions will be taken: + * -# Determine the EP + * -# Set incomplete flag in dwc_ep structure + * -# Read any data from the FIFO + * -# Disable EP. When "Endpoint Disabled" interrupt is received + * re-enable EP. + */ +int32_t dwc_otg_pcd_handle_incomplete_isoc_out_intr(dwc_otg_pcd_t * pcd) +{ + + gintsts_data_t gintsts; + +#ifdef DWC_EN_ISOC + dwc_otg_dev_if_t *dev_if; + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + dsts_data_t dsts = {.d32 = 0 }; + dwc_ep_t *dwc_ep; + int i; + + dev_if = GET_CORE_IF(pcd)->dev_if; + + for (i = 1; i <= dev_if->num_out_eps; ++i) { + dwc_ep = &pcd->in_ep[i].dwc_ep; + if (pcd->out_ep[i].dwc_ep.active && + pcd->out_ep[i].dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + deptsiz.d32 = + DWC_READ_REG32(&dev_if->out_ep_regs[i]->doeptsiz); + depctl.d32 = + DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl); + + if (depctl.b.epdis && deptsiz.d32) { + set_current_pkt_info(GET_CORE_IF(pcd), + &pcd->out_ep[i].dwc_ep); + if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) { + dwc_ep->cur_pkt = 0; + dwc_ep->proc_buf_num = + (dwc_ep->proc_buf_num ^ 1) & 0x1; + + if (dwc_ep->proc_buf_num) { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff1; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr1; + } else { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff0; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr0; + } + + } + + dsts.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)->dev_if-> + dev_global_regs->dsts); + dwc_ep->next_frame = dsts.b.soffn; + + dwc_otg_iso_ep_start_frm_transfer(GET_CORE_IF + (pcd), + dwc_ep); + } + } + } +#else + /** @todo implement ISR */ + gintmsk_data_t intr_mask = {.d32 = 0 }; + dwc_otg_core_if_t *core_if; + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + dwc_ep_t *dwc_ep = NULL; + int i; + core_if = GET_CORE_IF(pcd); + + for (i = 0; i < core_if->dev_if->num_out_eps; ++i) { + dwc_ep = &pcd->out_ep[i].dwc_ep; + depctl.d32 = + DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl); + if (depctl.b.epena && depctl.b.dpid == (core_if->frame_num & 0x1)) { + core_if->dev_if->isoc_ep = dwc_ep; + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doeptsiz); + break; + } + } + dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + intr_mask.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + + if (!intr_mask.b.goutnakeff) { + /* Unmask it */ + intr_mask.b.goutnakeff = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, intr_mask.d32); + } + if (!gintsts.b.goutnakeff) { + dctl.b.sgoutnak = 1; + } + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); + + depctl.d32 = DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl); + if (depctl.b.epena) { + depctl.b.epdis = 1; + depctl.b.snak = 1; + } + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl, depctl.d32); + + intr_mask.d32 = 0; + intr_mask.b.incomplisoout = 1; + +#endif /* DWC_EN_ISOC */ + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.incomplisoout = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * This function handles the Global IN NAK Effective interrupt. + * + */ +int32_t dwc_otg_pcd_handle_in_nak_effective(dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if; + depctl_data_t diepctl = {.d32 = 0 }; + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + int i; + + DWC_DEBUGPL(DBG_PCD, "Global IN NAK Effective\n"); + + /* Disable all active IN EPs */ + for (i = 0; i <= dev_if->num_in_eps; i++) { + diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (!(diepctl.b.eptype & 1) && diepctl.b.epena) { + diepctl.b.epdis = 1; + diepctl.b.snak = 1; + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, diepctl.d32); + } + } + + + /* Disable the Global IN NAK Effective Interrupt */ + intr_mask.b.ginnakeff = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.ginnakeff = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * OUT NAK Effective. + * + */ +int32_t dwc_otg_pcd_handle_out_nak_effective(dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if; + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + depctl_data_t doepctl; + int i; +#ifdef USB_DEBUG + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_core_global_regs_t *global_reg = core_if->core_global_regs; + gintsts_data_t gintr_status; + gintmsk_data_t ginmak_status; + gintsts_data_t gintr_status1; + + + gintr_status.d32 = dwc_otg_read_core_intr(core_if); + gintr_status1.d32= DWC_READ_REG32(&global_reg->gintsts); + ginmak_status.d32= DWC_READ_REG32(&global_reg->gintmsk); + + printk( "dwc_otg_pcd_handle_intr gintsts&gintmsk=%08x\n", gintr_status.d32); + printk( "dwc_otg_pcd_handle_intr gintsts =%08x\n ", gintr_status1.d32); + printk( "dwc_otg_pcd_handle_intr gintmsk=%08x\n ",ginmak_status.d32); +#endif + + /* Disable the Global OUT NAK Effective Interrupt */ + intr_mask.b.goutnakeff = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* If DEV OUT NAK enabled */ + if (pcd->core_if->core_params->dev_out_nak) { + /* Run over all out endpoints to determine the ep number on + * which the timeout has happened + */ + for (i = 0; i <= dev_if->num_out_eps; i++) { + if (pcd->core_if->ep_xfer_info[i].state == 2) + break; + } + if (i > dev_if->num_out_eps) { + dctl_data_t dctl; + dctl.d32 = + DWC_READ_REG32(&dev_if->dev_global_regs->dctl); + dctl.b.cgoutnak = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dctl, + dctl.d32); + goto out; + } + + /* Disable the endpoint */ + doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl); + if (doepctl.b.epena) { + doepctl.b.epdis = 1; + doepctl.b.snak = 1; + } + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, doepctl.d32); + return 1; + } + /* We come here from Incomplete ISO OUT handler */ + if (dev_if->isoc_ep) { + dwc_ep_t *dwc_ep = (dwc_ep_t *) dev_if->isoc_ep; + uint32_t epnum = dwc_ep->num; + doepint_data_t doepint; + doepint.d32 = + DWC_READ_REG32(&dev_if->out_ep_regs[dwc_ep->num]->doepint); + dev_if->isoc_ep = NULL; + doepctl.d32 = + DWC_READ_REG32(&dev_if->out_ep_regs[epnum]->doepctl); + DWC_PRINTF("Before disable DOEPCTL = %08x\n", doepctl.d32); + if (doepctl.b.epena) { + doepctl.b.epdis = 1; + doepctl.b.snak = 1; + } + DWC_WRITE_REG32(&dev_if->out_ep_regs[epnum]->doepctl, + doepctl.d32); + return 1; + } else + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", + "Global OUT NAK Effective\n"); + +out: + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.goutnakeff = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * PCD interrupt handler. + * + * The PCD handles the device interrupts. Many conditions can cause a + * device interrupt. When an interrupt occurs, the device interrupt + * service routine determines the cause of the interrupt and + * dispatches handling to the appropriate function. These interrupt + * handling functions are described below. + * + * All interrupt registers are processed from LSB to MSB. + * + */ +int32_t dwc_otg_pcd_handle_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); +#ifdef VERBOSE + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; +#endif + gintsts_data_t gintr_status; + int32_t retval = 0; + + /* Exit from ISR if core is hibernated */ + if (core_if->hibernation_suspend == 1) { + return retval; + } +#ifdef VERBOSE + DWC_DEBUGPL(DBG_ANY, "%s() gintsts=%08x gintmsk=%08x\n", + __func__, + DWC_READ_REG32(&global_regs->gintsts), + DWC_READ_REG32(&global_regs->gintmsk)); +#endif + + if (dwc_otg_is_device_mode(core_if)) { + DWC_SPINLOCK(pcd->lock); +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "%s() gintsts=%08x gintmsk=%08x\n", + __func__, + DWC_READ_REG32(&global_regs->gintsts), + DWC_READ_REG32(&global_regs->gintmsk)); +#endif + + gintr_status.d32 = dwc_otg_read_core_intr(core_if); + + DWC_DEBUGPL(DBG_PCDV, "%s: gintsts&gintmsk=%08x\n", + __func__, gintr_status.d32); + + if (gintr_status.b.sofintr) { + retval |= dwc_otg_pcd_handle_sof_intr(pcd); + } + if (gintr_status.b.rxstsqlvl) { + retval |= + dwc_otg_pcd_handle_rx_status_q_level_intr(pcd); + } + if (gintr_status.b.nptxfempty) { + retval |= dwc_otg_pcd_handle_np_tx_fifo_empty_intr(pcd); + } + if (gintr_status.b.goutnakeff) { + retval |= dwc_otg_pcd_handle_out_nak_effective(pcd); + } + if (gintr_status.b.i2cintr) { + retval |= dwc_otg_pcd_handle_i2c_intr(pcd); + } + if (gintr_status.b.erlysuspend) { + retval |= dwc_otg_pcd_handle_early_suspend_intr(pcd); + } + if (gintr_status.b.usbreset) { + retval |= dwc_otg_pcd_handle_usb_reset_intr(pcd); + } + if (gintr_status.b.enumdone) { + retval |= dwc_otg_pcd_handle_enum_done_intr(pcd); + } + if (gintr_status.b.isooutdrop) { + retval |= + dwc_otg_pcd_handle_isoc_out_packet_dropped_intr + (pcd); + } + if (gintr_status.b.eopframe) { + retval |= + dwc_otg_pcd_handle_end_periodic_frame_intr(pcd); + } + if (gintr_status.b.inepint) { + if (!core_if->multiproc_int_enable) { + retval |= dwc_otg_pcd_handle_in_ep_intr(pcd); + } + } + if (gintr_status.b.outepintr) { + if (!core_if->multiproc_int_enable) { + retval |= dwc_otg_pcd_handle_out_ep_intr(pcd); + } + } + if (gintr_status.b.epmismatch) { + retval |= dwc_otg_pcd_handle_ep_mismatch_intr(pcd); + } + if (gintr_status.b.fetsusp) { + retval |= dwc_otg_pcd_handle_ep_fetsusp_intr(pcd); + } + if (gintr_status.b.ginnakeff) { + retval |= dwc_otg_pcd_handle_in_nak_effective(pcd); + } + if (gintr_status.b.incomplisoin) { + retval |= + dwc_otg_pcd_handle_incomplete_isoc_in_intr(pcd); + } + if (gintr_status.b.incomplisoout) { + retval |= + dwc_otg_pcd_handle_incomplete_isoc_out_intr(pcd); + } + + /* In MPI mode Device Endpoints interrupts are asserted + * without setting outepintr and inepint bits set, so these + * Interrupt handlers are called without checking these bit-fields + */ + if (core_if->multiproc_int_enable) { + retval |= dwc_otg_pcd_handle_in_ep_intr(pcd); + retval |= dwc_otg_pcd_handle_out_ep_intr(pcd); + } +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "%s() gintsts=%0x\n", __func__, + DWC_READ_REG32(&global_regs->gintsts)); +#endif + DWC_SPINUNLOCK(pcd->lock); + } + return retval; +} + +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c new file mode 100644 index 00000000..a3001d00 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c @@ -0,0 +1,1675 @@ + /* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_linux.c $ + * $Revision: #6 $ + * $Date: 2009/02/18 $ + * $Change: 1190679 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY + +/** @file + * This file implements the Peripheral Controller Driver. + * + * The Peripheral Controller Driver (PCD) is responsible for + * translating requests from the Function Driver into the appropriate + * actions on the DWC_otg controller. It isolates the Function Driver + * from the specifics of the controller by providing an API to the + * Function Driver. + * + * The Peripheral Controller Driver for Linux will implement the + * Gadget API, so that the existing Gadget drivers can be used. + * (Gadget Driver is the Linux terminology for a Function Driver.) + * + * The Linux Gadget API is defined in the header file + * . The USB EP operations API is + * defined in the structure usb_ep_ops and the USB + * Controller API is defined in the structure + * usb_gadget_ops. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +# include +#include +#include +#include +#include +#include +#include + +#include "dwc_otg_driver.h" +#include "dwc_otg_pcd_if.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_pcd.h" +#include "dwc_otg_dbg.h" +#include "usb_hw.h" + +static struct gadget_wrapper { + dwc_otg_pcd_t *pcd; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct usb_ep ep0; + struct usb_ep in_ep[16]; + struct usb_ep out_ep[16]; + /* + * this timer is used for checking cable type, usb or ac adapter. + */ + struct workqueue_struct *cable2pc_wq; + struct workqueue_struct *detect_wq; + struct workqueue_struct *reinit_wq; + struct work_struct detect_work; + struct work_struct reinit_work; + struct delayed_work cable2pc; + + struct switch_dev sdev; + int udc_startup; + int enabled; + int vbus; + spinlock_t lock; +} *gadget_wrapper; + +static struct wake_lock usb_wake_lock; +static u64 dwc_otg_pcd_dmamask = DMA_BIT_MASK(32); + +static DEFINE_MUTEX(udc_lock); + +#define CABLE_TIMEOUT (HZ*15) + +/* Display the contents of the buffer */ +extern void dump_msg(const u8 * buf, unsigned int length); + +extern int in_calibration(void); +extern void dwc_otg_pcd_stop(dwc_otg_pcd_t *pcd); +static void dwc_otg_clear_all_int(dwc_otg_core_if_t *core_if); +static struct timer_list reinit_usb_timer; +static int factory_mode = false; +static int charge_mode = false; /* add by lgd, fix the problem that sometime poweroff charging is so slow */ +static int __init factory_start(char *str) +{ + if(str) + pr_info("factory mode!\n"); + factory_mode = true; + return 1; +} +__setup("factory", factory_start); + +int in_factory_mode(void) +{ + return (factory_mode == true); +} + +/* < add by lgd, fix the problem that sometime poweroff charging is so slow */ +static int __init charge_start(char *str) +{ + if(!strncmp(str,"charger",7)) + { + DWC_DEBUG("charge mode!\n"); + charge_mode = true; + return 1; + } else { + DWC_DEBUG("no charge mode!\n"); + charge_mode = false; + return 0; + } +} +__setup("androidboot.mode=", charge_start); + +int in_charge_mode(void) +{ + DWC_DEBUG("%s() in\n", __func__); + return (charge_mode == true); +} +/* add by lgd, fix the problem that sometime poweroff charging is so slow > */ + +static void usb_reinit_works(void) +{ + struct gadget_wrapper *d; + + d = gadget_wrapper; + + DWC_PRINTF("reinit start ...\n"); + dwc_otg_disable_global_interrupts(GET_CORE_IF(d->pcd)); + dwc_otg_clear_all_int(GET_CORE_IF(d->pcd)); + dwc_otg_pcd_stop(d->pcd); + udc_disable(); + mdelay(200); + udc_enable(); + dwc_otg_core_init(GET_CORE_IF(d->pcd)); + dwc_otg_enable_global_interrupts(GET_CORE_IF(d->pcd)); + dwc_otg_core_dev_init(GET_CORE_IF(d->pcd)); +} +static void reinit_usb_timer_fun(unsigned long para) +{ + struct gadget_wrapper *d; + + d = gadget_wrapper; + queue_work(d->reinit_wq, &d->reinit_work); + del_timer(&reinit_usb_timer); +} +void _reinit_usb_later(dwc_otg_pcd_t *pcd) +{ + dctl_data_t dctl = {.d32=0}; + dwc_otg_dev_if_t *dev_if; + + if (pcd->reinit_flag == 0) + return; + + dev_if = pcd->core_if->dev_if; + dctl.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dctl); + dctl.b.sftdiscon = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dctl, dctl.d32); + + DWC_PRINTF("call reinit USB controller...\n"); + + mod_timer(&reinit_usb_timer, jiffies + 3*HZ); +} +static struct timer_list setup_transfer_timer; +static int setup_transfer_timer_start = 0; + +static void setup_transfer_timer_fun(unsigned long para) +{ + if (gadget_wrapper->pcd->reinit_flag) { + setup_transfer_timer_start = 0; + _reinit_usb_later(gadget_wrapper->pcd); + } +} +/* USB Endpoint Operations */ +/* + * The following sections briefly describe the behavior of the Gadget + * API endpoint operations implemented in the DWC_otg driver + * software. Detailed descriptions of the generic behavior of each of + * these functions can be found in the Linux header file + * include/linux/usb_gadget.h. + * + * The Gadget API provides wrapper functions for each of the function + * pointers defined in usb_ep_ops. The Gadget Driver calls the wrapper + * function, which then calls the underlying PCD function. The + * following sections are named according to the wrapper + * functions. Within each section, the corresponding DWC_otg PCD + * function name is specified. + * + */ + +/** + * This function is called by the Gadget Driver for each EP to be + * configured for the current configuration (SET_CONFIGURATION). + * + * This function initializes the dwc_otg_ep_t data structure, and then + * calls dwc_otg_ep_activate. + */ +static int ep_enable(struct usb_ep *usb_ep, + const struct usb_endpoint_descriptor *ep_desc) +{ + int retval; + + if (!usb_ep || !ep_desc || ep_desc->bDescriptorType != USB_DT_ENDPOINT) { + DWC_WARN("%s, bad ep or descriptor\n", __func__); + return -EINVAL; + } + if (usb_ep == &gadget_wrapper->ep0) { + DWC_WARN("%s, bad ep(0)\n", __func__); + return -EINVAL; + } + + /* Check FIFO size? */ + if (!ep_desc->wMaxPacketSize) { + DWC_WARN("%s, bad %s maxpacket\n", __func__, usb_ep->name); + return -ERANGE; + } + + if (!gadget_wrapper->driver || + gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) { + DWC_WARN("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + retval = dwc_otg_pcd_ep_enable(gadget_wrapper->pcd, + (const uint8_t *)ep_desc, + (void *)usb_ep); + if (retval) { + DWC_WARN("dwc_otg_pcd_ep_enable failed\n"); + return -EINVAL; + } + + usb_ep->maxpacket = le16_to_cpu(ep_desc->wMaxPacketSize); + + return 0; +} + +/** + * This function is called when an EP is disabled due to disconnect or + * change in configuration. Any pending requests will terminate with a + * status of -ESHUTDOWN. + * + * This function modifies the dwc_otg_ep_t data structure for this EP, + * and then calls dwc_otg_ep_deactivate. + */ +static int ep_disable(struct usb_ep *usb_ep) +{ + int retval; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, usb_ep); + if (!usb_ep) { + DWC_DEBUGPL(DBG_PCD, "%s, %s not enabled\n", __func__, + usb_ep ? usb_ep->name : NULL); + return -EINVAL; + } + + retval = dwc_otg_pcd_ep_disable(gadget_wrapper->pcd, usb_ep); + if (retval) { + retval = -EINVAL; + } + + return retval; +} + +/** + * This function allocates a request object to use with the specified + * endpoint. + * + * @param ep The endpoint to be used with with the request + * @param gfp_flags the GFP_* flags to use. + */ +static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct usb_request *usb_req; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%d)\n", __func__, ep, gfp_flags); + if (0 == ep) { + DWC_WARN("%s() %s\n", __func__, "Invalid EP!\n"); + return 0; + } + usb_req = kmalloc(sizeof(*usb_req), gfp_flags); + if (0 == usb_req) { + DWC_WARN("%s() %s\n", __func__, "request allocation failed!\n"); + return 0; + } + memset(usb_req, 0, sizeof(*usb_req)); + usb_req->dma = DWC_DMA_ADDR_INVALID; + + return usb_req; +} + +/** + * This function frees a request object. + * + * @param ep The endpoint associated with the request + * @param req The request being freed + */ +static void dwc_otg_pcd_free_request(struct usb_ep *ep, struct usb_request *req) +{ + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p)\n", __func__, ep, req); + + if (0 == ep || 0 == req) { + DWC_WARN("%s() %s\n", __func__, + "Invalid ep or req argument!\n"); + return; + } + + kfree(req); +} + +/** + * This function is used to submit an I/O Request to an EP. + * + * - When the request completes the request's completion callback + * is called to return the request to the driver. + * - An EP, except control EPs, may have multiple requests + * pending. + * - Once submitted the request cannot be examined or modified. + * - Each request is turned into one or more packets. + * - A BULK EP can queue any amount of data; the transfer is + * packetized. + * - Zero length Packets are specified with the request 'zero' + * flag. + */ +static int ep_queue(struct usb_ep *usb_ep, struct usb_request *usb_req, + gfp_t gfp_flags) +{ + dwc_otg_pcd_t *pcd; + int retval = 0; + + //trace_printk("(%p,%p,%d)\n", + // usb_ep, usb_req, gfp_flags); + + if (!usb_req || !usb_req->complete || + (!gadget_wrapper->gadget.sg_supported && + !usb_req->buf)) { + DWC_WARN("bad params\n"); + return -EINVAL; + } + + if (!usb_ep) { + DWC_WARN("bad ep\n"); + return -EINVAL; + } + + pcd = gadget_wrapper->pcd; + if (!gadget_wrapper->driver || + gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) { + DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n", + gadget_wrapper->gadget.speed); + DWC_WARN("bogus device state\n"); + return -ESHUTDOWN; + } + + //trace_printk( "%s queue req %p, len %d buf %p\n", + // usb_ep->name, usb_req, usb_req->length, usb_req->buf); + + usb_req->status = -EINPROGRESS; + usb_req->actual = 0; + + retval = dwc_otg_pcd_ep_queue(pcd, usb_ep, usb_req->buf, usb_req->dma/*dma_addr*/, + usb_req->length, usb_req->zero, usb_req->num_sgs, + usb_req->sg, usb_req, gfp_flags == GFP_ATOMIC ? 1 : 0); + if (retval) { + pr_err("%s, cannot enqueue a renquest, err :%d\n", __func__, + retval); + pr_info( "%s queue req %p, len %d buf %p\n", + usb_ep->name, usb_req, usb_req->length, usb_req->buf); + return -EINVAL; + } + + return 0; +} + +/** + * This function cancels an I/O request from an EP. + */ +static int ep_dequeue(struct usb_ep *usb_ep, struct usb_request *usb_req) +{ + + if (!usb_ep || !usb_req) { + DWC_WARN("bad argument\n"); + return -EINVAL; + } + if (!gadget_wrapper->driver || + gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) { + DWC_WARN("bogus device state\n"); + return -ESHUTDOWN; + } + if (dwc_otg_pcd_ep_dequeue(gadget_wrapper->pcd, usb_ep, usb_req)) { + return -EINVAL; + } + + return 0; +} + +/** + * usb_ep_set_halt stalls an endpoint. + * + * usb_ep_clear_halt clears an endpoint halt and resets its data + * toggle. + * + * Both of these functions are implemented with the same underlying + * function. The behavior depends on the value argument. + * + * @param[in] usb_ep the Endpoint to halt or clear halt. + * @param[in] value + * - 0 means clear_halt. + * - 1 means set_halt, + * - 2 means clear stall lock flag. + * - 3 means set stall lock flag. + */ +static int ep_halt(struct usb_ep *usb_ep, int value) +{ + int retval = 0; + + DWC_DEBUGPL(DBG_PCD, "HALT %s %d\n", usb_ep->name, value); + + if (!usb_ep) { + DWC_WARN("bad ep\n"); + return -EINVAL; + } + + retval = dwc_otg_pcd_ep_halt(gadget_wrapper->pcd, usb_ep, value); + if (retval == -DWC_E_AGAIN) { + return -EAGAIN; + } else if (retval) { + retval = -EINVAL; + } + + return retval; +} + +#ifdef DWC_EN_ISOC +/** + * This function is used to submit an ISOC Transfer Request to an EP. + * + * - Every time a sync period completes the request's completion callback + * is called to provide data to the gadget driver. + * - Once submitted the request cannot be modified. + * - Each request is turned into periodic data packets untill ISO + * Transfer is stopped.. + */ +static int iso_ep_start(struct usb_ep *usb_ep, struct usb_iso_request *req, + gfp_t gfp_flags) +{ + int retval = 0; + + if (!req || !req->process_buffer || !req->buf0 || !req->buf1) { + DWC_WARN("bad params\n"); + return -EINVAL; + } + + if (!usb_ep) { + DWC_PRINTF("bad params\n"); + return -EINVAL; + } + + req->status = -EINPROGRESS; + + retval = + dwc_otg_pcd_iso_ep_start(gadget_wrapper->pcd, usb_ep, req->buf0, + req->buf1, req->dma0, req->dma1, + req->sync_frame, req->data_pattern_frame, + req->data_per_frame, + req->flags & USB_REQ_ISO_ASAP ? -1 : req-> + start_frame, req->buf_proc_intrvl, req, + gfp_flags == GFP_ATOMIC ? 1 : 0); + + if (retval) { + return -EINVAL; + } + + return retval; +} + +/** + * This function stops ISO EP Periodic Data Transfer. + */ +static int iso_ep_stop(struct usb_ep *usb_ep, struct usb_iso_request *req) +{ + int retval = 0; + if (!usb_ep) { + DWC_WARN("bad ep\n"); + } + + if (!gadget_wrapper->driver || + gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) { + DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n", + gadget_wrapper->gadget.speed); + DWC_WARN("bogus device state\n"); + } + + dwc_otg_pcd_iso_ep_stop(gadget_wrapper->pcd, usb_ep, req); + if (retval) { + retval = -EINVAL; + } + + return retval; +} + +static struct usb_iso_request *alloc_iso_request(struct usb_ep *ep, + int packets, gfp_t gfp_flags) +{ + struct usb_iso_request *pReq = NULL; + uint32_t req_size; + + req_size = sizeof(struct usb_iso_request); + req_size += + (2 * packets * (sizeof(struct usb_gadget_iso_packet_descriptor))); + + pReq = kmalloc(req_size, gfp_flags); + if (!pReq) { + DWC_WARN("Can't allocate Iso Request\n"); + return 0; + } + pReq->iso_packet_desc0 = (void *)(pReq + 1); + + pReq->iso_packet_desc1 = pReq->iso_packet_desc0 + packets; + + return pReq; +} + +static void free_iso_request(struct usb_ep *ep, struct usb_iso_request *req) +{ + kfree(req); +} + +static struct usb_isoc_ep_ops dwc_otg_pcd_ep_ops = { + .ep_ops = { + .enable = ep_enable, + .disable = ep_disable, + + .alloc_request = dwc_otg_pcd_alloc_request, + .free_request = dwc_otg_pcd_free_request, + + .alloc_buffer = dwc_otg_pcd_alloc_buffer, + .free_buffer = dwc_otg_pcd_free_buffer, + + .queue = ep_queue, + .dequeue = ep_dequeue, + + .set_halt = ep_halt, + .fifo_status = 0, + .fifo_flush = 0, + }, + .iso_ep_start = iso_ep_start, + .iso_ep_stop = iso_ep_stop, + .alloc_iso_request = alloc_iso_request, + .free_iso_request = free_iso_request, +}; + +#else + +static struct usb_ep_ops dwc_otg_pcd_ep_ops = { + .enable = ep_enable, + .disable = ep_disable, + + .alloc_request = dwc_otg_pcd_alloc_request, + .free_request = dwc_otg_pcd_free_request, + + .queue = ep_queue, + .dequeue = ep_dequeue, + + .set_halt = ep_halt, + .fifo_status = 0, + .fifo_flush = 0, + +}; + +#endif /* _EN_ISOC_ */ +/* Gadget Operations */ +/** + * The following gadget operations will be implemented in the DWC_otg + * PCD. Functions in the API that are not described below are not + * implemented. + * + * The Gadget API provides wrapper functions for each of the function + * pointers defined in usb_gadget_ops. The Gadget Driver calls the + * wrapper function, which then calls the underlying PCD function. The + * following sections are named according to the wrapper functions + * (except for ioctl, which doesn't have a wrapper function). Within + * each section, the corresponding DWC_otg PCD function name is + * specified. + * + */ + +/** + *Gets the USB Frame number of the last SOF. + */ +static int get_frame_number(struct usb_gadget *gadget) +{ + struct gadget_wrapper *d; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, gadget); + + if (gadget == 0) { + return -ENODEV; + } + + d = container_of(gadget, struct gadget_wrapper, gadget); + return dwc_otg_pcd_get_frame_number(d->pcd); +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +static int test_lpm_enabled(struct usb_gadget *gadget) +{ + struct gadget_wrapper *d; + + d = container_of(gadget, struct gadget_wrapper, gadget); + + return dwc_otg_pcd_is_lpm_enabled(d->pcd); +} +#endif + +/** + * Initiates Session Request Protocol (SRP) to wakeup the host if no + * session is in progress. If a session is already in progress, but + * the device is suspended, remote wakeup signaling is started. + * + */ +/* + * currently we don't need remote wakeup, for it will cause USBCV + * test fail; + */ +static int wakeup(struct usb_gadget *gadget)__attribute__((unused)); +static int wakeup(struct usb_gadget *gadget) +{ + struct gadget_wrapper *d; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, gadget); + + if (gadget == 0) { + return -ENODEV; + } else { + d = container_of(gadget, struct gadget_wrapper, gadget); + } + dwc_otg_pcd_wakeup(d->pcd); + return 0; +} + +extern void dwc_otg_pcd_stop(dwc_otg_pcd_t *pcd); + +static void __udc_startup(void); +static void __udc_shutdown(void); + +static int pullup(struct usb_gadget *gadget, int is_on) +{ + struct gadget_wrapper *d; + int action = is_on; + +#ifndef DWC_DEVICE_ONLY + if(!usb_get_id_state()) + return 0; +#endif + if (gadget == 0) + return -ENODEV; + else + d = container_of(gadget, struct gadget_wrapper, gadget); + + if (!d->enabled || !d->vbus) + action = 0; + + mutex_lock(&udc_lock); + if (action) { + queue_delayed_work(d->cable2pc_wq, &d->cable2pc,CABLE_TIMEOUT); + __udc_startup(); + } else { + /* + *this is soft disconnct, and maybe the timer started + *by plugin still work, need cancel this timer like plugout + */ + cancel_delayed_work_sync(&d->cable2pc); + __udc_shutdown(); + } + mutex_unlock(&udc_lock); + + return 0; +} + +static int dwc_usb_gadget_start(struct usb_gadget *, struct usb_gadget_driver *); +static int dwc_usb_gadget_stop(struct usb_gadget *, struct usb_gadget_driver *); + +static const struct usb_gadget_ops dwc_otg_pcd_ops = { + .get_frame = get_frame_number, + //.wakeup = wakeup, +#ifdef CONFIG_USB_DWC_OTG_LPM + .lpm_support = test_lpm_enabled, +#endif + .pullup = pullup, + // current versions must always be self-powered + .udc_start = dwc_usb_gadget_start, + .udc_stop = dwc_usb_gadget_stop, +}; + +static int _setup(dwc_otg_pcd_t * pcd, uint8_t * bytes) +{ + int retval = -DWC_E_NOT_SUPPORTED; + + if (pcd->reinit_flag == 1 && setup_transfer_timer_start == 0) { + setup_transfer_timer_start = 1; + mod_timer(&setup_transfer_timer, jiffies + HZ/2); + } + if (gadget_wrapper->driver && gadget_wrapper->driver->setup) { + retval = gadget_wrapper->driver->setup(&gadget_wrapper->gadget, + (struct usb_ctrlrequest + *)bytes); + } + + trace_printk("setup res val: %d\n", retval); + //sword + //if (retval == -ENOTSUPP) { + if (retval == -EOPNOTSUPP) { + retval = -DWC_E_NOT_SUPPORTED; + } else if (retval < 0) { + retval = -DWC_E_INVALID; + } + + return retval; +} + +#ifdef DWC_EN_ISOC +static int _isoc_complete(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int proc_buf_num) +{ + int i, packet_count; + struct usb_gadget_iso_packet_descriptor *iso_packet = 0; + struct usb_iso_request *iso_req = req_handle; + + if (proc_buf_num) { + iso_packet = iso_req->iso_packet_desc1; + } else { + iso_packet = iso_req->iso_packet_desc0; + } + packet_count = + dwc_otg_pcd_get_iso_packet_count(pcd, ep_handle, req_handle); + for (i = 0; i < packet_count; ++i) { + int status; + int actual; + int offset; + dwc_otg_pcd_get_iso_packet_params(pcd, ep_handle, req_handle, + i, &status, &actual, &offset); + switch (status) { + case -DWC_E_NO_DATA: + status = -ENODATA; + break; + default: + if (status) { + DWC_PRINTF("unknown status in isoc packet\n"); + } + + } + iso_packet[i].status = status; + iso_packet[i].offset = offset; + iso_packet[i].actual_length = actual; + } + + iso_req->status = 0; + iso_req->process_buffer(ep_handle, iso_req); + + return 0; +} +#endif /* DWC_EN_ISOC */ + +static int _complete(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int32_t status, uint32_t actual) +{ + struct usb_request *req = (struct usb_request *)req_handle; + + if (req && req->complete) { + switch (status) { + case -DWC_E_SHUTDOWN: + req->status = -ESHUTDOWN; + break; + case -DWC_E_RESTART: + req->status = -ECONNRESET; + break; + case -DWC_E_INVALID: + req->status = -EINVAL; + break; + case -DWC_E_TIMEOUT: + req->status = -ETIMEDOUT; + break; + default: + req->status = status; + + } + if (pcd->ep0.priv == ep_handle && pcd->reinit_flag == 1) { + pcd->reinit_flag = 3; + setup_transfer_timer_start = 0; + del_timer(&setup_transfer_timer); + } + req->actual = actual; + DWC_SPINUNLOCK(pcd->lock); + req->complete(ep_handle, req); + DWC_SPINLOCK(pcd->lock); + } + + return 0; +} + +static int _connect(dwc_otg_pcd_t * pcd, int speed) +{ + gadget_wrapper->gadget.speed = speed; + return 0; +} + +static int _disconnect(dwc_otg_pcd_t * pcd) +{ + if (gadget_wrapper->driver && gadget_wrapper->driver->disconnect) { + gadget_wrapper->driver->disconnect(&gadget_wrapper->gadget); + } + return 0; +} + +static int _resume(dwc_otg_pcd_t * pcd) +{ + if (gadget_wrapper->driver && gadget_wrapper->driver->resume) { + gadget_wrapper->driver->resume(&gadget_wrapper->gadget); + } + + return 0; +} + +static int _suspend(dwc_otg_pcd_t * pcd) +{ + if (gadget_wrapper->driver && gadget_wrapper->driver->suspend) { + gadget_wrapper->driver->suspend(&gadget_wrapper->gadget); + } + return 0; +} + +/** + * This function updates the otg values in the gadget structure. + */ +static int _hnp_changed(dwc_otg_pcd_t * pcd) +{ + + if (!gadget_wrapper->gadget.is_otg) + return 0; + + gadget_wrapper->gadget.b_hnp_enable = get_b_hnp_enable(pcd); + gadget_wrapper->gadget.a_hnp_support = get_a_hnp_support(pcd); + gadget_wrapper->gadget.a_alt_hnp_support = get_a_alt_hnp_support(pcd); + return 0; +} + +static int _reset(dwc_otg_pcd_t * pcd) +{ + return 0; +} + +#ifdef DWC_UTE_CFI +static int _cfi_setup(dwc_otg_pcd_t * pcd, void *cfi_req) +{ + int retval = -DWC_E_INVALID; + if (gadget_wrapper->driver->cfi_feature_setup) { + retval = + gadget_wrapper->driver->cfi_feature_setup(&gadget_wrapper-> + gadget, + (struct + cfi_usb_ctrlrequest + *)cfi_req); + } + + return retval; +} +#endif + +static const struct dwc_otg_pcd_function_ops fops = { + .complete = _complete, +#ifdef DWC_EN_ISOC + .isoc_complete = _isoc_complete, +#endif + .setup = _setup, + .disconnect = _disconnect, + .connect = _connect, + .resume = _resume, + .suspend = _suspend, + .hnp_changed = _hnp_changed, + .reset = _reset, +#ifdef DWC_UTE_CFI + .cfi_setup = _cfi_setup, +#endif +}; + +/** + * This function is the top level PCD interrupt handler. + */ +static irqreturn_t dwc_otg_pcd_irq(int irq, void *dev) +{ + dwc_otg_pcd_t *pcd = dev; + int32_t retval = IRQ_NONE; + + retval = dwc_otg_pcd_handle_intr(pcd); + if (retval != 0) { + S3C2410X_CLEAR_EINTPEND(); + } + return IRQ_RETVAL(retval); +} + +/** + * This function initialized the usb_ep structures to there default + * state. + * + * @param d Pointer on gadget_wrapper. + */ +void gadget_add_eps(struct gadget_wrapper *d) +{ + static const char *names[] = { + + "ep0", + "ep1in", + "ep2in", + "ep3in", + "ep4in", + "ep5in", + "ep6in", + "ep7in", + "ep8in", + "ep9in", + "ep10in", + "ep11in", + "ep12in", + "ep13in", + "ep14in", + "ep15in", + "ep1out", + "ep2out", + "ep3out", + "ep4out", + "ep5out", + "ep6out", + "ep7out", + "ep8out", + "ep9out", + "ep10out", + "ep11out", + "ep12out", + "ep13out", + "ep14out", + "ep15out" + }; + + int i; + struct usb_ep *ep; + + DWC_DEBUGPL(DBG_PCDV, "%s\n", __func__); + + INIT_LIST_HEAD(&d->gadget.ep_list); + d->gadget.ep0 = &d->ep0; + d->gadget.speed = USB_SPEED_UNKNOWN; + + INIT_LIST_HEAD(&d->gadget.ep0->ep_list); + + /** + * Initialize the EP0 structure. + */ + ep = &d->ep0; + + /* Init the usb_ep structure. */ + ep->name = names[0]; + ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops; + + /** + * @todo NGS: What should the max packet size be set to + * here? Before EP type is set? + */ + ep->maxpacket = MAX_PACKET_SIZE; + dwc_otg_pcd_ep_enable(d->pcd, NULL, ep); + + list_add_tail(&ep->ep_list, &d->gadget.ep_list); + + /** + * Initialize the EP structures. + */ + + for (i = 0; i < 15; i++) { + ep = &d->in_ep[i]; + + /* Init the usb_ep structure. */ + ep->name = names[i + 1]; + ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops; + + /** + * @todo NGS: What should the max packet size be set to + * here? Before EP type is set? + */ + ep->maxpacket = MAX_PACKET_SIZE; + list_add_tail(&ep->ep_list, &d->gadget.ep_list); + } + + for (i = 0; i < 15; i++) { + ep = &d->out_ep[i]; + + /* Init the usb_ep structure. */ + ep->name = names[15 + i + 1]; + ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops; + + /** + * @todo NGS: What should the max packet size be set to + * here? Before EP type is set? + */ + ep->maxpacket = MAX_PACKET_SIZE; + + list_add_tail(&ep->ep_list, &d->gadget.ep_list); + } + + /* remove ep0 from the list. There is a ep0 pointer. */ + list_del_init(&d->ep0.ep_list); + + d->ep0.maxpacket = MAX_EP0_SIZE; +} + +/** + * This function releases the Gadget device. + * required by device_unregister(). + * + * @todo Should this do something? Should it free the PCD? + */ +static void dwc_otg_pcd_gadget_release(struct device *dev) +{ + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, dev); +} + +static struct gadget_wrapper *alloc_wrapper( + struct platform_device *_dev + ) +{ + static char pcd_name[] = "dwc_otg"; + dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev); + struct gadget_wrapper *d; + int retval; + + d = dwc_alloc(sizeof(*d)); + if (d == NULL) { + return NULL; + } + + memset(d, 0, sizeof(*d)); + + d->gadget.name = pcd_name; + d->pcd = otg_dev->pcd; + //sword + /* + strcpy(d->gadget.dev.bus_id, "gadget"); + */ + dev_set_name(&d->gadget.dev, "gadget"); + d->gadget.dev.parent = &_dev->dev; + d->gadget.dev.release = dwc_otg_pcd_gadget_release; + d->gadget.ops = &dwc_otg_pcd_ops; + d->gadget.max_speed = USB_SPEED_HIGH; + d->gadget.is_otg = dwc_otg_pcd_is_otg(otg_dev->pcd); + if (d->pcd->core_if->dma_enable && + d->pcd->core_if->dma_desc_enable) + d->gadget.sg_supported = 1; + + d->driver = 0; + /* Register the gadget device */ +#if 0 + retval = device_register(&d->gadget.dev); + if (retval != 0) { + DWC_ERROR("device_register failed\n"); + dwc_free(d); + return NULL; + } +#endif + + return d; +} + +static void free_wrapper(struct gadget_wrapper *d) +{ + if (d->driver) { + /* should have been done already by driver model core */ + DWC_WARN("driver '%s' is still registered\n", + d->driver->driver.name); + usb_gadget_unregister_driver(d->driver); + } + + device_unregister(&d->gadget.dev); + dwc_free(d); +} + +static void __udc_startup(void) +{ + struct gadget_wrapper *d; + + d = gadget_wrapper; + + pr_info("USB:startup udc\n"); + if (!d->udc_startup) { + /* < add by lgd, fix the problem that sometime poweroff charging is so slow */ + //wake_lock(&usb_wake_lock); + if(!in_charge_mode()) { + wake_lock(&usb_wake_lock); + DWC_DEBUG("%s(): wake_lock", __func__); + } + /* add by lgd, fix the problem that sometime poweroff charging is so slow > */ + udc_enable(); + dwc_otg_core_init(GET_CORE_IF(d->pcd)); + dwc_otg_enable_global_interrupts(GET_CORE_IF(d->pcd)); + dwc_otg_core_dev_init(GET_CORE_IF(d->pcd)); + d->udc_startup = 1; + } +} +static void dwc_otg_clear_all_int(dwc_otg_core_if_t *core_if) +{ + uint32_t inep_intr; + uint32_t outep_intr; + uint32_t gintsts; + uint32_t epnum = 0; + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + for(epnum=0;epnumin_ep_regs[epnum]->diepint); + DWC_WRITE_REG32(&dev_if->in_ep_regs[epnum]->diepint, inep_intr); + } + for(epnum=0;epnumout_ep_regs[epnum]->doepint); + DWC_WRITE_REG32(&dev_if->out_ep_regs[epnum]->doepint, outep_intr); + } + gintsts = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts); +} +static void __udc_shutdown(void) +{ + struct gadget_wrapper *d; + + d = gadget_wrapper; + + pr_info("USB:shutdown udc\n"); + if (d->udc_startup) { + dwc_otg_disable_global_interrupts(GET_CORE_IF(d->pcd)); + dwc_otg_clear_all_int(GET_CORE_IF(d->pcd)); + dwc_otg_pcd_stop(d->pcd); + udc_disable(); + d->udc_startup = 0; + /* < add by lgd, fix the problem that sometime poweroff charging is so slow */ + //wake_unlock(&usb_wake_lock); + if(!in_charge_mode()) { + DWC_DEBUG("%s(): wake_unlock", __func__); + wake_unlock(&usb_wake_lock); + } + /* add by lgd, fix the problem that sometime poweroff charging is so slow > */ + } +} + +static int cable_is_connected(void); + +void dwc_udc_startup(void) +{ + //if usb cable not connect, do nothing + if (!cable_is_connected()) { + pr_warning("usb cable is not connect\n"); + return; + } + //udc not startup, startup udc and get a wacklock + mutex_lock(&udc_lock); + __udc_startup(); + mutex_unlock(&udc_lock); +} + +void dwc_udc_shutdown(void) +{ + mutex_lock(&udc_lock); + __udc_shutdown(); + mutex_unlock(&udc_lock); +} + +int dwc_udc_state(void) +{ + struct gadget_wrapper *d; + + d = gadget_wrapper; + return d->udc_startup; +} + +void udc_phy_down(void) +{ + //struct gadget_wrapper *d = gadget_wrapper; + + //dwc_otg_dev_soft_disconnect(GET_CORE_IF(d->pcd)); +} + +void udc_phy_up(void) +{ + //struct gadget_wrapper *d = gadget_wrapper; + + //dwc_otg_dev_soft_connect(GET_CORE_IF(d->pcd)); +} +static struct usb_hotplug_callback *hotplug_cb; +static void hotplug_callback(int event, int usb_cable) +{ + if (unlikely(!hotplug_cb || !hotplug_cb->plugin)) + pr_warning("%s, hotplug call back is not registered\n", + __func__); + switch (event) { + case VBUS_PLUG_IN: + if (hotplug_cb && hotplug_cb->plugin) + hotplug_cb->plugin(usb_cable, hotplug_cb->data); + break; + case VBUS_PLUG_OUT: + if (hotplug_cb && hotplug_cb->plugout) + hotplug_cb->plugout(usb_cable, hotplug_cb->data); + break; + default: + pr_warning("hotplug envent error %d\n", event); + break; + } + return; +} + + +static int cable_is_usb(void) +{ + int value; + struct gadget_wrapper *d = gadget_wrapper; + + value = d->pcd->ep0state; + return value != EP0_DISCONNECT; +} + +static int usb_first_enable_flag = 0; +void usb_first_enable_store_flag(void) +{ + usb_first_enable_flag = 1; +} + +int get_usb_first_enable_store_flag(void) +{ + return usb_first_enable_flag; +} + +EXPORT_SYMBOL(usb_first_enable_store_flag); +EXPORT_SYMBOL(get_usb_first_enable_store_flag); + +static void usb_detect_works(struct work_struct *work) +{ + struct gadget_wrapper *d; + unsigned long flags; + int plug_in; + + d = gadget_wrapper; + + spin_lock_irqsave(&d->lock, flags); + plug_in = d->vbus; + spin_unlock_irqrestore(&d->lock, flags); + + mutex_lock(&udc_lock); + if (plug_in){ + pr_info("usb detect plug in,vbus pull up\n"); + hotplug_callback(VBUS_PLUG_IN, 0); + if(get_usb_first_enable_store_flag()){ + queue_delayed_work(d->cable2pc_wq, &d->cable2pc,CABLE_TIMEOUT); + __udc_startup(); + } + } else { + pr_info("usb detect plug out,vbus pull down\n"); + cancel_delayed_work_sync(&d->cable2pc); + __udc_shutdown(); + hotplug_callback(VBUS_PLUG_OUT, cable_is_usb()); + } + mutex_unlock(&udc_lock); + switch_set_state(&d->sdev, !!plug_in); +} + +static irqreturn_t usb_detect_handler(int irq, void *dev_id) +{ + struct gadget_wrapper *d; + int value = 0; + +#ifndef DWC_DEVICE_ONLY + /* + *if otg cable is connected , id state =0 + *as host turn on vbus, in this case shouldn't call this handler + */ + if(!usb_get_id_state()) + return IRQ_HANDLED; +#endif + d = gadget_wrapper; + if (d->driver == NULL) { + pr_info("too early, no gadget drive\n"); + return IRQ_HANDLED; + } + + value = usb_get_vbus_state(); + if (value){ + pr_debug("usb detect plug in\n"); + usb_set_vbus_irq_type(irq, VBUS_PLUG_OUT); + } else { + pr_debug("usb detect plug out\n"); + usb_set_vbus_irq_type(irq, VBUS_PLUG_IN); + } + + d->vbus = value; + queue_work(d->detect_wq, &d->detect_work); + + return IRQ_HANDLED; +} + +int epmic_event_handler(int level) +{ + struct gadget_wrapper *d; + +#ifndef DWC_DEVICE_ONLY + /* + *if otg cable is connected , id state =0 + *as host turn on vbus, in this case shouldn't call this handler + */ + if(!usb_get_id_state()) + return 0; +#endif + d = gadget_wrapper; + d->vbus = level; + queue_work(d->detect_wq, &d->detect_work); + return 0; +} +EXPORT_SYMBOL(epmic_event_handler); + +static int cable_is_connected(void) +{ + if (usb_get_vbus_state()) + return 1; + else + return 0; +} + +/* + * this function will check the cable type, if it is + * ac adapter calbe, we disable udc for lowering the + * power. + */ +#define REENUM_CNT 1 +static void cable2pc_detect_works(struct work_struct *work) +{ + int usb_cable; + static int reenum_cnt = REENUM_CNT; + struct gadget_wrapper *d; + d = gadget_wrapper; + usb_cable = cable_is_usb(); + + /* in factory mode, we know the cable must be usb, so we enumerate + * again + */ + if (in_factory_mode() && !usb_cable && reenum_cnt){ + pr_info("try usb enumertation again\n"); + reenum_cnt--; + mutex_lock(&udc_lock); + __udc_shutdown(); + queue_delayed_work(d->cable2pc_wq, &d->cable2pc,CABLE_TIMEOUT); + __udc_startup(); + mutex_unlock(&udc_lock); + return; + } + + mutex_lock(&udc_lock); + if (!usb_cable) { + pr_info("cable is ac adapter\n"); + __udc_shutdown(); + } + mutex_unlock(&udc_lock); + return; +} + +static struct device *device_dwc_otg; +/** + * This function initialized the PCD portion of the driver. + * + */ +int pcd_init( + struct platform_device *_dev + ) +{ + dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev); + struct sprd_usb_platform_data *pdata= _dev->dev.platform_data; + int retval = 0; + int irq; + int plug_irq; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _dev); + + device_dwc_otg = &(_dev->dev); + if (dwc_otg_is_dma_enable(otg_dev->core_if)) { + _dev->dev.dma_mask = &dwc_otg_pcd_dmamask; + _dev->dev.coherent_dma_mask = dwc_otg_pcd_dmamask; + } else { + _dev->dev.dma_mask = (void *)0; + _dev->dev.coherent_dma_mask = 0; + } + + wake_lock_init(&usb_wake_lock, WAKE_LOCK_SUSPEND, "usb_work"); +// wake_lock(&usb_wake_lock); + otg_dev->pcd = dwc_otg_pcd_init(otg_dev->core_if); + + if (!otg_dev->pcd) { + DWC_ERROR("dwc_otg_pcd_init failed\n"); + return -ENOMEM; + } + + gadget_wrapper = alloc_wrapper(_dev); + + /* + * Initialize EP structures + */ + gadget_add_eps(gadget_wrapper); + + /* + * Setup interupt handler + */ + irq = platform_get_irq(_dev, 0); + DWC_DEBUGPL(DBG_ANY, "registering handler for irq%d\n", irq); + retval = request_irq(irq, dwc_otg_pcd_irq, + IRQF_SHARED, gadget_wrapper->gadget.name, + otg_dev->pcd); + //SA_SHIRQ, gadget_wrapper->gadget.name, + if (retval != 0) { + DWC_ERROR("request of irq%d failed\n", irq); + free_wrapper(gadget_wrapper); + return -EBUSY; + } + /* + * initialize a timer for checking cable type. + */ + setup_timer(&setup_transfer_timer, + setup_transfer_timer_fun, + (unsigned long)gadget_wrapper); + setup_transfer_timer_start = 0; + setup_timer(&reinit_usb_timer, reinit_usb_timer_fun, + (unsigned long) gadget_wrapper); + + INIT_DELAYED_WORK(&gadget_wrapper->cable2pc, cable2pc_detect_works); + gadget_wrapper->cable2pc_wq = + create_singlethread_workqueue("USB2pcWq"); + /* + * setup usb cable detect interupt + */ +#ifndef CONFIG_MFD_SM5504 + { + plug_irq = usb_alloc_vbus_irq(pdata->gpio_chgdet); + if (plug_irq < 0) { + pr_warning("cannot alloc vbus irq\n"); + return -EBUSY; + } + usb_set_vbus_irq_type(plug_irq, VBUS_PLUG_IN); + #ifdef CONFIG_SC_FPGA + gadget_wrapper->vbus = 1; + #else + gadget_wrapper->vbus = usb_get_vbus_state(); + #endif + pr_info("now usb vbus is :%d\n", gadget_wrapper->vbus); + retval = request_irq(plug_irq, usb_detect_handler, IRQF_SHARED | IRQF_NO_SUSPEND, + "usb detect", otg_dev->pcd); +#ifndef CONFIG_MUIC_CABLE_DETECT + disable_irq(plug_irq); +#endif + } + //gadget_wrapper->vbus = 1;//used when debug in FPGA, which doesn't have vbus operation +#endif + spin_lock_init(&gadget_wrapper->lock); +#ifdef CONFIG_SC_FPGA + gadget_wrapper->vbus = 1; +#endif + INIT_WORK(&gadget_wrapper->detect_work, usb_detect_works); + gadget_wrapper->detect_wq = + create_singlethread_workqueue("USBDetectWq"); + INIT_WORK(&gadget_wrapper->reinit_work, usb_reinit_works); + gadget_wrapper->reinit_wq = + create_singlethread_workqueue("USBReinitWq"); + /* + * register a switch device for sending pnp message, + * for the user app need be notified immediately + * when plug in & plug out happen; + */ + gadget_wrapper->sdev.name = "charger_cable"; + retval = switch_dev_register(&gadget_wrapper->sdev); + if (retval){ + pr_warning("register switch dev error:%s\n", __func__); + } + + dwc_otg_pcd_start(gadget_wrapper->pcd, &fops); + + /* + * dwc driver is ok, check if the cable is insert, if no, + * shutdown udc for saving power. + */ + if (!gadget_wrapper->vbus){ + pr_debug("vbus is not power now \n"); + gadget_wrapper->udc_startup = 1; + __udc_shutdown(); + } + gadget_wrapper->udc_startup = gadget_wrapper->vbus; + gadget_wrapper->enabled = 0; + + retval = usb_add_gadget_udc(&_dev->dev, &gadget_wrapper->gadget); + if (!retval) + return retval; + + return retval; +} + +/** + * Cleanup the PCD. + */ +void pcd_remove( +struct platform_device *_dev + ) +{ + dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev); + struct sprd_usb_platform_data *pdata= _dev->dev.platform_data; + dwc_otg_pcd_t *pcd = otg_dev->pcd; + int plug_irq; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _dev); + + usb_del_gadget_udc(&gadget_wrapper->gadget); + + /* + * Free the IRQ + */ + free_irq(platform_get_irq(_dev, 0), pcd); + plug_irq = usb_get_vbus_irq(); + usb_free_vbus_irq(plug_irq,pdata->gpio_chgdet); + dwc_otg_pcd_remove(pcd); + destroy_workqueue(gadget_wrapper->detect_wq); + destroy_workqueue(gadget_wrapper->cable2pc_wq); + destroy_workqueue(gadget_wrapper->reinit_wq); + wake_lock_destroy(&usb_wake_lock); + switch_dev_unregister(&gadget_wrapper->sdev); + free_wrapper(gadget_wrapper); + pcd = 0; +} + +/** + * This function registers a gadget driver with the PCD. + * + * When a driver is successfully registered, it will receive control + * requests including set_configuration(), which enables non-control + * requests. then usb traffic follows until a disconnect is reported. + * then a host may connect again, or the driver might get unbound. + * + * @param driver The driver being registered + */ +static int dwc_usb_gadget_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver) +{ + int retval; + + DWC_DEBUGPL(DBG_PCD, "registering gadget driver '%s'\n", + driver->driver.name); + pr_info("%s\n", __func__); + if (gadget_wrapper == 0) { + DWC_DEBUGPL(DBG_PCDV, "ENODEV\n"); + return -ENODEV; + } + if (gadget_wrapper->driver != 0) { + DWC_DEBUGPL(DBG_PCDV, "EBUSY (%p)\n", gadget_wrapper->driver); + return -EBUSY; + } + + /* hook up the driver */ + gadget_wrapper->driver = driver; + gadget_wrapper->gadget.dev.driver = &driver->driver; + + gadget_wrapper->enabled = 1; + DWC_DEBUGPL(DBG_ANY, "registered gadget driver '%s'\n", + driver->driver.name); + + return 0; +} + +/** + * This function unregisters a gadget driver + * + * @param driver The driver being unregistered + */ +static int dwc_usb_gadget_stop(struct usb_gadget *gadget, struct usb_gadget_driver *driver) +{ + //DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n", __func__, _driver); + + if (gadget_wrapper == 0) { + DWC_DEBUGPL(DBG_ANY, "%s Return(%d): s_pcd==0\n", __func__, + -ENODEV); + return -ENODEV; + } + if (driver == 0 || driver != gadget_wrapper->driver) { + DWC_DEBUGPL(DBG_ANY, "%s Return(%d): driver?\n", __func__, + -EINVAL); + return -EINVAL; + } + + gadget_wrapper->driver = 0; + gadget_wrapper->enabled = 0; + + DWC_DEBUGPL(DBG_ANY, "unregistered driver '%s'\n", driver->driver.name); + return 0; +} + +int usb_register_hotplug_callback(struct usb_hotplug_callback *cb) +{ + int ret = 0; + int plug_irq = usb_get_vbus_irq(); + if (cb){ + hotplug_cb = cb; + enable_irq(plug_irq); + } else { + pr_warning("%s, error\n", __func__); + ret = -EINVAL; + } + return ret; +} +EXPORT_SYMBOL(usb_register_hotplug_callback); + +struct device *get_gadget_wrapper_device() +{ +#ifdef CONFIG_ARM64 +if(NULL != device_dwc_otg) + { + return device_dwc_otg; + } +else + { + gadget_wrapper->gadget.dev.dma_mask = &dwc_otg_pcd_dmamask; + gadget_wrapper->gadget.dev.coherent_dma_mask = dwc_otg_pcd_dmamask; + return &(gadget_wrapper->gadget.dev); + } +#else + return NULL; +#endif +} + +EXPORT_SYMBOL(get_gadget_wrapper_device); + + +#endif /* DWC_HOST_ONLY */ + diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_regs.h b/drivers/usb/gadget/dwc_otg/dwc_otg_regs.h new file mode 100644 index 00000000..105ba1ba --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_regs.h @@ -0,0 +1,2550 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_regs.h $ + * $Revision: #98 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#ifndef __DWC_OTG_REGS_H__ +#define __DWC_OTG_REGS_H__ + +#include "dwc_otg_core_if.h" + +/** + * @file + * + * This file contains the data structures for accessing the DWC_otg core registers. + * + * The application interfaces with the HS OTG core by reading from and + * writing to the Control and Status Register (CSR) space through the + * AHB Slave interface. These registers are 32 bits wide, and the + * addresses are 32-bit-block aligned. + * CSRs are classified as follows: + * - Core Global Registers + * - Device Mode Registers + * - Device Global Registers + * - Device Endpoint Specific Registers + * - Host Mode Registers + * - Host Global Registers + * - Host Port CSRs + * - Host Channel Specific Registers + * + * Only the Core Global registers can be accessed in both Device and + * Host modes. When the HS OTG core is operating in one mode, either + * Device or Host, the application must not access registers from the + * other mode. When the core switches from one mode to another, the + * registers in the new mode of operation must be reprogrammed as they + * would be after a power-on reset. + */ + +/****************************************************************************/ +/** DWC_otg Core registers . + * The dwc_otg_core_global_regs structure defines the size + * and relative field offsets for the Core Global registers. + */ +typedef struct dwc_otg_core_global_regs { + /** OTG Control and Status Register. Offset: 000h */ + volatile uint32_t gotgctl; + /** OTG Interrupt Register. Offset: 004h */ + volatile uint32_t gotgint; + /**Core AHB Configuration Register. Offset: 008h */ + volatile uint32_t gahbcfg; + +#define DWC_GLBINTRMASK 0x0001 +#define DWC_DMAENABLE 0x0020 +#define DWC_NPTXEMPTYLVL_EMPTY 0x0080 +#define DWC_NPTXEMPTYLVL_HALFEMPTY 0x0000 +#define DWC_PTXEMPTYLVL_EMPTY 0x0100 +#define DWC_PTXEMPTYLVL_HALFEMPTY 0x0000 + + /**Core USB Configuration Register. Offset: 00Ch */ + volatile uint32_t gusbcfg; + /**Core Reset Register. Offset: 010h */ + volatile uint32_t grstctl; + /**Core Interrupt Register. Offset: 014h */ + volatile uint32_t gintsts; + /**Core Interrupt Mask Register. Offset: 018h */ + volatile uint32_t gintmsk; + /**Receive Status Queue Read Register (Read Only). Offset: 01Ch */ + volatile uint32_t grxstsr; + /**Receive Status Queue Read & POP Register (Read Only). Offset: 020h*/ + volatile uint32_t grxstsp; + /**Receive FIFO Size Register. Offset: 024h */ + volatile uint32_t grxfsiz; + /**Non Periodic Transmit FIFO Size Register. Offset: 028h */ + volatile uint32_t gnptxfsiz; + /**Non Periodic Transmit FIFO/Queue Status Register (Read + * Only). Offset: 02Ch */ + volatile uint32_t gnptxsts; + /**I2C Access Register. Offset: 030h */ + volatile uint32_t gi2cctl; + /**PHY Vendor Control Register. Offset: 034h */ + volatile uint32_t gpvndctl; + /**General Purpose Input/Output Register. Offset: 038h */ + volatile uint32_t ggpio; + /**User ID Register. Offset: 03Ch */ + volatile uint32_t guid; + /**Synopsys ID Register (Read Only). Offset: 040h */ + volatile uint32_t gsnpsid; + /**User HW Config1 Register (Read Only). Offset: 044h */ + volatile uint32_t ghwcfg1; + /**User HW Config2 Register (Read Only). Offset: 048h */ + volatile uint32_t ghwcfg2; +#define DWC_SLAVE_ONLY_ARCH 0 +#define DWC_EXT_DMA_ARCH 1 +#define DWC_INT_DMA_ARCH 2 + +#define DWC_MODE_HNP_SRP_CAPABLE 0 +#define DWC_MODE_SRP_ONLY_CAPABLE 1 +#define DWC_MODE_NO_HNP_SRP_CAPABLE 2 +#define DWC_MODE_SRP_CAPABLE_DEVICE 3 +#define DWC_MODE_NO_SRP_CAPABLE_DEVICE 4 +#define DWC_MODE_SRP_CAPABLE_HOST 5 +#define DWC_MODE_NO_SRP_CAPABLE_HOST 6 + + /**User HW Config3 Register (Read Only). Offset: 04Ch */ + volatile uint32_t ghwcfg3; + /**User HW Config4 Register (Read Only). Offset: 050h*/ + volatile uint32_t ghwcfg4; + /** Core LPM Configuration register Offset: 054h*/ + volatile uint32_t glpmcfg; + /** Global PowerDn Register Offset: 058h */ + volatile uint32_t gpwrdn; + /** Global DFIFO SW Config Register Offset: 05Ch */ + volatile uint32_t gdfifocfg; + /** ADP Control Register Offset: 060h */ + volatile uint32_t adpctl; + /** Reserved Offset: 064h-0FFh */ + volatile uint32_t reserved39[39]; + /** Host Periodic Transmit FIFO Size Register. Offset: 100h */ + volatile uint32_t hptxfsiz; + /** Device Periodic Transmit FIFO#n Register if dedicated fifos are disabled, + otherwise Device Transmit FIFO#n Register. + * Offset: 104h + (FIFO_Number-1)*04h, 1 <= FIFO Number <= 15 (1<=n<=15). */ + volatile uint32_t dtxfsiz[15]; +} dwc_otg_core_global_regs_t; + +/** + * This union represents the bit fields of the Core OTG Control + * and Status Register (GOTGCTL). Set the bits using the bit + * fields then write the d32 value to the register. + */ +typedef union gotgctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned sesreqscs:1; + unsigned sesreq:1; + unsigned vbvalidoven:1; + unsigned vbvalidovval:1; + unsigned avalidoven:1; + unsigned avalidovval:1; + unsigned bvalidoven:1; + unsigned bvalidovval:1; + unsigned hstnegscs:1; + unsigned hnpreq:1; + unsigned hstsethnpen:1; + unsigned devhnpen:1; + unsigned reserved12_15:4; + unsigned conidsts:1; + unsigned dbnctime:1; + unsigned asesvld:1; + unsigned bsesvld:1; + unsigned otgver:1; + unsigned reserved1:1; + unsigned multvalidbc:5; + unsigned chirpen:1; + unsigned reserved28_31:4; + } b; +} gotgctl_data_t; + +/** + * This union represents the bit fields of the Core OTG Interrupt Register + * (GOTGINT). Set/clear the bits using the bit fields then write the d32 + * value to the register. + */ +typedef union gotgint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Current Mode */ + unsigned reserved0_1:2; + + /** Session End Detected */ + unsigned sesenddet:1; + + unsigned reserved3_7:5; + + /** Session Request Success Status Change */ + unsigned sesreqsucstschng:1; + /** Host Negotiation Success Status Change */ + unsigned hstnegsucstschng:1; + + unsigned reserved10_16:7; + + /** Host Negotiation Detected */ + unsigned hstnegdet:1; + /** A-Device Timeout Change */ + unsigned adevtoutchng:1; + /** Debounce Done */ + unsigned debdone:1; + /** Multi-Valued input changed */ + unsigned mvic:1; + + unsigned reserved31_21:11; + + } b; +} gotgint_data_t; + +/** + * This union represents the bit fields of the Core AHB Configuration + * Register (GAHBCFG). Set/clear the bits using the bit fields then + * write the d32 value to the register. + */ +typedef union gahbcfg_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned glblintrmsk:1; +#define DWC_GAHBCFG_GLBINT_ENABLE 1 + + unsigned hburstlen:4; +#define DWC_GAHBCFG_INT_DMA_BURST_SINGLE 0 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR 1 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR4 3 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR8 5 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR16 7 + + unsigned dmaenable:1; +#define DWC_GAHBCFG_DMAENABLE 1 + unsigned reserved:1; + unsigned nptxfemplvl_txfemplvl:1; + unsigned ptxfemplvl:1; +#define DWC_GAHBCFG_TXFEMPTYLVL_EMPTY 1 +#define DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0 + unsigned reserved9_20:12; + unsigned remmemsupp:1; + unsigned notialldmawrit:1; + unsigned ahbsingle:1; + unsigned reserved24_31:8; + } b; +} gahbcfg_data_t; + +/** + * This union represents the bit fields of the Core USB Configuration + * Register (GUSBCFG). Set the bits using the bit fields then write + * the d32 value to the register. + */ +typedef union gusbcfg_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned toutcal:3; + unsigned phyif:1; + unsigned ulpi_utmi_sel:1; + unsigned fsintf:1; + unsigned physel:1; + unsigned ddrsel:1; + unsigned srpcap:1; + unsigned hnpcap:1; + unsigned usbtrdtim:4; + unsigned reserved1:1; + unsigned phylpwrclksel:1; + unsigned otgutmifssel:1; + unsigned ulpi_fsls:1; + unsigned ulpi_auto_res:1; + unsigned ulpi_clk_sus_m:1; + unsigned ulpi_ext_vbus_drv:1; + unsigned ulpi_int_vbus_indicator:1; + unsigned term_sel_dl_pulse:1; + unsigned indicator_complement:1; + unsigned indicator_pass_through:1; + unsigned ulpi_int_prot_dis:1; + unsigned ic_usb_cap:1; + unsigned ic_traffic_pull_remove:1; + unsigned tx_end_delay:1; + unsigned force_host_mode:1; + unsigned force_dev_mode:1; + unsigned reserved31:1; + } b; +} gusbcfg_data_t; + +/** + * This union represents the bit fields of the Core Reset Register + * (GRSTCTL). Set/clear the bits using the bit fields then write the + * d32 value to the register. + */ +typedef union grstctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Core Soft Reset (CSftRst) (Device and Host) + * + * The application can flush the control logic in the + * entire core using this bit. This bit resets the + * pipelines in the AHB Clock domain as well as the + * PHY Clock domain. + * + * The state machines are reset to an IDLE state, the + * control bits in the CSRs are cleared, all the + * transmit FIFOs and the receive FIFO are flushed. + * + * The status mask bits that control the generation of + * the interrupt, are cleared, to clear the + * interrupt. The interrupt status bits are not + * cleared, so the application can get the status of + * any events that occurred in the core after it has + * set this bit. + * + * Any transactions on the AHB are terminated as soon + * as possible following the protocol. Any + * transactions on the USB are terminated immediately. + * + * The configuration settings in the CSRs are + * unchanged, so the software doesn't have to + * reprogram these registers (Device + * Configuration/Host Configuration/Core System + * Configuration/Core PHY Configuration). + * + * The application can write to this bit, any time it + * wants to reset the core. This is a self clearing + * bit and the core clears this bit after all the + * necessary logic is reset in the core, which may + * take several clocks, depending on the current state + * of the core. + */ + unsigned csftrst:1; + /** Hclk Soft Reset + * + * The application uses this bit to reset the control logic in + * the AHB clock domain. Only AHB clock domain pipelines are + * reset. + */ + unsigned hsftrst:1; + /** Host Frame Counter Reset (Host Only)
+ * + * The application can reset the (micro)frame number + * counter inside the core, using this bit. When the + * (micro)frame counter is reset, the subsequent SOF + * sent out by the core, will have a (micro)frame + * number of 0. + */ + unsigned hstfrm:1; + /** In Token Sequence Learning Queue Flush + * (INTknQFlsh) (Device Only) + */ + unsigned intknqflsh:1; + /** RxFIFO Flush (RxFFlsh) (Device and Host) + * + * The application can flush the entire Receive FIFO + * using this bit. The application must first + * ensure that the core is not in the middle of a + * transaction. The application should write into + * this bit, only after making sure that neither the + * DMA engine is reading from the RxFIFO nor the MAC + * is writing the data in to the FIFO. The + * application should wait until the bit is cleared + * before performing any other operations. This bit + * will takes 8 clocks (slowest of PHY or AHB clock) + * to clear. + */ + unsigned rxfflsh:1; + /** TxFIFO Flush (TxFFlsh) (Device and Host). + * + * This bit is used to selectively flush a single or + * all transmit FIFOs. The application must first + * ensure that the core is not in the middle of a + * transaction. The application should write into + * this bit, only after making sure that neither the + * DMA engine is writing into the TxFIFO nor the MAC + * is reading the data out of the FIFO. The + * application should wait until the core clears this + * bit, before performing any operations. This bit + * will takes 8 clocks (slowest of PHY or AHB clock) + * to clear. + */ + unsigned txfflsh:1; + + /** TxFIFO Number (TxFNum) (Device and Host). + * + * This is the FIFO number which needs to be flushed, + * using the TxFIFO Flush bit. This field should not + * be changed until the TxFIFO Flush bit is cleared by + * the core. + * - 0x0 : Non Periodic TxFIFO Flush + * - 0x1 : Periodic TxFIFO #1 Flush in device mode + * or Periodic TxFIFO in host mode + * - 0x2 : Periodic TxFIFO #2 Flush in device mode. + * - ... + * - 0xF : Periodic TxFIFO #15 Flush in device mode + * - 0x10: Flush all the Transmit NonPeriodic and + * Transmit Periodic FIFOs in the core + */ + unsigned txfnum:5; + /** Reserved */ + unsigned reserved11_29:19; + /** DMA Request Signal. Indicated DMA request is in + * probress. Used for debug purpose. */ + unsigned dmareq:1; + /** AHB Master Idle. Indicates the AHB Master State + * Machine is in IDLE condition. */ + unsigned ahbidle:1; + } b; +} grstctl_t; + +/** + * This union represents the bit fields of the Core Interrupt Mask + * Register (GINTMSK). Set/clear the bits using the bit fields then + * write the d32 value to the register. + */ +typedef union gintmsk_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned reserved0:1; + unsigned modemismatch:1; + unsigned otgintr:1; + unsigned sofintr:1; + unsigned rxstsqlvl:1; + unsigned nptxfempty:1; + unsigned ginnakeff:1; + unsigned goutnakeff:1; + unsigned ulpickint:1; + unsigned i2cintr:1; + unsigned erlysuspend:1; + unsigned usbsuspend:1; + unsigned usbreset:1; + unsigned enumdone:1; + unsigned isooutdrop:1; + unsigned eopframe:1; + unsigned restoredone:1; + unsigned epmismatch:1; + unsigned inepintr:1; + unsigned outepintr:1; + unsigned incomplisoin:1; + unsigned incomplisoout:1; + unsigned fetsusp:1; + unsigned resetdet:1; + unsigned portintr:1; + unsigned hcintr:1; + unsigned ptxfempty:1; + unsigned lpmtranrcvd:1; + unsigned conidstschng:1; + unsigned disconnect:1; + unsigned sessreqintr:1; + unsigned wkupintr:1; + } b; +} gintmsk_data_t; +/** + * This union represents the bit fields of the Core Interrupt Register + * (GINTSTS). Set/clear the bits using the bit fields then write the + * d32 value to the register. + */ +typedef union gintsts_data { + /** raw register data */ + uint32_t d32; +#define DWC_SOF_INTR_MASK 0x0008 + /** register bits */ + struct { +#define DWC_HOST_MODE 1 + unsigned curmode:1; + unsigned modemismatch:1; + unsigned otgintr:1; + unsigned sofintr:1; + unsigned rxstsqlvl:1; + unsigned nptxfempty:1; + unsigned ginnakeff:1; + unsigned goutnakeff:1; + unsigned ulpickint:1; + unsigned i2cintr:1; + unsigned erlysuspend:1; + unsigned usbsuspend:1; + unsigned usbreset:1; + unsigned enumdone:1; + unsigned isooutdrop:1; + unsigned eopframe:1; + unsigned restoredone:1; + unsigned epmismatch:1; + unsigned inepint:1; + unsigned outepintr:1; + unsigned incomplisoin:1; + unsigned incomplisoout:1; + unsigned fetsusp:1; + unsigned resetdet:1; + unsigned portintr:1; + unsigned hcintr:1; + unsigned ptxfempty:1; + unsigned lpmtranrcvd:1; + unsigned conidstschng:1; + unsigned disconnect:1; + unsigned sessreqintr:1; + unsigned wkupintr:1; + } b; +} gintsts_data_t; + +/** + * This union represents the bit fields in the Device Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the d32 + * element then read out the bits using the bit elements. + */ +typedef union device_grxsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned epnum:4; + unsigned bcnt:11; + unsigned dpid:2; + +#define DWC_STS_DATA_UPDT 0x2 // OUT Data Packet +#define DWC_STS_XFER_COMP 0x3 // OUT Data Transfer Complete + +#define DWC_DSTS_GOUT_NAK 0x1 // Global OUT NAK +#define DWC_DSTS_SETUP_COMP 0x4 // Setup Phase Complete +#define DWC_DSTS_SETUP_UPDT 0x6 // SETUP Packet + unsigned pktsts:4; + unsigned fn:4; + unsigned reserved25_31:7; + } b; +} device_grxsts_data_t; + +/** + * This union represents the bit fields in the Host Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the d32 + * element then read out the bits using the bit elements. + */ +typedef union host_grxsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned chnum:4; + unsigned bcnt:11; + unsigned dpid:2; + + unsigned pktsts:4; +#define DWC_GRXSTS_PKTSTS_IN 0x2 +#define DWC_GRXSTS_PKTSTS_IN_XFER_COMP 0x3 +#define DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR 0x5 +#define DWC_GRXSTS_PKTSTS_CH_HALTED 0x7 + + unsigned reserved21_31:11; + } b; +} host_grxsts_data_t; + +/** + * This union represents the bit fields in the FIFO Size Registers (HPTXFSIZ, + * GNPTXFSIZ, DPTXFSIZn, DIEPTXFn). Read the register into the d32 element + * then read out the bits using the bit elements. + */ +typedef union fifosize_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned startaddr:16; + unsigned depth:16; + } b; +} fifosize_data_t; + +/** + * This union represents the bit fields in the Non-Periodic Transmit + * FIFO/Queue Status Register (GNPTXSTS). Read the register into the + * d32 element then read out the bits using the bit + * elements. + */ +typedef union gnptxsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned nptxfspcavail:16; + unsigned nptxqspcavail:8; + /** Top of the Non-Periodic Transmit Request Queue + * - bit 24 - Terminate (Last entry for the selected + * channel/EP) + * - bits 26:25 - Token Type + * - 2'b00 - IN/OUT + * - 2'b01 - Zero Length OUT + * - 2'b10 - PING/Complete Split + * - 2'b11 - Channel Halt + * - bits 30:27 - Channel/EP Number + */ + unsigned nptxqtop_terminate:1; + unsigned nptxqtop_token:2; + unsigned nptxqtop_chnep:4; + unsigned reserved:1; + } b; +} gnptxsts_data_t; + +/** + * This union represents the bit fields in the Transmit + * FIFO Status Register (DTXFSTS). Read the register into the + * d32 element then read out the bits using the bit + * elements. + */ +typedef union dtxfsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned txfspcavail:16; + unsigned reserved:16; + } b; +} dtxfsts_data_t; + +/** + * This union represents the bit fields in the I2C Control Register + * (I2CCTL). Read the register into the d32 element then read out the + * bits using the bit elements. + */ +typedef union gi2cctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned rwdata:8; + unsigned regaddr:8; + unsigned addr:7; + unsigned i2cen:1; + unsigned ack:1; + unsigned i2csuspctl:1; + unsigned i2cdevaddr:2; + unsigned i2cdatse0:1; + unsigned reserved:1; + unsigned rw:1; + unsigned bsydne:1; + } b; +} gi2cctl_data_t; + +/** + * This union represents the bit fields in the PHY Vendor Control Register + * (GPVNDCTL). Read the register into the d32 element then read out the + * bits using the bit elements. + */ +typedef union gpvndctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned regdata:8; + unsigned vctrl:8; + unsigned regaddr16_21:6; + unsigned regwr:1; + unsigned reserved23_24:2; + unsigned newregreq:1; + unsigned vstsbsy:1; + unsigned vstsdone:1; + unsigned reserved28_30:3; + unsigned disulpidrvr:1; + } b; +} gpvndctl_data_t; + +/** + * This union represents the bit fields in the General Purpose + * Input/Output Register (GGPIO). + * Read the register into the d32 element then read out the + * bits using the bit elements. + */ +typedef union ggpio_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned gpi:16; + unsigned gpo:16; + } b; +} ggpio_data_t; + +/** + * This union represents the bit fields in the User ID Register + * (GUID). Read the register into the d32 element then read out the + * bits using the bit elements. + */ +typedef union guid_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned rwdata:32; + } b; +} guid_data_t; + +/** + * This union represents the bit fields in the Synopsys ID Register + * (GSNPSID). Read the register into the d32 element then read out the + * bits using the bit elements. + */ +typedef union gsnpsid_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned rwdata:32; + } b; +} gsnpsid_data_t; + +/** + * This union represents the bit fields in the User HW Config1 + * Register. Read the register into the d32 element then read + * out the bits using the bit elements. + */ +typedef union hwcfg1_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned ep_dir0:2; + unsigned ep_dir1:2; + unsigned ep_dir2:2; + unsigned ep_dir3:2; + unsigned ep_dir4:2; + unsigned ep_dir5:2; + unsigned ep_dir6:2; + unsigned ep_dir7:2; + unsigned ep_dir8:2; + unsigned ep_dir9:2; + unsigned ep_dir10:2; + unsigned ep_dir11:2; + unsigned ep_dir12:2; + unsigned ep_dir13:2; + unsigned ep_dir14:2; + unsigned ep_dir15:2; + } b; +} hwcfg1_data_t; + +/** + * This union represents the bit fields in the User HW Config2 + * Register. Read the register into the d32 element then read + * out the bits using the bit elements. + */ +typedef union hwcfg2_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /* GHWCFG2 */ + unsigned op_mode:3; +#define DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG 0 +#define DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG 1 +#define DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG 2 +#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE 3 +#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE 4 +#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST 5 +#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST 6 + + unsigned architecture:2; + unsigned point2point:1; + unsigned hs_phy_type:2; +#define DWC_HWCFG2_HS_PHY_TYPE_NOT_SUPPORTED 0 +#define DWC_HWCFG2_HS_PHY_TYPE_UTMI 1 +#define DWC_HWCFG2_HS_PHY_TYPE_ULPI 2 +#define DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI 3 + + unsigned fs_phy_type:2; + unsigned num_dev_ep:4; + unsigned num_host_chan:4; + unsigned perio_ep_supported:1; + unsigned dynamic_fifo:1; + unsigned multi_proc_int:1; + unsigned reserved21:1; + unsigned nonperio_tx_q_depth:2; + unsigned host_perio_tx_q_depth:2; + unsigned dev_token_q_depth:5; + unsigned otg_enable_ic_usb:1; + } b; +} hwcfg2_data_t; + +/** + * This union represents the bit fields in the User HW Config3 + * Register. Read the register into the d32 element then read + * out the bits using the bit elements. + */ +typedef union hwcfg3_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /* GHWCFG3 */ + unsigned xfer_size_cntr_width:4; + unsigned packet_size_cntr_width:3; + unsigned otg_func:1; + unsigned i2c:1; + unsigned vendor_ctrl_if:1; + unsigned optional_features:1; + unsigned synch_reset_type:1; + unsigned adp_supp:1; + unsigned otg_enable_hsic:1; + unsigned bc_support:1; + unsigned otg_lpm_en:1; + unsigned dfifo_depth:16; + } b; +} hwcfg3_data_t; + +/** + * This union represents the bit fields in the User HW Config4 + * Register. Read the register into the d32 element then read + * out the bits using the bit elements. + */ +typedef union hwcfg4_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned num_dev_perio_in_ep:4; + unsigned power_optimiz:1; + unsigned min_ahb_freq:1; + unsigned hiber:1; + unsigned xhiber:1; + unsigned reserved:6; + unsigned utmi_phy_data_width:2; + unsigned num_dev_mode_ctrl_ep:4; + unsigned iddig_filt_en:1; + unsigned vbus_valid_filt_en:1; + unsigned a_valid_filt_en:1; + unsigned b_valid_filt_en:1; + unsigned session_end_filt_en:1; + unsigned ded_fifo_en:1; + unsigned num_in_eps:4; + unsigned desc_dma:1; + unsigned desc_dma_dyn:1; + } b; +} hwcfg4_data_t; + +/** + * This union represents the bit fields of the Core LPM Configuration + * Register (GLPMCFG). Set the bits using bit fields then write + * the d32 value to the register. + */ +typedef union glpmctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** LPM-Capable (LPMCap) (Device and Host) + * The application uses this bit to control + * the DWC_otg core LPM capabilities. + */ + unsigned lpm_cap_en:1; + /** LPM response programmed by application (AppL1Res) (Device) + * Handshake response to LPM token pre-programmed + * by device application software. + */ + unsigned appl_resp:1; + /** Host Initiated Resume Duration (HIRD) (Device and Host) + * In Host mode this field indicates the value of HIRD + * to be sent in an LPM transaction. + * In Device mode this field is updated with the + * Received LPM Token HIRD bmAttribute + * when an ACK/NYET/STALL response is sent + * to an LPM transaction. + */ + unsigned hird:4; + /** RemoteWakeEnable (bRemoteWake) (Device and Host) + * In Host mode this bit indicates the value of remote + * wake up to be sent in wIndex field of LPM transaction. + * In Device mode this field is updated with the + * Received LPM Token bRemoteWake bmAttribute + * when an ACK/NYET/STALL response is sent + * to an LPM transaction. + */ + unsigned rem_wkup_en:1; + /** Enable utmi_sleep_n (EnblSlpM) (Device and Host) + * The application uses this bit to control + * the utmi_sleep_n assertion to the PHY when in L1 state. + */ + unsigned en_utmi_sleep:1; + /** HIRD Threshold (HIRD_Thres) (Device and Host) + */ + unsigned hird_thres:5; + /** LPM Response (CoreL1Res) (Device and Host) + * In Host mode this bit contains handsake response to + * LPM transaction. + * In Device mode the response of the core to + * LPM transaction received is reflected in these two bits. + - 0x0 : ERROR (No handshake response) + - 0x1 : STALL + - 0x2 : NYET + - 0x3 : ACK + */ + unsigned lpm_resp:2; + /** Port Sleep Status (SlpSts) (Device and Host) + * This bit is set as long as a Sleep condition + * is present on the USB bus. + */ + unsigned prt_sleep_sts:1; + /** Sleep State Resume OK (L1ResumeOK) (Device and Host) + * Indicates that the application or host + * can start resume from Sleep state. + */ + unsigned sleep_state_resumeok:1; + /** LPM channel Index (LPM_Chnl_Indx) (Host) + * The channel number on which the LPM transaction + * has to be applied while sending + * an LPM transaction to the local device. + */ + unsigned lpm_chan_index:4; + /** LPM Retry Count (LPM_Retry_Cnt) (Host) + * Number host retries that would be performed + * if the device response was not valid response. + */ + unsigned retry_count:3; + /** Send LPM Transaction (SndLPM) (Host) + * When set by application software, + * an LPM transaction containing two tokens + * is sent. + */ + unsigned send_lpm:1; + /** LPM Retry status (LPM_RetryCnt_Sts) (Host) + * Number of LPM Host Retries still remaining + * to be transmitted for the current LPM sequence + */ + unsigned retry_count_sts:3; + unsigned reserved28_29:2; + /** In host mode once this bit is set, the host + * configures to drive the HSIC Idle state on the bus. + * It then waits for the device to initiate the Connect sequence. + * In device mode once this bit is set, the device waits for + * the HSIC Idle line state on the bus. Upon receving the Idle + * line state, it initiates the HSIC Connect sequence. + */ + unsigned hsic_connect:1; + /** This bit overrides and functionally inverts + * the if_select_hsic input port signal. + */ + unsigned inv_sel_hsic:1; + } b; +} glpmcfg_data_t; + +/** + * This union represents the bit fields of the Core ADP Timer, Control and + * Status Register (ADPTIMCTLSTS). Set the bits using bit fields then write + * the d32 value to the register. + */ +typedef union adpctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Probe Discharge (PRB_DSCHG) + * These bits set the times for TADP_DSCHG. + * These bits are defined as follows: + * 2'b00 - 4 msec + * 2'b01 - 8 msec + * 2'b10 - 16 msec + * 2'b11 - 32 msec + */ + unsigned prb_dschg:2; + /** Probe Delta (PRB_DELTA) + * These bits set the resolution for RTIM value. + * The bits are defined in units of 32 kHz clock cycles as follows: + * 2'b00 - 1 cycles + * 2'b01 - 2 cycles + * 2'b10 - 3 cycles + * 2'b11 - 4 cycles + * For example if this value is chosen to 2'b01, it means that RTIM + * increments for every 3(three) 32Khz clock cycles. + */ + unsigned prb_delta:2; + /** Probe Period (PRB_PER) + * These bits sets the TADP_PRD as shown in Figure 4 as follows: + * 2'b00 - 0.625 to 0.925 sec (typical 0.775 sec) + * 2'b01 - 1.25 to 1.85 sec (typical 1.55 sec) + * 2'b10 - 1.9 to 2.6 sec (typical 2.275 sec) + * 2'b11 - Reserved + */ + unsigned prb_per:2; + /** These bits capture the latest time it took for VBUS to ramp from + * VADP_SINK to VADP_PRB. + * 0x000 - 1 cycles + * 0x001 - 2 cycles + * 0x002 - 3 cycles + * etc + * 0x7FF - 2048 cycles + * A time of 1024 cycles at 32 kHz corresponds to a time of 32 msec. + */ + unsigned rtim:11; + /** Enable Probe (EnaPrb) + * When programmed to 1'b1, the core performs a probe operation. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned enaprb:1; + /** Enable Sense (EnaSns) + * When programmed to 1'b1, the core performs a Sense operation. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned enasns:1; + /** ADP Reset (ADPRes) + * When set, ADP controller is reset. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adpres:1; + /** ADP Enable (ADPEn) + * When set, the core performs either ADP probing or sensing + * based on EnaPrb or EnaSns. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adpen:1; + /** ADP Probe Interrupt (ADP_PRB_INT) + * When this bit is set, it means that the VBUS + * voltage is greater than VADP_PRB or VADP_PRB is reached. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_prb_int:1; + /** + * ADP Sense Interrupt (ADP_SNS_INT) + * When this bit is set, it means that the VBUS voltage is greater than + * VADP_SNS value or VADP_SNS is reached. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_sns_int:1; + /** ADP Tomeout Interrupt (ADP_TMOUT_INT) + * This bit is relevant only for an ADP probe. + * When this bit is set, it means that the ramp time has + * completed ie ADPCTL.RTIM has reached its terminal value + * of 0x7FF. This is a debug feature that allows software + * to read the ramp time after each cycle. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_tmout_int:1; + /** ADP Probe Interrupt Mask (ADP_PRB_INT_MSK) + * When this bit is set, it unmasks the interrupt due to ADP_PRB_INT. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_prb_int_msk:1; + /** ADP Sense Interrupt Mask (ADP_SNS_INT_MSK) + * When this bit is set, it unmasks the interrupt due to ADP_SNS_INT. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_sns_int_msk:1; + /** ADP Timoeout Interrupt Mask (ADP_TMOUT_MSK) + * When this bit is set, it unmasks the interrupt due to ADP_TMOUT_INT. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_tmout_int_msk:1; + /** Access Request + * 2'b00 - Read/Write Valid (updated by the core) + * 2'b01 - Read + * 2'b00 - Write + * 2'b00 - Reserved + */ + unsigned ar:2; + /** Reserved */ + unsigned reserved29_31:3; + } b; +} adpctl_data_t; + +//////////////////////////////////////////// +// Device Registers +/** + * Device Global Registers. Offsets 800h-BFFh + * + * The following structures define the size and relative field offsets + * for the Device Mode Registers. + * + * These registers are visible only in Device mode and must not be + * accessed in Host mode, as the results are unknown. + */ +typedef struct dwc_otg_dev_global_regs { + /** Device Configuration Register. Offset 800h */ + volatile uint32_t dcfg; + /** Device Control Register. Offset: 804h */ + volatile uint32_t dctl; + /** Device Status Register (Read Only). Offset: 808h */ + volatile uint32_t dsts; + /** Reserved. Offset: 80Ch */ + uint32_t unused; + /** Device IN Endpoint Common Interrupt Mask + * Register. Offset: 810h */ + volatile uint32_t diepmsk; + /** Device OUT Endpoint Common Interrupt Mask + * Register. Offset: 814h */ + volatile uint32_t doepmsk; + /** Device All Endpoints Interrupt Register. Offset: 818h */ + volatile uint32_t daint; + /** Device All Endpoints Interrupt Mask Register. Offset: + * 81Ch */ + volatile uint32_t daintmsk; + /** Device IN Token Queue Read Register-1 (Read Only). + * Offset: 820h */ + volatile uint32_t dtknqr1; + /** Device IN Token Queue Read Register-2 (Read Only). + * Offset: 824h */ + volatile uint32_t dtknqr2; + /** Device VBUS discharge Register. Offset: 828h */ + volatile uint32_t dvbusdis; + /** Device VBUS Pulse Register. Offset: 82Ch */ + volatile uint32_t dvbuspulse; + /** Device IN Token Queue Read Register-3 (Read Only). / + * Device Thresholding control register (Read/Write) + * Offset: 830h */ + volatile uint32_t dtknqr3_dthrctl; + /** Device IN Token Queue Read Register-4 (Read Only). / + * Device IN EPs empty Inr. Mask Register (Read/Write) + * Offset: 834h */ + volatile uint32_t dtknqr4_fifoemptymsk; + /** Device Each Endpoint Interrupt Register (Read Only). / + * Offset: 838h */ + volatile uint32_t deachint; + /** Device Each Endpoint Interrupt mask Register (Read/Write). / + * Offset: 83Ch */ + volatile uint32_t deachintmsk; + /** Device Each In Endpoint Interrupt mask Register (Read/Write). / + * Offset: 840h */ + volatile uint32_t diepeachintmsk[MAX_EPS_CHANNELS]; + /** Device Each Out Endpoint Interrupt mask Register (Read/Write). / + * Offset: 880h */ + volatile uint32_t doepeachintmsk[MAX_EPS_CHANNELS]; +} dwc_otg_device_global_regs_t; + +/** + * This union represents the bit fields in the Device Configuration + * Register. Read the register into the d32 member then + * set/clear the bits using the bit elements. Write the + * d32 member to the dcfg register. + */ +typedef union dcfg_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Device Speed */ + unsigned devspd:2; + /** Non Zero Length Status OUT Handshake */ + unsigned nzstsouthshk:1; +#define DWC_DCFG_SEND_STALL 1 + + unsigned ena32khzs:1; + /** Device Addresses */ + unsigned devaddr:7; + /** Periodic Frame Interval */ + unsigned perfrint:2; +#define DWC_DCFG_FRAME_INTERVAL_80 0 +#define DWC_DCFG_FRAME_INTERVAL_85 1 +#define DWC_DCFG_FRAME_INTERVAL_90 2 +#define DWC_DCFG_FRAME_INTERVAL_95 3 + + /** Enable Device OUT NAK for bulk in DDMA mode */ + unsigned endevoutnak:1; + + unsigned reserved14_17:4; + /** In Endpoint Mis-match count */ + unsigned epmscnt:5; + /** Enable Descriptor DMA in Device mode */ + unsigned descdma:1; + unsigned perschintvl:2; + unsigned resvalid:6; + } b; +} dcfg_data_t; + +/** + * This union represents the bit fields in the Device Control + * Register. Read the register into the d32 member then + * set/clear the bits using the bit elements. + */ +typedef union dctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Remote Wakeup */ + unsigned rmtwkupsig:1; + /** Soft Disconnect */ + unsigned sftdiscon:1; + /** Global Non-Periodic IN NAK Status */ + unsigned gnpinnaksts:1; + /** Global OUT NAK Status */ + unsigned goutnaksts:1; + /** Test Control */ + unsigned tstctl:3; + /** Set Global Non-Periodic IN NAK */ + unsigned sgnpinnak:1; + /** Clear Global Non-Periodic IN NAK */ + unsigned cgnpinnak:1; + /** Set Global OUT NAK */ + unsigned sgoutnak:1; + /** Clear Global OUT NAK */ + unsigned cgoutnak:1; + /** Power-On Programming Done */ + unsigned pwronprgdone:1; + /** Reserved */ + unsigned reserved:1; + /** Global Multi Count */ + unsigned gmc:2; + /** Ignore Frame Number for ISOC EPs */ + unsigned ifrmnum:1; + /** NAK on Babble */ + unsigned nakonbble:1; + /** Enable Continue on BNA */ + unsigned encontonbna:1; + + unsigned reserved18_31:14; + } b; +} dctl_data_t; + +/** + * This union represents the bit fields in the Device Status + * Register. Read the register into the d32 member then + * set/clear the bits using the bit elements. + */ +typedef union dsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Suspend Status */ + unsigned suspsts:1; + /** Enumerated Speed */ + unsigned enumspd:2; +#define DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ 0 +#define DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ 1 +#define DWC_DSTS_ENUMSPD_LS_PHY_6MHZ 2 +#define DWC_DSTS_ENUMSPD_FS_PHY_48MHZ 3 + /** Erratic Error */ + unsigned errticerr:1; + unsigned reserved4_7:4; + /** Frame or Microframe Number of the received SOF */ + unsigned soffn:14; + unsigned reserved22_31:10; + } b; +} dsts_data_t; + +/** + * This union represents the bit fields in the Device IN EP Interrupt + * Register and the Device IN EP Common Mask Register. + * + * - Read the register into the d32 member then set/clear the + * bits using the bit elements. + */ +typedef union diepint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer complete mask */ + unsigned xfercompl:1; + /** Endpoint disable mask */ + unsigned epdisabled:1; + /** AHB Error mask */ + unsigned ahberr:1; + /** TimeOUT Handshake mask (non-ISOC EPs) */ + unsigned timeout:1; + /** IN Token received with TxF Empty mask */ + unsigned intktxfemp:1; + /** IN Token Received with EP mismatch mask */ + unsigned intknepmis:1; + /** IN Endpoint NAK Effective mask */ + unsigned inepnakeff:1; + /** Reserved */ + unsigned emptyintr:1; + + unsigned txfifoundrn:1; + + /** BNA Interrupt mask */ + unsigned bna:1; + + unsigned reserved10_12:3; + /** BNA Interrupt mask */ + unsigned nak:1; + + unsigned reserved14_31:18; + } b; +} diepint_data_t; + +/** + * This union represents the bit fields in the Device IN EP + * Common/Dedicated Interrupt Mask Register. + */ +typedef union diepint_data diepmsk_data_t; + +/** + * This union represents the bit fields in the Device OUT EP Interrupt + * Registerand Device OUT EP Common Interrupt Mask Register. + * + * - Read the register into the d32 member then set/clear the + * bits using the bit elements. + */ +typedef union doepint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer complete */ + unsigned xfercompl:1; + /** Endpoint disable */ + unsigned epdisabled:1; + /** AHB Error */ + unsigned ahberr:1; + /** Setup Phase Done (contorl EPs) */ + unsigned setup:1; + /** OUT Token Received when Endpoint Disabled */ + unsigned outtknepdis:1; + + unsigned stsphsercvd:1; + /** Back-to-Back SETUP Packets Received */ + unsigned back2backsetup:1; + + unsigned reserved7:1; + /** OUT packet Error */ + unsigned outpkterr:1; + /** BNA Interrupt */ + unsigned bna:1; + + unsigned reserved10:1; + /** Packet Drop Status */ + unsigned pktdrpsts:1; + /** Babble Interrupt */ + unsigned babble:1; + /** NAK Interrupt */ + unsigned nak:1; + /** NYET Interrupt */ + unsigned nyet:1; + /** Bit indicating setup packet received */ + unsigned sr:1; + + unsigned reserved16_31:16; + } b; +} doepint_data_t; + +/** + * This union represents the bit fields in the Device OUT EP + * Common/Dedicated Interrupt Mask Register. + */ +typedef union doepint_data doepmsk_data_t; + +/** + * This union represents the bit fields in the Device All EP Interrupt + * and Mask Registers. + * - Read the register into the d32 member then set/clear the + * bits using the bit elements. + */ +typedef union daint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** IN Endpoint bits */ + unsigned in:16; + /** OUT Endpoint bits */ + unsigned out:16; + } ep; + struct { + /** IN Endpoint bits */ + unsigned inep0:1; + unsigned inep1:1; + unsigned inep2:1; + unsigned inep3:1; + unsigned inep4:1; + unsigned inep5:1; + unsigned inep6:1; + unsigned inep7:1; + unsigned inep8:1; + unsigned inep9:1; + unsigned inep10:1; + unsigned inep11:1; + unsigned inep12:1; + unsigned inep13:1; + unsigned inep14:1; + unsigned inep15:1; + /** OUT Endpoint bits */ + unsigned outep0:1; + unsigned outep1:1; + unsigned outep2:1; + unsigned outep3:1; + unsigned outep4:1; + unsigned outep5:1; + unsigned outep6:1; + unsigned outep7:1; + unsigned outep8:1; + unsigned outep9:1; + unsigned outep10:1; + unsigned outep11:1; + unsigned outep12:1; + unsigned outep13:1; + unsigned outep14:1; + unsigned outep15:1; + } b; +} daint_data_t; + +/** + * This union represents the bit fields in the Device IN Token Queue + * Read Registers. + * - Read the register into the d32 member. + * - READ-ONLY Register + */ +typedef union dtknq1_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** In Token Queue Write Pointer */ + unsigned intknwptr:5; + /** Reserved */ + unsigned reserved05_06:2; + /** write pointer has wrapped. */ + unsigned wrap_bit:1; + /** EP Numbers of IN Tokens 0 ... 4 */ + unsigned epnums0_5:24; + } b; +} dtknq1_data_t; + +/** + * This union represents Threshold control Register + * - Read and write the register into the d32 member. + * - READ-WRITABLE Register + */ +typedef union dthrctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** non ISO Tx Thr. Enable */ + unsigned non_iso_thr_en:1; + /** ISO Tx Thr. Enable */ + unsigned iso_thr_en:1; + /** Tx Thr. Length */ + unsigned tx_thr_len:9; + /** AHB Threshold ratio */ + unsigned ahb_thr_ratio:2; + /** Reserved */ + unsigned reserved13_15:3; + /** Rx Thr. Enable */ + unsigned rx_thr_en:1; + /** Rx Thr. Length */ + unsigned rx_thr_len:9; + unsigned reserved26:1; + /** Arbiter Parking Enable*/ + unsigned arbprken:1; + /** Reserved */ + unsigned reserved28_31:4; + } b; +} dthrctl_data_t; + +/** + * Device Logical IN Endpoint-Specific Registers. Offsets + * 900h-AFCh + * + * There will be one set of endpoint registers per logical endpoint + * implemented. + * + * These registers are visible only in Device mode and must not be + * accessed in Host mode, as the results are unknown. + */ +typedef struct dwc_otg_dev_in_ep_regs { + /** Device IN Endpoint Control Register. Offset:900h + + * (ep_num * 20h) + 00h */ + volatile uint32_t diepctl; + /** Reserved. Offset:900h + (ep_num * 20h) + 04h */ + uint32_t reserved04; + /** Device IN Endpoint Interrupt Register. Offset:900h + + * (ep_num * 20h) + 08h */ + volatile uint32_t diepint; + /** Reserved. Offset:900h + (ep_num * 20h) + 0Ch */ + uint32_t reserved0C; + /** Device IN Endpoint Transfer Size + * Register. Offset:900h + (ep_num * 20h) + 10h */ + volatile uint32_t dieptsiz; + /** Device IN Endpoint DMA Address Register. Offset:900h + + * (ep_num * 20h) + 14h */ + volatile uint32_t diepdma; + /** Device IN Endpoint Transmit FIFO Status Register. Offset:900h + + * (ep_num * 20h) + 18h */ + volatile uint32_t dtxfsts; + /** Device IN Endpoint DMA Buffer Register. Offset:900h + + * (ep_num * 20h) + 1Ch */ + volatile uint32_t diepdmab; +} dwc_otg_dev_in_ep_regs_t; + +/** + * Device Logical OUT Endpoint-Specific Registers. Offsets: + * B00h-CFCh + * + * There will be one set of endpoint registers per logical endpoint + * implemented. + * + * These registers are visible only in Device mode and must not be + * accessed in Host mode, as the results are unknown. + */ +typedef struct dwc_otg_dev_out_ep_regs { + /** Device OUT Endpoint Control Register. Offset:B00h + + * (ep_num * 20h) + 00h */ + volatile uint32_t doepctl; + /** Reserved. Offset:B00h + (ep_num * 20h) + 04h */ + uint32_t reserved04; + /** Device OUT Endpoint Interrupt Register. Offset:B00h + + * (ep_num * 20h) + 08h */ + volatile uint32_t doepint; + /** Reserved. Offset:B00h + (ep_num * 20h) + 0Ch */ + uint32_t reserved0C; + /** Device OUT Endpoint Transfer Size Register. Offset: + * B00h + (ep_num * 20h) + 10h */ + volatile uint32_t doeptsiz; + /** Device OUT Endpoint DMA Address Register. Offset:B00h + * + (ep_num * 20h) + 14h */ + volatile uint32_t doepdma; + /** Reserved. Offset:B00h + * (ep_num * 20h) + 18h */ + uint32_t unused; + /** Device OUT Endpoint DMA Buffer Register. Offset:B00h + * + (ep_num * 20h) + 1Ch */ + uint32_t doepdmab; +} dwc_otg_dev_out_ep_regs_t; + +/** + * This union represents the bit fields in the Device EP Control + * Register. Read the register into the d32 member then + * set/clear the bits using the bit elements. + */ +typedef union depctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Maximum Packet Size + * IN/OUT EPn + * IN/OUT EP0 - 2 bits + * 2'b00: 64 Bytes + * 2'b01: 32 + * 2'b10: 16 + * 2'b11: 8 */ + unsigned mps:11; +#define DWC_DEP0CTL_MPS_64 0 +#define DWC_DEP0CTL_MPS_32 1 +#define DWC_DEP0CTL_MPS_16 2 +#define DWC_DEP0CTL_MPS_8 3 + + /** Next Endpoint + * IN EPn/IN EP0 + * OUT EPn/OUT EP0 - reserved */ + unsigned nextep:4; + + /** USB Active Endpoint */ + unsigned usbactep:1; + + /** Endpoint DPID (INTR/Bulk IN and OUT endpoints) + * This field contains the PID of the packet going to + * be received or transmitted on this endpoint. The + * application should program the PID of the first + * packet going to be received or transmitted on this + * endpoint , after the endpoint is + * activated. Application use the SetD1PID and + * SetD0PID fields of this register to program either + * D0 or D1 PID. + * + * The encoding for this field is + * - 0: D0 + * - 1: D1 + */ + unsigned dpid:1; + + /** NAK Status */ + unsigned naksts:1; + + /** Endpoint Type + * 2'b00: Control + * 2'b01: Isochronous + * 2'b10: Bulk + * 2'b11: Interrupt */ + unsigned eptype:2; + + /** Snoop Mode + * OUT EPn/OUT EP0 + * IN EPn/IN EP0 - reserved */ + unsigned snp:1; + + /** Stall Handshake */ + unsigned stall:1; + + /** Tx Fifo Number + * IN EPn/IN EP0 + * OUT EPn/OUT EP0 - reserved */ + unsigned txfnum:4; + + /** Clear NAK */ + unsigned cnak:1; + /** Set NAK */ + unsigned snak:1; + /** Set DATA0 PID (INTR/Bulk IN and OUT endpoints) + * Writing to this field sets the Endpoint DPID (DPID) + * field in this register to DATA0. Set Even + * (micro)frame (SetEvenFr) (ISO IN and OUT Endpoints) + * Writing to this field sets the Even/Odd + * (micro)frame (EO_FrNum) field to even (micro) + * frame. + */ + unsigned setd0pid:1; + /** Set DATA1 PID (INTR/Bulk IN and OUT endpoints) + * Writing to this field sets the Endpoint DPID (DPID) + * field in this register to DATA1 Set Odd + * (micro)frame (SetOddFr) (ISO IN and OUT Endpoints) + * Writing to this field sets the Even/Odd + * (micro)frame (EO_FrNum) field to odd (micro) frame. + */ + unsigned setd1pid:1; + + /** Endpoint Disable */ + unsigned epdis:1; + /** Endpoint Enable */ + unsigned epena:1; + } b; +} depctl_data_t; + +/** + * This union represents the bit fields in the Device EP Transfer + * Size Register. Read the register into the d32 member then + * set/clear the bits using the bit elements. + */ +typedef union deptsiz_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer size */ + unsigned xfersize:19; +/** Max packet count for EP (pow(2,10)-1) */ +#define MAX_PKT_CNT 1023 + /** Packet Count */ + unsigned pktcnt:10; + /** Multi Count - Periodic IN endpoints */ + unsigned mc:2; + unsigned reserved:1; + } b; +} deptsiz_data_t; + +/** + * This union represents the bit fields in the Device EP 0 Transfer + * Size Register. Read the register into the d32 member then + * set/clear the bits using the bit elements. + */ +typedef union deptsiz0_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer size */ + unsigned xfersize:7; + /** Reserved */ + unsigned reserved7_18:12; + /** Packet Count */ + unsigned pktcnt:2; + /** Reserved */ + unsigned reserved21_28:8; + /**Setup Packet Count (DOEPTSIZ0 Only) */ + unsigned supcnt:2; + unsigned reserved31; + } b; +} deptsiz0_data_t; + +///////////////////////////////////////////////// +// DMA Descriptor Specific Structures +// + +/** Buffer status definitions */ + +#define BS_HOST_READY 0x0 +#define BS_DMA_BUSY 0x1 +#define BS_DMA_DONE 0x2 +#define BS_HOST_BUSY 0x3 + +/** Receive/Transmit status definitions */ + +#define RTS_SUCCESS 0x0 +#define RTS_BUFFLUSH 0x1 +#define RTS_RESERVED 0x2 +#define RTS_BUFERR 0x3 + +/** + * This union represents the bit fields in the DMA Descriptor + * status quadlet. Read the quadlet into the d32 member then + * set/clear the bits using the bit, b_iso_out and + * b_iso_in elements. + */ +typedef union dev_dma_desc_sts { + /** raw register data */ + uint32_t d32; + /** quadlet bits */ + struct { + /** Received number of bytes */ + unsigned bytes:16; + /** NAK bit - only for OUT EPs */ + unsigned nak:1; + unsigned reserved17_22:6; + /** Multiple Transfer - only for OUT EPs */ + unsigned mtrf:1; + /** Setup Packet received - only for OUT EPs */ + unsigned sr:1; + /** Interrupt On Complete */ + unsigned ioc:1; + /** Short Packet */ + unsigned sp:1; + /** Last */ + unsigned l:1; + /** Receive Status */ + unsigned sts:2; + /** Buffer Status */ + unsigned bs:2; + } b; + +//#ifdef DWC_EN_ISOC + /** iso out quadlet bits */ + struct { + /** Received number of bytes */ + unsigned rxbytes:11; + + unsigned reserved11:1; + /** Frame Number */ + unsigned framenum:11; + /** Received ISO Data PID */ + unsigned pid:2; + /** Interrupt On Complete */ + unsigned ioc:1; + /** Short Packet */ + unsigned sp:1; + /** Last */ + unsigned l:1; + /** Receive Status */ + unsigned rxsts:2; + /** Buffer Status */ + unsigned bs:2; + } b_iso_out; + + /** iso in quadlet bits */ + struct { + /** Transmited number of bytes */ + unsigned txbytes:12; + /** Frame Number */ + unsigned framenum:11; + /** Transmited ISO Data PID */ + unsigned pid:2; + /** Interrupt On Complete */ + unsigned ioc:1; + /** Short Packet */ + unsigned sp:1; + /** Last */ + unsigned l:1; + /** Transmit Status */ + unsigned txsts:2; + /** Buffer Status */ + unsigned bs:2; + } b_iso_in; +//#endif /* DWC_EN_ISOC */ +} dev_dma_desc_sts_t; + +/** + * DMA Descriptor structure + * + * DMA Descriptor structure contains two quadlets: + * Status quadlet and Data buffer pointer. + */ +typedef struct dwc_otg_dev_dma_desc { + /** DMA Descriptor status quadlet */ + dev_dma_desc_sts_t status; + /** DMA Descriptor data buffer pointer */ + uint32_t buf; +} dwc_otg_dev_dma_desc_t; + +/** + * The dwc_otg_dev_if structure contains information needed to manage + * the DWC_otg controller acting in device mode. It represents the + * programming view of the device-specific aspects of the controller. + */ +typedef struct dwc_otg_dev_if { + /** Pointer to device Global registers. + * Device Global Registers starting at offset 800h + */ + dwc_otg_device_global_regs_t *dev_global_regs; +#define DWC_DEV_GLOBAL_REG_OFFSET 0x800 + + /** + * Device Logical IN Endpoint-Specific Registers 900h-AFCh + */ + dwc_otg_dev_in_ep_regs_t *in_ep_regs[MAX_EPS_CHANNELS]; +#define DWC_DEV_IN_EP_REG_OFFSET 0x900 +#define DWC_EP_REG_OFFSET 0x20 + + /** Device Logical OUT Endpoint-Specific Registers B00h-CFCh */ + dwc_otg_dev_out_ep_regs_t *out_ep_regs[MAX_EPS_CHANNELS]; +#define DWC_DEV_OUT_EP_REG_OFFSET 0xB00 + + /* Device configuration information */ + uint8_t speed; /**< Device Speed 0: Unknown, 1: LS, 2:FS, 3: HS */ + uint8_t num_in_eps; /**< Number # of Tx EP range: 0-15 exept ep0 */ + uint8_t num_out_eps; /**< Number # of Rx EP range: 0-15 exept ep 0*/ + + /** Size of periodic FIFOs (Bytes) */ + uint16_t perio_tx_fifo_size[MAX_PERIO_FIFOS]; + + /** Size of Tx FIFOs (Bytes) */ + uint16_t tx_fifo_size[MAX_TX_FIFOS]; + + /** Thresholding enable flags and length varaiables **/ + uint16_t rx_thr_en; + uint16_t iso_tx_thr_en; + uint16_t non_iso_tx_thr_en; + + uint16_t rx_thr_length; + uint16_t tx_thr_length; + + /** + * Pointers to the DMA Descriptors for EP0 Control + * transfers (virtual and physical) + */ + + /** 2 descriptors for SETUP packets */ + dwc_dma_t dma_setup_desc_addr[2]; + dwc_otg_dev_dma_desc_t *setup_desc_addr[2]; + + /** Pointer to Descriptor with latest SETUP packet */ + dwc_otg_dev_dma_desc_t *psetup; + + /** Index of current SETUP handler descriptor */ + uint32_t setup_desc_index; + + /** Descriptor for Data In or Status In phases */ + dwc_dma_t dma_in_desc_addr; + dwc_otg_dev_dma_desc_t *in_desc_addr; + + /** Descriptor for Data Out or Status Out phases */ + dwc_dma_t dma_out_desc_addr; + dwc_otg_dev_dma_desc_t *out_desc_addr; + + /** Setup Packet Detected - if set clear NAK when queueing */ + uint32_t spd; + /** Isoc ep pointer on which incomplete happens */ + void *isoc_ep; + +} dwc_otg_dev_if_t; + +///////////////////////////////////////////////// +// Host Mode Register Structures +// +/** + * The Host Global Registers structure defines the size and relative + * field offsets for the Host Mode Global Registers. Host Global + * Registers offsets 400h-7FFh. +*/ +typedef struct dwc_otg_host_global_regs { + /** Host Configuration Register. Offset: 400h */ + volatile uint32_t hcfg; + /** Host Frame Interval Register. Offset: 404h */ + volatile uint32_t hfir; + /** Host Frame Number / Frame Remaining Register. Offset: 408h */ + volatile uint32_t hfnum; + /** Reserved. Offset: 40Ch */ + uint32_t reserved40C; + /** Host Periodic Transmit FIFO/ Queue Status Register. Offset: 410h */ + volatile uint32_t hptxsts; + /** Host All Channels Interrupt Register. Offset: 414h */ + volatile uint32_t haint; + /** Host All Channels Interrupt Mask Register. Offset: 418h */ + volatile uint32_t haintmsk; + /** Host Frame List Base Address Register . Offset: 41Ch */ + volatile uint32_t hflbaddr; +} dwc_otg_host_global_regs_t; + +/** + * This union represents the bit fields in the Host Configuration Register. + * Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hcfg register. + */ +typedef union hcfg_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** FS/LS Phy Clock Select */ + unsigned fslspclksel:2; +#define DWC_HCFG_30_60_MHZ 0 +#define DWC_HCFG_48_MHZ 1 +#define DWC_HCFG_6_MHZ 2 + + /** FS/LS Only Support */ + unsigned fslssupp:1; + unsigned reserved3_6:4; + /** Enable 32-KHz Suspend Mode */ + unsigned ena32khzs:1; + /** Resume Validation Periiod */ + unsigned resvalid:8; + unsigned reserved16_22:7; + /** Enable Scatter/gather DMA in Host mode */ + unsigned descdma:1; + /** Frame List Entries */ + unsigned frlisten:2; + /** Enable Periodic Scheduling */ + unsigned perschedena:1; + unsigned reserved27_30:4; + unsigned modechtimen:1; + } b; +} hcfg_data_t; + +/** + * This union represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +typedef union hfir_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + unsigned frint:16; + unsigned hfirrldctrl:1; + unsigned reserved:15; + } b; +} hfir_data_t; + +/** + * This union represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +typedef union hfnum_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + unsigned frnum:16; +#define DWC_HFNUM_MAX_FRNUM 0x3FFF + unsigned frrem:16; + } b; +} hfnum_data_t; + +typedef union hptxsts_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + unsigned ptxfspcavail:16; + unsigned ptxqspcavail:8; + /** Top of the Periodic Transmit Request Queue + * - bit 24 - Terminate (last entry for the selected channel) + * - bits 26:25 - Token Type + * - 2'b00 - Zero length + * - 2'b01 - Ping + * - 2'b10 - Disable + * - bits 30:27 - Channel Number + * - bit 31 - Odd/even microframe + */ + unsigned ptxqtop_terminate:1; + unsigned ptxqtop_token:2; + unsigned ptxqtop_chnum:4; + unsigned ptxqtop_odd:1; + } b; +} hptxsts_data_t; + +/** + * This union represents the bit fields in the Host Port Control and Status + * Register. Read the register into the d32 member then set/clear the + * bits using the bit elements. Write the d32 member to the + * hprt0 register. + */ +typedef union hprt0_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned prtconnsts:1; + unsigned prtconndet:1; + unsigned prtena:1; + unsigned prtenchng:1; + unsigned prtovrcurract:1; + unsigned prtovrcurrchng:1; + unsigned prtres:1; + unsigned prtsusp:1; + unsigned prtrst:1; + unsigned reserved9:1; + unsigned prtlnsts:2; + unsigned prtpwr:1; + unsigned prttstctl:4; + unsigned prtspd:2; +#define DWC_HPRT0_PRTSPD_HIGH_SPEED 0 +#define DWC_HPRT0_PRTSPD_FULL_SPEED 1 +#define DWC_HPRT0_PRTSPD_LOW_SPEED 2 + unsigned reserved19_31:13; + } b; +} hprt0_data_t; + +/** + * This union represents the bit fields in the Host All Interrupt + * Register. + */ +typedef union haint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned ch0:1; + unsigned ch1:1; + unsigned ch2:1; + unsigned ch3:1; + unsigned ch4:1; + unsigned ch5:1; + unsigned ch6:1; + unsigned ch7:1; + unsigned ch8:1; + unsigned ch9:1; + unsigned ch10:1; + unsigned ch11:1; + unsigned ch12:1; + unsigned ch13:1; + unsigned ch14:1; + unsigned ch15:1; + unsigned reserved:16; + } b; + + struct { + unsigned chint:16; + unsigned reserved:16; + } b2; +} haint_data_t; + +/** + * This union represents the bit fields in the Host All Interrupt + * Register. + */ +typedef union haintmsk_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned ch0:1; + unsigned ch1:1; + unsigned ch2:1; + unsigned ch3:1; + unsigned ch4:1; + unsigned ch5:1; + unsigned ch6:1; + unsigned ch7:1; + unsigned ch8:1; + unsigned ch9:1; + unsigned ch10:1; + unsigned ch11:1; + unsigned ch12:1; + unsigned ch13:1; + unsigned ch14:1; + unsigned ch15:1; + unsigned reserved:16; + } b; + + struct { + unsigned chint:16; + unsigned reserved:16; + } b2; +} haintmsk_data_t; + +/** + * Host Channel Specific Registers. 500h-5FCh + */ +typedef struct dwc_otg_hc_regs { + /** Host Channel 0 Characteristic Register. Offset: 500h + (chan_num * 20h) + 00h */ + volatile uint32_t hcchar; + /** Host Channel 0 Split Control Register. Offset: 500h + (chan_num * 20h) + 04h */ + volatile uint32_t hcsplt; + /** Host Channel 0 Interrupt Register. Offset: 500h + (chan_num * 20h) + 08h */ + volatile uint32_t hcint; + /** Host Channel 0 Interrupt Mask Register. Offset: 500h + (chan_num * 20h) + 0Ch */ + volatile uint32_t hcintmsk; + /** Host Channel 0 Transfer Size Register. Offset: 500h + (chan_num * 20h) + 10h */ + volatile uint32_t hctsiz; + /** Host Channel 0 DMA Address Register. Offset: 500h + (chan_num * 20h) + 14h */ + volatile uint32_t hcdma; + volatile uint32_t reserved; + /** Host Channel 0 DMA Buffer Address Register. Offset: 500h + (chan_num * 20h) + 1Ch */ + volatile uint32_t hcdmab; +} dwc_otg_hc_regs_t; + +/** + * This union represents the bit fields in the Host Channel Characteristics + * Register. Read the register into the d32 member then set/clear the + * bits using the bit elements. Write the d32 member to the + * hcchar register. + */ +typedef union hcchar_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** Maximum packet size in bytes */ + unsigned mps:11; + + /** Endpoint number */ + unsigned epnum:4; + + /** 0: OUT, 1: IN */ + unsigned epdir:1; + + unsigned reserved:1; + + /** 0: Full/high speed device, 1: Low speed device */ + unsigned lspddev:1; + + /** 0: Control, 1: Isoc, 2: Bulk, 3: Intr */ + unsigned eptype:2; + + /** Packets per frame for periodic transfers. 0 is reserved. */ + unsigned multicnt:2; + + /** Device address */ + unsigned devaddr:7; + + /** + * Frame to transmit periodic transaction. + * 0: even, 1: odd + */ + unsigned oddfrm:1; + + /** Channel disable */ + unsigned chdis:1; + + /** Channel enable */ + unsigned chen:1; + } b; +} hcchar_data_t; + +typedef union hcsplt_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** Port Address */ + unsigned prtaddr:7; + + /** Hub Address */ + unsigned hubaddr:7; + + /** Transaction Position */ + unsigned xactpos:2; +#define DWC_HCSPLIT_XACTPOS_MID 0 +#define DWC_HCSPLIT_XACTPOS_END 1 +#define DWC_HCSPLIT_XACTPOS_BEGIN 2 +#define DWC_HCSPLIT_XACTPOS_ALL 3 + + /** Do Complete Split */ + unsigned compsplt:1; + + /** Reserved */ + unsigned reserved:14; + + /** Split Enble */ + unsigned spltena:1; + } b; +} hcsplt_data_t; + +/** + * This union represents the bit fields in the Host All Interrupt + * Register. + */ +typedef union hcint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer Complete */ + unsigned xfercomp:1; + /** Channel Halted */ + unsigned chhltd:1; + /** AHB Error */ + unsigned ahberr:1; + /** STALL Response Received */ + unsigned stall:1; + /** NAK Response Received */ + unsigned nak:1; + /** ACK Response Received */ + unsigned ack:1; + /** NYET Response Received */ + unsigned nyet:1; + /** Transaction Err */ + unsigned xacterr:1; + /** Babble Error */ + unsigned bblerr:1; + /** Frame Overrun */ + unsigned frmovrun:1; + /** Data Toggle Error */ + unsigned datatglerr:1; + /** Buffer Not Available (only for DDMA mode) */ + unsigned bna:1; + /** Exessive transaction error (only for DDMA mode) */ + unsigned xcs_xact:1; + /** Frame List Rollover interrupt */ + unsigned frm_list_roll:1; + /** Reserved */ + unsigned reserved14_31:18; + } b; +} hcint_data_t; + +/** + * This union represents the bit fields in the Host Channel Interrupt Mask + * Register. Read the register into the d32 member then set/clear the + * bits using the bit elements. Write the d32 member to the + * hcintmsk register. + */ +typedef union hcintmsk_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + unsigned xfercompl:1; + unsigned chhltd:1; + unsigned ahberr:1; + unsigned stall:1; + unsigned nak:1; + unsigned ack:1; + unsigned nyet:1; + unsigned xacterr:1; + unsigned bblerr:1; + unsigned frmovrun:1; + unsigned datatglerr:1; + unsigned bna:1; + unsigned xcs_xact:1; + unsigned frm_list_roll:1; + unsigned reserved14_31:18; + } b; +} hcintmsk_data_t; + +/** + * This union represents the bit fields in the Host Channel Transfer Size + * Register. Read the register into the d32 member then set/clear the + * bits using the bit elements. Write the d32 member to the + * hcchar register. + */ + +typedef union hctsiz_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** Total transfer size in bytes */ + unsigned xfersize:19; + + /** Data packets to transfer */ + unsigned pktcnt:10; + + /** + * Packet ID for next data packet + * 0: DATA0 + * 1: DATA2 + * 2: DATA1 + * 3: MDATA (non-Control), SETUP (Control) + */ + unsigned pid:2; +#define DWC_HCTSIZ_DATA0 0 +#define DWC_HCTSIZ_DATA1 2 +#define DWC_HCTSIZ_DATA2 1 +#define DWC_HCTSIZ_MDATA 3 +#define DWC_HCTSIZ_SETUP 3 + + /** Do PING protocol when 1 */ + unsigned dopng:1; + } b; + + /** register bits */ + struct { + /** Scheduling information */ + unsigned schinfo:8; + + /** Number of transfer descriptors. + * Max value: + * 64 in general, + * 256 only for HS isochronous endpoint. + */ + unsigned ntd:8; + + /** Data packets to transfer */ + unsigned reserved16_28:13; + + /** + * Packet ID for next data packet + * 0: DATA0 + * 1: DATA2 + * 2: DATA1 + * 3: MDATA (non-Control) + */ + unsigned pid:2; + + /** Do PING protocol when 1 */ + unsigned dopng:1; + } b_ddma; +} hctsiz_data_t; + +/** + * This union represents the bit fields in the Host DMA Address + * Register used in Descriptor DMA mode. + */ +typedef union hcdma_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned reserved0_2:3; + /** Current Transfer Descriptor. Not used for ISOC */ + unsigned ctd:8; + /** Start Address of Descriptor List */ + unsigned dma_addr:21; + } b; +} hcdma_data_t; + +/** + * This union represents the bit fields in the DMA Descriptor + * status quadlet for host mode. Read the quadlet into the d32 member then + * set/clear the bits using the bit elements. + */ +typedef union host_dma_desc_sts { + /** raw register data */ + uint32_t d32; + /** quadlet bits */ + + /* for non-isochronous */ + struct { + /** Number of bytes */ + unsigned n_bytes:17; + /** QTD offset to jump when Short Packet received - only for IN EPs */ + unsigned qtd_offset:6; + /** + * Set to request the core to jump to alternate QTD if + * Short Packet received - only for IN EPs + */ + unsigned a_qtd:1; + /** + * Setup Packet bit. When set indicates that buffer contains + * setup packet. + */ + unsigned sup:1; + /** Interrupt On Complete */ + unsigned ioc:1; + /** End of List */ + unsigned eol:1; + unsigned reserved27:1; + /** Rx/Tx Status */ + unsigned sts:2; +#define DMA_DESC_STS_PKTERR 1 + unsigned reserved30:1; + /** Active Bit */ + unsigned a:1; + } b; + /* for isochronous */ + struct { + /** Number of bytes */ + unsigned n_bytes:12; + unsigned reserved12_24:13; + /** Interrupt On Complete */ + unsigned ioc:1; + unsigned reserved26_27:2; + /** Rx/Tx Status */ + unsigned sts:2; + unsigned reserved30:1; + /** Active Bit */ + unsigned a:1; + } b_isoc; +} host_dma_desc_sts_t; + +#define MAX_DMA_DESC_SIZE 131071 +#define MAX_DMA_DESC_NUM_GENERIC 64 +#define MAX_DMA_DESC_NUM_HS_ISOC 256 +#define MAX_FRLIST_EN_NUM 64 +/** + * Host-mode DMA Descriptor structure + * + * DMA Descriptor structure contains two quadlets: + * Status quadlet and Data buffer pointer. + */ +typedef struct dwc_otg_host_dma_desc { + /** DMA Descriptor status quadlet */ + host_dma_desc_sts_t status; + /** DMA Descriptor data buffer pointer */ + uint32_t buf; +} dwc_otg_host_dma_desc_t; + +/** OTG Host Interface Structure. + * + * The OTG Host Interface Structure structure contains information + * needed to manage the DWC_otg controller acting in host mode. It + * represents the programming view of the host-specific aspects of the + * controller. + */ +typedef struct dwc_otg_host_if { + /** Host Global Registers starting at offset 400h.*/ + dwc_otg_host_global_regs_t *host_global_regs; +#define DWC_OTG_HOST_GLOBAL_REG_OFFSET 0x400 + + /** Host Port 0 Control and Status Register */ + volatile uint32_t *hprt0; +#define DWC_OTG_HOST_PORT_REGS_OFFSET 0x440 + + /** Host Channel Specific Registers at offsets 500h-5FCh. */ + dwc_otg_hc_regs_t *hc_regs[MAX_EPS_CHANNELS]; +#define DWC_OTG_HOST_CHAN_REGS_OFFSET 0x500 +#define DWC_OTG_CHAN_REGS_OFFSET 0x20 + + /* Host configuration information */ + /** Number of Host Channels (range: 1-16) */ + uint8_t num_host_channels; + /** Periodic EPs supported (0: no, 1: yes) */ + uint8_t perio_eps_supported; + /** Periodic Tx FIFO Size (Only 1 host periodic Tx FIFO) */ + uint16_t perio_tx_fifo_size; + +} dwc_otg_host_if_t; + +/** + * This union represents the bit fields in the Power and Clock Gating Control + * Register. Read the register into the d32 member then set/clear the + * bits using the bit elements. + */ +typedef union pcgcctl_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** Stop Pclk */ + unsigned stoppclk:1; + /** Gate Hclk */ + unsigned gatehclk:1; + /** Power Clamp */ + unsigned pwrclmp:1; + /** Reset Power Down Modules */ + unsigned rstpdwnmodule:1; + /** Reserved */ + unsigned reserved:1; + /** Enable Sleep Clock Gating (Enbl_L1Gating) */ + unsigned enbl_sleep_gating:1; + /** PHY In Sleep (PhySleep) */ + unsigned phy_in_sleep:1; + /** Deep Sleep*/ + unsigned deep_sleep:1; + unsigned resetaftsusp:1; + unsigned restoremode:1; + unsigned enbl_extnd_hiber:1; + unsigned extnd_hiber_pwrclmp:1; + unsigned extnd_hiber_switch:1; + unsigned ess_reg_restored:1; + unsigned prt_clk_sel:2; + unsigned port_power:1; + unsigned max_xcvrselect:2; + unsigned max_termsel:1; + unsigned mac_dev_addr:7; + unsigned p2hd_dev_enum_spd:2; + unsigned p2hd_prt_spd:2; + unsigned if_dev_mode:1; + } b; +} pcgcctl_data_t; + +/** + * This union represents the bit fields in the Global Data FIFO Software + * Configuration Register. Read the register into the d32 member then + * set/clear the bits using the bit elements. + */ +typedef union gdfifocfg_data { + /* raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** OTG Data FIFO depth */ + unsigned gdfifocfg:16; + /** Start address of EP info controller */ + unsigned epinfobase:16; + } b; +} gdfifocfg_data_t; + +/** + * This union represents the bit fields in the Global Power Down Register + * Register. Read the register into the d32 member then set/clear the + * bits using the bit elements. + */ +typedef union gpwrdn_data { + /* raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** PMU Interrupt Select */ + unsigned pmuintsel:1; + /** PMU Active */ + unsigned pmuactv:1; + /** Restore */ + unsigned restore:1; + /** Power Down Clamp */ + unsigned pwrdnclmp:1; + /** Power Down Reset */ + unsigned pwrdnrstn:1; + /** Power Down Switch */ + unsigned pwrdnswtch:1; + /** Disable VBUS */ + unsigned dis_vbus:1; + /** Line State Change */ + unsigned lnstschng:1; + /** Line state change mask */ + unsigned lnstchng_msk:1; + /** Reset Detected */ + unsigned rst_det:1; + /** Reset Detect mask */ + unsigned rst_det_msk:1; + /** Disconnect Detected */ + unsigned disconn_det:1; + /** Disconnect Detect mask */ + unsigned disconn_det_msk:1; + /** Connect Detected*/ + unsigned connect_det:1; + /** Connect Detected Mask*/ + unsigned connect_det_msk:1; + /** SRP Detected */ + unsigned srp_det:1; + /** SRP Detect mask */ + unsigned srp_det_msk:1; + /** Status Change Interrupt */ + unsigned sts_chngint:1; + /** Status Change Interrupt Mask */ + unsigned sts_chngint_msk:1; + /** Line State */ + unsigned linestate:2; + /** Indicates current mode(status of IDDIG signal) */ + unsigned idsts:1; + /** B Session Valid signal status*/ + unsigned bsessvld:1; + /** ADP Event Detected */ + unsigned adp_int:1; + /** Multi Valued ID pin */ + unsigned mult_val_id_bc:5; + /** Reserved 24_31 */ + unsigned reserved29_31:3; + } b; +} gpwrdn_data_t; + +#endif diff --git a/drivers/usb/gadget/dwc_otg/usb.h b/drivers/usb/gadget/dwc_otg/usb.h new file mode 100644 index 00000000..0621d2e9 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/usb.h @@ -0,0 +1,850 @@ +/* + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* Modified by Synopsys, Inc, 12/12/2007 */ + + +#ifndef _USB_H_ +#define _USB_H_ + +#include "dwc_os.h" + +/* + * The USB records contain some unaligned little-endian word + * components. The U[SG]ETW macros take care of both the alignment + * and endian problem and should always be used to access non-byte + * values. + */ +typedef u_int8_t uByte; +typedef u_int8_t uWord[2]; +typedef u_int8_t uDWord[4]; + +#define USETW2(w,h,l) ((w)[0] = (u_int8_t)(l), (w)[1] = (u_int8_t)(h)) + +#if 1 +#define UGETW(w) ((w)[0] | ((w)[1] << 8)) +#define USETW(w,v) ((w)[0] = (u_int8_t)(v), (w)[1] = (u_int8_t)((v) >> 8)) +#define UGETDW(w) ((w)[0] | ((w)[1] << 8) | ((w)[2] << 16) | ((w)[3] << 24)) +#define USETDW(w,v) ((w)[0] = (u_int8_t)(v), \ + (w)[1] = (u_int8_t)((v) >> 8), \ + (w)[2] = (u_int8_t)((v) >> 16), \ + (w)[3] = (u_int8_t)((v) >> 24)) +#else +/* + * On little-endian machines that can handle unanliged accesses + * (e.g. i386) these macros can be replaced by the following. + */ +#define UGETW(w) (*(u_int16_t *)(w)) +#define USETW(w,v) (*(u_int16_t *)(w) = (v)) +#define UGETDW(w) (*(u_int32_t *)(w)) +#define USETDW(w,v) (*(u_int32_t *)(w) = (v)) +#endif + +#define UPACKED __attribute__((__packed__)) + +typedef struct { + uByte bmRequestType; + uByte bRequest; + uWord wValue; + uWord wIndex; + uWord wLength; +} UPACKED usb_device_request_t; + +#define UT_GET_DIR(a) ((a) & 0x80) +#define UT_WRITE 0x00 +#define UT_READ 0x80 + +#define UT_GET_TYPE(a) ((a) & 0x60) +#define UT_STANDARD 0x00 +#define UT_CLASS 0x20 +#define UT_VENDOR 0x40 + +#define UT_GET_RECIPIENT(a) ((a) & 0x1f) +#define UT_DEVICE 0x00 +#define UT_INTERFACE 0x01 +#define UT_ENDPOINT 0x02 +#define UT_OTHER 0x03 + +#define UT_READ_DEVICE (UT_READ | UT_STANDARD | UT_DEVICE) +#define UT_READ_INTERFACE (UT_READ | UT_STANDARD | UT_INTERFACE) +#define UT_READ_ENDPOINT (UT_READ | UT_STANDARD | UT_ENDPOINT) +#define UT_WRITE_DEVICE (UT_WRITE | UT_STANDARD | UT_DEVICE) +#define UT_WRITE_INTERFACE (UT_WRITE | UT_STANDARD | UT_INTERFACE) +#define UT_WRITE_ENDPOINT (UT_WRITE | UT_STANDARD | UT_ENDPOINT) +#define UT_READ_CLASS_DEVICE (UT_READ | UT_CLASS | UT_DEVICE) +#define UT_READ_CLASS_INTERFACE (UT_READ | UT_CLASS | UT_INTERFACE) +#define UT_READ_CLASS_OTHER (UT_READ | UT_CLASS | UT_OTHER) +#define UT_READ_CLASS_ENDPOINT (UT_READ | UT_CLASS | UT_ENDPOINT) +#define UT_WRITE_CLASS_DEVICE (UT_WRITE | UT_CLASS | UT_DEVICE) +#define UT_WRITE_CLASS_INTERFACE (UT_WRITE | UT_CLASS | UT_INTERFACE) +#define UT_WRITE_CLASS_OTHER (UT_WRITE | UT_CLASS | UT_OTHER) +#define UT_WRITE_CLASS_ENDPOINT (UT_WRITE | UT_CLASS | UT_ENDPOINT) +#define UT_READ_VENDOR_DEVICE (UT_READ | UT_VENDOR | UT_DEVICE) +#define UT_READ_VENDOR_INTERFACE (UT_READ | UT_VENDOR | UT_INTERFACE) +#define UT_READ_VENDOR_OTHER (UT_READ | UT_VENDOR | UT_OTHER) +#define UT_READ_VENDOR_ENDPOINT (UT_READ | UT_VENDOR | UT_ENDPOINT) +#define UT_WRITE_VENDOR_DEVICE (UT_WRITE | UT_VENDOR | UT_DEVICE) +#define UT_WRITE_VENDOR_INTERFACE (UT_WRITE | UT_VENDOR | UT_INTERFACE) +#define UT_WRITE_VENDOR_OTHER (UT_WRITE | UT_VENDOR | UT_OTHER) +#define UT_WRITE_VENDOR_ENDPOINT (UT_WRITE | UT_VENDOR | UT_ENDPOINT) + +/* Requests */ +#define UR_GET_STATUS 0x00 +#define USTAT_STANDARD_STATUS 0x00 +#define WUSTAT_WUSB_FEATURE 0x01 +#define WUSTAT_CHANNEL_INFO 0x02 +#define WUSTAT_RECEIVED_DATA 0x03 +#define WUSTAT_MAS_AVAILABILITY 0x04 +#define WUSTAT_CURRENT_TRANSMIT_POWER 0x05 +#define UR_CLEAR_FEATURE 0x01 +#define UR_SET_FEATURE 0x03 +#define UR_SET_AND_TEST_FEATURE 0x0c +#define UR_SET_ADDRESS 0x05 +#define UR_GET_DESCRIPTOR 0x06 +#define UDESC_DEVICE 0x01 +#define UDESC_CONFIG 0x02 +#define UDESC_STRING 0x03 +#define UDESC_INTERFACE 0x04 +#define UDESC_ENDPOINT 0x05 +#define UDESC_DEVICE_QUALIFIER 0x06 +#define UDESC_OTHER_SPEED_CONFIGURATION 0x07 +#define UDESC_INTERFACE_POWER 0x08 +#define UDESC_OTG 0x09 +#define WUDESC_SECURITY 0x0c +#define WUDESC_KEY 0x0d +#define WUD_GET_KEY_INDEX(_wValue_) ((_wValue_) & 0xf) +#define WUD_GET_KEY_TYPE(_wValue_) (((_wValue_) & 0x30) >> 4) +#define WUD_KEY_TYPE_ASSOC 0x01 +#define WUD_KEY_TYPE_GTK 0x02 +#define WUD_GET_KEY_ORIGIN(_wValue_) (((_wValue_) & 0x40) >> 6) +#define WUD_KEY_ORIGIN_HOST 0x00 +#define WUD_KEY_ORIGIN_DEVICE 0x01 +#define WUDESC_ENCRYPTION_TYPE 0x0e +#define WUDESC_BOS 0x0f +#define WUDESC_DEVICE_CAPABILITY 0x10 +#define WUDESC_WIRELESS_ENDPOINT_COMPANION 0x11 +#define UDESC_CS_DEVICE 0x21 /* class specific */ +#define UDESC_CS_CONFIG 0x22 +#define UDESC_CS_STRING 0x23 +#define UDESC_CS_INTERFACE 0x24 +#define UDESC_CS_ENDPOINT 0x25 +#define UDESC_HUB 0x29 +#define UR_SET_DESCRIPTOR 0x07 +#define UR_GET_CONFIG 0x08 +#define UR_SET_CONFIG 0x09 +#define UR_GET_INTERFACE 0x0a +#define UR_SET_INTERFACE 0x0b +#define UR_SYNCH_FRAME 0x0c +#define WUR_SET_ENCRYPTION 0x0d +#define WUR_GET_ENCRYPTION 0x0e +#define WUR_SET_HANDSHAKE 0x0f +#define WUR_GET_HANDSHAKE 0x10 +#define WUR_SET_CONNECTION 0x11 +#define WUR_SET_SECURITY_DATA 0x12 +#define WUR_GET_SECURITY_DATA 0x13 +#define WUR_SET_WUSB_DATA 0x14 +#define WUDATA_DRPIE_INFO 0x01 +#define WUDATA_TRANSMIT_DATA 0x02 +#define WUDATA_TRANSMIT_PARAMS 0x03 +#define WUDATA_RECEIVE_PARAMS 0x04 +#define WUDATA_TRANSMIT_POWER 0x05 +#define WUR_LOOPBACK_DATA_WRITE 0x15 +#define WUR_LOOPBACK_DATA_READ 0x16 +#define WUR_SET_INTERFACE_DS 0x17 + +/* Feature numbers */ +#define UF_ENDPOINT_HALT 0 +#define UF_DEVICE_REMOTE_WAKEUP 1 +#define UF_TEST_MODE 2 +#define UF_DEVICE_B_HNP_ENABLE 3 +#define UF_DEVICE_A_HNP_SUPPORT 4 +#define UF_DEVICE_A_ALT_HNP_SUPPORT 5 +#define WUF_WUSB 3 +#define WUF_TX_DRPIE 0x0 +#define WUF_DEV_XMIT_PACKET 0x1 +#define WUF_COUNT_PACKETS 0x2 +#define WUF_CAPTURE_PACKETS 0x3 + +/* Class requests from the USB 2.0 hub spec, table 11-15 */ +#define UCR_CLEAR_HUB_FEATURE (0x2000 | UR_CLEAR_FEATURE) +#define UCR_CLEAR_PORT_FEATURE (0x2300 | UR_CLEAR_FEATURE) +#define UCR_GET_HUB_DESCRIPTOR (0xa000 | UR_GET_DESCRIPTOR) +#define UCR_GET_HUB_STATUS (0xa000 | UR_GET_STATUS) +#define UCR_GET_PORT_STATUS (0xa300 | UR_GET_STATUS) +#define UCR_SET_HUB_FEATURE (0x2000 | UR_SET_FEATURE) +#define UCR_SET_PORT_FEATURE (0x2300 | UR_SET_FEATURE) +#define UCR_SET_AND_TEST_PORT_FEATURE (0xa300 | UR_SET_AND_TEST_FEATURE) + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; +} UPACKED usb_descriptor_t; + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; +#define UD_USB_2_0 0x0200 +#define UD_IS_USB2(d) (UGETW((d)->bcdUSB) >= UD_USB_2_0) + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize; + /* The fields below are not part of the initial descriptor. */ + uWord idVendor; + uWord idProduct; + uWord bcdDevice; + uByte iManufacturer; + uByte iProduct; + uByte iSerialNumber; + uByte bNumConfigurations; +} UPACKED usb_device_descriptor_t; +#define USB_DEVICE_DESCRIPTOR_SIZE 18 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uWord wTotalLength; + uByte bNumInterface; + uByte bConfigurationValue; + uByte iConfiguration; + uByte bmAttributes; +#define UC_BUS_POWERED 0x80 +#define UC_SELF_POWERED 0x40 +#define UC_REMOTE_WAKEUP 0x20 + uByte bMaxPower; /* max current in 2 mA units */ +#define UC_POWER_FACTOR 2 +} UPACKED usb_config_descriptor_t; +#define USB_CONFIG_DESCRIPTOR_SIZE 9 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bInterfaceNumber; + uByte bAlternateSetting; + uByte bNumEndpoints; + uByte bInterfaceClass; + uByte bInterfaceSubClass; + uByte bInterfaceProtocol; + uByte iInterface; +} UPACKED usb_interface_descriptor_t; +#define USB_INTERFACE_DESCRIPTOR_SIZE 9 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bEndpointAddress; +#define UE_GET_DIR(a) ((a) & 0x80) +#define UE_SET_DIR(a,d) ((a) | (((d)&1) << 7)) +#define UE_DIR_IN 0x80 +#define UE_DIR_OUT 0x00 +#define UE_ADDR 0x0f +#define UE_GET_ADDR(a) ((a) & UE_ADDR) + uByte bmAttributes; +#define UE_XFERTYPE 0x03 +#define UE_CONTROL 0x00 +#define UE_ISOCHRONOUS 0x01 +#define UE_BULK 0x02 +#define UE_INTERRUPT 0x03 +#define UE_GET_XFERTYPE(a) ((a) & UE_XFERTYPE) +#define UE_ISO_TYPE 0x0c +#define UE_ISO_ASYNC 0x04 +#define UE_ISO_ADAPT 0x08 +#define UE_ISO_SYNC 0x0c +#define UE_GET_ISO_TYPE(a) ((a) & UE_ISO_TYPE) + uWord wMaxPacketSize; + uByte bInterval; +} UPACKED usb_endpoint_descriptor_t; +#define USB_ENDPOINT_DESCRIPTOR_SIZE 7 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uWord bString[127]; +} UPACKED usb_string_descriptor_t; +#define USB_MAX_STRING_LEN 128 +#define USB_LANGUAGE_TABLE 0 /* # of the string language id table */ + +/* Hub specific request */ +#define UR_GET_BUS_STATE 0x02 +#define UR_CLEAR_TT_BUFFER 0x08 +#define UR_RESET_TT 0x09 +#define UR_GET_TT_STATE 0x0a +#define UR_STOP_TT 0x0b + +/* Hub features */ +#define UHF_C_HUB_LOCAL_POWER 0 +#define UHF_C_HUB_OVER_CURRENT 1 +#define UHF_PORT_CONNECTION 0 +#define UHF_PORT_ENABLE 1 +#define UHF_PORT_SUSPEND 2 +#define UHF_PORT_OVER_CURRENT 3 +#define UHF_PORT_RESET 4 +#define UHF_PORT_L1 5 +#define UHF_PORT_POWER 8 +#define UHF_PORT_LOW_SPEED 9 +#define UHF_PORT_HIGH_SPEED 10 +#define UHF_C_PORT_CONNECTION 16 +#define UHF_C_PORT_ENABLE 17 +#define UHF_C_PORT_SUSPEND 18 +#define UHF_C_PORT_OVER_CURRENT 19 +#define UHF_C_PORT_RESET 20 +#define UHF_C_PORT_L1 23 +#define UHF_PORT_TEST 21 +#define UHF_PORT_INDICATOR 22 + +typedef struct { + uByte bDescLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; +#define UHD_PWR 0x0003 +#define UHD_PWR_GANGED 0x0000 +#define UHD_PWR_INDIVIDUAL 0x0001 +#define UHD_PWR_NO_SWITCH 0x0002 +#define UHD_COMPOUND 0x0004 +#define UHD_OC 0x0018 +#define UHD_OC_GLOBAL 0x0000 +#define UHD_OC_INDIVIDUAL 0x0008 +#define UHD_OC_NONE 0x0010 +#define UHD_TT_THINK 0x0060 +#define UHD_TT_THINK_8 0x0000 +#define UHD_TT_THINK_16 0x0020 +#define UHD_TT_THINK_24 0x0040 +#define UHD_TT_THINK_32 0x0060 +#define UHD_PORT_IND 0x0080 + uByte bPwrOn2PwrGood; /* delay in 2 ms units */ +#define UHD_PWRON_FACTOR 2 + uByte bHubContrCurrent; + uByte DeviceRemovable[32]; /* max 255 ports */ +#define UHD_NOT_REMOV(desc, i) \ + (((desc)->DeviceRemovable[(i)/8] >> ((i) % 8)) & 1) + /* deprecated */ uByte PortPowerCtrlMask[1]; +} UPACKED usb_hub_descriptor_t; +#define USB_HUB_DESCRIPTOR_SIZE 9 /* includes deprecated PortPowerCtrlMask */ + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize0; + uByte bNumConfigurations; + uByte bReserved; +} UPACKED usb_device_qualifier_t; +#define USB_DEVICE_QUALIFIER_SIZE 10 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bmAttributes; +#define UOTG_SRP 0x01 +#define UOTG_HNP 0x02 +} UPACKED usb_otg_descriptor_t; + +/* OTG feature selectors */ +#define UOTG_B_HNP_ENABLE 3 +#define UOTG_A_HNP_SUPPORT 4 +#define UOTG_A_ALT_HNP_SUPPORT 5 + +typedef struct { + uWord wStatus; +/* Device status flags */ +#define UDS_SELF_POWERED 0x0001 +#define UDS_REMOTE_WAKEUP 0x0002 +/* Endpoint status flags */ +#define UES_HALT 0x0001 +} UPACKED usb_status_t; + +typedef struct { + uWord wHubStatus; +#define UHS_LOCAL_POWER 0x0001 +#define UHS_OVER_CURRENT 0x0002 + uWord wHubChange; +} UPACKED usb_hub_status_t; + +typedef struct { + uWord wPortStatus; +#define UPS_CURRENT_CONNECT_STATUS 0x0001 +#define UPS_PORT_ENABLED 0x0002 +#define UPS_SUSPEND 0x0004 +#define UPS_OVERCURRENT_INDICATOR 0x0008 +#define UPS_RESET 0x0010 +#define UPS_PORT_POWER 0x0100 +#define UPS_LOW_SPEED 0x0200 +#define UPS_HIGH_SPEED 0x0400 +#define UPS_PORT_TEST 0x0800 +#define UPS_PORT_INDICATOR 0x1000 + uWord wPortChange; +#define UPS_C_CONNECT_STATUS 0x0001 +#define UPS_C_PORT_ENABLED 0x0002 +#define UPS_C_SUSPEND 0x0004 +#define UPS_C_OVERCURRENT_INDICATOR 0x0008 +#define UPS_C_PORT_RESET 0x0010 +} UPACKED usb_port_status_t; + +/* Device class codes */ +#define UDCLASS_IN_INTERFACE 0x00 +#define UDCLASS_COMM 0x02 +#define UDCLASS_HUB 0x09 +#define UDSUBCLASS_HUB 0x00 +#define UDPROTO_FSHUB 0x00 +#define UDPROTO_HSHUBSTT 0x01 +#define UDPROTO_HSHUBMTT 0x02 +#define UDCLASS_DIAGNOSTIC 0xdc +#define UDCLASS_WIRELESS 0xe0 +#define UDSUBCLASS_RF 0x01 +#define UDPROTO_BLUETOOTH 0x01 +#define UDCLASS_VENDOR 0xff + +/* Interface class codes */ +#define UICLASS_UNSPEC 0x00 + +#define UICLASS_AUDIO 0x01 +#define UISUBCLASS_AUDIOCONTROL 1 +#define UISUBCLASS_AUDIOSTREAM 2 +#define UISUBCLASS_MIDISTREAM 3 + +#define UICLASS_CDC 0x02 /* communication */ +#define UISUBCLASS_DIRECT_LINE_CONTROL_MODEL 1 +#define UISUBCLASS_ABSTRACT_CONTROL_MODEL 2 +#define UISUBCLASS_TELEPHONE_CONTROL_MODEL 3 +#define UISUBCLASS_MULTICHANNEL_CONTROL_MODEL 4 +#define UISUBCLASS_CAPI_CONTROLMODEL 5 +#define UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL 6 +#define UISUBCLASS_ATM_NETWORKING_CONTROL_MODEL 7 +#define UIPROTO_CDC_AT 1 + +#define UICLASS_HID 0x03 +#define UISUBCLASS_BOOT 1 +#define UIPROTO_BOOT_KEYBOARD 1 + +#define UICLASS_PHYSICAL 0x05 + +#define UICLASS_IMAGE 0x06 + +#define UICLASS_PRINTER 0x07 +#define UISUBCLASS_PRINTER 1 +#define UIPROTO_PRINTER_UNI 1 +#define UIPROTO_PRINTER_BI 2 +#define UIPROTO_PRINTER_1284 3 + +#define UICLASS_MASS 0x08 +#define UISUBCLASS_RBC 1 +#define UISUBCLASS_SFF8020I 2 +#define UISUBCLASS_QIC157 3 +#define UISUBCLASS_UFI 4 +#define UISUBCLASS_SFF8070I 5 +#define UISUBCLASS_SCSI 6 +#define UIPROTO_MASS_CBI_I 0 +#define UIPROTO_MASS_CBI 1 +#define UIPROTO_MASS_BBB_OLD 2 /* Not in the spec anymore */ +#define UIPROTO_MASS_BBB 80 /* 'P' for the Iomega Zip drive */ + +#define UICLASS_HUB 0x09 +#define UISUBCLASS_HUB 0 +#define UIPROTO_FSHUB 0 +#define UIPROTO_HSHUBSTT 0 /* Yes, same as previous */ +#define UIPROTO_HSHUBMTT 1 + +#define UICLASS_CDC_DATA 0x0a +#define UISUBCLASS_DATA 0 +#define UIPROTO_DATA_ISDNBRI 0x30 /* Physical iface */ +#define UIPROTO_DATA_HDLC 0x31 /* HDLC */ +#define UIPROTO_DATA_TRANSPARENT 0x32 /* Transparent */ +#define UIPROTO_DATA_Q921M 0x50 /* Management for Q921 */ +#define UIPROTO_DATA_Q921 0x51 /* Data for Q921 */ +#define UIPROTO_DATA_Q921TM 0x52 /* TEI multiplexer for Q921 */ +#define UIPROTO_DATA_V42BIS 0x90 /* Data compression */ +#define UIPROTO_DATA_Q931 0x91 /* Euro-ISDN */ +#define UIPROTO_DATA_V120 0x92 /* V.24 rate adaption */ +#define UIPROTO_DATA_CAPI 0x93 /* CAPI 2.0 commands */ +#define UIPROTO_DATA_HOST_BASED 0xfd /* Host based driver */ +#define UIPROTO_DATA_PUF 0xfe /* see Prot. Unit Func. Desc.*/ +#define UIPROTO_DATA_VENDOR 0xff /* Vendor specific */ + +#define UICLASS_SMARTCARD 0x0b + +/*#define UICLASS_FIRM_UPD 0x0c*/ + +#define UICLASS_SECURITY 0x0d + +#define UICLASS_DIAGNOSTIC 0xdc + +#define UICLASS_WIRELESS 0xe0 +#define UISUBCLASS_RF 0x01 +#define UIPROTO_BLUETOOTH 0x01 + +#define UICLASS_APPL_SPEC 0xfe +#define UISUBCLASS_FIRMWARE_DOWNLOAD 1 +#define UISUBCLASS_IRDA 2 +#define UIPROTO_IRDA 0 + +#define UICLASS_VENDOR 0xff + + +#define USB_HUB_MAX_DEPTH 5 + +/* + * Minimum time a device needs to be powered down to go through + * a power cycle. XXX Are these time in the spec? + */ +#define USB_POWER_DOWN_TIME 200 /* ms */ +#define USB_PORT_POWER_DOWN_TIME 100 /* ms */ + +#if 0 +/* These are the values from the spec. */ +#define USB_PORT_RESET_DELAY 10 /* ms */ +#define USB_PORT_ROOT_RESET_DELAY 50 /* ms */ +#define USB_PORT_RESET_RECOVERY 10 /* ms */ +#define USB_PORT_POWERUP_DELAY 100 /* ms */ +#define USB_SET_ADDRESS_SETTLE 2 /* ms */ +#define USB_RESUME_DELAY (20*5) /* ms */ +#define USB_RESUME_WAIT 10 /* ms */ +#define USB_RESUME_RECOVERY 10 /* ms */ +#define USB_EXTRA_POWER_UP_TIME 0 /* ms */ +#else +/* Allow for marginal (i.e. non-conforming) devices. */ +#define USB_PORT_RESET_DELAY 50 /* ms */ +#define USB_PORT_ROOT_RESET_DELAY 250 /* ms */ +#define USB_PORT_RESET_RECOVERY 250 /* ms */ +#define USB_PORT_POWERUP_DELAY 300 /* ms */ +#define USB_SET_ADDRESS_SETTLE 10 /* ms */ +#define USB_RESUME_DELAY (50*5) /* ms */ +#define USB_RESUME_WAIT 50 /* ms */ +#define USB_RESUME_RECOVERY 50 /* ms */ +#define USB_EXTRA_POWER_UP_TIME 20 /* ms */ +#endif + +#define USB_MIN_POWER 100 /* mA */ +#define USB_MAX_POWER 500 /* mA */ + +#define USB_BUS_RESET_DELAY 100 /* ms XXX?*/ + + +#define USB_UNCONFIG_NO 0 +#define USB_UNCONFIG_INDEX (-1) + +/*** ioctl() related stuff ***/ + +struct usb_ctl_request { + int ucr_addr; + usb_device_request_t ucr_request; + void *ucr_data; + int ucr_flags; +#define USBD_SHORT_XFER_OK 0x04 /* allow short reads */ + int ucr_actlen; /* actual length transferred */ +}; + +struct usb_alt_interface { + int uai_config_index; + int uai_interface_index; + int uai_alt_no; +}; + +#define USB_CURRENT_CONFIG_INDEX (-1) +#define USB_CURRENT_ALT_INDEX (-1) + +struct usb_config_desc { + int ucd_config_index; + usb_config_descriptor_t ucd_desc; +}; + +struct usb_interface_desc { + int uid_config_index; + int uid_interface_index; + int uid_alt_index; + usb_interface_descriptor_t uid_desc; +}; + +struct usb_endpoint_desc { + int ued_config_index; + int ued_interface_index; + int ued_alt_index; + int ued_endpoint_index; + usb_endpoint_descriptor_t ued_desc; +}; + +struct usb_full_desc { + int ufd_config_index; + u_int ufd_size; + u_char *ufd_data; +}; + +struct usb_string_desc { + int usd_string_index; + int usd_language_id; + usb_string_descriptor_t usd_desc; +}; + +struct usb_ctl_report_desc { + int ucrd_size; + u_char ucrd_data[1024]; /* filled data size will vary */ +}; + +typedef struct { u_int32_t cookie; } usb_event_cookie_t; + +#define USB_MAX_DEVNAMES 4 +#define USB_MAX_DEVNAMELEN 16 +struct usb_device_info { + u_int8_t udi_bus; + u_int8_t udi_addr; /* device address */ + usb_event_cookie_t udi_cookie; + char udi_product[USB_MAX_STRING_LEN]; + char udi_vendor[USB_MAX_STRING_LEN]; + char udi_release[8]; + u_int16_t udi_productNo; + u_int16_t udi_vendorNo; + u_int16_t udi_releaseNo; + u_int8_t udi_class; + u_int8_t udi_subclass; + u_int8_t udi_protocol; + u_int8_t udi_config; + u_int8_t udi_speed; +//#define USB_SPEED_LOW 1 +//#define USB_SPEED_FULL 2 +//#define USB_SPEED_HIGH 3 + int udi_power; /* power consumption in mA, 0 if selfpowered */ + int udi_nports; + char udi_devnames[USB_MAX_DEVNAMES][USB_MAX_DEVNAMELEN]; + u_int8_t udi_ports[16];/* hub only: addresses of devices on ports */ +#define USB_PORT_ENABLED 0xff +#define USB_PORT_SUSPENDED 0xfe +#define USB_PORT_POWERED 0xfd +#define USB_PORT_DISABLED 0xfc +}; + +struct usb_ctl_report { + int ucr_report; + u_char ucr_data[1024]; /* filled data size will vary */ +}; + +struct usb_device_stats { + u_long uds_requests[4]; /* indexed by transfer type UE_* */ +}; + + + + +#define WUSB_MIN_IE 0x80 +#define WUSB_WCTA_IE 0x80 +#define WUSB_WCONNECTACK_IE 0x81 +#define WUSB_WHOSTINFO_IE 0x82 +#define WUHI_GET_CA(_bmAttributes_) ((_bmAttributes_) & 0x3) +#define WUHI_CA_RECONN 0x00 +#define WUHI_CA_LIMITED 0x01 +#define WUHI_CA_ALL 0x03 +#define WUHI_GET_MLSI(_bmAttributes_) (((_bmAttributes_) & 0x38) >> 3) +#define WUSB_WCHCHANGEANNOUNCE_IE 0x83 +#define WUSB_WDEV_DISCONNECT_IE 0x84 +#define WUSB_WHOST_DISCONNECT_IE 0x85 +#define WUSB_WRELEASE_CHANNEL_IE 0x86 +#define WUSB_WWORK_IE 0x87 +#define WUSB_WCHANNEL_STOP_IE 0x88 +#define WUSB_WDEV_KEEPALIVE_IE 0x89 +#define WUSB_WISOCH_DISCARD_IE 0x8A +#define WUSB_WRESETDEVICE_IE 0x8B +#define WUSB_WXMIT_PACKET_ADJUST_IE 0x8C +#define WUSB_MAX_IE 0x8C + +/* Device Notification Types */ + +#define WUSB_DN_MIN 0x01 +#define WUSB_DN_CONNECT 0x01 +# define WUSB_DA_OLDCONN 0x00 +# define WUSB_DA_NEWCONN 0x01 +# define WUSB_DA_SELF_BEACON 0x02 +# define WUSB_DA_DIR_BEACON 0x04 +# define WUSB_DA_NO_BEACON 0x06 +#define WUSB_DN_DISCONNECT 0x02 +#define WUSB_DN_EPRDY 0x03 +#define WUSB_DN_MASAVAILCHANGED 0x04 +#define WUSB_DN_REMOTEWAKEUP 0x05 +#define WUSB_DN_SLEEP 0x06 +#define WUSB_DN_ALIVE 0x07 +#define WUSB_DN_MAX 0x07 + + +/* WUSB Handshake Data. Used during the SET/GET HANDSHAKE requests */ +typedef struct wusb_hndshk_data { + uint8_t bMessageNumber; + uint8_t bStatus; + uint8_t tTKID[3]; + uint8_t bReserved; + uint8_t CDID[16]; + uint8_t Nonce[16]; + uint8_t MIC[8]; +} UPACKED wusb_hndshk_data_t; +#define WUSB_HANDSHAKE_LEN_FOR_MIC 38 + +/* WUSB Connection Context */ +typedef struct wusb_conn_context { + uint8_t CHID [16]; + uint8_t CDID [16]; + uint8_t CK [16]; +} UPACKED wusb_conn_context_t; + +/* WUSB Security Descriptor */ +typedef struct wusb_security_desc { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumEncryptionTypes; +} UPACKED wusb_security_desc_t; + +/* WUSB Encryption Type Descriptor */ +typedef struct wusb_encrypt_type_desc { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bEncryptionType; +#define WUETD_UNSECURE 0 +#define WUETD_WIRED 1 +#define WUETD_CCM_1 2 +#define WUETD_RSA_1 3 + + uint8_t bEncryptionValue; + uint8_t bAuthKeyIndex; +} UPACKED wusb_encrypt_type_desc_t; + +/* WUSB Key Descriptor */ +typedef struct wusb_key_desc { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t tTKID[3]; + uint8_t bReserved; + uint8_t KeyData[1]; /* variable length */ +} UPACKED wusb_key_desc_t; + +/* WUSB BOS Descriptor (Binary device Object Store) */ +typedef struct wusb_bos_desc { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumDeviceCaps; +} UPACKED wusb_bos_desc_t; + + +/* Device Capability Type Codes */ +#define WUSB_DEVICE_CAPABILITY_WIRELESS_USB 0x01 + +/* Device Capability Descriptor */ +typedef struct wusb_dev_cap_desc { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDevCapabilityType; + uint8_t caps[1]; /* Variable length */ +} UPACKED wusb_dev_cap_desc_t; + +/* Device Capability Descriptor */ +typedef struct wusb_dev_cap_uwb_desc { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDevCapabilityType; + uint8_t bmAttributes; + uint16_t wPHYRates; /* Bitmap */ + uint8_t bmTFITXPowerInfo; + uint8_t bmFFITXPowerInfo; + uint16_t bmBandGroup; + uint8_t bReserved; +} UPACKED wusb_dev_cap_uwb_desc_t; + +/* Wireless USB Endpoint Companion Descriptor */ +typedef struct wusb_endpoint_companion_desc { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bMaxBurst; + uint8_t bMaxSequence; + uint16_t wMaxStreamDelay; + uint16_t wOverTheAirPacketSize; + uint8_t bOverTheAirInterval; + uint8_t bmCompAttributes; +} UPACKED wusb_endpoint_companion_desc_t; + + +/* Wireless USB Numeric Association M1 Data Structure */ +typedef struct wusb_m1_data { + uint8_t version; + uint16_t langId; + uint8_t deviceFriendlyNameLength; + uint8_t sha_256_m3[32]; + uint8_t deviceFriendlyName[256]; +} UPACKED wusb_m1_data_t; + +typedef struct wusb_m2_data { + uint8_t version; + uint16_t langId; + uint8_t hostFriendlyNameLength; + uint8_t pkh[384]; + uint8_t hostFriendlyName[256]; +} UPACKED wusb_m2_data_t; + +typedef struct wusb_m3_data { + uint8_t pkd[384]; + uint8_t nd; +} UPACKED wusb_m3_data_t; + +typedef struct wusb_m4_data { + uint32_t _attributeTypeIdAndLength_1; + uint16_t associationTypeId; + + uint32_t _attributeTypeIdAndLength_2; + uint16_t associationSubTypeId; + + uint32_t _attributeTypeIdAndLength_3; + uint32_t length; + + uint32_t _attributeTypeIdAndLength_4; + uint32_t associationStatus; + + uint32_t _attributeTypeIdAndLength_5; + uint8_t chid[16]; + + uint32_t _attributeTypeIdAndLength_6; + uint8_t cdid[16]; + + uint32_t _attributeTypeIdAndLength_7; + uint8_t bandGroups[2]; +} UPACKED wusb_m4_data_t; + + + + +#endif /* _USB_H_ */ diff --git a/drivers/usb/gadget/dwc_otg/usb_hw.c b/drivers/usb/gadget/dwc_otg/usb_hw.c new file mode 100644 index 00000000..d456f5a9 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/usb_hw.c @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //__raw_writel +#include +#include +#include "usb_hw.h" +#ifdef CONFIG_MFD_SM5504 +#include +#endif + +#if (defined(CONFIG_ARCH_SCX35)||defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8)) +#define USB_LDO_NAME "vddusb" +#define USB_CLK_NAME "clk_usb_ref" +#else +#define USB_LDO_NAME "V_USB" +#define USB_CLK_NAME "clk_usb_ref" +#endif + +static int gpio_vbus =0xffffffff ; +static int gpio_id=0xffffffff; +static uint32_t tune_from_uboot = 0x44073e33; +static struct regulator *usb_regulator; + +#define GPIO_INVALID 0xffffffff + +extern int in_calibration(void); + +static int __init usb_phy_tune_get(char *str) +{ + if (str != NULL) { + sscanf(&str[0], "%x", &tune_from_uboot); + } + if(tune_from_uboot == 0) + { + tune_from_uboot = 0x44073e33; + } + pr_info("Usb_hw.c: [%s]usb phy tune from uboot: 0x%x\n", __FUNCTION__, tune_from_uboot); + return 1; +} +__setup("usb_phy_tune=", usb_phy_tune_get); + + +static void usb_ldo_switch(int is_on) +{ + + if(!IS_ERR_OR_NULL(usb_regulator)){ + if(is_on){ + regulator_set_voltage(usb_regulator,3300000,3300000); + regulator_enable(usb_regulator); + }else{ + regulator_disable(usb_regulator); + } + } +} +#if defined(CONFIG_ARCH_SC8825) +static int usb_clk_status = 0; +static int usb_clock_enable(int is_on) +{ + struct clk *usb_clock = NULL; + + usb_clock = clk_get(NULL,USB_CLK_NAME); + if (usb_clock) { + if (is_on) { + if(usb_clk_status == 0){ + clk_enable(usb_clock); + usb_clk_status = 1; + } + } else { + if(usb_clk_status == 1){ + clk_disable(usb_clock); + usb_clk_status = 0; + } + } + } + return 0; +} +#endif + +#if (defined(CONFIG_ARCH_SCX35)||defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8)) +static void usb_enable_module(int en) +{ + if (en){ + sci_glb_clr(REG_AON_APB_PWR_CTRL,BIT(0)); + sci_glb_set(REG_AP_AHB_AHB_EB,BIT_USB_EB); + }else { + sci_glb_set(REG_AON_APB_PWR_CTRL,BIT(0)); + sci_glb_clr(REG_AP_AHB_AHB_EB,BIT_USB_EB); + } +} + +#else +static void usb_enable_module(int en) +{ + if (en){ + usb_clock_enable(1); + sprd_greg_clear_bits(REG_TYPE_GLOBAL,BIT(9),GR_CLK_GEN5); + }else { + usb_clock_enable(0); + sprd_greg_set_bits(REG_TYPE_GLOBAL,BIT(9),GR_CLK_GEN5); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,AHB_CTL0_USBD_EN,AHB_CTL0); + } +} +#endif +void usb_phy_init(struct platform_device *_dev) +{ +#ifdef CONFIG_USB_CORE_IP_293A +#if (defined(CONFIG_ARCH_SCX35)||defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8)) + /*shark and dolphin are the same value with SPRD ref phone*/ +#ifdef CONFIG_OF + struct device_node *np = _dev->dev.of_node; + + if (of_property_read_u32(np, "tune_value", &tune_from_uboot)) + { + pr_info("read tune_value error\n"); + return -ENODEV; + } + pr_info("Usb_hw.c: [%s]usb phy tune from uboot: 0x%x\n", __FUNCTION__, tune_from_uboot); +#endif + +#ifdef CONFIG_SC_FPGA +__raw_writel(0x44073e33,REG_AP_AHB_OTG_PHY_TUNE); +#else +#if defined(CONFIG_ARCH_SCX35L) + __raw_writel(tune_from_uboot,REG_AP_AHB_OTG_PHY_TUNE); +#elif defined(CONFIG_ARCH_SCX20) + __raw_writel(tune_from_uboot,REG_AP_APB_USB_CTRL0); +#else + __raw_writel(tune_from_uboot,REG_AP_APB_USB_PHY_TUNE); +#endif +#endif + + //sci_glb_set(REG_AP_APB_USB_PHY_TUNE,BIT(9)|BIT(10)|BIT(11)|BIT(20)); +#else + /* + * tiger PHY reg is different with previous , + *7710 has the same core IP with tiger,but PHY reg also diff + */ + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(11), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(10), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(20), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(9), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(8), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(13), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(12), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(15)|BIT(14), USB_PHY_CTRL); +#endif +#else + if (sprd_greg_read(REG_TYPE_AHB_GLOBAL,CHIP_ID) == CHIP_ID_8810S){ + /*SMIC chip id == 0x88100001*/ + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(3)|BIT(2), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(1) | BIT(0), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(9), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(16), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(17), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(13), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL, BIT(12), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(15)|BIT(14), USB_PHY_CTRL); + sprd_greg_write(REG_TYPE_AHB_GLOBAL,0x28,USB_SPR_REG); + }else{ + /* + * config usb phy controller + */ + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(8), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(17), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(16), USB_PHY_CTRL); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT(13)|BIT(12), USB_PHY_CTRL); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT(15)|BIT(14), USB_PHY_CTRL); + } +#endif + usb_regulator = regulator_get(NULL,USB_LDO_NAME); + if(!IS_ERR_OR_NULL(usb_regulator)) + regulator_set_voltage(usb_regulator,3600000,3600000); +} + +void usb_phy_ahb_rst(void) +{ +#if defined(CONFIG_ARCH_SCX35) +#if defined(CONFIG_ARCH_SCX35L) + sci_glb_set(REG_AP_AHB_AHB_RST,BIT(6)); + mdelay(3); + sci_glb_clr(REG_AP_AHB_AHB_RST,BIT(6)); + mdelay(3); + sci_glb_set(REG_AP_AHB_OTG_PHY_CTRL,BIT(9)); + pr_info("Usb_hw.c: [%s]usb phy tune : 0x%x\n", __FUNCTION__, tune_from_uboot); +#else + sci_glb_set(REG_AP_AHB_AHB_RST,BIT(7)); + mdelay(3); + sci_glb_clr(REG_AP_AHB_AHB_RST,BIT(7)); + mdelay(3); +#endif +#endif +} +/* +spreadtrum usb phy shuttle has two reset control pins: +utmi_RST:soft reset +utmi_PORN:power on reset +pike chip: +pin ctrl AP_TOP_USB_PHY_RST:0x402A0000[0]<---->utmi_PORN +ahb reg REG_AP_AHB_AHB_RST[7]<------->utmi_RST +*/ +#ifdef CONFIG_ARCH_SCX20 +void sprd_usb_phy_rst(void) +{ + sci_glb_set(USB_GUSBCFG_REG, BIT(3));/*usc2s8c SPRD phy width:16 bit*/ + sci_glb_clr(AP_TOP_USB_PHY_RST, BIT(0));/*for SPRD phy utmi_PORN*/ + sci_glb_set(REG_AP_AHB_AHB_RST, BIT(7));/*for SPRD phy utmi_rst*/ + mdelay(5); + sci_glb_set(AP_TOP_USB_PHY_RST, BIT(0)); + sci_glb_clr(REG_AP_AHB_AHB_RST, BIT(7)); +} +#endif + +static void usb_startup(void) +{ + uint32_t ahb_reset_mask; + + ahb_reset_mask = BIT(5)|BIT(6)|BIT(7); +#if defined(CONFIG_ARCH_SCX35L) + ahb_reset_mask = ahb_reset_mask >> 1; +#endif + usb_ldo_switch(1); + mdelay(10); + usb_enable_module(1); + mdelay(2); +#if (defined(CONFIG_ARCH_SCX35)||defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8)) +#if defined(CONFIG_ARCH_SCX30G) +#if defined(CONFIG_ARCH_SCX20) + sci_glb_set(REG_AP_AHB_AHB_RST, BIT(5)|BIT(6)); + mdelay(5); + sci_glb_clr(REG_AP_AHB_AHB_RST, BIT(5)|BIT(6)); + + sprd_usb_phy_rst(); +#else + sci_glb_set(REG_AP_AHB_AHB_RST, ahb_reset_mask); + mdelay(5); + sci_glb_clr(REG_AP_AHB_AHB_RST, ahb_reset_mask); +#endif +#else + sci_glb_set(REG_AP_AHB_AHB_RST,BIT_OTG_SOFT_RST|BIT_OTG_UTMI_SOFT_RST|BIT_OTG_PHY_SOFT_RST); + mdelay(5); + sci_glb_clr(REG_AP_AHB_AHB_RST,BIT_OTG_SOFT_RST|BIT_OTG_UTMI_SOFT_RST|BIT_OTG_PHY_SOFT_RST); + sci_glb_set(REG_AP_AHB_AHB_EB,BIT_USB_EB); +#endif +#else + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT_USB_S_HBIGEIDIAN|BIT_USB_M_HBIGENDIAN,REG_AHB_AHB_CTL3); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT_CLK_USB_REF_EN,REG_AHB_AHB_CTL3); + + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT_USBD_UTMI_SOFT_RST|BIT_USBPHY_SOFT_RST ,REG_AHB_SOFT_RST); + mdelay(5); + sprd_greg_clear_bits(REG_TYPE_AHB_GLOBAL,BIT_USBD_UTMI_SOFT_RST|BIT_USBPHY_SOFT_RST,REG_AHB_SOFT_RST); + sprd_greg_set_bits(REG_TYPE_AHB_GLOBAL,BIT_USBD_EB,REG_AHB_AHB_CTL0); +#endif + mdelay(3); +} + +void udc_enable(void) +{ + pr_info("%s \n", __func__); + usb_startup(); +} +void udc_disable(void) +{ + pr_info("%s \n", __func__); + usb_enable_module(0); + usb_ldo_switch(0); +} + + +int usb_alloc_vbus_irq(int gpio) +{ + int irq; + + gpio_request(gpio,"sprd_otg"); + gpio_direction_input(gpio); + irq = gpio_to_irq(gpio); + gpio_vbus= gpio; + + return irq; +} + +void usb_free_vbus_irq(int irq,int gpio) +{ + gpio_free(gpio); +} + +int usb_get_vbus_irq(void) +{ + int value; + + value = gpio_to_irq(gpio_vbus); + + return value; +} +int usb_get_vbus_state(void) +{ + int value; + value = gpio_get_value(gpio_vbus); + return !!value; +} + +void usb_set_vbus_irq_type(int irq, int irq_type) +{ + if (irq_type == VBUS_PLUG_IN) + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH); + else if (irq_type == VBUS_PLUG_OUT) + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); + else { + pr_warning("error type for usb vbus\n"); + } + + return; +} + +#ifndef DWC_DEVICE_ONLY +void charge_pump_set(int gpio, int state) +{ + struct regulator *usb_regulator = NULL; +#define USB_CHG_PUMP_NAME "chg_pump" + + if( GPIO_INVALID != gpio) { + gpio_request(gpio, "chg_ pump"); + gpio_direction_output(gpio, 1); + gpio_set_value(gpio, state); + } else { + if(usb_regulator == NULL) + usb_regulator = regulator_get(NULL, USB_CHG_PUMP_NAME); + if (usb_regulator) { + if (state) + regulator_enable(usb_regulator); + else + regulator_disable(usb_regulator); + regulator_put(usb_regulator); + } + } +} + +int usb_alloc_id_irq(int gpio) +{ + int irq; + + gpio_request(gpio,"USB OTG CABLE"); + gpio_direction_input(gpio); + irq = gpio_to_irq(gpio); +#if defined(CONFIG_MACH_SPX35EA) + /**EA board H/W doesn't support OTG**/ + set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); +#endif + gpio_id=gpio; + + return irq; +} + +void usb_free_id_irq(int irq,int gpio) +{ + gpio_free(gpio); +} + +int usb_get_id_irq(void) +{ + int value; + + value = gpio_to_irq(gpio_id); + + return value; +} + +#ifdef CONFIG_MFD_SM5504 +extern bool sm5504_get_otg_status(void); +#endif +/* + *if otg cable is connected , id state =0 + *as host turn on vbus, in this case shouldn't call this handler + */ +int usb_get_id_state(void) +{ +#ifdef CONFIG_MFD_SM5504 + int value = 0; + value = !sm5504_get_otg_status(); + return value; +#else +#if defined(CONFIG_MACH_SPX35EA) + /**EA board H/W doesn't support OTG**/ + return 1; +#else + int value; + value = gpio_get_value(gpio_id); + return !!value; +#endif +#endif +} + +void usb_set_id_irq_type(int irq, int irq_type) +{ + if (irq_type == OTG_CABLE_PLUG_IN) + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); + else if (irq_type == OTG_CABLE_PLUG_OUT) + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH); + else { + pr_warning("error type for usb vbus\n"); + } + + return; +} +#endif + + +EXPORT_SYMBOL(udc_disable); +EXPORT_SYMBOL(udc_enable); diff --git a/drivers/usb/gadget/dwc_otg/usb_hw.h b/drivers/usb/gadget/dwc_otg/usb_hw.h new file mode 100644 index 00000000..75b74775 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/usb_hw.h @@ -0,0 +1,32 @@ +#ifndef _USB_HW_H +#define _USB_HW_H + +extern int usb_alloc_vbus_irq(int gpio); +extern void usb_free_vbus_irq(int irq,int gpio); +extern int usb_get_vbus_irq(void); +extern int usb_get_vbus_state(void); +extern void charge_pump_set(int gpio,int state); +extern void udc_enable(void); +extern void udc_disable(void); +extern int usb_alloc_id_irq(int gpio); +extern void usb_free_id_irq(int irq,int gpio); +extern int usb_get_id_irq(void); +extern int usb_get_id_state(void); +enum vbus_irq_type { + VBUS_PLUG_IN, + VBUS_PLUG_OUT +}; + +enum id_irq_type { + OTG_CABLE_PLUG_IN, + OTG_CABLE_PLUG_OUT +}; +extern void usb_set_vbus_irq_type(int irq, int irq_type); +extern void usb_set_id_irq_type(int irq, int irq_type); + +#if defined(CONFIG_ARCH_SCX20) +#define AP_TOP_USB_PHY_RST SPRD_PIN_BASE +extern unsigned int USB_GUSBCFG_REG; +#endif + +#endif diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c new file mode 100644 index 00000000..a777f7bd --- /dev/null +++ b/drivers/usb/gadget/epautoconf.c @@ -0,0 +1,380 @@ +/* + * epautoconf.c -- endpoint autoconfiguration for usb gadget drivers + * + * Copyright (C) 2004 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "gadget_chips.h" + +/* + * This should work with endpoints from controller drivers sharing the + * same endpoint naming convention. By example: + * + * - ep1, ep2, ... address is fixed, not direction or type + * - ep1in, ep2out, ... address and direction are fixed, not type + * - ep1-bulk, ep2-bulk, ... address and type are fixed, not direction + * - ep1in-bulk, ep2out-iso, ... all three are fixed + * - ep-* ... no functionality restrictions + * + * Type suffixes are "-bulk", "-iso", or "-int". Numbers are decimal. + * Less common restrictions are implied by gadget_is_*(). + * + * NOTE: each endpoint is unidirectional, as specified by its USB + * descriptor; and isn't specific to a configuration or altsetting. + */ +static int +ep_matches ( + struct usb_gadget *gadget, + struct usb_ep *ep, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ep_comp +) +{ + u8 type; + const char *tmp; + u16 max; + + int num_req_streams = 0; + + /* endpoint already claimed? */ + if (NULL != ep->driver_data) + return 0; + + /* only support ep0 for portable CONTROL traffic */ + type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + if (USB_ENDPOINT_XFER_CONTROL == type) + return 0; + + /* some other naming convention */ + if ('e' != ep->name[0]) + return 0; + + /* type-restriction: "-iso", "-bulk", or "-int". + * direction-restriction: "in", "out". + */ + if ('-' != ep->name[2]) { + tmp = strrchr (ep->name, '-'); + if (tmp) { + switch (type) { + case USB_ENDPOINT_XFER_INT: + /* bulk endpoints handle interrupt transfers, + * except the toggle-quirky iso-synch kind + */ + if ('s' == tmp[2]) // == "-iso" + return 0; + /* for now, avoid PXA "interrupt-in"; + * it's documented as never using DATA1. + */ + if (gadget_is_pxa (gadget) + && 'i' == tmp [1]) + return 0; + break; + case USB_ENDPOINT_XFER_BULK: + if ('b' != tmp[1]) // != "-bulk" + return 0; + break; + case USB_ENDPOINT_XFER_ISOC: + if ('s' != tmp[2]) // != "-iso" + return 0; + } + } else { + tmp = ep->name + strlen (ep->name); + } + + /* direction-restriction: "..in-..", "out-.." */ + tmp--; + if (!isdigit (*tmp)) { + if (desc->bEndpointAddress & USB_DIR_IN) { + if ('n' != *tmp) + return 0; + } else { + if ('t' != *tmp) + return 0; + } + } + } + + /* + * Get the number of required streams from the EP companion + * descriptor and see if the EP matches it + */ + if (usb_endpoint_xfer_bulk(desc)) { + if (ep_comp && gadget->max_speed >= USB_SPEED_SUPER) { + num_req_streams = ep_comp->bmAttributes & 0x1f; + if (num_req_streams > ep->max_streams) + return 0; + } + + } + + /* + * If the protocol driver hasn't yet decided on wMaxPacketSize + * and wants to know the maximum possible, provide the info. + */ + if (desc->wMaxPacketSize == 0) + desc->wMaxPacketSize = cpu_to_le16(ep->maxpacket); + + /* endpoint maxpacket size is an input parameter, except for bulk + * where it's an output parameter representing the full speed limit. + * the usb spec fixes high speed bulk maxpacket at 512 bytes. + */ + max = 0x7ff & usb_endpoint_maxp(desc); + switch (type) { + case USB_ENDPOINT_XFER_INT: + /* INT: limit 64 bytes full speed, 1024 high/super speed */ + if (!gadget_is_dualspeed(gadget) && max > 64) + return 0; + /* FALLTHROUGH */ + + case USB_ENDPOINT_XFER_ISOC: + /* ISO: limit 1023 bytes full speed, 1024 high/super speed */ + if (ep->maxpacket < max) + return 0; + if (!gadget_is_dualspeed(gadget) && max > 1023) + return 0; + + /* BOTH: "high bandwidth" works only at high speed */ + if ((desc->wMaxPacketSize & cpu_to_le16(3<<11))) { + if (!gadget_is_dualspeed(gadget)) + return 0; + /* configure your hardware with enough buffering!! */ + } + break; + } + + /* MATCH!! */ + + /* report address */ + desc->bEndpointAddress &= USB_DIR_IN; + if (isdigit (ep->name [2])) { + u8 num = simple_strtoul (&ep->name [2], NULL, 10); + desc->bEndpointAddress |= num; + } else if (desc->bEndpointAddress & USB_DIR_IN) { + if (++gadget->in_epnum > 15) + return 0; + desc->bEndpointAddress = USB_DIR_IN | gadget->in_epnum; + } else { + if (++gadget->out_epnum > 15) + return 0; + desc->bEndpointAddress |= gadget->out_epnum; + } + + /* report (variable) full speed bulk maxpacket */ + if ((USB_ENDPOINT_XFER_BULK == type) && !ep_comp) { + int size = ep->maxpacket; + + /* min() doesn't work on bitfields with gcc-3.5 */ + if (size > 64) + size = 64; + desc->wMaxPacketSize = cpu_to_le16(size); + } + ep->address = desc->bEndpointAddress; + return 1; +} + +static struct usb_ep * +find_ep (struct usb_gadget *gadget, const char *name) +{ + struct usb_ep *ep; + + list_for_each_entry (ep, &gadget->ep_list, ep_list) { + if (0 == strcmp (ep->name, name)) + return ep; + } + return NULL; +} + +/** + * usb_ep_autoconfig_ss() - choose an endpoint matching the ep + * descriptor and ep companion descriptor + * @gadget: The device to which the endpoint must belong. + * @desc: Endpoint descriptor, with endpoint direction and transfer mode + * initialized. For periodic transfers, the maximum packet + * size must also be initialized. This is modified on + * success. + * @ep_comp: Endpoint companion descriptor, with the required + * number of streams. Will be modified when the chosen EP + * supports a different number of streams. + * + * This routine replaces the usb_ep_autoconfig when needed + * superspeed enhancments. If such enhancemnets are required, + * the FD should call usb_ep_autoconfig_ss directly and provide + * the additional ep_comp parameter. + * + * By choosing an endpoint to use with the specified descriptor, + * this routine simplifies writing gadget drivers that work with + * multiple USB device controllers. The endpoint would be + * passed later to usb_ep_enable(), along with some descriptor. + * + * That second descriptor won't always be the same as the first one. + * For example, isochronous endpoints can be autoconfigured for high + * bandwidth, and then used in several lower bandwidth altsettings. + * Also, high and full speed descriptors will be different. + * + * Be sure to examine and test the results of autoconfiguration + * on your hardware. This code may not make the best choices + * about how to use the USB controller, and it can't know all + * the restrictions that may apply. Some combinations of driver + * and hardware won't be able to autoconfigure. + * + * On success, this returns an un-claimed usb_ep, and modifies the endpoint + * descriptor bEndpointAddress. For bulk endpoints, the wMaxPacket value + * is initialized as if the endpoint were used at full speed and + * the bmAttribute field in the ep companion descriptor is + * updated with the assigned number of streams if it is + * different from the original value. To prevent the endpoint + * from being returned by a later autoconfig call, claim it by + * assigning ep->driver_data to some non-null value. + * + * On failure, this returns a null endpoint descriptor. + */ +struct usb_ep *usb_ep_autoconfig_ss( + struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ep_comp +) +{ + struct usb_ep *ep; + u8 type; + + type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + /* First, apply chip-specific "best usage" knowledge. + * This might make a good usb_gadget_ops hook ... + */ + if (gadget_is_net2280 (gadget) && type == USB_ENDPOINT_XFER_INT) { + /* ep-e, ep-f are PIO with only 64 byte fifos */ + ep = find_ep (gadget, "ep-e"); + if (ep && ep_matches(gadget, ep, desc, ep_comp)) + goto found_ep; + ep = find_ep (gadget, "ep-f"); + if (ep && ep_matches(gadget, ep, desc, ep_comp)) + goto found_ep; + + } else if (gadget_is_goku (gadget)) { + if (USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough */ + ep = find_ep(gadget, "ep3-bulk"); + if (ep && ep_matches(gadget, ep, desc, ep_comp)) + goto found_ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && (USB_DIR_IN & desc->bEndpointAddress)) { + /* DMA may be available */ + ep = find_ep(gadget, "ep2-bulk"); + if (ep && ep_matches(gadget, ep, desc, + ep_comp)) + goto found_ep; + } + +#ifdef CONFIG_BLACKFIN + } else if (gadget_is_musbhdrc(gadget)) { + if ((USB_ENDPOINT_XFER_BULK == type) || + (USB_ENDPOINT_XFER_ISOC == type)) { + if (USB_DIR_IN & desc->bEndpointAddress) + ep = find_ep (gadget, "ep5in"); + else + ep = find_ep (gadget, "ep6out"); + } else if (USB_ENDPOINT_XFER_INT == type) { + if (USB_DIR_IN & desc->bEndpointAddress) + ep = find_ep(gadget, "ep1in"); + else + ep = find_ep(gadget, "ep2out"); + } else + ep = NULL; + if (ep && ep_matches(gadget, ep, desc, ep_comp)) + goto found_ep; +#endif + } + + /* Second, look at endpoints until an unclaimed one looks usable */ + list_for_each_entry (ep, &gadget->ep_list, ep_list) { + if (ep_matches(gadget, ep, desc, ep_comp)) + goto found_ep; + } + + /* Fail */ + return NULL; +found_ep: + ep->desc = NULL; + ep->comp_desc = NULL; + return ep; +} +EXPORT_SYMBOL_GPL(usb_ep_autoconfig_ss); + +/** + * usb_ep_autoconfig() - choose an endpoint matching the + * descriptor + * @gadget: The device to which the endpoint must belong. + * @desc: Endpoint descriptor, with endpoint direction and transfer mode + * initialized. For periodic transfers, the maximum packet + * size must also be initialized. This is modified on success. + * + * By choosing an endpoint to use with the specified descriptor, this + * routine simplifies writing gadget drivers that work with multiple + * USB device controllers. The endpoint would be passed later to + * usb_ep_enable(), along with some descriptor. + * + * That second descriptor won't always be the same as the first one. + * For example, isochronous endpoints can be autoconfigured for high + * bandwidth, and then used in several lower bandwidth altsettings. + * Also, high and full speed descriptors will be different. + * + * Be sure to examine and test the results of autoconfiguration on your + * hardware. This code may not make the best choices about how to use the + * USB controller, and it can't know all the restrictions that may apply. + * Some combinations of driver and hardware won't be able to autoconfigure. + * + * On success, this returns an un-claimed usb_ep, and modifies the endpoint + * descriptor bEndpointAddress. For bulk endpoints, the wMaxPacket value + * is initialized as if the endpoint were used at full speed. To prevent + * the endpoint from being returned by a later autoconfig call, claim it + * by assigning ep->driver_data to some non-null value. + * + * On failure, this returns a null endpoint descriptor. + */ +struct usb_ep *usb_ep_autoconfig( + struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc +) +{ + return usb_ep_autoconfig_ss(gadget, desc, NULL); +} +EXPORT_SYMBOL_GPL(usb_ep_autoconfig); + +/** + * usb_ep_autoconfig_reset - reset endpoint autoconfig state + * @gadget: device for which autoconfig state will be reset + * + * Use this for devices where one configuration may need to assign + * endpoint resources very differently from the next one. It clears + * state such as ep->driver_data and the record of assigned endpoints + * used by usb_ep_autoconfig(). + */ +void usb_ep_autoconfig_reset (struct usb_gadget *gadget) +{ + struct usb_ep *ep; + + list_for_each_entry (ep, &gadget->ep_list, ep_list) { + ep->driver_data = NULL; + } + gadget->in_epnum = 0; + gadget->out_epnum = 0; +} +EXPORT_SYMBOL_GPL(usb_ep_autoconfig_reset); diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c new file mode 100644 index 00000000..56c8ecae --- /dev/null +++ b/drivers/usb/gadget/ether.c @@ -0,0 +1,374 @@ +/* + * ether.c -- Ethernet gadget driver, with CDC and non-CDC options + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include + +#if defined USB_ETH_RNDIS +# undef USB_ETH_RNDIS +#endif +#ifdef CONFIG_USB_ETH_RNDIS +# define USB_ETH_RNDIS y +#endif + +#include "u_ether.h" + + +/* + * Ethernet gadget driver -- with CDC and non-CDC options + * Builds on hardware support for a full duplex link. + * + * CDC Ethernet is the standard USB solution for sending Ethernet frames + * using USB. Real hardware tends to use the same framing protocol but look + * different for control features. This driver strongly prefers to use + * this USB-IF standard as its open-systems interoperability solution; + * most host side USB stacks (except from Microsoft) support it. + * + * This is sometimes called "CDC ECM" (Ethernet Control Model) to support + * TLA-soup. "CDC ACM" (Abstract Control Model) is for modems, and a new + * "CDC EEM" (Ethernet Emulation Model) is starting to spread. + * + * There's some hardware that can't talk CDC ECM. We make that hardware + * implement a "minimalist" vendor-agnostic CDC core: same framing, but + * link-level setup only requires activating the configuration. Only the + * endpoint descriptors, and product/vendor IDs, are relevant; no control + * operations are available. Linux supports it, but other host operating + * systems may not. (This is a subset of CDC Ethernet.) + * + * It turns out that if you add a few descriptors to that "CDC Subset", + * (Windows) host side drivers from MCCI can treat it as one submode of + * a proprietary scheme called "SAFE" ... without needing to know about + * specific product/vendor IDs. So we do that, making it easier to use + * those MS-Windows drivers. Those added descriptors make it resemble a + * CDC MDLM device, but they don't change device behavior at all. (See + * MCCI Engineering report 950198 "SAFE Networking Functions".) + * + * A third option is also in use. Rather than CDC Ethernet, or something + * simpler, Microsoft pushes their own approach: RNDIS. The published + * RNDIS specs are ambiguous and appear to be incomplete, and are also + * needlessly complex. They borrow more from CDC ACM than CDC ECM. + */ + +#define DRIVER_DESC "Ethernet Gadget" +#define DRIVER_VERSION "Memorial Day 2008" + +#ifdef USB_ETH_RNDIS +#define PREFIX "RNDIS/" +#else +#define PREFIX "" +#endif + +/* + * This driver aims for interoperability by using CDC ECM unless + * + * can_support_ecm() + * + * returns false, in which case it supports the CDC Subset. By default, + * that returns true; most hardware has no problems with CDC ECM, that's + * a good default. Previous versions of this driver had no default; this + * version changes that, removing overhead for new controller support. + * + * IF YOUR HARDWARE CAN'T SUPPORT CDC ECM, UPDATE THAT ROUTINE! + */ + +static inline bool has_rndis(void) +{ +#ifdef USB_ETH_RNDIS + return true; +#else + return false; +#endif +} + +/*-------------------------------------------------------------------------*/ + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "f_ecm.c" +#include "f_subset.c" +#ifdef USB_ETH_RNDIS +#include "f_rndis.c" +#include "rndis.c" +#endif +#include "f_eem.c" +#include "u_ether.c" + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. + * It's for devices with only CDC Ethernet configurations. + */ +#define CDC_VENDOR_NUM 0x0525 /* NetChip */ +#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */ + +/* For hardware that can't talk CDC, we use the same vendor ID that + * ARM Linux has used for ethernet-over-usb, both with sa1100 and + * with pxa250. We're protocol-compatible, if the host-side drivers + * use the endpoint descriptors. bcdDevice (version) is nonzero, so + * drivers that need to hard-wire endpoint numbers have a hook. + * + * The protocol is a minimal subset of CDC Ether, which works on any bulk + * hardware that's not deeply broken ... even on hardware that can't talk + * RNDIS (like SA-1100, with no interrupt endpoint, or anything that + * doesn't handle control-OUT). + */ +#define SIMPLE_VENDOR_NUM 0x049f +#define SIMPLE_PRODUCT_NUM 0x505a + +/* For hardware that can talk RNDIS and either of the above protocols, + * use this ID ... the windows INF files will know it. Unless it's + * used with CDC Ethernet, Linux 2.4 hosts will need updates to choose + * the non-RNDIS configuration. + */ +#define RNDIS_VENDOR_NUM 0x0525 /* NetChip */ +#define RNDIS_PRODUCT_NUM 0xa4a2 /* Ethernet/RNDIS Gadget */ + +/* For EEM gadgets */ +#define EEM_VENDOR_NUM 0x1d6b /* Linux Foundation */ +#define EEM_PRODUCT_NUM 0x0102 /* EEM Gadget */ + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16 (0x0200), + + .bDeviceClass = USB_CLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id defaults change according to what configs + * we support. (As does bNumConfigurations.) These values can + * also be overridden by module parameters. + */ + .idVendor = cpu_to_le16 (CDC_VENDOR_NUM), + .idProduct = cpu_to_le16 (CDC_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = PREFIX DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static u8 hostaddr[ETH_ALEN]; +static struct eth_dev *the_dev; +/*-------------------------------------------------------------------------*/ + +/* + * We may not have an RNDIS configuration, but if we do it needs to be + * the first one present. That's to make Microsoft's drivers happy, + * and to follow DOCSIS 1.0 (cable modem standard). + */ +static int __init rndis_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + return rndis_bind_config(c, hostaddr, the_dev); +} + +static struct usb_configuration rndis_config_driver = { + .label = "RNDIS", + .bConfigurationValue = 2, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_ETH_EEM +static bool use_eem = 1; +#else +static bool use_eem; +#endif +module_param(use_eem, bool, 0); +MODULE_PARM_DESC(use_eem, "use CDC EEM mode"); + +/* + * We _always_ have an ECM, CDC Subset, or EEM configuration. + */ +static int __init eth_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + if (use_eem) + return eem_bind_config(c, the_dev); + else if (can_support_ecm(c->cdev->gadget)) + return ecm_bind_config(c, hostaddr, the_dev); + else + return geth_bind_config(c, hostaddr, the_dev); +} + +static struct usb_configuration eth_config_driver = { + /* .label = f(hardware) */ + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init eth_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int status; + + /* set up network link layer */ + the_dev = gether_setup(cdev->gadget, hostaddr); + if (IS_ERR(the_dev)) + return PTR_ERR(the_dev); + + /* set up main config label and device descriptor */ + if (use_eem) { + /* EEM */ + eth_config_driver.label = "CDC Ethernet (EEM)"; + device_desc.idVendor = cpu_to_le16(EEM_VENDOR_NUM); + device_desc.idProduct = cpu_to_le16(EEM_PRODUCT_NUM); + } else if (can_support_ecm(cdev->gadget)) { + /* ECM */ + eth_config_driver.label = "CDC Ethernet (ECM)"; + } else { + /* CDC Subset */ + eth_config_driver.label = "CDC Subset/SAFE"; + + device_desc.idVendor = cpu_to_le16(SIMPLE_VENDOR_NUM); + device_desc.idProduct = cpu_to_le16(SIMPLE_PRODUCT_NUM); + if (!has_rndis()) + device_desc.bDeviceClass = USB_CLASS_VENDOR_SPEC; + } + + if (has_rndis()) { + /* RNDIS plus ECM-or-Subset */ + device_desc.idVendor = cpu_to_le16(RNDIS_VENDOR_NUM); + device_desc.idProduct = cpu_to_le16(RNDIS_PRODUCT_NUM); + device_desc.bNumConfigurations = 2; + } + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto fail; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + /* register our configuration(s); RNDIS first, if it's used */ + if (has_rndis()) { + status = usb_add_config(cdev, &rndis_config_driver, + rndis_do_config); + if (status < 0) + goto fail; + } + + status = usb_add_config(cdev, ð_config_driver, eth_do_config); + if (status < 0) + goto fail; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n", + DRIVER_DESC); + + return 0; + +fail: + gether_cleanup(the_dev); + return status; +} + +static int __exit eth_unbind(struct usb_composite_dev *cdev) +{ + gether_cleanup(the_dev); + return 0; +} + +static __refdata struct usb_composite_driver eth_driver = { + .name = "g_ether", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = eth_bind, + .unbind = __exit_p(eth_unbind), +}; + +MODULE_DESCRIPTION(PREFIX DRIVER_DESC); +MODULE_AUTHOR("David Brownell, Benedikt Spanger"); +MODULE_LICENSE("GPL"); + +static int __init init(void) +{ + return usb_composite_probe(ð_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(ð_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c new file mode 100644 index 00000000..a401acdc --- /dev/null +++ b/drivers/usb/gadget/f_accessory.c @@ -0,0 +1,1211 @@ +/* + * Gadget Function Driver for Android USB accessories + * + * Copyright (C) 2011 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define BULK_BUFFER_SIZE 16384 +#define ACC_STRING_SIZE 256 + +#define PROTOCOL_VERSION 2 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 + +struct acc_hid_dev { + struct list_head list; + struct hid_device *hid; + struct acc_dev *dev; + /* accessory defined ID */ + int id; + /* HID report descriptor */ + u8 *report_desc; + /* length of HID report descriptor */ + int report_desc_len; + /* number of bytes of report_desc we have received so far */ + int report_desc_offset; +}; + +struct acc_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + /* set to 1 when we connect */ + int online:1; + /* Set to 1 when we disconnect. + * Not cleared until our file is closed. + */ + int disconnected:1; + + /* strings sent by the host */ + char manufacturer[ACC_STRING_SIZE]; + char model[ACC_STRING_SIZE]; + char description[ACC_STRING_SIZE]; + char version[ACC_STRING_SIZE]; + char uri[ACC_STRING_SIZE]; + char serial[ACC_STRING_SIZE]; + + /* for acc_complete_set_string */ + int string_index; + + /* set to 1 if we have a pending start request */ + int start_requested; + + int audio_mode; + + /* synchronize access to our device file */ + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* delayed work for handling ACCESSORY_START */ + struct delayed_work start_work; + + /* worker for registering and unregistering hid devices */ + struct work_struct hid_work; + + /* list of active HID devices */ + struct list_head hid_list; + + /* list of new HID devices to register */ + struct list_head new_hid_list; + + /* list of dead HID devices to unregister */ + struct list_head dead_hid_list; +}; + +static struct usb_interface_descriptor acc_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_endpoint_descriptor acc_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acc_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_fullspeed_in_desc, + (struct usb_descriptor_header *) &acc_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_highspeed_in_desc, + (struct usb_descriptor_header *) &acc_highspeed_out_desc, + NULL, +}; + +static struct usb_string acc_string_defs[] = { + [INTERFACE_STRING_INDEX].s = "Android Accessory Interface", + { }, /* end of list */ +}; + +static struct usb_gadget_strings acc_string_table = { + .language = 0x0409, /* en-US */ + .strings = acc_string_defs, +}; + +static struct usb_gadget_strings *acc_strings[] = { + &acc_string_table, + NULL, +}; + +/* temporary variable used between acc_open() and acc_gadget_bind() */ +static struct acc_dev *_acc_dev; + +static inline struct acc_dev *func_to_dev(struct usb_function *f) +{ + return container_of(f, struct acc_dev, function); +} + +static struct usb_request *acc_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void acc_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +/* add a request to the tail of a list */ +static void req_put(struct acc_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *req_get(struct acc_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void acc_set_disconnected(struct acc_dev *dev) +{ + dev->online = 0; + dev->disconnected = 1; +} + +static void acc_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = _acc_dev; + + if (req->status == -ESHUTDOWN) { + pr_debug("acc_complete_in set disconnected"); + acc_set_disconnected(dev); + } + + req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void acc_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = _acc_dev; + + dev->rx_done = 1; + if (req->status == -ESHUTDOWN) { + pr_debug("acc_complete_out set disconnected"); + acc_set_disconnected(dev); + } + + wake_up(&dev->read_wq); +} + +static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = ep->driver_data; + char *string_dest = NULL; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_string, err %d\n", req->status); + return; + } + + switch (dev->string_index) { + case ACCESSORY_STRING_MANUFACTURER: + string_dest = dev->manufacturer; + break; + case ACCESSORY_STRING_MODEL: + string_dest = dev->model; + break; + case ACCESSORY_STRING_DESCRIPTION: + string_dest = dev->description; + break; + case ACCESSORY_STRING_VERSION: + string_dest = dev->version; + break; + case ACCESSORY_STRING_URI: + string_dest = dev->uri; + break; + case ACCESSORY_STRING_SERIAL: + string_dest = dev->serial; + break; + } + if (string_dest) { + unsigned long flags; + + if (length >= ACC_STRING_SIZE) + length = ACC_STRING_SIZE - 1; + + spin_lock_irqsave(&dev->lock, flags); + memcpy(string_dest, req->buf, length); + /* ensure zero termination */ + string_dest[length] = 0; + spin_unlock_irqrestore(&dev->lock, flags); + } else { + pr_err("unknown accessory string index %d\n", + dev->string_index); + } +} + +static void acc_complete_set_hid_report_desc(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + struct acc_dev *dev = hid->dev; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_hid_report_desc, err %d\n", + req->status); + return; + } + + memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length); + hid->report_desc_offset += length; + if (hid->report_desc_offset == hid->report_desc_len) { + /* After we have received the entire report descriptor + * we schedule work to initialize the HID device + */ + schedule_work(&dev->hid_work); + } +} + +static void acc_complete_send_hid_event(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_send_hid_event, err %d\n", req->status); + return; + } + + hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1); +} + +static int acc_hid_parse(struct hid_device *hid) +{ + struct acc_hid_dev *hdev = hid->driver_data; + + hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len); + return 0; +} + +static int acc_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_stop(struct hid_device *hid) +{ +} + +static int acc_hid_open(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_close(struct hid_device *hid) +{ +} + +static struct hid_ll_driver acc_hid_ll_driver = { + .parse = acc_hid_parse, + .start = acc_hid_start, + .stop = acc_hid_stop, + .open = acc_hid_open, + .close = acc_hid_close, +}; + +static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev, + int id, int desc_len) +{ + struct acc_hid_dev *hdev; + + hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC); + if (!hdev) + return NULL; + hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC); + if (!hdev->report_desc) { + kfree(hdev); + return NULL; + } + hdev->dev = dev; + hdev->id = id; + hdev->report_desc_len = desc_len; + + return hdev; +} + +static struct acc_hid_dev *acc_hid_get(struct list_head *list, int id) +{ + struct acc_hid_dev *hid; + + list_for_each_entry(hid, list, list) { + if (hid->id == id) + return hid; + } + return NULL; +} + +static int acc_register_hid(struct acc_dev *dev, int id, int desc_length) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + /* report descriptor length must be > 0 */ + if (desc_length <= 0) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + /* replace HID if one already exists with this ID */ + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (hid) + list_move(&hid->list, &dev->dead_hid_list); + + hid = acc_hid_new(dev, id, desc_length); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENOMEM; + } + + list_add(&hid->list, &dev->new_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + /* schedule work to register the HID device */ + schedule_work(&dev->hid_work); + return 0; +} + +static int acc_unregister_hid(struct acc_dev *dev, int id) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + + list_move(&hid->list, &dev->dead_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); + return 0; +} + +static int create_bulk_endpoints(struct acc_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_in, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_in; + req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_out, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_out; + dev->rx_req[i] = req; + } + + return 0; + +fail: + pr_err("acc_bind() could not allocate requests\n"); + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + acc_request_free(dev->rx_req[i], dev->ep_out); + return -1; +} + +static ssize_t acc_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req; + ssize_t r = count; + unsigned xfer; + int ret = 0; + + pr_debug("acc_read(%zu)\n", count); + + if (dev->disconnected) { + pr_debug("acc_read disconnected"); + return -ENODEV; + } + + if (count > BULK_BUFFER_SIZE) + count = BULK_BUFFER_SIZE; + + /* we will block until we're online */ + pr_debug("acc_read: waiting for online\n"); + ret = wait_event_interruptible(dev->read_wq, dev->online); + if (ret < 0) { + r = ret; + goto done; + } + + if (dev->rx_done) { + // last req cancelled. try to get it. + req = dev->rx_req[0]; + goto copy_data; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + ret = usb_ep_dequeue(dev->ep_out, req); + if (ret != 0) { + // cancel failed. There can be a data already received. + // it will be retrieved in the next read. + pr_debug("acc_read: cancelling failed %d", ret); + } + goto done; + } + +copy_data: + dev->rx_done = 0; + if (dev->online) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %u\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + pr_debug("acc_read returning %zd\n", r); + return r; +} + +static ssize_t acc_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req = 0; + ssize_t r = count; + unsigned xfer; + int ret; + + pr_debug("acc_write(%zu)\n", count); + + if (!dev->online || dev->disconnected) { + pr_debug("acc_write disconnected or not online"); + return -ENODEV; + } + + while (count > 0) { + if (!dev->online) { + pr_debug("acc_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = req_get(dev, &dev->tx_idle)) || !dev->online)); + if (!req) { + r = ret; + break; + } + + if (count > BULK_BUFFER_SIZE) { + xfer = BULK_BUFFER_SIZE; + /* ZLP, They will be more TX requests so not yet. */ + req->zero = 0; + } else { + xfer = count; + /* If the data length is a multple of the + * maxpacket size then send a zero length packet(ZLP). + */ + req->zero = ((xfer % dev->ep_in->maxpacket) == 0); + } + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + pr_debug("acc_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + req_put(dev, &dev->tx_idle, req); + + pr_debug("acc_write returning %zd\n", r); + return r; +} + +static long acc_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct acc_dev *dev = fp->private_data; + char *src = NULL; + int ret; + + switch (code) { + case ACCESSORY_GET_STRING_MANUFACTURER: + src = dev->manufacturer; + break; + case ACCESSORY_GET_STRING_MODEL: + src = dev->model; + break; + case ACCESSORY_GET_STRING_DESCRIPTION: + src = dev->description; + break; + case ACCESSORY_GET_STRING_VERSION: + src = dev->version; + break; + case ACCESSORY_GET_STRING_URI: + src = dev->uri; + break; + case ACCESSORY_GET_STRING_SERIAL: + src = dev->serial; + break; + case ACCESSORY_IS_START_REQUESTED: + return dev->start_requested; + case ACCESSORY_GET_AUDIO_MODE: + return dev->audio_mode; + } + if (!src) + return -EINVAL; + + ret = strlen(src) + 1; + if (copy_to_user((void __user *)value, src, ret)) + ret = -EFAULT; + return ret; +} + +static int acc_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "acc_open\n"); + if (atomic_xchg(&_acc_dev->open_excl, 1)) + return -EBUSY; + + _acc_dev->disconnected = 0; + fp->private_data = _acc_dev; + return 0; +} + +static int acc_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "acc_release\n"); + + WARN_ON(!atomic_xchg(&_acc_dev->open_excl, 0)); + _acc_dev->disconnected = 0; + return 0; +} + +/* file operations for /dev/usb_accessory */ +static const struct file_operations acc_fops = { + .owner = THIS_MODULE, + .read = acc_read, + .write = acc_write, + .unlocked_ioctl = acc_ioctl, + .open = acc_open, + .release = acc_release, +}; + +static int acc_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) + return ret; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static struct miscdevice acc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "usb_accessory", + .fops = &acc_fops, +}; + +static const struct hid_device_id acc_hid_table[] = { + { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } +}; + +static struct hid_driver acc_hid_driver = { + .name = "USB accessory", + .id_table = acc_hid_table, + .probe = acc_hid_probe, +}; + +static int acc_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct acc_dev *dev = _acc_dev; + int value = -EOPNOTSUPP; + struct acc_hid_dev *hid; + int offset; + u8 b_requestType = ctrl->bRequestType; + u8 b_request = ctrl->bRequest; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + unsigned long flags; + +/* + printk(KERN_INFO "acc_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + b_requestType, b_request, + w_value, w_index, w_length); +*/ + + if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) { + if (b_request == ACCESSORY_START) { + dev->start_requested = 1; + schedule_delayed_work( + &dev->start_work, msecs_to_jiffies(10)); + value = 0; + } else if (b_request == ACCESSORY_SEND_STRING) { + dev->string_index = w_index; + cdev->gadget->ep0->driver_data = dev; + cdev->req->complete = acc_complete_set_string; + value = w_length; + } else if (b_request == ACCESSORY_SET_AUDIO_MODE && + w_index == 0 && w_length == 0) { + dev->audio_mode = w_value; + value = 0; + } else if (b_request == ACCESSORY_REGISTER_HID) { + value = acc_register_hid(dev, w_value, w_index); + } else if (b_request == ACCESSORY_UNREGISTER_HID) { + value = acc_unregister_hid(dev, w_value); + } else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->new_hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + offset = w_index; + if (offset != hid->report_desc_offset + || offset + w_length > hid->report_desc_len) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_set_hid_report_desc; + value = w_length; + } else if (b_request == ACCESSORY_SEND_HID_EVENT) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_send_hid_event; + value = w_length; + } + } else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) { + if (b_request == ACCESSORY_GET_PROTOCOL) { + *((u16 *)cdev->req->buf) = PROTOCOL_VERSION; + value = sizeof(u16); + + /* clear any string left over from a previous session */ + memset(dev->manufacturer, 0, sizeof(dev->manufacturer)); + memset(dev->model, 0, sizeof(dev->model)); + memset(dev->description, 0, sizeof(dev->description)); + memset(dev->version, 0, sizeof(dev->version)); + memset(dev->uri, 0, sizeof(dev->uri)); + memset(dev->serial, 0, sizeof(dev->serial)); + dev->start_requested = 0; + dev->audio_mode = 0; + } + } + + if (value >= 0) { + cdev->req->zero = 0; + cdev->req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "%s setup response queue error\n", + __func__); + } + +err: + if (value == -EOPNOTSUPP) + VDBG(cdev, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + return value; +} + +static int +acc_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct acc_dev *dev = func_to_dev(f); + int id; + int ret; + + DBG(cdev, "acc_function_bind dev: %p\n", dev); + + ret = hid_register_driver(&acc_hid_driver); + if (ret) + return ret; + + dev->start_requested = 0; + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + acc_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = create_bulk_endpoints(dev, &acc_fullspeed_in_desc, + &acc_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + acc_highspeed_in_desc.bEndpointAddress = + acc_fullspeed_in_desc.bEndpointAddress; + acc_highspeed_out_desc.bEndpointAddress = + acc_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +kill_all_hid_devices(struct acc_dev *dev) +{ + struct acc_hid_dev *hid; + struct list_head *entry, *temp; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_for_each_safe(entry, temp, &dev->hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); +} + +static void +acc_hid_unbind(struct acc_dev *dev) +{ + hid_unregister_driver(&acc_hid_driver); + kill_all_hid_devices(dev); +} + +static void +acc_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_request *req; + int i; + + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + acc_request_free(dev->rx_req[i], dev->ep_out); + + acc_hid_unbind(dev); +} + +static void acc_start_work(struct work_struct *data) +{ + char *envp[2] = { "ACCESSORY=START", NULL }; + kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); +} + +static int acc_hid_init(struct acc_hid_dev *hdev) +{ + struct hid_device *hid; + int ret; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + hid->ll_driver = &acc_hid_ll_driver; + hid->dev.parent = acc_device.this_device; + + hid->bus = BUS_USB; + hid->vendor = HID_ANY_ID; + hid->product = HID_ANY_ID; + hid->driver_data = hdev; + ret = hid_add_device(hid); + if (ret) { + pr_err("can't add hid device: %d\n", ret); + hid_destroy_device(hid); + return ret; + } + + hdev->hid = hid; + return 0; +} + +static void acc_hid_delete(struct acc_hid_dev *hid) +{ + kfree(hid->report_desc); + kfree(hid); +} + +static void acc_hid_work(struct work_struct *data) +{ + struct acc_dev *dev = _acc_dev; + struct list_head *entry, *temp; + struct acc_hid_dev *hid; + struct list_head new_list, dead_list; + unsigned long flags; + + INIT_LIST_HEAD(&new_list); + + spin_lock_irqsave(&dev->lock, flags); + + /* copy hids that are ready for initialization to new_list */ + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (hid->report_desc_offset == hid->report_desc_len) + list_move(&hid->list, &new_list); + } + + if (list_empty(&dev->dead_hid_list)) { + INIT_LIST_HEAD(&dead_list); + } else { + /* move all of dev->dead_hid_list to dead_list */ + dead_list.prev = dev->dead_hid_list.prev; + dead_list.next = dev->dead_hid_list.next; + dead_list.next->prev = &dead_list; + dead_list.prev->next = &dead_list; + INIT_LIST_HEAD(&dev->dead_hid_list); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + /* register new HID devices */ + list_for_each_safe(entry, temp, &new_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (acc_hid_init(hid)) { + pr_err("can't add HID device %p\n", hid); + acc_hid_delete(hid); + } else { + spin_lock_irqsave(&dev->lock, flags); + list_move(&hid->list, &dev->hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + } + } + + /* remove dead HID devices */ + list_for_each_safe(entry, temp, &dead_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + if (hid->hid) + hid_destroy_device(hid->hid); + acc_hid_delete(hid); + } +} + +static int acc_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "acc_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void acc_function_disable(struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "acc_function_disable\n"); + acc_set_disconnected(dev); + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int acc_bind_config(struct usb_configuration *c) +{ + struct acc_dev *dev = _acc_dev; + int ret; + + printk(KERN_INFO "acc_bind_config\n"); + + /* allocate a string ID for our interface */ + if (acc_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + acc_string_defs[INTERFACE_STRING_INDEX].id = ret; + acc_interface_desc.iInterface = ret; + } + + dev->cdev = c->cdev; + dev->function.name = "accessory"; + dev->function.strings = acc_strings, + dev->function.fs_descriptors = fs_acc_descs; + dev->function.hs_descriptors = hs_acc_descs; + dev->function.bind = acc_function_bind; + dev->function.unbind = acc_function_unbind; + dev->function.set_alt = acc_function_set_alt; + dev->function.disable = acc_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int acc_setup(void) +{ + struct acc_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + atomic_set(&dev->open_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->hid_list); + INIT_LIST_HEAD(&dev->new_hid_list); + INIT_LIST_HEAD(&dev->dead_hid_list); + INIT_DELAYED_WORK(&dev->start_work, acc_start_work); + INIT_WORK(&dev->hid_work, acc_hid_work); + + /* _acc_dev must be set before calling usb_gadget_register_driver */ + _acc_dev = dev; + + ret = misc_register(&acc_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + pr_err("USB accessory gadget driver failed to initialize\n"); + return ret; +} + +static void acc_disconnect(void) +{ + /* unregister all HID devices if USB is disconnected */ + kill_all_hid_devices(_acc_dev); +} + +static void acc_cleanup(void) +{ + misc_deregister(&acc_device); + kfree(_acc_dev); + _acc_dev = NULL; +} diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c new file mode 100644 index 00000000..3384486c --- /dev/null +++ b/drivers/usb/gadget/f_acm.c @@ -0,0 +1,849 @@ +/* + * f_acm.c -- USB CDC serial (ACM) function driver + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * Copyright (C) 2009 by Samsung Electronics + * Author: Michal Nazarewicz (mina86@mina86.com) + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include + +#include "u_serial.h" +#include "gadget_chips.h" + + +/* + * This CDC ACM function support just wraps control functions and + * notifications around the generic serial-over-usb code. + * + * Because CDC ACM is standardized by the USB-IF, many host operating + * systems have drivers for it. Accordingly, ACM is the preferred + * interop solution for serial-port type connections. The control + * models are often not necessary, and in any case don't do much in + * this bare-bones implementation. + * + * Note that even MS-Windows has some support for ACM. However, that + * support is somewhat broken because when you use ACM in a composite + * device, having multiple interfaces confuses the poor OS. It doesn't + * seem to understand CDC Union descriptors. The new "association" + * descriptors (roughly equivalent to CDC Unions) may sometimes help. + */ + +struct f_acm { + struct gserial port; + u8 ctrl_id, data_id; + u8 port_num; + + u8 pending; + + /* lock is mostly for pending and notify_req ... they get accessed + * by callbacks both from tty (open/close/break) under its spinlock, + * and notify_req.complete() which can't use that lock. + */ + spinlock_t lock; + + struct usb_ep *notify; + struct usb_request *notify_req; + + struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ + + /* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */ + u16 port_handshake_bits; +#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ + + /* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */ + u16 serial_state; +#define ACM_CTRL_OVERRUN (1 << 6) +#define ACM_CTRL_PARITY (1 << 5) +#define ACM_CTRL_FRAMING (1 << 4) +#define ACM_CTRL_RI (1 << 3) +#define ACM_CTRL_BRK (1 << 2) +#define ACM_CTRL_DSR (1 << 1) +#define ACM_CTRL_DCD (1 << 0) +}; + +static inline struct f_acm *func_to_acm(struct usb_function *f) +{ + return container_of(f, struct f_acm, port.func); +} + +static inline struct f_acm *port_to_acm(struct gserial *p) +{ + return container_of(p, struct f_acm, port); +} + +/*-------------------------------------------------------------------------*/ + +/* notification endpoint uses smallish and infrequent fixed-size messages */ + +#define GS_NOTIFY_INTERVAL_MS 32 +#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */ + +/* interface and class descriptors: */ + +static struct usb_interface_assoc_descriptor +acm_iad_descriptor = { + .bLength = sizeof acm_iad_descriptor, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, // control + data + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .iFunction = DYNAMIC */ +}; + + +static struct usb_interface_descriptor acm_control_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_interface_descriptor acm_data_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc acm_header_desc = { + .bLength = sizeof(acm_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor +acm_call_mgmt_descriptor = { + .bLength = sizeof(acm_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + /* .bDataInterface = DYNAMIC */ +}; + +static struct usb_cdc_acm_descriptor acm_descriptor = { + .bLength = sizeof(acm_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = USB_CDC_CAP_LINE, +}; + +static struct usb_cdc_union_desc acm_union_desc = { + .bLength = sizeof(acm_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor acm_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_NOTIFY_INTERVAL_MS, +}; + +static struct usb_endpoint_descriptor acm_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acm_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *acm_fs_function[] = { + (struct usb_descriptor_header *) &acm_iad_descriptor, + (struct usb_descriptor_header *) &acm_control_interface_desc, + (struct usb_descriptor_header *) &acm_header_desc, + (struct usb_descriptor_header *) &acm_call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &acm_union_desc, + (struct usb_descriptor_header *) &acm_fs_notify_desc, + (struct usb_descriptor_header *) &acm_data_interface_desc, + (struct usb_descriptor_header *) &acm_fs_in_desc, + (struct usb_descriptor_header *) &acm_fs_out_desc, + NULL, +}; + +/* high speed support: */ +static struct usb_endpoint_descriptor acm_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = USB_MS_TO_HS_INTERVAL(GS_NOTIFY_INTERVAL_MS), +}; + +static struct usb_endpoint_descriptor acm_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acm_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *acm_hs_function[] = { + (struct usb_descriptor_header *) &acm_iad_descriptor, + (struct usb_descriptor_header *) &acm_control_interface_desc, + (struct usb_descriptor_header *) &acm_header_desc, + (struct usb_descriptor_header *) &acm_call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &acm_union_desc, + (struct usb_descriptor_header *) &acm_hs_notify_desc, + (struct usb_descriptor_header *) &acm_data_interface_desc, + (struct usb_descriptor_header *) &acm_hs_in_desc, + (struct usb_descriptor_header *) &acm_hs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor acm_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor acm_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor acm_ss_bulk_comp_desc = { + .bLength = sizeof acm_ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *acm_ss_function[] = { + (struct usb_descriptor_header *) &acm_iad_descriptor, + (struct usb_descriptor_header *) &acm_control_interface_desc, + (struct usb_descriptor_header *) &acm_header_desc, + (struct usb_descriptor_header *) &acm_call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &acm_union_desc, + (struct usb_descriptor_header *) &acm_hs_notify_desc, + (struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &acm_data_interface_desc, + (struct usb_descriptor_header *) &acm_ss_in_desc, + (struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &acm_ss_out_desc, + (struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +#define ACM_CTRL_IDX 0 +#define ACM_DATA_IDX 1 +#define ACM_IAD_IDX 2 + +/* static strings, in UTF-8 */ +static struct usb_string acm_string_defs[] = { + [ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)", + [ACM_DATA_IDX].s = "CDC ACM Data", + [ACM_IAD_IDX ].s = "CDC Serial", + { } /* end of list */ +}; + +static struct usb_gadget_strings acm_string_table = { + .language = 0x0409, /* en-us */ + .strings = acm_string_defs, +}; + +static struct usb_gadget_strings *acm_strings[] = { + &acm_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +/* ACM control ... data handling is delegated to tty library code. + * The main task of this function is to activate and deactivate + * that code based on device state; track parameters like line + * speed, handshake state, and so on; and issue notifications. + */ + +static void acm_complete_set_line_coding(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_acm *acm = ep->driver_data; + struct usb_composite_dev *cdev = acm->port.func.config->cdev; + + if (req->status != 0) { + DBG(cdev, "acm ttyGS%d completion, err %d\n", + acm->port_num, req->status); + return; + } + + /* normal completion */ + if (req->actual != sizeof(acm->port_line_coding)) { + DBG(cdev, "acm ttyGS%d short resp, len %d\n", + acm->port_num, req->actual); + usb_ep_set_halt(ep); + } else { + struct usb_cdc_line_coding *value = req->buf; + + /* REVISIT: we currently just remember this data. + * If we change that, (a) validate it first, then + * (b) update whatever hardware needs updating, + * (c) worry about locking. This is information on + * the order of 9600-8-N-1 ... most of which means + * nothing unless we control a real RS232 line. + */ + acm->port_line_coding = *value; + } +} + +static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + * + * Note CDC spec table 4 lists the ACM request profile. It requires + * encapsulated command support ... we don't handle any, and respond + * to them by stalling. Options include get/set/clear comm features + * (not that useful) and SEND_BREAK. + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* SET_LINE_CODING ... just read and save what the host sends */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_LINE_CODING: + if (w_length != sizeof(struct usb_cdc_line_coding) + || w_index != acm->ctrl_id) + goto invalid; + + value = w_length; + cdev->gadget->ep0->driver_data = acm; + req->complete = acm_complete_set_line_coding; + break; + + /* GET_LINE_CODING ... return what host sent, or initial value */ + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_GET_LINE_CODING: + if (w_index != acm->ctrl_id) + goto invalid; + + value = min_t(unsigned, w_length, + sizeof(struct usb_cdc_line_coding)); + memcpy(req->buf, &acm->port_line_coding, value); + break; + + /* SET_CONTROL_LINE_STATE ... save what the host sent */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + if (w_index != acm->ctrl_id) + goto invalid; + + value = 0; + + /* FIXME we should not allow data to flow until the + * host sets the ACM_CTRL_DTR bit; and when it clears + * that bit, we should return to that no-flow state. + */ + acm->port_handshake_bits = w_value; + break; + + default: +invalid: + VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n", + acm->port_num, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "acm response on ttyGS%d, err %d\n", + acm->port_num, value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt == 0, so this is an activation or a reset */ + + if (intf == acm->ctrl_id) { + if (acm->notify->driver_data) { + VDBG(cdev, "reset acm control interface %d\n", intf); + usb_ep_disable(acm->notify); + } + + if (!acm->notify->desc) + if (config_ep_by_speed(cdev->gadget, f, acm->notify)) + return -EINVAL; + + usb_ep_enable(acm->notify); + acm->notify->driver_data = acm; + + } else if (intf == acm->data_id) { + if (acm->port.in->driver_data) { + DBG(cdev, "reset acm ttyGS%d\n", acm->port_num); + gserial_disconnect(&acm->port); + } + if (!acm->port.in->desc || !acm->port.out->desc) { + DBG(cdev, "activate acm ttyGS%d\n", acm->port_num); + if (config_ep_by_speed(cdev->gadget, f, + acm->port.in) || + config_ep_by_speed(cdev->gadget, f, + acm->port.out)) { + acm->port.in->desc = NULL; + acm->port.out->desc = NULL; + return -EINVAL; + } + } + gserial_connect(&acm->port, acm->port_num); + + } else + return -EINVAL; + + return 0; +} + +static void acm_disable(struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "acm ttyGS%d deactivated\n", acm->port_num); + gserial_disconnect(&acm->port); + usb_ep_disable(acm->notify); + acm->notify->driver_data = NULL; +} + +/*-------------------------------------------------------------------------*/ + +/** + * acm_cdc_notify - issue CDC notification to host + * @acm: wraps host to be notified + * @type: notification type + * @value: Refer to cdc specs, wValue field. + * @data: data to be sent + * @length: size of data + * Context: irqs blocked, acm->lock held, acm_notify_req non-null + * + * Returns zero on success or a negative errno. + * + * See section 6.3.5 of the CDC 1.1 specification for information + * about the only notification we issue: SerialState change. + */ +static int acm_cdc_notify(struct f_acm *acm, u8 type, u16 value, + void *data, unsigned length) +{ + struct usb_ep *ep = acm->notify; + struct usb_request *req; + struct usb_cdc_notification *notify; + const unsigned len = sizeof(*notify) + length; + void *buf; + int status; + + req = acm->notify_req; + acm->notify_req = NULL; + acm->pending = false; + + req->length = len; + notify = req->buf; + buf = notify + 1; + + notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + notify->bNotificationType = type; + notify->wValue = cpu_to_le16(value); + notify->wIndex = cpu_to_le16(acm->ctrl_id); + notify->wLength = cpu_to_le16(length); + memcpy(buf, data, length); + + /* ep_queue() can complete immediately if it fills the fifo... */ + spin_unlock(&acm->lock); + status = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&acm->lock); + + if (status < 0) { + ERROR(acm->port.func.config->cdev, + "acm ttyGS%d can't notify serial state, %d\n", + acm->port_num, status); + acm->notify_req = req; + } + + return status; +} + +static int acm_notify_serial_state(struct f_acm *acm) +{ + struct usb_composite_dev *cdev = acm->port.func.config->cdev; + int status; + + spin_lock(&acm->lock); + if (acm->notify_req) { + DBG(cdev, "acm ttyGS%d serial state %04x\n", + acm->port_num, acm->serial_state); + status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &acm->serial_state, sizeof(acm->serial_state)); + } else { + acm->pending = true; + status = 0; + } + spin_unlock(&acm->lock); + return status; +} + +static void acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_acm *acm = req->context; + u8 doit = false; + + /* on this call path we do NOT hold the port spinlock, + * which is why ACM needs its own spinlock + */ + spin_lock(&acm->lock); + if (req->status != -ESHUTDOWN) + doit = acm->pending; + acm->notify_req = req; + spin_unlock(&acm->lock); + + if (doit) + acm_notify_serial_state(acm); +} + +/* connect == the TTY link is open */ + +static void acm_connect(struct gserial *port) +{ + struct f_acm *acm = port_to_acm(port); + + acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD; + acm_notify_serial_state(acm); +} + +static void acm_disconnect(struct gserial *port) +{ + struct f_acm *acm = port_to_acm(port); + + acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD); + acm_notify_serial_state(acm); +} + +static int acm_send_break(struct gserial *port, int duration) +{ + struct f_acm *acm = port_to_acm(port); + u16 state; + + state = acm->serial_state; + state &= ~ACM_CTRL_BRK; + if (duration) + state |= ACM_CTRL_BRK; + + acm->serial_state = state; + return acm_notify_serial_state(acm); +} + +/*-------------------------------------------------------------------------*/ + +/* ACM function driver setup/binding */ +static int +acm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_acm *acm = func_to_acm(f); + struct usb_string *us; + int status; + struct usb_ep *ep; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* maybe allocate device-global string IDs, and patch descriptors */ + us = usb_gstrings_attach(cdev, acm_strings, + ARRAY_SIZE(acm_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + acm_control_interface_desc.iInterface = us[ACM_CTRL_IDX].id; + acm_data_interface_desc.iInterface = us[ACM_DATA_IDX].id; + acm_iad_descriptor.iFunction = us[ACM_IAD_IDX].id; + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + acm->ctrl_id = status; + acm_iad_descriptor.bFirstInterface = status; + + acm_control_interface_desc.bInterfaceNumber = status; + acm_union_desc .bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + acm->data_id = status; + + acm_data_interface_desc.bInterfaceNumber = status; + acm_union_desc.bSlaveInterface0 = status; + acm_call_mgmt_descriptor.bDataInterface = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc); + if (!ep) + goto fail; + acm->port.in = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc); + if (!ep) + goto fail; + acm->port.out = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc); + if (!ep) + goto fail; + acm->notify = ep; + ep->driver_data = cdev; /* claim */ + + /* allocate notification */ + acm->notify_req = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!acm->notify_req) + goto fail; + + acm->notify_req->complete = acm_cdc_notify_complete; + acm->notify_req->context = acm; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + acm_hs_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress; + acm_hs_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress; + acm_hs_notify_desc.bEndpointAddress = + acm_fs_notify_desc.bEndpointAddress; + + acm_ss_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress; + acm_ss_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function, + acm_ss_function); + if (status) + goto fail; + + DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n", + acm->port_num, + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + acm->port.in->name, acm->port.out->name, + acm->notify->name); + return 0; + +fail: + if (acm->notify_req) + gs_free_req(acm->notify, acm->notify_req); + + /* we might as well release our claims on endpoints */ + if (acm->notify) + acm->notify->driver_data = NULL; + if (acm->port.out) + acm->port.out->driver_data = NULL; + if (acm->port.in) + acm->port.in->driver_data = NULL; + + ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); + + return status; +} + +static void acm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + + acm_string_defs[0].id = 0; + usb_free_all_descriptors(f); + if (acm->notify_req) + gs_free_req(acm->notify, acm->notify_req); +} + +static void acm_free_func(struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + + kfree(acm); +} + +static struct usb_function *acm_alloc_func(struct usb_function_instance *fi) +{ + struct f_serial_opts *opts; + struct f_acm *acm; + + acm = kzalloc(sizeof(*acm), GFP_KERNEL); + if (!acm) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&acm->lock); + + acm->port.connect = acm_connect; + acm->port.disconnect = acm_disconnect; + acm->port.send_break = acm_send_break; + + acm->port.func.name = "acm"; + acm->port.func.strings = acm_strings; + /* descriptors are per-instance copies */ + acm->port.func.bind = acm_bind; + acm->port.func.set_alt = acm_set_alt; + acm->port.func.setup = acm_setup; + acm->port.func.disable = acm_disable; + + opts = container_of(fi, struct f_serial_opts, func_inst); + acm->port_num = opts->port_num; + acm->port.func.unbind = acm_unbind; + acm->port.func.free_func = acm_free_func; + + return &acm->port.func; +} + +static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_serial_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_serial_opts); +static ssize_t f_acm_attr_show(struct config_item *item, + struct configfs_attribute *attr, + char *page) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + struct f_serial_opts_attribute *f_serial_opts_attr = + container_of(attr, struct f_serial_opts_attribute, attr); + ssize_t ret = 0; + + if (f_serial_opts_attr->show) + ret = f_serial_opts_attr->show(opts, page); + return ret; +} + +static void acm_attr_release(struct config_item *item) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations acm_item_ops = { + .release = acm_attr_release, + .show_attribute = f_acm_attr_show, +}; + +static ssize_t f_acm_port_num_show(struct f_serial_opts *opts, char *page) +{ + return sprintf(page, "%u\n", opts->port_num); +} + +static struct f_serial_opts_attribute f_acm_port_num = + __CONFIGFS_ATTR_RO(port_num, f_acm_port_num_show); + + +static struct configfs_attribute *acm_attrs[] = { + &f_acm_port_num.attr, + NULL, +}; + +static struct config_item_type acm_func_type = { + .ct_item_ops = &acm_item_ops, + .ct_attrs = acm_attrs, + .ct_owner = THIS_MODULE, +}; + +static void acm_free_instance(struct usb_function_instance *fi) +{ + struct f_serial_opts *opts; + + opts = container_of(fi, struct f_serial_opts, func_inst); + gserial_free_line(opts->port_num); + kfree(opts); +} + +static struct usb_function_instance *acm_alloc_instance(void) +{ + struct f_serial_opts *opts; + int ret; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + opts->func_inst.free_func_inst = acm_free_instance; + ret = gserial_alloc_line(&opts->port_num); + if (ret) { + kfree(opts); + return ERR_PTR(ret); + } + config_group_init_type_name(&opts->func_inst.group, "", + &acm_func_type); + return &opts->func_inst; +} +DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_adb.c b/drivers/usb/gadget/f_adb.c new file mode 100644 index 00000000..a1d70d27 --- /dev/null +++ b/drivers/usb/gadget/f_adb.c @@ -0,0 +1,619 @@ +/* + * Gadget Driver for Android ADB + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADB_BULK_BUFFER_SIZE 4096 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +static const char adb_shortname[] = "android_adb"; + +struct adb_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; +}; + +static struct usb_interface_descriptor adb_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0x42, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor adb_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor adb_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor adb_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor adb_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_fullspeed_in_desc, + (struct usb_descriptor_header *) &adb_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_highspeed_in_desc, + (struct usb_descriptor_header *) &adb_highspeed_out_desc, + NULL, +}; + +static void adb_ready_callback(void); +static void adb_closed_callback(void); + +/* temporary variable used between adb_open() and adb_gadget_bind() */ +static struct adb_dev *_adb_dev; + +static inline struct adb_dev *func_to_adb(struct usb_function *f) +{ + return container_of(f, struct adb_dev, function); +} + + +static struct usb_request *adb_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void adb_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int adb_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void adb_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void adb_req_put(struct adb_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +struct usb_request *adb_req_get(struct adb_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void adb_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + if (req->status != 0) + dev->error = 1; + + adb_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void adb_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + dev->rx_done = 1; + if (req->status != 0 && req->status != -ECONNRESET) + dev->error = 1; + + wake_up(&dev->read_wq); +} + +static int adb_create_bulk_endpoints(struct adb_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for adb ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + req = adb_request_new(dev->ep_out, ADB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_out; + dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = adb_request_new(dev->ep_in, ADB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_in; + adb_req_put(dev, &dev->tx_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "adb_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t adb_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_request *req; + int r = count, xfer; + int ret; + + pr_debug("adb_read(%d)\n", count); + if (!_adb_dev) + return -ENODEV; + + if (count > ADB_BULK_BUFFER_SIZE) + return -EINVAL; + + if (adb_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + pr_debug("adb_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + adb_unlock(&dev->read_excl); + return ret; + } + } + if (dev->error) { + r = -EIO; + goto done; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("adb_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + dev->error = 1; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + if (ret != -ERESTARTSYS) + dev->error = 1; + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (!dev->error) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + + } else + r = -EIO; + +done: + adb_unlock(&dev->read_excl); + pr_debug("adb_read returning %d\n", r); + return r; +} + +static ssize_t adb_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + if (!_adb_dev) + return -ENODEV; + pr_debug("adb_write(%d)\n", count); + + if (adb_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + pr_debug("adb_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = adb_req_get(dev, &dev->tx_idle)) || dev->error); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + if (count > ADB_BULK_BUFFER_SIZE) + xfer = ADB_BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("adb_write: xfer error %d\n", ret); + dev->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + adb_req_put(dev, &dev->tx_idle, req); + + adb_unlock(&dev->write_excl); + pr_debug("adb_write returning %d\n", r); + return r; +} + +static int adb_open(struct inode *ip, struct file *fp) +{ + pr_info("adb_open\n"); + if (!_adb_dev) + return -ENODEV; + + if (adb_lock(&_adb_dev->open_excl)) + return -EBUSY; + + fp->private_data = _adb_dev; + + /* clear the error latch */ + _adb_dev->error = 0; + + adb_ready_callback(); + + return 0; +} + +static int adb_release(struct inode *ip, struct file *fp) +{ + pr_info("adb_release\n"); + + adb_closed_callback(); + + adb_unlock(&_adb_dev->open_excl); + return 0; +} + +/* file operations for ADB device /dev/android_adb */ +static const struct file_operations adb_fops = { + .owner = THIS_MODULE, + .read = adb_read, + .write = adb_write, + .open = adb_open, + .release = adb_release, +}; + +static struct miscdevice adb_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = adb_shortname, + .fops = &adb_fops, +}; + + + + +static int +adb_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct adb_dev *dev = func_to_adb(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "adb_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + adb_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = adb_create_bulk_endpoints(dev, &adb_fullspeed_in_desc, + &adb_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + adb_highspeed_in_desc.bEndpointAddress = + adb_fullspeed_in_desc.bEndpointAddress; + adb_highspeed_out_desc.bEndpointAddress = + adb_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +adb_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_request *req; + + + dev->online = 0; + dev->error = 1; + + wake_up(&dev->read_wq); + + adb_request_free(dev->rx_req, dev->ep_out); + while ((req = adb_req_get(dev, &dev->tx_idle))) + adb_request_free(req, dev->ep_in); +} + +static int adb_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void adb_function_disable(struct usb_function *f) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "adb_function_disable cdev %p\n", cdev); + dev->online = 0; + dev->error = 1; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int adb_bind_config(struct usb_configuration *c) +{ + struct adb_dev *dev = _adb_dev; + + printk(KERN_INFO "adb_bind_config\n"); + + dev->cdev = c->cdev; + dev->function.name = "adb"; + dev->function.fs_descriptors = fs_adb_descs; + dev->function.hs_descriptors = hs_adb_descs; + dev->function.bind = adb_function_bind; + dev->function.unbind = adb_function_unbind; + dev->function.set_alt = adb_function_set_alt; + dev->function.disable = adb_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int adb_setup(void) +{ + struct adb_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + INIT_LIST_HEAD(&dev->tx_idle); + + _adb_dev = dev; + + ret = misc_register(&adb_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + printk(KERN_ERR "adb gadget driver failed to initialize\n"); + return ret; +} + +static void adb_cleanup(void) +{ + misc_deregister(&adb_device); + + kfree(_adb_dev); + _adb_dev = NULL; +} diff --git a/drivers/usb/gadget/f_audio_source.c b/drivers/usb/gadget/f_audio_source.c new file mode 100644 index 00000000..78e931a6 --- /dev/null +++ b/drivers/usb/gadget/f_audio_source.c @@ -0,0 +1,839 @@ +/* + * Gadget Function Driver for USB audio source device + * + * Copyright (C) 2012 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include + +#define SAMPLE_RATE 44100 +#define FRAMES_PER_MSEC (SAMPLE_RATE / 1000) + +#define IN_EP_MAX_PACKET_SIZE 256 + +/* Number of requests to allocate */ +#define IN_EP_REQ_COUNT 4 + +#define AUDIO_AC_INTERFACE 0 +#define AUDIO_AS_INTERFACE 1 +#define AUDIO_NUM_INTERFACES 2 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); + +#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(AUDIO_NUM_INTERFACES) +/* 1 input terminal, 1 output terminal and 1 feature unit */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \ + + UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE \ + + UAC_DT_FEATURE_UNIT_SIZE(0)) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_2 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + [0] = AUDIO_AC_INTERFACE, + [1] = AUDIO_AS_INTERFACE, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct uac_input_terminal_descriptor input_terminal_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct uac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct uac1_output_terminal_descriptor output_terminal_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = FEATURE_UNIT_ID, + .bSourceID = FEATURE_UNIT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct uac1_as_header_descriptor as_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO IN Endpoint Descriptor for highspeed */ +static struct usb_endpoint_descriptor hs_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_SYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE), + .bInterval = 4, /* poll 1 per millisecond */ +}; + +/* Standard ISO IN Endpoint Descriptor for highspeed */ +static struct usb_endpoint_descriptor fs_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_SYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE), + .bInterval = 1, /* poll 1 per millisecond */ +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_in_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *hs_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&hs_as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&fs_as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct snd_pcm_hardware audio_hw_info = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = SAMPLE_RATE, + .rate_max = SAMPLE_RATE, + + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 512 * 1024, + .periods_min = 2, + .periods_max = 1024, +}; + +/*-------------------------------------------------------------------------*/ + +struct audio_source_config { + int card; + int device; +}; + +struct audio_dev { + struct usb_function func; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + + struct list_head idle_reqs; + struct usb_ep *in_ep; + + spinlock_t lock; + + /* beginning, end and current position in our buffer */ + void *buffer_start; + void *buffer_end; + void *buffer_pos; + + /* byte size of a "period" */ + unsigned int period; + /* bytes sent since last call to snd_pcm_period_elapsed */ + unsigned int period_offset; + /* time we started playing */ + ktime_t start_time; + /* number of frames sent since start_time */ + s64 frames_sent; +}; + +static inline struct audio_dev *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct audio_dev, func); +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_request *audio_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + req->length = buffer_size; + return req; +} + +static void audio_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static void audio_req_put(struct audio_dev *audio, struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + list_add_tail(&req->list, &audio->idle_reqs); + spin_unlock_irqrestore(&audio->lock, flags); +} + +static struct usb_request *audio_req_get(struct audio_dev *audio) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&audio->lock, flags); + if (list_empty(&audio->idle_reqs)) { + req = 0; + } else { + req = list_first_entry(&audio->idle_reqs, struct usb_request, + list); + list_del(&req->list); + } + spin_unlock_irqrestore(&audio->lock, flags); + return req; +} + +/* send the appropriate number of packets to match our bitrate */ +static void audio_send(struct audio_dev *audio) +{ + struct snd_pcm_runtime *runtime; + struct usb_request *req; + int length, length1, length2, ret; + s64 msecs; + s64 frames; + ktime_t now; + + /* audio->substream will be null if we have been closed */ + if (!audio->substream) + return; + /* audio->buffer_pos will be null if we have been stopped */ + if (!audio->buffer_pos) + return; + + runtime = audio->substream->runtime; + + /* compute number of frames to send */ + now = ktime_get(); + msecs = ktime_to_ns(now) - ktime_to_ns(audio->start_time); + do_div(msecs, 1000000); + frames = msecs * SAMPLE_RATE; + do_div(frames, 1000); + + /* Readjust our frames_sent if we fall too far behind. + * If we get too far behind it is better to drop some frames than + * to keep sending data too fast in an attempt to catch up. + */ + if (frames - audio->frames_sent > 10 * FRAMES_PER_MSEC) + audio->frames_sent = frames - FRAMES_PER_MSEC; + + frames -= audio->frames_sent; + + /* We need to send something to keep the pipeline going */ + if (frames <= 0) + frames = FRAMES_PER_MSEC; + + while (frames > 0) { + req = audio_req_get(audio); + if (!req) + break; + + length = frames_to_bytes(runtime, frames); + if (length > IN_EP_MAX_PACKET_SIZE) + length = IN_EP_MAX_PACKET_SIZE; + + if (audio->buffer_pos + length > audio->buffer_end) + length1 = audio->buffer_end - audio->buffer_pos; + else + length1 = length; + memcpy(req->buf, audio->buffer_pos, length1); + if (length1 < length) { + /* Wrap around and copy remaining length + * at beginning of buffer. + */ + length2 = length - length1; + memcpy(req->buf + length1, audio->buffer_start, + length2); + audio->buffer_pos = audio->buffer_start + length2; + } else { + audio->buffer_pos += length1; + if (audio->buffer_pos >= audio->buffer_end) + audio->buffer_pos = audio->buffer_start; + } + + req->length = length; + ret = usb_ep_queue(audio->in_ep, req, GFP_ATOMIC); + if (ret < 0) { + pr_err("usb_ep_queue failed ret: %d\n", ret); + audio_req_put(audio, req); + break; + } + + frames -= bytes_to_frames(runtime, length); + audio->frames_sent += bytes_to_frames(runtime, length); + } +} + +static void audio_control_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* nothing to do here */ +} + +static void audio_data_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct audio_dev *audio = req->context; + + pr_debug("audio_data_complete req->status %d req->actual %d\n", + req->status, req->actual); + + audio_req_put(audio, req); + + if (!audio->buffer_start || req->status) + return; + + audio->period_offset += req->actual; + if (audio->period_offset >= audio->period) { + snd_pcm_period_elapsed(audio->substream); + audio->period_offset = 0; + } + audio_send(audio); +} + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_SET_CUR: + case UAC_SET_MIN: + case UAC_SET_MAX: + case UAC_SET_RES: + value = len; + break; + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 *buf = cdev->req->buf; + + pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + if (w_value == UAC_EP_CS_ATTR_SAMPLE_RATE << 8) { + switch (ctrl->bRequest) { + case UAC_GET_CUR: + case UAC_GET_MIN: + case UAC_GET_MAX: + case UAC_GET_RES: + /* return our sample rate */ + buf[0] = (u8)SAMPLE_RATE; + buf[1] = (u8)(SAMPLE_RATE >> 8); + buf[2] = (u8)(SAMPLE_RATE >> 16); + value = 3; + break; + default: + break; + } + } + + return value; +} + +static int +audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything; interface + * activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_set_endpoint_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_get_endpoint_req(f, ctrl); + break; + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + pr_debug("audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + req->complete = audio_control_complete; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + pr_err("audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct audio_dev *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, audio->in_ep); + if (ret) + return ret; + + usb_ep_enable(audio->in_ep); + return 0; +} + +static void audio_disable(struct usb_function *f) +{ + struct audio_dev *audio = func_to_audio(f); + + pr_debug("audio_disable\n"); + usb_ep_disable(audio->in_ep); +} + +/*-------------------------------------------------------------------------*/ + +static void audio_build_desc(struct audio_dev *audio) +{ + u8 *sam_freq; + int rate; + + /* Set channel numbers */ + input_terminal_desc.bNrChannels = 2; + as_type_i_desc.bNrChannels = 2; + + /* Set sample rates */ + rate = SAMPLE_RATE; + sam_freq = as_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); +} + +/* audio function driver setup/binding */ +static int +audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct audio_dev *audio = func_to_audio(f); + int status; + struct usb_ep *ep; + struct usb_request *req; + int i; + + audio_build_desc(audio); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + + /* AUDIO_AC_INTERFACE */ + ac_header_desc.baInterfaceNr[0] = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_interface_alt_0_desc.bInterfaceNumber = status; + as_interface_alt_1_desc.bInterfaceNumber = status; + + /* AUDIO_AS_INTERFACE */ + ac_header_desc.baInterfaceNr[1] = status; + + status = -ENODEV; + + /* allocate our endpoint */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc); + if (!ep) + goto fail; + audio->in_ep = ep; + ep->driver_data = audio; /* claim */ + + if (gadget_is_dualspeed(c->cdev->gadget)) + hs_as_in_ep_desc.bEndpointAddress = + fs_as_in_ep_desc.bEndpointAddress; + + f->fs_descriptors = fs_audio_desc; + f->hs_descriptors = hs_audio_desc; + + for (i = 0, status = 0; i < IN_EP_REQ_COUNT && status == 0; i++) { + req = audio_request_new(ep, IN_EP_MAX_PACKET_SIZE); + if (req) { + req->context = audio; + req->complete = audio_data_complete; + audio_req_put(audio, req); + } else + status = -ENOMEM; + } + +fail: + return status; +} + +static void +audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct audio_dev *audio = func_to_audio(f); + struct usb_request *req; + + while ((req = audio_req_get(audio))) + audio_request_free(req, audio->in_ep); + + snd_card_free_when_closed(audio->card); + audio->card = NULL; + audio->pcm = NULL; + audio->substream = NULL; + audio->in_ep = NULL; +} + +static void audio_pcm_playback_start(struct audio_dev *audio) +{ + audio->start_time = ktime_get(); + audio->frames_sent = 0; + audio_send(audio); +} + +static void audio_pcm_playback_stop(struct audio_dev *audio) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + audio->buffer_start = 0; + audio->buffer_end = 0; + audio->buffer_pos = 0; + spin_unlock_irqrestore(&audio->lock, flags); +} + +static int audio_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = substream->private_data; + + runtime->private_data = audio; + runtime->hw = audio_hw_info; +#ifdef CONFIG_SOUND + snd_pcm_limit_hw_rates(runtime); +#endif + runtime->hw.channels_max = 2; + + audio->substream = substream; + return 0; +} + +static int audio_pcm_close(struct snd_pcm_substream *substream) +{ + struct audio_dev *audio = substream->private_data; + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + audio->substream = NULL; + spin_unlock_irqrestore(&audio->lock, flags); + + return 0; +} + +static int audio_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + + if (rate != SAMPLE_RATE) + return -EINVAL; + if (channels != 2) + return -EINVAL; +#ifdef CONFIG_SOUND + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +#else + return 1; +#endif +} + +static int audio_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int audio_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = runtime->private_data; + + audio->period = snd_pcm_lib_period_bytes(substream); + audio->period_offset = 0; + audio->buffer_start = runtime->dma_area; + audio->buffer_end = audio->buffer_start + + snd_pcm_lib_buffer_bytes(substream); + audio->buffer_pos = audio->buffer_start; + + return 0; +} + +static snd_pcm_uframes_t audio_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = runtime->private_data; + ssize_t bytes = audio->buffer_pos - audio->buffer_start; + + /* return offset of next frame to fill in our buffer */ + return bytes_to_frames(runtime, bytes); +} + +static int audio_pcm_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct audio_dev *audio = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + audio_pcm_playback_start(audio); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + audio_pcm_playback_stop(audio); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static struct audio_dev _audio_dev = { + .func = { + .name = "audio_source", + .bind = audio_bind, + .unbind = audio_unbind, + .set_alt = audio_set_alt, + .setup = audio_setup, + .disable = audio_disable, + }, + .lock = __SPIN_LOCK_UNLOCKED(_audio_dev.lock), + .idle_reqs = LIST_HEAD_INIT(_audio_dev.idle_reqs), +}; + +static struct snd_pcm_ops audio_playback_ops = { + .open = audio_pcm_open, + .close = audio_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = audio_pcm_hw_params, + .hw_free = audio_pcm_hw_free, + .prepare = audio_pcm_prepare, + .trigger = audio_pcm_playback_trigger, + .pointer = audio_pcm_pointer, +}; + +int audio_source_bind_config(struct usb_configuration *c, + struct audio_source_config *config) +{ + struct audio_dev *audio; + struct snd_card *card; + struct snd_pcm *pcm; + int err; + + config->card = -1; + config->device = -1; + + audio = &_audio_dev; + + err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err) + return err; + + snd_card_set_dev(card, &c->cdev->gadget->dev); + + err = snd_pcm_new(card, "USB audio source", 0, 1, 0, &pcm); + if (err) + goto pcm_fail; + pcm->private_data = audio; + pcm->info_flags = 0; + audio->pcm = pcm; + + strlcpy(pcm->name, "USB gadget audio", sizeof(pcm->name)); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_playback_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + NULL, 0, 64 * 1024); + + strlcpy(card->driver, "audio_source", sizeof(card->driver)); + strlcpy(card->shortname, card->driver, sizeof(card->shortname)); + strlcpy(card->longname, "USB accessory audio source", + sizeof(card->longname)); + + err = snd_card_register(card); + if (err) + goto register_fail; + + err = usb_add_function(c, &audio->func); + if (err) + goto add_fail; + + config->card = pcm->card->number; + config->device = pcm->device; + audio->card = card; + return 0; + +add_fail: +register_fail: +pcm_fail: + snd_card_free(audio->card); + return err; +} diff --git a/drivers/usb/gadget/f_ecm.c b/drivers/usb/gadget/f_ecm.c new file mode 100644 index 00000000..abf8a31a --- /dev/null +++ b/drivers/usb/gadget/f_ecm.c @@ -0,0 +1,874 @@ +/* + * f_ecm.c -- USB CDC Ethernet (ECM) link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include + +#include "u_ether.h" + + +/* + * This function is a "CDC Ethernet Networking Control Model" (CDC ECM) + * Ethernet link. The data transfer model is simple (packets sent and + * received over bulk endpoints using normal short packet termination), + * and the control model exposes various data and optional notifications. + * + * ECM is well standardized and (except for Microsoft) supported by most + * operating systems with USB host support. It's the preferred interop + * solution for Ethernet over USB, at least for firmware based solutions. + * (Hardware solutions tend to be more minimalist.) A newer and simpler + * "Ethernet Emulation Model" (CDC EEM) hasn't yet caught on. + * + * Note that ECM requires the use of "alternate settings" for its data + * interface. This means that the set_alt() method has real work to do, + * and also means that a get_alt() method is required. + */ + + +enum ecm_notify_state { + ECM_NOTIFY_NONE, /* don't notify */ + ECM_NOTIFY_CONNECT, /* issue CONNECT next */ + ECM_NOTIFY_SPEED, /* issue SPEED_CHANGE next */ +}; + +struct f_ecm { + struct gether port; + u8 ctrl_id, data_id; + + char ethaddr[14]; + + struct usb_ep *notify; + struct usb_request *notify_req; + u8 notify_state; + bool is_open; + + /* FIXME is_open needs some irq-ish locking + * ... possibly the same as port.ioport + */ +}; + +static inline struct f_ecm *func_to_ecm(struct usb_function *f) +{ + return container_of(f, struct f_ecm, port.func); +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static inline unsigned ecm_bitrate(struct usb_gadget *g) +{ + if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) + return 13 * 1024 * 8 * 1000 * 8; + else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Include the status endpoint if we can, even though it's optional. + * + * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one + * packet, to simplify cancellation; and a big transfer interval, to + * waste less bandwidth. + * + * Some drivers (like Linux 2.4 cdc-ether!) "need" it to exist even + * if they ignore the connect/disconnect notifications that real aether + * can provide. More advanced cdc configurations might want to support + * encapsulated commands (vendor-specific, using control-OUT). + */ + +#define ECM_STATUS_INTERVAL_MS 32 +#define ECM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ + + +/* interface descriptor: */ + +static struct usb_interface_assoc_descriptor +ecm_iad_descriptor = { + .bLength = sizeof ecm_iad_descriptor, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, /* control + data */ + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bFunctionProtocol = USB_CDC_PROTO_NONE, + /* .iFunction = DYNAMIC */ +}; + + +static struct usb_interface_descriptor ecm_control_intf = { + .bLength = sizeof ecm_control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + /* status endpoint is optional; this could be patched later */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc ecm_header_desc = { + .bLength = sizeof ecm_header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_union_desc ecm_union_desc = { + .bLength = sizeof(ecm_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +static struct usb_cdc_ether_desc ecm_desc = { + .bLength = sizeof ecm_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, + + /* this descriptor actually adds value, surprise! */ + /* .iMACAddress = DYNAMIC */ + .bmEthernetStatistics = cpu_to_le32(0), /* no statistics */ + .wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN), + .wNumberMCFilters = cpu_to_le16(0), + .bNumberPowerFilters = 0, +}; + +/* the default data interface has no endpoints ... */ + +static struct usb_interface_descriptor ecm_data_nop_intf = { + .bLength = sizeof ecm_data_nop_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +/* ... but the "real" data interface has two bulk endpoints */ + +static struct usb_interface_descriptor ecm_data_intf = { + .bLength = sizeof ecm_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_ecm_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT), + .bInterval = ECM_STATUS_INTERVAL_MS, +}; + +static struct usb_endpoint_descriptor fs_ecm_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_ecm_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *ecm_fs_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_iad_descriptor, + (struct usb_descriptor_header *) &ecm_control_intf, + (struct usb_descriptor_header *) &ecm_header_desc, + (struct usb_descriptor_header *) &ecm_union_desc, + (struct usb_descriptor_header *) &ecm_desc, + + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &fs_ecm_notify_desc, + + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_data_nop_intf, + (struct usb_descriptor_header *) &ecm_data_intf, + (struct usb_descriptor_header *) &fs_ecm_in_desc, + (struct usb_descriptor_header *) &fs_ecm_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_ecm_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT), + .bInterval = USB_MS_TO_HS_INTERVAL(ECM_STATUS_INTERVAL_MS), +}; + +static struct usb_endpoint_descriptor hs_ecm_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_ecm_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *ecm_hs_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_iad_descriptor, + (struct usb_descriptor_header *) &ecm_control_intf, + (struct usb_descriptor_header *) &ecm_header_desc, + (struct usb_descriptor_header *) &ecm_union_desc, + (struct usb_descriptor_header *) &ecm_desc, + + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &hs_ecm_notify_desc, + + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_data_nop_intf, + (struct usb_descriptor_header *) &ecm_data_intf, + (struct usb_descriptor_header *) &hs_ecm_in_desc, + (struct usb_descriptor_header *) &hs_ecm_out_desc, + NULL, +}; + +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_ecm_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT), + .bInterval = USB_MS_TO_HS_INTERVAL(ECM_STATUS_INTERVAL_MS), +}; + +static struct usb_ss_ep_comp_descriptor ss_ecm_intr_comp_desc = { + .bLength = sizeof ss_ecm_intr_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(ECM_STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor ss_ecm_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor ss_ecm_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ss_ecm_bulk_comp_desc = { + .bLength = sizeof ss_ecm_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *ecm_ss_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_iad_descriptor, + (struct usb_descriptor_header *) &ecm_control_intf, + (struct usb_descriptor_header *) &ecm_header_desc, + (struct usb_descriptor_header *) &ecm_union_desc, + (struct usb_descriptor_header *) &ecm_desc, + + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &ss_ecm_notify_desc, + (struct usb_descriptor_header *) &ss_ecm_intr_comp_desc, + + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_data_nop_intf, + (struct usb_descriptor_header *) &ecm_data_intf, + (struct usb_descriptor_header *) &ss_ecm_in_desc, + (struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc, + (struct usb_descriptor_header *) &ss_ecm_out_desc, + (struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +static struct usb_string ecm_string_defs[] = { + [0].s = "CDC Ethernet Control Model (ECM)", + [1].s = "", + [2].s = "CDC Ethernet Data", + [3].s = "CDC ECM", + { } /* end of list */ +}; + +static struct usb_gadget_strings ecm_string_table = { + .language = 0x0409, /* en-us */ + .strings = ecm_string_defs, +}; + +static struct usb_gadget_strings *ecm_strings[] = { + &ecm_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static void ecm_do_notify(struct f_ecm *ecm) +{ + struct usb_request *req = ecm->notify_req; + struct usb_cdc_notification *event; + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + __le32 *data; + int status; + + /* notification already in flight? */ + if (!req) + return; + + event = req->buf; + switch (ecm->notify_state) { + case ECM_NOTIFY_NONE: + return; + + case ECM_NOTIFY_CONNECT: + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + if (ecm->is_open) + event->wValue = cpu_to_le16(1); + else + event->wValue = cpu_to_le16(0); + event->wLength = 0; + req->length = sizeof *event; + + DBG(cdev, "notify connect %s\n", + ecm->is_open ? "true" : "false"); + ecm->notify_state = ECM_NOTIFY_SPEED; + break; + + case ECM_NOTIFY_SPEED: + event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; + event->wValue = cpu_to_le16(0); + event->wLength = cpu_to_le16(8); + req->length = ECM_STATUS_BYTECOUNT; + + /* SPEED_CHANGE data is up/down speeds in bits/sec */ + data = req->buf + sizeof *event; + data[0] = cpu_to_le32(ecm_bitrate(cdev->gadget)); + data[1] = data[0]; + + DBG(cdev, "notify speed %d\n", ecm_bitrate(cdev->gadget)); + ecm->notify_state = ECM_NOTIFY_NONE; + break; + } + event->bmRequestType = 0xA1; + event->wIndex = cpu_to_le16(ecm->ctrl_id); + + ecm->notify_req = NULL; + status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC); + if (status < 0) { + ecm->notify_req = req; + DBG(cdev, "notify --> %d\n", status); + } +} + +static void ecm_notify(struct f_ecm *ecm) +{ + /* NOTE on most versions of Linux, host side cdc-ethernet + * won't listen for notifications until its netdevice opens. + * The first notification then sits in the FIFO for a long + * time, and the second one is queued. + */ + ecm->notify_state = ECM_NOTIFY_CONNECT; + ecm_do_notify(ecm); +} + +static void ecm_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ecm *ecm = req->context; + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + struct usb_cdc_notification *event = req->buf; + + switch (req->status) { + case 0: + /* no fault */ + break; + case -ECONNRESET: + case -ESHUTDOWN: + ecm->notify_state = ECM_NOTIFY_NONE; + break; + default: + DBG(cdev, "event %02x --> %d\n", + event->bNotificationType, req->status); + break; + } + ecm->notify_req = req; + ecm_do_notify(ecm); +} + +static int ecm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_ecm *ecm = func_to_ecm(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_ETHERNET_PACKET_FILTER: + /* see 6.2.30: no data, wIndex = interface, + * wValue = packet filter bitmap + */ + if (w_length != 0 || w_index != ecm->ctrl_id) + goto invalid; + DBG(cdev, "packet filter %02x\n", w_value); + /* REVISIT locking of cdc_filter. This assumes the UDC + * driver won't have a concurrent packet TX irq running on + * another CPU; or that if it does, this write is atomic... + */ + ecm->port.cdc_filter = w_value; + value = 0; + break; + + /* and optionally: + * case USB_CDC_SEND_ENCAPSULATED_COMMAND: + * case USB_CDC_GET_ENCAPSULATED_RESPONSE: + * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS: + * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_STATISTIC: + */ + + default: +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "ecm req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "ecm req %02x.%02x response err %d\n", + ctrl->bRequestType, ctrl->bRequest, + value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + + +static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_ecm *ecm = func_to_ecm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* Control interface has only altsetting 0 */ + if (intf == ecm->ctrl_id) { + if (alt != 0) + goto fail; + + if (ecm->notify->driver_data) { + VDBG(cdev, "reset ecm control %d\n", intf); + usb_ep_disable(ecm->notify); + } + if (!(ecm->notify->desc)) { + VDBG(cdev, "init ecm ctrl %d\n", intf); + if (config_ep_by_speed(cdev->gadget, f, ecm->notify)) + goto fail; + } + usb_ep_enable(ecm->notify); + ecm->notify->driver_data = ecm; + + /* Data interface has two altsettings, 0 and 1 */ + } else if (intf == ecm->data_id) { + if (alt > 1) + goto fail; + + if (ecm->port.in_ep->driver_data) { + DBG(cdev, "reset ecm\n"); + gether_disconnect(&ecm->port); + } + + if (!ecm->port.in_ep->desc || + !ecm->port.out_ep->desc) { + DBG(cdev, "init ecm\n"); + if (config_ep_by_speed(cdev->gadget, f, + ecm->port.in_ep) || + config_ep_by_speed(cdev->gadget, f, + ecm->port.out_ep)) { + ecm->port.in_ep->desc = NULL; + ecm->port.out_ep->desc = NULL; + goto fail; + } + } + + /* CDC Ethernet only sends data in non-default altsettings. + * Changing altsettings resets filters, statistics, etc. + */ + if (alt == 1) { + struct net_device *net; + + /* Enable zlps by default for ECM conformance; + * override for musb_hdrc (avoids txdma ovhead). + */ + ecm->port.is_zlp_ok = !(gadget_is_musbhdrc(cdev->gadget) + ); + ecm->port.cdc_filter = DEFAULT_FILTER; + DBG(cdev, "activate ecm\n"); + net = gether_connect(&ecm->port); + if (IS_ERR(net)) + return PTR_ERR(net); + } + + /* NOTE this can be a minor disagreement with the ECM spec, + * which says speed notifications will "always" follow + * connection notifications. But we allow one connect to + * follow another (if the first is in flight), and instead + * just guarantee that a speed notification is always sent. + */ + ecm_notify(ecm); + } else + goto fail; + + return 0; +fail: + return -EINVAL; +} + +/* Because the data interface supports multiple altsettings, + * this ECM function *MUST* implement a get_alt() method. + */ +static int ecm_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_ecm *ecm = func_to_ecm(f); + + if (intf == ecm->ctrl_id) + return 0; + return ecm->port.in_ep->driver_data ? 1 : 0; +} + +static void ecm_disable(struct usb_function *f) +{ + struct f_ecm *ecm = func_to_ecm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "ecm deactivated\n"); + + if (ecm->port.in_ep->driver_data) + gether_disconnect(&ecm->port); + + if (ecm->notify->driver_data) { + usb_ep_disable(ecm->notify); + ecm->notify->driver_data = NULL; + ecm->notify->desc = NULL; + } +} + +/*-------------------------------------------------------------------------*/ + +/* + * Callbacks let us notify the host about connect/disconnect when the + * net device is opened or closed. + * + * For testing, note that link states on this side include both opened + * and closed variants of: + * + * - disconnected/unconfigured + * - configured but inactive (data alt 0) + * - configured and active (data alt 1) + * + * Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and + * SET_INTERFACE (altsetting). Remember also that "configured" doesn't + * imply the host is actually polling the notification endpoint, and + * likewise that "active" doesn't imply it's actually using the data + * endpoints for traffic. + */ + +static void ecm_open(struct gether *geth) +{ + struct f_ecm *ecm = func_to_ecm(&geth->func); + + DBG(ecm->port.func.config->cdev, "%s\n", __func__); + + ecm->is_open = true; + ecm_notify(ecm); +} + +static void ecm_close(struct gether *geth) +{ + struct f_ecm *ecm = func_to_ecm(&geth->func); + + DBG(ecm->port.func.config->cdev, "%s\n", __func__); + + ecm->is_open = false; + ecm_notify(ecm); +} + +/*-------------------------------------------------------------------------*/ + +/* ethernet function driver setup/binding */ + +static int +ecm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_ecm *ecm = func_to_ecm(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ecm->ctrl_id = status; + ecm_iad_descriptor.bFirstInterface = status; + + ecm_control_intf.bInterfaceNumber = status; + ecm_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ecm->data_id = status; + + ecm_data_nop_intf.bInterfaceNumber = status; + ecm_data_intf.bInterfaceNumber = status; + ecm_union_desc.bSlaveInterface0 = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc); + if (!ep) + goto fail; + ecm->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc); + if (!ep) + goto fail; + ecm->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + /* NOTE: a status/notification endpoint is *OPTIONAL* but we + * don't treat it that way. It's simpler, and some newer CDC + * profiles (wireless handsets) no longer treat it as optional. + */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_notify_desc); + if (!ep) + goto fail; + ecm->notify = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!ecm->notify_req) + goto fail; + ecm->notify_req->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!ecm->notify_req->buf) + goto fail; + ecm->notify_req->context = ecm; + ecm->notify_req->complete = ecm_notify_complete; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + hs_ecm_in_desc.bEndpointAddress = fs_ecm_in_desc.bEndpointAddress; + hs_ecm_out_desc.bEndpointAddress = fs_ecm_out_desc.bEndpointAddress; + hs_ecm_notify_desc.bEndpointAddress = + fs_ecm_notify_desc.bEndpointAddress; + + ss_ecm_in_desc.bEndpointAddress = fs_ecm_in_desc.bEndpointAddress; + ss_ecm_out_desc.bEndpointAddress = fs_ecm_out_desc.bEndpointAddress; + ss_ecm_notify_desc.bEndpointAddress = + fs_ecm_notify_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, ecm_fs_function, ecm_hs_function, + ecm_ss_function); + if (status) + goto fail; + + /* NOTE: all that is done without knowing or caring about + * the network link ... which is unavailable to this code + * until we're activated via set_alt(). + */ + + ecm->port.open = ecm_open; + ecm->port.close = ecm_close; + + DBG(cdev, "CDC Ethernet: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + ecm->port.in_ep->name, ecm->port.out_ep->name, + ecm->notify->name); + return 0; + +fail: + if (ecm->notify_req) { + kfree(ecm->notify_req->buf); + usb_ep_free_request(ecm->notify, ecm->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (ecm->notify) + ecm->notify->driver_data = NULL; + if (ecm->port.out_ep) + ecm->port.out_ep->driver_data = NULL; + if (ecm->port.in_ep) + ecm->port.in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +ecm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ecm *ecm = func_to_ecm(f); + + DBG(c->cdev, "ecm unbind\n"); + + ecm_string_defs[0].id = 0; + usb_free_all_descriptors(f); + + kfree(ecm->notify_req->buf); + usb_ep_free_request(ecm->notify, ecm->notify_req); + kfree(ecm); +} + +/** + * ecm_bind_config - add CDC Ethernet network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * @dev: eth_dev structure + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +int +ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + struct eth_dev *dev) +{ + struct f_ecm *ecm; + int status; + + if (!can_support_ecm(c->cdev->gadget) || !ethaddr) + return -EINVAL; + + if (ecm_string_defs[0].id == 0) { + status = usb_string_ids_tab(c->cdev, ecm_string_defs); + if (status) + return status; + + ecm_control_intf.iInterface = ecm_string_defs[0].id; + ecm_data_intf.iInterface = ecm_string_defs[2].id; + ecm_desc.iMACAddress = ecm_string_defs[1].id; + ecm_iad_descriptor.iFunction = ecm_string_defs[3].id; + } + + /* allocate and initialize one new instance */ + ecm = kzalloc(sizeof *ecm, GFP_KERNEL); + if (!ecm) + return -ENOMEM; + + /* export host's Ethernet address in CDC format */ + snprintf(ecm->ethaddr, sizeof ecm->ethaddr, "%pm", ethaddr); + ecm_string_defs[1].s = ecm->ethaddr; + + ecm->port.ioport = dev; + ecm->port.cdc_filter = DEFAULT_FILTER; + + ecm->port.func.name = "cdc_ethernet"; + ecm->port.func.strings = ecm_strings; + /* descriptors are per-instance copies */ + ecm->port.func.bind = ecm_bind; + ecm->port.func.unbind = ecm_unbind; + ecm->port.func.set_alt = ecm_set_alt; + ecm->port.func.get_alt = ecm_get_alt; + ecm->port.func.setup = ecm_setup; + ecm->port.func.disable = ecm_disable; + + status = usb_add_function(c, &ecm->port.func); + if (status) + kfree(ecm); + return status; +} diff --git a/drivers/usb/gadget/f_eem.c b/drivers/usb/gadget/f_eem.c new file mode 100644 index 00000000..f4e0bbef --- /dev/null +++ b/drivers/usb/gadget/f_eem.c @@ -0,0 +1,572 @@ +/* + * f_eem.c -- USB CDC Ethernet (EEM) link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2009 EF Johnson Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include "u_ether.h" + +#define EEM_HLEN 2 + +/* + * This function is a "CDC Ethernet Emulation Model" (CDC EEM) + * Ethernet link. + */ + +struct f_eem { + struct gether port; + u8 ctrl_id; +}; + +static inline struct f_eem *func_to_eem(struct usb_function *f) +{ + return container_of(f, struct f_eem, port.func); +} + +/*-------------------------------------------------------------------------*/ + +/* interface descriptor: */ + +static struct usb_interface_descriptor eem_intf __initdata = { + .bLength = sizeof eem_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_EEM, + .bInterfaceProtocol = USB_CDC_PROTO_EEM, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor eem_fs_in_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor eem_fs_out_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *eem_fs_function[] __initdata = { + /* CDC EEM control descriptors */ + (struct usb_descriptor_header *) &eem_intf, + (struct usb_descriptor_header *) &eem_fs_in_desc, + (struct usb_descriptor_header *) &eem_fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor eem_hs_in_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor eem_hs_out_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *eem_hs_function[] __initdata = { + /* CDC EEM control descriptors */ + (struct usb_descriptor_header *) &eem_intf, + (struct usb_descriptor_header *) &eem_hs_in_desc, + (struct usb_descriptor_header *) &eem_hs_out_desc, + NULL, +}; + +/* super speed support: */ + +static struct usb_endpoint_descriptor eem_ss_in_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor eem_ss_out_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor eem_ss_bulk_comp_desc __initdata = { + .bLength = sizeof eem_ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *eem_ss_function[] __initdata = { + /* CDC EEM control descriptors */ + (struct usb_descriptor_header *) &eem_intf, + (struct usb_descriptor_header *) &eem_ss_in_desc, + (struct usb_descriptor_header *) &eem_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &eem_ss_out_desc, + (struct usb_descriptor_header *) &eem_ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +static struct usb_string eem_string_defs[] = { + [0].s = "CDC Ethernet Emulation Model (EEM)", + { } /* end of list */ +}; + +static struct usb_gadget_strings eem_string_table = { + .language = 0x0409, /* en-us */ + .strings = eem_string_defs, +}; + +static struct usb_gadget_strings *eem_strings[] = { + &eem_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int eem_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* device either stalls (value < 0) or reports success */ + return value; +} + + +static int eem_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_eem *eem = func_to_eem(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct net_device *net; + + /* we know alt == 0, so this is an activation or a reset */ + if (alt != 0) + goto fail; + + if (intf == eem->ctrl_id) { + + if (eem->port.in_ep->driver_data) { + DBG(cdev, "reset eem\n"); + gether_disconnect(&eem->port); + } + + if (!eem->port.in_ep->desc || !eem->port.out_ep->desc) { + DBG(cdev, "init eem\n"); + if (config_ep_by_speed(cdev->gadget, f, + eem->port.in_ep) || + config_ep_by_speed(cdev->gadget, f, + eem->port.out_ep)) { + eem->port.in_ep->desc = NULL; + eem->port.out_ep->desc = NULL; + goto fail; + } + } + + /* zlps should not occur because zero-length EEM packets + * will be inserted in those cases where they would occur + */ + eem->port.is_zlp_ok = 1; + eem->port.cdc_filter = DEFAULT_FILTER; + DBG(cdev, "activate eem\n"); + net = gether_connect(&eem->port); + if (IS_ERR(net)) + return PTR_ERR(net); + } else + goto fail; + + return 0; +fail: + return -EINVAL; +} + +static void eem_disable(struct usb_function *f) +{ + struct f_eem *eem = func_to_eem(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "eem deactivated\n"); + + if (eem->port.in_ep->driver_data) + gether_disconnect(&eem->port); +} + +/*-------------------------------------------------------------------------*/ + +/* EEM function driver setup/binding */ + +static int __init +eem_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_eem *eem = func_to_eem(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + eem->ctrl_id = status; + eem_intf.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_in_desc); + if (!ep) + goto fail; + eem->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_out_desc); + if (!ep) + goto fail; + eem->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + eem_hs_in_desc.bEndpointAddress = eem_fs_in_desc.bEndpointAddress; + eem_hs_out_desc.bEndpointAddress = eem_fs_out_desc.bEndpointAddress; + + eem_ss_in_desc.bEndpointAddress = eem_fs_in_desc.bEndpointAddress; + eem_ss_out_desc.bEndpointAddress = eem_fs_out_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, eem_fs_function, eem_hs_function, + eem_ss_function); + if (status) + goto fail; + + DBG(cdev, "CDC Ethernet (EEM): %s speed IN/%s OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + eem->port.in_ep->name, eem->port.out_ep->name); + return 0; + +fail: + usb_free_all_descriptors(f); + if (eem->port.out_ep) + eem->port.out_ep->driver_data = NULL; + if (eem->port.in_ep) + eem->port.in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +eem_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_eem *eem = func_to_eem(f); + + DBG(c->cdev, "eem unbind\n"); + + usb_free_all_descriptors(f); + kfree(eem); +} + +static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sk_buff *skb = (struct sk_buff *)req->context; + + dev_kfree_skb_any(skb); +} + +/* + * Add the EEM header and ethernet checksum. + * We currently do not attempt to put multiple ethernet frames + * into a single USB transfer + */ +static struct sk_buff *eem_wrap(struct gether *port, struct sk_buff *skb) +{ + struct sk_buff *skb2 = NULL; + struct usb_ep *in = port->in_ep; + int padlen = 0; + u16 len = skb->len; + + if (!skb_cloned(skb)) { + int headroom = skb_headroom(skb); + int tailroom = skb_tailroom(skb); + + /* When (len + EEM_HLEN + ETH_FCS_LEN) % in->maxpacket) is 0, + * stick two bytes of zero-length EEM packet on the end. + */ + if (((len + EEM_HLEN + ETH_FCS_LEN) % in->maxpacket) == 0) + padlen += 2; + + if ((tailroom >= (ETH_FCS_LEN + padlen)) && + (headroom >= EEM_HLEN)) + goto done; + } + + skb2 = skb_copy_expand(skb, EEM_HLEN, ETH_FCS_LEN + padlen, GFP_ATOMIC); + dev_kfree_skb_any(skb); + skb = skb2; + if (!skb) + return skb; + +done: + /* use the "no CRC" option */ + put_unaligned_be32(0xdeadbeef, skb_put(skb, 4)); + + /* EEM packet header format: + * b0..13: length of ethernet frame + * b14: bmCRC (0 == sentinel CRC) + * b15: bmType (0 == data) + */ + len = skb->len; + put_unaligned_le16(len & 0x3FFF, skb_push(skb, 2)); + + /* add a zero-length EEM packet, if needed */ + if (padlen) + put_unaligned_le16(0, skb_put(skb, 2)); + + return skb; +} + +/* + * Remove the EEM header. Note that there can be many EEM packets in a single + * USB transfer, so we need to break them out and handle them independently. + */ +static int eem_unwrap(struct gether *port, + struct sk_buff *skb, + struct sk_buff_head *list) +{ + struct usb_composite_dev *cdev = port->func.config->cdev; + int status = 0; + + do { + struct sk_buff *skb2; + u16 header; + u16 len = 0; + + if (skb->len < EEM_HLEN) { + status = -EINVAL; + DBG(cdev, "invalid EEM header\n"); + goto error; + } + + /* remove the EEM header */ + header = get_unaligned_le16(skb->data); + skb_pull(skb, EEM_HLEN); + + /* EEM packet header format: + * b0..14: EEM type dependent (data or command) + * b15: bmType (0 == data, 1 == command) + */ + if (header & BIT(15)) { + struct usb_request *req = cdev->req; + u16 bmEEMCmd; + + /* EEM command packet format: + * b0..10: bmEEMCmdParam + * b11..13: bmEEMCmd + * b14: reserved (must be zero) + * b15: bmType (1 == command) + */ + if (header & BIT(14)) + continue; + + bmEEMCmd = (header >> 11) & 0x7; + switch (bmEEMCmd) { + case 0: /* echo */ + len = header & 0x7FF; + if (skb->len < len) { + status = -EOVERFLOW; + goto error; + } + + skb2 = skb_clone(skb, GFP_ATOMIC); + if (unlikely(!skb2)) { + DBG(cdev, "EEM echo response error\n"); + goto next; + } + skb_trim(skb2, len); + put_unaligned_le16(BIT(15) | BIT(11) | len, + skb_push(skb2, 2)); + skb_copy_bits(skb2, 0, req->buf, skb2->len); + req->length = skb2->len; + req->complete = eem_cmd_complete; + req->zero = 1; + req->context = skb2; + if (usb_ep_queue(port->in_ep, req, GFP_ATOMIC)) + DBG(cdev, "echo response queue fail\n"); + break; + + case 1: /* echo response */ + case 2: /* suspend hint */ + case 3: /* response hint */ + case 4: /* response complete hint */ + case 5: /* tickle */ + default: /* reserved */ + continue; + } + } else { + u32 crc, crc2; + struct sk_buff *skb3; + + /* check for zero-length EEM packet */ + if (header == 0) + continue; + + /* EEM data packet format: + * b0..13: length of ethernet frame + * b14: bmCRC (0 == sentinel, 1 == calculated) + * b15: bmType (0 == data) + */ + len = header & 0x3FFF; + if ((skb->len < len) + || (len < (ETH_HLEN + ETH_FCS_LEN))) { + status = -EINVAL; + goto error; + } + + /* validate CRC */ + if (header & BIT(14)) { + crc = get_unaligned_le32(skb->data + len + - ETH_FCS_LEN); + crc2 = ~crc32_le(~0, + skb->data, len - ETH_FCS_LEN); + } else { + crc = get_unaligned_be32(skb->data + len + - ETH_FCS_LEN); + crc2 = 0xdeadbeef; + } + if (crc != crc2) { + DBG(cdev, "invalid EEM CRC\n"); + goto next; + } + + skb2 = skb_clone(skb, GFP_ATOMIC); + if (unlikely(!skb2)) { + DBG(cdev, "unable to unframe EEM packet\n"); + continue; + } + skb_trim(skb2, len - ETH_FCS_LEN); + + skb3 = skb_copy_expand(skb2, + NET_IP_ALIGN, + 0, + GFP_ATOMIC); + if (unlikely(!skb3)) { + DBG(cdev, "unable to realign EEM packet\n"); + dev_kfree_skb_any(skb2); + continue; + } + dev_kfree_skb_any(skb2); + skb_queue_tail(list, skb3); + } +next: + skb_pull(skb, len); + } while (skb->len); + +error: + dev_kfree_skb_any(skb); + return status; +} + +/** + * eem_bind_config - add CDC Ethernet (EEM) network link to a configuration + * @c: the configuration to support the network link + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +int __init eem_bind_config(struct usb_configuration *c, struct eth_dev *dev) +{ + struct f_eem *eem; + int status; + + /* maybe allocate device-global string IDs */ + if (eem_string_defs[0].id == 0) { + + /* control interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + eem_string_defs[0].id = status; + eem_intf.iInterface = status; + } + + /* allocate and initialize one new instance */ + eem = kzalloc(sizeof *eem, GFP_KERNEL); + if (!eem) + return -ENOMEM; + + eem->port.ioport = dev; + eem->port.cdc_filter = DEFAULT_FILTER; + + eem->port.func.name = "cdc_eem"; + eem->port.func.strings = eem_strings; + /* descriptors are per-instance copies */ + eem->port.func.bind = eem_bind; + eem->port.func.unbind = eem_unbind; + eem->port.func.set_alt = eem_set_alt; + eem->port.func.setup = eem_setup; + eem->port.func.disable = eem_disable; + eem->port.wrap = eem_wrap; + eem->port.unwrap = eem_unwrap; + eem->port.header_len = EEM_HLEN; + + status = usb_add_function(c, &eem->port.func); + if (status) + kfree(eem); + return status; +} + diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c new file mode 100644 index 00000000..1b35deab --- /dev/null +++ b/drivers/usb/gadget/f_fs.c @@ -0,0 +1,2548 @@ +/* + * f_fs.c -- user mode file system API for USB composite function controllers + * + * Copyright (C) 2010 Samsung Electronics + * Author: Michal Nazarewicz + * + * Based on inode.c (GadgetFS) which was: + * Copyright (C) 2003-2004 David Brownell + * Copyright (C) 2003 Agilent Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include + +#include +#include + + +#define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */ + + +/* Debugging ****************************************************************/ + +#ifdef VERBOSE_DEBUG +#ifndef pr_vdebug +# define pr_vdebug pr_debug +#endif /* pr_vdebug */ +# define ffs_dump_mem(prefix, ptr, len) \ + print_hex_dump_bytes(pr_fmt(prefix ": "), DUMP_PREFIX_NONE, ptr, len) +#else +#ifndef pr_vdebug +# define pr_vdebug(...) do { } while (0) +#endif /* pr_vdebug */ +# define ffs_dump_mem(prefix, ptr, len) do { } while (0) +#endif /* VERBOSE_DEBUG */ + +#define ENTER() pr_vdebug("%s()\n", __func__) + + +/* The data structure and setup file ****************************************/ + +enum ffs_state { + /* + * Waiting for descriptors and strings. + * + * In this state no open(2), read(2) or write(2) on epfiles + * may succeed (which should not be the problem as there + * should be no such files opened in the first place). + */ + FFS_READ_DESCRIPTORS, + FFS_READ_STRINGS, + + /* + * We've got descriptors and strings. We are or have called + * functionfs_ready_callback(). functionfs_bind() may have + * been called but we don't know. + * + * This is the only state in which operations on epfiles may + * succeed. + */ + FFS_ACTIVE, + + /* + * All endpoints have been closed. This state is also set if + * we encounter an unrecoverable error. The only + * unrecoverable error is situation when after reading strings + * from user space we fail to initialise epfiles or + * functionfs_ready_callback() returns with error (<0). + * + * In this state no open(2), read(2) or write(2) (both on ep0 + * as well as epfile) may succeed (at this point epfiles are + * unlinked and all closed so this is not a problem; ep0 is + * also closed but ep0 file exists and so open(2) on ep0 must + * fail). + */ + /*add by chunhou.wang for kernel crash bug 378800*/ + FFS_DEACTIVATED, + /*end add by chunhou.wang*/ + FFS_CLOSING +}; + + +enum ffs_setup_state { + /* There is no setup request pending. */ + FFS_NO_SETUP, + /* + * User has read events and there was a setup request event + * there. The next read/write on ep0 will handle the + * request. + */ + FFS_SETUP_PENDING, + /* + * There was event pending but before user space handled it + * some other event was introduced which canceled existing + * setup. If this state is set read/write on ep0 return + * -EIDRM. This state is only set when adding event. + */ + FFS_SETUP_CANCELED +}; + + + +struct ffs_epfile; +struct ffs_function; + +struct ffs_data { + struct usb_gadget *gadget; + + /* + * Protect access read/write operations, only one read/write + * at a time. As a consequence protects ep0req and company. + * While setup request is being processed (queued) this is + * held. + */ + struct mutex mutex; + + /* + * Protect access to endpoint related structures (basically + * usb_ep_queue(), usb_ep_dequeue(), etc. calls) except for + * endpoint zero. + */ + spinlock_t eps_lock; + + /* + * XXX REVISIT do we need our own request? Since we are not + * handling setup requests immediately user space may be so + * slow that another setup will be sent to the gadget but this + * time not to us but another function and then there could be + * a race. Is that the case? Or maybe we can use cdev->req + * after all, maybe we just need some spinlock for that? + */ + struct usb_request *ep0req; /* P: mutex */ + struct completion ep0req_completion; /* P: mutex */ + int ep0req_status; /* P: mutex */ + struct completion epin_completion; /* P: mutex */ + struct completion epout_completion; /* P: mutex */ + + /* reference counter */ + atomic_t ref; + /* how many files are opened (EP0 and others) */ + atomic_t opened; + + /* EP0 state */ + enum ffs_state state; + + /* + * Possible transitions: + * + FFS_NO_SETUP -> FFS_SETUP_PENDING -- P: ev.waitq.lock + * happens only in ep0 read which is P: mutex + * + FFS_SETUP_PENDING -> FFS_NO_SETUP -- P: ev.waitq.lock + * happens only in ep0 i/o which is P: mutex + * + FFS_SETUP_PENDING -> FFS_SETUP_CANCELED -- P: ev.waitq.lock + * + FFS_SETUP_CANCELED -> FFS_NO_SETUP -- cmpxchg + */ + enum ffs_setup_state setup_state; + +#define FFS_SETUP_STATE(ffs) \ + ((enum ffs_setup_state)cmpxchg(&(ffs)->setup_state, \ + FFS_SETUP_CANCELED, FFS_NO_SETUP)) + + /* Events & such. */ + struct { + u8 types[4]; + unsigned short count; + /* XXX REVISIT need to update it in some places, or do we? */ + unsigned short can_stall; + struct usb_ctrlrequest setup; + + wait_queue_head_t waitq; + } ev; /* the whole structure, P: ev.waitq.lock */ + + /* Flags */ + unsigned long flags; +#define FFS_FL_CALL_CLOSED_CALLBACK 0 +#define FFS_FL_BOUND 1 + + /* Active function */ + struct ffs_function *func; + + /* + * Device name, write once when file system is mounted. + * Intended for user to read if she wants. + */ + const char *dev_name; + /* Private data for our user (ie. gadget). Managed by user. */ + void *private_data; + + /* filled by __ffs_data_got_descs() */ + /* + * Real descriptors are 16 bytes after raw_descs (so you need + * to skip 16 bytes (ie. ffs->raw_descs + 16) to get to the + * first full speed descriptor). raw_descs_length and + * raw_fs_descs_length do not have those 16 bytes added. + */ + const void *raw_descs; + unsigned raw_descs_length; + unsigned raw_fs_descs_length; + unsigned fs_descs_count; + unsigned hs_descs_count; + + unsigned short strings_count; + unsigned short interfaces_count; + unsigned short eps_count; + unsigned short _pad1; + + /* filled by __ffs_data_got_strings() */ + /* ids in stringtabs are set in functionfs_bind() */ + const void *raw_strings; + struct usb_gadget_strings **stringtabs; + + /* + * File system's super block, write once when file system is + * mounted. + */ + struct super_block *sb; + + /* File permissions, written once when fs is mounted */ + struct ffs_file_perms { + umode_t mode; + kuid_t uid; + kgid_t gid; + } file_perms; + + /* + * The endpoint files, filled by ffs_epfiles_create(), + * destroyed by ffs_epfiles_destroy(). + */ + struct ffs_epfile *epfiles; + /*add by chunhou.wang for kernel crash bug 378800*/ + bool no_disconnect; + /*end add by chunhou.wang*/ +}; + +/* Reference counter handling */ +static void ffs_data_get(struct ffs_data *ffs); +static void ffs_data_put(struct ffs_data *ffs); +/* Creates new ffs_data object. */ +static struct ffs_data *__must_check ffs_data_new(void) __attribute__((malloc)); + +/* Opened counter handling. */ +static void ffs_data_opened(struct ffs_data *ffs); +static void ffs_data_closed(struct ffs_data *ffs); + +/* Called with ffs->mutex held; take over ownership of data. */ +static int __must_check +__ffs_data_got_descs(struct ffs_data *ffs, char *data, size_t len); +static int __must_check +__ffs_data_got_strings(struct ffs_data *ffs, char *data, size_t len); + + +/* The function structure ***************************************************/ + +struct ffs_ep; + +struct ffs_function { + struct usb_configuration *conf; + struct usb_gadget *gadget; + struct ffs_data *ffs; + + struct ffs_ep *eps; + u8 eps_revmap[16]; + short *interfaces_nums; + + struct usb_function function; +}; + + +static struct ffs_function *ffs_func_from_usb(struct usb_function *f) +{ + return container_of(f, struct ffs_function, function); +} + +static void ffs_func_free(struct ffs_function *func); + +static void ffs_func_eps_disable(struct ffs_function *func); +static int __must_check ffs_func_eps_enable(struct ffs_function *func); + +static int ffs_func_bind(struct usb_configuration *, + struct usb_function *); +static void ffs_func_unbind(struct usb_configuration *, + struct usb_function *); +static int ffs_func_set_alt(struct usb_function *, unsigned, unsigned); +static void ffs_func_disable(struct usb_function *); +static int ffs_func_setup(struct usb_function *, + const struct usb_ctrlrequest *); +static void ffs_func_suspend(struct usb_function *); +static void ffs_func_resume(struct usb_function *); + + +static int ffs_func_revmap_ep(struct ffs_function *func, u8 num); +static int ffs_func_revmap_intf(struct ffs_function *func, u8 intf); + + +/* The endpoints structures *************************************************/ + +static unsigned int send_buffer[160+2048]; +static unsigned int recv_buffer[160+2048]; +struct ffs_ep { + struct usb_ep *ep; /* P: ffs->eps_lock */ + struct usb_request *req; /* P: epfile->mutex */ + + /* [0]: full speed, [1]: high speed */ + struct usb_endpoint_descriptor *descs[2]; + + u8 num; + + int status; /* P: epfile->mutex */ +}; + +struct ffs_epfile { + /* Protects ep->ep and ep->req. */ + struct mutex mutex; + wait_queue_head_t wait; + + struct ffs_data *ffs; + struct ffs_ep *ep; /* P: ffs->eps_lock */ + + struct dentry *dentry; + + char name[5]; + + unsigned char in; /* P: ffs->eps_lock */ + unsigned char isoc; /* P: ffs->eps_lock */ + + unsigned char _pad; +}; + +static int __must_check ffs_epfiles_create(struct ffs_data *ffs); +static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count); + +static struct inode *__must_check +ffs_sb_create_file(struct super_block *sb, const char *name, void *data, + const struct file_operations *fops, + struct dentry **dentry_p); + + +/* Misc helper functions ****************************************************/ + +static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock) + __attribute__((warn_unused_result, nonnull)); +static char *ffs_prepare_buffer(const char __user *buf, size_t len) + __attribute__((warn_unused_result, nonnull)); + + +/* Control file aka ep0 *****************************************************/ + +static void ffs_ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct ffs_data *ffs = req->context; + + complete_all(&ffs->ep0req_completion); +} + +static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len) +{ + struct usb_request *req = ffs->ep0req; + int ret; + + req->zero = len < le16_to_cpu(ffs->ev.setup.wLength); + + spin_unlock_irq(&ffs->ev.waitq.lock); + + req->buf = data; + req->length = len; + + /* + * UDC layer requires to provide a buffer even for ZLP, but should + * not use it at all. Let's provide some poisoned pointer to catch + * possible bug in the driver. + */ + if (req->buf == NULL) + req->buf = (void *)0xDEADBABE; + + INIT_COMPLETION(ffs->ep0req_completion); + + ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC); + if (unlikely(ret < 0)) + return ret; + + ret = wait_for_completion_interruptible(&ffs->ep0req_completion); + if (unlikely(ret)) { + usb_ep_dequeue(ffs->gadget->ep0, req); + return -EINTR; + } + + ffs->setup_state = FFS_NO_SETUP; + return ffs->ep0req_status; +} + +static int __ffs_ep0_stall(struct ffs_data *ffs) +{ + if (ffs->ev.can_stall) { + pr_vdebug("ep0 stall\n"); + usb_ep_set_halt(ffs->gadget->ep0); + ffs->setup_state = FFS_NO_SETUP; + return -EL2HLT; + } else { + pr_debug("bogus ep0 stall!\n"); + return -ESRCH; + } +} + +static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, + size_t len, loff_t *ptr) +{ + struct ffs_data *ffs = file->private_data; + ssize_t ret; + char *data; + + ENTER(); + + /* Fast check if setup was canceled */ + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) + return -EIDRM; + + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (unlikely(ret < 0)) + return ret; + + /* Check state */ + switch (ffs->state) { + case FFS_READ_DESCRIPTORS: + case FFS_READ_STRINGS: + /* Copy data */ + if (unlikely(len < 16)) { + ret = -EINVAL; + break; + } + + data = ffs_prepare_buffer(buf, len); + if (IS_ERR(data)) { + ret = PTR_ERR(data); + break; + } + + /* Handle data */ + if (ffs->state == FFS_READ_DESCRIPTORS) { + pr_info("read descriptors\n"); + ret = __ffs_data_got_descs(ffs, data, len); + if (unlikely(ret < 0)) + break; + + ffs->state = FFS_READ_STRINGS; + ret = len; + } else { + pr_info("read strings\n"); + ret = __ffs_data_got_strings(ffs, data, len); + if (unlikely(ret < 0)) + break; + + ret = ffs_epfiles_create(ffs); + if (unlikely(ret)) { + ffs->state = FFS_CLOSING; + break; + } + + ffs->state = FFS_ACTIVE; + mutex_unlock(&ffs->mutex); + + ret = functionfs_ready_callback(ffs); + if (unlikely(ret < 0)) { + ffs->state = FFS_CLOSING; + return ret; + } + + set_bit(FFS_FL_CALL_CLOSED_CALLBACK, &ffs->flags); + return len; + } + break; + + case FFS_ACTIVE: + data = NULL; + /* + * We're called from user space, we can use _irq + * rather then _irqsave + */ + spin_lock_irq(&ffs->ev.waitq.lock); + switch (FFS_SETUP_STATE(ffs)) { + case FFS_SETUP_CANCELED: + ret = -EIDRM; + goto done_spin; + + case FFS_NO_SETUP: + ret = -ESRCH; + goto done_spin; + + case FFS_SETUP_PENDING: + break; + } + + /* FFS_SETUP_PENDING */ + if (!(ffs->ev.setup.bRequestType & USB_DIR_IN)) { + spin_unlock_irq(&ffs->ev.waitq.lock); + ret = __ffs_ep0_stall(ffs); + break; + } + + /* FFS_SETUP_PENDING and not stall */ + len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength)); + + spin_unlock_irq(&ffs->ev.waitq.lock); + + data = ffs_prepare_buffer(buf, len); + if (IS_ERR(data)) { + ret = PTR_ERR(data); + break; + } + + spin_lock_irq(&ffs->ev.waitq.lock); + + /* + * We are guaranteed to be still in FFS_ACTIVE state + * but the state of setup could have changed from + * FFS_SETUP_PENDING to FFS_SETUP_CANCELED so we need + * to check for that. If that happened we copied data + * from user space in vain but it's unlikely. + * + * For sure we are not in FFS_NO_SETUP since this is + * the only place FFS_SETUP_PENDING -> FFS_NO_SETUP + * transition can be performed and it's protected by + * mutex. + */ + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) { + ret = -EIDRM; +done_spin: + spin_unlock_irq(&ffs->ev.waitq.lock); + } else { + /* unlocks spinlock */ + ret = __ffs_ep0_queue_wait(ffs, data, len); + } + kfree(data); + break; + + default: + ret = -EBADFD; + break; + } + + mutex_unlock(&ffs->mutex); + return ret; +} + +static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf, + size_t n) +{ + /* + * We are holding ffs->ev.waitq.lock and ffs->mutex and we need + * to release them. + */ + struct usb_functionfs_event events[n]; + unsigned i = 0; + + memset(events, 0, sizeof events); + + do { + events[i].type = ffs->ev.types[i]; + if (events[i].type == FUNCTIONFS_SETUP) { + events[i].u.setup = ffs->ev.setup; + ffs->setup_state = FFS_SETUP_PENDING; + } + } while (++i < n); + + if (n < ffs->ev.count) { + ffs->ev.count -= n; + memmove(ffs->ev.types, ffs->ev.types + n, + ffs->ev.count * sizeof *ffs->ev.types); + } else { + ffs->ev.count = 0; + } + + spin_unlock_irq(&ffs->ev.waitq.lock); + mutex_unlock(&ffs->mutex); + + return unlikely(__copy_to_user(buf, events, sizeof events)) + ? -EFAULT : sizeof events; +} + +static ssize_t ffs_ep0_read(struct file *file, char __user *buf, + size_t len, loff_t *ptr) +{ + struct ffs_data *ffs = file->private_data; + char *data = NULL; + size_t n; + int ret; + + ENTER(); + + /* Fast check if setup was canceled */ + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) + return -EIDRM; + + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (unlikely(ret < 0)) + return ret; + + /* Check state */ + if (ffs->state != FFS_ACTIVE) { + ret = -EBADFD; + goto done_mutex; + } + + /* + * We're called from user space, we can use _irq rather then + * _irqsave + */ + spin_lock_irq(&ffs->ev.waitq.lock); + + switch (FFS_SETUP_STATE(ffs)) { + case FFS_SETUP_CANCELED: + ret = -EIDRM; + break; + + case FFS_NO_SETUP: + n = len / sizeof(struct usb_functionfs_event); + if (unlikely(!n)) { + ret = -EINVAL; + break; + } + + if ((file->f_flags & O_NONBLOCK) && !ffs->ev.count) { + ret = -EAGAIN; + break; + } + + if (wait_event_interruptible_exclusive_locked_irq(ffs->ev.waitq, + ffs->ev.count)) { + ret = -EINTR; + break; + } + + return __ffs_ep0_read_events(ffs, buf, + min(n, (size_t)ffs->ev.count)); + + case FFS_SETUP_PENDING: + if (ffs->ev.setup.bRequestType & USB_DIR_IN) { + spin_unlock_irq(&ffs->ev.waitq.lock); + ret = __ffs_ep0_stall(ffs); + goto done_mutex; + } + + len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength)); + + spin_unlock_irq(&ffs->ev.waitq.lock); + + if (likely(len)) { + data = kmalloc(len, GFP_KERNEL); + if (unlikely(!data)) { + ret = -ENOMEM; + goto done_mutex; + } + } + + spin_lock_irq(&ffs->ev.waitq.lock); + + /* See ffs_ep0_write() */ + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) { + ret = -EIDRM; + break; + } + + /* unlocks spinlock */ + ret = __ffs_ep0_queue_wait(ffs, data, len); + if (likely(ret > 0) && unlikely(__copy_to_user(buf, data, len))) + ret = -EFAULT; + goto done_mutex; + + default: + ret = -EBADFD; + break; + } + + spin_unlock_irq(&ffs->ev.waitq.lock); +done_mutex: + mutex_unlock(&ffs->mutex); + kfree(data); + return ret; +} + +static int ffs_ep0_open(struct inode *inode, struct file *file) +{ + struct ffs_data *ffs = inode->i_private; + + ENTER(); + + if (unlikely(ffs->state == FFS_CLOSING)) + return -EBUSY; + + file->private_data = ffs; + ffs_data_opened(ffs); + + return 0; +} + +static int ffs_ep0_release(struct inode *inode, struct file *file) +{ + struct ffs_data *ffs = file->private_data; + + ENTER(); + + ffs_data_closed(ffs); + + return 0; +} + +static long ffs_ep0_ioctl(struct file *file, unsigned code, unsigned long value) +{ + struct ffs_data *ffs = file->private_data; + struct usb_gadget *gadget = ffs->gadget; + long ret; + + ENTER(); + + if (code == FUNCTIONFS_INTERFACE_REVMAP) { + struct ffs_function *func = ffs->func; + ret = func ? ffs_func_revmap_intf(func, value) : -ENODEV; + } else if (gadget && gadget->ops->ioctl) { + ret = gadget->ops->ioctl(gadget, code, value); + } else { + ret = -ENOTTY; + } + + return ret; +} + +static const struct file_operations ffs_ep0_operations = { + .llseek = no_llseek, + + .open = ffs_ep0_open, + .write = ffs_ep0_write, + .read = ffs_ep0_read, + .release = ffs_ep0_release, + .unlocked_ioctl = ffs_ep0_ioctl, +}; + + +/* "Normal" endpoints operations ********************************************/ + +static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct ffs_ep *ep = _ep->driver_data; + ENTER(); + /* req may be freed during unbind */ + if (ep && ep->req && likely(req->context)) { + struct ffs_ep *ep = _ep->driver_data; + ep->status = req->status ? req->status : req->actual; + complete(req->context); + } +} + +static ssize_t ffs_epfile_io(struct file *file, + char __user *buf, size_t len, int read) +{ + struct ffs_epfile *epfile = file->private_data; + struct ffs_data *ffs = epfile->ffs; + struct ffs_ep *ep; + char *data = NULL; + ssize_t ret, data_len; + int halt; + + if(read) + data = &recv_buffer[8]; + else + data = &send_buffer[8]; + goto first_try; + do { + spin_unlock_irq(&epfile->ffs->eps_lock); + mutex_unlock(&epfile->mutex); + +first_try: + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) { + ret = -ENODEV; + goto error; + } + + /* Wait for endpoint to be enabled */ + ep = epfile->ep; + if (!ep) { + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto error; + } + + if (wait_event_interruptible(epfile->wait, + (ep = epfile->ep))) { + ret = -EINTR; + goto error; + } + } + + /* Do we halt? */ + halt = !read == !epfile->in; + if (halt && epfile->isoc) { + ret = -EINVAL; + goto error; + } + + /* Allocate & copy */ + if (!halt) { + if(len < 8192) + data_len = len; + else + data_len = 8192; + + if (!read && + unlikely(__copy_from_user(data, buf, data_len))) { + ret = -EFAULT; + goto error; + } + } + + /* We will be using request */ + ret = ffs_mutex_lock(&epfile->mutex, + file->f_flags & O_NONBLOCK); + if (unlikely(ret)) + goto error; + + /* + * We're called from user space, we can use _irq rather then + * _irqsave + */ + spin_lock_irq(&epfile->ffs->eps_lock); + + /* + * While we were acquiring mutex endpoint got disabled + * or changed? + */ + } while (unlikely(epfile->ep != ep)); + + /* Halt */ + if (unlikely(halt)) { + if (likely(epfile->ep == ep) && !WARN_ON(!ep->ep)) + usb_ep_set_halt(ep->ep); + spin_unlock_irq(&epfile->ffs->eps_lock); + ret = -EBADMSG; + } else { + /* Fire the request */ + struct completion *done; + + struct usb_request *req = ep->req; + req->complete = ffs_epfile_io_complete; + req->buf = data; + req->length = data_len; + + if (read) { + INIT_COMPLETION(ffs->epout_completion); + req->context = done = &ffs->epout_completion; + } else { + INIT_COMPLETION(ffs->epin_completion); + req->context = done = &ffs->epin_completion; + } + + ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + + spin_unlock_irq(&epfile->ffs->eps_lock); + + if (unlikely(ret < 0)) { + /* nop */ + } else if (unlikely(wait_for_completion_interruptible(done))) { + ret = -EINTR; + usb_ep_dequeue(ep->ep, req); + } else { + ret = ep->status; + if (read && ret > 0){ + ret = min_t(size_t, ret, len); + if(unlikely(copy_to_user(buf, data, ret))) + ret = -EFAULT; + } + } + } + + mutex_unlock(&epfile->mutex); +error: + return ret; +} + +static ssize_t +ffs_epfile_write(struct file *file, const char __user *buf, size_t len, loff_t *ptr) +{ + int sent_len; + int temp_len=len; + + ENTER(); + + do{ + sent_len = ffs_epfile_io(file, (char __user *)buf, temp_len,0); + if(sent_len > 0){ + buf += sent_len; + temp_len -= sent_len; + } else return sent_len; + }while(temp_len>0); + + return len; +} + +static ssize_t +ffs_epfile_read(struct file *file, char __user *buf, size_t len, loff_t *ptr) +{ + ENTER(); + + return ffs_epfile_io(file, buf, len, 1); +} + +static int +ffs_epfile_open(struct inode *inode, struct file *file) +{ + struct ffs_epfile *epfile = inode->i_private; + + ENTER(); + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + + file->private_data = epfile; + ffs_data_opened(epfile->ffs); + + return 0; +} + +static int +ffs_epfile_release(struct inode *inode, struct file *file) +{ + struct ffs_epfile *epfile = inode->i_private; + + ENTER(); + + ffs_data_closed(epfile->ffs); + + return 0; +} + +static long ffs_epfile_ioctl(struct file *file, unsigned code, + unsigned long value) +{ + struct ffs_epfile *epfile = file->private_data; + int ret; + + ENTER(); + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + + spin_lock_irq(&epfile->ffs->eps_lock); + if (likely(epfile->ep)) { + switch (code) { + case FUNCTIONFS_FIFO_STATUS: + ret = usb_ep_fifo_status(epfile->ep->ep); + break; + case FUNCTIONFS_FIFO_FLUSH: + usb_ep_fifo_flush(epfile->ep->ep); + ret = 0; + break; + case FUNCTIONFS_CLEAR_HALT: + ret = usb_ep_clear_halt(epfile->ep->ep); + break; + case FUNCTIONFS_ENDPOINT_REVMAP: + ret = epfile->ep->num; + break; + default: + ret = -ENOTTY; + } + } else { + ret = -ENODEV; + } + spin_unlock_irq(&epfile->ffs->eps_lock); + + return ret; +} + +static const struct file_operations ffs_epfile_operations = { + .llseek = no_llseek, + + .open = ffs_epfile_open, + .write = ffs_epfile_write, + .read = ffs_epfile_read, + .release = ffs_epfile_release, + .unlocked_ioctl = ffs_epfile_ioctl, +}; + + +/* File system and super block operations ***********************************/ + +/* + * Mounting the file system creates a controller file, used first for + * function configuration then later for event monitoring. + */ + +static struct inode *__must_check +ffs_sb_make_inode(struct super_block *sb, void *data, + const struct file_operations *fops, + const struct inode_operations *iops, + struct ffs_file_perms *perms) +{ + struct inode *inode; + + ENTER(); + + inode = new_inode(sb); + + if (likely(inode)) { + struct timespec current_time = CURRENT_TIME; + + inode->i_ino = get_next_ino(); + inode->i_mode = perms->mode; + inode->i_uid = perms->uid; + inode->i_gid = perms->gid; + inode->i_atime = current_time; + inode->i_mtime = current_time; + inode->i_ctime = current_time; + inode->i_private = data; + if (fops) + inode->i_fop = fops; + if (iops) + inode->i_op = iops; + } + + return inode; +} + +/* Create "regular" file */ +static struct inode *ffs_sb_create_file(struct super_block *sb, + const char *name, void *data, + const struct file_operations *fops, + struct dentry **dentry_p) +{ + struct ffs_data *ffs = sb->s_fs_info; + struct dentry *dentry; + struct inode *inode; + + ENTER(); + + dentry = d_alloc_name(sb->s_root, name); + if (unlikely(!dentry)) + return NULL; + + inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms); + if (unlikely(!inode)) { + dput(dentry); + return NULL; + } + + d_add(dentry, inode); + if (dentry_p) + *dentry_p = dentry; + + return inode; +} + +/* Super block */ +static const struct super_operations ffs_sb_operations = { + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, +}; + +struct ffs_sb_fill_data { + struct ffs_file_perms perms; + umode_t root_mode; + const char *dev_name; + /*add by chunhou.wang for kernel crash bug 378800*/ + bool no_disconnect; + /*end add by chunhou.wang*/ + struct ffs_data *ffs_data; +}; + +static int ffs_sb_fill(struct super_block *sb, void *_data, int silent) +{ + struct ffs_sb_fill_data *data = _data; + struct inode *inode; + struct ffs_data *ffs = data->ffs_data; + + ENTER(); + + ffs->sb = sb; + data->ffs_data = NULL; + sb->s_fs_info = ffs; + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = FUNCTIONFS_MAGIC; + sb->s_op = &ffs_sb_operations; + sb->s_time_gran = 1; + + /* Root inode */ + data->perms.mode = data->root_mode; + inode = ffs_sb_make_inode(sb, NULL, + &simple_dir_operations, + &simple_dir_inode_operations, + &data->perms); + sb->s_root = d_make_root(inode); + if (unlikely(!sb->s_root)) + return -ENOMEM; + + /* EP0 file */ + if (unlikely(!ffs_sb_create_file(sb, "ep0", ffs, + &ffs_ep0_operations, NULL))) + return -ENOMEM; + + return 0; +} + +static int ffs_fs_parse_opts(struct ffs_sb_fill_data *data, char *opts) +{ + ENTER(); + + if (!opts || !*opts) + return 0; + + for (;;) { + unsigned long value; + char *eq, *comma; + + /* Option limit */ + comma = strchr(opts, ','); + if (comma) + *comma = 0; + + /* Value limit */ + eq = strchr(opts, '='); + if (unlikely(!eq)) { + pr_err("'=' missing in %s\n", opts); + return -EINVAL; + } + *eq = 0; + + /* Parse value */ + if (kstrtoul(eq + 1, 0, &value)) { + pr_err("%s: invalid value: %s\n", opts, eq + 1); + return -EINVAL; + } + + /* Interpret option */ + switch (eq - opts) { + /*add by chunhou.wang for kernel crash bug 378800*/ + case 13: + if (!memcmp(opts, "no_disconnect", 13)) + data->no_disconnect = !!value; + else + goto invalid; + break; + /*end add by chunhou.wang*/ + case 5: + if (!memcmp(opts, "rmode", 5)) + data->root_mode = (value & 0555) | S_IFDIR; + else if (!memcmp(opts, "fmode", 5)) + data->perms.mode = (value & 0666) | S_IFREG; + else + goto invalid; + break; + + case 4: + if (!memcmp(opts, "mode", 4)) { + data->root_mode = (value & 0555) | S_IFDIR; + data->perms.mode = (value & 0666) | S_IFREG; + } else { + goto invalid; + } + break; + + case 3: + if (!memcmp(opts, "uid", 3)) { + data->perms.uid = make_kuid(current_user_ns(), value); + if (!uid_valid(data->perms.uid)) { + pr_err("%s: unmapped value: %lu\n", opts, value); + return -EINVAL; + } + } else if (!memcmp(opts, "gid", 3)) { + data->perms.gid = make_kgid(current_user_ns(), value); + if (!gid_valid(data->perms.gid)) { + pr_err("%s: unmapped value: %lu\n", opts, value); + return -EINVAL; + } + } else { + goto invalid; + } + break; + + default: +invalid: + pr_err("%s: invalid option\n", opts); + return -EINVAL; + } + + /* Next iteration */ + if (!comma) + break; + opts = comma + 1; + } + + return 0; +} + +/* "mount -t functionfs dev_name /dev/function" ends up here */ + +static struct dentry * +ffs_fs_mount(struct file_system_type *t, int flags, + const char *dev_name, void *opts) +{ + struct ffs_sb_fill_data data = { + .perms = { + .mode = S_IFREG | 0600, + .uid = GLOBAL_ROOT_UID, + .gid = GLOBAL_ROOT_GID, + }, + .root_mode = S_IFDIR | 0500, + /*add by chunhou.wang for kernel crash bug 378800*/ + .no_disconnect = false, + /*end add by chunhou.wang*/ + }; + struct dentry *rv; + int ret; + void *ffs_dev; + struct ffs_data *ffs; + + ENTER(); + + ret = ffs_fs_parse_opts(&data, opts); + if (unlikely(ret < 0)) + return ERR_PTR(ret); + + ffs = ffs_data_new(); + if (unlikely(!ffs)) + return ERR_PTR(-ENOMEM); + ffs->file_perms = data.perms; + /*add by chunhou.wang for kernel crash bug 378800*/ + ffs->no_disconnect = data.no_disconnect; + /*end add by chunhou.wang*/ + + ffs->dev_name = kstrdup(dev_name, GFP_KERNEL); + if (unlikely(!ffs->dev_name)) { + ffs_data_put(ffs); + return ERR_PTR(-ENOMEM); + } + + ffs_dev = functionfs_acquire_dev_callback(dev_name); + if (IS_ERR(ffs_dev)) { + ffs_data_put(ffs); + return ERR_CAST(ffs_dev); + } + ffs->private_data = ffs_dev; + data.ffs_data = ffs; + + rv = mount_nodev(t, flags, &data, ffs_sb_fill); + if (IS_ERR(rv) && data.ffs_data) { + functionfs_release_dev_callback(data.ffs_data); + ffs_data_put(data.ffs_data); + } + return rv; +} + +static void +ffs_fs_kill_sb(struct super_block *sb) +{ + ENTER(); + + kill_litter_super(sb); + if (sb->s_fs_info) { + functionfs_release_dev_callback(sb->s_fs_info); + /*add by chunhou.wang for kernel crash bug 378800*/ + ffs_data_closed(sb->s_fs_info); + /*end add by chunhou.wang*/ + ffs_data_put(sb->s_fs_info); + } +} + +static struct file_system_type ffs_fs_type = { + .owner = THIS_MODULE, + .name = "functionfs", + .mount = ffs_fs_mount, + .kill_sb = ffs_fs_kill_sb, +}; +MODULE_ALIAS_FS("functionfs"); + + +/* Driver's main init/cleanup functions *************************************/ + +static int functionfs_init(void) +{ + int ret; + + ENTER(); + + ret = register_filesystem(&ffs_fs_type); + if (likely(!ret)) + pr_info("file system registered\n"); + else + pr_err("failed registering file system (%d)\n", ret); + + return ret; +} + +static void functionfs_cleanup(void) +{ + ENTER(); + + pr_info("unloading\n"); + unregister_filesystem(&ffs_fs_type); +} + + +/* ffs_data and ffs_function construction and destruction code **************/ + +static void ffs_data_clear(struct ffs_data *ffs); +static void ffs_data_reset(struct ffs_data *ffs); + +static void ffs_data_get(struct ffs_data *ffs) +{ + ENTER(); + + atomic_inc(&ffs->ref); +} + +static void ffs_data_opened(struct ffs_data *ffs) +{ + ENTER(); + + atomic_inc(&ffs->ref); + /*add by chunhou.wang for kernel crash bug 378800*/ + //atomic_inc(&ffs->opened); + if (atomic_add_return(1, &ffs->opened) == 1) + if (ffs->state == FFS_DEACTIVATED) + ffs_data_reset(ffs); + /*end add by chunhou.wang*/ +} + +static void ffs_data_put(struct ffs_data *ffs) +{ + ENTER(); + + if (unlikely(atomic_dec_and_test(&ffs->ref))) { + pr_info("%s(): freeing\n", __func__); + ffs_data_clear(ffs); + BUG_ON(waitqueue_active(&ffs->ev.waitq) || + waitqueue_active(&ffs->ep0req_completion.wait)); + kfree(ffs->dev_name); + kfree(ffs); + } +} + +static void ffs_data_closed(struct ffs_data *ffs) +{ + ENTER(); + + if (atomic_dec_and_test(&ffs->opened)) { + /*add by chunhou.wang for kernel crash bug 378800*/ + if (ffs->no_disconnect) { + ffs->state = FFS_DEACTIVATED; + if (ffs->epfiles) { + ffs_epfiles_destroy(ffs->epfiles,ffs->eps_count); + ffs->epfiles = NULL; + } + if (ffs->setup_state == FFS_SETUP_PENDING) + __ffs_ep0_stall(ffs); + }else{ + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + } + } + if (atomic_read(&ffs->opened) < 0) + ffs_data_clear(ffs); + /*end add by chunhou.wang*/ + ffs_data_put(ffs); +} + +static struct ffs_data *ffs_data_new(void) +{ + struct ffs_data *ffs = kzalloc(sizeof *ffs, GFP_KERNEL); + if (unlikely(!ffs)) + return 0; + + ENTER(); + + atomic_set(&ffs->ref, 1); + atomic_set(&ffs->opened, 0); + ffs->state = FFS_READ_DESCRIPTORS; + mutex_init(&ffs->mutex); + spin_lock_init(&ffs->eps_lock); + init_waitqueue_head(&ffs->ev.waitq); + init_completion(&ffs->ep0req_completion); + init_completion(&ffs->epin_completion); + init_completion(&ffs->epout_completion); + + /* XXX REVISIT need to update it in some places, or do we? */ + ffs->ev.can_stall = 1; + + return ffs; +} + +static void ffs_data_clear(struct ffs_data *ffs) +{ + ENTER(); + + if (test_and_clear_bit(FFS_FL_CALL_CLOSED_CALLBACK, &ffs->flags)) + functionfs_closed_callback(ffs); + + BUG_ON(ffs->gadget); + + if (ffs->epfiles) + ffs_epfiles_destroy(ffs->epfiles, ffs->eps_count); + + kfree(ffs->raw_descs); + kfree(ffs->raw_strings); + kfree(ffs->stringtabs); +} + +static void ffs_data_reset(struct ffs_data *ffs) +{ + ENTER(); + + ffs_data_clear(ffs); + + ffs->epfiles = NULL; + ffs->raw_descs = NULL; + ffs->raw_strings = NULL; + ffs->stringtabs = NULL; + + ffs->raw_descs_length = 0; + ffs->raw_fs_descs_length = 0; + ffs->fs_descs_count = 0; + ffs->hs_descs_count = 0; + + ffs->strings_count = 0; + ffs->interfaces_count = 0; + ffs->eps_count = 0; + + ffs->ev.count = 0; + + ffs->state = FFS_READ_DESCRIPTORS; + ffs->setup_state = FFS_NO_SETUP; + ffs->flags = 0; +} + + +static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev) +{ + struct usb_gadget_strings **lang; + int first_id; + + ENTER(); + + if (WARN_ON(ffs->state != FFS_ACTIVE + || test_and_set_bit(FFS_FL_BOUND, &ffs->flags))) + return -EBADFD; + + first_id = usb_string_ids_n(cdev, ffs->strings_count); + if (unlikely(first_id < 0)) + return first_id; + + ffs->ep0req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); + if (unlikely(!ffs->ep0req)) + return -ENOMEM; + ffs->ep0req->complete = ffs_ep0_complete; + ffs->ep0req->context = ffs; + + lang = ffs->stringtabs; + if (lang) { + for (; *lang; ++lang) { + struct usb_string *str = (*lang)->strings; + int id = first_id; + for (; str->s; ++id, ++str) + str->id = id; + } + } + + ffs->gadget = cdev->gadget; + ffs_data_get(ffs); + return 0; +} + +static void functionfs_unbind(struct ffs_data *ffs) +{ + ENTER(); + + if (!WARN_ON(!ffs->gadget)) { + usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req); + ffs->ep0req = NULL; + ffs->gadget = NULL; + ffs_data_put(ffs); + clear_bit(FFS_FL_BOUND, &ffs->flags); + } +} + +static int ffs_epfiles_create(struct ffs_data *ffs) +{ + struct ffs_epfile *epfile, *epfiles; + unsigned i, count; + + ENTER(); + + count = ffs->eps_count; + epfiles = kcalloc(count, sizeof(*epfiles), GFP_KERNEL); + if (!epfiles) + return -ENOMEM; + + epfile = epfiles; + for (i = 1; i <= count; ++i, ++epfile) { + epfile->ffs = ffs; + mutex_init(&epfile->mutex); + init_waitqueue_head(&epfile->wait); + sprintf(epfiles->name, "ep%u", i); + if (!unlikely(ffs_sb_create_file(ffs->sb, epfiles->name, epfile, + &ffs_epfile_operations, + &epfile->dentry))) { + ffs_epfiles_destroy(epfiles, i - 1); + return -ENOMEM; + } + } + + ffs->epfiles = epfiles; + return 0; +} + +static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) +{ + struct ffs_epfile *epfile = epfiles; + + ENTER(); + + for (; count; --count, ++epfile) { + BUG_ON(mutex_is_locked(&epfile->mutex) || + waitqueue_active(&epfile->wait)); + if (epfile->dentry) { + d_delete(epfile->dentry); + dput(epfile->dentry); + epfile->dentry = NULL; + } + } + + kfree(epfiles); +} + +static int functionfs_bind_config(struct usb_composite_dev *cdev, + struct usb_configuration *c, + struct ffs_data *ffs) +{ + struct ffs_function *func; + int ret; + + ENTER(); + + func = kzalloc(sizeof *func, GFP_KERNEL); + if (unlikely(!func)) + return -ENOMEM; + + func->function.name = "Function FS Gadget"; + func->function.strings = ffs->stringtabs; + + func->function.bind = ffs_func_bind; + func->function.unbind = ffs_func_unbind; + func->function.set_alt = ffs_func_set_alt; + func->function.disable = ffs_func_disable; + func->function.setup = ffs_func_setup; + func->function.suspend = ffs_func_suspend; + func->function.resume = ffs_func_resume; + + func->conf = c; + func->gadget = cdev->gadget; + func->ffs = ffs; + ffs_data_get(ffs); + + ret = usb_add_function(c, &func->function); + if (unlikely(ret)) + ffs_func_free(func); + + return ret; +} + +static void ffs_func_free(struct ffs_function *func) +{ + struct ffs_ep *ep = func->eps; + unsigned count = func->ffs->eps_count; + unsigned long flags; + + ENTER(); + + /* cleanup after autoconfig */ + spin_lock_irqsave(&func->ffs->eps_lock, flags); + do { + if (ep->ep && ep->req) + usb_ep_free_request(ep->ep, ep->req); + ep->req = NULL; + ++ep; + } while (--count); + spin_unlock_irqrestore(&func->ffs->eps_lock, flags); + + ffs_data_put(func->ffs); + + kfree(func->eps); + /* + * eps and interfaces_nums are allocated in the same chunk so + * only one free is required. Descriptors are also allocated + * in the same chunk. + */ + + kfree(func); +} + +static void ffs_func_eps_disable(struct ffs_function *func) +{ + struct ffs_ep *ep = func->eps; + struct ffs_epfile *epfile = func->ffs->epfiles; + unsigned count = func->ffs->eps_count; + unsigned long flags; + + spin_lock_irqsave(&func->ffs->eps_lock, flags); + do { + /* pending requests get nuked */ + if (likely(ep->ep)) + usb_ep_disable(ep->ep); + epfile->ep = NULL; + + /*add by chunhou.wang for kernel crash bug 378800*/ + ++ep; + if(epfile){ + epfile->ep = NULL; + ++epfile; + } + /*end add by chunhou.wang*/ + } while (--count); + spin_unlock_irqrestore(&func->ffs->eps_lock, flags); +} + +static int ffs_func_eps_enable(struct ffs_function *func) +{ + struct ffs_data *ffs = func->ffs; + struct ffs_ep *ep = func->eps; + struct ffs_epfile *epfile = ffs->epfiles; + unsigned count = ffs->eps_count; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&func->ffs->eps_lock, flags); + do { + struct usb_endpoint_descriptor *ds; + int desc_idx = ffs->gadget->speed == USB_SPEED_HIGH ? 1 : 0; + ds = ep->descs[desc_idx]; + if (!ds) { + ret = -EINVAL; + break; + } + + ep->ep->driver_data = ep; + ep->ep->desc = ds; + ret = usb_ep_enable(ep->ep); + if (likely(!ret)) { + epfile->ep = ep; + epfile->in = usb_endpoint_dir_in(ds); + epfile->isoc = usb_endpoint_xfer_isoc(ds); + } else { + break; + } + + wake_up(&epfile->wait); + + ++ep; + ++epfile; + } while (--count); + spin_unlock_irqrestore(&func->ffs->eps_lock, flags); + + return ret; +} + + +/* Parsing and building descriptors and strings *****************************/ + +/* + * This validates if data pointed by data is a valid USB descriptor as + * well as record how many interfaces, endpoints and strings are + * required by given configuration. Returns address after the + * descriptor or NULL if data is invalid. + */ + +enum ffs_entity_type { + FFS_DESCRIPTOR, FFS_INTERFACE, FFS_STRING, FFS_ENDPOINT +}; + +typedef int (*ffs_entity_callback)(enum ffs_entity_type entity, + u8 *valuep, + struct usb_descriptor_header *desc, + void *priv); + +static int __must_check ffs_do_desc(char *data, unsigned len, + ffs_entity_callback entity, void *priv) +{ + struct usb_descriptor_header *_ds = (void *)data; + u8 length; + int ret; + + ENTER(); + + /* At least two bytes are required: length and type */ + if (len < 2) { + pr_vdebug("descriptor too short\n"); + return -EINVAL; + } + + /* If we have at least as many bytes as the descriptor takes? */ + length = _ds->bLength; + if (len < length) { + pr_vdebug("descriptor longer then available data\n"); + return -EINVAL; + } + +#define __entity_check_INTERFACE(val) 1 +#define __entity_check_STRING(val) (val) +#define __entity_check_ENDPOINT(val) ((val) & USB_ENDPOINT_NUMBER_MASK) +#define __entity(type, val) do { \ + pr_vdebug("entity " #type "(%02x)\n", (val)); \ + if (unlikely(!__entity_check_ ##type(val))) { \ + pr_vdebug("invalid entity's value\n"); \ + return -EINVAL; \ + } \ + ret = entity(FFS_ ##type, &val, _ds, priv); \ + if (unlikely(ret < 0)) { \ + pr_debug("entity " #type "(%02x); ret = %d\n", \ + (val), ret); \ + return ret; \ + } \ + } while (0) + + /* Parse descriptor depending on type. */ + switch (_ds->bDescriptorType) { + case USB_DT_DEVICE: + case USB_DT_CONFIG: + case USB_DT_STRING: + case USB_DT_DEVICE_QUALIFIER: + /* function can't have any of those */ + pr_vdebug("descriptor reserved for gadget: %d\n", + _ds->bDescriptorType); + return -EINVAL; + + case USB_DT_INTERFACE: { + struct usb_interface_descriptor *ds = (void *)_ds; + pr_vdebug("interface descriptor\n"); + if (length != sizeof *ds) + goto inv_length; + + __entity(INTERFACE, ds->bInterfaceNumber); + if (ds->iInterface) + __entity(STRING, ds->iInterface); + } + break; + + case USB_DT_ENDPOINT: { + struct usb_endpoint_descriptor *ds = (void *)_ds; + pr_vdebug("endpoint descriptor\n"); + if (length != USB_DT_ENDPOINT_SIZE && + length != USB_DT_ENDPOINT_AUDIO_SIZE) + goto inv_length; + __entity(ENDPOINT, ds->bEndpointAddress); + } + break; + + case HID_DT_HID: + pr_vdebug("hid descriptor\n"); + if (length != sizeof(struct hid_descriptor)) + goto inv_length; + break; + + case USB_DT_OTG: + if (length != sizeof(struct usb_otg_descriptor)) + goto inv_length; + break; + + case USB_DT_INTERFACE_ASSOCIATION: { + struct usb_interface_assoc_descriptor *ds = (void *)_ds; + pr_vdebug("interface association descriptor\n"); + if (length != sizeof *ds) + goto inv_length; + if (ds->iFunction) + __entity(STRING, ds->iFunction); + } + break; + + case USB_DT_OTHER_SPEED_CONFIG: + case USB_DT_INTERFACE_POWER: + case USB_DT_DEBUG: + case USB_DT_SECURITY: + case USB_DT_CS_RADIO_CONTROL: + /* TODO */ + pr_vdebug("unimplemented descriptor: %d\n", _ds->bDescriptorType); + return -EINVAL; + + default: + /* We should never be here */ + pr_vdebug("unknown descriptor: %d\n", _ds->bDescriptorType); + return -EINVAL; + +inv_length: + pr_vdebug("invalid length: %d (descriptor %d)\n", + _ds->bLength, _ds->bDescriptorType); + return -EINVAL; + } + +#undef __entity +#undef __entity_check_DESCRIPTOR +#undef __entity_check_INTERFACE +#undef __entity_check_STRING +#undef __entity_check_ENDPOINT + + return length; +} + +static int __must_check ffs_do_descs(unsigned count, char *data, unsigned len, + ffs_entity_callback entity, void *priv) +{ + const unsigned _len = len; + unsigned long num = 0; + + ENTER(); + + for (;;) { + int ret; + + if (num == count) + data = NULL; + + /* Record "descriptor" entity */ + ret = entity(FFS_DESCRIPTOR, (u8 *)num, (void *)data, priv); + if (unlikely(ret < 0)) { + pr_debug("entity DESCRIPTOR(%02lx); ret = %d\n", + num, ret); + return ret; + } + + if (!data) + return _len - len; + + ret = ffs_do_desc(data, len, entity, priv); + if (unlikely(ret < 0)) { + pr_debug("%s returns %d\n", __func__, ret); + return ret; + } + + len -= ret; + data += ret; + ++num; + } +} + +static int __ffs_data_do_entity(enum ffs_entity_type type, + u8 *valuep, struct usb_descriptor_header *desc, + void *priv) +{ + struct ffs_data *ffs = priv; + + ENTER(); + + switch (type) { + case FFS_DESCRIPTOR: + break; + + case FFS_INTERFACE: + /* + * Interfaces are indexed from zero so if we + * encountered interface "n" then there are at least + * "n+1" interfaces. + */ + if (*valuep >= ffs->interfaces_count) + ffs->interfaces_count = *valuep + 1; + break; + + case FFS_STRING: + /* + * Strings are indexed from 1 (0 is magic ;) reserved + * for languages list or some such) + */ + if (*valuep > ffs->strings_count) + ffs->strings_count = *valuep; + break; + + case FFS_ENDPOINT: + /* Endpoints are indexed from 1 as well. */ + if ((*valuep & USB_ENDPOINT_NUMBER_MASK) > ffs->eps_count) + ffs->eps_count = (*valuep & USB_ENDPOINT_NUMBER_MASK); + break; + } + + return 0; +} + +static int __ffs_data_got_descs(struct ffs_data *ffs, + char *const _data, size_t len) +{ + unsigned fs_count, hs_count; + int fs_len, ret = -EINVAL; + char *data = _data; + + ENTER(); + + if (unlikely(get_unaligned_le32(data) != FUNCTIONFS_DESCRIPTORS_MAGIC || + get_unaligned_le32(data + 4) != len)) + goto error; + fs_count = get_unaligned_le32(data + 8); + hs_count = get_unaligned_le32(data + 12); + + if (!fs_count && !hs_count) + goto einval; + + data += 16; + len -= 16; + + if (likely(fs_count)) { + fs_len = ffs_do_descs(fs_count, data, len, + __ffs_data_do_entity, ffs); + if (unlikely(fs_len < 0)) { + ret = fs_len; + goto error; + } + + data += fs_len; + len -= fs_len; + } else { + fs_len = 0; + } + + if (likely(hs_count)) { + ret = ffs_do_descs(hs_count, data, len, + __ffs_data_do_entity, ffs); + if (unlikely(ret < 0)) + goto error; + } else { + ret = 0; + } + + if (unlikely(len != ret)) + goto einval; + + ffs->raw_fs_descs_length = fs_len; + ffs->raw_descs_length = fs_len + ret; + ffs->raw_descs = _data; + ffs->fs_descs_count = fs_count; + ffs->hs_descs_count = hs_count; + + return 0; + +einval: + ret = -EINVAL; +error: + kfree(_data); + return ret; +} + +static int __ffs_data_got_strings(struct ffs_data *ffs, + char *const _data, size_t len) +{ + u32 str_count, needed_count, lang_count; + struct usb_gadget_strings **stringtabs, *t; + struct usb_string *strings, *s; + const char *data = _data; + + ENTER(); + + if (unlikely(get_unaligned_le32(data) != FUNCTIONFS_STRINGS_MAGIC || + get_unaligned_le32(data + 4) != len)) + goto error; + str_count = get_unaligned_le32(data + 8); + lang_count = get_unaligned_le32(data + 12); + + /* if one is zero the other must be zero */ + if (unlikely(!str_count != !lang_count)) + goto error; + + /* Do we have at least as many strings as descriptors need? */ + needed_count = ffs->strings_count; + if (unlikely(str_count < needed_count)) + goto error; + + /* + * If we don't need any strings just return and free all + * memory. + */ + if (!needed_count) { + kfree(_data); + return 0; + } + + /* Allocate everything in one chunk so there's less maintenance. */ + { + struct { + struct usb_gadget_strings *stringtabs[lang_count + 1]; + struct usb_gadget_strings stringtab[lang_count]; + struct usb_string strings[lang_count*(needed_count+1)]; + } *d; + unsigned i = 0; + + d = kmalloc(sizeof *d, GFP_KERNEL); + if (unlikely(!d)) { + kfree(_data); + return -ENOMEM; + } + + stringtabs = d->stringtabs; + t = d->stringtab; + i = lang_count; + do { + *stringtabs++ = t++; + } while (--i); + *stringtabs = NULL; + + stringtabs = d->stringtabs; + t = d->stringtab; + s = d->strings; + strings = s; + } + + /* For each language */ + data += 16; + len -= 16; + + do { /* lang_count > 0 so we can use do-while */ + unsigned needed = needed_count; + + if (unlikely(len < 3)) + goto error_free; + t->language = get_unaligned_le16(data); + t->strings = s; + ++t; + + data += 2; + len -= 2; + + /* For each string */ + do { /* str_count > 0 so we can use do-while */ + size_t length = strnlen(data, len); + + if (unlikely(length == len)) + goto error_free; + + /* + * User may provide more strings then we need, + * if that's the case we simply ignore the + * rest + */ + if (likely(needed)) { + /* + * s->id will be set while adding + * function to configuration so for + * now just leave garbage here. + */ + s->s = data; + --needed; + ++s; + } + + data += length + 1; + len -= length + 1; + } while (--str_count); + + s->id = 0; /* terminator */ + s->s = NULL; + ++s; + + } while (--lang_count); + + /* Some garbage left? */ + if (unlikely(len)) + goto error_free; + + /* Done! */ + ffs->stringtabs = stringtabs; + ffs->raw_strings = _data; + + return 0; + +error_free: + kfree(stringtabs); +error: + kfree(_data); + return -EINVAL; +} + + +/* Events handling and management *******************************************/ + +static void __ffs_event_add(struct ffs_data *ffs, + enum usb_functionfs_event_type type) +{ + enum usb_functionfs_event_type rem_type1, rem_type2 = type; + int neg = 0; + + /* + * Abort any unhandled setup + * + * We do not need to worry about some cmpxchg() changing value + * of ffs->setup_state without holding the lock because when + * state is FFS_SETUP_PENDING cmpxchg() in several places in + * the source does nothing. + */ + if (ffs->setup_state == FFS_SETUP_PENDING) + ffs->setup_state = FFS_SETUP_CANCELED; + + switch (type) { + case FUNCTIONFS_RESUME: + rem_type2 = FUNCTIONFS_SUSPEND; + /* FALL THROUGH */ + case FUNCTIONFS_SUSPEND: + case FUNCTIONFS_SETUP: + rem_type1 = type; + /* Discard all similar events */ + break; + + case FUNCTIONFS_BIND: + case FUNCTIONFS_UNBIND: + case FUNCTIONFS_DISABLE: + case FUNCTIONFS_ENABLE: + /* Discard everything other then power management. */ + rem_type1 = FUNCTIONFS_SUSPEND; + rem_type2 = FUNCTIONFS_RESUME; + neg = 1; + break; + + default: + BUG(); + } + + { + u8 *ev = ffs->ev.types, *out = ev; + unsigned n = ffs->ev.count; + for (; n; --n, ++ev) + if ((*ev == rem_type1 || *ev == rem_type2) == neg) + *out++ = *ev; + else + pr_vdebug("purging event %d\n", *ev); + ffs->ev.count = out - ffs->ev.types; + } + + pr_vdebug("adding event %d\n", type); + ffs->ev.types[ffs->ev.count++] = type; + wake_up_locked(&ffs->ev.waitq); +} + +static void ffs_event_add(struct ffs_data *ffs, + enum usb_functionfs_event_type type) +{ + unsigned long flags; + spin_lock_irqsave(&ffs->ev.waitq.lock, flags); + __ffs_event_add(ffs, type); + spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags); +} + + +/* Bind/unbind USB function hooks *******************************************/ + +static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, + struct usb_descriptor_header *desc, + void *priv) +{ + struct usb_endpoint_descriptor *ds = (void *)desc; + struct ffs_function *func = priv; + struct ffs_ep *ffs_ep; + + /* + * If hs_descriptors is not NULL then we are reading hs + * descriptors now + */ + const int isHS = func->function.hs_descriptors != NULL; + unsigned idx; + + if (type != FFS_DESCRIPTOR) + return 0; + + if (isHS) + func->function.hs_descriptors[(long)valuep] = desc; + else + func->function.fs_descriptors[(long)valuep] = desc; + + if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT) + return 0; + + idx = (ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) - 1; + ffs_ep = func->eps + idx; + + if (unlikely(ffs_ep->descs[isHS])) { + pr_vdebug("two %sspeed descriptors for EP %d\n", + isHS ? "high" : "full", + ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + return -EINVAL; + } + ffs_ep->descs[isHS] = ds; + + ffs_dump_mem(": Original ep desc", ds, ds->bLength); + if (ffs_ep->ep) { + ds->bEndpointAddress = ffs_ep->descs[0]->bEndpointAddress; + if (!ds->wMaxPacketSize) + ds->wMaxPacketSize = ffs_ep->descs[0]->wMaxPacketSize; + } else { + struct usb_request *req; + struct usb_ep *ep; + + pr_vdebug("autoconfig\n"); + ep = usb_ep_autoconfig(func->gadget, ds); + if (unlikely(!ep)) + return -ENOTSUPP; + ep->driver_data = func->eps + idx; + + req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (unlikely(!req)) + return -ENOMEM; + + ffs_ep->ep = ep; + ffs_ep->req = req; + func->eps_revmap[ds->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK] = idx + 1; + } + ffs_dump_mem(": Rewritten ep desc", ds, ds->bLength); + + return 0; +} + +static int __ffs_func_bind_do_nums(enum ffs_entity_type type, u8 *valuep, + struct usb_descriptor_header *desc, + void *priv) +{ + struct ffs_function *func = priv; + unsigned idx; + u8 newValue; + + switch (type) { + default: + case FFS_DESCRIPTOR: + /* Handled in previous pass by __ffs_func_bind_do_descs() */ + return 0; + + case FFS_INTERFACE: + idx = *valuep; + if (func->interfaces_nums[idx] < 0) { + int id = usb_interface_id(func->conf, &func->function); + if (unlikely(id < 0)) + return id; + func->interfaces_nums[idx] = id; + } + newValue = func->interfaces_nums[idx]; + break; + + case FFS_STRING: + /* String' IDs are allocated when fsf_data is bound to cdev */ + newValue = func->ffs->stringtabs[0]->strings[*valuep - 1].id; + break; + + case FFS_ENDPOINT: + /* + * USB_DT_ENDPOINT are handled in + * __ffs_func_bind_do_descs(). + */ + if (desc->bDescriptorType == USB_DT_ENDPOINT) + return 0; + + idx = (*valuep & USB_ENDPOINT_NUMBER_MASK) - 1; + if (unlikely(!func->eps[idx].ep)) + return -EINVAL; + + { + struct usb_endpoint_descriptor **descs; + descs = func->eps[idx].descs; + newValue = descs[descs[0] ? 0 : 1]->bEndpointAddress; + } + break; + } + + pr_vdebug("%02x -> %02x\n", *valuep, newValue); + *valuep = newValue; + return 0; +} + +static int ffs_func_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + + const int full = !!func->ffs->fs_descs_count; + const int high = gadget_is_dualspeed(func->gadget) && + func->ffs->hs_descs_count; + + int ret; + + /* Make it a single chunk, less management later on */ + struct { + struct ffs_ep eps[ffs->eps_count]; + struct usb_descriptor_header + *fs_descs[full ? ffs->fs_descs_count + 1 : 0]; + struct usb_descriptor_header + *hs_descs[high ? ffs->hs_descs_count + 1 : 0]; + short inums[ffs->interfaces_count]; + char raw_descs[high ? ffs->raw_descs_length + : ffs->raw_fs_descs_length]; + } *data; + + ENTER(); + + /* Only high speed but not supported by gadget? */ + if (unlikely(!(full | high))) + return -ENOTSUPP; + + /* Allocate */ + data = kmalloc(sizeof *data, GFP_KERNEL); + if (unlikely(!data)) + return -ENOMEM; + + /* Zero */ + memset(data->eps, 0, sizeof data->eps); + memcpy(data->raw_descs, ffs->raw_descs + 16, sizeof data->raw_descs); + if(data->inums) + memset(data->inums, 0xff, sizeof data->inums); + for (ret = ffs->eps_count; ret; --ret) + data->eps[ret].num = -1; + + /* Save pointers */ + func->eps = data->eps; + func->interfaces_nums = data->inums; + + /* + * Go through all the endpoint descriptors and allocate + * endpoints first, so that later we can rewrite the endpoint + * numbers without worrying that it may be described later on. + */ + if (likely(full)) { + func->function.fs_descriptors = data->fs_descs; + ret = ffs_do_descs(ffs->fs_descs_count, + data->raw_descs, + sizeof data->raw_descs, + __ffs_func_bind_do_descs, func); + if (unlikely(ret < 0)) + goto error; + } else { + ret = 0; + } + + if (likely(high)) { + func->function.hs_descriptors = data->hs_descs; + ret = ffs_do_descs(ffs->hs_descs_count, + data->raw_descs + ret, + (sizeof data->raw_descs) - ret, + __ffs_func_bind_do_descs, func); + } + + /* + * Now handle interface numbers allocation and interface and + * endpoint numbers rewriting. We can do that in one go + * now. + */ + ret = ffs_do_descs(ffs->fs_descs_count + + (high ? ffs->hs_descs_count : 0), + data->raw_descs, sizeof data->raw_descs, + __ffs_func_bind_do_nums, func); + if (unlikely(ret < 0)) + goto error; + + /* And we're done */ + ffs_event_add(ffs, FUNCTIONFS_BIND); + return 0; + +error: + /* XXX Do we need to release all claimed endpoints here? */ + kfree(data); + return ret; +} + + +/* Other USB function hooks *************************************************/ + +static void ffs_func_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + + ENTER(); + + if (ffs->func == func) { + ffs_func_eps_disable(func); + ffs->func = NULL; + } + + ffs_event_add(ffs, FUNCTIONFS_UNBIND); + + ffs_func_free(func); +} + +static int ffs_func_set_alt(struct usb_function *f, + unsigned interface, unsigned alt) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + int ret = 0, intf; + + if (alt != (unsigned)-1) { + intf = ffs_func_revmap_intf(func, interface); + if (unlikely(intf < 0)) + return intf; + } + + if (ffs->func) + ffs_func_eps_disable(ffs->func); + + if (ffs->state != FFS_ACTIVE) + return -ENODEV; + + if (alt == (unsigned)-1) { + ffs->func = NULL; + ffs_event_add(ffs, FUNCTIONFS_DISABLE); + return 0; + } + + ffs->func = func; + ret = ffs_func_eps_enable(func); + if (likely(ret >= 0)) + ffs_event_add(ffs, FUNCTIONFS_ENABLE); + return ret; +} + +static void ffs_func_disable(struct usb_function *f) +{ + ffs_func_set_alt(f, 0, (unsigned)-1); +} + +static int ffs_func_setup(struct usb_function *f, + const struct usb_ctrlrequest *creq) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + unsigned long flags; + int ret; + + ENTER(); + + pr_vdebug("creq->bRequestType = %02x\n", creq->bRequestType); + pr_vdebug("creq->bRequest = %02x\n", creq->bRequest); + pr_vdebug("creq->wValue = %04x\n", le16_to_cpu(creq->wValue)); + pr_vdebug("creq->wIndex = %04x\n", le16_to_cpu(creq->wIndex)); + pr_vdebug("creq->wLength = %04x\n", le16_to_cpu(creq->wLength)); + + /* + * Most requests directed to interface go through here + * (notable exceptions are set/get interface) so we need to + * handle them. All other either handled by composite or + * passed to usb_configuration->setup() (if one is set). No + * matter, we will handle requests directed to endpoint here + * as well (as it's straightforward) but what to do with any + * other request? + */ + if (ffs->state != FFS_ACTIVE) + return -ENODEV; + + switch (creq->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + ret = ffs_func_revmap_intf(func, le16_to_cpu(creq->wIndex)); + if (unlikely(ret < 0)) + return ret; + break; + + case USB_RECIP_ENDPOINT: + ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex)); + if (unlikely(ret < 0)) + return ret; + break; + + default: + return -EOPNOTSUPP; + } + + spin_lock_irqsave(&ffs->ev.waitq.lock, flags); + ffs->ev.setup = *creq; + ffs->ev.setup.wIndex = cpu_to_le16(ret); + __ffs_event_add(ffs, FUNCTIONFS_SETUP); + spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags); + + return 0; +} + +static void ffs_func_suspend(struct usb_function *f) +{ + ENTER(); + ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_SUSPEND); +} + +static void ffs_func_resume(struct usb_function *f) +{ + ENTER(); + ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_RESUME); +} + + +/* Endpoint and interface numbers reverse mapping ***************************/ + +static int ffs_func_revmap_ep(struct ffs_function *func, u8 num) +{ + num = func->eps_revmap[num & USB_ENDPOINT_NUMBER_MASK]; + return num ? num : -EDOM; +} + +static int ffs_func_revmap_intf(struct ffs_function *func, u8 intf) +{ + short *nums = func->interfaces_nums; + unsigned count = func->ffs->interfaces_count; + + for (; count; --count, ++nums) { + if (*nums >= 0 && *nums == intf) + return nums - func->interfaces_nums; + } + + return -EDOM; +} + + +/* Misc helper functions ****************************************************/ + +static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock) +{ + return nonblock + ? likely(mutex_trylock(mutex)) ? 0 : -EAGAIN + : mutex_lock_interruptible(mutex); +} + +static char *ffs_prepare_buffer(const char __user *buf, size_t len) +{ + char *data; + + if (unlikely(!len)) + return NULL; + + data = kmalloc(len, GFP_KERNEL); + if (unlikely(!data)) + return ERR_PTR(-ENOMEM); + + if (unlikely(__copy_from_user(data, buf, len))) { + kfree(data); + return ERR_PTR(-EFAULT); + } + + pr_vdebug("Buffer from user space:\n"); + ffs_dump_mem("", data, len); + + return data; +} diff --git a/drivers/usb/gadget/f_hid.c b/drivers/usb/gadget/f_hid.c new file mode 100644 index 00000000..6e69a8e8 --- /dev/null +++ b/drivers/usb/gadget/f_hid.c @@ -0,0 +1,771 @@ +/* + * f_hid.c -- USB HID function driver + * + * Copyright (C) 2010 Fabien Chouteau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int major, minors; +static struct class *hidg_class; + +/*-------------------------------------------------------------------------*/ +/* HID gadget struct */ + +struct f_hidg_req_list { + struct usb_request *req; + unsigned int pos; + struct list_head list; +}; + +struct f_hidg { + /* configuration */ + unsigned char bInterfaceSubClass; + unsigned char bInterfaceProtocol; + unsigned short report_desc_length; + char *report_desc; + unsigned short report_length; + + /* recv report */ + struct list_head completed_out_req; + spinlock_t spinlock; + wait_queue_head_t read_queue; + unsigned int qlen; + + /* send report */ + struct mutex lock; + bool write_pending; + wait_queue_head_t write_queue; + struct usb_request *req; + + int minor; + struct cdev cdev; + struct usb_function func; + + struct usb_ep *in_ep; + struct usb_ep *out_ep; +}; + +static inline struct f_hidg *func_to_hidg(struct usb_function *f) +{ + return container_of(f, struct f_hidg, func); +} + +/*-------------------------------------------------------------------------*/ +/* Static descriptors */ + +static struct usb_interface_descriptor hidg_interface_desc = { + .bLength = sizeof hidg_interface_desc, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + /* .bInterfaceSubClass = DYNAMIC */ + /* .bInterfaceProtocol = DYNAMIC */ + /* .iInterface = DYNAMIC */ +}; + +static struct hid_descriptor hidg_desc = { + .bLength = sizeof hidg_desc, + .bDescriptorType = HID_DT_HID, + .bcdHID = 0x0101, + .bCountryCode = 0x00, + .bNumDescriptors = 0x1, + /*.desc[0].bDescriptorType = DYNAMIC */ + /*.desc[0].wDescriptorLenght = DYNAMIC */ +}; + +/* High-Speed Support */ + +static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 4, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + +static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 4, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + +static struct usb_descriptor_header *hidg_hs_descriptors[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_hs_in_ep_desc, + (struct usb_descriptor_header *)&hidg_hs_out_ep_desc, + NULL, +}; + +/* Full-Speed Support */ + +static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 10, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + +static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 10, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + +static struct usb_descriptor_header *hidg_fs_descriptors[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_fs_in_ep_desc, + (struct usb_descriptor_header *)&hidg_fs_out_ep_desc, + NULL, +}; + +/*-------------------------------------------------------------------------*/ +/* Char Device */ + +static ssize_t f_hidg_read(struct file *file, char __user *buffer, + size_t count, loff_t *ptr) +{ + struct f_hidg *hidg = file->private_data; + struct f_hidg_req_list *list; + struct usb_request *req; + unsigned long flags; + int ret; + + if (!count) + return 0; + + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + + spin_lock_irqsave(&hidg->spinlock, flags); + +#define READ_COND (!list_empty(&hidg->completed_out_req)) + + /* wait for at least one buffer to complete */ + while (!READ_COND) { + spin_unlock_irqrestore(&hidg->spinlock, flags); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(hidg->read_queue, READ_COND)) + return -ERESTARTSYS; + + spin_lock_irqsave(&hidg->spinlock, flags); + } + + /* pick the first one */ + list = list_first_entry(&hidg->completed_out_req, + struct f_hidg_req_list, list); + req = list->req; + count = min_t(unsigned int, count, req->actual - list->pos); + spin_unlock_irqrestore(&hidg->spinlock, flags); + + /* copy to user outside spinlock */ + count -= copy_to_user(buffer, req->buf + list->pos, count); + list->pos += count; + + /* + * if this request is completely handled and transfered to + * userspace, remove its entry from the list and requeue it + * again. Otherwise, we will revisit it again upon the next + * call, taking into account its current read position. + */ + if (list->pos == req->actual) { + spin_lock_irqsave(&hidg->spinlock, flags); + list_del(&list->list); + kfree(list); + spin_unlock_irqrestore(&hidg->spinlock, flags); + + req->length = hidg->report_length; + ret = usb_ep_queue(hidg->out_ep, req, GFP_KERNEL); + if (ret < 0) + return ret; + } + + return count; +} + +static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_hidg *hidg = (struct f_hidg *)ep->driver_data; + + if (req->status != 0) { + ERROR(hidg->func.config->cdev, + "End Point Request ERROR: %d\n", req->status); + } + + hidg->write_pending = 0; + wake_up(&hidg->write_queue); +} + +static ssize_t f_hidg_write(struct file *file, const char __user *buffer, + size_t count, loff_t *offp) +{ + struct f_hidg *hidg = file->private_data; + ssize_t status = -ENOMEM; + + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + + mutex_lock(&hidg->lock); + +#define WRITE_COND (!hidg->write_pending) + + /* write queue */ + while (!WRITE_COND) { + mutex_unlock(&hidg->lock); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible_exclusive( + hidg->write_queue, WRITE_COND)) + return -ERESTARTSYS; + + mutex_lock(&hidg->lock); + } + + count = min_t(unsigned, count, hidg->report_length); + status = copy_from_user(hidg->req->buf, buffer, count); + + if (status != 0) { + ERROR(hidg->func.config->cdev, + "copy_from_user error\n"); + mutex_unlock(&hidg->lock); + return -EINVAL; + } + + hidg->req->status = 0; + hidg->req->zero = 0; + hidg->req->length = count; + hidg->req->complete = f_hidg_req_complete; + hidg->req->context = hidg; + hidg->write_pending = 1; + + status = usb_ep_queue(hidg->in_ep, hidg->req, GFP_ATOMIC); + if (status < 0) { + ERROR(hidg->func.config->cdev, + "usb_ep_queue error on int endpoint %zd\n", status); + hidg->write_pending = 0; + wake_up(&hidg->write_queue); + } else { + status = count; + } + + mutex_unlock(&hidg->lock); + + return status; +} + +static unsigned int f_hidg_poll(struct file *file, poll_table *wait) +{ + struct f_hidg *hidg = file->private_data; + unsigned int ret = 0; + + poll_wait(file, &hidg->read_queue, wait); + poll_wait(file, &hidg->write_queue, wait); + + if (WRITE_COND) + ret |= POLLOUT | POLLWRNORM; + + if (READ_COND) + ret |= POLLIN | POLLRDNORM; + + return ret; +} + +#undef WRITE_COND +#undef READ_COND + +static int f_hidg_release(struct inode *inode, struct file *fd) +{ + fd->private_data = NULL; + return 0; +} + +static int f_hidg_open(struct inode *inode, struct file *fd) +{ + struct f_hidg *hidg = + container_of(inode->i_cdev, struct f_hidg, cdev); + + fd->private_data = hidg; + + return 0; +} + +/*-------------------------------------------------------------------------*/ +/* usb_function */ + +static struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep, unsigned length) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req) { + req->length = length; + req->buf = kmalloc(length, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + return req; +} + +static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_hidg *hidg = (struct f_hidg *) req->context; + struct f_hidg_req_list *req_list; + unsigned long flags; + + req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC); + if (!req_list) + return; + + req_list->req = req; + + spin_lock_irqsave(&hidg->spinlock, flags); + list_add_tail(&req_list->list, &hidg->completed_out_req); + spin_unlock_irqrestore(&hidg->spinlock, flags); + + wake_up(&hidg->read_queue); +} + +static int hidg_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_hidg *hidg = func_to_hidg(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int status = 0; + __u16 value, length; + + value = __le16_to_cpu(ctrl->wValue); + length = __le16_to_cpu(ctrl->wLength); + + VDBG(cdev, "hid_setup crtl_request : bRequestType:0x%x bRequest:0x%x " + "Value:0x%x\n", ctrl->bRequestType, ctrl->bRequest, value); + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_GET_REPORT): + VDBG(cdev, "get_report\n"); + + /* send an empty report */ + length = min_t(unsigned, length, hidg->report_length); + memset(req->buf, 0x0, length); + + goto respond; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_GET_PROTOCOL): + VDBG(cdev, "get_protocol\n"); + goto stall; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_REPORT): + VDBG(cdev, "set_report | wLenght=%d\n", ctrl->wLength); + goto stall; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_PROTOCOL): + VDBG(cdev, "set_protocol\n"); + goto stall; + break; + + case ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8 + | USB_REQ_GET_DESCRIPTOR): + switch (value >> 8) { + case HID_DT_HID: + VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n"); + length = min_t(unsigned short, length, + hidg_desc.bLength); + memcpy(req->buf, &hidg_desc, length); + goto respond; + break; + case HID_DT_REPORT: + VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n"); + length = min_t(unsigned short, length, + hidg->report_desc_length); + memcpy(req->buf, hidg->report_desc, length); + goto respond; + break; + + default: + VDBG(cdev, "Unknown descriptor request 0x%x\n", + value >> 8); + goto stall; + break; + } + break; + + default: + VDBG(cdev, "Unknown request 0x%x\n", + ctrl->bRequest); + goto stall; + break; + } + +stall: + return -EOPNOTSUPP; + +respond: + req->zero = 0; + req->length = length; + status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (status < 0) + ERROR(cdev, "usb_ep_queue error on ep0 %d\n", value); + return status; +} + +static void hidg_disable(struct usb_function *f) +{ + struct f_hidg *hidg = func_to_hidg(f); + struct f_hidg_req_list *list, *next; + + usb_ep_disable(hidg->in_ep); + hidg->in_ep->driver_data = NULL; + + usb_ep_disable(hidg->out_ep); + hidg->out_ep->driver_data = NULL; + + list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) { + list_del(&list->list); + kfree(list); + } +} + +static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct f_hidg *hidg = func_to_hidg(f); + int i, status = 0; + + VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt); + + if (hidg->in_ep != NULL) { + /* restart endpoint */ + if (hidg->in_ep->driver_data != NULL) + usb_ep_disable(hidg->in_ep); + + status = config_ep_by_speed(f->config->cdev->gadget, f, + hidg->in_ep); + if (status) { + ERROR(cdev, "config_ep_by_speed FAILED!\n"); + goto fail; + } + status = usb_ep_enable(hidg->in_ep); + if (status < 0) { + ERROR(cdev, "Enable IN endpoint FAILED!\n"); + goto fail; + } + hidg->in_ep->driver_data = hidg; + } + + + if (hidg->out_ep != NULL) { + /* restart endpoint */ + if (hidg->out_ep->driver_data != NULL) + usb_ep_disable(hidg->out_ep); + + status = config_ep_by_speed(f->config->cdev->gadget, f, + hidg->out_ep); + if (status) { + ERROR(cdev, "config_ep_by_speed FAILED!\n"); + goto fail; + } + status = usb_ep_enable(hidg->out_ep); + if (status < 0) { + ERROR(cdev, "Enable IN endpoint FAILED!\n"); + goto fail; + } + hidg->out_ep->driver_data = hidg; + + /* + * allocate a bunch of read buffers and queue them all at once. + */ + for (i = 0; i < hidg->qlen && status == 0; i++) { + struct usb_request *req = + hidg_alloc_ep_req(hidg->out_ep, + hidg->report_length); + if (req) { + req->complete = hidg_set_report_complete; + req->context = hidg; + status = usb_ep_queue(hidg->out_ep, req, + GFP_ATOMIC); + if (status) + ERROR(cdev, "%s queue req --> %d\n", + hidg->out_ep->name, status); + } else { + usb_ep_disable(hidg->out_ep); + hidg->out_ep->driver_data = NULL; + status = -ENOMEM; + goto fail; + } + } + } + +fail: + return status; +} + +const struct file_operations f_hidg_fops = { + .owner = THIS_MODULE, + .open = f_hidg_open, + .release = f_hidg_release, + .write = f_hidg_write, + .read = f_hidg_read, + .poll = f_hidg_poll, + .llseek = noop_llseek, +}; + +static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_ep *ep; + struct f_hidg *hidg = func_to_hidg(f); + int status; + dev_t dev; + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + hidg_interface_desc.bInterfaceNumber = status; + + /* allocate instance-specific endpoints */ + status = -ENODEV; + ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_in_ep_desc); + if (!ep) + goto fail; + ep->driver_data = c->cdev; /* claim */ + hidg->in_ep = ep; + + ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc); + if (!ep) + goto fail; + ep->driver_data = c->cdev; /* claim */ + hidg->out_ep = ep; + + /* preallocate request and buffer */ + status = -ENOMEM; + hidg->req = usb_ep_alloc_request(hidg->in_ep, GFP_KERNEL); + if (!hidg->req) + goto fail; + + hidg->req->buf = kmalloc(hidg->report_length, GFP_KERNEL); + if (!hidg->req->buf) + goto fail; + + /* set descriptor dynamic values */ + hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass; + hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol; + hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_fs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_desc.desc[0].bDescriptorType = HID_DT_REPORT; + hidg_desc.desc[0].wDescriptorLength = + cpu_to_le16(hidg->report_desc_length); + + hidg_hs_in_ep_desc.bEndpointAddress = + hidg_fs_in_ep_desc.bEndpointAddress; + hidg_hs_out_ep_desc.bEndpointAddress = + hidg_fs_out_ep_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, hidg_fs_descriptors, + hidg_hs_descriptors, NULL); + if (status) + goto fail; + + mutex_init(&hidg->lock); + spin_lock_init(&hidg->spinlock); + init_waitqueue_head(&hidg->write_queue); + init_waitqueue_head(&hidg->read_queue); + INIT_LIST_HEAD(&hidg->completed_out_req); + + /* create char device */ + cdev_init(&hidg->cdev, &f_hidg_fops); + dev = MKDEV(major, hidg->minor); + status = cdev_add(&hidg->cdev, dev, 1); + if (status) + goto fail; + + device_create(hidg_class, NULL, dev, NULL, "%s%d", "hidg", hidg->minor); + + return 0; + +fail: + ERROR(f->config->cdev, "hidg_bind FAILED\n"); + if (hidg->req != NULL) { + kfree(hidg->req->buf); + if (hidg->in_ep != NULL) + usb_ep_free_request(hidg->in_ep, hidg->req); + } + + usb_free_all_descriptors(f); + return status; +} + +static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_hidg *hidg = func_to_hidg(f); + + device_destroy(hidg_class, MKDEV(major, hidg->minor)); + cdev_del(&hidg->cdev); + + /* disable/free request and end point */ + usb_ep_disable(hidg->in_ep); + usb_ep_dequeue(hidg->in_ep, hidg->req); + kfree(hidg->req->buf); + usb_ep_free_request(hidg->in_ep, hidg->req); + + usb_free_all_descriptors(f); + + kfree(hidg->report_desc); + kfree(hidg); +} + +/*-------------------------------------------------------------------------*/ +/* Strings */ + +#define CT_FUNC_HID_IDX 0 + +static struct usb_string ct_func_string_defs[] = { + [CT_FUNC_HID_IDX].s = "HID Interface", + {}, /* end of list */ +}; + +static struct usb_gadget_strings ct_func_string_table = { + .language = 0x0409, /* en-US */ + .strings = ct_func_string_defs, +}; + +static struct usb_gadget_strings *ct_func_strings[] = { + &ct_func_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ +/* usb_configuration */ + +int __init hidg_bind_config(struct usb_configuration *c, + struct hidg_func_descriptor *fdesc, int index) +{ + struct f_hidg *hidg; + int status; + + if (index >= minors) + return -ENOENT; + + /* maybe allocate device-global string IDs, and patch descriptors */ + if (ct_func_string_defs[CT_FUNC_HID_IDX].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ct_func_string_defs[CT_FUNC_HID_IDX].id = status; + hidg_interface_desc.iInterface = status; + } + + /* allocate and initialize one new instance */ + hidg = kzalloc(sizeof *hidg, GFP_KERNEL); + if (!hidg) + return -ENOMEM; + + hidg->minor = index; + hidg->bInterfaceSubClass = fdesc->subclass; + hidg->bInterfaceProtocol = fdesc->protocol; + hidg->report_length = fdesc->report_length; + hidg->report_desc_length = fdesc->report_desc_length; + hidg->report_desc = kmemdup(fdesc->report_desc, + fdesc->report_desc_length, + GFP_KERNEL); + if (!hidg->report_desc) { + kfree(hidg); + return -ENOMEM; + } + + hidg->func.name = "hid"; + hidg->func.strings = ct_func_strings; + hidg->func.bind = hidg_bind; + hidg->func.unbind = hidg_unbind; + hidg->func.set_alt = hidg_set_alt; + hidg->func.disable = hidg_disable; + hidg->func.setup = hidg_setup; + + /* this could me made configurable at some point */ + hidg->qlen = 4; + + status = usb_add_function(c, &hidg->func); + if (status) + kfree(hidg); + + return status; +} + +int __init ghid_setup(struct usb_gadget *g, int count) +{ + int status; + dev_t dev; + + hidg_class = class_create(THIS_MODULE, "hidg"); + + status = alloc_chrdev_region(&dev, 0, count, "hidg"); + if (!status) { + major = MAJOR(dev); + minors = count; + } + + return status; +} + +void ghid_cleanup(void) +{ + if (major) { + unregister_chrdev_region(MKDEV(major, 0), minors); + major = minors = 0; + } + + class_destroy(hidg_class); + hidg_class = NULL; +} diff --git a/drivers/usb/gadget/f_loopback.c b/drivers/usb/gadget/f_loopback.c new file mode 100644 index 00000000..4a3873a0 --- /dev/null +++ b/drivers/usb/gadget/f_loopback.c @@ -0,0 +1,433 @@ +/* + * f_loopback.c - USB peripheral loopback configuration driver + * + * Copyright (C) 2003-2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include + +#include "g_zero.h" + +/* + * LOOPBACK FUNCTION ... a testing vehicle for USB peripherals, + * + * This takes messages of various sizes written OUT to a device, and loops + * them back so they can be read IN from it. It has been used by certain + * test applications. It supports limited testing of data queueing logic. + * + * + * This is currently packaged as a configuration driver, which can't be + * combined with other functions to make composite devices. However, it + * can be combined with other independent configurations. + */ +struct f_loopback { + struct usb_function function; + + struct usb_ep *in_ep; + struct usb_ep *out_ep; +}; + +static inline struct f_loopback *func_to_loop(struct usb_function *f) +{ + return container_of(f, struct f_loopback, function); +} + +static unsigned qlen; +static unsigned buflen; + +/*-------------------------------------------------------------------------*/ + +static struct usb_interface_descriptor loopback_intf = { + .bLength = sizeof loopback_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_loop_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_loop_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_loopback_descs[] = { + (struct usb_descriptor_header *) &loopback_intf, + (struct usb_descriptor_header *) &fs_loop_sink_desc, + (struct usb_descriptor_header *) &fs_loop_source_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_loop_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_loop_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *hs_loopback_descs[] = { + (struct usb_descriptor_header *) &loopback_intf, + (struct usb_descriptor_header *) &hs_loop_source_desc, + (struct usb_descriptor_header *) &hs_loop_sink_desc, + NULL, +}; + +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_loop_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +struct usb_ss_ep_comp_descriptor ss_loop_source_comp_desc = { + .bLength = USB_DT_SS_EP_COMP_SIZE, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = 0, +}; + +static struct usb_endpoint_descriptor ss_loop_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +struct usb_ss_ep_comp_descriptor ss_loop_sink_comp_desc = { + .bLength = USB_DT_SS_EP_COMP_SIZE, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = 0, +}; + +static struct usb_descriptor_header *ss_loopback_descs[] = { + (struct usb_descriptor_header *) &loopback_intf, + (struct usb_descriptor_header *) &ss_loop_source_desc, + (struct usb_descriptor_header *) &ss_loop_source_comp_desc, + (struct usb_descriptor_header *) &ss_loop_sink_desc, + (struct usb_descriptor_header *) &ss_loop_sink_comp_desc, + NULL, +}; + +/* function-specific strings: */ + +static struct usb_string strings_loopback[] = { + [0].s = "loop input to output", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_loop = { + .language = 0x0409, /* en-us */ + .strings = strings_loopback, +}; + +static struct usb_gadget_strings *loopback_strings[] = { + &stringtab_loop, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int loopback_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_loopback *loop = func_to_loop(f); + int id; + int ret; + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + loopback_intf.bInterfaceNumber = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_loopback[0].id = id; + loopback_intf.iInterface = id; + + /* allocate endpoints */ + + loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc); + if (!loop->in_ep) { +autoconf_fail: + ERROR(cdev, "%s: can't autoconfigure on %s\n", + f->name, cdev->gadget->name); + return -ENODEV; + } + loop->in_ep->driver_data = cdev; /* claim */ + + loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc); + if (!loop->out_ep) + goto autoconf_fail; + loop->out_ep->driver_data = cdev; /* claim */ + + /* support high speed hardware */ + hs_loop_source_desc.bEndpointAddress = + fs_loop_source_desc.bEndpointAddress; + hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress; + + /* support super speed hardware */ + ss_loop_source_desc.bEndpointAddress = + fs_loop_source_desc.bEndpointAddress; + ss_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress; + + ret = usb_assign_descriptors(f, fs_loopback_descs, hs_loopback_descs, + ss_loopback_descs); + if (ret) + return ret; + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + (gadget_is_superspeed(c->cdev->gadget) ? "super" : + (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")), + f->name, loop->in_ep->name, loop->out_ep->name); + return 0; +} + +static void lb_free_func(struct usb_function *f) +{ + usb_free_all_descriptors(f); + kfree(func_to_loop(f)); +} + +static void loopback_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_loopback *loop = ep->driver_data; + struct usb_composite_dev *cdev = loop->function.config->cdev; + int status = req->status; + + switch (status) { + + case 0: /* normal completion? */ + if (ep == loop->out_ep) { + /* loop this OUT packet back IN to the host */ + req->zero = (req->actual < req->length); + req->length = req->actual; + status = usb_ep_queue(loop->in_ep, req, GFP_ATOMIC); + if (status == 0) + return; + + /* "should never get here" */ + ERROR(cdev, "can't loop %s to %s: %d\n", + ep->name, loop->in_ep->name, + status); + } + + /* queue the buffer for some later OUT packet */ + req->length = buflen; + status = usb_ep_queue(loop->out_ep, req, GFP_ATOMIC); + if (status == 0) + return; + + /* "should never get here" */ + /* FALLTHROUGH */ + + default: + ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name, + status, req->actual, req->length); + /* FALLTHROUGH */ + + /* NOTE: since this driver doesn't maintain an explicit record + * of requests it submitted (just maintains qlen count), we + * rely on the hardware driver to clean up on disconnect or + * endpoint disable. + */ + case -ECONNABORTED: /* hardware forced ep reset */ + case -ECONNRESET: /* request dequeued */ + case -ESHUTDOWN: /* disconnect from host */ + free_ep_req(ep, req); + return; + } +} + +static void disable_loopback(struct f_loopback *loop) +{ + struct usb_composite_dev *cdev; + + cdev = loop->function.config->cdev; + disable_endpoints(cdev, loop->in_ep, loop->out_ep, NULL, NULL); + VDBG(cdev, "%s disabled\n", loop->function.name); +} + +static int +enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop) +{ + int result = 0; + struct usb_ep *ep; + struct usb_request *req; + unsigned i; + + /* one endpoint writes data back IN to the host */ + ep = loop->in_ep; + result = config_ep_by_speed(cdev->gadget, &(loop->function), ep); + if (result) + return result; + result = usb_ep_enable(ep); + if (result < 0) + return result; + ep->driver_data = loop; + + /* one endpoint just reads OUT packets */ + ep = loop->out_ep; + result = config_ep_by_speed(cdev->gadget, &(loop->function), ep); + if (result) + goto fail0; + + result = usb_ep_enable(ep); + if (result < 0) { +fail0: + ep = loop->in_ep; + usb_ep_disable(ep); + ep->driver_data = NULL; + return result; + } + ep->driver_data = loop; + + /* allocate a bunch of read buffers and queue them all at once. + * we buffer at most 'qlen' transfers; fewer if any need more + * than 'buflen' bytes each. + */ + for (i = 0; i < qlen && result == 0; i++) { + req = alloc_ep_req(ep, 0); + if (req) { + req->complete = loopback_complete; + result = usb_ep_queue(ep, req, GFP_ATOMIC); + if (result) + ERROR(cdev, "%s queue req --> %d\n", + ep->name, result); + } else { + usb_ep_disable(ep); + ep->driver_data = NULL; + result = -ENOMEM; + goto fail0; + } + } + + DBG(cdev, "%s enabled\n", loop->function.name); + return result; +} + +static int loopback_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct f_loopback *loop = func_to_loop(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt is zero */ + if (loop->in_ep->driver_data) + disable_loopback(loop); + return enable_loopback(cdev, loop); +} + +static void loopback_disable(struct usb_function *f) +{ + struct f_loopback *loop = func_to_loop(f); + + disable_loopback(loop); +} + +static struct usb_function *loopback_alloc(struct usb_function_instance *fi) +{ + struct f_loopback *loop; + struct f_lb_opts *lb_opts; + + loop = kzalloc(sizeof *loop, GFP_KERNEL); + if (!loop) + return ERR_PTR(-ENOMEM); + + lb_opts = container_of(fi, struct f_lb_opts, func_inst); + buflen = lb_opts->bulk_buflen; + qlen = lb_opts->qlen; + if (!qlen) + qlen = 32; + + loop->function.name = "loopback"; + loop->function.bind = loopback_bind; + loop->function.set_alt = loopback_set_alt; + loop->function.disable = loopback_disable; + loop->function.strings = loopback_strings; + + loop->function.free_func = lb_free_func; + + return &loop->function; +} + +static void lb_free_instance(struct usb_function_instance *fi) +{ + struct f_lb_opts *lb_opts; + + lb_opts = container_of(fi, struct f_lb_opts, func_inst); + kfree(lb_opts); +} + +static struct usb_function_instance *loopback_alloc_instance(void) +{ + struct f_lb_opts *lb_opts; + + lb_opts = kzalloc(sizeof(*lb_opts), GFP_KERNEL); + if (!lb_opts) + return ERR_PTR(-ENOMEM); + lb_opts->func_inst.free_func_inst = lb_free_instance; + return &lb_opts->func_inst; +} +DECLARE_USB_FUNCTION(Loopback, loopback_alloc_instance, loopback_alloc); + +int __init lb_modinit(void) +{ + int ret; + + ret = usb_function_register(&Loopbackusb_func); + if (ret) + return ret; + return ret; +} +void __exit lb_modexit(void) +{ + usb_function_unregister(&Loopbackusb_func); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c new file mode 100644 index 00000000..e73e6d66 --- /dev/null +++ b/drivers/usb/gadget/f_mass_storage.c @@ -0,0 +1,3173 @@ +/* + * f_mass_storage.c -- Mass Storage USB Composite Function + * + * Copyright (C) 2003-2008 Alan Stern + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * The Mass Storage Function acts as a USB Mass Storage device, + * appearing to the host as a disk drive or as a CD-ROM drive. In + * addition to providing an example of a genuinely useful composite + * function for a USB device, it also illustrates a technique of + * double-buffering for increased throughput. + * + * For more information about MSF and in particular its module + * parameters and sysfs interface read the + * file. + */ + +/* + * MSF is configured by specifying a fsg_config structure. It has the + * following fields: + * + * nluns Number of LUNs function have (anywhere from 1 + * to FSG_MAX_LUNS which is 8). + * luns An array of LUN configuration values. This + * should be filled for each LUN that + * function will include (ie. for "nluns" + * LUNs). Each element of the array has + * the following fields: + * ->filename The path to the backing file for the LUN. + * Required if LUN is not marked as + * removable. + * ->ro Flag specifying access to the LUN shall be + * read-only. This is implied if CD-ROM + * emulation is enabled as well as when + * it was impossible to open "filename" + * in R/W mode. + * ->removable Flag specifying that LUN shall be indicated as + * being removable. + * ->cdrom Flag specifying that LUN shall be reported as + * being a CD-ROM. + * ->nofua Flag specifying that FUA flag in SCSI WRITE(10,12) + * commands for this LUN shall be ignored. + * + * vendor_name + * product_name + * release Information used as a reply to INQUIRY + * request. To use default set to NULL, + * NULL, 0xffff respectively. The first + * field should be 8 and the second 16 + * characters or less. + * + * can_stall Set to permit function to halt bulk endpoints. + * Disabled on some USB devices known not + * to work correctly. You should set it + * to true. + * + * If "removable" is not set for a LUN then a backing file must be + * specified. If it is set, then NULL filename means the LUN's medium + * is not loaded (an empty string as "filename" in the fsg_config + * structure causes error). The CD-ROM emulation includes a single + * data track and no audio tracks; hence there need be only one + * backing file per LUN. + * + * This function is heavily based on "File-backed Storage Gadget" by + * Alan Stern which in turn is heavily based on "Gadget Zero" by David + * Brownell. The driver's SCSI command interface was based on the + * "Information technology - Small Computer System Interface - 2" + * document from X3T9.2 Project 375D, Revision 10L, 7-SEP-93, + * available at . + * The single exception is opcode 0x23 (READ FORMAT CAPACITIES), which + * was based on the "Universal Serial Bus Mass Storage Class UFI + * Command Specification" document, Revision 1.0, December 14, 1998, + * available at + * . + */ + +/* + * Driver Design + * + * The MSF is fairly straightforward. There is a main kernel + * thread that handles most of the work. Interrupt routines field + * callbacks from the controller driver: bulk- and interrupt-request + * completion notifications, endpoint-0 events, and disconnect events. + * Completion events are passed to the main thread by wakeup calls. Many + * ep0 requests are handled at interrupt time, but SetInterface, + * SetConfiguration, and device reset requests are forwarded to the + * thread in the form of "exceptions" using SIGUSR1 signals (since they + * should interrupt any ongoing file I/O operations). + * + * The thread's main routine implements the standard command/data/status + * parts of a SCSI interaction. It and its subroutines are full of tests + * for pending signals/exceptions -- all this polling is necessary since + * the kernel has no setjmp/longjmp equivalents. (Maybe this is an + * indication that the driver really wants to be running in userspace.) + * An important point is that so long as the thread is alive it keeps an + * open reference to the backing file. This will prevent unmounting + * the backing file's underlying filesystem and could cause problems + * during system shutdown, for example. To prevent such problems, the + * thread catches INT, TERM, and KILL signals and converts them into + * an EXIT exception. + * + * In normal operation the main thread is started during the gadget's + * fsg_bind() callback and stopped during fsg_unbind(). But it can + * also exit when it receives a signal, and there's no point leaving + * the gadget running when the thread is dead. As of this moment, MSF + * provides no way to deregister the gadget when thread dies -- maybe + * a callback functions is needed. + * + * To provide maximum throughput, the driver uses a circular pipeline of + * buffer heads (struct fsg_buffhd). In principle the pipeline can be + * arbitrarily long; in practice the benefits don't justify having more + * than 2 stages (i.e., double buffering). But it helps to think of the + * pipeline as being a long one. Each buffer head contains a bulk-in and + * a bulk-out request pointer (since the buffer can be used for both + * output and input -- directions always are given from the host's + * point of view) as well as a pointer to the buffer and various state + * variables. + * + * Use of the pipeline follows a simple protocol. There is a variable + * (fsg->next_buffhd_to_fill) that points to the next buffer head to use. + * At any time that buffer head may still be in use from an earlier + * request, so each buffer head has a state variable indicating whether + * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the + * buffer head to be EMPTY, filling the buffer either by file I/O or by + * USB I/O (during which the buffer head is BUSY), and marking the buffer + * head FULL when the I/O is complete. Then the buffer will be emptied + * (again possibly by USB I/O, during which it is marked BUSY) and + * finally marked EMPTY again (possibly by a completion routine). + * + * A module parameter tells the driver to avoid stalling the bulk + * endpoints wherever the transport specification allows. This is + * necessary for some UDCs like the SuperH, which cannot reliably clear a + * halt on a bulk endpoint. However, under certain circumstances the + * Bulk-only specification requires a stall. In such cases the driver + * will halt the endpoint and set a flag indicating that it should clear + * the halt in software during the next device reset. Hopefully this + * will permit everything to work correctly. Furthermore, although the + * specification allows the bulk-out endpoint to halt when the host sends + * too much data, implementing this would cause an unavoidable race. + * The driver will always use the "no-stall" approach for OUT transfers. + * + * One subtle point concerns sending status-stage responses for ep0 + * requests. Some of these requests, such as device reset, can involve + * interrupting an ongoing file I/O operation, which might take an + * arbitrarily long time. During that delay the host might give up on + * the original ep0 request and issue a new one. When that happens the + * driver should not notify the host about completion of the original + * request, as the host will no longer be waiting for it. So the driver + * assigns to each ep0 request a unique tag, and it keeps track of the + * tag value of the request associated with a long-running exception + * (device-reset, interface-change, or configuration-change). When the + * exception handler is finished, the status-stage response is submitted + * only if the current ep0 request tag is equal to the exception request + * tag. Thus only the most recently received ep0 request will get a + * status-stage response. + * + * Warning: This driver source file is too long. It ought to be split up + * into a header file plus about 3 separate .c files, to handle the details + * of the Gadget, USB Mass Storage, and SCSI protocols. + */ + + +/* #define VERBOSE_DEBUG */ +/* #define DUMP_MSGS */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "gadget_chips.h" + + +/*------------------------------------------------------------------------*/ + +#define FSG_DRIVER_DESC "Mass Storage Function" +#define FSG_DRIVER_VERSION "2009/09/11" + +static const char fsg_string_interface[] = "Mass Storage"; + +#include "storage_common.c" + + +/*-------------------------------------------------------------------------*/ + +struct fsg_dev; +struct fsg_common; + +/* FSF callback functions */ +struct fsg_operations { + /* + * Callback function to call when thread exits. If no + * callback is set or it returns value lower then zero MSF + * will force eject all LUNs it operates on (including those + * marked as non-removable or with prevent_medium_removal flag + * set). + */ + int (*thread_exits)(struct fsg_common *common); +}; + +/* Data shared by all the FSG instances. */ +struct fsg_common { + struct usb_gadget *gadget; + struct usb_composite_dev *cdev; + struct fsg_dev *fsg, *new_fsg; + wait_queue_head_t fsg_wait; + + /* filesem protects: backing files in use */ + struct rw_semaphore filesem; + + /* lock protects: state, all the req_busy's */ + spinlock_t lock; + + struct usb_ep *ep0; /* Copy of gadget->ep0 */ + struct usb_request *ep0req; /* Copy of cdev->req */ + unsigned int ep0_req_tag; + + struct fsg_buffhd *next_buffhd_to_fill; + struct fsg_buffhd *next_buffhd_to_drain; + struct fsg_buffhd *buffhds; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + + unsigned int nluns; + unsigned int board_support_luns; + unsigned int lun; + struct fsg_lun *luns; + struct fsg_lun *curlun; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; /* For exception handling */ + unsigned int exception_req_tag; + + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + u32 residue; + u32 usb_amount_left; + + unsigned int can_stall:1; + unsigned int free_storage_on_release:1; + unsigned int phase_error:1; + unsigned int short_packet_received:1; + unsigned int bad_lun_okay:1; + unsigned int running:1; + + int thread_wakeup_needed; + struct completion thread_notifier; + struct task_struct *thread_task; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + /* + * Vendor (8 chars), product (16 chars), release (4 + * hexadecimal digits) and NUL byte + */ + char inquiry_string[8 + 16 + 4 + 1]; + + struct kref ref; + struct work_struct fsync_work; +}; + +struct fsg_config { + unsigned nluns; + struct fsg_lun_config { + const char *filename; + char ro; + char removable; + char cdrom; + char nofua; + } luns[FSG_MAX_LUNS]; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + const char *vendor_name; /* 8 characters or less */ + const char *product_name; /* 16 characters or less */ + + char can_stall; +}; + +struct fsg_dev { + struct usb_function function; + struct usb_gadget *gadget; /* Copy of cdev->gadget */ + struct fsg_common *common; + + u16 interface_number; + + unsigned int bulk_in_enabled:1; + unsigned int bulk_out_enabled:1; + + unsigned long atomic_bitflags; +#define IGNORE_BULK_OUT 0 + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; +}; + +static inline int __fsg_is_set(struct fsg_common *common, + const char *func, unsigned line) +{ + if (common->fsg) + return 1; + ERROR(common, "common->fsg is NULL in %s at %u\n", func, line); + WARN_ON(1); + return 0; +} + +#define fsg_is_set(common) likely(__fsg_is_set(common, __func__, __LINE__)) + +static inline struct fsg_dev *fsg_from_func(struct usb_function *f) +{ + return container_of(f, struct fsg_dev, function); +} + +typedef void (*fsg_routine_t)(struct fsg_dev *); + +static int exception_in_progress(struct fsg_common *common) +{ + return common->state > FSG_STATE_IDLE; +} + +/* Make bulk-out requests be divisible by the maxpacket size */ +static void set_bulk_out_req_length(struct fsg_common *common, + struct fsg_buffhd *bh, unsigned int length) +{ + unsigned int rem; + + bh->bulk_out_intended_length = length; + rem = length % common->bulk_out_maxpacket; + if (rem > 0) + length += common->bulk_out_maxpacket - rem; + bh->outreq->length = length; +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) +{ + const char *name; + + if (ep == fsg->bulk_in) + name = "bulk-in"; + else if (ep == fsg->bulk_out) + name = "bulk-out"; + else + name = ep->name; + DBG(fsg, "%s set halt\n", name); + return usb_ep_set_halt(ep); +} + + +/*-------------------------------------------------------------------------*/ + +/* These routines may be called in process context or in_irq */ + +/* Caller must hold fsg->lock */ +static void wakeup_thread(struct fsg_common *common) +{ + smp_wmb(); /* ensure the write of bh->state is complete */ + /* Tell the main thread that something has happened */ + common->thread_wakeup_needed = 1; + if (common->thread_task) + wake_up_process(common->thread_task); +} + +static void raise_exception(struct fsg_common *common, enum fsg_state new_state) +{ + unsigned long flags; + + /* + * Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. + */ + printk("%s:state=%d,%d fsg=%p \n",__func__,common->state,new_state,common->new_fsg); + spin_lock_irqsave(&common->lock, flags); + if (common->state <= new_state) { + common->exception_req_tag = common->ep0_req_tag; + common->state = new_state; + if (common->thread_task) + send_sig_info(SIGUSR1, SEND_SIG_FORCED, + common->thread_task); + } + spin_unlock_irqrestore(&common->lock, flags); +} + + +/*-------------------------------------------------------------------------*/ + +static int ep0_queue(struct fsg_common *common) +{ + int rc; + + rc = usb_ep_queue(common->ep0, common->ep0req, GFP_ATOMIC); + common->ep0->driver_data = common; + if (rc != 0 && rc != -ESHUTDOWN) { + /* We can't do much more than wait for a reset */ + WARNING(common, "error in submission: %s --> %d\n", + common->ep0->name, rc); + } + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +/* Completion handlers. These always run in_irq. */ + +static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_common *common = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + if (req->status || req->actual != req->length) + DBG(common, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) /* Request was cancelled */ + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&common->lock); + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(common); + spin_unlock(&common->lock); +} + +static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_common *common = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + dump_msg(common, "bulk-out", req->buf, req->actual); + if (req->status || req->actual != bh->bulk_out_intended_length) + DBG(common, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, bh->bulk_out_intended_length); + if (req->status == -ECONNRESET) /* Request was cancelled */ + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&common->lock); + bh->outreq_busy = 0; + bh->state = BUF_STATE_FULL; + wakeup_thread(common); + spin_unlock(&common->lock); +} + +static int fsg_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_request *req = fsg->common->ep0req; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + if (!fsg_is_set(fsg->common)) + return -EOPNOTSUPP; + + ++fsg->common->ep0_req_tag; /* Record arrival of a new request */ + req->context = NULL; + req->length = 0; + dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl)); + + switch (ctrl->bRequest) { + + case US_BULK_RESET_REQUEST: + if (ctrl->bRequestType != + (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != fsg->interface_number || w_value != 0 || + w_length != 0) + return -EDOM; + + /* + * Raise an exception to stop the current operation + * and reinitialize our state. + */ + DBG(fsg, "bulk reset request\n"); + raise_exception(fsg->common, FSG_STATE_RESET); + return DELAYED_STATUS; + + case US_BULK_GET_MAX_LUN: + if (ctrl->bRequestType != + (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != fsg->interface_number || w_value != 0 || + w_length != 1) + return -EDOM; + VDBG(fsg, "get max LUN\n"); + *(u8 *)req->buf = fsg->common->board_support_luns - 1; + + /* Respond with data/status */ + req->length = min((u16)1, w_length); + return ep0_queue(fsg->common); + } + + VDBG(fsg, + "unknown class-specific control req %02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + le16_to_cpu(ctrl->wValue), w_index, w_length); + return -EOPNOTSUPP; +} + + +/*-------------------------------------------------------------------------*/ + +/* All the following routines run in process context */ + +/* Use this for bulk or interrupt transfers, not ep0 */ +static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req, int *pbusy, + enum fsg_buffer_state *state) +{ + int rc; + + if (ep == fsg->bulk_in) + dump_msg(fsg, "bulk-in", req->buf, req->length); + + spin_lock_irq(&fsg->common->lock); + *pbusy = 1; + *state = BUF_STATE_BUSY; + spin_unlock_irq(&fsg->common->lock); + rc = usb_ep_queue(ep, req, GFP_KERNEL); + if (rc != 0) { + *pbusy = 0; + *state = BUF_STATE_EMPTY; + + /* We can't do much more than wait for a reset */ + + /* + * Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. + */ + if (rc != -ESHUTDOWN && + !(rc == -EOPNOTSUPP && req->length == 0)) + WARNING(fsg, "error in submission: %s --> %d\n", + ep->name, rc); + } +} + +static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh) +{ + if (!fsg_is_set(common)) + return false; + start_transfer(common->fsg, common->fsg->bulk_in, + bh->inreq, &bh->inreq_busy, &bh->state); + return true; +} + +static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh) +{ + if (!fsg_is_set(common)) + return false; + start_transfer(common->fsg, common->fsg->bulk_out, + bh->outreq, &bh->outreq_busy, &bh->state); + return true; +} + +static int sleep_thread(struct fsg_common *common) +{ + int rc = 0; + + /* Wait until a signal arrives or we are woken up */ + for (;;) { + try_to_freeze(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + rc = -EINTR; + break; + } + if (common->thread_wakeup_needed) + break; + schedule(); + } + __set_current_state(TASK_RUNNING); + common->thread_wakeup_needed = 0; + smp_rmb(); /* ensure the latest bh->state is visible */ + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_read(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset, file_offset_tmp; + unsigned int amount; + ssize_t nread; + + /* + * Get the starting Logical Block Address and check that it's + * not too big. + */ + if (common->cmnd[0] == READ_6) + lba = get_unaligned_be24(&common->cmnd[1]); + else { + lba = get_unaligned_be32(&common->cmnd[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. + */ + if ((common->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + file_offset = ((loff_t) lba) << curlun->blkbits; + + /* Carry out the file reads */ + amount_left = common->data_size_from_cmnd; + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply */ + + for (;;) { + /* + * Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + */ + amount = min(amount_left, FSG_BUFLEN); + amount = min((loff_t)amount, + curlun->file_length - file_offset); + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* + * If we were asked to read past the end of file, + * end with an empty buffer. + */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = + file_offset >> curlun->blkbits; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *)bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long)file_offset, (int)nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", (int)nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int)nread, amount); + nread = round_down(nread, curlun->blksize); + } + file_offset += nread; + amount_left -= nread; + common->residue -= nread; + + /* + * Except at the end of the transfer, nread will be + * equal to the buffer size, which is divisible by the + * bulk-in maxpacket size. + */ + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = + file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; /* No more left to read */ + + /* Send this buffer and go read some more */ + bh->inreq->zero = 0; + if (!start_in_transfer(common, bh)) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + } + + return -EIO; /* No default reply */ +} + + +/*-------------------------------------------------------------------------*/ + +static int do_write(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + loff_t usb_offset, file_offset, file_offset_tmp; + unsigned int amount; + ssize_t nwritten; + int rc; + + if (curlun->ro) { + curlun->sense_data = SS_WRITE_PROTECTED; + return -EINVAL; + } + spin_lock(&curlun->filp->f_lock); + curlun->filp->f_flags &= ~O_SYNC; /* Default is not to wait */ + spin_unlock(&curlun->filp->f_lock); + + /* + * Get the starting Logical Block Address and check that it's + * not too big + */ + if (common->cmnd[0] == WRITE_6) + lba = get_unaligned_be24(&common->cmnd[1]); + else { + lba = get_unaligned_be32(&common->cmnd[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. + */ + if (common->cmnd[1] & ~0x18) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (!curlun->nofua && (common->cmnd[1] & 0x08)) { /* FUA */ + spin_lock(&curlun->filp->f_lock); + curlun->filp->f_flags |= O_SYNC; + spin_unlock(&curlun->filp->f_lock); + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* Carry out the file writes */ + get_some_more = 1; + file_offset = usb_offset = ((loff_t) lba) << curlun->blkbits; + amount_left_to_req = common->data_size_from_cmnd; + amount_left_to_write = common->data_size_from_cmnd; + + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* + * Figure out how much we want to get: + * Try to get the remaining amount, + * but not more than the buffer size. + */ + amount = min(amount_left_to_req, FSG_BUFLEN); + + /* Beyond the end of the backing file? */ + if (usb_offset >= curlun->file_length) { + get_some_more = 0; + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = + usb_offset >> curlun->blkbits; + curlun->info_valid = 1; + continue; + } + + /* Get the next buffer */ + usb_offset += amount; + common->usb_amount_left -= amount; + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* + * Except at the end of the transfer, amount will be + * equal to the buffer size, which is divisible by + * the bulk-out maxpacket size. + */ + set_bulk_out_req_length(common, bh, amount); + if (!start_out_transfer(common, bh)) + /* Dunno what to do if common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = common->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; /* We stopped early */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + common->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->sense_data_info = + file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + + amount = bh->outreq->actual; + if (curlun->file_length - file_offset < amount) { + LERROR(curlun, + "write %u @ %llu beyond end %llu\n", + amount, (unsigned long long)file_offset, + (unsigned long long)curlun->file_length); + amount = curlun->file_length - file_offset; + } + + /* Don't accept excess data. The spec doesn't say + * what to do in this case. We'll ignore the error. + */ + amount = min(amount, bh->bulk_out_intended_length); + + /* Don't write a partial block */ + amount = round_down(amount, curlun->blksize); + if (amount == 0) + goto empty_write; + + /* Perform the write */ + file_offset_tmp = file_offset; + nwritten = vfs_write(curlun->filp, + (char __user *)bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, + (unsigned long long)file_offset, (int)nwritten); + if (signal_pending(current)) + return -EINTR; /* Interrupted! */ + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %d\n", + (int)nwritten); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int)nwritten, amount); + nwritten = round_down(nwritten, curlun->blksize); + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + common->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { + curlun->sense_data = SS_WRITE_ERROR; + curlun->sense_data_info = + file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + + empty_write: + /* Did the host decide to stop early? */ + if (bh->outreq->actual < bh->bulk_out_intended_length) { + common->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(common); + if (rc) + return rc; + } + + return -EIO; /* No default reply */ +} + + +/*-------------------------------------------------------------------------*/ + +static int do_synchronize_cache(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + int rc; + + /* We ignore the requested LBA and write out all file's + * dirty data buffers. */ + rc = fsg_lun_fsync_sub(curlun); + if (rc) + curlun->sense_data = SS_WRITE_ERROR; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static void invalidate_sub(struct fsg_lun *curlun) +{ + struct file *filp = curlun->filp; + struct inode *inode = file_inode(filp); + unsigned long rc; + + rc = invalidate_mapping_pages(inode->i_mapping, 0, -1); + VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc); +} + +static int do_verify(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + u32 verification_length; + struct fsg_buffhd *bh = common->next_buffhd_to_fill; + loff_t file_offset, file_offset_tmp; + u32 amount_left; + unsigned int amount; + ssize_t nread; + + /* + * Get the starting Logical Block Address and check that it's + * not too big. + */ + lba = get_unaligned_be32(&common->cmnd[2]); + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) but we don't implement it. + */ + if (common->cmnd[1] & ~0x10) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + verification_length = get_unaligned_be16(&common->cmnd[7]); + if (unlikely(verification_length == 0)) + return -EIO; /* No default reply */ + + /* Prepare to carry out the file verify */ + amount_left = verification_length << curlun->blkbits; + file_offset = ((loff_t) lba) << curlun->blkbits; + + /* Write out all the dirty buffers before invalidating them */ + fsg_lun_fsync_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + invalidate_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + /* Just try to read the requested blocks */ + while (amount_left > 0) { + /* + * Figure out how much we need to read: + * Try to read the remaining amount, but not more than + * the buffer size. + * And don't try to read past the end of the file. + */ + amount = min(amount_left, FSG_BUFLEN); + amount = min((loff_t)amount, + curlun->file_length - file_offset); + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = + file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file verify: %d\n", (int)nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file verify: %d/%u\n", + (int)nread, amount); + nread = round_down(nread, curlun->blksize); + } + if (nread == 0) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = + file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + file_offset += nread; + amount_left -= nread; + } + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u8 *buf = (u8 *) bh->buf; + + if (!curlun) { /* Unsupported LUNs are okay */ + common->bad_lun_okay = 1; + memset(buf, 0, 36); + buf[0] = 0x7f; /* Unsupported, no device-type */ + buf[4] = 31; /* Additional length */ + return 36; + } + + buf[0] = curlun->cdrom ? TYPE_ROM : TYPE_DISK; + buf[1] = curlun->removable ? 0x80 : 0; + buf[2] = 2; /* ANSI SCSI level 2 */ + buf[3] = 2; /* SCSI-2 INQUIRY data format */ + buf[4] = 31; /* Additional length */ + buf[5] = 0; /* No special options */ + buf[6] = 0; + buf[7] = 0; + memcpy(buf + 8, common->inquiry_string, sizeof common->inquiry_string); + return 36; +} + +static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u8 *buf = (u8 *) bh->buf; + u32 sd, sdinfo; + int valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (curlun && curlun->unit_attention_data != SS_NO_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!curlun) { /* Unsupported LUNs are okay */ + common->bad_lun_okay = 1; + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + sdinfo = 0; + valid = 0; + } else { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + valid = curlun->info_valid << 7; + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; /* Valid, current error */ + buf[2] = SK(sd); + put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */ + buf[7] = 18 - 8; /* Additional sense length */ + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + return 18; +} + +static int do_read_capacity(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba = get_unaligned_be32(&common->cmnd[2]); + int pmi = common->cmnd[8]; + u8 *buf = (u8 *)bh->buf; + + /* Check the PMI and LBA fields */ + if (pmi > 1 || (pmi == 0 && lba != 0)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + put_unaligned_be32(curlun->num_sectors - 1, &buf[0]); + /* Max logical block */ + put_unaligned_be32(curlun->blksize, &buf[4]);/* Block length */ + return 8; +} + +static int do_read_header(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + int msf = common->cmnd[1] & 0x02; + u32 lba = get_unaligned_be32(&common->cmnd[2]); + u8 *buf = (u8 *)bh->buf; + + if (common->cmnd[1] & ~0x02) { /* Mask away MSF */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + memset(buf, 0, 8); + buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */ + store_cdrom_address(&buf[4], msf, lba); + return 8; +} + +static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + int msf = common->cmnd[1] & 0x02; + int start_track = common->cmnd[6]; + u8 *buf = (u8 *)bh->buf; +#ifdef CONFIG_USB_SPRD_DWC + // Seen in MMC5RC01 6.32 READ TOC/PMA/ATIP Command + u8 toc_response_data1[] = { + 0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + u8 toc_response_data2[] = { + 0x00, 0x2e, 0x01, 0x01, 0x01, 0x14, 0x00, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x14, 0x00, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x01, 0x14, 0x00, 0xa2, 0x00, 0x00, + 0x00, 0x00, 0x09, 0x0c, 0x94, 0x14, 0xf2, 0x00, + 0xa8, 0x14, 0xf2, 0x00, 0xbc, 0x14, 0xf2, 0x00 + }; + u8 toc_response_data3[] = { + 0x00, 0x2e, 0x01, 0x01, 0x01, 0x14, 0x00, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x14, 0x00, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x01, 0x14, 0x00, 0xa2, 0x00, 0x00, + 0x00, 0x00, 0x09, 0x0c, 0x94, 0x14, 0xf2, 0x00, + 0xa8, 0x14, 0xf2, 0x00, 0xbc, 0x14, 0xf2, 0x00, + 0xd0, 0x14, 0xf2, 0x00, 0xe4, 0x14, 0xf2, 0x00, + 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, + 0x12, 0x00, 0x34, 0x35, 0x33, 0x32, 0x33, 0x32, + 0x33, 0x38, 0x00, 0x00, 0xf0, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x0a + }; + int rc = 0; + u8* data; + if(common->data_size_from_cmnd == 0xc){ + rc = sizeof toc_response_data1; + data = toc_response_data1; + }else if(msf == 0x1){ + rc = sizeof toc_response_data2; + data = toc_response_data2; + }else{ + rc = sizeof toc_response_data3; + data = toc_response_data3; + } + rc = rc < common->data_size_from_cmnd ? rc : common->data_size_from_cmnd; + memcpy(buf, data, rc); + return rc; + +#else + if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ + start_track > 1) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + memset(buf, 0, 20); + buf[1] = (20-2); /* TOC data length */ + buf[2] = 1; /* First track number */ + buf[3] = 1; /* Last track number */ + buf[5] = 0x16; /* Data track, copying allowed */ + buf[6] = 0x01; /* Only track is number 1 */ + store_cdrom_address(&buf[8], msf, 0); + + buf[13] = 0x16; /* Lead-out track is data */ + buf[14] = 0xAA; /* Lead-out track number */ + store_cdrom_address(&buf[16], msf, curlun->num_sectors); + return 20; + +#endif +} + +static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + int mscmnd = common->cmnd[0]; + u8 *buf = (u8 *) bh->buf; + u8 *buf0 = buf; + int pc, page_code; + int changeable_values, all_pages; + int valid_page = 0; + int len, limit; + + if ((common->cmnd[1] & ~0x08) != 0) { /* Mask away DBD */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + pc = common->cmnd[2] >> 6; + page_code = common->cmnd[2] & 0x3f; + if (pc == 3) { + curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return -EINVAL; + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* + * Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. + */ + memset(buf, 0, 8); + if (mscmnd == MODE_SENSE) { + buf[2] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 4; + limit = 255; + } else { /* MODE_SENSE_10 */ + buf[3] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 8; + limit = 65535; /* Should really be FSG_BUFLEN */ + } + + /* No block descriptors */ + + /* + * The mode pages, in numerical order. The only page we support + * is the Caching page. + */ + if (page_code == 0x08 || all_pages) { + valid_page = 1; + buf[0] = 0x08; /* Page code */ + buf[1] = 10; /* Page length */ + memset(buf+2, 0, 10); /* None of the fields are changeable */ + + if (!changeable_values) { + buf[2] = 0x04; /* Write cache enable, */ + /* Read cache not disabled */ + /* No cache retention priorities */ + put_unaligned_be16(0xffff, &buf[4]); + /* Don't disable prefetch */ + /* Minimum prefetch = 0 */ + put_unaligned_be16(0xffff, &buf[8]); + /* Maximum prefetch */ + put_unaligned_be16(0xffff, &buf[10]); + /* Maximum prefetch ceiling */ + } + buf += 12; + } + + /* + * Check that a valid page was requested and the mode data length + * isn't too long. + */ + len = buf - buf0; + if (!valid_page || len > limit) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + /* Store the mode data length */ + if (mscmnd == MODE_SENSE) + buf0[0] = len - 1; + else + put_unaligned_be16(len - 2, buf0); + return len; +} + +static int do_start_stop(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + int loej, start; + + if (!curlun) { + return -EINVAL; + } else if (!curlun->removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } else if ((common->cmnd[1] & ~0x01) != 0 || /* Mask away Immed */ + (common->cmnd[4] & ~0x03) != 0) { /* Mask LoEj, Start */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + loej = common->cmnd[4] & 0x02; + start = common->cmnd[4] & 0x01; + + /* + * Our emulation doesn't support mounting; the medium is + * available for use as soon as it is loaded. + */ + if (start) { + if (!fsg_lun_is_open(curlun)) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + return 0; + } + + /* Are we allowed to unload the media? */ + if (curlun->prevent_medium_removal) { + LDBG(curlun, "unload attempt prevented\n"); + curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED; + return -EINVAL; + } + + if (!loej) + return 0; + + up_read(&common->filesem); + down_write(&common->filesem); + fsg_lun_close(curlun); + up_write(&common->filesem); + down_read(&common->filesem); + + return 0; +} + +static int do_prevent_allow(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + int prevent; + + if (!common->curlun) { + return -EINVAL; + } else if (!common->curlun->removable) { + common->curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + prevent = common->cmnd[4] & 0x01; + if ((common->cmnd[4] & ~0x01) != 0) { /* Mask away Prevent */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (!curlun->nofua && curlun->prevent_medium_removal && !prevent) + fsg_lun_fsync_sub(curlun); + else + schedule_work(&common->fsync_work); + + curlun->prevent_medium_removal = prevent; + return 0; +} + +static void do_fsync(struct work_struct *work) +{ + struct fsg_common *common = + container_of(work, struct fsg_common, fsync_work); + + struct file *filp = common->curlun->filp; + static int syncing = 0; + + if (common->curlun->ro || !filp) + return; + + if(!syncing) { + syncing = 1; + printk("ums sync 1\n"); + vfs_fsync(filp, 1); + printk("ums sync 0\n"); + syncing = 0; + } +} + +static int do_read_format_capacities(struct fsg_common *common, + struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u8 *buf = (u8 *) bh->buf; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; /* Only the Current/Maximum Capacity Descriptor */ + buf += 4; + + put_unaligned_be32(curlun->num_sectors, &buf[0]); + /* Number of blocks */ + put_unaligned_be32(curlun->blksize, &buf[4]);/* Block length */ + buf[4] = 0x02; /* Current capacity */ + return 12; +} + +static int do_mode_select(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + + /* We don't support MODE SELECT */ + if (curlun) + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; +} + + +/*-------------------------------------------------------------------------*/ + +static int halt_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + rc = fsg_set_halt(fsg, fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint halt\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_halt -> %d\n", rc); + rc = 0; + break; + } + + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ep_set_halt(fsg->bulk_in); + } + return rc; +} + +static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + DBG(fsg, "bulk-in set wedge\n"); + rc = usb_ep_set_wedge(fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint wedge\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc); + rc = 0; + break; + } + + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ep_set_wedge(fsg->bulk_in); + } + return rc; +} + +static int throw_away_data(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + u32 amount; + int rc; + + for (bh = common->next_buffhd_to_drain; + bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0; + bh = common->next_buffhd_to_drain) { + + /* Throw away the data in a filled buffer */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + bh->state = BUF_STATE_EMPTY; + common->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual < bh->bulk_out_intended_length || + bh->outreq->status != 0) { + raise_exception(common, + FSG_STATE_ABORT_BULK_OUT); + return -EINTR; + } + continue; + } + + /* Try to submit another request if we need one */ + bh = common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY + && common->usb_amount_left > 0) { + amount = min(common->usb_amount_left, FSG_BUFLEN); + + /* + * Except at the end of the transfer, amount will be + * equal to the buffer size, which is divisible by + * the bulk-out maxpacket size. + */ + set_bulk_out_req_length(common, bh, amount); + if (!start_out_transfer(common, bh)) + /* Dunno what to do if common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + common->usb_amount_left -= amount; + continue; + } + + /* Otherwise wait for something to happen */ + rc = sleep_thread(common); + if (rc) + return rc; + } + return 0; +} + +static int finish_reply(struct fsg_common *common) +{ + struct fsg_buffhd *bh = common->next_buffhd_to_fill; + int rc = 0; + + switch (common->data_dir) { + case DATA_DIR_NONE: + break; /* Nothing to send */ + + /* + * If we don't know whether the host wants to read or write, + * this must be CB or CBI with an unknown command. We mustn't + * try to send or receive any data. So stall both bulk pipes + * if we can and wait for a reset. + */ + case DATA_DIR_UNKNOWN: + if (!common->can_stall) { + /* Nothing */ + } else if (fsg_is_set(common)) { + fsg_set_halt(common->fsg, common->fsg->bulk_out); + rc = halt_bulk_in_endpoint(common->fsg); + } else { + /* Don't know what to do if common->fsg is NULL */ + rc = -EIO; + } + break; + + /* All but the last buffer of data must have already been sent */ + case DATA_DIR_TO_HOST: + if (common->data_size == 0) { + /* Nothing to send */ + + /* Don't know what to do if common->fsg is NULL */ + } else if (!fsg_is_set(common)) { + rc = -EIO; + + /* If there's no residue, simply send the last buffer */ + } else if (common->residue == 0) { + bh->inreq->zero = 0; + if (!start_in_transfer(common, bh)) + return -EIO; + common->next_buffhd_to_fill = bh->next; + + /* + * For Bulk-only, mark the end of the data with a short + * packet. If we are allowed to stall, halt the bulk-in + * endpoint. (Note: This violates the Bulk-Only Transport + * specification, which requires us to pad the data if we + * don't halt the endpoint. Presumably nobody will mind.) + */ + } else { + bh->inreq->zero = 1; +#ifdef CONFIG_USB_SPRD_DWC + if (common->curlun->sense_data && common->can_stall + && common->cmnd[0] != INQUIRY && common->cmnd[0] != REQUEST_SENSE){ + rc = halt_bulk_in_endpoint(common->fsg); + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + common->next_buffhd_to_fill = bh->next; + common->state = FSG_STATE_IDLE; + return 0; + } +#endif + if (!start_in_transfer(common, bh)) + rc = -EIO; + common->next_buffhd_to_fill = bh->next; + if (common->can_stall) + rc = halt_bulk_in_endpoint(common->fsg); + } + break; + + /* + * We have processed all we want from the data the host has sent. + * There may still be outstanding bulk-out requests. + */ + case DATA_DIR_FROM_HOST: + if (common->residue == 0) { + /* Nothing to receive */ + + /* Did the host stop sending unexpectedly early? */ + } else if (common->short_packet_received) { + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; + + /* + * We haven't processed all the incoming data. Even though + * we may be allowed to stall, doing so would cause a race. + * The controller may already have ACK'ed all the remaining + * bulk-out packets, in which case the host wouldn't see a + * STALL. Not realizing the endpoint was halted, it wouldn't + * clear the halt -- leading to problems later on. + */ +#if 0 + } else if (common->can_stall) { + if (fsg_is_set(common)) + fsg_set_halt(common->fsg, + common->fsg->bulk_out); + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; +#endif + + /* + * We can't stall. Read in the excess data and throw it + * all away. + */ + } else { + rc = throw_away_data(common); + } + break; + } + return rc; +} + +static int send_status(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + struct fsg_buffhd *bh; + struct bulk_cs_wrap *csw; + int rc; + u8 status = US_BULK_STAT_OK; + u32 sd, sdinfo = 0; + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + if (curlun) { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + } else if (common->bad_lun_okay) + sd = SS_NO_SENSE; + else + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + + if (common->phase_error) { + DBG(common, "sending phase-error status\n"); + status = US_BULK_STAT_PHASE; + sd = SS_INVALID_COMMAND; + } else if (sd != SS_NO_SENSE) { + DBG(common, "sending command-failure status\n"); + status = US_BULK_STAT_FAIL; + VDBG(common, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" + " info x%x\n", + SK(sd), ASC(sd), ASCQ(sd), sdinfo); + } + + /* Store and send the Bulk-only CSW */ + csw = (void *)bh->buf; + + csw->Signature = cpu_to_le32(US_BULK_CS_SIGN); + csw->Tag = common->tag; + csw->Residue = cpu_to_le32(common->residue); + csw->Status = status; + + bh->inreq->length = US_BULK_CS_WRAP_LEN; + bh->inreq->zero = 0; + if (!start_in_transfer(common, bh)) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + + common->next_buffhd_to_fill = bh->next; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* + * Check whether the command is properly formed and whether its data size + * and direction agree with the values we already have. + */ +static int check_command(struct fsg_common *common, int cmnd_size, + enum data_direction data_dir, unsigned int mask, + int needs_medium, const char *name) +{ + int i; + unsigned int lun = common->cmnd[1] >> 5; + static const char dirletter[4] = {'u', 'o', 'i', 'n'}; + char hdlen[20]; + struct fsg_lun *curlun; + + hdlen[0] = 0; + if (common->data_dir != DATA_DIR_UNKNOWN) + sprintf(hdlen, ", H%c=%u", dirletter[(int) common->data_dir], + common->data_size); + VDBG(common, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n", + name, cmnd_size, dirletter[(int) data_dir], + common->data_size_from_cmnd, common->cmnd_size, hdlen); + + /* + * We can't reply at all until we know the correct data direction + * and size. + */ + if (common->data_size_from_cmnd == 0) + data_dir = DATA_DIR_NONE; + if (common->data_size < common->data_size_from_cmnd) { + /* + * Host data size < Device data size is a phase error. + * Carry out the command, but only transfer as much as + * we are allowed. + */ + common->data_size_from_cmnd = common->data_size; + common->phase_error = 1; + } + common->residue = common->data_size; + common->usb_amount_left = common->data_size; + + /* Conflicting data directions is a phase error */ + if (common->data_dir != data_dir && common->data_size_from_cmnd > 0) { + common->phase_error = 1; + return -EINVAL; + } + + /* Verify the length of the command itself */ + if (cmnd_size != common->cmnd_size) { + + /* + * Special case workaround: There are plenty of buggy SCSI + * implementations. Many have issues with cbw->Length + * field passing a wrong command size. For those cases we + * always try to work around the problem by using the length + * sent by the host side provided it is at least as large + * as the correct command length. + * Examples of such cases would be MS-Windows, which issues + * REQUEST SENSE with cbw->Length == 12 where it should + * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and + * REQUEST SENSE with cbw->Length == 10 where it should + * be 6 as well. + */ + if (cmnd_size <= common->cmnd_size) { + DBG(common, "%s is buggy! Expected length %d " + "but we got %d\n", name, + cmnd_size, common->cmnd_size); + cmnd_size = common->cmnd_size; + } else { + common->phase_error = 1; + return -EINVAL; + } + } + + /* Check that the LUN values are consistent */ + if (common->lun != lun) + DBG(common, "using LUN %u from CBW, not LUN %u from CDB\n", + common->lun, lun); + + /* Check the LUN */ + curlun = common->curlun; + if (curlun) { + if (common->cmnd[0] != REQUEST_SENSE) { + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + } else { + common->bad_lun_okay = 0; + + /* + * INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. + */ + if (common->cmnd[0] != INQUIRY && + common->cmnd[0] != REQUEST_SENSE) { + DBG(common, "unsupported LUN %u\n", common->lun); + return -EINVAL; + } + } + + /* + * If a unit attention condition exists, only INQUIRY and + * REQUEST SENSE commands are allowed; anything else must fail. + */ + if (curlun && curlun->unit_attention_data != SS_NO_SENSE && + common->cmnd[0] != INQUIRY && + common->cmnd[0] != REQUEST_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + return -EINVAL; + } + + /* Check that only command bytes listed in the mask are non-zero */ + common->cmnd[1] &= 0x1f; /* Mask away the LUN */ + for (i = 1; i < cmnd_size; ++i) { + if (common->cmnd[i] && !(mask & (1 << i))) { + if (curlun) + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + + /* If the medium isn't mounted and the command needs to access + * it, return an error. */ + if (curlun && !fsg_lun_is_open(curlun) && needs_medium) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + + return 0; +} + +/* wrapper of check_command for data size in blocks handling */ +static int check_command_size_in_blocks(struct fsg_common *common, + int cmnd_size, enum data_direction data_dir, + unsigned int mask, int needs_medium, const char *name) +{ + if (common->curlun) + common->data_size_from_cmnd <<= common->curlun->blkbits; + return check_command(common, cmnd_size, data_dir, + mask, needs_medium, name); +} + +static int do_scsi_command(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + int rc; + int reply = -EINVAL; + int i; + static char unknown[16]; + + dump_cdb(common); + + /* Wait for the next buffer to become available for data or status */ + bh = common->next_buffhd_to_fill; + common->next_buffhd_to_drain = bh; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + common->phase_error = 0; + common->short_packet_received = 0; + + down_read(&common->filesem); /* We're using the backing file */ + switch (common->cmnd[0]) { + + case INQUIRY: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "INQUIRY"); + if (reply == 0) + reply = do_inquiry(common, bh); + break; + + case MODE_SELECT: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_FROM_HOST, + (1<<1) | (1<<4), 0, + "MODE SELECT(6)"); + if (reply == 0) + reply = do_mode_select(common, bh); + break; + + case MODE_SELECT_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_FROM_HOST, + (1<<1) | (3<<7), 0, + "MODE SELECT(10)"); + if (reply == 0) + reply = do_mode_select(common, bh); + break; + + case MODE_SENSE: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (1<<4), 0, + "MODE SENSE(6)"); + if (reply == 0) + reply = do_mode_sense(common, bh); + break; + + case MODE_SENSE_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (3<<7), 0, + "MODE SENSE(10)"); + if (reply == 0) + reply = do_mode_sense(common, bh); + break; + + case ALLOW_MEDIUM_REMOVAL: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + (1<<4), 0, + "PREVENT-ALLOW MEDIUM REMOVAL"); + if (reply == 0) + reply = do_prevent_allow(common); + break; + + case READ_6: + i = common->cmnd[4]; + common->data_size_from_cmnd = (i == 0) ? 256 : i; + reply = check_command_size_in_blocks(common, 6, + DATA_DIR_TO_HOST, + (7<<1) | (1<<4), 1, + "READ(6)"); + if (reply == 0) + reply = do_read(common); + break; + + case READ_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command_size_in_blocks(common, 10, + DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "READ(10)"); + if (reply == 0) + reply = do_read(common); + break; + + case READ_12: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[6]); + reply = check_command_size_in_blocks(common, 12, + DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "READ(12)"); + if (reply == 0) + reply = do_read(common); + break; + + case READ_CAPACITY: + common->data_size_from_cmnd = 8; + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (0xf<<2) | (1<<8), 1, + "READ CAPACITY"); + if (reply == 0) + reply = do_read_capacity(common, bh); + break; + + case READ_HEADER: + if (!common->curlun || !common->curlun->cdrom) + goto unknown_cmnd; + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (3<<7) | (0x1f<<1), 1, + "READ HEADER"); + if (reply == 0) + reply = do_read_header(common, bh); + break; + + case READ_TOC: + if (!common->curlun || !common->curlun->cdrom) + goto unknown_cmnd; + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (0xf<<6) | (3<<1), 1, + "READ TOC"); + if (reply == 0) + reply = do_read_toc(common, bh); + break; + + case READ_FORMAT_CAPACITIES: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (3<<7), 1, + "READ FORMAT CAPACITIES"); + if (reply == 0) + reply = do_read_format_capacities(common, bh); + break; + + case REQUEST_SENSE: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "REQUEST SENSE"); + if (reply == 0) + reply = do_request_sense(common, bh); + break; + + case START_STOP: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + (1<<1) | (1<<4), 0, + "START-STOP UNIT"); + if (reply == 0) + reply = do_start_stop(common); + break; + + case SYNCHRONIZE_CACHE: + common->data_size_from_cmnd = 0; + reply = check_command(common, 10, DATA_DIR_NONE, + (0xf<<2) | (3<<7), 1, + "SYNCHRONIZE CACHE"); + if (reply == 0) + reply = do_synchronize_cache(common); + break; + + case TEST_UNIT_READY: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + 0, 1, + "TEST UNIT READY"); + break; + + /* + * Although optional, this command is used by MS-Windows. We + * support a minimal version: BytChk must be 0. + */ + case VERIFY: + common->data_size_from_cmnd = 0; + reply = check_command(common, 10, DATA_DIR_NONE, + (1<<1) | (0xf<<2) | (3<<7), 1, + "VERIFY"); + if (reply == 0) + reply = do_verify(common); + break; + + case WRITE_6: + i = common->cmnd[4]; + common->data_size_from_cmnd = (i == 0) ? 256 : i; + reply = check_command_size_in_blocks(common, 6, + DATA_DIR_FROM_HOST, + (7<<1) | (1<<4), 1, + "WRITE(6)"); + if (reply == 0) + reply = do_write(common); + break; + + case WRITE_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command_size_in_blocks(common, 10, + DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "WRITE(10)"); + if (reply == 0) + reply = do_write(common); + break; + + case WRITE_12: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[6]); + reply = check_command_size_in_blocks(common, 12, + DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "WRITE(12)"); + if (reply == 0) + reply = do_write(common); + break; + + /* + * Some mandatory commands that we recognize but don't implement. + * They don't mean much in this setting. It's left as an exercise + * for anyone interested to implement RESERVE and RELEASE in terms + * of Posix locks. + */ + case FORMAT_UNIT: + case RELEASE: + case RESERVE: + case SEND_DIAGNOSTIC: + /* Fall through */ + + default: +unknown_cmnd: + common->data_size_from_cmnd = 0; + sprintf(unknown, "Unknown x%02x", common->cmnd[0]); + reply = check_command(common, common->cmnd_size, + DATA_DIR_UNKNOWN, ~0, 0, unknown); + if (reply == 0) { + common->curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } + break; + } + up_read(&common->filesem); + + if (reply == -EINTR || signal_pending(current)) + return -EINTR; + + /* Set up the single reply buffer for finish_reply() */ + if (reply == -EINVAL) + reply = 0; /* Error reply length */ + if (reply >= 0 && common->data_dir == DATA_DIR_TO_HOST) { + reply = min((u32)reply, common->data_size_from_cmnd); + bh->inreq->length = reply; + bh->state = BUF_STATE_FULL; + common->residue -= reply; + } /* Otherwise it's already set */ + + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = bh->outreq; + struct bulk_cb_wrap *cbw = req->buf; + struct fsg_common *common = fsg->common; + + /* Was this a real packet? Should it be ignored? */ + if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags)) + return -EINVAL; + + /* Is the CBW valid? */ + if (req->actual != US_BULK_CB_WRAP_LEN || + cbw->Signature != cpu_to_le32( + US_BULK_CB_SIGN)) { + DBG(fsg, "invalid CBW: len %u sig 0x%x\n", + req->actual, + le32_to_cpu(cbw->Signature)); + + /* + * The Bulk-only spec says we MUST stall the IN endpoint + * (6.6.1), so it's unavoidable. It also says we must + * retain this state until the next reset, but there's + * no way to tell the controller driver it should ignore + * Clear-Feature(HALT) requests. + * + * We aren't required to halt the OUT endpoint; instead + * we can simply accept and discard any data received + * until the next reset. + */ + wedge_bulk_in_endpoint(fsg); + set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + return -EINVAL; + } + + /* Is the CBW meaningful? */ + if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~US_BULK_FLAG_IN || + cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) { + DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, " + "cmdlen %u\n", + cbw->Lun, cbw->Flags, cbw->Length); + + /* + * We can do anything we want here, so let's stall the + * bulk pipes if we are allowed to. + */ + if (common->can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + halt_bulk_in_endpoint(fsg); + } + return -EINVAL; + } + + /* Save the command for later */ + common->cmnd_size = cbw->Length; + memcpy(common->cmnd, cbw->CDB, common->cmnd_size); + if (cbw->Flags & US_BULK_FLAG_IN) + common->data_dir = DATA_DIR_TO_HOST; + else + common->data_dir = DATA_DIR_FROM_HOST; + common->data_size = le32_to_cpu(cbw->DataTransferLength); + if (common->data_size == 0) + common->data_dir = DATA_DIR_NONE; + common->lun = cbw->Lun; + if (common->lun < common->nluns) + common->curlun = &common->luns[common->lun]; + else + common->curlun = NULL; + common->tag = cbw->Tag; + return 0; +} + +static int get_next_command(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + int rc = 0; + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* Queue a request to read a Bulk-only CBW */ + set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN); + if (!start_out_transfer(common, bh)) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + + /* + * We will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. + */ + + /* Wait for the CBW to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(common); + if (rc) + return rc; + } + smp_rmb(); + rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO; + bh->state = BUF_STATE_EMPTY; + + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int alloc_request(struct fsg_common *common, struct usb_ep *ep, + struct usb_request **preq) +{ + *preq = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (*preq) + return 0; + ERROR(common, "can't allocate request for %s\n", ep->name); + return -ENOMEM; +} + +/* Reset interface setting and re-init endpoint state (toggle etc). */ +static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) +{ + struct fsg_dev *fsg; + int i, rc = 0; + + if (common->running) + DBG(common, "reset interface\n"); + +reset: + /* Deallocate the requests */ + if (common->fsg) { + fsg = common->fsg; + + for (i = 0; i < fsg_num_buffers; ++i) { + struct fsg_buffhd *bh = &common->buffhds[i]; + + if (bh->inreq) { + usb_ep_free_request(fsg->bulk_in, bh->inreq); + bh->inreq = NULL; + } + if (bh->outreq) { + usb_ep_free_request(fsg->bulk_out, bh->outreq); + bh->outreq = NULL; + } + } + + /* Disable the endpoints */ + if (fsg->bulk_in_enabled) { + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + } + if (fsg->bulk_out_enabled) { + usb_ep_disable(fsg->bulk_out); + fsg->bulk_out_enabled = 0; + } + + common->fsg = NULL; + wake_up(&common->fsg_wait); + } + + common->running = 0; + if (!new_fsg || rc) + return rc; + + if(new_fsg->common != common){ + printk("%s new_fsg->common = 0x%x common = 0x%x\n",__func__,new_fsg->common,common); + return rc; + } + common->fsg = new_fsg; + fsg = common->fsg; + + /* Enable the endpoints */ + rc = config_ep_by_speed(common->gadget, &(fsg->function), fsg->bulk_in); + if (rc) + goto reset; + rc = usb_ep_enable(fsg->bulk_in); + if (rc) + goto reset; + fsg->bulk_in->driver_data = common; + fsg->bulk_in_enabled = 1; + + rc = config_ep_by_speed(common->gadget, &(fsg->function), + fsg->bulk_out); + if (rc) + goto reset; + rc = usb_ep_enable(fsg->bulk_out); + if (rc) + goto reset; + fsg->bulk_out->driver_data = common; + fsg->bulk_out_enabled = 1; + common->bulk_out_maxpacket = usb_endpoint_maxp(fsg->bulk_out->desc); + clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + + /* Allocate the requests */ + for (i = 0; i < fsg_num_buffers; ++i) { + struct fsg_buffhd *bh = &common->buffhds[i]; + + rc = alloc_request(common, fsg->bulk_in, &bh->inreq); + if (rc) + goto reset; + rc = alloc_request(common, fsg->bulk_out, &bh->outreq); + if (rc) + goto reset; + bh->inreq->buf = bh->outreq->buf = bh->buf; + bh->inreq->context = bh->outreq->context = bh; + bh->inreq->complete = bulk_in_complete; + bh->outreq->complete = bulk_out_complete; + } + + common->running = 1; + for (i = 0; i < common->nluns; ++i) + common->luns[i].unit_attention_data = SS_RESET_OCCURRED; + return rc; +} + + +/****************************** ALT CONFIGS ******************************/ + +static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct fsg_dev *fsg = fsg_from_func(f); + + fsg->common->new_fsg = fsg; + printk("%s f=%p,fsg=%p\n",__func__,f,fsg); + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); + return USB_GADGET_DELAYED_STATUS; +} + +static void fsg_disable(struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + printk("%s f=%p\n",__func__,f); + fsg->common->new_fsg = NULL; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); +} + + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct fsg_common *common) +{ + siginfo_t info; + int i; + struct fsg_buffhd *bh; + enum fsg_state old_state; + struct fsg_lun *curlun; + unsigned int exception_req_tag; + + /* + * Clear the existing signals. Anything but SIGUSR1 is converted + * into a high-priority EXIT exception. + */ + for (;;) { + int sig = + dequeue_signal_lock(current, ¤t->blocked, &info); + if (!sig) + break; + if (sig != SIGUSR1) { + if (common->state < FSG_STATE_EXIT) + DBG(common, "Main thread exiting on signal\n"); + raise_exception(common, FSG_STATE_EXIT); + } + } + + /* Cancel all the pending transfers */ + if (likely(common->fsg)) { + for (i = 0; i < fsg_num_buffers; ++i) { + bh = &common->buffhds[i]; + if (bh->inreq_busy) + usb_ep_dequeue(common->fsg->bulk_in, bh->inreq); + if (bh->outreq_busy) + usb_ep_dequeue(common->fsg->bulk_out, + bh->outreq); + } + + /* Wait until everything is idle */ + for (;;) { + int num_active = 0; + for (i = 0; i < fsg_num_buffers; ++i) { + bh = &common->buffhds[i]; + num_active += bh->inreq_busy + bh->outreq_busy; + } + if (num_active == 0) + break; + if (sleep_thread(common)) + return; + } + + /* Clear out the controller's fifos */ + if (common->fsg->bulk_in_enabled) + usb_ep_fifo_flush(common->fsg->bulk_in); + if (common->fsg->bulk_out_enabled) + usb_ep_fifo_flush(common->fsg->bulk_out); + } + + /* + * Reset the I/O buffer states and pointers, the SCSI + * state, and the exception. Then invoke the handler. + */ + spin_lock_irq(&common->lock); + + for (i = 0; i < fsg_num_buffers; ++i) { + bh = &common->buffhds[i]; + bh->state = BUF_STATE_EMPTY; + } + common->next_buffhd_to_fill = &common->buffhds[0]; + common->next_buffhd_to_drain = &common->buffhds[0]; + exception_req_tag = common->exception_req_tag; + old_state = common->state; + + if (old_state == FSG_STATE_ABORT_BULK_OUT) + common->state = FSG_STATE_STATUS_PHASE; + else { + for (i = 0; i < common->nluns; ++i) { + curlun = &common->luns[i]; + curlun->prevent_medium_removal = 0; + curlun->sense_data = SS_NO_SENSE; + curlun->unit_attention_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + common->state = FSG_STATE_IDLE; + } + spin_unlock_irq(&common->lock); + + /* Carry out any extra actions required for the exception */ + switch (old_state) { + case FSG_STATE_ABORT_BULK_OUT: + send_status(common); + spin_lock_irq(&common->lock); + if (common->state == FSG_STATE_STATUS_PHASE) + common->state = FSG_STATE_IDLE; + spin_unlock_irq(&common->lock); + break; + + case FSG_STATE_RESET: + /* + * In case we were forced against our will to halt a + * bulk endpoint, clear the halt now. (The SuperH UDC + * requires this.) + */ + if (!fsg_is_set(common)) + break; + if (test_and_clear_bit(IGNORE_BULK_OUT, + &common->fsg->atomic_bitflags)) + usb_ep_clear_halt(common->fsg->bulk_in); + + if (common->ep0_req_tag == exception_req_tag) + ep0_queue(common); /* Complete the status stage */ + + /* + * Technically this should go here, but it would only be + * a waste of time. Ditto for the INTERFACE_CHANGE and + * CONFIG_CHANGE cases. + */ + /* for (i = 0; i < common->nluns; ++i) */ + /* common->luns[i].unit_attention_data = */ + /* SS_RESET_OCCURRED; */ + break; + + case FSG_STATE_CONFIG_CHANGE: + do_set_interface(common, common->new_fsg); + if (common->new_fsg) + usb_composite_setup_continue(common->cdev); + break; + + case FSG_STATE_EXIT: + case FSG_STATE_TERMINATED: + do_set_interface(common, NULL); /* Free resources */ + spin_lock_irq(&common->lock); + common->state = FSG_STATE_TERMINATED; /* Stop the thread */ + spin_unlock_irq(&common->lock); + break; + + case FSG_STATE_INTERFACE_CHANGE: + case FSG_STATE_DISCONNECT: + case FSG_STATE_COMMAND_PHASE: + case FSG_STATE_DATA_PHASE: + case FSG_STATE_STATUS_PHASE: + case FSG_STATE_IDLE: + break; + } +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_main_thread(void *common_) +{ + struct fsg_common *common = common_; + + /* + * Allow the thread to be killed by a signal, but set the signal mask + * to block everything but INT, TERM, KILL, and USR1. + */ + allow_signal(SIGINT); + allow_signal(SIGTERM); + allow_signal(SIGKILL); + allow_signal(SIGUSR1); + + /* Allow the thread to be frozen */ + set_freezable(); + + /* + * Arrange for userspace references to be interpreted as kernel + * pointers. That way we can pass a kernel pointer to a routine + * that expects a __user pointer and it will work okay. + */ + set_fs(get_ds()); + + /* The main loop */ + while (common->state != FSG_STATE_TERMINATED) { + if (exception_in_progress(common) || signal_pending(current)) { + handle_exception(common); + continue; + } + + if (!common->running) { + sleep_thread(common); + continue; + } + + if (get_next_command(common)) + continue; + + spin_lock_irq(&common->lock); + if (!exception_in_progress(common)) + common->state = FSG_STATE_DATA_PHASE; + spin_unlock_irq(&common->lock); + + if (do_scsi_command(common) || finish_reply(common)) + continue; + + spin_lock_irq(&common->lock); + if (!exception_in_progress(common)) + common->state = FSG_STATE_STATUS_PHASE; + spin_unlock_irq(&common->lock); + + if (send_status(common)) + continue; + + spin_lock_irq(&common->lock); + if (!exception_in_progress(common)) + common->state = FSG_STATE_IDLE; + spin_unlock_irq(&common->lock); + } + + spin_lock_irq(&common->lock); + common->thread_task = NULL; + spin_unlock_irq(&common->lock); + + if (!common->ops || !common->ops->thread_exits + || common->ops->thread_exits(common) < 0) { + struct fsg_lun *curlun = common->luns; + unsigned i = common->nluns; + + down_write(&common->filesem); + for (; i--; ++curlun) { + if (!fsg_lun_is_open(curlun)) + continue; + + fsg_lun_close(curlun); + curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; + } + up_write(&common->filesem); + } + + /* Let fsg_unbind() know the thread has exited */ + complete_and_exit(&common->thread_notifier, 0); +} + + +/*************************** DEVICE ATTRIBUTES ***************************/ + +static DEVICE_ATTR(ro, 0644, fsg_show_ro, fsg_store_ro); +static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, fsg_store_nofua); +static DEVICE_ATTR(file, 0644, fsg_show_file, fsg_store_file); +static DEVICE_ATTR(cdrom, 0644, fsg_show_cdrom, fsg_store_cdrom); + +static struct device_attribute dev_attr_ro_cdrom = + __ATTR(ro, 0444, fsg_show_ro, NULL); +static struct device_attribute dev_attr_file_nonremovable = + __ATTR(file, 0444, fsg_show_file, NULL); + + +/****************************** FSG COMMON ******************************/ + +static void fsg_common_release(struct kref *ref); + +static void fsg_lun_release(struct device *dev) +{ + /* Nothing needs to be done */ +} + +static inline void fsg_common_get(struct fsg_common *common) +{ + kref_get(&common->ref); +} + +static inline void fsg_common_put(struct fsg_common *common) +{ + kref_put(&common->ref, fsg_common_release); +} + +static struct fsg_common *fsg_common_init(struct fsg_common *common, + struct usb_composite_dev *cdev, + struct fsg_config *cfg) +{ + struct usb_gadget *gadget = cdev->gadget; + struct fsg_buffhd *bh; + struct fsg_lun *curlun; + struct fsg_lun_config *lcfg; + int nluns, i, rc; + char *pathbuf; + + rc = fsg_num_buffers_validate(); + if (rc != 0) + return ERR_PTR(rc); + + /* Find out how many LUNs there should be */ + nluns = cfg->nluns; + if (nluns < 1 || nluns > FSG_MAX_LUNS) { + dev_err(&gadget->dev, "invalid number of LUNs: %u\n", nluns); + return ERR_PTR(-EINVAL); + } + + /* Allocate? */ + if (!common) { + common = kzalloc(sizeof *common, GFP_KERNEL); + if (!common) + return ERR_PTR(-ENOMEM); + common->free_storage_on_release = 1; + } else { + memset(common, 0, sizeof *common); + common->free_storage_on_release = 0; + } + + common->buffhds = kcalloc(fsg_num_buffers, + sizeof *(common->buffhds), GFP_KERNEL); + if (!common->buffhds) { + if (common->free_storage_on_release) + kfree(common); + return ERR_PTR(-ENOMEM); + } + + common->ops = cfg->ops; + common->private_data = cfg->private_data; + + common->gadget = gadget; + common->ep0 = gadget->ep0; + common->ep0req = cdev->req; + common->cdev = cdev; +#ifdef CONFIG_USB_SPRD_DWC + common->can_stall = 1; +#endif + /* Maybe allocate device-global string IDs, and patch descriptors */ + if (fsg_strings[FSG_STRING_INTERFACE].id == 0) { + rc = usb_string_id(cdev); + if (unlikely(rc < 0)) + goto error_release; + fsg_strings[FSG_STRING_INTERFACE].id = rc; + fsg_intf_desc.iInterface = rc; + } + + /* + * Create the LUNs, open their backing files, and register the + * LUN devices in sysfs. + */ + curlun = kcalloc(nluns, sizeof(*curlun), GFP_KERNEL); + if (unlikely(!curlun)) { + rc = -ENOMEM; + goto error_release; + } + common->luns = curlun; + + init_rwsem(&common->filesem); + + for (i = 0, lcfg = cfg->luns; i < nluns; ++i, ++curlun, ++lcfg) { + curlun->cdrom = !!lcfg->cdrom; + curlun->ro = lcfg->cdrom || lcfg->ro; + curlun->initially_ro = curlun->ro; + curlun->removable = lcfg->removable; + curlun->nofua = lcfg->nofua; + curlun->dev.release = fsg_lun_release; + curlun->dev.parent = &gadget->dev; + /* curlun->dev.driver = &fsg_driver.driver; XXX */ + dev_set_drvdata(&curlun->dev, &common->filesem); + dev_set_name(&curlun->dev, "lun%d", i); + + rc = device_register(&curlun->dev); + if (rc) { + INFO(common, "failed to register LUN%d: %d\n", i, rc); + common->nluns = i; + put_device(&curlun->dev); + goto error_release; + } + + rc = device_create_file(&curlun->dev, + curlun->cdrom + ? &dev_attr_ro_cdrom + : &dev_attr_ro); + if (rc) + goto error_luns; + rc = device_create_file(&curlun->dev, + curlun->removable + ? &dev_attr_file + : &dev_attr_file_nonremovable); + if (rc) + goto error_luns; + rc = device_create_file(&curlun->dev, &dev_attr_nofua); + if (rc) + goto error_luns; + rc = device_create_file(&curlun->dev, &dev_attr_cdrom); + if (rc) + goto error_luns; + + if (lcfg->filename) { + rc = fsg_lun_open(curlun, lcfg->filename); + if (rc) + goto error_luns; + } else if (!curlun->removable) { + ERROR(common, "no file given for LUN%d\n", i); + rc = -EINVAL; + goto error_luns; + } + } + common->nluns = nluns; + common->board_support_luns = nluns; + + /* Data buffers cyclic list */ + bh = common->buffhds; + i = fsg_num_buffers; + goto buffhds_first_it; + do { + bh->next = bh + 1; + ++bh; +buffhds_first_it: + bh->buf = kmalloc(FSG_BUFLEN, GFP_KERNEL); + if (unlikely(!bh->buf)) { + rc = -ENOMEM; + goto error_release; + } + } while (--i); + bh->next = common->buffhds; + + /* Prepare inquiryString */ + i = get_default_bcdDevice(); + snprintf(common->inquiry_string, sizeof common->inquiry_string, + "%-8s%-16s%04x", cfg->vendor_name ?: "Linux", + /* Assume product name dependent on the first LUN */ + cfg->product_name ?: (common->luns->cdrom + ? "File-Stor Gadget" + : "File-CD Gadget"), + i); + + /* + * Some peripheral controllers are known not to be able to + * halt bulk endpoints correctly. If one of them is present, + * disable stalls. + */ + common->can_stall = cfg->can_stall && + !(gadget_is_at91(common->gadget)); + + spin_lock_init(&common->lock); + kref_init(&common->ref); + + /* Tell the thread to start working */ + common->thread_task = + kthread_create(fsg_main_thread, common, "file-storage"); + if (IS_ERR(common->thread_task)) { + rc = PTR_ERR(common->thread_task); + goto error_release; + } + init_completion(&common->thread_notifier); + init_waitqueue_head(&common->fsg_wait); + + INIT_WORK(&common->fsync_work, &do_fsync); + + /* Information */ + INFO(common, FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); + INFO(common, "Number of LUNs=%d\n", common->nluns); + + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + for (i = 0, nluns = common->nluns, curlun = common->luns; + i < nluns; + ++curlun, ++i) { + char *p = "(no medium)"; + if (fsg_lun_is_open(curlun)) { + p = "(error)"; + if (pathbuf) { + p = d_path(&curlun->filp->f_path, + pathbuf, PATH_MAX); + if (IS_ERR(p)) + p = "(error)"; + } + } + LINFO(curlun, "LUN: %s%s%sfile: %s\n", + curlun->removable ? "removable " : "", + curlun->ro ? "read only " : "", + curlun->cdrom ? "CD-ROM " : "", + p); + } + kfree(pathbuf); + + DBG(common, "I/O thread pid: %d\n", task_pid_nr(common->thread_task)); + + wake_up_process(common->thread_task); + + return common; + +error_luns: + common->nluns = i + 1; +error_release: + common->state = FSG_STATE_TERMINATED; /* The thread is dead */ + /* Call fsg_common_release() directly, ref might be not initialised. */ + fsg_common_release(&common->ref); + return ERR_PTR(rc); +} + +static void fsg_common_release(struct kref *ref) +{ + struct fsg_common *common = container_of(ref, struct fsg_common, ref); + + /* If the thread isn't already dead, tell it to exit now */ + if (common->state != FSG_STATE_TERMINATED) { + raise_exception(common, FSG_STATE_EXIT); + wait_for_completion(&common->thread_notifier); + } + + if (likely(common->luns)) { + struct fsg_lun *lun = common->luns; + unsigned i = common->nluns; + + /* In error recovery common->nluns may be zero. */ + for (; i; --i, ++lun) { + device_remove_file(&lun->dev, &dev_attr_nofua); + device_remove_file(&lun->dev, + lun->cdrom + ? &dev_attr_ro_cdrom + : &dev_attr_ro); + device_remove_file(&lun->dev, + lun->removable + ? &dev_attr_file + : &dev_attr_file_nonremovable); + device_remove_file(&lun->dev, &dev_attr_cdrom); + fsg_lun_close(lun); + device_unregister(&lun->dev); + } + + kfree(common->luns); + } + + { + struct fsg_buffhd *bh = common->buffhds; + unsigned i = fsg_num_buffers; + do { + kfree(bh->buf); + } while (++bh, --i); + } + + kfree(common->buffhds); + if (common->free_storage_on_release) + kfree(common); +} + + +/*-------------------------------------------------------------------------*/ + +static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct fsg_common *common = fsg->common; + + DBG(fsg, "unbind\n"); + if (fsg->common->fsg == fsg) { + fsg->common->new_fsg = NULL; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); + /* FIXME: make interruptible or killable somehow? */ + wait_event(common->fsg_wait, common->fsg != fsg); + } + + fsg_common_put(common); + usb_free_all_descriptors(&fsg->function); + kfree(fsg); +} + +static int fsg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_gadget *gadget = c->cdev->gadget; + int i; + struct usb_ep *ep; + unsigned max_burst; + int ret; + + fsg->gadget = gadget; + + /* New interface */ + i = usb_interface_id(c, f); + if (i < 0) + return i; + fsg_intf_desc.bInterfaceNumber = i; + fsg->interface_number = i; + + /* Find all the endpoints we will use */ + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg->common; /* claim the endpoint */ + fsg->bulk_in = ep; + + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg->common; /* claim the endpoint */ + fsg->bulk_out = ep; + + /* Assume endpoint addresses are the same for both speeds */ + fsg_hs_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_hs_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + + /* Calculate bMaxBurst, we know packet size is 1024 */ + max_burst = min_t(unsigned, FSG_BUFLEN / 1024, 15); + + fsg_ss_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_ss_bulk_in_comp_desc.bMaxBurst = max_burst; + + fsg_ss_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + fsg_ss_bulk_out_comp_desc.bMaxBurst = max_burst; + + ret = usb_assign_descriptors(f, fsg_fs_function, fsg_hs_function, + fsg_ss_function); + if (ret) + goto autoconf_fail; + + return 0; + +autoconf_fail: + ERROR(fsg, "unable to autoconfigure all endpoints\n"); + return -ENOTSUPP; +} + +/****************************** ADD FUNCTION ******************************/ + +static struct usb_gadget_strings *fsg_strings_array[] = { + &fsg_stringtab, + NULL, +}; + +static int fsg_bind_config(struct usb_composite_dev *cdev, + struct usb_configuration *c, + struct fsg_common *common) +{ + struct fsg_dev *fsg; + int rc; + + fsg = kzalloc(sizeof *fsg, GFP_KERNEL); + if (unlikely(!fsg)) + return -ENOMEM; + + fsg->function.name = FSG_DRIVER_DESC; + fsg->function.strings = fsg_strings_array; + fsg->function.bind = fsg_bind; + fsg->function.unbind = fsg_unbind; + fsg->function.setup = fsg_setup; + fsg->function.set_alt = fsg_set_alt; + fsg->function.disable = fsg_disable; + + fsg->common = common; + /* + * Our caller holds a reference to common structure so we + * don't have to be worry about it being freed until we return + * from this function. So instead of incrementing counter now + * and decrement in error recovery we increment it only when + * call to usb_add_function() was successful. + */ + + rc = usb_add_function(c, &fsg->function); + if (unlikely(rc)) + kfree(fsg); + else + fsg_common_get(fsg->common); + return rc; +} + + +/************************* Module parameters *************************/ + +struct fsg_module_parameters { + char *file[FSG_MAX_LUNS]; + bool ro[FSG_MAX_LUNS]; + bool removable[FSG_MAX_LUNS]; + bool cdrom[FSG_MAX_LUNS]; + bool nofua[FSG_MAX_LUNS]; + + unsigned int file_count, ro_count, removable_count, cdrom_count; + unsigned int nofua_count; + unsigned int luns; /* nluns */ + bool stall; /* can_stall */ +}; + +#define _FSG_MODULE_PARAM_ARRAY(prefix, params, name, type, desc) \ + module_param_array_named(prefix ## name, params.name, type, \ + &prefix ## params.name ## _count, \ + S_IRUGO); \ + MODULE_PARM_DESC(prefix ## name, desc) + +#define _FSG_MODULE_PARAM(prefix, params, name, type, desc) \ + module_param_named(prefix ## name, params.name, type, \ + S_IRUGO); \ + MODULE_PARM_DESC(prefix ## name, desc) + +#define FSG_MODULE_PARAMETERS(prefix, params) \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, file, charp, \ + "names of backing files or devices"); \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, ro, bool, \ + "true to force read-only"); \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, removable, bool, \ + "true to simulate removable media"); \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, cdrom, bool, \ + "true to simulate CD-ROM instead of disk"); \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, nofua, bool, \ + "true to ignore SCSI WRITE(10,12) FUA bit"); \ + _FSG_MODULE_PARAM(prefix, params, luns, uint, \ + "number of LUNs"); \ + _FSG_MODULE_PARAM(prefix, params, stall, bool, \ + "false to prevent bulk stalls") + +static void +fsg_config_from_params(struct fsg_config *cfg, + const struct fsg_module_parameters *params) +{ + struct fsg_lun_config *lun; + unsigned i; + + /* Configure LUNs */ + cfg->nluns = + min(params->luns ?: (params->file_count ?: 1u), + (unsigned)FSG_MAX_LUNS); + for (i = 0, lun = cfg->luns; i < cfg->nluns; ++i, ++lun) { + lun->ro = !!params->ro[i]; + lun->cdrom = !!params->cdrom[i]; + lun->removable = !!params->removable[i]; + lun->filename = + params->file_count > i && params->file[i][0] + ? params->file[i] + : 0; + } + + /* Let MSF use defaults */ + cfg->vendor_name = 0; + cfg->product_name = 0; + + cfg->ops = NULL; + cfg->private_data = NULL; + + /* Finalise */ + cfg->can_stall = params->stall; +} + +static inline struct fsg_common * +fsg_common_from_params(struct fsg_common *common, + struct usb_composite_dev *cdev, + const struct fsg_module_parameters *params) + __attribute__((unused)); +static inline struct fsg_common * +fsg_common_from_params(struct fsg_common *common, + struct usb_composite_dev *cdev, + const struct fsg_module_parameters *params) +{ + struct fsg_config cfg; + fsg_config_from_params(&cfg, params); + return fsg_common_init(common, cdev, &cfg); +} diff --git a/drivers/usb/gadget/f_midi.c b/drivers/usb/gadget/f_midi.c new file mode 100644 index 00000000..263e721c --- /dev/null +++ b/drivers/usb/gadget/f_midi.c @@ -0,0 +1,995 @@ +/* + * f_midi.c -- USB MIDI class function driver + * + * Copyright (C) 2006 Thumtronics Pty Ltd. + * Developed for Thumtronics by Grey Innovation + * Ben Williamson + * + * Rewritten for the composite framework + * Copyright (C) 2011 Daniel Mack + * + * Based on drivers/usb/gadget/f_audio.c, + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * and drivers/usb/gadget/midi.c, + * Copyright (C) 2006 Thumtronics Pty Ltd. + * Ben Williamson + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +MODULE_AUTHOR("Ben Williamson"); +MODULE_LICENSE("GPL v2"); + +static const char f_midi_shortname[] = "f_midi"; +static const char f_midi_longname[] = "MIDI Gadget"; + +/* + * We can only handle 16 cables on one single endpoint, as cable numbers are + * stored in 4-bit fields. And as the interface currently only holds one + * single endpoint, this is the maximum number of ports we can allow. + */ +#define MAX_PORTS 16 + +/* + * This is a gadget, and the IN/OUT naming is from the host's perspective. + * USB -> OUT endpoint -> rawmidi + * USB <- IN endpoint <- rawmidi + */ +struct gmidi_in_port { + struct f_midi *midi; + int active; + uint8_t cable; + uint8_t state; +#define STATE_UNKNOWN 0 +#define STATE_1PARAM 1 +#define STATE_2PARAM_1 2 +#define STATE_2PARAM_2 3 +#define STATE_SYSEX_0 4 +#define STATE_SYSEX_1 5 +#define STATE_SYSEX_2 6 + uint8_t data[2]; +}; + +struct f_midi { + struct usb_function func; + struct usb_gadget *gadget; + struct usb_ep *in_ep, *out_ep; + struct snd_card *card; + struct snd_rawmidi *rmidi; + + struct snd_rawmidi_substream *in_substream[MAX_PORTS]; + struct snd_rawmidi_substream *out_substream[MAX_PORTS]; + struct gmidi_in_port *in_port[MAX_PORTS]; + + unsigned long out_triggered; + struct tasklet_struct tasklet; + unsigned int in_ports; + unsigned int out_ports; + int index; + char *id; + unsigned int buflen, qlen; +}; + +static inline struct f_midi *func_to_midi(struct usb_function *f) +{ + return container_of(f, struct f_midi, func); +} + +static void f_midi_transmit(struct f_midi *midi, struct usb_request *req); + +DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); +DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1); +DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(16); + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc __initdata = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + /* .bNumEndpoints = DYNAMIC */ + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + /* .iInterface = DYNAMIC */ +}; + +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_1 ac_header_desc __initdata = { + .bLength = UAC_DT_AC_HEADER_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_HEADER, + .bcdADC = cpu_to_le16(0x0100), + .wTotalLength = cpu_to_le16(UAC_DT_AC_HEADER_SIZE(1)), + .bInCollection = 1, + /* .baInterfaceNr = DYNAMIC */ +}; + +/* B.4.1 Standard MS Interface Descriptor */ +static struct usb_interface_descriptor ms_interface_desc __initdata = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, + /* .iInterface = DYNAMIC */ +}; + +/* B.4.2 Class-Specific MS Interface Descriptor */ +static struct usb_ms_header_descriptor ms_header_desc __initdata = { + .bLength = USB_DT_MS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_HEADER, + .bcdMSC = cpu_to_le16(0x0100), + /* .wTotalLength = DYNAMIC */ +}; + +/* B.5.1 Standard Bulk OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +/* B.5.2 Class-specific MS Bulk OUT Endpoint Descriptor */ +static struct usb_ms_endpoint_descriptor_16 ms_out_desc = { + /* .bLength = DYNAMIC */ + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = USB_MS_GENERAL, + /* .bNumEmbMIDIJack = DYNAMIC */ + /* .baAssocJackID = DYNAMIC */ +}; + +/* B.6.1 Standard Bulk IN Endpoint Descriptor */ +static struct usb_endpoint_descriptor bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +/* B.6.2 Class-specific MS Bulk IN Endpoint Descriptor */ +static struct usb_ms_endpoint_descriptor_16 ms_in_desc = { + /* .bLength = DYNAMIC */ + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = USB_MS_GENERAL, + /* .bNumEmbMIDIJack = DYNAMIC */ + /* .baAssocJackID = DYNAMIC */ +}; + +/* string IDs are assigned dynamically */ + +#define STRING_FUNC_IDX 0 + +static struct usb_string midi_string_defs[] = { + [STRING_FUNC_IDX].s = "MIDI function", + { } /* end of list */ +}; + +static struct usb_gadget_strings midi_stringtab = { + .language = 0x0409, /* en-us */ + .strings = midi_string_defs, +}; + +static struct usb_gadget_strings *midi_strings[] = { + &midi_stringtab, + NULL, +}; + +static struct usb_request *alloc_ep_req(struct usb_ep *ep, unsigned length) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req) { + req->length = length; + req->buf = kmalloc(length, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + return req; +} + +static void free_ep_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static const uint8_t f_midi_cin_length[] = { + 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 +}; + +/* + * Receives a chunk of MIDI data. + */ +static void f_midi_read_data(struct usb_ep *ep, int cable, + uint8_t *data, int length) +{ + struct f_midi *midi = ep->driver_data; + struct snd_rawmidi_substream *substream = midi->out_substream[cable]; + + if (!substream) + /* Nobody is listening - throw it on the floor. */ + return; + + if (!test_bit(cable, &midi->out_triggered)) + return; + + snd_rawmidi_receive(substream, data, length); +} + +static void f_midi_handle_out_data(struct usb_ep *ep, struct usb_request *req) +{ + unsigned int i; + u8 *buf = req->buf; + + for (i = 0; i + 3 < req->actual; i += 4) + if (buf[i] != 0) { + int cable = buf[i] >> 4; + int length = f_midi_cin_length[buf[i] & 0x0f]; + f_midi_read_data(ep, cable, &buf[i + 1], length); + } +} + +static void +f_midi_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_midi *midi = ep->driver_data; + struct usb_composite_dev *cdev = midi->func.config->cdev; + int status = req->status; + + switch (status) { + case 0: /* normal completion */ + if (ep == midi->out_ep) { + /* We received stuff. req is queued again, below */ + f_midi_handle_out_data(ep, req); + } else if (ep == midi->in_ep) { + /* Our transmit completed. See if there's more to go. + * f_midi_transmit eats req, don't queue it again. */ + f_midi_transmit(midi, req); + return; + } + break; + + /* this endpoint is normally active while we're configured */ + case -ECONNABORTED: /* hardware forced ep reset */ + case -ECONNRESET: /* request dequeued */ + case -ESHUTDOWN: /* disconnect from host */ + VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status, + req->actual, req->length); + if (ep == midi->out_ep) + f_midi_handle_out_data(ep, req); + + free_ep_req(ep, req); + return; + + case -EOVERFLOW: /* buffer overrun on read means that + * we didn't provide a big enough buffer. + */ + default: + DBG(cdev, "%s complete --> %d, %d/%d\n", ep->name, + status, req->actual, req->length); + break; + case -EREMOTEIO: /* short read */ + break; + } + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status) { + ERROR(cdev, "kill %s: resubmit %d bytes --> %d\n", + ep->name, req->length, status); + usb_ep_set_halt(ep); + /* FIXME recover later ... somehow */ + } +} + +static int f_midi_start_ep(struct f_midi *midi, + struct usb_function *f, + struct usb_ep *ep) +{ + int err; + struct usb_composite_dev *cdev = f->config->cdev; + + if (ep->driver_data) + usb_ep_disable(ep); + + err = config_ep_by_speed(midi->gadget, f, ep); + if (err) { + ERROR(cdev, "can't configure %s: %d\n", ep->name, err); + return err; + } + + err = usb_ep_enable(ep); + if (err) { + ERROR(cdev, "can't start %s: %d\n", ep->name, err); + return err; + } + + ep->driver_data = midi; + + return 0; +} + +static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_midi *midi = func_to_midi(f); + struct usb_composite_dev *cdev = f->config->cdev; + unsigned i; + int err; + + err = f_midi_start_ep(midi, f, midi->in_ep); + if (err) + return err; + + err = f_midi_start_ep(midi, f, midi->out_ep); + if (err) + return err; + + if (midi->out_ep->driver_data) + usb_ep_disable(midi->out_ep); + + err = config_ep_by_speed(midi->gadget, f, midi->out_ep); + if (err) { + ERROR(cdev, "can't configure %s: %d\n", + midi->out_ep->name, err); + return err; + } + + err = usb_ep_enable(midi->out_ep); + if (err) { + ERROR(cdev, "can't start %s: %d\n", + midi->out_ep->name, err); + return err; + } + + midi->out_ep->driver_data = midi; + + /* allocate a bunch of read buffers and queue them all at once. */ + for (i = 0; i < midi->qlen && err == 0; i++) { + struct usb_request *req = + alloc_ep_req(midi->out_ep, midi->buflen); + if (req == NULL) + return -ENOMEM; + + req->complete = f_midi_complete; + err = usb_ep_queue(midi->out_ep, req, GFP_ATOMIC); + if (err) { + ERROR(midi, "%s queue req: %d\n", + midi->out_ep->name, err); + } + } + + return 0; +} + +static void f_midi_disable(struct usb_function *f) +{ + struct f_midi *midi = func_to_midi(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "disable\n"); + + /* + * just disable endpoints, forcing completion of pending i/o. + * all our completion handlers free their requests in this case. + */ + usb_ep_disable(midi->in_ep); + usb_ep_disable(midi->out_ep); +} + +static void f_midi_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct f_midi *midi = func_to_midi(f); + struct snd_card *card; + + DBG(cdev, "unbind\n"); + + /* just to be sure */ + f_midi_disable(f); + + card = midi->card; + midi->card = NULL; + if (card) + snd_card_free(card); + + kfree(midi->id); + midi->id = NULL; + + usb_free_all_descriptors(f); + kfree(midi); +} + +static int f_midi_snd_free(struct snd_device *device) +{ + return 0; +} + +static void f_midi_transmit_packet(struct usb_request *req, uint8_t p0, + uint8_t p1, uint8_t p2, uint8_t p3) +{ + unsigned length = req->length; + u8 *buf = (u8 *)req->buf + length; + + buf[0] = p0; + buf[1] = p1; + buf[2] = p2; + buf[3] = p3; + req->length = length + 4; +} + +/* + * Converts MIDI commands to USB MIDI packets. + */ +static void f_midi_transmit_byte(struct usb_request *req, + struct gmidi_in_port *port, uint8_t b) +{ + uint8_t p0 = port->cable << 4; + + if (b >= 0xf8) { + f_midi_transmit_packet(req, p0 | 0x0f, b, 0, 0); + } else if (b >= 0xf0) { + switch (b) { + case 0xf0: + port->data[0] = b; + port->state = STATE_SYSEX_1; + break; + case 0xf1: + case 0xf3: + port->data[0] = b; + port->state = STATE_1PARAM; + break; + case 0xf2: + port->data[0] = b; + port->state = STATE_2PARAM_1; + break; + case 0xf4: + case 0xf5: + port->state = STATE_UNKNOWN; + break; + case 0xf6: + f_midi_transmit_packet(req, p0 | 0x05, 0xf6, 0, 0); + port->state = STATE_UNKNOWN; + break; + case 0xf7: + switch (port->state) { + case STATE_SYSEX_0: + f_midi_transmit_packet(req, + p0 | 0x05, 0xf7, 0, 0); + break; + case STATE_SYSEX_1: + f_midi_transmit_packet(req, + p0 | 0x06, port->data[0], 0xf7, 0); + break; + case STATE_SYSEX_2: + f_midi_transmit_packet(req, + p0 | 0x07, port->data[0], + port->data[1], 0xf7); + break; + } + port->state = STATE_UNKNOWN; + break; + } + } else if (b >= 0x80) { + port->data[0] = b; + if (b >= 0xc0 && b <= 0xdf) + port->state = STATE_1PARAM; + else + port->state = STATE_2PARAM_1; + } else { /* b < 0x80 */ + switch (port->state) { + case STATE_1PARAM: + if (port->data[0] < 0xf0) { + p0 |= port->data[0] >> 4; + } else { + p0 |= 0x02; + port->state = STATE_UNKNOWN; + } + f_midi_transmit_packet(req, p0, port->data[0], b, 0); + break; + case STATE_2PARAM_1: + port->data[1] = b; + port->state = STATE_2PARAM_2; + break; + case STATE_2PARAM_2: + if (port->data[0] < 0xf0) { + p0 |= port->data[0] >> 4; + port->state = STATE_2PARAM_1; + } else { + p0 |= 0x03; + port->state = STATE_UNKNOWN; + } + f_midi_transmit_packet(req, + p0, port->data[0], port->data[1], b); + break; + case STATE_SYSEX_0: + port->data[0] = b; + port->state = STATE_SYSEX_1; + break; + case STATE_SYSEX_1: + port->data[1] = b; + port->state = STATE_SYSEX_2; + break; + case STATE_SYSEX_2: + f_midi_transmit_packet(req, + p0 | 0x04, port->data[0], port->data[1], b); + port->state = STATE_SYSEX_0; + break; + } + } +} + +static void f_midi_transmit(struct f_midi *midi, struct usb_request *req) +{ + struct usb_ep *ep = midi->in_ep; + int i; + + if (!ep) + return; + + if (!req) + req = alloc_ep_req(ep, midi->buflen); + + if (!req) { + ERROR(midi, "gmidi_transmit: alloc_ep_request failed\n"); + return; + } + req->length = 0; + req->complete = f_midi_complete; + + for (i = 0; i < MAX_PORTS; i++) { + struct gmidi_in_port *port = midi->in_port[i]; + struct snd_rawmidi_substream *substream = midi->in_substream[i]; + + if (!port || !port->active || !substream) + continue; + + while (req->length + 3 < midi->buflen) { + uint8_t b; + if (snd_rawmidi_transmit(substream, &b, 1) != 1) { + port->active = 0; + break; + } + f_midi_transmit_byte(req, port, b); + } + } + + if (req->length > 0) + usb_ep_queue(ep, req, GFP_ATOMIC); + else + free_ep_req(ep, req); +} + +static void f_midi_in_tasklet(unsigned long data) +{ + struct f_midi *midi = (struct f_midi *) data; + f_midi_transmit(midi, NULL); +} + +static int f_midi_in_open(struct snd_rawmidi_substream *substream) +{ + struct f_midi *midi = substream->rmidi->private_data; + + if (!midi->in_port[substream->number]) + return -EINVAL; + + VDBG(midi, "%s()\n", __func__); + midi->in_substream[substream->number] = substream; + midi->in_port[substream->number]->state = STATE_UNKNOWN; + return 0; +} + +static int f_midi_in_close(struct snd_rawmidi_substream *substream) +{ + struct f_midi *midi = substream->rmidi->private_data; + + VDBG(midi, "%s()\n", __func__); + return 0; +} + +static void f_midi_in_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct f_midi *midi = substream->rmidi->private_data; + + if (!midi->in_port[substream->number]) + return; + + VDBG(midi, "%s() %d\n", __func__, up); + midi->in_port[substream->number]->active = up; + if (up) + tasklet_hi_schedule(&midi->tasklet); +} + +static int f_midi_out_open(struct snd_rawmidi_substream *substream) +{ + struct f_midi *midi = substream->rmidi->private_data; + + if (substream->number >= MAX_PORTS) + return -EINVAL; + + VDBG(midi, "%s()\n", __func__); + midi->out_substream[substream->number] = substream; + return 0; +} + +static int f_midi_out_close(struct snd_rawmidi_substream *substream) +{ + struct f_midi *midi = substream->rmidi->private_data; + + VDBG(midi, "%s()\n", __func__); + return 0; +} + +static void f_midi_out_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct f_midi *midi = substream->rmidi->private_data; + + VDBG(midi, "%s()\n", __func__); + + if (up) + set_bit(substream->number, &midi->out_triggered); + else + clear_bit(substream->number, &midi->out_triggered); +} + +static struct snd_rawmidi_ops gmidi_in_ops = { + .open = f_midi_in_open, + .close = f_midi_in_close, + .trigger = f_midi_in_trigger, +}; + +static struct snd_rawmidi_ops gmidi_out_ops = { + .open = f_midi_out_open, + .close = f_midi_out_close, + .trigger = f_midi_out_trigger +}; + +/* register as a sound "card" */ +static int f_midi_register_card(struct f_midi *midi) +{ + struct snd_card *card; + struct snd_rawmidi *rmidi; + int err; + static struct snd_device_ops ops = { + .dev_free = f_midi_snd_free, + }; + + err = snd_card_create(midi->index, midi->id, THIS_MODULE, 0, &card); + if (err < 0) { + ERROR(midi, "snd_card_create() failed\n"); + goto fail; + } + midi->card = card; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, midi, &ops); + if (err < 0) { + ERROR(midi, "snd_device_new() failed: error %d\n", err); + goto fail; + } + + strcpy(card->driver, f_midi_longname); + strcpy(card->longname, f_midi_longname); + strcpy(card->shortname, f_midi_shortname); + + /* Set up rawmidi */ + snd_component_add(card, "MIDI"); + err = snd_rawmidi_new(card, card->longname, 0, + midi->out_ports, midi->in_ports, &rmidi); + if (err < 0) { + ERROR(midi, "snd_rawmidi_new() failed: error %d\n", err); + goto fail; + } + midi->rmidi = rmidi; + strcpy(rmidi->name, card->shortname); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = midi; + + /* + * Yes, rawmidi OUTPUT = USB IN, and rawmidi INPUT = USB OUT. + * It's an upside-down world being a gadget. + */ + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &gmidi_in_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &gmidi_out_ops); + + snd_card_set_dev(card, &midi->gadget->dev); + + /* register it - we're ready to go */ + err = snd_card_register(card); + if (err < 0) { + ERROR(midi, "snd_card_register() failed\n"); + goto fail; + } + + VDBG(midi, "%s() finished ok\n", __func__); + return 0; + +fail: + if (midi->card) { + snd_card_free(midi->card); + midi->card = NULL; + } + return err; +} + +/* MIDI function driver setup/binding */ + +static int __init +f_midi_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_descriptor_header **midi_function; + struct usb_midi_in_jack_descriptor jack_in_ext_desc[MAX_PORTS]; + struct usb_midi_in_jack_descriptor jack_in_emb_desc[MAX_PORTS]; + struct usb_midi_out_jack_descriptor_1 jack_out_ext_desc[MAX_PORTS]; + struct usb_midi_out_jack_descriptor_1 jack_out_emb_desc[MAX_PORTS]; + struct usb_composite_dev *cdev = c->cdev; + struct f_midi *midi = func_to_midi(f); + int status, n, jack = 1, i = 0; + + /* maybe allocate device-global string ID */ + if (midi_string_defs[0].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + goto fail; + midi_string_defs[0].id = status; + } + + /* We have two interfaces, AudioControl and MIDIStreaming */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ms_interface_desc.bInterfaceNumber = status; + ac_header_desc.baInterfaceNr[0] = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + midi->in_ep = usb_ep_autoconfig(cdev->gadget, &bulk_in_desc); + if (!midi->in_ep) + goto fail; + midi->in_ep->driver_data = cdev; /* claim */ + + midi->out_ep = usb_ep_autoconfig(cdev->gadget, &bulk_out_desc); + if (!midi->out_ep) + goto fail; + midi->out_ep->driver_data = cdev; /* claim */ + + /* allocate temporary function list */ + midi_function = kcalloc((MAX_PORTS * 4) + 9, sizeof(*midi_function), + GFP_KERNEL); + if (!midi_function) { + status = -ENOMEM; + goto fail; + } + + /* + * construct the function's descriptor set. As the number of + * input and output MIDI ports is configurable, we have to do + * it that way. + */ + + /* add the headers - these are always the same */ + midi_function[i++] = (struct usb_descriptor_header *) &ac_interface_desc; + midi_function[i++] = (struct usb_descriptor_header *) &ac_header_desc; + midi_function[i++] = (struct usb_descriptor_header *) &ms_interface_desc; + + /* calculate the header's wTotalLength */ + n = USB_DT_MS_HEADER_SIZE + + (midi->in_ports + midi->out_ports) * + (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); + ms_header_desc.wTotalLength = cpu_to_le16(n); + + midi_function[i++] = (struct usb_descriptor_header *) &ms_header_desc; + + /* configure the external IN jacks, each linked to an embedded OUT jack */ + for (n = 0; n < midi->in_ports; n++) { + struct usb_midi_in_jack_descriptor *in_ext = &jack_in_ext_desc[n]; + struct usb_midi_out_jack_descriptor_1 *out_emb = &jack_out_emb_desc[n]; + + in_ext->bLength = USB_DT_MIDI_IN_SIZE; + in_ext->bDescriptorType = USB_DT_CS_INTERFACE; + in_ext->bDescriptorSubtype = USB_MS_MIDI_IN_JACK; + in_ext->bJackType = USB_MS_EXTERNAL; + in_ext->bJackID = jack++; + in_ext->iJack = 0; + midi_function[i++] = (struct usb_descriptor_header *) in_ext; + + out_emb->bLength = USB_DT_MIDI_OUT_SIZE(1); + out_emb->bDescriptorType = USB_DT_CS_INTERFACE; + out_emb->bDescriptorSubtype = USB_MS_MIDI_OUT_JACK; + out_emb->bJackType = USB_MS_EMBEDDED; + out_emb->bJackID = jack++; + out_emb->bNrInputPins = 1; + out_emb->pins[0].baSourcePin = 1; + out_emb->pins[0].baSourceID = in_ext->bJackID; + out_emb->iJack = 0; + midi_function[i++] = (struct usb_descriptor_header *) out_emb; + + /* link it to the endpoint */ + ms_in_desc.baAssocJackID[n] = out_emb->bJackID; + } + + /* configure the external OUT jacks, each linked to an embedded IN jack */ + for (n = 0; n < midi->out_ports; n++) { + struct usb_midi_in_jack_descriptor *in_emb = &jack_in_emb_desc[n]; + struct usb_midi_out_jack_descriptor_1 *out_ext = &jack_out_ext_desc[n]; + + in_emb->bLength = USB_DT_MIDI_IN_SIZE; + in_emb->bDescriptorType = USB_DT_CS_INTERFACE; + in_emb->bDescriptorSubtype = USB_MS_MIDI_IN_JACK; + in_emb->bJackType = USB_MS_EMBEDDED; + in_emb->bJackID = jack++; + in_emb->iJack = 0; + midi_function[i++] = (struct usb_descriptor_header *) in_emb; + + out_ext->bLength = USB_DT_MIDI_OUT_SIZE(1); + out_ext->bDescriptorType = USB_DT_CS_INTERFACE; + out_ext->bDescriptorSubtype = USB_MS_MIDI_OUT_JACK; + out_ext->bJackType = USB_MS_EXTERNAL; + out_ext->bJackID = jack++; + out_ext->bNrInputPins = 1; + out_ext->iJack = 0; + out_ext->pins[0].baSourceID = in_emb->bJackID; + out_ext->pins[0].baSourcePin = 1; + midi_function[i++] = (struct usb_descriptor_header *) out_ext; + + /* link it to the endpoint */ + ms_out_desc.baAssocJackID[n] = in_emb->bJackID; + } + + /* configure the endpoint descriptors ... */ + ms_out_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->in_ports); + ms_out_desc.bNumEmbMIDIJack = midi->in_ports; + + ms_in_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->out_ports); + ms_in_desc.bNumEmbMIDIJack = midi->out_ports; + + /* ... and add them to the list */ + midi_function[i++] = (struct usb_descriptor_header *) &bulk_out_desc; + midi_function[i++] = (struct usb_descriptor_header *) &ms_out_desc; + midi_function[i++] = (struct usb_descriptor_header *) &bulk_in_desc; + midi_function[i++] = (struct usb_descriptor_header *) &ms_in_desc; + midi_function[i++] = NULL; + + /* + * support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + /* copy descriptors, and track endpoint copies */ + f->fs_descriptors = usb_copy_descriptors(midi_function); + if (!f->fs_descriptors) + goto fail_f_midi; + + if (gadget_is_dualspeed(c->cdev->gadget)) { + bulk_in_desc.wMaxPacketSize = cpu_to_le16(512); + bulk_out_desc.wMaxPacketSize = cpu_to_le16(512); + f->hs_descriptors = usb_copy_descriptors(midi_function); + if (!f->hs_descriptors) + goto fail_f_midi; + } + + kfree(midi_function); + + return 0; + +fail_f_midi: + kfree(midi_function); + usb_free_descriptors(f->hs_descriptors); +fail: + /* we might as well release our claims on endpoints */ + if (midi->out_ep) + midi->out_ep->driver_data = NULL; + if (midi->in_ep) + midi->in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +/** + * f_midi_bind_config - add USB MIDI function to a configuration + * @c: the configuration to supcard the USB audio function + * @index: the soundcard index to use for the ALSA device creation + * @id: the soundcard id to use for the ALSA device creation + * @buflen: the buffer length to use + * @qlen the number of read requests to pre-allocate + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + */ +int __init f_midi_bind_config(struct usb_configuration *c, + int index, char *id, + unsigned int in_ports, + unsigned int out_ports, + unsigned int buflen, + unsigned int qlen) +{ + struct f_midi *midi; + int status, i; + + /* sanity check */ + if (in_ports > MAX_PORTS || out_ports > MAX_PORTS) + return -EINVAL; + + /* allocate and initialize one new instance */ + midi = kzalloc(sizeof *midi, GFP_KERNEL); + if (!midi) { + status = -ENOMEM; + goto fail; + } + + for (i = 0; i < in_ports; i++) { + struct gmidi_in_port *port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + status = -ENOMEM; + goto setup_fail; + } + + port->midi = midi; + port->active = 0; + port->cable = i; + midi->in_port[i] = port; + } + + midi->gadget = c->cdev->gadget; + tasklet_init(&midi->tasklet, f_midi_in_tasklet, (unsigned long) midi); + + /* set up ALSA midi devices */ + midi->in_ports = in_ports; + midi->out_ports = out_ports; + status = f_midi_register_card(midi); + if (status < 0) + goto setup_fail; + + midi->func.name = "gmidi function"; + midi->func.strings = midi_strings; + midi->func.bind = f_midi_bind; + midi->func.unbind = f_midi_unbind; + midi->func.set_alt = f_midi_set_alt; + midi->func.disable = f_midi_disable; + + midi->id = kstrdup(id, GFP_KERNEL); + midi->index = index; + midi->buflen = buflen; + midi->qlen = qlen; + + status = usb_add_function(c, &midi->func); + if (status) + goto setup_fail; + + return 0; + +setup_fail: + for (--i; i >= 0; i--) + kfree(midi->in_port[i]); + kfree(midi); +fail: + return status; +} + diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c new file mode 100644 index 00000000..620aeaaf --- /dev/null +++ b/drivers/usb/gadget/f_mtp.c @@ -0,0 +1,1287 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MTP_BULK_BUFFER_SIZE 16384 +#define INTR_BUFFER_SIZE 28 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* values for mtp_dev.state */ +#define STATE_OFFLINE 0 /* initial state, disconnected */ +#define STATE_READY 1 /* ready for userspace calls */ +#define STATE_BUSY 2 /* processing userspace calls */ +#define STATE_CANCELED 3 /* transaction canceled by host */ +#define STATE_ERROR 4 /* error from completion routine */ + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 +#define INTR_REQ_MAX 5 + +/* ID for Microsoft MTP OS String */ +#define MTP_OS_STRING_ID 0xEE + +/* MTP class reqeusts */ +#define MTP_REQ_CANCEL 0x64 +#define MTP_REQ_GET_EXT_EVENT_DATA 0x65 +#define MTP_REQ_RESET 0x66 +#define MTP_REQ_GET_DEVICE_STATUS 0x67 + +/* constants for device status */ +#define MTP_RESPONSE_OK 0x2001 +#define MTP_RESPONSE_DEVICE_BUSY 0x2019 + +static const char mtp_shortname[] = "mtp_usb"; + +struct mtp_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + struct usb_ep *ep_intr; + + int state; + + /* synchronize access to our device file */ + atomic_t open_excl; + /* to enforce only one ioctl at a time */ + atomic_t ioctl_excl; + + struct list_head tx_idle; + struct list_head intr_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + wait_queue_head_t intr_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* for processing MTP_SEND_FILE, MTP_RECEIVE_FILE and + * MTP_SEND_FILE_WITH_HEADER ioctls on a work queue + */ + struct workqueue_struct *wq; + struct work_struct send_file_work; + struct work_struct receive_file_work; + struct file *xfer_file; + loff_t xfer_file_offset; + int64_t xfer_file_length; + unsigned xfer_send_header; + uint16_t xfer_command; + uint32_t xfer_transaction_id; + int xfer_result; +}; + +static struct usb_interface_descriptor mtp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_interface_descriptor ptp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor mtp_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_intr_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE), + .bInterval = 6, +}; + +static struct usb_descriptor_header *fs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_string mtp_string_defs[] = { + /* Naming interface "MTP" so libmtp will recognize us */ + [INTERFACE_STRING_INDEX].s = "MTP", + { }, /* end of list */ +}; + +static struct usb_gadget_strings mtp_string_table = { + .language = 0x0409, /* en-US */ + .strings = mtp_string_defs, +}; + +static struct usb_gadget_strings *mtp_strings[] = { + &mtp_string_table, + NULL, +}; + +/* Microsoft MTP OS String */ +static u8 mtp_os_string[] = { + 18, /* sizeof(mtp_os_string) */ + USB_DT_STRING, + /* Signature field: "MSFT100" */ + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, + /* vendor code */ + 1, + /* padding */ + 0 +}; + +/* Microsoft Extended Configuration Descriptor Header Section */ +struct mtp_ext_config_desc_header { + __le32 dwLength; + __u16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mtp_ext_config_desc_function { + __u8 bFirstInterfaceNumber; + __u8 bInterfaceCount; + __u8 compatibleID[8]; + __u8 subCompatibleID[8]; + __u8 reserved[6]; +}; + +/* MTP Extended Configuration Descriptor */ +struct { + struct mtp_ext_config_desc_header header; + struct mtp_ext_config_desc_function function; +} mtp_ext_config_desc = { + .header = { + .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)), + .bcdVersion = __constant_cpu_to_le16(0x0100), + .wIndex = __constant_cpu_to_le16(4), + .bCount = __constant_cpu_to_le16(1), + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'M', 'T', 'P' }, + }, +}; + +struct mtp_device_status { + __le16 wLength; + __le16 wCode; +}; + +struct mtp_data_header { + /* length of packet, including this header */ + __le32 length; + /* container type (2 for data packet) */ + __le16 type; + /* MTP command code */ + __le16 command; + /* MTP transaction ID */ + __le32 transaction_id; +}; + +/* temporary variable used between mtp_open() and mtp_gadget_bind() */ +static struct mtp_dev *_mtp_dev; + +static inline struct mtp_dev *func_to_mtp(struct usb_function *f) +{ + return container_of(f, struct mtp_dev, function); +} + +static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void mtp_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int mtp_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void mtp_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void mtp_req_put(struct mtp_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request +*mtp_req_get(struct mtp_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->state = STATE_ERROR; + + wake_up(&dev->read_wq); +} + +static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->intr_idle, req); + + wake_up(&dev->intr_wq); +} + +static int mtp_create_bulk_endpoints(struct mtp_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc, + struct usb_endpoint_descriptor *intr_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, intr_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_intr = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_in, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_in; + mtp_req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_out, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_out; + dev->rx_req[i] = req; + } + for (i = 0; i < INTR_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_intr; + mtp_req_put(dev, &dev->intr_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "mtp_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t mtp_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + ssize_t r = count; + unsigned xfer; + int ret = 0; + + DBG(cdev, "mtp_read(%zu)\n", count); + + if (count > MTP_BULK_BUFFER_SIZE) + return -EINVAL; + + /* we will block until we're online */ + DBG(cdev, "mtp_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + dev->state != STATE_OFFLINE); + if (ret < 0) { + r = ret; + goto done; + } + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + DBG(cdev, "rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (dev->state == STATE_BUSY) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + DBG(cdev, "rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_read returning %zd\n", r); + return r; +} + +static ssize_t mtp_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + ssize_t r = count; + unsigned xfer; + int sendZLP = 0; + int ret; + + DBG(cdev, "mtp_write(%zu)\n", count); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + return -ENODEV; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) + sendZLP = 1; + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + if (dev->state != STATE_BUSY) { + DBG(cdev, "mtp_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY)); + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + if (xfer && copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "mtp_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_write returning %zd\n", r); + return r; +} + +/* read from a local file and write to USB */ +static void send_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, + send_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + struct mtp_data_header *header; + struct file *filp; + loff_t offset; + int64_t count; + int xfer, ret, hdr_size; + int r = 0; + int sendZLP = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "send_file_work(%lld %lld)\n", offset, count); + + if (dev->xfer_send_header) { + hdr_size = sizeof(struct mtp_data_header); + count += hdr_size; + } else { + hdr_size = 0; + } + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) + sendZLP = 1; + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + break; + } + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + + if (hdr_size) { + /* prepend MTP data header */ + header = (struct mtp_data_header *)req->buf; + header->length = __cpu_to_le32(count); + header->type = __cpu_to_le16(2); /* data packet */ + header->command = __cpu_to_le16(dev->xfer_command); + header->transaction_id = + __cpu_to_le32(dev->xfer_transaction_id); + } + + ret = vfs_read(filp, req->buf + hdr_size, xfer - hdr_size, + &offset); + if (ret < 0) { + r = ret; + break; + } + xfer = ret + hdr_size; + hdr_size = 0; + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "send_file_work: xfer error %d\n", ret); + dev->state = STATE_ERROR; + r = -EIO; + break; + } + + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + DBG(cdev, "send_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +/* read from USB and write to a local file */ +static void receive_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, + receive_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *read_req = NULL, *write_req = NULL; + struct file *filp; + loff_t offset; + int64_t count; + int ret, cur_buf = 0; + int r = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "receive_file_work(%lld)\n", count); + + while (count > 0 || write_req) { + if (count > 0) { + /* queue a request */ + read_req = dev->rx_req[cur_buf]; + cur_buf = (cur_buf + 1) % RX_REQ_MAX; + + read_req->length = (count > MTP_BULK_BUFFER_SIZE + ? MTP_BULK_BUFFER_SIZE : count); + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + } + + if (write_req) { + DBG(cdev, "rx %p %d\n", write_req, write_req->actual); + ret = vfs_write(filp, write_req->buf, write_req->actual, + &offset); + DBG(cdev, "vfs_write %d\n", ret); + if (ret != write_req->actual) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + write_req = NULL; + } + + if (read_req) { + /* wait for our last read to complete */ + ret = wait_event_interruptible(dev->read_wq, + dev->rx_done || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + if (!dev->rx_done) + usb_ep_dequeue(dev->ep_out, read_req); + break; + } + /* if xfer_file_length is 0xFFFFFFFF, then we read until + * we get a zero length packet + */ + if (count != 0xFFFFFFFF) + count -= read_req->actual; + if (read_req->actual < read_req->length) { + /* + * short packet is used to signal EOF for + * sizes > 4 gig + */ + DBG(cdev, "got short packet\n"); + count = 0; + } + + write_req = read_req; + read_req = NULL; + } + } + + DBG(cdev, "receive_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event) +{ + struct usb_request *req = NULL; + int ret; + int length = event->length; + + DBG(dev->cdev, "mtp_send_event(%zu)\n", event->length); + + if (length < 0 || length > INTR_BUFFER_SIZE) + return -EINVAL; + if (dev->state == STATE_OFFLINE) + return -ENODEV; + + ret = wait_event_interruptible_timeout(dev->intr_wq, + (req = mtp_req_get(dev, &dev->intr_idle)), + msecs_to_jiffies(1000)); + if (!req) + return -ETIME; + + if (copy_from_user(req->buf, (void __user *)event->data, length)) { + mtp_req_put(dev, &dev->intr_idle, req); + return -EFAULT; + } + req->length = length; + ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL); + if (ret) + mtp_req_put(dev, &dev->intr_idle, req); + + return ret; +} + +static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct mtp_dev *dev = fp->private_data; + struct file *filp = NULL; + int ret = -EINVAL; + + if (mtp_lock(&dev->ioctl_excl)) + return -EBUSY; + + switch (code) { + case MTP_SEND_FILE: + case MTP_RECEIVE_FILE: + case MTP_SEND_FILE_WITH_HEADER: + { + struct mtp_file_range mfr; + struct work_struct *work; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + ret = -ECANCELED; + goto out; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + ret = -ENODEV; + goto out; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) { + ret = -EFAULT; + goto fail; + } + /* hold a reference to the file while we are working with it */ + filp = fget(mfr.fd); + if (!filp) { + ret = -EBADF; + goto fail; + } + + /* write the parameters */ + dev->xfer_file = filp; + dev->xfer_file_offset = mfr.offset; + dev->xfer_file_length = mfr.length; + smp_wmb(); + + if (code == MTP_SEND_FILE_WITH_HEADER) { + work = &dev->send_file_work; + dev->xfer_send_header = 1; + dev->xfer_command = mfr.command; + dev->xfer_transaction_id = mfr.transaction_id; + } else if (code == MTP_SEND_FILE) { + work = &dev->send_file_work; + dev->xfer_send_header = 0; + } else { + work = &dev->receive_file_work; + } + + /* We do the file transfer on a work queue so it will run + * in kernel context, which is necessary for vfs_read and + * vfs_write to use our buffers in the kernel address space. + */ + queue_work(dev->wq, work); + /* wait for operation to complete */ + flush_workqueue(dev->wq); + fput(filp); + + /* read the result */ + smp_rmb(); + ret = dev->xfer_result; + break; + } + case MTP_SEND_EVENT: + { + struct mtp_event event; + /* return here so we don't change dev->state below, + * which would interfere with bulk transfer state. + */ + if (copy_from_user(&event, (void __user *)value, sizeof(event))) + ret = -EFAULT; + else + ret = mtp_send_event(dev, &event); + goto out; + } + } + +fail: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + ret = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); +out: + mtp_unlock(&dev->ioctl_excl); + DBG(dev->cdev, "ioctl returning %d\n", ret); + return ret; +} + +static int mtp_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_open\n"); + if (mtp_lock(&_mtp_dev->open_excl)) + return -EBUSY; + + /* clear any error condition */ + if (_mtp_dev->state != STATE_OFFLINE) + _mtp_dev->state = STATE_READY; + + fp->private_data = _mtp_dev; + return 0; +} + +static int mtp_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_release\n"); + + mtp_unlock(&_mtp_dev->open_excl); + return 0; +} + +/* file operations for /dev/mtp_usb */ +static const struct file_operations mtp_fops = { + .owner = THIS_MODULE, + .read = mtp_read, + .write = mtp_write, + .unlocked_ioctl = mtp_ioctl, + .open = mtp_open, + .release = mtp_release, +}; + +static struct miscdevice mtp_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = mtp_shortname, + .fops = &mtp_fops, +}; + +static int mtp_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct mtp_dev *dev = _mtp_dev; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + unsigned long flags; + + VDBG(cdev, "mtp_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* Handle MTP OS string */ + if (ctrl->bRequestType == + (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) + && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR + && (w_value >> 8) == USB_DT_STRING + && (w_value & 0xFF) == MTP_OS_STRING_ID) { + value = (w_length < sizeof(mtp_os_string) + ? w_length : sizeof(mtp_os_string)); + memcpy(cdev->req->buf, mtp_os_string, value); + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + /* Handle MTP OS descriptor */ + DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == 1 + && (ctrl->bRequestType & USB_DIR_IN) + && (w_index == 4 || w_index == 5)) { + value = (w_length < sizeof(mtp_ext_config_desc) ? + w_length : sizeof(mtp_ext_config_desc)); + memcpy(cdev->req->buf, &mtp_ext_config_desc, value); + } + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + DBG(cdev, "class request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0 + && w_value == 0) { + DBG(cdev, "MTP_REQ_CANCEL\n"); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state == STATE_BUSY) { + dev->state = STATE_CANCELED; + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We need to queue a request to read the remaining + * bytes, but we don't actually need to look at + * the contents. + */ + value = w_length; + } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS + && w_index == 0 && w_value == 0) { + struct mtp_device_status *status = cdev->req->buf; + status->wLength = + __constant_cpu_to_le16(sizeof(*status)); + + DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n"); + spin_lock_irqsave(&dev->lock, flags); + /* device status is "busy" until we report + * the cancelation to userspace + */ + if (dev->state == STATE_CANCELED) + status->wCode = + __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY); + else + status->wCode = + __cpu_to_le16(MTP_RESPONSE_OK); + spin_unlock_irqrestore(&dev->lock, flags); + value = sizeof(*status); + } + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + ERROR(cdev, "%s: response queue error\n", __func__); + } + return value; +} + +static int +mtp_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct mtp_dev *dev = func_to_mtp(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "mtp_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + mtp_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc, + &mtp_fullspeed_out_desc, &mtp_intr_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + mtp_highspeed_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_highspeed_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +mtp_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_request *req; + int i; + + while ((req = mtp_req_get(dev, &dev->tx_idle))) + mtp_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + mtp_request_free(dev->rx_req[i], dev->ep_out); + while ((req = mtp_req_get(dev, &dev->intr_idle))) + mtp_request_free(req, dev->ep_intr); + dev->state = STATE_OFFLINE; +} + +static int mtp_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_intr); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_intr); + if (ret) { + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_in); + return ret; + } + dev->state = STATE_READY; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void mtp_function_disable(struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "mtp_function_disable\n"); + dev->state = STATE_OFFLINE; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_intr); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int mtp_bind_config(struct usb_configuration *c, bool ptp_config) +{ + struct mtp_dev *dev = _mtp_dev; + int ret = 0; + + printk(KERN_INFO "mtp_bind_config\n"); + + /* allocate a string ID for our interface */ + if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + mtp_string_defs[INTERFACE_STRING_INDEX].id = ret; + mtp_interface_desc.iInterface = ret; + } + + dev->cdev = c->cdev; + dev->function.name = "mtp"; + dev->function.strings = mtp_strings; + if (ptp_config) { + dev->function.fs_descriptors = fs_ptp_descs; + dev->function.hs_descriptors = hs_ptp_descs; + } else { + dev->function.fs_descriptors = fs_mtp_descs; + dev->function.hs_descriptors = hs_mtp_descs; + } + dev->function.bind = mtp_function_bind; + dev->function.unbind = mtp_function_unbind; + dev->function.set_alt = mtp_function_set_alt; + dev->function.disable = mtp_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int mtp_setup(void) +{ + struct mtp_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + init_waitqueue_head(&dev->intr_wq); + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->ioctl_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->intr_idle); + + dev->wq = create_singlethread_workqueue("f_mtp"); + if (!dev->wq) { + ret = -ENOMEM; + goto err1; + } + INIT_WORK(&dev->send_file_work, send_file_work); + INIT_WORK(&dev->receive_file_work, receive_file_work); + + _mtp_dev = dev; + + ret = misc_register(&mtp_device); + if (ret) + goto err2; + + return 0; + +err2: + destroy_workqueue(dev->wq); +err1: + _mtp_dev = NULL; + kfree(dev); + printk(KERN_ERR "mtp gadget driver failed to initialize\n"); + return ret; +} + +static void mtp_cleanup(void) +{ + struct mtp_dev *dev = _mtp_dev; + + if (!dev) + return; + + misc_deregister(&mtp_device); + destroy_workqueue(dev->wq); + _mtp_dev = NULL; + kfree(dev); +} diff --git a/drivers/usb/gadget/f_ncm.c b/drivers/usb/gadget/f_ncm.c new file mode 100644 index 00000000..ee19bc8d --- /dev/null +++ b/drivers/usb/gadget/f_ncm.c @@ -0,0 +1,1345 @@ +/* + * f_ncm.c -- USB CDC Network (NCM) link function driver + * + * Copyright (C) 2010 Nokia Corporation + * Contact: Yauheni Kaliuta + * + * The driver borrows from f_ecm.c which is: + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include + +#include + +#include "u_ether.h" + +/* + * This function is a "CDC Network Control Model" (CDC NCM) Ethernet link. + * NCM is intended to be used with high-speed network attachments. + * + * Note that NCM requires the use of "alternate settings" for its data + * interface. This means that the set_alt() method has real work to do, + * and also means that a get_alt() method is required. + */ + +/* to trigger crc/non-crc ndp signature */ + +#define NCM_NDP_HDR_CRC_MASK 0x01000000 +#define NCM_NDP_HDR_CRC 0x01000000 +#define NCM_NDP_HDR_NOCRC 0x00000000 + +enum ncm_notify_state { + NCM_NOTIFY_NONE, /* don't notify */ + NCM_NOTIFY_CONNECT, /* issue CONNECT next */ + NCM_NOTIFY_SPEED, /* issue SPEED_CHANGE next */ +}; + +struct f_ncm { + struct gether port; + u8 ctrl_id, data_id; + + char ethaddr[14]; + + struct usb_ep *notify; + struct usb_request *notify_req; + u8 notify_state; + bool is_open; + + const struct ndp_parser_opts *parser_opts; + bool is_crc; + u32 ndp_sign; + + /* + * for notification, it is accessed from both + * callback and ethernet open/close + */ + spinlock_t lock; +}; + +static inline struct f_ncm *func_to_ncm(struct usb_function *f) +{ + return container_of(f, struct f_ncm, port.func); +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static inline unsigned ncm_bitrate(struct usb_gadget *g) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +/*-------------------------------------------------------------------------*/ + +/* + * We cannot group frames so use just the minimal size which ok to put + * one max-size ethernet frame. + * If the host can group frames, allow it to do that, 16K is selected, + * because it's used by default by the current linux host driver + */ +#define NTB_DEFAULT_IN_SIZE USB_CDC_NCM_NTB_MIN_IN_SIZE +#define NTB_OUT_SIZE 16384 + +/* + * skbs of size less than that will not be aligned + * to NCM's dwNtbInMaxSize to save bus bandwidth + */ + +#define MAX_TX_NONFIXED (512 * 3) + +#define FORMATS_SUPPORTED (USB_CDC_NCM_NTB16_SUPPORTED | \ + USB_CDC_NCM_NTB32_SUPPORTED) + +static struct usb_cdc_ncm_ntb_parameters ntb_parameters = { + .wLength = cpu_to_le16(sizeof(ntb_parameters)), + .bmNtbFormatsSupported = cpu_to_le16(FORMATS_SUPPORTED), + .dwNtbInMaxSize = cpu_to_le32(NTB_DEFAULT_IN_SIZE), + .wNdpInDivisor = cpu_to_le16(4), + .wNdpInPayloadRemainder = cpu_to_le16(0), + .wNdpInAlignment = cpu_to_le16(4), + + .dwNtbOutMaxSize = cpu_to_le32(NTB_OUT_SIZE), + .wNdpOutDivisor = cpu_to_le16(4), + .wNdpOutPayloadRemainder = cpu_to_le16(0), + .wNdpOutAlignment = cpu_to_le16(4), +}; + +/* + * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one + * packet, to simplify cancellation; and a big transfer interval, to + * waste less bandwidth. + */ + +#define NCM_STATUS_INTERVAL_MS 32 +#define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = { + .bLength = sizeof ncm_iad_desc, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, /* control + data */ + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_NCM, + .bFunctionProtocol = USB_CDC_PROTO_NONE, + /* .iFunction = DYNAMIC */ +}; + +/* interface descriptor: */ + +static struct usb_interface_descriptor ncm_control_intf __initdata = { + .bLength = sizeof ncm_control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc ncm_header_desc __initdata = { + .bLength = sizeof ncm_header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_union_desc ncm_union_desc __initdata = { + .bLength = sizeof(ncm_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +static struct usb_cdc_ether_desc ecm_desc __initdata = { + .bLength = sizeof ecm_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, + + /* this descriptor actually adds value, surprise! */ + /* .iMACAddress = DYNAMIC */ + .bmEthernetStatistics = cpu_to_le32(0), /* no statistics */ + .wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN), + .wNumberMCFilters = cpu_to_le16(0), + .bNumberPowerFilters = 0, +}; + +#define NCAPS (USB_CDC_NCM_NCAP_ETH_FILTER | USB_CDC_NCM_NCAP_CRC_MODE) + +static struct usb_cdc_ncm_desc ncm_desc __initdata = { + .bLength = sizeof ncm_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_NCM_TYPE, + + .bcdNcmVersion = cpu_to_le16(0x0100), + /* can process SetEthernetPacketFilter */ + .bmNetworkCapabilities = NCAPS, +}; + +/* the default data interface has no endpoints ... */ + +static struct usb_interface_descriptor ncm_data_nop_intf __initdata = { + .bLength = sizeof ncm_data_nop_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = USB_CDC_NCM_PROTO_NTB, + /* .iInterface = DYNAMIC */ +}; + +/* ... but the "real" data interface has two bulk endpoints */ + +static struct usb_interface_descriptor ncm_data_intf __initdata = { + .bLength = sizeof ncm_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = USB_CDC_NCM_PROTO_NTB, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT), + .bInterval = NCM_STATUS_INTERVAL_MS, +}; + +static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *ncm_fs_function[] __initdata = { + (struct usb_descriptor_header *) &ncm_iad_desc, + /* CDC NCM control descriptors */ + (struct usb_descriptor_header *) &ncm_control_intf, + (struct usb_descriptor_header *) &ncm_header_desc, + (struct usb_descriptor_header *) &ncm_union_desc, + (struct usb_descriptor_header *) &ecm_desc, + (struct usb_descriptor_header *) &ncm_desc, + (struct usb_descriptor_header *) &fs_ncm_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ncm_data_nop_intf, + (struct usb_descriptor_header *) &ncm_data_intf, + (struct usb_descriptor_header *) &fs_ncm_in_desc, + (struct usb_descriptor_header *) &fs_ncm_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT), + .bInterval = USB_MS_TO_HS_INTERVAL(NCM_STATUS_INTERVAL_MS), +}; +static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_ncm_out_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *ncm_hs_function[] __initdata = { + (struct usb_descriptor_header *) &ncm_iad_desc, + /* CDC NCM control descriptors */ + (struct usb_descriptor_header *) &ncm_control_intf, + (struct usb_descriptor_header *) &ncm_header_desc, + (struct usb_descriptor_header *) &ncm_union_desc, + (struct usb_descriptor_header *) &ecm_desc, + (struct usb_descriptor_header *) &ncm_desc, + (struct usb_descriptor_header *) &hs_ncm_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ncm_data_nop_intf, + (struct usb_descriptor_header *) &ncm_data_intf, + (struct usb_descriptor_header *) &hs_ncm_in_desc, + (struct usb_descriptor_header *) &hs_ncm_out_desc, + NULL, +}; + +/* string descriptors: */ + +#define STRING_CTRL_IDX 0 +#define STRING_MAC_IDX 1 +#define STRING_DATA_IDX 2 +#define STRING_IAD_IDX 3 + +static struct usb_string ncm_string_defs[] = { + [STRING_CTRL_IDX].s = "CDC Network Control Model (NCM)", + [STRING_MAC_IDX].s = "", + [STRING_DATA_IDX].s = "CDC Network Data", + [STRING_IAD_IDX].s = "CDC NCM", + { } /* end of list */ +}; + +static struct usb_gadget_strings ncm_string_table = { + .language = 0x0409, /* en-us */ + .strings = ncm_string_defs, +}; + +static struct usb_gadget_strings *ncm_strings[] = { + &ncm_string_table, + NULL, +}; + +/* + * Here are options for NCM Datagram Pointer table (NDP) parser. + * There are 2 different formats: NDP16 and NDP32 in the spec (ch. 3), + * in NDP16 offsets and sizes fields are 1 16bit word wide, + * in NDP32 -- 2 16bit words wide. Also signatures are different. + * To make the parser code the same, put the differences in the structure, + * and switch pointers to the structures when the format is changed. + */ + +struct ndp_parser_opts { + u32 nth_sign; + u32 ndp_sign; + unsigned nth_size; + unsigned ndp_size; + unsigned ndplen_align; + /* sizes in u16 units */ + unsigned dgram_item_len; /* index or length */ + unsigned block_length; + unsigned fp_index; + unsigned reserved1; + unsigned reserved2; + unsigned next_fp_index; +}; + +#define INIT_NDP16_OPTS { \ + .nth_sign = USB_CDC_NCM_NTH16_SIGN, \ + .ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN, \ + .nth_size = sizeof(struct usb_cdc_ncm_nth16), \ + .ndp_size = sizeof(struct usb_cdc_ncm_ndp16), \ + .ndplen_align = 4, \ + .dgram_item_len = 1, \ + .block_length = 1, \ + .fp_index = 1, \ + .reserved1 = 0, \ + .reserved2 = 0, \ + .next_fp_index = 1, \ + } + + +#define INIT_NDP32_OPTS { \ + .nth_sign = USB_CDC_NCM_NTH32_SIGN, \ + .ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN, \ + .nth_size = sizeof(struct usb_cdc_ncm_nth32), \ + .ndp_size = sizeof(struct usb_cdc_ncm_ndp32), \ + .ndplen_align = 8, \ + .dgram_item_len = 2, \ + .block_length = 2, \ + .fp_index = 2, \ + .reserved1 = 1, \ + .reserved2 = 2, \ + .next_fp_index = 2, \ + } + +static const struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS; +static const struct ndp_parser_opts ndp32_opts = INIT_NDP32_OPTS; + +static inline void put_ncm(__le16 **p, unsigned size, unsigned val) +{ + switch (size) { + case 1: + put_unaligned_le16((u16)val, *p); + break; + case 2: + put_unaligned_le32((u32)val, *p); + + break; + default: + BUG(); + } + + *p += size; +} + +static inline unsigned get_ncm(__le16 **p, unsigned size) +{ + unsigned tmp; + + switch (size) { + case 1: + tmp = get_unaligned_le16(*p); + break; + case 2: + tmp = get_unaligned_le32(*p); + break; + default: + BUG(); + } + + *p += size; + return tmp; +} + +/*-------------------------------------------------------------------------*/ + +static inline void ncm_reset_values(struct f_ncm *ncm) +{ + ncm->parser_opts = &ndp16_opts; + ncm->is_crc = false; + ncm->port.cdc_filter = DEFAULT_FILTER; + + /* doesn't make sense for ncm, fixed size used */ + ncm->port.header_len = 0; + + ncm->port.fixed_out_len = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize); + ncm->port.fixed_in_len = NTB_DEFAULT_IN_SIZE; +} + +/* + * Context: ncm->lock held + */ +static void ncm_do_notify(struct f_ncm *ncm) +{ + struct usb_request *req = ncm->notify_req; + struct usb_cdc_notification *event; + struct usb_composite_dev *cdev = ncm->port.func.config->cdev; + __le32 *data; + int status; + + /* notification already in flight? */ + if (!req) + return; + + event = req->buf; + switch (ncm->notify_state) { + case NCM_NOTIFY_NONE: + return; + + case NCM_NOTIFY_CONNECT: + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + if (ncm->is_open) + event->wValue = cpu_to_le16(1); + else + event->wValue = cpu_to_le16(0); + event->wLength = 0; + req->length = sizeof *event; + + DBG(cdev, "notify connect %s\n", + ncm->is_open ? "true" : "false"); + ncm->notify_state = NCM_NOTIFY_NONE; + break; + + case NCM_NOTIFY_SPEED: + event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; + event->wValue = cpu_to_le16(0); + event->wLength = cpu_to_le16(8); + req->length = NCM_STATUS_BYTECOUNT; + + /* SPEED_CHANGE data is up/down speeds in bits/sec */ + data = req->buf + sizeof *event; + data[0] = cpu_to_le32(ncm_bitrate(cdev->gadget)); + data[1] = data[0]; + + DBG(cdev, "notify speed %d\n", ncm_bitrate(cdev->gadget)); + ncm->notify_state = NCM_NOTIFY_CONNECT; + break; + } + event->bmRequestType = 0xA1; + event->wIndex = cpu_to_le16(ncm->ctrl_id); + + ncm->notify_req = NULL; + /* + * In double buffering if there is a space in FIFO, + * completion callback can be called right after the call, + * so unlocking + */ + spin_unlock(&ncm->lock); + status = usb_ep_queue(ncm->notify, req, GFP_ATOMIC); + spin_lock(&ncm->lock); + if (status < 0) { + ncm->notify_req = req; + DBG(cdev, "notify --> %d\n", status); + } +} + +/* + * Context: ncm->lock held + */ +static void ncm_notify(struct f_ncm *ncm) +{ + /* + * NOTE on most versions of Linux, host side cdc-ethernet + * won't listen for notifications until its netdevice opens. + * The first notification then sits in the FIFO for a long + * time, and the second one is queued. + * + * If ncm_notify() is called before the second (CONNECT) + * notification is sent, then it will reset to send the SPEED + * notificaion again (and again, and again), but it's not a problem + */ + ncm->notify_state = NCM_NOTIFY_SPEED; + ncm_do_notify(ncm); +} + +static void ncm_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ncm *ncm = req->context; + struct usb_composite_dev *cdev = ncm->port.func.config->cdev; + struct usb_cdc_notification *event = req->buf; + + spin_lock(&ncm->lock); + switch (req->status) { + case 0: + VDBG(cdev, "Notification %02x sent\n", + event->bNotificationType); + break; + case -ECONNRESET: + case -ESHUTDOWN: + ncm->notify_state = NCM_NOTIFY_NONE; + break; + default: + DBG(cdev, "event %02x --> %d\n", + event->bNotificationType, req->status); + break; + } + ncm->notify_req = req; + ncm_do_notify(ncm); + spin_unlock(&ncm->lock); +} + +static void ncm_ep0out_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* now for SET_NTB_INPUT_SIZE only */ + unsigned in_size; + struct usb_function *f = req->context; + struct f_ncm *ncm = func_to_ncm(f); + struct usb_composite_dev *cdev = ep->driver_data; + + req->context = NULL; + if (req->status || req->actual != req->length) { + DBG(cdev, "Bad control-OUT transfer\n"); + goto invalid; + } + + in_size = get_unaligned_le32(req->buf); + if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE || + in_size > le32_to_cpu(ntb_parameters.dwNtbInMaxSize)) { + DBG(cdev, "Got wrong INPUT SIZE (%d) from host\n", in_size); + goto invalid; + } + + ncm->port.fixed_in_len = in_size; + VDBG(cdev, "Set NTB INPUT SIZE %d\n", in_size); + return; + +invalid: + usb_ep_set_halt(ep); + return; +} + +static int ncm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_ncm *ncm = func_to_ncm(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* + * composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_ETHERNET_PACKET_FILTER: + /* + * see 6.2.30: no data, wIndex = interface, + * wValue = packet filter bitmap + */ + if (w_length != 0 || w_index != ncm->ctrl_id) + goto invalid; + DBG(cdev, "packet filter %02x\n", w_value); + /* + * REVISIT locking of cdc_filter. This assumes the UDC + * driver won't have a concurrent packet TX irq running on + * another CPU; or that if it does, this write is atomic... + */ + ncm->port.cdc_filter = w_value; + value = 0; + break; + /* + * and optionally: + * case USB_CDC_SEND_ENCAPSULATED_COMMAND: + * case USB_CDC_GET_ENCAPSULATED_RESPONSE: + * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS: + * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_STATISTIC: + */ + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_PARAMETERS: + + if (w_length == 0 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + value = w_length > sizeof ntb_parameters ? + sizeof ntb_parameters : w_length; + memcpy(req->buf, &ntb_parameters, value); + VDBG(cdev, "Host asked NTB parameters\n"); + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_INPUT_SIZE: + + if (w_length < 4 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + put_unaligned_le32(ncm->port.fixed_in_len, req->buf); + value = 4; + VDBG(cdev, "Host asked INPUT SIZE, sending %d\n", + ncm->port.fixed_in_len); + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_NTB_INPUT_SIZE: + { + if (w_length != 4 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + req->complete = ncm_ep0out_complete; + req->length = w_length; + req->context = f; + + value = req->length; + break; + } + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_FORMAT: + { + uint16_t format; + + if (w_length < 2 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + format = (ncm->parser_opts == &ndp16_opts) ? 0x0000 : 0x0001; + put_unaligned_le16(format, req->buf); + value = 2; + VDBG(cdev, "Host asked NTB FORMAT, sending %d\n", format); + break; + } + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_NTB_FORMAT: + { + if (w_length != 0 || w_index != ncm->ctrl_id) + goto invalid; + switch (w_value) { + case 0x0000: + ncm->parser_opts = &ndp16_opts; + DBG(cdev, "NCM16 selected\n"); + break; + case 0x0001: + ncm->parser_opts = &ndp32_opts; + DBG(cdev, "NCM32 selected\n"); + break; + default: + goto invalid; + } + value = 0; + break; + } + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_CRC_MODE: + { + uint16_t is_crc; + + if (w_length < 2 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + is_crc = ncm->is_crc ? 0x0001 : 0x0000; + put_unaligned_le16(is_crc, req->buf); + value = 2; + VDBG(cdev, "Host asked CRC MODE, sending %d\n", is_crc); + break; + } + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_CRC_MODE: + { + int ndp_hdr_crc = 0; + + if (w_length != 0 || w_index != ncm->ctrl_id) + goto invalid; + switch (w_value) { + case 0x0000: + ncm->is_crc = false; + ndp_hdr_crc = NCM_NDP_HDR_NOCRC; + DBG(cdev, "non-CRC mode selected\n"); + break; + case 0x0001: + ncm->is_crc = true; + ndp_hdr_crc = NCM_NDP_HDR_CRC; + DBG(cdev, "CRC mode selected\n"); + break; + default: + goto invalid; + } + ncm->ndp_sign = ncm->parser_opts->ndp_sign | ndp_hdr_crc; + value = 0; + break; + } + + /* and disabled in ncm descriptor: */ + /* case USB_CDC_GET_NET_ADDRESS: */ + /* case USB_CDC_SET_NET_ADDRESS: */ + /* case USB_CDC_GET_MAX_DATAGRAM_SIZE: */ + /* case USB_CDC_SET_MAX_DATAGRAM_SIZE: */ + + default: +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "ncm req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "ncm req %02x.%02x response err %d\n", + ctrl->bRequestType, ctrl->bRequest, + value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + + +static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_ncm *ncm = func_to_ncm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* Control interface has only altsetting 0 */ + if (intf == ncm->ctrl_id) { + if (alt != 0) + goto fail; + + if (ncm->notify->driver_data) { + DBG(cdev, "reset ncm control %d\n", intf); + usb_ep_disable(ncm->notify); + } + + if (!(ncm->notify->desc)) { + DBG(cdev, "init ncm ctrl %d\n", intf); + if (config_ep_by_speed(cdev->gadget, f, ncm->notify)) + goto fail; + } + usb_ep_enable(ncm->notify); + ncm->notify->driver_data = ncm; + + /* Data interface has two altsettings, 0 and 1 */ + } else if (intf == ncm->data_id) { + if (alt > 1) + goto fail; + + if (ncm->port.in_ep->driver_data) { + DBG(cdev, "reset ncm\n"); + gether_disconnect(&ncm->port); + ncm_reset_values(ncm); + } + + /* + * CDC Network only sends data in non-default altsettings. + * Changing altsettings resets filters, statistics, etc. + */ + if (alt == 1) { + struct net_device *net; + + if (!ncm->port.in_ep->desc || + !ncm->port.out_ep->desc) { + DBG(cdev, "init ncm\n"); + if (config_ep_by_speed(cdev->gadget, f, + ncm->port.in_ep) || + config_ep_by_speed(cdev->gadget, f, + ncm->port.out_ep)) { + ncm->port.in_ep->desc = NULL; + ncm->port.out_ep->desc = NULL; + goto fail; + } + } + + /* TODO */ + /* Enable zlps by default for NCM conformance; + * override for musb_hdrc (avoids txdma ovhead) + */ + ncm->port.is_zlp_ok = !( + gadget_is_musbhdrc(cdev->gadget) + ); + ncm->port.cdc_filter = DEFAULT_FILTER; + DBG(cdev, "activate ncm\n"); + net = gether_connect(&ncm->port); + if (IS_ERR(net)) + return PTR_ERR(net); + } + + spin_lock(&ncm->lock); + ncm_notify(ncm); + spin_unlock(&ncm->lock); + } else + goto fail; + + return 0; +fail: + return -EINVAL; +} + +/* + * Because the data interface supports multiple altsettings, + * this NCM function *MUST* implement a get_alt() method. + */ +static int ncm_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_ncm *ncm = func_to_ncm(f); + + if (intf == ncm->ctrl_id) + return 0; + return ncm->port.in_ep->driver_data ? 1 : 0; +} + +static struct sk_buff *ncm_wrap_ntb(struct gether *port, + struct sk_buff *skb) +{ + struct f_ncm *ncm = func_to_ncm(&port->func); + struct sk_buff *skb2; + int ncb_len = 0; + __le16 *tmp; + int div; + int rem; + int pad; + int ndp_align; + int ndp_pad; + unsigned max_size = ncm->port.fixed_in_len; + const struct ndp_parser_opts *opts = ncm->parser_opts; + unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0; + + div = le16_to_cpu(ntb_parameters.wNdpInDivisor); + rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder); + ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment); + + ncb_len += opts->nth_size; + ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len; + ncb_len += ndp_pad; + ncb_len += opts->ndp_size; + ncb_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */ + ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */ + pad = ALIGN(ncb_len, div) + rem - ncb_len; + ncb_len += pad; + + if (ncb_len + skb->len + crc_len > max_size) { + dev_kfree_skb_any(skb); + return NULL; + } + + skb2 = skb_copy_expand(skb, ncb_len, + max_size - skb->len - ncb_len - crc_len, + GFP_ATOMIC); + dev_kfree_skb_any(skb); + if (!skb2) + return NULL; + + skb = skb2; + + tmp = (void *) skb_push(skb, ncb_len); + memset(tmp, 0, ncb_len); + + put_unaligned_le32(opts->nth_sign, tmp); /* dwSignature */ + tmp += 2; + /* wHeaderLength */ + put_unaligned_le16(opts->nth_size, tmp++); + tmp++; /* skip wSequence */ + put_ncm(&tmp, opts->block_length, skb->len); /* (d)wBlockLength */ + /* (d)wFpIndex */ + /* the first pointer is right after the NTH + align */ + put_ncm(&tmp, opts->fp_index, opts->nth_size + ndp_pad); + + tmp = (void *)tmp + ndp_pad; + + /* NDP */ + put_unaligned_le32(ncm->ndp_sign, tmp); /* dwSignature */ + tmp += 2; + /* wLength */ + put_unaligned_le16(ncb_len - opts->nth_size - pad, tmp++); + + tmp += opts->reserved1; + tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */ + tmp += opts->reserved2; + + if (ncm->is_crc) { + uint32_t crc; + + crc = ~crc32_le(~0, + skb->data + ncb_len, + skb->len - ncb_len); + put_unaligned_le32(crc, skb->data + skb->len); + skb_put(skb, crc_len); + } + + /* (d)wDatagramIndex[0] */ + put_ncm(&tmp, opts->dgram_item_len, ncb_len); + /* (d)wDatagramLength[0] */ + put_ncm(&tmp, opts->dgram_item_len, skb->len - ncb_len); + /* (d)wDatagramIndex[1] and (d)wDatagramLength[1] already zeroed */ + + if (skb->len > MAX_TX_NONFIXED) + memset(skb_put(skb, max_size - skb->len), + 0, max_size - skb->len); + + return skb; +} + +static int ncm_unwrap_ntb(struct gether *port, + struct sk_buff *skb, + struct sk_buff_head *list) +{ + struct f_ncm *ncm = func_to_ncm(&port->func); + __le16 *tmp = (void *) skb->data; + unsigned index, index2; + unsigned dg_len, dg_len2; + unsigned ndp_len; + struct sk_buff *skb2; + int ret = -EINVAL; + unsigned max_size = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize); + const struct ndp_parser_opts *opts = ncm->parser_opts; + unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0; + int dgram_counter; + + /* dwSignature */ + if (get_unaligned_le32(tmp) != opts->nth_sign) { + INFO(port->func.config->cdev, "Wrong NTH SIGN, skblen %d\n", + skb->len); + print_hex_dump(KERN_INFO, "HEAD:", DUMP_PREFIX_ADDRESS, 32, 1, + skb->data, 32, false); + + goto err; + } + tmp += 2; + /* wHeaderLength */ + if (get_unaligned_le16(tmp++) != opts->nth_size) { + INFO(port->func.config->cdev, "Wrong NTB headersize\n"); + goto err; + } + tmp++; /* skip wSequence */ + + /* (d)wBlockLength */ + if (get_ncm(&tmp, opts->block_length) > max_size) { + INFO(port->func.config->cdev, "OUT size exceeded\n"); + goto err; + } + + index = get_ncm(&tmp, opts->fp_index); + /* NCM 3.2 */ + if (((index % 4) != 0) && (index < opts->nth_size)) { + INFO(port->func.config->cdev, "Bad index: %x\n", + index); + goto err; + } + + /* walk through NDP */ + tmp = ((void *)skb->data) + index; + if (get_unaligned_le32(tmp) != ncm->ndp_sign) { + INFO(port->func.config->cdev, "Wrong NDP SIGN\n"); + goto err; + } + tmp += 2; + + ndp_len = get_unaligned_le16(tmp++); + /* + * NCM 3.3.1 + * entry is 2 items + * item size is 16/32 bits, opts->dgram_item_len * 2 bytes + * minimal: struct usb_cdc_ncm_ndpX + normal entry + zero entry + */ + if ((ndp_len < opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2)) + || (ndp_len % opts->ndplen_align != 0)) { + INFO(port->func.config->cdev, "Bad NDP length: %x\n", ndp_len); + goto err; + } + tmp += opts->reserved1; + tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */ + tmp += opts->reserved2; + + ndp_len -= opts->ndp_size; + index2 = get_ncm(&tmp, opts->dgram_item_len); + dg_len2 = get_ncm(&tmp, opts->dgram_item_len); + dgram_counter = 0; + + do { + index = index2; + dg_len = dg_len2; + if (dg_len < 14 + crc_len) { /* ethernet header + crc */ + INFO(port->func.config->cdev, "Bad dgram length: %x\n", + dg_len); + goto err; + } + if (ncm->is_crc) { + uint32_t crc, crc2; + + crc = get_unaligned_le32(skb->data + + index + dg_len - crc_len); + crc2 = ~crc32_le(~0, + skb->data + index, + dg_len - crc_len); + if (crc != crc2) { + INFO(port->func.config->cdev, "Bad CRC\n"); + goto err; + } + } + + index2 = get_ncm(&tmp, opts->dgram_item_len); + dg_len2 = get_ncm(&tmp, opts->dgram_item_len); + + if (index2 == 0 || dg_len2 == 0) { + skb2 = skb; + } else { + skb2 = skb_clone(skb, GFP_ATOMIC); + if (skb2 == NULL) + goto err; + } + + if (!skb_pull(skb2, index)) { + ret = -EOVERFLOW; + goto err; + } + + skb_trim(skb2, dg_len - crc_len); + skb_queue_tail(list, skb2); + + ndp_len -= 2 * (opts->dgram_item_len * 2); + + dgram_counter++; + + if (index2 == 0 || dg_len2 == 0) + break; + } while (ndp_len > 2 * (opts->dgram_item_len * 2)); /* zero entry */ + + VDBG(port->func.config->cdev, + "Parsed NTB with %d frames\n", dgram_counter); + return 0; +err: + skb_queue_purge(list); + dev_kfree_skb_any(skb); + return ret; +} + +static void ncm_disable(struct usb_function *f) +{ + struct f_ncm *ncm = func_to_ncm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "ncm deactivated\n"); + + if (ncm->port.in_ep->driver_data) + gether_disconnect(&ncm->port); + + if (ncm->notify->driver_data) { + usb_ep_disable(ncm->notify); + ncm->notify->driver_data = NULL; + ncm->notify->desc = NULL; + } +} + +/*-------------------------------------------------------------------------*/ + +/* + * Callbacks let us notify the host about connect/disconnect when the + * net device is opened or closed. + * + * For testing, note that link states on this side include both opened + * and closed variants of: + * + * - disconnected/unconfigured + * - configured but inactive (data alt 0) + * - configured and active (data alt 1) + * + * Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and + * SET_INTERFACE (altsetting). Remember also that "configured" doesn't + * imply the host is actually polling the notification endpoint, and + * likewise that "active" doesn't imply it's actually using the data + * endpoints for traffic. + */ + +static void ncm_open(struct gether *geth) +{ + struct f_ncm *ncm = func_to_ncm(&geth->func); + + DBG(ncm->port.func.config->cdev, "%s\n", __func__); + + spin_lock(&ncm->lock); + ncm->is_open = true; + ncm_notify(ncm); + spin_unlock(&ncm->lock); +} + +static void ncm_close(struct gether *geth) +{ + struct f_ncm *ncm = func_to_ncm(&geth->func); + + DBG(ncm->port.func.config->cdev, "%s\n", __func__); + + spin_lock(&ncm->lock); + ncm->is_open = false; + ncm_notify(ncm); + spin_unlock(&ncm->lock); +} + +/*-------------------------------------------------------------------------*/ + +/* ethernet function driver setup/binding */ + +static int __init +ncm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_ncm *ncm = func_to_ncm(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ncm->ctrl_id = status; + ncm_iad_desc.bFirstInterface = status; + + ncm_control_intf.bInterfaceNumber = status; + ncm_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ncm->data_id = status; + + ncm_data_nop_intf.bInterfaceNumber = status; + ncm_data_intf.bInterfaceNumber = status; + ncm_union_desc.bSlaveInterface0 = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc); + if (!ep) + goto fail; + ncm->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc); + if (!ep) + goto fail; + ncm->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc); + if (!ep) + goto fail; + ncm->notify = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + ncm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!ncm->notify_req) + goto fail; + ncm->notify_req->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!ncm->notify_req->buf) + goto fail; + ncm->notify_req->context = ncm; + ncm->notify_req->complete = ncm_notify_complete; + + /* + * support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + hs_ncm_in_desc.bEndpointAddress = fs_ncm_in_desc.bEndpointAddress; + hs_ncm_out_desc.bEndpointAddress = fs_ncm_out_desc.bEndpointAddress; + hs_ncm_notify_desc.bEndpointAddress = + fs_ncm_notify_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function, + NULL); + /* + * NOTE: all that is done without knowing or caring about + * the network link ... which is unavailable to this code + * until we're activated via set_alt(). + */ + + ncm->port.open = ncm_open; + ncm->port.close = ncm_close; + + DBG(cdev, "CDC Network: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + ncm->port.in_ep->name, ncm->port.out_ep->name, + ncm->notify->name); + return 0; + +fail: + usb_free_all_descriptors(f); + if (ncm->notify_req) { + kfree(ncm->notify_req->buf); + usb_ep_free_request(ncm->notify, ncm->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (ncm->notify) + ncm->notify->driver_data = NULL; + if (ncm->port.out_ep) + ncm->port.out_ep->driver_data = NULL; + if (ncm->port.in_ep) + ncm->port.in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +ncm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ncm *ncm = func_to_ncm(f); + + DBG(c->cdev, "ncm unbind\n"); + + ncm_string_defs[0].id = 0; + usb_free_all_descriptors(f); + + kfree(ncm->notify_req->buf); + usb_ep_free_request(ncm->notify, ncm->notify_req); + + kfree(ncm); +} + +/** + * ncm_bind_config - add CDC Network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + struct eth_dev *dev) +{ + struct f_ncm *ncm; + int status; + + if (!can_support_ecm(c->cdev->gadget) || !ethaddr) + return -EINVAL; + + if (ncm_string_defs[0].id == 0) { + status = usb_string_ids_tab(c->cdev, ncm_string_defs); + if (status < 0) + return status; + ncm_control_intf.iInterface = + ncm_string_defs[STRING_CTRL_IDX].id; + + status = ncm_string_defs[STRING_DATA_IDX].id; + ncm_data_nop_intf.iInterface = status; + ncm_data_intf.iInterface = status; + + ecm_desc.iMACAddress = ncm_string_defs[STRING_MAC_IDX].id; + ncm_iad_desc.iFunction = ncm_string_defs[STRING_IAD_IDX].id; + } + + /* allocate and initialize one new instance */ + ncm = kzalloc(sizeof *ncm, GFP_KERNEL); + if (!ncm) + return -ENOMEM; + + /* export host's Ethernet address in CDC format */ + snprintf(ncm->ethaddr, sizeof ncm->ethaddr, "%pm", ethaddr); + ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr; + + spin_lock_init(&ncm->lock); + ncm_reset_values(ncm); + ncm->port.ioport = dev; + ncm->port.is_fixed = true; + + ncm->port.func.name = "cdc_network"; + ncm->port.func.strings = ncm_strings; + /* descriptors are per-instance copies */ + ncm->port.func.bind = ncm_bind; + ncm->port.func.unbind = ncm_unbind; + ncm->port.func.set_alt = ncm_set_alt; + ncm->port.func.get_alt = ncm_get_alt; + ncm->port.func.setup = ncm_setup; + ncm->port.func.disable = ncm_disable; + + ncm->port.wrap = ncm_wrap_ntb; + ncm->port.unwrap = ncm_unwrap_ntb; + + status = usb_add_function(c, &ncm->port.func); + if (status) + kfree(ncm); + return status; +} diff --git a/drivers/usb/gadget/f_obex.c b/drivers/usb/gadget/f_obex.c new file mode 100644 index 00000000..8aa2be53 --- /dev/null +++ b/drivers/usb/gadget/f_obex.c @@ -0,0 +1,592 @@ +/* + * f_obex.c -- USB CDC OBEX function driver + * + * Copyright (C) 2008 Nokia Corporation + * Contact: Felipe Balbi + * + * Based on f_acm.c by Al Borchers and David Brownell. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include + +#include "u_serial.h" +#include "gadget_chips.h" + + +/* + * This CDC OBEX function support just packages a TTY-ish byte stream. + * A user mode server will put it into "raw" mode and handle all the + * relevant protocol details ... this is just a kernel passthrough. + * When possible, we prevent gadget enumeration until that server is + * ready to handle the commands. + */ + +struct f_obex { + struct gserial port; + u8 ctrl_id; + u8 data_id; + u8 port_num; + u8 can_activate; +}; + +static inline struct f_obex *func_to_obex(struct usb_function *f) +{ + return container_of(f, struct f_obex, port.func); +} + +static inline struct f_obex *port_to_obex(struct gserial *p) +{ + return container_of(p, struct f_obex, port); +} + +/*-------------------------------------------------------------------------*/ + +#define OBEX_CTRL_IDX 0 +#define OBEX_DATA_IDX 1 + +static struct usb_string obex_string_defs[] = { + [OBEX_CTRL_IDX].s = "CDC Object Exchange (OBEX)", + [OBEX_DATA_IDX].s = "CDC OBEX Data", + { }, /* end of list */ +}; + +static struct usb_gadget_strings obex_string_table = { + .language = 0x0409, /* en-US */ + .strings = obex_string_defs, +}; + +static struct usb_gadget_strings *obex_strings[] = { + &obex_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static struct usb_interface_descriptor obex_control_intf = { + .bLength = sizeof(obex_control_intf), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_OBEX, +}; + +static struct usb_interface_descriptor obex_data_nop_intf = { + .bLength = sizeof(obex_data_nop_intf), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 1, + + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, +}; + +static struct usb_interface_descriptor obex_data_intf = { + .bLength = sizeof(obex_data_intf), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 2, + + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, +}; + +static struct usb_cdc_header_desc obex_cdc_header_desc = { + .bLength = sizeof(obex_cdc_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = cpu_to_le16(0x0120), +}; + +static struct usb_cdc_union_desc obex_cdc_union_desc = { + .bLength = sizeof(obex_cdc_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + .bMasterInterface0 = 1, + .bSlaveInterface0 = 2, +}; + +static struct usb_cdc_obex_desc obex_desc = { + .bLength = sizeof(obex_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_OBEX_TYPE, + .bcdVersion = cpu_to_le16(0x0100), +}; + +/* High-Speed Support */ + +static struct usb_endpoint_descriptor obex_hs_ep_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor obex_hs_ep_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *hs_function[] = { + (struct usb_descriptor_header *) &obex_control_intf, + (struct usb_descriptor_header *) &obex_cdc_header_desc, + (struct usb_descriptor_header *) &obex_desc, + (struct usb_descriptor_header *) &obex_cdc_union_desc, + + (struct usb_descriptor_header *) &obex_data_nop_intf, + (struct usb_descriptor_header *) &obex_data_intf, + (struct usb_descriptor_header *) &obex_hs_ep_in_desc, + (struct usb_descriptor_header *) &obex_hs_ep_out_desc, + NULL, +}; + +/* Full-Speed Support */ + +static struct usb_endpoint_descriptor obex_fs_ep_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor obex_fs_ep_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_function[] = { + (struct usb_descriptor_header *) &obex_control_intf, + (struct usb_descriptor_header *) &obex_cdc_header_desc, + (struct usb_descriptor_header *) &obex_desc, + (struct usb_descriptor_header *) &obex_cdc_union_desc, + + (struct usb_descriptor_header *) &obex_data_nop_intf, + (struct usb_descriptor_header *) &obex_data_intf, + (struct usb_descriptor_header *) &obex_fs_ep_in_desc, + (struct usb_descriptor_header *) &obex_fs_ep_out_desc, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int obex_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_obex *obex = func_to_obex(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (intf == obex->ctrl_id) { + if (alt != 0) + goto fail; + /* NOP */ + DBG(cdev, "reset obex ttyGS%d control\n", obex->port_num); + + } else if (intf == obex->data_id) { + if (alt > 1) + goto fail; + + if (obex->port.in->driver_data) { + DBG(cdev, "reset obex ttyGS%d\n", obex->port_num); + gserial_disconnect(&obex->port); + } + + if (!obex->port.in->desc || !obex->port.out->desc) { + DBG(cdev, "init obex ttyGS%d\n", obex->port_num); + if (config_ep_by_speed(cdev->gadget, f, + obex->port.in) || + config_ep_by_speed(cdev->gadget, f, + obex->port.out)) { + obex->port.out->desc = NULL; + obex->port.in->desc = NULL; + goto fail; + } + } + + if (alt == 1) { + DBG(cdev, "activate obex ttyGS%d\n", obex->port_num); + gserial_connect(&obex->port, obex->port_num); + } + + } else + goto fail; + + return 0; + +fail: + return -EINVAL; +} + +static int obex_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_obex *obex = func_to_obex(f); + + if (intf == obex->ctrl_id) + return 0; + + return obex->port.in->driver_data ? 1 : 0; +} + +static void obex_disable(struct usb_function *f) +{ + struct f_obex *obex = func_to_obex(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "obex ttyGS%d disable\n", obex->port_num); + gserial_disconnect(&obex->port); +} + +/*-------------------------------------------------------------------------*/ + +static void obex_connect(struct gserial *g) +{ + struct f_obex *obex = port_to_obex(g); + struct usb_composite_dev *cdev = g->func.config->cdev; + int status; + + if (!obex->can_activate) + return; + + status = usb_function_activate(&g->func); + if (status) + DBG(cdev, "obex ttyGS%d function activate --> %d\n", + obex->port_num, status); +} + +static void obex_disconnect(struct gserial *g) +{ + struct f_obex *obex = port_to_obex(g); + struct usb_composite_dev *cdev = g->func.config->cdev; + int status; + + if (!obex->can_activate) + return; + + status = usb_function_deactivate(&g->func); + if (status) + DBG(cdev, "obex ttyGS%d function deactivate --> %d\n", + obex->port_num, status); +} + +/*-------------------------------------------------------------------------*/ + +/* Some controllers can't support CDC OBEX ... */ +static inline bool can_support_obex(struct usb_configuration *c) +{ + /* Since the first interface is a NOP, we can ignore the + * issue of multi-interface support on most controllers. + * + * Altsettings are mandatory, however... + */ + if (!gadget_supports_altsettings(c->cdev->gadget)) + return false; + + /* everything else is *probably* fine ... */ + return true; +} + +static int obex_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_obex *obex = func_to_obex(f); + int status; + struct usb_ep *ep; + + if (!can_support_obex(c)) + return -EINVAL; + + if (obex_string_defs[OBEX_CTRL_IDX].id == 0) { + status = usb_string_ids_tab(c->cdev, obex_string_defs); + if (status < 0) + return status; + obex_control_intf.iInterface = + obex_string_defs[OBEX_CTRL_IDX].id; + + status = obex_string_defs[OBEX_DATA_IDX].id; + obex_data_nop_intf.iInterface = status; + obex_data_intf.iInterface = status; + } + + /* allocate instance-specific interface IDs, and patch descriptors */ + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + obex->ctrl_id = status; + + obex_control_intf.bInterfaceNumber = status; + obex_cdc_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + obex->data_id = status; + + obex_data_nop_intf.bInterfaceNumber = status; + obex_data_intf.bInterfaceNumber = status; + obex_cdc_union_desc.bSlaveInterface0 = status; + + /* allocate instance-specific endpoints */ + + status = -ENODEV; + ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_in_desc); + if (!ep) + goto fail; + obex->port.in = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_out_desc); + if (!ep) + goto fail; + obex->port.out = ep; + ep->driver_data = cdev; /* claim */ + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + + obex_hs_ep_in_desc.bEndpointAddress = + obex_fs_ep_in_desc.bEndpointAddress; + obex_hs_ep_out_desc.bEndpointAddress = + obex_fs_ep_out_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, fs_function, hs_function, NULL); + if (status) + goto fail; + + /* Avoid letting this gadget enumerate until the userspace + * OBEX server is active. + */ + status = usb_function_deactivate(f); + if (status < 0) + WARNING(cdev, "obex ttyGS%d: can't prevent enumeration, %d\n", + obex->port_num, status); + else + obex->can_activate = true; + + + DBG(cdev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n", + obex->port_num, + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + obex->port.in->name, obex->port.out->name); + + return 0; + +fail: + usb_free_all_descriptors(f); + /* we might as well release our claims on endpoints */ + if (obex->port.out) + obex->port.out->driver_data = NULL; + if (obex->port.in) + obex->port.in->driver_data = NULL; + + ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); + + return status; +} + +#ifdef USBF_OBEX_INCLUDED + +static void +obex_old_unbind(struct usb_configuration *c, struct usb_function *f) +{ + obex_string_defs[OBEX_CTRL_IDX].id = 0; + usb_free_all_descriptors(f); + kfree(func_to_obex(f)); +} + +/** + * obex_bind_config - add a CDC OBEX function to a configuration + * @c: the configuration to support the CDC OBEX instance + * @port_num: /dev/ttyGS* port this interface will use + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + */ +int __init obex_bind_config(struct usb_configuration *c, u8 port_num) +{ + struct f_obex *obex; + int status; + + /* allocate and initialize one new instance */ + obex = kzalloc(sizeof *obex, GFP_KERNEL); + if (!obex) + return -ENOMEM; + + obex->port_num = port_num; + + obex->port.connect = obex_connect; + obex->port.disconnect = obex_disconnect; + + obex->port.func.name = "obex"; + obex->port.func.strings = obex_strings; + /* descriptors are per-instance copies */ + obex->port.func.bind = obex_bind; + obex->port.func.unbind = obex_old_unbind; + obex->port.func.set_alt = obex_set_alt; + obex->port.func.get_alt = obex_get_alt; + obex->port.func.disable = obex_disable; + + status = usb_add_function(c, &obex->port.func); + if (status) + kfree(obex); + + return status; +} + +#else + +static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_serial_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_serial_opts); +static ssize_t f_obex_attr_show(struct config_item *item, + struct configfs_attribute *attr, + char *page) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + struct f_serial_opts_attribute *f_serial_opts_attr = + container_of(attr, struct f_serial_opts_attribute, attr); + ssize_t ret = 0; + + if (f_serial_opts_attr->show) + ret = f_serial_opts_attr->show(opts, page); + + return ret; +} + +static void obex_attr_release(struct config_item *item) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations obex_item_ops = { + .release = obex_attr_release, + .show_attribute = f_obex_attr_show, +}; + +static ssize_t f_obex_port_num_show(struct f_serial_opts *opts, char *page) +{ + return sprintf(page, "%u\n", opts->port_num); +} + +static struct f_serial_opts_attribute f_obex_port_num = + __CONFIGFS_ATTR_RO(port_num, f_obex_port_num_show); + +static struct configfs_attribute *acm_attrs[] = { + &f_obex_port_num.attr, + NULL, +}; + +static struct config_item_type obex_func_type = { + .ct_item_ops = &obex_item_ops, + .ct_attrs = acm_attrs, + .ct_owner = THIS_MODULE, +}; + +static void obex_free_inst(struct usb_function_instance *f) +{ + struct f_serial_opts *opts; + + opts = container_of(f, struct f_serial_opts, func_inst); + gserial_free_line(opts->port_num); + kfree(opts); +} + +static struct usb_function_instance *obex_alloc_inst(void) +{ + struct f_serial_opts *opts; + int ret; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.free_func_inst = obex_free_inst; + ret = gserial_alloc_line(&opts->port_num); + if (ret) { + kfree(opts); + return ERR_PTR(ret); + } + config_group_init_type_name(&opts->func_inst.group, "", + &obex_func_type); + + return &opts->func_inst; +} + +static void obex_free(struct usb_function *f) +{ + struct f_obex *obex; + + obex = func_to_obex(f); + kfree(obex); +} + +static void obex_unbind(struct usb_configuration *c, struct usb_function *f) +{ + obex_string_defs[OBEX_CTRL_IDX].id = 0; + usb_free_all_descriptors(f); +} + +struct usb_function *obex_alloc(struct usb_function_instance *fi) +{ + struct f_obex *obex; + struct f_serial_opts *opts; + + /* allocate and initialize one new instance */ + obex = kzalloc(sizeof(*obex), GFP_KERNEL); + if (!obex) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_serial_opts, func_inst); + + obex->port_num = opts->port_num; + + obex->port.connect = obex_connect; + obex->port.disconnect = obex_disconnect; + + obex->port.func.name = "obex"; + obex->port.func.strings = obex_strings; + /* descriptors are per-instance copies */ + obex->port.func.bind = obex_bind; + obex->port.func.unbind = obex_unbind; + obex->port.func.set_alt = obex_set_alt; + obex->port.func.get_alt = obex_get_alt; + obex->port.func.disable = obex_disable; + obex->port.func.free_func = obex_free; + + return &obex->port.func; +} + +DECLARE_USB_FUNCTION_INIT(obex, obex_alloc_inst, obex_alloc); + +#endif + +MODULE_AUTHOR("Felipe Balbi"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_phonet.c b/drivers/usb/gadget/f_phonet.c new file mode 100644 index 00000000..b21ab558 --- /dev/null +++ b/drivers/usb/gadget/f_phonet.c @@ -0,0 +1,634 @@ +/* + * f_phonet.c -- USB CDC Phonet function + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Author: Rémi Denis-Courmont + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "u_phonet.h" + +#define PN_MEDIA_USB 0x1B +#define MAXPACKET 512 +#if (PAGE_SIZE % MAXPACKET) +#error MAXPACKET must divide PAGE_SIZE! +#endif + +/*-------------------------------------------------------------------------*/ + +struct phonet_port { + struct f_phonet *usb; + spinlock_t lock; +}; + +struct f_phonet { + struct usb_function function; + struct { + struct sk_buff *skb; + spinlock_t lock; + } rx; + struct net_device *dev; + struct usb_ep *in_ep, *out_ep; + + struct usb_request *in_req; + struct usb_request *out_reqv[0]; +}; + +static int phonet_rxq_size = 17; + +static inline struct f_phonet *func_to_pn(struct usb_function *f) +{ + return container_of(f, struct f_phonet, function); +} + +/*-------------------------------------------------------------------------*/ + +#define USB_CDC_SUBCLASS_PHONET 0xfe +#define USB_CDC_PHONET_TYPE 0xab + +static struct usb_interface_descriptor +pn_control_intf_desc = { + .bLength = sizeof pn_control_intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC, */ + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_PHONET, +}; + +static const struct usb_cdc_header_desc +pn_header_desc = { + .bLength = sizeof pn_header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = cpu_to_le16(0x0110), +}; + +static const struct usb_cdc_header_desc +pn_phonet_desc = { + .bLength = sizeof pn_phonet_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_PHONET_TYPE, + .bcdCDC = cpu_to_le16(0x1505), /* ??? */ +}; + +static struct usb_cdc_union_desc +pn_union_desc = { + .bLength = sizeof pn_union_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + + /* .bMasterInterface0 = DYNAMIC, */ + /* .bSlaveInterface0 = DYNAMIC, */ +}; + +static struct usb_interface_descriptor +pn_data_nop_intf_desc = { + .bLength = sizeof pn_data_nop_intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC, */ + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, +}; + +static struct usb_interface_descriptor +pn_data_intf_desc = { + .bLength = sizeof pn_data_intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC, */ + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, +}; + +static struct usb_endpoint_descriptor +pn_fs_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor +pn_hs_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(MAXPACKET), +}; + +static struct usb_endpoint_descriptor +pn_fs_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor +pn_hs_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *fs_pn_function[] = { + (struct usb_descriptor_header *) &pn_control_intf_desc, + (struct usb_descriptor_header *) &pn_header_desc, + (struct usb_descriptor_header *) &pn_phonet_desc, + (struct usb_descriptor_header *) &pn_union_desc, + (struct usb_descriptor_header *) &pn_data_nop_intf_desc, + (struct usb_descriptor_header *) &pn_data_intf_desc, + (struct usb_descriptor_header *) &pn_fs_sink_desc, + (struct usb_descriptor_header *) &pn_fs_source_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_pn_function[] = { + (struct usb_descriptor_header *) &pn_control_intf_desc, + (struct usb_descriptor_header *) &pn_header_desc, + (struct usb_descriptor_header *) &pn_phonet_desc, + (struct usb_descriptor_header *) &pn_union_desc, + (struct usb_descriptor_header *) &pn_data_nop_intf_desc, + (struct usb_descriptor_header *) &pn_data_intf_desc, + (struct usb_descriptor_header *) &pn_hs_sink_desc, + (struct usb_descriptor_header *) &pn_hs_source_desc, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int pn_net_open(struct net_device *dev) +{ + netif_wake_queue(dev); + return 0; +} + +static int pn_net_close(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +static void pn_tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_phonet *fp = ep->driver_data; + struct net_device *dev = fp->dev; + struct sk_buff *skb = req->context; + + switch (req->status) { + case 0: + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + break; + + case -ESHUTDOWN: /* disconnected */ + case -ECONNRESET: /* disabled */ + dev->stats.tx_aborted_errors++; + default: + dev->stats.tx_errors++; + } + + dev_kfree_skb_any(skb); + netif_wake_queue(dev); +} + +static int pn_net_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct phonet_port *port = netdev_priv(dev); + struct f_phonet *fp; + struct usb_request *req; + unsigned long flags; + + if (skb->protocol != htons(ETH_P_PHONET)) + goto out; + + spin_lock_irqsave(&port->lock, flags); + fp = port->usb; + if (unlikely(!fp)) /* race with carrier loss */ + goto out_unlock; + + req = fp->in_req; + req->buf = skb->data; + req->length = skb->len; + req->complete = pn_tx_complete; + req->zero = 1; + req->context = skb; + + if (unlikely(usb_ep_queue(fp->in_ep, req, GFP_ATOMIC))) + goto out_unlock; + + netif_stop_queue(dev); + skb = NULL; + +out_unlock: + spin_unlock_irqrestore(&port->lock, flags); +out: + if (unlikely(skb)) { + dev_kfree_skb(skb); + dev->stats.tx_dropped++; + } + return NETDEV_TX_OK; +} + +static int pn_net_mtu(struct net_device *dev, int new_mtu) +{ + if ((new_mtu < PHONET_MIN_MTU) || (new_mtu > PHONET_MAX_MTU)) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} + +static const struct net_device_ops pn_netdev_ops = { + .ndo_open = pn_net_open, + .ndo_stop = pn_net_close, + .ndo_start_xmit = pn_net_xmit, + .ndo_change_mtu = pn_net_mtu, +}; + +static void pn_net_setup(struct net_device *dev) +{ + dev->features = 0; + dev->type = ARPHRD_PHONET; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->mtu = PHONET_DEV_MTU; + dev->hard_header_len = 1; + dev->dev_addr[0] = PN_MEDIA_USB; + dev->addr_len = 1; + dev->tx_queue_len = 1; + + dev->netdev_ops = &pn_netdev_ops; + dev->destructor = free_netdev; + dev->header_ops = &phonet_header_ops; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Queue buffer for data from the host + */ +static int +pn_rx_submit(struct f_phonet *fp, struct usb_request *req, gfp_t gfp_flags) +{ + struct page *page; + int err; + + page = __skb_alloc_page(gfp_flags | __GFP_NOMEMALLOC, NULL); + if (!page) + return -ENOMEM; + + req->buf = page_address(page); + req->length = PAGE_SIZE; + req->context = page; + + err = usb_ep_queue(fp->out_ep, req, gfp_flags); + if (unlikely(err)) + put_page(page); + return err; +} + +static void pn_rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_phonet *fp = ep->driver_data; + struct net_device *dev = fp->dev; + struct page *page = req->context; + struct sk_buff *skb; + unsigned long flags; + int status = req->status; + + switch (status) { + case 0: + spin_lock_irqsave(&fp->rx.lock, flags); + skb = fp->rx.skb; + if (!skb) + skb = fp->rx.skb = netdev_alloc_skb(dev, 12); + if (req->actual < req->length) /* Last fragment */ + fp->rx.skb = NULL; + spin_unlock_irqrestore(&fp->rx.lock, flags); + + if (unlikely(!skb)) + break; + + if (skb->len == 0) { /* First fragment */ + skb->protocol = htons(ETH_P_PHONET); + skb_reset_mac_header(skb); + /* Can't use pskb_pull() on page in IRQ */ + memcpy(skb_put(skb, 1), page_address(page), 1); + } + + skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, + skb->len <= 1, req->actual, PAGE_SIZE); + page = NULL; + + if (req->actual < req->length) { /* Last fragment */ + skb->dev = dev; + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + + netif_rx(skb); + } + break; + + /* Do not resubmit in these cases: */ + case -ESHUTDOWN: /* disconnect */ + case -ECONNABORTED: /* hw reset */ + case -ECONNRESET: /* dequeued (unlink or netif down) */ + req = NULL; + break; + + /* Do resubmit in these cases: */ + case -EOVERFLOW: /* request buffer overflow */ + dev->stats.rx_over_errors++; + default: + dev->stats.rx_errors++; + break; + } + + if (page) + put_page(page); + if (req) + pn_rx_submit(fp, req, GFP_ATOMIC | __GFP_COLD); +} + +/*-------------------------------------------------------------------------*/ + +static void __pn_reset(struct usb_function *f) +{ + struct f_phonet *fp = func_to_pn(f); + struct net_device *dev = fp->dev; + struct phonet_port *port = netdev_priv(dev); + + netif_carrier_off(dev); + port->usb = NULL; + + usb_ep_disable(fp->out_ep); + usb_ep_disable(fp->in_ep); + if (fp->rx.skb) { + dev_kfree_skb_irq(fp->rx.skb); + fp->rx.skb = NULL; + } +} + +static int pn_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_phonet *fp = func_to_pn(f); + struct usb_gadget *gadget = fp->function.config->cdev->gadget; + + if (intf == pn_control_intf_desc.bInterfaceNumber) + /* control interface, no altsetting */ + return (alt > 0) ? -EINVAL : 0; + + if (intf == pn_data_intf_desc.bInterfaceNumber) { + struct net_device *dev = fp->dev; + struct phonet_port *port = netdev_priv(dev); + + /* data intf (0: inactive, 1: active) */ + if (alt > 1) + return -EINVAL; + + spin_lock(&port->lock); + __pn_reset(f); + if (alt == 1) { + int i; + + if (config_ep_by_speed(gadget, f, fp->in_ep) || + config_ep_by_speed(gadget, f, fp->out_ep)) { + fp->in_ep->desc = NULL; + fp->out_ep->desc = NULL; + spin_unlock(&port->lock); + return -EINVAL; + } + usb_ep_enable(fp->out_ep); + usb_ep_enable(fp->in_ep); + + port->usb = fp; + fp->out_ep->driver_data = fp; + fp->in_ep->driver_data = fp; + + netif_carrier_on(dev); + for (i = 0; i < phonet_rxq_size; i++) + pn_rx_submit(fp, fp->out_reqv[i], GFP_ATOMIC | __GFP_COLD); + } + spin_unlock(&port->lock); + return 0; + } + + return -EINVAL; +} + +static int pn_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_phonet *fp = func_to_pn(f); + + if (intf == pn_control_intf_desc.bInterfaceNumber) + return 0; + + if (intf == pn_data_intf_desc.bInterfaceNumber) { + struct phonet_port *port = netdev_priv(fp->dev); + u8 alt; + + spin_lock(&port->lock); + alt = port->usb != NULL; + spin_unlock(&port->lock); + return alt; + } + + return -EINVAL; +} + +static void pn_disconnect(struct usb_function *f) +{ + struct f_phonet *fp = func_to_pn(f); + struct phonet_port *port = netdev_priv(fp->dev); + unsigned long flags; + + /* remain disabled until set_alt */ + spin_lock_irqsave(&port->lock, flags); + __pn_reset(f); + spin_unlock_irqrestore(&port->lock, flags); +} + +/*-------------------------------------------------------------------------*/ + +static __init +int pn_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct f_phonet *fp = func_to_pn(f); + struct usb_ep *ep; + int status, i; + + /* Reserve interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto err; + pn_control_intf_desc.bInterfaceNumber = status; + pn_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto err; + pn_data_nop_intf_desc.bInterfaceNumber = status; + pn_data_intf_desc.bInterfaceNumber = status; + pn_union_desc.bSlaveInterface0 = status; + + /* Reserve endpoints */ + status = -ENODEV; + ep = usb_ep_autoconfig(gadget, &pn_fs_sink_desc); + if (!ep) + goto err; + fp->out_ep = ep; + ep->driver_data = fp; /* Claim */ + + ep = usb_ep_autoconfig(gadget, &pn_fs_source_desc); + if (!ep) + goto err; + fp->in_ep = ep; + ep->driver_data = fp; /* Claim */ + + pn_hs_sink_desc.bEndpointAddress = pn_fs_sink_desc.bEndpointAddress; + pn_hs_source_desc.bEndpointAddress = pn_fs_source_desc.bEndpointAddress; + + /* Do not try to bind Phonet twice... */ + status = usb_assign_descriptors(f, fs_pn_function, hs_pn_function, + NULL); + if (status) + goto err; + + /* Incoming USB requests */ + status = -ENOMEM; + for (i = 0; i < phonet_rxq_size; i++) { + struct usb_request *req; + + req = usb_ep_alloc_request(fp->out_ep, GFP_KERNEL); + if (!req) + goto err_req; + + req->complete = pn_rx_complete; + fp->out_reqv[i] = req; + } + + /* Outgoing USB requests */ + fp->in_req = usb_ep_alloc_request(fp->in_ep, GFP_KERNEL); + if (!fp->in_req) + goto err_req; + + INFO(cdev, "USB CDC Phonet function\n"); + INFO(cdev, "using %s, OUT %s, IN %s\n", cdev->gadget->name, + fp->out_ep->name, fp->in_ep->name); + return 0; + +err_req: + for (i = 0; i < phonet_rxq_size && fp->out_reqv[i]; i++) + usb_ep_free_request(fp->out_ep, fp->out_reqv[i]); +err: + usb_free_all_descriptors(f); + if (fp->out_ep) + fp->out_ep->driver_data = NULL; + if (fp->in_ep) + fp->in_ep->driver_data = NULL; + ERROR(cdev, "USB CDC Phonet: cannot autoconfigure\n"); + return status; +} + +static void +pn_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_phonet *fp = func_to_pn(f); + int i; + + /* We are already disconnected */ + if (fp->in_req) + usb_ep_free_request(fp->in_ep, fp->in_req); + for (i = 0; i < phonet_rxq_size; i++) + if (fp->out_reqv[i]) + usb_ep_free_request(fp->out_ep, fp->out_reqv[i]); + + usb_free_all_descriptors(f); + kfree(fp); +} + +/*-------------------------------------------------------------------------*/ + +static struct net_device *dev; + +int __init phonet_bind_config(struct usb_configuration *c) +{ + struct f_phonet *fp; + int err, size; + + size = sizeof(*fp) + (phonet_rxq_size * sizeof(struct usb_request *)); + fp = kzalloc(size, GFP_KERNEL); + if (!fp) + return -ENOMEM; + + fp->dev = dev; + fp->function.name = "phonet"; + fp->function.bind = pn_bind; + fp->function.unbind = pn_unbind; + fp->function.set_alt = pn_set_alt; + fp->function.get_alt = pn_get_alt; + fp->function.disable = pn_disconnect; + spin_lock_init(&fp->rx.lock); + + err = usb_add_function(c, &fp->function); + if (err) + kfree(fp); + return err; +} + +int __init gphonet_setup(struct usb_gadget *gadget) +{ + struct phonet_port *port; + int err; + + /* Create net device */ + BUG_ON(dev); + dev = alloc_netdev(sizeof(*port), "upnlink%d", pn_net_setup); + if (!dev) + return -ENOMEM; + + port = netdev_priv(dev); + spin_lock_init(&port->lock); + netif_carrier_off(dev); + SET_NETDEV_DEV(dev, &gadget->dev); + + err = register_netdev(dev); + if (err) + free_netdev(dev); + return err; +} + +void gphonet_cleanup(void) +{ + unregister_netdev(dev); +} diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c new file mode 100644 index 00000000..500f8249 --- /dev/null +++ b/drivers/usb/gadget/f_rndis.c @@ -0,0 +1,896 @@ +/* + * f_rndis.c -- RNDIS link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (mina86@mina86.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include + +#include + +#include "u_ether.h" +#include "rndis.h" + + +/* + * This function is an RNDIS Ethernet port -- a Microsoft protocol that's + * been promoted instead of the standard CDC Ethernet. The published RNDIS + * spec is ambiguous, incomplete, and needlessly complex. Variants such as + * ActiveSync have even worse status in terms of specification. + * + * In short: it's a protocol controlled by (and for) Microsoft, not for an + * Open ecosystem or markets. Linux supports it *only* because Microsoft + * doesn't support the CDC Ethernet standard. + * + * The RNDIS data transfer model is complex, with multiple Ethernet packets + * per USB message, and out of band data. The control model is built around + * what's essentially an "RNDIS RPC" protocol. It's all wrapped in a CDC ACM + * (modem, not Ethernet) veneer, with those ACM descriptors being entirely + * useless (they're ignored). RNDIS expects to be the only function in its + * configuration, so it's no real help if you need composite devices; and + * it expects to be the first configuration too. + * + * There is a single technical advantage of RNDIS over CDC Ethernet, if you + * discount the fluff that its RPC can be made to deliver: it doesn't need + * a NOP altsetting for the data interface. That lets it work on some of the + * "so smart it's stupid" hardware which takes over configuration changes + * from the software, and adds restrictions like "no altsettings". + * + * Unfortunately MSFT's RNDIS drivers are buggy. They hang or oops, and + * have all sorts of contrary-to-specification oddities that can prevent + * them from working sanely. Since bugfixes (or accurate specs, letting + * Linux work around those bugs) are unlikely to ever come from MSFT, you + * may want to avoid using RNDIS on purely operational grounds. + * + * Omissions from the RNDIS 1.0 specification include: + * + * - Power management ... references data that's scattered around lots + * of other documentation, which is incorrect/incomplete there too. + * + * - There are various undocumented protocol requirements, like the need + * to send garbage in some control-OUT messages. + * + * - MS-Windows drivers sometimes emit undocumented requests. + */ + +struct f_rndis { + struct gether port; + u8 ctrl_id, data_id; + u8 ethaddr[ETH_ALEN]; + u32 vendorID; + const char *manufacturer; + int config; + + struct usb_ep *notify; + struct usb_request *notify_req; + atomic_t notify_count; +}; +static struct f_rndis *__rndis; + +static struct f_rndis *__rndis; + +static inline struct f_rndis *func_to_rndis(struct usb_function *f) +{ + return container_of(f, struct f_rndis, port.func); +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static unsigned int bitrate(struct usb_gadget *g) +{ + if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) + return 13 * 1024 * 8 * 1000 * 8; + else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +/*-------------------------------------------------------------------------*/ + +/* + */ + +#define RNDIS_STATUS_INTERVAL_MS 32 +#define STATUS_BYTECOUNT 8 /* 8 bytes data */ + + +/* interface descriptor: */ + +static struct usb_interface_descriptor rndis_control_intf = { + .bLength = sizeof rndis_control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + /* status endpoint is optional; this could be patched later */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc header_desc = { + .bLength = sizeof header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor = { + .bLength = sizeof call_mgmt_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + + .bmCapabilities = 0x00, + .bDataInterface = 0x01, +}; + +static struct usb_cdc_acm_descriptor rndis_acm_descriptor = { + .bLength = sizeof rndis_acm_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + + .bmCapabilities = 0x00, +}; + +static struct usb_cdc_union_desc rndis_union_desc = { + .bLength = sizeof(rndis_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +/* the data interface has two bulk endpoints */ + +static struct usb_interface_descriptor rndis_data_intf = { + .bLength = sizeof rndis_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + + +static struct usb_interface_assoc_descriptor +rndis_iad_descriptor = { + .bLength = sizeof rndis_iad_descriptor, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + .bFirstInterface = 0, /* XXX, hardcoded */ + .bInterfaceCount = 2, // control + data + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bFunctionProtocol = USB_CDC_PROTO_NONE, + /* .iFunction = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT), + .bInterval = RNDIS_STATUS_INTERVAL_MS, +}; + +static struct usb_endpoint_descriptor fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *eth_fs_function[] = { + (struct usb_descriptor_header *) &rndis_iad_descriptor, + + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_acm_descriptor, + (struct usb_descriptor_header *) &rndis_union_desc, + (struct usb_descriptor_header *) &fs_notify_desc, + + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &fs_in_desc, + (struct usb_descriptor_header *) &fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT), + .bInterval = USB_MS_TO_HS_INTERVAL(RNDIS_STATUS_INTERVAL_MS) +}; + +static struct usb_endpoint_descriptor hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *eth_hs_function[] = { + (struct usb_descriptor_header *) &rndis_iad_descriptor, + + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_acm_descriptor, + (struct usb_descriptor_header *) &rndis_union_desc, + (struct usb_descriptor_header *) &hs_notify_desc, + + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &hs_in_desc, + (struct usb_descriptor_header *) &hs_out_desc, + NULL, +}; + +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT), + .bInterval = USB_MS_TO_HS_INTERVAL(RNDIS_STATUS_INTERVAL_MS) +}; + +static struct usb_ss_ep_comp_descriptor ss_intr_comp_desc = { + .bLength = sizeof ss_intr_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ss_bulk_comp_desc = { + .bLength = sizeof ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *eth_ss_function[] = { + (struct usb_descriptor_header *) &rndis_iad_descriptor, + + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_acm_descriptor, + (struct usb_descriptor_header *) &rndis_union_desc, + (struct usb_descriptor_header *) &ss_notify_desc, + (struct usb_descriptor_header *) &ss_intr_comp_desc, + + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &ss_in_desc, + (struct usb_descriptor_header *) &ss_bulk_comp_desc, + (struct usb_descriptor_header *) &ss_out_desc, + (struct usb_descriptor_header *) &ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +static struct usb_string rndis_string_defs[] = { + [0].s = "RNDIS Communications Control", + [1].s = "RNDIS Ethernet Data", + [2].s = "RNDIS", + { } /* end of list */ +}; + +static struct usb_gadget_strings rndis_string_table = { + .language = 0x0409, /* en-us */ + .strings = rndis_string_defs, +}; + +static struct usb_gadget_strings *rndis_strings[] = { + &rndis_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static struct sk_buff *rndis_add_header(struct gether *port, + struct sk_buff *skb) +{ + struct sk_buff *skb2; + + skb2 = skb_realloc_headroom(skb, sizeof(struct rndis_packet_msg_type)); + if (skb2) + rndis_add_hdr(skb2); + + dev_kfree_skb_any(skb); + return skb2; +} + +static void rndis_response_available(void *_rndis) +{ + struct f_rndis *rndis = _rndis; + struct usb_request *req = rndis->notify_req; + struct usb_composite_dev *cdev = rndis->port.func.config->cdev; + __le32 *data = req->buf; + int status; + + if (atomic_inc_return(&rndis->notify_count) != 1) + return; + + /* Send RNDIS RESPONSE_AVAILABLE notification; a + * USB_CDC_NOTIFY_RESPONSE_AVAILABLE "should" work too + * + * This is the only notification defined by RNDIS. + */ + data[0] = cpu_to_le32(1); + data[1] = cpu_to_le32(0); + + status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&rndis->notify_count); + DBG(cdev, "notify/0 --> %d\n", status); + } +} + +static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rndis *rndis = req->context; + struct usb_composite_dev *cdev; + int status = req->status; + + if (!rndis->port.func.config || !rndis->port.func.config->cdev) + return; + else + cdev = rndis->port.func.config->cdev; + + /* after TX: + * - USB_CDC_GET_ENCAPSULATED_RESPONSE (ep0/control) + * - RNDIS_RESPONSE_AVAILABLE (status/irq) + */ + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + atomic_set(&rndis->notify_count, 0); + break; + default: + DBG(cdev, "RNDIS %s response error %d, %d/%d\n", + ep->name, status, + req->actual, req->length); + /* FALLTHROUGH */ + case 0: + if (ep != rndis->notify) + break; + + /* handle multiple pending RNDIS_RESPONSE_AVAILABLE + * notifications by resending until we're done + */ + if (atomic_dec_and_test(&rndis->notify_count)) + break; + status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&rndis->notify_count); + DBG(cdev, "notify/1 --> %d\n", status); + } + break; + } +} + +static void rndis_command_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rndis *rndis = req->context; + int status; + rndis_init_msg_type *buf; + + /* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */ +// spin_lock(&dev->lock); + status = rndis_msg_parser(rndis->config, (u8 *) req->buf); + if (status < 0) + pr_err("RNDIS command error %d, %d/%d\n", + status, req->actual, req->length); +// spin_unlock(&dev->lock); + buf = (rndis_init_msg_type *)req->buf; + + if (buf->MessageType == RNDIS_MSG_INIT) { + + if (buf->MaxTransferSize > 2048) + rndis->port.multi_pkt_xfer = 1; + else + rndis->port.multi_pkt_xfer = 0; + } + +} + +static int +rndis_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_rndis *rndis = func_to_rndis(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* RNDIS uses the CDC command encapsulation mechanism to implement + * an RPC scheme, with much getting/setting of attributes by OID. + */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (w_value || w_index != rndis->ctrl_id) + goto invalid; + /* read the request; process it later */ + value = w_length; + req->complete = rndis_command_complete; + req->context = rndis; + /* later, rndis_response_available() sends a notification */ + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (w_value || w_index != rndis->ctrl_id) + goto invalid; + else { + u8 *buf; + u32 n; + + /* return the result */ + buf = rndis_get_next_response(rndis->config, &n); + if (buf) { + memcpy(req->buf, buf, n); + req->complete = rndis_response_complete; + req->context = rndis; + rndis_free_response(rndis->config, buf); + value = n; + } + /* else stalls ... spec says to avoid that */ + } + break; + + default: +invalid: + VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "rndis req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (value < w_length); + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "rndis response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + + +static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_rndis *rndis = func_to_rndis(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt == 0 */ + + if (intf == rndis->ctrl_id) { + if (rndis->notify->driver_data) { + VDBG(cdev, "reset rndis control %d\n", intf); + usb_ep_disable(rndis->notify); + } + if (!rndis->notify->desc) { + VDBG(cdev, "init rndis ctrl %d\n", intf); + if (config_ep_by_speed(cdev->gadget, f, rndis->notify)) + goto fail; + } + usb_ep_enable(rndis->notify); + rndis->notify->driver_data = rndis; + + } else if (intf == rndis->data_id) { + struct net_device *net; + + if (rndis->port.in_ep->driver_data) { + DBG(cdev, "reset rndis\n"); + gether_disconnect(&rndis->port); + } + + if (!rndis->port.in_ep->desc || !rndis->port.out_ep->desc) { + DBG(cdev, "init rndis\n"); + if (config_ep_by_speed(cdev->gadget, f, + rndis->port.in_ep) || + config_ep_by_speed(cdev->gadget, f, + rndis->port.out_ep)) { + rndis->port.in_ep->desc = NULL; + rndis->port.out_ep->desc = NULL; + goto fail; + } + } + + /* Avoid ZLPs; they can be troublesome. */ + rndis->port.is_zlp_ok = false; + + /* RNDIS should be in the "RNDIS uninitialized" state, + * either never activated or after rndis_uninit(). + * + * We don't want data to flow here until a nonzero packet + * filter is set, at which point it enters "RNDIS data + * initialized" state ... but we do want the endpoints + * to be activated. It's a strange little state. + * + * REVISIT the RNDIS gadget code has done this wrong for a + * very long time. We need another call to the link layer + * code -- gether_updown(...bool) maybe -- to do it right. + */ + rndis->port.cdc_filter = 0; + + DBG(cdev, "RNDIS RX/TX early activation ... \n"); + net = gether_connect(&rndis->port); + if (IS_ERR(net)) + return PTR_ERR(net); + + rndis_set_param_dev(rndis->config, net, + &rndis->port.cdc_filter); + } else + goto fail; + + return 0; +fail: + return -EINVAL; +} + +static void rndis_disable(struct usb_function *f) +{ + struct f_rndis *rndis = func_to_rndis(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (!rndis->notify->driver_data) + return; + + DBG(cdev, "rndis deactivated\n"); + + rndis_uninit(rndis->config); + gether_disconnect(&rndis->port); + + usb_ep_disable(rndis->notify); + rndis->notify->driver_data = NULL; +} + +/*-------------------------------------------------------------------------*/ + +/* + * This isn't quite the same mechanism as CDC Ethernet, since the + * notification scheme passes less data, but the same set of link + * states must be tested. A key difference is that altsettings are + * not used to tell whether the link should send packets or not. + */ + +static void rndis_open(struct gether *geth) +{ + struct f_rndis *rndis = func_to_rndis(&geth->func); + struct usb_composite_dev *cdev = geth->func.config->cdev; + + DBG(cdev, "%s\n", __func__); + + rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, + bitrate(cdev->gadget) / 100); + rndis_signal_connect(rndis->config); +} + +static void rndis_close(struct gether *geth) +{ + struct f_rndis *rndis = func_to_rndis(&geth->func); + + DBG(geth->func.config->cdev, "%s\n", __func__); + + rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, 0); + rndis_signal_disconnect(rndis->config); +} + +/*-------------------------------------------------------------------------*/ + +/* ethernet function driver setup/binding */ + +static int +rndis_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_rndis *rndis = func_to_rndis(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + rndis->ctrl_id = status; + rndis_iad_descriptor.bFirstInterface = status; + + rndis_control_intf.bInterfaceNumber = status; + rndis_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + rndis->data_id = status; + + rndis_data_intf.bInterfaceNumber = status; + rndis_union_desc.bSlaveInterface0 = status; + + status = -ENODEV; + + /* NOTE: a status/notification endpoint is, strictly speaking, + * optional. We don't treat it that way though! It's simpler, + * and some newer profiles don't treat it as optional. + */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc); + if (!ep) + goto fail; + rndis->notify = ep; + ep->driver_data = cdev; /* claim */ + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc); + if (!ep) + goto fail; + rndis->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc); + if (!ep) + goto fail; + rndis->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!rndis->notify_req) + goto fail; + rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); + if (!rndis->notify_req->buf) + goto fail; + rndis->notify_req->length = STATUS_BYTECOUNT; + rndis->notify_req->context = rndis; + rndis->notify_req->complete = rndis_response_complete; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + hs_in_desc.bEndpointAddress = fs_in_desc.bEndpointAddress; + hs_out_desc.bEndpointAddress = fs_out_desc.bEndpointAddress; + hs_notify_desc.bEndpointAddress = fs_notify_desc.bEndpointAddress; + + ss_in_desc.bEndpointAddress = fs_in_desc.bEndpointAddress; + ss_out_desc.bEndpointAddress = fs_out_desc.bEndpointAddress; + ss_notify_desc.bEndpointAddress = fs_notify_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, eth_fs_function, eth_hs_function, + eth_ss_function); + if (status) + goto fail; + + rndis->port.open = rndis_open; + rndis->port.close = rndis_close; + + status = rndis_register(rndis_response_available, rndis); + if (status < 0) + goto fail; + rndis->config = status; + + rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, 0); + rndis_set_host_mac(rndis->config, rndis->ethaddr); + + if (rndis->manufacturer && rndis->vendorID && + rndis_set_param_vendor(rndis->config, rndis->vendorID, + rndis->manufacturer)) + goto fail; + + /* NOTE: all that is done without knowing or caring about + * the network link ... which is unavailable to this code + * until we're activated via set_alt(). + */ + + DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + rndis->port.in_ep->name, rndis->port.out_ep->name, + rndis->notify->name); + return 0; + +fail: + usb_free_all_descriptors(f); + + if (rndis->notify_req) { + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (rndis->notify) + rndis->notify->driver_data = NULL; + if (rndis->port.out_ep) + rndis->port.out_ep->driver_data = NULL; + if (rndis->port.in_ep) + rndis->port.in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +rndis_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rndis *rndis = func_to_rndis(f); + + rndis_deregister(rndis->config); + rndis_exit(); + + rndis_string_defs[0].id = 0; + usb_free_all_descriptors(f); + + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + + kfree(rndis); + __rndis = NULL; +} + +/* Some controllers can't support RNDIS ... */ +static inline bool can_support_rndis(struct usb_configuration *c) +{ + /* everything else is *presumably* fine */ + return true; +} + +int +rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer, struct eth_dev *dev) +{ + struct f_rndis *rndis; + int status; + + if (!can_support_rndis(c) || !ethaddr) + return -EINVAL; + + /* setup RNDIS itself */ + status = rndis_init(); + if (status < 0) + return status; + + if (rndis_string_defs[0].id == 0) { + status = usb_string_ids_tab(c->cdev, rndis_string_defs); + if (status) + return status; + + rndis_control_intf.iInterface = rndis_string_defs[0].id; + rndis_data_intf.iInterface = rndis_string_defs[1].id; + rndis_iad_descriptor.iFunction = rndis_string_defs[2].id; + } + + /* allocate and initialize one new instance */ + status = -ENOMEM; + rndis = kzalloc(sizeof *rndis, GFP_KERNEL); + if (!rndis) + goto fail; + + __rndis = rndis; + + memcpy(rndis->ethaddr, ethaddr, ETH_ALEN); + rndis->vendorID = vendorID; + rndis->manufacturer = manufacturer; + + rndis->port.ioport = dev; + /* RNDIS activates when the host changes this filter */ + rndis->port.cdc_filter = 0; + + /* RNDIS has special (and complex) framing */ + rndis->port.header_len = sizeof(struct rndis_packet_msg_type); + rndis->port.wrap = rndis_add_header; + rndis->port.unwrap = rndis_rm_hdr; + + rndis->port.func.name = "rndis"; + rndis->port.func.strings = rndis_strings; + /* descriptors are per-instance copies */ + rndis->port.func.bind = rndis_bind; + rndis->port.func.unbind = rndis_unbind; + rndis->port.func.set_alt = rndis_set_alt; + rndis->port.func.setup = rndis_setup; + rndis->port.func.disable = rndis_disable; + + status = usb_add_function(c, &rndis->port.func); + if (status) { + kfree(rndis); +fail: + rndis_exit(); + } + return status; +} diff --git a/drivers/usb/gadget/f_serial.c b/drivers/usb/gadget/f_serial.c new file mode 100644 index 00000000..2353c73a --- /dev/null +++ b/drivers/usb/gadget/f_serial.c @@ -0,0 +1,427 @@ +/* + * f_serial.c - generic USB serial function driver + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#include +#include +#include +#include +#include "u_serial.h" +#include "gadget_chips.h" + + +/* + * This function packages a simple "generic serial" port with no real + * control mechanisms, just raw data transfer over two bulk endpoints. + * + * Because it's not standardized, this isn't as interoperable as the + * CDC ACM driver. However, for many purposes it's just as functional + * if you can arrange appropriate host side drivers. + */ + +struct f_gser { + struct gserial port; + u8 data_id; + u8 port_num; +}; + +static inline struct f_gser *func_to_gser(struct usb_function *f) +{ + return container_of(f, struct f_gser, port.func); +} + +/*-------------------------------------------------------------------------*/ + +/* interface descriptor: */ + +static struct usb_interface_descriptor gser_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor gser_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor gser_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *gser_fs_function[] = { + (struct usb_descriptor_header *) &gser_interface_desc, + (struct usb_descriptor_header *) &gser_fs_in_desc, + (struct usb_descriptor_header *) &gser_fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor gser_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor gser_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *gser_hs_function[] = { + (struct usb_descriptor_header *) &gser_interface_desc, + (struct usb_descriptor_header *) &gser_hs_in_desc, + (struct usb_descriptor_header *) &gser_hs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor gser_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor gser_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor gser_ss_bulk_comp_desc = { + .bLength = sizeof gser_ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *gser_ss_function[] = { + (struct usb_descriptor_header *) &gser_interface_desc, + (struct usb_descriptor_header *) &gser_ss_in_desc, + (struct usb_descriptor_header *) &gser_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &gser_ss_out_desc, + (struct usb_descriptor_header *) &gser_ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +static struct usb_string gser_string_defs[] = { + [0].s = "Generic Serial", + { } /* end of list */ +}; + +static struct usb_gadget_strings gser_string_table = { + .language = 0x0409, /* en-us */ + .strings = gser_string_defs, +}; + +static struct usb_gadget_strings *gser_strings[] = { + &gser_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_gser *gser = func_to_gser(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt == 0, so this is an activation or a reset */ + + if (gser->port.in->driver_data) { + DBG(cdev, "reset generic ttyGS%d\n", gser->port_num); + gserial_disconnect(&gser->port); + } + if (!gser->port.in->desc || !gser->port.out->desc) { + DBG(cdev, "activate generic ttyGS%d\n", gser->port_num); + if (config_ep_by_speed(cdev->gadget, f, gser->port.in) || + config_ep_by_speed(cdev->gadget, f, gser->port.out)) { + gser->port.in->desc = NULL; + gser->port.out->desc = NULL; + return -EINVAL; + } + } + gserial_connect(&gser->port, gser->port_num); + + return 0; +} + +static void gser_disable(struct usb_function *f) +{ + struct f_gser *gser = func_to_gser(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "generic ttyGS%d deactivated\n", gser->port_num); + gserial_disconnect(&gser->port); +} + +/*-------------------------------------------------------------------------*/ + +/* serial function driver setup/binding */ + +static int gser_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_gser *gser = func_to_gser(f); + int status; + struct usb_ep *ep; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* maybe allocate device-global string ID */ + if (gser_string_defs[0].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + gser_string_defs[0].id = status; + } + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + gser->data_id = status; + gser_interface_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_in_desc); + if (!ep) + goto fail; + gser->port.in = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc); + if (!ep) + goto fail; + gser->port.out = ep; + ep->driver_data = cdev; /* claim */ + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + gser_hs_in_desc.bEndpointAddress = gser_fs_in_desc.bEndpointAddress; + gser_hs_out_desc.bEndpointAddress = gser_fs_out_desc.bEndpointAddress; + + gser_ss_in_desc.bEndpointAddress = gser_fs_in_desc.bEndpointAddress; + gser_ss_out_desc.bEndpointAddress = gser_fs_out_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, gser_fs_function, gser_hs_function, + gser_ss_function); + if (status) + goto fail; + DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n", + gser->port_num, + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + gser->port.in->name, gser->port.out->name); + return 0; + +fail: + /* we might as well release our claims on endpoints */ + if (gser->port.out) + gser->port.out->driver_data = NULL; + if (gser->port.in) + gser->port.in->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_serial_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_serial_opts); +static ssize_t f_serial_attr_show(struct config_item *item, + struct configfs_attribute *attr, + char *page) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + struct f_serial_opts_attribute *f_serial_opts_attr = + container_of(attr, struct f_serial_opts_attribute, attr); + ssize_t ret = 0; + + if (f_serial_opts_attr->show) + ret = f_serial_opts_attr->show(opts, page); + + return ret; +} + +static void serial_attr_release(struct config_item *item) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations serial_item_ops = { + .release = serial_attr_release, + .show_attribute = f_serial_attr_show, +}; + +static ssize_t f_serial_port_num_show(struct f_serial_opts *opts, char *page) +{ + return sprintf(page, "%u\n", opts->port_num); +} + +static struct f_serial_opts_attribute f_serial_port_num = + __CONFIGFS_ATTR_RO(port_num, f_serial_port_num_show); + +static struct configfs_attribute *acm_attrs[] = { + &f_serial_port_num.attr, + NULL, +}; + +static struct config_item_type serial_func_type = { + .ct_item_ops = &serial_item_ops, + .ct_attrs = acm_attrs, + .ct_owner = THIS_MODULE, +}; + +static void gser_free_inst(struct usb_function_instance *f) +{ + struct f_serial_opts *opts; + + opts = container_of(f, struct f_serial_opts, func_inst); + gserial_free_line(opts->port_num); + kfree(opts); +} + +static struct usb_function_instance *gser_alloc_inst(void) +{ + struct f_serial_opts *opts; + int ret; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.free_func_inst = gser_free_inst; + ret = gserial_alloc_line(&opts->port_num); + if (ret) { + kfree(opts); + return ERR_PTR(ret); + } + config_group_init_type_name(&opts->func_inst.group, "", + &serial_func_type); + + return &opts->func_inst; +} + +static void gser_free(struct usb_function *f) +{ + struct f_gser *serial; + + serial = func_to_gser(f); + kfree(serial); +} + +static void gser_unbind(struct usb_configuration *c, struct usb_function *f) +{ + usb_free_all_descriptors(f); +} +#ifdef CONFIG_USB_SPRD_DWC + +static void gser_setup_complete(struct usb_ep *ep, struct usb_request *req) +{ +} + +static int gser_setup(struct usb_composite_dev *cdev, const struct usb_ctrlrequest *ctrl) +{ + u16 w_length = le16_to_cpu(ctrl->wLength); + int value = -EOPNOTSUPP; + + DBG(cdev, "%s\n", __func__); + /* Handle Bulk-only class-specific requests */ + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + switch (ctrl->bRequest) { + case 0x22: + value = 0; + break; + } + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + cdev->req->complete = gser_setup_complete; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + printk("%s setup response queue error\n", __func__); + } + + if (value == -EOPNOTSUPP) + VDBG(cdev, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + le16_to_cpu(ctrl->wValue), le16_to_cpu(ctrl->wIndex), + le16_to_cpu(ctrl->wLength)); + return value; +} +#endif + +struct usb_function *gser_alloc(struct usb_function_instance *fi) +{ + struct f_gser *gser; + struct f_serial_opts *opts; + + /* allocate and initialize one new instance */ + gser = kzalloc(sizeof(*gser), GFP_KERNEL); + if (!gser) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_serial_opts, func_inst); + + gser->port_num = opts->port_num; + + gser->port.func.name = "gser"; + gser->port.func.strings = gser_strings; + gser->port.func.bind = gser_bind; + gser->port.func.unbind = gser_unbind; + gser->port.func.set_alt = gser_set_alt; + gser->port.func.disable = gser_disable; + gser->port.func.free_func = gser_free; + + return &gser->port.func; +} + +DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Al Borchers"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/f_sourcesink.c b/drivers/usb/gadget/f_sourcesink.c new file mode 100644 index 00000000..a8895859 --- /dev/null +++ b/drivers/usb/gadget/f_sourcesink.c @@ -0,0 +1,942 @@ +/* + * f_sourcesink.c - USB peripheral source/sink configuration driver + * + * Copyright (C) 2003-2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include + +#include "g_zero.h" +#include "gadget_chips.h" + +/* + * SOURCE/SINK FUNCTION ... a primary testing vehicle for USB peripheral + * controller drivers. + * + * This just sinks bulk packets OUT to the peripheral and sources them IN + * to the host, optionally with specific data patterns for integrity tests. + * As such it supports basic functionality and load tests. + * + * In terms of control messaging, this supports all the standard requests + * plus two that support control-OUT tests. If the optional "autoresume" + * mode is enabled, it provides good functional coverage for the "USBCV" + * test harness from USB-IF. + * + * Note that because this doesn't queue more than one request at a time, + * some other function must be used to test queueing logic. The network + * link (g_ether) is the best overall option for that, since its TX and RX + * queues are relatively independent, will receive a range of packet sizes, + * and can often be made to run out completely. Those issues are important + * when stress testing peripheral controller drivers. + * + * + * This is currently packaged as a configuration driver, which can't be + * combined with other functions to make composite devices. However, it + * can be combined with other independent configurations. + */ +struct f_sourcesink { + struct usb_function function; + + struct usb_ep *in_ep; + struct usb_ep *out_ep; + struct usb_ep *iso_in_ep; + struct usb_ep *iso_out_ep; + int cur_alt; +}; + +static inline struct f_sourcesink *func_to_ss(struct usb_function *f) +{ + return container_of(f, struct f_sourcesink, function); +} + +static unsigned pattern; +static unsigned isoc_interval; +static unsigned isoc_maxpacket; +static unsigned isoc_mult; +static unsigned isoc_maxburst; +static unsigned buflen; + +/*-------------------------------------------------------------------------*/ + +static struct usb_interface_descriptor source_sink_intf_alt0 = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_interface_descriptor source_sink_intf_alt1 = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + + .bAlternateSetting = 1, + .bNumEndpoints = 4, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_iso_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(1023), + .bInterval = 4, +}; + +static struct usb_endpoint_descriptor fs_iso_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(1023), + .bInterval = 4, +}; + +static struct usb_descriptor_header *fs_source_sink_descs[] = { + (struct usb_descriptor_header *) &source_sink_intf_alt0, + (struct usb_descriptor_header *) &fs_sink_desc, + (struct usb_descriptor_header *) &fs_source_desc, + (struct usb_descriptor_header *) &source_sink_intf_alt1, +#define FS_ALT_IFC_1_OFFSET 3 + (struct usb_descriptor_header *) &fs_sink_desc, + (struct usb_descriptor_header *) &fs_source_desc, + (struct usb_descriptor_header *) &fs_iso_sink_desc, + (struct usb_descriptor_header *) &fs_iso_source_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_iso_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(1024), + .bInterval = 4, +}; + +static struct usb_endpoint_descriptor hs_iso_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(1024), + .bInterval = 4, +}; + +static struct usb_descriptor_header *hs_source_sink_descs[] = { + (struct usb_descriptor_header *) &source_sink_intf_alt0, + (struct usb_descriptor_header *) &hs_source_desc, + (struct usb_descriptor_header *) &hs_sink_desc, + (struct usb_descriptor_header *) &source_sink_intf_alt1, +#define HS_ALT_IFC_1_OFFSET 3 + (struct usb_descriptor_header *) &hs_source_desc, + (struct usb_descriptor_header *) &hs_sink_desc, + (struct usb_descriptor_header *) &hs_iso_source_desc, + (struct usb_descriptor_header *) &hs_iso_sink_desc, + NULL, +}; + +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +struct usb_ss_ep_comp_descriptor ss_source_comp_desc = { + .bLength = USB_DT_SS_EP_COMP_SIZE, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = 0, +}; + +static struct usb_endpoint_descriptor ss_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +struct usb_ss_ep_comp_descriptor ss_sink_comp_desc = { + .bLength = USB_DT_SS_EP_COMP_SIZE, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = 0, +}; + +static struct usb_endpoint_descriptor ss_iso_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(1024), + .bInterval = 4, +}; + +struct usb_ss_ep_comp_descriptor ss_iso_source_comp_desc = { + .bLength = USB_DT_SS_EP_COMP_SIZE, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor ss_iso_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(1024), + .bInterval = 4, +}; + +struct usb_ss_ep_comp_descriptor ss_iso_sink_comp_desc = { + .bLength = USB_DT_SS_EP_COMP_SIZE, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = cpu_to_le16(1024), +}; + +static struct usb_descriptor_header *ss_source_sink_descs[] = { + (struct usb_descriptor_header *) &source_sink_intf_alt0, + (struct usb_descriptor_header *) &ss_source_desc, + (struct usb_descriptor_header *) &ss_source_comp_desc, + (struct usb_descriptor_header *) &ss_sink_desc, + (struct usb_descriptor_header *) &ss_sink_comp_desc, + (struct usb_descriptor_header *) &source_sink_intf_alt1, +#define SS_ALT_IFC_1_OFFSET 5 + (struct usb_descriptor_header *) &ss_source_desc, + (struct usb_descriptor_header *) &ss_source_comp_desc, + (struct usb_descriptor_header *) &ss_sink_desc, + (struct usb_descriptor_header *) &ss_sink_comp_desc, + (struct usb_descriptor_header *) &ss_iso_source_desc, + (struct usb_descriptor_header *) &ss_iso_source_comp_desc, + (struct usb_descriptor_header *) &ss_iso_sink_desc, + (struct usb_descriptor_header *) &ss_iso_sink_comp_desc, + NULL, +}; + +/* function-specific strings: */ + +static struct usb_string strings_sourcesink[] = { + [0].s = "source and sink data", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_sourcesink = { + .language = 0x0409, /* en-us */ + .strings = strings_sourcesink, +}; + +static struct usb_gadget_strings *sourcesink_strings[] = { + &stringtab_sourcesink, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +struct usb_request *alloc_ep_req(struct usb_ep *ep, int len) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req) { + if (len) + req->length = len; + else + req->length = buflen; + req->buf = kmalloc(req->length, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + return req; +} + +void free_ep_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void disable_ep(struct usb_composite_dev *cdev, struct usb_ep *ep) +{ + int value; + + if (ep->driver_data) { + value = usb_ep_disable(ep); + if (value < 0) + DBG(cdev, "disable %s --> %d\n", + ep->name, value); + ep->driver_data = NULL; + } +} + +void disable_endpoints(struct usb_composite_dev *cdev, + struct usb_ep *in, struct usb_ep *out, + struct usb_ep *iso_in, struct usb_ep *iso_out) +{ + disable_ep(cdev, in); + disable_ep(cdev, out); + if (iso_in) + disable_ep(cdev, iso_in); + if (iso_out) + disable_ep(cdev, iso_out); +} + +static int +sourcesink_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_sourcesink *ss = func_to_ss(f); + int id; + int ret; + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + source_sink_intf_alt0.bInterfaceNumber = id; + source_sink_intf_alt1.bInterfaceNumber = id; + + /* allocate bulk endpoints */ + ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc); + if (!ss->in_ep) { +autoconf_fail: + ERROR(cdev, "%s: can't autoconfigure on %s\n", + f->name, cdev->gadget->name); + return -ENODEV; + } + ss->in_ep->driver_data = cdev; /* claim */ + + ss->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_sink_desc); + if (!ss->out_ep) + goto autoconf_fail; + ss->out_ep->driver_data = cdev; /* claim */ + + /* sanity check the isoc module parameters */ + if (isoc_interval < 1) + isoc_interval = 1; + if (isoc_interval > 16) + isoc_interval = 16; + if (isoc_mult > 2) + isoc_mult = 2; + if (isoc_maxburst > 15) + isoc_maxburst = 15; + + /* fill in the FS isoc descriptors from the module parameters */ + fs_iso_source_desc.wMaxPacketSize = isoc_maxpacket > 1023 ? + 1023 : isoc_maxpacket; + fs_iso_source_desc.bInterval = isoc_interval; + fs_iso_sink_desc.wMaxPacketSize = isoc_maxpacket > 1023 ? + 1023 : isoc_maxpacket; + fs_iso_sink_desc.bInterval = isoc_interval; + + /* allocate iso endpoints */ + ss->iso_in_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_source_desc); + if (!ss->iso_in_ep) + goto no_iso; + ss->iso_in_ep->driver_data = cdev; /* claim */ + + ss->iso_out_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_sink_desc); + if (ss->iso_out_ep) { + ss->iso_out_ep->driver_data = cdev; /* claim */ + } else { + ss->iso_in_ep->driver_data = NULL; + ss->iso_in_ep = NULL; +no_iso: + /* + * We still want to work even if the UDC doesn't have isoc + * endpoints, so null out the alt interface that contains + * them and continue. + */ + fs_source_sink_descs[FS_ALT_IFC_1_OFFSET] = NULL; + hs_source_sink_descs[HS_ALT_IFC_1_OFFSET] = NULL; + ss_source_sink_descs[SS_ALT_IFC_1_OFFSET] = NULL; + } + + if (isoc_maxpacket > 1024) + isoc_maxpacket = 1024; + + /* support high speed hardware */ + hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress; + hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress; + + /* + * Fill in the HS isoc descriptors from the module parameters. + * We assume that the user knows what they are doing and won't + * give parameters that their UDC doesn't support. + */ + hs_iso_source_desc.wMaxPacketSize = isoc_maxpacket; + hs_iso_source_desc.wMaxPacketSize |= isoc_mult << 11; + hs_iso_source_desc.bInterval = isoc_interval; + hs_iso_source_desc.bEndpointAddress = + fs_iso_source_desc.bEndpointAddress; + + hs_iso_sink_desc.wMaxPacketSize = isoc_maxpacket; + hs_iso_sink_desc.wMaxPacketSize |= isoc_mult << 11; + hs_iso_sink_desc.bInterval = isoc_interval; + hs_iso_sink_desc.bEndpointAddress = fs_iso_sink_desc.bEndpointAddress; + + /* support super speed hardware */ + ss_source_desc.bEndpointAddress = + fs_source_desc.bEndpointAddress; + ss_sink_desc.bEndpointAddress = + fs_sink_desc.bEndpointAddress; + + /* + * Fill in the SS isoc descriptors from the module parameters. + * We assume that the user knows what they are doing and won't + * give parameters that their UDC doesn't support. + */ + ss_iso_source_desc.wMaxPacketSize = isoc_maxpacket; + ss_iso_source_desc.bInterval = isoc_interval; + ss_iso_source_comp_desc.bmAttributes = isoc_mult; + ss_iso_source_comp_desc.bMaxBurst = isoc_maxburst; + ss_iso_source_comp_desc.wBytesPerInterval = + isoc_maxpacket * (isoc_mult + 1) * (isoc_maxburst + 1); + ss_iso_source_desc.bEndpointAddress = + fs_iso_source_desc.bEndpointAddress; + + ss_iso_sink_desc.wMaxPacketSize = isoc_maxpacket; + ss_iso_sink_desc.bInterval = isoc_interval; + ss_iso_sink_comp_desc.bmAttributes = isoc_mult; + ss_iso_sink_comp_desc.bMaxBurst = isoc_maxburst; + ss_iso_sink_comp_desc.wBytesPerInterval = + isoc_maxpacket * (isoc_mult + 1) * (isoc_maxburst + 1); + ss_iso_sink_desc.bEndpointAddress = fs_iso_sink_desc.bEndpointAddress; + + ret = usb_assign_descriptors(f, fs_source_sink_descs, + hs_source_sink_descs, ss_source_sink_descs); + if (ret) + return ret; + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s, ISO-IN/%s, ISO-OUT/%s\n", + (gadget_is_superspeed(c->cdev->gadget) ? "super" : + (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")), + f->name, ss->in_ep->name, ss->out_ep->name, + ss->iso_in_ep ? ss->iso_in_ep->name : "", + ss->iso_out_ep ? ss->iso_out_ep->name : ""); + return 0; +} + +static void +sourcesink_free_func(struct usb_function *f) +{ + usb_free_all_descriptors(f); + kfree(func_to_ss(f)); +} + +/* optionally require specific source/sink data patterns */ +static int check_read_data(struct f_sourcesink *ss, struct usb_request *req) +{ + unsigned i; + u8 *buf = req->buf; + struct usb_composite_dev *cdev = ss->function.config->cdev; + + if (pattern == 2) + return 0; + + for (i = 0; i < req->actual; i++, buf++) { + switch (pattern) { + + /* all-zeroes has no synchronization issues */ + case 0: + if (*buf == 0) + continue; + break; + + /* "mod63" stays in sync with short-terminated transfers, + * OR otherwise when host and gadget agree on how large + * each usb transfer request should be. Resync is done + * with set_interface or set_config. (We *WANT* it to + * get quickly out of sync if controllers or their drivers + * stutter for any reason, including buffer duplication...) + */ + case 1: + if (*buf == (u8)(i % 63)) + continue; + break; + } + ERROR(cdev, "bad OUT byte, buf[%d] = %d\n", i, *buf); + usb_ep_set_halt(ss->out_ep); + return -EINVAL; + } + return 0; +} + +static void reinit_write_data(struct usb_ep *ep, struct usb_request *req) +{ + unsigned i; + u8 *buf = req->buf; + + switch (pattern) { + case 0: + memset(req->buf, 0, req->length); + break; + case 1: + for (i = 0; i < req->length; i++) + *buf++ = (u8) (i % 63); + break; + case 2: + break; + } +} + +static void source_sink_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct usb_composite_dev *cdev; + struct f_sourcesink *ss = ep->driver_data; + int status = req->status; + + /* driver_data will be null if ep has been disabled */ + if (!ss) + return; + + cdev = ss->function.config->cdev; + + switch (status) { + + case 0: /* normal completion? */ + if (ep == ss->out_ep) { + check_read_data(ss, req); + if (pattern != 2) + memset(req->buf, 0x55, req->length); + } + break; + + /* this endpoint is normally active while we're configured */ + case -ECONNABORTED: /* hardware forced ep reset */ + case -ECONNRESET: /* request dequeued */ + case -ESHUTDOWN: /* disconnect from host */ + VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status, + req->actual, req->length); + if (ep == ss->out_ep) + check_read_data(ss, req); + free_ep_req(ep, req); + return; + + case -EOVERFLOW: /* buffer overrun on read means that + * we didn't provide a big enough + * buffer. + */ + default: +#if 1 + DBG(cdev, "%s complete --> %d, %d/%d\n", ep->name, + status, req->actual, req->length); +#endif + case -EREMOTEIO: /* short read */ + break; + } + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status) { + ERROR(cdev, "kill %s: resubmit %d bytes --> %d\n", + ep->name, req->length, status); + usb_ep_set_halt(ep); + /* FIXME recover later ... somehow */ + } +} + +static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in, + bool is_iso, int speed) +{ + struct usb_ep *ep; + struct usb_request *req; + int i, size, status; + + for (i = 0; i < 8; i++) { + if (is_iso) { + switch (speed) { + case USB_SPEED_SUPER: + size = isoc_maxpacket * (isoc_mult + 1) * + (isoc_maxburst + 1); + break; + case USB_SPEED_HIGH: + size = isoc_maxpacket * (isoc_mult + 1); + break; + default: + size = isoc_maxpacket > 1023 ? + 1023 : isoc_maxpacket; + break; + } + ep = is_in ? ss->iso_in_ep : ss->iso_out_ep; + req = alloc_ep_req(ep, size); + } else { + ep = is_in ? ss->in_ep : ss->out_ep; + req = alloc_ep_req(ep, 0); + } + + if (!req) + return -ENOMEM; + + req->complete = source_sink_complete; + if (is_in) + reinit_write_data(ep, req); + else if (pattern != 2) + memset(req->buf, 0x55, req->length); + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status) { + struct usb_composite_dev *cdev; + + cdev = ss->function.config->cdev; + ERROR(cdev, "start %s%s %s --> %d\n", + is_iso ? "ISO-" : "", is_in ? "IN" : "OUT", + ep->name, status); + free_ep_req(ep, req); + } + + if (!is_iso) + break; + } + + return status; +} + +static void disable_source_sink(struct f_sourcesink *ss) +{ + struct usb_composite_dev *cdev; + + cdev = ss->function.config->cdev; + disable_endpoints(cdev, ss->in_ep, ss->out_ep, ss->iso_in_ep, + ss->iso_out_ep); + VDBG(cdev, "%s disabled\n", ss->function.name); +} + +static int +enable_source_sink(struct usb_composite_dev *cdev, struct f_sourcesink *ss, + int alt) +{ + int result = 0; + int speed = cdev->gadget->speed; + struct usb_ep *ep; + + /* one bulk endpoint writes (sources) zeroes IN (to the host) */ + ep = ss->in_ep; + result = config_ep_by_speed(cdev->gadget, &(ss->function), ep); + if (result) + return result; + result = usb_ep_enable(ep); + if (result < 0) + return result; + ep->driver_data = ss; + + result = source_sink_start_ep(ss, true, false, speed); + if (result < 0) { +fail: + ep = ss->in_ep; + usb_ep_disable(ep); + ep->driver_data = NULL; + return result; + } + + /* one bulk endpoint reads (sinks) anything OUT (from the host) */ + ep = ss->out_ep; + result = config_ep_by_speed(cdev->gadget, &(ss->function), ep); + if (result) + goto fail; + result = usb_ep_enable(ep); + if (result < 0) + goto fail; + ep->driver_data = ss; + + result = source_sink_start_ep(ss, false, false, speed); + if (result < 0) { +fail2: + ep = ss->out_ep; + usb_ep_disable(ep); + ep->driver_data = NULL; + goto fail; + } + + if (alt == 0) + goto out; + + /* one iso endpoint writes (sources) zeroes IN (to the host) */ + ep = ss->iso_in_ep; + if (ep) { + result = config_ep_by_speed(cdev->gadget, &(ss->function), ep); + if (result) + goto fail2; + result = usb_ep_enable(ep); + if (result < 0) + goto fail2; + ep->driver_data = ss; + + result = source_sink_start_ep(ss, true, true, speed); + if (result < 0) { +fail3: + ep = ss->iso_in_ep; + if (ep) { + usb_ep_disable(ep); + ep->driver_data = NULL; + } + goto fail2; + } + } + + /* one iso endpoint reads (sinks) anything OUT (from the host) */ + ep = ss->iso_out_ep; + if (ep) { + result = config_ep_by_speed(cdev->gadget, &(ss->function), ep); + if (result) + goto fail3; + result = usb_ep_enable(ep); + if (result < 0) + goto fail3; + ep->driver_data = ss; + + result = source_sink_start_ep(ss, false, true, speed); + if (result < 0) { + usb_ep_disable(ep); + ep->driver_data = NULL; + goto fail3; + } + } +out: + ss->cur_alt = alt; + + DBG(cdev, "%s enabled, alt intf %d\n", ss->function.name, alt); + return result; +} + +static int sourcesink_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct f_sourcesink *ss = func_to_ss(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (ss->in_ep->driver_data) + disable_source_sink(ss); + return enable_source_sink(cdev, ss, alt); +} + +static int sourcesink_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_sourcesink *ss = func_to_ss(f); + + return ss->cur_alt; +} + +static void sourcesink_disable(struct usb_function *f) +{ + struct f_sourcesink *ss = func_to_ss(f); + + disable_source_sink(ss); +} + +/*-------------------------------------------------------------------------*/ + +static int sourcesink_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_configuration *c = f->config; + struct usb_request *req = c->cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + req->length = USB_COMP_EP0_BUFSIZ; + + /* composite driver infrastructure handles everything except + * the two control test requests. + */ + switch (ctrl->bRequest) { + + /* + * These are the same vendor-specific requests supported by + * Intel's USB 2.0 compliance test devices. We exceed that + * device spec by allowing multiple-packet requests. + * + * NOTE: the Control-OUT data stays in req->buf ... better + * would be copying it into a scratch buffer, so that other + * requests may safely intervene. + */ + case 0x5b: /* control WRITE test -- fill the buffer */ + if (ctrl->bRequestType != (USB_DIR_OUT|USB_TYPE_VENDOR)) + goto unknown; + if (w_value || w_index) + break; + /* just read that many bytes into the buffer */ + if (w_length > req->length) + break; + value = w_length; + break; + case 0x5c: /* control READ test -- return the buffer */ + if (ctrl->bRequestType != (USB_DIR_IN|USB_TYPE_VENDOR)) + goto unknown; + if (w_value || w_index) + break; + /* expect those bytes are still in the buffer; send back */ + if (w_length > req->length) + break; + value = w_length; + break; + + default: +unknown: + VDBG(c->cdev, + "unknown control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + VDBG(c->cdev, "source/sink req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(c->cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(c->cdev, "source/sink response, err %d\n", + value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static struct usb_function *source_sink_alloc_func( + struct usb_function_instance *fi) +{ + struct f_sourcesink *ss; + struct f_ss_opts *ss_opts; + + ss = kzalloc(sizeof(*ss), GFP_KERNEL); + if (!ss) + return NULL; + + ss_opts = container_of(fi, struct f_ss_opts, func_inst); + pattern = ss_opts->pattern; + isoc_interval = ss_opts->isoc_interval; + isoc_maxpacket = ss_opts->isoc_maxpacket; + isoc_mult = ss_opts->isoc_mult; + isoc_maxburst = ss_opts->isoc_maxburst; + buflen = ss_opts->bulk_buflen; + + ss->function.name = "source/sink"; + ss->function.bind = sourcesink_bind; + ss->function.set_alt = sourcesink_set_alt; + ss->function.get_alt = sourcesink_get_alt; + ss->function.disable = sourcesink_disable; + ss->function.setup = sourcesink_setup; + ss->function.strings = sourcesink_strings; + + ss->function.free_func = sourcesink_free_func; + + return &ss->function; +} + +static void source_sink_free_instance(struct usb_function_instance *fi) +{ + struct f_ss_opts *ss_opts; + + ss_opts = container_of(fi, struct f_ss_opts, func_inst); + kfree(ss_opts); +} + +static struct usb_function_instance *source_sink_alloc_inst(void) +{ + struct f_ss_opts *ss_opts; + + ss_opts = kzalloc(sizeof(*ss_opts), GFP_KERNEL); + if (!ss_opts) + return ERR_PTR(-ENOMEM); + ss_opts->func_inst.free_func_inst = source_sink_free_instance; + return &ss_opts->func_inst; +} +DECLARE_USB_FUNCTION(SourceSink, source_sink_alloc_inst, + source_sink_alloc_func); + +static int __init sslb_modinit(void) +{ + int ret; + + ret = usb_function_register(&SourceSinkusb_func); + if (ret) + return ret; + ret = lb_modinit(); + if (ret) + usb_function_unregister(&SourceSinkusb_func); + return ret; +} +static void __exit sslb_modexit(void) +{ + usb_function_unregister(&SourceSinkusb_func); + lb_modexit(); +} +module_init(sslb_modinit); +module_exit(sslb_modexit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_subset.c b/drivers/usb/gadget/f_subset.c new file mode 100644 index 00000000..7be04b34 --- /dev/null +++ b/drivers/usb/gadget/f_subset.c @@ -0,0 +1,425 @@ +/* + * f_subset.c -- "CDC Subset" Ethernet link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include + +#include "u_ether.h" + + +/* + * This function packages a simple "CDC Subset" Ethernet port with no real + * control mechanisms; just raw data transfer over two bulk endpoints. + * The data transfer model is exactly that of CDC Ethernet, which is + * why we call it the "CDC Subset". + * + * Because it's not standardized, this has some interoperability issues. + * They mostly relate to driver binding, since the data transfer model is + * so simple (CDC Ethernet). The original versions of this protocol used + * specific product/vendor IDs: byteswapped IDs for Digital Equipment's + * SA-1100 "Itsy" board, which could run Linux 2.4 kernels and supported + * daughtercards with USB peripheral connectors. (It was used more often + * with other boards, using the Itsy identifiers.) Linux hosts recognized + * this with CONFIG_USB_ARMLINUX; these devices have only one configuration + * and one interface. + * + * At some point, MCCI defined a (nonconformant) CDC MDLM variant called + * "SAFE", which happens to have a mode which is identical to the "CDC + * Subset" in terms of data transfer and lack of control model. This was + * adopted by later Sharp Zaurus models, and by some other software which + * Linux hosts recognize with CONFIG_USB_NET_ZAURUS. + * + * Because Microsoft's RNDIS drivers are far from robust, we added a few + * descriptors to the CDC Subset code, making this code look like a SAFE + * implementation. This lets you use MCCI's host side MS-Windows drivers + * if you get fed up with RNDIS. It also makes it easier for composite + * drivers to work, since they can use class based binding instead of + * caring about specific product and vendor IDs. + */ + +struct f_gether { + struct gether port; + + char ethaddr[14]; +}; + +static inline struct f_gether *func_to_geth(struct usb_function *f) +{ + return container_of(f, struct f_gether, port.func); +} + +/*-------------------------------------------------------------------------*/ + +/* + * "Simple" CDC-subset option is a simple vendor-neutral model that most + * full speed controllers can handle: one interface, two bulk endpoints. + * To assist host side drivers, we fancy it up a bit, and add descriptors so + * some host side drivers will understand it as a "SAFE" variant. + * + * "SAFE" loosely follows CDC WMC MDLM, violating the spec in various ways. + * Data endpoints live in the control interface, there's no data interface. + * And it's not used to talk to a cell phone radio. + */ + +/* interface descriptor: */ + +static struct usb_interface_descriptor subset_data_intf = { + .bLength = sizeof subset_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_MDLM, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc mdlm_header_desc = { + .bLength = sizeof mdlm_header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_mdlm_desc mdlm_desc = { + .bLength = sizeof mdlm_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_MDLM_TYPE, + + .bcdVersion = cpu_to_le16(0x0100), + .bGUID = { + 0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6, + 0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f, + }, +}; + +/* since "usb_cdc_mdlm_detail_desc" is a variable length structure, we + * can't really use its struct. All we do here is say that we're using + * the submode of "SAFE" which directly matches the CDC Subset. + */ +static u8 mdlm_detail_desc[] = { + 6, + USB_DT_CS_INTERFACE, + USB_CDC_MDLM_DETAIL_TYPE, + + 0, /* "SAFE" */ + 0, /* network control capabilities (none) */ + 0, /* network data capabilities ("raw" encapsulation) */ +}; + +static struct usb_cdc_ether_desc ether_desc = { + .bLength = sizeof ether_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, + + /* this descriptor actually adds value, surprise! */ + /* .iMACAddress = DYNAMIC */ + .bmEthernetStatistics = cpu_to_le32(0), /* no statistics */ + .wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN), + .wNumberMCFilters = cpu_to_le16(0), + .bNumberPowerFilters = 0, +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_subset_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_subset_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_eth_function[] = { + (struct usb_descriptor_header *) &subset_data_intf, + (struct usb_descriptor_header *) &mdlm_header_desc, + (struct usb_descriptor_header *) &mdlm_desc, + (struct usb_descriptor_header *) &mdlm_detail_desc, + (struct usb_descriptor_header *) ðer_desc, + (struct usb_descriptor_header *) &fs_subset_in_desc, + (struct usb_descriptor_header *) &fs_subset_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_subset_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_subset_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *hs_eth_function[] = { + (struct usb_descriptor_header *) &subset_data_intf, + (struct usb_descriptor_header *) &mdlm_header_desc, + (struct usb_descriptor_header *) &mdlm_desc, + (struct usb_descriptor_header *) &mdlm_detail_desc, + (struct usb_descriptor_header *) ðer_desc, + (struct usb_descriptor_header *) &hs_subset_in_desc, + (struct usb_descriptor_header *) &hs_subset_out_desc, + NULL, +}; + +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_subset_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor ss_subset_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ss_subset_bulk_comp_desc = { + .bLength = sizeof ss_subset_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *ss_eth_function[] = { + (struct usb_descriptor_header *) &subset_data_intf, + (struct usb_descriptor_header *) &mdlm_header_desc, + (struct usb_descriptor_header *) &mdlm_desc, + (struct usb_descriptor_header *) &mdlm_detail_desc, + (struct usb_descriptor_header *) ðer_desc, + (struct usb_descriptor_header *) &ss_subset_in_desc, + (struct usb_descriptor_header *) &ss_subset_bulk_comp_desc, + (struct usb_descriptor_header *) &ss_subset_out_desc, + (struct usb_descriptor_header *) &ss_subset_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +static struct usb_string geth_string_defs[] = { + [0].s = "CDC Ethernet Subset/SAFE", + [1].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings geth_string_table = { + .language = 0x0409, /* en-us */ + .strings = geth_string_defs, +}; + +static struct usb_gadget_strings *geth_strings[] = { + &geth_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int geth_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_gether *geth = func_to_geth(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct net_device *net; + + /* we know alt == 0, so this is an activation or a reset */ + + if (geth->port.in_ep->driver_data) { + DBG(cdev, "reset cdc subset\n"); + gether_disconnect(&geth->port); + } + + DBG(cdev, "init + activate cdc subset\n"); + if (config_ep_by_speed(cdev->gadget, f, geth->port.in_ep) || + config_ep_by_speed(cdev->gadget, f, geth->port.out_ep)) { + geth->port.in_ep->desc = NULL; + geth->port.out_ep->desc = NULL; + return -EINVAL; + } + + net = gether_connect(&geth->port); + return IS_ERR(net) ? PTR_ERR(net) : 0; +} + +static void geth_disable(struct usb_function *f) +{ + struct f_gether *geth = func_to_geth(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "net deactivated\n"); + gether_disconnect(&geth->port); +} + +/*-------------------------------------------------------------------------*/ + +/* serial function driver setup/binding */ + +static int +geth_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_gether *geth = func_to_geth(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + subset_data_intf.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc); + if (!ep) + goto fail; + geth->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc); + if (!ep) + goto fail; + geth->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + hs_subset_in_desc.bEndpointAddress = fs_subset_in_desc.bEndpointAddress; + hs_subset_out_desc.bEndpointAddress = + fs_subset_out_desc.bEndpointAddress; + + ss_subset_in_desc.bEndpointAddress = fs_subset_in_desc.bEndpointAddress; + ss_subset_out_desc.bEndpointAddress = + fs_subset_out_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, fs_eth_function, hs_eth_function, + ss_eth_function); + if (status) + goto fail; + + /* NOTE: all that is done without knowing or caring about + * the network link ... which is unavailable to this code + * until we're activated via set_alt(). + */ + + DBG(cdev, "CDC Subset: %s speed IN/%s OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + geth->port.in_ep->name, geth->port.out_ep->name); + return 0; + +fail: + usb_free_all_descriptors(f); + /* we might as well release our claims on endpoints */ + if (geth->port.out_ep) + geth->port.out_ep->driver_data = NULL; + if (geth->port.in_ep) + geth->port.in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +geth_unbind(struct usb_configuration *c, struct usb_function *f) +{ + geth_string_defs[0].id = 0; + usb_free_all_descriptors(f); + kfree(func_to_geth(f)); +} + +/** + * geth_bind_config - add CDC Subset network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * @dev: eth_dev structure + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + struct eth_dev *dev) +{ + struct f_gether *geth; + int status; + + if (!ethaddr) + return -EINVAL; + + /* maybe allocate device-global string IDs */ + if (geth_string_defs[0].id == 0) { + status = usb_string_ids_tab(c->cdev, geth_string_defs); + if (status < 0) + return status; + subset_data_intf.iInterface = geth_string_defs[0].id; + ether_desc.iMACAddress = geth_string_defs[1].id; + } + + /* allocate and initialize one new instance */ + geth = kzalloc(sizeof *geth, GFP_KERNEL); + if (!geth) + return -ENOMEM; + + /* export host's Ethernet address in CDC format */ + snprintf(geth->ethaddr, sizeof geth->ethaddr, "%pm", ethaddr); + geth_string_defs[1].s = geth->ethaddr; + + geth->port.ioport = dev; + geth->port.cdc_filter = DEFAULT_FILTER; + + geth->port.func.name = "cdc_subset"; + geth->port.func.strings = geth_strings; + geth->port.func.bind = geth_bind; + geth->port.func.unbind = geth_unbind; + geth->port.func.set_alt = geth_set_alt; + geth->port.func.disable = geth_disable; + + status = usb_add_function(c, &geth->port.func); + if (status) + kfree(geth); + return status; +} diff --git a/drivers/usb/gadget/f_uac1.c b/drivers/usb/gadget/f_uac1.c new file mode 100644 index 00000000..fa8ea4ea --- /dev/null +++ b/drivers/usb/gadget/f_uac1.c @@ -0,0 +1,768 @@ +/* + * f_audio.c -- USB Audio class function driver + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include + +#include "u_uac1.h" + +#define OUT_EP_MAX_PACKET_SIZE 200 +static int req_buf_size = OUT_EP_MAX_PACKET_SIZE; +module_param(req_buf_size, int, S_IRUGO); +MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size"); + +static int req_count = 256; +module_param(req_count, int, S_IRUGO); +MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count"); + +static int audio_buf_size = 48000; +module_param(audio_buf_size, int, S_IRUGO); +MODULE_PARM_DESC(audio_buf_size, "Audio buffer size"); + +static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value); +static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); + +/* + * DESCRIPTORS ... most are static, but strings and full + * configuration descriptors are built on demand. + */ + +/* + * We have two interfaces- AudioControl and AudioStreaming + * TODO: only supcard playback currently + */ +#define F_AUDIO_AC_INTERFACE 0 +#define F_AUDIO_AS_INTERFACE 1 +#define F_AUDIO_NUM_INTERFACES 2 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc __initdata = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); + +#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) +/* 1 input terminal, 1 output terminal and 1 feature unit */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \ + + UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0)) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_2 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = F_AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + [0] = F_AUDIO_AC_INTERFACE, + [1] = F_AUDIO_AS_INTERFACE, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct uac_input_terminal_descriptor input_terminal_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct uac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, + .bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME), +}; + +static struct usb_audio_control mute_control = { + .list = LIST_HEAD_INIT(mute_control.list), + .name = "Mute Control", + .type = UAC_FU_MUTE, + /* Todo: add real Mute control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control volume_control = { + .list = LIST_HEAD_INIT(volume_control.list), + .name = "Volume Control", + .type = UAC_FU_VOLUME, + /* Todo: add real Volume control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control_selector feature_unit = { + .list = LIST_HEAD_INIT(feature_unit.list), + .id = FEATURE_UNIT_ID, + .name = "Mute & Volume Control", + .type = UAC_FEATURE_UNIT, + .desc = (struct usb_descriptor_header *)&feature_unit_desc, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct uac1_output_terminal_descriptor output_terminal_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER, + .bAssocTerminal = FEATURE_UNIT_ID, + .bSourceID = FEATURE_UNIT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct uac1_as_header_descriptor as_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_out_desc __initdata = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *f_audio_desc[] __initdata = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&as_out_ep_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + NULL, +}; + +/* + * This function is an ALSA sound card following USB Audio Class Spec 1.0. + */ + +/*-------------------------------------------------------------------------*/ +struct f_audio_buf { + u8 *buf; + int actual; + struct list_head list; +}; + +static struct f_audio_buf *f_audio_buffer_alloc(int buf_size) +{ + struct f_audio_buf *copy_buf; + + copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC); + if (!copy_buf) + return ERR_PTR(-ENOMEM); + + copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC); + if (!copy_buf->buf) { + kfree(copy_buf); + return ERR_PTR(-ENOMEM); + } + + return copy_buf; +} + +static void f_audio_buffer_free(struct f_audio_buf *audio_buf) +{ + kfree(audio_buf->buf); + kfree(audio_buf); +} +/*-------------------------------------------------------------------------*/ + +struct f_audio { + struct gaudio card; + + /* endpoints handle full and/or high speeds */ + struct usb_ep *out_ep; + + spinlock_t lock; + struct f_audio_buf *copy_buf; + struct work_struct playback_work; + struct list_head play_queue; + + /* Control Set command */ + struct list_head cs; + u8 set_cmd; + struct usb_audio_control *set_con; +}; + +static inline struct f_audio *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct f_audio, card.func); +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_playback_work(struct work_struct *data) +{ + struct f_audio *audio = container_of(data, struct f_audio, + playback_work); + struct f_audio_buf *play_buf; + + spin_lock_irq(&audio->lock); + if (list_empty(&audio->play_queue)) { + spin_unlock_irq(&audio->lock); + return; + } + play_buf = list_first_entry(&audio->play_queue, + struct f_audio_buf, list); + list_del(&play_buf->list); + spin_unlock_irq(&audio->lock); + + u_audio_playback(&audio->card, play_buf->buf, play_buf->actual); + f_audio_buffer_free(play_buf); +} + +static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + struct usb_composite_dev *cdev = audio->card.func.config->cdev; + struct f_audio_buf *copy_buf = audio->copy_buf; + int err; + + if (!copy_buf) + return -EINVAL; + + /* Copy buffer is full, add it to the play_queue */ + if (audio_buf_size - copy_buf->actual < req->actual) { + list_add_tail(©_buf->list, &audio->play_queue); + schedule_work(&audio->playback_work); + copy_buf = f_audio_buffer_alloc(audio_buf_size); + if (IS_ERR(copy_buf)) + return -ENOMEM; + } + + memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual); + copy_buf->actual += req->actual; + audio->copy_buf = copy_buf; + + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err) + ERROR(cdev, "%s queue req: %d\n", ep->name, err); + + return 0; + +} + +static void f_audio_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + int status = req->status; + u32 data = 0; + struct usb_ep *out_ep = audio->out_ep; + + switch (status) { + + case 0: /* normal completion? */ + if (ep == out_ep) + f_audio_out_ep_complete(ep, req); + else if (audio->set_con) { + memcpy(&data, req->buf, req->length); + audio->set_con->set(audio->set_con, audio->set_cmd, + le16_to_cpu(data)); + audio->set_con = NULL; + } + break; + default: + break; + } +} + +static int audio_set_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel) { + audio->set_con = con; + break; + } + } + break; + } + } + + audio->set_cmd = cmd; + req->context = audio; + req->complete = f_audio_complete; + + return len; +} + +static int audio_get_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel && con->get) { + value = con->get(con, cmd); + break; + } + } + break; + } + } + + req->context = audio; + req->complete = f_audio_complete; + len = min_t(size_t, sizeof(value), len); + memcpy(req->buf, &value, len); + + return len; +} + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_SET_CUR: + value = len; + break; + + case UAC_SET_MIN: + break; + + case UAC_SET_MAX: + break; + + case UAC_SET_RES: + break; + + case UAC_SET_MEM: + break; + + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_GET_CUR: + case UAC_GET_MIN: + case UAC_GET_MAX: + case UAC_GET_RES: + value = len; + break; + case UAC_GET_MEM: + break; + default: + break; + } + + return value; +} + +static int +f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything; interface + * activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = audio_set_intf_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = audio_get_intf_req(f, ctrl); + break; + + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_set_endpoint_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_get_endpoint_req(f, ctrl); + break; + + default: + ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_ep *out_ep = audio->out_ep; + struct usb_request *req; + int i = 0, err = 0; + + DBG(cdev, "intf %d, alt %d\n", intf, alt); + + if (intf == 1) { + if (alt == 1) { + usb_ep_enable(out_ep); + out_ep->driver_data = audio; + audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); + if (IS_ERR(audio->copy_buf)) + return -ENOMEM; + + /* + * allocate a bunch of read buffers + * and queue them all at once. + */ + for (i = 0; i < req_count && err == 0; i++) { + req = usb_ep_alloc_request(out_ep, GFP_ATOMIC); + if (req) { + req->buf = kzalloc(req_buf_size, + GFP_ATOMIC); + if (req->buf) { + req->length = req_buf_size; + req->context = audio; + req->complete = + f_audio_complete; + err = usb_ep_queue(out_ep, + req, GFP_ATOMIC); + if (err) + ERROR(cdev, + "%s queue req: %d\n", + out_ep->name, err); + } else + err = -ENOMEM; + } else + err = -ENOMEM; + } + + } else { + struct f_audio_buf *copy_buf = audio->copy_buf; + if (copy_buf) { + list_add_tail(©_buf->list, + &audio->play_queue); + schedule_work(&audio->playback_work); + } + } + } + + return err; +} + +static void f_audio_disable(struct usb_function *f) +{ + return; +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_build_desc(struct f_audio *audio) +{ + struct gaudio *card = &audio->card; + u8 *sam_freq; + int rate; + + /* Set channel numbers */ + input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card); + as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card); + + /* Set sample rates */ + rate = u_audio_get_playback_rate(card); + sam_freq = as_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); + + /* Todo: Set Sample bits and other parameters */ + + return; +} + +/* audio function driver setup/binding */ +static int __init +f_audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_audio *audio = func_to_audio(f); + int status; + struct usb_ep *ep = NULL; + + f_audio_build_desc(audio); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_interface_alt_0_desc.bInterfaceNumber = status; + as_interface_alt_1_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); + if (!ep) + goto fail; + audio->out_ep = ep; + audio->out_ep->desc = &as_out_ep_desc; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* copy descriptors, and track endpoint copies */ + status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL); + if (status) + goto fail; + return 0; + +fail: + if (ep) + ep->driver_data = NULL; + return status; +} + +static void +f_audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_audio *audio = func_to_audio(f); + + usb_free_all_descriptors(f); + kfree(audio); +} + +/*-------------------------------------------------------------------------*/ + +static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) +{ + con->data[cmd] = value; + + return 0; +} + +static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) +{ + return con->data[cmd]; +} + +/* Todo: add more control selecotor dynamically */ +int __init control_selector_init(struct f_audio *audio) +{ + INIT_LIST_HEAD(&audio->cs); + list_add(&feature_unit.list, &audio->cs); + + INIT_LIST_HEAD(&feature_unit.control); + list_add(&mute_control.list, &feature_unit.control); + list_add(&volume_control.list, &feature_unit.control); + + volume_control.data[UAC__CUR] = 0xffc0; + volume_control.data[UAC__MIN] = 0xe3a0; + volume_control.data[UAC__MAX] = 0xfff0; + volume_control.data[UAC__RES] = 0x0030; + + return 0; +} + +/** + * audio_bind_config - add USB audio function to a configuration + * @c: the configuration to supcard the USB audio function + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + */ +int __init audio_bind_config(struct usb_configuration *c) +{ + struct f_audio *audio; + int status; + + /* allocate and initialize one new instance */ + audio = kzalloc(sizeof *audio, GFP_KERNEL); + if (!audio) + return -ENOMEM; + + audio->card.func.name = "g_audio"; + audio->card.gadget = c->cdev->gadget; + + INIT_LIST_HEAD(&audio->play_queue); + spin_lock_init(&audio->lock); + + /* set up ASLA audio devices */ + status = gaudio_setup(&audio->card); + if (status < 0) + goto setup_fail; + + audio->card.func.strings = audio_strings; + audio->card.func.bind = f_audio_bind; + audio->card.func.unbind = f_audio_unbind; + audio->card.func.set_alt = f_audio_set_alt; + audio->card.func.setup = f_audio_setup; + audio->card.func.disable = f_audio_disable; + + control_selector_init(audio); + + INIT_WORK(&audio->playback_work, f_audio_playback_work); + + status = usb_add_function(c, &audio->card.func); + if (status) + goto add_fail; + + INFO(c->cdev, "audio_buf_size %d, req_buf_size %d, req_count %d\n", + audio_buf_size, req_buf_size, req_count); + + return status; + +add_fail: + gaudio_cleanup(); +setup_fail: + kfree(audio); + return status; +} diff --git a/drivers/usb/gadget/f_uac2.c b/drivers/usb/gadget/f_uac2.c new file mode 100644 index 00000000..03c1fb68 --- /dev/null +++ b/drivers/usb/gadget/f_uac2.c @@ -0,0 +1,1370 @@ +/* + * f_uac2.c -- USB Audio Class 2.0 Function + * + * Copyright (C) 2011 + * Yadwinder Singh (yadi.brar01@gmail.com) + * Jaswinder Singh (jaswinder.singh@linaro.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include + +#include +#include +#include + +/* Playback(USB-IN) Default Stereo - Fl/Fr */ +static int p_chmask = 0x3; +module_param(p_chmask, uint, S_IRUGO); +MODULE_PARM_DESC(p_chmask, "Playback Channel Mask"); + +/* Playback Default 48 KHz */ +static int p_srate = 48000; +module_param(p_srate, uint, S_IRUGO); +MODULE_PARM_DESC(p_srate, "Playback Sampling Rate"); + +/* Playback Default 16bits/sample */ +static int p_ssize = 2; +module_param(p_ssize, uint, S_IRUGO); +MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)"); + +/* Capture(USB-OUT) Default Stereo - Fl/Fr */ +static int c_chmask = 0x3; +module_param(c_chmask, uint, S_IRUGO); +MODULE_PARM_DESC(c_chmask, "Capture Channel Mask"); + +/* Capture Default 64 KHz */ +static int c_srate = 64000; +module_param(c_srate, uint, S_IRUGO); +MODULE_PARM_DESC(c_srate, "Capture Sampling Rate"); + +/* Capture Default 16bits/sample */ +static int c_ssize = 2; +module_param(c_ssize, uint, S_IRUGO); +MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); + +/* Keep everyone on toes */ +#define USB_XFERS 2 + +/* + * The driver implements a simple UAC_2 topology. + * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture + * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN + * Capture and Playback sampling rates are independently + * controlled by two clock sources : + * CLK_5 := c_srate, and CLK_6 := p_srate + */ +#define USB_OUT_IT_ID 1 +#define IO_IN_IT_ID 2 +#define IO_OUT_OT_ID 3 +#define USB_IN_OT_ID 4 +#define USB_OUT_CLK_ID 5 +#define USB_IN_CLK_ID 6 + +#define CONTROL_ABSENT 0 +#define CONTROL_RDONLY 1 +#define CONTROL_RDWR 3 + +#define CLK_FREQ_CTRL 0 +#define CLK_VLD_CTRL 2 + +#define COPY_CTRL 0 +#define CONN_CTRL 2 +#define OVRLD_CTRL 4 +#define CLSTR_CTRL 6 +#define UNFLW_CTRL 8 +#define OVFLW_CTRL 10 + +const char *uac2_name = "snd_uac2"; + +struct uac2_req { + struct uac2_rtd_params *pp; /* parent param */ + struct usb_request *req; +}; + +struct uac2_rtd_params { + bool ep_enabled; /* if the ep is enabled */ + /* Size of the ring buffer */ + size_t dma_bytes; + unsigned char *dma_area; + + struct snd_pcm_substream *ss; + + /* Ring buffer */ + ssize_t hw_ptr; + + void *rbuf; + + size_t period_size; + + unsigned max_psize; + struct uac2_req ureq[USB_XFERS]; + + spinlock_t lock; +}; + +struct snd_uac2_chip { + struct platform_device pdev; + struct platform_driver pdrv; + + struct uac2_rtd_params p_prm; + struct uac2_rtd_params c_prm; + + struct snd_card *card; + struct snd_pcm *pcm; +}; + +#define BUFF_SIZE_MAX (PAGE_SIZE * 16) +#define PRD_SIZE_MAX PAGE_SIZE +#define MIN_PERIODS 4 + +static struct snd_pcm_hardware uac2_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX, + .buffer_bytes_max = BUFF_SIZE_MAX, + .period_bytes_max = PRD_SIZE_MAX, + .periods_min = MIN_PERIODS, +}; + +struct audio_dev { + u8 ac_intf, ac_alt; + u8 as_out_intf, as_out_alt; + u8 as_in_intf, as_in_alt; + + struct usb_ep *in_ep, *out_ep; + struct usb_function func; + + /* The ALSA Sound Card it represents on the USB-Client side */ + struct snd_uac2_chip uac2; +}; + +static struct audio_dev *agdev_g; + +static inline +struct audio_dev *func_to_agdev(struct usb_function *f) +{ + return container_of(f, struct audio_dev, func); +} + +static inline +struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u) +{ + return container_of(u, struct audio_dev, uac2); +} + +static inline +struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p) +{ + return container_of(p, struct snd_uac2_chip, pdev); +} + +static inline +struct snd_uac2_chip *prm_to_uac2(struct uac2_rtd_params *r) +{ + struct snd_uac2_chip *uac2 = container_of(r, + struct snd_uac2_chip, c_prm); + + if (&uac2->c_prm != r) + uac2 = container_of(r, struct snd_uac2_chip, p_prm); + + return uac2; +} + +static inline +uint num_channels(uint chanmask) +{ + uint num = 0; + + while (chanmask) { + num += (chanmask & 1); + chanmask >>= 1; + } + + return num; +} + +static void +agdev_iso_complete(struct usb_ep *ep, struct usb_request *req) +{ + unsigned pending; + unsigned long flags; + bool update_alsa = false; + unsigned char *src, *dst; + int status = req->status; + struct uac2_req *ur = req->context; + struct snd_pcm_substream *substream; + struct uac2_rtd_params *prm = ur->pp; + struct snd_uac2_chip *uac2 = prm_to_uac2(prm); + + /* i/f shutting down */ + if (!prm->ep_enabled) + return; + + /* + * We can't really do much about bad xfers. + * Afterall, the ISOCH xfers could fail legitimately. + */ + if (status) + pr_debug("%s: iso_complete status(%d) %d/%d\n", + __func__, status, req->actual, req->length); + + substream = prm->ss; + + /* Do nothing if ALSA isn't active */ + if (!substream) + goto exit; + + spin_lock_irqsave(&prm->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + src = prm->dma_area + prm->hw_ptr; + req->actual = req->length; + dst = req->buf; + } else { + dst = prm->dma_area + prm->hw_ptr; + src = req->buf; + } + + pending = prm->hw_ptr % prm->period_size; + pending += req->actual; + if (pending >= prm->period_size) + update_alsa = true; + + prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; + + spin_unlock_irqrestore(&prm->lock, flags); + + /* Pack USB load in ALSA ring buffer */ + memcpy(dst, src, req->actual); +exit: + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__); + + if (update_alsa) + snd_pcm_period_elapsed(substream); + + return; +} + +static int +uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); + struct uac2_rtd_params *prm; + unsigned long flags; + int err = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac2->p_prm; + else + prm = &uac2->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + + /* Reset */ + prm->hw_ptr = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + prm->ss = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + prm->ss = NULL; + break; + default: + err = -EINVAL; + } + + spin_unlock_irqrestore(&prm->lock, flags); + + /* Clear buffer after Play stops */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) + memset(prm->rbuf, 0, prm->max_psize * USB_XFERS); + + return err; +} + +static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); + struct uac2_rtd_params *prm; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac2->p_prm; + else + prm = &uac2->c_prm; + + return bytes_to_frames(substream->runtime, prm->hw_ptr); +} + +static int uac2_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); + struct uac2_rtd_params *prm; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac2->p_prm; + else + prm = &uac2->c_prm; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err >= 0) { + prm->dma_bytes = substream->runtime->dma_bytes; + prm->dma_area = substream->runtime->dma_area; + prm->period_size = params_period_bytes(hw_params); + } + + return err; +} + +static int uac2_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); + struct uac2_rtd_params *prm; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac2->p_prm; + else + prm = &uac2->c_prm; + + prm->dma_area = NULL; + prm->dma_bytes = 0; + prm->period_size = 0; + + return snd_pcm_lib_free_pages(substream); +} + +static int uac2_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = uac2_pcm_hardware; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + spin_lock_init(&uac2->p_prm.lock); + runtime->hw.rate_min = p_srate; + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! p_ssize ! */ + runtime->hw.channels_min = num_channels(p_chmask); + runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize + / runtime->hw.periods_min; + } else { + spin_lock_init(&uac2->c_prm.lock); + runtime->hw.rate_min = c_srate; + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! c_ssize ! */ + runtime->hw.channels_min = num_channels(c_chmask); + runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize + / runtime->hw.periods_min; + } + + runtime->hw.rate_max = runtime->hw.rate_min; + runtime->hw.channels_max = runtime->hw.channels_min; + + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + + return 0; +} + +/* ALSA cries without these function pointers */ +static int uac2_pcm_null(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_pcm_ops uac2_pcm_ops = { + .open = uac2_pcm_open, + .close = uac2_pcm_null, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = uac2_pcm_hw_params, + .hw_free = uac2_pcm_hw_free, + .trigger = uac2_pcm_trigger, + .pointer = uac2_pcm_pointer, + .prepare = uac2_pcm_null, +}; + +static int snd_uac2_probe(struct platform_device *pdev) +{ + struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev); + struct snd_card *card; + struct snd_pcm *pcm; + int err; + + /* Choose any slot, with no id */ + err = snd_card_create(-1, NULL, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + uac2->card = card; + + /* + * Create first PCM device + * Create a substream only for non-zero channel streams + */ + err = snd_pcm_new(uac2->card, "UAC2 PCM", 0, + p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); + if (err < 0) + goto snd_fail; + + strcpy(pcm->name, "UAC2 PCM"); + pcm->private_data = uac2; + + uac2->pcm = pcm; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops); + + strcpy(card->driver, "UAC2_Gadget"); + strcpy(card->shortname, "UAC2_Gadget"); + sprintf(card->longname, "UAC2_Gadget %i", pdev->id); + + snd_card_set_dev(card, &pdev->dev); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); + + err = snd_card_register(card); + if (!err) { + platform_set_drvdata(pdev, card); + return 0; + } + +snd_fail: + snd_card_free(card); + + uac2->pcm = NULL; + uac2->card = NULL; + + return err; +} + +static int snd_uac2_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + + if (card) + return snd_card_free(card); + + return 0; +} + +static int alsa_uac2_init(struct audio_dev *agdev) +{ + struct snd_uac2_chip *uac2 = &agdev->uac2; + int err; + + uac2->pdrv.probe = snd_uac2_probe; + uac2->pdrv.remove = snd_uac2_remove; + uac2->pdrv.driver.name = uac2_name; + + uac2->pdev.id = 0; + uac2->pdev.name = uac2_name; + + /* Register snd_uac2 driver */ + err = platform_driver_register(&uac2->pdrv); + if (err) + return err; + + /* Register snd_uac2 device */ + err = platform_device_register(&uac2->pdev); + if (err) + platform_driver_unregister(&uac2->pdrv); + + return err; +} + +static void alsa_uac2_exit(struct audio_dev *agdev) +{ + struct snd_uac2_chip *uac2 = &agdev->uac2; + + platform_driver_unregister(&uac2->pdrv); + platform_device_unregister(&uac2->pdev); +} + + +/* --------- USB Function Interface ------------- */ + +enum { + STR_ASSOC, + STR_IF_CTRL, + STR_CLKSRC_IN, + STR_CLKSRC_OUT, + STR_USB_IT, + STR_IO_IT, + STR_USB_OT, + STR_IO_OT, + STR_AS_OUT_ALT0, + STR_AS_OUT_ALT1, + STR_AS_IN_ALT0, + STR_AS_IN_ALT1, +}; + +static char clksrc_in[8]; +static char clksrc_out[8]; + +static struct usb_string strings_fn[] = { + [STR_ASSOC].s = "Source/Sink", + [STR_IF_CTRL].s = "Topology Control", + [STR_CLKSRC_IN].s = clksrc_in, + [STR_CLKSRC_OUT].s = clksrc_out, + [STR_USB_IT].s = "USBH Out", + [STR_IO_IT].s = "USBD Out", + [STR_USB_OT].s = "USBH In", + [STR_IO_OT].s = "USBD In", + [STR_AS_OUT_ALT0].s = "Playback Inactive", + [STR_AS_OUT_ALT1].s = "Playback Active", + [STR_AS_IN_ALT0].s = "Capture Inactive", + [STR_AS_IN_ALT1].s = "Capture Active", + { }, +}; + +static struct usb_gadget_strings str_fn = { + .language = 0x0409, /* en-us */ + .strings = strings_fn, +}; + +static struct usb_gadget_strings *fn_strings[] = { + &str_fn, + NULL, +}; + +static struct usb_qualifier_descriptor devqual_desc = { + .bLength = sizeof devqual_desc, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + + .bcdUSB = cpu_to_le16(0x200), + .bDeviceClass = USB_CLASS_MISC, + .bDeviceSubClass = 0x02, + .bDeviceProtocol = 0x01, + .bNumConfigurations = 1, + .bRESERVED = 0, +}; + +static struct usb_interface_assoc_descriptor iad_desc = { + .bLength = sizeof iad_desc, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + .bFirstInterface = 0, + .bInterfaceCount = 3, + .bFunctionClass = USB_CLASS_AUDIO, + .bFunctionSubClass = UAC2_FUNCTION_SUBCLASS_UNDEFINED, + .bFunctionProtocol = UAC_VERSION_2, +}; + +/* Audio Control Interface */ +static struct usb_interface_descriptor std_ac_if_desc = { + .bLength = sizeof std_ac_if_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .bInterfaceProtocol = UAC_VERSION_2, +}; + +/* Clock source for IN traffic */ +struct uac_clock_source_descriptor in_clk_src_desc = { + .bLength = sizeof in_clk_src_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC2_CLOCK_SOURCE, + .bClockID = USB_IN_CLK_ID, + .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED, + .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL), + .bAssocTerminal = 0, +}; + +/* Clock source for OUT traffic */ +struct uac_clock_source_descriptor out_clk_src_desc = { + .bLength = sizeof out_clk_src_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC2_CLOCK_SOURCE, + .bClockID = USB_OUT_CLK_ID, + .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED, + .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL), + .bAssocTerminal = 0, +}; + +/* Input Terminal for USB_OUT */ +struct uac2_input_terminal_descriptor usb_out_it_desc = { + .bLength = sizeof usb_out_it_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = USB_OUT_IT_ID, + .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING), + .bAssocTerminal = 0, + .bCSourceID = USB_OUT_CLK_ID, + .iChannelNames = 0, + .bmControls = (CONTROL_RDWR << COPY_CTRL), +}; + +/* Input Terminal for I/O-In */ +struct uac2_input_terminal_descriptor io_in_it_desc = { + .bLength = sizeof io_in_it_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = IO_IN_IT_ID, + .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_UNDEFINED), + .bAssocTerminal = 0, + .bCSourceID = USB_IN_CLK_ID, + .iChannelNames = 0, + .bmControls = (CONTROL_RDWR << COPY_CTRL), +}; + +/* Ouput Terminal for USB_IN */ +struct uac2_output_terminal_descriptor usb_in_ot_desc = { + .bLength = sizeof usb_in_ot_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = USB_IN_OT_ID, + .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING), + .bAssocTerminal = 0, + .bSourceID = IO_IN_IT_ID, + .bCSourceID = USB_IN_CLK_ID, + .bmControls = (CONTROL_RDWR << COPY_CTRL), +}; + +/* Ouput Terminal for I/O-Out */ +struct uac2_output_terminal_descriptor io_out_ot_desc = { + .bLength = sizeof io_out_ot_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = IO_OUT_OT_ID, + .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_UNDEFINED), + .bAssocTerminal = 0, + .bSourceID = USB_OUT_IT_ID, + .bCSourceID = USB_OUT_CLK_ID, + .bmControls = (CONTROL_RDWR << COPY_CTRL), +}; + +struct uac2_ac_header_descriptor ac_hdr_desc = { + .bLength = sizeof ac_hdr_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC_MS_HEADER, + .bcdADC = cpu_to_le16(0x200), + .bCategory = UAC2_FUNCTION_IO_BOX, + .wTotalLength = sizeof in_clk_src_desc + sizeof out_clk_src_desc + + sizeof usb_out_it_desc + sizeof io_in_it_desc + + sizeof usb_in_ot_desc + sizeof io_out_ot_desc, + .bmControls = 0, +}; + +/* Audio Streaming OUT Interface - Alt0 */ +static struct usb_interface_descriptor std_as_out_if0_desc = { + .bLength = sizeof std_as_out_if0_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, + .bInterfaceProtocol = UAC_VERSION_2, +}; + +/* Audio Streaming OUT Interface - Alt1 */ +static struct usb_interface_descriptor std_as_out_if1_desc = { + .bLength = sizeof std_as_out_if1_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, + .bInterfaceProtocol = UAC_VERSION_2, +}; + +/* Audio Stream OUT Intface Desc */ +struct uac2_as_header_descriptor as_out_hdr_desc = { + .bLength = sizeof as_out_hdr_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = USB_OUT_IT_ID, + .bmControls = 0, + .bFormatType = UAC_FORMAT_TYPE_I, + .bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM), + .iChannelNames = 0, +}; + +/* Audio USB_OUT Format */ +struct uac2_format_type_i_descriptor as_out_fmt1_desc = { + .bLength = sizeof as_out_fmt1_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, +}; + +/* STD AS ISO OUT Endpoint */ +struct usb_endpoint_descriptor fs_epout_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bInterval = 1, +}; + +struct usb_endpoint_descriptor hs_epout_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bInterval = 4, +}; + +/* CS AS ISO OUT Endpoint */ +static struct uac2_iso_endpoint_descriptor as_iso_out_desc = { + .bLength = sizeof as_iso_out_desc, + .bDescriptorType = USB_DT_CS_ENDPOINT, + + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 0, + .bmControls = 0, + .bLockDelayUnits = 0, + .wLockDelay = 0, +}; + +/* Audio Streaming IN Interface - Alt0 */ +static struct usb_interface_descriptor std_as_in_if0_desc = { + .bLength = sizeof std_as_in_if0_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, + .bInterfaceProtocol = UAC_VERSION_2, +}; + +/* Audio Streaming IN Interface - Alt1 */ +static struct usb_interface_descriptor std_as_in_if1_desc = { + .bLength = sizeof std_as_in_if1_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, + .bInterfaceProtocol = UAC_VERSION_2, +}; + +/* Audio Stream IN Intface Desc */ +struct uac2_as_header_descriptor as_in_hdr_desc = { + .bLength = sizeof as_in_hdr_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = USB_IN_OT_ID, + .bmControls = 0, + .bFormatType = UAC_FORMAT_TYPE_I, + .bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM), + .iChannelNames = 0, +}; + +/* Audio USB_IN Format */ +struct uac2_format_type_i_descriptor as_in_fmt1_desc = { + .bLength = sizeof as_in_fmt1_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, +}; + +/* STD AS ISO IN Endpoint */ +struct usb_endpoint_descriptor fs_epin_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bInterval = 1, +}; + +struct usb_endpoint_descriptor hs_epin_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bInterval = 4, +}; + +/* CS AS ISO IN Endpoint */ +static struct uac2_iso_endpoint_descriptor as_iso_in_desc = { + .bLength = sizeof as_iso_in_desc, + .bDescriptorType = USB_DT_CS_ENDPOINT, + + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 0, + .bmControls = 0, + .bLockDelayUnits = 0, + .wLockDelay = 0, +}; + +static struct usb_descriptor_header *fs_audio_desc[] = { + (struct usb_descriptor_header *)&iad_desc, + (struct usb_descriptor_header *)&std_ac_if_desc, + + (struct usb_descriptor_header *)&ac_hdr_desc, + (struct usb_descriptor_header *)&in_clk_src_desc, + (struct usb_descriptor_header *)&out_clk_src_desc, + (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + + (struct usb_descriptor_header *)&std_as_out_if0_desc, + (struct usb_descriptor_header *)&std_as_out_if1_desc, + + (struct usb_descriptor_header *)&as_out_hdr_desc, + (struct usb_descriptor_header *)&as_out_fmt1_desc, + (struct usb_descriptor_header *)&fs_epout_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + + (struct usb_descriptor_header *)&std_as_in_if0_desc, + (struct usb_descriptor_header *)&std_as_in_if1_desc, + + (struct usb_descriptor_header *)&as_in_hdr_desc, + (struct usb_descriptor_header *)&as_in_fmt1_desc, + (struct usb_descriptor_header *)&fs_epin_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_audio_desc[] = { + (struct usb_descriptor_header *)&iad_desc, + (struct usb_descriptor_header *)&std_ac_if_desc, + + (struct usb_descriptor_header *)&ac_hdr_desc, + (struct usb_descriptor_header *)&in_clk_src_desc, + (struct usb_descriptor_header *)&out_clk_src_desc, + (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + + (struct usb_descriptor_header *)&std_as_out_if0_desc, + (struct usb_descriptor_header *)&std_as_out_if1_desc, + + (struct usb_descriptor_header *)&as_out_hdr_desc, + (struct usb_descriptor_header *)&as_out_fmt1_desc, + (struct usb_descriptor_header *)&hs_epout_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + + (struct usb_descriptor_header *)&std_as_in_if0_desc, + (struct usb_descriptor_header *)&std_as_in_if1_desc, + + (struct usb_descriptor_header *)&as_in_hdr_desc, + (struct usb_descriptor_header *)&as_in_fmt1_desc, + (struct usb_descriptor_header *)&hs_epin_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +struct cntrl_cur_lay3 { + __u32 dCUR; +}; + +struct cntrl_range_lay3 { + __u16 wNumSubRanges; + __u32 dMIN; + __u32 dMAX; + __u32 dRES; +} __packed; + +static inline void +free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep) +{ + struct snd_uac2_chip *uac2 = prm_to_uac2(prm); + int i; + + prm->ep_enabled = false; + + for (i = 0; i < USB_XFERS; i++) { + if (prm->ureq[i].req) { + usb_ep_dequeue(ep, prm->ureq[i].req); + usb_ep_free_request(ep, prm->ureq[i].req); + prm->ureq[i].req = NULL; + } + } + + if (usb_ep_disable(ep)) + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); +} + +static int __init +afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) +{ + struct audio_dev *agdev = func_to_agdev(fn); + struct snd_uac2_chip *uac2 = &agdev->uac2; + struct usb_composite_dev *cdev = cfg->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct uac2_rtd_params *prm; + int ret; + + ret = usb_interface_id(cfg, fn); + if (ret < 0) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + std_ac_if_desc.bInterfaceNumber = ret; + agdev->ac_intf = ret; + agdev->ac_alt = 0; + + ret = usb_interface_id(cfg, fn); + if (ret < 0) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + std_as_out_if0_desc.bInterfaceNumber = ret; + std_as_out_if1_desc.bInterfaceNumber = ret; + agdev->as_out_intf = ret; + agdev->as_out_alt = 0; + + ret = usb_interface_id(cfg, fn); + if (ret < 0) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + std_as_in_if0_desc.bInterfaceNumber = ret; + std_as_in_if1_desc.bInterfaceNumber = ret; + agdev->as_in_intf = ret; + agdev->as_in_alt = 0; + + agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); + if (!agdev->out_ep) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + goto err; + } + agdev->out_ep->driver_data = agdev; + + agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc); + if (!agdev->in_ep) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + goto err; + } + agdev->in_ep->driver_data = agdev; + + hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; + hs_epout_desc.wMaxPacketSize = fs_epout_desc.wMaxPacketSize; + hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; + hs_epin_desc.wMaxPacketSize = fs_epin_desc.wMaxPacketSize; + + ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, NULL); + if (ret) + goto err; + + prm = &agdev->uac2.c_prm; + prm->max_psize = hs_epout_desc.wMaxPacketSize; + prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL); + if (!prm->rbuf) { + prm->max_psize = 0; + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + goto err; + } + + prm = &agdev->uac2.p_prm; + prm->max_psize = hs_epin_desc.wMaxPacketSize; + prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL); + if (!prm->rbuf) { + prm->max_psize = 0; + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + goto err; + } + + ret = alsa_uac2_init(agdev); + if (ret) + goto err; + return 0; +err: + kfree(agdev->uac2.p_prm.rbuf); + kfree(agdev->uac2.c_prm.rbuf); + usb_free_all_descriptors(fn); + if (agdev->in_ep) + agdev->in_ep->driver_data = NULL; + if (agdev->out_ep) + agdev->out_ep->driver_data = NULL; + return -EINVAL; +} + +static void +afunc_unbind(struct usb_configuration *cfg, struct usb_function *fn) +{ + struct audio_dev *agdev = func_to_agdev(fn); + struct uac2_rtd_params *prm; + + alsa_uac2_exit(agdev); + + prm = &agdev->uac2.p_prm; + kfree(prm->rbuf); + + prm = &agdev->uac2.c_prm; + kfree(prm->rbuf); + usb_free_all_descriptors(fn); + + if (agdev->in_ep) + agdev->in_ep->driver_data = NULL; + if (agdev->out_ep) + agdev->out_ep->driver_data = NULL; +} + +static int +afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) +{ + struct usb_composite_dev *cdev = fn->config->cdev; + struct audio_dev *agdev = func_to_agdev(fn); + struct snd_uac2_chip *uac2 = &agdev->uac2; + struct usb_gadget *gadget = cdev->gadget; + struct usb_request *req; + struct usb_ep *ep; + struct uac2_rtd_params *prm; + int i; + + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (intf == agdev->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + return 0; + } + + if (intf == agdev->as_out_intf) { + ep = agdev->out_ep; + prm = &uac2->c_prm; + config_ep_by_speed(gadget, fn, ep); + agdev->as_out_alt = alt; + } else if (intf == agdev->as_in_intf) { + ep = agdev->in_ep; + prm = &uac2->p_prm; + config_ep_by_speed(gadget, fn, ep); + agdev->as_in_alt = alt; + } else { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (alt == 0) { + free_ep(prm, ep); + return 0; + } + + prm->ep_enabled = true; + usb_ep_enable(ep); + + for (i = 0; i < USB_XFERS; i++) { + if (prm->ureq[i].req) { + if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) + dev_err(&uac2->pdev.dev, "%d Error!\n", + __LINE__); + continue; + } + + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req == NULL) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + prm->ureq[i].req = req; + prm->ureq[i].pp = prm; + + req->zero = 0; + req->context = &prm->ureq[i]; + req->length = prm->max_psize; + req->complete = agdev_iso_complete; + req->buf = prm->rbuf + i * req->length; + + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__); + } + + return 0; +} + +static int +afunc_get_alt(struct usb_function *fn, unsigned intf) +{ + struct audio_dev *agdev = func_to_agdev(fn); + struct snd_uac2_chip *uac2 = &agdev->uac2; + + if (intf == agdev->ac_intf) + return agdev->ac_alt; + else if (intf == agdev->as_out_intf) + return agdev->as_out_alt; + else if (intf == agdev->as_in_intf) + return agdev->as_in_alt; + else + dev_err(&uac2->pdev.dev, + "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; +} + +static void +afunc_disable(struct usb_function *fn) +{ + struct audio_dev *agdev = func_to_agdev(fn); + struct snd_uac2_chip *uac2 = &agdev->uac2; + + free_ep(&uac2->p_prm, agdev->in_ep); + agdev->as_in_alt = 0; + + free_ep(&uac2->c_prm, agdev->out_ep); + agdev->as_out_alt = 0; +} + +static int +in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct audio_dev *agdev = func_to_agdev(fn); + struct snd_uac2_chip *uac2 = &agdev->uac2; + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + struct cntrl_cur_lay3 c; + + if (entity_id == USB_IN_CLK_ID) + c.dCUR = p_srate; + else if (entity_id == USB_OUT_CLK_ID) + c.dCUR = c_srate; + + value = min_t(unsigned, w_length, sizeof c); + memcpy(req->buf, &c, value); + } else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) { + *(u8 *)req->buf = 1; + value = min_t(unsigned, w_length, 1); + } else { + dev_err(&uac2->pdev.dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + + return value; +} + +static int +in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct audio_dev *agdev = func_to_agdev(fn); + struct snd_uac2_chip *uac2 = &agdev->uac2; + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + struct cntrl_range_lay3 r; + int value = -EOPNOTSUPP; + + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + if (entity_id == USB_IN_CLK_ID) + r.dMIN = p_srate; + else if (entity_id == USB_OUT_CLK_ID) + r.dMIN = c_srate; + else + return -EOPNOTSUPP; + + r.dMAX = r.dMIN; + r.dRES = 0; + r.wNumSubRanges = 1; + + value = min_t(unsigned, w_length, sizeof r); + memcpy(req->buf, &r, value); + } else { + dev_err(&uac2->pdev.dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + + return value; +} + +static int +ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + if (cr->bRequest == UAC2_CS_CUR) + return in_rq_cur(fn, cr); + else if (cr->bRequest == UAC2_CS_RANGE) + return in_rq_range(fn, cr); + else + return -EOPNOTSUPP; +} + +static int +out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_value = le16_to_cpu(cr->wValue); + u8 control_selector = w_value >> 8; + + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) + return w_length; + + return -EOPNOTSUPP; +} + +static int +setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct audio_dev *agdev = func_to_agdev(fn); + struct snd_uac2_chip *uac2 = &agdev->uac2; + u16 w_index = le16_to_cpu(cr->wIndex); + u8 intf = w_index & 0xff; + + if (intf != agdev->ac_intf) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + return -EOPNOTSUPP; + } + + if (cr->bRequestType & USB_DIR_IN) + return ac_rq_in(fn, cr); + else if (cr->bRequest == UAC2_CS_CUR) + return out_rq_cur(fn, cr); + + return -EOPNOTSUPP; +} + +static int +afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_composite_dev *cdev = fn->config->cdev; + struct audio_dev *agdev = func_to_agdev(fn); + struct snd_uac2_chip *uac2 = &agdev->uac2; + struct usb_request *req = cdev->req; + u16 w_length = le16_to_cpu(cr->wLength); + int value = -EOPNOTSUPP; + + /* Only Class specific requests are supposed to reach here */ + if ((cr->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) + return -EOPNOTSUPP; + + if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE) + value = setup_rq_inf(fn, cr); + else + dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__, __LINE__); + + if (value >= 0) { + req->length = value; + req->zero = value < w_length; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + dev_err(&uac2->pdev.dev, + "%s:%d Error!\n", __func__, __LINE__); + req->status = 0; + } + } + + return value; +} + +static int audio_bind_config(struct usb_configuration *cfg) +{ + int res; + + agdev_g = kzalloc(sizeof *agdev_g, GFP_KERNEL); + if (agdev_g == NULL) { + printk(KERN_ERR "Unable to allocate audio gadget\n"); + return -ENOMEM; + } + + res = usb_string_ids_tab(cfg->cdev, strings_fn); + if (res) + return res; + iad_desc.iFunction = strings_fn[STR_ASSOC].id; + std_ac_if_desc.iInterface = strings_fn[STR_IF_CTRL].id; + in_clk_src_desc.iClockSource = strings_fn[STR_CLKSRC_IN].id; + out_clk_src_desc.iClockSource = strings_fn[STR_CLKSRC_OUT].id; + usb_out_it_desc.iTerminal = strings_fn[STR_USB_IT].id; + io_in_it_desc.iTerminal = strings_fn[STR_IO_IT].id; + usb_in_ot_desc.iTerminal = strings_fn[STR_USB_OT].id; + io_out_ot_desc.iTerminal = strings_fn[STR_IO_OT].id; + std_as_out_if0_desc.iInterface = strings_fn[STR_AS_OUT_ALT0].id; + std_as_out_if1_desc.iInterface = strings_fn[STR_AS_OUT_ALT1].id; + std_as_in_if0_desc.iInterface = strings_fn[STR_AS_IN_ALT0].id; + std_as_in_if1_desc.iInterface = strings_fn[STR_AS_IN_ALT1].id; + + agdev_g->func.name = "uac2_func"; + agdev_g->func.strings = fn_strings; + agdev_g->func.bind = afunc_bind; + agdev_g->func.unbind = afunc_unbind; + agdev_g->func.set_alt = afunc_set_alt; + agdev_g->func.get_alt = afunc_get_alt; + agdev_g->func.disable = afunc_disable; + agdev_g->func.setup = afunc_setup; + + /* Initialize the configurable parameters */ + usb_out_it_desc.bNrChannels = num_channels(c_chmask); + usb_out_it_desc.bmChannelConfig = cpu_to_le32(c_chmask); + io_in_it_desc.bNrChannels = num_channels(p_chmask); + io_in_it_desc.bmChannelConfig = cpu_to_le32(p_chmask); + as_out_hdr_desc.bNrChannels = num_channels(c_chmask); + as_out_hdr_desc.bmChannelConfig = cpu_to_le32(c_chmask); + as_in_hdr_desc.bNrChannels = num_channels(p_chmask); + as_in_hdr_desc.bmChannelConfig = cpu_to_le32(p_chmask); + as_out_fmt1_desc.bSubslotSize = c_ssize; + as_out_fmt1_desc.bBitResolution = c_ssize * 8; + as_in_fmt1_desc.bSubslotSize = p_ssize; + as_in_fmt1_desc.bBitResolution = p_ssize * 8; + + snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", p_srate); + snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", c_srate); + + res = usb_add_function(cfg, &agdev_g->func); + if (res < 0) + kfree(agdev_g); + + return res; +} + +static void +uac2_unbind_config(struct usb_configuration *cfg) +{ + kfree(agdev_g); + agdev_g = NULL; +} diff --git a/drivers/usb/gadget/f_uvc.c b/drivers/usb/gadget/f_uvc.c new file mode 100644 index 00000000..38dceddd --- /dev/null +++ b/drivers/usb/gadget/f_uvc.c @@ -0,0 +1,836 @@ +/* + * uvc_gadget.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "uvc.h" + +unsigned int uvc_gadget_trace_param; + +/*-------------------------------------------------------------------------*/ + +/* module parameters specific to the Video streaming endpoint */ +static unsigned int streaming_interval = 1; +module_param(streaming_interval, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(streaming_interval, "1 - 16"); + +static unsigned int streaming_maxpacket = 1024; +module_param(streaming_maxpacket, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(streaming_maxpacket, "1 - 1023 (FS), 1 - 3072 (hs/ss)"); + +static unsigned int streaming_maxburst; +module_param(streaming_maxburst, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(streaming_maxburst, "0 - 15 (ss only)"); + +/* -------------------------------------------------------------------------- + * Function descriptors + */ + +/* string IDs are assigned dynamically */ + +#define UVC_STRING_CONTROL_IDX 0 +#define UVC_STRING_STREAMING_IDX 1 + +static struct usb_string uvc_en_us_strings[] = { + [UVC_STRING_CONTROL_IDX].s = "UVC Camera", + [UVC_STRING_STREAMING_IDX].s = "Video Streaming", + { } +}; + +static struct usb_gadget_strings uvc_stringtab = { + .language = 0x0409, /* en-us */ + .strings = uvc_en_us_strings, +}; + +static struct usb_gadget_strings *uvc_function_strings[] = { + &uvc_stringtab, + NULL, +}; + +#define UVC_INTF_VIDEO_CONTROL 0 +#define UVC_INTF_VIDEO_STREAMING 1 + +#define UVC_STATUS_MAX_PACKET_SIZE 16 /* 16 bytes status */ + +static struct usb_interface_assoc_descriptor uvc_iad __initdata = { + .bLength = sizeof(uvc_iad), + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + .bFirstInterface = 0, + .bInterfaceCount = 2, + .bFunctionClass = USB_CLASS_VIDEO, + .bFunctionSubClass = UVC_SC_VIDEO_INTERFACE_COLLECTION, + .bFunctionProtocol = 0x00, + .iFunction = 0, +}; + +static struct usb_interface_descriptor uvc_control_intf __initdata = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = UVC_INTF_VIDEO_CONTROL, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = UVC_SC_VIDEOCONTROL, + .bInterfaceProtocol = 0x00, + .iInterface = 0, +}; + +static struct usb_endpoint_descriptor uvc_control_ep __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE), + .bInterval = 8, +}; + +static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp __initdata = { + .bLength = sizeof(uvc_ss_control_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* The following 3 values can be tweaked if necessary. */ + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE), +}; + +static struct uvc_control_endpoint_descriptor uvc_control_cs_ep __initdata = { + .bLength = UVC_DT_CONTROL_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubType = UVC_EP_INTERRUPT, + .wMaxTransferSize = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE), +}; + +static struct usb_interface_descriptor uvc_streaming_intf_alt0 __initdata = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = UVC_SC_VIDEOSTREAMING, + .bInterfaceProtocol = 0x00, + .iInterface = 0, +}; + +static struct usb_interface_descriptor uvc_streaming_intf_alt1 __initdata = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = UVC_SC_VIDEOSTREAMING, + .bInterfaceProtocol = 0x00, + .iInterface = 0, +}; + +static struct usb_endpoint_descriptor uvc_fs_streaming_ep __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_ASYNC + | USB_ENDPOINT_XFER_ISOC, + /* The wMaxPacketSize and bInterval values will be initialized from + * module parameters. + */ + .wMaxPacketSize = 0, + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor uvc_hs_streaming_ep __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_ASYNC + | USB_ENDPOINT_XFER_ISOC, + /* The wMaxPacketSize and bInterval values will be initialized from + * module parameters. + */ + .wMaxPacketSize = 0, + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor uvc_ss_streaming_ep __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_ASYNC + | USB_ENDPOINT_XFER_ISOC, + /* The wMaxPacketSize and bInterval values will be initialized from + * module parameters. + */ + .wMaxPacketSize = 0, + .bInterval = 0, +}; + +static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp __initdata = { + .bLength = sizeof(uvc_ss_streaming_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* The following 3 values can be tweaked if necessary. */ + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = cpu_to_le16(1024), +}; + +static const struct usb_descriptor_header * const uvc_fs_streaming[] = { + (struct usb_descriptor_header *) &uvc_streaming_intf_alt1, + (struct usb_descriptor_header *) &uvc_fs_streaming_ep, + NULL, +}; + +static const struct usb_descriptor_header * const uvc_hs_streaming[] = { + (struct usb_descriptor_header *) &uvc_streaming_intf_alt1, + (struct usb_descriptor_header *) &uvc_hs_streaming_ep, + NULL, +}; + +static const struct usb_descriptor_header * const uvc_ss_streaming[] = { + (struct usb_descriptor_header *) &uvc_streaming_intf_alt1, + (struct usb_descriptor_header *) &uvc_ss_streaming_ep, + (struct usb_descriptor_header *) &uvc_ss_streaming_comp, + NULL, +}; + +/* -------------------------------------------------------------------------- + * Control requests + */ + +static void +uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct uvc_device *uvc = req->context; + struct v4l2_event v4l2_event; + struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; + + if (uvc->event_setup_out) { + uvc->event_setup_out = 0; + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_DATA; + uvc_event->data.length = req->actual; + memcpy(&uvc_event->data.data, req->buf, req->actual); + v4l2_event_queue(uvc->vdev, &v4l2_event); + } +} + +static int +uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct uvc_device *uvc = to_uvc(f); + struct v4l2_event v4l2_event; + struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; + + /* printk(KERN_INFO "setup request %02x %02x value %04x index %04x %04x\n", + * ctrl->bRequestType, ctrl->bRequest, le16_to_cpu(ctrl->wValue), + * le16_to_cpu(ctrl->wIndex), le16_to_cpu(ctrl->wLength)); + */ + + if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) { + INFO(f->config->cdev, "invalid request type\n"); + return -EINVAL; + } + + /* Stall too big requests. */ + if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE) + return -EINVAL; + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_SETUP; + memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req)); + v4l2_event_queue(uvc->vdev, &v4l2_event); + + return 0; +} + +void uvc_function_setup_continue(struct uvc_device *uvc) +{ + struct usb_composite_dev *cdev = uvc->func.config->cdev; + + usb_composite_setup_continue(cdev); +} + +static int +uvc_function_get_alt(struct usb_function *f, unsigned interface) +{ + struct uvc_device *uvc = to_uvc(f); + + INFO(f->config->cdev, "uvc_function_get_alt(%u)\n", interface); + + if (interface == uvc->control_intf) + return 0; + else if (interface != uvc->streaming_intf) + return -EINVAL; + else + return uvc->state == UVC_STATE_STREAMING ? 1 : 0; +} + +static int +uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) +{ + struct uvc_device *uvc = to_uvc(f); + struct v4l2_event v4l2_event; + struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; + int ret; + + INFO(f->config->cdev, "uvc_function_set_alt(%u, %u)\n", interface, alt); + + if (interface == uvc->control_intf) { + if (alt) + return -EINVAL; + + if (uvc->state == UVC_STATE_DISCONNECTED) { + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_CONNECT; + uvc_event->speed = f->config->cdev->gadget->speed; + v4l2_event_queue(uvc->vdev, &v4l2_event); + + uvc->state = UVC_STATE_CONNECTED; + } + + return 0; + } + + if (interface != uvc->streaming_intf) + return -EINVAL; + + /* TODO + if (usb_endpoint_xfer_bulk(&uvc->desc.vs_ep)) + return alt ? -EINVAL : 0; + */ + + switch (alt) { + case 0: + if (uvc->state != UVC_STATE_STREAMING) + return 0; + + if (uvc->video.ep) + usb_ep_disable(uvc->video.ep); + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_STREAMOFF; + v4l2_event_queue(uvc->vdev, &v4l2_event); + + uvc->state = UVC_STATE_CONNECTED; + return 0; + + case 1: + if (uvc->state != UVC_STATE_CONNECTED) + return 0; + + if (uvc->video.ep) { + ret = config_ep_by_speed(f->config->cdev->gadget, + &(uvc->func), uvc->video.ep); + if (ret) + return ret; + usb_ep_enable(uvc->video.ep); + } + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_STREAMON; + v4l2_event_queue(uvc->vdev, &v4l2_event); + return USB_GADGET_DELAYED_STATUS; + + default: + return -EINVAL; + } +} + +static void +uvc_function_disable(struct usb_function *f) +{ + struct uvc_device *uvc = to_uvc(f); + struct v4l2_event v4l2_event; + + INFO(f->config->cdev, "uvc_function_disable\n"); + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_DISCONNECT; + v4l2_event_queue(uvc->vdev, &v4l2_event); + + uvc->state = UVC_STATE_DISCONNECTED; +} + +/* -------------------------------------------------------------------------- + * Connection / disconnection + */ + +void +uvc_function_connect(struct uvc_device *uvc) +{ + struct usb_composite_dev *cdev = uvc->func.config->cdev; + int ret; + + if ((ret = usb_function_activate(&uvc->func)) < 0) + INFO(cdev, "UVC connect failed with %d\n", ret); +} + +void +uvc_function_disconnect(struct uvc_device *uvc) +{ + struct usb_composite_dev *cdev = uvc->func.config->cdev; + int ret; + + if ((ret = usb_function_deactivate(&uvc->func)) < 0) + INFO(cdev, "UVC disconnect failed with %d\n", ret); +} + +/* -------------------------------------------------------------------------- + * USB probe and disconnect + */ + +static int +uvc_register_video(struct uvc_device *uvc) +{ + struct usb_composite_dev *cdev = uvc->func.config->cdev; + struct video_device *video; + + /* TODO reference counting. */ + video = video_device_alloc(); + if (video == NULL) + return -ENOMEM; + + video->parent = &cdev->gadget->dev; + video->fops = &uvc_v4l2_fops; + video->release = video_device_release; + strlcpy(video->name, cdev->gadget->name, sizeof(video->name)); + + uvc->vdev = video; + video_set_drvdata(video, uvc); + + return video_register_device(video, VFL_TYPE_GRABBER, -1); +} + +#define UVC_COPY_DESCRIPTOR(mem, dst, desc) \ + do { \ + memcpy(mem, desc, (desc)->bLength); \ + *(dst)++ = mem; \ + mem += (desc)->bLength; \ + } while (0); + +#define UVC_COPY_DESCRIPTORS(mem, dst, src) \ + do { \ + const struct usb_descriptor_header * const *__src; \ + for (__src = src; *__src; ++__src) { \ + memcpy(mem, *__src, (*__src)->bLength); \ + *dst++ = mem; \ + mem += (*__src)->bLength; \ + } \ + } while (0) + +static struct usb_descriptor_header ** __init +uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) +{ + struct uvc_input_header_descriptor *uvc_streaming_header; + struct uvc_header_descriptor *uvc_control_header; + const struct uvc_descriptor_header * const *uvc_control_desc; + const struct uvc_descriptor_header * const *uvc_streaming_cls; + const struct usb_descriptor_header * const *uvc_streaming_std; + const struct usb_descriptor_header * const *src; + struct usb_descriptor_header **dst; + struct usb_descriptor_header **hdr; + unsigned int control_size; + unsigned int streaming_size; + unsigned int n_desc; + unsigned int bytes; + void *mem; + + switch (speed) { + case USB_SPEED_SUPER: + uvc_control_desc = uvc->desc.ss_control; + uvc_streaming_cls = uvc->desc.ss_streaming; + uvc_streaming_std = uvc_ss_streaming; + break; + + case USB_SPEED_HIGH: + uvc_control_desc = uvc->desc.fs_control; + uvc_streaming_cls = uvc->desc.hs_streaming; + uvc_streaming_std = uvc_hs_streaming; + break; + + case USB_SPEED_FULL: + default: + uvc_control_desc = uvc->desc.fs_control; + uvc_streaming_cls = uvc->desc.fs_streaming; + uvc_streaming_std = uvc_fs_streaming; + break; + } + + /* Descriptors layout + * + * uvc_iad + * uvc_control_intf + * Class-specific UVC control descriptors + * uvc_control_ep + * uvc_control_cs_ep + * uvc_ss_control_comp (for SS only) + * uvc_streaming_intf_alt0 + * Class-specific UVC streaming descriptors + * uvc_{fs|hs}_streaming + */ + + /* Count descriptors and compute their size. */ + control_size = 0; + streaming_size = 0; + bytes = uvc_iad.bLength + uvc_control_intf.bLength + + uvc_control_ep.bLength + uvc_control_cs_ep.bLength + + uvc_streaming_intf_alt0.bLength; + + if (speed == USB_SPEED_SUPER) { + bytes += uvc_ss_control_comp.bLength; + n_desc = 6; + } else { + n_desc = 5; + } + + for (src = (const struct usb_descriptor_header **)uvc_control_desc; + *src; ++src) { + control_size += (*src)->bLength; + bytes += (*src)->bLength; + n_desc++; + } + for (src = (const struct usb_descriptor_header **)uvc_streaming_cls; + *src; ++src) { + streaming_size += (*src)->bLength; + bytes += (*src)->bLength; + n_desc++; + } + for (src = uvc_streaming_std; *src; ++src) { + bytes += (*src)->bLength; + n_desc++; + } + + mem = kmalloc((n_desc + 1) * sizeof(*src) + bytes, GFP_KERNEL); + if (mem == NULL) + return NULL; + + hdr = mem; + dst = mem; + mem += (n_desc + 1) * sizeof(*src); + + /* Copy the descriptors. */ + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_iad); + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_intf); + + uvc_control_header = mem; + UVC_COPY_DESCRIPTORS(mem, dst, + (const struct usb_descriptor_header **)uvc_control_desc); + uvc_control_header->wTotalLength = cpu_to_le16(control_size); + uvc_control_header->bInCollection = 1; + uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf; + + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_ep); + if (speed == USB_SPEED_SUPER) + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_control_comp); + + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_cs_ep); + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_streaming_intf_alt0); + + uvc_streaming_header = mem; + UVC_COPY_DESCRIPTORS(mem, dst, + (const struct usb_descriptor_header**)uvc_streaming_cls); + uvc_streaming_header->wTotalLength = cpu_to_le16(streaming_size); + uvc_streaming_header->bEndpointAddress = uvc->video.ep->address; + + UVC_COPY_DESCRIPTORS(mem, dst, uvc_streaming_std); + + *dst = NULL; + return hdr; +} + +static void +uvc_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct uvc_device *uvc = to_uvc(f); + + INFO(cdev, "uvc_function_unbind\n"); + + video_unregister_device(uvc->vdev); + uvc->control_ep->driver_data = NULL; + uvc->video.ep->driver_data = NULL; + + uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id = 0; + usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); + kfree(uvc->control_buf); + + usb_free_all_descriptors(f); + + kfree(uvc); +} + +static int __init +uvc_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct uvc_device *uvc = to_uvc(f); + unsigned int max_packet_mult; + unsigned int max_packet_size; + struct usb_ep *ep; + int ret = -EINVAL; + + INFO(cdev, "uvc_function_bind\n"); + + /* Sanity check the streaming endpoint module parameters. + */ + streaming_interval = clamp(streaming_interval, 1U, 16U); + streaming_maxpacket = clamp(streaming_maxpacket, 1U, 3072U); + streaming_maxburst = min(streaming_maxburst, 15U); + + /* Fill in the FS/HS/SS Video Streaming specific descriptors from the + * module parameters. + * + * NOTE: We assume that the user knows what they are doing and won't + * give parameters that their UDC doesn't support. + */ + if (streaming_maxpacket <= 1024) { + max_packet_mult = 1; + max_packet_size = streaming_maxpacket; + } else if (streaming_maxpacket <= 2048) { + max_packet_mult = 2; + max_packet_size = streaming_maxpacket / 2; + } else { + max_packet_mult = 3; + max_packet_size = streaming_maxpacket / 3; + } + + uvc_fs_streaming_ep.wMaxPacketSize = min(streaming_maxpacket, 1023U); + uvc_fs_streaming_ep.bInterval = streaming_interval; + + uvc_hs_streaming_ep.wMaxPacketSize = max_packet_size; + uvc_hs_streaming_ep.wMaxPacketSize |= ((max_packet_mult - 1) << 11); + uvc_hs_streaming_ep.bInterval = streaming_interval; + + uvc_ss_streaming_ep.wMaxPacketSize = max_packet_size; + uvc_ss_streaming_ep.bInterval = streaming_interval; + uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1; + uvc_ss_streaming_comp.bMaxBurst = streaming_maxburst; + uvc_ss_streaming_comp.wBytesPerInterval = + max_packet_size * max_packet_mult * streaming_maxburst; + + /* Allocate endpoints. */ + ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep); + if (!ep) { + INFO(cdev, "Unable to allocate control EP\n"); + goto error; + } + uvc->control_ep = ep; + ep->driver_data = uvc; + + if (gadget_is_superspeed(c->cdev->gadget)) + ep = usb_ep_autoconfig_ss(cdev->gadget, &uvc_ss_streaming_ep, + &uvc_ss_streaming_comp); + else if (gadget_is_dualspeed(cdev->gadget)) + ep = usb_ep_autoconfig(cdev->gadget, &uvc_hs_streaming_ep); + else + ep = usb_ep_autoconfig(cdev->gadget, &uvc_fs_streaming_ep); + + if (!ep) { + INFO(cdev, "Unable to allocate streaming EP\n"); + goto error; + } + uvc->video.ep = ep; + ep->driver_data = uvc; + + uvc_fs_streaming_ep.bEndpointAddress = uvc->video.ep->address; + uvc_hs_streaming_ep.bEndpointAddress = uvc->video.ep->address; + uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address; + + /* Allocate interface IDs. */ + if ((ret = usb_interface_id(c, f)) < 0) + goto error; + uvc_iad.bFirstInterface = ret; + uvc_control_intf.bInterfaceNumber = ret; + uvc->control_intf = ret; + + if ((ret = usb_interface_id(c, f)) < 0) + goto error; + uvc_streaming_intf_alt0.bInterfaceNumber = ret; + uvc_streaming_intf_alt1.bInterfaceNumber = ret; + uvc->streaming_intf = ret; + + /* Copy descriptors */ + f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL); + if (gadget_is_dualspeed(cdev->gadget)) + f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH); + if (gadget_is_superspeed(c->cdev->gadget)) + f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER); + + /* Preallocate control endpoint request. */ + uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); + uvc->control_buf = kmalloc(UVC_MAX_REQUEST_SIZE, GFP_KERNEL); + if (uvc->control_req == NULL || uvc->control_buf == NULL) { + ret = -ENOMEM; + goto error; + } + + uvc->control_req->buf = uvc->control_buf; + uvc->control_req->complete = uvc_function_ep0_complete; + uvc->control_req->context = uvc; + + /* Avoid letting this gadget enumerate until the userspace server is + * active. + */ + if ((ret = usb_function_deactivate(f)) < 0) + goto error; + + /* Initialise video. */ + ret = uvc_video_init(&uvc->video); + if (ret < 0) + goto error; + + /* Register a V4L2 device. */ + ret = uvc_register_video(uvc); + if (ret < 0) { + printk(KERN_INFO "Unable to register video device\n"); + goto error; + } + + return 0; + +error: + if (uvc->vdev) + video_device_release(uvc->vdev); + + if (uvc->control_ep) + uvc->control_ep->driver_data = NULL; + if (uvc->video.ep) + uvc->video.ep->driver_data = NULL; + + if (uvc->control_req) { + usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); + kfree(uvc->control_buf); + } + + usb_free_all_descriptors(f); + return ret; +} + +/* -------------------------------------------------------------------------- + * USB gadget function + */ + +/** + * uvc_bind_config - add a UVC function to a configuration + * @c: the configuration to support the UVC instance + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @uvc_setup(). Caller is also responsible for + * calling @uvc_cleanup() before module unload. + */ +int __init +uvc_bind_config(struct usb_configuration *c, + const struct uvc_descriptor_header * const *fs_control, + const struct uvc_descriptor_header * const *ss_control, + const struct uvc_descriptor_header * const *fs_streaming, + const struct uvc_descriptor_header * const *hs_streaming, + const struct uvc_descriptor_header * const *ss_streaming) +{ + struct uvc_device *uvc; + int ret = 0; + + /* TODO Check if the USB device controller supports the required + * features. + */ + if (!gadget_is_dualspeed(c->cdev->gadget)) + return -EINVAL; + + uvc = kzalloc(sizeof(*uvc), GFP_KERNEL); + if (uvc == NULL) + return -ENOMEM; + + uvc->state = UVC_STATE_DISCONNECTED; + + /* Validate the descriptors. */ + if (fs_control == NULL || fs_control[0] == NULL || + fs_control[0]->bDescriptorSubType != UVC_VC_HEADER) + goto error; + + if (ss_control == NULL || ss_control[0] == NULL || + ss_control[0]->bDescriptorSubType != UVC_VC_HEADER) + goto error; + + if (fs_streaming == NULL || fs_streaming[0] == NULL || + fs_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER) + goto error; + + if (hs_streaming == NULL || hs_streaming[0] == NULL || + hs_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER) + goto error; + + if (ss_streaming == NULL || ss_streaming[0] == NULL || + ss_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER) + goto error; + + uvc->desc.fs_control = fs_control; + uvc->desc.ss_control = ss_control; + uvc->desc.fs_streaming = fs_streaming; + uvc->desc.hs_streaming = hs_streaming; + uvc->desc.ss_streaming = ss_streaming; + + /* String descriptors are global, we only need to allocate string IDs + * for the first UVC function. UVC functions beyond the first (if any) + * will reuse the same IDs. + */ + if (uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id == 0) { + ret = usb_string_ids_tab(c->cdev, uvc_en_us_strings); + if (ret) + goto error; + uvc_iad.iFunction = + uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id; + uvc_control_intf.iInterface = + uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id; + ret = uvc_en_us_strings[UVC_STRING_STREAMING_IDX].id; + uvc_streaming_intf_alt0.iInterface = ret; + uvc_streaming_intf_alt1.iInterface = ret; + } + + /* Register the function. */ + uvc->func.name = "uvc"; + uvc->func.strings = uvc_function_strings; + uvc->func.bind = uvc_function_bind; + uvc->func.unbind = uvc_function_unbind; + uvc->func.get_alt = uvc_function_get_alt; + uvc->func.set_alt = uvc_function_set_alt; + uvc->func.disable = uvc_function_disable; + uvc->func.setup = uvc_function_setup; + + ret = usb_add_function(c, &uvc->func); + if (ret) + kfree(uvc); + + return ret; + +error: + kfree(uvc); + return ret; +} + +module_param_named(trace, uvc_gadget_trace_param, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(trace, "Trace level bitmask"); + diff --git a/drivers/usb/gadget/f_uvc.h b/drivers/usb/gadget/f_uvc.h new file mode 100644 index 00000000..ec52752f --- /dev/null +++ b/drivers/usb/gadget/f_uvc.h @@ -0,0 +1,27 @@ +/* + * f_uvc.h -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _F_UVC_H_ +#define _F_UVC_H_ + +#include +#include + +int uvc_bind_config(struct usb_configuration *c, + const struct uvc_descriptor_header * const *fs_control, + const struct uvc_descriptor_header * const *hs_control, + const struct uvc_descriptor_header * const *fs_streaming, + const struct uvc_descriptor_header * const *hs_streaming, + const struct uvc_descriptor_header * const *ss_streaming); + +#endif /* _F_UVC_H_ */ + diff --git a/drivers/usb/gadget/f_vserial.c b/drivers/usb/gadget/f_vserial.c new file mode 100755 index 00000000..7481ecc4 --- /dev/null +++ b/drivers/usb/gadget/f_vserial.c @@ -0,0 +1,894 @@ +/* + * Vendor specific serial Gadget Driver use for put modem log. + * From the Gadget side, it is a misc device(No TTY), + * from PC side, sprd's usb2serial driver will enumerate the + * device as a serial device. + * This driver basicly from android adb driver, many thanks to + * Android guys. + * + * Copyright (C) 2011 Spreadtrum, Inc. + * Author: Yingchun Li + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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. + * + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define VSER_BULK_BUFFER_SIZE (4096*4) + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +#ifdef CONFIG_SPRD_IQ +extern int in_iqmode(void); +#endif + +static const char shortname[] = "vser"; + +struct vser_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + int online; + int rd_error; + int wr_error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + int open_count; + + struct list_head tx_idle; + +#ifdef CONFIG_SPRD_IQ + struct list_head tx_iq_idle; +#endif + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; +}; + +#ifdef CONFIG_SPRD_IQ +void (*bulk_in_complete_function)(char *buffer,int length) = NULL; +#endif + +static struct usb_interface_descriptor vser_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, +}; + +static struct usb_endpoint_descriptor vser_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor vser_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor vser_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor vser_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_vser_descs[] = { + (struct usb_descriptor_header *) &vser_interface_desc, + (struct usb_descriptor_header *) &vser_fullspeed_in_desc, + (struct usb_descriptor_header *) &vser_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_vser_descs[] = { + (struct usb_descriptor_header *) &vser_interface_desc, + (struct usb_descriptor_header *) &vser_highspeed_in_desc, + (struct usb_descriptor_header *) &vser_highspeed_out_desc, + NULL, +}; + + +/* temporary variable used between vser_open() and vser_gadget_bind() */ +static struct vser_dev *_vser_dev; + +static inline struct vser_dev *func_to_vser(struct usb_function *f) +{ + return container_of(f, struct vser_dev, function); +} + + +static struct usb_request *vser_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void vser_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int vser_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void vser_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void vser_req_put(struct vser_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *vser_req_get(struct vser_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void vser_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct vser_dev *dev = _vser_dev; + + if (req->status != 0) + dev->wr_error = 1; + + vser_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +#ifdef CONFIG_SPRD_IQ +static void vser_iq_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct vser_dev *dev = _vser_dev; + + //printk("%s(%p) status = %d\n",__func__,req->buf,req->status); + if (req->status != 0) + dev->wr_error = 1; + + vser_req_put(dev, &dev->tx_iq_idle, req); + + if(bulk_in_complete_function!=NULL) + bulk_in_complete_function(req->buf,req->length); + + wake_up(&dev->write_wq); +} +#endif + +static void vser_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct vser_dev *dev = _vser_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->rd_error = 1; + + wake_up(&dev->read_wq); +} + +static int vser_create_bulk_endpoints(struct vser_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "vser_create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for vser ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + req = vser_request_new(dev->ep_out, VSER_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = vser_complete_out; + dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = vser_request_new(dev->ep_in, VSER_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = vser_complete_in; + vser_req_put(dev, &dev->tx_idle, req); + } + +#ifdef CONFIG_SPRD_IQ + if(in_iqmode()){ + for (i = 0; i < TX_REQ_MAX; i++) { + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + goto fail; + req->buf = NULL; + req->complete = vser_iq_complete_in; + vser_req_put(dev, &dev->tx_iq_idle, req); + } + } +#endif + + return 0; + +fail: + printk(KERN_ERR "vser_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t vser_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct vser_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int r = count, xfer; + int ret; + + DBG(cdev, "vser_read(%d)\n", count); + + if (count > VSER_BULK_BUFFER_SIZE) + return -EINVAL; + + if (vser_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->rd_error)) { + DBG(cdev, "vser_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->rd_error)); + if (ret < 0) { + vser_unlock(&dev->read_excl); + return ret; + } + } + if (dev->rd_error) { + r = -EIO; + goto done; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); + if (ret < 0) { + DBG(cdev, "vser_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + dev->rd_error = 1; + goto done; + } else { + DBG(cdev, "rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + dev->rd_error = 1; + r = ret; + goto done; + } + if (!dev->rd_error) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + DBG(cdev, "rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + vser_unlock(&dev->read_excl); + DBG(cdev, "vser_read returning %d\n", r); + return r; +} + +static ssize_t vser_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct vser_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + +#ifdef CONFIG_SPRD_IQ + if(in_iqmode()){ + msleep(100); + return count; + } +#endif + DBG(cdev, "vser_write(%d)\n", count); + + if (vser_lock(&dev->write_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->wr_error)) { + DBG(cdev, "vser_write: waiting for online state\n"); + ret = wait_event_interruptible(dev->write_wq, + (dev->online || dev->wr_error)); + if (ret < 0) { + vser_unlock(&dev->write_excl); + return ret; + } + } + while (count > 0) { + if (dev->wr_error) { + DBG(cdev, "vser_write dev->wr_error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + + if(list_empty(&dev->tx_idle)){ + printk("%s: tx buffer is full!!!!\n",__func__); + } + ret = wait_event_interruptible(dev->write_wq, + ((req = vser_req_get(dev, &dev->tx_idle)) || dev->wr_error)); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + if (count > VSER_BULK_BUFFER_SIZE) + xfer = VSER_BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + DBG(cdev, "vser_write: xfer error %d\n", ret); + dev->wr_error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + vser_req_put(dev, &dev->tx_idle, req); + + vser_unlock(&dev->write_excl); + DBG(cdev, "vser_write returning %d\n", r); + return r; +} + +static int vser_open(struct inode *ip, struct file *fp) +{ + struct vser_dev *dev = _vser_dev; + + /* + if (_lock(&_vser_dev->open_excl)) + return -EBUSY; + */ + spin_lock_irq(&dev->lock); + fp->private_data = dev; + + dev->open_count++; + if (dev->open_count > 2){ + dev->open_count--; + pr_warning("too many user open vser\n"); + spin_unlock_irq(&dev->lock); + return -EBUSY; + } + /* clear the error latch */ + dev->wr_error = 0; + dev->rd_error = 0; + + spin_unlock_irq(&dev->lock); + printk(KERN_INFO "vser_open %d times\n", dev->open_count); + return 0; +} + +static int vser_release(struct inode *ip, struct file *fp) +{ + struct vser_dev *dev = _vser_dev; + + printk(KERN_INFO "vser_release %d\n", dev->open_count); + + spin_lock_irq(&dev->lock); + dev->open_count--; + spin_unlock_irq(&dev->lock); + //_unlock(&_vser_dev->open_excl); + return 0; +} + +/* file operations for ADB device /dev/android_vser */ +static struct file_operations vser_fops = { + .owner = THIS_MODULE, + .read = vser_read, + .write = vser_write, + .open = vser_open, + .release = vser_release, +}; + +static struct miscdevice vser_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = shortname, + .fops = &vser_fops, +}; + + +static int +vser_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct vser_dev *dev = func_to_vser(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "vser_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + vser_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = vser_create_bulk_endpoints(dev, &vser_fullspeed_in_desc, + &vser_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + vser_highspeed_in_desc.bEndpointAddress = + vser_fullspeed_in_desc.bEndpointAddress; + vser_highspeed_out_desc.bEndpointAddress = + vser_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +vser_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct vser_dev *dev = func_to_vser(f); + struct usb_request *req; + + pr_info("%s\n", __func__); + //spin_lock_irq(&dev->lock); + vser_request_free(dev->rx_req, dev->ep_out); + while ((req = vser_req_get(dev, &dev->tx_idle))) + vser_request_free(req, dev->ep_in); + +#ifdef CONFIG_SPRD_IQ + if(in_iqmode()){ + while ((req = vser_req_get(dev, &dev->tx_iq_idle))) + usb_ep_free_request(dev->ep_in, req); + } +#endif + + dev->online = 0; + dev->wr_error = 1; + dev->rd_error = 1; + //spin_unlock_irq(&dev->lock); +} + +static int vser_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct vser_dev *dev = func_to_vser(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "vser_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + /* readers may be blocked waiting for us to go online */ +/* + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); +*/ + return 0; +} + +static void vser_function_disable(struct usb_function *f) +{ + struct vser_dev *dev = func_to_vser(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "vser_function_disable\n"); + dev->online = 0; + dev->wr_error = 1; + dev->rd_error = 1; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int vser_setup(struct usb_composite_dev *cdev, const struct usb_ctrlrequest *ctrl) +{ + struct vser_dev *dev = _vser_dev; + u16 w_length = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + int value = -EOPNOTSUPP; + int port_open = false; + + VDBG(cdev, "request :%x request type: %x, wValue %x, wIndex %x, wLength %x\n", + ctrl->bRequest, ctrl->bRequestType, + w_value, + le16_to_cpu(ctrl->wIndex), + w_length); + /* Handle Bulk-only class-specific requests */ + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + switch (ctrl->bRequest) { + case 0x22: + value = 0; + if ((w_value & 0xff) == 1) + port_open = true; + break; + } + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + printk("%s setup response queue error\n", __func__); + } + + if (value == -EOPNOTSUPP) + VDBG(cdev, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + le16_to_cpu(ctrl->wValue), le16_to_cpu(ctrl->wIndex), + le16_to_cpu(ctrl->wLength)); + if (port_open){ + DBG(cdev, "cmd for port open\n"); + dev->online = 1; + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); + } + return value; +} + +static int vser_bind_config(struct usb_configuration *c) +{ + struct vser_dev *dev = _vser_dev; + + printk(KERN_INFO "vser_bind_config\n"); + + pr_info("vserial bind to config %p c->cdev %p\n", c, c->cdev); + pr_info("vserial dev:%p\n", dev); + dev->cdev = c->cdev; + pr_info("vserial set desc\n"); + dev->function.name = "vser"; + dev->function.fs_descriptors = fs_vser_descs; + dev->function.hs_descriptors = hs_vser_descs; + dev->function.bind = vser_function_bind; + dev->function.unbind = vser_function_unbind; + dev->function.set_alt = vser_function_set_alt; + dev->function.disable = vser_function_disable; + + pr_info("now add vserion to composite\n"); + return usb_add_function(c, &dev->function); +} + +#ifdef CONFIG_SPRD_IQ +// #define VSER_IQ_WRITE_TEST +#ifdef VSER_IQ_WRITE_TEST +ssize_t vser_iq_write(char *buf, size_t count); +wait_queue_head_t usb_transfer_event; +int *transfer_buffer=NULL,transfer_size=0; + +static struct timer_list usb_test_timer; +int test_buffer[64*1024]={0},test_data=0x5a; + +void usb_test_timer_fun(unsigned long para) +{ + int data; + data = para; + memset(test_buffer,data,sizeof(test_buffer)); + transfer_buffer = test_buffer; + transfer_size = sizeof(test_buffer); + wake_up_interruptible(&usb_transfer_event); + mod_timer(&usb_test_timer, jiffies + HZ); +} + +static int usb_transfer_worker(void *data) +{ + struct vser_dev *dev = (struct vser_dev *)data; + printk("%s is running ..............\n",__func__); + init_waitqueue_head(&usb_transfer_event); + //setup_timer(&usb_test_timer,usb_test_timer_fun,(unsigned long)&test_data); + //mod_timer(&usb_test_timer, jiffies + HZ*20); + while(1){ + wait_event_interruptible(usb_transfer_event,(transfer_buffer && transfer_size)); + + if(transfer_buffer && transfer_size){ + dev->online = 1; + vser_iq_write(transfer_buffer, transfer_size); + transfer_buffer = NULL; + transfer_size = 0; + } + } +} +#endif +#endif + +static int vser_init(void) +{ + struct vser_dev *dev; + int ret; + + printk(KERN_INFO "vser_init\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev ->write_excl, 0); + dev->open_count = 0; + + INIT_LIST_HEAD(&dev->tx_idle); + +#ifdef CONFIG_SPRD_IQ + INIT_LIST_HEAD(&dev->tx_iq_idle); +#endif + + _vser_dev = dev; + + ret = misc_register(&vser_device); + if (ret) + goto err; + +#ifdef CONFIG_SPRD_IQ + if(in_iqmode()){ +#ifdef VSER_IQ_WRITE_TEST + struct task_struct * task; + + task = kthread_create(usb_transfer_worker, _vser_dev, "USBTranferWorker"); + wake_up_process(task); +#endif + //printk(KERN_INFO "sprd_iq:bulk_in_complete_function set null\n"); + //bulk_in_complete_function = NULL; /*vser_init may be later than the sprd_iq_init */ + } +#endif + return 0; +err: + kfree(dev); + printk(KERN_ERR "vser gadget driver failed to initialize\n"); + return ret; +} + +#ifdef CONFIG_SPRD_IQ +void kernel_vser_register_callback(void *function) +{ + bulk_in_complete_function = function; +} +ssize_t vser_iq_write(char *buf, size_t count) +{ + struct vser_dev *dev = _vser_dev; + //struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + printk("%s(%p,0x%x) epname=%s\n",__func__,buf, count,dev->ep_in->name); + + if (vser_lock(&dev->write_excl)) + return -EBUSY; + + /* we will block until we're online */ +#if 0 + while (!(dev->online || dev->wr_error)) { + printk("%s: waiting for online state\n",__func__); + ret = wait_event_interruptible(dev->write_wq, + (dev->online)); + if (ret < 0) { + vser_unlock(&dev->write_excl); + return ret; + } + } +#endif + while (count > 0) { + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = vser_req_get(dev, &dev->tx_iq_idle)) ); + + if (ret < 0) { + r = ret; + printk("%s line %d wait event failed \n",__func__,__LINE__); + break; + } + + xfer = count; + if (req != 0) { + req->buf = buf; + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + printk("%s: xfer error %d\n",__func__, ret); + dev->wr_error = 1; + r = -EIO; + break; + } + count -= xfer; + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + vser_req_put(dev, &dev->tx_iq_idle, req); + + vser_unlock(&dev->write_excl); + printk("vser_iq_write returning %x\n", r); + return r; +} +#endif + +static void vser_cleanup(void) +{ + pr_info("vendor serial cleanup \n"); + misc_deregister(&vser_device); + + kfree(_vser_dev); + _vser_dev = NULL; + +} diff --git a/drivers/usb/gadget/fsl_mxc_udc.c b/drivers/usb/gadget/fsl_mxc_udc.c new file mode 100644 index 00000000..d3bd7b09 --- /dev/null +++ b/drivers/usb/gadget/fsl_mxc_udc.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2009 + * Guennadi Liakhovetski, DENX Software Engineering, + * + * Description: + * Helper routines for i.MX3x SoCs from Freescale, needed by the fsl_usb2_udc.c + * driver to function correctly on these systems. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include +#include +#include + +static struct clk *mxc_ahb_clk; +static struct clk *mxc_per_clk; +static struct clk *mxc_ipg_clk; + +/* workaround ENGcm09152 for i.MX35 */ +#define MX35_USBPHYCTRL_OFFSET 0x600 +#define USBPHYCTRL_OTGBASE_OFFSET 0x8 +#define USBPHYCTRL_EVDO (1 << 23) + +int fsl_udc_clk_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + unsigned long freq; + int ret; + + pdata = pdev->dev.platform_data; + + mxc_ipg_clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(mxc_ipg_clk)) { + dev_err(&pdev->dev, "clk_get(\"ipg\") failed\n"); + return PTR_ERR(mxc_ipg_clk); + } + + mxc_ahb_clk = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(mxc_ahb_clk)) { + dev_err(&pdev->dev, "clk_get(\"ahb\") failed\n"); + return PTR_ERR(mxc_ahb_clk); + } + + mxc_per_clk = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(mxc_per_clk)) { + dev_err(&pdev->dev, "clk_get(\"per\") failed\n"); + return PTR_ERR(mxc_per_clk); + } + + clk_prepare_enable(mxc_ipg_clk); + clk_prepare_enable(mxc_ahb_clk); + clk_prepare_enable(mxc_per_clk); + + /* make sure USB_CLK is running at 60 MHz +/- 1000 Hz */ + if (!strcmp(pdev->id_entry->name, "imx-udc-mx27")) { + freq = clk_get_rate(mxc_per_clk); + if (pdata->phy_mode != FSL_USB2_PHY_ULPI && + (freq < 59999000 || freq > 60001000)) { + dev_err(&pdev->dev, "USB_CLK=%lu, should be 60MHz\n", freq); + ret = -EINVAL; + goto eclkrate; + } + } + + return 0; + +eclkrate: + clk_disable_unprepare(mxc_ipg_clk); + clk_disable_unprepare(mxc_ahb_clk); + clk_disable_unprepare(mxc_per_clk); + mxc_per_clk = NULL; + return ret; +} + +int fsl_udc_clk_finalize(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + int ret = 0; + + /* workaround ENGcm09152 for i.MX35 */ + if (pdata->workaround & FLS_USB2_WORKAROUND_ENGCM09152) { + unsigned int v; + struct resource *res = platform_get_resource + (pdev, IORESOURCE_MEM, 0); + void __iomem *phy_regs = ioremap(res->start + + MX35_USBPHYCTRL_OFFSET, 512); + if (!phy_regs) { + dev_err(&pdev->dev, "ioremap for phy address fails\n"); + ret = -EINVAL; + goto ioremap_err; + } + + v = readl(phy_regs + USBPHYCTRL_OTGBASE_OFFSET); + writel(v | USBPHYCTRL_EVDO, + phy_regs + USBPHYCTRL_OTGBASE_OFFSET); + + iounmap(phy_regs); + } + + +ioremap_err: + /* ULPI transceivers don't need usbpll */ + if (pdata->phy_mode == FSL_USB2_PHY_ULPI) { + clk_disable_unprepare(mxc_per_clk); + mxc_per_clk = NULL; + } + + return ret; +} + +void fsl_udc_clk_release(void) +{ + if (mxc_per_clk) + clk_disable_unprepare(mxc_per_clk); + clk_disable_unprepare(mxc_ahb_clk); + clk_disable_unprepare(mxc_ipg_clk); +} diff --git a/drivers/usb/gadget/fsl_qe_udc.c b/drivers/usb/gadget/fsl_qe_udc.c new file mode 100644 index 00000000..9a7ee334 --- /dev/null +++ b/drivers/usb/gadget/fsl_qe_udc.c @@ -0,0 +1,2736 @@ +/* + * driver/usb/gadget/fsl_qe_udc.c + * + * Copyright (c) 2006-2008 Freescale Semiconductor, Inc. All rights reserved. + * + * Xie Xiaobo + * Li Yang + * Based on bareboard code from Shlomi Gridish. + * + * Description: + * Freescle QE/CPM USB Pheripheral Controller Driver + * The controller can be found on MPC8360, MPC8272, and etc. + * MPC8360 Rev 1.1 may need QE mircocode update + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#undef USB_TRACE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fsl_qe_udc.h" + +#define DRIVER_DESC "Freescale QE/CPM USB Device Controller driver" +#define DRIVER_AUTHOR "Xie XiaoBo" +#define DRIVER_VERSION "1.0" + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +static const char driver_name[] = "fsl_qe_udc"; +static const char driver_desc[] = DRIVER_DESC; + +/*ep name is important in gadget, it should obey the convention of ep_match()*/ +static const char *const ep_name[] = { + "ep0-control", /* everyone has ep0 */ + /* 3 configurable endpoints */ + "ep1", + "ep2", + "ep3", +}; + +static struct usb_endpoint_descriptor qe_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, +}; + +/******************************************************************** + * Internal Used Function Start +********************************************************************/ +/*----------------------------------------------------------------- + * done() - retire a request; caller blocked irqs + *--------------------------------------------------------------*/ +static void done(struct qe_ep *ep, struct qe_req *req, int status) +{ + struct qe_udc *udc = ep->udc; + unsigned char stopped = ep->stopped; + + /* the req->queue pointer is used by ep_queue() func, in which + * the request will be added into a udc_ep->queue 'd tail + * so here the req will be dropped from the ep->queue + */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (req->mapped) { + dma_unmap_single(udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } else + dma_sync_single_for_cpu(udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + + if (status && (status != -ESHUTDOWN)) + dev_vdbg(udc->dev, "complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + spin_unlock(&udc->lock); + + /* this complete() should a func implemented by gadget layer, + * eg fsg->bulk_in_complete() */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&udc->lock); + + ep->stopped = stopped; +} + +/*----------------------------------------------------------------- + * nuke(): delete all requests related to this ep + *--------------------------------------------------------------*/ +static void nuke(struct qe_ep *ep, int status) +{ + /* Whether this eq has request linked */ + while (!list_empty(&ep->queue)) { + struct qe_req *req = NULL; + req = list_entry(ep->queue.next, struct qe_req, queue); + + done(ep, req, status); + } +} + +/*---------------------------------------------------------------------------* + * USB and Endpoint manipulate process, include parameter and register * + *---------------------------------------------------------------------------*/ +/* @value: 1--set stall 0--clean stall */ +static int qe_eprx_stall_change(struct qe_ep *ep, int value) +{ + u16 tem_usep; + u8 epnum = ep->epnum; + struct qe_udc *udc = ep->udc; + + tem_usep = in_be16(&udc->usb_regs->usb_usep[epnum]); + tem_usep = tem_usep & ~USB_RHS_MASK; + if (value == 1) + tem_usep |= USB_RHS_STALL; + else if (ep->dir == USB_DIR_IN) + tem_usep |= USB_RHS_IGNORE_OUT; + + out_be16(&udc->usb_regs->usb_usep[epnum], tem_usep); + return 0; +} + +static int qe_eptx_stall_change(struct qe_ep *ep, int value) +{ + u16 tem_usep; + u8 epnum = ep->epnum; + struct qe_udc *udc = ep->udc; + + tem_usep = in_be16(&udc->usb_regs->usb_usep[epnum]); + tem_usep = tem_usep & ~USB_THS_MASK; + if (value == 1) + tem_usep |= USB_THS_STALL; + else if (ep->dir == USB_DIR_OUT) + tem_usep |= USB_THS_IGNORE_IN; + + out_be16(&udc->usb_regs->usb_usep[epnum], tem_usep); + + return 0; +} + +static int qe_ep0_stall(struct qe_udc *udc) +{ + qe_eptx_stall_change(&udc->eps[0], 1); + qe_eprx_stall_change(&udc->eps[0], 1); + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; + return 0; +} + +static int qe_eprx_nack(struct qe_ep *ep) +{ + u8 epnum = ep->epnum; + struct qe_udc *udc = ep->udc; + + if (ep->state == EP_STATE_IDLE) { + /* Set the ep's nack */ + clrsetbits_be16(&udc->usb_regs->usb_usep[epnum], + USB_RHS_MASK, USB_RHS_NACK); + + /* Mask Rx and Busy interrupts */ + clrbits16(&udc->usb_regs->usb_usbmr, + (USB_E_RXB_MASK | USB_E_BSY_MASK)); + + ep->state = EP_STATE_NACK; + } + return 0; +} + +static int qe_eprx_normal(struct qe_ep *ep) +{ + struct qe_udc *udc = ep->udc; + + if (ep->state == EP_STATE_NACK) { + clrsetbits_be16(&udc->usb_regs->usb_usep[ep->epnum], + USB_RTHS_MASK, USB_THS_IGNORE_IN); + + /* Unmask RX interrupts */ + out_be16(&udc->usb_regs->usb_usber, + USB_E_BSY_MASK | USB_E_RXB_MASK); + setbits16(&udc->usb_regs->usb_usbmr, + (USB_E_RXB_MASK | USB_E_BSY_MASK)); + + ep->state = EP_STATE_IDLE; + ep->has_data = 0; + } + + return 0; +} + +static int qe_ep_cmd_stoptx(struct qe_ep *ep) +{ + if (ep->udc->soc_type == PORT_CPM) + cpm_command(CPM_USB_STOP_TX | (ep->epnum << CPM_USB_EP_SHIFT), + CPM_USB_STOP_TX_OPCODE); + else + qe_issue_cmd(QE_USB_STOP_TX, QE_CR_SUBBLOCK_USB, + ep->epnum, 0); + + return 0; +} + +static int qe_ep_cmd_restarttx(struct qe_ep *ep) +{ + if (ep->udc->soc_type == PORT_CPM) + cpm_command(CPM_USB_RESTART_TX | (ep->epnum << + CPM_USB_EP_SHIFT), CPM_USB_RESTART_TX_OPCODE); + else + qe_issue_cmd(QE_USB_RESTART_TX, QE_CR_SUBBLOCK_USB, + ep->epnum, 0); + + return 0; +} + +static int qe_ep_flushtxfifo(struct qe_ep *ep) +{ + struct qe_udc *udc = ep->udc; + int i; + + i = (int)ep->epnum; + + qe_ep_cmd_stoptx(ep); + out_8(&udc->usb_regs->usb_uscom, + USB_CMD_FLUSH_FIFO | (USB_CMD_EP_MASK & (ep->epnum))); + out_be16(&udc->ep_param[i]->tbptr, in_be16(&udc->ep_param[i]->tbase)); + out_be32(&udc->ep_param[i]->tstate, 0); + out_be16(&udc->ep_param[i]->tbcnt, 0); + + ep->c_txbd = ep->txbase; + ep->n_txbd = ep->txbase; + qe_ep_cmd_restarttx(ep); + return 0; +} + +static int qe_ep_filltxfifo(struct qe_ep *ep) +{ + struct qe_udc *udc = ep->udc; + + out_8(&udc->usb_regs->usb_uscom, + USB_CMD_STR_FIFO | (USB_CMD_EP_MASK & (ep->epnum))); + return 0; +} + +static int qe_epbds_reset(struct qe_udc *udc, int pipe_num) +{ + struct qe_ep *ep; + u32 bdring_len; + struct qe_bd __iomem *bd; + int i; + + ep = &udc->eps[pipe_num]; + + if (ep->dir == USB_DIR_OUT) + bdring_len = USB_BDRING_LEN_RX; + else + bdring_len = USB_BDRING_LEN; + + bd = ep->rxbase; + for (i = 0; i < (bdring_len - 1); i++) { + out_be32((u32 __iomem *)bd, R_E | R_I); + bd++; + } + out_be32((u32 __iomem *)bd, R_E | R_I | R_W); + + bd = ep->txbase; + for (i = 0; i < USB_BDRING_LEN_TX - 1; i++) { + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, 0); + bd++; + } + out_be32((u32 __iomem *)bd, T_W); + + return 0; +} + +static int qe_ep_reset(struct qe_udc *udc, int pipe_num) +{ + struct qe_ep *ep; + u16 tmpusep; + + ep = &udc->eps[pipe_num]; + tmpusep = in_be16(&udc->usb_regs->usb_usep[pipe_num]); + tmpusep &= ~USB_RTHS_MASK; + + switch (ep->dir) { + case USB_DIR_BOTH: + qe_ep_flushtxfifo(ep); + break; + case USB_DIR_OUT: + tmpusep |= USB_THS_IGNORE_IN; + break; + case USB_DIR_IN: + qe_ep_flushtxfifo(ep); + tmpusep |= USB_RHS_IGNORE_OUT; + break; + default: + break; + } + out_be16(&udc->usb_regs->usb_usep[pipe_num], tmpusep); + + qe_epbds_reset(udc, pipe_num); + + return 0; +} + +static int qe_ep_toggledata01(struct qe_ep *ep) +{ + ep->data01 ^= 0x1; + return 0; +} + +static int qe_ep_bd_init(struct qe_udc *udc, unsigned char pipe_num) +{ + struct qe_ep *ep = &udc->eps[pipe_num]; + unsigned long tmp_addr = 0; + struct usb_ep_para __iomem *epparam; + int i; + struct qe_bd __iomem *bd; + int bdring_len; + + if (ep->dir == USB_DIR_OUT) + bdring_len = USB_BDRING_LEN_RX; + else + bdring_len = USB_BDRING_LEN; + + epparam = udc->ep_param[pipe_num]; + /* alloc multi-ram for BD rings and set the ep parameters */ + tmp_addr = cpm_muram_alloc(sizeof(struct qe_bd) * (bdring_len + + USB_BDRING_LEN_TX), QE_ALIGNMENT_OF_BD); + if (IS_ERR_VALUE(tmp_addr)) + return -ENOMEM; + + out_be16(&epparam->rbase, (u16)tmp_addr); + out_be16(&epparam->tbase, (u16)(tmp_addr + + (sizeof(struct qe_bd) * bdring_len))); + + out_be16(&epparam->rbptr, in_be16(&epparam->rbase)); + out_be16(&epparam->tbptr, in_be16(&epparam->tbase)); + + ep->rxbase = cpm_muram_addr(tmp_addr); + ep->txbase = cpm_muram_addr(tmp_addr + (sizeof(struct qe_bd) + * bdring_len)); + ep->n_rxbd = ep->rxbase; + ep->e_rxbd = ep->rxbase; + ep->n_txbd = ep->txbase; + ep->c_txbd = ep->txbase; + ep->data01 = 0; /* data0 */ + + /* Init TX and RX bds */ + bd = ep->rxbase; + for (i = 0; i < bdring_len - 1; i++) { + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, 0); + bd++; + } + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, R_W); + + bd = ep->txbase; + for (i = 0; i < USB_BDRING_LEN_TX - 1; i++) { + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, 0); + bd++; + } + out_be32(&bd->buf, 0); + out_be32((u32 __iomem *)bd, T_W); + + return 0; +} + +static int qe_ep_rxbd_update(struct qe_ep *ep) +{ + unsigned int size; + int i; + unsigned int tmp; + struct qe_bd __iomem *bd; + unsigned int bdring_len; + + if (ep->rxbase == NULL) + return -EINVAL; + + bd = ep->rxbase; + + ep->rxframe = kmalloc(sizeof(*ep->rxframe), GFP_ATOMIC); + if (ep->rxframe == NULL) { + dev_err(ep->udc->dev, "malloc rxframe failed\n"); + return -ENOMEM; + } + + qe_frame_init(ep->rxframe); + + if (ep->dir == USB_DIR_OUT) + bdring_len = USB_BDRING_LEN_RX; + else + bdring_len = USB_BDRING_LEN; + + size = (ep->ep.maxpacket + USB_CRC_SIZE + 2) * (bdring_len + 1); + ep->rxbuffer = kzalloc(size, GFP_ATOMIC); + if (ep->rxbuffer == NULL) { + dev_err(ep->udc->dev, "malloc rxbuffer failed,size=%d\n", + size); + kfree(ep->rxframe); + return -ENOMEM; + } + + ep->rxbuf_d = virt_to_phys((void *)ep->rxbuffer); + if (ep->rxbuf_d == DMA_ADDR_INVALID) { + ep->rxbuf_d = dma_map_single(ep->udc->gadget.dev.parent, + ep->rxbuffer, + size, + DMA_FROM_DEVICE); + ep->rxbufmap = 1; + } else { + dma_sync_single_for_device(ep->udc->gadget.dev.parent, + ep->rxbuf_d, size, + DMA_FROM_DEVICE); + ep->rxbufmap = 0; + } + + size = ep->ep.maxpacket + USB_CRC_SIZE + 2; + tmp = ep->rxbuf_d; + tmp = (u32)(((tmp >> 2) << 2) + 4); + + for (i = 0; i < bdring_len - 1; i++) { + out_be32(&bd->buf, tmp); + out_be32((u32 __iomem *)bd, (R_E | R_I)); + tmp = tmp + size; + bd++; + } + out_be32(&bd->buf, tmp); + out_be32((u32 __iomem *)bd, (R_E | R_I | R_W)); + + return 0; +} + +static int qe_ep_register_init(struct qe_udc *udc, unsigned char pipe_num) +{ + struct qe_ep *ep = &udc->eps[pipe_num]; + struct usb_ep_para __iomem *epparam; + u16 usep, logepnum; + u16 tmp; + u8 rtfcr = 0; + + epparam = udc->ep_param[pipe_num]; + + usep = 0; + logepnum = (ep->ep.desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + usep |= (logepnum << USB_EPNUM_SHIFT); + + switch (ep->ep.desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_BULK: + usep |= USB_TRANS_BULK; + break; + case USB_ENDPOINT_XFER_ISOC: + usep |= USB_TRANS_ISO; + break; + case USB_ENDPOINT_XFER_INT: + usep |= USB_TRANS_INT; + break; + default: + usep |= USB_TRANS_CTR; + break; + } + + switch (ep->dir) { + case USB_DIR_OUT: + usep |= USB_THS_IGNORE_IN; + break; + case USB_DIR_IN: + usep |= USB_RHS_IGNORE_OUT; + break; + default: + break; + } + out_be16(&udc->usb_regs->usb_usep[pipe_num], usep); + + rtfcr = 0x30; + out_8(&epparam->rbmr, rtfcr); + out_8(&epparam->tbmr, rtfcr); + + tmp = (u16)(ep->ep.maxpacket + USB_CRC_SIZE); + /* MRBLR must be divisble by 4 */ + tmp = (u16)(((tmp >> 2) << 2) + 4); + out_be16(&epparam->mrblr, tmp); + + return 0; +} + +static int qe_ep_init(struct qe_udc *udc, + unsigned char pipe_num, + const struct usb_endpoint_descriptor *desc) +{ + struct qe_ep *ep = &udc->eps[pipe_num]; + unsigned long flags; + int reval = 0; + u16 max = 0; + + max = usb_endpoint_maxp(desc); + + /* check the max package size validate for this endpoint */ + /* Refer to USB2.0 spec table 9-13, + */ + if (pipe_num != 0) { + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + if (strstr(ep->ep.name, "-iso") + || strstr(ep->ep.name, "-int")) + goto en_done; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if ((max == 128) || (max == 256) || (max == 512)) + break; + default: + switch (max) { + case 4: + case 8: + case 16: + case 32: + case 64: + break; + default: + case USB_SPEED_LOW: + goto en_done; + } + } + break; + case USB_ENDPOINT_XFER_INT: + if (strstr(ep->ep.name, "-iso")) /* bulk is ok */ + goto en_done; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 64) + break; + default: + if (max <= 8) + break; + goto en_done; + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (strstr(ep->ep.name, "-bulk") + || strstr(ep->ep.name, "-int")) + goto en_done; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 1023) + break; + default: + goto en_done; + } + break; + case USB_ENDPOINT_XFER_CONTROL: + if (strstr(ep->ep.name, "-iso") + || strstr(ep->ep.name, "-int")) + goto en_done; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + switch (max) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + break; + default: + goto en_done; + } + case USB_SPEED_LOW: + switch (max) { + case 1: + case 2: + case 4: + case 8: + break; + default: + goto en_done; + } + default: + goto en_done; + } + break; + + default: + goto en_done; + } + } /* if ep0*/ + + spin_lock_irqsave(&udc->lock, flags); + + /* initialize ep structure */ + ep->ep.maxpacket = max; + ep->tm = (u8)(desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); + ep->ep.desc = desc; + ep->stopped = 0; + ep->init = 1; + + if (pipe_num == 0) { + ep->dir = USB_DIR_BOTH; + udc->ep0_dir = USB_DIR_OUT; + udc->ep0_state = WAIT_FOR_SETUP; + } else { + switch (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { + case USB_DIR_OUT: + ep->dir = USB_DIR_OUT; + break; + case USB_DIR_IN: + ep->dir = USB_DIR_IN; + default: + break; + } + } + + /* hardware special operation */ + qe_ep_bd_init(udc, pipe_num); + if ((ep->tm == USBP_TM_CTL) || (ep->dir == USB_DIR_OUT)) { + reval = qe_ep_rxbd_update(ep); + if (reval) + goto en_done1; + } + + if ((ep->tm == USBP_TM_CTL) || (ep->dir == USB_DIR_IN)) { + ep->txframe = kmalloc(sizeof(*ep->txframe), GFP_ATOMIC); + if (ep->txframe == NULL) { + dev_err(udc->dev, "malloc txframe failed\n"); + goto en_done2; + } + qe_frame_init(ep->txframe); + } + + qe_ep_register_init(udc, pipe_num); + + /* Now HW will be NAKing transfers to that EP, + * until a buffer is queued to it. */ + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +en_done2: + kfree(ep->rxbuffer); + kfree(ep->rxframe); +en_done1: + spin_unlock_irqrestore(&udc->lock, flags); +en_done: + dev_err(udc->dev, "failed to initialize %s\n", ep->ep.name); + return -ENODEV; +} + +static inline void qe_usb_enable(struct qe_udc *udc) +{ + setbits8(&udc->usb_regs->usb_usmod, USB_MODE_EN); +} + +static inline void qe_usb_disable(struct qe_udc *udc) +{ + clrbits8(&udc->usb_regs->usb_usmod, USB_MODE_EN); +} + +/*----------------------------------------------------------------------------* + * USB and EP basic manipulate function end * + *----------------------------------------------------------------------------*/ + + +/****************************************************************************** + UDC transmit and receive process + ******************************************************************************/ +static void recycle_one_rxbd(struct qe_ep *ep) +{ + u32 bdstatus; + + bdstatus = in_be32((u32 __iomem *)ep->e_rxbd); + bdstatus = R_I | R_E | (bdstatus & R_W); + out_be32((u32 __iomem *)ep->e_rxbd, bdstatus); + + if (bdstatus & R_W) + ep->e_rxbd = ep->rxbase; + else + ep->e_rxbd++; +} + +static void recycle_rxbds(struct qe_ep *ep, unsigned char stopatnext) +{ + u32 bdstatus; + struct qe_bd __iomem *bd, *nextbd; + unsigned char stop = 0; + + nextbd = ep->n_rxbd; + bd = ep->e_rxbd; + bdstatus = in_be32((u32 __iomem *)bd); + + while (!(bdstatus & R_E) && !(bdstatus & BD_LENGTH_MASK) && !stop) { + bdstatus = R_E | R_I | (bdstatus & R_W); + out_be32((u32 __iomem *)bd, bdstatus); + + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + bdstatus = in_be32((u32 __iomem *)bd); + if (stopatnext && (bd == nextbd)) + stop = 1; + } + + ep->e_rxbd = bd; +} + +static void ep_recycle_rxbds(struct qe_ep *ep) +{ + struct qe_bd __iomem *bd = ep->n_rxbd; + u32 bdstatus; + u8 epnum = ep->epnum; + struct qe_udc *udc = ep->udc; + + bdstatus = in_be32((u32 __iomem *)bd); + if (!(bdstatus & R_E) && !(bdstatus & BD_LENGTH_MASK)) { + bd = ep->rxbase + + ((in_be16(&udc->ep_param[epnum]->rbptr) - + in_be16(&udc->ep_param[epnum]->rbase)) + >> 3); + bdstatus = in_be32((u32 __iomem *)bd); + + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + ep->e_rxbd = bd; + recycle_rxbds(ep, 0); + ep->e_rxbd = ep->n_rxbd; + } else + recycle_rxbds(ep, 1); + + if (in_be16(&udc->usb_regs->usb_usber) & USB_E_BSY_MASK) + out_be16(&udc->usb_regs->usb_usber, USB_E_BSY_MASK); + + if (ep->has_data <= 0 && (!list_empty(&ep->queue))) + qe_eprx_normal(ep); + + ep->localnack = 0; +} + +static void setup_received_handle(struct qe_udc *udc, + struct usb_ctrlrequest *setup); +static int qe_ep_rxframe_handle(struct qe_ep *ep); +static void ep0_req_complete(struct qe_udc *udc, struct qe_req *req); +/* when BD PID is setup, handle the packet */ +static int ep0_setup_handle(struct qe_udc *udc) +{ + struct qe_ep *ep = &udc->eps[0]; + struct qe_frame *pframe; + unsigned int fsize; + u8 *cp; + + pframe = ep->rxframe; + if ((frame_get_info(pframe) & PID_SETUP) + && (udc->ep0_state == WAIT_FOR_SETUP)) { + fsize = frame_get_length(pframe); + if (unlikely(fsize != 8)) + return -EINVAL; + cp = (u8 *)&udc->local_setup_buff; + memcpy(cp, pframe->data, fsize); + ep->data01 = 1; + + /* handle the usb command base on the usb_ctrlrequest */ + setup_received_handle(udc, &udc->local_setup_buff); + return 0; + } + return -EINVAL; +} + +static int qe_ep0_rx(struct qe_udc *udc) +{ + struct qe_ep *ep = &udc->eps[0]; + struct qe_frame *pframe; + struct qe_bd __iomem *bd; + u32 bdstatus, length; + u32 vaddr; + + pframe = ep->rxframe; + + if (ep->dir == USB_DIR_IN) { + dev_err(udc->dev, "ep0 not a control endpoint\n"); + return -EINVAL; + } + + bd = ep->n_rxbd; + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + + while (!(bdstatus & R_E) && length) { + if ((bdstatus & R_F) && (bdstatus & R_L) + && !(bdstatus & R_ERROR)) { + if (length == USB_CRC_SIZE) { + udc->ep0_state = WAIT_FOR_SETUP; + dev_vdbg(udc->dev, + "receive a ZLP in status phase\n"); + } else { + qe_frame_clean(pframe); + vaddr = (u32)phys_to_virt(in_be32(&bd->buf)); + frame_set_data(pframe, (u8 *)vaddr); + frame_set_length(pframe, + (length - USB_CRC_SIZE)); + frame_set_status(pframe, FRAME_OK); + switch (bdstatus & R_PID) { + case R_PID_SETUP: + frame_set_info(pframe, PID_SETUP); + break; + case R_PID_DATA1: + frame_set_info(pframe, PID_DATA1); + break; + default: + frame_set_info(pframe, PID_DATA0); + break; + } + + if ((bdstatus & R_PID) == R_PID_SETUP) + ep0_setup_handle(udc); + else + qe_ep_rxframe_handle(ep); + } + } else { + dev_err(udc->dev, "The receive frame with error!\n"); + } + + /* note: don't clear the rxbd's buffer address */ + recycle_one_rxbd(ep); + + /* Get next BD */ + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + + } + + ep->n_rxbd = bd; + + return 0; +} + +static int qe_ep_rxframe_handle(struct qe_ep *ep) +{ + struct qe_frame *pframe; + u8 framepid = 0; + unsigned int fsize; + u8 *cp; + struct qe_req *req; + + pframe = ep->rxframe; + + if (frame_get_info(pframe) & PID_DATA1) + framepid = 0x1; + + if (framepid != ep->data01) { + dev_err(ep->udc->dev, "the data01 error!\n"); + return -EIO; + } + + fsize = frame_get_length(pframe); + if (list_empty(&ep->queue)) { + dev_err(ep->udc->dev, "the %s have no requeue!\n", ep->name); + } else { + req = list_entry(ep->queue.next, struct qe_req, queue); + + cp = (u8 *)(req->req.buf) + req->req.actual; + if (cp) { + memcpy(cp, pframe->data, fsize); + req->req.actual += fsize; + if ((fsize < ep->ep.maxpacket) || + (req->req.actual >= req->req.length)) { + if (ep->epnum == 0) + ep0_req_complete(ep->udc, req); + else + done(ep, req, 0); + if (list_empty(&ep->queue) && ep->epnum != 0) + qe_eprx_nack(ep); + } + } + } + + qe_ep_toggledata01(ep); + + return 0; +} + +static void ep_rx_tasklet(unsigned long data) +{ + struct qe_udc *udc = (struct qe_udc *)data; + struct qe_ep *ep; + struct qe_frame *pframe; + struct qe_bd __iomem *bd; + unsigned long flags; + u32 bdstatus, length; + u32 vaddr, i; + + spin_lock_irqsave(&udc->lock, flags); + + for (i = 1; i < USB_MAX_ENDPOINTS; i++) { + ep = &udc->eps[i]; + + if (ep->dir == USB_DIR_IN || ep->enable_tasklet == 0) { + dev_dbg(udc->dev, + "This is a transmit ep or disable tasklet!\n"); + continue; + } + + pframe = ep->rxframe; + bd = ep->n_rxbd; + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + + while (!(bdstatus & R_E) && length) { + if (list_empty(&ep->queue)) { + qe_eprx_nack(ep); + dev_dbg(udc->dev, + "The rxep have noreq %d\n", + ep->has_data); + break; + } + + if ((bdstatus & R_F) && (bdstatus & R_L) + && !(bdstatus & R_ERROR)) { + qe_frame_clean(pframe); + vaddr = (u32)phys_to_virt(in_be32(&bd->buf)); + frame_set_data(pframe, (u8 *)vaddr); + frame_set_length(pframe, + (length - USB_CRC_SIZE)); + frame_set_status(pframe, FRAME_OK); + switch (bdstatus & R_PID) { + case R_PID_DATA1: + frame_set_info(pframe, PID_DATA1); + break; + case R_PID_SETUP: + frame_set_info(pframe, PID_SETUP); + break; + default: + frame_set_info(pframe, PID_DATA0); + break; + } + /* handle the rx frame */ + qe_ep_rxframe_handle(ep); + } else { + dev_err(udc->dev, + "error in received frame\n"); + } + /* note: don't clear the rxbd's buffer address */ + /*clear the length */ + out_be32((u32 __iomem *)bd, bdstatus & BD_STATUS_MASK); + ep->has_data--; + if (!(ep->localnack)) + recycle_one_rxbd(ep); + + /* Get next BD */ + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + } + + ep->n_rxbd = bd; + + if (ep->localnack) + ep_recycle_rxbds(ep); + + ep->enable_tasklet = 0; + } /* for i=1 */ + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static int qe_ep_rx(struct qe_ep *ep) +{ + struct qe_udc *udc; + struct qe_frame *pframe; + struct qe_bd __iomem *bd; + u16 swoffs, ucoffs, emptybds; + + udc = ep->udc; + pframe = ep->rxframe; + + if (ep->dir == USB_DIR_IN) { + dev_err(udc->dev, "transmit ep in rx function\n"); + return -EINVAL; + } + + bd = ep->n_rxbd; + + swoffs = (u16)(bd - ep->rxbase); + ucoffs = (u16)((in_be16(&udc->ep_param[ep->epnum]->rbptr) - + in_be16(&udc->ep_param[ep->epnum]->rbase)) >> 3); + if (swoffs < ucoffs) + emptybds = USB_BDRING_LEN_RX - ucoffs + swoffs; + else + emptybds = swoffs - ucoffs; + + if (emptybds < MIN_EMPTY_BDS) { + qe_eprx_nack(ep); + ep->localnack = 1; + dev_vdbg(udc->dev, "%d empty bds, send NACK\n", emptybds); + } + ep->has_data = USB_BDRING_LEN_RX - emptybds; + + if (list_empty(&ep->queue)) { + qe_eprx_nack(ep); + dev_vdbg(udc->dev, "The rxep have no req queued with %d BDs\n", + ep->has_data); + return 0; + } + + tasklet_schedule(&udc->rx_tasklet); + ep->enable_tasklet = 1; + + return 0; +} + +/* send data from a frame, no matter what tx_req */ +static int qe_ep_tx(struct qe_ep *ep, struct qe_frame *frame) +{ + struct qe_udc *udc = ep->udc; + struct qe_bd __iomem *bd; + u16 saveusbmr; + u32 bdstatus, pidmask; + u32 paddr; + + if (ep->dir == USB_DIR_OUT) { + dev_err(udc->dev, "receive ep passed to tx function\n"); + return -EINVAL; + } + + /* Disable the Tx interrupt */ + saveusbmr = in_be16(&udc->usb_regs->usb_usbmr); + out_be16(&udc->usb_regs->usb_usbmr, + saveusbmr & ~(USB_E_TXB_MASK | USB_E_TXE_MASK)); + + bd = ep->n_txbd; + bdstatus = in_be32((u32 __iomem *)bd); + + if (!(bdstatus & (T_R | BD_LENGTH_MASK))) { + if (frame_get_length(frame) == 0) { + frame_set_data(frame, udc->nullbuf); + frame_set_length(frame, 2); + frame->info |= (ZLP | NO_CRC); + dev_vdbg(udc->dev, "the frame size = 0\n"); + } + paddr = virt_to_phys((void *)frame->data); + out_be32(&bd->buf, paddr); + bdstatus = (bdstatus&T_W); + if (!(frame_get_info(frame) & NO_CRC)) + bdstatus |= T_R | T_I | T_L | T_TC + | frame_get_length(frame); + else + bdstatus |= T_R | T_I | T_L | frame_get_length(frame); + + /* if the packet is a ZLP in status phase */ + if ((ep->epnum == 0) && (udc->ep0_state == DATA_STATE_NEED_ZLP)) + ep->data01 = 0x1; + + if (ep->data01) { + pidmask = T_PID_DATA1; + frame->info |= PID_DATA1; + } else { + pidmask = T_PID_DATA0; + frame->info |= PID_DATA0; + } + bdstatus |= T_CNF; + bdstatus |= pidmask; + out_be32((u32 __iomem *)bd, bdstatus); + qe_ep_filltxfifo(ep); + + /* enable the TX interrupt */ + out_be16(&udc->usb_regs->usb_usbmr, saveusbmr); + + qe_ep_toggledata01(ep); + if (bdstatus & T_W) + ep->n_txbd = ep->txbase; + else + ep->n_txbd++; + + return 0; + } else { + out_be16(&udc->usb_regs->usb_usbmr, saveusbmr); + dev_vdbg(udc->dev, "The tx bd is not ready!\n"); + return -EBUSY; + } +} + +/* when a bd was transmitted, the function can + * handle the tx_req, not include ep0 */ +static int txcomplete(struct qe_ep *ep, unsigned char restart) +{ + if (ep->tx_req != NULL) { + struct qe_req *req = ep->tx_req; + unsigned zlp = 0, last_len = 0; + + last_len = min_t(unsigned, req->req.length - ep->sent, + ep->ep.maxpacket); + + if (!restart) { + int asent = ep->last; + ep->sent += asent; + ep->last -= asent; + } else { + ep->last = 0; + } + + /* zlp needed when req->re.zero is set */ + if (req->req.zero) { + if (last_len == 0 || + (req->req.length % ep->ep.maxpacket) != 0) + zlp = 0; + else + zlp = 1; + } else + zlp = 0; + + /* a request already were transmitted completely */ + if (((ep->tx_req->req.length - ep->sent) <= 0) && !zlp) { + done(ep, ep->tx_req, 0); + ep->tx_req = NULL; + ep->last = 0; + ep->sent = 0; + } + } + + /* we should gain a new tx_req fot this endpoint */ + if (ep->tx_req == NULL) { + if (!list_empty(&ep->queue)) { + ep->tx_req = list_entry(ep->queue.next, struct qe_req, + queue); + ep->last = 0; + ep->sent = 0; + } + } + + return 0; +} + +/* give a frame and a tx_req, send some data */ +static int qe_usb_senddata(struct qe_ep *ep, struct qe_frame *frame) +{ + unsigned int size; + u8 *buf; + + qe_frame_clean(frame); + size = min_t(u32, (ep->tx_req->req.length - ep->sent), + ep->ep.maxpacket); + buf = (u8 *)ep->tx_req->req.buf + ep->sent; + if (buf && size) { + ep->last = size; + ep->tx_req->req.actual += size; + frame_set_data(frame, buf); + frame_set_length(frame, size); + frame_set_status(frame, FRAME_OK); + frame_set_info(frame, 0); + return qe_ep_tx(ep, frame); + } + return -EIO; +} + +/* give a frame struct,send a ZLP */ +static int sendnulldata(struct qe_ep *ep, struct qe_frame *frame, uint infor) +{ + struct qe_udc *udc = ep->udc; + + if (frame == NULL) + return -ENODEV; + + qe_frame_clean(frame); + frame_set_data(frame, (u8 *)udc->nullbuf); + frame_set_length(frame, 2); + frame_set_status(frame, FRAME_OK); + frame_set_info(frame, (ZLP | NO_CRC | infor)); + + return qe_ep_tx(ep, frame); +} + +static int frame_create_tx(struct qe_ep *ep, struct qe_frame *frame) +{ + struct qe_req *req = ep->tx_req; + int reval; + + if (req == NULL) + return -ENODEV; + + if ((req->req.length - ep->sent) > 0) + reval = qe_usb_senddata(ep, frame); + else + reval = sendnulldata(ep, frame, 0); + + return reval; +} + +/* if direction is DIR_IN, the status is Device->Host + * if direction is DIR_OUT, the status transaction is Device<-Host + * in status phase, udc create a request and gain status */ +static int ep0_prime_status(struct qe_udc *udc, int direction) +{ + + struct qe_ep *ep = &udc->eps[0]; + + if (direction == USB_DIR_IN) { + udc->ep0_state = DATA_STATE_NEED_ZLP; + udc->ep0_dir = USB_DIR_IN; + sendnulldata(ep, ep->txframe, SETUP_STATUS | NO_REQ); + } else { + udc->ep0_dir = USB_DIR_OUT; + udc->ep0_state = WAIT_FOR_OUT_STATUS; + } + + return 0; +} + +/* a request complete in ep0, whether gadget request or udc request */ +static void ep0_req_complete(struct qe_udc *udc, struct qe_req *req) +{ + struct qe_ep *ep = &udc->eps[0]; + /* because usb and ep's status already been set in ch9setaddress() */ + + switch (udc->ep0_state) { + case DATA_STATE_XMIT: + done(ep, req, 0); + /* receive status phase */ + if (ep0_prime_status(udc, USB_DIR_OUT)) + qe_ep0_stall(udc); + break; + + case DATA_STATE_NEED_ZLP: + done(ep, req, 0); + udc->ep0_state = WAIT_FOR_SETUP; + break; + + case DATA_STATE_RECV: + done(ep, req, 0); + /* send status phase */ + if (ep0_prime_status(udc, USB_DIR_IN)) + qe_ep0_stall(udc); + break; + + case WAIT_FOR_OUT_STATUS: + done(ep, req, 0); + udc->ep0_state = WAIT_FOR_SETUP; + break; + + case WAIT_FOR_SETUP: + dev_vdbg(udc->dev, "Unexpected interrupt\n"); + break; + + default: + qe_ep0_stall(udc); + break; + } +} + +static int ep0_txcomplete(struct qe_ep *ep, unsigned char restart) +{ + struct qe_req *tx_req = NULL; + struct qe_frame *frame = ep->txframe; + + if ((frame_get_info(frame) & (ZLP | NO_REQ)) == (ZLP | NO_REQ)) { + if (!restart) + ep->udc->ep0_state = WAIT_FOR_SETUP; + else + sendnulldata(ep, ep->txframe, SETUP_STATUS | NO_REQ); + return 0; + } + + tx_req = ep->tx_req; + if (tx_req != NULL) { + if (!restart) { + int asent = ep->last; + ep->sent += asent; + ep->last -= asent; + } else { + ep->last = 0; + } + + /* a request already were transmitted completely */ + if ((ep->tx_req->req.length - ep->sent) <= 0) { + ep->tx_req->req.actual = (unsigned int)ep->sent; + ep0_req_complete(ep->udc, ep->tx_req); + ep->tx_req = NULL; + ep->last = 0; + ep->sent = 0; + } + } else { + dev_vdbg(ep->udc->dev, "the ep0_controller have no req\n"); + } + + return 0; +} + +static int ep0_txframe_handle(struct qe_ep *ep) +{ + /* if have error, transmit again */ + if (frame_get_status(ep->txframe) & FRAME_ERROR) { + qe_ep_flushtxfifo(ep); + dev_vdbg(ep->udc->dev, "The EP0 transmit data have error!\n"); + if (frame_get_info(ep->txframe) & PID_DATA0) + ep->data01 = 0; + else + ep->data01 = 1; + + ep0_txcomplete(ep, 1); + } else + ep0_txcomplete(ep, 0); + + frame_create_tx(ep, ep->txframe); + return 0; +} + +static int qe_ep0_txconf(struct qe_ep *ep) +{ + struct qe_bd __iomem *bd; + struct qe_frame *pframe; + u32 bdstatus; + + bd = ep->c_txbd; + bdstatus = in_be32((u32 __iomem *)bd); + while (!(bdstatus & T_R) && (bdstatus & ~T_W)) { + pframe = ep->txframe; + + /* clear and recycle the BD */ + out_be32((u32 __iomem *)bd, bdstatus & T_W); + out_be32(&bd->buf, 0); + if (bdstatus & T_W) + ep->c_txbd = ep->txbase; + else + ep->c_txbd++; + + if (ep->c_txbd == ep->n_txbd) { + if (bdstatus & DEVICE_T_ERROR) { + frame_set_status(pframe, FRAME_ERROR); + if (bdstatus & T_TO) + pframe->status |= TX_ER_TIMEOUT; + if (bdstatus & T_UN) + pframe->status |= TX_ER_UNDERUN; + } + ep0_txframe_handle(ep); + } + + bd = ep->c_txbd; + bdstatus = in_be32((u32 __iomem *)bd); + } + + return 0; +} + +static int ep_txframe_handle(struct qe_ep *ep) +{ + if (frame_get_status(ep->txframe) & FRAME_ERROR) { + qe_ep_flushtxfifo(ep); + dev_vdbg(ep->udc->dev, "The EP0 transmit data have error!\n"); + if (frame_get_info(ep->txframe) & PID_DATA0) + ep->data01 = 0; + else + ep->data01 = 1; + + txcomplete(ep, 1); + } else + txcomplete(ep, 0); + + frame_create_tx(ep, ep->txframe); /* send the data */ + return 0; +} + +/* confirm the already trainsmited bd */ +static int qe_ep_txconf(struct qe_ep *ep) +{ + struct qe_bd __iomem *bd; + struct qe_frame *pframe = NULL; + u32 bdstatus; + unsigned char breakonrxinterrupt = 0; + + bd = ep->c_txbd; + bdstatus = in_be32((u32 __iomem *)bd); + while (!(bdstatus & T_R) && (bdstatus & ~T_W)) { + pframe = ep->txframe; + if (bdstatus & DEVICE_T_ERROR) { + frame_set_status(pframe, FRAME_ERROR); + if (bdstatus & T_TO) + pframe->status |= TX_ER_TIMEOUT; + if (bdstatus & T_UN) + pframe->status |= TX_ER_UNDERUN; + } + + /* clear and recycle the BD */ + out_be32((u32 __iomem *)bd, bdstatus & T_W); + out_be32(&bd->buf, 0); + if (bdstatus & T_W) + ep->c_txbd = ep->txbase; + else + ep->c_txbd++; + + /* handle the tx frame */ + ep_txframe_handle(ep); + bd = ep->c_txbd; + bdstatus = in_be32((u32 __iomem *)bd); + } + if (breakonrxinterrupt) + return -EIO; + else + return 0; +} + +/* Add a request in queue, and try to transmit a packet */ +static int ep_req_send(struct qe_ep *ep, struct qe_req *req) +{ + int reval = 0; + + if (ep->tx_req == NULL) { + ep->sent = 0; + ep->last = 0; + txcomplete(ep, 0); /* can gain a new tx_req */ + reval = frame_create_tx(ep, ep->txframe); + } + return reval; +} + +/* Maybe this is a good ideal */ +static int ep_req_rx(struct qe_ep *ep, struct qe_req *req) +{ + struct qe_udc *udc = ep->udc; + struct qe_frame *pframe = NULL; + struct qe_bd __iomem *bd; + u32 bdstatus, length; + u32 vaddr, fsize; + u8 *cp; + u8 finish_req = 0; + u8 framepid; + + if (list_empty(&ep->queue)) { + dev_vdbg(udc->dev, "the req already finish!\n"); + return 0; + } + pframe = ep->rxframe; + + bd = ep->n_rxbd; + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + + while (!(bdstatus & R_E) && length) { + if (finish_req) + break; + if ((bdstatus & R_F) && (bdstatus & R_L) + && !(bdstatus & R_ERROR)) { + qe_frame_clean(pframe); + vaddr = (u32)phys_to_virt(in_be32(&bd->buf)); + frame_set_data(pframe, (u8 *)vaddr); + frame_set_length(pframe, (length - USB_CRC_SIZE)); + frame_set_status(pframe, FRAME_OK); + switch (bdstatus & R_PID) { + case R_PID_DATA1: + frame_set_info(pframe, PID_DATA1); break; + default: + frame_set_info(pframe, PID_DATA0); break; + } + /* handle the rx frame */ + + if (frame_get_info(pframe) & PID_DATA1) + framepid = 0x1; + else + framepid = 0; + + if (framepid != ep->data01) { + dev_vdbg(udc->dev, "the data01 error!\n"); + } else { + fsize = frame_get_length(pframe); + + cp = (u8 *)(req->req.buf) + req->req.actual; + if (cp) { + memcpy(cp, pframe->data, fsize); + req->req.actual += fsize; + if ((fsize < ep->ep.maxpacket) + || (req->req.actual >= + req->req.length)) { + finish_req = 1; + done(ep, req, 0); + if (list_empty(&ep->queue)) + qe_eprx_nack(ep); + } + } + qe_ep_toggledata01(ep); + } + } else { + dev_err(udc->dev, "The receive frame with error!\n"); + } + + /* note: don't clear the rxbd's buffer address * + * only Clear the length */ + out_be32((u32 __iomem *)bd, (bdstatus & BD_STATUS_MASK)); + ep->has_data--; + + /* Get next BD */ + if (bdstatus & R_W) + bd = ep->rxbase; + else + bd++; + + bdstatus = in_be32((u32 __iomem *)bd); + length = bdstatus & BD_LENGTH_MASK; + } + + ep->n_rxbd = bd; + ep_recycle_rxbds(ep); + + return 0; +} + +/* only add the request in queue */ +static int ep_req_receive(struct qe_ep *ep, struct qe_req *req) +{ + if (ep->state == EP_STATE_NACK) { + if (ep->has_data <= 0) { + /* Enable rx and unmask rx interrupt */ + qe_eprx_normal(ep); + } else { + /* Copy the exist BD data */ + ep_req_rx(ep, req); + } + } + + return 0; +} + +/******************************************************************** + Internal Used Function End +********************************************************************/ + +/*----------------------------------------------------------------------- + Endpoint Management Functions For Gadget + -----------------------------------------------------------------------*/ +static int qe_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct qe_udc *udc; + struct qe_ep *ep; + int retval = 0; + unsigned char epnum; + + ep = container_of(_ep, struct qe_ep, ep); + + /* catch various bogus parameters */ + if (!_ep || !desc || _ep->name == ep_name[0] || + (desc->bDescriptorType != USB_DT_ENDPOINT)) + return -EINVAL; + + udc = ep->udc; + if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + epnum = (u8)desc->bEndpointAddress & 0xF; + + retval = qe_ep_init(udc, epnum, desc); + if (retval != 0) { + cpm_muram_free(cpm_muram_offset(ep->rxbase)); + dev_dbg(udc->dev, "enable ep%d failed\n", ep->epnum); + return -EINVAL; + } + dev_dbg(udc->dev, "enable ep%d successful\n", ep->epnum); + return 0; +} + +static int qe_ep_disable(struct usb_ep *_ep) +{ + struct qe_udc *udc; + struct qe_ep *ep; + unsigned long flags; + unsigned int size; + + ep = container_of(_ep, struct qe_ep, ep); + udc = ep->udc; + + if (!_ep || !ep->ep.desc) { + dev_dbg(udc->dev, "%s not enabled\n", _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + spin_lock_irqsave(&udc->lock, flags); + /* Nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + ep->ep.desc = NULL; + ep->stopped = 1; + ep->tx_req = NULL; + qe_ep_reset(udc, ep->epnum); + spin_unlock_irqrestore(&udc->lock, flags); + + cpm_muram_free(cpm_muram_offset(ep->rxbase)); + + if (ep->dir == USB_DIR_OUT) + size = (ep->ep.maxpacket + USB_CRC_SIZE + 2) * + (USB_BDRING_LEN_RX + 1); + else + size = (ep->ep.maxpacket + USB_CRC_SIZE + 2) * + (USB_BDRING_LEN + 1); + + if (ep->dir != USB_DIR_IN) { + kfree(ep->rxframe); + if (ep->rxbufmap) { + dma_unmap_single(udc->gadget.dev.parent, + ep->rxbuf_d, size, + DMA_FROM_DEVICE); + ep->rxbuf_d = DMA_ADDR_INVALID; + } else { + dma_sync_single_for_cpu( + udc->gadget.dev.parent, + ep->rxbuf_d, size, + DMA_FROM_DEVICE); + } + kfree(ep->rxbuffer); + } + + if (ep->dir != USB_DIR_OUT) + kfree(ep->txframe); + + dev_dbg(udc->dev, "disabled %s OK\n", _ep->name); + return 0; +} + +static struct usb_request *qe_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct qe_req *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + req->req.dma = DMA_ADDR_INVALID; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void qe_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct qe_req *req; + + req = container_of(_req, struct qe_req, req); + + if (_req) + kfree(req); +} + +static int __qe_ep_queue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct qe_ep *ep = container_of(_ep, struct qe_ep, ep); + struct qe_req *req = container_of(_req, struct qe_req, req); + struct qe_udc *udc; + int reval; + + udc = ep->udc; + /* catch various bogus parameters */ + if (!_req || !req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + dev_dbg(udc->dev, "bad params\n"); + return -EINVAL; + } + if (!_ep || (!ep->ep.desc && ep_index(ep))) { + dev_dbg(udc->dev, "bad ep\n"); + return -EINVAL; + } + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + req->ep = ep; + + /* map virtual address to hardware */ + if (req->req.dma == DMA_ADDR_INVALID) { + req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, + req->req.buf, + req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->mapped = 1; + } else { + dma_sync_single_for_device(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->mapped = 0; + } + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + + list_add_tail(&req->queue, &ep->queue); + dev_vdbg(udc->dev, "gadget have request in %s! %d\n", + ep->name, req->req.length); + + /* push the request to device */ + if (ep_is_in(ep)) + reval = ep_req_send(ep, req); + + /* EP0 */ + if (ep_index(ep) == 0 && req->req.length > 0) { + if (ep_is_in(ep)) + udc->ep0_state = DATA_STATE_XMIT; + else + udc->ep0_state = DATA_STATE_RECV; + } + + if (ep->dir == USB_DIR_OUT) + reval = ep_req_receive(ep, req); + + return 0; +} + +/* queues (submits) an I/O request to an endpoint */ +static int qe_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct qe_ep *ep = container_of(_ep, struct qe_ep, ep); + struct qe_udc *udc = ep->udc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&udc->lock, flags); + ret = __qe_ep_queue(_ep, _req); + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +/* dequeues (cancels, unlinks) an I/O request from an endpoint */ +static int qe_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct qe_ep *ep = container_of(_ep, struct qe_ep, ep); + struct qe_req *req; + unsigned long flags; + + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->udc->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return 0; +} + +/*----------------------------------------------------------------- + * modify the endpoint halt feature + * @ep: the non-isochronous endpoint being stalled + * @value: 1--set halt 0--clear halt + * Returns zero, or a negative error code. +*----------------------------------------------------------------*/ +static int qe_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct qe_ep *ep; + unsigned long flags; + int status = -EOPNOTSUPP; + struct qe_udc *udc; + + ep = container_of(_ep, struct qe_ep, ep); + if (!_ep || !ep->ep.desc) { + status = -EINVAL; + goto out; + } + + udc = ep->udc; + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + status = 0; + spin_lock_irqsave(&ep->udc->lock, flags); + qe_eptx_stall_change(ep, value); + qe_eprx_stall_change(ep, value); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + if (ep->epnum == 0) { + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; + } + + /* set data toggle to DATA0 on clear halt */ + if (value == 0) + ep->data01 = 0; +out: + dev_vdbg(udc->dev, "%s %s halt stat %d\n", ep->ep.name, + value ? "set" : "clear", status); + + return status; +} + +static struct usb_ep_ops qe_ep_ops = { + .enable = qe_ep_enable, + .disable = qe_ep_disable, + + .alloc_request = qe_alloc_request, + .free_request = qe_free_request, + + .queue = qe_ep_queue, + .dequeue = qe_ep_dequeue, + + .set_halt = qe_ep_set_halt, +}; + +/*------------------------------------------------------------------------ + Gadget Driver Layer Operations + ------------------------------------------------------------------------*/ + +/* Get the current frame number */ +static int qe_get_frame(struct usb_gadget *gadget) +{ + struct qe_udc *udc = container_of(gadget, struct qe_udc, gadget); + u16 tmp; + + tmp = in_be16(&udc->usb_param->frame_n); + if (tmp & 0x8000) + tmp = tmp & 0x07ff; + else + tmp = -EINVAL; + + return (int)tmp; +} + +static int fsl_qe_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int fsl_qe_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); + +/* defined in usb_gadget.h */ +static const struct usb_gadget_ops qe_gadget_ops = { + .get_frame = qe_get_frame, + .udc_start = fsl_qe_start, + .udc_stop = fsl_qe_stop, +}; + +/*------------------------------------------------------------------------- + USB ep0 Setup process in BUS Enumeration + -------------------------------------------------------------------------*/ +static int udc_reset_ep_queue(struct qe_udc *udc, u8 pipe) +{ + struct qe_ep *ep = &udc->eps[pipe]; + + nuke(ep, -ECONNRESET); + ep->tx_req = NULL; + return 0; +} + +static int reset_queues(struct qe_udc *udc) +{ + u8 pipe; + + for (pipe = 0; pipe < USB_MAX_ENDPOINTS; pipe++) + udc_reset_ep_queue(udc, pipe); + + /* report disconnect; the driver is already quiesced */ + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + + return 0; +} + +static void ch9setaddress(struct qe_udc *udc, u16 value, u16 index, + u16 length) +{ + /* Save the new address to device struct */ + udc->device_address = (u8) value; + /* Update usb state */ + udc->usb_state = USB_STATE_ADDRESS; + + /* Status phase , send a ZLP */ + if (ep0_prime_status(udc, USB_DIR_IN)) + qe_ep0_stall(udc); +} + +static void ownercomplete(struct usb_ep *_ep, struct usb_request *_req) +{ + struct qe_req *req = container_of(_req, struct qe_req, req); + + req->req.buf = NULL; + kfree(req); +} + +static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) +{ + u16 usb_status = 0; + struct qe_req *req; + struct qe_ep *ep; + int status = 0; + + ep = &udc->eps[0]; + if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* Get device status */ + usb_status = 1 << USB_DEVICE_SELF_POWERED; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { + /* Get interface status */ + /* We don't have interface information in udc driver */ + usb_status = 0; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { + /* Get endpoint status */ + int pipe = index & USB_ENDPOINT_NUMBER_MASK; + struct qe_ep *target_ep = &udc->eps[pipe]; + u16 usep; + + /* stall if endpoint doesn't exist */ + if (!target_ep->ep.desc) + goto stall; + + usep = in_be16(&udc->usb_regs->usb_usep[pipe]); + if (index & USB_DIR_IN) { + if (target_ep->dir != USB_DIR_IN) + goto stall; + if ((usep & USB_THS_MASK) == USB_THS_STALL) + usb_status = 1 << USB_ENDPOINT_HALT; + } else { + if (target_ep->dir != USB_DIR_OUT) + goto stall; + if ((usep & USB_RHS_MASK) == USB_RHS_STALL) + usb_status = 1 << USB_ENDPOINT_HALT; + } + } + + req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL), + struct qe_req, req); + req->req.length = 2; + req->req.buf = udc->statusbuf; + *(u16 *)req->req.buf = cpu_to_le16(usb_status); + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = ownercomplete; + + udc->ep0_dir = USB_DIR_IN; + + /* data phase */ + status = __qe_ep_queue(&ep->ep, &req->req); + + if (status == 0) + return; +stall: + dev_err(udc->dev, "Can't respond to getstatus request \n"); + qe_ep0_stall(udc); +} + +/* only handle the setup request, suppose the device in normal status */ +static void setup_received_handle(struct qe_udc *udc, + struct usb_ctrlrequest *setup) +{ + /* Fix Endian (udc->local_setup_buff is cpu Endian now)*/ + u16 wValue = le16_to_cpu(setup->wValue); + u16 wIndex = le16_to_cpu(setup->wIndex); + u16 wLength = le16_to_cpu(setup->wLength); + + /* clear the previous request in the ep0 */ + udc_reset_ep_queue(udc, 0); + + if (setup->bRequestType & USB_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + switch (setup->bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase form udc */ + if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + ch9getstatus(udc, setup->bRequestType, wValue, wIndex, + wLength); + return; + + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (setup->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + ch9setaddress(udc, wValue, wIndex, wLength); + return; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Requests with no data phase, status phase from udc */ + if ((setup->bRequestType & USB_TYPE_MASK) + != USB_TYPE_STANDARD) + break; + + if ((setup->bRequestType & USB_RECIP_MASK) + == USB_RECIP_ENDPOINT) { + int pipe = wIndex & USB_ENDPOINT_NUMBER_MASK; + struct qe_ep *ep; + + if (wValue != 0 || wLength != 0 + || pipe > USB_MAX_ENDPOINTS) + break; + ep = &udc->eps[pipe]; + + spin_unlock(&udc->lock); + qe_ep_set_halt(&ep->ep, + (setup->bRequest == USB_REQ_SET_FEATURE) + ? 1 : 0); + spin_lock(&udc->lock); + } + + ep0_prime_status(udc, USB_DIR_IN); + + return; + + default: + break; + } + + if (wLength) { + /* Data phase from gadget, status phase from udc */ + if (setup->bRequestType & USB_DIR_IN) { + udc->ep0_state = DATA_STATE_XMIT; + udc->ep0_dir = USB_DIR_IN; + } else { + udc->ep0_state = DATA_STATE_RECV; + udc->ep0_dir = USB_DIR_OUT; + } + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + qe_ep0_stall(udc); + spin_lock(&udc->lock); + } else { + /* No data phase, IN status from gadget */ + udc->ep0_dir = USB_DIR_IN; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + qe_ep0_stall(udc); + spin_lock(&udc->lock); + udc->ep0_state = DATA_STATE_NEED_ZLP; + } +} + +/*------------------------------------------------------------------------- + USB Interrupt handlers + -------------------------------------------------------------------------*/ +static void suspend_irq(struct qe_udc *udc) +{ + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + + /* report suspend to the driver ,serial.c not support this*/ + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void resume_irq(struct qe_udc *udc) +{ + udc->usb_state = udc->resume_state; + udc->resume_state = 0; + + /* report resume to the driver , serial.c not support this*/ + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +static void idle_irq(struct qe_udc *udc) +{ + u8 usbs; + + usbs = in_8(&udc->usb_regs->usb_usbs); + if (usbs & USB_IDLE_STATUS_MASK) { + if ((udc->usb_state) != USB_STATE_SUSPENDED) + suspend_irq(udc); + } else { + if (udc->usb_state == USB_STATE_SUSPENDED) + resume_irq(udc); + } +} + +static int reset_irq(struct qe_udc *udc) +{ + unsigned char i; + + if (udc->usb_state == USB_STATE_DEFAULT) + return 0; + + qe_usb_disable(udc); + out_8(&udc->usb_regs->usb_usadr, 0); + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) { + if (udc->eps[i].init) + qe_ep_reset(udc, i); + } + + reset_queues(udc); + udc->usb_state = USB_STATE_DEFAULT; + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = USB_DIR_OUT; + qe_usb_enable(udc); + return 0; +} + +static int bsy_irq(struct qe_udc *udc) +{ + return 0; +} + +static int txe_irq(struct qe_udc *udc) +{ + return 0; +} + +/* ep0 tx interrupt also in here */ +static int tx_irq(struct qe_udc *udc) +{ + struct qe_ep *ep; + struct qe_bd __iomem *bd; + int i, res = 0; + + if ((udc->usb_state == USB_STATE_ADDRESS) + && (in_8(&udc->usb_regs->usb_usadr) == 0)) + out_8(&udc->usb_regs->usb_usadr, udc->device_address); + + for (i = (USB_MAX_ENDPOINTS-1); ((i >= 0) && (res == 0)); i--) { + ep = &udc->eps[i]; + if (ep && ep->init && (ep->dir != USB_DIR_OUT)) { + bd = ep->c_txbd; + if (!(in_be32((u32 __iomem *)bd) & T_R) + && (in_be32(&bd->buf))) { + /* confirm the transmitted bd */ + if (ep->epnum == 0) + res = qe_ep0_txconf(ep); + else + res = qe_ep_txconf(ep); + } + } + } + return res; +} + + +/* setup packect's rx is handle in the function too */ +static void rx_irq(struct qe_udc *udc) +{ + struct qe_ep *ep; + struct qe_bd __iomem *bd; + int i; + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) { + ep = &udc->eps[i]; + if (ep && ep->init && (ep->dir != USB_DIR_IN)) { + bd = ep->n_rxbd; + if (!(in_be32((u32 __iomem *)bd) & R_E) + && (in_be32(&bd->buf))) { + if (ep->epnum == 0) { + qe_ep0_rx(udc); + } else { + /*non-setup package receive*/ + qe_ep_rx(ep); + } + } + } + } +} + +static irqreturn_t qe_udc_irq(int irq, void *_udc) +{ + struct qe_udc *udc = (struct qe_udc *)_udc; + u16 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + irq_src = in_be16(&udc->usb_regs->usb_usber) & + in_be16(&udc->usb_regs->usb_usbmr); + /* Clear notification bits */ + out_be16(&udc->usb_regs->usb_usber, irq_src); + /* USB Interrupt */ + if (irq_src & USB_E_IDLE_MASK) { + idle_irq(udc); + irq_src &= ~USB_E_IDLE_MASK; + status = IRQ_HANDLED; + } + + if (irq_src & USB_E_TXB_MASK) { + tx_irq(udc); + irq_src &= ~USB_E_TXB_MASK; + status = IRQ_HANDLED; + } + + if (irq_src & USB_E_RXB_MASK) { + rx_irq(udc); + irq_src &= ~USB_E_RXB_MASK; + status = IRQ_HANDLED; + } + + if (irq_src & USB_E_RESET_MASK) { + reset_irq(udc); + irq_src &= ~USB_E_RESET_MASK; + status = IRQ_HANDLED; + } + + if (irq_src & USB_E_BSY_MASK) { + bsy_irq(udc); + irq_src &= ~USB_E_BSY_MASK; + status = IRQ_HANDLED; + } + + if (irq_src & USB_E_TXE_MASK) { + txe_irq(udc); + irq_src &= ~USB_E_TXE_MASK; + status = IRQ_HANDLED; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return status; +} + +/*------------------------------------------------------------------------- + Gadget driver probe and unregister. + --------------------------------------------------------------------------*/ +static int fsl_qe_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct qe_udc *udc; + unsigned long flags; + + udc = container_of(gadget, struct qe_udc, gadget); + /* lock is needed but whether should use this lock or another */ + spin_lock_irqsave(&udc->lock, flags); + + driver->driver.bus = NULL; + /* hook up the driver */ + udc->driver = driver; + udc->gadget.speed = driver->max_speed; + + /* Enable IRQ reg and Set usbcmd reg EN bit */ + qe_usb_enable(udc); + + out_be16(&udc->usb_regs->usb_usber, 0xffff); + out_be16(&udc->usb_regs->usb_usbmr, USB_E_DEFAULT_DEVICE); + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = USB_DIR_OUT; + spin_unlock_irqrestore(&udc->lock, flags); + + dev_info(udc->dev, "%s bind to driver %s\n", udc->gadget.name, + driver->driver.name); + return 0; +} + +static int fsl_qe_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct qe_udc *udc; + struct qe_ep *loop_ep; + unsigned long flags; + + udc = container_of(gadget, struct qe_udc, gadget); + /* stop usb controller, disable intr */ + qe_usb_disable(udc); + + /* in fact, no needed */ + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; + + /* stand operation */ + spin_lock_irqsave(&udc->lock, flags); + udc->gadget.speed = USB_SPEED_UNKNOWN; + nuke(&udc->eps[0], -ESHUTDOWN); + list_for_each_entry(loop_ep, &udc->gadget.ep_list, ep.ep_list) + nuke(loop_ep, -ESHUTDOWN); + spin_unlock_irqrestore(&udc->lock, flags); + + udc->driver = NULL; + + dev_info(udc->dev, "unregistered gadget driver '%s'\r\n", + driver->driver.name); + return 0; +} + +/* udc structure's alloc and setup, include ep-param alloc */ +static struct qe_udc *qe_udc_config(struct platform_device *ofdev) +{ + struct qe_udc *udc; + struct device_node *np = ofdev->dev.of_node; + unsigned int tmp_addr = 0; + struct usb_device_para __iomem *usbpram; + unsigned int i; + u64 size; + u32 offset; + + udc = kzalloc(sizeof(*udc), GFP_KERNEL); + if (udc == NULL) { + dev_err(&ofdev->dev, "malloc udc failed\n"); + goto cleanup; + } + + udc->dev = &ofdev->dev; + + /* get default address of usb parameter in MURAM from device tree */ + offset = *of_get_address(np, 1, &size, NULL); + udc->usb_param = cpm_muram_addr(offset); + memset_io(udc->usb_param, 0, size); + + usbpram = udc->usb_param; + out_be16(&usbpram->frame_n, 0); + out_be32(&usbpram->rstate, 0); + + tmp_addr = cpm_muram_alloc((USB_MAX_ENDPOINTS * + sizeof(struct usb_ep_para)), + USB_EP_PARA_ALIGNMENT); + if (IS_ERR_VALUE(tmp_addr)) + goto cleanup; + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) { + out_be16(&usbpram->epptr[i], (u16)tmp_addr); + udc->ep_param[i] = cpm_muram_addr(tmp_addr); + tmp_addr += 32; + } + + memset_io(udc->ep_param[0], 0, + USB_MAX_ENDPOINTS * sizeof(struct usb_ep_para)); + + udc->resume_state = USB_STATE_NOTATTACHED; + udc->usb_state = USB_STATE_POWERED; + udc->ep0_dir = 0; + + spin_lock_init(&udc->lock); + return udc; + +cleanup: + kfree(udc); + return NULL; +} + +/* USB Controller register init */ +static int qe_udc_reg_init(struct qe_udc *udc) +{ + struct usb_ctlr __iomem *qe_usbregs; + qe_usbregs = udc->usb_regs; + + /* Spec says that we must enable the USB controller to change mode. */ + out_8(&qe_usbregs->usb_usmod, 0x01); + /* Mode changed, now disable it, since muram isn't initialized yet. */ + out_8(&qe_usbregs->usb_usmod, 0x00); + + /* Initialize the rest. */ + out_be16(&qe_usbregs->usb_usbmr, 0); + out_8(&qe_usbregs->usb_uscom, 0); + out_be16(&qe_usbregs->usb_usber, USBER_ALL_CLEAR); + + return 0; +} + +static int qe_ep_config(struct qe_udc *udc, unsigned char pipe_num) +{ + struct qe_ep *ep = &udc->eps[pipe_num]; + + ep->udc = udc; + strcpy(ep->name, ep_name[pipe_num]); + ep->ep.name = ep_name[pipe_num]; + + ep->ep.ops = &qe_ep_ops; + ep->stopped = 1; + ep->ep.maxpacket = (unsigned short) ~0; + ep->ep.desc = NULL; + ep->dir = 0xff; + ep->epnum = (u8)pipe_num; + ep->sent = 0; + ep->last = 0; + ep->init = 0; + ep->rxframe = NULL; + ep->txframe = NULL; + ep->tx_req = NULL; + ep->state = EP_STATE_IDLE; + ep->has_data = 0; + + /* the queue lists any req for this ep */ + INIT_LIST_HEAD(&ep->queue); + + /* gagdet.ep_list used for ep_autoconfig so no ep0*/ + if (pipe_num != 0) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + + ep->gadget = &udc->gadget; + + return 0; +} + +/*----------------------------------------------------------------------- + * UDC device Driver operation functions * + *----------------------------------------------------------------------*/ +static void qe_udc_release(struct device *dev) +{ + struct qe_udc *udc = container_of(dev, struct qe_udc, gadget.dev); + int i; + + complete(udc->done); + cpm_muram_free(cpm_muram_offset(udc->ep_param[0])); + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + udc->ep_param[i] = NULL; + + kfree(udc); +} + +/* Driver probe functions */ +static const struct of_device_id qe_udc_match[]; +static int qe_udc_probe(struct platform_device *ofdev) +{ + struct qe_udc *udc; + const struct of_device_id *match; + struct device_node *np = ofdev->dev.of_node; + struct qe_ep *ep; + unsigned int ret = 0; + unsigned int i; + const void *prop; + + match = of_match_device(qe_udc_match, &ofdev->dev); + if (!match) + return -EINVAL; + + prop = of_get_property(np, "mode", NULL); + if (!prop || strcmp(prop, "peripheral")) + return -ENODEV; + + /* Initialize the udc structure including QH member and other member */ + udc = qe_udc_config(ofdev); + if (!udc) { + dev_err(&ofdev->dev, "failed to initialize\n"); + return -ENOMEM; + } + + udc->soc_type = (unsigned long)match->data; + udc->usb_regs = of_iomap(np, 0); + if (!udc->usb_regs) { + ret = -ENOMEM; + goto err1; + } + + /* initialize usb hw reg except for regs for EP, + * leave usbintr reg untouched*/ + qe_udc_reg_init(udc); + + /* here comes the stand operations for probe + * set the qe_udc->gadget.xxx */ + udc->gadget.ops = &qe_gadget_ops; + + /* gadget.ep0 is a pointer */ + udc->gadget.ep0 = &udc->eps[0].ep; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + /* modify in register gadget process */ + udc->gadget.speed = USB_SPEED_UNKNOWN; + + /* name: Identifies the controller hardware type. */ + udc->gadget.name = driver_name; + udc->gadget.dev.parent = &ofdev->dev; + + /* initialize qe_ep struct */ + for (i = 0; i < USB_MAX_ENDPOINTS ; i++) { + /* because the ep type isn't decide here so + * qe_ep_init() should be called in ep_enable() */ + + /* setup the qe_ep struct and link ep.ep.list + * into gadget.ep_list */ + qe_ep_config(udc, (unsigned char)i); + } + + /* ep0 initialization in here */ + ret = qe_ep_init(udc, 0, &qe_ep0_desc); + if (ret) + goto err2; + + /* create a buf for ZLP send, need to remain zeroed */ + udc->nullbuf = kzalloc(256, GFP_KERNEL); + if (udc->nullbuf == NULL) { + dev_err(udc->dev, "cannot alloc nullbuf\n"); + ret = -ENOMEM; + goto err3; + } + + /* buffer for data of get_status request */ + udc->statusbuf = kzalloc(2, GFP_KERNEL); + if (udc->statusbuf == NULL) { + ret = -ENOMEM; + goto err4; + } + + udc->nullp = virt_to_phys((void *)udc->nullbuf); + if (udc->nullp == DMA_ADDR_INVALID) { + udc->nullp = dma_map_single( + udc->gadget.dev.parent, + udc->nullbuf, + 256, + DMA_TO_DEVICE); + udc->nullmap = 1; + } else { + dma_sync_single_for_device(udc->gadget.dev.parent, + udc->nullp, 256, + DMA_TO_DEVICE); + } + + tasklet_init(&udc->rx_tasklet, ep_rx_tasklet, + (unsigned long)udc); + /* request irq and disable DR */ + udc->usb_irq = irq_of_parse_and_map(np, 0); + if (!udc->usb_irq) { + ret = -EINVAL; + goto err_noirq; + } + + ret = request_irq(udc->usb_irq, qe_udc_irq, 0, + driver_name, udc); + if (ret) { + dev_err(udc->dev, "cannot request irq %d err %d\n", + udc->usb_irq, ret); + goto err5; + } + + ret = usb_add_gadget_udc_release(&ofdev->dev, &udc->gadget, + qe_udc_release); + if (ret) + goto err6; + + dev_set_drvdata(&ofdev->dev, udc); + dev_info(udc->dev, + "%s USB controller initialized as device\n", + (udc->soc_type == PORT_QE) ? "QE" : "CPM"); + return 0; + +err6: + free_irq(udc->usb_irq, udc); +err5: + irq_dispose_mapping(udc->usb_irq); +err_noirq: + if (udc->nullmap) { + dma_unmap_single(udc->gadget.dev.parent, + udc->nullp, 256, + DMA_TO_DEVICE); + udc->nullp = DMA_ADDR_INVALID; + } else { + dma_sync_single_for_cpu(udc->gadget.dev.parent, + udc->nullp, 256, + DMA_TO_DEVICE); + } + kfree(udc->statusbuf); +err4: + kfree(udc->nullbuf); +err3: + ep = &udc->eps[0]; + cpm_muram_free(cpm_muram_offset(ep->rxbase)); + kfree(ep->rxframe); + kfree(ep->rxbuffer); + kfree(ep->txframe); +err2: + iounmap(udc->usb_regs); +err1: + kfree(udc); + return ret; +} + +#ifdef CONFIG_PM +static int qe_udc_suspend(struct platform_device *dev, pm_message_t state) +{ + return -ENOTSUPP; +} + +static int qe_udc_resume(struct platform_device *dev) +{ + return -ENOTSUPP; +} +#endif + +static int qe_udc_remove(struct platform_device *ofdev) +{ + struct qe_udc *udc = dev_get_drvdata(&ofdev->dev); + struct qe_ep *ep; + unsigned int size; + DECLARE_COMPLETION(done); + + usb_del_gadget_udc(&udc->gadget); + + udc->done = &done; + tasklet_disable(&udc->rx_tasklet); + + if (udc->nullmap) { + dma_unmap_single(udc->gadget.dev.parent, + udc->nullp, 256, + DMA_TO_DEVICE); + udc->nullp = DMA_ADDR_INVALID; + } else { + dma_sync_single_for_cpu(udc->gadget.dev.parent, + udc->nullp, 256, + DMA_TO_DEVICE); + } + kfree(udc->statusbuf); + kfree(udc->nullbuf); + + ep = &udc->eps[0]; + cpm_muram_free(cpm_muram_offset(ep->rxbase)); + size = (ep->ep.maxpacket + USB_CRC_SIZE + 2) * (USB_BDRING_LEN + 1); + + kfree(ep->rxframe); + if (ep->rxbufmap) { + dma_unmap_single(udc->gadget.dev.parent, + ep->rxbuf_d, size, + DMA_FROM_DEVICE); + ep->rxbuf_d = DMA_ADDR_INVALID; + } else { + dma_sync_single_for_cpu(udc->gadget.dev.parent, + ep->rxbuf_d, size, + DMA_FROM_DEVICE); + } + + kfree(ep->rxbuffer); + kfree(ep->txframe); + + free_irq(udc->usb_irq, udc); + irq_dispose_mapping(udc->usb_irq); + + tasklet_kill(&udc->rx_tasklet); + + iounmap(udc->usb_regs); + + /* wait for release() of gadget.dev to free udc */ + wait_for_completion(&done); + + return 0; +} + +/*-------------------------------------------------------------------------*/ +static const struct of_device_id qe_udc_match[] = { + { + .compatible = "fsl,mpc8323-qe-usb", + .data = (void *)PORT_QE, + }, + { + .compatible = "fsl,mpc8360-qe-usb", + .data = (void *)PORT_QE, + }, + { + .compatible = "fsl,mpc8272-cpm-usb", + .data = (void *)PORT_CPM, + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, qe_udc_match); + +static struct platform_driver udc_driver = { + .driver = { + .name = (char *)driver_name, + .owner = THIS_MODULE, + .of_match_table = qe_udc_match, + }, + .probe = qe_udc_probe, + .remove = qe_udc_remove, +#ifdef CONFIG_PM + .suspend = qe_udc_suspend, + .resume = qe_udc_resume, +#endif +}; + +module_platform_driver(udc_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/gadget/fsl_qe_udc.h b/drivers/usb/gadget/fsl_qe_udc.h new file mode 100644 index 00000000..7026919f --- /dev/null +++ b/drivers/usb/gadget/fsl_qe_udc.h @@ -0,0 +1,421 @@ +/* + * drivers/usb/gadget/qe_udc.h + * + * Copyright (C) 2006-2008 Freescale Semiconductor, Inc. All rights reserved. + * + * Xiaobo Xie + * Li Yang + * + * Description: + * Freescale USB device/endpoint management registers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef __FSL_QE_UDC_H +#define __FSL_QE_UDC_H + +/* SoC type */ +#define PORT_CPM 0 +#define PORT_QE 1 + +#define USB_MAX_ENDPOINTS 4 +#define USB_MAX_PIPES USB_MAX_ENDPOINTS +#define USB_EP0_MAX_SIZE 64 +#define USB_MAX_CTRL_PAYLOAD 0x4000 +#define USB_BDRING_LEN 16 +#define USB_BDRING_LEN_RX 256 +#define USB_BDRING_LEN_TX 16 +#define MIN_EMPTY_BDS 128 +#define MAX_DATA_BDS 8 +#define USB_CRC_SIZE 2 +#define USB_DIR_BOTH 0x88 +#define R_BUF_MAXSIZE 0x800 +#define USB_EP_PARA_ALIGNMENT 32 + +/* USB Mode Register bit define */ +#define USB_MODE_EN 0x01 +#define USB_MODE_HOST 0x02 +#define USB_MODE_TEST 0x04 +#define USB_MODE_SFTE 0x08 +#define USB_MODE_RESUME 0x40 +#define USB_MODE_LSS 0x80 + +/* USB Slave Address Register Mask */ +#define USB_SLVADDR_MASK 0x7F + +/* USB Endpoint register define */ +#define USB_EPNUM_MASK 0xF000 +#define USB_EPNUM_SHIFT 12 + +#define USB_TRANS_MODE_SHIFT 8 +#define USB_TRANS_CTR 0x0000 +#define USB_TRANS_INT 0x0100 +#define USB_TRANS_BULK 0x0200 +#define USB_TRANS_ISO 0x0300 + +#define USB_EP_MF 0x0020 +#define USB_EP_RTE 0x0010 + +#define USB_THS_SHIFT 2 +#define USB_THS_MASK 0x000c +#define USB_THS_NORMAL 0x0 +#define USB_THS_IGNORE_IN 0x0004 +#define USB_THS_NACK 0x0008 +#define USB_THS_STALL 0x000c + +#define USB_RHS_SHIFT 0 +#define USB_RHS_MASK 0x0003 +#define USB_RHS_NORMAL 0x0 +#define USB_RHS_IGNORE_OUT 0x0001 +#define USB_RHS_NACK 0x0002 +#define USB_RHS_STALL 0x0003 + +#define USB_RTHS_MASK 0x000f + +/* USB Command Register define */ +#define USB_CMD_STR_FIFO 0x80 +#define USB_CMD_FLUSH_FIFO 0x40 +#define USB_CMD_ISFT 0x20 +#define USB_CMD_DSFT 0x10 +#define USB_CMD_EP_MASK 0x03 + +/* USB Event and Mask Register define */ +#define USB_E_MSF_MASK 0x0800 +#define USB_E_SFT_MASK 0x0400 +#define USB_E_RESET_MASK 0x0200 +#define USB_E_IDLE_MASK 0x0100 +#define USB_E_TXE4_MASK 0x0080 +#define USB_E_TXE3_MASK 0x0040 +#define USB_E_TXE2_MASK 0x0020 +#define USB_E_TXE1_MASK 0x0010 +#define USB_E_SOF_MASK 0x0008 +#define USB_E_BSY_MASK 0x0004 +#define USB_E_TXB_MASK 0x0002 +#define USB_E_RXB_MASK 0x0001 +#define USBER_ALL_CLEAR 0x0fff + +#define USB_E_DEFAULT_DEVICE (USB_E_RESET_MASK | USB_E_TXE4_MASK | \ + USB_E_TXE3_MASK | USB_E_TXE2_MASK | \ + USB_E_TXE1_MASK | USB_E_BSY_MASK | \ + USB_E_TXB_MASK | USB_E_RXB_MASK) + +#define USB_E_TXE_MASK (USB_E_TXE4_MASK | USB_E_TXE3_MASK|\ + USB_E_TXE2_MASK | USB_E_TXE1_MASK) +/* USB Status Register define */ +#define USB_IDLE_STATUS_MASK 0x01 + +/* USB Start of Frame Timer */ +#define USB_USSFT_MASK 0x3FFF + +/* USB Frame Number Register */ +#define USB_USFRN_MASK 0xFFFF + +struct usb_device_para{ + u16 epptr[4]; + u32 rstate; + u32 rptr; + u16 frame_n; + u16 rbcnt; + u32 rtemp; + u32 rxusb_data; + u16 rxuptr; + u8 reso[2]; + u32 softbl; + u8 sofucrctemp; +}; + +struct usb_ep_para{ + u16 rbase; + u16 tbase; + u8 rbmr; + u8 tbmr; + u16 mrblr; + u16 rbptr; + u16 tbptr; + u32 tstate; + u32 tptr; + u16 tcrc; + u16 tbcnt; + u32 ttemp; + u16 txusbu_ptr; + u8 reserve[2]; +}; + +#define USB_BUSMODE_GBL 0x20 +#define USB_BUSMODE_BO_MASK 0x18 +#define USB_BUSMODE_BO_SHIFT 0x3 +#define USB_BUSMODE_BE 0x2 +#define USB_BUSMODE_CETM 0x04 +#define USB_BUSMODE_DTB 0x02 + +/* Endpoint basic handle */ +#define ep_index(EP) ((EP)->ep.desc->bEndpointAddress & 0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) +#define ep_is_in(EP) ((ep_index(EP) == 0) ? (EP->udc->ep0_dir == \ + USB_DIR_IN) : ((EP)->ep.desc->bEndpointAddress \ + & USB_DIR_IN) == USB_DIR_IN) + +/* ep0 transfer state */ +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 + +/* ep tramsfer mode */ +#define USBP_TM_CTL 0 +#define USBP_TM_ISO 1 +#define USBP_TM_BULK 2 +#define USBP_TM_INT 3 + +/*----------------------------------------------------------------------------- + USB RX And TX DATA Frame + -----------------------------------------------------------------------------*/ +struct qe_frame{ + u8 *data; + u32 len; + u32 status; + u32 info; + + void *privdata; + struct list_head node; +}; + +/* Frame structure, info field. */ +#define PID_DATA0 0x80000000 /* Data toggle zero */ +#define PID_DATA1 0x40000000 /* Data toggle one */ +#define PID_SETUP 0x20000000 /* setup bit */ +#define SETUP_STATUS 0x10000000 /* setup status bit */ +#define SETADDR_STATUS 0x08000000 /* setupup address status bit */ +#define NO_REQ 0x04000000 /* Frame without request */ +#define HOST_DATA 0x02000000 /* Host data frame */ +#define FIRST_PACKET_IN_FRAME 0x01000000 /* first packet in the frame */ +#define TOKEN_FRAME 0x00800000 /* Host token frame */ +#define ZLP 0x00400000 /* Zero length packet */ +#define IN_TOKEN_FRAME 0x00200000 /* In token package */ +#define OUT_TOKEN_FRAME 0x00100000 /* Out token package */ +#define SETUP_TOKEN_FRAME 0x00080000 /* Setup token package */ +#define STALL_FRAME 0x00040000 /* Stall handshake */ +#define NACK_FRAME 0x00020000 /* Nack handshake */ +#define NO_PID 0x00010000 /* No send PID */ +#define NO_CRC 0x00008000 /* No send CRC */ +#define HOST_COMMAND 0x00004000 /* Host command frame */ + +/* Frame status field */ +/* Receive side */ +#define FRAME_OK 0x00000000 /* Frame transmitted or received OK */ +#define FRAME_ERROR 0x80000000 /* Error occurred on frame */ +#define START_FRAME_LOST 0x40000000 /* START_FRAME_LOST */ +#define END_FRAME_LOST 0x20000000 /* END_FRAME_LOST */ +#define RX_ER_NONOCT 0x10000000 /* Rx Non Octet Aligned Packet */ +#define RX_ER_BITSTUFF 0x08000000 /* Frame Aborted --Received packet + with bit stuff error */ +#define RX_ER_CRC 0x04000000 /* Received packet with CRC error */ +#define RX_ER_OVERUN 0x02000000 /* Over-run occurred on reception */ +#define RX_ER_PID 0x01000000 /* Wrong PID received */ +/* Tranmit side */ +#define TX_ER_NAK 0x00800000 /* Received NAK handshake */ +#define TX_ER_STALL 0x00400000 /* Received STALL handshake */ +#define TX_ER_TIMEOUT 0x00200000 /* Transmit time out */ +#define TX_ER_UNDERUN 0x00100000 /* Transmit underrun */ +#define FRAME_INPROGRESS 0x00080000 /* Frame is being transmitted */ +#define ER_DATA_UNDERUN 0x00040000 /* Frame is shorter then expected */ +#define ER_DATA_OVERUN 0x00020000 /* Frame is longer then expected */ + +/* QE USB frame operation functions */ +#define frame_get_length(frm) (frm->len) +#define frame_set_length(frm, leng) (frm->len = leng) +#define frame_get_data(frm) (frm->data) +#define frame_set_data(frm, dat) (frm->data = dat) +#define frame_get_info(frm) (frm->info) +#define frame_set_info(frm, inf) (frm->info = inf) +#define frame_get_status(frm) (frm->status) +#define frame_set_status(frm, stat) (frm->status = stat) +#define frame_get_privdata(frm) (frm->privdata) +#define frame_set_privdata(frm, dat) (frm->privdata = dat) + +static inline void qe_frame_clean(struct qe_frame *frm) +{ + frame_set_data(frm, NULL); + frame_set_length(frm, 0); + frame_set_status(frm, FRAME_OK); + frame_set_info(frm, 0); + frame_set_privdata(frm, NULL); +} + +static inline void qe_frame_init(struct qe_frame *frm) +{ + qe_frame_clean(frm); + INIT_LIST_HEAD(&(frm->node)); +} + +struct qe_req { + struct usb_request req; + struct list_head queue; + /* ep_queue() func will add + a request->queue into a udc_ep->queue 'd tail */ + struct qe_ep *ep; + unsigned mapped:1; +}; + +struct qe_ep { + struct usb_ep ep; + struct list_head queue; + struct qe_udc *udc; + struct usb_gadget *gadget; + + u8 state; + + struct qe_bd __iomem *rxbase; + struct qe_bd __iomem *n_rxbd; + struct qe_bd __iomem *e_rxbd; + + struct qe_bd __iomem *txbase; + struct qe_bd __iomem *n_txbd; + struct qe_bd __iomem *c_txbd; + + struct qe_frame *rxframe; + u8 *rxbuffer; + dma_addr_t rxbuf_d; + u8 rxbufmap; + unsigned char localnack; + int has_data; + + struct qe_frame *txframe; + struct qe_req *tx_req; + int sent; /*data already sent */ + int last; /*data sent in the last time*/ + + u8 dir; + u8 epnum; + u8 tm; /* transfer mode */ + u8 data01; + u8 init; + + u8 already_seen; + u8 enable_tasklet; + u8 setup_stage; + u32 last_io; /* timestamp */ + + char name[14]; + + unsigned double_buf:1; + unsigned stopped:1; + unsigned fnf:1; + unsigned has_dma:1; + + u8 ackwait; + u8 dma_channel; + u16 dma_counter; + int lch; + + struct timer_list timer; +}; + +struct qe_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct device *dev; + struct qe_ep eps[USB_MAX_ENDPOINTS]; + struct usb_ctrlrequest local_setup_buff; + spinlock_t lock; /* lock for set/config qe_udc */ + unsigned long soc_type; /* QE or CPM soc */ + + struct qe_req *status_req; /* ep0 status request */ + + /* USB and EP Parameter Block pointer */ + struct usb_device_para __iomem *usb_param; + struct usb_ep_para __iomem *ep_param[4]; + + u32 max_pipes; /* Device max pipes */ + u32 max_use_endpts; /* Max endpointes to be used */ + u32 bus_reset; /* Device is bus reseting */ + u32 resume_state; /* USB state to resume*/ + u32 usb_state; /* USB current state */ + u32 usb_next_state; /* USB next state */ + u32 ep0_state; /* Enpoint zero state */ + u32 ep0_dir; /* Enpoint zero direction: can be + USB_DIR_IN or USB_DIR_OUT*/ + u32 usb_sof_count; /* SOF count */ + u32 errors; /* USB ERRORs count */ + + u8 *tmpbuf; + u32 c_start; + u32 c_end; + + u8 *nullbuf; + u8 *statusbuf; + dma_addr_t nullp; + u8 nullmap; + u8 device_address; /* Device USB address */ + + unsigned int usb_clock; + unsigned int usb_irq; + struct usb_ctlr __iomem *usb_regs; + + struct tasklet_struct rx_tasklet; + + struct completion *done; /* to make sure release() is done */ +}; + +#define EP_STATE_IDLE 0 +#define EP_STATE_NACK 1 +#define EP_STATE_STALL 2 + +/* + * transmit BD's status + */ +#define T_R 0x80000000 /* ready bit */ +#define T_W 0x20000000 /* wrap bit */ +#define T_I 0x10000000 /* interrupt on completion */ +#define T_L 0x08000000 /* last */ +#define T_TC 0x04000000 /* transmit CRC */ +#define T_CNF 0x02000000 /* wait for transmit confirm */ +#define T_LSP 0x01000000 /* Low-speed transaction */ +#define T_PID 0x00c00000 /* packet id */ +#define T_NAK 0x00100000 /* No ack. */ +#define T_STAL 0x00080000 /* Stall received */ +#define T_TO 0x00040000 /* time out */ +#define T_UN 0x00020000 /* underrun */ + +#define DEVICE_T_ERROR (T_UN | T_TO) +#define HOST_T_ERROR (T_UN | T_TO | T_NAK | T_STAL) +#define DEVICE_T_BD_MASK DEVICE_T_ERROR +#define HOST_T_BD_MASK HOST_T_ERROR + +#define T_PID_SHIFT 6 +#define T_PID_DATA0 0x00800000 /* Data 0 toggle */ +#define T_PID_DATA1 0x00c00000 /* Data 1 toggle */ + +/* + * receive BD's status + */ +#define R_E 0x80000000 /* buffer empty */ +#define R_W 0x20000000 /* wrap bit */ +#define R_I 0x10000000 /* interrupt on reception */ +#define R_L 0x08000000 /* last */ +#define R_F 0x04000000 /* first */ +#define R_PID 0x00c00000 /* packet id */ +#define R_NO 0x00100000 /* Rx Non Octet Aligned Packet */ +#define R_AB 0x00080000 /* Frame Aborted */ +#define R_CR 0x00040000 /* CRC Error */ +#define R_OV 0x00020000 /* Overrun */ + +#define R_ERROR (R_NO | R_AB | R_CR | R_OV) +#define R_BD_MASK R_ERROR + +#define R_PID_DATA0 0x00000000 +#define R_PID_DATA1 0x00400000 +#define R_PID_SETUP 0x00800000 + +#define CPM_USB_STOP_TX 0x2e600000 +#define CPM_USB_RESTART_TX 0x2e600000 +#define CPM_USB_STOP_TX_OPCODE 0x0a +#define CPM_USB_RESTART_TX_OPCODE 0x0b +#define CPM_USB_EP_SHIFT 5 + +#endif /* __FSL_QE_UDC_H */ diff --git a/drivers/usb/gadget/fsl_udc_core.c b/drivers/usb/gadget/fsl_udc_core.c new file mode 100644 index 00000000..a766a4ca --- /dev/null +++ b/drivers/usb/gadget/fsl_udc_core.c @@ -0,0 +1,2682 @@ +/* + * Copyright (C) 2004-2007,2011-2012 Freescale Semiconductor, Inc. + * All rights reserved. + * + * Author: Li Yang + * Jiang Bo + * + * Description: + * Freescale high-speed USB SOC DR module device controller driver. + * This can be found on MPC8349E/MPC8313E/MPC5121E cpus. + * The driver is previously named as mpc_udc. Based on bare board + * code from Dave Liu and Shlomi Gridish. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#undef VERBOSE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "fsl_usb2_udc.h" + +#define DRIVER_DESC "Freescale High-Speed USB SOC Device Controller driver" +#define DRIVER_AUTHOR "Li Yang/Jiang Bo" +#define DRIVER_VERSION "Apr 20, 2007" + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +static const char driver_name[] = "fsl-usb2-udc"; +static const char driver_desc[] = DRIVER_DESC; + +static struct usb_dr_device *dr_regs; + +static struct usb_sys_interface *usb_sys_regs; + +/* it is initialized in probe() */ +static struct fsl_udc *udc_controller = NULL; + +static const struct usb_endpoint_descriptor +fsl_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, +}; + +static void fsl_ep_fifo_flush(struct usb_ep *_ep); + +#ifdef CONFIG_PPC32 +/* + * On some SoCs, the USB controller registers can be big or little endian, + * depending on the version of the chip. In order to be able to run the + * same kernel binary on 2 different versions of an SoC, the BE/LE decision + * must be made at run time. _fsl_readl and fsl_writel are pointers to the + * BE or LE readl() and writel() functions, and fsl_readl() and fsl_writel() + * call through those pointers. Platform code for SoCs that have BE USB + * registers should set pdata->big_endian_mmio flag. + * + * This also applies to controller-to-cpu accessors for the USB descriptors, + * since their endianness is also SoC dependant. Platform code for SoCs that + * have BE USB descriptors should set pdata->big_endian_desc flag. + */ +static u32 _fsl_readl_be(const unsigned __iomem *p) +{ + return in_be32(p); +} + +static u32 _fsl_readl_le(const unsigned __iomem *p) +{ + return in_le32(p); +} + +static void _fsl_writel_be(u32 v, unsigned __iomem *p) +{ + out_be32(p, v); +} + +static void _fsl_writel_le(u32 v, unsigned __iomem *p) +{ + out_le32(p, v); +} + +static u32 (*_fsl_readl)(const unsigned __iomem *p); +static void (*_fsl_writel)(u32 v, unsigned __iomem *p); + +#define fsl_readl(p) (*_fsl_readl)((p)) +#define fsl_writel(v, p) (*_fsl_writel)((v), (p)) + +static inline void fsl_set_accessors(struct fsl_usb2_platform_data *pdata) +{ + if (pdata->big_endian_mmio) { + _fsl_readl = _fsl_readl_be; + _fsl_writel = _fsl_writel_be; + } else { + _fsl_readl = _fsl_readl_le; + _fsl_writel = _fsl_writel_le; + } +} + +static inline u32 cpu_to_hc32(const u32 x) +{ + return udc_controller->pdata->big_endian_desc + ? (__force u32)cpu_to_be32(x) + : (__force u32)cpu_to_le32(x); +} + +static inline u32 hc32_to_cpu(const u32 x) +{ + return udc_controller->pdata->big_endian_desc + ? be32_to_cpu((__force __be32)x) + : le32_to_cpu((__force __le32)x); +} +#else /* !CONFIG_PPC32 */ +static inline void fsl_set_accessors(struct fsl_usb2_platform_data *pdata) {} + +#define fsl_readl(addr) readl(addr) +#define fsl_writel(val32, addr) writel(val32, addr) +#define cpu_to_hc32(x) cpu_to_le32(x) +#define hc32_to_cpu(x) le32_to_cpu(x) +#endif /* CONFIG_PPC32 */ + +/******************************************************************** + * Internal Used Function +********************************************************************/ +/*----------------------------------------------------------------- + * done() - retire a request; caller blocked irqs + * @status : request status to be set, only works when + * request is still in progress. + *--------------------------------------------------------------*/ +static void done(struct fsl_ep *ep, struct fsl_req *req, int status) +{ + struct fsl_udc *udc = NULL; + unsigned char stopped = ep->stopped; + struct ep_td_struct *curr_td, *next_td; + int j; + + udc = (struct fsl_udc *)ep->udc; + /* Removed the req from fsl_ep->queue */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + /* Free dtd for the request */ + next_td = req->head; + for (j = 0; j < req->dtd_count; j++) { + curr_td = next_td; + if (j != req->dtd_count - 1) { + next_td = curr_td->next_td_virt; + } + dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); + } + + usb_gadget_unmap_request(&ep->udc->gadget, &req->req, ep_is_in(ep)); + + if (status && (status != -ESHUTDOWN)) + VDBG("complete %s req %p stat %d len %u/%u", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + ep->stopped = 1; + + spin_unlock(&ep->udc->lock); + /* complete() is from gadget layer, + * eg fsg->bulk_in_complete() */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&ep->udc->lock); + ep->stopped = stopped; +} + +/*----------------------------------------------------------------- + * nuke(): delete all requests related to this ep + * called with spinlock held + *--------------------------------------------------------------*/ +static void nuke(struct fsl_ep *ep, int status) +{ + ep->stopped = 1; + + /* Flush fifo */ + fsl_ep_fifo_flush(&ep->ep); + + /* Whether this eq has request linked */ + while (!list_empty(&ep->queue)) { + struct fsl_req *req = NULL; + + req = list_entry(ep->queue.next, struct fsl_req, queue); + done(ep, req, status); + } +} + +/*------------------------------------------------------------------ + Internal Hardware related function + ------------------------------------------------------------------*/ + +static int dr_controller_setup(struct fsl_udc *udc) +{ + unsigned int tmp, portctrl, ep_num; + unsigned int max_no_of_ep; + unsigned int ctrl; + unsigned long timeout; + +#define FSL_UDC_RESET_TIMEOUT 1000 + + /* Config PHY interface */ + portctrl = fsl_readl(&dr_regs->portsc1); + portctrl &= ~(PORTSCX_PHY_TYPE_SEL | PORTSCX_PORT_WIDTH); + switch (udc->phy_mode) { + case FSL_USB2_PHY_ULPI: + if (udc->pdata->have_sysif_regs) { + if (udc->pdata->controller_ver) { + /* controller version 1.6 or above */ + ctrl = __raw_readl(&usb_sys_regs->control); + ctrl &= ~USB_CTRL_UTMI_PHY_EN; + ctrl |= USB_CTRL_USB_EN; + __raw_writel(ctrl, &usb_sys_regs->control); + } + } + portctrl |= PORTSCX_PTS_ULPI; + break; + case FSL_USB2_PHY_UTMI_WIDE: + portctrl |= PORTSCX_PTW_16BIT; + /* fall through */ + case FSL_USB2_PHY_UTMI: + if (udc->pdata->have_sysif_regs) { + if (udc->pdata->controller_ver) { + /* controller version 1.6 or above */ + ctrl = __raw_readl(&usb_sys_regs->control); + ctrl |= (USB_CTRL_UTMI_PHY_EN | + USB_CTRL_USB_EN); + __raw_writel(ctrl, &usb_sys_regs->control); + mdelay(FSL_UTMI_PHY_DLY); /* Delay for UTMI + PHY CLK to become stable - 10ms*/ + } + } + portctrl |= PORTSCX_PTS_UTMI; + break; + case FSL_USB2_PHY_SERIAL: + portctrl |= PORTSCX_PTS_FSLS; + break; + default: + return -EINVAL; + } + fsl_writel(portctrl, &dr_regs->portsc1); + + /* Stop and reset the usb controller */ + tmp = fsl_readl(&dr_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + fsl_writel(tmp, &dr_regs->usbcmd); + + tmp = fsl_readl(&dr_regs->usbcmd); + tmp |= USB_CMD_CTRL_RESET; + fsl_writel(tmp, &dr_regs->usbcmd); + + /* Wait for reset to complete */ + timeout = jiffies + FSL_UDC_RESET_TIMEOUT; + while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { + if (time_after(jiffies, timeout)) { + ERR("udc reset timeout!\n"); + return -ETIMEDOUT; + } + cpu_relax(); + } + + /* Set the controller as device mode */ + tmp = fsl_readl(&dr_regs->usbmode); + tmp &= ~USB_MODE_CTRL_MODE_MASK; /* clear mode bits */ + tmp |= USB_MODE_CTRL_MODE_DEVICE; + /* Disable Setup Lockout */ + tmp |= USB_MODE_SETUP_LOCK_OFF; + if (udc->pdata->es) + tmp |= USB_MODE_ES; + fsl_writel(tmp, &dr_regs->usbmode); + + /* Clear the setup status */ + fsl_writel(0, &dr_regs->usbsts); + + tmp = udc->ep_qh_dma; + tmp &= USB_EP_LIST_ADDRESS_MASK; + fsl_writel(tmp, &dr_regs->endpointlistaddr); + + VDBG("vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x", + udc->ep_qh, (int)tmp, + fsl_readl(&dr_regs->endpointlistaddr)); + + max_no_of_ep = (0x0000001F & fsl_readl(&dr_regs->dccparams)); + for (ep_num = 1; ep_num < max_no_of_ep; ep_num++) { + tmp = fsl_readl(&dr_regs->endptctrl[ep_num]); + tmp &= ~(EPCTRL_TX_TYPE | EPCTRL_RX_TYPE); + tmp |= (EPCTRL_EP_TYPE_BULK << EPCTRL_TX_EP_TYPE_SHIFT) + | (EPCTRL_EP_TYPE_BULK << EPCTRL_RX_EP_TYPE_SHIFT); + fsl_writel(tmp, &dr_regs->endptctrl[ep_num]); + } + /* Config control enable i/o output, cpu endian register */ +#ifndef CONFIG_ARCH_MXC + if (udc->pdata->have_sysif_regs) { + ctrl = __raw_readl(&usb_sys_regs->control); + ctrl |= USB_CTRL_IOENB; + __raw_writel(ctrl, &usb_sys_regs->control); + } +#endif + +#if defined(CONFIG_PPC32) && !defined(CONFIG_NOT_COHERENT_CACHE) + /* Turn on cache snooping hardware, since some PowerPC platforms + * wholly rely on hardware to deal with cache coherent. */ + + if (udc->pdata->have_sysif_regs) { + /* Setup Snooping for all the 4GB space */ + tmp = SNOOP_SIZE_2GB; /* starts from 0x0, size 2G */ + __raw_writel(tmp, &usb_sys_regs->snoop1); + tmp |= 0x80000000; /* starts from 0x8000000, size 2G */ + __raw_writel(tmp, &usb_sys_regs->snoop2); + } +#endif + + return 0; +} + +/* Enable DR irq and set controller to run state */ +static void dr_controller_run(struct fsl_udc *udc) +{ + u32 temp; + + /* Enable DR irq reg */ + temp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN + | USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN + | USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; + + fsl_writel(temp, &dr_regs->usbintr); + + /* Clear stopped bit */ + udc->stopped = 0; + + /* Set the controller as device mode */ + temp = fsl_readl(&dr_regs->usbmode); + temp |= USB_MODE_CTRL_MODE_DEVICE; + fsl_writel(temp, &dr_regs->usbmode); + + /* Set controller to Run */ + temp = fsl_readl(&dr_regs->usbcmd); + temp |= USB_CMD_RUN_STOP; + fsl_writel(temp, &dr_regs->usbcmd); +} + +static void dr_controller_stop(struct fsl_udc *udc) +{ + unsigned int tmp; + + pr_debug("%s\n", __func__); + + /* if we're in OTG mode, and the Host is currently using the port, + * stop now and don't rip the controller out from under the + * ehci driver + */ + if (udc->gadget.is_otg) { + if (!(fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID)) { + pr_debug("udc: Leaving early\n"); + return; + } + } + + /* disable all INTR */ + fsl_writel(0, &dr_regs->usbintr); + + /* Set stopped bit for isr */ + udc->stopped = 1; + + /* disable IO output */ +/* usb_sys_regs->control = 0; */ + + /* set controller to Stop */ + tmp = fsl_readl(&dr_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + fsl_writel(tmp, &dr_regs->usbcmd); +} + +static void dr_ep_setup(unsigned char ep_num, unsigned char dir, + unsigned char ep_type) +{ + unsigned int tmp_epctrl = 0; + + tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (dir) { + if (ep_num) + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_TX_ENABLE; + tmp_epctrl &= ~EPCTRL_TX_TYPE; + tmp_epctrl |= ((unsigned int)(ep_type) + << EPCTRL_TX_EP_TYPE_SHIFT); + } else { + if (ep_num) + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_RX_ENABLE; + tmp_epctrl &= ~EPCTRL_RX_TYPE; + tmp_epctrl |= ((unsigned int)(ep_type) + << EPCTRL_RX_EP_TYPE_SHIFT); + } + + fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); +} + +static void +dr_ep_change_stall(unsigned char ep_num, unsigned char dir, int value) +{ + u32 tmp_epctrl = 0; + + tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + + if (value) { + /* set the stall bit */ + if (dir) + tmp_epctrl |= EPCTRL_TX_EP_STALL; + else + tmp_epctrl |= EPCTRL_RX_EP_STALL; + } else { + /* clear the stall bit and reset data toggle */ + if (dir) { + tmp_epctrl &= ~EPCTRL_TX_EP_STALL; + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + } else { + tmp_epctrl &= ~EPCTRL_RX_EP_STALL; + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + } + } + fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); +} + +/* Get stall status of a specific ep + Return: 0: not stalled; 1:stalled */ +static int dr_ep_get_stall(unsigned char ep_num, unsigned char dir) +{ + u32 epctrl; + + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (dir) + return (epctrl & EPCTRL_TX_EP_STALL) ? 1 : 0; + else + return (epctrl & EPCTRL_RX_EP_STALL) ? 1 : 0; +} + +/******************************************************************** + Internal Structure Build up functions +********************************************************************/ + +/*------------------------------------------------------------------ +* struct_ep_qh_setup(): set the Endpoint Capabilites field of QH + * @zlt: Zero Length Termination Select (1: disable; 0: enable) + * @mult: Mult field + ------------------------------------------------------------------*/ +static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, + unsigned char dir, unsigned char ep_type, + unsigned int max_pkt_len, + unsigned int zlt, unsigned char mult) +{ + struct ep_queue_head *p_QH = &udc->ep_qh[2 * ep_num + dir]; + unsigned int tmp = 0; + + /* set the Endpoint Capabilites in QH */ + switch (ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + /* Interrupt On Setup (IOS). for control ep */ + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | EP_QUEUE_HEAD_IOS; + break; + case USB_ENDPOINT_XFER_ISOC: + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | (mult << EP_QUEUE_HEAD_MULT_POS); + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; + break; + default: + VDBG("error ep type is %d", ep_type); + return; + } + if (zlt) + tmp |= EP_QUEUE_HEAD_ZLT_SEL; + + p_QH->max_pkt_length = cpu_to_hc32(tmp); + p_QH->next_dtd_ptr = 1; + p_QH->size_ioc_int_sts = 0; +} + +/* Setup qh structure and ep register for ep0. */ +static void ep0_setup(struct fsl_udc *udc) +{ + /* the intialization of an ep includes: fields in QH, Regs, + * fsl_ep struct */ + struct_ep_qh_setup(udc, 0, USB_RECV, USB_ENDPOINT_XFER_CONTROL, + USB_MAX_CTRL_PAYLOAD, 0, 0); + struct_ep_qh_setup(udc, 0, USB_SEND, USB_ENDPOINT_XFER_CONTROL, + USB_MAX_CTRL_PAYLOAD, 0, 0); + dr_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); + dr_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); + + return; + +} + +/*********************************************************************** + Endpoint Management Functions +***********************************************************************/ + +/*------------------------------------------------------------------------- + * when configurations are set, or when interface settings change + * for example the do_set_interface() in gadget layer, + * the driver will enable or disable the relevant endpoints + * ep0 doesn't use this routine. It is always enabled. +-------------------------------------------------------------------------*/ +static int fsl_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fsl_udc *udc = NULL; + struct fsl_ep *ep = NULL; + unsigned short max = 0; + unsigned char mult = 0, zlt; + int retval = -EINVAL; + unsigned long flags = 0; + + ep = container_of(_ep, struct fsl_ep, ep); + + /* catch various bogus parameters */ + if (!_ep || !desc + || (desc->bDescriptorType != USB_DT_ENDPOINT)) + return -EINVAL; + + udc = ep->udc; + + if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + max = usb_endpoint_maxp(desc); + + /* Disable automatic zlp generation. Driver is responsible to indicate + * explicitly through req->req.zero. This is needed to enable multi-td + * request. */ + zlt = 1; + + /* Assume the max packet size from gadget is always correct */ + switch (desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + /* mult = 0. Execute N Transactions as demonstrated by + * the USB variable length packet protocol where N is + * computed using the Maximum Packet Length (dQH) and + * the Total Bytes field (dTD) */ + mult = 0; + break; + case USB_ENDPOINT_XFER_ISOC: + /* Calculate transactions needed for high bandwidth iso */ + mult = (unsigned char)(1 + ((max >> 11) & 0x03)); + max = max & 0x7ff; /* bit 0~10 */ + /* 3 transactions at most */ + if (mult > 3) + goto en_done; + break; + default: + goto en_done; + } + + spin_lock_irqsave(&udc->lock, flags); + ep->ep.maxpacket = max; + ep->ep.desc = desc; + ep->stopped = 0; + + /* Controller related setup */ + /* Init EPx Queue Head (Ep Capabilites field in QH + * according to max, zlt, mult) */ + struct_ep_qh_setup(udc, (unsigned char) ep_index(ep), + (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char) (desc->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK), + max, zlt, mult); + + /* Init endpoint ctrl register */ + dr_ep_setup((unsigned char) ep_index(ep), + (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char) (desc->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK)); + + spin_unlock_irqrestore(&udc->lock, flags); + retval = 0; + + VDBG("enabled %s (ep%d%s) maxpacket %d",ep->ep.name, + ep->ep.desc->bEndpointAddress & 0x0f, + (desc->bEndpointAddress & USB_DIR_IN) + ? "in" : "out", max); +en_done: + return retval; +} + +/*--------------------------------------------------------------------- + * @ep : the ep being unconfigured. May not be ep0 + * Any pending and uncomplete req will complete with status (-ESHUTDOWN) +*---------------------------------------------------------------------*/ +static int fsl_ep_disable(struct usb_ep *_ep) +{ + struct fsl_udc *udc = NULL; + struct fsl_ep *ep = NULL; + unsigned long flags = 0; + u32 epctrl; + int ep_num; + + ep = container_of(_ep, struct fsl_ep, ep); + if (!_ep || !ep->ep.desc) { + VDBG("%s not enabled", _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + /* disable ep on controller */ + ep_num = ep_index(ep); + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) { + epctrl &= ~(EPCTRL_TX_ENABLE | EPCTRL_TX_TYPE); + epctrl |= EPCTRL_EP_TYPE_BULK << EPCTRL_TX_EP_TYPE_SHIFT; + } else { + epctrl &= ~(EPCTRL_RX_ENABLE | EPCTRL_TX_TYPE); + epctrl |= EPCTRL_EP_TYPE_BULK << EPCTRL_RX_EP_TYPE_SHIFT; + } + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + + udc = (struct fsl_udc *)ep->udc; + spin_lock_irqsave(&udc->lock, flags); + + /* nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + ep->ep.desc = NULL; + ep->stopped = 1; + spin_unlock_irqrestore(&udc->lock, flags); + + VDBG("disabled %s OK", _ep->name); + return 0; +} + +/*--------------------------------------------------------------------- + * allocate a request object used by this endpoint + * the main operation is to insert the req->queue to the eq->queue + * Returns the request, or null if one could not be allocated +*---------------------------------------------------------------------*/ +static struct usb_request * +fsl_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct fsl_req *req = NULL; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + req->req.dma = DMA_ADDR_INVALID; + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void fsl_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fsl_req *req = NULL; + + req = container_of(_req, struct fsl_req, req); + + if (_req) + kfree(req); +} + +/* Actually add a dTD chain to an empty dQH and let go */ +static void fsl_prime_ep(struct fsl_ep *ep, struct ep_td_struct *td) +{ + struct ep_queue_head *qh = get_qh_by_ep(ep); + + /* Write dQH next pointer and terminate bit to 0 */ + qh->next_dtd_ptr = cpu_to_hc32(td->td_dma + & EP_QUEUE_HEAD_NEXT_POINTER_MASK); + + /* Clear active and halt bit */ + qh->size_ioc_int_sts &= cpu_to_hc32(~(EP_QUEUE_HEAD_STATUS_ACTIVE + | EP_QUEUE_HEAD_STATUS_HALT)); + + /* Ensure that updates to the QH will occur before priming. */ + wmb(); + + /* Prime endpoint by writing correct bit to ENDPTPRIME */ + fsl_writel(ep_is_in(ep) ? (1 << (ep_index(ep) + 16)) + : (1 << (ep_index(ep))), &dr_regs->endpointprime); +} + +/* Add dTD chain to the dQH of an EP */ +static void fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) +{ + u32 temp, bitmask, tmp_stat; + + /* VDBG("QH addr Register 0x%8x", dr_regs->endpointlistaddr); + VDBG("ep_qh[%d] addr is 0x%8x", i, (u32)&(ep->udc->ep_qh[i])); */ + + bitmask = ep_is_in(ep) + ? (1 << (ep_index(ep) + 16)) + : (1 << (ep_index(ep))); + + /* check if the pipe is empty */ + if (!(list_empty(&ep->queue)) && !(ep_index(ep) == 0)) { + /* Add td to the end */ + struct fsl_req *lastreq; + lastreq = list_entry(ep->queue.prev, struct fsl_req, queue); + lastreq->tail->next_td_ptr = + cpu_to_hc32(req->head->td_dma & DTD_ADDR_MASK); + /* Ensure dTD's next dtd pointer to be updated */ + wmb(); + /* Read prime bit, if 1 goto done */ + if (fsl_readl(&dr_regs->endpointprime) & bitmask) + return; + + do { + /* Set ATDTW bit in USBCMD */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp | USB_CMD_ATDTW, &dr_regs->usbcmd); + + /* Read correct status bit */ + tmp_stat = fsl_readl(&dr_regs->endptstatus) & bitmask; + + } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_ATDTW)); + + /* Write ATDTW bit to 0 */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp & ~USB_CMD_ATDTW, &dr_regs->usbcmd); + + if (tmp_stat) + return; + } + + fsl_prime_ep(ep, req->head); +} + +/* Fill in the dTD structure + * @req: request that the transfer belongs to + * @length: return actually data length of the dTD + * @dma: return dma address of the dTD + * @is_last: return flag if it is the last dTD of the request + * return: pointer to the built dTD */ +static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, + dma_addr_t *dma, int *is_last, gfp_t gfp_flags) +{ + u32 swap_temp; + struct ep_td_struct *dtd; + + /* how big will this transfer be? */ + *length = min(req->req.length - req->req.actual, + (unsigned)EP_MAX_LENGTH_TRANSFER); + + dtd = dma_pool_alloc(udc_controller->td_pool, gfp_flags, dma); + if (dtd == NULL) + return dtd; + + dtd->td_dma = *dma; + /* Clear reserved field */ + swap_temp = hc32_to_cpu(dtd->size_ioc_sts); + swap_temp &= ~DTD_RESERVED_FIELDS; + dtd->size_ioc_sts = cpu_to_hc32(swap_temp); + + /* Init all of buffer page pointers */ + swap_temp = (u32) (req->req.dma + req->req.actual); + dtd->buff_ptr0 = cpu_to_hc32(swap_temp); + dtd->buff_ptr1 = cpu_to_hc32(swap_temp + 0x1000); + dtd->buff_ptr2 = cpu_to_hc32(swap_temp + 0x2000); + dtd->buff_ptr3 = cpu_to_hc32(swap_temp + 0x3000); + dtd->buff_ptr4 = cpu_to_hc32(swap_temp + 0x4000); + + req->req.actual += *length; + + /* zlp is needed if req->req.zero is set */ + if (req->req.zero) { + if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) + *is_last = 1; + else + *is_last = 0; + } else if (req->req.length == req->req.actual) + *is_last = 1; + else + *is_last = 0; + + if ((*is_last) == 0) + VDBG("multi-dtd request!"); + /* Fill in the transfer size; set active bit */ + swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); + + /* Enable interrupt for the last dtd of a request */ + if (*is_last && !req->req.no_interrupt) + swap_temp |= DTD_IOC; + + dtd->size_ioc_sts = cpu_to_hc32(swap_temp); + + mb(); + + VDBG("length = %d address= 0x%x", *length, (int)*dma); + + return dtd; +} + +/* Generate dtd chain for a request */ +static int fsl_req_to_dtd(struct fsl_req *req, gfp_t gfp_flags) +{ + unsigned count; + int is_last; + int is_first =1; + struct ep_td_struct *last_dtd = NULL, *dtd; + dma_addr_t dma; + + do { + dtd = fsl_build_dtd(req, &count, &dma, &is_last, gfp_flags); + if (dtd == NULL) + return -ENOMEM; + + if (is_first) { + is_first = 0; + req->head = dtd; + } else { + last_dtd->next_td_ptr = cpu_to_hc32(dma); + last_dtd->next_td_virt = dtd; + } + last_dtd = dtd; + + req->dtd_count++; + } while (!is_last); + + dtd->next_td_ptr = cpu_to_hc32(DTD_NEXT_TERMINATE); + + req->tail = dtd; + + return 0; +} + +/* queues (submits) an I/O request to an endpoint */ +static int +fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); + struct fsl_req *req = container_of(_req, struct fsl_req, req); + struct fsl_udc *udc; + unsigned long flags; + int ret; + + /* catch various bogus parameters */ + if (!_req || !req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + VDBG("%s, bad params", __func__); + return -EINVAL; + } + if (unlikely(!_ep || !ep->ep.desc)) { + VDBG("%s, bad ep", __func__); + return -EINVAL; + } + if (usb_endpoint_xfer_isoc(ep->ep.desc)) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + req->ep = ep; + + ret = usb_gadget_map_request(&ep->udc->gadget, &req->req, ep_is_in(ep)); + if (ret) + return ret; + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->dtd_count = 0; + + /* build dtds and push them to device queue */ + if (!fsl_req_to_dtd(req, gfp_flags)) { + spin_lock_irqsave(&udc->lock, flags); + fsl_queue_td(ep, req); + } else { + return -ENOMEM; + } + + /* irq handler advances the queue */ + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/* dequeues (cancels, unlinks) an I/O request from an endpoint */ +static int fsl_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); + struct fsl_req *req; + unsigned long flags; + int ep_num, stopped, ret = 0; + u32 epctrl; + + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + stopped = ep->stopped; + + /* Stop the ep before we deal with the queue */ + ep->stopped = 1; + ep_num = ep_index(ep); + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl &= ~EPCTRL_TX_ENABLE; + else + epctrl &= ~EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + ret = -EINVAL; + goto out; + } + + /* The request is in progress, or completed but not dequeued */ + if (ep->queue.next == &req->queue) { + _req->status = -ECONNRESET; + fsl_ep_fifo_flush(_ep); /* flush current transfer */ + + /* The request isn't the last request in this ep queue */ + if (req->queue.next != &ep->queue) { + struct fsl_req *next_req; + + next_req = list_entry(req->queue.next, struct fsl_req, + queue); + + /* prime with dTD of next request */ + fsl_prime_ep(ep, next_req->head); + } + /* The request hasn't been processed, patch up the TD chain */ + } else { + struct fsl_req *prev_req; + + prev_req = list_entry(req->queue.prev, struct fsl_req, queue); + prev_req->tail->next_td_ptr = req->tail->next_td_ptr; + } + + done(ep, req, -ECONNRESET); + + /* Enable EP */ +out: epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl |= EPCTRL_TX_ENABLE; + else + epctrl |= EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + ep->stopped = stopped; + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +/*-------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------- + * modify the endpoint halt feature + * @ep: the non-isochronous endpoint being stalled + * @value: 1--set halt 0--clear halt + * Returns zero, or a negative error code. +*----------------------------------------------------------------*/ +static int fsl_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct fsl_ep *ep = NULL; + unsigned long flags = 0; + int status = -EOPNOTSUPP; /* operation not supported */ + unsigned char ep_dir = 0, ep_num = 0; + struct fsl_udc *udc = NULL; + + ep = container_of(_ep, struct fsl_ep, ep); + udc = ep->udc; + if (!_ep || !ep->ep.desc) { + status = -EINVAL; + goto out; + } + + if (usb_endpoint_xfer_isoc(ep->ep.desc)) { + status = -EOPNOTSUPP; + goto out; + } + + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + status = 0; + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + ep_num = (unsigned char)(ep_index(ep)); + spin_lock_irqsave(&ep->udc->lock, flags); + dr_ep_change_stall(ep_num, ep_dir, value); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + if (ep_index(ep) == 0) { + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; + } +out: + VDBG(" %s %s halt stat %d", ep->ep.name, + value ? "set" : "clear", status); + + return status; +} + +static int fsl_ep_fifo_status(struct usb_ep *_ep) +{ + struct fsl_ep *ep; + struct fsl_udc *udc; + int size = 0; + u32 bitmask; + struct ep_queue_head *qh; + + ep = container_of(_ep, struct fsl_ep, ep); + if (!_ep || (!ep->ep.desc && ep_index(ep) != 0)) + return -ENODEV; + + udc = (struct fsl_udc *)ep->udc; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + qh = get_qh_by_ep(ep); + + bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + if (fsl_readl(&dr_regs->endptstatus) & bitmask) + size = (qh->size_ioc_int_sts & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + + pr_debug("%s %u\n", __func__, size); + return size; +} + +static void fsl_ep_fifo_flush(struct usb_ep *_ep) +{ + struct fsl_ep *ep; + int ep_num, ep_dir; + u32 bits; + unsigned long timeout; +#define FSL_UDC_FLUSH_TIMEOUT 1000 + + if (!_ep) { + return; + } else { + ep = container_of(_ep, struct fsl_ep, ep); + if (!ep->ep.desc) + return; + } + ep_num = ep_index(ep); + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + + if (ep_num == 0) + bits = (1 << 16) | 1; + else if (ep_dir == USB_SEND) + bits = 1 << (16 + ep_num); + else + bits = 1 << ep_num; + + timeout = jiffies + FSL_UDC_FLUSH_TIMEOUT; + do { + fsl_writel(bits, &dr_regs->endptflush); + + /* Wait until flush complete */ + while (fsl_readl(&dr_regs->endptflush)) { + if (time_after(jiffies, timeout)) { + ERR("ep flush timeout\n"); + return; + } + cpu_relax(); + } + /* See if we need to flush again */ + } while (fsl_readl(&dr_regs->endptstatus) & bits); +} + +static struct usb_ep_ops fsl_ep_ops = { + .enable = fsl_ep_enable, + .disable = fsl_ep_disable, + + .alloc_request = fsl_alloc_request, + .free_request = fsl_free_request, + + .queue = fsl_ep_queue, + .dequeue = fsl_ep_dequeue, + + .set_halt = fsl_ep_set_halt, + .fifo_status = fsl_ep_fifo_status, + .fifo_flush = fsl_ep_fifo_flush, /* flush fifo */ +}; + +/*------------------------------------------------------------------------- + Gadget Driver Layer Operations +-------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------- + * Get the current frame number (from DR frame_index Reg ) + *----------------------------------------------------------------------*/ +static int fsl_get_frame(struct usb_gadget *gadget) +{ + return (int)(fsl_readl(&dr_regs->frindex) & USB_FRINDEX_MASKS); +} + +/*----------------------------------------------------------------------- + * Tries to wake up the host connected to this gadget + -----------------------------------------------------------------------*/ +static int fsl_wakeup(struct usb_gadget *gadget) +{ + struct fsl_udc *udc = container_of(gadget, struct fsl_udc, gadget); + u32 portsc; + + /* Remote wakeup feature not enabled by host */ + if (!udc->remote_wakeup) + return -ENOTSUPP; + + portsc = fsl_readl(&dr_regs->portsc1); + /* not suspended? */ + if (!(portsc & PORTSCX_PORT_SUSPEND)) + return 0; + /* trigger force resume */ + portsc |= PORTSCX_PORT_FORCE_RESUME; + fsl_writel(portsc, &dr_regs->portsc1); + return 0; +} + +static int can_pullup(struct fsl_udc *udc) +{ + return udc->driver && udc->softconnect && udc->vbus_active; +} + +/* Notify controller that VBUS is powered, Called by whatever + detects VBUS sessions */ +static int fsl_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct fsl_udc *udc; + unsigned long flags; + + udc = container_of(gadget, struct fsl_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + VDBG("VBUS %s", is_active ? "on" : "off"); + udc->vbus_active = (is_active != 0); + if (can_pullup(udc)) + fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + else + fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/* constrain controller's VBUS power usage + * This call is used by gadget drivers during SET_CONFIGURATION calls, + * reporting how much power the device may consume. For example, this + * could affect how quickly batteries are recharged. + * + * Returns zero on success, else negative errno. + */ +static int fsl_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct fsl_udc *udc; + + udc = container_of(gadget, struct fsl_udc, gadget); + if (!IS_ERR_OR_NULL(udc->transceiver)) + return usb_phy_set_power(udc->transceiver, mA); + return -ENOTSUPP; +} + +/* Change Data+ pullup status + * this func is used by usb_gadget_connect/disconnet + */ +static int fsl_pullup(struct usb_gadget *gadget, int is_on) +{ + struct fsl_udc *udc; + + udc = container_of(gadget, struct fsl_udc, gadget); + udc->softconnect = (is_on != 0); + if (can_pullup(udc)) + fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + else + fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + + return 0; +} + +static int fsl_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int fsl_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); +/* defined in gadget.h */ +static const struct usb_gadget_ops fsl_gadget_ops = { + .get_frame = fsl_get_frame, + .wakeup = fsl_wakeup, +/* .set_selfpowered = fsl_set_selfpowered, */ /* Always selfpowered */ + .vbus_session = fsl_vbus_session, + .vbus_draw = fsl_vbus_draw, + .pullup = fsl_pullup, + .udc_start = fsl_udc_start, + .udc_stop = fsl_udc_stop, +}; + +/* Set protocol stall on ep0, protocol stall will automatically be cleared + on new transaction */ +static void ep0stall(struct fsl_udc *udc) +{ + u32 tmp; + + /* must set tx and rx to stall at the same time */ + tmp = fsl_readl(&dr_regs->endptctrl[0]); + tmp |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; + fsl_writel(tmp, &dr_regs->endptctrl[0]); + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; +} + +/* Prime a status phase for ep0 */ +static int ep0_prime_status(struct fsl_udc *udc, int direction) +{ + struct fsl_req *req = udc->status_req; + struct fsl_ep *ep; + int ret; + + if (direction == EP_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + ep = &udc->eps[0]; + if (udc->ep0_state != DATA_STATE_XMIT) + udc->ep0_state = WAIT_FOR_OUT_STATUS; + + req->ep = ep; + req->req.length = 0; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = NULL; + req->dtd_count = 0; + + ret = usb_gadget_map_request(&ep->udc->gadget, &req->req, ep_is_in(ep)); + if (ret) + return ret; + + if (fsl_req_to_dtd(req, GFP_ATOMIC) == 0) + fsl_queue_td(ep, req); + else + return -ENOMEM; + + list_add_tail(&req->queue, &ep->queue); + + return 0; +} + +static void udc_reset_ep_queue(struct fsl_udc *udc, u8 pipe) +{ + struct fsl_ep *ep = get_ep_by_pipe(udc, pipe); + + if (ep->name) + nuke(ep, -ESHUTDOWN); +} + +/* + * ch9 Set address + */ +static void ch9setaddress(struct fsl_udc *udc, u16 value, u16 index, u16 length) +{ + /* Save the new address to device struct */ + udc->device_address = (u8) value; + /* Update usb state */ + udc->usb_state = USB_STATE_ADDRESS; + /* Status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); +} + +/* + * ch9 Get status + */ +static void ch9getstatus(struct fsl_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) +{ + u16 tmp = 0; /* Status, cpu endian */ + struct fsl_req *req; + struct fsl_ep *ep; + int ret; + + ep = &udc->eps[0]; + + if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* Get device status */ + tmp = 1 << USB_DEVICE_SELF_POWERED; + tmp |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { + /* Get interface status */ + /* We don't have interface information in udc driver */ + tmp = 0; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { + /* Get endpoint status */ + struct fsl_ep *target_ep; + + target_ep = get_ep_by_pipe(udc, get_pipe_by_windex(index)); + + /* stall if endpoint doesn't exist */ + if (!target_ep->ep.desc) + goto stall; + tmp = dr_ep_get_stall(ep_index(target_ep), ep_is_in(target_ep)) + << USB_ENDPOINT_HALT; + } + + udc->ep0_dir = USB_DIR_IN; + /* Borrow the per device status_req */ + req = udc->status_req; + /* Fill in the reqest structure */ + *((u16 *) req->req.buf) = cpu_to_le16(tmp); + + req->ep = ep; + req->req.length = 2; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = NULL; + req->dtd_count = 0; + + ret = usb_gadget_map_request(&ep->udc->gadget, &req->req, ep_is_in(ep)); + if (ret) + goto stall; + + /* prime the data phase */ + if ((fsl_req_to_dtd(req, GFP_ATOMIC) == 0)) + fsl_queue_td(ep, req); + else /* no mem */ + goto stall; + + list_add_tail(&req->queue, &ep->queue); + udc->ep0_state = DATA_STATE_XMIT; + if (ep0_prime_status(udc, EP_DIR_OUT)) + ep0stall(udc); + + return; +stall: + ep0stall(udc); +} + +static void setup_received_irq(struct fsl_udc *udc, + struct usb_ctrlrequest *setup) +{ + u16 wValue = le16_to_cpu(setup->wValue); + u16 wIndex = le16_to_cpu(setup->wIndex); + u16 wLength = le16_to_cpu(setup->wLength); + + udc_reset_ep_queue(udc, 0); + + /* We process some stardard setup requests here */ + switch (setup->bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase from udc */ + if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + ch9getstatus(udc, setup->bRequestType, wValue, wIndex, wLength); + return; + + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (setup->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD + | USB_RECIP_DEVICE)) + break; + ch9setaddress(udc, wValue, wIndex, wLength); + return; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Status phase from udc */ + { + int rc = -EOPNOTSUPP; + u16 ptc = 0; + + if ((setup->bRequestType & (USB_RECIP_MASK | USB_TYPE_MASK)) + == (USB_RECIP_ENDPOINT | USB_TYPE_STANDARD)) { + int pipe = get_pipe_by_windex(wIndex); + struct fsl_ep *ep; + + if (wValue != 0 || wLength != 0 || pipe >= udc->max_ep) + break; + ep = get_ep_by_pipe(udc, pipe); + + spin_unlock(&udc->lock); + rc = fsl_ep_set_halt(&ep->ep, + (setup->bRequest == USB_REQ_SET_FEATURE) + ? 1 : 0); + spin_lock(&udc->lock); + + } else if ((setup->bRequestType & (USB_RECIP_MASK + | USB_TYPE_MASK)) == (USB_RECIP_DEVICE + | USB_TYPE_STANDARD)) { + /* Note: The driver has not include OTG support yet. + * This will be set when OTG support is added */ + if (wValue == USB_DEVICE_TEST_MODE) + ptc = wIndex >> 8; + else if (gadget_is_otg(&udc->gadget)) { + if (setup->bRequest == + USB_DEVICE_B_HNP_ENABLE) + udc->gadget.b_hnp_enable = 1; + else if (setup->bRequest == + USB_DEVICE_A_HNP_SUPPORT) + udc->gadget.a_hnp_support = 1; + else if (setup->bRequest == + USB_DEVICE_A_ALT_HNP_SUPPORT) + udc->gadget.a_alt_hnp_support = 1; + } + rc = 0; + } else + break; + + if (rc == 0) { + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); + } + if (ptc) { + u32 tmp; + + mdelay(10); + tmp = fsl_readl(&dr_regs->portsc1) | (ptc << 16); + fsl_writel(tmp, &dr_regs->portsc1); + printk(KERN_INFO "udc: switch to test mode %d.\n", ptc); + } + + return; + } + + default: + break; + } + + /* Requests handled by gadget */ + if (wLength) { + /* Data phase from gadget, status phase from udc */ + udc->ep0_dir = (setup->bRequestType & USB_DIR_IN) + ? USB_DIR_IN : USB_DIR_OUT; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + ep0stall(udc); + spin_lock(&udc->lock); + udc->ep0_state = (setup->bRequestType & USB_DIR_IN) + ? DATA_STATE_XMIT : DATA_STATE_RECV; + /* + * If the data stage is IN, send status prime immediately. + * See 2.0 Spec chapter 8.5.3.3 for detail. + */ + if (udc->ep0_state == DATA_STATE_XMIT) + if (ep0_prime_status(udc, EP_DIR_OUT)) + ep0stall(udc); + + } else { + /* No data phase, IN status from gadget */ + udc->ep0_dir = USB_DIR_IN; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + ep0stall(udc); + spin_lock(&udc->lock); + udc->ep0_state = WAIT_FOR_OUT_STATUS; + } +} + +/* Process request for Data or Status phase of ep0 + * prime status phase if needed */ +static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0, + struct fsl_req *req) +{ + if (udc->usb_state == USB_STATE_ADDRESS) { + /* Set the new address */ + u32 new_address = (u32) udc->device_address; + fsl_writel(new_address << USB_DEVICE_ADDRESS_BIT_POS, + &dr_regs->deviceaddr); + } + + done(ep0, req, 0); + + switch (udc->ep0_state) { + case DATA_STATE_XMIT: + /* already primed at setup_received_irq */ + udc->ep0_state = WAIT_FOR_OUT_STATUS; + break; + case DATA_STATE_RECV: + /* send status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); + break; + case WAIT_FOR_OUT_STATUS: + udc->ep0_state = WAIT_FOR_SETUP; + break; + case WAIT_FOR_SETUP: + ERR("Unexpect ep0 packets\n"); + break; + default: + ep0stall(udc); + break; + } +} + +/* Tripwire mechanism to ensure a setup packet payload is extracted without + * being corrupted by another incoming setup packet */ +static void tripwire_handler(struct fsl_udc *udc, u8 ep_num, u8 *buffer_ptr) +{ + u32 temp; + struct ep_queue_head *qh; + struct fsl_usb2_platform_data *pdata = udc->pdata; + + qh = &udc->ep_qh[ep_num * 2 + EP_DIR_OUT]; + + /* Clear bit in ENDPTSETUPSTAT */ + temp = fsl_readl(&dr_regs->endptsetupstat); + fsl_writel(temp | (1 << ep_num), &dr_regs->endptsetupstat); + + /* while a hazard exists when setup package arrives */ + do { + /* Set Setup Tripwire */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp | USB_CMD_SUTW, &dr_regs->usbcmd); + + /* Copy the setup packet to local buffer */ + if (pdata->le_setup_buf) { + u32 *p = (u32 *)buffer_ptr; + u32 *s = (u32 *)qh->setup_buffer; + + /* Convert little endian setup buffer to CPU endian */ + *p++ = le32_to_cpu(*s++); + *p = le32_to_cpu(*s); + } else { + memcpy(buffer_ptr, (u8 *) qh->setup_buffer, 8); + } + } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_SUTW)); + + /* Clear Setup Tripwire */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp & ~USB_CMD_SUTW, &dr_regs->usbcmd); +} + +/* process-ep_req(): free the completed Tds for this req */ +static int process_ep_req(struct fsl_udc *udc, int pipe, + struct fsl_req *curr_req) +{ + struct ep_td_struct *curr_td; + int td_complete, actual, remaining_length, j, tmp; + int status = 0; + int errors = 0; + struct ep_queue_head *curr_qh = &udc->ep_qh[pipe]; + int direction = pipe % 2; + + curr_td = curr_req->head; + td_complete = 0; + actual = curr_req->req.length; + + for (j = 0; j < curr_req->dtd_count; j++) { + remaining_length = (hc32_to_cpu(curr_td->size_ioc_sts) + & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + actual -= remaining_length; + + errors = hc32_to_cpu(curr_td->size_ioc_sts); + if (errors & DTD_ERROR_MASK) { + if (errors & DTD_STATUS_HALTED) { + ERR("dTD error %08x QH=%d\n", errors, pipe); + /* Clear the errors and Halt condition */ + tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts); + tmp &= ~errors; + curr_qh->size_ioc_int_sts = cpu_to_hc32(tmp); + status = -EPIPE; + /* FIXME: continue with next queued TD? */ + + break; + } + if (errors & DTD_STATUS_DATA_BUFF_ERR) { + VDBG("Transfer overflow"); + status = -EPROTO; + break; + } else if (errors & DTD_STATUS_TRANSACTION_ERR) { + VDBG("ISO error"); + status = -EILSEQ; + break; + } else + ERR("Unknown error has occurred (0x%x)!\n", + errors); + + } else if (hc32_to_cpu(curr_td->size_ioc_sts) + & DTD_STATUS_ACTIVE) { + VDBG("Request not complete"); + status = REQ_UNCOMPLETE; + return status; + } else if (remaining_length) { + if (direction) { + VDBG("Transmit dTD remaining length not zero"); + status = -EPROTO; + break; + } else { + td_complete++; + break; + } + } else { + td_complete++; + VDBG("dTD transmitted successful"); + } + + if (j != curr_req->dtd_count - 1) + curr_td = (struct ep_td_struct *)curr_td->next_td_virt; + } + + if (status) + return status; + + curr_req->req.actual = actual; + + return 0; +} + +/* Process a DTD completion interrupt */ +static void dtd_complete_irq(struct fsl_udc *udc) +{ + u32 bit_pos; + int i, ep_num, direction, bit_mask, status; + struct fsl_ep *curr_ep; + struct fsl_req *curr_req, *temp_req; + + /* Clear the bits in the register */ + bit_pos = fsl_readl(&dr_regs->endptcomplete); + fsl_writel(bit_pos, &dr_regs->endptcomplete); + + if (!bit_pos) + return; + + for (i = 0; i < udc->max_ep; i++) { + ep_num = i >> 1; + direction = i % 2; + + bit_mask = 1 << (ep_num + 16 * direction); + + if (!(bit_pos & bit_mask)) + continue; + + curr_ep = get_ep_by_pipe(udc, i); + + /* If the ep is configured */ + if (curr_ep->name == NULL) { + WARNING("Invalid EP?"); + continue; + } + + /* process the req queue until an uncomplete request */ + list_for_each_entry_safe(curr_req, temp_req, &curr_ep->queue, + queue) { + status = process_ep_req(udc, i, curr_req); + + VDBG("status of process_ep_req= %d, ep = %d", + status, ep_num); + if (status == REQ_UNCOMPLETE) + break; + /* write back status to req */ + curr_req->req.status = status; + + if (ep_num == 0) { + ep0_req_complete(udc, curr_ep, curr_req); + break; + } else + done(curr_ep, curr_req, status); + } + } +} + +static inline enum usb_device_speed portscx_device_speed(u32 reg) +{ + switch (reg & PORTSCX_PORT_SPEED_MASK) { + case PORTSCX_PORT_SPEED_HIGH: + return USB_SPEED_HIGH; + case PORTSCX_PORT_SPEED_FULL: + return USB_SPEED_FULL; + case PORTSCX_PORT_SPEED_LOW: + return USB_SPEED_LOW; + default: + return USB_SPEED_UNKNOWN; + } +} + +/* Process a port change interrupt */ +static void port_change_irq(struct fsl_udc *udc) +{ + if (udc->bus_reset) + udc->bus_reset = 0; + + /* Bus resetting is finished */ + if (!(fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET)) + /* Get the speed */ + udc->gadget.speed = + portscx_device_speed(fsl_readl(&dr_regs->portsc1)); + + /* Update USB state */ + if (!udc->resume_state) + udc->usb_state = USB_STATE_DEFAULT; +} + +/* Process suspend interrupt */ +static void suspend_irq(struct fsl_udc *udc) +{ + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + + /* report suspend to the driver, serial.c does not support this */ + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void bus_resume(struct fsl_udc *udc) +{ + udc->usb_state = udc->resume_state; + udc->resume_state = 0; + + /* report resume to the driver, serial.c does not support this */ + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* Clear up all ep queues */ +static int reset_queues(struct fsl_udc *udc) +{ + u8 pipe; + + for (pipe = 0; pipe < udc->max_pipes; pipe++) + udc_reset_ep_queue(udc, pipe); + + /* report disconnect; the driver is already quiesced */ + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + + return 0; +} + +/* Process reset interrupt */ +static void reset_irq(struct fsl_udc *udc) +{ + u32 temp; + unsigned long timeout; + + /* Clear the device address */ + temp = fsl_readl(&dr_regs->deviceaddr); + fsl_writel(temp & ~USB_DEVICE_ADDRESS_MASK, &dr_regs->deviceaddr); + + udc->device_address = 0; + + /* Clear usb state */ + udc->resume_state = 0; + udc->ep0_dir = 0; + udc->ep0_state = WAIT_FOR_SETUP; + udc->remote_wakeup = 0; /* default to 0 on reset */ + udc->gadget.b_hnp_enable = 0; + udc->gadget.a_hnp_support = 0; + udc->gadget.a_alt_hnp_support = 0; + + /* Clear all the setup token semaphores */ + temp = fsl_readl(&dr_regs->endptsetupstat); + fsl_writel(temp, &dr_regs->endptsetupstat); + + /* Clear all the endpoint complete status bits */ + temp = fsl_readl(&dr_regs->endptcomplete); + fsl_writel(temp, &dr_regs->endptcomplete); + + timeout = jiffies + 100; + while (fsl_readl(&dr_regs->endpointprime)) { + /* Wait until all endptprime bits cleared */ + if (time_after(jiffies, timeout)) { + ERR("Timeout for reset\n"); + break; + } + cpu_relax(); + } + + /* Write 1s to the flush register */ + fsl_writel(0xffffffff, &dr_regs->endptflush); + + if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) { + VDBG("Bus reset"); + /* Bus is reseting */ + udc->bus_reset = 1; + /* Reset all the queues, include XD, dTD, EP queue + * head and TR Queue */ + reset_queues(udc); + udc->usb_state = USB_STATE_DEFAULT; + } else { + VDBG("Controller reset"); + /* initialize usb hw reg except for regs for EP, not + * touch usbintr reg */ + dr_controller_setup(udc); + + /* Reset all internal used Queues */ + reset_queues(udc); + + ep0_setup(udc); + + /* Enable DR IRQ reg, Set Run bit, change udc state */ + dr_controller_run(udc); + udc->usb_state = USB_STATE_ATTACHED; + } +} + +/* + * USB device controller interrupt handler + */ +static irqreturn_t fsl_udc_irq(int irq, void *_udc) +{ + struct fsl_udc *udc = _udc; + u32 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + + /* Disable ISR for OTG host mode */ + if (udc->stopped) + return IRQ_NONE; + spin_lock_irqsave(&udc->lock, flags); + irq_src = fsl_readl(&dr_regs->usbsts) & fsl_readl(&dr_regs->usbintr); + /* Clear notification bits */ + fsl_writel(irq_src, &dr_regs->usbsts); + + /* VDBG("irq_src [0x%8x]", irq_src); */ + + /* Need to resume? */ + if (udc->usb_state == USB_STATE_SUSPENDED) + if ((fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_SUSPEND) == 0) + bus_resume(udc); + + /* USB Interrupt */ + if (irq_src & USB_STS_INT) { + VDBG("Packet int"); + /* Setup package, we only support ep0 as control ep */ + if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) { + tripwire_handler(udc, 0, + (u8 *) (&udc->local_setup_buff)); + setup_received_irq(udc, &udc->local_setup_buff); + status = IRQ_HANDLED; + } + + /* completion of dtd */ + if (fsl_readl(&dr_regs->endptcomplete)) { + dtd_complete_irq(udc); + status = IRQ_HANDLED; + } + } + + /* SOF (for ISO transfer) */ + if (irq_src & USB_STS_SOF) { + status = IRQ_HANDLED; + } + + /* Port Change */ + if (irq_src & USB_STS_PORT_CHANGE) { + port_change_irq(udc); + status = IRQ_HANDLED; + } + + /* Reset Received */ + if (irq_src & USB_STS_RESET) { + VDBG("reset int"); + reset_irq(udc); + status = IRQ_HANDLED; + } + + /* Sleep Enable (Suspend) */ + if (irq_src & USB_STS_SUSPEND) { + suspend_irq(udc); + status = IRQ_HANDLED; + } + + if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { + VDBG("Error IRQ %x", irq_src); + } + + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +/*----------------------------------------------------------------* + * Hook to gadget drivers + * Called by initialization code of gadget drivers +*----------------------------------------------------------------*/ +static int fsl_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + int retval = 0; + unsigned long flags = 0; + + /* lock is needed but whether should use this lock or another */ + spin_lock_irqsave(&udc_controller->lock, flags); + + driver->driver.bus = NULL; + /* hook up the driver */ + udc_controller->driver = driver; + spin_unlock_irqrestore(&udc_controller->lock, flags); + + if (!IS_ERR_OR_NULL(udc_controller->transceiver)) { + /* Suspend the controller until OTG enable it */ + udc_controller->stopped = 1; + printk(KERN_INFO "Suspend udc for OTG auto detect\n"); + + /* connect to bus through transceiver */ + if (!IS_ERR_OR_NULL(udc_controller->transceiver)) { + retval = otg_set_peripheral( + udc_controller->transceiver->otg, + &udc_controller->gadget); + if (retval < 0) { + ERR("can't bind to transceiver\n"); + driver->unbind(&udc_controller->gadget); + udc_controller->driver = 0; + return retval; + } + } + } else { + /* Enable DR IRQ reg and set USBCMD reg Run bit */ + dr_controller_run(udc_controller); + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_state = WAIT_FOR_SETUP; + udc_controller->ep0_dir = 0; + } + + return retval; +} + +/* Disconnect from gadget driver */ +static int fsl_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct fsl_ep *loop_ep; + unsigned long flags; + + if (!IS_ERR_OR_NULL(udc_controller->transceiver)) + otg_set_peripheral(udc_controller->transceiver->otg, NULL); + + /* stop DR, disable intr */ + dr_controller_stop(udc_controller); + + /* in fact, no needed */ + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_state = WAIT_FOR_SETUP; + udc_controller->ep0_dir = 0; + + /* stand operation */ + spin_lock_irqsave(&udc_controller->lock, flags); + udc_controller->gadget.speed = USB_SPEED_UNKNOWN; + nuke(&udc_controller->eps[0], -ESHUTDOWN); + list_for_each_entry(loop_ep, &udc_controller->gadget.ep_list, + ep.ep_list) + nuke(loop_ep, -ESHUTDOWN); + spin_unlock_irqrestore(&udc_controller->lock, flags); + + udc_controller->driver = NULL; + + return 0; +} + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#include + +static const char proc_filename[] = "driver/fsl_usb2_udc"; + +static int fsl_proc_read(struct seq_file *m, void *v) +{ + unsigned long flags; + int i; + u32 tmp_reg; + struct fsl_ep *ep = NULL; + struct fsl_req *req; + + struct fsl_udc *udc = udc_controller; + + spin_lock_irqsave(&udc->lock, flags); + + /* ------basic driver information ---- */ + seq_printf(m, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n\n", + driver_name, DRIVER_VERSION, + udc->driver ? udc->driver->driver.name : "(none)"); + + /* ------ DR Registers ----- */ + tmp_reg = fsl_readl(&dr_regs->usbcmd); + seq_printf(m, + "USBCMD reg:\n" + "SetupTW: %d\n" + "Run/Stop: %s\n\n", + (tmp_reg & USB_CMD_SUTW) ? 1 : 0, + (tmp_reg & USB_CMD_RUN_STOP) ? "Run" : "Stop"); + + tmp_reg = fsl_readl(&dr_regs->usbsts); + seq_printf(m, + "USB Status Reg:\n" + "Dr Suspend: %d Reset Received: %d System Error: %s " + "USB Error Interrupt: %s\n\n", + (tmp_reg & USB_STS_SUSPEND) ? 1 : 0, + (tmp_reg & USB_STS_RESET) ? 1 : 0, + (tmp_reg & USB_STS_SYS_ERR) ? "Err" : "Normal", + (tmp_reg & USB_STS_ERR) ? "Err detected" : "No err"); + + tmp_reg = fsl_readl(&dr_regs->usbintr); + seq_printf(m, + "USB Interrupt Enable Reg:\n" + "Sleep Enable: %d SOF Received Enable: %d " + "Reset Enable: %d\n" + "System Error Enable: %d " + "Port Change Dectected Enable: %d\n" + "USB Error Intr Enable: %d USB Intr Enable: %d\n\n", + (tmp_reg & USB_INTR_DEVICE_SUSPEND) ? 1 : 0, + (tmp_reg & USB_INTR_SOF_EN) ? 1 : 0, + (tmp_reg & USB_INTR_RESET_EN) ? 1 : 0, + (tmp_reg & USB_INTR_SYS_ERR_EN) ? 1 : 0, + (tmp_reg & USB_INTR_PTC_DETECT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_ERR_INT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_INT_EN) ? 1 : 0); + + tmp_reg = fsl_readl(&dr_regs->frindex); + seq_printf(m, + "USB Frame Index Reg: Frame Number is 0x%x\n\n", + (tmp_reg & USB_FRINDEX_MASKS)); + + tmp_reg = fsl_readl(&dr_regs->deviceaddr); + seq_printf(m, + "USB Device Address Reg: Device Addr is 0x%x\n\n", + (tmp_reg & USB_DEVICE_ADDRESS_MASK)); + + tmp_reg = fsl_readl(&dr_regs->endpointlistaddr); + seq_printf(m, + "USB Endpoint List Address Reg: " + "Device Addr is 0x%x\n\n", + (tmp_reg & USB_EP_LIST_ADDRESS_MASK)); + + tmp_reg = fsl_readl(&dr_regs->portsc1); + seq_printf(m, + "USB Port Status&Control Reg:\n" + "Port Transceiver Type : %s Port Speed: %s\n" + "PHY Low Power Suspend: %s Port Reset: %s " + "Port Suspend Mode: %s\n" + "Over-current Change: %s " + "Port Enable/Disable Change: %s\n" + "Port Enabled/Disabled: %s " + "Current Connect Status: %s\n\n", ( { + const char *s; + switch (tmp_reg & PORTSCX_PTS_FSLS) { + case PORTSCX_PTS_UTMI: + s = "UTMI"; break; + case PORTSCX_PTS_ULPI: + s = "ULPI "; break; + case PORTSCX_PTS_FSLS: + s = "FS/LS Serial"; break; + default: + s = "None"; break; + } + s;} ), + usb_speed_string(portscx_device_speed(tmp_reg)), + (tmp_reg & PORTSCX_PHY_LOW_POWER_SPD) ? + "Normal PHY mode" : "Low power mode", + (tmp_reg & PORTSCX_PORT_RESET) ? "In Reset" : + "Not in Reset", + (tmp_reg & PORTSCX_PORT_SUSPEND) ? "In " : "Not in", + (tmp_reg & PORTSCX_OVER_CURRENT_CHG) ? "Dected" : + "No", + (tmp_reg & PORTSCX_PORT_EN_DIS_CHANGE) ? "Disable" : + "Not change", + (tmp_reg & PORTSCX_PORT_ENABLE) ? "Enable" : + "Not correct", + (tmp_reg & PORTSCX_CURRENT_CONNECT_STATUS) ? + "Attached" : "Not-Att"); + + tmp_reg = fsl_readl(&dr_regs->usbmode); + seq_printf(m, + "USB Mode Reg: Controller Mode is: %s\n\n", ( { + const char *s; + switch (tmp_reg & USB_MODE_CTRL_MODE_HOST) { + case USB_MODE_CTRL_MODE_IDLE: + s = "Idle"; break; + case USB_MODE_CTRL_MODE_DEVICE: + s = "Device Controller"; break; + case USB_MODE_CTRL_MODE_HOST: + s = "Host Controller"; break; + default: + s = "None"; break; + } + s; + } )); + + tmp_reg = fsl_readl(&dr_regs->endptsetupstat); + seq_printf(m, + "Endpoint Setup Status Reg: SETUP on ep 0x%x\n\n", + (tmp_reg & EP_SETUP_STATUS_MASK)); + + for (i = 0; i < udc->max_ep / 2; i++) { + tmp_reg = fsl_readl(&dr_regs->endptctrl[i]); + seq_printf(m, "EP Ctrl Reg [0x%x]: = [0x%x]\n", i, tmp_reg); + } + tmp_reg = fsl_readl(&dr_regs->endpointprime); + seq_printf(m, "EP Prime Reg = [0x%x]\n\n", tmp_reg); + +#ifndef CONFIG_ARCH_MXC + if (udc->pdata->have_sysif_regs) { + tmp_reg = usb_sys_regs->snoop1; + seq_printf(m, "Snoop1 Reg : = [0x%x]\n\n", tmp_reg); + + tmp_reg = usb_sys_regs->control; + seq_printf(m, "General Control Reg : = [0x%x]\n\n", tmp_reg); + } +#endif + + /* ------fsl_udc, fsl_ep, fsl_request structure information ----- */ + ep = &udc->eps[0]; + seq_printf(m, "For %s Maxpkt is 0x%x index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), ep_index(ep)); + + if (list_empty(&ep->queue)) { + seq_puts(m, "its req queue is empty\n\n"); + } else { + list_for_each_entry(req, &ep->queue, queue) { + seq_printf(m, + "req %p actual 0x%x length 0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + } + } + /* other gadget->eplist ep */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->ep.desc) { + seq_printf(m, + "\nFor %s Maxpkt is 0x%x " + "index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), + ep_index(ep)); + + if (list_empty(&ep->queue)) { + seq_puts(m, "its req queue is empty\n\n"); + } else { + list_for_each_entry(req, &ep->queue, queue) { + seq_printf(m, + "req %p actual 0x%x length " + "0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + } /* end for each_entry of ep req */ + } /* end for else */ + } /* end for if(ep->queue) */ + } /* end (ep->desc) */ + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/* + * seq_file wrappers for procfile show routines. + */ +static int fsl_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, fsl_proc_read, NULL); +} + +static const struct file_operations fsl_proc_fops = { + .open = fsl_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define create_proc_file() proc_create(proc_filename, 0, NULL, &fsl_proc_fops) +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +/*-------------------------------------------------------------------------*/ + +/* Release udc structures */ +static void fsl_udc_release(struct device *dev) +{ + complete(udc_controller->done); + dma_free_coherent(dev->parent, udc_controller->ep_qh_size, + udc_controller->ep_qh, udc_controller->ep_qh_dma); + kfree(udc_controller); +} + +/****************************************************************** + Internal structure setup functions +*******************************************************************/ +/*------------------------------------------------------------------ + * init resource for globle controller + * Return the udc handle on success or NULL on failure + ------------------------------------------------------------------*/ +static int __init struct_udc_setup(struct fsl_udc *udc, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + size_t size; + + pdata = pdev->dev.platform_data; + udc->phy_mode = pdata->phy_mode; + + udc->eps = kzalloc(sizeof(struct fsl_ep) * udc->max_ep, GFP_KERNEL); + if (!udc->eps) { + ERR("malloc fsl_ep failed\n"); + return -1; + } + + /* initialized QHs, take care of alignment */ + size = udc->max_ep * sizeof(struct ep_queue_head); + if (size < QH_ALIGNMENT) + size = QH_ALIGNMENT; + else if ((size % QH_ALIGNMENT) != 0) { + size += QH_ALIGNMENT + 1; + size &= ~(QH_ALIGNMENT - 1); + } + udc->ep_qh = dma_alloc_coherent(&pdev->dev, size, + &udc->ep_qh_dma, GFP_KERNEL); + if (!udc->ep_qh) { + ERR("malloc QHs for udc failed\n"); + kfree(udc->eps); + return -1; + } + + udc->ep_qh_size = size; + + /* Initialize ep0 status request structure */ + /* FIXME: fsl_alloc_request() ignores ep argument */ + udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), + struct fsl_req, req); + /* allocate a small amount of memory to get valid address */ + udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); + + udc->resume_state = USB_STATE_NOTATTACHED; + udc->usb_state = USB_STATE_POWERED; + udc->ep0_dir = 0; + udc->remote_wakeup = 0; /* default to 0 on reset */ + + return 0; +} + +/*---------------------------------------------------------------- + * Setup the fsl_ep struct for eps + * Link fsl_ep->ep to gadget->ep_list + * ep0out is not used so do nothing here + * ep0in should be taken care + *--------------------------------------------------------------*/ +static int __init struct_ep_setup(struct fsl_udc *udc, unsigned char index, + char *name, int link) +{ + struct fsl_ep *ep = &udc->eps[index]; + + ep->udc = udc; + strcpy(ep->name, name); + ep->ep.name = ep->name; + + ep->ep.ops = &fsl_ep_ops; + ep->stopped = 0; + + /* for ep0: maxP defined in desc + * for other eps, maxP is set by epautoconfig() called by gadget layer + */ + ep->ep.maxpacket = (unsigned short) ~0; + + /* the queue lists any req for this ep */ + INIT_LIST_HEAD(&ep->queue); + + /* gagdet.ep_list used for ep_autoconfig so no ep0 */ + if (link) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->gadget = &udc->gadget; + ep->qh = &udc->ep_qh[index]; + + return 0; +} + +/* Driver probe function + * all intialization operations implemented here except enabling usb_intr reg + * board setup should have been done in the platform code + */ +static int __init fsl_udc_probe(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + struct resource *res; + int ret = -ENODEV; + unsigned int i; + u32 dccparams; + + udc_controller = kzalloc(sizeof(struct fsl_udc), GFP_KERNEL); + if (udc_controller == NULL) { + ERR("malloc udc failed\n"); + return -ENOMEM; + } + + pdata = pdev->dev.platform_data; + udc_controller->pdata = pdata; + spin_lock_init(&udc_controller->lock); + udc_controller->stopped = 1; + +#ifdef CONFIG_USB_OTG + if (pdata->operating_mode == FSL_USB2_DR_OTG) { + udc_controller->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(udc_controller->transceiver)) { + ERR("Can't find OTG driver!\n"); + ret = -ENODEV; + goto err_kfree; + } + } +#endif + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENXIO; + goto err_kfree; + } + + if (pdata->operating_mode == FSL_USB2_DR_DEVICE) { + if (!request_mem_region(res->start, resource_size(res), + driver_name)) { + ERR("request mem region for %s failed\n", pdev->name); + ret = -EBUSY; + goto err_kfree; + } + } + + dr_regs = ioremap(res->start, resource_size(res)); + if (!dr_regs) { + ret = -ENOMEM; + goto err_release_mem_region; + } + + pdata->regs = (void *)dr_regs; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->init && pdata->init(pdev)) { + ret = -ENODEV; + goto err_iounmap_noclk; + } + + /* Set accessors only after pdata->init() ! */ + fsl_set_accessors(pdata); + +#ifndef CONFIG_ARCH_MXC + if (pdata->have_sysif_regs) + usb_sys_regs = (void *)dr_regs + USB_DR_SYS_OFFSET; +#endif + + /* Initialize USB clocks */ + ret = fsl_udc_clk_init(pdev); + if (ret < 0) + goto err_iounmap_noclk; + + /* Read Device Controller Capability Parameters register */ + dccparams = fsl_readl(&dr_regs->dccparams); + if (!(dccparams & DCCPARAMS_DC)) { + ERR("This SOC doesn't support device role\n"); + ret = -ENODEV; + goto err_iounmap; + } + /* Get max device endpoints */ + /* DEN is bidirectional ep number, max_ep doubles the number */ + udc_controller->max_ep = (dccparams & DCCPARAMS_DEN_MASK) * 2; + + udc_controller->irq = platform_get_irq(pdev, 0); + if (!udc_controller->irq) { + ret = -ENODEV; + goto err_iounmap; + } + + ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, + driver_name, udc_controller); + if (ret != 0) { + ERR("cannot request irq %d err %d\n", + udc_controller->irq, ret); + goto err_iounmap; + } + + /* Initialize the udc structure including QH member and other member */ + if (struct_udc_setup(udc_controller, pdev)) { + ERR("Can't initialize udc data structure\n"); + ret = -ENOMEM; + goto err_free_irq; + } + + if (IS_ERR_OR_NULL(udc_controller->transceiver)) { + /* initialize usb hw reg except for regs for EP, + * leave usbintr reg untouched */ + dr_controller_setup(udc_controller); + } + + ret = fsl_udc_clk_finalize(pdev); + if (ret) + goto err_free_irq; + + /* Setup gadget structure */ + udc_controller->gadget.ops = &fsl_gadget_ops; + udc_controller->gadget.max_speed = USB_SPEED_HIGH; + udc_controller->gadget.ep0 = &udc_controller->eps[0].ep; + INIT_LIST_HEAD(&udc_controller->gadget.ep_list); + udc_controller->gadget.speed = USB_SPEED_UNKNOWN; + udc_controller->gadget.name = driver_name; + + /* Setup gadget.dev and register with kernel */ + dev_set_name(&udc_controller->gadget.dev, "gadget"); + udc_controller->gadget.dev.of_node = pdev->dev.of_node; + + if (!IS_ERR_OR_NULL(udc_controller->transceiver)) + udc_controller->gadget.is_otg = 1; + + /* setup QH and epctrl for ep0 */ + ep0_setup(udc_controller); + + /* setup udc->eps[] for ep0 */ + struct_ep_setup(udc_controller, 0, "ep0", 0); + /* for ep0: the desc defined here; + * for other eps, gadget layer called ep_enable with defined desc + */ + udc_controller->eps[0].ep.desc = &fsl_ep0_desc; + udc_controller->eps[0].ep.maxpacket = USB_MAX_CTRL_PAYLOAD; + + /* setup the udc->eps[] for non-control endpoints and link + * to gadget.ep_list */ + for (i = 1; i < (int)(udc_controller->max_ep / 2); i++) { + char name[14]; + + sprintf(name, "ep%dout", i); + struct_ep_setup(udc_controller, i * 2, name, 1); + sprintf(name, "ep%din", i); + struct_ep_setup(udc_controller, i * 2 + 1, name, 1); + } + + /* use dma_pool for TD management */ + udc_controller->td_pool = dma_pool_create("udc_td", &pdev->dev, + sizeof(struct ep_td_struct), + DTD_ALIGNMENT, UDC_DMA_BOUNDARY); + if (udc_controller->td_pool == NULL) { + ret = -ENOMEM; + goto err_free_irq; + } + + ret = usb_add_gadget_udc_release(&pdev->dev, &udc_controller->gadget, + fsl_udc_release); + if (ret) + goto err_del_udc; + + create_proc_file(); + return 0; + +err_del_udc: + dma_pool_destroy(udc_controller->td_pool); +err_free_irq: + free_irq(udc_controller->irq, udc_controller); +err_iounmap: + if (pdata->exit) + pdata->exit(pdev); + fsl_udc_clk_release(); +err_iounmap_noclk: + iounmap(dr_regs); +err_release_mem_region: + if (pdata->operating_mode == FSL_USB2_DR_DEVICE) + release_mem_region(res->start, resource_size(res)); +err_kfree: + kfree(udc_controller); + udc_controller = NULL; + return ret; +} + +/* Driver removal function + * Free resources and finish pending transactions + */ +static int __exit fsl_udc_remove(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + DECLARE_COMPLETION(done); + + if (!udc_controller) + return -ENODEV; + + usb_del_gadget_udc(&udc_controller->gadget); + udc_controller->done = &done; + + fsl_udc_clk_release(); + + /* DR has been stopped in usb_gadget_unregister_driver() */ + remove_proc_file(); + + /* Free allocated memory */ + kfree(udc_controller->status_req->req.buf); + kfree(udc_controller->status_req); + kfree(udc_controller->eps); + + dma_pool_destroy(udc_controller->td_pool); + free_irq(udc_controller->irq, udc_controller); + iounmap(dr_regs); + if (pdata->operating_mode == FSL_USB2_DR_DEVICE) + release_mem_region(res->start, resource_size(res)); + + /* free udc --wait for the release() finished */ + wait_for_completion(&done); + + /* + * do platform specific un-initialization: + * release iomux pins, etc. + */ + if (pdata->exit) + pdata->exit(pdev); + + return 0; +} + +/*----------------------------------------------------------------- + * Modify Power management attributes + * Used by OTG statemachine to disable gadget temporarily + -----------------------------------------------------------------*/ +static int fsl_udc_suspend(struct platform_device *pdev, pm_message_t state) +{ + dr_controller_stop(udc_controller); + return 0; +} + +/*----------------------------------------------------------------- + * Invoked on USB resume. May be called in_interrupt. + * Here we start the DR controller and enable the irq + *-----------------------------------------------------------------*/ +static int fsl_udc_resume(struct platform_device *pdev) +{ + /* Enable DR irq reg and set controller Run */ + if (udc_controller->stopped) { + dr_controller_setup(udc_controller); + dr_controller_run(udc_controller); + } + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_state = WAIT_FOR_SETUP; + udc_controller->ep0_dir = 0; + return 0; +} + +static int fsl_udc_otg_suspend(struct device *dev, pm_message_t state) +{ + struct fsl_udc *udc = udc_controller; + u32 mode, usbcmd; + + mode = fsl_readl(&dr_regs->usbmode) & USB_MODE_CTRL_MODE_MASK; + + pr_debug("%s(): mode 0x%x stopped %d\n", __func__, mode, udc->stopped); + + /* + * If the controller is already stopped, then this must be a + * PM suspend. Remember this fact, so that we will leave the + * controller stopped at PM resume time. + */ + if (udc->stopped) { + pr_debug("gadget already stopped, leaving early\n"); + udc->already_stopped = 1; + return 0; + } + + if (mode != USB_MODE_CTRL_MODE_DEVICE) { + pr_debug("gadget not in device mode, leaving early\n"); + return 0; + } + + /* stop the controller */ + usbcmd = fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP; + fsl_writel(usbcmd, &dr_regs->usbcmd); + + udc->stopped = 1; + + pr_info("USB Gadget suspended\n"); + + return 0; +} + +static int fsl_udc_otg_resume(struct device *dev) +{ + pr_debug("%s(): stopped %d already_stopped %d\n", __func__, + udc_controller->stopped, udc_controller->already_stopped); + + /* + * If the controller was stopped at suspend time, then + * don't resume it now. + */ + if (udc_controller->already_stopped) { + udc_controller->already_stopped = 0; + pr_debug("gadget was already stopped, leaving early\n"); + return 0; + } + + pr_info("USB Gadget resume\n"); + + return fsl_udc_resume(NULL); +} +/*------------------------------------------------------------------------- + Register entry point for the peripheral controller driver +--------------------------------------------------------------------------*/ +static const struct platform_device_id fsl_udc_devtype[] = { + { + .name = "imx-udc-mx27", + }, { + .name = "imx-udc-mx51", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, fsl_udc_devtype); +static struct platform_driver udc_driver = { + .remove = __exit_p(fsl_udc_remove), + /* Just for FSL i.mx SoC currently */ + .id_table = fsl_udc_devtype, + /* these suspend and resume are not usb suspend and resume */ + .suspend = fsl_udc_suspend, + .resume = fsl_udc_resume, + .driver = { + .name = (char *)driver_name, + .owner = THIS_MODULE, + /* udc suspend/resume called from OTG driver */ + .suspend = fsl_udc_otg_suspend, + .resume = fsl_udc_otg_resume, + }, +}; + +module_platform_driver_probe(udc_driver, fsl_udc_probe); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:fsl-usb2-udc"); diff --git a/drivers/usb/gadget/fsl_usb2_udc.h b/drivers/usb/gadget/fsl_usb2_udc.h new file mode 100644 index 00000000..c6703bb0 --- /dev/null +++ b/drivers/usb/gadget/fsl_usb2_udc.h @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2004,2012 Freescale Semiconductor, Inc + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Freescale USB device/endpoint management registers + */ +#ifndef __FSL_USB2_UDC_H +#define __FSL_USB2_UDC_H + +/* ### define USB registers here + */ +#define USB_MAX_CTRL_PAYLOAD 64 +#define USB_DR_SYS_OFFSET 0x400 + + /* USB DR device mode registers (Little Endian) */ +struct usb_dr_device { + /* Capability register */ + u8 res1[256]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structural Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u8 res2[20]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u8 res3[24]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u8 res4[4]; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u8 res5[4]; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u8 res6[24]; + u32 configflag; /* Configure Flag Register */ + u32 portsc1; /* Port 1 Status and Control Register */ + u8 res7[28]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[6]; /* Endpoint Control Registers */ +}; + + /* USB DR host mode registers (Little Endian) */ +struct usb_dr_host { + /* Capability register */ + u8 res1[256]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structural Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u8 res2[20]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u8 res3[24]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u8 res4[4]; + u32 periodiclistbase; /* Periodic Frame List Base Address Register */ + u32 asynclistaddr; /* Current Asynchronous List Address Register */ + u8 res5[4]; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u8 res6[24]; + u32 configflag; /* Configure Flag Register */ + u32 portsc1; /* Port 1 Status and Control Register */ + u8 res7[28]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[6]; /* Endpoint Control Registers */ +}; + + /* non-EHCI USB system interface registers (Big Endian) */ +struct usb_sys_interface { + u32 snoop1; + u32 snoop2; + u32 age_cnt_thresh; /* Age Count Threshold Register */ + u32 pri_ctrl; /* Priority Control Register */ + u32 si_ctrl; /* System Interface Control Register */ + u8 res[236]; + u32 control; /* General Purpose Control Register */ +}; + +/* ep0 transfer state */ +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 + +/* Device Controller Capability Parameter register */ +#define DCCPARAMS_DC 0x00000080 +#define DCCPARAMS_DEN_MASK 0x0000001f + +/* Frame Index Register Bit Masks */ +#define USB_FRINDEX_MASKS 0x3fff +/* USB CMD Register Bit Masks */ +#define USB_CMD_RUN_STOP 0x00000001 +#define USB_CMD_CTRL_RESET 0x00000002 +#define USB_CMD_PERIODIC_SCHEDULE_EN 0x00000010 +#define USB_CMD_ASYNC_SCHEDULE_EN 0x00000020 +#define USB_CMD_INT_AA_DOORBELL 0x00000040 +#define USB_CMD_ASP 0x00000300 +#define USB_CMD_ASYNC_SCH_PARK_EN 0x00000800 +#define USB_CMD_SUTW 0x00002000 +#define USB_CMD_ATDTW 0x00004000 +#define USB_CMD_ITC 0x00FF0000 + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 0x00000000 +#define USB_CMD_FRAME_SIZE_512 0x00000004 +#define USB_CMD_FRAME_SIZE_256 0x00000008 +#define USB_CMD_FRAME_SIZE_128 0x0000000C +#define USB_CMD_FRAME_SIZE_64 0x00008000 +#define USB_CMD_FRAME_SIZE_32 0x00008004 +#define USB_CMD_FRAME_SIZE_16 0x00008008 +#define USB_CMD_FRAME_SIZE_8 0x0000800C + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 0x00000000 +#define USB_CMD_ASP_01 0x00000100 +#define USB_CMD_ASP_10 0x00000200 +#define USB_CMD_ASP_11 0x00000300 +#define USB_CMD_ASP_BIT_POS 8 + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD 0x00000000 +#define USB_CMD_ITC_1_MICRO_FRM 0x00010000 +#define USB_CMD_ITC_2_MICRO_FRM 0x00020000 +#define USB_CMD_ITC_4_MICRO_FRM 0x00040000 +#define USB_CMD_ITC_8_MICRO_FRM 0x00080000 +#define USB_CMD_ITC_16_MICRO_FRM 0x00100000 +#define USB_CMD_ITC_32_MICRO_FRM 0x00200000 +#define USB_CMD_ITC_64_MICRO_FRM 0x00400000 +#define USB_CMD_ITC_BIT_POS 16 + +/* USB STS Register Bit Masks */ +#define USB_STS_INT 0x00000001 +#define USB_STS_ERR 0x00000002 +#define USB_STS_PORT_CHANGE 0x00000004 +#define USB_STS_FRM_LST_ROLL 0x00000008 +#define USB_STS_SYS_ERR 0x00000010 +#define USB_STS_IAA 0x00000020 +#define USB_STS_RESET 0x00000040 +#define USB_STS_SOF 0x00000080 +#define USB_STS_SUSPEND 0x00000100 +#define USB_STS_HC_HALTED 0x00001000 +#define USB_STS_RCL 0x00002000 +#define USB_STS_PERIODIC_SCHEDULE 0x00004000 +#define USB_STS_ASYNC_SCHEDULE 0x00008000 + +/* USB INTR Register Bit Masks */ +#define USB_INTR_INT_EN 0x00000001 +#define USB_INTR_ERR_INT_EN 0x00000002 +#define USB_INTR_PTC_DETECT_EN 0x00000004 +#define USB_INTR_FRM_LST_ROLL_EN 0x00000008 +#define USB_INTR_SYS_ERR_EN 0x00000010 +#define USB_INTR_ASYN_ADV_EN 0x00000020 +#define USB_INTR_RESET_EN 0x00000040 +#define USB_INTR_SOF_EN 0x00000080 +#define USB_INTR_DEVICE_SUSPEND 0x00000100 + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK 0xFE000000 +#define USB_DEVICE_ADDRESS_BIT_POS 25 + +/* endpoint list address bit masks */ +#define USB_EP_LIST_ADDRESS_MASK 0xfffff800 + +/* PORTSCX Register Bit Masks */ +#define PORTSCX_CURRENT_CONNECT_STATUS 0x00000001 +#define PORTSCX_CONNECT_STATUS_CHANGE 0x00000002 +#define PORTSCX_PORT_ENABLE 0x00000004 +#define PORTSCX_PORT_EN_DIS_CHANGE 0x00000008 +#define PORTSCX_OVER_CURRENT_ACT 0x00000010 +#define PORTSCX_OVER_CURRENT_CHG 0x00000020 +#define PORTSCX_PORT_FORCE_RESUME 0x00000040 +#define PORTSCX_PORT_SUSPEND 0x00000080 +#define PORTSCX_PORT_RESET 0x00000100 +#define PORTSCX_LINE_STATUS_BITS 0x00000C00 +#define PORTSCX_PORT_POWER 0x00001000 +#define PORTSCX_PORT_INDICTOR_CTRL 0x0000C000 +#define PORTSCX_PORT_TEST_CTRL 0x000F0000 +#define PORTSCX_WAKE_ON_CONNECT_EN 0x00100000 +#define PORTSCX_WAKE_ON_CONNECT_DIS 0x00200000 +#define PORTSCX_WAKE_ON_OVER_CURRENT 0x00400000 +#define PORTSCX_PHY_LOW_POWER_SPD 0x00800000 +#define PORTSCX_PORT_FORCE_FULL_SPEED 0x01000000 +#define PORTSCX_PORT_SPEED_MASK 0x0C000000 +#define PORTSCX_PORT_WIDTH 0x10000000 +#define PORTSCX_PHY_TYPE_SEL 0xC0000000 + +/* bit 11-10 are line status */ +#define PORTSCX_LINE_STATUS_SE0 0x00000000 +#define PORTSCX_LINE_STATUS_JSTATE 0x00000400 +#define PORTSCX_LINE_STATUS_KSTATE 0x00000800 +#define PORTSCX_LINE_STATUS_UNDEF 0x00000C00 +#define PORTSCX_LINE_STATUS_BIT_POS 10 + +/* bit 15-14 are port indicator control */ +#define PORTSCX_PIC_OFF 0x00000000 +#define PORTSCX_PIC_AMBER 0x00004000 +#define PORTSCX_PIC_GREEN 0x00008000 +#define PORTSCX_PIC_UNDEF 0x0000C000 +#define PORTSCX_PIC_BIT_POS 14 + +/* bit 19-16 are port test control */ +#define PORTSCX_PTC_DISABLE 0x00000000 +#define PORTSCX_PTC_JSTATE 0x00010000 +#define PORTSCX_PTC_KSTATE 0x00020000 +#define PORTSCX_PTC_SEQNAK 0x00030000 +#define PORTSCX_PTC_PACKET 0x00040000 +#define PORTSCX_PTC_FORCE_EN 0x00050000 +#define PORTSCX_PTC_BIT_POS 16 + +/* bit 27-26 are port speed */ +#define PORTSCX_PORT_SPEED_FULL 0x00000000 +#define PORTSCX_PORT_SPEED_LOW 0x04000000 +#define PORTSCX_PORT_SPEED_HIGH 0x08000000 +#define PORTSCX_PORT_SPEED_UNDEF 0x0C000000 +#define PORTSCX_SPEED_BIT_POS 26 + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSCX_PTW 0x10000000 +#define PORTSCX_PTW_8BIT 0x00000000 +#define PORTSCX_PTW_16BIT 0x10000000 + +/* bit 31-30 are port transceiver select */ +#define PORTSCX_PTS_UTMI 0x00000000 +#define PORTSCX_PTS_ULPI 0x80000000 +#define PORTSCX_PTS_FSLS 0xC0000000 +#define PORTSCX_PTS_BIT_POS 30 + +/* otgsc Register Bit Masks */ +#define OTGSC_CTRL_VUSB_DISCHARGE 0x00000001 +#define OTGSC_CTRL_VUSB_CHARGE 0x00000002 +#define OTGSC_CTRL_OTG_TERM 0x00000008 +#define OTGSC_CTRL_DATA_PULSING 0x00000010 +#define OTGSC_STS_USB_ID 0x00000100 +#define OTGSC_STS_A_VBUS_VALID 0x00000200 +#define OTGSC_STS_A_SESSION_VALID 0x00000400 +#define OTGSC_STS_B_SESSION_VALID 0x00000800 +#define OTGSC_STS_B_SESSION_END 0x00001000 +#define OTGSC_STS_1MS_TOGGLE 0x00002000 +#define OTGSC_STS_DATA_PULSING 0x00004000 +#define OTGSC_INTSTS_USB_ID 0x00010000 +#define OTGSC_INTSTS_A_VBUS_VALID 0x00020000 +#define OTGSC_INTSTS_A_SESSION_VALID 0x00040000 +#define OTGSC_INTSTS_B_SESSION_VALID 0x00080000 +#define OTGSC_INTSTS_B_SESSION_END 0x00100000 +#define OTGSC_INTSTS_1MS 0x00200000 +#define OTGSC_INTSTS_DATA_PULSING 0x00400000 +#define OTGSC_INTR_USB_ID 0x01000000 +#define OTGSC_INTR_A_VBUS_VALID 0x02000000 +#define OTGSC_INTR_A_SESSION_VALID 0x04000000 +#define OTGSC_INTR_B_SESSION_VALID 0x08000000 +#define OTGSC_INTR_B_SESSION_END 0x10000000 +#define OTGSC_INTR_1MS_TIMER 0x20000000 +#define OTGSC_INTR_DATA_PULSING 0x40000000 + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE 0x00000000 +#define USB_MODE_CTRL_MODE_DEVICE 0x00000002 +#define USB_MODE_CTRL_MODE_HOST 0x00000003 +#define USB_MODE_CTRL_MODE_MASK 0x00000003 +#define USB_MODE_CTRL_MODE_RSV 0x00000001 +#define USB_MODE_ES 0x00000004 /* Endian Select */ +#define USB_MODE_SETUP_LOCK_OFF 0x00000008 +#define USB_MODE_STREAM_DISABLE 0x00000010 +/* Endpoint Flush Register */ +#define EPFLUSH_TX_OFFSET 0x00010000 +#define EPFLUSH_RX_OFFSET 0x00000000 + +/* Endpoint Setup Status bit masks */ +#define EP_SETUP_STATUS_MASK 0x0000003F +#define EP_SETUP_STATUS_EP0 0x00000001 + +/* ENDPOINTCTRLx Register Bit Masks */ +#define EPCTRL_TX_ENABLE 0x00800000 +#define EPCTRL_TX_DATA_TOGGLE_RST 0x00400000 /* Not EP0 */ +#define EPCTRL_TX_DATA_TOGGLE_INH 0x00200000 /* Not EP0 */ +#define EPCTRL_TX_TYPE 0x000C0000 +#define EPCTRL_TX_DATA_SOURCE 0x00020000 /* Not EP0 */ +#define EPCTRL_TX_EP_STALL 0x00010000 +#define EPCTRL_RX_ENABLE 0x00000080 +#define EPCTRL_RX_DATA_TOGGLE_RST 0x00000040 /* Not EP0 */ +#define EPCTRL_RX_DATA_TOGGLE_INH 0x00000020 /* Not EP0 */ +#define EPCTRL_RX_TYPE 0x0000000C +#define EPCTRL_RX_DATA_SINK 0x00000002 /* Not EP0 */ +#define EPCTRL_RX_EP_STALL 0x00000001 + +/* bit 19-18 and 3-2 are endpoint type */ +#define EPCTRL_EP_TYPE_CONTROL 0 +#define EPCTRL_EP_TYPE_ISO 1 +#define EPCTRL_EP_TYPE_BULK 2 +#define EPCTRL_EP_TYPE_INTERRUPT 3 +#define EPCTRL_TX_EP_TYPE_SHIFT 18 +#define EPCTRL_RX_EP_TYPE_SHIFT 2 + +/* SNOOPn Register Bit Masks */ +#define SNOOP_ADDRESS_MASK 0xFFFFF000 +#define SNOOP_SIZE_ZERO 0x00 /* snooping disable */ +#define SNOOP_SIZE_4KB 0x0B /* 4KB snoop size */ +#define SNOOP_SIZE_8KB 0x0C +#define SNOOP_SIZE_16KB 0x0D +#define SNOOP_SIZE_32KB 0x0E +#define SNOOP_SIZE_64KB 0x0F +#define SNOOP_SIZE_128KB 0x10 +#define SNOOP_SIZE_256KB 0x11 +#define SNOOP_SIZE_512KB 0x12 +#define SNOOP_SIZE_1MB 0x13 +#define SNOOP_SIZE_2MB 0x14 +#define SNOOP_SIZE_4MB 0x15 +#define SNOOP_SIZE_8MB 0x16 +#define SNOOP_SIZE_16MB 0x17 +#define SNOOP_SIZE_32MB 0x18 +#define SNOOP_SIZE_64MB 0x19 +#define SNOOP_SIZE_128MB 0x1A +#define SNOOP_SIZE_256MB 0x1B +#define SNOOP_SIZE_512MB 0x1C +#define SNOOP_SIZE_1GB 0x1D +#define SNOOP_SIZE_2GB 0x1E /* 2GB snoop size */ + +/* pri_ctrl Register Bit Masks */ +#define PRI_CTRL_PRI_LVL1 0x0000000C +#define PRI_CTRL_PRI_LVL0 0x00000003 + +/* si_ctrl Register Bit Masks */ +#define SI_CTRL_ERR_DISABLE 0x00000010 +#define SI_CTRL_IDRC_DISABLE 0x00000008 +#define SI_CTRL_RD_SAFE_EN 0x00000004 +#define SI_CTRL_RD_PREFETCH_DISABLE 0x00000002 +#define SI_CTRL_RD_PREFEFETCH_VAL 0x00000001 + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB 0x00000004 +#define USB_CTRL_ULPI_INT0EN 0x00000001 +#define USB_CTRL_UTMI_PHY_EN 0x00000200 +#define USB_CTRL_USB_EN 0x00000004 +#define USB_CTRL_ULPI_PHY_CLK_SEL 0x00000400 + +/* Endpoint Queue Head data struct + * Rem: all the variables of qh are LittleEndian Mode + * and NEXT_POINTER_MASK should operate on a LittleEndian, Phy Addr + */ +struct ep_queue_head { + u32 max_pkt_length; /* Mult(31-30) , Zlt(29) , Max Pkt len + and IOS(15) */ + u32 curr_dtd_ptr; /* Current dTD Pointer(31-5) */ + u32 next_dtd_ptr; /* Next dTD Pointer(31-5), T(0) */ + u32 size_ioc_int_sts; /* Total bytes (30-16), IOC (15), + MultO(11-10), STS (7-0) */ + u32 buff_ptr0; /* Buffer pointer Page 0 (31-12) */ + u32 buff_ptr1; /* Buffer pointer Page 1 (31-12) */ + u32 buff_ptr2; /* Buffer pointer Page 2 (31-12) */ + u32 buff_ptr3; /* Buffer pointer Page 3 (31-12) */ + u32 buff_ptr4; /* Buffer pointer Page 4 (31-12) */ + u32 res1; + u8 setup_buffer[8]; /* Setup data 8 bytes */ + u32 res2[4]; +}; + +/* Endpoint Queue Head Bit Masks */ +#define EP_QUEUE_HEAD_MULT_POS 30 +#define EP_QUEUE_HEAD_ZLT_SEL 0x20000000 +#define EP_QUEUE_HEAD_MAX_PKT_LEN_POS 16 +#define EP_QUEUE_HEAD_MAX_PKT_LEN(ep_info) (((ep_info)>>16)&0x07ff) +#define EP_QUEUE_HEAD_IOS 0x00008000 +#define EP_QUEUE_HEAD_NEXT_TERMINATE 0x00000001 +#define EP_QUEUE_HEAD_IOC 0x00008000 +#define EP_QUEUE_HEAD_MULTO 0x00000C00 +#define EP_QUEUE_HEAD_STATUS_HALT 0x00000040 +#define EP_QUEUE_HEAD_STATUS_ACTIVE 0x00000080 +#define EP_QUEUE_CURRENT_OFFSET_MASK 0x00000FFF +#define EP_QUEUE_HEAD_NEXT_POINTER_MASK 0xFFFFFFE0 +#define EP_QUEUE_FRINDEX_MASK 0x000007FF +#define EP_MAX_LENGTH_TRANSFER 0x4000 + +/* Endpoint Transfer Descriptor data struct */ +/* Rem: all the variables of td are LittleEndian Mode */ +struct ep_td_struct { + u32 next_td_ptr; /* Next TD pointer(31-5), T(0) set + indicate invalid */ + u32 size_ioc_sts; /* Total bytes (30-16), IOC (15), + MultO(11-10), STS (7-0) */ + u32 buff_ptr0; /* Buffer pointer Page 0 */ + u32 buff_ptr1; /* Buffer pointer Page 1 */ + u32 buff_ptr2; /* Buffer pointer Page 2 */ + u32 buff_ptr3; /* Buffer pointer Page 3 */ + u32 buff_ptr4; /* Buffer pointer Page 4 */ + u32 res; + /* 32 bytes */ + dma_addr_t td_dma; /* dma address for this td */ + /* virtual address of next td specified in next_td_ptr */ + struct ep_td_struct *next_td_virt; +}; + +/* Endpoint Transfer Descriptor bit Masks */ +#define DTD_NEXT_TERMINATE 0x00000001 +#define DTD_IOC 0x00008000 +#define DTD_STATUS_ACTIVE 0x00000080 +#define DTD_STATUS_HALTED 0x00000040 +#define DTD_STATUS_DATA_BUFF_ERR 0x00000020 +#define DTD_STATUS_TRANSACTION_ERR 0x00000008 +#define DTD_RESERVED_FIELDS 0x80007300 +#define DTD_ADDR_MASK 0xFFFFFFE0 +#define DTD_PACKET_SIZE 0x7FFF0000 +#define DTD_LENGTH_BIT_POS 16 +#define DTD_ERROR_MASK (DTD_STATUS_HALTED | \ + DTD_STATUS_DATA_BUFF_ERR | \ + DTD_STATUS_TRANSACTION_ERR) +/* Alignment requirements; must be a power of two */ +#define DTD_ALIGNMENT 0x20 +#define QH_ALIGNMENT 2048 + +/* Controller dma boundary */ +#define UDC_DMA_BOUNDARY 0x1000 + +/*-------------------------------------------------------------------------*/ + +/* ### driver private data + */ +struct fsl_req { + struct usb_request req; + struct list_head queue; + /* ep_queue() func will add + a request->queue into a udc_ep->queue 'd tail */ + struct fsl_ep *ep; + unsigned mapped:1; + + struct ep_td_struct *head, *tail; /* For dTD List + cpu endian Virtual addr */ + unsigned int dtd_count; +}; + +#define REQ_UNCOMPLETE 1 + +struct fsl_ep { + struct usb_ep ep; + struct list_head queue; + struct fsl_udc *udc; + struct ep_queue_head *qh; + struct usb_gadget *gadget; + + char name[14]; + unsigned stopped:1; +}; + +#define EP_DIR_IN 1 +#define EP_DIR_OUT 0 + +struct fsl_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct fsl_usb2_platform_data *pdata; + struct completion *done; /* to make sure release() is done */ + struct fsl_ep *eps; + unsigned int max_ep; + unsigned int irq; + + struct usb_ctrlrequest local_setup_buff; + spinlock_t lock; + struct usb_phy *transceiver; + unsigned softconnect:1; + unsigned vbus_active:1; + unsigned stopped:1; + unsigned remote_wakeup:1; + unsigned already_stopped:1; + unsigned big_endian_desc:1; + + struct ep_queue_head *ep_qh; /* Endpoints Queue-Head */ + struct fsl_req *status_req; /* ep0 status request */ + struct dma_pool *td_pool; /* dma pool for DTD */ + enum fsl_usb2_phy_modes phy_mode; + + size_t ep_qh_size; /* size after alignment adjustment*/ + dma_addr_t ep_qh_dma; /* dma address of QH */ + + u32 max_pipes; /* Device max pipes */ + u32 bus_reset; /* Device is bus resetting */ + u32 resume_state; /* USB state to resume */ + u32 usb_state; /* USB current state */ + u32 ep0_state; /* Endpoint zero state */ + u32 ep0_dir; /* Endpoint zero direction: can be + USB_DIR_IN or USB_DIR_OUT */ + u8 device_address; /* Device USB address */ +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt "\n", \ + __func__, ## args) +#else +#define DBG(fmt, args...) do{}while(0) +#endif + +#if 0 +static void dump_msg(const char *label, const u8 * buf, unsigned int length) +{ + unsigned int start, num, i; + char line[52], *p; + + if (length >= 512) + return; + DBG("%s, length %u:\n", label, length); + start = 0; + while (length > 0) { + num = min(length, 16u); + p = line; + for (i = 0; i < num; ++i) { + if (i == 8) + *p++ = ' '; + sprintf(p, " %02x", buf[i]); + p += 3; + } + *p = 0; + printk(KERN_DEBUG "%6x: %s\n", start, line); + buf += num; + start += num; + length -= num; + } +} +#endif + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(stuff...) do{}while(0) +#endif + +#define ERR(stuff...) pr_err("udc: " stuff) +#define WARNING(stuff...) pr_warning("udc: " stuff) +#define INFO(stuff...) pr_info("udc: " stuff) + +/*-------------------------------------------------------------------------*/ + +/* ### Add board specific defines here + */ + +/* + * ### pipe direction macro from device view + */ +#define USB_RECV 0 /* OUT EP */ +#define USB_SEND 1 /* IN EP */ + +/* + * ### internal used help routines. + */ +#define ep_index(EP) ((EP)->ep.desc->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) +#define ep_is_in(EP) ( (ep_index(EP) == 0) ? (EP->udc->ep0_dir == \ + USB_DIR_IN) : ((EP)->ep.desc->bEndpointAddress \ + & USB_DIR_IN)==USB_DIR_IN) +#define get_ep_by_pipe(udc, pipe) ((pipe == 1)? &udc->eps[0]: \ + &udc->eps[pipe]) +#define get_pipe_by_windex(windex) ((windex & USB_ENDPOINT_NUMBER_MASK) \ + * 2 + ((windex & USB_DIR_IN) ? 1 : 0)) +#define get_pipe_by_ep(EP) (ep_index(EP) * 2 + ep_is_in(EP)) + +static inline struct ep_queue_head *get_qh_by_ep(struct fsl_ep *ep) +{ + /* we only have one ep0 structure but two queue heads */ + if (ep_index(ep) != 0) + return ep->qh; + else + return &ep->udc->ep_qh[(ep->udc->ep0_dir == + USB_DIR_IN) ? 1 : 0]; +} + +struct platform_device; +#ifdef CONFIG_ARCH_MXC +int fsl_udc_clk_init(struct platform_device *pdev); +int fsl_udc_clk_finalize(struct platform_device *pdev); +void fsl_udc_clk_release(void); +#else +static inline int fsl_udc_clk_init(struct platform_device *pdev) +{ + return 0; +} +static inline int fsl_udc_clk_finalize(struct platform_device *pdev) +{ + return 0; +} +static inline void fsl_udc_clk_release(void) +{ +} +#endif + +#endif diff --git a/drivers/usb/gadget/functions.c b/drivers/usb/gadget/functions.c new file mode 100644 index 00000000..b13f839e --- /dev/null +++ b/drivers/usb/gadget/functions.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include + +#include + +static LIST_HEAD(func_list); +static DEFINE_MUTEX(func_lock); + +static struct usb_function_instance *try_get_usb_function_instance(const char *name) +{ + struct usb_function_driver *fd; + struct usb_function_instance *fi; + + fi = ERR_PTR(-ENOENT); + mutex_lock(&func_lock); + list_for_each_entry(fd, &func_list, list) { + + if (strcmp(name, fd->name)) + continue; + + if (!try_module_get(fd->mod)) { + fi = ERR_PTR(-EBUSY); + break; + } + fi = fd->alloc_inst(); + if (IS_ERR(fi)) + module_put(fd->mod); + else + fi->fd = fd; + break; + } + mutex_unlock(&func_lock); + return fi; +} + +struct usb_function_instance *usb_get_function_instance(const char *name) +{ + struct usb_function_instance *fi; + int ret; + + fi = try_get_usb_function_instance(name); + if (!IS_ERR(fi)) + return fi; + ret = PTR_ERR(fi); + if (ret != -ENOENT) + return fi; + ret = request_module("usbfunc:%s", name); + if (ret < 0) + return ERR_PTR(ret); + return try_get_usb_function_instance(name); +} +EXPORT_SYMBOL_GPL(usb_get_function_instance); + +struct usb_function *usb_get_function(struct usb_function_instance *fi) +{ + struct usb_function *f; + + f = fi->fd->alloc_func(fi); + if (IS_ERR(f)) + return f; + f->fi = fi; + return f; +} +EXPORT_SYMBOL_GPL(usb_get_function); + +void usb_put_function_instance(struct usb_function_instance *fi) +{ + struct module *mod; + + if (!fi) + return; + + mod = fi->fd->mod; + fi->free_func_inst(fi); + module_put(mod); +} +EXPORT_SYMBOL_GPL(usb_put_function_instance); + +void usb_put_function(struct usb_function *f) +{ + if (!f) + return; + + f->free_func(f); +} +EXPORT_SYMBOL_GPL(usb_put_function); + +int usb_function_register(struct usb_function_driver *newf) +{ + struct usb_function_driver *fd; + int ret; + + ret = -EEXIST; + + mutex_lock(&func_lock); + list_for_each_entry(fd, &func_list, list) { + if (!strcmp(fd->name, newf->name)) + goto out; + } + ret = 0; + list_add_tail(&newf->list, &func_list); +out: + mutex_unlock(&func_lock); + return ret; +} +EXPORT_SYMBOL_GPL(usb_function_register); + +void usb_function_unregister(struct usb_function_driver *fd) +{ + mutex_lock(&func_lock); + list_del(&fd->list); + mutex_unlock(&func_lock); +} +EXPORT_SYMBOL_GPL(usb_function_unregister); diff --git a/drivers/usb/gadget/fusb300_udc.c b/drivers/usb/gadget/fusb300_udc.c new file mode 100644 index 00000000..b8632d40 --- /dev/null +++ b/drivers/usb/gadget/fusb300_udc.c @@ -0,0 +1,1505 @@ +/* + * Fusb300 UDC (USB gadget) + * + * Copyright (C) 2010 Faraday Technology Corp. + * + * Author : Yuan-hsin Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fusb300_udc.h" + +MODULE_DESCRIPTION("FUSB300 USB gadget driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yuan Hsin Chen "); +MODULE_ALIAS("platform:fusb300_udc"); + +#define DRIVER_VERSION "20 October 2010" + +static const char udc_name[] = "fusb300_udc"; +static const char * const fusb300_ep_name[] = { + "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7", "ep8", "ep9", + "ep10", "ep11", "ep12", "ep13", "ep14", "ep15" +}; + +static void done(struct fusb300_ep *ep, struct fusb300_request *req, + int status); + +static void fusb300_enable_bit(struct fusb300 *fusb300, u32 offset, + u32 value) +{ + u32 reg = ioread32(fusb300->reg + offset); + + reg |= value; + iowrite32(reg, fusb300->reg + offset); +} + +static void fusb300_disable_bit(struct fusb300 *fusb300, u32 offset, + u32 value) +{ + u32 reg = ioread32(fusb300->reg + offset); + + reg &= ~value; + iowrite32(reg, fusb300->reg + offset); +} + + +static void fusb300_ep_setting(struct fusb300_ep *ep, + struct fusb300_ep_info info) +{ + ep->epnum = info.epnum; + ep->type = info.type; +} + +static int fusb300_ep_release(struct fusb300_ep *ep) +{ + if (!ep->epnum) + return 0; + ep->epnum = 0; + ep->stall = 0; + ep->wedged = 0; + return 0; +} + +static void fusb300_set_fifo_entry(struct fusb300 *fusb300, + u32 ep) +{ + u32 val = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); + + val &= ~FUSB300_EPSET1_FIFOENTRY_MSK; + val |= FUSB300_EPSET1_FIFOENTRY(FUSB300_FIFO_ENTRY_NUM); + iowrite32(val, fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); +} + +static void fusb300_set_start_entry(struct fusb300 *fusb300, + u8 ep) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); + u32 start_entry = fusb300->fifo_entry_num * FUSB300_FIFO_ENTRY_NUM; + + reg &= ~FUSB300_EPSET1_START_ENTRY_MSK ; + reg |= FUSB300_EPSET1_START_ENTRY(start_entry); + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); + if (fusb300->fifo_entry_num == FUSB300_MAX_FIFO_ENTRY) { + fusb300->fifo_entry_num = 0; + fusb300->addrofs = 0; + pr_err("fifo entry is over the maximum number!\n"); + } else + fusb300->fifo_entry_num++; +} + +/* set fusb300_set_start_entry first before fusb300_set_epaddrofs */ +static void fusb300_set_epaddrofs(struct fusb300 *fusb300, + struct fusb300_ep_info info) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET2(info.epnum)); + + reg &= ~FUSB300_EPSET2_ADDROFS_MSK; + reg |= FUSB300_EPSET2_ADDROFS(fusb300->addrofs); + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET2(info.epnum)); + fusb300->addrofs += (info.maxpacket + 7) / 8 * FUSB300_FIFO_ENTRY_NUM; +} + +static void ep_fifo_setting(struct fusb300 *fusb300, + struct fusb300_ep_info info) +{ + fusb300_set_fifo_entry(fusb300, info.epnum); + fusb300_set_start_entry(fusb300, info.epnum); + fusb300_set_epaddrofs(fusb300, info); +} + +static void fusb300_set_eptype(struct fusb300 *fusb300, + struct fusb300_ep_info info) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); + + reg &= ~FUSB300_EPSET1_TYPE_MSK; + reg |= FUSB300_EPSET1_TYPE(info.type); + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); +} + +static void fusb300_set_epdir(struct fusb300 *fusb300, + struct fusb300_ep_info info) +{ + u32 reg; + + if (!info.dir_in) + return; + reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); + reg &= ~FUSB300_EPSET1_DIR_MSK; + reg |= FUSB300_EPSET1_DIRIN; + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); +} + +static void fusb300_set_ep_active(struct fusb300 *fusb300, + u8 ep) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); + + reg |= FUSB300_EPSET1_ACTEN; + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(ep)); +} + +static void fusb300_set_epmps(struct fusb300 *fusb300, + struct fusb300_ep_info info) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET2(info.epnum)); + + reg &= ~FUSB300_EPSET2_MPS_MSK; + reg |= FUSB300_EPSET2_MPS(info.maxpacket); + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET2(info.epnum)); +} + +static void fusb300_set_interval(struct fusb300 *fusb300, + struct fusb300_ep_info info) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); + + reg &= ~FUSB300_EPSET1_INTERVAL(0x7); + reg |= FUSB300_EPSET1_INTERVAL(info.interval); + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); +} + +static void fusb300_set_bwnum(struct fusb300 *fusb300, + struct fusb300_ep_info info) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); + + reg &= ~FUSB300_EPSET1_BWNUM(0x3); + reg |= FUSB300_EPSET1_BWNUM(info.bw_num); + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET1(info.epnum)); +} + +static void set_ep_reg(struct fusb300 *fusb300, + struct fusb300_ep_info info) +{ + fusb300_set_eptype(fusb300, info); + fusb300_set_epdir(fusb300, info); + fusb300_set_epmps(fusb300, info); + + if (info.interval) + fusb300_set_interval(fusb300, info); + + if (info.bw_num) + fusb300_set_bwnum(fusb300, info); + + fusb300_set_ep_active(fusb300, info.epnum); +} + +static int config_ep(struct fusb300_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fusb300 *fusb300 = ep->fusb300; + struct fusb300_ep_info info; + + ep->ep.desc = desc; + + info.interval = 0; + info.addrofs = 0; + info.bw_num = 0; + + info.type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + info.dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0; + info.maxpacket = usb_endpoint_maxp(desc); + info.epnum = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + + if ((info.type == USB_ENDPOINT_XFER_INT) || + (info.type == USB_ENDPOINT_XFER_ISOC)) { + info.interval = desc->bInterval; + if (info.type == USB_ENDPOINT_XFER_ISOC) + info.bw_num = ((desc->wMaxPacketSize & 0x1800) >> 11); + } + + ep_fifo_setting(fusb300, info); + + set_ep_reg(fusb300, info); + + fusb300_ep_setting(ep, info); + + fusb300->ep[info.epnum] = ep; + + return 0; +} + +static int fusb300_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fusb300_ep *ep; + + ep = container_of(_ep, struct fusb300_ep, ep); + + if (ep->fusb300->reenum) { + ep->fusb300->fifo_entry_num = 0; + ep->fusb300->addrofs = 0; + ep->fusb300->reenum = 0; + } + + return config_ep(ep, desc); +} + +static int fusb300_disable(struct usb_ep *_ep) +{ + struct fusb300_ep *ep; + struct fusb300_request *req; + unsigned long flags; + + ep = container_of(_ep, struct fusb300_ep, ep); + + BUG_ON(!ep); + + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct fusb300_request, queue); + spin_lock_irqsave(&ep->fusb300->lock, flags); + done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->fusb300->lock, flags); + } + + return fusb300_ep_release(ep); +} + +static struct usb_request *fusb300_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct fusb300_request *req; + + req = kzalloc(sizeof(struct fusb300_request), gfp_flags); + if (!req) + return NULL; + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void fusb300_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fusb300_request *req; + + req = container_of(_req, struct fusb300_request, req); + kfree(req); +} + +static int enable_fifo_int(struct fusb300_ep *ep) +{ + struct fusb300 *fusb300 = ep->fusb300; + + if (ep->epnum) { + fusb300_enable_bit(fusb300, FUSB300_OFFSET_IGER0, + FUSB300_IGER0_EEPn_FIFO_INT(ep->epnum)); + } else { + pr_err("can't enable_fifo_int ep0\n"); + return -EINVAL; + } + + return 0; +} + +static int disable_fifo_int(struct fusb300_ep *ep) +{ + struct fusb300 *fusb300 = ep->fusb300; + + if (ep->epnum) { + fusb300_disable_bit(fusb300, FUSB300_OFFSET_IGER0, + FUSB300_IGER0_EEPn_FIFO_INT(ep->epnum)); + } else { + pr_err("can't disable_fifo_int ep0\n"); + return -EINVAL; + } + + return 0; +} + +static void fusb300_set_cxlen(struct fusb300 *fusb300, u32 length) +{ + u32 reg; + + reg = ioread32(fusb300->reg + FUSB300_OFFSET_CSR); + reg &= ~FUSB300_CSR_LEN_MSK; + reg |= FUSB300_CSR_LEN(length); + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_CSR); +} + +/* write data to cx fifo */ +static void fusb300_wrcxf(struct fusb300_ep *ep, + struct fusb300_request *req) +{ + int i = 0; + u8 *tmp; + u32 data; + struct fusb300 *fusb300 = ep->fusb300; + u32 length = req->req.length - req->req.actual; + + tmp = req->req.buf + req->req.actual; + + if (length > SS_CTL_MAX_PACKET_SIZE) { + fusb300_set_cxlen(fusb300, SS_CTL_MAX_PACKET_SIZE); + for (i = (SS_CTL_MAX_PACKET_SIZE >> 2); i > 0; i--) { + data = *tmp | *(tmp + 1) << 8 | *(tmp + 2) << 16 | + *(tmp + 3) << 24; + iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); + tmp += 4; + } + req->req.actual += SS_CTL_MAX_PACKET_SIZE; + } else { /* length is less than max packet size */ + fusb300_set_cxlen(fusb300, length); + for (i = length >> 2; i > 0; i--) { + data = *tmp | *(tmp + 1) << 8 | *(tmp + 2) << 16 | + *(tmp + 3) << 24; + printk(KERN_DEBUG " 0x%x\n", data); + iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); + tmp = tmp + 4; + } + switch (length % 4) { + case 1: + data = *tmp; + printk(KERN_DEBUG " 0x%x\n", data); + iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); + break; + case 2: + data = *tmp | *(tmp + 1) << 8; + printk(KERN_DEBUG " 0x%x\n", data); + iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); + break; + case 3: + data = *tmp | *(tmp + 1) << 8 | *(tmp + 2) << 16; + printk(KERN_DEBUG " 0x%x\n", data); + iowrite32(data, fusb300->reg + FUSB300_OFFSET_CXPORT); + break; + default: + break; + } + req->req.actual += length; + } +} + +static void fusb300_set_epnstall(struct fusb300 *fusb300, u8 ep) +{ + fusb300_enable_bit(fusb300, FUSB300_OFFSET_EPSET0(ep), + FUSB300_EPSET0_STL); +} + +static void fusb300_clear_epnstall(struct fusb300 *fusb300, u8 ep) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET0(ep)); + + if (reg & FUSB300_EPSET0_STL) { + printk(KERN_DEBUG "EP%d stall... Clear!!\n", ep); + reg |= FUSB300_EPSET0_STL_CLR; + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_EPSET0(ep)); + } +} + +static void ep0_queue(struct fusb300_ep *ep, struct fusb300_request *req) +{ + if (ep->fusb300->ep0_dir) { /* if IN */ + if (req->req.length) { + fusb300_wrcxf(ep, req); + } else + printk(KERN_DEBUG "%s : req->req.length = 0x%x\n", + __func__, req->req.length); + if ((req->req.length == req->req.actual) || + (req->req.actual < ep->ep.maxpacket)) + done(ep, req, 0); + } else { /* OUT */ + if (!req->req.length) + done(ep, req, 0); + else + fusb300_enable_bit(ep->fusb300, FUSB300_OFFSET_IGER1, + FUSB300_IGER1_CX_OUT_INT); + } +} + +static int fusb300_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct fusb300_ep *ep; + struct fusb300_request *req; + unsigned long flags; + int request = 0; + + ep = container_of(_ep, struct fusb300_ep, ep); + req = container_of(_req, struct fusb300_request, req); + + if (ep->fusb300->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&ep->fusb300->lock, flags); + + if (list_empty(&ep->queue)) + request = 1; + + list_add_tail(&req->queue, &ep->queue); + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + + if (ep->ep.desc == NULL) /* ep0 */ + ep0_queue(ep, req); + else if (request && !ep->stall) + enable_fifo_int(ep); + + spin_unlock_irqrestore(&ep->fusb300->lock, flags); + + return 0; +} + +static int fusb300_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fusb300_ep *ep; + struct fusb300_request *req; + unsigned long flags; + + ep = container_of(_ep, struct fusb300_ep, ep); + req = container_of(_req, struct fusb300_request, req); + + spin_lock_irqsave(&ep->fusb300->lock, flags); + if (!list_empty(&ep->queue)) + done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->fusb300->lock, flags); + + return 0; +} + +static int fusb300_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedge) +{ + struct fusb300_ep *ep; + struct fusb300 *fusb300; + unsigned long flags; + int ret = 0; + + ep = container_of(_ep, struct fusb300_ep, ep); + + fusb300 = ep->fusb300; + + spin_lock_irqsave(&ep->fusb300->lock, flags); + + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + goto out; + } + + if (value) { + fusb300_set_epnstall(fusb300, ep->epnum); + ep->stall = 1; + if (wedge) + ep->wedged = 1; + } else { + fusb300_clear_epnstall(fusb300, ep->epnum); + ep->stall = 0; + ep->wedged = 0; + } + +out: + spin_unlock_irqrestore(&ep->fusb300->lock, flags); + return ret; +} + +static int fusb300_set_halt(struct usb_ep *_ep, int value) +{ + return fusb300_set_halt_and_wedge(_ep, value, 0); +} + +static int fusb300_set_wedge(struct usb_ep *_ep) +{ + return fusb300_set_halt_and_wedge(_ep, 1, 1); +} + +static void fusb300_fifo_flush(struct usb_ep *_ep) +{ +} + +static struct usb_ep_ops fusb300_ep_ops = { + .enable = fusb300_enable, + .disable = fusb300_disable, + + .alloc_request = fusb300_alloc_request, + .free_request = fusb300_free_request, + + .queue = fusb300_queue, + .dequeue = fusb300_dequeue, + + .set_halt = fusb300_set_halt, + .fifo_flush = fusb300_fifo_flush, + .set_wedge = fusb300_set_wedge, +}; + +/*****************************************************************************/ +static void fusb300_clear_int(struct fusb300 *fusb300, u32 offset, + u32 value) +{ + iowrite32(value, fusb300->reg + offset); +} + +static void fusb300_reset(void) +{ +} + +static void fusb300_set_cxstall(struct fusb300 *fusb300) +{ + fusb300_enable_bit(fusb300, FUSB300_OFFSET_CSR, + FUSB300_CSR_STL); +} + +static void fusb300_set_cxdone(struct fusb300 *fusb300) +{ + fusb300_enable_bit(fusb300, FUSB300_OFFSET_CSR, + FUSB300_CSR_DONE); +} + +/* read data from cx fifo */ +void fusb300_rdcxf(struct fusb300 *fusb300, + u8 *buffer, u32 length) +{ + int i = 0; + u8 *tmp; + u32 data; + + tmp = buffer; + + for (i = (length >> 2); i > 0; i--) { + data = ioread32(fusb300->reg + FUSB300_OFFSET_CXPORT); + printk(KERN_DEBUG " 0x%x\n", data); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + *(tmp + 2) = (data >> 16) & 0xFF; + *(tmp + 3) = (data >> 24) & 0xFF; + tmp = tmp + 4; + } + + switch (length % 4) { + case 1: + data = ioread32(fusb300->reg + FUSB300_OFFSET_CXPORT); + printk(KERN_DEBUG " 0x%x\n", data); + *tmp = data & 0xFF; + break; + case 2: + data = ioread32(fusb300->reg + FUSB300_OFFSET_CXPORT); + printk(KERN_DEBUG " 0x%x\n", data); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + break; + case 3: + data = ioread32(fusb300->reg + FUSB300_OFFSET_CXPORT); + printk(KERN_DEBUG " 0x%x\n", data); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + *(tmp + 2) = (data >> 16) & 0xFF; + break; + default: + break; + } +} + +static void fusb300_rdfifo(struct fusb300_ep *ep, + struct fusb300_request *req, + u32 length) +{ + int i = 0; + u8 *tmp; + u32 data, reg; + struct fusb300 *fusb300 = ep->fusb300; + + tmp = req->req.buf + req->req.actual; + req->req.actual += length; + + if (req->req.actual > req->req.length) + printk(KERN_DEBUG "req->req.actual > req->req.length\n"); + + for (i = (length >> 2); i > 0; i--) { + data = ioread32(fusb300->reg + + FUSB300_OFFSET_EPPORT(ep->epnum)); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + *(tmp + 2) = (data >> 16) & 0xFF; + *(tmp + 3) = (data >> 24) & 0xFF; + tmp = tmp + 4; + } + + switch (length % 4) { + case 1: + data = ioread32(fusb300->reg + + FUSB300_OFFSET_EPPORT(ep->epnum)); + *tmp = data & 0xFF; + break; + case 2: + data = ioread32(fusb300->reg + + FUSB300_OFFSET_EPPORT(ep->epnum)); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + break; + case 3: + data = ioread32(fusb300->reg + + FUSB300_OFFSET_EPPORT(ep->epnum)); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + *(tmp + 2) = (data >> 16) & 0xFF; + break; + default: + break; + } + + do { + reg = ioread32(fusb300->reg + FUSB300_OFFSET_IGR1); + reg &= FUSB300_IGR1_SYNF0_EMPTY_INT; + if (i) + printk(KERN_INFO "sync fifo is not empty!\n"); + i++; + } while (!reg); +} + +static u8 fusb300_get_epnstall(struct fusb300 *fusb300, u8 ep) +{ + u8 value; + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPSET0(ep)); + + value = reg & FUSB300_EPSET0_STL; + + return value; +} + +static u8 fusb300_get_cxstall(struct fusb300 *fusb300) +{ + u8 value; + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_CSR); + + value = (reg & FUSB300_CSR_STL) >> 1; + + return value; +} + +static void request_error(struct fusb300 *fusb300) +{ + fusb300_set_cxstall(fusb300); + printk(KERN_DEBUG "request error!!\n"); +} + +static void get_status(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) +__releases(fusb300->lock) +__acquires(fusb300->lock) +{ + u8 ep; + u16 status = 0; + u16 w_index = ctrl->wIndex; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + status = 1 << USB_DEVICE_SELF_POWERED; + break; + case USB_RECIP_INTERFACE: + status = 0; + break; + case USB_RECIP_ENDPOINT: + ep = w_index & USB_ENDPOINT_NUMBER_MASK; + if (ep) { + if (fusb300_get_epnstall(fusb300, ep)) + status = 1 << USB_ENDPOINT_HALT; + } else { + if (fusb300_get_cxstall(fusb300)) + status = 0; + } + break; + + default: + request_error(fusb300); + return; /* exit */ + } + + fusb300->ep0_data = cpu_to_le16(status); + fusb300->ep0_req->buf = &fusb300->ep0_data; + fusb300->ep0_req->length = 2; + + spin_unlock(&fusb300->lock); + fusb300_queue(fusb300->gadget.ep0, fusb300->ep0_req, GFP_KERNEL); + spin_lock(&fusb300->lock); +} + +static void set_feature(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) +{ + u8 ep; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + fusb300_set_cxdone(fusb300); + break; + case USB_RECIP_INTERFACE: + fusb300_set_cxdone(fusb300); + break; + case USB_RECIP_ENDPOINT: { + u16 w_index = le16_to_cpu(ctrl->wIndex); + + ep = w_index & USB_ENDPOINT_NUMBER_MASK; + if (ep) + fusb300_set_epnstall(fusb300, ep); + else + fusb300_set_cxstall(fusb300); + fusb300_set_cxdone(fusb300); + } + break; + default: + request_error(fusb300); + break; + } +} + +static void fusb300_clear_seqnum(struct fusb300 *fusb300, u8 ep) +{ + fusb300_enable_bit(fusb300, FUSB300_OFFSET_EPSET0(ep), + FUSB300_EPSET0_CLRSEQNUM); +} + +static void clear_feature(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) +{ + struct fusb300_ep *ep = + fusb300->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK]; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + fusb300_set_cxdone(fusb300); + break; + case USB_RECIP_INTERFACE: + fusb300_set_cxdone(fusb300); + break; + case USB_RECIP_ENDPOINT: + if (ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK) { + if (ep->wedged) { + fusb300_set_cxdone(fusb300); + break; + } + if (ep->stall) { + ep->stall = 0; + fusb300_clear_seqnum(fusb300, ep->epnum); + fusb300_clear_epnstall(fusb300, ep->epnum); + if (!list_empty(&ep->queue)) + enable_fifo_int(ep); + } + } + fusb300_set_cxdone(fusb300); + break; + default: + request_error(fusb300); + break; + } +} + +static void fusb300_set_dev_addr(struct fusb300 *fusb300, u16 addr) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_DAR); + + reg &= ~FUSB300_DAR_DRVADDR_MSK; + reg |= FUSB300_DAR_DRVADDR(addr); + + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_DAR); +} + +static void set_address(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) +{ + if (ctrl->wValue >= 0x0100) + request_error(fusb300); + else { + fusb300_set_dev_addr(fusb300, ctrl->wValue); + fusb300_set_cxdone(fusb300); + } +} + +#define UVC_COPY_DESCRIPTORS(mem, src) \ + do { \ + const struct usb_descriptor_header * const *__src; \ + for (__src = src; *__src; ++__src) { \ + memcpy(mem, *__src, (*__src)->bLength); \ + mem += (*__src)->bLength; \ + } \ + } while (0) + +static int setup_packet(struct fusb300 *fusb300, struct usb_ctrlrequest *ctrl) +{ + u8 *p = (u8 *)ctrl; + u8 ret = 0; + u8 i = 0; + + fusb300_rdcxf(fusb300, p, 8); + fusb300->ep0_dir = ctrl->bRequestType & USB_DIR_IN; + fusb300->ep0_length = ctrl->wLength; + + /* check request */ + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + get_status(fusb300, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + clear_feature(fusb300, ctrl); + break; + case USB_REQ_SET_FEATURE: + set_feature(fusb300, ctrl); + break; + case USB_REQ_SET_ADDRESS: + set_address(fusb300, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + fusb300_enable_bit(fusb300, FUSB300_OFFSET_DAR, + FUSB300_DAR_SETCONFG); + /* clear sequence number */ + for (i = 1; i <= FUSB300_MAX_NUM_EP; i++) + fusb300_clear_seqnum(fusb300, i); + fusb300->reenum = 1; + ret = 1; + break; + default: + ret = 1; + break; + } + } else + ret = 1; + + return ret; +} + +static void done(struct fusb300_ep *ep, struct fusb300_request *req, + int status) +{ + list_del_init(&req->queue); + + /* don't modify queue heads during completion callback */ + if (ep->fusb300->gadget.speed == USB_SPEED_UNKNOWN) + req->req.status = -ESHUTDOWN; + else + req->req.status = status; + + spin_unlock(&ep->fusb300->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->fusb300->lock); + + if (ep->epnum) { + disable_fifo_int(ep); + if (!list_empty(&ep->queue)) + enable_fifo_int(ep); + } else + fusb300_set_cxdone(ep->fusb300); +} + +static void fusb300_fill_idma_prdtbl(struct fusb300_ep *ep, dma_addr_t d, + u32 len) +{ + u32 value; + u32 reg; + + /* wait SW owner */ + do { + reg = ioread32(ep->fusb300->reg + + FUSB300_OFFSET_EPPRD_W0(ep->epnum)); + reg &= FUSB300_EPPRD0_H; + } while (reg); + + iowrite32(d, ep->fusb300->reg + FUSB300_OFFSET_EPPRD_W1(ep->epnum)); + + value = FUSB300_EPPRD0_BTC(len) | FUSB300_EPPRD0_H | + FUSB300_EPPRD0_F | FUSB300_EPPRD0_L | FUSB300_EPPRD0_I; + iowrite32(value, ep->fusb300->reg + FUSB300_OFFSET_EPPRD_W0(ep->epnum)); + + iowrite32(0x0, ep->fusb300->reg + FUSB300_OFFSET_EPPRD_W2(ep->epnum)); + + fusb300_enable_bit(ep->fusb300, FUSB300_OFFSET_EPPRDRDY, + FUSB300_EPPRDR_EP_PRD_RDY(ep->epnum)); +} + +static void fusb300_wait_idma_finished(struct fusb300_ep *ep) +{ + u32 reg; + + do { + reg = ioread32(ep->fusb300->reg + FUSB300_OFFSET_IGR1); + if ((reg & FUSB300_IGR1_VBUS_CHG_INT) || + (reg & FUSB300_IGR1_WARM_RST_INT) || + (reg & FUSB300_IGR1_HOT_RST_INT) || + (reg & FUSB300_IGR1_USBRST_INT) + ) + goto IDMA_RESET; + reg = ioread32(ep->fusb300->reg + FUSB300_OFFSET_IGR0); + reg &= FUSB300_IGR0_EPn_PRD_INT(ep->epnum); + } while (!reg); + + fusb300_clear_int(ep->fusb300, FUSB300_OFFSET_IGR0, + FUSB300_IGR0_EPn_PRD_INT(ep->epnum)); + return; + +IDMA_RESET: + reg = ioread32(ep->fusb300->reg + FUSB300_OFFSET_IGER0); + reg &= ~FUSB300_IGER0_EEPn_PRD_INT(ep->epnum); + iowrite32(reg, ep->fusb300->reg + FUSB300_OFFSET_IGER0); +} + +static void fusb300_set_idma(struct fusb300_ep *ep, + struct fusb300_request *req) +{ + int ret; + + ret = usb_gadget_map_request(&ep->fusb300->gadget, + &req->req, DMA_TO_DEVICE); + if (ret) + return; + + fusb300_enable_bit(ep->fusb300, FUSB300_OFFSET_IGER0, + FUSB300_IGER0_EEPn_PRD_INT(ep->epnum)); + + fusb300_fill_idma_prdtbl(ep, req->req.dma, req->req.length); + /* check idma is done */ + fusb300_wait_idma_finished(ep); + + usb_gadget_unmap_request(&ep->fusb300->gadget, + &req->req, DMA_TO_DEVICE); +} + +static void in_ep_fifo_handler(struct fusb300_ep *ep) +{ + struct fusb300_request *req = list_entry(ep->queue.next, + struct fusb300_request, queue); + + if (req->req.length) + fusb300_set_idma(ep, req); + done(ep, req, 0); +} + +static void out_ep_fifo_handler(struct fusb300_ep *ep) +{ + struct fusb300 *fusb300 = ep->fusb300; + struct fusb300_request *req = list_entry(ep->queue.next, + struct fusb300_request, queue); + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_EPFFR(ep->epnum)); + u32 length = reg & FUSB300_FFR_BYCNT; + + fusb300_rdfifo(ep, req, length); + + /* finish out transfer */ + if ((req->req.length == req->req.actual) || (length < ep->ep.maxpacket)) + done(ep, req, 0); +} + +static void check_device_mode(struct fusb300 *fusb300) +{ + u32 reg = ioread32(fusb300->reg + FUSB300_OFFSET_GCR); + + switch (reg & FUSB300_GCR_DEVEN_MSK) { + case FUSB300_GCR_DEVEN_SS: + fusb300->gadget.speed = USB_SPEED_SUPER; + break; + case FUSB300_GCR_DEVEN_HS: + fusb300->gadget.speed = USB_SPEED_HIGH; + break; + case FUSB300_GCR_DEVEN_FS: + fusb300->gadget.speed = USB_SPEED_FULL; + break; + default: + fusb300->gadget.speed = USB_SPEED_UNKNOWN; + break; + } + printk(KERN_INFO "dev_mode = %d\n", (reg & FUSB300_GCR_DEVEN_MSK)); +} + + +static void fusb300_ep0out(struct fusb300 *fusb300) +{ + struct fusb300_ep *ep = fusb300->ep[0]; + u32 reg; + + if (!list_empty(&ep->queue)) { + struct fusb300_request *req; + + req = list_first_entry(&ep->queue, + struct fusb300_request, queue); + if (req->req.length) + fusb300_rdcxf(ep->fusb300, req->req.buf, + req->req.length); + done(ep, req, 0); + reg = ioread32(fusb300->reg + FUSB300_OFFSET_IGER1); + reg &= ~FUSB300_IGER1_CX_OUT_INT; + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_IGER1); + } else + pr_err("%s : empty queue\n", __func__); +} + +static void fusb300_ep0in(struct fusb300 *fusb300) +{ + struct fusb300_request *req; + struct fusb300_ep *ep = fusb300->ep[0]; + + if ((!list_empty(&ep->queue)) && (fusb300->ep0_dir)) { + req = list_entry(ep->queue.next, + struct fusb300_request, queue); + if (req->req.length) + fusb300_wrcxf(ep, req); + if ((req->req.length - req->req.actual) < ep->ep.maxpacket) + done(ep, req, 0); + } else + fusb300_set_cxdone(fusb300); +} + +static void fusb300_grp2_handler(void) +{ +} + +static void fusb300_grp3_handler(void) +{ +} + +static void fusb300_grp4_handler(void) +{ +} + +static void fusb300_grp5_handler(void) +{ +} + +static irqreturn_t fusb300_irq(int irq, void *_fusb300) +{ + struct fusb300 *fusb300 = _fusb300; + u32 int_grp1 = ioread32(fusb300->reg + FUSB300_OFFSET_IGR1); + u32 int_grp1_en = ioread32(fusb300->reg + FUSB300_OFFSET_IGER1); + u32 int_grp0 = ioread32(fusb300->reg + FUSB300_OFFSET_IGR0); + u32 int_grp0_en = ioread32(fusb300->reg + FUSB300_OFFSET_IGER0); + struct usb_ctrlrequest ctrl; + u8 in; + u32 reg; + int i; + + spin_lock(&fusb300->lock); + + int_grp1 &= int_grp1_en; + int_grp0 &= int_grp0_en; + + if (int_grp1 & FUSB300_IGR1_WARM_RST_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_WARM_RST_INT); + printk(KERN_INFO"fusb300_warmreset\n"); + fusb300_reset(); + } + + if (int_grp1 & FUSB300_IGR1_HOT_RST_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_HOT_RST_INT); + printk(KERN_INFO"fusb300_hotreset\n"); + fusb300_reset(); + } + + if (int_grp1 & FUSB300_IGR1_USBRST_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_USBRST_INT); + fusb300_reset(); + } + /* COMABT_INT has a highest priority */ + + if (int_grp1 & FUSB300_IGR1_CX_COMABT_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_CX_COMABT_INT); + printk(KERN_INFO"fusb300_ep0abt\n"); + } + + if (int_grp1 & FUSB300_IGR1_VBUS_CHG_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_VBUS_CHG_INT); + printk(KERN_INFO"fusb300_vbus_change\n"); + } + + if (int_grp1 & FUSB300_IGR1_U3_EXIT_FAIL_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U3_EXIT_FAIL_INT); + } + + if (int_grp1 & FUSB300_IGR1_U2_EXIT_FAIL_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U2_EXIT_FAIL_INT); + } + + if (int_grp1 & FUSB300_IGR1_U1_EXIT_FAIL_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U1_EXIT_FAIL_INT); + } + + if (int_grp1 & FUSB300_IGR1_U2_ENTRY_FAIL_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U2_ENTRY_FAIL_INT); + } + + if (int_grp1 & FUSB300_IGR1_U1_ENTRY_FAIL_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U1_ENTRY_FAIL_INT); + } + + if (int_grp1 & FUSB300_IGR1_U3_EXIT_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U3_EXIT_INT); + printk(KERN_INFO "FUSB300_IGR1_U3_EXIT_INT\n"); + } + + if (int_grp1 & FUSB300_IGR1_U2_EXIT_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U2_EXIT_INT); + printk(KERN_INFO "FUSB300_IGR1_U2_EXIT_INT\n"); + } + + if (int_grp1 & FUSB300_IGR1_U1_EXIT_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U1_EXIT_INT); + printk(KERN_INFO "FUSB300_IGR1_U1_EXIT_INT\n"); + } + + if (int_grp1 & FUSB300_IGR1_U3_ENTRY_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U3_ENTRY_INT); + printk(KERN_INFO "FUSB300_IGR1_U3_ENTRY_INT\n"); + fusb300_enable_bit(fusb300, FUSB300_OFFSET_SSCR1, + FUSB300_SSCR1_GO_U3_DONE); + } + + if (int_grp1 & FUSB300_IGR1_U2_ENTRY_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U2_ENTRY_INT); + printk(KERN_INFO "FUSB300_IGR1_U2_ENTRY_INT\n"); + } + + if (int_grp1 & FUSB300_IGR1_U1_ENTRY_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_U1_ENTRY_INT); + printk(KERN_INFO "FUSB300_IGR1_U1_ENTRY_INT\n"); + } + + if (int_grp1 & FUSB300_IGR1_RESM_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_RESM_INT); + printk(KERN_INFO "fusb300_resume\n"); + } + + if (int_grp1 & FUSB300_IGR1_SUSP_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_SUSP_INT); + printk(KERN_INFO "fusb300_suspend\n"); + } + + if (int_grp1 & FUSB300_IGR1_HS_LPM_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_HS_LPM_INT); + printk(KERN_INFO "fusb300_HS_LPM_INT\n"); + } + + if (int_grp1 & FUSB300_IGR1_DEV_MODE_CHG_INT) { + fusb300_clear_int(fusb300, FUSB300_OFFSET_IGR1, + FUSB300_IGR1_DEV_MODE_CHG_INT); + check_device_mode(fusb300); + } + + if (int_grp1 & FUSB300_IGR1_CX_COMFAIL_INT) { + fusb300_set_cxstall(fusb300); + printk(KERN_INFO "fusb300_ep0fail\n"); + } + + if (int_grp1 & FUSB300_IGR1_CX_SETUP_INT) { + printk(KERN_INFO "fusb300_ep0setup\n"); + if (setup_packet(fusb300, &ctrl)) { + spin_unlock(&fusb300->lock); + if (fusb300->driver->setup(&fusb300->gadget, &ctrl) < 0) + fusb300_set_cxstall(fusb300); + spin_lock(&fusb300->lock); + } + } + + if (int_grp1 & FUSB300_IGR1_CX_CMDEND_INT) + printk(KERN_INFO "fusb300_cmdend\n"); + + + if (int_grp1 & FUSB300_IGR1_CX_OUT_INT) { + printk(KERN_INFO "fusb300_cxout\n"); + fusb300_ep0out(fusb300); + } + + if (int_grp1 & FUSB300_IGR1_CX_IN_INT) { + printk(KERN_INFO "fusb300_cxin\n"); + fusb300_ep0in(fusb300); + } + + if (int_grp1 & FUSB300_IGR1_INTGRP5) + fusb300_grp5_handler(); + + if (int_grp1 & FUSB300_IGR1_INTGRP4) + fusb300_grp4_handler(); + + if (int_grp1 & FUSB300_IGR1_INTGRP3) + fusb300_grp3_handler(); + + if (int_grp1 & FUSB300_IGR1_INTGRP2) + fusb300_grp2_handler(); + + if (int_grp0) { + for (i = 1; i < FUSB300_MAX_NUM_EP; i++) { + if (int_grp0 & FUSB300_IGR0_EPn_FIFO_INT(i)) { + reg = ioread32(fusb300->reg + + FUSB300_OFFSET_EPSET1(i)); + in = (reg & FUSB300_EPSET1_DIRIN) ? 1 : 0; + if (in) + in_ep_fifo_handler(fusb300->ep[i]); + else + out_ep_fifo_handler(fusb300->ep[i]); + } + } + } + + spin_unlock(&fusb300->lock); + + return IRQ_HANDLED; +} + +static void fusb300_set_u2_timeout(struct fusb300 *fusb300, + u32 time) +{ + u32 reg; + + reg = ioread32(fusb300->reg + FUSB300_OFFSET_TT); + reg &= ~0xff; + reg |= FUSB300_SSCR2_U2TIMEOUT(time); + + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_TT); +} + +static void fusb300_set_u1_timeout(struct fusb300 *fusb300, + u32 time) +{ + u32 reg; + + reg = ioread32(fusb300->reg + FUSB300_OFFSET_TT); + reg &= ~(0xff << 8); + reg |= FUSB300_SSCR2_U1TIMEOUT(time); + + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_TT); +} + +static void init_controller(struct fusb300 *fusb300) +{ + u32 reg; + u32 mask = 0; + u32 val = 0; + + /* split on */ + mask = val = FUSB300_AHBBCR_S0_SPLIT_ON | FUSB300_AHBBCR_S1_SPLIT_ON; + reg = ioread32(fusb300->reg + FUSB300_OFFSET_AHBCR); + reg &= ~mask; + reg |= val; + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_AHBCR); + + /* enable high-speed LPM */ + mask = val = FUSB300_HSCR_HS_LPM_PERMIT; + reg = ioread32(fusb300->reg + FUSB300_OFFSET_HSCR); + reg &= ~mask; + reg |= val; + iowrite32(reg, fusb300->reg + FUSB300_OFFSET_HSCR); + + /*set u1 u2 timmer*/ + fusb300_set_u2_timeout(fusb300, 0xff); + fusb300_set_u1_timeout(fusb300, 0xff); + + /* enable all grp1 interrupt */ + iowrite32(0xcfffff9f, fusb300->reg + FUSB300_OFFSET_IGER1); +} +/*------------------------------------------------------------------------*/ +static int fusb300_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct fusb300 *fusb300 = to_fusb300(g); + + /* hook up the driver */ + driver->driver.bus = NULL; + fusb300->driver = driver; + + return 0; +} + +static int fusb300_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct fusb300 *fusb300 = to_fusb300(g); + + driver->unbind(&fusb300->gadget); + + init_controller(fusb300); + fusb300->driver = NULL; + + return 0; +} +/*--------------------------------------------------------------------------*/ + +static int fusb300_udc_pullup(struct usb_gadget *_gadget, int is_active) +{ + return 0; +} + +static const struct usb_gadget_ops fusb300_gadget_ops = { + .pullup = fusb300_udc_pullup, + .udc_start = fusb300_udc_start, + .udc_stop = fusb300_udc_stop, +}; + +static int __exit fusb300_remove(struct platform_device *pdev) +{ + struct fusb300 *fusb300 = dev_get_drvdata(&pdev->dev); + + usb_del_gadget_udc(&fusb300->gadget); + iounmap(fusb300->reg); + free_irq(platform_get_irq(pdev, 0), fusb300); + + fusb300_free_request(&fusb300->ep[0]->ep, fusb300->ep0_req); + kfree(fusb300); + + return 0; +} + +static int __init fusb300_probe(struct platform_device *pdev) +{ + struct resource *res, *ires, *ires1; + void __iomem *reg = NULL; + struct fusb300 *fusb300 = NULL; + struct fusb300_ep *_ep[FUSB300_MAX_NUM_EP]; + int ret = 0; + int i; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + pr_err("platform_get_resource error.\n"); + goto clean_up; + } + + ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!ires) { + ret = -ENODEV; + dev_err(&pdev->dev, + "platform_get_resource IORESOURCE_IRQ error.\n"); + goto clean_up; + } + + ires1 = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (!ires1) { + ret = -ENODEV; + dev_err(&pdev->dev, + "platform_get_resource IORESOURCE_IRQ 1 error.\n"); + goto clean_up; + } + + reg = ioremap(res->start, resource_size(res)); + if (reg == NULL) { + ret = -ENOMEM; + pr_err("ioremap error.\n"); + goto clean_up; + } + + /* initialize udc */ + fusb300 = kzalloc(sizeof(struct fusb300), GFP_KERNEL); + if (fusb300 == NULL) { + pr_err("kzalloc error\n"); + goto clean_up; + } + + for (i = 0; i < FUSB300_MAX_NUM_EP; i++) { + _ep[i] = kzalloc(sizeof(struct fusb300_ep), GFP_KERNEL); + if (_ep[i] == NULL) { + pr_err("_ep kzalloc error\n"); + goto clean_up; + } + fusb300->ep[i] = _ep[i]; + } + + spin_lock_init(&fusb300->lock); + + dev_set_drvdata(&pdev->dev, fusb300); + + fusb300->gadget.ops = &fusb300_gadget_ops; + + fusb300->gadget.max_speed = USB_SPEED_HIGH; + fusb300->gadget.name = udc_name; + fusb300->reg = reg; + + ret = request_irq(ires->start, fusb300_irq, IRQF_SHARED, + udc_name, fusb300); + if (ret < 0) { + pr_err("request_irq error (%d)\n", ret); + goto clean_up; + } + + ret = request_irq(ires1->start, fusb300_irq, + IRQF_SHARED, udc_name, fusb300); + if (ret < 0) { + pr_err("request_irq1 error (%d)\n", ret); + goto clean_up; + } + + INIT_LIST_HEAD(&fusb300->gadget.ep_list); + + for (i = 0; i < FUSB300_MAX_NUM_EP ; i++) { + struct fusb300_ep *ep = fusb300->ep[i]; + + if (i != 0) { + INIT_LIST_HEAD(&fusb300->ep[i]->ep.ep_list); + list_add_tail(&fusb300->ep[i]->ep.ep_list, + &fusb300->gadget.ep_list); + } + ep->fusb300 = fusb300; + INIT_LIST_HEAD(&ep->queue); + ep->ep.name = fusb300_ep_name[i]; + ep->ep.ops = &fusb300_ep_ops; + ep->ep.maxpacket = HS_BULK_MAX_PACKET_SIZE; + } + fusb300->ep[0]->ep.maxpacket = HS_CTL_MAX_PACKET_SIZE; + fusb300->ep[0]->epnum = 0; + fusb300->gadget.ep0 = &fusb300->ep[0]->ep; + INIT_LIST_HEAD(&fusb300->gadget.ep0->ep_list); + + fusb300->ep0_req = fusb300_alloc_request(&fusb300->ep[0]->ep, + GFP_KERNEL); + if (fusb300->ep0_req == NULL) { + ret = -ENOMEM; + goto clean_up3; + } + + init_controller(fusb300); + ret = usb_add_gadget_udc(&pdev->dev, &fusb300->gadget); + if (ret) + goto err_add_udc; + + dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION); + + return 0; + +err_add_udc: + fusb300_free_request(&fusb300->ep[0]->ep, fusb300->ep0_req); + +clean_up3: + free_irq(ires->start, fusb300); + +clean_up: + if (fusb300) { + if (fusb300->ep0_req) + fusb300_free_request(&fusb300->ep[0]->ep, + fusb300->ep0_req); + kfree(fusb300); + } + if (reg) + iounmap(reg); + + return ret; +} + +static struct platform_driver fusb300_driver = { + .remove = __exit_p(fusb300_remove), + .driver = { + .name = (char *) udc_name, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver_probe(fusb300_driver, fusb300_probe); diff --git a/drivers/usb/gadget/fusb300_udc.h b/drivers/usb/gadget/fusb300_udc.h new file mode 100644 index 00000000..ae811d8d --- /dev/null +++ b/drivers/usb/gadget/fusb300_udc.h @@ -0,0 +1,678 @@ +/* + * Fusb300 UDC (USB gadget) + * + * Copyright (C) 2010 Faraday Technology Corp. + * + * Author : Yuan-hsin Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + + +#ifndef __FUSB300_UDC_H__ +#define __FUSB300_UDC_H_ + +#include + +#define FUSB300_OFFSET_GCR 0x00 +#define FUSB300_OFFSET_GTM 0x04 +#define FUSB300_OFFSET_DAR 0x08 +#define FUSB300_OFFSET_CSR 0x0C +#define FUSB300_OFFSET_CXPORT 0x10 +#define FUSB300_OFFSET_EPSET0(n) (0x20 + (n - 1) * 0x30) +#define FUSB300_OFFSET_EPSET1(n) (0x24 + (n - 1) * 0x30) +#define FUSB300_OFFSET_EPSET2(n) (0x28 + (n - 1) * 0x30) +#define FUSB300_OFFSET_EPFFR(n) (0x2c + (n - 1) * 0x30) +#define FUSB300_OFFSET_EPSTRID(n) (0x40 + (n - 1) * 0x30) +#define FUSB300_OFFSET_HSPTM 0x300 +#define FUSB300_OFFSET_HSCR 0x304 +#define FUSB300_OFFSET_SSCR0 0x308 +#define FUSB300_OFFSET_SSCR1 0x30C +#define FUSB300_OFFSET_TT 0x310 +#define FUSB300_OFFSET_DEVNOTF 0x314 +#define FUSB300_OFFSET_DNC1 0x318 +#define FUSB300_OFFSET_CS 0x31C +#define FUSB300_OFFSET_SOF 0x324 +#define FUSB300_OFFSET_EFCS 0x328 +#define FUSB300_OFFSET_IGR0 0x400 +#define FUSB300_OFFSET_IGR1 0x404 +#define FUSB300_OFFSET_IGR2 0x408 +#define FUSB300_OFFSET_IGR3 0x40C +#define FUSB300_OFFSET_IGR4 0x410 +#define FUSB300_OFFSET_IGR5 0x414 +#define FUSB300_OFFSET_IGER0 0x420 +#define FUSB300_OFFSET_IGER1 0x424 +#define FUSB300_OFFSET_IGER2 0x428 +#define FUSB300_OFFSET_IGER3 0x42C +#define FUSB300_OFFSET_IGER4 0x430 +#define FUSB300_OFFSET_IGER5 0x434 +#define FUSB300_OFFSET_DMAHMER 0x500 +#define FUSB300_OFFSET_EPPRDRDY 0x504 +#define FUSB300_OFFSET_DMAEPMR 0x508 +#define FUSB300_OFFSET_DMAENR 0x50C +#define FUSB300_OFFSET_DMAAPR 0x510 +#define FUSB300_OFFSET_AHBCR 0x514 +#define FUSB300_OFFSET_EPPRD_W0(n) (0x520 + (n - 1) * 0x10) +#define FUSB300_OFFSET_EPPRD_W1(n) (0x524 + (n - 1) * 0x10) +#define FUSB300_OFFSET_EPPRD_W2(n) (0x528 + (n - 1) * 0x10) +#define FUSB300_OFFSET_EPRD_PTR(n) (0x52C + (n - 1) * 0x10) +#define FUSB300_OFFSET_BUFDBG_START 0x800 +#define FUSB300_OFFSET_BUFDBG_END 0xBFC +#define FUSB300_OFFSET_EPPORT(n) (0x1010 + (n - 1) * 0x10) + +/* + * * Global Control Register (offset = 000H) + * */ +#define FUSB300_GCR_SF_RST (1 << 8) +#define FUSB300_GCR_VBUS_STATUS (1 << 7) +#define FUSB300_GCR_FORCE_HS_SUSP (1 << 6) +#define FUSB300_GCR_SYNC_FIFO1_CLR (1 << 5) +#define FUSB300_GCR_SYNC_FIFO0_CLR (1 << 4) +#define FUSB300_GCR_FIFOCLR (1 << 3) +#define FUSB300_GCR_GLINTEN (1 << 2) +#define FUSB300_GCR_DEVEN_FS 0x3 +#define FUSB300_GCR_DEVEN_HS 0x2 +#define FUSB300_GCR_DEVEN_SS 0x1 +#define FUSB300_GCR_DEVDIS 0x0 +#define FUSB300_GCR_DEVEN_MSK 0x3 + + +/* + * *Global Test Mode (offset = 004H) + * */ +#define FUSB300_GTM_TST_DIS_SOFGEN (1 << 16) +#define FUSB300_GTM_TST_CUR_EP_ENTRY(n) ((n & 0xF) << 12) +#define FUSB300_GTM_TST_EP_ENTRY(n) ((n & 0xF) << 8) +#define FUSB300_GTM_TST_EP_NUM(n) ((n & 0xF) << 4) +#define FUSB300_GTM_TST_FIFO_DEG (1 << 1) +#define FUSB300_GTM_TSTMODE (1 << 0) + +/* + * * Device Address Register (offset = 008H) + * */ +#define FUSB300_DAR_SETCONFG (1 << 7) +#define FUSB300_DAR_DRVADDR(x) (x & 0x7F) +#define FUSB300_DAR_DRVADDR_MSK 0x7F + +/* + * *Control Transfer Configuration and Status Register + * (CX_Config_Status, offset = 00CH) + * */ +#define FUSB300_CSR_LEN(x) ((x & 0xFFFF) << 8) +#define FUSB300_CSR_LEN_MSK (0xFFFF << 8) +#define FUSB300_CSR_EMP (1 << 4) +#define FUSB300_CSR_FUL (1 << 3) +#define FUSB300_CSR_CLR (1 << 2) +#define FUSB300_CSR_STL (1 << 1) +#define FUSB300_CSR_DONE (1 << 0) + +/* + * * EPn Setting 0 (EPn_SET0, offset = 020H+(n-1)*30H, n=1~15 ) + * */ +#define FUSB300_EPSET0_STL_CLR (1 << 3) +#define FUSB300_EPSET0_CLRSEQNUM (1 << 2) +#define FUSB300_EPSET0_STL (1 << 0) + +/* + * * EPn Setting 1 (EPn_SET1, offset = 024H+(n-1)*30H, n=1~15) + * */ +#define FUSB300_EPSET1_START_ENTRY(x) ((x & 0xFF) << 24) +#define FUSB300_EPSET1_START_ENTRY_MSK (0xFF << 24) +#define FUSB300_EPSET1_FIFOENTRY(x) ((x & 0x1F) << 12) +#define FUSB300_EPSET1_FIFOENTRY_MSK (0x1f << 12) +#define FUSB300_EPSET1_INTERVAL(x) ((x & 0x7) << 6) +#define FUSB300_EPSET1_BWNUM(x) ((x & 0x3) << 4) +#define FUSB300_EPSET1_TYPEISO (1 << 2) +#define FUSB300_EPSET1_TYPEBLK (2 << 2) +#define FUSB300_EPSET1_TYPEINT (3 << 2) +#define FUSB300_EPSET1_TYPE(x) ((x & 0x3) << 2) +#define FUSB300_EPSET1_TYPE_MSK (0x3 << 2) +#define FUSB300_EPSET1_DIROUT (0 << 1) +#define FUSB300_EPSET1_DIRIN (1 << 1) +#define FUSB300_EPSET1_DIR(x) ((x & 0x1) << 1) +#define FUSB300_EPSET1_DIRIN (1 << 1) +#define FUSB300_EPSET1_DIR_MSK ((0x1) << 1) +#define FUSB300_EPSET1_ACTDIS 0 +#define FUSB300_EPSET1_ACTEN 1 + +/* + * *EPn Setting 2 (EPn_SET2, offset = 028H+(n-1)*30H, n=1~15) + * */ +#define FUSB300_EPSET2_ADDROFS(x) ((x & 0x7FFF) << 16) +#define FUSB300_EPSET2_ADDROFS_MSK (0x7fff << 16) +#define FUSB300_EPSET2_MPS(x) (x & 0x7FF) +#define FUSB300_EPSET2_MPS_MSK 0x7FF + +/* + * * EPn FIFO Register (offset = 2cH+(n-1)*30H) + * */ +#define FUSB300_FFR_RST (1 << 31) +#define FUSB300_FF_FUL (1 << 30) +#define FUSB300_FF_EMPTY (1 << 29) +#define FUSB300_FFR_BYCNT 0x1FFFF + +/* + * *EPn Stream ID (EPn_STR_ID, offset = 040H+(n-1)*30H, n=1~15) + * */ +#define FUSB300_STRID_STREN (1 << 16) +#define FUSB300_STRID_STRID(x) (x & 0xFFFF) + +/* + * *HS PHY Test Mode (offset = 300H) + * */ +#define FUSB300_HSPTM_TSTPKDONE (1 << 4) +#define FUSB300_HSPTM_TSTPKT (1 << 3) +#define FUSB300_HSPTM_TSTSET0NAK (1 << 2) +#define FUSB300_HSPTM_TSTKSTA (1 << 1) +#define FUSB300_HSPTM_TSTJSTA (1 << 0) + +/* + * *HS Control Register (offset = 304H) + * */ +#define FUSB300_HSCR_HS_LPM_PERMIT (1 << 8) +#define FUSB300_HSCR_HS_LPM_RMWKUP (1 << 7) +#define FUSB300_HSCR_CAP_LPM_RMWKUP (1 << 6) +#define FUSB300_HSCR_HS_GOSUSP (1 << 5) +#define FUSB300_HSCR_HS_GORMWKU (1 << 4) +#define FUSB300_HSCR_CAP_RMWKUP (1 << 3) +#define FUSB300_HSCR_IDLECNT_0MS 0 +#define FUSB300_HSCR_IDLECNT_1MS 1 +#define FUSB300_HSCR_IDLECNT_2MS 2 +#define FUSB300_HSCR_IDLECNT_3MS 3 +#define FUSB300_HSCR_IDLECNT_4MS 4 +#define FUSB300_HSCR_IDLECNT_5MS 5 +#define FUSB300_HSCR_IDLECNT_6MS 6 +#define FUSB300_HSCR_IDLECNT_7MS 7 + +/* + * * SS Controller Register 0 (offset = 308H) + * */ +#define FUSB300_SSCR0_MAX_INTERVAL(x) ((x & 0x7) << 4) +#define FUSB300_SSCR0_U2_FUN_EN (1 << 1) +#define FUSB300_SSCR0_U1_FUN_EN (1 << 0) + +/* + * * SS Controller Register 1 (offset = 30CH) + * */ +#define FUSB300_SSCR1_GO_U3_DONE (1 << 8) +#define FUSB300_SSCR1_TXDEEMPH_LEVEL (1 << 7) +#define FUSB300_SSCR1_DIS_SCRMB (1 << 6) +#define FUSB300_SSCR1_FORCE_RECOVERY (1 << 5) +#define FUSB300_SSCR1_U3_WAKEUP_EN (1 << 4) +#define FUSB300_SSCR1_U2_EXIT_EN (1 << 3) +#define FUSB300_SSCR1_U1_EXIT_EN (1 << 2) +#define FUSB300_SSCR1_U2_ENTRY_EN (1 << 1) +#define FUSB300_SSCR1_U1_ENTRY_EN (1 << 0) + +/* + * *SS Controller Register 2 (offset = 310H) + * */ +#define FUSB300_SSCR2_SS_TX_SWING (1 << 25) +#define FUSB300_SSCR2_FORCE_LINKPM_ACCEPT (1 << 24) +#define FUSB300_SSCR2_U2_INACT_TIMEOUT(x) ((x & 0xFF) << 16) +#define FUSB300_SSCR2_U1TIMEOUT(x) ((x & 0xFF) << 8) +#define FUSB300_SSCR2_U2TIMEOUT(x) (x & 0xFF) + +/* + * *SS Device Notification Control (DEV_NOTF, offset = 314H) + * */ +#define FUSB300_DEVNOTF_CONTEXT0(x) ((x & 0xFFFFFF) << 8) +#define FUSB300_DEVNOTF_TYPE_DIS 0 +#define FUSB300_DEVNOTF_TYPE_FUNCWAKE 1 +#define FUSB300_DEVNOTF_TYPE_LTM 2 +#define FUSB300_DEVNOTF_TYPE_BUSINT_ADJMSG 3 + +/* + * *BFM Arbiter Priority Register (BFM_ARB offset = 31CH) + * */ +#define FUSB300_BFMARB_ARB_M1 (1 << 3) +#define FUSB300_BFMARB_ARB_M0 (1 << 2) +#define FUSB300_BFMARB_ARB_S1 (1 << 1) +#define FUSB300_BFMARB_ARB_S0 1 + +/* + * *Vendor Specific IO Control Register (offset = 320H) + * */ +#define FUSB300_VSIC_VCTLOAD_N (1 << 8) +#define FUSB300_VSIC_VCTL(x) (x & 0x3F) + +/* + * *SOF Mask Timer (offset = 324H) + * */ +#define FUSB300_SOF_MASK_TIMER_HS 0x044c +#define FUSB300_SOF_MASK_TIMER_FS 0x2710 + +/* + * *Error Flag and Control Status (offset = 328H) + * */ +#define FUSB300_EFCS_PM_STATE_U3 3 +#define FUSB300_EFCS_PM_STATE_U2 2 +#define FUSB300_EFCS_PM_STATE_U1 1 +#define FUSB300_EFCS_PM_STATE_U0 0 + +/* + * *Interrupt Group 0 Register (offset = 400H) + * */ +#define FUSB300_IGR0_EP15_PRD_INT (1 << 31) +#define FUSB300_IGR0_EP14_PRD_INT (1 << 30) +#define FUSB300_IGR0_EP13_PRD_INT (1 << 29) +#define FUSB300_IGR0_EP12_PRD_INT (1 << 28) +#define FUSB300_IGR0_EP11_PRD_INT (1 << 27) +#define FUSB300_IGR0_EP10_PRD_INT (1 << 26) +#define FUSB300_IGR0_EP9_PRD_INT (1 << 25) +#define FUSB300_IGR0_EP8_PRD_INT (1 << 24) +#define FUSB300_IGR0_EP7_PRD_INT (1 << 23) +#define FUSB300_IGR0_EP6_PRD_INT (1 << 22) +#define FUSB300_IGR0_EP5_PRD_INT (1 << 21) +#define FUSB300_IGR0_EP4_PRD_INT (1 << 20) +#define FUSB300_IGR0_EP3_PRD_INT (1 << 19) +#define FUSB300_IGR0_EP2_PRD_INT (1 << 18) +#define FUSB300_IGR0_EP1_PRD_INT (1 << 17) +#define FUSB300_IGR0_EPn_PRD_INT(n) (1 << (n + 16)) + +#define FUSB300_IGR0_EP15_FIFO_INT (1 << 15) +#define FUSB300_IGR0_EP14_FIFO_INT (1 << 14) +#define FUSB300_IGR0_EP13_FIFO_INT (1 << 13) +#define FUSB300_IGR0_EP12_FIFO_INT (1 << 12) +#define FUSB300_IGR0_EP11_FIFO_INT (1 << 11) +#define FUSB300_IGR0_EP10_FIFO_INT (1 << 10) +#define FUSB300_IGR0_EP9_FIFO_INT (1 << 9) +#define FUSB300_IGR0_EP8_FIFO_INT (1 << 8) +#define FUSB300_IGR0_EP7_FIFO_INT (1 << 7) +#define FUSB300_IGR0_EP6_FIFO_INT (1 << 6) +#define FUSB300_IGR0_EP5_FIFO_INT (1 << 5) +#define FUSB300_IGR0_EP4_FIFO_INT (1 << 4) +#define FUSB300_IGR0_EP3_FIFO_INT (1 << 3) +#define FUSB300_IGR0_EP2_FIFO_INT (1 << 2) +#define FUSB300_IGR0_EP1_FIFO_INT (1 << 1) +#define FUSB300_IGR0_EPn_FIFO_INT(n) (1 << n) + +/* + * *Interrupt Group 1 Register (offset = 404H) + * */ +#define FUSB300_IGR1_INTGRP5 (1 << 31) +#define FUSB300_IGR1_VBUS_CHG_INT (1 << 30) +#define FUSB300_IGR1_SYNF1_EMPTY_INT (1 << 29) +#define FUSB300_IGR1_SYNF0_EMPTY_INT (1 << 28) +#define FUSB300_IGR1_U3_EXIT_FAIL_INT (1 << 27) +#define FUSB300_IGR1_U2_EXIT_FAIL_INT (1 << 26) +#define FUSB300_IGR1_U1_EXIT_FAIL_INT (1 << 25) +#define FUSB300_IGR1_U2_ENTRY_FAIL_INT (1 << 24) +#define FUSB300_IGR1_U1_ENTRY_FAIL_INT (1 << 23) +#define FUSB300_IGR1_U3_EXIT_INT (1 << 22) +#define FUSB300_IGR1_U2_EXIT_INT (1 << 21) +#define FUSB300_IGR1_U1_EXIT_INT (1 << 20) +#define FUSB300_IGR1_U3_ENTRY_INT (1 << 19) +#define FUSB300_IGR1_U2_ENTRY_INT (1 << 18) +#define FUSB300_IGR1_U1_ENTRY_INT (1 << 17) +#define FUSB300_IGR1_HOT_RST_INT (1 << 16) +#define FUSB300_IGR1_WARM_RST_INT (1 << 15) +#define FUSB300_IGR1_RESM_INT (1 << 14) +#define FUSB300_IGR1_SUSP_INT (1 << 13) +#define FUSB300_IGR1_HS_LPM_INT (1 << 12) +#define FUSB300_IGR1_USBRST_INT (1 << 11) +#define FUSB300_IGR1_DEV_MODE_CHG_INT (1 << 9) +#define FUSB300_IGR1_CX_COMABT_INT (1 << 8) +#define FUSB300_IGR1_CX_COMFAIL_INT (1 << 7) +#define FUSB300_IGR1_CX_CMDEND_INT (1 << 6) +#define FUSB300_IGR1_CX_OUT_INT (1 << 5) +#define FUSB300_IGR1_CX_IN_INT (1 << 4) +#define FUSB300_IGR1_CX_SETUP_INT (1 << 3) +#define FUSB300_IGR1_INTGRP4 (1 << 2) +#define FUSB300_IGR1_INTGRP3 (1 << 1) +#define FUSB300_IGR1_INTGRP2 (1 << 0) + +/* + * *Interrupt Group 2 Register (offset = 408H) + * */ +#define FUSB300_IGR2_EP6_STR_ACCEPT_INT (1 << 29) +#define FUSB300_IGR2_EP6_STR_RESUME_INT (1 << 28) +#define FUSB300_IGR2_EP6_STR_REQ_INT (1 << 27) +#define FUSB300_IGR2_EP6_STR_NOTRDY_INT (1 << 26) +#define FUSB300_IGR2_EP6_STR_PRIME_INT (1 << 25) +#define FUSB300_IGR2_EP5_STR_ACCEPT_INT (1 << 24) +#define FUSB300_IGR2_EP5_STR_RESUME_INT (1 << 23) +#define FUSB300_IGR2_EP5_STR_REQ_INT (1 << 22) +#define FUSB300_IGR2_EP5_STR_NOTRDY_INT (1 << 21) +#define FUSB300_IGR2_EP5_STR_PRIME_INT (1 << 20) +#define FUSB300_IGR2_EP4_STR_ACCEPT_INT (1 << 19) +#define FUSB300_IGR2_EP4_STR_RESUME_INT (1 << 18) +#define FUSB300_IGR2_EP4_STR_REQ_INT (1 << 17) +#define FUSB300_IGR2_EP4_STR_NOTRDY_INT (1 << 16) +#define FUSB300_IGR2_EP4_STR_PRIME_INT (1 << 15) +#define FUSB300_IGR2_EP3_STR_ACCEPT_INT (1 << 14) +#define FUSB300_IGR2_EP3_STR_RESUME_INT (1 << 13) +#define FUSB300_IGR2_EP3_STR_REQ_INT (1 << 12) +#define FUSB300_IGR2_EP3_STR_NOTRDY_INT (1 << 11) +#define FUSB300_IGR2_EP3_STR_PRIME_INT (1 << 10) +#define FUSB300_IGR2_EP2_STR_ACCEPT_INT (1 << 9) +#define FUSB300_IGR2_EP2_STR_RESUME_INT (1 << 8) +#define FUSB300_IGR2_EP2_STR_REQ_INT (1 << 7) +#define FUSB300_IGR2_EP2_STR_NOTRDY_INT (1 << 6) +#define FUSB300_IGR2_EP2_STR_PRIME_INT (1 << 5) +#define FUSB300_IGR2_EP1_STR_ACCEPT_INT (1 << 4) +#define FUSB300_IGR2_EP1_STR_RESUME_INT (1 << 3) +#define FUSB300_IGR2_EP1_STR_REQ_INT (1 << 2) +#define FUSB300_IGR2_EP1_STR_NOTRDY_INT (1 << 1) +#define FUSB300_IGR2_EP1_STR_PRIME_INT (1 << 0) + +#define FUSB300_IGR2_EP_STR_ACCEPT_INT(n) (1 << (5 * n - 1)) +#define FUSB300_IGR2_EP_STR_RESUME_INT(n) (1 << (5 * n - 2)) +#define FUSB300_IGR2_EP_STR_REQ_INT(n) (1 << (5 * n - 3)) +#define FUSB300_IGR2_EP_STR_NOTRDY_INT(n) (1 << (5 * n - 4)) +#define FUSB300_IGR2_EP_STR_PRIME_INT(n) (1 << (5 * n - 5)) + +/* + * *Interrupt Group 3 Register (offset = 40CH) + * */ +#define FUSB300_IGR3_EP12_STR_ACCEPT_INT (1 << 29) +#define FUSB300_IGR3_EP12_STR_RESUME_INT (1 << 28) +#define FUSB300_IGR3_EP12_STR_REQ_INT (1 << 27) +#define FUSB300_IGR3_EP12_STR_NOTRDY_INT (1 << 26) +#define FUSB300_IGR3_EP12_STR_PRIME_INT (1 << 25) +#define FUSB300_IGR3_EP11_STR_ACCEPT_INT (1 << 24) +#define FUSB300_IGR3_EP11_STR_RESUME_INT (1 << 23) +#define FUSB300_IGR3_EP11_STR_REQ_INT (1 << 22) +#define FUSB300_IGR3_EP11_STR_NOTRDY_INT (1 << 21) +#define FUSB300_IGR3_EP11_STR_PRIME_INT (1 << 20) +#define FUSB300_IGR3_EP10_STR_ACCEPT_INT (1 << 19) +#define FUSB300_IGR3_EP10_STR_RESUME_INT (1 << 18) +#define FUSB300_IGR3_EP10_STR_REQ_INT (1 << 17) +#define FUSB300_IGR3_EP10_STR_NOTRDY_INT (1 << 16) +#define FUSB300_IGR3_EP10_STR_PRIME_INT (1 << 15) +#define FUSB300_IGR3_EP9_STR_ACCEPT_INT (1 << 14) +#define FUSB300_IGR3_EP9_STR_RESUME_INT (1 << 13) +#define FUSB300_IGR3_EP9_STR_REQ_INT (1 << 12) +#define FUSB300_IGR3_EP9_STR_NOTRDY_INT (1 << 11) +#define FUSB300_IGR3_EP9_STR_PRIME_INT (1 << 10) +#define FUSB300_IGR3_EP8_STR_ACCEPT_INT (1 << 9) +#define FUSB300_IGR3_EP8_STR_RESUME_INT (1 << 8) +#define FUSB300_IGR3_EP8_STR_REQ_INT (1 << 7) +#define FUSB300_IGR3_EP8_STR_NOTRDY_INT (1 << 6) +#define FUSB300_IGR3_EP8_STR_PRIME_INT (1 << 5) +#define FUSB300_IGR3_EP7_STR_ACCEPT_INT (1 << 4) +#define FUSB300_IGR3_EP7_STR_RESUME_INT (1 << 3) +#define FUSB300_IGR3_EP7_STR_REQ_INT (1 << 2) +#define FUSB300_IGR3_EP7_STR_NOTRDY_INT (1 << 1) +#define FUSB300_IGR3_EP7_STR_PRIME_INT (1 << 0) + +#define FUSB300_IGR3_EP_STR_ACCEPT_INT(n) (1 << (5 * (n - 6) - 1)) +#define FUSB300_IGR3_EP_STR_RESUME_INT(n) (1 << (5 * (n - 6) - 2)) +#define FUSB300_IGR3_EP_STR_REQ_INT(n) (1 << (5 * (n - 6) - 3)) +#define FUSB300_IGR3_EP_STR_NOTRDY_INT(n) (1 << (5 * (n - 6) - 4)) +#define FUSB300_IGR3_EP_STR_PRIME_INT(n) (1 << (5 * (n - 6) - 5)) + +/* + * *Interrupt Group 4 Register (offset = 410H) + * */ +#define FUSB300_IGR4_EP15_RX0_INT (1 << 31) +#define FUSB300_IGR4_EP14_RX0_INT (1 << 30) +#define FUSB300_IGR4_EP13_RX0_INT (1 << 29) +#define FUSB300_IGR4_EP12_RX0_INT (1 << 28) +#define FUSB300_IGR4_EP11_RX0_INT (1 << 27) +#define FUSB300_IGR4_EP10_RX0_INT (1 << 26) +#define FUSB300_IGR4_EP9_RX0_INT (1 << 25) +#define FUSB300_IGR4_EP8_RX0_INT (1 << 24) +#define FUSB300_IGR4_EP7_RX0_INT (1 << 23) +#define FUSB300_IGR4_EP6_RX0_INT (1 << 22) +#define FUSB300_IGR4_EP5_RX0_INT (1 << 21) +#define FUSB300_IGR4_EP4_RX0_INT (1 << 20) +#define FUSB300_IGR4_EP3_RX0_INT (1 << 19) +#define FUSB300_IGR4_EP2_RX0_INT (1 << 18) +#define FUSB300_IGR4_EP1_RX0_INT (1 << 17) +#define FUSB300_IGR4_EP_RX0_INT(x) (1 << (x + 16)) +#define FUSB300_IGR4_EP15_STR_ACCEPT_INT (1 << 14) +#define FUSB300_IGR4_EP15_STR_RESUME_INT (1 << 13) +#define FUSB300_IGR4_EP15_STR_REQ_INT (1 << 12) +#define FUSB300_IGR4_EP15_STR_NOTRDY_INT (1 << 11) +#define FUSB300_IGR4_EP15_STR_PRIME_INT (1 << 10) +#define FUSB300_IGR4_EP14_STR_ACCEPT_INT (1 << 9) +#define FUSB300_IGR4_EP14_STR_RESUME_INT (1 << 8) +#define FUSB300_IGR4_EP14_STR_REQ_INT (1 << 7) +#define FUSB300_IGR4_EP14_STR_NOTRDY_INT (1 << 6) +#define FUSB300_IGR4_EP14_STR_PRIME_INT (1 << 5) +#define FUSB300_IGR4_EP13_STR_ACCEPT_INT (1 << 4) +#define FUSB300_IGR4_EP13_STR_RESUME_INT (1 << 3) +#define FUSB300_IGR4_EP13_STR_REQ_INT (1 << 2) +#define FUSB300_IGR4_EP13_STR_NOTRDY_INT (1 << 1) +#define FUSB300_IGR4_EP13_STR_PRIME_INT (1 << 0) + +#define FUSB300_IGR4_EP_STR_ACCEPT_INT(n) (1 << (5 * (n - 12) - 1)) +#define FUSB300_IGR4_EP_STR_RESUME_INT(n) (1 << (5 * (n - 12) - 2)) +#define FUSB300_IGR4_EP_STR_REQ_INT(n) (1 << (5 * (n - 12) - 3)) +#define FUSB300_IGR4_EP_STR_NOTRDY_INT(n) (1 << (5 * (n - 12) - 4)) +#define FUSB300_IGR4_EP_STR_PRIME_INT(n) (1 << (5 * (n - 12) - 5)) + +/* + * *Interrupt Group 5 Register (offset = 414H) + * */ +#define FUSB300_IGR5_EP_STL_INT(n) (1 << n) + +/* + * *Interrupt Enable Group 0 Register (offset = 420H) + * */ +#define FUSB300_IGER0_EEP15_PRD_INT (1 << 31) +#define FUSB300_IGER0_EEP14_PRD_INT (1 << 30) +#define FUSB300_IGER0_EEP13_PRD_INT (1 << 29) +#define FUSB300_IGER0_EEP12_PRD_INT (1 << 28) +#define FUSB300_IGER0_EEP11_PRD_INT (1 << 27) +#define FUSB300_IGER0_EEP10_PRD_INT (1 << 26) +#define FUSB300_IGER0_EEP9_PRD_INT (1 << 25) +#define FUSB300_IGER0_EP8_PRD_INT (1 << 24) +#define FUSB300_IGER0_EEP7_PRD_INT (1 << 23) +#define FUSB300_IGER0_EEP6_PRD_INT (1 << 22) +#define FUSB300_IGER0_EEP5_PRD_INT (1 << 21) +#define FUSB300_IGER0_EEP4_PRD_INT (1 << 20) +#define FUSB300_IGER0_EEP3_PRD_INT (1 << 19) +#define FUSB300_IGER0_EEP2_PRD_INT (1 << 18) +#define FUSB300_IGER0_EEP1_PRD_INT (1 << 17) +#define FUSB300_IGER0_EEPn_PRD_INT(n) (1 << (n + 16)) + +#define FUSB300_IGER0_EEP15_FIFO_INT (1 << 15) +#define FUSB300_IGER0_EEP14_FIFO_INT (1 << 14) +#define FUSB300_IGER0_EEP13_FIFO_INT (1 << 13) +#define FUSB300_IGER0_EEP12_FIFO_INT (1 << 12) +#define FUSB300_IGER0_EEP11_FIFO_INT (1 << 11) +#define FUSB300_IGER0_EEP10_FIFO_INT (1 << 10) +#define FUSB300_IGER0_EEP9_FIFO_INT (1 << 9) +#define FUSB300_IGER0_EEP8_FIFO_INT (1 << 8) +#define FUSB300_IGER0_EEP7_FIFO_INT (1 << 7) +#define FUSB300_IGER0_EEP6_FIFO_INT (1 << 6) +#define FUSB300_IGER0_EEP5_FIFO_INT (1 << 5) +#define FUSB300_IGER0_EEP4_FIFO_INT (1 << 4) +#define FUSB300_IGER0_EEP3_FIFO_INT (1 << 3) +#define FUSB300_IGER0_EEP2_FIFO_INT (1 << 2) +#define FUSB300_IGER0_EEP1_FIFO_INT (1 << 1) +#define FUSB300_IGER0_EEPn_FIFO_INT(n) (1 << n) + +/* + * *Interrupt Enable Group 1 Register (offset = 424H) + * */ +#define FUSB300_IGER1_EINT_GRP5 (1 << 31) +#define FUSB300_IGER1_VBUS_CHG_INT (1 << 30) +#define FUSB300_IGER1_SYNF1_EMPTY_INT (1 << 29) +#define FUSB300_IGER1_SYNF0_EMPTY_INT (1 << 28) +#define FUSB300_IGER1_U3_EXIT_FAIL_INT (1 << 27) +#define FUSB300_IGER1_U2_EXIT_FAIL_INT (1 << 26) +#define FUSB300_IGER1_U1_EXIT_FAIL_INT (1 << 25) +#define FUSB300_IGER1_U2_ENTRY_FAIL_INT (1 << 24) +#define FUSB300_IGER1_U1_ENTRY_FAIL_INT (1 << 23) +#define FUSB300_IGER1_U3_EXIT_INT (1 << 22) +#define FUSB300_IGER1_U2_EXIT_INT (1 << 21) +#define FUSB300_IGER1_U1_EXIT_INT (1 << 20) +#define FUSB300_IGER1_U3_ENTRY_INT (1 << 19) +#define FUSB300_IGER1_U2_ENTRY_INT (1 << 18) +#define FUSB300_IGER1_U1_ENTRY_INT (1 << 17) +#define FUSB300_IGER1_HOT_RST_INT (1 << 16) +#define FUSB300_IGER1_WARM_RST_INT (1 << 15) +#define FUSB300_IGER1_RESM_INT (1 << 14) +#define FUSB300_IGER1_SUSP_INT (1 << 13) +#define FUSB300_IGER1_LPM_INT (1 << 12) +#define FUSB300_IGER1_HS_RST_INT (1 << 11) +#define FUSB300_IGER1_EDEV_MODE_CHG_INT (1 << 9) +#define FUSB300_IGER1_CX_COMABT_INT (1 << 8) +#define FUSB300_IGER1_CX_COMFAIL_INT (1 << 7) +#define FUSB300_IGER1_CX_CMDEND_INT (1 << 6) +#define FUSB300_IGER1_CX_OUT_INT (1 << 5) +#define FUSB300_IGER1_CX_IN_INT (1 << 4) +#define FUSB300_IGER1_CX_SETUP_INT (1 << 3) +#define FUSB300_IGER1_INTGRP4 (1 << 2) +#define FUSB300_IGER1_INTGRP3 (1 << 1) +#define FUSB300_IGER1_INTGRP2 (1 << 0) + +/* + * *Interrupt Enable Group 2 Register (offset = 428H) + * */ +#define FUSB300_IGER2_EEP_STR_ACCEPT_INT(n) (1 << (5 * n - 1)) +#define FUSB300_IGER2_EEP_STR_RESUME_INT(n) (1 << (5 * n - 2)) +#define FUSB300_IGER2_EEP_STR_REQ_INT(n) (1 << (5 * n - 3)) +#define FUSB300_IGER2_EEP_STR_NOTRDY_INT(n) (1 << (5 * n - 4)) +#define FUSB300_IGER2_EEP_STR_PRIME_INT(n) (1 << (5 * n - 5)) + +/* + * *Interrupt Enable Group 3 Register (offset = 42CH) + * */ + +#define FUSB300_IGER3_EEP_STR_ACCEPT_INT(n) (1 << (5 * (n - 6) - 1)) +#define FUSB300_IGER3_EEP_STR_RESUME_INT(n) (1 << (5 * (n - 6) - 2)) +#define FUSB300_IGER3_EEP_STR_REQ_INT(n) (1 << (5 * (n - 6) - 3)) +#define FUSB300_IGER3_EEP_STR_NOTRDY_INT(n) (1 << (5 * (n - 6) - 4)) +#define FUSB300_IGER3_EEP_STR_PRIME_INT(n) (1 << (5 * (n - 6) - 5)) + +/* + * *Interrupt Enable Group 4 Register (offset = 430H) + * */ + +#define FUSB300_IGER4_EEP_RX0_INT(n) (1 << (n + 16)) +#define FUSB300_IGER4_EEP_STR_ACCEPT_INT(n) (1 << (5 * (n - 6) - 1)) +#define FUSB300_IGER4_EEP_STR_RESUME_INT(n) (1 << (5 * (n - 6) - 2)) +#define FUSB300_IGER4_EEP_STR_REQ_INT(n) (1 << (5 * (n - 6) - 3)) +#define FUSB300_IGER4_EEP_STR_NOTRDY_INT(n) (1 << (5 * (n - 6) - 4)) +#define FUSB300_IGER4_EEP_STR_PRIME_INT(n) (1 << (5 * (n - 6) - 5)) + +/* EP PRD Ready (EP_PRD_RDY, offset = 504H) */ + +#define FUSB300_EPPRDR_EP15_PRD_RDY (1 << 15) +#define FUSB300_EPPRDR_EP14_PRD_RDY (1 << 14) +#define FUSB300_EPPRDR_EP13_PRD_RDY (1 << 13) +#define FUSB300_EPPRDR_EP12_PRD_RDY (1 << 12) +#define FUSB300_EPPRDR_EP11_PRD_RDY (1 << 11) +#define FUSB300_EPPRDR_EP10_PRD_RDY (1 << 10) +#define FUSB300_EPPRDR_EP9_PRD_RDY (1 << 9) +#define FUSB300_EPPRDR_EP8_PRD_RDY (1 << 8) +#define FUSB300_EPPRDR_EP7_PRD_RDY (1 << 7) +#define FUSB300_EPPRDR_EP6_PRD_RDY (1 << 6) +#define FUSB300_EPPRDR_EP5_PRD_RDY (1 << 5) +#define FUSB300_EPPRDR_EP4_PRD_RDY (1 << 4) +#define FUSB300_EPPRDR_EP3_PRD_RDY (1 << 3) +#define FUSB300_EPPRDR_EP2_PRD_RDY (1 << 2) +#define FUSB300_EPPRDR_EP1_PRD_RDY (1 << 1) +#define FUSB300_EPPRDR_EP_PRD_RDY(n) (1 << n) + +/* AHB Bus Control Register (offset = 514H) */ +#define FUSB300_AHBBCR_S1_SPLIT_ON (1 << 17) +#define FUSB300_AHBBCR_S0_SPLIT_ON (1 << 16) +#define FUSB300_AHBBCR_S1_1entry (0 << 12) +#define FUSB300_AHBBCR_S1_4entry (3 << 12) +#define FUSB300_AHBBCR_S1_8entry (5 << 12) +#define FUSB300_AHBBCR_S1_16entry (7 << 12) +#define FUSB300_AHBBCR_S0_1entry (0 << 8) +#define FUSB300_AHBBCR_S0_4entry (3 << 8) +#define FUSB300_AHBBCR_S0_8entry (5 << 8) +#define FUSB300_AHBBCR_S0_16entry (7 << 8) +#define FUSB300_AHBBCR_M1_BURST_SINGLE (0 << 4) +#define FUSB300_AHBBCR_M1_BURST_INCR (1 << 4) +#define FUSB300_AHBBCR_M1_BURST_INCR4 (3 << 4) +#define FUSB300_AHBBCR_M1_BURST_INCR8 (5 << 4) +#define FUSB300_AHBBCR_M1_BURST_INCR16 (7 << 4) +#define FUSB300_AHBBCR_M0_BURST_SINGLE 0 +#define FUSB300_AHBBCR_M0_BURST_INCR 1 +#define FUSB300_AHBBCR_M0_BURST_INCR4 3 +#define FUSB300_AHBBCR_M0_BURST_INCR8 5 +#define FUSB300_AHBBCR_M0_BURST_INCR16 7 +#define FUSB300_IGER5_EEP_STL_INT(n) (1 << n) + +/* WORD 0 Data Structure of PRD Table */ +#define FUSB300_EPPRD0_M (1 << 30) +#define FUSB300_EPPRD0_O (1 << 29) +/* The finished prd */ +#define FUSB300_EPPRD0_F (1 << 28) +#define FUSB300_EPPRD0_I (1 << 27) +#define FUSB300_EPPRD0_A (1 << 26) +/* To decide HW point to first prd at next time */ +#define FUSB300_EPPRD0_L (1 << 25) +#define FUSB300_EPPRD0_H (1 << 24) +#define FUSB300_EPPRD0_BTC(n) (n & 0xFFFFFF) + +/*----------------------------------------------------------------------*/ +#define FUSB300_MAX_NUM_EP 16 + +#define FUSB300_FIFO_ENTRY_NUM 8 +#define FUSB300_MAX_FIFO_ENTRY 8 + +#define SS_CTL_MAX_PACKET_SIZE 0x200 +#define SS_BULK_MAX_PACKET_SIZE 0x400 +#define SS_INT_MAX_PACKET_SIZE 0x400 +#define SS_ISO_MAX_PACKET_SIZE 0x400 + +#define HS_BULK_MAX_PACKET_SIZE 0x200 +#define HS_CTL_MAX_PACKET_SIZE 0x40 +#define HS_INT_MAX_PACKET_SIZE 0x400 +#define HS_ISO_MAX_PACKET_SIZE 0x400 + +struct fusb300_ep_info { + u8 epnum; + u8 type; + u8 interval; + u8 dir_in; + u16 maxpacket; + u16 addrofs; + u16 bw_num; +}; + +struct fusb300_request { + + struct usb_request req; + struct list_head queue; +}; + + +struct fusb300_ep { + struct usb_ep ep; + struct fusb300 *fusb300; + + struct list_head queue; + unsigned stall:1; + unsigned wedged:1; + unsigned use_dma:1; + + unsigned char epnum; + unsigned char type; +}; + +struct fusb300 { + spinlock_t lock; + void __iomem *reg; + + unsigned long irq_trigger; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct fusb300_ep *ep[FUSB300_MAX_NUM_EP]; + + struct usb_request *ep0_req; /* for internal request */ + __le16 ep0_data; + u32 ep0_length; /* for internal request */ + u8 ep0_dir; /* 0/0x80 out/in */ + + u8 fifo_entry_num; /* next start fifo entry */ + u32 addrofs; /* next fifo address offset */ + u8 reenum; /* if re-enumeration */ +}; + +#define to_fusb300(g) (container_of((g), struct fusb300, gadget)) + +#endif diff --git a/drivers/usb/gadget/g_ffs.c b/drivers/usb/gadget/g_ffs.c new file mode 100644 index 00000000..787a78e9 --- /dev/null +++ b/drivers/usb/gadget/g_ffs.c @@ -0,0 +1,486 @@ +/* + * g_ffs.c -- user mode file system API for USB composite function controllers + * + * Copyright (C) 2010 Samsung Electronics + * Author: Michal Nazarewicz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#define pr_fmt(fmt) "g_ffs: " fmt + +#include +/* + * kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS +# if defined USB_ETH_RNDIS +# undef USB_ETH_RNDIS +# endif +# ifdef CONFIG_USB_FUNCTIONFS_RNDIS +# define USB_ETH_RNDIS y +# endif + +# include "f_ecm.c" +# include "f_subset.c" +# ifdef USB_ETH_RNDIS +# include "f_rndis.c" +# include "rndis.c" +# endif +# include "u_ether.c" + +static u8 gfs_hostaddr[ETH_ALEN]; +static struct eth_dev *the_dev; +# ifdef CONFIG_USB_FUNCTIONFS_ETH +static int eth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + struct eth_dev *dev); +# endif +#else +# define the_dev NULL +# define gether_cleanup(dev) do { } while (0) +# define gfs_hostaddr NULL +struct eth_dev; +#endif + +#include "f_fs.c" + +#define DRIVER_NAME "g_ffs" +#define DRIVER_DESC "USB Function Filesystem" +#define DRIVER_VERSION "24 Aug 2004" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Michal Nazarewicz"); +MODULE_LICENSE("GPL"); + +#define GFS_VENDOR_ID 0x1d6b /* Linux Foundation */ +#define GFS_PRODUCT_ID 0x0105 /* FunctionFS Gadget */ + +#define GFS_MAX_DEVS 10 + +struct gfs_ffs_obj { + const char *name; + bool mounted; + bool desc_ready; + struct ffs_data *ffs_data; +}; + +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct usb_device_descriptor gfs_dev_desc = { + .bLength = sizeof gfs_dev_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + .idVendor = cpu_to_le16(GFS_VENDOR_ID), + .idProduct = cpu_to_le16(GFS_PRODUCT_ID), +}; + +static char *func_names[GFS_MAX_DEVS]; +static unsigned int func_num; + +module_param_named(bDeviceClass, gfs_dev_desc.bDeviceClass, byte, 0644); +MODULE_PARM_DESC(bDeviceClass, "USB Device class"); +module_param_named(bDeviceSubClass, gfs_dev_desc.bDeviceSubClass, byte, 0644); +MODULE_PARM_DESC(bDeviceSubClass, "USB Device subclass"); +module_param_named(bDeviceProtocol, gfs_dev_desc.bDeviceProtocol, byte, 0644); +MODULE_PARM_DESC(bDeviceProtocol, "USB Device protocol"); +module_param_array_named(functions, func_names, charp, &func_num, 0); +MODULE_PARM_DESC(functions, "USB Functions list"); + +static const struct usb_descriptor_header *gfs_otg_desc[] = { + (const struct usb_descriptor_header *) + &(const struct usb_otg_descriptor) { + .bLength = sizeof(struct usb_otg_descriptor), + .bDescriptorType = USB_DT_OTG, + + /* + * REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, + }, + + NULL +}; + +/* String IDs are assigned dynamically */ +static struct usb_string gfs_strings[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + { .s = "FunctionFS + RNDIS" }, +#endif +#ifdef CONFIG_USB_FUNCTIONFS_ETH + { .s = "FunctionFS + ECM" }, +#endif +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC + { .s = "FunctionFS" }, +#endif + { } /* end of list */ +}; + +static struct usb_gadget_strings *gfs_dev_strings[] = { + &(struct usb_gadget_strings) { + .language = 0x0409, /* en-us */ + .strings = gfs_strings, + }, + NULL, +}; + +struct gfs_configuration { + struct usb_configuration c; + int (*eth)(struct usb_configuration *c, u8 *ethaddr, + struct eth_dev *dev); +} gfs_configurations[] = { +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + { + .eth = rndis_bind_config, + }, +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_ETH + { + .eth = eth_bind_config, + }, +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC + { + }, +#endif +}; + +static int gfs_bind(struct usb_composite_dev *cdev); +static int gfs_unbind(struct usb_composite_dev *cdev); +static int gfs_do_config(struct usb_configuration *c); + +static __refdata struct usb_composite_driver gfs_driver = { + .name = DRIVER_NAME, + .dev = &gfs_dev_desc, + .strings = gfs_dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = gfs_bind, + .unbind = gfs_unbind, +}; + +static DEFINE_MUTEX(gfs_lock); +static unsigned int missing_funcs; +static bool gfs_ether_setup; +static bool gfs_registered; +static bool gfs_single_func; +static struct gfs_ffs_obj *ffs_tab; + +static int __init gfs_init(void) +{ + int i; + + ENTER(); + + if (!func_num) { + gfs_single_func = true; + func_num = 1; + } + + ffs_tab = kcalloc(func_num, sizeof *ffs_tab, GFP_KERNEL); + if (!ffs_tab) + return -ENOMEM; + + if (!gfs_single_func) + for (i = 0; i < func_num; i++) + ffs_tab[i].name = func_names[i]; + + missing_funcs = func_num; + + return functionfs_init(); +} +module_init(gfs_init); + +static void __exit gfs_exit(void) +{ + ENTER(); + mutex_lock(&gfs_lock); + + if (gfs_registered) + usb_composite_unregister(&gfs_driver); + gfs_registered = false; + + functionfs_cleanup(); + + mutex_unlock(&gfs_lock); + kfree(ffs_tab); +} +module_exit(gfs_exit); + +static struct gfs_ffs_obj *gfs_find_dev(const char *dev_name) +{ + int i; + + ENTER(); + + if (gfs_single_func) + return &ffs_tab[0]; + + for (i = 0; i < func_num; i++) + if (strcmp(ffs_tab[i].name, dev_name) == 0) + return &ffs_tab[i]; + + return NULL; +} + +static int functionfs_ready_callback(struct ffs_data *ffs) +{ + struct gfs_ffs_obj *ffs_obj; + int ret; + + ENTER(); + mutex_lock(&gfs_lock); + + ffs_obj = ffs->private_data; + if (!ffs_obj) { + ret = -EINVAL; + goto done; + } + + if (WARN_ON(ffs_obj->desc_ready)) { + ret = -EBUSY; + goto done; + } + ffs_obj->desc_ready = true; + ffs_obj->ffs_data = ffs; + + if (--missing_funcs) { + ret = 0; + goto done; + } + + if (gfs_registered) { + ret = -EBUSY; + goto done; + } + gfs_registered = true; + + ret = usb_composite_probe(&gfs_driver); + if (unlikely(ret < 0)) + gfs_registered = false; + +done: + mutex_unlock(&gfs_lock); + return ret; +} + +static void functionfs_closed_callback(struct ffs_data *ffs) +{ + struct gfs_ffs_obj *ffs_obj; + + ENTER(); + mutex_lock(&gfs_lock); + + ffs_obj = ffs->private_data; + if (!ffs_obj) + goto done; + + ffs_obj->desc_ready = false; + missing_funcs++; + + if (gfs_registered) + usb_composite_unregister(&gfs_driver); + gfs_registered = false; + +done: + mutex_unlock(&gfs_lock); +} + +static void *functionfs_acquire_dev_callback(const char *dev_name) +{ + struct gfs_ffs_obj *ffs_dev; + + ENTER(); + mutex_lock(&gfs_lock); + + ffs_dev = gfs_find_dev(dev_name); + if (!ffs_dev) { + ffs_dev = ERR_PTR(-ENODEV); + goto done; + } + + if (ffs_dev->mounted) { + ffs_dev = ERR_PTR(-EBUSY); + goto done; + } + ffs_dev->mounted = true; + +done: + mutex_unlock(&gfs_lock); + return ffs_dev; +} + +static void functionfs_release_dev_callback(struct ffs_data *ffs_data) +{ + struct gfs_ffs_obj *ffs_dev; + + ENTER(); + mutex_lock(&gfs_lock); + + ffs_dev = ffs_data->private_data; + if (ffs_dev) + ffs_dev->mounted = false; + + mutex_unlock(&gfs_lock); +} + +/* + * It is assumed that gfs_bind is called from a context where gfs_lock is held + */ +static int gfs_bind(struct usb_composite_dev *cdev) +{ + int ret, i; + + ENTER(); + + if (missing_funcs) + return -ENODEV; +#if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS + the_dev = gether_setup(cdev->gadget, gfs_hostaddr); +#endif + if (IS_ERR(the_dev)) { + ret = PTR_ERR(the_dev); + goto error_quick; + } + gfs_ether_setup = true; + + ret = usb_string_ids_tab(cdev, gfs_strings); + if (unlikely(ret < 0)) + goto error; + gfs_dev_desc.iProduct = gfs_strings[USB_GADGET_PRODUCT_IDX].id; + + for (i = func_num; i--; ) { + ret = functionfs_bind(ffs_tab[i].ffs_data, cdev); + if (unlikely(ret < 0)) { + while (++i < func_num) + functionfs_unbind(ffs_tab[i].ffs_data); + goto error; + } + } + + for (i = 0; i < ARRAY_SIZE(gfs_configurations); ++i) { + struct gfs_configuration *c = gfs_configurations + i; + int sid = USB_GADGET_FIRST_AVAIL_IDX + i; + + c->c.label = gfs_strings[sid].s; + c->c.iConfiguration = gfs_strings[sid].id; + c->c.bConfigurationValue = 1 + i; + c->c.bmAttributes = USB_CONFIG_ATT_SELFPOWER; + + ret = usb_add_config(cdev, &c->c, gfs_do_config); + if (unlikely(ret < 0)) + goto error_unbind; + } + usb_composite_overwrite_options(cdev, &coverwrite); + return 0; + +error_unbind: + for (i = 0; i < func_num; i++) + functionfs_unbind(ffs_tab[i].ffs_data); +error: + gether_cleanup(the_dev); +error_quick: + gfs_ether_setup = false; + return ret; +} + +/* + * It is assumed that gfs_unbind is called from a context where gfs_lock is held + */ +static int gfs_unbind(struct usb_composite_dev *cdev) +{ + int i; + + ENTER(); + + /* + * We may have been called in an error recovery from + * composite_bind() after gfs_unbind() failure so we need to + * check if gfs_ffs_data is not NULL since gfs_bind() handles + * all error recovery itself. I'd rather we werent called + * from composite on orror recovery, but what you're gonna + * do...? + */ + if (gfs_ether_setup) + gether_cleanup(the_dev); + gfs_ether_setup = false; + + for (i = func_num; i--; ) + if (ffs_tab[i].ffs_data) + functionfs_unbind(ffs_tab[i].ffs_data); + + return 0; +} + +/* + * It is assumed that gfs_do_config is called from a context where + * gfs_lock is held + */ +static int gfs_do_config(struct usb_configuration *c) +{ + struct gfs_configuration *gc = + container_of(c, struct gfs_configuration, c); + int i; + int ret; + + if (missing_funcs) + return -ENODEV; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = gfs_otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + if (gc->eth) { + ret = gc->eth(c, gfs_hostaddr, the_dev); + if (unlikely(ret < 0)) + return ret; + } + + for (i = 0; i < func_num; i++) { + ret = functionfs_bind_config(c->cdev, c, ffs_tab[i].ffs_data); + if (unlikely(ret < 0)) + return ret; + } + + /* + * After previous do_configs there may be some invalid + * pointers in c->interface array. This happens every time + * a user space function with fewer interfaces than a user + * space function that was run before the new one is run. The + * compasit's set_config() assumes that if there is no more + * then MAX_CONFIG_INTERFACES interfaces in a configuration + * then there is a NULL pointer after the last interface in + * c->interface array. We need to make sure this is true. + */ + if (c->next_interface_id < ARRAY_SIZE(c->interface)) + c->interface[c->next_interface_id] = NULL; + + return 0; +} + +#ifdef CONFIG_USB_FUNCTIONFS_ETH + +static int eth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + struct eth_dev *dev) +{ + return can_support_ecm(c->cdev->gadget) + ? ecm_bind_config(c, ethaddr, dev) + : geth_bind_config(c, ethaddr, dev); +} + +#endif diff --git a/drivers/usb/gadget/g_zero.h b/drivers/usb/gadget/g_zero.h new file mode 100644 index 00000000..ef3e8515 --- /dev/null +++ b/drivers/usb/gadget/g_zero.h @@ -0,0 +1,45 @@ +/* + * This header declares the utility functions used by "Gadget Zero", plus + * interfaces to its two single-configuration function drivers. + */ + +#ifndef __G_ZERO_H +#define __G_ZERO_H + +struct usb_zero_options { + unsigned pattern; + unsigned isoc_interval; + unsigned isoc_maxpacket; + unsigned isoc_mult; + unsigned isoc_maxburst; + unsigned bulk_buflen; + unsigned qlen; +}; + +struct f_ss_opts { + struct usb_function_instance func_inst; + unsigned pattern; + unsigned isoc_interval; + unsigned isoc_maxpacket; + unsigned isoc_mult; + unsigned isoc_maxburst; + unsigned bulk_buflen; +}; + +struct f_lb_opts { + struct usb_function_instance func_inst; + unsigned bulk_buflen; + unsigned qlen; +}; + +void lb_modexit(void); +int lb_modinit(void); + +/* common utilities */ +struct usb_request *alloc_ep_req(struct usb_ep *ep, int len); +void free_ep_req(struct usb_ep *ep, struct usb_request *req); +void disable_endpoints(struct usb_composite_dev *cdev, + struct usb_ep *in, struct usb_ep *out, + struct usb_ep *iso_in, struct usb_ep *iso_out); + +#endif /* __G_ZERO_H */ diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h new file mode 100644 index 00000000..cb6f9be9 --- /dev/null +++ b/drivers/usb/gadget/gadget_chips.h @@ -0,0 +1,66 @@ +/* + * USB device controllers have lots of quirks. Use these macros in + * gadget drivers or other code that needs to deal with them, and which + * autoconfigures instead of using early binding to the hardware. + * + * This SHOULD eventually work like the ARM mach_is_*() stuff, driven by + * some config file that gets updated as new hardware is supported. + * (And avoiding all runtime comparisons in typical one-choice configs!) + * + * NOTE: some of these controller drivers may not be available yet. + * Some are available on 2.4 kernels; several are available, but not + * yet pushed in the 2.6 mainline tree. + */ + +#ifndef __GADGET_CHIPS_H +#define __GADGET_CHIPS_H + +#include + +/* + * NOTICE: the entries below are alphabetical and should be kept + * that way. + * + * Always be sure to add new entries to the correct position or + * accept the bashing later. + * + * If you have forgotten the alphabetical order let VIM/EMACS + * do that for you. + */ +#define gadget_is_at91(g) (!strcmp("at91_udc", (g)->name)) +#define gadget_is_goku(g) (!strcmp("goku_udc", (g)->name)) +#define gadget_is_musbhdrc(g) (!strcmp("musb-hdrc", (g)->name)) +#define gadget_is_net2280(g) (!strcmp("net2280", (g)->name)) +#define gadget_is_pxa(g) (!strcmp("pxa25x_udc", (g)->name)) +#define gadget_is_pxa27x(g) (!strcmp("pxa27x_udc", (g)->name)) +#define gadget_is_sprd_dwc(g) (!strcmp("dwc_otg", (g)->name)) + +/** + * gadget_supports_altsettings - return true if altsettings work + * @gadget: the gadget in question + */ +static inline bool gadget_supports_altsettings(struct usb_gadget *gadget) +{ + /* PXA 21x/25x/26x has no altsettings at all */ + if (gadget_is_pxa(gadget)) + return false; + + /* PXA 27x and 3xx have *broken* altsetting support */ + if (gadget_is_pxa27x(gadget)) + return false; + + /* Everything else is *presumably* fine ... */ + return true; +} + +/** + * gadget_dma32 - return true if we want buffer aligned on 32 bits (for dma) + * @gadget: the gadget in question + */ +static inline bool gadget_dma32(struct usb_gadget *gadget) +{ + if (gadget_is_sprd_dwc(gadget)) + return true; + return false; +} +#endif /* __GADGET_CHIPS_H */ diff --git a/drivers/usb/gadget/gmidi.c b/drivers/usb/gadget/gmidi.c new file mode 100644 index 00000000..e879e2c9 --- /dev/null +++ b/drivers/usb/gadget/gmidi.c @@ -0,0 +1,177 @@ +/* + * gmidi.c -- USB MIDI Gadget Driver + * + * Copyright (C) 2006 Thumtronics Pty Ltd. + * Developed for Thumtronics by Grey Innovation + * Ben Williamson + * + * This software is distributed under the terms of the GNU General Public + * License ("GPL") version 2, as published by the Free Software Foundation. + * + * This code is based in part on: + * + * Gadget Zero driver, Copyright (C) 2003-2004 David Brownell. + * USB Audio driver, Copyright (C) 2002 by Takashi Iwai. + * USB MIDI driver, Copyright (C) 2002-2005 Clemens Ladisch. + * + * Refer to the USB Device Class Definition for MIDI Devices: + * http://www.usb.org/developers/devclass_docs/midi10.pdf + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gadget_chips.h" + +#include "f_midi.c" + +/*-------------------------------------------------------------------------*/ + +MODULE_AUTHOR("Ben Williamson"); +MODULE_LICENSE("GPL v2"); + +static const char shortname[] = "g_midi"; +static const char longname[] = "MIDI Gadget"; + +USB_GADGET_COMPOSITE_OPTIONS(); + +static int index = SNDRV_DEFAULT_IDX1; +module_param(index, int, S_IRUGO); +MODULE_PARM_DESC(index, "Index value for the USB MIDI Gadget adapter."); + +static char *id = SNDRV_DEFAULT_STR1; +module_param(id, charp, S_IRUGO); +MODULE_PARM_DESC(id, "ID string for the USB MIDI Gadget adapter."); + +static unsigned int buflen = 256; +module_param(buflen, uint, S_IRUGO); +MODULE_PARM_DESC(buflen, "MIDI buffer length"); + +static unsigned int qlen = 32; +module_param(qlen, uint, S_IRUGO); +MODULE_PARM_DESC(qlen, "USB read request queue length"); + +static unsigned int in_ports = 1; +module_param(in_ports, uint, S_IRUGO); +MODULE_PARM_DESC(in_ports, "Number of MIDI input ports"); + +static unsigned int out_ports = 1; +module_param(out_ports, uint, S_IRUGO); +MODULE_PARM_DESC(out_ports, "Number of MIDI output ports"); + +/* Thanks to Grey Innovation for donating this product ID. + * + * DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ +#define DRIVER_VENDOR_NUM 0x17b3 /* Grey Innovation */ +#define DRIVER_PRODUCT_NUM 0x0004 /* Linux-USB "MIDI Gadget" */ + +/* string IDs are assigned dynamically */ + +#define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX + +static struct usb_device_descriptor device_desc = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = __constant_cpu_to_le16(DRIVER_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16(DRIVER_PRODUCT_NUM), + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + .bNumConfigurations = 1, +}; + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "Grey Innovation", + [USB_GADGET_PRODUCT_IDX].s = "MIDI Gadget", + [USB_GADGET_SERIAL_IDX].s = "", + [STRING_DESCRIPTION_IDX].s = "MIDI", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static int __exit midi_unbind(struct usb_composite_dev *dev) +{ + return 0; +} + +static struct usb_configuration midi_config = { + .label = "MIDI Gadget", + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_ONE, + .MaxPower = CONFIG_USB_GADGET_VBUS_DRAW, +}; + +static int __init midi_bind_config(struct usb_configuration *c) +{ + return f_midi_bind_config(c, index, id, + in_ports, out_ports, + buflen, qlen); +} + +static int __init midi_bind(struct usb_composite_dev *cdev) +{ + int status; + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + return status; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + midi_config.iConfiguration = strings_dev[STRING_DESCRIPTION_IDX].id; + + status = usb_add_config(cdev, &midi_config, midi_bind_config); + if (status < 0) + return status; + usb_composite_overwrite_options(cdev, &coverwrite); + pr_info("%s\n", longname); + return 0; +} + +static __refdata struct usb_composite_driver midi_driver = { + .name = (char *) longname, + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = midi_bind, + .unbind = __exit_p(midi_unbind), +}; + +static int __init midi_init(void) +{ + return usb_composite_probe(&midi_driver); +} +module_init(midi_init); + +static void __exit midi_cleanup(void) +{ + usb_composite_unregister(&midi_driver); +} +module_exit(midi_cleanup); + diff --git a/drivers/usb/gadget/goku_udc.c b/drivers/usb/gadget/goku_udc.c new file mode 100644 index 00000000..52dd6cc6 --- /dev/null +++ b/drivers/usb/gadget/goku_udc.c @@ -0,0 +1,1829 @@ +/* + * Toshiba TC86C001 ("Goku-S") USB Device Controller driver + * + * Copyright (C) 2000-2002 Lineo + * by Stuart Lynne, Tom Rushworth, and Bruce Balden + * Copyright (C) 2002 Toshiba Corporation + * Copyright (C) 2003 MontaVista Software (source@mvista.com) + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +/* + * This device has ep0 and three semi-configurable bulk/interrupt endpoints. + * + * - Endpoint numbering is fixed: ep{1,2,3}-bulk + * - Gadget drivers can choose ep maxpacket (8/16/32/64) + * - Gadget drivers can choose direction (IN, OUT) + * - DMA works with ep1 (OUT transfers) and ep2 (IN transfers). + */ + +// #define VERBOSE /* extra debug messages (success too) */ +// #define USB_TRACE /* packet-level success messages */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#include "goku_udc.h" + +#define DRIVER_DESC "TC86C001 USB Device Controller" +#define DRIVER_VERSION "30-Oct 2003" + +static const char driver_name [] = "goku_udc"; +static const char driver_desc [] = DRIVER_DESC; + +MODULE_AUTHOR("source@mvista.com"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + + +/* + * IN dma behaves ok under testing, though the IN-dma abort paths don't + * seem to behave quite as expected. Used by default. + * + * OUT dma documents design problems handling the common "short packet" + * transfer termination policy; it couldn't be enabled by default, even + * if the OUT-dma abort problems had a resolution. + */ +static unsigned use_dma = 1; + +#if 0 +//#include +/* "modprobe goku_udc use_dma=1" etc + * 0 to disable dma + * 1 to use IN dma only (normal operation) + * 2 to use IN and OUT dma + */ +module_param(use_dma, uint, S_IRUGO); +#endif + +/*-------------------------------------------------------------------------*/ + +static void nuke(struct goku_ep *, int status); + +static inline void +command(struct goku_udc_regs __iomem *regs, int command, unsigned epnum) +{ + writel(COMMAND_EP(epnum) | command, ®s->Command); + udelay(300); +} + +static int +goku_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct goku_udc *dev; + struct goku_ep *ep; + u32 mode; + u16 max; + unsigned long flags; + + ep = container_of(_ep, struct goku_ep, ep); + if (!_ep || !desc + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + dev = ep->dev; + if (ep == &dev->ep[0]) + return -EINVAL; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + if (ep->num != usb_endpoint_num(desc)) + return -EINVAL; + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + break; + default: + return -EINVAL; + } + + if ((readl(ep->reg_status) & EPxSTATUS_EP_MASK) + != EPxSTATUS_EP_INVALID) + return -EBUSY; + + /* enabling the no-toggle interrupt mode would need an api hook */ + mode = 0; + max = get_unaligned_le16(&desc->wMaxPacketSize); + switch (max) { + case 64: mode++; + case 32: mode++; + case 16: mode++; + case 8: mode <<= 3; + break; + default: + return -EINVAL; + } + mode |= 2 << 1; /* bulk, or intr-with-toggle */ + + /* ep1/ep2 dma direction is chosen early; it works in the other + * direction, with pio. be cautious with out-dma. + */ + ep->is_in = usb_endpoint_dir_in(desc); + if (ep->is_in) { + mode |= 1; + ep->dma = (use_dma != 0) && (ep->num == UDC_MSTRD_ENDPOINT); + } else { + ep->dma = (use_dma == 2) && (ep->num == UDC_MSTWR_ENDPOINT); + if (ep->dma) + DBG(dev, "%s out-dma hides short packets\n", + ep->ep.name); + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* ep1 and ep2 can do double buffering and/or dma */ + if (ep->num < 3) { + struct goku_udc_regs __iomem *regs = ep->dev->regs; + u32 tmp; + + /* double buffer except (for now) with pio in */ + tmp = ((ep->dma || !ep->is_in) + ? 0x10 /* double buffered */ + : 0x11 /* single buffer */ + ) << ep->num; + tmp |= readl(®s->EPxSingle); + writel(tmp, ®s->EPxSingle); + + tmp = (ep->dma ? 0x10/*dma*/ : 0x11/*pio*/) << ep->num; + tmp |= readl(®s->EPxBCS); + writel(tmp, ®s->EPxBCS); + } + writel(mode, ep->reg_mode); + command(ep->dev->regs, COMMAND_RESET, ep->num); + ep->ep.maxpacket = max; + ep->stopped = 0; + ep->ep.desc = desc; + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DBG(dev, "enable %s %s %s maxpacket %u\n", ep->ep.name, + ep->is_in ? "IN" : "OUT", + ep->dma ? "dma" : "pio", + max); + + return 0; +} + +static void ep_reset(struct goku_udc_regs __iomem *regs, struct goku_ep *ep) +{ + struct goku_udc *dev = ep->dev; + + if (regs) { + command(regs, COMMAND_INVALID, ep->num); + if (ep->num) { + if (ep->num == UDC_MSTWR_ENDPOINT) + dev->int_enable &= ~(INT_MSTWREND + |INT_MSTWRTMOUT); + else if (ep->num == UDC_MSTRD_ENDPOINT) + dev->int_enable &= ~INT_MSTRDEND; + dev->int_enable &= ~INT_EPxDATASET (ep->num); + } else + dev->int_enable &= ~INT_EP0; + writel(dev->int_enable, ®s->int_enable); + readl(®s->int_enable); + if (ep->num < 3) { + struct goku_udc_regs __iomem *r = ep->dev->regs; + u32 tmp; + + tmp = readl(&r->EPxSingle); + tmp &= ~(0x11 << ep->num); + writel(tmp, &r->EPxSingle); + + tmp = readl(&r->EPxBCS); + tmp &= ~(0x11 << ep->num); + writel(tmp, &r->EPxBCS); + } + /* reset dma in case we're still using it */ + if (ep->dma) { + u32 master; + + master = readl(®s->dma_master) & MST_RW_BITS; + if (ep->num == UDC_MSTWR_ENDPOINT) { + master &= ~MST_W_BITS; + master |= MST_WR_RESET; + } else { + master &= ~MST_R_BITS; + master |= MST_RD_RESET; + } + writel(master, ®s->dma_master); + } + } + + ep->ep.maxpacket = MAX_FIFO_SIZE; + ep->ep.desc = NULL; + ep->stopped = 1; + ep->irqs = 0; + ep->dma = 0; +} + +static int goku_ep_disable(struct usb_ep *_ep) +{ + struct goku_ep *ep; + struct goku_udc *dev; + unsigned long flags; + + ep = container_of(_ep, struct goku_ep, ep); + if (!_ep || !ep->ep.desc) + return -ENODEV; + dev = ep->dev; + if (dev->ep0state == EP0_SUSPEND) + return -EBUSY; + + VDBG(dev, "disable %s\n", _ep->name); + + spin_lock_irqsave(&dev->lock, flags); + nuke(ep, -ESHUTDOWN); + ep_reset(dev->regs, ep); + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_request * +goku_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct goku_request *req; + + if (!_ep) + return NULL; + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +static void +goku_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct goku_request *req; + + if (!_ep || !_req) + return; + + req = container_of(_req, struct goku_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/*-------------------------------------------------------------------------*/ + +static void +done(struct goku_ep *ep, struct goku_request *req, int status) +{ + struct goku_udc *dev; + unsigned stopped = ep->stopped; + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + dev = ep->dev; + + if (ep->dma) + usb_gadget_unmap_request(&dev->gadget, &req->req, ep->is_in); + +#ifndef USB_TRACE + if (status && status != -ESHUTDOWN) +#endif + VDBG(dev, "complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + spin_unlock(&dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&dev->lock); + ep->stopped = stopped; +} + +/*-------------------------------------------------------------------------*/ + +static inline int +write_packet(u32 __iomem *fifo, u8 *buf, struct goku_request *req, unsigned max) +{ + unsigned length, count; + + length = min(req->req.length - req->req.actual, max); + req->req.actual += length; + + count = length; + while (likely(count--)) + writel(*buf++, fifo); + return length; +} + +// return: 0 = still running, 1 = completed, negative = errno +static int write_fifo(struct goku_ep *ep, struct goku_request *req) +{ + struct goku_udc *dev = ep->dev; + u32 tmp; + u8 *buf; + unsigned count; + int is_last; + + tmp = readl(&dev->regs->DataSet); + buf = req->req.buf + req->req.actual; + prefetch(buf); + + dev = ep->dev; + if (unlikely(ep->num == 0 && dev->ep0state != EP0_IN)) + return -EL2HLT; + + /* NOTE: just single-buffered PIO-IN for now. */ + if (unlikely((tmp & DATASET_A(ep->num)) != 0)) + return 0; + + /* clear our "packet available" irq */ + if (ep->num != 0) + writel(~INT_EPxDATASET(ep->num), &dev->regs->int_status); + + count = write_packet(ep->reg_fifo, buf, req, ep->ep.maxpacket); + + /* last packet often short (sometimes a zlp, especially on ep0) */ + if (unlikely(count != ep->ep.maxpacket)) { + writel(~(1<num), &dev->regs->EOP); + if (ep->num == 0) { + dev->ep[0].stopped = 1; + dev->ep0state = EP0_STATUS; + } + is_last = 1; + } else { + if (likely(req->req.length != req->req.actual) + || req->req.zero) + is_last = 0; + else + is_last = 1; + } +#if 0 /* printk seemed to trash is_last...*/ +//#ifdef USB_TRACE + VDBG(dev, "wrote %s %u bytes%s IN %u left %p\n", + ep->ep.name, count, is_last ? "/last" : "", + req->req.length - req->req.actual, req); +#endif + + /* requests complete when all IN data is in the FIFO, + * or sometimes later, if a zlp was needed. + */ + if (is_last) { + done(ep, req, 0); + return 1; + } + + return 0; +} + +static int read_fifo(struct goku_ep *ep, struct goku_request *req) +{ + struct goku_udc_regs __iomem *regs; + u32 size, set; + u8 *buf; + unsigned bufferspace, is_short, dbuff; + + regs = ep->dev->regs; +top: + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + if (unlikely(ep->num == 0 && ep->dev->ep0state != EP0_OUT)) + return -EL2HLT; + + dbuff = (ep->num == 1 || ep->num == 2); + do { + /* ack dataset irq matching the status we'll handle */ + if (ep->num != 0) + writel(~INT_EPxDATASET(ep->num), ®s->int_status); + + set = readl(®s->DataSet) & DATASET_AB(ep->num); + size = readl(®s->EPxSizeLA[ep->num]); + bufferspace = req->req.length - req->req.actual; + + /* usually do nothing without an OUT packet */ + if (likely(ep->num != 0 || bufferspace != 0)) { + if (unlikely(set == 0)) + break; + /* use ep1/ep2 double-buffering for OUT */ + if (!(size & PACKET_ACTIVE)) + size = readl(®s->EPxSizeLB[ep->num]); + if (!(size & PACKET_ACTIVE)) /* "can't happen" */ + break; + size &= DATASIZE; /* EPxSizeH == 0 */ + + /* ep0out no-out-data case for set_config, etc */ + } else + size = 0; + + /* read all bytes from this packet */ + req->req.actual += size; + is_short = (size < ep->ep.maxpacket); +#ifdef USB_TRACE + VDBG(ep->dev, "read %s %u bytes%s OUT req %p %u/%u\n", + ep->ep.name, size, is_short ? "/S" : "", + req, req->req.actual, req->req.length); +#endif + while (likely(size-- != 0)) { + u8 byte = (u8) readl(ep->reg_fifo); + + if (unlikely(bufferspace == 0)) { + /* this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data in this packet. + */ + if (req->req.status != -EOVERFLOW) + DBG(ep->dev, "%s overflow %u\n", + ep->ep.name, size); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + bufferspace--; + } + } + + /* completion */ + if (unlikely(is_short || req->req.actual == req->req.length)) { + if (unlikely(ep->num == 0)) { + /* non-control endpoints now usable? */ + if (ep->dev->req_config) + writel(ep->dev->configured + ? USBSTATE_CONFIGURED + : 0, + ®s->UsbState); + /* ep0out status stage */ + writel(~(1<<0), ®s->EOP); + ep->stopped = 1; + ep->dev->ep0state = EP0_STATUS; + } + done(ep, req, 0); + + /* empty the second buffer asap */ + if (dbuff && !list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct goku_request, queue); + goto top; + } + return 1; + } + } while (dbuff); + return 0; +} + +static inline void +pio_irq_enable(struct goku_udc *dev, + struct goku_udc_regs __iomem *regs, int epnum) +{ + dev->int_enable |= INT_EPxDATASET (epnum); + writel(dev->int_enable, ®s->int_enable); + /* write may still be posted */ +} + +static inline void +pio_irq_disable(struct goku_udc *dev, + struct goku_udc_regs __iomem *regs, int epnum) +{ + dev->int_enable &= ~INT_EPxDATASET (epnum); + writel(dev->int_enable, ®s->int_enable); + /* write may still be posted */ +} + +static inline void +pio_advance(struct goku_ep *ep) +{ + struct goku_request *req; + + if (unlikely(list_empty (&ep->queue))) + return; + req = list_entry(ep->queue.next, struct goku_request, queue); + (ep->is_in ? write_fifo : read_fifo)(ep, req); +} + + +/*-------------------------------------------------------------------------*/ + +// return: 0 = q running, 1 = q stopped, negative = errno +static int start_dma(struct goku_ep *ep, struct goku_request *req) +{ + struct goku_udc_regs __iomem *regs = ep->dev->regs; + u32 master; + u32 start = req->req.dma; + u32 end = start + req->req.length - 1; + + master = readl(®s->dma_master) & MST_RW_BITS; + + /* re-init the bits affecting IN dma; careful with zlps */ + if (likely(ep->is_in)) { + if (unlikely(master & MST_RD_ENA)) { + DBG (ep->dev, "start, IN active dma %03x!!\n", + master); +// return -EL2HLT; + } + writel(end, ®s->in_dma_end); + writel(start, ®s->in_dma_start); + + master &= ~MST_R_BITS; + if (unlikely(req->req.length == 0)) + master = MST_RD_ENA | MST_RD_EOPB; + else if ((req->req.length % ep->ep.maxpacket) != 0 + || req->req.zero) + master = MST_RD_ENA | MST_EOPB_ENA; + else + master = MST_RD_ENA | MST_EOPB_DIS; + + ep->dev->int_enable |= INT_MSTRDEND; + + /* Goku DMA-OUT merges short packets, which plays poorly with + * protocols where short packets mark the transfer boundaries. + * The chip supports a nonstandard policy with INT_MSTWRTMOUT, + * ending transfers after 3 SOFs; we don't turn it on. + */ + } else { + if (unlikely(master & MST_WR_ENA)) { + DBG (ep->dev, "start, OUT active dma %03x!!\n", + master); +// return -EL2HLT; + } + writel(end, ®s->out_dma_end); + writel(start, ®s->out_dma_start); + + master &= ~MST_W_BITS; + master |= MST_WR_ENA | MST_TIMEOUT_DIS; + + ep->dev->int_enable |= INT_MSTWREND|INT_MSTWRTMOUT; + } + + writel(master, ®s->dma_master); + writel(ep->dev->int_enable, ®s->int_enable); + return 0; +} + +static void dma_advance(struct goku_udc *dev, struct goku_ep *ep) +{ + struct goku_request *req; + struct goku_udc_regs __iomem *regs = ep->dev->regs; + u32 master; + + master = readl(®s->dma_master); + + if (unlikely(list_empty(&ep->queue))) { +stop: + if (ep->is_in) + dev->int_enable &= ~INT_MSTRDEND; + else + dev->int_enable &= ~(INT_MSTWREND|INT_MSTWRTMOUT); + writel(dev->int_enable, ®s->int_enable); + return; + } + req = list_entry(ep->queue.next, struct goku_request, queue); + + /* normal hw dma completion (not abort) */ + if (likely(ep->is_in)) { + if (unlikely(master & MST_RD_ENA)) + return; + req->req.actual = readl(®s->in_dma_current); + } else { + if (unlikely(master & MST_WR_ENA)) + return; + + /* hardware merges short packets, and also hides packet + * overruns. a partial packet MAY be in the fifo here. + */ + req->req.actual = readl(®s->out_dma_current); + } + req->req.actual -= req->req.dma; + req->req.actual++; + +#ifdef USB_TRACE + VDBG(dev, "done %s %s dma, %u/%u bytes, req %p\n", + ep->ep.name, ep->is_in ? "IN" : "OUT", + req->req.actual, req->req.length, req); +#endif + done(ep, req, 0); + if (list_empty(&ep->queue)) + goto stop; + req = list_entry(ep->queue.next, struct goku_request, queue); + (void) start_dma(ep, req); +} + +static void abort_dma(struct goku_ep *ep, int status) +{ + struct goku_udc_regs __iomem *regs = ep->dev->regs; + struct goku_request *req; + u32 curr, master; + + /* NAK future host requests, hoping the implicit delay lets the + * dma engine finish reading (or writing) its latest packet and + * empty the dma buffer (up to 16 bytes). + * + * This avoids needing to clean up a partial packet in the fifo; + * we can't do that for IN without side effects to HALT and TOGGLE. + */ + command(regs, COMMAND_FIFO_DISABLE, ep->num); + req = list_entry(ep->queue.next, struct goku_request, queue); + master = readl(®s->dma_master) & MST_RW_BITS; + + /* FIXME using these resets isn't usably documented. this may + * not work unless it's followed by disabling the endpoint. + * + * FIXME the OUT reset path doesn't even behave consistently. + */ + if (ep->is_in) { + if (unlikely((readl(®s->dma_master) & MST_RD_ENA) == 0)) + goto finished; + curr = readl(®s->in_dma_current); + + writel(curr, ®s->in_dma_end); + writel(curr, ®s->in_dma_start); + + master &= ~MST_R_BITS; + master |= MST_RD_RESET; + writel(master, ®s->dma_master); + + if (readl(®s->dma_master) & MST_RD_ENA) + DBG(ep->dev, "IN dma active after reset!\n"); + + } else { + if (unlikely((readl(®s->dma_master) & MST_WR_ENA) == 0)) + goto finished; + curr = readl(®s->out_dma_current); + + writel(curr, ®s->out_dma_end); + writel(curr, ®s->out_dma_start); + + master &= ~MST_W_BITS; + master |= MST_WR_RESET; + writel(master, ®s->dma_master); + + if (readl(®s->dma_master) & MST_WR_ENA) + DBG(ep->dev, "OUT dma active after reset!\n"); + } + req->req.actual = (curr - req->req.dma) + 1; + req->req.status = status; + + VDBG(ep->dev, "%s %s %s %d/%d\n", __func__, ep->ep.name, + ep->is_in ? "IN" : "OUT", + req->req.actual, req->req.length); + + command(regs, COMMAND_FIFO_ENABLE, ep->num); + + return; + +finished: + /* dma already completed; no abort needed */ + command(regs, COMMAND_FIFO_ENABLE, ep->num); + req->req.actual = req->req.length; + req->req.status = 0; +} + +/*-------------------------------------------------------------------------*/ + +static int +goku_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct goku_request *req; + struct goku_ep *ep; + struct goku_udc *dev; + unsigned long flags; + int status; + + /* always require a cpu-view buffer so pio works */ + req = container_of(_req, struct goku_request, req); + if (unlikely(!_req || !_req->complete + || !_req->buf || !list_empty(&req->queue))) + return -EINVAL; + ep = container_of(_ep, struct goku_ep, ep); + if (unlikely(!_ep || (!ep->ep.desc && ep->num != 0))) + return -EINVAL; + dev = ep->dev; + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + /* can't touch registers when suspended */ + if (dev->ep0state == EP0_SUSPEND) + return -EBUSY; + + /* set up dma mapping in case the caller didn't */ + if (ep->dma) { + status = usb_gadget_map_request(&dev->gadget, &req->req, + ep->is_in); + if (status) + return status; + } + +#ifdef USB_TRACE + VDBG(dev, "%s queue req %p, len %u buf %p\n", + _ep->name, _req, _req->length, _req->buf); +#endif + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* for ep0 IN without premature status, zlp is required and + * writing EOP starts the status stage (OUT). + */ + if (unlikely(ep->num == 0 && ep->is_in)) + _req->zero = 1; + + /* kickstart this i/o queue? */ + status = 0; + if (list_empty(&ep->queue) && likely(!ep->stopped)) { + /* dma: done after dma completion IRQ (or error) + * pio: done after last fifo operation + */ + if (ep->dma) + status = start_dma(ep, req); + else + status = (ep->is_in ? write_fifo : read_fifo)(ep, req); + + if (unlikely(status != 0)) { + if (status > 0) + status = 0; + req = NULL; + } + + } /* else pio or dma irq handler advances the queue. */ + + if (likely(req != 0)) + list_add_tail(&req->queue, &ep->queue); + + if (likely(!list_empty(&ep->queue)) + && likely(ep->num != 0) + && !ep->dma + && !(dev->int_enable & INT_EPxDATASET (ep->num))) + pio_irq_enable(dev, dev->regs, ep->num); + + spin_unlock_irqrestore(&dev->lock, flags); + + /* pci writes may still be posted */ + return status; +} + +/* dequeue ALL requests */ +static void nuke(struct goku_ep *ep, int status) +{ + struct goku_request *req; + + ep->stopped = 1; + if (list_empty(&ep->queue)) + return; + if (ep->dma) + abort_dma(ep, status); + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct goku_request, queue); + done(ep, req, status); + } +} + +/* dequeue JUST ONE request */ +static int goku_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct goku_request *req; + struct goku_ep *ep; + struct goku_udc *dev; + unsigned long flags; + + ep = container_of(_ep, struct goku_ep, ep); + if (!_ep || !_req || (!ep->ep.desc && ep->num != 0)) + return -EINVAL; + dev = ep->dev; + if (!dev->driver) + return -ESHUTDOWN; + + /* we can't touch (dma) registers when suspended */ + if (dev->ep0state == EP0_SUSPEND) + return -EBUSY; + + VDBG(dev, "%s %s %s %s %p\n", __func__, _ep->name, + ep->is_in ? "IN" : "OUT", + ep->dma ? "dma" : "pio", + _req); + + spin_lock_irqsave(&dev->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry (req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore (&dev->lock, flags); + return -EINVAL; + } + + if (ep->dma && ep->queue.next == &req->queue && !ep->stopped) { + abort_dma(ep, -ECONNRESET); + done(ep, req, -ECONNRESET); + dma_advance(dev, ep); + } else if (!list_empty(&req->queue)) + done(ep, req, -ECONNRESET); + else + req = NULL; + spin_unlock_irqrestore(&dev->lock, flags); + + return req ? 0 : -EOPNOTSUPP; +} + +/*-------------------------------------------------------------------------*/ + +static void goku_clear_halt(struct goku_ep *ep) +{ + // assert (ep->num !=0) + VDBG(ep->dev, "%s clear halt\n", ep->ep.name); + command(ep->dev->regs, COMMAND_SETDATA0, ep->num); + command(ep->dev->regs, COMMAND_STALL_CLEAR, ep->num); + if (ep->stopped) { + ep->stopped = 0; + if (ep->dma) { + struct goku_request *req; + + if (list_empty(&ep->queue)) + return; + req = list_entry(ep->queue.next, struct goku_request, + queue); + (void) start_dma(ep, req); + } else + pio_advance(ep); + } +} + +static int goku_set_halt(struct usb_ep *_ep, int value) +{ + struct goku_ep *ep; + unsigned long flags; + int retval = 0; + + if (!_ep) + return -ENODEV; + ep = container_of (_ep, struct goku_ep, ep); + + if (ep->num == 0) { + if (value) { + ep->dev->ep0state = EP0_STALL; + ep->dev->ep[0].stopped = 1; + } else + return -EINVAL; + + /* don't change EPxSTATUS_EP_INVALID to READY */ + } else if (!ep->ep.desc) { + DBG(ep->dev, "%s %s inactive?\n", __func__, ep->ep.name); + return -EINVAL; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + if (!list_empty(&ep->queue)) + retval = -EAGAIN; + else if (ep->is_in && value + /* data in (either) packet buffer? */ + && (readl(&ep->dev->regs->DataSet) + & DATASET_AB(ep->num))) + retval = -EAGAIN; + else if (!value) + goku_clear_halt(ep); + else { + ep->stopped = 1; + VDBG(ep->dev, "%s set halt\n", ep->ep.name); + command(ep->dev->regs, COMMAND_STALL, ep->num); + readl(ep->reg_status); + } + spin_unlock_irqrestore(&ep->dev->lock, flags); + return retval; +} + +static int goku_fifo_status(struct usb_ep *_ep) +{ + struct goku_ep *ep; + struct goku_udc_regs __iomem *regs; + u32 size; + + if (!_ep) + return -ENODEV; + ep = container_of(_ep, struct goku_ep, ep); + + /* size is only reported sanely for OUT */ + if (ep->is_in) + return -EOPNOTSUPP; + + /* ignores 16-byte dma buffer; SizeH == 0 */ + regs = ep->dev->regs; + size = readl(®s->EPxSizeLA[ep->num]) & DATASIZE; + size += readl(®s->EPxSizeLB[ep->num]) & DATASIZE; + VDBG(ep->dev, "%s %s %u\n", __func__, ep->ep.name, size); + return size; +} + +static void goku_fifo_flush(struct usb_ep *_ep) +{ + struct goku_ep *ep; + struct goku_udc_regs __iomem *regs; + u32 size; + + if (!_ep) + return; + ep = container_of(_ep, struct goku_ep, ep); + VDBG(ep->dev, "%s %s\n", __func__, ep->ep.name); + + /* don't change EPxSTATUS_EP_INVALID to READY */ + if (!ep->ep.desc && ep->num != 0) { + DBG(ep->dev, "%s %s inactive?\n", __func__, ep->ep.name); + return; + } + + regs = ep->dev->regs; + size = readl(®s->EPxSizeLA[ep->num]); + size &= DATASIZE; + + /* Non-desirable behavior: FIFO_CLEAR also clears the + * endpoint halt feature. For OUT, we _could_ just read + * the bytes out (PIO, if !ep->dma); for in, no choice. + */ + if (size) + command(regs, COMMAND_FIFO_CLEAR, ep->num); +} + +static struct usb_ep_ops goku_ep_ops = { + .enable = goku_ep_enable, + .disable = goku_ep_disable, + + .alloc_request = goku_alloc_request, + .free_request = goku_free_request, + + .queue = goku_queue, + .dequeue = goku_dequeue, + + .set_halt = goku_set_halt, + .fifo_status = goku_fifo_status, + .fifo_flush = goku_fifo_flush, +}; + +/*-------------------------------------------------------------------------*/ + +static int goku_get_frame(struct usb_gadget *_gadget) +{ + return -EOPNOTSUPP; +} + +static int goku_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int goku_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops goku_ops = { + .get_frame = goku_get_frame, + .udc_start = goku_udc_start, + .udc_stop = goku_udc_stop, + // no remote wakeup + // not selfpowered +}; + +/*-------------------------------------------------------------------------*/ + +static inline const char *dmastr(void) +{ + if (use_dma == 0) + return "(dma disabled)"; + else if (use_dma == 2) + return "(dma IN and OUT)"; + else + return "(dma IN)"; +} + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static const char proc_node_name [] = "driver/udc"; + +#define FOURBITS "%s%s%s%s" +#define EIGHTBITS FOURBITS FOURBITS + +static void dump_intmask(struct seq_file *m, const char *label, u32 mask) +{ + /* int_status is the same format ... */ + seq_printf(m, + "%s %05X =" FOURBITS EIGHTBITS EIGHTBITS "\n", + label, mask, + (mask & INT_PWRDETECT) ? " power" : "", + (mask & INT_SYSERROR) ? " sys" : "", + (mask & INT_MSTRDEND) ? " in-dma" : "", + (mask & INT_MSTWRTMOUT) ? " wrtmo" : "", + + (mask & INT_MSTWREND) ? " out-dma" : "", + (mask & INT_MSTWRSET) ? " wrset" : "", + (mask & INT_ERR) ? " err" : "", + (mask & INT_SOF) ? " sof" : "", + + (mask & INT_EP3NAK) ? " ep3nak" : "", + (mask & INT_EP2NAK) ? " ep2nak" : "", + (mask & INT_EP1NAK) ? " ep1nak" : "", + (mask & INT_EP3DATASET) ? " ep3" : "", + + (mask & INT_EP2DATASET) ? " ep2" : "", + (mask & INT_EP1DATASET) ? " ep1" : "", + (mask & INT_STATUSNAK) ? " ep0snak" : "", + (mask & INT_STATUS) ? " ep0status" : "", + + (mask & INT_SETUP) ? " setup" : "", + (mask & INT_ENDPOINT0) ? " ep0" : "", + (mask & INT_USBRESET) ? " reset" : "", + (mask & INT_SUSPEND) ? " suspend" : ""); +} + + +static int udc_proc_read(struct seq_file *m, void *v) +{ + struct goku_udc *dev = m->private; + struct goku_udc_regs __iomem *regs = dev->regs; + unsigned long flags; + int i, is_usb_connected; + u32 tmp; + + local_irq_save(flags); + + /* basic device status */ + tmp = readl(®s->power_detect); + is_usb_connected = tmp & PW_DETECT; + seq_printf(m, + "%s - %s\n" + "%s version: %s %s\n" + "Gadget driver: %s\n" + "Host %s, %s\n" + "\n", + pci_name(dev->pdev), driver_desc, + driver_name, DRIVER_VERSION, dmastr(), + dev->driver ? dev->driver->driver.name : "(none)", + is_usb_connected + ? ((tmp & PW_PULLUP) ? "full speed" : "powered") + : "disconnected", + ({const char *state; + switch(dev->ep0state){ + case EP0_DISCONNECT: state = "ep0_disconnect"; break; + case EP0_IDLE: state = "ep0_idle"; break; + case EP0_IN: state = "ep0_in"; break; + case EP0_OUT: state = "ep0_out"; break; + case EP0_STATUS: state = "ep0_status"; break; + case EP0_STALL: state = "ep0_stall"; break; + case EP0_SUSPEND: state = "ep0_suspend"; break; + default: state = "ep0_?"; break; + } state; }) + ); + + dump_intmask(m, "int_status", readl(®s->int_status)); + dump_intmask(m, "int_enable", readl(®s->int_enable)); + + if (!is_usb_connected || !dev->driver || (tmp & PW_PULLUP) == 0) + goto done; + + /* registers for (active) device and ep0 */ + if (seq_printf(m, "\nirqs %lu\ndataset %02x " + "single.bcs %02x.%02x state %x addr %u\n", + dev->irqs, readl(®s->DataSet), + readl(®s->EPxSingle), readl(®s->EPxBCS), + readl(®s->UsbState), + readl(®s->address)) < 0) + goto done; + + tmp = readl(®s->dma_master); + if (seq_printf(m, + "dma %03X =" EIGHTBITS "%s %s\n", tmp, + (tmp & MST_EOPB_DIS) ? " eopb-" : "", + (tmp & MST_EOPB_ENA) ? " eopb+" : "", + (tmp & MST_TIMEOUT_DIS) ? " tmo-" : "", + (tmp & MST_TIMEOUT_ENA) ? " tmo+" : "", + + (tmp & MST_RD_EOPB) ? " eopb" : "", + (tmp & MST_RD_RESET) ? " in_reset" : "", + (tmp & MST_WR_RESET) ? " out_reset" : "", + (tmp & MST_RD_ENA) ? " IN" : "", + + (tmp & MST_WR_ENA) ? " OUT" : "", + (tmp & MST_CONNECTION) + ? "ep1in/ep2out" + : "ep1out/ep2in") < 0) + goto done; + + /* dump endpoint queues */ + for (i = 0; i < 4; i++) { + struct goku_ep *ep = &dev->ep [i]; + struct goku_request *req; + + if (i && !ep->ep.desc) + continue; + + tmp = readl(ep->reg_status); + if (seq_printf(m, + "%s %s max %u %s, irqs %lu, " + "status %02x (%s) " FOURBITS "\n", + ep->ep.name, + ep->is_in ? "in" : "out", + ep->ep.maxpacket, + ep->dma ? "dma" : "pio", + ep->irqs, + tmp, ({ char *s; + switch (tmp & EPxSTATUS_EP_MASK) { + case EPxSTATUS_EP_READY: + s = "ready"; break; + case EPxSTATUS_EP_DATAIN: + s = "packet"; break; + case EPxSTATUS_EP_FULL: + s = "full"; break; + case EPxSTATUS_EP_TX_ERR: // host will retry + s = "tx_err"; break; + case EPxSTATUS_EP_RX_ERR: + s = "rx_err"; break; + case EPxSTATUS_EP_BUSY: /* ep0 only */ + s = "busy"; break; + case EPxSTATUS_EP_STALL: + s = "stall"; break; + case EPxSTATUS_EP_INVALID: // these "can't happen" + s = "invalid"; break; + default: + s = "?"; break; + }; s; }), + (tmp & EPxSTATUS_TOGGLE) ? "data1" : "data0", + (tmp & EPxSTATUS_SUSPEND) ? " suspend" : "", + (tmp & EPxSTATUS_FIFO_DISABLE) ? " disable" : "", + (tmp & EPxSTATUS_STAGE_ERROR) ? " ep0stat" : "" + ) < 0) + goto done; + + if (list_empty(&ep->queue)) { + if (seq_puts(m, "\t(nothing queued)\n") < 0) + goto done; + continue; + } + list_for_each_entry(req, &ep->queue, queue) { + if (ep->dma && req->queue.prev == &ep->queue) { + if (i == UDC_MSTRD_ENDPOINT) + tmp = readl(®s->in_dma_current); + else + tmp = readl(®s->out_dma_current); + tmp -= req->req.dma; + tmp++; + } else + tmp = req->req.actual; + + if (seq_printf(m, + "\treq %p len %u/%u buf %p\n", + &req->req, tmp, req->req.length, + req->req.buf) < 0) + goto done; + } + } + +done: + local_irq_restore(flags); + return 0; +} + +/* + * seq_file wrappers for procfile show routines. + */ +static int udc_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, udc_proc_read, PDE_DATA(file_inode(file))); +} + +static const struct file_operations udc_proc_fops = { + .open = udc_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +/*-------------------------------------------------------------------------*/ + +static void udc_reinit (struct goku_udc *dev) +{ + static char *names [] = { "ep0", "ep1-bulk", "ep2-bulk", "ep3-bulk" }; + + unsigned i; + + INIT_LIST_HEAD (&dev->gadget.ep_list); + dev->gadget.ep0 = &dev->ep [0].ep; + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->ep0state = EP0_DISCONNECT; + dev->irqs = 0; + + for (i = 0; i < 4; i++) { + struct goku_ep *ep = &dev->ep[i]; + + ep->num = i; + ep->ep.name = names[i]; + ep->reg_fifo = &dev->regs->ep_fifo [i]; + ep->reg_status = &dev->regs->ep_status [i]; + ep->reg_mode = &dev->regs->ep_mode[i]; + + ep->ep.ops = &goku_ep_ops; + list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); + ep->dev = dev; + INIT_LIST_HEAD (&ep->queue); + + ep_reset(NULL, ep); + } + + dev->ep[0].reg_mode = NULL; + dev->ep[0].ep.maxpacket = MAX_EP0_SIZE; + list_del_init (&dev->ep[0].ep.ep_list); +} + +static void udc_reset(struct goku_udc *dev) +{ + struct goku_udc_regs __iomem *regs = dev->regs; + + writel(0, ®s->power_detect); + writel(0, ®s->int_enable); + readl(®s->int_enable); + dev->int_enable = 0; + + /* deassert reset, leave USB D+ at hi-Z (no pullup) + * don't let INT_PWRDETECT sequence begin + */ + udelay(250); + writel(PW_RESETB, ®s->power_detect); + readl(®s->int_enable); +} + +static void ep0_start(struct goku_udc *dev) +{ + struct goku_udc_regs __iomem *regs = dev->regs; + unsigned i; + + VDBG(dev, "%s\n", __func__); + + udc_reset(dev); + udc_reinit (dev); + //writel(MST_EOPB_ENA | MST_TIMEOUT_ENA, ®s->dma_master); + + /* hw handles set_address, set_feature, get_status; maybe more */ + writel( G_REQMODE_SET_INTF | G_REQMODE_GET_INTF + | G_REQMODE_SET_CONF | G_REQMODE_GET_CONF + | G_REQMODE_GET_DESC + | G_REQMODE_CLEAR_FEAT + , ®s->reqmode); + + for (i = 0; i < 4; i++) + dev->ep[i].irqs = 0; + + /* can't modify descriptors after writing UsbReady */ + for (i = 0; i < DESC_LEN; i++) + writel(0, ®s->descriptors[i]); + writel(0, ®s->UsbReady); + + /* expect ep0 requests when the host drops reset */ + writel(PW_RESETB | PW_PULLUP, ®s->power_detect); + dev->int_enable = INT_DEVWIDE | INT_EP0; + writel(dev->int_enable, &dev->regs->int_enable); + readl(®s->int_enable); + dev->gadget.speed = USB_SPEED_FULL; + dev->ep0state = EP0_IDLE; +} + +static void udc_enable(struct goku_udc *dev) +{ + /* start enumeration now, or after power detect irq */ + if (readl(&dev->regs->power_detect) & PW_DETECT) + ep0_start(dev); + else { + DBG(dev, "%s\n", __func__); + dev->int_enable = INT_PWRDETECT; + writel(dev->int_enable, &dev->regs->int_enable); + } +} + +/*-------------------------------------------------------------------------*/ + +/* keeping it simple: + * - one bus driver, initted first; + * - one function driver, initted second + */ + +/* when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ +static int goku_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct goku_udc *dev = to_goku_udc(g); + + /* hook up the driver */ + driver->driver.bus = NULL; + dev->driver = driver; + + /* + * then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + udc_enable(dev); + + return 0; +} + +static void +stop_activity(struct goku_udc *dev, struct usb_gadget_driver *driver) +{ + unsigned i; + + DBG (dev, "%s\n", __func__); + + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + + /* disconnect gadget driver after quiesceing hw and the driver */ + udc_reset (dev); + for (i = 0; i < 4; i++) + nuke(&dev->ep [i], -ESHUTDOWN); + + if (dev->driver) + udc_enable(dev); +} + +static int goku_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct goku_udc *dev = to_goku_udc(g); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->driver = NULL; + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static void ep0_setup(struct goku_udc *dev) +{ + struct goku_udc_regs __iomem *regs = dev->regs; + struct usb_ctrlrequest ctrl; + int tmp; + + /* read SETUP packet and enter DATA stage */ + ctrl.bRequestType = readl(®s->bRequestType); + ctrl.bRequest = readl(®s->bRequest); + ctrl.wValue = cpu_to_le16((readl(®s->wValueH) << 8) + | readl(®s->wValueL)); + ctrl.wIndex = cpu_to_le16((readl(®s->wIndexH) << 8) + | readl(®s->wIndexL)); + ctrl.wLength = cpu_to_le16((readl(®s->wLengthH) << 8) + | readl(®s->wLengthL)); + writel(0, ®s->SetupRecv); + + nuke(&dev->ep[0], 0); + dev->ep[0].stopped = 0; + if (likely(ctrl.bRequestType & USB_DIR_IN)) { + dev->ep[0].is_in = 1; + dev->ep0state = EP0_IN; + /* detect early status stages */ + writel(ICONTROL_STATUSNAK, &dev->regs->IntControl); + } else { + dev->ep[0].is_in = 0; + dev->ep0state = EP0_OUT; + + /* NOTE: CLEAR_FEATURE is done in software so that we can + * synchronize transfer restarts after bulk IN stalls. data + * won't even enter the fifo until the halt is cleared. + */ + switch (ctrl.bRequest) { + case USB_REQ_CLEAR_FEATURE: + switch (ctrl.bRequestType) { + case USB_RECIP_ENDPOINT: + tmp = le16_to_cpu(ctrl.wIndex) & 0x0f; + /* active endpoint */ + if (tmp > 3 || + (!dev->ep[tmp].ep.desc && tmp != 0)) + goto stall; + if (ctrl.wIndex & cpu_to_le16( + USB_DIR_IN)) { + if (!dev->ep[tmp].is_in) + goto stall; + } else { + if (dev->ep[tmp].is_in) + goto stall; + } + if (ctrl.wValue != cpu_to_le16( + USB_ENDPOINT_HALT)) + goto stall; + if (tmp) + goku_clear_halt(&dev->ep[tmp]); +succeed: + /* start ep0out status stage */ + writel(~(1<<0), ®s->EOP); + dev->ep[0].stopped = 1; + dev->ep0state = EP0_STATUS; + return; + case USB_RECIP_DEVICE: + /* device remote wakeup: always clear */ + if (ctrl.wValue != cpu_to_le16(1)) + goto stall; + VDBG(dev, "clear dev remote wakeup\n"); + goto succeed; + case USB_RECIP_INTERFACE: + goto stall; + default: /* pass to gadget driver */ + break; + } + break; + default: + break; + } + } + +#ifdef USB_TRACE + VDBG(dev, "SETUP %02x.%02x v%04x i%04x l%04x\n", + ctrl.bRequestType, ctrl.bRequest, + le16_to_cpu(ctrl.wValue), le16_to_cpu(ctrl.wIndex), + le16_to_cpu(ctrl.wLength)); +#endif + + /* hw wants to know when we're configured (or not) */ + dev->req_config = (ctrl.bRequest == USB_REQ_SET_CONFIGURATION + && ctrl.bRequestType == USB_RECIP_DEVICE); + if (unlikely(dev->req_config)) + dev->configured = (ctrl.wValue != cpu_to_le16(0)); + + /* delegate everything to the gadget driver. + * it may respond after this irq handler returns. + */ + spin_unlock (&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &ctrl); + spin_lock (&dev->lock); + if (unlikely(tmp < 0)) { +stall: +#ifdef USB_TRACE + VDBG(dev, "req %02x.%02x protocol STALL; err %d\n", + ctrl.bRequestType, ctrl.bRequest, tmp); +#endif + command(regs, COMMAND_STALL, 0); + dev->ep[0].stopped = 1; + dev->ep0state = EP0_STALL; + } + + /* expect at least one data or status stage irq */ +} + +#define ACK(irqbit) { \ + stat &= ~irqbit; \ + writel(~irqbit, ®s->int_status); \ + handled = 1; \ + } + +static irqreturn_t goku_irq(int irq, void *_dev) +{ + struct goku_udc *dev = _dev; + struct goku_udc_regs __iomem *regs = dev->regs; + struct goku_ep *ep; + u32 stat, handled = 0; + unsigned i, rescans = 5; + + spin_lock(&dev->lock); + +rescan: + stat = readl(®s->int_status) & dev->int_enable; + if (!stat) + goto done; + dev->irqs++; + + /* device-wide irqs */ + if (unlikely(stat & INT_DEVWIDE)) { + if (stat & INT_SYSERROR) { + ERROR(dev, "system error\n"); + stop_activity(dev, dev->driver); + stat = 0; + handled = 1; + // FIXME have a neater way to prevent re-enumeration + dev->driver = NULL; + goto done; + } + if (stat & INT_PWRDETECT) { + writel(~stat, ®s->int_status); + if (readl(&dev->regs->power_detect) & PW_DETECT) { + VDBG(dev, "connect\n"); + ep0_start(dev); + } else { + DBG(dev, "disconnect\n"); + if (dev->gadget.speed == USB_SPEED_FULL) + stop_activity(dev, dev->driver); + dev->ep0state = EP0_DISCONNECT; + dev->int_enable = INT_DEVWIDE; + writel(dev->int_enable, &dev->regs->int_enable); + } + stat = 0; + handled = 1; + goto done; + } + if (stat & INT_SUSPEND) { + ACK(INT_SUSPEND); + if (readl(®s->ep_status[0]) & EPxSTATUS_SUSPEND) { + switch (dev->ep0state) { + case EP0_DISCONNECT: + case EP0_SUSPEND: + goto pm_next; + default: + break; + } + DBG(dev, "USB suspend\n"); + dev->ep0state = EP0_SUSPEND; + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) { + spin_unlock(&dev->lock); + dev->driver->suspend(&dev->gadget); + spin_lock(&dev->lock); + } + } else { + if (dev->ep0state != EP0_SUSPEND) { + DBG(dev, "bogus USB resume %d\n", + dev->ep0state); + goto pm_next; + } + DBG(dev, "USB resume\n"); + dev->ep0state = EP0_IDLE; + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) { + spin_unlock(&dev->lock); + dev->driver->resume(&dev->gadget); + spin_lock(&dev->lock); + } + } + } +pm_next: + if (stat & INT_USBRESET) { /* hub reset done */ + ACK(INT_USBRESET); + INFO(dev, "USB reset done, gadget %s\n", + dev->driver->driver.name); + } + // and INT_ERR on some endpoint's crc/bitstuff/... problem + } + + /* progress ep0 setup, data, or status stages. + * no transition {EP0_STATUS, EP0_STALL} --> EP0_IDLE; saves irqs + */ + if (stat & INT_SETUP) { + ACK(INT_SETUP); + dev->ep[0].irqs++; + ep0_setup(dev); + } + if (stat & INT_STATUSNAK) { + ACK(INT_STATUSNAK|INT_ENDPOINT0); + if (dev->ep0state == EP0_IN) { + ep = &dev->ep[0]; + ep->irqs++; + nuke(ep, 0); + writel(~(1<<0), ®s->EOP); + dev->ep0state = EP0_STATUS; + } + } + if (stat & INT_ENDPOINT0) { + ACK(INT_ENDPOINT0); + ep = &dev->ep[0]; + ep->irqs++; + pio_advance(ep); + } + + /* dma completion */ + if (stat & INT_MSTRDEND) { /* IN */ + ACK(INT_MSTRDEND); + ep = &dev->ep[UDC_MSTRD_ENDPOINT]; + ep->irqs++; + dma_advance(dev, ep); + } + if (stat & INT_MSTWREND) { /* OUT */ + ACK(INT_MSTWREND); + ep = &dev->ep[UDC_MSTWR_ENDPOINT]; + ep->irqs++; + dma_advance(dev, ep); + } + if (stat & INT_MSTWRTMOUT) { /* OUT */ + ACK(INT_MSTWRTMOUT); + ep = &dev->ep[UDC_MSTWR_ENDPOINT]; + ep->irqs++; + ERROR(dev, "%s write timeout ?\n", ep->ep.name); + // reset dma? then dma_advance() + } + + /* pio */ + for (i = 1; i < 4; i++) { + u32 tmp = INT_EPxDATASET(i); + + if (!(stat & tmp)) + continue; + ep = &dev->ep[i]; + pio_advance(ep); + if (list_empty (&ep->queue)) + pio_irq_disable(dev, regs, i); + stat &= ~tmp; + handled = 1; + ep->irqs++; + } + + if (rescans--) + goto rescan; + +done: + (void)readl(®s->int_enable); + spin_unlock(&dev->lock); + if (stat) + DBG(dev, "unhandled irq status: %05x (%05x, %05x)\n", stat, + readl(®s->int_status), dev->int_enable); + return IRQ_RETVAL(handled); +} + +#undef ACK + +/*-------------------------------------------------------------------------*/ + +static void gadget_release(struct device *_dev) +{ + struct goku_udc *dev = dev_get_drvdata(_dev); + + kfree(dev); +} + +/* tear down the binding between this driver and the pci device */ + +static void goku_remove(struct pci_dev *pdev) +{ + struct goku_udc *dev = pci_get_drvdata(pdev); + + DBG(dev, "%s\n", __func__); + + usb_del_gadget_udc(&dev->gadget); + + BUG_ON(dev->driver); + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + remove_proc_entry(proc_node_name, NULL); +#endif + if (dev->regs) + udc_reset(dev); + if (dev->got_irq) + free_irq(pdev->irq, dev); + if (dev->regs) + iounmap(dev->regs); + if (dev->got_region) + release_mem_region(pci_resource_start (pdev, 0), + pci_resource_len (pdev, 0)); + if (dev->enabled) + pci_disable_device(pdev); + + pci_set_drvdata(pdev, NULL); + dev->regs = NULL; + + INFO(dev, "unbind\n"); +} + +/* wrap this driver around the specified pci device, but + * don't respond over USB until a gadget driver binds to us. + */ + +static int goku_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct goku_udc *dev = NULL; + unsigned long resource, len; + void __iomem *base = NULL; + int retval; + + if (!pdev->irq) { + printk(KERN_ERR "Check PCI %s IRQ setup!\n", pci_name(pdev)); + retval = -ENODEV; + goto err; + } + + /* alloc, and start init */ + dev = kzalloc (sizeof *dev, GFP_KERNEL); + if (dev == NULL){ + pr_debug("enomem %s\n", pci_name(pdev)); + retval = -ENOMEM; + goto err; + } + + spin_lock_init(&dev->lock); + dev->pdev = pdev; + dev->gadget.ops = &goku_ops; + dev->gadget.max_speed = USB_SPEED_FULL; + + /* the "gadget" abstracts/virtualizes the controller */ + dev->gadget.name = driver_name; + + /* now all the pci goodies ... */ + retval = pci_enable_device(pdev); + if (retval < 0) { + DBG(dev, "can't enable, %d\n", retval); + goto err; + } + dev->enabled = 1; + + resource = pci_resource_start(pdev, 0); + len = pci_resource_len(pdev, 0); + if (!request_mem_region(resource, len, driver_name)) { + DBG(dev, "controller already in use\n"); + retval = -EBUSY; + goto err; + } + dev->got_region = 1; + + base = ioremap_nocache(resource, len); + if (base == NULL) { + DBG(dev, "can't map memory\n"); + retval = -EFAULT; + goto err; + } + dev->regs = (struct goku_udc_regs __iomem *) base; + + pci_set_drvdata(pdev, dev); + INFO(dev, "%s\n", driver_desc); + INFO(dev, "version: " DRIVER_VERSION " %s\n", dmastr()); + INFO(dev, "irq %d, pci mem %p\n", pdev->irq, base); + + /* init to known state, then setup irqs */ + udc_reset(dev); + udc_reinit (dev); + if (request_irq(pdev->irq, goku_irq, IRQF_SHARED, + driver_name, dev) != 0) { + DBG(dev, "request interrupt %d failed\n", pdev->irq); + retval = -EBUSY; + goto err; + } + dev->got_irq = 1; + if (use_dma) + pci_set_master(pdev); + + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + proc_create_data(proc_node_name, 0, NULL, &udc_proc_fops, dev); +#endif + + retval = usb_add_gadget_udc_release(&pdev->dev, &dev->gadget, + gadget_release); + if (retval) + goto err; + + return 0; + +err: + if (dev) + goku_remove (pdev); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +static const struct pci_device_id pci_ids[] = { { + .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class_mask = ~0, + .vendor = 0x102f, /* Toshiba */ + .device = 0x0107, /* this UDC */ + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + +}, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE (pci, pci_ids); + +static struct pci_driver goku_pci_driver = { + .name = (char *) driver_name, + .id_table = pci_ids, + + .probe = goku_probe, + .remove = goku_remove, + + /* FIXME add power management support */ +}; + +module_pci_driver(goku_pci_driver); diff --git a/drivers/usb/gadget/goku_udc.h b/drivers/usb/gadget/goku_udc.h new file mode 100644 index 00000000..86d2adaf --- /dev/null +++ b/drivers/usb/gadget/goku_udc.h @@ -0,0 +1,292 @@ +/* + * Toshiba TC86C001 ("Goku-S") USB Device Controller driver + * + * Copyright (C) 2000-2002 Lineo + * by Stuart Lynne, Tom Rushworth, and Bruce Balden + * Copyright (C) 2002 Toshiba Corporation + * Copyright (C) 2003 MontaVista Software (source@mvista.com) + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +/* + * PCI BAR 0 points to these registers. + */ +struct goku_udc_regs { + /* irq management */ + u32 int_status; /* 0x000 */ + u32 int_enable; +#define INT_SUSPEND 0x00001 /* or resume */ +#define INT_USBRESET 0x00002 +#define INT_ENDPOINT0 0x00004 +#define INT_SETUP 0x00008 +#define INT_STATUS 0x00010 +#define INT_STATUSNAK 0x00020 +#define INT_EPxDATASET(n) (0x00020 << (n)) /* 0 < n < 4 */ +# define INT_EP1DATASET 0x00040 +# define INT_EP2DATASET 0x00080 +# define INT_EP3DATASET 0x00100 +#define INT_EPnNAK(n) (0x00100 < (n)) /* 0 < n < 4 */ +# define INT_EP1NAK 0x00200 +# define INT_EP2NAK 0x00400 +# define INT_EP3NAK 0x00800 +#define INT_SOF 0x01000 +#define INT_ERR 0x02000 +#define INT_MSTWRSET 0x04000 +#define INT_MSTWREND 0x08000 +#define INT_MSTWRTMOUT 0x10000 +#define INT_MSTRDEND 0x20000 +#define INT_SYSERROR 0x40000 +#define INT_PWRDETECT 0x80000 + +#define INT_DEVWIDE \ + (INT_PWRDETECT|INT_SYSERROR/*|INT_ERR*/|INT_USBRESET|INT_SUSPEND) +#define INT_EP0 \ + (INT_SETUP|INT_ENDPOINT0/*|INT_STATUS*/|INT_STATUSNAK) + + u32 dma_master; +#define MST_EOPB_DIS 0x0800 +#define MST_EOPB_ENA 0x0400 +#define MST_TIMEOUT_DIS 0x0200 +#define MST_TIMEOUT_ENA 0x0100 +#define MST_RD_EOPB 0x0080 /* write-only */ +#define MST_RD_RESET 0x0040 +#define MST_WR_RESET 0x0020 +#define MST_RD_ENA 0x0004 /* 1:start, 0:ignore */ +#define MST_WR_ENA 0x0002 /* 1:start, 0:ignore */ +#define MST_CONNECTION 0x0001 /* 0 for ep1out/ep2in */ + +#define MST_R_BITS (MST_EOPB_DIS|MST_EOPB_ENA \ + |MST_RD_ENA|MST_RD_RESET) +#define MST_W_BITS (MST_TIMEOUT_DIS|MST_TIMEOUT_ENA \ + |MST_WR_ENA|MST_WR_RESET) +#define MST_RW_BITS (MST_R_BITS|MST_W_BITS \ + |MST_CONNECTION) + +/* these values assume (dma_master & MST_CONNECTION) == 0 */ +#define UDC_MSTWR_ENDPOINT 1 +#define UDC_MSTRD_ENDPOINT 2 + + /* dma master write */ + u32 out_dma_start; + u32 out_dma_end; + u32 out_dma_current; + + /* dma master read */ + u32 in_dma_start; + u32 in_dma_end; + u32 in_dma_current; + + u32 power_detect; +#define PW_DETECT 0x04 +#define PW_RESETB 0x02 +#define PW_PULLUP 0x01 + + u8 _reserved0 [0x1d8]; + + /* endpoint registers */ + u32 ep_fifo [4]; /* 0x200 */ + u8 _reserved1 [0x10]; + u32 ep_mode [4]; /* only 1-3 valid */ + u8 _reserved2 [0x10]; + + u32 ep_status [4]; +#define EPxSTATUS_TOGGLE 0x40 +#define EPxSTATUS_SUSPEND 0x20 +#define EPxSTATUS_EP_MASK (0x07<<2) +# define EPxSTATUS_EP_READY (0<<2) +# define EPxSTATUS_EP_DATAIN (1<<2) +# define EPxSTATUS_EP_FULL (2<<2) +# define EPxSTATUS_EP_TX_ERR (3<<2) +# define EPxSTATUS_EP_RX_ERR (4<<2) +# define EPxSTATUS_EP_BUSY (5<<2) +# define EPxSTATUS_EP_STALL (6<<2) +# define EPxSTATUS_EP_INVALID (7<<2) +#define EPxSTATUS_FIFO_DISABLE 0x02 +#define EPxSTATUS_STAGE_ERROR 0x01 + + u8 _reserved3 [0x10]; + u32 EPxSizeLA[4]; +#define PACKET_ACTIVE (1<<7) +#define DATASIZE 0x7f + u8 _reserved3a [0x10]; + u32 EPxSizeLB[4]; /* only 1,2 valid */ + u8 _reserved3b [0x10]; + u32 EPxSizeHA[4]; /* only 1-3 valid */ + u8 _reserved3c [0x10]; + u32 EPxSizeHB[4]; /* only 1,2 valid */ + u8 _reserved4[0x30]; + + /* SETUP packet contents */ + u32 bRequestType; /* 0x300 */ + u32 bRequest; + u32 wValueL; + u32 wValueH; + u32 wIndexL; + u32 wIndexH; + u32 wLengthL; + u32 wLengthH; + + /* command interaction/handshaking */ + u32 SetupRecv; /* 0x320 */ + u32 CurrConfig; + u32 StdRequest; + u32 Request; + u32 DataSet; +#define DATASET_A(epnum) (1<<(2*(epnum))) +#define DATASET_B(epnum) (2<<(2*(epnum))) +#define DATASET_AB(epnum) (3<<(2*(epnum))) + u8 _reserved5[4]; + + u32 UsbState; +#define USBSTATE_CONFIGURED 0x04 +#define USBSTATE_ADDRESSED 0x02 +#define USBSTATE_DEFAULT 0x01 + + u32 EOP; + + u32 Command; /* 0x340 */ +#define COMMAND_SETDATA0 2 +#define COMMAND_RESET 3 +#define COMMAND_STALL 4 +#define COMMAND_INVALID 5 +#define COMMAND_FIFO_DISABLE 7 +#define COMMAND_FIFO_ENABLE 8 +#define COMMAND_INIT_DESCRIPTOR 9 +#define COMMAND_FIFO_CLEAR 10 /* also stall */ +#define COMMAND_STALL_CLEAR 11 +#define COMMAND_EP(n) ((n) << 4) + + u32 EPxSingle; + u8 _reserved6[4]; + u32 EPxBCS; + u8 _reserved7[8]; + u32 IntControl; +#define ICONTROL_STATUSNAK 1 + u8 _reserved8[4]; + + u32 reqmode; // 0x360 standard request mode, low 8 bits +#define G_REQMODE_SET_INTF (1<<7) +#define G_REQMODE_GET_INTF (1<<6) +#define G_REQMODE_SET_CONF (1<<5) +#define G_REQMODE_GET_CONF (1<<4) +#define G_REQMODE_GET_DESC (1<<3) +#define G_REQMODE_SET_FEAT (1<<2) +#define G_REQMODE_CLEAR_FEAT (1<<1) +#define G_REQMODE_GET_STATUS (1<<0) + + u32 ReqMode; + u8 _reserved9[0x18]; + u32 PortStatus; /* 0x380 */ + u8 _reserved10[8]; + u32 address; + u32 buff_test; + u8 _reserved11[4]; + u32 UsbReady; + u8 _reserved12[4]; + u32 SetDescStall; /* 0x3a0 */ + u8 _reserved13[0x45c]; + + /* hardware could handle limited GET_DESCRIPTOR duties */ +#define DESC_LEN 0x80 + u32 descriptors[DESC_LEN]; /* 0x800 */ + u8 _reserved14[0x600]; + +} __attribute__ ((packed)); + +#define MAX_FIFO_SIZE 64 +#define MAX_EP0_SIZE 8 /* ep0 fifo is bigger, though */ + + +/*-------------------------------------------------------------------------*/ + +/* DRIVER DATA STRUCTURES and UTILITIES */ + +struct goku_ep { + struct usb_ep ep; + struct goku_udc *dev; + unsigned long irqs; + + unsigned num:8, + dma:1, + is_in:1, + stopped:1; + + /* analogous to a host-side qh */ + struct list_head queue; + + u32 __iomem *reg_fifo; + u32 __iomem *reg_mode; + u32 __iomem *reg_status; +}; + +struct goku_request { + struct usb_request req; + struct list_head queue; + + unsigned mapped:1; +}; + +enum ep0state { + EP0_DISCONNECT, /* no host */ + EP0_IDLE, /* between STATUS ack and SETUP report */ + EP0_IN, EP0_OUT, /* data stage */ + EP0_STATUS, /* status stage */ + EP0_STALL, /* data or status stages */ + EP0_SUSPEND, /* usb suspend */ +}; + +struct goku_udc { + /* each pci device provides one gadget, several endpoints */ + struct usb_gadget gadget; + spinlock_t lock; + struct goku_ep ep[4]; + struct usb_gadget_driver *driver; + + enum ep0state ep0state; + unsigned got_irq:1, + got_region:1, + req_config:1, + configured:1, + enabled:1; + + /* pci state used to access those endpoints */ + struct pci_dev *pdev; + struct goku_udc_regs __iomem *regs; + u32 int_enable; + + /* statistics... */ + unsigned long irqs; +}; +#define to_goku_udc(g) (container_of((g), struct goku_udc, gadget)) + +/*-------------------------------------------------------------------------*/ + +#define xprintk(dev,level,fmt,args...) \ + printk(level "%s %s: " fmt , driver_name , \ + pci_name(dev->pdev) , ## args) + +#ifdef DEBUG +#define DBG(dev,fmt,args...) \ + xprintk(dev , KERN_DEBUG , fmt , ## args) +#else +#define DBG(dev,fmt,args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(dev,fmt,args...) \ + do { } while (0) +#endif /* VERBOSE */ + +#define ERROR(dev,fmt,args...) \ + xprintk(dev , KERN_ERR , fmt , ## args) +#define WARNING(dev,fmt,args...) \ + xprintk(dev , KERN_WARNING , fmt , ## args) +#define INFO(dev,fmt,args...) \ + xprintk(dev , KERN_INFO , fmt , ## args) + diff --git a/drivers/usb/gadget/hid.c b/drivers/usb/gadget/hid.c new file mode 100644 index 00000000..c36260ea --- /dev/null +++ b/drivers/usb/gadget/hid.c @@ -0,0 +1,266 @@ +/* + * hid.c -- HID Composite driver + * + * Based on multi.c + * + * Copyright (C) 2010 Fabien Chouteau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +#include +#include +#include +#include +#include + +#include "gadget_chips.h" +#define DRIVER_DESC "HID Gadget" +#define DRIVER_VERSION "2010/03/16" + +/*-------------------------------------------------------------------------*/ + +#define HIDG_VENDOR_NUM 0x0525 /* XXX NetChip */ +#define HIDG_PRODUCT_NUM 0xa4ac /* Linux-USB HID gadget */ + +/*-------------------------------------------------------------------------*/ + +/* + * kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "f_hid.c" + + +struct hidg_func_node { + struct list_head node; + struct hidg_func_descriptor *func; +}; + +static LIST_HEAD(hidg_func_list); + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + + /* .bDeviceClass = USB_CLASS_COMM, */ + /* .bDeviceSubClass = 0, */ + /* .bDeviceProtocol = 0, */ + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id can be overridden by module parameters. */ + .idVendor = cpu_to_le16(HIDG_VENDOR_NUM), + .idProduct = cpu_to_le16(HIDG_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + + +/* string IDs are assigned dynamically */ +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + + + +/****************************** Configurations ******************************/ + +static int __init do_config(struct usb_configuration *c) +{ + struct hidg_func_node *e; + int func = 0, status = 0; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + list_for_each_entry(e, &hidg_func_list, node) { + status = hidg_bind_config(c, e->func, func++); + if (status) + break; + } + + return status; +} + +static struct usb_configuration config_driver = { + .label = "HID Gadget", + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/****************************** Gadget Bind ******************************/ + +static int __init hid_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct list_head *tmp; + int status, funcs = 0; + + list_for_each(tmp, &hidg_func_list) + funcs++; + + if (!funcs) + return -ENODEV; + + /* set up HID */ + status = ghid_setup(cdev->gadget, funcs); + if (status < 0) + return status; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + return status; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + /* register our configuration */ + status = usb_add_config(cdev, &config_driver, do_config); + if (status < 0) + return status; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + + return 0; +} + +static int __exit hid_unbind(struct usb_composite_dev *cdev) +{ + ghid_cleanup(); + return 0; +} + +static int __init hidg_plat_driver_probe(struct platform_device *pdev) +{ + struct hidg_func_descriptor *func = pdev->dev.platform_data; + struct hidg_func_node *entry; + + if (!func) { + dev_err(&pdev->dev, "Platform data missing\n"); + return -ENODEV; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->func = func; + list_add_tail(&entry->node, &hidg_func_list); + + return 0; +} + +static int hidg_plat_driver_remove(struct platform_device *pdev) +{ + struct hidg_func_node *e, *n; + + list_for_each_entry_safe(e, n, &hidg_func_list, node) { + list_del(&e->node); + kfree(e); + } + + return 0; +} + + +/****************************** Some noise ******************************/ + + +static __refdata struct usb_composite_driver hidg_driver = { + .name = "g_hid", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = hid_bind, + .unbind = __exit_p(hid_unbind), +}; + +static struct platform_driver hidg_plat_driver = { + .remove = hidg_plat_driver_remove, + .driver = { + .owner = THIS_MODULE, + .name = "hidg", + }, +}; + + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Fabien Chouteau, Peter Korsgaard"); +MODULE_LICENSE("GPL"); + +static int __init hidg_init(void) +{ + int status; + + status = platform_driver_probe(&hidg_plat_driver, + hidg_plat_driver_probe); + if (status < 0) + return status; + + status = usb_composite_probe(&hidg_driver); + if (status < 0) + platform_driver_unregister(&hidg_plat_driver); + + return status; +} +module_init(hidg_init); + +static void __exit hidg_cleanup(void) +{ + platform_driver_unregister(&hidg_plat_driver); + usb_composite_unregister(&hidg_driver); +} +module_exit(hidg_cleanup); diff --git a/drivers/usb/gadget/imx_udc.c b/drivers/usb/gadget/imx_udc.c new file mode 100644 index 00000000..9b2d24e4 --- /dev/null +++ b/drivers/usb/gadget/imx_udc.c @@ -0,0 +1,1544 @@ +/* + * driver/usb/gadget/imx_udc.c + * + * Copyright (C) 2005 Mike Lee + * Copyright (C) 2008 Darius Augulis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "imx_udc.h" + +static const char driver_name[] = "imx_udc"; +static const char ep0name[] = "ep0"; + +void ep0_chg_stat(const char *label, struct imx_udc_struct *imx_usb, + enum ep0_state stat); + +/******************************************************************************* + * IMX UDC hardware related functions + ******************************************************************************* + */ + +void imx_udc_enable(struct imx_udc_struct *imx_usb) +{ + int temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp | CTRL_FE_ENA | CTRL_AFE_ENA, + imx_usb->base + USB_CTRL); + imx_usb->gadget.speed = USB_SPEED_FULL; +} + +void imx_udc_disable(struct imx_udc_struct *imx_usb) +{ + int temp = __raw_readl(imx_usb->base + USB_CTRL); + + __raw_writel(temp & ~(CTRL_FE_ENA | CTRL_AFE_ENA), + imx_usb->base + USB_CTRL); + + ep0_chg_stat(__func__, imx_usb, EP0_IDLE); + imx_usb->gadget.speed = USB_SPEED_UNKNOWN; +} + +void imx_udc_reset(struct imx_udc_struct *imx_usb) +{ + int temp = __raw_readl(imx_usb->base + USB_ENAB); + + /* set RST bit */ + __raw_writel(temp | ENAB_RST, imx_usb->base + USB_ENAB); + + /* wait RST bit to clear */ + do {} while (__raw_readl(imx_usb->base + USB_ENAB) & ENAB_RST); + + /* wait CFG bit to assert */ + do {} while (!(__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG)); + + /* udc module is now ready */ +} + +void imx_udc_config(struct imx_udc_struct *imx_usb) +{ + u8 ep_conf[5]; + u8 i, j, cfg; + struct imx_ep_struct *imx_ep; + + /* wait CFG bit to assert */ + do {} while (!(__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG)); + + /* Download the endpoint buffer for endpoint 0. */ + for (j = 0; j < 5; j++) { + i = (j == 2 ? imx_usb->imx_ep[0].fifosize : 0x00); + __raw_writeb(i, imx_usb->base + USB_DDAT); + do {} while (__raw_readl(imx_usb->base + USB_DADR) & DADR_BSY); + } + + /* Download the endpoint buffers for endpoints 1-5. + * We specify two configurations, one interface + */ + for (cfg = 1; cfg < 3; cfg++) { + for (i = 1; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + /* EP no | Config no */ + ep_conf[0] = (i << 4) | (cfg << 2); + /* Type | Direction */ + ep_conf[1] = (imx_ep->bmAttributes << 3) | + (EP_DIR(imx_ep) << 2); + /* Max packet size */ + ep_conf[2] = imx_ep->fifosize; + /* TRXTYP */ + ep_conf[3] = 0xC0; + /* FIFO no */ + ep_conf[4] = i; + + D_INI(imx_usb->dev, + "<%s> ep%d_conf[%d]:" + "[%02x-%02x-%02x-%02x-%02x]\n", + __func__, i, cfg, + ep_conf[0], ep_conf[1], ep_conf[2], + ep_conf[3], ep_conf[4]); + + for (j = 0; j < 5; j++) { + __raw_writeb(ep_conf[j], + imx_usb->base + USB_DDAT); + do {} while (__raw_readl(imx_usb->base + + USB_DADR) + & DADR_BSY); + } + } + } + + /* wait CFG bit to clear */ + do {} while (__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG); +} + +void imx_udc_init_irq(struct imx_udc_struct *imx_usb) +{ + int i; + + /* Mask and clear all irqs */ + __raw_writel(0xFFFFFFFF, imx_usb->base + USB_MASK); + __raw_writel(0xFFFFFFFF, imx_usb->base + USB_INTR); + for (i = 0; i < IMX_USB_NB_EP; i++) { + __raw_writel(0x1FF, imx_usb->base + USB_EP_MASK(i)); + __raw_writel(0x1FF, imx_usb->base + USB_EP_INTR(i)); + } + + /* Enable USB irqs */ + __raw_writel(INTR_MSOF | INTR_FRAME_MATCH, imx_usb->base + USB_MASK); + + /* Enable EP0 irqs */ + __raw_writel(0x1FF & ~(EPINTR_DEVREQ | EPINTR_MDEVREQ | EPINTR_EOT + | EPINTR_EOF | EPINTR_FIFO_EMPTY | EPINTR_FIFO_FULL), + imx_usb->base + USB_EP_MASK(0)); +} + +void imx_udc_init_ep(struct imx_udc_struct *imx_usb) +{ + int i, max, temp; + struct imx_ep_struct *imx_ep; + for (i = 0; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + switch (imx_ep->fifosize) { + case 8: + max = 0; + break; + case 16: + max = 1; + break; + case 32: + max = 2; + break; + case 64: + max = 3; + break; + default: + max = 1; + break; + } + temp = (EP_DIR(imx_ep) << 7) | (max << 5) + | (imx_ep->bmAttributes << 3); + __raw_writel(temp, imx_usb->base + USB_EP_STAT(i)); + __raw_writel(temp | EPSTAT_FLUSH, + imx_usb->base + USB_EP_STAT(i)); + D_INI(imx_usb->dev, "<%s> ep%d_stat %08x\n", __func__, i, + __raw_readl(imx_usb->base + USB_EP_STAT(i))); + } +} + +void imx_udc_init_fifo(struct imx_udc_struct *imx_usb) +{ + int i, temp; + struct imx_ep_struct *imx_ep; + for (i = 0; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + + /* Fifo control */ + temp = EP_DIR(imx_ep) ? 0x0B000000 : 0x0F000000; + __raw_writel(temp, imx_usb->base + USB_EP_FCTRL(i)); + D_INI(imx_usb->dev, "<%s> ep%d_fctrl %08x\n", __func__, i, + __raw_readl(imx_usb->base + USB_EP_FCTRL(i))); + + /* Fifo alarm */ + temp = (i ? imx_ep->fifosize / 2 : 0); + __raw_writel(temp, imx_usb->base + USB_EP_FALRM(i)); + D_INI(imx_usb->dev, "<%s> ep%d_falrm %08x\n", __func__, i, + __raw_readl(imx_usb->base + USB_EP_FALRM(i))); + } +} + +static void imx_udc_init(struct imx_udc_struct *imx_usb) +{ + /* Reset UDC */ + imx_udc_reset(imx_usb); + + /* Download config to enpoint buffer */ + imx_udc_config(imx_usb); + + /* Setup interrups */ + imx_udc_init_irq(imx_usb); + + /* Setup endpoints */ + imx_udc_init_ep(imx_usb); + + /* Setup fifos */ + imx_udc_init_fifo(imx_usb); +} + +void imx_ep_irq_enable(struct imx_ep_struct *imx_ep) +{ + + int i = EP_NO(imx_ep); + + __raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_MASK(i)); + __raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_INTR(i)); + __raw_writel(0x1FF & ~(EPINTR_EOT | EPINTR_EOF), + imx_ep->imx_usb->base + USB_EP_MASK(i)); +} + +void imx_ep_irq_disable(struct imx_ep_struct *imx_ep) +{ + + int i = EP_NO(imx_ep); + + __raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_MASK(i)); + __raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_INTR(i)); +} + +int imx_ep_empty(struct imx_ep_struct *imx_ep) +{ + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + + return __raw_readl(imx_usb->base + USB_EP_FSTAT(EP_NO(imx_ep))) + & FSTAT_EMPTY; +} + +unsigned imx_fifo_bcount(struct imx_ep_struct *imx_ep) +{ + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + + return (__raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))) + & EPSTAT_BCOUNT) >> 16; +} + +void imx_flush(struct imx_ep_struct *imx_ep) +{ + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + + int temp = __raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); + __raw_writel(temp | EPSTAT_FLUSH, + imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); +} + +void imx_ep_stall(struct imx_ep_struct *imx_ep) +{ + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + int temp, i; + + D_ERR(imx_usb->dev, + "<%s> Forced stall on %s\n", __func__, imx_ep->ep.name); + + imx_flush(imx_ep); + + /* Special care for ep0 */ + if (!EP_NO(imx_ep)) { + temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp | CTRL_CMDOVER | CTRL_CMDERROR, + imx_usb->base + USB_CTRL); + do { } while (__raw_readl(imx_usb->base + USB_CTRL) + & CTRL_CMDOVER); + temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp & ~CTRL_CMDERROR, imx_usb->base + USB_CTRL); + } + else { + temp = __raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); + __raw_writel(temp | EPSTAT_STALL, + imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); + + for (i = 0; i < 100; i ++) { + temp = __raw_readl(imx_usb->base + + USB_EP_STAT(EP_NO(imx_ep))); + if (!(temp & EPSTAT_STALL)) + break; + udelay(20); + } + if (i == 100) + D_ERR(imx_usb->dev, "<%s> Non finished stall on %s\n", + __func__, imx_ep->ep.name); + } +} + +static int imx_udc_get_frame(struct usb_gadget *_gadget) +{ + struct imx_udc_struct *imx_usb = container_of(_gadget, + struct imx_udc_struct, gadget); + + return __raw_readl(imx_usb->base + USB_FRAME) & 0x7FF; +} + +static int imx_udc_wakeup(struct usb_gadget *_gadget) +{ + return 0; +} + +/******************************************************************************* + * USB request control functions + ******************************************************************************* + */ + +static void ep_add_request(struct imx_ep_struct *imx_ep, + struct imx_request *req) +{ + if (unlikely(!req)) + return; + + req->in_use = 1; + list_add_tail(&req->queue, &imx_ep->queue); +} + +static void ep_del_request(struct imx_ep_struct *imx_ep, + struct imx_request *req) +{ + if (unlikely(!req)) + return; + + list_del_init(&req->queue); + req->in_use = 0; +} + +static void done(struct imx_ep_struct *imx_ep, + struct imx_request *req, int status) +{ + ep_del_request(imx_ep, req); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + D_ERR(imx_ep->imx_usb->dev, + "<%s> complete %s req %p stat %d len %u/%u\n", __func__, + imx_ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + req->req.complete(&imx_ep->ep, &req->req); +} + +static void nuke(struct imx_ep_struct *imx_ep, int status) +{ + struct imx_request *req; + + while (!list_empty(&imx_ep->queue)) { + req = list_entry(imx_ep->queue.next, struct imx_request, queue); + done(imx_ep, req, status); + } +} + +/******************************************************************************* + * Data tansfer over USB functions + ******************************************************************************* + */ +static int read_packet(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + u8 *buf; + int bytes_ep, bufferspace, count, i; + + bytes_ep = imx_fifo_bcount(imx_ep); + bufferspace = req->req.length - req->req.actual; + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + if (unlikely(imx_ep_empty(imx_ep))) + count = 0; /* zlp */ + else + count = min(bytes_ep, bufferspace); + + for (i = count; i > 0; i--) + *buf++ = __raw_readb(imx_ep->imx_usb->base + + USB_EP_FDAT0(EP_NO(imx_ep))); + req->req.actual += count; + + return count; +} + +static int write_packet(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + u8 *buf; + int length, count, temp; + + if (unlikely(__raw_readl(imx_ep->imx_usb->base + + USB_EP_STAT(EP_NO(imx_ep))) & EPSTAT_ZLPS)) { + D_TRX(imx_ep->imx_usb->dev, "<%s> zlp still queued in EP %s\n", + __func__, imx_ep->ep.name); + return -1; + } + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + length = min(req->req.length - req->req.actual, (u32)imx_ep->fifosize); + + if (imx_fifo_bcount(imx_ep) + length > imx_ep->fifosize) { + D_TRX(imx_ep->imx_usb->dev, "<%s> packet overfill %s fifo\n", + __func__, imx_ep->ep.name); + return -1; + } + + req->req.actual += length; + count = length; + + if (!count && req->req.zero) { /* zlp */ + temp = __raw_readl(imx_ep->imx_usb->base + + USB_EP_STAT(EP_NO(imx_ep))); + __raw_writel(temp | EPSTAT_ZLPS, imx_ep->imx_usb->base + + USB_EP_STAT(EP_NO(imx_ep))); + D_TRX(imx_ep->imx_usb->dev, "<%s> zero packet\n", __func__); + return 0; + } + + while (count--) { + if (count == 0) { /* last byte */ + temp = __raw_readl(imx_ep->imx_usb->base + + USB_EP_FCTRL(EP_NO(imx_ep))); + __raw_writel(temp | FCTRL_WFR, imx_ep->imx_usb->base + + USB_EP_FCTRL(EP_NO(imx_ep))); + } + __raw_writeb(*buf++, + imx_ep->imx_usb->base + USB_EP_FDAT0(EP_NO(imx_ep))); + } + + return length; +} + +static int read_fifo(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + int bytes = 0, + count, + completed = 0; + + while (__raw_readl(imx_ep->imx_usb->base + USB_EP_FSTAT(EP_NO(imx_ep))) + & FSTAT_FR) { + count = read_packet(imx_ep, req); + bytes += count; + + completed = (count != imx_ep->fifosize); + if (completed || req->req.actual == req->req.length) { + completed = 1; + break; + } + } + + if (completed || !req->req.length) { + done(imx_ep, req, 0); + D_REQ(imx_ep->imx_usb->dev, "<%s> %s req<%p> %s\n", + __func__, imx_ep->ep.name, req, + completed ? "completed" : "not completed"); + if (!EP_NO(imx_ep)) + ep0_chg_stat(__func__, imx_ep->imx_usb, EP0_IDLE); + } + + D_TRX(imx_ep->imx_usb->dev, "<%s> bytes read: %d\n", __func__, bytes); + + return completed; +} + +static int write_fifo(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + int bytes = 0, + count, + completed = 0; + + while (!completed) { + count = write_packet(imx_ep, req); + if (count < 0) + break; /* busy */ + bytes += count; + + /* last packet "must be" short (or a zlp) */ + completed = (count != imx_ep->fifosize); + + if (unlikely(completed)) { + done(imx_ep, req, 0); + D_REQ(imx_ep->imx_usb->dev, "<%s> %s req<%p> %s\n", + __func__, imx_ep->ep.name, req, + completed ? "completed" : "not completed"); + if (!EP_NO(imx_ep)) + ep0_chg_stat(__func__, + imx_ep->imx_usb, EP0_IDLE); + } + } + + D_TRX(imx_ep->imx_usb->dev, "<%s> bytes sent: %d\n", __func__, bytes); + + return completed; +} + +/******************************************************************************* + * Endpoint handlers + ******************************************************************************* + */ +static int handle_ep(struct imx_ep_struct *imx_ep) +{ + struct imx_request *req; + int completed = 0; + + do { + if (!list_empty(&imx_ep->queue)) + req = list_entry(imx_ep->queue.next, + struct imx_request, queue); + else { + D_REQ(imx_ep->imx_usb->dev, "<%s> no request on %s\n", + __func__, imx_ep->ep.name); + return 0; + } + + if (EP_DIR(imx_ep)) /* to host */ + completed = write_fifo(imx_ep, req); + else /* to device */ + completed = read_fifo(imx_ep, req); + + dump_ep_stat(__func__, imx_ep); + + } while (completed); + + return 0; +} + +static int handle_ep0(struct imx_ep_struct *imx_ep) +{ + struct imx_request *req = NULL; + int ret = 0; + + if (!list_empty(&imx_ep->queue)) { + req = list_entry(imx_ep->queue.next, struct imx_request, queue); + + switch (imx_ep->imx_usb->ep0state) { + + case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR */ + write_fifo(imx_ep, req); + break; + case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR */ + read_fifo(imx_ep, req); + break; + default: + D_EP0(imx_ep->imx_usb->dev, + "<%s> ep0 i/o, odd state %d\n", + __func__, imx_ep->imx_usb->ep0state); + ep_del_request(imx_ep, req); + ret = -EL2HLT; + break; + } + } + + else + D_ERR(imx_ep->imx_usb->dev, "<%s> no request on %s\n", + __func__, imx_ep->ep.name); + + return ret; +} + +static void handle_ep0_devreq(struct imx_udc_struct *imx_usb) +{ + struct imx_ep_struct *imx_ep = &imx_usb->imx_ep[0]; + union { + struct usb_ctrlrequest r; + u8 raw[8]; + u32 word[2]; + } u; + int temp, i; + + nuke(imx_ep, -EPROTO); + + /* read SETUP packet */ + for (i = 0; i < 2; i++) { + if (imx_ep_empty(imx_ep)) { + D_ERR(imx_usb->dev, + "<%s> no setup packet received\n", __func__); + goto stall; + } + u.word[i] = __raw_readl(imx_usb->base + + USB_EP_FDAT(EP_NO(imx_ep))); + } + + temp = imx_ep_empty(imx_ep); + while (!imx_ep_empty(imx_ep)) { + i = __raw_readl(imx_usb->base + USB_EP_FDAT(EP_NO(imx_ep))); + D_ERR(imx_usb->dev, + "<%s> wrong to have extra bytes for setup : 0x%08x\n", + __func__, i); + } + if (!temp) + goto stall; + + le16_to_cpus(&u.r.wValue); + le16_to_cpus(&u.r.wIndex); + le16_to_cpus(&u.r.wLength); + + D_REQ(imx_usb->dev, "<%s> SETUP %02x.%02x v%04x i%04x l%04x\n", + __func__, u.r.bRequestType, u.r.bRequest, + u.r.wValue, u.r.wIndex, u.r.wLength); + + if (imx_usb->set_config) { + /* NACK the host by using CMDOVER */ + temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp | CTRL_CMDOVER, imx_usb->base + USB_CTRL); + + D_ERR(imx_usb->dev, + "<%s> set config req is pending, NACK the host\n", + __func__); + return; + } + + if (u.r.bRequestType & USB_DIR_IN) + ep0_chg_stat(__func__, imx_usb, EP0_IN_DATA_PHASE); + else + ep0_chg_stat(__func__, imx_usb, EP0_OUT_DATA_PHASE); + + i = imx_usb->driver->setup(&imx_usb->gadget, &u.r); + if (i < 0) { + D_ERR(imx_usb->dev, "<%s> device setup error %d\n", + __func__, i); + goto stall; + } + + return; +stall: + D_ERR(imx_usb->dev, "<%s> protocol STALL\n", __func__); + imx_ep_stall(imx_ep); + ep0_chg_stat(__func__, imx_usb, EP0_STALL); + return; +} + +/******************************************************************************* + * USB gadget callback functions + ******************************************************************************* + */ + +static int imx_ep_enable(struct usb_ep *usb_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct imx_ep_struct *imx_ep = container_of(usb_ep, + struct imx_ep_struct, ep); + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + unsigned long flags; + + if (!usb_ep + || !desc + || !EP_NO(imx_ep) + || desc->bDescriptorType != USB_DT_ENDPOINT + || imx_ep->bEndpointAddress != desc->bEndpointAddress) { + D_ERR(imx_usb->dev, + "<%s> bad ep or descriptor\n", __func__); + return -EINVAL; + } + + if (imx_ep->bmAttributes != desc->bmAttributes) { + D_ERR(imx_usb->dev, + "<%s> %s type mismatch\n", __func__, usb_ep->name); + return -EINVAL; + } + + if (imx_ep->fifosize < usb_endpoint_maxp(desc)) { + D_ERR(imx_usb->dev, + "<%s> bad %s maxpacket\n", __func__, usb_ep->name); + return -ERANGE; + } + + if (!imx_usb->driver || imx_usb->gadget.speed == USB_SPEED_UNKNOWN) { + D_ERR(imx_usb->dev, "<%s> bogus device state\n", __func__); + return -ESHUTDOWN; + } + + local_irq_save(flags); + + imx_ep->stopped = 0; + imx_flush(imx_ep); + imx_ep_irq_enable(imx_ep); + + local_irq_restore(flags); + + D_EPX(imx_usb->dev, "<%s> ENABLED %s\n", __func__, usb_ep->name); + return 0; +} + +static int imx_ep_disable(struct usb_ep *usb_ep) +{ + struct imx_ep_struct *imx_ep = container_of(usb_ep, + struct imx_ep_struct, ep); + unsigned long flags; + + if (!usb_ep || !EP_NO(imx_ep) || !list_empty(&imx_ep->queue)) { + D_ERR(imx_ep->imx_usb->dev, "<%s> %s can not be disabled\n", + __func__, usb_ep ? imx_ep->ep.name : NULL); + return -EINVAL; + } + + local_irq_save(flags); + + imx_ep->stopped = 1; + nuke(imx_ep, -ESHUTDOWN); + imx_flush(imx_ep); + imx_ep_irq_disable(imx_ep); + + local_irq_restore(flags); + + D_EPX(imx_ep->imx_usb->dev, + "<%s> DISABLED %s\n", __func__, usb_ep->name); + return 0; +} + +static struct usb_request *imx_ep_alloc_request + (struct usb_ep *usb_ep, gfp_t gfp_flags) +{ + struct imx_request *req; + + if (!usb_ep) + return NULL; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + req->in_use = 0; + + return &req->req; +} + +static void imx_ep_free_request + (struct usb_ep *usb_ep, struct usb_request *usb_req) +{ + struct imx_request *req; + + req = container_of(usb_req, struct imx_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +static int imx_ep_queue + (struct usb_ep *usb_ep, struct usb_request *usb_req, gfp_t gfp_flags) +{ + struct imx_ep_struct *imx_ep; + struct imx_udc_struct *imx_usb; + struct imx_request *req; + unsigned long flags; + int ret = 0; + + imx_ep = container_of(usb_ep, struct imx_ep_struct, ep); + imx_usb = imx_ep->imx_usb; + req = container_of(usb_req, struct imx_request, req); + + /* + Special care on IMX udc. + Ignore enqueue when after set configuration from the + host. This assume all gadget drivers reply set + configuration with the next ep0 req enqueue. + */ + if (imx_usb->set_config && !EP_NO(imx_ep)) { + imx_usb->set_config = 0; + D_ERR(imx_usb->dev, + "<%s> gadget reply set config\n", __func__); + return 0; + } + + if (unlikely(!usb_req || !req || !usb_req->complete || !usb_req->buf)) { + D_ERR(imx_usb->dev, "<%s> bad params\n", __func__); + return -EINVAL; + } + + if (unlikely(!usb_ep || !imx_ep)) { + D_ERR(imx_usb->dev, "<%s> bad ep\n", __func__); + return -EINVAL; + } + + if (!imx_usb->driver || imx_usb->gadget.speed == USB_SPEED_UNKNOWN) { + D_ERR(imx_usb->dev, "<%s> bogus device state\n", __func__); + return -ESHUTDOWN; + } + + /* Debug */ + D_REQ(imx_usb->dev, "<%s> ep%d %s request for [%d] bytes\n", + __func__, EP_NO(imx_ep), + ((!EP_NO(imx_ep) && imx_ep->imx_usb->ep0state + == EP0_IN_DATA_PHASE) + || (EP_NO(imx_ep) && EP_DIR(imx_ep))) + ? "IN" : "OUT", usb_req->length); + dump_req(__func__, imx_ep, usb_req); + + if (imx_ep->stopped) { + usb_req->status = -ESHUTDOWN; + return -ESHUTDOWN; + } + + if (req->in_use) { + D_ERR(imx_usb->dev, + "<%s> refusing to queue req %p (already queued)\n", + __func__, req); + return 0; + } + + local_irq_save(flags); + + usb_req->status = -EINPROGRESS; + usb_req->actual = 0; + + ep_add_request(imx_ep, req); + + if (!EP_NO(imx_ep)) + ret = handle_ep0(imx_ep); + else + ret = handle_ep(imx_ep); + + local_irq_restore(flags); + return ret; +} + +static int imx_ep_dequeue(struct usb_ep *usb_ep, struct usb_request *usb_req) +{ + + struct imx_ep_struct *imx_ep = container_of + (usb_ep, struct imx_ep_struct, ep); + struct imx_request *req; + unsigned long flags; + + if (unlikely(!usb_ep || !EP_NO(imx_ep))) { + D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__); + return -EINVAL; + } + + local_irq_save(flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &imx_ep->queue, queue) { + if (&req->req == usb_req) + break; + } + if (&req->req != usb_req) { + local_irq_restore(flags); + return -EINVAL; + } + + done(imx_ep, req, -ECONNRESET); + + local_irq_restore(flags); + return 0; +} + +static int imx_ep_set_halt(struct usb_ep *usb_ep, int value) +{ + struct imx_ep_struct *imx_ep = container_of + (usb_ep, struct imx_ep_struct, ep); + unsigned long flags; + + if (unlikely(!usb_ep || !EP_NO(imx_ep))) { + D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__); + return -EINVAL; + } + + local_irq_save(flags); + + if ((imx_ep->bEndpointAddress & USB_DIR_IN) + && !list_empty(&imx_ep->queue)) { + local_irq_restore(flags); + return -EAGAIN; + } + + imx_ep_stall(imx_ep); + + local_irq_restore(flags); + + D_EPX(imx_ep->imx_usb->dev, "<%s> %s halt\n", __func__, usb_ep->name); + return 0; +} + +static int imx_ep_fifo_status(struct usb_ep *usb_ep) +{ + struct imx_ep_struct *imx_ep = container_of + (usb_ep, struct imx_ep_struct, ep); + + if (!usb_ep) { + D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__); + return -ENODEV; + } + + if (imx_ep->imx_usb->gadget.speed == USB_SPEED_UNKNOWN) + return 0; + else + return imx_fifo_bcount(imx_ep); +} + +static void imx_ep_fifo_flush(struct usb_ep *usb_ep) +{ + struct imx_ep_struct *imx_ep = container_of + (usb_ep, struct imx_ep_struct, ep); + unsigned long flags; + + local_irq_save(flags); + + if (!usb_ep || !EP_NO(imx_ep) || !list_empty(&imx_ep->queue)) { + D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__); + local_irq_restore(flags); + return; + } + + /* toggle and halt bits stay unchanged */ + imx_flush(imx_ep); + + local_irq_restore(flags); +} + +static struct usb_ep_ops imx_ep_ops = { + .enable = imx_ep_enable, + .disable = imx_ep_disable, + + .alloc_request = imx_ep_alloc_request, + .free_request = imx_ep_free_request, + + .queue = imx_ep_queue, + .dequeue = imx_ep_dequeue, + + .set_halt = imx_ep_set_halt, + .fifo_status = imx_ep_fifo_status, + .fifo_flush = imx_ep_fifo_flush, +}; + +/******************************************************************************* + * USB endpoint control functions + ******************************************************************************* + */ + +void ep0_chg_stat(const char *label, + struct imx_udc_struct *imx_usb, enum ep0_state stat) +{ + D_EP0(imx_usb->dev, "<%s> from %15s to %15s\n", + label, state_name[imx_usb->ep0state], state_name[stat]); + + if (imx_usb->ep0state == stat) + return; + + imx_usb->ep0state = stat; +} + +static void usb_init_data(struct imx_udc_struct *imx_usb) +{ + struct imx_ep_struct *imx_ep; + u8 i; + + /* device/ep0 records init */ + INIT_LIST_HEAD(&imx_usb->gadget.ep_list); + INIT_LIST_HEAD(&imx_usb->gadget.ep0->ep_list); + ep0_chg_stat(__func__, imx_usb, EP0_IDLE); + + /* basic endpoint records init */ + for (i = 0; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + + if (i) { + list_add_tail(&imx_ep->ep.ep_list, + &imx_usb->gadget.ep_list); + imx_ep->stopped = 1; + } else + imx_ep->stopped = 0; + + INIT_LIST_HEAD(&imx_ep->queue); + } +} + +static void udc_stop_activity(struct imx_udc_struct *imx_usb, + struct usb_gadget_driver *driver) +{ + struct imx_ep_struct *imx_ep; + int i; + + if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 1; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + imx_flush(imx_ep); + imx_ep->stopped = 1; + imx_ep_irq_disable(imx_ep); + nuke(imx_ep, -ESHUTDOWN); + } + + imx_usb->cfg = 0; + imx_usb->intf = 0; + imx_usb->alt = 0; + + if (driver) + driver->disconnect(&imx_usb->gadget); +} + +/******************************************************************************* + * Interrupt handlers + ******************************************************************************* + */ + +/* + * Called when timer expires. + * Timer is started when CFG_CHG is received. + */ +static void handle_config(unsigned long data) +{ + struct imx_udc_struct *imx_usb = (void *)data; + struct usb_ctrlrequest u; + int temp, cfg, intf, alt; + + local_irq_disable(); + + temp = __raw_readl(imx_usb->base + USB_STAT); + cfg = (temp & STAT_CFG) >> 5; + intf = (temp & STAT_INTF) >> 3; + alt = temp & STAT_ALTSET; + + D_REQ(imx_usb->dev, + "<%s> orig config C=%d, I=%d, A=%d / " + "req config C=%d, I=%d, A=%d\n", + __func__, imx_usb->cfg, imx_usb->intf, imx_usb->alt, + cfg, intf, alt); + + if (cfg == 1 || cfg == 2) { + + if (imx_usb->cfg != cfg) { + u.bRequest = USB_REQ_SET_CONFIGURATION; + u.bRequestType = USB_DIR_OUT | + USB_TYPE_STANDARD | + USB_RECIP_DEVICE; + u.wValue = cfg; + u.wIndex = 0; + u.wLength = 0; + imx_usb->cfg = cfg; + imx_usb->driver->setup(&imx_usb->gadget, &u); + + } + if (imx_usb->intf != intf || imx_usb->alt != alt) { + u.bRequest = USB_REQ_SET_INTERFACE; + u.bRequestType = USB_DIR_OUT | + USB_TYPE_STANDARD | + USB_RECIP_INTERFACE; + u.wValue = alt; + u.wIndex = intf; + u.wLength = 0; + imx_usb->intf = intf; + imx_usb->alt = alt; + imx_usb->driver->setup(&imx_usb->gadget, &u); + } + } + + imx_usb->set_config = 0; + + local_irq_enable(); +} + +static irqreturn_t imx_udc_irq(int irq, void *dev) +{ + struct imx_udc_struct *imx_usb = dev; + int intr = __raw_readl(imx_usb->base + USB_INTR); + int temp; + + if (intr & (INTR_WAKEUP | INTR_SUSPEND | INTR_RESUME | INTR_RESET_START + | INTR_RESET_STOP | INTR_CFG_CHG)) { + dump_intr(__func__, intr, imx_usb->dev); + dump_usb_stat(__func__, imx_usb); + } + + if (!imx_usb->driver) + goto end_irq; + + if (intr & INTR_SOF) { + /* Copy from Freescale BSP. + We must enable SOF intr and set CMDOVER. + Datasheet don't specifiy this action, but it + is done in Freescale BSP, so just copy it. + */ + if (imx_usb->ep0state == EP0_IDLE) { + temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp | CTRL_CMDOVER, + imx_usb->base + USB_CTRL); + } + } + + if (intr & INTR_CFG_CHG) { + /* A workaround of serious IMX UDC bug. + Handling of CFG_CHG should be delayed for some time, because + IMX does not NACK the host when CFG_CHG interrupt is pending. + There is no time to handle current CFG_CHG + if next CFG_CHG or SETUP packed is send immediately. + We have to clear CFG_CHG, start the timer and + NACK the host by setting CTRL_CMDOVER + if it sends any SETUP packet. + When timer expires, handler is called to handle configuration + changes. While CFG_CHG is not handled (set_config=1), + we must NACK the host to every SETUP packed. + This delay prevents from going out of sync with host. + */ + __raw_writel(INTR_CFG_CHG, imx_usb->base + USB_INTR); + imx_usb->set_config = 1; + mod_timer(&imx_usb->timer, jiffies + 5); + goto end_irq; + } + + if (intr & INTR_WAKEUP) { + if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN + && imx_usb->driver && imx_usb->driver->resume) + imx_usb->driver->resume(&imx_usb->gadget); + imx_usb->set_config = 0; + del_timer(&imx_usb->timer); + imx_usb->gadget.speed = USB_SPEED_FULL; + } + + if (intr & INTR_SUSPEND) { + if (imx_usb->gadget.speed != USB_SPEED_UNKNOWN + && imx_usb->driver && imx_usb->driver->suspend) + imx_usb->driver->suspend(&imx_usb->gadget); + imx_usb->set_config = 0; + del_timer(&imx_usb->timer); + imx_usb->gadget.speed = USB_SPEED_UNKNOWN; + } + + if (intr & INTR_RESET_START) { + __raw_writel(intr, imx_usb->base + USB_INTR); + udc_stop_activity(imx_usb, imx_usb->driver); + imx_usb->set_config = 0; + del_timer(&imx_usb->timer); + imx_usb->gadget.speed = USB_SPEED_UNKNOWN; + } + + if (intr & INTR_RESET_STOP) + imx_usb->gadget.speed = USB_SPEED_FULL; + +end_irq: + __raw_writel(intr, imx_usb->base + USB_INTR); + return IRQ_HANDLED; +} + +static irqreturn_t imx_udc_ctrl_irq(int irq, void *dev) +{ + struct imx_udc_struct *imx_usb = dev; + struct imx_ep_struct *imx_ep = &imx_usb->imx_ep[0]; + int intr = __raw_readl(imx_usb->base + USB_EP_INTR(0)); + + dump_ep_intr(__func__, 0, intr, imx_usb->dev); + + if (!imx_usb->driver) { + __raw_writel(intr, imx_usb->base + USB_EP_INTR(0)); + return IRQ_HANDLED; + } + + /* DEVREQ has highest priority */ + if (intr & (EPINTR_DEVREQ | EPINTR_MDEVREQ)) + handle_ep0_devreq(imx_usb); + /* Seem i.MX is missing EOF interrupt sometimes. + * Therefore we don't monitor EOF. + * We call handle_ep0() only if a request is queued for ep0. + */ + else if (!list_empty(&imx_ep->queue)) + handle_ep0(imx_ep); + + __raw_writel(intr, imx_usb->base + USB_EP_INTR(0)); + + return IRQ_HANDLED; +} + +#ifndef MX1_INT_USBD0 +#define MX1_INT_USBD0 MX1_USBD_INT0 +#endif + +static irqreturn_t imx_udc_bulk_irq(int irq, void *dev) +{ + struct imx_udc_struct *imx_usb = dev; + struct imx_ep_struct *imx_ep = &imx_usb->imx_ep[irq - MX1_INT_USBD0]; + int intr = __raw_readl(imx_usb->base + USB_EP_INTR(EP_NO(imx_ep))); + + dump_ep_intr(__func__, irq - MX1_INT_USBD0, intr, imx_usb->dev); + + if (!imx_usb->driver) { + __raw_writel(intr, imx_usb->base + USB_EP_INTR(EP_NO(imx_ep))); + return IRQ_HANDLED; + } + + handle_ep(imx_ep); + + __raw_writel(intr, imx_usb->base + USB_EP_INTR(EP_NO(imx_ep))); + + return IRQ_HANDLED; +} + +irq_handler_t intr_handler(int i) +{ + switch (i) { + case 0: + return imx_udc_ctrl_irq; + case 1: + case 2: + case 3: + case 4: + case 5: + return imx_udc_bulk_irq; + default: + return imx_udc_irq; + } +} + +/******************************************************************************* + * Static defined IMX UDC structure + ******************************************************************************* + */ + +static int imx_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int imx_udc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static const struct usb_gadget_ops imx_udc_ops = { + .get_frame = imx_udc_get_frame, + .wakeup = imx_udc_wakeup, + .udc_start = imx_udc_start, + .udc_stop = imx_udc_stop, +}; + +static struct imx_udc_struct controller = { + .gadget = { + .ops = &imx_udc_ops, + .ep0 = &controller.imx_ep[0].ep, + .name = driver_name, + .dev = { + .init_name = "gadget", + }, + }, + + .imx_ep[0] = { + .ep = { + .name = ep0name, + .ops = &imx_ep_ops, + .maxpacket = 32, + }, + .imx_usb = &controller, + .fifosize = 32, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + }, + .imx_ep[1] = { + .ep = { + .name = "ep1in-bulk", + .ops = &imx_ep_ops, + .maxpacket = 64, + }, + .imx_usb = &controller, + .fifosize = 64, + .bEndpointAddress = USB_DIR_IN | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .imx_ep[2] = { + .ep = { + .name = "ep2out-bulk", + .ops = &imx_ep_ops, + .maxpacket = 64, + }, + .imx_usb = &controller, + .fifosize = 64, + .bEndpointAddress = USB_DIR_OUT | 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .imx_ep[3] = { + .ep = { + .name = "ep3out-bulk", + .ops = &imx_ep_ops, + .maxpacket = 32, + }, + .imx_usb = &controller, + .fifosize = 32, + .bEndpointAddress = USB_DIR_OUT | 3, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .imx_ep[4] = { + .ep = { + .name = "ep4in-int", + .ops = &imx_ep_ops, + .maxpacket = 32, + }, + .imx_usb = &controller, + .fifosize = 32, + .bEndpointAddress = USB_DIR_IN | 4, + .bmAttributes = USB_ENDPOINT_XFER_INT, + }, + .imx_ep[5] = { + .ep = { + .name = "ep5out-int", + .ops = &imx_ep_ops, + .maxpacket = 32, + }, + .imx_usb = &controller, + .fifosize = 32, + .bEndpointAddress = USB_DIR_OUT | 5, + .bmAttributes = USB_ENDPOINT_XFER_INT, + }, +}; + +/******************************************************************************* + * USB gadget driver functions + ******************************************************************************* + */ +static int imx_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct imx_udc_struct *imx_usb; + + imx_usb = container_of(gadget, struct imx_udc_struct, gadget); + /* first hook up the driver ... */ + imx_usb->driver = driver; + + D_INI(imx_usb->dev, "<%s> registered gadget driver '%s'\n", + __func__, driver->driver.name); + + imx_udc_enable(imx_usb); + + return 0; +} + +static int imx_udc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct imx_udc_struct *imx_usb = container_of(gadget, + struct imx_udc_struct, gadget); + + udc_stop_activity(imx_usb, driver); + imx_udc_disable(imx_usb); + del_timer(&imx_usb->timer); + + imx_usb->driver = NULL; + + D_INI(imx_usb->dev, "<%s> unregistered gadget driver '%s'\n", + __func__, driver->driver.name); + + return 0; +} + +/******************************************************************************* + * Module functions + ******************************************************************************* + */ + +static int __init imx_udc_probe(struct platform_device *pdev) +{ + struct imx_udc_struct *imx_usb = &controller; + struct resource *res; + struct imxusb_platform_data *pdata; + struct clk *clk; + void __iomem *base; + int ret = 0; + int i; + resource_size_t res_size; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "can't get device resources\n"); + return -ENODEV; + } + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "driver needs platform data\n"); + return -ENODEV; + } + + res_size = resource_size(res); + if (!request_mem_region(res->start, res_size, res->name)) { + dev_err(&pdev->dev, "can't allocate %d bytes at %d address\n", + res_size, res->start); + return -ENOMEM; + } + + if (pdata->init) { + ret = pdata->init(&pdev->dev); + if (ret) + goto fail0; + } + + base = ioremap(res->start, res_size); + if (!base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -EIO; + goto fail1; + } + + clk = clk_get(NULL, "usbd_clk"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(&pdev->dev, "can't get USB clock\n"); + goto fail2; + } + clk_prepare_enable(clk); + + if (clk_get_rate(clk) != 48000000) { + D_INI(&pdev->dev, + "Bad USB clock (%d Hz), changing to 48000000 Hz\n", + (int)clk_get_rate(clk)); + if (clk_set_rate(clk, 48000000)) { + dev_err(&pdev->dev, + "Unable to set correct USB clock (48MHz)\n"); + ret = -EIO; + goto fail3; + } + } + + for (i = 0; i < IMX_USB_NB_EP + 1; i++) { + imx_usb->usbd_int[i] = platform_get_irq(pdev, i); + if (imx_usb->usbd_int[i] < 0) { + dev_err(&pdev->dev, "can't get irq number\n"); + ret = -ENODEV; + goto fail3; + } + } + + for (i = 0; i < IMX_USB_NB_EP + 1; i++) { + ret = request_irq(imx_usb->usbd_int[i], intr_handler(i), + 0, driver_name, imx_usb); + if (ret) { + dev_err(&pdev->dev, "can't get irq %i, err %d\n", + imx_usb->usbd_int[i], ret); + for (--i; i >= 0; i--) + free_irq(imx_usb->usbd_int[i], imx_usb); + goto fail3; + } + } + + imx_usb->res = res; + imx_usb->base = base; + imx_usb->clk = clk; + imx_usb->dev = &pdev->dev; + + platform_set_drvdata(pdev, imx_usb); + + usb_init_data(imx_usb); + imx_udc_init(imx_usb); + + init_timer(&imx_usb->timer); + imx_usb->timer.function = handle_config; + imx_usb->timer.data = (unsigned long)imx_usb; + + ret = usb_add_gadget_udc(&pdev->dev, &imx_usb->gadget); + if (ret) + goto fail4; + + return 0; +fail4: + for (i = 0; i < IMX_USB_NB_EP + 1; i++) + free_irq(imx_usb->usbd_int[i], imx_usb); +fail3: + clk_put(clk); + clk_disable_unprepare(clk); +fail2: + iounmap(base); +fail1: + if (pdata->exit) + pdata->exit(&pdev->dev); +fail0: + release_mem_region(res->start, res_size); + return ret; +} + +static int __exit imx_udc_remove(struct platform_device *pdev) +{ + struct imx_udc_struct *imx_usb = platform_get_drvdata(pdev); + struct imxusb_platform_data *pdata = pdev->dev.platform_data; + int i; + + usb_del_gadget_udc(&imx_usb->gadget); + imx_udc_disable(imx_usb); + del_timer(&imx_usb->timer); + + for (i = 0; i < IMX_USB_NB_EP + 1; i++) + free_irq(imx_usb->usbd_int[i], imx_usb); + + clk_put(imx_usb->clk); + clk_disable_unprepare(imx_usb->clk); + iounmap(imx_usb->base); + + release_mem_region(imx_usb->res->start, resource_size(imx_usb->res)); + + if (pdata->exit) + pdata->exit(&pdev->dev); + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +#define imx_udc_suspend NULL +#define imx_udc_resume NULL +#else +#define imx_udc_suspend NULL +#define imx_udc_resume NULL +#endif + +/*----------------------------------------------------------------------------*/ + +static struct platform_driver udc_driver = { + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + }, + .remove = __exit_p(imx_udc_remove), + .suspend = imx_udc_suspend, + .resume = imx_udc_resume, +}; + +module_platform_driver_probe(udc_driver, imx_udc_probe); + +MODULE_DESCRIPTION("IMX USB Device Controller driver"); +MODULE_AUTHOR("Darius Augulis "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx_udc"); diff --git a/drivers/usb/gadget/imx_udc.h b/drivers/usb/gadget/imx_udc.h new file mode 100644 index 00000000..d118fb77 --- /dev/null +++ b/drivers/usb/gadget/imx_udc.h @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2005 Mike Lee(eemike@gmail.com) + * + * This udc driver is now under testing and code is based on pxa2xx_udc.h + * Please use it with your own risk! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __LINUX_USB_GADGET_IMX_H +#define __LINUX_USB_GADGET_IMX_H + +#include + +/* Helper macros */ +#define EP_NO(ep) ((ep->bEndpointAddress) & ~USB_DIR_IN) /* IN:1, OUT:0 */ +#define EP_DIR(ep) ((ep->bEndpointAddress) & USB_DIR_IN ? 1 : 0) +#define IMX_USB_NB_EP 6 + +/* Driver structures */ +struct imx_request { + struct usb_request req; + struct list_head queue; + unsigned int in_use; +}; + +enum ep0_state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_CONFIG, + EP0_STALL, +}; + +struct imx_ep_struct { + struct usb_ep ep; + struct imx_udc_struct *imx_usb; + struct list_head queue; + unsigned char stopped; + unsigned char fifosize; + unsigned char bEndpointAddress; + unsigned char bmAttributes; +}; + +struct imx_udc_struct { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct device *dev; + struct imx_ep_struct imx_ep[IMX_USB_NB_EP]; + struct clk *clk; + struct timer_list timer; + enum ep0_state ep0state; + struct resource *res; + void __iomem *base; + unsigned char set_config; + int cfg, + intf, + alt, + usbd_int[7]; +}; + +/* USB registers */ +#define USB_FRAME (0x00) /* USB frame */ +#define USB_SPEC (0x04) /* USB Spec */ +#define USB_STAT (0x08) /* USB Status */ +#define USB_CTRL (0x0C) /* USB Control */ +#define USB_DADR (0x10) /* USB Desc RAM addr */ +#define USB_DDAT (0x14) /* USB Desc RAM/EP buffer data */ +#define USB_INTR (0x18) /* USB interrupt */ +#define USB_MASK (0x1C) /* USB Mask */ +#define USB_ENAB (0x24) /* USB Enable */ +#define USB_EP_STAT(x) (0x30 + (x*0x30)) /* USB status/control */ +#define USB_EP_INTR(x) (0x34 + (x*0x30)) /* USB interrupt */ +#define USB_EP_MASK(x) (0x38 + (x*0x30)) /* USB mask */ +#define USB_EP_FDAT(x) (0x3C + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FDAT0(x) (0x3C + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FDAT1(x) (0x3D + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FDAT2(x) (0x3E + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FDAT3(x) (0x3F + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FSTAT(x) (0x40 + (x*0x30)) /* USB FIFO status */ +#define USB_EP_FCTRL(x) (0x44 + (x*0x30)) /* USB FIFO control */ +#define USB_EP_LRFP(x) (0x48 + (x*0x30)) /* USB last rd f. pointer */ +#define USB_EP_LWFP(x) (0x4C + (x*0x30)) /* USB last wr f. pointer */ +#define USB_EP_FALRM(x) (0x50 + (x*0x30)) /* USB FIFO alarm */ +#define USB_EP_FRDP(x) (0x54 + (x*0x30)) /* USB FIFO read pointer */ +#define USB_EP_FWRP(x) (0x58 + (x*0x30)) /* USB FIFO write pointer */ +/* USB Control Register Bit Fields.*/ +#define CTRL_CMDOVER (1<<6) /* UDC status */ +#define CTRL_CMDERROR (1<<5) /* UDC status */ +#define CTRL_FE_ENA (1<<3) /* Enable Font End logic */ +#define CTRL_UDC_RST (1<<2) /* UDC reset */ +#define CTRL_AFE_ENA (1<<1) /* Analog Font end enable */ +#define CTRL_RESUME (1<<0) /* UDC resume */ +/* USB Status Register Bit Fields.*/ +#define STAT_RST (1<<8) +#define STAT_SUSP (1<<7) +#define STAT_CFG (3<<5) +#define STAT_INTF (3<<3) +#define STAT_ALTSET (7<<0) +/* USB Interrupt Status/Mask Registers Bit fields */ +#define INTR_WAKEUP (1<<31) /* Wake up Interrupt */ +#define INTR_MSOF (1<<7) /* Missed Start of Frame */ +#define INTR_SOF (1<<6) /* Start of Frame */ +#define INTR_RESET_STOP (1<<5) /* Reset Signaling stop */ +#define INTR_RESET_START (1<<4) /* Reset Signaling start */ +#define INTR_RESUME (1<<3) /* Suspend to resume */ +#define INTR_SUSPEND (1<<2) /* Active to suspend */ +#define INTR_FRAME_MATCH (1<<1) /* Frame matched */ +#define INTR_CFG_CHG (1<<0) /* Configuration change occurred */ +/* USB Enable Register Bit Fields.*/ +#define ENAB_RST (1<<31) /* Reset USB modules */ +#define ENAB_ENAB (1<<30) /* Enable USB modules*/ +#define ENAB_SUSPEND (1<<29) /* Suspend USB modules */ +#define ENAB_ENDIAN (1<<28) /* Endian of USB modules */ +#define ENAB_PWRMD (1<<0) /* Power mode of USB modules */ +/* USB Descriptor Ram Address Register bit fields */ +#define DADR_CFG (1<<31) /* Configuration */ +#define DADR_BSY (1<<30) /* Busy status */ +#define DADR_DADR (0x1FF) /* Descriptor Ram Address */ +/* USB Descriptor RAM/Endpoint Buffer Data Register bit fields */ +#define DDAT_DDAT (0xFF) /* Descriptor Endpoint Buffer */ +/* USB Endpoint Status Register bit fields */ +#define EPSTAT_BCOUNT (0x7F<<16) /* Endpoint FIFO byte count */ +#define EPSTAT_SIP (1<<8) /* Endpoint setup in progress */ +#define EPSTAT_DIR (1<<7) /* Endpoint transfer direction */ +#define EPSTAT_MAX (3<<5) /* Endpoint Max packet size */ +#define EPSTAT_TYP (3<<3) /* Endpoint type */ +#define EPSTAT_ZLPS (1<<2) /* Send zero length packet */ +#define EPSTAT_FLUSH (1<<1) /* Endpoint FIFO Flush */ +#define EPSTAT_STALL (1<<0) /* Force stall */ +/* USB Endpoint FIFO Status Register bit fields */ +#define FSTAT_FRAME_STAT (0xF<<24) /* Frame status bit [0-3] */ +#define FSTAT_ERR (1<<22) /* FIFO error */ +#define FSTAT_UF (1<<21) /* FIFO underflow */ +#define FSTAT_OF (1<<20) /* FIFO overflow */ +#define FSTAT_FR (1<<19) /* FIFO frame ready */ +#define FSTAT_FULL (1<<18) /* FIFO full */ +#define FSTAT_ALRM (1<<17) /* FIFO alarm */ +#define FSTAT_EMPTY (1<<16) /* FIFO empty */ +/* USB Endpoint FIFO Control Register bit fields */ +#define FCTRL_WFR (1<<29) /* Write frame end */ +/* USB Endpoint Interrupt Status Regsiter bit fields */ +#define EPINTR_FIFO_FULL (1<<8) /* fifo full */ +#define EPINTR_FIFO_EMPTY (1<<7) /* fifo empty */ +#define EPINTR_FIFO_ERROR (1<<6) /* fifo error */ +#define EPINTR_FIFO_HIGH (1<<5) /* fifo high */ +#define EPINTR_FIFO_LOW (1<<4) /* fifo low */ +#define EPINTR_MDEVREQ (1<<3) /* multi Device request */ +#define EPINTR_EOT (1<<2) /* fifo end of transfer */ +#define EPINTR_DEVREQ (1<<1) /* Device request */ +#define EPINTR_EOF (1<<0) /* fifo end of frame */ + +/* Debug macros */ +#ifdef DEBUG + +/* #define DEBUG_REQ */ +/* #define DEBUG_TRX */ +/* #define DEBUG_INIT */ +/* #define DEBUG_EP0 */ +/* #define DEBUG_EPX */ +/* #define DEBUG_IRQ */ +/* #define DEBUG_EPIRQ */ +/* #define DEBUG_DUMP */ +/* #define DEBUG_ERR */ + +#ifdef DEBUG_REQ + #define D_REQ(dev, args...) dev_dbg(dev, ## args) +#else + #define D_REQ(dev, args...) do {} while (0) +#endif /* DEBUG_REQ */ + +#ifdef DEBUG_TRX + #define D_TRX(dev, args...) dev_dbg(dev, ## args) +#else + #define D_TRX(dev, args...) do {} while (0) +#endif /* DEBUG_TRX */ + +#ifdef DEBUG_INIT + #define D_INI(dev, args...) dev_dbg(dev, ## args) +#else + #define D_INI(dev, args...) do {} while (0) +#endif /* DEBUG_INIT */ + +#ifdef DEBUG_EP0 + static const char *state_name[] = { + "EP0_IDLE", + "EP0_IN_DATA_PHASE", + "EP0_OUT_DATA_PHASE", + "EP0_CONFIG", + "EP0_STALL" + }; + #define D_EP0(dev, args...) dev_dbg(dev, ## args) +#else + #define D_EP0(dev, args...) do {} while (0) +#endif /* DEBUG_EP0 */ + +#ifdef DEBUG_EPX + #define D_EPX(dev, args...) dev_dbg(dev, ## args) +#else + #define D_EPX(dev, args...) do {} while (0) +#endif /* DEBUG_EP0 */ + +#ifdef DEBUG_IRQ + static void dump_intr(const char *label, int irqreg, struct device *dev) + { + dev_dbg(dev, "<%s> USB_INTR=[%s%s%s%s%s%s%s%s%s]\n", label, + (irqreg & INTR_WAKEUP) ? " wake" : "", + (irqreg & INTR_MSOF) ? " msof" : "", + (irqreg & INTR_SOF) ? " sof" : "", + (irqreg & INTR_RESUME) ? " resume" : "", + (irqreg & INTR_SUSPEND) ? " suspend" : "", + (irqreg & INTR_RESET_STOP) ? " noreset" : "", + (irqreg & INTR_RESET_START) ? " reset" : "", + (irqreg & INTR_FRAME_MATCH) ? " fmatch" : "", + (irqreg & INTR_CFG_CHG) ? " config" : ""); + } +#else + #define dump_intr(x, y, z) do {} while (0) +#endif /* DEBUG_IRQ */ + +#ifdef DEBUG_EPIRQ + static void dump_ep_intr(const char *label, int nr, int irqreg, + struct device *dev) + { + dev_dbg(dev, "<%s> EP%d_INTR=[%s%s%s%s%s%s%s%s%s]\n", label, nr, + (irqreg & EPINTR_FIFO_FULL) ? " full" : "", + (irqreg & EPINTR_FIFO_EMPTY) ? " fempty" : "", + (irqreg & EPINTR_FIFO_ERROR) ? " ferr" : "", + (irqreg & EPINTR_FIFO_HIGH) ? " fhigh" : "", + (irqreg & EPINTR_FIFO_LOW) ? " flow" : "", + (irqreg & EPINTR_MDEVREQ) ? " mreq" : "", + (irqreg & EPINTR_EOF) ? " eof" : "", + (irqreg & EPINTR_DEVREQ) ? " devreq" : "", + (irqreg & EPINTR_EOT) ? " eot" : ""); + } +#else + #define dump_ep_intr(x, y, z, i) do {} while (0) +#endif /* DEBUG_IRQ */ + +#ifdef DEBUG_DUMP + static void dump_usb_stat(const char *label, + struct imx_udc_struct *imx_usb) + { + int temp = __raw_readl(imx_usb->base + USB_STAT); + + dev_dbg(imx_usb->dev, + "<%s> USB_STAT=[%s%s CFG=%d, INTF=%d, ALTR=%d]\n", label, + (temp & STAT_RST) ? " reset" : "", + (temp & STAT_SUSP) ? " suspend" : "", + (temp & STAT_CFG) >> 5, + (temp & STAT_INTF) >> 3, + (temp & STAT_ALTSET)); + } + + static void dump_ep_stat(const char *label, + struct imx_ep_struct *imx_ep) + { + int temp = __raw_readl(imx_ep->imx_usb->base + + USB_EP_INTR(EP_NO(imx_ep))); + + dev_dbg(imx_ep->imx_usb->dev, + "<%s> EP%d_INTR=[%s%s%s%s%s%s%s%s%s]\n", + label, EP_NO(imx_ep), + (temp & EPINTR_FIFO_FULL) ? " full" : "", + (temp & EPINTR_FIFO_EMPTY) ? " fempty" : "", + (temp & EPINTR_FIFO_ERROR) ? " ferr" : "", + (temp & EPINTR_FIFO_HIGH) ? " fhigh" : "", + (temp & EPINTR_FIFO_LOW) ? " flow" : "", + (temp & EPINTR_MDEVREQ) ? " mreq" : "", + (temp & EPINTR_EOF) ? " eof" : "", + (temp & EPINTR_DEVREQ) ? " devreq" : "", + (temp & EPINTR_EOT) ? " eot" : ""); + + temp = __raw_readl(imx_ep->imx_usb->base + + USB_EP_STAT(EP_NO(imx_ep))); + + dev_dbg(imx_ep->imx_usb->dev, + "<%s> EP%d_STAT=[%s%s bcount=%d]\n", + label, EP_NO(imx_ep), + (temp & EPSTAT_SIP) ? " sip" : "", + (temp & EPSTAT_STALL) ? " stall" : "", + (temp & EPSTAT_BCOUNT) >> 16); + + temp = __raw_readl(imx_ep->imx_usb->base + + USB_EP_FSTAT(EP_NO(imx_ep))); + + dev_dbg(imx_ep->imx_usb->dev, + "<%s> EP%d_FSTAT=[%s%s%s%s%s%s%s]\n", + label, EP_NO(imx_ep), + (temp & FSTAT_ERR) ? " ferr" : "", + (temp & FSTAT_UF) ? " funder" : "", + (temp & FSTAT_OF) ? " fover" : "", + (temp & FSTAT_FR) ? " fready" : "", + (temp & FSTAT_FULL) ? " ffull" : "", + (temp & FSTAT_ALRM) ? " falarm" : "", + (temp & FSTAT_EMPTY) ? " fempty" : ""); + } + + static void dump_req(const char *label, struct imx_ep_struct *imx_ep, + struct usb_request *req) + { + int i; + + if (!req || !req->buf) { + dev_dbg(imx_ep->imx_usb->dev, + "<%s> req or req buf is free\n", label); + return; + } + + if ((!EP_NO(imx_ep) && imx_ep->imx_usb->ep0state + == EP0_IN_DATA_PHASE) + || (EP_NO(imx_ep) && EP_DIR(imx_ep))) { + + dev_dbg(imx_ep->imx_usb->dev, + "<%s> request dump <", label); + for (i = 0; i < req->length; i++) + printk("%02x-", *((u8 *)req->buf + i)); + printk(">\n"); + } + } + +#else + #define dump_ep_stat(x, y) do {} while (0) + #define dump_usb_stat(x, y) do {} while (0) + #define dump_req(x, y, z) do {} while (0) +#endif /* DEBUG_DUMP */ + +#ifdef DEBUG_ERR + #define D_ERR(dev, args...) dev_dbg(dev, ## args) +#else + #define D_ERR(dev, args...) do {} while (0) +#endif + +#else + #define D_REQ(dev, args...) do {} while (0) + #define D_TRX(dev, args...) do {} while (0) + #define D_INI(dev, args...) do {} while (0) + #define D_EP0(dev, args...) do {} while (0) + #define D_EPX(dev, args...) do {} while (0) + #define dump_ep_intr(x, y, z, i) do {} while (0) + #define dump_intr(x, y, z) do {} while (0) + #define dump_ep_stat(x, y) do {} while (0) + #define dump_usb_stat(x, y) do {} while (0) + #define dump_req(x, y, z) do {} while (0) + #define D_ERR(dev, args...) do {} while (0) +#endif /* DEBUG */ + +#endif /* __LINUX_USB_GADGET_IMX_H */ diff --git a/drivers/usb/gadget/inode.c b/drivers/usb/gadget/inode.c new file mode 100644 index 00000000..42a30903 --- /dev/null +++ b/drivers/usb/gadget/inode.c @@ -0,0 +1,2148 @@ +/* + * inode.c -- user mode filesystem api for usb gadget controllers + * + * Copyright (C) 2003-2004 David Brownell + * Copyright (C) 2003 Agilent Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +/* + * The gadgetfs API maps each endpoint to a file descriptor so that you + * can use standard synchronous read/write calls for I/O. There's some + * O_NONBLOCK and O_ASYNC/FASYNC style i/o support. Example usermode + * drivers show how this works in practice. You can also use AIO to + * eliminate I/O gaps between requests, to help when streaming data. + * + * Key parts that must be USB-specific are protocols defining how the + * read/write operations relate to the hardware state machines. There + * are two types of files. One type is for the device, implementing ep0. + * The other type is for each IN or OUT endpoint. In both cases, the + * user mode driver must configure the hardware before using it. + * + * - First, dev_config() is called when /dev/gadget/$CHIP is configured + * (by writing configuration and device descriptors). Afterwards it + * may serve as a source of device events, used to handle all control + * requests other than basic enumeration. + * + * - Then, after a SET_CONFIGURATION control request, ep_config() is + * called when each /dev/gadget/ep* file is configured (by writing + * endpoint descriptors). Afterwards these files are used to write() + * IN data or to read() OUT data. To halt the endpoint, a "wrong + * direction" request is issued (like reading an IN endpoint). + * + * Unlike "usbfs" the only ioctl()s are for things that are rare, and maybe + * not possible on all hardware. For example, precise fault handling with + * respect to data left in endpoint fifos after aborted operations; or + * selective clearing of endpoint halts, to implement SET_INTERFACE. + */ + +#define DRIVER_DESC "USB Gadget filesystem" +#define DRIVER_VERSION "24 Aug 2004" + +static const char driver_desc [] = DRIVER_DESC; +static const char shortname [] = "gadgetfs"; + +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_AUTHOR ("David Brownell"); +MODULE_LICENSE ("GPL"); + + +/*----------------------------------------------------------------------*/ + +#define GADGETFS_MAGIC 0xaee71ee7 + +/* /dev/gadget/$CHIP represents ep0 and the whole device */ +enum ep0_state { + /* DISBLED is the initial state. + */ + STATE_DEV_DISABLED = 0, + + /* Only one open() of /dev/gadget/$CHIP; only one file tracks + * ep0/device i/o modes and binding to the controller. Driver + * must always write descriptors to initialize the device, then + * the device becomes UNCONNECTED until enumeration. + */ + STATE_DEV_OPENED, + + /* From then on, ep0 fd is in either of two basic modes: + * - (UN)CONNECTED: read usb_gadgetfs_event(s) from it + * - SETUP: read/write will transfer control data and succeed; + * or if "wrong direction", performs protocol stall + */ + STATE_DEV_UNCONNECTED, + STATE_DEV_CONNECTED, + STATE_DEV_SETUP, + + /* UNBOUND means the driver closed ep0, so the device won't be + * accessible again (DEV_DISABLED) until all fds are closed. + */ + STATE_DEV_UNBOUND, +}; + +/* enough for the whole queue: most events invalidate others */ +#define N_EVENT 5 + +struct dev_data { + spinlock_t lock; + atomic_t count; + enum ep0_state state; /* P: lock */ + struct usb_gadgetfs_event event [N_EVENT]; + unsigned ev_next; + struct fasync_struct *fasync; + u8 current_config; + + /* drivers reading ep0 MUST handle control requests (SETUP) + * reported that way; else the host will time out. + */ + unsigned usermode_setup : 1, + setup_in : 1, + setup_can_stall : 1, + setup_out_ready : 1, + setup_out_error : 1, + setup_abort : 1; + unsigned setup_wLength; + + /* the rest is basically write-once */ + struct usb_config_descriptor *config, *hs_config; + struct usb_device_descriptor *dev; + struct usb_request *req; + struct usb_gadget *gadget; + struct list_head epfiles; + void *buf; + wait_queue_head_t wait; + struct super_block *sb; + struct dentry *dentry; + + /* except this scratch i/o buffer for ep0 */ + u8 rbuf [256]; +}; + +static inline void get_dev (struct dev_data *data) +{ + atomic_inc (&data->count); +} + +static void put_dev (struct dev_data *data) +{ + if (likely (!atomic_dec_and_test (&data->count))) + return; + /* needs no more cleanup */ + BUG_ON (waitqueue_active (&data->wait)); + kfree (data); +} + +static struct dev_data *dev_new (void) +{ + struct dev_data *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + dev->state = STATE_DEV_DISABLED; + atomic_set (&dev->count, 1); + spin_lock_init (&dev->lock); + INIT_LIST_HEAD (&dev->epfiles); + init_waitqueue_head (&dev->wait); + return dev; +} + +/*----------------------------------------------------------------------*/ + +/* other /dev/gadget/$ENDPOINT files represent endpoints */ +enum ep_state { + STATE_EP_DISABLED = 0, + STATE_EP_READY, + STATE_EP_ENABLED, + STATE_EP_UNBOUND, +}; + +struct ep_data { + struct mutex lock; + enum ep_state state; + atomic_t count; + struct dev_data *dev; + /* must hold dev->lock before accessing ep or req */ + struct usb_ep *ep; + struct usb_request *req; + ssize_t status; + char name [16]; + struct usb_endpoint_descriptor desc, hs_desc; + struct list_head epfiles; + wait_queue_head_t wait; + struct dentry *dentry; + struct inode *inode; +}; + +static inline void get_ep (struct ep_data *data) +{ + atomic_inc (&data->count); +} + +static void put_ep (struct ep_data *data) +{ + if (likely (!atomic_dec_and_test (&data->count))) + return; + put_dev (data->dev); + /* needs no more cleanup */ + BUG_ON (!list_empty (&data->epfiles)); + BUG_ON (waitqueue_active (&data->wait)); + kfree (data); +} + +/*----------------------------------------------------------------------*/ + +/* most "how to use the hardware" policy choices are in userspace: + * mapping endpoint roles (which the driver needs) to the capabilities + * which the usb controller has. most of those capabilities are exposed + * implicitly, starting with the driver name and then endpoint names. + */ + +static const char *CHIP; + +/*----------------------------------------------------------------------*/ + +/* NOTE: don't use dev_printk calls before binding to the gadget + * at the end of ep0 configuration, or after unbind. + */ + +/* too wordy: dev_printk(level , &(d)->gadget->dev , fmt , ## args) */ +#define xprintk(d,level,fmt,args...) \ + printk(level "%s: " fmt , shortname , ## args) + +#ifdef DEBUG +#define DBG(dev,fmt,args...) \ + xprintk(dev , KERN_DEBUG , fmt , ## args) +#else +#define DBG(dev,fmt,args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VDEBUG DBG +#else +#define VDEBUG(dev,fmt,args...) \ + do { } while (0) +#endif /* DEBUG */ + +#define ERROR(dev,fmt,args...) \ + xprintk(dev , KERN_ERR , fmt , ## args) +#define INFO(dev,fmt,args...) \ + xprintk(dev , KERN_INFO , fmt , ## args) + + +/*----------------------------------------------------------------------*/ + +/* SYNCHRONOUS ENDPOINT OPERATIONS (bulk/intr/iso) + * + * After opening, configure non-control endpoints. Then use normal + * stream read() and write() requests; and maybe ioctl() to get more + * precise FIFO status when recovering from cancellation. + */ + +static void epio_complete (struct usb_ep *ep, struct usb_request *req) +{ + struct ep_data *epdata = ep->driver_data; + + if (!req->context) + return; + if (req->status) + epdata->status = req->status; + else + epdata->status = req->actual; + complete ((struct completion *)req->context); +} + +/* tasklock endpoint, returning when it's connected. + * still need dev->lock to use epdata->ep. + */ +static int +get_ready_ep (unsigned f_flags, struct ep_data *epdata) +{ + int val; + + if (f_flags & O_NONBLOCK) { + if (!mutex_trylock(&epdata->lock)) + goto nonblock; + if (epdata->state != STATE_EP_ENABLED) { + mutex_unlock(&epdata->lock); +nonblock: + val = -EAGAIN; + } else + val = 0; + return val; + } + + val = mutex_lock_interruptible(&epdata->lock); + if (val < 0) + return val; + + switch (epdata->state) { + case STATE_EP_ENABLED: + break; + // case STATE_EP_DISABLED: /* "can't happen" */ + // case STATE_EP_READY: /* "can't happen" */ + default: /* error! */ + pr_debug ("%s: ep %p not available, state %d\n", + shortname, epdata, epdata->state); + // FALLTHROUGH + case STATE_EP_UNBOUND: /* clean disconnect */ + val = -ENODEV; + mutex_unlock(&epdata->lock); + } + return val; +} + +static ssize_t +ep_io (struct ep_data *epdata, void *buf, unsigned len) +{ + DECLARE_COMPLETION_ONSTACK (done); + int value; + + spin_lock_irq (&epdata->dev->lock); + if (likely (epdata->ep != NULL)) { + struct usb_request *req = epdata->req; + + req->context = &done; + req->complete = epio_complete; + req->buf = buf; + req->length = len; + value = usb_ep_queue (epdata->ep, req, GFP_ATOMIC); + } else + value = -ENODEV; + spin_unlock_irq (&epdata->dev->lock); + + if (likely (value == 0)) { + value = wait_event_interruptible (done.wait, done.done); + if (value != 0) { + spin_lock_irq (&epdata->dev->lock); + if (likely (epdata->ep != NULL)) { + DBG (epdata->dev, "%s i/o interrupted\n", + epdata->name); + usb_ep_dequeue (epdata->ep, epdata->req); + spin_unlock_irq (&epdata->dev->lock); + + wait_event (done.wait, done.done); + if (epdata->status == -ECONNRESET) + epdata->status = -EINTR; + } else { + spin_unlock_irq (&epdata->dev->lock); + + DBG (epdata->dev, "endpoint gone\n"); + epdata->status = -ENODEV; + } + } + return epdata->status; + } + return value; +} + + +/* handle a synchronous OUT bulk/intr/iso transfer */ +static ssize_t +ep_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr) +{ + struct ep_data *data = fd->private_data; + void *kbuf; + ssize_t value; + + if ((value = get_ready_ep (fd->f_flags, data)) < 0) + return value; + + /* halt any endpoint by doing a "wrong direction" i/o call */ + if (usb_endpoint_dir_in(&data->desc)) { + if (usb_endpoint_xfer_isoc(&data->desc)) { + mutex_unlock(&data->lock); + return -EINVAL; + } + DBG (data->dev, "%s halt\n", data->name); + spin_lock_irq (&data->dev->lock); + if (likely (data->ep != NULL)) + usb_ep_set_halt (data->ep); + spin_unlock_irq (&data->dev->lock); + mutex_unlock(&data->lock); + return -EBADMSG; + } + + /* FIXME readahead for O_NONBLOCK and poll(); careful with ZLPs */ + + value = -ENOMEM; + kbuf = kmalloc (len, GFP_KERNEL); + if (unlikely (!kbuf)) + goto free1; + + value = ep_io (data, kbuf, len); + VDEBUG (data->dev, "%s read %zu OUT, status %d\n", + data->name, len, (int) value); + if (value >= 0 && copy_to_user (buf, kbuf, value)) + value = -EFAULT; + +free1: + mutex_unlock(&data->lock); + kfree (kbuf); + return value; +} + +/* handle a synchronous IN bulk/intr/iso transfer */ +static ssize_t +ep_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct ep_data *data = fd->private_data; + void *kbuf; + ssize_t value; + + if ((value = get_ready_ep (fd->f_flags, data)) < 0) + return value; + + /* halt any endpoint by doing a "wrong direction" i/o call */ + if (!usb_endpoint_dir_in(&data->desc)) { + if (usb_endpoint_xfer_isoc(&data->desc)) { + mutex_unlock(&data->lock); + return -EINVAL; + } + DBG (data->dev, "%s halt\n", data->name); + spin_lock_irq (&data->dev->lock); + if (likely (data->ep != NULL)) + usb_ep_set_halt (data->ep); + spin_unlock_irq (&data->dev->lock); + mutex_unlock(&data->lock); + return -EBADMSG; + } + + /* FIXME writebehind for O_NONBLOCK and poll(), qlen = 1 */ + + value = -ENOMEM; + kbuf = kmalloc (len, GFP_KERNEL); + if (!kbuf) + goto free1; + if (copy_from_user (kbuf, buf, len)) { + value = -EFAULT; + goto free1; + } + + value = ep_io (data, kbuf, len); + VDEBUG (data->dev, "%s write %zu IN, status %d\n", + data->name, len, (int) value); +free1: + mutex_unlock(&data->lock); + kfree (kbuf); + return value; +} + +static int +ep_release (struct inode *inode, struct file *fd) +{ + struct ep_data *data = fd->private_data; + int value; + + value = mutex_lock_interruptible(&data->lock); + if (value < 0) + return value; + + /* clean up if this can be reopened */ + if (data->state != STATE_EP_UNBOUND) { + data->state = STATE_EP_DISABLED; + data->desc.bDescriptorType = 0; + data->hs_desc.bDescriptorType = 0; + usb_ep_disable(data->ep); + } + mutex_unlock(&data->lock); + put_ep (data); + return 0; +} + +static long ep_ioctl(struct file *fd, unsigned code, unsigned long value) +{ + struct ep_data *data = fd->private_data; + int status; + + if ((status = get_ready_ep (fd->f_flags, data)) < 0) + return status; + + spin_lock_irq (&data->dev->lock); + if (likely (data->ep != NULL)) { + switch (code) { + case GADGETFS_FIFO_STATUS: + status = usb_ep_fifo_status (data->ep); + break; + case GADGETFS_FIFO_FLUSH: + usb_ep_fifo_flush (data->ep); + break; + case GADGETFS_CLEAR_HALT: + status = usb_ep_clear_halt (data->ep); + break; + default: + status = -ENOTTY; + } + } else + status = -ENODEV; + spin_unlock_irq (&data->dev->lock); + mutex_unlock(&data->lock); + return status; +} + +/*----------------------------------------------------------------------*/ + +/* ASYNCHRONOUS ENDPOINT I/O OPERATIONS (bulk/intr/iso) */ + +struct kiocb_priv { + struct usb_request *req; + struct ep_data *epdata; + struct kiocb *iocb; + struct mm_struct *mm; + struct work_struct work; + void *buf; + const struct iovec *iv; + unsigned long nr_segs; + unsigned actual; +}; + +static int ep_aio_cancel(struct kiocb *iocb, struct io_event *e) +{ + struct kiocb_priv *priv = iocb->private; + struct ep_data *epdata; + int value; + + local_irq_disable(); + epdata = priv->epdata; + // spin_lock(&epdata->dev->lock); + if (likely(epdata && epdata->ep && priv->req)) + value = usb_ep_dequeue (epdata->ep, priv->req); + else + value = -EINVAL; + // spin_unlock(&epdata->dev->lock); + local_irq_enable(); + + aio_put_req(iocb); + return value; +} + +static ssize_t ep_copy_to_user(struct kiocb_priv *priv) +{ + ssize_t len, total; + void *to_copy; + int i; + + /* copy stuff into user buffers */ + total = priv->actual; + len = 0; + to_copy = priv->buf; + for (i=0; i < priv->nr_segs; i++) { + ssize_t this = min((ssize_t)(priv->iv[i].iov_len), total); + + if (copy_to_user(priv->iv[i].iov_base, to_copy, this)) { + if (len == 0) + len = -EFAULT; + break; + } + + total -= this; + len += this; + to_copy += this; + if (total == 0) + break; + } + + return len; +} + +static void ep_user_copy_worker(struct work_struct *work) +{ + struct kiocb_priv *priv = container_of(work, struct kiocb_priv, work); + struct mm_struct *mm = priv->mm; + struct kiocb *iocb = priv->iocb; + size_t ret; + + use_mm(mm); + ret = ep_copy_to_user(priv); + unuse_mm(mm); + + /* completing the iocb can drop the ctx and mm, don't touch mm after */ + aio_complete(iocb, ret, ret); + + kfree(priv->buf); + kfree(priv); +} + +static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct kiocb *iocb = req->context; + struct kiocb_priv *priv = iocb->private; + struct ep_data *epdata = priv->epdata; + + /* lock against disconnect (and ideally, cancel) */ + spin_lock(&epdata->dev->lock); + priv->req = NULL; + priv->epdata = NULL; + + /* if this was a write or a read returning no data then we + * don't need to copy anything to userspace, so we can + * complete the aio request immediately. + */ + if (priv->iv == NULL || unlikely(req->actual == 0)) { + kfree(req->buf); + kfree(priv); + iocb->private = NULL; + /* aio_complete() reports bytes-transferred _and_ faults */ + aio_complete(iocb, req->actual ? req->actual : req->status, + req->status); + } else { + /* ep_copy_to_user() won't report both; we hide some faults */ + if (unlikely(0 != req->status)) + DBG(epdata->dev, "%s fault %d len %d\n", + ep->name, req->status, req->actual); + + priv->buf = req->buf; + priv->actual = req->actual; + schedule_work(&priv->work); + } + spin_unlock(&epdata->dev->lock); + + usb_ep_free_request(ep, req); + put_ep(epdata); +} + +static ssize_t +ep_aio_rwtail( + struct kiocb *iocb, + char *buf, + size_t len, + struct ep_data *epdata, + const struct iovec *iv, + unsigned long nr_segs +) +{ + struct kiocb_priv *priv; + struct usb_request *req; + ssize_t value; + + priv = kmalloc(sizeof *priv, GFP_KERNEL); + if (!priv) { + value = -ENOMEM; +fail: + kfree(buf); + return value; + } + iocb->private = priv; + priv->iocb = iocb; + priv->iv = iv; + priv->nr_segs = nr_segs; + INIT_WORK(&priv->work, ep_user_copy_worker); + + value = get_ready_ep(iocb->ki_filp->f_flags, epdata); + if (unlikely(value < 0)) { + kfree(priv); + goto fail; + } + + kiocb_set_cancel_fn(iocb, ep_aio_cancel); + get_ep(epdata); + priv->epdata = epdata; + priv->actual = 0; + priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ + + /* each kiocb is coupled to one usb_request, but we can't + * allocate or submit those if the host disconnected. + */ + spin_lock_irq(&epdata->dev->lock); + if (likely(epdata->ep)) { + req = usb_ep_alloc_request(epdata->ep, GFP_ATOMIC); + if (likely(req)) { + priv->req = req; + req->buf = buf; + req->length = len; + req->complete = ep_aio_complete; + req->context = iocb; + value = usb_ep_queue(epdata->ep, req, GFP_ATOMIC); + if (unlikely(0 != value)) + usb_ep_free_request(epdata->ep, req); + } else + value = -EAGAIN; + } else + value = -ENODEV; + spin_unlock_irq(&epdata->dev->lock); + + mutex_unlock(&epdata->lock); + + if (unlikely(value)) { + kfree(priv); + put_ep(epdata); + } else + value = -EIOCBQUEUED; + return value; +} + +static ssize_t +ep_aio_read(struct kiocb *iocb, const struct iovec *iov, + unsigned long nr_segs, loff_t o) +{ + struct ep_data *epdata = iocb->ki_filp->private_data; + char *buf; + + if (unlikely(usb_endpoint_dir_in(&epdata->desc))) + return -EINVAL; + + buf = kmalloc(iocb->ki_left, GFP_KERNEL); + if (unlikely(!buf)) + return -ENOMEM; + + return ep_aio_rwtail(iocb, buf, iocb->ki_left, epdata, iov, nr_segs); +} + +static ssize_t +ep_aio_write(struct kiocb *iocb, const struct iovec *iov, + unsigned long nr_segs, loff_t o) +{ + struct ep_data *epdata = iocb->ki_filp->private_data; + char *buf; + size_t len = 0; + int i = 0; + + if (unlikely(!usb_endpoint_dir_in(&epdata->desc))) + return -EINVAL; + + buf = kmalloc(iocb->ki_left, GFP_KERNEL); + if (unlikely(!buf)) + return -ENOMEM; + + for (i=0; i < nr_segs; i++) { + if (unlikely(copy_from_user(&buf[len], iov[i].iov_base, + iov[i].iov_len) != 0)) { + kfree(buf); + return -EFAULT; + } + len += iov[i].iov_len; + } + return ep_aio_rwtail(iocb, buf, len, epdata, NULL, 0); +} + +/*----------------------------------------------------------------------*/ + +/* used after endpoint configuration */ +static const struct file_operations ep_io_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + + .read = ep_read, + .write = ep_write, + .unlocked_ioctl = ep_ioctl, + .release = ep_release, + + .aio_read = ep_aio_read, + .aio_write = ep_aio_write, +}; + +/* ENDPOINT INITIALIZATION + * + * fd = open ("/dev/gadget/$ENDPOINT", O_RDWR) + * status = write (fd, descriptors, sizeof descriptors) + * + * That write establishes the endpoint configuration, configuring + * the controller to process bulk, interrupt, or isochronous transfers + * at the right maxpacket size, and so on. + * + * The descriptors are message type 1, identified by a host order u32 + * at the beginning of what's written. Descriptor order is: full/low + * speed descriptor, then optional high speed descriptor. + */ +static ssize_t +ep_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct ep_data *data = fd->private_data; + struct usb_ep *ep; + u32 tag; + int value, length = len; + + value = mutex_lock_interruptible(&data->lock); + if (value < 0) + return value; + + if (data->state != STATE_EP_READY) { + value = -EL2HLT; + goto fail; + } + + value = len; + if (len < USB_DT_ENDPOINT_SIZE + 4) + goto fail0; + + /* we might need to change message format someday */ + if (copy_from_user (&tag, buf, 4)) { + goto fail1; + } + if (tag != 1) { + DBG(data->dev, "config %s, bad tag %d\n", data->name, tag); + goto fail0; + } + buf += 4; + len -= 4; + + /* NOTE: audio endpoint extensions not accepted here; + * just don't include the extra bytes. + */ + + /* full/low speed descriptor, then high speed */ + if (copy_from_user (&data->desc, buf, USB_DT_ENDPOINT_SIZE)) { + goto fail1; + } + if (data->desc.bLength != USB_DT_ENDPOINT_SIZE + || data->desc.bDescriptorType != USB_DT_ENDPOINT) + goto fail0; + if (len != USB_DT_ENDPOINT_SIZE) { + if (len != 2 * USB_DT_ENDPOINT_SIZE) + goto fail0; + if (copy_from_user (&data->hs_desc, buf + USB_DT_ENDPOINT_SIZE, + USB_DT_ENDPOINT_SIZE)) { + goto fail1; + } + if (data->hs_desc.bLength != USB_DT_ENDPOINT_SIZE + || data->hs_desc.bDescriptorType + != USB_DT_ENDPOINT) { + DBG(data->dev, "config %s, bad hs length or type\n", + data->name); + goto fail0; + } + } + + spin_lock_irq (&data->dev->lock); + if (data->dev->state == STATE_DEV_UNBOUND) { + value = -ENOENT; + goto gone; + } else if ((ep = data->ep) == NULL) { + value = -ENODEV; + goto gone; + } + switch (data->dev->gadget->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + ep->desc = &data->desc; + value = usb_ep_enable(ep); + if (value == 0) + data->state = STATE_EP_ENABLED; + break; + case USB_SPEED_HIGH: + /* fails if caller didn't provide that descriptor... */ + ep->desc = &data->hs_desc; + value = usb_ep_enable(ep); + if (value == 0) + data->state = STATE_EP_ENABLED; + break; + default: + DBG(data->dev, "unconnected, %s init abandoned\n", + data->name); + value = -EINVAL; + } + if (value == 0) { + fd->f_op = &ep_io_operations; + value = length; + } +gone: + spin_unlock_irq (&data->dev->lock); + if (value < 0) { +fail: + data->desc.bDescriptorType = 0; + data->hs_desc.bDescriptorType = 0; + } + mutex_unlock(&data->lock); + return value; +fail0: + value = -EINVAL; + goto fail; +fail1: + value = -EFAULT; + goto fail; +} + +static int +ep_open (struct inode *inode, struct file *fd) +{ + struct ep_data *data = inode->i_private; + int value = -EBUSY; + + if (mutex_lock_interruptible(&data->lock) != 0) + return -EINTR; + spin_lock_irq (&data->dev->lock); + if (data->dev->state == STATE_DEV_UNBOUND) + value = -ENOENT; + else if (data->state == STATE_EP_DISABLED) { + value = 0; + data->state = STATE_EP_READY; + get_ep (data); + fd->private_data = data; + VDEBUG (data->dev, "%s ready\n", data->name); + } else + DBG (data->dev, "%s state %d\n", + data->name, data->state); + spin_unlock_irq (&data->dev->lock); + mutex_unlock(&data->lock); + return value; +} + +/* used before endpoint configuration */ +static const struct file_operations ep_config_operations = { + .llseek = no_llseek, + + .open = ep_open, + .write = ep_config, + .release = ep_release, +}; + +/*----------------------------------------------------------------------*/ + +/* EP0 IMPLEMENTATION can be partly in userspace. + * + * Drivers that use this facility receive various events, including + * control requests the kernel doesn't handle. Drivers that don't + * use this facility may be too simple-minded for real applications. + */ + +static inline void ep0_readable (struct dev_data *dev) +{ + wake_up (&dev->wait); + kill_fasync (&dev->fasync, SIGIO, POLL_IN); +} + +static void clean_req (struct usb_ep *ep, struct usb_request *req) +{ + struct dev_data *dev = ep->driver_data; + + if (req->buf != dev->rbuf) { + kfree(req->buf); + req->buf = dev->rbuf; + } + req->complete = epio_complete; + dev->setup_out_ready = 0; +} + +static void ep0_complete (struct usb_ep *ep, struct usb_request *req) +{ + struct dev_data *dev = ep->driver_data; + unsigned long flags; + int free = 1; + + /* for control OUT, data must still get to userspace */ + spin_lock_irqsave(&dev->lock, flags); + if (!dev->setup_in) { + dev->setup_out_error = (req->status != 0); + if (!dev->setup_out_error) + free = 0; + dev->setup_out_ready = 1; + ep0_readable (dev); + } + + /* clean up as appropriate */ + if (free && req->buf != &dev->rbuf) + clean_req (ep, req); + req->complete = epio_complete; + spin_unlock_irqrestore(&dev->lock, flags); +} + +static int setup_req (struct usb_ep *ep, struct usb_request *req, u16 len) +{ + struct dev_data *dev = ep->driver_data; + + if (dev->setup_out_ready) { + DBG (dev, "ep0 request busy!\n"); + return -EBUSY; + } + if (len > sizeof (dev->rbuf)) + req->buf = kmalloc(len, GFP_ATOMIC); + if (req->buf == NULL) { + req->buf = dev->rbuf; + return -ENOMEM; + } + req->complete = ep0_complete; + req->length = len; + req->zero = 0; + return 0; +} + +static ssize_t +ep0_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr) +{ + struct dev_data *dev = fd->private_data; + ssize_t retval; + enum ep0_state state; + + spin_lock_irq (&dev->lock); + + /* report fd mode change before acting on it */ + if (dev->setup_abort) { + dev->setup_abort = 0; + retval = -EIDRM; + goto done; + } + + /* control DATA stage */ + if ((state = dev->state) == STATE_DEV_SETUP) { + + if (dev->setup_in) { /* stall IN */ + VDEBUG(dev, "ep0in stall\n"); + (void) usb_ep_set_halt (dev->gadget->ep0); + retval = -EL2HLT; + dev->state = STATE_DEV_CONNECTED; + + } else if (len == 0) { /* ack SET_CONFIGURATION etc */ + struct usb_ep *ep = dev->gadget->ep0; + struct usb_request *req = dev->req; + + if ((retval = setup_req (ep, req, 0)) == 0) + retval = usb_ep_queue (ep, req, GFP_ATOMIC); + dev->state = STATE_DEV_CONNECTED; + + /* assume that was SET_CONFIGURATION */ + if (dev->current_config) { + unsigned power; + + if (gadget_is_dualspeed(dev->gadget) + && (dev->gadget->speed + == USB_SPEED_HIGH)) + power = dev->hs_config->bMaxPower; + else + power = dev->config->bMaxPower; + usb_gadget_vbus_draw(dev->gadget, 2 * power); + } + + } else { /* collect OUT data */ + if ((fd->f_flags & O_NONBLOCK) != 0 + && !dev->setup_out_ready) { + retval = -EAGAIN; + goto done; + } + spin_unlock_irq (&dev->lock); + retval = wait_event_interruptible (dev->wait, + dev->setup_out_ready != 0); + + /* FIXME state could change from under us */ + spin_lock_irq (&dev->lock); + if (retval) + goto done; + + if (dev->state != STATE_DEV_SETUP) { + retval = -ECANCELED; + goto done; + } + dev->state = STATE_DEV_CONNECTED; + + if (dev->setup_out_error) + retval = -EIO; + else { + len = min (len, (size_t)dev->req->actual); +// FIXME don't call this with the spinlock held ... + if (copy_to_user (buf, dev->req->buf, len)) + retval = -EFAULT; + else + retval = len; + clean_req (dev->gadget->ep0, dev->req); + /* NOTE userspace can't yet choose to stall */ + } + } + goto done; + } + + /* else normal: return event data */ + if (len < sizeof dev->event [0]) { + retval = -EINVAL; + goto done; + } + len -= len % sizeof (struct usb_gadgetfs_event); + dev->usermode_setup = 1; + +scan: + /* return queued events right away */ + if (dev->ev_next != 0) { + unsigned i, n; + + n = len / sizeof (struct usb_gadgetfs_event); + if (dev->ev_next < n) + n = dev->ev_next; + + /* ep0 i/o has special semantics during STATE_DEV_SETUP */ + for (i = 0; i < n; i++) { + if (dev->event [i].type == GADGETFS_SETUP) { + dev->state = STATE_DEV_SETUP; + n = i + 1; + break; + } + } + spin_unlock_irq (&dev->lock); + len = n * sizeof (struct usb_gadgetfs_event); + if (copy_to_user (buf, &dev->event, len)) + retval = -EFAULT; + else + retval = len; + if (len > 0) { + /* NOTE this doesn't guard against broken drivers; + * concurrent ep0 readers may lose events. + */ + spin_lock_irq (&dev->lock); + if (dev->ev_next > n) { + memmove(&dev->event[0], &dev->event[n], + sizeof (struct usb_gadgetfs_event) + * (dev->ev_next - n)); + } + dev->ev_next -= n; + spin_unlock_irq (&dev->lock); + } + return retval; + } + if (fd->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto done; + } + + switch (state) { + default: + DBG (dev, "fail %s, state %d\n", __func__, state); + retval = -ESRCH; + break; + case STATE_DEV_UNCONNECTED: + case STATE_DEV_CONNECTED: + spin_unlock_irq (&dev->lock); + DBG (dev, "%s wait\n", __func__); + + /* wait for events */ + retval = wait_event_interruptible (dev->wait, + dev->ev_next != 0); + if (retval < 0) + return retval; + spin_lock_irq (&dev->lock); + goto scan; + } + +done: + spin_unlock_irq (&dev->lock); + return retval; +} + +static struct usb_gadgetfs_event * +next_event (struct dev_data *dev, enum usb_gadgetfs_event_type type) +{ + struct usb_gadgetfs_event *event; + unsigned i; + + switch (type) { + /* these events purge the queue */ + case GADGETFS_DISCONNECT: + if (dev->state == STATE_DEV_SETUP) + dev->setup_abort = 1; + // FALL THROUGH + case GADGETFS_CONNECT: + dev->ev_next = 0; + break; + case GADGETFS_SETUP: /* previous request timed out */ + case GADGETFS_SUSPEND: /* same effect */ + /* these events can't be repeated */ + for (i = 0; i != dev->ev_next; i++) { + if (dev->event [i].type != type) + continue; + DBG(dev, "discard old event[%d] %d\n", i, type); + dev->ev_next--; + if (i == dev->ev_next) + break; + /* indices start at zero, for simplicity */ + memmove (&dev->event [i], &dev->event [i + 1], + sizeof (struct usb_gadgetfs_event) + * (dev->ev_next - i)); + } + break; + default: + BUG (); + } + VDEBUG(dev, "event[%d] = %d\n", dev->ev_next, type); + event = &dev->event [dev->ev_next++]; + BUG_ON (dev->ev_next > N_EVENT); + memset (event, 0, sizeof *event); + event->type = type; + return event; +} + +static ssize_t +ep0_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct dev_data *dev = fd->private_data; + ssize_t retval = -ESRCH; + + spin_lock_irq (&dev->lock); + + /* report fd mode change before acting on it */ + if (dev->setup_abort) { + dev->setup_abort = 0; + retval = -EIDRM; + + /* data and/or status stage for control request */ + } else if (dev->state == STATE_DEV_SETUP) { + + /* IN DATA+STATUS caller makes len <= wLength */ + if (dev->setup_in) { + retval = setup_req (dev->gadget->ep0, dev->req, len); + if (retval == 0) { + dev->state = STATE_DEV_CONNECTED; + spin_unlock_irq (&dev->lock); + if (copy_from_user (dev->req->buf, buf, len)) + retval = -EFAULT; + else { + if (len < dev->setup_wLength) + dev->req->zero = 1; + retval = usb_ep_queue ( + dev->gadget->ep0, dev->req, + GFP_KERNEL); + } + if (retval < 0) { + spin_lock_irq (&dev->lock); + clean_req (dev->gadget->ep0, dev->req); + spin_unlock_irq (&dev->lock); + } else + retval = len; + + return retval; + } + + /* can stall some OUT transfers */ + } else if (dev->setup_can_stall) { + VDEBUG(dev, "ep0out stall\n"); + (void) usb_ep_set_halt (dev->gadget->ep0); + retval = -EL2HLT; + dev->state = STATE_DEV_CONNECTED; + } else { + DBG(dev, "bogus ep0out stall!\n"); + } + } else + DBG (dev, "fail %s, state %d\n", __func__, dev->state); + + spin_unlock_irq (&dev->lock); + return retval; +} + +static int +ep0_fasync (int f, struct file *fd, int on) +{ + struct dev_data *dev = fd->private_data; + // caller must F_SETOWN before signal delivery happens + VDEBUG (dev, "%s %s\n", __func__, on ? "on" : "off"); + return fasync_helper (f, fd, on, &dev->fasync); +} + +static struct usb_gadget_driver gadgetfs_driver; + +static int +dev_release (struct inode *inode, struct file *fd) +{ + struct dev_data *dev = fd->private_data; + + /* closing ep0 === shutdown all */ + + usb_gadget_unregister_driver (&gadgetfs_driver); + + /* at this point "good" hardware has disconnected the + * device from USB; the host won't see it any more. + * alternatively, all host requests will time out. + */ + + kfree (dev->buf); + dev->buf = NULL; + put_dev (dev); + + /* other endpoints were all decoupled from this device */ + spin_lock_irq(&dev->lock); + dev->state = STATE_DEV_DISABLED; + spin_unlock_irq(&dev->lock); + return 0; +} + +static unsigned int +ep0_poll (struct file *fd, poll_table *wait) +{ + struct dev_data *dev = fd->private_data; + int mask = 0; + + poll_wait(fd, &dev->wait, wait); + + spin_lock_irq (&dev->lock); + + /* report fd mode change before acting on it */ + if (dev->setup_abort) { + dev->setup_abort = 0; + mask = POLLHUP; + goto out; + } + + if (dev->state == STATE_DEV_SETUP) { + if (dev->setup_in || dev->setup_can_stall) + mask = POLLOUT; + } else { + if (dev->ev_next != 0) + mask = POLLIN; + } +out: + spin_unlock_irq(&dev->lock); + return mask; +} + +static long dev_ioctl (struct file *fd, unsigned code, unsigned long value) +{ + struct dev_data *dev = fd->private_data; + struct usb_gadget *gadget = dev->gadget; + long ret = -ENOTTY; + + if (gadget->ops->ioctl) + ret = gadget->ops->ioctl (gadget, code, value); + + return ret; +} + +/* used after device configuration */ +static const struct file_operations ep0_io_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + + .read = ep0_read, + .write = ep0_write, + .fasync = ep0_fasync, + .poll = ep0_poll, + .unlocked_ioctl = dev_ioctl, + .release = dev_release, +}; + +/*----------------------------------------------------------------------*/ + +/* The in-kernel gadget driver handles most ep0 issues, in particular + * enumerating the single configuration (as provided from user space). + * + * Unrecognized ep0 requests may be handled in user space. + */ + +static void make_qualifier (struct dev_data *dev) +{ + struct usb_qualifier_descriptor qual; + struct usb_device_descriptor *desc; + + qual.bLength = sizeof qual; + qual.bDescriptorType = USB_DT_DEVICE_QUALIFIER; + qual.bcdUSB = cpu_to_le16 (0x0200); + + desc = dev->dev; + qual.bDeviceClass = desc->bDeviceClass; + qual.bDeviceSubClass = desc->bDeviceSubClass; + qual.bDeviceProtocol = desc->bDeviceProtocol; + + /* assumes ep0 uses the same value for both speeds ... */ + qual.bMaxPacketSize0 = dev->gadget->ep0->maxpacket; + + qual.bNumConfigurations = 1; + qual.bRESERVED = 0; + + memcpy (dev->rbuf, &qual, sizeof qual); +} + +static int +config_buf (struct dev_data *dev, u8 type, unsigned index) +{ + int len; + int hs = 0; + + /* only one configuration */ + if (index > 0) + return -EINVAL; + + if (gadget_is_dualspeed(dev->gadget)) { + hs = (dev->gadget->speed == USB_SPEED_HIGH); + if (type == USB_DT_OTHER_SPEED_CONFIG) + hs = !hs; + } + if (hs) { + dev->req->buf = dev->hs_config; + len = le16_to_cpu(dev->hs_config->wTotalLength); + } else { + dev->req->buf = dev->config; + len = le16_to_cpu(dev->config->wTotalLength); + } + ((u8 *)dev->req->buf) [1] = type; + return len; +} + +static int +gadgetfs_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + struct dev_data *dev = get_gadget_data (gadget); + struct usb_request *req = dev->req; + int value = -EOPNOTSUPP; + struct usb_gadgetfs_event *event; + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + spin_lock (&dev->lock); + dev->setup_abort = 0; + if (dev->state == STATE_DEV_UNCONNECTED) { + if (gadget_is_dualspeed(gadget) + && gadget->speed == USB_SPEED_HIGH + && dev->hs_config == NULL) { + spin_unlock(&dev->lock); + ERROR (dev, "no high speed config??\n"); + return -EINVAL; + } + + dev->state = STATE_DEV_CONNECTED; + + INFO (dev, "connected\n"); + event = next_event (dev, GADGETFS_CONNECT); + event->u.speed = gadget->speed; + ep0_readable (dev); + + /* host may have given up waiting for response. we can miss control + * requests handled lower down (device/endpoint status and features); + * then ep0_{read,write} will report the wrong status. controller + * driver will have aborted pending i/o. + */ + } else if (dev->state == STATE_DEV_SETUP) + dev->setup_abort = 1; + + req->buf = dev->rbuf; + req->context = NULL; + value = -EOPNOTSUPP; + switch (ctrl->bRequest) { + + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + goto unrecognized; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + value = min (w_length, (u16) sizeof *dev->dev); + dev->dev->bMaxPacketSize0 = dev->gadget->ep0->maxpacket; + req->buf = dev->dev; + break; + case USB_DT_DEVICE_QUALIFIER: + if (!dev->hs_config) + break; + value = min (w_length, (u16) + sizeof (struct usb_qualifier_descriptor)); + make_qualifier (dev); + break; + case USB_DT_OTHER_SPEED_CONFIG: + // FALLTHROUGH + case USB_DT_CONFIG: + value = config_buf (dev, + w_value >> 8, + w_value & 0xff); + if (value >= 0) + value = min (w_length, (u16) value); + break; + case USB_DT_STRING: + goto unrecognized; + + default: // all others are errors + break; + } + break; + + /* currently one config, two speeds */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != 0) + goto unrecognized; + if (0 == (u8) w_value) { + value = 0; + dev->current_config = 0; + usb_gadget_vbus_draw(gadget, 8 /* mA */ ); + // user mode expected to disable endpoints + } else { + u8 config, power; + + if (gadget_is_dualspeed(gadget) + && gadget->speed == USB_SPEED_HIGH) { + config = dev->hs_config->bConfigurationValue; + power = dev->hs_config->bMaxPower; + } else { + config = dev->config->bConfigurationValue; + power = dev->config->bMaxPower; + } + + if (config == (u8) w_value) { + value = 0; + dev->current_config = config; + usb_gadget_vbus_draw(gadget, 2 * power); + } + } + + /* report SET_CONFIGURATION like any other control request, + * except that usermode may not stall this. the next + * request mustn't be allowed start until this finishes: + * endpoints and threads set up, etc. + * + * NOTE: older PXA hardware (before PXA 255: without UDCCFR) + * has bad/racey automagic that prevents synchronizing here. + * even kernel mode drivers often miss them. + */ + if (value == 0) { + INFO (dev, "configuration #%d\n", dev->current_config); + if (dev->usermode_setup) { + dev->setup_can_stall = 0; + goto delegate; + } + } + break; + +#ifndef CONFIG_USB_PXA25X + /* PXA automagically handles this request too */ + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != 0x80) + goto unrecognized; + *(u8 *)req->buf = dev->current_config; + value = min (w_length, (u16) 1); + break; +#endif + + default: +unrecognized: + VDEBUG (dev, "%s req%02x.%02x v%04x i%04x l%d\n", + dev->usermode_setup ? "delegate" : "fail", + ctrl->bRequestType, ctrl->bRequest, + w_value, le16_to_cpu(ctrl->wIndex), w_length); + + /* if there's an ep0 reader, don't stall */ + if (dev->usermode_setup) { + dev->setup_can_stall = 1; +delegate: + dev->setup_in = (ctrl->bRequestType & USB_DIR_IN) + ? 1 : 0; + dev->setup_wLength = w_length; + dev->setup_out_ready = 0; + dev->setup_out_error = 0; + value = 0; + + /* read DATA stage for OUT right away */ + if (unlikely (!dev->setup_in && w_length)) { + value = setup_req (gadget->ep0, dev->req, + w_length); + if (value < 0) + break; + value = usb_ep_queue (gadget->ep0, dev->req, + GFP_ATOMIC); + if (value < 0) { + clean_req (gadget->ep0, dev->req); + break; + } + + /* we can't currently stall these */ + dev->setup_can_stall = 0; + } + + /* state changes when reader collects event */ + event = next_event (dev, GADGETFS_SETUP); + event->u.setup = *ctrl; + ep0_readable (dev); + spin_unlock (&dev->lock); + return 0; + } + } + + /* proceed with data transfer and status phases? */ + if (value >= 0 && dev->state != STATE_DEV_SETUP) { + req->length = value; + req->zero = value < w_length; + value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + DBG (dev, "ep_queue --> %d\n", value); + req->status = 0; + } + } + + /* device stalls when value < 0 */ + spin_unlock (&dev->lock); + return value; +} + +static void destroy_ep_files (struct dev_data *dev) +{ + DBG (dev, "%s %d\n", __func__, dev->state); + + /* dev->state must prevent interference */ + spin_lock_irq (&dev->lock); + while (!list_empty(&dev->epfiles)) { + struct ep_data *ep; + struct inode *parent; + struct dentry *dentry; + + /* break link to FS */ + ep = list_first_entry (&dev->epfiles, struct ep_data, epfiles); + list_del_init (&ep->epfiles); + dentry = ep->dentry; + ep->dentry = NULL; + parent = dentry->d_parent->d_inode; + + /* break link to controller */ + if (ep->state == STATE_EP_ENABLED) + (void) usb_ep_disable (ep->ep); + ep->state = STATE_EP_UNBOUND; + usb_ep_free_request (ep->ep, ep->req); + ep->ep = NULL; + wake_up (&ep->wait); + put_ep (ep); + + spin_unlock_irq (&dev->lock); + + /* break link to dcache */ + mutex_lock (&parent->i_mutex); + d_delete (dentry); + dput (dentry); + mutex_unlock (&parent->i_mutex); + + spin_lock_irq (&dev->lock); + } + spin_unlock_irq (&dev->lock); +} + + +static struct inode * +gadgetfs_create_file (struct super_block *sb, char const *name, + void *data, const struct file_operations *fops, + struct dentry **dentry_p); + +static int activate_ep_files (struct dev_data *dev) +{ + struct usb_ep *ep; + struct ep_data *data; + + gadget_for_each_ep (ep, dev->gadget) { + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + goto enomem0; + data->state = STATE_EP_DISABLED; + mutex_init(&data->lock); + init_waitqueue_head (&data->wait); + + strncpy (data->name, ep->name, sizeof (data->name) - 1); + atomic_set (&data->count, 1); + data->dev = dev; + get_dev (dev); + + data->ep = ep; + ep->driver_data = data; + + data->req = usb_ep_alloc_request (ep, GFP_KERNEL); + if (!data->req) + goto enomem1; + + data->inode = gadgetfs_create_file (dev->sb, data->name, + data, &ep_config_operations, + &data->dentry); + if (!data->inode) + goto enomem2; + list_add_tail (&data->epfiles, &dev->epfiles); + } + return 0; + +enomem2: + usb_ep_free_request (ep, data->req); +enomem1: + put_dev (dev); + kfree (data); +enomem0: + DBG (dev, "%s enomem\n", __func__); + destroy_ep_files (dev); + return -ENOMEM; +} + +static void +gadgetfs_unbind (struct usb_gadget *gadget) +{ + struct dev_data *dev = get_gadget_data (gadget); + + DBG (dev, "%s\n", __func__); + + spin_lock_irq (&dev->lock); + dev->state = STATE_DEV_UNBOUND; + spin_unlock_irq (&dev->lock); + + destroy_ep_files (dev); + gadget->ep0->driver_data = NULL; + set_gadget_data (gadget, NULL); + + /* we've already been disconnected ... no i/o is active */ + if (dev->req) + usb_ep_free_request (gadget->ep0, dev->req); + DBG (dev, "%s done\n", __func__); + put_dev (dev); +} + +static struct dev_data *the_device; + +static int gadgetfs_bind(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct dev_data *dev = the_device; + + if (!dev) + return -ESRCH; + if (0 != strcmp (CHIP, gadget->name)) { + pr_err("%s expected %s controller not %s\n", + shortname, CHIP, gadget->name); + return -ENODEV; + } + + set_gadget_data (gadget, dev); + dev->gadget = gadget; + gadget->ep0->driver_data = dev; + + /* preallocate control response and buffer */ + dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL); + if (!dev->req) + goto enomem; + dev->req->context = NULL; + dev->req->complete = epio_complete; + + if (activate_ep_files (dev) < 0) + goto enomem; + + INFO (dev, "bound to %s driver\n", gadget->name); + spin_lock_irq(&dev->lock); + dev->state = STATE_DEV_UNCONNECTED; + spin_unlock_irq(&dev->lock); + get_dev (dev); + return 0; + +enomem: + gadgetfs_unbind (gadget); + return -ENOMEM; +} + +static void +gadgetfs_disconnect (struct usb_gadget *gadget) +{ + struct dev_data *dev = get_gadget_data (gadget); + unsigned long flags; + + spin_lock_irqsave (&dev->lock, flags); + if (dev->state == STATE_DEV_UNCONNECTED) + goto exit; + dev->state = STATE_DEV_UNCONNECTED; + + INFO (dev, "disconnected\n"); + next_event (dev, GADGETFS_DISCONNECT); + ep0_readable (dev); +exit: + spin_unlock_irqrestore (&dev->lock, flags); +} + +static void +gadgetfs_suspend (struct usb_gadget *gadget) +{ + struct dev_data *dev = get_gadget_data (gadget); + + INFO (dev, "suspended from state %d\n", dev->state); + spin_lock (&dev->lock); + switch (dev->state) { + case STATE_DEV_SETUP: // VERY odd... host died?? + case STATE_DEV_CONNECTED: + case STATE_DEV_UNCONNECTED: + next_event (dev, GADGETFS_SUSPEND); + ep0_readable (dev); + /* FALLTHROUGH */ + default: + break; + } + spin_unlock (&dev->lock); +} + +static struct usb_gadget_driver gadgetfs_driver = { + .function = (char *) driver_desc, + .bind = gadgetfs_bind, + .unbind = gadgetfs_unbind, + .setup = gadgetfs_setup, + .disconnect = gadgetfs_disconnect, + .suspend = gadgetfs_suspend, + + .driver = { + .name = (char *) shortname, + }, +}; + +/*----------------------------------------------------------------------*/ + +static void gadgetfs_nop(struct usb_gadget *arg) { } + +static int gadgetfs_probe(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + CHIP = gadget->name; + return -EISNAM; +} + +static struct usb_gadget_driver probe_driver = { + .max_speed = USB_SPEED_HIGH, + .bind = gadgetfs_probe, + .unbind = gadgetfs_nop, + .setup = (void *)gadgetfs_nop, + .disconnect = gadgetfs_nop, + .driver = { + .name = "nop", + }, +}; + + +/* DEVICE INITIALIZATION + * + * fd = open ("/dev/gadget/$CHIP", O_RDWR) + * status = write (fd, descriptors, sizeof descriptors) + * + * That write establishes the device configuration, so the kernel can + * bind to the controller ... guaranteeing it can handle enumeration + * at all necessary speeds. Descriptor order is: + * + * . message tag (u32, host order) ... for now, must be zero; it + * would change to support features like multi-config devices + * . full/low speed config ... all wTotalLength bytes (with interface, + * class, altsetting, endpoint, and other descriptors) + * . high speed config ... all descriptors, for high speed operation; + * this one's optional except for high-speed hardware + * . device descriptor + * + * Endpoints are not yet enabled. Drivers must wait until device + * configuration and interface altsetting changes create + * the need to configure (or unconfigure) them. + * + * After initialization, the device stays active for as long as that + * $CHIP file is open. Events must then be read from that descriptor, + * such as configuration notifications. + */ + +static int is_valid_config (struct usb_config_descriptor *config) +{ + return config->bDescriptorType == USB_DT_CONFIG + && config->bLength == USB_DT_CONFIG_SIZE + && config->bConfigurationValue != 0 + && (config->bmAttributes & USB_CONFIG_ATT_ONE) != 0 + && (config->bmAttributes & USB_CONFIG_ATT_WAKEUP) == 0; + /* FIXME if gadget->is_otg, _must_ include an otg descriptor */ + /* FIXME check lengths: walk to end */ +} + +static ssize_t +dev_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct dev_data *dev = fd->private_data; + ssize_t value = len, length = len; + unsigned total; + u32 tag; + char *kbuf; + + if (len < (USB_DT_CONFIG_SIZE + USB_DT_DEVICE_SIZE + 4)) + return -EINVAL; + + /* we might need to change message format someday */ + if (copy_from_user (&tag, buf, 4)) + return -EFAULT; + if (tag != 0) + return -EINVAL; + buf += 4; + length -= 4; + + kbuf = memdup_user(buf, length); + if (IS_ERR(kbuf)) + return PTR_ERR(kbuf); + + spin_lock_irq (&dev->lock); + value = -EINVAL; + if (dev->buf) + goto fail; + dev->buf = kbuf; + + /* full or low speed config */ + dev->config = (void *) kbuf; + total = le16_to_cpu(dev->config->wTotalLength); + if (!is_valid_config (dev->config) || total >= length) + goto fail; + kbuf += total; + length -= total; + + /* optional high speed config */ + if (kbuf [1] == USB_DT_CONFIG) { + dev->hs_config = (void *) kbuf; + total = le16_to_cpu(dev->hs_config->wTotalLength); + if (!is_valid_config (dev->hs_config) || total >= length) + goto fail; + kbuf += total; + length -= total; + } + + /* could support multiple configs, using another encoding! */ + + /* device descriptor (tweaked for paranoia) */ + if (length != USB_DT_DEVICE_SIZE) + goto fail; + dev->dev = (void *)kbuf; + if (dev->dev->bLength != USB_DT_DEVICE_SIZE + || dev->dev->bDescriptorType != USB_DT_DEVICE + || dev->dev->bNumConfigurations != 1) + goto fail; + dev->dev->bNumConfigurations = 1; + dev->dev->bcdUSB = cpu_to_le16 (0x0200); + + /* triggers gadgetfs_bind(); then we can enumerate. */ + spin_unlock_irq (&dev->lock); + if (dev->hs_config) + gadgetfs_driver.max_speed = USB_SPEED_HIGH; + else + gadgetfs_driver.max_speed = USB_SPEED_FULL; + + value = usb_gadget_probe_driver(&gadgetfs_driver); + if (value != 0) { + kfree (dev->buf); + dev->buf = NULL; + } else { + /* at this point "good" hardware has for the first time + * let the USB the host see us. alternatively, if users + * unplug/replug that will clear all the error state. + * + * note: everything running before here was guaranteed + * to choke driver model style diagnostics. from here + * on, they can work ... except in cleanup paths that + * kick in after the ep0 descriptor is closed. + */ + fd->f_op = &ep0_io_operations; + value = len; + } + return value; + +fail: + spin_unlock_irq (&dev->lock); + pr_debug ("%s: %s fail %Zd, %p\n", shortname, __func__, value, dev); + kfree (dev->buf); + dev->buf = NULL; + return value; +} + +static int +dev_open (struct inode *inode, struct file *fd) +{ + struct dev_data *dev = inode->i_private; + int value = -EBUSY; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_DEV_DISABLED) { + dev->ev_next = 0; + dev->state = STATE_DEV_OPENED; + fd->private_data = dev; + get_dev (dev); + value = 0; + } + spin_unlock_irq(&dev->lock); + return value; +} + +static const struct file_operations dev_init_operations = { + .llseek = no_llseek, + + .open = dev_open, + .write = dev_config, + .fasync = ep0_fasync, + .unlocked_ioctl = dev_ioctl, + .release = dev_release, +}; + +/*----------------------------------------------------------------------*/ + +/* FILESYSTEM AND SUPERBLOCK OPERATIONS + * + * Mounting the filesystem creates a controller file, used first for + * device configuration then later for event monitoring. + */ + + +/* FIXME PAM etc could set this security policy without mount options + * if epfiles inherited ownership and permissons from ep0 ... + */ + +static unsigned default_uid; +static unsigned default_gid; +static unsigned default_perm = S_IRUSR | S_IWUSR; + +module_param (default_uid, uint, 0644); +module_param (default_gid, uint, 0644); +module_param (default_perm, uint, 0644); + + +static struct inode * +gadgetfs_make_inode (struct super_block *sb, + void *data, const struct file_operations *fops, + int mode) +{ + struct inode *inode = new_inode (sb); + + if (inode) { + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_uid = make_kuid(&init_user_ns, default_uid); + inode->i_gid = make_kgid(&init_user_ns, default_gid); + inode->i_atime = inode->i_mtime = inode->i_ctime + = CURRENT_TIME; + inode->i_private = data; + inode->i_fop = fops; + } + return inode; +} + +/* creates in fs root directory, so non-renamable and non-linkable. + * so inode and dentry are paired, until device reconfig. + */ +static struct inode * +gadgetfs_create_file (struct super_block *sb, char const *name, + void *data, const struct file_operations *fops, + struct dentry **dentry_p) +{ + struct dentry *dentry; + struct inode *inode; + + dentry = d_alloc_name(sb->s_root, name); + if (!dentry) + return NULL; + + inode = gadgetfs_make_inode (sb, data, fops, + S_IFREG | (default_perm & S_IRWXUGO)); + if (!inode) { + dput(dentry); + return NULL; + } + d_add (dentry, inode); + *dentry_p = dentry; + return inode; +} + +static const struct super_operations gadget_fs_operations = { + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, +}; + +static int +gadgetfs_fill_super (struct super_block *sb, void *opts, int silent) +{ + struct inode *inode; + struct dev_data *dev; + + if (the_device) + return -ESRCH; + + /* fake probe to determine $CHIP */ + usb_gadget_probe_driver(&probe_driver); + if (!CHIP) + return -ENODEV; + + /* superblock */ + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = GADGETFS_MAGIC; + sb->s_op = &gadget_fs_operations; + sb->s_time_gran = 1; + + /* root inode */ + inode = gadgetfs_make_inode (sb, + NULL, &simple_dir_operations, + S_IFDIR | S_IRUGO | S_IXUGO); + if (!inode) + goto Enomem; + inode->i_op = &simple_dir_inode_operations; + if (!(sb->s_root = d_make_root (inode))) + goto Enomem; + + /* the ep0 file is named after the controller we expect; + * user mode code can use it for sanity checks, like we do. + */ + dev = dev_new (); + if (!dev) + goto Enomem; + + dev->sb = sb; + if (!gadgetfs_create_file (sb, CHIP, + dev, &dev_init_operations, + &dev->dentry)) { + put_dev(dev); + goto Enomem; + } + + /* other endpoint files are available after hardware setup, + * from binding to a controller. + */ + the_device = dev; + return 0; + +Enomem: + return -ENOMEM; +} + +/* "mount -t gadgetfs path /dev/gadget" ends up here */ +static struct dentry * +gadgetfs_mount (struct file_system_type *t, int flags, + const char *path, void *opts) +{ + return mount_single (t, flags, opts, gadgetfs_fill_super); +} + +static void +gadgetfs_kill_sb (struct super_block *sb) +{ + kill_litter_super (sb); + if (the_device) { + put_dev (the_device); + the_device = NULL; + } +} + +/*----------------------------------------------------------------------*/ + +static struct file_system_type gadgetfs_type = { + .owner = THIS_MODULE, + .name = shortname, + .mount = gadgetfs_mount, + .kill_sb = gadgetfs_kill_sb, +}; +MODULE_ALIAS_FS("gadgetfs"); + +/*----------------------------------------------------------------------*/ + +static int __init init (void) +{ + int status; + + status = register_filesystem (&gadgetfs_type); + if (status == 0) + pr_info ("%s: %s, version " DRIVER_VERSION "\n", + shortname, driver_desc); + return status; +} +module_init (init); + +static void __exit cleanup (void) +{ + pr_debug ("unregister %s\n", shortname); + unregister_filesystem (&gadgetfs_type); +} +module_exit (cleanup); + diff --git a/drivers/usb/gadget/lpc32xx_udc.c b/drivers/usb/gadget/lpc32xx_udc.c new file mode 100644 index 00000000..67128be1 --- /dev/null +++ b/drivers/usb/gadget/lpc32xx_udc.c @@ -0,0 +1,3424 @@ +/* + * USB Gadget driver for LPC32xx + * + * Authors: + * Kevin Wells + * Mike James + * Roland Stigge + * + * Copyright (C) 2006 Philips Semiconductors + * Copyright (C) 2009 NXP Semiconductors + * Copyright (C) 2012 Roland Stigge + * + * Note: This driver is based on original work done by Mike James for + * the LPC3180. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef CONFIG_USB_GADGET_DEBUG_FILES +#include +#include +#endif + +/* + * USB device configuration structure + */ +typedef void (*usc_chg_event)(int); +struct lpc32xx_usbd_cfg { + int vbus_drv_pol; /* 0=active low drive for VBUS via ISP1301 */ + usc_chg_event conn_chgb; /* Connection change event (optional) */ + usc_chg_event susp_chgb; /* Suspend/resume event (optional) */ + usc_chg_event rmwk_chgb; /* Enable/disable remote wakeup */ +}; + +/* + * controller driver data structures + */ + +/* 16 endpoints (not to be confused with 32 hardware endpoints) */ +#define NUM_ENDPOINTS 16 + +/* + * IRQ indices make reading the code a little easier + */ +#define IRQ_USB_LP 0 +#define IRQ_USB_HP 1 +#define IRQ_USB_DEVDMA 2 +#define IRQ_USB_ATX 3 + +#define EP_OUT 0 /* RX (from host) */ +#define EP_IN 1 /* TX (to host) */ + +/* Returns the interrupt mask for the selected hardware endpoint */ +#define EP_MASK_SEL(ep, dir) (1 << (((ep) * 2) + dir)) + +#define EP_INT_TYPE 0 +#define EP_ISO_TYPE 1 +#define EP_BLK_TYPE 2 +#define EP_CTL_TYPE 3 + +/* EP0 states */ +#define WAIT_FOR_SETUP 0 /* Wait for setup packet */ +#define DATA_IN 1 /* Expect dev->host transfer */ +#define DATA_OUT 2 /* Expect host->dev transfer */ + +/* DD (DMA Descriptor) structure, requires word alignment, this is already + * defined in the LPC32XX USB device header file, but this version is slightly + * modified to tag some work data with each DMA descriptor. */ +struct lpc32xx_usbd_dd_gad { + u32 dd_next_phy; + u32 dd_setup; + u32 dd_buffer_addr; + u32 dd_status; + u32 dd_iso_ps_mem_addr; + u32 this_dma; + u32 iso_status[6]; /* 5 spare */ + u32 dd_next_v; +}; + +/* + * Logical endpoint structure + */ +struct lpc32xx_ep { + struct usb_ep ep; + struct list_head queue; + struct lpc32xx_udc *udc; + + u32 hwep_num_base; /* Physical hardware EP */ + u32 hwep_num; /* Maps to hardware endpoint */ + u32 maxpacket; + u32 lep; + + bool is_in; + bool req_pending; + u32 eptype; + + u32 totalints; + + bool wedge; +}; + +/* + * Common UDC structure + */ +struct lpc32xx_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + struct device *dev; + struct dentry *pde; + spinlock_t lock; + struct i2c_client *isp1301_i2c_client; + + /* Board and device specific */ + struct lpc32xx_usbd_cfg *board; + u32 io_p_start; + u32 io_p_size; + void __iomem *udp_baseaddr; + int udp_irq[4]; + struct clk *usb_pll_clk; + struct clk *usb_slv_clk; + struct clk *usb_otg_clk; + + /* DMA support */ + u32 *udca_v_base; + u32 udca_p_base; + struct dma_pool *dd_cache; + + /* Common EP and control data */ + u32 enabled_devints; + u32 enabled_hwepints; + u32 dev_status; + u32 realized_eps; + + /* VBUS detection, pullup, and power flags */ + u8 vbus; + u8 last_vbus; + int pullup; + int poweron; + + /* Work queues related to I2C support */ + struct work_struct pullup_job; + struct work_struct vbus_job; + struct work_struct power_job; + + /* USB device peripheral - various */ + struct lpc32xx_ep ep[NUM_ENDPOINTS]; + bool enabled; + bool clocked; + bool suspended; + bool selfpowered; + int ep0state; + atomic_t enabled_ep_cnt; + wait_queue_head_t ep_disable_wait_queue; +}; + +/* + * Endpoint request + */ +struct lpc32xx_request { + struct usb_request req; + struct list_head queue; + struct lpc32xx_usbd_dd_gad *dd_desc_ptr; + bool mapped; + bool send_zlp; +}; + +static inline struct lpc32xx_udc *to_udc(struct usb_gadget *g) +{ + return container_of(g, struct lpc32xx_udc, gadget); +} + +#define ep_dbg(epp, fmt, arg...) \ + dev_dbg(epp->udc->dev, "%s: " fmt, __func__, ## arg) +#define ep_err(epp, fmt, arg...) \ + dev_err(epp->udc->dev, "%s: " fmt, __func__, ## arg) +#define ep_info(epp, fmt, arg...) \ + dev_info(epp->udc->dev, "%s: " fmt, __func__, ## arg) +#define ep_warn(epp, fmt, arg...) \ + dev_warn(epp->udc->dev, "%s:" fmt, __func__, ## arg) + +#define UDCA_BUFF_SIZE (128) + +/* TODO: When the clock framework is introduced in LPC32xx, IO_ADDRESS will + * be replaced with an inremap()ed pointer + * */ +#define USB_CTRL IO_ADDRESS(LPC32XX_CLK_PM_BASE + 0x64) + +/* USB_CTRL bit defines */ +#define USB_SLAVE_HCLK_EN (1 << 24) +#define USB_HOST_NEED_CLK_EN (1 << 21) +#define USB_DEV_NEED_CLK_EN (1 << 22) + +/********************************************************************** + * USB device controller register offsets + **********************************************************************/ + +#define USBD_DEVINTST(x) ((x) + 0x200) +#define USBD_DEVINTEN(x) ((x) + 0x204) +#define USBD_DEVINTCLR(x) ((x) + 0x208) +#define USBD_DEVINTSET(x) ((x) + 0x20C) +#define USBD_CMDCODE(x) ((x) + 0x210) +#define USBD_CMDDATA(x) ((x) + 0x214) +#define USBD_RXDATA(x) ((x) + 0x218) +#define USBD_TXDATA(x) ((x) + 0x21C) +#define USBD_RXPLEN(x) ((x) + 0x220) +#define USBD_TXPLEN(x) ((x) + 0x224) +#define USBD_CTRL(x) ((x) + 0x228) +#define USBD_DEVINTPRI(x) ((x) + 0x22C) +#define USBD_EPINTST(x) ((x) + 0x230) +#define USBD_EPINTEN(x) ((x) + 0x234) +#define USBD_EPINTCLR(x) ((x) + 0x238) +#define USBD_EPINTSET(x) ((x) + 0x23C) +#define USBD_EPINTPRI(x) ((x) + 0x240) +#define USBD_REEP(x) ((x) + 0x244) +#define USBD_EPIND(x) ((x) + 0x248) +#define USBD_EPMAXPSIZE(x) ((x) + 0x24C) +/* DMA support registers only below */ +/* Set, clear, or get enabled state of the DMA request status. If + * enabled, an IN or OUT token will start a DMA transfer for the EP */ +#define USBD_DMARST(x) ((x) + 0x250) +#define USBD_DMARCLR(x) ((x) + 0x254) +#define USBD_DMARSET(x) ((x) + 0x258) +/* DMA UDCA head pointer */ +#define USBD_UDCAH(x) ((x) + 0x280) +/* EP DMA status, enable, and disable. This is used to specifically + * enabled or disable DMA for a specific EP */ +#define USBD_EPDMAST(x) ((x) + 0x284) +#define USBD_EPDMAEN(x) ((x) + 0x288) +#define USBD_EPDMADIS(x) ((x) + 0x28C) +/* DMA master interrupts enable and pending interrupts */ +#define USBD_DMAINTST(x) ((x) + 0x290) +#define USBD_DMAINTEN(x) ((x) + 0x294) +/* DMA end of transfer interrupt enable, disable, status */ +#define USBD_EOTINTST(x) ((x) + 0x2A0) +#define USBD_EOTINTCLR(x) ((x) + 0x2A4) +#define USBD_EOTINTSET(x) ((x) + 0x2A8) +/* New DD request interrupt enable, disable, status */ +#define USBD_NDDRTINTST(x) ((x) + 0x2AC) +#define USBD_NDDRTINTCLR(x) ((x) + 0x2B0) +#define USBD_NDDRTINTSET(x) ((x) + 0x2B4) +/* DMA error interrupt enable, disable, status */ +#define USBD_SYSERRTINTST(x) ((x) + 0x2B8) +#define USBD_SYSERRTINTCLR(x) ((x) + 0x2BC) +#define USBD_SYSERRTINTSET(x) ((x) + 0x2C0) + +/********************************************************************** + * USBD_DEVINTST/USBD_DEVINTEN/USBD_DEVINTCLR/USBD_DEVINTSET/ + * USBD_DEVINTPRI register definitions + **********************************************************************/ +#define USBD_ERR_INT (1 << 9) +#define USBD_EP_RLZED (1 << 8) +#define USBD_TXENDPKT (1 << 7) +#define USBD_RXENDPKT (1 << 6) +#define USBD_CDFULL (1 << 5) +#define USBD_CCEMPTY (1 << 4) +#define USBD_DEV_STAT (1 << 3) +#define USBD_EP_SLOW (1 << 2) +#define USBD_EP_FAST (1 << 1) +#define USBD_FRAME (1 << 0) + +/********************************************************************** + * USBD_EPINTST/USBD_EPINTEN/USBD_EPINTCLR/USBD_EPINTSET/ + * USBD_EPINTPRI register definitions + **********************************************************************/ +/* End point selection macro (RX) */ +#define USBD_RX_EP_SEL(e) (1 << ((e) << 1)) + +/* End point selection macro (TX) */ +#define USBD_TX_EP_SEL(e) (1 << (((e) << 1) + 1)) + +/********************************************************************** + * USBD_REEP/USBD_DMARST/USBD_DMARCLR/USBD_DMARSET/USBD_EPDMAST/ + * USBD_EPDMAEN/USBD_EPDMADIS/ + * USBD_NDDRTINTST/USBD_NDDRTINTCLR/USBD_NDDRTINTSET/ + * USBD_EOTINTST/USBD_EOTINTCLR/USBD_EOTINTSET/ + * USBD_SYSERRTINTST/USBD_SYSERRTINTCLR/USBD_SYSERRTINTSET + * register definitions + **********************************************************************/ +/* Endpoint selection macro */ +#define USBD_EP_SEL(e) (1 << (e)) + +/********************************************************************** + * SBD_DMAINTST/USBD_DMAINTEN + **********************************************************************/ +#define USBD_SYS_ERR_INT (1 << 2) +#define USBD_NEW_DD_INT (1 << 1) +#define USBD_EOT_INT (1 << 0) + +/********************************************************************** + * USBD_RXPLEN register definitions + **********************************************************************/ +#define USBD_PKT_RDY (1 << 11) +#define USBD_DV (1 << 10) +#define USBD_PK_LEN_MASK 0x3FF + +/********************************************************************** + * USBD_CTRL register definitions + **********************************************************************/ +#define USBD_LOG_ENDPOINT(e) ((e) << 2) +#define USBD_WR_EN (1 << 1) +#define USBD_RD_EN (1 << 0) + +/********************************************************************** + * USBD_CMDCODE register definitions + **********************************************************************/ +#define USBD_CMD_CODE(c) ((c) << 16) +#define USBD_CMD_PHASE(p) ((p) << 8) + +/********************************************************************** + * USBD_DMARST/USBD_DMARCLR/USBD_DMARSET register definitions + **********************************************************************/ +#define USBD_DMAEP(e) (1 << (e)) + +/* DD (DMA Descriptor) structure, requires word alignment */ +struct lpc32xx_usbd_dd { + u32 *dd_next; + u32 dd_setup; + u32 dd_buffer_addr; + u32 dd_status; + u32 dd_iso_ps_mem_addr; +}; + +/* dd_setup bit defines */ +#define DD_SETUP_ATLE_DMA_MODE 0x01 +#define DD_SETUP_NEXT_DD_VALID 0x04 +#define DD_SETUP_ISO_EP 0x10 +#define DD_SETUP_PACKETLEN(n) (((n) & 0x7FF) << 5) +#define DD_SETUP_DMALENBYTES(n) (((n) & 0xFFFF) << 16) + +/* dd_status bit defines */ +#define DD_STATUS_DD_RETIRED 0x01 +#define DD_STATUS_STS_MASK 0x1E +#define DD_STATUS_STS_NS 0x00 /* Not serviced */ +#define DD_STATUS_STS_BS 0x02 /* Being serviced */ +#define DD_STATUS_STS_NC 0x04 /* Normal completion */ +#define DD_STATUS_STS_DUR 0x06 /* Data underrun (short packet) */ +#define DD_STATUS_STS_DOR 0x08 /* Data overrun */ +#define DD_STATUS_STS_SE 0x12 /* System error */ +#define DD_STATUS_PKT_VAL 0x20 /* Packet valid */ +#define DD_STATUS_LSB_EX 0x40 /* LS byte extracted (ATLE) */ +#define DD_STATUS_MSB_EX 0x80 /* MS byte extracted (ATLE) */ +#define DD_STATUS_MLEN(n) (((n) >> 8) & 0x3F) +#define DD_STATUS_CURDMACNT(n) (((n) >> 16) & 0xFFFF) + +/* + * + * Protocol engine bits below + * + */ +/* Device Interrupt Bit Definitions */ +#define FRAME_INT 0x00000001 +#define EP_FAST_INT 0x00000002 +#define EP_SLOW_INT 0x00000004 +#define DEV_STAT_INT 0x00000008 +#define CCEMTY_INT 0x00000010 +#define CDFULL_INT 0x00000020 +#define RxENDPKT_INT 0x00000040 +#define TxENDPKT_INT 0x00000080 +#define EP_RLZED_INT 0x00000100 +#define ERR_INT 0x00000200 + +/* Rx & Tx Packet Length Definitions */ +#define PKT_LNGTH_MASK 0x000003FF +#define PKT_DV 0x00000400 +#define PKT_RDY 0x00000800 + +/* USB Control Definitions */ +#define CTRL_RD_EN 0x00000001 +#define CTRL_WR_EN 0x00000002 + +/* Command Codes */ +#define CMD_SET_ADDR 0x00D00500 +#define CMD_CFG_DEV 0x00D80500 +#define CMD_SET_MODE 0x00F30500 +#define CMD_RD_FRAME 0x00F50500 +#define DAT_RD_FRAME 0x00F50200 +#define CMD_RD_TEST 0x00FD0500 +#define DAT_RD_TEST 0x00FD0200 +#define CMD_SET_DEV_STAT 0x00FE0500 +#define CMD_GET_DEV_STAT 0x00FE0500 +#define DAT_GET_DEV_STAT 0x00FE0200 +#define CMD_GET_ERR_CODE 0x00FF0500 +#define DAT_GET_ERR_CODE 0x00FF0200 +#define CMD_RD_ERR_STAT 0x00FB0500 +#define DAT_RD_ERR_STAT 0x00FB0200 +#define DAT_WR_BYTE(x) (0x00000100 | ((x) << 16)) +#define CMD_SEL_EP(x) (0x00000500 | ((x) << 16)) +#define DAT_SEL_EP(x) (0x00000200 | ((x) << 16)) +#define CMD_SEL_EP_CLRI(x) (0x00400500 | ((x) << 16)) +#define DAT_SEL_EP_CLRI(x) (0x00400200 | ((x) << 16)) +#define CMD_SET_EP_STAT(x) (0x00400500 | ((x) << 16)) +#define CMD_CLR_BUF 0x00F20500 +#define DAT_CLR_BUF 0x00F20200 +#define CMD_VALID_BUF 0x00FA0500 + +/* Device Address Register Definitions */ +#define DEV_ADDR_MASK 0x7F +#define DEV_EN 0x80 + +/* Device Configure Register Definitions */ +#define CONF_DVICE 0x01 + +/* Device Mode Register Definitions */ +#define AP_CLK 0x01 +#define INAK_CI 0x02 +#define INAK_CO 0x04 +#define INAK_II 0x08 +#define INAK_IO 0x10 +#define INAK_BI 0x20 +#define INAK_BO 0x40 + +/* Device Status Register Definitions */ +#define DEV_CON 0x01 +#define DEV_CON_CH 0x02 +#define DEV_SUS 0x04 +#define DEV_SUS_CH 0x08 +#define DEV_RST 0x10 + +/* Error Code Register Definitions */ +#define ERR_EC_MASK 0x0F +#define ERR_EA 0x10 + +/* Error Status Register Definitions */ +#define ERR_PID 0x01 +#define ERR_UEPKT 0x02 +#define ERR_DCRC 0x04 +#define ERR_TIMOUT 0x08 +#define ERR_EOP 0x10 +#define ERR_B_OVRN 0x20 +#define ERR_BTSTF 0x40 +#define ERR_TGL 0x80 + +/* Endpoint Select Register Definitions */ +#define EP_SEL_F 0x01 +#define EP_SEL_ST 0x02 +#define EP_SEL_STP 0x04 +#define EP_SEL_PO 0x08 +#define EP_SEL_EPN 0x10 +#define EP_SEL_B_1_FULL 0x20 +#define EP_SEL_B_2_FULL 0x40 + +/* Endpoint Status Register Definitions */ +#define EP_STAT_ST 0x01 +#define EP_STAT_DA 0x20 +#define EP_STAT_RF_MO 0x40 +#define EP_STAT_CND_ST 0x80 + +/* Clear Buffer Register Definitions */ +#define CLR_BUF_PO 0x01 + +/* DMA Interrupt Bit Definitions */ +#define EOT_INT 0x01 +#define NDD_REQ_INT 0x02 +#define SYS_ERR_INT 0x04 + +#define DRIVER_VERSION "1.03" +static const char driver_name[] = "lpc32xx_udc"; + +/* + * + * proc interface support + * + */ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES +static char *epnames[] = {"INT", "ISO", "BULK", "CTRL"}; +static const char debug_filename[] = "driver/udc"; + +static void proc_ep_show(struct seq_file *s, struct lpc32xx_ep *ep) +{ + struct lpc32xx_request *req; + + seq_printf(s, "\n"); + seq_printf(s, "%12s, maxpacket %4d %3s", + ep->ep.name, ep->ep.maxpacket, + ep->is_in ? "in" : "out"); + seq_printf(s, " type %4s", epnames[ep->eptype]); + seq_printf(s, " ints: %12d", ep->totalints); + + if (list_empty(&ep->queue)) + seq_printf(s, "\t(queue empty)\n"); + else { + list_for_each_entry(req, &ep->queue, queue) { + u32 length = req->req.actual; + + seq_printf(s, "\treq %p len %d/%d buf %p\n", + &req->req, length, + req->req.length, req->req.buf); + } + } +} + +static int proc_udc_show(struct seq_file *s, void *unused) +{ + struct lpc32xx_udc *udc = s->private; + struct lpc32xx_ep *ep; + unsigned long flags; + + seq_printf(s, "%s: version %s\n", driver_name, DRIVER_VERSION); + + spin_lock_irqsave(&udc->lock, flags); + + seq_printf(s, "vbus %s, pullup %s, %s powered%s, gadget %s\n\n", + udc->vbus ? "present" : "off", + udc->enabled ? (udc->vbus ? "active" : "enabled") : + "disabled", + udc->selfpowered ? "self" : "VBUS", + udc->suspended ? ", suspended" : "", + udc->driver ? udc->driver->driver.name : "(none)"); + + if (udc->enabled && udc->vbus) { + proc_ep_show(s, &udc->ep[0]); + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) + proc_ep_show(s, ep); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int proc_udc_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_udc_show, PDE_DATA(inode)); +} + +static const struct file_operations proc_ops = { + .owner = THIS_MODULE, + .open = proc_udc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void create_debug_file(struct lpc32xx_udc *udc) +{ + udc->pde = debugfs_create_file(debug_filename, 0, NULL, udc, &proc_ops); +} + +static void remove_debug_file(struct lpc32xx_udc *udc) +{ + if (udc->pde) + debugfs_remove(udc->pde); +} + +#else +static inline void create_debug_file(struct lpc32xx_udc *udc) {} +static inline void remove_debug_file(struct lpc32xx_udc *udc) {} +#endif + +/* Primary initialization sequence for the ISP1301 transceiver */ +static void isp1301_udc_configure(struct lpc32xx_udc *udc) +{ + /* LPC32XX only supports DAT_SE0 USB mode */ + /* This sequence is important */ + + /* Disable transparent UART mode first */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + (ISP1301_I2C_MODE_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), + MC1_UART_EN); + + /* Set full speed and SE0 mode */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + (ISP1301_I2C_MODE_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), ~0); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_MODE_CONTROL_1, (MC1_SPEED_REG | MC1_DAT_SE0)); + + /* + * The PSW_OE enable bit state is reversed in the ISP1301 User's Guide + */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + (ISP1301_I2C_MODE_CONTROL_2 | ISP1301_I2C_REG_CLEAR_ADDR), ~0); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_MODE_CONTROL_2, (MC2_BI_DI | MC2_SPD_SUSP_CTRL)); + + /* Driver VBUS_DRV high or low depending on board setup */ + if (udc->board->vbus_drv_pol != 0) + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV); + else + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR, + OTG1_VBUS_DRV); + + /* Bi-directional mode with suspend control + * Enable both pulldowns for now - the pullup will be enable when VBUS + * is detected */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + (ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), ~0); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1, + (0 | OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN)); + + /* Discharge VBUS (just in case) */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DISCHRG); + msleep(1); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + (ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), + OTG1_VBUS_DISCHRG); + + /* Clear and enable VBUS high edge interrupt */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_LATCH | ISP1301_I2C_REG_CLEAR_ADDR, ~0); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_FALLING | ISP1301_I2C_REG_CLEAR_ADDR, ~0); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_FALLING, INT_VBUS_VLD); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_RISING | ISP1301_I2C_REG_CLEAR_ADDR, ~0); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_RISING, INT_VBUS_VLD); + + /* Enable usb_need_clk clock after transceiver is initialized */ + writel((readl(USB_CTRL) | USB_DEV_NEED_CLK_EN), USB_CTRL); + + dev_info(udc->dev, "ISP1301 Vendor ID : 0x%04x\n", + i2c_smbus_read_word_data(udc->isp1301_i2c_client, 0x00)); + dev_info(udc->dev, "ISP1301 Product ID : 0x%04x\n", + i2c_smbus_read_word_data(udc->isp1301_i2c_client, 0x02)); + dev_info(udc->dev, "ISP1301 Version ID : 0x%04x\n", + i2c_smbus_read_word_data(udc->isp1301_i2c_client, 0x14)); +} + +/* Enables or disables the USB device pullup via the ISP1301 transceiver */ +static void isp1301_pullup_set(struct lpc32xx_udc *udc) +{ + if (udc->pullup) + /* Enable pullup for bus signalling */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1, OTG1_DP_PULLUP); + else + /* Enable pullup for bus signalling */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR, + OTG1_DP_PULLUP); +} + +static void pullup_work(struct work_struct *work) +{ + struct lpc32xx_udc *udc = + container_of(work, struct lpc32xx_udc, pullup_job); + + isp1301_pullup_set(udc); +} + +static void isp1301_pullup_enable(struct lpc32xx_udc *udc, int en_pullup, + int block) +{ + if (en_pullup == udc->pullup) + return; + + udc->pullup = en_pullup; + if (block) + isp1301_pullup_set(udc); + else + /* defer slow i2c pull up setting */ + schedule_work(&udc->pullup_job); +} + +#ifdef CONFIG_PM +/* Powers up or down the ISP1301 transceiver */ +static void isp1301_set_powerstate(struct lpc32xx_udc *udc, int enable) +{ + if (enable != 0) + /* Power up ISP1301 - this ISP1301 will automatically wakeup + when VBUS is detected */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_MODE_CONTROL_2 | ISP1301_I2C_REG_CLEAR_ADDR, + MC2_GLOBAL_PWR_DN); + else + /* Power down ISP1301 */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN); +} + +static void power_work(struct work_struct *work) +{ + struct lpc32xx_udc *udc = + container_of(work, struct lpc32xx_udc, power_job); + + isp1301_set_powerstate(udc, udc->poweron); +} +#endif + +/* + * + * USB protocol engine command/data read/write helper functions + * + */ +/* Issues a single command to the USB device state machine */ +static void udc_protocol_cmd_w(struct lpc32xx_udc *udc, u32 cmd) +{ + u32 pass = 0; + int to; + + /* EP may lock on CLRI if this read isn't done */ + u32 tmp = readl(USBD_DEVINTST(udc->udp_baseaddr)); + (void) tmp; + + while (pass == 0) { + writel(USBD_CCEMPTY, USBD_DEVINTCLR(udc->udp_baseaddr)); + + /* Write command code */ + writel(cmd, USBD_CMDCODE(udc->udp_baseaddr)); + to = 10000; + while (((readl(USBD_DEVINTST(udc->udp_baseaddr)) & + USBD_CCEMPTY) == 0) && (to > 0)) { + to--; + } + + if (to > 0) + pass = 1; + + cpu_relax(); + } +} + +/* Issues 2 commands (or command and data) to the USB device state machine */ +static inline void udc_protocol_cmd_data_w(struct lpc32xx_udc *udc, u32 cmd, + u32 data) +{ + udc_protocol_cmd_w(udc, cmd); + udc_protocol_cmd_w(udc, data); +} + +/* Issues a single command to the USB device state machine and reads + * response data */ +static u32 udc_protocol_cmd_r(struct lpc32xx_udc *udc, u32 cmd) +{ + u32 tmp; + int to = 1000; + + /* Write a command and read data from the protocol engine */ + writel((USBD_CDFULL | USBD_CCEMPTY), + USBD_DEVINTCLR(udc->udp_baseaddr)); + + /* Write command code */ + udc_protocol_cmd_w(udc, cmd); + + tmp = readl(USBD_DEVINTST(udc->udp_baseaddr)); + while ((!(readl(USBD_DEVINTST(udc->udp_baseaddr)) & USBD_CDFULL)) + && (to > 0)) + to--; + if (!to) + dev_dbg(udc->dev, + "Protocol engine didn't receive response (CDFULL)\n"); + + return readl(USBD_CMDDATA(udc->udp_baseaddr)); +} + +/* + * + * USB device interrupt mask support functions + * + */ +/* Enable one or more USB device interrupts */ +static inline void uda_enable_devint(struct lpc32xx_udc *udc, u32 devmask) +{ + udc->enabled_devints |= devmask; + writel(udc->enabled_devints, USBD_DEVINTEN(udc->udp_baseaddr)); +} + +/* Disable one or more USB device interrupts */ +static inline void uda_disable_devint(struct lpc32xx_udc *udc, u32 mask) +{ + udc->enabled_devints &= ~mask; + writel(udc->enabled_devints, USBD_DEVINTEN(udc->udp_baseaddr)); +} + +/* Clear one or more USB device interrupts */ +static inline void uda_clear_devint(struct lpc32xx_udc *udc, u32 mask) +{ + writel(mask, USBD_DEVINTCLR(udc->udp_baseaddr)); +} + +/* + * + * Endpoint interrupt disable/enable functions + * + */ +/* Enable one or more USB endpoint interrupts */ +static void uda_enable_hwepint(struct lpc32xx_udc *udc, u32 hwep) +{ + udc->enabled_hwepints |= (1 << hwep); + writel(udc->enabled_hwepints, USBD_EPINTEN(udc->udp_baseaddr)); +} + +/* Disable one or more USB endpoint interrupts */ +static void uda_disable_hwepint(struct lpc32xx_udc *udc, u32 hwep) +{ + udc->enabled_hwepints &= ~(1 << hwep); + writel(udc->enabled_hwepints, USBD_EPINTEN(udc->udp_baseaddr)); +} + +/* Clear one or more USB endpoint interrupts */ +static inline void uda_clear_hwepint(struct lpc32xx_udc *udc, u32 hwep) +{ + writel((1 << hwep), USBD_EPINTCLR(udc->udp_baseaddr)); +} + +/* Enable DMA for the HW channel */ +static inline void udc_ep_dma_enable(struct lpc32xx_udc *udc, u32 hwep) +{ + writel((1 << hwep), USBD_EPDMAEN(udc->udp_baseaddr)); +} + +/* Disable DMA for the HW channel */ +static inline void udc_ep_dma_disable(struct lpc32xx_udc *udc, u32 hwep) +{ + writel((1 << hwep), USBD_EPDMADIS(udc->udp_baseaddr)); +} + +/* + * + * Endpoint realize/unrealize functions + * + */ +/* Before an endpoint can be used, it needs to be realized + * in the USB protocol engine - this realizes the endpoint. + * The interrupt (FIFO or DMA) is not enabled with this function */ +static void udc_realize_hwep(struct lpc32xx_udc *udc, u32 hwep, + u32 maxpacket) +{ + int to = 1000; + + writel(USBD_EP_RLZED, USBD_DEVINTCLR(udc->udp_baseaddr)); + writel(hwep, USBD_EPIND(udc->udp_baseaddr)); + udc->realized_eps |= (1 << hwep); + writel(udc->realized_eps, USBD_REEP(udc->udp_baseaddr)); + writel(maxpacket, USBD_EPMAXPSIZE(udc->udp_baseaddr)); + + /* Wait until endpoint is realized in hardware */ + while ((!(readl(USBD_DEVINTST(udc->udp_baseaddr)) & + USBD_EP_RLZED)) && (to > 0)) + to--; + if (!to) + dev_dbg(udc->dev, "EP not correctly realized in hardware\n"); + + writel(USBD_EP_RLZED, USBD_DEVINTCLR(udc->udp_baseaddr)); +} + +/* Unrealize an EP */ +static void udc_unrealize_hwep(struct lpc32xx_udc *udc, u32 hwep) +{ + udc->realized_eps &= ~(1 << hwep); + writel(udc->realized_eps, USBD_REEP(udc->udp_baseaddr)); +} + +/* + * + * Endpoint support functions + * + */ +/* Select and clear endpoint interrupt */ +static u32 udc_selep_clrint(struct lpc32xx_udc *udc, u32 hwep) +{ + udc_protocol_cmd_w(udc, CMD_SEL_EP_CLRI(hwep)); + return udc_protocol_cmd_r(udc, DAT_SEL_EP_CLRI(hwep)); +} + +/* Disables the endpoint in the USB protocol engine */ +static void udc_disable_hwep(struct lpc32xx_udc *udc, u32 hwep) +{ + udc_protocol_cmd_data_w(udc, CMD_SET_EP_STAT(hwep), + DAT_WR_BYTE(EP_STAT_DA)); +} + +/* Stalls the endpoint - endpoint will return STALL */ +static void udc_stall_hwep(struct lpc32xx_udc *udc, u32 hwep) +{ + udc_protocol_cmd_data_w(udc, CMD_SET_EP_STAT(hwep), + DAT_WR_BYTE(EP_STAT_ST)); +} + +/* Clear stall or reset endpoint */ +static void udc_clrstall_hwep(struct lpc32xx_udc *udc, u32 hwep) +{ + udc_protocol_cmd_data_w(udc, CMD_SET_EP_STAT(hwep), + DAT_WR_BYTE(0)); +} + +/* Select an endpoint for endpoint status, clear, validate */ +static void udc_select_hwep(struct lpc32xx_udc *udc, u32 hwep) +{ + udc_protocol_cmd_w(udc, CMD_SEL_EP(hwep)); +} + +/* + * + * Endpoint buffer management functions + * + */ +/* Clear the current endpoint's buffer */ +static void udc_clr_buffer_hwep(struct lpc32xx_udc *udc, u32 hwep) +{ + udc_select_hwep(udc, hwep); + udc_protocol_cmd_w(udc, CMD_CLR_BUF); +} + +/* Validate the current endpoint's buffer */ +static void udc_val_buffer_hwep(struct lpc32xx_udc *udc, u32 hwep) +{ + udc_select_hwep(udc, hwep); + udc_protocol_cmd_w(udc, CMD_VALID_BUF); +} + +static inline u32 udc_clearep_getsts(struct lpc32xx_udc *udc, u32 hwep) +{ + /* Clear EP interrupt */ + uda_clear_hwepint(udc, hwep); + return udc_selep_clrint(udc, hwep); +} + +/* + * + * USB EP DMA support + * + */ +/* Allocate a DMA Descriptor */ +static struct lpc32xx_usbd_dd_gad *udc_dd_alloc(struct lpc32xx_udc *udc) +{ + dma_addr_t dma; + struct lpc32xx_usbd_dd_gad *dd; + + dd = (struct lpc32xx_usbd_dd_gad *) dma_pool_alloc( + udc->dd_cache, (GFP_KERNEL | GFP_DMA), &dma); + if (dd) + dd->this_dma = dma; + + return dd; +} + +/* Free a DMA Descriptor */ +static void udc_dd_free(struct lpc32xx_udc *udc, struct lpc32xx_usbd_dd_gad *dd) +{ + dma_pool_free(udc->dd_cache, dd, dd->this_dma); +} + +/* + * + * USB setup and shutdown functions + * + */ +/* Enables or disables most of the USB system clocks when low power mode is + * needed. Clocks are typically started on a connection event, and disabled + * when a cable is disconnected */ +static void udc_clk_set(struct lpc32xx_udc *udc, int enable) +{ + if (enable != 0) { + if (udc->clocked) + return; + + udc->clocked = 1; + + /* 48MHz PLL up */ + clk_enable(udc->usb_pll_clk); + + /* Enable the USB device clock */ + writel(readl(USB_CTRL) | USB_DEV_NEED_CLK_EN, + USB_CTRL); + + clk_enable(udc->usb_otg_clk); + } else { + if (!udc->clocked) + return; + + udc->clocked = 0; + + /* Never disable the USB_HCLK during normal operation */ + + /* 48MHz PLL dpwn */ + clk_disable(udc->usb_pll_clk); + + /* Disable the USB device clock */ + writel(readl(USB_CTRL) & ~USB_DEV_NEED_CLK_EN, + USB_CTRL); + + clk_disable(udc->usb_otg_clk); + } +} + +/* Set/reset USB device address */ +static void udc_set_address(struct lpc32xx_udc *udc, u32 addr) +{ + /* Address will be latched at the end of the status phase, or + latched immediately if function is called twice */ + udc_protocol_cmd_data_w(udc, CMD_SET_ADDR, + DAT_WR_BYTE(DEV_EN | addr)); +} + +/* Setup up a IN request for DMA transfer - this consists of determining the + * list of DMA addresses for the transfer, allocating DMA Descriptors, + * installing the DD into the UDCA, and then enabling the DMA for that EP */ +static int udc_ep_in_req_dma(struct lpc32xx_udc *udc, struct lpc32xx_ep *ep) +{ + struct lpc32xx_request *req; + u32 hwep = ep->hwep_num; + + ep->req_pending = 1; + + /* There will always be a request waiting here */ + req = list_entry(ep->queue.next, struct lpc32xx_request, queue); + + /* Place the DD Descriptor into the UDCA */ + udc->udca_v_base[hwep] = req->dd_desc_ptr->this_dma; + + /* Enable DMA and interrupt for the HW EP */ + udc_ep_dma_enable(udc, hwep); + + /* Clear ZLP if last packet is not of MAXP size */ + if (req->req.length % ep->ep.maxpacket) + req->send_zlp = 0; + + return 0; +} + +/* Setup up a OUT request for DMA transfer - this consists of determining the + * list of DMA addresses for the transfer, allocating DMA Descriptors, + * installing the DD into the UDCA, and then enabling the DMA for that EP */ +static int udc_ep_out_req_dma(struct lpc32xx_udc *udc, struct lpc32xx_ep *ep) +{ + struct lpc32xx_request *req; + u32 hwep = ep->hwep_num; + + ep->req_pending = 1; + + /* There will always be a request waiting here */ + req = list_entry(ep->queue.next, struct lpc32xx_request, queue); + + /* Place the DD Descriptor into the UDCA */ + udc->udca_v_base[hwep] = req->dd_desc_ptr->this_dma; + + /* Enable DMA and interrupt for the HW EP */ + udc_ep_dma_enable(udc, hwep); + return 0; +} + +static void udc_disable(struct lpc32xx_udc *udc) +{ + u32 i; + + /* Disable device */ + udc_protocol_cmd_data_w(udc, CMD_CFG_DEV, DAT_WR_BYTE(0)); + udc_protocol_cmd_data_w(udc, CMD_SET_DEV_STAT, DAT_WR_BYTE(0)); + + /* Disable all device interrupts (including EP0) */ + uda_disable_devint(udc, 0x3FF); + + /* Disable and reset all endpoint interrupts */ + for (i = 0; i < 32; i++) { + uda_disable_hwepint(udc, i); + uda_clear_hwepint(udc, i); + udc_disable_hwep(udc, i); + udc_unrealize_hwep(udc, i); + udc->udca_v_base[i] = 0; + + /* Disable and clear all interrupts and DMA */ + udc_ep_dma_disable(udc, i); + writel((1 << i), USBD_EOTINTCLR(udc->udp_baseaddr)); + writel((1 << i), USBD_NDDRTINTCLR(udc->udp_baseaddr)); + writel((1 << i), USBD_SYSERRTINTCLR(udc->udp_baseaddr)); + writel((1 << i), USBD_DMARCLR(udc->udp_baseaddr)); + } + + /* Disable DMA interrupts */ + writel(0, USBD_DMAINTEN(udc->udp_baseaddr)); + + writel(0, USBD_UDCAH(udc->udp_baseaddr)); +} + +static void udc_enable(struct lpc32xx_udc *udc) +{ + u32 i; + struct lpc32xx_ep *ep = &udc->ep[0]; + + /* Start with known state */ + udc_disable(udc); + + /* Enable device */ + udc_protocol_cmd_data_w(udc, CMD_SET_DEV_STAT, DAT_WR_BYTE(DEV_CON)); + + /* EP interrupts on high priority, FRAME interrupt on low priority */ + writel(USBD_EP_FAST, USBD_DEVINTPRI(udc->udp_baseaddr)); + writel(0xFFFF, USBD_EPINTPRI(udc->udp_baseaddr)); + + /* Clear any pending device interrupts */ + writel(0x3FF, USBD_DEVINTCLR(udc->udp_baseaddr)); + + /* Setup UDCA - not yet used (DMA) */ + writel(udc->udca_p_base, USBD_UDCAH(udc->udp_baseaddr)); + + /* Only enable EP0 in and out for now, EP0 only works in FIFO mode */ + for (i = 0; i <= 1; i++) { + udc_realize_hwep(udc, i, ep->ep.maxpacket); + uda_enable_hwepint(udc, i); + udc_select_hwep(udc, i); + udc_clrstall_hwep(udc, i); + udc_clr_buffer_hwep(udc, i); + } + + /* Device interrupt setup */ + uda_clear_devint(udc, (USBD_ERR_INT | USBD_DEV_STAT | USBD_EP_SLOW | + USBD_EP_FAST)); + uda_enable_devint(udc, (USBD_ERR_INT | USBD_DEV_STAT | USBD_EP_SLOW | + USBD_EP_FAST)); + + /* Set device address to 0 - called twice to force a latch in the USB + engine without the need of a setup packet status closure */ + udc_set_address(udc, 0); + udc_set_address(udc, 0); + + /* Enable master DMA interrupts */ + writel((USBD_SYS_ERR_INT | USBD_EOT_INT), + USBD_DMAINTEN(udc->udp_baseaddr)); + + udc->dev_status = 0; +} + +/* + * + * USB device board specific events handled via callbacks + * + */ +/* Connection change event - notify board function of change */ +static void uda_power_event(struct lpc32xx_udc *udc, u32 conn) +{ + /* Just notify of a connection change event (optional) */ + if (udc->board->conn_chgb != NULL) + udc->board->conn_chgb(conn); +} + +/* Suspend/resume event - notify board function of change */ +static void uda_resm_susp_event(struct lpc32xx_udc *udc, u32 conn) +{ + /* Just notify of a Suspend/resume change event (optional) */ + if (udc->board->susp_chgb != NULL) + udc->board->susp_chgb(conn); + + if (conn) + udc->suspended = 0; + else + udc->suspended = 1; +} + +/* Remote wakeup enable/disable - notify board function of change */ +static void uda_remwkp_cgh(struct lpc32xx_udc *udc) +{ + if (udc->board->rmwk_chgb != NULL) + udc->board->rmwk_chgb(udc->dev_status & + (1 << USB_DEVICE_REMOTE_WAKEUP)); +} + +/* Reads data from FIFO, adjusts for alignment and data size */ +static void udc_pop_fifo(struct lpc32xx_udc *udc, u8 *data, u32 bytes) +{ + int n, i, bl; + u16 *p16; + u32 *p32, tmp, cbytes; + + /* Use optimal data transfer method based on source address and size */ + switch (((u32) data) & 0x3) { + case 0: /* 32-bit aligned */ + p32 = (u32 *) data; + cbytes = (bytes & ~0x3); + + /* Copy 32-bit aligned data first */ + for (n = 0; n < cbytes; n += 4) + *p32++ = readl(USBD_RXDATA(udc->udp_baseaddr)); + + /* Handle any remaining bytes */ + bl = bytes - cbytes; + if (bl) { + tmp = readl(USBD_RXDATA(udc->udp_baseaddr)); + for (n = 0; n < bl; n++) + data[cbytes + n] = ((tmp >> (n * 8)) & 0xFF); + + } + break; + + case 1: /* 8-bit aligned */ + case 3: + /* Each byte has to be handled independently */ + for (n = 0; n < bytes; n += 4) { + tmp = readl(USBD_RXDATA(udc->udp_baseaddr)); + + bl = bytes - n; + if (bl > 3) + bl = 3; + + for (i = 0; i < bl; i++) + data[n + i] = (u8) ((tmp >> (n * 8)) & 0xFF); + } + break; + + case 2: /* 16-bit aligned */ + p16 = (u16 *) data; + cbytes = (bytes & ~0x3); + + /* Copy 32-bit sized objects first with 16-bit alignment */ + for (n = 0; n < cbytes; n += 4) { + tmp = readl(USBD_RXDATA(udc->udp_baseaddr)); + *p16++ = (u16)(tmp & 0xFFFF); + *p16++ = (u16)((tmp >> 16) & 0xFFFF); + } + + /* Handle any remaining bytes */ + bl = bytes - cbytes; + if (bl) { + tmp = readl(USBD_RXDATA(udc->udp_baseaddr)); + for (n = 0; n < bl; n++) + data[cbytes + n] = ((tmp >> (n * 8)) & 0xFF); + } + break; + } +} + +/* Read data from the FIFO for an endpoint. This function is for endpoints (such + * as EP0) that don't use DMA. This function should only be called if a packet + * is known to be ready to read for the endpoint. Note that the endpoint must + * be selected in the protocol engine prior to this call. */ +static u32 udc_read_hwep(struct lpc32xx_udc *udc, u32 hwep, u32 *data, + u32 bytes) +{ + u32 tmpv; + int to = 1000; + u32 tmp, hwrep = ((hwep & 0x1E) << 1) | CTRL_RD_EN; + + /* Setup read of endpoint */ + writel(hwrep, USBD_CTRL(udc->udp_baseaddr)); + + /* Wait until packet is ready */ + while ((((tmpv = readl(USBD_RXPLEN(udc->udp_baseaddr))) & + PKT_RDY) == 0) && (to > 0)) + to--; + if (!to) + dev_dbg(udc->dev, "No packet ready on FIFO EP read\n"); + + /* Mask out count */ + tmp = tmpv & PKT_LNGTH_MASK; + if (bytes < tmp) + tmp = bytes; + + if ((tmp > 0) && (data != NULL)) + udc_pop_fifo(udc, (u8 *) data, tmp); + + writel(((hwep & 0x1E) << 1), USBD_CTRL(udc->udp_baseaddr)); + + /* Clear the buffer */ + udc_clr_buffer_hwep(udc, hwep); + + return tmp; +} + +/* Stuffs data into the FIFO, adjusts for alignment and data size */ +static void udc_stuff_fifo(struct lpc32xx_udc *udc, u8 *data, u32 bytes) +{ + int n, i, bl; + u16 *p16; + u32 *p32, tmp, cbytes; + + /* Use optimal data transfer method based on source address and size */ + switch (((u32) data) & 0x3) { + case 0: /* 32-bit aligned */ + p32 = (u32 *) data; + cbytes = (bytes & ~0x3); + + /* Copy 32-bit aligned data first */ + for (n = 0; n < cbytes; n += 4) + writel(*p32++, USBD_TXDATA(udc->udp_baseaddr)); + + /* Handle any remaining bytes */ + bl = bytes - cbytes; + if (bl) { + tmp = 0; + for (n = 0; n < bl; n++) + tmp |= data[cbytes + n] << (n * 8); + + writel(tmp, USBD_TXDATA(udc->udp_baseaddr)); + } + break; + + case 1: /* 8-bit aligned */ + case 3: + /* Each byte has to be handled independently */ + for (n = 0; n < bytes; n += 4) { + bl = bytes - n; + if (bl > 4) + bl = 4; + + tmp = 0; + for (i = 0; i < bl; i++) + tmp |= data[n + i] << (i * 8); + + writel(tmp, USBD_TXDATA(udc->udp_baseaddr)); + } + break; + + case 2: /* 16-bit aligned */ + p16 = (u16 *) data; + cbytes = (bytes & ~0x3); + + /* Copy 32-bit aligned data first */ + for (n = 0; n < cbytes; n += 4) { + tmp = *p16++ & 0xFFFF; + tmp |= (*p16++ & 0xFFFF) << 16; + writel(tmp, USBD_TXDATA(udc->udp_baseaddr)); + } + + /* Handle any remaining bytes */ + bl = bytes - cbytes; + if (bl) { + tmp = 0; + for (n = 0; n < bl; n++) + tmp |= data[cbytes + n] << (n * 8); + + writel(tmp, USBD_TXDATA(udc->udp_baseaddr)); + } + break; + } +} + +/* Write data to the FIFO for an endpoint. This function is for endpoints (such + * as EP0) that don't use DMA. Note that the endpoint must be selected in the + * protocol engine prior to this call. */ +static void udc_write_hwep(struct lpc32xx_udc *udc, u32 hwep, u32 *data, + u32 bytes) +{ + u32 hwwep = ((hwep & 0x1E) << 1) | CTRL_WR_EN; + + if ((bytes > 0) && (data == NULL)) + return; + + /* Setup write of endpoint */ + writel(hwwep, USBD_CTRL(udc->udp_baseaddr)); + + writel(bytes, USBD_TXPLEN(udc->udp_baseaddr)); + + /* Need at least 1 byte to trigger TX */ + if (bytes == 0) + writel(0, USBD_TXDATA(udc->udp_baseaddr)); + else + udc_stuff_fifo(udc, (u8 *) data, bytes); + + writel(((hwep & 0x1E) << 1), USBD_CTRL(udc->udp_baseaddr)); + + udc_val_buffer_hwep(udc, hwep); +} + +/* USB device reset - resets USB to a default state with just EP0 + enabled */ +static void uda_usb_reset(struct lpc32xx_udc *udc) +{ + u32 i = 0; + /* Re-init device controller and EP0 */ + udc_enable(udc); + udc->gadget.speed = USB_SPEED_FULL; + + for (i = 1; i < NUM_ENDPOINTS; i++) { + struct lpc32xx_ep *ep = &udc->ep[i]; + ep->req_pending = 0; + } +} + +/* Send a ZLP on EP0 */ +static void udc_ep0_send_zlp(struct lpc32xx_udc *udc) +{ + udc_write_hwep(udc, EP_IN, NULL, 0); +} + +/* Get current frame number */ +static u16 udc_get_current_frame(struct lpc32xx_udc *udc) +{ + u16 flo, fhi; + + udc_protocol_cmd_w(udc, CMD_RD_FRAME); + flo = (u16) udc_protocol_cmd_r(udc, DAT_RD_FRAME); + fhi = (u16) udc_protocol_cmd_r(udc, DAT_RD_FRAME); + + return (fhi << 8) | flo; +} + +/* Set the device as configured - enables all endpoints */ +static inline void udc_set_device_configured(struct lpc32xx_udc *udc) +{ + udc_protocol_cmd_data_w(udc, CMD_CFG_DEV, DAT_WR_BYTE(CONF_DVICE)); +} + +/* Set the device as unconfigured - disables all endpoints */ +static inline void udc_set_device_unconfigured(struct lpc32xx_udc *udc) +{ + udc_protocol_cmd_data_w(udc, CMD_CFG_DEV, DAT_WR_BYTE(0)); +} + +/* reinit == restore initial software state */ +static void udc_reinit(struct lpc32xx_udc *udc) +{ + u32 i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->gadget.ep0->ep_list); + + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct lpc32xx_ep *ep = &udc->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->ep.maxpacket = ep->maxpacket; + INIT_LIST_HEAD(&ep->queue); + ep->req_pending = 0; + } + + udc->ep0state = WAIT_FOR_SETUP; +} + +/* Must be called with lock */ +static void done(struct lpc32xx_ep *ep, struct lpc32xx_request *req, int status) +{ + struct lpc32xx_udc *udc = ep->udc; + + list_del_init(&req->queue); + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (ep->lep) { + usb_gadget_unmap_request(&udc->gadget, &req->req, ep->is_in); + + /* Free DDs */ + udc_dd_free(udc, req->dd_desc_ptr); + } + + if (status && status != -ESHUTDOWN) + ep_dbg(ep, "%s done %p, status %d\n", ep->ep.name, req, status); + + ep->req_pending = 0; + spin_unlock(&udc->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&udc->lock); +} + +/* Must be called with lock */ +static void nuke(struct lpc32xx_ep *ep, int status) +{ + struct lpc32xx_request *req; + + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct lpc32xx_request, queue); + done(ep, req, status); + } + + if (status == -ESHUTDOWN) { + uda_disable_hwepint(ep->udc, ep->hwep_num); + udc_disable_hwep(ep->udc, ep->hwep_num); + } +} + +/* IN endpoint 0 transfer */ +static int udc_ep0_in_req(struct lpc32xx_udc *udc) +{ + struct lpc32xx_request *req; + struct lpc32xx_ep *ep0 = &udc->ep[0]; + u32 tsend, ts = 0; + + if (list_empty(&ep0->queue)) + /* Nothing to send */ + return 0; + else + req = list_entry(ep0->queue.next, struct lpc32xx_request, + queue); + + tsend = ts = req->req.length - req->req.actual; + if (ts == 0) { + /* Send a ZLP */ + udc_ep0_send_zlp(udc); + done(ep0, req, 0); + return 1; + } else if (ts > ep0->ep.maxpacket) + ts = ep0->ep.maxpacket; /* Just send what we can */ + + /* Write data to the EP0 FIFO and start transfer */ + udc_write_hwep(udc, EP_IN, (req->req.buf + req->req.actual), ts); + + /* Increment data pointer */ + req->req.actual += ts; + + if (tsend >= ep0->ep.maxpacket) + return 0; /* Stay in data transfer state */ + + /* Transfer request is complete */ + udc->ep0state = WAIT_FOR_SETUP; + done(ep0, req, 0); + return 1; +} + +/* OUT endpoint 0 transfer */ +static int udc_ep0_out_req(struct lpc32xx_udc *udc) +{ + struct lpc32xx_request *req; + struct lpc32xx_ep *ep0 = &udc->ep[0]; + u32 tr, bufferspace; + + if (list_empty(&ep0->queue)) + return 0; + else + req = list_entry(ep0->queue.next, struct lpc32xx_request, + queue); + + if (req) { + if (req->req.length == 0) { + /* Just dequeue request */ + done(ep0, req, 0); + udc->ep0state = WAIT_FOR_SETUP; + return 1; + } + + /* Get data from FIFO */ + bufferspace = req->req.length - req->req.actual; + if (bufferspace > ep0->ep.maxpacket) + bufferspace = ep0->ep.maxpacket; + + /* Copy data to buffer */ + prefetchw(req->req.buf + req->req.actual); + tr = udc_read_hwep(udc, EP_OUT, req->req.buf + req->req.actual, + bufferspace); + req->req.actual += bufferspace; + + if (tr < ep0->ep.maxpacket) { + /* This is the last packet */ + done(ep0, req, 0); + udc->ep0state = WAIT_FOR_SETUP; + return 1; + } + } + + return 0; +} + +/* Must be called with lock */ +static void stop_activity(struct lpc32xx_udc *udc) +{ + struct usb_gadget_driver *driver = udc->driver; + int i; + + if (udc->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->suspended = 0; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct lpc32xx_ep *ep = &udc->ep[i]; + nuke(ep, -ESHUTDOWN); + } + if (driver) { + spin_unlock(&udc->lock); + driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + + isp1301_pullup_enable(udc, 0, 0); + udc_disable(udc); + udc_reinit(udc); +} + +/* + * Activate or kill host pullup + * Can be called with or without lock + */ +static void pullup(struct lpc32xx_udc *udc, int is_on) +{ + if (!udc->clocked) + return; + + if (!udc->enabled || !udc->vbus) + is_on = 0; + + if (is_on != udc->pullup) + isp1301_pullup_enable(udc, is_on, 0); +} + +/* Must be called without lock */ +static int lpc32xx_ep_disable(struct usb_ep *_ep) +{ + struct lpc32xx_ep *ep = container_of(_ep, struct lpc32xx_ep, ep); + struct lpc32xx_udc *udc = ep->udc; + unsigned long flags; + + if ((ep->hwep_num_base == 0) || (ep->hwep_num == 0)) + return -EINVAL; + spin_lock_irqsave(&udc->lock, flags); + + nuke(ep, -ESHUTDOWN); + + /* Clear all DMA statuses for this EP */ + udc_ep_dma_disable(udc, ep->hwep_num); + writel(1 << ep->hwep_num, USBD_EOTINTCLR(udc->udp_baseaddr)); + writel(1 << ep->hwep_num, USBD_NDDRTINTCLR(udc->udp_baseaddr)); + writel(1 << ep->hwep_num, USBD_SYSERRTINTCLR(udc->udp_baseaddr)); + writel(1 << ep->hwep_num, USBD_DMARCLR(udc->udp_baseaddr)); + + /* Remove the DD pointer in the UDCA */ + udc->udca_v_base[ep->hwep_num] = 0; + + /* Disable and reset endpoint and interrupt */ + uda_clear_hwepint(udc, ep->hwep_num); + udc_unrealize_hwep(udc, ep->hwep_num); + + ep->hwep_num = 0; + + spin_unlock_irqrestore(&udc->lock, flags); + + atomic_dec(&udc->enabled_ep_cnt); + wake_up(&udc->ep_disable_wait_queue); + + return 0; +} + +/* Must be called without lock */ +static int lpc32xx_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct lpc32xx_ep *ep = container_of(_ep, struct lpc32xx_ep, ep); + struct lpc32xx_udc *udc = ep->udc; + u16 maxpacket; + u32 tmp; + unsigned long flags; + + /* Verify EP data */ + if ((!_ep) || (!ep) || (!desc) || + (desc->bDescriptorType != USB_DT_ENDPOINT)) { + dev_dbg(udc->dev, "bad ep or descriptor\n"); + return -EINVAL; + } + maxpacket = usb_endpoint_maxp(desc); + if ((maxpacket == 0) || (maxpacket > ep->maxpacket)) { + dev_dbg(udc->dev, "bad ep descriptor's packet size\n"); + return -EINVAL; + } + + /* Don't touch EP0 */ + if (ep->hwep_num_base == 0) { + dev_dbg(udc->dev, "Can't re-enable EP0!!!\n"); + return -EINVAL; + } + + /* Is driver ready? */ + if ((!udc->driver) || (udc->gadget.speed == USB_SPEED_UNKNOWN)) { + dev_dbg(udc->dev, "bogus device state\n"); + return -ESHUTDOWN; + } + + tmp = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + switch (tmp) { + case USB_ENDPOINT_XFER_CONTROL: + return -EINVAL; + + case USB_ENDPOINT_XFER_INT: + if (maxpacket > ep->maxpacket) { + dev_dbg(udc->dev, + "Bad INT endpoint maxpacket %d\n", maxpacket); + return -EINVAL; + } + break; + + case USB_ENDPOINT_XFER_BULK: + switch (maxpacket) { + case 8: + case 16: + case 32: + case 64: + break; + + default: + dev_dbg(udc->dev, + "Bad BULK endpoint maxpacket %d\n", maxpacket); + return -EINVAL; + } + break; + + case USB_ENDPOINT_XFER_ISOC: + break; + } + spin_lock_irqsave(&udc->lock, flags); + + /* Initialize endpoint to match the selected descriptor */ + ep->is_in = (desc->bEndpointAddress & USB_DIR_IN) != 0; + ep->ep.maxpacket = maxpacket; + + /* Map hardware endpoint from base and direction */ + if (ep->is_in) + /* IN endpoints are offset 1 from the OUT endpoint */ + ep->hwep_num = ep->hwep_num_base + EP_IN; + else + ep->hwep_num = ep->hwep_num_base; + + ep_dbg(ep, "EP enabled: %s, HW:%d, MP:%d IN:%d\n", ep->ep.name, + ep->hwep_num, maxpacket, (ep->is_in == 1)); + + /* Realize the endpoint, interrupt is enabled later when + * buffers are queued, IN EPs will NAK until buffers are ready */ + udc_realize_hwep(udc, ep->hwep_num, ep->ep.maxpacket); + udc_clr_buffer_hwep(udc, ep->hwep_num); + uda_disable_hwepint(udc, ep->hwep_num); + udc_clrstall_hwep(udc, ep->hwep_num); + + /* Clear all DMA statuses for this EP */ + udc_ep_dma_disable(udc, ep->hwep_num); + writel(1 << ep->hwep_num, USBD_EOTINTCLR(udc->udp_baseaddr)); + writel(1 << ep->hwep_num, USBD_NDDRTINTCLR(udc->udp_baseaddr)); + writel(1 << ep->hwep_num, USBD_SYSERRTINTCLR(udc->udp_baseaddr)); + writel(1 << ep->hwep_num, USBD_DMARCLR(udc->udp_baseaddr)); + + spin_unlock_irqrestore(&udc->lock, flags); + + atomic_inc(&udc->enabled_ep_cnt); + return 0; +} + +/* + * Allocate a USB request list + * Can be called with or without lock + */ +static struct usb_request *lpc32xx_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct lpc32xx_request *req; + + req = kzalloc(sizeof(struct lpc32xx_request), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +/* + * De-allocate a USB request list + * Can be called with or without lock + */ +static void lpc32xx_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct lpc32xx_request *req; + + req = container_of(_req, struct lpc32xx_request, req); + BUG_ON(!list_empty(&req->queue)); + kfree(req); +} + +/* Must be called without lock */ +static int lpc32xx_ep_queue(struct usb_ep *_ep, + struct usb_request *_req, gfp_t gfp_flags) +{ + struct lpc32xx_request *req; + struct lpc32xx_ep *ep; + struct lpc32xx_udc *udc; + unsigned long flags; + int status = 0; + + req = container_of(_req, struct lpc32xx_request, req); + ep = container_of(_ep, struct lpc32xx_ep, ep); + + if (!_req || !_req->complete || !_req->buf || + !list_empty(&req->queue)) + return -EINVAL; + + udc = ep->udc; + + if (!_ep) { + dev_dbg(udc->dev, "invalid ep\n"); + return -EINVAL; + } + + + if ((!udc) || (!udc->driver) || + (udc->gadget.speed == USB_SPEED_UNKNOWN)) { + dev_dbg(udc->dev, "invalid device\n"); + return -EINVAL; + } + + if (ep->lep) { + struct lpc32xx_usbd_dd_gad *dd; + + status = usb_gadget_map_request(&udc->gadget, _req, ep->is_in); + if (status) + return status; + + /* For the request, build a list of DDs */ + dd = udc_dd_alloc(udc); + if (!dd) { + /* Error allocating DD */ + return -ENOMEM; + } + req->dd_desc_ptr = dd; + + /* Setup the DMA descriptor */ + dd->dd_next_phy = dd->dd_next_v = 0; + dd->dd_buffer_addr = req->req.dma; + dd->dd_status = 0; + + /* Special handling for ISO EPs */ + if (ep->eptype == EP_ISO_TYPE) { + dd->dd_setup = DD_SETUP_ISO_EP | + DD_SETUP_PACKETLEN(0) | + DD_SETUP_DMALENBYTES(1); + dd->dd_iso_ps_mem_addr = dd->this_dma + 24; + if (ep->is_in) + dd->iso_status[0] = req->req.length; + else + dd->iso_status[0] = 0; + } else + dd->dd_setup = DD_SETUP_PACKETLEN(ep->ep.maxpacket) | + DD_SETUP_DMALENBYTES(req->req.length); + } + + ep_dbg(ep, "%s queue req %p len %d buf %p (in=%d) z=%d\n", _ep->name, + _req, _req->length, _req->buf, ep->is_in, _req->zero); + + spin_lock_irqsave(&udc->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + req->send_zlp = _req->zero; + + /* Kickstart empty queues */ + if (list_empty(&ep->queue)) { + list_add_tail(&req->queue, &ep->queue); + + if (ep->hwep_num_base == 0) { + /* Handle expected data direction */ + if (ep->is_in) { + /* IN packet to host */ + udc->ep0state = DATA_IN; + status = udc_ep0_in_req(udc); + } else { + /* OUT packet from host */ + udc->ep0state = DATA_OUT; + status = udc_ep0_out_req(udc); + } + } else if (ep->is_in) { + /* IN packet to host and kick off transfer */ + if (!ep->req_pending) + udc_ep_in_req_dma(udc, ep); + } else + /* OUT packet from host and kick off list */ + if (!ep->req_pending) + udc_ep_out_req_dma(udc, ep); + } else + list_add_tail(&req->queue, &ep->queue); + + spin_unlock_irqrestore(&udc->lock, flags); + + return (status < 0) ? status : 0; +} + +/* Must be called without lock */ +static int lpc32xx_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct lpc32xx_ep *ep; + struct lpc32xx_request *req; + unsigned long flags; + + ep = container_of(_ep, struct lpc32xx_ep, ep); + if (!_ep || ep->hwep_num_base == 0) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->udc->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->udc->lock, flags); + + return 0; +} + +/* Must be called without lock */ +static int lpc32xx_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct lpc32xx_ep *ep = container_of(_ep, struct lpc32xx_ep, ep); + struct lpc32xx_udc *udc = ep->udc; + unsigned long flags; + + if ((!ep) || (ep->hwep_num <= 1)) + return -EINVAL; + + /* Don't halt an IN EP */ + if (ep->is_in) + return -EAGAIN; + + spin_lock_irqsave(&udc->lock, flags); + + if (value == 1) { + /* stall */ + udc_protocol_cmd_data_w(udc, CMD_SET_EP_STAT(ep->hwep_num), + DAT_WR_BYTE(EP_STAT_ST)); + } else { + /* End stall */ + ep->wedge = 0; + udc_protocol_cmd_data_w(udc, CMD_SET_EP_STAT(ep->hwep_num), + DAT_WR_BYTE(0)); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/* set the halt feature and ignores clear requests */ +static int lpc32xx_ep_set_wedge(struct usb_ep *_ep) +{ + struct lpc32xx_ep *ep = container_of(_ep, struct lpc32xx_ep, ep); + + if (!_ep || !ep->udc) + return -EINVAL; + + ep->wedge = 1; + + return usb_ep_set_halt(_ep); +} + +static const struct usb_ep_ops lpc32xx_ep_ops = { + .enable = lpc32xx_ep_enable, + .disable = lpc32xx_ep_disable, + .alloc_request = lpc32xx_ep_alloc_request, + .free_request = lpc32xx_ep_free_request, + .queue = lpc32xx_ep_queue, + .dequeue = lpc32xx_ep_dequeue, + .set_halt = lpc32xx_ep_set_halt, + .set_wedge = lpc32xx_ep_set_wedge, +}; + +/* Send a ZLP on a non-0 IN EP */ +void udc_send_in_zlp(struct lpc32xx_udc *udc, struct lpc32xx_ep *ep) +{ + /* Clear EP status */ + udc_clearep_getsts(udc, ep->hwep_num); + + /* Send ZLP via FIFO mechanism */ + udc_write_hwep(udc, ep->hwep_num, NULL, 0); +} + +/* + * Handle EP completion for ZLP + * This function will only be called when a delayed ZLP needs to be sent out + * after a DMA transfer has filled both buffers. + */ +void udc_handle_eps(struct lpc32xx_udc *udc, struct lpc32xx_ep *ep) +{ + u32 epstatus; + struct lpc32xx_request *req; + + if (ep->hwep_num <= 0) + return; + + uda_clear_hwepint(udc, ep->hwep_num); + + /* If this interrupt isn't enabled, return now */ + if (!(udc->enabled_hwepints & (1 << ep->hwep_num))) + return; + + /* Get endpoint status */ + epstatus = udc_clearep_getsts(udc, ep->hwep_num); + + /* + * This should never happen, but protect against writing to the + * buffer when full. + */ + if (epstatus & EP_SEL_F) + return; + + if (ep->is_in) { + udc_send_in_zlp(udc, ep); + uda_disable_hwepint(udc, ep->hwep_num); + } else + return; + + /* If there isn't a request waiting, something went wrong */ + req = list_entry(ep->queue.next, struct lpc32xx_request, queue); + if (req) { + done(ep, req, 0); + + /* Start another request if ready */ + if (!list_empty(&ep->queue)) { + if (ep->is_in) + udc_ep_in_req_dma(udc, ep); + else + udc_ep_out_req_dma(udc, ep); + } else + ep->req_pending = 0; + } +} + + +/* DMA end of transfer completion */ +static void udc_handle_dma_ep(struct lpc32xx_udc *udc, struct lpc32xx_ep *ep) +{ + u32 status, epstatus; + struct lpc32xx_request *req; + struct lpc32xx_usbd_dd_gad *dd; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + ep->totalints++; +#endif + + req = list_entry(ep->queue.next, struct lpc32xx_request, queue); + if (!req) { + ep_err(ep, "DMA interrupt on no req!\n"); + return; + } + dd = req->dd_desc_ptr; + + /* DMA descriptor should always be retired for this call */ + if (!(dd->dd_status & DD_STATUS_DD_RETIRED)) + ep_warn(ep, "DMA descriptor did not retire\n"); + + /* Disable DMA */ + udc_ep_dma_disable(udc, ep->hwep_num); + writel((1 << ep->hwep_num), USBD_EOTINTCLR(udc->udp_baseaddr)); + writel((1 << ep->hwep_num), USBD_NDDRTINTCLR(udc->udp_baseaddr)); + + /* System error? */ + if (readl(USBD_SYSERRTINTST(udc->udp_baseaddr)) & + (1 << ep->hwep_num)) { + writel((1 << ep->hwep_num), + USBD_SYSERRTINTCLR(udc->udp_baseaddr)); + ep_err(ep, "AHB critical error!\n"); + ep->req_pending = 0; + + /* The error could have occurred on a packet of a multipacket + * transfer, so recovering the transfer is not possible. Close + * the request with an error */ + done(ep, req, -ECONNABORTED); + return; + } + + /* Handle the current DD's status */ + status = dd->dd_status; + switch (status & DD_STATUS_STS_MASK) { + case DD_STATUS_STS_NS: + /* DD not serviced? This shouldn't happen! */ + ep->req_pending = 0; + ep_err(ep, "DMA critical EP error: DD not serviced (0x%x)!\n", + status); + + done(ep, req, -ECONNABORTED); + return; + + case DD_STATUS_STS_BS: + /* Interrupt only fires on EOT - This shouldn't happen! */ + ep->req_pending = 0; + ep_err(ep, "DMA critical EP error: EOT prior to service completion (0x%x)!\n", + status); + done(ep, req, -ECONNABORTED); + return; + + case DD_STATUS_STS_NC: + case DD_STATUS_STS_DUR: + /* Really just a short packet, not an underrun */ + /* This is a good status and what we expect */ + break; + + default: + /* Data overrun, system error, or unknown */ + ep->req_pending = 0; + ep_err(ep, "DMA critical EP error: System error (0x%x)!\n", + status); + done(ep, req, -ECONNABORTED); + return; + } + + /* ISO endpoints are handled differently */ + if (ep->eptype == EP_ISO_TYPE) { + if (ep->is_in) + req->req.actual = req->req.length; + else + req->req.actual = dd->iso_status[0] & 0xFFFF; + } else + req->req.actual += DD_STATUS_CURDMACNT(status); + + /* Send a ZLP if necessary. This will be done for non-int + * packets which have a size that is a divisor of MAXP */ + if (req->send_zlp) { + /* + * If at least 1 buffer is available, send the ZLP now. + * Otherwise, the ZLP send needs to be deferred until a + * buffer is available. + */ + if (udc_clearep_getsts(udc, ep->hwep_num) & EP_SEL_F) { + udc_clearep_getsts(udc, ep->hwep_num); + uda_enable_hwepint(udc, ep->hwep_num); + epstatus = udc_clearep_getsts(udc, ep->hwep_num); + + /* Let the EP interrupt handle the ZLP */ + return; + } else + udc_send_in_zlp(udc, ep); + } + + /* Transfer request is complete */ + done(ep, req, 0); + + /* Start another request if ready */ + udc_clearep_getsts(udc, ep->hwep_num); + if (!list_empty((&ep->queue))) { + if (ep->is_in) + udc_ep_in_req_dma(udc, ep); + else + udc_ep_out_req_dma(udc, ep); + } else + ep->req_pending = 0; + +} + +/* + * + * Endpoint 0 functions + * + */ +static void udc_handle_dev(struct lpc32xx_udc *udc) +{ + u32 tmp; + + udc_protocol_cmd_w(udc, CMD_GET_DEV_STAT); + tmp = udc_protocol_cmd_r(udc, DAT_GET_DEV_STAT); + + if (tmp & DEV_RST) + uda_usb_reset(udc); + else if (tmp & DEV_CON_CH) + uda_power_event(udc, (tmp & DEV_CON)); + else if (tmp & DEV_SUS_CH) { + if (tmp & DEV_SUS) { + if (udc->vbus == 0) + stop_activity(udc); + else if ((udc->gadget.speed != USB_SPEED_UNKNOWN) && + udc->driver) { + /* Power down transceiver */ + udc->poweron = 0; + schedule_work(&udc->pullup_job); + uda_resm_susp_event(udc, 1); + } + } else if ((udc->gadget.speed != USB_SPEED_UNKNOWN) && + udc->driver && udc->vbus) { + uda_resm_susp_event(udc, 0); + /* Power up transceiver */ + udc->poweron = 1; + schedule_work(&udc->pullup_job); + } + } +} + +static int udc_get_status(struct lpc32xx_udc *udc, u16 reqtype, u16 wIndex) +{ + struct lpc32xx_ep *ep; + u32 ep0buff = 0, tmp; + + switch (reqtype & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + break; /* Not supported */ + + case USB_RECIP_DEVICE: + ep0buff = (udc->selfpowered << USB_DEVICE_SELF_POWERED); + if (udc->dev_status & (1 << USB_DEVICE_REMOTE_WAKEUP)) + ep0buff |= (1 << USB_DEVICE_REMOTE_WAKEUP); + break; + + case USB_RECIP_ENDPOINT: + tmp = wIndex & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[tmp]; + if ((tmp == 0) || (tmp >= NUM_ENDPOINTS)) + return -EOPNOTSUPP; + + if (wIndex & USB_DIR_IN) { + if (!ep->is_in) + return -EOPNOTSUPP; /* Something's wrong */ + } else if (ep->is_in) + return -EOPNOTSUPP; /* Not an IN endpoint */ + + /* Get status of the endpoint */ + udc_protocol_cmd_w(udc, CMD_SEL_EP(ep->hwep_num)); + tmp = udc_protocol_cmd_r(udc, DAT_SEL_EP(ep->hwep_num)); + + if (tmp & EP_SEL_ST) + ep0buff = (1 << USB_ENDPOINT_HALT); + else + ep0buff = 0; + break; + + default: + break; + } + + /* Return data */ + udc_write_hwep(udc, EP_IN, &ep0buff, 2); + + return 0; +} + +static void udc_handle_ep0_setup(struct lpc32xx_udc *udc) +{ + struct lpc32xx_ep *ep, *ep0 = &udc->ep[0]; + struct usb_ctrlrequest ctrlpkt; + int i, bytes; + u16 wIndex, wValue, wLength, reqtype, req, tmp; + + /* Nuke previous transfers */ + nuke(ep0, -EPROTO); + + /* Get setup packet */ + bytes = udc_read_hwep(udc, EP_OUT, (u32 *) &ctrlpkt, 8); + if (bytes != 8) { + ep_warn(ep0, "Incorrectly sized setup packet (s/b 8, is %d)!\n", + bytes); + return; + } + + /* Native endianness */ + wIndex = le16_to_cpu(ctrlpkt.wIndex); + wValue = le16_to_cpu(ctrlpkt.wValue); + wLength = le16_to_cpu(ctrlpkt.wLength); + reqtype = le16_to_cpu(ctrlpkt.bRequestType); + + /* Set direction of EP0 */ + if (likely(reqtype & USB_DIR_IN)) + ep0->is_in = 1; + else + ep0->is_in = 0; + + /* Handle SETUP packet */ + req = le16_to_cpu(ctrlpkt.bRequest); + switch (req) { + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + switch (reqtype) { + case (USB_TYPE_STANDARD | USB_RECIP_DEVICE): + if (wValue != USB_DEVICE_REMOTE_WAKEUP) + goto stall; /* Nothing else handled */ + + /* Tell board about event */ + if (req == USB_REQ_CLEAR_FEATURE) + udc->dev_status &= + ~(1 << USB_DEVICE_REMOTE_WAKEUP); + else + udc->dev_status |= + (1 << USB_DEVICE_REMOTE_WAKEUP); + uda_remwkp_cgh(udc); + goto zlp_send; + + case (USB_TYPE_STANDARD | USB_RECIP_ENDPOINT): + tmp = wIndex & USB_ENDPOINT_NUMBER_MASK; + if ((wValue != USB_ENDPOINT_HALT) || + (tmp >= NUM_ENDPOINTS)) + break; + + /* Find hardware endpoint from logical endpoint */ + ep = &udc->ep[tmp]; + tmp = ep->hwep_num; + if (tmp == 0) + break; + + if (req == USB_REQ_SET_FEATURE) + udc_stall_hwep(udc, tmp); + else if (!ep->wedge) + udc_clrstall_hwep(udc, tmp); + + goto zlp_send; + + default: + break; + } + + + case USB_REQ_SET_ADDRESS: + if (reqtype == (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) { + udc_set_address(udc, wValue); + goto zlp_send; + } + break; + + case USB_REQ_GET_STATUS: + udc_get_status(udc, reqtype, wIndex); + return; + + default: + break; /* Let GadgetFS handle the descriptor instead */ + } + + if (likely(udc->driver)) { + /* device-2-host (IN) or no data setup command, process + * immediately */ + spin_unlock(&udc->lock); + i = udc->driver->setup(&udc->gadget, &ctrlpkt); + + spin_lock(&udc->lock); + if (req == USB_REQ_SET_CONFIGURATION) { + /* Configuration is set after endpoints are realized */ + if (wValue) { + /* Set configuration */ + udc_set_device_configured(udc); + + udc_protocol_cmd_data_w(udc, CMD_SET_MODE, + DAT_WR_BYTE(AP_CLK | + INAK_BI | INAK_II)); + } else { + /* Clear configuration */ + udc_set_device_unconfigured(udc); + + /* Disable NAK interrupts */ + udc_protocol_cmd_data_w(udc, CMD_SET_MODE, + DAT_WR_BYTE(AP_CLK)); + } + } + + if (i < 0) { + /* setup processing failed, force stall */ + dev_dbg(udc->dev, + "req %02x.%02x protocol STALL; stat %d\n", + reqtype, req, i); + udc->ep0state = WAIT_FOR_SETUP; + goto stall; + } + } + + if (!ep0->is_in) + udc_ep0_send_zlp(udc); /* ZLP IN packet on data phase */ + + return; + +stall: + udc_stall_hwep(udc, EP_IN); + return; + +zlp_send: + udc_ep0_send_zlp(udc); + return; +} + +/* IN endpoint 0 transfer */ +static void udc_handle_ep0_in(struct lpc32xx_udc *udc) +{ + struct lpc32xx_ep *ep0 = &udc->ep[0]; + u32 epstatus; + + /* Clear EP interrupt */ + epstatus = udc_clearep_getsts(udc, EP_IN); + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + ep0->totalints++; +#endif + + /* Stalled? Clear stall and reset buffers */ + if (epstatus & EP_SEL_ST) { + udc_clrstall_hwep(udc, EP_IN); + nuke(ep0, -ECONNABORTED); + udc->ep0state = WAIT_FOR_SETUP; + return; + } + + /* Is a buffer available? */ + if (!(epstatus & EP_SEL_F)) { + /* Handle based on current state */ + if (udc->ep0state == DATA_IN) + udc_ep0_in_req(udc); + else { + /* Unknown state for EP0 oe end of DATA IN phase */ + nuke(ep0, -ECONNABORTED); + udc->ep0state = WAIT_FOR_SETUP; + } + } +} + +/* OUT endpoint 0 transfer */ +static void udc_handle_ep0_out(struct lpc32xx_udc *udc) +{ + struct lpc32xx_ep *ep0 = &udc->ep[0]; + u32 epstatus; + + /* Clear EP interrupt */ + epstatus = udc_clearep_getsts(udc, EP_OUT); + + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + ep0->totalints++; +#endif + + /* Stalled? */ + if (epstatus & EP_SEL_ST) { + udc_clrstall_hwep(udc, EP_OUT); + nuke(ep0, -ECONNABORTED); + udc->ep0state = WAIT_FOR_SETUP; + return; + } + + /* A NAK may occur if a packet couldn't be received yet */ + if (epstatus & EP_SEL_EPN) + return; + /* Setup packet incoming? */ + if (epstatus & EP_SEL_STP) { + nuke(ep0, 0); + udc->ep0state = WAIT_FOR_SETUP; + } + + /* Data available? */ + if (epstatus & EP_SEL_F) + /* Handle based on current state */ + switch (udc->ep0state) { + case WAIT_FOR_SETUP: + udc_handle_ep0_setup(udc); + break; + + case DATA_OUT: + udc_ep0_out_req(udc); + break; + + default: + /* Unknown state for EP0 */ + nuke(ep0, -ECONNABORTED); + udc->ep0state = WAIT_FOR_SETUP; + } +} + +/* Must be called without lock */ +static int lpc32xx_get_frame(struct usb_gadget *gadget) +{ + int frame; + unsigned long flags; + struct lpc32xx_udc *udc = to_udc(gadget); + + if (!udc->clocked) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + + frame = (int) udc_get_current_frame(udc); + + spin_unlock_irqrestore(&udc->lock, flags); + + return frame; +} + +static int lpc32xx_wakeup(struct usb_gadget *gadget) +{ + return -ENOTSUPP; +} + +static int lpc32xx_set_selfpowered(struct usb_gadget *gadget, int is_on) +{ + struct lpc32xx_udc *udc = to_udc(gadget); + + /* Always self-powered */ + udc->selfpowered = (is_on != 0); + + return 0; +} + +/* + * vbus is here! turn everything on that's ready + * Must be called without lock + */ +static int lpc32xx_vbus_session(struct usb_gadget *gadget, int is_active) +{ + unsigned long flags; + struct lpc32xx_udc *udc = to_udc(gadget); + + spin_lock_irqsave(&udc->lock, flags); + + /* Doesn't need lock */ + if (udc->driver) { + udc_clk_set(udc, 1); + udc_enable(udc); + pullup(udc, is_active); + } else { + stop_activity(udc); + pullup(udc, 0); + + spin_unlock_irqrestore(&udc->lock, flags); + /* + * Wait for all the endpoints to disable, + * before disabling clocks. Don't wait if + * endpoints are not enabled. + */ + if (atomic_read(&udc->enabled_ep_cnt)) + wait_event_interruptible(udc->ep_disable_wait_queue, + (atomic_read(&udc->enabled_ep_cnt) == 0)); + + spin_lock_irqsave(&udc->lock, flags); + + udc_clk_set(udc, 0); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/* Can be called with or without lock */ +static int lpc32xx_pullup(struct usb_gadget *gadget, int is_on) +{ + struct lpc32xx_udc *udc = to_udc(gadget); + + /* Doesn't need lock */ + pullup(udc, is_on); + + return 0; +} + +static int lpc32xx_start(struct usb_gadget *, struct usb_gadget_driver *); +static int lpc32xx_stop(struct usb_gadget *, struct usb_gadget_driver *); + +static const struct usb_gadget_ops lpc32xx_udc_ops = { + .get_frame = lpc32xx_get_frame, + .wakeup = lpc32xx_wakeup, + .set_selfpowered = lpc32xx_set_selfpowered, + .vbus_session = lpc32xx_vbus_session, + .pullup = lpc32xx_pullup, + .udc_start = lpc32xx_start, + .udc_stop = lpc32xx_stop, +}; + +static void nop_release(struct device *dev) +{ + /* nothing to free */ +} + +static const struct lpc32xx_udc controller_template = { + .gadget = { + .ops = &lpc32xx_udc_ops, + .name = driver_name, + .dev = { + .init_name = "gadget", + .release = nop_release, + } + }, + .ep[0] = { + .ep = { + .name = "ep0", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 0, + .hwep_num = 0, /* Can be 0 or 1, has special handling */ + .lep = 0, + .eptype = EP_CTL_TYPE, + }, + .ep[1] = { + .ep = { + .name = "ep1-int", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 2, + .hwep_num = 0, /* 2 or 3, will be set later */ + .lep = 1, + .eptype = EP_INT_TYPE, + }, + .ep[2] = { + .ep = { + .name = "ep2-bulk", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 4, + .hwep_num = 0, /* 4 or 5, will be set later */ + .lep = 2, + .eptype = EP_BLK_TYPE, + }, + .ep[3] = { + .ep = { + .name = "ep3-iso", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 1023, + .hwep_num_base = 6, + .hwep_num = 0, /* 6 or 7, will be set later */ + .lep = 3, + .eptype = EP_ISO_TYPE, + }, + .ep[4] = { + .ep = { + .name = "ep4-int", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 8, + .hwep_num = 0, /* 8 or 9, will be set later */ + .lep = 4, + .eptype = EP_INT_TYPE, + }, + .ep[5] = { + .ep = { + .name = "ep5-bulk", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 10, + .hwep_num = 0, /* 10 or 11, will be set later */ + .lep = 5, + .eptype = EP_BLK_TYPE, + }, + .ep[6] = { + .ep = { + .name = "ep6-iso", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 1023, + .hwep_num_base = 12, + .hwep_num = 0, /* 12 or 13, will be set later */ + .lep = 6, + .eptype = EP_ISO_TYPE, + }, + .ep[7] = { + .ep = { + .name = "ep7-int", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 14, + .hwep_num = 0, + .lep = 7, + .eptype = EP_INT_TYPE, + }, + .ep[8] = { + .ep = { + .name = "ep8-bulk", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 16, + .hwep_num = 0, + .lep = 8, + .eptype = EP_BLK_TYPE, + }, + .ep[9] = { + .ep = { + .name = "ep9-iso", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 1023, + .hwep_num_base = 18, + .hwep_num = 0, + .lep = 9, + .eptype = EP_ISO_TYPE, + }, + .ep[10] = { + .ep = { + .name = "ep10-int", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 20, + .hwep_num = 0, + .lep = 10, + .eptype = EP_INT_TYPE, + }, + .ep[11] = { + .ep = { + .name = "ep11-bulk", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 22, + .hwep_num = 0, + .lep = 11, + .eptype = EP_BLK_TYPE, + }, + .ep[12] = { + .ep = { + .name = "ep12-iso", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 1023, + .hwep_num_base = 24, + .hwep_num = 0, + .lep = 12, + .eptype = EP_ISO_TYPE, + }, + .ep[13] = { + .ep = { + .name = "ep13-int", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 26, + .hwep_num = 0, + .lep = 13, + .eptype = EP_INT_TYPE, + }, + .ep[14] = { + .ep = { + .name = "ep14-bulk", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 64, + .hwep_num_base = 28, + .hwep_num = 0, + .lep = 14, + .eptype = EP_BLK_TYPE, + }, + .ep[15] = { + .ep = { + .name = "ep15-bulk", + .ops = &lpc32xx_ep_ops, + }, + .maxpacket = 1023, + .hwep_num_base = 30, + .hwep_num = 0, + .lep = 15, + .eptype = EP_BLK_TYPE, + }, +}; + +/* ISO and status interrupts */ +static irqreturn_t lpc32xx_usb_lp_irq(int irq, void *_udc) +{ + u32 tmp, devstat; + struct lpc32xx_udc *udc = _udc; + + spin_lock(&udc->lock); + + /* Read the device status register */ + devstat = readl(USBD_DEVINTST(udc->udp_baseaddr)); + + devstat &= ~USBD_EP_FAST; + writel(devstat, USBD_DEVINTCLR(udc->udp_baseaddr)); + devstat = devstat & udc->enabled_devints; + + /* Device specific handling needed? */ + if (devstat & USBD_DEV_STAT) + udc_handle_dev(udc); + + /* Start of frame? (devstat & FRAME_INT): + * The frame interrupt isn't really needed for ISO support, + * as the driver will queue the necessary packets */ + + /* Error? */ + if (devstat & ERR_INT) { + /* All types of errors, from cable removal during transfer to + * misc protocol and bit errors. These are mostly for just info, + * as the USB hardware will work around these. If these errors + * happen alot, something is wrong. */ + udc_protocol_cmd_w(udc, CMD_RD_ERR_STAT); + tmp = udc_protocol_cmd_r(udc, DAT_RD_ERR_STAT); + dev_dbg(udc->dev, "Device error (0x%x)!\n", tmp); + } + + spin_unlock(&udc->lock); + + return IRQ_HANDLED; +} + +/* EP interrupts */ +static irqreturn_t lpc32xx_usb_hp_irq(int irq, void *_udc) +{ + u32 tmp; + struct lpc32xx_udc *udc = _udc; + + spin_lock(&udc->lock); + + /* Read the device status register */ + writel(USBD_EP_FAST, USBD_DEVINTCLR(udc->udp_baseaddr)); + + /* Endpoints */ + tmp = readl(USBD_EPINTST(udc->udp_baseaddr)); + + /* Special handling for EP0 */ + if (tmp & (EP_MASK_SEL(0, EP_OUT) | EP_MASK_SEL(0, EP_IN))) { + /* Handle EP0 IN */ + if (tmp & (EP_MASK_SEL(0, EP_IN))) + udc_handle_ep0_in(udc); + + /* Handle EP0 OUT */ + if (tmp & (EP_MASK_SEL(0, EP_OUT))) + udc_handle_ep0_out(udc); + } + + /* All other EPs */ + if (tmp & ~(EP_MASK_SEL(0, EP_OUT) | EP_MASK_SEL(0, EP_IN))) { + int i; + + /* Handle other EP interrupts */ + for (i = 1; i < NUM_ENDPOINTS; i++) { + if (tmp & (1 << udc->ep[i].hwep_num)) + udc_handle_eps(udc, &udc->ep[i]); + } + } + + spin_unlock(&udc->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t lpc32xx_usb_devdma_irq(int irq, void *_udc) +{ + struct lpc32xx_udc *udc = _udc; + + int i; + u32 tmp; + + spin_lock(&udc->lock); + + /* Handle EP DMA EOT interrupts */ + tmp = readl(USBD_EOTINTST(udc->udp_baseaddr)) | + (readl(USBD_EPDMAST(udc->udp_baseaddr)) & + readl(USBD_NDDRTINTST(udc->udp_baseaddr))) | + readl(USBD_SYSERRTINTST(udc->udp_baseaddr)); + for (i = 1; i < NUM_ENDPOINTS; i++) { + if (tmp & (1 << udc->ep[i].hwep_num)) + udc_handle_dma_ep(udc, &udc->ep[i]); + } + + spin_unlock(&udc->lock); + + return IRQ_HANDLED; +} + +/* + * + * VBUS detection, pullup handler, and Gadget cable state notification + * + */ +static void vbus_work(struct work_struct *work) +{ + u8 value; + struct lpc32xx_udc *udc = container_of(work, struct lpc32xx_udc, + vbus_job); + + if (udc->enabled != 0) { + /* Discharge VBUS real quick */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DISCHRG); + + /* Give VBUS some time (100mS) to discharge */ + msleep(100); + + /* Disable VBUS discharge resistor */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR, + OTG1_VBUS_DISCHRG); + + /* Clear interrupt */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_LATCH | + ISP1301_I2C_REG_CLEAR_ADDR, ~0); + + /* Get the VBUS status from the transceiver */ + value = i2c_smbus_read_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_SOURCE); + + /* VBUS on or off? */ + if (value & INT_SESS_VLD) + udc->vbus = 1; + else + udc->vbus = 0; + + /* VBUS changed? */ + if (udc->last_vbus != udc->vbus) { + udc->last_vbus = udc->vbus; + lpc32xx_vbus_session(&udc->gadget, udc->vbus); + } + } + + /* Re-enable after completion */ + enable_irq(udc->udp_irq[IRQ_USB_ATX]); +} + +static irqreturn_t lpc32xx_usb_vbus_irq(int irq, void *_udc) +{ + struct lpc32xx_udc *udc = _udc; + + /* Defer handling of VBUS IRQ to work queue */ + disable_irq_nosync(udc->udp_irq[IRQ_USB_ATX]); + schedule_work(&udc->vbus_job); + + return IRQ_HANDLED; +} + +static int lpc32xx_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct lpc32xx_udc *udc = to_udc(gadget); + int i; + + if (!driver || driver->max_speed < USB_SPEED_FULL || !driver->setup) { + dev_err(udc->dev, "bad parameter.\n"); + return -EINVAL; + } + + if (udc->driver) { + dev_err(udc->dev, "UDC already has a gadget driver\n"); + return -EBUSY; + } + + udc->driver = driver; + udc->gadget.dev.of_node = udc->dev->of_node; + udc->enabled = 1; + udc->selfpowered = 1; + udc->vbus = 0; + + /* Force VBUS process once to check for cable insertion */ + udc->last_vbus = udc->vbus = 0; + schedule_work(&udc->vbus_job); + + /* Do not re-enable ATX IRQ (3) */ + for (i = IRQ_USB_LP; i < IRQ_USB_ATX; i++) + enable_irq(udc->udp_irq[i]); + + return 0; +} + +static int lpc32xx_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + int i; + struct lpc32xx_udc *udc = to_udc(gadget); + + if (!driver || driver != udc->driver) + return -EINVAL; + + for (i = IRQ_USB_LP; i <= IRQ_USB_ATX; i++) + disable_irq(udc->udp_irq[i]); + + if (udc->clocked) { + spin_lock(&udc->lock); + stop_activity(udc); + spin_unlock(&udc->lock); + + /* + * Wait for all the endpoints to disable, + * before disabling clocks. Don't wait if + * endpoints are not enabled. + */ + if (atomic_read(&udc->enabled_ep_cnt)) + wait_event_interruptible(udc->ep_disable_wait_queue, + (atomic_read(&udc->enabled_ep_cnt) == 0)); + + spin_lock(&udc->lock); + udc_clk_set(udc, 0); + spin_unlock(&udc->lock); + } + + udc->enabled = 0; + udc->driver = NULL; + + return 0; +} + +static void lpc32xx_udc_shutdown(struct platform_device *dev) +{ + /* Force disconnect on reboot */ + struct lpc32xx_udc *udc = platform_get_drvdata(dev); + + pullup(udc, 0); +} + +/* + * Callbacks to be overridden by options passed via OF (TODO) + */ + +static void lpc32xx_usbd_conn_chg(int conn) +{ + /* Do nothing, it might be nice to enable an LED + * based on conn state being !0 */ +} + +static void lpc32xx_usbd_susp_chg(int susp) +{ + /* Device suspend if susp != 0 */ +} + +static void lpc32xx_rmwkup_chg(int remote_wakup_enable) +{ + /* Enable or disable USB remote wakeup */ +} + +struct lpc32xx_usbd_cfg lpc32xx_usbddata = { + .vbus_drv_pol = 0, + .conn_chgb = &lpc32xx_usbd_conn_chg, + .susp_chgb = &lpc32xx_usbd_susp_chg, + .rmwk_chgb = &lpc32xx_rmwkup_chg, +}; + + +static u64 lpc32xx_usbd_dmamask = ~(u32) 0x7F; + +static int __init lpc32xx_udc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lpc32xx_udc *udc; + int retval, i; + struct resource *res; + dma_addr_t dma_handle; + struct device_node *isp1301_node; + + udc = kzalloc(sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + memcpy(udc, &controller_template, sizeof(*udc)); + for (i = 0; i <= 15; i++) + udc->ep[i].udc = udc; + udc->gadget.ep0 = &udc->ep[0].ep; + + /* init software state */ + udc->gadget.dev.parent = dev; + udc->pdev = pdev; + udc->dev = &pdev->dev; + udc->enabled = 0; + + if (pdev->dev.of_node) { + isp1301_node = of_parse_phandle(pdev->dev.of_node, + "transceiver", 0); + } else { + isp1301_node = NULL; + } + + udc->isp1301_i2c_client = isp1301_get_client(isp1301_node); + if (!udc->isp1301_i2c_client) { + retval = -EPROBE_DEFER; + goto phy_fail; + } + + dev_info(udc->dev, "ISP1301 I2C device at address 0x%x\n", + udc->isp1301_i2c_client->addr); + + pdev->dev.dma_mask = &lpc32xx_usbd_dmamask; + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + udc->board = &lpc32xx_usbddata; + + /* + * Resources are mapped as follows: + * IORESOURCE_MEM, base address and size of USB space + * IORESOURCE_IRQ, USB device low priority interrupt number + * IORESOURCE_IRQ, USB device high priority interrupt number + * IORESOURCE_IRQ, USB device interrupt number + * IORESOURCE_IRQ, USB transceiver interrupt number + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + retval = -ENXIO; + goto resource_fail; + } + + spin_lock_init(&udc->lock); + + /* Get IRQs */ + for (i = 0; i < 4; i++) { + udc->udp_irq[i] = platform_get_irq(pdev, i); + if (udc->udp_irq[i] < 0) { + dev_err(udc->dev, + "irq resource %d not available!\n", i); + retval = udc->udp_irq[i]; + goto irq_fail; + } + } + + udc->io_p_start = res->start; + udc->io_p_size = resource_size(res); + if (!request_mem_region(udc->io_p_start, udc->io_p_size, driver_name)) { + dev_err(udc->dev, "someone's using UDC memory\n"); + retval = -EBUSY; + goto request_mem_region_fail; + } + + udc->udp_baseaddr = ioremap(udc->io_p_start, udc->io_p_size); + if (!udc->udp_baseaddr) { + retval = -ENOMEM; + dev_err(udc->dev, "IO map failure\n"); + goto io_map_fail; + } + + /* Enable AHB slave USB clock, needed for further USB clock control */ + writel(USB_SLAVE_HCLK_EN | (1 << 19), USB_CTRL); + + /* Get required clocks */ + udc->usb_pll_clk = clk_get(&pdev->dev, "ck_pll5"); + if (IS_ERR(udc->usb_pll_clk)) { + dev_err(udc->dev, "failed to acquire USB PLL\n"); + retval = PTR_ERR(udc->usb_pll_clk); + goto pll_get_fail; + } + udc->usb_slv_clk = clk_get(&pdev->dev, "ck_usbd"); + if (IS_ERR(udc->usb_slv_clk)) { + dev_err(udc->dev, "failed to acquire USB device clock\n"); + retval = PTR_ERR(udc->usb_slv_clk); + goto usb_clk_get_fail; + } + udc->usb_otg_clk = clk_get(&pdev->dev, "ck_usb_otg"); + if (IS_ERR(udc->usb_otg_clk)) { + dev_err(udc->dev, "failed to acquire USB otg clock\n"); + retval = PTR_ERR(udc->usb_otg_clk); + goto usb_otg_clk_get_fail; + } + + /* Setup PLL clock to 48MHz */ + retval = clk_enable(udc->usb_pll_clk); + if (retval < 0) { + dev_err(udc->dev, "failed to start USB PLL\n"); + goto pll_enable_fail; + } + + retval = clk_set_rate(udc->usb_pll_clk, 48000); + if (retval < 0) { + dev_err(udc->dev, "failed to set USB clock rate\n"); + goto pll_set_fail; + } + + writel(readl(USB_CTRL) | USB_DEV_NEED_CLK_EN, USB_CTRL); + + /* Enable USB device clock */ + retval = clk_enable(udc->usb_slv_clk); + if (retval < 0) { + dev_err(udc->dev, "failed to start USB device clock\n"); + goto usb_clk_enable_fail; + } + + /* Enable USB OTG clock */ + retval = clk_enable(udc->usb_otg_clk); + if (retval < 0) { + dev_err(udc->dev, "failed to start USB otg clock\n"); + goto usb_otg_clk_enable_fail; + } + + /* Setup deferred workqueue data */ + udc->poweron = udc->pullup = 0; + INIT_WORK(&udc->pullup_job, pullup_work); + INIT_WORK(&udc->vbus_job, vbus_work); +#ifdef CONFIG_PM + INIT_WORK(&udc->power_job, power_work); +#endif + + /* All clocks are now on */ + udc->clocked = 1; + + isp1301_udc_configure(udc); + /* Allocate memory for the UDCA */ + udc->udca_v_base = dma_alloc_coherent(&pdev->dev, UDCA_BUFF_SIZE, + &dma_handle, + (GFP_KERNEL | GFP_DMA)); + if (!udc->udca_v_base) { + dev_err(udc->dev, "error getting UDCA region\n"); + retval = -ENOMEM; + goto i2c_fail; + } + udc->udca_p_base = dma_handle; + dev_dbg(udc->dev, "DMA buffer(0x%x bytes), P:0x%08x, V:0x%p\n", + UDCA_BUFF_SIZE, udc->udca_p_base, udc->udca_v_base); + + /* Setup the DD DMA memory pool */ + udc->dd_cache = dma_pool_create("udc_dd", udc->dev, + sizeof(struct lpc32xx_usbd_dd_gad), + sizeof(u32), 0); + if (!udc->dd_cache) { + dev_err(udc->dev, "error getting DD DMA region\n"); + retval = -ENOMEM; + goto dma_alloc_fail; + } + + /* Clear USB peripheral and initialize gadget endpoints */ + udc_disable(udc); + udc_reinit(udc); + + /* Request IRQs - low and high priority USB device IRQs are routed to + * the same handler, while the DMA interrupt is routed elsewhere */ + retval = request_irq(udc->udp_irq[IRQ_USB_LP], lpc32xx_usb_lp_irq, + 0, "udc_lp", udc); + if (retval < 0) { + dev_err(udc->dev, "LP request irq %d failed\n", + udc->udp_irq[IRQ_USB_LP]); + goto irq_lp_fail; + } + retval = request_irq(udc->udp_irq[IRQ_USB_HP], lpc32xx_usb_hp_irq, + 0, "udc_hp", udc); + if (retval < 0) { + dev_err(udc->dev, "HP request irq %d failed\n", + udc->udp_irq[IRQ_USB_HP]); + goto irq_hp_fail; + } + + retval = request_irq(udc->udp_irq[IRQ_USB_DEVDMA], + lpc32xx_usb_devdma_irq, 0, "udc_dma", udc); + if (retval < 0) { + dev_err(udc->dev, "DEV request irq %d failed\n", + udc->udp_irq[IRQ_USB_DEVDMA]); + goto irq_dev_fail; + } + + /* The transceiver interrupt is used for VBUS detection and will + kick off the VBUS handler function */ + retval = request_irq(udc->udp_irq[IRQ_USB_ATX], lpc32xx_usb_vbus_irq, + 0, "udc_otg", udc); + if (retval < 0) { + dev_err(udc->dev, "VBUS request irq %d failed\n", + udc->udp_irq[IRQ_USB_ATX]); + goto irq_xcvr_fail; + } + + /* Initialize wait queue */ + init_waitqueue_head(&udc->ep_disable_wait_queue); + atomic_set(&udc->enabled_ep_cnt, 0); + + /* Keep all IRQs disabled until GadgetFS starts up */ + for (i = IRQ_USB_LP; i <= IRQ_USB_ATX; i++) + disable_irq(udc->udp_irq[i]); + + retval = usb_add_gadget_udc(dev, &udc->gadget); + if (retval < 0) + goto add_gadget_fail; + + dev_set_drvdata(dev, udc); + device_init_wakeup(dev, 1); + create_debug_file(udc); + + /* Disable clocks for now */ + udc_clk_set(udc, 0); + + dev_info(udc->dev, "%s version %s\n", driver_name, DRIVER_VERSION); + return 0; + +add_gadget_fail: + free_irq(udc->udp_irq[IRQ_USB_ATX], udc); +irq_xcvr_fail: + free_irq(udc->udp_irq[IRQ_USB_DEVDMA], udc); +irq_dev_fail: + free_irq(udc->udp_irq[IRQ_USB_HP], udc); +irq_hp_fail: + free_irq(udc->udp_irq[IRQ_USB_LP], udc); +irq_lp_fail: + dma_pool_destroy(udc->dd_cache); +dma_alloc_fail: + dma_free_coherent(&pdev->dev, UDCA_BUFF_SIZE, + udc->udca_v_base, udc->udca_p_base); +i2c_fail: + clk_disable(udc->usb_otg_clk); +usb_otg_clk_enable_fail: + clk_disable(udc->usb_slv_clk); +usb_clk_enable_fail: +pll_set_fail: + clk_disable(udc->usb_pll_clk); +pll_enable_fail: + clk_put(udc->usb_slv_clk); +usb_otg_clk_get_fail: + clk_put(udc->usb_otg_clk); +usb_clk_get_fail: + clk_put(udc->usb_pll_clk); +pll_get_fail: + iounmap(udc->udp_baseaddr); +io_map_fail: + release_mem_region(udc->io_p_start, udc->io_p_size); + dev_err(udc->dev, "%s probe failed, %d\n", driver_name, retval); +request_mem_region_fail: +irq_fail: +resource_fail: +phy_fail: + kfree(udc); + return retval; +} + +static int lpc32xx_udc_remove(struct platform_device *pdev) +{ + struct lpc32xx_udc *udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) + return -EBUSY; + + udc_clk_set(udc, 1); + udc_disable(udc); + pullup(udc, 0); + + free_irq(udc->udp_irq[IRQ_USB_ATX], udc); + + device_init_wakeup(&pdev->dev, 0); + remove_debug_file(udc); + + dma_pool_destroy(udc->dd_cache); + dma_free_coherent(&pdev->dev, UDCA_BUFF_SIZE, + udc->udca_v_base, udc->udca_p_base); + free_irq(udc->udp_irq[IRQ_USB_DEVDMA], udc); + free_irq(udc->udp_irq[IRQ_USB_HP], udc); + free_irq(udc->udp_irq[IRQ_USB_LP], udc); + + clk_disable(udc->usb_otg_clk); + clk_put(udc->usb_otg_clk); + clk_disable(udc->usb_slv_clk); + clk_put(udc->usb_slv_clk); + clk_disable(udc->usb_pll_clk); + clk_put(udc->usb_pll_clk); + iounmap(udc->udp_baseaddr); + release_mem_region(udc->io_p_start, udc->io_p_size); + kfree(udc); + + return 0; +} + +#ifdef CONFIG_PM +static int lpc32xx_udc_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct lpc32xx_udc *udc = platform_get_drvdata(pdev); + + if (udc->clocked) { + /* Power down ISP */ + udc->poweron = 0; + isp1301_set_powerstate(udc, 0); + + /* Disable clocking */ + udc_clk_set(udc, 0); + + /* Keep clock flag on, so we know to re-enable clocks + on resume */ + udc->clocked = 1; + + /* Kill global USB clock */ + clk_disable(udc->usb_slv_clk); + } + + return 0; +} + +static int lpc32xx_udc_resume(struct platform_device *pdev) +{ + struct lpc32xx_udc *udc = platform_get_drvdata(pdev); + + if (udc->clocked) { + /* Enable global USB clock */ + clk_enable(udc->usb_slv_clk); + + /* Enable clocking */ + udc_clk_set(udc, 1); + + /* ISP back to normal power mode */ + udc->poweron = 1; + isp1301_set_powerstate(udc, 1); + } + + return 0; +} +#else +#define lpc32xx_udc_suspend NULL +#define lpc32xx_udc_resume NULL +#endif + +#ifdef CONFIG_OF +static struct of_device_id lpc32xx_udc_of_match[] = { + { .compatible = "nxp,lpc3220-udc", }, + { }, +}; +MODULE_DEVICE_TABLE(of, lpc32xx_udc_of_match); +#endif + +static struct platform_driver lpc32xx_udc_driver = { + .remove = lpc32xx_udc_remove, + .shutdown = lpc32xx_udc_shutdown, + .suspend = lpc32xx_udc_suspend, + .resume = lpc32xx_udc_resume, + .driver = { + .name = (char *) driver_name, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(lpc32xx_udc_of_match), + }, +}; + +module_platform_driver_probe(lpc32xx_udc_driver, lpc32xx_udc_probe); + +MODULE_DESCRIPTION("LPC32XX udc driver"); +MODULE_AUTHOR("Kevin Wells "); +MODULE_AUTHOR("Roland Stigge "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lpc32xx_udc"); diff --git a/drivers/usb/gadget/m66592-udc.c b/drivers/usb/gadget/m66592-udc.c new file mode 100644 index 00000000..51cfe72d --- /dev/null +++ b/drivers/usb/gadget/m66592-udc.c @@ -0,0 +1,1709 @@ +/* + * M66592 UDC (USB gadget) + * + * Copyright (C) 2006-2007 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "m66592-udc.h" + +MODULE_DESCRIPTION("M66592 USB gadget driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_ALIAS("platform:m66592_udc"); + +#define DRIVER_VERSION "21 July 2009" + +static const char udc_name[] = "m66592_udc"; +static const char *m66592_ep_name[] = { + "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7" +}; + +static void disable_controller(struct m66592 *m66592); +static void irq_ep0_write(struct m66592_ep *ep, struct m66592_request *req); +static void irq_packet_write(struct m66592_ep *ep, struct m66592_request *req); +static int m66592_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags); + +static void transfer_complete(struct m66592_ep *ep, + struct m66592_request *req, int status); + +/*-------------------------------------------------------------------------*/ +static inline u16 get_usb_speed(struct m66592 *m66592) +{ + return (m66592_read(m66592, M66592_DVSTCTR) & M66592_RHST); +} + +static void enable_pipe_irq(struct m66592 *m66592, u16 pipenum, + unsigned long reg) +{ + u16 tmp; + + tmp = m66592_read(m66592, M66592_INTENB0); + m66592_bclr(m66592, M66592_BEMPE | M66592_NRDYE | M66592_BRDYE, + M66592_INTENB0); + m66592_bset(m66592, (1 << pipenum), reg); + m66592_write(m66592, tmp, M66592_INTENB0); +} + +static void disable_pipe_irq(struct m66592 *m66592, u16 pipenum, + unsigned long reg) +{ + u16 tmp; + + tmp = m66592_read(m66592, M66592_INTENB0); + m66592_bclr(m66592, M66592_BEMPE | M66592_NRDYE | M66592_BRDYE, + M66592_INTENB0); + m66592_bclr(m66592, (1 << pipenum), reg); + m66592_write(m66592, tmp, M66592_INTENB0); +} + +static void m66592_usb_connect(struct m66592 *m66592) +{ + m66592_bset(m66592, M66592_CTRE, M66592_INTENB0); + m66592_bset(m66592, M66592_WDST | M66592_RDST | M66592_CMPL, + M66592_INTENB0); + m66592_bset(m66592, M66592_BEMPE | M66592_BRDYE, M66592_INTENB0); + + m66592_bset(m66592, M66592_DPRPU, M66592_SYSCFG); +} + +static void m66592_usb_disconnect(struct m66592 *m66592) +__releases(m66592->lock) +__acquires(m66592->lock) +{ + m66592_bclr(m66592, M66592_CTRE, M66592_INTENB0); + m66592_bclr(m66592, M66592_WDST | M66592_RDST | M66592_CMPL, + M66592_INTENB0); + m66592_bclr(m66592, M66592_BEMPE | M66592_BRDYE, M66592_INTENB0); + m66592_bclr(m66592, M66592_DPRPU, M66592_SYSCFG); + + m66592->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock(&m66592->lock); + m66592->driver->disconnect(&m66592->gadget); + spin_lock(&m66592->lock); + + disable_controller(m66592); + INIT_LIST_HEAD(&m66592->ep[0].queue); +} + +static inline u16 control_reg_get_pid(struct m66592 *m66592, u16 pipenum) +{ + u16 pid = 0; + unsigned long offset; + + if (pipenum == 0) + pid = m66592_read(m66592, M66592_DCPCTR) & M66592_PID; + else if (pipenum < M66592_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + pid = m66592_read(m66592, offset) & M66592_PID; + } else + pr_err("unexpect pipe num (%d)\n", pipenum); + + return pid; +} + +static inline void control_reg_set_pid(struct m66592 *m66592, u16 pipenum, + u16 pid) +{ + unsigned long offset; + + if (pipenum == 0) + m66592_mdfy(m66592, pid, M66592_PID, M66592_DCPCTR); + else if (pipenum < M66592_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + m66592_mdfy(m66592, pid, M66592_PID, offset); + } else + pr_err("unexpect pipe num (%d)\n", pipenum); +} + +static inline void pipe_start(struct m66592 *m66592, u16 pipenum) +{ + control_reg_set_pid(m66592, pipenum, M66592_PID_BUF); +} + +static inline void pipe_stop(struct m66592 *m66592, u16 pipenum) +{ + control_reg_set_pid(m66592, pipenum, M66592_PID_NAK); +} + +static inline void pipe_stall(struct m66592 *m66592, u16 pipenum) +{ + control_reg_set_pid(m66592, pipenum, M66592_PID_STALL); +} + +static inline u16 control_reg_get(struct m66592 *m66592, u16 pipenum) +{ + u16 ret = 0; + unsigned long offset; + + if (pipenum == 0) + ret = m66592_read(m66592, M66592_DCPCTR); + else if (pipenum < M66592_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + ret = m66592_read(m66592, offset); + } else + pr_err("unexpect pipe num (%d)\n", pipenum); + + return ret; +} + +static inline void control_reg_sqclr(struct m66592 *m66592, u16 pipenum) +{ + unsigned long offset; + + pipe_stop(m66592, pipenum); + + if (pipenum == 0) + m66592_bset(m66592, M66592_SQCLR, M66592_DCPCTR); + else if (pipenum < M66592_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + m66592_bset(m66592, M66592_SQCLR, offset); + } else + pr_err("unexpect pipe num(%d)\n", pipenum); +} + +static inline int get_buffer_size(struct m66592 *m66592, u16 pipenum) +{ + u16 tmp; + int size; + + if (pipenum == 0) { + tmp = m66592_read(m66592, M66592_DCPCFG); + if ((tmp & M66592_CNTMD) != 0) + size = 256; + else { + tmp = m66592_read(m66592, M66592_DCPMAXP); + size = tmp & M66592_MAXP; + } + } else { + m66592_write(m66592, pipenum, M66592_PIPESEL); + tmp = m66592_read(m66592, M66592_PIPECFG); + if ((tmp & M66592_CNTMD) != 0) { + tmp = m66592_read(m66592, M66592_PIPEBUF); + size = ((tmp >> 10) + 1) * 64; + } else { + tmp = m66592_read(m66592, M66592_PIPEMAXP); + size = tmp & M66592_MXPS; + } + } + + return size; +} + +static inline void pipe_change(struct m66592 *m66592, u16 pipenum) +{ + struct m66592_ep *ep = m66592->pipenum2ep[pipenum]; + unsigned short mbw; + + if (ep->use_dma) + return; + + m66592_mdfy(m66592, pipenum, M66592_CURPIPE, ep->fifosel); + + ndelay(450); + + if (m66592->pdata->on_chip) + mbw = M66592_MBW_32; + else + mbw = M66592_MBW_16; + + m66592_bset(m66592, mbw, ep->fifosel); +} + +static int pipe_buffer_setting(struct m66592 *m66592, + struct m66592_pipe_info *info) +{ + u16 bufnum = 0, buf_bsize = 0; + u16 pipecfg = 0; + + if (info->pipe == 0) + return -EINVAL; + + m66592_write(m66592, info->pipe, M66592_PIPESEL); + + if (info->dir_in) + pipecfg |= M66592_DIR; + pipecfg |= info->type; + pipecfg |= info->epnum; + switch (info->type) { + case M66592_INT: + bufnum = 4 + (info->pipe - M66592_BASE_PIPENUM_INT); + buf_bsize = 0; + break; + case M66592_BULK: + /* isochronous pipes may be used as bulk pipes */ + if (info->pipe >= M66592_BASE_PIPENUM_BULK) + bufnum = info->pipe - M66592_BASE_PIPENUM_BULK; + else + bufnum = info->pipe - M66592_BASE_PIPENUM_ISOC; + + bufnum = M66592_BASE_BUFNUM + (bufnum * 16); + buf_bsize = 7; + pipecfg |= M66592_DBLB; + if (!info->dir_in) + pipecfg |= M66592_SHTNAK; + break; + case M66592_ISO: + bufnum = M66592_BASE_BUFNUM + + (info->pipe - M66592_BASE_PIPENUM_ISOC) * 16; + buf_bsize = 7; + break; + } + + if (buf_bsize && ((bufnum + 16) >= M66592_MAX_BUFNUM)) { + pr_err("m66592 pipe memory is insufficient\n"); + return -ENOMEM; + } + + m66592_write(m66592, pipecfg, M66592_PIPECFG); + m66592_write(m66592, (buf_bsize << 10) | (bufnum), M66592_PIPEBUF); + m66592_write(m66592, info->maxpacket, M66592_PIPEMAXP); + if (info->interval) + info->interval--; + m66592_write(m66592, info->interval, M66592_PIPEPERI); + + return 0; +} + +static void pipe_buffer_release(struct m66592 *m66592, + struct m66592_pipe_info *info) +{ + if (info->pipe == 0) + return; + + if (is_bulk_pipe(info->pipe)) { + m66592->bulk--; + } else if (is_interrupt_pipe(info->pipe)) + m66592->interrupt--; + else if (is_isoc_pipe(info->pipe)) { + m66592->isochronous--; + if (info->type == M66592_BULK) + m66592->bulk--; + } else + pr_err("ep_release: unexpect pipenum (%d)\n", + info->pipe); +} + +static void pipe_initialize(struct m66592_ep *ep) +{ + struct m66592 *m66592 = ep->m66592; + unsigned short mbw; + + m66592_mdfy(m66592, 0, M66592_CURPIPE, ep->fifosel); + + m66592_write(m66592, M66592_ACLRM, ep->pipectr); + m66592_write(m66592, 0, ep->pipectr); + m66592_write(m66592, M66592_SQCLR, ep->pipectr); + if (ep->use_dma) { + m66592_mdfy(m66592, ep->pipenum, M66592_CURPIPE, ep->fifosel); + + ndelay(450); + + if (m66592->pdata->on_chip) + mbw = M66592_MBW_32; + else + mbw = M66592_MBW_16; + + m66592_bset(m66592, mbw, ep->fifosel); + } +} + +static void m66592_ep_setting(struct m66592 *m66592, struct m66592_ep *ep, + const struct usb_endpoint_descriptor *desc, + u16 pipenum, int dma) +{ + if ((pipenum != 0) && dma) { + if (m66592->num_dma == 0) { + m66592->num_dma++; + ep->use_dma = 1; + ep->fifoaddr = M66592_D0FIFO; + ep->fifosel = M66592_D0FIFOSEL; + ep->fifoctr = M66592_D0FIFOCTR; + ep->fifotrn = M66592_D0FIFOTRN; + } else if (!m66592->pdata->on_chip && m66592->num_dma == 1) { + m66592->num_dma++; + ep->use_dma = 1; + ep->fifoaddr = M66592_D1FIFO; + ep->fifosel = M66592_D1FIFOSEL; + ep->fifoctr = M66592_D1FIFOCTR; + ep->fifotrn = M66592_D1FIFOTRN; + } else { + ep->use_dma = 0; + ep->fifoaddr = M66592_CFIFO; + ep->fifosel = M66592_CFIFOSEL; + ep->fifoctr = M66592_CFIFOCTR; + ep->fifotrn = 0; + } + } else { + ep->use_dma = 0; + ep->fifoaddr = M66592_CFIFO; + ep->fifosel = M66592_CFIFOSEL; + ep->fifoctr = M66592_CFIFOCTR; + ep->fifotrn = 0; + } + + ep->pipectr = get_pipectr_addr(pipenum); + ep->pipenum = pipenum; + ep->ep.maxpacket = usb_endpoint_maxp(desc); + m66592->pipenum2ep[pipenum] = ep; + m66592->epaddr2ep[desc->bEndpointAddress&USB_ENDPOINT_NUMBER_MASK] = ep; + INIT_LIST_HEAD(&ep->queue); +} + +static void m66592_ep_release(struct m66592_ep *ep) +{ + struct m66592 *m66592 = ep->m66592; + u16 pipenum = ep->pipenum; + + if (pipenum == 0) + return; + + if (ep->use_dma) + m66592->num_dma--; + ep->pipenum = 0; + ep->busy = 0; + ep->use_dma = 0; +} + +static int alloc_pipe_config(struct m66592_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct m66592 *m66592 = ep->m66592; + struct m66592_pipe_info info; + int dma = 0; + int *counter; + int ret; + + ep->ep.desc = desc; + + BUG_ON(ep->pipenum); + + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + if (m66592->bulk >= M66592_MAX_NUM_BULK) { + if (m66592->isochronous >= M66592_MAX_NUM_ISOC) { + pr_err("bulk pipe is insufficient\n"); + return -ENODEV; + } else { + info.pipe = M66592_BASE_PIPENUM_ISOC + + m66592->isochronous; + counter = &m66592->isochronous; + } + } else { + info.pipe = M66592_BASE_PIPENUM_BULK + m66592->bulk; + counter = &m66592->bulk; + } + info.type = M66592_BULK; + dma = 1; + break; + case USB_ENDPOINT_XFER_INT: + if (m66592->interrupt >= M66592_MAX_NUM_INT) { + pr_err("interrupt pipe is insufficient\n"); + return -ENODEV; + } + info.pipe = M66592_BASE_PIPENUM_INT + m66592->interrupt; + info.type = M66592_INT; + counter = &m66592->interrupt; + break; + case USB_ENDPOINT_XFER_ISOC: + if (m66592->isochronous >= M66592_MAX_NUM_ISOC) { + pr_err("isochronous pipe is insufficient\n"); + return -ENODEV; + } + info.pipe = M66592_BASE_PIPENUM_ISOC + m66592->isochronous; + info.type = M66592_ISO; + counter = &m66592->isochronous; + break; + default: + pr_err("unexpect xfer type\n"); + return -EINVAL; + } + ep->type = info.type; + + info.epnum = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + info.maxpacket = usb_endpoint_maxp(desc); + info.interval = desc->bInterval; + if (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + info.dir_in = 1; + else + info.dir_in = 0; + + ret = pipe_buffer_setting(m66592, &info); + if (ret < 0) { + pr_err("pipe_buffer_setting fail\n"); + return ret; + } + + (*counter)++; + if ((counter == &m66592->isochronous) && info.type == M66592_BULK) + m66592->bulk++; + + m66592_ep_setting(m66592, ep, desc, info.pipe, dma); + pipe_initialize(ep); + + return 0; +} + +static int free_pipe_config(struct m66592_ep *ep) +{ + struct m66592 *m66592 = ep->m66592; + struct m66592_pipe_info info; + + info.pipe = ep->pipenum; + info.type = ep->type; + pipe_buffer_release(m66592, &info); + m66592_ep_release(ep); + + return 0; +} + +/*-------------------------------------------------------------------------*/ +static void pipe_irq_enable(struct m66592 *m66592, u16 pipenum) +{ + enable_irq_ready(m66592, pipenum); + enable_irq_nrdy(m66592, pipenum); +} + +static void pipe_irq_disable(struct m66592 *m66592, u16 pipenum) +{ + disable_irq_ready(m66592, pipenum); + disable_irq_nrdy(m66592, pipenum); +} + +/* if complete is true, gadget driver complete function is not call */ +static void control_end(struct m66592 *m66592, unsigned ccpl) +{ + m66592->ep[0].internal_ccpl = ccpl; + pipe_start(m66592, 0); + m66592_bset(m66592, M66592_CCPL, M66592_DCPCTR); +} + +static void start_ep0_write(struct m66592_ep *ep, struct m66592_request *req) +{ + struct m66592 *m66592 = ep->m66592; + + pipe_change(m66592, ep->pipenum); + m66592_mdfy(m66592, M66592_ISEL | M66592_PIPE0, + (M66592_ISEL | M66592_CURPIPE), + M66592_CFIFOSEL); + m66592_write(m66592, M66592_BCLR, ep->fifoctr); + if (req->req.length == 0) { + m66592_bset(m66592, M66592_BVAL, ep->fifoctr); + pipe_start(m66592, 0); + transfer_complete(ep, req, 0); + } else { + m66592_write(m66592, ~M66592_BEMP0, M66592_BEMPSTS); + irq_ep0_write(ep, req); + } +} + +static void start_packet_write(struct m66592_ep *ep, struct m66592_request *req) +{ + struct m66592 *m66592 = ep->m66592; + u16 tmp; + + pipe_change(m66592, ep->pipenum); + disable_irq_empty(m66592, ep->pipenum); + pipe_start(m66592, ep->pipenum); + + tmp = m66592_read(m66592, ep->fifoctr); + if (unlikely((tmp & M66592_FRDY) == 0)) + pipe_irq_enable(m66592, ep->pipenum); + else + irq_packet_write(ep, req); +} + +static void start_packet_read(struct m66592_ep *ep, struct m66592_request *req) +{ + struct m66592 *m66592 = ep->m66592; + u16 pipenum = ep->pipenum; + + if (ep->pipenum == 0) { + m66592_mdfy(m66592, M66592_PIPE0, + (M66592_ISEL | M66592_CURPIPE), + M66592_CFIFOSEL); + m66592_write(m66592, M66592_BCLR, ep->fifoctr); + pipe_start(m66592, pipenum); + pipe_irq_enable(m66592, pipenum); + } else { + if (ep->use_dma) { + m66592_bset(m66592, M66592_TRCLR, ep->fifosel); + pipe_change(m66592, pipenum); + m66592_bset(m66592, M66592_TRENB, ep->fifosel); + m66592_write(m66592, + (req->req.length + ep->ep.maxpacket - 1) + / ep->ep.maxpacket, + ep->fifotrn); + } + pipe_start(m66592, pipenum); /* trigger once */ + pipe_irq_enable(m66592, pipenum); + } +} + +static void start_packet(struct m66592_ep *ep, struct m66592_request *req) +{ + if (ep->ep.desc->bEndpointAddress & USB_DIR_IN) + start_packet_write(ep, req); + else + start_packet_read(ep, req); +} + +static void start_ep0(struct m66592_ep *ep, struct m66592_request *req) +{ + u16 ctsq; + + ctsq = m66592_read(ep->m66592, M66592_INTSTS0) & M66592_CTSQ; + + switch (ctsq) { + case M66592_CS_RDDS: + start_ep0_write(ep, req); + break; + case M66592_CS_WRDS: + start_packet_read(ep, req); + break; + + case M66592_CS_WRND: + control_end(ep->m66592, 0); + break; + default: + pr_err("start_ep0: unexpect ctsq(%x)\n", ctsq); + break; + } +} + +static void init_controller(struct m66592 *m66592) +{ + unsigned int endian; + + if (m66592->pdata->on_chip) { + if (m66592->pdata->endian) + endian = 0; /* big endian */ + else + endian = M66592_LITTLE; /* little endian */ + + m66592_bset(m66592, M66592_HSE, M66592_SYSCFG); /* High spd */ + m66592_bclr(m66592, M66592_USBE, M66592_SYSCFG); + m66592_bclr(m66592, M66592_DPRPU, M66592_SYSCFG); + m66592_bset(m66592, M66592_USBE, M66592_SYSCFG); + + /* This is a workaound for SH7722 2nd cut */ + m66592_bset(m66592, 0x8000, M66592_DVSTCTR); + m66592_bset(m66592, 0x1000, M66592_TESTMODE); + m66592_bclr(m66592, 0x8000, M66592_DVSTCTR); + + m66592_bset(m66592, M66592_INTL, M66592_INTENB1); + + m66592_write(m66592, 0, M66592_CFBCFG); + m66592_write(m66592, 0, M66592_D0FBCFG); + m66592_bset(m66592, endian, M66592_CFBCFG); + m66592_bset(m66592, endian, M66592_D0FBCFG); + } else { + unsigned int clock, vif, irq_sense; + + if (m66592->pdata->endian) + endian = M66592_BIGEND; /* big endian */ + else + endian = 0; /* little endian */ + + if (m66592->pdata->vif) + vif = M66592_LDRV; /* 3.3v */ + else + vif = 0; /* 1.5v */ + + switch (m66592->pdata->xtal) { + case M66592_PLATDATA_XTAL_12MHZ: + clock = M66592_XTAL12; + break; + case M66592_PLATDATA_XTAL_24MHZ: + clock = M66592_XTAL24; + break; + case M66592_PLATDATA_XTAL_48MHZ: + clock = M66592_XTAL48; + break; + default: + pr_warning("m66592-udc: xtal configuration error\n"); + clock = 0; + } + + switch (m66592->irq_trigger) { + case IRQF_TRIGGER_LOW: + irq_sense = M66592_INTL; + break; + case IRQF_TRIGGER_FALLING: + irq_sense = 0; + break; + default: + pr_warning("m66592-udc: irq trigger config error\n"); + irq_sense = 0; + } + + m66592_bset(m66592, + (vif & M66592_LDRV) | (endian & M66592_BIGEND), + M66592_PINCFG); + m66592_bset(m66592, M66592_HSE, M66592_SYSCFG); /* High spd */ + m66592_mdfy(m66592, clock & M66592_XTAL, M66592_XTAL, + M66592_SYSCFG); + m66592_bclr(m66592, M66592_USBE, M66592_SYSCFG); + m66592_bclr(m66592, M66592_DPRPU, M66592_SYSCFG); + m66592_bset(m66592, M66592_USBE, M66592_SYSCFG); + + m66592_bset(m66592, M66592_XCKE, M66592_SYSCFG); + + msleep(3); + + m66592_bset(m66592, M66592_RCKE | M66592_PLLC, M66592_SYSCFG); + + msleep(1); + + m66592_bset(m66592, M66592_SCKE, M66592_SYSCFG); + + m66592_bset(m66592, irq_sense & M66592_INTL, M66592_INTENB1); + m66592_write(m66592, M66592_BURST | M66592_CPU_ADR_RD_WR, + M66592_DMA0CFG); + } +} + +static void disable_controller(struct m66592 *m66592) +{ + m66592_bclr(m66592, M66592_UTST, M66592_TESTMODE); + if (!m66592->pdata->on_chip) { + m66592_bclr(m66592, M66592_SCKE, M66592_SYSCFG); + udelay(1); + m66592_bclr(m66592, M66592_PLLC, M66592_SYSCFG); + udelay(1); + m66592_bclr(m66592, M66592_RCKE, M66592_SYSCFG); + udelay(1); + m66592_bclr(m66592, M66592_XCKE, M66592_SYSCFG); + } +} + +static void m66592_start_xclock(struct m66592 *m66592) +{ + u16 tmp; + + if (!m66592->pdata->on_chip) { + tmp = m66592_read(m66592, M66592_SYSCFG); + if (!(tmp & M66592_XCKE)) + m66592_bset(m66592, M66592_XCKE, M66592_SYSCFG); + } +} + +/*-------------------------------------------------------------------------*/ +static void transfer_complete(struct m66592_ep *ep, + struct m66592_request *req, int status) +__releases(m66592->lock) +__acquires(m66592->lock) +{ + int restart = 0; + + if (unlikely(ep->pipenum == 0)) { + if (ep->internal_ccpl) { + ep->internal_ccpl = 0; + return; + } + } + + list_del_init(&req->queue); + if (ep->m66592->gadget.speed == USB_SPEED_UNKNOWN) + req->req.status = -ESHUTDOWN; + else + req->req.status = status; + + if (!list_empty(&ep->queue)) + restart = 1; + + spin_unlock(&ep->m66592->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->m66592->lock); + + if (restart) { + req = list_entry(ep->queue.next, struct m66592_request, queue); + if (ep->ep.desc) + start_packet(ep, req); + } +} + +static void irq_ep0_write(struct m66592_ep *ep, struct m66592_request *req) +{ + int i; + u16 tmp; + unsigned bufsize; + size_t size; + void *buf; + u16 pipenum = ep->pipenum; + struct m66592 *m66592 = ep->m66592; + + pipe_change(m66592, pipenum); + m66592_bset(m66592, M66592_ISEL, ep->fifosel); + + i = 0; + do { + tmp = m66592_read(m66592, ep->fifoctr); + if (i++ > 100000) { + pr_err("pipe0 is busy. maybe cpu i/o bus " + "conflict. please power off this controller."); + return; + } + ndelay(1); + } while ((tmp & M66592_FRDY) == 0); + + /* prepare parameters */ + bufsize = get_buffer_size(m66592, pipenum); + buf = req->req.buf + req->req.actual; + size = min(bufsize, req->req.length - req->req.actual); + + /* write fifo */ + if (req->req.buf) { + if (size > 0) + m66592_write_fifo(m66592, ep, buf, size); + if ((size == 0) || ((size % ep->ep.maxpacket) != 0)) + m66592_bset(m66592, M66592_BVAL, ep->fifoctr); + } + + /* update parameters */ + req->req.actual += size; + + /* check transfer finish */ + if ((!req->req.zero && (req->req.actual == req->req.length)) + || (size % ep->ep.maxpacket) + || (size == 0)) { + disable_irq_ready(m66592, pipenum); + disable_irq_empty(m66592, pipenum); + } else { + disable_irq_ready(m66592, pipenum); + enable_irq_empty(m66592, pipenum); + } + pipe_start(m66592, pipenum); +} + +static void irq_packet_write(struct m66592_ep *ep, struct m66592_request *req) +{ + u16 tmp; + unsigned bufsize; + size_t size; + void *buf; + u16 pipenum = ep->pipenum; + struct m66592 *m66592 = ep->m66592; + + pipe_change(m66592, pipenum); + tmp = m66592_read(m66592, ep->fifoctr); + if (unlikely((tmp & M66592_FRDY) == 0)) { + pipe_stop(m66592, pipenum); + pipe_irq_disable(m66592, pipenum); + pr_err("write fifo not ready. pipnum=%d\n", pipenum); + return; + } + + /* prepare parameters */ + bufsize = get_buffer_size(m66592, pipenum); + buf = req->req.buf + req->req.actual; + size = min(bufsize, req->req.length - req->req.actual); + + /* write fifo */ + if (req->req.buf) { + m66592_write_fifo(m66592, ep, buf, size); + if ((size == 0) + || ((size % ep->ep.maxpacket) != 0) + || ((bufsize != ep->ep.maxpacket) + && (bufsize > size))) + m66592_bset(m66592, M66592_BVAL, ep->fifoctr); + } + + /* update parameters */ + req->req.actual += size; + /* check transfer finish */ + if ((!req->req.zero && (req->req.actual == req->req.length)) + || (size % ep->ep.maxpacket) + || (size == 0)) { + disable_irq_ready(m66592, pipenum); + enable_irq_empty(m66592, pipenum); + } else { + disable_irq_empty(m66592, pipenum); + pipe_irq_enable(m66592, pipenum); + } +} + +static void irq_packet_read(struct m66592_ep *ep, struct m66592_request *req) +{ + u16 tmp; + int rcv_len, bufsize, req_len; + int size; + void *buf; + u16 pipenum = ep->pipenum; + struct m66592 *m66592 = ep->m66592; + int finish = 0; + + pipe_change(m66592, pipenum); + tmp = m66592_read(m66592, ep->fifoctr); + if (unlikely((tmp & M66592_FRDY) == 0)) { + req->req.status = -EPIPE; + pipe_stop(m66592, pipenum); + pipe_irq_disable(m66592, pipenum); + pr_err("read fifo not ready"); + return; + } + + /* prepare parameters */ + rcv_len = tmp & M66592_DTLN; + bufsize = get_buffer_size(m66592, pipenum); + + buf = req->req.buf + req->req.actual; + req_len = req->req.length - req->req.actual; + if (rcv_len < bufsize) + size = min(rcv_len, req_len); + else + size = min(bufsize, req_len); + + /* update parameters */ + req->req.actual += size; + + /* check transfer finish */ + if ((!req->req.zero && (req->req.actual == req->req.length)) + || (size % ep->ep.maxpacket) + || (size == 0)) { + pipe_stop(m66592, pipenum); + pipe_irq_disable(m66592, pipenum); + finish = 1; + } + + /* read fifo */ + if (req->req.buf) { + if (size == 0) + m66592_write(m66592, M66592_BCLR, ep->fifoctr); + else + m66592_read_fifo(m66592, ep->fifoaddr, buf, size); + } + + if ((ep->pipenum != 0) && finish) + transfer_complete(ep, req, 0); +} + +static void irq_pipe_ready(struct m66592 *m66592, u16 status, u16 enb) +{ + u16 check; + u16 pipenum; + struct m66592_ep *ep; + struct m66592_request *req; + + if ((status & M66592_BRDY0) && (enb & M66592_BRDY0)) { + m66592_write(m66592, ~M66592_BRDY0, M66592_BRDYSTS); + m66592_mdfy(m66592, M66592_PIPE0, M66592_CURPIPE, + M66592_CFIFOSEL); + + ep = &m66592->ep[0]; + req = list_entry(ep->queue.next, struct m66592_request, queue); + irq_packet_read(ep, req); + } else { + for (pipenum = 1; pipenum < M66592_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if ((status & check) && (enb & check)) { + m66592_write(m66592, ~check, M66592_BRDYSTS); + ep = m66592->pipenum2ep[pipenum]; + req = list_entry(ep->queue.next, + struct m66592_request, queue); + if (ep->ep.desc->bEndpointAddress & USB_DIR_IN) + irq_packet_write(ep, req); + else + irq_packet_read(ep, req); + } + } + } +} + +static void irq_pipe_empty(struct m66592 *m66592, u16 status, u16 enb) +{ + u16 tmp; + u16 check; + u16 pipenum; + struct m66592_ep *ep; + struct m66592_request *req; + + if ((status & M66592_BEMP0) && (enb & M66592_BEMP0)) { + m66592_write(m66592, ~M66592_BEMP0, M66592_BEMPSTS); + + ep = &m66592->ep[0]; + req = list_entry(ep->queue.next, struct m66592_request, queue); + irq_ep0_write(ep, req); + } else { + for (pipenum = 1; pipenum < M66592_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if ((status & check) && (enb & check)) { + m66592_write(m66592, ~check, M66592_BEMPSTS); + tmp = control_reg_get(m66592, pipenum); + if ((tmp & M66592_INBUFM) == 0) { + disable_irq_empty(m66592, pipenum); + pipe_irq_disable(m66592, pipenum); + pipe_stop(m66592, pipenum); + ep = m66592->pipenum2ep[pipenum]; + req = list_entry(ep->queue.next, + struct m66592_request, + queue); + if (!list_empty(&ep->queue)) + transfer_complete(ep, req, 0); + } + } + } + } +} + +static void get_status(struct m66592 *m66592, struct usb_ctrlrequest *ctrl) +__releases(m66592->lock) +__acquires(m66592->lock) +{ + struct m66592_ep *ep; + u16 pid; + u16 status = 0; + u16 w_index = le16_to_cpu(ctrl->wIndex); + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + status = 1 << USB_DEVICE_SELF_POWERED; + break; + case USB_RECIP_INTERFACE: + status = 0; + break; + case USB_RECIP_ENDPOINT: + ep = m66592->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK]; + pid = control_reg_get_pid(m66592, ep->pipenum); + if (pid == M66592_PID_STALL) + status = 1 << USB_ENDPOINT_HALT; + else + status = 0; + break; + default: + pipe_stall(m66592, 0); + return; /* exit */ + } + + m66592->ep0_data = cpu_to_le16(status); + m66592->ep0_req->buf = &m66592->ep0_data; + m66592->ep0_req->length = 2; + /* AV: what happens if we get called again before that gets through? */ + spin_unlock(&m66592->lock); + m66592_queue(m66592->gadget.ep0, m66592->ep0_req, GFP_KERNEL); + spin_lock(&m66592->lock); +} + +static void clear_feature(struct m66592 *m66592, struct usb_ctrlrequest *ctrl) +{ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + control_end(m66592, 1); + break; + case USB_RECIP_INTERFACE: + control_end(m66592, 1); + break; + case USB_RECIP_ENDPOINT: { + struct m66592_ep *ep; + struct m66592_request *req; + u16 w_index = le16_to_cpu(ctrl->wIndex); + + ep = m66592->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK]; + pipe_stop(m66592, ep->pipenum); + control_reg_sqclr(m66592, ep->pipenum); + + control_end(m66592, 1); + + req = list_entry(ep->queue.next, + struct m66592_request, queue); + if (ep->busy) { + ep->busy = 0; + if (list_empty(&ep->queue)) + break; + start_packet(ep, req); + } else if (!list_empty(&ep->queue)) + pipe_start(m66592, ep->pipenum); + } + break; + default: + pipe_stall(m66592, 0); + break; + } +} + +static void set_feature(struct m66592 *m66592, struct usb_ctrlrequest *ctrl) +{ + u16 tmp; + int timeout = 3000; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (le16_to_cpu(ctrl->wValue)) { + case USB_DEVICE_TEST_MODE: + control_end(m66592, 1); + /* Wait for the completion of status stage */ + do { + tmp = m66592_read(m66592, M66592_INTSTS0) & + M66592_CTSQ; + udelay(1); + } while (tmp != M66592_CS_IDST || timeout-- > 0); + + if (tmp == M66592_CS_IDST) + m66592_bset(m66592, + le16_to_cpu(ctrl->wIndex >> 8), + M66592_TESTMODE); + break; + default: + pipe_stall(m66592, 0); + break; + } + break; + case USB_RECIP_INTERFACE: + control_end(m66592, 1); + break; + case USB_RECIP_ENDPOINT: { + struct m66592_ep *ep; + u16 w_index = le16_to_cpu(ctrl->wIndex); + + ep = m66592->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK]; + pipe_stall(m66592, ep->pipenum); + + control_end(m66592, 1); + } + break; + default: + pipe_stall(m66592, 0); + break; + } +} + +/* if return value is true, call class driver's setup() */ +static int setup_packet(struct m66592 *m66592, struct usb_ctrlrequest *ctrl) +{ + u16 *p = (u16 *)ctrl; + unsigned long offset = M66592_USBREQ; + int i, ret = 0; + + /* read fifo */ + m66592_write(m66592, ~M66592_VALID, M66592_INTSTS0); + + for (i = 0; i < 4; i++) + p[i] = m66592_read(m66592, offset + i*2); + + /* check request */ + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + get_status(m66592, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + clear_feature(m66592, ctrl); + break; + case USB_REQ_SET_FEATURE: + set_feature(m66592, ctrl); + break; + default: + ret = 1; + break; + } + } else + ret = 1; + return ret; +} + +static void m66592_update_usb_speed(struct m66592 *m66592) +{ + u16 speed = get_usb_speed(m66592); + + switch (speed) { + case M66592_HSMODE: + m66592->gadget.speed = USB_SPEED_HIGH; + break; + case M66592_FSMODE: + m66592->gadget.speed = USB_SPEED_FULL; + break; + default: + m66592->gadget.speed = USB_SPEED_UNKNOWN; + pr_err("USB speed unknown\n"); + } +} + +static void irq_device_state(struct m66592 *m66592) +{ + u16 dvsq; + + dvsq = m66592_read(m66592, M66592_INTSTS0) & M66592_DVSQ; + m66592_write(m66592, ~M66592_DVST, M66592_INTSTS0); + + if (dvsq == M66592_DS_DFLT) { /* bus reset */ + m66592->driver->disconnect(&m66592->gadget); + m66592_update_usb_speed(m66592); + } + if (m66592->old_dvsq == M66592_DS_CNFG && dvsq != M66592_DS_CNFG) + m66592_update_usb_speed(m66592); + if ((dvsq == M66592_DS_CNFG || dvsq == M66592_DS_ADDS) + && m66592->gadget.speed == USB_SPEED_UNKNOWN) + m66592_update_usb_speed(m66592); + + m66592->old_dvsq = dvsq; +} + +static void irq_control_stage(struct m66592 *m66592) +__releases(m66592->lock) +__acquires(m66592->lock) +{ + struct usb_ctrlrequest ctrl; + u16 ctsq; + + ctsq = m66592_read(m66592, M66592_INTSTS0) & M66592_CTSQ; + m66592_write(m66592, ~M66592_CTRT, M66592_INTSTS0); + + switch (ctsq) { + case M66592_CS_IDST: { + struct m66592_ep *ep; + struct m66592_request *req; + ep = &m66592->ep[0]; + req = list_entry(ep->queue.next, struct m66592_request, queue); + transfer_complete(ep, req, 0); + } + break; + + case M66592_CS_RDDS: + case M66592_CS_WRDS: + case M66592_CS_WRND: + if (setup_packet(m66592, &ctrl)) { + spin_unlock(&m66592->lock); + if (m66592->driver->setup(&m66592->gadget, &ctrl) < 0) + pipe_stall(m66592, 0); + spin_lock(&m66592->lock); + } + break; + case M66592_CS_RDSS: + case M66592_CS_WRSS: + control_end(m66592, 0); + break; + default: + pr_err("ctrl_stage: unexpect ctsq(%x)\n", ctsq); + break; + } +} + +static irqreturn_t m66592_irq(int irq, void *_m66592) +{ + struct m66592 *m66592 = _m66592; + u16 intsts0; + u16 intenb0; + u16 brdysts, nrdysts, bempsts; + u16 brdyenb, nrdyenb, bempenb; + u16 savepipe; + u16 mask0; + + spin_lock(&m66592->lock); + + intsts0 = m66592_read(m66592, M66592_INTSTS0); + intenb0 = m66592_read(m66592, M66592_INTENB0); + + if (m66592->pdata->on_chip && !intsts0 && !intenb0) { + /* + * When USB clock stops, it cannot read register. Even if a + * clock stops, the interrupt occurs. So this driver turn on + * a clock by this timing and do re-reading of register. + */ + m66592_start_xclock(m66592); + intsts0 = m66592_read(m66592, M66592_INTSTS0); + intenb0 = m66592_read(m66592, M66592_INTENB0); + } + + savepipe = m66592_read(m66592, M66592_CFIFOSEL); + + mask0 = intsts0 & intenb0; + if (mask0) { + brdysts = m66592_read(m66592, M66592_BRDYSTS); + nrdysts = m66592_read(m66592, M66592_NRDYSTS); + bempsts = m66592_read(m66592, M66592_BEMPSTS); + brdyenb = m66592_read(m66592, M66592_BRDYENB); + nrdyenb = m66592_read(m66592, M66592_NRDYENB); + bempenb = m66592_read(m66592, M66592_BEMPENB); + + if (mask0 & M66592_VBINT) { + m66592_write(m66592, 0xffff & ~M66592_VBINT, + M66592_INTSTS0); + m66592_start_xclock(m66592); + + /* start vbus sampling */ + m66592->old_vbus = m66592_read(m66592, M66592_INTSTS0) + & M66592_VBSTS; + m66592->scount = M66592_MAX_SAMPLING; + + mod_timer(&m66592->timer, + jiffies + msecs_to_jiffies(50)); + } + if (intsts0 & M66592_DVSQ) + irq_device_state(m66592); + + if ((intsts0 & M66592_BRDY) && (intenb0 & M66592_BRDYE) + && (brdysts & brdyenb)) { + irq_pipe_ready(m66592, brdysts, brdyenb); + } + if ((intsts0 & M66592_BEMP) && (intenb0 & M66592_BEMPE) + && (bempsts & bempenb)) { + irq_pipe_empty(m66592, bempsts, bempenb); + } + + if (intsts0 & M66592_CTRT) + irq_control_stage(m66592); + } + + m66592_write(m66592, savepipe, M66592_CFIFOSEL); + + spin_unlock(&m66592->lock); + return IRQ_HANDLED; +} + +static void m66592_timer(unsigned long _m66592) +{ + struct m66592 *m66592 = (struct m66592 *)_m66592; + unsigned long flags; + u16 tmp; + + spin_lock_irqsave(&m66592->lock, flags); + tmp = m66592_read(m66592, M66592_SYSCFG); + if (!(tmp & M66592_RCKE)) { + m66592_bset(m66592, M66592_RCKE | M66592_PLLC, M66592_SYSCFG); + udelay(10); + m66592_bset(m66592, M66592_SCKE, M66592_SYSCFG); + } + if (m66592->scount > 0) { + tmp = m66592_read(m66592, M66592_INTSTS0) & M66592_VBSTS; + if (tmp == m66592->old_vbus) { + m66592->scount--; + if (m66592->scount == 0) { + if (tmp == M66592_VBSTS) + m66592_usb_connect(m66592); + else + m66592_usb_disconnect(m66592); + } else { + mod_timer(&m66592->timer, + jiffies + msecs_to_jiffies(50)); + } + } else { + m66592->scount = M66592_MAX_SAMPLING; + m66592->old_vbus = tmp; + mod_timer(&m66592->timer, + jiffies + msecs_to_jiffies(50)); + } + } + spin_unlock_irqrestore(&m66592->lock, flags); +} + +/*-------------------------------------------------------------------------*/ +static int m66592_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct m66592_ep *ep; + + ep = container_of(_ep, struct m66592_ep, ep); + return alloc_pipe_config(ep, desc); +} + +static int m66592_disable(struct usb_ep *_ep) +{ + struct m66592_ep *ep; + struct m66592_request *req; + unsigned long flags; + + ep = container_of(_ep, struct m66592_ep, ep); + BUG_ON(!ep); + + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct m66592_request, queue); + spin_lock_irqsave(&ep->m66592->lock, flags); + transfer_complete(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->m66592->lock, flags); + } + + pipe_irq_disable(ep->m66592, ep->pipenum); + return free_pipe_config(ep); +} + +static struct usb_request *m66592_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct m66592_request *req; + + req = kzalloc(sizeof(struct m66592_request), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void m66592_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct m66592_request *req; + + req = container_of(_req, struct m66592_request, req); + kfree(req); +} + +static int m66592_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct m66592_ep *ep; + struct m66592_request *req; + unsigned long flags; + int request = 0; + + ep = container_of(_ep, struct m66592_ep, ep); + req = container_of(_req, struct m66592_request, req); + + if (ep->m66592->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&ep->m66592->lock, flags); + + if (list_empty(&ep->queue)) + request = 1; + + list_add_tail(&req->queue, &ep->queue); + req->req.actual = 0; + req->req.status = -EINPROGRESS; + + if (ep->ep.desc == NULL) /* control */ + start_ep0(ep, req); + else { + if (request && !ep->busy) + start_packet(ep, req); + } + + spin_unlock_irqrestore(&ep->m66592->lock, flags); + + return 0; +} + +static int m66592_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct m66592_ep *ep; + struct m66592_request *req; + unsigned long flags; + + ep = container_of(_ep, struct m66592_ep, ep); + req = container_of(_req, struct m66592_request, req); + + spin_lock_irqsave(&ep->m66592->lock, flags); + if (!list_empty(&ep->queue)) + transfer_complete(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->m66592->lock, flags); + + return 0; +} + +static int m66592_set_halt(struct usb_ep *_ep, int value) +{ + struct m66592_ep *ep; + struct m66592_request *req; + unsigned long flags; + int ret = 0; + + ep = container_of(_ep, struct m66592_ep, ep); + req = list_entry(ep->queue.next, struct m66592_request, queue); + + spin_lock_irqsave(&ep->m66592->lock, flags); + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + goto out; + } + if (value) { + ep->busy = 1; + pipe_stall(ep->m66592, ep->pipenum); + } else { + ep->busy = 0; + pipe_stop(ep->m66592, ep->pipenum); + } + +out: + spin_unlock_irqrestore(&ep->m66592->lock, flags); + return ret; +} + +static void m66592_fifo_flush(struct usb_ep *_ep) +{ + struct m66592_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct m66592_ep, ep); + spin_lock_irqsave(&ep->m66592->lock, flags); + if (list_empty(&ep->queue) && !ep->busy) { + pipe_stop(ep->m66592, ep->pipenum); + m66592_bclr(ep->m66592, M66592_BCLR, ep->fifoctr); + } + spin_unlock_irqrestore(&ep->m66592->lock, flags); +} + +static struct usb_ep_ops m66592_ep_ops = { + .enable = m66592_enable, + .disable = m66592_disable, + + .alloc_request = m66592_alloc_request, + .free_request = m66592_free_request, + + .queue = m66592_queue, + .dequeue = m66592_dequeue, + + .set_halt = m66592_set_halt, + .fifo_flush = m66592_fifo_flush, +}; + +/*-------------------------------------------------------------------------*/ +static int m66592_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct m66592 *m66592 = to_m66592(g); + + /* hook up the driver */ + driver->driver.bus = NULL; + m66592->driver = driver; + + m66592_bset(m66592, M66592_VBSE | M66592_URST, M66592_INTENB0); + if (m66592_read(m66592, M66592_INTSTS0) & M66592_VBSTS) { + m66592_start_xclock(m66592); + /* start vbus sampling */ + m66592->old_vbus = m66592_read(m66592, + M66592_INTSTS0) & M66592_VBSTS; + m66592->scount = M66592_MAX_SAMPLING; + mod_timer(&m66592->timer, jiffies + msecs_to_jiffies(50)); + } + + return 0; +} + +static int m66592_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct m66592 *m66592 = to_m66592(g); + + m66592_bclr(m66592, M66592_VBSE | M66592_URST, M66592_INTENB0); + + driver->unbind(&m66592->gadget); + + init_controller(m66592); + disable_controller(m66592); + + m66592->driver = NULL; + + return 0; +} + +/*-------------------------------------------------------------------------*/ +static int m66592_get_frame(struct usb_gadget *_gadget) +{ + struct m66592 *m66592 = gadget_to_m66592(_gadget); + return m66592_read(m66592, M66592_FRMNUM) & 0x03FF; +} + +static int m66592_pullup(struct usb_gadget *gadget, int is_on) +{ + struct m66592 *m66592 = gadget_to_m66592(gadget); + unsigned long flags; + + spin_lock_irqsave(&m66592->lock, flags); + if (is_on) + m66592_bset(m66592, M66592_DPRPU, M66592_SYSCFG); + else + m66592_bclr(m66592, M66592_DPRPU, M66592_SYSCFG); + spin_unlock_irqrestore(&m66592->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops m66592_gadget_ops = { + .get_frame = m66592_get_frame, + .udc_start = m66592_udc_start, + .udc_stop = m66592_udc_stop, + .pullup = m66592_pullup, +}; + +static int __exit m66592_remove(struct platform_device *pdev) +{ + struct m66592 *m66592 = dev_get_drvdata(&pdev->dev); + + usb_del_gadget_udc(&m66592->gadget); + + del_timer_sync(&m66592->timer); + iounmap(m66592->reg); + free_irq(platform_get_irq(pdev, 0), m66592); + m66592_free_request(&m66592->ep[0].ep, m66592->ep0_req); + if (m66592->pdata->on_chip) { + clk_disable(m66592->clk); + clk_put(m66592->clk); + } + kfree(m66592); + return 0; +} + +static void nop_completion(struct usb_ep *ep, struct usb_request *r) +{ +} + +static int __init m66592_probe(struct platform_device *pdev) +{ + struct resource *res, *ires; + void __iomem *reg = NULL; + struct m66592 *m66592 = NULL; + char clk_name[8]; + int ret = 0; + int i; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + pr_err("platform_get_resource error.\n"); + goto clean_up; + } + + ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!ires) { + ret = -ENODEV; + dev_err(&pdev->dev, + "platform_get_resource IORESOURCE_IRQ error.\n"); + goto clean_up; + } + + reg = ioremap(res->start, resource_size(res)); + if (reg == NULL) { + ret = -ENOMEM; + pr_err("ioremap error.\n"); + goto clean_up; + } + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + ret = -ENODEV; + goto clean_up; + } + + /* initialize ucd */ + m66592 = kzalloc(sizeof(struct m66592), GFP_KERNEL); + if (m66592 == NULL) { + ret = -ENOMEM; + pr_err("kzalloc error\n"); + goto clean_up; + } + + m66592->pdata = pdev->dev.platform_data; + m66592->irq_trigger = ires->flags & IRQF_TRIGGER_MASK; + + spin_lock_init(&m66592->lock); + dev_set_drvdata(&pdev->dev, m66592); + + m66592->gadget.ops = &m66592_gadget_ops; + m66592->gadget.max_speed = USB_SPEED_HIGH; + m66592->gadget.name = udc_name; + + init_timer(&m66592->timer); + m66592->timer.function = m66592_timer; + m66592->timer.data = (unsigned long)m66592; + m66592->reg = reg; + + ret = request_irq(ires->start, m66592_irq, IRQF_SHARED, + udc_name, m66592); + if (ret < 0) { + pr_err("request_irq error (%d)\n", ret); + goto clean_up; + } + + if (m66592->pdata->on_chip) { + snprintf(clk_name, sizeof(clk_name), "usbf%d", pdev->id); + m66592->clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(m66592->clk)) { + dev_err(&pdev->dev, "cannot get clock \"%s\"\n", + clk_name); + ret = PTR_ERR(m66592->clk); + goto clean_up2; + } + clk_enable(m66592->clk); + } + + INIT_LIST_HEAD(&m66592->gadget.ep_list); + m66592->gadget.ep0 = &m66592->ep[0].ep; + INIT_LIST_HEAD(&m66592->gadget.ep0->ep_list); + for (i = 0; i < M66592_MAX_NUM_PIPE; i++) { + struct m66592_ep *ep = &m66592->ep[i]; + + if (i != 0) { + INIT_LIST_HEAD(&m66592->ep[i].ep.ep_list); + list_add_tail(&m66592->ep[i].ep.ep_list, + &m66592->gadget.ep_list); + } + ep->m66592 = m66592; + INIT_LIST_HEAD(&ep->queue); + ep->ep.name = m66592_ep_name[i]; + ep->ep.ops = &m66592_ep_ops; + ep->ep.maxpacket = 512; + } + m66592->ep[0].ep.maxpacket = 64; + m66592->ep[0].pipenum = 0; + m66592->ep[0].fifoaddr = M66592_CFIFO; + m66592->ep[0].fifosel = M66592_CFIFOSEL; + m66592->ep[0].fifoctr = M66592_CFIFOCTR; + m66592->ep[0].fifotrn = 0; + m66592->ep[0].pipectr = get_pipectr_addr(0); + m66592->pipenum2ep[0] = &m66592->ep[0]; + m66592->epaddr2ep[0] = &m66592->ep[0]; + + m66592->ep0_req = m66592_alloc_request(&m66592->ep[0].ep, GFP_KERNEL); + if (m66592->ep0_req == NULL) { + ret = -ENOMEM; + goto clean_up3; + } + m66592->ep0_req->complete = nop_completion; + + init_controller(m66592); + + ret = usb_add_gadget_udc(&pdev->dev, &m66592->gadget); + if (ret) + goto err_add_udc; + + dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION); + return 0; + +err_add_udc: + m66592_free_request(&m66592->ep[0].ep, m66592->ep0_req); + +clean_up3: + if (m66592->pdata->on_chip) { + clk_disable(m66592->clk); + clk_put(m66592->clk); + } +clean_up2: + free_irq(ires->start, m66592); +clean_up: + if (m66592) { + if (m66592->ep0_req) + m66592_free_request(&m66592->ep[0].ep, m66592->ep0_req); + kfree(m66592); + } + if (reg) + iounmap(reg); + + return ret; +} + +/*-------------------------------------------------------------------------*/ +static struct platform_driver m66592_driver = { + .remove = __exit_p(m66592_remove), + .driver = { + .name = (char *) udc_name, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver_probe(m66592_driver, m66592_probe); diff --git a/drivers/usb/gadget/m66592-udc.h b/drivers/usb/gadget/m66592-udc.h new file mode 100644 index 00000000..96d49d7b --- /dev/null +++ b/drivers/usb/gadget/m66592-udc.h @@ -0,0 +1,606 @@ +/* + * M66592 UDC (USB gadget) + * + * Copyright (C) 2006-2007 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#ifndef __M66592_UDC_H__ +#define __M66592_UDC_H__ + +#include +#include + +#define M66592_SYSCFG 0x00 +#define M66592_XTAL 0xC000 /* b15-14: Crystal selection */ +#define M66592_XTAL48 0x8000 /* 48MHz */ +#define M66592_XTAL24 0x4000 /* 24MHz */ +#define M66592_XTAL12 0x0000 /* 12MHz */ +#define M66592_XCKE 0x2000 /* b13: External clock enable */ +#define M66592_RCKE 0x1000 /* b12: Register clock enable */ +#define M66592_PLLC 0x0800 /* b11: PLL control */ +#define M66592_SCKE 0x0400 /* b10: USB clock enable */ +#define M66592_ATCKM 0x0100 /* b8: Automatic clock supply */ +#define M66592_HSE 0x0080 /* b7: Hi-speed enable */ +#define M66592_DCFM 0x0040 /* b6: Controller function select */ +#define M66592_DMRPD 0x0020 /* b5: D- pull down control */ +#define M66592_DPRPU 0x0010 /* b4: D+ pull up control */ +#define M66592_FSRPC 0x0004 /* b2: Full-speed receiver enable */ +#define M66592_PCUT 0x0002 /* b1: Low power sleep enable */ +#define M66592_USBE 0x0001 /* b0: USB module operation enable */ + +#define M66592_SYSSTS 0x02 +#define M66592_LNST 0x0003 /* b1-0: D+, D- line status */ +#define M66592_SE1 0x0003 /* SE1 */ +#define M66592_KSTS 0x0002 /* K State */ +#define M66592_JSTS 0x0001 /* J State */ +#define M66592_SE0 0x0000 /* SE0 */ + +#define M66592_DVSTCTR 0x04 +#define M66592_WKUP 0x0100 /* b8: Remote wakeup */ +#define M66592_RWUPE 0x0080 /* b7: Remote wakeup sense */ +#define M66592_USBRST 0x0040 /* b6: USB reset enable */ +#define M66592_RESUME 0x0020 /* b5: Resume enable */ +#define M66592_UACT 0x0010 /* b4: USB bus enable */ +#define M66592_RHST 0x0003 /* b1-0: Reset handshake status */ +#define M66592_HSMODE 0x0003 /* Hi-Speed mode */ +#define M66592_FSMODE 0x0002 /* Full-Speed mode */ +#define M66592_HSPROC 0x0001 /* HS handshake is processing */ + +#define M66592_TESTMODE 0x06 +#define M66592_UTST 0x000F /* b4-0: Test select */ +#define M66592_H_TST_PACKET 0x000C /* HOST TEST Packet */ +#define M66592_H_TST_SE0_NAK 0x000B /* HOST TEST SE0 NAK */ +#define M66592_H_TST_K 0x000A /* HOST TEST K */ +#define M66592_H_TST_J 0x0009 /* HOST TEST J */ +#define M66592_H_TST_NORMAL 0x0000 /* HOST Normal Mode */ +#define M66592_P_TST_PACKET 0x0004 /* PERI TEST Packet */ +#define M66592_P_TST_SE0_NAK 0x0003 /* PERI TEST SE0 NAK */ +#define M66592_P_TST_K 0x0002 /* PERI TEST K */ +#define M66592_P_TST_J 0x0001 /* PERI TEST J */ +#define M66592_P_TST_NORMAL 0x0000 /* PERI Normal Mode */ + +/* built-in registers */ +#define M66592_CFBCFG 0x0A +#define M66592_D0FBCFG 0x0C +#define M66592_LITTLE 0x0100 /* b8: Little endian mode */ +/* external chip case */ +#define M66592_PINCFG 0x0A +#define M66592_LDRV 0x8000 /* b15: Drive Current Adjust */ +#define M66592_BIGEND 0x0100 /* b8: Big endian mode */ + +#define M66592_DMA0CFG 0x0C +#define M66592_DMA1CFG 0x0E +#define M66592_DREQA 0x4000 /* b14: Dreq active select */ +#define M66592_BURST 0x2000 /* b13: Burst mode */ +#define M66592_DACKA 0x0400 /* b10: Dack active select */ +#define M66592_DFORM 0x0380 /* b9-7: DMA mode select */ +#define M66592_CPU_ADR_RD_WR 0x0000 /* Address + RD/WR mode (CPU bus) */ +#define M66592_CPU_DACK_RD_WR 0x0100 /* DACK + RD/WR mode (CPU bus) */ +#define M66592_CPU_DACK_ONLY 0x0180 /* DACK only mode (CPU bus) */ +#define M66592_SPLIT_DACK_ONLY 0x0200 /* DACK only mode (SPLIT bus) */ +#define M66592_SPLIT_DACK_DSTB 0x0300 /* DACK + DSTB0 mode (SPLIT bus) */ +#define M66592_DENDA 0x0040 /* b6: Dend active select */ +#define M66592_PKTM 0x0020 /* b5: Packet mode */ +#define M66592_DENDE 0x0010 /* b4: Dend enable */ +#define M66592_OBUS 0x0004 /* b2: OUTbus mode */ + +/* common case */ +#define M66592_CFIFO 0x10 +#define M66592_D0FIFO 0x14 +#define M66592_D1FIFO 0x18 + +#define M66592_CFIFOSEL 0x1E +#define M66592_D0FIFOSEL 0x24 +#define M66592_D1FIFOSEL 0x2A +#define M66592_RCNT 0x8000 /* b15: Read count mode */ +#define M66592_REW 0x4000 /* b14: Buffer rewind */ +#define M66592_DCLRM 0x2000 /* b13: DMA buffer clear mode */ +#define M66592_DREQE 0x1000 /* b12: DREQ output enable */ +#define M66592_MBW_8 0x0000 /* 8bit */ +#define M66592_MBW_16 0x0400 /* 16bit */ +#define M66592_MBW_32 0x0800 /* 32bit */ +#define M66592_TRENB 0x0200 /* b9: Transaction counter enable */ +#define M66592_TRCLR 0x0100 /* b8: Transaction counter clear */ +#define M66592_DEZPM 0x0080 /* b7: Zero-length packet mode */ +#define M66592_ISEL 0x0020 /* b5: DCP FIFO port direction select */ +#define M66592_CURPIPE 0x0007 /* b2-0: PIPE select */ + +#define M66592_CFIFOCTR 0x20 +#define M66592_D0FIFOCTR 0x26 +#define M66592_D1FIFOCTR 0x2c +#define M66592_BVAL 0x8000 /* b15: Buffer valid flag */ +#define M66592_BCLR 0x4000 /* b14: Buffer clear */ +#define M66592_FRDY 0x2000 /* b13: FIFO ready */ +#define M66592_DTLN 0x0FFF /* b11-0: FIFO received data length */ + +#define M66592_CFIFOSIE 0x22 +#define M66592_TGL 0x8000 /* b15: Buffer toggle */ +#define M66592_SCLR 0x4000 /* b14: Buffer clear */ +#define M66592_SBUSY 0x2000 /* b13: SIE_FIFO busy */ + +#define M66592_D0FIFOTRN 0x28 +#define M66592_D1FIFOTRN 0x2E +#define M66592_TRNCNT 0xFFFF /* b15-0: Transaction counter */ + +#define M66592_INTENB0 0x30 +#define M66592_VBSE 0x8000 /* b15: VBUS interrupt */ +#define M66592_RSME 0x4000 /* b14: Resume interrupt */ +#define M66592_SOFE 0x2000 /* b13: Frame update interrupt */ +#define M66592_DVSE 0x1000 /* b12: Device state transition interrupt */ +#define M66592_CTRE 0x0800 /* b11: Control transfer stage transition irq */ +#define M66592_BEMPE 0x0400 /* b10: Buffer empty interrupt */ +#define M66592_NRDYE 0x0200 /* b9: Buffer not ready interrupt */ +#define M66592_BRDYE 0x0100 /* b8: Buffer ready interrupt */ +#define M66592_URST 0x0080 /* b7: USB reset detected interrupt */ +#define M66592_SADR 0x0040 /* b6: Set address executed interrupt */ +#define M66592_SCFG 0x0020 /* b5: Set configuration executed interrupt */ +#define M66592_SUSP 0x0010 /* b4: Suspend detected interrupt */ +#define M66592_WDST 0x0008 /* b3: Control write data stage completed irq */ +#define M66592_RDST 0x0004 /* b2: Control read data stage completed irq */ +#define M66592_CMPL 0x0002 /* b1: Control transfer complete interrupt */ +#define M66592_SERR 0x0001 /* b0: Sequence error interrupt */ + +#define M66592_INTENB1 0x32 +#define M66592_BCHGE 0x4000 /* b14: USB us chenge interrupt */ +#define M66592_DTCHE 0x1000 /* b12: Detach sense interrupt */ +#define M66592_SIGNE 0x0020 /* b5: SETUP IGNORE interrupt */ +#define M66592_SACKE 0x0010 /* b4: SETUP ACK interrupt */ +#define M66592_BRDYM 0x0004 /* b2: BRDY clear timing */ +#define M66592_INTL 0x0002 /* b1: Interrupt sense select */ +#define M66592_PCSE 0x0001 /* b0: PCUT enable by CS assert */ + +#define M66592_BRDYENB 0x36 +#define M66592_BRDYSTS 0x46 +#define M66592_BRDY7 0x0080 /* b7: PIPE7 */ +#define M66592_BRDY6 0x0040 /* b6: PIPE6 */ +#define M66592_BRDY5 0x0020 /* b5: PIPE5 */ +#define M66592_BRDY4 0x0010 /* b4: PIPE4 */ +#define M66592_BRDY3 0x0008 /* b3: PIPE3 */ +#define M66592_BRDY2 0x0004 /* b2: PIPE2 */ +#define M66592_BRDY1 0x0002 /* b1: PIPE1 */ +#define M66592_BRDY0 0x0001 /* b1: PIPE0 */ + +#define M66592_NRDYENB 0x38 +#define M66592_NRDYSTS 0x48 +#define M66592_NRDY7 0x0080 /* b7: PIPE7 */ +#define M66592_NRDY6 0x0040 /* b6: PIPE6 */ +#define M66592_NRDY5 0x0020 /* b5: PIPE5 */ +#define M66592_NRDY4 0x0010 /* b4: PIPE4 */ +#define M66592_NRDY3 0x0008 /* b3: PIPE3 */ +#define M66592_NRDY2 0x0004 /* b2: PIPE2 */ +#define M66592_NRDY1 0x0002 /* b1: PIPE1 */ +#define M66592_NRDY0 0x0001 /* b1: PIPE0 */ + +#define M66592_BEMPENB 0x3A +#define M66592_BEMPSTS 0x4A +#define M66592_BEMP7 0x0080 /* b7: PIPE7 */ +#define M66592_BEMP6 0x0040 /* b6: PIPE6 */ +#define M66592_BEMP5 0x0020 /* b5: PIPE5 */ +#define M66592_BEMP4 0x0010 /* b4: PIPE4 */ +#define M66592_BEMP3 0x0008 /* b3: PIPE3 */ +#define M66592_BEMP2 0x0004 /* b2: PIPE2 */ +#define M66592_BEMP1 0x0002 /* b1: PIPE1 */ +#define M66592_BEMP0 0x0001 /* b0: PIPE0 */ + +#define M66592_SOFCFG 0x3C +#define M66592_SOFM 0x000C /* b3-2: SOF palse mode */ +#define M66592_SOF_125US 0x0008 /* SOF OUT 125us uFrame Signal */ +#define M66592_SOF_1MS 0x0004 /* SOF OUT 1ms Frame Signal */ +#define M66592_SOF_DISABLE 0x0000 /* SOF OUT Disable */ + +#define M66592_INTSTS0 0x40 +#define M66592_VBINT 0x8000 /* b15: VBUS interrupt */ +#define M66592_RESM 0x4000 /* b14: Resume interrupt */ +#define M66592_SOFR 0x2000 /* b13: SOF frame update interrupt */ +#define M66592_DVST 0x1000 /* b12: Device state transition */ +#define M66592_CTRT 0x0800 /* b11: Control stage transition */ +#define M66592_BEMP 0x0400 /* b10: Buffer empty interrupt */ +#define M66592_NRDY 0x0200 /* b9: Buffer not ready interrupt */ +#define M66592_BRDY 0x0100 /* b8: Buffer ready interrupt */ +#define M66592_VBSTS 0x0080 /* b7: VBUS input port */ +#define M66592_DVSQ 0x0070 /* b6-4: Device state */ +#define M66592_DS_SPD_CNFG 0x0070 /* Suspend Configured */ +#define M66592_DS_SPD_ADDR 0x0060 /* Suspend Address */ +#define M66592_DS_SPD_DFLT 0x0050 /* Suspend Default */ +#define M66592_DS_SPD_POWR 0x0040 /* Suspend Powered */ +#define M66592_DS_SUSP 0x0040 /* Suspend */ +#define M66592_DS_CNFG 0x0030 /* Configured */ +#define M66592_DS_ADDS 0x0020 /* Address */ +#define M66592_DS_DFLT 0x0010 /* Default */ +#define M66592_DS_POWR 0x0000 /* Powered */ +#define M66592_DVSQS 0x0030 /* b5-4: Device state */ +#define M66592_VALID 0x0008 /* b3: Setup packet detected flag */ +#define M66592_CTSQ 0x0007 /* b2-0: Control transfer stage */ +#define M66592_CS_SQER 0x0006 /* Sequence error */ +#define M66592_CS_WRND 0x0005 /* Control write nodata status */ +#define M66592_CS_WRSS 0x0004 /* Control write status stage */ +#define M66592_CS_WRDS 0x0003 /* Control write data stage */ +#define M66592_CS_RDSS 0x0002 /* Control read status stage */ +#define M66592_CS_RDDS 0x0001 /* Control read data stage */ +#define M66592_CS_IDST 0x0000 /* Idle or setup stage */ + +#define M66592_INTSTS1 0x42 +#define M66592_BCHG 0x4000 /* b14: USB bus chenge interrupt */ +#define M66592_DTCH 0x1000 /* b12: Detach sense interrupt */ +#define M66592_SIGN 0x0020 /* b5: SETUP IGNORE interrupt */ +#define M66592_SACK 0x0010 /* b4: SETUP ACK interrupt */ + +#define M66592_FRMNUM 0x4C +#define M66592_OVRN 0x8000 /* b15: Overrun error */ +#define M66592_CRCE 0x4000 /* b14: Received data error */ +#define M66592_SOFRM 0x0800 /* b11: SOF output mode */ +#define M66592_FRNM 0x07FF /* b10-0: Frame number */ + +#define M66592_UFRMNUM 0x4E +#define M66592_UFRNM 0x0007 /* b2-0: Micro frame number */ + +#define M66592_RECOVER 0x50 +#define M66592_STSRECOV 0x0700 /* Status recovery */ +#define M66592_STSR_HI 0x0400 /* FULL(0) or HI(1) Speed */ +#define M66592_STSR_DEFAULT 0x0100 /* Default state */ +#define M66592_STSR_ADDRESS 0x0200 /* Address state */ +#define M66592_STSR_CONFIG 0x0300 /* Configured state */ +#define M66592_USBADDR 0x007F /* b6-0: USB address */ + +#define M66592_USBREQ 0x54 +#define M66592_bRequest 0xFF00 /* b15-8: bRequest */ +#define M66592_GET_STATUS 0x0000 +#define M66592_CLEAR_FEATURE 0x0100 +#define M66592_ReqRESERVED 0x0200 +#define M66592_SET_FEATURE 0x0300 +#define M66592_ReqRESERVED1 0x0400 +#define M66592_SET_ADDRESS 0x0500 +#define M66592_GET_DESCRIPTOR 0x0600 +#define M66592_SET_DESCRIPTOR 0x0700 +#define M66592_GET_CONFIGURATION 0x0800 +#define M66592_SET_CONFIGURATION 0x0900 +#define M66592_GET_INTERFACE 0x0A00 +#define M66592_SET_INTERFACE 0x0B00 +#define M66592_SYNCH_FRAME 0x0C00 +#define M66592_bmRequestType 0x00FF /* b7-0: bmRequestType */ +#define M66592_bmRequestTypeDir 0x0080 /* b7 : Data direction */ +#define M66592_HOST_TO_DEVICE 0x0000 +#define M66592_DEVICE_TO_HOST 0x0080 +#define M66592_bmRequestTypeType 0x0060 /* b6-5: Type */ +#define M66592_STANDARD 0x0000 +#define M66592_CLASS 0x0020 +#define M66592_VENDOR 0x0040 +#define M66592_bmRequestTypeRecip 0x001F /* b4-0: Recipient */ +#define M66592_DEVICE 0x0000 +#define M66592_INTERFACE 0x0001 +#define M66592_ENDPOINT 0x0002 + +#define M66592_USBVAL 0x56 +#define M66592_wValue 0xFFFF /* b15-0: wValue */ +/* Standard Feature Selector */ +#define M66592_ENDPOINT_HALT 0x0000 +#define M66592_DEVICE_REMOTE_WAKEUP 0x0001 +#define M66592_TEST_MODE 0x0002 +/* Descriptor Types */ +#define M66592_DT_TYPE 0xFF00 +#define M66592_GET_DT_TYPE(v) (((v) & DT_TYPE) >> 8) +#define M66592_DT_DEVICE 0x01 +#define M66592_DT_CONFIGURATION 0x02 +#define M66592_DT_STRING 0x03 +#define M66592_DT_INTERFACE 0x04 +#define M66592_DT_ENDPOINT 0x05 +#define M66592_DT_DEVICE_QUALIFIER 0x06 +#define M66592_DT_OTHER_SPEED_CONFIGURATION 0x07 +#define M66592_DT_INTERFACE_POWER 0x08 +#define M66592_DT_INDEX 0x00FF +#define M66592_CONF_NUM 0x00FF +#define M66592_ALT_SET 0x00FF + +#define M66592_USBINDEX 0x58 +#define M66592_wIndex 0xFFFF /* b15-0: wIndex */ +#define M66592_TEST_SELECT 0xFF00 /* b15-b8: Test Mode */ +#define M66592_TEST_J 0x0100 /* Test_J */ +#define M66592_TEST_K 0x0200 /* Test_K */ +#define M66592_TEST_SE0_NAK 0x0300 /* Test_SE0_NAK */ +#define M66592_TEST_PACKET 0x0400 /* Test_Packet */ +#define M66592_TEST_FORCE_ENABLE 0x0500 /* Test_Force_Enable */ +#define M66592_TEST_STSelectors 0x0600 /* Standard test selectors */ +#define M66592_TEST_Reserved 0x4000 /* Reserved */ +#define M66592_TEST_VSTModes 0xC000 /* Vendor-specific tests */ +#define M66592_EP_DIR 0x0080 /* b7: Endpoint Direction */ +#define M66592_EP_DIR_IN 0x0080 +#define M66592_EP_DIR_OUT 0x0000 + +#define M66592_USBLENG 0x5A +#define M66592_wLength 0xFFFF /* b15-0: wLength */ + +#define M66592_DCPCFG 0x5C +#define M66592_CNTMD 0x0100 /* b8: Continuous transfer mode */ +#define M66592_DIR 0x0010 /* b4: Control transfer DIR select */ + +#define M66592_DCPMAXP 0x5E +#define M66592_DEVSEL 0xC000 /* b15-14: Device address select */ +#define M66592_DEVICE_0 0x0000 /* Device address 0 */ +#define M66592_DEVICE_1 0x4000 /* Device address 1 */ +#define M66592_DEVICE_2 0x8000 /* Device address 2 */ +#define M66592_DEVICE_3 0xC000 /* Device address 3 */ +#define M66592_MAXP 0x007F /* b6-0: Maxpacket size of ep0 */ + +#define M66592_DCPCTR 0x60 +#define M66592_BSTS 0x8000 /* b15: Buffer status */ +#define M66592_SUREQ 0x4000 /* b14: Send USB request */ +#define M66592_SQCLR 0x0100 /* b8: Sequence toggle bit clear */ +#define M66592_SQSET 0x0080 /* b7: Sequence toggle bit set */ +#define M66592_SQMON 0x0040 /* b6: Sequence toggle bit monitor */ +#define M66592_CCPL 0x0004 /* b2: control transfer complete */ +#define M66592_PID 0x0003 /* b1-0: Response PID */ +#define M66592_PID_STALL 0x0002 /* STALL */ +#define M66592_PID_BUF 0x0001 /* BUF */ +#define M66592_PID_NAK 0x0000 /* NAK */ + +#define M66592_PIPESEL 0x64 +#define M66592_PIPENM 0x0007 /* b2-0: Pipe select */ +#define M66592_PIPE0 0x0000 /* PIPE 0 */ +#define M66592_PIPE1 0x0001 /* PIPE 1 */ +#define M66592_PIPE2 0x0002 /* PIPE 2 */ +#define M66592_PIPE3 0x0003 /* PIPE 3 */ +#define M66592_PIPE4 0x0004 /* PIPE 4 */ +#define M66592_PIPE5 0x0005 /* PIPE 5 */ +#define M66592_PIPE6 0x0006 /* PIPE 6 */ +#define M66592_PIPE7 0x0007 /* PIPE 7 */ + +#define M66592_PIPECFG 0x66 +#define M66592_TYP 0xC000 /* b15-14: Transfer type */ +#define M66592_ISO 0xC000 /* Isochronous */ +#define M66592_INT 0x8000 /* Interrupt */ +#define M66592_BULK 0x4000 /* Bulk */ +#define M66592_BFRE 0x0400 /* b10: Buffer ready interrupt mode */ +#define M66592_DBLB 0x0200 /* b9: Double buffer mode select */ +#define M66592_CNTMD 0x0100 /* b8: Continuous transfer mode */ +#define M66592_SHTNAK 0x0080 /* b7: Transfer end NAK */ +#define M66592_DIR 0x0010 /* b4: Transfer direction select */ +#define M66592_DIR_H_OUT 0x0010 /* HOST OUT */ +#define M66592_DIR_P_IN 0x0010 /* PERI IN */ +#define M66592_DIR_H_IN 0x0000 /* HOST IN */ +#define M66592_DIR_P_OUT 0x0000 /* PERI OUT */ +#define M66592_EPNUM 0x000F /* b3-0: Eendpoint number select */ +#define M66592_EP1 0x0001 +#define M66592_EP2 0x0002 +#define M66592_EP3 0x0003 +#define M66592_EP4 0x0004 +#define M66592_EP5 0x0005 +#define M66592_EP6 0x0006 +#define M66592_EP7 0x0007 +#define M66592_EP8 0x0008 +#define M66592_EP9 0x0009 +#define M66592_EP10 0x000A +#define M66592_EP11 0x000B +#define M66592_EP12 0x000C +#define M66592_EP13 0x000D +#define M66592_EP14 0x000E +#define M66592_EP15 0x000F + +#define M66592_PIPEBUF 0x68 +#define M66592_BUFSIZE 0x7C00 /* b14-10: Pipe buffer size */ +#define M66592_BUF_SIZE(x) ((((x) / 64) - 1) << 10) +#define M66592_BUFNMB 0x00FF /* b7-0: Pipe buffer number */ + +#define M66592_PIPEMAXP 0x6A +#define M66592_MXPS 0x07FF /* b10-0: Maxpacket size */ + +#define M66592_PIPEPERI 0x6C +#define M66592_IFIS 0x1000 /* b12: ISO in-buffer flush mode */ +#define M66592_IITV 0x0007 /* b2-0: ISO interval */ + +#define M66592_PIPE1CTR 0x70 +#define M66592_PIPE2CTR 0x72 +#define M66592_PIPE3CTR 0x74 +#define M66592_PIPE4CTR 0x76 +#define M66592_PIPE5CTR 0x78 +#define M66592_PIPE6CTR 0x7A +#define M66592_PIPE7CTR 0x7C +#define M66592_BSTS 0x8000 /* b15: Buffer status */ +#define M66592_INBUFM 0x4000 /* b14: IN buffer monitor (PIPE 1-5) */ +#define M66592_ACLRM 0x0200 /* b9: Out buffer auto clear mode */ +#define M66592_SQCLR 0x0100 /* b8: Sequence toggle bit clear */ +#define M66592_SQSET 0x0080 /* b7: Sequence toggle bit set */ +#define M66592_SQMON 0x0040 /* b6: Sequence toggle bit monitor */ +#define M66592_PID 0x0003 /* b1-0: Response PID */ + +#define M66592_INVALID_REG 0x7E + + +#define get_pipectr_addr(pipenum) (M66592_PIPE1CTR + (pipenum - 1) * 2) + +#define M66592_MAX_SAMPLING 10 + +#define M66592_MAX_NUM_PIPE 8 +#define M66592_MAX_NUM_BULK 3 +#define M66592_MAX_NUM_ISOC 2 +#define M66592_MAX_NUM_INT 2 + +#define M66592_BASE_PIPENUM_BULK 3 +#define M66592_BASE_PIPENUM_ISOC 1 +#define M66592_BASE_PIPENUM_INT 6 + +#define M66592_BASE_BUFNUM 6 +#define M66592_MAX_BUFNUM 0x4F + +struct m66592_pipe_info { + u16 pipe; + u16 epnum; + u16 maxpacket; + u16 type; + u16 interval; + u16 dir_in; +}; + +struct m66592_request { + struct usb_request req; + struct list_head queue; +}; + +struct m66592_ep { + struct usb_ep ep; + struct m66592 *m66592; + + struct list_head queue; + unsigned busy:1; + unsigned internal_ccpl:1; /* use only control */ + + /* this member can able to after m66592_enable */ + unsigned use_dma:1; + u16 pipenum; + u16 type; + + /* register address */ + unsigned long fifoaddr; + unsigned long fifosel; + unsigned long fifoctr; + unsigned long fifotrn; + unsigned long pipectr; +}; + +struct m66592 { + spinlock_t lock; + void __iomem *reg; + struct clk *clk; + struct m66592_platdata *pdata; + unsigned long irq_trigger; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct m66592_ep ep[M66592_MAX_NUM_PIPE]; + struct m66592_ep *pipenum2ep[M66592_MAX_NUM_PIPE]; + struct m66592_ep *epaddr2ep[16]; + + struct usb_request *ep0_req; /* for internal request */ + __le16 ep0_data; /* for internal request */ + u16 old_vbus; + + struct timer_list timer; + + int scount; + + int old_dvsq; + + /* pipe config */ + int bulk; + int interrupt; + int isochronous; + int num_dma; +}; +#define to_m66592(g) (container_of((g), struct m66592, gadget)) + +#define gadget_to_m66592(_gadget) container_of(_gadget, struct m66592, gadget) +#define m66592_to_gadget(m66592) (&m66592->gadget) + +#define is_bulk_pipe(pipenum) \ + ((pipenum >= M66592_BASE_PIPENUM_BULK) && \ + (pipenum < (M66592_BASE_PIPENUM_BULK + M66592_MAX_NUM_BULK))) +#define is_interrupt_pipe(pipenum) \ + ((pipenum >= M66592_BASE_PIPENUM_INT) && \ + (pipenum < (M66592_BASE_PIPENUM_INT + M66592_MAX_NUM_INT))) +#define is_isoc_pipe(pipenum) \ + ((pipenum >= M66592_BASE_PIPENUM_ISOC) && \ + (pipenum < (M66592_BASE_PIPENUM_ISOC + M66592_MAX_NUM_ISOC))) + +#define enable_irq_ready(m66592, pipenum) \ + enable_pipe_irq(m66592, pipenum, M66592_BRDYENB) +#define disable_irq_ready(m66592, pipenum) \ + disable_pipe_irq(m66592, pipenum, M66592_BRDYENB) +#define enable_irq_empty(m66592, pipenum) \ + enable_pipe_irq(m66592, pipenum, M66592_BEMPENB) +#define disable_irq_empty(m66592, pipenum) \ + disable_pipe_irq(m66592, pipenum, M66592_BEMPENB) +#define enable_irq_nrdy(m66592, pipenum) \ + enable_pipe_irq(m66592, pipenum, M66592_NRDYENB) +#define disable_irq_nrdy(m66592, pipenum) \ + disable_pipe_irq(m66592, pipenum, M66592_NRDYENB) + +/*-------------------------------------------------------------------------*/ +static inline u16 m66592_read(struct m66592 *m66592, unsigned long offset) +{ + return ioread16(m66592->reg + offset); +} + +static inline void m66592_read_fifo(struct m66592 *m66592, + unsigned long offset, + void *buf, unsigned long len) +{ + void __iomem *fifoaddr = m66592->reg + offset; + + if (m66592->pdata->on_chip) { + len = (len + 3) / 4; + ioread32_rep(fifoaddr, buf, len); + } else { + len = (len + 1) / 2; + ioread16_rep(fifoaddr, buf, len); + } +} + +static inline void m66592_write(struct m66592 *m66592, u16 val, + unsigned long offset) +{ + iowrite16(val, m66592->reg + offset); +} + +static inline void m66592_mdfy(struct m66592 *m66592, u16 val, u16 pat, + unsigned long offset) +{ + u16 tmp; + tmp = m66592_read(m66592, offset); + tmp = tmp & (~pat); + tmp = tmp | val; + m66592_write(m66592, tmp, offset); +} + +#define m66592_bclr(m66592, val, offset) \ + m66592_mdfy(m66592, 0, val, offset) +#define m66592_bset(m66592, val, offset) \ + m66592_mdfy(m66592, val, 0, offset) + +static inline void m66592_write_fifo(struct m66592 *m66592, + struct m66592_ep *ep, + void *buf, unsigned long len) +{ + void __iomem *fifoaddr = m66592->reg + ep->fifoaddr; + + if (m66592->pdata->on_chip) { + unsigned long count; + unsigned char *pb; + int i; + + count = len / 4; + iowrite32_rep(fifoaddr, buf, count); + + if (len & 0x00000003) { + pb = buf + count * 4; + for (i = 0; i < (len & 0x00000003); i++) { + if (m66592_read(m66592, M66592_CFBCFG)) /* le */ + iowrite8(pb[i], fifoaddr + (3 - i)); + else + iowrite8(pb[i], fifoaddr + i); + } + } + } else { + unsigned long odd = len & 0x0001; + + len = len / 2; + iowrite16_rep(fifoaddr, buf, len); + if (odd) { + unsigned char *p = buf + len*2; + if (m66592->pdata->wr0_shorted_to_wr1) + m66592_bclr(m66592, M66592_MBW_16, ep->fifosel); + iowrite8(*p, fifoaddr); + if (m66592->pdata->wr0_shorted_to_wr1) + m66592_bset(m66592, M66592_MBW_16, ep->fifosel); + } + } +} + +#endif /* ifndef __M66592_UDC_H__ */ + + diff --git a/drivers/usb/gadget/mass_storage.c b/drivers/usb/gadget/mass_storage.c new file mode 100644 index 00000000..080e5777 --- /dev/null +++ b/drivers/usb/gadget/mass_storage.c @@ -0,0 +1,199 @@ +/* + * mass_storage.c -- Mass Storage USB Gadget + * + * Copyright (C) 2003-2008 Alan Stern + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +/* + * The Mass Storage Gadget acts as a USB Mass Storage device, + * appearing to the host as a disk drive or as a CD-ROM drive. In + * addition to providing an example of a genuinely useful gadget + * driver for a USB device, it also illustrates a technique of + * double-buffering for increased throughput. Last but not least, it + * gives an easy way to probe the behavior of the Mass Storage drivers + * in a USB host. + * + * Since this file serves only administrative purposes and all the + * business logic is implemented in f_mass_storage.* file. Read + * comments in this file for more detailed description. + */ + + +#include +#include +#include + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_DESC "Mass Storage Gadget" +#define DRIVER_VERSION "2009/09/11" + +/*-------------------------------------------------------------------------*/ + +/* + * kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "f_mass_storage.c" + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct usb_device_descriptor msg_device_desc = { + .bLength = sizeof msg_device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + /* Vendor and product id can be overridden by module parameters. */ + .idVendor = cpu_to_le16(FSG_VENDOR_ID), + .idProduct = cpu_to_le16(FSG_PRODUCT_ID), + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* + * REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +/****************************** Configurations ******************************/ + +static struct fsg_module_parameters mod_data = { + .stall = 1 +}; +FSG_MODULE_PARAMETERS(/* no prefix */, mod_data); + +static unsigned long msg_registered; +static void msg_cleanup(void); + +static int msg_thread_exits(struct fsg_common *common) +{ + msg_cleanup(); + return 0; +} + +static int __init msg_do_config(struct usb_configuration *c) +{ + static const struct fsg_operations ops = { + .thread_exits = msg_thread_exits, + }; + static struct fsg_common common; + + struct fsg_common *retp; + struct fsg_config config; + int ret; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + fsg_config_from_params(&config, &mod_data); + config.ops = &ops; + + retp = fsg_common_init(&common, c->cdev, &config); + if (IS_ERR(retp)) + return PTR_ERR(retp); + + ret = fsg_bind_config(c->cdev, c, &common); + fsg_common_put(&common); + return ret; +} + +static struct usb_configuration msg_config_driver = { + .label = "Linux File-Backed Storage", + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + + +/****************************** Gadget Bind ******************************/ + +static int __init msg_bind(struct usb_composite_dev *cdev) +{ + int status; + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + return status; + msg_device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + status = usb_add_config(cdev, &msg_config_driver, msg_do_config); + if (status < 0) + return status; + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&cdev->gadget->dev, + DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + set_bit(0, &msg_registered); + return 0; +} + + +/****************************** Some noise ******************************/ + +static __refdata struct usb_composite_driver msg_driver = { + .name = "g_mass_storage", + .dev = &msg_device_desc, + .max_speed = USB_SPEED_SUPER, + .needs_serial = 1, + .strings = dev_strings, + .bind = msg_bind, +}; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Michal Nazarewicz"); +MODULE_LICENSE("GPL"); + +static int __init msg_init(void) +{ + return usb_composite_probe(&msg_driver); +} +module_init(msg_init); + +static void msg_cleanup(void) +{ + if (test_and_clear_bit(0, &msg_registered)) + usb_composite_unregister(&msg_driver); +} +module_exit(msg_cleanup); diff --git a/drivers/usb/gadget/multi.c b/drivers/usb/gadget/multi.c new file mode 100644 index 00000000..4a45e80c --- /dev/null +++ b/drivers/usb/gadget/multi.c @@ -0,0 +1,374 @@ +/* + * multi.c -- Multifunction Composite driver + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (mina86@mina86.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +#include +#include + +#include "u_serial.h" +#if defined USB_ETH_RNDIS +# undef USB_ETH_RNDIS +#endif +#ifdef CONFIG_USB_G_MULTI_RNDIS +# define USB_ETH_RNDIS y +#endif + + +#define DRIVER_DESC "Multifunction Composite Gadget" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Michal Nazarewicz"); +MODULE_LICENSE("GPL"); + + +/***************************** All the files... *****************************/ + +/* + * kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "f_mass_storage.c" + +#include "f_ecm.c" +#include "f_subset.c" +#ifdef USB_ETH_RNDIS +# include "f_rndis.c" +# include "rndis.c" +#endif +#include "u_ether.c" + +USB_GADGET_COMPOSITE_OPTIONS(); + +/***************************** Device Descriptor ****************************/ + +#define MULTI_VENDOR_NUM 0x1d6b /* Linux Foundation */ +#define MULTI_PRODUCT_NUM 0x0104 /* Multifunction Composite Gadget */ + + +enum { + __MULTI_NO_CONFIG, +#ifdef CONFIG_USB_G_MULTI_RNDIS + MULTI_RNDIS_CONFIG_NUM, +#endif +#ifdef CONFIG_USB_G_MULTI_CDC + MULTI_CDC_CONFIG_NUM, +#endif +}; + + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + + .bDeviceClass = USB_CLASS_MISC /* 0xEF */, + .bDeviceSubClass = 2, + .bDeviceProtocol = 1, + + /* Vendor and product id can be overridden by module parameters. */ + .idVendor = cpu_to_le16(MULTI_VENDOR_NUM), + .idProduct = cpu_to_le16(MULTI_PRODUCT_NUM), +}; + + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &(struct usb_otg_descriptor){ + .bLength = sizeof(struct usb_otg_descriptor), + .bDescriptorType = USB_DT_OTG, + + /* + * REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, + }, + NULL, +}; + + +enum { + MULTI_STRING_RNDIS_CONFIG_IDX = USB_GADGET_FIRST_AVAIL_IDX, + MULTI_STRING_CDC_CONFIG_IDX, +}; + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", + [MULTI_STRING_RNDIS_CONFIG_IDX].s = "Multifunction with RNDIS", + [MULTI_STRING_CDC_CONFIG_IDX].s = "Multifunction with CDC ECM", + { } /* end of list */ +}; + +static struct usb_gadget_strings *dev_strings[] = { + &(struct usb_gadget_strings){ + .language = 0x0409, /* en-us */ + .strings = strings_dev, + }, + NULL, +}; + + + + +/****************************** Configurations ******************************/ + +static struct fsg_module_parameters fsg_mod_data = { .stall = 1 }; +FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +static struct fsg_common fsg_common; + +static u8 hostaddr[ETH_ALEN]; + +static struct usb_function_instance *fi_acm; +static struct eth_dev *the_dev; + +/********** RNDIS **********/ + +#ifdef USB_ETH_RNDIS +static struct usb_function *f_acm_rndis; + +static __init int rndis_do_config(struct usb_configuration *c) +{ + int ret; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + ret = rndis_bind_config(c, hostaddr, the_dev); + if (ret < 0) + return ret; + + f_acm_rndis = usb_get_function(fi_acm); + if (IS_ERR(f_acm_rndis)) { + ret = PTR_ERR(f_acm_rndis); + goto err_func_acm; + } + + ret = usb_add_function(c, f_acm_rndis); + if (ret) + goto err_conf; + + ret = fsg_bind_config(c->cdev, c, &fsg_common); + if (ret < 0) + goto err_fsg; + + return 0; +err_fsg: + usb_remove_function(c, f_acm_rndis); +err_conf: + usb_put_function(f_acm_rndis); +err_func_acm: + return ret; +} + +static int rndis_config_register(struct usb_composite_dev *cdev) +{ + static struct usb_configuration config = { + .bConfigurationValue = MULTI_RNDIS_CONFIG_NUM, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + }; + + config.label = strings_dev[MULTI_STRING_RNDIS_CONFIG_IDX].s; + config.iConfiguration = strings_dev[MULTI_STRING_RNDIS_CONFIG_IDX].id; + + return usb_add_config(cdev, &config, rndis_do_config); +} + +#else + +static int rndis_config_register(struct usb_composite_dev *cdev) +{ + return 0; +} + +#endif + + +/********** CDC ECM **********/ + +#ifdef CONFIG_USB_G_MULTI_CDC +static struct usb_function *f_acm_multi; + +static __init int cdc_do_config(struct usb_configuration *c) +{ + int ret; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + ret = ecm_bind_config(c, hostaddr, the_dev); + if (ret < 0) + return ret; + + /* implicit port_num is zero */ + f_acm_multi = usb_get_function(fi_acm); + if (IS_ERR(f_acm_multi)) + goto err_func_acm; + + ret = usb_add_function(c, f_acm_multi); + if (ret) + goto err_conf; + + ret = fsg_bind_config(c->cdev, c, &fsg_common); + if (ret < 0) + goto err_fsg; + + return 0; +err_fsg: + usb_remove_function(c, f_acm_multi); +err_conf: + usb_put_function(f_acm_multi); +err_func_acm: + return ret; +} + +static int cdc_config_register(struct usb_composite_dev *cdev) +{ + static struct usb_configuration config = { + .bConfigurationValue = MULTI_CDC_CONFIG_NUM, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + }; + + config.label = strings_dev[MULTI_STRING_CDC_CONFIG_IDX].s; + config.iConfiguration = strings_dev[MULTI_STRING_CDC_CONFIG_IDX].id; + + return usb_add_config(cdev, &config, cdc_do_config); +} + +#else + +static int cdc_config_register(struct usb_composite_dev *cdev) +{ + return 0; +} + +#endif + + + +/****************************** Gadget Bind ******************************/ + +static int __ref multi_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int status; + + if (!can_support_ecm(cdev->gadget)) { + dev_err(&gadget->dev, "controller '%s' not usable\n", + gadget->name); + return -EINVAL; + } + + /* set up network link layer */ + the_dev = gether_setup(cdev->gadget, hostaddr); + if (IS_ERR(the_dev)) + return PTR_ERR(the_dev); + + /* set up serial link layer */ + fi_acm = usb_get_function_instance("acm"); + if (IS_ERR(fi_acm)) { + status = PTR_ERR(fi_acm); + goto fail0; + } + + /* set up mass storage function */ + { + void *retp; + retp = fsg_common_from_params(&fsg_common, cdev, &fsg_mod_data); + if (IS_ERR(retp)) { + status = PTR_ERR(retp); + goto fail1; + } + } + + /* allocate string IDs */ + status = usb_string_ids_tab(cdev, strings_dev); + if (unlikely(status < 0)) + goto fail2; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + /* register configurations */ + status = rndis_config_register(cdev); + if (unlikely(status < 0)) + goto fail2; + + status = cdc_config_register(cdev); + if (unlikely(status < 0)) + goto fail2; + usb_composite_overwrite_options(cdev, &coverwrite); + + /* we're done */ + dev_info(&gadget->dev, DRIVER_DESC "\n"); + fsg_common_put(&fsg_common); + return 0; + + + /* error recovery */ +fail2: + fsg_common_put(&fsg_common); +fail1: + usb_put_function_instance(fi_acm); +fail0: + gether_cleanup(the_dev); + return status; +} + +static int __exit multi_unbind(struct usb_composite_dev *cdev) +{ +#ifdef CONFIG_USB_G_MULTI_CDC + usb_put_function(f_acm_multi); +#endif +#ifdef USB_ETH_RNDIS + usb_put_function(f_acm_rndis); +#endif + usb_put_function_instance(fi_acm); + gether_cleanup(the_dev); + return 0; +} + + +/****************************** Some noise ******************************/ + + +static __refdata struct usb_composite_driver multi_driver = { + .name = "g_multi", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = multi_bind, + .unbind = __exit_p(multi_unbind), + .needs_serial = 1, +}; + + +static int __init multi_init(void) +{ + return usb_composite_probe(&multi_driver); +} +module_init(multi_init); + +static void __exit multi_exit(void) +{ + usb_composite_unregister(&multi_driver); +} +module_exit(multi_exit); diff --git a/drivers/usb/gadget/mv_u3d.h b/drivers/usb/gadget/mv_u3d.h new file mode 100644 index 00000000..e32a787a --- /dev/null +++ b/drivers/usb/gadget/mv_u3d.h @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#ifndef __MV_U3D_H +#define __MV_U3D_H + +#define MV_U3D_EP_CONTEXT_ALIGNMENT 32 +#define MV_U3D_TRB_ALIGNMENT 16 +#define MV_U3D_DMA_BOUNDARY 4096 +#define MV_U3D_EP0_MAX_PKT_SIZE 512 + +/* ep0 transfer state */ +#define MV_U3D_WAIT_FOR_SETUP 0 +#define MV_U3D_DATA_STATE_XMIT 1 +#define MV_U3D_DATA_STATE_NEED_ZLP 2 +#define MV_U3D_WAIT_FOR_OUT_STATUS 3 +#define MV_U3D_DATA_STATE_RECV 4 +#define MV_U3D_STATUS_STAGE 5 + +#define MV_U3D_EP_MAX_LENGTH_TRANSFER 0x10000 + +/* USB3 Interrupt Status */ +#define MV_U3D_USBINT_SETUP 0x00000001 +#define MV_U3D_USBINT_RX_COMPLETE 0x00000002 +#define MV_U3D_USBINT_TX_COMPLETE 0x00000004 +#define MV_U3D_USBINT_UNDER_RUN 0x00000008 +#define MV_U3D_USBINT_RXDESC_ERR 0x00000010 +#define MV_U3D_USBINT_TXDESC_ERR 0x00000020 +#define MV_U3D_USBINT_RX_TRB_COMPLETE 0x00000040 +#define MV_U3D_USBINT_TX_TRB_COMPLETE 0x00000080 +#define MV_U3D_USBINT_VBUS_VALID 0x00010000 +#define MV_U3D_USBINT_STORAGE_CMD_FULL 0x00020000 +#define MV_U3D_USBINT_LINK_CHG 0x01000000 + +/* USB3 Interrupt Enable */ +#define MV_U3D_INTR_ENABLE_SETUP 0x00000001 +#define MV_U3D_INTR_ENABLE_RX_COMPLETE 0x00000002 +#define MV_U3D_INTR_ENABLE_TX_COMPLETE 0x00000004 +#define MV_U3D_INTR_ENABLE_UNDER_RUN 0x00000008 +#define MV_U3D_INTR_ENABLE_RXDESC_ERR 0x00000010 +#define MV_U3D_INTR_ENABLE_TXDESC_ERR 0x00000020 +#define MV_U3D_INTR_ENABLE_RX_TRB_COMPLETE 0x00000040 +#define MV_U3D_INTR_ENABLE_TX_TRB_COMPLETE 0x00000080 +#define MV_U3D_INTR_ENABLE_RX_BUFFER_ERR 0x00000100 +#define MV_U3D_INTR_ENABLE_VBUS_VALID 0x00010000 +#define MV_U3D_INTR_ENABLE_STORAGE_CMD_FULL 0x00020000 +#define MV_U3D_INTR_ENABLE_LINK_CHG 0x01000000 +#define MV_U3D_INTR_ENABLE_PRIME_STATUS 0x02000000 + +/* USB3 Link Change */ +#define MV_U3D_LINK_CHANGE_LINK_UP 0x00000001 +#define MV_U3D_LINK_CHANGE_SUSPEND 0x00000002 +#define MV_U3D_LINK_CHANGE_RESUME 0x00000004 +#define MV_U3D_LINK_CHANGE_WRESET 0x00000008 +#define MV_U3D_LINK_CHANGE_HRESET 0x00000010 +#define MV_U3D_LINK_CHANGE_VBUS_INVALID 0x00000020 +#define MV_U3D_LINK_CHANGE_INACT 0x00000040 +#define MV_U3D_LINK_CHANGE_DISABLE_AFTER_U0 0x00000080 +#define MV_U3D_LINK_CHANGE_U1 0x00000100 +#define MV_U3D_LINK_CHANGE_U2 0x00000200 +#define MV_U3D_LINK_CHANGE_U3 0x00000400 + +/* bridge setting */ +#define MV_U3D_BRIDGE_SETTING_VBUS_VALID (1 << 16) + +/* Command Register Bit Masks */ +#define MV_U3D_CMD_RUN_STOP 0x00000001 +#define MV_U3D_CMD_CTRL_RESET 0x00000002 + +/* ep control register */ +#define MV_U3D_EPXCR_EP_TYPE_CONTROL 0 +#define MV_U3D_EPXCR_EP_TYPE_ISOC 1 +#define MV_U3D_EPXCR_EP_TYPE_BULK 2 +#define MV_U3D_EPXCR_EP_TYPE_INT 3 +#define MV_U3D_EPXCR_EP_ENABLE_SHIFT 4 +#define MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT 12 +#define MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT 16 +#define MV_U3D_USB_BULK_BURST_OUT 6 +#define MV_U3D_USB_BULK_BURST_IN 14 + +#define MV_U3D_EPXCR_EP_FLUSH (1 << 7) +#define MV_U3D_EPXCR_EP_HALT (1 << 1) +#define MV_U3D_EPXCR_EP_INIT (1) + +/* TX/RX Status Register */ +#define MV_U3D_XFERSTATUS_COMPLETE_SHIFT 24 +#define MV_U3D_COMPLETE_INVALID 0 +#define MV_U3D_COMPLETE_SUCCESS 1 +#define MV_U3D_COMPLETE_BUFF_ERR 2 +#define MV_U3D_COMPLETE_SHORT_PACKET 3 +#define MV_U3D_COMPLETE_TRB_ERR 5 +#define MV_U3D_XFERSTATUS_TRB_LENGTH_MASK (0xFFFFFF) + +#define MV_U3D_USB_LINK_BYPASS_VBUS 0x8 + +#define MV_U3D_LTSSM_PHY_INIT_DONE 0x80000000 +#define MV_U3D_LTSSM_NEVER_GO_COMPLIANCE 0x40000000 + +#define MV_U3D_USB3_OP_REGS_OFFSET 0x100 +#define MV_U3D_USB3_PHY_OFFSET 0xB800 + +#define DCS_ENABLE 0x1 + +/* timeout */ +#define MV_U3D_RESET_TIMEOUT 10000 +#define MV_U3D_FLUSH_TIMEOUT 100000 +#define MV_U3D_OWN_TIMEOUT 10000 +#define LOOPS_USEC_SHIFT 4 +#define LOOPS_USEC (1 << LOOPS_USEC_SHIFT) +#define LOOPS(timeout) ((timeout) >> LOOPS_USEC_SHIFT) + +/* ep direction */ +#define MV_U3D_EP_DIR_IN 1 +#define MV_U3D_EP_DIR_OUT 0 +#define mv_u3d_ep_dir(ep) (((ep)->ep_num == 0) ? \ + ((ep)->u3d->ep0_dir) : ((ep)->direction)) + +/* usb capability registers */ +struct mv_u3d_cap_regs { + u32 rsvd[5]; + u32 dboff; /* doorbell register offset */ + u32 rtsoff; /* runtime register offset */ + u32 vuoff; /* vendor unique register offset */ +}; + +/* operation registers */ +struct mv_u3d_op_regs { + u32 usbcmd; /* Command register */ + u32 rsvd1[11]; + u32 dcbaapl; /* Device Context Base Address low register */ + u32 dcbaaph; /* Device Context Base Address high register */ + u32 rsvd2[243]; + u32 portsc; /* port status and control register*/ + u32 portlinkinfo; /* port link info register*/ + u32 rsvd3[9917]; + u32 doorbell; /* doorbell register */ +}; + +/* control enpoint enable registers */ +struct epxcr { + u32 epxoutcr0; /* ep out control 0 register */ + u32 epxoutcr1; /* ep out control 1 register */ + u32 epxincr0; /* ep in control 0 register */ + u32 epxincr1; /* ep in control 1 register */ +}; + +/* transfer status registers */ +struct xferstatus { + u32 curdeqlo; /* current TRB pointer low */ + u32 curdeqhi; /* current TRB pointer high */ + u32 statuslo; /* transfer status low */ + u32 statushi; /* transfer status high */ +}; + +/* vendor unique control registers */ +struct mv_u3d_vuc_regs { + u32 ctrlepenable; /* control endpoint enable register */ + u32 setuplock; /* setup lock register */ + u32 endcomplete; /* endpoint transfer complete register */ + u32 intrcause; /* interrupt cause register */ + u32 intrenable; /* interrupt enable register */ + u32 trbcomplete; /* TRB complete register */ + u32 linkchange; /* link change register */ + u32 rsvd1[5]; + u32 trbunderrun; /* TRB underrun register */ + u32 rsvd2[43]; + u32 bridgesetting; /* bridge setting register */ + u32 rsvd3[7]; + struct xferstatus txst[16]; /* TX status register */ + struct xferstatus rxst[16]; /* RX status register */ + u32 ltssm; /* LTSSM control register */ + u32 pipe; /* PIPE control register */ + u32 linkcr0; /* link control 0 register */ + u32 linkcr1; /* link control 1 register */ + u32 rsvd6[60]; + u32 mib0; /* MIB0 counter register */ + u32 usblink; /* usb link control register */ + u32 ltssmstate; /* LTSSM state register */ + u32 linkerrorcause; /* link error cause register */ + u32 rsvd7[60]; + u32 devaddrtiebrkr; /* device address and tie breaker */ + u32 itpinfo0; /* ITP info 0 register */ + u32 itpinfo1; /* ITP info 1 register */ + u32 rsvd8[61]; + struct epxcr epcr[16]; /* ep control register */ + u32 rsvd9[64]; + u32 phyaddr; /* PHY address register */ + u32 phydata; /* PHY data register */ +}; + +/* Endpoint context structure */ +struct mv_u3d_ep_context { + u32 rsvd0; + u32 rsvd1; + u32 trb_addr_lo; /* TRB address low 32 bit */ + u32 trb_addr_hi; /* TRB address high 32 bit */ + u32 rsvd2; + u32 rsvd3; + struct usb_ctrlrequest setup_buffer; /* setup data buffer */ +}; + +/* TRB control data structure */ +struct mv_u3d_trb_ctrl { + u32 own:1; /* owner of TRB */ + u32 rsvd1:3; + u32 chain:1; /* associate this TRB with the + next TRB on the Ring */ + u32 ioc:1; /* interrupt on complete */ + u32 rsvd2:4; + u32 type:6; /* TRB type */ +#define TYPE_NORMAL 1 +#define TYPE_DATA 3 +#define TYPE_LINK 6 + u32 dir:1; /* Working at data stage of control endpoint + operation. 0 is OUT and 1 is IN. */ + u32 rsvd3:15; +}; + +/* TRB data structure + * For multiple TRB, all the TRBs' physical address should be continuous. + */ +struct mv_u3d_trb_hw { + u32 buf_addr_lo; /* data buffer address low 32 bit */ + u32 buf_addr_hi; /* data buffer address high 32 bit */ + u32 trb_len; /* transfer length */ + struct mv_u3d_trb_ctrl ctrl; /* TRB control data */ +}; + +/* TRB structure */ +struct mv_u3d_trb { + struct mv_u3d_trb_hw *trb_hw; /* point to the trb_hw structure */ + dma_addr_t trb_dma; /* dma address for this trb_hw */ + struct list_head trb_list; /* trb list */ +}; + +/* device data structure */ +struct mv_u3d { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + spinlock_t lock; /* device lock */ + struct completion *done; + struct device *dev; + int irq; + + /* usb controller registers */ + struct mv_u3d_cap_regs __iomem *cap_regs; + struct mv_u3d_op_regs __iomem *op_regs; + struct mv_u3d_vuc_regs __iomem *vuc_regs; + void __iomem *phy_regs; + + unsigned int max_eps; + struct mv_u3d_ep_context *ep_context; + size_t ep_context_size; + dma_addr_t ep_context_dma; + + struct dma_pool *trb_pool; /* for TRB data structure */ + struct mv_u3d_ep *eps; + + struct mv_u3d_req *status_req; /* ep0 status request */ + struct usb_ctrlrequest local_setup_buff; /* store setup data*/ + + unsigned int resume_state; /* USB state to resume */ + unsigned int usb_state; /* USB current state */ + unsigned int ep0_state; /* Endpoint zero state */ + unsigned int ep0_dir; + + unsigned int dev_addr; /* device address */ + + unsigned int errors; + + unsigned softconnect:1; + unsigned vbus_active:1; /* vbus is active or not */ + unsigned remote_wakeup:1; /* support remote wakeup */ + unsigned clock_gating:1; /* clock gating or not */ + unsigned active:1; /* udc is active or not */ + unsigned vbus_valid_detect:1; /* udc vbus detection */ + + struct mv_usb_addon_irq *vbus; + unsigned int power; + + struct clk *clk; +}; + +/* endpoint data structure */ +struct mv_u3d_ep { + struct usb_ep ep; + struct mv_u3d *u3d; + struct list_head queue; /* ep request queued hardware */ + struct list_head req_list; /* list of ep request */ + struct mv_u3d_ep_context *ep_context; /* ep context */ + u32 direction; + char name[14]; + u32 processing; /* there is ep request + queued on haredware */ + spinlock_t req_lock; /* ep lock */ + unsigned wedge:1; + unsigned enabled:1; + unsigned ep_type:2; + unsigned ep_num:8; +}; + +/* request data structure */ +struct mv_u3d_req { + struct usb_request req; + struct mv_u3d_ep *ep; + struct list_head queue; /* ep requst queued on hardware */ + struct list_head list; /* ep request list */ + struct list_head trb_list; /* trb list of a request */ + + struct mv_u3d_trb *trb_head; /* point to first trb of a request */ + unsigned trb_count; /* TRB number in the chain */ + unsigned chain; /* TRB chain or not */ +}; + +#endif diff --git a/drivers/usb/gadget/mv_u3d_core.c b/drivers/usb/gadget/mv_u3d_core.c new file mode 100644 index 00000000..58288e9c --- /dev/null +++ b/drivers/usb/gadget/mv_u3d_core.c @@ -0,0 +1,2079 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mv_u3d.h" + +#define DRIVER_DESC "Marvell PXA USB3.0 Device Controller driver" + +static const char driver_name[] = "mv_u3d"; +static const char driver_desc[] = DRIVER_DESC; + +static void mv_u3d_nuke(struct mv_u3d_ep *ep, int status); +static void mv_u3d_stop_activity(struct mv_u3d *u3d, + struct usb_gadget_driver *driver); + +/* for endpoint 0 operations */ +static const struct usb_endpoint_descriptor mv_u3d_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = MV_U3D_EP0_MAX_PKT_SIZE, +}; + +static void mv_u3d_ep0_reset(struct mv_u3d *u3d) +{ + struct mv_u3d_ep *ep; + u32 epxcr; + int i; + + for (i = 0; i < 2; i++) { + ep = &u3d->eps[i]; + ep->u3d = u3d; + + /* ep0 ep context, ep0 in and out share the same ep context */ + ep->ep_context = &u3d->ep_context[1]; + } + + /* reset ep state machine */ + /* reset ep0 out */ + epxcr = ioread32(&u3d->vuc_regs->epcr[0].epxoutcr0); + epxcr |= MV_U3D_EPXCR_EP_INIT; + iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxoutcr0); + udelay(5); + epxcr &= ~MV_U3D_EPXCR_EP_INIT; + iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxoutcr0); + + epxcr = ((MV_U3D_EP0_MAX_PKT_SIZE + << MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT) + | (1 << MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT) + | (1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) + | MV_U3D_EPXCR_EP_TYPE_CONTROL); + iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxoutcr1); + + /* reset ep0 in */ + epxcr = ioread32(&u3d->vuc_regs->epcr[0].epxincr0); + epxcr |= MV_U3D_EPXCR_EP_INIT; + iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxincr0); + udelay(5); + epxcr &= ~MV_U3D_EPXCR_EP_INIT; + iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxincr0); + + epxcr = ((MV_U3D_EP0_MAX_PKT_SIZE + << MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT) + | (1 << MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT) + | (1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) + | MV_U3D_EPXCR_EP_TYPE_CONTROL); + iowrite32(epxcr, &u3d->vuc_regs->epcr[0].epxincr1); +} + +static void mv_u3d_ep0_stall(struct mv_u3d *u3d) +{ + u32 tmp; + dev_dbg(u3d->dev, "%s\n", __func__); + + /* set TX and RX to stall */ + tmp = ioread32(&u3d->vuc_regs->epcr[0].epxoutcr0); + tmp |= MV_U3D_EPXCR_EP_HALT; + iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxoutcr0); + + tmp = ioread32(&u3d->vuc_regs->epcr[0].epxincr0); + tmp |= MV_U3D_EPXCR_EP_HALT; + iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxincr0); + + /* update ep0 state */ + u3d->ep0_state = MV_U3D_WAIT_FOR_SETUP; + u3d->ep0_dir = MV_U3D_EP_DIR_OUT; +} + +static int mv_u3d_process_ep_req(struct mv_u3d *u3d, int index, + struct mv_u3d_req *curr_req) +{ + struct mv_u3d_trb *curr_trb; + dma_addr_t cur_deq_lo; + struct mv_u3d_ep_context *curr_ep_context; + int trb_complete, actual, remaining_length = 0; + int direction, ep_num; + int retval = 0; + u32 tmp, status, length; + + curr_ep_context = &u3d->ep_context[index]; + direction = index % 2; + ep_num = index / 2; + + trb_complete = 0; + actual = curr_req->req.length; + + while (!list_empty(&curr_req->trb_list)) { + curr_trb = list_entry(curr_req->trb_list.next, + struct mv_u3d_trb, trb_list); + if (!curr_trb->trb_hw->ctrl.own) { + dev_err(u3d->dev, "%s, TRB own error!\n", + u3d->eps[index].name); + return 1; + } + + curr_trb->trb_hw->ctrl.own = 0; + if (direction == MV_U3D_EP_DIR_OUT) { + tmp = ioread32(&u3d->vuc_regs->rxst[ep_num].statuslo); + cur_deq_lo = + ioread32(&u3d->vuc_regs->rxst[ep_num].curdeqlo); + } else { + tmp = ioread32(&u3d->vuc_regs->txst[ep_num].statuslo); + cur_deq_lo = + ioread32(&u3d->vuc_regs->txst[ep_num].curdeqlo); + } + + status = tmp >> MV_U3D_XFERSTATUS_COMPLETE_SHIFT; + length = tmp & MV_U3D_XFERSTATUS_TRB_LENGTH_MASK; + + if (status == MV_U3D_COMPLETE_SUCCESS || + (status == MV_U3D_COMPLETE_SHORT_PACKET && + direction == MV_U3D_EP_DIR_OUT)) { + remaining_length += length; + actual -= remaining_length; + } else { + dev_err(u3d->dev, + "complete_tr error: ep=%d %s: error = 0x%x\n", + index >> 1, direction ? "SEND" : "RECV", + status); + retval = -EPROTO; + } + + list_del_init(&curr_trb->trb_list); + } + if (retval) + return retval; + + curr_req->req.actual = actual; + return 0; +} + +/* + * mv_u3d_done() - retire a request; caller blocked irqs + * @status : request status to be set, only works when + * request is still in progress. + */ +static +void mv_u3d_done(struct mv_u3d_ep *ep, struct mv_u3d_req *req, int status) + __releases(&ep->udc->lock) + __acquires(&ep->udc->lock) +{ + struct mv_u3d *u3d = (struct mv_u3d *)ep->u3d; + + dev_dbg(u3d->dev, "mv_u3d_done: remove req->queue\n"); + /* Removed the req from ep queue */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + /* Free trb for the request */ + if (!req->chain) + dma_pool_free(u3d->trb_pool, + req->trb_head->trb_hw, req->trb_head->trb_dma); + else { + dma_unmap_single(ep->u3d->gadget.dev.parent, + (dma_addr_t)req->trb_head->trb_dma, + req->trb_count * sizeof(struct mv_u3d_trb_hw), + DMA_BIDIRECTIONAL); + kfree(req->trb_head->trb_hw); + } + kfree(req->trb_head); + + usb_gadget_unmap_request(&u3d->gadget, &req->req, mv_u3d_ep_dir(ep)); + + if (status && (status != -ESHUTDOWN)) { + dev_dbg(u3d->dev, "complete %s req %p stat %d len %u/%u", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + } + + spin_unlock(&ep->u3d->lock); + /* + * complete() is from gadget layer, + * eg fsg->bulk_in_complete() + */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&ep->u3d->lock); +} + +static int mv_u3d_queue_trb(struct mv_u3d_ep *ep, struct mv_u3d_req *req) +{ + u32 tmp, direction; + struct mv_u3d *u3d; + struct mv_u3d_ep_context *ep_context; + int retval = 0; + + u3d = ep->u3d; + direction = mv_u3d_ep_dir(ep); + + /* ep0 in and out share the same ep context slot 1*/ + if (ep->ep_num == 0) + ep_context = &(u3d->ep_context[1]); + else + ep_context = &(u3d->ep_context[ep->ep_num * 2 + direction]); + + /* check if the pipe is empty or not */ + if (!list_empty(&ep->queue)) { + dev_err(u3d->dev, "add trb to non-empty queue!\n"); + retval = -ENOMEM; + WARN_ON(1); + } else { + ep_context->rsvd0 = cpu_to_le32(1); + ep_context->rsvd1 = 0; + + /* Configure the trb address and set the DCS bit. + * Both DCS bit and own bit in trb should be set. + */ + ep_context->trb_addr_lo = + cpu_to_le32(req->trb_head->trb_dma | DCS_ENABLE); + ep_context->trb_addr_hi = 0; + + /* Ensure that updates to the EP Context will + * occure before Ring Bell. + */ + wmb(); + + /* ring bell the ep */ + if (ep->ep_num == 0) + tmp = 0x1; + else + tmp = ep->ep_num * 2 + + ((direction == MV_U3D_EP_DIR_OUT) ? 0 : 1); + + iowrite32(tmp, &u3d->op_regs->doorbell); + } + return retval; +} + +static struct mv_u3d_trb *mv_u3d_build_trb_one(struct mv_u3d_req *req, + unsigned *length, dma_addr_t *dma) +{ + u32 temp; + unsigned int direction; + struct mv_u3d_trb *trb; + struct mv_u3d_trb_hw *trb_hw; + struct mv_u3d *u3d; + + /* how big will this transfer be? */ + *length = req->req.length - req->req.actual; + BUG_ON(*length > (unsigned)MV_U3D_EP_MAX_LENGTH_TRANSFER); + + u3d = req->ep->u3d; + + trb = kzalloc(sizeof(*trb), GFP_ATOMIC); + if (!trb) { + dev_err(u3d->dev, "%s, trb alloc fail\n", __func__); + return NULL; + } + + /* + * Be careful that no _GFP_HIGHMEM is set, + * or we can not use dma_to_virt + * cannot use GFP_KERNEL in spin lock + */ + trb_hw = dma_pool_alloc(u3d->trb_pool, GFP_ATOMIC, dma); + if (!trb_hw) { + dev_err(u3d->dev, + "%s, dma_pool_alloc fail\n", __func__); + return NULL; + } + trb->trb_dma = *dma; + trb->trb_hw = trb_hw; + + /* initialize buffer page pointers */ + temp = (u32)(req->req.dma + req->req.actual); + + trb_hw->buf_addr_lo = cpu_to_le32(temp); + trb_hw->buf_addr_hi = 0; + trb_hw->trb_len = cpu_to_le32(*length); + trb_hw->ctrl.own = 1; + + if (req->ep->ep_num == 0) + trb_hw->ctrl.type = TYPE_DATA; + else + trb_hw->ctrl.type = TYPE_NORMAL; + + req->req.actual += *length; + + direction = mv_u3d_ep_dir(req->ep); + if (direction == MV_U3D_EP_DIR_IN) + trb_hw->ctrl.dir = 1; + else + trb_hw->ctrl.dir = 0; + + /* Enable interrupt for the last trb of a request */ + if (!req->req.no_interrupt) + trb_hw->ctrl.ioc = 1; + + trb_hw->ctrl.chain = 0; + + wmb(); + return trb; +} + +static int mv_u3d_build_trb_chain(struct mv_u3d_req *req, unsigned *length, + struct mv_u3d_trb *trb, int *is_last) +{ + u32 temp; + unsigned int direction; + struct mv_u3d *u3d; + + /* how big will this transfer be? */ + *length = min(req->req.length - req->req.actual, + (unsigned)MV_U3D_EP_MAX_LENGTH_TRANSFER); + + u3d = req->ep->u3d; + + trb->trb_dma = 0; + + /* initialize buffer page pointers */ + temp = (u32)(req->req.dma + req->req.actual); + + trb->trb_hw->buf_addr_lo = cpu_to_le32(temp); + trb->trb_hw->buf_addr_hi = 0; + trb->trb_hw->trb_len = cpu_to_le32(*length); + trb->trb_hw->ctrl.own = 1; + + if (req->ep->ep_num == 0) + trb->trb_hw->ctrl.type = TYPE_DATA; + else + trb->trb_hw->ctrl.type = TYPE_NORMAL; + + req->req.actual += *length; + + direction = mv_u3d_ep_dir(req->ep); + if (direction == MV_U3D_EP_DIR_IN) + trb->trb_hw->ctrl.dir = 1; + else + trb->trb_hw->ctrl.dir = 0; + + /* zlp is needed if req->req.zero is set */ + if (req->req.zero) { + if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) + *is_last = 1; + else + *is_last = 0; + } else if (req->req.length == req->req.actual) + *is_last = 1; + else + *is_last = 0; + + /* Enable interrupt for the last trb of a request */ + if (*is_last && !req->req.no_interrupt) + trb->trb_hw->ctrl.ioc = 1; + + if (*is_last) + trb->trb_hw->ctrl.chain = 0; + else { + trb->trb_hw->ctrl.chain = 1; + dev_dbg(u3d->dev, "chain trb\n"); + } + + wmb(); + + return 0; +} + +/* generate TRB linked list for a request + * usb controller only supports continous trb chain, + * that trb structure physical address should be continous. + */ +static int mv_u3d_req_to_trb(struct mv_u3d_req *req) +{ + unsigned count; + int is_last; + struct mv_u3d_trb *trb; + struct mv_u3d_trb_hw *trb_hw; + struct mv_u3d *u3d; + dma_addr_t dma; + unsigned length; + unsigned trb_num; + + u3d = req->ep->u3d; + + INIT_LIST_HEAD(&req->trb_list); + + length = req->req.length - req->req.actual; + /* normally the request transfer length is less than 16KB. + * we use buil_trb_one() to optimize it. + */ + if (length <= (unsigned)MV_U3D_EP_MAX_LENGTH_TRANSFER) { + trb = mv_u3d_build_trb_one(req, &count, &dma); + list_add_tail(&trb->trb_list, &req->trb_list); + req->trb_head = trb; + req->trb_count = 1; + req->chain = 0; + } else { + trb_num = length / MV_U3D_EP_MAX_LENGTH_TRANSFER; + if (length % MV_U3D_EP_MAX_LENGTH_TRANSFER) + trb_num++; + + trb = kcalloc(trb_num, sizeof(*trb), GFP_ATOMIC); + if (!trb) { + dev_err(u3d->dev, + "%s, trb alloc fail\n", __func__); + return -ENOMEM; + } + + trb_hw = kcalloc(trb_num, sizeof(*trb_hw), GFP_ATOMIC); + if (!trb_hw) { + dev_err(u3d->dev, + "%s, trb_hw alloc fail\n", __func__); + return -ENOMEM; + } + + do { + trb->trb_hw = trb_hw; + if (mv_u3d_build_trb_chain(req, &count, + trb, &is_last)) { + dev_err(u3d->dev, + "%s, mv_u3d_build_trb_chain fail\n", + __func__); + return -EIO; + } + + list_add_tail(&trb->trb_list, &req->trb_list); + req->trb_count++; + trb++; + trb_hw++; + } while (!is_last); + + req->trb_head = list_entry(req->trb_list.next, + struct mv_u3d_trb, trb_list); + req->trb_head->trb_dma = dma_map_single(u3d->gadget.dev.parent, + req->trb_head->trb_hw, + trb_num * sizeof(*trb_hw), + DMA_BIDIRECTIONAL); + + req->chain = 1; + } + + return 0; +} + +static int +mv_u3d_start_queue(struct mv_u3d_ep *ep) +{ + struct mv_u3d *u3d = ep->u3d; + struct mv_u3d_req *req; + int ret; + + if (!list_empty(&ep->req_list) && !ep->processing) + req = list_entry(ep->req_list.next, struct mv_u3d_req, list); + else + return 0; + + ep->processing = 1; + + /* set up dma mapping */ + ret = usb_gadget_map_request(&u3d->gadget, &req->req, + mv_u3d_ep_dir(ep)); + if (ret) + return ret; + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->trb_count = 0; + + /* build trbs and push them to device queue */ + if (!mv_u3d_req_to_trb(req)) { + ret = mv_u3d_queue_trb(ep, req); + if (ret) { + ep->processing = 0; + return ret; + } + } else { + ep->processing = 0; + dev_err(u3d->dev, "%s, mv_u3d_req_to_trb fail\n", __func__); + return -ENOMEM; + } + + /* irq handler advances the queue */ + if (req) + list_add_tail(&req->queue, &ep->queue); + + return 0; +} + +static int mv_u3d_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct mv_u3d *u3d; + struct mv_u3d_ep *ep; + struct mv_u3d_ep_context *ep_context; + u16 max = 0; + unsigned maxburst = 0; + u32 epxcr, direction; + + if (!_ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + ep = container_of(_ep, struct mv_u3d_ep, ep); + u3d = ep->u3d; + + if (!u3d->driver || u3d->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + direction = mv_u3d_ep_dir(ep); + max = le16_to_cpu(desc->wMaxPacketSize); + + if (!_ep->maxburst) + _ep->maxburst = 1; + maxburst = _ep->maxburst; + + /* Get the endpoint context address */ + ep_context = (struct mv_u3d_ep_context *)ep->ep_context; + + /* Set the max burst size */ + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + if (maxburst > 16) { + dev_dbg(u3d->dev, + "max burst should not be greater " + "than 16 on bulk ep\n"); + maxburst = 1; + _ep->maxburst = maxburst; + } + dev_dbg(u3d->dev, + "maxburst: %d on bulk %s\n", maxburst, ep->name); + break; + case USB_ENDPOINT_XFER_CONTROL: + /* control transfer only supports maxburst as one */ + maxburst = 1; + _ep->maxburst = maxburst; + break; + case USB_ENDPOINT_XFER_INT: + if (maxburst != 1) { + dev_dbg(u3d->dev, + "max burst should be 1 on int ep " + "if transfer size is not 1024\n"); + maxburst = 1; + _ep->maxburst = maxburst; + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (maxburst != 1) { + dev_dbg(u3d->dev, + "max burst should be 1 on isoc ep " + "if transfer size is not 1024\n"); + maxburst = 1; + _ep->maxburst = maxburst; + } + break; + default: + goto en_done; + } + + ep->ep.maxpacket = max; + ep->ep.desc = desc; + ep->enabled = 1; + + /* Enable the endpoint for Rx or Tx and set the endpoint type */ + if (direction == MV_U3D_EP_DIR_OUT) { + epxcr = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); + epxcr |= MV_U3D_EPXCR_EP_INIT; + iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); + udelay(5); + epxcr &= ~MV_U3D_EPXCR_EP_INIT; + iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); + + epxcr = ((max << MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT) + | ((maxburst - 1) << MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT) + | (1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) + | (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)); + iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr1); + } else { + epxcr = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr0); + epxcr |= MV_U3D_EPXCR_EP_INIT; + iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxincr0); + udelay(5); + epxcr &= ~MV_U3D_EPXCR_EP_INIT; + iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxincr0); + + epxcr = ((max << MV_U3D_EPXCR_MAX_PACKET_SIZE_SHIFT) + | ((maxburst - 1) << MV_U3D_EPXCR_MAX_BURST_SIZE_SHIFT) + | (1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) + | (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)); + iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxincr1); + } + + return 0; +en_done: + return -EINVAL; +} + +static int mv_u3d_ep_disable(struct usb_ep *_ep) +{ + struct mv_u3d *u3d; + struct mv_u3d_ep *ep; + struct mv_u3d_ep_context *ep_context; + u32 epxcr, direction; + + if (!_ep) + return -EINVAL; + + ep = container_of(_ep, struct mv_u3d_ep, ep); + if (!ep->ep.desc) + return -EINVAL; + + u3d = ep->u3d; + + /* Get the endpoint context address */ + ep_context = ep->ep_context; + + direction = mv_u3d_ep_dir(ep); + + /* nuke all pending requests (does flush) */ + mv_u3d_nuke(ep, -ESHUTDOWN); + + /* Disable the endpoint for Rx or Tx and reset the endpoint type */ + if (direction == MV_U3D_EP_DIR_OUT) { + epxcr = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr1); + epxcr &= ~((1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) + | USB_ENDPOINT_XFERTYPE_MASK); + iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr1); + } else { + epxcr = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr1); + epxcr &= ~((1 << MV_U3D_EPXCR_EP_ENABLE_SHIFT) + | USB_ENDPOINT_XFERTYPE_MASK); + iowrite32(epxcr, &u3d->vuc_regs->epcr[ep->ep_num].epxincr1); + } + + ep->enabled = 0; + + ep->ep.desc = NULL; + return 0; +} + +static struct usb_request * +mv_u3d_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct mv_u3d_req *req = NULL; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void mv_u3d_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct mv_u3d_req *req = container_of(_req, struct mv_u3d_req, req); + + kfree(req); +} + +static void mv_u3d_ep_fifo_flush(struct usb_ep *_ep) +{ + struct mv_u3d *u3d; + u32 direction; + struct mv_u3d_ep *ep = container_of(_ep, struct mv_u3d_ep, ep); + unsigned int loops; + u32 tmp; + + /* if endpoint is not enabled, cannot flush endpoint */ + if (!ep->enabled) + return; + + u3d = ep->u3d; + direction = mv_u3d_ep_dir(ep); + + /* ep0 need clear bit after flushing fifo. */ + if (!ep->ep_num) { + if (direction == MV_U3D_EP_DIR_OUT) { + tmp = ioread32(&u3d->vuc_regs->epcr[0].epxoutcr0); + tmp |= MV_U3D_EPXCR_EP_FLUSH; + iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxoutcr0); + udelay(10); + tmp &= ~MV_U3D_EPXCR_EP_FLUSH; + iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxoutcr0); + } else { + tmp = ioread32(&u3d->vuc_regs->epcr[0].epxincr0); + tmp |= MV_U3D_EPXCR_EP_FLUSH; + iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxincr0); + udelay(10); + tmp &= ~MV_U3D_EPXCR_EP_FLUSH; + iowrite32(tmp, &u3d->vuc_regs->epcr[0].epxincr0); + } + return; + } + + if (direction == MV_U3D_EP_DIR_OUT) { + tmp = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); + tmp |= MV_U3D_EPXCR_EP_FLUSH; + iowrite32(tmp, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); + + /* Wait until flushing completed */ + loops = LOOPS(MV_U3D_FLUSH_TIMEOUT); + while (ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0) & + MV_U3D_EPXCR_EP_FLUSH) { + /* + * EP_FLUSH bit should be cleared to indicate this + * operation is complete + */ + if (loops == 0) { + dev_dbg(u3d->dev, + "EP FLUSH TIMEOUT for ep%d%s\n", ep->ep_num, + direction ? "in" : "out"); + return; + } + loops--; + udelay(LOOPS_USEC); + } + } else { /* EP_DIR_IN */ + tmp = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr0); + tmp |= MV_U3D_EPXCR_EP_FLUSH; + iowrite32(tmp, &u3d->vuc_regs->epcr[ep->ep_num].epxincr0); + + /* Wait until flushing completed */ + loops = LOOPS(MV_U3D_FLUSH_TIMEOUT); + while (ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr0) & + MV_U3D_EPXCR_EP_FLUSH) { + /* + * EP_FLUSH bit should be cleared to indicate this + * operation is complete + */ + if (loops == 0) { + dev_dbg(u3d->dev, + "EP FLUSH TIMEOUT for ep%d%s\n", ep->ep_num, + direction ? "in" : "out"); + return; + } + loops--; + udelay(LOOPS_USEC); + } + } +} + +/* queues (submits) an I/O request to an endpoint */ +static int +mv_u3d_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct mv_u3d_ep *ep; + struct mv_u3d_req *req; + struct mv_u3d *u3d; + unsigned long flags; + int is_first_req = 0; + + if (unlikely(!_ep || !_req)) + return -EINVAL; + + ep = container_of(_ep, struct mv_u3d_ep, ep); + u3d = ep->u3d; + + req = container_of(_req, struct mv_u3d_req, req); + + if (!ep->ep_num + && u3d->ep0_state == MV_U3D_STATUS_STAGE + && !_req->length) { + dev_dbg(u3d->dev, "ep0 status stage\n"); + u3d->ep0_state = MV_U3D_WAIT_FOR_SETUP; + return 0; + } + + dev_dbg(u3d->dev, "%s: %s, req: 0x%p\n", + __func__, _ep->name, req); + + /* catch various bogus parameters */ + if (!req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + dev_err(u3d->dev, + "%s, bad params, _req: 0x%p," + "req->req.complete: 0x%p, req->req.buf: 0x%p," + "list_empty: 0x%x\n", + __func__, _req, + req->req.complete, req->req.buf, + list_empty(&req->queue)); + return -EINVAL; + } + if (unlikely(!ep->ep.desc)) { + dev_err(u3d->dev, "%s, bad ep\n", __func__); + return -EINVAL; + } + if (ep->ep.desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + } + + if (!u3d->driver || u3d->gadget.speed == USB_SPEED_UNKNOWN) { + dev_err(u3d->dev, + "bad params of driver/speed\n"); + return -ESHUTDOWN; + } + + req->ep = ep; + + /* Software list handles usb request. */ + spin_lock_irqsave(&ep->req_lock, flags); + is_first_req = list_empty(&ep->req_list); + list_add_tail(&req->list, &ep->req_list); + spin_unlock_irqrestore(&ep->req_lock, flags); + if (!is_first_req) { + dev_dbg(u3d->dev, "list is not empty\n"); + return 0; + } + + dev_dbg(u3d->dev, "call mv_u3d_start_queue from usb_ep_queue\n"); + spin_lock_irqsave(&u3d->lock, flags); + mv_u3d_start_queue(ep); + spin_unlock_irqrestore(&u3d->lock, flags); + return 0; +} + +/* dequeues (cancels, unlinks) an I/O request from an endpoint */ +static int mv_u3d_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct mv_u3d_ep *ep; + struct mv_u3d_req *req; + struct mv_u3d *u3d; + struct mv_u3d_ep_context *ep_context; + struct mv_u3d_req *next_req; + + unsigned long flags; + int ret = 0; + + if (!_ep || !_req) + return -EINVAL; + + ep = container_of(_ep, struct mv_u3d_ep, ep); + u3d = ep->u3d; + + spin_lock_irqsave(&ep->u3d->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + ret = -EINVAL; + goto out; + } + + /* The request is in progress, or completed but not dequeued */ + if (ep->queue.next == &req->queue) { + _req->status = -ECONNRESET; + mv_u3d_ep_fifo_flush(_ep); + + /* The request isn't the last request in this ep queue */ + if (req->queue.next != &ep->queue) { + dev_dbg(u3d->dev, + "it is the last request in this ep queue\n"); + ep_context = ep->ep_context; + next_req = list_entry(req->queue.next, + struct mv_u3d_req, queue); + + /* Point first TRB of next request to the EP context. */ + iowrite32((unsigned long) next_req->trb_head, + &ep_context->trb_addr_lo); + } else { + struct mv_u3d_ep_context *ep_context; + ep_context = ep->ep_context; + ep_context->trb_addr_lo = 0; + ep_context->trb_addr_hi = 0; + } + + } else + WARN_ON(1); + + mv_u3d_done(ep, req, -ECONNRESET); + + /* remove the req from the ep req list */ + if (!list_empty(&ep->req_list)) { + struct mv_u3d_req *curr_req; + curr_req = list_entry(ep->req_list.next, + struct mv_u3d_req, list); + if (curr_req == req) { + list_del_init(&req->list); + ep->processing = 0; + } + } + +out: + spin_unlock_irqrestore(&ep->u3d->lock, flags); + return ret; +} + +static void +mv_u3d_ep_set_stall(struct mv_u3d *u3d, u8 ep_num, u8 direction, int stall) +{ + u32 tmp; + struct mv_u3d_ep *ep = u3d->eps; + + dev_dbg(u3d->dev, "%s\n", __func__); + if (direction == MV_U3D_EP_DIR_OUT) { + tmp = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); + if (stall) + tmp |= MV_U3D_EPXCR_EP_HALT; + else + tmp &= ~MV_U3D_EPXCR_EP_HALT; + iowrite32(tmp, &u3d->vuc_regs->epcr[ep->ep_num].epxoutcr0); + } else { + tmp = ioread32(&u3d->vuc_regs->epcr[ep->ep_num].epxincr0); + if (stall) + tmp |= MV_U3D_EPXCR_EP_HALT; + else + tmp &= ~MV_U3D_EPXCR_EP_HALT; + iowrite32(tmp, &u3d->vuc_regs->epcr[ep->ep_num].epxincr0); + } +} + +static int mv_u3d_ep_set_halt_wedge(struct usb_ep *_ep, int halt, int wedge) +{ + struct mv_u3d_ep *ep; + unsigned long flags = 0; + int status = 0; + struct mv_u3d *u3d; + + ep = container_of(_ep, struct mv_u3d_ep, ep); + u3d = ep->u3d; + if (!ep->ep.desc) { + status = -EINVAL; + goto out; + } + + if (ep->ep.desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + status = -EOPNOTSUPP; + goto out; + } + + /* + * Attempt to halt IN ep will fail if any transfer requests + * are still queue + */ + if (halt && (mv_u3d_ep_dir(ep) == MV_U3D_EP_DIR_IN) + && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + spin_lock_irqsave(&ep->u3d->lock, flags); + mv_u3d_ep_set_stall(u3d, ep->ep_num, mv_u3d_ep_dir(ep), halt); + if (halt && wedge) + ep->wedge = 1; + else if (!halt) + ep->wedge = 0; + spin_unlock_irqrestore(&ep->u3d->lock, flags); + + if (ep->ep_num == 0) + u3d->ep0_dir = MV_U3D_EP_DIR_OUT; +out: + return status; +} + +static int mv_u3d_ep_set_halt(struct usb_ep *_ep, int halt) +{ + return mv_u3d_ep_set_halt_wedge(_ep, halt, 0); +} + +static int mv_u3d_ep_set_wedge(struct usb_ep *_ep) +{ + return mv_u3d_ep_set_halt_wedge(_ep, 1, 1); +} + +static struct usb_ep_ops mv_u3d_ep_ops = { + .enable = mv_u3d_ep_enable, + .disable = mv_u3d_ep_disable, + + .alloc_request = mv_u3d_alloc_request, + .free_request = mv_u3d_free_request, + + .queue = mv_u3d_ep_queue, + .dequeue = mv_u3d_ep_dequeue, + + .set_wedge = mv_u3d_ep_set_wedge, + .set_halt = mv_u3d_ep_set_halt, + .fifo_flush = mv_u3d_ep_fifo_flush, +}; + +static void mv_u3d_controller_stop(struct mv_u3d *u3d) +{ + u32 tmp; + + if (!u3d->clock_gating && u3d->vbus_valid_detect) + iowrite32(MV_U3D_INTR_ENABLE_VBUS_VALID, + &u3d->vuc_regs->intrenable); + else + iowrite32(0, &u3d->vuc_regs->intrenable); + iowrite32(~0x0, &u3d->vuc_regs->endcomplete); + iowrite32(~0x0, &u3d->vuc_regs->trbunderrun); + iowrite32(~0x0, &u3d->vuc_regs->trbcomplete); + iowrite32(~0x0, &u3d->vuc_regs->linkchange); + iowrite32(0x1, &u3d->vuc_regs->setuplock); + + /* Reset the RUN bit in the command register to stop USB */ + tmp = ioread32(&u3d->op_regs->usbcmd); + tmp &= ~MV_U3D_CMD_RUN_STOP; + iowrite32(tmp, &u3d->op_regs->usbcmd); + dev_dbg(u3d->dev, "after u3d_stop, USBCMD 0x%x\n", + ioread32(&u3d->op_regs->usbcmd)); +} + +static void mv_u3d_controller_start(struct mv_u3d *u3d) +{ + u32 usbintr; + u32 temp; + + /* enable link LTSSM state machine */ + temp = ioread32(&u3d->vuc_regs->ltssm); + temp |= MV_U3D_LTSSM_PHY_INIT_DONE; + iowrite32(temp, &u3d->vuc_regs->ltssm); + + /* Enable interrupts */ + usbintr = MV_U3D_INTR_ENABLE_LINK_CHG | MV_U3D_INTR_ENABLE_TXDESC_ERR | + MV_U3D_INTR_ENABLE_RXDESC_ERR | MV_U3D_INTR_ENABLE_TX_COMPLETE | + MV_U3D_INTR_ENABLE_RX_COMPLETE | MV_U3D_INTR_ENABLE_SETUP | + (u3d->vbus_valid_detect ? MV_U3D_INTR_ENABLE_VBUS_VALID : 0); + iowrite32(usbintr, &u3d->vuc_regs->intrenable); + + /* Enable ctrl ep */ + iowrite32(0x1, &u3d->vuc_regs->ctrlepenable); + + /* Set the Run bit in the command register */ + iowrite32(MV_U3D_CMD_RUN_STOP, &u3d->op_regs->usbcmd); + dev_dbg(u3d->dev, "after u3d_start, USBCMD 0x%x\n", + ioread32(&u3d->op_regs->usbcmd)); +} + +static int mv_u3d_controller_reset(struct mv_u3d *u3d) +{ + unsigned int loops; + u32 tmp; + + /* Stop the controller */ + tmp = ioread32(&u3d->op_regs->usbcmd); + tmp &= ~MV_U3D_CMD_RUN_STOP; + iowrite32(tmp, &u3d->op_regs->usbcmd); + + /* Reset the controller to get default values */ + iowrite32(MV_U3D_CMD_CTRL_RESET, &u3d->op_regs->usbcmd); + + /* wait for reset to complete */ + loops = LOOPS(MV_U3D_RESET_TIMEOUT); + while (ioread32(&u3d->op_regs->usbcmd) & MV_U3D_CMD_CTRL_RESET) { + if (loops == 0) { + dev_err(u3d->dev, + "Wait for RESET completed TIMEOUT\n"); + return -ETIMEDOUT; + } + loops--; + udelay(LOOPS_USEC); + } + + /* Configure the Endpoint Context Address */ + iowrite32(u3d->ep_context_dma, &u3d->op_regs->dcbaapl); + iowrite32(0, &u3d->op_regs->dcbaaph); + + return 0; +} + +static int mv_u3d_enable(struct mv_u3d *u3d) +{ + struct mv_usb_platform_data *pdata = u3d->dev->platform_data; + int retval; + + if (u3d->active) + return 0; + + if (!u3d->clock_gating) { + u3d->active = 1; + return 0; + } + + dev_dbg(u3d->dev, "enable u3d\n"); + clk_enable(u3d->clk); + if (pdata->phy_init) { + retval = pdata->phy_init(u3d->phy_regs); + if (retval) { + dev_err(u3d->dev, + "init phy error %d\n", retval); + clk_disable(u3d->clk); + return retval; + } + } + u3d->active = 1; + + return 0; +} + +static void mv_u3d_disable(struct mv_u3d *u3d) +{ + struct mv_usb_platform_data *pdata = u3d->dev->platform_data; + if (u3d->clock_gating && u3d->active) { + dev_dbg(u3d->dev, "disable u3d\n"); + if (pdata->phy_deinit) + pdata->phy_deinit(u3d->phy_regs); + clk_disable(u3d->clk); + u3d->active = 0; + } +} + +static int mv_u3d_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct mv_u3d *u3d; + unsigned long flags; + int retval = 0; + + u3d = container_of(gadget, struct mv_u3d, gadget); + + spin_lock_irqsave(&u3d->lock, flags); + + u3d->vbus_active = (is_active != 0); + dev_dbg(u3d->dev, "%s: softconnect %d, vbus_active %d\n", + __func__, u3d->softconnect, u3d->vbus_active); + /* + * 1. external VBUS detect: we can disable/enable clock on demand. + * 2. UDC VBUS detect: we have to enable clock all the time. + * 3. No VBUS detect: we have to enable clock all the time. + */ + if (u3d->driver && u3d->softconnect && u3d->vbus_active) { + retval = mv_u3d_enable(u3d); + if (retval == 0) { + /* + * after clock is disabled, we lost all the register + * context. We have to re-init registers + */ + mv_u3d_controller_reset(u3d); + mv_u3d_ep0_reset(u3d); + mv_u3d_controller_start(u3d); + } + } else if (u3d->driver && u3d->softconnect) { + if (!u3d->active) + goto out; + + /* stop all the transfer in queue*/ + mv_u3d_stop_activity(u3d, u3d->driver); + mv_u3d_controller_stop(u3d); + mv_u3d_disable(u3d); + } + +out: + spin_unlock_irqrestore(&u3d->lock, flags); + return retval; +} + +/* constrain controller's VBUS power usage + * This call is used by gadget drivers during SET_CONFIGURATION calls, + * reporting how much power the device may consume. For example, this + * could affect how quickly batteries are recharged. + * + * Returns zero on success, else negative errno. + */ +static int mv_u3d_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct mv_u3d *u3d = container_of(gadget, struct mv_u3d, gadget); + + u3d->power = mA; + + return 0; +} + +static int mv_u3d_pullup(struct usb_gadget *gadget, int is_on) +{ + struct mv_u3d *u3d = container_of(gadget, struct mv_u3d, gadget); + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&u3d->lock, flags); + + dev_dbg(u3d->dev, "%s: softconnect %d, vbus_active %d\n", + __func__, u3d->softconnect, u3d->vbus_active); + u3d->softconnect = (is_on != 0); + if (u3d->driver && u3d->softconnect && u3d->vbus_active) { + retval = mv_u3d_enable(u3d); + if (retval == 0) { + /* + * after clock is disabled, we lost all the register + * context. We have to re-init registers + */ + mv_u3d_controller_reset(u3d); + mv_u3d_ep0_reset(u3d); + mv_u3d_controller_start(u3d); + } + } else if (u3d->driver && u3d->vbus_active) { + /* stop all the transfer in queue*/ + mv_u3d_stop_activity(u3d, u3d->driver); + mv_u3d_controller_stop(u3d); + mv_u3d_disable(u3d); + } + + spin_unlock_irqrestore(&u3d->lock, flags); + + return retval; +} + +static int mv_u3d_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct mv_u3d *u3d = container_of(g, struct mv_u3d, gadget); + struct mv_usb_platform_data *pdata = u3d->dev->platform_data; + unsigned long flags; + + if (u3d->driver) + return -EBUSY; + + spin_lock_irqsave(&u3d->lock, flags); + + if (!u3d->clock_gating) { + clk_enable(u3d->clk); + if (pdata->phy_init) + pdata->phy_init(u3d->phy_regs); + } + + /* hook up the driver ... */ + driver->driver.bus = NULL; + u3d->driver = driver; + + u3d->ep0_dir = USB_DIR_OUT; + + spin_unlock_irqrestore(&u3d->lock, flags); + + u3d->vbus_valid_detect = 1; + + return 0; +} + +static int mv_u3d_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct mv_u3d *u3d = container_of(g, struct mv_u3d, gadget); + struct mv_usb_platform_data *pdata = u3d->dev->platform_data; + unsigned long flags; + + u3d->vbus_valid_detect = 0; + spin_lock_irqsave(&u3d->lock, flags); + + /* enable clock to access controller register */ + clk_enable(u3d->clk); + if (pdata->phy_init) + pdata->phy_init(u3d->phy_regs); + + mv_u3d_controller_stop(u3d); + /* stop all usb activities */ + u3d->gadget.speed = USB_SPEED_UNKNOWN; + mv_u3d_stop_activity(u3d, driver); + mv_u3d_disable(u3d); + + if (pdata->phy_deinit) + pdata->phy_deinit(u3d->phy_regs); + clk_disable(u3d->clk); + + spin_unlock_irqrestore(&u3d->lock, flags); + + u3d->driver = NULL; + + return 0; +} + +/* device controller usb_gadget_ops structure */ +static const struct usb_gadget_ops mv_u3d_ops = { + /* notify controller that VBUS is powered or not */ + .vbus_session = mv_u3d_vbus_session, + + /* constrain controller's VBUS power usage */ + .vbus_draw = mv_u3d_vbus_draw, + + .pullup = mv_u3d_pullup, + .udc_start = mv_u3d_start, + .udc_stop = mv_u3d_stop, +}; + +static int mv_u3d_eps_init(struct mv_u3d *u3d) +{ + struct mv_u3d_ep *ep; + char name[14]; + int i; + + /* initialize ep0, ep0 in/out use eps[1] */ + ep = &u3d->eps[1]; + ep->u3d = u3d; + strncpy(ep->name, "ep0", sizeof(ep->name)); + ep->ep.name = ep->name; + ep->ep.ops = &mv_u3d_ep_ops; + ep->wedge = 0; + ep->ep.maxpacket = MV_U3D_EP0_MAX_PKT_SIZE; + ep->ep_num = 0; + ep->ep.desc = &mv_u3d_ep0_desc; + INIT_LIST_HEAD(&ep->queue); + INIT_LIST_HEAD(&ep->req_list); + ep->ep_type = USB_ENDPOINT_XFER_CONTROL; + + /* add ep0 ep_context */ + ep->ep_context = &u3d->ep_context[1]; + + /* initialize other endpoints */ + for (i = 2; i < u3d->max_eps * 2; i++) { + ep = &u3d->eps[i]; + if (i & 1) { + snprintf(name, sizeof(name), "ep%din", i >> 1); + ep->direction = MV_U3D_EP_DIR_IN; + } else { + snprintf(name, sizeof(name), "ep%dout", i >> 1); + ep->direction = MV_U3D_EP_DIR_OUT; + } + ep->u3d = u3d; + strncpy(ep->name, name, sizeof(ep->name)); + ep->ep.name = ep->name; + + ep->ep.ops = &mv_u3d_ep_ops; + ep->ep.maxpacket = (unsigned short) ~0; + ep->ep_num = i / 2; + + INIT_LIST_HEAD(&ep->queue); + list_add_tail(&ep->ep.ep_list, &u3d->gadget.ep_list); + + INIT_LIST_HEAD(&ep->req_list); + spin_lock_init(&ep->req_lock); + ep->ep_context = &u3d->ep_context[i]; + } + + return 0; +} + +/* delete all endpoint requests, called with spinlock held */ +static void mv_u3d_nuke(struct mv_u3d_ep *ep, int status) +{ + /* endpoint fifo flush */ + mv_u3d_ep_fifo_flush(&ep->ep); + + while (!list_empty(&ep->queue)) { + struct mv_u3d_req *req = NULL; + req = list_entry(ep->queue.next, struct mv_u3d_req, queue); + mv_u3d_done(ep, req, status); + } +} + +/* stop all USB activities */ +static +void mv_u3d_stop_activity(struct mv_u3d *u3d, struct usb_gadget_driver *driver) +{ + struct mv_u3d_ep *ep; + + mv_u3d_nuke(&u3d->eps[1], -ESHUTDOWN); + + list_for_each_entry(ep, &u3d->gadget.ep_list, ep.ep_list) { + mv_u3d_nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&u3d->lock); + driver->disconnect(&u3d->gadget); + spin_lock(&u3d->lock); + } +} + +static void mv_u3d_irq_process_error(struct mv_u3d *u3d) +{ + /* Increment the error count */ + u3d->errors++; + dev_err(u3d->dev, "%s\n", __func__); +} + +static void mv_u3d_irq_process_link_change(struct mv_u3d *u3d) +{ + u32 linkchange; + + linkchange = ioread32(&u3d->vuc_regs->linkchange); + iowrite32(linkchange, &u3d->vuc_regs->linkchange); + + dev_dbg(u3d->dev, "linkchange: 0x%x\n", linkchange); + + if (linkchange & MV_U3D_LINK_CHANGE_LINK_UP) { + dev_dbg(u3d->dev, "link up: ltssm state: 0x%x\n", + ioread32(&u3d->vuc_regs->ltssmstate)); + + u3d->usb_state = USB_STATE_DEFAULT; + u3d->ep0_dir = MV_U3D_EP_DIR_OUT; + u3d->ep0_state = MV_U3D_WAIT_FOR_SETUP; + + /* set speed */ + u3d->gadget.speed = USB_SPEED_SUPER; + } + + if (linkchange & MV_U3D_LINK_CHANGE_SUSPEND) { + dev_dbg(u3d->dev, "link suspend\n"); + u3d->resume_state = u3d->usb_state; + u3d->usb_state = USB_STATE_SUSPENDED; + } + + if (linkchange & MV_U3D_LINK_CHANGE_RESUME) { + dev_dbg(u3d->dev, "link resume\n"); + u3d->usb_state = u3d->resume_state; + u3d->resume_state = 0; + } + + if (linkchange & MV_U3D_LINK_CHANGE_WRESET) { + dev_dbg(u3d->dev, "warm reset\n"); + u3d->usb_state = USB_STATE_POWERED; + } + + if (linkchange & MV_U3D_LINK_CHANGE_HRESET) { + dev_dbg(u3d->dev, "hot reset\n"); + u3d->usb_state = USB_STATE_DEFAULT; + } + + if (linkchange & MV_U3D_LINK_CHANGE_INACT) + dev_dbg(u3d->dev, "inactive\n"); + + if (linkchange & MV_U3D_LINK_CHANGE_DISABLE_AFTER_U0) + dev_dbg(u3d->dev, "ss.disabled\n"); + + if (linkchange & MV_U3D_LINK_CHANGE_VBUS_INVALID) { + dev_dbg(u3d->dev, "vbus invalid\n"); + u3d->usb_state = USB_STATE_ATTACHED; + u3d->vbus_valid_detect = 1; + /* if external vbus detect is not supported, + * we handle it here. + */ + if (!u3d->vbus) { + spin_unlock(&u3d->lock); + mv_u3d_vbus_session(&u3d->gadget, 0); + spin_lock(&u3d->lock); + } + } +} + +static void mv_u3d_ch9setaddress(struct mv_u3d *u3d, + struct usb_ctrlrequest *setup) +{ + u32 tmp; + + if (u3d->usb_state != USB_STATE_DEFAULT) { + dev_err(u3d->dev, + "%s, cannot setaddr in this state (%d)\n", + __func__, u3d->usb_state); + goto err; + } + + u3d->dev_addr = (u8)setup->wValue; + + dev_dbg(u3d->dev, "%s: 0x%x\n", __func__, u3d->dev_addr); + + if (u3d->dev_addr > 127) { + dev_err(u3d->dev, + "%s, u3d address is wrong (out of range)\n", __func__); + u3d->dev_addr = 0; + goto err; + } + + /* update usb state */ + u3d->usb_state = USB_STATE_ADDRESS; + + /* set the new address */ + tmp = ioread32(&u3d->vuc_regs->devaddrtiebrkr); + tmp &= ~0x7F; + tmp |= (u32)u3d->dev_addr; + iowrite32(tmp, &u3d->vuc_regs->devaddrtiebrkr); + + return; +err: + mv_u3d_ep0_stall(u3d); +} + +static int mv_u3d_is_set_configuration(struct usb_ctrlrequest *setup) +{ + if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + if (setup->bRequest == USB_REQ_SET_CONFIGURATION) + return 1; + + return 0; +} + +static void mv_u3d_handle_setup_packet(struct mv_u3d *u3d, u8 ep_num, + struct usb_ctrlrequest *setup) + __releases(&u3c->lock) + __acquires(&u3c->lock) +{ + bool delegate = false; + + mv_u3d_nuke(&u3d->eps[ep_num * 2 + MV_U3D_EP_DIR_IN], -ESHUTDOWN); + + dev_dbg(u3d->dev, "SETUP %02x.%02x v%04x i%04x l%04x\n", + setup->bRequestType, setup->bRequest, + setup->wValue, setup->wIndex, setup->wLength); + + /* We process some stardard setup requests here */ + if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (setup->bRequest) { + case USB_REQ_GET_STATUS: + delegate = true; + break; + + case USB_REQ_SET_ADDRESS: + mv_u3d_ch9setaddress(u3d, setup); + break; + + case USB_REQ_CLEAR_FEATURE: + delegate = true; + break; + + case USB_REQ_SET_FEATURE: + delegate = true; + break; + + default: + delegate = true; + } + } else + delegate = true; + + /* delegate USB standard requests to the gadget driver */ + if (delegate == true) { + /* USB requests handled by gadget */ + if (setup->wLength) { + /* DATA phase from gadget, STATUS phase from u3d */ + u3d->ep0_dir = (setup->bRequestType & USB_DIR_IN) + ? MV_U3D_EP_DIR_IN : MV_U3D_EP_DIR_OUT; + spin_unlock(&u3d->lock); + if (u3d->driver->setup(&u3d->gadget, + &u3d->local_setup_buff) < 0) { + dev_err(u3d->dev, "setup error!\n"); + mv_u3d_ep0_stall(u3d); + } + spin_lock(&u3d->lock); + } else { + /* no DATA phase, STATUS phase from gadget */ + u3d->ep0_dir = MV_U3D_EP_DIR_IN; + u3d->ep0_state = MV_U3D_STATUS_STAGE; + spin_unlock(&u3d->lock); + if (u3d->driver->setup(&u3d->gadget, + &u3d->local_setup_buff) < 0) + mv_u3d_ep0_stall(u3d); + spin_lock(&u3d->lock); + } + + if (mv_u3d_is_set_configuration(setup)) { + dev_dbg(u3d->dev, "u3d configured\n"); + u3d->usb_state = USB_STATE_CONFIGURED; + } + } +} + +static void mv_u3d_get_setup_data(struct mv_u3d *u3d, u8 ep_num, u8 *buffer_ptr) +{ + struct mv_u3d_ep_context *epcontext; + + epcontext = &u3d->ep_context[ep_num * 2 + MV_U3D_EP_DIR_IN]; + + /* Copy the setup packet to local buffer */ + memcpy(buffer_ptr, (u8 *) &epcontext->setup_buffer, 8); +} + +static void mv_u3d_irq_process_setup(struct mv_u3d *u3d) +{ + u32 tmp, i; + /* Process all Setup packet received interrupts */ + tmp = ioread32(&u3d->vuc_regs->setuplock); + if (tmp) { + for (i = 0; i < u3d->max_eps; i++) { + if (tmp & (1 << i)) { + mv_u3d_get_setup_data(u3d, i, + (u8 *)(&u3d->local_setup_buff)); + mv_u3d_handle_setup_packet(u3d, i, + &u3d->local_setup_buff); + } + } + } + + iowrite32(tmp, &u3d->vuc_regs->setuplock); +} + +static void mv_u3d_irq_process_tr_complete(struct mv_u3d *u3d) +{ + u32 tmp, bit_pos; + int i, ep_num = 0, direction = 0; + struct mv_u3d_ep *curr_ep; + struct mv_u3d_req *curr_req, *temp_req; + int status; + + tmp = ioread32(&u3d->vuc_regs->endcomplete); + + dev_dbg(u3d->dev, "tr_complete: ep: 0x%x\n", tmp); + if (!tmp) + return; + iowrite32(tmp, &u3d->vuc_regs->endcomplete); + + for (i = 0; i < u3d->max_eps * 2; i++) { + ep_num = i >> 1; + direction = i % 2; + + bit_pos = 1 << (ep_num + 16 * direction); + + if (!(bit_pos & tmp)) + continue; + + if (i == 0) + curr_ep = &u3d->eps[1]; + else + curr_ep = &u3d->eps[i]; + + /* remove req out of ep request list after completion */ + dev_dbg(u3d->dev, "tr comp: check req_list\n"); + spin_lock(&curr_ep->req_lock); + if (!list_empty(&curr_ep->req_list)) { + struct mv_u3d_req *req; + req = list_entry(curr_ep->req_list.next, + struct mv_u3d_req, list); + list_del_init(&req->list); + curr_ep->processing = 0; + } + spin_unlock(&curr_ep->req_lock); + + /* process the req queue until an uncomplete request */ + list_for_each_entry_safe(curr_req, temp_req, + &curr_ep->queue, queue) { + status = mv_u3d_process_ep_req(u3d, i, curr_req); + if (status) + break; + /* write back status to req */ + curr_req->req.status = status; + + /* ep0 request completion */ + if (ep_num == 0) { + mv_u3d_done(curr_ep, curr_req, 0); + break; + } else { + mv_u3d_done(curr_ep, curr_req, status); + } + } + + dev_dbg(u3d->dev, "call mv_u3d_start_queue from ep complete\n"); + mv_u3d_start_queue(curr_ep); + } +} + +static irqreturn_t mv_u3d_irq(int irq, void *dev) +{ + struct mv_u3d *u3d = (struct mv_u3d *)dev; + u32 status, intr; + u32 bridgesetting; + u32 trbunderrun; + + spin_lock(&u3d->lock); + + status = ioread32(&u3d->vuc_regs->intrcause); + intr = ioread32(&u3d->vuc_regs->intrenable); + status &= intr; + + if (status == 0) { + spin_unlock(&u3d->lock); + dev_err(u3d->dev, "irq error!\n"); + return IRQ_NONE; + } + + if (status & MV_U3D_USBINT_VBUS_VALID) { + bridgesetting = ioread32(&u3d->vuc_regs->bridgesetting); + if (bridgesetting & MV_U3D_BRIDGE_SETTING_VBUS_VALID) { + /* write vbus valid bit of bridge setting to clear */ + bridgesetting = MV_U3D_BRIDGE_SETTING_VBUS_VALID; + iowrite32(bridgesetting, &u3d->vuc_regs->bridgesetting); + dev_dbg(u3d->dev, "vbus valid\n"); + + u3d->usb_state = USB_STATE_POWERED; + u3d->vbus_valid_detect = 0; + /* if external vbus detect is not supported, + * we handle it here. + */ + if (!u3d->vbus) { + spin_unlock(&u3d->lock); + mv_u3d_vbus_session(&u3d->gadget, 1); + spin_lock(&u3d->lock); + } + } else + dev_err(u3d->dev, "vbus bit is not set\n"); + } + + /* RX data is already in the 16KB FIFO.*/ + if (status & MV_U3D_USBINT_UNDER_RUN) { + trbunderrun = ioread32(&u3d->vuc_regs->trbunderrun); + dev_err(u3d->dev, "under run, ep%d\n", trbunderrun); + iowrite32(trbunderrun, &u3d->vuc_regs->trbunderrun); + mv_u3d_irq_process_error(u3d); + } + + if (status & (MV_U3D_USBINT_RXDESC_ERR | MV_U3D_USBINT_TXDESC_ERR)) { + /* write one to clear */ + iowrite32(status & (MV_U3D_USBINT_RXDESC_ERR + | MV_U3D_USBINT_TXDESC_ERR), + &u3d->vuc_regs->intrcause); + dev_err(u3d->dev, "desc err 0x%x\n", status); + mv_u3d_irq_process_error(u3d); + } + + if (status & MV_U3D_USBINT_LINK_CHG) + mv_u3d_irq_process_link_change(u3d); + + if (status & MV_U3D_USBINT_TX_COMPLETE) + mv_u3d_irq_process_tr_complete(u3d); + + if (status & MV_U3D_USBINT_RX_COMPLETE) + mv_u3d_irq_process_tr_complete(u3d); + + if (status & MV_U3D_USBINT_SETUP) + mv_u3d_irq_process_setup(u3d); + + spin_unlock(&u3d->lock); + return IRQ_HANDLED; +} + +static int mv_u3d_remove(struct platform_device *dev) +{ + struct mv_u3d *u3d = platform_get_drvdata(dev); + + BUG_ON(u3d == NULL); + + usb_del_gadget_udc(&u3d->gadget); + + /* free memory allocated in probe */ + if (u3d->trb_pool) + dma_pool_destroy(u3d->trb_pool); + + if (u3d->ep_context) + dma_free_coherent(&dev->dev, u3d->ep_context_size, + u3d->ep_context, u3d->ep_context_dma); + + kfree(u3d->eps); + + if (u3d->irq) + free_irq(u3d->irq, &dev->dev); + + if (u3d->cap_regs) + iounmap(u3d->cap_regs); + u3d->cap_regs = NULL; + + kfree(u3d->status_req); + + clk_put(u3d->clk); + + platform_set_drvdata(dev, NULL); + + kfree(u3d); + + return 0; +} + +static int mv_u3d_probe(struct platform_device *dev) +{ + struct mv_u3d *u3d = NULL; + struct mv_usb_platform_data *pdata = dev->dev.platform_data; + int retval = 0; + struct resource *r; + size_t size; + + if (!dev->dev.platform_data) { + dev_err(&dev->dev, "missing platform_data\n"); + retval = -ENODEV; + goto err_pdata; + } + + u3d = kzalloc(sizeof(*u3d), GFP_KERNEL); + if (!u3d) { + dev_err(&dev->dev, "failed to allocate memory for u3d\n"); + retval = -ENOMEM; + goto err_alloc_private; + } + + spin_lock_init(&u3d->lock); + + platform_set_drvdata(dev, u3d); + + u3d->dev = &dev->dev; + u3d->vbus = pdata->vbus; + + u3d->clk = clk_get(&dev->dev, NULL); + if (IS_ERR(u3d->clk)) { + retval = PTR_ERR(u3d->clk); + goto err_get_clk; + } + + r = platform_get_resource_byname(dev, IORESOURCE_MEM, "capregs"); + if (!r) { + dev_err(&dev->dev, "no I/O memory resource defined\n"); + retval = -ENODEV; + goto err_get_cap_regs; + } + + u3d->cap_regs = (struct mv_u3d_cap_regs __iomem *) + ioremap(r->start, resource_size(r)); + if (!u3d->cap_regs) { + dev_err(&dev->dev, "failed to map I/O memory\n"); + retval = -EBUSY; + goto err_map_cap_regs; + } else { + dev_dbg(&dev->dev, "cap_regs address: 0x%lx/0x%lx\n", + (unsigned long) r->start, + (unsigned long) u3d->cap_regs); + } + + /* we will access controller register, so enable the u3d controller */ + clk_enable(u3d->clk); + + if (pdata->phy_init) { + retval = pdata->phy_init(u3d->phy_regs); + if (retval) { + dev_err(&dev->dev, "init phy error %d\n", retval); + goto err_u3d_enable; + } + } + + u3d->op_regs = (struct mv_u3d_op_regs __iomem *)(u3d->cap_regs + + MV_U3D_USB3_OP_REGS_OFFSET); + + u3d->vuc_regs = (struct mv_u3d_vuc_regs __iomem *)(u3d->cap_regs + + ioread32(&u3d->cap_regs->vuoff)); + + u3d->max_eps = 16; + + /* + * some platform will use usb to download image, it may not disconnect + * usb gadget before loading kernel. So first stop u3d here. + */ + mv_u3d_controller_stop(u3d); + iowrite32(0xFFFFFFFF, &u3d->vuc_regs->intrcause); + + if (pdata->phy_deinit) + pdata->phy_deinit(u3d->phy_regs); + clk_disable(u3d->clk); + + size = u3d->max_eps * sizeof(struct mv_u3d_ep_context) * 2; + size = (size + MV_U3D_EP_CONTEXT_ALIGNMENT - 1) + & ~(MV_U3D_EP_CONTEXT_ALIGNMENT - 1); + u3d->ep_context = dma_alloc_coherent(&dev->dev, size, + &u3d->ep_context_dma, GFP_KERNEL); + if (!u3d->ep_context) { + dev_err(&dev->dev, "allocate ep context memory failed\n"); + retval = -ENOMEM; + goto err_alloc_ep_context; + } + u3d->ep_context_size = size; + + /* create TRB dma_pool resource */ + u3d->trb_pool = dma_pool_create("u3d_trb", + &dev->dev, + sizeof(struct mv_u3d_trb_hw), + MV_U3D_TRB_ALIGNMENT, + MV_U3D_DMA_BOUNDARY); + + if (!u3d->trb_pool) { + retval = -ENOMEM; + goto err_alloc_trb_pool; + } + + size = u3d->max_eps * sizeof(struct mv_u3d_ep) * 2; + u3d->eps = kzalloc(size, GFP_KERNEL); + if (!u3d->eps) { + dev_err(&dev->dev, "allocate ep memory failed\n"); + retval = -ENOMEM; + goto err_alloc_eps; + } + + /* initialize ep0 status request structure */ + u3d->status_req = kzalloc(sizeof(struct mv_u3d_req) + 8, GFP_KERNEL); + if (!u3d->status_req) { + dev_err(&dev->dev, "allocate status_req memory failed\n"); + retval = -ENOMEM; + goto err_alloc_status_req; + } + INIT_LIST_HEAD(&u3d->status_req->queue); + + /* allocate a small amount of memory to get valid address */ + u3d->status_req->req.buf = (char *)u3d->status_req + + sizeof(struct mv_u3d_req); + u3d->status_req->req.dma = virt_to_phys(u3d->status_req->req.buf); + + u3d->resume_state = USB_STATE_NOTATTACHED; + u3d->usb_state = USB_STATE_ATTACHED; + u3d->ep0_dir = MV_U3D_EP_DIR_OUT; + u3d->remote_wakeup = 0; + + r = platform_get_resource(dev, IORESOURCE_IRQ, 0); + if (!r) { + dev_err(&dev->dev, "no IRQ resource defined\n"); + retval = -ENODEV; + goto err_get_irq; + } + u3d->irq = r->start; + if (request_irq(u3d->irq, mv_u3d_irq, + IRQF_DISABLED | IRQF_SHARED, driver_name, u3d)) { + u3d->irq = 0; + dev_err(&dev->dev, "Request irq %d for u3d failed\n", + u3d->irq); + retval = -ENODEV; + goto err_request_irq; + } + + /* initialize gadget structure */ + u3d->gadget.ops = &mv_u3d_ops; /* usb_gadget_ops */ + u3d->gadget.ep0 = &u3d->eps[1].ep; /* gadget ep0 */ + INIT_LIST_HEAD(&u3d->gadget.ep_list); /* ep_list */ + u3d->gadget.speed = USB_SPEED_UNKNOWN; /* speed */ + + /* the "gadget" abstracts/virtualizes the controller */ + u3d->gadget.name = driver_name; /* gadget name */ + + mv_u3d_eps_init(u3d); + + /* external vbus detection */ + if (u3d->vbus) { + u3d->clock_gating = 1; + dev_err(&dev->dev, "external vbus detection\n"); + } + + if (!u3d->clock_gating) + u3d->vbus_active = 1; + + /* enable usb3 controller vbus detection */ + u3d->vbus_valid_detect = 1; + + retval = usb_add_gadget_udc(&dev->dev, &u3d->gadget); + if (retval) + goto err_unregister; + + dev_dbg(&dev->dev, "successful probe usb3 device %s clock gating.\n", + u3d->clock_gating ? "with" : "without"); + + return 0; + +err_unregister: + free_irq(u3d->irq, &dev->dev); +err_request_irq: +err_get_irq: + kfree(u3d->status_req); +err_alloc_status_req: + kfree(u3d->eps); +err_alloc_eps: + dma_pool_destroy(u3d->trb_pool); +err_alloc_trb_pool: + dma_free_coherent(&dev->dev, u3d->ep_context_size, + u3d->ep_context, u3d->ep_context_dma); +err_alloc_ep_context: + if (pdata->phy_deinit) + pdata->phy_deinit(u3d->phy_regs); + clk_disable(u3d->clk); +err_u3d_enable: + iounmap(u3d->cap_regs); +err_map_cap_regs: +err_get_cap_regs: +err_get_clk: + clk_put(u3d->clk); + platform_set_drvdata(dev, NULL); + kfree(u3d); +err_alloc_private: +err_pdata: + return retval; +} + +#ifdef CONFIG_PM_SLEEP +static int mv_u3d_suspend(struct device *dev) +{ + struct mv_u3d *u3d = dev_get_drvdata(dev); + + /* + * only cable is unplugged, usb can suspend. + * So do not care about clock_gating == 1, it is handled by + * vbus session. + */ + if (!u3d->clock_gating) { + mv_u3d_controller_stop(u3d); + + spin_lock_irq(&u3d->lock); + /* stop all usb activities */ + mv_u3d_stop_activity(u3d, u3d->driver); + spin_unlock_irq(&u3d->lock); + + mv_u3d_disable(u3d); + } + + return 0; +} + +static int mv_u3d_resume(struct device *dev) +{ + struct mv_u3d *u3d = dev_get_drvdata(dev); + int retval; + + if (!u3d->clock_gating) { + retval = mv_u3d_enable(u3d); + if (retval) + return retval; + + if (u3d->driver && u3d->softconnect) { + mv_u3d_controller_reset(u3d); + mv_u3d_ep0_reset(u3d); + mv_u3d_controller_start(u3d); + } + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(mv_u3d_pm_ops, mv_u3d_suspend, mv_u3d_resume); + +static void mv_u3d_shutdown(struct platform_device *dev) +{ + struct mv_u3d *u3d = dev_get_drvdata(&dev->dev); + u32 tmp; + + tmp = ioread32(&u3d->op_regs->usbcmd); + tmp &= ~MV_U3D_CMD_RUN_STOP; + iowrite32(tmp, &u3d->op_regs->usbcmd); +} + +static struct platform_driver mv_u3d_driver = { + .probe = mv_u3d_probe, + .remove = mv_u3d_remove, + .shutdown = mv_u3d_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "mv-u3d", + .pm = &mv_u3d_pm_ops, + }, +}; + +module_platform_driver(mv_u3d_driver); +MODULE_ALIAS("platform:mv-u3d"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Yu Xu "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/mv_udc.h b/drivers/usb/gadget/mv_udc.h new file mode 100644 index 00000000..be77f207 --- /dev/null +++ b/drivers/usb/gadget/mv_udc.h @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __MV_UDC_H +#define __MV_UDC_H + +#define VUSBHS_MAX_PORTS 8 + +#define DQH_ALIGNMENT 2048 +#define DTD_ALIGNMENT 64 +#define DMA_BOUNDARY 4096 + +#define EP_DIR_IN 1 +#define EP_DIR_OUT 0 + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +#define EP0_MAX_PKT_SIZE 64 +/* ep0 transfer state */ +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 + +#define CAPLENGTH_MASK (0xff) +#define DCCPARAMS_DEN_MASK (0x1f) + +#define HCSPARAMS_PPC (0x10) + +/* Frame Index Register Bit Masks */ +#define USB_FRINDEX_MASKS 0x3fff + +/* Command Register Bit Masks */ +#define USBCMD_RUN_STOP (0x00000001) +#define USBCMD_CTRL_RESET (0x00000002) +#define USBCMD_SETUP_TRIPWIRE_SET (0x00002000) +#define USBCMD_SETUP_TRIPWIRE_CLEAR (~USBCMD_SETUP_TRIPWIRE_SET) + +#define USBCMD_ATDTW_TRIPWIRE_SET (0x00004000) +#define USBCMD_ATDTW_TRIPWIRE_CLEAR (~USBCMD_ATDTW_TRIPWIRE_SET) + +/* bit 15,3,2 are for frame list size */ +#define USBCMD_FRAME_SIZE_1024 (0x00000000) /* 000 */ +#define USBCMD_FRAME_SIZE_512 (0x00000004) /* 001 */ +#define USBCMD_FRAME_SIZE_256 (0x00000008) /* 010 */ +#define USBCMD_FRAME_SIZE_128 (0x0000000C) /* 011 */ +#define USBCMD_FRAME_SIZE_64 (0x00008000) /* 100 */ +#define USBCMD_FRAME_SIZE_32 (0x00008004) /* 101 */ +#define USBCMD_FRAME_SIZE_16 (0x00008008) /* 110 */ +#define USBCMD_FRAME_SIZE_8 (0x0000800C) /* 111 */ + +#define EPCTRL_TX_ALL_MASK (0xFFFF0000) +#define EPCTRL_RX_ALL_MASK (0x0000FFFF) + +#define EPCTRL_TX_DATA_TOGGLE_RST (0x00400000) +#define EPCTRL_TX_EP_STALL (0x00010000) +#define EPCTRL_RX_EP_STALL (0x00000001) +#define EPCTRL_RX_DATA_TOGGLE_RST (0x00000040) +#define EPCTRL_RX_ENABLE (0x00000080) +#define EPCTRL_TX_ENABLE (0x00800000) +#define EPCTRL_CONTROL (0x00000000) +#define EPCTRL_ISOCHRONOUS (0x00040000) +#define EPCTRL_BULK (0x00080000) +#define EPCTRL_INT (0x000C0000) +#define EPCTRL_TX_TYPE (0x000C0000) +#define EPCTRL_RX_TYPE (0x0000000C) +#define EPCTRL_DATA_TOGGLE_INHIBIT (0x00000020) +#define EPCTRL_TX_EP_TYPE_SHIFT (18) +#define EPCTRL_RX_EP_TYPE_SHIFT (2) + +#define EPCOMPLETE_MAX_ENDPOINTS (16) + +/* endpoint list address bit masks */ +#define USB_EP_LIST_ADDRESS_MASK 0xfffff800 + +#define PORTSCX_W1C_BITS 0x2a +#define PORTSCX_PORT_RESET 0x00000100 +#define PORTSCX_PORT_POWER 0x00001000 +#define PORTSCX_FORCE_FULL_SPEED_CONNECT 0x01000000 +#define PORTSCX_PAR_XCVR_SELECT 0xC0000000 +#define PORTSCX_PORT_FORCE_RESUME 0x00000040 +#define PORTSCX_PORT_SUSPEND 0x00000080 +#define PORTSCX_PORT_SPEED_FULL 0x00000000 +#define PORTSCX_PORT_SPEED_LOW 0x04000000 +#define PORTSCX_PORT_SPEED_HIGH 0x08000000 +#define PORTSCX_PORT_SPEED_MASK 0x0C000000 + +/* USB MODE Register Bit Masks */ +#define USBMODE_CTRL_MODE_IDLE 0x00000000 +#define USBMODE_CTRL_MODE_DEVICE 0x00000002 +#define USBMODE_CTRL_MODE_HOST 0x00000003 +#define USBMODE_CTRL_MODE_RSV 0x00000001 +#define USBMODE_SETUP_LOCK_OFF 0x00000008 +#define USBMODE_STREAM_DISABLE 0x00000010 + +/* USB STS Register Bit Masks */ +#define USBSTS_INT 0x00000001 +#define USBSTS_ERR 0x00000002 +#define USBSTS_PORT_CHANGE 0x00000004 +#define USBSTS_FRM_LST_ROLL 0x00000008 +#define USBSTS_SYS_ERR 0x00000010 +#define USBSTS_IAA 0x00000020 +#define USBSTS_RESET 0x00000040 +#define USBSTS_SOF 0x00000080 +#define USBSTS_SUSPEND 0x00000100 +#define USBSTS_HC_HALTED 0x00001000 +#define USBSTS_RCL 0x00002000 +#define USBSTS_PERIODIC_SCHEDULE 0x00004000 +#define USBSTS_ASYNC_SCHEDULE 0x00008000 + + +/* Interrupt Enable Register Bit Masks */ +#define USBINTR_INT_EN (0x00000001) +#define USBINTR_ERR_INT_EN (0x00000002) +#define USBINTR_PORT_CHANGE_DETECT_EN (0x00000004) + +#define USBINTR_ASYNC_ADV_AAE (0x00000020) +#define USBINTR_ASYNC_ADV_AAE_ENABLE (0x00000020) +#define USBINTR_ASYNC_ADV_AAE_DISABLE (0xFFFFFFDF) + +#define USBINTR_RESET_EN (0x00000040) +#define USBINTR_SOF_UFRAME_EN (0x00000080) +#define USBINTR_DEVICE_SUSPEND (0x00000100) + +#define USB_DEVICE_ADDRESS_MASK (0xfe000000) +#define USB_DEVICE_ADDRESS_BIT_SHIFT (25) + +struct mv_cap_regs { + u32 caplength_hciversion; + u32 hcsparams; /* HC structural parameters */ + u32 hccparams; /* HC Capability Parameters*/ + u32 reserved[5]; + u32 dciversion; /* DC version number and reserved 16 bits */ + u32 dccparams; /* DC Capability Parameters */ +}; + +struct mv_op_regs { + u32 usbcmd; /* Command register */ + u32 usbsts; /* Status register */ + u32 usbintr; /* Interrupt enable */ + u32 frindex; /* Frame index */ + u32 reserved1[1]; + u32 deviceaddr; /* Device Address */ + u32 eplistaddr; /* Endpoint List Address */ + u32 ttctrl; /* HOST TT status and control */ + u32 burstsize; /* Programmable Burst Size */ + u32 txfilltuning; /* Host Transmit Pre-Buffer Packet Tuning */ + u32 reserved[4]; + u32 epnak; /* Endpoint NAK */ + u32 epnaken; /* Endpoint NAK Enable */ + u32 configflag; /* Configured Flag register */ + u32 portsc[VUSBHS_MAX_PORTS]; /* Port Status/Control x, x = 1..8 */ + u32 otgsc; + u32 usbmode; /* USB Host/Device mode */ + u32 epsetupstat; /* Endpoint Setup Status */ + u32 epprime; /* Endpoint Initialize */ + u32 epflush; /* Endpoint De-initialize */ + u32 epstatus; /* Endpoint Status */ + u32 epcomplete; /* Endpoint Interrupt On Complete */ + u32 epctrlx[16]; /* Endpoint Control, where x = 0.. 15 */ + u32 mcr; /* Mux Control */ + u32 isr; /* Interrupt Status */ + u32 ier; /* Interrupt Enable */ +}; + +struct mv_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + spinlock_t lock; + struct completion *done; + struct platform_device *dev; + int irq; + + struct mv_cap_regs __iomem *cap_regs; + struct mv_op_regs __iomem *op_regs; + void __iomem *phy_regs; + unsigned int max_eps; + struct mv_dqh *ep_dqh; + size_t ep_dqh_size; + dma_addr_t ep_dqh_dma; + + struct dma_pool *dtd_pool; + struct mv_ep *eps; + + struct mv_dtd *dtd_head; + struct mv_dtd *dtd_tail; + unsigned int dtd_entries; + + struct mv_req *status_req; + struct usb_ctrlrequest local_setup_buff; + + unsigned int resume_state; /* USB state to resume */ + unsigned int usb_state; /* USB current state */ + unsigned int ep0_state; /* Endpoint zero state */ + unsigned int ep0_dir; + + unsigned int dev_addr; + unsigned int test_mode; + + int errors; + unsigned softconnect:1, + vbus_active:1, + remote_wakeup:1, + softconnected:1, + force_fs:1, + clock_gating:1, + active:1, + stopped:1; /* stop bit is setted */ + + struct work_struct vbus_work; + struct workqueue_struct *qwork; + + struct usb_phy *transceiver; + + struct mv_usb_platform_data *pdata; + + /* some SOC has mutiple clock sources for USB*/ + struct clk *clk; +}; + +/* endpoint data structure */ +struct mv_ep { + struct usb_ep ep; + struct mv_udc *udc; + struct list_head queue; + struct mv_dqh *dqh; + u32 direction; + char name[14]; + unsigned stopped:1, + wedge:1, + ep_type:2, + ep_num:8; +}; + +/* request data structure */ +struct mv_req { + struct usb_request req; + struct mv_dtd *dtd, *head, *tail; + struct mv_ep *ep; + struct list_head queue; + unsigned int test_mode; + unsigned dtd_count; + unsigned mapped:1; +}; + +#define EP_QUEUE_HEAD_MULT_POS 30 +#define EP_QUEUE_HEAD_ZLT_SEL 0x20000000 +#define EP_QUEUE_HEAD_MAX_PKT_LEN_POS 16 +#define EP_QUEUE_HEAD_MAX_PKT_LEN(ep_info) (((ep_info)>>16)&0x07ff) +#define EP_QUEUE_HEAD_IOS 0x00008000 +#define EP_QUEUE_HEAD_NEXT_TERMINATE 0x00000001 +#define EP_QUEUE_HEAD_IOC 0x00008000 +#define EP_QUEUE_HEAD_MULTO 0x00000C00 +#define EP_QUEUE_HEAD_STATUS_HALT 0x00000040 +#define EP_QUEUE_HEAD_STATUS_ACTIVE 0x00000080 +#define EP_QUEUE_CURRENT_OFFSET_MASK 0x00000FFF +#define EP_QUEUE_HEAD_NEXT_POINTER_MASK 0xFFFFFFE0 +#define EP_QUEUE_FRINDEX_MASK 0x000007FF +#define EP_MAX_LENGTH_TRANSFER 0x4000 + +struct mv_dqh { + /* Bits 16..26 Bit 15 is Interrupt On Setup */ + u32 max_packet_length; + u32 curr_dtd_ptr; /* Current dTD Pointer */ + u32 next_dtd_ptr; /* Next dTD Pointer */ + /* Total bytes (16..30), IOC (15), INT (8), STS (0-7) */ + u32 size_ioc_int_sts; + u32 buff_ptr0; /* Buffer pointer Page 0 (12-31) */ + u32 buff_ptr1; /* Buffer pointer Page 1 (12-31) */ + u32 buff_ptr2; /* Buffer pointer Page 2 (12-31) */ + u32 buff_ptr3; /* Buffer pointer Page 3 (12-31) */ + u32 buff_ptr4; /* Buffer pointer Page 4 (12-31) */ + u32 reserved1; + /* 8 bytes of setup data that follows the Setup PID */ + u8 setup_buffer[8]; + u32 reserved2[4]; +}; + + +#define DTD_NEXT_TERMINATE (0x00000001) +#define DTD_IOC (0x00008000) +#define DTD_STATUS_ACTIVE (0x00000080) +#define DTD_STATUS_HALTED (0x00000040) +#define DTD_STATUS_DATA_BUFF_ERR (0x00000020) +#define DTD_STATUS_TRANSACTION_ERR (0x00000008) +#define DTD_RESERVED_FIELDS (0x00007F00) +#define DTD_ERROR_MASK (0x68) +#define DTD_ADDR_MASK (0xFFFFFFE0) +#define DTD_PACKET_SIZE 0x7FFF0000 +#define DTD_LENGTH_BIT_POS (16) + +struct mv_dtd { + u32 dtd_next; + u32 size_ioc_sts; + u32 buff_ptr0; /* Buffer pointer Page 0 */ + u32 buff_ptr1; /* Buffer pointer Page 1 */ + u32 buff_ptr2; /* Buffer pointer Page 2 */ + u32 buff_ptr3; /* Buffer pointer Page 3 */ + u32 buff_ptr4; /* Buffer pointer Page 4 */ + u32 scratch_ptr; + /* 32 bytes */ + dma_addr_t td_dma; /* dma address for this td */ + struct mv_dtd *next_dtd_virt; +}; + +#endif diff --git a/drivers/usb/gadget/mv_udc_core.c b/drivers/usb/gadget/mv_udc_core.c new file mode 100644 index 00000000..c2a57023 --- /dev/null +++ b/drivers/usb/gadget/mv_udc_core.c @@ -0,0 +1,2424 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * Author: Chao Xie + * Neil Zhang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mv_udc.h" + +#define DRIVER_DESC "Marvell PXA USB Device Controller driver" +#define DRIVER_VERSION "8 Nov 2010" + +#define ep_dir(ep) (((ep)->ep_num == 0) ? \ + ((ep)->udc->ep0_dir) : ((ep)->direction)) + +/* timeout value -- usec */ +#define RESET_TIMEOUT 10000 +#define FLUSH_TIMEOUT 10000 +#define EPSTATUS_TIMEOUT 10000 +#define PRIME_TIMEOUT 10000 +#define READSAFE_TIMEOUT 1000 + +#define LOOPS_USEC_SHIFT 1 +#define LOOPS_USEC (1 << LOOPS_USEC_SHIFT) +#define LOOPS(timeout) ((timeout) >> LOOPS_USEC_SHIFT) + +static DECLARE_COMPLETION(release_done); + +static const char driver_name[] = "mv_udc"; +static const char driver_desc[] = DRIVER_DESC; + +static void nuke(struct mv_ep *ep, int status); +static void stop_activity(struct mv_udc *udc, struct usb_gadget_driver *driver); + +/* for endpoint 0 operations */ +static const struct usb_endpoint_descriptor mv_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = EP0_MAX_PKT_SIZE, +}; + +static void ep0_reset(struct mv_udc *udc) +{ + struct mv_ep *ep; + u32 epctrlx; + int i = 0; + + /* ep0 in and out */ + for (i = 0; i < 2; i++) { + ep = &udc->eps[i]; + ep->udc = udc; + + /* ep0 dQH */ + ep->dqh = &udc->ep_dqh[i]; + + /* configure ep0 endpoint capabilities in dQH */ + ep->dqh->max_packet_length = + (EP0_MAX_PKT_SIZE << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | EP_QUEUE_HEAD_IOS; + + ep->dqh->next_dtd_ptr = EP_QUEUE_HEAD_NEXT_TERMINATE; + + epctrlx = readl(&udc->op_regs->epctrlx[0]); + if (i) { /* TX */ + epctrlx |= EPCTRL_TX_ENABLE + | (USB_ENDPOINT_XFER_CONTROL + << EPCTRL_TX_EP_TYPE_SHIFT); + + } else { /* RX */ + epctrlx |= EPCTRL_RX_ENABLE + | (USB_ENDPOINT_XFER_CONTROL + << EPCTRL_RX_EP_TYPE_SHIFT); + } + + writel(epctrlx, &udc->op_regs->epctrlx[0]); + } +} + +/* protocol ep0 stall, will automatically be cleared on new transaction */ +static void ep0_stall(struct mv_udc *udc) +{ + u32 epctrlx; + + /* set TX and RX to stall */ + epctrlx = readl(&udc->op_regs->epctrlx[0]); + epctrlx |= EPCTRL_RX_EP_STALL | EPCTRL_TX_EP_STALL; + writel(epctrlx, &udc->op_regs->epctrlx[0]); + + /* update ep0 state */ + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = EP_DIR_OUT; +} + +static int process_ep_req(struct mv_udc *udc, int index, + struct mv_req *curr_req) +{ + struct mv_dtd *curr_dtd; + struct mv_dqh *curr_dqh; + int td_complete, actual, remaining_length; + int i, direction; + int retval = 0; + u32 errors; + u32 bit_pos; + + curr_dqh = &udc->ep_dqh[index]; + direction = index % 2; + + curr_dtd = curr_req->head; + td_complete = 0; + actual = curr_req->req.length; + + for (i = 0; i < curr_req->dtd_count; i++) { + if (curr_dtd->size_ioc_sts & DTD_STATUS_ACTIVE) { + dev_dbg(&udc->dev->dev, "%s, dTD not completed\n", + udc->eps[index].name); + return 1; + } + + errors = curr_dtd->size_ioc_sts & DTD_ERROR_MASK; + if (!errors) { + remaining_length = + (curr_dtd->size_ioc_sts & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + actual -= remaining_length; + + if (remaining_length) { + if (direction) { + dev_dbg(&udc->dev->dev, + "TX dTD remains data\n"); + retval = -EPROTO; + break; + } else + break; + } + } else { + dev_info(&udc->dev->dev, + "complete_tr error: ep=%d %s: error = 0x%x\n", + index >> 1, direction ? "SEND" : "RECV", + errors); + if (errors & DTD_STATUS_HALTED) { + /* Clear the errors and Halt condition */ + curr_dqh->size_ioc_int_sts &= ~errors; + retval = -EPIPE; + } else if (errors & DTD_STATUS_DATA_BUFF_ERR) { + retval = -EPROTO; + } else if (errors & DTD_STATUS_TRANSACTION_ERR) { + retval = -EILSEQ; + } + } + if (i != curr_req->dtd_count - 1) + curr_dtd = (struct mv_dtd *)curr_dtd->next_dtd_virt; + } + if (retval) + return retval; + + if (direction == EP_DIR_OUT) + bit_pos = 1 << curr_req->ep->ep_num; + else + bit_pos = 1 << (16 + curr_req->ep->ep_num); + + while ((curr_dqh->curr_dtd_ptr == curr_dtd->td_dma)) { + if (curr_dtd->dtd_next == EP_QUEUE_HEAD_NEXT_TERMINATE) { + while (readl(&udc->op_regs->epstatus) & bit_pos) + udelay(1); + break; + } + udelay(1); + } + + curr_req->req.actual = actual; + + return 0; +} + +/* + * done() - retire a request; caller blocked irqs + * @status : request status to be set, only works when + * request is still in progress. + */ +static void done(struct mv_ep *ep, struct mv_req *req, int status) + __releases(&ep->udc->lock) + __acquires(&ep->udc->lock) +{ + struct mv_udc *udc = NULL; + unsigned char stopped = ep->stopped; + struct mv_dtd *curr_td, *next_td; + int j; + + udc = (struct mv_udc *)ep->udc; + /* Removed the req from fsl_ep->queue */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + /* Free dtd for the request */ + next_td = req->head; + for (j = 0; j < req->dtd_count; j++) { + curr_td = next_td; + if (j != req->dtd_count - 1) + next_td = curr_td->next_dtd_virt; + dma_pool_free(udc->dtd_pool, curr_td, curr_td->td_dma); + } + + usb_gadget_unmap_request(&udc->gadget, &req->req, ep_dir(ep)); + + if (status && (status != -ESHUTDOWN)) + dev_info(&udc->dev->dev, "complete %s req %p stat %d len %u/%u", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + ep->stopped = 1; + + spin_unlock(&ep->udc->lock); + /* + * complete() is from gadget layer, + * eg fsg->bulk_in_complete() + */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&ep->udc->lock); + ep->stopped = stopped; +} + +static int queue_dtd(struct mv_ep *ep, struct mv_req *req) +{ + struct mv_udc *udc; + struct mv_dqh *dqh; + u32 bit_pos, direction; + u32 usbcmd, epstatus; + unsigned int loops; + int retval = 0; + + udc = ep->udc; + direction = ep_dir(ep); + dqh = &(udc->ep_dqh[ep->ep_num * 2 + direction]); + bit_pos = 1 << (((direction == EP_DIR_OUT) ? 0 : 16) + ep->ep_num); + + /* check if the pipe is empty */ + if (!(list_empty(&ep->queue))) { + struct mv_req *lastreq; + lastreq = list_entry(ep->queue.prev, struct mv_req, queue); + lastreq->tail->dtd_next = + req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK; + + wmb(); + + if (readl(&udc->op_regs->epprime) & bit_pos) + goto done; + + loops = LOOPS(READSAFE_TIMEOUT); + while (1) { + /* start with setting the semaphores */ + usbcmd = readl(&udc->op_regs->usbcmd); + usbcmd |= USBCMD_ATDTW_TRIPWIRE_SET; + writel(usbcmd, &udc->op_regs->usbcmd); + + /* read the endpoint status */ + epstatus = readl(&udc->op_regs->epstatus) & bit_pos; + + /* + * Reread the ATDTW semaphore bit to check if it is + * cleared. When hardware see a hazard, it will clear + * the bit or else we remain set to 1 and we can + * proceed with priming of endpoint if not already + * primed. + */ + if (readl(&udc->op_regs->usbcmd) + & USBCMD_ATDTW_TRIPWIRE_SET) + break; + + loops--; + if (loops == 0) { + dev_err(&udc->dev->dev, + "Timeout for ATDTW_TRIPWIRE...\n"); + retval = -ETIME; + goto done; + } + udelay(LOOPS_USEC); + } + + /* Clear the semaphore */ + usbcmd = readl(&udc->op_regs->usbcmd); + usbcmd &= USBCMD_ATDTW_TRIPWIRE_CLEAR; + writel(usbcmd, &udc->op_regs->usbcmd); + + if (epstatus) + goto done; + } + + /* Write dQH next pointer and terminate bit to 0 */ + dqh->next_dtd_ptr = req->head->td_dma + & EP_QUEUE_HEAD_NEXT_POINTER_MASK; + + /* clear active and halt bit, in case set from a previous error */ + dqh->size_ioc_int_sts &= ~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED); + + /* Ensure that updates to the QH will occure before priming. */ + wmb(); + + /* Prime the Endpoint */ + writel(bit_pos, &udc->op_regs->epprime); + +done: + return retval; +} + +static struct mv_dtd *build_dtd(struct mv_req *req, unsigned *length, + dma_addr_t *dma, int *is_last) +{ + struct mv_dtd *dtd; + struct mv_udc *udc; + struct mv_dqh *dqh; + u32 temp, mult = 0; + + /* how big will this transfer be? */ + if (usb_endpoint_xfer_isoc(req->ep->ep.desc)) { + dqh = req->ep->dqh; + mult = (dqh->max_packet_length >> EP_QUEUE_HEAD_MULT_POS) + & 0x3; + *length = min(req->req.length - req->req.actual, + (unsigned)(mult * req->ep->ep.maxpacket)); + } else + *length = min(req->req.length - req->req.actual, + (unsigned)EP_MAX_LENGTH_TRANSFER); + + udc = req->ep->udc; + + /* + * Be careful that no _GFP_HIGHMEM is set, + * or we can not use dma_to_virt + */ + dtd = dma_pool_alloc(udc->dtd_pool, GFP_ATOMIC, dma); + if (dtd == NULL) + return dtd; + + dtd->td_dma = *dma; + /* initialize buffer page pointers */ + temp = (u32)(req->req.dma + req->req.actual); + dtd->buff_ptr0 = cpu_to_le32(temp); + temp &= ~0xFFF; + dtd->buff_ptr1 = cpu_to_le32(temp + 0x1000); + dtd->buff_ptr2 = cpu_to_le32(temp + 0x2000); + dtd->buff_ptr3 = cpu_to_le32(temp + 0x3000); + dtd->buff_ptr4 = cpu_to_le32(temp + 0x4000); + + req->req.actual += *length; + + /* zlp is needed if req->req.zero is set */ + if (req->req.zero) { + if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) + *is_last = 1; + else + *is_last = 0; + } else if (req->req.length == req->req.actual) + *is_last = 1; + else + *is_last = 0; + + /* Fill in the transfer size; set active bit */ + temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); + + /* Enable interrupt for the last dtd of a request */ + if (*is_last && !req->req.no_interrupt) + temp |= DTD_IOC; + + temp |= mult << 10; + + dtd->size_ioc_sts = temp; + + mb(); + + return dtd; +} + +/* generate dTD linked list for a request */ +static int req_to_dtd(struct mv_req *req) +{ + unsigned count; + int is_last, is_first = 1; + struct mv_dtd *dtd, *last_dtd = NULL; + struct mv_udc *udc; + dma_addr_t dma; + + udc = req->ep->udc; + + do { + dtd = build_dtd(req, &count, &dma, &is_last); + if (dtd == NULL) + return -ENOMEM; + + if (is_first) { + is_first = 0; + req->head = dtd; + } else { + last_dtd->dtd_next = dma; + last_dtd->next_dtd_virt = dtd; + } + last_dtd = dtd; + req->dtd_count++; + } while (!is_last); + + /* set terminate bit to 1 for the last dTD */ + dtd->dtd_next = DTD_NEXT_TERMINATE; + + req->tail = dtd; + + return 0; +} + +static int mv_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct mv_udc *udc; + struct mv_ep *ep; + struct mv_dqh *dqh; + u16 max = 0; + u32 bit_pos, epctrlx, direction; + unsigned char zlt = 0, ios = 0, mult = 0; + unsigned long flags; + + ep = container_of(_ep, struct mv_ep, ep); + udc = ep->udc; + + if (!_ep || !desc + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + direction = ep_dir(ep); + max = usb_endpoint_maxp(desc); + + /* + * disable HW zero length termination select + * driver handles zero length packet through req->req.zero + */ + zlt = 1; + + bit_pos = 1 << ((direction == EP_DIR_OUT ? 0 : 16) + ep->ep_num); + + /* Check if the Endpoint is Primed */ + if ((readl(&udc->op_regs->epprime) & bit_pos) + || (readl(&udc->op_regs->epstatus) & bit_pos)) { + dev_info(&udc->dev->dev, + "ep=%d %s: Init ERROR: ENDPTPRIME=0x%x," + " ENDPTSTATUS=0x%x, bit_pos=0x%x\n", + (unsigned)ep->ep_num, direction ? "SEND" : "RECV", + (unsigned)readl(&udc->op_regs->epprime), + (unsigned)readl(&udc->op_regs->epstatus), + (unsigned)bit_pos); + goto en_done; + } + /* Set the max packet length, interrupt on Setup and Mult fields */ + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + zlt = 1; + mult = 0; + break; + case USB_ENDPOINT_XFER_CONTROL: + ios = 1; + case USB_ENDPOINT_XFER_INT: + mult = 0; + break; + case USB_ENDPOINT_XFER_ISOC: + /* Calculate transactions needed for high bandwidth iso */ + mult = (unsigned char)(1 + ((max >> 11) & 0x03)); + max = max & 0x7ff; /* bit 0~10 */ + /* 3 transactions at most */ + if (mult > 3) + goto en_done; + break; + default: + goto en_done; + } + + spin_lock_irqsave(&udc->lock, flags); + /* Get the endpoint queue head address */ + dqh = ep->dqh; + dqh->max_packet_length = (max << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | (mult << EP_QUEUE_HEAD_MULT_POS) + | (zlt ? EP_QUEUE_HEAD_ZLT_SEL : 0) + | (ios ? EP_QUEUE_HEAD_IOS : 0); + dqh->next_dtd_ptr = 1; + dqh->size_ioc_int_sts = 0; + + ep->ep.maxpacket = max; + ep->ep.desc = desc; + ep->stopped = 0; + + /* Enable the endpoint for Rx or Tx and set the endpoint type */ + epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); + if (direction == EP_DIR_IN) { + epctrlx &= ~EPCTRL_TX_ALL_MASK; + epctrlx |= EPCTRL_TX_ENABLE | EPCTRL_TX_DATA_TOGGLE_RST + | ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + << EPCTRL_TX_EP_TYPE_SHIFT); + } else { + epctrlx &= ~EPCTRL_RX_ALL_MASK; + epctrlx |= EPCTRL_RX_ENABLE | EPCTRL_RX_DATA_TOGGLE_RST + | ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + << EPCTRL_RX_EP_TYPE_SHIFT); + } + writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); + + /* + * Implement Guideline (GL# USB-7) The unused endpoint type must + * be programmed to bulk. + */ + epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); + if ((epctrlx & EPCTRL_RX_ENABLE) == 0) { + epctrlx |= (USB_ENDPOINT_XFER_BULK + << EPCTRL_RX_EP_TYPE_SHIFT); + writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); + } + + epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); + if ((epctrlx & EPCTRL_TX_ENABLE) == 0) { + epctrlx |= (USB_ENDPOINT_XFER_BULK + << EPCTRL_TX_EP_TYPE_SHIFT); + writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +en_done: + return -EINVAL; +} + +static int mv_ep_disable(struct usb_ep *_ep) +{ + struct mv_udc *udc; + struct mv_ep *ep; + struct mv_dqh *dqh; + u32 bit_pos, epctrlx, direction; + unsigned long flags; + + ep = container_of(_ep, struct mv_ep, ep); + if ((_ep == NULL) || !ep->ep.desc) + return -EINVAL; + + udc = ep->udc; + + /* Get the endpoint queue head address */ + dqh = ep->dqh; + + spin_lock_irqsave(&udc->lock, flags); + + direction = ep_dir(ep); + bit_pos = 1 << ((direction == EP_DIR_OUT ? 0 : 16) + ep->ep_num); + + /* Reset the max packet length and the interrupt on Setup */ + dqh->max_packet_length = 0; + + /* Disable the endpoint for Rx or Tx and reset the endpoint type */ + epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); + epctrlx &= ~((direction == EP_DIR_IN) + ? (EPCTRL_TX_ENABLE | EPCTRL_TX_TYPE) + : (EPCTRL_RX_ENABLE | EPCTRL_RX_TYPE)); + writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); + + /* nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + ep->ep.desc = NULL; + ep->stopped = 1; + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request * +mv_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct mv_req *req = NULL; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + req->req.dma = DMA_ADDR_INVALID; + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void mv_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct mv_req *req = NULL; + + req = container_of(_req, struct mv_req, req); + + if (_req) + kfree(req); +} + +static void mv_ep_fifo_flush(struct usb_ep *_ep) +{ + struct mv_udc *udc; + u32 bit_pos, direction; + struct mv_ep *ep; + unsigned int loops; + + if (!_ep) + return; + + ep = container_of(_ep, struct mv_ep, ep); + if (!ep->ep.desc) + return; + + udc = ep->udc; + direction = ep_dir(ep); + + if (ep->ep_num == 0) + bit_pos = (1 << 16) | 1; + else if (direction == EP_DIR_OUT) + bit_pos = 1 << ep->ep_num; + else + bit_pos = 1 << (16 + ep->ep_num); + + loops = LOOPS(EPSTATUS_TIMEOUT); + do { + unsigned int inter_loops; + + if (loops == 0) { + dev_err(&udc->dev->dev, + "TIMEOUT for ENDPTSTATUS=0x%x, bit_pos=0x%x\n", + (unsigned)readl(&udc->op_regs->epstatus), + (unsigned)bit_pos); + return; + } + /* Write 1 to the Flush register */ + writel(bit_pos, &udc->op_regs->epflush); + + /* Wait until flushing completed */ + inter_loops = LOOPS(FLUSH_TIMEOUT); + while (readl(&udc->op_regs->epflush)) { + /* + * ENDPTFLUSH bit should be cleared to indicate this + * operation is complete + */ + if (inter_loops == 0) { + dev_err(&udc->dev->dev, + "TIMEOUT for ENDPTFLUSH=0x%x," + "bit_pos=0x%x\n", + (unsigned)readl(&udc->op_regs->epflush), + (unsigned)bit_pos); + return; + } + inter_loops--; + udelay(LOOPS_USEC); + } + loops--; + } while (readl(&udc->op_regs->epstatus) & bit_pos); +} + +/* queues (submits) an I/O request to an endpoint */ +static int +mv_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct mv_ep *ep = container_of(_ep, struct mv_ep, ep); + struct mv_req *req = container_of(_req, struct mv_req, req); + struct mv_udc *udc = ep->udc; + unsigned long flags; + int retval; + + /* catch various bogus parameters */ + if (!_req || !req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + dev_err(&udc->dev->dev, "%s, bad params", __func__); + return -EINVAL; + } + if (unlikely(!_ep || !ep->ep.desc)) { + dev_err(&udc->dev->dev, "%s, bad ep", __func__); + return -EINVAL; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + req->ep = ep; + + /* map virtual address to hardware */ + retval = usb_gadget_map_request(&udc->gadget, _req, ep_dir(ep)); + if (retval) + return retval; + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->dtd_count = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* build dtds and push them to device queue */ + if (!req_to_dtd(req)) { + retval = queue_dtd(ep, req); + if (retval) { + spin_unlock_irqrestore(&udc->lock, flags); + dev_err(&udc->dev->dev, "Failed to queue dtd\n"); + goto err_unmap_dma; + } + } else { + spin_unlock_irqrestore(&udc->lock, flags); + dev_err(&udc->dev->dev, "Failed to dma_pool_alloc\n"); + retval = -ENOMEM; + goto err_unmap_dma; + } + + /* Update ep0 state */ + if (ep->ep_num == 0) + udc->ep0_state = DATA_STATE_XMIT; + + /* irq handler advances the queue */ + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; + +err_unmap_dma: + usb_gadget_unmap_request(&udc->gadget, _req, ep_dir(ep)); + + return retval; +} + +static void mv_prime_ep(struct mv_ep *ep, struct mv_req *req) +{ + struct mv_dqh *dqh = ep->dqh; + u32 bit_pos; + + /* Write dQH next pointer and terminate bit to 0 */ + dqh->next_dtd_ptr = req->head->td_dma + & EP_QUEUE_HEAD_NEXT_POINTER_MASK; + + /* clear active and halt bit, in case set from a previous error */ + dqh->size_ioc_int_sts &= ~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED); + + /* Ensure that updates to the QH will occure before priming. */ + wmb(); + + bit_pos = 1 << (((ep_dir(ep) == EP_DIR_OUT) ? 0 : 16) + ep->ep_num); + + /* Prime the Endpoint */ + writel(bit_pos, &ep->udc->op_regs->epprime); +} + +/* dequeues (cancels, unlinks) an I/O request from an endpoint */ +static int mv_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct mv_ep *ep = container_of(_ep, struct mv_ep, ep); + struct mv_req *req; + struct mv_udc *udc = ep->udc; + unsigned long flags; + int stopped, ret = 0; + u32 epctrlx; + + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + stopped = ep->stopped; + + /* Stop the ep before we deal with the queue */ + ep->stopped = 1; + epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); + if (ep_dir(ep) == EP_DIR_IN) + epctrlx &= ~EPCTRL_TX_ENABLE; + else + epctrlx &= ~EPCTRL_RX_ENABLE; + writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + ret = -EINVAL; + goto out; + } + + /* The request is in progress, or completed but not dequeued */ + if (ep->queue.next == &req->queue) { + _req->status = -ECONNRESET; + mv_ep_fifo_flush(_ep); /* flush current transfer */ + + /* The request isn't the last request in this ep queue */ + if (req->queue.next != &ep->queue) { + struct mv_req *next_req; + + next_req = list_entry(req->queue.next, + struct mv_req, queue); + + /* Point the QH to the first TD of next request */ + mv_prime_ep(ep, next_req); + } else { + struct mv_dqh *qh; + + qh = ep->dqh; + qh->next_dtd_ptr = 1; + qh->size_ioc_int_sts = 0; + } + + /* The request hasn't been processed, patch up the TD chain */ + } else { + struct mv_req *prev_req; + + prev_req = list_entry(req->queue.prev, struct mv_req, queue); + writel(readl(&req->tail->dtd_next), + &prev_req->tail->dtd_next); + + } + + done(ep, req, -ECONNRESET); + + /* Enable EP */ +out: + epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]); + if (ep_dir(ep) == EP_DIR_IN) + epctrlx |= EPCTRL_TX_ENABLE; + else + epctrlx |= EPCTRL_RX_ENABLE; + writel(epctrlx, &udc->op_regs->epctrlx[ep->ep_num]); + ep->stopped = stopped; + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +static void ep_set_stall(struct mv_udc *udc, u8 ep_num, u8 direction, int stall) +{ + u32 epctrlx; + + epctrlx = readl(&udc->op_regs->epctrlx[ep_num]); + + if (stall) { + if (direction == EP_DIR_IN) + epctrlx |= EPCTRL_TX_EP_STALL; + else + epctrlx |= EPCTRL_RX_EP_STALL; + } else { + if (direction == EP_DIR_IN) { + epctrlx &= ~EPCTRL_TX_EP_STALL; + epctrlx |= EPCTRL_TX_DATA_TOGGLE_RST; + } else { + epctrlx &= ~EPCTRL_RX_EP_STALL; + epctrlx |= EPCTRL_RX_DATA_TOGGLE_RST; + } + } + writel(epctrlx, &udc->op_regs->epctrlx[ep_num]); +} + +static int ep_is_stall(struct mv_udc *udc, u8 ep_num, u8 direction) +{ + u32 epctrlx; + + epctrlx = readl(&udc->op_regs->epctrlx[ep_num]); + + if (direction == EP_DIR_OUT) + return (epctrlx & EPCTRL_RX_EP_STALL) ? 1 : 0; + else + return (epctrlx & EPCTRL_TX_EP_STALL) ? 1 : 0; +} + +static int mv_ep_set_halt_wedge(struct usb_ep *_ep, int halt, int wedge) +{ + struct mv_ep *ep; + unsigned long flags = 0; + int status = 0; + struct mv_udc *udc; + + ep = container_of(_ep, struct mv_ep, ep); + udc = ep->udc; + if (!_ep || !ep->ep.desc) { + status = -EINVAL; + goto out; + } + + if (ep->ep.desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + status = -EOPNOTSUPP; + goto out; + } + + /* + * Attempt to halt IN ep will fail if any transfer requests + * are still queue + */ + if (halt && (ep_dir(ep) == EP_DIR_IN) && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + spin_lock_irqsave(&ep->udc->lock, flags); + ep_set_stall(udc, ep->ep_num, ep_dir(ep), halt); + if (halt && wedge) + ep->wedge = 1; + else if (!halt) + ep->wedge = 0; + spin_unlock_irqrestore(&ep->udc->lock, flags); + + if (ep->ep_num == 0) { + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = EP_DIR_OUT; + } +out: + return status; +} + +static int mv_ep_set_halt(struct usb_ep *_ep, int halt) +{ + return mv_ep_set_halt_wedge(_ep, halt, 0); +} + +static int mv_ep_set_wedge(struct usb_ep *_ep) +{ + return mv_ep_set_halt_wedge(_ep, 1, 1); +} + +static struct usb_ep_ops mv_ep_ops = { + .enable = mv_ep_enable, + .disable = mv_ep_disable, + + .alloc_request = mv_alloc_request, + .free_request = mv_free_request, + + .queue = mv_ep_queue, + .dequeue = mv_ep_dequeue, + + .set_wedge = mv_ep_set_wedge, + .set_halt = mv_ep_set_halt, + .fifo_flush = mv_ep_fifo_flush, /* flush fifo */ +}; + +static void udc_clock_enable(struct mv_udc *udc) +{ + clk_prepare_enable(udc->clk); +} + +static void udc_clock_disable(struct mv_udc *udc) +{ + clk_disable_unprepare(udc->clk); +} + +static void udc_stop(struct mv_udc *udc) +{ + u32 tmp; + + /* Disable interrupts */ + tmp = readl(&udc->op_regs->usbintr); + tmp &= ~(USBINTR_INT_EN | USBINTR_ERR_INT_EN | + USBINTR_PORT_CHANGE_DETECT_EN | USBINTR_RESET_EN); + writel(tmp, &udc->op_regs->usbintr); + + udc->stopped = 1; + + /* Reset the Run the bit in the command register to stop VUSB */ + tmp = readl(&udc->op_regs->usbcmd); + tmp &= ~USBCMD_RUN_STOP; + writel(tmp, &udc->op_regs->usbcmd); +} + +static void udc_start(struct mv_udc *udc) +{ + u32 usbintr; + + usbintr = USBINTR_INT_EN | USBINTR_ERR_INT_EN + | USBINTR_PORT_CHANGE_DETECT_EN + | USBINTR_RESET_EN | USBINTR_DEVICE_SUSPEND; + /* Enable interrupts */ + writel(usbintr, &udc->op_regs->usbintr); + + udc->stopped = 0; + + /* Set the Run bit in the command register */ + writel(USBCMD_RUN_STOP, &udc->op_regs->usbcmd); +} + +static int udc_reset(struct mv_udc *udc) +{ + unsigned int loops; + u32 tmp, portsc; + + /* Stop the controller */ + tmp = readl(&udc->op_regs->usbcmd); + tmp &= ~USBCMD_RUN_STOP; + writel(tmp, &udc->op_regs->usbcmd); + + /* Reset the controller to get default values */ + writel(USBCMD_CTRL_RESET, &udc->op_regs->usbcmd); + + /* wait for reset to complete */ + loops = LOOPS(RESET_TIMEOUT); + while (readl(&udc->op_regs->usbcmd) & USBCMD_CTRL_RESET) { + if (loops == 0) { + dev_err(&udc->dev->dev, + "Wait for RESET completed TIMEOUT\n"); + return -ETIMEDOUT; + } + loops--; + udelay(LOOPS_USEC); + } + + /* set controller to device mode */ + tmp = readl(&udc->op_regs->usbmode); + tmp |= USBMODE_CTRL_MODE_DEVICE; + + /* turn setup lockout off, require setup tripwire in usbcmd */ + tmp |= USBMODE_SETUP_LOCK_OFF; + + writel(tmp, &udc->op_regs->usbmode); + + writel(0x0, &udc->op_regs->epsetupstat); + + /* Configure the Endpoint List Address */ + writel(udc->ep_dqh_dma & USB_EP_LIST_ADDRESS_MASK, + &udc->op_regs->eplistaddr); + + portsc = readl(&udc->op_regs->portsc[0]); + if (readl(&udc->cap_regs->hcsparams) & HCSPARAMS_PPC) + portsc &= (~PORTSCX_W1C_BITS | ~PORTSCX_PORT_POWER); + + if (udc->force_fs) + portsc |= PORTSCX_FORCE_FULL_SPEED_CONNECT; + else + portsc &= (~PORTSCX_FORCE_FULL_SPEED_CONNECT); + + writel(portsc, &udc->op_regs->portsc[0]); + + tmp = readl(&udc->op_regs->epctrlx[0]); + tmp &= ~(EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL); + writel(tmp, &udc->op_regs->epctrlx[0]); + + return 0; +} + +static int mv_udc_enable_internal(struct mv_udc *udc) +{ + int retval; + + if (udc->active) + return 0; + + dev_dbg(&udc->dev->dev, "enable udc\n"); + udc_clock_enable(udc); + if (udc->pdata->phy_init) { + retval = udc->pdata->phy_init(udc->phy_regs); + if (retval) { + dev_err(&udc->dev->dev, + "init phy error %d\n", retval); + udc_clock_disable(udc); + return retval; + } + } + udc->active = 1; + + return 0; +} + +static int mv_udc_enable(struct mv_udc *udc) +{ + if (udc->clock_gating) + return mv_udc_enable_internal(udc); + + return 0; +} + +static void mv_udc_disable_internal(struct mv_udc *udc) +{ + if (udc->active) { + dev_dbg(&udc->dev->dev, "disable udc\n"); + if (udc->pdata->phy_deinit) + udc->pdata->phy_deinit(udc->phy_regs); + udc_clock_disable(udc); + udc->active = 0; + } +} + +static void mv_udc_disable(struct mv_udc *udc) +{ + if (udc->clock_gating) + mv_udc_disable_internal(udc); +} + +static int mv_udc_get_frame(struct usb_gadget *gadget) +{ + struct mv_udc *udc; + u16 retval; + + if (!gadget) + return -ENODEV; + + udc = container_of(gadget, struct mv_udc, gadget); + + retval = readl(&udc->op_regs->frindex) & USB_FRINDEX_MASKS; + + return retval; +} + +/* Tries to wake up the host connected to this gadget */ +static int mv_udc_wakeup(struct usb_gadget *gadget) +{ + struct mv_udc *udc = container_of(gadget, struct mv_udc, gadget); + u32 portsc; + + /* Remote wakeup feature not enabled by host */ + if (!udc->remote_wakeup) + return -ENOTSUPP; + + portsc = readl(&udc->op_regs->portsc); + /* not suspended? */ + if (!(portsc & PORTSCX_PORT_SUSPEND)) + return 0; + /* trigger force resume */ + portsc |= PORTSCX_PORT_FORCE_RESUME; + writel(portsc, &udc->op_regs->portsc[0]); + return 0; +} + +static int mv_udc_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct mv_udc *udc; + unsigned long flags; + int retval = 0; + + udc = container_of(gadget, struct mv_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + + udc->vbus_active = (is_active != 0); + + dev_dbg(&udc->dev->dev, "%s: softconnect %d, vbus_active %d\n", + __func__, udc->softconnect, udc->vbus_active); + + if (udc->driver && udc->softconnect && udc->vbus_active) { + retval = mv_udc_enable(udc); + if (retval == 0) { + /* Clock is disabled, need re-init registers */ + udc_reset(udc); + ep0_reset(udc); + udc_start(udc); + } + } else if (udc->driver && udc->softconnect) { + if (!udc->active) + goto out; + + /* stop all the transfer in queue*/ + stop_activity(udc, udc->driver); + udc_stop(udc); + mv_udc_disable(udc); + } + +out: + spin_unlock_irqrestore(&udc->lock, flags); + return retval; +} + +static int mv_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct mv_udc *udc; + unsigned long flags; + int retval = 0; + + udc = container_of(gadget, struct mv_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + + udc->softconnect = (is_on != 0); + + dev_dbg(&udc->dev->dev, "%s: softconnect %d, vbus_active %d\n", + __func__, udc->softconnect, udc->vbus_active); + + if (udc->driver && udc->softconnect && udc->vbus_active) { + retval = mv_udc_enable(udc); + if (retval == 0) { + /* Clock is disabled, need re-init registers */ + udc_reset(udc); + ep0_reset(udc); + udc_start(udc); + } + } else if (udc->driver && udc->vbus_active) { + /* stop all the transfer in queue*/ + stop_activity(udc, udc->driver); + udc_stop(udc); + mv_udc_disable(udc); + } + + spin_unlock_irqrestore(&udc->lock, flags); + return retval; +} + +static int mv_udc_start(struct usb_gadget *, struct usb_gadget_driver *); +static int mv_udc_stop(struct usb_gadget *, struct usb_gadget_driver *); +/* device controller usb_gadget_ops structure */ +static const struct usb_gadget_ops mv_ops = { + + /* returns the current frame number */ + .get_frame = mv_udc_get_frame, + + /* tries to wake up the host connected to this gadget */ + .wakeup = mv_udc_wakeup, + + /* notify controller that VBUS is powered or not */ + .vbus_session = mv_udc_vbus_session, + + /* D+ pullup, software-controlled connect/disconnect to USB host */ + .pullup = mv_udc_pullup, + .udc_start = mv_udc_start, + .udc_stop = mv_udc_stop, +}; + +static int eps_init(struct mv_udc *udc) +{ + struct mv_ep *ep; + char name[14]; + int i; + + /* initialize ep0 */ + ep = &udc->eps[0]; + ep->udc = udc; + strncpy(ep->name, "ep0", sizeof(ep->name)); + ep->ep.name = ep->name; + ep->ep.ops = &mv_ep_ops; + ep->wedge = 0; + ep->stopped = 0; + ep->ep.maxpacket = EP0_MAX_PKT_SIZE; + ep->ep_num = 0; + ep->ep.desc = &mv_ep0_desc; + INIT_LIST_HEAD(&ep->queue); + + ep->ep_type = USB_ENDPOINT_XFER_CONTROL; + + /* initialize other endpoints */ + for (i = 2; i < udc->max_eps * 2; i++) { + ep = &udc->eps[i]; + if (i % 2) { + snprintf(name, sizeof(name), "ep%din", i / 2); + ep->direction = EP_DIR_IN; + } else { + snprintf(name, sizeof(name), "ep%dout", i / 2); + ep->direction = EP_DIR_OUT; + } + ep->udc = udc; + strncpy(ep->name, name, sizeof(ep->name)); + ep->ep.name = ep->name; + + ep->ep.ops = &mv_ep_ops; + ep->stopped = 0; + ep->ep.maxpacket = (unsigned short) ~0; + ep->ep_num = i / 2; + + INIT_LIST_HEAD(&ep->queue); + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + + ep->dqh = &udc->ep_dqh[i]; + } + + return 0; +} + +/* delete all endpoint requests, called with spinlock held */ +static void nuke(struct mv_ep *ep, int status) +{ + /* called with spinlock held */ + ep->stopped = 1; + + /* endpoint fifo flush */ + mv_ep_fifo_flush(&ep->ep); + + while (!list_empty(&ep->queue)) { + struct mv_req *req = NULL; + req = list_entry(ep->queue.next, struct mv_req, queue); + done(ep, req, status); + } +} + +/* stop all USB activities */ +static void stop_activity(struct mv_udc *udc, struct usb_gadget_driver *driver) +{ + struct mv_ep *ep; + + nuke(&udc->eps[0], -ESHUTDOWN); + + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&udc->lock); + driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } +} + +static int mv_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct mv_udc *udc; + int retval = 0; + unsigned long flags; + + udc = container_of(gadget, struct mv_udc, gadget); + + if (udc->driver) + return -EBUSY; + + spin_lock_irqsave(&udc->lock, flags); + + /* hook up the driver ... */ + driver->driver.bus = NULL; + udc->driver = driver; + + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = EP_DIR_OUT; + + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->transceiver) { + retval = otg_set_peripheral(udc->transceiver->otg, + &udc->gadget); + if (retval) { + dev_err(&udc->dev->dev, + "unable to register peripheral to otg\n"); + udc->driver = NULL; + return retval; + } + } + + /* pullup is always on */ + mv_udc_pullup(&udc->gadget, 1); + + /* When boot with cable attached, there will be no vbus irq occurred */ + if (udc->qwork) + queue_work(udc->qwork, &udc->vbus_work); + + return 0; +} + +static int mv_udc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct mv_udc *udc; + unsigned long flags; + + udc = container_of(gadget, struct mv_udc, gadget); + + spin_lock_irqsave(&udc->lock, flags); + + mv_udc_enable(udc); + udc_stop(udc); + + /* stop all usb activities */ + udc->gadget.speed = USB_SPEED_UNKNOWN; + stop_activity(udc, driver); + mv_udc_disable(udc); + + spin_unlock_irqrestore(&udc->lock, flags); + + /* unbind gadget driver */ + udc->driver = NULL; + + return 0; +} + +static void mv_set_ptc(struct mv_udc *udc, u32 mode) +{ + u32 portsc; + + portsc = readl(&udc->op_regs->portsc[0]); + portsc |= mode << 16; + writel(portsc, &udc->op_regs->portsc[0]); +} + +static void prime_status_complete(struct usb_ep *ep, struct usb_request *_req) +{ + struct mv_ep *mvep = container_of(ep, struct mv_ep, ep); + struct mv_req *req = container_of(_req, struct mv_req, req); + struct mv_udc *udc; + unsigned long flags; + + udc = mvep->udc; + + dev_info(&udc->dev->dev, "switch to test mode %d\n", req->test_mode); + + spin_lock_irqsave(&udc->lock, flags); + if (req->test_mode) { + mv_set_ptc(udc, req->test_mode); + req->test_mode = 0; + } + spin_unlock_irqrestore(&udc->lock, flags); +} + +static int +udc_prime_status(struct mv_udc *udc, u8 direction, u16 status, bool empty) +{ + int retval = 0; + struct mv_req *req; + struct mv_ep *ep; + + ep = &udc->eps[0]; + udc->ep0_dir = direction; + udc->ep0_state = WAIT_FOR_OUT_STATUS; + + req = udc->status_req; + + /* fill in the reqest structure */ + if (empty == false) { + *((u16 *) req->req.buf) = cpu_to_le16(status); + req->req.length = 2; + } else + req->req.length = 0; + + req->ep = ep; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + if (udc->test_mode) { + req->req.complete = prime_status_complete; + req->test_mode = udc->test_mode; + udc->test_mode = 0; + } else + req->req.complete = NULL; + req->dtd_count = 0; + + if (req->req.dma == DMA_ADDR_INVALID) { + req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, + req->req.buf, req->req.length, + ep_dir(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + req->mapped = 1; + } + + /* prime the data phase */ + if (!req_to_dtd(req)) { + retval = queue_dtd(ep, req); + if (retval) { + dev_err(&udc->dev->dev, + "Failed to queue dtd when prime status\n"); + goto out; + } + } else{ /* no mem */ + retval = -ENOMEM; + dev_err(&udc->dev->dev, + "Failed to dma_pool_alloc when prime status\n"); + goto out; + } + + list_add_tail(&req->queue, &ep->queue); + + return 0; +out: + usb_gadget_unmap_request(&udc->gadget, &req->req, ep_dir(ep)); + + return retval; +} + +static void mv_udc_testmode(struct mv_udc *udc, u16 index) +{ + if (index <= TEST_FORCE_EN) { + udc->test_mode = index; + if (udc_prime_status(udc, EP_DIR_IN, 0, true)) + ep0_stall(udc); + } else + dev_err(&udc->dev->dev, + "This test mode(%d) is not supported\n", index); +} + +static void ch9setaddress(struct mv_udc *udc, struct usb_ctrlrequest *setup) +{ + udc->dev_addr = (u8)setup->wValue; + + /* update usb state */ + udc->usb_state = USB_STATE_ADDRESS; + + if (udc_prime_status(udc, EP_DIR_IN, 0, true)) + ep0_stall(udc); +} + +static void ch9getstatus(struct mv_udc *udc, u8 ep_num, + struct usb_ctrlrequest *setup) +{ + u16 status = 0; + int retval; + + if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + return; + + if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + status = 1 << USB_DEVICE_SELF_POWERED; + status |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; + } else if ((setup->bRequestType & USB_RECIP_MASK) + == USB_RECIP_INTERFACE) { + /* get interface status */ + status = 0; + } else if ((setup->bRequestType & USB_RECIP_MASK) + == USB_RECIP_ENDPOINT) { + u8 ep_num, direction; + + ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK; + direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK) + ? EP_DIR_IN : EP_DIR_OUT; + status = ep_is_stall(udc, ep_num, direction) + << USB_ENDPOINT_HALT; + } + + retval = udc_prime_status(udc, EP_DIR_IN, status, false); + if (retval) + ep0_stall(udc); + else + udc->ep0_state = DATA_STATE_XMIT; +} + +static void ch9clearfeature(struct mv_udc *udc, struct usb_ctrlrequest *setup) +{ + u8 ep_num; + u8 direction; + struct mv_ep *ep; + + if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) + == ((USB_TYPE_STANDARD | USB_RECIP_DEVICE))) { + switch (setup->wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + udc->remote_wakeup = 0; + break; + default: + goto out; + } + } else if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) + == ((USB_TYPE_STANDARD | USB_RECIP_ENDPOINT))) { + switch (setup->wValue) { + case USB_ENDPOINT_HALT: + ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK; + direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK) + ? EP_DIR_IN : EP_DIR_OUT; + if (setup->wValue != 0 || setup->wLength != 0 + || ep_num > udc->max_eps) + goto out; + ep = &udc->eps[ep_num * 2 + direction]; + if (ep->wedge == 1) + break; + spin_unlock(&udc->lock); + ep_set_stall(udc, ep_num, direction, 0); + spin_lock(&udc->lock); + break; + default: + goto out; + } + } else + goto out; + + if (udc_prime_status(udc, EP_DIR_IN, 0, true)) + ep0_stall(udc); +out: + return; +} + +static void ch9setfeature(struct mv_udc *udc, struct usb_ctrlrequest *setup) +{ + u8 ep_num; + u8 direction; + + if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) + == ((USB_TYPE_STANDARD | USB_RECIP_DEVICE))) { + switch (setup->wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + udc->remote_wakeup = 1; + break; + case USB_DEVICE_TEST_MODE: + if (setup->wIndex & 0xFF + || udc->gadget.speed != USB_SPEED_HIGH) + ep0_stall(udc); + + if (udc->usb_state != USB_STATE_CONFIGURED + && udc->usb_state != USB_STATE_ADDRESS + && udc->usb_state != USB_STATE_DEFAULT) + ep0_stall(udc); + + mv_udc_testmode(udc, (setup->wIndex >> 8)); + goto out; + default: + goto out; + } + } else if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) + == ((USB_TYPE_STANDARD | USB_RECIP_ENDPOINT))) { + switch (setup->wValue) { + case USB_ENDPOINT_HALT: + ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK; + direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK) + ? EP_DIR_IN : EP_DIR_OUT; + if (setup->wValue != 0 || setup->wLength != 0 + || ep_num > udc->max_eps) + goto out; + spin_unlock(&udc->lock); + ep_set_stall(udc, ep_num, direction, 1); + spin_lock(&udc->lock); + break; + default: + goto out; + } + } else + goto out; + + if (udc_prime_status(udc, EP_DIR_IN, 0, true)) + ep0_stall(udc); +out: + return; +} + +static void handle_setup_packet(struct mv_udc *udc, u8 ep_num, + struct usb_ctrlrequest *setup) + __releases(&ep->udc->lock) + __acquires(&ep->udc->lock) +{ + bool delegate = false; + + nuke(&udc->eps[ep_num * 2 + EP_DIR_OUT], -ESHUTDOWN); + + dev_dbg(&udc->dev->dev, "SETUP %02x.%02x v%04x i%04x l%04x\n", + setup->bRequestType, setup->bRequest, + setup->wValue, setup->wIndex, setup->wLength); + /* We process some stardard setup requests here */ + if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (setup->bRequest) { + case USB_REQ_GET_STATUS: + ch9getstatus(udc, ep_num, setup); + break; + + case USB_REQ_SET_ADDRESS: + ch9setaddress(udc, setup); + break; + + case USB_REQ_CLEAR_FEATURE: + ch9clearfeature(udc, setup); + break; + + case USB_REQ_SET_FEATURE: + ch9setfeature(udc, setup); + break; + + default: + delegate = true; + } + } else + delegate = true; + + /* delegate USB standard requests to the gadget driver */ + if (delegate == true) { + /* USB requests handled by gadget */ + if (setup->wLength) { + /* DATA phase from gadget, STATUS phase from udc */ + udc->ep0_dir = (setup->bRequestType & USB_DIR_IN) + ? EP_DIR_IN : EP_DIR_OUT; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + ep0_stall(udc); + spin_lock(&udc->lock); + udc->ep0_state = (setup->bRequestType & USB_DIR_IN) + ? DATA_STATE_XMIT : DATA_STATE_RECV; + } else { + /* no DATA phase, IN STATUS phase from gadget */ + udc->ep0_dir = EP_DIR_IN; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + ep0_stall(udc); + spin_lock(&udc->lock); + udc->ep0_state = WAIT_FOR_OUT_STATUS; + } + } +} + +/* complete DATA or STATUS phase of ep0 prime status phase if needed */ +static void ep0_req_complete(struct mv_udc *udc, + struct mv_ep *ep0, struct mv_req *req) +{ + u32 new_addr; + + if (udc->usb_state == USB_STATE_ADDRESS) { + /* set the new address */ + new_addr = (u32)udc->dev_addr; + writel(new_addr << USB_DEVICE_ADDRESS_BIT_SHIFT, + &udc->op_regs->deviceaddr); + } + + done(ep0, req, 0); + + switch (udc->ep0_state) { + case DATA_STATE_XMIT: + /* receive status phase */ + if (udc_prime_status(udc, EP_DIR_OUT, 0, true)) + ep0_stall(udc); + break; + case DATA_STATE_RECV: + /* send status phase */ + if (udc_prime_status(udc, EP_DIR_IN, 0 , true)) + ep0_stall(udc); + break; + case WAIT_FOR_OUT_STATUS: + udc->ep0_state = WAIT_FOR_SETUP; + break; + case WAIT_FOR_SETUP: + dev_err(&udc->dev->dev, "unexpect ep0 packets\n"); + break; + default: + ep0_stall(udc); + break; + } +} + +static void get_setup_data(struct mv_udc *udc, u8 ep_num, u8 *buffer_ptr) +{ + u32 temp; + struct mv_dqh *dqh; + + dqh = &udc->ep_dqh[ep_num * 2 + EP_DIR_OUT]; + + /* Clear bit in ENDPTSETUPSTAT */ + writel((1 << ep_num), &udc->op_regs->epsetupstat); + + /* while a hazard exists when setup package arrives */ + do { + /* Set Setup Tripwire */ + temp = readl(&udc->op_regs->usbcmd); + writel(temp | USBCMD_SETUP_TRIPWIRE_SET, &udc->op_regs->usbcmd); + + /* Copy the setup packet to local buffer */ + memcpy(buffer_ptr, (u8 *) dqh->setup_buffer, 8); + } while (!(readl(&udc->op_regs->usbcmd) & USBCMD_SETUP_TRIPWIRE_SET)); + + /* Clear Setup Tripwire */ + temp = readl(&udc->op_regs->usbcmd); + writel(temp & ~USBCMD_SETUP_TRIPWIRE_SET, &udc->op_regs->usbcmd); +} + +static void irq_process_tr_complete(struct mv_udc *udc) +{ + u32 tmp, bit_pos; + int i, ep_num = 0, direction = 0; + struct mv_ep *curr_ep; + struct mv_req *curr_req, *temp_req; + int status; + + /* + * We use separate loops for ENDPTSETUPSTAT and ENDPTCOMPLETE + * because the setup packets are to be read ASAP + */ + + /* Process all Setup packet received interrupts */ + tmp = readl(&udc->op_regs->epsetupstat); + + if (tmp) { + for (i = 0; i < udc->max_eps; i++) { + if (tmp & (1 << i)) { + get_setup_data(udc, i, + (u8 *)(&udc->local_setup_buff)); + handle_setup_packet(udc, i, + &udc->local_setup_buff); + } + } + } + + /* Don't clear the endpoint setup status register here. + * It is cleared as a setup packet is read out of the buffer + */ + + /* Process non-setup transaction complete interrupts */ + tmp = readl(&udc->op_regs->epcomplete); + + if (!tmp) + return; + + writel(tmp, &udc->op_regs->epcomplete); + + for (i = 0; i < udc->max_eps * 2; i++) { + ep_num = i >> 1; + direction = i % 2; + + bit_pos = 1 << (ep_num + 16 * direction); + + if (!(bit_pos & tmp)) + continue; + + if (i == 1) + curr_ep = &udc->eps[0]; + else + curr_ep = &udc->eps[i]; + /* process the req queue until an uncomplete request */ + list_for_each_entry_safe(curr_req, temp_req, + &curr_ep->queue, queue) { + status = process_ep_req(udc, i, curr_req); + if (status) + break; + + /* write back status to req */ + curr_req->req.status = status; + + /* ep0 request completion */ + if (ep_num == 0) { + ep0_req_complete(udc, curr_ep, curr_req); + break; + } else { + done(curr_ep, curr_req, status); + } + } + } +} + +static void irq_process_reset(struct mv_udc *udc) +{ + u32 tmp; + unsigned int loops; + + udc->ep0_dir = EP_DIR_OUT; + udc->ep0_state = WAIT_FOR_SETUP; + udc->remote_wakeup = 0; /* default to 0 on reset */ + + /* The address bits are past bit 25-31. Set the address */ + tmp = readl(&udc->op_regs->deviceaddr); + tmp &= ~(USB_DEVICE_ADDRESS_MASK); + writel(tmp, &udc->op_regs->deviceaddr); + + /* Clear all the setup token semaphores */ + tmp = readl(&udc->op_regs->epsetupstat); + writel(tmp, &udc->op_regs->epsetupstat); + + /* Clear all the endpoint complete status bits */ + tmp = readl(&udc->op_regs->epcomplete); + writel(tmp, &udc->op_regs->epcomplete); + + /* wait until all endptprime bits cleared */ + loops = LOOPS(PRIME_TIMEOUT); + while (readl(&udc->op_regs->epprime) & 0xFFFFFFFF) { + if (loops == 0) { + dev_err(&udc->dev->dev, + "Timeout for ENDPTPRIME = 0x%x\n", + readl(&udc->op_regs->epprime)); + break; + } + loops--; + udelay(LOOPS_USEC); + } + + /* Write 1s to the Flush register */ + writel((u32)~0, &udc->op_regs->epflush); + + if (readl(&udc->op_regs->portsc[0]) & PORTSCX_PORT_RESET) { + dev_info(&udc->dev->dev, "usb bus reset\n"); + udc->usb_state = USB_STATE_DEFAULT; + /* reset all the queues, stop all USB activities */ + stop_activity(udc, udc->driver); + } else { + dev_info(&udc->dev->dev, "USB reset portsc 0x%x\n", + readl(&udc->op_regs->portsc)); + + /* + * re-initialize + * controller reset + */ + udc_reset(udc); + + /* reset all the queues, stop all USB activities */ + stop_activity(udc, udc->driver); + + /* reset ep0 dQH and endptctrl */ + ep0_reset(udc); + + /* enable interrupt and set controller to run state */ + udc_start(udc); + + udc->usb_state = USB_STATE_ATTACHED; + } +} + +static void handle_bus_resume(struct mv_udc *udc) +{ + udc->usb_state = udc->resume_state; + udc->resume_state = 0; + + /* report resume to the driver */ + if (udc->driver) { + if (udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + } +} + +static void irq_process_suspend(struct mv_udc *udc) +{ + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + + if (udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } +} + +static void irq_process_port_change(struct mv_udc *udc) +{ + u32 portsc; + + portsc = readl(&udc->op_regs->portsc[0]); + if (!(portsc & PORTSCX_PORT_RESET)) { + /* Get the speed */ + u32 speed = portsc & PORTSCX_PORT_SPEED_MASK; + switch (speed) { + case PORTSCX_PORT_SPEED_HIGH: + udc->gadget.speed = USB_SPEED_HIGH; + break; + case PORTSCX_PORT_SPEED_FULL: + udc->gadget.speed = USB_SPEED_FULL; + break; + case PORTSCX_PORT_SPEED_LOW: + udc->gadget.speed = USB_SPEED_LOW; + break; + default: + udc->gadget.speed = USB_SPEED_UNKNOWN; + break; + } + } + + if (portsc & PORTSCX_PORT_SUSPEND) { + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + if (udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (!(portsc & PORTSCX_PORT_SUSPEND) + && udc->usb_state == USB_STATE_SUSPENDED) { + handle_bus_resume(udc); + } + + if (!udc->resume_state) + udc->usb_state = USB_STATE_DEFAULT; +} + +static void irq_process_error(struct mv_udc *udc) +{ + /* Increment the error count */ + udc->errors++; +} + +static irqreturn_t mv_udc_irq(int irq, void *dev) +{ + struct mv_udc *udc = (struct mv_udc *)dev; + u32 status, intr; + + /* Disable ISR when stopped bit is set */ + if (udc->stopped) + return IRQ_NONE; + + spin_lock(&udc->lock); + + status = readl(&udc->op_regs->usbsts); + intr = readl(&udc->op_regs->usbintr); + status &= intr; + + if (status == 0) { + spin_unlock(&udc->lock); + return IRQ_NONE; + } + + /* Clear all the interrupts occurred */ + writel(status, &udc->op_regs->usbsts); + + if (status & USBSTS_ERR) + irq_process_error(udc); + + if (status & USBSTS_RESET) + irq_process_reset(udc); + + if (status & USBSTS_PORT_CHANGE) + irq_process_port_change(udc); + + if (status & USBSTS_INT) + irq_process_tr_complete(udc); + + if (status & USBSTS_SUSPEND) + irq_process_suspend(udc); + + spin_unlock(&udc->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t mv_udc_vbus_irq(int irq, void *dev) +{ + struct mv_udc *udc = (struct mv_udc *)dev; + + /* polling VBUS and init phy may cause too much time*/ + if (udc->qwork) + queue_work(udc->qwork, &udc->vbus_work); + + return IRQ_HANDLED; +} + +static void mv_udc_vbus_work(struct work_struct *work) +{ + struct mv_udc *udc; + unsigned int vbus; + + udc = container_of(work, struct mv_udc, vbus_work); + if (!udc->pdata->vbus) + return; + + vbus = udc->pdata->vbus->poll(); + dev_info(&udc->dev->dev, "vbus is %d\n", vbus); + + if (vbus == VBUS_HIGH) + mv_udc_vbus_session(&udc->gadget, 1); + else if (vbus == VBUS_LOW) + mv_udc_vbus_session(&udc->gadget, 0); +} + +/* release device structure */ +static void gadget_release(struct device *_dev) +{ + struct mv_udc *udc; + + udc = dev_get_drvdata(_dev); + + complete(udc->done); +} + +static int mv_udc_remove(struct platform_device *pdev) +{ + struct mv_udc *udc; + + udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + + if (udc->qwork) { + flush_workqueue(udc->qwork); + destroy_workqueue(udc->qwork); + } + + /* free memory allocated in probe */ + if (udc->dtd_pool) + dma_pool_destroy(udc->dtd_pool); + + if (udc->ep_dqh) + dma_free_coherent(&pdev->dev, udc->ep_dqh_size, + udc->ep_dqh, udc->ep_dqh_dma); + + mv_udc_disable(udc); + + /* free dev, wait for the release() finished */ + wait_for_completion(udc->done); + + return 0; +} + +static int mv_udc_probe(struct platform_device *pdev) +{ + struct mv_usb_platform_data *pdata = pdev->dev.platform_data; + struct mv_udc *udc; + int retval = 0; + struct resource *r; + size_t size; + + if (pdata == NULL) { + dev_err(&pdev->dev, "missing platform_data\n"); + return -ENODEV; + } + + udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL); + if (udc == NULL) { + dev_err(&pdev->dev, "failed to allocate memory for udc\n"); + return -ENOMEM; + } + + udc->done = &release_done; + udc->pdata = pdev->dev.platform_data; + spin_lock_init(&udc->lock); + + udc->dev = pdev; + + if (pdata->mode == MV_USB_MODE_OTG) { + udc->transceiver = devm_usb_get_phy(&pdev->dev, + USB_PHY_TYPE_USB2); + if (IS_ERR(udc->transceiver)) { + retval = PTR_ERR(udc->transceiver); + + if (retval == -ENXIO) + return retval; + + udc->transceiver = NULL; + return -EPROBE_DEFER; + } + } + + /* udc only have one sysclk. */ + udc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(udc->clk)) + return PTR_ERR(udc->clk); + + r = platform_get_resource_byname(udc->dev, IORESOURCE_MEM, "capregs"); + if (r == NULL) { + dev_err(&pdev->dev, "no I/O memory resource defined\n"); + return -ENODEV; + } + + udc->cap_regs = (struct mv_cap_regs __iomem *) + devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (udc->cap_regs == NULL) { + dev_err(&pdev->dev, "failed to map I/O memory\n"); + return -EBUSY; + } + + r = platform_get_resource_byname(udc->dev, IORESOURCE_MEM, "phyregs"); + if (r == NULL) { + dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); + return -ENODEV; + } + + udc->phy_regs = ioremap(r->start, resource_size(r)); + if (udc->phy_regs == NULL) { + dev_err(&pdev->dev, "failed to map phy I/O memory\n"); + return -EBUSY; + } + + /* we will acces controller register, so enable the clk */ + retval = mv_udc_enable_internal(udc); + if (retval) + return retval; + + udc->op_regs = + (struct mv_op_regs __iomem *)((unsigned long)udc->cap_regs + + (readl(&udc->cap_regs->caplength_hciversion) + & CAPLENGTH_MASK)); + udc->max_eps = readl(&udc->cap_regs->dccparams) & DCCPARAMS_DEN_MASK; + + /* + * some platform will use usb to download image, it may not disconnect + * usb gadget before loading kernel. So first stop udc here. + */ + udc_stop(udc); + writel(0xFFFFFFFF, &udc->op_regs->usbsts); + + size = udc->max_eps * sizeof(struct mv_dqh) *2; + size = (size + DQH_ALIGNMENT - 1) & ~(DQH_ALIGNMENT - 1); + udc->ep_dqh = dma_alloc_coherent(&pdev->dev, size, + &udc->ep_dqh_dma, GFP_KERNEL); + + if (udc->ep_dqh == NULL) { + dev_err(&pdev->dev, "allocate dQH memory failed\n"); + retval = -ENOMEM; + goto err_disable_clock; + } + udc->ep_dqh_size = size; + + /* create dTD dma_pool resource */ + udc->dtd_pool = dma_pool_create("mv_dtd", + &pdev->dev, + sizeof(struct mv_dtd), + DTD_ALIGNMENT, + DMA_BOUNDARY); + + if (!udc->dtd_pool) { + retval = -ENOMEM; + goto err_free_dma; + } + + size = udc->max_eps * sizeof(struct mv_ep) *2; + udc->eps = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (udc->eps == NULL) { + dev_err(&pdev->dev, "allocate ep memory failed\n"); + retval = -ENOMEM; + goto err_destroy_dma; + } + + /* initialize ep0 status request structure */ + udc->status_req = devm_kzalloc(&pdev->dev, sizeof(struct mv_req), + GFP_KERNEL); + if (!udc->status_req) { + dev_err(&pdev->dev, "allocate status_req memory failed\n"); + retval = -ENOMEM; + goto err_destroy_dma; + } + INIT_LIST_HEAD(&udc->status_req->queue); + + /* allocate a small amount of memory to get valid address */ + udc->status_req->req.buf = kzalloc(8, GFP_KERNEL); + udc->status_req->req.dma = DMA_ADDR_INVALID; + + udc->resume_state = USB_STATE_NOTATTACHED; + udc->usb_state = USB_STATE_POWERED; + udc->ep0_dir = EP_DIR_OUT; + udc->remote_wakeup = 0; + + r = platform_get_resource(udc->dev, IORESOURCE_IRQ, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no IRQ resource defined\n"); + retval = -ENODEV; + goto err_destroy_dma; + } + udc->irq = r->start; + if (devm_request_irq(&pdev->dev, udc->irq, mv_udc_irq, + IRQF_SHARED, driver_name, udc)) { + dev_err(&pdev->dev, "Request irq %d for UDC failed\n", + udc->irq); + retval = -ENODEV; + goto err_destroy_dma; + } + + /* initialize gadget structure */ + udc->gadget.ops = &mv_ops; /* usb_gadget_ops */ + udc->gadget.ep0 = &udc->eps[0].ep; /* gadget ep0 */ + INIT_LIST_HEAD(&udc->gadget.ep_list); /* ep_list */ + udc->gadget.speed = USB_SPEED_UNKNOWN; /* speed */ + udc->gadget.max_speed = USB_SPEED_HIGH; /* support dual speed */ + + /* the "gadget" abstracts/virtualizes the controller */ + udc->gadget.name = driver_name; /* gadget name */ + + eps_init(udc); + + /* VBUS detect: we can disable/enable clock on demand.*/ + if (udc->transceiver) + udc->clock_gating = 1; + else if (pdata->vbus) { + udc->clock_gating = 1; + retval = devm_request_threaded_irq(&pdev->dev, + pdata->vbus->irq, NULL, + mv_udc_vbus_irq, IRQF_ONESHOT, "vbus", udc); + if (retval) { + dev_info(&pdev->dev, + "Can not request irq for VBUS, " + "disable clock gating\n"); + udc->clock_gating = 0; + } + + udc->qwork = create_singlethread_workqueue("mv_udc_queue"); + if (!udc->qwork) { + dev_err(&pdev->dev, "cannot create workqueue\n"); + retval = -ENOMEM; + goto err_destroy_dma; + } + + INIT_WORK(&udc->vbus_work, mv_udc_vbus_work); + } + + /* + * When clock gating is supported, we can disable clk and phy. + * If not, it means that VBUS detection is not supported, we + * have to enable vbus active all the time to let controller work. + */ + if (udc->clock_gating) + mv_udc_disable_internal(udc); + else + udc->vbus_active = 1; + + retval = usb_add_gadget_udc_release(&pdev->dev, &udc->gadget, + gadget_release); + if (retval) + goto err_create_workqueue; + + platform_set_drvdata(pdev, udc); + dev_info(&pdev->dev, "successful probe UDC device %s clock gating.\n", + udc->clock_gating ? "with" : "without"); + + return 0; + +err_create_workqueue: + destroy_workqueue(udc->qwork); +err_destroy_dma: + dma_pool_destroy(udc->dtd_pool); +err_free_dma: + dma_free_coherent(&pdev->dev, udc->ep_dqh_size, + udc->ep_dqh, udc->ep_dqh_dma); +err_disable_clock: + mv_udc_disable_internal(udc); + + return retval; +} + +#ifdef CONFIG_PM +static int mv_udc_suspend(struct device *dev) +{ + struct mv_udc *udc; + + udc = dev_get_drvdata(dev); + + /* if OTG is enabled, the following will be done in OTG driver*/ + if (udc->transceiver) + return 0; + + if (udc->pdata->vbus && udc->pdata->vbus->poll) + if (udc->pdata->vbus->poll() == VBUS_HIGH) { + dev_info(&udc->dev->dev, "USB cable is connected!\n"); + return -EAGAIN; + } + + /* + * only cable is unplugged, udc can suspend. + * So do not care about clock_gating == 1. + */ + if (!udc->clock_gating) { + udc_stop(udc); + + spin_lock_irq(&udc->lock); + /* stop all usb activities */ + stop_activity(udc, udc->driver); + spin_unlock_irq(&udc->lock); + + mv_udc_disable_internal(udc); + } + + return 0; +} + +static int mv_udc_resume(struct device *dev) +{ + struct mv_udc *udc; + int retval; + + udc = dev_get_drvdata(dev); + + /* if OTG is enabled, the following will be done in OTG driver*/ + if (udc->transceiver) + return 0; + + if (!udc->clock_gating) { + retval = mv_udc_enable_internal(udc); + if (retval) + return retval; + + if (udc->driver && udc->softconnect) { + udc_reset(udc); + ep0_reset(udc); + udc_start(udc); + } + } + + return 0; +} + +static const struct dev_pm_ops mv_udc_pm_ops = { + .suspend = mv_udc_suspend, + .resume = mv_udc_resume, +}; +#endif + +static void mv_udc_shutdown(struct platform_device *pdev) +{ + struct mv_udc *udc; + u32 mode; + + udc = platform_get_drvdata(pdev); + /* reset controller mode to IDLE */ + mv_udc_enable(udc); + mode = readl(&udc->op_regs->usbmode); + mode &= ~3; + writel(mode, &udc->op_regs->usbmode); + mv_udc_disable(udc); +} + +static struct platform_driver udc_driver = { + .probe = mv_udc_probe, + .remove = mv_udc_remove, + .shutdown = mv_udc_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "mv-udc", +#ifdef CONFIG_PM + .pm = &mv_udc_pm_ops, +#endif + }, +}; + +module_platform_driver(udc_driver); +MODULE_ALIAS("platform:mv-udc"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Chao Xie "); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/ncm.c b/drivers/usb/gadget/ncm.c new file mode 100644 index 00000000..3b02fd46 --- /dev/null +++ b/drivers/usb/gadget/ncm.c @@ -0,0 +1,205 @@ +/* + * ncm.c -- NCM gadget driver + * + * Copyright (C) 2010 Nokia Corporation + * Contact: Yauheni Kaliuta + * + * The driver borrows from ether.c which is: + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include + +#include "u_ether.h" + +#define DRIVER_DESC "NCM Gadget" + +/*-------------------------------------------------------------------------*/ + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "f_ncm.c" +#include "u_ether.c" + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. + * It's for devices with only CDC Ethernet configurations. + */ +#define CDC_VENDOR_NUM 0x0525 /* NetChip */ +#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */ + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16 (0x0200), + + .bDeviceClass = USB_CLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id defaults change according to what configs + * we support. (As does bNumConfigurations.) These values can + * also be overridden by module parameters. + */ + .idVendor = cpu_to_le16 (CDC_VENDOR_NUM), + .idProduct = cpu_to_le16 (CDC_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +/* string IDs are assigned dynamically */ +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, + [USB_GADGET_SERIAL_IDX].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +struct eth_dev *the_dev; +static u8 hostaddr[ETH_ALEN]; + +/*-------------------------------------------------------------------------*/ + +static int __init ncm_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + return ncm_bind_config(c, hostaddr, the_dev); +} + +static struct usb_configuration ncm_config_driver = { + /* .label = f(hardware) */ + .label = "CDC Ethernet (NCM)", + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init gncm_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int status; + + /* set up network link layer */ + the_dev = gether_setup(cdev->gadget, hostaddr); + if (IS_ERR(the_dev)) + return PTR_ERR(the_dev); + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto fail; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + status = usb_add_config(cdev, &ncm_config_driver, + ncm_do_config); + if (status < 0) + goto fail; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s\n", DRIVER_DESC); + + return 0; + +fail: + gether_cleanup(the_dev); + return status; +} + +static int __exit gncm_unbind(struct usb_composite_dev *cdev) +{ + gether_cleanup(the_dev); + return 0; +} + +static __refdata struct usb_composite_driver ncm_driver = { + .name = "g_ncm", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = gncm_bind, + .unbind = __exit_p(gncm_unbind), +}; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Yauheni Kaliuta"); +MODULE_LICENSE("GPL"); + +static int __init init(void) +{ + return usb_composite_probe(&ncm_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&ncm_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/ndis.h b/drivers/usb/gadget/ndis.h new file mode 100644 index 00000000..a19f72de --- /dev/null +++ b/drivers/usb/gadget/ndis.h @@ -0,0 +1,47 @@ +/* + * ndis.h + * + * ntddndis.h modified by Benedikt Spranger + * + * Thanks to the cygwin development team, + * espacially to Casper S. Hornstrup + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + */ + +#ifndef _LINUX_NDIS_H +#define _LINUX_NDIS_H + +enum NDIS_DEVICE_POWER_STATE { + NdisDeviceStateUnspecified = 0, + NdisDeviceStateD0, + NdisDeviceStateD1, + NdisDeviceStateD2, + NdisDeviceStateD3, + NdisDeviceStateMaximum +}; + +struct NDIS_PM_WAKE_UP_CAPABILITIES { + enum NDIS_DEVICE_POWER_STATE MinMagicPacketWakeUp; + enum NDIS_DEVICE_POWER_STATE MinPatternWakeUp; + enum NDIS_DEVICE_POWER_STATE MinLinkChangeWakeUp; +}; + +struct NDIS_PNP_CAPABILITIES { + __le32 Flags; + struct NDIS_PM_WAKE_UP_CAPABILITIES WakeUpCapabilities; +}; + +struct NDIS_PM_PACKET_PATTERN { + __le32 Priority; + __le32 Reserved; + __le32 MaskSize; + __le32 PatternOffset; + __le32 PatternSize; + __le32 PatternFlags; +}; + +#endif /* _LINUX_NDIS_H */ diff --git a/drivers/usb/gadget/net2272.c b/drivers/usb/gadget/net2272.c new file mode 100644 index 00000000..f1e50a3e --- /dev/null +++ b/drivers/usb/gadget/net2272.c @@ -0,0 +1,2710 @@ +/* + * Driver for PLX NET2272 USB device controller + * + * Copyright (C) 2005-2006 PLX Technology, Inc. + * Copyright (C) 2006-2011 Analog Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "net2272.h" + +#define DRIVER_DESC "PLX NET2272 USB Peripheral Controller" + +static const char driver_name[] = "net2272"; +static const char driver_vers[] = "2006 October 17/mainline"; +static const char driver_desc[] = DRIVER_DESC; + +static const char ep0name[] = "ep0"; +static const char * const ep_name[] = { + ep0name, + "ep-a", "ep-b", "ep-c", +}; + +#ifdef CONFIG_USB_NET2272_DMA +/* + * use_dma: the NET2272 can use an external DMA controller. + * Note that since there is no generic DMA api, some functions, + * notably request_dma, start_dma, and cancel_dma will need to be + * modified for your platform's particular dma controller. + * + * If use_dma is disabled, pio will be used instead. + */ +static bool use_dma = 0; +module_param(use_dma, bool, 0644); + +/* + * dma_ep: selects the endpoint for use with dma (1=ep-a, 2=ep-b) + * The NET2272 can only use dma for a single endpoint at a time. + * At some point this could be modified to allow either endpoint + * to take control of dma as it becomes available. + * + * Note that DMA should not be used on OUT endpoints unless it can + * be guaranteed that no short packets will arrive on an IN endpoint + * while the DMA operation is pending. Otherwise the OUT DMA will + * terminate prematurely (See NET2272 Errata 630-0213-0101) + */ +static ushort dma_ep = 1; +module_param(dma_ep, ushort, 0644); + +/* + * dma_mode: net2272 dma mode setting (see LOCCTL1 definiton): + * mode 0 == Slow DREQ mode + * mode 1 == Fast DREQ mode + * mode 2 == Burst mode + */ +static ushort dma_mode = 2; +module_param(dma_mode, ushort, 0644); +#else +#define use_dma 0 +#define dma_ep 1 +#define dma_mode 2 +#endif + +/* + * fifo_mode: net2272 buffer configuration: + * mode 0 == ep-{a,b,c} 512db each + * mode 1 == ep-a 1k, ep-{b,c} 512db + * mode 2 == ep-a 1k, ep-b 1k, ep-c 512db + * mode 3 == ep-a 1k, ep-b disabled, ep-c 512db + */ +static ushort fifo_mode = 0; +module_param(fifo_mode, ushort, 0644); + +/* + * enable_suspend: When enabled, the driver will respond to + * USB suspend requests by powering down the NET2272. Otherwise, + * USB suspend requests will be ignored. This is acceptible for + * self-powered devices. For bus powered devices set this to 1. + */ +static ushort enable_suspend = 0; +module_param(enable_suspend, ushort, 0644); + +static void assert_out_naking(struct net2272_ep *ep, const char *where) +{ + u8 tmp; + +#ifndef DEBUG + return; +#endif + + tmp = net2272_ep_read(ep, EP_STAT0); + if ((tmp & (1 << NAK_OUT_PACKETS)) == 0) { + dev_dbg(ep->dev->dev, "%s %s %02x !NAK\n", + ep->ep.name, where, tmp); + net2272_ep_write(ep, EP_RSPSET, 1 << ALT_NAK_OUT_PACKETS); + } +} +#define ASSERT_OUT_NAKING(ep) assert_out_naking(ep, __func__) + +static void stop_out_naking(struct net2272_ep *ep) +{ + u8 tmp = net2272_ep_read(ep, EP_STAT0); + + if ((tmp & (1 << NAK_OUT_PACKETS)) != 0) + net2272_ep_write(ep, EP_RSPCLR, 1 << ALT_NAK_OUT_PACKETS); +} + +#define PIPEDIR(bAddress) (usb_pipein(bAddress) ? "in" : "out") + +static char *type_string(u8 bmAttributes) +{ + switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: return "bulk"; + case USB_ENDPOINT_XFER_ISOC: return "iso"; + case USB_ENDPOINT_XFER_INT: return "intr"; + default: return "control"; + } +} + +static char *buf_state_string(unsigned state) +{ + switch (state) { + case BUFF_FREE: return "free"; + case BUFF_VALID: return "valid"; + case BUFF_LCL: return "local"; + case BUFF_USB: return "usb"; + default: return "unknown"; + } +} + +static char *dma_mode_string(void) +{ + if (!use_dma) + return "PIO"; + switch (dma_mode) { + case 0: return "SLOW DREQ"; + case 1: return "FAST DREQ"; + case 2: return "BURST"; + default: return "invalid"; + } +} + +static void net2272_dequeue_all(struct net2272_ep *); +static int net2272_kick_dma(struct net2272_ep *, struct net2272_request *); +static int net2272_fifo_status(struct usb_ep *); + +static struct usb_ep_ops net2272_ep_ops; + +/*---------------------------------------------------------------------------*/ + +static int +net2272_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct net2272 *dev; + struct net2272_ep *ep; + u32 max; + u8 tmp; + unsigned long flags; + + ep = container_of(_ep, struct net2272_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + max = usb_endpoint_maxp(desc) & 0x1fff; + + spin_lock_irqsave(&dev->lock, flags); + _ep->maxpacket = max & 0x7fff; + ep->desc = desc; + + /* net2272_ep_reset() has already been called */ + ep->stopped = 0; + ep->wedged = 0; + + /* set speed-dependent max packet */ + net2272_ep_write(ep, EP_MAXPKT0, max & 0xff); + net2272_ep_write(ep, EP_MAXPKT1, (max & 0xff00) >> 8); + + /* set type, direction, address; reset fifo counters */ + net2272_ep_write(ep, EP_STAT1, 1 << BUFFER_FLUSH); + tmp = usb_endpoint_type(desc); + if (usb_endpoint_xfer_bulk(desc)) { + /* catch some particularly blatant driver bugs */ + if ((dev->gadget.speed == USB_SPEED_HIGH && max != 512) || + (dev->gadget.speed == USB_SPEED_FULL && max > 64)) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ERANGE; + } + } + ep->is_iso = usb_endpoint_xfer_isoc(desc) ? 1 : 0; + tmp <<= ENDPOINT_TYPE; + tmp |= ((desc->bEndpointAddress & 0x0f) << ENDPOINT_NUMBER); + tmp |= usb_endpoint_dir_in(desc) << ENDPOINT_DIRECTION; + tmp |= (1 << ENDPOINT_ENABLE); + + /* for OUT transfers, block the rx fifo until a read is posted */ + ep->is_in = usb_endpoint_dir_in(desc); + if (!ep->is_in) + net2272_ep_write(ep, EP_RSPSET, 1 << ALT_NAK_OUT_PACKETS); + + net2272_ep_write(ep, EP_CFG, tmp); + + /* enable irqs */ + tmp = (1 << ep->num) | net2272_read(dev, IRQENB0); + net2272_write(dev, IRQENB0, tmp); + + tmp = (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) + | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE) + | net2272_ep_read(ep, EP_IRQENB); + net2272_ep_write(ep, EP_IRQENB, tmp); + + tmp = desc->bEndpointAddress; + dev_dbg(dev->dev, "enabled %s (ep%d%s-%s) max %04x cfg %02x\n", + _ep->name, tmp & 0x0f, PIPEDIR(tmp), + type_string(desc->bmAttributes), max, + net2272_ep_read(ep, EP_CFG)); + + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} + +static void net2272_ep_reset(struct net2272_ep *ep) +{ + u8 tmp; + + ep->desc = NULL; + INIT_LIST_HEAD(&ep->queue); + + ep->ep.maxpacket = ~0; + ep->ep.ops = &net2272_ep_ops; + + /* disable irqs, endpoint */ + net2272_ep_write(ep, EP_IRQENB, 0); + + /* init to our chosen defaults, notably so that we NAK OUT + * packets until the driver queues a read. + */ + tmp = (1 << NAK_OUT_PACKETS_MODE) | (1 << ALT_NAK_OUT_PACKETS); + net2272_ep_write(ep, EP_RSPSET, tmp); + + tmp = (1 << INTERRUPT_MODE) | (1 << HIDE_STATUS_PHASE); + if (ep->num != 0) + tmp |= (1 << ENDPOINT_TOGGLE) | (1 << ENDPOINT_HALT); + + net2272_ep_write(ep, EP_RSPCLR, tmp); + + /* scrub most status bits, and flush any fifo state */ + net2272_ep_write(ep, EP_STAT0, + (1 << DATA_IN_TOKEN_INTERRUPT) + | (1 << DATA_OUT_TOKEN_INTERRUPT) + | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) + | (1 << DATA_PACKET_RECEIVED_INTERRUPT) + | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)); + + net2272_ep_write(ep, EP_STAT1, + (1 << TIMEOUT) + | (1 << USB_OUT_ACK_SENT) + | (1 << USB_OUT_NAK_SENT) + | (1 << USB_IN_ACK_RCVD) + | (1 << USB_IN_NAK_SENT) + | (1 << USB_STALL_SENT) + | (1 << LOCAL_OUT_ZLP) + | (1 << BUFFER_FLUSH)); + + /* fifo size is handled seperately */ +} + +static int net2272_disable(struct usb_ep *_ep) +{ + struct net2272_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct net2272_ep, ep); + if (!_ep || !ep->desc || _ep->name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + net2272_dequeue_all(ep); + net2272_ep_reset(ep); + + dev_vdbg(ep->dev->dev, "disabled %s\n", _ep->name); + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +/*---------------------------------------------------------------------------*/ + +static struct usb_request * +net2272_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct net2272_ep *ep; + struct net2272_request *req; + + if (!_ep) + return NULL; + ep = container_of(_ep, struct net2272_ep, ep); + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void +net2272_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct net2272_ep *ep; + struct net2272_request *req; + + ep = container_of(_ep, struct net2272_ep, ep); + if (!_ep || !_req) + return; + + req = container_of(_req, struct net2272_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +static void +net2272_done(struct net2272_ep *ep, struct net2272_request *req, int status) +{ + struct net2272 *dev; + unsigned stopped = ep->stopped; + + if (ep->num == 0) { + if (ep->dev->protocol_stall) { + ep->stopped = 1; + set_halt(ep); + } + allow_status(ep); + } + + list_del_init(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + dev = ep->dev; + if (use_dma && ep->dma) + usb_gadget_unmap_request(&dev->gadget, &req->req, + ep->is_in); + + if (status && status != -ESHUTDOWN) + dev_vdbg(dev->dev, "complete %s req %p stat %d len %u/%u buf %p\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length, req->req.buf); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + spin_unlock(&dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&dev->lock); + ep->stopped = stopped; +} + +static int +net2272_write_packet(struct net2272_ep *ep, u8 *buf, + struct net2272_request *req, unsigned max) +{ + u16 __iomem *ep_data = net2272_reg_addr(ep->dev, EP_DATA); + u16 *bufp; + unsigned length, count; + u8 tmp; + + length = min(req->req.length - req->req.actual, max); + req->req.actual += length; + + dev_vdbg(ep->dev->dev, "write packet %s req %p max %u len %u avail %u\n", + ep->ep.name, req, max, length, + (net2272_ep_read(ep, EP_AVAIL1) << 8) | net2272_ep_read(ep, EP_AVAIL0)); + + count = length; + bufp = (u16 *)buf; + + while (likely(count >= 2)) { + /* no byte-swap required; chip endian set during init */ + writew(*bufp++, ep_data); + count -= 2; + } + buf = (u8 *)bufp; + + /* write final byte by placing the NET2272 into 8-bit mode */ + if (unlikely(count)) { + tmp = net2272_read(ep->dev, LOCCTL); + net2272_write(ep->dev, LOCCTL, tmp & ~(1 << DATA_WIDTH)); + writeb(*buf, ep_data); + net2272_write(ep->dev, LOCCTL, tmp); + } + return length; +} + +/* returns: 0: still running, 1: completed, negative: errno */ +static int +net2272_write_fifo(struct net2272_ep *ep, struct net2272_request *req) +{ + u8 *buf; + unsigned count, max; + int status; + + dev_vdbg(ep->dev->dev, "write_fifo %s actual %d len %d\n", + ep->ep.name, req->req.actual, req->req.length); + + /* + * Keep loading the endpoint until the final packet is loaded, + * or the endpoint buffer is full. + */ + top: + /* + * Clear interrupt status + * - Packet Transmitted interrupt will become set again when the + * host successfully takes another packet + */ + net2272_ep_write(ep, EP_STAT0, (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)); + while (!(net2272_ep_read(ep, EP_STAT0) & (1 << BUFFER_FULL))) { + buf = req->req.buf + req->req.actual; + prefetch(buf); + + /* force pagesel */ + net2272_ep_read(ep, EP_STAT0); + + max = (net2272_ep_read(ep, EP_AVAIL1) << 8) | + (net2272_ep_read(ep, EP_AVAIL0)); + + if (max < ep->ep.maxpacket) + max = (net2272_ep_read(ep, EP_AVAIL1) << 8) + | (net2272_ep_read(ep, EP_AVAIL0)); + + count = net2272_write_packet(ep, buf, req, max); + /* see if we are done */ + if (req->req.length == req->req.actual) { + /* validate short or zlp packet */ + if (count < ep->ep.maxpacket) + set_fifo_bytecount(ep, 0); + net2272_done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct net2272_request, + queue); + status = net2272_kick_dma(ep, req); + + if (status < 0) + if ((net2272_ep_read(ep, EP_STAT0) + & (1 << BUFFER_EMPTY))) + goto top; + } + return 1; + } + net2272_ep_write(ep, EP_STAT0, (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)); + } + return 0; +} + +static void +net2272_out_flush(struct net2272_ep *ep) +{ + ASSERT_OUT_NAKING(ep); + + net2272_ep_write(ep, EP_STAT0, (1 << DATA_OUT_TOKEN_INTERRUPT) + | (1 << DATA_PACKET_RECEIVED_INTERRUPT)); + net2272_ep_write(ep, EP_STAT1, 1 << BUFFER_FLUSH); +} + +static int +net2272_read_packet(struct net2272_ep *ep, u8 *buf, + struct net2272_request *req, unsigned avail) +{ + u16 __iomem *ep_data = net2272_reg_addr(ep->dev, EP_DATA); + unsigned is_short; + u16 *bufp; + + req->req.actual += avail; + + dev_vdbg(ep->dev->dev, "read packet %s req %p len %u avail %u\n", + ep->ep.name, req, avail, + (net2272_ep_read(ep, EP_AVAIL1) << 8) | net2272_ep_read(ep, EP_AVAIL0)); + + is_short = (avail < ep->ep.maxpacket); + + if (unlikely(avail == 0)) { + /* remove any zlp from the buffer */ + (void)readw(ep_data); + return is_short; + } + + /* Ensure we get the final byte */ + if (unlikely(avail % 2)) + avail++; + bufp = (u16 *)buf; + + do { + *bufp++ = readw(ep_data); + avail -= 2; + } while (avail); + + /* + * To avoid false endpoint available race condition must read + * ep stat0 twice in the case of a short transfer + */ + if (net2272_ep_read(ep, EP_STAT0) & (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)) + net2272_ep_read(ep, EP_STAT0); + + return is_short; +} + +static int +net2272_read_fifo(struct net2272_ep *ep, struct net2272_request *req) +{ + u8 *buf; + unsigned is_short; + int count; + int tmp; + int cleanup = 0; + int status = -1; + + dev_vdbg(ep->dev->dev, "read_fifo %s actual %d len %d\n", + ep->ep.name, req->req.actual, req->req.length); + + top: + do { + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + count = (net2272_ep_read(ep, EP_AVAIL1) << 8) + | net2272_ep_read(ep, EP_AVAIL0); + + net2272_ep_write(ep, EP_STAT0, + (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT) | + (1 << DATA_PACKET_RECEIVED_INTERRUPT)); + + tmp = req->req.length - req->req.actual; + + if (count > tmp) { + if ((tmp % ep->ep.maxpacket) != 0) { + dev_err(ep->dev->dev, + "%s out fifo %d bytes, expected %d\n", + ep->ep.name, count, tmp); + cleanup = 1; + } + count = (tmp > 0) ? tmp : 0; + } + + is_short = net2272_read_packet(ep, buf, req, count); + + /* completion */ + if (unlikely(cleanup || is_short || + ((req->req.actual == req->req.length) + && !req->req.zero))) { + + if (cleanup) { + net2272_out_flush(ep); + net2272_done(ep, req, -EOVERFLOW); + } else + net2272_done(ep, req, 0); + + /* re-initialize endpoint transfer registers + * otherwise they may result in erroneous pre-validation + * for subsequent control reads + */ + if (unlikely(ep->num == 0)) { + net2272_ep_write(ep, EP_TRANSFER2, 0); + net2272_ep_write(ep, EP_TRANSFER1, 0); + net2272_ep_write(ep, EP_TRANSFER0, 0); + } + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct net2272_request, queue); + status = net2272_kick_dma(ep, req); + if ((status < 0) && + !(net2272_ep_read(ep, EP_STAT0) & (1 << BUFFER_EMPTY))) + goto top; + } + return 1; + } + } while (!(net2272_ep_read(ep, EP_STAT0) & (1 << BUFFER_EMPTY))); + + return 0; +} + +static void +net2272_pio_advance(struct net2272_ep *ep) +{ + struct net2272_request *req; + + if (unlikely(list_empty(&ep->queue))) + return; + + req = list_entry(ep->queue.next, struct net2272_request, queue); + (ep->is_in ? net2272_write_fifo : net2272_read_fifo)(ep, req); +} + +/* returns 0 on success, else negative errno */ +static int +net2272_request_dma(struct net2272 *dev, unsigned ep, u32 buf, + unsigned len, unsigned dir) +{ + dev_vdbg(dev->dev, "request_dma ep %d buf %08x len %d dir %d\n", + ep, buf, len, dir); + + /* The NET2272 only supports a single dma channel */ + if (dev->dma_busy) + return -EBUSY; + /* + * EP_TRANSFER (used to determine the number of bytes received + * in an OUT transfer) is 24 bits wide; don't ask for more than that. + */ + if ((dir == 1) && (len > 0x1000000)) + return -EINVAL; + + dev->dma_busy = 1; + + /* initialize platform's dma */ +#ifdef CONFIG_PCI + /* NET2272 addr, buffer addr, length, etc. */ + switch (dev->dev_id) { + case PCI_DEVICE_ID_RDK1: + /* Setup PLX 9054 DMA mode */ + writel((1 << LOCAL_BUS_WIDTH) | + (1 << TA_READY_INPUT_ENABLE) | + (0 << LOCAL_BURST_ENABLE) | + (1 << DONE_INTERRUPT_ENABLE) | + (1 << LOCAL_ADDRESSING_MODE) | + (1 << DEMAND_MODE) | + (1 << DMA_EOT_ENABLE) | + (1 << FAST_SLOW_TERMINATE_MODE_SELECT) | + (1 << DMA_CHANNEL_INTERRUPT_SELECT), + dev->rdk1.plx9054_base_addr + DMAMODE0); + + writel(0x100000, dev->rdk1.plx9054_base_addr + DMALADR0); + writel(buf, dev->rdk1.plx9054_base_addr + DMAPADR0); + writel(len, dev->rdk1.plx9054_base_addr + DMASIZ0); + writel((dir << DIRECTION_OF_TRANSFER) | + (1 << INTERRUPT_AFTER_TERMINAL_COUNT), + dev->rdk1.plx9054_base_addr + DMADPR0); + writel((1 << LOCAL_DMA_CHANNEL_0_INTERRUPT_ENABLE) | + readl(dev->rdk1.plx9054_base_addr + INTCSR), + dev->rdk1.plx9054_base_addr + INTCSR); + + break; + } +#endif + + net2272_write(dev, DMAREQ, + (0 << DMA_BUFFER_VALID) | + (1 << DMA_REQUEST_ENABLE) | + (1 << DMA_CONTROL_DACK) | + (dev->dma_eot_polarity << EOT_POLARITY) | + (dev->dma_dack_polarity << DACK_POLARITY) | + (dev->dma_dreq_polarity << DREQ_POLARITY) | + ((ep >> 1) << DMA_ENDPOINT_SELECT)); + + (void) net2272_read(dev, SCRATCH); + + return 0; +} + +static void +net2272_start_dma(struct net2272 *dev) +{ + /* start platform's dma controller */ +#ifdef CONFIG_PCI + switch (dev->dev_id) { + case PCI_DEVICE_ID_RDK1: + writeb((1 << CHANNEL_ENABLE) | (1 << CHANNEL_START), + dev->rdk1.plx9054_base_addr + DMACSR0); + break; + } +#endif +} + +/* returns 0 on success, else negative errno */ +static int +net2272_kick_dma(struct net2272_ep *ep, struct net2272_request *req) +{ + unsigned size; + u8 tmp; + + if (!use_dma || (ep->num < 1) || (ep->num > 2) || !ep->dma) + return -EINVAL; + + /* don't use dma for odd-length transfers + * otherwise, we'd need to deal with the last byte with pio + */ + if (req->req.length & 1) + return -EINVAL; + + dev_vdbg(ep->dev->dev, "kick_dma %s req %p dma %08llx\n", + ep->ep.name, req, (unsigned long long) req->req.dma); + + net2272_ep_write(ep, EP_RSPSET, 1 << ALT_NAK_OUT_PACKETS); + + /* The NET2272 can only use DMA on one endpoint at a time */ + if (ep->dev->dma_busy) + return -EBUSY; + + /* Make sure we only DMA an even number of bytes (we'll use + * pio to complete the transfer) + */ + size = req->req.length; + size &= ~1; + + /* device-to-host transfer */ + if (ep->is_in) { + /* initialize platform's dma controller */ + if (net2272_request_dma(ep->dev, ep->num, req->req.dma, size, 0)) + /* unable to obtain DMA channel; return error and use pio mode */ + return -EBUSY; + req->req.actual += size; + + /* host-to-device transfer */ + } else { + tmp = net2272_ep_read(ep, EP_STAT0); + + /* initialize platform's dma controller */ + if (net2272_request_dma(ep->dev, ep->num, req->req.dma, size, 1)) + /* unable to obtain DMA channel; return error and use pio mode */ + return -EBUSY; + + if (!(tmp & (1 << BUFFER_EMPTY))) + ep->not_empty = 1; + else + ep->not_empty = 0; + + + /* allow the endpoint's buffer to fill */ + net2272_ep_write(ep, EP_RSPCLR, 1 << ALT_NAK_OUT_PACKETS); + + /* this transfer completed and data's already in the fifo + * return error so pio gets used. + */ + if (tmp & (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)) { + + /* deassert dreq */ + net2272_write(ep->dev, DMAREQ, + (0 << DMA_BUFFER_VALID) | + (0 << DMA_REQUEST_ENABLE) | + (1 << DMA_CONTROL_DACK) | + (ep->dev->dma_eot_polarity << EOT_POLARITY) | + (ep->dev->dma_dack_polarity << DACK_POLARITY) | + (ep->dev->dma_dreq_polarity << DREQ_POLARITY) | + ((ep->num >> 1) << DMA_ENDPOINT_SELECT)); + + return -EBUSY; + } + } + + /* Don't use per-packet interrupts: use dma interrupts only */ + net2272_ep_write(ep, EP_IRQENB, 0); + + net2272_start_dma(ep->dev); + + return 0; +} + +static void net2272_cancel_dma(struct net2272 *dev) +{ +#ifdef CONFIG_PCI + switch (dev->dev_id) { + case PCI_DEVICE_ID_RDK1: + writeb(0, dev->rdk1.plx9054_base_addr + DMACSR0); + writeb(1 << CHANNEL_ABORT, dev->rdk1.plx9054_base_addr + DMACSR0); + while (!(readb(dev->rdk1.plx9054_base_addr + DMACSR0) & + (1 << CHANNEL_DONE))) + continue; /* wait for dma to stabalize */ + + /* dma abort generates an interrupt */ + writeb(1 << CHANNEL_CLEAR_INTERRUPT, + dev->rdk1.plx9054_base_addr + DMACSR0); + break; + } +#endif + + dev->dma_busy = 0; +} + +/*---------------------------------------------------------------------------*/ + +static int +net2272_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct net2272_request *req; + struct net2272_ep *ep; + struct net2272 *dev; + unsigned long flags; + int status = -1; + u8 s; + + req = container_of(_req, struct net2272_request, req); + if (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue)) + return -EINVAL; + ep = container_of(_ep, struct net2272_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return -EINVAL; + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + /* set up dma mapping in case the caller didn't */ + if (use_dma && ep->dma) { + status = usb_gadget_map_request(&dev->gadget, _req, + ep->is_in); + if (status) + return status; + } + + dev_vdbg(dev->dev, "%s queue req %p, len %d buf %p dma %08llx %s\n", + _ep->name, _req, _req->length, _req->buf, + (unsigned long long) _req->dma, _req->zero ? "zero" : "!zero"); + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + if (list_empty(&ep->queue) && !ep->stopped) { + /* maybe there's no control data, just status ack */ + if (ep->num == 0 && _req->length == 0) { + net2272_done(ep, req, 0); + dev_vdbg(dev->dev, "%s status ack\n", ep->ep.name); + goto done; + } + + /* Return zlp, don't let it block subsequent packets */ + s = net2272_ep_read(ep, EP_STAT0); + if (s & (1 << BUFFER_EMPTY)) { + /* Buffer is empty check for a blocking zlp, handle it */ + if ((s & (1 << NAK_OUT_PACKETS)) && + net2272_ep_read(ep, EP_STAT1) & (1 << LOCAL_OUT_ZLP)) { + dev_dbg(dev->dev, "WARNING: returning ZLP short packet termination!\n"); + /* + * Request is going to terminate with a short packet ... + * hope the client is ready for it! + */ + status = net2272_read_fifo(ep, req); + /* clear short packet naking */ + net2272_ep_write(ep, EP_STAT0, (1 << NAK_OUT_PACKETS)); + goto done; + } + } + + /* try dma first */ + status = net2272_kick_dma(ep, req); + + if (status < 0) { + /* dma failed (most likely in use by another endpoint) + * fallback to pio + */ + status = 0; + + if (ep->is_in) + status = net2272_write_fifo(ep, req); + else { + s = net2272_ep_read(ep, EP_STAT0); + if ((s & (1 << BUFFER_EMPTY)) == 0) + status = net2272_read_fifo(ep, req); + } + + if (unlikely(status != 0)) { + if (status > 0) + status = 0; + req = NULL; + } + } + } + if (likely(req)) + list_add_tail(&req->queue, &ep->queue); + + if (likely(!list_empty(&ep->queue))) + net2272_ep_write(ep, EP_RSPCLR, 1 << ALT_NAK_OUT_PACKETS); + done: + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/* dequeue ALL requests */ +static void +net2272_dequeue_all(struct net2272_ep *ep) +{ + struct net2272_request *req; + + /* called with spinlock held */ + ep->stopped = 1; + + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct net2272_request, + queue); + net2272_done(ep, req, -ESHUTDOWN); + } +} + +/* dequeue JUST ONE request */ +static int +net2272_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct net2272_ep *ep; + struct net2272_request *req; + unsigned long flags; + int stopped; + + ep = container_of(_ep, struct net2272_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0) || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + stopped = ep->stopped; + ep->stopped = 1; + + /* make sure it's still queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->dev->lock, flags); + return -EINVAL; + } + + /* queue head may be partially complete */ + if (ep->queue.next == &req->queue) { + dev_dbg(ep->dev->dev, "unlink (%s) pio\n", _ep->name); + net2272_done(ep, req, -ECONNRESET); + } + req = NULL; + ep->stopped = stopped; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +/*---------------------------------------------------------------------------*/ + +static int +net2272_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) +{ + struct net2272_ep *ep; + unsigned long flags; + int ret = 0; + + ep = container_of(_ep, struct net2272_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return -EINVAL; + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + if (ep->desc /* not ep0 */ && usb_endpoint_xfer_isoc(ep->desc)) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + if (!list_empty(&ep->queue)) + ret = -EAGAIN; + else if (ep->is_in && value && net2272_fifo_status(_ep) != 0) + ret = -EAGAIN; + else { + dev_vdbg(ep->dev->dev, "%s %s %s\n", _ep->name, + value ? "set" : "clear", + wedged ? "wedge" : "halt"); + /* set/clear */ + if (value) { + if (ep->num == 0) + ep->dev->protocol_stall = 1; + else + set_halt(ep); + if (wedged) + ep->wedged = 1; + } else { + clear_halt(ep); + ep->wedged = 0; + } + } + spin_unlock_irqrestore(&ep->dev->lock, flags); + + return ret; +} + +static int +net2272_set_halt(struct usb_ep *_ep, int value) +{ + return net2272_set_halt_and_wedge(_ep, value, 0); +} + +static int +net2272_set_wedge(struct usb_ep *_ep) +{ + if (!_ep || _ep->name == ep0name) + return -EINVAL; + return net2272_set_halt_and_wedge(_ep, 1, 1); +} + +static int +net2272_fifo_status(struct usb_ep *_ep) +{ + struct net2272_ep *ep; + u16 avail; + + ep = container_of(_ep, struct net2272_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return -ENODEV; + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + avail = net2272_ep_read(ep, EP_AVAIL1) << 8; + avail |= net2272_ep_read(ep, EP_AVAIL0); + if (avail > ep->fifo_size) + return -EOVERFLOW; + if (ep->is_in) + avail = ep->fifo_size - avail; + return avail; +} + +static void +net2272_fifo_flush(struct usb_ep *_ep) +{ + struct net2272_ep *ep; + + ep = container_of(_ep, struct net2272_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return; + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return; + + net2272_ep_write(ep, EP_STAT1, 1 << BUFFER_FLUSH); +} + +static struct usb_ep_ops net2272_ep_ops = { + .enable = net2272_enable, + .disable = net2272_disable, + + .alloc_request = net2272_alloc_request, + .free_request = net2272_free_request, + + .queue = net2272_queue, + .dequeue = net2272_dequeue, + + .set_halt = net2272_set_halt, + .set_wedge = net2272_set_wedge, + .fifo_status = net2272_fifo_status, + .fifo_flush = net2272_fifo_flush, +}; + +/*---------------------------------------------------------------------------*/ + +static int +net2272_get_frame(struct usb_gadget *_gadget) +{ + struct net2272 *dev; + unsigned long flags; + u16 ret; + + if (!_gadget) + return -ENODEV; + dev = container_of(_gadget, struct net2272, gadget); + spin_lock_irqsave(&dev->lock, flags); + + ret = net2272_read(dev, FRAME1) << 8; + ret |= net2272_read(dev, FRAME0); + + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int +net2272_wakeup(struct usb_gadget *_gadget) +{ + struct net2272 *dev; + u8 tmp; + unsigned long flags; + + if (!_gadget) + return 0; + dev = container_of(_gadget, struct net2272, gadget); + + spin_lock_irqsave(&dev->lock, flags); + tmp = net2272_read(dev, USBCTL0); + if (tmp & (1 << IO_WAKEUP_ENABLE)) + net2272_write(dev, USBCTL1, (1 << GENERATE_RESUME)); + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +static int +net2272_set_selfpowered(struct usb_gadget *_gadget, int value) +{ + struct net2272 *dev; + + if (!_gadget) + return -ENODEV; + dev = container_of(_gadget, struct net2272, gadget); + + dev->is_selfpowered = value; + + return 0; +} + +static int +net2272_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct net2272 *dev; + u8 tmp; + unsigned long flags; + + if (!_gadget) + return -ENODEV; + dev = container_of(_gadget, struct net2272, gadget); + + spin_lock_irqsave(&dev->lock, flags); + tmp = net2272_read(dev, USBCTL0); + dev->softconnect = (is_on != 0); + if (is_on) + tmp |= (1 << USB_DETECT_ENABLE); + else + tmp &= ~(1 << USB_DETECT_ENABLE); + net2272_write(dev, USBCTL0, tmp); + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +static int net2272_start(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver); +static int net2272_stop(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops net2272_ops = { + .get_frame = net2272_get_frame, + .wakeup = net2272_wakeup, + .set_selfpowered = net2272_set_selfpowered, + .pullup = net2272_pullup, + .udc_start = net2272_start, + .udc_stop = net2272_stop, +}; + +/*---------------------------------------------------------------------------*/ + +static ssize_t +net2272_show_registers(struct device *_dev, struct device_attribute *attr, char *buf) +{ + struct net2272 *dev; + char *next; + unsigned size, t; + unsigned long flags; + u8 t1, t2; + int i; + const char *s; + + dev = dev_get_drvdata(_dev); + next = buf; + size = PAGE_SIZE; + spin_lock_irqsave(&dev->lock, flags); + + if (dev->driver) + s = dev->driver->driver.name; + else + s = "(none)"; + + /* Main Control Registers */ + t = scnprintf(next, size, "%s version %s," + "chiprev %02x, locctl %02x\n" + "irqenb0 %02x irqenb1 %02x " + "irqstat0 %02x irqstat1 %02x\n", + driver_name, driver_vers, dev->chiprev, + net2272_read(dev, LOCCTL), + net2272_read(dev, IRQENB0), + net2272_read(dev, IRQENB1), + net2272_read(dev, IRQSTAT0), + net2272_read(dev, IRQSTAT1)); + size -= t; + next += t; + + /* DMA */ + t1 = net2272_read(dev, DMAREQ); + t = scnprintf(next, size, "\ndmareq %02x: %s %s%s%s%s\n", + t1, ep_name[(t1 & 0x01) + 1], + t1 & (1 << DMA_CONTROL_DACK) ? "dack " : "", + t1 & (1 << DMA_REQUEST_ENABLE) ? "reqenb " : "", + t1 & (1 << DMA_REQUEST) ? "req " : "", + t1 & (1 << DMA_BUFFER_VALID) ? "valid " : ""); + size -= t; + next += t; + + /* USB Control Registers */ + t1 = net2272_read(dev, USBCTL1); + if (t1 & (1 << VBUS_PIN)) { + if (t1 & (1 << USB_HIGH_SPEED)) + s = "high speed"; + else if (dev->gadget.speed == USB_SPEED_UNKNOWN) + s = "powered"; + else + s = "full speed"; + } else + s = "not attached"; + t = scnprintf(next, size, + "usbctl0 %02x usbctl1 %02x addr 0x%02x (%s)\n", + net2272_read(dev, USBCTL0), t1, + net2272_read(dev, OURADDR), s); + size -= t; + next += t; + + /* Endpoint Registers */ + for (i = 0; i < 4; ++i) { + struct net2272_ep *ep; + + ep = &dev->ep[i]; + if (i && !ep->desc) + continue; + + t1 = net2272_ep_read(ep, EP_CFG); + t2 = net2272_ep_read(ep, EP_RSPSET); + t = scnprintf(next, size, + "\n%s\tcfg %02x rsp (%02x) %s%s%s%s%s%s%s%s" + "irqenb %02x\n", + ep->ep.name, t1, t2, + (t2 & (1 << ALT_NAK_OUT_PACKETS)) ? "NAK " : "", + (t2 & (1 << HIDE_STATUS_PHASE)) ? "hide " : "", + (t2 & (1 << AUTOVALIDATE)) ? "auto " : "", + (t2 & (1 << INTERRUPT_MODE)) ? "interrupt " : "", + (t2 & (1 << CONTROL_STATUS_PHASE_HANDSHAKE)) ? "status " : "", + (t2 & (1 << NAK_OUT_PACKETS_MODE)) ? "NAKmode " : "", + (t2 & (1 << ENDPOINT_TOGGLE)) ? "DATA1 " : "DATA0 ", + (t2 & (1 << ENDPOINT_HALT)) ? "HALT " : "", + net2272_ep_read(ep, EP_IRQENB)); + size -= t; + next += t; + + t = scnprintf(next, size, + "\tstat0 %02x stat1 %02x avail %04x " + "(ep%d%s-%s)%s\n", + net2272_ep_read(ep, EP_STAT0), + net2272_ep_read(ep, EP_STAT1), + (net2272_ep_read(ep, EP_AVAIL1) << 8) | net2272_ep_read(ep, EP_AVAIL0), + t1 & 0x0f, + ep->is_in ? "in" : "out", + type_string(t1 >> 5), + ep->stopped ? "*" : ""); + size -= t; + next += t; + + t = scnprintf(next, size, + "\tep_transfer %06x\n", + ((net2272_ep_read(ep, EP_TRANSFER2) & 0xff) << 16) | + ((net2272_ep_read(ep, EP_TRANSFER1) & 0xff) << 8) | + ((net2272_ep_read(ep, EP_TRANSFER0) & 0xff))); + size -= t; + next += t; + + t1 = net2272_ep_read(ep, EP_BUFF_STATES) & 0x03; + t2 = (net2272_ep_read(ep, EP_BUFF_STATES) >> 2) & 0x03; + t = scnprintf(next, size, + "\tbuf-a %s buf-b %s\n", + buf_state_string(t1), + buf_state_string(t2)); + size -= t; + next += t; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return PAGE_SIZE - size; +} +static DEVICE_ATTR(registers, S_IRUGO, net2272_show_registers, NULL); + +/*---------------------------------------------------------------------------*/ + +static void +net2272_set_fifo_mode(struct net2272 *dev, int mode) +{ + u8 tmp; + + tmp = net2272_read(dev, LOCCTL) & 0x3f; + tmp |= (mode << 6); + net2272_write(dev, LOCCTL, tmp); + + INIT_LIST_HEAD(&dev->gadget.ep_list); + + /* always ep-a, ep-c ... maybe not ep-b */ + list_add_tail(&dev->ep[1].ep.ep_list, &dev->gadget.ep_list); + + switch (mode) { + case 0: + list_add_tail(&dev->ep[2].ep.ep_list, &dev->gadget.ep_list); + dev->ep[1].fifo_size = dev->ep[2].fifo_size = 512; + break; + case 1: + list_add_tail(&dev->ep[2].ep.ep_list, &dev->gadget.ep_list); + dev->ep[1].fifo_size = 1024; + dev->ep[2].fifo_size = 512; + break; + case 2: + list_add_tail(&dev->ep[2].ep.ep_list, &dev->gadget.ep_list); + dev->ep[1].fifo_size = dev->ep[2].fifo_size = 1024; + break; + case 3: + dev->ep[1].fifo_size = 1024; + break; + } + + /* ep-c is always 2 512 byte buffers */ + list_add_tail(&dev->ep[3].ep.ep_list, &dev->gadget.ep_list); + dev->ep[3].fifo_size = 512; +} + +/*---------------------------------------------------------------------------*/ + +static void +net2272_usb_reset(struct net2272 *dev) +{ + dev->gadget.speed = USB_SPEED_UNKNOWN; + + net2272_cancel_dma(dev); + + net2272_write(dev, IRQENB0, 0); + net2272_write(dev, IRQENB1, 0); + + /* clear irq state */ + net2272_write(dev, IRQSTAT0, 0xff); + net2272_write(dev, IRQSTAT1, ~(1 << SUSPEND_REQUEST_INTERRUPT)); + + net2272_write(dev, DMAREQ, + (0 << DMA_BUFFER_VALID) | + (0 << DMA_REQUEST_ENABLE) | + (1 << DMA_CONTROL_DACK) | + (dev->dma_eot_polarity << EOT_POLARITY) | + (dev->dma_dack_polarity << DACK_POLARITY) | + (dev->dma_dreq_polarity << DREQ_POLARITY) | + ((dma_ep >> 1) << DMA_ENDPOINT_SELECT)); + + net2272_cancel_dma(dev); + net2272_set_fifo_mode(dev, (fifo_mode <= 3) ? fifo_mode : 0); + + /* Set the NET2272 ep fifo data width to 16-bit mode and for correct byte swapping + * note that the higher level gadget drivers are expected to convert data to little endian. + * Enable byte swap for your local bus/cpu if needed by setting BYTE_SWAP in LOCCTL here + */ + net2272_write(dev, LOCCTL, net2272_read(dev, LOCCTL) | (1 << DATA_WIDTH)); + net2272_write(dev, LOCCTL1, (dma_mode << DMA_MODE)); +} + +static void +net2272_usb_reinit(struct net2272 *dev) +{ + int i; + + /* basic endpoint init */ + for (i = 0; i < 4; ++i) { + struct net2272_ep *ep = &dev->ep[i]; + + ep->ep.name = ep_name[i]; + ep->dev = dev; + ep->num = i; + ep->not_empty = 0; + + if (use_dma && ep->num == dma_ep) + ep->dma = 1; + + if (i > 0 && i <= 3) + ep->fifo_size = 512; + else + ep->fifo_size = 64; + net2272_ep_reset(ep); + } + dev->ep[0].ep.maxpacket = 64; + + dev->gadget.ep0 = &dev->ep[0].ep; + dev->ep[0].stopped = 0; + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); +} + +static void +net2272_ep0_start(struct net2272 *dev) +{ + struct net2272_ep *ep0 = &dev->ep[0]; + + net2272_ep_write(ep0, EP_RSPSET, + (1 << NAK_OUT_PACKETS_MODE) | + (1 << ALT_NAK_OUT_PACKETS)); + net2272_ep_write(ep0, EP_RSPCLR, + (1 << HIDE_STATUS_PHASE) | + (1 << CONTROL_STATUS_PHASE_HANDSHAKE)); + net2272_write(dev, USBCTL0, + (dev->softconnect << USB_DETECT_ENABLE) | + (1 << USB_ROOT_PORT_WAKEUP_ENABLE) | + (1 << IO_WAKEUP_ENABLE)); + net2272_write(dev, IRQENB0, + (1 << SETUP_PACKET_INTERRUPT_ENABLE) | + (1 << ENDPOINT_0_INTERRUPT_ENABLE) | + (1 << DMA_DONE_INTERRUPT_ENABLE)); + net2272_write(dev, IRQENB1, + (1 << VBUS_INTERRUPT_ENABLE) | + (1 << ROOT_PORT_RESET_INTERRUPT_ENABLE) | + (1 << SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE)); +} + +/* when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ +static int net2272_start(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver) +{ + struct net2272 *dev; + unsigned i; + + if (!driver || !driver->unbind || !driver->setup || + driver->max_speed != USB_SPEED_HIGH) + return -EINVAL; + + dev = container_of(_gadget, struct net2272, gadget); + + for (i = 0; i < 4; ++i) + dev->ep[i].irqs = 0; + /* hook up the driver ... */ + dev->softconnect = 1; + driver->driver.bus = NULL; + dev->driver = driver; + + /* ... then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + net2272_ep0_start(dev); + + dev_dbg(dev->dev, "%s ready\n", driver->driver.name); + + return 0; +} + +static void +stop_activity(struct net2272 *dev, struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect if it's not connected */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + + /* stop hardware; prevent new request submissions; + * and kill any outstanding requests. + */ + net2272_usb_reset(dev); + for (i = 0; i < 4; ++i) + net2272_dequeue_all(&dev->ep[i]); + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + net2272_usb_reinit(dev); +} + +static int net2272_stop(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver) +{ + struct net2272 *dev; + unsigned long flags; + + dev = container_of(_gadget, struct net2272, gadget); + + spin_lock_irqsave(&dev->lock, flags); + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + dev->driver = NULL; + + dev_dbg(dev->dev, "unregistered driver '%s'\n", driver->driver.name); + return 0; +} + +/*---------------------------------------------------------------------------*/ +/* handle ep-a/ep-b dma completions */ +static void +net2272_handle_dma(struct net2272_ep *ep) +{ + struct net2272_request *req; + unsigned len; + int status; + + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, + struct net2272_request, queue); + else + req = NULL; + + dev_vdbg(ep->dev->dev, "handle_dma %s req %p\n", ep->ep.name, req); + + /* Ensure DREQ is de-asserted */ + net2272_write(ep->dev, DMAREQ, + (0 << DMA_BUFFER_VALID) + | (0 << DMA_REQUEST_ENABLE) + | (1 << DMA_CONTROL_DACK) + | (ep->dev->dma_eot_polarity << EOT_POLARITY) + | (ep->dev->dma_dack_polarity << DACK_POLARITY) + | (ep->dev->dma_dreq_polarity << DREQ_POLARITY) + | (ep->dma << DMA_ENDPOINT_SELECT)); + + ep->dev->dma_busy = 0; + + net2272_ep_write(ep, EP_IRQENB, + (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) + | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE) + | net2272_ep_read(ep, EP_IRQENB)); + + /* device-to-host transfer completed */ + if (ep->is_in) { + /* validate a short packet or zlp if necessary */ + if ((req->req.length % ep->ep.maxpacket != 0) || + req->req.zero) + set_fifo_bytecount(ep, 0); + + net2272_done(ep, req, 0); + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct net2272_request, queue); + status = net2272_kick_dma(ep, req); + if (status < 0) + net2272_pio_advance(ep); + } + + /* host-to-device transfer completed */ + } else { + /* terminated with a short packet? */ + if (net2272_read(ep->dev, IRQSTAT0) & + (1 << DMA_DONE_INTERRUPT)) { + /* abort system dma */ + net2272_cancel_dma(ep->dev); + } + + /* EP_TRANSFER will contain the number of bytes + * actually received. + * NOTE: There is no overflow detection on EP_TRANSFER: + * We can't deal with transfers larger than 2^24 bytes! + */ + len = (net2272_ep_read(ep, EP_TRANSFER2) << 16) + | (net2272_ep_read(ep, EP_TRANSFER1) << 8) + | (net2272_ep_read(ep, EP_TRANSFER0)); + + if (ep->not_empty) + len += 4; + + req->req.actual += len; + + /* get any remaining data */ + net2272_pio_advance(ep); + } +} + +/*---------------------------------------------------------------------------*/ + +static void +net2272_handle_ep(struct net2272_ep *ep) +{ + struct net2272_request *req; + u8 stat0, stat1; + + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, + struct net2272_request, queue); + else + req = NULL; + + /* ack all, and handle what we care about */ + stat0 = net2272_ep_read(ep, EP_STAT0); + stat1 = net2272_ep_read(ep, EP_STAT1); + ep->irqs++; + + dev_vdbg(ep->dev->dev, "%s ack ep_stat0 %02x, ep_stat1 %02x, req %p\n", + ep->ep.name, stat0, stat1, req ? &req->req : NULL); + + net2272_ep_write(ep, EP_STAT0, stat0 & + ~((1 << NAK_OUT_PACKETS) + | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT))); + net2272_ep_write(ep, EP_STAT1, stat1); + + /* data packet(s) received (in the fifo, OUT) + * direction must be validated, otherwise control read status phase + * could be interpreted as a valid packet + */ + if (!ep->is_in && (stat0 & (1 << DATA_PACKET_RECEIVED_INTERRUPT))) + net2272_pio_advance(ep); + /* data packet(s) transmitted (IN) */ + else if (stat0 & (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)) + net2272_pio_advance(ep); +} + +static struct net2272_ep * +net2272_get_ep_by_addr(struct net2272 *dev, u16 wIndex) +{ + struct net2272_ep *ep; + + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) + return &dev->ep[0]; + + list_for_each_entry(ep, &dev->gadget.ep_list, ep.ep_list) { + u8 bEndpointAddress; + + if (!ep->desc) + continue; + bEndpointAddress = ep->desc->bEndpointAddress; + if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) + continue; + if ((wIndex & 0x0f) == (bEndpointAddress & 0x0f)) + return ep; + } + return NULL; +} + +/* + * USB Test Packet: + * JKJKJKJK * 9 + * JJKKJJKK * 8 + * JJJJKKKK * 8 + * JJJJJJJKKKKKKK * 8 + * JJJJJJJK * 8 + * {JKKKKKKK * 10}, JK + */ +static const u8 net2272_test_packet[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, + 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFD, 0x7E +}; + +static void +net2272_set_test_mode(struct net2272 *dev, int mode) +{ + int i; + + /* Disable all net2272 interrupts: + * Nothing but a power cycle should stop the test. + */ + net2272_write(dev, IRQENB0, 0x00); + net2272_write(dev, IRQENB1, 0x00); + + /* Force tranceiver to high-speed */ + net2272_write(dev, XCVRDIAG, 1 << FORCE_HIGH_SPEED); + + net2272_write(dev, PAGESEL, 0); + net2272_write(dev, EP_STAT0, 1 << DATA_PACKET_TRANSMITTED_INTERRUPT); + net2272_write(dev, EP_RSPCLR, + (1 << CONTROL_STATUS_PHASE_HANDSHAKE) + | (1 << HIDE_STATUS_PHASE)); + net2272_write(dev, EP_CFG, 1 << ENDPOINT_DIRECTION); + net2272_write(dev, EP_STAT1, 1 << BUFFER_FLUSH); + + /* wait for status phase to complete */ + while (!(net2272_read(dev, EP_STAT0) & + (1 << DATA_PACKET_TRANSMITTED_INTERRUPT))) + ; + + /* Enable test mode */ + net2272_write(dev, USBTEST, mode); + + /* load test packet */ + if (mode == TEST_PACKET) { + /* switch to 8 bit mode */ + net2272_write(dev, LOCCTL, net2272_read(dev, LOCCTL) & + ~(1 << DATA_WIDTH)); + + for (i = 0; i < sizeof(net2272_test_packet); ++i) + net2272_write(dev, EP_DATA, net2272_test_packet[i]); + + /* Validate test packet */ + net2272_write(dev, EP_TRANSFER0, 0); + } +} + +static void +net2272_handle_stat0_irqs(struct net2272 *dev, u8 stat) +{ + struct net2272_ep *ep; + u8 num, scratch; + + /* starting a control request? */ + if (unlikely(stat & (1 << SETUP_PACKET_INTERRUPT))) { + union { + u8 raw[8]; + struct usb_ctrlrequest r; + } u; + int tmp = 0; + struct net2272_request *req; + + if (dev->gadget.speed == USB_SPEED_UNKNOWN) { + if (net2272_read(dev, USBCTL1) & (1 << USB_HIGH_SPEED)) + dev->gadget.speed = USB_SPEED_HIGH; + else + dev->gadget.speed = USB_SPEED_FULL; + dev_dbg(dev->dev, "%s\n", + usb_speed_string(dev->gadget.speed)); + } + + ep = &dev->ep[0]; + ep->irqs++; + + /* make sure any leftover interrupt state is cleared */ + stat &= ~(1 << ENDPOINT_0_INTERRUPT); + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct net2272_request, queue); + net2272_done(ep, req, + (req->req.actual == req->req.length) ? 0 : -EPROTO); + } + ep->stopped = 0; + dev->protocol_stall = 0; + net2272_ep_write(ep, EP_STAT0, + (1 << DATA_IN_TOKEN_INTERRUPT) + | (1 << DATA_OUT_TOKEN_INTERRUPT) + | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) + | (1 << DATA_PACKET_RECEIVED_INTERRUPT) + | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)); + net2272_ep_write(ep, EP_STAT1, + (1 << TIMEOUT) + | (1 << USB_OUT_ACK_SENT) + | (1 << USB_OUT_NAK_SENT) + | (1 << USB_IN_ACK_RCVD) + | (1 << USB_IN_NAK_SENT) + | (1 << USB_STALL_SENT) + | (1 << LOCAL_OUT_ZLP)); + + /* + * Ensure Control Read pre-validation setting is beyond maximum size + * - Control Writes can leave non-zero values in EP_TRANSFER. If + * an EP0 transfer following the Control Write is a Control Read, + * the NET2272 sees the non-zero EP_TRANSFER as an unexpected + * pre-validation count. + * - Setting EP_TRANSFER beyond the maximum EP0 transfer size ensures + * the pre-validation count cannot cause an unexpected validatation + */ + net2272_write(dev, PAGESEL, 0); + net2272_write(dev, EP_TRANSFER2, 0xff); + net2272_write(dev, EP_TRANSFER1, 0xff); + net2272_write(dev, EP_TRANSFER0, 0xff); + + u.raw[0] = net2272_read(dev, SETUP0); + u.raw[1] = net2272_read(dev, SETUP1); + u.raw[2] = net2272_read(dev, SETUP2); + u.raw[3] = net2272_read(dev, SETUP3); + u.raw[4] = net2272_read(dev, SETUP4); + u.raw[5] = net2272_read(dev, SETUP5); + u.raw[6] = net2272_read(dev, SETUP6); + u.raw[7] = net2272_read(dev, SETUP7); + /* + * If you have a big endian cpu make sure le16_to_cpus + * performs the proper byte swapping here... + */ + le16_to_cpus(&u.r.wValue); + le16_to_cpus(&u.r.wIndex); + le16_to_cpus(&u.r.wLength); + + /* ack the irq */ + net2272_write(dev, IRQSTAT0, 1 << SETUP_PACKET_INTERRUPT); + stat ^= (1 << SETUP_PACKET_INTERRUPT); + + /* watch control traffic at the token level, and force + * synchronization before letting the status phase happen. + */ + ep->is_in = (u.r.bRequestType & USB_DIR_IN) != 0; + if (ep->is_in) { + scratch = (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE) + | (1 << DATA_OUT_TOKEN_INTERRUPT_ENABLE) + | (1 << DATA_IN_TOKEN_INTERRUPT_ENABLE); + stop_out_naking(ep); + } else + scratch = (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) + | (1 << DATA_OUT_TOKEN_INTERRUPT_ENABLE) + | (1 << DATA_IN_TOKEN_INTERRUPT_ENABLE); + net2272_ep_write(ep, EP_IRQENB, scratch); + + if ((u.r.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) + goto delegate; + switch (u.r.bRequest) { + case USB_REQ_GET_STATUS: { + struct net2272_ep *e; + u16 status = 0; + + switch (u.r.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_ENDPOINT: + e = net2272_get_ep_by_addr(dev, u.r.wIndex); + if (!e || u.r.wLength > 2) + goto do_stall; + if (net2272_ep_read(e, EP_RSPSET) & (1 << ENDPOINT_HALT)) + status = __constant_cpu_to_le16(1); + else + status = __constant_cpu_to_le16(0); + + /* don't bother with a request object! */ + net2272_ep_write(&dev->ep[0], EP_IRQENB, 0); + writew(status, net2272_reg_addr(dev, EP_DATA)); + set_fifo_bytecount(&dev->ep[0], 0); + allow_status(ep); + dev_vdbg(dev->dev, "%s stat %02x\n", + ep->ep.name, status); + goto next_endpoints; + case USB_RECIP_DEVICE: + if (u.r.wLength > 2) + goto do_stall; + if (dev->is_selfpowered) + status = (1 << USB_DEVICE_SELF_POWERED); + + /* don't bother with a request object! */ + net2272_ep_write(&dev->ep[0], EP_IRQENB, 0); + writew(status, net2272_reg_addr(dev, EP_DATA)); + set_fifo_bytecount(&dev->ep[0], 0); + allow_status(ep); + dev_vdbg(dev->dev, "device stat %02x\n", status); + goto next_endpoints; + case USB_RECIP_INTERFACE: + if (u.r.wLength > 2) + goto do_stall; + + /* don't bother with a request object! */ + net2272_ep_write(&dev->ep[0], EP_IRQENB, 0); + writew(status, net2272_reg_addr(dev, EP_DATA)); + set_fifo_bytecount(&dev->ep[0], 0); + allow_status(ep); + dev_vdbg(dev->dev, "interface status %02x\n", status); + goto next_endpoints; + } + + break; + } + case USB_REQ_CLEAR_FEATURE: { + struct net2272_ep *e; + + if (u.r.bRequestType != USB_RECIP_ENDPOINT) + goto delegate; + if (u.r.wValue != USB_ENDPOINT_HALT || + u.r.wLength != 0) + goto do_stall; + e = net2272_get_ep_by_addr(dev, u.r.wIndex); + if (!e) + goto do_stall; + if (e->wedged) { + dev_vdbg(dev->dev, "%s wedged, halt not cleared\n", + ep->ep.name); + } else { + dev_vdbg(dev->dev, "%s clear halt\n", ep->ep.name); + clear_halt(e); + } + allow_status(ep); + goto next_endpoints; + } + case USB_REQ_SET_FEATURE: { + struct net2272_ep *e; + + if (u.r.bRequestType == USB_RECIP_DEVICE) { + if (u.r.wIndex != NORMAL_OPERATION) + net2272_set_test_mode(dev, (u.r.wIndex >> 8)); + allow_status(ep); + dev_vdbg(dev->dev, "test mode: %d\n", u.r.wIndex); + goto next_endpoints; + } else if (u.r.bRequestType != USB_RECIP_ENDPOINT) + goto delegate; + if (u.r.wValue != USB_ENDPOINT_HALT || + u.r.wLength != 0) + goto do_stall; + e = net2272_get_ep_by_addr(dev, u.r.wIndex); + if (!e) + goto do_stall; + set_halt(e); + allow_status(ep); + dev_vdbg(dev->dev, "%s set halt\n", ep->ep.name); + goto next_endpoints; + } + case USB_REQ_SET_ADDRESS: { + net2272_write(dev, OURADDR, u.r.wValue & 0xff); + allow_status(ep); + break; + } + default: + delegate: + dev_vdbg(dev->dev, "setup %02x.%02x v%04x i%04x " + "ep_cfg %08x\n", + u.r.bRequestType, u.r.bRequest, + u.r.wValue, u.r.wIndex, + net2272_ep_read(ep, EP_CFG)); + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &u.r); + spin_lock(&dev->lock); + } + + /* stall ep0 on error */ + if (tmp < 0) { + do_stall: + dev_vdbg(dev->dev, "req %02x.%02x protocol STALL; stat %d\n", + u.r.bRequestType, u.r.bRequest, tmp); + dev->protocol_stall = 1; + } + /* endpoint dma irq? */ + } else if (stat & (1 << DMA_DONE_INTERRUPT)) { + net2272_cancel_dma(dev); + net2272_write(dev, IRQSTAT0, 1 << DMA_DONE_INTERRUPT); + stat &= ~(1 << DMA_DONE_INTERRUPT); + num = (net2272_read(dev, DMAREQ) & (1 << DMA_ENDPOINT_SELECT)) + ? 2 : 1; + + ep = &dev->ep[num]; + net2272_handle_dma(ep); + } + + next_endpoints: + /* endpoint data irq? */ + scratch = stat & 0x0f; + stat &= ~0x0f; + for (num = 0; scratch; num++) { + u8 t; + + /* does this endpoint's FIFO and queue need tending? */ + t = 1 << num; + if ((scratch & t) == 0) + continue; + scratch ^= t; + + ep = &dev->ep[num]; + net2272_handle_ep(ep); + } + + /* some interrupts we can just ignore */ + stat &= ~(1 << SOF_INTERRUPT); + + if (stat) + dev_dbg(dev->dev, "unhandled irqstat0 %02x\n", stat); +} + +static void +net2272_handle_stat1_irqs(struct net2272 *dev, u8 stat) +{ + u8 tmp, mask; + + /* after disconnect there's nothing else to do! */ + tmp = (1 << VBUS_INTERRUPT) | (1 << ROOT_PORT_RESET_INTERRUPT); + mask = (1 << USB_HIGH_SPEED) | (1 << USB_FULL_SPEED); + + if (stat & tmp) { + net2272_write(dev, IRQSTAT1, tmp); + if ((((stat & (1 << ROOT_PORT_RESET_INTERRUPT)) && + ((net2272_read(dev, USBCTL1) & mask) == 0)) + || ((net2272_read(dev, USBCTL1) & (1 << VBUS_PIN)) + == 0)) + && (dev->gadget.speed != USB_SPEED_UNKNOWN)) { + dev_dbg(dev->dev, "disconnect %s\n", + dev->driver->driver.name); + stop_activity(dev, dev->driver); + net2272_ep0_start(dev); + return; + } + stat &= ~tmp; + + if (!stat) + return; + } + + tmp = (1 << SUSPEND_REQUEST_CHANGE_INTERRUPT); + if (stat & tmp) { + net2272_write(dev, IRQSTAT1, tmp); + if (stat & (1 << SUSPEND_REQUEST_INTERRUPT)) { + if (dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + if (!enable_suspend) { + stat &= ~(1 << SUSPEND_REQUEST_INTERRUPT); + dev_dbg(dev->dev, "Suspend disabled, ignoring\n"); + } + } else { + if (dev->driver->resume) + dev->driver->resume(&dev->gadget); + } + stat &= ~tmp; + } + + /* clear any other status/irqs */ + if (stat) + net2272_write(dev, IRQSTAT1, stat); + + /* some status we can just ignore */ + stat &= ~((1 << CONTROL_STATUS_INTERRUPT) + | (1 << SUSPEND_REQUEST_INTERRUPT) + | (1 << RESUME_INTERRUPT)); + if (!stat) + return; + else + dev_dbg(dev->dev, "unhandled irqstat1 %02x\n", stat); +} + +static irqreturn_t net2272_irq(int irq, void *_dev) +{ + struct net2272 *dev = _dev; +#if defined(PLX_PCI_RDK) || defined(PLX_PCI_RDK2) + u32 intcsr; +#endif +#if defined(PLX_PCI_RDK) + u8 dmareq; +#endif + spin_lock(&dev->lock); +#if defined(PLX_PCI_RDK) + intcsr = readl(dev->rdk1.plx9054_base_addr + INTCSR); + + if ((intcsr & LOCAL_INTERRUPT_TEST) == LOCAL_INTERRUPT_TEST) { + writel(intcsr & ~(1 << PCI_INTERRUPT_ENABLE), + dev->rdk1.plx9054_base_addr + INTCSR); + net2272_handle_stat1_irqs(dev, net2272_read(dev, IRQSTAT1)); + net2272_handle_stat0_irqs(dev, net2272_read(dev, IRQSTAT0)); + intcsr = readl(dev->rdk1.plx9054_base_addr + INTCSR); + writel(intcsr | (1 << PCI_INTERRUPT_ENABLE), + dev->rdk1.plx9054_base_addr + INTCSR); + } + if ((intcsr & DMA_CHANNEL_0_TEST) == DMA_CHANNEL_0_TEST) { + writeb((1 << CHANNEL_CLEAR_INTERRUPT | (0 << CHANNEL_ENABLE)), + dev->rdk1.plx9054_base_addr + DMACSR0); + + dmareq = net2272_read(dev, DMAREQ); + if (dmareq & 0x01) + net2272_handle_dma(&dev->ep[2]); + else + net2272_handle_dma(&dev->ep[1]); + } +#endif +#if defined(PLX_PCI_RDK2) + /* see if PCI int for us by checking irqstat */ + intcsr = readl(dev->rdk2.fpga_base_addr + RDK2_IRQSTAT); + if (!intcsr & (1 << NET2272_PCI_IRQ)) { + spin_unlock(&dev->lock); + return IRQ_NONE; + } + /* check dma interrupts */ +#endif + /* Platform/devcice interrupt handler */ +#if !defined(PLX_PCI_RDK) + net2272_handle_stat1_irqs(dev, net2272_read(dev, IRQSTAT1)); + net2272_handle_stat0_irqs(dev, net2272_read(dev, IRQSTAT0)); +#endif + spin_unlock(&dev->lock); + + return IRQ_HANDLED; +} + +static int net2272_present(struct net2272 *dev) +{ + /* + * Quick test to see if CPU can communicate properly with the NET2272. + * Verifies connection using writes and reads to write/read and + * read-only registers. + * + * This routine is strongly recommended especially during early bring-up + * of new hardware, however for designs that do not apply Power On System + * Tests (POST) it may discarded (or perhaps minimized). + */ + unsigned int ii; + u8 val, refval; + + /* Verify NET2272 write/read SCRATCH register can write and read */ + refval = net2272_read(dev, SCRATCH); + for (ii = 0; ii < 0x100; ii += 7) { + net2272_write(dev, SCRATCH, ii); + val = net2272_read(dev, SCRATCH); + if (val != ii) { + dev_dbg(dev->dev, + "%s: write/read SCRATCH register test failed: " + "wrote:0x%2.2x, read:0x%2.2x\n", + __func__, ii, val); + return -EINVAL; + } + } + /* To be nice, we write the original SCRATCH value back: */ + net2272_write(dev, SCRATCH, refval); + + /* Verify NET2272 CHIPREV register is read-only: */ + refval = net2272_read(dev, CHIPREV_2272); + for (ii = 0; ii < 0x100; ii += 7) { + net2272_write(dev, CHIPREV_2272, ii); + val = net2272_read(dev, CHIPREV_2272); + if (val != refval) { + dev_dbg(dev->dev, + "%s: write/read CHIPREV register test failed: " + "wrote 0x%2.2x, read:0x%2.2x expected:0x%2.2x\n", + __func__, ii, val, refval); + return -EINVAL; + } + } + + /* + * Verify NET2272's "NET2270 legacy revision" register + * - NET2272 has two revision registers. The NET2270 legacy revision + * register should read the same value, regardless of the NET2272 + * silicon revision. The legacy register applies to NET2270 + * firmware being applied to the NET2272. + */ + val = net2272_read(dev, CHIPREV_LEGACY); + if (val != NET2270_LEGACY_REV) { + /* + * Unexpected legacy revision value + * - Perhaps the chip is a NET2270? + */ + dev_dbg(dev->dev, + "%s: WARNING: UNEXPECTED NET2272 LEGACY REGISTER VALUE:\n" + " - CHIPREV_LEGACY: expected 0x%2.2x, got:0x%2.2x. (Not NET2272?)\n", + __func__, NET2270_LEGACY_REV, val); + return -EINVAL; + } + + /* + * Verify NET2272 silicon revision + * - This revision register is appropriate for the silicon version + * of the NET2272 + */ + val = net2272_read(dev, CHIPREV_2272); + switch (val) { + case CHIPREV_NET2272_R1: + /* + * NET2272 Rev 1 has DMA related errata: + * - Newer silicon (Rev 1A or better) required + */ + dev_dbg(dev->dev, + "%s: Rev 1 detected: newer silicon recommended for DMA support\n", + __func__); + break; + case CHIPREV_NET2272_R1A: + break; + default: + /* NET2272 silicon version *may* not work with this firmware */ + dev_dbg(dev->dev, + "%s: unexpected silicon revision register value: " + " CHIPREV_2272: 0x%2.2x\n", + __func__, val); + /* + * Return Success, even though the chip rev is not an expected value + * - Older, pre-built firmware can attempt to operate on newer silicon + * - Often, new silicon is perfectly compatible + */ + } + + /* Success: NET2272 checks out OK */ + return 0; +} + +static void +net2272_gadget_release(struct device *_dev) +{ + struct net2272 *dev = dev_get_drvdata(_dev); + kfree(dev); +} + +/*---------------------------------------------------------------------------*/ + +static void +net2272_remove(struct net2272 *dev) +{ + usb_del_gadget_udc(&dev->gadget); + + /* start with the driver above us */ + if (dev->driver) { + /* should have been done already by driver model core */ + dev_warn(dev->dev, "pci remove, driver '%s' is still registered\n", + dev->driver->driver.name); + usb_gadget_unregister_driver(dev->driver); + } + + free_irq(dev->irq, dev); + iounmap(dev->base_addr); + + device_remove_file(dev->dev, &dev_attr_registers); + + dev_info(dev->dev, "unbind\n"); +} + +static struct net2272 *net2272_probe_init(struct device *dev, unsigned int irq) +{ + struct net2272 *ret; + + if (!irq) { + dev_dbg(dev, "No IRQ!\n"); + return ERR_PTR(-ENODEV); + } + + /* alloc, and start init */ + ret = kzalloc(sizeof(*ret), GFP_KERNEL); + if (!ret) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&ret->lock); + ret->irq = irq; + ret->dev = dev; + ret->gadget.ops = &net2272_ops; + ret->gadget.max_speed = USB_SPEED_HIGH; + + /* the "gadget" abstracts/virtualizes the controller */ + ret->gadget.name = driver_name; + + return ret; +} + +static int +net2272_probe_fin(struct net2272 *dev, unsigned int irqflags) +{ + int ret; + + /* See if there... */ + if (net2272_present(dev)) { + dev_warn(dev->dev, "2272 not found!\n"); + ret = -ENODEV; + goto err; + } + + net2272_usb_reset(dev); + net2272_usb_reinit(dev); + + ret = request_irq(dev->irq, net2272_irq, irqflags, driver_name, dev); + if (ret) { + dev_err(dev->dev, "request interrupt %i failed\n", dev->irq); + goto err; + } + + dev->chiprev = net2272_read(dev, CHIPREV_2272); + + /* done */ + dev_info(dev->dev, "%s\n", driver_desc); + dev_info(dev->dev, "irq %i, mem %p, chip rev %04x, dma %s\n", + dev->irq, dev->base_addr, dev->chiprev, + dma_mode_string()); + dev_info(dev->dev, "version: %s\n", driver_vers); + + ret = device_create_file(dev->dev, &dev_attr_registers); + if (ret) + goto err_irq; + + ret = usb_add_gadget_udc_release(dev->dev, &dev->gadget, + net2272_gadget_release); + if (ret) + goto err_add_udc; + + return 0; + +err_add_udc: + device_remove_file(dev->dev, &dev_attr_registers); + err_irq: + free_irq(dev->irq, dev); + err: + return ret; +} + +#ifdef CONFIG_PCI + +/* + * wrap this driver around the specified device, but + * don't respond over USB until a gadget driver binds to us + */ + +static int +net2272_rdk1_probe(struct pci_dev *pdev, struct net2272 *dev) +{ + unsigned long resource, len, tmp; + void __iomem *mem_mapped_addr[4]; + int ret, i; + + /* + * BAR 0 holds PLX 9054 config registers + * BAR 1 is i/o memory; unused here + * BAR 2 holds EPLD config registers + * BAR 3 holds NET2272 registers + */ + + /* Find and map all address spaces */ + for (i = 0; i < 4; ++i) { + if (i == 1) + continue; /* BAR1 unused */ + + resource = pci_resource_start(pdev, i); + len = pci_resource_len(pdev, i); + + if (!request_mem_region(resource, len, driver_name)) { + dev_dbg(dev->dev, "controller already in use\n"); + ret = -EBUSY; + goto err; + } + + mem_mapped_addr[i] = ioremap_nocache(resource, len); + if (mem_mapped_addr[i] == NULL) { + release_mem_region(resource, len); + dev_dbg(dev->dev, "can't map memory\n"); + ret = -EFAULT; + goto err; + } + } + + dev->rdk1.plx9054_base_addr = mem_mapped_addr[0]; + dev->rdk1.epld_base_addr = mem_mapped_addr[2]; + dev->base_addr = mem_mapped_addr[3]; + + /* Set PLX 9054 bus width (16 bits) */ + tmp = readl(dev->rdk1.plx9054_base_addr + LBRD1); + writel((tmp & ~(3 << MEMORY_SPACE_LOCAL_BUS_WIDTH)) | W16_BIT, + dev->rdk1.plx9054_base_addr + LBRD1); + + /* Enable PLX 9054 Interrupts */ + writel(readl(dev->rdk1.plx9054_base_addr + INTCSR) | + (1 << PCI_INTERRUPT_ENABLE) | + (1 << LOCAL_INTERRUPT_INPUT_ENABLE), + dev->rdk1.plx9054_base_addr + INTCSR); + + writeb((1 << CHANNEL_CLEAR_INTERRUPT | (0 << CHANNEL_ENABLE)), + dev->rdk1.plx9054_base_addr + DMACSR0); + + /* reset */ + writeb((1 << EPLD_DMA_ENABLE) | + (1 << DMA_CTL_DACK) | + (1 << DMA_TIMEOUT_ENABLE) | + (1 << USER) | + (0 << MPX_MODE) | + (1 << BUSWIDTH) | + (1 << NET2272_RESET), + dev->base_addr + EPLD_IO_CONTROL_REGISTER); + + mb(); + writeb(readb(dev->base_addr + EPLD_IO_CONTROL_REGISTER) & + ~(1 << NET2272_RESET), + dev->base_addr + EPLD_IO_CONTROL_REGISTER); + udelay(200); + + return 0; + + err: + while (--i >= 0) { + iounmap(mem_mapped_addr[i]); + release_mem_region(pci_resource_start(pdev, i), + pci_resource_len(pdev, i)); + } + + return ret; +} + +static int +net2272_rdk2_probe(struct pci_dev *pdev, struct net2272 *dev) +{ + unsigned long resource, len; + void __iomem *mem_mapped_addr[2]; + int ret, i; + + /* + * BAR 0 holds FGPA config registers + * BAR 1 holds NET2272 registers + */ + + /* Find and map all address spaces, bar2-3 unused in rdk 2 */ + for (i = 0; i < 2; ++i) { + resource = pci_resource_start(pdev, i); + len = pci_resource_len(pdev, i); + + if (!request_mem_region(resource, len, driver_name)) { + dev_dbg(dev->dev, "controller already in use\n"); + ret = -EBUSY; + goto err; + } + + mem_mapped_addr[i] = ioremap_nocache(resource, len); + if (mem_mapped_addr[i] == NULL) { + release_mem_region(resource, len); + dev_dbg(dev->dev, "can't map memory\n"); + ret = -EFAULT; + goto err; + } + } + + dev->rdk2.fpga_base_addr = mem_mapped_addr[0]; + dev->base_addr = mem_mapped_addr[1]; + + mb(); + /* Set 2272 bus width (16 bits) and reset */ + writel((1 << CHIP_RESET), dev->rdk2.fpga_base_addr + RDK2_LOCCTLRDK); + udelay(200); + writel((1 << BUS_WIDTH), dev->rdk2.fpga_base_addr + RDK2_LOCCTLRDK); + /* Print fpga version number */ + dev_info(dev->dev, "RDK2 FPGA version %08x\n", + readl(dev->rdk2.fpga_base_addr + RDK2_FPGAREV)); + /* Enable FPGA Interrupts */ + writel((1 << NET2272_PCI_IRQ), dev->rdk2.fpga_base_addr + RDK2_IRQENB); + + return 0; + + err: + while (--i >= 0) { + iounmap(mem_mapped_addr[i]); + release_mem_region(pci_resource_start(pdev, i), + pci_resource_len(pdev, i)); + } + + return ret; +} + +static int +net2272_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct net2272 *dev; + int ret; + + dev = net2272_probe_init(&pdev->dev, pdev->irq); + if (IS_ERR(dev)) + return PTR_ERR(dev); + dev->dev_id = pdev->device; + + if (pci_enable_device(pdev) < 0) { + ret = -ENODEV; + goto err_free; + } + + pci_set_master(pdev); + + switch (pdev->device) { + case PCI_DEVICE_ID_RDK1: ret = net2272_rdk1_probe(pdev, dev); break; + case PCI_DEVICE_ID_RDK2: ret = net2272_rdk2_probe(pdev, dev); break; + default: BUG(); + } + if (ret) + goto err_pci; + + ret = net2272_probe_fin(dev, 0); + if (ret) + goto err_pci; + + pci_set_drvdata(pdev, dev); + + return 0; + + err_pci: + pci_disable_device(pdev); + err_free: + kfree(dev); + + return ret; +} + +static void +net2272_rdk1_remove(struct pci_dev *pdev, struct net2272 *dev) +{ + int i; + + /* disable PLX 9054 interrupts */ + writel(readl(dev->rdk1.plx9054_base_addr + INTCSR) & + ~(1 << PCI_INTERRUPT_ENABLE), + dev->rdk1.plx9054_base_addr + INTCSR); + + /* clean up resources allocated during probe() */ + iounmap(dev->rdk1.plx9054_base_addr); + iounmap(dev->rdk1.epld_base_addr); + + for (i = 0; i < 4; ++i) { + if (i == 1) + continue; /* BAR1 unused */ + release_mem_region(pci_resource_start(pdev, i), + pci_resource_len(pdev, i)); + } +} + +static void +net2272_rdk2_remove(struct pci_dev *pdev, struct net2272 *dev) +{ + int i; + + /* disable fpga interrupts + writel(readl(dev->rdk1.plx9054_base_addr + INTCSR) & + ~(1 << PCI_INTERRUPT_ENABLE), + dev->rdk1.plx9054_base_addr + INTCSR); + */ + + /* clean up resources allocated during probe() */ + iounmap(dev->rdk2.fpga_base_addr); + + for (i = 0; i < 2; ++i) + release_mem_region(pci_resource_start(pdev, i), + pci_resource_len(pdev, i)); +} + +static void +net2272_pci_remove(struct pci_dev *pdev) +{ + struct net2272 *dev = pci_get_drvdata(pdev); + + net2272_remove(dev); + + switch (pdev->device) { + case PCI_DEVICE_ID_RDK1: net2272_rdk1_remove(pdev, dev); break; + case PCI_DEVICE_ID_RDK2: net2272_rdk2_remove(pdev, dev); break; + default: BUG(); + } + + pci_disable_device(pdev); + + kfree(dev); +} + +/* Table of matching PCI IDs */ +static struct pci_device_id pci_ids[] = { + { /* RDK 1 card */ + .class = ((PCI_CLASS_BRIDGE_OTHER << 8) | 0xfe), + .class_mask = 0, + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_RDK1, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { /* RDK 2 card */ + .class = ((PCI_CLASS_BRIDGE_OTHER << 8) | 0xfe), + .class_mask = 0, + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_RDK2, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +static struct pci_driver net2272_pci_driver = { + .name = driver_name, + .id_table = pci_ids, + + .probe = net2272_pci_probe, + .remove = net2272_pci_remove, +}; + +static int net2272_pci_register(void) +{ + return pci_register_driver(&net2272_pci_driver); +} + +static void net2272_pci_unregister(void) +{ + pci_unregister_driver(&net2272_pci_driver); +} + +#else +static inline int net2272_pci_register(void) { return 0; } +static inline void net2272_pci_unregister(void) { } +#endif + +/*---------------------------------------------------------------------------*/ + +static int +net2272_plat_probe(struct platform_device *pdev) +{ + struct net2272 *dev; + int ret; + unsigned int irqflags; + resource_size_t base, len; + struct resource *iomem, *iomem_bus, *irq_res; + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iomem_bus = platform_get_resource(pdev, IORESOURCE_BUS, 0); + if (!irq_res || !iomem) { + dev_err(&pdev->dev, "must provide irq/base addr"); + return -EINVAL; + } + + dev = net2272_probe_init(&pdev->dev, irq_res->start); + if (IS_ERR(dev)) + return PTR_ERR(dev); + + irqflags = 0; + if (irq_res->flags & IORESOURCE_IRQ_HIGHEDGE) + irqflags |= IRQF_TRIGGER_RISING; + if (irq_res->flags & IORESOURCE_IRQ_LOWEDGE) + irqflags |= IRQF_TRIGGER_FALLING; + if (irq_res->flags & IORESOURCE_IRQ_HIGHLEVEL) + irqflags |= IRQF_TRIGGER_HIGH; + if (irq_res->flags & IORESOURCE_IRQ_LOWLEVEL) + irqflags |= IRQF_TRIGGER_LOW; + + base = iomem->start; + len = resource_size(iomem); + if (iomem_bus) + dev->base_shift = iomem_bus->start; + + if (!request_mem_region(base, len, driver_name)) { + dev_dbg(dev->dev, "get request memory region!\n"); + ret = -EBUSY; + goto err; + } + dev->base_addr = ioremap_nocache(base, len); + if (!dev->base_addr) { + dev_dbg(dev->dev, "can't map memory\n"); + ret = -EFAULT; + goto err_req; + } + + ret = net2272_probe_fin(dev, IRQF_TRIGGER_LOW); + if (ret) + goto err_io; + + platform_set_drvdata(pdev, dev); + dev_info(&pdev->dev, "running in 16-bit, %sbyte swap local bus mode\n", + (net2272_read(dev, LOCCTL) & (1 << BYTE_SWAP)) ? "" : "no "); + + return 0; + + err_io: + iounmap(dev->base_addr); + err_req: + release_mem_region(base, len); + err: + return ret; +} + +static int +net2272_plat_remove(struct platform_device *pdev) +{ + struct net2272 *dev = platform_get_drvdata(pdev); + + net2272_remove(dev); + + release_mem_region(pdev->resource[0].start, + resource_size(&pdev->resource[0])); + + kfree(dev); + + return 0; +} + +static struct platform_driver net2272_plat_driver = { + .probe = net2272_plat_probe, + .remove = net2272_plat_remove, + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + }, + /* FIXME .suspend, .resume */ +}; +MODULE_ALIAS("platform:net2272"); + +static int __init net2272_init(void) +{ + int ret; + + ret = net2272_pci_register(); + if (ret) + return ret; + ret = platform_driver_register(&net2272_plat_driver); + if (ret) + goto err_pci; + return ret; + +err_pci: + net2272_pci_unregister(); + return ret; +} +module_init(net2272_init); + +static void __exit net2272_cleanup(void) +{ + net2272_pci_unregister(); + platform_driver_unregister(&net2272_plat_driver); +} +module_exit(net2272_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("PLX Technology, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/net2272.h b/drivers/usb/gadget/net2272.h new file mode 100644 index 00000000..e5950578 --- /dev/null +++ b/drivers/usb/gadget/net2272.h @@ -0,0 +1,601 @@ +/* + * PLX NET2272 high/full speed USB device controller + * + * Copyright (C) 2005-2006 PLX Technology, Inc. + * Copyright (C) 2006-2011 Analog Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __NET2272_H__ +#define __NET2272_H__ + +/* Main Registers */ +#define REGADDRPTR 0x00 +#define REGDATA 0x01 +#define IRQSTAT0 0x02 +#define ENDPOINT_0_INTERRUPT 0 +#define ENDPOINT_A_INTERRUPT 1 +#define ENDPOINT_B_INTERRUPT 2 +#define ENDPOINT_C_INTERRUPT 3 +#define VIRTUALIZED_ENDPOINT_INTERRUPT 4 +#define SETUP_PACKET_INTERRUPT 5 +#define DMA_DONE_INTERRUPT 6 +#define SOF_INTERRUPT 7 +#define IRQSTAT1 0x03 +#define CONTROL_STATUS_INTERRUPT 1 +#define VBUS_INTERRUPT 2 +#define SUSPEND_REQUEST_INTERRUPT 3 +#define SUSPEND_REQUEST_CHANGE_INTERRUPT 4 +#define RESUME_INTERRUPT 5 +#define ROOT_PORT_RESET_INTERRUPT 6 +#define RESET_STATUS 7 +#define PAGESEL 0x04 +#define DMAREQ 0x1c +#define DMA_ENDPOINT_SELECT 0 +#define DREQ_POLARITY 1 +#define DACK_POLARITY 2 +#define EOT_POLARITY 3 +#define DMA_CONTROL_DACK 4 +#define DMA_REQUEST_ENABLE 5 +#define DMA_REQUEST 6 +#define DMA_BUFFER_VALID 7 +#define SCRATCH 0x1d +#define IRQENB0 0x20 +#define ENDPOINT_0_INTERRUPT_ENABLE 0 +#define ENDPOINT_A_INTERRUPT_ENABLE 1 +#define ENDPOINT_B_INTERRUPT_ENABLE 2 +#define ENDPOINT_C_INTERRUPT_ENABLE 3 +#define VIRTUALIZED_ENDPOINT_INTERRUPT_ENABLE 4 +#define SETUP_PACKET_INTERRUPT_ENABLE 5 +#define DMA_DONE_INTERRUPT_ENABLE 6 +#define SOF_INTERRUPT_ENABLE 7 +#define IRQENB1 0x21 +#define VBUS_INTERRUPT_ENABLE 2 +#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3 +#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 4 +#define RESUME_INTERRUPT_ENABLE 5 +#define ROOT_PORT_RESET_INTERRUPT_ENABLE 6 +#define LOCCTL 0x22 +#define DATA_WIDTH 0 +#define LOCAL_CLOCK_OUTPUT 1 +#define LOCAL_CLOCK_OUTPUT_OFF 0 +#define LOCAL_CLOCK_OUTPUT_3_75MHZ 1 +#define LOCAL_CLOCK_OUTPUT_7_5MHZ 2 +#define LOCAL_CLOCK_OUTPUT_15MHZ 3 +#define LOCAL_CLOCK_OUTPUT_30MHZ 4 +#define LOCAL_CLOCK_OUTPUT_60MHZ 5 +#define DMA_SPLIT_BUS_MODE 4 +#define BYTE_SWAP 5 +#define BUFFER_CONFIGURATION 6 +#define BUFFER_CONFIGURATION_EPA512_EPB512 0 +#define BUFFER_CONFIGURATION_EPA1024_EPB512 1 +#define BUFFER_CONFIGURATION_EPA1024_EPB1024 2 +#define BUFFER_CONFIGURATION_EPA1024DB 3 +#define CHIPREV_LEGACY 0x23 +#define NET2270_LEGACY_REV 0x40 +#define LOCCTL1 0x24 +#define DMA_MODE 0 +#define SLOW_DREQ 0 +#define FAST_DREQ 1 +#define BURST_MODE 2 +#define DMA_DACK_ENABLE 2 +#define CHIPREV_2272 0x25 +#define CHIPREV_NET2272_R1 0x10 +#define CHIPREV_NET2272_R1A 0x11 +/* USB Registers */ +#define USBCTL0 0x18 +#define IO_WAKEUP_ENABLE 1 +#define USB_DETECT_ENABLE 3 +#define USB_ROOT_PORT_WAKEUP_ENABLE 5 +#define USBCTL1 0x19 +#define VBUS_PIN 0 +#define USB_FULL_SPEED 1 +#define USB_HIGH_SPEED 2 +#define GENERATE_RESUME 3 +#define VIRTUAL_ENDPOINT_ENABLE 4 +#define FRAME0 0x1a +#define FRAME1 0x1b +#define OURADDR 0x30 +#define FORCE_IMMEDIATE 7 +#define USBDIAG 0x31 +#define FORCE_TRANSMIT_CRC_ERROR 0 +#define PREVENT_TRANSMIT_BIT_STUFF 1 +#define FORCE_RECEIVE_ERROR 2 +#define FAST_TIMES 4 +#define USBTEST 0x32 +#define TEST_MODE_SELECT 0 +#define NORMAL_OPERATION 0 +#define TEST_J 1 +#define TEST_K 2 +#define TEST_SE0_NAK 3 +#define TEST_PACKET 4 +#define TEST_FORCE_ENABLE 5 +#define XCVRDIAG 0x33 +#define FORCE_FULL_SPEED 2 +#define FORCE_HIGH_SPEED 3 +#define OPMODE 4 +#define NORMAL_OPERATION 0 +#define NON_DRIVING 1 +#define DISABLE_BITSTUFF_AND_NRZI_ENCODE 2 +#define LINESTATE 6 +#define SE0_STATE 0 +#define J_STATE 1 +#define K_STATE 2 +#define SE1_STATE 3 +#define VIRTOUT0 0x34 +#define VIRTOUT1 0x35 +#define VIRTIN0 0x36 +#define VIRTIN1 0x37 +#define SETUP0 0x40 +#define SETUP1 0x41 +#define SETUP2 0x42 +#define SETUP3 0x43 +#define SETUP4 0x44 +#define SETUP5 0x45 +#define SETUP6 0x46 +#define SETUP7 0x47 +/* Endpoint Registers (Paged via PAGESEL) */ +#define EP_DATA 0x05 +#define EP_STAT0 0x06 +#define DATA_IN_TOKEN_INTERRUPT 0 +#define DATA_OUT_TOKEN_INTERRUPT 1 +#define DATA_PACKET_TRANSMITTED_INTERRUPT 2 +#define DATA_PACKET_RECEIVED_INTERRUPT 3 +#define SHORT_PACKET_TRANSFERRED_INTERRUPT 4 +#define NAK_OUT_PACKETS 5 +#define BUFFER_EMPTY 6 +#define BUFFER_FULL 7 +#define EP_STAT1 0x07 +#define TIMEOUT 0 +#define USB_OUT_ACK_SENT 1 +#define USB_OUT_NAK_SENT 2 +#define USB_IN_ACK_RCVD 3 +#define USB_IN_NAK_SENT 4 +#define USB_STALL_SENT 5 +#define LOCAL_OUT_ZLP 6 +#define BUFFER_FLUSH 7 +#define EP_TRANSFER0 0x08 +#define EP_TRANSFER1 0x09 +#define EP_TRANSFER2 0x0a +#define EP_IRQENB 0x0b +#define DATA_IN_TOKEN_INTERRUPT_ENABLE 0 +#define DATA_OUT_TOKEN_INTERRUPT_ENABLE 1 +#define DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE 2 +#define DATA_PACKET_RECEIVED_INTERRUPT_ENABLE 3 +#define SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE 4 +#define EP_AVAIL0 0x0c +#define EP_AVAIL1 0x0d +#define EP_RSPCLR 0x0e +#define EP_RSPSET 0x0f +#define ENDPOINT_HALT 0 +#define ENDPOINT_TOGGLE 1 +#define NAK_OUT_PACKETS_MODE 2 +#define CONTROL_STATUS_PHASE_HANDSHAKE 3 +#define INTERRUPT_MODE 4 +#define AUTOVALIDATE 5 +#define HIDE_STATUS_PHASE 6 +#define ALT_NAK_OUT_PACKETS 7 +#define EP_MAXPKT0 0x28 +#define EP_MAXPKT1 0x29 +#define ADDITIONAL_TRANSACTION_OPPORTUNITIES 3 +#define NONE_ADDITIONAL_TRANSACTION 0 +#define ONE_ADDITIONAL_TRANSACTION 1 +#define TWO_ADDITIONAL_TRANSACTION 2 +#define EP_CFG 0x2a +#define ENDPOINT_NUMBER 0 +#define ENDPOINT_DIRECTION 4 +#define ENDPOINT_TYPE 5 +#define ENDPOINT_ENABLE 7 +#define EP_HBW 0x2b +#define HIGH_BANDWIDTH_OUT_TRANSACTION_PID 0 +#define DATA0_PID 0 +#define DATA1_PID 1 +#define DATA2_PID 2 +#define MDATA_PID 3 +#define EP_BUFF_STATES 0x2c +#define BUFFER_A_STATE 0 +#define BUFFER_B_STATE 2 +#define BUFF_FREE 0 +#define BUFF_VALID 1 +#define BUFF_LCL 2 +#define BUFF_USB 3 + +/*---------------------------------------------------------------------------*/ + +#define PCI_DEVICE_ID_RDK1 0x9054 + +/* PCI-RDK EPLD Registers */ +#define RDK_EPLD_IO_REGISTER1 0x00000000 +#define RDK_EPLD_USB_RESET 0 +#define RDK_EPLD_USB_POWERDOWN 1 +#define RDK_EPLD_USB_WAKEUP 2 +#define RDK_EPLD_USB_EOT 3 +#define RDK_EPLD_DPPULL 4 +#define RDK_EPLD_IO_REGISTER2 0x00000004 +#define RDK_EPLD_BUSWIDTH 0 +#define RDK_EPLD_USER 2 +#define RDK_EPLD_RESET_INTERRUPT_ENABLE 3 +#define RDK_EPLD_DMA_TIMEOUT_ENABLE 4 +#define RDK_EPLD_STATUS_REGISTER 0x00000008 +#define RDK_EPLD_USB_LRESET 0 +#define RDK_EPLD_REVISION_REGISTER 0x0000000c + +/* PCI-RDK PLX 9054 Registers */ +#define INTCSR 0x68 +#define PCI_INTERRUPT_ENABLE 8 +#define LOCAL_INTERRUPT_INPUT_ENABLE 11 +#define LOCAL_INPUT_INTERRUPT_ACTIVE 15 +#define LOCAL_DMA_CHANNEL_0_INTERRUPT_ENABLE 18 +#define LOCAL_DMA_CHANNEL_1_INTERRUPT_ENABLE 19 +#define DMA_CHANNEL_0_INTERRUPT_ACTIVE 21 +#define DMA_CHANNEL_1_INTERRUPT_ACTIVE 22 +#define CNTRL 0x6C +#define RELOAD_CONFIGURATION_REGISTERS 29 +#define PCI_ADAPTER_SOFTWARE_RESET 30 +#define DMAMODE0 0x80 +#define LOCAL_BUS_WIDTH 0 +#define INTERNAL_WAIT_STATES 2 +#define TA_READY_INPUT_ENABLE 6 +#define LOCAL_BURST_ENABLE 8 +#define SCATTER_GATHER_MODE 9 +#define DONE_INTERRUPT_ENABLE 10 +#define LOCAL_ADDRESSING_MODE 11 +#define DEMAND_MODE 12 +#define DMA_EOT_ENABLE 14 +#define FAST_SLOW_TERMINATE_MODE_SELECT 15 +#define DMA_CHANNEL_INTERRUPT_SELECT 17 +#define DMAPADR0 0x84 +#define DMALADR0 0x88 +#define DMASIZ0 0x8c +#define DMADPR0 0x90 +#define DESCRIPTOR_LOCATION 0 +#define END_OF_CHAIN 1 +#define INTERRUPT_AFTER_TERMINAL_COUNT 2 +#define DIRECTION_OF_TRANSFER 3 +#define DMACSR0 0xa8 +#define CHANNEL_ENABLE 0 +#define CHANNEL_START 1 +#define CHANNEL_ABORT 2 +#define CHANNEL_CLEAR_INTERRUPT 3 +#define CHANNEL_DONE 4 +#define DMATHR 0xb0 +#define LBRD1 0xf8 +#define MEMORY_SPACE_LOCAL_BUS_WIDTH 0 +#define W8_BIT 0 +#define W16_BIT 1 + +/* Special OR'ing of INTCSR bits */ +#define LOCAL_INTERRUPT_TEST \ + ((1 << LOCAL_INPUT_INTERRUPT_ACTIVE) | \ + (1 << LOCAL_INTERRUPT_INPUT_ENABLE)) + +#define DMA_CHANNEL_0_TEST \ + ((1 << DMA_CHANNEL_0_INTERRUPT_ACTIVE) | \ + (1 << LOCAL_DMA_CHANNEL_0_INTERRUPT_ENABLE)) + +#define DMA_CHANNEL_1_TEST \ + ((1 << DMA_CHANNEL_1_INTERRUPT_ACTIVE) | \ + (1 << LOCAL_DMA_CHANNEL_1_INTERRUPT_ENABLE)) + +/* EPLD Registers */ +#define RDK_EPLD_IO_REGISTER1 0x00000000 +#define RDK_EPLD_USB_RESET 0 +#define RDK_EPLD_USB_POWERDOWN 1 +#define RDK_EPLD_USB_WAKEUP 2 +#define RDK_EPLD_USB_EOT 3 +#define RDK_EPLD_DPPULL 4 +#define RDK_EPLD_IO_REGISTER2 0x00000004 +#define RDK_EPLD_BUSWIDTH 0 +#define RDK_EPLD_USER 2 +#define RDK_EPLD_RESET_INTERRUPT_ENABLE 3 +#define RDK_EPLD_DMA_TIMEOUT_ENABLE 4 +#define RDK_EPLD_STATUS_REGISTER 0x00000008 +#define RDK_EPLD_USB_LRESET 0 +#define RDK_EPLD_REVISION_REGISTER 0x0000000c + +#define EPLD_IO_CONTROL_REGISTER 0x400 +#define NET2272_RESET 0 +#define BUSWIDTH 1 +#define MPX_MODE 3 +#define USER 4 +#define DMA_TIMEOUT_ENABLE 5 +#define DMA_CTL_DACK 6 +#define EPLD_DMA_ENABLE 7 +#define EPLD_DMA_CONTROL_REGISTER 0x800 +#define SPLIT_DMA_MODE 0 +#define SPLIT_DMA_DIRECTION 1 +#define SPLIT_DMA_ENABLE 2 +#define SPLIT_DMA_INTERRUPT_ENABLE 3 +#define SPLIT_DMA_INTERRUPT 4 +#define EPLD_DMA_MODE 5 +#define EPLD_DMA_CONTROLLER_ENABLE 7 +#define SPLIT_DMA_ADDRESS_LOW 0xc00 +#define SPLIT_DMA_ADDRESS_HIGH 0x1000 +#define SPLIT_DMA_BYTE_COUNT_LOW 0x1400 +#define SPLIT_DMA_BYTE_COUNT_HIGH 0x1800 +#define EPLD_REVISION_REGISTER 0x1c00 +#define SPLIT_DMA_RAM 0x4000 +#define DMA_RAM_SIZE 0x1000 + +/*---------------------------------------------------------------------------*/ + +#define PCI_DEVICE_ID_RDK2 0x3272 + +/* PCI-RDK version 2 registers */ + +/* Main Control Registers */ + +#define RDK2_IRQENB 0x00 +#define RDK2_IRQSTAT 0x04 +#define PB7 23 +#define PB6 22 +#define PB5 21 +#define PB4 20 +#define PB3 19 +#define PB2 18 +#define PB1 17 +#define PB0 16 +#define GP3 23 +#define GP2 23 +#define GP1 23 +#define GP0 23 +#define DMA_RETRY_ABORT 6 +#define DMA_PAUSE_DONE 5 +#define DMA_ABORT_DONE 4 +#define DMA_OUT_FIFO_TRANSFER_DONE 3 +#define DMA_LOCAL_DONE 2 +#define DMA_PCI_DONE 1 +#define NET2272_PCI_IRQ 0 + +#define RDK2_LOCCTLRDK 0x08 +#define CHIP_RESET 3 +#define SPLIT_DMA 2 +#define MULTIPLEX_MODE 1 +#define BUS_WIDTH 0 + +#define RDK2_GPIOCTL 0x10 +#define GP3_OUT_ENABLE 7 +#define GP2_OUT_ENABLE 6 +#define GP1_OUT_ENABLE 5 +#define GP0_OUT_ENABLE 4 +#define GP3_DATA 3 +#define GP2_DATA 2 +#define GP1_DATA 1 +#define GP0_DATA 0 + +#define RDK2_LEDSW 0x14 +#define LED3 27 +#define LED2 26 +#define LED1 25 +#define LED0 24 +#define PBUTTON 16 +#define DIPSW 0 + +#define RDK2_DIAG 0x18 +#define RDK2_FAST_TIMES 2 +#define FORCE_PCI_SERR 1 +#define FORCE_PCI_INT 0 +#define RDK2_FPGAREV 0x1C + +/* Dma Control registers */ +#define RDK2_DMACTL 0x80 +#define ADDR_HOLD 24 +#define RETRY_COUNT 16 /* 23:16 */ +#define FIFO_THRESHOLD 11 /* 15:11 */ +#define MEM_WRITE_INVALIDATE 10 +#define READ_MULTIPLE 9 +#define READ_LINE 8 +#define RDK2_DMA_MODE 6 /* 7:6 */ +#define CONTROL_DACK 5 +#define EOT_ENABLE 4 +#define EOT_POLARITY 3 +#define DACK_POLARITY 2 +#define DREQ_POLARITY 1 +#define DMA_ENABLE 0 + +#define RDK2_DMASTAT 0x84 +#define GATHER_COUNT 12 /* 14:12 */ +#define FIFO_COUNT 6 /* 11:6 */ +#define FIFO_FLUSH 5 +#define FIFO_TRANSFER 4 +#define PAUSE_DONE 3 +#define ABORT_DONE 2 +#define DMA_ABORT 1 +#define DMA_START 0 + +#define RDK2_DMAPCICOUNT 0x88 +#define DMA_DIRECTION 31 +#define DMA_PCI_BYTE_COUNT 0 /* 0:23 */ + +#define RDK2_DMALOCCOUNT 0x8C /* 0:23 dma local byte count */ + +#define RDK2_DMAADDR 0x90 /* 2:31 PCI bus starting address */ + +/*---------------------------------------------------------------------------*/ + +#define REG_INDEXED_THRESHOLD (1 << 5) + +/* DRIVER DATA STRUCTURES and UTILITIES */ +struct net2272_ep { + struct usb_ep ep; + struct net2272 *dev; + unsigned long irqs; + + /* analogous to a host-side qh */ + struct list_head queue; + const struct usb_endpoint_descriptor *desc; + unsigned num:8, + fifo_size:12, + stopped:1, + wedged:1, + is_in:1, + is_iso:1, + dma:1, + not_empty:1; +}; + +struct net2272 { + /* each device provides one gadget, several endpoints */ + struct usb_gadget gadget; + struct device *dev; + unsigned short dev_id; + + spinlock_t lock; + struct net2272_ep ep[4]; + struct usb_gadget_driver *driver; + unsigned protocol_stall:1, + softconnect:1, + is_selfpowered:1, + wakeup:1, + dma_eot_polarity:1, + dma_dack_polarity:1, + dma_dreq_polarity:1, + dma_busy:1; + u16 chiprev; + u8 pagesel; + + unsigned int irq; + unsigned short fifo_mode; + + unsigned int base_shift; + u16 __iomem *base_addr; + union { +#ifdef CONFIG_PCI + struct { + void __iomem *plx9054_base_addr; + void __iomem *epld_base_addr; + } rdk1; + struct { + /* Bar0, Bar1 is base_addr both mem-mapped */ + void __iomem *fpga_base_addr; + } rdk2; +#endif + }; +}; + +static void __iomem * +net2272_reg_addr(struct net2272 *dev, unsigned int reg) +{ + return dev->base_addr + (reg << dev->base_shift); +} + +static void +net2272_write(struct net2272 *dev, unsigned int reg, u8 value) +{ + if (reg >= REG_INDEXED_THRESHOLD) { + /* + * Indexed register; use REGADDRPTR/REGDATA + * - Save and restore REGADDRPTR. This prevents REGADDRPTR from + * changes between other code sections, but it is time consuming. + * - Performance tips: either do not save and restore REGADDRPTR (if it + * is safe) or do save/restore operations only in critical sections. + u8 tmp = readb(dev->base_addr + REGADDRPTR); + */ + writeb((u8)reg, net2272_reg_addr(dev, REGADDRPTR)); + writeb(value, net2272_reg_addr(dev, REGDATA)); + /* writeb(tmp, net2272_reg_addr(dev, REGADDRPTR)); */ + } else + writeb(value, net2272_reg_addr(dev, reg)); +} + +static u8 +net2272_read(struct net2272 *dev, unsigned int reg) +{ + u8 ret; + + if (reg >= REG_INDEXED_THRESHOLD) { + /* + * Indexed register; use REGADDRPTR/REGDATA + * - Save and restore REGADDRPTR. This prevents REGADDRPTR from + * changes between other code sections, but it is time consuming. + * - Performance tips: either do not save and restore REGADDRPTR (if it + * is safe) or do save/restore operations only in critical sections. + u8 tmp = readb(dev->base_addr + REGADDRPTR); + */ + writeb((u8)reg, net2272_reg_addr(dev, REGADDRPTR)); + ret = readb(net2272_reg_addr(dev, REGDATA)); + /* writeb(tmp, net2272_reg_addr(dev, REGADDRPTR)); */ + } else + ret = readb(net2272_reg_addr(dev, reg)); + + return ret; +} + +static void +net2272_ep_write(struct net2272_ep *ep, unsigned int reg, u8 value) +{ + struct net2272 *dev = ep->dev; + + if (dev->pagesel != ep->num) { + net2272_write(dev, PAGESEL, ep->num); + dev->pagesel = ep->num; + } + net2272_write(dev, reg, value); +} + +static u8 +net2272_ep_read(struct net2272_ep *ep, unsigned int reg) +{ + struct net2272 *dev = ep->dev; + + if (dev->pagesel != ep->num) { + net2272_write(dev, PAGESEL, ep->num); + dev->pagesel = ep->num; + } + return net2272_read(dev, reg); +} + +static void allow_status(struct net2272_ep *ep) +{ + /* ep0 only */ + net2272_ep_write(ep, EP_RSPCLR, + (1 << CONTROL_STATUS_PHASE_HANDSHAKE) | + (1 << ALT_NAK_OUT_PACKETS) | + (1 << NAK_OUT_PACKETS_MODE)); + ep->stopped = 1; +} + +static void set_halt(struct net2272_ep *ep) +{ + /* ep0 and bulk/intr endpoints */ + net2272_ep_write(ep, EP_RSPCLR, 1 << CONTROL_STATUS_PHASE_HANDSHAKE); + net2272_ep_write(ep, EP_RSPSET, 1 << ENDPOINT_HALT); +} + +static void clear_halt(struct net2272_ep *ep) +{ + /* ep0 and bulk/intr endpoints */ + net2272_ep_write(ep, EP_RSPCLR, + (1 << ENDPOINT_HALT) | (1 << ENDPOINT_TOGGLE)); +} + +/* count (<= 4) bytes in the next fifo write will be valid */ +static void set_fifo_bytecount(struct net2272_ep *ep, unsigned count) +{ + /* net2272_ep_write will truncate to u8 for us */ + net2272_ep_write(ep, EP_TRANSFER2, count >> 16); + net2272_ep_write(ep, EP_TRANSFER1, count >> 8); + net2272_ep_write(ep, EP_TRANSFER0, count); +} + +struct net2272_request { + struct usb_request req; + struct list_head queue; + unsigned mapped:1, + valid:1; +}; + +#endif diff --git a/drivers/usb/gadget/net2280.c b/drivers/usb/gadget/net2280.c new file mode 100644 index 00000000..fbd006ab --- /dev/null +++ b/drivers/usb/gadget/net2280.c @@ -0,0 +1,2904 @@ +/* + * Driver for the PLX NET2280 USB device controller. + * Specs and errata are available from . + * + * PLX Technology Inc. (formerly NetChip Technology) supported the + * development of this driver. + * + * + * CODE STATUS HIGHLIGHTS + * + * This driver should work well with most "gadget" drivers, including + * the Mass Storage, Serial, and Ethernet/RNDIS gadget drivers + * as well as Gadget Zero and Gadgetfs. + * + * DMA is enabled by default. Drivers using transfer queues might use + * DMA chaining to remove IRQ latencies between transfers. (Except when + * short OUT transfers happen.) Drivers can use the req->no_interrupt + * hint to completely eliminate some IRQs, if a later IRQ is guaranteed + * and DMA chaining is enabled. + * + * Note that almost all the errata workarounds here are only needed for + * rev1 chips. Rev1a silicon (0110) fixes almost all of them. + */ + +/* + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003-2005 PLX Technology, Inc. + * + * Modified Seth Levy 2005 PLX Technology, Inc. to provide compatibility + * with 2282 chip + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#undef DEBUG /* messages on error and most fault paths */ +#undef VERBOSE /* extra debug messages (success too) */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define DRIVER_DESC "PLX NET228x USB Peripheral Controller" +#define DRIVER_VERSION "2005 Sept 27" + +#define EP_DONTUSE 13 /* nonzero */ + +#define USE_RDK_LEDS /* GPIO pins control three LEDs */ + + +static const char driver_name [] = "net2280"; +static const char driver_desc [] = DRIVER_DESC; + +static const char ep0name [] = "ep0"; +static const char *const ep_name [] = { + ep0name, + "ep-a", "ep-b", "ep-c", "ep-d", + "ep-e", "ep-f", +}; + +/* use_dma -- general goodness, fewer interrupts, less cpu load (vs PIO) + * use_dma_chaining -- dma descriptor queueing gives even more irq reduction + * + * The net2280 DMA engines are not tightly integrated with their FIFOs; + * not all cases are (yet) handled well in this driver or the silicon. + * Some gadget drivers work better with the dma support here than others. + * These two parameters let you use PIO or more aggressive DMA. + */ +static bool use_dma = 1; +static bool use_dma_chaining = 0; + +/* "modprobe net2280 use_dma=n" etc */ +module_param (use_dma, bool, S_IRUGO); +module_param (use_dma_chaining, bool, S_IRUGO); + + +/* mode 0 == ep-{a,b,c,d} 1K fifo each + * mode 1 == ep-{a,b} 2K fifo each, ep-{c,d} unavailable + * mode 2 == ep-a 2K fifo, ep-{b,c} 1K each, ep-d unavailable + */ +static ushort fifo_mode = 0; + +/* "modprobe net2280 fifo_mode=1" etc */ +module_param (fifo_mode, ushort, 0644); + +/* enable_suspend -- When enabled, the driver will respond to + * USB suspend requests by powering down the NET2280. Otherwise, + * USB suspend requests will be ignored. This is acceptable for + * self-powered devices + */ +static bool enable_suspend = 0; + +/* "modprobe net2280 enable_suspend=1" etc */ +module_param (enable_suspend, bool, S_IRUGO); + +/* force full-speed operation */ +static bool full_speed; +module_param(full_speed, bool, 0444); +MODULE_PARM_DESC(full_speed, "force full-speed mode -- for testing only!"); + +#define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "in" : "out") + +#if defined(CONFIG_USB_GADGET_DEBUG_FILES) || defined (DEBUG) +static char *type_string (u8 bmAttributes) +{ + switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: return "bulk"; + case USB_ENDPOINT_XFER_ISOC: return "iso"; + case USB_ENDPOINT_XFER_INT: return "intr"; + }; + return "control"; +} +#endif + +#include "net2280.h" + +#define valid_bit cpu_to_le32 (1 << VALID_BIT) +#define dma_done_ie cpu_to_le32 (1 << DMA_DONE_INTERRUPT_ENABLE) + +/*-------------------------------------------------------------------------*/ + +static int +net2280_enable (struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct net2280 *dev; + struct net2280_ep *ep; + u32 max, tmp; + unsigned long flags; + + ep = container_of (_ep, struct net2280_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + /* erratum 0119 workaround ties up an endpoint number */ + if ((desc->bEndpointAddress & 0x0f) == EP_DONTUSE) + return -EDOM; + + /* sanity check ep-e/ep-f since their fifos are small */ + max = usb_endpoint_maxp (desc) & 0x1fff; + if (ep->num > 4 && max > 64) + return -ERANGE; + + spin_lock_irqsave (&dev->lock, flags); + _ep->maxpacket = max & 0x7ff; + ep->desc = desc; + + /* ep_reset() has already been called */ + ep->stopped = 0; + ep->wedged = 0; + ep->out_overflow = 0; + + /* set speed-dependent max packet; may kick in high bandwidth */ + set_idx_reg (dev->regs, REG_EP_MAXPKT (dev, ep->num), max); + + /* FIFO lines can't go to different packets. PIO is ok, so + * use it instead of troublesome (non-bulk) multi-packet DMA. + */ + if (ep->dma && (max % 4) != 0 && use_dma_chaining) { + DEBUG (ep->dev, "%s, no dma for maxpacket %d\n", + ep->ep.name, ep->ep.maxpacket); + ep->dma = NULL; + } + + /* set type, direction, address; reset fifo counters */ + writel ((1 << FIFO_FLUSH), &ep->regs->ep_stat); + tmp = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); + if (tmp == USB_ENDPOINT_XFER_INT) { + /* erratum 0105 workaround prevents hs NYET */ + if (dev->chiprev == 0100 + && dev->gadget.speed == USB_SPEED_HIGH + && !(desc->bEndpointAddress & USB_DIR_IN)) + writel ((1 << CLEAR_NAK_OUT_PACKETS_MODE), + &ep->regs->ep_rsp); + } else if (tmp == USB_ENDPOINT_XFER_BULK) { + /* catch some particularly blatant driver bugs */ + if ((dev->gadget.speed == USB_SPEED_HIGH + && max != 512) + || (dev->gadget.speed == USB_SPEED_FULL + && max > 64)) { + spin_unlock_irqrestore (&dev->lock, flags); + return -ERANGE; + } + } + ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC) ? 1 : 0; + tmp <<= ENDPOINT_TYPE; + tmp |= desc->bEndpointAddress; + tmp |= (4 << ENDPOINT_BYTE_COUNT); /* default full fifo lines */ + tmp |= 1 << ENDPOINT_ENABLE; + wmb (); + + /* for OUT transfers, block the rx fifo until a read is posted */ + ep->is_in = (tmp & USB_DIR_IN) != 0; + if (!ep->is_in) + writel ((1 << SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + else if (dev->pdev->device != 0x2280) { + /* Added for 2282, Don't use nak packets on an in endpoint, + * this was ignored on 2280 + */ + writel ((1 << CLEAR_NAK_OUT_PACKETS) + | (1 << CLEAR_NAK_OUT_PACKETS_MODE), &ep->regs->ep_rsp); + } + + writel (tmp, &ep->regs->ep_cfg); + + /* enable irqs */ + if (!ep->dma) { /* pio, per-packet */ + tmp = (1 << ep->num) | readl (&dev->regs->pciirqenb0); + writel (tmp, &dev->regs->pciirqenb0); + + tmp = (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) + | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE); + if (dev->pdev->device == 0x2280) + tmp |= readl (&ep->regs->ep_irqenb); + writel (tmp, &ep->regs->ep_irqenb); + } else { /* dma, per-request */ + tmp = (1 << (8 + ep->num)); /* completion */ + tmp |= readl (&dev->regs->pciirqenb1); + writel (tmp, &dev->regs->pciirqenb1); + + /* for short OUT transfers, dma completions can't + * advance the queue; do it pio-style, by hand. + * NOTE erratum 0112 workaround #2 + */ + if ((desc->bEndpointAddress & USB_DIR_IN) == 0) { + tmp = (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE); + writel (tmp, &ep->regs->ep_irqenb); + + tmp = (1 << ep->num) | readl (&dev->regs->pciirqenb0); + writel (tmp, &dev->regs->pciirqenb0); + } + } + + tmp = desc->bEndpointAddress; + DEBUG (dev, "enabled %s (ep%d%s-%s) %s max %04x\n", + _ep->name, tmp & 0x0f, DIR_STRING (tmp), + type_string (desc->bmAttributes), + ep->dma ? "dma" : "pio", max); + + /* pci writes may still be posted */ + spin_unlock_irqrestore (&dev->lock, flags); + return 0; +} + +static int handshake (u32 __iomem *ptr, u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = readl (ptr); + if (result == ~(u32)0) /* "device unplugged" */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay (1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +static const struct usb_ep_ops net2280_ep_ops; + +static void ep_reset (struct net2280_regs __iomem *regs, struct net2280_ep *ep) +{ + u32 tmp; + + ep->desc = NULL; + INIT_LIST_HEAD (&ep->queue); + + ep->ep.maxpacket = ~0; + ep->ep.ops = &net2280_ep_ops; + + /* disable the dma, irqs, endpoint... */ + if (ep->dma) { + writel (0, &ep->dma->dmactl); + writel ( (1 << DMA_SCATTER_GATHER_DONE_INTERRUPT) + | (1 << DMA_TRANSACTION_DONE_INTERRUPT) + | (1 << DMA_ABORT) + , &ep->dma->dmastat); + + tmp = readl (®s->pciirqenb0); + tmp &= ~(1 << ep->num); + writel (tmp, ®s->pciirqenb0); + } else { + tmp = readl (®s->pciirqenb1); + tmp &= ~(1 << (8 + ep->num)); /* completion */ + writel (tmp, ®s->pciirqenb1); + } + writel (0, &ep->regs->ep_irqenb); + + /* init to our chosen defaults, notably so that we NAK OUT + * packets until the driver queues a read (+note erratum 0112) + */ + if (!ep->is_in || ep->dev->pdev->device == 0x2280) { + tmp = (1 << SET_NAK_OUT_PACKETS_MODE) + | (1 << SET_NAK_OUT_PACKETS) + | (1 << CLEAR_EP_HIDE_STATUS_PHASE) + | (1 << CLEAR_INTERRUPT_MODE); + } else { + /* added for 2282 */ + tmp = (1 << CLEAR_NAK_OUT_PACKETS_MODE) + | (1 << CLEAR_NAK_OUT_PACKETS) + | (1 << CLEAR_EP_HIDE_STATUS_PHASE) + | (1 << CLEAR_INTERRUPT_MODE); + } + + if (ep->num != 0) { + tmp |= (1 << CLEAR_ENDPOINT_TOGGLE) + | (1 << CLEAR_ENDPOINT_HALT); + } + writel (tmp, &ep->regs->ep_rsp); + + /* scrub most status bits, and flush any fifo state */ + if (ep->dev->pdev->device == 0x2280) + tmp = (1 << FIFO_OVERFLOW) + | (1 << FIFO_UNDERFLOW); + else + tmp = 0; + + writel (tmp | (1 << TIMEOUT) + | (1 << USB_STALL_SENT) + | (1 << USB_IN_NAK_SENT) + | (1 << USB_IN_ACK_RCVD) + | (1 << USB_OUT_PING_NAK_SENT) + | (1 << USB_OUT_ACK_SENT) + | (1 << FIFO_FLUSH) + | (1 << SHORT_PACKET_OUT_DONE_INTERRUPT) + | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT) + | (1 << DATA_PACKET_RECEIVED_INTERRUPT) + | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) + | (1 << DATA_OUT_PING_TOKEN_INTERRUPT) + | (1 << DATA_IN_TOKEN_INTERRUPT) + , &ep->regs->ep_stat); + + /* fifo size is handled separately */ +} + +static void nuke (struct net2280_ep *); + +static int net2280_disable (struct usb_ep *_ep) +{ + struct net2280_ep *ep; + unsigned long flags; + + ep = container_of (_ep, struct net2280_ep, ep); + if (!_ep || !ep->desc || _ep->name == ep0name) + return -EINVAL; + + spin_lock_irqsave (&ep->dev->lock, flags); + nuke (ep); + ep_reset (ep->dev->regs, ep); + + VDEBUG (ep->dev, "disabled %s %s\n", + ep->dma ? "dma" : "pio", _ep->name); + + /* synch memory views with the device */ + (void) readl (&ep->regs->ep_cfg); + + if (use_dma && !ep->dma && ep->num >= 1 && ep->num <= 4) + ep->dma = &ep->dev->dma [ep->num - 1]; + + spin_unlock_irqrestore (&ep->dev->lock, flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_request * +net2280_alloc_request (struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct net2280_ep *ep; + struct net2280_request *req; + + if (!_ep) + return NULL; + ep = container_of (_ep, struct net2280_ep, ep); + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD (&req->queue); + + /* this dma descriptor may be swapped with the previous dummy */ + if (ep->dma) { + struct net2280_dma *td; + + td = pci_pool_alloc (ep->dev->requests, gfp_flags, + &req->td_dma); + if (!td) { + kfree (req); + return NULL; + } + td->dmacount = 0; /* not VALID */ + td->dmadesc = td->dmaaddr; + req->td = td; + } + return &req->req; +} + +static void +net2280_free_request (struct usb_ep *_ep, struct usb_request *_req) +{ + struct net2280_ep *ep; + struct net2280_request *req; + + ep = container_of (_ep, struct net2280_ep, ep); + if (!_ep || !_req) + return; + + req = container_of (_req, struct net2280_request, req); + WARN_ON (!list_empty (&req->queue)); + if (req->td) + pci_pool_free (ep->dev->requests, req->td, req->td_dma); + kfree (req); +} + +/*-------------------------------------------------------------------------*/ + +/* load a packet into the fifo we use for usb IN transfers. + * works for all endpoints. + * + * NOTE: pio with ep-a..ep-d could stuff multiple packets into the fifo + * at a time, but this code is simpler because it knows it only writes + * one packet. ep-a..ep-d should use dma instead. + */ +static void +write_fifo (struct net2280_ep *ep, struct usb_request *req) +{ + struct net2280_ep_regs __iomem *regs = ep->regs; + u8 *buf; + u32 tmp; + unsigned count, total; + + /* INVARIANT: fifo is currently empty. (testable) */ + + if (req) { + buf = req->buf + req->actual; + prefetch (buf); + total = req->length - req->actual; + } else { + total = 0; + buf = NULL; + } + + /* write just one packet at a time */ + count = ep->ep.maxpacket; + if (count > total) /* min() cannot be used on a bitfield */ + count = total; + + VDEBUG (ep->dev, "write %s fifo (IN) %d bytes%s req %p\n", + ep->ep.name, count, + (count != ep->ep.maxpacket) ? " (short)" : "", + req); + while (count >= 4) { + /* NOTE be careful if you try to align these. fifo lines + * should normally be full (4 bytes) and successive partial + * lines are ok only in certain cases. + */ + tmp = get_unaligned ((u32 *)buf); + cpu_to_le32s (&tmp); + writel (tmp, ®s->ep_data); + buf += 4; + count -= 4; + } + + /* last fifo entry is "short" unless we wrote a full packet. + * also explicitly validate last word in (periodic) transfers + * when maxpacket is not a multiple of 4 bytes. + */ + if (count || total < ep->ep.maxpacket) { + tmp = count ? get_unaligned ((u32 *)buf) : count; + cpu_to_le32s (&tmp); + set_fifo_bytecount (ep, count & 0x03); + writel (tmp, ®s->ep_data); + } + + /* pci writes may still be posted */ +} + +/* work around erratum 0106: PCI and USB race over the OUT fifo. + * caller guarantees chiprev 0100, out endpoint is NAKing, and + * there's no real data in the fifo. + * + * NOTE: also used in cases where that erratum doesn't apply: + * where the host wrote "too much" data to us. + */ +static void out_flush (struct net2280_ep *ep) +{ + u32 __iomem *statp; + u32 tmp; + + ASSERT_OUT_NAKING (ep); + + statp = &ep->regs->ep_stat; + writel ( (1 << DATA_OUT_PING_TOKEN_INTERRUPT) + | (1 << DATA_PACKET_RECEIVED_INTERRUPT) + , statp); + writel ((1 << FIFO_FLUSH), statp); + mb (); + tmp = readl (statp); + if (tmp & (1 << DATA_OUT_PING_TOKEN_INTERRUPT) + /* high speed did bulk NYET; fifo isn't filling */ + && ep->dev->gadget.speed == USB_SPEED_FULL) { + unsigned usec; + + usec = 50; /* 64 byte bulk/interrupt */ + handshake (statp, (1 << USB_OUT_PING_NAK_SENT), + (1 << USB_OUT_PING_NAK_SENT), usec); + /* NAK done; now CLEAR_NAK_OUT_PACKETS is safe */ + } +} + +/* unload packet(s) from the fifo we use for usb OUT transfers. + * returns true iff the request completed, because of short packet + * or the request buffer having filled with full packets. + * + * for ep-a..ep-d this will read multiple packets out when they + * have been accepted. + */ +static int +read_fifo (struct net2280_ep *ep, struct net2280_request *req) +{ + struct net2280_ep_regs __iomem *regs = ep->regs; + u8 *buf = req->req.buf + req->req.actual; + unsigned count, tmp, is_short; + unsigned cleanup = 0, prevent = 0; + + /* erratum 0106 ... packets coming in during fifo reads might + * be incompletely rejected. not all cases have workarounds. + */ + if (ep->dev->chiprev == 0x0100 + && ep->dev->gadget.speed == USB_SPEED_FULL) { + udelay (1); + tmp = readl (&ep->regs->ep_stat); + if ((tmp & (1 << NAK_OUT_PACKETS))) + cleanup = 1; + else if ((tmp & (1 << FIFO_FULL))) { + start_out_naking (ep); + prevent = 1; + } + /* else: hope we don't see the problem */ + } + + /* never overflow the rx buffer. the fifo reads packets until + * it sees a short one; we might not be ready for them all. + */ + prefetchw (buf); + count = readl (®s->ep_avail); + if (unlikely (count == 0)) { + udelay (1); + tmp = readl (&ep->regs->ep_stat); + count = readl (®s->ep_avail); + /* handled that data already? */ + if (count == 0 && (tmp & (1 << NAK_OUT_PACKETS)) == 0) + return 0; + } + + tmp = req->req.length - req->req.actual; + if (count > tmp) { + /* as with DMA, data overflow gets flushed */ + if ((tmp % ep->ep.maxpacket) != 0) { + ERROR (ep->dev, + "%s out fifo %d bytes, expected %d\n", + ep->ep.name, count, tmp); + req->req.status = -EOVERFLOW; + cleanup = 1; + /* NAK_OUT_PACKETS will be set, so flushing is safe; + * the next read will start with the next packet + */ + } /* else it's a ZLP, no worries */ + count = tmp; + } + req->req.actual += count; + + is_short = (count == 0) || ((count % ep->ep.maxpacket) != 0); + + VDEBUG (ep->dev, "read %s fifo (OUT) %d bytes%s%s%s req %p %d/%d\n", + ep->ep.name, count, is_short ? " (short)" : "", + cleanup ? " flush" : "", prevent ? " nak" : "", + req, req->req.actual, req->req.length); + + while (count >= 4) { + tmp = readl (®s->ep_data); + cpu_to_le32s (&tmp); + put_unaligned (tmp, (u32 *)buf); + buf += 4; + count -= 4; + } + if (count) { + tmp = readl (®s->ep_data); + /* LE conversion is implicit here: */ + do { + *buf++ = (u8) tmp; + tmp >>= 8; + } while (--count); + } + if (cleanup) + out_flush (ep); + if (prevent) { + writel ((1 << CLEAR_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + (void) readl (&ep->regs->ep_rsp); + } + + return is_short || ((req->req.actual == req->req.length) + && !req->req.zero); +} + +/* fill out dma descriptor to match a given request */ +static void +fill_dma_desc (struct net2280_ep *ep, struct net2280_request *req, int valid) +{ + struct net2280_dma *td = req->td; + u32 dmacount = req->req.length; + + /* don't let DMA continue after a short OUT packet, + * so overruns can't affect the next transfer. + * in case of overruns on max-size packets, we can't + * stop the fifo from filling but we can flush it. + */ + if (ep->is_in) + dmacount |= (1 << DMA_DIRECTION); + if ((!ep->is_in && (dmacount % ep->ep.maxpacket) != 0) + || ep->dev->pdev->device != 0x2280) + dmacount |= (1 << END_OF_CHAIN); + + req->valid = valid; + if (valid) + dmacount |= (1 << VALID_BIT); + if (likely(!req->req.no_interrupt || !use_dma_chaining)) + dmacount |= (1 << DMA_DONE_INTERRUPT_ENABLE); + + /* td->dmadesc = previously set by caller */ + td->dmaaddr = cpu_to_le32 (req->req.dma); + + /* 2280 may be polling VALID_BIT through ep->dma->dmadesc */ + wmb (); + td->dmacount = cpu_to_le32(dmacount); +} + +static const u32 dmactl_default = + (1 << DMA_SCATTER_GATHER_DONE_INTERRUPT) + | (1 << DMA_CLEAR_COUNT_ENABLE) + /* erratum 0116 workaround part 1 (use POLLING) */ + | (POLL_100_USEC << DESCRIPTOR_POLLING_RATE) + | (1 << DMA_VALID_BIT_POLLING_ENABLE) + | (1 << DMA_VALID_BIT_ENABLE) + | (1 << DMA_SCATTER_GATHER_ENABLE) + /* erratum 0116 workaround part 2 (no AUTOSTART) */ + | (1 << DMA_ENABLE); + +static inline void spin_stop_dma (struct net2280_dma_regs __iomem *dma) +{ + handshake (&dma->dmactl, (1 << DMA_ENABLE), 0, 50); +} + +static inline void stop_dma (struct net2280_dma_regs __iomem *dma) +{ + writel (readl (&dma->dmactl) & ~(1 << DMA_ENABLE), &dma->dmactl); + spin_stop_dma (dma); +} + +static void start_queue (struct net2280_ep *ep, u32 dmactl, u32 td_dma) +{ + struct net2280_dma_regs __iomem *dma = ep->dma; + unsigned int tmp = (1 << VALID_BIT) | (ep->is_in << DMA_DIRECTION); + + if (ep->dev->pdev->device != 0x2280) + tmp |= (1 << END_OF_CHAIN); + + writel (tmp, &dma->dmacount); + writel (readl (&dma->dmastat), &dma->dmastat); + + writel (td_dma, &dma->dmadesc); + writel (dmactl, &dma->dmactl); + + /* erratum 0116 workaround part 3: pci arbiter away from net2280 */ + (void) readl (&ep->dev->pci->pcimstctl); + + writel ((1 << DMA_START), &dma->dmastat); + + if (!ep->is_in) + stop_out_naking (ep); +} + +static void start_dma (struct net2280_ep *ep, struct net2280_request *req) +{ + u32 tmp; + struct net2280_dma_regs __iomem *dma = ep->dma; + + /* FIXME can't use DMA for ZLPs */ + + /* on this path we "know" there's no dma active (yet) */ + WARN_ON (readl (&dma->dmactl) & (1 << DMA_ENABLE)); + writel (0, &ep->dma->dmactl); + + /* previous OUT packet might have been short */ + if (!ep->is_in && ((tmp = readl (&ep->regs->ep_stat)) + & (1 << NAK_OUT_PACKETS)) != 0) { + writel ((1 << SHORT_PACKET_TRANSFERRED_INTERRUPT), + &ep->regs->ep_stat); + + tmp = readl (&ep->regs->ep_avail); + if (tmp) { + writel (readl (&dma->dmastat), &dma->dmastat); + + /* transfer all/some fifo data */ + writel (req->req.dma, &dma->dmaaddr); + tmp = min (tmp, req->req.length); + + /* dma irq, faking scatterlist status */ + req->td->dmacount = cpu_to_le32 (req->req.length - tmp); + writel ((1 << DMA_DONE_INTERRUPT_ENABLE) + | tmp, &dma->dmacount); + req->td->dmadesc = 0; + req->valid = 1; + + writel ((1 << DMA_ENABLE), &dma->dmactl); + writel ((1 << DMA_START), &dma->dmastat); + return; + } + } + + tmp = dmactl_default; + + /* force packet boundaries between dma requests, but prevent the + * controller from automagically writing a last "short" packet + * (zero length) unless the driver explicitly said to do that. + */ + if (ep->is_in) { + if (likely ((req->req.length % ep->ep.maxpacket) != 0 + || req->req.zero)) { + tmp |= (1 << DMA_FIFO_VALIDATE); + ep->in_fifo_validate = 1; + } else + ep->in_fifo_validate = 0; + } + + /* init req->td, pointing to the current dummy */ + req->td->dmadesc = cpu_to_le32 (ep->td_dma); + fill_dma_desc (ep, req, 1); + + if (!use_dma_chaining) + req->td->dmacount |= cpu_to_le32 (1 << END_OF_CHAIN); + + start_queue (ep, tmp, req->td_dma); +} + +static inline void +queue_dma (struct net2280_ep *ep, struct net2280_request *req, int valid) +{ + struct net2280_dma *end; + dma_addr_t tmp; + + /* swap new dummy for old, link; fill and maybe activate */ + end = ep->dummy; + ep->dummy = req->td; + req->td = end; + + tmp = ep->td_dma; + ep->td_dma = req->td_dma; + req->td_dma = tmp; + + end->dmadesc = cpu_to_le32 (ep->td_dma); + + fill_dma_desc (ep, req, valid); +} + +static void +done (struct net2280_ep *ep, struct net2280_request *req, int status) +{ + struct net2280 *dev; + unsigned stopped = ep->stopped; + + list_del_init (&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + dev = ep->dev; + if (ep->dma) + usb_gadget_unmap_request(&dev->gadget, &req->req, ep->is_in); + + if (status && status != -ESHUTDOWN) + VDEBUG (dev, "complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + spin_unlock (&dev->lock); + req->req.complete (&ep->ep, &req->req); + spin_lock (&dev->lock); + ep->stopped = stopped; +} + +/*-------------------------------------------------------------------------*/ + +static int +net2280_queue (struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct net2280_request *req; + struct net2280_ep *ep; + struct net2280 *dev; + unsigned long flags; + + /* we always require a cpu-view buffer, so that we can + * always use pio (as fallback or whatever). + */ + req = container_of (_req, struct net2280_request, req); + if (!_req || !_req->complete || !_req->buf + || !list_empty (&req->queue)) + return -EINVAL; + if (_req->length > (~0 & DMA_BYTE_COUNT_MASK)) + return -EDOM; + ep = container_of (_ep, struct net2280_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return -EINVAL; + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + /* FIXME implement PIO fallback for ZLPs with DMA */ + if (ep->dma && _req->length == 0) + return -EOPNOTSUPP; + + /* set up dma mapping in case the caller didn't */ + if (ep->dma) { + int ret; + + ret = usb_gadget_map_request(&dev->gadget, _req, + ep->is_in); + if (ret) + return ret; + } + +#if 0 + VDEBUG (dev, "%s queue req %p, len %d buf %p\n", + _ep->name, _req, _req->length, _req->buf); +#endif + + spin_lock_irqsave (&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + if (list_empty (&ep->queue) && !ep->stopped) { + /* use DMA if the endpoint supports it, else pio */ + if (ep->dma) + start_dma (ep, req); + else { + /* maybe there's no control data, just status ack */ + if (ep->num == 0 && _req->length == 0) { + allow_status (ep); + done (ep, req, 0); + VDEBUG (dev, "%s status ack\n", ep->ep.name); + goto done; + } + + /* PIO ... stuff the fifo, or unblock it. */ + if (ep->is_in) + write_fifo (ep, _req); + else if (list_empty (&ep->queue)) { + u32 s; + + /* OUT FIFO might have packet(s) buffered */ + s = readl (&ep->regs->ep_stat); + if ((s & (1 << FIFO_EMPTY)) == 0) { + /* note: _req->short_not_ok is + * ignored here since PIO _always_ + * stops queue advance here, and + * _req->status doesn't change for + * short reads (only _req->actual) + */ + if (read_fifo (ep, req)) { + done (ep, req, 0); + if (ep->num == 0) + allow_status (ep); + /* don't queue it */ + req = NULL; + } else + s = readl (&ep->regs->ep_stat); + } + + /* don't NAK, let the fifo fill */ + if (req && (s & (1 << NAK_OUT_PACKETS))) + writel ((1 << CLEAR_NAK_OUT_PACKETS), + &ep->regs->ep_rsp); + } + } + + } else if (ep->dma) { + int valid = 1; + + if (ep->is_in) { + int expect; + + /* preventing magic zlps is per-engine state, not + * per-transfer; irq logic must recover hiccups. + */ + expect = likely (req->req.zero + || (req->req.length % ep->ep.maxpacket) != 0); + if (expect != ep->in_fifo_validate) + valid = 0; + } + queue_dma (ep, req, valid); + + } /* else the irq handler advances the queue. */ + + ep->responded = 1; + if (req) + list_add_tail (&req->queue, &ep->queue); +done: + spin_unlock_irqrestore (&dev->lock, flags); + + /* pci writes may still be posted */ + return 0; +} + +static inline void +dma_done ( + struct net2280_ep *ep, + struct net2280_request *req, + u32 dmacount, + int status +) +{ + req->req.actual = req->req.length - (DMA_BYTE_COUNT_MASK & dmacount); + done (ep, req, status); +} + +static void restart_dma (struct net2280_ep *ep); + +static void scan_dma_completions (struct net2280_ep *ep) +{ + /* only look at descriptors that were "naturally" retired, + * so fifo and list head state won't matter + */ + while (!list_empty (&ep->queue)) { + struct net2280_request *req; + u32 tmp; + + req = list_entry (ep->queue.next, + struct net2280_request, queue); + if (!req->valid) + break; + rmb (); + tmp = le32_to_cpup (&req->td->dmacount); + if ((tmp & (1 << VALID_BIT)) != 0) + break; + + /* SHORT_PACKET_TRANSFERRED_INTERRUPT handles "usb-short" + * cases where DMA must be aborted; this code handles + * all non-abort DMA completions. + */ + if (unlikely (req->td->dmadesc == 0)) { + /* paranoia */ + tmp = readl (&ep->dma->dmacount); + if (tmp & DMA_BYTE_COUNT_MASK) + break; + /* single transfer mode */ + dma_done (ep, req, tmp, 0); + break; + } else if (!ep->is_in + && (req->req.length % ep->ep.maxpacket) != 0) { + tmp = readl (&ep->regs->ep_stat); + + /* AVOID TROUBLE HERE by not issuing short reads from + * your gadget driver. That helps avoids errata 0121, + * 0122, and 0124; not all cases trigger the warning. + */ + if ((tmp & (1 << NAK_OUT_PACKETS)) == 0) { + WARNING (ep->dev, "%s lost packet sync!\n", + ep->ep.name); + req->req.status = -EOVERFLOW; + } else if ((tmp = readl (&ep->regs->ep_avail)) != 0) { + /* fifo gets flushed later */ + ep->out_overflow = 1; + DEBUG (ep->dev, "%s dma, discard %d len %d\n", + ep->ep.name, tmp, + req->req.length); + req->req.status = -EOVERFLOW; + } + } + dma_done (ep, req, tmp, 0); + } +} + +static void restart_dma (struct net2280_ep *ep) +{ + struct net2280_request *req; + u32 dmactl = dmactl_default; + + if (ep->stopped) + return; + req = list_entry (ep->queue.next, struct net2280_request, queue); + + if (!use_dma_chaining) { + start_dma (ep, req); + return; + } + + /* the 2280 will be processing the queue unless queue hiccups after + * the previous transfer: + * IN: wanted automagic zlp, head doesn't (or vice versa) + * DMA_FIFO_VALIDATE doesn't init from dma descriptors. + * OUT: was "usb-short", we must restart. + */ + if (ep->is_in && !req->valid) { + struct net2280_request *entry, *prev = NULL; + int reqmode, done = 0; + + DEBUG (ep->dev, "%s dma hiccup td %p\n", ep->ep.name, req->td); + ep->in_fifo_validate = likely (req->req.zero + || (req->req.length % ep->ep.maxpacket) != 0); + if (ep->in_fifo_validate) + dmactl |= (1 << DMA_FIFO_VALIDATE); + list_for_each_entry (entry, &ep->queue, queue) { + __le32 dmacount; + + if (entry == req) + continue; + dmacount = entry->td->dmacount; + if (!done) { + reqmode = likely (entry->req.zero + || (entry->req.length + % ep->ep.maxpacket) != 0); + if (reqmode == ep->in_fifo_validate) { + entry->valid = 1; + dmacount |= valid_bit; + entry->td->dmacount = dmacount; + prev = entry; + continue; + } else { + /* force a hiccup */ + prev->td->dmacount |= dma_done_ie; + done = 1; + } + } + + /* walk the rest of the queue so unlinks behave */ + entry->valid = 0; + dmacount &= ~valid_bit; + entry->td->dmacount = dmacount; + prev = entry; + } + } + + writel (0, &ep->dma->dmactl); + start_queue (ep, dmactl, req->td_dma); +} + +static void abort_dma (struct net2280_ep *ep) +{ + /* abort the current transfer */ + if (likely (!list_empty (&ep->queue))) { + /* FIXME work around errata 0121, 0122, 0124 */ + writel ((1 << DMA_ABORT), &ep->dma->dmastat); + spin_stop_dma (ep->dma); + } else + stop_dma (ep->dma); + scan_dma_completions (ep); +} + +/* dequeue ALL requests */ +static void nuke (struct net2280_ep *ep) +{ + struct net2280_request *req; + + /* called with spinlock held */ + ep->stopped = 1; + if (ep->dma) + abort_dma (ep); + while (!list_empty (&ep->queue)) { + req = list_entry (ep->queue.next, + struct net2280_request, + queue); + done (ep, req, -ESHUTDOWN); + } +} + +/* dequeue JUST ONE request */ +static int net2280_dequeue (struct usb_ep *_ep, struct usb_request *_req) +{ + struct net2280_ep *ep; + struct net2280_request *req; + unsigned long flags; + u32 dmactl; + int stopped; + + ep = container_of (_ep, struct net2280_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0) || !_req) + return -EINVAL; + + spin_lock_irqsave (&ep->dev->lock, flags); + stopped = ep->stopped; + + /* quiesce dma while we patch the queue */ + dmactl = 0; + ep->stopped = 1; + if (ep->dma) { + dmactl = readl (&ep->dma->dmactl); + /* WARNING erratum 0127 may kick in ... */ + stop_dma (ep->dma); + scan_dma_completions (ep); + } + + /* make sure it's still queued on this endpoint */ + list_for_each_entry (req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore (&ep->dev->lock, flags); + return -EINVAL; + } + + /* queue head may be partially complete. */ + if (ep->queue.next == &req->queue) { + if (ep->dma) { + DEBUG (ep->dev, "unlink (%s) dma\n", _ep->name); + _req->status = -ECONNRESET; + abort_dma (ep); + if (likely (ep->queue.next == &req->queue)) { + // NOTE: misreports single-transfer mode + req->td->dmacount = 0; /* invalidate */ + dma_done (ep, req, + readl (&ep->dma->dmacount), + -ECONNRESET); + } + } else { + DEBUG (ep->dev, "unlink (%s) pio\n", _ep->name); + done (ep, req, -ECONNRESET); + } + req = NULL; + + /* patch up hardware chaining data */ + } else if (ep->dma && use_dma_chaining) { + if (req->queue.prev == ep->queue.next) { + writel (le32_to_cpu (req->td->dmadesc), + &ep->dma->dmadesc); + if (req->td->dmacount & dma_done_ie) + writel (readl (&ep->dma->dmacount) + | le32_to_cpu(dma_done_ie), + &ep->dma->dmacount); + } else { + struct net2280_request *prev; + + prev = list_entry (req->queue.prev, + struct net2280_request, queue); + prev->td->dmadesc = req->td->dmadesc; + if (req->td->dmacount & dma_done_ie) + prev->td->dmacount |= dma_done_ie; + } + } + + if (req) + done (ep, req, -ECONNRESET); + ep->stopped = stopped; + + if (ep->dma) { + /* turn off dma on inactive queues */ + if (list_empty (&ep->queue)) + stop_dma (ep->dma); + else if (!ep->stopped) { + /* resume current request, or start new one */ + if (req) + writel (dmactl, &ep->dma->dmactl); + else + start_dma (ep, list_entry (ep->queue.next, + struct net2280_request, queue)); + } + } + + spin_unlock_irqrestore (&ep->dev->lock, flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int net2280_fifo_status (struct usb_ep *_ep); + +static int +net2280_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) +{ + struct net2280_ep *ep; + unsigned long flags; + int retval = 0; + + ep = container_of (_ep, struct net2280_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return -EINVAL; + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + if (ep->desc /* not ep0 */ && (ep->desc->bmAttributes & 0x03) + == USB_ENDPOINT_XFER_ISOC) + return -EINVAL; + + spin_lock_irqsave (&ep->dev->lock, flags); + if (!list_empty (&ep->queue)) + retval = -EAGAIN; + else if (ep->is_in && value && net2280_fifo_status (_ep) != 0) + retval = -EAGAIN; + else { + VDEBUG (ep->dev, "%s %s %s\n", _ep->name, + value ? "set" : "clear", + wedged ? "wedge" : "halt"); + /* set/clear, then synch memory views with the device */ + if (value) { + if (ep->num == 0) + ep->dev->protocol_stall = 1; + else + set_halt (ep); + if (wedged) + ep->wedged = 1; + } else { + clear_halt (ep); + ep->wedged = 0; + } + (void) readl (&ep->regs->ep_rsp); + } + spin_unlock_irqrestore (&ep->dev->lock, flags); + + return retval; +} + +static int +net2280_set_halt(struct usb_ep *_ep, int value) +{ + return net2280_set_halt_and_wedge(_ep, value, 0); +} + +static int +net2280_set_wedge(struct usb_ep *_ep) +{ + if (!_ep || _ep->name == ep0name) + return -EINVAL; + return net2280_set_halt_and_wedge(_ep, 1, 1); +} + +static int +net2280_fifo_status (struct usb_ep *_ep) +{ + struct net2280_ep *ep; + u32 avail; + + ep = container_of (_ep, struct net2280_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return -ENODEV; + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + avail = readl (&ep->regs->ep_avail) & ((1 << 12) - 1); + if (avail > ep->fifo_size) + return -EOVERFLOW; + if (ep->is_in) + avail = ep->fifo_size - avail; + return avail; +} + +static void +net2280_fifo_flush (struct usb_ep *_ep) +{ + struct net2280_ep *ep; + + ep = container_of (_ep, struct net2280_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return; + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return; + + writel ((1 << FIFO_FLUSH), &ep->regs->ep_stat); + (void) readl (&ep->regs->ep_rsp); +} + +static const struct usb_ep_ops net2280_ep_ops = { + .enable = net2280_enable, + .disable = net2280_disable, + + .alloc_request = net2280_alloc_request, + .free_request = net2280_free_request, + + .queue = net2280_queue, + .dequeue = net2280_dequeue, + + .set_halt = net2280_set_halt, + .set_wedge = net2280_set_wedge, + .fifo_status = net2280_fifo_status, + .fifo_flush = net2280_fifo_flush, +}; + +/*-------------------------------------------------------------------------*/ + +static int net2280_get_frame (struct usb_gadget *_gadget) +{ + struct net2280 *dev; + unsigned long flags; + u16 retval; + + if (!_gadget) + return -ENODEV; + dev = container_of (_gadget, struct net2280, gadget); + spin_lock_irqsave (&dev->lock, flags); + retval = get_idx_reg (dev->regs, REG_FRAME) & 0x03ff; + spin_unlock_irqrestore (&dev->lock, flags); + return retval; +} + +static int net2280_wakeup (struct usb_gadget *_gadget) +{ + struct net2280 *dev; + u32 tmp; + unsigned long flags; + + if (!_gadget) + return 0; + dev = container_of (_gadget, struct net2280, gadget); + + spin_lock_irqsave (&dev->lock, flags); + tmp = readl (&dev->usb->usbctl); + if (tmp & (1 << DEVICE_REMOTE_WAKEUP_ENABLE)) + writel (1 << GENERATE_RESUME, &dev->usb->usbstat); + spin_unlock_irqrestore (&dev->lock, flags); + + /* pci writes may still be posted */ + return 0; +} + +static int net2280_set_selfpowered (struct usb_gadget *_gadget, int value) +{ + struct net2280 *dev; + u32 tmp; + unsigned long flags; + + if (!_gadget) + return 0; + dev = container_of (_gadget, struct net2280, gadget); + + spin_lock_irqsave (&dev->lock, flags); + tmp = readl (&dev->usb->usbctl); + if (value) + tmp |= (1 << SELF_POWERED_STATUS); + else + tmp &= ~(1 << SELF_POWERED_STATUS); + writel (tmp, &dev->usb->usbctl); + spin_unlock_irqrestore (&dev->lock, flags); + + return 0; +} + +static int net2280_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct net2280 *dev; + u32 tmp; + unsigned long flags; + + if (!_gadget) + return -ENODEV; + dev = container_of (_gadget, struct net2280, gadget); + + spin_lock_irqsave (&dev->lock, flags); + tmp = readl (&dev->usb->usbctl); + dev->softconnect = (is_on != 0); + if (is_on) + tmp |= (1 << USB_DETECT_ENABLE); + else + tmp &= ~(1 << USB_DETECT_ENABLE); + writel (tmp, &dev->usb->usbctl); + spin_unlock_irqrestore (&dev->lock, flags); + + return 0; +} + +static int net2280_start(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver); +static int net2280_stop(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops net2280_ops = { + .get_frame = net2280_get_frame, + .wakeup = net2280_wakeup, + .set_selfpowered = net2280_set_selfpowered, + .pullup = net2280_pullup, + .udc_start = net2280_start, + .udc_stop = net2280_stop, +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +/* FIXME move these into procfs, and use seq_file. + * Sysfs _still_ doesn't behave for arbitrarily sized files, + * and also doesn't help products using this with 2.4 kernels. + */ + +/* "function" sysfs attribute */ +static ssize_t +show_function (struct device *_dev, struct device_attribute *attr, char *buf) +{ + struct net2280 *dev = dev_get_drvdata (_dev); + + if (!dev->driver + || !dev->driver->function + || strlen (dev->driver->function) > PAGE_SIZE) + return 0; + return scnprintf (buf, PAGE_SIZE, "%s\n", dev->driver->function); +} +static DEVICE_ATTR (function, S_IRUGO, show_function, NULL); + +static ssize_t net2280_show_registers(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct net2280 *dev; + char *next; + unsigned size, t; + unsigned long flags; + int i; + u32 t1, t2; + const char *s; + + dev = dev_get_drvdata (_dev); + next = buf; + size = PAGE_SIZE; + spin_lock_irqsave (&dev->lock, flags); + + if (dev->driver) + s = dev->driver->driver.name; + else + s = "(none)"; + + /* Main Control Registers */ + t = scnprintf (next, size, "%s version " DRIVER_VERSION + ", chiprev %04x, dma %s\n\n" + "devinit %03x fifoctl %08x gadget '%s'\n" + "pci irqenb0 %02x irqenb1 %08x " + "irqstat0 %04x irqstat1 %08x\n", + driver_name, dev->chiprev, + use_dma + ? (use_dma_chaining ? "chaining" : "enabled") + : "disabled", + readl (&dev->regs->devinit), + readl (&dev->regs->fifoctl), + s, + readl (&dev->regs->pciirqenb0), + readl (&dev->regs->pciirqenb1), + readl (&dev->regs->irqstat0), + readl (&dev->regs->irqstat1)); + size -= t; + next += t; + + /* USB Control Registers */ + t1 = readl (&dev->usb->usbctl); + t2 = readl (&dev->usb->usbstat); + if (t1 & (1 << VBUS_PIN)) { + if (t2 & (1 << HIGH_SPEED)) + s = "high speed"; + else if (dev->gadget.speed == USB_SPEED_UNKNOWN) + s = "powered"; + else + s = "full speed"; + /* full speed bit (6) not working?? */ + } else + s = "not attached"; + t = scnprintf (next, size, + "stdrsp %08x usbctl %08x usbstat %08x " + "addr 0x%02x (%s)\n", + readl (&dev->usb->stdrsp), t1, t2, + readl (&dev->usb->ouraddr), s); + size -= t; + next += t; + + /* PCI Master Control Registers */ + + /* DMA Control Registers */ + + /* Configurable EP Control Registers */ + for (i = 0; i < 7; i++) { + struct net2280_ep *ep; + + ep = &dev->ep [i]; + if (i && !ep->desc) + continue; + + t1 = readl (&ep->regs->ep_cfg); + t2 = readl (&ep->regs->ep_rsp) & 0xff; + t = scnprintf (next, size, + "\n%s\tcfg %05x rsp (%02x) %s%s%s%s%s%s%s%s" + "irqenb %02x\n", + ep->ep.name, t1, t2, + (t2 & (1 << CLEAR_NAK_OUT_PACKETS)) + ? "NAK " : "", + (t2 & (1 << CLEAR_EP_HIDE_STATUS_PHASE)) + ? "hide " : "", + (t2 & (1 << CLEAR_EP_FORCE_CRC_ERROR)) + ? "CRC " : "", + (t2 & (1 << CLEAR_INTERRUPT_MODE)) + ? "interrupt " : "", + (t2 & (1<regs->ep_irqenb)); + size -= t; + next += t; + + t = scnprintf (next, size, + "\tstat %08x avail %04x " + "(ep%d%s-%s)%s\n", + readl (&ep->regs->ep_stat), + readl (&ep->regs->ep_avail), + t1 & 0x0f, DIR_STRING (t1), + type_string (t1 >> 8), + ep->stopped ? "*" : ""); + size -= t; + next += t; + + if (!ep->dma) + continue; + + t = scnprintf (next, size, + " dma\tctl %08x stat %08x count %08x\n" + "\taddr %08x desc %08x\n", + readl (&ep->dma->dmactl), + readl (&ep->dma->dmastat), + readl (&ep->dma->dmacount), + readl (&ep->dma->dmaaddr), + readl (&ep->dma->dmadesc)); + size -= t; + next += t; + + } + + /* Indexed Registers */ + // none yet + + /* Statistics */ + t = scnprintf (next, size, "\nirqs: "); + size -= t; + next += t; + for (i = 0; i < 7; i++) { + struct net2280_ep *ep; + + ep = &dev->ep [i]; + if (i && !ep->irqs) + continue; + t = scnprintf (next, size, " %s/%lu", ep->ep.name, ep->irqs); + size -= t; + next += t; + + } + t = scnprintf (next, size, "\n"); + size -= t; + next += t; + + spin_unlock_irqrestore (&dev->lock, flags); + + return PAGE_SIZE - size; +} +static DEVICE_ATTR(registers, S_IRUGO, net2280_show_registers, NULL); + +static ssize_t +show_queues (struct device *_dev, struct device_attribute *attr, char *buf) +{ + struct net2280 *dev; + char *next; + unsigned size; + unsigned long flags; + int i; + + dev = dev_get_drvdata (_dev); + next = buf; + size = PAGE_SIZE; + spin_lock_irqsave (&dev->lock, flags); + + for (i = 0; i < 7; i++) { + struct net2280_ep *ep = &dev->ep [i]; + struct net2280_request *req; + int t; + + if (i != 0) { + const struct usb_endpoint_descriptor *d; + + d = ep->desc; + if (!d) + continue; + t = d->bEndpointAddress; + t = scnprintf (next, size, + "\n%s (ep%d%s-%s) max %04x %s fifo %d\n", + ep->ep.name, t & USB_ENDPOINT_NUMBER_MASK, + (t & USB_DIR_IN) ? "in" : "out", + ({ char *val; + switch (d->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_BULK: + val = "bulk"; break; + case USB_ENDPOINT_XFER_INT: + val = "intr"; break; + default: + val = "iso"; break; + }; val; }), + usb_endpoint_maxp (d) & 0x1fff, + ep->dma ? "dma" : "pio", ep->fifo_size + ); + } else /* ep0 should only have one transfer queued */ + t = scnprintf (next, size, "ep0 max 64 pio %s\n", + ep->is_in ? "in" : "out"); + if (t <= 0 || t > size) + goto done; + size -= t; + next += t; + + if (list_empty (&ep->queue)) { + t = scnprintf (next, size, "\t(nothing queued)\n"); + if (t <= 0 || t > size) + goto done; + size -= t; + next += t; + continue; + } + list_for_each_entry (req, &ep->queue, queue) { + if (ep->dma && req->td_dma == readl (&ep->dma->dmadesc)) + t = scnprintf (next, size, + "\treq %p len %d/%d " + "buf %p (dmacount %08x)\n", + &req->req, req->req.actual, + req->req.length, req->req.buf, + readl (&ep->dma->dmacount)); + else + t = scnprintf (next, size, + "\treq %p len %d/%d buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + if (t <= 0 || t > size) + goto done; + size -= t; + next += t; + + if (ep->dma) { + struct net2280_dma *td; + + td = req->td; + t = scnprintf (next, size, "\t td %08x " + " count %08x buf %08x desc %08x\n", + (u32) req->td_dma, + le32_to_cpu (td->dmacount), + le32_to_cpu (td->dmaaddr), + le32_to_cpu (td->dmadesc)); + if (t <= 0 || t > size) + goto done; + size -= t; + next += t; + } + } + } + +done: + spin_unlock_irqrestore (&dev->lock, flags); + return PAGE_SIZE - size; +} +static DEVICE_ATTR (queues, S_IRUGO, show_queues, NULL); + + +#else + +#define device_create_file(a,b) (0) +#define device_remove_file(a,b) do { } while (0) + +#endif + +/*-------------------------------------------------------------------------*/ + +/* another driver-specific mode might be a request type doing dma + * to/from another device fifo instead of to/from memory. + */ + +static void set_fifo_mode (struct net2280 *dev, int mode) +{ + /* keeping high bits preserves BAR2 */ + writel ((0xffff << PCI_BASE2_RANGE) | mode, &dev->regs->fifoctl); + + /* always ep-{a,b,e,f} ... maybe not ep-c or ep-d */ + INIT_LIST_HEAD (&dev->gadget.ep_list); + list_add_tail (&dev->ep [1].ep.ep_list, &dev->gadget.ep_list); + list_add_tail (&dev->ep [2].ep.ep_list, &dev->gadget.ep_list); + switch (mode) { + case 0: + list_add_tail (&dev->ep [3].ep.ep_list, &dev->gadget.ep_list); + list_add_tail (&dev->ep [4].ep.ep_list, &dev->gadget.ep_list); + dev->ep [1].fifo_size = dev->ep [2].fifo_size = 1024; + break; + case 1: + dev->ep [1].fifo_size = dev->ep [2].fifo_size = 2048; + break; + case 2: + list_add_tail (&dev->ep [3].ep.ep_list, &dev->gadget.ep_list); + dev->ep [1].fifo_size = 2048; + dev->ep [2].fifo_size = 1024; + break; + } + /* fifo sizes for ep0, ep-c, ep-d, ep-e, and ep-f never change */ + list_add_tail (&dev->ep [5].ep.ep_list, &dev->gadget.ep_list); + list_add_tail (&dev->ep [6].ep.ep_list, &dev->gadget.ep_list); +} + +/* keeping it simple: + * - one bus driver, initted first; + * - one function driver, initted second + * + * most of the work to support multiple net2280 controllers would + * be to associate this gadget driver (yes?) with all of them, or + * perhaps to bind specific drivers to specific devices. + */ + +static void usb_reset (struct net2280 *dev) +{ + u32 tmp; + + dev->gadget.speed = USB_SPEED_UNKNOWN; + (void) readl (&dev->usb->usbctl); + + net2280_led_init (dev); + + /* disable automatic responses, and irqs */ + writel (0, &dev->usb->stdrsp); + writel (0, &dev->regs->pciirqenb0); + writel (0, &dev->regs->pciirqenb1); + + /* clear old dma and irq state */ + for (tmp = 0; tmp < 4; tmp++) { + struct net2280_ep *ep = &dev->ep [tmp + 1]; + + if (ep->dma) + abort_dma (ep); + } + writel (~0, &dev->regs->irqstat0), + writel (~(1 << SUSPEND_REQUEST_INTERRUPT), &dev->regs->irqstat1), + + /* reset, and enable pci */ + tmp = readl (&dev->regs->devinit) + | (1 << PCI_ENABLE) + | (1 << FIFO_SOFT_RESET) + | (1 << USB_SOFT_RESET) + | (1 << M8051_RESET); + writel (tmp, &dev->regs->devinit); + + /* standard fifo and endpoint allocations */ + set_fifo_mode (dev, (fifo_mode <= 2) ? fifo_mode : 0); +} + +static void usb_reinit (struct net2280 *dev) +{ + u32 tmp; + int init_dma; + + /* use_dma changes are ignored till next device re-init */ + init_dma = use_dma; + + /* basic endpoint init */ + for (tmp = 0; tmp < 7; tmp++) { + struct net2280_ep *ep = &dev->ep [tmp]; + + ep->ep.name = ep_name [tmp]; + ep->dev = dev; + ep->num = tmp; + + if (tmp > 0 && tmp <= 4) { + ep->fifo_size = 1024; + if (init_dma) + ep->dma = &dev->dma [tmp - 1]; + } else + ep->fifo_size = 64; + ep->regs = &dev->epregs [tmp]; + ep_reset (dev->regs, ep); + } + dev->ep [0].ep.maxpacket = 64; + dev->ep [5].ep.maxpacket = 64; + dev->ep [6].ep.maxpacket = 64; + + dev->gadget.ep0 = &dev->ep [0].ep; + dev->ep [0].stopped = 0; + INIT_LIST_HEAD (&dev->gadget.ep0->ep_list); + + /* we want to prevent lowlevel/insecure access from the USB host, + * but erratum 0119 means this enable bit is ignored + */ + for (tmp = 0; tmp < 5; tmp++) + writel (EP_DONTUSE, &dev->dep [tmp].dep_cfg); +} + +static void ep0_start (struct net2280 *dev) +{ + writel ( (1 << CLEAR_EP_HIDE_STATUS_PHASE) + | (1 << CLEAR_NAK_OUT_PACKETS) + | (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) + , &dev->epregs [0].ep_rsp); + + /* + * hardware optionally handles a bunch of standard requests + * that the API hides from drivers anyway. have it do so. + * endpoint status/features are handled in software, to + * help pass tests for some dubious behavior. + */ + writel ( (1 << SET_TEST_MODE) + | (1 << SET_ADDRESS) + | (1 << DEVICE_SET_CLEAR_DEVICE_REMOTE_WAKEUP) + | (1 << GET_DEVICE_STATUS) + | (1 << GET_INTERFACE_STATUS) + , &dev->usb->stdrsp); + writel ( (1 << USB_ROOT_PORT_WAKEUP_ENABLE) + | (1 << SELF_POWERED_USB_DEVICE) + | (1 << REMOTE_WAKEUP_SUPPORT) + | (dev->softconnect << USB_DETECT_ENABLE) + | (1 << SELF_POWERED_STATUS) + , &dev->usb->usbctl); + + /* enable irqs so we can see ep0 and general operation */ + writel ( (1 << SETUP_PACKET_INTERRUPT_ENABLE) + | (1 << ENDPOINT_0_INTERRUPT_ENABLE) + , &dev->regs->pciirqenb0); + writel ( (1 << PCI_INTERRUPT_ENABLE) + | (1 << PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE) + | (1 << PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE) + | (1 << PCI_RETRY_ABORT_INTERRUPT_ENABLE) + | (1 << VBUS_INTERRUPT_ENABLE) + | (1 << ROOT_PORT_RESET_INTERRUPT_ENABLE) + | (1 << SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE) + , &dev->regs->pciirqenb1); + + /* don't leave any writes posted */ + (void) readl (&dev->usb->usbctl); +} + +/* when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ +static int net2280_start(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver) +{ + struct net2280 *dev; + int retval; + unsigned i; + + /* insist on high speed support from the driver, since + * (dev->usb->xcvrdiag & FORCE_FULL_SPEED_MODE) + * "must not be used in normal operation" + */ + if (!driver || driver->max_speed < USB_SPEED_HIGH + || !driver->setup) + return -EINVAL; + + dev = container_of (_gadget, struct net2280, gadget); + + for (i = 0; i < 7; i++) + dev->ep [i].irqs = 0; + + /* hook up the driver ... */ + dev->softconnect = 1; + driver->driver.bus = NULL; + dev->driver = driver; + + retval = device_create_file (&dev->pdev->dev, &dev_attr_function); + if (retval) goto err_unbind; + retval = device_create_file (&dev->pdev->dev, &dev_attr_queues); + if (retval) goto err_func; + + /* Enable force-full-speed testing mode, if desired */ + if (full_speed) + writel(1 << FORCE_FULL_SPEED_MODE, &dev->usb->xcvrdiag); + + /* ... then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + net2280_led_active (dev, 1); + ep0_start (dev); + + DEBUG (dev, "%s ready, usbctl %08x stdrsp %08x\n", + driver->driver.name, + readl (&dev->usb->usbctl), + readl (&dev->usb->stdrsp)); + + /* pci writes may still be posted */ + return 0; + +err_func: + device_remove_file (&dev->pdev->dev, &dev_attr_function); +err_unbind: + dev->driver = NULL; + return retval; +} + +static void +stop_activity (struct net2280 *dev, struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect if it's not connected */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + + /* stop hardware; prevent new request submissions; + * and kill any outstanding requests. + */ + usb_reset (dev); + for (i = 0; i < 7; i++) + nuke (&dev->ep [i]); + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + usb_reinit (dev); +} + +static int net2280_stop(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver) +{ + struct net2280 *dev; + unsigned long flags; + + dev = container_of (_gadget, struct net2280, gadget); + + spin_lock_irqsave (&dev->lock, flags); + stop_activity (dev, driver); + spin_unlock_irqrestore (&dev->lock, flags); + + dev->driver = NULL; + + net2280_led_active (dev, 0); + + /* Disable full-speed test mode */ + writel(0, &dev->usb->xcvrdiag); + + device_remove_file (&dev->pdev->dev, &dev_attr_function); + device_remove_file (&dev->pdev->dev, &dev_attr_queues); + + DEBUG (dev, "unregistered driver '%s'\n", driver->driver.name); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* handle ep0, ep-e, ep-f with 64 byte packets: packet per irq. + * also works for dma-capable endpoints, in pio mode or just + * to manually advance the queue after short OUT transfers. + */ +static void handle_ep_small (struct net2280_ep *ep) +{ + struct net2280_request *req; + u32 t; + /* 0 error, 1 mid-data, 2 done */ + int mode = 1; + + if (!list_empty (&ep->queue)) + req = list_entry (ep->queue.next, + struct net2280_request, queue); + else + req = NULL; + + /* ack all, and handle what we care about */ + t = readl (&ep->regs->ep_stat); + ep->irqs++; +#if 0 + VDEBUG (ep->dev, "%s ack ep_stat %08x, req %p\n", + ep->ep.name, t, req ? &req->req : 0); +#endif + if (!ep->is_in || ep->dev->pdev->device == 0x2280) + writel (t & ~(1 << NAK_OUT_PACKETS), &ep->regs->ep_stat); + else + /* Added for 2282 */ + writel (t, &ep->regs->ep_stat); + + /* for ep0, monitor token irqs to catch data stage length errors + * and to synchronize on status. + * + * also, to defer reporting of protocol stalls ... here's where + * data or status first appears, handling stalls here should never + * cause trouble on the host side.. + * + * control requests could be slightly faster without token synch for + * status, but status can jam up that way. + */ + if (unlikely (ep->num == 0)) { + if (ep->is_in) { + /* status; stop NAKing */ + if (t & (1 << DATA_OUT_PING_TOKEN_INTERRUPT)) { + if (ep->dev->protocol_stall) { + ep->stopped = 1; + set_halt (ep); + } + if (!req) + allow_status (ep); + mode = 2; + /* reply to extra IN data tokens with a zlp */ + } else if (t & (1 << DATA_IN_TOKEN_INTERRUPT)) { + if (ep->dev->protocol_stall) { + ep->stopped = 1; + set_halt (ep); + mode = 2; + } else if (ep->responded && + !req && !ep->stopped) + write_fifo (ep, NULL); + } + } else { + /* status; stop NAKing */ + if (t & (1 << DATA_IN_TOKEN_INTERRUPT)) { + if (ep->dev->protocol_stall) { + ep->stopped = 1; + set_halt (ep); + } + mode = 2; + /* an extra OUT token is an error */ + } else if (((t & (1 << DATA_OUT_PING_TOKEN_INTERRUPT)) + && req + && req->req.actual == req->req.length) + || (ep->responded && !req)) { + ep->dev->protocol_stall = 1; + set_halt (ep); + ep->stopped = 1; + if (req) + done (ep, req, -EOVERFLOW); + req = NULL; + } + } + } + + if (unlikely (!req)) + return; + + /* manual DMA queue advance after short OUT */ + if (likely (ep->dma)) { + if (t & (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)) { + u32 count; + int stopped = ep->stopped; + + /* TRANSFERRED works around OUT_DONE erratum 0112. + * we expect (N <= maxpacket) bytes; host wrote M. + * iff (M < N) we won't ever see a DMA interrupt. + */ + ep->stopped = 1; + for (count = 0; ; t = readl (&ep->regs->ep_stat)) { + + /* any preceding dma transfers must finish. + * dma handles (M >= N), may empty the queue + */ + scan_dma_completions (ep); + if (unlikely (list_empty (&ep->queue) + || ep->out_overflow)) { + req = NULL; + break; + } + req = list_entry (ep->queue.next, + struct net2280_request, queue); + + /* here either (M < N), a "real" short rx; + * or (M == N) and the queue didn't empty + */ + if (likely (t & (1 << FIFO_EMPTY))) { + count = readl (&ep->dma->dmacount); + count &= DMA_BYTE_COUNT_MASK; + if (readl (&ep->dma->dmadesc) + != req->td_dma) + req = NULL; + break; + } + udelay(1); + } + + /* stop DMA, leave ep NAKing */ + writel ((1 << DMA_ABORT), &ep->dma->dmastat); + spin_stop_dma (ep->dma); + + if (likely (req)) { + req->td->dmacount = 0; + t = readl (&ep->regs->ep_avail); + dma_done (ep, req, count, + (ep->out_overflow || t) + ? -EOVERFLOW : 0); + } + + /* also flush to prevent erratum 0106 trouble */ + if (unlikely (ep->out_overflow + || (ep->dev->chiprev == 0x0100 + && ep->dev->gadget.speed + == USB_SPEED_FULL))) { + out_flush (ep); + ep->out_overflow = 0; + } + + /* (re)start dma if needed, stop NAKing */ + ep->stopped = stopped; + if (!list_empty (&ep->queue)) + restart_dma (ep); + } else + DEBUG (ep->dev, "%s dma ep_stat %08x ??\n", + ep->ep.name, t); + return; + + /* data packet(s) received (in the fifo, OUT) */ + } else if (t & (1 << DATA_PACKET_RECEIVED_INTERRUPT)) { + if (read_fifo (ep, req) && ep->num != 0) + mode = 2; + + /* data packet(s) transmitted (IN) */ + } else if (t & (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)) { + unsigned len; + + len = req->req.length - req->req.actual; + if (len > ep->ep.maxpacket) + len = ep->ep.maxpacket; + req->req.actual += len; + + /* if we wrote it all, we're usually done */ + if (req->req.actual == req->req.length) { + if (ep->num == 0) { + /* send zlps until the status stage */ + } else if (!req->req.zero || len != ep->ep.maxpacket) + mode = 2; + } + + /* there was nothing to do ... */ + } else if (mode == 1) + return; + + /* done */ + if (mode == 2) { + /* stream endpoints often resubmit/unlink in completion */ + done (ep, req, 0); + + /* maybe advance queue to next request */ + if (ep->num == 0) { + /* NOTE: net2280 could let gadget driver start the + * status stage later. since not all controllers let + * them control that, the api doesn't (yet) allow it. + */ + if (!ep->stopped) + allow_status (ep); + req = NULL; + } else { + if (!list_empty (&ep->queue) && !ep->stopped) + req = list_entry (ep->queue.next, + struct net2280_request, queue); + else + req = NULL; + if (req && !ep->is_in) + stop_out_naking (ep); + } + } + + /* is there a buffer for the next packet? + * for best streaming performance, make sure there is one. + */ + if (req && !ep->stopped) { + + /* load IN fifo with next packet (may be zlp) */ + if (t & (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)) + write_fifo (ep, &req->req); + } +} + +static struct net2280_ep * +get_ep_by_addr (struct net2280 *dev, u16 wIndex) +{ + struct net2280_ep *ep; + + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) + return &dev->ep [0]; + list_for_each_entry (ep, &dev->gadget.ep_list, ep.ep_list) { + u8 bEndpointAddress; + + if (!ep->desc) + continue; + bEndpointAddress = ep->desc->bEndpointAddress; + if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) + continue; + if ((wIndex & 0x0f) == (bEndpointAddress & 0x0f)) + return ep; + } + return NULL; +} + +static void handle_stat0_irqs (struct net2280 *dev, u32 stat) +{ + struct net2280_ep *ep; + u32 num, scratch; + + /* most of these don't need individual acks */ + stat &= ~(1 << INTA_ASSERTED); + if (!stat) + return; + // DEBUG (dev, "irqstat0 %04x\n", stat); + + /* starting a control request? */ + if (unlikely (stat & (1 << SETUP_PACKET_INTERRUPT))) { + union { + u32 raw [2]; + struct usb_ctrlrequest r; + } u; + int tmp; + struct net2280_request *req; + + if (dev->gadget.speed == USB_SPEED_UNKNOWN) { + if (readl (&dev->usb->usbstat) & (1 << HIGH_SPEED)) + dev->gadget.speed = USB_SPEED_HIGH; + else + dev->gadget.speed = USB_SPEED_FULL; + net2280_led_speed (dev, dev->gadget.speed); + DEBUG(dev, "%s\n", usb_speed_string(dev->gadget.speed)); + } + + ep = &dev->ep [0]; + ep->irqs++; + + /* make sure any leftover request state is cleared */ + stat &= ~(1 << ENDPOINT_0_INTERRUPT); + while (!list_empty (&ep->queue)) { + req = list_entry (ep->queue.next, + struct net2280_request, queue); + done (ep, req, (req->req.actual == req->req.length) + ? 0 : -EPROTO); + } + ep->stopped = 0; + dev->protocol_stall = 0; + + if (ep->dev->pdev->device == 0x2280) + tmp = (1 << FIFO_OVERFLOW) + | (1 << FIFO_UNDERFLOW); + else + tmp = 0; + + writel (tmp | (1 << TIMEOUT) + | (1 << USB_STALL_SENT) + | (1 << USB_IN_NAK_SENT) + | (1 << USB_IN_ACK_RCVD) + | (1 << USB_OUT_PING_NAK_SENT) + | (1 << USB_OUT_ACK_SENT) + | (1 << SHORT_PACKET_OUT_DONE_INTERRUPT) + | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT) + | (1 << DATA_PACKET_RECEIVED_INTERRUPT) + | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) + | (1 << DATA_OUT_PING_TOKEN_INTERRUPT) + | (1 << DATA_IN_TOKEN_INTERRUPT) + , &ep->regs->ep_stat); + u.raw [0] = readl (&dev->usb->setup0123); + u.raw [1] = readl (&dev->usb->setup4567); + + cpu_to_le32s (&u.raw [0]); + cpu_to_le32s (&u.raw [1]); + + tmp = 0; + +#define w_value le16_to_cpu(u.r.wValue) +#define w_index le16_to_cpu(u.r.wIndex) +#define w_length le16_to_cpu(u.r.wLength) + + /* ack the irq */ + writel (1 << SETUP_PACKET_INTERRUPT, &dev->regs->irqstat0); + stat ^= (1 << SETUP_PACKET_INTERRUPT); + + /* watch control traffic at the token level, and force + * synchronization before letting the status stage happen. + * FIXME ignore tokens we'll NAK, until driver responds. + * that'll mean a lot less irqs for some drivers. + */ + ep->is_in = (u.r.bRequestType & USB_DIR_IN) != 0; + if (ep->is_in) { + scratch = (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) + | (1 << DATA_OUT_PING_TOKEN_INTERRUPT) + | (1 << DATA_IN_TOKEN_INTERRUPT); + stop_out_naking (ep); + } else + scratch = (1 << DATA_PACKET_RECEIVED_INTERRUPT) + | (1 << DATA_OUT_PING_TOKEN_INTERRUPT) + | (1 << DATA_IN_TOKEN_INTERRUPT); + writel (scratch, &dev->epregs [0].ep_irqenb); + + /* we made the hardware handle most lowlevel requests; + * everything else goes uplevel to the gadget code. + */ + ep->responded = 1; + switch (u.r.bRequest) { + case USB_REQ_GET_STATUS: { + struct net2280_ep *e; + __le32 status; + + /* hw handles device and interface status */ + if (u.r.bRequestType != (USB_DIR_IN|USB_RECIP_ENDPOINT)) + goto delegate; + if ((e = get_ep_by_addr (dev, w_index)) == NULL + || w_length > 2) + goto do_stall; + + if (readl (&e->regs->ep_rsp) + & (1 << SET_ENDPOINT_HALT)) + status = cpu_to_le32 (1); + else + status = cpu_to_le32 (0); + + /* don't bother with a request object! */ + writel (0, &dev->epregs [0].ep_irqenb); + set_fifo_bytecount (ep, w_length); + writel ((__force u32)status, &dev->epregs [0].ep_data); + allow_status (ep); + VDEBUG (dev, "%s stat %02x\n", ep->ep.name, status); + goto next_endpoints; + } + break; + case USB_REQ_CLEAR_FEATURE: { + struct net2280_ep *e; + + /* hw handles device features */ + if (u.r.bRequestType != USB_RECIP_ENDPOINT) + goto delegate; + if (w_value != USB_ENDPOINT_HALT + || w_length != 0) + goto do_stall; + if ((e = get_ep_by_addr (dev, w_index)) == NULL) + goto do_stall; + if (e->wedged) { + VDEBUG(dev, "%s wedged, halt not cleared\n", + ep->ep.name); + } else { + VDEBUG(dev, "%s clear halt\n", ep->ep.name); + clear_halt(e); + } + allow_status (ep); + goto next_endpoints; + } + break; + case USB_REQ_SET_FEATURE: { + struct net2280_ep *e; + + /* hw handles device features */ + if (u.r.bRequestType != USB_RECIP_ENDPOINT) + goto delegate; + if (w_value != USB_ENDPOINT_HALT + || w_length != 0) + goto do_stall; + if ((e = get_ep_by_addr (dev, w_index)) == NULL) + goto do_stall; + if (e->ep.name == ep0name) + goto do_stall; + set_halt (e); + allow_status (ep); + VDEBUG (dev, "%s set halt\n", ep->ep.name); + goto next_endpoints; + } + break; + default: +delegate: + VDEBUG (dev, "setup %02x.%02x v%04x i%04x l%04x " + "ep_cfg %08x\n", + u.r.bRequestType, u.r.bRequest, + w_value, w_index, w_length, + readl (&ep->regs->ep_cfg)); + ep->responded = 0; + spin_unlock (&dev->lock); + tmp = dev->driver->setup (&dev->gadget, &u.r); + spin_lock (&dev->lock); + } + + /* stall ep0 on error */ + if (tmp < 0) { +do_stall: + VDEBUG (dev, "req %02x.%02x protocol STALL; stat %d\n", + u.r.bRequestType, u.r.bRequest, tmp); + dev->protocol_stall = 1; + } + + /* some in/out token irq should follow; maybe stall then. + * driver must queue a request (even zlp) or halt ep0 + * before the host times out. + */ + } + +#undef w_value +#undef w_index +#undef w_length + +next_endpoints: + /* endpoint data irq ? */ + scratch = stat & 0x7f; + stat &= ~0x7f; + for (num = 0; scratch; num++) { + u32 t; + + /* do this endpoint's FIFO and queue need tending? */ + t = 1 << num; + if ((scratch & t) == 0) + continue; + scratch ^= t; + + ep = &dev->ep [num]; + handle_ep_small (ep); + } + + if (stat) + DEBUG (dev, "unhandled irqstat0 %08x\n", stat); +} + +#define DMA_INTERRUPTS ( \ + (1 << DMA_D_INTERRUPT) \ + | (1 << DMA_C_INTERRUPT) \ + | (1 << DMA_B_INTERRUPT) \ + | (1 << DMA_A_INTERRUPT)) +#define PCI_ERROR_INTERRUPTS ( \ + (1 << PCI_MASTER_ABORT_RECEIVED_INTERRUPT) \ + | (1 << PCI_TARGET_ABORT_RECEIVED_INTERRUPT) \ + | (1 << PCI_RETRY_ABORT_INTERRUPT)) + +static void handle_stat1_irqs (struct net2280 *dev, u32 stat) +{ + struct net2280_ep *ep; + u32 tmp, num, mask, scratch; + + /* after disconnect there's nothing else to do! */ + tmp = (1 << VBUS_INTERRUPT) | (1 << ROOT_PORT_RESET_INTERRUPT); + mask = (1 << HIGH_SPEED) | (1 << FULL_SPEED); + + /* VBUS disconnect is indicated by VBUS_PIN and VBUS_INTERRUPT set. + * Root Port Reset is indicated by ROOT_PORT_RESET_INTERRUPT set and + * both HIGH_SPEED and FULL_SPEED clear (as ROOT_PORT_RESET_INTERRUPT + * only indicates a change in the reset state). + */ + if (stat & tmp) { + writel (tmp, &dev->regs->irqstat1); + if ((((stat & (1 << ROOT_PORT_RESET_INTERRUPT)) + && ((readl (&dev->usb->usbstat) & mask) + == 0)) + || ((readl (&dev->usb->usbctl) + & (1 << VBUS_PIN)) == 0) + ) && ( dev->gadget.speed != USB_SPEED_UNKNOWN)) { + DEBUG (dev, "disconnect %s\n", + dev->driver->driver.name); + stop_activity (dev, dev->driver); + ep0_start (dev); + return; + } + stat &= ~tmp; + + /* vBUS can bounce ... one of many reasons to ignore the + * notion of hotplug events on bus connect/disconnect! + */ + if (!stat) + return; + } + + /* NOTE: chip stays in PCI D0 state for now, but it could + * enter D1 to save more power + */ + tmp = (1 << SUSPEND_REQUEST_CHANGE_INTERRUPT); + if (stat & tmp) { + writel (tmp, &dev->regs->irqstat1); + if (stat & (1 << SUSPEND_REQUEST_INTERRUPT)) { + if (dev->driver->suspend) + dev->driver->suspend (&dev->gadget); + if (!enable_suspend) + stat &= ~(1 << SUSPEND_REQUEST_INTERRUPT); + } else { + if (dev->driver->resume) + dev->driver->resume (&dev->gadget); + /* at high speed, note erratum 0133 */ + } + stat &= ~tmp; + } + + /* clear any other status/irqs */ + if (stat) + writel (stat, &dev->regs->irqstat1); + + /* some status we can just ignore */ + if (dev->pdev->device == 0x2280) + stat &= ~((1 << CONTROL_STATUS_INTERRUPT) + | (1 << SUSPEND_REQUEST_INTERRUPT) + | (1 << RESUME_INTERRUPT) + | (1 << SOF_INTERRUPT)); + else + stat &= ~((1 << CONTROL_STATUS_INTERRUPT) + | (1 << RESUME_INTERRUPT) + | (1 << SOF_DOWN_INTERRUPT) + | (1 << SOF_INTERRUPT)); + + if (!stat) + return; + // DEBUG (dev, "irqstat1 %08x\n", stat); + + /* DMA status, for ep-{a,b,c,d} */ + scratch = stat & DMA_INTERRUPTS; + stat &= ~DMA_INTERRUPTS; + scratch >>= 9; + for (num = 0; scratch; num++) { + struct net2280_dma_regs __iomem *dma; + + tmp = 1 << num; + if ((tmp & scratch) == 0) + continue; + scratch ^= tmp; + + ep = &dev->ep [num + 1]; + dma = ep->dma; + + if (!dma) + continue; + + /* clear ep's dma status */ + tmp = readl (&dma->dmastat); + writel (tmp, &dma->dmastat); + + /* chaining should stop on abort, short OUT from fifo, + * or (stat0 codepath) short OUT transfer. + */ + if (!use_dma_chaining) { + if ((tmp & (1 << DMA_TRANSACTION_DONE_INTERRUPT)) + == 0) { + DEBUG (ep->dev, "%s no xact done? %08x\n", + ep->ep.name, tmp); + continue; + } + stop_dma (ep->dma); + } + + /* OUT transfers terminate when the data from the + * host is in our memory. Process whatever's done. + * On this path, we know transfer's last packet wasn't + * less than req->length. NAK_OUT_PACKETS may be set, + * or the FIFO may already be holding new packets. + * + * IN transfers can linger in the FIFO for a very + * long time ... we ignore that for now, accounting + * precisely (like PIO does) needs per-packet irqs + */ + scan_dma_completions (ep); + + /* disable dma on inactive queues; else maybe restart */ + if (list_empty (&ep->queue)) { + if (use_dma_chaining) + stop_dma (ep->dma); + } else { + tmp = readl (&dma->dmactl); + if (!use_dma_chaining + || (tmp & (1 << DMA_ENABLE)) == 0) + restart_dma (ep); + else if (ep->is_in && use_dma_chaining) { + struct net2280_request *req; + __le32 dmacount; + + /* the descriptor at the head of the chain + * may still have VALID_BIT clear; that's + * used to trigger changing DMA_FIFO_VALIDATE + * (affects automagic zlp writes). + */ + req = list_entry (ep->queue.next, + struct net2280_request, queue); + dmacount = req->td->dmacount; + dmacount &= cpu_to_le32 ( + (1 << VALID_BIT) + | DMA_BYTE_COUNT_MASK); + if (dmacount && (dmacount & valid_bit) == 0) + restart_dma (ep); + } + } + ep->irqs++; + } + + /* NOTE: there are other PCI errors we might usefully notice. + * if they appear very often, here's where to try recovering. + */ + if (stat & PCI_ERROR_INTERRUPTS) { + ERROR (dev, "pci dma error; stat %08x\n", stat); + stat &= ~PCI_ERROR_INTERRUPTS; + /* these are fatal errors, but "maybe" they won't + * happen again ... + */ + stop_activity (dev, dev->driver); + ep0_start (dev); + stat = 0; + } + + if (stat) + DEBUG (dev, "unhandled irqstat1 %08x\n", stat); +} + +static irqreturn_t net2280_irq (int irq, void *_dev) +{ + struct net2280 *dev = _dev; + + /* shared interrupt, not ours */ + if (!(readl(&dev->regs->irqstat0) & (1 << INTA_ASSERTED))) + return IRQ_NONE; + + spin_lock (&dev->lock); + + /* handle disconnect, dma, and more */ + handle_stat1_irqs (dev, readl (&dev->regs->irqstat1)); + + /* control requests and PIO */ + handle_stat0_irqs (dev, readl (&dev->regs->irqstat0)); + + spin_unlock (&dev->lock); + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static void gadget_release (struct device *_dev) +{ + struct net2280 *dev = dev_get_drvdata (_dev); + + kfree (dev); +} + +/* tear down the binding between this driver and the pci device */ + +static void net2280_remove (struct pci_dev *pdev) +{ + struct net2280 *dev = pci_get_drvdata (pdev); + + usb_del_gadget_udc(&dev->gadget); + + BUG_ON(dev->driver); + + /* then clean up the resources we allocated during probe() */ + net2280_led_shutdown (dev); + if (dev->requests) { + int i; + for (i = 1; i < 5; i++) { + if (!dev->ep [i].dummy) + continue; + pci_pool_free (dev->requests, dev->ep [i].dummy, + dev->ep [i].td_dma); + } + pci_pool_destroy (dev->requests); + } + if (dev->got_irq) + free_irq (pdev->irq, dev); + if (dev->regs) + iounmap (dev->regs); + if (dev->region) + release_mem_region (pci_resource_start (pdev, 0), + pci_resource_len (pdev, 0)); + if (dev->enabled) + pci_disable_device (pdev); + device_remove_file (&pdev->dev, &dev_attr_registers); + pci_set_drvdata (pdev, NULL); + + INFO (dev, "unbind\n"); +} + +/* wrap this driver around the specified device, but + * don't respond over USB until a gadget driver binds to us. + */ + +static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct net2280 *dev; + unsigned long resource, len; + void __iomem *base = NULL; + int retval, i; + + /* alloc, and start init */ + dev = kzalloc (sizeof *dev, GFP_KERNEL); + if (dev == NULL){ + retval = -ENOMEM; + goto done; + } + + pci_set_drvdata (pdev, dev); + spin_lock_init (&dev->lock); + dev->pdev = pdev; + dev->gadget.ops = &net2280_ops; + dev->gadget.max_speed = USB_SPEED_HIGH; + + /* the "gadget" abstracts/virtualizes the controller */ + dev->gadget.name = driver_name; + + /* now all the pci goodies ... */ + if (pci_enable_device (pdev) < 0) { + retval = -ENODEV; + goto done; + } + dev->enabled = 1; + + /* BAR 0 holds all the registers + * BAR 1 is 8051 memory; unused here (note erratum 0103) + * BAR 2 is fifo memory; unused here + */ + resource = pci_resource_start (pdev, 0); + len = pci_resource_len (pdev, 0); + if (!request_mem_region (resource, len, driver_name)) { + DEBUG (dev, "controller already in use\n"); + retval = -EBUSY; + goto done; + } + dev->region = 1; + + /* FIXME provide firmware download interface to put + * 8051 code into the chip, e.g. to turn on PCI PM. + */ + + base = ioremap_nocache (resource, len); + if (base == NULL) { + DEBUG (dev, "can't map memory\n"); + retval = -EFAULT; + goto done; + } + dev->regs = (struct net2280_regs __iomem *) base; + dev->usb = (struct net2280_usb_regs __iomem *) (base + 0x0080); + dev->pci = (struct net2280_pci_regs __iomem *) (base + 0x0100); + dev->dma = (struct net2280_dma_regs __iomem *) (base + 0x0180); + dev->dep = (struct net2280_dep_regs __iomem *) (base + 0x0200); + dev->epregs = (struct net2280_ep_regs __iomem *) (base + 0x0300); + + /* put into initial config, link up all endpoints */ + writel (0, &dev->usb->usbctl); + usb_reset (dev); + usb_reinit (dev); + + /* irq setup after old hardware is cleaned up */ + if (!pdev->irq) { + ERROR (dev, "No IRQ. Check PCI setup!\n"); + retval = -ENODEV; + goto done; + } + + if (request_irq (pdev->irq, net2280_irq, IRQF_SHARED, driver_name, dev) + != 0) { + ERROR (dev, "request interrupt %d failed\n", pdev->irq); + retval = -EBUSY; + goto done; + } + dev->got_irq = 1; + + /* DMA setup */ + /* NOTE: we know only the 32 LSBs of dma addresses may be nonzero */ + dev->requests = pci_pool_create ("requests", pdev, + sizeof (struct net2280_dma), + 0 /* no alignment requirements */, + 0 /* or page-crossing issues */); + if (!dev->requests) { + DEBUG (dev, "can't get request pool\n"); + retval = -ENOMEM; + goto done; + } + for (i = 1; i < 5; i++) { + struct net2280_dma *td; + + td = pci_pool_alloc (dev->requests, GFP_KERNEL, + &dev->ep [i].td_dma); + if (!td) { + DEBUG (dev, "can't get dummy %d\n", i); + retval = -ENOMEM; + goto done; + } + td->dmacount = 0; /* not VALID */ + td->dmadesc = td->dmaaddr; + dev->ep [i].dummy = td; + } + + /* enable lower-overhead pci memory bursts during DMA */ + writel ( (1 << DMA_MEMORY_WRITE_AND_INVALIDATE_ENABLE) + // 256 write retries may not be enough... + // | (1 << PCI_RETRY_ABORT_ENABLE) + | (1 << DMA_READ_MULTIPLE_ENABLE) + | (1 << DMA_READ_LINE_ENABLE) + , &dev->pci->pcimstctl); + /* erratum 0115 shouldn't appear: Linux inits PCI_LATENCY_TIMER */ + pci_set_master (pdev); + pci_try_set_mwi (pdev); + + /* ... also flushes any posted pci writes */ + dev->chiprev = get_idx_reg (dev->regs, REG_CHIPREV) & 0xffff; + + /* done */ + INFO (dev, "%s\n", driver_desc); + INFO (dev, "irq %d, pci mem %p, chip rev %04x\n", + pdev->irq, base, dev->chiprev); + INFO (dev, "version: " DRIVER_VERSION "; dma %s\n", + use_dma + ? (use_dma_chaining ? "chaining" : "enabled") + : "disabled"); + retval = device_create_file (&pdev->dev, &dev_attr_registers); + if (retval) goto done; + + retval = usb_add_gadget_udc_release(&pdev->dev, &dev->gadget, + gadget_release); + if (retval) + goto done; + return 0; + +done: + if (dev) + net2280_remove (pdev); + return retval; +} + +/* make sure the board is quiescent; otherwise it will continue + * generating IRQs across the upcoming reboot. + */ + +static void net2280_shutdown (struct pci_dev *pdev) +{ + struct net2280 *dev = pci_get_drvdata (pdev); + + /* disable IRQs */ + writel (0, &dev->regs->pciirqenb0); + writel (0, &dev->regs->pciirqenb1); + + /* disable the pullup so the host will think we're gone */ + writel (0, &dev->usb->usbctl); + + /* Disable full-speed test mode */ + writel(0, &dev->usb->xcvrdiag); +} + + +/*-------------------------------------------------------------------------*/ + +static const struct pci_device_id pci_ids [] = { { + .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class_mask = ~0, + .vendor = 0x17cc, + .device = 0x2280, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, +}, { + .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class_mask = ~0, + .vendor = 0x17cc, + .device = 0x2282, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + +}, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE (pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver net2280_pci_driver = { + .name = (char *) driver_name, + .id_table = pci_ids, + + .probe = net2280_probe, + .remove = net2280_remove, + .shutdown = net2280_shutdown, + + /* FIXME add power management support */ +}; + +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_AUTHOR ("David Brownell"); +MODULE_LICENSE ("GPL"); + +static int __init init (void) +{ + if (!use_dma) + use_dma_chaining = 0; + return pci_register_driver (&net2280_pci_driver); +} +module_init (init); + +static void __exit cleanup (void) +{ + pci_unregister_driver (&net2280_pci_driver); +} +module_exit (cleanup); diff --git a/drivers/usb/gadget/net2280.h b/drivers/usb/gadget/net2280.h new file mode 100644 index 00000000..a844be0d --- /dev/null +++ b/drivers/usb/gadget/net2280.h @@ -0,0 +1,308 @@ +/* + * NetChip 2280 high/full speed USB device controller. + * Unlike many such controllers, this one talks PCI. + */ + +/* + * Copyright (C) 2002 NetChip Technology, Inc. (http://www.netchip.com) + * Copyright (C) 2003 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include + +/*-------------------------------------------------------------------------*/ + +#ifdef __KERNEL__ + +/* indexed registers [11.10] are accessed indirectly + * caller must own the device lock. + */ + +static inline u32 +get_idx_reg (struct net2280_regs __iomem *regs, u32 index) +{ + writel (index, ®s->idxaddr); + /* NOTE: synchs device/cpu memory views */ + return readl (®s->idxdata); +} + +static inline void +set_idx_reg (struct net2280_regs __iomem *regs, u32 index, u32 value) +{ + writel (index, ®s->idxaddr); + writel (value, ®s->idxdata); + /* posted, may not be visible yet */ +} + +#endif /* __KERNEL__ */ + + +#define REG_DIAG 0x0 +#define RETRY_COUNTER 16 +#define FORCE_PCI_SERR 11 +#define FORCE_PCI_INTERRUPT 10 +#define FORCE_USB_INTERRUPT 9 +#define FORCE_CPU_INTERRUPT 8 +#define ILLEGAL_BYTE_ENABLES 5 +#define FAST_TIMES 4 +#define FORCE_RECEIVE_ERROR 2 +#define FORCE_TRANSMIT_CRC_ERROR 0 +#define REG_FRAME 0x02 /* from last sof */ +#define REG_CHIPREV 0x03 /* in bcd */ +#define REG_HS_NAK_RATE 0x0a /* NAK per N uframes */ + +#define CHIPREV_1 0x0100 +#define CHIPREV_1A 0x0110 + +#ifdef __KERNEL__ + +/* ep a-f highspeed and fullspeed maxpacket, addresses + * computed from ep->num + */ +#define REG_EP_MAXPKT(dev,num) (((num) + 1) * 0x10 + \ + (((dev)->gadget.speed == USB_SPEED_HIGH) ? 0 : 1)) + +/*-------------------------------------------------------------------------*/ + +/* [8.3] for scatter/gather i/o + * use struct net2280_dma_regs bitfields + */ +struct net2280_dma { + __le32 dmacount; + __le32 dmaaddr; /* the buffer */ + __le32 dmadesc; /* next dma descriptor */ + __le32 _reserved; +} __attribute__ ((aligned (16))); + +/*-------------------------------------------------------------------------*/ + +/* DRIVER DATA STRUCTURES and UTILITIES */ + +struct net2280_ep { + struct usb_ep ep; + struct net2280_ep_regs __iomem *regs; + struct net2280_dma_regs __iomem *dma; + struct net2280_dma *dummy; + dma_addr_t td_dma; /* of dummy */ + struct net2280 *dev; + unsigned long irqs; + + /* analogous to a host-side qh */ + struct list_head queue; + const struct usb_endpoint_descriptor *desc; + unsigned num : 8, + fifo_size : 12, + in_fifo_validate : 1, + out_overflow : 1, + stopped : 1, + wedged : 1, + is_in : 1, + is_iso : 1, + responded : 1; +}; + +static inline void allow_status (struct net2280_ep *ep) +{ + /* ep0 only */ + writel ( (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) + | (1 << CLEAR_NAK_OUT_PACKETS) + | (1 << CLEAR_NAK_OUT_PACKETS_MODE) + , &ep->regs->ep_rsp); + ep->stopped = 1; +} + +/* count (<= 4) bytes in the next fifo write will be valid */ +static inline void set_fifo_bytecount (struct net2280_ep *ep, unsigned count) +{ + writeb (count, 2 + (u8 __iomem *) &ep->regs->ep_cfg); +} + +struct net2280_request { + struct usb_request req; + struct net2280_dma *td; + dma_addr_t td_dma; + struct list_head queue; + unsigned mapped : 1, + valid : 1; +}; + +struct net2280 { + /* each pci device provides one gadget, several endpoints */ + struct usb_gadget gadget; + spinlock_t lock; + struct net2280_ep ep [7]; + struct usb_gadget_driver *driver; + unsigned enabled : 1, + protocol_stall : 1, + softconnect : 1, + got_irq : 1, + region : 1; + u16 chiprev; + + /* pci state used to access those endpoints */ + struct pci_dev *pdev; + struct net2280_regs __iomem *regs; + struct net2280_usb_regs __iomem *usb; + struct net2280_pci_regs __iomem *pci; + struct net2280_dma_regs __iomem *dma; + struct net2280_dep_regs __iomem *dep; + struct net2280_ep_regs __iomem *epregs; + + struct pci_pool *requests; + // statistics... +}; + +static inline void set_halt (struct net2280_ep *ep) +{ + /* ep0 and bulk/intr endpoints */ + writel ( (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) + /* set NAK_OUT for erratum 0114 */ + | ((ep->dev->chiprev == CHIPREV_1) << SET_NAK_OUT_PACKETS) + | (1 << SET_ENDPOINT_HALT) + , &ep->regs->ep_rsp); +} + +static inline void clear_halt (struct net2280_ep *ep) +{ + /* ep0 and bulk/intr endpoints */ + writel ( (1 << CLEAR_ENDPOINT_HALT) + | (1 << CLEAR_ENDPOINT_TOGGLE) + /* unless the gadget driver left a short packet in the + * fifo, this reverses the erratum 0114 workaround. + */ + | ((ep->dev->chiprev == CHIPREV_1) << CLEAR_NAK_OUT_PACKETS) + , &ep->regs->ep_rsp); +} + +#ifdef USE_RDK_LEDS + +static inline void net2280_led_init (struct net2280 *dev) +{ + /* LED3 (green) is on during USB activity. note erratum 0113. */ + writel ((1 << GPIO3_LED_SELECT) + | (1 << GPIO3_OUTPUT_ENABLE) + | (1 << GPIO2_OUTPUT_ENABLE) + | (1 << GPIO1_OUTPUT_ENABLE) + | (1 << GPIO0_OUTPUT_ENABLE) + , &dev->regs->gpioctl); +} + +/* indicate speed with bi-color LED 0/1 */ +static inline +void net2280_led_speed (struct net2280 *dev, enum usb_device_speed speed) +{ + u32 val = readl (&dev->regs->gpioctl); + switch (speed) { + case USB_SPEED_HIGH: /* green */ + val &= ~(1 << GPIO0_DATA); + val |= (1 << GPIO1_DATA); + break; + case USB_SPEED_FULL: /* red */ + val &= ~(1 << GPIO1_DATA); + val |= (1 << GPIO0_DATA); + break; + default: /* (off/black) */ + val &= ~((1 << GPIO1_DATA) | (1 << GPIO0_DATA)); + break; + } + writel (val, &dev->regs->gpioctl); +} + +/* indicate power with LED 2 */ +static inline void net2280_led_active (struct net2280 *dev, int is_active) +{ + u32 val = readl (&dev->regs->gpioctl); + + // FIXME this LED never seems to turn on. + if (is_active) + val |= GPIO2_DATA; + else + val &= ~GPIO2_DATA; + writel (val, &dev->regs->gpioctl); +} +static inline void net2280_led_shutdown (struct net2280 *dev) +{ + /* turn off all four GPIO*_DATA bits */ + writel (readl (&dev->regs->gpioctl) & ~0x0f, + &dev->regs->gpioctl); +} + +#else + +#define net2280_led_init(dev) do { } while (0) +#define net2280_led_speed(dev, speed) do { } while (0) +#define net2280_led_shutdown(dev) do { } while (0) + +#endif + +/*-------------------------------------------------------------------------*/ + +#define xprintk(dev,level,fmt,args...) \ + printk(level "%s %s: " fmt , driver_name , \ + pci_name(dev->pdev) , ## args) + +#ifdef DEBUG +#undef DEBUG +#define DEBUG(dev,fmt,args...) \ + xprintk(dev , KERN_DEBUG , fmt , ## args) +#else +#define DEBUG(dev,fmt,args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE +#define VDEBUG DEBUG +#else +#define VDEBUG(dev,fmt,args...) \ + do { } while (0) +#endif /* VERBOSE */ + +#define ERROR(dev,fmt,args...) \ + xprintk(dev , KERN_ERR , fmt , ## args) +#define WARNING(dev,fmt,args...) \ + xprintk(dev , KERN_WARNING , fmt , ## args) +#define INFO(dev,fmt,args...) \ + xprintk(dev , KERN_INFO , fmt , ## args) + +/*-------------------------------------------------------------------------*/ + +static inline void start_out_naking (struct net2280_ep *ep) +{ + /* NOTE: hardware races lurk here, and PING protocol issues */ + writel ((1 << SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + /* synch with device */ + readl (&ep->regs->ep_rsp); +} + +#ifdef DEBUG +static inline void assert_out_naking (struct net2280_ep *ep, const char *where) +{ + u32 tmp = readl (&ep->regs->ep_stat); + + if ((tmp & (1 << NAK_OUT_PACKETS)) == 0) { + DEBUG (ep->dev, "%s %s %08x !NAK\n", + ep->ep.name, where, tmp); + writel ((1 << SET_NAK_OUT_PACKETS), + &ep->regs->ep_rsp); + } +} +#define ASSERT_OUT_NAKING(ep) assert_out_naking(ep,__func__) +#else +#define ASSERT_OUT_NAKING(ep) do {} while (0) +#endif + +static inline void stop_out_naking (struct net2280_ep *ep) +{ + u32 tmp; + + tmp = readl (&ep->regs->ep_stat); + if ((tmp & (1 << NAK_OUT_PACKETS)) != 0) + writel ((1 << CLEAR_NAK_OUT_PACKETS), &ep->regs->ep_rsp); +} + +#endif /* __KERNEL__ */ diff --git a/drivers/usb/gadget/nokia.c b/drivers/usb/gadget/nokia.c new file mode 100644 index 00000000..3b344b41 --- /dev/null +++ b/drivers/usb/gadget/nokia.c @@ -0,0 +1,279 @@ +/* + * nokia.c -- Nokia Composite Gadget Driver + * + * Copyright (C) 2008-2010 Nokia Corporation + * Contact: Felipe Balbi + * + * This gadget driver borrows from serial.c which is: + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * version 2 of that License. + */ + +#include +#include + +#include "u_serial.h" +#include "u_ether.h" +#include "u_phonet.h" +#include "gadget_chips.h" + +/* Defines */ + +#define NOKIA_VERSION_NUM 0x0211 +#define NOKIA_LONG_NAME "N900 (PC-Suite Mode)" + +/*-------------------------------------------------------------------------*/ + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#define USBF_OBEX_INCLUDED +#include "f_ecm.c" +#include "f_obex.c" +#include "f_phonet.c" +#include "u_ether.c" + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +#define NOKIA_VENDOR_ID 0x0421 /* Nokia */ +#define NOKIA_PRODUCT_ID 0x01c8 /* Nokia Gadget */ + +/* string IDs are assigned dynamically */ + +#define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX + +static char manufacturer_nokia[] = "Nokia"; +static const char product_nokia[] = NOKIA_LONG_NAME; +static const char description_nokia[] = "PC-Suite Configuration"; + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = manufacturer_nokia, + [USB_GADGET_PRODUCT_IDX].s = NOKIA_LONG_NAME, + [USB_GADGET_SERIAL_IDX].s = "", + [STRING_DESCRIPTION_IDX].s = description_nokia, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_COMM, + .idVendor = __constant_cpu_to_le16(NOKIA_VENDOR_ID), + .idProduct = __constant_cpu_to_le16(NOKIA_PRODUCT_ID), + .bcdDevice = cpu_to_le16(NOKIA_VERSION_NUM), + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + .bNumConfigurations = 1, +}; + +/*-------------------------------------------------------------------------*/ + +/* Module */ +MODULE_DESCRIPTION("Nokia composite gadget driver for N900"); +MODULE_AUTHOR("Felipe Balbi"); +MODULE_LICENSE("GPL"); + +/*-------------------------------------------------------------------------*/ +static struct usb_function *f_acm_cfg1; +static struct usb_function *f_acm_cfg2; +static u8 hostaddr[ETH_ALEN]; +static struct eth_dev *the_dev; + +enum { + TTY_PORT_OBEX0, + TTY_PORT_OBEX1, + TTY_PORTS_MAX, +}; + +static unsigned char tty_lines[TTY_PORTS_MAX]; + +static struct usb_configuration nokia_config_500ma_driver = { + .label = "Bus Powered", + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_ONE, + .MaxPower = 500, +}; + +static struct usb_configuration nokia_config_100ma_driver = { + .label = "Self Powered", + .bConfigurationValue = 2, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .MaxPower = 100, +}; + +static struct usb_function_instance *fi_acm; + +static int __init nokia_bind_config(struct usb_configuration *c) +{ + struct usb_function *f_acm; + int status = 0; + + status = phonet_bind_config(c); + if (status) + printk(KERN_DEBUG "could not bind phonet config\n"); + + status = obex_bind_config(c, tty_lines[TTY_PORT_OBEX0]); + if (status) + printk(KERN_DEBUG "could not bind obex config %d\n", 0); + + status = obex_bind_config(c, tty_lines[TTY_PORT_OBEX1]); + if (status) + printk(KERN_DEBUG "could not bind obex config %d\n", 0); + + f_acm = usb_get_function(fi_acm); + if (IS_ERR(f_acm)) + return PTR_ERR(f_acm); + + status = usb_add_function(c, f_acm); + if (status) + goto err_conf; + + status = ecm_bind_config(c, hostaddr, the_dev); + if (status) { + pr_debug("could not bind ecm config %d\n", status); + goto err_ecm; + } + if (c == &nokia_config_500ma_driver) + f_acm_cfg1 = f_acm; + else + f_acm_cfg2 = f_acm; + + return status; +err_ecm: + usb_remove_function(c, f_acm); +err_conf: + usb_put_function(f_acm); + return status; +} + +static int __init nokia_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int status; + int cur_line; + + status = gphonet_setup(cdev->gadget); + if (status < 0) + goto err_phonet; + + for (cur_line = 0; cur_line < TTY_PORTS_MAX; cur_line++) { + status = gserial_alloc_line(&tty_lines[cur_line]); + if (status) + goto err_ether; + } + + the_dev = gether_setup(cdev->gadget, hostaddr); + if (IS_ERR(the_dev)) { + status = PTR_ERR(the_dev); + goto err_ether; + } + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto err_usb; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + status = strings_dev[STRING_DESCRIPTION_IDX].id; + nokia_config_500ma_driver.iConfiguration = status; + nokia_config_100ma_driver.iConfiguration = status; + + if (!gadget_supports_altsettings(gadget)) + goto err_usb; + + fi_acm = usb_get_function_instance("acm"); + if (IS_ERR(fi_acm)) + goto err_usb; + + /* finally register the configuration */ + status = usb_add_config(cdev, &nokia_config_500ma_driver, + nokia_bind_config); + if (status < 0) + goto err_acm_inst; + + status = usb_add_config(cdev, &nokia_config_100ma_driver, + nokia_bind_config); + if (status < 0) + goto err_put_cfg1; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s\n", NOKIA_LONG_NAME); + + return 0; + +err_put_cfg1: + usb_put_function(f_acm_cfg1); +err_acm_inst: + usb_put_function_instance(fi_acm); +err_usb: + gether_cleanup(the_dev); +err_ether: + cur_line--; + while (cur_line >= 0) + gserial_free_line(tty_lines[cur_line--]); + + gphonet_cleanup(); +err_phonet: + return status; +} + +static int __exit nokia_unbind(struct usb_composite_dev *cdev) +{ + int i; + + usb_put_function(f_acm_cfg1); + usb_put_function(f_acm_cfg2); + usb_put_function_instance(fi_acm); + gphonet_cleanup(); + + for (i = 0; i < TTY_PORTS_MAX; i++) + gserial_free_line(tty_lines[i]); + + gether_cleanup(the_dev); + + return 0; +} + +static __refdata struct usb_composite_driver nokia_driver = { + .name = "g_nokia", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = nokia_bind, + .unbind = __exit_p(nokia_unbind), +}; + +static int __init nokia_init(void) +{ + return usb_composite_probe(&nokia_driver); +} +module_init(nokia_init); + +static void __exit nokia_cleanup(void) +{ + usb_composite_unregister(&nokia_driver); +} +module_exit(nokia_cleanup); diff --git a/drivers/usb/gadget/omap_udc.c b/drivers/usb/gadget/omap_udc.c new file mode 100644 index 00000000..b8ed74a8 --- /dev/null +++ b/drivers/usb/gadget/omap_udc.c @@ -0,0 +1,3041 @@ +/* + * omap_udc.c -- for OMAP full speed udc; most chips support OTG. + * + * Copyright (C) 2004 Texas Instruments, Inc. + * Copyright (C) 2004-2005 David Brownell + * + * OMAP2 & DMA support by Kyungmin Park + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#undef DEBUG +#undef VERBOSE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include "omap_udc.h" + +#undef USB_TRACE + +/* bulk DMA seems to be behaving for both IN and OUT */ +#define USE_DMA + +/* ISO too */ +#define USE_ISO + +#define DRIVER_DESC "OMAP UDC driver" +#define DRIVER_VERSION "4 October 2004" + +#define OMAP_DMA_USB_W2FC_TX0 29 +#define OMAP_DMA_USB_W2FC_RX0 26 + +/* + * The OMAP UDC needs _very_ early endpoint setup: before enabling the + * D+ pullup to allow enumeration. That's too early for the gadget + * framework to use from usb_endpoint_enable(), which happens after + * enumeration as part of activating an interface. (But if we add an + * optional new "UDC not yet running" state to the gadget driver model, + * even just during driver binding, the endpoint autoconfig logic is the + * natural spot to manufacture new endpoints.) + * + * So instead of using endpoint enable calls to control the hardware setup, + * this driver defines a "fifo mode" parameter. It's used during driver + * initialization to choose among a set of pre-defined endpoint configs. + * See omap_udc_setup() for available modes, or to add others. That code + * lives in an init section, so use this driver as a module if you need + * to change the fifo mode after the kernel boots. + * + * Gadget drivers normally ignore endpoints they don't care about, and + * won't include them in configuration descriptors. That means only + * misbehaving hosts would even notice they exist. + */ +#ifdef USE_ISO +static unsigned fifo_mode = 3; +#else +static unsigned fifo_mode; +#endif + +/* "modprobe omap_udc fifo_mode=42", or else as a kernel + * boot parameter "omap_udc:fifo_mode=42" + */ +module_param(fifo_mode, uint, 0); +MODULE_PARM_DESC(fifo_mode, "endpoint configuration"); + +#ifdef USE_DMA +static bool use_dma = 1; + +/* "modprobe omap_udc use_dma=y", or else as a kernel + * boot parameter "omap_udc:use_dma=y" + */ +module_param(use_dma, bool, 0); +MODULE_PARM_DESC(use_dma, "enable/disable DMA"); +#else /* !USE_DMA */ + +/* save a bit of code */ +#define use_dma 0 +#endif /* !USE_DMA */ + + +static const char driver_name[] = "omap_udc"; +static const char driver_desc[] = DRIVER_DESC; + +/*-------------------------------------------------------------------------*/ + +/* there's a notion of "current endpoint" for modifying endpoint + * state, and PIO access to its FIFO. + */ + +static void use_ep(struct omap_ep *ep, u16 select) +{ + u16 num = ep->bEndpointAddress & 0x0f; + + if (ep->bEndpointAddress & USB_DIR_IN) + num |= UDC_EP_DIR; + omap_writew(num | select, UDC_EP_NUM); + /* when select, MUST deselect later !! */ +} + +static inline void deselect_ep(void) +{ + u16 w; + + w = omap_readw(UDC_EP_NUM); + w &= ~UDC_EP_SEL; + omap_writew(w, UDC_EP_NUM); + /* 6 wait states before TX will happen */ +} + +static void dma_channel_claim(struct omap_ep *ep, unsigned preferred); + +/*-------------------------------------------------------------------------*/ + +static int omap_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct omap_ep *ep = container_of(_ep, struct omap_ep, ep); + struct omap_udc *udc; + unsigned long flags; + u16 maxp; + + /* catch various bogus parameters */ + if (!_ep || !desc + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep->maxpacket < usb_endpoint_maxp(desc)) { + DBG("%s, bad ep or descriptor\n", __func__); + return -EINVAL; + } + maxp = usb_endpoint_maxp(desc); + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && maxp != ep->maxpacket) + || usb_endpoint_maxp(desc) > ep->maxpacket + || !desc->wMaxPacketSize) { + DBG("%s, bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + +#ifdef USE_ISO + if ((desc->bmAttributes == USB_ENDPOINT_XFER_ISOC + && desc->bInterval != 1)) { + /* hardware wants period = 1; USB allows 2^(Interval-1) */ + DBG("%s, unsupported ISO period %dms\n", _ep->name, + 1 << (desc->bInterval - 1)); + return -EDOM; + } +#else + if (desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + DBG("%s, ISO nyet\n", _ep->name); + return -EDOM; + } +#endif + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != desc->bmAttributes + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + DBG("%s, %s type mismatch\n", __func__, _ep->name); + return -EINVAL; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + DBG("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&udc->lock, flags); + + ep->ep.desc = desc; + ep->irqs = 0; + ep->stopped = 0; + ep->ep.maxpacket = maxp; + + /* set endpoint to initial state */ + ep->dma_channel = 0; + ep->has_dma = 0; + ep->lch = -1; + use_ep(ep, UDC_EP_SEL); + omap_writew(udc->clr_halt, UDC_CTRL); + ep->ackwait = 0; + deselect_ep(); + + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) + list_add(&ep->iso, &udc->iso); + + /* maybe assign a DMA channel to this endpoint */ + if (use_dma && desc->bmAttributes == USB_ENDPOINT_XFER_BULK) + /* FIXME ISO can dma, but prefers first channel */ + dma_channel_claim(ep, 0); + + /* PIO OUT may RX packets */ + if (desc->bmAttributes != USB_ENDPOINT_XFER_ISOC + && !ep->has_dma + && !(ep->bEndpointAddress & USB_DIR_IN)) { + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + ep->ackwait = 1 + ep->double_buf; + } + + spin_unlock_irqrestore(&udc->lock, flags); + VDBG("%s enabled\n", _ep->name); + return 0; +} + +static void nuke(struct omap_ep *, int status); + +static int omap_ep_disable(struct usb_ep *_ep) +{ + struct omap_ep *ep = container_of(_ep, struct omap_ep, ep); + unsigned long flags; + + if (!_ep || !ep->ep.desc) { + DBG("%s, %s not enabled\n", __func__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + spin_lock_irqsave(&ep->udc->lock, flags); + ep->ep.desc = NULL; + nuke(ep, -ESHUTDOWN); + ep->ep.maxpacket = ep->maxpacket; + ep->has_dma = 0; + omap_writew(UDC_SET_HALT, UDC_CTRL); + list_del_init(&ep->iso); + del_timer(&ep->timer); + + spin_unlock_irqrestore(&ep->udc->lock, flags); + + VDBG("%s disabled\n", _ep->name); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_request * +omap_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) +{ + struct omap_req *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void +omap_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct omap_req *req = container_of(_req, struct omap_req, req); + + kfree(req); +} + +/*-------------------------------------------------------------------------*/ + +static void +done(struct omap_ep *ep, struct omap_req *req, int status) +{ + struct omap_udc *udc = ep->udc; + unsigned stopped = ep->stopped; + + list_del_init(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (use_dma && ep->has_dma) + usb_gadget_unmap_request(&udc->gadget, &req->req, + (ep->bEndpointAddress & USB_DIR_IN)); + +#ifndef USB_TRACE + if (status && status != -ESHUTDOWN) +#endif + VDBG("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + spin_unlock(&ep->udc->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->udc->lock); + ep->stopped = stopped; +} + +/*-------------------------------------------------------------------------*/ + +#define UDC_FIFO_FULL (UDC_NON_ISO_FIFO_FULL | UDC_ISO_FIFO_FULL) +#define UDC_FIFO_UNWRITABLE (UDC_EP_HALTED | UDC_FIFO_FULL) + +#define FIFO_EMPTY (UDC_NON_ISO_FIFO_EMPTY | UDC_ISO_FIFO_EMPTY) +#define FIFO_UNREADABLE (UDC_EP_HALTED | FIFO_EMPTY) + +static inline int +write_packet(u8 *buf, struct omap_req *req, unsigned max) +{ + unsigned len; + u16 *wp; + + len = min(req->req.length - req->req.actual, max); + req->req.actual += len; + + max = len; + if (likely((((int)buf) & 1) == 0)) { + wp = (u16 *)buf; + while (max >= 2) { + omap_writew(*wp++, UDC_DATA); + max -= 2; + } + buf = (u8 *)wp; + } + while (max--) + omap_writeb(*buf++, UDC_DATA); + return len; +} + +/* FIXME change r/w fifo calling convention */ + + +/* return: 0 = still running, 1 = completed, negative = errno */ +static int write_fifo(struct omap_ep *ep, struct omap_req *req) +{ + u8 *buf; + unsigned count; + int is_last; + u16 ep_stat; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + /* PIO-IN isn't double buffered except for iso */ + ep_stat = omap_readw(UDC_STAT_FLG); + if (ep_stat & UDC_FIFO_UNWRITABLE) + return 0; + + count = ep->ep.maxpacket; + count = write_packet(buf, req, count); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + ep->ackwait = 1; + + /* last packet is often short (sometimes a zlp) */ + if (count != ep->ep.maxpacket) + is_last = 1; + else if (req->req.length == req->req.actual + && !req->req.zero) + is_last = 1; + else + is_last = 0; + + /* NOTE: requests complete when all IN data is in a + * FIFO (or sometimes later, if a zlp was needed). + * Use usb_ep_fifo_status() where needed. + */ + if (is_last) + done(ep, req, 0); + return is_last; +} + +static inline int +read_packet(u8 *buf, struct omap_req *req, unsigned avail) +{ + unsigned len; + u16 *wp; + + len = min(req->req.length - req->req.actual, avail); + req->req.actual += len; + avail = len; + + if (likely((((int)buf) & 1) == 0)) { + wp = (u16 *)buf; + while (avail >= 2) { + *wp++ = omap_readw(UDC_DATA); + avail -= 2; + } + buf = (u8 *)wp; + } + while (avail--) + *buf++ = omap_readb(UDC_DATA); + return len; +} + +/* return: 0 = still running, 1 = queue empty, negative = errno */ +static int read_fifo(struct omap_ep *ep, struct omap_req *req) +{ + u8 *buf; + unsigned count, avail; + int is_last; + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + for (;;) { + u16 ep_stat = omap_readw(UDC_STAT_FLG); + + is_last = 0; + if (ep_stat & FIFO_EMPTY) { + if (!ep->double_buf) + break; + ep->fnf = 1; + } + if (ep_stat & UDC_EP_HALTED) + break; + + if (ep_stat & UDC_FIFO_FULL) + avail = ep->ep.maxpacket; + else { + avail = omap_readw(UDC_RXFSTAT); + ep->fnf = ep->double_buf; + } + count = read_packet(buf, req, avail); + + /* partial packet reads may not be errors */ + if (count < ep->ep.maxpacket) { + is_last = 1; + /* overflowed this request? flush extra data */ + if (count != avail) { + req->req.status = -EOVERFLOW; + avail -= count; + while (avail--) + omap_readw(UDC_DATA); + } + } else if (req->req.length == req->req.actual) + is_last = 1; + else + is_last = 0; + + if (!ep->bEndpointAddress) + break; + if (is_last) + done(ep, req, 0); + break; + } + return is_last; +} + +/*-------------------------------------------------------------------------*/ + +static u16 dma_src_len(struct omap_ep *ep, dma_addr_t start) +{ + dma_addr_t end; + + /* IN-DMA needs this on fault/cancel paths, so 15xx misreports + * the last transfer's bytecount by more than a FIFO's worth. + */ + if (cpu_is_omap15xx()) + return 0; + + end = omap_get_dma_src_pos(ep->lch); + if (end == ep->dma_counter) + return 0; + + end |= start & (0xffff << 16); + if (end < start) + end += 0x10000; + return end - start; +} + +static u16 dma_dest_len(struct omap_ep *ep, dma_addr_t start) +{ + dma_addr_t end; + + end = omap_get_dma_dst_pos(ep->lch); + if (end == ep->dma_counter) + return 0; + + end |= start & (0xffff << 16); + if (cpu_is_omap15xx()) + end++; + if (end < start) + end += 0x10000; + return end - start; +} + + +/* Each USB transfer request using DMA maps to one or more DMA transfers. + * When DMA completion isn't request completion, the UDC continues with + * the next DMA transfer for that USB transfer. + */ + +static void next_in_dma(struct omap_ep *ep, struct omap_req *req) +{ + u16 txdma_ctrl, w; + unsigned length = req->req.length - req->req.actual; + const int sync_mode = cpu_is_omap15xx() + ? OMAP_DMA_SYNC_FRAME + : OMAP_DMA_SYNC_ELEMENT; + int dma_trigger = 0; + + /* measure length in either bytes or packets */ + if ((cpu_is_omap16xx() && length <= UDC_TXN_TSC) + || (cpu_is_omap15xx() && length < ep->maxpacket)) { + txdma_ctrl = UDC_TXN_EOT | length; + omap_set_dma_transfer_params(ep->lch, OMAP_DMA_DATA_TYPE_S8, + length, 1, sync_mode, dma_trigger, 0); + } else { + length = min(length / ep->maxpacket, + (unsigned) UDC_TXN_TSC + 1); + txdma_ctrl = length; + omap_set_dma_transfer_params(ep->lch, OMAP_DMA_DATA_TYPE_S16, + ep->ep.maxpacket >> 1, length, sync_mode, + dma_trigger, 0); + length *= ep->maxpacket; + } + omap_set_dma_src_params(ep->lch, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, req->req.dma + req->req.actual, + 0, 0); + + omap_start_dma(ep->lch); + ep->dma_counter = omap_get_dma_src_pos(ep->lch); + w = omap_readw(UDC_DMA_IRQ_EN); + w |= UDC_TX_DONE_IE(ep->dma_channel); + omap_writew(w, UDC_DMA_IRQ_EN); + omap_writew(UDC_TXN_START | txdma_ctrl, UDC_TXDMA(ep->dma_channel)); + req->dma_bytes = length; +} + +static void finish_in_dma(struct omap_ep *ep, struct omap_req *req, int status) +{ + u16 w; + + if (status == 0) { + req->req.actual += req->dma_bytes; + + /* return if this request needs to send data or zlp */ + if (req->req.actual < req->req.length) + return; + if (req->req.zero + && req->dma_bytes != 0 + && (req->req.actual % ep->maxpacket) == 0) + return; + } else + req->req.actual += dma_src_len(ep, req->req.dma + + req->req.actual); + + /* tx completion */ + omap_stop_dma(ep->lch); + w = omap_readw(UDC_DMA_IRQ_EN); + w &= ~UDC_TX_DONE_IE(ep->dma_channel); + omap_writew(w, UDC_DMA_IRQ_EN); + done(ep, req, status); +} + +static void next_out_dma(struct omap_ep *ep, struct omap_req *req) +{ + unsigned packets = req->req.length - req->req.actual; + int dma_trigger = 0; + u16 w; + + /* set up this DMA transfer, enable the fifo, start */ + packets /= ep->ep.maxpacket; + packets = min(packets, (unsigned)UDC_RXN_TC + 1); + req->dma_bytes = packets * ep->ep.maxpacket; + omap_set_dma_transfer_params(ep->lch, OMAP_DMA_DATA_TYPE_S16, + ep->ep.maxpacket >> 1, packets, + OMAP_DMA_SYNC_ELEMENT, + dma_trigger, 0); + omap_set_dma_dest_params(ep->lch, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, req->req.dma + req->req.actual, + 0, 0); + ep->dma_counter = omap_get_dma_dst_pos(ep->lch); + + omap_writew(UDC_RXN_STOP | (packets - 1), UDC_RXDMA(ep->dma_channel)); + w = omap_readw(UDC_DMA_IRQ_EN); + w |= UDC_RX_EOT_IE(ep->dma_channel); + omap_writew(w, UDC_DMA_IRQ_EN); + omap_writew(ep->bEndpointAddress & 0xf, UDC_EP_NUM); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + + omap_start_dma(ep->lch); +} + +static void +finish_out_dma(struct omap_ep *ep, struct omap_req *req, int status, int one) +{ + u16 count, w; + + if (status == 0) + ep->dma_counter = (u16) (req->req.dma + req->req.actual); + count = dma_dest_len(ep, req->req.dma + req->req.actual); + count += req->req.actual; + if (one) + count--; + if (count <= req->req.length) + req->req.actual = count; + + if (count != req->dma_bytes || status) + omap_stop_dma(ep->lch); + + /* if this wasn't short, request may need another transfer */ + else if (req->req.actual < req->req.length) + return; + + /* rx completion */ + w = omap_readw(UDC_DMA_IRQ_EN); + w &= ~UDC_RX_EOT_IE(ep->dma_channel); + omap_writew(w, UDC_DMA_IRQ_EN); + done(ep, req, status); +} + +static void dma_irq(struct omap_udc *udc, u16 irq_src) +{ + u16 dman_stat = omap_readw(UDC_DMAN_STAT); + struct omap_ep *ep; + struct omap_req *req; + + /* IN dma: tx to host */ + if (irq_src & UDC_TXN_DONE) { + ep = &udc->ep[16 + UDC_DMA_TX_SRC(dman_stat)]; + ep->irqs++; + /* can see TXN_DONE after dma abort */ + if (!list_empty(&ep->queue)) { + req = container_of(ep->queue.next, + struct omap_req, queue); + finish_in_dma(ep, req, 0); + } + omap_writew(UDC_TXN_DONE, UDC_IRQ_SRC); + + if (!list_empty(&ep->queue)) { + req = container_of(ep->queue.next, + struct omap_req, queue); + next_in_dma(ep, req); + } + } + + /* OUT dma: rx from host */ + if (irq_src & UDC_RXN_EOT) { + ep = &udc->ep[UDC_DMA_RX_SRC(dman_stat)]; + ep->irqs++; + /* can see RXN_EOT after dma abort */ + if (!list_empty(&ep->queue)) { + req = container_of(ep->queue.next, + struct omap_req, queue); + finish_out_dma(ep, req, 0, dman_stat & UDC_DMA_RX_SB); + } + omap_writew(UDC_RXN_EOT, UDC_IRQ_SRC); + + if (!list_empty(&ep->queue)) { + req = container_of(ep->queue.next, + struct omap_req, queue); + next_out_dma(ep, req); + } + } + + if (irq_src & UDC_RXN_CNT) { + ep = &udc->ep[UDC_DMA_RX_SRC(dman_stat)]; + ep->irqs++; + /* omap15xx does this unasked... */ + VDBG("%s, RX_CNT irq?\n", ep->ep.name); + omap_writew(UDC_RXN_CNT, UDC_IRQ_SRC); + } +} + +static void dma_error(int lch, u16 ch_status, void *data) +{ + struct omap_ep *ep = data; + + /* if ch_status & OMAP_DMA_DROP_IRQ ... */ + /* if ch_status & OMAP1_DMA_TOUT_IRQ ... */ + ERR("%s dma error, lch %d status %02x\n", ep->ep.name, lch, ch_status); + + /* complete current transfer ... */ +} + +static void dma_channel_claim(struct omap_ep *ep, unsigned channel) +{ + u16 reg; + int status, restart, is_in; + int dma_channel; + + is_in = ep->bEndpointAddress & USB_DIR_IN; + if (is_in) + reg = omap_readw(UDC_TXDMA_CFG); + else + reg = omap_readw(UDC_RXDMA_CFG); + reg |= UDC_DMA_REQ; /* "pulse" activated */ + + ep->dma_channel = 0; + ep->lch = -1; + if (channel == 0 || channel > 3) { + if ((reg & 0x0f00) == 0) + channel = 3; + else if ((reg & 0x00f0) == 0) + channel = 2; + else if ((reg & 0x000f) == 0) /* preferred for ISO */ + channel = 1; + else { + status = -EMLINK; + goto just_restart; + } + } + reg |= (0x0f & ep->bEndpointAddress) << (4 * (channel - 1)); + ep->dma_channel = channel; + + if (is_in) { + dma_channel = OMAP_DMA_USB_W2FC_TX0 - 1 + channel; + status = omap_request_dma(dma_channel, + ep->ep.name, dma_error, ep, &ep->lch); + if (status == 0) { + omap_writew(reg, UDC_TXDMA_CFG); + /* EMIFF or SDRC */ + omap_set_dma_src_burst_mode(ep->lch, + OMAP_DMA_DATA_BURST_4); + omap_set_dma_src_data_pack(ep->lch, 1); + /* TIPB */ + omap_set_dma_dest_params(ep->lch, + OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, + UDC_DATA_DMA, + 0, 0); + } + } else { + dma_channel = OMAP_DMA_USB_W2FC_RX0 - 1 + channel; + status = omap_request_dma(dma_channel, + ep->ep.name, dma_error, ep, &ep->lch); + if (status == 0) { + omap_writew(reg, UDC_RXDMA_CFG); + /* TIPB */ + omap_set_dma_src_params(ep->lch, + OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, + UDC_DATA_DMA, + 0, 0); + /* EMIFF or SDRC */ + omap_set_dma_dest_burst_mode(ep->lch, + OMAP_DMA_DATA_BURST_4); + omap_set_dma_dest_data_pack(ep->lch, 1); + } + } + if (status) + ep->dma_channel = 0; + else { + ep->has_dma = 1; + omap_disable_dma_irq(ep->lch, OMAP_DMA_BLOCK_IRQ); + + /* channel type P: hw synch (fifo) */ + if (!cpu_is_omap15xx()) + omap_set_dma_channel_mode(ep->lch, OMAP_DMA_LCH_P); + } + +just_restart: + /* restart any queue, even if the claim failed */ + restart = !ep->stopped && !list_empty(&ep->queue); + + if (status) + DBG("%s no dma channel: %d%s\n", ep->ep.name, status, + restart ? " (restart)" : ""); + else + DBG("%s claimed %cxdma%d lch %d%s\n", ep->ep.name, + is_in ? 't' : 'r', + ep->dma_channel - 1, ep->lch, + restart ? " (restart)" : ""); + + if (restart) { + struct omap_req *req; + req = container_of(ep->queue.next, struct omap_req, queue); + if (ep->has_dma) + (is_in ? next_in_dma : next_out_dma)(ep, req); + else { + use_ep(ep, UDC_EP_SEL); + (is_in ? write_fifo : read_fifo)(ep, req); + deselect_ep(); + if (!is_in) { + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + ep->ackwait = 1 + ep->double_buf; + } + /* IN: 6 wait states before it'll tx */ + } + } +} + +static void dma_channel_release(struct omap_ep *ep) +{ + int shift = 4 * (ep->dma_channel - 1); + u16 mask = 0x0f << shift; + struct omap_req *req; + int active; + + /* abort any active usb transfer request */ + if (!list_empty(&ep->queue)) + req = container_of(ep->queue.next, struct omap_req, queue); + else + req = NULL; + + active = omap_get_dma_active_status(ep->lch); + + DBG("%s release %s %cxdma%d %p\n", ep->ep.name, + active ? "active" : "idle", + (ep->bEndpointAddress & USB_DIR_IN) ? 't' : 'r', + ep->dma_channel - 1, req); + + /* NOTE: re-setting RX_REQ/TX_REQ because of a chip bug (before + * OMAP 1710 ES2.0) where reading the DMA_CFG can clear them. + */ + + /* wait till current packet DMA finishes, and fifo empties */ + if (ep->bEndpointAddress & USB_DIR_IN) { + omap_writew((omap_readw(UDC_TXDMA_CFG) & ~mask) | UDC_DMA_REQ, + UDC_TXDMA_CFG); + + if (req) { + finish_in_dma(ep, req, -ECONNRESET); + + /* clear FIFO; hosts probably won't empty it */ + use_ep(ep, UDC_EP_SEL); + omap_writew(UDC_CLR_EP, UDC_CTRL); + deselect_ep(); + } + while (omap_readw(UDC_TXDMA_CFG) & mask) + udelay(10); + } else { + omap_writew((omap_readw(UDC_RXDMA_CFG) & ~mask) | UDC_DMA_REQ, + UDC_RXDMA_CFG); + + /* dma empties the fifo */ + while (omap_readw(UDC_RXDMA_CFG) & mask) + udelay(10); + if (req) + finish_out_dma(ep, req, -ECONNRESET, 0); + } + omap_free_dma(ep->lch); + ep->dma_channel = 0; + ep->lch = -1; + /* has_dma still set, till endpoint is fully quiesced */ +} + + +/*-------------------------------------------------------------------------*/ + +static int +omap_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct omap_ep *ep = container_of(_ep, struct omap_ep, ep); + struct omap_req *req = container_of(_req, struct omap_req, req); + struct omap_udc *udc; + unsigned long flags; + int is_iso = 0; + + /* catch various bogus parameters */ + if (!_req || !req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + DBG("%s, bad params\n", __func__); + return -EINVAL; + } + if (!_ep || (!ep->ep.desc && ep->bEndpointAddress)) { + DBG("%s, bad ep\n", __func__); + return -EINVAL; + } + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + is_iso = 1; + } + + /* this isn't bogus, but OMAP DMA isn't the only hardware to + * have a hard time with partial packet reads... reject it. + */ + if (use_dma + && ep->has_dma + && ep->bEndpointAddress != 0 + && (ep->bEndpointAddress & USB_DIR_IN) == 0 + && (req->req.length % ep->ep.maxpacket) != 0) { + DBG("%s, no partial packet OUT reads\n", __func__); + return -EMSGSIZE; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + if (use_dma && ep->has_dma) + usb_gadget_map_request(&udc->gadget, &req->req, + (ep->bEndpointAddress & USB_DIR_IN)); + + VDBG("%s queue req %p, len %d buf %p\n", + ep->ep.name, _req, _req->length, _req->buf); + + spin_lock_irqsave(&udc->lock, flags); + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + + /* maybe kickstart non-iso i/o queues */ + if (is_iso) { + u16 w; + + w = omap_readw(UDC_IRQ_EN); + w |= UDC_SOF_IE; + omap_writew(w, UDC_IRQ_EN); + } else if (list_empty(&ep->queue) && !ep->stopped && !ep->ackwait) { + int is_in; + + if (ep->bEndpointAddress == 0) { + if (!udc->ep0_pending || !list_empty(&ep->queue)) { + spin_unlock_irqrestore(&udc->lock, flags); + return -EL2HLT; + } + + /* empty DATA stage? */ + is_in = udc->ep0_in; + if (!req->req.length) { + + /* chip became CONFIGURED or ADDRESSED + * earlier; drivers may already have queued + * requests to non-control endpoints + */ + if (udc->ep0_set_config) { + u16 irq_en = omap_readw(UDC_IRQ_EN); + + irq_en |= UDC_DS_CHG_IE | UDC_EP0_IE; + if (!udc->ep0_reset_config) + irq_en |= UDC_EPN_RX_IE + | UDC_EPN_TX_IE; + omap_writew(irq_en, UDC_IRQ_EN); + } + + /* STATUS for zero length DATA stages is + * always an IN ... even for IN transfers, + * a weird case which seem to stall OMAP. + */ + omap_writew(UDC_EP_SEL | UDC_EP_DIR, + UDC_EP_NUM); + omap_writew(UDC_CLR_EP, UDC_CTRL); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + omap_writew(UDC_EP_DIR, UDC_EP_NUM); + + /* cleanup */ + udc->ep0_pending = 0; + done(ep, req, 0); + req = NULL; + + /* non-empty DATA stage */ + } else if (is_in) { + omap_writew(UDC_EP_SEL | UDC_EP_DIR, + UDC_EP_NUM); + } else { + if (udc->ep0_setup) + goto irq_wait; + omap_writew(UDC_EP_SEL, UDC_EP_NUM); + } + } else { + is_in = ep->bEndpointAddress & USB_DIR_IN; + if (!ep->has_dma) + use_ep(ep, UDC_EP_SEL); + /* if ISO: SOF IRQs must be enabled/disabled! */ + } + + if (ep->has_dma) + (is_in ? next_in_dma : next_out_dma)(ep, req); + else if (req) { + if ((is_in ? write_fifo : read_fifo)(ep, req) == 1) + req = NULL; + deselect_ep(); + if (!is_in) { + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + ep->ackwait = 1 + ep->double_buf; + } + /* IN: 6 wait states before it'll tx */ + } + } + +irq_wait: + /* irq handler advances the queue */ + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int omap_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct omap_ep *ep = container_of(_ep, struct omap_ep, ep); + struct omap_req *req; + unsigned long flags; + + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->udc->lock, flags); + return -EINVAL; + } + + if (use_dma && ep->dma_channel && ep->queue.next == &req->queue) { + int channel = ep->dma_channel; + + /* releasing the channel cancels the request, + * reclaiming the channel restarts the queue + */ + dma_channel_release(ep); + dma_channel_claim(ep, channel); + } else + done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->udc->lock, flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int omap_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct omap_ep *ep = container_of(_ep, struct omap_ep, ep); + unsigned long flags; + int status = -EOPNOTSUPP; + + spin_lock_irqsave(&ep->udc->lock, flags); + + /* just use protocol stalls for ep0; real halts are annoying */ + if (ep->bEndpointAddress == 0) { + if (!ep->udc->ep0_pending) + status = -EINVAL; + else if (value) { + if (ep->udc->ep0_set_config) { + WARNING("error changing config?\n"); + omap_writew(UDC_CLR_CFG, UDC_SYSCON2); + } + omap_writew(UDC_STALL_CMD, UDC_SYSCON2); + ep->udc->ep0_pending = 0; + status = 0; + } else /* NOP */ + status = 0; + + /* otherwise, all active non-ISO endpoints can halt */ + } else if (ep->bmAttributes != USB_ENDPOINT_XFER_ISOC && ep->ep.desc) { + + /* IN endpoints must already be idle */ + if ((ep->bEndpointAddress & USB_DIR_IN) + && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto done; + } + + if (value) { + int channel; + + if (use_dma && ep->dma_channel + && !list_empty(&ep->queue)) { + channel = ep->dma_channel; + dma_channel_release(ep); + } else + channel = 0; + + use_ep(ep, UDC_EP_SEL); + if (omap_readw(UDC_STAT_FLG) & UDC_NON_ISO_FIFO_EMPTY) { + omap_writew(UDC_SET_HALT, UDC_CTRL); + status = 0; + } else + status = -EAGAIN; + deselect_ep(); + + if (channel) + dma_channel_claim(ep, channel); + } else { + use_ep(ep, 0); + omap_writew(ep->udc->clr_halt, UDC_CTRL); + ep->ackwait = 0; + if (!(ep->bEndpointAddress & USB_DIR_IN)) { + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + ep->ackwait = 1 + ep->double_buf; + } + } + } +done: + VDBG("%s %s halt stat %d\n", ep->ep.name, + value ? "set" : "clear", status); + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return status; +} + +static struct usb_ep_ops omap_ep_ops = { + .enable = omap_ep_enable, + .disable = omap_ep_disable, + + .alloc_request = omap_alloc_request, + .free_request = omap_free_request, + + .queue = omap_ep_queue, + .dequeue = omap_ep_dequeue, + + .set_halt = omap_ep_set_halt, + /* fifo_status ... report bytes in fifo */ + /* fifo_flush ... flush fifo */ +}; + +/*-------------------------------------------------------------------------*/ + +static int omap_get_frame(struct usb_gadget *gadget) +{ + u16 sof = omap_readw(UDC_SOF); + return (sof & UDC_TS_OK) ? (sof & UDC_TS) : -EL2NSYNC; +} + +static int omap_wakeup(struct usb_gadget *gadget) +{ + struct omap_udc *udc; + unsigned long flags; + int retval = -EHOSTUNREACH; + + udc = container_of(gadget, struct omap_udc, gadget); + + spin_lock_irqsave(&udc->lock, flags); + if (udc->devstat & UDC_SUS) { + /* NOTE: OTG spec erratum says that OTG devices may + * issue wakeups without host enable. + */ + if (udc->devstat & (UDC_B_HNP_ENABLE|UDC_R_WK_OK)) { + DBG("remote wakeup...\n"); + omap_writew(UDC_RMT_WKP, UDC_SYSCON2); + retval = 0; + } + + /* NOTE: non-OTG systems may use SRP TOO... */ + } else if (!(udc->devstat & UDC_ATT)) { + if (!IS_ERR_OR_NULL(udc->transceiver)) + retval = otg_start_srp(udc->transceiver->otg); + } + spin_unlock_irqrestore(&udc->lock, flags); + + return retval; +} + +static int +omap_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) +{ + struct omap_udc *udc; + unsigned long flags; + u16 syscon1; + + udc = container_of(gadget, struct omap_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + syscon1 = omap_readw(UDC_SYSCON1); + if (is_selfpowered) + syscon1 |= UDC_SELF_PWR; + else + syscon1 &= ~UDC_SELF_PWR; + omap_writew(syscon1, UDC_SYSCON1); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int can_pullup(struct omap_udc *udc) +{ + return udc->driver && udc->softconnect && udc->vbus_active; +} + +static void pullup_enable(struct omap_udc *udc) +{ + u16 w; + + w = omap_readw(UDC_SYSCON1); + w |= UDC_PULLUP_EN; + omap_writew(w, UDC_SYSCON1); + if (!gadget_is_otg(&udc->gadget) && !cpu_is_omap15xx()) { + u32 l; + + l = omap_readl(OTG_CTRL); + l |= OTG_BSESSVLD; + omap_writel(l, OTG_CTRL); + } + omap_writew(UDC_DS_CHG_IE, UDC_IRQ_EN); +} + +static void pullup_disable(struct omap_udc *udc) +{ + u16 w; + + if (!gadget_is_otg(&udc->gadget) && !cpu_is_omap15xx()) { + u32 l; + + l = omap_readl(OTG_CTRL); + l &= ~OTG_BSESSVLD; + omap_writel(l, OTG_CTRL); + } + omap_writew(UDC_DS_CHG_IE, UDC_IRQ_EN); + w = omap_readw(UDC_SYSCON1); + w &= ~UDC_PULLUP_EN; + omap_writew(w, UDC_SYSCON1); +} + +static struct omap_udc *udc; + +static void omap_udc_enable_clock(int enable) +{ + if (udc == NULL || udc->dc_clk == NULL || udc->hhc_clk == NULL) + return; + + if (enable) { + clk_enable(udc->dc_clk); + clk_enable(udc->hhc_clk); + udelay(100); + } else { + clk_disable(udc->hhc_clk); + clk_disable(udc->dc_clk); + } +} + +/* + * Called by whatever detects VBUS sessions: external transceiver + * driver, or maybe GPIO0 VBUS IRQ. May request 48 MHz clock. + */ +static int omap_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct omap_udc *udc; + unsigned long flags; + u32 l; + + udc = container_of(gadget, struct omap_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + VDBG("VBUS %s\n", is_active ? "on" : "off"); + udc->vbus_active = (is_active != 0); + if (cpu_is_omap15xx()) { + /* "software" detect, ignored if !VBUS_MODE_1510 */ + l = omap_readl(FUNC_MUX_CTRL_0); + if (is_active) + l |= VBUS_CTRL_1510; + else + l &= ~VBUS_CTRL_1510; + omap_writel(l, FUNC_MUX_CTRL_0); + } + if (udc->dc_clk != NULL && is_active) { + if (!udc->clk_requested) { + omap_udc_enable_clock(1); + udc->clk_requested = 1; + } + } + if (can_pullup(udc)) + pullup_enable(udc); + else + pullup_disable(udc); + if (udc->dc_clk != NULL && !is_active) { + if (udc->clk_requested) { + omap_udc_enable_clock(0); + udc->clk_requested = 0; + } + } + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int omap_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct omap_udc *udc; + + udc = container_of(gadget, struct omap_udc, gadget); + if (!IS_ERR_OR_NULL(udc->transceiver)) + return usb_phy_set_power(udc->transceiver, mA); + return -EOPNOTSUPP; +} + +static int omap_pullup(struct usb_gadget *gadget, int is_on) +{ + struct omap_udc *udc; + unsigned long flags; + + udc = container_of(gadget, struct omap_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + udc->softconnect = (is_on != 0); + if (can_pullup(udc)) + pullup_enable(udc); + else + pullup_disable(udc); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int omap_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int omap_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops omap_gadget_ops = { + .get_frame = omap_get_frame, + .wakeup = omap_wakeup, + .set_selfpowered = omap_set_selfpowered, + .vbus_session = omap_vbus_session, + .vbus_draw = omap_vbus_draw, + .pullup = omap_pullup, + .udc_start = omap_udc_start, + .udc_stop = omap_udc_stop, +}; + +/*-------------------------------------------------------------------------*/ + +/* dequeue ALL requests; caller holds udc->lock */ +static void nuke(struct omap_ep *ep, int status) +{ + struct omap_req *req; + + ep->stopped = 1; + + if (use_dma && ep->dma_channel) + dma_channel_release(ep); + + use_ep(ep, 0); + omap_writew(UDC_CLR_EP, UDC_CTRL); + if (ep->bEndpointAddress && ep->bmAttributes != USB_ENDPOINT_XFER_ISOC) + omap_writew(UDC_SET_HALT, UDC_CTRL); + + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct omap_req, queue); + done(ep, req, status); + } +} + +/* caller holds udc->lock */ +static void udc_quiesce(struct omap_udc *udc) +{ + struct omap_ep *ep; + + udc->gadget.speed = USB_SPEED_UNKNOWN; + nuke(&udc->ep[0], -ESHUTDOWN); + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) + nuke(ep, -ESHUTDOWN); +} + +/*-------------------------------------------------------------------------*/ + +static void update_otg(struct omap_udc *udc) +{ + u16 devstat; + + if (!gadget_is_otg(&udc->gadget)) + return; + + if (omap_readl(OTG_CTRL) & OTG_ID) + devstat = omap_readw(UDC_DEVSTAT); + else + devstat = 0; + + udc->gadget.b_hnp_enable = !!(devstat & UDC_B_HNP_ENABLE); + udc->gadget.a_hnp_support = !!(devstat & UDC_A_HNP_SUPPORT); + udc->gadget.a_alt_hnp_support = !!(devstat & UDC_A_ALT_HNP_SUPPORT); + + /* Enable HNP early, avoiding races on suspend irq path. + * ASSUMES OTG state machine B_BUS_REQ input is true. + */ + if (udc->gadget.b_hnp_enable) { + u32 l; + + l = omap_readl(OTG_CTRL); + l |= OTG_B_HNPEN | OTG_B_BUSREQ; + l &= ~OTG_PULLUP; + omap_writel(l, OTG_CTRL); + } +} + +static void ep0_irq(struct omap_udc *udc, u16 irq_src) +{ + struct omap_ep *ep0 = &udc->ep[0]; + struct omap_req *req = NULL; + + ep0->irqs++; + + /* Clear any pending requests and then scrub any rx/tx state + * before starting to handle the SETUP request. + */ + if (irq_src & UDC_SETUP) { + u16 ack = irq_src & (UDC_EP0_TX|UDC_EP0_RX); + + nuke(ep0, 0); + if (ack) { + omap_writew(ack, UDC_IRQ_SRC); + irq_src = UDC_SETUP; + } + } + + /* IN/OUT packets mean we're in the DATA or STATUS stage. + * This driver uses only uses protocol stalls (ep0 never halts), + * and if we got this far the gadget driver already had a + * chance to stall. Tries to be forgiving of host oddities. + * + * NOTE: the last chance gadget drivers have to stall control + * requests is during their request completion callback. + */ + if (!list_empty(&ep0->queue)) + req = container_of(ep0->queue.next, struct omap_req, queue); + + /* IN == TX to host */ + if (irq_src & UDC_EP0_TX) { + int stat; + + omap_writew(UDC_EP0_TX, UDC_IRQ_SRC); + omap_writew(UDC_EP_SEL|UDC_EP_DIR, UDC_EP_NUM); + stat = omap_readw(UDC_STAT_FLG); + if (stat & UDC_ACK) { + if (udc->ep0_in) { + /* write next IN packet from response, + * or set up the status stage. + */ + if (req) + stat = write_fifo(ep0, req); + omap_writew(UDC_EP_DIR, UDC_EP_NUM); + if (!req && udc->ep0_pending) { + omap_writew(UDC_EP_SEL, UDC_EP_NUM); + omap_writew(UDC_CLR_EP, UDC_CTRL); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + omap_writew(0, UDC_EP_NUM); + udc->ep0_pending = 0; + } /* else: 6 wait states before it'll tx */ + } else { + /* ack status stage of OUT transfer */ + omap_writew(UDC_EP_DIR, UDC_EP_NUM); + if (req) + done(ep0, req, 0); + } + req = NULL; + } else if (stat & UDC_STALL) { + omap_writew(UDC_CLR_HALT, UDC_CTRL); + omap_writew(UDC_EP_DIR, UDC_EP_NUM); + } else { + omap_writew(UDC_EP_DIR, UDC_EP_NUM); + } + } + + /* OUT == RX from host */ + if (irq_src & UDC_EP0_RX) { + int stat; + + omap_writew(UDC_EP0_RX, UDC_IRQ_SRC); + omap_writew(UDC_EP_SEL, UDC_EP_NUM); + stat = omap_readw(UDC_STAT_FLG); + if (stat & UDC_ACK) { + if (!udc->ep0_in) { + stat = 0; + /* read next OUT packet of request, maybe + * reactiviting the fifo; stall on errors. + */ + stat = read_fifo(ep0, req); + if (!req || stat < 0) { + omap_writew(UDC_STALL_CMD, UDC_SYSCON2); + udc->ep0_pending = 0; + stat = 0; + } else if (stat == 0) + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + omap_writew(0, UDC_EP_NUM); + + /* activate status stage */ + if (stat == 1) { + done(ep0, req, 0); + /* that may have STALLed ep0... */ + omap_writew(UDC_EP_SEL | UDC_EP_DIR, + UDC_EP_NUM); + omap_writew(UDC_CLR_EP, UDC_CTRL); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + omap_writew(UDC_EP_DIR, UDC_EP_NUM); + udc->ep0_pending = 0; + } + } else { + /* ack status stage of IN transfer */ + omap_writew(0, UDC_EP_NUM); + if (req) + done(ep0, req, 0); + } + } else if (stat & UDC_STALL) { + omap_writew(UDC_CLR_HALT, UDC_CTRL); + omap_writew(0, UDC_EP_NUM); + } else { + omap_writew(0, UDC_EP_NUM); + } + } + + /* SETUP starts all control transfers */ + if (irq_src & UDC_SETUP) { + union u { + u16 word[4]; + struct usb_ctrlrequest r; + } u; + int status = -EINVAL; + struct omap_ep *ep; + + /* read the (latest) SETUP message */ + do { + omap_writew(UDC_SETUP_SEL, UDC_EP_NUM); + /* two bytes at a time */ + u.word[0] = omap_readw(UDC_DATA); + u.word[1] = omap_readw(UDC_DATA); + u.word[2] = omap_readw(UDC_DATA); + u.word[3] = omap_readw(UDC_DATA); + omap_writew(0, UDC_EP_NUM); + } while (omap_readw(UDC_IRQ_SRC) & UDC_SETUP); + +#define w_value le16_to_cpu(u.r.wValue) +#define w_index le16_to_cpu(u.r.wIndex) +#define w_length le16_to_cpu(u.r.wLength) + + /* Delegate almost all control requests to the gadget driver, + * except for a handful of ch9 status/feature requests that + * hardware doesn't autodecode _and_ the gadget API hides. + */ + udc->ep0_in = (u.r.bRequestType & USB_DIR_IN) != 0; + udc->ep0_set_config = 0; + udc->ep0_pending = 1; + ep0->stopped = 0; + ep0->ackwait = 0; + switch (u.r.bRequest) { + case USB_REQ_SET_CONFIGURATION: + /* udc needs to know when ep != 0 is valid */ + if (u.r.bRequestType != USB_RECIP_DEVICE) + goto delegate; + if (w_length != 0) + goto do_stall; + udc->ep0_set_config = 1; + udc->ep0_reset_config = (w_value == 0); + VDBG("set config %d\n", w_value); + + /* update udc NOW since gadget driver may start + * queueing requests immediately; clear config + * later if it fails the request. + */ + if (udc->ep0_reset_config) + omap_writew(UDC_CLR_CFG, UDC_SYSCON2); + else + omap_writew(UDC_DEV_CFG, UDC_SYSCON2); + update_otg(udc); + goto delegate; + case USB_REQ_CLEAR_FEATURE: + /* clear endpoint halt */ + if (u.r.bRequestType != USB_RECIP_ENDPOINT) + goto delegate; + if (w_value != USB_ENDPOINT_HALT + || w_length != 0) + goto do_stall; + ep = &udc->ep[w_index & 0xf]; + if (ep != ep0) { + if (w_index & USB_DIR_IN) + ep += 16; + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + || !ep->ep.desc) + goto do_stall; + use_ep(ep, 0); + omap_writew(udc->clr_halt, UDC_CTRL); + ep->ackwait = 0; + if (!(ep->bEndpointAddress & USB_DIR_IN)) { + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + ep->ackwait = 1 + ep->double_buf; + } + /* NOTE: assumes the host behaves sanely, + * only clearing real halts. Else we may + * need to kill pending transfers and then + * restart the queue... very messy for DMA! + */ + } + VDBG("%s halt cleared by host\n", ep->name); + goto ep0out_status_stage; + case USB_REQ_SET_FEATURE: + /* set endpoint halt */ + if (u.r.bRequestType != USB_RECIP_ENDPOINT) + goto delegate; + if (w_value != USB_ENDPOINT_HALT + || w_length != 0) + goto do_stall; + ep = &udc->ep[w_index & 0xf]; + if (w_index & USB_DIR_IN) + ep += 16; + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + || ep == ep0 || !ep->ep.desc) + goto do_stall; + if (use_dma && ep->has_dma) { + /* this has rude side-effects (aborts) and + * can't really work if DMA-IN is active + */ + DBG("%s host set_halt, NYET\n", ep->name); + goto do_stall; + } + use_ep(ep, 0); + /* can't halt if fifo isn't empty... */ + omap_writew(UDC_CLR_EP, UDC_CTRL); + omap_writew(UDC_SET_HALT, UDC_CTRL); + VDBG("%s halted by host\n", ep->name); +ep0out_status_stage: + status = 0; + omap_writew(UDC_EP_SEL|UDC_EP_DIR, UDC_EP_NUM); + omap_writew(UDC_CLR_EP, UDC_CTRL); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + omap_writew(UDC_EP_DIR, UDC_EP_NUM); + udc->ep0_pending = 0; + break; + case USB_REQ_GET_STATUS: + /* USB_ENDPOINT_HALT status? */ + if (u.r.bRequestType != (USB_DIR_IN|USB_RECIP_ENDPOINT)) + goto intf_status; + + /* ep0 never stalls */ + if (!(w_index & 0xf)) + goto zero_status; + + /* only active endpoints count */ + ep = &udc->ep[w_index & 0xf]; + if (w_index & USB_DIR_IN) + ep += 16; + if (!ep->ep.desc) + goto do_stall; + + /* iso never stalls */ + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) + goto zero_status; + + /* FIXME don't assume non-halted endpoints!! */ + ERR("%s status, can't report\n", ep->ep.name); + goto do_stall; + +intf_status: + /* return interface status. if we were pedantic, + * we'd detect non-existent interfaces, and stall. + */ + if (u.r.bRequestType + != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto delegate; + +zero_status: + /* return two zero bytes */ + omap_writew(UDC_EP_SEL|UDC_EP_DIR, UDC_EP_NUM); + omap_writew(0, UDC_DATA); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + omap_writew(UDC_EP_DIR, UDC_EP_NUM); + status = 0; + VDBG("GET_STATUS, interface %d\n", w_index); + /* next, status stage */ + break; + default: +delegate: + /* activate the ep0out fifo right away */ + if (!udc->ep0_in && w_length) { + omap_writew(0, UDC_EP_NUM); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + } + + /* gadget drivers see class/vendor specific requests, + * {SET,GET}_{INTERFACE,DESCRIPTOR,CONFIGURATION}, + * and more + */ + VDBG("SETUP %02x.%02x v%04x i%04x l%04x\n", + u.r.bRequestType, u.r.bRequest, + w_value, w_index, w_length); + +#undef w_value +#undef w_index +#undef w_length + + /* The gadget driver may return an error here, + * causing an immediate protocol stall. + * + * Else it must issue a response, either queueing a + * response buffer for the DATA stage, or halting ep0 + * (causing a protocol stall, not a real halt). A + * zero length buffer means no DATA stage. + * + * It's fine to issue that response after the setup() + * call returns, and this IRQ was handled. + */ + udc->ep0_setup = 1; + spin_unlock(&udc->lock); + status = udc->driver->setup(&udc->gadget, &u.r); + spin_lock(&udc->lock); + udc->ep0_setup = 0; + } + + if (status < 0) { +do_stall: + VDBG("req %02x.%02x protocol STALL; stat %d\n", + u.r.bRequestType, u.r.bRequest, status); + if (udc->ep0_set_config) { + if (udc->ep0_reset_config) + WARNING("error resetting config?\n"); + else + omap_writew(UDC_CLR_CFG, UDC_SYSCON2); + } + omap_writew(UDC_STALL_CMD, UDC_SYSCON2); + udc->ep0_pending = 0; + } + } +} + +/*-------------------------------------------------------------------------*/ + +#define OTG_FLAGS (UDC_B_HNP_ENABLE|UDC_A_HNP_SUPPORT|UDC_A_ALT_HNP_SUPPORT) + +static void devstate_irq(struct omap_udc *udc, u16 irq_src) +{ + u16 devstat, change; + + devstat = omap_readw(UDC_DEVSTAT); + change = devstat ^ udc->devstat; + udc->devstat = devstat; + + if (change & (UDC_USB_RESET|UDC_ATT)) { + udc_quiesce(udc); + + if (change & UDC_ATT) { + /* driver for any external transceiver will + * have called omap_vbus_session() already + */ + if (devstat & UDC_ATT) { + udc->gadget.speed = USB_SPEED_FULL; + VDBG("connect\n"); + if (IS_ERR_OR_NULL(udc->transceiver)) + pullup_enable(udc); + /* if (driver->connect) call it */ + } else if (udc->gadget.speed != USB_SPEED_UNKNOWN) { + udc->gadget.speed = USB_SPEED_UNKNOWN; + if (IS_ERR_OR_NULL(udc->transceiver)) + pullup_disable(udc); + DBG("disconnect, gadget %s\n", + udc->driver->driver.name); + if (udc->driver->disconnect) { + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + } + change &= ~UDC_ATT; + } + + if (change & UDC_USB_RESET) { + if (devstat & UDC_USB_RESET) { + VDBG("RESET=1\n"); + } else { + udc->gadget.speed = USB_SPEED_FULL; + INFO("USB reset done, gadget %s\n", + udc->driver->driver.name); + /* ep0 traffic is legal from now on */ + omap_writew(UDC_DS_CHG_IE | UDC_EP0_IE, + UDC_IRQ_EN); + } + change &= ~UDC_USB_RESET; + } + } + if (change & UDC_SUS) { + if (udc->gadget.speed != USB_SPEED_UNKNOWN) { + /* FIXME tell isp1301 to suspend/resume (?) */ + if (devstat & UDC_SUS) { + VDBG("suspend\n"); + update_otg(udc); + /* HNP could be under way already */ + if (udc->gadget.speed == USB_SPEED_FULL + && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + if (!IS_ERR_OR_NULL(udc->transceiver)) + usb_phy_set_suspend( + udc->transceiver, 1); + } else { + VDBG("resume\n"); + if (!IS_ERR_OR_NULL(udc->transceiver)) + usb_phy_set_suspend( + udc->transceiver, 0); + if (udc->gadget.speed == USB_SPEED_FULL + && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + } + } + change &= ~UDC_SUS; + } + if (!cpu_is_omap15xx() && (change & OTG_FLAGS)) { + update_otg(udc); + change &= ~OTG_FLAGS; + } + + change &= ~(UDC_CFG|UDC_DEF|UDC_ADD); + if (change) + VDBG("devstat %03x, ignore change %03x\n", + devstat, change); + + omap_writew(UDC_DS_CHG, UDC_IRQ_SRC); +} + +static irqreturn_t omap_udc_irq(int irq, void *_udc) +{ + struct omap_udc *udc = _udc; + u16 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + irq_src = omap_readw(UDC_IRQ_SRC); + + /* Device state change (usb ch9 stuff) */ + if (irq_src & UDC_DS_CHG) { + devstate_irq(_udc, irq_src); + status = IRQ_HANDLED; + irq_src &= ~UDC_DS_CHG; + } + + /* EP0 control transfers */ + if (irq_src & (UDC_EP0_RX|UDC_SETUP|UDC_EP0_TX)) { + ep0_irq(_udc, irq_src); + status = IRQ_HANDLED; + irq_src &= ~(UDC_EP0_RX|UDC_SETUP|UDC_EP0_TX); + } + + /* DMA transfer completion */ + if (use_dma && (irq_src & (UDC_TXN_DONE|UDC_RXN_CNT|UDC_RXN_EOT))) { + dma_irq(_udc, irq_src); + status = IRQ_HANDLED; + irq_src &= ~(UDC_TXN_DONE|UDC_RXN_CNT|UDC_RXN_EOT); + } + + irq_src &= ~(UDC_IRQ_SOF | UDC_EPN_TX|UDC_EPN_RX); + if (irq_src) + DBG("udc_irq, unhandled %03x\n", irq_src); + spin_unlock_irqrestore(&udc->lock, flags); + + return status; +} + +/* workaround for seemingly-lost IRQs for RX ACKs... */ +#define PIO_OUT_TIMEOUT (jiffies + HZ/3) +#define HALF_FULL(f) (!((f)&(UDC_NON_ISO_FIFO_FULL|UDC_NON_ISO_FIFO_EMPTY))) + +static void pio_out_timer(unsigned long _ep) +{ + struct omap_ep *ep = (void *) _ep; + unsigned long flags; + u16 stat_flg; + + spin_lock_irqsave(&ep->udc->lock, flags); + if (!list_empty(&ep->queue) && ep->ackwait) { + use_ep(ep, UDC_EP_SEL); + stat_flg = omap_readw(UDC_STAT_FLG); + + if ((stat_flg & UDC_ACK) && (!(stat_flg & UDC_FIFO_EN) + || (ep->double_buf && HALF_FULL(stat_flg)))) { + struct omap_req *req; + + VDBG("%s: lose, %04x\n", ep->ep.name, stat_flg); + req = container_of(ep->queue.next, + struct omap_req, queue); + (void) read_fifo(ep, req); + omap_writew(ep->bEndpointAddress, UDC_EP_NUM); + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + ep->ackwait = 1 + ep->double_buf; + } else + deselect_ep(); + } + mod_timer(&ep->timer, PIO_OUT_TIMEOUT); + spin_unlock_irqrestore(&ep->udc->lock, flags); +} + +static irqreturn_t omap_udc_pio_irq(int irq, void *_dev) +{ + u16 epn_stat, irq_src; + irqreturn_t status = IRQ_NONE; + struct omap_ep *ep; + int epnum; + struct omap_udc *udc = _dev; + struct omap_req *req; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + epn_stat = omap_readw(UDC_EPN_STAT); + irq_src = omap_readw(UDC_IRQ_SRC); + + /* handle OUT first, to avoid some wasteful NAKs */ + if (irq_src & UDC_EPN_RX) { + epnum = (epn_stat >> 8) & 0x0f; + omap_writew(UDC_EPN_RX, UDC_IRQ_SRC); + status = IRQ_HANDLED; + ep = &udc->ep[epnum]; + ep->irqs++; + + omap_writew(epnum | UDC_EP_SEL, UDC_EP_NUM); + ep->fnf = 0; + if (omap_readw(UDC_STAT_FLG) & UDC_ACK) { + ep->ackwait--; + if (!list_empty(&ep->queue)) { + int stat; + req = container_of(ep->queue.next, + struct omap_req, queue); + stat = read_fifo(ep, req); + if (!ep->double_buf) + ep->fnf = 1; + } + } + /* min 6 clock delay before clearing EP_SEL ... */ + epn_stat = omap_readw(UDC_EPN_STAT); + epn_stat = omap_readw(UDC_EPN_STAT); + omap_writew(epnum, UDC_EP_NUM); + + /* enabling fifo _after_ clearing ACK, contrary to docs, + * reduces lossage; timer still needed though (sigh). + */ + if (ep->fnf) { + omap_writew(UDC_SET_FIFO_EN, UDC_CTRL); + ep->ackwait = 1 + ep->double_buf; + } + mod_timer(&ep->timer, PIO_OUT_TIMEOUT); + } + + /* then IN transfers */ + else if (irq_src & UDC_EPN_TX) { + epnum = epn_stat & 0x0f; + omap_writew(UDC_EPN_TX, UDC_IRQ_SRC); + status = IRQ_HANDLED; + ep = &udc->ep[16 + epnum]; + ep->irqs++; + + omap_writew(epnum | UDC_EP_DIR | UDC_EP_SEL, UDC_EP_NUM); + if (omap_readw(UDC_STAT_FLG) & UDC_ACK) { + ep->ackwait = 0; + if (!list_empty(&ep->queue)) { + req = container_of(ep->queue.next, + struct omap_req, queue); + (void) write_fifo(ep, req); + } + } + /* min 6 clock delay before clearing EP_SEL ... */ + epn_stat = omap_readw(UDC_EPN_STAT); + epn_stat = omap_readw(UDC_EPN_STAT); + omap_writew(epnum | UDC_EP_DIR, UDC_EP_NUM); + /* then 6 clocks before it'd tx */ + } + + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +#ifdef USE_ISO +static irqreturn_t omap_udc_iso_irq(int irq, void *_dev) +{ + struct omap_udc *udc = _dev; + struct omap_ep *ep; + int pending = 0; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* handle all non-DMA ISO transfers */ + list_for_each_entry(ep, &udc->iso, iso) { + u16 stat; + struct omap_req *req; + + if (ep->has_dma || list_empty(&ep->queue)) + continue; + req = list_entry(ep->queue.next, struct omap_req, queue); + + use_ep(ep, UDC_EP_SEL); + stat = omap_readw(UDC_STAT_FLG); + + /* NOTE: like the other controller drivers, this isn't + * currently reporting lost or damaged frames. + */ + if (ep->bEndpointAddress & USB_DIR_IN) { + if (stat & UDC_MISS_IN) + /* done(ep, req, -EPROTO) */; + else + write_fifo(ep, req); + } else { + int status = 0; + + if (stat & UDC_NO_RXPACKET) + status = -EREMOTEIO; + else if (stat & UDC_ISO_ERR) + status = -EILSEQ; + else if (stat & UDC_DATA_FLUSH) + status = -ENOSR; + + if (status) + /* done(ep, req, status) */; + else + read_fifo(ep, req); + } + deselect_ep(); + /* 6 wait states before next EP */ + + ep->irqs++; + if (!list_empty(&ep->queue)) + pending = 1; + } + if (!pending) { + u16 w; + + w = omap_readw(UDC_IRQ_EN); + w &= ~UDC_SOF_IE; + omap_writew(w, UDC_IRQ_EN); + } + omap_writew(UDC_IRQ_SOF, UDC_IRQ_SRC); + + spin_unlock_irqrestore(&udc->lock, flags); + return IRQ_HANDLED; +} +#endif + +/*-------------------------------------------------------------------------*/ + +static inline int machine_without_vbus_sense(void) +{ + return machine_is_omap_innovator() + || machine_is_omap_osk() + || machine_is_sx1() + /* No known omap7xx boards with vbus sense */ + || cpu_is_omap7xx(); +} + +static int omap_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + int status = -ENODEV; + struct omap_ep *ep; + unsigned long flags; + + + spin_lock_irqsave(&udc->lock, flags); + /* reset state */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + ep->irqs = 0; + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) + continue; + use_ep(ep, 0); + omap_writew(UDC_SET_HALT, UDC_CTRL); + } + udc->ep0_pending = 0; + udc->ep[0].irqs = 0; + udc->softconnect = 1; + + /* hook up the driver */ + driver->driver.bus = NULL; + udc->driver = driver; + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->dc_clk != NULL) + omap_udc_enable_clock(1); + + omap_writew(UDC_IRQ_SRC_MASK, UDC_IRQ_SRC); + + /* connect to bus through transceiver */ + if (!IS_ERR_OR_NULL(udc->transceiver)) { + status = otg_set_peripheral(udc->transceiver->otg, + &udc->gadget); + if (status < 0) { + ERR("can't bind to transceiver\n"); + if (driver->unbind) { + driver->unbind(&udc->gadget); + udc->driver = NULL; + } + goto done; + } + } else { + if (can_pullup(udc)) + pullup_enable(udc); + else + pullup_disable(udc); + } + + /* boards that don't have VBUS sensing can't autogate 48MHz; + * can't enter deep sleep while a gadget driver is active. + */ + if (machine_without_vbus_sense()) + omap_vbus_session(&udc->gadget, 1); + +done: + if (udc->dc_clk != NULL) + omap_udc_enable_clock(0); + + return status; +} + +static int omap_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + unsigned long flags; + int status = -ENODEV; + + if (udc->dc_clk != NULL) + omap_udc_enable_clock(1); + + if (machine_without_vbus_sense()) + omap_vbus_session(&udc->gadget, 0); + + if (!IS_ERR_OR_NULL(udc->transceiver)) + (void) otg_set_peripheral(udc->transceiver->otg, NULL); + else + pullup_disable(udc); + + spin_lock_irqsave(&udc->lock, flags); + udc_quiesce(udc); + spin_unlock_irqrestore(&udc->lock, flags); + + udc->driver = NULL; + + if (udc->dc_clk != NULL) + omap_udc_enable_clock(0); + + return status; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#include + +static const char proc_filename[] = "driver/udc"; + +#define FOURBITS "%s%s%s%s" +#define EIGHTBITS "%s%s%s%s%s%s%s%s" + +static void proc_ep_show(struct seq_file *s, struct omap_ep *ep) +{ + u16 stat_flg; + struct omap_req *req; + char buf[20]; + + use_ep(ep, 0); + + if (use_dma && ep->has_dma) + snprintf(buf, sizeof buf, "(%cxdma%d lch%d) ", + (ep->bEndpointAddress & USB_DIR_IN) ? 't' : 'r', + ep->dma_channel - 1, ep->lch); + else + buf[0] = 0; + + stat_flg = omap_readw(UDC_STAT_FLG); + seq_printf(s, + "\n%s %s%s%sirqs %ld stat %04x " EIGHTBITS FOURBITS "%s\n", + ep->name, buf, + ep->double_buf ? "dbuf " : "", + ({ char *s; + switch (ep->ackwait) { + case 0: + s = ""; + break; + case 1: + s = "(ackw) "; + break; + case 2: + s = "(ackw2) "; + break; + default: + s = "(?) "; + break; + } s; }), + ep->irqs, stat_flg, + (stat_flg & UDC_NO_RXPACKET) ? "no_rxpacket " : "", + (stat_flg & UDC_MISS_IN) ? "miss_in " : "", + (stat_flg & UDC_DATA_FLUSH) ? "data_flush " : "", + (stat_flg & UDC_ISO_ERR) ? "iso_err " : "", + (stat_flg & UDC_ISO_FIFO_EMPTY) ? "iso_fifo_empty " : "", + (stat_flg & UDC_ISO_FIFO_FULL) ? "iso_fifo_full " : "", + (stat_flg & UDC_EP_HALTED) ? "HALT " : "", + (stat_flg & UDC_STALL) ? "STALL " : "", + (stat_flg & UDC_NAK) ? "NAK " : "", + (stat_flg & UDC_ACK) ? "ACK " : "", + (stat_flg & UDC_FIFO_EN) ? "fifo_en " : "", + (stat_flg & UDC_NON_ISO_FIFO_EMPTY) ? "fifo_empty " : "", + (stat_flg & UDC_NON_ISO_FIFO_FULL) ? "fifo_full " : ""); + + if (list_empty(&ep->queue)) + seq_printf(s, "\t(queue empty)\n"); + else + list_for_each_entry(req, &ep->queue, queue) { + unsigned length = req->req.actual; + + if (use_dma && buf[0]) { + length += ((ep->bEndpointAddress & USB_DIR_IN) + ? dma_src_len : dma_dest_len) + (ep, req->req.dma + length); + buf[0] = 0; + } + seq_printf(s, "\treq %p len %d/%d buf %p\n", + &req->req, length, + req->req.length, req->req.buf); + } +} + +static char *trx_mode(unsigned m, int enabled) +{ + switch (m) { + case 0: + return enabled ? "*6wire" : "unused"; + case 1: + return "4wire"; + case 2: + return "3wire"; + case 3: + return "6wire"; + default: + return "unknown"; + } +} + +static int proc_otg_show(struct seq_file *s) +{ + u32 tmp; + u32 trans = 0; + char *ctrl_name = "(UNKNOWN)"; + + tmp = omap_readl(OTG_REV); + ctrl_name = "tranceiver_ctrl"; + trans = omap_readw(USB_TRANSCEIVER_CTRL); + seq_printf(s, "\nOTG rev %d.%d, %s %05x\n", + tmp >> 4, tmp & 0xf, ctrl_name, trans); + tmp = omap_readw(OTG_SYSCON_1); + seq_printf(s, "otg_syscon1 %08x usb2 %s, usb1 %s, usb0 %s," + FOURBITS "\n", tmp, + trx_mode(USB2_TRX_MODE(tmp), trans & CONF_USB2_UNI_R), + trx_mode(USB1_TRX_MODE(tmp), trans & CONF_USB1_UNI_R), + (USB0_TRX_MODE(tmp) == 0 && !cpu_is_omap1710()) + ? "internal" + : trx_mode(USB0_TRX_MODE(tmp), 1), + (tmp & OTG_IDLE_EN) ? " !otg" : "", + (tmp & HST_IDLE_EN) ? " !host" : "", + (tmp & DEV_IDLE_EN) ? " !dev" : "", + (tmp & OTG_RESET_DONE) ? " reset_done" : " reset_active"); + tmp = omap_readl(OTG_SYSCON_2); + seq_printf(s, "otg_syscon2 %08x%s" EIGHTBITS + " b_ase_brst=%d hmc=%d\n", tmp, + (tmp & OTG_EN) ? " otg_en" : "", + (tmp & USBX_SYNCHRO) ? " synchro" : "", + /* much more SRP stuff */ + (tmp & SRP_DATA) ? " srp_data" : "", + (tmp & SRP_VBUS) ? " srp_vbus" : "", + (tmp & OTG_PADEN) ? " otg_paden" : "", + (tmp & HMC_PADEN) ? " hmc_paden" : "", + (tmp & UHOST_EN) ? " uhost_en" : "", + (tmp & HMC_TLLSPEED) ? " tllspeed" : "", + (tmp & HMC_TLLATTACH) ? " tllattach" : "", + B_ASE_BRST(tmp), + OTG_HMC(tmp)); + tmp = omap_readl(OTG_CTRL); + seq_printf(s, "otg_ctrl %06x" EIGHTBITS EIGHTBITS "%s\n", tmp, + (tmp & OTG_ASESSVLD) ? " asess" : "", + (tmp & OTG_BSESSEND) ? " bsess_end" : "", + (tmp & OTG_BSESSVLD) ? " bsess" : "", + (tmp & OTG_VBUSVLD) ? " vbus" : "", + (tmp & OTG_ID) ? " id" : "", + (tmp & OTG_DRIVER_SEL) ? " DEVICE" : " HOST", + (tmp & OTG_A_SETB_HNPEN) ? " a_setb_hnpen" : "", + (tmp & OTG_A_BUSREQ) ? " a_bus" : "", + (tmp & OTG_B_HNPEN) ? " b_hnpen" : "", + (tmp & OTG_B_BUSREQ) ? " b_bus" : "", + (tmp & OTG_BUSDROP) ? " busdrop" : "", + (tmp & OTG_PULLDOWN) ? " down" : "", + (tmp & OTG_PULLUP) ? " up" : "", + (tmp & OTG_DRV_VBUS) ? " drv" : "", + (tmp & OTG_PD_VBUS) ? " pd_vb" : "", + (tmp & OTG_PU_VBUS) ? " pu_vb" : "", + (tmp & OTG_PU_ID) ? " pu_id" : "" + ); + tmp = omap_readw(OTG_IRQ_EN); + seq_printf(s, "otg_irq_en %04x" "\n", tmp); + tmp = omap_readw(OTG_IRQ_SRC); + seq_printf(s, "otg_irq_src %04x" "\n", tmp); + tmp = omap_readw(OTG_OUTCTRL); + seq_printf(s, "otg_outctrl %04x" "\n", tmp); + tmp = omap_readw(OTG_TEST); + seq_printf(s, "otg_test %04x" "\n", tmp); + return 0; +} + +static int proc_udc_show(struct seq_file *s, void *_) +{ + u32 tmp; + struct omap_ep *ep; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + seq_printf(s, "%s, version: " DRIVER_VERSION +#ifdef USE_ISO + " (iso)" +#endif + "%s\n", + driver_desc, + use_dma ? " (dma)" : ""); + + tmp = omap_readw(UDC_REV) & 0xff; + seq_printf(s, + "UDC rev %d.%d, fifo mode %d, gadget %s\n" + "hmc %d, transceiver %s\n", + tmp >> 4, tmp & 0xf, + fifo_mode, + udc->driver ? udc->driver->driver.name : "(none)", + HMC, + udc->transceiver + ? udc->transceiver->label + : (cpu_is_omap1710() + ? "external" : "(none)")); + seq_printf(s, "ULPD control %04x req %04x status %04x\n", + omap_readw(ULPD_CLOCK_CTRL), + omap_readw(ULPD_SOFT_REQ), + omap_readw(ULPD_STATUS_REQ)); + + /* OTG controller registers */ + if (!cpu_is_omap15xx()) + proc_otg_show(s); + + tmp = omap_readw(UDC_SYSCON1); + seq_printf(s, "\nsyscon1 %04x" EIGHTBITS "\n", tmp, + (tmp & UDC_CFG_LOCK) ? " cfg_lock" : "", + (tmp & UDC_DATA_ENDIAN) ? " data_endian" : "", + (tmp & UDC_DMA_ENDIAN) ? " dma_endian" : "", + (tmp & UDC_NAK_EN) ? " nak" : "", + (tmp & UDC_AUTODECODE_DIS) ? " autodecode_dis" : "", + (tmp & UDC_SELF_PWR) ? " self_pwr" : "", + (tmp & UDC_SOFF_DIS) ? " soff_dis" : "", + (tmp & UDC_PULLUP_EN) ? " PULLUP" : ""); + /* syscon2 is write-only */ + + /* UDC controller registers */ + if (!(tmp & UDC_PULLUP_EN)) { + seq_printf(s, "(suspended)\n"); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; + } + + tmp = omap_readw(UDC_DEVSTAT); + seq_printf(s, "devstat %04x" EIGHTBITS "%s%s\n", tmp, + (tmp & UDC_B_HNP_ENABLE) ? " b_hnp" : "", + (tmp & UDC_A_HNP_SUPPORT) ? " a_hnp" : "", + (tmp & UDC_A_ALT_HNP_SUPPORT) ? " a_alt_hnp" : "", + (tmp & UDC_R_WK_OK) ? " r_wk_ok" : "", + (tmp & UDC_USB_RESET) ? " usb_reset" : "", + (tmp & UDC_SUS) ? " SUS" : "", + (tmp & UDC_CFG) ? " CFG" : "", + (tmp & UDC_ADD) ? " ADD" : "", + (tmp & UDC_DEF) ? " DEF" : "", + (tmp & UDC_ATT) ? " ATT" : ""); + seq_printf(s, "sof %04x\n", omap_readw(UDC_SOF)); + tmp = omap_readw(UDC_IRQ_EN); + seq_printf(s, "irq_en %04x" FOURBITS "%s\n", tmp, + (tmp & UDC_SOF_IE) ? " sof" : "", + (tmp & UDC_EPN_RX_IE) ? " epn_rx" : "", + (tmp & UDC_EPN_TX_IE) ? " epn_tx" : "", + (tmp & UDC_DS_CHG_IE) ? " ds_chg" : "", + (tmp & UDC_EP0_IE) ? " ep0" : ""); + tmp = omap_readw(UDC_IRQ_SRC); + seq_printf(s, "irq_src %04x" EIGHTBITS "%s%s\n", tmp, + (tmp & UDC_TXN_DONE) ? " txn_done" : "", + (tmp & UDC_RXN_CNT) ? " rxn_cnt" : "", + (tmp & UDC_RXN_EOT) ? " rxn_eot" : "", + (tmp & UDC_IRQ_SOF) ? " sof" : "", + (tmp & UDC_EPN_RX) ? " epn_rx" : "", + (tmp & UDC_EPN_TX) ? " epn_tx" : "", + (tmp & UDC_DS_CHG) ? " ds_chg" : "", + (tmp & UDC_SETUP) ? " setup" : "", + (tmp & UDC_EP0_RX) ? " ep0out" : "", + (tmp & UDC_EP0_TX) ? " ep0in" : ""); + if (use_dma) { + unsigned i; + + tmp = omap_readw(UDC_DMA_IRQ_EN); + seq_printf(s, "dma_irq_en %04x%s" EIGHTBITS "\n", tmp, + (tmp & UDC_TX_DONE_IE(3)) ? " tx2_done" : "", + (tmp & UDC_RX_CNT_IE(3)) ? " rx2_cnt" : "", + (tmp & UDC_RX_EOT_IE(3)) ? " rx2_eot" : "", + + (tmp & UDC_TX_DONE_IE(2)) ? " tx1_done" : "", + (tmp & UDC_RX_CNT_IE(2)) ? " rx1_cnt" : "", + (tmp & UDC_RX_EOT_IE(2)) ? " rx1_eot" : "", + + (tmp & UDC_TX_DONE_IE(1)) ? " tx0_done" : "", + (tmp & UDC_RX_CNT_IE(1)) ? " rx0_cnt" : "", + (tmp & UDC_RX_EOT_IE(1)) ? " rx0_eot" : ""); + + tmp = omap_readw(UDC_RXDMA_CFG); + seq_printf(s, "rxdma_cfg %04x\n", tmp); + if (tmp) { + for (i = 0; i < 3; i++) { + if ((tmp & (0x0f << (i * 4))) == 0) + continue; + seq_printf(s, "rxdma[%d] %04x\n", i, + omap_readw(UDC_RXDMA(i + 1))); + } + } + tmp = omap_readw(UDC_TXDMA_CFG); + seq_printf(s, "txdma_cfg %04x\n", tmp); + if (tmp) { + for (i = 0; i < 3; i++) { + if (!(tmp & (0x0f << (i * 4)))) + continue; + seq_printf(s, "txdma[%d] %04x\n", i, + omap_readw(UDC_TXDMA(i + 1))); + } + } + } + + tmp = omap_readw(UDC_DEVSTAT); + if (tmp & UDC_ATT) { + proc_ep_show(s, &udc->ep[0]); + if (tmp & UDC_ADD) { + list_for_each_entry(ep, &udc->gadget.ep_list, + ep.ep_list) { + if (ep->ep.desc) + proc_ep_show(s, ep); + } + } + } + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int proc_udc_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_udc_show, NULL); +} + +static const struct file_operations proc_ops = { + .owner = THIS_MODULE, + .open = proc_udc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void create_proc_file(void) +{ + proc_create(proc_filename, 0, NULL, &proc_ops); +} + +static void remove_proc_file(void) +{ + remove_proc_entry(proc_filename, NULL); +} + +#else + +static inline void create_proc_file(void) {} +static inline void remove_proc_file(void) {} + +#endif + +/*-------------------------------------------------------------------------*/ + +/* Before this controller can enumerate, we need to pick an endpoint + * configuration, or "fifo_mode" That involves allocating 2KB of packet + * buffer space among the endpoints we'll be operating. + * + * NOTE: as of OMAP 1710 ES2.0, writing a new endpoint config when + * UDC_SYSCON_1.CFG_LOCK is set can now work. We won't use that + * capability yet though. + */ +static unsigned +omap_ep_setup(char *name, u8 addr, u8 type, + unsigned buf, unsigned maxp, int dbuf) +{ + struct omap_ep *ep; + u16 epn_rxtx = 0; + + /* OUT endpoints first, then IN */ + ep = &udc->ep[addr & 0xf]; + if (addr & USB_DIR_IN) + ep += 16; + + /* in case of ep init table bugs */ + BUG_ON(ep->name[0]); + + /* chip setup ... bit values are same for IN, OUT */ + if (type == USB_ENDPOINT_XFER_ISOC) { + switch (maxp) { + case 8: + epn_rxtx = 0 << 12; + break; + case 16: + epn_rxtx = 1 << 12; + break; + case 32: + epn_rxtx = 2 << 12; + break; + case 64: + epn_rxtx = 3 << 12; + break; + case 128: + epn_rxtx = 4 << 12; + break; + case 256: + epn_rxtx = 5 << 12; + break; + case 512: + epn_rxtx = 6 << 12; + break; + default: + BUG(); + } + epn_rxtx |= UDC_EPN_RX_ISO; + dbuf = 1; + } else { + /* double-buffering "not supported" on 15xx, + * and ignored for PIO-IN on newer chips + * (for more reliable behavior) + */ + if (!use_dma || cpu_is_omap15xx()) + dbuf = 0; + + switch (maxp) { + case 8: + epn_rxtx = 0 << 12; + break; + case 16: + epn_rxtx = 1 << 12; + break; + case 32: + epn_rxtx = 2 << 12; + break; + case 64: + epn_rxtx = 3 << 12; + break; + default: + BUG(); + } + if (dbuf && addr) + epn_rxtx |= UDC_EPN_RX_DB; + init_timer(&ep->timer); + ep->timer.function = pio_out_timer; + ep->timer.data = (unsigned long) ep; + } + if (addr) + epn_rxtx |= UDC_EPN_RX_VALID; + BUG_ON(buf & 0x07); + epn_rxtx |= buf >> 3; + + DBG("%s addr %02x rxtx %04x maxp %d%s buf %d\n", + name, addr, epn_rxtx, maxp, dbuf ? "x2" : "", buf); + + if (addr & USB_DIR_IN) + omap_writew(epn_rxtx, UDC_EP_TX(addr & 0xf)); + else + omap_writew(epn_rxtx, UDC_EP_RX(addr)); + + /* next endpoint's buffer starts after this one's */ + buf += maxp; + if (dbuf) + buf += maxp; + BUG_ON(buf > 2048); + + /* set up driver data structures */ + BUG_ON(strlen(name) >= sizeof ep->name); + strlcpy(ep->name, name, sizeof ep->name); + INIT_LIST_HEAD(&ep->queue); + INIT_LIST_HEAD(&ep->iso); + ep->bEndpointAddress = addr; + ep->bmAttributes = type; + ep->double_buf = dbuf; + ep->udc = udc; + + ep->ep.name = ep->name; + ep->ep.ops = &omap_ep_ops; + ep->ep.maxpacket = ep->maxpacket = maxp; + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + + return buf; +} + +static void omap_udc_release(struct device *dev) +{ + complete(udc->done); + kfree(udc); + udc = NULL; +} + +static int +omap_udc_setup(struct platform_device *odev, struct usb_phy *xceiv) +{ + unsigned tmp, buf; + + /* abolish any previous hardware state */ + omap_writew(0, UDC_SYSCON1); + omap_writew(0, UDC_IRQ_EN); + omap_writew(UDC_IRQ_SRC_MASK, UDC_IRQ_SRC); + omap_writew(0, UDC_DMA_IRQ_EN); + omap_writew(0, UDC_RXDMA_CFG); + omap_writew(0, UDC_TXDMA_CFG); + + /* UDC_PULLUP_EN gates the chip clock */ + /* OTG_SYSCON_1 |= DEV_IDLE_EN; */ + + udc = kzalloc(sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + spin_lock_init(&udc->lock); + + udc->gadget.ops = &omap_gadget_ops; + udc->gadget.ep0 = &udc->ep[0].ep; + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->iso); + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_FULL; + udc->gadget.name = driver_name; + udc->transceiver = xceiv; + + /* ep0 is special; put it right after the SETUP buffer */ + buf = omap_ep_setup("ep0", 0, USB_ENDPOINT_XFER_CONTROL, + 8 /* after SETUP */, 64 /* maxpacket */, 0); + list_del_init(&udc->ep[0].ep.ep_list); + + /* initially disable all non-ep0 endpoints */ + for (tmp = 1; tmp < 15; tmp++) { + omap_writew(0, UDC_EP_RX(tmp)); + omap_writew(0, UDC_EP_TX(tmp)); + } + +#define OMAP_BULK_EP(name, addr) \ + buf = omap_ep_setup(name "-bulk", addr, \ + USB_ENDPOINT_XFER_BULK, buf, 64, 1); +#define OMAP_INT_EP(name, addr, maxp) \ + buf = omap_ep_setup(name "-int", addr, \ + USB_ENDPOINT_XFER_INT, buf, maxp, 0); +#define OMAP_ISO_EP(name, addr, maxp) \ + buf = omap_ep_setup(name "-iso", addr, \ + USB_ENDPOINT_XFER_ISOC, buf, maxp, 1); + + switch (fifo_mode) { + case 0: + OMAP_BULK_EP("ep1in", USB_DIR_IN | 1); + OMAP_BULK_EP("ep2out", USB_DIR_OUT | 2); + OMAP_INT_EP("ep3in", USB_DIR_IN | 3, 16); + break; + case 1: + OMAP_BULK_EP("ep1in", USB_DIR_IN | 1); + OMAP_BULK_EP("ep2out", USB_DIR_OUT | 2); + OMAP_INT_EP("ep9in", USB_DIR_IN | 9, 16); + + OMAP_BULK_EP("ep3in", USB_DIR_IN | 3); + OMAP_BULK_EP("ep4out", USB_DIR_OUT | 4); + OMAP_INT_EP("ep10in", USB_DIR_IN | 10, 16); + + OMAP_BULK_EP("ep5in", USB_DIR_IN | 5); + OMAP_BULK_EP("ep5out", USB_DIR_OUT | 5); + OMAP_INT_EP("ep11in", USB_DIR_IN | 11, 16); + + OMAP_BULK_EP("ep6in", USB_DIR_IN | 6); + OMAP_BULK_EP("ep6out", USB_DIR_OUT | 6); + OMAP_INT_EP("ep12in", USB_DIR_IN | 12, 16); + + OMAP_BULK_EP("ep7in", USB_DIR_IN | 7); + OMAP_BULK_EP("ep7out", USB_DIR_OUT | 7); + OMAP_INT_EP("ep13in", USB_DIR_IN | 13, 16); + OMAP_INT_EP("ep13out", USB_DIR_OUT | 13, 16); + + OMAP_BULK_EP("ep8in", USB_DIR_IN | 8); + OMAP_BULK_EP("ep8out", USB_DIR_OUT | 8); + OMAP_INT_EP("ep14in", USB_DIR_IN | 14, 16); + OMAP_INT_EP("ep14out", USB_DIR_OUT | 14, 16); + + OMAP_BULK_EP("ep15in", USB_DIR_IN | 15); + OMAP_BULK_EP("ep15out", USB_DIR_OUT | 15); + + break; + +#ifdef USE_ISO + case 2: /* mixed iso/bulk */ + OMAP_ISO_EP("ep1in", USB_DIR_IN | 1, 256); + OMAP_ISO_EP("ep2out", USB_DIR_OUT | 2, 256); + OMAP_ISO_EP("ep3in", USB_DIR_IN | 3, 128); + OMAP_ISO_EP("ep4out", USB_DIR_OUT | 4, 128); + + OMAP_INT_EP("ep5in", USB_DIR_IN | 5, 16); + + OMAP_BULK_EP("ep6in", USB_DIR_IN | 6); + OMAP_BULK_EP("ep7out", USB_DIR_OUT | 7); + OMAP_INT_EP("ep8in", USB_DIR_IN | 8, 16); + break; + case 3: /* mixed bulk/iso */ + OMAP_BULK_EP("ep1in", USB_DIR_IN | 1); + OMAP_BULK_EP("ep2out", USB_DIR_OUT | 2); + OMAP_INT_EP("ep3in", USB_DIR_IN | 3, 16); + + OMAP_BULK_EP("ep4in", USB_DIR_IN | 4); + OMAP_BULK_EP("ep5out", USB_DIR_OUT | 5); + OMAP_INT_EP("ep6in", USB_DIR_IN | 6, 16); + + OMAP_ISO_EP("ep7in", USB_DIR_IN | 7, 256); + OMAP_ISO_EP("ep8out", USB_DIR_OUT | 8, 256); + OMAP_INT_EP("ep9in", USB_DIR_IN | 9, 16); + break; +#endif + + /* add more modes as needed */ + + default: + ERR("unsupported fifo_mode #%d\n", fifo_mode); + return -ENODEV; + } + omap_writew(UDC_CFG_LOCK|UDC_SELF_PWR, UDC_SYSCON1); + INFO("fifo mode %d, %d bytes not used\n", fifo_mode, 2048 - buf); + return 0; +} + +static int omap_udc_probe(struct platform_device *pdev) +{ + int status = -ENODEV; + int hmc; + struct usb_phy *xceiv = NULL; + const char *type = NULL; + struct omap_usb_config *config = pdev->dev.platform_data; + struct clk *dc_clk = NULL; + struct clk *hhc_clk = NULL; + + if (cpu_is_omap7xx()) + use_dma = 0; + + /* NOTE: "knows" the order of the resources! */ + if (!request_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1, + driver_name)) { + DBG("request_mem_region failed\n"); + return -EBUSY; + } + + if (cpu_is_omap16xx()) { + dc_clk = clk_get(&pdev->dev, "usb_dc_ck"); + hhc_clk = clk_get(&pdev->dev, "usb_hhc_ck"); + BUG_ON(IS_ERR(dc_clk) || IS_ERR(hhc_clk)); + /* can't use omap_udc_enable_clock yet */ + clk_enable(dc_clk); + clk_enable(hhc_clk); + udelay(100); + } + + if (cpu_is_omap7xx()) { + dc_clk = clk_get(&pdev->dev, "usb_dc_ck"); + hhc_clk = clk_get(&pdev->dev, "l3_ocpi_ck"); + BUG_ON(IS_ERR(dc_clk) || IS_ERR(hhc_clk)); + /* can't use omap_udc_enable_clock yet */ + clk_enable(dc_clk); + clk_enable(hhc_clk); + udelay(100); + } + + INFO("OMAP UDC rev %d.%d%s\n", + omap_readw(UDC_REV) >> 4, omap_readw(UDC_REV) & 0xf, + config->otg ? ", Mini-AB" : ""); + + /* use the mode given to us by board init code */ + if (cpu_is_omap15xx()) { + hmc = HMC_1510; + type = "(unknown)"; + + if (machine_without_vbus_sense()) { + /* just set up software VBUS detect, and then + * later rig it so we always report VBUS. + * FIXME without really sensing VBUS, we can't + * know when to turn PULLUP_EN on/off; and that + * means we always "need" the 48MHz clock. + */ + u32 tmp = omap_readl(FUNC_MUX_CTRL_0); + tmp &= ~VBUS_CTRL_1510; + omap_writel(tmp, FUNC_MUX_CTRL_0); + tmp |= VBUS_MODE_1510; + tmp &= ~VBUS_CTRL_1510; + omap_writel(tmp, FUNC_MUX_CTRL_0); + } + } else { + /* The transceiver may package some GPIO logic or handle + * loopback and/or transceiverless setup; if we find one, + * use it. Except for OTG, we don't _need_ to talk to one; + * but not having one probably means no VBUS detection. + */ + xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (!IS_ERR_OR_NULL(xceiv)) + type = xceiv->label; + else if (config->otg) { + DBG("OTG requires external transceiver!\n"); + goto cleanup0; + } + + hmc = HMC_1610; + + switch (hmc) { + case 0: /* POWERUP DEFAULT == 0 */ + case 4: + case 12: + case 20: + if (!cpu_is_omap1710()) { + type = "integrated"; + break; + } + /* FALL THROUGH */ + case 3: + case 11: + case 16: + case 19: + case 25: + if (IS_ERR_OR_NULL(xceiv)) { + DBG("external transceiver not registered!\n"); + type = "unknown"; + } + break; + case 21: /* internal loopback */ + type = "loopback"; + break; + case 14: /* transceiverless */ + if (cpu_is_omap1710()) + goto bad_on_1710; + /* FALL THROUGH */ + case 13: + case 15: + type = "no"; + break; + + default: +bad_on_1710: + ERR("unrecognized UDC HMC mode %d\n", hmc); + goto cleanup0; + } + } + + INFO("hmc mode %d, %s transceiver\n", hmc, type); + + /* a "gadget" abstracts/virtualizes the controller */ + status = omap_udc_setup(pdev, xceiv); + if (status) + goto cleanup0; + + xceiv = NULL; + /* "udc" is now valid */ + pullup_disable(udc); +#if defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) + udc->gadget.is_otg = (config->otg != 0); +#endif + + /* starting with omap1710 es2.0, clear toggle is a separate bit */ + if (omap_readw(UDC_REV) >= 0x61) + udc->clr_halt = UDC_RESET_EP | UDC_CLRDATA_TOGGLE; + else + udc->clr_halt = UDC_RESET_EP; + + /* USB general purpose IRQ: ep0, state changes, dma, etc */ + status = request_irq(pdev->resource[1].start, omap_udc_irq, + 0, driver_name, udc); + if (status != 0) { + ERR("can't get irq %d, err %d\n", + (int) pdev->resource[1].start, status); + goto cleanup1; + } + + /* USB "non-iso" IRQ (PIO for all but ep0) */ + status = request_irq(pdev->resource[2].start, omap_udc_pio_irq, + 0, "omap_udc pio", udc); + if (status != 0) { + ERR("can't get irq %d, err %d\n", + (int) pdev->resource[2].start, status); + goto cleanup2; + } +#ifdef USE_ISO + status = request_irq(pdev->resource[3].start, omap_udc_iso_irq, + 0, "omap_udc iso", udc); + if (status != 0) { + ERR("can't get irq %d, err %d\n", + (int) pdev->resource[3].start, status); + goto cleanup3; + } +#endif + if (cpu_is_omap16xx() || cpu_is_omap7xx()) { + udc->dc_clk = dc_clk; + udc->hhc_clk = hhc_clk; + clk_disable(hhc_clk); + clk_disable(dc_clk); + } + + create_proc_file(); + status = usb_add_gadget_udc_release(&pdev->dev, &udc->gadget, + omap_udc_release); + if (status) + goto cleanup4; + + return 0; + +cleanup4: + remove_proc_file(); + +#ifdef USE_ISO +cleanup3: + free_irq(pdev->resource[2].start, udc); +#endif + +cleanup2: + free_irq(pdev->resource[1].start, udc); + +cleanup1: + kfree(udc); + udc = NULL; + +cleanup0: + if (!IS_ERR_OR_NULL(xceiv)) + usb_put_phy(xceiv); + + if (cpu_is_omap16xx() || cpu_is_omap7xx()) { + clk_disable(hhc_clk); + clk_disable(dc_clk); + clk_put(hhc_clk); + clk_put(dc_clk); + } + + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); + + return status; +} + +static int omap_udc_remove(struct platform_device *pdev) +{ + DECLARE_COMPLETION_ONSTACK(done); + + if (!udc) + return -ENODEV; + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) + return -EBUSY; + + udc->done = &done; + + pullup_disable(udc); + if (!IS_ERR_OR_NULL(udc->transceiver)) { + usb_put_phy(udc->transceiver); + udc->transceiver = NULL; + } + omap_writew(0, UDC_SYSCON1); + + remove_proc_file(); + +#ifdef USE_ISO + free_irq(pdev->resource[3].start, udc); +#endif + free_irq(pdev->resource[2].start, udc); + free_irq(pdev->resource[1].start, udc); + + if (udc->dc_clk) { + if (udc->clk_requested) + omap_udc_enable_clock(0); + clk_put(udc->hhc_clk); + clk_put(udc->dc_clk); + } + + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); + + wait_for_completion(&done); + + return 0; +} + +/* suspend/resume/wakeup from sysfs (echo > power/state) or when the + * system is forced into deep sleep + * + * REVISIT we should probably reject suspend requests when there's a host + * session active, rather than disconnecting, at least on boards that can + * report VBUS irqs (UDC_DEVSTAT.UDC_ATT). And in any case, we need to + * make host resumes and VBUS detection trigger OMAP wakeup events; that + * may involve talking to an external transceiver (e.g. isp1301). + */ + +static int omap_udc_suspend(struct platform_device *dev, pm_message_t message) +{ + u32 devstat; + + devstat = omap_readw(UDC_DEVSTAT); + + /* we're requesting 48 MHz clock if the pullup is enabled + * (== we're attached to the host) and we're not suspended, + * which would prevent entry to deep sleep... + */ + if ((devstat & UDC_ATT) != 0 && (devstat & UDC_SUS) == 0) { + WARNING("session active; suspend requires disconnect\n"); + omap_pullup(&udc->gadget, 0); + } + + return 0; +} + +static int omap_udc_resume(struct platform_device *dev) +{ + DBG("resume + wakeup/SRP\n"); + omap_pullup(&udc->gadget, 1); + + /* maybe the host would enumerate us if we nudged it */ + msleep(100); + return omap_wakeup(&udc->gadget); +} + +/*-------------------------------------------------------------------------*/ + +static struct platform_driver udc_driver = { + .probe = omap_udc_probe, + .remove = omap_udc_remove, + .suspend = omap_udc_suspend, + .resume = omap_udc_resume, + .driver = { + .owner = THIS_MODULE, + .name = (char *) driver_name, + }, +}; + +module_platform_driver(udc_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:omap_udc"); diff --git a/drivers/usb/gadget/omap_udc.h b/drivers/usb/gadget/omap_udc.h new file mode 100644 index 00000000..cfadeb5f --- /dev/null +++ b/drivers/usb/gadget/omap_udc.h @@ -0,0 +1,206 @@ +/* + * omap_udc.h -- for omap 3.2 udc, with OTG support + * + * 2004 (C) Texas Instruments, Inc. + * 2004 (C) David Brownell + */ + +/* + * USB device/endpoint management registers + */ + +#define UDC_REV (UDC_BASE + 0x0) /* Revision */ +#define UDC_EP_NUM (UDC_BASE + 0x4) /* Which endpoint */ +# define UDC_SETUP_SEL (1 << 6) +# define UDC_EP_SEL (1 << 5) +# define UDC_EP_DIR (1 << 4) + /* low 4 bits for endpoint number */ +#define UDC_DATA (UDC_BASE + 0x08) /* Endpoint FIFO */ +#define UDC_CTRL (UDC_BASE + 0x0C) /* Endpoint control */ +# define UDC_CLR_HALT (1 << 7) +# define UDC_SET_HALT (1 << 6) +# define UDC_CLRDATA_TOGGLE (1 << 3) +# define UDC_SET_FIFO_EN (1 << 2) +# define UDC_CLR_EP (1 << 1) +# define UDC_RESET_EP (1 << 0) +#define UDC_STAT_FLG (UDC_BASE + 0x10) /* Endpoint status */ +# define UDC_NO_RXPACKET (1 << 15) +# define UDC_MISS_IN (1 << 14) +# define UDC_DATA_FLUSH (1 << 13) +# define UDC_ISO_ERR (1 << 12) +# define UDC_ISO_FIFO_EMPTY (1 << 9) +# define UDC_ISO_FIFO_FULL (1 << 8) +# define UDC_EP_HALTED (1 << 6) +# define UDC_STALL (1 << 5) +# define UDC_NAK (1 << 4) +# define UDC_ACK (1 << 3) +# define UDC_FIFO_EN (1 << 2) +# define UDC_NON_ISO_FIFO_EMPTY (1 << 1) +# define UDC_NON_ISO_FIFO_FULL (1 << 0) +#define UDC_RXFSTAT (UDC_BASE + 0x14) /* OUT bytecount */ +#define UDC_SYSCON1 (UDC_BASE + 0x18) /* System config 1 */ +# define UDC_CFG_LOCK (1 << 8) +# define UDC_DATA_ENDIAN (1 << 7) +# define UDC_DMA_ENDIAN (1 << 6) +# define UDC_NAK_EN (1 << 4) +# define UDC_AUTODECODE_DIS (1 << 3) +# define UDC_SELF_PWR (1 << 2) +# define UDC_SOFF_DIS (1 << 1) +# define UDC_PULLUP_EN (1 << 0) +#define UDC_SYSCON2 (UDC_BASE + 0x1C) /* System config 2 */ +# define UDC_RMT_WKP (1 << 6) +# define UDC_STALL_CMD (1 << 5) +# define UDC_DEV_CFG (1 << 3) +# define UDC_CLR_CFG (1 << 2) +#define UDC_DEVSTAT (UDC_BASE + 0x20) /* Device status */ +# define UDC_B_HNP_ENABLE (1 << 9) +# define UDC_A_HNP_SUPPORT (1 << 8) +# define UDC_A_ALT_HNP_SUPPORT (1 << 7) +# define UDC_R_WK_OK (1 << 6) +# define UDC_USB_RESET (1 << 5) +# define UDC_SUS (1 << 4) +# define UDC_CFG (1 << 3) +# define UDC_ADD (1 << 2) +# define UDC_DEF (1 << 1) +# define UDC_ATT (1 << 0) +#define UDC_SOF (UDC_BASE + 0x24) /* Start of frame */ +# define UDC_FT_LOCK (1 << 12) +# define UDC_TS_OK (1 << 11) +# define UDC_TS 0x03ff +#define UDC_IRQ_EN (UDC_BASE + 0x28) /* Interrupt enable */ +# define UDC_SOF_IE (1 << 7) +# define UDC_EPN_RX_IE (1 << 5) +# define UDC_EPN_TX_IE (1 << 4) +# define UDC_DS_CHG_IE (1 << 3) +# define UDC_EP0_IE (1 << 0) +#define UDC_DMA_IRQ_EN (UDC_BASE + 0x2C) /* DMA irq enable */ + /* rx/tx dma channels numbered 1-3 not 0-2 */ +# define UDC_TX_DONE_IE(n) (1 << (4 * (n) - 2)) +# define UDC_RX_CNT_IE(n) (1 << (4 * (n) - 3)) +# define UDC_RX_EOT_IE(n) (1 << (4 * (n) - 4)) +#define UDC_IRQ_SRC (UDC_BASE + 0x30) /* Interrupt source */ +# define UDC_TXN_DONE (1 << 10) +# define UDC_RXN_CNT (1 << 9) +# define UDC_RXN_EOT (1 << 8) +# define UDC_IRQ_SOF (1 << 7) +# define UDC_EPN_RX (1 << 5) +# define UDC_EPN_TX (1 << 4) +# define UDC_DS_CHG (1 << 3) +# define UDC_SETUP (1 << 2) +# define UDC_EP0_RX (1 << 1) +# define UDC_EP0_TX (1 << 0) +# define UDC_IRQ_SRC_MASK 0x7bf +#define UDC_EPN_STAT (UDC_BASE + 0x34) /* EP irq status */ +#define UDC_DMAN_STAT (UDC_BASE + 0x38) /* DMA irq status */ +# define UDC_DMA_RX_SB (1 << 12) +# define UDC_DMA_RX_SRC(x) (((x)>>8) & 0xf) +# define UDC_DMA_TX_SRC(x) (((x)>>0) & 0xf) + + +/* DMA configuration registers: up to three channels in each direction. */ +#define UDC_RXDMA_CFG (UDC_BASE + 0x40) /* 3 eps for RX DMA */ +# define UDC_DMA_REQ (1 << 12) +#define UDC_TXDMA_CFG (UDC_BASE + 0x44) /* 3 eps for TX DMA */ +#define UDC_DATA_DMA (UDC_BASE + 0x48) /* rx/tx fifo addr */ + +/* rx/tx dma control, numbering channels 1-3 not 0-2 */ +#define UDC_TXDMA(chan) (UDC_BASE + 0x50 - 4 + 4 * (chan)) +# define UDC_TXN_EOT (1 << 15) /* bytes vs packets */ +# define UDC_TXN_START (1 << 14) /* start transfer */ +# define UDC_TXN_TSC 0x03ff /* units in xfer */ +#define UDC_RXDMA(chan) (UDC_BASE + 0x60 - 4 + 4 * (chan)) +# define UDC_RXN_STOP (1 << 15) /* enable EOT irq */ +# define UDC_RXN_TC 0x00ff /* packets in xfer */ + + +/* + * Endpoint configuration registers (used before CFG_LOCK is set) + * UDC_EP_TX(0) is unused + */ +#define UDC_EP_RX(endpoint) (UDC_BASE + 0x80 + (endpoint)*4) +# define UDC_EPN_RX_VALID (1 << 15) +# define UDC_EPN_RX_DB (1 << 14) + /* buffer size in bits 13, 12 */ +# define UDC_EPN_RX_ISO (1 << 11) + /* buffer pointer in low 11 bits */ +#define UDC_EP_TX(endpoint) (UDC_BASE + 0xc0 + (endpoint)*4) + /* same bitfields as in RX */ + +/*-------------------------------------------------------------------------*/ + +struct omap_req { + struct usb_request req; + struct list_head queue; + unsigned dma_bytes; + unsigned mapped:1; +}; + +struct omap_ep { + struct usb_ep ep; + struct list_head queue; + unsigned long irqs; + struct list_head iso; + char name[14]; + u16 maxpacket; + u8 bEndpointAddress; + u8 bmAttributes; + unsigned double_buf:1; + unsigned stopped:1; + unsigned fnf:1; + unsigned has_dma:1; + u8 ackwait; + u8 dma_channel; + u16 dma_counter; + int lch; + struct omap_udc *udc; + struct timer_list timer; +}; + +struct omap_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + spinlock_t lock; + struct omap_ep ep[32]; + u16 devstat; + u16 clr_halt; + struct usb_phy *transceiver; + struct list_head iso; + unsigned softconnect:1; + unsigned vbus_active:1; + unsigned ep0_pending:1; + unsigned ep0_in:1; + unsigned ep0_set_config:1; + unsigned ep0_reset_config:1; + unsigned ep0_setup:1; + struct completion *done; + struct clk *dc_clk; + struct clk *hhc_clk; + unsigned clk_requested:1; +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef VERBOSE +# define VDBG DBG +#else +# define VDBG(stuff...) do{}while(0) +#endif + +#define ERR(stuff...) pr_err("udc: " stuff) +#define WARNING(stuff...) pr_warning("udc: " stuff) +#define INFO(stuff...) pr_info("udc: " stuff) +#define DBG(stuff...) pr_debug("udc: " stuff) + +/*-------------------------------------------------------------------------*/ + +/* MOD_CONF_CTRL_0 */ +#define VBUS_W2FC_1510 (1 << 17) /* 0 gpio0, 1 dvdd2 pin */ + +/* FUNC_MUX_CTRL_0 */ +#define VBUS_CTRL_1510 (1 << 19) /* 1 connected (software) */ +#define VBUS_MODE_1510 (1 << 18) /* 0 hardware, 1 software */ + +#define HMC_1510 ((omap_readl(MOD_CONF_CTRL_0) >> 1) & 0x3f) +#define HMC_1610 (omap_readl(OTG_SYSCON_2) & 0x3f) +#define HMC (cpu_is_omap15xx() ? HMC_1510 : HMC_1610) + diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c new file mode 100644 index 00000000..24174e1d --- /dev/null +++ b/drivers/usb/gadget/pch_udc.c @@ -0,0 +1,3249 @@ +/* + * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* GPIO port for VBUS detecting */ +static int vbus_gpio_port = -1; /* GPIO port number (-1:Not used) */ + +#define PCH_VBUS_PERIOD 3000 /* VBUS polling period (msec) */ +#define PCH_VBUS_INTERVAL 10 /* VBUS polling interval (msec) */ + +/* Address offset of Registers */ +#define UDC_EP_REG_SHIFT 0x20 /* Offset to next EP */ + +#define UDC_EPCTL_ADDR 0x00 /* Endpoint control */ +#define UDC_EPSTS_ADDR 0x04 /* Endpoint status */ +#define UDC_BUFIN_FRAMENUM_ADDR 0x08 /* buffer size in / frame number out */ +#define UDC_BUFOUT_MAXPKT_ADDR 0x0C /* buffer size out / maxpkt in */ +#define UDC_SUBPTR_ADDR 0x10 /* setup buffer pointer */ +#define UDC_DESPTR_ADDR 0x14 /* Data descriptor pointer */ +#define UDC_CONFIRM_ADDR 0x18 /* Write/Read confirmation */ + +#define UDC_DEVCFG_ADDR 0x400 /* Device configuration */ +#define UDC_DEVCTL_ADDR 0x404 /* Device control */ +#define UDC_DEVSTS_ADDR 0x408 /* Device status */ +#define UDC_DEVIRQSTS_ADDR 0x40C /* Device irq status */ +#define UDC_DEVIRQMSK_ADDR 0x410 /* Device irq mask */ +#define UDC_EPIRQSTS_ADDR 0x414 /* Endpoint irq status */ +#define UDC_EPIRQMSK_ADDR 0x418 /* Endpoint irq mask */ +#define UDC_DEVLPM_ADDR 0x41C /* LPM control / status */ +#define UDC_CSR_BUSY_ADDR 0x4f0 /* UDC_CSR_BUSY Status register */ +#define UDC_SRST_ADDR 0x4fc /* SOFT RESET register */ +#define UDC_CSR_ADDR 0x500 /* USB_DEVICE endpoint register */ + +/* Endpoint control register */ +/* Bit position */ +#define UDC_EPCTL_MRXFLUSH (1 << 12) +#define UDC_EPCTL_RRDY (1 << 9) +#define UDC_EPCTL_CNAK (1 << 8) +#define UDC_EPCTL_SNAK (1 << 7) +#define UDC_EPCTL_NAK (1 << 6) +#define UDC_EPCTL_P (1 << 3) +#define UDC_EPCTL_F (1 << 1) +#define UDC_EPCTL_S (1 << 0) +#define UDC_EPCTL_ET_SHIFT 4 +/* Mask patern */ +#define UDC_EPCTL_ET_MASK 0x00000030 +/* Value for ET field */ +#define UDC_EPCTL_ET_CONTROL 0 +#define UDC_EPCTL_ET_ISO 1 +#define UDC_EPCTL_ET_BULK 2 +#define UDC_EPCTL_ET_INTERRUPT 3 + +/* Endpoint status register */ +/* Bit position */ +#define UDC_EPSTS_XFERDONE (1 << 27) +#define UDC_EPSTS_RSS (1 << 26) +#define UDC_EPSTS_RCS (1 << 25) +#define UDC_EPSTS_TXEMPTY (1 << 24) +#define UDC_EPSTS_TDC (1 << 10) +#define UDC_EPSTS_HE (1 << 9) +#define UDC_EPSTS_MRXFIFO_EMP (1 << 8) +#define UDC_EPSTS_BNA (1 << 7) +#define UDC_EPSTS_IN (1 << 6) +#define UDC_EPSTS_OUT_SHIFT 4 +/* Mask patern */ +#define UDC_EPSTS_OUT_MASK 0x00000030 +#define UDC_EPSTS_ALL_CLR_MASK 0x1F0006F0 +/* Value for OUT field */ +#define UDC_EPSTS_OUT_SETUP 2 +#define UDC_EPSTS_OUT_DATA 1 + +/* Device configuration register */ +/* Bit position */ +#define UDC_DEVCFG_CSR_PRG (1 << 17) +#define UDC_DEVCFG_SP (1 << 3) +/* SPD Valee */ +#define UDC_DEVCFG_SPD_HS 0x0 +#define UDC_DEVCFG_SPD_FS 0x1 +#define UDC_DEVCFG_SPD_LS 0x2 + +/* Device control register */ +/* Bit position */ +#define UDC_DEVCTL_THLEN_SHIFT 24 +#define UDC_DEVCTL_BRLEN_SHIFT 16 +#define UDC_DEVCTL_CSR_DONE (1 << 13) +#define UDC_DEVCTL_SD (1 << 10) +#define UDC_DEVCTL_MODE (1 << 9) +#define UDC_DEVCTL_BREN (1 << 8) +#define UDC_DEVCTL_THE (1 << 7) +#define UDC_DEVCTL_DU (1 << 4) +#define UDC_DEVCTL_TDE (1 << 3) +#define UDC_DEVCTL_RDE (1 << 2) +#define UDC_DEVCTL_RES (1 << 0) + +/* Device status register */ +/* Bit position */ +#define UDC_DEVSTS_TS_SHIFT 18 +#define UDC_DEVSTS_ENUM_SPEED_SHIFT 13 +#define UDC_DEVSTS_ALT_SHIFT 8 +#define UDC_DEVSTS_INTF_SHIFT 4 +#define UDC_DEVSTS_CFG_SHIFT 0 +/* Mask patern */ +#define UDC_DEVSTS_TS_MASK 0xfffc0000 +#define UDC_DEVSTS_ENUM_SPEED_MASK 0x00006000 +#define UDC_DEVSTS_ALT_MASK 0x00000f00 +#define UDC_DEVSTS_INTF_MASK 0x000000f0 +#define UDC_DEVSTS_CFG_MASK 0x0000000f +/* value for maximum speed for SPEED field */ +#define UDC_DEVSTS_ENUM_SPEED_FULL 1 +#define UDC_DEVSTS_ENUM_SPEED_HIGH 0 +#define UDC_DEVSTS_ENUM_SPEED_LOW 2 +#define UDC_DEVSTS_ENUM_SPEED_FULLX 3 + +/* Device irq register */ +/* Bit position */ +#define UDC_DEVINT_RWKP (1 << 7) +#define UDC_DEVINT_ENUM (1 << 6) +#define UDC_DEVINT_SOF (1 << 5) +#define UDC_DEVINT_US (1 << 4) +#define UDC_DEVINT_UR (1 << 3) +#define UDC_DEVINT_ES (1 << 2) +#define UDC_DEVINT_SI (1 << 1) +#define UDC_DEVINT_SC (1 << 0) +/* Mask patern */ +#define UDC_DEVINT_MSK 0x7f + +/* Endpoint irq register */ +/* Bit position */ +#define UDC_EPINT_IN_SHIFT 0 +#define UDC_EPINT_OUT_SHIFT 16 +#define UDC_EPINT_IN_EP0 (1 << 0) +#define UDC_EPINT_OUT_EP0 (1 << 16) +/* Mask patern */ +#define UDC_EPINT_MSK_DISABLE_ALL 0xffffffff + +/* UDC_CSR_BUSY Status register */ +/* Bit position */ +#define UDC_CSR_BUSY (1 << 0) + +/* SOFT RESET register */ +/* Bit position */ +#define UDC_PSRST (1 << 1) +#define UDC_SRST (1 << 0) + +/* USB_DEVICE endpoint register */ +/* Bit position */ +#define UDC_CSR_NE_NUM_SHIFT 0 +#define UDC_CSR_NE_DIR_SHIFT 4 +#define UDC_CSR_NE_TYPE_SHIFT 5 +#define UDC_CSR_NE_CFG_SHIFT 7 +#define UDC_CSR_NE_INTF_SHIFT 11 +#define UDC_CSR_NE_ALT_SHIFT 15 +#define UDC_CSR_NE_MAX_PKT_SHIFT 19 +/* Mask patern */ +#define UDC_CSR_NE_NUM_MASK 0x0000000f +#define UDC_CSR_NE_DIR_MASK 0x00000010 +#define UDC_CSR_NE_TYPE_MASK 0x00000060 +#define UDC_CSR_NE_CFG_MASK 0x00000780 +#define UDC_CSR_NE_INTF_MASK 0x00007800 +#define UDC_CSR_NE_ALT_MASK 0x00078000 +#define UDC_CSR_NE_MAX_PKT_MASK 0x3ff80000 + +#define PCH_UDC_CSR(ep) (UDC_CSR_ADDR + ep*4) +#define PCH_UDC_EPINT(in, num)\ + (1 << (num + (in ? UDC_EPINT_IN_SHIFT : UDC_EPINT_OUT_SHIFT))) + +/* Index of endpoint */ +#define UDC_EP0IN_IDX 0 +#define UDC_EP0OUT_IDX 1 +#define UDC_EPIN_IDX(ep) (ep * 2) +#define UDC_EPOUT_IDX(ep) (ep * 2 + 1) +#define PCH_UDC_EP0 0 +#define PCH_UDC_EP1 1 +#define PCH_UDC_EP2 2 +#define PCH_UDC_EP3 3 + +/* Number of endpoint */ +#define PCH_UDC_EP_NUM 32 /* Total number of EPs (16 IN,16 OUT) */ +#define PCH_UDC_USED_EP_NUM 4 /* EP number of EP's really used */ +/* Length Value */ +#define PCH_UDC_BRLEN 0x0F /* Burst length */ +#define PCH_UDC_THLEN 0x1F /* Threshold length */ +/* Value of EP Buffer Size */ +#define UDC_EP0IN_BUFF_SIZE 16 +#define UDC_EPIN_BUFF_SIZE 256 +#define UDC_EP0OUT_BUFF_SIZE 16 +#define UDC_EPOUT_BUFF_SIZE 256 +/* Value of EP maximum packet size */ +#define UDC_EP0IN_MAX_PKT_SIZE 64 +#define UDC_EP0OUT_MAX_PKT_SIZE 64 +#define UDC_BULK_MAX_PKT_SIZE 512 + +/* DMA */ +#define DMA_DIR_RX 1 /* DMA for data receive */ +#define DMA_DIR_TX 2 /* DMA for data transmit */ +#define DMA_ADDR_INVALID (~(dma_addr_t)0) +#define UDC_DMA_MAXPACKET 65536 /* maximum packet size for DMA */ + +/** + * struct pch_udc_data_dma_desc - Structure to hold DMA descriptor information + * for data + * @status: Status quadlet + * @reserved: Reserved + * @dataptr: Buffer descriptor + * @next: Next descriptor + */ +struct pch_udc_data_dma_desc { + u32 status; + u32 reserved; + u32 dataptr; + u32 next; +}; + +/** + * struct pch_udc_stp_dma_desc - Structure to hold DMA descriptor information + * for control data + * @status: Status + * @reserved: Reserved + * @data12: First setup word + * @data34: Second setup word + */ +struct pch_udc_stp_dma_desc { + u32 status; + u32 reserved; + struct usb_ctrlrequest request; +} __attribute((packed)); + +/* DMA status definitions */ +/* Buffer status */ +#define PCH_UDC_BUFF_STS 0xC0000000 +#define PCH_UDC_BS_HST_RDY 0x00000000 +#define PCH_UDC_BS_DMA_BSY 0x40000000 +#define PCH_UDC_BS_DMA_DONE 0x80000000 +#define PCH_UDC_BS_HST_BSY 0xC0000000 +/* Rx/Tx Status */ +#define PCH_UDC_RXTX_STS 0x30000000 +#define PCH_UDC_RTS_SUCC 0x00000000 +#define PCH_UDC_RTS_DESERR 0x10000000 +#define PCH_UDC_RTS_BUFERR 0x30000000 +/* Last Descriptor Indication */ +#define PCH_UDC_DMA_LAST 0x08000000 +/* Number of Rx/Tx Bytes Mask */ +#define PCH_UDC_RXTX_BYTES 0x0000ffff + +/** + * struct pch_udc_cfg_data - Structure to hold current configuration + * and interface information + * @cur_cfg: current configuration in use + * @cur_intf: current interface in use + * @cur_alt: current alt interface in use + */ +struct pch_udc_cfg_data { + u16 cur_cfg; + u16 cur_intf; + u16 cur_alt; +}; + +/** + * struct pch_udc_ep - Structure holding a PCH USB device Endpoint information + * @ep: embedded ep request + * @td_stp_phys: for setup request + * @td_data_phys: for data request + * @td_stp: for setup request + * @td_data: for data request + * @dev: reference to device struct + * @offset_addr: offset address of ep register + * @desc: for this ep + * @queue: queue for requests + * @num: endpoint number + * @in: endpoint is IN + * @halted: endpoint halted? + * @epsts: Endpoint status + */ +struct pch_udc_ep { + struct usb_ep ep; + dma_addr_t td_stp_phys; + dma_addr_t td_data_phys; + struct pch_udc_stp_dma_desc *td_stp; + struct pch_udc_data_dma_desc *td_data; + struct pch_udc_dev *dev; + unsigned long offset_addr; + struct list_head queue; + unsigned num:5, + in:1, + halted:1; + unsigned long epsts; +}; + +/** + * struct pch_vbus_gpio_data - Structure holding GPIO informaton + * for detecting VBUS + * @port: gpio port number + * @intr: gpio interrupt number + * @irq_work_fall Structure for WorkQueue + * @irq_work_rise Structure for WorkQueue + */ +struct pch_vbus_gpio_data { + int port; + int intr; + struct work_struct irq_work_fall; + struct work_struct irq_work_rise; +}; + +/** + * struct pch_udc_dev - Structure holding complete information + * of the PCH USB device + * @gadget: gadget driver data + * @driver: reference to gadget driver bound + * @pdev: reference to the PCI device + * @ep: array of endpoints + * @lock: protects all state + * @active: enabled the PCI device + * @stall: stall requested + * @prot_stall: protcol stall requested + * @irq_registered: irq registered with system + * @mem_region: device memory mapped + * @registered: driver regsitered with system + * @suspended: driver in suspended state + * @connected: gadget driver associated + * @vbus_session: required vbus_session state + * @set_cfg_not_acked: pending acknowledgement 4 setup + * @waiting_zlp_ack: pending acknowledgement 4 ZLP + * @data_requests: DMA pool for data requests + * @stp_requests: DMA pool for setup requests + * @dma_addr: DMA pool for received + * @ep0out_buf: Buffer for DMA + * @setup_data: Received setup data + * @phys_addr: of device memory + * @base_addr: for mapped device memory + * @irq: IRQ line for the device + * @cfg_data: current cfg, intf, and alt in use + * @vbus_gpio: GPIO informaton for detecting VBUS + */ +struct pch_udc_dev { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct pci_dev *pdev; + struct pch_udc_ep ep[PCH_UDC_EP_NUM]; + spinlock_t lock; /* protects all state */ + unsigned active:1, + stall:1, + prot_stall:1, + irq_registered:1, + mem_region:1, + suspended:1, + connected:1, + vbus_session:1, + set_cfg_not_acked:1, + waiting_zlp_ack:1; + struct pci_pool *data_requests; + struct pci_pool *stp_requests; + dma_addr_t dma_addr; + void *ep0out_buf; + struct usb_ctrlrequest setup_data; + unsigned long phys_addr; + void __iomem *base_addr; + unsigned irq; + struct pch_udc_cfg_data cfg_data; + struct pch_vbus_gpio_data vbus_gpio; +}; +#define to_pch_udc(g) (container_of((g), struct pch_udc_dev, gadget)) + +#define PCH_UDC_PCI_BAR 1 +#define PCI_DEVICE_ID_INTEL_EG20T_UDC 0x8808 +#define PCI_VENDOR_ID_ROHM 0x10DB +#define PCI_DEVICE_ID_ML7213_IOH_UDC 0x801D +#define PCI_DEVICE_ID_ML7831_IOH_UDC 0x8808 + +static const char ep0_string[] = "ep0in"; +static DEFINE_SPINLOCK(udc_stall_spinlock); /* stall spin lock */ +static bool speed_fs; +module_param_named(speed_fs, speed_fs, bool, S_IRUGO); +MODULE_PARM_DESC(speed_fs, "true for Full speed operation"); + +/** + * struct pch_udc_request - Structure holding a PCH USB device request packet + * @req: embedded ep request + * @td_data_phys: phys. address + * @td_data: first dma desc. of chain + * @td_data_last: last dma desc. of chain + * @queue: associated queue + * @dma_going: DMA in progress for request + * @dma_mapped: DMA memory mapped for request + * @dma_done: DMA completed for request + * @chain_len: chain length + * @buf: Buffer memory for align adjustment + * @dma: DMA memory for align adjustment + */ +struct pch_udc_request { + struct usb_request req; + dma_addr_t td_data_phys; + struct pch_udc_data_dma_desc *td_data; + struct pch_udc_data_dma_desc *td_data_last; + struct list_head queue; + unsigned dma_going:1, + dma_mapped:1, + dma_done:1; + unsigned chain_len; + void *buf; + dma_addr_t dma; +}; + +static inline u32 pch_udc_readl(struct pch_udc_dev *dev, unsigned long reg) +{ + return ioread32(dev->base_addr + reg); +} + +static inline void pch_udc_writel(struct pch_udc_dev *dev, + unsigned long val, unsigned long reg) +{ + iowrite32(val, dev->base_addr + reg); +} + +static inline void pch_udc_bit_set(struct pch_udc_dev *dev, + unsigned long reg, + unsigned long bitmask) +{ + pch_udc_writel(dev, pch_udc_readl(dev, reg) | bitmask, reg); +} + +static inline void pch_udc_bit_clr(struct pch_udc_dev *dev, + unsigned long reg, + unsigned long bitmask) +{ + pch_udc_writel(dev, pch_udc_readl(dev, reg) & ~(bitmask), reg); +} + +static inline u32 pch_udc_ep_readl(struct pch_udc_ep *ep, unsigned long reg) +{ + return ioread32(ep->dev->base_addr + ep->offset_addr + reg); +} + +static inline void pch_udc_ep_writel(struct pch_udc_ep *ep, + unsigned long val, unsigned long reg) +{ + iowrite32(val, ep->dev->base_addr + ep->offset_addr + reg); +} + +static inline void pch_udc_ep_bit_set(struct pch_udc_ep *ep, + unsigned long reg, + unsigned long bitmask) +{ + pch_udc_ep_writel(ep, pch_udc_ep_readl(ep, reg) | bitmask, reg); +} + +static inline void pch_udc_ep_bit_clr(struct pch_udc_ep *ep, + unsigned long reg, + unsigned long bitmask) +{ + pch_udc_ep_writel(ep, pch_udc_ep_readl(ep, reg) & ~(bitmask), reg); +} + +/** + * pch_udc_csr_busy() - Wait till idle. + * @dev: Reference to pch_udc_dev structure + */ +static void pch_udc_csr_busy(struct pch_udc_dev *dev) +{ + unsigned int count = 200; + + /* Wait till idle */ + while ((pch_udc_readl(dev, UDC_CSR_BUSY_ADDR) & UDC_CSR_BUSY) + && --count) + cpu_relax(); + if (!count) + dev_err(&dev->pdev->dev, "%s: wait error\n", __func__); +} + +/** + * pch_udc_write_csr() - Write the command and status registers. + * @dev: Reference to pch_udc_dev structure + * @val: value to be written to CSR register + * @addr: address of CSR register + */ +static void pch_udc_write_csr(struct pch_udc_dev *dev, unsigned long val, + unsigned int ep) +{ + unsigned long reg = PCH_UDC_CSR(ep); + + pch_udc_csr_busy(dev); /* Wait till idle */ + pch_udc_writel(dev, val, reg); + pch_udc_csr_busy(dev); /* Wait till idle */ +} + +/** + * pch_udc_read_csr() - Read the command and status registers. + * @dev: Reference to pch_udc_dev structure + * @addr: address of CSR register + * + * Return codes: content of CSR register + */ +static u32 pch_udc_read_csr(struct pch_udc_dev *dev, unsigned int ep) +{ + unsigned long reg = PCH_UDC_CSR(ep); + + pch_udc_csr_busy(dev); /* Wait till idle */ + pch_udc_readl(dev, reg); /* Dummy read */ + pch_udc_csr_busy(dev); /* Wait till idle */ + return pch_udc_readl(dev, reg); +} + +/** + * pch_udc_rmt_wakeup() - Initiate for remote wakeup + * @dev: Reference to pch_udc_dev structure + */ +static inline void pch_udc_rmt_wakeup(struct pch_udc_dev *dev) +{ + pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES); + mdelay(1); + pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES); +} + +/** + * pch_udc_get_frame() - Get the current frame from device status register + * @dev: Reference to pch_udc_dev structure + * Retern current frame + */ +static inline int pch_udc_get_frame(struct pch_udc_dev *dev) +{ + u32 frame = pch_udc_readl(dev, UDC_DEVSTS_ADDR); + return (frame & UDC_DEVSTS_TS_MASK) >> UDC_DEVSTS_TS_SHIFT; +} + +/** + * pch_udc_clear_selfpowered() - Clear the self power control + * @dev: Reference to pch_udc_regs structure + */ +static inline void pch_udc_clear_selfpowered(struct pch_udc_dev *dev) +{ + pch_udc_bit_clr(dev, UDC_DEVCFG_ADDR, UDC_DEVCFG_SP); +} + +/** + * pch_udc_set_selfpowered() - Set the self power control + * @dev: Reference to pch_udc_regs structure + */ +static inline void pch_udc_set_selfpowered(struct pch_udc_dev *dev) +{ + pch_udc_bit_set(dev, UDC_DEVCFG_ADDR, UDC_DEVCFG_SP); +} + +/** + * pch_udc_set_disconnect() - Set the disconnect status. + * @dev: Reference to pch_udc_regs structure + */ +static inline void pch_udc_set_disconnect(struct pch_udc_dev *dev) +{ + pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_SD); +} + +/** + * pch_udc_clear_disconnect() - Clear the disconnect status. + * @dev: Reference to pch_udc_regs structure + */ +static void pch_udc_clear_disconnect(struct pch_udc_dev *dev) +{ + /* Clear the disconnect */ + pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES); + pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_SD); + mdelay(1); + /* Resume USB signalling */ + pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES); +} + +/** + * pch_udc_reconnect() - This API initializes usb device controller, + * and clear the disconnect status. + * @dev: Reference to pch_udc_regs structure + */ +static void pch_udc_init(struct pch_udc_dev *dev); +static void pch_udc_reconnect(struct pch_udc_dev *dev) +{ + pch_udc_init(dev); + + /* enable device interrupts */ + /* pch_udc_enable_interrupts() */ + pch_udc_bit_clr(dev, UDC_DEVIRQMSK_ADDR, + UDC_DEVINT_UR | UDC_DEVINT_ENUM); + + /* Clear the disconnect */ + pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES); + pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_SD); + mdelay(1); + /* Resume USB signalling */ + pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RES); +} + +/** + * pch_udc_vbus_session() - set or clearr the disconnect status. + * @dev: Reference to pch_udc_regs structure + * @is_active: Parameter specifying the action + * 0: indicating VBUS power is ending + * !0: indicating VBUS power is starting + */ +static inline void pch_udc_vbus_session(struct pch_udc_dev *dev, + int is_active) +{ + if (is_active) { + pch_udc_reconnect(dev); + dev->vbus_session = 1; + } else { + if (dev->driver && dev->driver->disconnect) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + pch_udc_set_disconnect(dev); + dev->vbus_session = 0; + } +} + +/** + * pch_udc_ep_set_stall() - Set the stall of endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + */ +static void pch_udc_ep_set_stall(struct pch_udc_ep *ep) +{ + if (ep->in) { + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_F); + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_S); + } else { + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_S); + } +} + +/** + * pch_udc_ep_clear_stall() - Clear the stall of endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + */ +static inline void pch_udc_ep_clear_stall(struct pch_udc_ep *ep) +{ + /* Clear the stall */ + pch_udc_ep_bit_clr(ep, UDC_EPCTL_ADDR, UDC_EPCTL_S); + /* Clear NAK by writing CNAK */ + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_CNAK); +} + +/** + * pch_udc_ep_set_trfr_type() - Set the transfer type of endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + * @type: Type of endpoint + */ +static inline void pch_udc_ep_set_trfr_type(struct pch_udc_ep *ep, + u8 type) +{ + pch_udc_ep_writel(ep, ((type << UDC_EPCTL_ET_SHIFT) & + UDC_EPCTL_ET_MASK), UDC_EPCTL_ADDR); +} + +/** + * pch_udc_ep_set_bufsz() - Set the maximum packet size for the endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + * @buf_size: The buffer word size + */ +static void pch_udc_ep_set_bufsz(struct pch_udc_ep *ep, + u32 buf_size, u32 ep_in) +{ + u32 data; + if (ep_in) { + data = pch_udc_ep_readl(ep, UDC_BUFIN_FRAMENUM_ADDR); + data = (data & 0xffff0000) | (buf_size & 0xffff); + pch_udc_ep_writel(ep, data, UDC_BUFIN_FRAMENUM_ADDR); + } else { + data = pch_udc_ep_readl(ep, UDC_BUFOUT_MAXPKT_ADDR); + data = (buf_size << 16) | (data & 0xffff); + pch_udc_ep_writel(ep, data, UDC_BUFOUT_MAXPKT_ADDR); + } +} + +/** + * pch_udc_ep_set_maxpkt() - Set the Max packet size for the endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + * @pkt_size: The packet byte size + */ +static void pch_udc_ep_set_maxpkt(struct pch_udc_ep *ep, u32 pkt_size) +{ + u32 data = pch_udc_ep_readl(ep, UDC_BUFOUT_MAXPKT_ADDR); + data = (data & 0xffff0000) | (pkt_size & 0xffff); + pch_udc_ep_writel(ep, data, UDC_BUFOUT_MAXPKT_ADDR); +} + +/** + * pch_udc_ep_set_subptr() - Set the Setup buffer pointer for the endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + * @addr: Address of the register + */ +static inline void pch_udc_ep_set_subptr(struct pch_udc_ep *ep, u32 addr) +{ + pch_udc_ep_writel(ep, addr, UDC_SUBPTR_ADDR); +} + +/** + * pch_udc_ep_set_ddptr() - Set the Data descriptor pointer for the endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + * @addr: Address of the register + */ +static inline void pch_udc_ep_set_ddptr(struct pch_udc_ep *ep, u32 addr) +{ + pch_udc_ep_writel(ep, addr, UDC_DESPTR_ADDR); +} + +/** + * pch_udc_ep_set_pd() - Set the poll demand bit for the endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + */ +static inline void pch_udc_ep_set_pd(struct pch_udc_ep *ep) +{ + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_P); +} + +/** + * pch_udc_ep_set_rrdy() - Set the receive ready bit for the endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + */ +static inline void pch_udc_ep_set_rrdy(struct pch_udc_ep *ep) +{ + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_RRDY); +} + +/** + * pch_udc_ep_clear_rrdy() - Clear the receive ready bit for the endpoint + * @ep: Reference to structure of type pch_udc_ep_regs + */ +static inline void pch_udc_ep_clear_rrdy(struct pch_udc_ep *ep) +{ + pch_udc_ep_bit_clr(ep, UDC_EPCTL_ADDR, UDC_EPCTL_RRDY); +} + +/** + * pch_udc_set_dma() - Set the 'TDE' or RDE bit of device control + * register depending on the direction specified + * @dev: Reference to structure of type pch_udc_regs + * @dir: whether Tx or Rx + * DMA_DIR_RX: Receive + * DMA_DIR_TX: Transmit + */ +static inline void pch_udc_set_dma(struct pch_udc_dev *dev, int dir) +{ + if (dir == DMA_DIR_RX) + pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RDE); + else if (dir == DMA_DIR_TX) + pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_TDE); +} + +/** + * pch_udc_clear_dma() - Clear the 'TDE' or RDE bit of device control + * register depending on the direction specified + * @dev: Reference to structure of type pch_udc_regs + * @dir: Whether Tx or Rx + * DMA_DIR_RX: Receive + * DMA_DIR_TX: Transmit + */ +static inline void pch_udc_clear_dma(struct pch_udc_dev *dev, int dir) +{ + if (dir == DMA_DIR_RX) + pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_RDE); + else if (dir == DMA_DIR_TX) + pch_udc_bit_clr(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_TDE); +} + +/** + * pch_udc_set_csr_done() - Set the device control register + * CSR done field (bit 13) + * @dev: reference to structure of type pch_udc_regs + */ +static inline void pch_udc_set_csr_done(struct pch_udc_dev *dev) +{ + pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, UDC_DEVCTL_CSR_DONE); +} + +/** + * pch_udc_disable_interrupts() - Disables the specified interrupts + * @dev: Reference to structure of type pch_udc_regs + * @mask: Mask to disable interrupts + */ +static inline void pch_udc_disable_interrupts(struct pch_udc_dev *dev, + u32 mask) +{ + pch_udc_bit_set(dev, UDC_DEVIRQMSK_ADDR, mask); +} + +/** + * pch_udc_enable_interrupts() - Enable the specified interrupts + * @dev: Reference to structure of type pch_udc_regs + * @mask: Mask to enable interrupts + */ +static inline void pch_udc_enable_interrupts(struct pch_udc_dev *dev, + u32 mask) +{ + pch_udc_bit_clr(dev, UDC_DEVIRQMSK_ADDR, mask); +} + +/** + * pch_udc_disable_ep_interrupts() - Disable endpoint interrupts + * @dev: Reference to structure of type pch_udc_regs + * @mask: Mask to disable interrupts + */ +static inline void pch_udc_disable_ep_interrupts(struct pch_udc_dev *dev, + u32 mask) +{ + pch_udc_bit_set(dev, UDC_EPIRQMSK_ADDR, mask); +} + +/** + * pch_udc_enable_ep_interrupts() - Enable endpoint interrupts + * @dev: Reference to structure of type pch_udc_regs + * @mask: Mask to enable interrupts + */ +static inline void pch_udc_enable_ep_interrupts(struct pch_udc_dev *dev, + u32 mask) +{ + pch_udc_bit_clr(dev, UDC_EPIRQMSK_ADDR, mask); +} + +/** + * pch_udc_read_device_interrupts() - Read the device interrupts + * @dev: Reference to structure of type pch_udc_regs + * Retern The device interrupts + */ +static inline u32 pch_udc_read_device_interrupts(struct pch_udc_dev *dev) +{ + return pch_udc_readl(dev, UDC_DEVIRQSTS_ADDR); +} + +/** + * pch_udc_write_device_interrupts() - Write device interrupts + * @dev: Reference to structure of type pch_udc_regs + * @val: The value to be written to interrupt register + */ +static inline void pch_udc_write_device_interrupts(struct pch_udc_dev *dev, + u32 val) +{ + pch_udc_writel(dev, val, UDC_DEVIRQSTS_ADDR); +} + +/** + * pch_udc_read_ep_interrupts() - Read the endpoint interrupts + * @dev: Reference to structure of type pch_udc_regs + * Retern The endpoint interrupt + */ +static inline u32 pch_udc_read_ep_interrupts(struct pch_udc_dev *dev) +{ + return pch_udc_readl(dev, UDC_EPIRQSTS_ADDR); +} + +/** + * pch_udc_write_ep_interrupts() - Clear endpoint interupts + * @dev: Reference to structure of type pch_udc_regs + * @val: The value to be written to interrupt register + */ +static inline void pch_udc_write_ep_interrupts(struct pch_udc_dev *dev, + u32 val) +{ + pch_udc_writel(dev, val, UDC_EPIRQSTS_ADDR); +} + +/** + * pch_udc_read_device_status() - Read the device status + * @dev: Reference to structure of type pch_udc_regs + * Retern The device status + */ +static inline u32 pch_udc_read_device_status(struct pch_udc_dev *dev) +{ + return pch_udc_readl(dev, UDC_DEVSTS_ADDR); +} + +/** + * pch_udc_read_ep_control() - Read the endpoint control + * @ep: Reference to structure of type pch_udc_ep_regs + * Retern The endpoint control register value + */ +static inline u32 pch_udc_read_ep_control(struct pch_udc_ep *ep) +{ + return pch_udc_ep_readl(ep, UDC_EPCTL_ADDR); +} + +/** + * pch_udc_clear_ep_control() - Clear the endpoint control register + * @ep: Reference to structure of type pch_udc_ep_regs + * Retern The endpoint control register value + */ +static inline void pch_udc_clear_ep_control(struct pch_udc_ep *ep) +{ + return pch_udc_ep_writel(ep, 0, UDC_EPCTL_ADDR); +} + +/** + * pch_udc_read_ep_status() - Read the endpoint status + * @ep: Reference to structure of type pch_udc_ep_regs + * Retern The endpoint status + */ +static inline u32 pch_udc_read_ep_status(struct pch_udc_ep *ep) +{ + return pch_udc_ep_readl(ep, UDC_EPSTS_ADDR); +} + +/** + * pch_udc_clear_ep_status() - Clear the endpoint status + * @ep: Reference to structure of type pch_udc_ep_regs + * @stat: Endpoint status + */ +static inline void pch_udc_clear_ep_status(struct pch_udc_ep *ep, + u32 stat) +{ + return pch_udc_ep_writel(ep, stat, UDC_EPSTS_ADDR); +} + +/** + * pch_udc_ep_set_nak() - Set the bit 7 (SNAK field) + * of the endpoint control register + * @ep: Reference to structure of type pch_udc_ep_regs + */ +static inline void pch_udc_ep_set_nak(struct pch_udc_ep *ep) +{ + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_SNAK); +} + +/** + * pch_udc_ep_clear_nak() - Set the bit 8 (CNAK field) + * of the endpoint control register + * @ep: reference to structure of type pch_udc_ep_regs + */ +static void pch_udc_ep_clear_nak(struct pch_udc_ep *ep) +{ + unsigned int loopcnt = 0; + struct pch_udc_dev *dev = ep->dev; + + if (!(pch_udc_ep_readl(ep, UDC_EPCTL_ADDR) & UDC_EPCTL_NAK)) + return; + if (!ep->in) { + loopcnt = 10000; + while (!(pch_udc_read_ep_status(ep) & UDC_EPSTS_MRXFIFO_EMP) && + --loopcnt) + udelay(5); + if (!loopcnt) + dev_err(&dev->pdev->dev, "%s: RxFIFO not Empty\n", + __func__); + } + loopcnt = 10000; + while ((pch_udc_read_ep_control(ep) & UDC_EPCTL_NAK) && --loopcnt) { + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_CNAK); + udelay(5); + } + if (!loopcnt) + dev_err(&dev->pdev->dev, "%s: Clear NAK not set for ep%d%s\n", + __func__, ep->num, (ep->in ? "in" : "out")); +} + +/** + * pch_udc_ep_fifo_flush() - Flush the endpoint fifo + * @ep: reference to structure of type pch_udc_ep_regs + * @dir: direction of endpoint + * 0: endpoint is OUT + * !0: endpoint is IN + */ +static void pch_udc_ep_fifo_flush(struct pch_udc_ep *ep, int dir) +{ + if (dir) { /* IN ep */ + pch_udc_ep_bit_set(ep, UDC_EPCTL_ADDR, UDC_EPCTL_F); + return; + } +} + +/** + * pch_udc_ep_enable() - This api enables endpoint + * @regs: Reference to structure pch_udc_ep_regs + * @desc: endpoint descriptor + */ +static void pch_udc_ep_enable(struct pch_udc_ep *ep, + struct pch_udc_cfg_data *cfg, + const struct usb_endpoint_descriptor *desc) +{ + u32 val = 0; + u32 buff_size = 0; + + pch_udc_ep_set_trfr_type(ep, desc->bmAttributes); + if (ep->in) + buff_size = UDC_EPIN_BUFF_SIZE; + else + buff_size = UDC_EPOUT_BUFF_SIZE; + pch_udc_ep_set_bufsz(ep, buff_size, ep->in); + pch_udc_ep_set_maxpkt(ep, usb_endpoint_maxp(desc)); + pch_udc_ep_set_nak(ep); + pch_udc_ep_fifo_flush(ep, ep->in); + /* Configure the endpoint */ + val = ep->num << UDC_CSR_NE_NUM_SHIFT | ep->in << UDC_CSR_NE_DIR_SHIFT | + ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) << + UDC_CSR_NE_TYPE_SHIFT) | + (cfg->cur_cfg << UDC_CSR_NE_CFG_SHIFT) | + (cfg->cur_intf << UDC_CSR_NE_INTF_SHIFT) | + (cfg->cur_alt << UDC_CSR_NE_ALT_SHIFT) | + usb_endpoint_maxp(desc) << UDC_CSR_NE_MAX_PKT_SHIFT; + + if (ep->in) + pch_udc_write_csr(ep->dev, val, UDC_EPIN_IDX(ep->num)); + else + pch_udc_write_csr(ep->dev, val, UDC_EPOUT_IDX(ep->num)); +} + +/** + * pch_udc_ep_disable() - This api disables endpoint + * @regs: Reference to structure pch_udc_ep_regs + */ +static void pch_udc_ep_disable(struct pch_udc_ep *ep) +{ + if (ep->in) { + /* flush the fifo */ + pch_udc_ep_writel(ep, UDC_EPCTL_F, UDC_EPCTL_ADDR); + /* set NAK */ + pch_udc_ep_writel(ep, UDC_EPCTL_SNAK, UDC_EPCTL_ADDR); + pch_udc_ep_bit_set(ep, UDC_EPSTS_ADDR, UDC_EPSTS_IN); + } else { + /* set NAK */ + pch_udc_ep_writel(ep, UDC_EPCTL_SNAK, UDC_EPCTL_ADDR); + } + /* reset desc pointer */ + pch_udc_ep_writel(ep, 0, UDC_DESPTR_ADDR); +} + +/** + * pch_udc_wait_ep_stall() - Wait EP stall. + * @dev: Reference to pch_udc_dev structure + */ +static void pch_udc_wait_ep_stall(struct pch_udc_ep *ep) +{ + unsigned int count = 10000; + + /* Wait till idle */ + while ((pch_udc_read_ep_control(ep) & UDC_EPCTL_S) && --count) + udelay(5); + if (!count) + dev_err(&ep->dev->pdev->dev, "%s: wait error\n", __func__); +} + +/** + * pch_udc_init() - This API initializes usb device controller + * @dev: Rreference to pch_udc_regs structure + */ +static void pch_udc_init(struct pch_udc_dev *dev) +{ + if (NULL == dev) { + pr_err("%s: Invalid address\n", __func__); + return; + } + /* Soft Reset and Reset PHY */ + pch_udc_writel(dev, UDC_SRST, UDC_SRST_ADDR); + pch_udc_writel(dev, UDC_SRST | UDC_PSRST, UDC_SRST_ADDR); + mdelay(1); + pch_udc_writel(dev, UDC_SRST, UDC_SRST_ADDR); + pch_udc_writel(dev, 0x00, UDC_SRST_ADDR); + mdelay(1); + /* mask and clear all device interrupts */ + pch_udc_bit_set(dev, UDC_DEVIRQMSK_ADDR, UDC_DEVINT_MSK); + pch_udc_bit_set(dev, UDC_DEVIRQSTS_ADDR, UDC_DEVINT_MSK); + + /* mask and clear all ep interrupts */ + pch_udc_bit_set(dev, UDC_EPIRQMSK_ADDR, UDC_EPINT_MSK_DISABLE_ALL); + pch_udc_bit_set(dev, UDC_EPIRQSTS_ADDR, UDC_EPINT_MSK_DISABLE_ALL); + + /* enable dynamic CSR programmingi, self powered and device speed */ + if (speed_fs) + pch_udc_bit_set(dev, UDC_DEVCFG_ADDR, UDC_DEVCFG_CSR_PRG | + UDC_DEVCFG_SP | UDC_DEVCFG_SPD_FS); + else /* defaul high speed */ + pch_udc_bit_set(dev, UDC_DEVCFG_ADDR, UDC_DEVCFG_CSR_PRG | + UDC_DEVCFG_SP | UDC_DEVCFG_SPD_HS); + pch_udc_bit_set(dev, UDC_DEVCTL_ADDR, + (PCH_UDC_THLEN << UDC_DEVCTL_THLEN_SHIFT) | + (PCH_UDC_BRLEN << UDC_DEVCTL_BRLEN_SHIFT) | + UDC_DEVCTL_MODE | UDC_DEVCTL_BREN | + UDC_DEVCTL_THE); +} + +/** + * pch_udc_exit() - This API exit usb device controller + * @dev: Reference to pch_udc_regs structure + */ +static void pch_udc_exit(struct pch_udc_dev *dev) +{ + /* mask all device interrupts */ + pch_udc_bit_set(dev, UDC_DEVIRQMSK_ADDR, UDC_DEVINT_MSK); + /* mask all ep interrupts */ + pch_udc_bit_set(dev, UDC_EPIRQMSK_ADDR, UDC_EPINT_MSK_DISABLE_ALL); + /* put device in disconnected state */ + pch_udc_set_disconnect(dev); +} + +/** + * pch_udc_pcd_get_frame() - This API is invoked to get the current frame number + * @gadget: Reference to the gadget driver + * + * Return codes: + * 0: Success + * -EINVAL: If the gadget passed is NULL + */ +static int pch_udc_pcd_get_frame(struct usb_gadget *gadget) +{ + struct pch_udc_dev *dev; + + if (!gadget) + return -EINVAL; + dev = container_of(gadget, struct pch_udc_dev, gadget); + return pch_udc_get_frame(dev); +} + +/** + * pch_udc_pcd_wakeup() - This API is invoked to initiate a remote wakeup + * @gadget: Reference to the gadget driver + * + * Return codes: + * 0: Success + * -EINVAL: If the gadget passed is NULL + */ +static int pch_udc_pcd_wakeup(struct usb_gadget *gadget) +{ + struct pch_udc_dev *dev; + unsigned long flags; + + if (!gadget) + return -EINVAL; + dev = container_of(gadget, struct pch_udc_dev, gadget); + spin_lock_irqsave(&dev->lock, flags); + pch_udc_rmt_wakeup(dev); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} + +/** + * pch_udc_pcd_selfpowered() - This API is invoked to specify whether the device + * is self powered or not + * @gadget: Reference to the gadget driver + * @value: Specifies self powered or not + * + * Return codes: + * 0: Success + * -EINVAL: If the gadget passed is NULL + */ +static int pch_udc_pcd_selfpowered(struct usb_gadget *gadget, int value) +{ + struct pch_udc_dev *dev; + + if (!gadget) + return -EINVAL; + dev = container_of(gadget, struct pch_udc_dev, gadget); + if (value) + pch_udc_set_selfpowered(dev); + else + pch_udc_clear_selfpowered(dev); + return 0; +} + +/** + * pch_udc_pcd_pullup() - This API is invoked to make the device + * visible/invisible to the host + * @gadget: Reference to the gadget driver + * @is_on: Specifies whether the pull up is made active or inactive + * + * Return codes: + * 0: Success + * -EINVAL: If the gadget passed is NULL + */ +static int pch_udc_pcd_pullup(struct usb_gadget *gadget, int is_on) +{ + struct pch_udc_dev *dev; + + if (!gadget) + return -EINVAL; + dev = container_of(gadget, struct pch_udc_dev, gadget); + if (is_on) { + pch_udc_reconnect(dev); + } else { + if (dev->driver && dev->driver->disconnect) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + pch_udc_set_disconnect(dev); + } + + return 0; +} + +/** + * pch_udc_pcd_vbus_session() - This API is used by a driver for an external + * transceiver (or GPIO) that + * detects a VBUS power session starting/ending + * @gadget: Reference to the gadget driver + * @is_active: specifies whether the session is starting or ending + * + * Return codes: + * 0: Success + * -EINVAL: If the gadget passed is NULL + */ +static int pch_udc_pcd_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct pch_udc_dev *dev; + + if (!gadget) + return -EINVAL; + dev = container_of(gadget, struct pch_udc_dev, gadget); + pch_udc_vbus_session(dev, is_active); + return 0; +} + +/** + * pch_udc_pcd_vbus_draw() - This API is used by gadget drivers during + * SET_CONFIGURATION calls to + * specify how much power the device can consume + * @gadget: Reference to the gadget driver + * @mA: specifies the current limit in 2mA unit + * + * Return codes: + * -EINVAL: If the gadget passed is NULL + * -EOPNOTSUPP: + */ +static int pch_udc_pcd_vbus_draw(struct usb_gadget *gadget, unsigned int mA) +{ + return -EOPNOTSUPP; +} + +static int pch_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int pch_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static const struct usb_gadget_ops pch_udc_ops = { + .get_frame = pch_udc_pcd_get_frame, + .wakeup = pch_udc_pcd_wakeup, + .set_selfpowered = pch_udc_pcd_selfpowered, + .pullup = pch_udc_pcd_pullup, + .vbus_session = pch_udc_pcd_vbus_session, + .vbus_draw = pch_udc_pcd_vbus_draw, + .udc_start = pch_udc_start, + .udc_stop = pch_udc_stop, +}; + +/** + * pch_vbus_gpio_get_value() - This API gets value of GPIO port as VBUS status. + * @dev: Reference to the driver structure + * + * Return value: + * 1: VBUS is high + * 0: VBUS is low + * -1: It is not enable to detect VBUS using GPIO + */ +static int pch_vbus_gpio_get_value(struct pch_udc_dev *dev) +{ + int vbus = 0; + + if (dev->vbus_gpio.port) + vbus = gpio_get_value(dev->vbus_gpio.port) ? 1 : 0; + else + vbus = -1; + + return vbus; +} + +/** + * pch_vbus_gpio_work_fall() - This API keeps watch on VBUS becoming Low. + * If VBUS is Low, disconnect is processed + * @irq_work: Structure for WorkQueue + * + */ +static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) +{ + struct pch_vbus_gpio_data *vbus_gpio = container_of(irq_work, + struct pch_vbus_gpio_data, irq_work_fall); + struct pch_udc_dev *dev = + container_of(vbus_gpio, struct pch_udc_dev, vbus_gpio); + int vbus_saved = -1; + int vbus; + int count; + + if (!dev->vbus_gpio.port) + return; + + for (count = 0; count < (PCH_VBUS_PERIOD / PCH_VBUS_INTERVAL); + count++) { + vbus = pch_vbus_gpio_get_value(dev); + + if ((vbus_saved == vbus) && (vbus == 0)) { + dev_dbg(&dev->pdev->dev, "VBUS fell"); + if (dev->driver + && dev->driver->disconnect) { + dev->driver->disconnect( + &dev->gadget); + } + if (dev->vbus_gpio.intr) + pch_udc_init(dev); + else + pch_udc_reconnect(dev); + return; + } + vbus_saved = vbus; + mdelay(PCH_VBUS_INTERVAL); + } +} + +/** + * pch_vbus_gpio_work_rise() - This API checks VBUS is High. + * If VBUS is High, connect is processed + * @irq_work: Structure for WorkQueue + * + */ +static void pch_vbus_gpio_work_rise(struct work_struct *irq_work) +{ + struct pch_vbus_gpio_data *vbus_gpio = container_of(irq_work, + struct pch_vbus_gpio_data, irq_work_rise); + struct pch_udc_dev *dev = + container_of(vbus_gpio, struct pch_udc_dev, vbus_gpio); + int vbus; + + if (!dev->vbus_gpio.port) + return; + + mdelay(PCH_VBUS_INTERVAL); + vbus = pch_vbus_gpio_get_value(dev); + + if (vbus == 1) { + dev_dbg(&dev->pdev->dev, "VBUS rose"); + pch_udc_reconnect(dev); + return; + } +} + +/** + * pch_vbus_gpio_irq() - IRQ handler for GPIO intrerrupt for changing VBUS + * @irq: Interrupt request number + * @dev: Reference to the device structure + * + * Return codes: + * 0: Success + * -EINVAL: GPIO port is invalid or can't be initialized. + */ +static irqreturn_t pch_vbus_gpio_irq(int irq, void *data) +{ + struct pch_udc_dev *dev = (struct pch_udc_dev *)data; + + if (!dev->vbus_gpio.port || !dev->vbus_gpio.intr) + return IRQ_NONE; + + if (pch_vbus_gpio_get_value(dev)) + schedule_work(&dev->vbus_gpio.irq_work_rise); + else + schedule_work(&dev->vbus_gpio.irq_work_fall); + + return IRQ_HANDLED; +} + +/** + * pch_vbus_gpio_init() - This API initializes GPIO port detecting VBUS. + * @dev: Reference to the driver structure + * @vbus_gpio Number of GPIO port to detect gpio + * + * Return codes: + * 0: Success + * -EINVAL: GPIO port is invalid or can't be initialized. + */ +static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) +{ + int err; + int irq_num = 0; + + dev->vbus_gpio.port = 0; + dev->vbus_gpio.intr = 0; + + if (vbus_gpio_port <= -1) + return -EINVAL; + + err = gpio_is_valid(vbus_gpio_port); + if (!err) { + pr_err("%s: gpio port %d is invalid\n", + __func__, vbus_gpio_port); + return -EINVAL; + } + + err = gpio_request(vbus_gpio_port, "pch_vbus"); + if (err) { + pr_err("%s: can't request gpio port %d, err: %d\n", + __func__, vbus_gpio_port, err); + return -EINVAL; + } + + dev->vbus_gpio.port = vbus_gpio_port; + gpio_direction_input(vbus_gpio_port); + INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); + + irq_num = gpio_to_irq(vbus_gpio_port); + if (irq_num > 0) { + irq_set_irq_type(irq_num, IRQ_TYPE_EDGE_BOTH); + err = request_irq(irq_num, pch_vbus_gpio_irq, 0, + "vbus_detect", dev); + if (!err) { + dev->vbus_gpio.intr = irq_num; + INIT_WORK(&dev->vbus_gpio.irq_work_rise, + pch_vbus_gpio_work_rise); + } else { + pr_err("%s: can't request irq %d, err: %d\n", + __func__, irq_num, err); + } + } + + return 0; +} + +/** + * pch_vbus_gpio_free() - This API frees resources of GPIO port + * @dev: Reference to the driver structure + */ +static void pch_vbus_gpio_free(struct pch_udc_dev *dev) +{ + if (dev->vbus_gpio.intr) + free_irq(dev->vbus_gpio.intr, dev); + + if (dev->vbus_gpio.port) + gpio_free(dev->vbus_gpio.port); +} + +/** + * complete_req() - This API is invoked from the driver when processing + * of a request is complete + * @ep: Reference to the endpoint structure + * @req: Reference to the request structure + * @status: Indicates the success/failure of completion + */ +static void complete_req(struct pch_udc_ep *ep, struct pch_udc_request *req, + int status) + __releases(&dev->lock) + __acquires(&dev->lock) +{ + struct pch_udc_dev *dev; + unsigned halted = ep->halted; + + list_del_init(&req->queue); + + /* set new status if pending */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + dev = ep->dev; + if (req->dma_mapped) { + if (req->dma == DMA_ADDR_INVALID) { + if (ep->in) + dma_unmap_single(&dev->pdev->dev, req->req.dma, + req->req.length, + DMA_TO_DEVICE); + else + dma_unmap_single(&dev->pdev->dev, req->req.dma, + req->req.length, + DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + } else { + if (ep->in) + dma_unmap_single(&dev->pdev->dev, req->dma, + req->req.length, + DMA_TO_DEVICE); + else { + dma_unmap_single(&dev->pdev->dev, req->dma, + req->req.length, + DMA_FROM_DEVICE); + memcpy(req->req.buf, req->buf, req->req.length); + } + kfree(req->buf); + req->dma = DMA_ADDR_INVALID; + } + req->dma_mapped = 0; + } + ep->halted = 1; + spin_unlock(&dev->lock); + if (!ep->in) + pch_udc_ep_clear_rrdy(ep); + req->req.complete(&ep->ep, &req->req); + spin_lock(&dev->lock); + ep->halted = halted; +} + +/** + * empty_req_queue() - This API empties the request queue of an endpoint + * @ep: Reference to the endpoint structure + */ +static void empty_req_queue(struct pch_udc_ep *ep) +{ + struct pch_udc_request *req; + + ep->halted = 1; + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct pch_udc_request, queue); + complete_req(ep, req, -ESHUTDOWN); /* Remove from list */ + } +} + +/** + * pch_udc_free_dma_chain() - This function frees the DMA chain created + * for the request + * @dev Reference to the driver structure + * @req Reference to the request to be freed + * + * Return codes: + * 0: Success + */ +static void pch_udc_free_dma_chain(struct pch_udc_dev *dev, + struct pch_udc_request *req) +{ + struct pch_udc_data_dma_desc *td = req->td_data; + unsigned i = req->chain_len; + + dma_addr_t addr2; + dma_addr_t addr = (dma_addr_t)td->next; + td->next = 0x00; + for (; i > 1; --i) { + /* do not free first desc., will be done by free for request */ + td = phys_to_virt(addr); + addr2 = (dma_addr_t)td->next; + pci_pool_free(dev->data_requests, td, addr); + td->next = 0x00; + addr = addr2; + } + req->chain_len = 1; +} + +/** + * pch_udc_create_dma_chain() - This function creates or reinitializes + * a DMA chain + * @ep: Reference to the endpoint structure + * @req: Reference to the request + * @buf_len: The buffer length + * @gfp_flags: Flags to be used while mapping the data buffer + * + * Return codes: + * 0: success, + * -ENOMEM: pci_pool_alloc invocation fails + */ +static int pch_udc_create_dma_chain(struct pch_udc_ep *ep, + struct pch_udc_request *req, + unsigned long buf_len, + gfp_t gfp_flags) +{ + struct pch_udc_data_dma_desc *td = req->td_data, *last; + unsigned long bytes = req->req.length, i = 0; + dma_addr_t dma_addr; + unsigned len = 1; + + if (req->chain_len > 1) + pch_udc_free_dma_chain(ep->dev, req); + + if (req->dma == DMA_ADDR_INVALID) + td->dataptr = req->req.dma; + else + td->dataptr = req->dma; + + td->status = PCH_UDC_BS_HST_BSY; + for (; ; bytes -= buf_len, ++len) { + td->status = PCH_UDC_BS_HST_BSY | min(buf_len, bytes); + if (bytes <= buf_len) + break; + last = td; + td = pci_pool_alloc(ep->dev->data_requests, gfp_flags, + &dma_addr); + if (!td) + goto nomem; + i += buf_len; + td->dataptr = req->td_data->dataptr + i; + last->next = dma_addr; + } + + req->td_data_last = td; + td->status |= PCH_UDC_DMA_LAST; + td->next = req->td_data_phys; + req->chain_len = len; + return 0; + +nomem: + if (len > 1) { + req->chain_len = len; + pch_udc_free_dma_chain(ep->dev, req); + } + req->chain_len = 1; + return -ENOMEM; +} + +/** + * prepare_dma() - This function creates and initializes the DMA chain + * for the request + * @ep: Reference to the endpoint structure + * @req: Reference to the request + * @gfp: Flag to be used while mapping the data buffer + * + * Return codes: + * 0: Success + * Other 0: linux error number on failure + */ +static int prepare_dma(struct pch_udc_ep *ep, struct pch_udc_request *req, + gfp_t gfp) +{ + int retval; + + /* Allocate and create a DMA chain */ + retval = pch_udc_create_dma_chain(ep, req, ep->ep.maxpacket, gfp); + if (retval) { + pr_err("%s: could not create DMA chain:%d\n", __func__, retval); + return retval; + } + if (ep->in) + req->td_data->status = (req->td_data->status & + ~PCH_UDC_BUFF_STS) | PCH_UDC_BS_HST_RDY; + return 0; +} + +/** + * process_zlp() - This function process zero length packets + * from the gadget driver + * @ep: Reference to the endpoint structure + * @req: Reference to the request + */ +static void process_zlp(struct pch_udc_ep *ep, struct pch_udc_request *req) +{ + struct pch_udc_dev *dev = ep->dev; + + /* IN zlp's are handled by hardware */ + complete_req(ep, req, 0); + + /* if set_config or set_intf is waiting for ack by zlp + * then set CSR_DONE + */ + if (dev->set_cfg_not_acked) { + pch_udc_set_csr_done(dev); + dev->set_cfg_not_acked = 0; + } + /* setup command is ACK'ed now by zlp */ + if (!dev->stall && dev->waiting_zlp_ack) { + pch_udc_ep_clear_nak(&(dev->ep[UDC_EP0IN_IDX])); + dev->waiting_zlp_ack = 0; + } +} + +/** + * pch_udc_start_rxrequest() - This function starts the receive requirement. + * @ep: Reference to the endpoint structure + * @req: Reference to the request structure + */ +static void pch_udc_start_rxrequest(struct pch_udc_ep *ep, + struct pch_udc_request *req) +{ + struct pch_udc_data_dma_desc *td_data; + + pch_udc_clear_dma(ep->dev, DMA_DIR_RX); + td_data = req->td_data; + /* Set the status bits for all descriptors */ + while (1) { + td_data->status = (td_data->status & ~PCH_UDC_BUFF_STS) | + PCH_UDC_BS_HST_RDY; + if ((td_data->status & PCH_UDC_DMA_LAST) == PCH_UDC_DMA_LAST) + break; + td_data = phys_to_virt(td_data->next); + } + /* Write the descriptor pointer */ + pch_udc_ep_set_ddptr(ep, req->td_data_phys); + req->dma_going = 1; + pch_udc_enable_ep_interrupts(ep->dev, UDC_EPINT_OUT_EP0 << ep->num); + pch_udc_set_dma(ep->dev, DMA_DIR_RX); + pch_udc_ep_clear_nak(ep); + pch_udc_ep_set_rrdy(ep); +} + +/** + * pch_udc_pcd_ep_enable() - This API enables the endpoint. It is called + * from gadget driver + * @usbep: Reference to the USB endpoint structure + * @desc: Reference to the USB endpoint descriptor structure + * + * Return codes: + * 0: Success + * -EINVAL: + * -ESHUTDOWN: + */ +static int pch_udc_pcd_ep_enable(struct usb_ep *usbep, + const struct usb_endpoint_descriptor *desc) +{ + struct pch_udc_ep *ep; + struct pch_udc_dev *dev; + unsigned long iflags; + + if (!usbep || (usbep->name == ep0_string) || !desc || + (desc->bDescriptorType != USB_DT_ENDPOINT) || !desc->wMaxPacketSize) + return -EINVAL; + + ep = container_of(usbep, struct pch_udc_ep, ep); + dev = ep->dev; + if (!dev->driver || (dev->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + spin_lock_irqsave(&dev->lock, iflags); + ep->ep.desc = desc; + ep->halted = 0; + pch_udc_ep_enable(ep, &ep->dev->cfg_data, desc); + ep->ep.maxpacket = usb_endpoint_maxp(desc); + pch_udc_enable_ep_interrupts(ep->dev, PCH_UDC_EPINT(ep->in, ep->num)); + spin_unlock_irqrestore(&dev->lock, iflags); + return 0; +} + +/** + * pch_udc_pcd_ep_disable() - This API disables endpoint and is called + * from gadget driver + * @usbep Reference to the USB endpoint structure + * + * Return codes: + * 0: Success + * -EINVAL: + */ +static int pch_udc_pcd_ep_disable(struct usb_ep *usbep) +{ + struct pch_udc_ep *ep; + struct pch_udc_dev *dev; + unsigned long iflags; + + if (!usbep) + return -EINVAL; + + ep = container_of(usbep, struct pch_udc_ep, ep); + dev = ep->dev; + if ((usbep->name == ep0_string) || !ep->ep.desc) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, iflags); + empty_req_queue(ep); + ep->halted = 1; + pch_udc_ep_disable(ep); + pch_udc_disable_ep_interrupts(ep->dev, PCH_UDC_EPINT(ep->in, ep->num)); + ep->ep.desc = NULL; + INIT_LIST_HEAD(&ep->queue); + spin_unlock_irqrestore(&ep->dev->lock, iflags); + return 0; +} + +/** + * pch_udc_alloc_request() - This function allocates request structure. + * It is called by gadget driver + * @usbep: Reference to the USB endpoint structure + * @gfp: Flag to be used while allocating memory + * + * Return codes: + * NULL: Failure + * Allocated address: Success + */ +static struct usb_request *pch_udc_alloc_request(struct usb_ep *usbep, + gfp_t gfp) +{ + struct pch_udc_request *req; + struct pch_udc_ep *ep; + struct pch_udc_data_dma_desc *dma_desc; + struct pch_udc_dev *dev; + + if (!usbep) + return NULL; + ep = container_of(usbep, struct pch_udc_ep, ep); + dev = ep->dev; + req = kzalloc(sizeof *req, gfp); + if (!req) + return NULL; + req->req.dma = DMA_ADDR_INVALID; + req->dma = DMA_ADDR_INVALID; + INIT_LIST_HEAD(&req->queue); + if (!ep->dev->dma_addr) + return &req->req; + /* ep0 in requests are allocated from data pool here */ + dma_desc = pci_pool_alloc(ep->dev->data_requests, gfp, + &req->td_data_phys); + if (NULL == dma_desc) { + kfree(req); + return NULL; + } + /* prevent from using desc. - set HOST BUSY */ + dma_desc->status |= PCH_UDC_BS_HST_BSY; + dma_desc->dataptr = __constant_cpu_to_le32(DMA_ADDR_INVALID); + req->td_data = dma_desc; + req->td_data_last = dma_desc; + req->chain_len = 1; + return &req->req; +} + +/** + * pch_udc_free_request() - This function frees request structure. + * It is called by gadget driver + * @usbep: Reference to the USB endpoint structure + * @usbreq: Reference to the USB request + */ +static void pch_udc_free_request(struct usb_ep *usbep, + struct usb_request *usbreq) +{ + struct pch_udc_ep *ep; + struct pch_udc_request *req; + struct pch_udc_dev *dev; + + if (!usbep || !usbreq) + return; + ep = container_of(usbep, struct pch_udc_ep, ep); + req = container_of(usbreq, struct pch_udc_request, req); + dev = ep->dev; + if (!list_empty(&req->queue)) + dev_err(&dev->pdev->dev, "%s: %s req=0x%p queue not empty\n", + __func__, usbep->name, req); + if (req->td_data != NULL) { + if (req->chain_len > 1) + pch_udc_free_dma_chain(ep->dev, req); + pci_pool_free(ep->dev->data_requests, req->td_data, + req->td_data_phys); + } + kfree(req); +} + +/** + * pch_udc_pcd_queue() - This function queues a request packet. It is called + * by gadget driver + * @usbep: Reference to the USB endpoint structure + * @usbreq: Reference to the USB request + * @gfp: Flag to be used while mapping the data buffer + * + * Return codes: + * 0: Success + * linux error number: Failure + */ +static int pch_udc_pcd_queue(struct usb_ep *usbep, struct usb_request *usbreq, + gfp_t gfp) +{ + int retval = 0; + struct pch_udc_ep *ep; + struct pch_udc_dev *dev; + struct pch_udc_request *req; + unsigned long iflags; + + if (!usbep || !usbreq || !usbreq->complete || !usbreq->buf) + return -EINVAL; + ep = container_of(usbep, struct pch_udc_ep, ep); + dev = ep->dev; + if (!ep->ep.desc && ep->num) + return -EINVAL; + req = container_of(usbreq, struct pch_udc_request, req); + if (!list_empty(&req->queue)) + return -EINVAL; + if (!dev->driver || (dev->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + spin_lock_irqsave(&dev->lock, iflags); + /* map the buffer for dma */ + if (usbreq->length && + ((usbreq->dma == DMA_ADDR_INVALID) || !usbreq->dma)) { + if (!((unsigned long)(usbreq->buf) & 0x03)) { + if (ep->in) + usbreq->dma = dma_map_single(&dev->pdev->dev, + usbreq->buf, + usbreq->length, + DMA_TO_DEVICE); + else + usbreq->dma = dma_map_single(&dev->pdev->dev, + usbreq->buf, + usbreq->length, + DMA_FROM_DEVICE); + } else { + req->buf = kzalloc(usbreq->length, GFP_ATOMIC); + if (!req->buf) { + retval = -ENOMEM; + goto probe_end; + } + if (ep->in) { + memcpy(req->buf, usbreq->buf, usbreq->length); + req->dma = dma_map_single(&dev->pdev->dev, + req->buf, + usbreq->length, + DMA_TO_DEVICE); + } else + req->dma = dma_map_single(&dev->pdev->dev, + req->buf, + usbreq->length, + DMA_FROM_DEVICE); + } + req->dma_mapped = 1; + } + if (usbreq->length > 0) { + retval = prepare_dma(ep, req, GFP_ATOMIC); + if (retval) + goto probe_end; + } + usbreq->actual = 0; + usbreq->status = -EINPROGRESS; + req->dma_done = 0; + if (list_empty(&ep->queue) && !ep->halted) { + /* no pending transfer, so start this req */ + if (!usbreq->length) { + process_zlp(ep, req); + retval = 0; + goto probe_end; + } + if (!ep->in) { + pch_udc_start_rxrequest(ep, req); + } else { + /* + * For IN trfr the descriptors will be programmed and + * P bit will be set when + * we get an IN token + */ + pch_udc_wait_ep_stall(ep); + pch_udc_ep_clear_nak(ep); + pch_udc_enable_ep_interrupts(ep->dev, (1 << ep->num)); + } + } + /* Now add this request to the ep's pending requests */ + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + +probe_end: + spin_unlock_irqrestore(&dev->lock, iflags); + return retval; +} + +/** + * pch_udc_pcd_dequeue() - This function de-queues a request packet. + * It is called by gadget driver + * @usbep: Reference to the USB endpoint structure + * @usbreq: Reference to the USB request + * + * Return codes: + * 0: Success + * linux error number: Failure + */ +static int pch_udc_pcd_dequeue(struct usb_ep *usbep, + struct usb_request *usbreq) +{ + struct pch_udc_ep *ep; + struct pch_udc_request *req; + struct pch_udc_dev *dev; + unsigned long flags; + int ret = -EINVAL; + + ep = container_of(usbep, struct pch_udc_ep, ep); + dev = ep->dev; + if (!usbep || !usbreq || (!ep->ep.desc && ep->num)) + return ret; + req = container_of(usbreq, struct pch_udc_request, req); + spin_lock_irqsave(&ep->dev->lock, flags); + /* make sure it's still queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == usbreq) { + pch_udc_ep_set_nak(ep); + if (!list_empty(&req->queue)) + complete_req(ep, req, -ECONNRESET); + ret = 0; + break; + } + } + spin_unlock_irqrestore(&ep->dev->lock, flags); + return ret; +} + +/** + * pch_udc_pcd_set_halt() - This function Sets or clear the endpoint halt + * feature + * @usbep: Reference to the USB endpoint structure + * @halt: Specifies whether to set or clear the feature + * + * Return codes: + * 0: Success + * linux error number: Failure + */ +static int pch_udc_pcd_set_halt(struct usb_ep *usbep, int halt) +{ + struct pch_udc_ep *ep; + struct pch_udc_dev *dev; + unsigned long iflags; + int ret; + + if (!usbep) + return -EINVAL; + ep = container_of(usbep, struct pch_udc_ep, ep); + dev = ep->dev; + if (!ep->ep.desc && !ep->num) + return -EINVAL; + if (!ep->dev->driver || (ep->dev->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + spin_lock_irqsave(&udc_stall_spinlock, iflags); + if (list_empty(&ep->queue)) { + if (halt) { + if (ep->num == PCH_UDC_EP0) + ep->dev->stall = 1; + pch_udc_ep_set_stall(ep); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, + ep->num)); + } else { + pch_udc_ep_clear_stall(ep); + } + ret = 0; + } else { + ret = -EAGAIN; + } + spin_unlock_irqrestore(&udc_stall_spinlock, iflags); + return ret; +} + +/** + * pch_udc_pcd_set_wedge() - This function Sets or clear the endpoint + * halt feature + * @usbep: Reference to the USB endpoint structure + * @halt: Specifies whether to set or clear the feature + * + * Return codes: + * 0: Success + * linux error number: Failure + */ +static int pch_udc_pcd_set_wedge(struct usb_ep *usbep) +{ + struct pch_udc_ep *ep; + struct pch_udc_dev *dev; + unsigned long iflags; + int ret; + + if (!usbep) + return -EINVAL; + ep = container_of(usbep, struct pch_udc_ep, ep); + dev = ep->dev; + if (!ep->ep.desc && !ep->num) + return -EINVAL; + if (!ep->dev->driver || (ep->dev->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + spin_lock_irqsave(&udc_stall_spinlock, iflags); + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + } else { + if (ep->num == PCH_UDC_EP0) + ep->dev->stall = 1; + pch_udc_ep_set_stall(ep); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + ep->dev->prot_stall = 1; + ret = 0; + } + spin_unlock_irqrestore(&udc_stall_spinlock, iflags); + return ret; +} + +/** + * pch_udc_pcd_fifo_flush() - This function Flush the FIFO of specified endpoint + * @usbep: Reference to the USB endpoint structure + */ +static void pch_udc_pcd_fifo_flush(struct usb_ep *usbep) +{ + struct pch_udc_ep *ep; + + if (!usbep) + return; + + ep = container_of(usbep, struct pch_udc_ep, ep); + if (ep->ep.desc || !ep->num) + pch_udc_ep_fifo_flush(ep, ep->in); +} + +static const struct usb_ep_ops pch_udc_ep_ops = { + .enable = pch_udc_pcd_ep_enable, + .disable = pch_udc_pcd_ep_disable, + .alloc_request = pch_udc_alloc_request, + .free_request = pch_udc_free_request, + .queue = pch_udc_pcd_queue, + .dequeue = pch_udc_pcd_dequeue, + .set_halt = pch_udc_pcd_set_halt, + .set_wedge = pch_udc_pcd_set_wedge, + .fifo_status = NULL, + .fifo_flush = pch_udc_pcd_fifo_flush, +}; + +/** + * pch_udc_init_setup_buff() - This function initializes the SETUP buffer + * @td_stp: Reference to the SETP buffer structure + */ +static void pch_udc_init_setup_buff(struct pch_udc_stp_dma_desc *td_stp) +{ + static u32 pky_marker; + + if (!td_stp) + return; + td_stp->reserved = ++pky_marker; + memset(&td_stp->request, 0xFF, sizeof td_stp->request); + td_stp->status = PCH_UDC_BS_HST_RDY; +} + +/** + * pch_udc_start_next_txrequest() - This function starts + * the next transmission requirement + * @ep: Reference to the endpoint structure + */ +static void pch_udc_start_next_txrequest(struct pch_udc_ep *ep) +{ + struct pch_udc_request *req; + struct pch_udc_data_dma_desc *td_data; + + if (pch_udc_read_ep_control(ep) & UDC_EPCTL_P) + return; + + if (list_empty(&ep->queue)) + return; + + /* next request */ + req = list_entry(ep->queue.next, struct pch_udc_request, queue); + if (req->dma_going) + return; + if (!req->td_data) + return; + pch_udc_wait_ep_stall(ep); + req->dma_going = 1; + pch_udc_ep_set_ddptr(ep, 0); + td_data = req->td_data; + while (1) { + td_data->status = (td_data->status & ~PCH_UDC_BUFF_STS) | + PCH_UDC_BS_HST_RDY; + if ((td_data->status & PCH_UDC_DMA_LAST) == PCH_UDC_DMA_LAST) + break; + td_data = phys_to_virt(td_data->next); + } + pch_udc_ep_set_ddptr(ep, req->td_data_phys); + pch_udc_set_dma(ep->dev, DMA_DIR_TX); + pch_udc_ep_set_pd(ep); + pch_udc_enable_ep_interrupts(ep->dev, PCH_UDC_EPINT(ep->in, ep->num)); + pch_udc_ep_clear_nak(ep); +} + +/** + * pch_udc_complete_transfer() - This function completes a transfer + * @ep: Reference to the endpoint structure + */ +static void pch_udc_complete_transfer(struct pch_udc_ep *ep) +{ + struct pch_udc_request *req; + struct pch_udc_dev *dev = ep->dev; + + if (list_empty(&ep->queue)) + return; + req = list_entry(ep->queue.next, struct pch_udc_request, queue); + if ((req->td_data_last->status & PCH_UDC_BUFF_STS) != + PCH_UDC_BS_DMA_DONE) + return; + if ((req->td_data_last->status & PCH_UDC_RXTX_STS) != + PCH_UDC_RTS_SUCC) { + dev_err(&dev->pdev->dev, "Invalid RXTX status (0x%08x) " + "epstatus=0x%08x\n", + (req->td_data_last->status & PCH_UDC_RXTX_STS), + (int)(ep->epsts)); + return; + } + + req->req.actual = req->req.length; + req->td_data_last->status = PCH_UDC_BS_HST_BSY | PCH_UDC_DMA_LAST; + req->td_data->status = PCH_UDC_BS_HST_BSY | PCH_UDC_DMA_LAST; + complete_req(ep, req, 0); + req->dma_going = 0; + if (!list_empty(&ep->queue)) { + pch_udc_wait_ep_stall(ep); + pch_udc_ep_clear_nak(ep); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + } else { + pch_udc_disable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + } +} + +/** + * pch_udc_complete_receiver() - This function completes a receiver + * @ep: Reference to the endpoint structure + */ +static void pch_udc_complete_receiver(struct pch_udc_ep *ep) +{ + struct pch_udc_request *req; + struct pch_udc_dev *dev = ep->dev; + unsigned int count; + struct pch_udc_data_dma_desc *td; + dma_addr_t addr; + + if (list_empty(&ep->queue)) + return; + /* next request */ + req = list_entry(ep->queue.next, struct pch_udc_request, queue); + pch_udc_clear_dma(ep->dev, DMA_DIR_RX); + pch_udc_ep_set_ddptr(ep, 0); + if ((req->td_data_last->status & PCH_UDC_BUFF_STS) == + PCH_UDC_BS_DMA_DONE) + td = req->td_data_last; + else + td = req->td_data; + + while (1) { + if ((td->status & PCH_UDC_RXTX_STS) != PCH_UDC_RTS_SUCC) { + dev_err(&dev->pdev->dev, "Invalid RXTX status=0x%08x " + "epstatus=0x%08x\n", + (req->td_data->status & PCH_UDC_RXTX_STS), + (int)(ep->epsts)); + return; + } + if ((td->status & PCH_UDC_BUFF_STS) == PCH_UDC_BS_DMA_DONE) + if (td->status & PCH_UDC_DMA_LAST) { + count = td->status & PCH_UDC_RXTX_BYTES; + break; + } + if (td == req->td_data_last) { + dev_err(&dev->pdev->dev, "Not complete RX descriptor"); + return; + } + addr = (dma_addr_t)td->next; + td = phys_to_virt(addr); + } + /* on 64k packets the RXBYTES field is zero */ + if (!count && (req->req.length == UDC_DMA_MAXPACKET)) + count = UDC_DMA_MAXPACKET; + req->td_data->status |= PCH_UDC_DMA_LAST; + td->status |= PCH_UDC_BS_HST_BSY; + + req->dma_going = 0; + req->req.actual = count; + complete_req(ep, req, 0); + /* If there is a new/failed requests try that now */ + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct pch_udc_request, queue); + pch_udc_start_rxrequest(ep, req); + } +} + +/** + * pch_udc_svc_data_in() - This function process endpoint interrupts + * for IN endpoints + * @dev: Reference to the device structure + * @ep_num: Endpoint that generated the interrupt + */ +static void pch_udc_svc_data_in(struct pch_udc_dev *dev, int ep_num) +{ + u32 epsts; + struct pch_udc_ep *ep; + + ep = &dev->ep[UDC_EPIN_IDX(ep_num)]; + epsts = ep->epsts; + ep->epsts = 0; + + if (!(epsts & (UDC_EPSTS_IN | UDC_EPSTS_BNA | UDC_EPSTS_HE | + UDC_EPSTS_TDC | UDC_EPSTS_RCS | UDC_EPSTS_TXEMPTY | + UDC_EPSTS_RSS | UDC_EPSTS_XFERDONE))) + return; + if ((epsts & UDC_EPSTS_BNA)) + return; + if (epsts & UDC_EPSTS_HE) + return; + if (epsts & UDC_EPSTS_RSS) { + pch_udc_ep_set_stall(ep); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + } + if (epsts & UDC_EPSTS_RCS) { + if (!dev->prot_stall) { + pch_udc_ep_clear_stall(ep); + } else { + pch_udc_ep_set_stall(ep); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + } + } + if (epsts & UDC_EPSTS_TDC) + pch_udc_complete_transfer(ep); + /* On IN interrupt, provide data if we have any */ + if ((epsts & UDC_EPSTS_IN) && !(epsts & UDC_EPSTS_RSS) && + !(epsts & UDC_EPSTS_TDC) && !(epsts & UDC_EPSTS_TXEMPTY)) + pch_udc_start_next_txrequest(ep); +} + +/** + * pch_udc_svc_data_out() - Handles interrupts from OUT endpoint + * @dev: Reference to the device structure + * @ep_num: Endpoint that generated the interrupt + */ +static void pch_udc_svc_data_out(struct pch_udc_dev *dev, int ep_num) +{ + u32 epsts; + struct pch_udc_ep *ep; + struct pch_udc_request *req = NULL; + + ep = &dev->ep[UDC_EPOUT_IDX(ep_num)]; + epsts = ep->epsts; + ep->epsts = 0; + + if ((epsts & UDC_EPSTS_BNA) && (!list_empty(&ep->queue))) { + /* next request */ + req = list_entry(ep->queue.next, struct pch_udc_request, + queue); + if ((req->td_data_last->status & PCH_UDC_BUFF_STS) != + PCH_UDC_BS_DMA_DONE) { + if (!req->dma_going) + pch_udc_start_rxrequest(ep, req); + return; + } + } + if (epsts & UDC_EPSTS_HE) + return; + if (epsts & UDC_EPSTS_RSS) { + pch_udc_ep_set_stall(ep); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + } + if (epsts & UDC_EPSTS_RCS) { + if (!dev->prot_stall) { + pch_udc_ep_clear_stall(ep); + } else { + pch_udc_ep_set_stall(ep); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + } + } + if (((epsts & UDC_EPSTS_OUT_MASK) >> UDC_EPSTS_OUT_SHIFT) == + UDC_EPSTS_OUT_DATA) { + if (ep->dev->prot_stall == 1) { + pch_udc_ep_set_stall(ep); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + } else { + pch_udc_complete_receiver(ep); + } + } + if (list_empty(&ep->queue)) + pch_udc_set_dma(dev, DMA_DIR_RX); +} + +/** + * pch_udc_svc_control_in() - Handle Control IN endpoint interrupts + * @dev: Reference to the device structure + */ +static void pch_udc_svc_control_in(struct pch_udc_dev *dev) +{ + u32 epsts; + struct pch_udc_ep *ep; + struct pch_udc_ep *ep_out; + + ep = &dev->ep[UDC_EP0IN_IDX]; + ep_out = &dev->ep[UDC_EP0OUT_IDX]; + epsts = ep->epsts; + ep->epsts = 0; + + if (!(epsts & (UDC_EPSTS_IN | UDC_EPSTS_BNA | UDC_EPSTS_HE | + UDC_EPSTS_TDC | UDC_EPSTS_RCS | UDC_EPSTS_TXEMPTY | + UDC_EPSTS_XFERDONE))) + return; + if ((epsts & UDC_EPSTS_BNA)) + return; + if (epsts & UDC_EPSTS_HE) + return; + if ((epsts & UDC_EPSTS_TDC) && (!dev->stall)) { + pch_udc_complete_transfer(ep); + pch_udc_clear_dma(dev, DMA_DIR_RX); + ep_out->td_data->status = (ep_out->td_data->status & + ~PCH_UDC_BUFF_STS) | + PCH_UDC_BS_HST_RDY; + pch_udc_ep_clear_nak(ep_out); + pch_udc_set_dma(dev, DMA_DIR_RX); + pch_udc_ep_set_rrdy(ep_out); + } + /* On IN interrupt, provide data if we have any */ + if ((epsts & UDC_EPSTS_IN) && !(epsts & UDC_EPSTS_TDC) && + !(epsts & UDC_EPSTS_TXEMPTY)) + pch_udc_start_next_txrequest(ep); +} + +/** + * pch_udc_svc_control_out() - Routine that handle Control + * OUT endpoint interrupts + * @dev: Reference to the device structure + */ +static void pch_udc_svc_control_out(struct pch_udc_dev *dev) + __releases(&dev->lock) + __acquires(&dev->lock) +{ + u32 stat; + int setup_supported; + struct pch_udc_ep *ep; + + ep = &dev->ep[UDC_EP0OUT_IDX]; + stat = ep->epsts; + ep->epsts = 0; + + /* If setup data */ + if (((stat & UDC_EPSTS_OUT_MASK) >> UDC_EPSTS_OUT_SHIFT) == + UDC_EPSTS_OUT_SETUP) { + dev->stall = 0; + dev->ep[UDC_EP0IN_IDX].halted = 0; + dev->ep[UDC_EP0OUT_IDX].halted = 0; + dev->setup_data = ep->td_stp->request; + pch_udc_init_setup_buff(ep->td_stp); + pch_udc_clear_dma(dev, DMA_DIR_RX); + pch_udc_ep_fifo_flush(&(dev->ep[UDC_EP0IN_IDX]), + dev->ep[UDC_EP0IN_IDX].in); + if ((dev->setup_data.bRequestType & USB_DIR_IN)) + dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IDX].ep; + else /* OUT */ + dev->gadget.ep0 = &ep->ep; + spin_unlock(&dev->lock); + /* If Mass storage Reset */ + if ((dev->setup_data.bRequestType == 0x21) && + (dev->setup_data.bRequest == 0xFF)) + dev->prot_stall = 0; + /* call gadget with setup data received */ + setup_supported = dev->driver->setup(&dev->gadget, + &dev->setup_data); + spin_lock(&dev->lock); + + if (dev->setup_data.bRequestType & USB_DIR_IN) { + ep->td_data->status = (ep->td_data->status & + ~PCH_UDC_BUFF_STS) | + PCH_UDC_BS_HST_RDY; + pch_udc_ep_set_ddptr(ep, ep->td_data_phys); + } + /* ep0 in returns data on IN phase */ + if (setup_supported >= 0 && setup_supported < + UDC_EP0IN_MAX_PKT_SIZE) { + pch_udc_ep_clear_nak(&(dev->ep[UDC_EP0IN_IDX])); + /* Gadget would have queued a request when + * we called the setup */ + if (!(dev->setup_data.bRequestType & USB_DIR_IN)) { + pch_udc_set_dma(dev, DMA_DIR_RX); + pch_udc_ep_clear_nak(ep); + } + } else if (setup_supported < 0) { + /* if unsupported request, then stall */ + pch_udc_ep_set_stall(&(dev->ep[UDC_EP0IN_IDX])); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + dev->stall = 0; + pch_udc_set_dma(dev, DMA_DIR_RX); + } else { + dev->waiting_zlp_ack = 1; + } + } else if ((((stat & UDC_EPSTS_OUT_MASK) >> UDC_EPSTS_OUT_SHIFT) == + UDC_EPSTS_OUT_DATA) && !dev->stall) { + pch_udc_clear_dma(dev, DMA_DIR_RX); + pch_udc_ep_set_ddptr(ep, 0); + if (!list_empty(&ep->queue)) { + ep->epsts = stat; + pch_udc_svc_data_out(dev, PCH_UDC_EP0); + } + pch_udc_set_dma(dev, DMA_DIR_RX); + } + pch_udc_ep_set_rrdy(ep); +} + + +/** + * pch_udc_postsvc_epinters() - This function enables end point interrupts + * and clears NAK status + * @dev: Reference to the device structure + * @ep_num: End point number + */ +static void pch_udc_postsvc_epinters(struct pch_udc_dev *dev, int ep_num) +{ + struct pch_udc_ep *ep; + struct pch_udc_request *req; + + ep = &dev->ep[UDC_EPIN_IDX(ep_num)]; + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct pch_udc_request, queue); + pch_udc_enable_ep_interrupts(ep->dev, + PCH_UDC_EPINT(ep->in, ep->num)); + pch_udc_ep_clear_nak(ep); + } +} + +/** + * pch_udc_read_all_epstatus() - This function read all endpoint status + * @dev: Reference to the device structure + * @ep_intr: Status of endpoint interrupt + */ +static void pch_udc_read_all_epstatus(struct pch_udc_dev *dev, u32 ep_intr) +{ + int i; + struct pch_udc_ep *ep; + + for (i = 0; i < PCH_UDC_USED_EP_NUM; i++) { + /* IN */ + if (ep_intr & (0x1 << i)) { + ep = &dev->ep[UDC_EPIN_IDX(i)]; + ep->epsts = pch_udc_read_ep_status(ep); + pch_udc_clear_ep_status(ep, ep->epsts); + } + /* OUT */ + if (ep_intr & (0x10000 << i)) { + ep = &dev->ep[UDC_EPOUT_IDX(i)]; + ep->epsts = pch_udc_read_ep_status(ep); + pch_udc_clear_ep_status(ep, ep->epsts); + } + } +} + +/** + * pch_udc_activate_control_ep() - This function enables the control endpoints + * for traffic after a reset + * @dev: Reference to the device structure + */ +static void pch_udc_activate_control_ep(struct pch_udc_dev *dev) +{ + struct pch_udc_ep *ep; + u32 val; + + /* Setup the IN endpoint */ + ep = &dev->ep[UDC_EP0IN_IDX]; + pch_udc_clear_ep_control(ep); + pch_udc_ep_fifo_flush(ep, ep->in); + pch_udc_ep_set_bufsz(ep, UDC_EP0IN_BUFF_SIZE, ep->in); + pch_udc_ep_set_maxpkt(ep, UDC_EP0IN_MAX_PKT_SIZE); + /* Initialize the IN EP Descriptor */ + ep->td_data = NULL; + ep->td_stp = NULL; + ep->td_data_phys = 0; + ep->td_stp_phys = 0; + + /* Setup the OUT endpoint */ + ep = &dev->ep[UDC_EP0OUT_IDX]; + pch_udc_clear_ep_control(ep); + pch_udc_ep_fifo_flush(ep, ep->in); + pch_udc_ep_set_bufsz(ep, UDC_EP0OUT_BUFF_SIZE, ep->in); + pch_udc_ep_set_maxpkt(ep, UDC_EP0OUT_MAX_PKT_SIZE); + val = UDC_EP0OUT_MAX_PKT_SIZE << UDC_CSR_NE_MAX_PKT_SHIFT; + pch_udc_write_csr(ep->dev, val, UDC_EP0OUT_IDX); + + /* Initialize the SETUP buffer */ + pch_udc_init_setup_buff(ep->td_stp); + /* Write the pointer address of dma descriptor */ + pch_udc_ep_set_subptr(ep, ep->td_stp_phys); + /* Write the pointer address of Setup descriptor */ + pch_udc_ep_set_ddptr(ep, ep->td_data_phys); + + /* Initialize the dma descriptor */ + ep->td_data->status = PCH_UDC_DMA_LAST; + ep->td_data->dataptr = dev->dma_addr; + ep->td_data->next = ep->td_data_phys; + + pch_udc_ep_clear_nak(ep); +} + + +/** + * pch_udc_svc_ur_interrupt() - This function handles a USB reset interrupt + * @dev: Reference to driver structure + */ +static void pch_udc_svc_ur_interrupt(struct pch_udc_dev *dev) +{ + struct pch_udc_ep *ep; + int i; + + pch_udc_clear_dma(dev, DMA_DIR_TX); + pch_udc_clear_dma(dev, DMA_DIR_RX); + /* Mask all endpoint interrupts */ + pch_udc_disable_ep_interrupts(dev, UDC_EPINT_MSK_DISABLE_ALL); + /* clear all endpoint interrupts */ + pch_udc_write_ep_interrupts(dev, UDC_EPINT_MSK_DISABLE_ALL); + + for (i = 0; i < PCH_UDC_EP_NUM; i++) { + ep = &dev->ep[i]; + pch_udc_clear_ep_status(ep, UDC_EPSTS_ALL_CLR_MASK); + pch_udc_clear_ep_control(ep); + pch_udc_ep_set_ddptr(ep, 0); + pch_udc_write_csr(ep->dev, 0x00, i); + } + dev->stall = 0; + dev->prot_stall = 0; + dev->waiting_zlp_ack = 0; + dev->set_cfg_not_acked = 0; + + /* disable ep to empty req queue. Skip the control EP's */ + for (i = 0; i < (PCH_UDC_USED_EP_NUM*2); i++) { + ep = &dev->ep[i]; + pch_udc_ep_set_nak(ep); + pch_udc_ep_fifo_flush(ep, ep->in); + /* Complete request queue */ + empty_req_queue(ep); + } + if (dev->driver && dev->driver->disconnect) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } +} + +/** + * pch_udc_svc_enum_interrupt() - This function handles a USB speed enumeration + * done interrupt + * @dev: Reference to driver structure + */ +static void pch_udc_svc_enum_interrupt(struct pch_udc_dev *dev) +{ + u32 dev_stat, dev_speed; + u32 speed = USB_SPEED_FULL; + + dev_stat = pch_udc_read_device_status(dev); + dev_speed = (dev_stat & UDC_DEVSTS_ENUM_SPEED_MASK) >> + UDC_DEVSTS_ENUM_SPEED_SHIFT; + switch (dev_speed) { + case UDC_DEVSTS_ENUM_SPEED_HIGH: + speed = USB_SPEED_HIGH; + break; + case UDC_DEVSTS_ENUM_SPEED_FULL: + speed = USB_SPEED_FULL; + break; + case UDC_DEVSTS_ENUM_SPEED_LOW: + speed = USB_SPEED_LOW; + break; + default: + BUG(); + } + dev->gadget.speed = speed; + pch_udc_activate_control_ep(dev); + pch_udc_enable_ep_interrupts(dev, UDC_EPINT_IN_EP0 | UDC_EPINT_OUT_EP0); + pch_udc_set_dma(dev, DMA_DIR_TX); + pch_udc_set_dma(dev, DMA_DIR_RX); + pch_udc_ep_set_rrdy(&(dev->ep[UDC_EP0OUT_IDX])); + + /* enable device interrupts */ + pch_udc_enable_interrupts(dev, UDC_DEVINT_UR | UDC_DEVINT_US | + UDC_DEVINT_ES | UDC_DEVINT_ENUM | + UDC_DEVINT_SI | UDC_DEVINT_SC); +} + +/** + * pch_udc_svc_intf_interrupt() - This function handles a set interface + * interrupt + * @dev: Reference to driver structure + */ +static void pch_udc_svc_intf_interrupt(struct pch_udc_dev *dev) +{ + u32 reg, dev_stat = 0; + int i, ret; + + dev_stat = pch_udc_read_device_status(dev); + dev->cfg_data.cur_intf = (dev_stat & UDC_DEVSTS_INTF_MASK) >> + UDC_DEVSTS_INTF_SHIFT; + dev->cfg_data.cur_alt = (dev_stat & UDC_DEVSTS_ALT_MASK) >> + UDC_DEVSTS_ALT_SHIFT; + dev->set_cfg_not_acked = 1; + /* Construct the usb request for gadget driver and inform it */ + memset(&dev->setup_data, 0 , sizeof dev->setup_data); + dev->setup_data.bRequest = USB_REQ_SET_INTERFACE; + dev->setup_data.bRequestType = USB_RECIP_INTERFACE; + dev->setup_data.wValue = cpu_to_le16(dev->cfg_data.cur_alt); + dev->setup_data.wIndex = cpu_to_le16(dev->cfg_data.cur_intf); + /* programm the Endpoint Cfg registers */ + /* Only one end point cfg register */ + reg = pch_udc_read_csr(dev, UDC_EP0OUT_IDX); + reg = (reg & ~UDC_CSR_NE_INTF_MASK) | + (dev->cfg_data.cur_intf << UDC_CSR_NE_INTF_SHIFT); + reg = (reg & ~UDC_CSR_NE_ALT_MASK) | + (dev->cfg_data.cur_alt << UDC_CSR_NE_ALT_SHIFT); + pch_udc_write_csr(dev, reg, UDC_EP0OUT_IDX); + for (i = 0; i < PCH_UDC_USED_EP_NUM * 2; i++) { + /* clear stall bits */ + pch_udc_ep_clear_stall(&(dev->ep[i])); + dev->ep[i].halted = 0; + } + dev->stall = 0; + spin_unlock(&dev->lock); + ret = dev->driver->setup(&dev->gadget, &dev->setup_data); + spin_lock(&dev->lock); +} + +/** + * pch_udc_svc_cfg_interrupt() - This function handles a set configuration + * interrupt + * @dev: Reference to driver structure + */ +static void pch_udc_svc_cfg_interrupt(struct pch_udc_dev *dev) +{ + int i, ret; + u32 reg, dev_stat = 0; + + dev_stat = pch_udc_read_device_status(dev); + dev->set_cfg_not_acked = 1; + dev->cfg_data.cur_cfg = (dev_stat & UDC_DEVSTS_CFG_MASK) >> + UDC_DEVSTS_CFG_SHIFT; + /* make usb request for gadget driver */ + memset(&dev->setup_data, 0 , sizeof dev->setup_data); + dev->setup_data.bRequest = USB_REQ_SET_CONFIGURATION; + dev->setup_data.wValue = cpu_to_le16(dev->cfg_data.cur_cfg); + /* program the NE registers */ + /* Only one end point cfg register */ + reg = pch_udc_read_csr(dev, UDC_EP0OUT_IDX); + reg = (reg & ~UDC_CSR_NE_CFG_MASK) | + (dev->cfg_data.cur_cfg << UDC_CSR_NE_CFG_SHIFT); + pch_udc_write_csr(dev, reg, UDC_EP0OUT_IDX); + for (i = 0; i < PCH_UDC_USED_EP_NUM * 2; i++) { + /* clear stall bits */ + pch_udc_ep_clear_stall(&(dev->ep[i])); + dev->ep[i].halted = 0; + } + dev->stall = 0; + + /* call gadget zero with setup data received */ + spin_unlock(&dev->lock); + ret = dev->driver->setup(&dev->gadget, &dev->setup_data); + spin_lock(&dev->lock); +} + +/** + * pch_udc_dev_isr() - This function services device interrupts + * by invoking appropriate routines. + * @dev: Reference to the device structure + * @dev_intr: The Device interrupt status. + */ +static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr) +{ + int vbus; + + /* USB Reset Interrupt */ + if (dev_intr & UDC_DEVINT_UR) { + pch_udc_svc_ur_interrupt(dev); + dev_dbg(&dev->pdev->dev, "USB_RESET\n"); + } + /* Enumeration Done Interrupt */ + if (dev_intr & UDC_DEVINT_ENUM) { + pch_udc_svc_enum_interrupt(dev); + dev_dbg(&dev->pdev->dev, "USB_ENUM\n"); + } + /* Set Interface Interrupt */ + if (dev_intr & UDC_DEVINT_SI) + pch_udc_svc_intf_interrupt(dev); + /* Set Config Interrupt */ + if (dev_intr & UDC_DEVINT_SC) + pch_udc_svc_cfg_interrupt(dev); + /* USB Suspend interrupt */ + if (dev_intr & UDC_DEVINT_US) { + if (dev->driver + && dev->driver->suspend) { + spin_unlock(&dev->lock); + dev->driver->suspend(&dev->gadget); + spin_lock(&dev->lock); + } + + vbus = pch_vbus_gpio_get_value(dev); + if ((dev->vbus_session == 0) + && (vbus != 1)) { + if (dev->driver && dev->driver->disconnect) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + pch_udc_reconnect(dev); + } else if ((dev->vbus_session == 0) + && (vbus == 1) + && !dev->vbus_gpio.intr) + schedule_work(&dev->vbus_gpio.irq_work_fall); + + dev_dbg(&dev->pdev->dev, "USB_SUSPEND\n"); + } + /* Clear the SOF interrupt, if enabled */ + if (dev_intr & UDC_DEVINT_SOF) + dev_dbg(&dev->pdev->dev, "SOF\n"); + /* ES interrupt, IDLE > 3ms on the USB */ + if (dev_intr & UDC_DEVINT_ES) + dev_dbg(&dev->pdev->dev, "ES\n"); + /* RWKP interrupt */ + if (dev_intr & UDC_DEVINT_RWKP) + dev_dbg(&dev->pdev->dev, "RWKP\n"); +} + +/** + * pch_udc_isr() - This function handles interrupts from the PCH USB Device + * @irq: Interrupt request number + * @dev: Reference to the device structure + */ +static irqreturn_t pch_udc_isr(int irq, void *pdev) +{ + struct pch_udc_dev *dev = (struct pch_udc_dev *) pdev; + u32 dev_intr, ep_intr; + int i; + + dev_intr = pch_udc_read_device_interrupts(dev); + ep_intr = pch_udc_read_ep_interrupts(dev); + + /* For a hot plug, this find that the controller is hung up. */ + if (dev_intr == ep_intr) + if (dev_intr == pch_udc_readl(dev, UDC_DEVCFG_ADDR)) { + dev_dbg(&dev->pdev->dev, "UDC: Hung up\n"); + /* The controller is reset */ + pch_udc_writel(dev, UDC_SRST, UDC_SRST_ADDR); + return IRQ_HANDLED; + } + if (dev_intr) + /* Clear device interrupts */ + pch_udc_write_device_interrupts(dev, dev_intr); + if (ep_intr) + /* Clear ep interrupts */ + pch_udc_write_ep_interrupts(dev, ep_intr); + if (!dev_intr && !ep_intr) + return IRQ_NONE; + spin_lock(&dev->lock); + if (dev_intr) + pch_udc_dev_isr(dev, dev_intr); + if (ep_intr) { + pch_udc_read_all_epstatus(dev, ep_intr); + /* Process Control In interrupts, if present */ + if (ep_intr & UDC_EPINT_IN_EP0) { + pch_udc_svc_control_in(dev); + pch_udc_postsvc_epinters(dev, 0); + } + /* Process Control Out interrupts, if present */ + if (ep_intr & UDC_EPINT_OUT_EP0) + pch_udc_svc_control_out(dev); + /* Process data in end point interrupts */ + for (i = 1; i < PCH_UDC_USED_EP_NUM; i++) { + if (ep_intr & (1 << i)) { + pch_udc_svc_data_in(dev, i); + pch_udc_postsvc_epinters(dev, i); + } + } + /* Process data out end point interrupts */ + for (i = UDC_EPINT_OUT_SHIFT + 1; i < (UDC_EPINT_OUT_SHIFT + + PCH_UDC_USED_EP_NUM); i++) + if (ep_intr & (1 << i)) + pch_udc_svc_data_out(dev, i - + UDC_EPINT_OUT_SHIFT); + } + spin_unlock(&dev->lock); + return IRQ_HANDLED; +} + +/** + * pch_udc_setup_ep0() - This function enables control endpoint for traffic + * @dev: Reference to the device structure + */ +static void pch_udc_setup_ep0(struct pch_udc_dev *dev) +{ + /* enable ep0 interrupts */ + pch_udc_enable_ep_interrupts(dev, UDC_EPINT_IN_EP0 | + UDC_EPINT_OUT_EP0); + /* enable device interrupts */ + pch_udc_enable_interrupts(dev, UDC_DEVINT_UR | UDC_DEVINT_US | + UDC_DEVINT_ES | UDC_DEVINT_ENUM | + UDC_DEVINT_SI | UDC_DEVINT_SC); +} + +/** + * gadget_release() - Free the gadget driver private data + * @pdev reference to struct pci_dev + */ +static void gadget_release(struct device *pdev) +{ + struct pch_udc_dev *dev = dev_get_drvdata(pdev); + + kfree(dev); +} + +/** + * pch_udc_pcd_reinit() - This API initializes the endpoint structures + * @dev: Reference to the driver structure + */ +static void pch_udc_pcd_reinit(struct pch_udc_dev *dev) +{ + const char *const ep_string[] = { + ep0_string, "ep0out", "ep1in", "ep1out", "ep2in", "ep2out", + "ep3in", "ep3out", "ep4in", "ep4out", "ep5in", "ep5out", + "ep6in", "ep6out", "ep7in", "ep7out", "ep8in", "ep8out", + "ep9in", "ep9out", "ep10in", "ep10out", "ep11in", "ep11out", + "ep12in", "ep12out", "ep13in", "ep13out", "ep14in", "ep14out", + "ep15in", "ep15out", + }; + int i; + + dev->gadget.speed = USB_SPEED_UNKNOWN; + INIT_LIST_HEAD(&dev->gadget.ep_list); + + /* Initialize the endpoints structures */ + memset(dev->ep, 0, sizeof dev->ep); + for (i = 0; i < PCH_UDC_EP_NUM; i++) { + struct pch_udc_ep *ep = &dev->ep[i]; + ep->dev = dev; + ep->halted = 1; + ep->num = i / 2; + ep->in = ~i & 1; + ep->ep.name = ep_string[i]; + ep->ep.ops = &pch_udc_ep_ops; + if (ep->in) + ep->offset_addr = ep->num * UDC_EP_REG_SHIFT; + else + ep->offset_addr = (UDC_EPINT_OUT_SHIFT + ep->num) * + UDC_EP_REG_SHIFT; + /* need to set ep->ep.maxpacket and set Default Configuration?*/ + ep->ep.maxpacket = UDC_BULK_MAX_PKT_SIZE; + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + INIT_LIST_HEAD(&ep->queue); + } + dev->ep[UDC_EP0IN_IDX].ep.maxpacket = UDC_EP0IN_MAX_PKT_SIZE; + dev->ep[UDC_EP0OUT_IDX].ep.maxpacket = UDC_EP0OUT_MAX_PKT_SIZE; + + /* remove ep0 in and out from the list. They have own pointer */ + list_del_init(&dev->ep[UDC_EP0IN_IDX].ep.ep_list); + list_del_init(&dev->ep[UDC_EP0OUT_IDX].ep.ep_list); + + dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IDX].ep; + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); +} + +/** + * pch_udc_pcd_init() - This API initializes the driver structure + * @dev: Reference to the driver structure + * + * Return codes: + * 0: Success + */ +static int pch_udc_pcd_init(struct pch_udc_dev *dev) +{ + pch_udc_init(dev); + pch_udc_pcd_reinit(dev); + pch_vbus_gpio_init(dev, vbus_gpio_port); + return 0; +} + +/** + * init_dma_pools() - create dma pools during initialization + * @pdev: reference to struct pci_dev + */ +static int init_dma_pools(struct pch_udc_dev *dev) +{ + struct pch_udc_stp_dma_desc *td_stp; + struct pch_udc_data_dma_desc *td_data; + + /* DMA setup */ + dev->data_requests = pci_pool_create("data_requests", dev->pdev, + sizeof(struct pch_udc_data_dma_desc), 0, 0); + if (!dev->data_requests) { + dev_err(&dev->pdev->dev, "%s: can't get request data pool\n", + __func__); + return -ENOMEM; + } + + /* dma desc for setup data */ + dev->stp_requests = pci_pool_create("setup requests", dev->pdev, + sizeof(struct pch_udc_stp_dma_desc), 0, 0); + if (!dev->stp_requests) { + dev_err(&dev->pdev->dev, "%s: can't get setup request pool\n", + __func__); + return -ENOMEM; + } + /* setup */ + td_stp = pci_pool_alloc(dev->stp_requests, GFP_KERNEL, + &dev->ep[UDC_EP0OUT_IDX].td_stp_phys); + if (!td_stp) { + dev_err(&dev->pdev->dev, + "%s: can't allocate setup dma descriptor\n", __func__); + return -ENOMEM; + } + dev->ep[UDC_EP0OUT_IDX].td_stp = td_stp; + + /* data: 0 packets !? */ + td_data = pci_pool_alloc(dev->data_requests, GFP_KERNEL, + &dev->ep[UDC_EP0OUT_IDX].td_data_phys); + if (!td_data) { + dev_err(&dev->pdev->dev, + "%s: can't allocate data dma descriptor\n", __func__); + return -ENOMEM; + } + dev->ep[UDC_EP0OUT_IDX].td_data = td_data; + dev->ep[UDC_EP0IN_IDX].td_stp = NULL; + dev->ep[UDC_EP0IN_IDX].td_stp_phys = 0; + dev->ep[UDC_EP0IN_IDX].td_data = NULL; + dev->ep[UDC_EP0IN_IDX].td_data_phys = 0; + + dev->ep0out_buf = kzalloc(UDC_EP0OUT_BUFF_SIZE * 4, GFP_KERNEL); + if (!dev->ep0out_buf) + return -ENOMEM; + dev->dma_addr = dma_map_single(&dev->pdev->dev, dev->ep0out_buf, + UDC_EP0OUT_BUFF_SIZE * 4, + DMA_FROM_DEVICE); + return 0; +} + +static int pch_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct pch_udc_dev *dev = to_pch_udc(g); + + driver->driver.bus = NULL; + dev->driver = driver; + + /* get ready for ep0 traffic */ + pch_udc_setup_ep0(dev); + + /* clear SD */ + if ((pch_vbus_gpio_get_value(dev) != 0) || !dev->vbus_gpio.intr) + pch_udc_clear_disconnect(dev); + + dev->connected = 1; + return 0; +} + +static int pch_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct pch_udc_dev *dev = to_pch_udc(g); + + pch_udc_disable_interrupts(dev, UDC_DEVINT_MSK); + + /* Assures that there are no pending requests with this driver */ + dev->driver = NULL; + dev->connected = 0; + + /* set SD */ + pch_udc_set_disconnect(dev); + + return 0; +} + +static void pch_udc_shutdown(struct pci_dev *pdev) +{ + struct pch_udc_dev *dev = pci_get_drvdata(pdev); + + pch_udc_disable_interrupts(dev, UDC_DEVINT_MSK); + pch_udc_disable_ep_interrupts(dev, UDC_EPINT_MSK_DISABLE_ALL); + + /* disable the pullup so the host will think we're gone */ + pch_udc_set_disconnect(dev); +} + +static void pch_udc_remove(struct pci_dev *pdev) +{ + struct pch_udc_dev *dev = pci_get_drvdata(pdev); + + usb_del_gadget_udc(&dev->gadget); + + /* gadget driver must not be registered */ + if (dev->driver) + dev_err(&pdev->dev, + "%s: gadget driver still bound!!!\n", __func__); + /* dma pool cleanup */ + if (dev->data_requests) + pci_pool_destroy(dev->data_requests); + + if (dev->stp_requests) { + /* cleanup DMA desc's for ep0in */ + if (dev->ep[UDC_EP0OUT_IDX].td_stp) { + pci_pool_free(dev->stp_requests, + dev->ep[UDC_EP0OUT_IDX].td_stp, + dev->ep[UDC_EP0OUT_IDX].td_stp_phys); + } + if (dev->ep[UDC_EP0OUT_IDX].td_data) { + pci_pool_free(dev->stp_requests, + dev->ep[UDC_EP0OUT_IDX].td_data, + dev->ep[UDC_EP0OUT_IDX].td_data_phys); + } + pci_pool_destroy(dev->stp_requests); + } + + if (dev->dma_addr) + dma_unmap_single(&dev->pdev->dev, dev->dma_addr, + UDC_EP0OUT_BUFF_SIZE * 4, DMA_FROM_DEVICE); + kfree(dev->ep0out_buf); + + pch_vbus_gpio_free(dev); + + pch_udc_exit(dev); + + if (dev->irq_registered) + free_irq(pdev->irq, dev); + if (dev->base_addr) + iounmap(dev->base_addr); + if (dev->mem_region) + release_mem_region(dev->phys_addr, + pci_resource_len(pdev, PCH_UDC_PCI_BAR)); + if (dev->active) + pci_disable_device(pdev); + kfree(dev); + pci_set_drvdata(pdev, NULL); +} + +#ifdef CONFIG_PM +static int pch_udc_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct pch_udc_dev *dev = pci_get_drvdata(pdev); + + pch_udc_disable_interrupts(dev, UDC_DEVINT_MSK); + pch_udc_disable_ep_interrupts(dev, UDC_EPINT_MSK_DISABLE_ALL); + + pci_disable_device(pdev); + pci_enable_wake(pdev, PCI_D3hot, 0); + + if (pci_save_state(pdev)) { + dev_err(&pdev->dev, + "%s: could not save PCI config state\n", __func__); + return -ENOMEM; + } + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + return 0; +} + +static int pch_udc_resume(struct pci_dev *pdev) +{ + int ret; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "%s: pci_enable_device failed\n", __func__); + return ret; + } + pci_enable_wake(pdev, PCI_D3hot, 0); + return 0; +} +#else +#define pch_udc_suspend NULL +#define pch_udc_resume NULL +#endif /* CONFIG_PM */ + +static int pch_udc_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + unsigned long resource; + unsigned long len; + int retval; + struct pch_udc_dev *dev; + + /* init */ + dev = kzalloc(sizeof *dev, GFP_KERNEL); + if (!dev) { + pr_err("%s: no memory for device structure\n", __func__); + return -ENOMEM; + } + /* pci setup */ + if (pci_enable_device(pdev) < 0) { + kfree(dev); + pr_err("%s: pci_enable_device failed\n", __func__); + return -ENODEV; + } + dev->active = 1; + pci_set_drvdata(pdev, dev); + + /* PCI resource allocation */ + resource = pci_resource_start(pdev, 1); + len = pci_resource_len(pdev, 1); + + if (!request_mem_region(resource, len, KBUILD_MODNAME)) { + dev_err(&pdev->dev, "%s: pci device used already\n", __func__); + retval = -EBUSY; + goto finished; + } + dev->phys_addr = resource; + dev->mem_region = 1; + + dev->base_addr = ioremap_nocache(resource, len); + if (!dev->base_addr) { + pr_err("%s: device memory cannot be mapped\n", __func__); + retval = -ENOMEM; + goto finished; + } + if (!pdev->irq) { + dev_err(&pdev->dev, "%s: irq not set\n", __func__); + retval = -ENODEV; + goto finished; + } + /* initialize the hardware */ + if (pch_udc_pcd_init(dev)) { + retval = -ENODEV; + goto finished; + } + if (request_irq(pdev->irq, pch_udc_isr, IRQF_SHARED, KBUILD_MODNAME, + dev)) { + dev_err(&pdev->dev, "%s: request_irq(%d) fail\n", __func__, + pdev->irq); + retval = -ENODEV; + goto finished; + } + dev->irq = pdev->irq; + dev->irq_registered = 1; + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + /* device struct setup */ + spin_lock_init(&dev->lock); + dev->pdev = pdev; + dev->gadget.ops = &pch_udc_ops; + + retval = init_dma_pools(dev); + if (retval) + goto finished; + + dev->gadget.name = KBUILD_MODNAME; + dev->gadget.max_speed = USB_SPEED_HIGH; + + /* Put the device in disconnected state till a driver is bound */ + pch_udc_set_disconnect(dev); + retval = usb_add_gadget_udc_release(&pdev->dev, &dev->gadget, + gadget_release); + if (retval) + goto finished; + return 0; + +finished: + pch_udc_remove(pdev); + return retval; +} + +static DEFINE_PCI_DEVICE_TABLE(pch_udc_pcidev_id) = { + { + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_EG20T_UDC), + .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class_mask = 0xffffffff, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_ROHM, PCI_DEVICE_ID_ML7213_IOH_UDC), + .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class_mask = 0xffffffff, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_ROHM, PCI_DEVICE_ID_ML7831_IOH_UDC), + .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class_mask = 0xffffffff, + }, + { 0 }, +}; + +MODULE_DEVICE_TABLE(pci, pch_udc_pcidev_id); + +static struct pci_driver pch_udc_driver = { + .name = KBUILD_MODNAME, + .id_table = pch_udc_pcidev_id, + .probe = pch_udc_probe, + .remove = pch_udc_remove, + .suspend = pch_udc_suspend, + .resume = pch_udc_resume, + .shutdown = pch_udc_shutdown, +}; + +module_pci_driver(pch_udc_driver); + +MODULE_DESCRIPTION("Intel EG20T USB Device Controller"); +MODULE_AUTHOR("LAPIS Semiconductor, "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/printer.c b/drivers/usb/gadget/printer.c new file mode 100644 index 00000000..bf7a56b6 --- /dev/null +++ b/drivers/usb/gadget/printer.c @@ -0,0 +1,1299 @@ +/* + * printer.c -- Printer gadget driver + * + * Copyright (C) 2003-2005 David Brownell + * Copyright (C) 2006 Craig W. Nadler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "gadget_chips.h" + +USB_GADGET_COMPOSITE_OPTIONS(); + +#define DRIVER_DESC "Printer Gadget" +#define DRIVER_VERSION "2007 OCT 06" + +static DEFINE_MUTEX(printer_mutex); +static const char shortname [] = "printer"; +static const char driver_desc [] = DRIVER_DESC; + +static dev_t g_printer_devno; + +static struct class *usb_gadget_class; + +/*-------------------------------------------------------------------------*/ + +struct printer_dev { + spinlock_t lock; /* lock this structure */ + /* lock buffer lists during read/write calls */ + struct mutex lock_printer_io; + struct usb_gadget *gadget; + s8 interface; + struct usb_ep *in_ep, *out_ep; + + struct list_head rx_reqs; /* List of free RX structs */ + struct list_head rx_reqs_active; /* List of Active RX xfers */ + struct list_head rx_buffers; /* List of completed xfers */ + /* wait until there is data to be read. */ + wait_queue_head_t rx_wait; + struct list_head tx_reqs; /* List of free TX structs */ + struct list_head tx_reqs_active; /* List of Active TX xfers */ + /* Wait until there are write buffers available to use. */ + wait_queue_head_t tx_wait; + /* Wait until all write buffers have been sent. */ + wait_queue_head_t tx_flush_wait; + struct usb_request *current_rx_req; + size_t current_rx_bytes; + u8 *current_rx_buf; + u8 printer_status; + u8 reset_printer; + struct cdev printer_cdev; + struct device *pdev; + u8 printer_cdev_open; + wait_queue_head_t wait; + struct usb_function function; +}; + +static struct printer_dev usb_printer_gadget; + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. + */ +#define PRINTER_VENDOR_NUM 0x0525 /* NetChip */ +#define PRINTER_PRODUCT_NUM 0xa4a8 /* Linux-USB Printer Gadget */ + +/* Some systems will want different product identifiers published in the + * device descriptor, either numbers or strings or both. These string + * parameters are in UTF-8 (superset of ASCII's 7 bit characters). + */ + +module_param_named(iSerialNum, coverwrite.serial_number, charp, S_IRUGO); +MODULE_PARM_DESC(iSerialNum, "1"); + +static char *iPNPstring; +module_param(iPNPstring, charp, S_IRUGO); +MODULE_PARM_DESC(iPNPstring, "MFG:linux;MDL:g_printer;CLS:PRINTER;SN:1;"); + +/* Number of requests to allocate per endpoint, not used for ep0. */ +static unsigned qlen = 10; +module_param(qlen, uint, S_IRUGO|S_IWUSR); + +#define QLEN qlen + +/*-------------------------------------------------------------------------*/ + +/* + * DESCRIPTORS ... most are static, but strings and (full) configuration + * descriptors are built on demand. + */ + +/* holds our biggest descriptor */ +#define USB_DESC_BUFSIZE 256 +#define USB_BUFSIZE 8192 + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .idVendor = cpu_to_le16(PRINTER_VENDOR_NUM), + .idProduct = cpu_to_le16(PRINTER_PRODUCT_NUM), + .bNumConfigurations = 1 +}; + +static struct usb_interface_descriptor intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_PRINTER, + .bInterfaceSubClass = 1, /* Printer Sub-Class */ + .bInterfaceProtocol = 2, /* Bi-Directional */ + .iInterface = 0 +}; + +static struct usb_endpoint_descriptor fs_ep_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK +}; + +static struct usb_endpoint_descriptor fs_ep_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK +}; + +static struct usb_descriptor_header *fs_printer_function[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &fs_ep_in_desc, + (struct usb_descriptor_header *) &fs_ep_out_desc, + NULL +}; + +/* + * usb 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + */ + +static struct usb_endpoint_descriptor hs_ep_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512) +}; + +static struct usb_endpoint_descriptor hs_ep_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512) +}; + +static struct usb_qualifier_descriptor dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PRINTER, + .bNumConfigurations = 1 +}; + +static struct usb_descriptor_header *hs_printer_function[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &hs_ep_in_desc, + (struct usb_descriptor_header *) &hs_ep_out_desc, + NULL +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + .bmAttributes = USB_OTG_SRP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +/* maxpacket and other transfer characteristics vary by speed. */ +#define ep_desc(g, hs, fs) (((g)->speed == USB_SPEED_HIGH)?(hs):(fs)) + +/*-------------------------------------------------------------------------*/ + +/* descriptors that are built on-demand */ + +static char product_desc [40] = DRIVER_DESC; +static char serial_num [40] = "1"; +static char pnp_string [1024] = + "XXMFG:linux;MDL:g_printer;CLS:PRINTER;SN:1;"; + +/* static strings, in UTF-8 */ +static struct usb_string strings [] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = product_desc, + [USB_GADGET_SERIAL_IDX].s = serial_num, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static struct usb_request * +printer_req_alloc(struct usb_ep *ep, unsigned len, gfp_t gfp_flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, gfp_flags); + + if (req != NULL) { + req->length = len; + req->buf = kmalloc(len, gfp_flags); + if (req->buf == NULL) { + usb_ep_free_request(ep, req); + return NULL; + } + } + + return req; +} + +static void +printer_req_free(struct usb_ep *ep, struct usb_request *req) +{ + if (ep != NULL && req != NULL) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +/*-------------------------------------------------------------------------*/ + +static void rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct printer_dev *dev = ep->driver_data; + int status = req->status; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + list_del_init(&req->list); /* Remode from Active List */ + + switch (status) { + + /* normal completion */ + case 0: + if (req->actual > 0) { + list_add_tail(&req->list, &dev->rx_buffers); + DBG(dev, "G_Printer : rx length %d\n", req->actual); + } else { + list_add(&req->list, &dev->rx_reqs); + } + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + VDBG(dev, "rx shutdown, code %d\n", status); + list_add(&req->list, &dev->rx_reqs); + break; + + /* for hardware automagic (such as pxa) */ + case -ECONNABORTED: /* endpoint reset */ + DBG(dev, "rx %s reset\n", ep->name); + list_add(&req->list, &dev->rx_reqs); + break; + + /* data overrun */ + case -EOVERFLOW: + /* FALLTHROUGH */ + + default: + DBG(dev, "rx status %d\n", status); + list_add(&req->list, &dev->rx_reqs); + break; + } + + wake_up_interruptible(&dev->rx_wait); + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct printer_dev *dev = ep->driver_data; + + switch (req->status) { + default: + VDBG(dev, "tx err %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + break; + case 0: + break; + } + + spin_lock(&dev->lock); + /* Take the request struct off the active list and put it on the + * free list. + */ + list_del_init(&req->list); + list_add(&req->list, &dev->tx_reqs); + wake_up_interruptible(&dev->tx_wait); + if (likely(list_empty(&dev->tx_reqs_active))) + wake_up_interruptible(&dev->tx_flush_wait); + + spin_unlock(&dev->lock); +} + +/*-------------------------------------------------------------------------*/ + +static int +printer_open(struct inode *inode, struct file *fd) +{ + struct printer_dev *dev; + unsigned long flags; + int ret = -EBUSY; + + mutex_lock(&printer_mutex); + dev = container_of(inode->i_cdev, struct printer_dev, printer_cdev); + + spin_lock_irqsave(&dev->lock, flags); + + if (!dev->printer_cdev_open) { + dev->printer_cdev_open = 1; + fd->private_data = dev; + ret = 0; + /* Change the printer status to show that it's on-line. */ + dev->printer_status |= PRINTER_SELECTED; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + DBG(dev, "printer_open returned %x\n", ret); + mutex_unlock(&printer_mutex); + return ret; +} + +static int +printer_close(struct inode *inode, struct file *fd) +{ + struct printer_dev *dev = fd->private_data; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->printer_cdev_open = 0; + fd->private_data = NULL; + /* Change printer status to show that the printer is off-line. */ + dev->printer_status &= ~PRINTER_SELECTED; + spin_unlock_irqrestore(&dev->lock, flags); + + DBG(dev, "printer_close\n"); + + return 0; +} + +/* This function must be called with interrupts turned off. */ +static void +setup_rx_reqs(struct printer_dev *dev) +{ + struct usb_request *req; + + while (likely(!list_empty(&dev->rx_reqs))) { + int error; + + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del_init(&req->list); + + /* The USB Host sends us whatever amount of data it wants to + * so we always set the length field to the full USB_BUFSIZE. + * If the amount of data is more than the read() caller asked + * for it will be stored in the request buffer until it is + * asked for by read(). + */ + req->length = USB_BUFSIZE; + req->complete = rx_complete; + + error = usb_ep_queue(dev->out_ep, req, GFP_ATOMIC); + if (error) { + DBG(dev, "rx submit --> %d\n", error); + list_add(&req->list, &dev->rx_reqs); + break; + } else { + list_add(&req->list, &dev->rx_reqs_active); + } + } +} + +static ssize_t +printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) +{ + struct printer_dev *dev = fd->private_data; + unsigned long flags; + size_t size; + size_t bytes_copied; + struct usb_request *req; + /* This is a pointer to the current USB rx request. */ + struct usb_request *current_rx_req; + /* This is the number of bytes in the current rx buffer. */ + size_t current_rx_bytes; + /* This is a pointer to the current rx buffer. */ + u8 *current_rx_buf; + + if (len == 0) + return -EINVAL; + + DBG(dev, "printer_read trying to read %d bytes\n", (int)len); + + mutex_lock(&dev->lock_printer_io); + spin_lock_irqsave(&dev->lock, flags); + + /* We will use this flag later to check if a printer reset happened + * after we turn interrupts back on. + */ + dev->reset_printer = 0; + + setup_rx_reqs(dev); + + bytes_copied = 0; + current_rx_req = dev->current_rx_req; + current_rx_bytes = dev->current_rx_bytes; + current_rx_buf = dev->current_rx_buf; + dev->current_rx_req = NULL; + dev->current_rx_bytes = 0; + dev->current_rx_buf = NULL; + + /* Check if there is any data in the read buffers. Please note that + * current_rx_bytes is the number of bytes in the current rx buffer. + * If it is zero then check if there are any other rx_buffers that + * are on the completed list. We are only out of data if all rx + * buffers are empty. + */ + if ((current_rx_bytes == 0) && + (likely(list_empty(&dev->rx_buffers)))) { + /* Turn interrupts back on before sleeping. */ + spin_unlock_irqrestore(&dev->lock, flags); + + /* + * If no data is available check if this is a NON-Blocking + * call or not. + */ + if (fd->f_flags & (O_NONBLOCK|O_NDELAY)) { + mutex_unlock(&dev->lock_printer_io); + return -EAGAIN; + } + + /* Sleep until data is available */ + wait_event_interruptible(dev->rx_wait, + (likely(!list_empty(&dev->rx_buffers)))); + spin_lock_irqsave(&dev->lock, flags); + } + + /* We have data to return then copy it to the caller's buffer.*/ + while ((current_rx_bytes || likely(!list_empty(&dev->rx_buffers))) + && len) { + if (current_rx_bytes == 0) { + req = container_of(dev->rx_buffers.next, + struct usb_request, list); + list_del_init(&req->list); + + if (req->actual && req->buf) { + current_rx_req = req; + current_rx_bytes = req->actual; + current_rx_buf = req->buf; + } else { + list_add(&req->list, &dev->rx_reqs); + continue; + } + } + + /* Don't leave irqs off while doing memory copies */ + spin_unlock_irqrestore(&dev->lock, flags); + + if (len > current_rx_bytes) + size = current_rx_bytes; + else + size = len; + + size -= copy_to_user(buf, current_rx_buf, size); + bytes_copied += size; + len -= size; + buf += size; + + spin_lock_irqsave(&dev->lock, flags); + + /* We've disconnected or reset so return. */ + if (dev->reset_printer) { + list_add(¤t_rx_req->list, &dev->rx_reqs); + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return -EAGAIN; + } + + /* If we not returning all the data left in this RX request + * buffer then adjust the amount of data left in the buffer. + * Othewise if we are done with this RX request buffer then + * requeue it to get any incoming data from the USB host. + */ + if (size < current_rx_bytes) { + current_rx_bytes -= size; + current_rx_buf += size; + } else { + list_add(¤t_rx_req->list, &dev->rx_reqs); + current_rx_bytes = 0; + current_rx_buf = NULL; + current_rx_req = NULL; + } + } + + dev->current_rx_req = current_rx_req; + dev->current_rx_bytes = current_rx_bytes; + dev->current_rx_buf = current_rx_buf; + + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + + DBG(dev, "printer_read returned %d bytes\n", (int)bytes_copied); + + if (bytes_copied) + return bytes_copied; + else + return -EAGAIN; +} + +static ssize_t +printer_write(struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct printer_dev *dev = fd->private_data; + unsigned long flags; + size_t size; /* Amount of data in a TX request. */ + size_t bytes_copied = 0; + struct usb_request *req; + + DBG(dev, "printer_write trying to send %d bytes\n", (int)len); + + if (len == 0) + return -EINVAL; + + mutex_lock(&dev->lock_printer_io); + spin_lock_irqsave(&dev->lock, flags); + + /* Check if a printer reset happens while we have interrupts on */ + dev->reset_printer = 0; + + /* Check if there is any available write buffers */ + if (likely(list_empty(&dev->tx_reqs))) { + /* Turn interrupts back on before sleeping. */ + spin_unlock_irqrestore(&dev->lock, flags); + + /* + * If write buffers are available check if this is + * a NON-Blocking call or not. + */ + if (fd->f_flags & (O_NONBLOCK|O_NDELAY)) { + mutex_unlock(&dev->lock_printer_io); + return -EAGAIN; + } + + /* Sleep until a write buffer is available */ + wait_event_interruptible(dev->tx_wait, + (likely(!list_empty(&dev->tx_reqs)))); + spin_lock_irqsave(&dev->lock, flags); + } + + while (likely(!list_empty(&dev->tx_reqs)) && len) { + + if (len > USB_BUFSIZE) + size = USB_BUFSIZE; + else + size = len; + + req = container_of(dev->tx_reqs.next, struct usb_request, + list); + list_del_init(&req->list); + + req->complete = tx_complete; + req->length = size; + + /* Check if we need to send a zero length packet. */ + if (len > size) + /* They will be more TX requests so no yet. */ + req->zero = 0; + else + /* If the data amount is not a multple of the + * maxpacket size then send a zero length packet. + */ + req->zero = ((len % dev->in_ep->maxpacket) == 0); + + /* Don't leave irqs off while doing memory copies */ + spin_unlock_irqrestore(&dev->lock, flags); + + if (copy_from_user(req->buf, buf, size)) { + list_add(&req->list, &dev->tx_reqs); + mutex_unlock(&dev->lock_printer_io); + return bytes_copied; + } + + bytes_copied += size; + len -= size; + buf += size; + + spin_lock_irqsave(&dev->lock, flags); + + /* We've disconnected or reset so free the req and buffer */ + if (dev->reset_printer) { + list_add(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return -EAGAIN; + } + + if (usb_ep_queue(dev->in_ep, req, GFP_ATOMIC)) { + list_add(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return -EAGAIN; + } + + list_add(&req->list, &dev->tx_reqs_active); + + } + + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + + DBG(dev, "printer_write sent %d bytes\n", (int)bytes_copied); + + if (bytes_copied) { + return bytes_copied; + } else { + return -EAGAIN; + } +} + +static int +printer_fsync(struct file *fd, loff_t start, loff_t end, int datasync) +{ + struct printer_dev *dev = fd->private_data; + struct inode *inode = file_inode(fd); + unsigned long flags; + int tx_list_empty; + + mutex_lock(&inode->i_mutex); + spin_lock_irqsave(&dev->lock, flags); + tx_list_empty = (likely(list_empty(&dev->tx_reqs))); + spin_unlock_irqrestore(&dev->lock, flags); + + if (!tx_list_empty) { + /* Sleep until all data has been sent */ + wait_event_interruptible(dev->tx_flush_wait, + (likely(list_empty(&dev->tx_reqs_active)))); + } + mutex_unlock(&inode->i_mutex); + + return 0; +} + +static unsigned int +printer_poll(struct file *fd, poll_table *wait) +{ + struct printer_dev *dev = fd->private_data; + unsigned long flags; + int status = 0; + + mutex_lock(&dev->lock_printer_io); + spin_lock_irqsave(&dev->lock, flags); + setup_rx_reqs(dev); + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + + poll_wait(fd, &dev->rx_wait, wait); + poll_wait(fd, &dev->tx_wait, wait); + + spin_lock_irqsave(&dev->lock, flags); + if (likely(!list_empty(&dev->tx_reqs))) + status |= POLLOUT | POLLWRNORM; + + if (likely(dev->current_rx_bytes) || + likely(!list_empty(&dev->rx_buffers))) + status |= POLLIN | POLLRDNORM; + + spin_unlock_irqrestore(&dev->lock, flags); + + return status; +} + +static long +printer_ioctl(struct file *fd, unsigned int code, unsigned long arg) +{ + struct printer_dev *dev = fd->private_data; + unsigned long flags; + int status = 0; + + DBG(dev, "printer_ioctl: cmd=0x%4.4x, arg=%lu\n", code, arg); + + /* handle ioctls */ + + spin_lock_irqsave(&dev->lock, flags); + + switch (code) { + case GADGET_GET_PRINTER_STATUS: + status = (int)dev->printer_status; + break; + case GADGET_SET_PRINTER_STATUS: + dev->printer_status = (u8)arg; + break; + default: + /* could not handle ioctl */ + DBG(dev, "printer_ioctl: ERROR cmd=0x%4.4xis not supported\n", + code); + status = -ENOTTY; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return status; +} + +/* used after endpoint configuration */ +static const struct file_operations printer_io_operations = { + .owner = THIS_MODULE, + .open = printer_open, + .read = printer_read, + .write = printer_write, + .fsync = printer_fsync, + .poll = printer_poll, + .unlocked_ioctl = printer_ioctl, + .release = printer_close, + .llseek = noop_llseek, +}; + +/*-------------------------------------------------------------------------*/ + +static int +set_printer_interface(struct printer_dev *dev) +{ + int result = 0; + + dev->in_ep->desc = ep_desc(dev->gadget, &hs_ep_in_desc, &fs_ep_in_desc); + dev->in_ep->driver_data = dev; + + dev->out_ep->desc = ep_desc(dev->gadget, &hs_ep_out_desc, + &fs_ep_out_desc); + dev->out_ep->driver_data = dev; + + result = usb_ep_enable(dev->in_ep); + if (result != 0) { + DBG(dev, "enable %s --> %d\n", dev->in_ep->name, result); + goto done; + } + + result = usb_ep_enable(dev->out_ep); + if (result != 0) { + DBG(dev, "enable %s --> %d\n", dev->in_ep->name, result); + goto done; + } + +done: + /* on error, disable any endpoints */ + if (result != 0) { + (void) usb_ep_disable(dev->in_ep); + (void) usb_ep_disable(dev->out_ep); + dev->in_ep->desc = NULL; + dev->out_ep->desc = NULL; + } + + /* caller is responsible for cleanup on error */ + return result; +} + +static void printer_reset_interface(struct printer_dev *dev) +{ + if (dev->interface < 0) + return; + + DBG(dev, "%s\n", __func__); + + if (dev->in_ep->desc) + usb_ep_disable(dev->in_ep); + + if (dev->out_ep->desc) + usb_ep_disable(dev->out_ep); + + dev->in_ep->desc = NULL; + dev->out_ep->desc = NULL; + dev->interface = -1; +} + +/* Change our operational Interface. */ +static int set_interface(struct printer_dev *dev, unsigned number) +{ + int result = 0; + + /* Free the current interface */ + printer_reset_interface(dev); + + result = set_printer_interface(dev); + if (result) + printer_reset_interface(dev); + else + dev->interface = number; + + if (!result) + INFO(dev, "Using interface %x\n", number); + + return result; +} + +static void printer_soft_reset(struct printer_dev *dev) +{ + struct usb_request *req; + + INFO(dev, "Received Printer Reset Request\n"); + + if (usb_ep_disable(dev->in_ep)) + DBG(dev, "Failed to disable USB in_ep\n"); + if (usb_ep_disable(dev->out_ep)) + DBG(dev, "Failed to disable USB out_ep\n"); + + if (dev->current_rx_req != NULL) { + list_add(&dev->current_rx_req->list, &dev->rx_reqs); + dev->current_rx_req = NULL; + } + dev->current_rx_bytes = 0; + dev->current_rx_buf = NULL; + dev->reset_printer = 1; + + while (likely(!(list_empty(&dev->rx_buffers)))) { + req = container_of(dev->rx_buffers.next, struct usb_request, + list); + list_del_init(&req->list); + list_add(&req->list, &dev->rx_reqs); + } + + while (likely(!(list_empty(&dev->rx_reqs_active)))) { + req = container_of(dev->rx_buffers.next, struct usb_request, + list); + list_del_init(&req->list); + list_add(&req->list, &dev->rx_reqs); + } + + while (likely(!(list_empty(&dev->tx_reqs_active)))) { + req = container_of(dev->tx_reqs_active.next, + struct usb_request, list); + list_del_init(&req->list); + list_add(&req->list, &dev->tx_reqs); + } + + if (usb_ep_enable(dev->in_ep)) + DBG(dev, "Failed to enable USB in_ep\n"); + if (usb_ep_enable(dev->out_ep)) + DBG(dev, "Failed to enable USB out_ep\n"); + + wake_up_interruptible(&dev->rx_wait); + wake_up_interruptible(&dev->tx_wait); + wake_up_interruptible(&dev->tx_flush_wait); +} + +/*-------------------------------------------------------------------------*/ + +/* + * The setup() callback implements all the ep0 functionality that's not + * handled lower down. + */ +static int printer_func_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct printer_dev *dev = container_of(f, struct printer_dev, function); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 wIndex = le16_to_cpu(ctrl->wIndex); + u16 wValue = le16_to_cpu(ctrl->wValue); + u16 wLength = le16_to_cpu(ctrl->wLength); + + DBG(dev, "ctrl req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, wValue, wIndex, wLength); + + switch (ctrl->bRequestType&USB_TYPE_MASK) { + case USB_TYPE_CLASS: + switch (ctrl->bRequest) { + case 0: /* Get the IEEE-1284 PNP String */ + /* Only one printer interface is supported. */ + if ((wIndex>>8) != dev->interface) + break; + + value = (pnp_string[0]<<8)|pnp_string[1]; + memcpy(req->buf, pnp_string, value); + DBG(dev, "1284 PNP String: %x %s\n", value, + &pnp_string[2]); + break; + + case 1: /* Get Port Status */ + /* Only one printer interface is supported. */ + if (wIndex != dev->interface) + break; + + *(u8 *)req->buf = dev->printer_status; + value = min(wLength, (u16) 1); + break; + + case 2: /* Soft Reset */ + /* Only one printer interface is supported. */ + if (wIndex != dev->interface) + break; + + printer_soft_reset(dev); + + value = 0; + break; + + default: + goto unknown; + } + break; + + default: +unknown: + VDBG(dev, + "unknown ctrl req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + wValue, wIndex, wLength); + break; + } + /* host either stalls (value < 0) or reports success */ + return value; +} + +static int __init printer_func_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct printer_dev *dev = container_of(f, struct printer_dev, function); + struct usb_composite_dev *cdev = c->cdev; + struct usb_ep *in_ep; + struct usb_ep *out_ep = NULL; + int id; + int ret; + + id = usb_interface_id(c, f); + if (id < 0) + return id; + intf_desc.bInterfaceNumber = id; + + /* all we really need is bulk IN/OUT */ + in_ep = usb_ep_autoconfig(cdev->gadget, &fs_ep_in_desc); + if (!in_ep) { +autoconf_fail: + dev_err(&cdev->gadget->dev, "can't autoconfigure on %s\n", + cdev->gadget->name); + return -ENODEV; + } + in_ep->driver_data = in_ep; /* claim */ + + out_ep = usb_ep_autoconfig(cdev->gadget, &fs_ep_out_desc); + if (!out_ep) + goto autoconf_fail; + out_ep->driver_data = out_ep; /* claim */ + + /* assumes that all endpoints are dual-speed */ + hs_ep_in_desc.bEndpointAddress = fs_ep_in_desc.bEndpointAddress; + hs_ep_out_desc.bEndpointAddress = fs_ep_out_desc.bEndpointAddress; + + ret = usb_assign_descriptors(f, fs_printer_function, + hs_printer_function, NULL); + if (ret) + return ret; + + dev->in_ep = in_ep; + dev->out_ep = out_ep; + return 0; +} + +static void printer_func_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + usb_free_all_descriptors(f); +} + +static int printer_func_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct printer_dev *dev = container_of(f, struct printer_dev, function); + int ret = -ENOTSUPP; + + if (!alt) + ret = set_interface(dev, intf); + + return ret; +} + +static void printer_func_disable(struct usb_function *f) +{ + struct printer_dev *dev = container_of(f, struct printer_dev, function); + unsigned long flags; + + DBG(dev, "%s\n", __func__); + + spin_lock_irqsave(&dev->lock, flags); + printer_reset_interface(dev); + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void printer_cfg_unbind(struct usb_configuration *c) +{ + struct printer_dev *dev; + struct usb_request *req; + + dev = &usb_printer_gadget; + + DBG(dev, "%s\n", __func__); + + /* Remove sysfs files */ + device_destroy(usb_gadget_class, g_printer_devno); + + /* Remove Character Device */ + cdev_del(&dev->printer_cdev); + + /* we must already have been disconnected ... no i/o may be active */ + WARN_ON(!list_empty(&dev->tx_reqs_active)); + WARN_ON(!list_empty(&dev->rx_reqs_active)); + + /* Free all memory for this driver. */ + while (!list_empty(&dev->tx_reqs)) { + req = container_of(dev->tx_reqs.next, struct usb_request, + list); + list_del(&req->list); + printer_req_free(dev->in_ep, req); + } + + if (dev->current_rx_req != NULL) + printer_req_free(dev->out_ep, dev->current_rx_req); + + while (!list_empty(&dev->rx_reqs)) { + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del(&req->list); + printer_req_free(dev->out_ep, req); + } + + while (!list_empty(&dev->rx_buffers)) { + req = container_of(dev->rx_buffers.next, + struct usb_request, list); + list_del(&req->list); + printer_req_free(dev->out_ep, req); + } +} + +static struct usb_configuration printer_cfg_driver = { + .label = "printer", + .unbind = printer_cfg_unbind, + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, +}; + +static int __init printer_bind_config(struct usb_configuration *c) +{ + struct usb_gadget *gadget = c->cdev->gadget; + struct printer_dev *dev; + int status = -ENOMEM; + size_t len; + u32 i; + struct usb_request *req; + + usb_ep_autoconfig_reset(gadget); + + dev = &usb_printer_gadget; + + dev->function.name = shortname; + dev->function.bind = printer_func_bind; + dev->function.setup = printer_func_setup; + dev->function.unbind = printer_func_unbind; + dev->function.set_alt = printer_func_set_alt; + dev->function.disable = printer_func_disable; + + status = usb_add_function(c, &dev->function); + if (status) + return status; + + /* Setup the sysfs files for the printer gadget. */ + dev->pdev = device_create(usb_gadget_class, NULL, g_printer_devno, + NULL, "g_printer"); + if (IS_ERR(dev->pdev)) { + ERROR(dev, "Failed to create device: g_printer\n"); + goto fail; + } + + /* + * Register a character device as an interface to a user mode + * program that handles the printer specific functionality. + */ + cdev_init(&dev->printer_cdev, &printer_io_operations); + dev->printer_cdev.owner = THIS_MODULE; + status = cdev_add(&dev->printer_cdev, g_printer_devno, 1); + if (status) { + ERROR(dev, "Failed to open char device\n"); + goto fail; + } + + if (iPNPstring) + strlcpy(&pnp_string[2], iPNPstring, (sizeof pnp_string)-2); + + len = strlen(pnp_string); + pnp_string[0] = (len >> 8) & 0xFF; + pnp_string[1] = len & 0xFF; + + usb_gadget_set_selfpowered(gadget); + + if (gadget->is_otg) { + otg_descriptor.bmAttributes |= USB_OTG_HNP; + printer_cfg_driver.descriptors = otg_desc; + printer_cfg_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + spin_lock_init(&dev->lock); + mutex_init(&dev->lock_printer_io); + INIT_LIST_HEAD(&dev->tx_reqs); + INIT_LIST_HEAD(&dev->tx_reqs_active); + INIT_LIST_HEAD(&dev->rx_reqs); + INIT_LIST_HEAD(&dev->rx_reqs_active); + INIT_LIST_HEAD(&dev->rx_buffers); + init_waitqueue_head(&dev->rx_wait); + init_waitqueue_head(&dev->tx_wait); + init_waitqueue_head(&dev->tx_flush_wait); + + dev->interface = -1; + dev->printer_cdev_open = 0; + dev->printer_status = PRINTER_NOT_ERROR; + dev->current_rx_req = NULL; + dev->current_rx_bytes = 0; + dev->current_rx_buf = NULL; + + for (i = 0; i < QLEN; i++) { + req = printer_req_alloc(dev->in_ep, USB_BUFSIZE, GFP_KERNEL); + if (!req) { + while (!list_empty(&dev->tx_reqs)) { + req = container_of(dev->tx_reqs.next, + struct usb_request, list); + list_del(&req->list); + printer_req_free(dev->in_ep, req); + } + return -ENOMEM; + } + list_add(&req->list, &dev->tx_reqs); + } + + for (i = 0; i < QLEN; i++) { + req = printer_req_alloc(dev->out_ep, USB_BUFSIZE, GFP_KERNEL); + if (!req) { + while (!list_empty(&dev->rx_reqs)) { + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del(&req->list); + printer_req_free(dev->out_ep, req); + } + return -ENOMEM; + } + list_add(&req->list, &dev->rx_reqs); + } + + /* finish hookup to lower layer ... */ + dev->gadget = gadget; + + INFO(dev, "%s, version: " DRIVER_VERSION "\n", driver_desc); + return 0; + +fail: + printer_cfg_unbind(c); + return status; +} + +static int printer_unbind(struct usb_composite_dev *cdev) +{ + return 0; +} + +static int __init printer_bind(struct usb_composite_dev *cdev) +{ + int ret; + + ret = usb_string_ids_tab(cdev, strings); + if (ret < 0) + return ret; + device_desc.iManufacturer = strings[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings[USB_GADGET_PRODUCT_IDX].id; + device_desc.iSerialNumber = strings[USB_GADGET_SERIAL_IDX].id; + + ret = usb_add_config(cdev, &printer_cfg_driver, printer_bind_config); + if (ret) + return ret; + usb_composite_overwrite_options(cdev, &coverwrite); + return ret; +} + +static __refdata struct usb_composite_driver printer_driver = { + .name = shortname, + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = printer_bind, + .unbind = printer_unbind, +}; + +static int __init +init(void) +{ + int status; + + usb_gadget_class = class_create(THIS_MODULE, "usb_printer_gadget"); + if (IS_ERR(usb_gadget_class)) { + status = PTR_ERR(usb_gadget_class); + pr_err("unable to create usb_gadget class %d\n", status); + return status; + } + + status = alloc_chrdev_region(&g_printer_devno, 0, 1, + "USB printer gadget"); + if (status) { + pr_err("alloc_chrdev_region %d\n", status); + class_destroy(usb_gadget_class); + return status; + } + + status = usb_composite_probe(&printer_driver); + if (status) { + class_destroy(usb_gadget_class); + unregister_chrdev_region(g_printer_devno, 1); + pr_err("usb_gadget_probe_driver %x\n", status); + } + + return status; +} +module_init(init); + +static void __exit +cleanup(void) +{ + mutex_lock(&usb_printer_gadget.lock_printer_io); + usb_composite_unregister(&printer_driver); + unregister_chrdev_region(g_printer_devno, 1); + class_destroy(usb_gadget_class); + mutex_unlock(&usb_printer_gadget.lock_printer_io); +} +module_exit(cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Craig Nadler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/pxa25x_udc.c b/drivers/usb/gadget/pxa25x_udc.c new file mode 100644 index 00000000..95c531d5 --- /dev/null +++ b/drivers/usb/gadget/pxa25x_udc.c @@ -0,0 +1,2311 @@ +/* + * Intel PXA25x and IXP4xx on-chip full speed USB device controllers + * + * Copyright (C) 2002 Intrinsyc, Inc. (Frank Becker) + * Copyright (C) 2003 Robert Schwebel, Pengutronix + * Copyright (C) 2003 Benedikt Spranger, Pengutronix + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003 Joshua Wise + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * This driver is PXA25x only. Grab the right register definitions. + */ +#ifdef CONFIG_ARCH_PXA +#include +#endif + +#ifdef CONFIG_ARCH_LUBBOCK +#include +#endif + +/* + * This driver handles the USB Device Controller (UDC) in Intel's PXA 25x + * series processors. The UDC for the IXP 4xx series is very similar. + * There are fifteen endpoints, in addition to ep0. + * + * Such controller drivers work with a gadget driver. The gadget driver + * returns descriptors, implements configuration and data protocols used + * by the host to interact with this device, and allocates endpoints to + * the different protocol interfaces. The controller driver virtualizes + * usb hardware so that the gadget drivers will be more portable. + * + * This UDC hardware wants to implement a bit too much USB protocol, so + * it constrains the sorts of USB configuration change events that work. + * The errata for these chips are misleading; some "fixed" bugs from + * pxa250 a0/a1 b0/b1/b2 sure act like they're still there. + * + * Note that the UDC hardware supports DMA (except on IXP) but that's + * not used here. IN-DMA (to host) is simple enough, when the data is + * suitably aligned (16 bytes) ... the network stack doesn't do that, + * other software can. OUT-DMA is buggy in most chip versions, as well + * as poorly designed (data toggle not automatic). So this driver won't + * bother using DMA. (Mostly-working IN-DMA support was available in + * kernels before 2.6.23, but was never enabled or well tested.) + */ + +#define DRIVER_VERSION "30-June-2007" +#define DRIVER_DESC "PXA 25x USB Device Controller driver" + + +static const char driver_name [] = "pxa25x_udc"; + +static const char ep0name [] = "ep0"; + + +#ifdef CONFIG_ARCH_IXP4XX + +/* cpu-specific register addresses are compiled in to this code */ +#ifdef CONFIG_ARCH_PXA +#error "Can't configure both IXP and PXA" +#endif + +/* IXP doesn't yet support */ +#define clk_get(dev,name) NULL +#define clk_enable(clk) do { } while (0) +#define clk_disable(clk) do { } while (0) +#define clk_put(clk) do { } while (0) + +#endif + +#include "pxa25x_udc.h" + + +#ifdef CONFIG_USB_PXA25X_SMALL +#define SIZE_STR " (small)" +#else +#define SIZE_STR "" +#endif + +/* --------------------------------------------------------------------------- + * endpoint related parts of the api to the usb controller hardware, + * used by gadget driver; and the inner talker-to-hardware core. + * --------------------------------------------------------------------------- + */ + +static void pxa25x_ep_fifo_flush (struct usb_ep *ep); +static void nuke (struct pxa25x_ep *, int status); + +/* one GPIO should control a D+ pullup, so host sees this device (or not) */ +static void pullup_off(void) +{ + struct pxa2xx_udc_mach_info *mach = the_controller->mach; + int off_level = mach->gpio_pullup_inverted; + + if (gpio_is_valid(mach->gpio_pullup)) + gpio_set_value(mach->gpio_pullup, off_level); + else if (mach->udc_command) + mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT); +} + +static void pullup_on(void) +{ + struct pxa2xx_udc_mach_info *mach = the_controller->mach; + int on_level = !mach->gpio_pullup_inverted; + + if (gpio_is_valid(mach->gpio_pullup)) + gpio_set_value(mach->gpio_pullup, on_level); + else if (mach->udc_command) + mach->udc_command(PXA2XX_UDC_CMD_CONNECT); +} + +static void pio_irq_enable(int bEndpointAddress) +{ + bEndpointAddress &= 0xf; + if (bEndpointAddress < 8) + UICR0 &= ~(1 << bEndpointAddress); + else { + bEndpointAddress -= 8; + UICR1 &= ~(1 << bEndpointAddress); + } +} + +static void pio_irq_disable(int bEndpointAddress) +{ + bEndpointAddress &= 0xf; + if (bEndpointAddress < 8) + UICR0 |= 1 << bEndpointAddress; + else { + bEndpointAddress -= 8; + UICR1 |= 1 << bEndpointAddress; + } +} + +/* The UDCCR reg contains mask and interrupt status bits, + * so using '|=' isn't safe as it may ack an interrupt. + */ +#define UDCCR_MASK_BITS (UDCCR_REM | UDCCR_SRM | UDCCR_UDE) + +static inline void udc_set_mask_UDCCR(int mask) +{ + UDCCR = (UDCCR & UDCCR_MASK_BITS) | (mask & UDCCR_MASK_BITS); +} + +static inline void udc_clear_mask_UDCCR(int mask) +{ + UDCCR = (UDCCR & UDCCR_MASK_BITS) & ~(mask & UDCCR_MASK_BITS); +} + +static inline void udc_ack_int_UDCCR(int mask) +{ + /* udccr contains the bits we dont want to change */ + __u32 udccr = UDCCR & UDCCR_MASK_BITS; + + UDCCR = udccr | (mask & ~UDCCR_MASK_BITS); +} + +/* + * endpoint enable/disable + * + * we need to verify the descriptors used to enable endpoints. since pxa25x + * endpoint configurations are fixed, and are pretty much always enabled, + * there's not a lot to manage here. + * + * because pxa25x can't selectively initialize bulk (or interrupt) endpoints, + * (resetting endpoint halt and toggle), SET_INTERFACE is unusable except + * for a single interface (with only the default altsetting) and for gadget + * drivers that don't halt endpoints (not reset by set_interface). that also + * means that if you use ISO, you must violate the USB spec rule that all + * iso endpoints must be in non-default altsettings. + */ +static int pxa25x_ep_enable (struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct pxa25x_ep *ep; + struct pxa25x_udc *dev; + + ep = container_of (_ep, struct pxa25x_ep, ep); + if (!_ep || !desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep->fifo_size < usb_endpoint_maxp (desc)) { + DMSG("%s, bad ep or descriptor\n", __func__); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != desc->bmAttributes + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + DMSG("%s, %s type mismatch\n", __func__, _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && usb_endpoint_maxp (desc) + != BULK_FIFO_SIZE) + || !desc->wMaxPacketSize) { + DMSG("%s, bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + DMSG("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + ep->ep.desc = desc; + ep->stopped = 0; + ep->pio_irqs = 0; + ep->ep.maxpacket = usb_endpoint_maxp (desc); + + /* flush fifo (mostly for OUT buffers) */ + pxa25x_ep_fifo_flush (_ep); + + /* ... reset halt state too, if we could ... */ + + DBG(DBG_VERBOSE, "enabled %s\n", _ep->name); + return 0; +} + +static int pxa25x_ep_disable (struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + unsigned long flags; + + ep = container_of (_ep, struct pxa25x_ep, ep); + if (!_ep || !ep->ep.desc) { + DMSG("%s, %s not enabled\n", __func__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + local_irq_save(flags); + + nuke (ep, -ESHUTDOWN); + + /* flush fifo (mostly for IN buffers) */ + pxa25x_ep_fifo_flush (_ep); + + ep->ep.desc = NULL; + ep->stopped = 1; + + local_irq_restore(flags); + DBG(DBG_VERBOSE, "%s disabled\n", _ep->name); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* for the pxa25x, these can just wrap kmalloc/kfree. gadget drivers + * must still pass correctly initialized endpoints, since other controller + * drivers may care about how it's currently set up (dma issues etc). + */ + +/* + * pxa25x_ep_alloc_request - allocate a request data structure + */ +static struct usb_request * +pxa25x_ep_alloc_request (struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct pxa25x_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD (&req->queue); + return &req->req; +} + + +/* + * pxa25x_ep_free_request - deallocate a request data structure + */ +static void +pxa25x_ep_free_request (struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa25x_request *req; + + req = container_of (_req, struct pxa25x_request, req); + WARN_ON(!list_empty (&req->queue)); + kfree(req); +} + +/*-------------------------------------------------------------------------*/ + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct pxa25x_ep *ep, struct pxa25x_request *req, int status) +{ + unsigned stopped = ep->stopped; + + list_del_init(&req->queue); + + if (likely (req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + DBG(DBG_VERBOSE, "complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + req->req.complete(&ep->ep, &req->req); + ep->stopped = stopped; +} + + +static inline void ep0_idle (struct pxa25x_udc *dev) +{ + dev->ep0state = EP0_IDLE; +} + +static int +write_packet(volatile u32 *uddr, struct pxa25x_request *req, unsigned max) +{ + u8 *buf; + unsigned length, count; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + /* how big will this packet be? */ + length = min(req->req.length - req->req.actual, max); + req->req.actual += length; + + count = length; + while (likely(count--)) + *uddr = *buf++; + + return length; +} + +/* + * write to an IN endpoint fifo, as many packets as possible. + * irqs will use this to write the rest later. + * caller guarantees at least one packet buffer is ready (or a zlp). + */ +static int +write_fifo (struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + unsigned max; + + max = usb_endpoint_maxp(ep->ep.desc); + do { + unsigned count; + int is_last, is_short; + + count = write_packet(ep->reg_uddr, req, max); + + /* last packet is usually short (or a zlp) */ + if (unlikely (count != max)) + is_last = is_short = 1; + else { + if (likely(req->req.length != req->req.actual) + || req->req.zero) + is_last = 0; + else + is_last = 1; + /* interrupt/iso maxpacket may not fill the fifo */ + is_short = unlikely (max < ep->fifo_size); + } + + DBG(DBG_VERY_NOISY, "wrote %s %d bytes%s%s %d left %p\n", + ep->ep.name, count, + is_last ? "/L" : "", is_short ? "/S" : "", + req->req.length - req->req.actual, req); + + /* let loose that packet. maybe try writing another one, + * double buffering might work. TSP, TPC, and TFS + * bit values are the same for all normal IN endpoints. + */ + *ep->reg_udccs = UDCCS_BI_TPC; + if (is_short) + *ep->reg_udccs = UDCCS_BI_TSP; + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + done (ep, req, 0); + if (list_empty(&ep->queue)) + pio_irq_disable (ep->bEndpointAddress); + return 1; + } + + // TODO experiment: how robust can fifo mode tweaking be? + // double buffering is off in the default fifo mode, which + // prevents TFS from being set here. + + } while (*ep->reg_udccs & UDCCS_BI_TFS); + return 0; +} + +/* caller asserts req->pending (ep0 irq status nyet cleared); starts + * ep0 data stage. these chips want very simple state transitions. + */ +static inline +void ep0start(struct pxa25x_udc *dev, u32 flags, const char *tag) +{ + UDCCS0 = flags|UDCCS0_SA|UDCCS0_OPR; + USIR0 = USIR0_IR0; + dev->req_pending = 0; + DBG(DBG_VERY_NOISY, "%s %s, %02x/%02x\n", + __func__, tag, UDCCS0, flags); +} + +static int +write_ep0_fifo (struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + unsigned count; + int is_short; + + count = write_packet(&UDDR0, req, EP0_FIFO_SIZE); + ep->dev->stats.write.bytes += count; + + /* last packet "must be" short (or a zlp) */ + is_short = (count != EP0_FIFO_SIZE); + + DBG(DBG_VERY_NOISY, "ep0in %d bytes %d left %p\n", count, + req->req.length - req->req.actual, req); + + if (unlikely (is_short)) { + if (ep->dev->req_pending) + ep0start(ep->dev, UDCCS0_IPR, "short IN"); + else + UDCCS0 = UDCCS0_IPR; + + count = req->req.length; + done (ep, req, 0); + ep0_idle(ep->dev); +#ifndef CONFIG_ARCH_IXP4XX +#if 1 + /* This seems to get rid of lost status irqs in some cases: + * host responds quickly, or next request involves config + * change automagic, or should have been hidden, or ... + * + * FIXME get rid of all udelays possible... + */ + if (count >= EP0_FIFO_SIZE) { + count = 100; + do { + if ((UDCCS0 & UDCCS0_OPR) != 0) { + /* clear OPR, generate ack */ + UDCCS0 = UDCCS0_OPR; + break; + } + count--; + udelay(1); + } while (count); + } +#endif +#endif + } else if (ep->dev->req_pending) + ep0start(ep->dev, 0, "IN"); + return is_short; +} + + +/* + * read_fifo - unload packet(s) from the fifo we use for usb OUT + * transfers and put them into the request. caller should have made + * sure there's at least one packet ready. + * + * returns true if the request completed because of short packet or the + * request buffer having filled (and maybe overran till end-of-packet). + */ +static int +read_fifo (struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + for (;;) { + u32 udccs; + u8 *buf; + unsigned bufferspace, count, is_short; + + /* make sure there's a packet in the FIFO. + * UDCCS_{BO,IO}_RPC are all the same bit value. + * UDCCS_{BO,IO}_RNE are all the same bit value. + */ + udccs = *ep->reg_udccs; + if (unlikely ((udccs & UDCCS_BO_RPC) == 0)) + break; + buf = req->req.buf + req->req.actual; + prefetchw(buf); + bufferspace = req->req.length - req->req.actual; + + /* read all bytes from this packet */ + if (likely (udccs & UDCCS_BO_RNE)) { + count = 1 + (0x0ff & *ep->reg_ubcr); + req->req.actual += min (count, bufferspace); + } else /* zlp */ + count = 0; + is_short = (count < ep->ep.maxpacket); + DBG(DBG_VERY_NOISY, "read %s %02x, %d bytes%s req %p %d/%d\n", + ep->ep.name, udccs, count, + is_short ? "/S" : "", + req, req->req.actual, req->req.length); + while (likely (count-- != 0)) { + u8 byte = (u8) *ep->reg_uddr; + + if (unlikely (bufferspace == 0)) { + /* this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->req.status != -EOVERFLOW) + DMSG("%s overflow %d\n", + ep->ep.name, count); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + bufferspace--; + } + } + *ep->reg_udccs = UDCCS_BO_RPC; + /* RPC/RSP/RNE could now reflect the other packet buffer */ + + /* iso is one request per packet */ + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (udccs & UDCCS_IO_ROF) + req->req.status = -EHOSTUNREACH; + /* more like "is_done" */ + is_short = 1; + } + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + done (ep, req, 0); + if (list_empty(&ep->queue)) + pio_irq_disable (ep->bEndpointAddress); + return 1; + } + + /* finished that packet. the next one may be waiting... */ + } + return 0; +} + +/* + * special ep0 version of the above. no UBCR0 or double buffering; status + * handshaking is magic. most device protocols don't need control-OUT. + * CDC vendor commands (and RNDIS), mass storage CB/CBI, and some other + * protocols do use them. + */ +static int +read_ep0_fifo (struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + u8 *buf, byte; + unsigned bufferspace; + + buf = req->req.buf + req->req.actual; + bufferspace = req->req.length - req->req.actual; + + while (UDCCS0 & UDCCS0_RNE) { + byte = (u8) UDDR0; + + if (unlikely (bufferspace == 0)) { + /* this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->req.status != -EOVERFLOW) + DMSG("%s overflow\n", ep->ep.name); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + req->req.actual++; + bufferspace--; + } + } + + UDCCS0 = UDCCS0_OPR | UDCCS0_IPR; + + /* completion */ + if (req->req.actual >= req->req.length) + return 1; + + /* finished that packet. the next one may be waiting... */ + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int +pxa25x_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct pxa25x_request *req; + struct pxa25x_ep *ep; + struct pxa25x_udc *dev; + unsigned long flags; + + req = container_of(_req, struct pxa25x_request, req); + if (unlikely (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue))) { + DMSG("%s, bad params\n", __func__); + return -EINVAL; + } + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (unlikely(!_ep || (!ep->ep.desc && ep->ep.name != ep0name))) { + DMSG("%s, bad ep\n", __func__); + return -EINVAL; + } + + dev = ep->dev; + if (unlikely (!dev->driver + || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + DMSG("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + /* iso is always one packet per request, that's the only way + * we can report per-packet status. that also helps with dma. + */ + if (unlikely (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + && req->req.length > usb_endpoint_maxp(ep->ep.desc))) + return -EMSGSIZE; + + DBG(DBG_NOISY, "%s queue req %p, len %d buf %p\n", + _ep->name, _req, _req->length, _req->buf); + + local_irq_save(flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + if (list_empty(&ep->queue) && !ep->stopped) { + if (ep->ep.desc == NULL/* ep0 */) { + unsigned length = _req->length; + + switch (dev->ep0state) { + case EP0_IN_DATA_PHASE: + dev->stats.write.ops++; + if (write_ep0_fifo(ep, req)) + req = NULL; + break; + + case EP0_OUT_DATA_PHASE: + dev->stats.read.ops++; + /* messy ... */ + if (dev->req_config) { + DBG(DBG_VERBOSE, "ep0 config ack%s\n", + dev->has_cfr ? "" : " raced"); + if (dev->has_cfr) + UDCCFR = UDCCFR_AREN|UDCCFR_ACM + |UDCCFR_MB1; + done(ep, req, 0); + dev->ep0state = EP0_END_XFER; + local_irq_restore (flags); + return 0; + } + if (dev->req_pending) + ep0start(dev, UDCCS0_IPR, "OUT"); + if (length == 0 || ((UDCCS0 & UDCCS0_RNE) != 0 + && read_ep0_fifo(ep, req))) { + ep0_idle(dev); + done(ep, req, 0); + req = NULL; + } + break; + + default: + DMSG("ep0 i/o, odd state %d\n", dev->ep0state); + local_irq_restore (flags); + return -EL2HLT; + } + /* can the FIFO can satisfy the request immediately? */ + } else if ((ep->bEndpointAddress & USB_DIR_IN) != 0) { + if ((*ep->reg_udccs & UDCCS_BI_TFS) != 0 + && write_fifo(ep, req)) + req = NULL; + } else if ((*ep->reg_udccs & UDCCS_BO_RFS) != 0 + && read_fifo(ep, req)) { + req = NULL; + } + + if (likely(req && ep->ep.desc)) + pio_irq_enable(ep->bEndpointAddress); + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != NULL)) + list_add_tail(&req->queue, &ep->queue); + local_irq_restore(flags); + + return 0; +} + + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct pxa25x_ep *ep, int status) +{ + struct pxa25x_request *req; + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct pxa25x_request, + queue); + done(ep, req, status); + } + if (ep->ep.desc) + pio_irq_disable (ep->bEndpointAddress); +} + + +/* dequeue JUST ONE request */ +static int pxa25x_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa25x_ep *ep; + struct pxa25x_request *req; + unsigned long flags; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + local_irq_save(flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry (req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + local_irq_restore(flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + local_irq_restore(flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int pxa25x_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct pxa25x_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (unlikely (!_ep + || (!ep->ep.desc && ep->ep.name != ep0name)) + || ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + DMSG("%s, bad ep\n", __func__); + return -EINVAL; + } + if (value == 0) { + /* this path (reset toggle+halt) is needed to implement + * SET_INTERFACE on normal hardware. but it can't be + * done from software on the PXA UDC, and the hardware + * forgets to do it as part of SET_INTERFACE automagic. + */ + DMSG("only host can clear %s halt\n", _ep->name); + return -EROFS; + } + + local_irq_save(flags); + + if ((ep->bEndpointAddress & USB_DIR_IN) != 0 + && ((*ep->reg_udccs & UDCCS_BI_TFS) == 0 + || !list_empty(&ep->queue))) { + local_irq_restore(flags); + return -EAGAIN; + } + + /* FST bit is the same for control, bulk in, bulk out, interrupt in */ + *ep->reg_udccs = UDCCS_BI_FST|UDCCS_BI_FTF; + + /* ep0 needs special care */ + if (!ep->ep.desc) { + start_watchdog(ep->dev); + ep->dev->req_pending = 0; + ep->dev->ep0state = EP0_STALL; + + /* and bulk/intr endpoints like dropping stalls too */ + } else { + unsigned i; + for (i = 0; i < 1000; i += 20) { + if (*ep->reg_udccs & UDCCS_BI_SST) + break; + udelay(20); + } + } + local_irq_restore(flags); + + DBG(DBG_VERBOSE, "%s halt\n", _ep->name); + return 0; +} + +static int pxa25x_ep_fifo_status(struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep) { + DMSG("%s, bad ep\n", __func__); + return -ENODEV; + } + /* pxa can't report unclaimed bytes from IN fifos */ + if ((ep->bEndpointAddress & USB_DIR_IN) != 0) + return -EOPNOTSUPP; + if (ep->dev->gadget.speed == USB_SPEED_UNKNOWN + || (*ep->reg_udccs & UDCCS_BO_RFS) == 0) + return 0; + else + return (*ep->reg_ubcr & 0xfff) + 1; +} + +static void pxa25x_ep_fifo_flush(struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || ep->ep.name == ep0name || !list_empty(&ep->queue)) { + DMSG("%s, bad ep\n", __func__); + return; + } + + /* toggle and halt bits stay unchanged */ + + /* for OUT, just read and discard the FIFO contents. */ + if ((ep->bEndpointAddress & USB_DIR_IN) == 0) { + while (((*ep->reg_udccs) & UDCCS_BO_RNE) != 0) + (void) *ep->reg_uddr; + return; + } + + /* most IN status is the same, but ISO can't stall */ + *ep->reg_udccs = UDCCS_BI_TPC|UDCCS_BI_FTF|UDCCS_BI_TUR + | (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + ? 0 : UDCCS_BI_SST); +} + + +static struct usb_ep_ops pxa25x_ep_ops = { + .enable = pxa25x_ep_enable, + .disable = pxa25x_ep_disable, + + .alloc_request = pxa25x_ep_alloc_request, + .free_request = pxa25x_ep_free_request, + + .queue = pxa25x_ep_queue, + .dequeue = pxa25x_ep_dequeue, + + .set_halt = pxa25x_ep_set_halt, + .fifo_status = pxa25x_ep_fifo_status, + .fifo_flush = pxa25x_ep_fifo_flush, +}; + + +/* --------------------------------------------------------------------------- + * device-scoped parts of the api to the usb controller hardware + * --------------------------------------------------------------------------- + */ + +static int pxa25x_udc_get_frame(struct usb_gadget *_gadget) +{ + return ((UFNRH & 0x07) << 8) | (UFNRL & 0xff); +} + +static int pxa25x_udc_wakeup(struct usb_gadget *_gadget) +{ + /* host may not have enabled remote wakeup */ + if ((UDCCS0 & UDCCS0_DRWF) == 0) + return -EHOSTUNREACH; + udc_set_mask_UDCCR(UDCCR_RSM); + return 0; +} + +static void stop_activity(struct pxa25x_udc *, struct usb_gadget_driver *); +static void udc_enable (struct pxa25x_udc *); +static void udc_disable(struct pxa25x_udc *); + +/* We disable the UDC -- and its 48 MHz clock -- whenever it's not + * in active use. + */ +static int pullup(struct pxa25x_udc *udc) +{ + int is_active = udc->vbus && udc->pullup && !udc->suspended; + DMSG("%s\n", is_active ? "active" : "inactive"); + if (is_active) { + if (!udc->active) { + udc->active = 1; + /* Enable clock for USB device */ + clk_enable(udc->clk); + udc_enable(udc); + } + } else { + if (udc->active) { + if (udc->gadget.speed != USB_SPEED_UNKNOWN) { + DMSG("disconnect %s\n", udc->driver + ? udc->driver->driver.name + : "(no driver)"); + stop_activity(udc, udc->driver); + } + udc_disable(udc); + /* Disable clock for USB device */ + clk_disable(udc->clk); + udc->active = 0; + } + + } + return 0; +} + +/* VBUS reporting logically comes from a transceiver */ +static int pxa25x_udc_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct pxa25x_udc *udc; + + udc = container_of(_gadget, struct pxa25x_udc, gadget); + udc->vbus = is_active; + DMSG("vbus %s\n", is_active ? "supplied" : "inactive"); + pullup(udc); + return 0; +} + +/* drivers may have software control over D+ pullup */ +static int pxa25x_udc_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct pxa25x_udc *udc; + + udc = container_of(_gadget, struct pxa25x_udc, gadget); + + /* not all boards support pullup control */ + if (!gpio_is_valid(udc->mach->gpio_pullup) && !udc->mach->udc_command) + return -EOPNOTSUPP; + + udc->pullup = (is_active != 0); + pullup(udc); + return 0; +} + +/* boards may consume current from VBUS, up to 100-500mA based on config. + * the 500uA suspend ceiling means that exclusively vbus-powered PXA designs + * violate USB specs. + */ +static int pxa25x_udc_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +{ + struct pxa25x_udc *udc; + + udc = container_of(_gadget, struct pxa25x_udc, gadget); + + if (!IS_ERR_OR_NULL(udc->transceiver)) + return usb_phy_set_power(udc->transceiver, mA); + return -EOPNOTSUPP; +} + +static int pxa25x_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int pxa25x_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops pxa25x_udc_ops = { + .get_frame = pxa25x_udc_get_frame, + .wakeup = pxa25x_udc_wakeup, + .vbus_session = pxa25x_udc_vbus_session, + .pullup = pxa25x_udc_pullup, + .vbus_draw = pxa25x_udc_vbus_draw, + .udc_start = pxa25x_udc_start, + .udc_stop = pxa25x_udc_stop, +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_GADGET_DEBUG_FS + +static int +udc_seq_show(struct seq_file *m, void *_d) +{ + struct pxa25x_udc *dev = m->private; + unsigned long flags; + int i; + u32 tmp; + + local_irq_save(flags); + + /* basic device status */ + seq_printf(m, DRIVER_DESC "\n" + "%s version: %s\nGadget driver: %s\nHost %s\n\n", + driver_name, DRIVER_VERSION SIZE_STR "(pio)", + dev->driver ? dev->driver->driver.name : "(none)", + dev->gadget.speed == USB_SPEED_FULL ? "full speed" : "disconnected"); + + /* registers for device and ep0 */ + seq_printf(m, + "uicr %02X.%02X, usir %02X.%02x, ufnr %02X.%02X\n", + UICR1, UICR0, USIR1, USIR0, UFNRH, UFNRL); + + tmp = UDCCR; + seq_printf(m, + "udccr %02X =%s%s%s%s%s%s%s%s\n", tmp, + (tmp & UDCCR_REM) ? " rem" : "", + (tmp & UDCCR_RSTIR) ? " rstir" : "", + (tmp & UDCCR_SRM) ? " srm" : "", + (tmp & UDCCR_SUSIR) ? " susir" : "", + (tmp & UDCCR_RESIR) ? " resir" : "", + (tmp & UDCCR_RSM) ? " rsm" : "", + (tmp & UDCCR_UDA) ? " uda" : "", + (tmp & UDCCR_UDE) ? " ude" : ""); + + tmp = UDCCS0; + seq_printf(m, + "udccs0 %02X =%s%s%s%s%s%s%s%s\n", tmp, + (tmp & UDCCS0_SA) ? " sa" : "", + (tmp & UDCCS0_RNE) ? " rne" : "", + (tmp & UDCCS0_FST) ? " fst" : "", + (tmp & UDCCS0_SST) ? " sst" : "", + (tmp & UDCCS0_DRWF) ? " dwrf" : "", + (tmp & UDCCS0_FTF) ? " ftf" : "", + (tmp & UDCCS0_IPR) ? " ipr" : "", + (tmp & UDCCS0_OPR) ? " opr" : ""); + + if (dev->has_cfr) { + tmp = UDCCFR; + seq_printf(m, + "udccfr %02X =%s%s\n", tmp, + (tmp & UDCCFR_AREN) ? " aren" : "", + (tmp & UDCCFR_ACM) ? " acm" : ""); + } + + if (dev->gadget.speed != USB_SPEED_FULL || !dev->driver) + goto done; + + seq_printf(m, "ep0 IN %lu/%lu, OUT %lu/%lu\nirqs %lu\n\n", + dev->stats.write.bytes, dev->stats.write.ops, + dev->stats.read.bytes, dev->stats.read.ops, + dev->stats.irqs); + + /* dump endpoint queues */ + for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { + struct pxa25x_ep *ep = &dev->ep [i]; + struct pxa25x_request *req; + + if (i != 0) { + const struct usb_endpoint_descriptor *desc; + + desc = ep->ep.desc; + if (!desc) + continue; + tmp = *dev->ep [i].reg_udccs; + seq_printf(m, + "%s max %d %s udccs %02x irqs %lu\n", + ep->ep.name, usb_endpoint_maxp(desc), + "pio", tmp, ep->pio_irqs); + /* TODO translate all five groups of udccs bits! */ + + } else /* ep0 should only have one transfer queued */ + seq_printf(m, "ep0 max 16 pio irqs %lu\n", + ep->pio_irqs); + + if (list_empty(&ep->queue)) { + seq_printf(m, "\t(nothing queued)\n"); + continue; + } + list_for_each_entry(req, &ep->queue, queue) { + seq_printf(m, + "\treq %p len %d/%d buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + } + } + +done: + local_irq_restore(flags); + return 0; +} + +static int +udc_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, udc_seq_show, inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = udc_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +#define create_debug_files(dev) \ + do { \ + dev->debugfs_udc = debugfs_create_file(dev->gadget.name, \ + S_IRUGO, NULL, dev, &debug_fops); \ + } while (0) +#define remove_debug_files(dev) \ + do { \ + if (dev->debugfs_udc) \ + debugfs_remove(dev->debugfs_udc); \ + } while (0) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_debug_files(dev) do {} while (0) +#define remove_debug_files(dev) do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +/*-------------------------------------------------------------------------*/ + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct pxa25x_udc *dev) +{ + /* block all irqs */ + udc_set_mask_UDCCR(UDCCR_SRM|UDCCR_REM); + UICR0 = UICR1 = 0xff; + UFNRH = UFNRH_SIM; + + /* if hardware supports it, disconnect from usb */ + pullup_off(); + + udc_clear_mask_UDCCR(UDCCR_UDE); + + ep0_idle (dev); + dev->gadget.speed = USB_SPEED_UNKNOWN; +} + + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct pxa25x_udc *dev) +{ + u32 i; + + /* device/ep0 records init */ + INIT_LIST_HEAD (&dev->gadget.ep_list); + INIT_LIST_HEAD (&dev->gadget.ep0->ep_list); + dev->ep0state = EP0_IDLE; + + /* basic endpoint records init */ + for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { + struct pxa25x_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->ep.desc = NULL; + ep->stopped = 0; + INIT_LIST_HEAD (&ep->queue); + ep->pio_irqs = 0; + } + + /* the rest was statically initialized, and is read-only */ +} + +/* until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static void udc_enable (struct pxa25x_udc *dev) +{ + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* try to clear these bits before we enable the udc */ + udc_ack_int_UDCCR(UDCCR_SUSIR|/*UDCCR_RSTIR|*/UDCCR_RESIR); + + ep0_idle(dev); + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->stats.irqs = 0; + + /* + * sequence taken from chapter 12.5.10, PXA250 AppProcDevManual: + * - enable UDC + * - if RESET is already in progress, ack interrupt + * - unmask reset interrupt + */ + udc_set_mask_UDCCR(UDCCR_UDE); + if (!(UDCCR & UDCCR_UDA)) + udc_ack_int_UDCCR(UDCCR_RSTIR); + + if (dev->has_cfr /* UDC_RES2 is defined */) { + /* pxa255 (a0+) can avoid a set_config race that could + * prevent gadget drivers from configuring correctly + */ + UDCCFR = UDCCFR_ACM | UDCCFR_MB1; + } else { + /* "USB test mode" for pxa250 errata 40-42 (stepping a0, a1) + * which could result in missing packets and interrupts. + * supposedly one bit per endpoint, controlling whether it + * double buffers or not; ACM/AREN bits fit into the holes. + * zero bits (like USIR0_IRx) disable double buffering. + */ + UDC_RES1 = 0x00; + UDC_RES2 = 0x00; + } + + /* enable suspend/resume and reset irqs */ + udc_clear_mask_UDCCR(UDCCR_SRM | UDCCR_REM); + + /* enable ep0 irqs */ + UICR0 &= ~UICR0_IM0; + + /* if hardware supports it, pullup D+ and wait for reset */ + pullup_on(); +} + + +/* when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ +static int pxa25x_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct pxa25x_udc *dev = to_pxa25x(g); + int retval; + + /* first hook up the driver ... */ + dev->driver = driver; + dev->pullup = 1; + + /* ... then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + /* connect to bus through transceiver */ + if (!IS_ERR_OR_NULL(dev->transceiver)) { + retval = otg_set_peripheral(dev->transceiver->otg, + &dev->gadget); + if (retval) + goto bind_fail; + } + + pullup(dev); + dump_state(dev); + return 0; +bind_fail: + return retval; +} + +static void +stop_activity(struct pxa25x_udc *dev, struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { + struct pxa25x_ep *ep = &dev->ep[i]; + + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + del_timer_sync(&dev->timer); + + /* report disconnect; the driver is already quiesced */ + if (driver) + driver->disconnect(&dev->gadget); + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +static int pxa25x_udc_stop(struct usb_gadget*g, + struct usb_gadget_driver *driver) +{ + struct pxa25x_udc *dev = to_pxa25x(g); + + local_irq_disable(); + dev->pullup = 0; + pullup(dev); + stop_activity(dev, driver); + local_irq_enable(); + + if (!IS_ERR_OR_NULL(dev->transceiver)) + (void) otg_set_peripheral(dev->transceiver->otg, NULL); + + dev->driver = NULL; + + dump_state(dev); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_ARCH_LUBBOCK + +/* Lubbock has separate connect and disconnect irqs. More typical designs + * use one GPIO as the VBUS IRQ, and another to control the D+ pullup. + */ + +static irqreturn_t +lubbock_vbus_irq(int irq, void *_dev) +{ + struct pxa25x_udc *dev = _dev; + int vbus; + + dev->stats.irqs++; + switch (irq) { + case LUBBOCK_USB_IRQ: + vbus = 1; + disable_irq(LUBBOCK_USB_IRQ); + enable_irq(LUBBOCK_USB_DISC_IRQ); + break; + case LUBBOCK_USB_DISC_IRQ: + vbus = 0; + disable_irq(LUBBOCK_USB_DISC_IRQ); + enable_irq(LUBBOCK_USB_IRQ); + break; + default: + return IRQ_NONE; + } + + pxa25x_udc_vbus_session(&dev->gadget, vbus); + return IRQ_HANDLED; +} + +#endif + + +/*-------------------------------------------------------------------------*/ + +static inline void clear_ep_state (struct pxa25x_udc *dev) +{ + unsigned i; + + /* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint + * fifos, and pending transactions mustn't be continued in any case. + */ + for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) + nuke(&dev->ep[i], -ECONNABORTED); +} + +static void udc_watchdog(unsigned long _dev) +{ + struct pxa25x_udc *dev = (void *)_dev; + + local_irq_disable(); + if (dev->ep0state == EP0_STALL + && (UDCCS0 & UDCCS0_FST) == 0 + && (UDCCS0 & UDCCS0_SST) == 0) { + UDCCS0 = UDCCS0_FST|UDCCS0_FTF; + DBG(DBG_VERBOSE, "ep0 re-stall\n"); + start_watchdog(dev); + } + local_irq_enable(); +} + +static void handle_ep0 (struct pxa25x_udc *dev) +{ + u32 udccs0 = UDCCS0; + struct pxa25x_ep *ep = &dev->ep [0]; + struct pxa25x_request *req; + union { + struct usb_ctrlrequest r; + u8 raw [8]; + u32 word [2]; + } u; + + if (list_empty(&ep->queue)) + req = NULL; + else + req = list_entry(ep->queue.next, struct pxa25x_request, queue); + + /* clear stall status */ + if (udccs0 & UDCCS0_SST) { + nuke(ep, -EPIPE); + UDCCS0 = UDCCS0_SST; + del_timer(&dev->timer); + ep0_idle(dev); + } + + /* previous request unfinished? non-error iff back-to-back ... */ + if ((udccs0 & UDCCS0_SA) != 0 && dev->ep0state != EP0_IDLE) { + nuke(ep, 0); + del_timer(&dev->timer); + ep0_idle(dev); + } + + switch (dev->ep0state) { + case EP0_IDLE: + /* late-breaking status? */ + udccs0 = UDCCS0; + + /* start control request? */ + if (likely((udccs0 & (UDCCS0_OPR|UDCCS0_SA|UDCCS0_RNE)) + == (UDCCS0_OPR|UDCCS0_SA|UDCCS0_RNE))) { + int i; + + nuke (ep, -EPROTO); + + /* read SETUP packet */ + for (i = 0; i < 8; i++) { + if (unlikely(!(UDCCS0 & UDCCS0_RNE))) { +bad_setup: + DMSG("SETUP %d!\n", i); + goto stall; + } + u.raw [i] = (u8) UDDR0; + } + if (unlikely((UDCCS0 & UDCCS0_RNE) != 0)) + goto bad_setup; + +got_setup: + DBG(DBG_VERBOSE, "SETUP %02x.%02x v%04x i%04x l%04x\n", + u.r.bRequestType, u.r.bRequest, + le16_to_cpu(u.r.wValue), + le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wLength)); + + /* cope with automagic for some standard requests. */ + dev->req_std = (u.r.bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD; + dev->req_config = 0; + dev->req_pending = 1; + switch (u.r.bRequest) { + /* hardware restricts gadget drivers here! */ + case USB_REQ_SET_CONFIGURATION: + if (u.r.bRequestType == USB_RECIP_DEVICE) { + /* reflect hardware's automagic + * up to the gadget driver. + */ +config_change: + dev->req_config = 1; + clear_ep_state(dev); + /* if !has_cfr, there's no synch + * else use AREN (later) not SA|OPR + * USIR0_IR0 acts edge sensitive + */ + } + break; + /* ... and here, even more ... */ + case USB_REQ_SET_INTERFACE: + if (u.r.bRequestType == USB_RECIP_INTERFACE) { + /* udc hardware is broken by design: + * - altsetting may only be zero; + * - hw resets all interfaces' eps; + * - ep reset doesn't include halt(?). + */ + DMSG("broken set_interface (%d/%d)\n", + le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wValue)); + goto config_change; + } + break; + /* hardware was supposed to hide this */ + case USB_REQ_SET_ADDRESS: + if (u.r.bRequestType == USB_RECIP_DEVICE) { + ep0start(dev, 0, "address"); + return; + } + break; + } + + if (u.r.bRequestType & USB_DIR_IN) + dev->ep0state = EP0_IN_DATA_PHASE; + else + dev->ep0state = EP0_OUT_DATA_PHASE; + + i = dev->driver->setup(&dev->gadget, &u.r); + if (i < 0) { + /* hardware automagic preventing STALL... */ + if (dev->req_config) { + /* hardware sometimes neglects to tell + * tell us about config change events, + * so later ones may fail... + */ + WARNING("config change %02x fail %d?\n", + u.r.bRequest, i); + return; + /* TODO experiment: if has_cfr, + * hardware didn't ACK; maybe we + * could actually STALL! + */ + } + DBG(DBG_VERBOSE, "protocol STALL, " + "%02x err %d\n", UDCCS0, i); +stall: + /* the watchdog timer helps deal with cases + * where udc seems to clear FST wrongly, and + * then NAKs instead of STALLing. + */ + ep0start(dev, UDCCS0_FST|UDCCS0_FTF, "stall"); + start_watchdog(dev); + dev->ep0state = EP0_STALL; + + /* deferred i/o == no response yet */ + } else if (dev->req_pending) { + if (likely(dev->ep0state == EP0_IN_DATA_PHASE + || dev->req_std || u.r.wLength)) + ep0start(dev, 0, "defer"); + else + ep0start(dev, UDCCS0_IPR, "defer/IPR"); + } + + /* expect at least one data or status stage irq */ + return; + + } else if (likely((udccs0 & (UDCCS0_OPR|UDCCS0_SA)) + == (UDCCS0_OPR|UDCCS0_SA))) { + unsigned i; + + /* pxa210/250 erratum 131 for B0/B1 says RNE lies. + * still observed on a pxa255 a0. + */ + DBG(DBG_VERBOSE, "e131\n"); + nuke(ep, -EPROTO); + + /* read SETUP data, but don't trust it too much */ + for (i = 0; i < 8; i++) + u.raw [i] = (u8) UDDR0; + if ((u.r.bRequestType & USB_RECIP_MASK) + > USB_RECIP_OTHER) + goto stall; + if (u.word [0] == 0 && u.word [1] == 0) + goto stall; + goto got_setup; + } else { + /* some random early IRQ: + * - we acked FST + * - IPR cleared + * - OPR got set, without SA (likely status stage) + */ + UDCCS0 = udccs0 & (UDCCS0_SA|UDCCS0_OPR); + } + break; + case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ + if (udccs0 & UDCCS0_OPR) { + UDCCS0 = UDCCS0_OPR|UDCCS0_FTF; + DBG(DBG_VERBOSE, "ep0in premature status\n"); + if (req) + done(ep, req, 0); + ep0_idle(dev); + } else /* irq was IPR clearing */ { + if (req) { + /* this IN packet might finish the request */ + (void) write_ep0_fifo(ep, req); + } /* else IN token before response was written */ + } + break; + case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ + if (udccs0 & UDCCS0_OPR) { + if (req) { + /* this OUT packet might finish the request */ + if (read_ep0_fifo(ep, req)) + done(ep, req, 0); + /* else more OUT packets expected */ + } /* else OUT token before read was issued */ + } else /* irq was IPR clearing */ { + DBG(DBG_VERBOSE, "ep0out premature status\n"); + if (req) + done(ep, req, 0); + ep0_idle(dev); + } + break; + case EP0_END_XFER: + if (req) + done(ep, req, 0); + /* ack control-IN status (maybe in-zlp was skipped) + * also appears after some config change events. + */ + if (udccs0 & UDCCS0_OPR) + UDCCS0 = UDCCS0_OPR; + ep0_idle(dev); + break; + case EP0_STALL: + UDCCS0 = UDCCS0_FST; + break; + } + USIR0 = USIR0_IR0; +} + +static void handle_ep(struct pxa25x_ep *ep) +{ + struct pxa25x_request *req; + int is_in = ep->bEndpointAddress & USB_DIR_IN; + int completed; + u32 udccs, tmp; + + do { + completed = 0; + if (likely (!list_empty(&ep->queue))) + req = list_entry(ep->queue.next, + struct pxa25x_request, queue); + else + req = NULL; + + // TODO check FST handling + + udccs = *ep->reg_udccs; + if (unlikely(is_in)) { /* irq from TPC, SST, or (ISO) TUR */ + tmp = UDCCS_BI_TUR; + if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) + tmp |= UDCCS_BI_SST; + tmp &= udccs; + if (likely (tmp)) + *ep->reg_udccs = tmp; + if (req && likely ((udccs & UDCCS_BI_TFS) != 0)) + completed = write_fifo(ep, req); + + } else { /* irq from RPC (or for ISO, ROF) */ + if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) + tmp = UDCCS_BO_SST | UDCCS_BO_DME; + else + tmp = UDCCS_IO_ROF | UDCCS_IO_DME; + tmp &= udccs; + if (likely(tmp)) + *ep->reg_udccs = tmp; + + /* fifos can hold packets, ready for reading... */ + if (likely(req)) { + completed = read_fifo(ep, req); + } else + pio_irq_disable (ep->bEndpointAddress); + } + ep->pio_irqs++; + } while (completed); +} + +/* + * pxa25x_udc_irq - interrupt handler + * + * avoid delays in ep0 processing. the control handshaking isn't always + * under software control (pxa250c0 and the pxa255 are better), and delays + * could cause usb protocol errors. + */ +static irqreturn_t +pxa25x_udc_irq(int irq, void *_dev) +{ + struct pxa25x_udc *dev = _dev; + int handled; + + dev->stats.irqs++; + do { + u32 udccr = UDCCR; + + handled = 0; + + /* SUSpend Interrupt Request */ + if (unlikely(udccr & UDCCR_SUSIR)) { + udc_ack_int_UDCCR(UDCCR_SUSIR); + handled = 1; + DBG(DBG_VERBOSE, "USB suspend\n"); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + ep0_idle (dev); + } + + /* RESume Interrupt Request */ + if (unlikely(udccr & UDCCR_RESIR)) { + udc_ack_int_UDCCR(UDCCR_RESIR); + handled = 1; + DBG(DBG_VERBOSE, "USB resume\n"); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) + dev->driver->resume(&dev->gadget); + } + + /* ReSeT Interrupt Request - USB reset */ + if (unlikely(udccr & UDCCR_RSTIR)) { + udc_ack_int_UDCCR(UDCCR_RSTIR); + handled = 1; + + if ((UDCCR & UDCCR_UDA) == 0) { + DBG(DBG_VERBOSE, "USB reset start\n"); + + /* reset driver and endpoints, + * in case that's not yet done + */ + stop_activity (dev, dev->driver); + + } else { + DBG(DBG_VERBOSE, "USB reset end\n"); + dev->gadget.speed = USB_SPEED_FULL; + memset(&dev->stats, 0, sizeof dev->stats); + /* driver and endpoints are still reset */ + } + + } else { + u32 usir0 = USIR0 & ~UICR0; + u32 usir1 = USIR1 & ~UICR1; + int i; + + if (unlikely (!usir0 && !usir1)) + continue; + + DBG(DBG_VERY_NOISY, "irq %02x.%02x\n", usir1, usir0); + + /* control traffic */ + if (usir0 & USIR0_IR0) { + dev->ep[0].pio_irqs++; + handle_ep0(dev); + handled = 1; + } + + /* endpoint data transfers */ + for (i = 0; i < 8; i++) { + u32 tmp = 1 << i; + + if (i && (usir0 & tmp)) { + handle_ep(&dev->ep[i]); + USIR0 |= tmp; + handled = 1; + } +#ifndef CONFIG_USB_PXA25X_SMALL + if (usir1 & tmp) { + handle_ep(&dev->ep[i+8]); + USIR1 |= tmp; + handled = 1; + } +#endif + } + } + + /* we could also ask for 1 msec SOF (SIR) interrupts */ + + } while (handled); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static void nop_release (struct device *dev) +{ + DMSG("%s %s\n", __func__, dev_name(dev)); +} + +/* this uses load-time allocation and initialization (instead of + * doing it at run-time) to save code, eliminate fault paths, and + * be more obviously correct. + */ +static struct pxa25x_udc memory = { + .gadget = { + .ops = &pxa25x_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + .dev = { + .init_name = "gadget", + .release = nop_release, + }, + }, + + /* control endpoint */ + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &pxa25x_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + .reg_udccs = &UDCCS0, + .reg_uddr = &UDDR0, + }, + + /* first group of endpoints */ + .ep[1] = { + .ep = { + .name = "ep1in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDCCS1, + .reg_uddr = &UDDR1, + }, + .ep[2] = { + .ep = { + .name = "ep2out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDCCS2, + .reg_ubcr = &UBCR2, + .reg_uddr = &UDDR2, + }, +#ifndef CONFIG_USB_PXA25X_SMALL + .ep[3] = { + .ep = { + .name = "ep3in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDCCS3, + .reg_uddr = &UDDR3, + }, + .ep[4] = { + .ep = { + .name = "ep4out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 4, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDCCS4, + .reg_ubcr = &UBCR4, + .reg_uddr = &UDDR4, + }, + .ep[5] = { + .ep = { + .name = "ep5in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 5, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDCCS5, + .reg_uddr = &UDDR5, + }, + + /* second group of endpoints */ + .ep[6] = { + .ep = { + .name = "ep6in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 6, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDCCS6, + .reg_uddr = &UDDR6, + }, + .ep[7] = { + .ep = { + .name = "ep7out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 7, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDCCS7, + .reg_ubcr = &UBCR7, + .reg_uddr = &UDDR7, + }, + .ep[8] = { + .ep = { + .name = "ep8in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 8, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDCCS8, + .reg_uddr = &UDDR8, + }, + .ep[9] = { + .ep = { + .name = "ep9out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 9, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDCCS9, + .reg_ubcr = &UBCR9, + .reg_uddr = &UDDR9, + }, + .ep[10] = { + .ep = { + .name = "ep10in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 10, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDCCS10, + .reg_uddr = &UDDR10, + }, + + /* third group of endpoints */ + .ep[11] = { + .ep = { + .name = "ep11in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 11, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDCCS11, + .reg_uddr = &UDDR11, + }, + .ep[12] = { + .ep = { + .name = "ep12out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 12, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDCCS12, + .reg_ubcr = &UBCR12, + .reg_uddr = &UDDR12, + }, + .ep[13] = { + .ep = { + .name = "ep13in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 13, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDCCS13, + .reg_uddr = &UDDR13, + }, + .ep[14] = { + .ep = { + .name = "ep14out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 14, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDCCS14, + .reg_ubcr = &UBCR14, + .reg_uddr = &UDDR14, + }, + .ep[15] = { + .ep = { + .name = "ep15in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 15, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDCCS15, + .reg_uddr = &UDDR15, + }, +#endif /* !CONFIG_USB_PXA25X_SMALL */ +}; + +#define CP15R0_VENDOR_MASK 0xffffe000 + +#if defined(CONFIG_ARCH_PXA) +#define CP15R0_XSCALE_VALUE 0x69052000 /* intel/arm/xscale */ + +#elif defined(CONFIG_ARCH_IXP4XX) +#define CP15R0_XSCALE_VALUE 0x69054000 /* intel/arm/ixp4xx */ + +#endif + +#define CP15R0_PROD_MASK 0x000003f0 +#define PXA25x 0x00000100 /* and PXA26x */ +#define PXA210 0x00000120 + +#define CP15R0_REV_MASK 0x0000000f + +#define CP15R0_PRODREV_MASK (CP15R0_PROD_MASK | CP15R0_REV_MASK) + +#define PXA255_A0 0x00000106 /* or PXA260_B1 */ +#define PXA250_C0 0x00000105 /* or PXA26x_B0 */ +#define PXA250_B2 0x00000104 +#define PXA250_B1 0x00000103 /* or PXA260_A0 */ +#define PXA250_B0 0x00000102 +#define PXA250_A1 0x00000101 +#define PXA250_A0 0x00000100 + +#define PXA210_C0 0x00000125 +#define PXA210_B2 0x00000124 +#define PXA210_B1 0x00000123 +#define PXA210_B0 0x00000122 +#define IXP425_A0 0x000001c1 +#define IXP425_B0 0x000001f1 +#define IXP465_AD 0x00000200 + +/* + * probe - binds to the platform device + */ +static int __init pxa25x_udc_probe(struct platform_device *pdev) +{ + struct pxa25x_udc *dev = &memory; + int retval, irq; + u32 chiprev; + + pr_info("%s: version %s\n", driver_name, DRIVER_VERSION); + + /* insist on Intel/ARM/XScale */ + asm("mrc%? p15, 0, %0, c0, c0" : "=r" (chiprev)); + if ((chiprev & CP15R0_VENDOR_MASK) != CP15R0_XSCALE_VALUE) { + pr_err("%s: not XScale!\n", driver_name); + return -ENODEV; + } + + /* trigger chiprev-specific logic */ + switch (chiprev & CP15R0_PRODREV_MASK) { +#if defined(CONFIG_ARCH_PXA) + case PXA255_A0: + dev->has_cfr = 1; + break; + case PXA250_A0: + case PXA250_A1: + /* A0/A1 "not released"; ep 13, 15 unusable */ + /* fall through */ + case PXA250_B2: case PXA210_B2: + case PXA250_B1: case PXA210_B1: + case PXA250_B0: case PXA210_B0: + /* OUT-DMA is broken ... */ + /* fall through */ + case PXA250_C0: case PXA210_C0: + break; +#elif defined(CONFIG_ARCH_IXP4XX) + case IXP425_A0: + case IXP425_B0: + case IXP465_AD: + dev->has_cfr = 1; + break; +#endif + default: + pr_err("%s: unrecognized processor: %08x\n", + driver_name, chiprev); + /* iop3xx, ixp4xx, ... */ + return -ENODEV; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENODEV; + + dev->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) { + retval = PTR_ERR(dev->clk); + goto err_clk; + } + + pr_debug("%s: IRQ %d%s%s\n", driver_name, irq, + dev->has_cfr ? "" : " (!cfr)", + SIZE_STR "(pio)" + ); + + /* other non-static parts of init */ + dev->dev = &pdev->dev; + dev->mach = pdev->dev.platform_data; + + dev->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + + if (gpio_is_valid(dev->mach->gpio_pullup)) { + if ((retval = gpio_request(dev->mach->gpio_pullup, + "pca25x_udc GPIO PULLUP"))) { + dev_dbg(&pdev->dev, + "can't get pullup gpio %d, err: %d\n", + dev->mach->gpio_pullup, retval); + goto err_gpio_pullup; + } + gpio_direction_output(dev->mach->gpio_pullup, 0); + } + + init_timer(&dev->timer); + dev->timer.function = udc_watchdog; + dev->timer.data = (unsigned long) dev; + + the_controller = dev; + platform_set_drvdata(pdev, dev); + + udc_disable(dev); + udc_reinit(dev); + + dev->vbus = 0; + + /* irq setup after old hardware state is cleaned up */ + retval = request_irq(irq, pxa25x_udc_irq, + 0, driver_name, dev); + if (retval != 0) { + pr_err("%s: can't get irq %d, err %d\n", + driver_name, irq, retval); + goto err_irq1; + } + dev->got_irq = 1; + +#ifdef CONFIG_ARCH_LUBBOCK + if (machine_is_lubbock()) { + retval = request_irq(LUBBOCK_USB_DISC_IRQ, lubbock_vbus_irq, + 0, driver_name, dev); + if (retval != 0) { + pr_err("%s: can't get irq %i, err %d\n", + driver_name, LUBBOCK_USB_DISC_IRQ, retval); + goto err_irq_lub; + } + retval = request_irq(LUBBOCK_USB_IRQ, lubbock_vbus_irq, + 0, driver_name, dev); + if (retval != 0) { + pr_err("%s: can't get irq %i, err %d\n", + driver_name, LUBBOCK_USB_IRQ, retval); + goto lubbock_fail0; + } + } else +#endif + create_debug_files(dev); + + retval = usb_add_gadget_udc(&pdev->dev, &dev->gadget); + if (!retval) + return retval; + + remove_debug_files(dev); +#ifdef CONFIG_ARCH_LUBBOCK +lubbock_fail0: + free_irq(LUBBOCK_USB_DISC_IRQ, dev); + err_irq_lub: + free_irq(irq, dev); +#endif + err_irq1: + if (gpio_is_valid(dev->mach->gpio_pullup)) + gpio_free(dev->mach->gpio_pullup); + err_gpio_pullup: + if (!IS_ERR_OR_NULL(dev->transceiver)) { + usb_put_phy(dev->transceiver); + dev->transceiver = NULL; + } + clk_put(dev->clk); + err_clk: + return retval; +} + +static void pxa25x_udc_shutdown(struct platform_device *_dev) +{ + pullup_off(); +} + +static int __exit pxa25x_udc_remove(struct platform_device *pdev) +{ + struct pxa25x_udc *dev = platform_get_drvdata(pdev); + + if (dev->driver) + return -EBUSY; + + usb_del_gadget_udc(&dev->gadget); + dev->pullup = 0; + pullup(dev); + + remove_debug_files(dev); + + if (dev->got_irq) { + free_irq(platform_get_irq(pdev, 0), dev); + dev->got_irq = 0; + } +#ifdef CONFIG_ARCH_LUBBOCK + if (machine_is_lubbock()) { + free_irq(LUBBOCK_USB_DISC_IRQ, dev); + free_irq(LUBBOCK_USB_IRQ, dev); + } +#endif + if (gpio_is_valid(dev->mach->gpio_pullup)) + gpio_free(dev->mach->gpio_pullup); + + clk_put(dev->clk); + + if (!IS_ERR_OR_NULL(dev->transceiver)) { + usb_put_phy(dev->transceiver); + dev->transceiver = NULL; + } + + the_controller = NULL; + return 0; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +/* USB suspend (controlled by the host) and system suspend (controlled + * by the PXA) don't necessarily work well together. If USB is active, + * the 48 MHz clock is required; so the system can't enter 33 MHz idle + * mode, or any deeper PM saving state. + * + * For now, we punt and forcibly disconnect from the USB host when PXA + * enters any suspend state. While we're disconnected, we always disable + * the 48MHz USB clock ... allowing PXA sleep and/or 33 MHz idle states. + * Boards without software pullup control shouldn't use those states. + * VBUS IRQs should probably be ignored so that the PXA device just acts + * "dead" to USB hosts until system resume. + */ +static int pxa25x_udc_suspend(struct platform_device *dev, pm_message_t state) +{ + struct pxa25x_udc *udc = platform_get_drvdata(dev); + unsigned long flags; + + if (!gpio_is_valid(udc->mach->gpio_pullup) && !udc->mach->udc_command) + WARNING("USB host won't detect disconnect!\n"); + udc->suspended = 1; + + local_irq_save(flags); + pullup(udc); + local_irq_restore(flags); + + return 0; +} + +static int pxa25x_udc_resume(struct platform_device *dev) +{ + struct pxa25x_udc *udc = platform_get_drvdata(dev); + unsigned long flags; + + udc->suspended = 0; + local_irq_save(flags); + pullup(udc); + local_irq_restore(flags); + + return 0; +} + +#else +#define pxa25x_udc_suspend NULL +#define pxa25x_udc_resume NULL +#endif + +/*-------------------------------------------------------------------------*/ + +static struct platform_driver udc_driver = { + .shutdown = pxa25x_udc_shutdown, + .remove = __exit_p(pxa25x_udc_remove), + .suspend = pxa25x_udc_suspend, + .resume = pxa25x_udc_resume, + .driver = { + .owner = THIS_MODULE, + .name = "pxa25x-udc", + }, +}; + +module_platform_driver_probe(udc_driver, pxa25x_udc_probe); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Frank Becker, Robert Schwebel, David Brownell"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa25x-udc"); diff --git a/drivers/usb/gadget/pxa25x_udc.h b/drivers/usb/gadget/pxa25x_udc.h new file mode 100644 index 00000000..3fe5931d --- /dev/null +++ b/drivers/usb/gadget/pxa25x_udc.h @@ -0,0 +1,252 @@ +/* + * Intel PXA25x on-chip full speed USB device controller + * + * Copyright (C) 2003 Robert Schwebel , Pengutronix + * Copyright (C) 2003 David Brownell + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __LINUX_USB_GADGET_PXA25X_H +#define __LINUX_USB_GADGET_PXA25X_H + +#include + +/*-------------------------------------------------------------------------*/ + +/* pxa25x has this (move to include/asm-arm/arch-pxa/pxa-regs.h) */ +#define UFNRH_SIR (1 << 7) /* SOF interrupt request */ +#define UFNRH_SIM (1 << 6) /* SOF interrupt mask */ +#define UFNRH_IPE14 (1 << 5) /* ISO packet error, ep14 */ +#define UFNRH_IPE9 (1 << 4) /* ISO packet error, ep9 */ +#define UFNRH_IPE4 (1 << 3) /* ISO packet error, ep4 */ + +/* pxa255 has this (move to include/asm-arm/arch-pxa/pxa-regs.h) */ +#define UDCCFR UDC_RES2 /* UDC Control Function Register */ +#define UDCCFR_AREN (1 << 7) /* ACK response enable (now) */ +#define UDCCFR_ACM (1 << 2) /* ACK control mode (wait for AREN) */ + +/* latest pxa255 errata define new "must be one" bits in UDCCFR */ +#define UDCCFR_MB1 (0xff & ~(UDCCFR_AREN|UDCCFR_ACM)) + +/*-------------------------------------------------------------------------*/ + +struct pxa25x_udc; + +struct pxa25x_ep { + struct usb_ep ep; + struct pxa25x_udc *dev; + + struct list_head queue; + unsigned long pio_irqs; + + unsigned short fifo_size; + u8 bEndpointAddress; + u8 bmAttributes; + + unsigned stopped : 1; + unsigned dma_fixup : 1; + + /* UDCCS = UDC Control/Status for this EP + * UBCR = UDC Byte Count Remaining (contents of OUT fifo) + * UDDR = UDC Endpoint Data Register (the fifo) + * DRCM = DMA Request Channel Map + */ + volatile u32 *reg_udccs; + volatile u32 *reg_ubcr; + volatile u32 *reg_uddr; +}; + +struct pxa25x_request { + struct usb_request req; + struct list_head queue; +}; + +enum ep0_state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_END_XFER, + EP0_STALL, +}; + +#define EP0_FIFO_SIZE ((unsigned)16) +#define BULK_FIFO_SIZE ((unsigned)64) +#define ISO_FIFO_SIZE ((unsigned)256) +#define INT_FIFO_SIZE ((unsigned)8) + +struct udc_stats { + struct ep0stats { + unsigned long ops; + unsigned long bytes; + } read, write; + unsigned long irqs; +}; + +#ifdef CONFIG_USB_PXA25X_SMALL +/* when memory's tight, SMALL config saves code+data. */ +#define PXA_UDC_NUM_ENDPOINTS 3 +#endif + +#ifndef PXA_UDC_NUM_ENDPOINTS +#define PXA_UDC_NUM_ENDPOINTS 16 +#endif + +struct pxa25x_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + enum ep0_state ep0state; + struct udc_stats stats; + unsigned got_irq : 1, + vbus : 1, + pullup : 1, + has_cfr : 1, + req_pending : 1, + req_std : 1, + req_config : 1, + suspended : 1, + active : 1; + +#define start_watchdog(dev) mod_timer(&dev->timer, jiffies + (HZ/200)) + struct timer_list timer; + + struct device *dev; + struct clk *clk; + struct pxa2xx_udc_mach_info *mach; + struct usb_phy *transceiver; + u64 dma_mask; + struct pxa25x_ep ep [PXA_UDC_NUM_ENDPOINTS]; + +#ifdef CONFIG_USB_GADGET_DEBUG_FS + struct dentry *debugfs_udc; +#endif +}; +#define to_pxa25x(g) (container_of((g), struct pxa25x_udc, gadget)) + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_ARCH_LUBBOCK +#include +/* lubbock can also report usb connect/disconnect irqs */ +#endif + +static struct pxa25x_udc *the_controller; + +/*-------------------------------------------------------------------------*/ + +/* + * Debugging support vanishes in non-debug builds. DBG_NORMAL should be + * mostly silent during normal use/testing, with no timing side-effects. + */ +#define DBG_NORMAL 1 /* error paths, device state transitions */ +#define DBG_VERBOSE 2 /* add some success path trace info */ +#define DBG_NOISY 3 /* ... even more: request level */ +#define DBG_VERY_NOISY 4 /* ... even more: packet level */ + +#define DMSG(stuff...) pr_debug("udc: " stuff) + +#ifdef DEBUG + +static const char *state_name[] = { + "EP0_IDLE", + "EP0_IN_DATA_PHASE", "EP0_OUT_DATA_PHASE", + "EP0_END_XFER", "EP0_STALL" +}; + +#ifdef VERBOSE_DEBUG +# define UDC_DEBUG DBG_VERBOSE +#else +# define UDC_DEBUG DBG_NORMAL +#endif + +static void __maybe_unused +dump_udccr(const char *label) +{ + u32 udccr = UDCCR; + DMSG("%s %02X =%s%s%s%s%s%s%s%s\n", + label, udccr, + (udccr & UDCCR_REM) ? " rem" : "", + (udccr & UDCCR_RSTIR) ? " rstir" : "", + (udccr & UDCCR_SRM) ? " srm" : "", + (udccr & UDCCR_SUSIR) ? " susir" : "", + (udccr & UDCCR_RESIR) ? " resir" : "", + (udccr & UDCCR_RSM) ? " rsm" : "", + (udccr & UDCCR_UDA) ? " uda" : "", + (udccr & UDCCR_UDE) ? " ude" : ""); +} + +static void __maybe_unused +dump_udccs0(const char *label) +{ + u32 udccs0 = UDCCS0; + + DMSG("%s %s %02X =%s%s%s%s%s%s%s%s\n", + label, state_name[the_controller->ep0state], udccs0, + (udccs0 & UDCCS0_SA) ? " sa" : "", + (udccs0 & UDCCS0_RNE) ? " rne" : "", + (udccs0 & UDCCS0_FST) ? " fst" : "", + (udccs0 & UDCCS0_SST) ? " sst" : "", + (udccs0 & UDCCS0_DRWF) ? " dwrf" : "", + (udccs0 & UDCCS0_FTF) ? " ftf" : "", + (udccs0 & UDCCS0_IPR) ? " ipr" : "", + (udccs0 & UDCCS0_OPR) ? " opr" : ""); +} + +static void __maybe_unused +dump_state(struct pxa25x_udc *dev) +{ + u32 tmp; + unsigned i; + + DMSG("%s, uicr %02X.%02X, usir %02X.%02x, ufnr %02X.%02X\n", + state_name[dev->ep0state], + UICR1, UICR0, USIR1, USIR0, UFNRH, UFNRL); + dump_udccr("udccr"); + if (dev->has_cfr) { + tmp = UDCCFR; + DMSG("udccfr %02X =%s%s\n", tmp, + (tmp & UDCCFR_AREN) ? " aren" : "", + (tmp & UDCCFR_ACM) ? " acm" : ""); + } + + if (!dev->driver) { + DMSG("no gadget driver bound\n"); + return; + } else + DMSG("ep0 driver '%s'\n", dev->driver->driver.name); + + dump_udccs0 ("udccs0"); + DMSG("ep0 IN %lu/%lu, OUT %lu/%lu\n", + dev->stats.write.bytes, dev->stats.write.ops, + dev->stats.read.bytes, dev->stats.read.ops); + + for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) { + if (dev->ep[i].ep.desc == NULL) + continue; + DMSG ("udccs%d = %02x\n", i, *dev->ep->reg_udccs); + } +} + +#else + +#define dump_udccr(x) do{}while(0) +#define dump_udccs0(x) do{}while(0) +#define dump_state(x) do{}while(0) + +#define UDC_DEBUG ((unsigned)0) + +#endif + +#define DBG(lvl, stuff...) do{if ((lvl) <= UDC_DEBUG) DMSG(stuff);}while(0) + +#define ERR(stuff...) pr_err("udc: " stuff) +#define WARNING(stuff...) pr_warning("udc: " stuff) +#define INFO(stuff...) pr_info("udc: " stuff) + + +#endif /* __LINUX_USB_GADGET_PXA25X_H */ diff --git a/drivers/usb/gadget/pxa27x_udc.c b/drivers/usb/gadget/pxa27x_udc.c new file mode 100644 index 00000000..6b4c7d95 --- /dev/null +++ b/drivers/usb/gadget/pxa27x_udc.c @@ -0,0 +1,2624 @@ +/* + * Handles the Intel 27x USB Device Controller (UDC) + * + * Inspired by original driver by Frank Becker, David Brownell, and others. + * Copyright (C) 2008 Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "pxa27x_udc.h" + +/* + * This driver handles the USB Device Controller (UDC) in Intel's PXA 27x + * series processors. + * + * Such controller drivers work with a gadget driver. The gadget driver + * returns descriptors, implements configuration and data protocols used + * by the host to interact with this device, and allocates endpoints to + * the different protocol interfaces. The controller driver virtualizes + * usb hardware so that the gadget drivers will be more portable. + * + * This UDC hardware wants to implement a bit too much USB protocol. The + * biggest issues are: that the endpoints have to be set up before the + * controller can be enabled (minor, and not uncommon); and each endpoint + * can only have one configuration, interface and alternative interface + * number (major, and very unusual). Once set up, these cannot be changed + * without a controller reset. + * + * The workaround is to setup all combinations necessary for the gadgets which + * will work with this driver. This is done in pxa_udc structure, statically. + * See pxa_udc, udc_usb_ep versus pxa_ep, and matching function find_pxa_ep. + * (You could modify this if needed. Some drivers have a "fifo_mode" module + * parameter to facilitate such changes.) + * + * The combinations have been tested with these gadgets : + * - zero gadget + * - file storage gadget + * - ether gadget + * + * The driver doesn't use DMA, only IO access and IRQ callbacks. No use is + * made of UDC's double buffering either. USB "On-The-Go" is not implemented. + * + * All the requests are handled the same way : + * - the drivers tries to handle the request directly to the IO + * - if the IO fifo is not big enough, the remaining is send/received in + * interrupt handling. + */ + +#define DRIVER_VERSION "2008-04-18" +#define DRIVER_DESC "PXA 27x USB Device Controller driver" + +static const char driver_name[] = "pxa27x_udc"; +static struct pxa_udc *the_controller; + +static void handle_ep(struct pxa_ep *ep); + +/* + * Debug filesystem + */ +#ifdef CONFIG_USB_GADGET_DEBUG_FS + +#include +#include +#include + +static int state_dbg_show(struct seq_file *s, void *p) +{ + struct pxa_udc *udc = s->private; + int pos = 0, ret; + u32 tmp; + + ret = -ENODEV; + if (!udc->driver) + goto out; + + /* basic device status */ + pos += seq_printf(s, DRIVER_DESC "\n" + "%s version: %s\nGadget driver: %s\n", + driver_name, DRIVER_VERSION, + udc->driver ? udc->driver->driver.name : "(none)"); + + tmp = udc_readl(udc, UDCCR); + pos += seq_printf(s, + "udccr=0x%0x(%s%s%s%s%s%s%s%s%s%s), " + "con=%d,inter=%d,altinter=%d\n", tmp, + (tmp & UDCCR_OEN) ? " oen":"", + (tmp & UDCCR_AALTHNP) ? " aalthnp":"", + (tmp & UDCCR_AHNP) ? " rem" : "", + (tmp & UDCCR_BHNP) ? " rstir" : "", + (tmp & UDCCR_DWRE) ? " dwre" : "", + (tmp & UDCCR_SMAC) ? " smac" : "", + (tmp & UDCCR_EMCE) ? " emce" : "", + (tmp & UDCCR_UDR) ? " udr" : "", + (tmp & UDCCR_UDA) ? " uda" : "", + (tmp & UDCCR_UDE) ? " ude" : "", + (tmp & UDCCR_ACN) >> UDCCR_ACN_S, + (tmp & UDCCR_AIN) >> UDCCR_AIN_S, + (tmp & UDCCR_AAISN) >> UDCCR_AAISN_S); + /* registers for device and ep0 */ + pos += seq_printf(s, "udcicr0=0x%08x udcicr1=0x%08x\n", + udc_readl(udc, UDCICR0), udc_readl(udc, UDCICR1)); + pos += seq_printf(s, "udcisr0=0x%08x udcisr1=0x%08x\n", + udc_readl(udc, UDCISR0), udc_readl(udc, UDCISR1)); + pos += seq_printf(s, "udcfnr=%d\n", udc_readl(udc, UDCFNR)); + pos += seq_printf(s, "irqs: reset=%lu, suspend=%lu, resume=%lu, " + "reconfig=%lu\n", + udc->stats.irqs_reset, udc->stats.irqs_suspend, + udc->stats.irqs_resume, udc->stats.irqs_reconfig); + + ret = 0; +out: + return ret; +} + +static int queues_dbg_show(struct seq_file *s, void *p) +{ + struct pxa_udc *udc = s->private; + struct pxa_ep *ep; + struct pxa27x_request *req; + int pos = 0, i, maxpkt, ret; + + ret = -ENODEV; + if (!udc->driver) + goto out; + + /* dump endpoint queues */ + for (i = 0; i < NR_PXA_ENDPOINTS; i++) { + ep = &udc->pxa_ep[i]; + maxpkt = ep->fifo_size; + pos += seq_printf(s, "%-12s max_pkt=%d %s\n", + EPNAME(ep), maxpkt, "pio"); + + if (list_empty(&ep->queue)) { + pos += seq_printf(s, "\t(nothing queued)\n"); + continue; + } + + list_for_each_entry(req, &ep->queue, queue) { + pos += seq_printf(s, "\treq %p len %d/%d buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + } + } + + ret = 0; +out: + return ret; +} + +static int eps_dbg_show(struct seq_file *s, void *p) +{ + struct pxa_udc *udc = s->private; + struct pxa_ep *ep; + int pos = 0, i, ret; + u32 tmp; + + ret = -ENODEV; + if (!udc->driver) + goto out; + + ep = &udc->pxa_ep[0]; + tmp = udc_ep_readl(ep, UDCCSR); + pos += seq_printf(s, "udccsr0=0x%03x(%s%s%s%s%s%s%s)\n", tmp, + (tmp & UDCCSR0_SA) ? " sa" : "", + (tmp & UDCCSR0_RNE) ? " rne" : "", + (tmp & UDCCSR0_FST) ? " fst" : "", + (tmp & UDCCSR0_SST) ? " sst" : "", + (tmp & UDCCSR0_DME) ? " dme" : "", + (tmp & UDCCSR0_IPR) ? " ipr" : "", + (tmp & UDCCSR0_OPC) ? " opc" : ""); + for (i = 0; i < NR_PXA_ENDPOINTS; i++) { + ep = &udc->pxa_ep[i]; + tmp = i? udc_ep_readl(ep, UDCCR) : udc_readl(udc, UDCCR); + pos += seq_printf(s, "%-12s: " + "IN %lu(%lu reqs), OUT %lu(%lu reqs), " + "irqs=%lu, udccr=0x%08x, udccsr=0x%03x, " + "udcbcr=%d\n", + EPNAME(ep), + ep->stats.in_bytes, ep->stats.in_ops, + ep->stats.out_bytes, ep->stats.out_ops, + ep->stats.irqs, + tmp, udc_ep_readl(ep, UDCCSR), + udc_ep_readl(ep, UDCBCR)); + } + + ret = 0; +out: + return ret; +} + +static int eps_dbg_open(struct inode *inode, struct file *file) +{ + return single_open(file, eps_dbg_show, inode->i_private); +} + +static int queues_dbg_open(struct inode *inode, struct file *file) +{ + return single_open(file, queues_dbg_show, inode->i_private); +} + +static int state_dbg_open(struct inode *inode, struct file *file) +{ + return single_open(file, state_dbg_show, inode->i_private); +} + +static const struct file_operations state_dbg_fops = { + .owner = THIS_MODULE, + .open = state_dbg_open, + .llseek = seq_lseek, + .read = seq_read, + .release = single_release, +}; + +static const struct file_operations queues_dbg_fops = { + .owner = THIS_MODULE, + .open = queues_dbg_open, + .llseek = seq_lseek, + .read = seq_read, + .release = single_release, +}; + +static const struct file_operations eps_dbg_fops = { + .owner = THIS_MODULE, + .open = eps_dbg_open, + .llseek = seq_lseek, + .read = seq_read, + .release = single_release, +}; + +static void pxa_init_debugfs(struct pxa_udc *udc) +{ + struct dentry *root, *state, *queues, *eps; + + root = debugfs_create_dir(udc->gadget.name, NULL); + if (IS_ERR(root) || !root) + goto err_root; + + state = debugfs_create_file("udcstate", 0400, root, udc, + &state_dbg_fops); + if (!state) + goto err_state; + queues = debugfs_create_file("queues", 0400, root, udc, + &queues_dbg_fops); + if (!queues) + goto err_queues; + eps = debugfs_create_file("epstate", 0400, root, udc, + &eps_dbg_fops); + if (!eps) + goto err_eps; + + udc->debugfs_root = root; + udc->debugfs_state = state; + udc->debugfs_queues = queues; + udc->debugfs_eps = eps; + return; +err_eps: + debugfs_remove(eps); +err_queues: + debugfs_remove(queues); +err_state: + debugfs_remove(root); +err_root: + dev_err(udc->dev, "debugfs is not available\n"); +} + +static void pxa_cleanup_debugfs(struct pxa_udc *udc) +{ + debugfs_remove(udc->debugfs_eps); + debugfs_remove(udc->debugfs_queues); + debugfs_remove(udc->debugfs_state); + debugfs_remove(udc->debugfs_root); + udc->debugfs_eps = NULL; + udc->debugfs_queues = NULL; + udc->debugfs_state = NULL; + udc->debugfs_root = NULL; +} + +#else +static inline void pxa_init_debugfs(struct pxa_udc *udc) +{ +} + +static inline void pxa_cleanup_debugfs(struct pxa_udc *udc) +{ +} +#endif + +/** + * is_match_usb_pxa - check if usb_ep and pxa_ep match + * @udc_usb_ep: usb endpoint + * @ep: pxa endpoint + * @config: configuration required in pxa_ep + * @interface: interface required in pxa_ep + * @altsetting: altsetting required in pxa_ep + * + * Returns 1 if all criteria match between pxa and usb endpoint, 0 otherwise + */ +static int is_match_usb_pxa(struct udc_usb_ep *udc_usb_ep, struct pxa_ep *ep, + int config, int interface, int altsetting) +{ + if (usb_endpoint_num(&udc_usb_ep->desc) != ep->addr) + return 0; + if (usb_endpoint_dir_in(&udc_usb_ep->desc) != ep->dir_in) + return 0; + if (usb_endpoint_type(&udc_usb_ep->desc) != ep->type) + return 0; + if ((ep->config != config) || (ep->interface != interface) + || (ep->alternate != altsetting)) + return 0; + return 1; +} + +/** + * find_pxa_ep - find pxa_ep structure matching udc_usb_ep + * @udc: pxa udc + * @udc_usb_ep: udc_usb_ep structure + * + * Match udc_usb_ep and all pxa_ep available, to see if one matches. + * This is necessary because of the strong pxa hardware restriction requiring + * that once pxa endpoints are initialized, their configuration is freezed, and + * no change can be made to their address, direction, or in which configuration, + * interface or altsetting they are active ... which differs from more usual + * models which have endpoints be roughly just addressable fifos, and leave + * configuration events up to gadget drivers (like all control messages). + * + * Note that there is still a blurred point here : + * - we rely on UDCCR register "active interface" and "active altsetting". + * This is a nonsense in regard of USB spec, where multiple interfaces are + * active at the same time. + * - if we knew for sure that the pxa can handle multiple interface at the + * same time, assuming Intel's Developer Guide is wrong, this function + * should be reviewed, and a cache of couples (iface, altsetting) should + * be kept in the pxa_udc structure. In this case this function would match + * against the cache of couples instead of the "last altsetting" set up. + * + * Returns the matched pxa_ep structure or NULL if none found + */ +static struct pxa_ep *find_pxa_ep(struct pxa_udc *udc, + struct udc_usb_ep *udc_usb_ep) +{ + int i; + struct pxa_ep *ep; + int cfg = udc->config; + int iface = udc->last_interface; + int alt = udc->last_alternate; + + if (udc_usb_ep == &udc->udc_usb_ep[0]) + return &udc->pxa_ep[0]; + + for (i = 1; i < NR_PXA_ENDPOINTS; i++) { + ep = &udc->pxa_ep[i]; + if (is_match_usb_pxa(udc_usb_ep, ep, cfg, iface, alt)) + return ep; + } + return NULL; +} + +/** + * update_pxa_ep_matches - update pxa_ep cached values in all udc_usb_ep + * @udc: pxa udc + * + * Context: in_interrupt() + * + * Updates all pxa_ep fields in udc_usb_ep structures, if this field was + * previously set up (and is not NULL). The update is necessary is a + * configuration change or altsetting change was issued by the USB host. + */ +static void update_pxa_ep_matches(struct pxa_udc *udc) +{ + int i; + struct udc_usb_ep *udc_usb_ep; + + for (i = 1; i < NR_USB_ENDPOINTS; i++) { + udc_usb_ep = &udc->udc_usb_ep[i]; + if (udc_usb_ep->pxa_ep) + udc_usb_ep->pxa_ep = find_pxa_ep(udc, udc_usb_ep); + } +} + +/** + * pio_irq_enable - Enables irq generation for one endpoint + * @ep: udc endpoint + */ +static void pio_irq_enable(struct pxa_ep *ep) +{ + struct pxa_udc *udc = ep->dev; + int index = EPIDX(ep); + u32 udcicr0 = udc_readl(udc, UDCICR0); + u32 udcicr1 = udc_readl(udc, UDCICR1); + + if (index < 16) + udc_writel(udc, UDCICR0, udcicr0 | (3 << (index * 2))); + else + udc_writel(udc, UDCICR1, udcicr1 | (3 << ((index - 16) * 2))); +} + +/** + * pio_irq_disable - Disables irq generation for one endpoint + * @ep: udc endpoint + */ +static void pio_irq_disable(struct pxa_ep *ep) +{ + struct pxa_udc *udc = ep->dev; + int index = EPIDX(ep); + u32 udcicr0 = udc_readl(udc, UDCICR0); + u32 udcicr1 = udc_readl(udc, UDCICR1); + + if (index < 16) + udc_writel(udc, UDCICR0, udcicr0 & ~(3 << (index * 2))); + else + udc_writel(udc, UDCICR1, udcicr1 & ~(3 << ((index - 16) * 2))); +} + +/** + * udc_set_mask_UDCCR - set bits in UDCCR + * @udc: udc device + * @mask: bits to set in UDCCR + * + * Sets bits in UDCCR, leaving DME and FST bits as they were. + */ +static inline void udc_set_mask_UDCCR(struct pxa_udc *udc, int mask) +{ + u32 udccr = udc_readl(udc, UDCCR); + udc_writel(udc, UDCCR, + (udccr & UDCCR_MASK_BITS) | (mask & UDCCR_MASK_BITS)); +} + +/** + * udc_clear_mask_UDCCR - clears bits in UDCCR + * @udc: udc device + * @mask: bit to clear in UDCCR + * + * Clears bits in UDCCR, leaving DME and FST bits as they were. + */ +static inline void udc_clear_mask_UDCCR(struct pxa_udc *udc, int mask) +{ + u32 udccr = udc_readl(udc, UDCCR); + udc_writel(udc, UDCCR, + (udccr & UDCCR_MASK_BITS) & ~(mask & UDCCR_MASK_BITS)); +} + +/** + * ep_write_UDCCSR - set bits in UDCCSR + * @udc: udc device + * @mask: bits to set in UDCCR + * + * Sets bits in UDCCSR (UDCCSR0 and UDCCSR*). + * + * A specific case is applied to ep0 : the ACM bit is always set to 1, for + * SET_INTERFACE and SET_CONFIGURATION. + */ +static inline void ep_write_UDCCSR(struct pxa_ep *ep, int mask) +{ + if (is_ep0(ep)) + mask |= UDCCSR0_ACM; + udc_ep_writel(ep, UDCCSR, mask); +} + +/** + * ep_count_bytes_remain - get how many bytes in udc endpoint + * @ep: udc endpoint + * + * Returns number of bytes in OUT fifos. Broken for IN fifos (-EOPNOTSUPP) + */ +static int ep_count_bytes_remain(struct pxa_ep *ep) +{ + if (ep->dir_in) + return -EOPNOTSUPP; + return udc_ep_readl(ep, UDCBCR) & 0x3ff; +} + +/** + * ep_is_empty - checks if ep has byte ready for reading + * @ep: udc endpoint + * + * If endpoint is the control endpoint, checks if there are bytes in the + * control endpoint fifo. If endpoint is a data endpoint, checks if bytes + * are ready for reading on OUT endpoint. + * + * Returns 0 if ep not empty, 1 if ep empty, -EOPNOTSUPP if IN endpoint + */ +static int ep_is_empty(struct pxa_ep *ep) +{ + int ret; + + if (!is_ep0(ep) && ep->dir_in) + return -EOPNOTSUPP; + if (is_ep0(ep)) + ret = !(udc_ep_readl(ep, UDCCSR) & UDCCSR0_RNE); + else + ret = !(udc_ep_readl(ep, UDCCSR) & UDCCSR_BNE); + return ret; +} + +/** + * ep_is_full - checks if ep has place to write bytes + * @ep: udc endpoint + * + * If endpoint is not the control endpoint and is an IN endpoint, checks if + * there is place to write bytes into the endpoint. + * + * Returns 0 if ep not full, 1 if ep full, -EOPNOTSUPP if OUT endpoint + */ +static int ep_is_full(struct pxa_ep *ep) +{ + if (is_ep0(ep)) + return (udc_ep_readl(ep, UDCCSR) & UDCCSR0_IPR); + if (!ep->dir_in) + return -EOPNOTSUPP; + return (!(udc_ep_readl(ep, UDCCSR) & UDCCSR_BNF)); +} + +/** + * epout_has_pkt - checks if OUT endpoint fifo has a packet available + * @ep: pxa endpoint + * + * Returns 1 if a complete packet is available, 0 if not, -EOPNOTSUPP for IN ep. + */ +static int epout_has_pkt(struct pxa_ep *ep) +{ + if (!is_ep0(ep) && ep->dir_in) + return -EOPNOTSUPP; + if (is_ep0(ep)) + return (udc_ep_readl(ep, UDCCSR) & UDCCSR0_OPC); + return (udc_ep_readl(ep, UDCCSR) & UDCCSR_PC); +} + +/** + * set_ep0state - Set ep0 automata state + * @dev: udc device + * @state: state + */ +static void set_ep0state(struct pxa_udc *udc, int state) +{ + struct pxa_ep *ep = &udc->pxa_ep[0]; + char *old_stname = EP0_STNAME(udc); + + udc->ep0state = state; + ep_dbg(ep, "state=%s->%s, udccsr0=0x%03x, udcbcr=%d\n", old_stname, + EP0_STNAME(udc), udc_ep_readl(ep, UDCCSR), + udc_ep_readl(ep, UDCBCR)); +} + +/** + * ep0_idle - Put control endpoint into idle state + * @dev: udc device + */ +static void ep0_idle(struct pxa_udc *dev) +{ + set_ep0state(dev, WAIT_FOR_SETUP); +} + +/** + * inc_ep_stats_reqs - Update ep stats counts + * @ep: physical endpoint + * @req: usb request + * @is_in: ep direction (USB_DIR_IN or 0) + * + */ +static void inc_ep_stats_reqs(struct pxa_ep *ep, int is_in) +{ + if (is_in) + ep->stats.in_ops++; + else + ep->stats.out_ops++; +} + +/** + * inc_ep_stats_bytes - Update ep stats counts + * @ep: physical endpoint + * @count: bytes transferred on endpoint + * @is_in: ep direction (USB_DIR_IN or 0) + */ +static void inc_ep_stats_bytes(struct pxa_ep *ep, int count, int is_in) +{ + if (is_in) + ep->stats.in_bytes += count; + else + ep->stats.out_bytes += count; +} + +/** + * pxa_ep_setup - Sets up an usb physical endpoint + * @ep: pxa27x physical endpoint + * + * Find the physical pxa27x ep, and setup its UDCCR + */ +static void pxa_ep_setup(struct pxa_ep *ep) +{ + u32 new_udccr; + + new_udccr = ((ep->config << UDCCONR_CN_S) & UDCCONR_CN) + | ((ep->interface << UDCCONR_IN_S) & UDCCONR_IN) + | ((ep->alternate << UDCCONR_AISN_S) & UDCCONR_AISN) + | ((EPADDR(ep) << UDCCONR_EN_S) & UDCCONR_EN) + | ((EPXFERTYPE(ep) << UDCCONR_ET_S) & UDCCONR_ET) + | ((ep->dir_in) ? UDCCONR_ED : 0) + | ((ep->fifo_size << UDCCONR_MPS_S) & UDCCONR_MPS) + | UDCCONR_EE; + + udc_ep_writel(ep, UDCCR, new_udccr); +} + +/** + * pxa_eps_setup - Sets up all usb physical endpoints + * @dev: udc device + * + * Setup all pxa physical endpoints, except ep0 + */ +static void pxa_eps_setup(struct pxa_udc *dev) +{ + unsigned int i; + + dev_dbg(dev->dev, "%s: dev=%p\n", __func__, dev); + + for (i = 1; i < NR_PXA_ENDPOINTS; i++) + pxa_ep_setup(&dev->pxa_ep[i]); +} + +/** + * pxa_ep_alloc_request - Allocate usb request + * @_ep: usb endpoint + * @gfp_flags: + * + * For the pxa27x, these can just wrap kmalloc/kfree. gadget drivers + * must still pass correctly initialized endpoints, since other controller + * drivers may care about how it's currently set up (dma issues etc). + */ +static struct usb_request * +pxa_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct pxa27x_request *req; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + req->in_use = 0; + req->udc_usb_ep = container_of(_ep, struct udc_usb_ep, usb_ep); + + return &req->req; +} + +/** + * pxa_ep_free_request - Free usb request + * @_ep: usb endpoint + * @_req: usb request + * + * Wrapper around kfree to free _req + */ +static void pxa_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa27x_request *req; + + req = container_of(_req, struct pxa27x_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/** + * ep_add_request - add a request to the endpoint's queue + * @ep: usb endpoint + * @req: usb request + * + * Context: ep->lock held + * + * Queues the request in the endpoint's queue, and enables the interrupts + * on the endpoint. + */ +static void ep_add_request(struct pxa_ep *ep, struct pxa27x_request *req) +{ + if (unlikely(!req)) + return; + ep_vdbg(ep, "req:%p, lg=%d, udccsr=0x%03x\n", req, + req->req.length, udc_ep_readl(ep, UDCCSR)); + + req->in_use = 1; + list_add_tail(&req->queue, &ep->queue); + pio_irq_enable(ep); +} + +/** + * ep_del_request - removes a request from the endpoint's queue + * @ep: usb endpoint + * @req: usb request + * + * Context: ep->lock held + * + * Unqueue the request from the endpoint's queue. If there are no more requests + * on the endpoint, and if it's not the control endpoint, interrupts are + * disabled on the endpoint. + */ +static void ep_del_request(struct pxa_ep *ep, struct pxa27x_request *req) +{ + if (unlikely(!req)) + return; + ep_vdbg(ep, "req:%p, lg=%d, udccsr=0x%03x\n", req, + req->req.length, udc_ep_readl(ep, UDCCSR)); + + list_del_init(&req->queue); + req->in_use = 0; + if (!is_ep0(ep) && list_empty(&ep->queue)) + pio_irq_disable(ep); +} + +/** + * req_done - Complete an usb request + * @ep: pxa physical endpoint + * @req: pxa request + * @status: usb request status sent to gadget API + * @pflags: flags of previous spinlock_irq_save() or NULL if no lock held + * + * Context: ep->lock held if flags not NULL, else ep->lock released + * + * Retire a pxa27x usb request. Endpoint must be locked. + */ +static void req_done(struct pxa_ep *ep, struct pxa27x_request *req, int status, + unsigned long *pflags) +{ + unsigned long flags; + + ep_del_request(ep, req); + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + ep_dbg(ep, "complete req %p stat %d len %u/%u\n", + &req->req, status, + req->req.actual, req->req.length); + + if (pflags) + spin_unlock_irqrestore(&ep->lock, *pflags); + local_irq_save(flags); + req->req.complete(&req->udc_usb_ep->usb_ep, &req->req); + local_irq_restore(flags); + if (pflags) + spin_lock_irqsave(&ep->lock, *pflags); +} + +/** + * ep_end_out_req - Ends endpoint OUT request + * @ep: physical endpoint + * @req: pxa request + * @pflags: flags of previous spinlock_irq_save() or NULL if no lock held + * + * Context: ep->lock held or released (see req_done()) + * + * Ends endpoint OUT request (completes usb request). + */ +static void ep_end_out_req(struct pxa_ep *ep, struct pxa27x_request *req, + unsigned long *pflags) +{ + inc_ep_stats_reqs(ep, !USB_DIR_IN); + req_done(ep, req, 0, pflags); +} + +/** + * ep0_end_out_req - Ends control endpoint OUT request (ends data stage) + * @ep: physical endpoint + * @req: pxa request + * @pflags: flags of previous spinlock_irq_save() or NULL if no lock held + * + * Context: ep->lock held or released (see req_done()) + * + * Ends control endpoint OUT request (completes usb request), and puts + * control endpoint into idle state + */ +static void ep0_end_out_req(struct pxa_ep *ep, struct pxa27x_request *req, + unsigned long *pflags) +{ + set_ep0state(ep->dev, OUT_STATUS_STAGE); + ep_end_out_req(ep, req, pflags); + ep0_idle(ep->dev); +} + +/** + * ep_end_in_req - Ends endpoint IN request + * @ep: physical endpoint + * @req: pxa request + * @pflags: flags of previous spinlock_irq_save() or NULL if no lock held + * + * Context: ep->lock held or released (see req_done()) + * + * Ends endpoint IN request (completes usb request). + */ +static void ep_end_in_req(struct pxa_ep *ep, struct pxa27x_request *req, + unsigned long *pflags) +{ + inc_ep_stats_reqs(ep, USB_DIR_IN); + req_done(ep, req, 0, pflags); +} + +/** + * ep0_end_in_req - Ends control endpoint IN request (ends data stage) + * @ep: physical endpoint + * @req: pxa request + * @pflags: flags of previous spinlock_irq_save() or NULL if no lock held + * + * Context: ep->lock held or released (see req_done()) + * + * Ends control endpoint IN request (completes usb request), and puts + * control endpoint into status state + */ +static void ep0_end_in_req(struct pxa_ep *ep, struct pxa27x_request *req, + unsigned long *pflags) +{ + set_ep0state(ep->dev, IN_STATUS_STAGE); + ep_end_in_req(ep, req, pflags); +} + +/** + * nuke - Dequeue all requests + * @ep: pxa endpoint + * @status: usb request status + * + * Context: ep->lock released + * + * Dequeues all requests on an endpoint. As a side effect, interrupts will be + * disabled on that endpoint (because no more requests). + */ +static void nuke(struct pxa_ep *ep, int status) +{ + struct pxa27x_request *req; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct pxa27x_request, queue); + req_done(ep, req, status, &flags); + } + spin_unlock_irqrestore(&ep->lock, flags); +} + +/** + * read_packet - transfer 1 packet from an OUT endpoint into request + * @ep: pxa physical endpoint + * @req: usb request + * + * Takes bytes from OUT endpoint and transfers them info the usb request. + * If there is less space in request than bytes received in OUT endpoint, + * bytes are left in the OUT endpoint. + * + * Returns how many bytes were actually transferred + */ +static int read_packet(struct pxa_ep *ep, struct pxa27x_request *req) +{ + u32 *buf; + int bytes_ep, bufferspace, count, i; + + bytes_ep = ep_count_bytes_remain(ep); + bufferspace = req->req.length - req->req.actual; + + buf = (u32 *)(req->req.buf + req->req.actual); + prefetchw(buf); + + if (likely(!ep_is_empty(ep))) + count = min(bytes_ep, bufferspace); + else /* zlp */ + count = 0; + + for (i = count; i > 0; i -= 4) + *buf++ = udc_ep_readl(ep, UDCDR); + req->req.actual += count; + + ep_write_UDCCSR(ep, UDCCSR_PC); + + return count; +} + +/** + * write_packet - transfer 1 packet from request into an IN endpoint + * @ep: pxa physical endpoint + * @req: usb request + * @max: max bytes that fit into endpoint + * + * Takes bytes from usb request, and transfers them into the physical + * endpoint. If there are no bytes to transfer, doesn't write anything + * to physical endpoint. + * + * Returns how many bytes were actually transferred. + */ +static int write_packet(struct pxa_ep *ep, struct pxa27x_request *req, + unsigned int max) +{ + int length, count, remain, i; + u32 *buf; + u8 *buf_8; + + buf = (u32 *)(req->req.buf + req->req.actual); + prefetch(buf); + + length = min(req->req.length - req->req.actual, max); + req->req.actual += length; + + remain = length & 0x3; + count = length & ~(0x3); + for (i = count; i > 0 ; i -= 4) + udc_ep_writel(ep, UDCDR, *buf++); + + buf_8 = (u8 *)buf; + for (i = remain; i > 0; i--) + udc_ep_writeb(ep, UDCDR, *buf_8++); + + ep_vdbg(ep, "length=%d+%d, udccsr=0x%03x\n", count, remain, + udc_ep_readl(ep, UDCCSR)); + + return length; +} + +/** + * read_fifo - Transfer packets from OUT endpoint into usb request + * @ep: pxa physical endpoint + * @req: usb request + * + * Context: callable when in_interrupt() + * + * Unload as many packets as possible from the fifo we use for usb OUT + * transfers and put them into the request. Caller should have made sure + * there's at least one packet ready. + * Doesn't complete the request, that's the caller's job + * + * Returns 1 if the request completed, 0 otherwise + */ +static int read_fifo(struct pxa_ep *ep, struct pxa27x_request *req) +{ + int count, is_short, completed = 0; + + while (epout_has_pkt(ep)) { + count = read_packet(ep, req); + inc_ep_stats_bytes(ep, count, !USB_DIR_IN); + + is_short = (count < ep->fifo_size); + ep_dbg(ep, "read udccsr:%03x, count:%d bytes%s req %p %d/%d\n", + udc_ep_readl(ep, UDCCSR), count, is_short ? "/S" : "", + &req->req, req->req.actual, req->req.length); + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + completed = 1; + break; + } + /* finished that packet. the next one may be waiting... */ + } + return completed; +} + +/** + * write_fifo - transfer packets from usb request into an IN endpoint + * @ep: pxa physical endpoint + * @req: pxa usb request + * + * Write to an IN endpoint fifo, as many packets as possible. + * irqs will use this to write the rest later. + * caller guarantees at least one packet buffer is ready (or a zlp). + * Doesn't complete the request, that's the caller's job + * + * Returns 1 if request fully transferred, 0 if partial transfer + */ +static int write_fifo(struct pxa_ep *ep, struct pxa27x_request *req) +{ + unsigned max; + int count, is_short, is_last = 0, completed = 0, totcount = 0; + u32 udccsr; + + max = ep->fifo_size; + do { + is_short = 0; + + udccsr = udc_ep_readl(ep, UDCCSR); + if (udccsr & UDCCSR_PC) { + ep_vdbg(ep, "Clearing Transmit Complete, udccsr=%x\n", + udccsr); + ep_write_UDCCSR(ep, UDCCSR_PC); + } + if (udccsr & UDCCSR_TRN) { + ep_vdbg(ep, "Clearing Underrun on, udccsr=%x\n", + udccsr); + ep_write_UDCCSR(ep, UDCCSR_TRN); + } + + count = write_packet(ep, req, max); + inc_ep_stats_bytes(ep, count, USB_DIR_IN); + totcount += count; + + /* last packet is usually short (or a zlp) */ + if (unlikely(count < max)) { + is_last = 1; + is_short = 1; + } else { + if (likely(req->req.length > req->req.actual) + || req->req.zero) + is_last = 0; + else + is_last = 1; + /* interrupt/iso maxpacket may not fill the fifo */ + is_short = unlikely(max < ep->fifo_size); + } + + if (is_short) + ep_write_UDCCSR(ep, UDCCSR_SP); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + completed = 1; + break; + } + } while (!ep_is_full(ep)); + + ep_dbg(ep, "wrote count:%d bytes%s%s, left:%d req=%p\n", + totcount, is_last ? "/L" : "", is_short ? "/S" : "", + req->req.length - req->req.actual, &req->req); + + return completed; +} + +/** + * read_ep0_fifo - Transfer packets from control endpoint into usb request + * @ep: control endpoint + * @req: pxa usb request + * + * Special ep0 version of the above read_fifo. Reads as many bytes from control + * endpoint as can be read, and stores them into usb request (limited by request + * maximum length). + * + * Returns 0 if usb request only partially filled, 1 if fully filled + */ +static int read_ep0_fifo(struct pxa_ep *ep, struct pxa27x_request *req) +{ + int count, is_short, completed = 0; + + while (epout_has_pkt(ep)) { + count = read_packet(ep, req); + ep_write_UDCCSR(ep, UDCCSR0_OPC); + inc_ep_stats_bytes(ep, count, !USB_DIR_IN); + + is_short = (count < ep->fifo_size); + ep_dbg(ep, "read udccsr:%03x, count:%d bytes%s req %p %d/%d\n", + udc_ep_readl(ep, UDCCSR), count, is_short ? "/S" : "", + &req->req, req->req.actual, req->req.length); + + if (is_short || req->req.actual >= req->req.length) { + completed = 1; + break; + } + } + + return completed; +} + +/** + * write_ep0_fifo - Send a request to control endpoint (ep0 in) + * @ep: control endpoint + * @req: request + * + * Context: callable when in_interrupt() + * + * Sends a request (or a part of the request) to the control endpoint (ep0 in). + * If the request doesn't fit, the remaining part will be sent from irq. + * The request is considered fully written only if either : + * - last write transferred all remaining bytes, but fifo was not fully filled + * - last write was a 0 length write + * + * Returns 1 if request fully written, 0 if request only partially sent + */ +static int write_ep0_fifo(struct pxa_ep *ep, struct pxa27x_request *req) +{ + unsigned count; + int is_last, is_short; + + count = write_packet(ep, req, EP0_FIFO_SIZE); + inc_ep_stats_bytes(ep, count, USB_DIR_IN); + + is_short = (count < EP0_FIFO_SIZE); + is_last = ((count == 0) || (count < EP0_FIFO_SIZE)); + + /* Sends either a short packet or a 0 length packet */ + if (unlikely(is_short)) + ep_write_UDCCSR(ep, UDCCSR0_IPR); + + ep_dbg(ep, "in %d bytes%s%s, %d left, req=%p, udccsr0=0x%03x\n", + count, is_short ? "/S" : "", is_last ? "/L" : "", + req->req.length - req->req.actual, + &req->req, udc_ep_readl(ep, UDCCSR)); + + return is_last; +} + +/** + * pxa_ep_queue - Queue a request into an IN endpoint + * @_ep: usb endpoint + * @_req: usb request + * @gfp_flags: flags + * + * Context: normally called when !in_interrupt, but callable when in_interrupt() + * in the special case of ep0 setup : + * (irq->handle_ep0_ctrl_req->gadget_setup->pxa_ep_queue) + * + * Returns 0 if succedeed, error otherwise + */ +static int pxa_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct udc_usb_ep *udc_usb_ep; + struct pxa_ep *ep; + struct pxa27x_request *req; + struct pxa_udc *dev; + unsigned long flags; + int rc = 0; + int is_first_req; + unsigned length; + int recursion_detected; + + req = container_of(_req, struct pxa27x_request, req); + udc_usb_ep = container_of(_ep, struct udc_usb_ep, usb_ep); + + if (unlikely(!_req || !_req->complete || !_req->buf)) + return -EINVAL; + + if (unlikely(!_ep)) + return -EINVAL; + + dev = udc_usb_ep->dev; + ep = udc_usb_ep->pxa_ep; + if (unlikely(!ep)) + return -EINVAL; + + dev = ep->dev; + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + ep_dbg(ep, "bogus device state\n"); + return -ESHUTDOWN; + } + + /* iso is always one packet per request, that's the only way + * we can report per-packet status. that also helps with dma. + */ + if (unlikely(EPXFERTYPE_is_ISO(ep) + && req->req.length > ep->fifo_size)) + return -EMSGSIZE; + + spin_lock_irqsave(&ep->lock, flags); + recursion_detected = ep->in_handle_ep; + + is_first_req = list_empty(&ep->queue); + ep_dbg(ep, "queue req %p(first=%s), len %d buf %p\n", + _req, is_first_req ? "yes" : "no", + _req->length, _req->buf); + + if (!ep->enabled) { + _req->status = -ESHUTDOWN; + rc = -ESHUTDOWN; + goto out_locked; + } + + if (req->in_use) { + ep_err(ep, "refusing to queue req %p (already queued)\n", req); + goto out_locked; + } + + length = _req->length; + _req->status = -EINPROGRESS; + _req->actual = 0; + + ep_add_request(ep, req); + spin_unlock_irqrestore(&ep->lock, flags); + + if (is_ep0(ep)) { + switch (dev->ep0state) { + case WAIT_ACK_SET_CONF_INTERF: + if (length == 0) { + ep_end_in_req(ep, req, NULL); + } else { + ep_err(ep, "got a request of %d bytes while" + "in state WAIT_ACK_SET_CONF_INTERF\n", + length); + ep_del_request(ep, req); + rc = -EL2HLT; + } + ep0_idle(ep->dev); + break; + case IN_DATA_STAGE: + if (!ep_is_full(ep)) + if (write_ep0_fifo(ep, req)) + ep0_end_in_req(ep, req, NULL); + break; + case OUT_DATA_STAGE: + if ((length == 0) || !epout_has_pkt(ep)) + if (read_ep0_fifo(ep, req)) + ep0_end_out_req(ep, req, NULL); + break; + default: + ep_err(ep, "odd state %s to send me a request\n", + EP0_STNAME(ep->dev)); + ep_del_request(ep, req); + rc = -EL2HLT; + break; + } + } else { + if (!recursion_detected) + handle_ep(ep); + } + +out: + return rc; +out_locked: + spin_unlock_irqrestore(&ep->lock, flags); + goto out; +} + +/** + * pxa_ep_dequeue - Dequeue one request + * @_ep: usb endpoint + * @_req: usb request + * + * Return 0 if no error, -EINVAL or -ECONNRESET otherwise + */ +static int pxa_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa_ep *ep; + struct udc_usb_ep *udc_usb_ep; + struct pxa27x_request *req; + unsigned long flags; + int rc = -EINVAL; + + if (!_ep) + return rc; + udc_usb_ep = container_of(_ep, struct udc_usb_ep, usb_ep); + ep = udc_usb_ep->pxa_ep; + if (!ep || is_ep0(ep)) + return rc; + + spin_lock_irqsave(&ep->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) { + rc = 0; + break; + } + } + + spin_unlock_irqrestore(&ep->lock, flags); + if (!rc) + req_done(ep, req, -ECONNRESET, NULL); + return rc; +} + +/** + * pxa_ep_set_halt - Halts operations on one endpoint + * @_ep: usb endpoint + * @value: + * + * Returns 0 if no error, -EINVAL, -EROFS, -EAGAIN otherwise + */ +static int pxa_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct pxa_ep *ep; + struct udc_usb_ep *udc_usb_ep; + unsigned long flags; + int rc; + + + if (!_ep) + return -EINVAL; + udc_usb_ep = container_of(_ep, struct udc_usb_ep, usb_ep); + ep = udc_usb_ep->pxa_ep; + if (!ep || is_ep0(ep)) + return -EINVAL; + + if (value == 0) { + /* + * This path (reset toggle+halt) is needed to implement + * SET_INTERFACE on normal hardware. but it can't be + * done from software on the PXA UDC, and the hardware + * forgets to do it as part of SET_INTERFACE automagic. + */ + ep_dbg(ep, "only host can clear halt\n"); + return -EROFS; + } + + spin_lock_irqsave(&ep->lock, flags); + + rc = -EAGAIN; + if (ep->dir_in && (ep_is_full(ep) || !list_empty(&ep->queue))) + goto out; + + /* FST, FEF bits are the same for control and non control endpoints */ + rc = 0; + ep_write_UDCCSR(ep, UDCCSR_FST | UDCCSR_FEF); + if (is_ep0(ep)) + set_ep0state(ep->dev, STALL); + +out: + spin_unlock_irqrestore(&ep->lock, flags); + return rc; +} + +/** + * pxa_ep_fifo_status - Get how many bytes in physical endpoint + * @_ep: usb endpoint + * + * Returns number of bytes in OUT fifos. Broken for IN fifos. + */ +static int pxa_ep_fifo_status(struct usb_ep *_ep) +{ + struct pxa_ep *ep; + struct udc_usb_ep *udc_usb_ep; + + if (!_ep) + return -ENODEV; + udc_usb_ep = container_of(_ep, struct udc_usb_ep, usb_ep); + ep = udc_usb_ep->pxa_ep; + if (!ep || is_ep0(ep)) + return -ENODEV; + + if (ep->dir_in) + return -EOPNOTSUPP; + if (ep->dev->gadget.speed == USB_SPEED_UNKNOWN || ep_is_empty(ep)) + return 0; + else + return ep_count_bytes_remain(ep) + 1; +} + +/** + * pxa_ep_fifo_flush - Flushes one endpoint + * @_ep: usb endpoint + * + * Discards all data in one endpoint(IN or OUT), except control endpoint. + */ +static void pxa_ep_fifo_flush(struct usb_ep *_ep) +{ + struct pxa_ep *ep; + struct udc_usb_ep *udc_usb_ep; + unsigned long flags; + + if (!_ep) + return; + udc_usb_ep = container_of(_ep, struct udc_usb_ep, usb_ep); + ep = udc_usb_ep->pxa_ep; + if (!ep || is_ep0(ep)) + return; + + spin_lock_irqsave(&ep->lock, flags); + + if (unlikely(!list_empty(&ep->queue))) + ep_dbg(ep, "called while queue list not empty\n"); + ep_dbg(ep, "called\n"); + + /* for OUT, just read and discard the FIFO contents. */ + if (!ep->dir_in) { + while (!ep_is_empty(ep)) + udc_ep_readl(ep, UDCDR); + } else { + /* most IN status is the same, but ISO can't stall */ + ep_write_UDCCSR(ep, + UDCCSR_PC | UDCCSR_FEF | UDCCSR_TRN + | (EPXFERTYPE_is_ISO(ep) ? 0 : UDCCSR_SST)); + } + + spin_unlock_irqrestore(&ep->lock, flags); +} + +/** + * pxa_ep_enable - Enables usb endpoint + * @_ep: usb endpoint + * @desc: usb endpoint descriptor + * + * Nothing much to do here, as ep configuration is done once and for all + * before udc is enabled. After udc enable, no physical endpoint configuration + * can be changed. + * Function makes sanity checks and flushes the endpoint. + */ +static int pxa_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct pxa_ep *ep; + struct udc_usb_ep *udc_usb_ep; + struct pxa_udc *udc; + + if (!_ep || !desc) + return -EINVAL; + + udc_usb_ep = container_of(_ep, struct udc_usb_ep, usb_ep); + if (udc_usb_ep->pxa_ep) { + ep = udc_usb_ep->pxa_ep; + ep_warn(ep, "usb_ep %s already enabled, doing nothing\n", + _ep->name); + } else { + ep = find_pxa_ep(udc_usb_ep->dev, udc_usb_ep); + } + + if (!ep || is_ep0(ep)) { + dev_err(udc_usb_ep->dev->dev, + "unable to match pxa_ep for ep %s\n", + _ep->name); + return -EINVAL; + } + + if ((desc->bDescriptorType != USB_DT_ENDPOINT) + || (ep->type != usb_endpoint_type(desc))) { + ep_err(ep, "type mismatch\n"); + return -EINVAL; + } + + if (ep->fifo_size < usb_endpoint_maxp(desc)) { + ep_err(ep, "bad maxpacket\n"); + return -ERANGE; + } + + udc_usb_ep->pxa_ep = ep; + udc = ep->dev; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + ep_err(ep, "bogus device state\n"); + return -ESHUTDOWN; + } + + ep->enabled = 1; + + /* flush fifo (mostly for OUT buffers) */ + pxa_ep_fifo_flush(_ep); + + ep_dbg(ep, "enabled\n"); + return 0; +} + +/** + * pxa_ep_disable - Disable usb endpoint + * @_ep: usb endpoint + * + * Same as for pxa_ep_enable, no physical endpoint configuration can be + * changed. + * Function flushes the endpoint and related requests. + */ +static int pxa_ep_disable(struct usb_ep *_ep) +{ + struct pxa_ep *ep; + struct udc_usb_ep *udc_usb_ep; + + if (!_ep) + return -EINVAL; + + udc_usb_ep = container_of(_ep, struct udc_usb_ep, usb_ep); + ep = udc_usb_ep->pxa_ep; + if (!ep || is_ep0(ep) || !list_empty(&ep->queue)) + return -EINVAL; + + ep->enabled = 0; + nuke(ep, -ESHUTDOWN); + + pxa_ep_fifo_flush(_ep); + udc_usb_ep->pxa_ep = NULL; + + ep_dbg(ep, "disabled\n"); + return 0; +} + +static struct usb_ep_ops pxa_ep_ops = { + .enable = pxa_ep_enable, + .disable = pxa_ep_disable, + + .alloc_request = pxa_ep_alloc_request, + .free_request = pxa_ep_free_request, + + .queue = pxa_ep_queue, + .dequeue = pxa_ep_dequeue, + + .set_halt = pxa_ep_set_halt, + .fifo_status = pxa_ep_fifo_status, + .fifo_flush = pxa_ep_fifo_flush, +}; + +/** + * dplus_pullup - Connect or disconnect pullup resistor to D+ pin + * @udc: udc device + * @on: 0 if disconnect pullup resistor, 1 otherwise + * Context: any + * + * Handle D+ pullup resistor, make the device visible to the usb bus, and + * declare it as a full speed usb device + */ +static void dplus_pullup(struct pxa_udc *udc, int on) +{ + if (on) { + if (gpio_is_valid(udc->mach->gpio_pullup)) + gpio_set_value(udc->mach->gpio_pullup, + !udc->mach->gpio_pullup_inverted); + if (udc->mach->udc_command) + udc->mach->udc_command(PXA2XX_UDC_CMD_CONNECT); + } else { + if (gpio_is_valid(udc->mach->gpio_pullup)) + gpio_set_value(udc->mach->gpio_pullup, + udc->mach->gpio_pullup_inverted); + if (udc->mach->udc_command) + udc->mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT); + } + udc->pullup_on = on; +} + +/** + * pxa_udc_get_frame - Returns usb frame number + * @_gadget: usb gadget + */ +static int pxa_udc_get_frame(struct usb_gadget *_gadget) +{ + struct pxa_udc *udc = to_gadget_udc(_gadget); + + return (udc_readl(udc, UDCFNR) & 0x7ff); +} + +/** + * pxa_udc_wakeup - Force udc device out of suspend + * @_gadget: usb gadget + * + * Returns 0 if successful, error code otherwise + */ +static int pxa_udc_wakeup(struct usb_gadget *_gadget) +{ + struct pxa_udc *udc = to_gadget_udc(_gadget); + + /* host may not have enabled remote wakeup */ + if ((udc_readl(udc, UDCCR) & UDCCR_DWRE) == 0) + return -EHOSTUNREACH; + udc_set_mask_UDCCR(udc, UDCCR_UDR); + return 0; +} + +static void udc_enable(struct pxa_udc *udc); +static void udc_disable(struct pxa_udc *udc); + +/** + * should_enable_udc - Tells if UDC should be enabled + * @udc: udc device + * Context: any + * + * The UDC should be enabled if : + + * - the pullup resistor is connected + * - and a gadget driver is bound + * - and vbus is sensed (or no vbus sense is available) + * + * Returns 1 if UDC should be enabled, 0 otherwise + */ +static int should_enable_udc(struct pxa_udc *udc) +{ + int put_on; + + put_on = ((udc->pullup_on) && (udc->driver)); + put_on &= ((udc->vbus_sensed) || (IS_ERR_OR_NULL(udc->transceiver))); + return put_on; +} + +/** + * should_disable_udc - Tells if UDC should be disabled + * @udc: udc device + * Context: any + * + * The UDC should be disabled if : + * - the pullup resistor is not connected + * - or no gadget driver is bound + * - or no vbus is sensed (when vbus sesing is available) + * + * Returns 1 if UDC should be disabled + */ +static int should_disable_udc(struct pxa_udc *udc) +{ + int put_off; + + put_off = ((!udc->pullup_on) || (!udc->driver)); + put_off |= ((!udc->vbus_sensed) && (!IS_ERR_OR_NULL(udc->transceiver))); + return put_off; +} + +/** + * pxa_udc_pullup - Offer manual D+ pullup control + * @_gadget: usb gadget using the control + * @is_active: 0 if disconnect, else connect D+ pullup resistor + * Context: !in_interrupt() + * + * Returns 0 if OK, -EOPNOTSUPP if udc driver doesn't handle D+ pullup + */ +static int pxa_udc_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct pxa_udc *udc = to_gadget_udc(_gadget); + + if (!gpio_is_valid(udc->mach->gpio_pullup) && !udc->mach->udc_command) + return -EOPNOTSUPP; + + dplus_pullup(udc, is_active); + + if (should_enable_udc(udc)) + udc_enable(udc); + if (should_disable_udc(udc)) + udc_disable(udc); + return 0; +} + +static void udc_enable(struct pxa_udc *udc); +static void udc_disable(struct pxa_udc *udc); + +/** + * pxa_udc_vbus_session - Called by external transceiver to enable/disable udc + * @_gadget: usb gadget + * @is_active: 0 if should disable the udc, 1 if should enable + * + * Enables the udc, and optionnaly activates D+ pullup resistor. Or disables the + * udc, and deactivates D+ pullup resistor. + * + * Returns 0 + */ +static int pxa_udc_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct pxa_udc *udc = to_gadget_udc(_gadget); + + udc->vbus_sensed = is_active; + if (should_enable_udc(udc)) + udc_enable(udc); + if (should_disable_udc(udc)) + udc_disable(udc); + + return 0; +} + +/** + * pxa_udc_vbus_draw - Called by gadget driver after SET_CONFIGURATION completed + * @_gadget: usb gadget + * @mA: current drawn + * + * Context: !in_interrupt() + * + * Called after a configuration was chosen by a USB host, to inform how much + * current can be drawn by the device from VBus line. + * + * Returns 0 or -EOPNOTSUPP if no transceiver is handling the udc + */ +static int pxa_udc_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +{ + struct pxa_udc *udc; + + udc = to_gadget_udc(_gadget); + if (!IS_ERR_OR_NULL(udc->transceiver)) + return usb_phy_set_power(udc->transceiver, mA); + return -EOPNOTSUPP; +} + +static int pxa27x_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int pxa27x_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops pxa_udc_ops = { + .get_frame = pxa_udc_get_frame, + .wakeup = pxa_udc_wakeup, + .pullup = pxa_udc_pullup, + .vbus_session = pxa_udc_vbus_session, + .vbus_draw = pxa_udc_vbus_draw, + .udc_start = pxa27x_udc_start, + .udc_stop = pxa27x_udc_stop, +}; + +/** + * udc_disable - disable udc device controller + * @udc: udc device + * Context: any + * + * Disables the udc device : disables clocks, udc interrupts, control endpoint + * interrupts. + */ +static void udc_disable(struct pxa_udc *udc) +{ + if (!udc->enabled) + return; + + udc_writel(udc, UDCICR0, 0); + udc_writel(udc, UDCICR1, 0); + + udc_clear_mask_UDCCR(udc, UDCCR_UDE); + clk_disable(udc->clk); + + ep0_idle(udc); + udc->gadget.speed = USB_SPEED_UNKNOWN; + + udc->enabled = 0; +} + +/** + * udc_init_data - Initialize udc device data structures + * @dev: udc device + * + * Initializes gadget endpoint list, endpoints locks. No action is taken + * on the hardware. + */ +static void udc_init_data(struct pxa_udc *dev) +{ + int i; + struct pxa_ep *ep; + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->udc_usb_ep[0].pxa_ep = &dev->pxa_ep[0]; + ep0_idle(dev); + + /* PXA endpoints init */ + for (i = 0; i < NR_PXA_ENDPOINTS; i++) { + ep = &dev->pxa_ep[i]; + + ep->enabled = is_ep0(ep); + INIT_LIST_HEAD(&ep->queue); + spin_lock_init(&ep->lock); + } + + /* USB endpoints init */ + for (i = 1; i < NR_USB_ENDPOINTS; i++) + list_add_tail(&dev->udc_usb_ep[i].usb_ep.ep_list, + &dev->gadget.ep_list); +} + +/** + * udc_enable - Enables the udc device + * @dev: udc device + * + * Enables the udc device : enables clocks, udc interrupts, control endpoint + * interrupts, sets usb as UDC client and setups endpoints. + */ +static void udc_enable(struct pxa_udc *udc) +{ + if (udc->enabled) + return; + + udc_writel(udc, UDCICR0, 0); + udc_writel(udc, UDCICR1, 0); + udc_clear_mask_UDCCR(udc, UDCCR_UDE); + + clk_enable(udc->clk); + + ep0_idle(udc); + udc->gadget.speed = USB_SPEED_FULL; + memset(&udc->stats, 0, sizeof(udc->stats)); + + udc_set_mask_UDCCR(udc, UDCCR_UDE); + ep_write_UDCCSR(&udc->pxa_ep[0], UDCCSR0_ACM); + udelay(2); + if (udc_readl(udc, UDCCR) & UDCCR_EMCE) + dev_err(udc->dev, "Configuration errors, udc disabled\n"); + + /* + * Caller must be able to sleep in order to cope with startup transients + */ + msleep(100); + + /* enable suspend/resume and reset irqs */ + udc_writel(udc, UDCICR1, + UDCICR1_IECC | UDCICR1_IERU + | UDCICR1_IESU | UDCICR1_IERS); + + /* enable ep0 irqs */ + pio_irq_enable(&udc->pxa_ep[0]); + + udc->enabled = 1; +} + +/** + * pxa27x_start - Register gadget driver + * @driver: gadget driver + * @bind: bind function + * + * When a driver is successfully registered, it will receive control requests + * including set_configuration(), which enables non-control requests. Then + * usb traffic follows until a disconnect is reported. Then a host may connect + * again, or the driver might get unbound. + * + * Note that the udc is not automatically enabled. Check function + * should_enable_udc(). + * + * Returns 0 if no error, -EINVAL, -ENODEV, -EBUSY otherwise + */ +static int pxa27x_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct pxa_udc *udc = to_pxa(g); + int retval; + + /* first hook up the driver ... */ + udc->driver = driver; + dplus_pullup(udc, 1); + + if (!IS_ERR_OR_NULL(udc->transceiver)) { + retval = otg_set_peripheral(udc->transceiver->otg, + &udc->gadget); + if (retval) { + dev_err(udc->dev, "can't bind to transceiver\n"); + goto fail; + } + } + + if (should_enable_udc(udc)) + udc_enable(udc); + return 0; + +fail: + udc->driver = NULL; + return retval; +} + +/** + * stop_activity - Stops udc endpoints + * @udc: udc device + * @driver: gadget driver + * + * Disables all udc endpoints (even control endpoint), report disconnect to + * the gadget user. + */ +static void stop_activity(struct pxa_udc *udc, struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (udc->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + + for (i = 0; i < NR_USB_ENDPOINTS; i++) + pxa_ep_disable(&udc->udc_usb_ep[i].usb_ep); +} + +/** + * pxa27x_udc_stop - Unregister the gadget driver + * @driver: gadget driver + * + * Returns 0 if no error, -ENODEV, -EINVAL otherwise + */ +static int pxa27x_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct pxa_udc *udc = to_pxa(g); + + stop_activity(udc, driver); + udc_disable(udc); + dplus_pullup(udc, 0); + + udc->driver = NULL; + + if (!IS_ERR_OR_NULL(udc->transceiver)) + return otg_set_peripheral(udc->transceiver->otg, NULL); + return 0; +} + +/** + * handle_ep0_ctrl_req - handle control endpoint control request + * @udc: udc device + * @req: control request + */ +static void handle_ep0_ctrl_req(struct pxa_udc *udc, + struct pxa27x_request *req) +{ + struct pxa_ep *ep = &udc->pxa_ep[0]; + union { + struct usb_ctrlrequest r; + u32 word[2]; + } u; + int i; + int have_extrabytes = 0; + unsigned long flags; + + nuke(ep, -EPROTO); + spin_lock_irqsave(&ep->lock, flags); + + /* + * In the PXA320 manual, in the section about Back-to-Back setup + * packets, it describes this situation. The solution is to set OPC to + * get rid of the status packet, and then continue with the setup + * packet. Generalize to pxa27x CPUs. + */ + if (epout_has_pkt(ep) && (ep_count_bytes_remain(ep) == 0)) + ep_write_UDCCSR(ep, UDCCSR0_OPC); + + /* read SETUP packet */ + for (i = 0; i < 2; i++) { + if (unlikely(ep_is_empty(ep))) + goto stall; + u.word[i] = udc_ep_readl(ep, UDCDR); + } + + have_extrabytes = !ep_is_empty(ep); + while (!ep_is_empty(ep)) { + i = udc_ep_readl(ep, UDCDR); + ep_err(ep, "wrong to have extra bytes for setup : 0x%08x\n", i); + } + + ep_dbg(ep, "SETUP %02x.%02x v%04x i%04x l%04x\n", + u.r.bRequestType, u.r.bRequest, + le16_to_cpu(u.r.wValue), le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wLength)); + if (unlikely(have_extrabytes)) + goto stall; + + if (u.r.bRequestType & USB_DIR_IN) + set_ep0state(udc, IN_DATA_STAGE); + else + set_ep0state(udc, OUT_DATA_STAGE); + + /* Tell UDC to enter Data Stage */ + ep_write_UDCCSR(ep, UDCCSR0_SA | UDCCSR0_OPC); + + spin_unlock_irqrestore(&ep->lock, flags); + i = udc->driver->setup(&udc->gadget, &u.r); + spin_lock_irqsave(&ep->lock, flags); + if (i < 0) + goto stall; +out: + spin_unlock_irqrestore(&ep->lock, flags); + return; +stall: + ep_dbg(ep, "protocol STALL, udccsr0=%03x err %d\n", + udc_ep_readl(ep, UDCCSR), i); + ep_write_UDCCSR(ep, UDCCSR0_FST | UDCCSR0_FTF); + set_ep0state(udc, STALL); + goto out; +} + +/** + * handle_ep0 - Handle control endpoint data transfers + * @udc: udc device + * @fifo_irq: 1 if triggered by fifo service type irq + * @opc_irq: 1 if triggered by output packet complete type irq + * + * Context : when in_interrupt() or with ep->lock held + * + * Tries to transfer all pending request data into the endpoint and/or + * transfer all pending data in the endpoint into usb requests. + * Handles states of ep0 automata. + * + * PXA27x hardware handles several standard usb control requests without + * driver notification. The requests fully handled by hardware are : + * SET_ADDRESS, SET_FEATURE, CLEAR_FEATURE, GET_CONFIGURATION, GET_INTERFACE, + * GET_STATUS + * The requests handled by hardware, but with irq notification are : + * SYNCH_FRAME, SET_CONFIGURATION, SET_INTERFACE + * The remaining standard requests really handled by handle_ep0 are : + * GET_DESCRIPTOR, SET_DESCRIPTOR, specific requests. + * Requests standardized outside of USB 2.0 chapter 9 are handled more + * uniformly, by gadget drivers. + * + * The control endpoint state machine is _not_ USB spec compliant, it's even + * hardly compliant with Intel PXA270 developers guide. + * The key points which inferred this state machine are : + * - on every setup token, bit UDCCSR0_SA is raised and held until cleared by + * software. + * - on every OUT packet received, UDCCSR0_OPC is raised and held until + * cleared by software. + * - clearing UDCCSR0_OPC always flushes ep0. If in setup stage, never do it + * before reading ep0. + * This is true only for PXA27x. This is not true anymore for PXA3xx family + * (check Back-to-Back setup packet in developers guide). + * - irq can be called on a "packet complete" event (opc_irq=1), while + * UDCCSR0_OPC is not yet raised (delta can be as big as 100ms + * from experimentation). + * - as UDCCSR0_SA can be activated while in irq handling, and clearing + * UDCCSR0_OPC would flush the setup data, we almost never clear UDCCSR0_OPC + * => we never actually read the "status stage" packet of an IN data stage + * => this is not documented in Intel documentation + * - hardware as no idea of STATUS STAGE, it only handle SETUP STAGE and DATA + * STAGE. The driver add STATUS STAGE to send last zero length packet in + * OUT_STATUS_STAGE. + * - special attention was needed for IN_STATUS_STAGE. If a packet complete + * event is detected, we terminate the status stage without ackowledging the + * packet (not to risk to loose a potential SETUP packet) + */ +static void handle_ep0(struct pxa_udc *udc, int fifo_irq, int opc_irq) +{ + u32 udccsr0; + struct pxa_ep *ep = &udc->pxa_ep[0]; + struct pxa27x_request *req = NULL; + int completed = 0; + + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, struct pxa27x_request, queue); + + udccsr0 = udc_ep_readl(ep, UDCCSR); + ep_dbg(ep, "state=%s, req=%p, udccsr0=0x%03x, udcbcr=%d, irq_msk=%x\n", + EP0_STNAME(udc), req, udccsr0, udc_ep_readl(ep, UDCBCR), + (fifo_irq << 1 | opc_irq)); + + if (udccsr0 & UDCCSR0_SST) { + ep_dbg(ep, "clearing stall status\n"); + nuke(ep, -EPIPE); + ep_write_UDCCSR(ep, UDCCSR0_SST); + ep0_idle(udc); + } + + if (udccsr0 & UDCCSR0_SA) { + nuke(ep, 0); + set_ep0state(udc, SETUP_STAGE); + } + + switch (udc->ep0state) { + case WAIT_FOR_SETUP: + /* + * Hardware bug : beware, we cannot clear OPC, since we would + * miss a potential OPC irq for a setup packet. + * So, we only do ... nothing, and hope for a next irq with + * UDCCSR0_SA set. + */ + break; + case SETUP_STAGE: + udccsr0 &= UDCCSR0_CTRL_REQ_MASK; + if (likely(udccsr0 == UDCCSR0_CTRL_REQ_MASK)) + handle_ep0_ctrl_req(udc, req); + break; + case IN_DATA_STAGE: /* GET_DESCRIPTOR */ + if (epout_has_pkt(ep)) + ep_write_UDCCSR(ep, UDCCSR0_OPC); + if (req && !ep_is_full(ep)) + completed = write_ep0_fifo(ep, req); + if (completed) + ep0_end_in_req(ep, req, NULL); + break; + case OUT_DATA_STAGE: /* SET_DESCRIPTOR */ + if (epout_has_pkt(ep) && req) + completed = read_ep0_fifo(ep, req); + if (completed) + ep0_end_out_req(ep, req, NULL); + break; + case STALL: + ep_write_UDCCSR(ep, UDCCSR0_FST); + break; + case IN_STATUS_STAGE: + /* + * Hardware bug : beware, we cannot clear OPC, since we would + * miss a potential PC irq for a setup packet. + * So, we only put the ep0 into WAIT_FOR_SETUP state. + */ + if (opc_irq) + ep0_idle(udc); + break; + case OUT_STATUS_STAGE: + case WAIT_ACK_SET_CONF_INTERF: + ep_warn(ep, "should never get in %s state here!!!\n", + EP0_STNAME(ep->dev)); + ep0_idle(udc); + break; + } +} + +/** + * handle_ep - Handle endpoint data tranfers + * @ep: pxa physical endpoint + * + * Tries to transfer all pending request data into the endpoint and/or + * transfer all pending data in the endpoint into usb requests. + * + * Is always called when in_interrupt() and with ep->lock released. + */ +static void handle_ep(struct pxa_ep *ep) +{ + struct pxa27x_request *req; + int completed; + u32 udccsr; + int is_in = ep->dir_in; + int loop = 0; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + if (ep->in_handle_ep) + goto recursion_detected; + ep->in_handle_ep = 1; + + do { + completed = 0; + udccsr = udc_ep_readl(ep, UDCCSR); + + if (likely(!list_empty(&ep->queue))) + req = list_entry(ep->queue.next, + struct pxa27x_request, queue); + else + req = NULL; + + ep_dbg(ep, "req:%p, udccsr 0x%03x loop=%d\n", + req, udccsr, loop++); + + if (unlikely(udccsr & (UDCCSR_SST | UDCCSR_TRN))) + udc_ep_writel(ep, UDCCSR, + udccsr & (UDCCSR_SST | UDCCSR_TRN)); + if (!req) + break; + + if (unlikely(is_in)) { + if (likely(!ep_is_full(ep))) + completed = write_fifo(ep, req); + } else { + if (likely(epout_has_pkt(ep))) + completed = read_fifo(ep, req); + } + + if (completed) { + if (is_in) + ep_end_in_req(ep, req, &flags); + else + ep_end_out_req(ep, req, &flags); + } + } while (completed); + + ep->in_handle_ep = 0; +recursion_detected: + spin_unlock_irqrestore(&ep->lock, flags); +} + +/** + * pxa27x_change_configuration - Handle SET_CONF usb request notification + * @udc: udc device + * @config: usb configuration + * + * Post the request to upper level. + * Don't use any pxa specific harware configuration capabilities + */ +static void pxa27x_change_configuration(struct pxa_udc *udc, int config) +{ + struct usb_ctrlrequest req ; + + dev_dbg(udc->dev, "config=%d\n", config); + + udc->config = config; + udc->last_interface = 0; + udc->last_alternate = 0; + + req.bRequestType = 0; + req.bRequest = USB_REQ_SET_CONFIGURATION; + req.wValue = config; + req.wIndex = 0; + req.wLength = 0; + + set_ep0state(udc, WAIT_ACK_SET_CONF_INTERF); + udc->driver->setup(&udc->gadget, &req); + ep_write_UDCCSR(&udc->pxa_ep[0], UDCCSR0_AREN); +} + +/** + * pxa27x_change_interface - Handle SET_INTERF usb request notification + * @udc: udc device + * @iface: interface number + * @alt: alternate setting number + * + * Post the request to upper level. + * Don't use any pxa specific harware configuration capabilities + */ +static void pxa27x_change_interface(struct pxa_udc *udc, int iface, int alt) +{ + struct usb_ctrlrequest req; + + dev_dbg(udc->dev, "interface=%d, alternate setting=%d\n", iface, alt); + + udc->last_interface = iface; + udc->last_alternate = alt; + + req.bRequestType = USB_RECIP_INTERFACE; + req.bRequest = USB_REQ_SET_INTERFACE; + req.wValue = alt; + req.wIndex = iface; + req.wLength = 0; + + set_ep0state(udc, WAIT_ACK_SET_CONF_INTERF); + udc->driver->setup(&udc->gadget, &req); + ep_write_UDCCSR(&udc->pxa_ep[0], UDCCSR0_AREN); +} + +/* + * irq_handle_data - Handle data transfer + * @irq: irq IRQ number + * @udc: dev pxa_udc device structure + * + * Called from irq handler, transferts data to or from endpoint to queue + */ +static void irq_handle_data(int irq, struct pxa_udc *udc) +{ + int i; + struct pxa_ep *ep; + u32 udcisr0 = udc_readl(udc, UDCISR0) & UDCCISR0_EP_MASK; + u32 udcisr1 = udc_readl(udc, UDCISR1) & UDCCISR1_EP_MASK; + + if (udcisr0 & UDCISR_INT_MASK) { + udc->pxa_ep[0].stats.irqs++; + udc_writel(udc, UDCISR0, UDCISR_INT(0, UDCISR_INT_MASK)); + handle_ep0(udc, !!(udcisr0 & UDCICR_FIFOERR), + !!(udcisr0 & UDCICR_PKTCOMPL)); + } + + udcisr0 >>= 2; + for (i = 1; udcisr0 != 0 && i < 16; udcisr0 >>= 2, i++) { + if (!(udcisr0 & UDCISR_INT_MASK)) + continue; + + udc_writel(udc, UDCISR0, UDCISR_INT(i, UDCISR_INT_MASK)); + + WARN_ON(i >= ARRAY_SIZE(udc->pxa_ep)); + if (i < ARRAY_SIZE(udc->pxa_ep)) { + ep = &udc->pxa_ep[i]; + ep->stats.irqs++; + handle_ep(ep); + } + } + + for (i = 16; udcisr1 != 0 && i < 24; udcisr1 >>= 2, i++) { + udc_writel(udc, UDCISR1, UDCISR_INT(i - 16, UDCISR_INT_MASK)); + if (!(udcisr1 & UDCISR_INT_MASK)) + continue; + + WARN_ON(i >= ARRAY_SIZE(udc->pxa_ep)); + if (i < ARRAY_SIZE(udc->pxa_ep)) { + ep = &udc->pxa_ep[i]; + ep->stats.irqs++; + handle_ep(ep); + } + } + +} + +/** + * irq_udc_suspend - Handle IRQ "UDC Suspend" + * @udc: udc device + */ +static void irq_udc_suspend(struct pxa_udc *udc) +{ + udc_writel(udc, UDCISR1, UDCISR1_IRSU); + udc->stats.irqs_suspend++; + + if (udc->gadget.speed != USB_SPEED_UNKNOWN + && udc->driver && udc->driver->suspend) + udc->driver->suspend(&udc->gadget); + ep0_idle(udc); +} + +/** + * irq_udc_resume - Handle IRQ "UDC Resume" + * @udc: udc device + */ +static void irq_udc_resume(struct pxa_udc *udc) +{ + udc_writel(udc, UDCISR1, UDCISR1_IRRU); + udc->stats.irqs_resume++; + + if (udc->gadget.speed != USB_SPEED_UNKNOWN + && udc->driver && udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/** + * irq_udc_reconfig - Handle IRQ "UDC Change Configuration" + * @udc: udc device + */ +static void irq_udc_reconfig(struct pxa_udc *udc) +{ + unsigned config, interface, alternate, config_change; + u32 udccr = udc_readl(udc, UDCCR); + + udc_writel(udc, UDCISR1, UDCISR1_IRCC); + udc->stats.irqs_reconfig++; + + config = (udccr & UDCCR_ACN) >> UDCCR_ACN_S; + config_change = (config != udc->config); + pxa27x_change_configuration(udc, config); + + interface = (udccr & UDCCR_AIN) >> UDCCR_AIN_S; + alternate = (udccr & UDCCR_AAISN) >> UDCCR_AAISN_S; + pxa27x_change_interface(udc, interface, alternate); + + if (config_change) + update_pxa_ep_matches(udc); + udc_set_mask_UDCCR(udc, UDCCR_SMAC); +} + +/** + * irq_udc_reset - Handle IRQ "UDC Reset" + * @udc: udc device + */ +static void irq_udc_reset(struct pxa_udc *udc) +{ + u32 udccr = udc_readl(udc, UDCCR); + struct pxa_ep *ep = &udc->pxa_ep[0]; + + dev_info(udc->dev, "USB reset\n"); + udc_writel(udc, UDCISR1, UDCISR1_IRRS); + udc->stats.irqs_reset++; + + if ((udccr & UDCCR_UDA) == 0) { + dev_dbg(udc->dev, "USB reset start\n"); + stop_activity(udc, udc->driver); + } + udc->gadget.speed = USB_SPEED_FULL; + memset(&udc->stats, 0, sizeof udc->stats); + + nuke(ep, -EPROTO); + ep_write_UDCCSR(ep, UDCCSR0_FTF | UDCCSR0_OPC); + ep0_idle(udc); +} + +/** + * pxa_udc_irq - Main irq handler + * @irq: irq number + * @_dev: udc device + * + * Handles all udc interrupts + */ +static irqreturn_t pxa_udc_irq(int irq, void *_dev) +{ + struct pxa_udc *udc = _dev; + u32 udcisr0 = udc_readl(udc, UDCISR0); + u32 udcisr1 = udc_readl(udc, UDCISR1); + u32 udccr = udc_readl(udc, UDCCR); + u32 udcisr1_spec; + + dev_vdbg(udc->dev, "Interrupt, UDCISR0:0x%08x, UDCISR1:0x%08x, " + "UDCCR:0x%08x\n", udcisr0, udcisr1, udccr); + + udcisr1_spec = udcisr1 & 0xf8000000; + if (unlikely(udcisr1_spec & UDCISR1_IRSU)) + irq_udc_suspend(udc); + if (unlikely(udcisr1_spec & UDCISR1_IRRU)) + irq_udc_resume(udc); + if (unlikely(udcisr1_spec & UDCISR1_IRCC)) + irq_udc_reconfig(udc); + if (unlikely(udcisr1_spec & UDCISR1_IRRS)) + irq_udc_reset(udc); + + if ((udcisr0 & UDCCISR0_EP_MASK) | (udcisr1 & UDCCISR1_EP_MASK)) + irq_handle_data(irq, udc); + + return IRQ_HANDLED; +} + +static struct pxa_udc memory = { + .gadget = { + .ops = &pxa_udc_ops, + .ep0 = &memory.udc_usb_ep[0].usb_ep, + .name = driver_name, + .dev = { + .init_name = "gadget", + }, + }, + + .udc_usb_ep = { + USB_EP_CTRL, + USB_EP_OUT_BULK(1), + USB_EP_IN_BULK(2), + USB_EP_IN_ISO(3), + USB_EP_OUT_ISO(4), + USB_EP_IN_INT(5), + }, + + .pxa_ep = { + PXA_EP_CTRL, + /* Endpoints for gadget zero */ + PXA_EP_OUT_BULK(1, 1, 3, 0, 0), + PXA_EP_IN_BULK(2, 2, 3, 0, 0), + /* Endpoints for ether gadget, file storage gadget */ + PXA_EP_OUT_BULK(3, 1, 1, 0, 0), + PXA_EP_IN_BULK(4, 2, 1, 0, 0), + PXA_EP_IN_ISO(5, 3, 1, 0, 0), + PXA_EP_OUT_ISO(6, 4, 1, 0, 0), + PXA_EP_IN_INT(7, 5, 1, 0, 0), + /* Endpoints for RNDIS, serial */ + PXA_EP_OUT_BULK(8, 1, 2, 0, 0), + PXA_EP_IN_BULK(9, 2, 2, 0, 0), + PXA_EP_IN_INT(10, 5, 2, 0, 0), + /* + * All the following endpoints are only for completion. They + * won't never work, as multiple interfaces are really broken on + * the pxa. + */ + PXA_EP_OUT_BULK(11, 1, 2, 1, 0), + PXA_EP_IN_BULK(12, 2, 2, 1, 0), + /* Endpoint for CDC Ether */ + PXA_EP_OUT_BULK(13, 1, 1, 1, 1), + PXA_EP_IN_BULK(14, 2, 1, 1, 1), + } +}; + +/** + * pxa_udc_probe - probes the udc device + * @_dev: platform device + * + * Perform basic init : allocates udc clock, creates sysfs files, requests + * irq. + */ +static int pxa_udc_probe(struct platform_device *pdev) +{ + struct resource *regs; + struct pxa_udc *udc = &memory; + int retval = 0, gpio; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + udc->irq = platform_get_irq(pdev, 0); + if (udc->irq < 0) + return udc->irq; + + udc->dev = &pdev->dev; + udc->mach = pdev->dev.platform_data; + udc->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + + gpio = udc->mach->gpio_pullup; + if (gpio_is_valid(gpio)) { + retval = gpio_request(gpio, "USB D+ pullup"); + if (retval == 0) + gpio_direction_output(gpio, + udc->mach->gpio_pullup_inverted); + } + if (retval) { + dev_err(&pdev->dev, "Couldn't request gpio %d : %d\n", + gpio, retval); + return retval; + } + + udc->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(udc->clk)) { + retval = PTR_ERR(udc->clk); + goto err_clk; + } + + retval = -ENOMEM; + udc->regs = ioremap(regs->start, resource_size(regs)); + if (!udc->regs) { + dev_err(&pdev->dev, "Unable to map UDC I/O memory\n"); + goto err_map; + } + + udc->vbus_sensed = 0; + + the_controller = udc; + platform_set_drvdata(pdev, udc); + udc_init_data(udc); + pxa_eps_setup(udc); + + /* irq setup after old hardware state is cleaned up */ + retval = request_irq(udc->irq, pxa_udc_irq, + IRQF_SHARED, driver_name, udc); + if (retval != 0) { + dev_err(udc->dev, "%s: can't get irq %i, err %d\n", + driver_name, udc->irq, retval); + goto err_irq; + } + + retval = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (retval) + goto err_add_udc; + + pxa_init_debugfs(udc); + + return 0; + +err_add_udc: + free_irq(udc->irq, udc); +err_irq: + iounmap(udc->regs); +err_map: + clk_put(udc->clk); + udc->clk = NULL; +err_clk: + return retval; +} + +/** + * pxa_udc_remove - removes the udc device driver + * @_dev: platform device + */ +static int pxa_udc_remove(struct platform_device *_dev) +{ + struct pxa_udc *udc = platform_get_drvdata(_dev); + int gpio = udc->mach->gpio_pullup; + + usb_del_gadget_udc(&udc->gadget); + usb_gadget_unregister_driver(udc->driver); + free_irq(udc->irq, udc); + pxa_cleanup_debugfs(udc); + if (gpio_is_valid(gpio)) + gpio_free(gpio); + + usb_put_phy(udc->transceiver); + + udc->transceiver = NULL; + platform_set_drvdata(_dev, NULL); + the_controller = NULL; + clk_put(udc->clk); + iounmap(udc->regs); + + return 0; +} + +static void pxa_udc_shutdown(struct platform_device *_dev) +{ + struct pxa_udc *udc = platform_get_drvdata(_dev); + + if (udc_readl(udc, UDCCR) & UDCCR_UDE) + udc_disable(udc); +} + +#ifdef CONFIG_PXA27x +extern void pxa27x_clear_otgph(void); +#else +#define pxa27x_clear_otgph() do {} while (0) +#endif + +#ifdef CONFIG_PM +/** + * pxa_udc_suspend - Suspend udc device + * @_dev: platform device + * @state: suspend state + * + * Suspends udc : saves configuration registers (UDCCR*), then disables the udc + * device. + */ +static int pxa_udc_suspend(struct platform_device *_dev, pm_message_t state) +{ + int i; + struct pxa_udc *udc = platform_get_drvdata(_dev); + struct pxa_ep *ep; + + ep = &udc->pxa_ep[0]; + udc->udccsr0 = udc_ep_readl(ep, UDCCSR); + for (i = 1; i < NR_PXA_ENDPOINTS; i++) { + ep = &udc->pxa_ep[i]; + ep->udccsr_value = udc_ep_readl(ep, UDCCSR); + ep->udccr_value = udc_ep_readl(ep, UDCCR); + ep_dbg(ep, "udccsr:0x%03x, udccr:0x%x\n", + ep->udccsr_value, ep->udccr_value); + } + + udc_disable(udc); + udc->pullup_resume = udc->pullup_on; + dplus_pullup(udc, 0); + + return 0; +} + +/** + * pxa_udc_resume - Resume udc device + * @_dev: platform device + * + * Resumes udc : restores configuration registers (UDCCR*), then enables the udc + * device. + */ +static int pxa_udc_resume(struct platform_device *_dev) +{ + int i; + struct pxa_udc *udc = platform_get_drvdata(_dev); + struct pxa_ep *ep; + + ep = &udc->pxa_ep[0]; + udc_ep_writel(ep, UDCCSR, udc->udccsr0 & (UDCCSR0_FST | UDCCSR0_DME)); + for (i = 1; i < NR_PXA_ENDPOINTS; i++) { + ep = &udc->pxa_ep[i]; + udc_ep_writel(ep, UDCCSR, ep->udccsr_value); + udc_ep_writel(ep, UDCCR, ep->udccr_value); + ep_dbg(ep, "udccsr:0x%03x, udccr:0x%x\n", + ep->udccsr_value, ep->udccr_value); + } + + dplus_pullup(udc, udc->pullup_resume); + if (should_enable_udc(udc)) + udc_enable(udc); + /* + * We do not handle OTG yet. + * + * OTGPH bit is set when sleep mode is entered. + * it indicates that OTG pad is retaining its state. + * Upon exit from sleep mode and before clearing OTGPH, + * Software must configure the USB OTG pad, UDC, and UHC + * to the state they were in before entering sleep mode. + */ + pxa27x_clear_otgph(); + + return 0; +} +#endif + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:pxa27x-udc"); + +static struct platform_driver udc_driver = { + .driver = { + .name = "pxa27x-udc", + .owner = THIS_MODULE, + }, + .probe = pxa_udc_probe, + .remove = pxa_udc_remove, + .shutdown = pxa_udc_shutdown, +#ifdef CONFIG_PM + .suspend = pxa_udc_suspend, + .resume = pxa_udc_resume +#endif +}; + +module_platform_driver(udc_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Robert Jarzmik"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/pxa27x_udc.h b/drivers/usb/gadget/pxa27x_udc.h new file mode 100644 index 00000000..28f2b535 --- /dev/null +++ b/drivers/usb/gadget/pxa27x_udc.h @@ -0,0 +1,497 @@ +/* + * linux/drivers/usb/gadget/pxa27x_udc.h + * Intel PXA27x on-chip full speed USB device controller + * + * Inspired by original driver by Frank Becker, David Brownell, and others. + * Copyright (C) 2008 Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __LINUX_USB_GADGET_PXA27X_H +#define __LINUX_USB_GADGET_PXA27X_H + +#include +#include +#include +#include + +/* + * Register definitions + */ +/* Offsets */ +#define UDCCR 0x0000 /* UDC Control Register */ +#define UDCICR0 0x0004 /* UDC Interrupt Control Register0 */ +#define UDCICR1 0x0008 /* UDC Interrupt Control Register1 */ +#define UDCISR0 0x000C /* UDC Interrupt Status Register 0 */ +#define UDCISR1 0x0010 /* UDC Interrupt Status Register 1 */ +#define UDCFNR 0x0014 /* UDC Frame Number Register */ +#define UDCOTGICR 0x0018 /* UDC On-The-Go interrupt control */ +#define UP2OCR 0x0020 /* USB Port 2 Output Control register */ +#define UP3OCR 0x0024 /* USB Port 3 Output Control register */ +#define UDCCSRn(x) (0x0100 + ((x)<<2)) /* UDC Control/Status register */ +#define UDCBCRn(x) (0x0200 + ((x)<<2)) /* UDC Byte Count Register */ +#define UDCDRn(x) (0x0300 + ((x)<<2)) /* UDC Data Register */ +#define UDCCRn(x) (0x0400 + ((x)<<2)) /* UDC Control Register */ + +#define UDCCR_OEN (1 << 31) /* On-the-Go Enable */ +#define UDCCR_AALTHNP (1 << 30) /* A-device Alternate Host Negotiation + Protocol Port Support */ +#define UDCCR_AHNP (1 << 29) /* A-device Host Negotiation Protocol + Support */ +#define UDCCR_BHNP (1 << 28) /* B-device Host Negotiation Protocol + Enable */ +#define UDCCR_DWRE (1 << 16) /* Device Remote Wake-up Enable */ +#define UDCCR_ACN (0x03 << 11) /* Active UDC configuration Number */ +#define UDCCR_ACN_S 11 +#define UDCCR_AIN (0x07 << 8) /* Active UDC interface Number */ +#define UDCCR_AIN_S 8 +#define UDCCR_AAISN (0x07 << 5) /* Active UDC Alternate Interface + Setting Number */ +#define UDCCR_AAISN_S 5 +#define UDCCR_SMAC (1 << 4) /* Switch Endpoint Memory to Active + Configuration */ +#define UDCCR_EMCE (1 << 3) /* Endpoint Memory Configuration + Error */ +#define UDCCR_UDR (1 << 2) /* UDC Resume */ +#define UDCCR_UDA (1 << 1) /* UDC Active */ +#define UDCCR_UDE (1 << 0) /* UDC Enable */ + +#define UDCICR_INT(n, intr) (((intr) & 0x03) << (((n) & 0x0F) * 2)) +#define UDCICR1_IECC (1 << 31) /* IntEn - Configuration Change */ +#define UDCICR1_IESOF (1 << 30) /* IntEn - Start of Frame */ +#define UDCICR1_IERU (1 << 29) /* IntEn - Resume */ +#define UDCICR1_IESU (1 << 28) /* IntEn - Suspend */ +#define UDCICR1_IERS (1 << 27) /* IntEn - Reset */ +#define UDCICR_FIFOERR (1 << 1) /* FIFO Error interrupt for EP */ +#define UDCICR_PKTCOMPL (1 << 0) /* Packet Complete interrupt for EP */ +#define UDCICR_INT_MASK (UDCICR_FIFOERR | UDCICR_PKTCOMPL) + +#define UDCISR_INT(n, intr) (((intr) & 0x03) << (((n) & 0x0F) * 2)) +#define UDCISR1_IRCC (1 << 31) /* IntReq - Configuration Change */ +#define UDCISR1_IRSOF (1 << 30) /* IntReq - Start of Frame */ +#define UDCISR1_IRRU (1 << 29) /* IntReq - Resume */ +#define UDCISR1_IRSU (1 << 28) /* IntReq - Suspend */ +#define UDCISR1_IRRS (1 << 27) /* IntReq - Reset */ +#define UDCISR_INT_MASK (UDCICR_FIFOERR | UDCICR_PKTCOMPL) + +#define UDCOTGICR_IESF (1 << 24) /* OTG SET_FEATURE command recvd */ +#define UDCOTGICR_IEXR (1 << 17) /* Extra Transceiver Interrupt + Rising Edge Interrupt Enable */ +#define UDCOTGICR_IEXF (1 << 16) /* Extra Transceiver Interrupt + Falling Edge Interrupt Enable */ +#define UDCOTGICR_IEVV40R (1 << 9) /* OTG Vbus Valid 4.0V Rising Edge + Interrupt Enable */ +#define UDCOTGICR_IEVV40F (1 << 8) /* OTG Vbus Valid 4.0V Falling Edge + Interrupt Enable */ +#define UDCOTGICR_IEVV44R (1 << 7) /* OTG Vbus Valid 4.4V Rising Edge + Interrupt Enable */ +#define UDCOTGICR_IEVV44F (1 << 6) /* OTG Vbus Valid 4.4V Falling Edge + Interrupt Enable */ +#define UDCOTGICR_IESVR (1 << 5) /* OTG Session Valid Rising Edge + Interrupt Enable */ +#define UDCOTGICR_IESVF (1 << 4) /* OTG Session Valid Falling Edge + Interrupt Enable */ +#define UDCOTGICR_IESDR (1 << 3) /* OTG A-Device SRP Detect Rising + Edge Interrupt Enable */ +#define UDCOTGICR_IESDF (1 << 2) /* OTG A-Device SRP Detect Falling + Edge Interrupt Enable */ +#define UDCOTGICR_IEIDR (1 << 1) /* OTG ID Change Rising Edge + Interrupt Enable */ +#define UDCOTGICR_IEIDF (1 << 0) /* OTG ID Change Falling Edge + Interrupt Enable */ + +/* Host Port 2 field bits */ +#define UP2OCR_CPVEN (1 << 0) /* Charge Pump Vbus Enable */ +#define UP2OCR_CPVPE (1 << 1) /* Charge Pump Vbus Pulse Enable */ + /* Transceiver enablers */ +#define UP2OCR_DPPDE (1 << 2) /* D+ Pull Down Enable */ +#define UP2OCR_DMPDE (1 << 3) /* D- Pull Down Enable */ +#define UP2OCR_DPPUE (1 << 4) /* D+ Pull Up Enable */ +#define UP2OCR_DMPUE (1 << 5) /* D- Pull Up Enable */ +#define UP2OCR_DPPUBE (1 << 6) /* D+ Pull Up Bypass Enable */ +#define UP2OCR_DMPUBE (1 << 7) /* D- Pull Up Bypass Enable */ +#define UP2OCR_EXSP (1 << 8) /* External Transceiver Speed Control */ +#define UP2OCR_EXSUS (1 << 9) /* External Transceiver Speed Enable */ +#define UP2OCR_IDON (1 << 10) /* OTG ID Read Enable */ +#define UP2OCR_HXS (1 << 16) /* Transceiver Output Select */ +#define UP2OCR_HXOE (1 << 17) /* Transceiver Output Enable */ +#define UP2OCR_SEOS (1 << 24) /* Single-Ended Output Select */ + +#define UDCCSR0_ACM (1 << 9) /* Ack Control Mode */ +#define UDCCSR0_AREN (1 << 8) /* Ack Response Enable */ +#define UDCCSR0_SA (1 << 7) /* Setup Active */ +#define UDCCSR0_RNE (1 << 6) /* Receive FIFO Not Empty */ +#define UDCCSR0_FST (1 << 5) /* Force Stall */ +#define UDCCSR0_SST (1 << 4) /* Sent Stall */ +#define UDCCSR0_DME (1 << 3) /* DMA Enable */ +#define UDCCSR0_FTF (1 << 2) /* Flush Transmit FIFO */ +#define UDCCSR0_IPR (1 << 1) /* IN Packet Ready */ +#define UDCCSR0_OPC (1 << 0) /* OUT Packet Complete */ + +#define UDCCSR_DPE (1 << 9) /* Data Packet Error */ +#define UDCCSR_FEF (1 << 8) /* Flush Endpoint FIFO */ +#define UDCCSR_SP (1 << 7) /* Short Packet Control/Status */ +#define UDCCSR_BNE (1 << 6) /* Buffer Not Empty (IN endpoints) */ +#define UDCCSR_BNF (1 << 6) /* Buffer Not Full (OUT endpoints) */ +#define UDCCSR_FST (1 << 5) /* Force STALL */ +#define UDCCSR_SST (1 << 4) /* Sent STALL */ +#define UDCCSR_DME (1 << 3) /* DMA Enable */ +#define UDCCSR_TRN (1 << 2) /* Tx/Rx NAK */ +#define UDCCSR_PC (1 << 1) /* Packet Complete */ +#define UDCCSR_FS (1 << 0) /* FIFO needs service */ + +#define UDCCONR_CN (0x03 << 25) /* Configuration Number */ +#define UDCCONR_CN_S 25 +#define UDCCONR_IN (0x07 << 22) /* Interface Number */ +#define UDCCONR_IN_S 22 +#define UDCCONR_AISN (0x07 << 19) /* Alternate Interface Number */ +#define UDCCONR_AISN_S 19 +#define UDCCONR_EN (0x0f << 15) /* Endpoint Number */ +#define UDCCONR_EN_S 15 +#define UDCCONR_ET (0x03 << 13) /* Endpoint Type: */ +#define UDCCONR_ET_S 13 +#define UDCCONR_ET_INT (0x03 << 13) /* Interrupt */ +#define UDCCONR_ET_BULK (0x02 << 13) /* Bulk */ +#define UDCCONR_ET_ISO (0x01 << 13) /* Isochronous */ +#define UDCCONR_ET_NU (0x00 << 13) /* Not used */ +#define UDCCONR_ED (1 << 12) /* Endpoint Direction */ +#define UDCCONR_MPS (0x3ff << 2) /* Maximum Packet Size */ +#define UDCCONR_MPS_S 2 +#define UDCCONR_DE (1 << 1) /* Double Buffering Enable */ +#define UDCCONR_EE (1 << 0) /* Endpoint Enable */ + +#define UDCCR_MASK_BITS (UDCCR_OEN | UDCCR_SMAC | UDCCR_UDR | UDCCR_UDE) +#define UDCCSR_WR_MASK (UDCCSR_DME | UDCCSR_FST) +#define UDC_FNR_MASK (0x7ff) +#define UDC_BCR_MASK (0x3ff) + +/* + * UDCCR = UDC Endpoint Configuration Registers + * UDCCSR = UDC Control/Status Register for this EP + * UDCBCR = UDC Byte Count Remaining (contents of OUT fifo) + * UDCDR = UDC Endpoint Data Register (the fifo) + */ +#define ofs_UDCCR(ep) (UDCCRn(ep->idx)) +#define ofs_UDCCSR(ep) (UDCCSRn(ep->idx)) +#define ofs_UDCBCR(ep) (UDCBCRn(ep->idx)) +#define ofs_UDCDR(ep) (UDCDRn(ep->idx)) + +/* Register access macros */ +#define udc_ep_readl(ep, reg) \ + __raw_readl((ep)->dev->regs + ofs_##reg(ep)) +#define udc_ep_writel(ep, reg, value) \ + __raw_writel((value), ep->dev->regs + ofs_##reg(ep)) +#define udc_ep_readb(ep, reg) \ + __raw_readb((ep)->dev->regs + ofs_##reg(ep)) +#define udc_ep_writeb(ep, reg, value) \ + __raw_writeb((value), ep->dev->regs + ofs_##reg(ep)) +#define udc_readl(dev, reg) \ + __raw_readl((dev)->regs + (reg)) +#define udc_writel(udc, reg, value) \ + __raw_writel((value), (udc)->regs + (reg)) + +#define UDCCSR_MASK (UDCCSR_FST | UDCCSR_DME) +#define UDCCISR0_EP_MASK ~0 +#define UDCCISR1_EP_MASK 0xffff +#define UDCCSR0_CTRL_REQ_MASK (UDCCSR0_OPC | UDCCSR0_SA | UDCCSR0_RNE) + +#define EPIDX(ep) (ep->idx) +#define EPADDR(ep) (ep->addr) +#define EPXFERTYPE(ep) (ep->type) +#define EPNAME(ep) (ep->name) +#define is_ep0(ep) (!ep->idx) +#define EPXFERTYPE_is_ISO(ep) (EPXFERTYPE(ep) == USB_ENDPOINT_XFER_ISOC) + +/* + * Endpoint definitions + * + * Once enabled, pxa endpoint configuration is freezed, and cannot change + * unless a reset happens or the udc is disabled. + * Therefore, we must define all pxa potential endpoint definitions needed for + * all gadget and set them up before the udc is enabled. + * + * As the architecture chosen is fully static, meaning the pxa endpoint + * configurations are set up once and for all, we must provide a way to match + * one usb endpoint (usb_ep) to several pxa endpoints. The reason is that gadget + * layer autoconf doesn't choose the usb_ep endpoint on (config, interface, alt) + * criteria, while the pxa architecture requires that. + * + * The solution is to define several pxa endpoints matching one usb_ep. Ex: + * - "ep1-in" matches pxa endpoint EPA (which is an IN ep at addr 1, when + * the udc talks on (config=3, interface=0, alt=0) + * - "ep1-in" matches pxa endpoint EPB (which is an IN ep at addr 1, when + * the udc talks on (config=3, interface=0, alt=1) + * - "ep1-in" matches pxa endpoint EPC (which is an IN ep at addr 1, when + * the udc talks on (config=2, interface=0, alt=0) + * + * We'll define the pxa endpoint by its index (EPA => idx=1, EPB => idx=2, ...) + */ + +/* + * Endpoint definition helpers + */ +#define USB_EP_DEF(addr, bname, dir, type, maxpkt) \ +{ .usb_ep = { .name = bname, .ops = &pxa_ep_ops, .maxpacket = maxpkt, }, \ + .desc = { .bEndpointAddress = addr | (dir ? USB_DIR_IN : 0), \ + .bmAttributes = type, \ + .wMaxPacketSize = maxpkt, }, \ + .dev = &memory \ +} +#define USB_EP_BULK(addr, bname, dir) \ + USB_EP_DEF(addr, bname, dir, USB_ENDPOINT_XFER_BULK, BULK_FIFO_SIZE) +#define USB_EP_ISO(addr, bname, dir) \ + USB_EP_DEF(addr, bname, dir, USB_ENDPOINT_XFER_ISOC, ISO_FIFO_SIZE) +#define USB_EP_INT(addr, bname, dir) \ + USB_EP_DEF(addr, bname, dir, USB_ENDPOINT_XFER_INT, INT_FIFO_SIZE) +#define USB_EP_IN_BULK(n) USB_EP_BULK(n, "ep" #n "in-bulk", 1) +#define USB_EP_OUT_BULK(n) USB_EP_BULK(n, "ep" #n "out-bulk", 0) +#define USB_EP_IN_ISO(n) USB_EP_ISO(n, "ep" #n "in-iso", 1) +#define USB_EP_OUT_ISO(n) USB_EP_ISO(n, "ep" #n "out-iso", 0) +#define USB_EP_IN_INT(n) USB_EP_INT(n, "ep" #n "in-int", 1) +#define USB_EP_CTRL USB_EP_DEF(0, "ep0", 0, 0, EP0_FIFO_SIZE) + +#define PXA_EP_DEF(_idx, _addr, dir, _type, maxpkt, _config, iface, altset) \ +{ \ + .dev = &memory, \ + .name = "ep" #_idx, \ + .idx = _idx, .enabled = 0, \ + .dir_in = dir, .addr = _addr, \ + .config = _config, .interface = iface, .alternate = altset, \ + .type = _type, .fifo_size = maxpkt, \ +} +#define PXA_EP_BULK(_idx, addr, dir, config, iface, alt) \ + PXA_EP_DEF(_idx, addr, dir, USB_ENDPOINT_XFER_BULK, BULK_FIFO_SIZE, \ + config, iface, alt) +#define PXA_EP_ISO(_idx, addr, dir, config, iface, alt) \ + PXA_EP_DEF(_idx, addr, dir, USB_ENDPOINT_XFER_ISOC, ISO_FIFO_SIZE, \ + config, iface, alt) +#define PXA_EP_INT(_idx, addr, dir, config, iface, alt) \ + PXA_EP_DEF(_idx, addr, dir, USB_ENDPOINT_XFER_INT, INT_FIFO_SIZE, \ + config, iface, alt) +#define PXA_EP_IN_BULK(i, adr, c, f, a) PXA_EP_BULK(i, adr, 1, c, f, a) +#define PXA_EP_OUT_BULK(i, adr, c, f, a) PXA_EP_BULK(i, adr, 0, c, f, a) +#define PXA_EP_IN_ISO(i, adr, c, f, a) PXA_EP_ISO(i, adr, 1, c, f, a) +#define PXA_EP_OUT_ISO(i, adr, c, f, a) PXA_EP_ISO(i, adr, 0, c, f, a) +#define PXA_EP_IN_INT(i, adr, c, f, a) PXA_EP_INT(i, adr, 1, c, f, a) +#define PXA_EP_CTRL PXA_EP_DEF(0, 0, 0, 0, EP0_FIFO_SIZE, 0, 0, 0) + +struct pxa27x_udc; + +struct stats { + unsigned long in_ops; + unsigned long out_ops; + unsigned long in_bytes; + unsigned long out_bytes; + unsigned long irqs; +}; + +/** + * struct udc_usb_ep - container of each usb_ep structure + * @usb_ep: usb endpoint + * @desc: usb descriptor, especially type and address + * @dev: udc managing this endpoint + * @pxa_ep: matching pxa_ep (cache of find_pxa_ep() call) + */ +struct udc_usb_ep { + struct usb_ep usb_ep; + struct usb_endpoint_descriptor desc; + struct pxa_udc *dev; + struct pxa_ep *pxa_ep; +}; + +/** + * struct pxa_ep - pxa endpoint + * @dev: udc device + * @queue: requests queue + * @lock: lock to pxa_ep data (queues and stats) + * @enabled: true when endpoint enabled (not stopped by gadget layer) + * @in_handle_ep: number of recursions of handle_ep() function + * Prevents deadlocks or infinite recursions of types : + * irq->handle_ep()->req_done()->req.complete()->pxa_ep_queue()->handle_ep() + * or + * pxa_ep_queue()->handle_ep()->req_done()->req.complete()->pxa_ep_queue() + * @idx: endpoint index (1 => epA, 2 => epB, ..., 24 => epX) + * @name: endpoint name (for trace/debug purpose) + * @dir_in: 1 if IN endpoint, 0 if OUT endpoint + * @addr: usb endpoint number + * @config: configuration in which this endpoint is active + * @interface: interface in which this endpoint is active + * @alternate: altsetting in which this endpoitn is active + * @fifo_size: max packet size in the endpoint fifo + * @type: endpoint type (bulk, iso, int, ...) + * @udccsr_value: save register of UDCCSR0 for suspend/resume + * @udccr_value: save register of UDCCR for suspend/resume + * @stats: endpoint statistics + * + * The *PROBLEM* is that pxa's endpoint configuration scheme is both misdesigned + * (cares about config/interface/altsetting, thus placing needless limits on + * device capability) and full of implementation bugs forcing it to be set up + * for use more or less like a pxa255. + * + * As we define the pxa_ep statically, we must guess all needed pxa_ep for all + * gadget which may work with this udc driver. + */ +struct pxa_ep { + struct pxa_udc *dev; + + struct list_head queue; + spinlock_t lock; /* Protects this structure */ + /* (queues, stats) */ + unsigned enabled:1; + unsigned in_handle_ep:1; + + unsigned idx:5; + char *name; + + /* + * Specific pxa endpoint data, needed for hardware initialization + */ + unsigned dir_in:1; + unsigned addr:4; + unsigned config:2; + unsigned interface:3; + unsigned alternate:3; + unsigned fifo_size; + unsigned type; + +#ifdef CONFIG_PM + u32 udccsr_value; + u32 udccr_value; +#endif + struct stats stats; +}; + +/** + * struct pxa27x_request - container of each usb_request structure + * @req: usb request + * @udc_usb_ep: usb endpoint the request was submitted on + * @in_use: sanity check if request already queued on an pxa_ep + * @queue: linked list of requests, linked on pxa_ep->queue + */ +struct pxa27x_request { + struct usb_request req; + struct udc_usb_ep *udc_usb_ep; + unsigned in_use:1; + struct list_head queue; +}; + +enum ep0_state { + WAIT_FOR_SETUP, + SETUP_STAGE, + IN_DATA_STAGE, + OUT_DATA_STAGE, + IN_STATUS_STAGE, + OUT_STATUS_STAGE, + STALL, + WAIT_ACK_SET_CONF_INTERF +}; + +static char *ep0_state_name[] = { + "WAIT_FOR_SETUP", "SETUP_STAGE", "IN_DATA_STAGE", "OUT_DATA_STAGE", + "IN_STATUS_STAGE", "OUT_STATUS_STAGE", "STALL", + "WAIT_ACK_SET_CONF_INTERF" +}; +#define EP0_STNAME(udc) ep0_state_name[(udc)->ep0state] + +#define EP0_FIFO_SIZE 16U +#define BULK_FIFO_SIZE 64U +#define ISO_FIFO_SIZE 256U +#define INT_FIFO_SIZE 16U + +struct udc_stats { + unsigned long irqs_reset; + unsigned long irqs_suspend; + unsigned long irqs_resume; + unsigned long irqs_reconfig; +}; + +#define NR_USB_ENDPOINTS (1 + 5) /* ep0 + ep1in-bulk + .. + ep3in-iso */ +#define NR_PXA_ENDPOINTS (1 + 14) /* ep0 + epA + epB + .. + epX */ + +/** + * struct pxa_udc - udc structure + * @regs: mapped IO space + * @irq: udc irq + * @clk: udc clock + * @usb_gadget: udc gadget structure + * @driver: bound gadget (zero, g_ether, g_mass_storage, ...) + * @dev: device + * @mach: machine info, used to activate specific GPIO + * @transceiver: external transceiver to handle vbus sense and D+ pullup + * @ep0state: control endpoint state machine state + * @stats: statistics on udc usage + * @udc_usb_ep: array of usb endpoints offered by the gadget + * @pxa_ep: array of pxa available endpoints + * @enabled: UDC was enabled by a previous udc_enable() + * @pullup_on: if pullup resistor connected to D+ pin + * @pullup_resume: if pullup resistor should be connected to D+ pin on resume + * @config: UDC active configuration + * @last_interface: UDC interface of the last SET_INTERFACE host request + * @last_alternate: UDC altsetting of the last SET_INTERFACE host request + * @udccsr0: save of udccsr0 in case of suspend + * @debugfs_root: root entry of debug filesystem + * @debugfs_state: debugfs entry for "udcstate" + * @debugfs_queues: debugfs entry for "queues" + * @debugfs_eps: debugfs entry for "epstate" + */ +struct pxa_udc { + void __iomem *regs; + int irq; + struct clk *clk; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct device *dev; + struct pxa2xx_udc_mach_info *mach; + struct usb_phy *transceiver; + + enum ep0_state ep0state; + struct udc_stats stats; + + struct udc_usb_ep udc_usb_ep[NR_USB_ENDPOINTS]; + struct pxa_ep pxa_ep[NR_PXA_ENDPOINTS]; + + unsigned enabled:1; + unsigned pullup_on:1; + unsigned pullup_resume:1; + unsigned vbus_sensed:1; + unsigned config:2; + unsigned last_interface:3; + unsigned last_alternate:3; + +#ifdef CONFIG_PM + unsigned udccsr0; +#endif +#ifdef CONFIG_USB_GADGET_DEBUG_FS + struct dentry *debugfs_root; + struct dentry *debugfs_state; + struct dentry *debugfs_queues; + struct dentry *debugfs_eps; +#endif +}; +#define to_pxa(g) (container_of((g), struct pxa_udc, gadget)) + +static inline struct pxa_udc *to_gadget_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct pxa_udc, gadget); +} + +/* + * Debugging/message support + */ +#define ep_dbg(ep, fmt, arg...) \ + dev_dbg(ep->dev->dev, "%s:%s: " fmt, EPNAME(ep), __func__, ## arg) +#define ep_vdbg(ep, fmt, arg...) \ + dev_vdbg(ep->dev->dev, "%s:%s: " fmt, EPNAME(ep), __func__, ## arg) +#define ep_err(ep, fmt, arg...) \ + dev_err(ep->dev->dev, "%s:%s: " fmt, EPNAME(ep), __func__, ## arg) +#define ep_info(ep, fmt, arg...) \ + dev_info(ep->dev->dev, "%s:%s: " fmt, EPNAME(ep), __func__, ## arg) +#define ep_warn(ep, fmt, arg...) \ + dev_warn(ep->dev->dev, "%s:%s:" fmt, EPNAME(ep), __func__, ## arg) + +#endif /* __LINUX_USB_GADGET_PXA27X_H */ diff --git a/drivers/usb/gadget/r8a66597-udc.c b/drivers/usb/gadget/r8a66597-udc.c new file mode 100644 index 00000000..7ff7d9cf --- /dev/null +++ b/drivers/usb/gadget/r8a66597-udc.c @@ -0,0 +1,2030 @@ +/* + * R8A66597 UDC (USB gadget) + * + * Copyright (C) 2006-2009 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "r8a66597-udc.h" + +#define DRIVER_VERSION "2011-09-26" + +static const char udc_name[] = "r8a66597_udc"; +static const char *r8a66597_ep_name[] = { + "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7", + "ep8", "ep9", +}; + +static void init_controller(struct r8a66597 *r8a66597); +static void disable_controller(struct r8a66597 *r8a66597); +static void irq_ep0_write(struct r8a66597_ep *ep, struct r8a66597_request *req); +static void irq_packet_write(struct r8a66597_ep *ep, + struct r8a66597_request *req); +static int r8a66597_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags); + +static void transfer_complete(struct r8a66597_ep *ep, + struct r8a66597_request *req, int status); + +/*-------------------------------------------------------------------------*/ +static inline u16 get_usb_speed(struct r8a66597 *r8a66597) +{ + return r8a66597_read(r8a66597, DVSTCTR0) & RHST; +} + +static void enable_pipe_irq(struct r8a66597 *r8a66597, u16 pipenum, + unsigned long reg) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, INTENB0); + r8a66597_bclr(r8a66597, BEMPE | NRDYE | BRDYE, + INTENB0); + r8a66597_bset(r8a66597, (1 << pipenum), reg); + r8a66597_write(r8a66597, tmp, INTENB0); +} + +static void disable_pipe_irq(struct r8a66597 *r8a66597, u16 pipenum, + unsigned long reg) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, INTENB0); + r8a66597_bclr(r8a66597, BEMPE | NRDYE | BRDYE, + INTENB0); + r8a66597_bclr(r8a66597, (1 << pipenum), reg); + r8a66597_write(r8a66597, tmp, INTENB0); +} + +static void r8a66597_usb_connect(struct r8a66597 *r8a66597) +{ + r8a66597_bset(r8a66597, CTRE, INTENB0); + r8a66597_bset(r8a66597, BEMPE | BRDYE, INTENB0); + + r8a66597_bset(r8a66597, DPRPU, SYSCFG0); +} + +static void r8a66597_usb_disconnect(struct r8a66597 *r8a66597) +__releases(r8a66597->lock) +__acquires(r8a66597->lock) +{ + r8a66597_bclr(r8a66597, CTRE, INTENB0); + r8a66597_bclr(r8a66597, BEMPE | BRDYE, INTENB0); + r8a66597_bclr(r8a66597, DPRPU, SYSCFG0); + + r8a66597->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock(&r8a66597->lock); + r8a66597->driver->disconnect(&r8a66597->gadget); + spin_lock(&r8a66597->lock); + + disable_controller(r8a66597); + init_controller(r8a66597); + r8a66597_bset(r8a66597, VBSE, INTENB0); + INIT_LIST_HEAD(&r8a66597->ep[0].queue); +} + +static inline u16 control_reg_get_pid(struct r8a66597 *r8a66597, u16 pipenum) +{ + u16 pid = 0; + unsigned long offset; + + if (pipenum == 0) { + pid = r8a66597_read(r8a66597, DCPCTR) & PID; + } else if (pipenum < R8A66597_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + pid = r8a66597_read(r8a66597, offset) & PID; + } else { + dev_err(r8a66597_to_dev(r8a66597), "unexpect pipe num (%d)\n", + pipenum); + } + + return pid; +} + +static inline void control_reg_set_pid(struct r8a66597 *r8a66597, u16 pipenum, + u16 pid) +{ + unsigned long offset; + + if (pipenum == 0) { + r8a66597_mdfy(r8a66597, pid, PID, DCPCTR); + } else if (pipenum < R8A66597_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + r8a66597_mdfy(r8a66597, pid, PID, offset); + } else { + dev_err(r8a66597_to_dev(r8a66597), "unexpect pipe num (%d)\n", + pipenum); + } +} + +static inline void pipe_start(struct r8a66597 *r8a66597, u16 pipenum) +{ + control_reg_set_pid(r8a66597, pipenum, PID_BUF); +} + +static inline void pipe_stop(struct r8a66597 *r8a66597, u16 pipenum) +{ + control_reg_set_pid(r8a66597, pipenum, PID_NAK); +} + +static inline void pipe_stall(struct r8a66597 *r8a66597, u16 pipenum) +{ + control_reg_set_pid(r8a66597, pipenum, PID_STALL); +} + +static inline u16 control_reg_get(struct r8a66597 *r8a66597, u16 pipenum) +{ + u16 ret = 0; + unsigned long offset; + + if (pipenum == 0) { + ret = r8a66597_read(r8a66597, DCPCTR); + } else if (pipenum < R8A66597_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + ret = r8a66597_read(r8a66597, offset); + } else { + dev_err(r8a66597_to_dev(r8a66597), "unexpect pipe num (%d)\n", + pipenum); + } + + return ret; +} + +static inline void control_reg_sqclr(struct r8a66597 *r8a66597, u16 pipenum) +{ + unsigned long offset; + + pipe_stop(r8a66597, pipenum); + + if (pipenum == 0) { + r8a66597_bset(r8a66597, SQCLR, DCPCTR); + } else if (pipenum < R8A66597_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + r8a66597_bset(r8a66597, SQCLR, offset); + } else { + dev_err(r8a66597_to_dev(r8a66597), "unexpect pipe num (%d)\n", + pipenum); + } +} + +static void control_reg_sqset(struct r8a66597 *r8a66597, u16 pipenum) +{ + unsigned long offset; + + pipe_stop(r8a66597, pipenum); + + if (pipenum == 0) { + r8a66597_bset(r8a66597, SQSET, DCPCTR); + } else if (pipenum < R8A66597_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + r8a66597_bset(r8a66597, SQSET, offset); + } else { + dev_err(r8a66597_to_dev(r8a66597), + "unexpect pipe num(%d)\n", pipenum); + } +} + +static u16 control_reg_sqmon(struct r8a66597 *r8a66597, u16 pipenum) +{ + unsigned long offset; + + if (pipenum == 0) { + return r8a66597_read(r8a66597, DCPCTR) & SQMON; + } else if (pipenum < R8A66597_MAX_NUM_PIPE) { + offset = get_pipectr_addr(pipenum); + return r8a66597_read(r8a66597, offset) & SQMON; + } else { + dev_err(r8a66597_to_dev(r8a66597), + "unexpect pipe num(%d)\n", pipenum); + } + + return 0; +} + +static u16 save_usb_toggle(struct r8a66597 *r8a66597, u16 pipenum) +{ + return control_reg_sqmon(r8a66597, pipenum); +} + +static void restore_usb_toggle(struct r8a66597 *r8a66597, u16 pipenum, + u16 toggle) +{ + if (toggle) + control_reg_sqset(r8a66597, pipenum); + else + control_reg_sqclr(r8a66597, pipenum); +} + +static inline int get_buffer_size(struct r8a66597 *r8a66597, u16 pipenum) +{ + u16 tmp; + int size; + + if (pipenum == 0) { + tmp = r8a66597_read(r8a66597, DCPCFG); + if ((tmp & R8A66597_CNTMD) != 0) + size = 256; + else { + tmp = r8a66597_read(r8a66597, DCPMAXP); + size = tmp & MAXP; + } + } else { + r8a66597_write(r8a66597, pipenum, PIPESEL); + tmp = r8a66597_read(r8a66597, PIPECFG); + if ((tmp & R8A66597_CNTMD) != 0) { + tmp = r8a66597_read(r8a66597, PIPEBUF); + size = ((tmp >> 10) + 1) * 64; + } else { + tmp = r8a66597_read(r8a66597, PIPEMAXP); + size = tmp & MXPS; + } + } + + return size; +} + +static inline unsigned short mbw_value(struct r8a66597 *r8a66597) +{ + if (r8a66597->pdata->on_chip) + return MBW_32; + else + return MBW_16; +} + +static void r8a66597_change_curpipe(struct r8a66597 *r8a66597, u16 pipenum, + u16 isel, u16 fifosel) +{ + u16 tmp, mask, loop; + int i = 0; + + if (!pipenum) { + mask = ISEL | CURPIPE; + loop = isel; + } else { + mask = CURPIPE; + loop = pipenum; + } + r8a66597_mdfy(r8a66597, loop, mask, fifosel); + + do { + tmp = r8a66597_read(r8a66597, fifosel); + if (i++ > 1000000) { + dev_err(r8a66597_to_dev(r8a66597), + "r8a66597: register%x, loop %x " + "is timeout\n", fifosel, loop); + break; + } + ndelay(1); + } while ((tmp & mask) != loop); +} + +static inline void pipe_change(struct r8a66597 *r8a66597, u16 pipenum) +{ + struct r8a66597_ep *ep = r8a66597->pipenum2ep[pipenum]; + + if (ep->use_dma) + r8a66597_bclr(r8a66597, DREQE, ep->fifosel); + + r8a66597_mdfy(r8a66597, pipenum, CURPIPE, ep->fifosel); + + ndelay(450); + + if (r8a66597_is_sudmac(r8a66597) && ep->use_dma) + r8a66597_bclr(r8a66597, mbw_value(r8a66597), ep->fifosel); + else + r8a66597_bset(r8a66597, mbw_value(r8a66597), ep->fifosel); + + if (ep->use_dma) + r8a66597_bset(r8a66597, DREQE, ep->fifosel); +} + +static int pipe_buffer_setting(struct r8a66597 *r8a66597, + struct r8a66597_pipe_info *info) +{ + u16 bufnum = 0, buf_bsize = 0; + u16 pipecfg = 0; + + if (info->pipe == 0) + return -EINVAL; + + r8a66597_write(r8a66597, info->pipe, PIPESEL); + + if (info->dir_in) + pipecfg |= R8A66597_DIR; + pipecfg |= info->type; + pipecfg |= info->epnum; + switch (info->type) { + case R8A66597_INT: + bufnum = 4 + (info->pipe - R8A66597_BASE_PIPENUM_INT); + buf_bsize = 0; + break; + case R8A66597_BULK: + /* isochronous pipes may be used as bulk pipes */ + if (info->pipe >= R8A66597_BASE_PIPENUM_BULK) + bufnum = info->pipe - R8A66597_BASE_PIPENUM_BULK; + else + bufnum = info->pipe - R8A66597_BASE_PIPENUM_ISOC; + + bufnum = R8A66597_BASE_BUFNUM + (bufnum * 16); + buf_bsize = 7; + pipecfg |= R8A66597_DBLB; + if (!info->dir_in) + pipecfg |= R8A66597_SHTNAK; + break; + case R8A66597_ISO: + bufnum = R8A66597_BASE_BUFNUM + + (info->pipe - R8A66597_BASE_PIPENUM_ISOC) * 16; + buf_bsize = 7; + break; + } + + if (buf_bsize && ((bufnum + 16) >= R8A66597_MAX_BUFNUM)) { + pr_err("r8a66597 pipe memory is insufficient\n"); + return -ENOMEM; + } + + r8a66597_write(r8a66597, pipecfg, PIPECFG); + r8a66597_write(r8a66597, (buf_bsize << 10) | (bufnum), PIPEBUF); + r8a66597_write(r8a66597, info->maxpacket, PIPEMAXP); + if (info->interval) + info->interval--; + r8a66597_write(r8a66597, info->interval, PIPEPERI); + + return 0; +} + +static void pipe_buffer_release(struct r8a66597 *r8a66597, + struct r8a66597_pipe_info *info) +{ + if (info->pipe == 0) + return; + + if (is_bulk_pipe(info->pipe)) { + r8a66597->bulk--; + } else if (is_interrupt_pipe(info->pipe)) { + r8a66597->interrupt--; + } else if (is_isoc_pipe(info->pipe)) { + r8a66597->isochronous--; + if (info->type == R8A66597_BULK) + r8a66597->bulk--; + } else { + dev_err(r8a66597_to_dev(r8a66597), + "ep_release: unexpect pipenum (%d)\n", info->pipe); + } +} + +static void pipe_initialize(struct r8a66597_ep *ep) +{ + struct r8a66597 *r8a66597 = ep->r8a66597; + + r8a66597_mdfy(r8a66597, 0, CURPIPE, ep->fifosel); + + r8a66597_write(r8a66597, ACLRM, ep->pipectr); + r8a66597_write(r8a66597, 0, ep->pipectr); + r8a66597_write(r8a66597, SQCLR, ep->pipectr); + if (ep->use_dma) { + r8a66597_mdfy(r8a66597, ep->pipenum, CURPIPE, ep->fifosel); + + ndelay(450); + + r8a66597_bset(r8a66597, mbw_value(r8a66597), ep->fifosel); + } +} + +static void r8a66597_ep_setting(struct r8a66597 *r8a66597, + struct r8a66597_ep *ep, + const struct usb_endpoint_descriptor *desc, + u16 pipenum, int dma) +{ + ep->use_dma = 0; + ep->fifoaddr = CFIFO; + ep->fifosel = CFIFOSEL; + ep->fifoctr = CFIFOCTR; + + ep->pipectr = get_pipectr_addr(pipenum); + if (is_bulk_pipe(pipenum) || is_isoc_pipe(pipenum)) { + ep->pipetre = get_pipetre_addr(pipenum); + ep->pipetrn = get_pipetrn_addr(pipenum); + } else { + ep->pipetre = 0; + ep->pipetrn = 0; + } + ep->pipenum = pipenum; + ep->ep.maxpacket = usb_endpoint_maxp(desc); + r8a66597->pipenum2ep[pipenum] = ep; + r8a66597->epaddr2ep[desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK] + = ep; + INIT_LIST_HEAD(&ep->queue); +} + +static void r8a66597_ep_release(struct r8a66597_ep *ep) +{ + struct r8a66597 *r8a66597 = ep->r8a66597; + u16 pipenum = ep->pipenum; + + if (pipenum == 0) + return; + + if (ep->use_dma) + r8a66597->num_dma--; + ep->pipenum = 0; + ep->busy = 0; + ep->use_dma = 0; +} + +static int alloc_pipe_config(struct r8a66597_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct r8a66597 *r8a66597 = ep->r8a66597; + struct r8a66597_pipe_info info; + int dma = 0; + unsigned char *counter; + int ret; + + ep->ep.desc = desc; + + if (ep->pipenum) /* already allocated pipe */ + return 0; + + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + if (r8a66597->bulk >= R8A66597_MAX_NUM_BULK) { + if (r8a66597->isochronous >= R8A66597_MAX_NUM_ISOC) { + dev_err(r8a66597_to_dev(r8a66597), + "bulk pipe is insufficient\n"); + return -ENODEV; + } else { + info.pipe = R8A66597_BASE_PIPENUM_ISOC + + r8a66597->isochronous; + counter = &r8a66597->isochronous; + } + } else { + info.pipe = R8A66597_BASE_PIPENUM_BULK + r8a66597->bulk; + counter = &r8a66597->bulk; + } + info.type = R8A66597_BULK; + dma = 1; + break; + case USB_ENDPOINT_XFER_INT: + if (r8a66597->interrupt >= R8A66597_MAX_NUM_INT) { + dev_err(r8a66597_to_dev(r8a66597), + "interrupt pipe is insufficient\n"); + return -ENODEV; + } + info.pipe = R8A66597_BASE_PIPENUM_INT + r8a66597->interrupt; + info.type = R8A66597_INT; + counter = &r8a66597->interrupt; + break; + case USB_ENDPOINT_XFER_ISOC: + if (r8a66597->isochronous >= R8A66597_MAX_NUM_ISOC) { + dev_err(r8a66597_to_dev(r8a66597), + "isochronous pipe is insufficient\n"); + return -ENODEV; + } + info.pipe = R8A66597_BASE_PIPENUM_ISOC + r8a66597->isochronous; + info.type = R8A66597_ISO; + counter = &r8a66597->isochronous; + break; + default: + dev_err(r8a66597_to_dev(r8a66597), "unexpect xfer type\n"); + return -EINVAL; + } + ep->type = info.type; + + info.epnum = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + info.maxpacket = usb_endpoint_maxp(desc); + info.interval = desc->bInterval; + if (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + info.dir_in = 1; + else + info.dir_in = 0; + + ret = pipe_buffer_setting(r8a66597, &info); + if (ret < 0) { + dev_err(r8a66597_to_dev(r8a66597), + "pipe_buffer_setting fail\n"); + return ret; + } + + (*counter)++; + if ((counter == &r8a66597->isochronous) && info.type == R8A66597_BULK) + r8a66597->bulk++; + + r8a66597_ep_setting(r8a66597, ep, desc, info.pipe, dma); + pipe_initialize(ep); + + return 0; +} + +static int free_pipe_config(struct r8a66597_ep *ep) +{ + struct r8a66597 *r8a66597 = ep->r8a66597; + struct r8a66597_pipe_info info; + + info.pipe = ep->pipenum; + info.type = ep->type; + pipe_buffer_release(r8a66597, &info); + r8a66597_ep_release(ep); + + return 0; +} + +/*-------------------------------------------------------------------------*/ +static void pipe_irq_enable(struct r8a66597 *r8a66597, u16 pipenum) +{ + enable_irq_ready(r8a66597, pipenum); + enable_irq_nrdy(r8a66597, pipenum); +} + +static void pipe_irq_disable(struct r8a66597 *r8a66597, u16 pipenum) +{ + disable_irq_ready(r8a66597, pipenum); + disable_irq_nrdy(r8a66597, pipenum); +} + +/* if complete is true, gadget driver complete function is not call */ +static void control_end(struct r8a66597 *r8a66597, unsigned ccpl) +{ + r8a66597->ep[0].internal_ccpl = ccpl; + pipe_start(r8a66597, 0); + r8a66597_bset(r8a66597, CCPL, DCPCTR); +} + +static void start_ep0_write(struct r8a66597_ep *ep, + struct r8a66597_request *req) +{ + struct r8a66597 *r8a66597 = ep->r8a66597; + + pipe_change(r8a66597, ep->pipenum); + r8a66597_mdfy(r8a66597, ISEL, (ISEL | CURPIPE), CFIFOSEL); + r8a66597_write(r8a66597, BCLR, ep->fifoctr); + if (req->req.length == 0) { + r8a66597_bset(r8a66597, BVAL, ep->fifoctr); + pipe_start(r8a66597, 0); + transfer_complete(ep, req, 0); + } else { + r8a66597_write(r8a66597, ~BEMP0, BEMPSTS); + irq_ep0_write(ep, req); + } +} + +static void disable_fifosel(struct r8a66597 *r8a66597, u16 pipenum, + u16 fifosel) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, fifosel) & CURPIPE; + if (tmp == pipenum) + r8a66597_change_curpipe(r8a66597, 0, 0, fifosel); +} + +static void change_bfre_mode(struct r8a66597 *r8a66597, u16 pipenum, + int enable) +{ + struct r8a66597_ep *ep = r8a66597->pipenum2ep[pipenum]; + u16 tmp, toggle; + + /* check current BFRE bit */ + r8a66597_write(r8a66597, pipenum, PIPESEL); + tmp = r8a66597_read(r8a66597, PIPECFG) & R8A66597_BFRE; + if ((enable && tmp) || (!enable && !tmp)) + return; + + /* change BFRE bit */ + pipe_stop(r8a66597, pipenum); + disable_fifosel(r8a66597, pipenum, CFIFOSEL); + disable_fifosel(r8a66597, pipenum, D0FIFOSEL); + disable_fifosel(r8a66597, pipenum, D1FIFOSEL); + + toggle = save_usb_toggle(r8a66597, pipenum); + + r8a66597_write(r8a66597, pipenum, PIPESEL); + if (enable) + r8a66597_bset(r8a66597, R8A66597_BFRE, PIPECFG); + else + r8a66597_bclr(r8a66597, R8A66597_BFRE, PIPECFG); + + /* initialize for internal BFRE flag */ + r8a66597_bset(r8a66597, ACLRM, ep->pipectr); + r8a66597_bclr(r8a66597, ACLRM, ep->pipectr); + + restore_usb_toggle(r8a66597, pipenum, toggle); +} + +static int sudmac_alloc_channel(struct r8a66597 *r8a66597, + struct r8a66597_ep *ep, + struct r8a66597_request *req) +{ + struct r8a66597_dma *dma; + + if (!r8a66597_is_sudmac(r8a66597)) + return -ENODEV; + + /* Check transfer type */ + if (!is_bulk_pipe(ep->pipenum)) + return -EIO; + + if (r8a66597->dma.used) + return -EBUSY; + + /* set SUDMAC parameters */ + dma = &r8a66597->dma; + dma->used = 1; + if (ep->ep.desc->bEndpointAddress & USB_DIR_IN) { + dma->dir = 1; + } else { + dma->dir = 0; + change_bfre_mode(r8a66597, ep->pipenum, 1); + } + + /* set r8a66597_ep paramters */ + ep->use_dma = 1; + ep->dma = dma; + ep->fifoaddr = D0FIFO; + ep->fifosel = D0FIFOSEL; + ep->fifoctr = D0FIFOCTR; + + /* dma mapping */ + return usb_gadget_map_request(&r8a66597->gadget, &req->req, dma->dir); +} + +static void sudmac_free_channel(struct r8a66597 *r8a66597, + struct r8a66597_ep *ep, + struct r8a66597_request *req) +{ + if (!r8a66597_is_sudmac(r8a66597)) + return; + + usb_gadget_unmap_request(&r8a66597->gadget, &req->req, ep->dma->dir); + + r8a66597_bclr(r8a66597, DREQE, ep->fifosel); + r8a66597_change_curpipe(r8a66597, 0, 0, ep->fifosel); + + ep->dma->used = 0; + ep->use_dma = 0; + ep->fifoaddr = CFIFO; + ep->fifosel = CFIFOSEL; + ep->fifoctr = CFIFOCTR; +} + +static void sudmac_start(struct r8a66597 *r8a66597, struct r8a66597_ep *ep, + struct r8a66597_request *req) +{ + BUG_ON(req->req.length == 0); + + r8a66597_sudmac_write(r8a66597, LBA_WAIT, CH0CFG); + r8a66597_sudmac_write(r8a66597, req->req.dma, CH0BA); + r8a66597_sudmac_write(r8a66597, req->req.length, CH0BBC); + r8a66597_sudmac_write(r8a66597, CH0ENDE, DINTCTRL); + + r8a66597_sudmac_write(r8a66597, DEN, CH0DEN); +} + +static void start_packet_write(struct r8a66597_ep *ep, + struct r8a66597_request *req) +{ + struct r8a66597 *r8a66597 = ep->r8a66597; + u16 tmp; + + pipe_change(r8a66597, ep->pipenum); + disable_irq_empty(r8a66597, ep->pipenum); + pipe_start(r8a66597, ep->pipenum); + + if (req->req.length == 0) { + transfer_complete(ep, req, 0); + } else { + r8a66597_write(r8a66597, ~(1 << ep->pipenum), BRDYSTS); + if (sudmac_alloc_channel(r8a66597, ep, req) < 0) { + /* PIO mode */ + pipe_change(r8a66597, ep->pipenum); + disable_irq_empty(r8a66597, ep->pipenum); + pipe_start(r8a66597, ep->pipenum); + tmp = r8a66597_read(r8a66597, ep->fifoctr); + if (unlikely((tmp & FRDY) == 0)) + pipe_irq_enable(r8a66597, ep->pipenum); + else + irq_packet_write(ep, req); + } else { + /* DMA mode */ + pipe_change(r8a66597, ep->pipenum); + disable_irq_nrdy(r8a66597, ep->pipenum); + pipe_start(r8a66597, ep->pipenum); + enable_irq_nrdy(r8a66597, ep->pipenum); + sudmac_start(r8a66597, ep, req); + } + } +} + +static void start_packet_read(struct r8a66597_ep *ep, + struct r8a66597_request *req) +{ + struct r8a66597 *r8a66597 = ep->r8a66597; + u16 pipenum = ep->pipenum; + + if (ep->pipenum == 0) { + r8a66597_mdfy(r8a66597, 0, (ISEL | CURPIPE), CFIFOSEL); + r8a66597_write(r8a66597, BCLR, ep->fifoctr); + pipe_start(r8a66597, pipenum); + pipe_irq_enable(r8a66597, pipenum); + } else { + pipe_stop(r8a66597, pipenum); + if (ep->pipetre) { + enable_irq_nrdy(r8a66597, pipenum); + r8a66597_write(r8a66597, TRCLR, ep->pipetre); + r8a66597_write(r8a66597, + DIV_ROUND_UP(req->req.length, ep->ep.maxpacket), + ep->pipetrn); + r8a66597_bset(r8a66597, TRENB, ep->pipetre); + } + + if (sudmac_alloc_channel(r8a66597, ep, req) < 0) { + /* PIO mode */ + change_bfre_mode(r8a66597, ep->pipenum, 0); + pipe_start(r8a66597, pipenum); /* trigger once */ + pipe_irq_enable(r8a66597, pipenum); + } else { + pipe_change(r8a66597, pipenum); + sudmac_start(r8a66597, ep, req); + pipe_start(r8a66597, pipenum); /* trigger once */ + } + } +} + +static void start_packet(struct r8a66597_ep *ep, struct r8a66597_request *req) +{ + if (ep->ep.desc->bEndpointAddress & USB_DIR_IN) + start_packet_write(ep, req); + else + start_packet_read(ep, req); +} + +static void start_ep0(struct r8a66597_ep *ep, struct r8a66597_request *req) +{ + u16 ctsq; + + ctsq = r8a66597_read(ep->r8a66597, INTSTS0) & CTSQ; + + switch (ctsq) { + case CS_RDDS: + start_ep0_write(ep, req); + break; + case CS_WRDS: + start_packet_read(ep, req); + break; + + case CS_WRND: + control_end(ep->r8a66597, 0); + break; + default: + dev_err(r8a66597_to_dev(ep->r8a66597), + "start_ep0: unexpect ctsq(%x)\n", ctsq); + break; + } +} + +static void init_controller(struct r8a66597 *r8a66597) +{ + u16 vif = r8a66597->pdata->vif ? LDRV : 0; + u16 irq_sense = r8a66597->irq_sense_low ? INTL : 0; + u16 endian = r8a66597->pdata->endian ? BIGEND : 0; + + if (r8a66597->pdata->on_chip) { + if (r8a66597->pdata->buswait) + r8a66597_write(r8a66597, r8a66597->pdata->buswait, + SYSCFG1); + else + r8a66597_write(r8a66597, 0x0f, SYSCFG1); + r8a66597_bset(r8a66597, HSE, SYSCFG0); + + r8a66597_bclr(r8a66597, USBE, SYSCFG0); + r8a66597_bclr(r8a66597, DPRPU, SYSCFG0); + r8a66597_bset(r8a66597, USBE, SYSCFG0); + + r8a66597_bset(r8a66597, SCKE, SYSCFG0); + + r8a66597_bset(r8a66597, irq_sense, INTENB1); + r8a66597_write(r8a66597, BURST | CPU_ADR_RD_WR, + DMA0CFG); + } else { + r8a66597_bset(r8a66597, vif | endian, PINCFG); + r8a66597_bset(r8a66597, HSE, SYSCFG0); /* High spd */ + r8a66597_mdfy(r8a66597, get_xtal_from_pdata(r8a66597->pdata), + XTAL, SYSCFG0); + + r8a66597_bclr(r8a66597, USBE, SYSCFG0); + r8a66597_bclr(r8a66597, DPRPU, SYSCFG0); + r8a66597_bset(r8a66597, USBE, SYSCFG0); + + r8a66597_bset(r8a66597, XCKE, SYSCFG0); + + msleep(3); + + r8a66597_bset(r8a66597, PLLC, SYSCFG0); + + msleep(1); + + r8a66597_bset(r8a66597, SCKE, SYSCFG0); + + r8a66597_bset(r8a66597, irq_sense, INTENB1); + r8a66597_write(r8a66597, BURST | CPU_ADR_RD_WR, + DMA0CFG); + } +} + +static void disable_controller(struct r8a66597 *r8a66597) +{ + if (r8a66597->pdata->on_chip) { + r8a66597_bset(r8a66597, SCKE, SYSCFG0); + r8a66597_bclr(r8a66597, UTST, TESTMODE); + + /* disable interrupts */ + r8a66597_write(r8a66597, 0, INTENB0); + r8a66597_write(r8a66597, 0, INTENB1); + r8a66597_write(r8a66597, 0, BRDYENB); + r8a66597_write(r8a66597, 0, BEMPENB); + r8a66597_write(r8a66597, 0, NRDYENB); + + /* clear status */ + r8a66597_write(r8a66597, 0, BRDYSTS); + r8a66597_write(r8a66597, 0, NRDYSTS); + r8a66597_write(r8a66597, 0, BEMPSTS); + + r8a66597_bclr(r8a66597, USBE, SYSCFG0); + r8a66597_bclr(r8a66597, SCKE, SYSCFG0); + + } else { + r8a66597_bclr(r8a66597, UTST, TESTMODE); + r8a66597_bclr(r8a66597, SCKE, SYSCFG0); + udelay(1); + r8a66597_bclr(r8a66597, PLLC, SYSCFG0); + udelay(1); + udelay(1); + r8a66597_bclr(r8a66597, XCKE, SYSCFG0); + } +} + +static void r8a66597_start_xclock(struct r8a66597 *r8a66597) +{ + u16 tmp; + + if (!r8a66597->pdata->on_chip) { + tmp = r8a66597_read(r8a66597, SYSCFG0); + if (!(tmp & XCKE)) + r8a66597_bset(r8a66597, XCKE, SYSCFG0); + } +} + +static struct r8a66597_request *get_request_from_ep(struct r8a66597_ep *ep) +{ + return list_entry(ep->queue.next, struct r8a66597_request, queue); +} + +/*-------------------------------------------------------------------------*/ +static void transfer_complete(struct r8a66597_ep *ep, + struct r8a66597_request *req, int status) +__releases(r8a66597->lock) +__acquires(r8a66597->lock) +{ + int restart = 0; + + if (unlikely(ep->pipenum == 0)) { + if (ep->internal_ccpl) { + ep->internal_ccpl = 0; + return; + } + } + + list_del_init(&req->queue); + if (ep->r8a66597->gadget.speed == USB_SPEED_UNKNOWN) + req->req.status = -ESHUTDOWN; + else + req->req.status = status; + + if (!list_empty(&ep->queue)) + restart = 1; + + if (ep->use_dma) + sudmac_free_channel(ep->r8a66597, ep, req); + + spin_unlock(&ep->r8a66597->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->r8a66597->lock); + + if (restart) { + req = get_request_from_ep(ep); + if (ep->ep.desc) + start_packet(ep, req); + } +} + +static void irq_ep0_write(struct r8a66597_ep *ep, struct r8a66597_request *req) +{ + int i; + u16 tmp; + unsigned bufsize; + size_t size; + void *buf; + u16 pipenum = ep->pipenum; + struct r8a66597 *r8a66597 = ep->r8a66597; + + pipe_change(r8a66597, pipenum); + r8a66597_bset(r8a66597, ISEL, ep->fifosel); + + i = 0; + do { + tmp = r8a66597_read(r8a66597, ep->fifoctr); + if (i++ > 100000) { + dev_err(r8a66597_to_dev(r8a66597), + "pipe0 is busy. maybe cpu i/o bus " + "conflict. please power off this controller."); + return; + } + ndelay(1); + } while ((tmp & FRDY) == 0); + + /* prepare parameters */ + bufsize = get_buffer_size(r8a66597, pipenum); + buf = req->req.buf + req->req.actual; + size = min(bufsize, req->req.length - req->req.actual); + + /* write fifo */ + if (req->req.buf) { + if (size > 0) + r8a66597_write_fifo(r8a66597, ep, buf, size); + if ((size == 0) || ((size % ep->ep.maxpacket) != 0)) + r8a66597_bset(r8a66597, BVAL, ep->fifoctr); + } + + /* update parameters */ + req->req.actual += size; + + /* check transfer finish */ + if ((!req->req.zero && (req->req.actual == req->req.length)) + || (size % ep->ep.maxpacket) + || (size == 0)) { + disable_irq_ready(r8a66597, pipenum); + disable_irq_empty(r8a66597, pipenum); + } else { + disable_irq_ready(r8a66597, pipenum); + enable_irq_empty(r8a66597, pipenum); + } + pipe_start(r8a66597, pipenum); +} + +static void irq_packet_write(struct r8a66597_ep *ep, + struct r8a66597_request *req) +{ + u16 tmp; + unsigned bufsize; + size_t size; + void *buf; + u16 pipenum = ep->pipenum; + struct r8a66597 *r8a66597 = ep->r8a66597; + + pipe_change(r8a66597, pipenum); + tmp = r8a66597_read(r8a66597, ep->fifoctr); + if (unlikely((tmp & FRDY) == 0)) { + pipe_stop(r8a66597, pipenum); + pipe_irq_disable(r8a66597, pipenum); + dev_err(r8a66597_to_dev(r8a66597), + "write fifo not ready. pipnum=%d\n", pipenum); + return; + } + + /* prepare parameters */ + bufsize = get_buffer_size(r8a66597, pipenum); + buf = req->req.buf + req->req.actual; + size = min(bufsize, req->req.length - req->req.actual); + + /* write fifo */ + if (req->req.buf) { + r8a66597_write_fifo(r8a66597, ep, buf, size); + if ((size == 0) + || ((size % ep->ep.maxpacket) != 0) + || ((bufsize != ep->ep.maxpacket) + && (bufsize > size))) + r8a66597_bset(r8a66597, BVAL, ep->fifoctr); + } + + /* update parameters */ + req->req.actual += size; + /* check transfer finish */ + if ((!req->req.zero && (req->req.actual == req->req.length)) + || (size % ep->ep.maxpacket) + || (size == 0)) { + disable_irq_ready(r8a66597, pipenum); + enable_irq_empty(r8a66597, pipenum); + } else { + disable_irq_empty(r8a66597, pipenum); + pipe_irq_enable(r8a66597, pipenum); + } +} + +static void irq_packet_read(struct r8a66597_ep *ep, + struct r8a66597_request *req) +{ + u16 tmp; + int rcv_len, bufsize, req_len; + int size; + void *buf; + u16 pipenum = ep->pipenum; + struct r8a66597 *r8a66597 = ep->r8a66597; + int finish = 0; + + pipe_change(r8a66597, pipenum); + tmp = r8a66597_read(r8a66597, ep->fifoctr); + if (unlikely((tmp & FRDY) == 0)) { + req->req.status = -EPIPE; + pipe_stop(r8a66597, pipenum); + pipe_irq_disable(r8a66597, pipenum); + dev_err(r8a66597_to_dev(r8a66597), "read fifo not ready"); + return; + } + + /* prepare parameters */ + rcv_len = tmp & DTLN; + bufsize = get_buffer_size(r8a66597, pipenum); + + buf = req->req.buf + req->req.actual; + req_len = req->req.length - req->req.actual; + if (rcv_len < bufsize) + size = min(rcv_len, req_len); + else + size = min(bufsize, req_len); + + /* update parameters */ + req->req.actual += size; + + /* check transfer finish */ + if ((!req->req.zero && (req->req.actual == req->req.length)) + || (size % ep->ep.maxpacket) + || (size == 0)) { + pipe_stop(r8a66597, pipenum); + pipe_irq_disable(r8a66597, pipenum); + finish = 1; + } + + /* read fifo */ + if (req->req.buf) { + if (size == 0) + r8a66597_write(r8a66597, BCLR, ep->fifoctr); + else + r8a66597_read_fifo(r8a66597, ep->fifoaddr, buf, size); + + } + + if ((ep->pipenum != 0) && finish) + transfer_complete(ep, req, 0); +} + +static void irq_pipe_ready(struct r8a66597 *r8a66597, u16 status, u16 enb) +{ + u16 check; + u16 pipenum; + struct r8a66597_ep *ep; + struct r8a66597_request *req; + + if ((status & BRDY0) && (enb & BRDY0)) { + r8a66597_write(r8a66597, ~BRDY0, BRDYSTS); + r8a66597_mdfy(r8a66597, 0, CURPIPE, CFIFOSEL); + + ep = &r8a66597->ep[0]; + req = get_request_from_ep(ep); + irq_packet_read(ep, req); + } else { + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if ((status & check) && (enb & check)) { + r8a66597_write(r8a66597, ~check, BRDYSTS); + ep = r8a66597->pipenum2ep[pipenum]; + req = get_request_from_ep(ep); + if (ep->ep.desc->bEndpointAddress & USB_DIR_IN) + irq_packet_write(ep, req); + else + irq_packet_read(ep, req); + } + } + } +} + +static void irq_pipe_empty(struct r8a66597 *r8a66597, u16 status, u16 enb) +{ + u16 tmp; + u16 check; + u16 pipenum; + struct r8a66597_ep *ep; + struct r8a66597_request *req; + + if ((status & BEMP0) && (enb & BEMP0)) { + r8a66597_write(r8a66597, ~BEMP0, BEMPSTS); + + ep = &r8a66597->ep[0]; + req = get_request_from_ep(ep); + irq_ep0_write(ep, req); + } else { + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if ((status & check) && (enb & check)) { + r8a66597_write(r8a66597, ~check, BEMPSTS); + tmp = control_reg_get(r8a66597, pipenum); + if ((tmp & INBUFM) == 0) { + disable_irq_empty(r8a66597, pipenum); + pipe_irq_disable(r8a66597, pipenum); + pipe_stop(r8a66597, pipenum); + ep = r8a66597->pipenum2ep[pipenum]; + req = get_request_from_ep(ep); + if (!list_empty(&ep->queue)) + transfer_complete(ep, req, 0); + } + } + } + } +} + +static void get_status(struct r8a66597 *r8a66597, struct usb_ctrlrequest *ctrl) +__releases(r8a66597->lock) +__acquires(r8a66597->lock) +{ + struct r8a66597_ep *ep; + u16 pid; + u16 status = 0; + u16 w_index = le16_to_cpu(ctrl->wIndex); + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + status = r8a66597->device_status; + break; + case USB_RECIP_INTERFACE: + status = 0; + break; + case USB_RECIP_ENDPOINT: + ep = r8a66597->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK]; + pid = control_reg_get_pid(r8a66597, ep->pipenum); + if (pid == PID_STALL) + status = 1 << USB_ENDPOINT_HALT; + else + status = 0; + break; + default: + pipe_stall(r8a66597, 0); + return; /* exit */ + } + + r8a66597->ep0_data = cpu_to_le16(status); + r8a66597->ep0_req->buf = &r8a66597->ep0_data; + r8a66597->ep0_req->length = 2; + /* AV: what happens if we get called again before that gets through? */ + spin_unlock(&r8a66597->lock); + r8a66597_queue(r8a66597->gadget.ep0, r8a66597->ep0_req, GFP_KERNEL); + spin_lock(&r8a66597->lock); +} + +static void clear_feature(struct r8a66597 *r8a66597, + struct usb_ctrlrequest *ctrl) +{ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + control_end(r8a66597, 1); + break; + case USB_RECIP_INTERFACE: + control_end(r8a66597, 1); + break; + case USB_RECIP_ENDPOINT: { + struct r8a66597_ep *ep; + struct r8a66597_request *req; + u16 w_index = le16_to_cpu(ctrl->wIndex); + + ep = r8a66597->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK]; + if (!ep->wedge) { + pipe_stop(r8a66597, ep->pipenum); + control_reg_sqclr(r8a66597, ep->pipenum); + spin_unlock(&r8a66597->lock); + usb_ep_clear_halt(&ep->ep); + spin_lock(&r8a66597->lock); + } + + control_end(r8a66597, 1); + + req = get_request_from_ep(ep); + if (ep->busy) { + ep->busy = 0; + if (list_empty(&ep->queue)) + break; + start_packet(ep, req); + } else if (!list_empty(&ep->queue)) + pipe_start(r8a66597, ep->pipenum); + } + break; + default: + pipe_stall(r8a66597, 0); + break; + } +} + +static void set_feature(struct r8a66597 *r8a66597, struct usb_ctrlrequest *ctrl) +{ + u16 tmp; + int timeout = 3000; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (le16_to_cpu(ctrl->wValue)) { + case USB_DEVICE_TEST_MODE: + control_end(r8a66597, 1); + /* Wait for the completion of status stage */ + do { + tmp = r8a66597_read(r8a66597, INTSTS0) & CTSQ; + udelay(1); + } while (tmp != CS_IDST || timeout-- > 0); + + if (tmp == CS_IDST) + r8a66597_bset(r8a66597, + le16_to_cpu(ctrl->wIndex >> 8), + TESTMODE); + break; + default: + pipe_stall(r8a66597, 0); + break; + } + break; + case USB_RECIP_INTERFACE: + control_end(r8a66597, 1); + break; + case USB_RECIP_ENDPOINT: { + struct r8a66597_ep *ep; + u16 w_index = le16_to_cpu(ctrl->wIndex); + + ep = r8a66597->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK]; + pipe_stall(r8a66597, ep->pipenum); + + control_end(r8a66597, 1); + } + break; + default: + pipe_stall(r8a66597, 0); + break; + } +} + +/* if return value is true, call class driver's setup() */ +static int setup_packet(struct r8a66597 *r8a66597, struct usb_ctrlrequest *ctrl) +{ + u16 *p = (u16 *)ctrl; + unsigned long offset = USBREQ; + int i, ret = 0; + + /* read fifo */ + r8a66597_write(r8a66597, ~VALID, INTSTS0); + + for (i = 0; i < 4; i++) + p[i] = r8a66597_read(r8a66597, offset + i*2); + + /* check request */ + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + get_status(r8a66597, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + clear_feature(r8a66597, ctrl); + break; + case USB_REQ_SET_FEATURE: + set_feature(r8a66597, ctrl); + break; + default: + ret = 1; + break; + } + } else + ret = 1; + return ret; +} + +static void r8a66597_update_usb_speed(struct r8a66597 *r8a66597) +{ + u16 speed = get_usb_speed(r8a66597); + + switch (speed) { + case HSMODE: + r8a66597->gadget.speed = USB_SPEED_HIGH; + break; + case FSMODE: + r8a66597->gadget.speed = USB_SPEED_FULL; + break; + default: + r8a66597->gadget.speed = USB_SPEED_UNKNOWN; + dev_err(r8a66597_to_dev(r8a66597), "USB speed unknown\n"); + } +} + +static void irq_device_state(struct r8a66597 *r8a66597) +{ + u16 dvsq; + + dvsq = r8a66597_read(r8a66597, INTSTS0) & DVSQ; + r8a66597_write(r8a66597, ~DVST, INTSTS0); + + if (dvsq == DS_DFLT) { + /* bus reset */ + spin_unlock(&r8a66597->lock); + r8a66597->driver->disconnect(&r8a66597->gadget); + spin_lock(&r8a66597->lock); + r8a66597_update_usb_speed(r8a66597); + } + if (r8a66597->old_dvsq == DS_CNFG && dvsq != DS_CNFG) + r8a66597_update_usb_speed(r8a66597); + if ((dvsq == DS_CNFG || dvsq == DS_ADDS) + && r8a66597->gadget.speed == USB_SPEED_UNKNOWN) + r8a66597_update_usb_speed(r8a66597); + + r8a66597->old_dvsq = dvsq; +} + +static void irq_control_stage(struct r8a66597 *r8a66597) +__releases(r8a66597->lock) +__acquires(r8a66597->lock) +{ + struct usb_ctrlrequest ctrl; + u16 ctsq; + + ctsq = r8a66597_read(r8a66597, INTSTS0) & CTSQ; + r8a66597_write(r8a66597, ~CTRT, INTSTS0); + + switch (ctsq) { + case CS_IDST: { + struct r8a66597_ep *ep; + struct r8a66597_request *req; + ep = &r8a66597->ep[0]; + req = get_request_from_ep(ep); + transfer_complete(ep, req, 0); + } + break; + + case CS_RDDS: + case CS_WRDS: + case CS_WRND: + if (setup_packet(r8a66597, &ctrl)) { + spin_unlock(&r8a66597->lock); + if (r8a66597->driver->setup(&r8a66597->gadget, &ctrl) + < 0) + pipe_stall(r8a66597, 0); + spin_lock(&r8a66597->lock); + } + break; + case CS_RDSS: + case CS_WRSS: + control_end(r8a66597, 0); + break; + default: + dev_err(r8a66597_to_dev(r8a66597), + "ctrl_stage: unexpect ctsq(%x)\n", ctsq); + break; + } +} + +static void sudmac_finish(struct r8a66597 *r8a66597, struct r8a66597_ep *ep) +{ + u16 pipenum; + struct r8a66597_request *req; + u32 len; + int i = 0; + + pipenum = ep->pipenum; + pipe_change(r8a66597, pipenum); + + while (!(r8a66597_read(r8a66597, ep->fifoctr) & FRDY)) { + udelay(1); + if (unlikely(i++ >= 10000)) { /* timeout = 10 msec */ + dev_err(r8a66597_to_dev(r8a66597), + "%s: FRDY was not set (%d)\n", + __func__, pipenum); + return; + } + } + + r8a66597_bset(r8a66597, BCLR, ep->fifoctr); + req = get_request_from_ep(ep); + + /* prepare parameters */ + len = r8a66597_sudmac_read(r8a66597, CH0CBC); + req->req.actual += len; + + /* clear */ + r8a66597_sudmac_write(r8a66597, CH0STCLR, DSTSCLR); + + /* check transfer finish */ + if ((!req->req.zero && (req->req.actual == req->req.length)) + || (len % ep->ep.maxpacket)) { + if (ep->dma->dir) { + disable_irq_ready(r8a66597, pipenum); + enable_irq_empty(r8a66597, pipenum); + } else { + /* Clear the interrupt flag for next transfer */ + r8a66597_write(r8a66597, ~(1 << pipenum), BRDYSTS); + transfer_complete(ep, req, 0); + } + } +} + +static void r8a66597_sudmac_irq(struct r8a66597 *r8a66597) +{ + u32 irqsts; + struct r8a66597_ep *ep; + u16 pipenum; + + irqsts = r8a66597_sudmac_read(r8a66597, DINTSTS); + if (irqsts & CH0ENDS) { + r8a66597_sudmac_write(r8a66597, CH0ENDC, DINTSTSCLR); + pipenum = (r8a66597_read(r8a66597, D0FIFOSEL) & CURPIPE); + ep = r8a66597->pipenum2ep[pipenum]; + sudmac_finish(r8a66597, ep); + } +} + +static irqreturn_t r8a66597_irq(int irq, void *_r8a66597) +{ + struct r8a66597 *r8a66597 = _r8a66597; + u16 intsts0; + u16 intenb0; + u16 brdysts, nrdysts, bempsts; + u16 brdyenb, nrdyenb, bempenb; + u16 savepipe; + u16 mask0; + + if (r8a66597_is_sudmac(r8a66597)) + r8a66597_sudmac_irq(r8a66597); + + spin_lock(&r8a66597->lock); + + intsts0 = r8a66597_read(r8a66597, INTSTS0); + intenb0 = r8a66597_read(r8a66597, INTENB0); + + savepipe = r8a66597_read(r8a66597, CFIFOSEL); + + mask0 = intsts0 & intenb0; + if (mask0) { + brdysts = r8a66597_read(r8a66597, BRDYSTS); + nrdysts = r8a66597_read(r8a66597, NRDYSTS); + bempsts = r8a66597_read(r8a66597, BEMPSTS); + brdyenb = r8a66597_read(r8a66597, BRDYENB); + nrdyenb = r8a66597_read(r8a66597, NRDYENB); + bempenb = r8a66597_read(r8a66597, BEMPENB); + + if (mask0 & VBINT) { + r8a66597_write(r8a66597, 0xffff & ~VBINT, + INTSTS0); + r8a66597_start_xclock(r8a66597); + + /* start vbus sampling */ + r8a66597->old_vbus = r8a66597_read(r8a66597, INTSTS0) + & VBSTS; + r8a66597->scount = R8A66597_MAX_SAMPLING; + + mod_timer(&r8a66597->timer, + jiffies + msecs_to_jiffies(50)); + } + if (intsts0 & DVSQ) + irq_device_state(r8a66597); + + if ((intsts0 & BRDY) && (intenb0 & BRDYE) + && (brdysts & brdyenb)) + irq_pipe_ready(r8a66597, brdysts, brdyenb); + if ((intsts0 & BEMP) && (intenb0 & BEMPE) + && (bempsts & bempenb)) + irq_pipe_empty(r8a66597, bempsts, bempenb); + + if (intsts0 & CTRT) + irq_control_stage(r8a66597); + } + + r8a66597_write(r8a66597, savepipe, CFIFOSEL); + + spin_unlock(&r8a66597->lock); + return IRQ_HANDLED; +} + +static void r8a66597_timer(unsigned long _r8a66597) +{ + struct r8a66597 *r8a66597 = (struct r8a66597 *)_r8a66597; + unsigned long flags; + u16 tmp; + + spin_lock_irqsave(&r8a66597->lock, flags); + tmp = r8a66597_read(r8a66597, SYSCFG0); + if (r8a66597->scount > 0) { + tmp = r8a66597_read(r8a66597, INTSTS0) & VBSTS; + if (tmp == r8a66597->old_vbus) { + r8a66597->scount--; + if (r8a66597->scount == 0) { + if (tmp == VBSTS) + r8a66597_usb_connect(r8a66597); + else + r8a66597_usb_disconnect(r8a66597); + } else { + mod_timer(&r8a66597->timer, + jiffies + msecs_to_jiffies(50)); + } + } else { + r8a66597->scount = R8A66597_MAX_SAMPLING; + r8a66597->old_vbus = tmp; + mod_timer(&r8a66597->timer, + jiffies + msecs_to_jiffies(50)); + } + } + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +/*-------------------------------------------------------------------------*/ +static int r8a66597_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct r8a66597_ep *ep; + + ep = container_of(_ep, struct r8a66597_ep, ep); + return alloc_pipe_config(ep, desc); +} + +static int r8a66597_disable(struct usb_ep *_ep) +{ + struct r8a66597_ep *ep; + struct r8a66597_request *req; + unsigned long flags; + + ep = container_of(_ep, struct r8a66597_ep, ep); + BUG_ON(!ep); + + while (!list_empty(&ep->queue)) { + req = get_request_from_ep(ep); + spin_lock_irqsave(&ep->r8a66597->lock, flags); + transfer_complete(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->r8a66597->lock, flags); + } + + pipe_irq_disable(ep->r8a66597, ep->pipenum); + return free_pipe_config(ep); +} + +static struct usb_request *r8a66597_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct r8a66597_request *req; + + req = kzalloc(sizeof(struct r8a66597_request), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void r8a66597_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct r8a66597_request *req; + + req = container_of(_req, struct r8a66597_request, req); + kfree(req); +} + +static int r8a66597_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct r8a66597_ep *ep; + struct r8a66597_request *req; + unsigned long flags; + int request = 0; + + ep = container_of(_ep, struct r8a66597_ep, ep); + req = container_of(_req, struct r8a66597_request, req); + + if (ep->r8a66597->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&ep->r8a66597->lock, flags); + + if (list_empty(&ep->queue)) + request = 1; + + list_add_tail(&req->queue, &ep->queue); + req->req.actual = 0; + req->req.status = -EINPROGRESS; + + if (ep->ep.desc == NULL) /* control */ + start_ep0(ep, req); + else { + if (request && !ep->busy) + start_packet(ep, req); + } + + spin_unlock_irqrestore(&ep->r8a66597->lock, flags); + + return 0; +} + +static int r8a66597_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct r8a66597_ep *ep; + struct r8a66597_request *req; + unsigned long flags; + + ep = container_of(_ep, struct r8a66597_ep, ep); + req = container_of(_req, struct r8a66597_request, req); + + spin_lock_irqsave(&ep->r8a66597->lock, flags); + if (!list_empty(&ep->queue)) + transfer_complete(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->r8a66597->lock, flags); + + return 0; +} + +static int r8a66597_set_halt(struct usb_ep *_ep, int value) +{ + struct r8a66597_ep *ep; + struct r8a66597_request *req; + unsigned long flags; + int ret = 0; + + ep = container_of(_ep, struct r8a66597_ep, ep); + req = get_request_from_ep(ep); + + spin_lock_irqsave(&ep->r8a66597->lock, flags); + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + goto out; + } + if (value) { + ep->busy = 1; + pipe_stall(ep->r8a66597, ep->pipenum); + } else { + ep->busy = 0; + ep->wedge = 0; + pipe_stop(ep->r8a66597, ep->pipenum); + } + +out: + spin_unlock_irqrestore(&ep->r8a66597->lock, flags); + return ret; +} + +static int r8a66597_set_wedge(struct usb_ep *_ep) +{ + struct r8a66597_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct r8a66597_ep, ep); + + if (!ep || !ep->ep.desc) + return -EINVAL; + + spin_lock_irqsave(&ep->r8a66597->lock, flags); + ep->wedge = 1; + spin_unlock_irqrestore(&ep->r8a66597->lock, flags); + + return usb_ep_set_halt(_ep); +} + +static void r8a66597_fifo_flush(struct usb_ep *_ep) +{ + struct r8a66597_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct r8a66597_ep, ep); + spin_lock_irqsave(&ep->r8a66597->lock, flags); + if (list_empty(&ep->queue) && !ep->busy) { + pipe_stop(ep->r8a66597, ep->pipenum); + r8a66597_bclr(ep->r8a66597, BCLR, ep->fifoctr); + r8a66597_write(ep->r8a66597, ACLRM, ep->pipectr); + r8a66597_write(ep->r8a66597, 0, ep->pipectr); + } + spin_unlock_irqrestore(&ep->r8a66597->lock, flags); +} + +static struct usb_ep_ops r8a66597_ep_ops = { + .enable = r8a66597_enable, + .disable = r8a66597_disable, + + .alloc_request = r8a66597_alloc_request, + .free_request = r8a66597_free_request, + + .queue = r8a66597_queue, + .dequeue = r8a66597_dequeue, + + .set_halt = r8a66597_set_halt, + .set_wedge = r8a66597_set_wedge, + .fifo_flush = r8a66597_fifo_flush, +}; + +/*-------------------------------------------------------------------------*/ +static int r8a66597_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct r8a66597 *r8a66597 = gadget_to_r8a66597(gadget); + + if (!driver + || driver->max_speed < USB_SPEED_HIGH + || !driver->setup) + return -EINVAL; + if (!r8a66597) + return -ENODEV; + + /* hook up the driver */ + r8a66597->driver = driver; + + init_controller(r8a66597); + r8a66597_bset(r8a66597, VBSE, INTENB0); + if (r8a66597_read(r8a66597, INTSTS0) & VBSTS) { + r8a66597_start_xclock(r8a66597); + /* start vbus sampling */ + r8a66597->old_vbus = r8a66597_read(r8a66597, + INTSTS0) & VBSTS; + r8a66597->scount = R8A66597_MAX_SAMPLING; + mod_timer(&r8a66597->timer, jiffies + msecs_to_jiffies(50)); + } + + return 0; +} + +static int r8a66597_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct r8a66597 *r8a66597 = gadget_to_r8a66597(gadget); + unsigned long flags; + + spin_lock_irqsave(&r8a66597->lock, flags); + r8a66597_bclr(r8a66597, VBSE, INTENB0); + disable_controller(r8a66597); + spin_unlock_irqrestore(&r8a66597->lock, flags); + + r8a66597->driver = NULL; + return 0; +} + +/*-------------------------------------------------------------------------*/ +static int r8a66597_get_frame(struct usb_gadget *_gadget) +{ + struct r8a66597 *r8a66597 = gadget_to_r8a66597(_gadget); + return r8a66597_read(r8a66597, FRMNUM) & 0x03FF; +} + +static int r8a66597_pullup(struct usb_gadget *gadget, int is_on) +{ + struct r8a66597 *r8a66597 = gadget_to_r8a66597(gadget); + unsigned long flags; + + spin_lock_irqsave(&r8a66597->lock, flags); + if (is_on) + r8a66597_bset(r8a66597, DPRPU, SYSCFG0); + else + r8a66597_bclr(r8a66597, DPRPU, SYSCFG0); + spin_unlock_irqrestore(&r8a66597->lock, flags); + + return 0; +} + +static int r8a66597_set_selfpowered(struct usb_gadget *gadget, int is_self) +{ + struct r8a66597 *r8a66597 = gadget_to_r8a66597(gadget); + + if (is_self) + r8a66597->device_status |= 1 << USB_DEVICE_SELF_POWERED; + else + r8a66597->device_status &= ~(1 << USB_DEVICE_SELF_POWERED); + + return 0; +} + +static const struct usb_gadget_ops r8a66597_gadget_ops = { + .get_frame = r8a66597_get_frame, + .udc_start = r8a66597_start, + .udc_stop = r8a66597_stop, + .pullup = r8a66597_pullup, + .set_selfpowered = r8a66597_set_selfpowered, +}; + +static int __exit r8a66597_remove(struct platform_device *pdev) +{ + struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev); + + usb_del_gadget_udc(&r8a66597->gadget); + del_timer_sync(&r8a66597->timer); + iounmap(r8a66597->reg); + if (r8a66597->pdata->sudmac) + iounmap(r8a66597->sudmac_reg); + free_irq(platform_get_irq(pdev, 0), r8a66597); + r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req); + + if (r8a66597->pdata->on_chip) { + clk_disable(r8a66597->clk); + clk_put(r8a66597->clk); + } + + kfree(r8a66597); + return 0; +} + +static void nop_completion(struct usb_ep *ep, struct usb_request *r) +{ +} + +static int __init r8a66597_sudmac_ioremap(struct r8a66597 *r8a66597, + struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sudmac"); + if (!res) { + dev_err(&pdev->dev, "platform_get_resource error(sudmac).\n"); + return -ENODEV; + } + + r8a66597->sudmac_reg = ioremap(res->start, resource_size(res)); + if (r8a66597->sudmac_reg == NULL) { + dev_err(&pdev->dev, "ioremap error(sudmac).\n"); + return -ENOMEM; + } + + return 0; +} + +static int __init r8a66597_probe(struct platform_device *pdev) +{ + char clk_name[8]; + struct resource *res, *ires; + int irq; + void __iomem *reg = NULL; + struct r8a66597 *r8a66597 = NULL; + int ret = 0; + int i; + unsigned long irq_trigger; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + dev_err(&pdev->dev, "platform_get_resource error.\n"); + goto clean_up; + } + + ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + irq = ires->start; + irq_trigger = ires->flags & IRQF_TRIGGER_MASK; + + if (irq < 0) { + ret = -ENODEV; + dev_err(&pdev->dev, "platform_get_irq error.\n"); + goto clean_up; + } + + reg = ioremap(res->start, resource_size(res)); + if (reg == NULL) { + ret = -ENOMEM; + dev_err(&pdev->dev, "ioremap error.\n"); + goto clean_up; + } + + /* initialize ucd */ + r8a66597 = kzalloc(sizeof(struct r8a66597), GFP_KERNEL); + if (r8a66597 == NULL) { + ret = -ENOMEM; + dev_err(&pdev->dev, "kzalloc error\n"); + goto clean_up; + } + + spin_lock_init(&r8a66597->lock); + dev_set_drvdata(&pdev->dev, r8a66597); + r8a66597->pdata = pdev->dev.platform_data; + r8a66597->irq_sense_low = irq_trigger == IRQF_TRIGGER_LOW; + + r8a66597->gadget.ops = &r8a66597_gadget_ops; + r8a66597->gadget.max_speed = USB_SPEED_HIGH; + r8a66597->gadget.name = udc_name; + + init_timer(&r8a66597->timer); + r8a66597->timer.function = r8a66597_timer; + r8a66597->timer.data = (unsigned long)r8a66597; + r8a66597->reg = reg; + + if (r8a66597->pdata->on_chip) { + snprintf(clk_name, sizeof(clk_name), "usb%d", pdev->id); + r8a66597->clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(r8a66597->clk)) { + dev_err(&pdev->dev, "cannot get clock \"%s\"\n", + clk_name); + ret = PTR_ERR(r8a66597->clk); + goto clean_up; + } + clk_enable(r8a66597->clk); + } + + if (r8a66597->pdata->sudmac) { + ret = r8a66597_sudmac_ioremap(r8a66597, pdev); + if (ret < 0) + goto clean_up2; + } + + disable_controller(r8a66597); /* make sure controller is disabled */ + + ret = request_irq(irq, r8a66597_irq, IRQF_SHARED, + udc_name, r8a66597); + if (ret < 0) { + dev_err(&pdev->dev, "request_irq error (%d)\n", ret); + goto clean_up2; + } + + INIT_LIST_HEAD(&r8a66597->gadget.ep_list); + r8a66597->gadget.ep0 = &r8a66597->ep[0].ep; + INIT_LIST_HEAD(&r8a66597->gadget.ep0->ep_list); + for (i = 0; i < R8A66597_MAX_NUM_PIPE; i++) { + struct r8a66597_ep *ep = &r8a66597->ep[i]; + + if (i != 0) { + INIT_LIST_HEAD(&r8a66597->ep[i].ep.ep_list); + list_add_tail(&r8a66597->ep[i].ep.ep_list, + &r8a66597->gadget.ep_list); + } + ep->r8a66597 = r8a66597; + INIT_LIST_HEAD(&ep->queue); + ep->ep.name = r8a66597_ep_name[i]; + ep->ep.ops = &r8a66597_ep_ops; + ep->ep.maxpacket = 512; + } + r8a66597->ep[0].ep.maxpacket = 64; + r8a66597->ep[0].pipenum = 0; + r8a66597->ep[0].fifoaddr = CFIFO; + r8a66597->ep[0].fifosel = CFIFOSEL; + r8a66597->ep[0].fifoctr = CFIFOCTR; + r8a66597->ep[0].pipectr = get_pipectr_addr(0); + r8a66597->pipenum2ep[0] = &r8a66597->ep[0]; + r8a66597->epaddr2ep[0] = &r8a66597->ep[0]; + + r8a66597->ep0_req = r8a66597_alloc_request(&r8a66597->ep[0].ep, + GFP_KERNEL); + if (r8a66597->ep0_req == NULL) { + ret = -ENOMEM; + goto clean_up3; + } + r8a66597->ep0_req->complete = nop_completion; + + ret = usb_add_gadget_udc(&pdev->dev, &r8a66597->gadget); + if (ret) + goto err_add_udc; + + dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION); + return 0; + +err_add_udc: + r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req); +clean_up3: + free_irq(irq, r8a66597); +clean_up2: + if (r8a66597->pdata->on_chip) { + clk_disable(r8a66597->clk); + clk_put(r8a66597->clk); + } +clean_up: + if (r8a66597) { + if (r8a66597->sudmac_reg) + iounmap(r8a66597->sudmac_reg); + if (r8a66597->ep0_req) + r8a66597_free_request(&r8a66597->ep[0].ep, + r8a66597->ep0_req); + kfree(r8a66597); + } + if (reg) + iounmap(reg); + + return ret; +} + +/*-------------------------------------------------------------------------*/ +static struct platform_driver r8a66597_driver = { + .remove = __exit_p(r8a66597_remove), + .driver = { + .name = (char *) udc_name, + }, +}; + +module_platform_driver_probe(r8a66597_driver, r8a66597_probe); + +MODULE_DESCRIPTION("R8A66597 USB gadget driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_ALIAS("platform:r8a66597_udc"); diff --git a/drivers/usb/gadget/r8a66597-udc.h b/drivers/usb/gadget/r8a66597-udc.h new file mode 100644 index 00000000..45c4b2df --- /dev/null +++ b/drivers/usb/gadget/r8a66597-udc.h @@ -0,0 +1,290 @@ +/* + * R8A66597 UDC + * + * Copyright (C) 2007-2009 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#ifndef __R8A66597_H__ +#define __R8A66597_H__ + +#include +#include + +#define R8A66597_MAX_SAMPLING 10 + +#define R8A66597_MAX_NUM_PIPE 8 +#define R8A66597_MAX_NUM_BULK 3 +#define R8A66597_MAX_NUM_ISOC 2 +#define R8A66597_MAX_NUM_INT 2 + +#define R8A66597_BASE_PIPENUM_BULK 3 +#define R8A66597_BASE_PIPENUM_ISOC 1 +#define R8A66597_BASE_PIPENUM_INT 6 + +#define R8A66597_BASE_BUFNUM 6 +#define R8A66597_MAX_BUFNUM 0x4F + +#define is_bulk_pipe(pipenum) \ + ((pipenum >= R8A66597_BASE_PIPENUM_BULK) && \ + (pipenum < (R8A66597_BASE_PIPENUM_BULK + R8A66597_MAX_NUM_BULK))) +#define is_interrupt_pipe(pipenum) \ + ((pipenum >= R8A66597_BASE_PIPENUM_INT) && \ + (pipenum < (R8A66597_BASE_PIPENUM_INT + R8A66597_MAX_NUM_INT))) +#define is_isoc_pipe(pipenum) \ + ((pipenum >= R8A66597_BASE_PIPENUM_ISOC) && \ + (pipenum < (R8A66597_BASE_PIPENUM_ISOC + R8A66597_MAX_NUM_ISOC))) + +#define r8a66597_is_sudmac(r8a66597) (r8a66597->pdata->sudmac) +struct r8a66597_pipe_info { + u16 pipe; + u16 epnum; + u16 maxpacket; + u16 type; + u16 interval; + u16 dir_in; +}; + +struct r8a66597_request { + struct usb_request req; + struct list_head queue; +}; + +struct r8a66597_ep { + struct usb_ep ep; + struct r8a66597 *r8a66597; + struct r8a66597_dma *dma; + + struct list_head queue; + unsigned busy:1; + unsigned wedge:1; + unsigned internal_ccpl:1; /* use only control */ + + /* this member can able to after r8a66597_enable */ + unsigned use_dma:1; + u16 pipenum; + u16 type; + + /* register address */ + unsigned char fifoaddr; + unsigned char fifosel; + unsigned char fifoctr; + unsigned char pipectr; + unsigned char pipetre; + unsigned char pipetrn; +}; + +struct r8a66597_dma { + unsigned used:1; + unsigned dir:1; /* 1 = IN(write), 0 = OUT(read) */ +}; + +struct r8a66597 { + spinlock_t lock; + void __iomem *reg; + void __iomem *sudmac_reg; + + struct clk *clk; + struct r8a66597_platdata *pdata; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct r8a66597_ep ep[R8A66597_MAX_NUM_PIPE]; + struct r8a66597_ep *pipenum2ep[R8A66597_MAX_NUM_PIPE]; + struct r8a66597_ep *epaddr2ep[16]; + struct r8a66597_dma dma; + + struct timer_list timer; + struct usb_request *ep0_req; /* for internal request */ + u16 ep0_data; /* for internal request */ + u16 old_vbus; + u16 scount; + u16 old_dvsq; + u16 device_status; /* for GET_STATUS */ + + /* pipe config */ + unsigned char bulk; + unsigned char interrupt; + unsigned char isochronous; + unsigned char num_dma; + + unsigned irq_sense_low:1; +}; + +#define gadget_to_r8a66597(_gadget) \ + container_of(_gadget, struct r8a66597, gadget) +#define r8a66597_to_gadget(r8a66597) (&r8a66597->gadget) +#define r8a66597_to_dev(r8a66597) (r8a66597->gadget.dev.parent) + +static inline u16 r8a66597_read(struct r8a66597 *r8a66597, unsigned long offset) +{ + return ioread16(r8a66597->reg + offset); +} + +static inline void r8a66597_read_fifo(struct r8a66597 *r8a66597, + unsigned long offset, + unsigned char *buf, + int len) +{ + void __iomem *fifoaddr = r8a66597->reg + offset; + unsigned int data = 0; + int i; + + if (r8a66597->pdata->on_chip) { + /* 32-bit accesses for on_chip controllers */ + + /* aligned buf case */ + if (len >= 4 && !((unsigned long)buf & 0x03)) { + ioread32_rep(fifoaddr, buf, len / 4); + buf += len & ~0x03; + len &= 0x03; + } + + /* unaligned buf case */ + for (i = 0; i < len; i++) { + if (!(i & 0x03)) + data = ioread32(fifoaddr); + + buf[i] = (data >> ((i & 0x03) * 8)) & 0xff; + } + } else { + /* 16-bit accesses for external controllers */ + + /* aligned buf case */ + if (len >= 2 && !((unsigned long)buf & 0x01)) { + ioread16_rep(fifoaddr, buf, len / 2); + buf += len & ~0x01; + len &= 0x01; + } + + /* unaligned buf case */ + for (i = 0; i < len; i++) { + if (!(i & 0x01)) + data = ioread16(fifoaddr); + + buf[i] = (data >> ((i & 0x01) * 8)) & 0xff; + } + } +} + +static inline void r8a66597_write(struct r8a66597 *r8a66597, u16 val, + unsigned long offset) +{ + iowrite16(val, r8a66597->reg + offset); +} + +static inline void r8a66597_mdfy(struct r8a66597 *r8a66597, + u16 val, u16 pat, unsigned long offset) +{ + u16 tmp; + tmp = r8a66597_read(r8a66597, offset); + tmp = tmp & (~pat); + tmp = tmp | val; + r8a66597_write(r8a66597, tmp, offset); +} + +#define r8a66597_bclr(r8a66597, val, offset) \ + r8a66597_mdfy(r8a66597, 0, val, offset) +#define r8a66597_bset(r8a66597, val, offset) \ + r8a66597_mdfy(r8a66597, val, 0, offset) + +static inline void r8a66597_write_fifo(struct r8a66597 *r8a66597, + struct r8a66597_ep *ep, + unsigned char *buf, + int len) +{ + void __iomem *fifoaddr = r8a66597->reg + ep->fifoaddr; + int adj = 0; + int i; + + if (r8a66597->pdata->on_chip) { + /* 32-bit access only if buf is 32-bit aligned */ + if (len >= 4 && !((unsigned long)buf & 0x03)) { + iowrite32_rep(fifoaddr, buf, len / 4); + buf += len & ~0x03; + len &= 0x03; + } + } else { + /* 16-bit access only if buf is 16-bit aligned */ + if (len >= 2 && !((unsigned long)buf & 0x01)) { + iowrite16_rep(fifoaddr, buf, len / 2); + buf += len & ~0x01; + len &= 0x01; + } + } + + /* adjust fifo address in the little endian case */ + if (!(r8a66597_read(r8a66597, CFIFOSEL) & BIGEND)) { + if (r8a66597->pdata->on_chip) + adj = 0x03; /* 32-bit wide */ + else + adj = 0x01; /* 16-bit wide */ + } + + if (r8a66597->pdata->wr0_shorted_to_wr1) + r8a66597_bclr(r8a66597, MBW_16, ep->fifosel); + for (i = 0; i < len; i++) + iowrite8(buf[i], fifoaddr + adj - (i & adj)); + if (r8a66597->pdata->wr0_shorted_to_wr1) + r8a66597_bclr(r8a66597, MBW_16, ep->fifosel); +} + +static inline u16 get_xtal_from_pdata(struct r8a66597_platdata *pdata) +{ + u16 clock = 0; + + switch (pdata->xtal) { + case R8A66597_PLATDATA_XTAL_12MHZ: + clock = XTAL12; + break; + case R8A66597_PLATDATA_XTAL_24MHZ: + clock = XTAL24; + break; + case R8A66597_PLATDATA_XTAL_48MHZ: + clock = XTAL48; + break; + default: + printk(KERN_ERR "r8a66597: platdata clock is wrong.\n"); + break; + } + + return clock; +} + +static inline u32 r8a66597_sudmac_read(struct r8a66597 *r8a66597, + unsigned long offset) +{ + return ioread32(r8a66597->sudmac_reg + offset); +} + +static inline void r8a66597_sudmac_write(struct r8a66597 *r8a66597, u32 val, + unsigned long offset) +{ + iowrite32(val, r8a66597->sudmac_reg + offset); +} + +#define get_pipectr_addr(pipenum) (PIPE1CTR + (pipenum - 1) * 2) +#define get_pipetre_addr(pipenum) (PIPE1TRE + (pipenum - 1) * 4) +#define get_pipetrn_addr(pipenum) (PIPE1TRN + (pipenum - 1) * 4) + +#define enable_irq_ready(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, BRDYENB) +#define disable_irq_ready(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, BRDYENB) +#define enable_irq_empty(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, BEMPENB) +#define disable_irq_empty(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, BEMPENB) +#define enable_irq_nrdy(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, NRDYENB) +#define disable_irq_nrdy(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, NRDYENB) + +#endif /* __R8A66597_H__ */ + diff --git a/drivers/usb/gadget/rndis.c b/drivers/usb/gadget/rndis.c new file mode 100644 index 00000000..e3614989 --- /dev/null +++ b/drivers/usb/gadget/rndis.c @@ -0,0 +1,1237 @@ +/* + * RNDIS MSG parser + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This software was originally developed in conformance with + * Microsoft's Remote NDIS Specification License Agreement. + * + * 03/12/2004 Kai-Uwe Bloem + * Fixed message length bug in init_response + * + * 03/25/2004 Kai-Uwe Bloem + * Fixed rndis_rm_hdr length bug. + * + * Copyright (C) 2004 by David Brownell + * updates to merge with Linux 2.6, better match RNDIS spec + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#undef VERBOSE_DEBUG + +#include "rndis.h" + + +/* The driver for your USB chip needs to support ep0 OUT to work with + * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional). + * + * Windows hosts need an INF file like Documentation/usb/linux.inf + * and will be happier if you provide the host_addr module parameter. + */ + +#if 0 +static int rndis_debug = 0; +module_param (rndis_debug, int, 0); +MODULE_PARM_DESC (rndis_debug, "enable debugging"); +#else +#define rndis_debug 0 +#endif + +#define RNDIS_MAX_CONFIGS 1 + + +static rndis_params rndis_per_dev_params[RNDIS_MAX_CONFIGS]; + +/* Driver Version */ +static const __le32 rndis_driver_version = cpu_to_le32(1); + +/* Function Prototypes */ +static rndis_resp_t *rndis_add_response(int configNr, u32 length); + + +/* supported OIDs */ +static const u32 oid_supported_list[] = +{ + /* the general stuff */ + RNDIS_OID_GEN_SUPPORTED_LIST, + RNDIS_OID_GEN_HARDWARE_STATUS, + RNDIS_OID_GEN_MEDIA_SUPPORTED, + RNDIS_OID_GEN_MEDIA_IN_USE, + RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE, + RNDIS_OID_GEN_LINK_SPEED, + RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE, + RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE, + RNDIS_OID_GEN_VENDOR_ID, + RNDIS_OID_GEN_VENDOR_DESCRIPTION, + RNDIS_OID_GEN_VENDOR_DRIVER_VERSION, + RNDIS_OID_GEN_CURRENT_PACKET_FILTER, + RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE, + RNDIS_OID_GEN_MEDIA_CONNECT_STATUS, + RNDIS_OID_GEN_PHYSICAL_MEDIUM, + + /* the statistical stuff */ + RNDIS_OID_GEN_XMIT_OK, + RNDIS_OID_GEN_RCV_OK, + RNDIS_OID_GEN_XMIT_ERROR, + RNDIS_OID_GEN_RCV_ERROR, + RNDIS_OID_GEN_RCV_NO_BUFFER, +#ifdef RNDIS_OPTIONAL_STATS + RNDIS_OID_GEN_DIRECTED_BYTES_XMIT, + RNDIS_OID_GEN_DIRECTED_FRAMES_XMIT, + RNDIS_OID_GEN_MULTICAST_BYTES_XMIT, + RNDIS_OID_GEN_MULTICAST_FRAMES_XMIT, + RNDIS_OID_GEN_BROADCAST_BYTES_XMIT, + RNDIS_OID_GEN_BROADCAST_FRAMES_XMIT, + RNDIS_OID_GEN_DIRECTED_BYTES_RCV, + RNDIS_OID_GEN_DIRECTED_FRAMES_RCV, + RNDIS_OID_GEN_MULTICAST_BYTES_RCV, + RNDIS_OID_GEN_MULTICAST_FRAMES_RCV, + RNDIS_OID_GEN_BROADCAST_BYTES_RCV, + RNDIS_OID_GEN_BROADCAST_FRAMES_RCV, + RNDIS_OID_GEN_RCV_CRC_ERROR, + RNDIS_OID_GEN_TRANSMIT_QUEUE_LENGTH, +#endif /* RNDIS_OPTIONAL_STATS */ + + /* mandatory 802.3 */ + /* the general stuff */ + RNDIS_OID_802_3_PERMANENT_ADDRESS, + RNDIS_OID_802_3_CURRENT_ADDRESS, + RNDIS_OID_802_3_MULTICAST_LIST, + RNDIS_OID_802_3_MAC_OPTIONS, + RNDIS_OID_802_3_MAXIMUM_LIST_SIZE, + + /* the statistical stuff */ + RNDIS_OID_802_3_RCV_ERROR_ALIGNMENT, + RNDIS_OID_802_3_XMIT_ONE_COLLISION, + RNDIS_OID_802_3_XMIT_MORE_COLLISIONS, +#ifdef RNDIS_OPTIONAL_STATS + RNDIS_OID_802_3_XMIT_DEFERRED, + RNDIS_OID_802_3_XMIT_MAX_COLLISIONS, + RNDIS_OID_802_3_RCV_OVERRUN, + RNDIS_OID_802_3_XMIT_UNDERRUN, + RNDIS_OID_802_3_XMIT_HEARTBEAT_FAILURE, + RNDIS_OID_802_3_XMIT_TIMES_CRS_LOST, + RNDIS_OID_802_3_XMIT_LATE_COLLISIONS, +#endif /* RNDIS_OPTIONAL_STATS */ + +#ifdef RNDIS_PM + /* PM and wakeup are "mandatory" for USB, but the RNDIS specs + * don't say what they mean ... and the NDIS specs are often + * confusing and/or ambiguous in this context. (That is, more + * so than their specs for the other OIDs.) + * + * FIXME someone who knows what these should do, please + * implement them! + */ + + /* power management */ + OID_PNP_CAPABILITIES, + OID_PNP_QUERY_POWER, + OID_PNP_SET_POWER, + +#ifdef RNDIS_WAKEUP + /* wake up host */ + OID_PNP_ENABLE_WAKE_UP, + OID_PNP_ADD_WAKE_UP_PATTERN, + OID_PNP_REMOVE_WAKE_UP_PATTERN, +#endif /* RNDIS_WAKEUP */ +#endif /* RNDIS_PM */ +}; + + +/* NDIS Functions */ +static int gen_ndis_query_resp(int configNr, u32 OID, u8 *buf, + unsigned buf_len, rndis_resp_t *r) +{ + int retval = -ENOTSUPP; + u32 length = 4; /* usually */ + __le32 *outbuf; + int i, count; + rndis_query_cmplt_type *resp; + struct net_device *net; + struct rtnl_link_stats64 temp; + const struct rtnl_link_stats64 *stats; + + if (!r) return -ENOMEM; + resp = (rndis_query_cmplt_type *)r->buf; + + if (!resp) return -ENOMEM; + + if (buf_len && rndis_debug > 1) { + pr_debug("query OID %08x value, len %d:\n", OID, buf_len); + for (i = 0; i < buf_len; i += 16) { + pr_debug("%03d: %08x %08x %08x %08x\n", i, + get_unaligned_le32(&buf[i]), + get_unaligned_le32(&buf[i + 4]), + get_unaligned_le32(&buf[i + 8]), + get_unaligned_le32(&buf[i + 12])); + } + } + + /* response goes here, right after the header */ + outbuf = (__le32 *)&resp[1]; + resp->InformationBufferOffset = cpu_to_le32(16); + + net = rndis_per_dev_params[configNr].dev; + stats = dev_get_stats(net, &temp); + + switch (OID) { + + /* general oids (table 4-1) */ + + /* mandatory */ + case RNDIS_OID_GEN_SUPPORTED_LIST: + pr_debug("%s: RNDIS_OID_GEN_SUPPORTED_LIST\n", __func__); + length = sizeof(oid_supported_list); + count = length / sizeof(u32); + for (i = 0; i < count; i++) + outbuf[i] = cpu_to_le32(oid_supported_list[i]); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_HARDWARE_STATUS: + pr_debug("%s: RNDIS_OID_GEN_HARDWARE_STATUS\n", __func__); + /* Bogus question! + * Hardware must be ready to receive high level protocols. + * BTW: + * reddite ergo quae sunt Caesaris Caesari + * et quae sunt Dei Deo! + */ + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_MEDIA_SUPPORTED: + pr_debug("%s: RNDIS_OID_GEN_MEDIA_SUPPORTED\n", __func__); + *outbuf = cpu_to_le32(rndis_per_dev_params[configNr].medium); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_MEDIA_IN_USE: + pr_debug("%s: RNDIS_OID_GEN_MEDIA_IN_USE\n", __func__); + /* one medium, one transport... (maybe you do it better) */ + *outbuf = cpu_to_le32(rndis_per_dev_params[configNr].medium); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE: + pr_debug("%s: RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].dev->mtu); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_GEN_LINK_SPEED: + if (rndis_debug > 1) + pr_debug("%s: RNDIS_OID_GEN_LINK_SPEED\n", __func__); + if (rndis_per_dev_params[configNr].media_state + == RNDIS_MEDIA_STATE_DISCONNECTED) + *outbuf = cpu_to_le32(0); + else + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].speed); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE: + pr_debug("%s: RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].dev->mtu); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE: + pr_debug("%s: RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].dev->mtu); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_GEN_VENDOR_ID: + pr_debug("%s: RNDIS_OID_GEN_VENDOR_ID\n", __func__); + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].vendorID); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_VENDOR_DESCRIPTION: + pr_debug("%s: RNDIS_OID_GEN_VENDOR_DESCRIPTION\n", __func__); + if (rndis_per_dev_params[configNr].vendorDescr) { + length = strlen(rndis_per_dev_params[configNr]. + vendorDescr); + memcpy(outbuf, + rndis_per_dev_params[configNr].vendorDescr, + length); + } else { + outbuf[0] = 0; + } + retval = 0; + break; + + case RNDIS_OID_GEN_VENDOR_DRIVER_VERSION: + pr_debug("%s: RNDIS_OID_GEN_VENDOR_DRIVER_VERSION\n", __func__); + /* Created as LE */ + *outbuf = rndis_driver_version; + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_CURRENT_PACKET_FILTER: + pr_debug("%s: RNDIS_OID_GEN_CURRENT_PACKET_FILTER\n", __func__); + *outbuf = cpu_to_le32(*rndis_per_dev_params[configNr].filter); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE: + pr_debug("%s: RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE\n", __func__); + *outbuf = cpu_to_le32(RNDIS_MAX_TOTAL_SIZE); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_MEDIA_CONNECT_STATUS: + if (rndis_debug > 1) + pr_debug("%s: RNDIS_OID_GEN_MEDIA_CONNECT_STATUS\n", __func__); + *outbuf = cpu_to_le32(rndis_per_dev_params[configNr] + .media_state); + retval = 0; + break; + + case RNDIS_OID_GEN_PHYSICAL_MEDIUM: + pr_debug("%s: RNDIS_OID_GEN_PHYSICAL_MEDIUM\n", __func__); + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + /* The RNDIS specification is incomplete/wrong. Some versions + * of MS-Windows expect OIDs that aren't specified there. Other + * versions emit undefined RNDIS messages. DOCUMENT ALL THESE! + */ + case RNDIS_OID_GEN_MAC_OPTIONS: /* from WinME */ + pr_debug("%s: RNDIS_OID_GEN_MAC_OPTIONS\n", __func__); + *outbuf = cpu_to_le32( + RNDIS_MAC_OPTION_RECEIVE_SERIALIZED + | RNDIS_MAC_OPTION_FULL_DUPLEX); + retval = 0; + break; + + /* statistics OIDs (table 4-2) */ + + /* mandatory */ + case RNDIS_OID_GEN_XMIT_OK: + if (rndis_debug > 1) + pr_debug("%s: RNDIS_OID_GEN_XMIT_OK\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->tx_packets + - stats->tx_errors - stats->tx_dropped); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_GEN_RCV_OK: + if (rndis_debug > 1) + pr_debug("%s: RNDIS_OID_GEN_RCV_OK\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->rx_packets + - stats->rx_errors - stats->rx_dropped); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_GEN_XMIT_ERROR: + if (rndis_debug > 1) + pr_debug("%s: RNDIS_OID_GEN_XMIT_ERROR\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->tx_errors); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_GEN_RCV_ERROR: + if (rndis_debug > 1) + pr_debug("%s: RNDIS_OID_GEN_RCV_ERROR\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->rx_errors); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_GEN_RCV_NO_BUFFER: + pr_debug("%s: RNDIS_OID_GEN_RCV_NO_BUFFER\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->rx_dropped); + retval = 0; + } + break; + + /* ieee802.3 OIDs (table 4-3) */ + + /* mandatory */ + case RNDIS_OID_802_3_PERMANENT_ADDRESS: + pr_debug("%s: RNDIS_OID_802_3_PERMANENT_ADDRESS\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + length = ETH_ALEN; + memcpy(outbuf, + rndis_per_dev_params[configNr].host_mac, + length); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_802_3_CURRENT_ADDRESS: + pr_debug("%s: RNDIS_OID_802_3_CURRENT_ADDRESS\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + length = ETH_ALEN; + memcpy(outbuf, + rndis_per_dev_params [configNr].host_mac, + length); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_802_3_MULTICAST_LIST: + pr_debug("%s: RNDIS_OID_802_3_MULTICAST_LIST\n", __func__); + /* Multicast base address only */ + *outbuf = cpu_to_le32(0xE0000000); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_802_3_MAXIMUM_LIST_SIZE: + pr_debug("%s: RNDIS_OID_802_3_MAXIMUM_LIST_SIZE\n", __func__); + /* Multicast base address only */ + *outbuf = cpu_to_le32(1); + retval = 0; + break; + + case RNDIS_OID_802_3_MAC_OPTIONS: + pr_debug("%s: RNDIS_OID_802_3_MAC_OPTIONS\n", __func__); + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + /* ieee802.3 statistics OIDs (table 4-4) */ + + /* mandatory */ + case RNDIS_OID_802_3_RCV_ERROR_ALIGNMENT: + pr_debug("%s: RNDIS_OID_802_3_RCV_ERROR_ALIGNMENT\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->rx_frame_errors); + retval = 0; + } + break; + + /* mandatory */ + case RNDIS_OID_802_3_XMIT_ONE_COLLISION: + pr_debug("%s: RNDIS_OID_802_3_XMIT_ONE_COLLISION\n", __func__); + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_802_3_XMIT_MORE_COLLISIONS: + pr_debug("%s: RNDIS_OID_802_3_XMIT_MORE_COLLISIONS\n", __func__); + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + default: + pr_warning("%s: query unknown OID 0x%08X\n", + __func__, OID); + } + if (retval < 0) + length = 0; + + resp->InformationBufferLength = cpu_to_le32(length); + r->length = length + sizeof(*resp); + resp->MessageLength = cpu_to_le32(r->length); + return retval; +} + +static int gen_ndis_set_resp(u8 configNr, u32 OID, u8 *buf, u32 buf_len, + rndis_resp_t *r) +{ + rndis_set_cmplt_type *resp; + int i, retval = -ENOTSUPP; + struct rndis_params *params; + + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *)r->buf; + if (!resp) + return -ENOMEM; + + if (buf_len && rndis_debug > 1) { + pr_debug("set OID %08x value, len %d:\n", OID, buf_len); + for (i = 0; i < buf_len; i += 16) { + pr_debug("%03d: %08x %08x %08x %08x\n", i, + get_unaligned_le32(&buf[i]), + get_unaligned_le32(&buf[i + 4]), + get_unaligned_le32(&buf[i + 8]), + get_unaligned_le32(&buf[i + 12])); + } + } + + params = &rndis_per_dev_params[configNr]; + switch (OID) { + case RNDIS_OID_GEN_CURRENT_PACKET_FILTER: + + /* these NDIS_PACKET_TYPE_* bitflags are shared with + * cdc_filter; it's not RNDIS-specific + * NDIS_PACKET_TYPE_x == USB_CDC_PACKET_TYPE_x for x in: + * PROMISCUOUS, DIRECTED, + * MULTICAST, ALL_MULTICAST, BROADCAST + */ + *params->filter = (u16)get_unaligned_le32(buf); + pr_debug("%s: RNDIS_OID_GEN_CURRENT_PACKET_FILTER %08x\n", + __func__, *params->filter); + + /* this call has a significant side effect: it's + * what makes the packet flow start and stop, like + * activating the CDC Ethernet altsetting. + */ + retval = 0; + if (*params->filter) { + params->state = RNDIS_DATA_INITIALIZED; + netif_carrier_on(params->dev); + if (netif_running(params->dev)) + netif_wake_queue(params->dev); + } else { + params->state = RNDIS_INITIALIZED; + netif_carrier_off(params->dev); + netif_stop_queue(params->dev); + } + break; + + case RNDIS_OID_802_3_MULTICAST_LIST: + /* I think we can ignore this */ + pr_debug("%s: RNDIS_OID_802_3_MULTICAST_LIST\n", __func__); + retval = 0; + break; + + default: + pr_warning("%s: set unknown OID 0x%08X, size %d\n", + __func__, OID, buf_len); + } + + return retval; +} + +/* + * Response Functions + */ +#ifdef CONFIG_USB_SPRD_DWC +#define RNDIS_MSG_MAX_NUM 10 +#else +#define RNDIS_MSG_MAX_NUM 1 +#endif +static int rndis_init_response(int configNr, rndis_init_msg_type *buf) +{ + rndis_init_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + if (!params->dev) + return -ENOTSUPP; + + r = rndis_add_response(configNr, sizeof(rndis_init_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_init_cmplt_type *)r->buf; + + resp->MessageType = cpu_to_le32(RNDIS_MSG_INIT_C); + resp->MessageLength = cpu_to_le32(52); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + resp->MajorVersion = cpu_to_le32(RNDIS_MAJOR_VERSION); + resp->MinorVersion = cpu_to_le32(RNDIS_MINOR_VERSION); + resp->DeviceFlags = cpu_to_le32(RNDIS_DF_CONNECTIONLESS); + resp->Medium = cpu_to_le32(RNDIS_MEDIUM_802_3); + resp->MaxPacketsPerTransfer = cpu_to_le32(RNDIS_MSG_MAX_NUM); + resp->MaxTransferSize = cpu_to_le32( + (params->dev->mtu + + sizeof(struct ethhdr) + + sizeof(struct rndis_packet_msg_type) + + 22)*RNDIS_MSG_MAX_NUM); + resp->PacketAlignmentFactor = cpu_to_le32(0); + resp->AFListOffset = cpu_to_le32(0); + resp->AFListSize = cpu_to_le32(0); + + params->resp_avail(params->v); + return 0; +} + +static int rndis_query_response(int configNr, rndis_query_msg_type *buf) +{ + rndis_query_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + /* pr_debug("%s: OID = %08X\n", __func__, cpu_to_le32(buf->OID)); */ + if (!params->dev) + return -ENOTSUPP; + + /* + * we need more memory: + * gen_ndis_query_resp expects enough space for + * rndis_query_cmplt_type followed by data. + * oid_supported_list is the largest data reply + */ + r = rndis_add_response(configNr, + sizeof(oid_supported_list) + sizeof(rndis_query_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_query_cmplt_type *)r->buf; + + resp->MessageType = cpu_to_le32(RNDIS_MSG_QUERY_C); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + + if (gen_ndis_query_resp(configNr, le32_to_cpu(buf->OID), + le32_to_cpu(buf->InformationBufferOffset) + + 8 + (u8 *)buf, + le32_to_cpu(buf->InformationBufferLength), + r)) { + /* OID not supported */ + resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED); + resp->MessageLength = cpu_to_le32(sizeof *resp); + resp->InformationBufferLength = cpu_to_le32(0); + resp->InformationBufferOffset = cpu_to_le32(0); + } else + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + + params->resp_avail(params->v); + return 0; +} + +static int rndis_set_response(int configNr, rndis_set_msg_type *buf) +{ + u32 BufLength, BufOffset; + rndis_set_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + r = rndis_add_response(configNr, sizeof(rndis_set_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *)r->buf; + + BufLength = le32_to_cpu(buf->InformationBufferLength); + BufOffset = le32_to_cpu(buf->InformationBufferOffset); + +#ifdef VERBOSE_DEBUG + pr_debug("%s: Length: %d\n", __func__, BufLength); + pr_debug("%s: Offset: %d\n", __func__, BufOffset); + pr_debug("%s: InfoBuffer: ", __func__); + + for (i = 0; i < BufLength; i++) { + pr_debug("%02x ", *(((u8 *) buf) + i + 8 + BufOffset)); + } + + pr_debug("\n"); +#endif + + resp->MessageType = cpu_to_le32(RNDIS_MSG_SET_C); + resp->MessageLength = cpu_to_le32(16); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + if (gen_ndis_set_resp(configNr, le32_to_cpu(buf->OID), + ((u8 *)buf) + 8 + BufOffset, BufLength, r)) + resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED); + else + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + + params->resp_avail(params->v); + return 0; +} + +static int rndis_reset_response(int configNr, rndis_reset_msg_type *buf) +{ + rndis_reset_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + u32 length; + u8 *xbuf; + + /* drain the response queue */ + while ((xbuf = rndis_get_next_response(configNr, &length))) + rndis_free_response(configNr, xbuf); + + r = rndis_add_response(configNr, sizeof(rndis_reset_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_reset_cmplt_type *)r->buf; + + resp->MessageType = cpu_to_le32(RNDIS_MSG_RESET_C); + resp->MessageLength = cpu_to_le32(16); + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + /* resent information */ + resp->AddressingReset = cpu_to_le32(1); + + params->resp_avail(params->v); + return 0; +} + +static int rndis_keepalive_response(int configNr, + rndis_keepalive_msg_type *buf) +{ + rndis_keepalive_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + /* host "should" check only in RNDIS_DATA_INITIALIZED state */ + + r = rndis_add_response(configNr, sizeof(rndis_keepalive_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_keepalive_cmplt_type *)r->buf; + + resp->MessageType = cpu_to_le32(RNDIS_MSG_KEEPALIVE_C); + resp->MessageLength = cpu_to_le32(16); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + + params->resp_avail(params->v); + return 0; +} + + +/* + * Device to Host Comunication + */ +static int rndis_indicate_status_msg(int configNr, u32 status) +{ + rndis_indicate_status_msg_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + if (params->state == RNDIS_UNINITIALIZED) + return -ENOTSUPP; + + r = rndis_add_response(configNr, + sizeof(rndis_indicate_status_msg_type)); + if (!r) + return -ENOMEM; + resp = (rndis_indicate_status_msg_type *)r->buf; + + resp->MessageType = cpu_to_le32(RNDIS_MSG_INDICATE); + resp->MessageLength = cpu_to_le32(20); + resp->Status = cpu_to_le32(status); + resp->StatusBufferLength = cpu_to_le32(0); + resp->StatusBufferOffset = cpu_to_le32(0); + + params->resp_avail(params->v); + return 0; +} + +int rndis_signal_connect(int configNr) +{ + rndis_per_dev_params[configNr].media_state + = RNDIS_MEDIA_STATE_CONNECTED; + return rndis_indicate_status_msg(configNr, + RNDIS_STATUS_MEDIA_CONNECT); +} + +int rndis_signal_disconnect(int configNr) +{ + rndis_per_dev_params[configNr].media_state + = RNDIS_MEDIA_STATE_DISCONNECTED; + return rndis_indicate_status_msg(configNr, + RNDIS_STATUS_MEDIA_DISCONNECT); +} + +void rndis_uninit(int configNr) +{ + u8 *buf; + u32 length; + + if (configNr >= RNDIS_MAX_CONFIGS) + return; + rndis_per_dev_params[configNr].state = RNDIS_UNINITIALIZED; + + /* drain the response queue */ + while ((buf = rndis_get_next_response(configNr, &length))) + rndis_free_response(configNr, buf); +} + +void rndis_set_host_mac(int configNr, const u8 *addr) +{ + rndis_per_dev_params[configNr].host_mac = addr; +} + +/* + * Message Parser + */ +int rndis_msg_parser(u8 configNr, u8 *buf) +{ + u32 MsgType, MsgLength; + __le32 *tmp; + struct rndis_params *params; + + if (!buf) + return -ENOMEM; + + tmp = (__le32 *)buf; + MsgType = get_unaligned_le32(tmp++); + MsgLength = get_unaligned_le32(tmp++); + + if (configNr >= RNDIS_MAX_CONFIGS) + return -ENOTSUPP; + params = &rndis_per_dev_params[configNr]; + + /* NOTE: RNDIS is *EXTREMELY* chatty ... Windows constantly polls for + * rx/tx statistics and link status, in addition to KEEPALIVE traffic + * and normal HC level polling to see if there's any IN traffic. + */ + + /* For USB: responses may take up to 10 seconds */ + switch (MsgType) { + case RNDIS_MSG_INIT: + pr_debug("%s: RNDIS_MSG_INIT\n", + __func__); + params->state = RNDIS_INITIALIZED; + return rndis_init_response(configNr, + (rndis_init_msg_type *)buf); + + case RNDIS_MSG_HALT: + pr_debug("%s: RNDIS_MSG_HALT\n", + __func__); + params->state = RNDIS_UNINITIALIZED; + if (params->dev) { + netif_carrier_off(params->dev); + netif_stop_queue(params->dev); + } + return 0; + + case RNDIS_MSG_QUERY: + return rndis_query_response(configNr, + (rndis_query_msg_type *)buf); + + case RNDIS_MSG_SET: + return rndis_set_response(configNr, + (rndis_set_msg_type *)buf); + + case RNDIS_MSG_RESET: + pr_debug("%s: RNDIS_MSG_RESET\n", + __func__); + return rndis_reset_response(configNr, + (rndis_reset_msg_type *)buf); + + case RNDIS_MSG_KEEPALIVE: + /* For USB: host does this every 5 seconds */ + if (rndis_debug > 1) + pr_debug("%s: RNDIS_MSG_KEEPALIVE\n", + __func__); + return rndis_keepalive_response(configNr, + (rndis_keepalive_msg_type *) + buf); + + default: + /* At least Windows XP emits some undefined RNDIS messages. + * In one case those messages seemed to relate to the host + * suspending itself. + */ + pr_warning("%s: unknown RNDIS message 0x%08X len %d\n", + __func__, MsgType, MsgLength); + print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET, + buf, MsgLength); + break; + } + + return -ENOTSUPP; +} + +int rndis_register(void (*resp_avail)(void *v), void *v) +{ + u8 i; + + if (!resp_avail) + return -EINVAL; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { + if (!rndis_per_dev_params[i].used) { + rndis_per_dev_params[i].used = 1; + rndis_per_dev_params[i].resp_avail = resp_avail; + rndis_per_dev_params[i].v = v; + pr_debug("%s: configNr = %d\n", __func__, i); + return i; + } + } + pr_debug("failed\n"); + + return -ENODEV; +} + +void rndis_deregister(int configNr) +{ + pr_debug("%s:\n", __func__); + + if (configNr >= RNDIS_MAX_CONFIGS) return; + rndis_per_dev_params[configNr].used = 0; +} + +int rndis_set_param_dev(u8 configNr, struct net_device *dev, u16 *cdc_filter) +{ + pr_debug("%s:\n", __func__); + if (!dev) + return -EINVAL; + if (configNr >= RNDIS_MAX_CONFIGS) return -1; + + rndis_per_dev_params[configNr].dev = dev; + rndis_per_dev_params[configNr].filter = cdc_filter; + + return 0; +} + +int rndis_set_param_vendor(u8 configNr, u32 vendorID, const char *vendorDescr) +{ + pr_debug("%s:\n", __func__); + if (!vendorDescr) return -1; + if (configNr >= RNDIS_MAX_CONFIGS) return -1; + + rndis_per_dev_params[configNr].vendorID = vendorID; + rndis_per_dev_params[configNr].vendorDescr = vendorDescr; + + return 0; +} + +int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed) +{ + pr_debug("%s: %u %u\n", __func__, medium, speed); + if (configNr >= RNDIS_MAX_CONFIGS) return -1; + + rndis_per_dev_params[configNr].medium = medium; + rndis_per_dev_params[configNr].speed = speed; + + return 0; +} + +void rndis_add_hdr(struct sk_buff *skb) +{ + struct rndis_packet_msg_type *header; + + if (!skb) + return; + header = (void *)skb_push(skb, sizeof(*header)); + memset(header, 0, sizeof *header); + header->MessageType = cpu_to_le32(RNDIS_MSG_PACKET); + header->MessageLength = cpu_to_le32(skb->len); + header->DataOffset = cpu_to_le32(36); + header->DataLength = cpu_to_le32(skb->len - sizeof(*header)); +} + +void rndis_free_response(int configNr, u8 *buf) +{ + rndis_resp_t *r; + struct list_head *act, *tmp; + + list_for_each_safe(act, tmp, + &(rndis_per_dev_params[configNr].resp_queue)) + { + r = list_entry(act, rndis_resp_t, list); + if (r && r->buf == buf) { + list_del(&r->list); + kfree(r); + } + } +} + +u8 *rndis_get_next_response(int configNr, u32 *length) +{ + rndis_resp_t *r; + struct list_head *act, *tmp; + + if (!length) return NULL; + + list_for_each_safe(act, tmp, + &(rndis_per_dev_params[configNr].resp_queue)) + { + r = list_entry(act, rndis_resp_t, list); + if (!r->send) { + r->send = 1; + *length = r->length; + return r->buf; + } + } + + return NULL; +} + +static rndis_resp_t *rndis_add_response(int configNr, u32 length) +{ + rndis_resp_t *r; + + /* NOTE: this gets copied into ether.c USB_BUFSIZ bytes ... */ + r = kmalloc(sizeof(rndis_resp_t) + length, GFP_ATOMIC); + if (!r) return NULL; + + r->buf = (u8 *)(r + 1); + r->length = length; + r->send = 0; + + list_add_tail(&r->list, + &(rndis_per_dev_params[configNr].resp_queue)); + return r; +} + +int rndis_rm_hdr(struct gether *port, + struct sk_buff *skb, + struct sk_buff_head *list) +{ + int num_pkts = 0; + + while (skb->len) { + struct rndis_packet_msg_type *hdr; + struct sk_buff *skb2; + u32 msg_len, data_offset, data_len; + + if (skb->len < sizeof *hdr) { + pr_err("invalid rndis pkt: skblen:%u hdr_len:%u", + skb->len, sizeof *hdr); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + hdr = (void *)skb->data; + msg_len = le32_to_cpu(hdr->MessageLength); + data_offset = le32_to_cpu(hdr->DataOffset); + data_len = le32_to_cpu(hdr->DataLength); + + if (skb->len < msg_len || + ((data_offset + data_len + 8) > msg_len)) { + pr_err("invalid rndis message: %d/%d/%d/%d, len:%d\n", + le32_to_cpu(hdr->MessageType), + msg_len, data_offset, data_len, skb->len); + dev_kfree_skb_any(skb); + return -EOVERFLOW; + } + + if (le32_to_cpu(hdr->MessageType) != RNDIS_MSG_PACKET) { + pr_err("invalid rndis message: %d/%d/%d/%d, len:%d\n", + le32_to_cpu(hdr->MessageType), + msg_len, data_offset, data_len, skb->len); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + num_pkts++; + + skb_pull(skb, data_offset + 8); + + if (data_len == skb->len || + data_len == (skb->len - 1)) { + skb_trim(skb, data_len); + break; + } + + skb2 = skb_clone(skb, GFP_ATOMIC); + if (!skb2) { + pr_err("%s:skb clone failed\n", __func__); + dev_kfree_skb_any(skb); + return -ENOMEM; + } + + skb_pull(skb, msg_len - sizeof *hdr); + skb_trim(skb2, data_len); + skb_queue_tail(list, skb2); + } + + skb_queue_tail(list, skb); + + return 0; +} + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static int rndis_proc_show(struct seq_file *m, void *v) +{ + rndis_params *param = m->private; + + seq_printf(m, + "Config Nr. %d\n" + "used : %s\n" + "state : %s\n" + "medium : 0x%08X\n" + "speed : %d\n" + "cable : %s\n" + "vendor ID : 0x%08X\n" + "vendor : %s\n", + param->confignr, (param->used) ? "y" : "n", + ({ char *s = "?"; + switch (param->state) { + case RNDIS_UNINITIALIZED: + s = "RNDIS_UNINITIALIZED"; break; + case RNDIS_INITIALIZED: + s = "RNDIS_INITIALIZED"; break; + case RNDIS_DATA_INITIALIZED: + s = "RNDIS_DATA_INITIALIZED"; break; + }; s; }), + param->medium, + (param->media_state) ? 0 : param->speed*100, + (param->media_state) ? "disconnected" : "connected", + param->vendorID, param->vendorDescr); + return 0; +} + +static ssize_t rndis_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + rndis_params *p = PDE_DATA(file_inode(file)); + u32 speed = 0; + int i, fl_speed = 0; + + for (i = 0; i < count; i++) { + char c; + if (get_user(c, buffer)) + return -EFAULT; + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + fl_speed = 1; + speed = speed * 10 + c - '0'; + break; + case 'C': + case 'c': + rndis_signal_connect(p->confignr); + break; + case 'D': + case 'd': + rndis_signal_disconnect(p->confignr); + break; + default: + if (fl_speed) p->speed = speed; + else pr_debug("%c is not valid\n", c); + break; + } + + buffer++; + } + + return count; +} + +static int rndis_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, rndis_proc_show, PDE_DATA(inode)); +} + +static const struct file_operations rndis_proc_fops = { + .owner = THIS_MODULE, + .open = rndis_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = rndis_proc_write, +}; + +#define NAME_TEMPLATE "driver/rndis-%03d" + +static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS]; + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +static bool rndis_initialized; + +int rndis_init(void) +{ + u8 i; + + if (rndis_initialized) + return 0; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + char name [20]; + + sprintf(name, NAME_TEMPLATE, i); + rndis_connect_state[i] = proc_create_data(name, 0660, NULL, + &rndis_proc_fops, + (void *)(rndis_per_dev_params + i)); + if (!rndis_connect_state[i]) { + pr_debug("%s: remove entries", __func__); + while (i) { + sprintf(name, NAME_TEMPLATE, --i); + remove_proc_entry(name, NULL); + } + pr_debug("\n"); + return -EIO; + } +#endif + rndis_per_dev_params[i].confignr = i; + rndis_per_dev_params[i].used = 0; + rndis_per_dev_params[i].state = RNDIS_UNINITIALIZED; + rndis_per_dev_params[i].media_state + = RNDIS_MEDIA_STATE_DISCONNECTED; + INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue)); + } + + rndis_initialized = true; + return 0; +} + +void rndis_exit(void) +{ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + u8 i; + char name[20]; +#endif + + if (!rndis_initialized) + return; + rndis_initialized = false; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { + sprintf(name, NAME_TEMPLATE, i); + remove_proc_entry(name, NULL); + } +#endif +} diff --git a/drivers/usb/gadget/rndis.h b/drivers/usb/gadget/rndis.h new file mode 100644 index 00000000..0647f2f3 --- /dev/null +++ b/drivers/usb/gadget/rndis.h @@ -0,0 +1,222 @@ +/* + * RNDIS Definitions for Remote NDIS + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This software was originally developed in conformance with + * Microsoft's Remote NDIS Specification License Agreement. + */ + +#ifndef _LINUX_RNDIS_H +#define _LINUX_RNDIS_H + +#include +#include "ndis.h" + +#define RNDIS_MAXIMUM_FRAME_SIZE 1518 +#define RNDIS_MAX_TOTAL_SIZE 1558 + +typedef struct rndis_init_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 MajorVersion; + __le32 MinorVersion; + __le32 MaxTransferSize; +} rndis_init_msg_type; + +typedef struct rndis_init_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; + __le32 MajorVersion; + __le32 MinorVersion; + __le32 DeviceFlags; + __le32 Medium; + __le32 MaxPacketsPerTransfer; + __le32 MaxTransferSize; + __le32 PacketAlignmentFactor; + __le32 AFListOffset; + __le32 AFListSize; +} rndis_init_cmplt_type; + +typedef struct rndis_halt_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; +} rndis_halt_msg_type; + +typedef struct rndis_query_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 OID; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; + __le32 DeviceVcHandle; +} rndis_query_msg_type; + +typedef struct rndis_query_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; +} rndis_query_cmplt_type; + +typedef struct rndis_set_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 OID; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; + __le32 DeviceVcHandle; +} rndis_set_msg_type; + +typedef struct rndis_set_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; +} rndis_set_cmplt_type; + +typedef struct rndis_reset_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 Reserved; +} rndis_reset_msg_type; + +typedef struct rndis_reset_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 Status; + __le32 AddressingReset; +} rndis_reset_cmplt_type; + +typedef struct rndis_indicate_status_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 Status; + __le32 StatusBufferLength; + __le32 StatusBufferOffset; +} rndis_indicate_status_msg_type; + +typedef struct rndis_keepalive_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; +} rndis_keepalive_msg_type; + +typedef struct rndis_keepalive_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; +} rndis_keepalive_cmplt_type; + +struct rndis_packet_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 DataOffset; + __le32 DataLength; + __le32 OOBDataOffset; + __le32 OOBDataLength; + __le32 NumOOBDataElements; + __le32 PerPacketInfoOffset; + __le32 PerPacketInfoLength; + __le32 VcHandle; + __le32 Reserved; +} __attribute__ ((packed)); + +struct rndis_config_parameter +{ + __le32 ParameterNameOffset; + __le32 ParameterNameLength; + __le32 ParameterType; + __le32 ParameterValueOffset; + __le32 ParameterValueLength; +}; + +/* implementation specific */ +enum rndis_state +{ + RNDIS_UNINITIALIZED, + RNDIS_INITIALIZED, + RNDIS_DATA_INITIALIZED, +}; + +typedef struct rndis_resp_t +{ + struct list_head list; + u8 *buf; + u32 length; + int send; +} rndis_resp_t; + +typedef struct rndis_params +{ + u8 confignr; + u8 used; + u16 saved_filter; + enum rndis_state state; + u32 medium; + u32 speed; + u32 media_state; + + const u8 *host_mac; + u16 *filter; + struct net_device *dev; + + u32 vendorID; + const char *vendorDescr; + void (*resp_avail)(void *v); + void *v; + struct list_head resp_queue; +} rndis_params; + +/* RNDIS Message parser and other useless functions */ +int rndis_msg_parser (u8 configNr, u8 *buf); +int rndis_register(void (*resp_avail)(void *v), void *v); +void rndis_deregister (int configNr); +int rndis_set_param_dev (u8 configNr, struct net_device *dev, + u16 *cdc_filter); +int rndis_set_param_vendor (u8 configNr, u32 vendorID, + const char *vendorDescr); +int rndis_set_param_medium (u8 configNr, u32 medium, u32 speed); +void rndis_add_hdr (struct sk_buff *skb); +int rndis_rm_hdr(struct gether *port, struct sk_buff *skb, + struct sk_buff_head *list); +u8 *rndis_get_next_response (int configNr, u32 *length); +void rndis_free_response (int configNr, u8 *buf); + +void rndis_uninit (int configNr); +int rndis_signal_connect (int configNr); +int rndis_signal_disconnect (int configNr); +int rndis_state (int configNr); +extern void rndis_set_host_mac (int configNr, const u8 *addr); + +int rndis_init(void); +void rndis_exit (void); + +#endif /* _LINUX_RNDIS_H */ diff --git a/drivers/usb/gadget/s3c-hsotg.c b/drivers/usb/gadget/s3c-hsotg.c new file mode 100644 index 00000000..af22f240 --- /dev/null +++ b/drivers/usb/gadget/s3c-hsotg.c @@ -0,0 +1,3667 @@ +/** + * linux/drivers/usb/gadget/s3c-hsotg.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * Ben Dooks + * http://armlinux.simtec.co.uk/ + * + * S3C USB2.0 High-speed / OtG driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "s3c-hsotg.h" + +static const char * const s3c_hsotg_supply_names[] = { + "vusb_d", /* digital USB supply, 1.2V */ + "vusb_a", /* analog USB supply, 1.1V */ +}; + +/* + * EP0_MPS_LIMIT + * + * Unfortunately there seems to be a limit of the amount of data that can + * be transferred by IN transactions on EP0. This is either 127 bytes or 3 + * packets (which practically means 1 packet and 63 bytes of data) when the + * MPS is set to 64. + * + * This means if we are wanting to move >127 bytes of data, we need to + * split the transactions up, but just doing one packet at a time does + * not work (this may be an implicit DATA0 PID on first packet of the + * transaction) and doing 2 packets is outside the controller's limits. + * + * If we try to lower the MPS size for EP0, then no transfers work properly + * for EP0, and the system will fail basic enumeration. As no cause for this + * has currently been found, we cannot support any large IN transfers for + * EP0. + */ +#define EP0_MPS_LIMIT 64 + +struct s3c_hsotg; +struct s3c_hsotg_req; + +/** + * struct s3c_hsotg_ep - driver endpoint definition. + * @ep: The gadget layer representation of the endpoint. + * @name: The driver generated name for the endpoint. + * @queue: Queue of requests for this endpoint. + * @parent: Reference back to the parent device structure. + * @req: The current request that the endpoint is processing. This is + * used to indicate an request has been loaded onto the endpoint + * and has yet to be completed (maybe due to data move, or simply + * awaiting an ack from the core all the data has been completed). + * @debugfs: File entry for debugfs file for this endpoint. + * @lock: State lock to protect contents of endpoint. + * @dir_in: Set to true if this endpoint is of the IN direction, which + * means that it is sending data to the Host. + * @index: The index for the endpoint registers. + * @name: The name array passed to the USB core. + * @halted: Set if the endpoint has been halted. + * @periodic: Set if this is a periodic ep, such as Interrupt + * @sent_zlp: Set if we've sent a zero-length packet. + * @total_data: The total number of data bytes done. + * @fifo_size: The size of the FIFO (for periodic IN endpoints) + * @fifo_load: The amount of data loaded into the FIFO (periodic IN) + * @last_load: The offset of data for the last start of request. + * @size_loaded: The last loaded size for DxEPTSIZE for periodic IN + * + * This is the driver's state for each registered enpoint, allowing it + * to keep track of transactions that need doing. Each endpoint has a + * lock to protect the state, to try and avoid using an overall lock + * for the host controller as much as possible. + * + * For periodic IN endpoints, we have fifo_size and fifo_load to try + * and keep track of the amount of data in the periodic FIFO for each + * of these as we don't have a status register that tells us how much + * is in each of them. (note, this may actually be useless information + * as in shared-fifo mode periodic in acts like a single-frame packet + * buffer than a fifo) + */ +struct s3c_hsotg_ep { + struct usb_ep ep; + struct list_head queue; + struct s3c_hsotg *parent; + struct s3c_hsotg_req *req; + struct dentry *debugfs; + + + unsigned long total_data; + unsigned int size_loaded; + unsigned int last_load; + unsigned int fifo_load; + unsigned short fifo_size; + + unsigned char dir_in; + unsigned char index; + + unsigned int halted:1; + unsigned int periodic:1; + unsigned int sent_zlp:1; + + char name[10]; +}; + +/** + * struct s3c_hsotg - driver state. + * @dev: The parent device supplied to the probe function + * @driver: USB gadget driver + * @phy: The otg phy transceiver structure for phy control. + * @plat: The platform specific configuration data. This can be removed once + * all SoCs support usb transceiver. + * @regs: The memory area mapped for accessing registers. + * @irq: The IRQ number we are using + * @supplies: Definition of USB power supplies + * @dedicated_fifos: Set if the hardware has dedicated IN-EP fifos. + * @num_of_eps: Number of available EPs (excluding EP0) + * @debug_root: root directrory for debugfs. + * @debug_file: main status file for debugfs. + * @debug_fifo: FIFO status file for debugfs. + * @ep0_reply: Request used for ep0 reply. + * @ep0_buff: Buffer for EP0 reply data, if needed. + * @ctrl_buff: Buffer for EP0 control requests. + * @ctrl_req: Request for EP0 control packets. + * @setup: NAK management for EP0 SETUP + * @last_rst: Time of last reset + * @eps: The endpoints being supplied to the gadget framework + */ +struct s3c_hsotg { + struct device *dev; + struct usb_gadget_driver *driver; + struct usb_phy *phy; + struct s3c_hsotg_plat *plat; + + spinlock_t lock; + + void __iomem *regs; + int irq; + struct clk *clk; + + struct regulator_bulk_data supplies[ARRAY_SIZE(s3c_hsotg_supply_names)]; + + unsigned int dedicated_fifos:1; + unsigned char num_of_eps; + + struct dentry *debug_root; + struct dentry *debug_file; + struct dentry *debug_fifo; + + struct usb_request *ep0_reply; + struct usb_request *ctrl_req; + u8 ep0_buff[8]; + u8 ctrl_buff[8]; + + struct usb_gadget gadget; + unsigned int setup; + unsigned long last_rst; + struct s3c_hsotg_ep *eps; +}; + +/** + * struct s3c_hsotg_req - data transfer request + * @req: The USB gadget request + * @queue: The list of requests for the endpoint this is queued for. + * @in_progress: Has already had size/packets written to core + * @mapped: DMA buffer for this request has been mapped via dma_map_single(). + */ +struct s3c_hsotg_req { + struct usb_request req; + struct list_head queue; + unsigned char in_progress; + unsigned char mapped; +}; + +/* conversion functions */ +static inline struct s3c_hsotg_req *our_req(struct usb_request *req) +{ + return container_of(req, struct s3c_hsotg_req, req); +} + +static inline struct s3c_hsotg_ep *our_ep(struct usb_ep *ep) +{ + return container_of(ep, struct s3c_hsotg_ep, ep); +} + +static inline struct s3c_hsotg *to_hsotg(struct usb_gadget *gadget) +{ + return container_of(gadget, struct s3c_hsotg, gadget); +} + +static inline void __orr32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) | val, ptr); +} + +static inline void __bic32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) & ~val, ptr); +} + +/* forward decleration of functions */ +static void s3c_hsotg_dump(struct s3c_hsotg *hsotg); + +/** + * using_dma - return the DMA status of the driver. + * @hsotg: The driver state. + * + * Return true if we're using DMA. + * + * Currently, we have the DMA support code worked into everywhere + * that needs it, but the AMBA DMA implementation in the hardware can + * only DMA from 32bit aligned addresses. This means that gadgets such + * as the CDC Ethernet cannot work as they often pass packets which are + * not 32bit aligned. + * + * Unfortunately the choice to use DMA or not is global to the controller + * and seems to be only settable when the controller is being put through + * a core reset. This means we either need to fix the gadgets to take + * account of DMA alignment, or add bounce buffers (yuerk). + * + * Until this issue is sorted out, we always return 'false'. + */ +static inline bool using_dma(struct s3c_hsotg *hsotg) +{ + return false; /* support is not complete */ +} + +/** + * s3c_hsotg_en_gsint - enable one or more of the general interrupt + * @hsotg: The device state + * @ints: A bitmask of the interrupts to enable + */ +static void s3c_hsotg_en_gsint(struct s3c_hsotg *hsotg, u32 ints) +{ + u32 gsintmsk = readl(hsotg->regs + GINTMSK); + u32 new_gsintmsk; + + new_gsintmsk = gsintmsk | ints; + + if (new_gsintmsk != gsintmsk) { + dev_dbg(hsotg->dev, "gsintmsk now 0x%08x\n", new_gsintmsk); + writel(new_gsintmsk, hsotg->regs + GINTMSK); + } +} + +/** + * s3c_hsotg_disable_gsint - disable one or more of the general interrupt + * @hsotg: The device state + * @ints: A bitmask of the interrupts to enable + */ +static void s3c_hsotg_disable_gsint(struct s3c_hsotg *hsotg, u32 ints) +{ + u32 gsintmsk = readl(hsotg->regs + GINTMSK); + u32 new_gsintmsk; + + new_gsintmsk = gsintmsk & ~ints; + + if (new_gsintmsk != gsintmsk) + writel(new_gsintmsk, hsotg->regs + GINTMSK); +} + +/** + * s3c_hsotg_ctrl_epint - enable/disable an endpoint irq + * @hsotg: The device state + * @ep: The endpoint index + * @dir_in: True if direction is in. + * @en: The enable value, true to enable + * + * Set or clear the mask for an individual endpoint's interrupt + * request. + */ +static void s3c_hsotg_ctrl_epint(struct s3c_hsotg *hsotg, + unsigned int ep, unsigned int dir_in, + unsigned int en) +{ + unsigned long flags; + u32 bit = 1 << ep; + u32 daint; + + if (!dir_in) + bit <<= 16; + + local_irq_save(flags); + daint = readl(hsotg->regs + DAINTMSK); + if (en) + daint |= bit; + else + daint &= ~bit; + writel(daint, hsotg->regs + DAINTMSK); + local_irq_restore(flags); +} + +/** + * s3c_hsotg_init_fifo - initialise non-periodic FIFOs + * @hsotg: The device instance. + */ +static void s3c_hsotg_init_fifo(struct s3c_hsotg *hsotg) +{ + unsigned int ep; + unsigned int addr; + unsigned int size; + int timeout; + u32 val; + + /* set FIFO sizes to 2048/1024 */ + + writel(2048, hsotg->regs + GRXFSIZ); + writel(GNPTXFSIZ_NPTxFStAddr(2048) | + GNPTXFSIZ_NPTxFDep(1024), + hsotg->regs + GNPTXFSIZ); + + /* + * arange all the rest of the TX FIFOs, as some versions of this + * block have overlapping default addresses. This also ensures + * that if the settings have been changed, then they are set to + * known values. + */ + + /* start at the end of the GNPTXFSIZ, rounded up */ + addr = 2048 + 1024; + size = 768; + + /* + * currently we allocate TX FIFOs for all possible endpoints, + * and assume that they are all the same size. + */ + + for (ep = 1; ep <= 15; ep++) { + val = addr; + val |= size << DPTXFSIZn_DPTxFSize_SHIFT; + addr += size; + + writel(val, hsotg->regs + DPTXFSIZn(ep)); + } + + /* + * according to p428 of the design guide, we need to ensure that + * all fifos are flushed before continuing + */ + + writel(GRSTCTL_TxFNum(0x10) | GRSTCTL_TxFFlsh | + GRSTCTL_RxFFlsh, hsotg->regs + GRSTCTL); + + /* wait until the fifos are both flushed */ + timeout = 100; + while (1) { + val = readl(hsotg->regs + GRSTCTL); + + if ((val & (GRSTCTL_TxFFlsh | GRSTCTL_RxFFlsh)) == 0) + break; + + if (--timeout == 0) { + dev_err(hsotg->dev, + "%s: timeout flushing fifos (GRSTCTL=%08x)\n", + __func__, val); + } + + udelay(1); + } + + dev_dbg(hsotg->dev, "FIFOs reset, timeout at %d\n", timeout); +} + +/** + * @ep: USB endpoint to allocate request for. + * @flags: Allocation flags + * + * Allocate a new USB request structure appropriate for the specified endpoint + */ +static struct usb_request *s3c_hsotg_ep_alloc_request(struct usb_ep *ep, + gfp_t flags) +{ + struct s3c_hsotg_req *req; + + req = kzalloc(sizeof(struct s3c_hsotg_req), flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +/** + * is_ep_periodic - return true if the endpoint is in periodic mode. + * @hs_ep: The endpoint to query. + * + * Returns true if the endpoint is in periodic mode, meaning it is being + * used for an Interrupt or ISO transfer. + */ +static inline int is_ep_periodic(struct s3c_hsotg_ep *hs_ep) +{ + return hs_ep->periodic; +} + +/** + * s3c_hsotg_unmap_dma - unmap the DMA memory being used for the request + * @hsotg: The device state. + * @hs_ep: The endpoint for the request + * @hs_req: The request being processed. + * + * This is the reverse of s3c_hsotg_map_dma(), called for the completion + * of a request to ensure the buffer is ready for access by the caller. + */ +static void s3c_hsotg_unmap_dma(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req) +{ + struct usb_request *req = &hs_req->req; + + /* ignore this if we're not moving any data */ + if (hs_req->req.length == 0) + return; + + usb_gadget_unmap_request(&hsotg->gadget, req, hs_ep->dir_in); +} + +/** + * s3c_hsotg_write_fifo - write packet Data to the TxFIFO + * @hsotg: The controller state. + * @hs_ep: The endpoint we're going to write for. + * @hs_req: The request to write data for. + * + * This is called when the TxFIFO has some space in it to hold a new + * transmission and we have something to give it. The actual setup of + * the data size is done elsewhere, so all we have to do is to actually + * write the data. + * + * The return value is zero if there is more space (or nothing was done) + * otherwise -ENOSPC is returned if the FIFO space was used up. + * + * This routine is only needed for PIO + */ +static int s3c_hsotg_write_fifo(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req) +{ + bool periodic = is_ep_periodic(hs_ep); + u32 gnptxsts = readl(hsotg->regs + GNPTXSTS); + int buf_pos = hs_req->req.actual; + int to_write = hs_ep->size_loaded; + void *data; + int can_write; + int pkt_round; + + to_write -= (buf_pos - hs_ep->last_load); + + /* if there's nothing to write, get out early */ + if (to_write == 0) + return 0; + + if (periodic && !hsotg->dedicated_fifos) { + u32 epsize = readl(hsotg->regs + DIEPTSIZ(hs_ep->index)); + int size_left; + int size_done; + + /* + * work out how much data was loaded so we can calculate + * how much data is left in the fifo. + */ + + size_left = DxEPTSIZ_XferSize_GET(epsize); + + /* + * if shared fifo, we cannot write anything until the + * previous data has been completely sent. + */ + if (hs_ep->fifo_load != 0) { + s3c_hsotg_en_gsint(hsotg, GINTSTS_PTxFEmp); + return -ENOSPC; + } + + dev_dbg(hsotg->dev, "%s: left=%d, load=%d, fifo=%d, size %d\n", + __func__, size_left, + hs_ep->size_loaded, hs_ep->fifo_load, hs_ep->fifo_size); + + /* how much of the data has moved */ + size_done = hs_ep->size_loaded - size_left; + + /* how much data is left in the fifo */ + can_write = hs_ep->fifo_load - size_done; + dev_dbg(hsotg->dev, "%s: => can_write1=%d\n", + __func__, can_write); + + can_write = hs_ep->fifo_size - can_write; + dev_dbg(hsotg->dev, "%s: => can_write2=%d\n", + __func__, can_write); + + if (can_write <= 0) { + s3c_hsotg_en_gsint(hsotg, GINTSTS_PTxFEmp); + return -ENOSPC; + } + } else if (hsotg->dedicated_fifos && hs_ep->index != 0) { + can_write = readl(hsotg->regs + DTXFSTS(hs_ep->index)); + + can_write &= 0xffff; + can_write *= 4; + } else { + if (GNPTXSTS_NPTxQSpcAvail_GET(gnptxsts) == 0) { + dev_dbg(hsotg->dev, + "%s: no queue slots available (0x%08x)\n", + __func__, gnptxsts); + + s3c_hsotg_en_gsint(hsotg, GINTSTS_NPTxFEmp); + return -ENOSPC; + } + + can_write = GNPTXSTS_NPTxFSpcAvail_GET(gnptxsts); + can_write *= 4; /* fifo size is in 32bit quantities. */ + } + + dev_dbg(hsotg->dev, "%s: GNPTXSTS=%08x, can=%d, to=%d, mps %d\n", + __func__, gnptxsts, can_write, to_write, hs_ep->ep.maxpacket); + + /* + * limit to 512 bytes of data, it seems at least on the non-periodic + * FIFO, requests of >512 cause the endpoint to get stuck with a + * fragment of the end of the transfer in it. + */ + if (can_write > 512) + can_write = 512; + + /* + * limit the write to one max-packet size worth of data, but allow + * the transfer to return that it did not run out of fifo space + * doing it. + */ + if (to_write > hs_ep->ep.maxpacket) { + to_write = hs_ep->ep.maxpacket; + + s3c_hsotg_en_gsint(hsotg, + periodic ? GINTSTS_PTxFEmp : + GINTSTS_NPTxFEmp); + } + + /* see if we can write data */ + + if (to_write > can_write) { + to_write = can_write; + pkt_round = to_write % hs_ep->ep.maxpacket; + + /* + * Round the write down to an + * exact number of packets. + * + * Note, we do not currently check to see if we can ever + * write a full packet or not to the FIFO. + */ + + if (pkt_round) + to_write -= pkt_round; + + /* + * enable correct FIFO interrupt to alert us when there + * is more room left. + */ + + s3c_hsotg_en_gsint(hsotg, + periodic ? GINTSTS_PTxFEmp : + GINTSTS_NPTxFEmp); + } + + dev_dbg(hsotg->dev, "write %d/%d, can_write %d, done %d\n", + to_write, hs_req->req.length, can_write, buf_pos); + + if (to_write <= 0) + return -ENOSPC; + + hs_req->req.actual = buf_pos + to_write; + hs_ep->total_data += to_write; + + if (periodic) + hs_ep->fifo_load += to_write; + + to_write = DIV_ROUND_UP(to_write, 4); + data = hs_req->req.buf + buf_pos; + + writesl(hsotg->regs + EPFIFO(hs_ep->index), data, to_write); + + return (to_write >= can_write) ? -ENOSPC : 0; +} + +/** + * get_ep_limit - get the maximum data legnth for this endpoint + * @hs_ep: The endpoint + * + * Return the maximum data that can be queued in one go on a given endpoint + * so that transfers that are too long can be split. + */ +static unsigned get_ep_limit(struct s3c_hsotg_ep *hs_ep) +{ + int index = hs_ep->index; + unsigned maxsize; + unsigned maxpkt; + + if (index != 0) { + maxsize = DxEPTSIZ_XferSize_LIMIT + 1; + maxpkt = DxEPTSIZ_PktCnt_LIMIT + 1; + } else { + maxsize = 64+64; + if (hs_ep->dir_in) + maxpkt = DIEPTSIZ0_PktCnt_LIMIT + 1; + else + maxpkt = 2; + } + + /* we made the constant loading easier above by using +1 */ + maxpkt--; + maxsize--; + + /* + * constrain by packet count if maxpkts*pktsize is greater + * than the length register size. + */ + + if ((maxpkt * hs_ep->ep.maxpacket) < maxsize) + maxsize = maxpkt * hs_ep->ep.maxpacket; + + return maxsize; +} + +/** + * s3c_hsotg_start_req - start a USB request from an endpoint's queue + * @hsotg: The controller state. + * @hs_ep: The endpoint to process a request for + * @hs_req: The request to start. + * @continuing: True if we are doing more for the current request. + * + * Start the given request running by setting the endpoint registers + * appropriately, and writing any data to the FIFOs. + */ +static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req, + bool continuing) +{ + struct usb_request *ureq = &hs_req->req; + int index = hs_ep->index; + int dir_in = hs_ep->dir_in; + u32 epctrl_reg; + u32 epsize_reg; + u32 epsize; + u32 ctrl; + unsigned length; + unsigned packets; + unsigned maxreq; + + if (index != 0) { + if (hs_ep->req && !continuing) { + dev_err(hsotg->dev, "%s: active request\n", __func__); + WARN_ON(1); + return; + } else if (hs_ep->req != hs_req && continuing) { + dev_err(hsotg->dev, + "%s: continue different req\n", __func__); + WARN_ON(1); + return; + } + } + + epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); + epsize_reg = dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index); + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x, ep %d, dir %s\n", + __func__, readl(hsotg->regs + epctrl_reg), index, + hs_ep->dir_in ? "in" : "out"); + + /* If endpoint is stalled, we will restart request later */ + ctrl = readl(hsotg->regs + epctrl_reg); + + if (ctrl & DxEPCTL_Stall) { + dev_warn(hsotg->dev, "%s: ep%d is stalled\n", __func__, index); + return; + } + + length = ureq->length - ureq->actual; + dev_dbg(hsotg->dev, "ureq->length:%d ureq->actual:%d\n", + ureq->length, ureq->actual); + if (0) + dev_dbg(hsotg->dev, + "REQ buf %p len %d dma 0x%08x noi=%d zp=%d snok=%d\n", + ureq->buf, length, ureq->dma, + ureq->no_interrupt, ureq->zero, ureq->short_not_ok); + + maxreq = get_ep_limit(hs_ep); + if (length > maxreq) { + int round = maxreq % hs_ep->ep.maxpacket; + + dev_dbg(hsotg->dev, "%s: length %d, max-req %d, r %d\n", + __func__, length, maxreq, round); + + /* round down to multiple of packets */ + if (round) + maxreq -= round; + + length = maxreq; + } + + if (length) + packets = DIV_ROUND_UP(length, hs_ep->ep.maxpacket); + else + packets = 1; /* send one packet if length is zero. */ + + if (dir_in && index != 0) + epsize = DxEPTSIZ_MC(1); + else + epsize = 0; + + if (index != 0 && ureq->zero) { + /* + * test for the packets being exactly right for the + * transfer + */ + + if (length == (packets * hs_ep->ep.maxpacket)) + packets++; + } + + epsize |= DxEPTSIZ_PktCnt(packets); + epsize |= DxEPTSIZ_XferSize(length); + + dev_dbg(hsotg->dev, "%s: %d@%d/%d, 0x%08x => 0x%08x\n", + __func__, packets, length, ureq->length, epsize, epsize_reg); + + /* store the request as the current one we're doing */ + hs_ep->req = hs_req; + + /* write size / packets */ + writel(epsize, hsotg->regs + epsize_reg); + + if (using_dma(hsotg) && !continuing) { + unsigned int dma_reg; + + /* + * write DMA address to control register, buffer already + * synced by s3c_hsotg_ep_queue(). + */ + + dma_reg = dir_in ? DIEPDMA(index) : DOEPDMA(index); + writel(ureq->dma, hsotg->regs + dma_reg); + + dev_dbg(hsotg->dev, "%s: 0x%08x => 0x%08x\n", + __func__, ureq->dma, dma_reg); + } + + ctrl |= DxEPCTL_EPEna; /* ensure ep enabled */ + ctrl |= DxEPCTL_USBActEp; + + dev_dbg(hsotg->dev, "setup req:%d\n", hsotg->setup); + + /* For Setup request do not clear NAK */ + if (hsotg->setup && index == 0) + hsotg->setup = 0; + else + ctrl |= DxEPCTL_CNAK; /* clear NAK set by core */ + + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); + writel(ctrl, hsotg->regs + epctrl_reg); + + /* + * set these, it seems that DMA support increments past the end + * of the packet buffer so we need to calculate the length from + * this information. + */ + hs_ep->size_loaded = length; + hs_ep->last_load = ureq->actual; + + if (dir_in && !using_dma(hsotg)) { + /* set these anyway, we may need them for non-periodic in */ + hs_ep->fifo_load = 0; + + s3c_hsotg_write_fifo(hsotg, hs_ep, hs_req); + } + + /* + * clear the INTknTXFEmpMsk when we start request, more as a aide + * to debugging to see what is going on. + */ + if (dir_in) + writel(DIEPMSK_INTknTXFEmpMsk, + hsotg->regs + DIEPINT(index)); + + /* + * Note, trying to clear the NAK here causes problems with transmit + * on the S3C6400 ending up with the TXFIFO becoming full. + */ + + /* check ep is enabled */ + if (!(readl(hsotg->regs + epctrl_reg) & DxEPCTL_EPEna)) + dev_warn(hsotg->dev, + "ep%d: failed to become enabled (DxEPCTL=0x%08x)?\n", + index, readl(hsotg->regs + epctrl_reg)); + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", + __func__, readl(hsotg->regs + epctrl_reg)); +} + +/** + * s3c_hsotg_map_dma - map the DMA memory being used for the request + * @hsotg: The device state. + * @hs_ep: The endpoint the request is on. + * @req: The request being processed. + * + * We've been asked to queue a request, so ensure that the memory buffer + * is correctly setup for DMA. If we've been passed an extant DMA address + * then ensure the buffer has been synced to memory. If our buffer has no + * DMA memory, then we map the memory and mark our request to allow us to + * cleanup on completion. + */ +static int s3c_hsotg_map_dma(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct usb_request *req) +{ + struct s3c_hsotg_req *hs_req = our_req(req); + int ret; + + /* if the length is zero, ignore the DMA data */ + if (hs_req->req.length == 0) + return 0; + + ret = usb_gadget_map_request(&hsotg->gadget, req, hs_ep->dir_in); + if (ret) + goto dma_error; + + return 0; + +dma_error: + dev_err(hsotg->dev, "%s: failed to map buffer %p, %d bytes\n", + __func__, req->buf, req->length); + + return -EIO; +} + +static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags) +{ + struct s3c_hsotg_req *hs_req = our_req(req); + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hs = hs_ep->parent; + bool first; + + dev_dbg(hs->dev, "%s: req %p: %d@%p, noi=%d, zero=%d, snok=%d\n", + ep->name, req, req->length, req->buf, req->no_interrupt, + req->zero, req->short_not_ok); + + /* initialise status of the request */ + INIT_LIST_HEAD(&hs_req->queue); + req->actual = 0; + req->status = -EINPROGRESS; + + /* if we're using DMA, sync the buffers as necessary */ + if (using_dma(hs)) { + int ret = s3c_hsotg_map_dma(hs, hs_ep, req); + if (ret) + return ret; + } + + first = list_empty(&hs_ep->queue); + list_add_tail(&hs_req->queue, &hs_ep->queue); + + if (first) + s3c_hsotg_start_req(hs, hs_ep, hs_req, false); + + return 0; +} + +static int s3c_hsotg_ep_queue_lock(struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hs = hs_ep->parent; + unsigned long flags = 0; + int ret = 0; + + spin_lock_irqsave(&hs->lock, flags); + ret = s3c_hsotg_ep_queue(ep, req, gfp_flags); + spin_unlock_irqrestore(&hs->lock, flags); + + return ret; +} + +static void s3c_hsotg_ep_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + struct s3c_hsotg_req *hs_req = our_req(req); + + kfree(hs_req); +} + +/** + * s3c_hsotg_complete_oursetup - setup completion callback + * @ep: The endpoint the request was on. + * @req: The request completed. + * + * Called on completion of any requests the driver itself + * submitted that need cleaning up. + */ +static void s3c_hsotg_complete_oursetup(struct usb_ep *ep, + struct usb_request *req) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hsotg = hs_ep->parent; + + dev_dbg(hsotg->dev, "%s: ep %p, req %p\n", __func__, ep, req); + + s3c_hsotg_ep_free_request(ep, req); +} + +/** + * ep_from_windex - convert control wIndex value to endpoint + * @hsotg: The driver state. + * @windex: The control request wIndex field (in host order). + * + * Convert the given wIndex into a pointer to an driver endpoint + * structure, or return NULL if it is not a valid endpoint. + */ +static struct s3c_hsotg_ep *ep_from_windex(struct s3c_hsotg *hsotg, + u32 windex) +{ + struct s3c_hsotg_ep *ep = &hsotg->eps[windex & 0x7F]; + int dir = (windex & USB_DIR_IN) ? 1 : 0; + int idx = windex & 0x7F; + + if (windex >= 0x100) + return NULL; + + if (idx > hsotg->num_of_eps) + return NULL; + + if (idx && ep->dir_in != dir) + return NULL; + + return ep; +} + +/** + * s3c_hsotg_send_reply - send reply to control request + * @hsotg: The device state + * @ep: Endpoint 0 + * @buff: Buffer for request + * @length: Length of reply. + * + * Create a request and queue it on the given endpoint. This is useful as + * an internal method of sending replies to certain control requests, etc. + */ +static int s3c_hsotg_send_reply(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *ep, + void *buff, + int length) +{ + struct usb_request *req; + int ret; + + dev_dbg(hsotg->dev, "%s: buff %p, len %d\n", __func__, buff, length); + + req = s3c_hsotg_ep_alloc_request(&ep->ep, GFP_ATOMIC); + hsotg->ep0_reply = req; + if (!req) { + dev_warn(hsotg->dev, "%s: cannot alloc req\n", __func__); + return -ENOMEM; + } + + req->buf = hsotg->ep0_buff; + req->length = length; + req->zero = 1; /* always do zero-length final transfer */ + req->complete = s3c_hsotg_complete_oursetup; + + if (length) + memcpy(req->buf, buff, length); + else + ep->sent_zlp = 1; + + ret = s3c_hsotg_ep_queue(&ep->ep, req, GFP_ATOMIC); + if (ret) { + dev_warn(hsotg->dev, "%s: cannot queue req\n", __func__); + return ret; + } + + return 0; +} + +/** + * s3c_hsotg_process_req_status - process request GET_STATUS + * @hsotg: The device state + * @ctrl: USB control request + */ +static int s3c_hsotg_process_req_status(struct s3c_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep; + __le16 reply; + int ret; + + dev_dbg(hsotg->dev, "%s: USB_REQ_GET_STATUS\n", __func__); + + if (!ep0->dir_in) { + dev_warn(hsotg->dev, "%s: direction out?\n", __func__); + return -EINVAL; + } + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + reply = cpu_to_le16(0); /* bit 0 => self powered, + * bit 1 => remote wakeup */ + break; + + case USB_RECIP_INTERFACE: + /* currently, the data result should be zero */ + reply = cpu_to_le16(0); + break; + + case USB_RECIP_ENDPOINT: + ep = ep_from_windex(hsotg, le16_to_cpu(ctrl->wIndex)); + if (!ep) + return -ENOENT; + + reply = cpu_to_le16(ep->halted ? 1 : 0); + break; + + default: + return 0; + } + + if (le16_to_cpu(ctrl->wLength) != 2) + return -EINVAL; + + ret = s3c_hsotg_send_reply(hsotg, ep0, &reply, 2); + if (ret) { + dev_err(hsotg->dev, "%s: failed to send reply\n", __func__); + return ret; + } + + return 1; +} + +static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value); + +/** + * get_ep_head - return the first request on the endpoint + * @hs_ep: The controller endpoint to get + * + * Get the first request on the endpoint. + */ +static struct s3c_hsotg_req *get_ep_head(struct s3c_hsotg_ep *hs_ep) +{ + if (list_empty(&hs_ep->queue)) + return NULL; + + return list_first_entry(&hs_ep->queue, struct s3c_hsotg_req, queue); +} + +/** + * s3c_hsotg_process_req_featire - process request {SET,CLEAR}_FEATURE + * @hsotg: The device state + * @ctrl: USB control request + */ +static int s3c_hsotg_process_req_feature(struct s3c_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_req *hs_req; + bool restart; + bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE); + struct s3c_hsotg_ep *ep; + int ret; + + dev_dbg(hsotg->dev, "%s: %s_FEATURE\n", + __func__, set ? "SET" : "CLEAR"); + + if (ctrl->bRequestType == USB_RECIP_ENDPOINT) { + ep = ep_from_windex(hsotg, le16_to_cpu(ctrl->wIndex)); + if (!ep) { + dev_dbg(hsotg->dev, "%s: no endpoint for 0x%04x\n", + __func__, le16_to_cpu(ctrl->wIndex)); + return -ENOENT; + } + + switch (le16_to_cpu(ctrl->wValue)) { + case USB_ENDPOINT_HALT: + s3c_hsotg_ep_sethalt(&ep->ep, set); + + ret = s3c_hsotg_send_reply(hsotg, ep0, NULL, 0); + if (ret) { + dev_err(hsotg->dev, + "%s: failed to send reply\n", __func__); + return ret; + } + + if (!set) { + /* + * If we have request in progress, + * then complete it + */ + if (ep->req) { + hs_req = ep->req; + ep->req = NULL; + list_del_init(&hs_req->queue); + hs_req->req.complete(&ep->ep, + &hs_req->req); + } + + /* If we have pending request, then start it */ + restart = !list_empty(&ep->queue); + if (restart) { + hs_req = get_ep_head(ep); + s3c_hsotg_start_req(hsotg, ep, + hs_req, false); + } + } + + break; + + default: + return -ENOENT; + } + } else + return -ENOENT; /* currently only deal with endpoint */ + + return 1; +} + +/** + * s3c_hsotg_process_control - process a control request + * @hsotg: The device state + * @ctrl: The control request received + * + * The controller has received the SETUP phase of a control request, and + * needs to work out what to do next (and whether to pass it on to the + * gadget driver). + */ +static void s3c_hsotg_process_control(struct s3c_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + int ret = 0; + u32 dcfg; + + ep0->sent_zlp = 0; + + dev_dbg(hsotg->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n", + ctrl->bRequest, ctrl->bRequestType, + ctrl->wValue, ctrl->wLength); + + /* + * record the direction of the request, for later use when enquing + * packets onto EP0. + */ + + ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0; + dev_dbg(hsotg->dev, "ctrl: dir_in=%d\n", ep0->dir_in); + + /* + * if we've no data with this request, then the last part of the + * transaction is going to implicitly be IN. + */ + if (ctrl->wLength == 0) + ep0->dir_in = 1; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + dcfg = readl(hsotg->regs + DCFG); + dcfg &= ~DCFG_DevAddr_MASK; + dcfg |= ctrl->wValue << DCFG_DevAddr_SHIFT; + writel(dcfg, hsotg->regs + DCFG); + + dev_info(hsotg->dev, "new address %d\n", ctrl->wValue); + + ret = s3c_hsotg_send_reply(hsotg, ep0, NULL, 0); + return; + + case USB_REQ_GET_STATUS: + ret = s3c_hsotg_process_req_status(hsotg, ctrl); + break; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + ret = s3c_hsotg_process_req_feature(hsotg, ctrl); + break; + } + } + + /* as a fallback, try delivering it to the driver to deal with */ + + if (ret == 0 && hsotg->driver) { + ret = hsotg->driver->setup(&hsotg->gadget, ctrl); + if (ret < 0) + dev_dbg(hsotg->dev, "driver->setup() ret %d\n", ret); + } + + /* + * the request is either unhandlable, or is not formatted correctly + * so respond with a STALL for the status stage to indicate failure. + */ + + if (ret < 0) { + u32 reg; + u32 ctrl; + + dev_dbg(hsotg->dev, "ep0 stall (dir=%d)\n", ep0->dir_in); + reg = (ep0->dir_in) ? DIEPCTL0 : DOEPCTL0; + + /* + * DxEPCTL_Stall will be cleared by EP once it has + * taken effect, so no need to clear later. + */ + + ctrl = readl(hsotg->regs + reg); + ctrl |= DxEPCTL_Stall; + ctrl |= DxEPCTL_CNAK; + writel(ctrl, hsotg->regs + reg); + + dev_dbg(hsotg->dev, + "written DxEPCTL=0x%08x to %08x (DxEPCTL=0x%08x)\n", + ctrl, reg, readl(hsotg->regs + reg)); + + /* + * don't believe we need to anything more to get the EP + * to reply with a STALL packet + */ + } +} + +static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg); + +/** + * s3c_hsotg_complete_setup - completion of a setup transfer + * @ep: The endpoint the request was on. + * @req: The request completed. + * + * Called on completion of any requests the driver itself submitted for + * EP0 setup packets + */ +static void s3c_hsotg_complete_setup(struct usb_ep *ep, + struct usb_request *req) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hsotg = hs_ep->parent; + + if (req->status < 0) { + dev_dbg(hsotg->dev, "%s: failed %d\n", __func__, req->status); + return; + } + + if (req->actual == 0) + s3c_hsotg_enqueue_setup(hsotg); + else + s3c_hsotg_process_control(hsotg, req->buf); +} + +/** + * s3c_hsotg_enqueue_setup - start a request for EP0 packets + * @hsotg: The device state. + * + * Enqueue a request on EP0 if necessary to received any SETUP packets + * received from the host. + */ +static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg) +{ + struct usb_request *req = hsotg->ctrl_req; + struct s3c_hsotg_req *hs_req = our_req(req); + int ret; + + dev_dbg(hsotg->dev, "%s: queueing setup request\n", __func__); + + req->zero = 0; + req->length = 8; + req->buf = hsotg->ctrl_buff; + req->complete = s3c_hsotg_complete_setup; + + if (!list_empty(&hs_req->queue)) { + dev_dbg(hsotg->dev, "%s already queued???\n", __func__); + return; + } + + hsotg->eps[0].dir_in = 0; + + ret = s3c_hsotg_ep_queue(&hsotg->eps[0].ep, req, GFP_ATOMIC); + if (ret < 0) { + dev_err(hsotg->dev, "%s: failed queue (%d)\n", __func__, ret); + /* + * Don't think there's much we can do other than watch the + * driver fail. + */ + } +} + +/** + * s3c_hsotg_complete_request - complete a request given to us + * @hsotg: The device state. + * @hs_ep: The endpoint the request was on. + * @hs_req: The request to complete. + * @result: The result code (0 => Ok, otherwise errno) + * + * The given request has finished, so call the necessary completion + * if it has one and then look to see if we can start a new request + * on the endpoint. + * + * Note, expects the ep to already be locked as appropriate. + */ +static void s3c_hsotg_complete_request(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req, + int result) +{ + bool restart; + + if (!hs_req) { + dev_dbg(hsotg->dev, "%s: nothing to complete?\n", __func__); + return; + } + + dev_dbg(hsotg->dev, "complete: ep %p %s, req %p, %d => %p\n", + hs_ep, hs_ep->ep.name, hs_req, result, hs_req->req.complete); + + /* + * only replace the status if we've not already set an error + * from a previous transaction + */ + + if (hs_req->req.status == -EINPROGRESS) + hs_req->req.status = result; + + hs_ep->req = NULL; + list_del_init(&hs_req->queue); + + if (using_dma(hsotg)) + s3c_hsotg_unmap_dma(hsotg, hs_ep, hs_req); + + /* + * call the complete request with the locks off, just in case the + * request tries to queue more work for this endpoint. + */ + + if (hs_req->req.complete) { + spin_unlock(&hsotg->lock); + hs_req->req.complete(&hs_ep->ep, &hs_req->req); + spin_lock(&hsotg->lock); + } + + /* + * Look to see if there is anything else to do. Note, the completion + * of the previous request may have caused a new request to be started + * so be careful when doing this. + */ + + if (!hs_ep->req && result >= 0) { + restart = !list_empty(&hs_ep->queue); + if (restart) { + hs_req = get_ep_head(hs_ep); + s3c_hsotg_start_req(hsotg, hs_ep, hs_req, false); + } + } +} + +/** + * s3c_hsotg_rx_data - receive data from the FIFO for an endpoint + * @hsotg: The device state. + * @ep_idx: The endpoint index for the data + * @size: The size of data in the fifo, in bytes + * + * The FIFO status shows there is data to read from the FIFO for a given + * endpoint, so sort out whether we need to read the data into a request + * that has been made for that endpoint. + */ +static void s3c_hsotg_rx_data(struct s3c_hsotg *hsotg, int ep_idx, int size) +{ + struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep_idx]; + struct s3c_hsotg_req *hs_req = hs_ep->req; + void __iomem *fifo = hsotg->regs + EPFIFO(ep_idx); + int to_read; + int max_req; + int read_ptr; + + + if (!hs_req) { + u32 epctl = readl(hsotg->regs + DOEPCTL(ep_idx)); + int ptr; + + dev_warn(hsotg->dev, + "%s: FIFO %d bytes on ep%d but no req (DxEPCTl=0x%08x)\n", + __func__, size, ep_idx, epctl); + + /* dump the data from the FIFO, we've nothing we can do */ + for (ptr = 0; ptr < size; ptr += 4) + (void)readl(fifo); + + return; + } + + to_read = size; + read_ptr = hs_req->req.actual; + max_req = hs_req->req.length - read_ptr; + + dev_dbg(hsotg->dev, "%s: read %d/%d, done %d/%d\n", + __func__, to_read, max_req, read_ptr, hs_req->req.length); + + if (to_read > max_req) { + /* + * more data appeared than we where willing + * to deal with in this request. + */ + + /* currently we don't deal this */ + WARN_ON_ONCE(1); + } + + hs_ep->total_data += to_read; + hs_req->req.actual += to_read; + to_read = DIV_ROUND_UP(to_read, 4); + + /* + * note, we might over-write the buffer end by 3 bytes depending on + * alignment of the data. + */ + readsl(fifo, hs_req->req.buf + read_ptr, to_read); +} + +/** + * s3c_hsotg_send_zlp - send zero-length packet on control endpoint + * @hsotg: The device instance + * @req: The request currently on this endpoint + * + * Generate a zero-length IN packet request for terminating a SETUP + * transaction. + * + * Note, since we don't write any data to the TxFIFO, then it is + * currently believed that we do not need to wait for any space in + * the TxFIFO. + */ +static void s3c_hsotg_send_zlp(struct s3c_hsotg *hsotg, + struct s3c_hsotg_req *req) +{ + u32 ctrl; + + if (!req) { + dev_warn(hsotg->dev, "%s: no request?\n", __func__); + return; + } + + if (req->req.length == 0) { + hsotg->eps[0].sent_zlp = 1; + s3c_hsotg_enqueue_setup(hsotg); + return; + } + + hsotg->eps[0].dir_in = 1; + hsotg->eps[0].sent_zlp = 1; + + dev_dbg(hsotg->dev, "sending zero-length packet\n"); + + /* issue a zero-sized packet to terminate this */ + writel(DxEPTSIZ_MC(1) | DxEPTSIZ_PktCnt(1) | + DxEPTSIZ_XferSize(0), hsotg->regs + DIEPTSIZ(0)); + + ctrl = readl(hsotg->regs + DIEPCTL0); + ctrl |= DxEPCTL_CNAK; /* clear NAK set by core */ + ctrl |= DxEPCTL_EPEna; /* ensure ep enabled */ + ctrl |= DxEPCTL_USBActEp; + writel(ctrl, hsotg->regs + DIEPCTL0); +} + +/** + * s3c_hsotg_handle_outdone - handle receiving OutDone/SetupDone from RXFIFO + * @hsotg: The device instance + * @epnum: The endpoint received from + * @was_setup: Set if processing a SetupDone event. + * + * The RXFIFO has delivered an OutDone event, which means that the data + * transfer for an OUT endpoint has been completed, either by a short + * packet or by the finish of a transfer. + */ +static void s3c_hsotg_handle_outdone(struct s3c_hsotg *hsotg, + int epnum, bool was_setup) +{ + u32 epsize = readl(hsotg->regs + DOEPTSIZ(epnum)); + struct s3c_hsotg_ep *hs_ep = &hsotg->eps[epnum]; + struct s3c_hsotg_req *hs_req = hs_ep->req; + struct usb_request *req = &hs_req->req; + unsigned size_left = DxEPTSIZ_XferSize_GET(epsize); + int result = 0; + + if (!hs_req) { + dev_dbg(hsotg->dev, "%s: no request active\n", __func__); + return; + } + + if (using_dma(hsotg)) { + unsigned size_done; + + /* + * Calculate the size of the transfer by checking how much + * is left in the endpoint size register and then working it + * out from the amount we loaded for the transfer. + * + * We need to do this as DMA pointers are always 32bit aligned + * so may overshoot/undershoot the transfer. + */ + + size_done = hs_ep->size_loaded - size_left; + size_done += hs_ep->last_load; + + req->actual = size_done; + } + + /* if there is more request to do, schedule new transfer */ + if (req->actual < req->length && size_left == 0) { + s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); + return; + } else if (epnum == 0) { + /* + * After was_setup = 1 => + * set CNAK for non Setup requests + */ + hsotg->setup = was_setup ? 0 : 1; + } + + if (req->actual < req->length && req->short_not_ok) { + dev_dbg(hsotg->dev, "%s: got %d/%d (short not ok) => error\n", + __func__, req->actual, req->length); + + /* + * todo - what should we return here? there's no one else + * even bothering to check the status. + */ + } + + if (epnum == 0) { + /* + * Condition req->complete != s3c_hsotg_complete_setup says: + * send ZLP when we have an asynchronous request from gadget + */ + if (!was_setup && req->complete != s3c_hsotg_complete_setup) + s3c_hsotg_send_zlp(hsotg, hs_req); + } + + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, result); +} + +/** + * s3c_hsotg_read_frameno - read current frame number + * @hsotg: The device instance + * + * Return the current frame number + */ +static u32 s3c_hsotg_read_frameno(struct s3c_hsotg *hsotg) +{ + u32 dsts; + + dsts = readl(hsotg->regs + DSTS); + dsts &= DSTS_SOFFN_MASK; + dsts >>= DSTS_SOFFN_SHIFT; + + return dsts; +} + +/** + * s3c_hsotg_handle_rx - RX FIFO has data + * @hsotg: The device instance + * + * The IRQ handler has detected that the RX FIFO has some data in it + * that requires processing, so find out what is in there and do the + * appropriate read. + * + * The RXFIFO is a true FIFO, the packets coming out are still in packet + * chunks, so if you have x packets received on an endpoint you'll get x + * FIFO events delivered, each with a packet's worth of data in it. + * + * When using DMA, we should not be processing events from the RXFIFO + * as the actual data should be sent to the memory directly and we turn + * on the completion interrupts to get notifications of transfer completion. + */ +static void s3c_hsotg_handle_rx(struct s3c_hsotg *hsotg) +{ + u32 grxstsr = readl(hsotg->regs + GRXSTSP); + u32 epnum, status, size; + + WARN_ON(using_dma(hsotg)); + + epnum = grxstsr & GRXSTS_EPNum_MASK; + status = grxstsr & GRXSTS_PktSts_MASK; + + size = grxstsr & GRXSTS_ByteCnt_MASK; + size >>= GRXSTS_ByteCnt_SHIFT; + + if (1) + dev_dbg(hsotg->dev, "%s: GRXSTSP=0x%08x (%d@%d)\n", + __func__, grxstsr, size, epnum); + +#define __status(x) ((x) >> GRXSTS_PktSts_SHIFT) + + switch (status >> GRXSTS_PktSts_SHIFT) { + case __status(GRXSTS_PktSts_GlobalOutNAK): + dev_dbg(hsotg->dev, "GlobalOutNAK\n"); + break; + + case __status(GRXSTS_PktSts_OutDone): + dev_dbg(hsotg->dev, "OutDone (Frame=0x%08x)\n", + s3c_hsotg_read_frameno(hsotg)); + + if (!using_dma(hsotg)) + s3c_hsotg_handle_outdone(hsotg, epnum, false); + break; + + case __status(GRXSTS_PktSts_SetupDone): + dev_dbg(hsotg->dev, + "SetupDone (Frame=0x%08x, DOPEPCTL=0x%08x)\n", + s3c_hsotg_read_frameno(hsotg), + readl(hsotg->regs + DOEPCTL(0))); + + s3c_hsotg_handle_outdone(hsotg, epnum, true); + break; + + case __status(GRXSTS_PktSts_OutRX): + s3c_hsotg_rx_data(hsotg, epnum, size); + break; + + case __status(GRXSTS_PktSts_SetupRX): + dev_dbg(hsotg->dev, + "SetupRX (Frame=0x%08x, DOPEPCTL=0x%08x)\n", + s3c_hsotg_read_frameno(hsotg), + readl(hsotg->regs + DOEPCTL(0))); + + s3c_hsotg_rx_data(hsotg, epnum, size); + break; + + default: + dev_warn(hsotg->dev, "%s: unknown status %08x\n", + __func__, grxstsr); + + s3c_hsotg_dump(hsotg); + break; + } +} + +/** + * s3c_hsotg_ep0_mps - turn max packet size into register setting + * @mps: The maximum packet size in bytes. + */ +static u32 s3c_hsotg_ep0_mps(unsigned int mps) +{ + switch (mps) { + case 64: + return D0EPCTL_MPS_64; + case 32: + return D0EPCTL_MPS_32; + case 16: + return D0EPCTL_MPS_16; + case 8: + return D0EPCTL_MPS_8; + } + + /* bad max packet size, warn and return invalid result */ + WARN_ON(1); + return (u32)-1; +} + +/** + * s3c_hsotg_set_ep_maxpacket - set endpoint's max-packet field + * @hsotg: The driver state. + * @ep: The index number of the endpoint + * @mps: The maximum packet size in bytes + * + * Configure the maximum packet size for the given endpoint, updating + * the hardware control registers to reflect this. + */ +static void s3c_hsotg_set_ep_maxpacket(struct s3c_hsotg *hsotg, + unsigned int ep, unsigned int mps) +{ + struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep]; + void __iomem *regs = hsotg->regs; + u32 mpsval; + u32 reg; + + if (ep == 0) { + /* EP0 is a special case */ + mpsval = s3c_hsotg_ep0_mps(mps); + if (mpsval > 3) + goto bad_mps; + } else { + if (mps >= DxEPCTL_MPS_LIMIT+1) + goto bad_mps; + + mpsval = mps; + } + + hs_ep->ep.maxpacket = mps; + + /* + * update both the in and out endpoint controldir_ registers, even + * if one of the directions may not be in use. + */ + + reg = readl(regs + DIEPCTL(ep)); + reg &= ~DxEPCTL_MPS_MASK; + reg |= mpsval; + writel(reg, regs + DIEPCTL(ep)); + + if (ep) { + reg = readl(regs + DOEPCTL(ep)); + reg &= ~DxEPCTL_MPS_MASK; + reg |= mpsval; + writel(reg, regs + DOEPCTL(ep)); + } + + return; + +bad_mps: + dev_err(hsotg->dev, "ep%d: bad mps of %d\n", ep, mps); +} + +/** + * s3c_hsotg_txfifo_flush - flush Tx FIFO + * @hsotg: The driver state + * @idx: The index for the endpoint (0..15) + */ +static void s3c_hsotg_txfifo_flush(struct s3c_hsotg *hsotg, unsigned int idx) +{ + int timeout; + int val; + + writel(GRSTCTL_TxFNum(idx) | GRSTCTL_TxFFlsh, + hsotg->regs + GRSTCTL); + + /* wait until the fifo is flushed */ + timeout = 100; + + while (1) { + val = readl(hsotg->regs + GRSTCTL); + + if ((val & (GRSTCTL_TxFFlsh)) == 0) + break; + + if (--timeout == 0) { + dev_err(hsotg->dev, + "%s: timeout flushing fifo (GRSTCTL=%08x)\n", + __func__, val); + } + + udelay(1); + } +} + +/** + * s3c_hsotg_trytx - check to see if anything needs transmitting + * @hsotg: The driver state + * @hs_ep: The driver endpoint to check. + * + * Check to see if there is a request that has data to send, and if so + * make an attempt to write data into the FIFO. + */ +static int s3c_hsotg_trytx(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep) +{ + struct s3c_hsotg_req *hs_req = hs_ep->req; + + if (!hs_ep->dir_in || !hs_req) + return 0; + + if (hs_req->req.actual < hs_req->req.length) { + dev_dbg(hsotg->dev, "trying to write more for ep%d\n", + hs_ep->index); + return s3c_hsotg_write_fifo(hsotg, hs_ep, hs_req); + } + + return 0; +} + +/** + * s3c_hsotg_complete_in - complete IN transfer + * @hsotg: The device state. + * @hs_ep: The endpoint that has just completed. + * + * An IN transfer has been completed, update the transfer's state and then + * call the relevant completion routines. + */ +static void s3c_hsotg_complete_in(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep) +{ + struct s3c_hsotg_req *hs_req = hs_ep->req; + u32 epsize = readl(hsotg->regs + DIEPTSIZ(hs_ep->index)); + int size_left, size_done; + + if (!hs_req) { + dev_dbg(hsotg->dev, "XferCompl but no req\n"); + return; + } + + /* Finish ZLP handling for IN EP0 transactions */ + if (hsotg->eps[0].sent_zlp) { + dev_dbg(hsotg->dev, "zlp packet received\n"); + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + return; + } + + /* + * Calculate the size of the transfer by checking how much is left + * in the endpoint size register and then working it out from + * the amount we loaded for the transfer. + * + * We do this even for DMA, as the transfer may have incremented + * past the end of the buffer (DMA transfers are always 32bit + * aligned). + */ + + size_left = DxEPTSIZ_XferSize_GET(epsize); + + size_done = hs_ep->size_loaded - size_left; + size_done += hs_ep->last_load; + + if (hs_req->req.actual != size_done) + dev_dbg(hsotg->dev, "%s: adjusting size done %d => %d\n", + __func__, hs_req->req.actual, size_done); + + hs_req->req.actual = size_done; + dev_dbg(hsotg->dev, "req->length:%d req->actual:%d req->zero:%d\n", + hs_req->req.length, hs_req->req.actual, hs_req->req.zero); + + /* + * Check if dealing with Maximum Packet Size(MPS) IN transfer at EP0 + * When sent data is a multiple MPS size (e.g. 64B ,128B ,192B + * ,256B ... ), after last MPS sized packet send IN ZLP packet to + * inform the host that no more data is available. + * The state of req.zero member is checked to be sure that the value to + * send is smaller than wValue expected from host. + * Check req.length to NOT send another ZLP when the current one is + * under completion (the one for which this completion has been called). + */ + if (hs_req->req.length && hs_ep->index == 0 && hs_req->req.zero && + hs_req->req.length == hs_req->req.actual && + !(hs_req->req.length % hs_ep->ep.maxpacket)) { + + dev_dbg(hsotg->dev, "ep0 zlp IN packet sent\n"); + s3c_hsotg_send_zlp(hsotg, hs_req); + + return; + } + + if (!size_left && hs_req->req.actual < hs_req->req.length) { + dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); + s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); + } else + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); +} + +/** + * s3c_hsotg_epint - handle an in/out endpoint interrupt + * @hsotg: The driver state + * @idx: The index for the endpoint (0..15) + * @dir_in: Set if this is an IN endpoint + * + * Process and clear any interrupt pending for an individual endpoint + */ +static void s3c_hsotg_epint(struct s3c_hsotg *hsotg, unsigned int idx, + int dir_in) +{ + struct s3c_hsotg_ep *hs_ep = &hsotg->eps[idx]; + u32 epint_reg = dir_in ? DIEPINT(idx) : DOEPINT(idx); + u32 epctl_reg = dir_in ? DIEPCTL(idx) : DOEPCTL(idx); + u32 epsiz_reg = dir_in ? DIEPTSIZ(idx) : DOEPTSIZ(idx); + u32 ints; + + ints = readl(hsotg->regs + epint_reg); + + /* Clear endpoint interrupts */ + writel(ints, hsotg->regs + epint_reg); + + dev_dbg(hsotg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n", + __func__, idx, dir_in ? "in" : "out", ints); + + if (ints & DxEPINT_XferCompl) { + dev_dbg(hsotg->dev, + "%s: XferCompl: DxEPCTL=0x%08x, DxEPTSIZ=%08x\n", + __func__, readl(hsotg->regs + epctl_reg), + readl(hsotg->regs + epsiz_reg)); + + /* + * we get OutDone from the FIFO, so we only need to look + * at completing IN requests here + */ + if (dir_in) { + s3c_hsotg_complete_in(hsotg, hs_ep); + + if (idx == 0 && !hs_ep->req) + s3c_hsotg_enqueue_setup(hsotg); + } else if (using_dma(hsotg)) { + /* + * We're using DMA, we need to fire an OutDone here + * as we ignore the RXFIFO. + */ + + s3c_hsotg_handle_outdone(hsotg, idx, false); + } + } + + if (ints & DxEPINT_EPDisbld) { + dev_dbg(hsotg->dev, "%s: EPDisbld\n", __func__); + + if (dir_in) { + int epctl = readl(hsotg->regs + epctl_reg); + + s3c_hsotg_txfifo_flush(hsotg, idx); + + if ((epctl & DxEPCTL_Stall) && + (epctl & DxEPCTL_EPType_Bulk)) { + int dctl = readl(hsotg->regs + DCTL); + + dctl |= DCTL_CGNPInNAK; + writel(dctl, hsotg->regs + DCTL); + } + } + } + + if (ints & DxEPINT_AHBErr) + dev_dbg(hsotg->dev, "%s: AHBErr\n", __func__); + + if (ints & DxEPINT_Setup) { /* Setup or Timeout */ + dev_dbg(hsotg->dev, "%s: Setup/Timeout\n", __func__); + + if (using_dma(hsotg) && idx == 0) { + /* + * this is the notification we've received a + * setup packet. In non-DMA mode we'd get this + * from the RXFIFO, instead we need to process + * the setup here. + */ + + if (dir_in) + WARN_ON_ONCE(1); + else + s3c_hsotg_handle_outdone(hsotg, 0, true); + } + } + + if (ints & DxEPINT_Back2BackSetup) + dev_dbg(hsotg->dev, "%s: B2BSetup/INEPNakEff\n", __func__); + + if (dir_in) { + /* not sure if this is important, but we'll clear it anyway */ + if (ints & DIEPMSK_INTknTXFEmpMsk) { + dev_dbg(hsotg->dev, "%s: ep%d: INTknTXFEmpMsk\n", + __func__, idx); + } + + /* this probably means something bad is happening */ + if (ints & DIEPMSK_INTknEPMisMsk) { + dev_warn(hsotg->dev, "%s: ep%d: INTknEP\n", + __func__, idx); + } + + /* FIFO has space or is empty (see GAHBCFG) */ + if (hsotg->dedicated_fifos && + ints & DIEPMSK_TxFIFOEmpty) { + dev_dbg(hsotg->dev, "%s: ep%d: TxFIFOEmpty\n", + __func__, idx); + if (!using_dma(hsotg)) + s3c_hsotg_trytx(hsotg, hs_ep); + } + } +} + +/** + * s3c_hsotg_irq_enumdone - Handle EnumDone interrupt (enumeration done) + * @hsotg: The device state. + * + * Handle updating the device settings after the enumeration phase has + * been completed. + */ +static void s3c_hsotg_irq_enumdone(struct s3c_hsotg *hsotg) +{ + u32 dsts = readl(hsotg->regs + DSTS); + int ep0_mps = 0, ep_mps; + + /* + * This should signal the finish of the enumeration phase + * of the USB handshaking, so we should now know what rate + * we connected at. + */ + + dev_dbg(hsotg->dev, "EnumDone (DSTS=0x%08x)\n", dsts); + + /* + * note, since we're limited by the size of transfer on EP0, and + * it seems IN transfers must be a even number of packets we do + * not advertise a 64byte MPS on EP0. + */ + + /* catch both EnumSpd_FS and EnumSpd_FS48 */ + switch (dsts & DSTS_EnumSpd_MASK) { + case DSTS_EnumSpd_FS: + case DSTS_EnumSpd_FS48: + hsotg->gadget.speed = USB_SPEED_FULL; + ep0_mps = EP0_MPS_LIMIT; + ep_mps = 64; + break; + + case DSTS_EnumSpd_HS: + hsotg->gadget.speed = USB_SPEED_HIGH; + ep0_mps = EP0_MPS_LIMIT; + ep_mps = 512; + break; + + case DSTS_EnumSpd_LS: + hsotg->gadget.speed = USB_SPEED_LOW; + /* + * note, we don't actually support LS in this driver at the + * moment, and the documentation seems to imply that it isn't + * supported by the PHYs on some of the devices. + */ + break; + } + dev_info(hsotg->dev, "new device is %s\n", + usb_speed_string(hsotg->gadget.speed)); + + /* + * we should now know the maximum packet size for an + * endpoint, so set the endpoints to a default value. + */ + + if (ep0_mps) { + int i; + s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps); + for (i = 1; i < hsotg->num_of_eps; i++) + s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps); + } + + /* ensure after enumeration our EP0 is active */ + + s3c_hsotg_enqueue_setup(hsotg); + + dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + readl(hsotg->regs + DIEPCTL0), + readl(hsotg->regs + DOEPCTL0)); +} + +/** + * kill_all_requests - remove all requests from the endpoint's queue + * @hsotg: The device state. + * @ep: The endpoint the requests may be on. + * @result: The result code to use. + * @force: Force removal of any current requests + * + * Go through the requests on the given endpoint and mark them + * completed with the given result code. + */ +static void kill_all_requests(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *ep, + int result, bool force) +{ + struct s3c_hsotg_req *req, *treq; + + list_for_each_entry_safe(req, treq, &ep->queue, queue) { + /* + * currently, we can't do much about an already + * running request on an in endpoint + */ + + if (ep->req == req && ep->dir_in && !force) + continue; + + s3c_hsotg_complete_request(hsotg, ep, req, + result); + } +} + +#define call_gadget(_hs, _entry) \ + if ((_hs)->gadget.speed != USB_SPEED_UNKNOWN && \ + (_hs)->driver && (_hs)->driver->_entry) { \ + spin_unlock(&_hs->lock); \ + (_hs)->driver->_entry(&(_hs)->gadget); \ + spin_lock(&_hs->lock); \ + } + +/** + * s3c_hsotg_disconnect - disconnect service + * @hsotg: The device state. + * + * The device has been disconnected. Remove all current + * transactions and signal the gadget driver that this + * has happened. + */ +static void s3c_hsotg_disconnect(struct s3c_hsotg *hsotg) +{ + unsigned ep; + + for (ep = 0; ep < hsotg->num_of_eps; ep++) + kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN, true); + + call_gadget(hsotg, disconnect); +} + +/** + * s3c_hsotg_irq_fifoempty - TX FIFO empty interrupt handler + * @hsotg: The device state: + * @periodic: True if this is a periodic FIFO interrupt + */ +static void s3c_hsotg_irq_fifoempty(struct s3c_hsotg *hsotg, bool periodic) +{ + struct s3c_hsotg_ep *ep; + int epno, ret; + + /* look through for any more data to transmit */ + + for (epno = 0; epno < hsotg->num_of_eps; epno++) { + ep = &hsotg->eps[epno]; + + if (!ep->dir_in) + continue; + + if ((periodic && !ep->periodic) || + (!periodic && ep->periodic)) + continue; + + ret = s3c_hsotg_trytx(hsotg, ep); + if (ret < 0) + break; + } +} + +/* IRQ flags which will trigger a retry around the IRQ loop */ +#define IRQ_RETRY_MASK (GINTSTS_NPTxFEmp | \ + GINTSTS_PTxFEmp | \ + GINTSTS_RxFLvl) + +/** + * s3c_hsotg_corereset - issue softreset to the core + * @hsotg: The device state + * + * Issue a soft reset to the core, and await the core finishing it. + */ +static int s3c_hsotg_corereset(struct s3c_hsotg *hsotg) +{ + int timeout; + u32 grstctl; + + dev_dbg(hsotg->dev, "resetting core\n"); + + /* issue soft reset */ + writel(GRSTCTL_CSftRst, hsotg->regs + GRSTCTL); + + timeout = 10000; + do { + grstctl = readl(hsotg->regs + GRSTCTL); + } while ((grstctl & GRSTCTL_CSftRst) && timeout-- > 0); + + if (grstctl & GRSTCTL_CSftRst) { + dev_err(hsotg->dev, "Failed to get CSftRst asserted\n"); + return -EINVAL; + } + + timeout = 10000; + + while (1) { + u32 grstctl = readl(hsotg->regs + GRSTCTL); + + if (timeout-- < 0) { + dev_info(hsotg->dev, + "%s: reset failed, GRSTCTL=%08x\n", + __func__, grstctl); + return -ETIMEDOUT; + } + + if (!(grstctl & GRSTCTL_AHBIdle)) + continue; + + break; /* reset done */ + } + + dev_dbg(hsotg->dev, "reset successful\n"); + return 0; +} + +/** + * s3c_hsotg_core_init - issue softreset to the core + * @hsotg: The device state + * + * Issue a soft reset to the core, and await the core finishing it. + */ +static void s3c_hsotg_core_init(struct s3c_hsotg *hsotg) +{ + s3c_hsotg_corereset(hsotg); + + /* + * we must now enable ep0 ready for host detection and then + * set configuration. + */ + + /* set the PLL on, remove the HNP/SRP and set the PHY */ + writel(GUSBCFG_PHYIf16 | GUSBCFG_TOutCal(7) | + (0x5 << 10), hsotg->regs + GUSBCFG); + + s3c_hsotg_init_fifo(hsotg); + + __orr32(hsotg->regs + DCTL, DCTL_SftDiscon); + + writel(1 << 18 | DCFG_DevSpd_HS, hsotg->regs + DCFG); + + /* Clear any pending OTG interrupts */ + writel(0xffffffff, hsotg->regs + GOTGINT); + + /* Clear any pending interrupts */ + writel(0xffffffff, hsotg->regs + GINTSTS); + + writel(GINTSTS_ErlySusp | GINTSTS_SessReqInt | + GINTSTS_GOUTNakEff | GINTSTS_GINNakEff | + GINTSTS_ConIDStsChng | GINTSTS_USBRst | + GINTSTS_EnumDone | GINTSTS_OTGInt | + GINTSTS_USBSusp | GINTSTS_WkUpInt, + hsotg->regs + GINTMSK); + + if (using_dma(hsotg)) + writel(GAHBCFG_GlblIntrEn | GAHBCFG_DMAEn | + GAHBCFG_HBstLen_Incr4, + hsotg->regs + GAHBCFG); + else + writel(GAHBCFG_GlblIntrEn, hsotg->regs + GAHBCFG); + + /* + * Enabling INTknTXFEmpMsk here seems to be a big mistake, we end + * up being flooded with interrupts if the host is polling the + * endpoint to try and read data. + */ + + writel(((hsotg->dedicated_fifos) ? DIEPMSK_TxFIFOEmpty : 0) | + DIEPMSK_EPDisbldMsk | DIEPMSK_XferComplMsk | + DIEPMSK_TimeOUTMsk | DIEPMSK_AHBErrMsk | + DIEPMSK_INTknEPMisMsk, + hsotg->regs + DIEPMSK); + + /* + * don't need XferCompl, we get that from RXFIFO in slave mode. In + * DMA mode we may need this. + */ + writel((using_dma(hsotg) ? (DIEPMSK_XferComplMsk | + DIEPMSK_TimeOUTMsk) : 0) | + DOEPMSK_EPDisbldMsk | DOEPMSK_AHBErrMsk | + DOEPMSK_SetupMsk, + hsotg->regs + DOEPMSK); + + writel(0, hsotg->regs + DAINTMSK); + + dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + readl(hsotg->regs + DIEPCTL0), + readl(hsotg->regs + DOEPCTL0)); + + /* enable in and out endpoint interrupts */ + s3c_hsotg_en_gsint(hsotg, GINTSTS_OEPInt | GINTSTS_IEPInt); + + /* + * Enable the RXFIFO when in slave mode, as this is how we collect + * the data. In DMA mode, we get events from the FIFO but also + * things we cannot process, so do not use it. + */ + if (!using_dma(hsotg)) + s3c_hsotg_en_gsint(hsotg, GINTSTS_RxFLvl); + + /* Enable interrupts for EP0 in and out */ + s3c_hsotg_ctrl_epint(hsotg, 0, 0, 1); + s3c_hsotg_ctrl_epint(hsotg, 0, 1, 1); + + __orr32(hsotg->regs + DCTL, DCTL_PWROnPrgDone); + udelay(10); /* see openiboot */ + __bic32(hsotg->regs + DCTL, DCTL_PWROnPrgDone); + + dev_dbg(hsotg->dev, "DCTL=0x%08x\n", readl(hsotg->regs + DCTL)); + + /* + * DxEPCTL_USBActEp says RO in manual, but seems to be set by + * writing to the EPCTL register.. + */ + + /* set to read 1 8byte packet */ + writel(DxEPTSIZ_MC(1) | DxEPTSIZ_PktCnt(1) | + DxEPTSIZ_XferSize(8), hsotg->regs + DOEPTSIZ0); + + writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | + DxEPCTL_CNAK | DxEPCTL_EPEna | + DxEPCTL_USBActEp, + hsotg->regs + DOEPCTL0); + + /* enable, but don't activate EP0in */ + writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | + DxEPCTL_USBActEp, hsotg->regs + DIEPCTL0); + + s3c_hsotg_enqueue_setup(hsotg); + + dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + readl(hsotg->regs + DIEPCTL0), + readl(hsotg->regs + DOEPCTL0)); + + /* clear global NAKs */ + writel(DCTL_CGOUTNak | DCTL_CGNPInNAK, + hsotg->regs + DCTL); + + /* must be at-least 3ms to allow bus to see disconnect */ + mdelay(3); + + /* remove the soft-disconnect and let's go */ + __bic32(hsotg->regs + DCTL, DCTL_SftDiscon); +} + +/** + * s3c_hsotg_irq - handle device interrupt + * @irq: The IRQ number triggered + * @pw: The pw value when registered the handler. + */ +static irqreturn_t s3c_hsotg_irq(int irq, void *pw) +{ + struct s3c_hsotg *hsotg = pw; + int retry_count = 8; + u32 gintsts; + u32 gintmsk; + + spin_lock(&hsotg->lock); +irq_retry: + gintsts = readl(hsotg->regs + GINTSTS); + gintmsk = readl(hsotg->regs + GINTMSK); + + dev_dbg(hsotg->dev, "%s: %08x %08x (%08x) retry %d\n", + __func__, gintsts, gintsts & gintmsk, gintmsk, retry_count); + + gintsts &= gintmsk; + + if (gintsts & GINTSTS_OTGInt) { + u32 otgint = readl(hsotg->regs + GOTGINT); + + dev_info(hsotg->dev, "OTGInt: %08x\n", otgint); + + writel(otgint, hsotg->regs + GOTGINT); + } + + if (gintsts & GINTSTS_SessReqInt) { + dev_dbg(hsotg->dev, "%s: SessReqInt\n", __func__); + writel(GINTSTS_SessReqInt, hsotg->regs + GINTSTS); + } + + if (gintsts & GINTSTS_EnumDone) { + writel(GINTSTS_EnumDone, hsotg->regs + GINTSTS); + + s3c_hsotg_irq_enumdone(hsotg); + } + + if (gintsts & GINTSTS_ConIDStsChng) { + dev_dbg(hsotg->dev, "ConIDStsChg (DSTS=0x%08x, GOTCTL=%08x)\n", + readl(hsotg->regs + DSTS), + readl(hsotg->regs + GOTGCTL)); + + writel(GINTSTS_ConIDStsChng, hsotg->regs + GINTSTS); + } + + if (gintsts & (GINTSTS_OEPInt | GINTSTS_IEPInt)) { + u32 daint = readl(hsotg->regs + DAINT); + u32 daint_out = daint >> DAINT_OutEP_SHIFT; + u32 daint_in = daint & ~(daint_out << DAINT_OutEP_SHIFT); + int ep; + + dev_dbg(hsotg->dev, "%s: daint=%08x\n", __func__, daint); + + for (ep = 0; ep < 15 && daint_out; ep++, daint_out >>= 1) { + if (daint_out & 1) + s3c_hsotg_epint(hsotg, ep, 0); + } + + for (ep = 0; ep < 15 && daint_in; ep++, daint_in >>= 1) { + if (daint_in & 1) + s3c_hsotg_epint(hsotg, ep, 1); + } + } + + if (gintsts & GINTSTS_USBRst) { + + u32 usb_status = readl(hsotg->regs + GOTGCTL); + + dev_info(hsotg->dev, "%s: USBRst\n", __func__); + dev_dbg(hsotg->dev, "GNPTXSTS=%08x\n", + readl(hsotg->regs + GNPTXSTS)); + + writel(GINTSTS_USBRst, hsotg->regs + GINTSTS); + + if (usb_status & GOTGCTL_BSESVLD) { + if (time_after(jiffies, hsotg->last_rst + + msecs_to_jiffies(200))) { + + kill_all_requests(hsotg, &hsotg->eps[0], + -ECONNRESET, true); + + s3c_hsotg_core_init(hsotg); + hsotg->last_rst = jiffies; + } + } + } + + /* check both FIFOs */ + + if (gintsts & GINTSTS_NPTxFEmp) { + dev_dbg(hsotg->dev, "NPTxFEmp\n"); + + /* + * Disable the interrupt to stop it happening again + * unless one of these endpoint routines decides that + * it needs re-enabling + */ + + s3c_hsotg_disable_gsint(hsotg, GINTSTS_NPTxFEmp); + s3c_hsotg_irq_fifoempty(hsotg, false); + } + + if (gintsts & GINTSTS_PTxFEmp) { + dev_dbg(hsotg->dev, "PTxFEmp\n"); + + /* See note in GINTSTS_NPTxFEmp */ + + s3c_hsotg_disable_gsint(hsotg, GINTSTS_PTxFEmp); + s3c_hsotg_irq_fifoempty(hsotg, true); + } + + if (gintsts & GINTSTS_RxFLvl) { + /* + * note, since GINTSTS_RxFLvl doubles as FIFO-not-empty, + * we need to retry s3c_hsotg_handle_rx if this is still + * set. + */ + + s3c_hsotg_handle_rx(hsotg); + } + + if (gintsts & GINTSTS_ModeMis) { + dev_warn(hsotg->dev, "warning, mode mismatch triggered\n"); + writel(GINTSTS_ModeMis, hsotg->regs + GINTSTS); + } + + if (gintsts & GINTSTS_USBSusp) { + dev_info(hsotg->dev, "GINTSTS_USBSusp\n"); + writel(GINTSTS_USBSusp, hsotg->regs + GINTSTS); + + call_gadget(hsotg, suspend); + s3c_hsotg_disconnect(hsotg); + } + + if (gintsts & GINTSTS_WkUpInt) { + dev_info(hsotg->dev, "GINTSTS_WkUpIn\n"); + writel(GINTSTS_WkUpInt, hsotg->regs + GINTSTS); + + call_gadget(hsotg, resume); + } + + if (gintsts & GINTSTS_ErlySusp) { + dev_dbg(hsotg->dev, "GINTSTS_ErlySusp\n"); + writel(GINTSTS_ErlySusp, hsotg->regs + GINTSTS); + + s3c_hsotg_disconnect(hsotg); + } + + /* + * these next two seem to crop-up occasionally causing the core + * to shutdown the USB transfer, so try clearing them and logging + * the occurrence. + */ + + if (gintsts & GINTSTS_GOUTNakEff) { + dev_info(hsotg->dev, "GOUTNakEff triggered\n"); + + writel(DCTL_CGOUTNak, hsotg->regs + DCTL); + + s3c_hsotg_dump(hsotg); + } + + if (gintsts & GINTSTS_GINNakEff) { + dev_info(hsotg->dev, "GINNakEff triggered\n"); + + writel(DCTL_CGNPInNAK, hsotg->regs + DCTL); + + s3c_hsotg_dump(hsotg); + } + + /* + * if we've had fifo events, we should try and go around the + * loop again to see if there's any point in returning yet. + */ + + if (gintsts & IRQ_RETRY_MASK && --retry_count > 0) + goto irq_retry; + + spin_unlock(&hsotg->lock); + + return IRQ_HANDLED; +} + +/** + * s3c_hsotg_ep_enable - enable the given endpoint + * @ep: The USB endpint to configure + * @desc: The USB endpoint descriptor to configure with. + * + * This is called from the USB gadget code's usb_ep_enable(). + */ +static int s3c_hsotg_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hsotg = hs_ep->parent; + unsigned long flags; + int index = hs_ep->index; + u32 epctrl_reg; + u32 epctrl; + u32 mps; + int dir_in; + int ret = 0; + + dev_dbg(hsotg->dev, + "%s: ep %s: a 0x%02x, attr 0x%02x, mps 0x%04x, intr %d\n", + __func__, ep->name, desc->bEndpointAddress, desc->bmAttributes, + desc->wMaxPacketSize, desc->bInterval); + + /* not to be called for EP0 */ + WARN_ON(index == 0); + + dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0; + if (dir_in != hs_ep->dir_in) { + dev_err(hsotg->dev, "%s: direction mismatch!\n", __func__); + return -EINVAL; + } + + mps = usb_endpoint_maxp(desc); + + /* note, we handle this here instead of s3c_hsotg_set_ep_maxpacket */ + + epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); + epctrl = readl(hsotg->regs + epctrl_reg); + + dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x from 0x%08x\n", + __func__, epctrl, epctrl_reg); + + spin_lock_irqsave(&hsotg->lock, flags); + + epctrl &= ~(DxEPCTL_EPType_MASK | DxEPCTL_MPS_MASK); + epctrl |= DxEPCTL_MPS(mps); + + /* + * mark the endpoint as active, otherwise the core may ignore + * transactions entirely for this endpoint + */ + epctrl |= DxEPCTL_USBActEp; + + /* + * set the NAK status on the endpoint, otherwise we might try and + * do something with data that we've yet got a request to process + * since the RXFIFO will take data for an endpoint even if the + * size register hasn't been set. + */ + + epctrl |= DxEPCTL_SNAK; + + /* update the endpoint state */ + hs_ep->ep.maxpacket = mps; + + /* default, set to non-periodic */ + hs_ep->periodic = 0; + + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_ISOC: + dev_err(hsotg->dev, "no current ISOC support\n"); + ret = -EINVAL; + goto out; + + case USB_ENDPOINT_XFER_BULK: + epctrl |= DxEPCTL_EPType_Bulk; + break; + + case USB_ENDPOINT_XFER_INT: + if (dir_in) { + /* + * Allocate our TxFNum by simply using the index + * of the endpoint for the moment. We could do + * something better if the host indicates how + * many FIFOs we are expecting to use. + */ + + hs_ep->periodic = 1; + epctrl |= DxEPCTL_TxFNum(index); + } + + epctrl |= DxEPCTL_EPType_Intterupt; + break; + + case USB_ENDPOINT_XFER_CONTROL: + epctrl |= DxEPCTL_EPType_Control; + break; + } + + /* + * if the hardware has dedicated fifos, we must give each IN EP + * a unique tx-fifo even if it is non-periodic. + */ + if (dir_in && hsotg->dedicated_fifos) + epctrl |= DxEPCTL_TxFNum(index); + + /* for non control endpoints, set PID to D0 */ + if (index) + epctrl |= DxEPCTL_SetD0PID; + + dev_dbg(hsotg->dev, "%s: write DxEPCTL=0x%08x\n", + __func__, epctrl); + + writel(epctrl, hsotg->regs + epctrl_reg); + dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x\n", + __func__, readl(hsotg->regs + epctrl_reg)); + + /* enable the endpoint interrupt */ + s3c_hsotg_ctrl_epint(hsotg, index, dir_in, 1); + +out: + spin_unlock_irqrestore(&hsotg->lock, flags); + return ret; +} + +/** + * s3c_hsotg_ep_disable - disable given endpoint + * @ep: The endpoint to disable. + */ +static int s3c_hsotg_ep_disable(struct usb_ep *ep) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hsotg = hs_ep->parent; + int dir_in = hs_ep->dir_in; + int index = hs_ep->index; + unsigned long flags; + u32 epctrl_reg; + u32 ctrl; + + dev_info(hsotg->dev, "%s(ep %p)\n", __func__, ep); + + if (ep == &hsotg->eps[0].ep) { + dev_err(hsotg->dev, "%s: called for ep0\n", __func__); + return -EINVAL; + } + + epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); + + spin_lock_irqsave(&hsotg->lock, flags); + /* terminate all requests with shutdown */ + kill_all_requests(hsotg, hs_ep, -ESHUTDOWN, false); + + + ctrl = readl(hsotg->regs + epctrl_reg); + ctrl &= ~DxEPCTL_EPEna; + ctrl &= ~DxEPCTL_USBActEp; + ctrl |= DxEPCTL_SNAK; + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); + writel(ctrl, hsotg->regs + epctrl_reg); + + /* disable endpoint interrupts */ + s3c_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 0); + + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; +} + +/** + * on_list - check request is on the given endpoint + * @ep: The endpoint to check. + * @test: The request to test if it is on the endpoint. + */ +static bool on_list(struct s3c_hsotg_ep *ep, struct s3c_hsotg_req *test) +{ + struct s3c_hsotg_req *req, *treq; + + list_for_each_entry_safe(req, treq, &ep->queue, queue) { + if (req == test) + return true; + } + + return false; +} + +/** + * s3c_hsotg_ep_dequeue - dequeue given endpoint + * @ep: The endpoint to dequeue. + * @req: The request to be removed from a queue. + */ +static int s3c_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct s3c_hsotg_req *hs_req = our_req(req); + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hs = hs_ep->parent; + unsigned long flags; + + dev_info(hs->dev, "ep_dequeue(%p,%p)\n", ep, req); + + spin_lock_irqsave(&hs->lock, flags); + + if (!on_list(hs_ep, hs_req)) { + spin_unlock_irqrestore(&hs->lock, flags); + return -EINVAL; + } + + s3c_hsotg_complete_request(hs, hs_ep, hs_req, -ECONNRESET); + spin_unlock_irqrestore(&hs->lock, flags); + + return 0; +} + +/** + * s3c_hsotg_ep_sethalt - set halt on a given endpoint + * @ep: The endpoint to set halt. + * @value: Set or unset the halt. + */ +static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hs = hs_ep->parent; + int index = hs_ep->index; + u32 epreg; + u32 epctl; + u32 xfertype; + + dev_info(hs->dev, "%s(ep %p %s, %d)\n", __func__, ep, ep->name, value); + + /* write both IN and OUT control registers */ + + epreg = DIEPCTL(index); + epctl = readl(hs->regs + epreg); + + if (value) { + epctl |= DxEPCTL_Stall + DxEPCTL_SNAK; + if (epctl & DxEPCTL_EPEna) + epctl |= DxEPCTL_EPDis; + } else { + epctl &= ~DxEPCTL_Stall; + xfertype = epctl & DxEPCTL_EPType_MASK; + if (xfertype == DxEPCTL_EPType_Bulk || + xfertype == DxEPCTL_EPType_Intterupt) + epctl |= DxEPCTL_SetD0PID; + } + + writel(epctl, hs->regs + epreg); + + epreg = DOEPCTL(index); + epctl = readl(hs->regs + epreg); + + if (value) + epctl |= DxEPCTL_Stall; + else { + epctl &= ~DxEPCTL_Stall; + xfertype = epctl & DxEPCTL_EPType_MASK; + if (xfertype == DxEPCTL_EPType_Bulk || + xfertype == DxEPCTL_EPType_Intterupt) + epctl |= DxEPCTL_SetD0PID; + } + + writel(epctl, hs->regs + epreg); + + return 0; +} + +/** + * s3c_hsotg_ep_sethalt_lock - set halt on a given endpoint with lock held + * @ep: The endpoint to set halt. + * @value: Set or unset the halt. + */ +static int s3c_hsotg_ep_sethalt_lock(struct usb_ep *ep, int value) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hs = hs_ep->parent; + unsigned long flags = 0; + int ret = 0; + + spin_lock_irqsave(&hs->lock, flags); + ret = s3c_hsotg_ep_sethalt(ep, value); + spin_unlock_irqrestore(&hs->lock, flags); + + return ret; +} + +static struct usb_ep_ops s3c_hsotg_ep_ops = { + .enable = s3c_hsotg_ep_enable, + .disable = s3c_hsotg_ep_disable, + .alloc_request = s3c_hsotg_ep_alloc_request, + .free_request = s3c_hsotg_ep_free_request, + .queue = s3c_hsotg_ep_queue_lock, + .dequeue = s3c_hsotg_ep_dequeue, + .set_halt = s3c_hsotg_ep_sethalt_lock, + /* note, don't believe we have any call for the fifo routines */ +}; + +/** + * s3c_hsotg_phy_enable - enable platform phy dev + * @hsotg: The driver state + * + * A wrapper for platform code responsible for controlling + * low-level USB code + */ +static void s3c_hsotg_phy_enable(struct s3c_hsotg *hsotg) +{ + struct platform_device *pdev = to_platform_device(hsotg->dev); + + dev_dbg(hsotg->dev, "pdev 0x%p\n", pdev); + + if (hsotg->phy) + usb_phy_init(hsotg->phy); + else if (hsotg->plat->phy_init) + hsotg->plat->phy_init(pdev, hsotg->plat->phy_type); +} + +/** + * s3c_hsotg_phy_disable - disable platform phy dev + * @hsotg: The driver state + * + * A wrapper for platform code responsible for controlling + * low-level USB code + */ +static void s3c_hsotg_phy_disable(struct s3c_hsotg *hsotg) +{ + struct platform_device *pdev = to_platform_device(hsotg->dev); + + if (hsotg->phy) + usb_phy_shutdown(hsotg->phy); + else if (hsotg->plat->phy_exit) + hsotg->plat->phy_exit(pdev, hsotg->plat->phy_type); +} + +/** + * s3c_hsotg_init - initalize the usb core + * @hsotg: The driver state + */ +static void s3c_hsotg_init(struct s3c_hsotg *hsotg) +{ + /* unmask subset of endpoint interrupts */ + + writel(DIEPMSK_TimeOUTMsk | DIEPMSK_AHBErrMsk | + DIEPMSK_EPDisbldMsk | DIEPMSK_XferComplMsk, + hsotg->regs + DIEPMSK); + + writel(DOEPMSK_SetupMsk | DOEPMSK_AHBErrMsk | + DOEPMSK_EPDisbldMsk | DOEPMSK_XferComplMsk, + hsotg->regs + DOEPMSK); + + writel(0, hsotg->regs + DAINTMSK); + + /* Be in disconnected state until gadget is registered */ + __orr32(hsotg->regs + DCTL, DCTL_SftDiscon); + + if (0) { + /* post global nak until we're ready */ + writel(DCTL_SGNPInNAK | DCTL_SGOUTNak, + hsotg->regs + DCTL); + } + + /* setup fifos */ + + dev_dbg(hsotg->dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", + readl(hsotg->regs + GRXFSIZ), + readl(hsotg->regs + GNPTXFSIZ)); + + s3c_hsotg_init_fifo(hsotg); + + /* set the PLL on, remove the HNP/SRP and set the PHY */ + writel(GUSBCFG_PHYIf16 | GUSBCFG_TOutCal(7) | (0x5 << 10), + hsotg->regs + GUSBCFG); + + writel(using_dma(hsotg) ? GAHBCFG_DMAEn : 0x0, + hsotg->regs + GAHBCFG); +} + +/** + * s3c_hsotg_udc_start - prepare the udc for work + * @gadget: The usb gadget state + * @driver: The usb gadget driver + * + * Perform initialization to prepare udc device and driver + * to work. + */ +static int s3c_hsotg_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct s3c_hsotg *hsotg = to_hsotg(gadget); + int ret; + + if (!hsotg) { + printk(KERN_ERR "%s: called with no device\n", __func__); + return -ENODEV; + } + + if (!driver) { + dev_err(hsotg->dev, "%s: no driver\n", __func__); + return -EINVAL; + } + + if (driver->max_speed < USB_SPEED_FULL) + dev_err(hsotg->dev, "%s: bad speed\n", __func__); + + if (!driver->setup) { + dev_err(hsotg->dev, "%s: missing entry points\n", __func__); + return -EINVAL; + } + + WARN_ON(hsotg->driver); + + driver->driver.bus = NULL; + hsotg->driver = driver; + hsotg->gadget.dev.of_node = hsotg->dev->of_node; + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + + ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + if (ret) { + dev_err(hsotg->dev, "failed to enable supplies: %d\n", ret); + goto err; + } + + hsotg->last_rst = jiffies; + dev_info(hsotg->dev, "bound driver %s\n", driver->driver.name); + return 0; + +err: + hsotg->driver = NULL; + return ret; +} + +/** + * s3c_hsotg_udc_stop - stop the udc + * @gadget: The usb gadget state + * @driver: The usb gadget driver + * + * Stop udc hw block and stay tunned for future transmissions + */ +static int s3c_hsotg_udc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct s3c_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags = 0; + int ep; + + if (!hsotg) + return -ENODEV; + + if (!driver || driver != hsotg->driver || !driver->unbind) + return -EINVAL; + + /* all endpoints should be shutdown */ + for (ep = 0; ep < hsotg->num_of_eps; ep++) + s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + + spin_lock_irqsave(&hsotg->lock, flags); + + s3c_hsotg_phy_disable(hsotg); + regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); + + hsotg->driver = NULL; + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + + spin_unlock_irqrestore(&hsotg->lock, flags); + + dev_info(hsotg->dev, "unregistered gadget driver '%s'\n", + driver->driver.name); + + return 0; +} + +/** + * s3c_hsotg_gadget_getframe - read the frame number + * @gadget: The usb gadget state + * + * Read the {micro} frame number + */ +static int s3c_hsotg_gadget_getframe(struct usb_gadget *gadget) +{ + return s3c_hsotg_read_frameno(to_hsotg(gadget)); +} + +/** + * s3c_hsotg_pullup - connect/disconnect the USB PHY + * @gadget: The usb gadget state + * @is_on: Current state of the USB PHY + * + * Connect/Disconnect the USB PHY pullup + */ +static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) +{ + struct s3c_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags = 0; + + dev_dbg(hsotg->dev, "%s: is_in: %d\n", __func__, is_on); + + spin_lock_irqsave(&hsotg->lock, flags); + if (is_on) { + s3c_hsotg_phy_enable(hsotg); + s3c_hsotg_core_init(hsotg); + } else { + s3c_hsotg_disconnect(hsotg); + s3c_hsotg_phy_disable(hsotg); + } + + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { + .get_frame = s3c_hsotg_gadget_getframe, + .udc_start = s3c_hsotg_udc_start, + .udc_stop = s3c_hsotg_udc_stop, + .pullup = s3c_hsotg_pullup, +}; + +/** + * s3c_hsotg_initep - initialise a single endpoint + * @hsotg: The device state. + * @hs_ep: The endpoint to be initialised. + * @epnum: The endpoint number + * + * Initialise the given endpoint (as part of the probe and device state + * creation) to give to the gadget driver. Setup the endpoint name, any + * direction information and other state that may be required. + */ +static void s3c_hsotg_initep(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + int epnum) +{ + u32 ptxfifo; + char *dir; + + if (epnum == 0) + dir = ""; + else if ((epnum % 2) == 0) { + dir = "out"; + } else { + dir = "in"; + hs_ep->dir_in = 1; + } + + hs_ep->index = epnum; + + snprintf(hs_ep->name, sizeof(hs_ep->name), "ep%d%s", epnum, dir); + + INIT_LIST_HEAD(&hs_ep->queue); + INIT_LIST_HEAD(&hs_ep->ep.ep_list); + + /* add to the list of endpoints known by the gadget driver */ + if (epnum) + list_add_tail(&hs_ep->ep.ep_list, &hsotg->gadget.ep_list); + + hs_ep->parent = hsotg; + hs_ep->ep.name = hs_ep->name; + hs_ep->ep.maxpacket = epnum ? 512 : EP0_MPS_LIMIT; + hs_ep->ep.ops = &s3c_hsotg_ep_ops; + + /* + * Read the FIFO size for the Periodic TX FIFO, even if we're + * an OUT endpoint, we may as well do this if in future the + * code is changed to make each endpoint's direction changeable. + */ + + ptxfifo = readl(hsotg->regs + DPTXFSIZn(epnum)); + hs_ep->fifo_size = DPTXFSIZn_DPTxFSize_GET(ptxfifo) * 4; + + /* + * if we're using dma, we need to set the next-endpoint pointer + * to be something valid. + */ + + if (using_dma(hsotg)) { + u32 next = DxEPCTL_NextEp((epnum + 1) % 15); + writel(next, hsotg->regs + DIEPCTL(epnum)); + writel(next, hsotg->regs + DOEPCTL(epnum)); + } +} + +/** + * s3c_hsotg_hw_cfg - read HW configuration registers + * @param: The device state + * + * Read the USB core HW configuration registers + */ +static void s3c_hsotg_hw_cfg(struct s3c_hsotg *hsotg) +{ + u32 cfg2, cfg4; + /* check hardware configuration */ + + cfg2 = readl(hsotg->regs + 0x48); + hsotg->num_of_eps = (cfg2 >> 10) & 0xF; + + dev_info(hsotg->dev, "EPs:%d\n", hsotg->num_of_eps); + + cfg4 = readl(hsotg->regs + 0x50); + hsotg->dedicated_fifos = (cfg4 >> 25) & 1; + + dev_info(hsotg->dev, "%s fifos\n", + hsotg->dedicated_fifos ? "dedicated" : "shared"); +} + +/** + * s3c_hsotg_dump - dump state of the udc + * @param: The device state + */ +static void s3c_hsotg_dump(struct s3c_hsotg *hsotg) +{ +#ifdef DEBUG + struct device *dev = hsotg->dev; + void __iomem *regs = hsotg->regs; + u32 val; + int idx; + + dev_info(dev, "DCFG=0x%08x, DCTL=0x%08x, DIEPMSK=%08x\n", + readl(regs + DCFG), readl(regs + DCTL), + readl(regs + DIEPMSK)); + + dev_info(dev, "GAHBCFG=0x%08x, 0x44=0x%08x\n", + readl(regs + GAHBCFG), readl(regs + 0x44)); + + dev_info(dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", + readl(regs + GRXFSIZ), readl(regs + GNPTXFSIZ)); + + /* show periodic fifo settings */ + + for (idx = 1; idx <= 15; idx++) { + val = readl(regs + DPTXFSIZn(idx)); + dev_info(dev, "DPTx[%d] FSize=%d, StAddr=0x%08x\n", idx, + val >> DPTXFSIZn_DPTxFSize_SHIFT, + val & DPTXFSIZn_DPTxFStAddr_MASK); + } + + for (idx = 0; idx < 15; idx++) { + dev_info(dev, + "ep%d-in: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", idx, + readl(regs + DIEPCTL(idx)), + readl(regs + DIEPTSIZ(idx)), + readl(regs + DIEPDMA(idx))); + + val = readl(regs + DOEPCTL(idx)); + dev_info(dev, + "ep%d-out: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", + idx, readl(regs + DOEPCTL(idx)), + readl(regs + DOEPTSIZ(idx)), + readl(regs + DOEPDMA(idx))); + + } + + dev_info(dev, "DVBUSDIS=0x%08x, DVBUSPULSE=%08x\n", + readl(regs + DVBUSDIS), readl(regs + DVBUSPULSE)); +#endif +} + +/** + * state_show - debugfs: show overall driver and device state. + * @seq: The seq file to write to. + * @v: Unused parameter. + * + * This debugfs entry shows the overall state of the hardware and + * some general information about each of the endpoints available + * to the system. + */ +static int state_show(struct seq_file *seq, void *v) +{ + struct s3c_hsotg *hsotg = seq->private; + void __iomem *regs = hsotg->regs; + int idx; + + seq_printf(seq, "DCFG=0x%08x, DCTL=0x%08x, DSTS=0x%08x\n", + readl(regs + DCFG), + readl(regs + DCTL), + readl(regs + DSTS)); + + seq_printf(seq, "DIEPMSK=0x%08x, DOEPMASK=0x%08x\n", + readl(regs + DIEPMSK), readl(regs + DOEPMSK)); + + seq_printf(seq, "GINTMSK=0x%08x, GINTSTS=0x%08x\n", + readl(regs + GINTMSK), + readl(regs + GINTSTS)); + + seq_printf(seq, "DAINTMSK=0x%08x, DAINT=0x%08x\n", + readl(regs + DAINTMSK), + readl(regs + DAINT)); + + seq_printf(seq, "GNPTXSTS=0x%08x, GRXSTSR=%08x\n", + readl(regs + GNPTXSTS), + readl(regs + GRXSTSR)); + + seq_printf(seq, "\nEndpoint status:\n"); + + for (idx = 0; idx < 15; idx++) { + u32 in, out; + + in = readl(regs + DIEPCTL(idx)); + out = readl(regs + DOEPCTL(idx)); + + seq_printf(seq, "ep%d: DIEPCTL=0x%08x, DOEPCTL=0x%08x", + idx, in, out); + + in = readl(regs + DIEPTSIZ(idx)); + out = readl(regs + DOEPTSIZ(idx)); + + seq_printf(seq, ", DIEPTSIZ=0x%08x, DOEPTSIZ=0x%08x", + in, out); + + seq_printf(seq, "\n"); + } + + return 0; +} + +static int state_open(struct inode *inode, struct file *file) +{ + return single_open(file, state_show, inode->i_private); +} + +static const struct file_operations state_fops = { + .owner = THIS_MODULE, + .open = state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * fifo_show - debugfs: show the fifo information + * @seq: The seq_file to write data to. + * @v: Unused parameter. + * + * Show the FIFO information for the overall fifo and all the + * periodic transmission FIFOs. + */ +static int fifo_show(struct seq_file *seq, void *v) +{ + struct s3c_hsotg *hsotg = seq->private; + void __iomem *regs = hsotg->regs; + u32 val; + int idx; + + seq_printf(seq, "Non-periodic FIFOs:\n"); + seq_printf(seq, "RXFIFO: Size %d\n", readl(regs + GRXFSIZ)); + + val = readl(regs + GNPTXFSIZ); + seq_printf(seq, "NPTXFIFO: Size %d, Start 0x%08x\n", + val >> GNPTXFSIZ_NPTxFDep_SHIFT, + val & GNPTXFSIZ_NPTxFStAddr_MASK); + + seq_printf(seq, "\nPeriodic TXFIFOs:\n"); + + for (idx = 1; idx <= 15; idx++) { + val = readl(regs + DPTXFSIZn(idx)); + + seq_printf(seq, "\tDPTXFIFO%2d: Size %d, Start 0x%08x\n", idx, + val >> DPTXFSIZn_DPTxFSize_SHIFT, + val & DPTXFSIZn_DPTxFStAddr_MASK); + } + + return 0; +} + +static int fifo_open(struct inode *inode, struct file *file) +{ + return single_open(file, fifo_show, inode->i_private); +} + +static const struct file_operations fifo_fops = { + .owner = THIS_MODULE, + .open = fifo_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + + +static const char *decode_direction(int is_in) +{ + return is_in ? "in" : "out"; +} + +/** + * ep_show - debugfs: show the state of an endpoint. + * @seq: The seq_file to write data to. + * @v: Unused parameter. + * + * This debugfs entry shows the state of the given endpoint (one is + * registered for each available). + */ +static int ep_show(struct seq_file *seq, void *v) +{ + struct s3c_hsotg_ep *ep = seq->private; + struct s3c_hsotg *hsotg = ep->parent; + struct s3c_hsotg_req *req; + void __iomem *regs = hsotg->regs; + int index = ep->index; + int show_limit = 15; + unsigned long flags; + + seq_printf(seq, "Endpoint index %d, named %s, dir %s:\n", + ep->index, ep->ep.name, decode_direction(ep->dir_in)); + + /* first show the register state */ + + seq_printf(seq, "\tDIEPCTL=0x%08x, DOEPCTL=0x%08x\n", + readl(regs + DIEPCTL(index)), + readl(regs + DOEPCTL(index))); + + seq_printf(seq, "\tDIEPDMA=0x%08x, DOEPDMA=0x%08x\n", + readl(regs + DIEPDMA(index)), + readl(regs + DOEPDMA(index))); + + seq_printf(seq, "\tDIEPINT=0x%08x, DOEPINT=0x%08x\n", + readl(regs + DIEPINT(index)), + readl(regs + DOEPINT(index))); + + seq_printf(seq, "\tDIEPTSIZ=0x%08x, DOEPTSIZ=0x%08x\n", + readl(regs + DIEPTSIZ(index)), + readl(regs + DOEPTSIZ(index))); + + seq_printf(seq, "\n"); + seq_printf(seq, "mps %d\n", ep->ep.maxpacket); + seq_printf(seq, "total_data=%ld\n", ep->total_data); + + seq_printf(seq, "request list (%p,%p):\n", + ep->queue.next, ep->queue.prev); + + spin_lock_irqsave(&hsotg->lock, flags); + + list_for_each_entry(req, &ep->queue, queue) { + if (--show_limit < 0) { + seq_printf(seq, "not showing more requests...\n"); + break; + } + + seq_printf(seq, "%c req %p: %d bytes @%p, ", + req == ep->req ? '*' : ' ', + req, req->req.length, req->req.buf); + seq_printf(seq, "%d done, res %d\n", + req->req.actual, req->req.status); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; +} + +static int ep_open(struct inode *inode, struct file *file) +{ + return single_open(file, ep_show, inode->i_private); +} + +static const struct file_operations ep_fops = { + .owner = THIS_MODULE, + .open = ep_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * s3c_hsotg_create_debug - create debugfs directory and files + * @hsotg: The driver state + * + * Create the debugfs files to allow the user to get information + * about the state of the system. The directory name is created + * with the same name as the device itself, in case we end up + * with multiple blocks in future systems. + */ +static void s3c_hsotg_create_debug(struct s3c_hsotg *hsotg) +{ + struct dentry *root; + unsigned epidx; + + root = debugfs_create_dir(dev_name(hsotg->dev), NULL); + hsotg->debug_root = root; + if (IS_ERR(root)) { + dev_err(hsotg->dev, "cannot create debug root\n"); + return; + } + + /* create general state file */ + + hsotg->debug_file = debugfs_create_file("state", 0444, root, + hsotg, &state_fops); + + if (IS_ERR(hsotg->debug_file)) + dev_err(hsotg->dev, "%s: failed to create state\n", __func__); + + hsotg->debug_fifo = debugfs_create_file("fifo", 0444, root, + hsotg, &fifo_fops); + + if (IS_ERR(hsotg->debug_fifo)) + dev_err(hsotg->dev, "%s: failed to create fifo\n", __func__); + + /* create one file for each endpoint */ + + for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { + struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; + + ep->debugfs = debugfs_create_file(ep->name, 0444, + root, ep, &ep_fops); + + if (IS_ERR(ep->debugfs)) + dev_err(hsotg->dev, "failed to create %s debug file\n", + ep->name); + } +} + +/** + * s3c_hsotg_delete_debug - cleanup debugfs entries + * @hsotg: The driver state + * + * Cleanup (remove) the debugfs files for use on module exit. + */ +static void s3c_hsotg_delete_debug(struct s3c_hsotg *hsotg) +{ + unsigned epidx; + + for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { + struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; + debugfs_remove(ep->debugfs); + } + + debugfs_remove(hsotg->debug_file); + debugfs_remove(hsotg->debug_fifo); + debugfs_remove(hsotg->debug_root); +} + +/** + * s3c_hsotg_probe - probe function for hsotg driver + * @pdev: The platform information for the driver + */ + +static int s3c_hsotg_probe(struct platform_device *pdev) +{ + struct s3c_hsotg_plat *plat = pdev->dev.platform_data; + struct usb_phy *phy; + struct device *dev = &pdev->dev; + struct s3c_hsotg_ep *eps; + struct s3c_hsotg *hsotg; + struct resource *res; + int epnum; + int ret; + int i; + + hsotg = devm_kzalloc(&pdev->dev, sizeof(struct s3c_hsotg), GFP_KERNEL); + if (!hsotg) { + dev_err(dev, "cannot get memory\n"); + return -ENOMEM; + } + + phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(phy)) { + /* Fallback for pdata */ + plat = pdev->dev.platform_data; + if (!plat) { + dev_err(&pdev->dev, "no platform data or transceiver defined\n"); + return -EPROBE_DEFER; + } else { + hsotg->plat = plat; + } + } else { + hsotg->phy = phy; + } + + hsotg->dev = dev; + + hsotg->clk = devm_clk_get(&pdev->dev, "otg"); + if (IS_ERR(hsotg->clk)) { + dev_err(dev, "cannot get otg clock\n"); + return PTR_ERR(hsotg->clk); + } + + platform_set_drvdata(pdev, hsotg); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + hsotg->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hsotg->regs)) { + ret = PTR_ERR(hsotg->regs); + goto err_clk; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(dev, "cannot find IRQ\n"); + goto err_clk; + } + + spin_lock_init(&hsotg->lock); + + hsotg->irq = ret; + + ret = devm_request_irq(&pdev->dev, hsotg->irq, s3c_hsotg_irq, 0, + dev_name(dev), hsotg); + if (ret < 0) { + dev_err(dev, "cannot claim IRQ\n"); + goto err_clk; + } + + dev_info(dev, "regs %p, irq %d\n", hsotg->regs, hsotg->irq); + + hsotg->gadget.max_speed = USB_SPEED_HIGH; + hsotg->gadget.ops = &s3c_hsotg_gadget_ops; + hsotg->gadget.name = dev_name(dev); + + /* reset the system */ + + clk_prepare_enable(hsotg->clk); + + /* regulators */ + + for (i = 0; i < ARRAY_SIZE(hsotg->supplies); i++) + hsotg->supplies[i].supply = s3c_hsotg_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + if (ret) { + dev_err(dev, "failed to request supplies: %d\n", ret); + goto err_clk; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + + if (ret) { + dev_err(hsotg->dev, "failed to enable supplies: %d\n", ret); + goto err_supplies; + } + + /* usb phy enable */ + s3c_hsotg_phy_enable(hsotg); + + s3c_hsotg_corereset(hsotg); + s3c_hsotg_init(hsotg); + s3c_hsotg_hw_cfg(hsotg); + + /* hsotg->num_of_eps holds number of EPs other than ep0 */ + + if (hsotg->num_of_eps == 0) { + dev_err(dev, "wrong number of EPs (zero)\n"); + ret = -EINVAL; + goto err_supplies; + } + + eps = kcalloc(hsotg->num_of_eps + 1, sizeof(struct s3c_hsotg_ep), + GFP_KERNEL); + if (!eps) { + dev_err(dev, "cannot get memory\n"); + ret = -ENOMEM; + goto err_supplies; + } + + hsotg->eps = eps; + + /* setup endpoint information */ + + INIT_LIST_HEAD(&hsotg->gadget.ep_list); + hsotg->gadget.ep0 = &hsotg->eps[0].ep; + + /* allocate EP0 request */ + + hsotg->ctrl_req = s3c_hsotg_ep_alloc_request(&hsotg->eps[0].ep, + GFP_KERNEL); + if (!hsotg->ctrl_req) { + dev_err(dev, "failed to allocate ctrl req\n"); + ret = -ENOMEM; + goto err_ep_mem; + } + + /* initialise the endpoints now the core has been initialised */ + for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) + s3c_hsotg_initep(hsotg, &hsotg->eps[epnum], epnum); + + /* disable power and clock */ + + ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + if (ret) { + dev_err(hsotg->dev, "failed to disable supplies: %d\n", ret); + goto err_ep_mem; + } + + s3c_hsotg_phy_disable(hsotg); + + ret = usb_add_gadget_udc(&pdev->dev, &hsotg->gadget); + if (ret) + goto err_ep_mem; + + s3c_hsotg_create_debug(hsotg); + + s3c_hsotg_dump(hsotg); + + return 0; + +err_ep_mem: + kfree(eps); +err_supplies: + s3c_hsotg_phy_disable(hsotg); +err_clk: + clk_disable_unprepare(hsotg->clk); + + return ret; +} + +/** + * s3c_hsotg_remove - remove function for hsotg driver + * @pdev: The platform information for the driver + */ +static int s3c_hsotg_remove(struct platform_device *pdev) +{ + struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&hsotg->gadget); + + s3c_hsotg_delete_debug(hsotg); + + if (hsotg->driver) { + /* should have been done already by driver model core */ + usb_gadget_unregister_driver(hsotg->driver); + } + + s3c_hsotg_phy_disable(hsotg); + clk_disable_unprepare(hsotg->clk); + + return 0; +} + +#if 1 +#define s3c_hsotg_suspend NULL +#define s3c_hsotg_resume NULL +#endif + +static struct platform_driver s3c_hsotg_driver = { + .driver = { + .name = "s3c-hsotg", + .owner = THIS_MODULE, + }, + .probe = s3c_hsotg_probe, + .remove = s3c_hsotg_remove, + .suspend = s3c_hsotg_suspend, + .resume = s3c_hsotg_resume, +}; + +module_platform_driver(s3c_hsotg_driver); + +MODULE_DESCRIPTION("Samsung S3C USB High-speed/OtG device"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c-hsotg"); diff --git a/drivers/usb/gadget/s3c-hsotg.h b/drivers/usb/gadget/s3c-hsotg.h new file mode 100644 index 00000000..d650b129 --- /dev/null +++ b/drivers/usb/gadget/s3c-hsotg.h @@ -0,0 +1,377 @@ +/* drivers/usb/gadget/s3c-hsotg.h + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * USB2.0 Highspeed/OtG Synopsis DWC2 device block registers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#ifndef __REGS_USB_HSOTG_H +#define __REGS_USB_HSOTG_H __FILE__ + +#define HSOTG_REG(x) (x) + +#define GOTGCTL HSOTG_REG(0x000) +#define GOTGCTL_BSESVLD (1 << 19) +#define GOTGCTL_ASESVLD (1 << 18) +#define GOTGCTL_DBNC_SHORT (1 << 17) +#define GOTGCTL_CONID_B (1 << 16) +#define GOTGCTL_DEVHNPEN (1 << 11) +#define GOTGCTL_HSSETHNPEN (1 << 10) +#define GOTGCTL_HNPREQ (1 << 9) +#define GOTGCTL_HSTNEGSCS (1 << 8) +#define GOTGCTL_SESREQ (1 << 1) +#define GOTGCTL_SESREQSCS (1 << 0) + +#define GOTGINT HSOTG_REG(0x004) +#define GOTGINT_DbnceDone (1 << 19) +#define GOTGINT_ADevTOUTChg (1 << 18) +#define GOTGINT_HstNegDet (1 << 17) +#define GOTGINT_HstnegSucStsChng (1 << 9) +#define GOTGINT_SesReqSucStsChng (1 << 8) +#define GOTGINT_SesEndDet (1 << 2) + +#define GAHBCFG HSOTG_REG(0x008) +#define GAHBCFG_PTxFEmpLvl (1 << 8) +#define GAHBCFG_NPTxFEmpLvl (1 << 7) +#define GAHBCFG_DMAEn (1 << 5) +#define GAHBCFG_HBstLen_MASK (0xf << 1) +#define GAHBCFG_HBstLen_SHIFT (1) +#define GAHBCFG_HBstLen_Single (0x0 << 1) +#define GAHBCFG_HBstLen_Incr (0x1 << 1) +#define GAHBCFG_HBstLen_Incr4 (0x3 << 1) +#define GAHBCFG_HBstLen_Incr8 (0x5 << 1) +#define GAHBCFG_HBstLen_Incr16 (0x7 << 1) +#define GAHBCFG_GlblIntrEn (1 << 0) + +#define GUSBCFG HSOTG_REG(0x00C) +#define GUSBCFG_PHYLPClkSel (1 << 15) +#define GUSBCFG_HNPCap (1 << 9) +#define GUSBCFG_SRPCap (1 << 8) +#define GUSBCFG_PHYIf16 (1 << 3) +#define GUSBCFG_TOutCal_MASK (0x7 << 0) +#define GUSBCFG_TOutCal_SHIFT (0) +#define GUSBCFG_TOutCal_LIMIT (0x7) +#define GUSBCFG_TOutCal(_x) ((_x) << 0) + +#define GRSTCTL HSOTG_REG(0x010) + +#define GRSTCTL_AHBIdle (1 << 31) +#define GRSTCTL_DMAReq (1 << 30) +#define GRSTCTL_TxFNum_MASK (0x1f << 6) +#define GRSTCTL_TxFNum_SHIFT (6) +#define GRSTCTL_TxFNum_LIMIT (0x1f) +#define GRSTCTL_TxFNum(_x) ((_x) << 6) +#define GRSTCTL_TxFFlsh (1 << 5) +#define GRSTCTL_RxFFlsh (1 << 4) +#define GRSTCTL_INTknQFlsh (1 << 3) +#define GRSTCTL_FrmCntrRst (1 << 2) +#define GRSTCTL_HSftRst (1 << 1) +#define GRSTCTL_CSftRst (1 << 0) + +#define GINTSTS HSOTG_REG(0x014) +#define GINTMSK HSOTG_REG(0x018) + +#define GINTSTS_WkUpInt (1 << 31) +#define GINTSTS_SessReqInt (1 << 30) +#define GINTSTS_DisconnInt (1 << 29) +#define GINTSTS_ConIDStsChng (1 << 28) +#define GINTSTS_PTxFEmp (1 << 26) +#define GINTSTS_HChInt (1 << 25) +#define GINTSTS_PrtInt (1 << 24) +#define GINTSTS_FetSusp (1 << 22) +#define GINTSTS_incompIP (1 << 21) +#define GINTSTS_IncomplSOIN (1 << 20) +#define GINTSTS_OEPInt (1 << 19) +#define GINTSTS_IEPInt (1 << 18) +#define GINTSTS_EPMis (1 << 17) +#define GINTSTS_EOPF (1 << 15) +#define GINTSTS_ISOutDrop (1 << 14) +#define GINTSTS_EnumDone (1 << 13) +#define GINTSTS_USBRst (1 << 12) +#define GINTSTS_USBSusp (1 << 11) +#define GINTSTS_ErlySusp (1 << 10) +#define GINTSTS_GOUTNakEff (1 << 7) +#define GINTSTS_GINNakEff (1 << 6) +#define GINTSTS_NPTxFEmp (1 << 5) +#define GINTSTS_RxFLvl (1 << 4) +#define GINTSTS_SOF (1 << 3) +#define GINTSTS_OTGInt (1 << 2) +#define GINTSTS_ModeMis (1 << 1) +#define GINTSTS_CurMod_Host (1 << 0) + +#define GRXSTSR HSOTG_REG(0x01C) +#define GRXSTSP HSOTG_REG(0x020) + +#define GRXSTS_FN_MASK (0x7f << 25) +#define GRXSTS_FN_SHIFT (25) + +#define GRXSTS_PktSts_MASK (0xf << 17) +#define GRXSTS_PktSts_SHIFT (17) +#define GRXSTS_PktSts_GlobalOutNAK (0x1 << 17) +#define GRXSTS_PktSts_OutRX (0x2 << 17) +#define GRXSTS_PktSts_OutDone (0x3 << 17) +#define GRXSTS_PktSts_SetupDone (0x4 << 17) +#define GRXSTS_PktSts_SetupRX (0x6 << 17) + +#define GRXSTS_DPID_MASK (0x3 << 15) +#define GRXSTS_DPID_SHIFT (15) +#define GRXSTS_ByteCnt_MASK (0x7ff << 4) +#define GRXSTS_ByteCnt_SHIFT (4) +#define GRXSTS_EPNum_MASK (0xf << 0) +#define GRXSTS_EPNum_SHIFT (0) + +#define GRXFSIZ HSOTG_REG(0x024) + +#define GNPTXFSIZ HSOTG_REG(0x028) + +#define GNPTXFSIZ_NPTxFDep_MASK (0xffff << 16) +#define GNPTXFSIZ_NPTxFDep_SHIFT (16) +#define GNPTXFSIZ_NPTxFDep_LIMIT (0xffff) +#define GNPTXFSIZ_NPTxFDep(_x) ((_x) << 16) +#define GNPTXFSIZ_NPTxFStAddr_MASK (0xffff << 0) +#define GNPTXFSIZ_NPTxFStAddr_SHIFT (0) +#define GNPTXFSIZ_NPTxFStAddr_LIMIT (0xffff) +#define GNPTXFSIZ_NPTxFStAddr(_x) ((_x) << 0) + +#define GNPTXSTS HSOTG_REG(0x02C) + +#define GNPTXSTS_NPtxQTop_MASK (0x7f << 24) +#define GNPTXSTS_NPtxQTop_SHIFT (24) + +#define GNPTXSTS_NPTxQSpcAvail_MASK (0xff << 16) +#define GNPTXSTS_NPTxQSpcAvail_SHIFT (16) +#define GNPTXSTS_NPTxQSpcAvail_GET(_v) (((_v) >> 16) & 0xff) + +#define GNPTXSTS_NPTxFSpcAvail_MASK (0xffff << 0) +#define GNPTXSTS_NPTxFSpcAvail_SHIFT (0) +#define GNPTXSTS_NPTxFSpcAvail_GET(_v) (((_v) >> 0) & 0xffff) + + +#define HPTXFSIZ HSOTG_REG(0x100) + +#define DPTXFSIZn(_a) HSOTG_REG(0x104 + (((_a) - 1) * 4)) + +#define DPTXFSIZn_DPTxFSize_MASK (0xffff << 16) +#define DPTXFSIZn_DPTxFSize_SHIFT (16) +#define DPTXFSIZn_DPTxFSize_GET(_v) (((_v) >> 16) & 0xffff) +#define DPTXFSIZn_DPTxFSize_LIMIT (0xffff) +#define DPTXFSIZn_DPTxFSize(_x) ((_x) << 16) + +#define DPTXFSIZn_DPTxFStAddr_MASK (0xffff << 0) +#define DPTXFSIZn_DPTxFStAddr_SHIFT (0) + +/* Device mode registers */ +#define DCFG HSOTG_REG(0x800) + +#define DCFG_EPMisCnt_MASK (0x1f << 18) +#define DCFG_EPMisCnt_SHIFT (18) +#define DCFG_EPMisCnt_LIMIT (0x1f) +#define DCFG_EPMisCnt(_x) ((_x) << 18) + +#define DCFG_PerFrInt_MASK (0x3 << 11) +#define DCFG_PerFrInt_SHIFT (11) +#define DCFG_PerFrInt_LIMIT (0x3) +#define DCFG_PerFrInt(_x) ((_x) << 11) + +#define DCFG_DevAddr_MASK (0x7f << 4) +#define DCFG_DevAddr_SHIFT (4) +#define DCFG_DevAddr_LIMIT (0x7f) +#define DCFG_DevAddr(_x) ((_x) << 4) + +#define DCFG_NZStsOUTHShk (1 << 2) + +#define DCFG_DevSpd_MASK (0x3 << 0) +#define DCFG_DevSpd_SHIFT (0) +#define DCFG_DevSpd_HS (0x0 << 0) +#define DCFG_DevSpd_FS (0x1 << 0) +#define DCFG_DevSpd_LS (0x2 << 0) +#define DCFG_DevSpd_FS48 (0x3 << 0) + +#define DCTL HSOTG_REG(0x804) + +#define DCTL_PWROnPrgDone (1 << 11) +#define DCTL_CGOUTNak (1 << 10) +#define DCTL_SGOUTNak (1 << 9) +#define DCTL_CGNPInNAK (1 << 8) +#define DCTL_SGNPInNAK (1 << 7) +#define DCTL_TstCtl_MASK (0x7 << 4) +#define DCTL_TstCtl_SHIFT (4) +#define DCTL_GOUTNakSts (1 << 3) +#define DCTL_GNPINNakSts (1 << 2) +#define DCTL_SftDiscon (1 << 1) +#define DCTL_RmtWkUpSig (1 << 0) + +#define DSTS HSOTG_REG(0x808) + +#define DSTS_SOFFN_MASK (0x3fff << 8) +#define DSTS_SOFFN_SHIFT (8) +#define DSTS_SOFFN_LIMIT (0x3fff) +#define DSTS_SOFFN(_x) ((_x) << 8) +#define DSTS_ErraticErr (1 << 3) +#define DSTS_EnumSpd_MASK (0x3 << 1) +#define DSTS_EnumSpd_SHIFT (1) +#define DSTS_EnumSpd_HS (0x0 << 1) +#define DSTS_EnumSpd_FS (0x1 << 1) +#define DSTS_EnumSpd_LS (0x2 << 1) +#define DSTS_EnumSpd_FS48 (0x3 << 1) + +#define DSTS_SuspSts (1 << 0) + +#define DIEPMSK HSOTG_REG(0x810) + +#define DIEPMSK_TxFIFOEmpty (1 << 7) +#define DIEPMSK_INEPNakEffMsk (1 << 6) +#define DIEPMSK_INTknEPMisMsk (1 << 5) +#define DIEPMSK_INTknTXFEmpMsk (1 << 4) +#define DIEPMSK_TimeOUTMsk (1 << 3) +#define DIEPMSK_AHBErrMsk (1 << 2) +#define DIEPMSK_EPDisbldMsk (1 << 1) +#define DIEPMSK_XferComplMsk (1 << 0) + +#define DOEPMSK HSOTG_REG(0x814) + +#define DOEPMSK_Back2BackSetup (1 << 6) +#define DOEPMSK_OUTTknEPdisMsk (1 << 4) +#define DOEPMSK_SetupMsk (1 << 3) +#define DOEPMSK_AHBErrMsk (1 << 2) +#define DOEPMSK_EPDisbldMsk (1 << 1) +#define DOEPMSK_XferComplMsk (1 << 0) + +#define DAINT HSOTG_REG(0x818) +#define DAINTMSK HSOTG_REG(0x81C) + +#define DAINT_OutEP_SHIFT (16) +#define DAINT_OutEP(x) (1 << ((x) + 16)) +#define DAINT_InEP(x) (1 << (x)) + +#define DTKNQR1 HSOTG_REG(0x820) +#define DTKNQR2 HSOTG_REG(0x824) +#define DTKNQR3 HSOTG_REG(0x830) +#define DTKNQR4 HSOTG_REG(0x834) + +#define DVBUSDIS HSOTG_REG(0x828) +#define DVBUSPULSE HSOTG_REG(0x82C) + +#define DIEPCTL0 HSOTG_REG(0x900) +#define DOEPCTL0 HSOTG_REG(0xB00) +#define DIEPCTL(_a) HSOTG_REG(0x900 + ((_a) * 0x20)) +#define DOEPCTL(_a) HSOTG_REG(0xB00 + ((_a) * 0x20)) + +/* EP0 specialness: + * bits[29..28] - reserved (no SetD0PID, SetD1PID) + * bits[25..22] - should always be zero, this isn't a periodic endpoint + * bits[10..0] - MPS setting differenct for EP0 + */ +#define D0EPCTL_MPS_MASK (0x3 << 0) +#define D0EPCTL_MPS_SHIFT (0) +#define D0EPCTL_MPS_64 (0x0 << 0) +#define D0EPCTL_MPS_32 (0x1 << 0) +#define D0EPCTL_MPS_16 (0x2 << 0) +#define D0EPCTL_MPS_8 (0x3 << 0) + +#define DxEPCTL_EPEna (1 << 31) +#define DxEPCTL_EPDis (1 << 30) +#define DxEPCTL_SetD1PID (1 << 29) +#define DxEPCTL_SetOddFr (1 << 29) +#define DxEPCTL_SetD0PID (1 << 28) +#define DxEPCTL_SetEvenFr (1 << 28) +#define DxEPCTL_SNAK (1 << 27) +#define DxEPCTL_CNAK (1 << 26) +#define DxEPCTL_TxFNum_MASK (0xf << 22) +#define DxEPCTL_TxFNum_SHIFT (22) +#define DxEPCTL_TxFNum_LIMIT (0xf) +#define DxEPCTL_TxFNum(_x) ((_x) << 22) + +#define DxEPCTL_Stall (1 << 21) +#define DxEPCTL_Snp (1 << 20) +#define DxEPCTL_EPType_MASK (0x3 << 18) +#define DxEPCTL_EPType_SHIFT (18) +#define DxEPCTL_EPType_Control (0x0 << 18) +#define DxEPCTL_EPType_Iso (0x1 << 18) +#define DxEPCTL_EPType_Bulk (0x2 << 18) +#define DxEPCTL_EPType_Intterupt (0x3 << 18) + +#define DxEPCTL_NAKsts (1 << 17) +#define DxEPCTL_DPID (1 << 16) +#define DxEPCTL_EOFrNum (1 << 16) +#define DxEPCTL_USBActEp (1 << 15) +#define DxEPCTL_NextEp_MASK (0xf << 11) +#define DxEPCTL_NextEp_SHIFT (11) +#define DxEPCTL_NextEp_LIMIT (0xf) +#define DxEPCTL_NextEp(_x) ((_x) << 11) + +#define DxEPCTL_MPS_MASK (0x7ff << 0) +#define DxEPCTL_MPS_SHIFT (0) +#define DxEPCTL_MPS_LIMIT (0x7ff) +#define DxEPCTL_MPS(_x) ((_x) << 0) + +#define DIEPINT(_a) HSOTG_REG(0x908 + ((_a) * 0x20)) +#define DOEPINT(_a) HSOTG_REG(0xB08 + ((_a) * 0x20)) + +#define DxEPINT_INEPNakEff (1 << 6) +#define DxEPINT_Back2BackSetup (1 << 6) +#define DxEPINT_INTknEPMis (1 << 5) +#define DxEPINT_INTknTXFEmp (1 << 4) +#define DxEPINT_OUTTknEPdis (1 << 4) +#define DxEPINT_Timeout (1 << 3) +#define DxEPINT_Setup (1 << 3) +#define DxEPINT_AHBErr (1 << 2) +#define DxEPINT_EPDisbld (1 << 1) +#define DxEPINT_XferCompl (1 << 0) + +#define DIEPTSIZ0 HSOTG_REG(0x910) + +#define DIEPTSIZ0_PktCnt_MASK (0x3 << 19) +#define DIEPTSIZ0_PktCnt_SHIFT (19) +#define DIEPTSIZ0_PktCnt_LIMIT (0x3) +#define DIEPTSIZ0_PktCnt(_x) ((_x) << 19) + +#define DIEPTSIZ0_XferSize_MASK (0x7f << 0) +#define DIEPTSIZ0_XferSize_SHIFT (0) +#define DIEPTSIZ0_XferSize_LIMIT (0x7f) +#define DIEPTSIZ0_XferSize(_x) ((_x) << 0) + +#define DOEPTSIZ0 HSOTG_REG(0xB10) +#define DOEPTSIZ0_SUPCnt_MASK (0x3 << 29) +#define DOEPTSIZ0_SUPCnt_SHIFT (29) +#define DOEPTSIZ0_SUPCnt_LIMIT (0x3) +#define DOEPTSIZ0_SUPCnt(_x) ((_x) << 29) + +#define DOEPTSIZ0_PktCnt (1 << 19) +#define DOEPTSIZ0_XferSize_MASK (0x7f << 0) +#define DOEPTSIZ0_XferSize_SHIFT (0) + +#define DIEPTSIZ(_a) HSOTG_REG(0x910 + ((_a) * 0x20)) +#define DOEPTSIZ(_a) HSOTG_REG(0xB10 + ((_a) * 0x20)) + +#define DxEPTSIZ_MC_MASK (0x3 << 29) +#define DxEPTSIZ_MC_SHIFT (29) +#define DxEPTSIZ_MC_LIMIT (0x3) +#define DxEPTSIZ_MC(_x) ((_x) << 29) + +#define DxEPTSIZ_PktCnt_MASK (0x3ff << 19) +#define DxEPTSIZ_PktCnt_SHIFT (19) +#define DxEPTSIZ_PktCnt_GET(_v) (((_v) >> 19) & 0x3ff) +#define DxEPTSIZ_PktCnt_LIMIT (0x3ff) +#define DxEPTSIZ_PktCnt(_x) ((_x) << 19) + +#define DxEPTSIZ_XferSize_MASK (0x7ffff << 0) +#define DxEPTSIZ_XferSize_SHIFT (0) +#define DxEPTSIZ_XferSize_GET(_v) (((_v) >> 0) & 0x7ffff) +#define DxEPTSIZ_XferSize_LIMIT (0x7ffff) +#define DxEPTSIZ_XferSize(_x) ((_x) << 0) + +#define DIEPDMA(_a) HSOTG_REG(0x914 + ((_a) * 0x20)) +#define DOEPDMA(_a) HSOTG_REG(0xB14 + ((_a) * 0x20)) +#define DTXFSTS(_a) HSOTG_REG(0x918 + ((_a) * 0x20)) + +#define EPFIFO(_a) HSOTG_REG(0x1000 + ((_a) * 0x1000)) + +#endif /* __REGS_USB_HSOTG_H */ diff --git a/drivers/usb/gadget/s3c-hsudc.c b/drivers/usb/gadget/s3c-hsudc.c new file mode 100644 index 00000000..b1f0771f --- /dev/null +++ b/drivers/usb/gadget/s3c-hsudc.c @@ -0,0 +1,1370 @@ +/* linux/drivers/usb/gadget/s3c-hsudc.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * S3C24XX USB 2.0 High-speed USB controller gadget driver + * + * The S3C24XX USB 2.0 high-speed USB controller supports upto 9 endpoints. + * Each endpoint can be configured as either in or out endpoint. Endpoints + * can be configured for Bulk or Interrupt transfer mode. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define S3C_HSUDC_REG(x) (x) + +/* Non-Indexed Registers */ +#define S3C_IR S3C_HSUDC_REG(0x00) /* Index Register */ +#define S3C_EIR S3C_HSUDC_REG(0x04) /* EP Intr Status */ +#define S3C_EIR_EP0 (1<<0) +#define S3C_EIER S3C_HSUDC_REG(0x08) /* EP Intr Enable */ +#define S3C_FAR S3C_HSUDC_REG(0x0c) /* Gadget Address */ +#define S3C_FNR S3C_HSUDC_REG(0x10) /* Frame Number */ +#define S3C_EDR S3C_HSUDC_REG(0x14) /* EP Direction */ +#define S3C_TR S3C_HSUDC_REG(0x18) /* Test Register */ +#define S3C_SSR S3C_HSUDC_REG(0x1c) /* System Status */ +#define S3C_SSR_DTZIEN_EN (0xff8f) +#define S3C_SSR_ERR (0xff80) +#define S3C_SSR_VBUSON (1 << 8) +#define S3C_SSR_HSP (1 << 4) +#define S3C_SSR_SDE (1 << 3) +#define S3C_SSR_RESUME (1 << 2) +#define S3C_SSR_SUSPEND (1 << 1) +#define S3C_SSR_RESET (1 << 0) +#define S3C_SCR S3C_HSUDC_REG(0x20) /* System Control */ +#define S3C_SCR_DTZIEN_EN (1 << 14) +#define S3C_SCR_RRD_EN (1 << 5) +#define S3C_SCR_SUS_EN (1 << 1) +#define S3C_SCR_RST_EN (1 << 0) +#define S3C_EP0SR S3C_HSUDC_REG(0x24) /* EP0 Status */ +#define S3C_EP0SR_EP0_LWO (1 << 6) +#define S3C_EP0SR_STALL (1 << 4) +#define S3C_EP0SR_TX_SUCCESS (1 << 1) +#define S3C_EP0SR_RX_SUCCESS (1 << 0) +#define S3C_EP0CR S3C_HSUDC_REG(0x28) /* EP0 Control */ +#define S3C_BR(_x) S3C_HSUDC_REG(0x60 + (_x * 4)) + +/* Indexed Registers */ +#define S3C_ESR S3C_HSUDC_REG(0x2c) /* EPn Status */ +#define S3C_ESR_FLUSH (1 << 6) +#define S3C_ESR_STALL (1 << 5) +#define S3C_ESR_LWO (1 << 4) +#define S3C_ESR_PSIF_ONE (1 << 2) +#define S3C_ESR_PSIF_TWO (2 << 2) +#define S3C_ESR_TX_SUCCESS (1 << 1) +#define S3C_ESR_RX_SUCCESS (1 << 0) +#define S3C_ECR S3C_HSUDC_REG(0x30) /* EPn Control */ +#define S3C_ECR_DUEN (1 << 7) +#define S3C_ECR_FLUSH (1 << 6) +#define S3C_ECR_STALL (1 << 1) +#define S3C_ECR_IEMS (1 << 0) +#define S3C_BRCR S3C_HSUDC_REG(0x34) /* Read Count */ +#define S3C_BWCR S3C_HSUDC_REG(0x38) /* Write Count */ +#define S3C_MPR S3C_HSUDC_REG(0x3c) /* Max Pkt Size */ + +#define WAIT_FOR_SETUP (0) +#define DATA_STATE_XMIT (1) +#define DATA_STATE_RECV (2) + +static const char * const s3c_hsudc_supply_names[] = { + "vdda", /* analog phy supply, 3.3V */ + "vddi", /* digital phy supply, 1.2V */ + "vddosc", /* oscillator supply, 1.8V - 3.3V */ +}; + +/** + * struct s3c_hsudc_ep - Endpoint representation used by driver. + * @ep: USB gadget layer representation of device endpoint. + * @name: Endpoint name (as required by ep autoconfiguration). + * @dev: Reference to the device controller to which this EP belongs. + * @desc: Endpoint descriptor obtained from the gadget driver. + * @queue: Transfer request queue for the endpoint. + * @stopped: Maintains state of endpoint, set if EP is halted. + * @bEndpointAddress: EP address (including direction bit). + * @fifo: Base address of EP FIFO. + */ +struct s3c_hsudc_ep { + struct usb_ep ep; + char name[20]; + struct s3c_hsudc *dev; + struct list_head queue; + u8 stopped; + u8 wedge; + u8 bEndpointAddress; + void __iomem *fifo; +}; + +/** + * struct s3c_hsudc_req - Driver encapsulation of USB gadget transfer request. + * @req: Reference to USB gadget transfer request. + * @queue: Used for inserting this request to the endpoint request queue. + */ +struct s3c_hsudc_req { + struct usb_request req; + struct list_head queue; +}; + +/** + * struct s3c_hsudc - Driver's abstraction of the device controller. + * @gadget: Instance of usb_gadget which is referenced by gadget driver. + * @driver: Reference to currenty active gadget driver. + * @dev: The device reference used by probe function. + * @lock: Lock to synchronize the usage of Endpoints (EP's are indexed). + * @regs: Remapped base address of controller's register space. + * irq: IRQ number used by the controller. + * uclk: Reference to the controller clock. + * ep0state: Current state of EP0. + * ep: List of endpoints supported by the controller. + */ +struct s3c_hsudc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct device *dev; + struct s3c24xx_hsudc_platdata *pd; + struct usb_phy *transceiver; + struct regulator_bulk_data supplies[ARRAY_SIZE(s3c_hsudc_supply_names)]; + spinlock_t lock; + void __iomem *regs; + int irq; + struct clk *uclk; + int ep0state; + struct s3c_hsudc_ep ep[]; +}; + +#define ep_maxpacket(_ep) ((_ep)->ep.maxpacket) +#define ep_is_in(_ep) ((_ep)->bEndpointAddress & USB_DIR_IN) +#define ep_index(_ep) ((_ep)->bEndpointAddress & \ + USB_ENDPOINT_NUMBER_MASK) + +static const char driver_name[] = "s3c-udc"; +static const char ep0name[] = "ep0-control"; + +static inline struct s3c_hsudc_req *our_req(struct usb_request *req) +{ + return container_of(req, struct s3c_hsudc_req, req); +} + +static inline struct s3c_hsudc_ep *our_ep(struct usb_ep *ep) +{ + return container_of(ep, struct s3c_hsudc_ep, ep); +} + +static inline struct s3c_hsudc *to_hsudc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct s3c_hsudc, gadget); +} + +static inline void set_index(struct s3c_hsudc *hsudc, int ep_addr) +{ + ep_addr &= USB_ENDPOINT_NUMBER_MASK; + writel(ep_addr, hsudc->regs + S3C_IR); +} + +static inline void __orr32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) | val, ptr); +} + +static void s3c_hsudc_init_phy(void) +{ + u32 cfg; + + cfg = readl(S3C2443_PWRCFG) | S3C2443_PWRCFG_USBPHY; + writel(cfg, S3C2443_PWRCFG); + + cfg = readl(S3C2443_URSTCON); + cfg |= (S3C2443_URSTCON_FUNCRST | S3C2443_URSTCON_PHYRST); + writel(cfg, S3C2443_URSTCON); + mdelay(1); + + cfg = readl(S3C2443_URSTCON); + cfg &= ~(S3C2443_URSTCON_FUNCRST | S3C2443_URSTCON_PHYRST); + writel(cfg, S3C2443_URSTCON); + + cfg = readl(S3C2443_PHYCTRL); + cfg &= ~(S3C2443_PHYCTRL_CLKSEL | S3C2443_PHYCTRL_DSPORT); + cfg |= (S3C2443_PHYCTRL_EXTCLK | S3C2443_PHYCTRL_PLLSEL); + writel(cfg, S3C2443_PHYCTRL); + + cfg = readl(S3C2443_PHYPWR); + cfg &= ~(S3C2443_PHYPWR_FSUSPEND | S3C2443_PHYPWR_PLL_PWRDN | + S3C2443_PHYPWR_XO_ON | S3C2443_PHYPWR_PLL_REFCLK | + S3C2443_PHYPWR_ANALOG_PD); + cfg |= S3C2443_PHYPWR_COMMON_ON; + writel(cfg, S3C2443_PHYPWR); + + cfg = readl(S3C2443_UCLKCON); + cfg |= (S3C2443_UCLKCON_DETECT_VBUS | S3C2443_UCLKCON_FUNC_CLKEN | + S3C2443_UCLKCON_TCLKEN); + writel(cfg, S3C2443_UCLKCON); +} + +static void s3c_hsudc_uninit_phy(void) +{ + u32 cfg; + + cfg = readl(S3C2443_PWRCFG) & ~S3C2443_PWRCFG_USBPHY; + writel(cfg, S3C2443_PWRCFG); + + writel(S3C2443_PHYPWR_FSUSPEND, S3C2443_PHYPWR); + + cfg = readl(S3C2443_UCLKCON) & ~S3C2443_UCLKCON_FUNC_CLKEN; + writel(cfg, S3C2443_UCLKCON); +} + +/** + * s3c_hsudc_complete_request - Complete a transfer request. + * @hsep: Endpoint to which the request belongs. + * @hsreq: Transfer request to be completed. + * @status: Transfer completion status for the transfer request. + */ +static void s3c_hsudc_complete_request(struct s3c_hsudc_ep *hsep, + struct s3c_hsudc_req *hsreq, int status) +{ + unsigned int stopped = hsep->stopped; + struct s3c_hsudc *hsudc = hsep->dev; + + list_del_init(&hsreq->queue); + hsreq->req.status = status; + + if (!ep_index(hsep)) { + hsudc->ep0state = WAIT_FOR_SETUP; + hsep->bEndpointAddress &= ~USB_DIR_IN; + } + + hsep->stopped = 1; + spin_unlock(&hsudc->lock); + if (hsreq->req.complete != NULL) + hsreq->req.complete(&hsep->ep, &hsreq->req); + spin_lock(&hsudc->lock); + hsep->stopped = stopped; +} + +/** + * s3c_hsudc_nuke_ep - Terminate all requests queued for a endpoint. + * @hsep: Endpoint for which queued requests have to be terminated. + * @status: Transfer completion status for the transfer request. + */ +static void s3c_hsudc_nuke_ep(struct s3c_hsudc_ep *hsep, int status) +{ + struct s3c_hsudc_req *hsreq; + + while (!list_empty(&hsep->queue)) { + hsreq = list_entry(hsep->queue.next, + struct s3c_hsudc_req, queue); + s3c_hsudc_complete_request(hsep, hsreq, status); + } +} + +/** + * s3c_hsudc_stop_activity - Stop activity on all endpoints. + * @hsudc: Device controller for which EP activity is to be stopped. + * + * All the endpoints are stopped and any pending transfer requests if any on + * the endpoint are terminated. + */ +static void s3c_hsudc_stop_activity(struct s3c_hsudc *hsudc) +{ + struct s3c_hsudc_ep *hsep; + int epnum; + + hsudc->gadget.speed = USB_SPEED_UNKNOWN; + + for (epnum = 0; epnum < hsudc->pd->epnum; epnum++) { + hsep = &hsudc->ep[epnum]; + hsep->stopped = 1; + s3c_hsudc_nuke_ep(hsep, -ESHUTDOWN); + } +} + +/** + * s3c_hsudc_read_setup_pkt - Read the received setup packet from EP0 fifo. + * @hsudc: Device controller from which setup packet is to be read. + * @buf: The buffer into which the setup packet is read. + * + * The setup packet received in the EP0 fifo is read and stored into a + * given buffer address. + */ + +static void s3c_hsudc_read_setup_pkt(struct s3c_hsudc *hsudc, u16 *buf) +{ + int count; + + count = readl(hsudc->regs + S3C_BRCR); + while (count--) + *buf++ = (u16)readl(hsudc->regs + S3C_BR(0)); + + writel(S3C_EP0SR_RX_SUCCESS, hsudc->regs + S3C_EP0SR); +} + +/** + * s3c_hsudc_write_fifo - Write next chunk of transfer data to EP fifo. + * @hsep: Endpoint to which the data is to be written. + * @hsreq: Transfer request from which the next chunk of data is written. + * + * Write the next chunk of data from a transfer request to the endpoint FIFO. + * If the transfer request completes, 1 is returned, otherwise 0 is returned. + */ +static int s3c_hsudc_write_fifo(struct s3c_hsudc_ep *hsep, + struct s3c_hsudc_req *hsreq) +{ + u16 *buf; + u32 max = ep_maxpacket(hsep); + u32 count, length; + bool is_last; + void __iomem *fifo = hsep->fifo; + + buf = hsreq->req.buf + hsreq->req.actual; + prefetch(buf); + + length = hsreq->req.length - hsreq->req.actual; + length = min(length, max); + hsreq->req.actual += length; + + writel(length, hsep->dev->regs + S3C_BWCR); + for (count = 0; count < length; count += 2) + writel(*buf++, fifo); + + if (count != max) { + is_last = true; + } else { + if (hsreq->req.length != hsreq->req.actual || hsreq->req.zero) + is_last = false; + else + is_last = true; + } + + if (is_last) { + s3c_hsudc_complete_request(hsep, hsreq, 0); + return 1; + } + + return 0; +} + +/** + * s3c_hsudc_read_fifo - Read the next chunk of data from EP fifo. + * @hsep: Endpoint from which the data is to be read. + * @hsreq: Transfer request to which the next chunk of data read is written. + * + * Read the next chunk of data from the endpoint FIFO and a write it to the + * transfer request buffer. If the transfer request completes, 1 is returned, + * otherwise 0 is returned. + */ +static int s3c_hsudc_read_fifo(struct s3c_hsudc_ep *hsep, + struct s3c_hsudc_req *hsreq) +{ + struct s3c_hsudc *hsudc = hsep->dev; + u32 csr, offset; + u16 *buf, word; + u32 buflen, rcnt, rlen; + void __iomem *fifo = hsep->fifo; + u32 is_short = 0; + + offset = (ep_index(hsep)) ? S3C_ESR : S3C_EP0SR; + csr = readl(hsudc->regs + offset); + if (!(csr & S3C_ESR_RX_SUCCESS)) + return -EINVAL; + + buf = hsreq->req.buf + hsreq->req.actual; + prefetchw(buf); + buflen = hsreq->req.length - hsreq->req.actual; + + rcnt = readl(hsudc->regs + S3C_BRCR); + rlen = (csr & S3C_ESR_LWO) ? (rcnt * 2 - 1) : (rcnt * 2); + + hsreq->req.actual += min(rlen, buflen); + is_short = (rlen < hsep->ep.maxpacket); + + while (rcnt-- != 0) { + word = (u16)readl(fifo); + if (buflen) { + *buf++ = word; + buflen--; + } else { + hsreq->req.status = -EOVERFLOW; + } + } + + writel(S3C_ESR_RX_SUCCESS, hsudc->regs + offset); + + if (is_short || hsreq->req.actual == hsreq->req.length) { + s3c_hsudc_complete_request(hsep, hsreq, 0); + return 1; + } + + return 0; +} + +/** + * s3c_hsudc_epin_intr - Handle in-endpoint interrupt. + * @hsudc - Device controller for which the interrupt is to be handled. + * @ep_idx - Endpoint number on which an interrupt is pending. + * + * Handles interrupt for a in-endpoint. The interrupts that are handled are + * stall and data transmit complete interrupt. + */ +static void s3c_hsudc_epin_intr(struct s3c_hsudc *hsudc, u32 ep_idx) +{ + struct s3c_hsudc_ep *hsep = &hsudc->ep[ep_idx]; + struct s3c_hsudc_req *hsreq; + u32 csr; + + csr = readl(hsudc->regs + S3C_ESR); + if (csr & S3C_ESR_STALL) { + writel(S3C_ESR_STALL, hsudc->regs + S3C_ESR); + return; + } + + if (csr & S3C_ESR_TX_SUCCESS) { + writel(S3C_ESR_TX_SUCCESS, hsudc->regs + S3C_ESR); + if (list_empty(&hsep->queue)) + return; + + hsreq = list_entry(hsep->queue.next, + struct s3c_hsudc_req, queue); + if ((s3c_hsudc_write_fifo(hsep, hsreq) == 0) && + (csr & S3C_ESR_PSIF_TWO)) + s3c_hsudc_write_fifo(hsep, hsreq); + } +} + +/** + * s3c_hsudc_epout_intr - Handle out-endpoint interrupt. + * @hsudc - Device controller for which the interrupt is to be handled. + * @ep_idx - Endpoint number on which an interrupt is pending. + * + * Handles interrupt for a out-endpoint. The interrupts that are handled are + * stall, flush and data ready interrupt. + */ +static void s3c_hsudc_epout_intr(struct s3c_hsudc *hsudc, u32 ep_idx) +{ + struct s3c_hsudc_ep *hsep = &hsudc->ep[ep_idx]; + struct s3c_hsudc_req *hsreq; + u32 csr; + + csr = readl(hsudc->regs + S3C_ESR); + if (csr & S3C_ESR_STALL) { + writel(S3C_ESR_STALL, hsudc->regs + S3C_ESR); + return; + } + + if (csr & S3C_ESR_FLUSH) { + __orr32(hsudc->regs + S3C_ECR, S3C_ECR_FLUSH); + return; + } + + if (csr & S3C_ESR_RX_SUCCESS) { + if (list_empty(&hsep->queue)) + return; + + hsreq = list_entry(hsep->queue.next, + struct s3c_hsudc_req, queue); + if (((s3c_hsudc_read_fifo(hsep, hsreq)) == 0) && + (csr & S3C_ESR_PSIF_TWO)) + s3c_hsudc_read_fifo(hsep, hsreq); + } +} + +/** s3c_hsudc_set_halt - Set or clear a endpoint halt. + * @_ep: Endpoint on which halt has to be set or cleared. + * @value: 1 for setting halt on endpoint, 0 to clear halt. + * + * Set or clear endpoint halt. If halt is set, the endpoint is stopped. + * If halt is cleared, for in-endpoints, if there are any pending + * transfer requests, transfers are started. + */ +static int s3c_hsudc_set_halt(struct usb_ep *_ep, int value) +{ + struct s3c_hsudc_ep *hsep = our_ep(_ep); + struct s3c_hsudc *hsudc = hsep->dev; + struct s3c_hsudc_req *hsreq; + unsigned long irqflags; + u32 ecr; + u32 offset; + + if (value && ep_is_in(hsep) && !list_empty(&hsep->queue)) + return -EAGAIN; + + spin_lock_irqsave(&hsudc->lock, irqflags); + set_index(hsudc, ep_index(hsep)); + offset = (ep_index(hsep)) ? S3C_ECR : S3C_EP0CR; + ecr = readl(hsudc->regs + offset); + + if (value) { + ecr |= S3C_ECR_STALL; + if (ep_index(hsep)) + ecr |= S3C_ECR_FLUSH; + hsep->stopped = 1; + } else { + ecr &= ~S3C_ECR_STALL; + hsep->stopped = hsep->wedge = 0; + } + writel(ecr, hsudc->regs + offset); + + if (ep_is_in(hsep) && !list_empty(&hsep->queue) && !value) { + hsreq = list_entry(hsep->queue.next, + struct s3c_hsudc_req, queue); + if (hsreq) + s3c_hsudc_write_fifo(hsep, hsreq); + } + + spin_unlock_irqrestore(&hsudc->lock, irqflags); + return 0; +} + +/** s3c_hsudc_set_wedge - Sets the halt feature with the clear requests ignored + * @_ep: Endpoint on which wedge has to be set. + * + * Sets the halt feature with the clear requests ignored. + */ +static int s3c_hsudc_set_wedge(struct usb_ep *_ep) +{ + struct s3c_hsudc_ep *hsep = our_ep(_ep); + + if (!hsep) + return -EINVAL; + + hsep->wedge = 1; + return usb_ep_set_halt(_ep); +} + +/** s3c_hsudc_handle_reqfeat - Handle set feature or clear feature requests. + * @_ep: Device controller on which the set/clear feature needs to be handled. + * @ctrl: Control request as received on the endpoint 0. + * + * Handle set feature or clear feature control requests on the control endpoint. + */ +static int s3c_hsudc_handle_reqfeat(struct s3c_hsudc *hsudc, + struct usb_ctrlrequest *ctrl) +{ + struct s3c_hsudc_ep *hsep; + bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE); + u8 ep_num = ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK; + + if (ctrl->bRequestType == USB_RECIP_ENDPOINT) { + hsep = &hsudc->ep[ep_num]; + switch (le16_to_cpu(ctrl->wValue)) { + case USB_ENDPOINT_HALT: + if (set || (!set && !hsep->wedge)) + s3c_hsudc_set_halt(&hsep->ep, set); + return 0; + } + } + + return -ENOENT; +} + +/** + * s3c_hsudc_process_req_status - Handle get status control request. + * @hsudc: Device controller on which get status request has be handled. + * @ctrl: Control request as received on the endpoint 0. + * + * Handle get status control request received on control endpoint. + */ +static void s3c_hsudc_process_req_status(struct s3c_hsudc *hsudc, + struct usb_ctrlrequest *ctrl) +{ + struct s3c_hsudc_ep *hsep0 = &hsudc->ep[0]; + struct s3c_hsudc_req hsreq; + struct s3c_hsudc_ep *hsep; + __le16 reply; + u8 epnum; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + reply = cpu_to_le16(0); + break; + + case USB_RECIP_INTERFACE: + reply = cpu_to_le16(0); + break; + + case USB_RECIP_ENDPOINT: + epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + hsep = &hsudc->ep[epnum]; + reply = cpu_to_le16(hsep->stopped ? 1 : 0); + break; + } + + INIT_LIST_HEAD(&hsreq.queue); + hsreq.req.length = 2; + hsreq.req.buf = &reply; + hsreq.req.actual = 0; + hsreq.req.complete = NULL; + s3c_hsudc_write_fifo(hsep0, &hsreq); +} + +/** + * s3c_hsudc_process_setup - Process control request received on endpoint 0. + * @hsudc: Device controller on which control request has been received. + * + * Read the control request received on endpoint 0, decode it and handle + * the request. + */ +static void s3c_hsudc_process_setup(struct s3c_hsudc *hsudc) +{ + struct s3c_hsudc_ep *hsep = &hsudc->ep[0]; + struct usb_ctrlrequest ctrl = {0}; + int ret; + + s3c_hsudc_nuke_ep(hsep, -EPROTO); + s3c_hsudc_read_setup_pkt(hsudc, (u16 *)&ctrl); + + if (ctrl.bRequestType & USB_DIR_IN) { + hsep->bEndpointAddress |= USB_DIR_IN; + hsudc->ep0state = DATA_STATE_XMIT; + } else { + hsep->bEndpointAddress &= ~USB_DIR_IN; + hsudc->ep0state = DATA_STATE_RECV; + } + + switch (ctrl.bRequest) { + case USB_REQ_SET_ADDRESS: + if (ctrl.bRequestType != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + hsudc->ep0state = WAIT_FOR_SETUP; + return; + + case USB_REQ_GET_STATUS: + if ((ctrl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) + break; + s3c_hsudc_process_req_status(hsudc, &ctrl); + return; + + case USB_REQ_SET_FEATURE: + case USB_REQ_CLEAR_FEATURE: + if ((ctrl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) + break; + s3c_hsudc_handle_reqfeat(hsudc, &ctrl); + hsudc->ep0state = WAIT_FOR_SETUP; + return; + } + + if (hsudc->driver) { + spin_unlock(&hsudc->lock); + ret = hsudc->driver->setup(&hsudc->gadget, &ctrl); + spin_lock(&hsudc->lock); + + if (ctrl.bRequest == USB_REQ_SET_CONFIGURATION) { + hsep->bEndpointAddress &= ~USB_DIR_IN; + hsudc->ep0state = WAIT_FOR_SETUP; + } + + if (ret < 0) { + dev_err(hsudc->dev, "setup failed, returned %d\n", + ret); + s3c_hsudc_set_halt(&hsep->ep, 1); + hsudc->ep0state = WAIT_FOR_SETUP; + hsep->bEndpointAddress &= ~USB_DIR_IN; + } + } +} + +/** s3c_hsudc_handle_ep0_intr - Handle endpoint 0 interrupt. + * @hsudc: Device controller on which endpoint 0 interrupt has occured. + * + * Handle endpoint 0 interrupt when it occurs. EP0 interrupt could occur + * when a stall handshake is sent to host or data is sent/received on + * endpoint 0. + */ +static void s3c_hsudc_handle_ep0_intr(struct s3c_hsudc *hsudc) +{ + struct s3c_hsudc_ep *hsep = &hsudc->ep[0]; + struct s3c_hsudc_req *hsreq; + u32 csr = readl(hsudc->regs + S3C_EP0SR); + u32 ecr; + + if (csr & S3C_EP0SR_STALL) { + ecr = readl(hsudc->regs + S3C_EP0CR); + ecr &= ~(S3C_ECR_STALL | S3C_ECR_FLUSH); + writel(ecr, hsudc->regs + S3C_EP0CR); + + writel(S3C_EP0SR_STALL, hsudc->regs + S3C_EP0SR); + hsep->stopped = 0; + + s3c_hsudc_nuke_ep(hsep, -ECONNABORTED); + hsudc->ep0state = WAIT_FOR_SETUP; + hsep->bEndpointAddress &= ~USB_DIR_IN; + return; + } + + if (csr & S3C_EP0SR_TX_SUCCESS) { + writel(S3C_EP0SR_TX_SUCCESS, hsudc->regs + S3C_EP0SR); + if (ep_is_in(hsep)) { + if (list_empty(&hsep->queue)) + return; + + hsreq = list_entry(hsep->queue.next, + struct s3c_hsudc_req, queue); + s3c_hsudc_write_fifo(hsep, hsreq); + } + } + + if (csr & S3C_EP0SR_RX_SUCCESS) { + if (hsudc->ep0state == WAIT_FOR_SETUP) + s3c_hsudc_process_setup(hsudc); + else { + if (!ep_is_in(hsep)) { + if (list_empty(&hsep->queue)) + return; + hsreq = list_entry(hsep->queue.next, + struct s3c_hsudc_req, queue); + s3c_hsudc_read_fifo(hsep, hsreq); + } + } + } +} + +/** + * s3c_hsudc_ep_enable - Enable a endpoint. + * @_ep: The endpoint to be enabled. + * @desc: Endpoint descriptor. + * + * Enables a endpoint when called from the gadget driver. Endpoint stall if + * any is cleared, transfer type is configured and endpoint interrupt is + * enabled. + */ +static int s3c_hsudc_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct s3c_hsudc_ep *hsep; + struct s3c_hsudc *hsudc; + unsigned long flags; + u32 ecr = 0; + + hsep = our_ep(_ep); + if (!_ep || !desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || hsep->bEndpointAddress != desc->bEndpointAddress + || ep_maxpacket(hsep) < usb_endpoint_maxp(desc)) + return -EINVAL; + + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && usb_endpoint_maxp(desc) != ep_maxpacket(hsep)) + || !desc->wMaxPacketSize) + return -ERANGE; + + hsudc = hsep->dev; + if (!hsudc->driver || hsudc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&hsudc->lock, flags); + + set_index(hsudc, hsep->bEndpointAddress); + ecr |= ((usb_endpoint_xfer_int(desc)) ? S3C_ECR_IEMS : S3C_ECR_DUEN); + writel(ecr, hsudc->regs + S3C_ECR); + + hsep->stopped = hsep->wedge = 0; + hsep->ep.desc = desc; + hsep->ep.maxpacket = usb_endpoint_maxp(desc); + + s3c_hsudc_set_halt(_ep, 0); + __set_bit(ep_index(hsep), hsudc->regs + S3C_EIER); + + spin_unlock_irqrestore(&hsudc->lock, flags); + return 0; +} + +/** + * s3c_hsudc_ep_disable - Disable a endpoint. + * @_ep: The endpoint to be disabled. + * @desc: Endpoint descriptor. + * + * Disables a endpoint when called from the gadget driver. + */ +static int s3c_hsudc_ep_disable(struct usb_ep *_ep) +{ + struct s3c_hsudc_ep *hsep = our_ep(_ep); + struct s3c_hsudc *hsudc = hsep->dev; + unsigned long flags; + + if (!_ep || !hsep->ep.desc) + return -EINVAL; + + spin_lock_irqsave(&hsudc->lock, flags); + + set_index(hsudc, hsep->bEndpointAddress); + __clear_bit(ep_index(hsep), hsudc->regs + S3C_EIER); + + s3c_hsudc_nuke_ep(hsep, -ESHUTDOWN); + + hsep->ep.desc = NULL; + hsep->stopped = 1; + + spin_unlock_irqrestore(&hsudc->lock, flags); + return 0; +} + +/** + * s3c_hsudc_alloc_request - Allocate a new request. + * @_ep: Endpoint for which request is allocated (not used). + * @gfp_flags: Flags used for the allocation. + * + * Allocates a single transfer request structure when called from gadget driver. + */ +static struct usb_request *s3c_hsudc_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct s3c_hsudc_req *hsreq; + + hsreq = kzalloc(sizeof(*hsreq), gfp_flags); + if (!hsreq) + return NULL; + + INIT_LIST_HEAD(&hsreq->queue); + return &hsreq->req; +} + +/** + * s3c_hsudc_free_request - Deallocate a request. + * @ep: Endpoint for which request is deallocated (not used). + * @_req: Request to be deallocated. + * + * Allocates a single transfer request structure when called from gadget driver. + */ +static void s3c_hsudc_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct s3c_hsudc_req *hsreq; + + hsreq = our_req(_req); + WARN_ON(!list_empty(&hsreq->queue)); + kfree(hsreq); +} + +/** + * s3c_hsudc_queue - Queue a transfer request for the endpoint. + * @_ep: Endpoint for which the request is queued. + * @_req: Request to be queued. + * @gfp_flags: Not used. + * + * Start or enqueue a request for a endpoint when called from gadget driver. + */ +static int s3c_hsudc_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct s3c_hsudc_req *hsreq; + struct s3c_hsudc_ep *hsep; + struct s3c_hsudc *hsudc; + unsigned long flags; + u32 offset; + u32 csr; + + hsreq = our_req(_req); + if ((!_req || !_req->complete || !_req->buf || + !list_empty(&hsreq->queue))) + return -EINVAL; + + hsep = our_ep(_ep); + hsudc = hsep->dev; + if (!hsudc->driver || hsudc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&hsudc->lock, flags); + set_index(hsudc, hsep->bEndpointAddress); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + if (!ep_index(hsep) && _req->length == 0) { + hsudc->ep0state = WAIT_FOR_SETUP; + s3c_hsudc_complete_request(hsep, hsreq, 0); + spin_unlock_irqrestore(&hsudc->lock, flags); + return 0; + } + + if (list_empty(&hsep->queue) && !hsep->stopped) { + offset = (ep_index(hsep)) ? S3C_ESR : S3C_EP0SR; + if (ep_is_in(hsep)) { + csr = readl(hsudc->regs + offset); + if (!(csr & S3C_ESR_TX_SUCCESS) && + (s3c_hsudc_write_fifo(hsep, hsreq) == 1)) + hsreq = NULL; + } else { + csr = readl(hsudc->regs + offset); + if ((csr & S3C_ESR_RX_SUCCESS) + && (s3c_hsudc_read_fifo(hsep, hsreq) == 1)) + hsreq = NULL; + } + } + + if (hsreq) + list_add_tail(&hsreq->queue, &hsep->queue); + + spin_unlock_irqrestore(&hsudc->lock, flags); + return 0; +} + +/** + * s3c_hsudc_dequeue - Dequeue a transfer request from an endpoint. + * @_ep: Endpoint from which the request is dequeued. + * @_req: Request to be dequeued. + * + * Dequeue a request from a endpoint when called from gadget driver. + */ +static int s3c_hsudc_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct s3c_hsudc_ep *hsep = our_ep(_ep); + struct s3c_hsudc *hsudc = hsep->dev; + struct s3c_hsudc_req *hsreq; + unsigned long flags; + + hsep = our_ep(_ep); + if (!_ep || hsep->ep.name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&hsudc->lock, flags); + + list_for_each_entry(hsreq, &hsep->queue, queue) { + if (&hsreq->req == _req) + break; + } + if (&hsreq->req != _req) { + spin_unlock_irqrestore(&hsudc->lock, flags); + return -EINVAL; + } + + set_index(hsudc, hsep->bEndpointAddress); + s3c_hsudc_complete_request(hsep, hsreq, -ECONNRESET); + + spin_unlock_irqrestore(&hsudc->lock, flags); + return 0; +} + +static struct usb_ep_ops s3c_hsudc_ep_ops = { + .enable = s3c_hsudc_ep_enable, + .disable = s3c_hsudc_ep_disable, + .alloc_request = s3c_hsudc_alloc_request, + .free_request = s3c_hsudc_free_request, + .queue = s3c_hsudc_queue, + .dequeue = s3c_hsudc_dequeue, + .set_halt = s3c_hsudc_set_halt, + .set_wedge = s3c_hsudc_set_wedge, +}; + +/** + * s3c_hsudc_initep - Initialize a endpoint to default state. + * @hsudc - Reference to the device controller. + * @hsep - Endpoint to be initialized. + * @epnum - Address to be assigned to the endpoint. + * + * Initialize a endpoint with default configuration. + */ +static void s3c_hsudc_initep(struct s3c_hsudc *hsudc, + struct s3c_hsudc_ep *hsep, int epnum) +{ + char *dir; + + if ((epnum % 2) == 0) { + dir = "out"; + } else { + dir = "in"; + hsep->bEndpointAddress = USB_DIR_IN; + } + + hsep->bEndpointAddress |= epnum; + if (epnum) + snprintf(hsep->name, sizeof(hsep->name), "ep%d%s", epnum, dir); + else + snprintf(hsep->name, sizeof(hsep->name), "%s", ep0name); + + INIT_LIST_HEAD(&hsep->queue); + INIT_LIST_HEAD(&hsep->ep.ep_list); + if (epnum) + list_add_tail(&hsep->ep.ep_list, &hsudc->gadget.ep_list); + + hsep->dev = hsudc; + hsep->ep.name = hsep->name; + hsep->ep.maxpacket = epnum ? 512 : 64; + hsep->ep.ops = &s3c_hsudc_ep_ops; + hsep->fifo = hsudc->regs + S3C_BR(epnum); + hsep->ep.desc = NULL; + hsep->stopped = 0; + hsep->wedge = 0; + + set_index(hsudc, epnum); + writel(hsep->ep.maxpacket, hsudc->regs + S3C_MPR); +} + +/** + * s3c_hsudc_setup_ep - Configure all endpoints to default state. + * @hsudc: Reference to device controller. + * + * Configures all endpoints to default state. + */ +static void s3c_hsudc_setup_ep(struct s3c_hsudc *hsudc) +{ + int epnum; + + hsudc->ep0state = WAIT_FOR_SETUP; + INIT_LIST_HEAD(&hsudc->gadget.ep_list); + for (epnum = 0; epnum < hsudc->pd->epnum; epnum++) + s3c_hsudc_initep(hsudc, &hsudc->ep[epnum], epnum); +} + +/** + * s3c_hsudc_reconfig - Reconfigure the device controller to default state. + * @hsudc: Reference to device controller. + * + * Reconfigures the device controller registers to a default state. + */ +static void s3c_hsudc_reconfig(struct s3c_hsudc *hsudc) +{ + writel(0xAA, hsudc->regs + S3C_EDR); + writel(1, hsudc->regs + S3C_EIER); + writel(0, hsudc->regs + S3C_TR); + writel(S3C_SCR_DTZIEN_EN | S3C_SCR_RRD_EN | S3C_SCR_SUS_EN | + S3C_SCR_RST_EN, hsudc->regs + S3C_SCR); + writel(0, hsudc->regs + S3C_EP0CR); + + s3c_hsudc_setup_ep(hsudc); +} + +/** + * s3c_hsudc_irq - Interrupt handler for device controller. + * @irq: Not used. + * @_dev: Reference to the device controller. + * + * Interrupt handler for the device controller. This handler handles controller + * interrupts and endpoint interrupts. + */ +static irqreturn_t s3c_hsudc_irq(int irq, void *_dev) +{ + struct s3c_hsudc *hsudc = _dev; + struct s3c_hsudc_ep *hsep; + u32 ep_intr; + u32 sys_status; + u32 ep_idx; + + spin_lock(&hsudc->lock); + + sys_status = readl(hsudc->regs + S3C_SSR); + ep_intr = readl(hsudc->regs + S3C_EIR) & 0x3FF; + + if (!ep_intr && !(sys_status & S3C_SSR_DTZIEN_EN)) { + spin_unlock(&hsudc->lock); + return IRQ_HANDLED; + } + + if (sys_status) { + if (sys_status & S3C_SSR_VBUSON) + writel(S3C_SSR_VBUSON, hsudc->regs + S3C_SSR); + + if (sys_status & S3C_SSR_ERR) + writel(S3C_SSR_ERR, hsudc->regs + S3C_SSR); + + if (sys_status & S3C_SSR_SDE) { + writel(S3C_SSR_SDE, hsudc->regs + S3C_SSR); + hsudc->gadget.speed = (sys_status & S3C_SSR_HSP) ? + USB_SPEED_HIGH : USB_SPEED_FULL; + } + + if (sys_status & S3C_SSR_SUSPEND) { + writel(S3C_SSR_SUSPEND, hsudc->regs + S3C_SSR); + if (hsudc->gadget.speed != USB_SPEED_UNKNOWN + && hsudc->driver && hsudc->driver->suspend) + hsudc->driver->suspend(&hsudc->gadget); + } + + if (sys_status & S3C_SSR_RESUME) { + writel(S3C_SSR_RESUME, hsudc->regs + S3C_SSR); + if (hsudc->gadget.speed != USB_SPEED_UNKNOWN + && hsudc->driver && hsudc->driver->resume) + hsudc->driver->resume(&hsudc->gadget); + } + + if (sys_status & S3C_SSR_RESET) { + writel(S3C_SSR_RESET, hsudc->regs + S3C_SSR); + for (ep_idx = 0; ep_idx < hsudc->pd->epnum; ep_idx++) { + hsep = &hsudc->ep[ep_idx]; + hsep->stopped = 1; + s3c_hsudc_nuke_ep(hsep, -ECONNRESET); + } + s3c_hsudc_reconfig(hsudc); + hsudc->ep0state = WAIT_FOR_SETUP; + } + } + + if (ep_intr & S3C_EIR_EP0) { + writel(S3C_EIR_EP0, hsudc->regs + S3C_EIR); + set_index(hsudc, 0); + s3c_hsudc_handle_ep0_intr(hsudc); + } + + ep_intr >>= 1; + ep_idx = 1; + while (ep_intr) { + if (ep_intr & 1) { + hsep = &hsudc->ep[ep_idx]; + set_index(hsudc, ep_idx); + writel(1 << ep_idx, hsudc->regs + S3C_EIR); + if (ep_is_in(hsep)) + s3c_hsudc_epin_intr(hsudc, ep_idx); + else + s3c_hsudc_epout_intr(hsudc, ep_idx); + } + ep_intr >>= 1; + ep_idx++; + } + + spin_unlock(&hsudc->lock); + return IRQ_HANDLED; +} + +static int s3c_hsudc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct s3c_hsudc *hsudc = to_hsudc(gadget); + int ret; + + if (!driver + || driver->max_speed < USB_SPEED_FULL + || !driver->setup) + return -EINVAL; + + if (!hsudc) + return -ENODEV; + + if (hsudc->driver) + return -EBUSY; + + hsudc->driver = driver; + + ret = regulator_bulk_enable(ARRAY_SIZE(hsudc->supplies), + hsudc->supplies); + if (ret != 0) { + dev_err(hsudc->dev, "failed to enable supplies: %d\n", ret); + goto err_supplies; + } + + /* connect to bus through transceiver */ + if (!IS_ERR_OR_NULL(hsudc->transceiver)) { + ret = otg_set_peripheral(hsudc->transceiver->otg, + &hsudc->gadget); + if (ret) { + dev_err(hsudc->dev, "%s: can't bind to transceiver\n", + hsudc->gadget.name); + goto err_otg; + } + } + + enable_irq(hsudc->irq); + dev_info(hsudc->dev, "bound driver %s\n", driver->driver.name); + + s3c_hsudc_reconfig(hsudc); + + pm_runtime_get_sync(hsudc->dev); + + s3c_hsudc_init_phy(); + if (hsudc->pd->gpio_init) + hsudc->pd->gpio_init(); + + return 0; +err_otg: + regulator_bulk_disable(ARRAY_SIZE(hsudc->supplies), hsudc->supplies); +err_supplies: + hsudc->driver = NULL; + return ret; +} + +static int s3c_hsudc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct s3c_hsudc *hsudc = to_hsudc(gadget); + unsigned long flags; + + if (!hsudc) + return -ENODEV; + + if (!driver || driver != hsudc->driver) + return -EINVAL; + + spin_lock_irqsave(&hsudc->lock, flags); + hsudc->driver = NULL; + hsudc->gadget.speed = USB_SPEED_UNKNOWN; + s3c_hsudc_uninit_phy(); + + pm_runtime_put(hsudc->dev); + + if (hsudc->pd->gpio_uninit) + hsudc->pd->gpio_uninit(); + s3c_hsudc_stop_activity(hsudc); + spin_unlock_irqrestore(&hsudc->lock, flags); + + if (!IS_ERR_OR_NULL(hsudc->transceiver)) + (void) otg_set_peripheral(hsudc->transceiver->otg, NULL); + + disable_irq(hsudc->irq); + + regulator_bulk_disable(ARRAY_SIZE(hsudc->supplies), hsudc->supplies); + + dev_info(hsudc->dev, "unregistered gadget driver '%s'\n", + driver->driver.name); + return 0; +} + +static inline u32 s3c_hsudc_read_frameno(struct s3c_hsudc *hsudc) +{ + return readl(hsudc->regs + S3C_FNR) & 0x3FF; +} + +static int s3c_hsudc_gadget_getframe(struct usb_gadget *gadget) +{ + return s3c_hsudc_read_frameno(to_hsudc(gadget)); +} + +static int s3c_hsudc_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct s3c_hsudc *hsudc = to_hsudc(gadget); + + if (!hsudc) + return -ENODEV; + + if (!IS_ERR_OR_NULL(hsudc->transceiver)) + return usb_phy_set_power(hsudc->transceiver, mA); + + return -EOPNOTSUPP; +} + +static const struct usb_gadget_ops s3c_hsudc_gadget_ops = { + .get_frame = s3c_hsudc_gadget_getframe, + .udc_start = s3c_hsudc_start, + .udc_stop = s3c_hsudc_stop, + .vbus_draw = s3c_hsudc_vbus_draw, +}; + +static int s3c_hsudc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct s3c_hsudc *hsudc; + struct s3c24xx_hsudc_platdata *pd = pdev->dev.platform_data; + int ret, i; + + hsudc = devm_kzalloc(&pdev->dev, sizeof(struct s3c_hsudc) + + sizeof(struct s3c_hsudc_ep) * pd->epnum, + GFP_KERNEL); + if (!hsudc) { + dev_err(dev, "cannot allocate memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, dev); + hsudc->dev = dev; + hsudc->pd = pdev->dev.platform_data; + + hsudc->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + + for (i = 0; i < ARRAY_SIZE(hsudc->supplies); i++) + hsudc->supplies[i].supply = s3c_hsudc_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(hsudc->supplies), + hsudc->supplies); + if (ret != 0) { + dev_err(dev, "failed to request supplies: %d\n", ret); + goto err_supplies; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + hsudc->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hsudc->regs)) { + ret = PTR_ERR(hsudc->regs); + goto err_res; + } + + spin_lock_init(&hsudc->lock); + + hsudc->gadget.max_speed = USB_SPEED_HIGH; + hsudc->gadget.ops = &s3c_hsudc_gadget_ops; + hsudc->gadget.name = dev_name(dev); + hsudc->gadget.ep0 = &hsudc->ep[0].ep; + hsudc->gadget.is_otg = 0; + hsudc->gadget.is_a_peripheral = 0; + hsudc->gadget.speed = USB_SPEED_UNKNOWN; + + s3c_hsudc_setup_ep(hsudc); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(dev, "unable to obtain IRQ number\n"); + goto err_res; + } + hsudc->irq = ret; + + ret = devm_request_irq(&pdev->dev, hsudc->irq, s3c_hsudc_irq, 0, + driver_name, hsudc); + if (ret < 0) { + dev_err(dev, "irq request failed\n"); + goto err_res; + } + + hsudc->uclk = devm_clk_get(&pdev->dev, "usb-device"); + if (IS_ERR(hsudc->uclk)) { + dev_err(dev, "failed to find usb-device clock source\n"); + ret = PTR_ERR(hsudc->uclk); + goto err_res; + } + clk_enable(hsudc->uclk); + + local_irq_disable(); + + disable_irq(hsudc->irq); + local_irq_enable(); + + ret = usb_add_gadget_udc(&pdev->dev, &hsudc->gadget); + if (ret) + goto err_add_udc; + + pm_runtime_enable(dev); + + return 0; +err_add_udc: +err_add_device: + clk_disable(hsudc->uclk); +err_res: + if (!IS_ERR_OR_NULL(hsudc->transceiver)) + usb_put_phy(hsudc->transceiver); + +err_supplies: + return ret; +} + +static struct platform_driver s3c_hsudc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "s3c-hsudc", + }, + .probe = s3c_hsudc_probe, +}; + +module_platform_driver(s3c_hsudc_driver); + +MODULE_DESCRIPTION("Samsung S3C24XX USB high-speed controller driver"); +MODULE_AUTHOR("Thomas Abraham "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c-hsudc"); diff --git a/drivers/usb/gadget/s3c2410_udc.c b/drivers/usb/gadget/s3c2410_udc.c new file mode 100644 index 00000000..09c4f70c --- /dev/null +++ b/drivers/usb/gadget/s3c2410_udc.c @@ -0,0 +1,2043 @@ +/* + * linux/drivers/usb/gadget/s3c2410_udc.c + * + * Samsung S3C24xx series on-chip full speed USB device controllers + * + * Copyright (C) 2004-2007 Herbert Pötzl - Arnaud Patard + * Additional cleanups by Ben Dooks + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#define pr_fmt(fmt) "s3c2410_udc: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + + +#include "s3c2410_udc.h" + +#define DRIVER_DESC "S3C2410 USB Device Controller Gadget" +#define DRIVER_VERSION "29 Apr 2007" +#define DRIVER_AUTHOR "Herbert Pötzl , " \ + "Arnaud Patard " + +static const char gadget_name[] = "s3c2410_udc"; +static const char driver_desc[] = DRIVER_DESC; + +static struct s3c2410_udc *the_controller; +static struct clk *udc_clock; +static struct clk *usb_bus_clock; +static void __iomem *base_addr; +static u64 rsrc_start; +static u64 rsrc_len; +static struct dentry *s3c2410_udc_debugfs_root; + +static inline u32 udc_read(u32 reg) +{ + return readb(base_addr + reg); +} + +static inline void udc_write(u32 value, u32 reg) +{ + writeb(value, base_addr + reg); +} + +static inline void udc_writeb(void __iomem *base, u32 value, u32 reg) +{ + writeb(value, base + reg); +} + +static struct s3c2410_udc_mach_info *udc_info; + +/*************************** DEBUG FUNCTION ***************************/ +#define DEBUG_NORMAL 1 +#define DEBUG_VERBOSE 2 + +#ifdef CONFIG_USB_S3C2410_DEBUG +#define USB_S3C2410_DEBUG_LEVEL 0 + +static uint32_t s3c2410_ticks = 0; + +static int dprintk(int level, const char *fmt, ...) +{ + static char printk_buf[1024]; + static long prevticks; + static int invocation; + va_list args; + int len; + + if (level > USB_S3C2410_DEBUG_LEVEL) + return 0; + + if (s3c2410_ticks != prevticks) { + prevticks = s3c2410_ticks; + invocation = 0; + } + + len = scnprintf(printk_buf, + sizeof(printk_buf), "%1lu.%02d USB: ", + prevticks, invocation++); + + va_start(args, fmt); + len = vscnprintf(printk_buf+len, + sizeof(printk_buf)-len, fmt, args); + va_end(args); + + return pr_debug("%s", printk_buf); +} +#else +static int dprintk(int level, const char *fmt, ...) +{ + return 0; +} +#endif +static int s3c2410_udc_debugfs_seq_show(struct seq_file *m, void *p) +{ + u32 addr_reg, pwr_reg, ep_int_reg, usb_int_reg; + u32 ep_int_en_reg, usb_int_en_reg, ep0_csr; + u32 ep1_i_csr1, ep1_i_csr2, ep1_o_csr1, ep1_o_csr2; + u32 ep2_i_csr1, ep2_i_csr2, ep2_o_csr1, ep2_o_csr2; + + addr_reg = udc_read(S3C2410_UDC_FUNC_ADDR_REG); + pwr_reg = udc_read(S3C2410_UDC_PWR_REG); + ep_int_reg = udc_read(S3C2410_UDC_EP_INT_REG); + usb_int_reg = udc_read(S3C2410_UDC_USB_INT_REG); + ep_int_en_reg = udc_read(S3C2410_UDC_EP_INT_EN_REG); + usb_int_en_reg = udc_read(S3C2410_UDC_USB_INT_EN_REG); + udc_write(0, S3C2410_UDC_INDEX_REG); + ep0_csr = udc_read(S3C2410_UDC_IN_CSR1_REG); + udc_write(1, S3C2410_UDC_INDEX_REG); + ep1_i_csr1 = udc_read(S3C2410_UDC_IN_CSR1_REG); + ep1_i_csr2 = udc_read(S3C2410_UDC_IN_CSR2_REG); + ep1_o_csr1 = udc_read(S3C2410_UDC_IN_CSR1_REG); + ep1_o_csr2 = udc_read(S3C2410_UDC_IN_CSR2_REG); + udc_write(2, S3C2410_UDC_INDEX_REG); + ep2_i_csr1 = udc_read(S3C2410_UDC_IN_CSR1_REG); + ep2_i_csr2 = udc_read(S3C2410_UDC_IN_CSR2_REG); + ep2_o_csr1 = udc_read(S3C2410_UDC_IN_CSR1_REG); + ep2_o_csr2 = udc_read(S3C2410_UDC_IN_CSR2_REG); + + seq_printf(m, "FUNC_ADDR_REG : 0x%04X\n" + "PWR_REG : 0x%04X\n" + "EP_INT_REG : 0x%04X\n" + "USB_INT_REG : 0x%04X\n" + "EP_INT_EN_REG : 0x%04X\n" + "USB_INT_EN_REG : 0x%04X\n" + "EP0_CSR : 0x%04X\n" + "EP1_I_CSR1 : 0x%04X\n" + "EP1_I_CSR2 : 0x%04X\n" + "EP1_O_CSR1 : 0x%04X\n" + "EP1_O_CSR2 : 0x%04X\n" + "EP2_I_CSR1 : 0x%04X\n" + "EP2_I_CSR2 : 0x%04X\n" + "EP2_O_CSR1 : 0x%04X\n" + "EP2_O_CSR2 : 0x%04X\n", + addr_reg, pwr_reg, ep_int_reg, usb_int_reg, + ep_int_en_reg, usb_int_en_reg, ep0_csr, + ep1_i_csr1, ep1_i_csr2, ep1_o_csr1, ep1_o_csr2, + ep2_i_csr1, ep2_i_csr2, ep2_o_csr1, ep2_o_csr2 + ); + + return 0; +} + +static int s3c2410_udc_debugfs_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, s3c2410_udc_debugfs_seq_show, NULL); +} + +static const struct file_operations s3c2410_udc_debugfs_fops = { + .open = s3c2410_udc_debugfs_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +/* io macros */ + +static inline void s3c2410_udc_clear_ep0_opr(void __iomem *base) +{ + udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + udc_writeb(base, S3C2410_UDC_EP0_CSR_SOPKTRDY, + S3C2410_UDC_EP0_CSR_REG); +} + +static inline void s3c2410_udc_clear_ep0_sst(void __iomem *base) +{ + udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + writeb(0x00, base + S3C2410_UDC_EP0_CSR_REG); +} + +static inline void s3c2410_udc_clear_ep0_se(void __iomem *base) +{ + udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + udc_writeb(base, S3C2410_UDC_EP0_CSR_SSE, S3C2410_UDC_EP0_CSR_REG); +} + +static inline void s3c2410_udc_set_ep0_ipr(void __iomem *base) +{ + udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + udc_writeb(base, S3C2410_UDC_EP0_CSR_IPKRDY, S3C2410_UDC_EP0_CSR_REG); +} + +static inline void s3c2410_udc_set_ep0_de(void __iomem *base) +{ + udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + udc_writeb(base, S3C2410_UDC_EP0_CSR_DE, S3C2410_UDC_EP0_CSR_REG); +} + +inline void s3c2410_udc_set_ep0_ss(void __iomem *b) +{ + udc_writeb(b, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + udc_writeb(b, S3C2410_UDC_EP0_CSR_SENDSTL, S3C2410_UDC_EP0_CSR_REG); +} + +static inline void s3c2410_udc_set_ep0_de_out(void __iomem *base) +{ + udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + + udc_writeb(base, (S3C2410_UDC_EP0_CSR_SOPKTRDY + | S3C2410_UDC_EP0_CSR_DE), + S3C2410_UDC_EP0_CSR_REG); +} + +static inline void s3c2410_udc_set_ep0_sse_out(void __iomem *base) +{ + udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + udc_writeb(base, (S3C2410_UDC_EP0_CSR_SOPKTRDY + | S3C2410_UDC_EP0_CSR_SSE), + S3C2410_UDC_EP0_CSR_REG); +} + +static inline void s3c2410_udc_set_ep0_de_in(void __iomem *base) +{ + udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + udc_writeb(base, (S3C2410_UDC_EP0_CSR_IPKRDY + | S3C2410_UDC_EP0_CSR_DE), + S3C2410_UDC_EP0_CSR_REG); +} + +/*------------------------- I/O ----------------------------------*/ + +/* + * s3c2410_udc_done + */ +static void s3c2410_udc_done(struct s3c2410_ep *ep, + struct s3c2410_request *req, int status) +{ + unsigned halted = ep->halted; + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + ep->halted = 1; + req->req.complete(&ep->ep, &req->req); + ep->halted = halted; +} + +static void s3c2410_udc_nuke(struct s3c2410_udc *udc, + struct s3c2410_ep *ep, int status) +{ + /* Sanity check */ + if (&ep->queue == NULL) + return; + + while (!list_empty(&ep->queue)) { + struct s3c2410_request *req; + req = list_entry(ep->queue.next, struct s3c2410_request, + queue); + s3c2410_udc_done(ep, req, status); + } +} + +static inline void s3c2410_udc_clear_ep_state(struct s3c2410_udc *dev) +{ + unsigned i; + + /* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint + * fifos, and pending transactions mustn't be continued in any case. + */ + + for (i = 1; i < S3C2410_ENDPOINTS; i++) + s3c2410_udc_nuke(dev, &dev->ep[i], -ECONNABORTED); +} + +static inline int s3c2410_udc_fifo_count_out(void) +{ + int tmp; + + tmp = udc_read(S3C2410_UDC_OUT_FIFO_CNT2_REG) << 8; + tmp |= udc_read(S3C2410_UDC_OUT_FIFO_CNT1_REG); + return tmp; +} + +/* + * s3c2410_udc_write_packet + */ +static inline int s3c2410_udc_write_packet(int fifo, + struct s3c2410_request *req, + unsigned max) +{ + unsigned len = min(req->req.length - req->req.actual, max); + u8 *buf = req->req.buf + req->req.actual; + + prefetch(buf); + + dprintk(DEBUG_VERBOSE, "%s %d %d %d %d\n", __func__, + req->req.actual, req->req.length, len, req->req.actual + len); + + req->req.actual += len; + + udelay(5); + writesb(base_addr + fifo, buf, len); + return len; +} + +/* + * s3c2410_udc_write_fifo + * + * return: 0 = still running, 1 = completed, negative = errno + */ +static int s3c2410_udc_write_fifo(struct s3c2410_ep *ep, + struct s3c2410_request *req) +{ + unsigned count; + int is_last; + u32 idx; + int fifo_reg; + u32 ep_csr; + + idx = ep->bEndpointAddress & 0x7F; + switch (idx) { + default: + idx = 0; + case 0: + fifo_reg = S3C2410_UDC_EP0_FIFO_REG; + break; + case 1: + fifo_reg = S3C2410_UDC_EP1_FIFO_REG; + break; + case 2: + fifo_reg = S3C2410_UDC_EP2_FIFO_REG; + break; + case 3: + fifo_reg = S3C2410_UDC_EP3_FIFO_REG; + break; + case 4: + fifo_reg = S3C2410_UDC_EP4_FIFO_REG; + break; + } + + count = s3c2410_udc_write_packet(fifo_reg, req, ep->ep.maxpacket); + + /* last packet is often short (sometimes a zlp) */ + if (count != ep->ep.maxpacket) + is_last = 1; + else if (req->req.length != req->req.actual || req->req.zero) + is_last = 0; + else + is_last = 2; + + /* Only ep0 debug messages are interesting */ + if (idx == 0) + dprintk(DEBUG_NORMAL, + "Written ep%d %d.%d of %d b [last %d,z %d]\n", + idx, count, req->req.actual, req->req.length, + is_last, req->req.zero); + + if (is_last) { + /* The order is important. It prevents sending 2 packets + * at the same time */ + + if (idx == 0) { + /* Reset signal => no need to say 'data sent' */ + if (!(udc_read(S3C2410_UDC_USB_INT_REG) + & S3C2410_UDC_USBINT_RESET)) + s3c2410_udc_set_ep0_de_in(base_addr); + ep->dev->ep0state = EP0_IDLE; + } else { + udc_write(idx, S3C2410_UDC_INDEX_REG); + ep_csr = udc_read(S3C2410_UDC_IN_CSR1_REG); + udc_write(idx, S3C2410_UDC_INDEX_REG); + udc_write(ep_csr | S3C2410_UDC_ICSR1_PKTRDY, + S3C2410_UDC_IN_CSR1_REG); + } + + s3c2410_udc_done(ep, req, 0); + is_last = 1; + } else { + if (idx == 0) { + /* Reset signal => no need to say 'data sent' */ + if (!(udc_read(S3C2410_UDC_USB_INT_REG) + & S3C2410_UDC_USBINT_RESET)) + s3c2410_udc_set_ep0_ipr(base_addr); + } else { + udc_write(idx, S3C2410_UDC_INDEX_REG); + ep_csr = udc_read(S3C2410_UDC_IN_CSR1_REG); + udc_write(idx, S3C2410_UDC_INDEX_REG); + udc_write(ep_csr | S3C2410_UDC_ICSR1_PKTRDY, + S3C2410_UDC_IN_CSR1_REG); + } + } + + return is_last; +} + +static inline int s3c2410_udc_read_packet(int fifo, u8 *buf, + struct s3c2410_request *req, unsigned avail) +{ + unsigned len; + + len = min(req->req.length - req->req.actual, avail); + req->req.actual += len; + + readsb(fifo + base_addr, buf, len); + return len; +} + +/* + * return: 0 = still running, 1 = queue empty, negative = errno + */ +static int s3c2410_udc_read_fifo(struct s3c2410_ep *ep, + struct s3c2410_request *req) +{ + u8 *buf; + u32 ep_csr; + unsigned bufferspace; + int is_last = 1; + unsigned avail; + int fifo_count = 0; + u32 idx; + int fifo_reg; + + idx = ep->bEndpointAddress & 0x7F; + + switch (idx) { + default: + idx = 0; + case 0: + fifo_reg = S3C2410_UDC_EP0_FIFO_REG; + break; + case 1: + fifo_reg = S3C2410_UDC_EP1_FIFO_REG; + break; + case 2: + fifo_reg = S3C2410_UDC_EP2_FIFO_REG; + break; + case 3: + fifo_reg = S3C2410_UDC_EP3_FIFO_REG; + break; + case 4: + fifo_reg = S3C2410_UDC_EP4_FIFO_REG; + break; + } + + if (!req->req.length) + return 1; + + buf = req->req.buf + req->req.actual; + bufferspace = req->req.length - req->req.actual; + if (!bufferspace) { + dprintk(DEBUG_NORMAL, "%s: buffer full!\n", __func__); + return -1; + } + + udc_write(idx, S3C2410_UDC_INDEX_REG); + + fifo_count = s3c2410_udc_fifo_count_out(); + dprintk(DEBUG_NORMAL, "%s fifo count : %d\n", __func__, fifo_count); + + if (fifo_count > ep->ep.maxpacket) + avail = ep->ep.maxpacket; + else + avail = fifo_count; + + fifo_count = s3c2410_udc_read_packet(fifo_reg, buf, req, avail); + + /* checking this with ep0 is not accurate as we already + * read a control request + **/ + if (idx != 0 && fifo_count < ep->ep.maxpacket) { + is_last = 1; + /* overflowed this request? flush extra data */ + if (fifo_count != avail) + req->req.status = -EOVERFLOW; + } else { + is_last = (req->req.length <= req->req.actual) ? 1 : 0; + } + + udc_write(idx, S3C2410_UDC_INDEX_REG); + fifo_count = s3c2410_udc_fifo_count_out(); + + /* Only ep0 debug messages are interesting */ + if (idx == 0) + dprintk(DEBUG_VERBOSE, "%s fifo count : %d [last %d]\n", + __func__, fifo_count, is_last); + + if (is_last) { + if (idx == 0) { + s3c2410_udc_set_ep0_de_out(base_addr); + ep->dev->ep0state = EP0_IDLE; + } else { + udc_write(idx, S3C2410_UDC_INDEX_REG); + ep_csr = udc_read(S3C2410_UDC_OUT_CSR1_REG); + udc_write(idx, S3C2410_UDC_INDEX_REG); + udc_write(ep_csr & ~S3C2410_UDC_OCSR1_PKTRDY, + S3C2410_UDC_OUT_CSR1_REG); + } + + s3c2410_udc_done(ep, req, 0); + } else { + if (idx == 0) { + s3c2410_udc_clear_ep0_opr(base_addr); + } else { + udc_write(idx, S3C2410_UDC_INDEX_REG); + ep_csr = udc_read(S3C2410_UDC_OUT_CSR1_REG); + udc_write(idx, S3C2410_UDC_INDEX_REG); + udc_write(ep_csr & ~S3C2410_UDC_OCSR1_PKTRDY, + S3C2410_UDC_OUT_CSR1_REG); + } + } + + return is_last; +} + +static int s3c2410_udc_read_fifo_crq(struct usb_ctrlrequest *crq) +{ + unsigned char *outbuf = (unsigned char *)crq; + int bytes_read = 0; + + udc_write(0, S3C2410_UDC_INDEX_REG); + + bytes_read = s3c2410_udc_fifo_count_out(); + + dprintk(DEBUG_NORMAL, "%s: fifo_count=%d\n", __func__, bytes_read); + + if (bytes_read > sizeof(struct usb_ctrlrequest)) + bytes_read = sizeof(struct usb_ctrlrequest); + + readsb(S3C2410_UDC_EP0_FIFO_REG + base_addr, outbuf, bytes_read); + + dprintk(DEBUG_VERBOSE, "%s: len=%d %02x:%02x {%x,%x,%x}\n", __func__, + bytes_read, crq->bRequest, crq->bRequestType, + crq->wValue, crq->wIndex, crq->wLength); + + return bytes_read; +} + +static int s3c2410_udc_get_status(struct s3c2410_udc *dev, + struct usb_ctrlrequest *crq) +{ + u16 status = 0; + u8 ep_num = crq->wIndex & 0x7F; + u8 is_in = crq->wIndex & USB_DIR_IN; + + switch (crq->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + break; + + case USB_RECIP_DEVICE: + status = dev->devstatus; + break; + + case USB_RECIP_ENDPOINT: + if (ep_num > 4 || crq->wLength > 2) + return 1; + + if (ep_num == 0) { + udc_write(0, S3C2410_UDC_INDEX_REG); + status = udc_read(S3C2410_UDC_IN_CSR1_REG); + status = status & S3C2410_UDC_EP0_CSR_SENDSTL; + } else { + udc_write(ep_num, S3C2410_UDC_INDEX_REG); + if (is_in) { + status = udc_read(S3C2410_UDC_IN_CSR1_REG); + status = status & S3C2410_UDC_ICSR1_SENDSTL; + } else { + status = udc_read(S3C2410_UDC_OUT_CSR1_REG); + status = status & S3C2410_UDC_OCSR1_SENDSTL; + } + } + + status = status ? 1 : 0; + break; + + default: + return 1; + } + + /* Seems to be needed to get it working. ouch :( */ + udelay(5); + udc_write(status & 0xFF, S3C2410_UDC_EP0_FIFO_REG); + udc_write(status >> 8, S3C2410_UDC_EP0_FIFO_REG); + s3c2410_udc_set_ep0_de_in(base_addr); + + return 0; +} +/*------------------------- usb state machine -------------------------------*/ +static int s3c2410_udc_set_halt(struct usb_ep *_ep, int value); + +static void s3c2410_udc_handle_ep0_idle(struct s3c2410_udc *dev, + struct s3c2410_ep *ep, + struct usb_ctrlrequest *crq, + u32 ep0csr) +{ + int len, ret, tmp; + + /* start control request? */ + if (!(ep0csr & S3C2410_UDC_EP0_CSR_OPKRDY)) + return; + + s3c2410_udc_nuke(dev, ep, -EPROTO); + + len = s3c2410_udc_read_fifo_crq(crq); + if (len != sizeof(*crq)) { + dprintk(DEBUG_NORMAL, "setup begin: fifo READ ERROR" + " wanted %d bytes got %d. Stalling out...\n", + sizeof(*crq), len); + s3c2410_udc_set_ep0_ss(base_addr); + return; + } + + dprintk(DEBUG_NORMAL, "bRequest = %d bRequestType %d wLength = %d\n", + crq->bRequest, crq->bRequestType, crq->wLength); + + /* cope with automagic for some standard requests. */ + dev->req_std = (crq->bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD; + dev->req_config = 0; + dev->req_pending = 1; + + switch (crq->bRequest) { + case USB_REQ_SET_CONFIGURATION: + dprintk(DEBUG_NORMAL, "USB_REQ_SET_CONFIGURATION ...\n"); + + if (crq->bRequestType == USB_RECIP_DEVICE) { + dev->req_config = 1; + s3c2410_udc_set_ep0_de_out(base_addr); + } + break; + + case USB_REQ_SET_INTERFACE: + dprintk(DEBUG_NORMAL, "USB_REQ_SET_INTERFACE ...\n"); + + if (crq->bRequestType == USB_RECIP_INTERFACE) { + dev->req_config = 1; + s3c2410_udc_set_ep0_de_out(base_addr); + } + break; + + case USB_REQ_SET_ADDRESS: + dprintk(DEBUG_NORMAL, "USB_REQ_SET_ADDRESS ...\n"); + + if (crq->bRequestType == USB_RECIP_DEVICE) { + tmp = crq->wValue & 0x7F; + dev->address = tmp; + udc_write((tmp | S3C2410_UDC_FUNCADDR_UPDATE), + S3C2410_UDC_FUNC_ADDR_REG); + s3c2410_udc_set_ep0_de_out(base_addr); + return; + } + break; + + case USB_REQ_GET_STATUS: + dprintk(DEBUG_NORMAL, "USB_REQ_GET_STATUS ...\n"); + s3c2410_udc_clear_ep0_opr(base_addr); + + if (dev->req_std) { + if (!s3c2410_udc_get_status(dev, crq)) + return; + } + break; + + case USB_REQ_CLEAR_FEATURE: + s3c2410_udc_clear_ep0_opr(base_addr); + + if (crq->bRequestType != USB_RECIP_ENDPOINT) + break; + + if (crq->wValue != USB_ENDPOINT_HALT || crq->wLength != 0) + break; + + s3c2410_udc_set_halt(&dev->ep[crq->wIndex & 0x7f].ep, 0); + s3c2410_udc_set_ep0_de_out(base_addr); + return; + + case USB_REQ_SET_FEATURE: + s3c2410_udc_clear_ep0_opr(base_addr); + + if (crq->bRequestType != USB_RECIP_ENDPOINT) + break; + + if (crq->wValue != USB_ENDPOINT_HALT || crq->wLength != 0) + break; + + s3c2410_udc_set_halt(&dev->ep[crq->wIndex & 0x7f].ep, 1); + s3c2410_udc_set_ep0_de_out(base_addr); + return; + + default: + s3c2410_udc_clear_ep0_opr(base_addr); + break; + } + + if (crq->bRequestType & USB_DIR_IN) + dev->ep0state = EP0_IN_DATA_PHASE; + else + dev->ep0state = EP0_OUT_DATA_PHASE; + + if (!dev->driver) + return; + + /* deliver the request to the gadget driver */ + ret = dev->driver->setup(&dev->gadget, crq); + if (ret < 0) { + if (dev->req_config) { + dprintk(DEBUG_NORMAL, "config change %02x fail %d?\n", + crq->bRequest, ret); + return; + } + + if (ret == -EOPNOTSUPP) + dprintk(DEBUG_NORMAL, "Operation not supported\n"); + else + dprintk(DEBUG_NORMAL, + "dev->driver->setup failed. (%d)\n", ret); + + udelay(5); + s3c2410_udc_set_ep0_ss(base_addr); + s3c2410_udc_set_ep0_de_out(base_addr); + dev->ep0state = EP0_IDLE; + /* deferred i/o == no response yet */ + } else if (dev->req_pending) { + dprintk(DEBUG_VERBOSE, "dev->req_pending... what now?\n"); + dev->req_pending = 0; + } + + dprintk(DEBUG_VERBOSE, "ep0state %s\n", ep0states[dev->ep0state]); +} + +static void s3c2410_udc_handle_ep0(struct s3c2410_udc *dev) +{ + u32 ep0csr; + struct s3c2410_ep *ep = &dev->ep[0]; + struct s3c2410_request *req; + struct usb_ctrlrequest crq; + + if (list_empty(&ep->queue)) + req = NULL; + else + req = list_entry(ep->queue.next, struct s3c2410_request, queue); + + /* We make the assumption that S3C2410_UDC_IN_CSR1_REG equal to + * S3C2410_UDC_EP0_CSR_REG when index is zero */ + + udc_write(0, S3C2410_UDC_INDEX_REG); + ep0csr = udc_read(S3C2410_UDC_IN_CSR1_REG); + + dprintk(DEBUG_NORMAL, "ep0csr %x ep0state %s\n", + ep0csr, ep0states[dev->ep0state]); + + /* clear stall status */ + if (ep0csr & S3C2410_UDC_EP0_CSR_SENTSTL) { + s3c2410_udc_nuke(dev, ep, -EPIPE); + dprintk(DEBUG_NORMAL, "... clear SENT_STALL ...\n"); + s3c2410_udc_clear_ep0_sst(base_addr); + dev->ep0state = EP0_IDLE; + return; + } + + /* clear setup end */ + if (ep0csr & S3C2410_UDC_EP0_CSR_SE) { + dprintk(DEBUG_NORMAL, "... serviced SETUP_END ...\n"); + s3c2410_udc_nuke(dev, ep, 0); + s3c2410_udc_clear_ep0_se(base_addr); + dev->ep0state = EP0_IDLE; + } + + switch (dev->ep0state) { + case EP0_IDLE: + s3c2410_udc_handle_ep0_idle(dev, ep, &crq, ep0csr); + break; + + case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ + dprintk(DEBUG_NORMAL, "EP0_IN_DATA_PHASE ... what now?\n"); + if (!(ep0csr & S3C2410_UDC_EP0_CSR_IPKRDY) && req) + s3c2410_udc_write_fifo(ep, req); + break; + + case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ + dprintk(DEBUG_NORMAL, "EP0_OUT_DATA_PHASE ... what now?\n"); + if ((ep0csr & S3C2410_UDC_EP0_CSR_OPKRDY) && req) + s3c2410_udc_read_fifo(ep, req); + break; + + case EP0_END_XFER: + dprintk(DEBUG_NORMAL, "EP0_END_XFER ... what now?\n"); + dev->ep0state = EP0_IDLE; + break; + + case EP0_STALL: + dprintk(DEBUG_NORMAL, "EP0_STALL ... what now?\n"); + dev->ep0state = EP0_IDLE; + break; + } +} + +/* + * handle_ep - Manage I/O endpoints + */ + +static void s3c2410_udc_handle_ep(struct s3c2410_ep *ep) +{ + struct s3c2410_request *req; + int is_in = ep->bEndpointAddress & USB_DIR_IN; + u32 ep_csr1; + u32 idx; + + if (likely(!list_empty(&ep->queue))) + req = list_entry(ep->queue.next, + struct s3c2410_request, queue); + else + req = NULL; + + idx = ep->bEndpointAddress & 0x7F; + + if (is_in) { + udc_write(idx, S3C2410_UDC_INDEX_REG); + ep_csr1 = udc_read(S3C2410_UDC_IN_CSR1_REG); + dprintk(DEBUG_VERBOSE, "ep%01d write csr:%02x %d\n", + idx, ep_csr1, req ? 1 : 0); + + if (ep_csr1 & S3C2410_UDC_ICSR1_SENTSTL) { + dprintk(DEBUG_VERBOSE, "st\n"); + udc_write(idx, S3C2410_UDC_INDEX_REG); + udc_write(ep_csr1 & ~S3C2410_UDC_ICSR1_SENTSTL, + S3C2410_UDC_IN_CSR1_REG); + return; + } + + if (!(ep_csr1 & S3C2410_UDC_ICSR1_PKTRDY) && req) + s3c2410_udc_write_fifo(ep, req); + } else { + udc_write(idx, S3C2410_UDC_INDEX_REG); + ep_csr1 = udc_read(S3C2410_UDC_OUT_CSR1_REG); + dprintk(DEBUG_VERBOSE, "ep%01d rd csr:%02x\n", idx, ep_csr1); + + if (ep_csr1 & S3C2410_UDC_OCSR1_SENTSTL) { + udc_write(idx, S3C2410_UDC_INDEX_REG); + udc_write(ep_csr1 & ~S3C2410_UDC_OCSR1_SENTSTL, + S3C2410_UDC_OUT_CSR1_REG); + return; + } + + if ((ep_csr1 & S3C2410_UDC_OCSR1_PKTRDY) && req) + s3c2410_udc_read_fifo(ep, req); + } +} + +#include + +/* + * s3c2410_udc_irq - interrupt handler + */ +static irqreturn_t s3c2410_udc_irq(int dummy, void *_dev) +{ + struct s3c2410_udc *dev = _dev; + int usb_status; + int usbd_status; + int pwr_reg; + int ep0csr; + int i; + u32 idx, idx2; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + /* Driver connected ? */ + if (!dev->driver) { + /* Clear interrupts */ + udc_write(udc_read(S3C2410_UDC_USB_INT_REG), + S3C2410_UDC_USB_INT_REG); + udc_write(udc_read(S3C2410_UDC_EP_INT_REG), + S3C2410_UDC_EP_INT_REG); + } + + /* Save index */ + idx = udc_read(S3C2410_UDC_INDEX_REG); + + /* Read status registers */ + usb_status = udc_read(S3C2410_UDC_USB_INT_REG); + usbd_status = udc_read(S3C2410_UDC_EP_INT_REG); + pwr_reg = udc_read(S3C2410_UDC_PWR_REG); + + udc_writeb(base_addr, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); + ep0csr = udc_read(S3C2410_UDC_IN_CSR1_REG); + + dprintk(DEBUG_NORMAL, "usbs=%02x, usbds=%02x, pwr=%02x ep0csr=%02x\n", + usb_status, usbd_status, pwr_reg, ep0csr); + + /* + * Now, handle interrupts. There's two types : + * - Reset, Resume, Suspend coming -> usb_int_reg + * - EP -> ep_int_reg + */ + + /* RESET */ + if (usb_status & S3C2410_UDC_USBINT_RESET) { + /* two kind of reset : + * - reset start -> pwr reg = 8 + * - reset end -> pwr reg = 0 + **/ + dprintk(DEBUG_NORMAL, "USB reset csr %x pwr %x\n", + ep0csr, pwr_reg); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + udc_write(0x00, S3C2410_UDC_INDEX_REG); + udc_write((dev->ep[0].ep.maxpacket & 0x7ff) >> 3, + S3C2410_UDC_MAXP_REG); + dev->address = 0; + + dev->ep0state = EP0_IDLE; + dev->gadget.speed = USB_SPEED_FULL; + + /* clear interrupt */ + udc_write(S3C2410_UDC_USBINT_RESET, + S3C2410_UDC_USB_INT_REG); + + udc_write(idx, S3C2410_UDC_INDEX_REG); + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_HANDLED; + } + + /* RESUME */ + if (usb_status & S3C2410_UDC_USBINT_RESUME) { + dprintk(DEBUG_NORMAL, "USB resume\n"); + + /* clear interrupt */ + udc_write(S3C2410_UDC_USBINT_RESUME, + S3C2410_UDC_USB_INT_REG); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) + dev->driver->resume(&dev->gadget); + } + + /* SUSPEND */ + if (usb_status & S3C2410_UDC_USBINT_SUSPEND) { + dprintk(DEBUG_NORMAL, "USB suspend\n"); + + /* clear interrupt */ + udc_write(S3C2410_UDC_USBINT_SUSPEND, + S3C2410_UDC_USB_INT_REG); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + + dev->ep0state = EP0_IDLE; + } + + /* EP */ + /* control traffic */ + /* check on ep0csr != 0 is not a good idea as clearing in_pkt_ready + * generate an interrupt + */ + if (usbd_status & S3C2410_UDC_INT_EP0) { + dprintk(DEBUG_VERBOSE, "USB ep0 irq\n"); + /* Clear the interrupt bit by setting it to 1 */ + udc_write(S3C2410_UDC_INT_EP0, S3C2410_UDC_EP_INT_REG); + s3c2410_udc_handle_ep0(dev); + } + + /* endpoint data transfers */ + for (i = 1; i < S3C2410_ENDPOINTS; i++) { + u32 tmp = 1 << i; + if (usbd_status & tmp) { + dprintk(DEBUG_VERBOSE, "USB ep%d irq\n", i); + + /* Clear the interrupt bit by setting it to 1 */ + udc_write(tmp, S3C2410_UDC_EP_INT_REG); + s3c2410_udc_handle_ep(&dev->ep[i]); + } + } + + /* what else causes this interrupt? a receive! who is it? */ + if (!usb_status && !usbd_status && !pwr_reg && !ep0csr) { + for (i = 1; i < S3C2410_ENDPOINTS; i++) { + idx2 = udc_read(S3C2410_UDC_INDEX_REG); + udc_write(i, S3C2410_UDC_INDEX_REG); + + if (udc_read(S3C2410_UDC_OUT_CSR1_REG) & 0x1) + s3c2410_udc_handle_ep(&dev->ep[i]); + + /* restore index */ + udc_write(idx2, S3C2410_UDC_INDEX_REG); + } + } + + dprintk(DEBUG_VERBOSE, "irq: %d s3c2410_udc_done.\n", IRQ_USBD); + + /* Restore old index */ + udc_write(idx, S3C2410_UDC_INDEX_REG); + + spin_unlock_irqrestore(&dev->lock, flags); + + return IRQ_HANDLED; +} +/*------------------------- s3c2410_ep_ops ----------------------------------*/ + +static inline struct s3c2410_ep *to_s3c2410_ep(struct usb_ep *ep) +{ + return container_of(ep, struct s3c2410_ep, ep); +} + +static inline struct s3c2410_udc *to_s3c2410_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct s3c2410_udc, gadget); +} + +static inline struct s3c2410_request *to_s3c2410_req(struct usb_request *req) +{ + return container_of(req, struct s3c2410_request, req); +} + +/* + * s3c2410_udc_ep_enable + */ +static int s3c2410_udc_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct s3c2410_udc *dev; + struct s3c2410_ep *ep; + u32 max, tmp; + unsigned long flags; + u32 csr1, csr2; + u32 int_en_reg; + + ep = to_s3c2410_ep(_ep); + + if (!_ep || !desc + || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + max = usb_endpoint_maxp(desc) & 0x1fff; + + local_irq_save(flags); + _ep->maxpacket = max & 0x7ff; + ep->ep.desc = desc; + ep->halted = 0; + ep->bEndpointAddress = desc->bEndpointAddress; + + /* set max packet */ + udc_write(ep->num, S3C2410_UDC_INDEX_REG); + udc_write(max >> 3, S3C2410_UDC_MAXP_REG); + + /* set type, direction, address; reset fifo counters */ + if (desc->bEndpointAddress & USB_DIR_IN) { + csr1 = S3C2410_UDC_ICSR1_FFLUSH|S3C2410_UDC_ICSR1_CLRDT; + csr2 = S3C2410_UDC_ICSR2_MODEIN|S3C2410_UDC_ICSR2_DMAIEN; + + udc_write(ep->num, S3C2410_UDC_INDEX_REG); + udc_write(csr1, S3C2410_UDC_IN_CSR1_REG); + udc_write(ep->num, S3C2410_UDC_INDEX_REG); + udc_write(csr2, S3C2410_UDC_IN_CSR2_REG); + } else { + /* don't flush in fifo or it will cause endpoint interrupt */ + csr1 = S3C2410_UDC_ICSR1_CLRDT; + csr2 = S3C2410_UDC_ICSR2_DMAIEN; + + udc_write(ep->num, S3C2410_UDC_INDEX_REG); + udc_write(csr1, S3C2410_UDC_IN_CSR1_REG); + udc_write(ep->num, S3C2410_UDC_INDEX_REG); + udc_write(csr2, S3C2410_UDC_IN_CSR2_REG); + + csr1 = S3C2410_UDC_OCSR1_FFLUSH | S3C2410_UDC_OCSR1_CLRDT; + csr2 = S3C2410_UDC_OCSR2_DMAIEN; + + udc_write(ep->num, S3C2410_UDC_INDEX_REG); + udc_write(csr1, S3C2410_UDC_OUT_CSR1_REG); + udc_write(ep->num, S3C2410_UDC_INDEX_REG); + udc_write(csr2, S3C2410_UDC_OUT_CSR2_REG); + } + + /* enable irqs */ + int_en_reg = udc_read(S3C2410_UDC_EP_INT_EN_REG); + udc_write(int_en_reg | (1 << ep->num), S3C2410_UDC_EP_INT_EN_REG); + + /* print some debug message */ + tmp = desc->bEndpointAddress; + dprintk(DEBUG_NORMAL, "enable %s(%d) ep%x%s-blk max %02x\n", + _ep->name, ep->num, tmp, + desc->bEndpointAddress & USB_DIR_IN ? "in" : "out", max); + + local_irq_restore(flags); + s3c2410_udc_set_halt(_ep, 0); + + return 0; +} + +/* + * s3c2410_udc_ep_disable + */ +static int s3c2410_udc_ep_disable(struct usb_ep *_ep) +{ + struct s3c2410_ep *ep = to_s3c2410_ep(_ep); + unsigned long flags; + u32 int_en_reg; + + if (!_ep || !ep->ep.desc) { + dprintk(DEBUG_NORMAL, "%s not enabled\n", + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + local_irq_save(flags); + + dprintk(DEBUG_NORMAL, "ep_disable: %s\n", _ep->name); + + ep->ep.desc = NULL; + ep->halted = 1; + + s3c2410_udc_nuke(ep->dev, ep, -ESHUTDOWN); + + /* disable irqs */ + int_en_reg = udc_read(S3C2410_UDC_EP_INT_EN_REG); + udc_write(int_en_reg & ~(1<num), S3C2410_UDC_EP_INT_EN_REG); + + local_irq_restore(flags); + + dprintk(DEBUG_NORMAL, "%s disabled\n", _ep->name); + + return 0; +} + +/* + * s3c2410_udc_alloc_request + */ +static struct usb_request * +s3c2410_udc_alloc_request(struct usb_ep *_ep, gfp_t mem_flags) +{ + struct s3c2410_request *req; + + dprintk(DEBUG_VERBOSE, "%s(%p,%d)\n", __func__, _ep, mem_flags); + + if (!_ep) + return NULL; + + req = kzalloc(sizeof(struct s3c2410_request), mem_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +/* + * s3c2410_udc_free_request + */ +static void +s3c2410_udc_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct s3c2410_ep *ep = to_s3c2410_ep(_ep); + struct s3c2410_request *req = to_s3c2410_req(_req); + + dprintk(DEBUG_VERBOSE, "%s(%p,%p)\n", __func__, _ep, _req); + + if (!ep || !_req || (!ep->ep.desc && _ep->name != ep0name)) + return; + + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/* + * s3c2410_udc_queue + */ +static int s3c2410_udc_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct s3c2410_request *req = to_s3c2410_req(_req); + struct s3c2410_ep *ep = to_s3c2410_ep(_ep); + struct s3c2410_udc *dev; + u32 ep_csr = 0; + int fifo_count = 0; + unsigned long flags; + + if (unlikely(!_ep || (!ep->ep.desc && ep->ep.name != ep0name))) { + dprintk(DEBUG_NORMAL, "%s: invalid args\n", __func__); + return -EINVAL; + } + + dev = ep->dev; + if (unlikely(!dev->driver + || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + return -ESHUTDOWN; + } + + local_irq_save(flags); + + if (unlikely(!_req || !_req->complete + || !_req->buf || !list_empty(&req->queue))) { + if (!_req) + dprintk(DEBUG_NORMAL, "%s: 1 X X X\n", __func__); + else { + dprintk(DEBUG_NORMAL, "%s: 0 %01d %01d %01d\n", + __func__, !_req->complete, !_req->buf, + !list_empty(&req->queue)); + } + + local_irq_restore(flags); + return -EINVAL; + } + + _req->status = -EINPROGRESS; + _req->actual = 0; + + dprintk(DEBUG_VERBOSE, "%s: ep%x len %d\n", + __func__, ep->bEndpointAddress, _req->length); + + if (ep->bEndpointAddress) { + udc_write(ep->bEndpointAddress & 0x7F, S3C2410_UDC_INDEX_REG); + + ep_csr = udc_read((ep->bEndpointAddress & USB_DIR_IN) + ? S3C2410_UDC_IN_CSR1_REG + : S3C2410_UDC_OUT_CSR1_REG); + fifo_count = s3c2410_udc_fifo_count_out(); + } else { + udc_write(0, S3C2410_UDC_INDEX_REG); + ep_csr = udc_read(S3C2410_UDC_IN_CSR1_REG); + fifo_count = s3c2410_udc_fifo_count_out(); + } + + /* kickstart this i/o queue? */ + if (list_empty(&ep->queue) && !ep->halted) { + if (ep->bEndpointAddress == 0 /* ep0 */) { + switch (dev->ep0state) { + case EP0_IN_DATA_PHASE: + if (!(ep_csr&S3C2410_UDC_EP0_CSR_IPKRDY) + && s3c2410_udc_write_fifo(ep, + req)) { + dev->ep0state = EP0_IDLE; + req = NULL; + } + break; + + case EP0_OUT_DATA_PHASE: + if ((!_req->length) + || ((ep_csr & S3C2410_UDC_OCSR1_PKTRDY) + && s3c2410_udc_read_fifo(ep, + req))) { + dev->ep0state = EP0_IDLE; + req = NULL; + } + break; + + default: + local_irq_restore(flags); + return -EL2HLT; + } + } else if ((ep->bEndpointAddress & USB_DIR_IN) != 0 + && (!(ep_csr&S3C2410_UDC_OCSR1_PKTRDY)) + && s3c2410_udc_write_fifo(ep, req)) { + req = NULL; + } else if ((ep_csr & S3C2410_UDC_OCSR1_PKTRDY) + && fifo_count + && s3c2410_udc_read_fifo(ep, req)) { + req = NULL; + } + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req)) + list_add_tail(&req->queue, &ep->queue); + + local_irq_restore(flags); + + dprintk(DEBUG_VERBOSE, "%s ok\n", __func__); + return 0; +} + +/* + * s3c2410_udc_dequeue + */ +static int s3c2410_udc_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct s3c2410_ep *ep = to_s3c2410_ep(_ep); + struct s3c2410_udc *udc; + int retval = -EINVAL; + unsigned long flags; + struct s3c2410_request *req = NULL; + + dprintk(DEBUG_VERBOSE, "%s(%p,%p)\n", __func__, _ep, _req); + + if (!the_controller->driver) + return -ESHUTDOWN; + + if (!_ep || !_req) + return retval; + + udc = to_s3c2410_udc(ep->gadget); + + local_irq_save(flags); + + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) { + list_del_init(&req->queue); + _req->status = -ECONNRESET; + retval = 0; + break; + } + } + + if (retval == 0) { + dprintk(DEBUG_VERBOSE, + "dequeued req %p from %s, len %d buf %p\n", + req, _ep->name, _req->length, _req->buf); + + s3c2410_udc_done(ep, req, -ECONNRESET); + } + + local_irq_restore(flags); + return retval; +} + +/* + * s3c2410_udc_set_halt + */ +static int s3c2410_udc_set_halt(struct usb_ep *_ep, int value) +{ + struct s3c2410_ep *ep = to_s3c2410_ep(_ep); + u32 ep_csr = 0; + unsigned long flags; + u32 idx; + + if (unlikely(!_ep || (!ep->ep.desc && ep->ep.name != ep0name))) { + dprintk(DEBUG_NORMAL, "%s: inval 2\n", __func__); + return -EINVAL; + } + + local_irq_save(flags); + + idx = ep->bEndpointAddress & 0x7F; + + if (idx == 0) { + s3c2410_udc_set_ep0_ss(base_addr); + s3c2410_udc_set_ep0_de_out(base_addr); + } else { + udc_write(idx, S3C2410_UDC_INDEX_REG); + ep_csr = udc_read((ep->bEndpointAddress & USB_DIR_IN) + ? S3C2410_UDC_IN_CSR1_REG + : S3C2410_UDC_OUT_CSR1_REG); + + if ((ep->bEndpointAddress & USB_DIR_IN) != 0) { + if (value) + udc_write(ep_csr | S3C2410_UDC_ICSR1_SENDSTL, + S3C2410_UDC_IN_CSR1_REG); + else { + ep_csr &= ~S3C2410_UDC_ICSR1_SENDSTL; + udc_write(ep_csr, S3C2410_UDC_IN_CSR1_REG); + ep_csr |= S3C2410_UDC_ICSR1_CLRDT; + udc_write(ep_csr, S3C2410_UDC_IN_CSR1_REG); + } + } else { + if (value) + udc_write(ep_csr | S3C2410_UDC_OCSR1_SENDSTL, + S3C2410_UDC_OUT_CSR1_REG); + else { + ep_csr &= ~S3C2410_UDC_OCSR1_SENDSTL; + udc_write(ep_csr, S3C2410_UDC_OUT_CSR1_REG); + ep_csr |= S3C2410_UDC_OCSR1_CLRDT; + udc_write(ep_csr, S3C2410_UDC_OUT_CSR1_REG); + } + } + } + + ep->halted = value ? 1 : 0; + local_irq_restore(flags); + + return 0; +} + +static const struct usb_ep_ops s3c2410_ep_ops = { + .enable = s3c2410_udc_ep_enable, + .disable = s3c2410_udc_ep_disable, + + .alloc_request = s3c2410_udc_alloc_request, + .free_request = s3c2410_udc_free_request, + + .queue = s3c2410_udc_queue, + .dequeue = s3c2410_udc_dequeue, + + .set_halt = s3c2410_udc_set_halt, +}; + +/*------------------------- usb_gadget_ops ----------------------------------*/ + +/* + * s3c2410_udc_get_frame + */ +static int s3c2410_udc_get_frame(struct usb_gadget *_gadget) +{ + int tmp; + + dprintk(DEBUG_VERBOSE, "%s()\n", __func__); + + tmp = udc_read(S3C2410_UDC_FRAME_NUM2_REG) << 8; + tmp |= udc_read(S3C2410_UDC_FRAME_NUM1_REG); + return tmp; +} + +/* + * s3c2410_udc_wakeup + */ +static int s3c2410_udc_wakeup(struct usb_gadget *_gadget) +{ + dprintk(DEBUG_NORMAL, "%s()\n", __func__); + return 0; +} + +/* + * s3c2410_udc_set_selfpowered + */ +static int s3c2410_udc_set_selfpowered(struct usb_gadget *gadget, int value) +{ + struct s3c2410_udc *udc = to_s3c2410_udc(gadget); + + dprintk(DEBUG_NORMAL, "%s()\n", __func__); + + if (value) + udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED); + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + + return 0; +} + +static void s3c2410_udc_disable(struct s3c2410_udc *dev); +static void s3c2410_udc_enable(struct s3c2410_udc *dev); + +static int s3c2410_udc_set_pullup(struct s3c2410_udc *udc, int is_on) +{ + dprintk(DEBUG_NORMAL, "%s()\n", __func__); + + if (udc_info && (udc_info->udc_command || + gpio_is_valid(udc_info->pullup_pin))) { + + if (is_on) + s3c2410_udc_enable(udc); + else { + if (udc->gadget.speed != USB_SPEED_UNKNOWN) { + if (udc->driver && udc->driver->disconnect) + udc->driver->disconnect(&udc->gadget); + + } + s3c2410_udc_disable(udc); + } + } else { + return -EOPNOTSUPP; + } + + return 0; +} + +static int s3c2410_udc_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct s3c2410_udc *udc = to_s3c2410_udc(gadget); + + dprintk(DEBUG_NORMAL, "%s()\n", __func__); + + udc->vbus = (is_active != 0); + s3c2410_udc_set_pullup(udc, is_active); + return 0; +} + +static int s3c2410_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct s3c2410_udc *udc = to_s3c2410_udc(gadget); + + dprintk(DEBUG_NORMAL, "%s()\n", __func__); + + s3c2410_udc_set_pullup(udc, is_on ? 0 : 1); + return 0; +} + +static irqreturn_t s3c2410_udc_vbus_irq(int irq, void *_dev) +{ + struct s3c2410_udc *dev = _dev; + unsigned int value; + + dprintk(DEBUG_NORMAL, "%s()\n", __func__); + + value = gpio_get_value(udc_info->vbus_pin) ? 1 : 0; + if (udc_info->vbus_pin_inverted) + value = !value; + + if (value != dev->vbus) + s3c2410_udc_vbus_session(&dev->gadget, value); + + return IRQ_HANDLED; +} + +static int s3c2410_vbus_draw(struct usb_gadget *_gadget, unsigned ma) +{ + dprintk(DEBUG_NORMAL, "%s()\n", __func__); + + if (udc_info && udc_info->vbus_draw) { + udc_info->vbus_draw(ma); + return 0; + } + + return -ENOTSUPP; +} + +static int s3c2410_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int s3c2410_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops s3c2410_ops = { + .get_frame = s3c2410_udc_get_frame, + .wakeup = s3c2410_udc_wakeup, + .set_selfpowered = s3c2410_udc_set_selfpowered, + .pullup = s3c2410_udc_pullup, + .vbus_session = s3c2410_udc_vbus_session, + .vbus_draw = s3c2410_vbus_draw, + .udc_start = s3c2410_udc_start, + .udc_stop = s3c2410_udc_stop, +}; + +static void s3c2410_udc_command(enum s3c2410_udc_cmd_e cmd) +{ + if (!udc_info) + return; + + if (udc_info->udc_command) { + udc_info->udc_command(cmd); + } else if (gpio_is_valid(udc_info->pullup_pin)) { + int value; + + switch (cmd) { + case S3C2410_UDC_P_ENABLE: + value = 1; + break; + case S3C2410_UDC_P_DISABLE: + value = 0; + break; + default: + return; + } + value ^= udc_info->pullup_pin_inverted; + + gpio_set_value(udc_info->pullup_pin, value); + } +} + +/*------------------------- gadget driver handling---------------------------*/ +/* + * s3c2410_udc_disable + */ +static void s3c2410_udc_disable(struct s3c2410_udc *dev) +{ + dprintk(DEBUG_NORMAL, "%s()\n", __func__); + + /* Disable all interrupts */ + udc_write(0x00, S3C2410_UDC_USB_INT_EN_REG); + udc_write(0x00, S3C2410_UDC_EP_INT_EN_REG); + + /* Clear the interrupt registers */ + udc_write(S3C2410_UDC_USBINT_RESET + | S3C2410_UDC_USBINT_RESUME + | S3C2410_UDC_USBINT_SUSPEND, + S3C2410_UDC_USB_INT_REG); + + udc_write(0x1F, S3C2410_UDC_EP_INT_REG); + + /* Good bye, cruel world */ + s3c2410_udc_command(S3C2410_UDC_P_DISABLE); + + /* Set speed to unknown */ + dev->gadget.speed = USB_SPEED_UNKNOWN; +} + +/* + * s3c2410_udc_reinit + */ +static void s3c2410_udc_reinit(struct s3c2410_udc *dev) +{ + u32 i; + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->ep0state = EP0_IDLE; + + for (i = 0; i < S3C2410_ENDPOINTS; i++) { + struct s3c2410_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->dev = dev; + ep->ep.desc = NULL; + ep->halted = 0; + INIT_LIST_HEAD(&ep->queue); + } +} + +/* + * s3c2410_udc_enable + */ +static void s3c2410_udc_enable(struct s3c2410_udc *dev) +{ + int i; + + dprintk(DEBUG_NORMAL, "s3c2410_udc_enable called\n"); + + /* dev->gadget.speed = USB_SPEED_UNKNOWN; */ + dev->gadget.speed = USB_SPEED_FULL; + + /* Set MAXP for all endpoints */ + for (i = 0; i < S3C2410_ENDPOINTS; i++) { + udc_write(i, S3C2410_UDC_INDEX_REG); + udc_write((dev->ep[i].ep.maxpacket & 0x7ff) >> 3, + S3C2410_UDC_MAXP_REG); + } + + /* Set default power state */ + udc_write(DEFAULT_POWER_STATE, S3C2410_UDC_PWR_REG); + + /* Enable reset and suspend interrupt interrupts */ + udc_write(S3C2410_UDC_USBINT_RESET | S3C2410_UDC_USBINT_SUSPEND, + S3C2410_UDC_USB_INT_EN_REG); + + /* Enable ep0 interrupt */ + udc_write(S3C2410_UDC_INT_EP0, S3C2410_UDC_EP_INT_EN_REG); + + /* time to say "hello, world" */ + s3c2410_udc_command(S3C2410_UDC_P_ENABLE); +} + +static int s3c2410_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct s3c2410_udc *udc = to_s3c2410(g); + + dprintk(DEBUG_NORMAL, "%s() '%s'\n", __func__, driver->driver.name); + + /* Hook the driver */ + udc->driver = driver; + + /* Enable udc */ + s3c2410_udc_enable(udc); + + return 0; +} + +static int s3c2410_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct s3c2410_udc *udc = to_s3c2410(g); + + udc->driver = NULL; + + /* Disable udc */ + s3c2410_udc_disable(udc); + + return 0; +} + +/*---------------------------------------------------------------------------*/ +static struct s3c2410_udc memory = { + .gadget = { + .ops = &s3c2410_ops, + .ep0 = &memory.ep[0].ep, + .name = gadget_name, + .dev = { + .init_name = "gadget", + }, + }, + + /* control endpoint */ + .ep[0] = { + .num = 0, + .ep = { + .name = ep0name, + .ops = &s3c2410_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + }, + + /* first group of endpoints */ + .ep[1] = { + .num = 1, + .ep = { + .name = "ep1-bulk", + .ops = &s3c2410_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = EP_FIFO_SIZE, + .bEndpointAddress = 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .ep[2] = { + .num = 2, + .ep = { + .name = "ep2-bulk", + .ops = &s3c2410_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = EP_FIFO_SIZE, + .bEndpointAddress = 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .ep[3] = { + .num = 3, + .ep = { + .name = "ep3-bulk", + .ops = &s3c2410_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = EP_FIFO_SIZE, + .bEndpointAddress = 3, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .ep[4] = { + .num = 4, + .ep = { + .name = "ep4-bulk", + .ops = &s3c2410_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = EP_FIFO_SIZE, + .bEndpointAddress = 4, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + } + +}; + +/* + * probe - binds to the platform device + */ +static int s3c2410_udc_probe(struct platform_device *pdev) +{ + struct s3c2410_udc *udc = &memory; + struct device *dev = &pdev->dev; + int retval; + int irq; + + dev_dbg(dev, "%s()\n", __func__); + + usb_bus_clock = clk_get(NULL, "usb-bus-gadget"); + if (IS_ERR(usb_bus_clock)) { + dev_err(dev, "failed to get usb bus clock source\n"); + return PTR_ERR(usb_bus_clock); + } + + clk_enable(usb_bus_clock); + + udc_clock = clk_get(NULL, "usb-device"); + if (IS_ERR(udc_clock)) { + dev_err(dev, "failed to get udc clock source\n"); + return PTR_ERR(udc_clock); + } + + clk_enable(udc_clock); + + mdelay(10); + + dev_dbg(dev, "got and enabled clocks\n"); + + if (strncmp(pdev->name, "s3c2440", 7) == 0) { + dev_info(dev, "S3C2440: increasing FIFO to 128 bytes\n"); + memory.ep[1].fifo_size = S3C2440_EP_FIFO_SIZE; + memory.ep[2].fifo_size = S3C2440_EP_FIFO_SIZE; + memory.ep[3].fifo_size = S3C2440_EP_FIFO_SIZE; + memory.ep[4].fifo_size = S3C2440_EP_FIFO_SIZE; + } + + spin_lock_init(&udc->lock); + udc_info = pdev->dev.platform_data; + + rsrc_start = S3C2410_PA_USBDEV; + rsrc_len = S3C24XX_SZ_USBDEV; + + if (!request_mem_region(rsrc_start, rsrc_len, gadget_name)) + return -EBUSY; + + base_addr = ioremap(rsrc_start, rsrc_len); + if (!base_addr) { + retval = -ENOMEM; + goto err_mem; + } + + the_controller = udc; + platform_set_drvdata(pdev, udc); + + s3c2410_udc_disable(udc); + s3c2410_udc_reinit(udc); + + /* irq setup after old hardware state is cleaned up */ + retval = request_irq(IRQ_USBD, s3c2410_udc_irq, + 0, gadget_name, udc); + + if (retval != 0) { + dev_err(dev, "cannot get irq %i, err %d\n", IRQ_USBD, retval); + retval = -EBUSY; + goto err_map; + } + + dev_dbg(dev, "got irq %i\n", IRQ_USBD); + + if (udc_info && udc_info->vbus_pin > 0) { + retval = gpio_request(udc_info->vbus_pin, "udc vbus"); + if (retval < 0) { + dev_err(dev, "cannot claim vbus pin\n"); + goto err_int; + } + + irq = gpio_to_irq(udc_info->vbus_pin); + if (irq < 0) { + dev_err(dev, "no irq for gpio vbus pin\n"); + retval = irq; + goto err_gpio_claim; + } + + retval = request_irq(irq, s3c2410_udc_vbus_irq, + IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_SHARED, + gadget_name, udc); + + if (retval != 0) { + dev_err(dev, "can't get vbus irq %d, err %d\n", + irq, retval); + retval = -EBUSY; + goto err_gpio_claim; + } + + dev_dbg(dev, "got irq %i\n", irq); + } else { + udc->vbus = 1; + } + + if (udc_info && !udc_info->udc_command && + gpio_is_valid(udc_info->pullup_pin)) { + + retval = gpio_request_one(udc_info->pullup_pin, + udc_info->vbus_pin_inverted ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, + "udc pullup"); + if (retval) + goto err_vbus_irq; + } + + retval = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (retval) + goto err_add_udc; + + if (s3c2410_udc_debugfs_root) { + udc->regs_info = debugfs_create_file("registers", S_IRUGO, + s3c2410_udc_debugfs_root, + udc, &s3c2410_udc_debugfs_fops); + if (!udc->regs_info) + dev_warn(dev, "debugfs file creation failed\n"); + } + + dev_dbg(dev, "probe ok\n"); + + return 0; + +err_add_udc: + if (udc_info && !udc_info->udc_command && + gpio_is_valid(udc_info->pullup_pin)) + gpio_free(udc_info->pullup_pin); +err_vbus_irq: + if (udc_info && udc_info->vbus_pin > 0) + free_irq(gpio_to_irq(udc_info->vbus_pin), udc); +err_gpio_claim: + if (udc_info && udc_info->vbus_pin > 0) + gpio_free(udc_info->vbus_pin); +err_int: + free_irq(IRQ_USBD, udc); +err_map: + iounmap(base_addr); +err_mem: + release_mem_region(rsrc_start, rsrc_len); + + return retval; +} + +/* + * s3c2410_udc_remove + */ +static int s3c2410_udc_remove(struct platform_device *pdev) +{ + struct s3c2410_udc *udc = platform_get_drvdata(pdev); + unsigned int irq; + + dev_dbg(&pdev->dev, "%s()\n", __func__); + + if (udc->driver) + return -EBUSY; + + usb_del_gadget_udc(&udc->gadget); + debugfs_remove(udc->regs_info); + + if (udc_info && !udc_info->udc_command && + gpio_is_valid(udc_info->pullup_pin)) + gpio_free(udc_info->pullup_pin); + + if (udc_info && udc_info->vbus_pin > 0) { + irq = gpio_to_irq(udc_info->vbus_pin); + free_irq(irq, udc); + } + + free_irq(IRQ_USBD, udc); + + iounmap(base_addr); + release_mem_region(rsrc_start, rsrc_len); + + if (!IS_ERR(udc_clock) && udc_clock != NULL) { + clk_disable(udc_clock); + clk_put(udc_clock); + udc_clock = NULL; + } + + if (!IS_ERR(usb_bus_clock) && usb_bus_clock != NULL) { + clk_disable(usb_bus_clock); + clk_put(usb_bus_clock); + usb_bus_clock = NULL; + } + + dev_dbg(&pdev->dev, "%s: remove ok\n", __func__); + return 0; +} + +#ifdef CONFIG_PM +static int +s3c2410_udc_suspend(struct platform_device *pdev, pm_message_t message) +{ + s3c2410_udc_command(S3C2410_UDC_P_DISABLE); + + return 0; +} + +static int s3c2410_udc_resume(struct platform_device *pdev) +{ + s3c2410_udc_command(S3C2410_UDC_P_ENABLE); + + return 0; +} +#else +#define s3c2410_udc_suspend NULL +#define s3c2410_udc_resume NULL +#endif + +static const struct platform_device_id s3c_udc_ids[] = { + { "s3c2410-usbgadget", }, + { "s3c2440-usbgadget", }, + { } +}; +MODULE_DEVICE_TABLE(platform, s3c_udc_ids); + +static struct platform_driver udc_driver_24x0 = { + .driver = { + .name = "s3c24x0-usbgadget", + .owner = THIS_MODULE, + }, + .probe = s3c2410_udc_probe, + .remove = s3c2410_udc_remove, + .suspend = s3c2410_udc_suspend, + .resume = s3c2410_udc_resume, + .id_table = s3c_udc_ids, +}; + +static int __init udc_init(void) +{ + int retval; + + dprintk(DEBUG_NORMAL, "%s: version %s\n", gadget_name, DRIVER_VERSION); + + s3c2410_udc_debugfs_root = debugfs_create_dir(gadget_name, NULL); + if (IS_ERR(s3c2410_udc_debugfs_root)) { + pr_err("%s: debugfs dir creation failed %ld\n", + gadget_name, PTR_ERR(s3c2410_udc_debugfs_root)); + s3c2410_udc_debugfs_root = NULL; + } + + retval = platform_driver_register(&udc_driver_24x0); + if (retval) + goto err; + + return 0; + +err: + debugfs_remove(s3c2410_udc_debugfs_root); + return retval; +} + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&udc_driver_24x0); + debugfs_remove(s3c2410_udc_debugfs_root); +} + +module_init(udc_init); +module_exit(udc_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/s3c2410_udc.h b/drivers/usb/gadget/s3c2410_udc.h new file mode 100644 index 00000000..93bf225f --- /dev/null +++ b/drivers/usb/gadget/s3c2410_udc.h @@ -0,0 +1,100 @@ +/* + * linux/drivers/usb/gadget/s3c2410_udc.h + * Samsung on-chip full speed USB device controllers + * + * Copyright (C) 2004-2007 Herbert Pötzl - Arnaud Patard + * Additional cleanups by Ben Dooks + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _S3C2410_UDC_H +#define _S3C2410_UDC_H + +struct s3c2410_ep { + struct list_head queue; + unsigned long last_io; /* jiffies timestamp */ + struct usb_gadget *gadget; + struct s3c2410_udc *dev; + struct usb_ep ep; + u8 num; + + unsigned short fifo_size; + u8 bEndpointAddress; + u8 bmAttributes; + + unsigned halted : 1; + unsigned already_seen : 1; + unsigned setup_stage : 1; +}; + + +/* Warning : ep0 has a fifo of 16 bytes */ +/* Don't try to set 32 or 64 */ +/* also testusb 14 fails wit 16 but is */ +/* fine with 8 */ +#define EP0_FIFO_SIZE 8 +#define EP_FIFO_SIZE 64 +#define DEFAULT_POWER_STATE 0x00 + +#define S3C2440_EP_FIFO_SIZE 128 + +static const char ep0name [] = "ep0"; + +static const char *const ep_name[] = { + ep0name, /* everyone has ep0 */ + /* s3c2410 four bidirectional bulk endpoints */ + "ep1-bulk", "ep2-bulk", "ep3-bulk", "ep4-bulk", +}; + +#define S3C2410_ENDPOINTS ARRAY_SIZE(ep_name) + +struct s3c2410_request { + struct list_head queue; /* ep's requests */ + struct usb_request req; +}; + +enum ep0_state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_END_XFER, + EP0_STALL, +}; + +static const char *ep0states[]= { + "EP0_IDLE", + "EP0_IN_DATA_PHASE", + "EP0_OUT_DATA_PHASE", + "EP0_END_XFER", + "EP0_STALL", +}; + +struct s3c2410_udc { + spinlock_t lock; + + struct s3c2410_ep ep[S3C2410_ENDPOINTS]; + int address; + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct s3c2410_request fifo_req; + u8 fifo_buf[EP_FIFO_SIZE]; + u16 devstatus; + + u32 port_status; + int ep0state; + + unsigned got_irq : 1; + + unsigned req_std : 1; + unsigned req_config : 1; + unsigned req_pending : 1; + u8 vbus; + struct dentry *regs_info; +}; +#define to_s3c2410(g) (container_of((g), struct s3c2410_udc, gadget)) + +#endif diff --git a/drivers/usb/gadget/serial.c b/drivers/usb/gadget/serial.c new file mode 100644 index 00000000..1f5f978d --- /dev/null +++ b/drivers/usb/gadget/serial.c @@ -0,0 +1,276 @@ +/* + * serial.c -- USB gadget serial driver + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include "u_serial.h" +#include "gadget_chips.h" + + +/* Defines */ + +#define GS_VERSION_STR "v2.4" +#define GS_VERSION_NUM 0x2400 + +#define GS_LONG_NAME "Gadget Serial" +#define GS_VERSION_NAME GS_LONG_NAME " " GS_VERSION_STR + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +/* Thanks to NetChip Technologies for donating this product ID. +* +* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! +* Instead: allocate your own, using normal USB-IF procedures. +*/ +#define GS_VENDOR_ID 0x0525 /* NetChip */ +#define GS_PRODUCT_ID 0xa4a6 /* Linux-USB Serial Gadget */ +#define GS_CDC_PRODUCT_ID 0xa4a7 /* ... as CDC-ACM */ +#define GS_CDC_OBEX_PRODUCT_ID 0xa4a9 /* ... as CDC-OBEX */ + +/* string IDs are assigned dynamically */ + +#define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = GS_VERSION_NAME, + [USB_GADGET_SERIAL_IDX].s = "", + [STRING_DESCRIPTION_IDX].s = NULL /* updated; f(use_acm) */, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + /* .bDeviceClass = f(use_acm) */ + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + .idVendor = cpu_to_le16(GS_VENDOR_ID), + /* .idProduct = f(use_acm) */ + .bcdDevice = cpu_to_le16(GS_VERSION_NUM), + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +/* Module */ +MODULE_DESCRIPTION(GS_VERSION_NAME); +MODULE_AUTHOR("Al Borchers"); +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); + +static bool use_acm = true; +module_param(use_acm, bool, 0); +MODULE_PARM_DESC(use_acm, "Use CDC ACM, default=yes"); + +static bool use_obex = false; +module_param(use_obex, bool, 0); +MODULE_PARM_DESC(use_obex, "Use CDC OBEX, default=no"); + +static unsigned n_ports = 1; +module_param(n_ports, uint, 0); +MODULE_PARM_DESC(n_ports, "number of ports to create, default=1"); + +/*-------------------------------------------------------------------------*/ + +static struct usb_configuration serial_config_driver = { + /* .label = f(use_acm) */ + /* .bConfigurationValue = f(use_acm) */ + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +static struct usb_function_instance *fi_serial[MAX_U_SERIAL_PORTS]; +static struct usb_function *f_serial[MAX_U_SERIAL_PORTS]; + +static int serial_register_ports(struct usb_composite_dev *cdev, + struct usb_configuration *c, const char *f_name) +{ + int i; + int ret; + + ret = usb_add_config_only(cdev, c); + if (ret) + goto out; + + for (i = 0; i < n_ports; i++) { + + fi_serial[i] = usb_get_function_instance(f_name); + if (IS_ERR(fi_serial[i])) { + ret = PTR_ERR(fi_serial[i]); + goto fail; + } + + f_serial[i] = usb_get_function(fi_serial[i]); + if (IS_ERR(f_serial[i])) { + ret = PTR_ERR(f_serial[i]); + goto err_get_func; + } + + ret = usb_add_function(c, f_serial[i]); + if (ret) + goto err_add_func; + } + + return 0; + +err_add_func: + usb_put_function(f_serial[i]); +err_get_func: + usb_put_function_instance(fi_serial[i]); + +fail: + i--; + while (i >= 0) { + usb_remove_function(c, f_serial[i]); + usb_put_function(f_serial[i]); + usb_put_function_instance(fi_serial[i]); + i--; + } +out: + return ret; +} + +static int __init gs_bind(struct usb_composite_dev *cdev) +{ + int status; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto fail; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + status = strings_dev[STRING_DESCRIPTION_IDX].id; + serial_config_driver.iConfiguration = status; + + if (gadget_is_otg(cdev->gadget)) { + serial_config_driver.descriptors = otg_desc; + serial_config_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + /* register our configuration */ + if (use_acm) { + status = serial_register_ports(cdev, &serial_config_driver, + "acm"); + usb_ep_autoconfig_reset(cdev->gadget); + } else if (use_obex) + status = serial_register_ports(cdev, &serial_config_driver, + "obex"); + else { + status = serial_register_ports(cdev, &serial_config_driver, + "gser"); + } + if (status < 0) + goto fail; + + usb_composite_overwrite_options(cdev, &coverwrite); + INFO(cdev, "%s\n", GS_VERSION_NAME); + + return 0; + +fail: + return status; +} + +static int gs_unbind(struct usb_composite_dev *cdev) +{ + int i; + + for (i = 0; i < n_ports; i++) { + usb_put_function(f_serial[i]); + usb_put_function_instance(fi_serial[i]); + } + return 0; +} + +static __refdata struct usb_composite_driver gserial_driver = { + .name = "g_serial", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = gs_bind, + .unbind = gs_unbind, +}; + +static int __init init(void) +{ + /* We *could* export two configs; that'd be much cleaner... + * but neither of these product IDs was defined that way. + */ + if (use_acm) { + serial_config_driver.label = "CDC ACM config"; + serial_config_driver.bConfigurationValue = 2; + device_desc.bDeviceClass = USB_CLASS_COMM; + device_desc.idProduct = + cpu_to_le16(GS_CDC_PRODUCT_ID); + } else if (use_obex) { + serial_config_driver.label = "CDC OBEX config"; + serial_config_driver.bConfigurationValue = 3; + device_desc.bDeviceClass = USB_CLASS_COMM; + device_desc.idProduct = + cpu_to_le16(GS_CDC_OBEX_PRODUCT_ID); + } else { + serial_config_driver.label = "Generic Serial config"; + serial_config_driver.bConfigurationValue = 1; + device_desc.bDeviceClass = USB_CLASS_VENDOR_SPEC; + device_desc.idProduct = + cpu_to_le16(GS_PRODUCT_ID); + } + strings_dev[STRING_DESCRIPTION_IDX].s = serial_config_driver.label; + + return usb_composite_probe(&gserial_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&gserial_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c new file mode 100644 index 00000000..26636648 --- /dev/null +++ b/drivers/usb/gadget/storage_common.c @@ -0,0 +1,704 @@ +/* + * storage_common.c -- Common definitions for mass storage functionality + * + * Copyright (C) 2003-2008 Alan Stern + * Copyeight (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (mina86@mina86.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * This file requires the following identifiers used in USB strings to + * be defined (each of type pointer to char): + * - fsg_string_interface -- name of the interface + */ + +/* + * When USB_GADGET_DEBUG_FILES is defined the module param num_buffers + * sets the number of pipeline buffers (length of the fsg_buffhd array). + * The valid range of num_buffers is: num >= 2 && num <= 4. + */ + + +#include +#include +#include + + +/* + * Thanks to NetChip Technologies for donating this product ID. + * + * DO NOT REUSE THESE IDs with any other driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ +#define FSG_VENDOR_ID 0x0525 /* NetChip */ +#define FSG_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ + + +/*-------------------------------------------------------------------------*/ + + +#ifndef DEBUG +#undef VERBOSE_DEBUG +#undef DUMP_MSGS +#endif /* !DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VLDBG LDBG +#else +#define VLDBG(lun, fmt, args...) do { } while (0) +#endif /* VERBOSE_DEBUG */ + +#define LDBG(lun, fmt, args...) dev_dbg (&(lun)->dev, fmt, ## args) +#define LERROR(lun, fmt, args...) dev_err (&(lun)->dev, fmt, ## args) +#define LWARN(lun, fmt, args...) dev_warn(&(lun)->dev, fmt, ## args) +#define LINFO(lun, fmt, args...) dev_info(&(lun)->dev, fmt, ## args) + + +#ifdef DUMP_MSGS + +# define dump_msg(fsg, /* const char * */ label, \ + /* const u8 * */ buf, /* unsigned */ length) do { \ + if (length < 512) { \ + DBG(fsg, "%s, length %u:\n", label, length); \ + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, \ + 16, 1, buf, length, 0); \ + } \ +} while (0) + +# define dump_cdb(fsg) do { } while (0) + +#else + +# define dump_msg(fsg, /* const char * */ label, \ + /* const u8 * */ buf, /* unsigned */ length) do { } while (0) + +# ifdef VERBOSE_DEBUG + +# define dump_cdb(fsg) \ + print_hex_dump(KERN_DEBUG, "SCSI CDB: ", DUMP_PREFIX_NONE, \ + 16, 1, (fsg)->cmnd, (fsg)->cmnd_size, 0) \ + +# else + +# define dump_cdb(fsg) do { } while (0) + +# endif /* VERBOSE_DEBUG */ + +#endif /* DUMP_MSGS */ + +/*-------------------------------------------------------------------------*/ + +/* Length of a SCSI Command Data Block */ +#define MAX_COMMAND_SIZE 16 + +/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ +#define SS_NO_SENSE 0 +#define SS_COMMUNICATION_FAILURE 0x040800 +#define SS_INVALID_COMMAND 0x052000 +#define SS_INVALID_FIELD_IN_CDB 0x052400 +#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 +#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 +#define SS_MEDIUM_NOT_PRESENT 0x023a00 +#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 +#define SS_NOT_READY_TO_READY_TRANSITION 0x062800 +#define SS_RESET_OCCURRED 0x062900 +#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 +#define SS_UNRECOVERED_READ_ERROR 0x031100 +#define SS_WRITE_ERROR 0x030c02 +#define SS_WRITE_PROTECTED 0x072700 + +#define SK(x) ((u8) ((x) >> 16)) /* Sense Key byte, etc. */ +#define ASC(x) ((u8) ((x) >> 8)) +#define ASCQ(x) ((u8) (x)) + + +/*-------------------------------------------------------------------------*/ + + +struct fsg_lun { + struct file *filp; + loff_t file_length; + loff_t num_sectors; + + unsigned int initially_ro:1; + unsigned int ro:1; + unsigned int removable:1; + unsigned int cdrom:1; + unsigned int prevent_medium_removal:1; + unsigned int registered:1; + unsigned int info_valid:1; + unsigned int nofua:1; + + u32 sense_data; + u32 sense_data_info; + u32 unit_attention_data; + + unsigned int blkbits; /* Bits of logical block size of bound block device */ + unsigned int blksize; /* logical block size of bound block device */ + struct device dev; +}; + +static inline bool fsg_lun_is_open(struct fsg_lun *curlun) +{ + return curlun->filp != NULL; +} + +static inline struct fsg_lun *fsg_lun_from_dev(struct device *dev) +{ + return container_of(dev, struct fsg_lun, dev); +} + + +/* Big enough to hold our biggest descriptor */ +#define EP0_BUFSIZE 256 +#define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */ + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; +module_param_named(num_buffers, fsg_num_buffers, uint, S_IRUGO); +MODULE_PARM_DESC(num_buffers, "Number of pipeline buffers"); + +#else + +/* + * Number of buffers we will use. + * 2 is usually enough for good buffering pipeline + */ +#define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS + +#endif /* CONFIG_USB_DEBUG */ + +/* check if fsg_num_buffers is within a valid range */ +static inline int fsg_num_buffers_validate(void) +{ + if (fsg_num_buffers >= 2 && fsg_num_buffers <= 4) + return 0; + pr_err("fsg_num_buffers %u is out of range (%d to %d)\n", + fsg_num_buffers, 2 ,4); + return -EINVAL; +} + +/* Default size of buffer length. */ +#define FSG_BUFLEN ((u32)32768) + +/* Maximal number of LUNs supported in mass storage function */ +#define FSG_MAX_LUNS 8 + +enum fsg_buffer_state { + BUF_STATE_EMPTY = 0, + BUF_STATE_FULL, + BUF_STATE_BUSY +}; + +struct fsg_buffhd { + void *buf; + enum fsg_buffer_state state; + struct fsg_buffhd *next; + + /* + * The NetChip 2280 is faster, and handles some protocol faults + * better, if we don't submit any short bulk-out read requests. + * So we will record the intended request length here. + */ + unsigned int bulk_out_intended_length; + + struct usb_request *inreq; + int inreq_busy; + struct usb_request *outreq; + int outreq_busy; +}; + +enum fsg_state { + /* This one isn't used anywhere */ + FSG_STATE_COMMAND_PHASE = -10, + FSG_STATE_DATA_PHASE, + FSG_STATE_STATUS_PHASE, + + FSG_STATE_IDLE = 0, + FSG_STATE_ABORT_BULK_OUT, + FSG_STATE_RESET, + FSG_STATE_INTERFACE_CHANGE, + FSG_STATE_CONFIG_CHANGE, + FSG_STATE_DISCONNECT, + FSG_STATE_EXIT, + FSG_STATE_TERMINATED +}; + +enum data_direction { + DATA_DIR_UNKNOWN = 0, + DATA_DIR_FROM_HOST, + DATA_DIR_TO_HOST, + DATA_DIR_NONE +}; + + +/*-------------------------------------------------------------------------*/ + + +static inline u32 get_unaligned_be24(u8 *buf) +{ + return 0xffffff & (u32) get_unaligned_be32(buf - 1); +} + + +/*-------------------------------------------------------------------------*/ + + +enum { + FSG_STRING_INTERFACE +}; + + +/* There is only one interface. */ + +static struct usb_interface_descriptor +fsg_intf_desc = { + .bLength = sizeof fsg_intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 2, /* Adjusted during fsg_bind() */ + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, /* Adjusted during fsg_bind() */ + .bInterfaceProtocol = USB_PR_BULK, /* Adjusted during fsg_bind() */ + .iInterface = FSG_STRING_INTERFACE, +}; + +/* + * Three full-speed endpoint descriptors: bulk-in, bulk-out, and + * interrupt-in. + */ + +static struct usb_endpoint_descriptor +fsg_fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_endpoint_descriptor +fsg_fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_descriptor_header *fsg_fs_function[] = { + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_fs_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_fs_bulk_out_desc, + NULL, +}; + + +/* + * USB 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + * + * That means alternate endpoint descriptors (bigger packets) + * and a "device qualifier" ... plus more construction options + * for the configuration descriptor. + */ +static struct usb_endpoint_descriptor +fsg_hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor +fsg_hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + .bInterval = 1, /* NAK every 1 uframe */ +}; + + +static struct usb_descriptor_header *fsg_hs_function[] = { + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_hs_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_hs_bulk_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor +fsg_ss_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc = { + .bLength = sizeof(fsg_ss_bulk_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /*.bMaxBurst = DYNAMIC, */ +}; + +static struct usb_endpoint_descriptor +fsg_ss_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc = { + .bLength = sizeof(fsg_ss_bulk_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /*.bMaxBurst = DYNAMIC, */ +}; + +static struct usb_descriptor_header *fsg_ss_function[] = { + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_ss_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_ss_bulk_in_comp_desc, + (struct usb_descriptor_header *) &fsg_ss_bulk_out_desc, + (struct usb_descriptor_header *) &fsg_ss_bulk_out_comp_desc, + NULL, +}; + +/* Static strings, in UTF-8 (for simplicity we use only ASCII characters) */ +static struct usb_string fsg_strings[] = { + {FSG_STRING_INTERFACE, fsg_string_interface}, + {} +}; + +static struct usb_gadget_strings fsg_stringtab = { + .language = 0x0409, /* en-us */ + .strings = fsg_strings, +}; + + + /*-------------------------------------------------------------------------*/ + +/* + * If the next two routines are called while the gadget is registered, + * the caller must own fsg->filesem for writing. + */ + +static void fsg_lun_close(struct fsg_lun *curlun) +{ + if (curlun->filp) { + LDBG(curlun, "close backing file\n"); + fput(curlun->filp); + curlun->filp = NULL; + } +} + + +static int fsg_lun_open(struct fsg_lun *curlun, const char *filename) +{ + int ro; + struct file *filp = NULL; + int rc = -EINVAL; + struct inode *inode = NULL; + loff_t size; + loff_t num_sectors; + loff_t min_sectors; + unsigned int blkbits; + unsigned int blksize; + + /* R/W if we can, R/O if we must */ + ro = curlun->initially_ro; + if (!ro) { + filp = filp_open(filename, O_RDWR | O_LARGEFILE, 0); + if (PTR_ERR(filp) == -EROFS || PTR_ERR(filp) == -EACCES) + ro = 1; + } + if (ro) + filp = filp_open(filename, O_RDONLY | O_LARGEFILE, 0); + if (IS_ERR(filp)) { + LINFO(curlun, "unable to open backing file: %s\n", filename); + return PTR_ERR(filp); + } + + if (!(filp->f_mode & FMODE_WRITE)) + ro = 1; + + inode = file_inode(filp); + if ((!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))) { + LINFO(curlun, "invalid file type: %s\n", filename); + goto out; + } + + /* + * If we can't read the file, it's no good. + * If we can't write the file, use it read-only. + */ + if (!(filp->f_op->read || filp->f_op->aio_read)) { + LINFO(curlun, "file not readable: %s\n", filename); + goto out; + } + if (!(filp->f_op->write || filp->f_op->aio_write)) + ro = 1; + + size = i_size_read(inode->i_mapping->host); + if (size < 0) { + LINFO(curlun, "unable to find file size: %s\n", filename); + rc = (int) size; + goto out; + } + + blksize = 512; + blkbits = 9; + + + num_sectors = size >> blkbits; /* File size in logic-block-size blocks */ + min_sectors = 1; + if (curlun->cdrom) { + num_sectors &= ~3; /* Reduce to a multiple of 2048 */ + min_sectors = 300*4; /* Smallest track is 300 frames */ + if (num_sectors >= 256*60*75*4) { + num_sectors = (256*60*75 - 1) * 4; + + LINFO(curlun, "file too big: %s\n", filename); + LINFO(curlun, "using only first %d blocks\n", + (int) num_sectors); + } + } + if (num_sectors < min_sectors) { + LINFO(curlun, "file too small: %s\n", filename); + rc = -ETOOSMALL; + goto out; + } + + if (fsg_lun_is_open(curlun)) + fsg_lun_close(curlun); + + curlun->blksize = blksize; + curlun->blkbits = blkbits; + curlun->ro = ro; + curlun->filp = filp; + curlun->file_length = size; + curlun->num_sectors = num_sectors; + LINFO(curlun, "open backing file: %s\n", filename); + return 0; + +out: + fput(filp); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +/* + * Sync the file data, don't bother with the metadata. + * This code was copied from fs/buffer.c:sys_fdatasync(). + */ +static int fsg_lun_fsync_sub(struct fsg_lun *curlun) +{ + struct file *filp = curlun->filp; + + if (curlun->ro || !filp) + return 0; + return vfs_fsync(filp, 1); +} + +static void store_cdrom_address(u8 *dest, int msf, u32 addr) +{ + if (msf) { + /* Convert to Minutes-Seconds-Frames */ + addr >>= 2; /* Convert to 2048-byte frames */ + addr += 2*75; /* Lead-in occupies 2 seconds */ + dest[3] = addr % 75; /* Frames */ + addr /= 75; + dest[2] = addr % 60; /* Seconds */ + addr /= 60; + dest[1] = addr; /* Minutes */ + dest[0] = 0; /* Reserved */ + } else { + /* Absolute sector */ + put_unaligned_be32(addr, dest); + } +} + + +/*-------------------------------------------------------------------------*/ + + +static ssize_t fsg_show_ro(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return sprintf(buf, "%d\n", fsg_lun_is_open(curlun) + ? curlun->ro + : curlun->initially_ro); +} + +static ssize_t fsg_show_nofua(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return sprintf(buf, "%u\n", curlun->nofua); +} + +static ssize_t fsg_show_file(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + char *p; + ssize_t rc; + + down_read(filesem); + if (fsg_lun_is_open(curlun)) { /* Get the complete pathname */ + p = d_path(&curlun->filp->f_path, buf, PAGE_SIZE - 1); + if (IS_ERR(p)) + rc = PTR_ERR(p); + else { + rc = strlen(p); + memmove(buf, p, rc); + buf[rc] = '\n'; /* Add a newline */ + buf[++rc] = 0; + } + } else { /* No file, return 0 bytes */ + *buf = 0; + rc = 0; + } + up_read(filesem); + return rc; +} + + +static ssize_t fsg_store_ro(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + ssize_t rc; + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + unsigned ro; + + rc = kstrtouint(buf, 2, &ro); + if (rc) + return rc; + + /* + * Allow the write-enable status to change only while the + * backing file is closed. + */ + down_read(filesem); + if (fsg_lun_is_open(curlun)) { + LDBG(curlun, "read-only status change prevented\n"); + rc = -EBUSY; + } else { + curlun->ro = ro; + curlun->initially_ro = ro; + LDBG(curlun, "read-only status set to %d\n", curlun->ro); + rc = count; + } + up_read(filesem); + return rc; +} + +static ssize_t fsg_store_nofua(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + unsigned nofua; + int ret; + + ret = kstrtouint(buf, 2, &nofua); + if (ret) + return ret; + + /* Sync data when switching from async mode to sync */ + if (!nofua && curlun->nofua) + fsg_lun_fsync_sub(curlun); + + curlun->nofua = nofua; + + return count; +} + +static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + int rc = 0; + + /* Remove a trailing newline */ + if (count > 0 && buf[count-1] == '\n') + ((char *) buf)[count-1] = 0; /* Ugh! */ + + down_write(filesem); + /* Eject current medium */ + if (fsg_lun_is_open(curlun)) { + fsg_lun_close(curlun); + curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; + } + /* Load new medium */ + if (count > 0 && buf[0]) { + /* fsg_lun_open() will close existing file if any. */ + rc = fsg_lun_open(curlun, buf); + if (rc == 0) + curlun->unit_attention_data = + SS_NOT_READY_TO_READY_TRANSITION; + } + up_write(filesem); + return (rc < 0 ? rc : count); +} +static ssize_t fsg_show_cdrom(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return sprintf(buf, "%d\n", curlun->cdrom); +} +static ssize_t fsg_store_cdrom(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + ssize_t rc; + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + unsigned cdrom; + + if (sscanf(buf, "%d", &cdrom) != 1) + return -EINVAL; + + /* + * Allow the write-enable status to change only while the + * backing file is closed. + */ + down_read(filesem); + if (fsg_lun_is_open(curlun)) { + LDBG(curlun, "read-only status change prevented\n"); + rc = -EBUSY; + } else { + curlun->cdrom = cdrom; + LDBG(curlun, "file storage set to cdrom: %d\n", curlun->cdrom); + rc = count; + } + up_read(filesem); + return rc; +} + diff --git a/drivers/usb/gadget/tcm_usb_gadget.c b/drivers/usb/gadget/tcm_usb_gadget.c new file mode 100644 index 00000000..e4d8d79a --- /dev/null +++ b/drivers/usb/gadget/tcm_usb_gadget.c @@ -0,0 +1,2476 @@ +/* Target based USB-Gadget + * + * UAS protocol handling, target callbacks, configfs handling, + * BBB (USB Mass Storage Class Bulk-Only (BBB) and Transport protocol handling. + * + * Author: Sebastian Andrzej Siewior + * License: GPLv2 as published by FSF. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tcm_usb_gadget.h" + +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct target_fabric_configfs *usbg_fabric_configfs; + +static inline struct f_uas *to_f_uas(struct usb_function *f) +{ + return container_of(f, struct f_uas, function); +} + +static void usbg_cmd_release(struct kref *); + +static inline void usbg_cleanup_cmd(struct usbg_cmd *cmd) +{ + kref_put(&cmd->ref, usbg_cmd_release); +} + +/* Start bot.c code */ + +static int bot_enqueue_cmd_cbw(struct f_uas *fu) +{ + int ret; + + if (fu->flags & USBG_BOT_CMD_PEND) + return 0; + + ret = usb_ep_queue(fu->ep_out, fu->cmd.req, GFP_ATOMIC); + if (!ret) + fu->flags |= USBG_BOT_CMD_PEND; + return ret; +} + +static void bot_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct f_uas *fu = cmd->fu; + + usbg_cleanup_cmd(cmd); + if (req->status < 0) { + pr_err("ERR %s(%d)\n", __func__, __LINE__); + return; + } + + /* CSW completed, wait for next CBW */ + bot_enqueue_cmd_cbw(fu); +} + +static void bot_enqueue_sense_code(struct f_uas *fu, struct usbg_cmd *cmd) +{ + struct bulk_cs_wrap *csw = &fu->bot_status.csw; + int ret; + u8 *sense; + unsigned int csw_stat; + + csw_stat = cmd->csw_code; + + /* + * We can't send SENSE as a response. So we take ASC & ASCQ from our + * sense buffer and queue it and hope the host sends a REQUEST_SENSE + * command where it learns why we failed. + */ + sense = cmd->sense_iu.sense; + + csw->Tag = cmd->bot_tag; + csw->Status = csw_stat; + fu->bot_status.req->context = cmd; + ret = usb_ep_queue(fu->ep_in, fu->bot_status.req, GFP_ATOMIC); + if (ret) + pr_err("%s(%d) ERR: %d\n", __func__, __LINE__, ret); +} + +static void bot_err_compl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct f_uas *fu = cmd->fu; + + if (req->status < 0) + pr_err("ERR %s(%d)\n", __func__, __LINE__); + + if (cmd->data_len) { + if (cmd->data_len > ep->maxpacket) { + req->length = ep->maxpacket; + cmd->data_len -= ep->maxpacket; + } else { + req->length = cmd->data_len; + cmd->data_len = 0; + } + + usb_ep_queue(ep, req, GFP_ATOMIC); + return ; + } + bot_enqueue_sense_code(fu, cmd); +} + +static void bot_send_bad_status(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct bulk_cs_wrap *csw = &fu->bot_status.csw; + struct usb_request *req; + struct usb_ep *ep; + + csw->Residue = cpu_to_le32(cmd->data_len); + + if (cmd->data_len) { + if (cmd->is_read) { + ep = fu->ep_in; + req = fu->bot_req_in; + } else { + ep = fu->ep_out; + req = fu->bot_req_out; + } + + if (cmd->data_len > fu->ep_in->maxpacket) { + req->length = ep->maxpacket; + cmd->data_len -= ep->maxpacket; + } else { + req->length = cmd->data_len; + cmd->data_len = 0; + } + req->complete = bot_err_compl; + req->context = cmd; + req->buf = fu->cmd.buf; + usb_ep_queue(ep, req, GFP_KERNEL); + } else { + bot_enqueue_sense_code(fu, cmd); + } +} + +static int bot_send_status(struct usbg_cmd *cmd, bool moved_data) +{ + struct f_uas *fu = cmd->fu; + struct bulk_cs_wrap *csw = &fu->bot_status.csw; + int ret; + + if (cmd->se_cmd.scsi_status == SAM_STAT_GOOD) { + if (!moved_data && cmd->data_len) { + /* + * the host wants to move data, we don't. Fill / empty + * the pipe and then send the csw with reside set. + */ + cmd->csw_code = US_BULK_STAT_OK; + bot_send_bad_status(cmd); + return 0; + } + + csw->Tag = cmd->bot_tag; + csw->Residue = cpu_to_le32(0); + csw->Status = US_BULK_STAT_OK; + fu->bot_status.req->context = cmd; + + ret = usb_ep_queue(fu->ep_in, fu->bot_status.req, GFP_KERNEL); + if (ret) + pr_err("%s(%d) ERR: %d\n", __func__, __LINE__, ret); + } else { + cmd->csw_code = US_BULK_STAT_FAIL; + bot_send_bad_status(cmd); + } + return 0; +} + +/* + * Called after command (no data transfer) or after the write (to device) + * operation is completed + */ +static int bot_send_status_response(struct usbg_cmd *cmd) +{ + bool moved_data = false; + + if (!cmd->is_read) + moved_data = true; + return bot_send_status(cmd, moved_data); +} + +/* Read request completed, now we have to send the CSW */ +static void bot_read_compl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + + if (req->status < 0) + pr_err("ERR %s(%d)\n", __func__, __LINE__); + + bot_send_status(cmd, true); +} + +static int bot_send_read_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->se_cmd; + struct usb_gadget *gadget = fuas_to_gadget(fu); + int ret; + + if (!cmd->data_len) { + cmd->csw_code = US_BULK_STAT_PHASE; + bot_send_bad_status(cmd); + return 0; + } + + if (!gadget->sg_supported) { + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); + if (!cmd->data_buf) + return -ENOMEM; + + sg_copy_to_buffer(se_cmd->t_data_sg, + se_cmd->t_data_nents, + cmd->data_buf, + se_cmd->data_length); + + fu->bot_req_in->buf = cmd->data_buf; + } else { + fu->bot_req_in->buf = NULL; + fu->bot_req_in->num_sgs = se_cmd->t_data_nents; + fu->bot_req_in->sg = se_cmd->t_data_sg; + } + + fu->bot_req_in->complete = bot_read_compl; + fu->bot_req_in->length = se_cmd->data_length; + fu->bot_req_in->context = cmd; + ret = usb_ep_queue(fu->ep_in, fu->bot_req_in, GFP_ATOMIC); + if (ret) + pr_err("%s(%d)\n", __func__, __LINE__); + return 0; +} + +static void usbg_data_write_cmpl(struct usb_ep *, struct usb_request *); +static int usbg_prepare_w_request(struct usbg_cmd *, struct usb_request *); + +static int bot_send_write_request(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->se_cmd; + struct usb_gadget *gadget = fuas_to_gadget(fu); + int ret; + + init_completion(&cmd->write_complete); + cmd->fu = fu; + + if (!cmd->data_len) { + cmd->csw_code = US_BULK_STAT_PHASE; + return -EINVAL; + } + + if (!gadget->sg_supported) { + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_KERNEL); + if (!cmd->data_buf) + return -ENOMEM; + + fu->bot_req_out->buf = cmd->data_buf; + } else { + fu->bot_req_out->buf = NULL; + fu->bot_req_out->num_sgs = se_cmd->t_data_nents; + fu->bot_req_out->sg = se_cmd->t_data_sg; + } + + fu->bot_req_out->complete = usbg_data_write_cmpl; + fu->bot_req_out->length = se_cmd->data_length; + fu->bot_req_out->context = cmd; + + ret = usbg_prepare_w_request(cmd, fu->bot_req_out); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_out, fu->bot_req_out, GFP_KERNEL); + if (ret) + pr_err("%s(%d)\n", __func__, __LINE__); + + wait_for_completion(&cmd->write_complete); + target_execute_cmd(se_cmd); +cleanup: + return ret; +} + +static int bot_submit_command(struct f_uas *, void *, unsigned int); + +static void bot_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_uas *fu = req->context; + int ret; + + fu->flags &= ~USBG_BOT_CMD_PEND; + + if (req->status < 0) + return; + + ret = bot_submit_command(fu, req->buf, req->actual); + if (ret) + pr_err("%s(%d): %d\n", __func__, __LINE__, ret); +} + +static int bot_prepare_reqs(struct f_uas *fu) +{ + int ret; + + fu->bot_req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); + if (!fu->bot_req_in) + goto err; + + fu->bot_req_out = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!fu->bot_req_out) + goto err_out; + + fu->cmd.req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!fu->cmd.req) + goto err_cmd; + + fu->bot_status.req = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); + if (!fu->bot_status.req) + goto err_sts; + + fu->bot_status.req->buf = &fu->bot_status.csw; + fu->bot_status.req->length = US_BULK_CS_WRAP_LEN; + fu->bot_status.req->complete = bot_status_complete; + fu->bot_status.csw.Signature = cpu_to_le32(US_BULK_CS_SIGN); + + fu->cmd.buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL); + if (!fu->cmd.buf) + goto err_buf; + + fu->cmd.req->complete = bot_cmd_complete; + fu->cmd.req->buf = fu->cmd.buf; + fu->cmd.req->length = fu->ep_out->maxpacket; + fu->cmd.req->context = fu; + + ret = bot_enqueue_cmd_cbw(fu); + if (ret) + goto err_queue; + return 0; +err_queue: + kfree(fu->cmd.buf); + fu->cmd.buf = NULL; +err_buf: + usb_ep_free_request(fu->ep_in, fu->bot_status.req); +err_sts: + usb_ep_free_request(fu->ep_out, fu->cmd.req); + fu->cmd.req = NULL; +err_cmd: + usb_ep_free_request(fu->ep_out, fu->bot_req_out); + fu->bot_req_out = NULL; +err_out: + usb_ep_free_request(fu->ep_in, fu->bot_req_in); + fu->bot_req_in = NULL; +err: + pr_err("BOT: endpoint setup failed\n"); + return -ENOMEM; +} + +void bot_cleanup_old_alt(struct f_uas *fu) +{ + if (!(fu->flags & USBG_ENABLED)) + return; + + usb_ep_disable(fu->ep_in); + usb_ep_disable(fu->ep_out); + + if (!fu->bot_req_in) + return; + + usb_ep_free_request(fu->ep_in, fu->bot_req_in); + usb_ep_free_request(fu->ep_out, fu->bot_req_out); + usb_ep_free_request(fu->ep_out, fu->cmd.req); + usb_ep_free_request(fu->ep_out, fu->bot_status.req); + + kfree(fu->cmd.buf); + + fu->bot_req_in = NULL; + fu->bot_req_out = NULL; + fu->cmd.req = NULL; + fu->bot_status.req = NULL; + fu->cmd.buf = NULL; +} + +static void bot_set_alt(struct f_uas *fu) +{ + struct usb_function *f = &fu->function; + struct usb_gadget *gadget = f->config->cdev->gadget; + int ret; + + fu->flags = USBG_IS_BOT; + + config_ep_by_speed(gadget, f, fu->ep_in); + ret = usb_ep_enable(fu->ep_in); + if (ret) + goto err_b_in; + + config_ep_by_speed(gadget, f, fu->ep_out); + ret = usb_ep_enable(fu->ep_out); + if (ret) + goto err_b_out; + + ret = bot_prepare_reqs(fu); + if (ret) + goto err_wq; + fu->flags |= USBG_ENABLED; + pr_info("Using the BOT protocol\n"); + return; +err_wq: + usb_ep_disable(fu->ep_out); +err_b_out: + usb_ep_disable(fu->ep_in); +err_b_in: + fu->flags = USBG_IS_BOT; +} + +static int usbg_bot_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_uas *fu = to_f_uas(f); + struct usb_composite_dev *cdev = f->config->cdev; + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + int luns; + u8 *ret_lun; + + switch (ctrl->bRequest) { + case US_BULK_GET_MAX_LUN: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE)) + return -ENOTSUPP; + + if (w_length < 1) + return -EINVAL; + if (w_value != 0) + return -EINVAL; + luns = atomic_read(&fu->tpg->tpg_port_count); + if (!luns) { + pr_err("No LUNs configured?\n"); + return -EINVAL; + } + /* + * If 4 LUNs are present we return 3 i.e. LUN 0..3 can be + * accessed. The upper limit is 0xf + */ + luns--; + if (luns > 0xf) { + pr_info_once("Limiting the number of luns to 16\n"); + luns = 0xf; + } + ret_lun = cdev->req->buf; + *ret_lun = luns; + cdev->req->length = 1; + return usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + break; + + case US_BULK_RESET_REQUEST: + /* XXX maybe we should remove previous requests for IN + OUT */ + bot_enqueue_cmd_cbw(fu); + return 0; + break; + }; + return -ENOTSUPP; +} + +/* Start uas.c code */ + +static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream) +{ + /* We have either all three allocated or none */ + if (!stream->req_in) + return; + + usb_ep_free_request(fu->ep_in, stream->req_in); + usb_ep_free_request(fu->ep_out, stream->req_out); + usb_ep_free_request(fu->ep_status, stream->req_status); + + stream->req_in = NULL; + stream->req_out = NULL; + stream->req_status = NULL; +} + +static void uasp_free_cmdreq(struct f_uas *fu) +{ + usb_ep_free_request(fu->ep_cmd, fu->cmd.req); + kfree(fu->cmd.buf); + fu->cmd.req = NULL; + fu->cmd.buf = NULL; +} + +static void uasp_cleanup_old_alt(struct f_uas *fu) +{ + int i; + + if (!(fu->flags & USBG_ENABLED)) + return; + + usb_ep_disable(fu->ep_in); + usb_ep_disable(fu->ep_out); + usb_ep_disable(fu->ep_status); + usb_ep_disable(fu->ep_cmd); + + for (i = 0; i < UASP_SS_EP_COMP_NUM_STREAMS; i++) + uasp_cleanup_one_stream(fu, &fu->stream[i]); + uasp_free_cmdreq(fu); +} + +static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req); + +static int uasp_prepare_r_request(struct usbg_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct f_uas *fu = cmd->fu; + struct usb_gadget *gadget = fuas_to_gadget(fu); + struct uas_stream *stream = cmd->stream; + + if (!gadget->sg_supported) { + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); + if (!cmd->data_buf) + return -ENOMEM; + + sg_copy_to_buffer(se_cmd->t_data_sg, + se_cmd->t_data_nents, + cmd->data_buf, + se_cmd->data_length); + + stream->req_in->buf = cmd->data_buf; + } else { + stream->req_in->buf = NULL; + stream->req_in->num_sgs = se_cmd->t_data_nents; + stream->req_in->sg = se_cmd->t_data_sg; + } + + stream->req_in->complete = uasp_status_data_cmpl; + stream->req_in->length = se_cmd->data_length; + stream->req_in->context = cmd; + + cmd->state = UASP_SEND_STATUS; + return 0; +} + +static void uasp_prepare_status(struct usbg_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct sense_iu *iu = &cmd->sense_iu; + struct uas_stream *stream = cmd->stream; + + cmd->state = UASP_QUEUE_COMMAND; + iu->iu_id = IU_ID_STATUS; + iu->tag = cpu_to_be16(cmd->tag); + + /* + * iu->status_qual = cpu_to_be16(STATUS QUALIFIER SAM-4. Where R U?); + */ + iu->len = cpu_to_be16(se_cmd->scsi_sense_length); + iu->status = se_cmd->scsi_status; + stream->req_status->context = cmd; + stream->req_status->length = se_cmd->scsi_sense_length + 16; + stream->req_status->buf = iu; + stream->req_status->complete = uasp_status_data_cmpl; +} + +static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct uas_stream *stream = cmd->stream; + struct f_uas *fu = cmd->fu; + int ret; + + if (req->status < 0) + goto cleanup; + + switch (cmd->state) { + case UASP_SEND_DATA: + ret = uasp_prepare_r_request(cmd); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_in, stream->req_in, GFP_ATOMIC); + if (ret) + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + break; + + case UASP_RECEIVE_DATA: + ret = usbg_prepare_w_request(cmd, stream->req_out); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_out, stream->req_out, GFP_ATOMIC); + if (ret) + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + break; + + case UASP_SEND_STATUS: + uasp_prepare_status(cmd); + ret = usb_ep_queue(fu->ep_status, stream->req_status, + GFP_ATOMIC); + if (ret) + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + break; + + case UASP_QUEUE_COMMAND: + usbg_cleanup_cmd(cmd); + usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); + break; + + default: + BUG(); + }; + return; + +cleanup: + usbg_cleanup_cmd(cmd); +} + +static int uasp_send_status_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = cmd->stream; + struct sense_iu *iu = &cmd->sense_iu; + + iu->tag = cpu_to_be16(cmd->tag); + stream->req_status->complete = uasp_status_data_cmpl; + stream->req_status->context = cmd; + cmd->fu = fu; + uasp_prepare_status(cmd); + return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC); +} + +static int uasp_send_read_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = cmd->stream; + struct sense_iu *iu = &cmd->sense_iu; + int ret; + + cmd->fu = fu; + + iu->tag = cpu_to_be16(cmd->tag); + if (fu->flags & USBG_USE_STREAMS) { + + ret = uasp_prepare_r_request(cmd); + if (ret) + goto out; + ret = usb_ep_queue(fu->ep_in, stream->req_in, GFP_ATOMIC); + if (ret) { + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + kfree(cmd->data_buf); + cmd->data_buf = NULL; + } + + } else { + + iu->iu_id = IU_ID_READ_READY; + iu->tag = cpu_to_be16(cmd->tag); + + stream->req_status->complete = uasp_status_data_cmpl; + stream->req_status->context = cmd; + + cmd->state = UASP_SEND_DATA; + stream->req_status->buf = iu; + stream->req_status->length = sizeof(struct iu); + + ret = usb_ep_queue(fu->ep_status, stream->req_status, + GFP_ATOMIC); + if (ret) + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + } +out: + return ret; +} + +static int uasp_send_write_request(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->se_cmd; + struct uas_stream *stream = cmd->stream; + struct sense_iu *iu = &cmd->sense_iu; + int ret; + + init_completion(&cmd->write_complete); + cmd->fu = fu; + + iu->tag = cpu_to_be16(cmd->tag); + + if (fu->flags & USBG_USE_STREAMS) { + + ret = usbg_prepare_w_request(cmd, stream->req_out); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_out, stream->req_out, GFP_ATOMIC); + if (ret) + pr_err("%s(%d)\n", __func__, __LINE__); + + } else { + + iu->iu_id = IU_ID_WRITE_READY; + iu->tag = cpu_to_be16(cmd->tag); + + stream->req_status->complete = uasp_status_data_cmpl; + stream->req_status->context = cmd; + + cmd->state = UASP_RECEIVE_DATA; + stream->req_status->buf = iu; + stream->req_status->length = sizeof(struct iu); + + ret = usb_ep_queue(fu->ep_status, stream->req_status, + GFP_ATOMIC); + if (ret) + pr_err("%s(%d)\n", __func__, __LINE__); + } + + wait_for_completion(&cmd->write_complete); + target_execute_cmd(se_cmd); +cleanup: + return ret; +} + +static int usbg_submit_command(struct f_uas *, void *, unsigned int); + +static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_uas *fu = req->context; + int ret; + + if (req->status < 0) + return; + + ret = usbg_submit_command(fu, req->buf, req->actual); + /* + * Once we tune for performance enqueue the command req here again so + * we can receive a second command while we processing this one. Pay + * attention to properly sync STAUS endpoint with DATA IN + OUT so you + * don't break HS. + */ + if (!ret) + return; + usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); +} + +static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream) +{ + stream->req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); + if (!stream->req_in) + goto out; + + stream->req_out = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!stream->req_out) + goto err_out; + + stream->req_status = usb_ep_alloc_request(fu->ep_status, GFP_KERNEL); + if (!stream->req_status) + goto err_sts; + + return 0; +err_sts: + usb_ep_free_request(fu->ep_status, stream->req_status); + stream->req_status = NULL; +err_out: + usb_ep_free_request(fu->ep_out, stream->req_out); + stream->req_out = NULL; +out: + return -ENOMEM; +} + +static int uasp_alloc_cmd(struct f_uas *fu) +{ + fu->cmd.req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL); + if (!fu->cmd.req) + goto err; + + fu->cmd.buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL); + if (!fu->cmd.buf) + goto err_buf; + + fu->cmd.req->complete = uasp_cmd_complete; + fu->cmd.req->buf = fu->cmd.buf; + fu->cmd.req->length = fu->ep_cmd->maxpacket; + fu->cmd.req->context = fu; + return 0; + +err_buf: + usb_ep_free_request(fu->ep_cmd, fu->cmd.req); +err: + return -ENOMEM; +} + +static void uasp_setup_stream_res(struct f_uas *fu, int max_streams) +{ + int i; + + for (i = 0; i < max_streams; i++) { + struct uas_stream *s = &fu->stream[i]; + + s->req_in->stream_id = i + 1; + s->req_out->stream_id = i + 1; + s->req_status->stream_id = i + 1; + } +} + +static int uasp_prepare_reqs(struct f_uas *fu) +{ + int ret; + int i; + int max_streams; + + if (fu->flags & USBG_USE_STREAMS) + max_streams = UASP_SS_EP_COMP_NUM_STREAMS; + else + max_streams = 1; + + for (i = 0; i < max_streams; i++) { + ret = uasp_alloc_stream_res(fu, &fu->stream[i]); + if (ret) + goto err_cleanup; + } + + ret = uasp_alloc_cmd(fu); + if (ret) + goto err_free_stream; + uasp_setup_stream_res(fu, max_streams); + + ret = usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); + if (ret) + goto err_free_stream; + + return 0; + +err_free_stream: + uasp_free_cmdreq(fu); + +err_cleanup: + if (i) { + do { + uasp_cleanup_one_stream(fu, &fu->stream[i - 1]); + i--; + } while (i); + } + pr_err("UASP: endpoint setup failed\n"); + return ret; +} + +static void uasp_set_alt(struct f_uas *fu) +{ + struct usb_function *f = &fu->function; + struct usb_gadget *gadget = f->config->cdev->gadget; + int ret; + + fu->flags = USBG_IS_UAS; + + if (gadget->speed == USB_SPEED_SUPER) + fu->flags |= USBG_USE_STREAMS; + + config_ep_by_speed(gadget, f, fu->ep_in); + ret = usb_ep_enable(fu->ep_in); + if (ret) + goto err_b_in; + + config_ep_by_speed(gadget, f, fu->ep_out); + ret = usb_ep_enable(fu->ep_out); + if (ret) + goto err_b_out; + + config_ep_by_speed(gadget, f, fu->ep_cmd); + ret = usb_ep_enable(fu->ep_cmd); + if (ret) + goto err_cmd; + config_ep_by_speed(gadget, f, fu->ep_status); + ret = usb_ep_enable(fu->ep_status); + if (ret) + goto err_status; + + ret = uasp_prepare_reqs(fu); + if (ret) + goto err_wq; + fu->flags |= USBG_ENABLED; + + pr_info("Using the UAS protocol\n"); + return; +err_wq: + usb_ep_disable(fu->ep_status); +err_status: + usb_ep_disable(fu->ep_cmd); +err_cmd: + usb_ep_disable(fu->ep_out); +err_b_out: + usb_ep_disable(fu->ep_in); +err_b_in: + fu->flags = 0; +} + +static int get_cmd_dir(const unsigned char *cdb) +{ + int ret; + + switch (cdb[0]) { + case READ_6: + case READ_10: + case READ_12: + case READ_16: + case INQUIRY: + case MODE_SENSE: + case MODE_SENSE_10: + case SERVICE_ACTION_IN: + case MAINTENANCE_IN: + case PERSISTENT_RESERVE_IN: + case SECURITY_PROTOCOL_IN: + case ACCESS_CONTROL_IN: + case REPORT_LUNS: + case READ_BLOCK_LIMITS: + case READ_POSITION: + case READ_CAPACITY: + case READ_TOC: + case READ_FORMAT_CAPACITIES: + case REQUEST_SENSE: + ret = DMA_FROM_DEVICE; + break; + + case WRITE_6: + case WRITE_10: + case WRITE_12: + case WRITE_16: + case MODE_SELECT: + case MODE_SELECT_10: + case WRITE_VERIFY: + case WRITE_VERIFY_12: + case PERSISTENT_RESERVE_OUT: + case MAINTENANCE_OUT: + case SECURITY_PROTOCOL_OUT: + case ACCESS_CONTROL_OUT: + ret = DMA_TO_DEVICE; + break; + case ALLOW_MEDIUM_REMOVAL: + case TEST_UNIT_READY: + case SYNCHRONIZE_CACHE: + case START_STOP: + case ERASE: + case REZERO_UNIT: + case SEEK_10: + case SPACE: + case VERIFY: + case WRITE_FILEMARKS: + ret = DMA_NONE; + break; + default: + pr_warn("target: Unknown data direction for SCSI Opcode " + "0x%02x\n", cdb[0]); + ret = -EINVAL; + } + return ret; +} + +static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct se_cmd *se_cmd = &cmd->se_cmd; + + if (req->status < 0) { + pr_err("%s() state %d transfer failed\n", __func__, cmd->state); + goto cleanup; + } + + if (req->num_sgs == 0) { + sg_copy_from_buffer(se_cmd->t_data_sg, + se_cmd->t_data_nents, + cmd->data_buf, + se_cmd->data_length); + } + + complete(&cmd->write_complete); + return; + +cleanup: + usbg_cleanup_cmd(cmd); +} + +static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct f_uas *fu = cmd->fu; + struct usb_gadget *gadget = fuas_to_gadget(fu); + + if (!gadget->sg_supported) { + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); + if (!cmd->data_buf) + return -ENOMEM; + + req->buf = cmd->data_buf; + } else { + req->buf = NULL; + req->num_sgs = se_cmd->t_data_nents; + req->sg = se_cmd->t_data_sg; + } + + req->complete = usbg_data_write_cmpl; + req->length = se_cmd->data_length; + req->context = cmd; + return 0; +} + +static int usbg_send_status_response(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + struct f_uas *fu = cmd->fu; + + if (fu->flags & USBG_IS_BOT) + return bot_send_status_response(cmd); + else + return uasp_send_status_response(cmd); +} + +static int usbg_send_write_request(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + struct f_uas *fu = cmd->fu; + + if (fu->flags & USBG_IS_BOT) + return bot_send_write_request(cmd); + else + return uasp_send_write_request(cmd); +} + +static int usbg_send_read_response(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + struct f_uas *fu = cmd->fu; + + if (fu->flags & USBG_IS_BOT) + return bot_send_read_response(cmd); + else + return uasp_send_read_response(cmd); +} + +static void usbg_cmd_work(struct work_struct *work) +{ + struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); + struct se_cmd *se_cmd; + struct tcm_usbg_nexus *tv_nexus; + struct usbg_tpg *tpg; + int dir; + + se_cmd = &cmd->se_cmd; + tpg = cmd->fu->tpg; + tv_nexus = tpg->tpg_nexus; + dir = get_cmd_dir(cmd->cmd_buf); + if (dir < 0) { + transport_init_se_cmd(se_cmd, + tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, + tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, + cmd->prio_attr, cmd->sense_iu.sense); + goto out; + } + + if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, + cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, + 0, cmd->prio_attr, dir, TARGET_SCF_UNKNOWN_SIZE) < 0) + goto out; + + return; + +out: + transport_send_check_condition_and_sense(se_cmd, + TCM_UNSUPPORTED_SCSI_OPCODE, 1); + usbg_cleanup_cmd(cmd); +} + +static int usbg_submit_command(struct f_uas *fu, + void *cmdbuf, unsigned int len) +{ + struct command_iu *cmd_iu = cmdbuf; + struct usbg_cmd *cmd; + struct usbg_tpg *tpg; + struct se_cmd *se_cmd; + struct tcm_usbg_nexus *tv_nexus; + u32 cmd_len; + int ret; + + if (cmd_iu->iu_id != IU_ID_COMMAND) { + pr_err("Unsupported type %d\n", cmd_iu->iu_id); + return -EINVAL; + } + + cmd = kzalloc(sizeof *cmd, GFP_ATOMIC); + if (!cmd) + return -ENOMEM; + + cmd->fu = fu; + + /* XXX until I figure out why I can't free in on complete */ + kref_init(&cmd->ref); + kref_get(&cmd->ref); + + tpg = fu->tpg; + cmd_len = (cmd_iu->len & ~0x3) + 16; + if (cmd_len > USBG_MAX_CMD) + goto err; + + memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); + + cmd->tag = be16_to_cpup(&cmd_iu->tag); + if (fu->flags & USBG_USE_STREAMS) { + if (cmd->tag > UASP_SS_EP_COMP_NUM_STREAMS) + goto err; + if (!cmd->tag) + cmd->stream = &fu->stream[0]; + else + cmd->stream = &fu->stream[cmd->tag - 1]; + } else { + cmd->stream = &fu->stream[0]; + } + + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + pr_err("Missing nexus, ignoring command\n"); + goto err; + } + + switch (cmd_iu->prio_attr & 0x7) { + case UAS_HEAD_TAG: + cmd->prio_attr = MSG_HEAD_TAG; + break; + case UAS_ORDERED_TAG: + cmd->prio_attr = MSG_ORDERED_TAG; + break; + case UAS_ACA: + cmd->prio_attr = MSG_ACA_TAG; + break; + default: + pr_debug_once("Unsupported prio_attr: %02x.\n", + cmd_iu->prio_attr); + case UAS_SIMPLE_TAG: + cmd->prio_attr = MSG_SIMPLE_TAG; + break; + } + + se_cmd = &cmd->se_cmd; + cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun); + + INIT_WORK(&cmd->work, usbg_cmd_work); + ret = queue_work(tpg->workqueue, &cmd->work); + if (ret < 0) + goto err; + + return 0; +err: + kfree(cmd); + return -EINVAL; +} + +static void bot_cmd_work(struct work_struct *work) +{ + struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); + struct se_cmd *se_cmd; + struct tcm_usbg_nexus *tv_nexus; + struct usbg_tpg *tpg; + int dir; + + se_cmd = &cmd->se_cmd; + tpg = cmd->fu->tpg; + tv_nexus = tpg->tpg_nexus; + dir = get_cmd_dir(cmd->cmd_buf); + if (dir < 0) { + transport_init_se_cmd(se_cmd, + tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, + tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, + cmd->prio_attr, cmd->sense_iu.sense); + goto out; + } + + if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, + cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, + cmd->data_len, cmd->prio_attr, dir, 0) < 0) + goto out; + + return; + +out: + transport_send_check_condition_and_sense(se_cmd, + TCM_UNSUPPORTED_SCSI_OPCODE, 1); + usbg_cleanup_cmd(cmd); +} + +static int bot_submit_command(struct f_uas *fu, + void *cmdbuf, unsigned int len) +{ + struct bulk_cb_wrap *cbw = cmdbuf; + struct usbg_cmd *cmd; + struct usbg_tpg *tpg; + struct se_cmd *se_cmd; + struct tcm_usbg_nexus *tv_nexus; + u32 cmd_len; + int ret; + + if (cbw->Signature != cpu_to_le32(US_BULK_CB_SIGN)) { + pr_err("Wrong signature on CBW\n"); + return -EINVAL; + } + if (len != 31) { + pr_err("Wrong length for CBW\n"); + return -EINVAL; + } + + cmd_len = cbw->Length; + if (cmd_len < 1 || cmd_len > 16) + return -EINVAL; + + cmd = kzalloc(sizeof *cmd, GFP_ATOMIC); + if (!cmd) + return -ENOMEM; + + cmd->fu = fu; + + /* XXX until I figure out why I can't free in on complete */ + kref_init(&cmd->ref); + kref_get(&cmd->ref); + + tpg = fu->tpg; + + memcpy(cmd->cmd_buf, cbw->CDB, cmd_len); + + cmd->bot_tag = cbw->Tag; + + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + pr_err("Missing nexus, ignoring command\n"); + goto err; + } + + cmd->prio_attr = MSG_SIMPLE_TAG; + se_cmd = &cmd->se_cmd; + cmd->unpacked_lun = cbw->Lun; + cmd->is_read = cbw->Flags & US_BULK_FLAG_IN ? 1 : 0; + cmd->data_len = le32_to_cpu(cbw->DataTransferLength); + + INIT_WORK(&cmd->work, bot_cmd_work); + ret = queue_work(tpg->workqueue, &cmd->work); + if (ret < 0) + goto err; + + return 0; +err: + kfree(cmd); + return -EINVAL; +} + +/* Start fabric.c code */ + +static int usbg_check_true(struct se_portal_group *se_tpg) +{ + return 1; +} + +static int usbg_check_false(struct se_portal_group *se_tpg) +{ + return 0; +} + +static char *usbg_get_fabric_name(void) +{ + return "usb_gadget"; +} + +static u8 usbg_get_fabric_proto_ident(struct se_portal_group *se_tpg) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + struct usbg_tport *tport = tpg->tport; + u8 proto_id; + + switch (tport->tport_proto_id) { + case SCSI_PROTOCOL_SAS: + default: + proto_id = sas_get_fabric_proto_ident(se_tpg); + break; + } + + return proto_id; +} + +static char *usbg_get_fabric_wwn(struct se_portal_group *se_tpg) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + struct usbg_tport *tport = tpg->tport; + + return &tport->tport_name[0]; +} + +static u16 usbg_get_tag(struct se_portal_group *se_tpg) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + return tpg->tport_tpgt; +} + +static u32 usbg_get_default_depth(struct se_portal_group *se_tpg) +{ + return 1; +} + +static u32 usbg_get_pr_transport_id( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code, + unsigned char *buf) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + struct usbg_tport *tport = tpg->tport; + int ret = 0; + + switch (tport->tport_proto_id) { + case SCSI_PROTOCOL_SAS: + default: + ret = sas_get_pr_transport_id(se_tpg, se_nacl, pr_reg, + format_code, buf); + break; + } + + return ret; +} + +static u32 usbg_get_pr_transport_id_len( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + struct usbg_tport *tport = tpg->tport; + int ret = 0; + + switch (tport->tport_proto_id) { + case SCSI_PROTOCOL_SAS: + default: + ret = sas_get_pr_transport_id_len(se_tpg, se_nacl, pr_reg, + format_code); + break; + } + + return ret; +} + +static char *usbg_parse_pr_out_transport_id( + struct se_portal_group *se_tpg, + const char *buf, + u32 *out_tid_len, + char **port_nexus_ptr) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + struct usbg_tport *tport = tpg->tport; + char *tid = NULL; + + switch (tport->tport_proto_id) { + case SCSI_PROTOCOL_SAS: + default: + tid = sas_parse_pr_out_transport_id(se_tpg, buf, out_tid_len, + port_nexus_ptr); + } + + return tid; +} + +static struct se_node_acl *usbg_alloc_fabric_acl(struct se_portal_group *se_tpg) +{ + struct usbg_nacl *nacl; + + nacl = kzalloc(sizeof(struct usbg_nacl), GFP_KERNEL); + if (!nacl) { + printk(KERN_ERR "Unable to allocate struct usbg_nacl\n"); + return NULL; + } + + return &nacl->se_node_acl; +} + +static void usbg_release_fabric_acl( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl) +{ + struct usbg_nacl *nacl = container_of(se_nacl, + struct usbg_nacl, se_node_acl); + kfree(nacl); +} + +static u32 usbg_tpg_get_inst_index(struct se_portal_group *se_tpg) +{ + return 1; +} + +static void usbg_cmd_release(struct kref *ref) +{ + struct usbg_cmd *cmd = container_of(ref, struct usbg_cmd, + ref); + + transport_generic_free_cmd(&cmd->se_cmd, 0); +} + +static void usbg_release_cmd(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + kfree(cmd->data_buf); + kfree(cmd); + return; +} + +static int usbg_shutdown_session(struct se_session *se_sess) +{ + return 0; +} + +static void usbg_close_session(struct se_session *se_sess) +{ + return; +} + +static u32 usbg_sess_get_index(struct se_session *se_sess) +{ + return 0; +} + +/* + * XXX Error recovery: return != 0 if we expect writes. Dunno when that could be + */ +static int usbg_write_pending_status(struct se_cmd *se_cmd) +{ + return 0; +} + +static void usbg_set_default_node_attrs(struct se_node_acl *nacl) +{ + return; +} + +static u32 usbg_get_task_tag(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + struct f_uas *fu = cmd->fu; + + if (fu->flags & USBG_IS_BOT) + return le32_to_cpu(cmd->bot_tag); + else + return cmd->tag; +} + +static int usbg_get_cmd_state(struct se_cmd *se_cmd) +{ + return 0; +} + +static int usbg_queue_tm_rsp(struct se_cmd *se_cmd) +{ + return 0; +} + +static const char *usbg_check_wwn(const char *name) +{ + const char *n; + unsigned int len; + + n = strstr(name, "naa."); + if (!n) + return NULL; + n += 4; + len = strlen(n); + if (len == 0 || len > USBG_NAMELEN - 1) + return NULL; + return n; +} + +static struct se_node_acl *usbg_make_nodeacl( + struct se_portal_group *se_tpg, + struct config_group *group, + const char *name) +{ + struct se_node_acl *se_nacl, *se_nacl_new; + struct usbg_nacl *nacl; + u64 wwpn = 0; + u32 nexus_depth; + const char *wnn_name; + + wnn_name = usbg_check_wwn(name); + if (!wnn_name) + return ERR_PTR(-EINVAL); + se_nacl_new = usbg_alloc_fabric_acl(se_tpg); + if (!(se_nacl_new)) + return ERR_PTR(-ENOMEM); + + nexus_depth = 1; + /* + * se_nacl_new may be released by core_tpg_add_initiator_node_acl() + * when converting a NodeACL from demo mode -> explict + */ + se_nacl = core_tpg_add_initiator_node_acl(se_tpg, se_nacl_new, + name, nexus_depth); + if (IS_ERR(se_nacl)) { + usbg_release_fabric_acl(se_tpg, se_nacl_new); + return se_nacl; + } + /* + * Locate our struct usbg_nacl and set the FC Nport WWPN + */ + nacl = container_of(se_nacl, struct usbg_nacl, se_node_acl); + nacl->iport_wwpn = wwpn; + snprintf(nacl->iport_name, sizeof(nacl->iport_name), "%s", name); + return se_nacl; +} + +static void usbg_drop_nodeacl(struct se_node_acl *se_acl) +{ + struct usbg_nacl *nacl = container_of(se_acl, + struct usbg_nacl, se_node_acl); + core_tpg_del_initiator_node_acl(se_acl->se_tpg, se_acl, 1); + kfree(nacl); +} + +struct usbg_tpg *the_only_tpg_I_currently_have; + +static struct se_portal_group *usbg_make_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct usbg_tport *tport = container_of(wwn, struct usbg_tport, + tport_wwn); + struct usbg_tpg *tpg; + unsigned long tpgt; + int ret; + + if (strstr(name, "tpgt_") != name) + return ERR_PTR(-EINVAL); + if (kstrtoul(name + 5, 0, &tpgt) || tpgt > UINT_MAX) + return ERR_PTR(-EINVAL); + if (the_only_tpg_I_currently_have) { + pr_err("Until the gadget framework can't handle multiple\n"); + pr_err("gadgets, you can't do this here.\n"); + return ERR_PTR(-EBUSY); + } + + tpg = kzalloc(sizeof(struct usbg_tpg), GFP_KERNEL); + if (!tpg) { + printk(KERN_ERR "Unable to allocate struct usbg_tpg"); + return ERR_PTR(-ENOMEM); + } + mutex_init(&tpg->tpg_mutex); + atomic_set(&tpg->tpg_port_count, 0); + tpg->workqueue = alloc_workqueue("tcm_usb_gadget", 0, 1); + if (!tpg->workqueue) { + kfree(tpg); + return NULL; + } + + tpg->tport = tport; + tpg->tport_tpgt = tpgt; + + ret = core_tpg_register(&usbg_fabric_configfs->tf_ops, wwn, + &tpg->se_tpg, tpg, + TRANSPORT_TPG_TYPE_NORMAL); + if (ret < 0) { + destroy_workqueue(tpg->workqueue); + kfree(tpg); + return NULL; + } + the_only_tpg_I_currently_have = tpg; + return &tpg->se_tpg; +} + +static void usbg_drop_tpg(struct se_portal_group *se_tpg) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + + core_tpg_deregister(se_tpg); + destroy_workqueue(tpg->workqueue); + kfree(tpg); + the_only_tpg_I_currently_have = NULL; +} + +static struct se_wwn *usbg_make_tport( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct usbg_tport *tport; + const char *wnn_name; + u64 wwpn = 0; + + wnn_name = usbg_check_wwn(name); + if (!wnn_name) + return ERR_PTR(-EINVAL); + + tport = kzalloc(sizeof(struct usbg_tport), GFP_KERNEL); + if (!(tport)) { + printk(KERN_ERR "Unable to allocate struct usbg_tport"); + return ERR_PTR(-ENOMEM); + } + tport->tport_wwpn = wwpn; + snprintf(tport->tport_name, sizeof(tport->tport_name), "%s", wnn_name); + return &tport->tport_wwn; +} + +static void usbg_drop_tport(struct se_wwn *wwn) +{ + struct usbg_tport *tport = container_of(wwn, + struct usbg_tport, tport_wwn); + kfree(tport); +} + +/* + * If somebody feels like dropping the version property, go ahead. + */ +static ssize_t usbg_wwn_show_attr_version( + struct target_fabric_configfs *tf, + char *page) +{ + return sprintf(page, "usb-gadget fabric module\n"); +} +TF_WWN_ATTR_RO(usbg, version); + +static struct configfs_attribute *usbg_wwn_attrs[] = { + &usbg_wwn_version.attr, + NULL, +}; + +static ssize_t tcm_usbg_tpg_show_enable( + struct se_portal_group *se_tpg, + char *page) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + + return snprintf(page, PAGE_SIZE, "%u\n", tpg->gadget_connect); +} + +static int usbg_attach(struct usbg_tpg *); +static void usbg_detach(struct usbg_tpg *); + +static ssize_t tcm_usbg_tpg_store_enable( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + unsigned long op; + ssize_t ret; + + ret = kstrtoul(page, 0, &op); + if (ret < 0) + return -EINVAL; + if (op > 1) + return -EINVAL; + + if (op && tpg->gadget_connect) + goto out; + if (!op && !tpg->gadget_connect) + goto out; + + if (op) { + ret = usbg_attach(tpg); + if (ret) + goto out; + } else { + usbg_detach(tpg); + } + tpg->gadget_connect = op; +out: + return count; +} +TF_TPG_BASE_ATTR(tcm_usbg, enable, S_IRUGO | S_IWUSR); + +static ssize_t tcm_usbg_tpg_show_nexus( + struct se_portal_group *se_tpg, + char *page) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + struct tcm_usbg_nexus *tv_nexus; + ssize_t ret; + + mutex_lock(&tpg->tpg_mutex); + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + ret = -ENODEV; + goto out; + } + ret = snprintf(page, PAGE_SIZE, "%s\n", + tv_nexus->tvn_se_sess->se_node_acl->initiatorname); +out: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static int tcm_usbg_make_nexus(struct usbg_tpg *tpg, char *name) +{ + struct se_portal_group *se_tpg; + struct tcm_usbg_nexus *tv_nexus; + int ret; + + mutex_lock(&tpg->tpg_mutex); + if (tpg->tpg_nexus) { + ret = -EEXIST; + pr_debug("tpg->tpg_nexus already exists\n"); + goto err_unlock; + } + se_tpg = &tpg->se_tpg; + + ret = -ENOMEM; + tv_nexus = kzalloc(sizeof(*tv_nexus), GFP_KERNEL); + if (!tv_nexus) { + pr_err("Unable to allocate struct tcm_vhost_nexus\n"); + goto err_unlock; + } + tv_nexus->tvn_se_sess = transport_init_session(); + if (IS_ERR(tv_nexus->tvn_se_sess)) + goto err_free; + + /* + * Since we are running in 'demo mode' this call with generate a + * struct se_node_acl for the tcm_vhost struct se_portal_group with + * the SCSI Initiator port name of the passed configfs group 'name'. + */ + tv_nexus->tvn_se_sess->se_node_acl = core_tpg_check_initiator_node_acl( + se_tpg, name); + if (!tv_nexus->tvn_se_sess->se_node_acl) { + pr_debug("core_tpg_check_initiator_node_acl() failed" + " for %s\n", name); + goto err_session; + } + /* + * Now register the TCM vHost virtual I_T Nexus as active with the + * call to __transport_register_session() + */ + __transport_register_session(se_tpg, tv_nexus->tvn_se_sess->se_node_acl, + tv_nexus->tvn_se_sess, tv_nexus); + tpg->tpg_nexus = tv_nexus; + mutex_unlock(&tpg->tpg_mutex); + return 0; + +err_session: + transport_free_session(tv_nexus->tvn_se_sess); +err_free: + kfree(tv_nexus); +err_unlock: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static int tcm_usbg_drop_nexus(struct usbg_tpg *tpg) +{ + struct se_session *se_sess; + struct tcm_usbg_nexus *tv_nexus; + int ret = -ENODEV; + + mutex_lock(&tpg->tpg_mutex); + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) + goto out; + + se_sess = tv_nexus->tvn_se_sess; + if (!se_sess) + goto out; + + if (atomic_read(&tpg->tpg_port_count)) { + ret = -EPERM; + pr_err("Unable to remove Host I_T Nexus with" + " active TPG port count: %d\n", + atomic_read(&tpg->tpg_port_count)); + goto out; + } + + pr_debug("Removing I_T Nexus to Initiator Port: %s\n", + tv_nexus->tvn_se_sess->se_node_acl->initiatorname); + /* + * Release the SCSI I_T Nexus to the emulated vHost Target Port + */ + transport_deregister_session(tv_nexus->tvn_se_sess); + tpg->tpg_nexus = NULL; + + kfree(tv_nexus); + ret = 0; +out: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static ssize_t tcm_usbg_tpg_store_nexus( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + unsigned char i_port[USBG_NAMELEN], *ptr; + int ret; + + if (!strncmp(page, "NULL", 4)) { + ret = tcm_usbg_drop_nexus(tpg); + return (!ret) ? count : ret; + } + if (strlen(page) >= USBG_NAMELEN) { + pr_err("Emulated NAA Sas Address: %s, exceeds" + " max: %d\n", page, USBG_NAMELEN); + return -EINVAL; + } + snprintf(i_port, USBG_NAMELEN, "%s", page); + + ptr = strstr(i_port, "naa."); + if (!ptr) { + pr_err("Missing 'naa.' prefix\n"); + return -EINVAL; + } + + if (i_port[strlen(i_port) - 1] == '\n') + i_port[strlen(i_port) - 1] = '\0'; + + ret = tcm_usbg_make_nexus(tpg, &i_port[4]); + if (ret < 0) + return ret; + return count; +} +TF_TPG_BASE_ATTR(tcm_usbg, nexus, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *usbg_base_attrs[] = { + &tcm_usbg_tpg_enable.attr, + &tcm_usbg_tpg_nexus.attr, + NULL, +}; + +static int usbg_port_link(struct se_portal_group *se_tpg, struct se_lun *lun) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + + atomic_inc(&tpg->tpg_port_count); + smp_mb__after_atomic_inc(); + return 0; +} + +static void usbg_port_unlink(struct se_portal_group *se_tpg, + struct se_lun *se_lun) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + + atomic_dec(&tpg->tpg_port_count); + smp_mb__after_atomic_dec(); +} + +static int usbg_check_stop_free(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + + kref_put(&cmd->ref, usbg_cmd_release); + return 1; +} + +static struct target_core_fabric_ops usbg_ops = { + .get_fabric_name = usbg_get_fabric_name, + .get_fabric_proto_ident = usbg_get_fabric_proto_ident, + .tpg_get_wwn = usbg_get_fabric_wwn, + .tpg_get_tag = usbg_get_tag, + .tpg_get_default_depth = usbg_get_default_depth, + .tpg_get_pr_transport_id = usbg_get_pr_transport_id, + .tpg_get_pr_transport_id_len = usbg_get_pr_transport_id_len, + .tpg_parse_pr_out_transport_id = usbg_parse_pr_out_transport_id, + .tpg_check_demo_mode = usbg_check_true, + .tpg_check_demo_mode_cache = usbg_check_false, + .tpg_check_demo_mode_write_protect = usbg_check_false, + .tpg_check_prod_mode_write_protect = usbg_check_false, + .tpg_alloc_fabric_acl = usbg_alloc_fabric_acl, + .tpg_release_fabric_acl = usbg_release_fabric_acl, + .tpg_get_inst_index = usbg_tpg_get_inst_index, + .release_cmd = usbg_release_cmd, + .shutdown_session = usbg_shutdown_session, + .close_session = usbg_close_session, + .sess_get_index = usbg_sess_get_index, + .sess_get_initiator_sid = NULL, + .write_pending = usbg_send_write_request, + .write_pending_status = usbg_write_pending_status, + .set_default_node_attributes = usbg_set_default_node_attrs, + .get_task_tag = usbg_get_task_tag, + .get_cmd_state = usbg_get_cmd_state, + .queue_data_in = usbg_send_read_response, + .queue_status = usbg_send_status_response, + .queue_tm_rsp = usbg_queue_tm_rsp, + .check_stop_free = usbg_check_stop_free, + + .fabric_make_wwn = usbg_make_tport, + .fabric_drop_wwn = usbg_drop_tport, + .fabric_make_tpg = usbg_make_tpg, + .fabric_drop_tpg = usbg_drop_tpg, + .fabric_post_link = usbg_port_link, + .fabric_pre_unlink = usbg_port_unlink, + .fabric_make_np = NULL, + .fabric_drop_np = NULL, + .fabric_make_nodeacl = usbg_make_nodeacl, + .fabric_drop_nodeacl = usbg_drop_nodeacl, +}; + +static int usbg_register_configfs(void) +{ + struct target_fabric_configfs *fabric; + int ret; + + fabric = target_fabric_configfs_init(THIS_MODULE, "usb_gadget"); + if (IS_ERR(fabric)) { + printk(KERN_ERR "target_fabric_configfs_init() failed\n"); + return PTR_ERR(fabric); + } + + fabric->tf_ops = usbg_ops; + TF_CIT_TMPL(fabric)->tfc_wwn_cit.ct_attrs = usbg_wwn_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_base_cit.ct_attrs = usbg_base_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_attrib_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_param_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_np_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_param_cit.ct_attrs = NULL; + ret = target_fabric_configfs_register(fabric); + if (ret < 0) { + printk(KERN_ERR "target_fabric_configfs_register() failed" + " for usb-gadget\n"); + return ret; + } + usbg_fabric_configfs = fabric; + return 0; +}; + +static void usbg_deregister_configfs(void) +{ + if (!(usbg_fabric_configfs)) + return; + + target_fabric_configfs_deregister(usbg_fabric_configfs); + usbg_fabric_configfs = NULL; +}; + +/* Start gadget.c code */ + +static struct usb_interface_descriptor bot_intf_desc = { + .bLength = sizeof(bot_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bAlternateSetting = USB_G_ALT_INT_BBB, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, + .bInterfaceProtocol = USB_PR_BULK, +}; + +static struct usb_interface_descriptor uasp_intf_desc = { + .bLength = sizeof(uasp_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 4, + .bAlternateSetting = USB_G_ALT_INT_UAS, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, + .bInterfaceProtocol = USB_PR_UAS, +}; + +static struct usb_endpoint_descriptor uasp_bi_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uasp_fs_bi_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_pipe_usage_descriptor uasp_bi_pipe_desc = { + .bLength = sizeof(uasp_bi_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = DATA_IN_PIPE_ID, +}; + +static struct usb_endpoint_descriptor uasp_ss_bi_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = { + .bLength = sizeof(uasp_bi_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, + .wBytesPerInterval = 0, +}; + +static struct usb_ss_ep_comp_descriptor bot_bi_ep_comp_desc = { + .bLength = sizeof(bot_bi_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, +}; + +static struct usb_endpoint_descriptor uasp_bo_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uasp_fs_bo_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_pipe_usage_descriptor uasp_bo_pipe_desc = { + .bLength = sizeof(uasp_bo_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = DATA_OUT_PIPE_ID, +}; + +static struct usb_endpoint_descriptor uasp_ss_bo_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(0x400), +}; + +static struct usb_ss_ep_comp_descriptor uasp_bo_ep_comp_desc = { + .bLength = sizeof(uasp_bo_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, +}; + +static struct usb_ss_ep_comp_descriptor bot_bo_ep_comp_desc = { + .bLength = sizeof(bot_bo_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_endpoint_descriptor uasp_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uasp_fs_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_pipe_usage_descriptor uasp_status_pipe_desc = { + .bLength = sizeof(uasp_status_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = STATUS_PIPE_ID, +}; + +static struct usb_endpoint_descriptor uasp_ss_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_status_in_ep_comp_desc = { + .bLength = sizeof(uasp_status_in_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, +}; + +static struct usb_endpoint_descriptor uasp_cmd_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uasp_fs_cmd_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_pipe_usage_descriptor uasp_cmd_pipe_desc = { + .bLength = sizeof(uasp_cmd_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = CMD_PIPE_ID, +}; + +static struct usb_endpoint_descriptor uasp_ss_cmd_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_cmd_comp_desc = { + .bLength = sizeof(uasp_cmd_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *uasp_fs_function_desc[] = { + (struct usb_descriptor_header *) &bot_intf_desc, + (struct usb_descriptor_header *) &uasp_fs_bi_desc, + (struct usb_descriptor_header *) &uasp_fs_bo_desc, + + (struct usb_descriptor_header *) &uasp_intf_desc, + (struct usb_descriptor_header *) &uasp_fs_bi_desc, + (struct usb_descriptor_header *) &uasp_bi_pipe_desc, + (struct usb_descriptor_header *) &uasp_fs_bo_desc, + (struct usb_descriptor_header *) &uasp_bo_pipe_desc, + (struct usb_descriptor_header *) &uasp_fs_status_desc, + (struct usb_descriptor_header *) &uasp_status_pipe_desc, + (struct usb_descriptor_header *) &uasp_fs_cmd_desc, + (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, + NULL, +}; + +static struct usb_descriptor_header *uasp_hs_function_desc[] = { + (struct usb_descriptor_header *) &bot_intf_desc, + (struct usb_descriptor_header *) &uasp_bi_desc, + (struct usb_descriptor_header *) &uasp_bo_desc, + + (struct usb_descriptor_header *) &uasp_intf_desc, + (struct usb_descriptor_header *) &uasp_bi_desc, + (struct usb_descriptor_header *) &uasp_bi_pipe_desc, + (struct usb_descriptor_header *) &uasp_bo_desc, + (struct usb_descriptor_header *) &uasp_bo_pipe_desc, + (struct usb_descriptor_header *) &uasp_status_desc, + (struct usb_descriptor_header *) &uasp_status_pipe_desc, + (struct usb_descriptor_header *) &uasp_cmd_desc, + (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, + NULL, +}; + +static struct usb_descriptor_header *uasp_ss_function_desc[] = { + (struct usb_descriptor_header *) &bot_intf_desc, + (struct usb_descriptor_header *) &uasp_ss_bi_desc, + (struct usb_descriptor_header *) &bot_bi_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_ss_bo_desc, + (struct usb_descriptor_header *) &bot_bo_ep_comp_desc, + + (struct usb_descriptor_header *) &uasp_intf_desc, + (struct usb_descriptor_header *) &uasp_ss_bi_desc, + (struct usb_descriptor_header *) &uasp_bi_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_bi_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_bo_desc, + (struct usb_descriptor_header *) &uasp_bo_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_bo_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_status_desc, + (struct usb_descriptor_header *) &uasp_status_in_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_status_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_cmd_desc, + (struct usb_descriptor_header *) &uasp_cmd_comp_desc, + (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, + NULL, +}; + +#define UAS_VENDOR_ID 0x0525 /* NetChip */ +#define UAS_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ + +static struct usb_device_descriptor usbg_device_desc = { + .bLength = sizeof(usbg_device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = cpu_to_le16(UAS_VENDOR_ID), + .idProduct = cpu_to_le16(UAS_PRODUCT_ID), + .bNumConfigurations = 1, +}; + +static struct usb_string usbg_us_strings[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "Target Manufactor", + [USB_GADGET_PRODUCT_IDX].s = "Target Product", + [USB_GADGET_SERIAL_IDX].s = "000000000001", + [USB_G_STR_CONFIG].s = "default config", + [USB_G_STR_INT_UAS].s = "USB Attached SCSI", + [USB_G_STR_INT_BBB].s = "Bulk Only Transport", + { }, +}; + +static struct usb_gadget_strings usbg_stringtab = { + .language = 0x0409, + .strings = usbg_us_strings, +}; + +static struct usb_gadget_strings *usbg_strings[] = { + &usbg_stringtab, + NULL, +}; + +static int guas_unbind(struct usb_composite_dev *cdev) +{ + return 0; +} + +static struct usb_configuration usbg_config_driver = { + .label = "Linux Target", + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +static void give_back_ep(struct usb_ep **pep) +{ + struct usb_ep *ep = *pep; + if (!ep) + return; + ep->driver_data = NULL; +} + +static int usbg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + struct usb_gadget *gadget = c->cdev->gadget; + struct usb_ep *ep; + int iface; + int ret; + + iface = usb_interface_id(c, f); + if (iface < 0) + return iface; + + bot_intf_desc.bInterfaceNumber = iface; + uasp_intf_desc.bInterfaceNumber = iface; + fu->iface = iface; + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bi_desc, + &uasp_bi_ep_comp_desc); + if (!ep) + goto ep_fail; + + ep->driver_data = fu; + fu->ep_in = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bo_desc, + &uasp_bo_ep_comp_desc); + if (!ep) + goto ep_fail; + ep->driver_data = fu; + fu->ep_out = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_status_desc, + &uasp_status_in_ep_comp_desc); + if (!ep) + goto ep_fail; + ep->driver_data = fu; + fu->ep_status = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_cmd_desc, + &uasp_cmd_comp_desc); + if (!ep) + goto ep_fail; + ep->driver_data = fu; + fu->ep_cmd = ep; + + /* Assume endpoint addresses are the same for both speeds */ + uasp_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; + uasp_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; + uasp_status_desc.bEndpointAddress = + uasp_ss_status_desc.bEndpointAddress; + uasp_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + + uasp_fs_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; + uasp_fs_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; + uasp_fs_status_desc.bEndpointAddress = + uasp_ss_status_desc.bEndpointAddress; + uasp_fs_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + + ret = usb_assign_descriptors(f, uasp_fs_function_desc, + uasp_hs_function_desc, uasp_ss_function_desc); + if (ret) + goto ep_fail; + + return 0; +ep_fail: + pr_err("Can't claim all required eps\n"); + + give_back_ep(&fu->ep_in); + give_back_ep(&fu->ep_out); + give_back_ep(&fu->ep_status); + give_back_ep(&fu->ep_cmd); + return -ENOTSUPP; +} + +static void usbg_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + + usb_free_all_descriptors(f); + kfree(fu); +} + +struct guas_setup_wq { + struct work_struct work; + struct f_uas *fu; + unsigned int alt; +}; + +static void usbg_delayed_set_alt(struct work_struct *wq) +{ + struct guas_setup_wq *work = container_of(wq, struct guas_setup_wq, + work); + struct f_uas *fu = work->fu; + int alt = work->alt; + + kfree(work); + + if (fu->flags & USBG_IS_BOT) + bot_cleanup_old_alt(fu); + if (fu->flags & USBG_IS_UAS) + uasp_cleanup_old_alt(fu); + + if (alt == USB_G_ALT_INT_BBB) + bot_set_alt(fu); + else if (alt == USB_G_ALT_INT_UAS) + uasp_set_alt(fu); + usb_composite_setup_continue(fu->function.config->cdev); +} + +static int usbg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_uas *fu = to_f_uas(f); + + if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) { + struct guas_setup_wq *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (!work) + return -ENOMEM; + INIT_WORK(&work->work, usbg_delayed_set_alt); + work->fu = fu; + work->alt = alt; + schedule_work(&work->work); + return USB_GADGET_DELAYED_STATUS; + } + return -EOPNOTSUPP; +} + +static void usbg_disable(struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + + if (fu->flags & USBG_IS_UAS) + uasp_cleanup_old_alt(fu); + else if (fu->flags & USBG_IS_BOT) + bot_cleanup_old_alt(fu); + fu->flags = 0; +} + +static int usbg_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_uas *fu = to_f_uas(f); + + if (!(fu->flags & USBG_IS_BOT)) + return -EOPNOTSUPP; + + return usbg_bot_setup(f, ctrl); +} + +static int usbg_cfg_bind(struct usb_configuration *c) +{ + struct f_uas *fu; + int ret; + + fu = kzalloc(sizeof(*fu), GFP_KERNEL); + if (!fu) + return -ENOMEM; + fu->function.name = "Target Function"; + fu->function.bind = usbg_bind; + fu->function.unbind = usbg_unbind; + fu->function.set_alt = usbg_set_alt; + fu->function.setup = usbg_setup; + fu->function.disable = usbg_disable; + fu->tpg = the_only_tpg_I_currently_have; + + bot_intf_desc.iInterface = usbg_us_strings[USB_G_STR_INT_BBB].id; + uasp_intf_desc.iInterface = usbg_us_strings[USB_G_STR_INT_UAS].id; + + ret = usb_add_function(c, &fu->function); + if (ret) + goto err; + + return 0; +err: + kfree(fu); + return ret; +} + +static int usb_target_bind(struct usb_composite_dev *cdev) +{ + int ret; + + ret = usb_string_ids_tab(cdev, usbg_us_strings); + if (ret) + return ret; + + usbg_device_desc.iManufacturer = + usbg_us_strings[USB_GADGET_MANUFACTURER_IDX].id; + usbg_device_desc.iProduct = usbg_us_strings[USB_GADGET_PRODUCT_IDX].id; + usbg_device_desc.iSerialNumber = + usbg_us_strings[USB_GADGET_SERIAL_IDX].id; + usbg_config_driver.iConfiguration = + usbg_us_strings[USB_G_STR_CONFIG].id; + + ret = usb_add_config(cdev, &usbg_config_driver, + usbg_cfg_bind); + if (ret) + return ret; + usb_composite_overwrite_options(cdev, &coverwrite); + return 0; +} + +static __refdata struct usb_composite_driver usbg_driver = { + .name = "g_target", + .dev = &usbg_device_desc, + .strings = usbg_strings, + .max_speed = USB_SPEED_SUPER, + .bind = usb_target_bind, + .unbind = guas_unbind, +}; + +static int usbg_attach(struct usbg_tpg *tpg) +{ + return usb_composite_probe(&usbg_driver); +} + +static void usbg_detach(struct usbg_tpg *tpg) +{ + usb_composite_unregister(&usbg_driver); +} + +static int __init usb_target_gadget_init(void) +{ + int ret; + + ret = usbg_register_configfs(); + return ret; +} +module_init(usb_target_gadget_init); + +static void __exit usb_target_gadget_exit(void) +{ + usbg_deregister_configfs(); +} +module_exit(usb_target_gadget_exit); + +MODULE_AUTHOR("Sebastian Andrzej Siewior "); +MODULE_DESCRIPTION("usb-gadget fabric"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/tcm_usb_gadget.h b/drivers/usb/gadget/tcm_usb_gadget.h new file mode 100644 index 00000000..82892199 --- /dev/null +++ b/drivers/usb/gadget/tcm_usb_gadget.h @@ -0,0 +1,145 @@ +#ifndef __TARGET_USB_GADGET_H__ +#define __TARGET_USB_GADGET_H__ + +#include +/* #include */ +#include +#include +#include +#include +#include +#include + +#define USBG_NAMELEN 32 + +#define fuas_to_gadget(f) (f->function.config->cdev->gadget) +#define UASP_SS_EP_COMP_LOG_STREAMS 4 +#define UASP_SS_EP_COMP_NUM_STREAMS (1 << UASP_SS_EP_COMP_LOG_STREAMS) + +enum { + USB_G_STR_CONFIG = USB_GADGET_FIRST_AVAIL_IDX, + USB_G_STR_INT_UAS, + USB_G_STR_INT_BBB, +}; + +#define USB_G_ALT_INT_BBB 0 +#define USB_G_ALT_INT_UAS 1 + +struct usbg_nacl { + /* Binary World Wide unique Port Name for SAS Initiator port */ + u64 iport_wwpn; + /* ASCII formatted WWPN for Sas Initiator port */ + char iport_name[USBG_NAMELEN]; + /* Returned by usbg_make_nodeacl() */ + struct se_node_acl se_node_acl; +}; + +struct tcm_usbg_nexus { + struct se_session *tvn_se_sess; +}; + +struct usbg_tpg { + struct mutex tpg_mutex; + /* SAS port target portal group tag for TCM */ + u16 tport_tpgt; + /* Pointer back to usbg_tport */ + struct usbg_tport *tport; + struct workqueue_struct *workqueue; + /* Returned by usbg_make_tpg() */ + struct se_portal_group se_tpg; + u32 gadget_connect; + struct tcm_usbg_nexus *tpg_nexus; + atomic_t tpg_port_count; +}; + +struct usbg_tport { + /* SCSI protocol the tport is providing */ + u8 tport_proto_id; + /* Binary World Wide unique Port Name for SAS Target port */ + u64 tport_wwpn; + /* ASCII formatted WWPN for SAS Target port */ + char tport_name[USBG_NAMELEN]; + /* Returned by usbg_make_tport() */ + struct se_wwn tport_wwn; +}; + +enum uas_state { + UASP_SEND_DATA, + UASP_RECEIVE_DATA, + UASP_SEND_STATUS, + UASP_QUEUE_COMMAND, +}; + +#define USBG_MAX_CMD 64 +struct usbg_cmd { + /* common */ + u8 cmd_buf[USBG_MAX_CMD]; + u32 data_len; + struct work_struct work; + int unpacked_lun; + struct se_cmd se_cmd; + void *data_buf; /* used if no sg support available */ + struct f_uas *fu; + struct completion write_complete; + struct kref ref; + + /* UAS only */ + u16 tag; + u16 prio_attr; + struct sense_iu sense_iu; + enum uas_state state; + struct uas_stream *stream; + + /* BOT only */ + __le32 bot_tag; + unsigned int csw_code; + unsigned is_read:1; + +}; + +struct uas_stream { + struct usb_request *req_in; + struct usb_request *req_out; + struct usb_request *req_status; +}; + +struct usbg_cdb { + struct usb_request *req; + void *buf; +}; + +struct bot_status { + struct usb_request *req; + struct bulk_cs_wrap csw; +}; + +struct f_uas { + struct usbg_tpg *tpg; + struct usb_function function; + u16 iface; + + u32 flags; +#define USBG_ENABLED (1 << 0) +#define USBG_IS_UAS (1 << 1) +#define USBG_USE_STREAMS (1 << 2) +#define USBG_IS_BOT (1 << 3) +#define USBG_BOT_CMD_PEND (1 << 4) + + struct usbg_cdb cmd; + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + /* UAS */ + struct usb_ep *ep_status; + struct usb_ep *ep_cmd; + struct uas_stream stream[UASP_SS_EP_COMP_NUM_STREAMS]; + + /* BOT */ + struct bot_status bot_status; + struct usb_request *bot_req_in; + struct usb_request *bot_req_out; +}; + +extern struct usbg_tpg *the_only_tpg_I_currently_have; + +#endif diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c new file mode 100644 index 00000000..b06251bf --- /dev/null +++ b/drivers/usb/gadget/u_ether.c @@ -0,0 +1,1581 @@ +/* + * u_ether.c -- Ethernet-over-USB link layer utilities for Gadget stack + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_ether.h" + + +/* + * This component encapsulates the Ethernet link glue needed to provide + * one (!) network link through the USB gadget stack, normally "usb0". + * + * The control and data models are handled by the function driver which + * connects to this code; such as CDC Ethernet (ECM or EEM), + * "CDC Subset", or RNDIS. That includes all descriptor and endpoint + * management. + * + * Link level addressing is handled by this component using module + * parameters; if no such parameters are provided, random link level + * addresses are used. Each end of the link uses one address. The + * host end address is exported in various ways, and is often recorded + * in configuration databases. + * + * The driver which assembles each configuration using such a link is + * responsible for ensuring that each configuration includes at most one + * instance of is network link. (The network layer provides ways for + * this single "physical" link to be used by multiple virtual links.) + */ + +#define UETH__VERSION "29-May-2008" + +struct eth_dev { + /* lock is held while accessing port_usb + */ + spinlock_t lock; + struct gether *port_usb; + + struct net_device *net; + struct usb_gadget *gadget; + + spinlock_t req_lock; /* guard {rx,tx}_reqs */ + struct list_head tx_reqs, rx_reqs; + atomic_t tx_qlen; + + struct sk_buff_head rx_frames; + + unsigned header_len; + struct sk_buff *(*wrap)(struct gether *, struct sk_buff *skb); + int (*unwrap)(struct gether *, + struct sk_buff *skb, + struct sk_buff_head *list); + + struct work_struct work; + + unsigned long todo; +#define WORK_RX_MEMORY 0 + + bool zlp; + u8 host_mac[ETH_ALEN]; +}; + +/*-------------------------------------------------------------------------*/ + +#define RX_EXTRA 20 /* bytes guarding against rx overflows */ + +#define DEFAULT_QLEN 2 /* double buffering by default */ + +#ifdef CONFIG_USB_SPRD_DWC +static unsigned qmult = 15; +#else +static unsigned qmult = 5; +#endif +module_param(qmult, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(qmult, "queue length multiplier at high/super speed"); + +/* for dual-speed hardware, use deeper queues at high/super speed */ +static inline int qlen(struct usb_gadget *gadget) +{ + if (gadget_is_dualspeed(gadget) && (gadget->speed == USB_SPEED_HIGH || + gadget->speed == USB_SPEED_SUPER)) + return qmult * DEFAULT_QLEN; + else + return DEFAULT_QLEN; +} + +/*-------------------------------------------------------------------------*/ + +/* REVISIT there must be a better way than having two sets + * of debug calls ... + */ + +#undef DBG +#undef VDBG +#undef ERROR +#undef INFO + +#define xprintk(d, level, fmt, args...) \ + printk(level "%s: " fmt , (d)->net->name , ## args) + +#ifdef DEBUG +#undef DEBUG +#define DBG(dev, fmt, args...) \ + xprintk(dev , KERN_DEBUG , fmt , ## args) +#else +#define DBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VDBG DBG +#else +#define VDBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#define ERROR(dev, fmt, args...) \ + xprintk(dev , KERN_ERR , fmt , ## args) +#define INFO(dev, fmt, args...) \ + xprintk(dev , KERN_INFO , fmt , ## args) + +/*-------------------------------------------------------------------------*/ + +/* NETWORK DRIVER HOOKUP (to the layer above this driver) */ + +static int ueth_change_mtu(struct net_device *net, int new_mtu) +{ + struct eth_dev *dev = netdev_priv(net); + unsigned long flags; + int status = 0; + + /* don't change MTU on "live" link (peer won't know) */ + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) + status = -EBUSY; + else if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN) + status = -ERANGE; + else + net->mtu = new_mtu; + spin_unlock_irqrestore(&dev->lock, flags); + + return status; +} + +static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p) +{ + struct eth_dev *dev = netdev_priv(net); + + strlcpy(p->driver, "g_ether", sizeof(p->driver)); + strlcpy(p->version, UETH__VERSION, sizeof(p->version)); + strlcpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version)); + strlcpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info)); +} + +/* REVISIT can also support: + * - WOL (by tracking suspends and issuing remote wakeup) + * - msglevel (implies updated messaging) + * - ... probably more ethtool ops + */ + +static const struct ethtool_ops ops = { + .get_drvinfo = eth_get_drvinfo, + .get_link = ethtool_op_get_link, +}; + +static void defer_kevent(struct eth_dev *dev, int flag) +{ + if (test_and_set_bit(flag, &dev->todo)) + return; + if (!schedule_work(&dev->work)) + ERROR(dev, "kevent %d may have been dropped\n", flag); + else + DBG(dev, "kevent %d scheduled\n", flag); +} + +static void rx_complete(struct usb_ep *ep, struct usb_request *req); +static void tx_complete(struct usb_ep *ep, struct usb_request *req); +static int prealloc(struct list_head *list, + struct usb_ep *ep, unsigned n, + bool sg_supported); + +static int +rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) +{ + struct sk_buff *skb; + int retval = -ENOMEM; + size_t size = 0; + struct usb_ep *out; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) + out = dev->port_usb->out_ep; + else + out = NULL; + spin_unlock_irqrestore(&dev->lock, flags); + + if (!out) + return -ENOTCONN; + + + /* Padding up to RX_EXTRA handles minor disagreements with host. + * Normally we use the USB "terminate on short read" convention; + * so allow up to (N*maxpacket), since that memory is normally + * already allocated. Some hardware doesn't deal well with short + * reads (e.g. DMA must be N*maxpacket), so for now don't trim a + * byte off the end (to force hardware errors on overflow). + * + * RNDIS uses internal framing, and explicitly allows senders to + * pad to end-of-packet. That's potentially nice for speed, but + * means receivers can't recover lost synch on their own (because + * new packets don't only start after a short RX). + */ + size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA; + size += dev->port_usb->header_len; + size += out->maxpacket - 1; + size -= size % out->maxpacket; + + if (dev->port_usb->is_fixed) + size = max_t(size_t, size, dev->port_usb->fixed_out_len); + size = size * RNDIS_MSG_MAX_NUM; + + skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags); + if (skb == NULL) { + DBG(dev, "no rx skb\n"); + goto enomem; + } + + /* Some platforms perform better when IP packets are aligned, + * but on at least one, checksumming fails otherwise. Note: + * RNDIS headers involve variable numbers of LE32 values. + */ + /* + * RX: Do not move data by IP_ALIGN: + * if your DMA controller cannot handle it + */ + if (!gadget_dma32(dev->gadget)) + skb_reserve(skb, NET_IP_ALIGN); + + req->buf = skb->data; + req->length = size; + req->complete = rx_complete; + req->context = skb; + + retval = usb_ep_queue(out, req, gfp_flags); + if (retval == -ENOMEM) +enomem: + defer_kevent(dev, WORK_RX_MEMORY); + if (retval) { + DBG(dev, "rx submit --> %d\n", retval); + if (skb) + dev_kfree_skb_any(skb); + spin_lock_irqsave(&dev->req_lock, flags); + list_add(&req->list, &dev->rx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return retval; +} + +static void rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sk_buff *skb = req->context, *skb2; + struct eth_dev *dev = ep->driver_data; + int status = req->status; + + switch (status) { + + /* normal completion */ + case 0: + skb_put(skb, req->actual); + if (gadget_dma32(dev->gadget) && NET_IP_ALIGN) { + u8 *data = skb->data; + size_t len = skb_headlen(skb); + skb_reserve(skb, NET_IP_ALIGN); + memmove(skb->data, data, len); + } + if (dev->unwrap) { + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) { + status = dev->unwrap(dev->port_usb, + skb, + &dev->rx_frames); + } else { + dev_kfree_skb_any(skb); + status = -ENOTCONN; + } + spin_unlock_irqrestore(&dev->lock, flags); + } else { + skb_queue_tail(&dev->rx_frames, skb); + } + skb = NULL; + + skb2 = skb_dequeue(&dev->rx_frames); + while (skb2) { + if (status < 0 + || ETH_HLEN > skb2->len + || skb2->len > VLAN_ETH_FRAME_LEN) { + dev->net->stats.rx_errors++; + dev->net->stats.rx_length_errors++; + DBG(dev, "rx length %d\n", skb2->len); + dev_kfree_skb_any(skb2); + goto next_frame; + } + skb2->protocol = eth_type_trans(skb2, dev->net); + dev->net->stats.rx_packets++; + dev->net->stats.rx_bytes += skb2->len; + + /* no buffer copies needed, unless hardware can't + * use skb buffers. + */ + status = netif_rx(skb2); +next_frame: + skb2 = skb_dequeue(&dev->rx_frames); + } + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + VDBG(dev, "rx shutdown, code %d\n", status); + goto quiesce; + + /* for hardware automagic (such as pxa) */ + case -ECONNABORTED: /* endpoint reset */ + DBG(dev, "rx %s reset\n", ep->name); + defer_kevent(dev, WORK_RX_MEMORY); +quiesce: + dev_kfree_skb_any(skb); + goto clean; + + /* data overrun */ + case -EOVERFLOW: + dev->net->stats.rx_over_errors++; + /* FALLTHROUGH */ + + default: + dev->net->stats.rx_errors++; + DBG(dev, "rx status %d\n", status); + break; + } + + if (skb) + dev_kfree_skb_any(skb); + if (!netif_running(dev->net)) { +clean: + spin_lock(&dev->req_lock); + list_add(&req->list, &dev->rx_reqs); + spin_unlock(&dev->req_lock); + req = NULL; + } + if (req) + rx_submit(dev, req, GFP_ATOMIC); +} + +static int prealloc(struct list_head *list, + struct usb_ep *ep, unsigned n, + bool sg_supported) +{ + unsigned i; + struct usb_request *req; + bool usb_in; + + if (!n) + return -ENOMEM; + + /* queue/recycle up to N requests */ + i = n; + list_for_each_entry(req, list, list) { + if (i-- == 0) + goto extra; + } + + if (ep->desc->bEndpointAddress & USB_DIR_IN) + usb_in = true; + else + usb_in = false; + + while (i--) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (!req) + return list_empty(list) ? -ENOMEM : 0; + /* update completion handler */ + if (usb_in) { + req->complete = tx_complete; + if (sg_supported) { + req->sg = kmalloc(RNDIS_MSG_MAX_NUM * + sizeof(struct scatterlist), + GFP_ATOMIC); + if (!req->sg) + goto extra; + } else { + req->buf = kmalloc(RNDIS_MSG_MAX_NUM * (1516 + 44), + GFP_ATOMIC); + if (!req->buf) + goto extra; + } + } else { + req->complete = rx_complete; + } + list_add(&req->list, list); + } + return 0; + +extra: + /* free extras */ + for (;;) { + struct list_head *next; + + next = req->list.next; + list_del(&req->list); + + if (sg_supported) + kfree(req->sg); + else + kfree(req->buf); + + usb_ep_free_request(ep, req); + + if (next == list) + break; + + req = container_of(next, struct usb_request, list); + } + return 0; +} + + +static int alloc_requests(struct eth_dev *dev, struct gether *link, unsigned n) +{ + int status; + + spin_lock(&dev->req_lock); + status = prealloc(&dev->tx_reqs, link->in_ep, n, + dev->gadget->sg_supported); + if (status < 0){ + ERROR(dev, "fail to alloc in_ep\n"); + goto fail; + } + status = prealloc(&dev->rx_reqs, link->out_ep, n, + dev->gadget->sg_supported); + if (status < 0){ + ERROR(dev, "fail to alloc out_ep\n"); + goto fail; + } + goto done; +fail: + DBG(dev, "can't alloc requests\n"); +done: + spin_unlock(&dev->req_lock); + return status; +} + +static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags) +{ + struct usb_request *req; + unsigned long flags; + + /* fill unused rxq slots with some skb */ + spin_lock_irqsave(&dev->req_lock, flags); + while (!list_empty(&dev->rx_reqs)) { + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del_init(&req->list); + spin_unlock_irqrestore(&dev->req_lock, flags); + + if (rx_submit(dev, req, gfp_flags) < 0) { + defer_kevent(dev, WORK_RX_MEMORY); + return; + } + + spin_lock_irqsave(&dev->req_lock, flags); + } + spin_unlock_irqrestore(&dev->req_lock, flags); +} + +static void eth_work(struct work_struct *work) +{ + struct eth_dev *dev = container_of(work, struct eth_dev, work); + + if (test_and_clear_bit(WORK_RX_MEMORY, &dev->todo)) { + if (netif_running(dev->net)) + rx_fill(dev, GFP_KERNEL); + } + + if (dev->todo) + DBG(dev, "work done, flags = 0x%lx\n", dev->todo); +} +static inline int is_promisc(u16 cdc_filter) +{ + return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS; +} + +#ifndef CONFIG_USB_SPRD_DWC + + +static void tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sk_buff *skb = req->context; + struct eth_dev *dev = ep->driver_data; + + switch (req->status) { + default: + dev->net->stats.tx_errors++; + VDBG(dev, "tx err %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + break; + case 0: + dev->net->stats.tx_bytes += skb->len; + } + dev->net->stats.tx_packets++; + + spin_lock(&dev->req_lock); + list_add(&req->list, &dev->tx_reqs); + spin_unlock(&dev->req_lock); + + dev_kfree_skb_any(skb); + + atomic_dec(&dev->tx_qlen); + if (netif_carrier_ok(dev->net)) + netif_wake_queue(dev->net); +} + +static netdev_tx_t eth_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + int length = skb->len; + int retval; + struct usb_request *req = NULL; + unsigned long flags; + struct usb_ep *in; + u16 cdc_filter; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) { + in = dev->port_usb->in_ep; + cdc_filter = dev->port_usb->cdc_filter; + } else { + in = NULL; + cdc_filter = 0; + } + spin_unlock_irqrestore(&dev->lock, flags); + + if (!in) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + /* apply outgoing CDC or RNDIS filters */ + if (!is_promisc(cdc_filter)) { + u8 *dest = skb->data; + + if (is_multicast_ether_addr(dest)) { + u16 type; + + /* ignores USB_CDC_PACKET_TYPE_MULTICAST and host + * SET_ETHERNET_MULTICAST_FILTERS requests + */ + if (is_broadcast_ether_addr(dest)) + type = USB_CDC_PACKET_TYPE_BROADCAST; + else + type = USB_CDC_PACKET_TYPE_ALL_MULTICAST; + if (!(cdc_filter & type)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + } + /* ignores USB_CDC_PACKET_TYPE_DIRECTED */ + } + + spin_lock_irqsave(&dev->req_lock, flags); + /* + * this freelist can be empty if an interrupt triggered disconnect() + * and reconfigured the gadget (shutting down this queue) after the + * network stack decided to xmit but before we got the spinlock. + */ + if (list_empty(&dev->tx_reqs)) { + spin_unlock_irqrestore(&dev->req_lock, flags); + return NETDEV_TX_BUSY; + } + + req = container_of(dev->tx_reqs.next, struct usb_request, list); + list_del(&req->list); + + /* temporarily stop TX queue when the freelist empties */ + if (list_empty(&dev->tx_reqs)) + netif_stop_queue(net); + spin_unlock_irqrestore(&dev->req_lock, flags); + + /* no buffer copies needed, unless the network stack did it + * or the hardware can't use skb buffers. + * or there's not enough space for extra headers we need + */ + if (dev->wrap) { + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) + skb = dev->wrap(dev->port_usb, skb); + spin_unlock_irqrestore(&dev->lock, flags); + if (!skb) + goto drop; + + length = skb->len; + } + /* + * Align data to 32bit if the dma controller requires it + */ + if (gadget_dma32(dev->gadget)) { + unsigned long align = (unsigned long)skb->data & 3; + if (WARN_ON(skb_headroom(skb) < align)) { + dev_kfree_skb_any(skb); + goto drop; + } else if (align) { + u8 *data = skb->data; + size_t len = skb_headlen(skb); + skb->data -= align; + memmove(skb->data, data, len); + skb_set_tail_pointer(skb, len); + } + } + req->buf = skb->data; + req->context = skb; + req->complete = tx_complete; + + /* NCM requires no zlp if transfer is dwNtbInMaxSize */ + if (dev->port_usb->is_fixed && + length == dev->port_usb->fixed_in_len && + (length % in->maxpacket) == 0) + req->zero = 0; + else + req->zero = 1; + + /* use zlp framing on tx for strict CDC-Ether conformance, + * though any robust network rx path ignores extra padding. + * and some hardware doesn't like to write zlps. + */ + if (req->zero && !dev->zlp && (length % in->maxpacket) == 0) + length++; + + req->length = length; + + /* throttle high/super speed IRQ rate back slightly */ + if (gadget_is_dualspeed(dev->gadget)) + req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH || + dev->gadget->speed == USB_SPEED_SUPER) + ? ((atomic_read(&dev->tx_qlen) % qmult) != 0) + : 0; + + retval = usb_ep_queue(in, req, GFP_ATOMIC); + switch (retval) { + default: + DBG(dev, "tx queue err %d\n", retval); + break; + case 0: + net->trans_start = jiffies; + atomic_inc(&dev->tx_qlen); + } + + if (retval) { + dev_kfree_skb_any(skb); +drop: + dev->net->stats.tx_dropped++; + spin_lock_irqsave(&dev->req_lock, flags); + if (list_empty(&dev->tx_reqs)) + netif_start_queue(net); + list_add(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return NETDEV_TX_OK; +} + +#else +#define RNDIS_MSG_MAX_SIZE (1516 + 44) +#define RNDIS_MSG_MAX_QUEUE 32 +// RNDIS_MSG_MAX_NUM IP diagrams in the worst case +#define RNDIS_MSG_QUEUE_MAX_SIZE RNDIS_MSG_MAX_NUM*RNDIS_MSG_MAX_SIZE + +#define RNDIS_QUEUE_IDLE 1 +#define RNDIS_QUEUE_GATHER 2 +#define RNDIS_QUEUE_FULL 3 +#define RNDIS_QUEUE_SEND 4 + +#define idx_add_one(n) (((n) + 1) % RNDIS_MSG_MAX_QUEUE) + +struct pkt_msg{ + u32 state; + u32 len; + u32 pkt_cnt; + u32 q_idx; + void *req; + void *skbs[RNDIS_MSG_MAX_NUM]; + u32 skb_idx; + u32 bit_map; +}; +struct rndis_msg{ + struct eth_dev *dev; + spinlock_t buffer_lock; + u32 w_idx; + u32 r_idx; + u32 flow_stop; + u32 last_sent; + u32 last_complete; + struct pkt_msg q[RNDIS_MSG_MAX_QUEUE]; +}; + +static struct rndis_msg s_rndis_msg; + +static inline struct rndis_msg* get_rndis_msg(void) +{ + return &s_rndis_msg; +} + +static inline void pkt_msg_clean(struct pkt_msg *q) +{ + int i; + struct sk_buff *skb; + + q->len = 0; + q->pkt_cnt = 0; + q->req = NULL; + q->skb_idx = 0; + q->bit_map = 0; + for (i = 0; i < sizeof(q->skbs)/sizeof(q->skbs[0]); i++) { + skb = (struct sk_buff *)q->skbs[i]; + q->skbs[i] = NULL; + if(skb == NULL) + continue; + dev_kfree_skb_any(skb); + } + q->state = RNDIS_QUEUE_IDLE; +} + +static inline uint32_t pkt_msg_full(struct eth_dev *dev, uint32_t map) +{ + if (dev->port_usb->multi_pkt_xfer + || dev->gadget->sg_supported) + return map == ((1 << RNDIS_MSG_MAX_NUM) - 1); + else + return !!map; +} + +static int pkt_msg_send(struct rndis_msg *msg, struct pkt_msg *q, + struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + struct usb_ep *in = dev->port_usb->in_ep; + struct usb_request *req = NULL; + unsigned long flags; + int retval; + uint32_t i, len, length ; + struct sk_buff *skb; + + req = q->req; + req->context = q; + req->num_sgs = 0; + length = 0; + + if (dev->gadget->sg_supported) { + sg_init_table(req->sg, RNDIS_MSG_MAX_NUM); + for (i = 0; i < q->skb_idx; i++) { + skb = q->skbs[i]; + len = skb->len; + /* use zlp framing on tx for strict CDC-Ether conformance, + * though any robust network rx path ignores extra padding. + * and some hardware doesn't like to write zlps. + */ + if (dev->port_usb->is_fixed && + skb->len == dev->port_usb->fixed_in_len && + !(len & (in->maxpacket - 1))) + len++; + sg_set_buf(req->sg + i, skb->data, len); + length += len; + } + sg_mark_end(req->sg + i - 1); + req->num_sgs = q->skb_idx; + } else { + for (i = 0; i < q->skb_idx; i++) { + skb = q->skbs[i]; + memcpy(req->buf + length, skb->data, skb->len); + length += skb->len; + /* use zlp framing on tx for strict CDC-Ether conformance, + * though any robust network rx path ignores extra padding. + * and some hardware doesn't like to write zlps. + */ + if (dev->port_usb->is_fixed && + skb->len == dev->port_usb->fixed_in_len && + !(skb->len & (in->maxpacket - 1))) + length++; + } + } + q->len = length; + q->bit_map = 0; + + /* NCM requires no zlp if transfer is dwNtbInMaxSize */ + if (dev->port_usb->is_fixed && + length == dev->port_usb->fixed_in_len && + (length % in->maxpacket) == 0) + req->zero = 0; + else + req->zero = 1; + /* Here zero means short packet */ + if (dev->gadget->sg_supported + && !dev->port_usb->multi_pkt_xfer) + req->zero = 1; + + /* use zlp framing on tx for strict CDC-Ether conformance, + * though any robust network rx path ignores extra padding. + * and some hardware doesn't like to write zlps. + */ + if (req->zero && !dev->zlp && (length % in->maxpacket) == 0) + length++; + + req->length = length; + /* throttle high/super speed IRQ rate back slightly*/ + if (gadget_is_dualspeed(dev->gadget)) + req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH || + dev->gadget->speed == USB_SPEED_SUPER) + ? ((atomic_read(&dev->tx_qlen) % qmult) != 0) + : 0; + + retval = usb_ep_queue(in, req, GFP_ATOMIC); + switch (retval) { + default: + printk("tx queue err %d\n", retval); + break; + case 0: + + net->trans_start = jiffies; + atomic_inc(&dev->tx_qlen); + } + if (retval) { + printk("rndis gather, %i IP diagrams are lost! eth_start_xmit\n", + q->pkt_cnt); + net->stats.tx_dropped += q->skb_idx; + pkt_msg_clean(q); + spin_lock_irqsave(&dev->req_lock, flags); + if (list_empty(&dev->tx_reqs)) { + printk("tx_complete, netif_start_queue1 [%d,%d],[%d,%d]\n", + msg->w_idx, msg->r_idx, msg->last_sent, msg->last_complete); + netif_start_queue(net); + msg->flow_stop= 0; + } + list_add(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return retval; +} + +static void rndis_msg_init(struct eth_dev *dev) +{ + struct rndis_msg *msg = get_rndis_msg(); + u32 i; + + msg->dev = dev; + spin_lock_init(&msg->buffer_lock); + msg->w_idx = 0; + msg->r_idx = 0; + for (i = 0; i < RNDIS_MSG_MAX_QUEUE; i++) { + msg->q[i].q_idx = i; + memset(msg->q[i].skbs, 0, sizeof(msg->q[i].skbs)); + pkt_msg_clean(msg->q + i); + } +} + +static void tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + + struct pkt_msg *q = req->context; + struct eth_dev *dev = ep->driver_data; + struct rndis_msg *msg = get_rndis_msg(); + struct pkt_msg *w_q; + + switch (req->status) { + default: + dev->net->stats.tx_errors++; + VDBG(dev, "tx err %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + break; + case 0: + dev->net->stats.tx_bytes += q->len; + } + dev->net->stats.tx_packets += q->skb_idx; + if(req != q->req) + printk("tx_complete error: req != q->req [%p,%p]\n", req, q->req); + + msg->last_complete = q->q_idx; + pkt_msg_clean(q); + if (q->q_idx != msg->w_idx) + msg->r_idx = idx_add_one(q->q_idx); + + atomic_dec(&dev->tx_qlen); + + if (netif_carrier_ok(dev->net)) + netif_wake_queue(dev->net); + spin_lock(&dev->req_lock); + list_add(&req->list, &dev->tx_reqs); + spin_unlock(&dev->req_lock); + spin_lock(&msg->buffer_lock); + w_q = msg->q + msg->w_idx; + if (msg->w_idx == msg->r_idx && + w_q->state == RNDIS_QUEUE_GATHER && + w_q->skb_idx == w_q->pkt_cnt) { + msg->last_sent = w_q->q_idx; + w_q->state = RNDIS_QUEUE_SEND; + spin_unlock(&msg->buffer_lock); + pkt_msg_send(msg, w_q, dev->net); + return; + } + spin_unlock(&msg->buffer_lock); +} + + +static netdev_tx_t eth_alloc_req(struct net_device *net, + struct usb_request **req, + struct pkt_msg **w_q, + struct sk_buff *skb) +{ + unsigned long flags; + struct eth_dev *dev = netdev_priv(net); + struct rndis_msg *msg = get_rndis_msg(); + struct pkt_msg *q; + + spin_lock_irqsave(&msg->buffer_lock, flags); + q = msg->q + msg->w_idx; + if (q->state == RNDIS_QUEUE_GATHER) { + int next; + q->skbs[q->pkt_cnt] = skb; + q->pkt_cnt++; + *w_q = q; + *req = q->req; + if (q->pkt_cnt >= RNDIS_MSG_MAX_NUM) { + q->state = RNDIS_QUEUE_FULL; + next = idx_add_one(msg->w_idx); + if (msg->r_idx == next) { + netif_stop_queue(dev->net); + msg->flow_stop = 1; + } else + msg->w_idx = next; + } + spin_unlock_irqrestore(&msg->buffer_lock, flags); + return NETDEV_TX_OK; + } + spin_unlock_irqrestore(&msg->buffer_lock, flags); + *w_q = NULL; + spin_lock_irqsave(&dev->req_lock, flags); + /* + * this freelist can be empty if an interrupt triggered disconnect() + * and reconfigured the gadget (shutting down this queue) after the + * network stack decided to xmit but before we got the spinlock. + */ + if (list_empty(&dev->tx_reqs)) { + spin_unlock_irqrestore(&dev->req_lock, flags); + *req = NULL; + return NETDEV_TX_BUSY; + } + + *req = container_of(dev->tx_reqs.next, struct usb_request, list); + __list_del_entry(&(*req)->list); + + /* temporarily stop TX queue when the freelist empties */ + if (list_empty(&dev->tx_reqs)) { + netif_stop_queue(net); + msg->flow_stop= 1; + } + spin_unlock_irqrestore(&dev->req_lock, flags); + return NETDEV_TX_OK; +} + +int save_to_queue(struct eth_dev *dev,struct rndis_msg *msg, + struct sk_buff *skb, struct usb_request *req, + struct pkt_msg **q) +{ + unsigned long flags; + u32 next = 0; + struct pkt_msg *w_q; + + spin_lock_irqsave(&msg->buffer_lock, flags); + w_q = msg->q + msg->w_idx; + next = idx_add_one(msg->w_idx); + if (msg->r_idx == next) { + printk("save_to_queue, netif_stop_queue [%d,%d]\n", + msg->w_idx, msg->r_idx); + spin_unlock_irqrestore(&msg->buffer_lock, flags); + return 1; + } + + if (w_q->state >= RNDIS_QUEUE_FULL) { + msg->w_idx = next; + w_q = msg->q + msg->w_idx; + } + + if (w_q->state == RNDIS_QUEUE_IDLE) + w_q->req = req; + else if (w_q->req != req) { + unsigned long flag; + spin_lock_irqsave(&dev->req_lock, flag); + if (list_empty(&dev->tx_reqs)) + netif_start_queue(dev->net); + list_add(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flag); + } + + if (w_q->pkt_cnt < RNDIS_MSG_MAX_NUM) { + w_q->skbs[w_q->skb_idx] = skb; + w_q->bit_map |= (1<skb_idx); + w_q->skb_idx++; + w_q->pkt_cnt++; + *q = w_q; + } + if (w_q->pkt_cnt >= RNDIS_MSG_MAX_NUM) { + w_q->state = RNDIS_QUEUE_FULL; + next = idx_add_one(msg->w_idx); + if (next == msg->r_idx) { + netif_stop_queue(dev->net); + msg->flow_stop = 1; + }else + msg->w_idx = next; + + } else + w_q->state = RNDIS_QUEUE_GATHER; + spin_unlock_irqrestore(&msg->buffer_lock, flags); + return 0; +} +static void update_sbks_in_queue(struct pkt_msg *q, + struct sk_buff *skb_new, + struct sk_buff *skb_old) +{ + int i; + unsigned long flags; + struct rndis_msg *msg = get_rndis_msg(); + + spin_lock_irqsave(&msg->buffer_lock, flags); + for (i = 0; i < q->pkt_cnt; i++) { + if (q->skbs[i] == skb_old) { + q->skbs[i] = skb_new; + q->bit_map |= (1<skb_idx++; + break; + } + } + spin_unlock_irqrestore(&msg->buffer_lock, flags); +} +static netdev_tx_t eth_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + int length = skb->len; + int retval = 0; + struct usb_request *req = NULL; + unsigned long flags; + struct usb_ep *in; + u16 cdc_filter; + struct rndis_msg *msg = get_rndis_msg(); + struct pkt_msg *w_q = NULL; + struct sk_buff *skb_old; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) { + in = dev->port_usb->in_ep; + cdc_filter = dev->port_usb->cdc_filter; + } else { + in = NULL; + cdc_filter = 0; + } + spin_unlock_irqrestore(&dev->lock, flags); + + if (!in) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + /* apply outgoing CDC or RNDIS filters */ + if (!is_promisc(cdc_filter)) { + u8 *dest = skb->data; + + if (is_multicast_ether_addr(dest)) { + u16 type; + + /* ignores USB_CDC_PACKET_TYPE_MULTICAST and host + * SET_ETHERNET_MULTICAST_FILTERS requests + */ + if (is_broadcast_ether_addr(dest)) + type = USB_CDC_PACKET_TYPE_BROADCAST; + else + type = USB_CDC_PACKET_TYPE_ALL_MULTICAST; + if (!(cdc_filter & type)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + } + /* ignores USB_CDC_PACKET_TYPE_DIRECTED */ + } + skb_old = skb; + eth_alloc_req(net, &req, &w_q, skb); + if (req == NULL) + return NETDEV_TX_BUSY; + /* no buffer copies needed, unless the network stack did it + * or the hardware can't use skb buffers. + * or there's not enough space for extra headers we need + */ + if (dev->wrap) { + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->port_usb) + skb = dev->wrap(dev->port_usb, skb); + spin_unlock_irqrestore(&dev->lock, flags); + if (!skb) + goto drop; + + length = skb->len; + } + /* + * Align data to 32bit if the dma controller requires it + */ + if (gadget_dma32(dev->gadget)) { + unsigned long align = (unsigned long)skb->data & 3; + if (WARN_ON(skb_headroom(skb) < align)) { + dev_kfree_skb_any(skb); + goto drop; + } else if (align) { + u8 *data = skb->data; + size_t len = skb_headlen(skb); + skb->data -= align; + memmove(skb->data, data, len); + skb_set_tail_pointer(skb, len); + } + } + + retval = 0; + if (w_q == NULL) + retval = save_to_queue(dev, msg, skb, req, &w_q); + else if (skb != skb_old) + update_sbks_in_queue(w_q, skb, skb_old); + + if (retval == 0) { + spin_lock_irqsave(&msg->buffer_lock, flags); + if (w_q && + pkt_msg_full(dev, w_q->bit_map) && + (w_q->state != RNDIS_QUEUE_SEND)) { + msg->last_sent = w_q->q_idx; + w_q->state = RNDIS_QUEUE_SEND; + spin_unlock_irqrestore(&msg->buffer_lock, flags); + pkt_msg_send(msg, w_q, net); + return NETDEV_TX_OK; + } + + w_q = msg->q + msg->w_idx; + if (pkt_msg_full(dev, w_q->bit_map) + && w_q->state != RNDIS_QUEUE_SEND) { + msg->last_sent = w_q->q_idx; + w_q->state = RNDIS_QUEUE_SEND; + spin_unlock_irqrestore(&msg->buffer_lock, flags); + pkt_msg_send(msg, w_q, net); + return NETDEV_TX_OK; + } + if (msg->w_idx == msg->r_idx && + w_q->state == RNDIS_QUEUE_GATHER && + w_q->skb_idx == w_q->pkt_cnt) { + msg->last_sent = w_q->q_idx; + w_q->state = RNDIS_QUEUE_SEND; + spin_unlock_irqrestore(&msg->buffer_lock, flags); + pkt_msg_send(msg, w_q, net); + return NETDEV_TX_OK; + } + spin_unlock_irqrestore(&msg->buffer_lock, flags); + return NETDEV_TX_OK; + } + if (retval) { + dev_kfree_skb_any(skb); +drop: + printk("drop req\n"); + dev->net->stats.tx_dropped++; + spin_lock_irqsave(&dev->req_lock, flags); + if (list_empty(&dev->tx_reqs)) + netif_start_queue(net); + list_add(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return NETDEV_TX_OK; +} +#endif +/*-------------------------------------------------------------------------*/ + +static void eth_start(struct eth_dev *dev, gfp_t gfp_flags) +{ + DBG(dev, "%s\n", __func__); + + /* fill the rx queue */ + rx_fill(dev, gfp_flags); + + /* and open the tx floodgates */ + atomic_set(&dev->tx_qlen, 0); + netif_wake_queue(dev->net); +#ifdef CONFIG_USB_SPRD_DWC + rndis_msg_init(dev); +#endif +} + +static int eth_open(struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + struct gether *link; + + DBG(dev, "%s\n", __func__); + if (netif_carrier_ok(dev->net)) + eth_start(dev, GFP_KERNEL); + + spin_lock_irq(&dev->lock); + link = dev->port_usb; + if (link && link->open) + link->open(link); + spin_unlock_irq(&dev->lock); + + return 0; +} + +static int eth_stop(struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + unsigned long flags; + + VDBG(dev, "%s\n", __func__); + netif_stop_queue(net); + + DBG(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n", + dev->net->stats.rx_packets, dev->net->stats.tx_packets, + dev->net->stats.rx_errors, dev->net->stats.tx_errors + ); + + /* ensure there are no more active requests */ + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) { + struct gether *link = dev->port_usb; + const struct usb_endpoint_descriptor *in; + const struct usb_endpoint_descriptor *out; + + if (link->close) + link->close(link); + + /* NOTE: we have no abort-queue primitive we could use + * to cancel all pending I/O. Instead, we disable then + * reenable the endpoints ... this idiom may leave toggle + * wrong, but that's a self-correcting error. + * + * REVISIT: we *COULD* just let the transfers complete at + * their own pace; the network stack can handle old packets. + * For the moment we leave this here, since it works. + */ + in = link->in_ep->desc; + out = link->out_ep->desc; + usb_ep_disable(link->in_ep); + usb_ep_disable(link->out_ep); + if (netif_carrier_ok(net)) { + DBG(dev, "host still using in/out endpoints\n"); + link->in_ep->desc = in; + link->out_ep->desc = out; + usb_ep_enable(link->in_ep); + usb_ep_enable(link->out_ep); + } + } + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */ +static char *dev_addr; +module_param(dev_addr, charp, S_IRUGO); +MODULE_PARM_DESC(dev_addr, "Device Ethernet Address"); + +/* this address is invisible to ifconfig */ +static char *host_addr; +module_param(host_addr, charp, S_IRUGO); +MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); + +static int get_ether_addr(const char *str, u8 *dev_addr) +{ + if (str) { + unsigned i; + + for (i = 0; i < 6; i++) { + unsigned char num; + + if ((*str == '.') || (*str == ':')) + str++; + num = hex_to_bin(*str++) << 4; + num |= hex_to_bin(*str++); + dev_addr [i] = num; + } + if (is_valid_ether_addr(dev_addr)) + return 0; + } + eth_random_addr(dev_addr); + return 1; +} + +static const struct net_device_ops eth_netdev_ops = { + .ndo_open = eth_open, + .ndo_stop = eth_stop, + .ndo_start_xmit = eth_start_xmit, + .ndo_change_mtu = ueth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static struct device_type gadget_type = { + .name = "gadget", +}; + +/** + * gether_setup_name - initialize one ethernet-over-usb link + * @g: gadget to associated with these links + * @ethaddr: NULL, or a buffer in which the ethernet address of the + * host side of the link is recorded + * @netname: name for network device (for example, "usb") + * Context: may sleep + * + * This sets up the single network link that may be exported by a + * gadget driver using this framework. The link layer addresses are + * set up using module parameters. + * + * Returns negative errno, or zero on success + */ +struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], + const char *netname) +{ + struct eth_dev *dev; + struct net_device *net; + int status; + + net = alloc_etherdev(sizeof *dev); + if (!net) + return ERR_PTR(-ENOMEM); + + dev = netdev_priv(net); + spin_lock_init(&dev->lock); + spin_lock_init(&dev->req_lock); + INIT_WORK(&dev->work, eth_work); + INIT_LIST_HEAD(&dev->tx_reqs); + INIT_LIST_HEAD(&dev->rx_reqs); + + skb_queue_head_init(&dev->rx_frames); + + /* network device setup */ + dev->net = net; + snprintf(net->name, sizeof(net->name), "%s%%d", netname); + + if (get_ether_addr(dev_addr, net->dev_addr)) + dev_warn(&g->dev, + "using random %s ethernet address\n", "self"); + if (get_ether_addr(host_addr, dev->host_mac)) + dev_warn(&g->dev, + "using random %s ethernet address\n", "host"); + + if (ethaddr) + memcpy(ethaddr, dev->host_mac, ETH_ALEN); + + net->netdev_ops = ð_netdev_ops; + + SET_ETHTOOL_OPS(net, &ops); + + dev->gadget = g; + SET_NETDEV_DEV(net, &g->dev); + SET_NETDEV_DEVTYPE(net, &gadget_type); + + status = register_netdev(net); + if (status < 0) { + dev_dbg(&g->dev, "register_netdev failed, %d\n", status); + free_netdev(net); + dev = ERR_PTR(status); + } else { + INFO(dev, "MAC %pM\n", net->dev_addr); + INFO(dev, "HOST MAC %pM\n", dev->host_mac); + + /* two kinds of host-initiated state changes: + * - iff DATA transfer is active, carrier is "on" + * - tx queueing enabled if open *and* carrier is "on" + */ + netif_carrier_off(net); + } + + return dev; +} + +/** + * gether_cleanup - remove Ethernet-over-USB device + * Context: may sleep + * + * This is called to free all resources allocated by @gether_setup(). + */ +void gether_cleanup(struct eth_dev *dev) +{ + if (!dev) + return; + + unregister_netdev(dev->net); + flush_work(&dev->work); + free_netdev(dev->net); +} + +/** + * gether_connect - notify network layer that USB link is active + * @link: the USB link, set up with endpoints, descriptors matching + * current device speed, and any framing wrapper(s) set up. + * Context: irqs blocked + * + * This is called to activate endpoints and let the network layer know + * the connection is active ("carrier detect"). It may cause the I/O + * queues to open and start letting network packets flow, but will in + * any case activate the endpoints so that they respond properly to the + * USB host. + * + * Verify net_device pointer returned using IS_ERR(). If it doesn't + * indicate some error code (negative errno), ep->driver_data values + * have been overwritten. + */ +struct net_device *gether_connect(struct gether *link) +{ + struct eth_dev *dev = link->ioport; + int result = 0; + + if (!dev) + return ERR_PTR(-EINVAL); + + link->in_ep->driver_data = dev; + result = usb_ep_enable(link->in_ep); + if (result != 0) { + DBG(dev, "enable %s --> %d\n", + link->in_ep->name, result); + goto fail0; + } + + link->out_ep->driver_data = dev; + result = usb_ep_enable(link->out_ep); + if (result != 0) { + DBG(dev, "enable %s --> %d\n", + link->out_ep->name, result); + goto fail1; + } + + if (result == 0) + result = alloc_requests(dev, link, qlen(dev->gadget)); + + if (result == 0) { + dev->zlp = link->is_zlp_ok; + DBG(dev, "qlen %d\n", qlen(dev->gadget)); + + dev->header_len = link->header_len; + dev->unwrap = link->unwrap; + dev->wrap = link->wrap; + + spin_lock(&dev->lock); + dev->port_usb = link; + if (netif_running(dev->net)) { + if (link->open) + link->open(link); + } else { + if (link->close) + link->close(link); + } + spin_unlock(&dev->lock); + + netif_carrier_on(dev->net); + if (netif_running(dev->net)) + eth_start(dev, GFP_ATOMIC); + + /* on error, disable any endpoints */ + } else { + (void) usb_ep_disable(link->out_ep); +fail1: + (void) usb_ep_disable(link->in_ep); + } +fail0: + /* caller is responsible for cleanup on error */ + if (result < 0) + return ERR_PTR(result); + return dev->net; +} + +/** + * gether_disconnect - notify network layer that USB link is inactive + * @link: the USB link, on which gether_connect() was called + * Context: irqs blocked + * + * This is called to deactivate endpoints and let the network layer know + * the connection went inactive ("no carrier"). + * + * On return, the state is as if gether_connect() had never been called. + * The endpoints are inactive, and accordingly without active USB I/O. + * Pointers to endpoint descriptors and endpoint private data are nulled. + */ +void gether_disconnect(struct gether *link) +{ + struct eth_dev *dev = link->ioport; + struct usb_request *req; + + WARN_ON(!dev); + if (!dev) + return; + + DBG(dev, "%s\n", __func__); + + netif_stop_queue(dev->net); + netif_carrier_off(dev->net); + + /* disable endpoints, forcing (synchronous) completion + * of all pending i/o. then free the request objects + * and forget about the endpoints. + */ + usb_ep_disable(link->in_ep); + spin_lock(&dev->req_lock); + while (!list_empty(&dev->tx_reqs)) { + req = container_of(dev->tx_reqs.next, + struct usb_request, list); + list_del(&req->list); + + spin_unlock(&dev->req_lock); + if (dev->gadget->sg_supported) + kfree(req->sg); + else + kfree(req->buf); + usb_ep_free_request(link->in_ep, req); + spin_lock(&dev->req_lock); + } + spin_unlock(&dev->req_lock); + link->in_ep->driver_data = NULL; + link->in_ep->desc = NULL; + + usb_ep_disable(link->out_ep); + spin_lock(&dev->req_lock); + while (!list_empty(&dev->rx_reqs)) { + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del(&req->list); + + spin_unlock(&dev->req_lock); + usb_ep_free_request(link->out_ep, req); + spin_lock(&dev->req_lock); + } + spin_unlock(&dev->req_lock); + link->out_ep->driver_data = NULL; + link->out_ep->desc = NULL; + + /* finish forgetting about this USB link episode */ + dev->header_len = 0; + dev->unwrap = NULL; + dev->wrap = NULL; + + spin_lock(&dev->lock); + dev->port_usb = NULL; + spin_unlock(&dev->lock); +} diff --git a/drivers/usb/gadget/u_ether.h b/drivers/usb/gadget/u_ether.h new file mode 100644 index 00000000..739bda1a --- /dev/null +++ b/drivers/usb/gadget/u_ether.h @@ -0,0 +1,161 @@ +/* + * u_ether.h -- interface to USB gadget "ethernet link" utilities + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __U_ETHER_H +#define __U_ETHER_H + +#include +#include +#include +#include + +#include "gadget_chips.h" + +struct eth_dev; + +/* + * This represents the USB side of an "ethernet" link, managed by a USB + * function which provides control and (maybe) framing. Two functions + * in different configurations could share the same ethernet link/netdev, + * using different host interaction models. + * + * There is a current limitation that only one instance of this link may + * be present in any given configuration. When that's a problem, network + * layer facilities can be used to package multiple logical links on this + * single "physical" one. + */ +struct gether { + struct usb_function func; + + /* updated by gether_{connect,disconnect} */ + struct eth_dev *ioport; + + /* endpoints handle full and/or high speeds */ + struct usb_ep *in_ep; + struct usb_ep *out_ep; + + bool is_zlp_ok; + + u16 cdc_filter; + + /* hooks for added framing, as needed for RNDIS and EEM. */ + u32 header_len; + /* NCM requires fixed size bundles */ + bool is_fixed; + u32 fixed_out_len; + u32 fixed_in_len; + bool multi_pkt_xfer; + + struct sk_buff *(*wrap)(struct gether *port, + struct sk_buff *skb); + int (*unwrap)(struct gether *port, + struct sk_buff *skb, + struct sk_buff_head *list); + + /* called on network open/close */ + void (*open)(struct gether *); + void (*close)(struct gether *); +}; + +#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ + |USB_CDC_PACKET_TYPE_ALL_MULTICAST \ + |USB_CDC_PACKET_TYPE_PROMISCUOUS \ + |USB_CDC_PACKET_TYPE_DIRECTED) + +/* variant of gether_setup that allows customizing network device name */ +struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], + const char *netname); + +/* netdev setup/teardown as directed by the gadget driver */ +/* gether_setup - initialize one ethernet-over-usb link + * @g: gadget to associated with these links + * @ethaddr: NULL, or a buffer in which the ethernet address of the + * host side of the link is recorded + * Context: may sleep + * + * This sets up the single network link that may be exported by a + * gadget driver using this framework. The link layer addresses are + * set up using module parameters. + * + * Returns negative errno, or zero on success + */ +static inline struct eth_dev *gether_setup(struct usb_gadget *g, + u8 ethaddr[ETH_ALEN]) +{ + return gether_setup_name(g, ethaddr, "usb"); +} + +void gether_cleanup(struct eth_dev *dev); + +/* connect/disconnect is handled by individual functions */ +struct net_device *gether_connect(struct gether *); +void gether_disconnect(struct gether *); + +/* Some controllers can't support CDC Ethernet (ECM) ... */ +static inline bool can_support_ecm(struct usb_gadget *gadget) +{ + if (!gadget_supports_altsettings(gadget)) + return false; + + /* Everything else is *presumably* fine ... but this is a bit + * chancy, so be **CERTAIN** there are no hardware issues with + * your controller. Add it above if it can't handle CDC. + */ + return true; +} + +/* each configuration may bind one instance of an ethernet link */ +int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + struct eth_dev *dev); +int ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + struct eth_dev *dev); +int ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + struct eth_dev *dev); +int eem_bind_config(struct usb_configuration *c, struct eth_dev *dev); + +#ifdef USB_ETH_RNDIS + +int rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer, struct eth_dev *dev); + +#else + +static inline int +rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer, struct eth_dev *dev) +{ + return 0; +} + +#endif + +/** + * rndis_bind_config - add RNDIS network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +static inline int rndis_bind_config(struct usb_configuration *c, + u8 ethaddr[ETH_ALEN], struct eth_dev *dev) +{ + return rndis_bind_config_vendor(c, ethaddr, 0, NULL, dev); +} + + +#endif /* __U_ETHER_H */ diff --git a/drivers/usb/gadget/u_phonet.h b/drivers/usb/gadget/u_phonet.h new file mode 100644 index 00000000..09a75259 --- /dev/null +++ b/drivers/usb/gadget/u_phonet.h @@ -0,0 +1,21 @@ +/* + * u_phonet.h - interface to Phonet + * + * Copyright (C) 2007-2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#ifndef __U_PHONET_H +#define __U_PHONET_H + +#include +#include + +int gphonet_setup(struct usb_gadget *gadget); +int phonet_bind_config(struct usb_configuration *c); +void gphonet_cleanup(void); + +#endif /* __U_PHONET_H */ diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c new file mode 100644 index 00000000..6d7ac92a --- /dev/null +++ b/drivers/usb/gadget/u_serial.c @@ -0,0 +1,1360 @@ +/* + * u_serial.c - utilities for USB gadget "serial port"/TTY support + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This code also borrows from usbserial.c, which is + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2000 Peter Berger (pberger@brimson.com) + * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com) + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "u_serial.h" + + +/* + * This component encapsulates the TTY layer glue needed to provide basic + * "serial port" functionality through the USB gadget stack. Each such + * port is exposed through a /dev/ttyGS* node. + * + * After this module has been loaded, the individual TTY port can be requested + * (gserial_alloc_line()) and it will stay available until they are removed + * (gserial_free_line()). Each one may be connected to a USB function + * (gserial_connect), or disconnected (with gserial_disconnect) when the USB + * host issues a config change event. Data can only flow when the port is + * connected to the host. + * + * A given TTY port can be made available in multiple configurations. + * For example, each one might expose a ttyGS0 node which provides a + * login application. In one case that might use CDC ACM interface 0, + * while another configuration might use interface 3 for that. The + * work to handle that (including descriptor management) is not part + * of this component. + * + * Configurations may expose more than one TTY port. For example, if + * ttyGS0 provides login service, then ttyGS1 might provide dialer access + * for a telephone or fax link. And ttyGS2 might be something that just + * needs a simple byte stream interface for some messaging protocol that + * is managed in userspace ... OBEX, PTP, and MTP have been mentioned. + */ + +#define PREFIX "ttyGS" + +/* + * gserial is the lifecycle interface, used by USB functions + * gs_port is the I/O nexus, used by the tty driver + * tty_struct links to the tty/filesystem framework + * + * gserial <---> gs_port ... links will be null when the USB link is + * inactive; managed by gserial_{connect,disconnect}(). each gserial + * instance can wrap its own USB control protocol. + * gserial->ioport == usb_ep->driver_data ... gs_port + * gs_port->port_usb ... gserial + * + * gs_port <---> tty_struct ... links will be null when the TTY file + * isn't opened; managed by gs_open()/gs_close() + * gserial->port_tty ... tty_struct + * tty_struct->driver_data ... gserial + */ + +/* RX and TX queues can buffer QUEUE_SIZE packets before they hit the + * next layer of buffering. For TX that's a circular buffer; for RX + * consider it a NOP. A third layer is provided by the TTY code. + */ +#define QUEUE_SIZE 32 +#define WRITE_BUF_SIZE (8*1024) /* TX only */ + +/* circular buffer */ +struct gs_buf { + unsigned buf_size; + char *buf_buf; + char *buf_get; + char *buf_put; +}; +/* + * The port structure holds info for each port, one for each minor number + * (and thus for each /dev/ node). + */ +struct gs_port { + struct tty_port port; + spinlock_t port_lock; /* guard port_* access */ + + struct gserial *port_usb; + + bool openclose; /* open/close in progress */ + u8 port_num; + + struct list_head read_pool; + int read_started; + int read_allocated; + struct list_head read_queue; + unsigned n_read; + struct tasklet_struct push; + + struct list_head write_pool; + int write_started; + int write_allocated; + struct gs_buf port_write_buf; + wait_queue_head_t drain_wait; /* wait while writes drain */ + + /* REVISIT this state ... */ + struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ +}; + +static struct portmaster { + struct mutex lock; /* protect open/close */ + struct gs_port *port; +} ports[MAX_U_SERIAL_PORTS]; + +#define GS_CLOSE_TIMEOUT 15 /* seconds */ + + + +#ifdef VERBOSE_DEBUG +#ifndef pr_vdebug +#define pr_vdebug(fmt, arg...) \ + pr_debug(fmt, ##arg) +#endif /* pr_vdebug */ +#else +#ifndef pr_vdebug +#define pr_vdebug(fmt, arg...) \ + ({ if (0) pr_debug(fmt, ##arg); }) +#endif /* pr_vdebug */ +#endif + +/*-------------------------------------------------------------------------*/ + +/* Circular Buffer */ + +/* + * gs_buf_alloc + * + * Allocate a circular buffer and all associated memory. + */ +static int gs_buf_alloc(struct gs_buf *gb, unsigned size) +{ + gb->buf_buf = kmalloc(size, GFP_KERNEL); + if (gb->buf_buf == NULL) + return -ENOMEM; + + gb->buf_size = size; + gb->buf_put = gb->buf_buf; + gb->buf_get = gb->buf_buf; + + return 0; +} + +/* + * gs_buf_free + * + * Free the buffer and all associated memory. + */ +static void gs_buf_free(struct gs_buf *gb) +{ + kfree(gb->buf_buf); + gb->buf_buf = NULL; +} + +/* + * gs_buf_clear + * + * Clear out all data in the circular buffer. + */ +static void gs_buf_clear(struct gs_buf *gb) +{ + gb->buf_get = gb->buf_put; + /* equivalent to a get of all data available */ +} + +/* + * gs_buf_data_avail + * + * Return the number of bytes of data written into the circular + * buffer. + */ +static unsigned gs_buf_data_avail(struct gs_buf *gb) +{ + return (gb->buf_size + gb->buf_put - gb->buf_get) % gb->buf_size; +} + +/* + * gs_buf_space_avail + * + * Return the number of bytes of space available in the circular + * buffer. + */ +static unsigned gs_buf_space_avail(struct gs_buf *gb) +{ + return (gb->buf_size + gb->buf_get - gb->buf_put - 1) % gb->buf_size; +} + +/* + * gs_buf_put + * + * Copy data data from a user buffer and put it into the circular buffer. + * Restrict to the amount of space available. + * + * Return the number of bytes copied. + */ +static unsigned +gs_buf_put(struct gs_buf *gb, const char *buf, unsigned count) +{ + unsigned len; + + len = gs_buf_space_avail(gb); + if (count > len) + count = len; + + if (count == 0) + return 0; + + len = gb->buf_buf + gb->buf_size - gb->buf_put; + if (count > len) { + memcpy(gb->buf_put, buf, len); + memcpy(gb->buf_buf, buf+len, count - len); + gb->buf_put = gb->buf_buf + count - len; + } else { + memcpy(gb->buf_put, buf, count); + if (count < len) + gb->buf_put += count; + else /* count == len */ + gb->buf_put = gb->buf_buf; + } + + return count; +} + +/* + * gs_buf_get + * + * Get data from the circular buffer and copy to the given buffer. + * Restrict to the amount of data available. + * + * Return the number of bytes copied. + */ +static unsigned +gs_buf_get(struct gs_buf *gb, char *buf, unsigned count) +{ + unsigned len; + + len = gs_buf_data_avail(gb); + if (count > len) + count = len; + + if (count == 0) + return 0; + + len = gb->buf_buf + gb->buf_size - gb->buf_get; + if (count > len) { + memcpy(buf, gb->buf_get, len); + memcpy(buf+len, gb->buf_buf, count - len); + gb->buf_get = gb->buf_buf + count - len; + } else { + memcpy(buf, gb->buf_get, count); + if (count < len) + gb->buf_get += count; + else /* count == len */ + gb->buf_get = gb->buf_buf; + } + + return count; +} + +/*-------------------------------------------------------------------------*/ + +/* I/O glue between TTY (upper) and USB function (lower) driver layers */ + +/* + * gs_alloc_req + * + * Allocate a usb_request and its buffer. Returns a pointer to the + * usb_request or NULL if there is an error. + */ +struct usb_request * +gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, kmalloc_flags); + + if (req != NULL) { + req->length = len; + req->buf = kmalloc(len, kmalloc_flags); + if (req->buf == NULL) { + usb_ep_free_request(ep, req); + return NULL; + } + } + + return req; +} +EXPORT_SYMBOL_GPL(gs_alloc_req); + +/* + * gs_free_req + * + * Free a usb_request and its buffer. + */ +void gs_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} +EXPORT_SYMBOL_GPL(gs_free_req); + +/* + * gs_send_packet + * + * If there is data to send, a packet is built in the given + * buffer and the size is returned. If there is no data to + * send, 0 is returned. + * + * Called with port_lock held. + */ +static unsigned +gs_send_packet(struct gs_port *port, char *packet, unsigned size) +{ + unsigned len; + + len = gs_buf_data_avail(&port->port_write_buf); + if (len < size) + size = len; + if (size != 0) + size = gs_buf_get(&port->port_write_buf, packet, size); + return size; +} + +/* + * gs_start_tx + * + * This function finds available write requests, calls + * gs_send_packet to fill these packets with data, and + * continues until either there are no more write requests + * available or no more data to send. This function is + * run whenever data arrives or write requests are available. + * + * Context: caller owns port_lock; port_usb is non-null. + */ +static int gs_start_tx(struct gs_port *port) +/* +__releases(&port->port_lock) +__acquires(&port->port_lock) +*/ +{ + struct list_head *pool = &port->write_pool; + struct usb_ep *in = port->port_usb->in; + int status = 0; + bool do_tty_wake = false; + + while (!list_empty(pool)) { + struct usb_request *req; + int len; + + if (port->write_started >= QUEUE_SIZE) + break; + + req = list_entry(pool->next, struct usb_request, list); + len = gs_send_packet(port, req->buf, WRITE_BUF_SIZE); + if (len == 0) { + wake_up_interruptible(&port->drain_wait); + break; + } + do_tty_wake = true; + + req->length = len; + list_del(&req->list); + if(len >= in->maxpacket) + req->zero = ((len%in->maxpacket) == 0); + + pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", + port->port_num, len, *((u8 *)req->buf), + *((u8 *)req->buf+1), *((u8 *)req->buf+2)); + + /* Drop lock while we call out of driver; completions + * could be issued while we do so. Disconnection may + * happen too; maybe immediately before we queue this! + * + * NOTE that we may keep sending data for a while after + * the TTY closed (dev->ioport->port_tty is NULL). + */ + spin_unlock(&port->port_lock); + status = usb_ep_queue(in, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + + if (status) { + pr_debug("%s: %s %s err %d\n", + __func__, "queue", in->name, status); + list_add(&req->list, pool); + break; + } + + port->write_started++; + + /* abort immediately after disconnect */ + if (!port->port_usb) + break; + } + + if (do_tty_wake && port->port.tty) + tty_wakeup(port->port.tty); + return status; +} + +/* + * Context: caller owns port_lock, and port_usb is set + */ +static unsigned gs_start_rx(struct gs_port *port) +/* +__releases(&port->port_lock) +__acquires(&port->port_lock) +*/ +{ + struct list_head *pool = &port->read_pool; + struct usb_ep *out = port->port_usb->out; + + while (!list_empty(pool)) { + struct usb_request *req; + int status; + struct tty_struct *tty; + + /* no more rx if closed */ + tty = port->port.tty; + if (!tty) + break; + + if (port->read_started >= QUEUE_SIZE) + break; + + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + req->length = out->maxpacket; + + /* drop lock while we call out; the controller driver + * may need to call us back (e.g. for disconnect) + */ + spin_unlock(&port->port_lock); + status = usb_ep_queue(out, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + + if (status) { + pr_debug("%s: %s %s err %d\n", + __func__, "queue", out->name, status); + list_add(&req->list, pool); + break; + } + port->read_started++; + + /* abort immediately after disconnect */ + if (!port->port_usb) + break; + } + return port->read_started; +} + +/* + * RX tasklet takes data out of the RX queue and hands it up to the TTY + * layer until it refuses to take any more data (or is throttled back). + * Then it issues reads for any further data. + * + * If the RX queue becomes full enough that no usb_request is queued, + * the OUT endpoint may begin NAKing as soon as its FIFO fills up. + * So QUEUE_SIZE packets plus however many the FIFO holds (usually two) + * can be buffered before the TTY layer's buffers (currently 64 KB). + */ +static void gs_rx_push(unsigned long _port) +{ + struct gs_port *port = (void *)_port; + struct tty_struct *tty; + struct list_head *queue = &port->read_queue; + bool disconnect = false; + bool do_push = false; + + /* hand any queued data to the tty */ + spin_lock_irq(&port->port_lock); + tty = port->port.tty; + while (!list_empty(queue)) { + struct usb_request *req; + + req = list_first_entry(queue, struct usb_request, list); + + /* leave data queued if tty was rx throttled */ + if (tty && test_bit(TTY_THROTTLED, &tty->flags)) + break; + + switch (req->status) { + case -ESHUTDOWN: + disconnect = true; + pr_vdebug(PREFIX "%d: shutdown\n", port->port_num); + break; + + default: + /* presumably a transient fault */ + pr_warning(PREFIX "%d: unexpected RX status %d\n", + port->port_num, req->status); + /* FALLTHROUGH */ + case 0: + /* normal completion */ + break; + } + + /* push data to (open) tty */ + if (req->actual) { + char *packet = req->buf; + unsigned size = req->actual; + unsigned n; + int count; + + /* we may have pushed part of this packet already... */ + n = port->n_read; + if (n) { + packet += n; + size -= n; + } + + count = tty_insert_flip_string(&port->port, packet, + size); + if (count) + do_push = true; + if (count != size) { + /* stop pushing; TTY layer can't handle more */ + port->n_read += count; + pr_vdebug(PREFIX "%d: rx block %d/%d\n", + port->port_num, + count, req->actual); + break; + } + port->n_read = 0; + } + + list_move(&req->list, &port->read_pool); + port->read_started--; + } + + /* Push from tty to ldisc; without low_latency set this is handled by + * a workqueue, so we won't get callbacks and can hold port_lock + */ + if (do_push) + tty_flip_buffer_push(&port->port); + + + /* We want our data queue to become empty ASAP, keeping data + * in the tty and ldisc (not here). If we couldn't push any + * this time around, there may be trouble unless there's an + * implicit tty_unthrottle() call on its way... + * + * REVISIT we should probably add a timer to keep the tasklet + * from starving ... but it's not clear that case ever happens. + */ + if (!list_empty(queue) && tty) { + if (!test_bit(TTY_THROTTLED, &tty->flags)) { + if (do_push) + tasklet_schedule(&port->push); + else + pr_warning(PREFIX "%d: RX not scheduled?\n", + port->port_num); + } + } + + /* If we're still connected, refill the USB RX queue. */ + if (!disconnect && port->port_usb) + gs_start_rx(port); + + spin_unlock_irq(&port->port_lock); +} + +static void gs_read_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gs_port *port = ep->driver_data; + + /* Queue all received data until the tty layer is ready for it. */ + spin_lock(&port->port_lock); + list_add_tail(&req->list, &port->read_queue); + spin_unlock(&port->port_lock); + tasklet_schedule(&port->push); +} + +static void gs_write_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gs_port *port = ep->driver_data; + + spin_lock(&port->port_lock); + list_add(&req->list, &port->write_pool); + port->write_started--; + + switch (req->status) { + default: + /* presumably a transient fault */ + pr_warning("%s: unexpected %s status %d\n", + __func__, ep->name, req->status); + /* FALL THROUGH */ + case 0: + /* normal completion */ + gs_start_tx(port); + break; + + case -ESHUTDOWN: + /* disconnect */ + pr_vdebug("%s: %s shutdown\n", __func__, ep->name); + break; + } + + spin_unlock(&port->port_lock); +} + +static void gs_free_requests(struct usb_ep *ep, struct list_head *head, + int *allocated) +{ + struct usb_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct usb_request, list); + list_del(&req->list); + gs_free_req(ep, req); + if (allocated) + (*allocated)--; + } +} + +static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head, + void (*fn)(struct usb_ep *, struct usb_request *), + int *allocated) +{ + int i; + struct usb_request *req; + int n = allocated ? QUEUE_SIZE - *allocated : QUEUE_SIZE; + + /* Pre-allocate up to QUEUE_SIZE transfers, but if we can't + * do quite that many this time, don't fail ... we just won't + * be as speedy as we might otherwise be. + */ + for (i = 0; i < n; i++) { + req = gs_alloc_req(ep, WRITE_BUF_SIZE, GFP_ATOMIC); + if (!req) + return list_empty(head) ? -ENOMEM : 0; + req->complete = fn; + list_add_tail(&req->list, head); + if (allocated) + (*allocated)++; + } + return 0; +} + +/** + * gs_start_io - start USB I/O streams + * @dev: encapsulates endpoints to use + * Context: holding port_lock; port_tty and port_usb are non-null + * + * We only start I/O when something is connected to both sides of + * this port. If nothing is listening on the host side, we may + * be pointlessly filling up our TX buffers and FIFO. + */ +static int gs_start_io(struct gs_port *port) +{ + struct list_head *head = &port->read_pool; + struct usb_ep *ep = port->port_usb->out; + int status; + unsigned started; + + /* Allocate RX and TX I/O buffers. We can't easily do this much + * earlier (with GFP_KERNEL) because the requests are coupled to + * endpoints, as are the packet sizes we'll be using. Different + * configurations may use different endpoints with a given port; + * and high speed vs full speed changes packet sizes too. + */ + status = gs_alloc_requests(ep, head, gs_read_complete, + &port->read_allocated); + if (status) + return status; + + status = gs_alloc_requests(port->port_usb->in, &port->write_pool, + gs_write_complete, &port->write_allocated); + if (status) { + gs_free_requests(ep, head, &port->read_allocated); + return status; + } + + /* queue read requests */ + port->n_read = 0; + started = gs_start_rx(port); + + /* unblock any pending writes into our circular buffer */ + if (started) { + if (port->port.tty) + tty_wakeup(port->port.tty); + } else { + gs_free_requests(ep, head, &port->read_allocated); + gs_free_requests(port->port_usb->in, &port->write_pool, + &port->write_allocated); + status = -EIO; + } + + return status; +} + +/*-------------------------------------------------------------------------*/ + +/* TTY Driver */ + +/* + * gs_open sets up the link between a gs_port and its associated TTY. + * That link is broken *only* by TTY close(), and all driver methods + * know that. + */ +static int gs_open(struct tty_struct *tty, struct file *file) +{ + int port_num = tty->index; + struct gs_port *port; + int status; + + do { + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; + if (!port) + status = -ENODEV; + else { + spin_lock_irq(&port->port_lock); + + /* already open? Great. */ + if (port->port.count) { + status = 0; + port->port.count++; + + /* currently opening/closing? wait ... */ + } else if (port->openclose) { + status = -EBUSY; + + /* ... else we do the work */ + } else { + status = -EAGAIN; + port->openclose = true; + } + spin_unlock_irq(&port->port_lock); + } + mutex_unlock(&ports[port_num].lock); + + switch (status) { + default: + /* fully handled */ + return status; + case -EAGAIN: + /* must do the work */ + break; + case -EBUSY: + /* wait for EAGAIN task to finish */ + msleep(1); + /* REVISIT could have a waitchannel here, if + * concurrent open performance is important + */ + break; + } + } while (status != -EAGAIN); + + /* Do the "real open" */ + spin_lock_irq(&port->port_lock); + + /* allocate circular buffer on first open */ + if (port->port_write_buf.buf_buf == NULL) { + + spin_unlock_irq(&port->port_lock); + status = gs_buf_alloc(&port->port_write_buf, WRITE_BUF_SIZE); + spin_lock_irq(&port->port_lock); + + if (status) { + pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n", + port->port_num, tty, file); + port->openclose = false; + goto exit_unlock_port; + } + } + + /* REVISIT if REMOVED (ports[].port NULL), abort the open + * to let rmmod work faster (but this way isn't wrong). + */ + + /* REVISIT maybe wait for "carrier detect" */ + + tty->driver_data = port; + port->port.tty = tty; + + port->port.count = 1; + port->openclose = false; + + /* if connected, start the I/O stream */ + if (port->port_usb) { + struct gserial *gser = port->port_usb; + + pr_debug("gs_open: start ttyGS%d\n", port->port_num); + gs_start_io(port); + + if (gser->connect) + gser->connect(gser); + } + + pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file); + + status = 0; + +exit_unlock_port: + spin_unlock_irq(&port->port_lock); + return status; +} + +static int gs_writes_finished(struct gs_port *p) +{ + int cond; + + /* return true on disconnect or empty buffer */ + spin_lock_irq(&p->port_lock); + cond = (p->port_usb == NULL) || !gs_buf_data_avail(&p->port_write_buf); + spin_unlock_irq(&p->port_lock); + + return cond; +} + +static void gs_close(struct tty_struct *tty, struct file *file) +{ + struct gs_port *port = tty->driver_data; + struct gserial *gser; + + spin_lock_irq(&port->port_lock); + + if (port->port.count != 1) { + if (port->port.count == 0) + WARN_ON(1); + else + --port->port.count; + goto exit; + } + + pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file); + + /* mark port as closing but in use; we can drop port lock + * and sleep if necessary + */ + port->openclose = true; + port->port.count = 0; + + gser = port->port_usb; + if (gser && gser->disconnect) + gser->disconnect(gser); + + /* wait for circular write buffer to drain, disconnect, or at + * most GS_CLOSE_TIMEOUT seconds; then discard the rest + */ + if (gs_buf_data_avail(&port->port_write_buf) > 0 && gser) { + spin_unlock_irq(&port->port_lock); + wait_event_interruptible_timeout(port->drain_wait, + gs_writes_finished(port), + GS_CLOSE_TIMEOUT * HZ); + spin_lock_irq(&port->port_lock); + gser = port->port_usb; + } + + /* Iff we're disconnected, there can be no I/O in flight so it's + * ok to free the circular buffer; else just scrub it. And don't + * let the push tasklet fire again until we're re-opened. + */ + if (gser == NULL) + gs_buf_free(&port->port_write_buf); + else + gs_buf_clear(&port->port_write_buf); + + tty->driver_data = NULL; + port->port.tty = NULL; + + port->openclose = false; + + pr_debug("gs_close: ttyGS%d (%p,%p) done!\n", + port->port_num, tty, file); + + wake_up(&port->port.close_wait); +exit: + spin_unlock_irq(&port->port_lock); +} + +static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int status; + if(port == NULL) + return 0; + pr_vdebug("gs_write: ttyGS%d (%p) writing %d bytes\n", + port->port_num, tty, count); + + spin_lock_irqsave(&port->port_lock, flags); + if (count) + count = gs_buf_put(&port->port_write_buf, buf, count); + /* treat count == 0 as flush_chars() */ + if (port->port_usb) + status = gs_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); + + return count; +} + +static int gs_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int status; + + if(port == NULL) + return 0; + pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %pf\n", + port->port_num, tty, ch, __builtin_return_address(0)); + + spin_lock_irqsave(&port->port_lock, flags); + status = gs_buf_put(&port->port_write_buf, &ch, 1); + spin_unlock_irqrestore(&port->port_lock, flags); + + return status; +} + +static void gs_flush_chars(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + + if(port == NULL) + return ; + pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty); + + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + gs_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static int gs_write_room(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int room = 0; + if(port == NULL) + return room; + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + room = gs_buf_space_avail(&port->port_write_buf); + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_vdebug("gs_write_room: (%d,%p) room=%d\n", + port->port_num, tty, room); + + return room; +} + +static int gs_chars_in_buffer(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int chars = 0; + + if(port == NULL) + return 0; + spin_lock_irqsave(&port->port_lock, flags); + chars = gs_buf_data_avail(&port->port_write_buf); + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%d\n", + port->port_num, tty, chars); + + return chars; +} + +/* undo side effects of setting TTY_THROTTLED */ +static void gs_unthrottle(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + + if(port == NULL) + return; + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) { + /* Kickstart read queue processing. We don't do xon/xoff, + * rts/cts, or other handshaking with the host, but if the + * read queue backs up enough we'll be NAKing OUT packets. + */ + tasklet_schedule(&port->push); + pr_vdebug(PREFIX "%d: unthrottle\n", port->port_num); + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static int gs_break_ctl(struct tty_struct *tty, int duration) +{ + struct gs_port *port = tty->driver_data; + int status = 0; + struct gserial *gser; + + if(port == NULL) + return 0; + pr_vdebug("gs_break_ctl: ttyGS%d, send break (%d) \n", + port->port_num, duration); + + spin_lock_irq(&port->port_lock); + gser = port->port_usb; + if (gser && gser->send_break) + status = gser->send_break(gser, duration); + spin_unlock_irq(&port->port_lock); + + return status; +} + +static const struct tty_operations gs_tty_ops = { + .open = gs_open, + .close = gs_close, + .write = gs_write, + .put_char = gs_put_char, + .flush_chars = gs_flush_chars, + .write_room = gs_write_room, + .chars_in_buffer = gs_chars_in_buffer, + .unthrottle = gs_unthrottle, + .break_ctl = gs_break_ctl, +}; + +/*-------------------------------------------------------------------------*/ + +static struct tty_driver *gs_tty_driver; + +static int +gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) +{ + struct gs_port *port; + int ret = 0; + + mutex_lock(&ports[port_num].lock); + if (ports[port_num].port) { + ret = -EBUSY; + goto out; + } + + port = kzalloc(sizeof(struct gs_port), GFP_KERNEL); + if (port == NULL) { + ret = -ENOMEM; + goto out; + } + + tty_port_init(&port->port); + spin_lock_init(&port->port_lock); + init_waitqueue_head(&port->drain_wait); + + tasklet_init(&port->push, gs_rx_push, (unsigned long) port); + + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->read_queue); + INIT_LIST_HEAD(&port->write_pool); + + port->port_num = port_num; + port->port_line_coding = *coding; + + ports[port_num].port = port; +out: + mutex_unlock(&ports[port_num].lock); + return ret; +} + +static int gs_closed(struct gs_port *port) +{ + int cond; + + spin_lock_irq(&port->port_lock); + cond = (port->port.count == 0) && !port->openclose; + spin_unlock_irq(&port->port_lock); + return cond; +} + +static void gserial_free_port(struct gs_port *port) +{ + tasklet_kill(&port->push); + /* wait for old opens to finish */ + wait_event(port->port.close_wait, gs_closed(port)); + WARN_ON(port->port_usb != NULL); + tty_port_destroy(&port->port); + kfree(port); +} + +void gserial_free_line(unsigned char port_num) +{ + struct gs_port *port; + + mutex_lock(&ports[port_num].lock); + if (WARN_ON(!ports[port_num].port)) { + mutex_unlock(&ports[port_num].lock); + return; + } + port = ports[port_num].port; + ports[port_num].port = NULL; + mutex_unlock(&ports[port_num].lock); + + gserial_free_port(port); + tty_unregister_device(gs_tty_driver, port_num); +} +EXPORT_SYMBOL_GPL(gserial_free_line); + +int gserial_alloc_line(unsigned char *line_num) +{ + struct usb_cdc_line_coding coding; + struct device *tty_dev; + int ret; + int port_num; + + coding.dwDTERate = cpu_to_le32(9600); + coding.bCharFormat = 8; + coding.bParityType = USB_CDC_NO_PARITY; + coding.bDataBits = USB_CDC_1_STOP_BITS; + + for (port_num = 0; port_num < MAX_U_SERIAL_PORTS; port_num++) { + ret = gs_port_alloc(port_num, &coding); + if (ret == -EBUSY) + continue; + if (ret) + return ret; + break; + } + if (ret) + return ret; + + /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */ + + tty_dev = tty_port_register_device(&ports[port_num].port->port, + gs_tty_driver, port_num, NULL); + + if (IS_ERR(tty_dev)) { + struct gs_port *port; + pr_err("%s: failed to register tty for port %d, err %ld\n", + __func__, port_num, PTR_ERR(tty_dev)); + + ret = PTR_ERR(tty_dev); + port = ports[port_num].port; + ports[port_num].port = NULL; + gserial_free_port(port); + goto err; + } + *line_num = port_num; +err: + return ret; +} +EXPORT_SYMBOL_GPL(gserial_alloc_line); + +/** + * gserial_connect - notify TTY I/O glue that USB link is active + * @gser: the function, set up with endpoints and descriptors + * @port_num: which port is active + * Context: any (usually from irq) + * + * This is called activate endpoints and let the TTY layer know that + * the connection is active ... not unlike "carrier detect". It won't + * necessarily start I/O queues; unless the TTY is held open by any + * task, there would be no point. However, the endpoints will be + * activated so the USB host can perform I/O, subject to basic USB + * hardware flow control. + * + * Caller needs to have set up the endpoints and USB function in @dev + * before calling this, as well as the appropriate (speed-specific) + * endpoint descriptors, and also have allocate @port_num by calling + * @gserial_alloc_line(). + * + * Returns negative errno or zero. + * On success, ep->driver_data will be overwritten. + */ +int gserial_connect(struct gserial *gser, u8 port_num) +{ + struct gs_port *port; + unsigned long flags; + int status; + + if (port_num >= MAX_U_SERIAL_PORTS) + return -ENXIO; + + port = ports[port_num].port; + if (!port) { + pr_err("serial line %d not allocated.\n", port_num); + return -EINVAL; + } + if (port->port_usb) { + pr_err("serial line %d is in use.\n", port_num); + return -EBUSY; + } + + /* activate the endpoints */ + status = usb_ep_enable(gser->in); + if (status < 0) + return status; + gser->in->driver_data = port; + + status = usb_ep_enable(gser->out); + if (status < 0) + goto fail_out; + gser->out->driver_data = port; + + /* then tell the tty glue that I/O can work */ + spin_lock_irqsave(&port->port_lock, flags); + gser->ioport = port; + port->port_usb = gser; + + /* REVISIT unclear how best to handle this state... + * we don't really couple it with the Linux TTY. + */ + gser->port_line_coding = port->port_line_coding; + + /* REVISIT if waiting on "carrier detect", signal. */ + + /* if it's already open, start I/O ... and notify the serial + * protocol about open/close status (connect/disconnect). + */ + if (port->port.count) { + pr_debug("gserial_connect: start ttyGS%d\n", port->port_num); + gs_start_io(port); + if (gser->connect) + gser->connect(gser); + } else { + if (gser->disconnect) + gser->disconnect(gser); + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + return status; + +fail_out: + usb_ep_disable(gser->in); + gser->in->driver_data = NULL; + return status; +} +EXPORT_SYMBOL_GPL(gserial_connect); +/** + * gserial_disconnect - notify TTY I/O glue that USB link is inactive + * @gser: the function, on which gserial_connect() was called + * Context: any (usually from irq) + * + * This is called to deactivate endpoints and let the TTY layer know + * that the connection went inactive ... not unlike "hangup". + * + * On return, the state is as if gserial_connect() had never been called; + * there is no active USB I/O on these endpoints. + */ +void gserial_disconnect(struct gserial *gser) +{ + struct gs_port *port = gser->ioport; + unsigned long flags; + + if (!port) + return; + + /* tell the TTY glue not to do I/O here any more */ + spin_lock_irqsave(&port->port_lock, flags); + + /* REVISIT as above: how best to track this? */ + port->port_line_coding = gser->port_line_coding; + + port->port_usb = NULL; + gser->ioport = NULL; + if (port->port.count > 0 || port->openclose) { + wake_up_interruptible(&port->drain_wait); + if (port->port.tty) + tty_hangup(port->port.tty); + } + spin_unlock_irqrestore(&port->port_lock, flags); + + /* disable endpoints, aborting down any active I/O */ + usb_ep_disable(gser->out); + gser->out->driver_data = NULL; + + usb_ep_disable(gser->in); + gser->in->driver_data = NULL; + + /* finally, free any unused/unusable I/O buffers */ + spin_lock_irqsave(&port->port_lock, flags); + if (port->port.count == 0 && !port->openclose) + gs_buf_free(&port->port_write_buf); + gs_free_requests(gser->out, &port->read_pool, NULL); + gs_free_requests(gser->out, &port->read_queue, NULL); + gs_free_requests(gser->in, &port->write_pool, NULL); + + port->read_allocated = port->read_started = + port->write_allocated = port->write_started = 0; + + spin_unlock_irqrestore(&port->port_lock, flags); +} +EXPORT_SYMBOL_GPL(gserial_disconnect); + +static int userial_init(void) +{ + unsigned i; + int status; + + gs_tty_driver = alloc_tty_driver(MAX_U_SERIAL_PORTS); + if (!gs_tty_driver) + return -ENOMEM; + + gs_tty_driver->driver_name = "g_serial"; + gs_tty_driver->name = PREFIX; + /* uses dynamically assigned dev_t values */ + + gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + gs_tty_driver->subtype = SERIAL_TYPE_NORMAL; + gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + gs_tty_driver->init_termios = tty_std_termios; + + /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on + * MS-Windows. Otherwise, most of these flags shouldn't affect + * anything unless we were to actually hook up to a serial line. + */ + gs_tty_driver->init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + gs_tty_driver->init_termios.c_ispeed = 9600; + gs_tty_driver->init_termios.c_ospeed = 9600; + + tty_set_operations(gs_tty_driver, &gs_tty_ops); + for (i = 0; i < MAX_U_SERIAL_PORTS; i++) + mutex_init(&ports[i].lock); + + /* export the driver ... */ + status = tty_register_driver(gs_tty_driver); + if (status) { + pr_err("%s: cannot register, err %d\n", + __func__, status); + goto fail; + } + + pr_debug("%s: registered %d ttyGS* device%s\n", __func__, + MAX_U_SERIAL_PORTS, + (MAX_U_SERIAL_PORTS == 1) ? "" : "s"); + + return status; +fail: + put_tty_driver(gs_tty_driver); + gs_tty_driver = NULL; + return status; +} +module_init(userial_init); + +static void userial_cleanup(void) +{ + tty_unregister_driver(gs_tty_driver); + put_tty_driver(gs_tty_driver); + gs_tty_driver = NULL; +} +module_exit(userial_cleanup); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/u_serial.h b/drivers/usb/gadget/u_serial.h new file mode 100644 index 00000000..a5dc5537 --- /dev/null +++ b/drivers/usb/gadget/u_serial.h @@ -0,0 +1,71 @@ +/* + * u_serial.h - interface to USB gadget "serial port"/TTY utilities + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#ifndef __U_SERIAL_H +#define __U_SERIAL_H + +#include +#include + +#define MAX_U_SERIAL_PORTS 8 + +struct f_serial_opts { + struct usb_function_instance func_inst; + u8 port_num; +}; + +/* + * One non-multiplexed "serial" I/O port ... there can be several of these + * on any given USB peripheral device, if it provides enough endpoints. + * + * The "u_serial" utility component exists to do one thing: manage TTY + * style I/O using the USB peripheral endpoints listed here, including + * hookups to sysfs and /dev for each logical "tty" device. + * + * REVISIT at least ACM could support tiocmget() if needed. + * + * REVISIT someday, allow multiplexing several TTYs over these endpoints. + */ +struct gserial { + struct usb_function func; + + /* port is managed by gserial_{connect,disconnect} */ + struct gs_port *ioport; + + struct usb_ep *in; + struct usb_ep *out; + + /* REVISIT avoid this CDC-ACM support harder ... */ + struct usb_cdc_line_coding port_line_coding; /* 9600-8-N-1 etc */ + + /* notification callbacks */ + void (*connect)(struct gserial *p); + void (*disconnect)(struct gserial *p); + int (*send_break)(struct gserial *p, int duration); +}; + +/* utilities to allocate/free request and buffer */ +struct usb_request *gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags); +void gs_free_req(struct usb_ep *, struct usb_request *req); + +/* management of individual TTY ports */ +int gserial_alloc_line(unsigned char *port_line); +void gserial_free_line(unsigned char port_line); + +/* connect/disconnect is handled by individual functions */ +int gserial_connect(struct gserial *, u8 port_num); +void gserial_disconnect(struct gserial *); + +/* functions are bound to configurations by a config or gadget driver */ +int gser_bind_config(struct usb_configuration *c, u8 port_num); +int obex_bind_config(struct usb_configuration *c, u8 port_num); + +#endif /* __U_SERIAL_H */ diff --git a/drivers/usb/gadget/u_uac1.c b/drivers/usb/gadget/u_uac1.c new file mode 100644 index 00000000..c7d460f4 --- /dev/null +++ b/drivers/usb/gadget/u_uac1.c @@ -0,0 +1,330 @@ +/* + * u_uac1.c -- ALSA audio utilities for Gadget stack + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "u_uac1.h" + +/* + * This component encapsulates the ALSA devices for USB audio gadget + */ + +#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p" +#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c" +#define FILE_CONTROL "/dev/snd/controlC0" + +static char *fn_play = FILE_PCM_PLAYBACK; +module_param(fn_play, charp, S_IRUGO); +MODULE_PARM_DESC(fn_play, "Playback PCM device file name"); + +static char *fn_cap = FILE_PCM_CAPTURE; +module_param(fn_cap, charp, S_IRUGO); +MODULE_PARM_DESC(fn_cap, "Capture PCM device file name"); + +static char *fn_cntl = FILE_CONTROL; +module_param(fn_cntl, charp, S_IRUGO); +MODULE_PARM_DESC(fn_cntl, "Control device file name"); + +/*-------------------------------------------------------------------------*/ + +/** + * Some ALSA internal helper functions + */ +static int snd_interval_refine_set(struct snd_interval *i, unsigned int val) +{ + struct snd_interval t; + t.empty = 0; + t.min = t.max = val; + t.openmin = t.openmax = 0; + t.integer = 1; + return snd_interval_refine(i, &t); +} + +static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + if (hw_is_mask(var)) { + struct snd_mask *m = hw_param_mask(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_mask_none(m); + } else { + if (dir > 0) + val++; + else if (dir < 0) + val--; + changed = snd_mask_refine_set( + hw_param_mask(params, var), val); + } + } else if (hw_is_interval(var)) { + struct snd_interval *i = hw_param_interval(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_interval_none(i); + } else if (dir == 0) + changed = snd_interval_refine_set(i, val); + else { + struct snd_interval t; + t.openmin = 1; + t.openmax = 1; + t.empty = 0; + t.integer = 0; + if (dir < 0) { + t.min = val - 1; + t.max = val; + } else { + t.min = val; + t.max = val+1; + } + changed = snd_interval_refine(i, &t); + } + } else + return -EINVAL; + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} +/*-------------------------------------------------------------------------*/ + +/** + * Set default hardware params + */ +static int playback_default_hw_params(struct gaudio_snd_dev *snd) +{ + struct snd_pcm_substream *substream = snd->substream; + struct snd_pcm_hw_params *params; + snd_pcm_sframes_t result; + + /* + * SNDRV_PCM_ACCESS_RW_INTERLEAVED, + * SNDRV_PCM_FORMAT_S16_LE + * CHANNELS: 2 + * RATE: 48000 + */ + snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + snd->format = SNDRV_PCM_FORMAT_S16_LE; + snd->channels = 2; + snd->rate = 48000; + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, + snd->access, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, + snd->format, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + snd->channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, + snd->rate, 0); + + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params); + + result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); + if (result < 0) { + ERROR(snd->card, + "Preparing sound card failed: %d\n", (int)result); + kfree(params); + return result; + } + + /* Store the hardware parameters */ + snd->access = params_access(params); + snd->format = params_format(params); + snd->channels = params_channels(params); + snd->rate = params_rate(params); + + kfree(params); + + INFO(snd->card, + "Hardware params: access %x, format %x, channels %d, rate %d\n", + snd->access, snd->format, snd->channels, snd->rate); + + return 0; +} + +/** + * Playback audio buffer data by ALSA PCM device + */ +static size_t u_audio_playback(struct gaudio *card, void *buf, size_t count) +{ + struct gaudio_snd_dev *snd = &card->playback; + struct snd_pcm_substream *substream = snd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + mm_segment_t old_fs; + ssize_t result; + snd_pcm_sframes_t frames; + +try_again: + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { + result = snd_pcm_kernel_ioctl(substream, + SNDRV_PCM_IOCTL_PREPARE, NULL); + if (result < 0) { + ERROR(card, "Preparing sound card failed: %d\n", + (int)result); + return result; + } + } + + frames = bytes_to_frames(runtime, count); + old_fs = get_fs(); + set_fs(KERNEL_DS); + result = snd_pcm_lib_write(snd->substream, buf, frames); + if (result != frames) { + ERROR(card, "Playback error: %d\n", (int)result); + set_fs(old_fs); + goto try_again; + } + set_fs(old_fs); + + return 0; +} + +static int u_audio_get_playback_channels(struct gaudio *card) +{ + return card->playback.channels; +} + +static int u_audio_get_playback_rate(struct gaudio *card) +{ + return card->playback.rate; +} + +/** + * Open ALSA PCM and control device files + * Initial the PCM or control device + */ +static int gaudio_open_snd_dev(struct gaudio *card) +{ + struct snd_pcm_file *pcm_file; + struct gaudio_snd_dev *snd; + + if (!card) + return -ENODEV; + + /* Open control device */ + snd = &card->control; + snd->filp = filp_open(fn_cntl, O_RDWR, 0); + if (IS_ERR(snd->filp)) { + int ret = PTR_ERR(snd->filp); + ERROR(card, "unable to open sound control device file: %s\n", + fn_cntl); + snd->filp = NULL; + return ret; + } + snd->card = card; + + /* Open PCM playback device and setup substream */ + snd = &card->playback; + snd->filp = filp_open(fn_play, O_WRONLY, 0); + if (IS_ERR(snd->filp)) { + int ret = PTR_ERR(snd->filp); + + ERROR(card, "No such PCM playback device: %s\n", fn_play); + snd->filp = NULL; + return ret; + } + pcm_file = snd->filp->private_data; + snd->substream = pcm_file->substream; + snd->card = card; + playback_default_hw_params(snd); + + /* Open PCM capture device and setup substream */ + snd = &card->capture; + snd->filp = filp_open(fn_cap, O_RDONLY, 0); + if (IS_ERR(snd->filp)) { + ERROR(card, "No such PCM capture device: %s\n", fn_cap); + snd->substream = NULL; + snd->card = NULL; + snd->filp = NULL; + } else { + pcm_file = snd->filp->private_data; + snd->substream = pcm_file->substream; + snd->card = card; + } + + return 0; +} + +/** + * Close ALSA PCM and control device files + */ +static int gaudio_close_snd_dev(struct gaudio *gau) +{ + struct gaudio_snd_dev *snd; + + /* Close control device */ + snd = &gau->control; + if (snd->filp) + filp_close(snd->filp, NULL); + + /* Close PCM playback device and setup substream */ + snd = &gau->playback; + if (snd->filp) + filp_close(snd->filp, NULL); + + /* Close PCM capture device and setup substream */ + snd = &gau->capture; + if (snd->filp) + filp_close(snd->filp, NULL); + + return 0; +} + +static struct gaudio *the_card; +/** + * gaudio_setup - setup ALSA interface and preparing for USB transfer + * + * This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using. + * + * Returns negative errno, or zero on success + */ +int __init gaudio_setup(struct gaudio *card) +{ + int ret; + + ret = gaudio_open_snd_dev(card); + if (ret) + ERROR(card, "we need at least one control device\n"); + else if (!the_card) + the_card = card; + + return ret; + +} + +/** + * gaudio_cleanup - remove ALSA device interface + * + * This is called to free all resources allocated by @gaudio_setup(). + */ +void gaudio_cleanup(void) +{ + if (the_card) { + gaudio_close_snd_dev(the_card); + the_card = NULL; + } +} + diff --git a/drivers/usb/gadget/u_uac1.h b/drivers/usb/gadget/u_uac1.h new file mode 100644 index 00000000..18c2e729 --- /dev/null +++ b/drivers/usb/gadget/u_uac1.h @@ -0,0 +1,56 @@ +/* + * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __U_AUDIO_H +#define __U_AUDIO_H + +#include +#include +#include +#include + +#include +#include +#include + +#include "gadget_chips.h" + +/* + * This represents the USB side of an audio card device, managed by a USB + * function which provides control and stream interfaces. + */ + +struct gaudio_snd_dev { + struct gaudio *card; + struct file *filp; + struct snd_pcm_substream *substream; + int access; + int format; + int channels; + int rate; +}; + +struct gaudio { + struct usb_function func; + struct usb_gadget *gadget; + + /* ALSA sound device interfaces */ + struct gaudio_snd_dev control; + struct gaudio_snd_dev playback; + struct gaudio_snd_dev capture; + + /* TODO */ +}; + +int gaudio_setup(struct gaudio *card); +void gaudio_cleanup(void); + +#endif /* __U_AUDIO_H */ diff --git a/drivers/usb/gadget/udc-core.c b/drivers/usb/gadget/udc-core.c new file mode 100644 index 00000000..58a86139 --- /dev/null +++ b/drivers/usb/gadget/udc-core.c @@ -0,0 +1,579 @@ +/** + * udc.c - Core UDC Framework + * + * Copyright (C) 2010 Texas Instruments + * Author: Felipe Balbi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * struct usb_udc - describes one usb device controller + * @driver - the gadget driver pointer. For use by the class code + * @dev - the child device to the actual controller + * @gadget - the gadget. For use by the class code + * @list - for use by the udc class driver + * + * This represents the internal data structure which is used by the UDC-class + * to hold information about udc driver and gadget together. + */ +struct usb_udc { + struct usb_gadget_driver *driver; + struct usb_gadget *gadget; + struct device dev; + struct list_head list; +}; + +static struct class *udc_class; +static LIST_HEAD(udc_list); +static DEFINE_MUTEX(udc_lock); + +/* ------------------------------------------------------------------------- */ + +int usb_gadget_map_request(struct usb_gadget *gadget, + struct usb_request *req, int is_in) +{ + if (req->length == 0) + return 0; + + if (req->num_sgs) { + int mapped; + + mapped = dma_map_sg(&gadget->dev, req->sg, req->num_sgs, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (mapped == 0) { + dev_err(&gadget->dev, "failed to map SGs\n"); + return -EFAULT; + } + + req->num_mapped_sgs = mapped; + } else { + req->dma = dma_map_single(&gadget->dev, req->buf, req->length, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (dma_mapping_error(&gadget->dev, req->dma)) { + dev_err(&gadget->dev, "failed to map buffer\n"); + return -EFAULT; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_gadget_map_request); + +void usb_gadget_unmap_request(struct usb_gadget *gadget, + struct usb_request *req, int is_in) +{ + if (req->length == 0) + return; + + if (req->num_mapped_sgs) { + dma_unmap_sg(&gadget->dev, req->sg, req->num_mapped_sgs, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + req->num_mapped_sgs = 0; + } else { + dma_unmap_single(&gadget->dev, req->dma, req->length, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + } +} +EXPORT_SYMBOL_GPL(usb_gadget_unmap_request); + +/* ------------------------------------------------------------------------- */ + +void usb_gadget_set_state(struct usb_gadget *gadget, + enum usb_device_state state) +{ + gadget->state = state; + sysfs_notify(&gadget->dev.kobj, NULL, "state"); +} +EXPORT_SYMBOL_GPL(usb_gadget_set_state); + +/* ------------------------------------------------------------------------- */ + +/** + * usb_gadget_udc_start - tells usb device controller to start up + * @gadget: The gadget we want to get started + * @driver: The driver we want to bind to @gadget + * + * This call is issued by the UDC Class driver when it's about + * to register a gadget driver to the device controller, before + * calling gadget driver's bind() method. + * + * It allows the controller to be powered off until strictly + * necessary to have it powered on. + * + * Returns zero on success, else negative errno. + */ +static inline int usb_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + return gadget->ops->udc_start(gadget, driver); +} + +/** + * usb_gadget_udc_stop - tells usb device controller we don't need it anymore + * @gadget: The device we want to stop activity + * @driver: The driver to unbind from @gadget + * + * This call is issued by the UDC Class driver after calling + * gadget driver's unbind() method. + * + * The details are implementation specific, but it can go as + * far as powering off UDC completely and disable its data + * line pullups. + */ +static inline void usb_gadget_udc_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + gadget->ops->udc_stop(gadget, driver); +} + +/** + * usb_udc_release - release the usb_udc struct + * @dev: the dev member within usb_udc + * + * This is called by driver's core in order to free memory once the last + * reference is released. + */ +static void usb_udc_release(struct device *dev) +{ + struct usb_udc *udc; + + udc = container_of(dev, struct usb_udc, dev); + dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); + kfree(udc); +} + +static const struct attribute_group *usb_udc_attr_groups[]; + +static void usb_udc_nop_release(struct device *dev) +{ + dev_vdbg(dev, "%s\n", __func__); +} + +/** + * usb_add_gadget_udc_release - adds a new gadget to the udc class driver list + * @parent: the parent device to this udc. Usually the controller driver's + * device. + * @gadget: the gadget to be added to the list. + * @release: a gadget release function. + * + * Returns zero on success, negative errno otherwise. + */ +int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, + void (*release)(struct device *dev)) +{ + struct usb_udc *udc; + int ret = -ENOMEM; + + udc = kzalloc(sizeof(*udc), GFP_KERNEL); + if (!udc) + goto err1; + + dev_set_name(&gadget->dev, "gadget"); + gadget->dev.parent = parent; + + dma_set_coherent_mask(&gadget->dev, parent->coherent_dma_mask); + gadget->dev.dma_parms = parent->dma_parms; + gadget->dev.dma_mask = parent->dma_mask; + + if (release) + gadget->dev.release = release; + else + gadget->dev.release = usb_udc_nop_release; + + ret = device_register(&gadget->dev); + if (ret) + goto err2; + + device_initialize(&udc->dev); + udc->dev.release = usb_udc_release; + udc->dev.class = udc_class; + udc->dev.groups = usb_udc_attr_groups; + udc->dev.parent = parent; + ret = dev_set_name(&udc->dev, "%s", kobject_name(&parent->kobj)); + if (ret) + goto err3; + + udc->gadget = gadget; + + mutex_lock(&udc_lock); + list_add_tail(&udc->list, &udc_list); + + ret = device_add(&udc->dev); + if (ret) + goto err4; + + usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); + + mutex_unlock(&udc_lock); + + return 0; + +err4: + list_del(&udc->list); + mutex_unlock(&udc_lock); + +err3: + put_device(&udc->dev); + +err2: + put_device(&gadget->dev); + kfree(udc); + +err1: + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_gadget_udc_release); + +/** + * usb_add_gadget_udc - adds a new gadget to the udc class driver list + * @parent: the parent device to this udc. Usually the controller + * driver's device. + * @gadget: the gadget to be added to the list + * + * Returns zero on success, negative errno otherwise. + */ +int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget) +{ + return usb_add_gadget_udc_release(parent, gadget, NULL); +} +EXPORT_SYMBOL_GPL(usb_add_gadget_udc); + +static void usb_gadget_remove_driver(struct usb_udc *udc) +{ + dev_dbg(&udc->dev, "unregistering UDC driver [%s]\n", + udc->gadget->name); + + kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); + + usb_gadget_disconnect(udc->gadget); + udc->driver->disconnect(udc->gadget); + udc->driver->unbind(udc->gadget); + usb_gadget_udc_stop(udc->gadget, NULL); + + udc->driver = NULL; + udc->dev.driver = NULL; + udc->gadget->dev.driver = NULL; +} + +/** + * usb_del_gadget_udc - deletes @udc from udc_list + * @gadget: the gadget to be removed. + * + * This, will call usb_gadget_unregister_driver() if + * the @udc is still busy. + */ +void usb_del_gadget_udc(struct usb_gadget *gadget) +{ + struct usb_udc *udc = NULL; + + mutex_lock(&udc_lock); + list_for_each_entry(udc, &udc_list, list) + if (udc->gadget == gadget) + goto found; + + dev_err(gadget->dev.parent, "gadget not registered.\n"); + mutex_unlock(&udc_lock); + + return; + +found: + dev_vdbg(gadget->dev.parent, "unregistering gadget\n"); + + list_del(&udc->list); + mutex_unlock(&udc_lock); + + if (udc->driver) + usb_gadget_remove_driver(udc); + + kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); + device_unregister(&udc->dev); + device_unregister(&gadget->dev); +} +EXPORT_SYMBOL_GPL(usb_del_gadget_udc); + +/* ------------------------------------------------------------------------- */ + +static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver) +{ + int ret; + + dev_dbg(&udc->dev, "registering UDC driver [%s]\n", + driver->function); + + udc->driver = driver; + udc->dev.driver = &driver->driver; + udc->gadget->dev.driver = &driver->driver; + + ret = driver->bind(udc->gadget, driver); + if (ret) + goto err1; + ret = usb_gadget_udc_start(udc->gadget, driver); + if (ret) { + driver->unbind(udc->gadget); + goto err1; + } + /* + * HACK: The Android gadget driver disconnects the gadget + * on bind and expects the gadget to stay disconnected until + * it calls usb_gadget_connect when userspace is ready. Remove + * the call to usb_gadget_connect bellow to avoid enabling the + * pullup before userspace is ready. + * + * usb_gadget_connect(udc->gadget); + */ + + kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); + return 0; +err1: + dev_err(&udc->dev, "failed to start %s: %d\n", + udc->driver->function, ret); + udc->driver = NULL; + udc->dev.driver = NULL; + udc->gadget->dev.driver = NULL; + return ret; +} + +int udc_attach_driver(const char *name, struct usb_gadget_driver *driver) +{ + struct usb_udc *udc = NULL; + int ret = -ENODEV; + + mutex_lock(&udc_lock); + list_for_each_entry(udc, &udc_list, list) { + ret = strcmp(name, dev_name(&udc->dev)); + if (!ret) + break; + } + if (ret) { + ret = -ENODEV; + goto out; + } + if (udc->driver) { + ret = -EBUSY; + goto out; + } + ret = udc_bind_to_driver(udc, driver); +out: + mutex_unlock(&udc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(udc_attach_driver); + +int usb_gadget_probe_driver(struct usb_gadget_driver *driver) +{ + struct usb_udc *udc = NULL; + int ret; + + if (!driver || !driver->bind || !driver->setup) + return -EINVAL; + + mutex_lock(&udc_lock); + list_for_each_entry(udc, &udc_list, list) { + /* For now we take the first one */ + if (!udc->driver) + goto found; + } + + pr_debug("couldn't find an available UDC\n"); + mutex_unlock(&udc_lock); + return -ENODEV; +found: + ret = udc_bind_to_driver(udc, driver); + mutex_unlock(&udc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_probe_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usb_udc *udc = NULL; + int ret = -ENODEV; + + if (!driver || !driver->unbind) + return -EINVAL; + + mutex_lock(&udc_lock); + list_for_each_entry(udc, &udc_list, list) + if (udc->driver == driver) { + usb_gadget_remove_driver(udc); + ret = 0; + break; + } + + mutex_unlock(&udc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver); + +/* ------------------------------------------------------------------------- */ + +static ssize_t usb_udc_srp_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); + + if (sysfs_streq(buf, "1")) + usb_gadget_wakeup(udc->gadget); + + return n; +} +static DEVICE_ATTR(srp, S_IWUSR, NULL, usb_udc_srp_store); + +static ssize_t usb_udc_softconn_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); + + if (!udc->driver) { + dev_err(dev, "soft-connect without a gadget driver\n"); + return -EOPNOTSUPP; + } + + if (sysfs_streq(buf, "connect")) { + usb_gadget_udc_start(udc->gadget, udc->driver); + usb_gadget_connect(udc->gadget); + } else if (sysfs_streq(buf, "disconnect")) { + usb_gadget_disconnect(udc->gadget); + usb_gadget_udc_stop(udc->gadget, udc->driver); + } else { + dev_err(dev, "unsupported command '%s'\n", buf); + return -EINVAL; + } + + return n; +} +static DEVICE_ATTR(soft_connect, S_IWUSR, NULL, usb_udc_softconn_store); + +static ssize_t usb_gadget_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); + struct usb_gadget *gadget = udc->gadget; + + return sprintf(buf, "%s\n", usb_state_string(gadget->state)); +} +static DEVICE_ATTR(state, S_IRUGO, usb_gadget_state_show, NULL); + +#define USB_UDC_SPEED_ATTR(name, param) \ +ssize_t usb_udc_##param##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); \ + return snprintf(buf, PAGE_SIZE, "%s\n", \ + usb_speed_string(udc->gadget->param)); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, usb_udc_##param##_show, NULL) + +static USB_UDC_SPEED_ATTR(current_speed, speed); +static USB_UDC_SPEED_ATTR(maximum_speed, max_speed); + +#define USB_UDC_ATTR(name) \ +ssize_t usb_udc_##name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); \ + struct usb_gadget *gadget = udc->gadget; \ + \ + return snprintf(buf, PAGE_SIZE, "%d\n", gadget->name); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, usb_udc_##name##_show, NULL) + +static USB_UDC_ATTR(is_otg); +static USB_UDC_ATTR(is_a_peripheral); +static USB_UDC_ATTR(b_hnp_enable); +static USB_UDC_ATTR(a_hnp_support); +static USB_UDC_ATTR(a_alt_hnp_support); + +static struct attribute *usb_udc_attrs[] = { + &dev_attr_srp.attr, + &dev_attr_soft_connect.attr, + &dev_attr_state.attr, + &dev_attr_current_speed.attr, + &dev_attr_maximum_speed.attr, + + &dev_attr_is_otg.attr, + &dev_attr_is_a_peripheral.attr, + &dev_attr_b_hnp_enable.attr, + &dev_attr_a_hnp_support.attr, + &dev_attr_a_alt_hnp_support.attr, + NULL, +}; + +static const struct attribute_group usb_udc_attr_group = { + .attrs = usb_udc_attrs, +}; + +static const struct attribute_group *usb_udc_attr_groups[] = { + &usb_udc_attr_group, + NULL, +}; + +static int usb_udc_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); + int ret; + + ret = add_uevent_var(env, "USB_UDC_NAME=%s", udc->gadget->name); + if (ret) { + dev_err(dev, "failed to add uevent USB_UDC_NAME\n"); + return ret; + } + + if (udc->driver) { + ret = add_uevent_var(env, "USB_UDC_DRIVER=%s", + udc->driver->function); + if (ret) { + dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n"); + return ret; + } + } + + return 0; +} + +static int __init usb_udc_init(void) +{ + udc_class = class_create(THIS_MODULE, "udc"); + if (IS_ERR(udc_class)) { + pr_err("failed to create udc class --> %ld\n", + PTR_ERR(udc_class)); + return PTR_ERR(udc_class); + } + + udc_class->dev_uevent = usb_udc_uevent; + return 0; +} +subsys_initcall(usb_udc_init); + +static void __exit usb_udc_exit(void) +{ + class_destroy(udc_class); +} +module_exit(usb_udc_exit); + +MODULE_DESCRIPTION("UDC Framework"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/usbstring.c b/drivers/usb/gadget/usbstring.c new file mode 100644 index 00000000..1f49fce0 --- /dev/null +++ b/drivers/usb/gadget/usbstring.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +/** + * usb_gadget_get_string - fill out a string descriptor + * @table: of c strings encoded using UTF-8 + * @id: string id, from low byte of wValue in get string descriptor + * @buf: at least 256 bytes, must be 16-bit aligned + * + * Finds the UTF-8 string matching the ID, and converts it into a + * string descriptor in utf16-le. + * Returns length of descriptor (always even) or negative errno + * + * If your driver needs stings in multiple languages, you'll probably + * "switch (wIndex) { ... }" in your ep0 string descriptor logic, + * using this routine after choosing which set of UTF-8 strings to use. + * Note that US-ASCII is a strict subset of UTF-8; any string bytes with + * the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1 + * characters (which are also widely used in C strings). + */ +int +usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf) +{ + struct usb_string *s; + int len; + + /* descriptor 0 has the language id */ + if (id == 0) { + buf [0] = 4; + buf [1] = USB_DT_STRING; + buf [2] = (u8) table->language; + buf [3] = (u8) (table->language >> 8); + return 4; + } + for (s = table->strings; s && s->s; s++) + if (s->id == id) + break; + + /* unrecognized: stall. */ + if (!s || !s->s) + return -EINVAL; + + /* string descriptors have length, tag, then UTF16-LE text */ + len = min ((size_t) 126, strlen (s->s)); + len = utf8s_to_utf16s(s->s, len, UTF16_LITTLE_ENDIAN, + (wchar_t *) &buf[2], 126); + if (len < 0) + return -EINVAL; + buf [0] = (len + 1) * 2; + buf [1] = USB_DT_STRING; + return buf [0]; +} +EXPORT_SYMBOL_GPL(usb_gadget_get_string); diff --git a/drivers/usb/gadget/uvc.h b/drivers/usb/gadget/uvc.h new file mode 100644 index 00000000..817e9e19 --- /dev/null +++ b/drivers/usb/gadget/uvc.h @@ -0,0 +1,200 @@ +/* + * uvc_gadget.h -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _UVC_GADGET_H_ +#define _UVC_GADGET_H_ + +#include +#include +#include + +#define UVC_EVENT_FIRST (V4L2_EVENT_PRIVATE_START + 0) +#define UVC_EVENT_CONNECT (V4L2_EVENT_PRIVATE_START + 0) +#define UVC_EVENT_DISCONNECT (V4L2_EVENT_PRIVATE_START + 1) +#define UVC_EVENT_STREAMON (V4L2_EVENT_PRIVATE_START + 2) +#define UVC_EVENT_STREAMOFF (V4L2_EVENT_PRIVATE_START + 3) +#define UVC_EVENT_SETUP (V4L2_EVENT_PRIVATE_START + 4) +#define UVC_EVENT_DATA (V4L2_EVENT_PRIVATE_START + 5) +#define UVC_EVENT_LAST (V4L2_EVENT_PRIVATE_START + 5) + +struct uvc_request_data +{ + __s32 length; + __u8 data[60]; +}; + +struct uvc_event +{ + union { + enum usb_device_speed speed; + struct usb_ctrlrequest req; + struct uvc_request_data data; + }; +}; + +#define UVCIOC_SEND_RESPONSE _IOW('U', 1, struct uvc_request_data) + +#define UVC_INTF_CONTROL 0 +#define UVC_INTF_STREAMING 1 + +/* ------------------------------------------------------------------------ + * Debugging, printing and logging + */ + +#ifdef __KERNEL__ + +#include /* For usb_endpoint_* */ +#include +#include +#include +#include + +#include "uvc_queue.h" + +#define UVC_TRACE_PROBE (1 << 0) +#define UVC_TRACE_DESCR (1 << 1) +#define UVC_TRACE_CONTROL (1 << 2) +#define UVC_TRACE_FORMAT (1 << 3) +#define UVC_TRACE_CAPTURE (1 << 4) +#define UVC_TRACE_CALLS (1 << 5) +#define UVC_TRACE_IOCTL (1 << 6) +#define UVC_TRACE_FRAME (1 << 7) +#define UVC_TRACE_SUSPEND (1 << 8) +#define UVC_TRACE_STATUS (1 << 9) + +#define UVC_WARN_MINMAX 0 +#define UVC_WARN_PROBE_DEF 1 + +extern unsigned int uvc_gadget_trace_param; + +#define uvc_trace(flag, msg...) \ + do { \ + if (uvc_gadget_trace_param & flag) \ + printk(KERN_DEBUG "uvcvideo: " msg); \ + } while (0) + +#define uvc_warn_once(dev, warn, msg...) \ + do { \ + if (!test_and_set_bit(warn, &dev->warnings)) \ + printk(KERN_INFO "uvcvideo: " msg); \ + } while (0) + +#define uvc_printk(level, msg...) \ + printk(level "uvcvideo: " msg) + +/* ------------------------------------------------------------------------ + * Driver specific constants + */ + +#define DRIVER_VERSION "0.1.0" +#define DRIVER_VERSION_NUMBER KERNEL_VERSION(0, 1, 0) + +#define UVC_NUM_REQUESTS 4 +#define UVC_MAX_REQUEST_SIZE 64 +#define UVC_MAX_EVENTS 4 + +/* ------------------------------------------------------------------------ + * Structures + */ + +struct uvc_video +{ + struct usb_ep *ep; + + /* Frame parameters */ + u8 bpp; + u32 fcc; + unsigned int width; + unsigned int height; + unsigned int imagesize; + + /* Requests */ + unsigned int req_size; + struct usb_request *req[UVC_NUM_REQUESTS]; + __u8 *req_buffer[UVC_NUM_REQUESTS]; + struct list_head req_free; + spinlock_t req_lock; + + void (*encode) (struct usb_request *req, struct uvc_video *video, + struct uvc_buffer *buf); + + /* Context data used by the completion handler */ + __u32 payload_size; + __u32 max_payload_size; + + struct uvc_video_queue queue; + unsigned int fid; +}; + +enum uvc_state +{ + UVC_STATE_DISCONNECTED, + UVC_STATE_CONNECTED, + UVC_STATE_STREAMING, +}; + +struct uvc_device +{ + struct video_device *vdev; + enum uvc_state state; + struct usb_function func; + struct uvc_video video; + + /* Descriptors */ + struct { + const struct uvc_descriptor_header * const *fs_control; + const struct uvc_descriptor_header * const *ss_control; + const struct uvc_descriptor_header * const *fs_streaming; + const struct uvc_descriptor_header * const *hs_streaming; + const struct uvc_descriptor_header * const *ss_streaming; + } desc; + + unsigned int control_intf; + struct usb_ep *control_ep; + struct usb_request *control_req; + void *control_buf; + + unsigned int streaming_intf; + + /* Events */ + unsigned int event_length; + unsigned int event_setup_out : 1; +}; + +static inline struct uvc_device *to_uvc(struct usb_function *f) +{ + return container_of(f, struct uvc_device, func); +} + +struct uvc_file_handle +{ + struct v4l2_fh vfh; + struct uvc_video *device; +}; + +#define to_uvc_file_handle(handle) \ + container_of(handle, struct uvc_file_handle, vfh) + +/* ------------------------------------------------------------------------ + * Functions + */ + +extern void uvc_function_setup_continue(struct uvc_device *uvc); +extern void uvc_endpoint_stream(struct uvc_device *dev); + +extern void uvc_function_connect(struct uvc_device *uvc); +extern void uvc_function_disconnect(struct uvc_device *uvc); + +#endif /* __KERNEL__ */ + +#endif /* _UVC_GADGET_H_ */ + diff --git a/drivers/usb/gadget/uvc_queue.c b/drivers/usb/gadget/uvc_queue.c new file mode 100644 index 00000000..de456a5a --- /dev/null +++ b/drivers/usb/gadget/uvc_queue.c @@ -0,0 +1,393 @@ +/* + * uvc_queue.c -- USB Video Class driver - Buffers management + * + * Copyright (C) 2005-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "uvc.h" + +/* ------------------------------------------------------------------------ + * Video buffers queue management. + * + * Video queues is initialized by uvc_queue_init(). The function performs + * basic initialization of the uvc_video_queue struct and never fails. + * + * Video buffers are managed by videobuf2. The driver uses a mutex to protect + * the videobuf2 queue operations by serializing calls to videobuf2 and a + * spinlock to protect the IRQ queue that holds the buffers to be processed by + * the driver. + */ + +/* ----------------------------------------------------------------------------- + * videobuf2 queue operations + */ + +static int uvc_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct uvc_video_queue *queue = vb2_get_drv_priv(vq); + struct uvc_video *video = container_of(queue, struct uvc_video, queue); + + if (*nbuffers > UVC_MAX_VIDEO_BUFFERS) + *nbuffers = UVC_MAX_VIDEO_BUFFERS; + + *nplanes = 1; + + sizes[0] = video->imagesize; + + return 0; +} + +static int uvc_buffer_prepare(struct vb2_buffer *vb) +{ + struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue); + struct uvc_buffer *buf = container_of(vb, struct uvc_buffer, buf); + + if (vb->v4l2_buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT && + vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) { + uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n"); + return -EINVAL; + } + + if (unlikely(queue->flags & UVC_QUEUE_DISCONNECTED)) + return -ENODEV; + + buf->state = UVC_BUF_STATE_QUEUED; + buf->mem = vb2_plane_vaddr(vb, 0); + buf->length = vb2_plane_size(vb, 0); + if (vb->v4l2_buf.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + buf->bytesused = 0; + else + buf->bytesused = vb2_get_plane_payload(vb, 0); + + return 0; +} + +static void uvc_buffer_queue(struct vb2_buffer *vb) +{ + struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue); + struct uvc_buffer *buf = container_of(vb, struct uvc_buffer, buf); + unsigned long flags; + + spin_lock_irqsave(&queue->irqlock, flags); + + if (likely(!(queue->flags & UVC_QUEUE_DISCONNECTED))) { + list_add_tail(&buf->queue, &queue->irqqueue); + } else { + /* If the device is disconnected return the buffer to userspace + * directly. The next QBUF call will fail with -ENODEV. + */ + buf->state = UVC_BUF_STATE_ERROR; + vb2_buffer_done(&buf->buf, VB2_BUF_STATE_ERROR); + } + + spin_unlock_irqrestore(&queue->irqlock, flags); +} + +static struct vb2_ops uvc_queue_qops = { + .queue_setup = uvc_queue_setup, + .buf_prepare = uvc_buffer_prepare, + .buf_queue = uvc_buffer_queue, +}; + +static int uvc_queue_init(struct uvc_video_queue *queue, + enum v4l2_buf_type type) +{ + int ret; + + queue->queue.type = type; + queue->queue.io_modes = VB2_MMAP | VB2_USERPTR; + queue->queue.drv_priv = queue; + queue->queue.buf_struct_size = sizeof(struct uvc_buffer); + queue->queue.ops = &uvc_queue_qops; + queue->queue.mem_ops = &vb2_vmalloc_memops; + ret = vb2_queue_init(&queue->queue); + if (ret) + return ret; + + mutex_init(&queue->mutex); + spin_lock_init(&queue->irqlock); + INIT_LIST_HEAD(&queue->irqqueue); + queue->flags = 0; + + return 0; +} + +/* + * Free the video buffers. + */ +static void uvc_free_buffers(struct uvc_video_queue *queue) +{ + mutex_lock(&queue->mutex); + vb2_queue_release(&queue->queue); + mutex_unlock(&queue->mutex); +} + +/* + * Allocate the video buffers. + */ +static int uvc_alloc_buffers(struct uvc_video_queue *queue, + struct v4l2_requestbuffers *rb) +{ + int ret; + + mutex_lock(&queue->mutex); + ret = vb2_reqbufs(&queue->queue, rb); + mutex_unlock(&queue->mutex); + + return ret ? ret : rb->count; +} + +static int uvc_query_buffer(struct uvc_video_queue *queue, + struct v4l2_buffer *buf) +{ + int ret; + + mutex_lock(&queue->mutex); + ret = vb2_querybuf(&queue->queue, buf); + mutex_unlock(&queue->mutex); + + return ret; +} + +static int uvc_queue_buffer(struct uvc_video_queue *queue, + struct v4l2_buffer *buf) +{ + unsigned long flags; + int ret; + + mutex_lock(&queue->mutex); + ret = vb2_qbuf(&queue->queue, buf); + if (ret < 0) + goto done; + + spin_lock_irqsave(&queue->irqlock, flags); + ret = (queue->flags & UVC_QUEUE_PAUSED) != 0; + queue->flags &= ~UVC_QUEUE_PAUSED; + spin_unlock_irqrestore(&queue->irqlock, flags); + +done: + mutex_unlock(&queue->mutex); + return ret; +} + +/* + * Dequeue a video buffer. If nonblocking is false, block until a buffer is + * available. + */ +static int uvc_dequeue_buffer(struct uvc_video_queue *queue, + struct v4l2_buffer *buf, int nonblocking) +{ + int ret; + + mutex_lock(&queue->mutex); + ret = vb2_dqbuf(&queue->queue, buf, nonblocking); + mutex_unlock(&queue->mutex); + + return ret; +} + +/* + * Poll the video queue. + * + * This function implements video queue polling and is intended to be used by + * the device poll handler. + */ +static unsigned int uvc_queue_poll(struct uvc_video_queue *queue, + struct file *file, poll_table *wait) +{ + unsigned int ret; + + mutex_lock(&queue->mutex); + ret = vb2_poll(&queue->queue, file, wait); + mutex_unlock(&queue->mutex); + + return ret; +} + +static int uvc_queue_mmap(struct uvc_video_queue *queue, + struct vm_area_struct *vma) +{ + int ret; + + mutex_lock(&queue->mutex); + ret = vb2_mmap(&queue->queue, vma); + mutex_unlock(&queue->mutex); + + return ret; +} + +#ifndef CONFIG_MMU +/* + * Get unmapped area. + * + * NO-MMU arch need this function to make mmap() work correctly. + */ +static unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue, + unsigned long pgoff) +{ + unsigned long ret; + + mutex_lock(&queue->mutex); + ret = vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0); + mutex_unlock(&queue->mutex); + return ret; +} +#endif + +/* + * Cancel the video buffers queue. + * + * Cancelling the queue marks all buffers on the irq queue as erroneous, + * wakes them up and removes them from the queue. + * + * If the disconnect parameter is set, further calls to uvc_queue_buffer will + * fail with -ENODEV. + * + * This function acquires the irq spinlock and can be called from interrupt + * context. + */ +static void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect) +{ + struct uvc_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&queue->irqlock, flags); + while (!list_empty(&queue->irqqueue)) { + buf = list_first_entry(&queue->irqqueue, struct uvc_buffer, + queue); + list_del(&buf->queue); + buf->state = UVC_BUF_STATE_ERROR; + vb2_buffer_done(&buf->buf, VB2_BUF_STATE_ERROR); + } + /* This must be protected by the irqlock spinlock to avoid race + * conditions between uvc_queue_buffer and the disconnection event that + * could result in an interruptible wait in uvc_dequeue_buffer. Do not + * blindly replace this logic by checking for the UVC_DEV_DISCONNECTED + * state outside the queue code. + */ + if (disconnect) + queue->flags |= UVC_QUEUE_DISCONNECTED; + spin_unlock_irqrestore(&queue->irqlock, flags); +} + +/* + * Enable or disable the video buffers queue. + * + * The queue must be enabled before starting video acquisition and must be + * disabled after stopping it. This ensures that the video buffers queue + * state can be properly initialized before buffers are accessed from the + * interrupt handler. + * + * Enabling the video queue initializes parameters (such as sequence number, + * sync pattern, ...). If the queue is already enabled, return -EBUSY. + * + * Disabling the video queue cancels the queue and removes all buffers from + * the main queue. + * + * This function can't be called from interrupt context. Use + * uvc_queue_cancel() instead. + */ +static int uvc_queue_enable(struct uvc_video_queue *queue, int enable) +{ + unsigned long flags; + int ret = 0; + + mutex_lock(&queue->mutex); + if (enable) { + ret = vb2_streamon(&queue->queue, queue->queue.type); + if (ret < 0) + goto done; + + queue->sequence = 0; + queue->buf_used = 0; + } else { + ret = vb2_streamoff(&queue->queue, queue->queue.type); + if (ret < 0) + goto done; + + spin_lock_irqsave(&queue->irqlock, flags); + INIT_LIST_HEAD(&queue->irqqueue); + + /* + * FIXME: We need to clear the DISCONNECTED flag to ensure that + * applications will be able to queue buffers for the next + * streaming run. However, clearing it here doesn't guarantee + * that the device will be reconnected in the meantime. + */ + queue->flags &= ~UVC_QUEUE_DISCONNECTED; + spin_unlock_irqrestore(&queue->irqlock, flags); + } + +done: + mutex_unlock(&queue->mutex); + return ret; +} + +/* called with &queue_irqlock held.. */ +static struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue, + struct uvc_buffer *buf) +{ + struct uvc_buffer *nextbuf; + + if ((queue->flags & UVC_QUEUE_DROP_INCOMPLETE) && + buf->length != buf->bytesused) { + buf->state = UVC_BUF_STATE_QUEUED; + vb2_set_plane_payload(&buf->buf, 0, 0); + return buf; + } + + list_del(&buf->queue); + if (!list_empty(&queue->irqqueue)) + nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer, + queue); + else + nextbuf = NULL; + + /* + * FIXME: with videobuf2, the sequence number or timestamp fields + * are valid only for video capture devices and the UVC gadget usually + * is a video output device. Keeping these until the specs are clear on + * this aspect. + */ + buf->buf.v4l2_buf.sequence = queue->sequence++; + do_gettimeofday(&buf->buf.v4l2_buf.timestamp); + + vb2_set_plane_payload(&buf->buf, 0, buf->bytesused); + vb2_buffer_done(&buf->buf, VB2_BUF_STATE_DONE); + + return nextbuf; +} + +static struct uvc_buffer *uvc_queue_head(struct uvc_video_queue *queue) +{ + struct uvc_buffer *buf = NULL; + + if (!list_empty(&queue->irqqueue)) + buf = list_first_entry(&queue->irqqueue, struct uvc_buffer, + queue); + else + queue->flags |= UVC_QUEUE_PAUSED; + + return buf; +} + diff --git a/drivers/usb/gadget/uvc_queue.h b/drivers/usb/gadget/uvc_queue.h new file mode 100644 index 00000000..8e76ce98 --- /dev/null +++ b/drivers/usb/gadget/uvc_queue.h @@ -0,0 +1,63 @@ +#ifndef _UVC_QUEUE_H_ +#define _UVC_QUEUE_H_ + +#ifdef __KERNEL__ + +#include +#include +#include +#include + +/* Maximum frame size in bytes, for sanity checking. */ +#define UVC_MAX_FRAME_SIZE (16*1024*1024) +/* Maximum number of video buffers. */ +#define UVC_MAX_VIDEO_BUFFERS 32 + +/* ------------------------------------------------------------------------ + * Structures. + */ + +enum uvc_buffer_state { + UVC_BUF_STATE_IDLE = 0, + UVC_BUF_STATE_QUEUED = 1, + UVC_BUF_STATE_ACTIVE = 2, + UVC_BUF_STATE_DONE = 3, + UVC_BUF_STATE_ERROR = 4, +}; + +struct uvc_buffer { + struct vb2_buffer buf; + struct list_head queue; + + enum uvc_buffer_state state; + void *mem; + unsigned int length; + unsigned int bytesused; +}; + +#define UVC_QUEUE_DISCONNECTED (1 << 0) +#define UVC_QUEUE_DROP_INCOMPLETE (1 << 1) +#define UVC_QUEUE_PAUSED (1 << 2) + +struct uvc_video_queue { + struct vb2_queue queue; + struct mutex mutex; /* Protects queue */ + + unsigned int flags; + __u32 sequence; + + unsigned int buf_used; + + spinlock_t irqlock; /* Protects flags and irqqueue */ + struct list_head irqqueue; +}; + +static inline int uvc_queue_streaming(struct uvc_video_queue *queue) +{ + return vb2_is_streaming(&queue->queue); +} + +#endif /* __KERNEL__ */ + +#endif /* _UVC_QUEUE_H_ */ + diff --git a/drivers/usb/gadget/uvc_v4l2.c b/drivers/usb/gadget/uvc_v4l2.c new file mode 100644 index 00000000..ad48e811 --- /dev/null +++ b/drivers/usb/gadget/uvc_v4l2.c @@ -0,0 +1,365 @@ +/* + * uvc_v4l2.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "uvc.h" +#include "uvc_queue.h" + +/* -------------------------------------------------------------------------- + * Requests handling + */ + +static int +uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data) +{ + struct usb_composite_dev *cdev = uvc->func.config->cdev; + struct usb_request *req = uvc->control_req; + + if (data->length < 0) + return usb_ep_set_halt(cdev->gadget->ep0); + + req->length = min_t(unsigned int, uvc->event_length, data->length); + req->zero = data->length < uvc->event_length; + + memcpy(req->buf, data->data, req->length); + + return usb_ep_queue(cdev->gadget->ep0, req, GFP_KERNEL); +} + +/* -------------------------------------------------------------------------- + * V4L2 + */ + +struct uvc_format +{ + u8 bpp; + u32 fcc; +}; + +static struct uvc_format uvc_formats[] = { + { 16, V4L2_PIX_FMT_YUYV }, + { 0, V4L2_PIX_FMT_MJPEG }, +}; + +static int +uvc_v4l2_get_format(struct uvc_video *video, struct v4l2_format *fmt) +{ + fmt->fmt.pix.pixelformat = video->fcc; + fmt->fmt.pix.width = video->width; + fmt->fmt.pix.height = video->height; + fmt->fmt.pix.field = V4L2_FIELD_NONE; + fmt->fmt.pix.bytesperline = video->bpp * video->width / 8; + fmt->fmt.pix.sizeimage = video->imagesize; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + fmt->fmt.pix.priv = 0; + + return 0; +} + +static int +uvc_v4l2_set_format(struct uvc_video *video, struct v4l2_format *fmt) +{ + struct uvc_format *format; + unsigned int imagesize; + unsigned int bpl; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(uvc_formats); ++i) { + format = &uvc_formats[i]; + if (format->fcc == fmt->fmt.pix.pixelformat) + break; + } + + if (i == ARRAY_SIZE(uvc_formats)) { + printk(KERN_INFO "Unsupported format 0x%08x.\n", + fmt->fmt.pix.pixelformat); + return -EINVAL; + } + + bpl = format->bpp * fmt->fmt.pix.width / 8; + imagesize = bpl ? bpl * fmt->fmt.pix.height : fmt->fmt.pix.sizeimage; + + video->fcc = format->fcc; + video->bpp = format->bpp; + video->width = fmt->fmt.pix.width; + video->height = fmt->fmt.pix.height; + video->imagesize = imagesize; + + fmt->fmt.pix.field = V4L2_FIELD_NONE; + fmt->fmt.pix.bytesperline = bpl; + fmt->fmt.pix.sizeimage = imagesize; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + fmt->fmt.pix.priv = 0; + + return 0; +} + +static int +uvc_v4l2_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_file_handle *handle; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (handle == NULL) + return -ENOMEM; + + v4l2_fh_init(&handle->vfh, vdev); + v4l2_fh_add(&handle->vfh); + + handle->device = &uvc->video; + file->private_data = &handle->vfh; + + uvc_function_connect(uvc); + return 0; +} + +static int +uvc_v4l2_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data); + struct uvc_video *video = handle->device; + + uvc_function_disconnect(uvc); + + uvc_video_enable(video, 0); + uvc_free_buffers(&video->queue); + + file->private_data = NULL; + v4l2_fh_del(&handle->vfh); + v4l2_fh_exit(&handle->vfh); + kfree(handle); + + return 0; +} + +static long +uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data); + struct usb_composite_dev *cdev = uvc->func.config->cdev; + struct uvc_video *video = &uvc->video; + int ret = 0; + + switch (cmd) { + /* Query capabilities */ + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap, 0, sizeof *cap); + strlcpy(cap->driver, "g_uvc", sizeof(cap->driver)); + strlcpy(cap->card, cdev->gadget->name, sizeof(cap->card)); + strlcpy(cap->bus_info, dev_name(&cdev->gadget->dev), + sizeof cap->bus_info); + cap->version = DRIVER_VERSION_NUMBER; + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + break; + } + + /* Get & Set format */ + case VIDIOC_G_FMT: + { + struct v4l2_format *fmt = arg; + + if (fmt->type != video->queue.queue.type) + return -EINVAL; + + return uvc_v4l2_get_format(video, fmt); + } + + case VIDIOC_S_FMT: + { + struct v4l2_format *fmt = arg; + + if (fmt->type != video->queue.queue.type) + return -EINVAL; + + return uvc_v4l2_set_format(video, fmt); + } + + /* Buffers & streaming */ + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *rb = arg; + + if (rb->type != video->queue.queue.type) + return -EINVAL; + + ret = uvc_alloc_buffers(&video->queue, rb); + if (ret < 0) + return ret; + + ret = 0; + break; + } + + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + + return uvc_query_buffer(&video->queue, buf); + } + + case VIDIOC_QBUF: + if ((ret = uvc_queue_buffer(&video->queue, arg)) < 0) + return ret; + + return uvc_video_pump(video); + + case VIDIOC_DQBUF: + return uvc_dequeue_buffer(&video->queue, arg, + file->f_flags & O_NONBLOCK); + + case VIDIOC_STREAMON: + { + int *type = arg; + + if (*type != video->queue.queue.type) + return -EINVAL; + + /* Enable UVC video. */ + ret = uvc_video_enable(video, 1); + if (ret < 0) + return ret; + + /* + * Complete the alternate setting selection setup phase now that + * userspace is ready to provide video frames. + */ + uvc_function_setup_continue(uvc); + uvc->state = UVC_STATE_STREAMING; + + return 0; + } + + case VIDIOC_STREAMOFF: + { + int *type = arg; + + if (*type != video->queue.queue.type) + return -EINVAL; + + return uvc_video_enable(video, 0); + } + + /* Events */ + case VIDIOC_DQEVENT: + { + struct v4l2_event *event = arg; + + ret = v4l2_event_dequeue(&handle->vfh, event, + file->f_flags & O_NONBLOCK); + if (ret == 0 && event->type == UVC_EVENT_SETUP) { + struct uvc_event *uvc_event = (void *)&event->u.data; + + /* Tell the complete callback to generate an event for + * the next request that will be enqueued by + * uvc_event_write. + */ + uvc->event_setup_out = + !(uvc_event->req.bRequestType & USB_DIR_IN); + uvc->event_length = uvc_event->req.wLength; + } + + return ret; + } + + case VIDIOC_SUBSCRIBE_EVENT: + { + struct v4l2_event_subscription *sub = arg; + + if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST) + return -EINVAL; + + return v4l2_event_subscribe(&handle->vfh, arg, 2, NULL); + } + + case VIDIOC_UNSUBSCRIBE_EVENT: + return v4l2_event_unsubscribe(&handle->vfh, arg); + + case UVCIOC_SEND_RESPONSE: + ret = uvc_send_response(uvc, arg); + break; + + default: + return -ENOIOCTLCMD; + } + + return ret; +} + +static long +uvc_v4l2_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl); +} + +static int +uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + + return uvc_queue_mmap(&uvc->video.queue, vma); +} + +static unsigned int +uvc_v4l2_poll(struct file *file, poll_table *wait) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + + return uvc_queue_poll(&uvc->video.queue, file, wait); +} + +#ifndef CONFIG_MMU +static unsigned long uvc_v4l2_get_unmapped_area(struct file *file, + unsigned long addr, unsigned long len, unsigned long pgoff, + unsigned long flags) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + + return uvc_queue_get_unmapped_area(&uvc->video.queue, pgoff); +} +#endif + +static struct v4l2_file_operations uvc_v4l2_fops = { + .owner = THIS_MODULE, + .open = uvc_v4l2_open, + .release = uvc_v4l2_release, + .ioctl = uvc_v4l2_ioctl, + .mmap = uvc_v4l2_mmap, + .poll = uvc_v4l2_poll, +#ifndef CONFIG_MMU + .get_unmapped_area = uvc_v4l2_get_unmapped_area, +#endif +}; + diff --git a/drivers/usb/gadget/uvc_video.c b/drivers/usb/gadget/uvc_video.c new file mode 100644 index 00000000..71e896d4 --- /dev/null +++ b/drivers/usb/gadget/uvc_video.c @@ -0,0 +1,394 @@ +/* + * uvc_video.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include + +#include "uvc.h" +#include "uvc_queue.h" + +/* -------------------------------------------------------------------------- + * Video codecs + */ + +static int +uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf, + u8 *data, int len) +{ + data[0] = 2; + data[1] = UVC_STREAM_EOH | video->fid; + + if (buf->bytesused - video->queue.buf_used <= len - 2) + data[1] |= UVC_STREAM_EOF; + + return 2; +} + +static int +uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf, + u8 *data, int len) +{ + struct uvc_video_queue *queue = &video->queue; + unsigned int nbytes; + void *mem; + + /* Copy video data to the USB buffer. */ + mem = buf->mem + queue->buf_used; + nbytes = min((unsigned int)len, buf->bytesused - queue->buf_used); + + memcpy(data, mem, nbytes); + queue->buf_used += nbytes; + + return nbytes; +} + +static void +uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video, + struct uvc_buffer *buf) +{ + void *mem = req->buf; + int len = video->req_size; + int ret; + + /* Add a header at the beginning of the payload. */ + if (video->payload_size == 0) { + ret = uvc_video_encode_header(video, buf, mem, len); + video->payload_size += ret; + mem += ret; + len -= ret; + } + + /* Process video data. */ + len = min((int)(video->max_payload_size - video->payload_size), len); + ret = uvc_video_encode_data(video, buf, mem, len); + + video->payload_size += ret; + len -= ret; + + req->length = video->req_size - len; + req->zero = video->payload_size == video->max_payload_size; + + if (buf->bytesused == video->queue.buf_used) { + video->queue.buf_used = 0; + buf->state = UVC_BUF_STATE_DONE; + uvc_queue_next_buffer(&video->queue, buf); + video->fid ^= UVC_STREAM_FID; + + video->payload_size = 0; + } + + if (video->payload_size == video->max_payload_size || + buf->bytesused == video->queue.buf_used) + video->payload_size = 0; +} + +static void +uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, + struct uvc_buffer *buf) +{ + void *mem = req->buf; + int len = video->req_size; + int ret; + + /* Add the header. */ + ret = uvc_video_encode_header(video, buf, mem, len); + mem += ret; + len -= ret; + + /* Process video data. */ + ret = uvc_video_encode_data(video, buf, mem, len); + len -= ret; + + req->length = video->req_size - len; + + if (buf->bytesused == video->queue.buf_used) { + video->queue.buf_used = 0; + buf->state = UVC_BUF_STATE_DONE; + uvc_queue_next_buffer(&video->queue, buf); + video->fid ^= UVC_STREAM_FID; + } +} + +/* -------------------------------------------------------------------------- + * Request handling + */ + +/* + * I somehow feel that synchronisation won't be easy to achieve here. We have + * three events that control USB requests submission: + * + * - USB request completion: the completion handler will resubmit the request + * if a video buffer is available. + * + * - USB interface setting selection: in response to a SET_INTERFACE request, + * the handler will start streaming if a video buffer is available and if + * video is not currently streaming. + * + * - V4L2 buffer queueing: the driver will start streaming if video is not + * currently streaming. + * + * Race conditions between those 3 events might lead to deadlocks or other + * nasty side effects. + * + * The "video currently streaming" condition can't be detected by the irqqueue + * being empty, as a request can still be in flight. A separate "queue paused" + * flag is thus needed. + * + * The paused flag will be set when we try to retrieve the irqqueue head if the + * queue is empty, and cleared when we queue a buffer. + * + * The USB request completion handler will get the buffer at the irqqueue head + * under protection of the queue spinlock. If the queue is empty, the streaming + * paused flag will be set. Right after releasing the spinlock a userspace + * application can queue a buffer. The flag will then cleared, and the ioctl + * handler will restart the video stream. + */ +static void +uvc_video_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct uvc_video *video = req->context; + struct uvc_video_queue *queue = &video->queue; + struct uvc_buffer *buf; + unsigned long flags; + int ret; + + switch (req->status) { + case 0: + break; + + case -ESHUTDOWN: /* disconnect from host. */ + printk(KERN_INFO "VS request cancelled.\n"); + uvc_queue_cancel(queue, 1); + goto requeue; + + default: + printk(KERN_INFO "VS request completed with status %d.\n", + req->status); + uvc_queue_cancel(queue, 0); + goto requeue; + } + + spin_lock_irqsave(&video->queue.irqlock, flags); + buf = uvc_queue_head(&video->queue); + if (buf == NULL) { + spin_unlock_irqrestore(&video->queue.irqlock, flags); + goto requeue; + } + + video->encode(req, video, buf); + + if ((ret = usb_ep_queue(ep, req, GFP_ATOMIC)) < 0) { + printk(KERN_INFO "Failed to queue request (%d).\n", ret); + usb_ep_set_halt(ep); + spin_unlock_irqrestore(&video->queue.irqlock, flags); + goto requeue; + } + spin_unlock_irqrestore(&video->queue.irqlock, flags); + + return; + +requeue: + spin_lock_irqsave(&video->req_lock, flags); + list_add_tail(&req->list, &video->req_free); + spin_unlock_irqrestore(&video->req_lock, flags); +} + +static int +uvc_video_free_requests(struct uvc_video *video) +{ + unsigned int i; + + for (i = 0; i < UVC_NUM_REQUESTS; ++i) { + if (video->req[i]) { + usb_ep_free_request(video->ep, video->req[i]); + video->req[i] = NULL; + } + + if (video->req_buffer[i]) { + kfree(video->req_buffer[i]); + video->req_buffer[i] = NULL; + } + } + + INIT_LIST_HEAD(&video->req_free); + video->req_size = 0; + return 0; +} + +static int +uvc_video_alloc_requests(struct uvc_video *video) +{ + unsigned int req_size; + unsigned int i; + int ret = -ENOMEM; + + BUG_ON(video->req_size); + + req_size = video->ep->maxpacket + * max_t(unsigned int, video->ep->maxburst, 1) + * (video->ep->mult + 1); + + for (i = 0; i < UVC_NUM_REQUESTS; ++i) { + video->req_buffer[i] = kmalloc(req_size, GFP_KERNEL); + if (video->req_buffer[i] == NULL) + goto error; + + video->req[i] = usb_ep_alloc_request(video->ep, GFP_KERNEL); + if (video->req[i] == NULL) + goto error; + + video->req[i]->buf = video->req_buffer[i]; + video->req[i]->length = 0; + video->req[i]->complete = uvc_video_complete; + video->req[i]->context = video; + + list_add_tail(&video->req[i]->list, &video->req_free); + } + + video->req_size = req_size; + + return 0; + +error: + uvc_video_free_requests(video); + return ret; +} + +/* -------------------------------------------------------------------------- + * Video streaming + */ + +/* + * uvc_video_pump - Pump video data into the USB requests + * + * This function fills the available USB requests (listed in req_free) with + * video data from the queued buffers. + */ +static int +uvc_video_pump(struct uvc_video *video) +{ + struct usb_request *req; + struct uvc_buffer *buf; + unsigned long flags; + int ret; + + /* FIXME TODO Race between uvc_video_pump and requests completion + * handler ??? + */ + + while (1) { + /* Retrieve the first available USB request, protected by the + * request lock. + */ + spin_lock_irqsave(&video->req_lock, flags); + if (list_empty(&video->req_free)) { + spin_unlock_irqrestore(&video->req_lock, flags); + return 0; + } + req = list_first_entry(&video->req_free, struct usb_request, + list); + list_del(&req->list); + spin_unlock_irqrestore(&video->req_lock, flags); + + /* Retrieve the first available video buffer and fill the + * request, protected by the video queue irqlock. + */ + spin_lock_irqsave(&video->queue.irqlock, flags); + buf = uvc_queue_head(&video->queue); + if (buf == NULL) { + spin_unlock_irqrestore(&video->queue.irqlock, flags); + break; + } + + video->encode(req, video, buf); + + /* Queue the USB request */ + ret = usb_ep_queue(video->ep, req, GFP_ATOMIC); + if (ret < 0) { + printk(KERN_INFO "Failed to queue request (%d)\n", ret); + usb_ep_set_halt(video->ep); + spin_unlock_irqrestore(&video->queue.irqlock, flags); + break; + } + spin_unlock_irqrestore(&video->queue.irqlock, flags); + } + + spin_lock_irqsave(&video->req_lock, flags); + list_add_tail(&req->list, &video->req_free); + spin_unlock_irqrestore(&video->req_lock, flags); + return 0; +} + +/* + * Enable or disable the video stream. + */ +static int +uvc_video_enable(struct uvc_video *video, int enable) +{ + unsigned int i; + int ret; + + if (video->ep == NULL) { + printk(KERN_INFO "Video enable failed, device is " + "uninitialized.\n"); + return -ENODEV; + } + + if (!enable) { + for (i = 0; i < UVC_NUM_REQUESTS; ++i) + usb_ep_dequeue(video->ep, video->req[i]); + + uvc_video_free_requests(video); + uvc_queue_enable(&video->queue, 0); + return 0; + } + + if ((ret = uvc_queue_enable(&video->queue, 1)) < 0) + return ret; + + if ((ret = uvc_video_alloc_requests(video)) < 0) + return ret; + + if (video->max_payload_size) { + video->encode = uvc_video_encode_bulk; + video->payload_size = 0; + } else + video->encode = uvc_video_encode_isoc; + + return uvc_video_pump(video); +} + +/* + * Initialize the UVC video stream. + */ +static int +uvc_video_init(struct uvc_video *video) +{ + INIT_LIST_HEAD(&video->req_free); + spin_lock_init(&video->req_lock); + + video->fcc = V4L2_PIX_FMT_YUYV; + video->bpp = 16; + video->width = 320; + video->height = 240; + video->imagesize = 320 * 240 * 2; + + /* Initialize the video buffers queue. */ + uvc_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT); + return 0; +} + diff --git a/drivers/usb/gadget/webcam.c b/drivers/usb/gadget/webcam.c new file mode 100644 index 00000000..8cef1e65 --- /dev/null +++ b/drivers/usb/gadget/webcam.c @@ -0,0 +1,412 @@ +/* + * webcam.c -- USB webcam gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include + +#include "f_uvc.h" + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "uvc_queue.c" +#include "uvc_video.c" +#include "uvc_v4l2.c" +#include "f_uvc.c" + +USB_GADGET_COMPOSITE_OPTIONS(); +/* -------------------------------------------------------------------------- + * Device descriptor + */ + +#define WEBCAM_VENDOR_ID 0x1d6b /* Linux Foundation */ +#define WEBCAM_PRODUCT_ID 0x0102 /* Webcam A/V gadget */ +#define WEBCAM_DEVICE_BCD 0x0010 /* 0.10 */ + +static char webcam_vendor_label[] = "Linux Foundation"; +static char webcam_product_label[] = "Webcam gadget"; +static char webcam_config_label[] = "Video"; + +/* string IDs are assigned dynamically */ + +#define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX + +static struct usb_string webcam_strings[] = { + [USB_GADGET_MANUFACTURER_IDX].s = webcam_vendor_label, + [USB_GADGET_PRODUCT_IDX].s = webcam_product_label, + [USB_GADGET_SERIAL_IDX].s = "", + [STRING_DESCRIPTION_IDX].s = webcam_config_label, + { } +}; + +static struct usb_gadget_strings webcam_stringtab = { + .language = 0x0409, /* en-us */ + .strings = webcam_strings, +}; + +static struct usb_gadget_strings *webcam_device_strings[] = { + &webcam_stringtab, + NULL, +}; + +static struct usb_device_descriptor webcam_device_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_MISC, + .bDeviceSubClass = 0x02, + .bDeviceProtocol = 0x01, + .bMaxPacketSize0 = 0, /* dynamic */ + .idVendor = cpu_to_le16(WEBCAM_VENDOR_ID), + .idProduct = cpu_to_le16(WEBCAM_PRODUCT_ID), + .bcdDevice = cpu_to_le16(WEBCAM_DEVICE_BCD), + .iManufacturer = 0, /* dynamic */ + .iProduct = 0, /* dynamic */ + .iSerialNumber = 0, /* dynamic */ + .bNumConfigurations = 0, /* dynamic */ +}; + +DECLARE_UVC_HEADER_DESCRIPTOR(1); + +static const struct UVC_HEADER_DESCRIPTOR(1) uvc_control_header = { + .bLength = UVC_DT_HEADER_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VC_HEADER, + .bcdUVC = cpu_to_le16(0x0100), + .wTotalLength = 0, /* dynamic */ + .dwClockFrequency = cpu_to_le32(48000000), + .bInCollection = 0, /* dynamic */ + .baInterfaceNr[0] = 0, /* dynamic */ +}; + +static const struct uvc_camera_terminal_descriptor uvc_camera_terminal = { + .bLength = UVC_DT_CAMERA_TERMINAL_SIZE(3), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VC_INPUT_TERMINAL, + .bTerminalID = 1, + .wTerminalType = cpu_to_le16(0x0201), + .bAssocTerminal = 0, + .iTerminal = 0, + .wObjectiveFocalLengthMin = cpu_to_le16(0), + .wObjectiveFocalLengthMax = cpu_to_le16(0), + .wOcularFocalLength = cpu_to_le16(0), + .bControlSize = 3, + .bmControls[0] = 2, + .bmControls[1] = 0, + .bmControls[2] = 0, +}; + +static const struct uvc_processing_unit_descriptor uvc_processing = { + .bLength = UVC_DT_PROCESSING_UNIT_SIZE(2), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VC_PROCESSING_UNIT, + .bUnitID = 2, + .bSourceID = 1, + .wMaxMultiplier = cpu_to_le16(16*1024), + .bControlSize = 2, + .bmControls[0] = 1, + .bmControls[1] = 0, + .iProcessing = 0, +}; + +static const struct uvc_output_terminal_descriptor uvc_output_terminal = { + .bLength = UVC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VC_OUTPUT_TERMINAL, + .bTerminalID = 3, + .wTerminalType = cpu_to_le16(0x0101), + .bAssocTerminal = 0, + .bSourceID = 2, + .iTerminal = 0, +}; + +DECLARE_UVC_INPUT_HEADER_DESCRIPTOR(1, 2); + +static const struct UVC_INPUT_HEADER_DESCRIPTOR(1, 2) uvc_input_header = { + .bLength = UVC_DT_INPUT_HEADER_SIZE(1, 2), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_INPUT_HEADER, + .bNumFormats = 2, + .wTotalLength = 0, /* dynamic */ + .bEndpointAddress = 0, /* dynamic */ + .bmInfo = 0, + .bTerminalLink = 3, + .bStillCaptureMethod = 0, + .bTriggerSupport = 0, + .bTriggerUsage = 0, + .bControlSize = 1, + .bmaControls[0][0] = 0, + .bmaControls[1][0] = 4, +}; + +static const struct uvc_format_uncompressed uvc_format_yuv = { + .bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_FORMAT_UNCOMPRESSED, + .bFormatIndex = 1, + .bNumFrameDescriptors = 2, + .guidFormat = + { 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}, + .bBitsPerPixel = 16, + .bDefaultFrameIndex = 1, + .bAspectRatioX = 0, + .bAspectRatioY = 0, + .bmInterfaceFlags = 0, + .bCopyProtect = 0, +}; + +DECLARE_UVC_FRAME_UNCOMPRESSED(1); +DECLARE_UVC_FRAME_UNCOMPRESSED(3); + +static const struct UVC_FRAME_UNCOMPRESSED(3) uvc_frame_yuv_360p = { + .bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(3), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_FRAME_UNCOMPRESSED, + .bFrameIndex = 1, + .bmCapabilities = 0, + .wWidth = cpu_to_le16(640), + .wHeight = cpu_to_le16(360), + .dwMinBitRate = cpu_to_le32(18432000), + .dwMaxBitRate = cpu_to_le32(55296000), + .dwMaxVideoFrameBufferSize = cpu_to_le32(460800), + .dwDefaultFrameInterval = cpu_to_le32(666666), + .bFrameIntervalType = 3, + .dwFrameInterval[0] = cpu_to_le32(666666), + .dwFrameInterval[1] = cpu_to_le32(1000000), + .dwFrameInterval[2] = cpu_to_le32(5000000), +}; + +static const struct UVC_FRAME_UNCOMPRESSED(1) uvc_frame_yuv_720p = { + .bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_FRAME_UNCOMPRESSED, + .bFrameIndex = 2, + .bmCapabilities = 0, + .wWidth = cpu_to_le16(1280), + .wHeight = cpu_to_le16(720), + .dwMinBitRate = cpu_to_le32(29491200), + .dwMaxBitRate = cpu_to_le32(29491200), + .dwMaxVideoFrameBufferSize = cpu_to_le32(1843200), + .dwDefaultFrameInterval = cpu_to_le32(5000000), + .bFrameIntervalType = 1, + .dwFrameInterval[0] = cpu_to_le32(5000000), +}; + +static const struct uvc_format_mjpeg uvc_format_mjpg = { + .bLength = UVC_DT_FORMAT_MJPEG_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_FORMAT_MJPEG, + .bFormatIndex = 2, + .bNumFrameDescriptors = 2, + .bmFlags = 0, + .bDefaultFrameIndex = 1, + .bAspectRatioX = 0, + .bAspectRatioY = 0, + .bmInterfaceFlags = 0, + .bCopyProtect = 0, +}; + +DECLARE_UVC_FRAME_MJPEG(1); +DECLARE_UVC_FRAME_MJPEG(3); + +static const struct UVC_FRAME_MJPEG(3) uvc_frame_mjpg_360p = { + .bLength = UVC_DT_FRAME_MJPEG_SIZE(3), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_FRAME_MJPEG, + .bFrameIndex = 1, + .bmCapabilities = 0, + .wWidth = cpu_to_le16(640), + .wHeight = cpu_to_le16(360), + .dwMinBitRate = cpu_to_le32(18432000), + .dwMaxBitRate = cpu_to_le32(55296000), + .dwMaxVideoFrameBufferSize = cpu_to_le32(460800), + .dwDefaultFrameInterval = cpu_to_le32(666666), + .bFrameIntervalType = 3, + .dwFrameInterval[0] = cpu_to_le32(666666), + .dwFrameInterval[1] = cpu_to_le32(1000000), + .dwFrameInterval[2] = cpu_to_le32(5000000), +}; + +static const struct UVC_FRAME_MJPEG(1) uvc_frame_mjpg_720p = { + .bLength = UVC_DT_FRAME_MJPEG_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_FRAME_MJPEG, + .bFrameIndex = 2, + .bmCapabilities = 0, + .wWidth = cpu_to_le16(1280), + .wHeight = cpu_to_le16(720), + .dwMinBitRate = cpu_to_le32(29491200), + .dwMaxBitRate = cpu_to_le32(29491200), + .dwMaxVideoFrameBufferSize = cpu_to_le32(1843200), + .dwDefaultFrameInterval = cpu_to_le32(5000000), + .bFrameIntervalType = 1, + .dwFrameInterval[0] = cpu_to_le32(5000000), +}; + +static const struct uvc_color_matching_descriptor uvc_color_matching = { + .bLength = UVC_DT_COLOR_MATCHING_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_COLORFORMAT, + .bColorPrimaries = 1, + .bTransferCharacteristics = 1, + .bMatrixCoefficients = 4, +}; + +static const struct uvc_descriptor_header * const uvc_fs_control_cls[] = { + (const struct uvc_descriptor_header *) &uvc_control_header, + (const struct uvc_descriptor_header *) &uvc_camera_terminal, + (const struct uvc_descriptor_header *) &uvc_processing, + (const struct uvc_descriptor_header *) &uvc_output_terminal, + NULL, +}; + +static const struct uvc_descriptor_header * const uvc_ss_control_cls[] = { + (const struct uvc_descriptor_header *) &uvc_control_header, + (const struct uvc_descriptor_header *) &uvc_camera_terminal, + (const struct uvc_descriptor_header *) &uvc_processing, + (const struct uvc_descriptor_header *) &uvc_output_terminal, + NULL, +}; + +static const struct uvc_descriptor_header * const uvc_fs_streaming_cls[] = { + (const struct uvc_descriptor_header *) &uvc_input_header, + (const struct uvc_descriptor_header *) &uvc_format_yuv, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, + (const struct uvc_descriptor_header *) &uvc_format_mjpg, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, + (const struct uvc_descriptor_header *) &uvc_color_matching, + NULL, +}; + +static const struct uvc_descriptor_header * const uvc_hs_streaming_cls[] = { + (const struct uvc_descriptor_header *) &uvc_input_header, + (const struct uvc_descriptor_header *) &uvc_format_yuv, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, + (const struct uvc_descriptor_header *) &uvc_format_mjpg, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, + (const struct uvc_descriptor_header *) &uvc_color_matching, + NULL, +}; + +static const struct uvc_descriptor_header * const uvc_ss_streaming_cls[] = { + (const struct uvc_descriptor_header *) &uvc_input_header, + (const struct uvc_descriptor_header *) &uvc_format_yuv, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, + (const struct uvc_descriptor_header *) &uvc_format_mjpg, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, + (const struct uvc_descriptor_header *) &uvc_color_matching, + NULL, +}; + +/* -------------------------------------------------------------------------- + * USB configuration + */ + +static int __init +webcam_config_bind(struct usb_configuration *c) +{ + return uvc_bind_config(c, uvc_fs_control_cls, uvc_ss_control_cls, + uvc_fs_streaming_cls, uvc_hs_streaming_cls, + uvc_ss_streaming_cls); +} + +static struct usb_configuration webcam_config_driver = { + .label = webcam_config_label, + .bConfigurationValue = 1, + .iConfiguration = 0, /* dynamic */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + .MaxPower = CONFIG_USB_GADGET_VBUS_DRAW, +}; + +static int /* __init_or_exit */ +webcam_unbind(struct usb_composite_dev *cdev) +{ + return 0; +} + +static int __init +webcam_bind(struct usb_composite_dev *cdev) +{ + int ret; + + /* Allocate string descriptor numbers ... note that string contents + * can be overridden by the composite_dev glue. + */ + ret = usb_string_ids_tab(cdev, webcam_strings); + if (ret < 0) + goto error; + webcam_device_descriptor.iManufacturer = + webcam_strings[USB_GADGET_MANUFACTURER_IDX].id; + webcam_device_descriptor.iProduct = + webcam_strings[USB_GADGET_PRODUCT_IDX].id; + webcam_config_driver.iConfiguration = + webcam_strings[STRING_DESCRIPTION_IDX].id; + + /* Register our configuration. */ + if ((ret = usb_add_config(cdev, &webcam_config_driver, + webcam_config_bind)) < 0) + goto error; + + usb_composite_overwrite_options(cdev, &coverwrite); + INFO(cdev, "Webcam Video Gadget\n"); + return 0; + +error: + webcam_unbind(cdev); + return ret; +} + +/* -------------------------------------------------------------------------- + * Driver + */ + +static __refdata struct usb_composite_driver webcam_driver = { + .name = "g_webcam", + .dev = &webcam_device_descriptor, + .strings = webcam_device_strings, + .max_speed = USB_SPEED_SUPER, + .bind = webcam_bind, + .unbind = webcam_unbind, +}; + +static int __init +webcam_init(void) +{ + return usb_composite_probe(&webcam_driver); +} + +static void __exit +webcam_cleanup(void) +{ + usb_composite_unregister(&webcam_driver); +} + +module_init(webcam_init); +module_exit(webcam_cleanup); + +MODULE_AUTHOR("Laurent Pinchart"); +MODULE_DESCRIPTION("Webcam Video Gadget"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1.0"); + diff --git a/drivers/usb/gadget/zero.c b/drivers/usb/gadget/zero.c new file mode 100644 index 00000000..d31814c7 --- /dev/null +++ b/drivers/usb/gadget/zero.c @@ -0,0 +1,406 @@ +/* + * zero.c -- Gadget Zero, for USB development + * + * Copyright (C) 2003-2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * Gadget Zero only needs two bulk endpoints, and is an example of how you + * can write a hardware-agnostic gadget driver running inside a USB device. + * Some hardware details are visible, but don't affect most of the driver. + * + * Use it with the Linux host/master side "usbtest" driver to get a basic + * functional test of your device-side usb stack, or with "usb-skeleton". + * + * It supports two similar configurations. One sinks whatever the usb host + * writes, and in return sources zeroes. The other loops whatever the host + * writes back, so the host can read it. + * + * Many drivers will only have one configuration, letting them be much + * simpler if they also don't support high speed operation (like this + * driver does). + * + * Why is *this* driver using two configurations, rather than setting up + * two interfaces with different functions? To help verify that multiple + * configuration infrastucture is working correctly; also, so that it can + * work with low capability USB controllers without four bulk endpoints. + */ + +/* + * driver assumes self-powered hardware, and + * has no way for users to trigger remote wakeup. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include + +#include "g_zero.h" +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +#define DRIVER_VERSION "Cinco de Mayo 2008" + +static const char longname[] = "Gadget Zero"; + +/* + * Normally the "loopback" configuration is second (index 1) so + * it's not the default. Here's where to change that order, to + * work better with hosts where config changes are problematic or + * controllers (like original superh) that only support one config. + */ +static bool loopdefault = 0; +module_param(loopdefault, bool, S_IRUGO|S_IWUSR); + +static struct usb_zero_options gzero_options = { + .isoc_interval = 4, + .isoc_maxpacket = 1024, + .bulk_buflen = 4096, + .qlen = 32, +}; + +/*-------------------------------------------------------------------------*/ + +/* Thanks to NetChip Technologies for donating this product ID. + * + * DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ +#ifndef CONFIG_USB_ZERO_HNPTEST +#define DRIVER_VENDOR_NUM 0x0525 /* NetChip */ +#define DRIVER_PRODUCT_NUM 0xa4a0 /* Linux-USB "Gadget Zero" */ +#define DEFAULT_AUTORESUME 0 +#else +#define DRIVER_VENDOR_NUM 0x1a0a /* OTG test device IDs */ +#define DRIVER_PRODUCT_NUM 0xbadd +#define DEFAULT_AUTORESUME 5 +#endif + +/* If the optional "autoresume" mode is enabled, it provides good + * functional coverage for the "USBCV" test harness from USB-IF. + * It's always set if OTG mode is enabled. + */ +unsigned autoresume = DEFAULT_AUTORESUME; +module_param(autoresume, uint, S_IRUGO); +MODULE_PARM_DESC(autoresume, "zero, or seconds before remote wakeup"); + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + + .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM), + .idProduct = cpu_to_le16(DRIVER_PRODUCT_NUM), + .bNumConfigurations = 2, +}; + +#ifdef CONFIG_USB_OTG +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; +#else +#define otg_desc NULL +#endif + +/* string IDs are assigned dynamically */ +/* default serial number takes at least two packets */ +static char serial[] = "0123456789.0123456789.0123456789"; + +#define USB_GZERO_SS_DESC (USB_GADGET_FIRST_AVAIL_IDX + 0) +#define USB_GZERO_LB_DESC (USB_GADGET_FIRST_AVAIL_IDX + 1) + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = longname, + [USB_GADGET_SERIAL_IDX].s = serial, + [USB_GZERO_SS_DESC].s = "source and sink data", + [USB_GZERO_LB_DESC].s = "loop input to output", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static struct timer_list autoresume_timer; + +static void zero_autoresume(unsigned long _c) +{ + struct usb_composite_dev *cdev = (void *)_c; + struct usb_gadget *g = cdev->gadget; + + /* unconfigured devices can't issue wakeups */ + if (!cdev->config) + return; + + /* Normally the host would be woken up for something + * more significant than just a timer firing; likely + * because of some direct user request. + */ + if (g->speed != USB_SPEED_UNKNOWN) { + int status = usb_gadget_wakeup(g); + INFO(cdev, "%s --> %d\n", __func__, status); + } +} + +static void zero_suspend(struct usb_composite_dev *cdev) +{ + if (cdev->gadget->speed == USB_SPEED_UNKNOWN) + return; + + if (autoresume) { + mod_timer(&autoresume_timer, jiffies + (HZ * autoresume)); + DBG(cdev, "suspend, wakeup in %d seconds\n", autoresume); + } else + DBG(cdev, "%s\n", __func__); +} + +static void zero_resume(struct usb_composite_dev *cdev) +{ + DBG(cdev, "%s\n", __func__); + del_timer(&autoresume_timer); +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_configuration loopback_driver = { + .label = "loopback", + .bConfigurationValue = 2, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + /* .iConfiguration = DYNAMIC */ +}; + +static struct usb_function *func_ss; +static struct usb_function_instance *func_inst_ss; + +static int ss_config_setup(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + switch (ctrl->bRequest) { + case 0x5b: + case 0x5c: + return func_ss->setup(func_ss, ctrl); + default: + return -EOPNOTSUPP; + } +} + +static struct usb_configuration sourcesink_driver = { + .label = "source/sink", + .setup = ss_config_setup, + .bConfigurationValue = 3, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + /* .iConfiguration = DYNAMIC */ +}; + +module_param_named(buflen, gzero_options.bulk_buflen, uint, 0); +module_param_named(pattern, gzero_options.pattern, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(pattern, "0 = all zeroes, 1 = mod63, 2 = none"); + +module_param_named(isoc_interval, gzero_options.isoc_interval, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(isoc_interval, "1 - 16"); + +module_param_named(isoc_maxpacket, gzero_options.isoc_maxpacket, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(isoc_maxpacket, "0 - 1023 (fs), 0 - 1024 (hs/ss)"); + +module_param_named(isoc_mult, gzero_options.isoc_mult, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(isoc_mult, "0 - 2 (hs/ss only)"); + +module_param_named(isoc_maxburst, gzero_options.isoc_maxburst, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(isoc_maxburst, "0 - 15 (ss only)"); + +static struct usb_function *func_lb; +static struct usb_function_instance *func_inst_lb; + +module_param_named(qlen, gzero_options.qlen, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(qlen, "depth of loopback queue"); + +static int __init zero_bind(struct usb_composite_dev *cdev) +{ + struct f_ss_opts *ss_opts; + struct f_lb_opts *lb_opts; + int status; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + return status; + + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + device_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id; + + setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev); + + func_inst_ss = usb_get_function_instance("SourceSink"); + if (IS_ERR(func_inst_ss)) + return PTR_ERR(func_inst_ss); + + ss_opts = container_of(func_inst_ss, struct f_ss_opts, func_inst); + ss_opts->pattern = gzero_options.pattern; + ss_opts->isoc_interval = gzero_options.isoc_interval; + ss_opts->isoc_maxpacket = gzero_options.isoc_maxpacket; + ss_opts->isoc_mult = gzero_options.isoc_mult; + ss_opts->isoc_maxburst = gzero_options.isoc_maxburst; + ss_opts->bulk_buflen = gzero_options.bulk_buflen; + + func_ss = usb_get_function(func_inst_ss); + if (IS_ERR(func_ss)) { + status = PTR_ERR(func_ss); + goto err_put_func_inst_ss; + } + + func_inst_lb = usb_get_function_instance("Loopback"); + if (IS_ERR(func_inst_lb)) { + status = PTR_ERR(func_inst_lb); + goto err_put_func_ss; + } + + lb_opts = container_of(func_inst_lb, struct f_lb_opts, func_inst); + lb_opts->bulk_buflen = gzero_options.bulk_buflen; + lb_opts->qlen = gzero_options.qlen; + + func_lb = usb_get_function(func_inst_lb); + if (IS_ERR(func_lb)) { + status = PTR_ERR(func_lb); + goto err_put_func_inst_lb; + } + + sourcesink_driver.iConfiguration = strings_dev[USB_GZERO_SS_DESC].id; + loopback_driver.iConfiguration = strings_dev[USB_GZERO_LB_DESC].id; + + /* support autoresume for remote wakeup testing */ + sourcesink_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP; + loopback_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP; + sourcesink_driver.descriptors = NULL; + loopback_driver.descriptors = NULL; + if (autoresume) { + sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + /* support OTG systems */ + if (gadget_is_otg(cdev->gadget)) { + sourcesink_driver.descriptors = otg_desc; + sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + loopback_driver.descriptors = otg_desc; + loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + /* Register primary, then secondary configuration. Note that + * SH3 only allows one config... + */ + if (loopdefault) { + usb_add_config_only(cdev, &loopback_driver); + usb_add_config_only(cdev, &sourcesink_driver); + } else { + usb_add_config_only(cdev, &sourcesink_driver); + usb_add_config_only(cdev, &loopback_driver); + } + status = usb_add_function(&sourcesink_driver, func_ss); + if (status) + goto err_conf_flb; + + usb_ep_autoconfig_reset(cdev->gadget); + status = usb_add_function(&loopback_driver, func_lb); + if (status) + goto err_conf_flb; + + usb_ep_autoconfig_reset(cdev->gadget); + usb_composite_overwrite_options(cdev, &coverwrite); + + INFO(cdev, "%s, version: " DRIVER_VERSION "\n", longname); + + return 0; + +err_conf_flb: + usb_put_function(func_lb); + func_lb = NULL; +err_put_func_inst_lb: + usb_put_function_instance(func_inst_lb); + func_inst_lb = NULL; +err_put_func_ss: + usb_put_function(func_ss); + func_ss = NULL; +err_put_func_inst_ss: + usb_put_function_instance(func_inst_ss); + func_inst_ss = NULL; + return status; +} + +static int zero_unbind(struct usb_composite_dev *cdev) +{ + del_timer_sync(&autoresume_timer); + if (!IS_ERR_OR_NULL(func_ss)) + usb_put_function(func_ss); + usb_put_function_instance(func_inst_ss); + if (!IS_ERR_OR_NULL(func_lb)) + usb_put_function(func_lb); + usb_put_function_instance(func_inst_lb); + return 0; +} + +static __refdata struct usb_composite_driver zero_driver = { + .name = "zero", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = zero_bind, + .unbind = zero_unbind, + .suspend = zero_suspend, + .resume = zero_resume, +}; + +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); + +static int __init init(void) +{ + return usb_composite_probe(&zero_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&zero_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig new file mode 100644 index 00000000..8bc8e066 --- /dev/null +++ b/drivers/usb/host/Kconfig @@ -0,0 +1,708 @@ +# +# USB Host Controller Drivers +# +comment "USB Host Controller Drivers" + +config USB_C67X00_HCD + tristate "Cypress C67x00 HCD support" + help + The Cypress C67x00 (EZ-Host/EZ-OTG) chips are dual-role + host/peripheral/OTG USB controllers. + + Enable this option to support this chip in host controller mode. + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called c67x00. + +config USB_XHCI_HCD + tristate "xHCI HCD (USB 3.0) support" + ---help--- + The eXtensible Host Controller Interface (xHCI) is standard for USB 3.0 + "SuperSpeed" host controller hardware. + + To compile this driver as a module, choose M here: the + module will be called xhci-hcd. + +if USB_XHCI_HCD + +config USB_XHCI_PLATFORM + tristate + +config USB_XHCI_HCD_DEBUGGING + bool "Debugging for the xHCI host controller" + ---help--- + Say 'Y' to turn on debugging for the xHCI host controller driver. + This will spew debugging output, even in interrupt context. + This should only be used for debugging xHCI driver bugs. + + If unsure, say N. + +endif # USB_XHCI_HCD + +config USB_EHCI_HCD + tristate "EHCI HCD (USB 2.0) support" + ---help--- + The Enhanced Host Controller Interface (EHCI) is standard for USB 2.0 + "high speed" (480 Mbit/sec, 60 Mbyte/sec) host controller hardware. + If your USB host controller supports USB 2.0, you will likely want to + configure this Host Controller Driver. + + EHCI controllers are packaged with "companion" host controllers (OHCI + or UHCI) to handle USB 1.1 devices connected to root hub ports. Ports + will connect to EHCI if the device is high speed, otherwise they + connect to a companion controller. If you configure EHCI, you should + probably configure the OHCI (for NEC and some other vendors) USB Host + Controller Driver or UHCI (for Via motherboards) Host Controller + Driver too. + + You may want to read . + + To compile this driver as a module, choose M here: the + module will be called ehci-hcd. + +config USB_EHCI_ROOT_HUB_TT + bool "Root Hub Transaction Translators" + depends on USB_EHCI_HCD || USB_CHIPIDEA_HOST + ---help--- + Some EHCI chips have vendor-specific extensions to integrate + transaction translators, so that no OHCI or UHCI companion + controller is needed. It's safe to say "y" even if your + controller doesn't support this feature. + + This supports the EHCI implementation that's originally + from ARC, and has since changed hands a few times. + +config USB_EHCI_TT_NEWSCHED + bool "Improved Transaction Translator scheduling" + depends on USB_EHCI_HCD || USB_CHIPIDEA_HOST + default y + ---help--- + This changes the periodic scheduling code to fill more of the low + and full speed bandwidth available from the Transaction Translator + (TT) in USB 2.0 hubs. Without this, only one transfer will be + issued in each microframe, significantly reducing the number of + periodic low/fullspeed transfers possible. + + If you have multiple periodic low/fullspeed devices connected to a + highspeed USB hub which is connected to a highspeed USB Host + Controller, and some of those devices will not work correctly + (possibly due to "ENOSPC" or "-28" errors), say Y. Conversely, if + you have only one such device and it doesn't work, you could try + saying N. + + If unsure, say Y. + +config USB_FSL_MPH_DR_OF + tristate + +if USB_EHCI_HCD + +config USB_EHCI_PCI + tristate + depends on PCI + default y + +config USB_EHCI_HCD_PMC_MSP + tristate "EHCI support for on-chip PMC MSP71xx USB controller" + depends on MSP_HAS_USB + default n + select USB_EHCI_BIG_ENDIAN_DESC + select USB_EHCI_BIG_ENDIAN_MMIO + ---help--- + Enables support for the onchip USB controller on the PMC_MSP7100 Family SoC's. + If unsure, say N. + +config USB_EHCI_BIG_ENDIAN_MMIO + bool + +config USB_EHCI_BIG_ENDIAN_DESC + bool + +config XPS_USB_HCD_XILINX + bool "Use Xilinx usb host EHCI controller core" + depends on (PPC32 || MICROBLAZE) + select USB_EHCI_BIG_ENDIAN_DESC + select USB_EHCI_BIG_ENDIAN_MMIO + ---help--- + Xilinx xps USB host controller core is EHCI compilant and has + transaction translator built-in. It can be configured to either + support both high speed and full speed devices, or high speed + devices only. + +config USB_EHCI_FSL + bool "Support for Freescale PPC on-chip EHCI USB controller" + depends on FSL_SOC + select USB_EHCI_ROOT_HUB_TT + select USB_FSL_MPH_DR_OF if OF + ---help--- + Variation of ARC USB block used in some Freescale chips. + +config USB_EHCI_MXC + tristate "Support for Freescale i.MX on-chip EHCI USB controller" + depends on ARCH_MXC + select USB_EHCI_ROOT_HUB_TT + ---help--- + Variation of ARC USB block used in some Freescale chips. + +config USB_EHCI_HCD_OMAP + tristate "EHCI support for OMAP3 and later chips" + depends on ARCH_OMAP + default y + ---help--- + Enables support for the on-chip EHCI controller on + OMAP3 and later chips. + If your system uses a PHY on the USB port, you will need to + enable USB_PHY and the appropriate PHY driver as well. Most + boards need the NOP_USB_XCEIV PHY driver. + +config USB_EHCI_HCD_ORION + tristate "Support for Marvell EBU on-chip EHCI USB controller" + depends on USB_EHCI_HCD && PLAT_ORION + default y + ---help--- + Enables support for the on-chip EHCI controller on Marvell's + embedded ARM SoCs, including Orion, Kirkwood, Dove, Armada XP, + Armada 370. This is different from the EHCI implementation + on Marvell's mobile PXA and MMP SoC, see "EHCI support for + Marvell PXA/MMP USB controller" for those. + +config USB_EHCI_HCD_SPEAR + tristate "Support for ST SPEAr on-chip EHCI USB controller" + depends on USB_EHCI_HCD && PLAT_SPEAR + default y + ---help--- + Enables support for the on-chip EHCI controller on + ST SPEAr chips. + +config USB_EHCI_HCD_SYNOPSYS + tristate "Support for Synopsys Host-AHB USB 2.0 controller" + depends on USB_EHCI_HCD + ---help--- + Enable support for onchip USB controllers based on DesignWare USB 2.0 + Host-AHB Controller IP from Synopsys. + +config USB_EHCI_HCD_AT91 + tristate "Support for Atmel on-chip EHCI USB controller" + depends on USB_EHCI_HCD && ARCH_AT91 + default y + ---help--- + Enables support for the on-chip EHCI controller on + Atmel chips. + +config USB_EHCI_MSM + tristate "Support for Qualcomm QSD/MSM on-chip EHCI USB controller" + depends on ARCH_MSM + depends on USB_PHY + select USB_EHCI_ROOT_HUB_TT + select USB_MSM_OTG + ---help--- + Enables support for the USB Host controller present on the + Qualcomm chipsets. Root Hub has inbuilt TT. + This driver depends on OTG driver for PHY initialization, + clock management, powering up VBUS, and power management. + This driver is not supported on boards like trout which + has an external PHY. + +config USB_EHCI_TEGRA + boolean "NVIDIA Tegra HCD support" + depends on ARCH_TEGRA + select USB_EHCI_ROOT_HUB_TT + select USB_PHY + help + This driver enables support for the internal USB Host Controllers + found in NVIDIA Tegra SoCs. The controllers are EHCI compliant. + +config USB_EHCI_HCD_PPC_OF + bool "EHCI support for PPC USB controller on OF platform bus" + depends on PPC_OF + default y + ---help--- + Enables support for the USB controller present on the PowerPC + OpenFirmware platform bus. + +config USB_EHCI_SH + bool "EHCI support for SuperH USB controller" + depends on SUPERH + ---help--- + Enables support for the on-chip EHCI controller on the SuperH. + If you use the PCI EHCI controller, this option is not necessary. + +config USB_EHCI_S5P + tristate "EHCI support for Samsung S5P/EXYNOS SoC Series" + depends on PLAT_S5P + help + Enable support for the Samsung S5Pxxxx and Exynos3/4/5 SOC's + on-chip EHCI controller. + +config USB_EHCI_MV + bool "EHCI support for Marvell PXA/MMP USB controller" + depends on (ARCH_PXA || ARCH_MMP) + select USB_EHCI_ROOT_HUB_TT + ---help--- + Enables support for Marvell (including PXA and MMP series) on-chip + USB SPH and OTG controller. SPH is a single port host, and it can + only be EHCI host. OTG is controller that can switch to host mode. + Note that this driver will not work on Marvell's other EHCI + controller used by the EBU-type SoCs including Orion, Kirkwood, + Dova, Armada 370 and Armada XP. See "Support for Marvell EBU + on-chip EHCI USB controller" for those. + +config USB_W90X900_EHCI + bool "W90X900(W90P910) EHCI support" + depends on ARCH_W90X900 + ---help--- + Enables support for the W90X900 USB controller + +config USB_CNS3XXX_EHCI + bool "Cavium CNS3XXX EHCI Module (DEPRECATED)" + depends on ARCH_CNS3XXX + select USB_EHCI_HCD_PLATFORM + ---help--- + This option is deprecated now and the driver was removed, use + USB_EHCI_HCD_PLATFORM instead. + + Enable support for the CNS3XXX SOC's on-chip EHCI controller. + It is needed for high-speed (480Mbit/sec) USB 2.0 device + support. + +config USB_EHCI_ATH79 + bool "EHCI support for AR7XXX/AR9XXX SoCs (DEPRECATED)" + depends on (SOC_AR71XX || SOC_AR724X || SOC_AR913X || SOC_AR933X) + select USB_EHCI_ROOT_HUB_TT + select USB_EHCI_HCD_PLATFORM + default y + ---help--- + This option is deprecated now and the driver was removed, use + USB_EHCI_HCD_PLATFORM instead. + + Enables support for the built-in EHCI controller present + on the Atheros AR7XXX/AR9XXX SoCs. + +config USB_EHCI_HCD_PLATFORM + tristate "Generic EHCI driver for a platform device" + default n + ---help--- + Adds an EHCI host driver for a generic platform device, which + provides a memory space and an irq. + + If unsure, say N. + +config USB_OCTEON_EHCI + bool "Octeon on-chip EHCI support" + depends on CPU_CAVIUM_OCTEON + default n + select USB_EHCI_BIG_ENDIAN_MMIO + help + Enable support for the Octeon II SOC's on-chip EHCI + controller. It is needed for high-speed (480Mbit/sec) + USB 2.0 device support. All CN6XXX based chips with USB are + supported. + +endif # USB_EHCI_HCD + +config USB_OXU210HP_HCD + tristate "OXU210HP HCD support" + depends on GENERIC_HARDIRQS + ---help--- + The OXU210HP is an USB host/OTG/device controller. Enable this + option if your board has this chip. If unsure, say N. + + This driver does not support isochronous transfers and doesn't + implement OTG nor USB device controllers. + + To compile this driver as a module, choose M here: the + module will be called oxu210hp-hcd. + +config USB_ISP116X_HCD + tristate "ISP116X HCD support" + ---help--- + The ISP1160 and ISP1161 chips are USB host controllers. Enable this + option if your board has this chip. If unsure, say N. + + This driver does not support isochronous transfers. + + To compile this driver as a module, choose M here: the + module will be called isp116x-hcd. + +config USB_ISP1760_HCD + tristate "ISP 1760 HCD support" + ---help--- + The ISP1760 chip is a USB 2.0 host controller. + + This driver does not support isochronous transfers or OTG. + This USB controller is usually attached to a non-DMA-Master + capable bus. NXP's eval kit brings this chip on PCI card + where the chip itself is behind a PLB to simulate such + a bus. + + To compile this driver as a module, choose M here: the + module will be called isp1760. + +config USB_ISP1362_HCD + tristate "ISP1362 HCD support" + default N + ---help--- + Supports the Philips ISP1362 chip as a host controller + + This driver does not support isochronous transfers. + + To compile this driver as a module, choose M here: the + module will be called isp1362-hcd. + +config USB_OHCI_HCD + tristate "OHCI HCD support" + select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3 + depends on USB_ISP1301 || !ARCH_LPC32XX + ---help--- + The Open Host Controller Interface (OHCI) is a standard for accessing + USB 1.1 host controller hardware. It does more in hardware than Intel's + UHCI specification. If your USB host controller follows the OHCI spec, + say Y. On most non-x86 systems, and on x86 hardware that's not using a + USB controller from Intel or VIA, this is appropriate. If your host + controller doesn't use PCI, this is probably appropriate. For a PCI + based system where you're not sure, the "lspci -v" entry will list the + right "prog-if" for your USB controller(s): EHCI, OHCI, or UHCI. + + To compile this driver as a module, choose M here: the + module will be called ohci-hcd. + +if USB_OHCI_HCD + +config USB_OHCI_HCD_OMAP1 + bool "OHCI support for OMAP1/2 chips" + depends on ARCH_OMAP1 + default y + ---help--- + Enables support for the OHCI controller on OMAP1/2 chips. + +config USB_OHCI_HCD_OMAP3 + bool "OHCI support for OMAP3 and later chips" + depends on (ARCH_OMAP3 || ARCH_OMAP4) + default y + ---help--- + Enables support for the on-chip OHCI controller on + OMAP3 and later chips. + +config USB_OHCI_ATH79 + bool "USB OHCI support for the Atheros AR71XX/AR7240 SoCs (DEPRECATED)" + depends on (SOC_AR71XX || SOC_AR724X) + select USB_OHCI_HCD_PLATFORM + default y + help + This option is deprecated now and the driver was removed, use + USB_OHCI_HCD_PLATFORM instead. + + Enables support for the built-in OHCI controller present on the + Atheros AR71XX/AR7240 SoCs. + +config USB_OHCI_HCD_PPC_OF_BE + bool "OHCI support for OF platform bus (big endian)" + depends on PPC_OF + select USB_OHCI_BIG_ENDIAN_DESC + select USB_OHCI_BIG_ENDIAN_MMIO + ---help--- + Enables support for big-endian USB controllers present on the + OpenFirmware platform bus. + +config USB_OHCI_HCD_PPC_OF_LE + bool "OHCI support for OF platform bus (little endian)" + depends on PPC_OF + select USB_OHCI_LITTLE_ENDIAN + ---help--- + Enables support for little-endian USB controllers present on the + OpenFirmware platform bus. + +config USB_OHCI_HCD_PPC_OF + bool + depends on PPC_OF + default USB_OHCI_HCD_PPC_OF_BE || USB_OHCI_HCD_PPC_OF_LE + +config USB_OHCI_HCD_PCI + bool "OHCI support for PCI-bus USB controllers" + depends on PCI && (STB03xxx || PPC_MPC52xx || USB_OHCI_HCD_PPC_OF) + default y + select USB_OHCI_LITTLE_ENDIAN + ---help--- + Enables support for PCI-bus plug-in USB controller cards. + If unsure, say Y. + +config USB_OHCI_HCD_SSB + bool "OHCI support for Broadcom SSB OHCI core (DEPRECATED)" + depends on (SSB = y || SSB = USB_OHCI_HCD) + select USB_HCD_SSB + select USB_OHCI_HCD_PLATFORM + default n + ---help--- + This option is deprecated now and the driver was removed, use + USB_HCD_SSB and USB_OHCI_HCD_PLATFORM instead. + + Support for the Sonics Silicon Backplane (SSB) attached + Broadcom USB OHCI core. + + This device is present in some embedded devices with + Broadcom based SSB bus. + + If unsure, say N. + +config USB_OHCI_SH + bool "OHCI support for SuperH USB controller (DEPRECATED)" + depends on SUPERH + select USB_OHCI_HCD_PLATFORM + ---help--- + This option is deprecated now and the driver was removed, use + USB_OHCI_HCD_PLATFORM instead. + + Enables support for the on-chip OHCI controller on the SuperH. + If you use the PCI OHCI controller, this option is not necessary. + +config USB_OHCI_EXYNOS + boolean "OHCI support for Samsung EXYNOS SoC Series" + depends on ARCH_EXYNOS + help + Enable support for the Samsung Exynos SOC's on-chip OHCI controller. + +config USB_CNS3XXX_OHCI + bool "Cavium CNS3XXX OHCI Module (DEPRECATED)" + depends on ARCH_CNS3XXX + select USB_OHCI_HCD_PLATFORM + ---help--- + This option is deprecated now and the driver was removed, use + USB_OHCI_HCD_PLATFORM instead. + + Enable support for the CNS3XXX SOC's on-chip OHCI controller. + It is needed for low-speed USB 1.0 device support. + +config USB_OHCI_HCD_PLATFORM + bool "Generic OHCI driver for a platform device" + default n + ---help--- + Adds an OHCI host driver for a generic platform device, which + provides a memory space and an irq. + + If unsure, say N. + +config USB_OCTEON_OHCI + bool "Octeon on-chip OHCI support" + depends on CPU_CAVIUM_OCTEON + default USB_OCTEON_EHCI + select USB_OHCI_BIG_ENDIAN_MMIO + select USB_OHCI_LITTLE_ENDIAN + help + Enable support for the Octeon II SOC's on-chip OHCI + controller. It is needed for low-speed USB 1.0 device + support. All CN6XXX based chips with USB are supported. + + +config USB_OHCI_BIG_ENDIAN_DESC + bool + default n + +config USB_OHCI_BIG_ENDIAN_MMIO + bool + default n + +config USB_OHCI_LITTLE_ENDIAN + bool + default n if STB03xxx || PPC_MPC52xx + default y + +endif # USB_OHCI_HCD + +config USB_UHCI_HCD + tristate "UHCI HCD (most Intel and VIA) support" + depends on PCI || USB_UHCI_SUPPORT_NON_PCI_HC + ---help--- + The Universal Host Controller Interface is a standard by Intel for + accessing the USB hardware in the PC (which is also called the USB + host controller). If your USB host controller conforms to this + standard, you may want to say Y, but see below. All recent boards + with Intel PCI chipsets (like intel 430TX, 440FX, 440LX, 440BX, + i810, i820) conform to this standard. Also all VIA PCI chipsets + (like VIA VP2, VP3, MVP3, Apollo Pro, Apollo Pro II or Apollo Pro + 133) and LEON/GRLIB SoCs with the GRUSBHC controller. + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called uhci-hcd. + +config USB_UHCI_SUPPORT_NON_PCI_HC + bool + default y if (SPARC_LEON || USB_UHCI_PLATFORM) + +config USB_UHCI_PLATFORM + bool + default y if ARCH_VT8500 + +config USB_UHCI_BIG_ENDIAN_MMIO + bool + default y if SPARC_LEON + +config USB_UHCI_BIG_ENDIAN_DESC + bool + default y if SPARC_LEON + +config USB_FHCI_HCD + tristate "Freescale QE USB Host Controller support" + depends on OF_GPIO && QE_GPIO && QUICC_ENGINE + select FSL_GTM + select QE_USB + help + This driver enables support for Freescale QE USB Host Controller + (as found on MPC8360 and MPC8323 processors), the driver supports + Full and Low Speed USB. + +config FHCI_DEBUG + bool "Freescale QE USB Host Controller debug support" + depends on USB_FHCI_HCD && DEBUG_FS + help + Say "y" to see some FHCI debug information and statistics + through debugfs. + +config USB_U132_HCD + tristate "Elan U132 Adapter Host Controller" + depends on USB_FTDI_ELAN + default M + help + The U132 adapter is a USB to CardBus adapter specifically designed + for PC cards that contain an OHCI host controller. Typical PC cards + are the Orange Mobile 3G Option GlobeTrotter Fusion card. The U132 + adapter will *NOT* work with PC cards that do not contain an OHCI + controller. + + For those PC cards that contain multiple OHCI controllers only the + first one is used. + + The driver consists of two modules, the "ftdi-elan" module is a + USB client driver that interfaces to the FTDI chip within ELAN's + USB-to-PCMCIA adapter, and this "u132-hcd" module is a USB host + controller driver that talks to the OHCI controller within the + CardBus cards that are inserted in the U132 adapter. + + This driver has been tested with a CardBus OHCI USB adapter, and + worked with a USB PEN Drive inserted into the first USB port of + the PCCARD. A rather pointless thing to do, but useful for testing. + + It is safe to say M here. + + See also + +config USB_SL811_HCD + tristate "SL811HS HCD support" + help + The SL811HS is a single-port USB controller that supports either + host side or peripheral side roles. Enable this option if your + board has this chip, and you want to use it as a host controller. + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called sl811-hcd. + +config USB_SL811_HCD_ISO + bool "partial ISO support" + depends on USB_SL811_HCD + help + The driver doesn't support iso_frame_desc (yet), but for some simple + devices that just queue one ISO frame per URB, then ISO transfers + "should" work using the normal urb status fields. + + If unsure, say N. + +config USB_SL811_CS + tristate "CF/PCMCIA support for SL811HS HCD" + depends on USB_SL811_HCD && PCMCIA + help + Wraps a PCMCIA driver around the SL811HS HCD, supporting the RATOC + REX-CFU1U CF card (often used with PDAs). If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called "sl811_cs". + +config USB_R8A66597_HCD + tristate "R8A66597 HCD support" + help + The R8A66597 is a USB 2.0 host and peripheral controller. + + Enable this option if your board has this chip, and you want + to use it as a host controller. If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called r8a66597-hcd. + +config USB_RENESAS_USBHS_HCD + tristate "Renesas USBHS HCD support" + depends on USB_RENESAS_USBHS + help + The Renesas USBHS is a USB 2.0 host and peripheral controller. + + Enable this option if your board has this chip, and you want + to use it as a host controller. If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called renesas-usbhs. + +config USB_WHCI_HCD + tristate "Wireless USB Host Controller Interface (WHCI) driver" + depends on PCI && USB && UWB + select USB_WUSB + select UWB_WHCI + help + A driver for PCI-based Wireless USB Host Controllers that are + compliant with the WHCI specification. + + To compile this driver a module, choose M here: the module + will be called "whci-hcd". + +config USB_HWA_HCD + tristate "Host Wire Adapter (HWA) driver" + depends on UWB + select USB_WUSB + select UWB_HWA + help + This driver enables you to connect Wireless USB devices to + your system using a Host Wire Adaptor USB dongle. This is an + UWB Radio Controller and WUSB Host Controller connected to + your machine via USB (specified in WUSB1.0). + + To compile this driver a module, choose M here: the module + will be called "hwa-hc". + +config USB_IMX21_HCD + tristate "i.MX21 HCD support" + depends on ARM && ARCH_MXC + help + This driver enables support for the on-chip USB host in the + i.MX21 processor. + + To compile this driver as a module, choose M here: the + module will be called "imx21-hcd". + + + +config USB_OCTEON2_COMMON + bool + default y if USB_OCTEON_EHCI || USB_OCTEON_OHCI + +config USB_HCD_BCMA + tristate "BCMA usb host driver" + depends on BCMA + select USB_OHCI_HCD_PLATFORM if USB_OHCI_HCD + select USB_EHCI_HCD_PLATFORM if USB_EHCI_HCD + help + Enable support for the EHCI and OCHI host controller on an bcma bus. + It converts the bcma driver into two platform device drivers + for ehci and ohci. + + If unsure, say N. + +config USB_HCD_SSB + tristate "SSB usb host driver" + depends on SSB + select USB_OHCI_HCD_PLATFORM if USB_OHCI_HCD + select USB_EHCI_HCD_PLATFORM if USB_EHCI_HCD + help + Enable support for the EHCI and OCHI host controller on an bcma bus. + It converts the bcma driver into two platform device drivers + for ehci and ohci. + + If unsure, say N. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile new file mode 100644 index 00000000..70df7c8c --- /dev/null +++ b/drivers/usb/host/Makefile @@ -0,0 +1,55 @@ +# +# Makefile for USB Host Controller Drivers +# + +ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG + +isp1760-y := isp1760-hcd.o isp1760-if.o + +fhci-y := fhci-hcd.o fhci-hub.o fhci-q.o +fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o + +fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o + +xhci-hcd-y := xhci.o xhci-mem.o +xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o +xhci-hcd-$(CONFIG_PCI) += xhci-pci.o + +ifneq ($(CONFIG_USB_XHCI_PLATFORM), ) + xhci-hcd-y += xhci-plat.o +endif + +obj-$(CONFIG_USB_WHCI_HCD) += whci/ + +obj-$(CONFIG_PCI) += pci-quirks.o + +obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o +obj-$(CONFIG_USB_EHCI_PCI) += ehci-pci.o +obj-$(CONFIG_USB_EHCI_HCD_PLATFORM) += ehci-platform.o +obj-$(CONFIG_USB_EHCI_MXC) += ehci-mxc.o +obj-$(CONFIG_USB_EHCI_HCD_OMAP) += ehci-omap.o +obj-$(CONFIG_USB_EHCI_HCD_ORION) += ehci-orion.o +obj-$(CONFIG_USB_EHCI_HCD_SPEAR) += ehci-spear.o +obj-$(CONFIG_USB_EHCI_S5P) += ehci-s5p.o +obj-$(CONFIG_USB_EHCI_HCD_SYNOPSYS) += ehci-h20ahb.o +obj-$(CONFIG_USB_EHCI_HCD_AT91) += ehci-atmel.o +obj-$(CONFIG_USB_EHCI_MSM) += ehci-msm.o + +obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o +obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o +obj-$(CONFIG_USB_ISP1362_HCD) += isp1362-hcd.o +obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o +obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o +obj-$(CONFIG_USB_FHCI_HCD) += fhci.o +obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o +obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o +obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o +obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o +obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o +obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o +obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o +obj-$(CONFIG_USB_IMX21_HCD) += imx21-hcd.o +obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o +obj-$(CONFIG_USB_OCTEON2_COMMON) += octeon2-common.o +obj-$(CONFIG_USB_HCD_BCMA) += bcma-hcd.o +obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o diff --git a/drivers/usb/host/bcma-hcd.c b/drivers/usb/host/bcma-hcd.c new file mode 100644 index 00000000..df13d425 --- /dev/null +++ b/drivers/usb/host/bcma-hcd.c @@ -0,0 +1,334 @@ +/* + * Broadcom specific Advanced Microcontroller Bus + * Broadcom USB-core driver (BCMA bus glue) + * + * Copyright 2011-2012 Hauke Mehrtens + * + * Based on ssb-ohci driver + * Copyright 2007 Michael Buesch + * + * Derived from the OHCI-PCI driver + * Copyright 1999 Roman Weissgaerber + * Copyright 2000-2002 David Brownell + * Copyright 1999 Linus Torvalds + * Copyright 1999 Gregory P. Smith + * + * Derived from the USBcore related parts of Broadcom-SB + * Copyright 2005-2011 Broadcom Corporation + * + * Licensed under the GNU/GPL. See COPYING for details. + */ +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Hauke Mehrtens"); +MODULE_DESCRIPTION("Common USB driver for BCMA Bus"); +MODULE_LICENSE("GPL"); + +struct bcma_hcd_device { + struct platform_device *ehci_dev; + struct platform_device *ohci_dev; +}; + +/* Wait for bitmask in a register to get set or cleared. + * timeout is in units of ten-microseconds. + */ +static int bcma_wait_bits(struct bcma_device *dev, u16 reg, u32 bitmask, + int timeout) +{ + int i; + u32 val; + + for (i = 0; i < timeout; i++) { + val = bcma_read32(dev, reg); + if ((val & bitmask) == bitmask) + return 0; + udelay(10); + } + + return -ETIMEDOUT; +} + +static void bcma_hcd_4716wa(struct bcma_device *dev) +{ +#ifdef CONFIG_BCMA_DRIVER_MIPS + /* Work around for 4716 failures. */ + if (dev->bus->chipinfo.id == 0x4716) { + u32 tmp; + + tmp = bcma_cpu_clock(&dev->bus->drv_mips); + if (tmp >= 480000000) + tmp = 0x1846b; /* set CDR to 0x11(fast) */ + else if (tmp == 453000000) + tmp = 0x1046b; /* set CDR to 0x10(slow) */ + else + tmp = 0; + + /* Change Shim mdio control reg to fix host not acking at + * high frequencies + */ + if (tmp) { + bcma_write32(dev, 0x524, 0x1); /* write sel to enable */ + udelay(500); + + bcma_write32(dev, 0x524, tmp); + udelay(500); + bcma_write32(dev, 0x524, 0x4ab); + udelay(500); + bcma_read32(dev, 0x528); + bcma_write32(dev, 0x528, 0x80000000); + } + } +#endif /* CONFIG_BCMA_DRIVER_MIPS */ +} + +/* based on arch/mips/brcm-boards/bcm947xx/pcibios.c */ +static void bcma_hcd_init_chip(struct bcma_device *dev) +{ + u32 tmp; + + /* + * USB 2.0 special considerations: + * + * 1. Since the core supports both OHCI and EHCI functions, it must + * only be reset once. + * + * 2. In addition to the standard SI reset sequence, the Host Control + * Register must be programmed to bring the USB core and various + * phy components out of reset. + */ + if (!bcma_core_is_enabled(dev)) { + bcma_core_enable(dev, 0); + mdelay(10); + if (dev->id.rev >= 5) { + /* Enable Misc PLL */ + tmp = bcma_read32(dev, 0x1e0); + tmp |= 0x100; + bcma_write32(dev, 0x1e0, tmp); + if (bcma_wait_bits(dev, 0x1e0, 1 << 24, 100)) + printk(KERN_EMERG "Failed to enable misc PPL!\n"); + + /* Take out of resets */ + bcma_write32(dev, 0x200, 0x4ff); + udelay(25); + bcma_write32(dev, 0x200, 0x6ff); + udelay(25); + + /* Make sure digital and AFE are locked in USB PHY */ + bcma_write32(dev, 0x524, 0x6b); + udelay(50); + tmp = bcma_read32(dev, 0x524); + udelay(50); + bcma_write32(dev, 0x524, 0xab); + udelay(50); + tmp = bcma_read32(dev, 0x524); + udelay(50); + bcma_write32(dev, 0x524, 0x2b); + udelay(50); + tmp = bcma_read32(dev, 0x524); + udelay(50); + bcma_write32(dev, 0x524, 0x10ab); + udelay(50); + tmp = bcma_read32(dev, 0x524); + + if (bcma_wait_bits(dev, 0x528, 0xc000, 10000)) { + tmp = bcma_read32(dev, 0x528); + printk(KERN_EMERG + "USB20H mdio_rddata 0x%08x\n", tmp); + } + bcma_write32(dev, 0x528, 0x80000000); + tmp = bcma_read32(dev, 0x314); + udelay(265); + bcma_write32(dev, 0x200, 0x7ff); + udelay(10); + + /* Take USB and HSIC out of non-driving modes */ + bcma_write32(dev, 0x510, 0); + } else { + bcma_write32(dev, 0x200, 0x7ff); + + udelay(1); + } + + bcma_hcd_4716wa(dev); + } +} + +static const struct usb_ehci_pdata ehci_pdata = { +}; + +static const struct usb_ohci_pdata ohci_pdata = { +}; + +static struct platform_device *bcma_hcd_create_pdev(struct bcma_device *dev, bool ohci, u32 addr) +{ + struct platform_device *hci_dev; + struct resource hci_res[2]; + int ret = -ENOMEM; + + memset(hci_res, 0, sizeof(hci_res)); + + hci_res[0].start = addr; + hci_res[0].end = hci_res[0].start + 0x1000 - 1; + hci_res[0].flags = IORESOURCE_MEM; + + hci_res[1].start = dev->irq; + hci_res[1].flags = IORESOURCE_IRQ; + + hci_dev = platform_device_alloc(ohci ? "ohci-platform" : + "ehci-platform" , 0); + if (!hci_dev) + return NULL; + + hci_dev->dev.parent = &dev->dev; + hci_dev->dev.dma_mask = &hci_dev->dev.coherent_dma_mask; + + ret = platform_device_add_resources(hci_dev, hci_res, + ARRAY_SIZE(hci_res)); + if (ret) + goto err_alloc; + if (ohci) + ret = platform_device_add_data(hci_dev, &ohci_pdata, + sizeof(ohci_pdata)); + else + ret = platform_device_add_data(hci_dev, &ehci_pdata, + sizeof(ehci_pdata)); + if (ret) + goto err_alloc; + ret = platform_device_add(hci_dev); + if (ret) + goto err_alloc; + + return hci_dev; + +err_alloc: + platform_device_put(hci_dev); + return ERR_PTR(ret); +} + +static int bcma_hcd_probe(struct bcma_device *dev) +{ + int err; + u16 chipid_top; + u32 ohci_addr; + struct bcma_hcd_device *usb_dev; + struct bcma_chipinfo *chipinfo; + + chipinfo = &dev->bus->chipinfo; + /* USBcores are only connected on embedded devices. */ + chipid_top = (chipinfo->id & 0xFF00); + if (chipid_top != 0x4700 && chipid_top != 0x5300) + return -ENODEV; + + /* TODO: Probably need checks here; is the core connected? */ + + if (dma_set_mask(dev->dma_dev, DMA_BIT_MASK(32)) || + dma_set_coherent_mask(dev->dma_dev, DMA_BIT_MASK(32))) + return -EOPNOTSUPP; + + usb_dev = kzalloc(sizeof(struct bcma_hcd_device), GFP_KERNEL); + if (!usb_dev) + return -ENOMEM; + + bcma_hcd_init_chip(dev); + + /* In AI chips EHCI is addrspace 0, OHCI is 1 */ + ohci_addr = dev->addr1; + if ((chipinfo->id == 0x5357 || chipinfo->id == 0x4749) + && chipinfo->rev == 0) + ohci_addr = 0x18009000; + + usb_dev->ohci_dev = bcma_hcd_create_pdev(dev, true, ohci_addr); + if (IS_ERR(usb_dev->ohci_dev)) { + err = PTR_ERR(usb_dev->ohci_dev); + goto err_free_usb_dev; + } + + usb_dev->ehci_dev = bcma_hcd_create_pdev(dev, false, dev->addr); + if (IS_ERR(usb_dev->ehci_dev)) { + err = PTR_ERR(usb_dev->ehci_dev); + goto err_unregister_ohci_dev; + } + + bcma_set_drvdata(dev, usb_dev); + return 0; + +err_unregister_ohci_dev: + platform_device_unregister(usb_dev->ohci_dev); +err_free_usb_dev: + kfree(usb_dev); + return err; +} + +static void bcma_hcd_remove(struct bcma_device *dev) +{ + struct bcma_hcd_device *usb_dev = bcma_get_drvdata(dev); + struct platform_device *ohci_dev = usb_dev->ohci_dev; + struct platform_device *ehci_dev = usb_dev->ehci_dev; + + if (ohci_dev) + platform_device_unregister(ohci_dev); + if (ehci_dev) + platform_device_unregister(ehci_dev); + + bcma_core_disable(dev, 0); +} + +static void bcma_hcd_shutdown(struct bcma_device *dev) +{ + bcma_core_disable(dev, 0); +} + +#ifdef CONFIG_PM + +static int bcma_hcd_suspend(struct bcma_device *dev) +{ + bcma_core_disable(dev, 0); + + return 0; +} + +static int bcma_hcd_resume(struct bcma_device *dev) +{ + bcma_core_enable(dev, 0); + + return 0; +} + +#else /* !CONFIG_PM */ +#define bcma_hcd_suspend NULL +#define bcma_hcd_resume NULL +#endif /* CONFIG_PM */ + +static const struct bcma_device_id bcma_hcd_table[] = { + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_USB20_HOST, BCMA_ANY_REV, BCMA_ANY_CLASS), + BCMA_CORETABLE_END +}; +MODULE_DEVICE_TABLE(bcma, bcma_hcd_table); + +static struct bcma_driver bcma_hcd_driver = { + .name = KBUILD_MODNAME, + .id_table = bcma_hcd_table, + .probe = bcma_hcd_probe, + .remove = bcma_hcd_remove, + .shutdown = bcma_hcd_shutdown, + .suspend = bcma_hcd_suspend, + .resume = bcma_hcd_resume, +}; + +static int __init bcma_hcd_init(void) +{ + return bcma_driver_register(&bcma_hcd_driver); +} +module_init(bcma_hcd_init); + +static void __exit bcma_hcd_exit(void) +{ + bcma_driver_unregister(&bcma_hcd_driver); +} +module_exit(bcma_hcd_exit); diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c new file mode 100644 index 00000000..02f4611f --- /dev/null +++ b/drivers/usb/host/ehci-atmel.c @@ -0,0 +1,209 @@ +/* + * Driver for EHCI UHP on Atmel chips + * + * Copyright (C) 2009 Atmel Corporation, + * Nicolas Ferre + * + * Based on various ehci-*.c drivers + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI Atmel driver" + +static const char hcd_name[] = "ehci-atmel"; +static struct hc_driver __read_mostly ehci_atmel_hc_driver; + +/* interface and function clocks */ +static struct clk *iclk, *fclk; +static int clocked; + +/*-------------------------------------------------------------------------*/ + +static void atmel_start_clock(void) +{ + clk_enable(iclk); + clk_enable(fclk); + clocked = 1; +} + +static void atmel_stop_clock(void) +{ + clk_disable(fclk); + clk_disable(iclk); + clocked = 0; +} + +static void atmel_start_ehci(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "start\n"); + atmel_start_clock(); +} + +static void atmel_stop_ehci(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "stop\n"); + atmel_stop_clock(); +} + +/*-------------------------------------------------------------------------*/ + +static int ehci_atmel_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + const struct hc_driver *driver = &ehci_atmel_hc_driver; + struct resource *res; + struct ehci_hcd *ehci; + int irq; + int retval; + + if (usb_disabled()) + return -ENODEV; + + pr_debug("Initializing Atmel-SoC USB Host Controller\n"); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + retval = -ENODEV; + goto fail_create_hcd; + } + + /* 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. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto fail_create_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(&pdev->dev)); + retval = -ENODEV; + goto fail_request_resource; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + hcd->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto fail_request_resource; + } + + iclk = devm_clk_get(&pdev->dev, "ehci_clk"); + if (IS_ERR(iclk)) { + dev_err(&pdev->dev, "Error getting interface clock\n"); + retval = -ENOENT; + goto fail_request_resource; + } + fclk = devm_clk_get(&pdev->dev, "uhpck"); + if (IS_ERR(fclk)) { + dev_err(&pdev->dev, "Error getting function clock\n"); + retval = -ENOENT; + goto fail_request_resource; + } + + ehci = hcd_to_ehci(hcd); + /* registers start at offset 0x0 */ + ehci->caps = hcd->regs; + + atmel_start_ehci(pdev); + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) + goto fail_add_hcd; + + return retval; + +fail_add_hcd: + atmel_stop_ehci(pdev); +fail_request_resource: + usb_put_hcd(hcd); +fail_create_hcd: + dev_err(&pdev->dev, "init %s fail, %d\n", + dev_name(&pdev->dev), retval); + + return retval; +} + +static int ehci_atmel_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + atmel_stop_ehci(pdev); + fclk = iclk = NULL; + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id atmel_ehci_dt_ids[] = { + { .compatible = "atmel,at91sam9g45-ehci" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_ehci_dt_ids); +#endif + +static struct platform_driver ehci_atmel_driver = { + .probe = ehci_atmel_drv_probe, + .remove = ehci_atmel_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "atmel-ehci", + .of_match_table = of_match_ptr(atmel_ehci_dt_ids), + }, +}; + +static int __init ehci_atmel_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + ehci_init_driver(&ehci_atmel_hc_driver, NULL); + return platform_driver_register(&ehci_atmel_driver); +} +module_init(ehci_atmel_init); + +static void __exit ehci_atmel_cleanup(void) +{ + platform_driver_unregister(&ehci_atmel_driver); +} +module_exit(ehci_atmel_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:atmel-ehci"); +MODULE_AUTHOR("Nicolas Ferre"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c new file mode 100644 index 00000000..5429d264 --- /dev/null +++ b/drivers/usb/host/ehci-dbg.c @@ -0,0 +1,980 @@ +/* + * Copyright (c) 2001-2002 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +#ifdef DEBUG + +/* check the values in the HCSPARAMS register + * (host controller _Structural_ parameters) + * see EHCI spec, Table 2-4 for each value + */ +static void dbg_hcs_params (struct ehci_hcd *ehci, char *label) +{ + u32 params = ehci_readl(ehci, &ehci->caps->hcs_params); + + ehci_dbg (ehci, + "%s hcs_params 0x%x dbg=%d%s cc=%d pcc=%d%s%s ports=%d\n", + label, params, + HCS_DEBUG_PORT (params), + HCS_INDICATOR (params) ? " ind" : "", + HCS_N_CC (params), + HCS_N_PCC (params), + HCS_PORTROUTED (params) ? "" : " ordered", + HCS_PPC (params) ? "" : " !ppc", + HCS_N_PORTS (params) + ); + /* Port routing, per EHCI 0.95 Spec, Section 2.2.5 */ + if (HCS_PORTROUTED (params)) { + int i; + char buf [46], tmp [7], byte; + + buf[0] = 0; + for (i = 0; i < HCS_N_PORTS (params); i++) { + // FIXME MIPS won't readb() ... + byte = readb (&ehci->caps->portroute[(i>>1)]); + sprintf(tmp, "%d ", + ((i & 0x1) ? ((byte)&0xf) : ((byte>>4)&0xf))); + strcat(buf, tmp); + } + ehci_dbg (ehci, "%s portroute %s\n", + label, buf); + } +} +#else + +static inline void dbg_hcs_params (struct ehci_hcd *ehci, char *label) {} + +#endif + +#ifdef DEBUG + +/* check the values in the HCCPARAMS register + * (host controller _Capability_ parameters) + * see EHCI Spec, Table 2-5 for each value + * */ +static void dbg_hcc_params (struct ehci_hcd *ehci, char *label) +{ + u32 params = ehci_readl(ehci, &ehci->caps->hcc_params); + + if (HCC_ISOC_CACHE (params)) { + ehci_dbg (ehci, + "%s hcc_params %04x caching frame %s%s%s\n", + label, params, + HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024", + HCC_CANPARK(params) ? " park" : "", + HCC_64BIT_ADDR(params) ? " 64 bit addr" : ""); + } else { + ehci_dbg (ehci, + "%s hcc_params %04x thresh %d uframes %s%s%s%s%s%s%s\n", + label, + params, + HCC_ISOC_THRES(params), + HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024", + HCC_CANPARK(params) ? " park" : "", + HCC_64BIT_ADDR(params) ? " 64 bit addr" : "", + HCC_LPM(params) ? " LPM" : "", + HCC_PER_PORT_CHANGE_EVENT(params) ? " ppce" : "", + HCC_HW_PREFETCH(params) ? " hw prefetch" : "", + HCC_32FRAME_PERIODIC_LIST(params) ? + " 32 periodic list" : ""); + } +} +#else + +static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {} + +#endif + +#ifdef DEBUG + +static void __maybe_unused +dbg_qtd (const char *label, struct ehci_hcd *ehci, struct ehci_qtd *qtd) +{ + ehci_dbg(ehci, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + hc32_to_cpup(ehci, &qtd->hw_next), + hc32_to_cpup(ehci, &qtd->hw_alt_next), + hc32_to_cpup(ehci, &qtd->hw_token), + hc32_to_cpup(ehci, &qtd->hw_buf [0])); + if (qtd->hw_buf [1]) + ehci_dbg(ehci, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + hc32_to_cpup(ehci, &qtd->hw_buf[1]), + hc32_to_cpup(ehci, &qtd->hw_buf[2]), + hc32_to_cpup(ehci, &qtd->hw_buf[3]), + hc32_to_cpup(ehci, &qtd->hw_buf[4])); +} + +static void __maybe_unused +dbg_qh (const char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + struct ehci_qh_hw *hw = qh->hw; + + ehci_dbg (ehci, "%s qh %p n%08x info %x %x qtd %x\n", label, + qh, hw->hw_next, hw->hw_info1, hw->hw_info2, hw->hw_current); + dbg_qtd("overlay", ehci, (struct ehci_qtd *) &hw->hw_qtd_next); +} + +static void __maybe_unused +dbg_itd (const char *label, struct ehci_hcd *ehci, struct ehci_itd *itd) +{ + ehci_dbg (ehci, "%s [%d] itd %p, next %08x, urb %p\n", + label, itd->frame, itd, hc32_to_cpu(ehci, itd->hw_next), + itd->urb); + ehci_dbg (ehci, + " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(ehci, itd->hw_transaction[0]), + hc32_to_cpu(ehci, itd->hw_transaction[1]), + hc32_to_cpu(ehci, itd->hw_transaction[2]), + hc32_to_cpu(ehci, itd->hw_transaction[3]), + hc32_to_cpu(ehci, itd->hw_transaction[4]), + hc32_to_cpu(ehci, itd->hw_transaction[5]), + hc32_to_cpu(ehci, itd->hw_transaction[6]), + hc32_to_cpu(ehci, itd->hw_transaction[7])); + ehci_dbg (ehci, + " buf: %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(ehci, itd->hw_bufp[0]), + hc32_to_cpu(ehci, itd->hw_bufp[1]), + hc32_to_cpu(ehci, itd->hw_bufp[2]), + hc32_to_cpu(ehci, itd->hw_bufp[3]), + hc32_to_cpu(ehci, itd->hw_bufp[4]), + hc32_to_cpu(ehci, itd->hw_bufp[5]), + hc32_to_cpu(ehci, itd->hw_bufp[6])); + ehci_dbg (ehci, " index: %d %d %d %d %d %d %d %d\n", + itd->index[0], itd->index[1], itd->index[2], + itd->index[3], itd->index[4], itd->index[5], + itd->index[6], itd->index[7]); +} + +static void __maybe_unused +dbg_sitd (const char *label, struct ehci_hcd *ehci, struct ehci_sitd *sitd) +{ + ehci_dbg (ehci, "%s [%d] sitd %p, next %08x, urb %p\n", + label, sitd->frame, sitd, hc32_to_cpu(ehci, sitd->hw_next), + sitd->urb); + ehci_dbg (ehci, + " addr %08x sched %04x result %08x buf %08x %08x\n", + hc32_to_cpu(ehci, sitd->hw_fullspeed_ep), + hc32_to_cpu(ehci, sitd->hw_uframe), + hc32_to_cpu(ehci, sitd->hw_results), + hc32_to_cpu(ehci, sitd->hw_buf[0]), + hc32_to_cpu(ehci, sitd->hw_buf[1])); +} + +static int __maybe_unused +dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) +{ + return scnprintf (buf, len, + "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s%s", + label, label [0] ? " " : "", status, + (status & STS_PPCE_MASK) ? " PPCE" : "", + (status & STS_ASS) ? " Async" : "", + (status & STS_PSS) ? " Periodic" : "", + (status & STS_RECL) ? " Recl" : "", + (status & STS_HALT) ? " Halt" : "", + (status & STS_IAA) ? " IAA" : "", + (status & STS_FATAL) ? " FATAL" : "", + (status & STS_FLR) ? " FLR" : "", + (status & STS_PCD) ? " PCD" : "", + (status & STS_ERR) ? " ERR" : "", + (status & STS_INT) ? " INT" : "" + ); +} + +static int __maybe_unused +dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable) +{ + return scnprintf (buf, len, + "%s%sintrenable %02x%s%s%s%s%s%s%s", + label, label [0] ? " " : "", enable, + (enable & STS_PPCE_MASK) ? " PPCE" : "", + (enable & STS_IAA) ? " IAA" : "", + (enable & STS_FATAL) ? " FATAL" : "", + (enable & STS_FLR) ? " FLR" : "", + (enable & STS_PCD) ? " PCD" : "", + (enable & STS_ERR) ? " ERR" : "", + (enable & STS_INT) ? " INT" : "" + ); +} + +static const char *const fls_strings [] = + { "1024", "512", "256", "??" }; + +static int +dbg_command_buf (char *buf, unsigned len, const char *label, u32 command) +{ + return scnprintf (buf, len, + "%s%scommand %07x %s%s%s%s%s%s=%d ithresh=%d%s%s%s%s " + "period=%s%s %s", + label, label [0] ? " " : "", command, + (command & CMD_HIRD) ? " HIRD" : "", + (command & CMD_PPCEE) ? " PPCEE" : "", + (command & CMD_FSP) ? " FSP" : "", + (command & CMD_ASPE) ? " ASPE" : "", + (command & CMD_PSPE) ? " PSPE" : "", + (command & CMD_PARK) ? " park" : "(park)", + CMD_PARK_CNT (command), + (command >> 16) & 0x3f, + (command & CMD_LRESET) ? " LReset" : "", + (command & CMD_IAAD) ? " IAAD" : "", + (command & CMD_ASE) ? " Async" : "", + (command & CMD_PSE) ? " Periodic" : "", + fls_strings [(command >> 2) & 0x3], + (command & CMD_RESET) ? " Reset" : "", + (command & CMD_RUN) ? "RUN" : "HALT" + ); +} + +static int +dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status) +{ + char *sig; + + /* signaling state */ + switch (status & (3 << 10)) { + case 0 << 10: sig = "se0"; break; + case 1 << 10: sig = "k"; break; /* low speed */ + case 2 << 10: sig = "j"; break; + default: sig = "?"; break; + } + + return scnprintf (buf, len, + "%s%sport:%d status %06x %d %s%s%s%s%s%s " + "sig=%s%s%s%s%s%s%s%s%s%s%s", + label, label [0] ? " " : "", port, status, + status>>25,/*device address */ + (status & PORT_SSTS)>>23 == PORTSC_SUSPEND_STS_ACK ? + " ACK" : "", + (status & PORT_SSTS)>>23 == PORTSC_SUSPEND_STS_NYET ? + " NYET" : "", + (status & PORT_SSTS)>>23 == PORTSC_SUSPEND_STS_STALL ? + " STALL" : "", + (status & PORT_SSTS)>>23 == PORTSC_SUSPEND_STS_ERR ? + " ERR" : "", + (status & PORT_POWER) ? " POWER" : "", + (status & PORT_OWNER) ? " OWNER" : "", + sig, + (status & PORT_LPM) ? " LPM" : "", + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_OCC) ? " OCC" : "", + (status & PORT_OC) ? " OC" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : ""); +} + +#else +static inline void __maybe_unused +dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) +{} + +static inline int __maybe_unused +dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) +{ return 0; } + +static inline int __maybe_unused +dbg_command_buf (char *buf, unsigned len, const char *label, u32 command) +{ return 0; } + +static inline int __maybe_unused +dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable) +{ return 0; } + +static inline int __maybe_unused +dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status) +{ return 0; } + +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ +#define dbg_status(ehci, label, status) { \ + char _buf [80]; \ + dbg_status_buf (_buf, sizeof _buf, label, status); \ + ehci_dbg (ehci, "%s\n", _buf); \ +} + +#define dbg_cmd(ehci, label, command) { \ + char _buf [80]; \ + dbg_command_buf (_buf, sizeof _buf, label, command); \ + ehci_dbg (ehci, "%s\n", _buf); \ +} + +#define dbg_port(ehci, label, port, status) { \ + char _buf [80]; \ + dbg_port_buf (_buf, sizeof _buf, label, port, status); \ + ehci_dbg (ehci, "%s\n", _buf); \ +} + +/*-------------------------------------------------------------------------*/ + +#ifdef STUB_DEBUG_FILES + +static inline void create_debug_files (struct ehci_hcd *bus) { } +static inline void remove_debug_files (struct ehci_hcd *bus) { } + +#else + +/* troubleshooting help: expose state in debugfs */ + +static int debug_async_open(struct inode *, struct file *); +static int debug_periodic_open(struct inode *, struct file *); +static int debug_registers_open(struct inode *, struct file *); +static int debug_async_open(struct inode *, struct file *); + +static ssize_t debug_output(struct file*, char __user*, size_t, loff_t*); +static int debug_close(struct inode *, struct file *); + +static const struct file_operations debug_async_fops = { + .owner = THIS_MODULE, + .open = debug_async_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_periodic_fops = { + .owner = THIS_MODULE, + .open = debug_periodic_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_registers_fops = { + .owner = THIS_MODULE, + .open = debug_registers_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; + +static struct dentry *ehci_debug_root; + +struct debug_buffer { + ssize_t (*fill_func)(struct debug_buffer *); /* fill method */ + struct usb_bus *bus; + struct mutex mutex; /* protect filling of buffer */ + size_t count; /* number of characters filled into buffer */ + char *output_buf; + size_t alloc_size; +}; + +#define speed_char(info1) ({ char tmp; \ + switch (info1 & (3 << 12)) { \ + case QH_FULL_SPEED: tmp = 'f'; break; \ + case QH_LOW_SPEED: tmp = 'l'; break; \ + case QH_HIGH_SPEED: tmp = 'h'; break; \ + default: tmp = '?'; break; \ + }; tmp; }) + +static inline char token_mark(struct ehci_hcd *ehci, __hc32 token) +{ + __u32 v = hc32_to_cpu(ehci, token); + + if (v & QTD_STS_ACTIVE) + return '*'; + if (v & QTD_STS_HALT) + return '-'; + if (!IS_SHORT_READ (v)) + return ' '; + /* tries to advance through hw_alt_next */ + return '/'; +} + +static void qh_lines ( + struct ehci_hcd *ehci, + struct ehci_qh *qh, + char **nextp, + unsigned *sizep +) +{ + u32 scratch; + u32 hw_curr; + struct list_head *entry; + struct ehci_qtd *td; + unsigned temp; + unsigned size = *sizep; + char *next = *nextp; + char mark; + __le32 list_end = EHCI_LIST_END(ehci); + struct ehci_qh_hw *hw = qh->hw; + + if (hw->hw_qtd_next == list_end) /* NEC does this */ + mark = '@'; + else + mark = token_mark(ehci, hw->hw_token); + if (mark == '/') { /* qh_alt_next controls qh advance? */ + if ((hw->hw_alt_next & QTD_MASK(ehci)) + == ehci->async->hw->hw_alt_next) + mark = '#'; /* blocked */ + else if (hw->hw_alt_next == list_end) + mark = '.'; /* use hw_qtd_next */ + /* else alt_next points to some other qtd */ + } + scratch = hc32_to_cpup(ehci, &hw->hw_info1); + hw_curr = (mark == '*') ? hc32_to_cpup(ehci, &hw->hw_current) : 0; + temp = scnprintf (next, size, + "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)", + qh, scratch & 0x007f, + speed_char (scratch), + (scratch >> 8) & 0x000f, + scratch, hc32_to_cpup(ehci, &hw->hw_info2), + hc32_to_cpup(ehci, &hw->hw_token), mark, + (cpu_to_hc32(ehci, QTD_TOGGLE) & hw->hw_token) + ? "data1" : "data0", + (hc32_to_cpup(ehci, &hw->hw_alt_next) >> 1) & 0x0f); + size -= temp; + next += temp; + + /* hc may be modifying the list as we read it ... */ + list_for_each (entry, &qh->qtd_list) { + td = list_entry (entry, struct ehci_qtd, qtd_list); + scratch = hc32_to_cpup(ehci, &td->hw_token); + mark = ' '; + if (hw_curr == td->qtd_dma) + mark = '*'; + else if (hw->hw_qtd_next == cpu_to_hc32(ehci, td->qtd_dma)) + mark = '+'; + else if (QTD_LENGTH (scratch)) { + if (td->hw_alt_next == ehci->async->hw->hw_alt_next) + mark = '#'; + else if (td->hw_alt_next != list_end) + mark = '/'; + } + temp = snprintf (next, size, + "\n\t%p%c%s len=%d %08x urb %p", + td, mark, ({ char *tmp; + switch ((scratch>>8)&0x03) { + case 0: tmp = "out"; break; + case 1: tmp = "in"; break; + case 2: tmp = "setup"; break; + default: tmp = "?"; break; + } tmp;}), + (scratch >> 16) & 0x7fff, + scratch, + td->urb); + if (size < temp) + temp = size; + size -= temp; + next += temp; + if (temp == size) + goto done; + } + + temp = snprintf (next, size, "\n"); + if (size < temp) + temp = size; + size -= temp; + next += temp; + +done: + *sizep = size; + *nextp = next; +} + +static ssize_t fill_async_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + unsigned long flags; + unsigned temp, size; + char *next; + struct ehci_qh *qh; + + hcd = bus_to_hcd(buf->bus); + ehci = hcd_to_ehci (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + *next = 0; + + /* dumps a snapshot of the async schedule. + * usually empty except for long-term bulk reads, or head. + * one QH per line, and TDs we know about + */ + spin_lock_irqsave (&ehci->lock, flags); + for (qh = ehci->async->qh_next.qh; size > 0 && qh; qh = qh->qh_next.qh) + qh_lines (ehci, qh, &next, &size); + if (!list_empty(&ehci->async_unlink) && size > 0) { + temp = scnprintf(next, size, "\nunlink =\n"); + size -= temp; + next += temp; + + list_for_each_entry(qh, &ehci->async_unlink, unlink_node) { + if (size <= 0) + break; + qh_lines(ehci, qh, &next, &size); + } + } + spin_unlock_irqrestore (&ehci->lock, flags); + + return strlen(buf->output_buf); +} + +#define DBG_SCHED_LIMIT 64 +static ssize_t fill_periodic_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + unsigned long flags; + union ehci_shadow p, *seen; + unsigned temp, size, seen_count; + char *next; + unsigned i; + __hc32 tag; + + if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, GFP_ATOMIC))) + return 0; + seen_count = 0; + + hcd = bus_to_hcd(buf->bus); + ehci = hcd_to_ehci (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + temp = scnprintf (next, size, "size = %d\n", ehci->periodic_size); + size -= temp; + next += temp; + + /* dump a snapshot of the periodic schedule. + * iso changes, interrupt usually doesn't. + */ + spin_lock_irqsave (&ehci->lock, flags); + for (i = 0; i < ehci->periodic_size; i++) { + p = ehci->pshadow [i]; + if (likely (!p.ptr)) + continue; + tag = Q_NEXT_TYPE(ehci, ehci->periodic [i]); + + temp = scnprintf (next, size, "%4d: ", i); + size -= temp; + next += temp; + + do { + struct ehci_qh_hw *hw; + + switch (hc32_to_cpu(ehci, tag)) { + case Q_TYPE_QH: + hw = p.qh->hw; + temp = scnprintf (next, size, " qh%d-%04x/%p", + p.qh->period, + hc32_to_cpup(ehci, + &hw->hw_info2) + /* uframe masks */ + & (QH_CMASK | QH_SMASK), + p.qh); + size -= temp; + next += temp; + /* don't repeat what follows this qh */ + for (temp = 0; temp < seen_count; temp++) { + if (seen [temp].ptr != p.ptr) + continue; + if (p.qh->qh_next.ptr) { + temp = scnprintf (next, size, + " ..."); + size -= temp; + next += temp; + } + break; + } + /* show more info the first time around */ + if (temp == seen_count) { + u32 scratch = hc32_to_cpup(ehci, + &hw->hw_info1); + struct ehci_qtd *qtd; + char *type = ""; + + /* count tds, get ep direction */ + temp = 0; + list_for_each_entry (qtd, + &p.qh->qtd_list, + qtd_list) { + temp++; + switch (0x03 & (hc32_to_cpu( + ehci, + qtd->hw_token) >> 8)) { + case 0: type = "out"; continue; + case 1: type = "in"; continue; + } + } + + temp = scnprintf (next, size, + " (%c%d ep%d%s " + "[%d/%d] q%d p%d)", + speed_char (scratch), + scratch & 0x007f, + (scratch >> 8) & 0x000f, type, + p.qh->usecs, p.qh->c_usecs, + temp, + 0x7ff & (scratch >> 16)); + + if (seen_count < DBG_SCHED_LIMIT) + seen [seen_count++].qh = p.qh; + } else + temp = 0; + tag = Q_NEXT_TYPE(ehci, hw->hw_next); + p = p.qh->qh_next; + break; + case Q_TYPE_FSTN: + temp = scnprintf (next, size, + " fstn-%8x/%p", p.fstn->hw_prev, + p.fstn); + tag = Q_NEXT_TYPE(ehci, p.fstn->hw_next); + p = p.fstn->fstn_next; + break; + case Q_TYPE_ITD: + temp = scnprintf (next, size, + " itd/%p", p.itd); + tag = Q_NEXT_TYPE(ehci, p.itd->hw_next); + p = p.itd->itd_next; + break; + case Q_TYPE_SITD: + temp = scnprintf (next, size, + " sitd%d-%04x/%p", + p.sitd->stream->interval, + hc32_to_cpup(ehci, &p.sitd->hw_uframe) + & 0x0000ffff, + p.sitd); + tag = Q_NEXT_TYPE(ehci, p.sitd->hw_next); + p = p.sitd->sitd_next; + break; + } + size -= temp; + next += temp; + } while (p.ptr); + + temp = scnprintf (next, size, "\n"); + size -= temp; + next += temp; + } + spin_unlock_irqrestore (&ehci->lock, flags); + kfree (seen); + + return buf->alloc_size - size; +} +#undef DBG_SCHED_LIMIT + +static const char *rh_state_string(struct ehci_hcd *ehci) +{ + switch (ehci->rh_state) { + case EHCI_RH_HALTED: + return "halted"; + case EHCI_RH_SUSPENDED: + return "suspended"; + case EHCI_RH_RUNNING: + return "running"; + case EHCI_RH_STOPPING: + return "stopping"; + } + return "?"; +} + +static ssize_t fill_registers_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + unsigned long flags; + unsigned temp, size, i; + char *next, scratch [80]; + static char fmt [] = "%*s\n"; + static char label [] = ""; + + hcd = bus_to_hcd(buf->bus); + ehci = hcd_to_ehci (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + spin_lock_irqsave (&ehci->lock, flags); + + if (!HCD_HW_ACCESSIBLE(hcd)) { + size = scnprintf (next, size, + "bus %s, device %s\n" + "%s\n" + "SUSPENDED (no register access)\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc); + goto done; + } + + /* Capability Registers */ + i = HC_VERSION(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + temp = scnprintf (next, size, + "bus %s, device %s\n" + "%s\n" + "EHCI %x.%02x, rh state %s\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc, + i >> 8, i & 0x0ff, rh_state_string(ehci)); + size -= temp; + next += temp; + +#ifdef CONFIG_PCI + /* EHCI 0.96 and later may have "extended capabilities" */ + if (hcd->self.controller->bus == &pci_bus_type) { + struct pci_dev *pdev; + u32 offset, cap, cap2; + unsigned count = 256/4; + + pdev = to_pci_dev(ehci_to_hcd(ehci)->self.controller); + offset = HCC_EXT_CAPS(ehci_readl(ehci, + &ehci->caps->hcc_params)); + while (offset && count--) { + pci_read_config_dword (pdev, offset, &cap); + switch (cap & 0xff) { + case 1: + temp = scnprintf (next, size, + "ownership %08x%s%s\n", cap, + (cap & (1 << 24)) ? " linux" : "", + (cap & (1 << 16)) ? " firmware" : ""); + size -= temp; + next += temp; + + offset += 4; + pci_read_config_dword (pdev, offset, &cap2); + temp = scnprintf (next, size, + "SMI sts/enable 0x%08x\n", cap2); + size -= temp; + next += temp; + break; + case 0: /* illegal reserved capability */ + cap = 0; + /* FALLTHROUGH */ + default: /* unknown */ + break; + } + temp = (cap >> 8) & 0xff; + } + } +#endif + + // FIXME interpret both types of params + i = ehci_readl(ehci, &ehci->caps->hcs_params); + temp = scnprintf (next, size, "structural params 0x%08x\n", i); + size -= temp; + next += temp; + + i = ehci_readl(ehci, &ehci->caps->hcc_params); + temp = scnprintf (next, size, "capability params 0x%08x\n", i); + size -= temp; + next += temp; + + /* Operational Registers */ + temp = dbg_status_buf (scratch, sizeof scratch, label, + ehci_readl(ehci, &ehci->regs->status)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_command_buf (scratch, sizeof scratch, label, + ehci_readl(ehci, &ehci->regs->command)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_intr_buf (scratch, sizeof scratch, label, + ehci_readl(ehci, &ehci->regs->intr_enable)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "uframe %04x\n", + ehci_read_frame_index(ehci)); + size -= temp; + next += temp; + + for (i = 1; i <= HCS_N_PORTS (ehci->hcs_params); i++) { + temp = dbg_port_buf (scratch, sizeof scratch, label, i, + ehci_readl(ehci, + &ehci->regs->port_status[i - 1])); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + if (i == HCS_DEBUG_PORT(ehci->hcs_params) && ehci->debug) { + temp = scnprintf (next, size, + " debug control %08x\n", + ehci_readl(ehci, + &ehci->debug->control)); + size -= temp; + next += temp; + } + } + + if (!list_empty(&ehci->async_unlink)) { + temp = scnprintf(next, size, "async unlink qh %p\n", + list_first_entry(&ehci->async_unlink, + struct ehci_qh, unlink_node)); + size -= temp; + next += temp; + } + +#ifdef EHCI_STATS + temp = scnprintf (next, size, + "irq normal %ld err %ld iaa %ld (lost %ld)\n", + ehci->stats.normal, ehci->stats.error, ehci->stats.iaa, + ehci->stats.lost_iaa); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "complete %ld unlink %ld\n", + ehci->stats.complete, ehci->stats.unlink); + size -= temp; + next += temp; +#endif + +done: + spin_unlock_irqrestore (&ehci->lock, flags); + + return buf->alloc_size - size; +} + +static struct debug_buffer *alloc_buffer(struct usb_bus *bus, + ssize_t (*fill_func)(struct debug_buffer *)) +{ + struct debug_buffer *buf; + + buf = kzalloc(sizeof(struct debug_buffer), GFP_KERNEL); + + if (buf) { + buf->bus = bus; + buf->fill_func = fill_func; + mutex_init(&buf->mutex); + buf->alloc_size = PAGE_SIZE; + } + + return buf; +} + +static int fill_buffer(struct debug_buffer *buf) +{ + int ret = 0; + + if (!buf->output_buf) + buf->output_buf = vmalloc(buf->alloc_size); + + if (!buf->output_buf) { + ret = -ENOMEM; + goto out; + } + + ret = buf->fill_func(buf); + + if (ret >= 0) { + buf->count = ret; + ret = 0; + } + +out: + return ret; +} + +static ssize_t debug_output(struct file *file, char __user *user_buf, + size_t len, loff_t *offset) +{ + struct debug_buffer *buf = file->private_data; + int ret = 0; + + mutex_lock(&buf->mutex); + if (buf->count == 0) { + ret = fill_buffer(buf); + if (ret != 0) { + mutex_unlock(&buf->mutex); + goto out; + } + } + mutex_unlock(&buf->mutex); + + ret = simple_read_from_buffer(user_buf, len, offset, + buf->output_buf, buf->count); + +out: + return ret; + +} + +static int debug_close(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf = file->private_data; + + if (buf) { + vfree(buf->output_buf); + kfree(buf); + } + + return 0; +} +static int debug_async_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static int debug_periodic_open(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf; + buf = alloc_buffer(inode->i_private, fill_periodic_buffer); + if (!buf) + return -ENOMEM; + + buf->alloc_size = (sizeof(void *) == 4 ? 6 : 8)*PAGE_SIZE; + file->private_data = buf; + return 0; +} + +static int debug_registers_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_registers_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static inline void create_debug_files (struct ehci_hcd *ehci) +{ + struct usb_bus *bus = &ehci_to_hcd(ehci)->self; + + ehci->debug_dir = debugfs_create_dir(bus->bus_name, ehci_debug_root); + if (!ehci->debug_dir) + return; + + if (!debugfs_create_file("async", S_IRUGO, ehci->debug_dir, bus, + &debug_async_fops)) + goto file_error; + + if (!debugfs_create_file("periodic", S_IRUGO, ehci->debug_dir, bus, + &debug_periodic_fops)) + goto file_error; + + if (!debugfs_create_file("registers", S_IRUGO, ehci->debug_dir, bus, + &debug_registers_fops)) + goto file_error; + + return; + +file_error: + debugfs_remove_recursive(ehci->debug_dir); +} + +static inline void remove_debug_files (struct ehci_hcd *ehci) +{ + debugfs_remove_recursive(ehci->debug_dir); +} + +#endif /* STUB_DEBUG_FILES */ diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c new file mode 100644 index 00000000..bfcf3838 --- /dev/null +++ b/drivers/usb/host/ehci-fsl.c @@ -0,0 +1,732 @@ +/* + * Copyright 2005-2009 MontaVista Software, Inc. + * Copyright 2008,2012 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Ported to 834x by Randy Vinson using code provided + * by Hunter Wu. + * Power Management support by Dave Liu , + * Jerry Huang and + * Anton Vorontsov . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ehci-fsl.h" + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * usb_hcd_fsl_probe - initialize FSL-based HCDs + * @drvier: Driver to be used for this HCD + * @pdev: USB Host Controller being probed + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller. + * + */ +static int usb_hcd_fsl_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + struct usb_hcd *hcd; + struct resource *res; + int irq; + int retval; + + pr_debug("initializing FSL-SOC USB Controller\n"); + + /* Need platform data for setup */ + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, + "No platform data for %s.\n", dev_name(&pdev->dev)); + return -ENODEV; + } + + /* + * This is a host mode driver, verify that we're supposed to be + * in host mode. + */ + if (!((pdata->operating_mode == FSL_USB2_DR_HOST) || + (pdata->operating_mode == FSL_USB2_MPH_HOST) || + (pdata->operating_mode == FSL_USB2_DR_OTG))) { + dev_err(&pdev->dev, + "Non Host Mode configured for %s. Wrong driver linked.\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res->start; + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto err1; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(&pdev->dev)); + retval = -ENODEV; + goto err2; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + driver->description)) { + dev_dbg(&pdev->dev, "controller already in use\n"); + retval = -EBUSY; + goto err2; + } + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + + if (hcd->regs == NULL) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + retval = -EFAULT; + goto err3; + } + + pdata->regs = hcd->regs; + + if (pdata->power_budget) + hcd->power_budget = pdata->power_budget; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->init && pdata->init(pdev)) { + retval = -ENODEV; + goto err4; + } + + /* Enable USB controller, 83xx or 8536 */ + if (pdata->have_sysif_regs && pdata->controller_ver < FSL_USB_VER_1_6) + setbits32(hcd->regs + FSL_SOC_USB_CTRL, 0x4); + + /* Don't need to set host mode here. It will be done by tdi_reset() */ + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval != 0) + goto err4; + +#ifdef CONFIG_USB_OTG + if (pdata->operating_mode == FSL_USB2_DR_OTG) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + hcd->phy = usb_get_phy(USB_PHY_TYPE_USB2); + dev_dbg(&pdev->dev, "hcd=0x%p ehci=0x%p, phy=0x%p\n", + hcd, ehci, hcd->phy); + + if (!IS_ERR_OR_NULL(hcd->phy)) { + retval = otg_set_host(hcd->phy->otg, + &ehci_to_hcd(ehci)->self); + if (retval) { + usb_put_phy(hcd->phy); + goto err4; + } + } else { + dev_err(&pdev->dev, "can't find phy\n"); + retval = -ENODEV; + goto err4; + } + } +#endif + return retval; + + err4: + iounmap(hcd->regs); + err3: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + err2: + usb_put_hcd(hcd); + err1: + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), retval); + if (pdata->exit) + pdata->exit(pdev); + return retval; +} + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_fsl_remove - shutdown processing for FSL-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_fsl_probe(). + * + */ +static void usb_hcd_fsl_remove(struct usb_hcd *hcd, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + if (!IS_ERR_OR_NULL(hcd->phy)) { + otg_set_host(hcd->phy->otg, NULL); + usb_put_phy(hcd->phy); + } + + usb_remove_hcd(hcd); + + /* + * do platform specific un-initialization: + * release iomux pins, disable clock, etc. + */ + if (pdata->exit) + pdata->exit(pdev); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + +static int ehci_fsl_setup_phy(struct usb_hcd *hcd, + enum fsl_usb2_phy_modes phy_mode, + unsigned int port_offset) +{ + u32 portsc; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + void __iomem *non_ehci = hcd->regs; + struct device *dev = hcd->self.controller; + struct fsl_usb2_platform_data *pdata = dev->platform_data; + + if (pdata->controller_ver < 0) { + dev_warn(hcd->self.controller, "Could not get controller version\n"); + return -ENODEV; + } + + portsc = ehci_readl(ehci, &ehci->regs->port_status[port_offset]); + portsc &= ~(PORT_PTS_MSK | PORT_PTS_PTW); + + switch (phy_mode) { + case FSL_USB2_PHY_ULPI: + if (pdata->have_sysif_regs && pdata->controller_ver) { + /* controller version 1.6 or above */ + clrbits32(non_ehci + FSL_SOC_USB_CTRL, UTMI_PHY_EN); + setbits32(non_ehci + FSL_SOC_USB_CTRL, + ULPI_PHY_CLK_SEL | USB_CTRL_USB_EN); + } + portsc |= PORT_PTS_ULPI; + break; + case FSL_USB2_PHY_SERIAL: + portsc |= PORT_PTS_SERIAL; + break; + case FSL_USB2_PHY_UTMI_WIDE: + portsc |= PORT_PTS_PTW; + /* fall through */ + case FSL_USB2_PHY_UTMI: + if (pdata->have_sysif_regs && pdata->controller_ver) { + /* controller version 1.6 or above */ + setbits32(non_ehci + FSL_SOC_USB_CTRL, UTMI_PHY_EN); + mdelay(FSL_UTMI_PHY_DLY); /* Delay for UTMI PHY CLK to + become stable - 10ms*/ + } + /* enable UTMI PHY */ + if (pdata->have_sysif_regs) + setbits32(non_ehci + FSL_SOC_USB_CTRL, + CTRL_UTMI_PHY_EN); + portsc |= PORT_PTS_UTMI; + break; + case FSL_USB2_PHY_NONE: + break; + } + + if (pdata->have_sysif_regs && + pdata->controller_ver > FSL_USB_VER_1_6 && + (phy_mode == FSL_USB2_PHY_ULPI)) { + /* check PHY_CLK_VALID to get phy clk valid */ + if (!spin_event_timeout(in_be32(non_ehci + FSL_SOC_USB_CTRL) & + PHY_CLK_VALID, FSL_USB_PHY_CLK_TIMEOUT, 0)) { + printk(KERN_WARNING "fsl-ehci: USB PHY clock invalid\n"); + return -EINVAL; + } + } + + ehci_writel(ehci, portsc, &ehci->regs->port_status[port_offset]); + + if (phy_mode != FSL_USB2_PHY_ULPI && pdata->have_sysif_regs) + setbits32(non_ehci + FSL_SOC_USB_CTRL, USB_CTRL_USB_EN); + + return 0; +} + +static int ehci_fsl_usb_setup(struct ehci_hcd *ehci) +{ + struct usb_hcd *hcd = ehci_to_hcd(ehci); + struct fsl_usb2_platform_data *pdata; + void __iomem *non_ehci = hcd->regs; + + pdata = hcd->self.controller->platform_data; + + if (pdata->have_sysif_regs) { + /* + * Turn on cache snooping hardware, since some PowerPC platforms + * wholly rely on hardware to deal with cache coherent + */ + + /* Setup Snooping for all the 4GB space */ + /* SNOOP1 starts from 0x0, size 2G */ + out_be32(non_ehci + FSL_SOC_USB_SNOOP1, 0x0 | SNOOP_SIZE_2GB); + /* SNOOP2 starts from 0x80000000, size 2G */ + out_be32(non_ehci + FSL_SOC_USB_SNOOP2, 0x80000000 | SNOOP_SIZE_2GB); + } + + if ((pdata->operating_mode == FSL_USB2_DR_HOST) || + (pdata->operating_mode == FSL_USB2_DR_OTG)) + if (ehci_fsl_setup_phy(hcd, pdata->phy_mode, 0)) + return -EINVAL; + + if (pdata->operating_mode == FSL_USB2_MPH_HOST) { + unsigned int chip, rev, svr; + + svr = mfspr(SPRN_SVR); + chip = svr >> 16; + rev = (svr >> 4) & 0xf; + + /* Deal with USB Erratum #14 on MPC834x Rev 1.0 & 1.1 chips */ + if ((rev == 1) && (chip >= 0x8050) && (chip <= 0x8055)) + ehci->has_fsl_port_bug = 1; + + if (pdata->port_enables & FSL_USB2_PORT0_ENABLED) + if (ehci_fsl_setup_phy(hcd, pdata->phy_mode, 0)) + return -EINVAL; + + if (pdata->port_enables & FSL_USB2_PORT1_ENABLED) + if (ehci_fsl_setup_phy(hcd, pdata->phy_mode, 1)) + return -EINVAL; + } + + if (pdata->have_sysif_regs) { +#ifdef CONFIG_FSL_SOC_BOOKE + out_be32(non_ehci + FSL_SOC_USB_PRICTRL, 0x00000008); + out_be32(non_ehci + FSL_SOC_USB_AGECNTTHRSH, 0x00000080); +#else + out_be32(non_ehci + FSL_SOC_USB_PRICTRL, 0x0000000c); + out_be32(non_ehci + FSL_SOC_USB_AGECNTTHRSH, 0x00000040); +#endif + out_be32(non_ehci + FSL_SOC_USB_SICTRL, 0x00000001); + } + + return 0; +} + +/* called after powerup, by probe or system-pm "wakeup" */ +static int ehci_fsl_reinit(struct ehci_hcd *ehci) +{ + if (ehci_fsl_usb_setup(ehci)) + return -EINVAL; + + return 0; +} + +/* called during probe() after chip reset completes */ +static int ehci_fsl_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + struct fsl_usb2_platform_data *pdata; + struct device *dev; + + dev = hcd->self.controller; + pdata = hcd->self.controller->platform_data; + ehci->big_endian_desc = pdata->big_endian_desc; + ehci->big_endian_mmio = pdata->big_endian_mmio; + + /* EHCI registers start at offset 0x100 */ + ehci->caps = hcd->regs + 0x100; + +#ifdef CONFIG_PPC_83xx + /* + * Deal with MPC834X that need port power to be cycled after the power + * fault condition is removed. Otherwise the state machine does not + * reflect PORTSC[CSC] correctly. + */ + ehci->need_oc_pp_cycle = 1; +#endif + + hcd->has_tt = 1; + + retval = ehci_setup(hcd); + if (retval) + return retval; + + if (of_device_is_compatible(dev->parent->of_node, + "fsl,mpc5121-usb2-dr")) { + /* + * set SBUSCFG:AHBBRST so that control msgs don't + * fail when doing heavy PATA writes. + */ + ehci_writel(ehci, SBUSCFG_INCR8, + hcd->regs + FSL_SOC_USB_SBUSCFG); + } + + retval = ehci_fsl_reinit(ehci); + return retval; +} + +struct ehci_fsl { + struct ehci_hcd ehci; + +#ifdef CONFIG_PM + /* Saved USB PHY settings, need to restore after deep sleep. */ + u32 usb_ctrl; +#endif +}; + +#ifdef CONFIG_PM + +#ifdef CONFIG_PPC_MPC512x +static int ehci_fsl_mpc512x_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata = dev->platform_data; + u32 tmp; + +#ifdef DEBUG + u32 mode = ehci_readl(ehci, hcd->regs + FSL_SOC_USB_USBMODE); + mode &= USBMODE_CM_MASK; + tmp = ehci_readl(ehci, hcd->regs + 0x140); /* usbcmd */ + + dev_dbg(dev, "suspend=%d already_suspended=%d " + "mode=%d usbcmd %08x\n", pdata->suspended, + pdata->already_suspended, mode, tmp); +#endif + + /* + * If the controller is already suspended, then this must be a + * PM suspend. Remember this fact, so that we will leave the + * controller suspended at PM resume time. + */ + if (pdata->suspended) { + dev_dbg(dev, "already suspended, leaving early\n"); + pdata->already_suspended = 1; + return 0; + } + + dev_dbg(dev, "suspending...\n"); + + ehci->rh_state = EHCI_RH_SUSPENDED; + dev->power.power_state = PMSG_SUSPEND; + + /* ignore non-host interrupts */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + /* stop the controller */ + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp &= ~CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + + /* save EHCI registers */ + pdata->pm_command = ehci_readl(ehci, &ehci->regs->command); + pdata->pm_command &= ~CMD_RUN; + pdata->pm_status = ehci_readl(ehci, &ehci->regs->status); + pdata->pm_intr_enable = ehci_readl(ehci, &ehci->regs->intr_enable); + pdata->pm_frame_index = ehci_readl(ehci, &ehci->regs->frame_index); + pdata->pm_segment = ehci_readl(ehci, &ehci->regs->segment); + pdata->pm_frame_list = ehci_readl(ehci, &ehci->regs->frame_list); + pdata->pm_async_next = ehci_readl(ehci, &ehci->regs->async_next); + pdata->pm_configured_flag = + ehci_readl(ehci, &ehci->regs->configured_flag); + pdata->pm_portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); + pdata->pm_usbgenctrl = ehci_readl(ehci, + hcd->regs + FSL_SOC_USB_USBGENCTRL); + + /* clear the W1C bits */ + pdata->pm_portsc &= cpu_to_hc32(ehci, ~PORT_RWC_BITS); + + pdata->suspended = 1; + + /* clear PP to cut power to the port */ + tmp = ehci_readl(ehci, &ehci->regs->port_status[0]); + tmp &= ~PORT_POWER; + ehci_writel(ehci, tmp, &ehci->regs->port_status[0]); + + return 0; +} + +static int ehci_fsl_mpc512x_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata = dev->platform_data; + u32 tmp; + + dev_dbg(dev, "suspend=%d already_suspended=%d\n", + pdata->suspended, pdata->already_suspended); + + /* + * If the controller was already suspended at suspend time, + * then don't resume it now. + */ + if (pdata->already_suspended) { + dev_dbg(dev, "already suspended, leaving early\n"); + pdata->already_suspended = 0; + return 0; + } + + if (!pdata->suspended) { + dev_dbg(dev, "not suspended, leaving early\n"); + return 0; + } + + pdata->suspended = 0; + + dev_dbg(dev, "resuming...\n"); + + /* set host mode */ + tmp = USBMODE_CM_HOST | (pdata->es ? USBMODE_ES : 0); + ehci_writel(ehci, tmp, hcd->regs + FSL_SOC_USB_USBMODE); + + ehci_writel(ehci, pdata->pm_usbgenctrl, + hcd->regs + FSL_SOC_USB_USBGENCTRL); + ehci_writel(ehci, ISIPHYCTRL_PXE | ISIPHYCTRL_PHYE, + hcd->regs + FSL_SOC_USB_ISIPHYCTRL); + + ehci_writel(ehci, SBUSCFG_INCR8, hcd->regs + FSL_SOC_USB_SBUSCFG); + + /* restore EHCI registers */ + ehci_writel(ehci, pdata->pm_command, &ehci->regs->command); + ehci_writel(ehci, pdata->pm_intr_enable, &ehci->regs->intr_enable); + ehci_writel(ehci, pdata->pm_frame_index, &ehci->regs->frame_index); + ehci_writel(ehci, pdata->pm_segment, &ehci->regs->segment); + ehci_writel(ehci, pdata->pm_frame_list, &ehci->regs->frame_list); + ehci_writel(ehci, pdata->pm_async_next, &ehci->regs->async_next); + ehci_writel(ehci, pdata->pm_configured_flag, + &ehci->regs->configured_flag); + ehci_writel(ehci, pdata->pm_portsc, &ehci->regs->port_status[0]); + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + ehci->rh_state = EHCI_RH_RUNNING; + dev->power.power_state = PMSG_ON; + + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp |= CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + + usb_hcd_resume_root_hub(hcd); + + return 0; +} +#else +static inline int ehci_fsl_mpc512x_drv_suspend(struct device *dev) +{ + return 0; +} + +static inline int ehci_fsl_mpc512x_drv_resume(struct device *dev) +{ + return 0; +} +#endif /* CONFIG_PPC_MPC512x */ + +static struct ehci_fsl *hcd_to_ehci_fsl(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + return container_of(ehci, struct ehci_fsl, ehci); +} + +static int ehci_fsl_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_fsl *ehci_fsl = hcd_to_ehci_fsl(hcd); + void __iomem *non_ehci = hcd->regs; + + if (of_device_is_compatible(dev->parent->of_node, + "fsl,mpc5121-usb2-dr")) { + return ehci_fsl_mpc512x_drv_suspend(dev); + } + + ehci_prepare_ports_for_controller_suspend(hcd_to_ehci(hcd), + device_may_wakeup(dev)); + if (!fsl_deep_sleep()) + return 0; + + ehci_fsl->usb_ctrl = in_be32(non_ehci + FSL_SOC_USB_CTRL); + return 0; +} + +static int ehci_fsl_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_fsl *ehci_fsl = hcd_to_ehci_fsl(hcd); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + void __iomem *non_ehci = hcd->regs; + + if (of_device_is_compatible(dev->parent->of_node, + "fsl,mpc5121-usb2-dr")) { + return ehci_fsl_mpc512x_drv_resume(dev); + } + + ehci_prepare_ports_for_controller_resume(ehci); + if (!fsl_deep_sleep()) + return 0; + + usb_root_hub_lost_power(hcd->self.root_hub); + + /* Restore USB PHY settings and enable the controller. */ + out_be32(non_ehci + FSL_SOC_USB_CTRL, ehci_fsl->usb_ctrl); + + ehci_reset(ehci); + ehci_fsl_reinit(ehci); + + return 0; +} + +static int ehci_fsl_drv_restore(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + usb_root_hub_lost_power(hcd->self.root_hub); + return 0; +} + +static struct dev_pm_ops ehci_fsl_pm_ops = { + .suspend = ehci_fsl_drv_suspend, + .resume = ehci_fsl_drv_resume, + .restore = ehci_fsl_drv_restore, +}; + +#define EHCI_FSL_PM_OPS (&ehci_fsl_pm_ops) +#else +#define EHCI_FSL_PM_OPS NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_USB_OTG +static int ehci_start_port_reset(struct usb_hcd *hcd, unsigned port) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 status; + + if (!port) + return -EINVAL; + + port--; + + /* start port reset before HNP protocol time out */ + status = readl(&ehci->regs->port_status[port]); + if (!(status & PORT_CONNECT)) + return -ENODEV; + + /* khubd will finish the reset later */ + if (ehci_is_TDI(ehci)) { + writel(PORT_RESET | + (status & ~(PORT_CSC | PORT_PEC | PORT_OCC)), + &ehci->regs->port_status[port]); + } else { + writel(PORT_RESET, &ehci->regs->port_status[port]); + } + + return 0; +} +#else +#define ehci_start_port_reset NULL +#endif /* CONFIG_USB_OTG */ + + +static const struct hc_driver ehci_fsl_hc_driver = { + .description = hcd_name, + .product_desc = "Freescale On-Chip EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_fsl), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_USB2 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ehci_fsl_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, + .start_port_reset = ehci_start_port_reset, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ehci_fsl_drv_probe(struct platform_device *pdev) +{ + if (usb_disabled()) + return -ENODEV; + + /* FIXME we only want one one probe() not two */ + return usb_hcd_fsl_probe(&ehci_fsl_hc_driver, pdev); +} + +static int ehci_fsl_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + /* FIXME we only want one one remove() not two */ + usb_hcd_fsl_remove(hcd, pdev); + return 0; +} + +MODULE_ALIAS("platform:fsl-ehci"); + +static struct platform_driver ehci_fsl_driver = { + .probe = ehci_fsl_drv_probe, + .remove = ehci_fsl_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "fsl-ehci", + .pm = EHCI_FSL_PM_OPS, + }, +}; diff --git a/drivers/usb/host/ehci-fsl.h b/drivers/usb/host/ehci-fsl.h new file mode 100644 index 00000000..dbd292e9 --- /dev/null +++ b/drivers/usb/host/ehci-fsl.h @@ -0,0 +1,65 @@ +/* Copyright (C) 2005-2010,2012 Freescale Semiconductor, Inc. + * Copyright (c) 2005 MontaVista Software + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef _EHCI_FSL_H +#define _EHCI_FSL_H + +/* offsets for the non-ehci registers in the FSL SOC USB controller */ +#define FSL_SOC_USB_SBUSCFG 0x90 +#define SBUSCFG_INCR8 0x02 /* INCR8, specified */ +#define FSL_SOC_USB_ULPIVP 0x170 +#define FSL_SOC_USB_PORTSC1 0x184 +#define PORT_PTS_MSK (3<<30) +#define PORT_PTS_UTMI (0<<30) +#define PORT_PTS_ULPI (2<<30) +#define PORT_PTS_SERIAL (3<<30) +#define PORT_PTS_PTW (1<<28) +#define FSL_SOC_USB_PORTSC2 0x188 +#define FSL_SOC_USB_USBMODE 0x1a8 +#define USBMODE_CM_MASK (3 << 0) /* controller mode mask */ +#define USBMODE_CM_HOST (3 << 0) /* controller mode: host */ +#define USBMODE_ES (1 << 2) /* (Big) Endian Select */ + +#define FSL_SOC_USB_USBGENCTRL 0x200 +#define USBGENCTRL_PPP (1 << 3) +#define USBGENCTRL_PFP (1 << 2) +#define FSL_SOC_USB_ISIPHYCTRL 0x204 +#define ISIPHYCTRL_PXE (1) +#define ISIPHYCTRL_PHYE (1 << 4) + +#define FSL_SOC_USB_SNOOP1 0x400 /* NOTE: big-endian */ +#define FSL_SOC_USB_SNOOP2 0x404 /* NOTE: big-endian */ +#define FSL_SOC_USB_AGECNTTHRSH 0x408 /* NOTE: big-endian */ +#define FSL_SOC_USB_PRICTRL 0x40c /* NOTE: big-endian */ +#define FSL_SOC_USB_SICTRL 0x410 /* NOTE: big-endian */ +#define FSL_SOC_USB_CTRL 0x500 /* NOTE: big-endian */ +#define CTRL_UTMI_PHY_EN (1<<9) +#define CTRL_PHY_CLK_VALID (1 << 17) +#define SNOOP_SIZE_2GB 0x1e + +/* control Register Bit Masks */ +#define ULPI_INT_EN (1<<0) +#define WU_INT_EN (1<<1) +#define USB_CTRL_USB_EN (1<<2) +#define LINE_STATE_FILTER__EN (1<<3) +#define KEEP_OTG_ON (1<<4) +#define OTG_PORT (1<<5) +#define PLL_RESET (1<<8) +#define UTMI_PHY_EN (1<<9) +#define ULPI_PHY_CLK_SEL (1<<10) +#define PHY_CLK_VALID (1<<17) +#endif /* _EHCI_FSL_H */ diff --git a/drivers/usb/host/ehci-grlib.c b/drivers/usb/host/ehci-grlib.c new file mode 100644 index 00000000..5d75de97 --- /dev/null +++ b/drivers/usb/host/ehci-grlib.c @@ -0,0 +1,202 @@ +/* + * Driver for Aeroflex Gaisler GRLIB GRUSBHC EHCI host controller + * + * GRUSBHC is typically found on LEON/GRLIB SoCs + * + * (c) Jan Andersson + * + * Based on ehci-ppc-of.c which is: + * (c) Valentine Barshak + * and in turn based on "ehci-ppc-soc.c" by Stefan Roese + * and "ohci-ppc-of.c" by Sylvain Munaut + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include +#include +#include + +#define GRUSBHC_HCIVERSION 0x0100 /* Known value of cap. reg. HCIVERSION */ + +static const struct hc_driver ehci_grlib_hc_driver = { + .description = hcd_name, + .product_desc = "GRLIB GRUSBHC EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + + +static int ehci_hcd_grlib_probe(struct platform_device *op) +{ + struct device_node *dn = op->dev.of_node; + struct usb_hcd *hcd; + struct ehci_hcd *ehci = NULL; + struct resource res; + u32 hc_capbase; + int irq; + int rv; + + if (usb_disabled()) + return -ENODEV; + + dev_dbg(&op->dev, "initializing GRUSBHC EHCI USB Controller\n"); + + rv = of_address_to_resource(dn, 0, &res); + if (rv) + return rv; + + /* usb_create_hcd requires dma_mask != NULL */ + op->dev.dma_mask = &op->dev.coherent_dma_mask; + hcd = usb_create_hcd(&ehci_grlib_hc_driver, &op->dev, + "GRUSBHC EHCI USB"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = res.start; + hcd->rsrc_len = resource_size(&res); + + irq = irq_of_parse_and_map(dn, 0); + if (irq == NO_IRQ) { + printk(KERN_ERR "%s: irq_of_parse_and_map failed\n", __FILE__); + rv = -EBUSY; + goto err_irq; + } + + hcd->regs = devm_ioremap_resource(&op->dev, &res); + if (IS_ERR(hcd->regs)) { + rv = PTR_ERR(hcd->regs); + goto err_ioremap; + } + + ehci = hcd_to_ehci(hcd); + + ehci->caps = hcd->regs; + + /* determine endianness of this implementation */ + hc_capbase = ehci_readl(ehci, &ehci->caps->hc_capbase); + if (HC_VERSION(ehci, hc_capbase) != GRUSBHC_HCIVERSION) { + ehci->big_endian_mmio = 1; + ehci->big_endian_desc = 1; + ehci->big_endian_capbase = 1; + } + + rv = usb_add_hcd(hcd, irq, 0); + if (rv) + goto err_ioremap; + + return 0; + +err_ioremap: + irq_dispose_mapping(irq); +err_irq: + usb_put_hcd(hcd); + + return rv; +} + + +static int ehci_hcd_grlib_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + dev_set_drvdata(&op->dev, NULL); + + dev_dbg(&op->dev, "stopping GRLIB GRUSBHC EHCI USB Controller\n"); + + usb_remove_hcd(hcd); + + irq_dispose_mapping(hcd->irq); + + usb_put_hcd(hcd); + + return 0; +} + + +static void ehci_hcd_grlib_shutdown(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + + +static const struct of_device_id ehci_hcd_grlib_of_match[] = { + { + .name = "GAISLER_EHCI", + }, + { + .name = "01_026", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ehci_hcd_grlib_of_match); + + +static struct platform_driver ehci_grlib_driver = { + .probe = ehci_hcd_grlib_probe, + .remove = ehci_hcd_grlib_remove, + .shutdown = ehci_hcd_grlib_shutdown, + .driver = { + .name = "grlib-ehci", + .owner = THIS_MODULE, + .of_match_table = ehci_hcd_grlib_of_match, + }, +}; diff --git a/drivers/usb/host/ehci-h20ahb.c b/drivers/usb/host/ehci-h20ahb.c new file mode 100644 index 00000000..7724bab1 --- /dev/null +++ b/drivers/usb/host/ehci-h20ahb.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2007-2013 Texas Instruments, Inc. + * Author: Vikram Pandita + * Author: Anand Gadiyar + * Author: Keshava Munegowda + * Author: Roger Quadros + * + * Copyright (C) 2009 Nokia Corporation + * Contact: Felipe Balbi + * + * Based on ehci-omap.c - driver for USBHOST on OMAP3/4 processors + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define H20AHB_HS_USB_PORTS 1 + +/* EHCI Synopsys-specific Register Set */ +#define EHCI_INSNREG04 (0xA0) +#define EHCI_INSNREG04_DISABLE_UNSUSPEND (1 << 5) +#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 + +#define DRIVER_DESC "H20AHB-EHCI Host Controller driver" + +static const char hcd_name[] = "ehci-h20ahb"; + +/*-------------------------------------------------------------------------*/ + +struct h20ahb_hcd { + struct usb_phy *phy[H20AHB_HS_USB_PORTS]; /* one PHY for each port */ + int nports; +}; + +static inline void ehci_write(void __iomem *base, u32 reg, u32 val) +{ + writel_relaxed(val, base + reg); +} + +static inline u32 ehci_read(void __iomem *base, u32 reg) +{ + return readl_relaxed(base + reg); +} + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +static struct hc_driver __read_mostly ehci_h20ahb_hc_driver; + +static const struct ehci_driver_overrides ehci_h20ahb_overrides __initdata = { + .extra_priv_size = sizeof(struct h20ahb_hcd), +}; + +static int ehci_h20ahb_phy_read(struct usb_phy *x, u32 reg) +{ + u32 val = (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT) | + (1 << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) | + (3 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) | + (reg << EHCI_INSNREG05_ULPI_REGADD_SHIFT); + ehci_write(x->io_priv, 0, val); + while ((val = ehci_read(x->io_priv, 0)) & + (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT)); + return val & 0xff; +} + +static int ehci_h20ahb_phy_write(struct usb_phy *x, u32 val, u32 reg) +{ + u32 v = (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT) | + (1 << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) | + (2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) | + (reg << EHCI_INSNREG05_ULPI_REGADD_SHIFT) | + (val & 0xff); + ehci_write(x->io_priv, 0, v); + while ((v = ehci_read(x->io_priv, 0)) & + (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT)); + return 0; +} + +static struct usb_phy_io_ops ehci_h20ahb_phy_io_ops = { + .read = ehci_h20ahb_phy_read, + .write = ehci_h20ahb_phy_write, +}; + + +/** + * ehci_hcd_h20ahb_probe - initialize Synopsis-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_h20ahb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct usb_hcd *hcd; + void __iomem *regs; + int ret; + int irq; + int i; + struct h20ahb_hcd *h20ahb; + + if (usb_disabled()) + return -ENODEV; + + /* if (!dev->parent) { + dev_err(dev, "Missing parent device\n"); + return -ENODEV; + }*/ + + /* For DT boot, get platform data from parent. i.e. usbhshost */ + /*if (dev->of_node) { + pdata = dev_get_platdata(dev->parent); + dev->platform_data = pdata; + } + + if (!pdata) { + dev_err(dev, "Missing platform data\n"); + return -ENODEV; + }*/ + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "EHCI irq failed\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + /* + * 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. + */ + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + ret = -ENODEV; + hcd = usb_create_hcd(&ehci_h20ahb_hc_driver, dev, + dev_name(dev)); + if (!hcd) { + dev_err(dev, "Failed to create HCD\n"); + return -ENOMEM; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = regs; + hcd_to_ehci(hcd)->caps = regs; + + h20ahb = (struct h20ahb_hcd *)hcd_to_ehci(hcd)->priv; + h20ahb->nports = 1; + + platform_set_drvdata(pdev, hcd); + + /* get the PHY devices if needed */ + for (i = 0 ; i < h20ahb->nports ; i++) { + struct usb_phy *phy; + + /* get the PHY device */ +#if 0 + if (dev->of_node) + phy = devm_usb_get_phy_by_phandle(dev, "phys", i); + else + phy = devm_usb_get_phy_dev(dev, i); +#endif + phy = otg_ulpi_create(&ehci_h20ahb_phy_io_ops, 0); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + dev_err(dev, "Can't get PHY device for port %d: %d\n", + i, ret); + goto err_phy; + } + phy->dev = dev; + usb_add_phy_dev(phy); + + h20ahb->phy[i] = phy; + phy->io_priv = hcd->regs + EHCI_INSNREG05_ULPI; + +#if 0 + usb_phy_init(h20ahb->phy[i]); + /* bring PHY out of suspend */ + usb_phy_set_suspend(h20ahb->phy[i], 0); +#endif + } + + /* make the first port's phy the one used by hcd as well */ + hcd->phy = h20ahb->phy[0]; + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + /* + * An undocumented "feature" in the H20AHB 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); + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) { + dev_err(dev, "failed to add hcd with err %d\n", ret); + goto err_pm_runtime; + } + device_wakeup_enable(hcd->self.controller); + + /* + * Bring PHYs out of reset for non PHY modes. + * 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 < h20ahb->nports; i++) { + usb_phy_init(h20ahb->phy[i]); + /* bring PHY out of suspend */ + usb_phy_set_suspend(h20ahb->phy[i], 0); + } + + return 0; + +err_pm_runtime: + pm_runtime_put_sync(dev); + +err_phy: + for (i = 0; i < h20ahb->nports; i++) { + if (h20ahb->phy[i]) + usb_phy_shutdown(h20ahb->phy[i]); + } + + usb_put_hcd(hcd); + + return ret; +} + + +/** + * ehci_hcd_h20ahb_remove - shutdown processing for EHCI HCDs + * @pdev: USB Host Controller being removed + * + * Reverses the effect of usb_ehci_hcd_h20ahb_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_h20ahb_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct h20ahb_hcd *h20ahb = (struct h20ahb_hcd *)hcd_to_ehci(hcd)->priv; + int i; + + usb_remove_hcd(hcd); + + for (i = 0; i < h20ahb->nports; i++) { + if (h20ahb->phy[i]) + usb_phy_shutdown(h20ahb->phy[i]); + } + + usb_put_hcd(hcd); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return 0; +} + +static const struct of_device_id h20ahb_ehci_dt_ids[] = { + { .compatible = "snps,ehci-h20ahb" }, + { } +}; + +MODULE_DEVICE_TABLE(of, h20ahb_ehci_dt_ids); + +static struct platform_driver ehci_hcd_h20ahb_driver = { + .probe = ehci_hcd_h20ahb_probe, + .remove = ehci_hcd_h20ahb_remove, + .shutdown = usb_hcd_platform_shutdown, + /*.suspend = ehci_hcd_h20ahb_suspend, */ + /*.resume = ehci_hcd_h20ahb_resume, */ + .driver = { + .name = hcd_name, + .of_match_table = h20ahb_ehci_dt_ids, + } +}; + +/*-------------------------------------------------------------------------*/ + +static int __init ehci_h20ahb_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ehci_init_driver(&ehci_h20ahb_hc_driver, &ehci_h20ahb_overrides); + return platform_driver_register(&ehci_hcd_h20ahb_driver); +} +module_init(ehci_h20ahb_init); + +static void __exit ehci_h20ahb_cleanup(void) +{ + platform_driver_unregister(&ehci_hcd_h20ahb_driver); +} +module_exit(ehci_h20ahb_cleanup); + +MODULE_ALIAS("platform:ehci-h20ahb"); +MODULE_AUTHOR("Liviu Dudau "); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c new file mode 100644 index 00000000..f1c3ad69 --- /dev/null +++ b/drivers/usb/host/ehci-hcd.c @@ -0,0 +1,1396 @@ +/* + * Enhanced Host Controller Interface (EHCI) driver for USB. + * + * Maintainer: Alan Stern + * + * Copyright (c) 2000-2004 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(CONFIG_PPC_PS3) +#include +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI hc_driver implementation ... experimental, incomplete. + * Based on the final 1.0 register interface specification. + * + * USB 2.0 shows up in upcoming www.pcmcia.org technology. + * First was PCMCIA, like ISA; then CardBus, which is PCI. + * Next comes "CardBay", using USB 2.0 signals. + * + * Contains additional contributions by Brad Hards, Rory Bolt, and others. + * Special thanks to Intel and VIA for providing host controllers to + * test this driver on, and Cypress (including In-System Design) for + * providing early devices for those host controllers to talk to! + */ + +#define DRIVER_AUTHOR "David Brownell" +#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver" + +static const char hcd_name [] = "ehci_hcd"; + + +#undef VERBOSE_DEBUG +#undef EHCI_URB_TRACE + +/* magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +/* + * Some drivers think it's safe to schedule isochronous transfers more than + * 256 ms into the future (partly as a result of an old bug in the scheduling + * code). In an attempt to avoid trouble, we will use a minimum scheduling + * length of 512 frames instead of 256. + */ +#define EHCI_TUNE_FLS 1 /* (medium) 512-frame schedule */ + +/* Initial IRQ latency: faster than hw default */ +static int log2_irq_thresh = 0; // 0 to 6 +module_param (log2_irq_thresh, int, S_IRUGO); +MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +/* initial park setting: slower than hw default */ +static unsigned park = 0; +module_param (park, uint, S_IRUGO); +MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets"); + +/* for flakey hardware, ignore overcurrent indicators */ +static bool ignore_oc = 0; +module_param (ignore_oc, bool, S_IRUGO); +MODULE_PARM_DESC (ignore_oc, "ignore bogus hardware overcurrent indications"); + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + +/*-------------------------------------------------------------------------*/ + +#include "ehci.h" +#include "pci-quirks.h" + +/* + * The MosChip MCS9990 controller updates its microframe counter + * a little before the frame counter, and occasionally we will read + * the invalid intermediate value. Avoid problems by checking the + * microframe number (the low-order 3 bits); if they are 0 then + * re-read the register to get the correct value. + */ +static unsigned ehci_moschip_read_frame_index(struct ehci_hcd *ehci) +{ + unsigned uf; + + uf = ehci_readl(ehci, &ehci->regs->frame_index); + if (unlikely((uf & 7) == 0)) + uf = ehci_readl(ehci, &ehci->regs->frame_index); + return uf; +} + +static inline unsigned ehci_read_frame_index(struct ehci_hcd *ehci) +{ + if (ehci->frame_index_bug) + return ehci_moschip_read_frame_index(ehci); + return ehci_readl(ehci, &ehci->regs->frame_index); +} + +#include "ehci-dbg.c" + +/*-------------------------------------------------------------------------*/ + +/* + * handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + * + * That last failure should_only happen in cases like physical cardbus eject + * before driver shutdown. But it also seems to be caused by bugs in cardbus + * bridge shutdown: shutting down the bridge before the devices using it. + */ +static int handshake (struct ehci_hcd *ehci, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = ehci_readl(ehci, ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay (1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* check TDI/ARC silicon is in host mode */ +static int tdi_in_host_mode (struct ehci_hcd *ehci) +{ + u32 tmp; + + tmp = ehci_readl(ehci, &ehci->regs->usbmode); + return (tmp & 3) == USBMODE_CM_HC; +} + +/* + * Force HC to halt state from unknown (EHCI spec section 2.3). + * Must be called with interrupts enabled and the lock not held. + */ +static int ehci_halt (struct ehci_hcd *ehci) +{ + u32 temp; + + spin_lock_irq(&ehci->lock); + + /* disable any irqs left enabled by previous code */ + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + + if (ehci_is_TDI(ehci) && !tdi_in_host_mode(ehci)) { + spin_unlock_irq(&ehci->lock); + return 0; + } + + /* + * This routine gets called during probe before ehci->command + * has been initialized, so we can't rely on its value. + */ + ehci->command &= ~CMD_RUN; + temp = ehci_readl(ehci, &ehci->regs->command); + temp &= ~(CMD_RUN | CMD_IAAD); + ehci_writel(ehci, temp, &ehci->regs->command); + + spin_unlock_irq(&ehci->lock); + synchronize_irq(ehci_to_hcd(ehci)->irq); + + return handshake(ehci, &ehci->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* put TDI/ARC silicon into EHCI mode */ +static void tdi_reset (struct ehci_hcd *ehci) +{ + u32 tmp; + + tmp = ehci_readl(ehci, &ehci->regs->usbmode); + tmp |= USBMODE_CM_HC; + /* The default byte access to MMR space is LE after + * controller reset. Set the required endian mode + * for transfer buffers to match the host microprocessor + */ + if (ehci_big_endian_mmio(ehci)) + tmp |= USBMODE_BE; + ehci_writel(ehci, tmp, &ehci->regs->usbmode); +} + +/* + * Reset a non-running (STS_HALT == 1) controller. + * Must be called with interrupts enabled and the lock not held. + */ +static int ehci_reset (struct ehci_hcd *ehci) +{ + int retval; + u32 command = ehci_readl(ehci, &ehci->regs->command); + + /* If the EHCI debug controller is active, special care must be + * taken before and after a host controller reset */ + if (ehci->debug && !dbgp_reset_prep(ehci_to_hcd(ehci))) + ehci->debug = NULL; + + command |= CMD_RESET; + dbg_cmd (ehci, "reset", command); + ehci_writel(ehci, command, &ehci->regs->command); + ehci->rh_state = EHCI_RH_HALTED; + ehci->next_statechange = jiffies; + retval = handshake (ehci, &ehci->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (ehci->has_hostpc) { + ehci_writel(ehci, USBMODE_EX_HC | USBMODE_EX_VBPS, + &ehci->regs->usbmode_ex); + ehci_writel(ehci, TXFIFO_DEFAULT, &ehci->regs->txfill_tuning); + } + if (retval) + return retval; + + if (ehci_is_TDI(ehci)) + tdi_reset (ehci); + + if (ehci->debug) + dbgp_external_startup(ehci_to_hcd(ehci)); + + ehci->port_c_suspend = ehci->suspended_ports = + ehci->resuming_ports = 0; + return retval; +} + +/* + * Idle the controller (turn off the schedules). + * Must be called with interrupts enabled and the lock not held. + */ +static void ehci_quiesce (struct ehci_hcd *ehci) +{ + u32 temp; + + if (ehci->rh_state != EHCI_RH_RUNNING) + return; + + /* wait for any schedule enables/disables to take effect */ + temp = (ehci->command << 10) & (STS_ASS | STS_PSS); + handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, temp, 16 * 125); + + /* then disable anything that's still active */ + spin_lock_irq(&ehci->lock); + ehci->command &= ~(CMD_ASE | CMD_PSE); + ehci_writel(ehci, ehci->command, &ehci->regs->command); + spin_unlock_irq(&ehci->lock); + + /* hardware can take 16 microframes to turn off ... */ + handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, 0, 16 * 125); +} + +/*-------------------------------------------------------------------------*/ + +static void end_unlink_async(struct ehci_hcd *ehci); +static void unlink_empty_async(struct ehci_hcd *ehci); +static void unlink_empty_async_suspended(struct ehci_hcd *ehci); +static void ehci_work(struct ehci_hcd *ehci); +static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); +static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); + +#include "ehci-timer.c" +#include "ehci-hub.c" +#include "ehci-mem.c" +#include "ehci-q.c" +#include "ehci-sched.c" +#include "ehci-sysfs.c" + +/*-------------------------------------------------------------------------*/ + +/* On some systems, leaving remote wakeup enabled prevents system shutdown. + * The firmware seems to think that powering off is a wakeup event! + * This routine turns off remote wakeup and everything else, on all ports. + */ +static void ehci_turn_off_all_ports(struct ehci_hcd *ehci) +{ + int port = HCS_N_PORTS(ehci->hcs_params); + + while (port--) + ehci_writel(ehci, PORT_RWC_BITS, + &ehci->regs->port_status[port]); +} + +/* + * Halt HC, turn off all ports, and let the BIOS use the companion controllers. + * Must be called with interrupts enabled and the lock not held. + */ +static void ehci_silence_controller(struct ehci_hcd *ehci) +{ + ehci_halt(ehci); + + spin_lock_irq(&ehci->lock); + ehci->rh_state = EHCI_RH_HALTED; + ehci_turn_off_all_ports(ehci); + + /* make BIOS/etc use companion controller during reboot */ + ehci_writel(ehci, 0, &ehci->regs->configured_flag); + + /* unblock posted writes */ + ehci_readl(ehci, &ehci->regs->configured_flag); + spin_unlock_irq(&ehci->lock); +} + +/* ehci_shutdown kick in for silicon on any bus (not just pci, etc). + * This forcibly disables dma and IRQs, helping kexec and other cases + * where the next system software may expect clean state. + */ +static void ehci_shutdown(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + spin_lock_irq(&ehci->lock); + ehci->shutdown = true; + ehci->rh_state = EHCI_RH_STOPPING; + ehci->enabled_hrtimer_events = 0; + spin_unlock_irq(&ehci->lock); + + ehci_silence_controller(ehci); + + hrtimer_cancel(&ehci->hrtimer); +} + +/*-------------------------------------------------------------------------*/ + +/* + * ehci_work is called from some interrupts, timers, and so on. + * it calls driver completion functions, after dropping ehci->lock. + */ +static void ehci_work (struct ehci_hcd *ehci) +{ + /* another CPU may drop ehci->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (ehci->scanning) { + ehci->need_rescan = true; + return; + } + ehci->scanning = true; + + rescan: + ehci->need_rescan = false; + if (ehci->async_count) + scan_async(ehci); + if (ehci->intr_count > 0) + scan_intr(ehci); + if (ehci->isoc_count > 0) + scan_isoc(ehci); + if (ehci->need_rescan) + goto rescan; + ehci->scanning = false; + + /* the IO watchdog guards against hardware or driver bugs that + * misplace IRQs, and should let us run completely without IRQs. + * such lossage has been observed on both VT6202 and VT8235. + */ + turn_on_io_watchdog(ehci); +} + +/* + * Called when the ehci_hcd module is removed. + */ +static void ehci_stop (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + + ehci_dbg (ehci, "stop\n"); + + /* no more interrupts ... */ + + spin_lock_irq(&ehci->lock); + ehci->enabled_hrtimer_events = 0; + spin_unlock_irq(&ehci->lock); + + ehci_quiesce(ehci); + ehci_silence_controller(ehci); + ehci_reset (ehci); + + hrtimer_cancel(&ehci->hrtimer); + remove_sysfs_files(ehci); + remove_debug_files (ehci); + + /* root hub is shut down separately (first, when possible) */ + spin_lock_irq (&ehci->lock); + end_free_itds(ehci); + spin_unlock_irq (&ehci->lock); + ehci_mem_cleanup (ehci); + + if (ehci->amd_pll_fix == 1) + usb_amd_dev_put(); + +#ifdef EHCI_STATS + ehci_dbg(ehci, "irq normal %ld err %ld iaa %ld (lost %ld)\n", + ehci->stats.normal, ehci->stats.error, ehci->stats.iaa, + ehci->stats.lost_iaa); + ehci_dbg (ehci, "complete %ld unlink %ld\n", + ehci->stats.complete, ehci->stats.unlink); +#endif + + dbg_status (ehci, "ehci_stop completed", + ehci_readl(ehci, &ehci->regs->status)); +} + +/* one-time init, only for memory state */ +static int ehci_init(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 temp; + int retval; + u32 hcc_params; + struct ehci_qh_hw *hw; + + spin_lock_init(&ehci->lock); + + /* + * keep io watchdog by default, those good HCDs could turn off it later + */ + ehci->need_io_watchdog = 1; + + hrtimer_init(&ehci->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + ehci->hrtimer.function = ehci_hrtimer_func; + ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT; + + hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params); + + /* + * by default set standard 80% (== 100 usec/uframe) max periodic + * bandwidth as required by USB 2.0 + */ + ehci->uframe_periodic_max = 100; + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + ehci->periodic_size = DEFAULT_I_TDPS; + INIT_LIST_HEAD(&ehci->async_unlink); + INIT_LIST_HEAD(&ehci->async_idle); + INIT_LIST_HEAD(&ehci->intr_unlink); + INIT_LIST_HEAD(&ehci->intr_qh_list); + INIT_LIST_HEAD(&ehci->cached_itd_list); + INIT_LIST_HEAD(&ehci->cached_sitd_list); + + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + switch (EHCI_TUNE_FLS) { + case 0: ehci->periodic_size = 1024; break; + case 1: ehci->periodic_size = 512; break; + case 2: ehci->periodic_size = 256; break; + default: BUG(); + } + } + if ((retval = ehci_mem_init(ehci, GFP_KERNEL)) < 0) + return retval; + + /* controllers may cache some of the periodic schedule ... */ + if (HCC_ISOC_CACHE(hcc_params)) // full frame cache + ehci->i_thresh = 0; + else // N microframes cached + ehci->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + /* + * dedicate a qh for the async ring head, since we couldn't unlink + * a 'real' qh without stopping the async schedule [4.8]. use it + * as the 'reclamation list head' too. + * its dummy is used in hw_alt_next of many tds, to prevent the qh + * from automatically advancing to the next td after short reads. + */ + ehci->async->qh_next.qh = NULL; + hw = ehci->async->hw; + hw->hw_next = QH_NEXT(ehci, ehci->async->qh_dma); + hw->hw_info1 = cpu_to_hc32(ehci, QH_HEAD); +#if defined(CONFIG_PPC_PS3) + hw->hw_info1 |= cpu_to_hc32(ehci, QH_INACTIVATE); +#endif + hw->hw_token = cpu_to_hc32(ehci, QTD_STS_HALT); + hw->hw_qtd_next = EHCI_LIST_END(ehci); + ehci->async->qh_state = QH_STATE_LINKED; + hw->hw_alt_next = QTD_NEXT(ehci, ehci->async->dummy->qtd_dma); + + /* clear interrupt enables, set irq latency */ + if (log2_irq_thresh < 0 || log2_irq_thresh > 6) + log2_irq_thresh = 0; + temp = 1 << (16 + log2_irq_thresh); + if (HCC_PER_PORT_CHANGE_EVENT(hcc_params)) { + ehci->has_ppcd = 1; + ehci_dbg(ehci, "enable per-port change event\n"); + temp |= CMD_PPCEE; + } + if (HCC_CANPARK(hcc_params)) { + /* HW default park == 3, on hardware that supports it (like + * NVidia and ALI silicon), maximizes throughput on the async + * schedule by avoiding QH fetches between transfers. + * + * With fast usb storage devices and NForce2, "park" seems to + * make problems: throughput reduction (!), data errors... + */ + if (park) { + park = min(park, (unsigned) 3); + temp |= CMD_PARK; + temp |= park << 8; + } + ehci_dbg(ehci, "park %d\n", park); + } + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + temp &= ~(3 << 2); + temp |= (EHCI_TUNE_FLS << 2); + } + ehci->command = temp; + + /* Accept arbitrarily long scatter-gather lists */ + if (!(hcd->driver->flags & HCD_LOCAL_MEM)) + hcd->self.sg_tablesize = ~0; + return 0; +} + +/* start HC running; it's halted, ehci_init() has been run (once) */ +static int ehci_run (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 temp; + u32 hcc_params; + + hcd->uses_new_polling = 1; + + /* EHCI spec section 4.1 */ + + ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); + ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); + + /* + * hcc_params controls whether ehci->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * pci_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like pci_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dma_supported(), so + * drivers can pass this info along ... like NETIF_F_HIGHDMA, + * Scsi_Host.highmem_io, and so forth. It's readonly to all + * host side drivers though. + */ + hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params); + if (HCC_64BIT_ADDR(hcc_params)) { +#ifdef CONFIG_ARM64 + ehci_writel(ehci, ehci->periodic_dma >> 32, &ehci->regs->segment); + /* + * this is deeply broken on almost all architectures + * but arm64 can use it so enable it + */ + if (!dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64))) + ehci_info(ehci, "enabled 64bit DMA\n"); +#else + ehci_writel(ehci, 0, &ehci->regs->segment); +#endif + } + + + // Philips, Intel, and maybe others need CMD_RUN before the + // root hub will detect new devices (why?); NEC doesn't + ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); + ehci->command |= CMD_RUN; + ehci_writel(ehci, ehci->command, &ehci->regs->command); + dbg_cmd (ehci, "init", ehci->command); + + /* + * Start, enabling full USB 2.0 functionality ... usb 1.1 devices + * are explicitly handed to companion controller(s), so no TT is + * involved with the root hub. (Except where one is integrated, + * and there's no companion controller unless maybe for USB OTG.) + * + * Turning on the CF flag will transfer ownership of all ports + * from the companions to the EHCI controller. If any of the + * companions are in the middle of a port reset at the time, it + * could cause trouble. Write-locking ehci_cf_port_reset_rwsem + * guarantees that no resets are in progress. After we set CF, + * a short delay lets the hardware catch up; new resets shouldn't + * be started before the port switching actions could complete. + */ + down_write(&ehci_cf_port_reset_rwsem); + ehci->rh_state = EHCI_RH_RUNNING; + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ + msleep(5); + up_write(&ehci_cf_port_reset_rwsem); + ehci->last_periodic_enable = ktime_get_real(); + + temp = HC_VERSION(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + ehci_info (ehci, + "USB %x.%x started, EHCI %x.%02x%s\n", + ((ehci->sbrn & 0xf0)>>4), (ehci->sbrn & 0x0f), + temp >> 8, temp & 0xff, + ignore_oc ? ", overcurrent ignored" : ""); + + ehci_writel(ehci, INTR_MASK, + &ehci->regs->intr_enable); /* Turn On Interrupts */ + + /* GRR this is run-once init(), being done every time the HC starts. + * So long as they're part of class devices, we can't do it init() + * since the class device isn't created that early. + */ + create_debug_files(ehci); + create_sysfs_files(ehci); + + return 0; +} + +int ehci_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + ehci->regs = (void __iomem *)ehci->caps + + HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + + ehci->sbrn = HCD_USB2; + + /* data structure init */ + retval = ehci_init(hcd); + if (retval) + return retval; + + retval = ehci_halt(ehci); + if (retval) + return retval; + + ehci_reset(ehci); + + return 0; +} +EXPORT_SYMBOL_GPL(ehci_setup); + +/*-------------------------------------------------------------------------*/ + +static irqreturn_t ehci_irq (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 status, masked_status, pcd_status = 0, cmd; + int bh; + unsigned long flags; + + /* + * For threadirqs option we use spin_lock_irqsave() variant to prevent + * deadlock with ehci hrtimer callback, because hrtimer callbacks run + * in interrupt context even when threadirqs is specified. We can go + * back to spin_lock() variant when hrtimer callbacks become threaded. + */ + spin_lock_irqsave(&ehci->lock, flags); + + status = ehci_readl(ehci, &ehci->regs->status); + + /* e.g. cardbus physical eject */ + if (status == ~(u32) 0) { + ehci_dbg (ehci, "device removed\n"); + goto dead; + } + + /* + * We don't use STS_FLR, but some controllers don't like it to + * remain on, so mask it out along with the other status bits. + */ + masked_status = status & (INTR_MASK | STS_FLR); + + /* Shared IRQ? */ + if (!masked_status || unlikely(ehci->rh_state == EHCI_RH_HALTED)) { + spin_unlock_irqrestore(&ehci->lock, flags); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + ehci_writel(ehci, masked_status, &ehci->regs->status); + cmd = ehci_readl(ehci, &ehci->regs->command); + bh = 0; + +#ifdef VERBOSE_DEBUG + /* unrequested/ignored: Frame List Rollover */ + dbg_status (ehci, "irq", status); +#endif + + /* INT, ERR, and IAA interrupt rates can be throttled */ + + /* normal [4.15.1.2] or error [4.15.1.1] completion */ + if (likely ((status & (STS_INT|STS_ERR)) != 0)) { + if (likely ((status & STS_ERR) == 0)) + COUNT (ehci->stats.normal); + else + COUNT (ehci->stats.error); + bh = 1; + } + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + + /* Turn off the IAA watchdog */ + ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_IAA_WATCHDOG); + + /* + * Mild optimization: Allow another IAAD to reset the + * hrtimer, if one occurs before the next expiration. + * In theory we could always cancel the hrtimer, but + * tests show that about half the time it will be reset + * for some other event anyway. + */ + if (ehci->next_hrtimer_event == EHCI_HRTIMER_IAA_WATCHDOG) + ++ehci->next_hrtimer_event; + + /* guard against (alleged) silicon errata */ + if (cmd & CMD_IAAD) + ehci_dbg(ehci, "IAA with IAAD still set?\n"); + if (ehci->iaa_in_progress) + COUNT(ehci->stats.iaa); + end_unlink_async(ehci); + } + + /* remote wakeup [4.3.1] */ + if (status & STS_PCD) { + unsigned i = HCS_N_PORTS (ehci->hcs_params); + u32 ppcd = ~0; + + /* kick root hub later */ + pcd_status = status; + + /* resume root hub? */ + if (ehci->rh_state == EHCI_RH_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + + /* get per-port change detect bits */ + if (ehci->has_ppcd) + ppcd = status >> 16; + + while (i--) { + int pstatus; + + /* leverage per-port change bits feature */ + if (!(ppcd & (1 << i))) + continue; + pstatus = ehci_readl(ehci, + &ehci->regs->port_status[i]); + + if (pstatus & PORT_OWNER) + continue; + if (!(test_bit(i, &ehci->suspended_ports) && + ((pstatus & PORT_RESUME) || + !(pstatus & PORT_SUSPEND)) && + (pstatus & PORT_PE) && + ehci->reset_done[i] == 0)) + continue; + + /* start 20 msec resume signaling from this port, + * and make khubd collect PORT_STAT_C_SUSPEND to + * stop that signaling. Use 5 ms extra for safety, + * like usb_port_resume() does. + */ + ehci->reset_done[i] = jiffies + msecs_to_jiffies(25); + set_bit(i, &ehci->resuming_ports); + ehci_dbg (ehci, "port %d remote wakeup\n", i + 1); + usb_hcd_start_port_resume(&hcd->self, i); + mod_timer(&hcd->rh_timer, ehci->reset_done[i]); + } + } + + /* PCI errors [4.15.2.4] */ + if (unlikely ((status & STS_FATAL) != 0)) { + ehci_err(ehci, "fatal error\n"); + dbg_cmd(ehci, "fatal", cmd); + dbg_status(ehci, "fatal", status); +dead: + usb_hc_died(hcd); + + /* Don't let the controller do anything more */ + ehci->shutdown = true; + ehci->rh_state = EHCI_RH_STOPPING; + ehci->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE); + ehci_writel(ehci, ehci->command, &ehci->regs->command); + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + ehci_handle_controller_death(ehci); + + /* Handle completions when the controller stops */ + bh = 0; + } + + if (bh) + ehci_work (ehci); + spin_unlock_irqrestore(&ehci->lock, flags); + if (pcd_status) + usb_hcd_poll_rh_status(hcd); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* + * non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + * + * urb + dev is in hcd.self.controller.urb_list + * we're queueing TDs onto software and hardware lists + * + * hcd-specific init for hcpriv hasn't been done yet + * + * NOTE: control, bulk, and interrupt share the same code to append TDs + * to a (possibly active) QH, and the same QH scanning code. + */ +static int ehci_urb_enqueue ( + struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags +) { + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + struct list_head qtd_list; + + INIT_LIST_HEAD (&qtd_list); + + switch (usb_pipetype (urb->pipe)) { + case PIPE_CONTROL: + /* qh_completions() code doesn't handle all the fault cases + * in multi-TD control transfers. Even 1KB is rare anyway. + */ + if (urb->transfer_buffer_length > (16 * 1024)) + return -EMSGSIZE; + /* FALLTHROUGH */ + /* case PIPE_BULK: */ + default: + if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return submit_async(ehci, urb, &qtd_list, mem_flags); + + case PIPE_INTERRUPT: + if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit(ehci, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: + if (urb->dev->speed == USB_SPEED_HIGH) + return itd_submit (ehci, urb, mem_flags); + else + return sitd_submit (ehci, urb, mem_flags); + } +} + +/* remove from hardware lists + * completions normally happen asynchronously + */ + +static int ehci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + struct ehci_qh *qh; + unsigned long flags; + int rc; + + spin_lock_irqsave (&ehci->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + /* + * We don't expedite dequeue for isochronous URBs. + * Just wait until they complete normally or their + * time slot expires. + */ + } else { + qh = (struct ehci_qh *) urb->hcpriv; + qh->exception = 1; + switch (qh->qh_state) { + case QH_STATE_LINKED: + if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) + start_unlink_intr(ehci, qh); + else + start_unlink_async(ehci, qh); + break; + case QH_STATE_COMPLETING: + qh->dequeue_during_giveback = 1; + break; + case QH_STATE_UNLINK: + case QH_STATE_UNLINK_WAIT: + /* already started */ + break; + case QH_STATE_IDLE: + /* QH might be waiting for a Clear-TT-Buffer */ + qh_completions(ehci, qh); + break; + } + } +done: + spin_unlock_irqrestore (&ehci->lock, flags); + return rc; +} + +/*-------------------------------------------------------------------------*/ + +// bulk qh holds the data toggle + +static void +ehci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + unsigned long flags; + struct ehci_qh *qh, *tmp; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +rescan: + spin_lock_irqsave (&ehci->lock, flags); + qh = ep->hcpriv; + if (!qh) + goto done; + + /* endpoints can be iso streams. for now, we don't + * accelerate iso completions ... so spin a while. + */ + if (qh->hw == NULL) { + struct ehci_iso_stream *stream = ep->hcpriv; + + if (!list_empty(&stream->td_list)) + goto idle_timeout; + + /* BUG_ON(!list_empty(&stream->free_list)); */ + kfree(stream); + goto done; + } + + qh->exception = 1; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + for (tmp = ehci->async->qh_next.qh; + tmp && tmp != qh; + tmp = tmp->qh_next.qh) + continue; + /* periodic qh self-unlinks on empty, and a COMPLETING qh + * may already be unlinked. + */ + if (tmp) + start_unlink_async(ehci, qh); + /* FALL THROUGH */ + case QH_STATE_UNLINK: /* wait for hw to finish? */ + case QH_STATE_UNLINK_WAIT: +idle_timeout: + spin_unlock_irqrestore (&ehci->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case QH_STATE_IDLE: /* fully unlinked */ + if (qh->clearing_tt) + goto idle_timeout; + if (list_empty (&qh->qtd_list)) { + qh_destroy(ehci, qh); + break; + } + /* else FALL THROUGH */ + default: + /* caller was supposed to have unlinked any requests; + * that's not our job. just leak this memory. + */ + ehci_err (ehci, "qh %p (#%02x) state %d%s\n", + qh, ep->desc.bEndpointAddress, qh->qh_state, + list_empty (&qh->qtd_list) ? "" : "(has tds)"); + break; + } + done: + ep->hcpriv = NULL; + spin_unlock_irqrestore (&ehci->lock, flags); +} + +static void +ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct ehci_qh *qh; + int eptype = usb_endpoint_type(&ep->desc); + int epnum = usb_endpoint_num(&ep->desc); + int is_out = usb_endpoint_dir_out(&ep->desc); + unsigned long flags; + + if (eptype != USB_ENDPOINT_XFER_BULK && eptype != USB_ENDPOINT_XFER_INT) + return; + + spin_lock_irqsave(&ehci->lock, flags); + qh = ep->hcpriv; + + /* For Bulk and Interrupt endpoints we maintain the toggle state + * in the hardware; the toggle bits in udev aren't used at all. + * When an endpoint is reset by usb_clear_halt() we must reset + * the toggle bit in the QH. + */ + if (qh) { + usb_settoggle(qh->dev, epnum, is_out, 0); + if (!list_empty(&qh->qtd_list)) { + WARN_ONCE(1, "clear_halt for a busy endpoint\n"); + } else { + /* The toggle value in the QH can't be updated + * while the QH is active. Unlink it now; + * re-linking will call qh_refresh(). + */ + qh->exception = 1; + if (eptype == USB_ENDPOINT_XFER_BULK) + start_unlink_async(ehci, qh); + else + start_unlink_intr(ehci, qh); + } + } + spin_unlock_irqrestore(&ehci->lock, flags); +} + +static int ehci_get_frame (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + return (ehci_read_frame_index(ehci) >> 3) % ehci->periodic_size; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +/* suspend/resume, section 4.3 */ + +/* These routines handle the generic parts of controller suspend/resume */ + +int ehci_suspend(struct usb_hcd *hcd, bool do_wakeup) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + if (time_before(jiffies, ehci->next_statechange)) + msleep(10); + + /* + * Root hub was already suspended. Disable IRQ emission and + * mark HW unaccessible. The PM and USB cores make sure that + * the root hub is either suspended or stopped. + */ + ehci_prepare_ports_for_controller_suspend(ehci, do_wakeup); + + spin_lock_irq(&ehci->lock); + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + (void) ehci_readl(ehci, &ehci->regs->intr_enable); + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irq(&ehci->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(ehci_suspend); + +/* Returns 0 if power was preserved, 1 if power was lost */ +int ehci_resume(struct usb_hcd *hcd, bool hibernated) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + if (time_before(jiffies, ehci->next_statechange)) + msleep(100); + + /* Mark hardware accessible again as we are back to full power by now */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + if (ehci->shutdown) + return 0; /* Controller is dead */ + + /* + * If CF is still set and we aren't resuming from hibernation + * then we maintained suspend power. + * Just undo the effect of ehci_suspend(). + */ + if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF && + !hibernated) { + int mask = INTR_MASK; + + ehci_prepare_ports_for_controller_resume(ehci); + + spin_lock_irq(&ehci->lock); + if (ehci->shutdown) + goto skip; + + if (!hcd->self.root_hub->do_remote_wakeup) + mask &= ~STS_PCD; + ehci_writel(ehci, mask, &ehci->regs->intr_enable); + ehci_readl(ehci, &ehci->regs->intr_enable); + skip: + spin_unlock_irq(&ehci->lock); + return 0; + } + + /* + * Else reset, to cope with power loss or resume from hibernation + * having let the firmware kick in during reboot. + */ + usb_root_hub_lost_power(hcd->self.root_hub); + (void) ehci_halt(ehci); + (void) ehci_reset(ehci); + + spin_lock_irq(&ehci->lock); + if (ehci->shutdown) + goto skip; + + ehci_writel(ehci, ehci->command, &ehci->regs->command); + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ + + ehci->rh_state = EHCI_RH_SUSPENDED; + spin_unlock_irq(&ehci->lock); + + return 1; +} +EXPORT_SYMBOL_GPL(ehci_resume); + +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * Generic structure: This gets copied for platform drivers so that + * individual entries can be overridden as needed. + */ + +static const struct hc_driver ehci_hc_driver = { + .description = hcd_name, + .product_desc = "EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, +}; + +void ehci_init_driver(struct hc_driver *drv, + const struct ehci_driver_overrides *over) +{ + /* Copy the generic table to drv and then apply the overrides */ + *drv = ehci_hc_driver; + + if (over) { + drv->hcd_priv_size += over->extra_priv_size; + if (over->reset) + drv->reset = over->reset; + } +} +EXPORT_SYMBOL_GPL(ehci_init_driver); + +/*-------------------------------------------------------------------------*/ + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_LICENSE ("GPL"); + +#ifdef CONFIG_USB_EHCI_FSL +#include "ehci-fsl.c" +#define PLATFORM_DRIVER ehci_fsl_driver +#endif + +#ifdef CONFIG_USB_EHCI_SH +#include "ehci-sh.c" +#define PLATFORM_DRIVER ehci_hcd_sh_driver +#endif + +#ifdef CONFIG_PPC_PS3 +#include "ehci-ps3.c" +#define PS3_SYSTEM_BUS_DRIVER ps3_ehci_driver +#endif + +#ifdef CONFIG_USB_EHCI_HCD_PPC_OF +#include "ehci-ppc-of.c" +#define OF_PLATFORM_DRIVER ehci_hcd_ppc_of_driver +#endif + +#ifdef CONFIG_XPS_USB_HCD_XILINX +#include "ehci-xilinx-of.c" +#define XILINX_OF_PLATFORM_DRIVER ehci_hcd_xilinx_of_driver +#endif + +#ifdef CONFIG_USB_W90X900_EHCI +#include "ehci-w90x900.c" +#define PLATFORM_DRIVER ehci_hcd_w90x900_driver +#endif + +#ifdef CONFIG_USB_OCTEON_EHCI +#include "ehci-octeon.c" +#define PLATFORM_DRIVER ehci_octeon_driver +#endif + +#ifdef CONFIG_TILE_USB +#include "ehci-tilegx.c" +#define PLATFORM_DRIVER ehci_hcd_tilegx_driver +#endif + +#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP +#include "ehci-pmcmsp.c" +#define PLATFORM_DRIVER ehci_hcd_msp_driver +#endif + +#ifdef CONFIG_USB_EHCI_TEGRA +#include "ehci-tegra.c" +#define PLATFORM_DRIVER tegra_ehci_driver +#endif + +#ifdef CONFIG_SPARC_LEON +#include "ehci-grlib.c" +#define PLATFORM_DRIVER ehci_grlib_driver +#endif + +#ifdef CONFIG_USB_EHCI_MV +#include "ehci-mv.c" +#define PLATFORM_DRIVER ehci_mv_driver +#endif + +#ifdef CONFIG_MIPS_SEAD3 +#include "ehci-sead3.c" +#define PLATFORM_DRIVER ehci_hcd_sead3_driver +#endif + +static int __init ehci_hcd_init(void) +{ + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + printk(KERN_INFO "%s: " DRIVER_DESC "\n", hcd_name); + set_bit(USB_EHCI_LOADED, &usb_hcds_loaded); + if (test_bit(USB_UHCI_LOADED, &usb_hcds_loaded) || + test_bit(USB_OHCI_LOADED, &usb_hcds_loaded)) + printk(KERN_WARNING "Warning! ehci_hcd should always be loaded" + " before uhci_hcd and ohci_hcd, not after\n"); + + pr_debug("%s: block sizes: qh %Zd qtd %Zd itd %Zd sitd %Zd\n", + hcd_name, + sizeof(struct ehci_qh), sizeof(struct ehci_qtd), + sizeof(struct ehci_itd), sizeof(struct ehci_sitd)); + +#ifdef DEBUG + ehci_debug_root = debugfs_create_dir("ehci", usb_debug_root); + if (!ehci_debug_root) { + retval = -ENOENT; + goto err_debug; + } +#endif + +#ifdef PLATFORM_DRIVER + retval = platform_driver_register(&PLATFORM_DRIVER); + if (retval < 0) + goto clean0; +#endif + +#ifdef PS3_SYSTEM_BUS_DRIVER + retval = ps3_ehci_driver_register(&PS3_SYSTEM_BUS_DRIVER); + if (retval < 0) + goto clean2; +#endif + +#ifdef OF_PLATFORM_DRIVER + retval = platform_driver_register(&OF_PLATFORM_DRIVER); + if (retval < 0) + goto clean3; +#endif + +#ifdef XILINX_OF_PLATFORM_DRIVER + retval = platform_driver_register(&XILINX_OF_PLATFORM_DRIVER); + if (retval < 0) + goto clean4; +#endif + return retval; + +#ifdef XILINX_OF_PLATFORM_DRIVER + /* platform_driver_unregister(&XILINX_OF_PLATFORM_DRIVER); */ +clean4: +#endif +#ifdef OF_PLATFORM_DRIVER + platform_driver_unregister(&OF_PLATFORM_DRIVER); +clean3: +#endif +#ifdef PS3_SYSTEM_BUS_DRIVER + ps3_ehci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); +clean2: +#endif +#ifdef PLATFORM_DRIVER + platform_driver_unregister(&PLATFORM_DRIVER); +clean0: +#endif +#ifdef DEBUG + debugfs_remove(ehci_debug_root); + ehci_debug_root = NULL; +err_debug: +#endif + clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); + return retval; +} +module_init(ehci_hcd_init); + +static void __exit ehci_hcd_cleanup(void) +{ +#ifdef XILINX_OF_PLATFORM_DRIVER + platform_driver_unregister(&XILINX_OF_PLATFORM_DRIVER); +#endif +#ifdef OF_PLATFORM_DRIVER + platform_driver_unregister(&OF_PLATFORM_DRIVER); +#endif +#ifdef PLATFORM_DRIVER + platform_driver_unregister(&PLATFORM_DRIVER); +#endif +#ifdef PS3_SYSTEM_BUS_DRIVER + ps3_ehci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); +#endif +#ifdef DEBUG + debugfs_remove(ehci_debug_root); +#endif + clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); +} +module_exit(ehci_hcd_cleanup); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c new file mode 100644 index 00000000..ca6289b4 --- /dev/null +++ b/drivers/usb/host/ehci-hub.c @@ -0,0 +1,1139 @@ +/* + * Copyright (C) 2001-2004 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Root Hub ... the nonsharable stuff + * + * Registers don't need cpu_to_le32, that happens transparently + */ + +/*-------------------------------------------------------------------------*/ +#include + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) + +#ifdef CONFIG_PM + +static int ehci_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +); + +/* After a power loss, ports that were owned by the companion must be + * reset so that the companion can still own them. + */ +static void ehci_handover_companion_ports(struct ehci_hcd *ehci) +{ + u32 __iomem *reg; + u32 status; + int port; + __le32 buf; + struct usb_hcd *hcd = ehci_to_hcd(ehci); + + if (!ehci->owned_ports) + return; + + /* Make sure the ports are powered */ + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + if (test_bit(port, &ehci->owned_ports)) { + reg = &ehci->regs->port_status[port]; + status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + if (!(status & PORT_POWER)) { + status |= PORT_POWER; + ehci_writel(ehci, status, reg); + } + } + } + + /* Give the connections some time to appear */ + msleep(20); + + spin_lock_irq(&ehci->lock); + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + if (test_bit(port, &ehci->owned_ports)) { + reg = &ehci->regs->port_status[port]; + status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + + /* Port already owned by companion? */ + if (status & PORT_OWNER) + clear_bit(port, &ehci->owned_ports); + else if (test_bit(port, &ehci->companion_ports)) + ehci_writel(ehci, status & ~PORT_PE, reg); + else { + spin_unlock_irq(&ehci->lock); + ehci_hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_RESET, port + 1, + NULL, 0); + spin_lock_irq(&ehci->lock); + } + } + } + spin_unlock_irq(&ehci->lock); + + if (!ehci->owned_ports) + return; + msleep(90); /* Wait for resets to complete */ + + spin_lock_irq(&ehci->lock); + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + if (test_bit(port, &ehci->owned_ports)) { + spin_unlock_irq(&ehci->lock); + ehci_hub_control(hcd, GetPortStatus, + 0, port + 1, + (char *) &buf, sizeof(buf)); + spin_lock_irq(&ehci->lock); + + /* The companion should now own the port, + * but if something went wrong the port must not + * remain enabled. + */ + reg = &ehci->regs->port_status[port]; + status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + if (status & PORT_OWNER) + ehci_writel(ehci, status | PORT_CSC, reg); + else { + ehci_dbg(ehci, "failed handover port %d: %x\n", + port + 1, status); + ehci_writel(ehci, status & ~PORT_PE, reg); + } + } + } + + ehci->owned_ports = 0; + spin_unlock_irq(&ehci->lock); +} + +static int ehci_port_change(struct ehci_hcd *ehci) +{ + int i = HCS_N_PORTS(ehci->hcs_params); + + /* First check if the controller indicates a change event */ + + if (ehci_readl(ehci, &ehci->regs->status) & STS_PCD) + return 1; + + /* + * Not all controllers appear to update this while going from D3 to D0, + * so check the individual port status registers as well + */ + + while (i--) + if (ehci_readl(ehci, &ehci->regs->port_status[i]) & PORT_CSC) + return 1; + + return 0; +} + +static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, + bool suspending, bool do_wakeup) +{ + int port; + u32 temp; + + /* If remote wakeup is enabled for the root hub but disabled + * for the controller, we must adjust all the port wakeup flags + * when the controller is suspended or resumed. In all other + * cases they don't need to be changed. + */ + if (!ehci_to_hcd(ehci)->self.root_hub->do_remote_wakeup || do_wakeup) + return; + + spin_lock_irq(&ehci->lock); + + /* clear phy low-power mode before changing wakeup flags */ + if (ehci->has_hostpc) { + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *hostpc_reg = &ehci->regs->hostpc[port]; + + temp = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp & ~HOSTPC_PHCD, hostpc_reg); + } + spin_unlock_irq(&ehci->lock); + msleep(5); + spin_lock_irq(&ehci->lock); + } + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *reg = &ehci->regs->port_status[port]; + u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + u32 t2 = t1 & ~PORT_WAKE_BITS; + + /* If we are suspending the controller, clear the flags. + * If we are resuming the controller, set the wakeup flags. + */ + if (!suspending) { + if (t1 & PORT_CONNECT) + t2 |= PORT_WKOC_E | PORT_WKDISC_E; + else + t2 |= PORT_WKOC_E | PORT_WKCONN_E; + } + ehci_vdbg(ehci, "port %d, %08x -> %08x\n", + port + 1, t1, t2); + ehci_writel(ehci, t2, reg); + } + + /* enter phy low-power mode again */ + if (ehci->has_hostpc) { + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *hostpc_reg = &ehci->regs->hostpc[port]; + + temp = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp | HOSTPC_PHCD, hostpc_reg); + } + } + + /* Does the root hub have a port wakeup pending? */ + if (!suspending && ehci_port_change(ehci)) + usb_hcd_resume_root_hub(ehci_to_hcd(ehci)); + + spin_unlock_irq(&ehci->lock); +} + +static int ehci_bus_suspend (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + int port; + int mask; + int changed; + + ehci_dbg(ehci, "suspend root hub\n"); + + if (time_before (jiffies, ehci->next_statechange)) + msleep(5); + + /* stop the schedules */ + ehci_quiesce(ehci); + + spin_lock_irq (&ehci->lock); + if (ehci->rh_state < EHCI_RH_RUNNING) + goto done; + + /* Once the controller is stopped, port resumes that are already + * in progress won't complete. Hence if remote wakeup is enabled + * for the root hub and any ports are in the middle of a resume or + * remote wakeup, we must fail the suspend. + */ + if (hcd->self.root_hub->do_remote_wakeup) { + if (ehci->resuming_ports) { + spin_unlock_irq(&ehci->lock); + ehci_dbg(ehci, "suspend failed because a port is resuming\n"); + return -EBUSY; + } + } + + /* Unlike other USB host controller types, EHCI doesn't have + * any notion of "global" or bus-wide suspend. The driver has + * to manually suspend all the active unsuspended ports, and + * then manually resume them in the bus_resume() routine. + */ + ehci->bus_suspended = 0; + ehci->owned_ports = 0; + changed = 0; + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *reg = &ehci->regs->port_status [port]; + u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + u32 t2 = t1 & ~PORT_WAKE_BITS; + + /* keep track of which ports we suspend */ + if (t1 & PORT_OWNER) + set_bit(port, &ehci->owned_ports); + else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) { + t2 |= PORT_SUSPEND; + set_bit(port, &ehci->bus_suspended); + } + + /* enable remote wakeup on all ports, if told to do so */ + if (hcd->self.root_hub->do_remote_wakeup) { + /* only enable appropriate wake bits, otherwise the + * hardware can not go phy low power mode. If a race + * condition happens here(connection change during bits + * set), the port change detection will finally fix it. + */ + if (t1 & PORT_CONNECT) + t2 |= PORT_WKOC_E | PORT_WKDISC_E; + else + t2 |= PORT_WKOC_E | PORT_WKCONN_E; + } + + if (t1 != t2) { + ehci_vdbg (ehci, "port %d, %08x -> %08x\n", + port + 1, t1, t2); + ehci_writel(ehci, t2, reg); + changed = 1; + } + } + + if (changed && ehci->has_hostpc) { + spin_unlock_irq(&ehci->lock); + msleep(5); /* 5 ms for HCD to enter low-power mode */ + spin_lock_irq(&ehci->lock); + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *hostpc_reg = &ehci->regs->hostpc[port]; + u32 t3; + + t3 = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, t3 | HOSTPC_PHCD, hostpc_reg); + t3 = ehci_readl(ehci, hostpc_reg); + ehci_dbg(ehci, "Port %d phy low-power mode %s\n", + port, (t3 & HOSTPC_PHCD) ? + "succeeded" : "failed"); + } + } + spin_unlock_irq(&ehci->lock); + + /* Apparently some devices need a >= 1-uframe delay here */ + if (ehci->bus_suspended) + udelay(150); + + /* turn off now-idle HC */ + ehci_halt (ehci); + + spin_lock_irq(&ehci->lock); + if (ehci->enabled_hrtimer_events & BIT(EHCI_HRTIMER_POLL_DEAD)) + ehci_handle_controller_death(ehci); + if (ehci->rh_state != EHCI_RH_RUNNING) + goto done; + ehci->rh_state = EHCI_RH_SUSPENDED; + + end_unlink_async(ehci); + unlink_empty_async_suspended(ehci); + ehci_handle_intr_unlinks(ehci); + end_free_itds(ehci); + + /* allow remote wakeup */ + mask = INTR_MASK; + if (!hcd->self.root_hub->do_remote_wakeup) + mask &= ~STS_PCD; + ehci_writel(ehci, mask, &ehci->regs->intr_enable); + ehci_readl(ehci, &ehci->regs->intr_enable); + + done: + ehci->next_statechange = jiffies + msecs_to_jiffies(10); + ehci->enabled_hrtimer_events = 0; + ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT; + spin_unlock_irq (&ehci->lock); + + hrtimer_cancel(&ehci->hrtimer); + return 0; +} + + +/* caller has locked the root hub, and should reset/reinit on error */ +static int ehci_bus_resume (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 temp; + u32 power_okay; + int i; + unsigned long resume_needed = 0; + + if (time_before (jiffies, ehci->next_statechange)) + msleep(5); + spin_lock_irq (&ehci->lock); + if (!HCD_HW_ACCESSIBLE(hcd) || ehci->shutdown) + goto shutdown; + + if (unlikely(ehci->debug)) { + if (!dbgp_reset_prep(hcd)) + ehci->debug = NULL; + else + dbgp_external_startup(hcd); + } + + /* Ideally and we've got a real resume here, and no port's power + * was lost. (For PCI, that means Vaux was maintained.) But we + * could instead be restoring a swsusp snapshot -- so that BIOS was + * the last user of the controller, not reset/pm hardware keeping + * state we gave to it. + */ + power_okay = ehci_readl(ehci, &ehci->regs->intr_enable); + ehci_dbg(ehci, "resume root hub%s\n", + power_okay ? "" : " after power loss"); + + /* at least some APM implementations will try to deliver + * IRQs right away, so delay them until we're ready. + */ + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + + /* re-init operational registers */ + ehci_writel(ehci, 0, &ehci->regs->segment); + ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); + ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next); + + /* restore CMD_RUN, framelist size, and irq threshold */ + ehci->command |= CMD_RUN; + ehci_writel(ehci, ehci->command, &ehci->regs->command); + ehci->rh_state = EHCI_RH_RUNNING; + + /* + * According to Bugzilla #8190, the port status for some controllers + * will be wrong without a delay. At their wrong status, the port + * is enabled, but not suspended neither resumed. + */ + i = HCS_N_PORTS(ehci->hcs_params); + while (i--) { + temp = ehci_readl(ehci, &ehci->regs->port_status[i]); + if ((temp & PORT_PE) && + !(temp & (PORT_SUSPEND | PORT_RESUME))) { + ehci_dbg(ehci, "Port status(0x%x) is wrong\n", temp); + spin_unlock_irq(&ehci->lock); + msleep(8); + spin_lock_irq(&ehci->lock); + break; + } + } + + if (ehci->shutdown) + goto shutdown; + + /* clear phy low-power mode before resume */ + if (ehci->bus_suspended && ehci->has_hostpc) { + i = HCS_N_PORTS(ehci->hcs_params); + while (i--) { + if (test_bit(i, &ehci->bus_suspended)) { + u32 __iomem *hostpc_reg = + &ehci->regs->hostpc[i]; + + temp = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp & ~HOSTPC_PHCD, + hostpc_reg); + } + } + spin_unlock_irq(&ehci->lock); + msleep(5); + spin_lock_irq(&ehci->lock); + if (ehci->shutdown) + goto shutdown; + } + + /* manually resume the ports we suspended during bus_suspend() */ + i = HCS_N_PORTS (ehci->hcs_params); + while (i--) { + temp = ehci_readl(ehci, &ehci->regs->port_status [i]); + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + if (test_bit(i, &ehci->bus_suspended) && + (temp & PORT_SUSPEND)) { + temp |= PORT_RESUME; + set_bit(i, &resume_needed); + } + ehci_writel(ehci, temp, &ehci->regs->port_status [i]); + } + + /* msleep for 20ms only if code is trying to resume port */ + if (resume_needed) { + spin_unlock_irq(&ehci->lock); + msleep(20); + spin_lock_irq(&ehci->lock); + if (ehci->shutdown) + goto shutdown; + } + + i = HCS_N_PORTS (ehci->hcs_params); + while (i--) { + temp = ehci_readl(ehci, &ehci->regs->port_status [i]); + if (test_bit(i, &resume_needed)) { + temp &= ~(PORT_RWC_BITS | PORT_SUSPEND | PORT_RESUME); + ehci_writel(ehci, temp, &ehci->regs->port_status [i]); + ehci_vdbg (ehci, "resumed port %d\n", i + 1); + } + } + + ehci->next_statechange = jiffies + msecs_to_jiffies(5); + spin_unlock_irq(&ehci->lock); + + ehci_handover_companion_ports(ehci); + + /* Now we can safely re-enable irqs */ + spin_lock_irq(&ehci->lock); + if (ehci->shutdown) + goto shutdown; + ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); + (void) ehci_readl(ehci, &ehci->regs->intr_enable); + spin_unlock_irq(&ehci->lock); + + return 0; + + shutdown: + spin_unlock_irq(&ehci->lock); + return -ESHUTDOWN; +} + +#else + +#define ehci_bus_suspend NULL +#define ehci_bus_resume NULL + +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ + +/* + * Sets the owner of a port + */ +static void set_owner(struct ehci_hcd *ehci, int portnum, int new_owner) +{ + u32 __iomem *status_reg; + u32 port_status; + int try; + + status_reg = &ehci->regs->port_status[portnum]; + + /* + * The controller won't set the OWNER bit if the port is + * enabled, so this loop will sometimes require at least two + * iterations: one to disable the port and one to set OWNER. + */ + for (try = 4; try > 0; --try) { + spin_lock_irq(&ehci->lock); + port_status = ehci_readl(ehci, status_reg); + if ((port_status & PORT_OWNER) == new_owner + || (port_status & (PORT_OWNER | PORT_CONNECT)) + == 0) + try = 0; + else { + port_status ^= PORT_OWNER; + port_status &= ~(PORT_PE | PORT_RWC_BITS); + ehci_writel(ehci, port_status, status_reg); + } + spin_unlock_irq(&ehci->lock); + if (try > 1) + msleep(5); + } +} + +/*-------------------------------------------------------------------------*/ + +static int check_reset_complete ( + struct ehci_hcd *ehci, + int index, + u32 __iomem *status_reg, + int port_status +) { + if (!(port_status & PORT_CONNECT)) + return port_status; + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + + /* with integrated TT, there's nobody to hand it to! */ + if (ehci_is_TDI(ehci)) { + ehci_dbg (ehci, + "Failed to enable port %d on root hub TT\n", + index+1); + return port_status; + } + + ehci_dbg (ehci, "port %d full speed --> companion\n", + index + 1); + + // what happens if HCS_N_CC(params) == 0 ? + port_status |= PORT_OWNER; + port_status &= ~PORT_RWC_BITS; + ehci_writel(ehci, port_status, status_reg); + + /* ensure 440EPX ohci controller state is operational */ + if (ehci->has_amcc_usb23) + set_ohci_hcfs(ehci, 1); + } else { + ehci_dbg(ehci, "port %d reset complete, port enabled\n", + index + 1); + /* ensure 440EPx ohci controller state is suspended */ + if (ehci->has_amcc_usb23) + set_ohci_hcfs(ehci, 0); + } + + return port_status; +} + +/*-------------------------------------------------------------------------*/ + + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int +ehci_hub_status_data (struct usb_hcd *hcd, char *buf) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 temp, status; + u32 mask; + int ports, i, retval = 1; + unsigned long flags; + u32 ppcd = ~0; + + /* init status to no-changes */ + buf [0] = 0; + ports = HCS_N_PORTS (ehci->hcs_params); + if (ports > 7) { + buf [1] = 0; + retval++; + } + + /* Inform the core about resumes-in-progress by returning + * a non-zero value even if there are no status changes. + */ + status = ehci->resuming_ports; + + /* Some boards (mostly VIA?) report bogus overcurrent indications, + * causing massive log spam unless we completely ignore them. It + * may be relevant that VIA VT8235 controllers, where PORT_POWER is + * always set, seem to clear PORT_OCC and PORT_CSC when writing to + * PORT_POWER; that's surprising, but maybe within-spec. + */ + if (!ignore_oc) + mask = PORT_CSC | PORT_PEC | PORT_OCC; + else + mask = PORT_CSC | PORT_PEC; + // PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave (&ehci->lock, flags); + + /* get per-port change detect bits */ + if (ehci->has_ppcd) + ppcd = ehci_readl(ehci, &ehci->regs->status) >> 16; + + for (i = 0; i < ports; i++) { + /* leverage per-port change bits feature */ + if (ppcd & (1 << i)) + temp = ehci_readl(ehci, &ehci->regs->port_status[i]); + else + temp = 0; + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if ((temp & mask) != 0 || test_bit(i, &ehci->port_c_suspend) + || (ehci->reset_done[i] && time_after_eq( + jiffies, ehci->reset_done[i]))) { + if (i < 7) + buf [0] |= 1 << (i + 1); + else + buf [1] |= 1 << (i - 7); + status = STS_PCD; + } + } + + /* If a resume is in progress, make sure it can finish */ + if (ehci->resuming_ports) + mod_timer(&hcd->rh_timer, jiffies + msecs_to_jiffies(25)); + + spin_unlock_irqrestore (&ehci->lock, flags); + return status ? retval : 0; +} + +/*-------------------------------------------------------------------------*/ + +static void +ehci_hub_descriptor ( + struct ehci_hcd *ehci, + struct usb_hub_descriptor *desc +) { + int ports = HCS_N_PORTS (ehci->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* ehci 1.0, 2.3.9 says 20ms max */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + if (HCS_PPC (ehci->hcs_params)) + temp |= 0x0001; /* per-port power control */ + else + temp |= 0x0002; /* no power switching */ +#if 0 +// re-enable when we support USB_PORT_FEAT_INDICATOR below. + if (HCS_INDICATOR (ehci->hcs_params)) + temp |= 0x0080; /* per-port indicators (LEDs) */ +#endif + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +/*-------------------------------------------------------------------------*/ + +static int ehci_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + int ports = HCS_N_PORTS (ehci->hcs_params); + u32 __iomem *status_reg = &ehci->regs->port_status[ + (wIndex & 0xff) - 1]; + u32 __iomem *hostpc_reg = &ehci->regs->hostpc[(wIndex & 0xff) - 1]; + u32 temp, temp1, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave (&ehci->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = ehci_readl(ehci, status_reg); + temp &= ~PORT_RWC_BITS; + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, khubd needs to be able to clear + * the port-change status bits (especially + * USB_PORT_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + ehci_writel(ehci, temp & ~PORT_PE, status_reg); + break; + case USB_PORT_FEAT_C_ENABLE: + ehci_writel(ehci, temp | PORT_PEC, status_reg); + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + if (ehci->no_selective_suspend) + break; +#ifdef CONFIG_USB_OTG + if ((hcd->self.otg_port == (wIndex + 1)) + && hcd->self.b_hnp_enable) { + otg_start_hnp(hcd->phy->otg); + break; + } +#endif + if (!(temp & PORT_SUSPEND)) + break; + if ((temp & PORT_PE) == 0) + goto error; + + /* clear phy low-power mode before resume */ + if (ehci->has_hostpc) { + temp1 = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp1 & ~HOSTPC_PHCD, + hostpc_reg); + spin_unlock_irqrestore(&ehci->lock, flags); + msleep(5);/* wait to leave low-power mode */ + spin_lock_irqsave(&ehci->lock, flags); + } + /* resume signaling for 20 msec */ + temp &= ~PORT_WAKE_BITS; + ehci_writel(ehci, temp | PORT_RESUME, status_reg); + ehci->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + break; + case USB_PORT_FEAT_C_SUSPEND: + clear_bit(wIndex, &ehci->port_c_suspend); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC (ehci->hcs_params)) + ehci_writel(ehci, temp & ~PORT_POWER, + status_reg); + break; + case USB_PORT_FEAT_C_CONNECTION: + ehci_writel(ehci, temp | PORT_CSC, status_reg); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + ehci_writel(ehci, temp | PORT_OCC, status_reg); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + ehci_readl(ehci, &ehci->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset (buf, 0, 4); + //cpu_to_le32s ((u32 *) buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = ehci_readl(ehci, status_reg); + + // wPortChange bits + if (temp & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + if (temp & PORT_PEC) + status |= USB_PORT_STAT_C_ENABLE << 16; + + if ((temp & PORT_OCC) && !ignore_oc){ + status |= USB_PORT_STAT_C_OVERCURRENT << 16; + + /* + * Hubs should disable port power on over-current. + * However, not all EHCI implementations do this + * automatically, even if they _do_ support per-port + * power switching; they're allowed to just limit the + * current. khubd will turn the power back on. + */ + if (((temp & PORT_OC) || (ehci->need_oc_pp_cycle)) + && HCS_PPC(ehci->hcs_params)) { + ehci_writel(ehci, + temp & ~(PORT_RWC_BITS | PORT_POWER), + status_reg); + temp = ehci_readl(ehci, status_reg); + } + } + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + + /* Remote Wakeup received? */ + if (!ehci->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + ehci->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + usb_hcd_start_port_resume(&hcd->self, wIndex); + set_bit(wIndex, &ehci->resuming_ports); + /* check the port again */ + mod_timer(&ehci_to_hcd(ehci)->rh_timer, + ehci->reset_done[wIndex]); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + ehci->reset_done[wIndex])) { + clear_bit(wIndex, &ehci->suspended_ports); + set_bit(wIndex, &ehci->port_c_suspend); + ehci->reset_done[wIndex] = 0; + usb_hcd_end_port_resume(&hcd->self, wIndex); + + /* stop resume signaling */ + temp &= ~(PORT_RWC_BITS | + PORT_SUSPEND | PORT_RESUME); + ehci_writel(ehci, temp, status_reg); + clear_bit(wIndex, &ehci->resuming_ports); + retval = handshake(ehci, status_reg, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + ehci_err(ehci, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp = ehci_readl(ehci, status_reg); + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && time_after_eq(jiffies, + ehci->reset_done[wIndex])) { + status |= USB_PORT_STAT_C_RESET << 16; + ehci->reset_done [wIndex] = 0; + clear_bit(wIndex, &ehci->resuming_ports); + + /* force reset to complete */ + ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESET), + status_reg); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(ehci, status_reg, + PORT_RESET, 0, 1000); + if (retval != 0) { + ehci_err (ehci, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete (ehci, wIndex, status_reg, + ehci_readl(ehci, status_reg)); + } + + if (!(temp & (PORT_RESUME|PORT_RESET))) { + ehci->reset_done[wIndex] = 0; + clear_bit(wIndex, &ehci->resuming_ports); + } + + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &ehci->companion_ports)) { + temp &= ~PORT_RWC_BITS; + temp |= PORT_OWNER; + ehci_writel(ehci, temp, status_reg); + ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1); + temp = ehci_readl(ehci, status_reg); + } + + /* + * Even if OWNER is set, there's no harm letting khubd + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + // status may be from integrated TT + if (ehci->has_hostpc) { + temp1 = ehci_readl(ehci, hostpc_reg); + status |= ehci_port_speed(ehci, temp1); + } else + status |= ehci_port_speed(ehci, temp); + } + if (temp & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + + /* maybe the port was unsuspended without our knowledge */ + if (temp & (PORT_SUSPEND|PORT_RESUME)) { + status |= USB_PORT_STAT_SUSPEND; + } else if (test_bit(wIndex, &ehci->suspended_ports)) { + clear_bit(wIndex, &ehci->suspended_ports); + clear_bit(wIndex, &ehci->resuming_ports); + ehci->reset_done[wIndex] = 0; + if (temp & PORT_PE) + set_bit(wIndex, &ehci->port_c_suspend); + usb_hcd_end_port_resume(&hcd->self, wIndex); + } + + if (temp & PORT_OC) + status |= USB_PORT_STAT_OVERCURRENT; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (temp & PORT_POWER) + status |= USB_PORT_STAT_POWER; + if (test_bit(wIndex, &ehci->port_c_suspend)) + status |= USB_PORT_STAT_C_SUSPEND << 16; + +#ifndef VERBOSE_DEBUG + if (status & ~0xffff) /* only if wPortChange is interesting */ +#endif + dbg_port (ehci, "GetStatus", wIndex + 1, temp); + put_unaligned_le32(status, buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + selector = wIndex >> 8; + wIndex &= 0xff; + if (unlikely(ehci->debug)) { + /* If the debug port is active any port + * feature requests should get denied */ + if (wIndex == HCS_DEBUG_PORT(ehci->hcs_params) && + (readl(&ehci->debug->control) & DBGP_ENABLED)) { + retval = -ENODEV; + goto error_exit; + } + } + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = ehci_readl(ehci, status_reg); + if (temp & PORT_OWNER) + break; + + temp &= ~PORT_RWC_BITS; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if (ehci->no_selective_suspend) + break; + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + + /* After above check the port must be connected. + * Set appropriate bit thus could put phy into low power + * mode if we have hostpc feature + */ + temp &= ~PORT_WKCONN_E; + temp |= PORT_WKDISC_E | PORT_WKOC_E; + ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); + if (ehci->has_hostpc) { + spin_unlock_irqrestore(&ehci->lock, flags); + msleep(5);/* 5ms for HCD enter low pwr mode */ + spin_lock_irqsave(&ehci->lock, flags); + temp1 = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp1 | HOSTPC_PHCD, + hostpc_reg); + temp1 = ehci_readl(ehci, hostpc_reg); + ehci_dbg(ehci, "Port%d phy low pwr mode %s\n", + wIndex, (temp1 & HOSTPC_PHCD) ? + "succeeded" : "failed"); + } + set_bit(wIndex, &ehci->suspended_ports); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC (ehci->hcs_params)) + ehci_writel(ehci, temp | PORT_POWER, + status_reg); + break; + case USB_PORT_FEAT_RESET: + if (temp & PORT_RESUME) + goto error; + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT + && !ehci_is_TDI(ehci) + && PORT_USB11 (temp)) { + ehci_dbg (ehci, + "port %d low speed --> companion\n", + wIndex + 1); + temp |= PORT_OWNER; + } else { + ehci_vdbg (ehci, "port %d reset\n", wIndex + 1); + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + ehci->reset_done [wIndex] = jiffies + + msecs_to_jiffies (50); + } + ehci_writel(ehci, temp, status_reg); + break; + + /* For downstream facing ports (these): one hub port is put + * into test mode according to USB2 11.24.2.13, then the hub + * must be reset (which for root hub now means rmmod+modprobe, + * or else system reboot). See EHCI 2.3.9 and 4.14 for info + * about the EHCI-specific stuff. + */ + case USB_PORT_FEAT_TEST: + if (!selector || selector > 5) + goto error; + spin_unlock_irqrestore(&ehci->lock, flags); + ehci_quiesce(ehci); + spin_lock_irqsave(&ehci->lock, flags); + + /* Put all enabled ports into suspend */ + while (ports--) { + u32 __iomem *sreg = + &ehci->regs->port_status[ports]; + + temp = ehci_readl(ehci, sreg) & ~PORT_RWC_BITS; + if (temp & PORT_PE) + ehci_writel(ehci, temp | PORT_SUSPEND, + sreg); + } + + spin_unlock_irqrestore(&ehci->lock, flags); + ehci_halt(ehci); + spin_lock_irqsave(&ehci->lock, flags); + + temp = ehci_readl(ehci, status_reg); + temp |= selector << 16; + ehci_writel(ehci, temp, status_reg); + break; + + default: + goto error; + } + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } +error_exit: + spin_unlock_irqrestore (&ehci->lock, flags); + return retval; +} + +static void ehci_relinquish_port(struct usb_hcd *hcd, int portnum) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + if (ehci_is_TDI(ehci)) + return; + set_owner(ehci, --portnum, PORT_OWNER); +} + +static int ehci_port_handed_over(struct usb_hcd *hcd, int portnum) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 __iomem *reg; + + if (ehci_is_TDI(ehci)) + return 0; + reg = &ehci->regs->port_status[portnum - 1]; + return ehci_readl(ehci, reg) & PORT_OWNER; +} diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c new file mode 100644 index 00000000..ef2c3a1e --- /dev/null +++ b/drivers/usb/host/ehci-mem.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * There's basically three types of memory: + * - data used only by the HCD ... kmalloc is fine + * - async and periodic schedules, shared by HC and HCD ... these + * need to use dma_pool or dma_alloc_coherent + * - driver buffers, read/written by HC ... single shot DMA mapped + * + * There's also "register" data (e.g. PCI or SOC), which is memory mapped. + * No memory seen by this driver is pageable. + */ + +/*-------------------------------------------------------------------------*/ + +/* Allocate the key transfer structures from the previously allocated pool */ + +static inline void ehci_qtd_init(struct ehci_hcd *ehci, struct ehci_qtd *qtd, + dma_addr_t dma) +{ + memset (qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_token = cpu_to_hc32(ehci, QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END(ehci); + qtd->hw_alt_next = EHCI_LIST_END(ehci); + INIT_LIST_HEAD (&qtd->qtd_list); +} + +static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, gfp_t flags) +{ + struct ehci_qtd *qtd; + dma_addr_t dma; + + qtd = dma_pool_alloc (ehci->qtd_pool, flags, &dma); + if (qtd != NULL) { + ehci_qtd_init(ehci, qtd, dma); + } + return qtd; +} + +static inline void ehci_qtd_free (struct ehci_hcd *ehci, struct ehci_qtd *qtd) +{ + dma_pool_free (ehci->qtd_pool, qtd, qtd->qtd_dma); +} + + +static void qh_destroy(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + /* clean qtds first, and know this is not linked */ + if (!list_empty (&qh->qtd_list) || qh->qh_next.ptr) { + ehci_dbg (ehci, "unused qh not empty!\n"); + BUG (); + } + if (qh->dummy) + ehci_qtd_free (ehci, qh->dummy); + dma_pool_free(ehci->qh_pool, qh->hw, qh->qh_dma); + kfree(qh); +} + +static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, gfp_t flags) +{ + struct ehci_qh *qh; + dma_addr_t dma; + + qh = kzalloc(sizeof *qh, GFP_ATOMIC); + if (!qh) + goto done; + qh->hw = (struct ehci_qh_hw *) + dma_pool_alloc(ehci->qh_pool, flags, &dma); + if (!qh->hw) + goto fail; + memset(qh->hw, 0, sizeof *qh->hw); + qh->qh_dma = dma; + // INIT_LIST_HEAD (&qh->qh_list); + INIT_LIST_HEAD (&qh->qtd_list); + + /* dummy td enables safe urb queuing */ + qh->dummy = ehci_qtd_alloc (ehci, flags); + if (qh->dummy == NULL) { + ehci_dbg (ehci, "no dummy td\n"); + goto fail1; + } +done: + return qh; +fail1: + dma_pool_free(ehci->qh_pool, qh->hw, qh->qh_dma); +fail: + kfree(qh); + return NULL; +} + +/*-------------------------------------------------------------------------*/ + +/* The queue heads and transfer descriptors are managed from pools tied + * to each of the "per device" structures. + * This is the initialisation and cleanup code. + */ + +static void ehci_mem_cleanup (struct ehci_hcd *ehci) +{ + if (ehci->async) + qh_destroy(ehci, ehci->async); + ehci->async = NULL; + + if (ehci->dummy) + qh_destroy(ehci, ehci->dummy); + ehci->dummy = NULL; + + /* DMA consistent memory and pools */ + if (ehci->qtd_pool) + dma_pool_destroy (ehci->qtd_pool); + ehci->qtd_pool = NULL; + + if (ehci->qh_pool) { + dma_pool_destroy (ehci->qh_pool); + ehci->qh_pool = NULL; + } + + if (ehci->itd_pool) + dma_pool_destroy (ehci->itd_pool); + ehci->itd_pool = NULL; + + if (ehci->sitd_pool) + dma_pool_destroy (ehci->sitd_pool); + ehci->sitd_pool = NULL; + + if (ehci->periodic) + dma_free_coherent (ehci_to_hcd(ehci)->self.controller, + ehci->periodic_size * sizeof (u32), + ehci->periodic, ehci->periodic_dma); + ehci->periodic = NULL; + + /* shadow periodic table */ + kfree(ehci->pshadow); + ehci->pshadow = NULL; +} + +/* remember to add cleanup code (above) if you add anything here */ +static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) +{ + int i; + + /* QTDs for control/bulk/intr transfers */ + ehci->qtd_pool = dma_pool_create ("ehci_qtd", + ehci_to_hcd(ehci)->self.controller, + sizeof (struct ehci_qtd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!ehci->qtd_pool) { + goto fail; + } + + /* QHs for control/bulk/intr transfers */ + ehci->qh_pool = dma_pool_create ("ehci_qh", + ehci_to_hcd(ehci)->self.controller, + sizeof(struct ehci_qh_hw), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!ehci->qh_pool) { + goto fail; + } + ehci->async = ehci_qh_alloc (ehci, flags); + if (!ehci->async) { + goto fail; + } + + /* ITD for high speed ISO transfers */ + ehci->itd_pool = dma_pool_create ("ehci_itd", + ehci_to_hcd(ehci)->self.controller, + sizeof (struct ehci_itd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!ehci->itd_pool) { + goto fail; + } + + /* SITD for full/low speed split ISO transfers */ + ehci->sitd_pool = dma_pool_create ("ehci_sitd", + ehci_to_hcd(ehci)->self.controller, + sizeof (struct ehci_sitd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!ehci->sitd_pool) { + goto fail; + } + + /* Hardware periodic table */ + ehci->periodic = (__le32 *) + dma_alloc_coherent (ehci_to_hcd(ehci)->self.controller, + ehci->periodic_size * sizeof(__le32), + &ehci->periodic_dma, 0); + if (ehci->periodic == NULL) { + goto fail; + } + + if (ehci->use_dummy_qh) { + struct ehci_qh_hw *hw; + ehci->dummy = ehci_qh_alloc(ehci, flags); + if (!ehci->dummy) + goto fail; + + hw = ehci->dummy->hw; + hw->hw_next = EHCI_LIST_END(ehci); + hw->hw_qtd_next = EHCI_LIST_END(ehci); + hw->hw_alt_next = EHCI_LIST_END(ehci); + hw->hw_token &= ~QTD_STS_ACTIVE; + ehci->dummy->hw = hw; + + for (i = 0; i < ehci->periodic_size; i++) + ehci->periodic[i] = ehci->dummy->qh_dma; + } else { + for (i = 0; i < ehci->periodic_size; i++) + ehci->periodic[i] = EHCI_LIST_END(ehci); + } + + /* software shadow of hardware table */ + ehci->pshadow = kcalloc(ehci->periodic_size, sizeof(void *), flags); + if (ehci->pshadow != NULL) + return 0; + +fail: + ehci_dbg (ehci, "couldn't init memory\n"); + ehci_mem_cleanup (ehci); + return -ENOMEM; +} diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c new file mode 100644 index 00000000..0f717dc6 --- /dev/null +++ b/drivers/usb/host/ehci-msm.c @@ -0,0 +1,221 @@ +/* ehci-msm.c - HSUSB Host Controller Driver Implementation + * + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * + * Partly derived from ehci-fsl.c and ehci-hcd.c + * Copyright (c) 2000-2004 by David Brownell + * Copyright (c) 2005 MontaVista Software + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute 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 that 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define MSM_USB_BASE (hcd->regs) + +#define DRIVER_DESC "Qualcomm On-Chip EHCI Host Controller" + +static const char hcd_name[] = "ehci-msm"; +static struct hc_driver __read_mostly msm_hc_driver; +static struct usb_phy *phy; + +static int ehci_msm_reset(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + ehci->caps = USB_CAPLENGTH; + hcd->has_tt = 1; + + retval = ehci_setup(hcd); + if (retval) + return retval; + + /* bursts of unspecified length. */ + writel(0, USB_AHBBURST); + /* Use the AHB transactor */ + writel(0, USB_AHBMODE); + /* Disable streaming mode and select host mode */ + writel(0x13, USB_USBMODE); + + return 0; +} + +static int ehci_msm_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct resource *res; + int ret; + + dev_dbg(&pdev->dev, "ehci_msm proble\n"); + + hcd = usb_create_hcd(&msm_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + return -ENOMEM; + } + + hcd->irq = platform_get_irq(pdev, 0); + if (hcd->irq < 0) { + dev_err(&pdev->dev, "Unable to get IRQ resource\n"); + ret = hcd->irq; + goto put_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Unable to get memory resource\n"); + ret = -ENODEV; + goto put_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = devm_ioremap(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto put_hcd; + } + + /* + * OTG driver takes care of PHY initialization, clock management, + * powering up VBUS, mapping of registers address space and power + * management. + */ + phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + if (IS_ERR(phy)) { + dev_err(&pdev->dev, "unable to find transceiver\n"); + ret = -ENODEV; + goto put_hcd; + } + + ret = otg_set_host(phy->otg, &hcd->self); + if (ret < 0) { + dev_err(&pdev->dev, "unable to register with transceiver\n"); + goto put_hcd; + } + + device_init_wakeup(&pdev->dev, 1); + /* + * OTG device parent of HCD takes care of putting + * hardware into low power mode. + */ + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + /* FIXME: need to call usb_add_hcd() here? */ + + return 0; + +put_hcd: + usb_put_hcd(hcd); + + return ret; +} + +static int ehci_msm_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + + otg_set_host(phy->otg, NULL); + + /* FIXME: need to call usb_remove_hcd() here? */ + + usb_put_hcd(hcd); + + return 0; +} + +#ifdef CONFIG_PM +static int ehci_msm_pm_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + bool do_wakeup = device_may_wakeup(dev); + + dev_dbg(dev, "ehci-msm PM suspend\n"); + + return ehci_suspend(hcd, do_wakeup); +} + +static int ehci_msm_pm_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + dev_dbg(dev, "ehci-msm PM resume\n"); + ehci_resume(hcd, false); + + return 0; +} +#else +#define ehci_msm_pm_suspend NULL +#define ehci_msm_pm_resume NULL +#endif + +static const struct dev_pm_ops ehci_msm_dev_pm_ops = { + .suspend = ehci_msm_pm_suspend, + .resume = ehci_msm_pm_resume, +}; + +static struct platform_driver ehci_msm_driver = { + .probe = ehci_msm_probe, + .remove = ehci_msm_remove, + .driver = { + .name = "msm_hsusb_host", + .pm = &ehci_msm_dev_pm_ops, + }, +}; + +static const struct ehci_driver_overrides msm_overrides __initdata = { + .reset = ehci_msm_reset, +}; + +static int __init ehci_msm_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + ehci_init_driver(&msm_hc_driver, &msm_overrides); + return platform_driver_register(&ehci_msm_driver); +} +module_init(ehci_msm_init); + +static void __exit ehci_msm_cleanup(void) +{ + platform_driver_unregister(&ehci_msm_driver); +} +module_exit(ehci_msm_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:msm-ehci"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c new file mode 100644 index 00000000..40206297 --- /dev/null +++ b/drivers/usb/host/ehci-mv.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * Author: Chao Xie + * Neil Zhang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define CAPLENGTH_MASK (0xff) + +struct ehci_hcd_mv { + struct usb_hcd *hcd; + + /* Which mode does this ehci running OTG/Host ? */ + int mode; + + void __iomem *phy_regs; + void __iomem *cap_regs; + void __iomem *op_regs; + + struct usb_phy *otg; + + struct mv_usb_platform_data *pdata; + + struct clk *clk; +}; + +static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv) +{ + clk_prepare_enable(ehci_mv->clk); +} + +static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv) +{ + clk_disable_unprepare(ehci_mv->clk); +} + +static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) +{ + int retval; + + ehci_clock_enable(ehci_mv); + if (ehci_mv->pdata->phy_init) { + retval = ehci_mv->pdata->phy_init(ehci_mv->phy_regs); + if (retval) + return retval; + } + + return 0; +} + +static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) +{ + if (ehci_mv->pdata->phy_deinit) + ehci_mv->pdata->phy_deinit(ehci_mv->phy_regs); + ehci_clock_disable(ehci_mv); +} + +static int mv_ehci_reset(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + struct ehci_hcd_mv *ehci_mv = dev_get_drvdata(dev); + int retval; + + if (ehci_mv == NULL) { + dev_err(dev, "Can not find private ehci data\n"); + return -ENODEV; + } + + hcd->has_tt = 1; + + retval = ehci_setup(hcd); + if (retval) + dev_err(dev, "ehci_setup failed %d\n", retval); + + return retval; +} + +static const struct hc_driver mv_ehci_hc_driver = { + .description = hcd_name, + .product_desc = "Marvell EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = mv_ehci_reset, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +}; + +static int mv_ehci_probe(struct platform_device *pdev) +{ + struct mv_usb_platform_data *pdata = pdev->dev.platform_data; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct ehci_hcd_mv *ehci_mv; + struct resource *r; + int retval = -ENODEV; + u32 offset; + + if (!pdata) { + dev_err(&pdev->dev, "missing platform_data\n"); + return -ENODEV; + } + + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&mv_ehci_hc_driver, &pdev->dev, "mv ehci"); + if (!hcd) + return -ENOMEM; + + ehci_mv = devm_kzalloc(&pdev->dev, sizeof(*ehci_mv), GFP_KERNEL); + if (ehci_mv == NULL) { + dev_err(&pdev->dev, "cannot allocate ehci_hcd_mv\n"); + retval = -ENOMEM; + goto err_put_hcd; + } + + platform_set_drvdata(pdev, ehci_mv); + ehci_mv->pdata = pdata; + ehci_mv->hcd = hcd; + + ehci_mv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ehci_mv->clk)) { + dev_err(&pdev->dev, "error getting clock\n"); + retval = PTR_ERR(ehci_mv->clk); + goto err_clear_drvdata; + } + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); + if (r == NULL) { + dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); + retval = -ENODEV; + goto err_clear_drvdata; + } + + ehci_mv->phy_regs = devm_ioremap(&pdev->dev, r->start, + resource_size(r)); + if (ehci_mv->phy_regs == 0) { + dev_err(&pdev->dev, "failed to map phy I/O memory\n"); + retval = -EFAULT; + goto err_clear_drvdata; + } + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); + if (!r) { + dev_err(&pdev->dev, "no I/O memory resource defined\n"); + retval = -ENODEV; + goto err_clear_drvdata; + } + + ehci_mv->cap_regs = devm_ioremap(&pdev->dev, r->start, + resource_size(r)); + if (ehci_mv->cap_regs == NULL) { + dev_err(&pdev->dev, "failed to map I/O memory\n"); + retval = -EFAULT; + goto err_clear_drvdata; + } + + retval = mv_ehci_enable(ehci_mv); + if (retval) { + dev_err(&pdev->dev, "init phy error %d\n", retval); + goto err_clear_drvdata; + } + + offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; + ehci_mv->op_regs = + (void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset); + + hcd->rsrc_start = r->start; + hcd->rsrc_len = resource_size(r); + hcd->regs = ehci_mv->op_regs; + + hcd->irq = platform_get_irq(pdev, 0); + if (!hcd->irq) { + dev_err(&pdev->dev, "Cannot get irq."); + retval = -ENODEV; + goto err_disable_clk; + } + + ehci = hcd_to_ehci(hcd); + ehci->caps = (struct ehci_caps *) ehci_mv->cap_regs; + + ehci_mv->mode = pdata->mode; + if (ehci_mv->mode == MV_USB_MODE_OTG) { + ehci_mv->otg = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + if (IS_ERR(ehci_mv->otg)) { + retval = PTR_ERR(ehci_mv->otg); + + if (retval == -ENXIO) + dev_info(&pdev->dev, "MV_USB_MODE_OTG " + "must have CONFIG_USB_PHY enabled\n"); + else + dev_err(&pdev->dev, + "unable to find transceiver\n"); + goto err_disable_clk; + } + + retval = otg_set_host(ehci_mv->otg->otg, &hcd->self); + if (retval < 0) { + dev_err(&pdev->dev, + "unable to register with transceiver\n"); + retval = -ENODEV; + goto err_disable_clk; + } + /* otg will enable clock before use as host */ + mv_ehci_disable(ehci_mv); + } else { + if (pdata->set_vbus) + pdata->set_vbus(1); + + retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + if (retval) { + dev_err(&pdev->dev, + "failed to add hcd with err %d\n", retval); + goto err_set_vbus; + } + } + + if (pdata->private_init) + pdata->private_init(ehci_mv->op_regs, ehci_mv->phy_regs); + + dev_info(&pdev->dev, + "successful find EHCI device with regs 0x%p irq %d" + " working in %s mode\n", hcd->regs, hcd->irq, + ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host"); + + return 0; + +err_set_vbus: + if (pdata->set_vbus) + pdata->set_vbus(0); +err_disable_clk: + mv_ehci_disable(ehci_mv); +err_clear_drvdata: + platform_set_drvdata(pdev, NULL); +err_put_hcd: + usb_put_hcd(hcd); + + return retval; +} + +static int mv_ehci_remove(struct platform_device *pdev) +{ + struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); + struct usb_hcd *hcd = ehci_mv->hcd; + + if (hcd->rh_registered) + usb_remove_hcd(hcd); + + if (!IS_ERR_OR_NULL(ehci_mv->otg)) + otg_set_host(ehci_mv->otg->otg, NULL); + + if (ehci_mv->mode == MV_USB_MODE_HOST) { + if (ehci_mv->pdata->set_vbus) + ehci_mv->pdata->set_vbus(0); + + mv_ehci_disable(ehci_mv); + } + + platform_set_drvdata(pdev, NULL); + + usb_put_hcd(hcd); + + return 0; +} + +MODULE_ALIAS("mv-ehci"); + +static const struct platform_device_id ehci_id_table[] = { + {"pxa-u2oehci", PXA_U2OEHCI}, + {"pxa-sph", PXA_SPH}, + {"mmp3-hsic", MMP3_HSIC}, + {"mmp3-fsic", MMP3_FSIC}, + {}, +}; + +static void mv_ehci_shutdown(struct platform_device *pdev) +{ + struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); + struct usb_hcd *hcd = ehci_mv->hcd; + + if (!hcd->rh_registered) + return; + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static struct platform_driver ehci_mv_driver = { + .probe = mv_ehci_probe, + .remove = mv_ehci_remove, + .shutdown = mv_ehci_shutdown, + .driver = { + .name = "mv-ehci", + .bus = &platform_bus_type, + }, + .id_table = ehci_id_table, +}; diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c new file mode 100644 index 00000000..ec128bc7 --- /dev/null +++ b/drivers/usb/host/ehci-mxc.c @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2008 Sascha Hauer , Pengutronix + * Copyright (c) 2009 Daniel Mack + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ehci.h" + +#define DRIVER_DESC "Freescale On-Chip EHCI Host driver" + +static const char hcd_name[] = "ehci-mxc"; + +#define ULPI_VIEWPORT_OFFSET 0x170 + +struct ehci_mxc_priv { + struct clk *usbclk, *ahbclk, *phyclk; +}; + +static struct hc_driver __read_mostly ehci_mxc_hc_driver; + +static const struct ehci_driver_overrides ehci_mxc_overrides __initconst = { + .extra_priv_size = sizeof(struct ehci_mxc_priv), +}; + +static int ehci_mxc_drv_probe(struct platform_device *pdev) +{ + struct mxc_usbh_platform_data *pdata = pdev->dev.platform_data; + struct usb_hcd *hcd; + struct resource *res; + int irq, ret; + struct ehci_mxc_priv *priv; + struct device *dev = &pdev->dev; + struct ehci_hcd *ehci; + + if (!pdata) { + dev_err(dev, "No platform data given, bailing out.\n"); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + + hcd = usb_create_hcd(&ehci_mxc_hc_driver, dev, dev_name(dev)); + if (!hcd) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Found HC with no register addr. Check setup!\n"); + ret = -ENODEV; + goto err_alloc; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + hcd->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto err_alloc; + } + + hcd->has_tt = 1; + ehci = hcd_to_ehci(hcd); + priv = (struct ehci_mxc_priv *) ehci->priv; + + /* enable clocks */ + priv->usbclk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(priv->usbclk)) { + ret = PTR_ERR(priv->usbclk); + goto err_alloc; + } + clk_prepare_enable(priv->usbclk); + + priv->ahbclk = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(priv->ahbclk)) { + ret = PTR_ERR(priv->ahbclk); + goto err_clk_ahb; + } + clk_prepare_enable(priv->ahbclk); + + /* "dr" device has its own clock on i.MX51 */ + priv->phyclk = devm_clk_get(&pdev->dev, "phy"); + if (IS_ERR(priv->phyclk)) + priv->phyclk = NULL; + if (priv->phyclk) + clk_prepare_enable(priv->phyclk); + + + /* call platform specific init function */ + if (pdata->init) { + ret = pdata->init(pdev); + if (ret) { + dev_err(dev, "platform init failed\n"); + goto err_init; + } + /* platforms need some time to settle changed IO settings */ + mdelay(10); + } + + /* EHCI registers start at offset 0x100 */ + ehci->caps = hcd->regs + 0x100; + ehci->regs = hcd->regs + 0x100 + + HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + + /* set up the PORTSCx register */ + ehci_writel(ehci, pdata->portsc, &ehci->regs->port_status[0]); + + /* is this really needed? */ + msleep(10); + + /* Initialize the transceiver */ + if (pdata->otg) { + pdata->otg->io_priv = hcd->regs + ULPI_VIEWPORT_OFFSET; + ret = usb_phy_init(pdata->otg); + if (ret) { + dev_err(dev, "unable to init transceiver, probably missing\n"); + ret = -ENODEV; + goto err_add; + } + ret = otg_set_vbus(pdata->otg->otg, 1); + if (ret) { + dev_err(dev, "unable to enable vbus on transceiver\n"); + goto err_add; + } + } + + platform_set_drvdata(pdev, hcd); + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) + goto err_add; + + return 0; + +err_add: + if (pdata && pdata->exit) + pdata->exit(pdev); +err_init: + if (priv->phyclk) + clk_disable_unprepare(priv->phyclk); + + clk_disable_unprepare(priv->ahbclk); +err_clk_ahb: + clk_disable_unprepare(priv->usbclk); +err_alloc: + usb_put_hcd(hcd); + return ret; +} + +static int ehci_mxc_drv_remove(struct platform_device *pdev) +{ + struct mxc_usbh_platform_data *pdata = pdev->dev.platform_data; + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct ehci_mxc_priv *priv = (struct ehci_mxc_priv *) ehci->priv; + + usb_remove_hcd(hcd); + + if (pdata && pdata->exit) + pdata->exit(pdev); + + if (pdata && pdata->otg) + usb_phy_shutdown(pdata->otg); + + clk_disable_unprepare(priv->usbclk); + clk_disable_unprepare(priv->ahbclk); + + if (priv->phyclk) + clk_disable_unprepare(priv->phyclk); + + usb_put_hcd(hcd); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static void ehci_mxc_drv_shutdown(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +MODULE_ALIAS("platform:mxc-ehci"); + +static struct platform_driver ehci_mxc_driver = { + .probe = ehci_mxc_drv_probe, + .remove = ehci_mxc_drv_remove, + .shutdown = ehci_mxc_drv_shutdown, + .driver = { + .name = "mxc-ehci", + }, +}; + +static int __init ehci_mxc_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ehci_init_driver(&ehci_mxc_hc_driver, &ehci_mxc_overrides); + return platform_driver_register(&ehci_mxc_driver); +} +module_init(ehci_mxc_init); + +static void __exit ehci_mxc_cleanup(void) +{ + platform_driver_unregister(&ehci_mxc_driver); +} +module_exit(ehci_mxc_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Sascha Hauer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-octeon.c b/drivers/usb/host/ehci-octeon.c new file mode 100644 index 00000000..a89750ff --- /dev/null +++ b/drivers/usb/host/ehci-octeon.c @@ -0,0 +1,200 @@ +/* + * EHCI HCD glue for Cavium Octeon II SOCs. + * + * Loosely based on ehci-au1xxx.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2010 Cavium Networks + * + */ + +#include + +#include +#include + +#define OCTEON_EHCI_HCD_NAME "octeon-ehci" + +/* Common clock init code. */ +void octeon2_usb_clocks_start(void); +void octeon2_usb_clocks_stop(void); + +static void ehci_octeon_start(void) +{ + union cvmx_uctlx_ehci_ctl ehci_ctl; + + octeon2_usb_clocks_start(); + + ehci_ctl.u64 = cvmx_read_csr(CVMX_UCTLX_EHCI_CTL(0)); + /* Use 64-bit addressing. */ + ehci_ctl.s.ehci_64b_addr_en = 1; + ehci_ctl.s.l2c_addr_msb = 0; + ehci_ctl.s.l2c_buff_emod = 1; /* Byte swapped. */ + ehci_ctl.s.l2c_desc_emod = 1; /* Byte swapped. */ + cvmx_write_csr(CVMX_UCTLX_EHCI_CTL(0), ehci_ctl.u64); +} + +static void ehci_octeon_stop(void) +{ + octeon2_usb_clocks_stop(); +} + +static const struct hc_driver ehci_octeon_hc_driver = { + .description = hcd_name, + .product_desc = "Octeon EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static u64 ehci_octeon_dma_mask = DMA_BIT_MASK(64); + +static int ehci_octeon_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct resource *res_mem; + int irq; + int ret; + + if (usb_disabled()) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No irq assigned\n"); + return -ENODEV; + } + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res_mem == NULL) { + dev_err(&pdev->dev, "No register space assigned\n"); + return -ENODEV; + } + + /* + * We can DMA from anywhere. But the descriptors must be in + * the lower 4GB. + */ + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + pdev->dev.dma_mask = &ehci_octeon_dma_mask; + + hcd = usb_create_hcd(&ehci_octeon_hc_driver, &pdev->dev, "octeon"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + OCTEON_EHCI_HCD_NAME)) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto err1; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err2; + } + + ehci_octeon_start(); + + ehci = hcd_to_ehci(hcd); + + /* Octeon EHCI matches CPU endianness. */ +#ifdef __BIG_ENDIAN + ehci->big_endian_mmio = 1; +#endif + + ehci->caps = hcd->regs; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) { + dev_dbg(&pdev->dev, "failed to add hcd with err %d\n", ret); + goto err3; + } + + platform_set_drvdata(pdev, hcd); + + return 0; +err3: + ehci_octeon_stop(); + + iounmap(hcd->regs); +err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err1: + usb_put_hcd(hcd); + return ret; +} + +static int ehci_octeon_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + + ehci_octeon_stop(); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver ehci_octeon_driver = { + .probe = ehci_octeon_drv_probe, + .remove = ehci_octeon_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = OCTEON_EHCI_HCD_NAME, + .owner = THIS_MODULE, + } +}; + +MODULE_ALIAS("platform:" OCTEON_EHCI_HCD_NAME); diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c new file mode 100644 index 00000000..dda408f2 --- /dev/null +++ b/drivers/usb/host/ehci-omap.c @@ -0,0 +1,334 @@ +/* + * ehci-omap.c - driver for USBHOST on OMAP3/4 processors + * + * Bus Glue for the EHCI controllers in OMAP3/4 + * Tested on several OMAP3 boards, and OMAP4 Pandaboard + * + * Copyright (C) 2007-2013 Texas Instruments, Inc. + * Author: Vikram Pandita + * Author: Anand Gadiyar + * Author: Keshava Munegowda + * Author: Roger Quadros + * + * Copyright (C) 2009 Nokia Corporation + * Contact: Felipe Balbi + * + * Based on "ehci-fsl.c" and "ehci-au1xxx.c" ehci glue layers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#include + +/* EHCI Register Set */ +#define EHCI_INSNREG04 (0xA0) +#define EHCI_INSNREG04_DISABLE_UNSUSPEND (1 << 5) +#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 + +#define DRIVER_DESC "OMAP-EHCI Host Controller driver" + +static const char hcd_name[] = "ehci-omap"; + +/*-------------------------------------------------------------------------*/ + +struct omap_hcd { + struct usb_phy *phy[OMAP3_HS_USB_PORTS]; /* one PHY for each port */ + int nports; +}; + +static inline void ehci_write(void __iomem *base, u32 reg, u32 val) +{ + __raw_writel(val, base + reg); +} + +static inline u32 ehci_read(void __iomem *base, u32 reg) +{ + return __raw_readl(base + reg); +} + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +static struct hc_driver __read_mostly ehci_omap_hc_driver; + +static const struct ehci_driver_overrides ehci_omap_overrides __initdata = { + .extra_priv_size = sizeof(struct omap_hcd), +}; + +/** + * 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) +{ + struct device *dev = &pdev->dev; + struct usbhs_omap_platform_data *pdata = dev->platform_data; + struct resource *res; + struct usb_hcd *hcd; + void __iomem *regs; + int ret = -ENODEV; + int irq; + int i; + struct omap_hcd *omap; + + if (usb_disabled()) + return -ENODEV; + + if (!dev->parent) { + dev_err(dev, "Missing parent device\n"); + return -ENODEV; + } + + /* For DT boot, get platform data from parent. i.e. usbhshost */ + if (dev->of_node) { + pdata = dev->parent->platform_data; + dev->platform_data = pdata; + } + + if (!pdata) { + dev_err(dev, "Missing platform data\n"); + return -ENODEV; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "EHCI irq failed\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + /* + * 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. + */ + if (!dev->dma_mask) + dev->dma_mask = &dev->coherent_dma_mask; + if (!dev->coherent_dma_mask) + dev->coherent_dma_mask = DMA_BIT_MASK(32); + + hcd = usb_create_hcd(&ehci_omap_hc_driver, dev, + dev_name(dev)); + if (!hcd) { + dev_err(dev, "Failed to create HCD\n"); + return -ENOMEM; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = regs; + hcd_to_ehci(hcd)->caps = regs; + + 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 */ + if (dev->of_node) + phy = devm_usb_get_phy_by_phandle(dev, "phys", i); + else + phy = devm_usb_get_phy_dev(dev, i); + if (IS_ERR(phy)) { + /* Don't bail out if PHY is not absolutely necessary */ + if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) + continue; + + ret = PTR_ERR(phy); + dev_err(dev, "Can't get PHY device for port %d: %d\n", + i, ret); + goto err_phy; + } + + omap->phy[i] = phy; + + 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); + } + } + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + /* + * 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); + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) { + dev_err(dev, "failed to add hcd with err %d\n", ret); + goto err_pm_runtime; + } + + /* + * Bring PHYs out of reset for non PHY modes. + * 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++) { + if (!omap->phy[i] || + pdata->port_mode[i] == OMAP_EHCI_PORT_MODE_PHY) + continue; + + usb_phy_init(omap->phy[i]); + /* bring PHY out of suspend */ + usb_phy_set_suspend(omap->phy[i], 0); + } + + return 0; + +err_pm_runtime: + pm_runtime_put_sync(dev); + +err_phy: + for (i = 0; i < omap->nports; i++) { + if (omap->phy[i]) + usb_phy_shutdown(omap->phy[i]); + } + + usb_put_hcd(hcd); + + 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) +{ + 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; + + usb_remove_hcd(hcd); + + for (i = 0; i < omap->nports; i++) { + if (omap->phy[i]) + usb_phy_shutdown(omap->phy[i]); + } + + usb_put_hcd(hcd); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return 0; +} + +static void ehci_hcd_omap_shutdown(struct platform_device *pdev) +{ + struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static const struct of_device_id omap_ehci_dt_ids[] = { + { .compatible = "ti,ehci-omap" }, + { } +}; + +MODULE_DEVICE_TABLE(of, omap_ehci_dt_ids); + +static struct platform_driver ehci_hcd_omap_driver = { + .probe = ehci_hcd_omap_probe, + .remove = ehci_hcd_omap_remove, + .shutdown = ehci_hcd_omap_shutdown, + /*.suspend = ehci_hcd_omap_suspend, */ + /*.resume = ehci_hcd_omap_resume, */ + .driver = { + .name = hcd_name, + .of_match_table = of_match_ptr(omap_ehci_dt_ids), + } +}; + +/*-------------------------------------------------------------------------*/ + +static int __init ehci_omap_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ehci_init_driver(&ehci_omap_hc_driver, &ehci_omap_overrides); + return platform_driver_register(&ehci_hcd_omap_driver); +} +module_init(ehci_omap_init); + +static void __exit ehci_omap_cleanup(void) +{ + platform_driver_unregister(&ehci_hcd_omap_driver); +} +module_exit(ehci_omap_cleanup); + +MODULE_ALIAS("platform:ehci-omap"); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_AUTHOR("Roger Quadros "); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c new file mode 100644 index 00000000..efbc588b --- /dev/null +++ b/drivers/usb/host/ehci-orion.c @@ -0,0 +1,331 @@ +/* + * drivers/usb/host/ehci-orion.c + * + * Tzachi Perelstein + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define rdl(off) __raw_readl(hcd->regs + (off)) +#define wrl(off, val) __raw_writel((val), hcd->regs + (off)) + +#define USB_CMD 0x140 +#define USB_MODE 0x1a8 +#define USB_CAUSE 0x310 +#define USB_MASK 0x314 +#define USB_WINDOW_CTRL(i) (0x320 + ((i) << 4)) +#define USB_WINDOW_BASE(i) (0x324 + ((i) << 4)) +#define USB_IPG 0x360 +#define USB_PHY_PWR_CTRL 0x400 +#define USB_PHY_TX_CTRL 0x420 +#define USB_PHY_RX_CTRL 0x430 +#define USB_PHY_IVREF_CTRL 0x440 +#define USB_PHY_TST_GRP_CTRL 0x450 + +#define DRIVER_DESC "EHCI orion driver" + +static const char hcd_name[] = "ehci-orion"; + +static struct hc_driver __read_mostly ehci_orion_hc_driver; + +/* + * Implement Orion USB controller specification guidelines + */ +static void orion_usb_phy_v1_setup(struct usb_hcd *hcd) +{ + /* The below GLs are according to the Orion Errata document */ + /* + * Clear interrupt cause and mask + */ + wrl(USB_CAUSE, 0); + wrl(USB_MASK, 0); + + /* + * Reset controller + */ + wrl(USB_CMD, rdl(USB_CMD) | 0x2); + while (rdl(USB_CMD) & 0x2); + + /* + * GL# USB-10: Set IPG for non start of frame packets + * Bits[14:8]=0xc + */ + wrl(USB_IPG, (rdl(USB_IPG) & ~0x7f00) | 0xc00); + + /* + * GL# USB-9: USB 2.0 Power Control + * BG_VSEL[7:6]=0x1 + */ + wrl(USB_PHY_PWR_CTRL, (rdl(USB_PHY_PWR_CTRL) & ~0xc0)| 0x40); + + /* + * GL# USB-1: USB PHY Tx Control - force calibration to '8' + * TXDATA_BLOCK_EN[21]=0x1, EXT_RCAL_EN[13]=0x1, IMP_CAL[6:3]=0x8 + */ + wrl(USB_PHY_TX_CTRL, (rdl(USB_PHY_TX_CTRL) & ~0x78) | 0x202040); + + /* + * GL# USB-3 GL# USB-9: USB PHY Rx Control + * RXDATA_BLOCK_LENGHT[31:30]=0x3, EDGE_DET_SEL[27:26]=0, + * CDR_FASTLOCK_EN[21]=0, DISCON_THRESHOLD[9:8]=0, SQ_THRESH[7:4]=0x1 + */ + wrl(USB_PHY_RX_CTRL, (rdl(USB_PHY_RX_CTRL) & ~0xc2003f0) | 0xc0000010); + + /* + * GL# USB-3 GL# USB-9: USB PHY IVREF Control + * PLLVDD12[1:0]=0x2, RXVDD[5:4]=0x3, Reserved[19]=0 + */ + wrl(USB_PHY_IVREF_CTRL, (rdl(USB_PHY_IVREF_CTRL) & ~0x80003 ) | 0x32); + + /* + * GL# USB-3 GL# USB-9: USB PHY Test Group Control + * REG_FIFO_SQ_RST[15]=0 + */ + wrl(USB_PHY_TST_GRP_CTRL, rdl(USB_PHY_TST_GRP_CTRL) & ~0x8000); + + /* + * Stop and reset controller + */ + wrl(USB_CMD, rdl(USB_CMD) & ~0x1); + wrl(USB_CMD, rdl(USB_CMD) | 0x2); + while (rdl(USB_CMD) & 0x2); + + /* + * GL# USB-5 Streaming disable REG_USB_MODE[4]=1 + * TBD: This need to be done after each reset! + * GL# USB-4 Setup USB Host mode + */ + wrl(USB_MODE, 0x13); +} + +static void +ehci_orion_conf_mbus_windows(struct usb_hcd *hcd, + const struct mbus_dram_target_info *dram) +{ + int i; + + for (i = 0; i < 4; i++) { + wrl(USB_WINDOW_CTRL(i), 0); + wrl(USB_WINDOW_BASE(i), 0); + } + + for (i = 0; i < dram->num_cs; i++) { + const struct mbus_dram_window *cs = dram->cs + i; + + wrl(USB_WINDOW_CTRL(i), ((cs->size - 1) & 0xffff0000) | + (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1); + wrl(USB_WINDOW_BASE(i), cs->base); + } +} + +static int ehci_orion_drv_probe(struct platform_device *pdev) +{ + struct orion_ehci_data *pd = pdev->dev.platform_data; + const struct mbus_dram_target_info *dram; + struct resource *res; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct clk *clk; + void __iomem *regs; + int irq, err; + enum orion_ehci_phy_ver phy_version; + + if (usb_disabled()) + return -ENODEV; + + pr_debug("Initializing Orion-SoC USB Host Controller\n"); + + if (pdev->dev.of_node) + irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + else + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + err = -ENODEV; + goto err1; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(&pdev->dev)); + err = -ENODEV; + goto err1; + } + + /* + * 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. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + if (!request_mem_region(res->start, resource_size(res), + ehci_orion_hc_driver.description)) { + dev_dbg(&pdev->dev, "controller already in use\n"); + err = -EBUSY; + goto err1; + } + + regs = ioremap(res->start, resource_size(res)); + if (regs == NULL) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + err = -EFAULT; + goto err2; + } + + /* Not all platforms can gate the clock, so it is not + an error if the clock does not exists. */ + clk = clk_get(&pdev->dev, NULL); + if (!IS_ERR(clk)) { + clk_prepare_enable(clk); + clk_put(clk); + } + + hcd = usb_create_hcd(&ehci_orion_hc_driver, + &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + err = -ENOMEM; + goto err3; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = regs; + + ehci = hcd_to_ehci(hcd); + ehci->caps = hcd->regs + 0x100; + hcd->has_tt = 1; + + /* + * (Re-)program MBUS remapping windows if we are asked to. + */ + dram = mv_mbus_dram_info(); + if (dram) + ehci_orion_conf_mbus_windows(hcd, dram); + + /* + * setup Orion USB controller. + */ + if (pdev->dev.of_node) + phy_version = EHCI_PHY_NA; + else + phy_version = pd->phy_version; + + switch (phy_version) { + case EHCI_PHY_NA: /* dont change USB phy settings */ + break; + case EHCI_PHY_ORION: + orion_usb_phy_v1_setup(hcd); + break; + case EHCI_PHY_DD: + case EHCI_PHY_KW: + default: + printk(KERN_WARNING "Orion ehci -USB phy version isn't supported.\n"); + } + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err4; + + return 0; + +err4: + usb_put_hcd(hcd); +err3: + if (!IS_ERR(clk)) { + clk_disable_unprepare(clk); + clk_put(clk); + } + iounmap(regs); +err2: + release_mem_region(res->start, resource_size(res)); +err1: + dev_err(&pdev->dev, "init %s fail, %d\n", + dev_name(&pdev->dev), err); + + return err; +} + +static int ehci_orion_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct clk *clk; + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + clk = clk_get(&pdev->dev, NULL); + if (!IS_ERR(clk)) { + clk_disable_unprepare(clk); + clk_put(clk); + } + return 0; +} + +static const struct of_device_id ehci_orion_dt_ids[] = { + { .compatible = "marvell,orion-ehci", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ehci_orion_dt_ids); + +static struct platform_driver ehci_orion_driver = { + .probe = ehci_orion_drv_probe, + .remove = ehci_orion_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "orion-ehci", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ehci_orion_dt_ids), + }, +}; + +static int __init ehci_orion_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ehci_init_driver(&ehci_orion_hc_driver, NULL); + return platform_driver_register(&ehci_orion_driver); +} +module_init(ehci_orion_init); + +static void __exit ehci_orion_cleanup(void) +{ + platform_driver_unregister(&ehci_orion_driver); +} +module_exit(ehci_orion_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:orion-ehci"); +MODULE_AUTHOR("Tzachi Perelstein"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c new file mode 100644 index 00000000..fe131565 --- /dev/null +++ b/drivers/usb/host/ehci-pci.c @@ -0,0 +1,464 @@ +/* + * EHCI HCD (Host Controller Driver) PCI Bus Glue. + * + * Copyright (c) 2000-2004 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "ehci.h" +#include "pci-quirks.h" + +#define DRIVER_DESC "EHCI PCI platform driver" + +static const char hcd_name[] = "ehci-pci"; + +/* defined here to avoid adding to pci_ids.h for single instance use */ +#define PCI_DEVICE_ID_INTEL_CE4100_USB 0x2e70 + +/*-------------------------------------------------------------------------*/ +#define PCI_DEVICE_ID_INTEL_QUARK_X1000_SOC 0x0939 +static inline bool is_intel_quark_x1000(struct pci_dev *pdev) +{ + return pdev->vendor == PCI_VENDOR_ID_INTEL && + pdev->device == PCI_DEVICE_ID_INTEL_QUARK_X1000_SOC; +} + +/* + * 0x84 is the offset of in/out threshold register, + * and it is the same offset as the register of 'hostpc'. + */ +#define intel_quark_x1000_insnreg01 hostpc + +/* Maximum usable threshold value is 0x7f dwords for both IN and OUT */ +#define INTEL_QUARK_X1000_EHCI_MAX_THRESHOLD 0x007f007f + +/* called after powerup, by probe or system-pm "wakeup" */ +static int ehci_pci_reinit(struct ehci_hcd *ehci, struct pci_dev *pdev) +{ + int retval; + + /* we expect static quirk code to handle the "extended capabilities" + * (currently just BIOS handoff) allowed starting with EHCI 0.96 + */ + + /* PCI Memory-Write-Invalidate cycle support is optional (uncommon) */ + retval = pci_set_mwi(pdev); + if (!retval) + ehci_dbg(ehci, "MWI active\n"); + + /* Reset the threshold limit */ + if (is_intel_quark_x1000(pdev)) { + /* + * For the Intel QUARK X1000, raise the I/O threshold to the + * maximum usable value in order to improve performance. + */ + ehci_writel(ehci, INTEL_QUARK_X1000_EHCI_MAX_THRESHOLD, + ehci->regs->intel_quark_x1000_insnreg01); + } + + return 0; +} + +/* called during probe() after chip reset completes */ +static int ehci_pci_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + struct pci_dev *p_smbus; + u8 rev; + u32 temp; + int retval; + + ehci->caps = hcd->regs; + + /* + * ehci_init() causes memory for DMA transfers to be + * allocated. Thus, any vendor-specific workarounds based on + * limiting the type of memory used for DMA transfers must + * happen before ehci_setup() is called. + * + * Most other workarounds can be done either before or after + * init and reset; they are located here too. + */ + switch (pdev->vendor) { + case PCI_VENDOR_ID_TOSHIBA_2: + /* celleb's companion chip */ + if (pdev->device == 0x01b5) { +#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO + ehci->big_endian_mmio = 1; +#else + ehci_warn(ehci, + "unsupported big endian Toshiba quirk\n"); +#endif + } + break; + case PCI_VENDOR_ID_NVIDIA: + /* NVidia reports that certain chips don't handle + * QH, ITD, or SITD addresses above 2GB. (But TD, + * data buffer, and periodic schedule are normal.) + */ + switch (pdev->device) { + case 0x003c: /* MCP04 */ + case 0x005b: /* CK804 */ + case 0x00d8: /* CK8 */ + case 0x00e8: /* CK8S */ + if (pci_set_consistent_dma_mask(pdev, + DMA_BIT_MASK(31)) < 0) + ehci_warn(ehci, "can't enable NVidia " + "workaround for >2GB RAM\n"); + break; + + /* Some NForce2 chips have problems with selective suspend; + * fixed in newer silicon. + */ + case 0x0068: + if (pdev->revision < 0xa4) + ehci->no_selective_suspend = 1; + break; + } + break; + case PCI_VENDOR_ID_INTEL: + if (pdev->device == PCI_DEVICE_ID_INTEL_CE4100_USB) + hcd->has_tt = 1; + break; + case PCI_VENDOR_ID_TDI: + if (pdev->device == PCI_DEVICE_ID_TDI_EHCI) + hcd->has_tt = 1; + break; + case PCI_VENDOR_ID_AMD: + /* AMD PLL quirk */ + if (usb_amd_find_chipset_info()) + ehci->amd_pll_fix = 1; + /* AMD8111 EHCI doesn't work, according to AMD errata */ + if (pdev->device == 0x7463) { + ehci_info(ehci, "ignoring AMD8111 (errata)\n"); + retval = -EIO; + goto done; + } + + /* + * EHCI controller on AMD SB700/SB800/Hudson-2/3 platforms may + * read/write memory space which does not belong to it when + * there is NULL pointer with T-bit set to 1 in the frame list + * table. To avoid the issue, the frame list link pointer + * should always contain a valid pointer to a inactive qh. + */ + if (pdev->device == 0x7808) { + ehci->use_dummy_qh = 1; + ehci_info(ehci, "applying AMD SB700/SB800/Hudson-2/3 EHCI dummy qh workaround\n"); + } + break; + case PCI_VENDOR_ID_VIA: + if (pdev->device == 0x3104 && (pdev->revision & 0xf0) == 0x60) { + u8 tmp; + + /* The VT6212 defaults to a 1 usec EHCI sleep time which + * hogs the PCI bus *badly*. Setting bit 5 of 0x4B makes + * that sleep time use the conventional 10 usec. + */ + pci_read_config_byte(pdev, 0x4b, &tmp); + if (tmp & 0x20) + break; + pci_write_config_byte(pdev, 0x4b, tmp | 0x20); + } + break; + case PCI_VENDOR_ID_ATI: + /* AMD PLL quirk */ + if (usb_amd_find_chipset_info()) + ehci->amd_pll_fix = 1; + + /* + * EHCI controller on AMD SB700/SB800/Hudson-2/3 platforms may + * read/write memory space which does not belong to it when + * there is NULL pointer with T-bit set to 1 in the frame list + * table. To avoid the issue, the frame list link pointer + * should always contain a valid pointer to a inactive qh. + */ + if (pdev->device == 0x4396) { + ehci->use_dummy_qh = 1; + ehci_info(ehci, "applying AMD SB700/SB800/Hudson-2/3 EHCI dummy qh workaround\n"); + } + /* SB600 and old version of SB700 have a bug in EHCI controller, + * which causes usb devices lose response in some cases. + */ + if ((pdev->device == 0x4386) || (pdev->device == 0x4396)) { + p_smbus = pci_get_device(PCI_VENDOR_ID_ATI, + PCI_DEVICE_ID_ATI_SBX00_SMBUS, + NULL); + if (!p_smbus) + break; + rev = p_smbus->revision; + if ((pdev->device == 0x4386) || (rev == 0x3a) + || (rev == 0x3b)) { + u8 tmp; + ehci_info(ehci, "applying AMD SB600/SB700 USB " + "freeze workaround\n"); + pci_read_config_byte(pdev, 0x53, &tmp); + pci_write_config_byte(pdev, 0x53, tmp | (1<<3)); + } + pci_dev_put(p_smbus); + } + break; + case PCI_VENDOR_ID_NETMOS: + /* MosChip frame-index-register bug */ + ehci_info(ehci, "applying MosChip frame-index workaround\n"); + ehci->frame_index_bug = 1; + break; + } + + /* optional debug port, normally in the first BAR */ + temp = pci_find_capability(pdev, PCI_CAP_ID_DBG); + if (temp) { + pci_read_config_dword(pdev, temp, &temp); + temp >>= 16; + if (((temp >> 13) & 7) == 1) { + u32 hcs_params = ehci_readl(ehci, + &ehci->caps->hcs_params); + + temp &= 0x1fff; + ehci->debug = hcd->regs + temp; + temp = ehci_readl(ehci, &ehci->debug->control); + ehci_info(ehci, "debug port %d%s\n", + HCS_DEBUG_PORT(hcs_params), + (temp & DBGP_ENABLED) ? " IN USE" : ""); + if (!(temp & DBGP_ENABLED)) + ehci->debug = NULL; + } + } + + retval = ehci_setup(hcd); + if (retval) + return retval; + + /* These workarounds need to be applied after ehci_setup() */ + switch (pdev->vendor) { + case PCI_VENDOR_ID_NEC: + ehci->need_io_watchdog = 0; + break; + case PCI_VENDOR_ID_INTEL: + ehci->need_io_watchdog = 0; + break; + case PCI_VENDOR_ID_NVIDIA: + switch (pdev->device) { + /* MCP89 chips on the MacBookAir3,1 give EPROTO when + * fetching device descriptors unless LPM is disabled. + * There are also intermittent problems enumerating + * devices with PPCD enabled. + */ + case 0x0d9d: + ehci_info(ehci, "disable ppcd for nvidia mcp89\n"); + ehci->has_ppcd = 0; + ehci->command &= ~CMD_PPCEE; + break; + } + break; + } + + /* at least the Genesys GL880S needs fixup here */ + temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params); + temp &= 0x0f; + if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) { + ehci_dbg(ehci, "bogus port configuration: " + "cc=%d x pcc=%d < ports=%d\n", + HCS_N_CC(ehci->hcs_params), + HCS_N_PCC(ehci->hcs_params), + HCS_N_PORTS(ehci->hcs_params)); + + switch (pdev->vendor) { + case 0x17a0: /* GENESYS */ + /* GL880S: should be PORTS=2 */ + temp |= (ehci->hcs_params & ~0xf); + ehci->hcs_params = temp; + break; + case PCI_VENDOR_ID_NVIDIA: + /* NF4: should be PCC=10 */ + break; + } + } + + /* Serial Bus Release Number is at PCI 0x60 offset */ + if (pdev->vendor == PCI_VENDOR_ID_STMICRO + && pdev->device == PCI_DEVICE_ID_STMICRO_USB_HOST) + ; /* ConneXT has no sbrn register */ + else + pci_read_config_byte(pdev, 0x60, &ehci->sbrn); + + /* Keep this around for a while just in case some EHCI + * implementation uses legacy PCI PM support. This test + * can be removed on 17 Dec 2009 if the dev_warn() hasn't + * been triggered by then. + */ + if (!device_can_wakeup(&pdev->dev)) { + u16 port_wake; + + pci_read_config_word(pdev, 0x62, &port_wake); + if (port_wake & 0x0001) { + dev_warn(&pdev->dev, "Enabling legacy PCI PM\n"); + device_set_wakeup_capable(&pdev->dev, 1); + } + } + +#ifdef CONFIG_PM_RUNTIME + if (ehci->no_selective_suspend && device_can_wakeup(&pdev->dev)) + ehci_warn(ehci, "selective suspend/wakeup unavailable\n"); +#endif + + retval = ehci_pci_reinit(ehci, pdev); +done: + return retval; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +/* suspend/resume, section 4.3 */ + +/* These routines rely on the PCI bus glue + * to handle powerdown and wakeup, and currently also on + * transceivers that don't need any software attention to set up + * the right sort of wakeup. + * Also they depend on separate root hub suspend/resume. + */ + +static bool usb_is_intel_switchable_ehci(struct pci_dev *pdev) +{ + return pdev->class == PCI_CLASS_SERIAL_USB_EHCI && + pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == 0x1E26 || + pdev->device == 0x8C2D || + pdev->device == 0x8C26 || + pdev->device == 0x9C26); +} + +static void ehci_enable_xhci_companion(void) +{ + struct pci_dev *companion = NULL; + + /* The xHCI and EHCI controllers are not on the same PCI slot */ + for_each_pci_dev(companion) { + if (!usb_is_intel_switchable_xhci(companion)) + continue; + usb_enable_xhci_ports(companion); + return; + } +} + +static int ehci_pci_resume(struct usb_hcd *hcd, bool hibernated) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + + /* The BIOS on systems with the Intel Panther Point chipset may or may + * not support xHCI natively. That means that during system resume, it + * may switch the ports back to EHCI so that users can use their + * keyboard to select a kernel from GRUB after resume from hibernate. + * + * The BIOS is supposed to remember whether the OS had xHCI ports + * enabled before resume, and switch the ports back to xHCI when the + * BIOS/OS semaphore is written, but we all know we can't trust BIOS + * writers. + * + * Unconditionally switch the ports back to xHCI after a system resume. + * We can't tell whether the EHCI or xHCI controller will be resumed + * first, so we have to do the port switchover in both drivers. Writing + * a '1' to the port switchover registers should have no effect if the + * port was already switched over. + */ + if (usb_is_intel_switchable_ehci(pdev)) + ehci_enable_xhci_companion(); + + if (ehci_resume(hcd, hibernated) != 0) + (void) ehci_pci_reinit(ehci, pdev); + return 0; +} + +#else + +#define ehci_suspend NULL +#define ehci_pci_resume NULL +#endif /* CONFIG_PM */ + +static struct hc_driver __read_mostly ehci_pci_hc_driver; + +static const struct ehci_driver_overrides pci_overrides __initconst = { + .reset = ehci_pci_setup, +}; + +/*-------------------------------------------------------------------------*/ + +/* PCI driver selection metadata; PCI hotplugging uses this */ +static const struct pci_device_id pci_ids [] = { { + /* handle any USB 2.0 EHCI controller */ + PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_EHCI, ~0), + .driver_data = (unsigned long) &ehci_pci_hc_driver, + }, { + PCI_VDEVICE(STMICRO, PCI_DEVICE_ID_STMICRO_USB_HOST), + .driver_data = (unsigned long) &ehci_pci_hc_driver, + }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver ehci_pci_driver = { + .name = (char *) hcd_name, + .id_table = pci_ids, + + .probe = usb_hcd_pci_probe, + .remove = usb_hcd_pci_remove, + .shutdown = usb_hcd_pci_shutdown, + +#ifdef CONFIG_PM + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, +#endif +}; + +static int __init ehci_pci_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ehci_init_driver(&ehci_pci_hc_driver, &pci_overrides); + + /* Entries for the PCI suspend/resume callbacks are special */ + ehci_pci_hc_driver.pci_suspend = ehci_suspend; + ehci_pci_hc_driver.pci_resume = ehci_pci_resume; + + return pci_register_driver(&ehci_pci_driver); +} +module_init(ehci_pci_init); + +static void __exit ehci_pci_cleanup(void) +{ + pci_unregister_driver(&ehci_pci_driver); +} +module_exit(ehci_pci_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("David Brownell"); +MODULE_AUTHOR("Alan Stern"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c new file mode 100644 index 00000000..f47f2594 --- /dev/null +++ b/drivers/usb/host/ehci-platform.c @@ -0,0 +1,252 @@ +/* + * Generic platform ehci driver + * + * Copyright 2007 Steven Brown + * Copyright 2010-2012 Hauke Mehrtens + * + * Derived from the ohci-ssb driver + * Copyright 2007 Michael Buesch + * + * Derived from the EHCI-PCI driver + * Copyright (c) 2000-2004 by David Brownell + * + * Derived from the ohci-pci driver + * Copyright 1999 Roman Weissgaerber + * Copyright 2000-2002 David Brownell + * Copyright 1999 Linus Torvalds + * Copyright 1999 Gregory P. Smith + * + * Licensed under the GNU/GPL. See COPYING for details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI generic platform driver" + +static const char hcd_name[] = "ehci-platform"; + +static int ehci_platform_reset(struct usb_hcd *hcd) +{ + struct platform_device *pdev = to_platform_device(hcd->self.controller); + struct usb_ehci_pdata *pdata = pdev->dev.platform_data; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + hcd->has_tt = pdata->has_tt; + ehci->has_synopsys_hc_bug = pdata->has_synopsys_hc_bug; + ehci->big_endian_desc = pdata->big_endian_desc; + ehci->big_endian_mmio = pdata->big_endian_mmio; + + ehci->caps = hcd->regs + pdata->caps_offset; + retval = ehci_setup(hcd); + if (retval) + return retval; + + if (pdata->no_io_watchdog) + ehci->need_io_watchdog = 0; + return 0; +} + +static struct hc_driver __read_mostly ehci_platform_hc_driver; + +static const struct ehci_driver_overrides platform_overrides __initconst = { + .reset = ehci_platform_reset, +}; + +static struct usb_ehci_pdata ehci_platform_defaults; + +static int ehci_platform_probe(struct platform_device *dev) +{ + struct usb_hcd *hcd; + struct resource *res_mem; + struct usb_ehci_pdata *pdata; + int irq; + int err = -ENOMEM; + + if (usb_disabled()) + return -ENODEV; + + /* + * use reasonable defaults so platforms don't have to provide these. + * with DT probing on ARM, none of these are set. + */ + if (!dev->dev.platform_data) + dev->dev.platform_data = &ehci_platform_defaults; + if (!dev->dev.dma_mask) + dev->dev.dma_mask = &dev->dev.coherent_dma_mask; + if (!dev->dev.coherent_dma_mask) + dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + pdata = dev->dev.platform_data; + + irq = platform_get_irq(dev, 0); + if (irq < 0) { + dev_err(&dev->dev, "no irq provided"); + return irq; + } + res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res_mem) { + dev_err(&dev->dev, "no memory resource provided"); + return -ENXIO; + } + + if (pdata->power_on) { + err = pdata->power_on(dev); + if (err < 0) + return err; + } + + hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev, + dev_name(&dev->dev)); + if (!hcd) { + err = -ENOMEM; + goto err_power; + } + + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + + hcd->regs = devm_ioremap_resource(&dev->dev, res_mem); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto err_put_hcd; + } + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_put_hcd; + + platform_set_drvdata(dev, hcd); + + return err; + +err_put_hcd: + usb_put_hcd(hcd); +err_power: + if (pdata->power_off) + pdata->power_off(dev); + + return err; +} + +static int ehci_platform_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev->dev.platform_data; + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + platform_set_drvdata(dev, NULL); + + if (pdata->power_off) + pdata->power_off(dev); + + if (pdata == &ehci_platform_defaults) + dev->dev.platform_data = NULL; + + return 0; +} + +#ifdef CONFIG_PM + +static int ehci_platform_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev->platform_data; + struct platform_device *pdev = + container_of(dev, struct platform_device, dev); + bool do_wakeup = device_may_wakeup(dev); + int ret; + + ret = ehci_suspend(hcd, do_wakeup); + + if (pdata->power_suspend) + pdata->power_suspend(pdev); + + return ret; +} + +static int ehci_platform_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev->platform_data; + struct platform_device *pdev = + container_of(dev, struct platform_device, dev); + + if (pdata->power_on) { + int err = pdata->power_on(pdev); + if (err < 0) + return err; + } + + ehci_resume(hcd, false); + return 0; +} + +#else /* !CONFIG_PM */ +#define ehci_platform_suspend NULL +#define ehci_platform_resume NULL +#endif /* CONFIG_PM */ + +static const struct of_device_id vt8500_ehci_ids[] = { + { .compatible = "via,vt8500-ehci", }, + { .compatible = "wm,prizm-ehci", }, + {} +}; + +static const struct platform_device_id ehci_platform_table[] = { + { "ehci-platform", 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, ehci_platform_table); + +static const struct dev_pm_ops ehci_platform_pm_ops = { + .suspend = ehci_platform_suspend, + .resume = ehci_platform_resume, +}; + +static struct platform_driver ehci_platform_driver = { + .id_table = ehci_platform_table, + .probe = ehci_platform_probe, + .remove = ehci_platform_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "ehci-platform", + .pm = &ehci_platform_pm_ops, + .of_match_table = of_match_ptr(vt8500_ehci_ids), + } +}; + +static int __init ehci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides); + return platform_driver_register(&ehci_platform_driver); +} +module_init(ehci_platform_init); + +static void __exit ehci_platform_cleanup(void) +{ + platform_driver_unregister(&ehci_platform_driver); +} +module_exit(ehci_platform_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Hauke Mehrtens"); +MODULE_AUTHOR("Alan Stern"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c new file mode 100644 index 00000000..363890ee --- /dev/null +++ b/drivers/usb/host/ehci-pmcmsp.c @@ -0,0 +1,369 @@ +/* + * PMC MSP EHCI (Host Controller Driver) for USB. + * + * (C) Copyright 2006-2010 PMC-Sierra Inc + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +/* includes */ +#include +#include +#include +#include + +/* stream disable*/ +#define USB_CTRL_MODE_STREAM_DISABLE 0x10 + +/* threshold */ +#define USB_CTRL_FIFO_THRESH 0x00300000 + +/* register offset for usb_mode */ +#define USB_EHCI_REG_USB_MODE 0x68 + +/* register offset for usb fifo */ +#define USB_EHCI_REG_USB_FIFO 0x24 + +/* register offset for usb status */ +#define USB_EHCI_REG_USB_STATUS 0x44 + +/* serial/parallel transceiver */ +#define USB_EHCI_REG_BIT_STAT_STS (1<<29) + +/* TWI USB0 host device pin */ +#define MSP_PIN_USB0_HOST_DEV 49 + +/* TWI USB1 host device pin */ +#define MSP_PIN_USB1_HOST_DEV 50 + + +static void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci) +{ + u8 *base; + u8 *statreg; + u8 *fiforeg; + u32 val; + struct ehci_regs *reg_base = ehci->regs; + + /* get register base */ + base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE; + statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS; + fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO; + + /* Disable controller mode stream */ + val = ehci_readl(ehci, (u32 *)base); + ehci_writel(ehci, (val | USB_CTRL_MODE_STREAM_DISABLE), + (u32 *)base); + + /* clear STS to select parallel transceiver interface */ + val = ehci_readl(ehci, (u32 *)statreg); + val = val & ~USB_EHCI_REG_BIT_STAT_STS; + ehci_writel(ehci, val, (u32 *)statreg); + + /* write to set the proper fifo threshold */ + ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg); + + /* set TWI GPIO USB_HOST_DEV pin high */ + gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1); +#ifdef CONFIG_MSP_HAS_DUAL_USB + gpio_direction_output(MSP_PIN_USB1_HOST_DEV, 1); +#endif +} + +/* called during probe() after chip reset completes */ +static int ehci_msp_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + ehci->big_endian_mmio = 1; + ehci->big_endian_desc = 1; + + ehci->caps = hcd->regs; + hcd->has_tt = 1; + + retval = ehci_setup(hcd); + if (retval) + return retval; + + usb_hcd_tdi_set_mode(ehci); + + return retval; +} + + +/* configure so an HC device and id are always provided + * always called with process context; sleeping is OK + */ + +static int usb_hcd_msp_map_regs(struct mspusb_device *dev) +{ + struct resource *res; + struct platform_device *pdev = &dev->dev; + u32 res_len; + int retval; + + /* MAB register space */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res == NULL) + return -ENOMEM; + res_len = resource_size(res); + if (!request_mem_region(res->start, res_len, "mab regs")) + return -EBUSY; + + dev->mab_regs = ioremap_nocache(res->start, res_len); + if (dev->mab_regs == NULL) { + retval = -ENOMEM; + goto err1; + } + + /* MSP USB register space */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (res == NULL) { + retval = -ENOMEM; + goto err2; + } + res_len = resource_size(res); + if (!request_mem_region(res->start, res_len, "usbid regs")) { + retval = -EBUSY; + goto err2; + } + dev->usbid_regs = ioremap_nocache(res->start, res_len); + if (dev->usbid_regs == NULL) { + retval = -ENOMEM; + goto err3; + } + + return 0; +err3: + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + res_len = resource_size(res); + release_mem_region(res->start, res_len); +err2: + iounmap(dev->mab_regs); +err1: + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + res_len = resource_size(res); + release_mem_region(res->start, res_len); + dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n"); + return retval; +} + +/** + * usb_hcd_msp_probe - initialize PMC MSP-based HCDs + * Context: !in_interrupt() + * + * 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. + * + */ +int usb_hcd_msp_probe(const struct hc_driver *driver, + struct platform_device *dev) +{ + int retval; + struct usb_hcd *hcd; + struct resource *res; + struct ehci_hcd *ehci ; + + hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp"); + if (!hcd) + return -ENOMEM; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (res == NULL) { + pr_debug("No IOMEM resource info for %s.\n", dev->name); + retval = -ENOMEM; + goto err1; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) { + retval = -EBUSY; + goto err1; + } + hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + pr_debug("ioremap failed"); + retval = -ENOMEM; + goto err2; + } + + res = platform_get_resource(dev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name); + retval = -ENOMEM; + goto err3; + } + + /* Map non-EHCI register spaces */ + retval = usb_hcd_msp_map_regs(to_mspusb_device(dev)); + if (retval != 0) + goto err3; + + ehci = hcd_to_ehci(hcd); + ehci->big_endian_mmio = 1; + ehci->big_endian_desc = 1; + + + retval = usb_add_hcd(hcd, res->start, IRQF_SHARED); + if (retval == 0) + return 0; + + usb_remove_hcd(hcd); +err3: + iounmap(hcd->regs); +err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err1: + usb_put_hcd(hcd); + + return retval; +} + + + +/** + * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_msp_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + * may be called without controller electrically present + * may be called with controller, bus, and devices active + */ +void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev) +{ + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + +#ifdef CONFIG_MSP_HAS_DUAL_USB +/* + * Wrapper around the main ehci_irq. Since both USB host controllers are + * sharing the same IRQ, need to first determine whether we're the intended + * recipient of this interrupt. + */ +static irqreturn_t ehci_msp_irq(struct usb_hcd *hcd) +{ + u32 int_src; + struct device *dev = hcd->self.controller; + struct platform_device *pdev; + struct mspusb_device *mdev; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + /* need to reverse-map a couple of containers to get our device */ + pdev = to_platform_device(dev); + mdev = to_mspusb_device(pdev); + + /* Check to see if this interrupt is for this host controller */ + int_src = ehci_readl(ehci, &mdev->mab_regs->int_stat); + if (int_src & (1 << pdev->id)) + return ehci_irq(hcd); + + /* Not for this device */ + return IRQ_NONE; +} +#endif /* DUAL_USB */ + +static const struct hc_driver ehci_msp_hc_driver = { + .description = hcd_name, + .product_desc = "PMC MSP EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ +#ifdef CONFIG_MSP_HAS_DUAL_USB + .irq = ehci_msp_irq, +#else + .irq = ehci_irq, +#endif + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_msp_setup, + .start = ehci_run, + .shutdown = ehci_shutdown, + .start = ehci_run, + .stop = ehci_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ehci_hcd_msp_drv_probe(struct platform_device *pdev) +{ + int ret; + + pr_debug("In ehci_hcd_msp_drv_probe"); + + if (usb_disabled()) + return -ENODEV; + + gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO"); +#ifdef CONFIG_MSP_HAS_DUAL_USB + gpio_request(MSP_PIN_USB1_HOST_DEV, "USB1_HOST_DEV_GPIO"); +#endif + + ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev); + + return ret; +} + +static int ehci_hcd_msp_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_hcd_msp_remove(hcd, pdev); + + /* free TWI GPIO USB_HOST_DEV pin */ + gpio_free(MSP_PIN_USB0_HOST_DEV); +#ifdef CONFIG_MSP_HAS_DUAL_USB + gpio_free(MSP_PIN_USB1_HOST_DEV); +#endif + + return 0; +} + +MODULE_ALIAS("pmcmsp-ehci"); + +static struct platform_driver ehci_hcd_msp_driver = { + .probe = ehci_hcd_msp_drv_probe, + .remove = ehci_hcd_msp_drv_remove, + .driver = { + .name = "pmcmsp-ehci", + .owner = THIS_MODULE, + }, +}; diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c new file mode 100644 index 00000000..56dc732b --- /dev/null +++ b/drivers/usb/host/ehci-ppc-of.c @@ -0,0 +1,247 @@ +/* + * EHCI HCD (Host Controller Driver) for USB. + * + * Bus Glue for PPC On-Chip EHCI driver on the of_platform bus + * Tested on AMCC PPC 440EPx + * + * Valentine Barshak + * + * Based on "ehci-ppc-soc.c" by Stefan Roese + * and "ohci-ppc-of.c" by Sylvain Munaut + * + * This file is licenced under the GPL. + */ + +#include +#include + +#include +#include + + +static const struct hc_driver ehci_ppc_of_hc_driver = { + .description = hcd_name, + .product_desc = "OF EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + + +/* + * 440EPx Errata USBH_3 + * Fix: Enable Break Memory Transfer (BMT) in INSNREG3 + */ +#define PPC440EPX_EHCI0_INSREG_BMT (0x1 << 0) +static int +ppc44x_enable_bmt(struct device_node *dn) +{ + __iomem u32 *insreg_virt; + + insreg_virt = of_iomap(dn, 1); + if (!insreg_virt) + return -EINVAL; + + out_be32(insreg_virt + 3, PPC440EPX_EHCI0_INSREG_BMT); + + iounmap(insreg_virt); + return 0; +} + + +static int ehci_hcd_ppc_of_probe(struct platform_device *op) +{ + struct device_node *dn = op->dev.of_node; + struct usb_hcd *hcd; + struct ehci_hcd *ehci = NULL; + struct resource res; + int irq; + int rv; + + struct device_node *np; + + if (usb_disabled()) + return -ENODEV; + + dev_dbg(&op->dev, "initializing PPC-OF USB Controller\n"); + + rv = of_address_to_resource(dn, 0, &res); + if (rv) + return rv; + + hcd = usb_create_hcd(&ehci_ppc_of_hc_driver, &op->dev, "PPC-OF USB"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = res.start; + hcd->rsrc_len = resource_size(&res); + + irq = irq_of_parse_and_map(dn, 0); + if (irq == NO_IRQ) { + printk(KERN_ERR "%s: irq_of_parse_and_map failed\n", __FILE__); + rv = -EBUSY; + goto err_irq; + } + + hcd->regs = devm_ioremap_resource(&op->dev, &res); + if (IS_ERR(hcd->regs)) { + rv = PTR_ERR(hcd->regs); + goto err_ioremap; + } + + ehci = hcd_to_ehci(hcd); + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); + if (np != NULL) { + /* claim we really affected by usb23 erratum */ + if (!of_address_to_resource(np, 0, &res)) + ehci->ohci_hcctrl_reg = + devm_ioremap(&op->dev, + res.start + OHCI_HCCTRL_OFFSET, + OHCI_HCCTRL_LEN); + else + pr_debug("%s: no ohci offset in fdt\n", __FILE__); + if (!ehci->ohci_hcctrl_reg) { + pr_debug("%s: ioremap for ohci hcctrl failed\n", __FILE__); + } else { + ehci->has_amcc_usb23 = 1; + } + } + + if (of_get_property(dn, "big-endian", NULL)) { + ehci->big_endian_mmio = 1; + ehci->big_endian_desc = 1; + } + if (of_get_property(dn, "big-endian-regs", NULL)) + ehci->big_endian_mmio = 1; + if (of_get_property(dn, "big-endian-desc", NULL)) + ehci->big_endian_desc = 1; + + ehci->caps = hcd->regs; + + if (of_device_is_compatible(dn, "ibm,usb-ehci-440epx")) { + rv = ppc44x_enable_bmt(dn); + ehci_dbg(ehci, "Break Memory Transfer (BMT) is %senabled!\n", + rv ? "NOT ": ""); + } + + rv = usb_add_hcd(hcd, irq, 0); + if (rv) + goto err_ioremap; + + return 0; + +err_ioremap: + irq_dispose_mapping(irq); +err_irq: + usb_put_hcd(hcd); + + return rv; +} + + +static int ehci_hcd_ppc_of_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + struct device_node *np; + struct resource res; + + dev_set_drvdata(&op->dev, NULL); + + dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); + + usb_remove_hcd(hcd); + + irq_dispose_mapping(hcd->irq); + + /* use request_mem_region to test if the ohci driver is loaded. if so + * ensure the ohci core is operational. + */ + if (ehci->has_amcc_usb23) { + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); + if (np != NULL) { + if (!of_address_to_resource(np, 0, &res)) + if (!request_mem_region(res.start, + 0x4, hcd_name)) + set_ohci_hcfs(ehci, 1); + else + release_mem_region(res.start, 0x4); + else + pr_debug("%s: no ohci offset in fdt\n", __FILE__); + of_node_put(np); + } + } + usb_put_hcd(hcd); + + return 0; +} + + +static void ehci_hcd_ppc_of_shutdown(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + + +static const struct of_device_id ehci_hcd_ppc_of_match[] = { + { + .compatible = "usb-ehci", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ehci_hcd_ppc_of_match); + + +static struct platform_driver ehci_hcd_ppc_of_driver = { + .probe = ehci_hcd_ppc_of_probe, + .remove = ehci_hcd_ppc_of_remove, + .shutdown = ehci_hcd_ppc_of_shutdown, + .driver = { + .name = "ppc-of-ehci", + .owner = THIS_MODULE, + .of_match_table = ehci_hcd_ppc_of_match, + }, +}; diff --git a/drivers/usb/host/ehci-ps3.c b/drivers/usb/host/ehci-ps3.c new file mode 100644 index 00000000..fd983771 --- /dev/null +++ b/drivers/usb/host/ehci-ps3.c @@ -0,0 +1,265 @@ +/* + * PS3 EHCI Host Controller driver + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +static void ps3_ehci_setup_insnreg(struct ehci_hcd *ehci) +{ + /* PS3 HC internal setup register offsets. */ + + enum ps3_ehci_hc_insnreg { + ps3_ehci_hc_insnreg01 = 0x084, + ps3_ehci_hc_insnreg02 = 0x088, + ps3_ehci_hc_insnreg03 = 0x08c, + }; + + /* PS3 EHCI HC errata fix 316 - The PS3 EHCI HC will reset its + * internal INSNREGXX setup regs back to the chip default values + * on Host Controller Reset (CMD_RESET) or Light Host Controller + * Reset (CMD_LRESET). The work-around for this is for the HC + * driver to re-initialise these regs when ever the HC is reset. + */ + + /* Set burst transfer counts to 256 out, 32 in. */ + + writel_be(0x01000020, (void __iomem *)ehci->regs + + ps3_ehci_hc_insnreg01); + + /* Enable burst transfer counts. */ + + writel_be(0x00000001, (void __iomem *)ehci->regs + + ps3_ehci_hc_insnreg03); +} + +static int ps3_ehci_hc_reset(struct usb_hcd *hcd) +{ + int result; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + ehci->big_endian_mmio = 1; + ehci->caps = hcd->regs; + + result = ehci_setup(hcd); + if (result) + return result; + + ps3_ehci_setup_insnreg(ehci); + + return result; +} + +static const struct hc_driver ps3_ehci_hc_driver = { + .description = hcd_name, + .product_desc = "PS3 EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + .reset = ps3_ehci_hc_reset, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + .get_frame_number = ehci_get_frame, + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#if defined(CONFIG_PM) + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ps3_ehci_probe(struct ps3_system_bus_device *dev) +{ + int result; + struct usb_hcd *hcd; + unsigned int virq; + static u64 dummy_mask = DMA_BIT_MASK(32); + + if (usb_disabled()) { + result = -ENODEV; + goto fail_start; + } + + result = ps3_open_hv_device(dev); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_open_hv_device failed\n", + __func__, __LINE__); + goto fail_open; + } + + result = ps3_dma_region_create(dev->d_region); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_dma_region_create failed: " + "(%d)\n", __func__, __LINE__, result); + BUG_ON("check region type"); + goto fail_dma_region; + } + + result = ps3_mmio_region_create(dev->m_region); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_map_mmio_region failed\n", + __func__, __LINE__); + result = -EPERM; + goto fail_mmio_region; + } + + dev_dbg(&dev->core, "%s:%d: mmio mapped_addr %lxh\n", __func__, + __LINE__, dev->m_region->lpar_addr); + + result = ps3_io_irq_setup(PS3_BINDING_CPU_ANY, dev->interrupt_id, &virq); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_construct_io_irq(%d) failed.\n", + __func__, __LINE__, virq); + result = -EPERM; + goto fail_irq; + } + + dev->core.dma_mask = &dummy_mask; /* FIXME: for improper usb code */ + + hcd = usb_create_hcd(&ps3_ehci_hc_driver, &dev->core, dev_name(&dev->core)); + + if (!hcd) { + dev_dbg(&dev->core, "%s:%d: usb_create_hcd failed\n", __func__, + __LINE__); + result = -ENOMEM; + goto fail_create_hcd; + } + + hcd->rsrc_start = dev->m_region->lpar_addr; + hcd->rsrc_len = dev->m_region->len; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) + dev_dbg(&dev->core, "%s:%d: request_mem_region failed\n", + __func__, __LINE__); + + hcd->regs = ioremap(dev->m_region->lpar_addr, dev->m_region->len); + + if (!hcd->regs) { + dev_dbg(&dev->core, "%s:%d: ioremap failed\n", __func__, + __LINE__); + result = -EPERM; + goto fail_ioremap; + } + + dev_dbg(&dev->core, "%s:%d: hcd->rsrc_start %lxh\n", __func__, __LINE__, + (unsigned long)hcd->rsrc_start); + dev_dbg(&dev->core, "%s:%d: hcd->rsrc_len %lxh\n", __func__, __LINE__, + (unsigned long)hcd->rsrc_len); + dev_dbg(&dev->core, "%s:%d: hcd->regs %lxh\n", __func__, __LINE__, + (unsigned long)hcd->regs); + dev_dbg(&dev->core, "%s:%d: virq %lu\n", __func__, __LINE__, + (unsigned long)virq); + + ps3_system_bus_set_drvdata(dev, hcd); + + result = usb_add_hcd(hcd, virq, 0); + + if (result) { + dev_dbg(&dev->core, "%s:%d: usb_add_hcd failed (%d)\n", + __func__, __LINE__, result); + goto fail_add_hcd; + } + + return result; + +fail_add_hcd: + iounmap(hcd->regs); +fail_ioremap: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +fail_create_hcd: + ps3_io_irq_destroy(virq); +fail_irq: + ps3_free_mmio_region(dev->m_region); +fail_mmio_region: + ps3_dma_region_free(dev->d_region); +fail_dma_region: + ps3_close_hv_device(dev); +fail_open: +fail_start: + return result; +} + +static int ps3_ehci_remove(struct ps3_system_bus_device *dev) +{ + unsigned int tmp; + struct usb_hcd *hcd = ps3_system_bus_get_drvdata(dev); + + BUG_ON(!hcd); + + dev_dbg(&dev->core, "%s:%d: regs %p\n", __func__, __LINE__, hcd->regs); + dev_dbg(&dev->core, "%s:%d: irq %u\n", __func__, __LINE__, hcd->irq); + + tmp = hcd->irq; + + usb_remove_hcd(hcd); + + ps3_system_bus_set_drvdata(dev, NULL); + + BUG_ON(!hcd->regs); + iounmap(hcd->regs); + + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + ps3_io_irq_destroy(tmp); + ps3_free_mmio_region(dev->m_region); + + ps3_dma_region_free(dev->d_region); + ps3_close_hv_device(dev); + + return 0; +} + +static int __init ps3_ehci_driver_register(struct ps3_system_bus_driver *drv) +{ + return firmware_has_feature(FW_FEATURE_PS3_LV1) + ? ps3_system_bus_driver_register(drv) + : 0; +} + +static void ps3_ehci_driver_unregister(struct ps3_system_bus_driver *drv) +{ + if (firmware_has_feature(FW_FEATURE_PS3_LV1)) + ps3_system_bus_driver_unregister(drv); +} + +MODULE_ALIAS(PS3_MODULE_ALIAS_EHCI); + +static struct ps3_system_bus_driver ps3_ehci_driver = { + .core.name = "ps3-ehci-driver", + .core.owner = THIS_MODULE, + .match_id = PS3_MATCH_ID_EHCI, + .probe = ps3_ehci_probe, + .remove = ps3_ehci_remove, + .shutdown = ps3_ehci_remove, +}; diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c new file mode 100644 index 00000000..d34b399b --- /dev/null +++ b/drivers/usb/host/ehci-q.c @@ -0,0 +1,1368 @@ +/* + * Copyright (C) 2001-2004 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI hardware queue manipulation ... the core. QH/QTD manipulation. + * + * Control, bulk, and interrupt traffic all use "qh" lists. They list "qtd" + * entries describing USB transactions, max 16-20kB/entry (with 4kB-aligned + * buffers needed for the larger number). We use one QH per endpoint, queue + * multiple urbs (all three types) per endpoint. URBs may need several qtds. + * + * ISO traffic uses "ISO TD" (itd, and sitd) records, and (along with + * interrupts) needs careful scheduling. Performance improvements can be + * an ongoing challenge. That's in "ehci-sched.c". + * + * USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, + * or otherwise through transaction translators (TTs) in USB 2.0 hubs using + * (b) special fields in qh entries or (c) split iso entries. TTs will + * buffer low/full speed data so the host collects it at high speed. + */ + +/*-------------------------------------------------------------------------*/ + +/* fill a qtd, returning how much of the buffer we were able to queue up */ + +static int +qtd_fill(struct ehci_hcd *ehci, struct ehci_qtd *qtd, dma_addr_t buf, + size_t len, int token, int maxpacket) +{ + int i, count; + u64 addr = buf; + + /* one buffer entry per 4K ... first might be short or unaligned */ + qtd->hw_buf[0] = cpu_to_hc32(ehci, (u32)addr); + qtd->hw_buf_hi[0] = cpu_to_hc32(ehci, (u32)(addr >> 32)); + count = 0x1000 - (buf & 0x0fff); /* rest of that page */ + if (likely (len < count)) /* ... iff needed */ + count = len; + else { + buf += 0x1000; + buf &= ~0x0fff; + + /* per-qtd limit: from 16K to 20K (best alignment) */ + for (i = 1; count < len && i < 5; i++) { + addr = buf; + qtd->hw_buf[i] = cpu_to_hc32(ehci, (u32)addr); + qtd->hw_buf_hi[i] = cpu_to_hc32(ehci, + (u32)(addr >> 32)); + buf += 0x1000; + if ((count + 0x1000) < len) + count += 0x1000; + else + count = len; + } + + /* short packets may only terminate transfers */ + if (count != len) + count -= (count % maxpacket); + } + qtd->hw_token = cpu_to_hc32(ehci, (count << 16) | token); + qtd->length = count; + + return count; +} + +/*-------------------------------------------------------------------------*/ + +static inline void +qh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd) +{ + struct ehci_qh_hw *hw = qh->hw; + + /* writes to an active overlay are unsafe */ + WARN_ON(qh->qh_state != QH_STATE_IDLE); + + hw->hw_qtd_next = QTD_NEXT(ehci, qtd->qtd_dma); + hw->hw_alt_next = EHCI_LIST_END(ehci); + + /* Except for control endpoints, we make hardware maintain data + * toggle (like OHCI) ... here (re)initialize the toggle in the QH, + * and set the pseudo-toggle in udev. Only usb_clear_halt() will + * ever clear it. + */ + if (!(hw->hw_info1 & cpu_to_hc32(ehci, QH_TOGGLE_CTL))) { + unsigned is_out, epnum; + + is_out = qh->is_out; + epnum = (hc32_to_cpup(ehci, &hw->hw_info1) >> 8) & 0x0f; + if (unlikely (!usb_gettoggle (qh->dev, epnum, is_out))) { + hw->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE); + usb_settoggle (qh->dev, epnum, is_out, 1); + } + } + + hw->hw_token &= cpu_to_hc32(ehci, QTD_TOGGLE | QTD_STS_PING); +} + +/* if it weren't for a common silicon quirk (writing the dummy into the qh + * overlay, so qh->hw_token wrongly becomes inactive/halted), only fault + * recovery (including urb dequeue) would need software changes to a QH... + */ +static void +qh_refresh (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + struct ehci_qtd *qtd; + + qtd = list_entry(qh->qtd_list.next, struct ehci_qtd, qtd_list); + + /* + * first qtd may already be partially processed. + * If we come here during unlink, the QH overlay region + * might have reference to the just unlinked qtd. The + * qtd is updated in qh_completions(). Update the QH + * overlay here. + */ + if (qh->hw->hw_token & ACTIVE_BIT(ehci)) + qh->hw->hw_qtd_next = qtd->hw_next; + else + qh_update(ehci, qh, qtd); +} + +/*-------------------------------------------------------------------------*/ + +static void qh_link_async(struct ehci_hcd *ehci, struct ehci_qh *qh); + +static void ehci_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct ehci_qh *qh = ep->hcpriv; + unsigned long flags; + + spin_lock_irqsave(&ehci->lock, flags); + qh->clearing_tt = 0; + if (qh->qh_state == QH_STATE_IDLE && !list_empty(&qh->qtd_list) + && ehci->rh_state == EHCI_RH_RUNNING) + qh_link_async(ehci, qh); + spin_unlock_irqrestore(&ehci->lock, flags); +} + +static void ehci_clear_tt_buffer(struct ehci_hcd *ehci, struct ehci_qh *qh, + struct urb *urb, u32 token) +{ + + /* If an async split transaction gets an error or is unlinked, + * the TT buffer may be left in an indeterminate state. We + * have to clear the TT buffer. + * + * Note: this routine is never called for Isochronous transfers. + */ + if (urb->dev->tt && !usb_pipeint(urb->pipe) && !qh->clearing_tt) { +#ifdef DEBUG + struct usb_device *tt = urb->dev->tt->hub; + dev_dbg(&tt->dev, + "clear tt buffer port %d, a%d ep%d t%08x\n", + urb->dev->ttport, urb->dev->devnum, + usb_pipeendpoint(urb->pipe), token); +#endif /* DEBUG */ + if (!ehci_is_TDI(ehci) + || urb->dev->tt->hub != + ehci_to_hcd(ehci)->self.root_hub) { + if (usb_hub_clear_tt_buffer(urb) == 0) + qh->clearing_tt = 1; + } else { + + /* REVISIT ARC-derived cores don't clear the root + * hub TT buffer in this way... + */ + } + } +} + +static int qtd_copy_status ( + struct ehci_hcd *ehci, + struct urb *urb, + size_t length, + u32 token +) +{ + int status = -EINPROGRESS; + + /* count IN/OUT bytes, not SETUP (even short packets) */ + if (likely (QTD_PID (token) != 2)) + urb->actual_length += length - QTD_LENGTH (token); + + /* don't modify error codes */ + if (unlikely(urb->unlinked)) + return status; + + /* force cleanup after short read; not always an error */ + if (unlikely (IS_SHORT_READ (token))) + status = -EREMOTEIO; + + /* serious "can't proceed" faults reported by the hardware */ + if (token & QTD_STS_HALT) { + if (token & QTD_STS_BABBLE) { + /* FIXME "must" disable babbling device's port too */ + status = -EOVERFLOW; + /* CERR nonzero + halt --> stall */ + } else if (QTD_CERR(token)) { + status = -EPIPE; + + /* In theory, more than one of the following bits can be set + * since they are sticky and the transaction is retried. + * Which to test first is rather arbitrary. + */ + } else if (token & QTD_STS_MMF) { + /* fs/ls interrupt xfer missed the complete-split */ + status = -EPROTO; + } else if (token & QTD_STS_DBE) { + status = (QTD_PID (token) == 1) /* IN ? */ + ? -ENOSR /* hc couldn't read data */ + : -ECOMM; /* hc couldn't write data */ + } else if (token & QTD_STS_XACT) { + /* timeout, bad CRC, wrong PID, etc */ + ehci_dbg(ehci, "devpath %s ep%d%s 3strikes\n", + urb->dev->devpath, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out"); + status = -EPROTO; + } else { /* unknown */ + status = -EPROTO; + } + + ehci_vdbg (ehci, + "dev%d ep%d%s qtd token %08x --> status %d\n", + usb_pipedevice (urb->pipe), + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + token, status); + } + + return status; +} + +static void +ehci_urb_done(struct ehci_hcd *ehci, struct urb *urb, int status) +__releases(ehci->lock) +__acquires(ehci->lock) +{ + if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { + /* ... update hc-wide periodic stats */ + ehci_to_hcd(ehci)->self.bandwidth_int_reqs--; + } + + if (unlikely(urb->unlinked)) { + COUNT(ehci->stats.unlink); + } else { + /* report non-error and short read status as zero */ + if (status == -EINPROGRESS || status == -EREMOTEIO) + status = 0; + COUNT(ehci->stats.complete); + } + +#ifdef EHCI_URB_TRACE + ehci_dbg (ehci, + "%s %s urb %p ep%d%s status %d len %d/%d\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + status, + urb->actual_length, urb->transfer_buffer_length); +#endif + + /* complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); + spin_unlock (&ehci->lock); + usb_hcd_giveback_urb(ehci_to_hcd(ehci), urb, status); + spin_lock (&ehci->lock); +} + +static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh); + +/* + * Process and free completed qtds for a qh, returning URBs to drivers. + * Chases up to qh->hw_current. Returns nonzero if the caller should + * unlink qh. + */ +static unsigned +qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + struct ehci_qtd *last, *end = qh->dummy; + struct list_head *entry, *tmp; + int last_status; + int stopped; + u8 state; + struct ehci_qh_hw *hw = qh->hw; + + /* completions (or tasks on other cpus) must never clobber HALT + * till we've gone through and cleaned everything up, even when + * they add urbs to this qh's queue or mark them for unlinking. + * + * NOTE: unlinking expects to be done in queue order. + * + * It's a bug for qh->qh_state to be anything other than + * QH_STATE_IDLE, unless our caller is scan_async() or + * scan_intr(). + */ + state = qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + stopped = (state == QH_STATE_IDLE); + + rescan: + last = NULL; + last_status = -EINPROGRESS; + qh->dequeue_during_giveback = 0; + + /* remove de-activated QTDs from front of queue. + * after faults (including short reads), cleanup this urb + * then let the queue advance. + * if queue is stopped, handles unlinks. + */ + list_for_each_safe (entry, tmp, &qh->qtd_list) { + struct ehci_qtd *qtd; + struct urb *urb; + u32 token = 0; + + qtd = list_entry (entry, struct ehci_qtd, qtd_list); + urb = qtd->urb; + + /* clean up any state from previous QTD ...*/ + if (last) { + if (likely (last->urb != urb)) { + ehci_urb_done(ehci, last->urb, last_status); + last_status = -EINPROGRESS; + } + ehci_qtd_free (ehci, last); + last = NULL; + } + + /* ignore urbs submitted during completions we reported */ + if (qtd == end) + break; + + /* hardware copies qtd out of qh overlay */ + rmb (); + token = hc32_to_cpu(ehci, qtd->hw_token); + + /* always clean up qtds the hc de-activated */ + retry_xacterr: + if ((token & QTD_STS_ACTIVE) == 0) { + + /* Report Data Buffer Error: non-fatal but useful */ + if (token & QTD_STS_DBE) + ehci_dbg(ehci, + "detected DataBufferErr for urb %p ep%d%s len %d, qtd %p [qh %p]\n", + urb, + usb_endpoint_num(&urb->ep->desc), + usb_endpoint_dir_in(&urb->ep->desc) ? "in" : "out", + urb->transfer_buffer_length, + qtd, + qh); + + /* on STALL, error, and short reads this urb must + * complete and all its qtds must be recycled. + */ + if ((token & QTD_STS_HALT) != 0) { + + /* retry transaction errors until we + * reach the software xacterr limit + */ + if ((token & QTD_STS_XACT) && + QTD_CERR(token) == 0 && + ++qh->xacterrs < QH_XACTERR_MAX && + !urb->unlinked) { + ehci_dbg(ehci, + "detected XactErr len %zu/%zu retry %d\n", + qtd->length - QTD_LENGTH(token), qtd->length, qh->xacterrs); + + /* reset the token in the qtd and the + * qh overlay (which still contains + * the qtd) so that we pick up from + * where we left off + */ + token &= ~QTD_STS_HALT; + token |= QTD_STS_ACTIVE | + (EHCI_TUNE_CERR << 10); + qtd->hw_token = cpu_to_hc32(ehci, + token); + wmb(); + hw->hw_token = cpu_to_hc32(ehci, + token); + goto retry_xacterr; + } + stopped = 1; + + /* magic dummy for some short reads; qh won't advance. + * that silicon quirk can kick in with this dummy too. + * + * other short reads won't stop the queue, including + * control transfers (status stage handles that) or + * most other single-qtd reads ... the queue stops if + * URB_SHORT_NOT_OK was set so the driver submitting + * the urbs could clean it up. + */ + } else if (IS_SHORT_READ (token) + && !(qtd->hw_alt_next + & EHCI_LIST_END(ehci))) { + stopped = 1; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely (!stopped + && ehci->rh_state >= EHCI_RH_RUNNING)) { + break; + + /* scan the whole queue for unlinks whenever it stops */ + } else { + stopped = 1; + + /* cancel everything if we halt, suspend, etc */ + if (ehci->rh_state < EHCI_RH_RUNNING) + last_status = -ESHUTDOWN; + + /* this qtd is active; skip it unless a previous qtd + * for its urb faulted, or its urb was canceled. + */ + else if (last_status == -EINPROGRESS && !urb->unlinked) + continue; + + /* + * If this was the active qtd when the qh was unlinked + * and the overlay's token is active, then the overlay + * hasn't been written back to the qtd yet so use its + * token instead of the qtd's. After the qtd is + * processed and removed, the overlay won't be valid + * any more. + */ + if (state == QH_STATE_IDLE && + qh->qtd_list.next == &qtd->qtd_list && + (hw->hw_token & ACTIVE_BIT(ehci))) { + token = hc32_to_cpu(ehci, hw->hw_token); + hw->hw_token &= ~ACTIVE_BIT(ehci); + + /* An unlink may leave an incomplete + * async transaction in the TT buffer. + * We have to clear it. + */ + ehci_clear_tt_buffer(ehci, qh, urb, token); + } + } + + /* unless we already know the urb's status, collect qtd status + * and update count of bytes transferred. in common short read + * cases with only one data qtd (including control transfers), + * queue processing won't halt. but with two or more qtds (for + * example, with a 32 KB transfer), when the first qtd gets a + * short read the second must be removed by hand. + */ + if (last_status == -EINPROGRESS) { + last_status = qtd_copy_status(ehci, urb, + qtd->length, token); + if (last_status == -EREMOTEIO + && (qtd->hw_alt_next + & EHCI_LIST_END(ehci))) + last_status = -EINPROGRESS; + + /* As part of low/full-speed endpoint-halt processing + * we must clear the TT buffer (11.17.5). + */ + if (unlikely(last_status != -EINPROGRESS && + last_status != -EREMOTEIO)) { + /* The TT's in some hubs malfunction when they + * receive this request following a STALL (they + * stop sending isochronous packets). Since a + * STALL can't leave the TT buffer in a busy + * state (if you believe Figures 11-48 - 11-51 + * in the USB 2.0 spec), we won't clear the TT + * buffer in this case. Strictly speaking this + * is a violation of the spec. + */ + if (last_status != -EPIPE) + ehci_clear_tt_buffer(ehci, qh, urb, + token); + } + } + + /* if we're removing something not at the queue head, + * patch the hardware queue pointer. + */ + if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { + last = list_entry (qtd->qtd_list.prev, + struct ehci_qtd, qtd_list); + last->hw_next = qtd->hw_next; + } + + /* remove qtd; it's recycled after possible urb completion */ + list_del (&qtd->qtd_list); + last = qtd; + + /* reinit the xacterr counter for the next qtd */ + qh->xacterrs = 0; + } + + /* last urb's completion might still need calling */ + if (likely (last != NULL)) { + ehci_urb_done(ehci, last->urb, last_status); + ehci_qtd_free (ehci, last); + } + + /* Do we need to rescan for URBs dequeued during a giveback? */ + if (unlikely(qh->dequeue_during_giveback)) { + /* If the QH is already unlinked, do the rescan now. */ + if (state == QH_STATE_IDLE) + goto rescan; + + /* Otherwise the caller must unlink the QH. */ + } + + /* restore original state; caller must unlink or relink */ + qh->qh_state = state; + + /* be sure the hardware's done with the qh before refreshing + * it after fault cleanup, or recovering from silicon wrongly + * overlaying the dummy qtd (which reduces DMA chatter). + * + * We won't refresh a QH that's linked (after the HC + * stopped the queue). That avoids a race: + * - HC reads first part of QH; + * - CPU updates that first part and the token; + * - HC reads rest of that QH, including token + * Result: HC gets an inconsistent image, and then + * DMAs to/from the wrong memory (corrupting it). + * + * That should be rare for interrupt transfers, + * except maybe high bandwidth ... + */ + if (stopped != 0 || hw->hw_qtd_next == EHCI_LIST_END(ehci)) + qh->exception = 1; + + /* Let the caller know if the QH needs to be unlinked. */ + return qh->exception; +} + +/*-------------------------------------------------------------------------*/ + +// high bandwidth multiplier, as encoded in highspeed endpoint descriptors +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) +// ... and packet size, for any kind of endpoint descriptor +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/* + * reverse of qh_urb_transaction: free a list of TDs. + * used for cleanup after errors, before HC sees an URB's TDs. + */ +static void qtd_list_free ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list +) { + struct list_head *entry, *temp; + + list_for_each_safe (entry, temp, qtd_list) { + struct ehci_qtd *qtd; + + qtd = list_entry (entry, struct ehci_qtd, qtd_list); + list_del (&qtd->qtd_list); + ehci_qtd_free (ehci, qtd); + } +} + +/* + * create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head * +qh_urb_transaction ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *head, + gfp_t flags +) { + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, this_sg_len, maxpacket; + int is_input; + u32 token; + int i; + struct scatterlist *sg; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + return NULL; + list_add_tail (&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + len = urb->transfer_buffer_length; + is_input = usb_pipein (urb->pipe); + if (usb_pipecontrol (urb->pipe)) { + /* SETUP pid */ + qtd_fill(ehci, qtd, urb->setup_dma, + sizeof (struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (len == 0) + token |= (1 /* "in" */ << 8); + } + + /* + * data transfer stage: buffer setup + */ + i = urb->num_mapped_sgs; + if (len > 0 && i > 0) { + sg = urb->sg; + buf = sg_dma_address(sg); + + /* urb->transfer_buffer_length may be smaller than the + * size of the scatterlist (or vice versa) + */ + this_sg_len = min_t(int, sg_dma_len(sg), len); + } else { + sg = NULL; + buf = urb->transfer_dma; + this_sg_len = len; + } + + if (is_input) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input)); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + this_qtd_len = qtd_fill(ehci, qtd, buf, this_sg_len, token, + maxpacket); + this_sg_len -= this_qtd_len; + len -= this_qtd_len; + buf += this_qtd_len; + + /* + * short reads advance to a "magic" dummy instead of the next + * qtd ... that forces the queue to stop, for manual cleanup. + * (this will usually be overridden later.) + */ + if (is_input) + qtd->hw_alt_next = ehci->async->hw->hw_alt_next; + + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely(this_sg_len <= 0)) { + if (--i <= 0 || len <= 0) + break; + sg = sg_next(sg); + buf = sg_dma_address(sg); + this_sg_len = min_t(int, sg_dma_len(sg), len); + } + + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + } + + /* + * unless the caller requires manual cleanup after short reads, + * have the alt_next mechanism keep the queue running after the + * last data qtd (the only one, for control and most other cases). + */ + if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 + || usb_pipecontrol (urb->pipe))) + qtd->hw_alt_next = EHCI_LIST_END(ehci); + + /* + * control requests may need a terminating data "status" ack; + * other OUT ones may need a terminating short packet + * (zero length). + */ + if (likely (urb->transfer_buffer_length != 0)) { + int one_more = 0; + + if (usb_pipecontrol (urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipeout(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill(ehci, qtd, 0, 0, token, 0); + } + } + + /* by default, enable interrupt on urb completion */ + if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT))) + qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC); + return head; + +cleanup: + qtd_list_free (ehci, urb, head); + return NULL; +} + +/*-------------------------------------------------------------------------*/ + +// Would be best to create all qh's from config descriptors, +// when each interface/altsetting is established. Unlink +// any previous qh and cancel its urbs first; endpoints are +// implicitly reset then (data toggle too). +// That'd mean updating how usbcore talks to HCDs. (2.7?) + + +/* + * Each QH holds a qtd list; a QH is used for everything except iso. + * + * For interrupt urbs, the scheduler must set the microframe scheduling + * mask(s) each time the QH gets scheduled. For highspeed, that's + * just one microframe in the s-mask. For split interrupt transactions + * there are additional complications: c-mask, maybe FSTNs. + */ +static struct ehci_qh * +qh_make ( + struct ehci_hcd *ehci, + struct urb *urb, + gfp_t flags +) { + struct ehci_qh *qh = ehci_qh_alloc (ehci, flags); + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + struct usb_tt *tt = urb->dev->tt; + struct ehci_qh_hw *hw; + + if (!qh) + return qh; + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint (urb->pipe) << 8; + info1 |= usb_pipedevice (urb->pipe) << 0; + + is_input = usb_pipein (urb->pipe); + type = usb_pipetype (urb->pipe); + maxp = usb_maxpacket (urb->dev, urb->pipe, !is_input); + + /* 1024 byte maxpacket is a hardware ceiling. High bandwidth + * acts like up to 3KB, but is built from smaller packets. + */ + if (max_packet(maxp) > 1024) { + ehci_dbg(ehci, "bogus qh maxpacket %d\n", max_packet(maxp)); + goto done; + } + + /* Compute interrupt scheduling parameters just once, and save. + * - allowing for high bandwidth, how many nsec/uframe are used? + * - split transactions need a second CSPLIT uframe; same question + * - splits also need a schedule gap (for full/low speed I/O) + * - qh has a polling interval + * + * For control/bulk requests, the HC or TT handles these. + */ + if (type == PIPE_INTERRUPT) { + qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, + is_input, 0, + hb_mult(maxp) * max_packet(maxp))); + qh->start = NO_FRAME; + + if (urb->dev->speed == USB_SPEED_HIGH) { + qh->c_usecs = 0; + qh->gap_uf = 0; + + qh->period = urb->interval >> 3; + if (qh->period == 0 && urb->interval != 1) { + /* NOTE interval 2 or 4 uframes could work. + * But interval 1 scheduling is simpler, and + * includes high bandwidth. + */ + urb->interval = 1; + } else if (qh->period > ehci->periodic_size) { + qh->period = ehci->periodic_size; + urb->interval = qh->period << 3; + } + } else { + int think_time; + + /* gap is f(FS/LS transfer times) */ + qh->gap_uf = 1 + usb_calc_bus_time (urb->dev->speed, + is_input, 0, maxp) / (125 * 1000); + + /* FIXME this just approximates SPLIT/CSPLIT times */ + if (is_input) { // SPLIT, gap, CSPLIT+DATA + qh->c_usecs = qh->usecs + HS_USECS (0); + qh->usecs = HS_USECS (1); + } else { // SPLIT+DATA, gap, CSPLIT + qh->usecs += HS_USECS (1); + qh->c_usecs = HS_USECS (0); + } + + think_time = tt ? tt->think_time : 0; + qh->tt_usecs = NS_TO_US (think_time + + usb_calc_bus_time (urb->dev->speed, + is_input, 0, max_packet (maxp))); + qh->period = urb->interval; + if (qh->period > ehci->periodic_size) { + qh->period = ehci->periodic_size; + urb->interval = qh->period; + } + } + } + + /* support for tt scheduling, and access to toggles */ + qh->dev = urb->dev; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= QH_LOW_SPEED; + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) + info1 |= (EHCI_TUNE_RL_TT << 28); + if (type == PIPE_CONTROL) { + info1 |= QH_CONTROL_EP; /* for TT */ + info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ + } + info1 |= maxp << 16; + + info2 |= (EHCI_TUNE_MULT_TT << 30); + + /* Some Freescale processors have an erratum in which the + * port number in the queue head was 0..N-1 instead of 1..N. + */ + if (ehci_has_fsl_portno_bug(ehci)) + info2 |= (urb->dev->ttport-1) << 23; + else + info2 |= urb->dev->ttport << 23; + + /* set the address of the TT; for TDI's integrated + * root hub tt, leave it zeroed. + */ + if (tt && tt->hub != ehci_to_hcd(ehci)->self.root_hub) + info2 |= tt->hub->devnum << 16; + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ + + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= QH_HIGH_SPEED; + if (type == PIPE_CONTROL) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (EHCI_TUNE_RL_HS << 28); + /* The USB spec says that high speed bulk endpoints + * always use 512 byte maxpacket. But some device + * vendors decided to ignore that, and MSFT is happy + * to help them do so. So now people expect to use + * such nonconformant devices with Linux too; sigh. + */ + info1 |= max_packet(maxp) << 16; + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= max_packet (maxp) << 16; + info2 |= hb_mult (maxp) << 30; + } + break; + default: + ehci_dbg(ehci, "bogus dev %p speed %d\n", urb->dev, + urb->dev->speed); +done: + qh_destroy(ehci, qh); + return NULL; + } + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ + + /* init as live, toggle clear */ + qh->qh_state = QH_STATE_IDLE; + hw = qh->hw; + hw->hw_info1 = cpu_to_hc32(ehci, info1); + hw->hw_info2 = cpu_to_hc32(ehci, info2); + qh->is_out = !is_input; + usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1); + return qh; +} + +/*-------------------------------------------------------------------------*/ + +static void enable_async(struct ehci_hcd *ehci) +{ + if (ehci->async_count++) + return; + + /* Stop waiting to turn off the async schedule */ + ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_DISABLE_ASYNC); + + /* Don't start the schedule until ASS is 0 */ + ehci_poll_ASS(ehci); + turn_on_io_watchdog(ehci); +} + +static void disable_async(struct ehci_hcd *ehci) +{ + if (--ehci->async_count) + return; + + /* The async schedule and unlink lists are supposed to be empty */ + WARN_ON(ehci->async->qh_next.qh || !list_empty(&ehci->async_unlink) || + !list_empty(&ehci->async_idle)); + + /* Don't turn off the schedule until ASS is 1 */ + ehci_poll_ASS(ehci); +} + +/* move qh (and its qtds) onto async queue; maybe enable queue. */ + +static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + __hc32 dma = QH_NEXT(ehci, qh->qh_dma); + struct ehci_qh *head; + + /* Don't link a QH if there's a Clear-TT-Buffer pending */ + if (unlikely(qh->clearing_tt)) + return; + + WARN_ON(qh->qh_state != QH_STATE_IDLE); + + /* clear halt and/or toggle; and maybe recover from silicon quirk */ + qh_refresh(ehci, qh); + + /* splice right after start */ + head = ehci->async; + qh->qh_next = head->qh_next; + qh->hw->hw_next = head->hw->hw_next; + wmb (); + + head->qh_next.qh = qh; + head->hw->hw_next = dma; + + qh->qh_state = QH_STATE_LINKED; + qh->xacterrs = 0; + qh->exception = 0; + /* qtd completions reported later by interrupt */ + + enable_async(ehci); +} + +/*-------------------------------------------------------------------------*/ + +/* + * For control/bulk/interrupt, return QH with these TDs appended. + * Allocates and initializes the QH if necessary. + * Returns null if it can't allocate a QH it needs to. + * If the QH has TDs (urbs) already, that's great. + */ +static struct ehci_qh *qh_append_tds ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + int epnum, + void **ptr +) +{ + struct ehci_qh *qh = NULL; + __hc32 qh_addr_mask = cpu_to_hc32(ehci, 0x7f); + + qh = (struct ehci_qh *) *ptr; + if (unlikely (qh == NULL)) { + /* can't sleep here, we have ehci->lock... */ + qh = qh_make (ehci, urb, GFP_ATOMIC); + *ptr = qh; + } + if (likely (qh != NULL)) { + struct ehci_qtd *qtd; + + if (unlikely (list_empty (qtd_list))) + qtd = NULL; + else + qtd = list_entry (qtd_list->next, struct ehci_qtd, + qtd_list); + + /* control qh may need patching ... */ + if (unlikely (epnum == 0)) { + + /* usb_reset_device() briefly reverts to address 0 */ + if (usb_pipedevice (urb->pipe) == 0) + qh->hw->hw_info1 &= ~qh_addr_mask; + } + + /* just one way to queue requests: swap with the dummy qtd. + * only hc or qh_refresh() ever modify the overlay. + */ + if (likely (qtd != NULL)) { + struct ehci_qtd *dummy; + dma_addr_t dma; + __hc32 token; + + /* to avoid racing the HC, use the dummy td instead of + * the first td of our list (becomes new dummy). both + * tds stay deactivated until we're done, when the + * HC is allowed to fetch the old dummy (4.10.2). + */ + token = qtd->hw_token; + qtd->hw_token = HALT_BIT(ehci); + + dummy = qh->dummy; + + dma = dummy->qtd_dma; + *dummy = *qtd; + dummy->qtd_dma = dma; + + list_del (&qtd->qtd_list); + list_add (&dummy->qtd_list, qtd_list); + list_splice_tail(qtd_list, &qh->qtd_list); + + ehci_qtd_init(ehci, qtd, qtd->qtd_dma); + qh->dummy = qtd; + + /* hc must see the new dummy at list end */ + dma = qtd->qtd_dma; + qtd = list_entry (qh->qtd_list.prev, + struct ehci_qtd, qtd_list); + qtd->hw_next = QTD_NEXT(ehci, dma); + + /* let the hc process these next qtds */ + wmb (); + dummy->hw_token = token; + + urb->hcpriv = qh; + } + } + return qh; +} + +/*-------------------------------------------------------------------------*/ + +static int +submit_async ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + gfp_t mem_flags +) { + int epnum; + unsigned long flags; + struct ehci_qh *qh = NULL; + int rc; + + epnum = urb->ep->desc.bEndpointAddress; + +#ifdef EHCI_URB_TRACE + { + struct ehci_qtd *qtd; + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + ehci_dbg(ehci, + "%s %s urb %p ep%d%s len %d, qtd %p [qh %p]\n", + __func__, urb->dev->devpath, urb, + epnum & 0x0f, (epnum & USB_DIR_IN) ? "in" : "out", + urb->transfer_buffer_length, + qtd, urb->ep->hcpriv); + } +#endif + + spin_lock_irqsave (&ehci->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(ehci_to_hcd(ehci)))) { + rc = -ESHUTDOWN; + goto done; + } + rc = usb_hcd_link_urb_to_ep(ehci_to_hcd(ehci), urb); + if (unlikely(rc)) + goto done; + + qh = qh_append_tds(ehci, urb, qtd_list, epnum, &urb->ep->hcpriv); + if (unlikely(qh == NULL)) { + usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); + rc = -ENOMEM; + goto done; + } + + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + if (likely (qh->qh_state == QH_STATE_IDLE)) + qh_link_async(ehci, qh); + done: + spin_unlock_irqrestore (&ehci->lock, flags); + if (unlikely (qh == NULL)) + qtd_list_free (ehci, urb, qtd_list); + return rc; +} + +/*-------------------------------------------------------------------------*/ + +static void single_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + struct ehci_qh *prev; + + /* Add to the end of the list of QHs waiting for the next IAAD */ + qh->qh_state = QH_STATE_UNLINK_WAIT; + list_add_tail(&qh->unlink_node, &ehci->async_unlink); + + /* Unlink it from the schedule */ + prev = ehci->async; + while (prev->qh_next.qh != qh) + prev = prev->qh_next.qh; + + prev->hw->hw_next = qh->hw->hw_next; + prev->qh_next = qh->qh_next; + if (ehci->qh_scan_next == qh) + ehci->qh_scan_next = qh->qh_next.qh; +} + +static void start_iaa_cycle(struct ehci_hcd *ehci) +{ + /* Do nothing if an IAA cycle is already running */ + if (ehci->iaa_in_progress) + return; + ehci->iaa_in_progress = true; + + /* If the controller isn't running, we don't have to wait for it */ + if (unlikely(ehci->rh_state < EHCI_RH_RUNNING)) { + end_unlink_async(ehci); + + /* Otherwise start a new IAA cycle */ + } else if (likely(ehci->rh_state == EHCI_RH_RUNNING)) { + + /* Make sure the unlinks are all visible to the hardware */ + wmb(); + + ehci_writel(ehci, ehci->command | CMD_IAAD, + &ehci->regs->command); + ehci_readl(ehci, &ehci->regs->command); + ehci_enable_event(ehci, EHCI_HRTIMER_IAA_WATCHDOG, true); + } +} + +/* the async qh for the qtds being unlinked are now gone from the HC */ + +static void end_unlink_async(struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + bool early_exit; + + if (ehci->has_synopsys_hc_bug) + ehci_writel(ehci, (u32) ehci->async->qh_dma, + &ehci->regs->async_next); + + /* The current IAA cycle has ended */ + ehci->iaa_in_progress = false; + + if (list_empty(&ehci->async_unlink)) + return; + qh = list_first_entry(&ehci->async_unlink, struct ehci_qh, + unlink_node); /* QH whose IAA cycle just ended */ + + /* + * If async_unlinking is set then this routine is already running, + * either on the stack or on another CPU. + */ + early_exit = ehci->async_unlinking; + + /* If the controller isn't running, process all the waiting QHs */ + if (ehci->rh_state < EHCI_RH_RUNNING) + list_splice_tail_init(&ehci->async_unlink, &ehci->async_idle); + + /* + * Intel (?) bug: The HC can write back the overlay region even + * after the IAA interrupt occurs. In self-defense, always go + * through two IAA cycles for each QH. + */ + else if (qh->qh_state == QH_STATE_UNLINK_WAIT) { + qh->qh_state = QH_STATE_UNLINK; + early_exit = true; + } + + /* Otherwise process only the first waiting QH (NVIDIA bug?) */ + else + list_move_tail(&qh->unlink_node, &ehci->async_idle); + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (!list_empty(&ehci->async_unlink)) + start_iaa_cycle(ehci); + + /* + * Don't allow nesting or concurrent calls, + * or wait for the second IAA cycle for the next QH. + */ + if (early_exit) + return; + + /* Process the idle QHs */ + ehci->async_unlinking = true; + while (!list_empty(&ehci->async_idle)) { + qh = list_first_entry(&ehci->async_idle, struct ehci_qh, + unlink_node); + list_del(&qh->unlink_node); + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + + if (!list_empty(&qh->qtd_list)) + qh_completions(ehci, qh); + if (!list_empty(&qh->qtd_list) && + ehci->rh_state == EHCI_RH_RUNNING) + qh_link_async(ehci, qh); + disable_async(ehci); + } + ehci->async_unlinking = false; +} + +static void start_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh); + +static void unlink_empty_async(struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + struct ehci_qh *qh_to_unlink = NULL; + int count = 0; + + /* Find the last async QH which has been empty for a timer cycle */ + for (qh = ehci->async->qh_next.qh; qh; qh = qh->qh_next.qh) { + if (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED) { + ++count; + if (qh->unlink_cycle != ehci->async_unlink_cycle) + qh_to_unlink = qh; + } + } + + /* If nothing else is being unlinked, unlink the last empty QH */ + if (list_empty(&ehci->async_unlink) && qh_to_unlink) { + start_unlink_async(ehci, qh_to_unlink); + --count; + } + + /* Other QHs will be handled later */ + if (count > 0) { + ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true); + ++ehci->async_unlink_cycle; + } +} + +/* The root hub is suspended; unlink all the async QHs */ +static void __maybe_unused unlink_empty_async_suspended(struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + + while (ehci->async->qh_next.qh) { + qh = ehci->async->qh_next.qh; + WARN_ON(!list_empty(&qh->qtd_list)); + single_unlink_async(ehci, qh); + } + start_iaa_cycle(ehci); +} + +/* makes sure the async qh will become idle */ +/* caller must own ehci->lock */ + +static void start_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + /* If the QH isn't linked then there's nothing we can do. */ + if (qh->qh_state != QH_STATE_LINKED) + return; + + single_unlink_async(ehci, qh); + start_iaa_cycle(ehci); +} + +/*-------------------------------------------------------------------------*/ + +static void scan_async (struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + bool check_unlinks_later = false; + + ehci->qh_scan_next = ehci->async->qh_next.qh; + while (ehci->qh_scan_next) { + qh = ehci->qh_scan_next; + ehci->qh_scan_next = qh->qh_next.qh; + + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list)) { + int temp; + + /* + * Unlinks could happen here; completion reporting + * drops the lock. That's why ehci->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then ehci->qh_scan_next is adjusted + * in single_unlink_async(). + */ + temp = qh_completions(ehci, qh); + if (unlikely(temp)) { + start_unlink_async(ehci, qh); + } else if (list_empty(&qh->qtd_list) + && qh->qh_state == QH_STATE_LINKED) { + qh->unlink_cycle = ehci->async_unlink_cycle; + check_unlinks_later = true; + } + } + } + + /* + * Unlink empty entries, reducing DMA usage as well + * as HCD schedule-scanning costs. Delay for any qh + * we just scanned, there's a not-unusual case that it + * doesn't stay idle for long. + */ + if (check_unlinks_later && ehci->rh_state == EHCI_RH_RUNNING && + !(ehci->enabled_hrtimer_events & + BIT(EHCI_HRTIMER_ASYNC_UNLINKS))) { + ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true); + ++ehci->async_unlink_cycle; + } +} diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c new file mode 100644 index 00000000..379037f5 --- /dev/null +++ b/drivers/usb/host/ehci-s5p.c @@ -0,0 +1,321 @@ +/* + * SAMSUNG S5P USB HOST EHCI Controller + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Author: Jingoo Han + * Author: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI s5p driver" + +#define EHCI_INSNREG00(base) (base + 0x90) +#define EHCI_INSNREG00_ENA_INCR16 (0x1 << 25) +#define EHCI_INSNREG00_ENA_INCR8 (0x1 << 24) +#define EHCI_INSNREG00_ENA_INCR4 (0x1 << 23) +#define EHCI_INSNREG00_ENA_INCRX_ALIGN (0x1 << 22) +#define EHCI_INSNREG00_ENABLE_DMA_BURST \ + (EHCI_INSNREG00_ENA_INCR16 | EHCI_INSNREG00_ENA_INCR8 | \ + EHCI_INSNREG00_ENA_INCR4 | EHCI_INSNREG00_ENA_INCRX_ALIGN) + +static const char hcd_name[] = "ehci-s5p"; +static struct hc_driver __read_mostly s5p_ehci_hc_driver; + +struct s5p_ehci_hcd { + struct clk *clk; + struct usb_phy *phy; + struct usb_otg *otg; + struct s5p_ehci_platdata *pdata; +}; + +#define to_s5p_ehci(hcd) (struct s5p_ehci_hcd *)(hcd_to_ehci(hcd)->priv) + +static void s5p_setup_vbus_gpio(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int err; + int gpio; + + if (!dev->of_node) + return; + + gpio = of_get_named_gpio(dev->of_node, "samsung,vbus-gpio", 0); + if (!gpio_is_valid(gpio)) + return; + + err = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_HIGH, + "ehci_vbus_gpio"); + if (err) + dev_err(dev, "can't request ehci vbus gpio %d", gpio); +} + +static int s5p_ehci_probe(struct platform_device *pdev) +{ + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct resource *res; + struct usb_phy *phy; + int irq; + int err; + + /* + * 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 move to full device tree support this will vanish off. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + s5p_setup_vbus_gpio(pdev); + + hcd = usb_create_hcd(&s5p_ehci_hc_driver, + &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + return -ENOMEM; + } + s5p_ehci = to_s5p_ehci(hcd); + phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + if (IS_ERR(phy)) { + /* Fallback to pdata */ + if (!pdata) { + usb_put_hcd(hcd); + dev_warn(&pdev->dev, "no platform data or transceiver defined\n"); + return -EPROBE_DEFER; + } else { + s5p_ehci->pdata = pdata; + } + } else { + s5p_ehci->phy = phy; + s5p_ehci->otg = phy->otg; + } + + s5p_ehci->clk = devm_clk_get(&pdev->dev, "usbhost"); + + if (IS_ERR(s5p_ehci->clk)) { + dev_err(&pdev->dev, "Failed to get usbhost clock\n"); + err = PTR_ERR(s5p_ehci->clk); + goto fail_clk; + } + + err = clk_prepare_enable(s5p_ehci->clk); + if (err) + goto fail_clk; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + err = -ENXIO; + goto fail_io; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = devm_ioremap(&pdev->dev, res->start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + err = -ENOMEM; + goto fail_io; + } + + irq = platform_get_irq(pdev, 0); + if (!irq) { + dev_err(&pdev->dev, "Failed to get IRQ\n"); + err = -ENODEV; + goto fail_io; + } + + if (s5p_ehci->otg) + s5p_ehci->otg->set_host(s5p_ehci->otg, &hcd->self); + + if (s5p_ehci->phy) + usb_phy_init(s5p_ehci->phy); + else if (s5p_ehci->pdata->phy_init) + s5p_ehci->pdata->phy_init(pdev, USB_PHY_TYPE_HOST); + + ehci = hcd_to_ehci(hcd); + ehci->caps = hcd->regs; + + /* DMA burst Enable */ + writel(EHCI_INSNREG00_ENABLE_DMA_BURST, EHCI_INSNREG00(hcd->regs)); + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) { + dev_err(&pdev->dev, "Failed to add USB HCD\n"); + goto fail_add_hcd; + } + + platform_set_drvdata(pdev, hcd); + + return 0; + +fail_add_hcd: + if (s5p_ehci->phy) + usb_phy_shutdown(s5p_ehci->phy); + else if (s5p_ehci->pdata->phy_exit) + s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST); +fail_io: + clk_disable_unprepare(s5p_ehci->clk); +fail_clk: + usb_put_hcd(hcd); + return err; +} + +static int s5p_ehci_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct s5p_ehci_hcd *s5p_ehci = to_s5p_ehci(hcd); + + usb_remove_hcd(hcd); + + if (s5p_ehci->otg) + s5p_ehci->otg->set_host(s5p_ehci->otg, &hcd->self); + + if (s5p_ehci->phy) + usb_phy_shutdown(s5p_ehci->phy); + else if (s5p_ehci->pdata->phy_exit) + s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST); + + clk_disable_unprepare(s5p_ehci->clk); + + usb_put_hcd(hcd); + + return 0; +} + +static void s5p_ehci_shutdown(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +#ifdef CONFIG_PM +static int s5p_ehci_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct s5p_ehci_hcd *s5p_ehci = to_s5p_ehci(hcd); + struct platform_device *pdev = to_platform_device(dev); + + bool do_wakeup = device_may_wakeup(dev); + int rc; + + rc = ehci_suspend(hcd, do_wakeup); + + if (s5p_ehci->otg) + s5p_ehci->otg->set_host(s5p_ehci->otg, &hcd->self); + + if (s5p_ehci->phy) + usb_phy_shutdown(s5p_ehci->phy); + else if (s5p_ehci->pdata->phy_exit) + s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST); + + clk_disable_unprepare(s5p_ehci->clk); + + return rc; +} + +static int s5p_ehci_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct s5p_ehci_hcd *s5p_ehci = to_s5p_ehci(hcd); + struct platform_device *pdev = to_platform_device(dev); + + clk_prepare_enable(s5p_ehci->clk); + + if (s5p_ehci->otg) + s5p_ehci->otg->set_host(s5p_ehci->otg, &hcd->self); + + if (s5p_ehci->phy) + usb_phy_init(s5p_ehci->phy); + else if (s5p_ehci->pdata->phy_init) + s5p_ehci->pdata->phy_init(pdev, USB_PHY_TYPE_HOST); + + /* DMA burst Enable */ + writel(EHCI_INSNREG00_ENABLE_DMA_BURST, EHCI_INSNREG00(hcd->regs)); + + ehci_resume(hcd, false); + return 0; +} +#else +#define s5p_ehci_suspend NULL +#define s5p_ehci_resume NULL +#endif + +static const struct dev_pm_ops s5p_ehci_pm_ops = { + .suspend = s5p_ehci_suspend, + .resume = s5p_ehci_resume, +}; + +#ifdef CONFIG_OF +static const struct of_device_id exynos_ehci_match[] = { + { .compatible = "samsung,exynos4210-ehci" }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_ehci_match); +#endif + +static struct platform_driver s5p_ehci_driver = { + .probe = s5p_ehci_probe, + .remove = s5p_ehci_remove, + .shutdown = s5p_ehci_shutdown, + .driver = { + .name = "s5p-ehci", + .owner = THIS_MODULE, + .pm = &s5p_ehci_pm_ops, + .of_match_table = of_match_ptr(exynos_ehci_match), + } +}; +static const struct ehci_driver_overrides s5p_overrides __initdata = { + .extra_priv_size = sizeof(struct s5p_ehci_hcd), +}; + +static int __init ehci_s5p_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + ehci_init_driver(&s5p_ehci_hc_driver, &s5p_overrides); + return platform_driver_register(&s5p_ehci_driver); +} +module_init(ehci_s5p_init); + +static void __exit ehci_s5p_cleanup(void) +{ + platform_driver_unregister(&s5p_ehci_driver); +} +module_exit(ehci_s5p_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:s5p-ehci"); +MODULE_AUTHOR("Jingoo Han"); +MODULE_AUTHOR("Joonyoung Shim"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c new file mode 100644 index 00000000..8e3c878f --- /dev/null +++ b/drivers/usb/host/ehci-sched.c @@ -0,0 +1,2327 @@ +/* + * Copyright (c) 2001-2004 by David Brownell + * Copyright (c) 2003 Michal Sojka, for high-speed iso transfers + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI scheduled transaction support: interrupt, iso, split iso + * These are called "periodic" transactions in the EHCI spec. + * + * Note that for interrupt transfers, the QH/QTD manipulation is shared + * with the "asynchronous" transaction support (control/bulk transfers). + * The only real difference is in how interrupt transfers are scheduled. + * + * For ISO, we make an "iso_stream" head to serve the same role as a QH. + * It keeps track of every ITD (or SITD) that's linked, and holds enough + * pre-calculated schedule data to make appending to the queue be quick. + */ + +static int ehci_get_frame (struct usb_hcd *hcd); + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd/sitd + * @tag: hardware tag for type of this record + */ +static union ehci_shadow * +periodic_next_shadow(struct ehci_hcd *ehci, union ehci_shadow *periodic, + __hc32 tag) +{ + switch (hc32_to_cpu(ehci, tag)) { + case Q_TYPE_QH: + return &periodic->qh->qh_next; + case Q_TYPE_FSTN: + return &periodic->fstn->fstn_next; + case Q_TYPE_ITD: + return &periodic->itd->itd_next; + // case Q_TYPE_SITD: + default: + return &periodic->sitd->sitd_next; + } +} + +static __hc32 * +shadow_next_periodic(struct ehci_hcd *ehci, union ehci_shadow *periodic, + __hc32 tag) +{ + switch (hc32_to_cpu(ehci, tag)) { + /* our ehci_shadow.qh is actually software part */ + case Q_TYPE_QH: + return &periodic->qh->hw->hw_next; + /* others are hw parts */ + default: + return periodic->hw_next; + } +} + +/* caller must hold ehci->lock */ +static void periodic_unlink (struct ehci_hcd *ehci, unsigned frame, void *ptr) +{ + union ehci_shadow *prev_p = &ehci->pshadow[frame]; + __hc32 *hw_p = &ehci->periodic[frame]; + union ehci_shadow here = *prev_p; + + /* find predecessor of "ptr"; hw and shadow lists are in sync */ + while (here.ptr && here.ptr != ptr) { + prev_p = periodic_next_shadow(ehci, prev_p, + Q_NEXT_TYPE(ehci, *hw_p)); + hw_p = shadow_next_periodic(ehci, &here, + Q_NEXT_TYPE(ehci, *hw_p)); + here = *prev_p; + } + /* an interrupt entry (at list end) could have been shared */ + if (!here.ptr) + return; + + /* update shadow and hardware lists ... the old "next" pointers + * from ptr may still be in use, the caller updates them. + */ + *prev_p = *periodic_next_shadow(ehci, &here, + Q_NEXT_TYPE(ehci, *hw_p)); + + if (!ehci->use_dummy_qh || + *shadow_next_periodic(ehci, &here, Q_NEXT_TYPE(ehci, *hw_p)) + != EHCI_LIST_END(ehci)) + *hw_p = *shadow_next_periodic(ehci, &here, + Q_NEXT_TYPE(ehci, *hw_p)); + else + *hw_p = ehci->dummy->qh_dma; +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short +periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) +{ + __hc32 *hw_p = &ehci->periodic [frame]; + union ehci_shadow *q = &ehci->pshadow [frame]; + unsigned usecs = 0; + struct ehci_qh_hw *hw; + + while (q->ptr) { + switch (hc32_to_cpu(ehci, Q_NEXT_TYPE(ehci, *hw_p))) { + case Q_TYPE_QH: + hw = q->qh->hw; + /* is it in the S-mask? */ + if (hw->hw_info2 & cpu_to_hc32(ehci, 1 << uframe)) + usecs += q->qh->usecs; + /* ... or C-mask? */ + if (hw->hw_info2 & cpu_to_hc32(ehci, + 1 << (8 + uframe))) + usecs += q->qh->c_usecs; + hw_p = &hw->hw_next; + q = &q->qh->qh_next; + break; + // case Q_TYPE_FSTN: + default: + /* for "save place" FSTNs, count the relevant INTR + * bandwidth from the previous frame + */ + if (q->fstn->hw_prev != EHCI_LIST_END(ehci)) { + ehci_dbg (ehci, "ignoring FSTN cost ...\n"); + } + hw_p = &q->fstn->hw_next; + q = &q->fstn->fstn_next; + break; + case Q_TYPE_ITD: + if (q->itd->hw_transaction[uframe]) + usecs += q->itd->stream->usecs; + hw_p = &q->itd->hw_next; + q = &q->itd->itd_next; + break; + case Q_TYPE_SITD: + /* is it in the S-mask? (count SPLIT, DATA) */ + if (q->sitd->hw_uframe & cpu_to_hc32(ehci, + 1 << uframe)) { + if (q->sitd->hw_fullspeed_ep & + cpu_to_hc32(ehci, 1<<31)) + usecs += q->sitd->stream->usecs; + else /* worst case for OUT start-split */ + usecs += HS_USECS_ISO (188); + } + + /* ... C-mask? (count CSPLIT, DATA) */ + if (q->sitd->hw_uframe & + cpu_to_hc32(ehci, 1 << (8 + uframe))) { + /* worst case for IN complete-split */ + usecs += q->sitd->stream->c_usecs; + } + + hw_p = &q->sitd->hw_next; + q = &q->sitd->sitd_next; + break; + } + } +#ifdef DEBUG + if (usecs > ehci->uframe_periodic_max) + ehci_err (ehci, "uframe %d sched overrun: %d usecs\n", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +/*-------------------------------------------------------------------------*/ + +static int same_tt (struct usb_device *dev1, struct usb_device *dev2) +{ + if (!dev1->tt || !dev2->tt) + return 0; + if (dev1->tt != dev2->tt) + return 0; + if (dev1->tt->multi) + return dev1->ttport == dev2->ttport; + else + return 1; +} + +#ifdef CONFIG_USB_EHCI_TT_NEWSCHED + +/* Which uframe does the low/fullspeed transfer start in? + * + * The parameter is the mask of ssplits in "H-frame" terms + * and this returns the transfer start uframe in "B-frame" terms, + * which allows both to match, e.g. a ssplit in "H-frame" uframe 0 + * will cause a transfer in "B-frame" uframe 0. "B-frames" lag + * "H-frames" by 1 uframe. See the EHCI spec sec 4.5 and figure 4.7. + */ +static inline unsigned char tt_start_uframe(struct ehci_hcd *ehci, __hc32 mask) +{ + unsigned char smask = QH_SMASK & hc32_to_cpu(ehci, mask); + if (!smask) { + ehci_err(ehci, "invalid empty smask!\n"); + /* uframe 7 can't have bw so this will indicate failure */ + return 7; + } + return ffs(smask) - 1; +} + +static const unsigned char +max_tt_usecs[] = { 125, 125, 125, 125, 125, 125, 30, 0 }; + +/* carryover low/fullspeed bandwidth that crosses uframe boundries */ +static inline void carryover_tt_bandwidth(unsigned short tt_usecs[8]) +{ + int i; + for (i=0; i<7; i++) { + if (max_tt_usecs[i] < tt_usecs[i]) { + tt_usecs[i+1] += tt_usecs[i] - max_tt_usecs[i]; + tt_usecs[i] = max_tt_usecs[i]; + } + } +} + +/* How many of the tt's periodic downstream 1000 usecs are allocated? + * + * While this measures the bandwidth in terms of usecs/uframe, + * the low/fullspeed bus has no notion of uframes, so any particular + * low/fullspeed transfer can "carry over" from one uframe to the next, + * since the TT just performs downstream transfers in sequence. + * + * For example two separate 100 usec transfers can start in the same uframe, + * and the second one would "carry over" 75 usecs into the next uframe. + */ +static void +periodic_tt_usecs ( + struct ehci_hcd *ehci, + struct usb_device *dev, + unsigned frame, + unsigned short tt_usecs[8] +) +{ + __hc32 *hw_p = &ehci->periodic [frame]; + union ehci_shadow *q = &ehci->pshadow [frame]; + unsigned char uf; + + memset(tt_usecs, 0, 16); + + while (q->ptr) { + switch (hc32_to_cpu(ehci, Q_NEXT_TYPE(ehci, *hw_p))) { + case Q_TYPE_ITD: + hw_p = &q->itd->hw_next; + q = &q->itd->itd_next; + continue; + case Q_TYPE_QH: + if (same_tt(dev, q->qh->dev)) { + uf = tt_start_uframe(ehci, q->qh->hw->hw_info2); + tt_usecs[uf] += q->qh->tt_usecs; + } + hw_p = &q->qh->hw->hw_next; + q = &q->qh->qh_next; + continue; + case Q_TYPE_SITD: + if (same_tt(dev, q->sitd->urb->dev)) { + uf = tt_start_uframe(ehci, q->sitd->hw_uframe); + tt_usecs[uf] += q->sitd->stream->tt_usecs; + } + hw_p = &q->sitd->hw_next; + q = &q->sitd->sitd_next; + continue; + // case Q_TYPE_FSTN: + default: + ehci_dbg(ehci, "ignoring periodic frame %d FSTN\n", + frame); + hw_p = &q->fstn->hw_next; + q = &q->fstn->fstn_next; + } + } + + carryover_tt_bandwidth(tt_usecs); + + if (max_tt_usecs[7] < tt_usecs[7]) + ehci_err(ehci, "frame %d tt sched overrun: %d usecs\n", + frame, tt_usecs[7] - max_tt_usecs[7]); +} + +/* + * Return true if the device's tt's downstream bus is available for a + * periodic transfer of the specified length (usecs), starting at the + * specified frame/uframe. Note that (as summarized in section 11.19 + * of the usb 2.0 spec) TTs can buffer multiple transactions for each + * uframe. + * + * The uframe parameter is when the fullspeed/lowspeed transfer + * should be executed in "B-frame" terms, which is the same as the + * highspeed ssplit's uframe (which is in "H-frame" terms). For example + * a ssplit in "H-frame" 0 causes a transfer in "B-frame" 0. + * See the EHCI spec sec 4.5 and fig 4.7. + * + * This checks if the full/lowspeed bus, at the specified starting uframe, + * has the specified bandwidth available, according to rules listed + * in USB 2.0 spec section 11.18.1 fig 11-60. + * + * This does not check if the transfer would exceed the max ssplit + * limit of 16, specified in USB 2.0 spec section 11.18.4 requirement #4, + * since proper scheduling limits ssplits to less than 16 per uframe. + */ +static int tt_available ( + struct ehci_hcd *ehci, + unsigned period, + struct usb_device *dev, + unsigned frame, + unsigned uframe, + u16 usecs +) +{ + if ((period == 0) || (uframe >= 7)) /* error */ + return 0; + + for (; frame < ehci->periodic_size; frame += period) { + unsigned short tt_usecs[8]; + + periodic_tt_usecs (ehci, dev, frame, tt_usecs); + + ehci_vdbg(ehci, "tt frame %d check %d usecs start uframe %d in" + " schedule %d/%d/%d/%d/%d/%d/%d/%d\n", + frame, usecs, uframe, + tt_usecs[0], tt_usecs[1], tt_usecs[2], tt_usecs[3], + tt_usecs[4], tt_usecs[5], tt_usecs[6], tt_usecs[7]); + + if (max_tt_usecs[uframe] <= tt_usecs[uframe]) { + ehci_vdbg(ehci, "frame %d uframe %d fully scheduled\n", + frame, uframe); + return 0; + } + + /* special case for isoc transfers larger than 125us: + * the first and each subsequent fully used uframe + * must be empty, so as to not illegally delay + * already scheduled transactions + */ + if (125 < usecs) { + int ufs = (usecs / 125); + int i; + for (i = uframe; i < (uframe + ufs) && i < 8; i++) + if (0 < tt_usecs[i]) { + ehci_vdbg(ehci, + "multi-uframe xfer can't fit " + "in frame %d uframe %d\n", + frame, i); + return 0; + } + } + + tt_usecs[uframe] += usecs; + + carryover_tt_bandwidth(tt_usecs); + + /* fail if the carryover pushed bw past the last uframe's limit */ + if (max_tt_usecs[7] < tt_usecs[7]) { + ehci_vdbg(ehci, + "tt unavailable usecs %d frame %d uframe %d\n", + usecs, frame, uframe); + return 0; + } + } + + return 1; +} + +#else + +/* return true iff the device's transaction translator is available + * for a periodic transfer starting at the specified frame, using + * all the uframes in the mask. + */ +static int tt_no_collision ( + struct ehci_hcd *ehci, + unsigned period, + struct usb_device *dev, + unsigned frame, + u32 uf_mask +) +{ + if (period == 0) /* error */ + return 0; + + /* note bandwidth wastage: split never follows csplit + * (different dev or endpoint) until the next uframe. + * calling convention doesn't make that distinction. + */ + for (; frame < ehci->periodic_size; frame += period) { + union ehci_shadow here; + __hc32 type; + struct ehci_qh_hw *hw; + + here = ehci->pshadow [frame]; + type = Q_NEXT_TYPE(ehci, ehci->periodic [frame]); + while (here.ptr) { + switch (hc32_to_cpu(ehci, type)) { + case Q_TYPE_ITD: + type = Q_NEXT_TYPE(ehci, here.itd->hw_next); + here = here.itd->itd_next; + continue; + case Q_TYPE_QH: + hw = here.qh->hw; + if (same_tt (dev, here.qh->dev)) { + u32 mask; + + mask = hc32_to_cpu(ehci, + hw->hw_info2); + /* "knows" no gap is needed */ + mask |= mask >> 8; + if (mask & uf_mask) + break; + } + type = Q_NEXT_TYPE(ehci, hw->hw_next); + here = here.qh->qh_next; + continue; + case Q_TYPE_SITD: + if (same_tt (dev, here.sitd->urb->dev)) { + u16 mask; + + mask = hc32_to_cpu(ehci, here.sitd + ->hw_uframe); + /* FIXME assumes no gap for IN! */ + mask |= mask >> 8; + if (mask & uf_mask) + break; + } + type = Q_NEXT_TYPE(ehci, here.sitd->hw_next); + here = here.sitd->sitd_next; + continue; + // case Q_TYPE_FSTN: + default: + ehci_dbg (ehci, + "periodic frame %d bogus type %d\n", + frame, type); + } + + /* collision or error */ + return 0; + } + } + + /* no collision */ + return 1; +} + +#endif /* CONFIG_USB_EHCI_TT_NEWSCHED */ + +/*-------------------------------------------------------------------------*/ + +static void enable_periodic(struct ehci_hcd *ehci) +{ + if (ehci->periodic_count++) + return; + + /* Stop waiting to turn off the periodic schedule */ + ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_DISABLE_PERIODIC); + + /* Don't start the schedule until PSS is 0 */ + ehci_poll_PSS(ehci); + turn_on_io_watchdog(ehci); +} + +static void disable_periodic(struct ehci_hcd *ehci) +{ + if (--ehci->periodic_count) + return; + + /* Don't turn off the schedule until PSS is 1 */ + ehci_poll_PSS(ehci); +} + +/*-------------------------------------------------------------------------*/ + +/* periodic schedule slots have iso tds (normal or split) first, then a + * sparse tree for active interrupt transfers. + * + * this just links in a qh; caller guarantees uframe masks are set right. + * no FSTN support (yet; ehci 0.96+) + */ +static void qh_link_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + unsigned i; + unsigned period = qh->period; + + dev_dbg (&qh->dev->dev, + "link qh%d-%04x/%p start %d [%d/%d us]\n", + period, hc32_to_cpup(ehci, &qh->hw->hw_info2) + & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* high bandwidth, or otherwise every microframe */ + if (period == 0) + period = 1; + + for (i = qh->start; i < ehci->periodic_size; i += period) { + union ehci_shadow *prev = &ehci->pshadow[i]; + __hc32 *hw_p = &ehci->periodic[i]; + union ehci_shadow here = *prev; + __hc32 type = 0; + + /* skip the iso nodes at list head */ + while (here.ptr) { + type = Q_NEXT_TYPE(ehci, *hw_p); + if (type == cpu_to_hc32(ehci, Q_TYPE_QH)) + break; + prev = periodic_next_shadow(ehci, prev, type); + hw_p = shadow_next_periodic(ehci, &here, type); + here = *prev; + } + + /* sorting each branch by period (slow-->fast) + * enables sharing interior tree nodes + */ + while (here.ptr && qh != here.qh) { + if (qh->period > here.qh->period) + break; + prev = &here.qh->qh_next; + hw_p = &here.qh->hw->hw_next; + here = *prev; + } + /* link in this qh, unless some earlier pass did that */ + if (qh != here.qh) { + qh->qh_next = here; + if (here.qh) + qh->hw->hw_next = *hw_p; + wmb (); + prev->qh = qh; + *hw_p = QH_NEXT (ehci, qh->qh_dma); + } + } + qh->qh_state = QH_STATE_LINKED; + qh->xacterrs = 0; + qh->exception = 0; + + /* update per-qh bandwidth for usbfs */ + ehci_to_hcd(ehci)->self.bandwidth_allocated += qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + list_add(&qh->intr_node, &ehci->intr_qh_list); + + /* maybe enable periodic schedule processing */ + ++ehci->intr_count; + enable_periodic(ehci); +} + +static void qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + unsigned i; + unsigned period; + + /* + * If qh is for a low/full-speed device, simply unlinking it + * could interfere with an ongoing split transaction. To unlink + * it safely would require setting the QH_INACTIVATE bit and + * waiting at least one frame, as described in EHCI 4.12.2.5. + * + * We won't bother with any of this. Instead, we assume that the + * only reason for unlinking an interrupt QH while the current URB + * is still active is to dequeue all the URBs (flush the whole + * endpoint queue). + * + * If rebalancing the periodic schedule is ever implemented, this + * approach will no longer be valid. + */ + + /* high bandwidth, or otherwise part of every microframe */ + if ((period = qh->period) == 0) + period = 1; + + for (i = qh->start; i < ehci->periodic_size; i += period) + periodic_unlink (ehci, i, qh); + + /* update per-qh bandwidth for usbfs */ + ehci_to_hcd(ehci)->self.bandwidth_allocated -= qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + dev_dbg (&qh->dev->dev, + "unlink qh%d-%04x/%p start %d [%d/%d us]\n", + qh->period, + hc32_to_cpup(ehci, &qh->hw->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* qh->qh_next still "live" to HC */ + qh->qh_state = QH_STATE_UNLINK; + qh->qh_next.ptr = NULL; + + if (ehci->qh_scan_next == qh) + ehci->qh_scan_next = list_entry(qh->intr_node.next, + struct ehci_qh, intr_node); + list_del(&qh->intr_node); +} + +static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + /* If the QH isn't linked then there's nothing we can do. */ + if (qh->qh_state != QH_STATE_LINKED) + return; + + qh_unlink_periodic (ehci, qh); + + /* Make sure the unlinks are visible before starting the timer */ + wmb(); + + /* + * The EHCI spec doesn't say how long it takes the controller to + * stop accessing an unlinked interrupt QH. The timer delay is + * 9 uframes; presumably that will be long enough. + */ + qh->unlink_cycle = ehci->intr_unlink_cycle; + + /* New entries go at the end of the intr_unlink list */ + list_add_tail(&qh->unlink_node, &ehci->intr_unlink); + + if (ehci->intr_unlinking) + ; /* Avoid recursive calls */ + else if (ehci->rh_state < EHCI_RH_RUNNING) + ehci_handle_intr_unlinks(ehci); + else if (ehci->intr_unlink.next == &qh->unlink_node) { + ehci_enable_event(ehci, EHCI_HRTIMER_UNLINK_INTR, true); + ++ehci->intr_unlink_cycle; + } +} + +static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + struct ehci_qh_hw *hw = qh->hw; + int rc; + + qh->qh_state = QH_STATE_IDLE; + hw->hw_next = EHCI_LIST_END(ehci); + + if (!list_empty(&qh->qtd_list)) + qh_completions(ehci, qh); + + /* reschedule QH iff another request is queued */ + if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) { + rc = qh_schedule(ehci, qh); + if (rc == 0) { + qh_refresh(ehci, qh); + qh_link_periodic(ehci, qh); + } + + /* An error here likely indicates handshake failure + * or no space left in the schedule. Neither fault + * should happen often ... + * + * FIXME kill the now-dysfunctional queued urbs + */ + else { + ehci_err(ehci, "can't reschedule qh %p, err %d\n", + qh, rc); + } + } + + /* maybe turn off periodic schedule */ + --ehci->intr_count; + disable_periodic(ehci); +} + +/*-------------------------------------------------------------------------*/ + +static int check_period ( + struct ehci_hcd *ehci, + unsigned frame, + unsigned uframe, + unsigned period, + unsigned usecs +) { + int claimed; + + /* complete split running into next frame? + * given FSTN support, we could sometimes check... + */ + if (uframe >= 8) + return 0; + + /* convert "usecs we need" to "max already claimed" */ + usecs = ehci->uframe_periodic_max - usecs; + + /* we "know" 2 and 4 uframe intervals were rejected; so + * for period 0, check _every_ microframe in the schedule. + */ + if (unlikely (period == 0)) { + do { + for (uframe = 0; uframe < 7; uframe++) { + claimed = periodic_usecs (ehci, frame, uframe); + if (claimed > usecs) + return 0; + } + } while ((frame += 1) < ehci->periodic_size); + + /* just check the specified uframe, at that period */ + } else { + do { + claimed = periodic_usecs (ehci, frame, uframe); + if (claimed > usecs) + return 0; + } while ((frame += period) < ehci->periodic_size); + } + + // success! + return 1; +} + +static int check_intr_schedule ( + struct ehci_hcd *ehci, + unsigned frame, + unsigned uframe, + const struct ehci_qh *qh, + __hc32 *c_maskp +) +{ + int retval = -ENOSPC; + u8 mask = 0; + + if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period (ehci, frame, uframe, qh->period, qh->usecs)) + goto done; + if (!qh->c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + +#ifdef CONFIG_USB_EHCI_TT_NEWSCHED + if (tt_available (ehci, qh->period, qh->dev, frame, uframe, + qh->tt_usecs)) { + unsigned i; + + /* TODO : this may need FSTN for SSPLIT in uframe 5. */ + for (i=uframe+1; i<8 && iperiod, qh->c_usecs)) + goto done; + else + mask |= 1 << i; + + retval = 0; + + *c_maskp = cpu_to_hc32(ehci, mask << 8); + } +#else + /* Make sure this tt's buffer is also available for CSPLITs. + * We pessimize a bit; probably the typical full speed case + * doesn't need the second CSPLIT. + * + * NOTE: both SPLIT and CSPLIT could be checked in just + * one smart pass... + */ + mask = 0x03 << (uframe + qh->gap_uf); + *c_maskp = cpu_to_hc32(ehci, mask << 8); + + mask |= 1 << uframe; + if (tt_no_collision (ehci, qh->period, qh->dev, frame, mask)) { + if (!check_period (ehci, frame, uframe + qh->gap_uf + 1, + qh->period, qh->c_usecs)) + goto done; + if (!check_period (ehci, frame, uframe + qh->gap_uf, + qh->period, qh->c_usecs)) + goto done; + retval = 0; + } +#endif +done: + return retval; +} + +/* "first fit" scheduling policy used the first time through, + * or when the previous schedule slot can't be re-used. + */ +static int qh_schedule(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + int status; + unsigned uframe; + __hc32 c_mask; + unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ + struct ehci_qh_hw *hw = qh->hw; + + hw->hw_next = EHCI_LIST_END(ehci); + frame = qh->start; + + /* reuse the previous schedule slots, if we can */ + if (frame < qh->period) { + uframe = ffs(hc32_to_cpup(ehci, &hw->hw_info2) & QH_SMASK); + status = check_intr_schedule (ehci, frame, --uframe, + qh, &c_mask); + } else { + uframe = 0; + c_mask = 0; + status = -ENOSPC; + } + + /* else scan the schedule to find a group of slots such that all + * uframes have enough periodic bandwidth available. + */ + if (status) { + /* "normal" case, uframing flexible except with splits */ + if (qh->period) { + int i; + + for (i = qh->period; status && i > 0; --i) { + frame = ++ehci->random_frame % qh->period; + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule (ehci, + frame, uframe, qh, + &c_mask); + if (status == 0) + break; + } + } + + /* qh->period == 0 means every uframe */ + } else { + frame = 0; + status = check_intr_schedule (ehci, 0, 0, qh, &c_mask); + } + if (status) + goto done; + qh->start = frame; + + /* reset S-frame and (maybe) C-frame masks */ + hw->hw_info2 &= cpu_to_hc32(ehci, ~(QH_CMASK | QH_SMASK)); + hw->hw_info2 |= qh->period + ? cpu_to_hc32(ehci, 1 << uframe) + : cpu_to_hc32(ehci, QH_SMASK); + hw->hw_info2 |= c_mask; + } else + ehci_dbg (ehci, "reused qh %p schedule\n", qh); + +done: + return status; +} + +static int intr_submit ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + gfp_t mem_flags +) { + unsigned epnum; + unsigned long flags; + struct ehci_qh *qh; + int status; + struct list_head empty; + + /* get endpoint and transfer/schedule data */ + epnum = urb->ep->desc.bEndpointAddress; + + spin_lock_irqsave (&ehci->lock, flags); + + if (unlikely(!HCD_HW_ACCESSIBLE(ehci_to_hcd(ehci)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(ehci_to_hcd(ehci), urb); + if (unlikely(status)) + goto done_not_linked; + + /* get qh and force any scheduling errors */ + INIT_LIST_HEAD (&empty); + qh = qh_append_tds(ehci, urb, &empty, epnum, &urb->ep->hcpriv); + if (qh == NULL) { + status = -ENOMEM; + goto done; + } + if (qh->qh_state == QH_STATE_IDLE) { + if ((status = qh_schedule (ehci, qh)) != 0) + goto done; + } + + /* then queue the urb's tds to the qh */ + qh = qh_append_tds(ehci, urb, qtd_list, epnum, &urb->ep->hcpriv); + BUG_ON (qh == NULL); + + /* stuff into the periodic schedule */ + if (qh->qh_state == QH_STATE_IDLE) { + qh_refresh(ehci, qh); + qh_link_periodic(ehci, qh); + } + + /* ... update usbfs periodic stats */ + ehci_to_hcd(ehci)->self.bandwidth_int_reqs++; + +done: + if (unlikely(status)) + usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); +done_not_linked: + spin_unlock_irqrestore (&ehci->lock, flags); + if (status) + qtd_list_free (ehci, urb, qtd_list); + + return status; +} + +static void scan_intr(struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + + list_for_each_entry_safe(qh, ehci->qh_scan_next, &ehci->intr_qh_list, + intr_node) { + + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list)) { + int temp; + + /* + * Unlinks could happen here; completion reporting + * drops the lock. That's why ehci->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then ehci->qh_scan_next is adjusted + * in qh_unlink_periodic(). + */ + temp = qh_completions(ehci, qh); + if (unlikely(temp || (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED))) + start_unlink_intr(ehci, qh); + } + } +} + +/*-------------------------------------------------------------------------*/ + +/* ehci_iso_stream ops work with both ITD and SITD */ + +static struct ehci_iso_stream * +iso_stream_alloc (gfp_t mem_flags) +{ + struct ehci_iso_stream *stream; + + stream = kzalloc(sizeof *stream, mem_flags); + if (likely (stream != NULL)) { + INIT_LIST_HEAD(&stream->td_list); + INIT_LIST_HEAD(&stream->free_list); + stream->next_uframe = -1; + } + return stream; +} + +static void +iso_stream_init ( + struct ehci_hcd *ehci, + struct ehci_iso_stream *stream, + struct usb_device *dev, + int pipe, + unsigned interval +) +{ + static const u8 smask_out [] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f }; + + u32 buf1; + unsigned epnum, maxp; + int is_input; + long bandwidth; + + /* + * this might be a "high bandwidth" highspeed endpoint, + * as encoded in the ep descriptor's wMaxPacket field + */ + epnum = usb_pipeendpoint (pipe); + is_input = usb_pipein (pipe) ? USB_DIR_IN : 0; + maxp = usb_maxpacket(dev, pipe, !is_input); + if (is_input) { + buf1 = (1 << 11); + } else { + buf1 = 0; + } + + /* knows about ITD vs SITD */ + if (dev->speed == USB_SPEED_HIGH) { + unsigned multi = hb_mult(maxp); + + stream->highspeed = 1; + + maxp = max_packet(maxp); + buf1 |= maxp; + maxp *= multi; + + stream->buf0 = cpu_to_hc32(ehci, (epnum << 8) | dev->devnum); + stream->buf1 = cpu_to_hc32(ehci, buf1); + stream->buf2 = cpu_to_hc32(ehci, multi); + + /* usbfs wants to report the average usecs per frame tied up + * when transfers on this endpoint are scheduled ... + */ + stream->usecs = HS_USECS_ISO (maxp); + bandwidth = stream->usecs * 8; + bandwidth /= interval; + + } else { + u32 addr; + int think_time; + int hs_transfers; + + addr = dev->ttport << 24; + if (!ehci_is_TDI(ehci) + || (dev->tt->hub != + ehci_to_hcd(ehci)->self.root_hub)) + addr |= dev->tt->hub->devnum << 16; + addr |= epnum << 8; + addr |= dev->devnum; + stream->usecs = HS_USECS_ISO (maxp); + think_time = dev->tt ? dev->tt->think_time : 0; + stream->tt_usecs = NS_TO_US (think_time + usb_calc_bus_time ( + dev->speed, is_input, 1, maxp)); + hs_transfers = max (1u, (maxp + 187) / 188); + if (is_input) { + u32 tmp; + + addr |= 1 << 31; + stream->c_usecs = stream->usecs; + stream->usecs = HS_USECS_ISO (1); + stream->raw_mask = 1; + + /* c-mask as specified in USB 2.0 11.18.4 3.c */ + tmp = (1 << (hs_transfers + 2)) - 1; + stream->raw_mask |= tmp << (8 + 2); + } else + stream->raw_mask = smask_out [hs_transfers - 1]; + bandwidth = stream->usecs + stream->c_usecs; + bandwidth /= interval << 3; + + /* stream->splits gets created from raw_mask later */ + stream->address = cpu_to_hc32(ehci, addr); + } + stream->bandwidth = bandwidth; + + stream->udev = dev; + + stream->bEndpointAddress = is_input | epnum; + stream->interval = interval; + stream->maxp = maxp; +} + +static struct ehci_iso_stream * +iso_stream_find (struct ehci_hcd *ehci, struct urb *urb) +{ + unsigned epnum; + struct ehci_iso_stream *stream; + struct usb_host_endpoint *ep; + unsigned long flags; + + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein(urb->pipe)) + ep = urb->dev->ep_in[epnum]; + else + ep = urb->dev->ep_out[epnum]; + + spin_lock_irqsave (&ehci->lock, flags); + stream = ep->hcpriv; + + if (unlikely (stream == NULL)) { + stream = iso_stream_alloc(GFP_ATOMIC); + if (likely (stream != NULL)) { + ep->hcpriv = stream; + stream->ep = ep; + iso_stream_init(ehci, stream, urb->dev, urb->pipe, + urb->interval); + } + + /* if dev->ep [epnum] is a QH, hw is set */ + } else if (unlikely (stream->hw != NULL)) { + ehci_dbg (ehci, "dev %s ep%d%s, not iso??\n", + urb->dev->devpath, epnum, + usb_pipein(urb->pipe) ? "in" : "out"); + stream = NULL; + } + + spin_unlock_irqrestore (&ehci->lock, flags); + return stream; +} + +/*-------------------------------------------------------------------------*/ + +/* ehci_iso_sched ops can be ITD-only or SITD-only */ + +static struct ehci_iso_sched * +iso_sched_alloc (unsigned packets, gfp_t mem_flags) +{ + struct ehci_iso_sched *iso_sched; + int size = sizeof *iso_sched; + + size += packets * sizeof (struct ehci_iso_packet); + iso_sched = kzalloc(size, mem_flags); + if (likely (iso_sched != NULL)) { + INIT_LIST_HEAD (&iso_sched->td_list); + } + return iso_sched; +} + +static inline void +itd_sched_init( + struct ehci_hcd *ehci, + struct ehci_iso_sched *iso_sched, + struct ehci_iso_stream *stream, + struct urb *urb +) +{ + unsigned i; + dma_addr_t dma = urb->transfer_dma; + + /* how many uframes are needed for these transfers */ + iso_sched->span = urb->number_of_packets * stream->interval; + + /* figure out per-uframe itd fields that we'll need later + * when we fit new itds into the schedule. + */ + for (i = 0; i < urb->number_of_packets; i++) { + struct ehci_iso_packet *uframe = &iso_sched->packet [i]; + unsigned length; + dma_addr_t buf; + u32 trans; + + length = urb->iso_frame_desc [i].length; + buf = dma + urb->iso_frame_desc [i].offset; + + trans = EHCI_ISOC_ACTIVE; + trans |= buf & 0x0fff; + if (unlikely (((i + 1) == urb->number_of_packets)) + && !(urb->transfer_flags & URB_NO_INTERRUPT)) + trans |= EHCI_ITD_IOC; + trans |= length << 16; + uframe->transaction = cpu_to_hc32(ehci, trans); + + /* might need to cross a buffer page within a uframe */ + uframe->bufp = (buf & ~(u64)0x0fff); + buf += length; + if (unlikely ((uframe->bufp != (buf & ~(u64)0x0fff)))) + uframe->cross = 1; + } +} + +static void +iso_sched_free ( + struct ehci_iso_stream *stream, + struct ehci_iso_sched *iso_sched +) +{ + if (!iso_sched) + return; + // caller must hold ehci->lock! + list_splice (&iso_sched->td_list, &stream->free_list); + kfree (iso_sched); +} + +static int +itd_urb_transaction ( + struct ehci_iso_stream *stream, + struct ehci_hcd *ehci, + struct urb *urb, + gfp_t mem_flags +) +{ + struct ehci_itd *itd; + dma_addr_t itd_dma; + int i; + unsigned num_itds; + struct ehci_iso_sched *sched; + unsigned long flags; + + sched = iso_sched_alloc (urb->number_of_packets, mem_flags); + if (unlikely (sched == NULL)) + return -ENOMEM; + + itd_sched_init(ehci, sched, stream, urb); + + if (urb->interval < 8) + num_itds = 1 + (sched->span + 7) / 8; + else + num_itds = urb->number_of_packets; + + /* allocate/init ITDs */ + spin_lock_irqsave (&ehci->lock, flags); + for (i = 0; i < num_itds; i++) { + + /* + * Use iTDs from the free list, but not iTDs that may + * still be in use by the hardware. + */ + if (likely(!list_empty(&stream->free_list))) { + itd = list_first_entry(&stream->free_list, + struct ehci_itd, itd_list); + if (itd->frame == ehci->now_frame) + goto alloc_itd; + list_del (&itd->itd_list); + itd_dma = itd->itd_dma; + } else { + alloc_itd: + spin_unlock_irqrestore (&ehci->lock, flags); + itd = dma_pool_alloc (ehci->itd_pool, mem_flags, + &itd_dma); + spin_lock_irqsave (&ehci->lock, flags); + if (!itd) { + iso_sched_free(stream, sched); + spin_unlock_irqrestore(&ehci->lock, flags); + return -ENOMEM; + } + } + + memset (itd, 0, sizeof *itd); + itd->itd_dma = itd_dma; + itd->frame = 9999; /* an invalid value */ + list_add (&itd->itd_list, &sched->td_list); + } + spin_unlock_irqrestore (&ehci->lock, flags); + + /* temporarily store schedule info in hcpriv */ + urb->hcpriv = sched; + urb->error_count = 0; + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static inline int +itd_slot_ok ( + struct ehci_hcd *ehci, + u32 mod, + u32 uframe, + u8 usecs, + u32 period +) +{ + uframe %= period; + do { + /* can't commit more than uframe_periodic_max usec */ + if (periodic_usecs (ehci, uframe >> 3, uframe & 0x7) + > (ehci->uframe_periodic_max - usecs)) + return 0; + + /* we know urb->interval is 2^N uframes */ + uframe += period; + } while (uframe < mod); + return 1; +} + +static inline int +sitd_slot_ok ( + struct ehci_hcd *ehci, + u32 mod, + struct ehci_iso_stream *stream, + u32 uframe, + struct ehci_iso_sched *sched, + u32 period_uframes +) +{ + u32 mask, tmp; + u32 frame, uf; + + mask = stream->raw_mask << (uframe & 7); + + /* for IN, don't wrap CSPLIT into the next frame */ + if (mask & ~0xffff) + return 0; + + /* check bandwidth */ + uframe %= period_uframes; + frame = uframe >> 3; + +#ifdef CONFIG_USB_EHCI_TT_NEWSCHED + /* The tt's fullspeed bus bandwidth must be available. + * tt_available scheduling guarantees 10+% for control/bulk. + */ + uf = uframe & 7; + if (!tt_available(ehci, period_uframes >> 3, + stream->udev, frame, uf, stream->tt_usecs)) + return 0; +#else + /* tt must be idle for start(s), any gap, and csplit. + * assume scheduling slop leaves 10+% for control/bulk. + */ + if (!tt_no_collision(ehci, period_uframes >> 3, + stream->udev, frame, mask)) + return 0; +#endif + + /* this multi-pass logic is simple, but performance may + * suffer when the schedule data isn't cached. + */ + do { + u32 max_used; + + frame = uframe >> 3; + uf = uframe & 7; + + /* check starts (OUT uses more than one) */ + max_used = ehci->uframe_periodic_max - stream->usecs; + for (tmp = stream->raw_mask & 0xff; tmp; tmp >>= 1, uf++) { + if (periodic_usecs (ehci, frame, uf) > max_used) + return 0; + } + + /* for IN, check CSPLIT */ + if (stream->c_usecs) { + uf = uframe & 7; + max_used = ehci->uframe_periodic_max - stream->c_usecs; + do { + tmp = 1 << uf; + tmp <<= 8; + if ((stream->raw_mask & tmp) == 0) + continue; + if (periodic_usecs (ehci, frame, uf) + > max_used) + return 0; + } while (++uf < 8); + } + + /* we know urb->interval is 2^N uframes */ + uframe += period_uframes; + } while (uframe < mod); + + stream->splits = cpu_to_hc32(ehci, stream->raw_mask << (uframe & 7)); + return 1; +} + +/* + * This scheduler plans almost as far into the future as it has actual + * periodic schedule slots. (Affected by TUNE_FLS, which defaults to + * "as small as possible" to be cache-friendlier.) That limits the size + * transfers you can stream reliably; avoid more than 64 msec per urb. + * Also avoid queue depths of less than ehci's worst irq latency (affected + * by the per-urb URB_NO_INTERRUPT hint, the log2_irq_thresh module parameter, + * and other factors); or more than about 230 msec total (for portability, + * given EHCI_TUNE_FLS and the slop). Or, write a smarter scheduler! + */ + +#define SCHEDULING_DELAY 40 /* microframes */ + +static int +iso_stream_schedule ( + struct ehci_hcd *ehci, + struct urb *urb, + struct ehci_iso_stream *stream +) +{ + u32 now, base, next, start, period, span; + int status; + unsigned mod = ehci->periodic_size << 3; + struct ehci_iso_sched *sched = urb->hcpriv; + + period = urb->interval; + span = sched->span; + if (!stream->highspeed) { + period <<= 3; + span <<= 3; + } + + now = ehci_read_frame_index(ehci) & (mod - 1); + + /* Typical case: reuse current schedule, stream is still active. + * Hopefully there are no gaps from the host falling behind + * (irq delays etc). If there are, the behavior depends on + * whether URB_ISO_ASAP is set. + */ + if (likely (!list_empty (&stream->td_list))) { + + /* Take the isochronous scheduling threshold into account */ + if (ehci->i_thresh) + next = now + ehci->i_thresh; /* uframe cache */ + else + next = (now + 2 + 7) & ~0x07; /* full frame cache */ + + /* + * Use ehci->last_iso_frame as the base. There can't be any + * TDs scheduled for earlier than that. + */ + base = ehci->last_iso_frame << 3; + next = (next - base) & (mod - 1); + start = (stream->next_uframe - base) & (mod - 1); + + /* Is the schedule already full? */ + if (unlikely(start < period)) { + ehci_dbg(ehci, "iso sched full %p (%u-%u < %u mod %u)\n", + urb, stream->next_uframe, base, + period, mod); + status = -ENOSPC; + goto fail; + } + + /* Behind the scheduling threshold? */ + if (unlikely(start < next)) { + unsigned now2 = (now - base) & (mod - 1); + + /* USB_ISO_ASAP: Round up to the first available slot */ + if (urb->transfer_flags & URB_ISO_ASAP) + start += (next - start + period - 1) & -period; + + /* + * Not ASAP: Use the next slot in the stream, + * no matter what. + */ + else if (start + span - period < now2) { + ehci_dbg(ehci, "iso underrun %p (%u+%u < %u)\n", + urb, start + base, + span - period, now2 + base); + } + } + + start += base; + } + + /* need to schedule; when's the next (u)frame we could start? + * this is bigger than ehci->i_thresh allows; scheduling itself + * isn't free, the delay should handle reasonably slow cpus. it + * can also help high bandwidth if the dma and irq loads don't + * jump until after the queue is primed. + */ + else { + int done = 0; + + base = now & ~0x07; + start = base + SCHEDULING_DELAY; + + /* find a uframe slot with enough bandwidth. + * Early uframes are more precious because full-speed + * iso IN transfers can't use late uframes, + * and therefore they should be allocated last. + */ + next = start; + start += period; + do { + start--; + /* check schedule: enough space? */ + if (stream->highspeed) { + if (itd_slot_ok(ehci, mod, start, + stream->usecs, period)) + done = 1; + } else { + if ((start % 8) >= 6) + continue; + if (sitd_slot_ok(ehci, mod, stream, + start, sched, period)) + done = 1; + } + } while (start > next && !done); + + /* no room in the schedule */ + if (!done) { + ehci_dbg(ehci, "iso sched full %p", urb); + status = -ENOSPC; + goto fail; + } + } + + /* Tried to schedule too far into the future? */ + if (unlikely(start - base + span - period >= mod)) { + ehci_dbg(ehci, "request %p would overflow (%u+%u >= %u)\n", + urb, start - base, span - period, mod); + status = -EFBIG; + goto fail; + } + + stream->next_uframe = start & (mod - 1); + + /* report high speed start in uframes; full speed, in frames */ + urb->start_frame = stream->next_uframe; + if (!stream->highspeed) + urb->start_frame >>= 3; + + /* Make sure scan_isoc() sees these */ + if (ehci->isoc_count == 0) + ehci->last_iso_frame = now >> 3; + return 0; + + fail: + iso_sched_free(stream, sched); + urb->hcpriv = NULL; + return status; +} + +/*-------------------------------------------------------------------------*/ + +static inline void +itd_init(struct ehci_hcd *ehci, struct ehci_iso_stream *stream, + struct ehci_itd *itd) +{ + int i; + + /* it's been recently zeroed */ + itd->hw_next = EHCI_LIST_END(ehci); + itd->hw_bufp [0] = stream->buf0; + itd->hw_bufp [1] = stream->buf1; + itd->hw_bufp [2] = stream->buf2; + + for (i = 0; i < 8; i++) + itd->index[i] = -1; + + /* All other fields are filled when scheduling */ +} + +static inline void +itd_patch( + struct ehci_hcd *ehci, + struct ehci_itd *itd, + struct ehci_iso_sched *iso_sched, + unsigned index, + u16 uframe +) +{ + struct ehci_iso_packet *uf = &iso_sched->packet [index]; + unsigned pg = itd->pg; + + // BUG_ON (pg == 6 && uf->cross); + + uframe &= 0x07; + itd->index [uframe] = index; + + itd->hw_transaction[uframe] = uf->transaction; + itd->hw_transaction[uframe] |= cpu_to_hc32(ehci, pg << 12); + itd->hw_bufp[pg] |= cpu_to_hc32(ehci, uf->bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(ehci, (u32)(uf->bufp >> 32)); + + /* iso_frame_desc[].offset must be strictly increasing */ + if (unlikely (uf->cross)) { + u64 bufp = uf->bufp + 4096; + + itd->pg = ++pg; + itd->hw_bufp[pg] |= cpu_to_hc32(ehci, bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(ehci, (u32)(bufp >> 32)); + } +} + +static inline void +itd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_itd *itd) +{ + union ehci_shadow *prev = &ehci->pshadow[frame]; + __hc32 *hw_p = &ehci->periodic[frame]; + union ehci_shadow here = *prev; + __hc32 type = 0; + + /* skip any iso nodes which might belong to previous microframes */ + while (here.ptr) { + type = Q_NEXT_TYPE(ehci, *hw_p); + if (type == cpu_to_hc32(ehci, Q_TYPE_QH)) + break; + prev = periodic_next_shadow(ehci, prev, type); + hw_p = shadow_next_periodic(ehci, &here, type); + here = *prev; + } + + itd->itd_next = here; + itd->hw_next = *hw_p; + prev->itd = itd; + itd->frame = frame; + wmb (); + *hw_p = cpu_to_hc32(ehci, itd->itd_dma | Q_TYPE_ITD); +} + +/* fit urb's itds into the selected schedule slot; activate as needed */ +static void itd_link_urb( + struct ehci_hcd *ehci, + struct urb *urb, + unsigned mod, + struct ehci_iso_stream *stream +) +{ + int packet; + unsigned next_uframe, uframe, frame; + struct ehci_iso_sched *iso_sched = urb->hcpriv; + struct ehci_itd *itd; + + next_uframe = stream->next_uframe & (mod - 1); + + if (unlikely (list_empty(&stream->td_list))) { + ehci_to_hcd(ehci)->self.bandwidth_allocated + += stream->bandwidth; + ehci_vdbg (ehci, + "schedule devp %s ep%d%s-iso period %d start %d.%d\n", + urb->dev->devpath, stream->bEndpointAddress & 0x0f, + (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + urb->interval, + next_uframe >> 3, next_uframe & 0x7); + } + + if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) { + if (ehci->amd_pll_fix == 1) + usb_amd_quirk_pll_disable(); + } + + ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++; + + /* fill iTDs uframe by uframe */ + for (packet = 0, itd = NULL; packet < urb->number_of_packets; ) { + if (itd == NULL) { + /* ASSERT: we have all necessary itds */ + // BUG_ON (list_empty (&iso_sched->td_list)); + + /* ASSERT: no itds for this endpoint in this uframe */ + + itd = list_entry (iso_sched->td_list.next, + struct ehci_itd, itd_list); + list_move_tail (&itd->itd_list, &stream->td_list); + itd->stream = stream; + itd->urb = urb; + itd_init (ehci, stream, itd); + } + + uframe = next_uframe & 0x07; + frame = next_uframe >> 3; + + itd_patch(ehci, itd, iso_sched, packet, uframe); + + next_uframe += stream->interval; + next_uframe &= mod - 1; + packet++; + + /* link completed itds into the schedule */ + if (((next_uframe >> 3) != frame) + || packet == urb->number_of_packets) { + itd_link(ehci, frame & (ehci->periodic_size - 1), itd); + itd = NULL; + } + } + stream->next_uframe = next_uframe; + + /* don't need that schedule data any more */ + iso_sched_free (stream, iso_sched); + urb->hcpriv = stream; + + ++ehci->isoc_count; + enable_periodic(ehci); +} + +#define ISO_ERRS (EHCI_ISOC_BUF_ERR | EHCI_ISOC_BABBLE | EHCI_ISOC_XACTERR) + +/* Process and recycle a completed ITD. Return true iff its urb completed, + * and hence its completion callback probably added things to the hardware + * schedule. + * + * Note that we carefully avoid recycling this descriptor until after any + * completion callback runs, so that it won't be reused quickly. That is, + * assuming (a) no more than two urbs per frame on this endpoint, and also + * (b) only this endpoint's completions submit URBs. It seems some silicon + * corrupts things if you reuse completed descriptors very quickly... + */ +static bool itd_complete(struct ehci_hcd *ehci, struct ehci_itd *itd) +{ + struct urb *urb = itd->urb; + struct usb_iso_packet_descriptor *desc; + u32 t; + unsigned uframe; + int urb_index = -1; + struct ehci_iso_stream *stream = itd->stream; + struct usb_device *dev; + bool retval = false; + + /* for each uframe with a packet */ + for (uframe = 0; uframe < 8; uframe++) { + if (likely (itd->index[uframe] == -1)) + continue; + urb_index = itd->index[uframe]; + desc = &urb->iso_frame_desc [urb_index]; + + t = hc32_to_cpup(ehci, &itd->hw_transaction [uframe]); + itd->hw_transaction [uframe] = 0; + + /* report transfer status */ + if (unlikely (t & ISO_ERRS)) { + urb->error_count++; + if (t & EHCI_ISOC_BUF_ERR) + desc->status = usb_pipein (urb->pipe) + ? -ENOSR /* hc couldn't read */ + : -ECOMM; /* hc couldn't write */ + else if (t & EHCI_ISOC_BABBLE) + desc->status = -EOVERFLOW; + else /* (t & EHCI_ISOC_XACTERR) */ + desc->status = -EPROTO; + + /* HC need not update length with this error */ + if (!(t & EHCI_ISOC_BABBLE)) { + desc->actual_length = EHCI_ITD_LENGTH(t); + urb->actual_length += desc->actual_length; + } + } else if (likely ((t & EHCI_ISOC_ACTIVE) == 0)) { + desc->status = 0; + desc->actual_length = EHCI_ITD_LENGTH(t); + urb->actual_length += desc->actual_length; + } else { + /* URB was too late */ + urb->error_count++; + } + } + + /* handle completion now? */ + if (likely ((urb_index + 1) != urb->number_of_packets)) + goto done; + + /* ASSERT: it's really the last itd for this urb + list_for_each_entry (itd, &stream->td_list, itd_list) + BUG_ON (itd->urb == urb); + */ + + /* give urb back to the driver; completion often (re)submits */ + dev = urb->dev; + ehci_urb_done(ehci, urb, 0); + retval = true; + urb = NULL; + + --ehci->isoc_count; + disable_periodic(ehci); + + ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--; + if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) { + if (ehci->amd_pll_fix == 1) + usb_amd_quirk_pll_enable(); + } + + if (unlikely(list_is_singular(&stream->td_list))) { + ehci_to_hcd(ehci)->self.bandwidth_allocated + -= stream->bandwidth; + ehci_vdbg (ehci, + "deschedule devp %s ep%d%s-iso\n", + dev->devpath, stream->bEndpointAddress & 0x0f, + (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out"); + } + +done: + itd->urb = NULL; + + /* Add to the end of the free list for later reuse */ + list_move_tail(&itd->itd_list, &stream->free_list); + + /* Recycle the iTDs when the pipeline is empty (ep no longer in use) */ + if (list_empty(&stream->td_list)) { + list_splice_tail_init(&stream->free_list, + &ehci->cached_itd_list); + start_free_itds(ehci); + } + + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static int itd_submit (struct ehci_hcd *ehci, struct urb *urb, + gfp_t mem_flags) +{ + int status = -EINVAL; + unsigned long flags; + struct ehci_iso_stream *stream; + + /* Get iso_stream head */ + stream = iso_stream_find (ehci, urb); + if (unlikely (stream == NULL)) { + ehci_dbg (ehci, "can't get iso stream\n"); + return -ENOMEM; + } + if (unlikely (urb->interval != stream->interval)) { + ehci_dbg (ehci, "can't change iso interval %d --> %d\n", + stream->interval, urb->interval); + goto done; + } + +#ifdef EHCI_URB_TRACE + ehci_dbg (ehci, + "%s %s urb %p ep%d%s len %d, %d pkts %d uframes [%p]\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + urb->transfer_buffer_length, + urb->number_of_packets, urb->interval, + stream); +#endif + + /* allocate ITDs w/o locking anything */ + status = itd_urb_transaction (stream, ehci, urb, mem_flags); + if (unlikely (status < 0)) { + ehci_dbg (ehci, "can't init itds\n"); + goto done; + } + + /* schedule ... need to lock */ + spin_lock_irqsave (&ehci->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(ehci_to_hcd(ehci)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(ehci_to_hcd(ehci), urb); + if (unlikely(status)) + goto done_not_linked; + status = iso_stream_schedule(ehci, urb, stream); + if (likely (status == 0)) + itd_link_urb (ehci, urb, ehci->periodic_size << 3, stream); + else + usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); + done_not_linked: + spin_unlock_irqrestore (&ehci->lock, flags); + done: + return status; +} + +/*-------------------------------------------------------------------------*/ + +/* + * "Split ISO TDs" ... used for USB 1.1 devices going through the + * TTs in USB 2.0 hubs. These need microframe scheduling. + */ + +static inline void +sitd_sched_init( + struct ehci_hcd *ehci, + struct ehci_iso_sched *iso_sched, + struct ehci_iso_stream *stream, + struct urb *urb +) +{ + unsigned i; + dma_addr_t dma = urb->transfer_dma; + + /* how many frames are needed for these transfers */ + iso_sched->span = urb->number_of_packets * stream->interval; + + /* figure out per-frame sitd fields that we'll need later + * when we fit new sitds into the schedule. + */ + for (i = 0; i < urb->number_of_packets; i++) { + struct ehci_iso_packet *packet = &iso_sched->packet [i]; + unsigned length; + dma_addr_t buf; + u32 trans; + + length = urb->iso_frame_desc [i].length & 0x03ff; + buf = dma + urb->iso_frame_desc [i].offset; + + trans = SITD_STS_ACTIVE; + if (((i + 1) == urb->number_of_packets) + && !(urb->transfer_flags & URB_NO_INTERRUPT)) + trans |= SITD_IOC; + trans |= length << 16; + packet->transaction = cpu_to_hc32(ehci, trans); + + /* might need to cross a buffer page within a td */ + packet->bufp = buf; + packet->buf1 = (buf + length) & ~0x0fff; + if (packet->buf1 != (buf & ~(u64)0x0fff)) + packet->cross = 1; + + /* OUT uses multiple start-splits */ + if (stream->bEndpointAddress & USB_DIR_IN) + continue; + length = (length + 187) / 188; + if (length > 1) /* BEGIN vs ALL */ + length |= 1 << 3; + packet->buf1 |= length; + } +} + +static int +sitd_urb_transaction ( + struct ehci_iso_stream *stream, + struct ehci_hcd *ehci, + struct urb *urb, + gfp_t mem_flags +) +{ + struct ehci_sitd *sitd; + dma_addr_t sitd_dma; + int i; + struct ehci_iso_sched *iso_sched; + unsigned long flags; + + iso_sched = iso_sched_alloc (urb->number_of_packets, mem_flags); + if (iso_sched == NULL) + return -ENOMEM; + + sitd_sched_init(ehci, iso_sched, stream, urb); + + /* allocate/init sITDs */ + spin_lock_irqsave (&ehci->lock, flags); + for (i = 0; i < urb->number_of_packets; i++) { + + /* NOTE: for now, we don't try to handle wraparound cases + * for IN (using sitd->hw_backpointer, like a FSTN), which + * means we never need two sitds for full speed packets. + */ + + /* + * Use siTDs from the free list, but not siTDs that may + * still be in use by the hardware. + */ + if (likely(!list_empty(&stream->free_list))) { + sitd = list_first_entry(&stream->free_list, + struct ehci_sitd, sitd_list); + if (sitd->frame == ehci->now_frame) + goto alloc_sitd; + list_del (&sitd->sitd_list); + sitd_dma = sitd->sitd_dma; + } else { + alloc_sitd: + spin_unlock_irqrestore (&ehci->lock, flags); + sitd = dma_pool_alloc (ehci->sitd_pool, mem_flags, + &sitd_dma); + spin_lock_irqsave (&ehci->lock, flags); + if (!sitd) { + iso_sched_free(stream, iso_sched); + spin_unlock_irqrestore(&ehci->lock, flags); + return -ENOMEM; + } + } + + memset (sitd, 0, sizeof *sitd); + sitd->sitd_dma = sitd_dma; + sitd->frame = 9999; /* an invalid value */ + list_add (&sitd->sitd_list, &iso_sched->td_list); + } + + /* temporarily store schedule info in hcpriv */ + urb->hcpriv = iso_sched; + urb->error_count = 0; + + spin_unlock_irqrestore (&ehci->lock, flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static inline void +sitd_patch( + struct ehci_hcd *ehci, + struct ehci_iso_stream *stream, + struct ehci_sitd *sitd, + struct ehci_iso_sched *iso_sched, + unsigned index +) +{ + struct ehci_iso_packet *uf = &iso_sched->packet [index]; + u64 bufp = uf->bufp; + + sitd->hw_next = EHCI_LIST_END(ehci); + sitd->hw_fullspeed_ep = stream->address; + sitd->hw_uframe = stream->splits; + sitd->hw_results = uf->transaction; + sitd->hw_backpointer = EHCI_LIST_END(ehci); + + bufp = uf->bufp; + sitd->hw_buf[0] = cpu_to_hc32(ehci, bufp); + sitd->hw_buf_hi[0] = cpu_to_hc32(ehci, bufp >> 32); + + sitd->hw_buf[1] = cpu_to_hc32(ehci, uf->buf1); + if (uf->cross) + bufp += 4096; + sitd->hw_buf_hi[1] = cpu_to_hc32(ehci, bufp >> 32); + sitd->index = index; +} + +static inline void +sitd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_sitd *sitd) +{ + /* note: sitd ordering could matter (CSPLIT then SSPLIT) */ + sitd->sitd_next = ehci->pshadow [frame]; + sitd->hw_next = ehci->periodic [frame]; + ehci->pshadow [frame].sitd = sitd; + sitd->frame = frame; + wmb (); + ehci->periodic[frame] = cpu_to_hc32(ehci, sitd->sitd_dma | Q_TYPE_SITD); +} + +/* fit urb's sitds into the selected schedule slot; activate as needed */ +static void sitd_link_urb( + struct ehci_hcd *ehci, + struct urb *urb, + unsigned mod, + struct ehci_iso_stream *stream +) +{ + int packet; + unsigned next_uframe; + struct ehci_iso_sched *sched = urb->hcpriv; + struct ehci_sitd *sitd; + + next_uframe = stream->next_uframe; + + if (list_empty(&stream->td_list)) { + /* usbfs ignores TT bandwidth */ + ehci_to_hcd(ehci)->self.bandwidth_allocated + += stream->bandwidth; + ehci_vdbg (ehci, + "sched devp %s ep%d%s-iso [%d] %dms/%04x\n", + urb->dev->devpath, stream->bEndpointAddress & 0x0f, + (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + (next_uframe >> 3) & (ehci->periodic_size - 1), + stream->interval, hc32_to_cpu(ehci, stream->splits)); + } + + if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) { + if (ehci->amd_pll_fix == 1) + usb_amd_quirk_pll_disable(); + } + + ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++; + + /* fill sITDs frame by frame */ + for (packet = 0, sitd = NULL; + packet < urb->number_of_packets; + packet++) { + + /* ASSERT: we have all necessary sitds */ + BUG_ON (list_empty (&sched->td_list)); + + /* ASSERT: no itds for this endpoint in this frame */ + + sitd = list_entry (sched->td_list.next, + struct ehci_sitd, sitd_list); + list_move_tail (&sitd->sitd_list, &stream->td_list); + sitd->stream = stream; + sitd->urb = urb; + + sitd_patch(ehci, stream, sitd, sched, packet); + sitd_link(ehci, (next_uframe >> 3) & (ehci->periodic_size - 1), + sitd); + + next_uframe += stream->interval << 3; + } + stream->next_uframe = next_uframe & (mod - 1); + + /* don't need that schedule data any more */ + iso_sched_free (stream, sched); + urb->hcpriv = stream; + + ++ehci->isoc_count; + enable_periodic(ehci); +} + +/*-------------------------------------------------------------------------*/ + +#define SITD_ERRS (SITD_STS_ERR | SITD_STS_DBE | SITD_STS_BABBLE \ + | SITD_STS_XACT | SITD_STS_MMF) + +/* Process and recycle a completed SITD. Return true iff its urb completed, + * and hence its completion callback probably added things to the hardware + * schedule. + * + * Note that we carefully avoid recycling this descriptor until after any + * completion callback runs, so that it won't be reused quickly. That is, + * assuming (a) no more than two urbs per frame on this endpoint, and also + * (b) only this endpoint's completions submit URBs. It seems some silicon + * corrupts things if you reuse completed descriptors very quickly... + */ +static bool sitd_complete(struct ehci_hcd *ehci, struct ehci_sitd *sitd) +{ + struct urb *urb = sitd->urb; + struct usb_iso_packet_descriptor *desc; + u32 t; + int urb_index = -1; + struct ehci_iso_stream *stream = sitd->stream; + struct usb_device *dev; + bool retval = false; + + urb_index = sitd->index; + desc = &urb->iso_frame_desc [urb_index]; + t = hc32_to_cpup(ehci, &sitd->hw_results); + + /* report transfer status */ + if (unlikely(t & SITD_ERRS)) { + urb->error_count++; + if (t & SITD_STS_DBE) + desc->status = usb_pipein (urb->pipe) + ? -ENOSR /* hc couldn't read */ + : -ECOMM; /* hc couldn't write */ + else if (t & SITD_STS_BABBLE) + desc->status = -EOVERFLOW; + else /* XACT, MMF, etc */ + desc->status = -EPROTO; + } else if (unlikely(t & SITD_STS_ACTIVE)) { + /* URB was too late */ + urb->error_count++; + } else { + desc->status = 0; + desc->actual_length = desc->length - SITD_LENGTH(t); + urb->actual_length += desc->actual_length; + } + + /* handle completion now? */ + if ((urb_index + 1) != urb->number_of_packets) + goto done; + + /* ASSERT: it's really the last sitd for this urb + list_for_each_entry (sitd, &stream->td_list, sitd_list) + BUG_ON (sitd->urb == urb); + */ + + /* give urb back to the driver; completion often (re)submits */ + dev = urb->dev; + ehci_urb_done(ehci, urb, 0); + retval = true; + urb = NULL; + + --ehci->isoc_count; + disable_periodic(ehci); + + ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--; + if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) { + if (ehci->amd_pll_fix == 1) + usb_amd_quirk_pll_enable(); + } + + if (list_is_singular(&stream->td_list)) { + ehci_to_hcd(ehci)->self.bandwidth_allocated + -= stream->bandwidth; + ehci_vdbg (ehci, + "deschedule devp %s ep%d%s-iso\n", + dev->devpath, stream->bEndpointAddress & 0x0f, + (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out"); + } + +done: + sitd->urb = NULL; + + /* Add to the end of the free list for later reuse */ + list_move_tail(&sitd->sitd_list, &stream->free_list); + + /* Recycle the siTDs when the pipeline is empty (ep no longer in use) */ + if (list_empty(&stream->td_list)) { + list_splice_tail_init(&stream->free_list, + &ehci->cached_sitd_list); + start_free_itds(ehci); + } + + return retval; +} + + +static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb, + gfp_t mem_flags) +{ + int status = -EINVAL; + unsigned long flags; + struct ehci_iso_stream *stream; + + /* Get iso_stream head */ + stream = iso_stream_find (ehci, urb); + if (stream == NULL) { + ehci_dbg (ehci, "can't get iso stream\n"); + return -ENOMEM; + } + if (urb->interval != stream->interval) { + ehci_dbg (ehci, "can't change iso interval %d --> %d\n", + stream->interval, urb->interval); + goto done; + } + +#ifdef EHCI_URB_TRACE + ehci_dbg (ehci, + "submit %p dev%s ep%d%s-iso len %d\n", + urb, urb->dev->devpath, + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + urb->transfer_buffer_length); +#endif + + /* allocate SITDs */ + status = sitd_urb_transaction (stream, ehci, urb, mem_flags); + if (status < 0) { + ehci_dbg (ehci, "can't init sitds\n"); + goto done; + } + + /* schedule ... need to lock */ + spin_lock_irqsave (&ehci->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(ehci_to_hcd(ehci)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(ehci_to_hcd(ehci), urb); + if (unlikely(status)) + goto done_not_linked; + status = iso_stream_schedule(ehci, urb, stream); + if (status == 0) + sitd_link_urb (ehci, urb, ehci->periodic_size << 3, stream); + else + usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); + done_not_linked: + spin_unlock_irqrestore (&ehci->lock, flags); + done: + return status; +} + +/*-------------------------------------------------------------------------*/ + +static void scan_isoc(struct ehci_hcd *ehci) +{ + unsigned uf, now_frame, frame; + unsigned fmask = ehci->periodic_size - 1; + bool modified, live; + + /* + * When running, scan from last scan point up to "now" + * else clean up by scanning everything that's left. + * Touches as few pages as possible: cache-friendly. + */ + if (ehci->rh_state >= EHCI_RH_RUNNING) { + uf = ehci_read_frame_index(ehci); + now_frame = (uf >> 3) & fmask; + live = true; + } else { + now_frame = (ehci->last_iso_frame - 1) & fmask; + live = false; + } + ehci->now_frame = now_frame; + + frame = ehci->last_iso_frame; + for (;;) { + union ehci_shadow q, *q_p; + __hc32 type, *hw_p; + +restart: + /* scan each element in frame's queue for completions */ + q_p = &ehci->pshadow [frame]; + hw_p = &ehci->periodic [frame]; + q.ptr = q_p->ptr; + type = Q_NEXT_TYPE(ehci, *hw_p); + modified = false; + + while (q.ptr != NULL) { + switch (hc32_to_cpu(ehci, type)) { + case Q_TYPE_ITD: + /* If this ITD is still active, leave it for + * later processing ... check the next entry. + * No need to check for activity unless the + * frame is current. + */ + if (frame == now_frame && live) { + rmb(); + for (uf = 0; uf < 8; uf++) { + if (q.itd->hw_transaction[uf] & + ITD_ACTIVE(ehci)) + break; + } + if (uf < 8) { + q_p = &q.itd->itd_next; + hw_p = &q.itd->hw_next; + type = Q_NEXT_TYPE(ehci, + q.itd->hw_next); + q = *q_p; + break; + } + } + + /* Take finished ITDs out of the schedule + * and process them: recycle, maybe report + * URB completion. HC won't cache the + * pointer for much longer, if at all. + */ + *q_p = q.itd->itd_next; + if (!ehci->use_dummy_qh || + q.itd->hw_next != EHCI_LIST_END(ehci)) + *hw_p = q.itd->hw_next; + else + *hw_p = ehci->dummy->qh_dma; + type = Q_NEXT_TYPE(ehci, q.itd->hw_next); + wmb(); + modified = itd_complete (ehci, q.itd); + q = *q_p; + break; + case Q_TYPE_SITD: + /* If this SITD is still active, leave it for + * later processing ... check the next entry. + * No need to check for activity unless the + * frame is current. + */ + if (((frame == now_frame) || + (((frame + 1) & fmask) == now_frame)) + && live + && (q.sitd->hw_results & + SITD_ACTIVE(ehci))) { + + q_p = &q.sitd->sitd_next; + hw_p = &q.sitd->hw_next; + type = Q_NEXT_TYPE(ehci, + q.sitd->hw_next); + q = *q_p; + break; + } + + /* Take finished SITDs out of the schedule + * and process them: recycle, maybe report + * URB completion. + */ + *q_p = q.sitd->sitd_next; + if (!ehci->use_dummy_qh || + q.sitd->hw_next != EHCI_LIST_END(ehci)) + *hw_p = q.sitd->hw_next; + else + *hw_p = ehci->dummy->qh_dma; + type = Q_NEXT_TYPE(ehci, q.sitd->hw_next); + wmb(); + modified = sitd_complete (ehci, q.sitd); + q = *q_p; + break; + default: + ehci_dbg(ehci, "corrupt type %d frame %d shadow %p\n", + type, frame, q.ptr); + // BUG (); + /* FALL THROUGH */ + case Q_TYPE_QH: + case Q_TYPE_FSTN: + /* End of the iTDs and siTDs */ + q.ptr = NULL; + break; + } + + /* assume completion callbacks modify the queue */ + if (unlikely(modified && ehci->isoc_count > 0)) + goto restart; + } + + /* Stop when we have reached the current frame */ + if (frame == now_frame) + break; + + /* The last frame may still have active siTDs */ + ehci->last_iso_frame = frame; + frame = (frame + 1) & fmask; + } +} diff --git a/drivers/usb/host/ehci-sead3.c b/drivers/usb/host/ehci-sead3.c new file mode 100644 index 00000000..f55477c5 --- /dev/null +++ b/drivers/usb/host/ehci-sead3.c @@ -0,0 +1,187 @@ +/* + * MIPS CI13320A EHCI Host Controller driver + * Based on "ehci-au1xxx.c" by K.Boge + * + * Copyright (C) 2012 MIPS Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +static int ehci_sead3_setup(struct usb_hcd *hcd) +{ + int ret; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + ehci->caps = hcd->regs + 0x100; + +#ifdef __BIG_ENDIAN + ehci->big_endian_mmio = 1; + ehci->big_endian_desc = 1; +#endif + + ret = ehci_setup(hcd); + if (ret) + return ret; + + ehci->need_io_watchdog = 0; + + /* Set burst length to 16 words. */ + ehci_writel(ehci, 0x1010, &ehci->regs->reserved1[1]); + + return ret; +} + +const struct hc_driver ehci_sead3_hc_driver = { + .description = hcd_name, + .product_desc = "SEAD-3 EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + * + */ + .reset = ehci_sead3_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ehci_hcd_sead3_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct resource *res; + int ret; + + if (usb_disabled()) + return -ENODEV; + + if (pdev->resource[1].flags != IORESOURCE_IRQ) { + pr_debug("resource[1] is not IORESOURCE_IRQ"); + return -ENOMEM; + } + hcd = usb_create_hcd(&ehci_sead3_hc_driver, &pdev->dev, "SEAD-3"); + if (!hcd) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + hcd->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto err1; + } + + /* Root hub has integrated TT. */ + hcd->has_tt = 1; + + ret = usb_add_hcd(hcd, pdev->resource[1].start, + IRQF_SHARED); + if (ret == 0) { + platform_set_drvdata(pdev, hcd); + return ret; + } + +err1: + usb_put_hcd(hcd); + return ret; +} + +static int ehci_hcd_sead3_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int ehci_hcd_sead3_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + bool do_wakeup = device_may_wakeup(dev); + + return ehci_suspend(hcd, do_wakeup); +} + +static int ehci_hcd_sead3_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + ehci_resume(hcd, false); + return 0; +} + +static const struct dev_pm_ops sead3_ehci_pmops = { + .suspend = ehci_hcd_sead3_drv_suspend, + .resume = ehci_hcd_sead3_drv_resume, +}; + +#define SEAD3_EHCI_PMOPS (&sead3_ehci_pmops) + +#else +#define SEAD3_EHCI_PMOPS NULL +#endif + +static struct platform_driver ehci_hcd_sead3_driver = { + .probe = ehci_hcd_sead3_drv_probe, + .remove = ehci_hcd_sead3_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "sead3-ehci", + .owner = THIS_MODULE, + .pm = SEAD3_EHCI_PMOPS, + } +}; + +MODULE_ALIAS("platform:sead3-ehci"); diff --git a/drivers/usb/host/ehci-sh.c b/drivers/usb/host/ehci-sh.c new file mode 100644 index 00000000..b44d716d --- /dev/null +++ b/drivers/usb/host/ehci-sh.c @@ -0,0 +1,206 @@ +/* + * SuperH EHCI host controller driver + * + * Copyright (C) 2010 Paul Mundt + * + * Based on ohci-sh.c and ehci-atmel.c. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include + +struct ehci_sh_priv { + struct clk *iclk, *fclk; + struct usb_hcd *hcd; +}; + +static int ehci_sh_reset(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + ehci->caps = hcd->regs; + + return ehci_setup(hcd); +} + +static const struct hc_driver ehci_sh_hc_driver = { + .description = hcd_name, + .product_desc = "SuperH EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_USB2 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ehci_sh_reset, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + +#ifdef CONFIG_PM + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ehci_hcd_sh_probe(struct platform_device *pdev) +{ + struct resource *res; + struct ehci_sh_priv *priv; + struct ehci_sh_platdata *pdata; + struct usb_hcd *hcd; + int irq, ret; + + if (usb_disabled()) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(&pdev->dev)); + ret = -ENODEV; + goto fail_create_hcd; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + ret = -ENODEV; + goto fail_create_hcd; + } + + pdata = pdev->dev.platform_data; + + /* initialize hcd */ + hcd = usb_create_hcd(&ehci_sh_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + ret = -ENOMEM; + goto fail_create_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + hcd->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto fail_request_resource; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(struct ehci_sh_priv), + GFP_KERNEL); + if (!priv) { + dev_dbg(&pdev->dev, "error allocating priv data\n"); + ret = -ENOMEM; + goto fail_request_resource; + } + + /* These are optional, we don't care if they fail */ + priv->fclk = devm_clk_get(&pdev->dev, "usb_fck"); + if (IS_ERR(priv->fclk)) + priv->fclk = NULL; + + priv->iclk = devm_clk_get(&pdev->dev, "usb_ick"); + if (IS_ERR(priv->iclk)) + priv->iclk = NULL; + + clk_enable(priv->fclk); + clk_enable(priv->iclk); + + if (pdata && pdata->phy_init) + pdata->phy_init(); + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to add hcd"); + goto fail_add_hcd; + } + + priv->hcd = hcd; + platform_set_drvdata(pdev, priv); + + return ret; + +fail_add_hcd: + clk_disable(priv->iclk); + clk_disable(priv->fclk); + +fail_request_resource: + usb_put_hcd(hcd); +fail_create_hcd: + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), ret); + + return ret; +} + +static int ehci_hcd_sh_remove(struct platform_device *pdev) +{ + struct ehci_sh_priv *priv = platform_get_drvdata(pdev); + struct usb_hcd *hcd = priv->hcd; + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + platform_set_drvdata(pdev, NULL); + + clk_disable(priv->fclk); + clk_disable(priv->iclk); + + return 0; +} + +static void ehci_hcd_sh_shutdown(struct platform_device *pdev) +{ + struct ehci_sh_priv *priv = platform_get_drvdata(pdev); + struct usb_hcd *hcd = priv->hcd; + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static struct platform_driver ehci_hcd_sh_driver = { + .probe = ehci_hcd_sh_probe, + .remove = ehci_hcd_sh_remove, + .shutdown = ehci_hcd_sh_shutdown, + .driver = { + .name = "sh_ehci", + .owner = THIS_MODULE, + }, +}; + +MODULE_ALIAS("platform:sh_ehci"); diff --git a/drivers/usb/host/ehci-spear.c b/drivers/usb/host/ehci-spear.c new file mode 100644 index 00000000..bd3e5cbc --- /dev/null +++ b/drivers/usb/host/ehci-spear.c @@ -0,0 +1,206 @@ +/* +* Driver for EHCI HCD on SPEAr SOC +* +* Copyright (C) 2010 ST Micro Electronics, +* Deepak Sikri +* +* Based on various ehci-*.c drivers +* +* This file is subject to the terms and conditions of the GNU General Public +* License. See the file COPYING in the main directory of this archive for +* more details. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI SPEAr driver" + +static const char hcd_name[] = "SPEAr-ehci"; + +struct spear_ehci { + struct clk *clk; +}; + +#define to_spear_ehci(hcd) (struct spear_ehci *)(hcd_to_ehci(hcd)->priv) + +static struct hc_driver __read_mostly ehci_spear_hc_driver; + +#ifdef CONFIG_PM_SLEEP +static int ehci_spear_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + bool do_wakeup = device_may_wakeup(dev); + + return ehci_suspend(hcd, do_wakeup); +} + +static int ehci_spear_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + ehci_resume(hcd, false); + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(ehci_spear_pm_ops, ehci_spear_drv_suspend, + ehci_spear_drv_resume); + +static int spear_ehci_hcd_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd ; + struct spear_ehci *sehci; + struct resource *res; + struct clk *usbh_clk; + const struct hc_driver *driver = &ehci_spear_hc_driver; + int irq, retval; + + if (usb_disabled()) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + retval = irq; + goto fail; + } + + /* + * 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. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + usbh_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(usbh_clk)) { + dev_err(&pdev->dev, "Error getting interface clock\n"); + retval = PTR_ERR(usbh_clk); + goto fail; + } + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto fail; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + retval = -ENODEV; + goto err_put_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + if (!devm_request_mem_region(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len, + driver->description)) { + retval = -EBUSY; + goto err_put_hcd; + } + + hcd->regs = devm_ioremap(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len); + if (hcd->regs == NULL) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + retval = -ENOMEM; + goto err_put_hcd; + } + + sehci = to_spear_ehci(hcd); + sehci->clk = usbh_clk; + + /* registers start at offset 0x0 */ + hcd_to_ehci(hcd)->caps = hcd->regs; + + clk_prepare_enable(sehci->clk); + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) + goto err_stop_ehci; + + return retval; + +err_stop_ehci: + clk_disable_unprepare(sehci->clk); +err_put_hcd: + usb_put_hcd(hcd); +fail: + dev_err(&pdev->dev, "init fail, %d\n", retval); + + return retval ; +} + +static int spear_ehci_hcd_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct spear_ehci *sehci = to_spear_ehci(hcd); + + if (!hcd) + return 0; + if (in_interrupt()) + BUG(); + usb_remove_hcd(hcd); + + if (sehci->clk) + clk_disable_unprepare(sehci->clk); + usb_put_hcd(hcd); + + return 0; +} + +static struct of_device_id spear_ehci_id_table[] = { + { .compatible = "st,spear600-ehci", }, + { }, +}; + +static struct platform_driver spear_ehci_hcd_driver = { + .probe = spear_ehci_hcd_drv_probe, + .remove = spear_ehci_hcd_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "spear-ehci", + .bus = &platform_bus_type, + .pm = &ehci_spear_pm_ops, + .of_match_table = of_match_ptr(spear_ehci_id_table), + } +}; + +static const struct ehci_driver_overrides spear_overrides __initdata = { + .extra_priv_size = sizeof(struct spear_ehci), +}; + +static int __init ehci_spear_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ehci_init_driver(&ehci_spear_hc_driver, &spear_overrides); + return platform_driver_register(&spear_ehci_hcd_driver); +} +module_init(ehci_spear_init); + +static void __exit ehci_spear_cleanup(void) +{ + platform_driver_unregister(&spear_ehci_hcd_driver); +} +module_exit(ehci_spear_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:spear-ehci"); +MODULE_AUTHOR("Deepak Sikri"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-sysfs.c b/drivers/usb/host/ehci-sysfs.c new file mode 100644 index 00000000..14ced00b --- /dev/null +++ b/drivers/usb/host/ehci-sysfs.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2007 by Alan Stern + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + + +/* Display the ports dedicated to the companion controller */ +static ssize_t show_companion(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ehci_hcd *ehci; + int nports, index, n; + int count = PAGE_SIZE; + char *ptr = buf; + + ehci = hcd_to_ehci(bus_to_hcd(dev_get_drvdata(dev))); + nports = HCS_N_PORTS(ehci->hcs_params); + + for (index = 0; index < nports; ++index) { + if (test_bit(index, &ehci->companion_ports)) { + n = scnprintf(ptr, count, "%d\n", index + 1); + ptr += n; + count -= n; + } + } + return ptr - buf; +} + +/* + * Dedicate or undedicate a port to the companion controller. + * Syntax is "[-]portnum", where a leading '-' sign means + * return control of the port to the EHCI controller. + */ +static ssize_t store_companion(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ehci_hcd *ehci; + int portnum, new_owner; + + ehci = hcd_to_ehci(bus_to_hcd(dev_get_drvdata(dev))); + new_owner = PORT_OWNER; /* Owned by companion */ + if (sscanf(buf, "%d", &portnum) != 1) + return -EINVAL; + if (portnum < 0) { + portnum = - portnum; + new_owner = 0; /* Owned by EHCI */ + } + if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params)) + return -ENOENT; + portnum--; + if (new_owner) + set_bit(portnum, &ehci->companion_ports); + else + clear_bit(portnum, &ehci->companion_ports); + set_owner(ehci, portnum, new_owner); + return count; +} +static DEVICE_ATTR(companion, 0644, show_companion, store_companion); + + +/* + * Display / Set uframe_periodic_max + */ +static ssize_t show_uframe_periodic_max(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ehci_hcd *ehci; + int n; + + ehci = hcd_to_ehci(bus_to_hcd(dev_get_drvdata(dev))); + n = scnprintf(buf, PAGE_SIZE, "%d\n", ehci->uframe_periodic_max); + return n; +} + + +static ssize_t store_uframe_periodic_max(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ehci_hcd *ehci; + unsigned uframe_periodic_max; + unsigned frame, uframe; + unsigned short allocated_max; + unsigned long flags; + ssize_t ret; + + ehci = hcd_to_ehci(bus_to_hcd(dev_get_drvdata(dev))); + if (kstrtouint(buf, 0, &uframe_periodic_max) < 0) + return -EINVAL; + + if (uframe_periodic_max < 100 || uframe_periodic_max >= 125) { + ehci_info(ehci, "rejecting invalid request for " + "uframe_periodic_max=%u\n", uframe_periodic_max); + return -EINVAL; + } + + ret = -EINVAL; + + /* + * lock, so that our checking does not race with possible periodic + * bandwidth allocation through submitting new urbs. + */ + spin_lock_irqsave (&ehci->lock, flags); + + /* + * for request to decrease max periodic bandwidth, we have to check + * every microframe in the schedule to see whether the decrease is + * possible. + */ + if (uframe_periodic_max < ehci->uframe_periodic_max) { + allocated_max = 0; + + for (frame = 0; frame < ehci->periodic_size; ++frame) + for (uframe = 0; uframe < 7; ++uframe) + allocated_max = max(allocated_max, + periodic_usecs (ehci, frame, uframe)); + + if (allocated_max > uframe_periodic_max) { + ehci_info(ehci, + "cannot decrease uframe_periodic_max becase " + "periodic bandwidth is already allocated " + "(%u > %u)\n", + allocated_max, uframe_periodic_max); + goto out_unlock; + } + } + + /* increasing is always ok */ + + ehci_info(ehci, "setting max periodic bandwidth to %u%% " + "(== %u usec/uframe)\n", + 100*uframe_periodic_max/125, uframe_periodic_max); + + if (uframe_periodic_max != 100) + ehci_warn(ehci, "max periodic bandwidth set is non-standard\n"); + + ehci->uframe_periodic_max = uframe_periodic_max; + ret = count; + +out_unlock: + spin_unlock_irqrestore (&ehci->lock, flags); + return ret; +} +static DEVICE_ATTR(uframe_periodic_max, 0644, show_uframe_periodic_max, store_uframe_periodic_max); + + +static inline int create_sysfs_files(struct ehci_hcd *ehci) +{ + struct device *controller = ehci_to_hcd(ehci)->self.controller; + int i = 0; + + /* with integrated TT there is no companion! */ + if (!ehci_is_TDI(ehci)) + i = device_create_file(controller, &dev_attr_companion); + if (i) + goto out; + + i = device_create_file(controller, &dev_attr_uframe_periodic_max); +out: + return i; +} + +static inline void remove_sysfs_files(struct ehci_hcd *ehci) +{ + struct device *controller = ehci_to_hcd(ehci)->self.controller; + + /* with integrated TT there is no companion! */ + if (!ehci_is_TDI(ehci)) + device_remove_file(controller, &dev_attr_companion); + + device_remove_file(controller, &dev_attr_uframe_periodic_max); +} diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c new file mode 100644 index 00000000..59d111bf --- /dev/null +++ b/drivers/usb/host/ehci-tegra.c @@ -0,0 +1,859 @@ +/* + * EHCI-compliant USB host controller driver for NVIDIA Tegra SoCs + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2009 - 2013 NVIDIA Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEGRA_USB_BASE 0xC5000000 +#define TEGRA_USB2_BASE 0xC5004000 +#define TEGRA_USB3_BASE 0xC5008000 + +/* PORTSC registers */ +#define TEGRA_USB_PORTSC1 0x184 +#define TEGRA_USB_PORTSC1_PTS(x) (((x) & 0x3) << 30) +#define TEGRA_USB_PORTSC1_PHCD (1 << 23) + +#define TEGRA_USB_DMA_ALIGN 32 + +struct tegra_ehci_hcd { + struct ehci_hcd *ehci; + struct tegra_usb_phy *phy; + struct clk *clk; + struct usb_phy *transceiver; + int host_resumed; + int port_resuming; + bool needs_double_reset; + enum tegra_usb_phy_port_speed port_speed; +}; + +static void tegra_ehci_power_up(struct usb_hcd *hcd) +{ + struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + + clk_prepare_enable(tegra->clk); + usb_phy_set_suspend(hcd->phy, 0); + tegra->host_resumed = 1; +} + +static void tegra_ehci_power_down(struct usb_hcd *hcd) +{ + struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + + tegra->host_resumed = 0; + usb_phy_set_suspend(hcd->phy, 1); + clk_disable_unprepare(tegra->clk); +} + +static int tegra_ehci_internal_port_reset( + struct ehci_hcd *ehci, + u32 __iomem *portsc_reg +) +{ + u32 temp; + unsigned long flags; + int retval = 0; + int i, tries; + u32 saved_usbintr; + + spin_lock_irqsave(&ehci->lock, flags); + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); + /* disable USB interrupt */ + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + spin_unlock_irqrestore(&ehci->lock, flags); + + /* + * Here we have to do Port Reset at most twice for + * Port Enable bit to be set. + */ + for (i = 0; i < 2; i++) { + temp = ehci_readl(ehci, portsc_reg); + temp |= PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + mdelay(10); + temp &= ~PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + mdelay(1); + tries = 100; + do { + mdelay(1); + /* + * Up to this point, Port Enable bit is + * expected to be set after 2 ms waiting. + * USB1 usually takes extra 45 ms, for safety, + * we take 100 ms as timeout. + */ + temp = ehci_readl(ehci, portsc_reg); + } while (!(temp & PORT_PE) && tries--); + if (temp & PORT_PE) + break; + } + if (i == 2) + retval = -ETIMEDOUT; + + /* + * Clear Connect Status Change bit if it's set. + * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared. + */ + if (temp & PORT_CSC) + ehci_writel(ehci, PORT_CSC, portsc_reg); + + /* + * Write to clear any interrupt status bits that might be set + * during port reset. + */ + temp = ehci_readl(ehci, &ehci->regs->status); + ehci_writel(ehci, temp, &ehci->regs->status); + + /* restore original interrupt enable bits */ + ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); + return retval; +} + +static int tegra_ehci_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + u32 __iomem *status_reg; + u32 temp; + unsigned long flags; + int retval = 0; + + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + + spin_lock_irqsave(&ehci->lock, flags); + + if (typeReq == GetPortStatus) { + temp = ehci_readl(ehci, status_reg); + if (tegra->port_resuming && !(temp & PORT_SUSPEND)) { + /* Resume completed, re-enable disconnect detection */ + tegra->port_resuming = 0; + tegra_usb_phy_postresume(hcd->phy); + } + } + + else if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { + temp = ehci_readl(ehci, status_reg); + if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { + retval = -EPIPE; + goto done; + } + + temp &= ~(PORT_RWC_BITS | PORT_WKCONN_E); + temp |= PORT_WKDISC_E | PORT_WKOC_E; + ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); + + /* + * If a transaction is in progress, there may be a delay in + * suspending the port. Poll until the port is suspended. + */ + if (handshake(ehci, status_reg, PORT_SUSPEND, + PORT_SUSPEND, 5000)) + pr_err("%s: timeout waiting for SUSPEND\n", __func__); + + set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports); + goto done; + } + + /* For USB1 port we need to issue Port Reset twice internally */ + if (tegra->needs_double_reset && + (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_RESET)) { + spin_unlock_irqrestore(&ehci->lock, flags); + return tegra_ehci_internal_port_reset(ehci, status_reg); + } + + /* + * Tegra host controller will time the resume operation to clear the bit + * when the port control state switches to HS or FS Idle. This behavior + * is different from EHCI where the host controller driver is required + * to set this bit to a zero after the resume duration is timed in the + * driver. + */ + else if (typeReq == ClearPortFeature && + wValue == USB_PORT_FEAT_SUSPEND) { + temp = ehci_readl(ehci, status_reg); + if ((temp & PORT_RESET) || !(temp & PORT_PE)) { + retval = -EPIPE; + goto done; + } + + if (!(temp & PORT_SUSPEND)) + goto done; + + /* Disable disconnect detection during port resume */ + tegra_usb_phy_preresume(hcd->phy); + + ehci->reset_done[wIndex-1] = jiffies + msecs_to_jiffies(25); + + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + /* start resume signalling */ + ehci_writel(ehci, temp | PORT_RESUME, status_reg); + set_bit(wIndex-1, &ehci->resuming_ports); + + spin_unlock_irqrestore(&ehci->lock, flags); + msleep(20); + spin_lock_irqsave(&ehci->lock, flags); + + /* Poll until the controller clears RESUME and SUSPEND */ + if (handshake(ehci, status_reg, PORT_RESUME, 0, 2000)) + pr_err("%s: timeout waiting for RESUME\n", __func__); + if (handshake(ehci, status_reg, PORT_SUSPEND, 0, 2000)) + pr_err("%s: timeout waiting for SUSPEND\n", __func__); + + ehci->reset_done[wIndex-1] = 0; + clear_bit(wIndex-1, &ehci->resuming_ports); + + tegra->port_resuming = 1; + goto done; + } + + spin_unlock_irqrestore(&ehci->lock, flags); + + /* Handle the hub control events here */ + return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); +done: + spin_unlock_irqrestore(&ehci->lock, flags); + return retval; +} + +static void tegra_ehci_restart(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + ehci_reset(ehci); + + /* setup the frame list and Async q heads */ + ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); + ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); + /* setup the command register and set the controller in RUN mode */ + ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); + ehci->command |= CMD_RUN; + ehci_writel(ehci, ehci->command, &ehci->regs->command); + + down_write(&ehci_cf_port_reset_rwsem); + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + /* flush posted writes */ + ehci_readl(ehci, &ehci->regs->command); + up_write(&ehci_cf_port_reset_rwsem); +} + +static void tegra_ehci_shutdown(struct usb_hcd *hcd) +{ + struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + + /* ehci_shutdown touches the USB controller registers, make sure + * controller has clocks to it */ + if (!tegra->host_resumed) + tegra_ehci_power_up(hcd); + + ehci_shutdown(hcd); +} + +static int tegra_ehci_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + /* EHCI registers start at offset 0x100 */ + ehci->caps = hcd->regs + 0x100; + + /* switch to host mode */ + hcd->has_tt = 1; + + return ehci_setup(hcd); +} + +struct dma_aligned_buffer { + void *kmalloc_ptr; + void *old_xfer_buffer; + u8 data[0]; +}; + +static void free_dma_aligned_buffer(struct urb *urb) +{ + struct dma_aligned_buffer *temp; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + temp = container_of(urb->transfer_buffer, + struct dma_aligned_buffer, data); + + if (usb_urb_dir_in(urb)) + memcpy(temp->old_xfer_buffer, temp->data, + urb->transfer_buffer_length); + urb->transfer_buffer = temp->old_xfer_buffer; + kfree(temp->kmalloc_ptr); + + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; +} + +static int alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) +{ + struct dma_aligned_buffer *temp, *kmalloc_ptr; + size_t kmalloc_size; + + if (urb->num_sgs || urb->sg || + urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) + return 0; + + /* Allocate a buffer with enough padding for alignment */ + kmalloc_size = urb->transfer_buffer_length + + sizeof(struct dma_aligned_buffer) + TEGRA_USB_DMA_ALIGN - 1; + + kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); + if (!kmalloc_ptr) + return -ENOMEM; + + /* Position our struct dma_aligned_buffer such that data is aligned */ + temp = PTR_ALIGN(kmalloc_ptr + 1, TEGRA_USB_DMA_ALIGN) - 1; + temp->kmalloc_ptr = kmalloc_ptr; + temp->old_xfer_buffer = urb->transfer_buffer; + if (usb_urb_dir_out(urb)) + memcpy(temp->data, urb->transfer_buffer, + urb->transfer_buffer_length); + urb->transfer_buffer = temp->data; + + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; +} + +static int tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + int ret; + + ret = alloc_dma_aligned_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + free_dma_aligned_buffer(urb); + + return ret; +} + +static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + free_dma_aligned_buffer(urb); +} + +static const struct hc_driver tegra_ehci_hc_driver = { + .description = hcd_name, + .product_desc = "Tegra EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + .flags = HCD_USB2 | HCD_MEMORY, + + /* standard ehci functions */ + .irq = ehci_irq, + .start = ehci_run, + .stop = ehci_stop, + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + .get_frame_number = ehci_get_frame, + .hub_status_data = ehci_hub_status_data, + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + /* modified ehci functions for tegra */ + .reset = tegra_ehci_setup, + .shutdown = tegra_ehci_shutdown, + .map_urb_for_dma = tegra_ehci_map_urb_for_dma, + .unmap_urb_for_dma = tegra_ehci_unmap_urb_for_dma, + .hub_control = tegra_ehci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif +}; + +static int setup_vbus_gpio(struct platform_device *pdev, + struct tegra_ehci_platform_data *pdata) +{ + int err = 0; + int gpio; + + gpio = pdata->vbus_gpio; + if (!gpio_is_valid(gpio)) + gpio = of_get_named_gpio(pdev->dev.of_node, + "nvidia,vbus-gpio", 0); + if (!gpio_is_valid(gpio)) + return 0; + + err = gpio_request(gpio, "vbus_gpio"); + if (err) { + dev_err(&pdev->dev, "can't request vbus gpio %d", gpio); + return err; + } + err = gpio_direction_output(gpio, 1); + if (err) { + dev_err(&pdev->dev, "can't enable vbus\n"); + return err; + } + + return err; +} + +#ifdef CONFIG_PM + +static int controller_suspend(struct device *dev) +{ + struct tegra_ehci_hcd *tegra = + platform_get_drvdata(to_platform_device(dev)); + struct ehci_hcd *ehci = tegra->ehci; + struct usb_hcd *hcd = ehci_to_hcd(ehci); + struct ehci_regs __iomem *hw = ehci->regs; + unsigned long flags; + + if (time_before(jiffies, ehci->next_statechange)) + msleep(10); + + ehci_halt(ehci); + + spin_lock_irqsave(&ehci->lock, flags); + tegra->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3; + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irqrestore(&ehci->lock, flags); + + tegra_ehci_power_down(hcd); + return 0; +} + +static int controller_resume(struct device *dev) +{ + struct tegra_ehci_hcd *tegra = + platform_get_drvdata(to_platform_device(dev)); + struct ehci_hcd *ehci = tegra->ehci; + struct usb_hcd *hcd = ehci_to_hcd(ehci); + struct ehci_regs __iomem *hw = ehci->regs; + unsigned long val; + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + tegra_ehci_power_up(hcd); + + if (tegra->port_speed > TEGRA_USB_PHY_PORT_SPEED_HIGH) { + /* Wait for the phy to detect new devices + * before we restart the controller */ + msleep(10); + goto restart; + } + + /* Force the phy to keep data lines in suspend state */ + tegra_ehci_phy_restore_start(hcd->phy, tegra->port_speed); + + /* Enable host mode */ + tdi_reset(ehci); + + /* Enable Port Power */ + val = readl(&hw->port_status[0]); + val |= PORT_POWER; + writel(val, &hw->port_status[0]); + udelay(10); + + /* Check if the phy resume from LP0. When the phy resume from LP0 + * USB register will be reset. */ + if (!readl(&hw->async_next)) { + /* Program the field PTC based on the saved speed mode */ + val = readl(&hw->port_status[0]); + val &= ~PORT_TEST(~0); + if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_HIGH) + val |= PORT_TEST_FORCE; + else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_FULL) + val |= PORT_TEST(6); + else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW) + val |= PORT_TEST(7); + writel(val, &hw->port_status[0]); + udelay(10); + + /* Disable test mode by setting PTC field to NORMAL_OP */ + val = readl(&hw->port_status[0]); + val &= ~PORT_TEST(~0); + writel(val, &hw->port_status[0]); + udelay(10); + } + + /* Poll until CCS is enabled */ + if (handshake(ehci, &hw->port_status[0], PORT_CONNECT, + PORT_CONNECT, 2000)) { + pr_err("%s: timeout waiting for PORT_CONNECT\n", __func__); + goto restart; + } + + /* Poll until PE is enabled */ + if (handshake(ehci, &hw->port_status[0], PORT_PE, + PORT_PE, 2000)) { + pr_err("%s: timeout waiting for USB_PORTSC1_PE\n", __func__); + goto restart; + } + + /* Clear the PCI status, to avoid an interrupt taken upon resume */ + val = readl(&hw->status); + val |= STS_PCD; + writel(val, &hw->status); + + /* Put controller in suspend mode by writing 1 to SUSP bit of PORTSC */ + val = readl(&hw->port_status[0]); + if ((val & PORT_POWER) && (val & PORT_PE)) { + val |= PORT_SUSPEND; + writel(val, &hw->port_status[0]); + + /* Wait until port suspend completes */ + if (handshake(ehci, &hw->port_status[0], PORT_SUSPEND, + PORT_SUSPEND, 1000)) { + pr_err("%s: timeout waiting for PORT_SUSPEND\n", + __func__); + goto restart; + } + } + + tegra_ehci_phy_restore_end(hcd->phy); + goto done; + + restart: + if (tegra->port_speed <= TEGRA_USB_PHY_PORT_SPEED_HIGH) + tegra_ehci_phy_restore_end(hcd->phy); + + tegra_ehci_restart(hcd); + + done: + tegra_usb_phy_preresume(hcd->phy); + tegra->port_resuming = 1; + return 0; +} + +static int tegra_ehci_suspend(struct device *dev) +{ + struct tegra_ehci_hcd *tegra = + platform_get_drvdata(to_platform_device(dev)); + struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); + int rc = 0; + + /* + * When system sleep is supported and USB controller wakeup is + * implemented: If the controller is runtime-suspended and the + * wakeup setting needs to be changed, call pm_runtime_resume(). + */ + if (HCD_HW_ACCESSIBLE(hcd)) + rc = controller_suspend(dev); + return rc; +} + +static int tegra_ehci_resume(struct device *dev) +{ + int rc; + + rc = controller_resume(dev); + if (rc == 0) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + return rc; +} + +static int tegra_ehci_runtime_suspend(struct device *dev) +{ + return controller_suspend(dev); +} + +static int tegra_ehci_runtime_resume(struct device *dev) +{ + return controller_resume(dev); +} + +static const struct dev_pm_ops tegra_ehci_pm_ops = { + .suspend = tegra_ehci_suspend, + .resume = tegra_ehci_resume, + .runtime_suspend = tegra_ehci_runtime_suspend, + .runtime_resume = tegra_ehci_runtime_resume, +}; + +#endif + +/* Bits of PORTSC1, which will get cleared by writing 1 into them */ +#define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) + +static void tegra_ehci_set_pts(struct usb_phy *x, u8 pts_val) +{ + unsigned long val; + struct usb_hcd *hcd = bus_to_hcd(x->otg->host); + void __iomem *base = hcd->regs; + + val = readl(base + TEGRA_USB_PORTSC1) & ~TEGRA_PORTSC1_RWC_BITS; + val &= ~TEGRA_USB_PORTSC1_PTS(3); + val |= TEGRA_USB_PORTSC1_PTS(pts_val & 3); + writel(val, base + TEGRA_USB_PORTSC1); +} + +static void tegra_ehci_set_phcd(struct usb_phy *x, bool enable) +{ + unsigned long val; + struct usb_hcd *hcd = bus_to_hcd(x->otg->host); + void __iomem *base = hcd->regs; + + val = readl(base + TEGRA_USB_PORTSC1) & ~TEGRA_PORTSC1_RWC_BITS; + if (enable) + val |= TEGRA_USB_PORTSC1_PHCD; + else + val &= ~TEGRA_USB_PORTSC1_PHCD; + writel(val, base + TEGRA_USB_PORTSC1); +} + +static int tegra_ehci_probe(struct platform_device *pdev) +{ + struct resource *res; + struct usb_hcd *hcd; + struct tegra_ehci_hcd *tegra; + struct tegra_ehci_platform_data *pdata; + int err = 0; + int irq; + int instance = pdev->id; + struct usb_phy *u_phy; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "Platform data missing\n"); + return -EINVAL; + } + + /* 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. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + setup_vbus_gpio(pdev, pdata); + + tegra = devm_kzalloc(&pdev->dev, sizeof(struct tegra_ehci_hcd), + GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + hcd = usb_create_hcd(&tegra_ehci_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, tegra); + + tegra->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(tegra->clk)) { + dev_err(&pdev->dev, "Can't get ehci clock\n"); + err = PTR_ERR(tegra->clk); + goto fail_clk; + } + + err = clk_prepare_enable(tegra->clk); + if (err) + goto fail_clk; + + tegra_periph_reset_assert(tegra->clk); + udelay(1); + tegra_periph_reset_deassert(tegra->clk); + + tegra->needs_double_reset = of_property_read_bool(pdev->dev.of_node, + "nvidia,needs-double-reset"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + err = -ENXIO; + goto fail_io; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!hcd->regs) { + dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + err = -ENOMEM; + goto fail_io; + } + + /* This is pretty ugly and needs to be fixed when we do only + * device-tree probing. Old code relies on the platform_device + * numbering that we lack for device-tree-instantiated devices. + */ + if (instance < 0) { + switch (res->start) { + case TEGRA_USB_BASE: + instance = 0; + break; + case TEGRA_USB2_BASE: + instance = 1; + break; + case TEGRA_USB3_BASE: + instance = 2; + break; + default: + err = -ENODEV; + dev_err(&pdev->dev, "unknown usb instance\n"); + goto fail_io; + } + } + + tegra->phy = tegra_usb_phy_open(&pdev->dev, instance, hcd->regs, + pdata->phy_config, + TEGRA_USB_PHY_MODE_HOST, + tegra_ehci_set_pts, + tegra_ehci_set_phcd); + if (IS_ERR(tegra->phy)) { + dev_err(&pdev->dev, "Failed to open USB phy\n"); + err = -ENXIO; + goto fail_io; + } + + hcd->phy = u_phy = &tegra->phy->u_phy; + usb_phy_init(hcd->phy); + + u_phy->otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg), + GFP_KERNEL); + if (!u_phy->otg) { + dev_err(&pdev->dev, "Failed to alloc memory for otg\n"); + err = -ENOMEM; + goto fail_io; + } + u_phy->otg->host = hcd_to_bus(hcd); + + err = usb_phy_set_suspend(hcd->phy, 0); + if (err) { + dev_err(&pdev->dev, "Failed to power on the phy\n"); + goto fail_phy; + } + + tegra->host_resumed = 1; + tegra->ehci = hcd_to_ehci(hcd); + + irq = platform_get_irq(pdev, 0); + if (!irq) { + dev_err(&pdev->dev, "Failed to get IRQ\n"); + err = -ENODEV; + goto fail_phy; + } + + if (pdata->operating_mode == TEGRA_USB_OTG) { + tegra->transceiver = + devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + if (!IS_ERR(tegra->transceiver)) + otg_set_host(tegra->transceiver->otg, &hcd->self); + } else { + tegra->transceiver = ERR_PTR(-ENODEV); + } + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) { + dev_err(&pdev->dev, "Failed to add USB HCD\n"); + goto fail; + } + + pm_runtime_set_active(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + /* Don't skip the pm_runtime_forbid call if wakeup isn't working */ + /* if (!pdata->power_down_on_bus_suspend) */ + pm_runtime_forbid(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); + return err; + +fail: + if (!IS_ERR(tegra->transceiver)) + otg_set_host(tegra->transceiver->otg, NULL); +fail_phy: + usb_phy_shutdown(hcd->phy); +fail_io: + clk_disable_unprepare(tegra->clk); +fail_clk: + usb_put_hcd(hcd); + return err; +} + +static int tegra_ehci_remove(struct platform_device *pdev) +{ + struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); + struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); + + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + + if (!IS_ERR(tegra->transceiver)) + otg_set_host(tegra->transceiver->otg, NULL); + + usb_phy_shutdown(hcd->phy); + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + clk_disable_unprepare(tegra->clk); + + return 0; +} + +static void tegra_ehci_hcd_shutdown(struct platform_device *pdev) +{ + struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); + struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static struct of_device_id tegra_ehci_of_match[] = { + { .compatible = "nvidia,tegra20-ehci", }, + { }, +}; + +static struct platform_driver tegra_ehci_driver = { + .probe = tegra_ehci_probe, + .remove = tegra_ehci_remove, + .shutdown = tegra_ehci_hcd_shutdown, + .driver = { + .name = "tegra-ehci", + .of_match_table = tegra_ehci_of_match, +#ifdef CONFIG_PM + .pm = &tegra_ehci_pm_ops, +#endif + } +}; diff --git a/drivers/usb/host/ehci-tilegx.c b/drivers/usb/host/ehci-tilegx.c new file mode 100644 index 00000000..b083a350 --- /dev/null +++ b/drivers/usb/host/ehci-tilegx.c @@ -0,0 +1,217 @@ +/* + * Copyright 2012 Tilera Corporation. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for + * more details. + */ + +/* + * Tilera TILE-Gx USB EHCI host controller driver. + */ + +#include +#include +#include +#include + +#include + +#include +#include + +static void tilegx_start_ehc(void) +{ +} + +static void tilegx_stop_ehc(void) +{ +} + +static int tilegx_ehci_setup(struct usb_hcd *hcd) +{ + int ret = ehci_init(hcd); + + /* + * Some drivers do: + * + * struct ehci_hcd *ehci = hcd_to_ehci(hcd); + * ehci->need_io_watchdog = 0; + * + * here, but since this is a new driver we're going to leave the + * watchdog enabled. Later we may try to turn it off and see + * whether we run into any problems. + */ + + return ret; +} + +static const struct hc_driver ehci_tilegx_hc_driver = { + .description = hcd_name, + .product_desc = "Tile-Gx EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * Generic hardware linkage. + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * Basic lifecycle operations. + */ + .reset = tilegx_ehci_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * Managing I/O requests and associated device resources. + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * Scheduling support. + */ + .get_frame_number = ehci_get_frame, + + /* + * Root hub support. + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ehci_hcd_tilegx_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct tilegx_usb_platform_data *pdata = pdev->dev.platform_data; + pte_t pte = { 0 }; + int my_cpu = smp_processor_id(); + int ret; + + if (usb_disabled()) + return -ENODEV; + + /* + * Try to initialize our GXIO context; if we can't, the device + * doesn't exist. + */ + if (gxio_usb_host_init(&pdata->usb_ctx, pdata->dev_index, 1) != 0) + return -ENXIO; + + hcd = usb_create_hcd(&ehci_tilegx_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + ret = -ENOMEM; + goto err_hcd; + } + + /* + * We don't use rsrc_start to map in our registers, but seems like + * we ought to set it to something, so we use the register VA. + */ + hcd->rsrc_start = + (ulong) gxio_usb_host_get_reg_start(&pdata->usb_ctx); + hcd->rsrc_len = gxio_usb_host_get_reg_len(&pdata->usb_ctx); + hcd->regs = gxio_usb_host_get_reg_start(&pdata->usb_ctx); + + tilegx_start_ehc(); + + ehci = hcd_to_ehci(hcd); + ehci->caps = hcd->regs; + ehci->regs = + hcd->regs + HC_LENGTH(ehci, readl(&ehci->caps->hc_capbase)); + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = readl(&ehci->caps->hcs_params); + + /* Create our IRQs and register them. */ + pdata->irq = create_irq(); + if (pdata->irq < 0) { + ret = -ENXIO; + goto err_no_irq; + } + + tile_irq_activate(pdata->irq, TILE_IRQ_PERCPU); + + /* Configure interrupts. */ + ret = gxio_usb_host_cfg_interrupt(&pdata->usb_ctx, + cpu_x(my_cpu), cpu_y(my_cpu), + KERNEL_PL, pdata->irq); + if (ret) { + ret = -ENXIO; + goto err_have_irq; + } + + /* Register all of our memory. */ + pte = pte_set_home(pte, PAGE_HOME_HASH); + ret = gxio_usb_host_register_client_memory(&pdata->usb_ctx, pte, 0); + if (ret) { + ret = -ENXIO; + goto err_have_irq; + } + + ret = usb_add_hcd(hcd, pdata->irq, IRQF_SHARED); + if (ret == 0) { + platform_set_drvdata(pdev, hcd); + return ret; + } + +err_have_irq: + destroy_irq(pdata->irq); +err_no_irq: + tilegx_stop_ehc(); + usb_put_hcd(hcd); +err_hcd: + gxio_usb_host_destroy(&pdata->usb_ctx); + return ret; +} + +static int ehci_hcd_tilegx_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct tilegx_usb_platform_data *pdata = pdev->dev.platform_data; + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + tilegx_stop_ehc(); + gxio_usb_host_destroy(&pdata->usb_ctx); + destroy_irq(pdata->irq); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void ehci_hcd_tilegx_drv_shutdown(struct platform_device *pdev) +{ + usb_hcd_platform_shutdown(pdev); + ehci_hcd_tilegx_drv_remove(pdev); +} + +static struct platform_driver ehci_hcd_tilegx_driver = { + .probe = ehci_hcd_tilegx_drv_probe, + .remove = ehci_hcd_tilegx_drv_remove, + .shutdown = ehci_hcd_tilegx_drv_shutdown, + .driver = { + .name = "tilegx-ehci", + .owner = THIS_MODULE, + } +}; + +MODULE_ALIAS("platform:tilegx-ehci"); diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c new file mode 100644 index 00000000..11e5b32f --- /dev/null +++ b/drivers/usb/host/ehci-timer.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2012 by Alan Stern + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + */ + +/* This file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* Set a bit in the USBCMD register */ +static void ehci_set_command_bit(struct ehci_hcd *ehci, u32 bit) +{ + ehci->command |= bit; + ehci_writel(ehci, ehci->command, &ehci->regs->command); + + /* unblock posted write */ + ehci_readl(ehci, &ehci->regs->command); +} + +/* Clear a bit in the USBCMD register */ +static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) +{ + ehci->command &= ~bit; + ehci_writel(ehci, ehci->command, &ehci->regs->command); + + /* unblock posted write */ + ehci_readl(ehci, &ehci->regs->command); +} + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI timer support... Now using hrtimers. + * + * Lots of different events are triggered from ehci->hrtimer. Whenever + * the timer routine runs, it checks each possible event; events that are + * currently enabled and whose expiration time has passed get handled. + * The set of enabled events is stored as a collection of bitflags in + * ehci->enabled_hrtimer_events, and they are numbered in order of + * increasing delay values (ranging between 1 ms and 100 ms). + * + * Rather than implementing a sorted list or tree of all pending events, + * we keep track only of the lowest-numbered pending event, in + * ehci->next_hrtimer_event. Whenever ehci->hrtimer gets restarted, its + * expiration time is set to the timeout value for this event. + * + * As a result, events might not get handled right away; the actual delay + * could be anywhere up to twice the requested delay. This doesn't + * matter, because none of the events are especially time-critical. The + * ones that matter most all have a delay of 1 ms, so they will be + * handled after 2 ms at most, which is okay. In addition to this, we + * allow for an expiration range of 1 ms. + */ + +/* + * Delay lengths for the hrtimer event types. + * Keep this list sorted by delay length, in the same order as + * the event types indexed by enum ehci_hrtimer_event in ehci.h. + */ +static unsigned event_delays_ns[] = { + 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ + 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ + 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ + 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ + 2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */ + 6 * NSEC_PER_MSEC, /* EHCI_HRTIMER_ASYNC_UNLINKS */ + 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IAA_WATCHDOG */ + 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ + 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ + 100 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IO_WATCHDOG */ +}; + +/* Enable a pending hrtimer event */ +static void ehci_enable_event(struct ehci_hcd *ehci, unsigned event, + bool resched) +{ + ktime_t *timeout = &ehci->hr_timeouts[event]; + + if (resched) + *timeout = ktime_add(ktime_get(), + ktime_set(0, event_delays_ns[event])); + ehci->enabled_hrtimer_events |= (1 << event); + + /* Track only the lowest-numbered pending event */ + if (event < ehci->next_hrtimer_event) { + ehci->next_hrtimer_event = event; + hrtimer_start_range_ns(&ehci->hrtimer, *timeout, + NSEC_PER_MSEC, HRTIMER_MODE_ABS); + } +} + + +/* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */ +static void ehci_poll_ASS(struct ehci_hcd *ehci) +{ + unsigned actual, want; + + /* Don't enable anything if the controller isn't running (e.g., died) */ + if (ehci->rh_state != EHCI_RH_RUNNING) + return; + + want = (ehci->command & CMD_ASE) ? STS_ASS : 0; + actual = ehci_readl(ehci, &ehci->regs->status) & STS_ASS; + + if (want != actual) { + + /* Poll again later, but give up after about 2-4 ms */ + if (ehci->ASS_poll_count++ < 2) { + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_ASS, true); + return; + } + ehci_dbg(ehci, "Waited too long for the async schedule status (%x/%x), giving up\n", + want, actual); + } + ehci->ASS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (ehci->async_count > 0) + ehci_set_command_bit(ehci, CMD_ASE); + + } else { /* Running */ + if (ehci->async_count == 0) { + + /* Turn off the schedule after a while */ + ehci_enable_event(ehci, EHCI_HRTIMER_DISABLE_ASYNC, + true); + } + } +} + +/* Turn off the async schedule after a brief delay */ +static void ehci_disable_ASE(struct ehci_hcd *ehci) +{ + ehci_clear_command_bit(ehci, CMD_ASE); +} + + +/* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ +static void ehci_poll_PSS(struct ehci_hcd *ehci) +{ + unsigned actual, want; + + /* Don't do anything if the controller isn't running (e.g., died) */ + if (ehci->rh_state != EHCI_RH_RUNNING) + return; + + want = (ehci->command & CMD_PSE) ? STS_PSS : 0; + actual = ehci_readl(ehci, &ehci->regs->status) & STS_PSS; + + if (want != actual) { + + /* Poll again later, but give up after about 2-4 ms */ + if (ehci->PSS_poll_count++ < 2) { + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_PSS, true); + return; + } + ehci_dbg(ehci, "Waited too long for the periodic schedule status (%x/%x), giving up\n", + want, actual); + } + ehci->PSS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (ehci->periodic_count > 0) + ehci_set_command_bit(ehci, CMD_PSE); + + } else { /* Running */ + if (ehci->periodic_count == 0) { + + /* Turn off the schedule after a while */ + ehci_enable_event(ehci, EHCI_HRTIMER_DISABLE_PERIODIC, + true); + } + } +} + +/* Turn off the periodic schedule after a brief delay */ +static void ehci_disable_PSE(struct ehci_hcd *ehci) +{ + ehci_clear_command_bit(ehci, CMD_PSE); +} + + +/* Poll the STS_HALT status bit; see when a dead controller stops */ +static void ehci_handle_controller_death(struct ehci_hcd *ehci) +{ + if (!(ehci_readl(ehci, &ehci->regs->status) & STS_HALT)) { + + /* Give up after a few milliseconds */ + if (ehci->died_poll_count++ < 5) { + /* Try again later */ + ehci_enable_event(ehci, EHCI_HRTIMER_POLL_DEAD, true); + return; + } + ehci_warn(ehci, "Waited too long for the controller to stop, giving up\n"); + } + + /* Clean up the mess */ + ehci->rh_state = EHCI_RH_HALTED; + ehci_writel(ehci, 0, &ehci->regs->configured_flag); + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + ehci_work(ehci); + end_unlink_async(ehci); + + /* Not in process context, so don't try to reset the controller */ +} + + +/* Handle unlinked interrupt QHs once they are gone from the hardware */ +static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) +{ + bool stopped = (ehci->rh_state < EHCI_RH_RUNNING); + + /* + * Process all the QHs on the intr_unlink list that were added + * before the current unlink cycle began. The list is in + * temporal order, so stop when we reach the first entry in the + * current cycle. But if the root hub isn't running then + * process all the QHs on the list. + */ + ehci->intr_unlinking = true; + while (!list_empty(&ehci->intr_unlink)) { + struct ehci_qh *qh; + + qh = list_first_entry(&ehci->intr_unlink, struct ehci_qh, + unlink_node); + if (!stopped && qh->unlink_cycle == ehci->intr_unlink_cycle) + break; + list_del(&qh->unlink_node); + end_unlink_intr(ehci, qh); + } + + /* Handle remaining entries later */ + if (!list_empty(&ehci->intr_unlink)) { + ehci_enable_event(ehci, EHCI_HRTIMER_UNLINK_INTR, true); + ++ehci->intr_unlink_cycle; + } + ehci->intr_unlinking = false; +} + + +/* Start another free-iTDs/siTDs cycle */ +static void start_free_itds(struct ehci_hcd *ehci) +{ + if (!(ehci->enabled_hrtimer_events & BIT(EHCI_HRTIMER_FREE_ITDS))) { + ehci->last_itd_to_free = list_entry( + ehci->cached_itd_list.prev, + struct ehci_itd, itd_list); + ehci->last_sitd_to_free = list_entry( + ehci->cached_sitd_list.prev, + struct ehci_sitd, sitd_list); + ehci_enable_event(ehci, EHCI_HRTIMER_FREE_ITDS, true); + } +} + +/* Wait for controller to stop using old iTDs and siTDs */ +static void end_free_itds(struct ehci_hcd *ehci) +{ + struct ehci_itd *itd, *n; + struct ehci_sitd *sitd, *sn; + + if (ehci->rh_state < EHCI_RH_RUNNING) { + ehci->last_itd_to_free = NULL; + ehci->last_sitd_to_free = NULL; + } + + list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) { + list_del(&itd->itd_list); + dma_pool_free(ehci->itd_pool, itd, itd->itd_dma); + if (itd == ehci->last_itd_to_free) + break; + } + list_for_each_entry_safe(sitd, sn, &ehci->cached_sitd_list, sitd_list) { + list_del(&sitd->sitd_list); + dma_pool_free(ehci->sitd_pool, sitd, sitd->sitd_dma); + if (sitd == ehci->last_sitd_to_free) + break; + } + + if (!list_empty(&ehci->cached_itd_list) || + !list_empty(&ehci->cached_sitd_list)) + start_free_itds(ehci); +} + + +/* Handle lost (or very late) IAA interrupts */ +static void ehci_iaa_watchdog(struct ehci_hcd *ehci) +{ + u32 cmd, status; + + /* + * Lost IAA irqs wedge things badly; seen first with a vt8235. + * So we need this watchdog, but must protect it against both + * (a) SMP races against real IAA firing and retriggering, and + * (b) clean HC shutdown, when IAA watchdog was pending. + */ + if (!ehci->iaa_in_progress || ehci->rh_state != EHCI_RH_RUNNING) + return; + + /* If we get here, IAA is *REALLY* late. It's barely + * conceivable that the system is so busy that CMD_IAAD + * is still legitimately set, so let's be sure it's + * clear before we read STS_IAA. (The HC should clear + * CMD_IAAD when it sets STS_IAA.) + */ + cmd = ehci_readl(ehci, &ehci->regs->command); + + /* + * If IAA is set here it either legitimately triggered + * after the watchdog timer expired (_way_ late, so we'll + * still count it as lost) ... or a silicon erratum: + * - VIA seems to set IAA without triggering the IRQ; + * - IAAD potentially cleared without setting IAA. + */ + status = ehci_readl(ehci, &ehci->regs->status); + if ((status & STS_IAA) || !(cmd & CMD_IAAD)) { + COUNT(ehci->stats.lost_iaa); + ehci_writel(ehci, STS_IAA, &ehci->regs->status); + } + + ehci_dbg(ehci, "IAA watchdog: status %x cmd %x\n", status, cmd); + end_unlink_async(ehci); +} + + +/* Enable the I/O watchdog, if appropriate */ +static void turn_on_io_watchdog(struct ehci_hcd *ehci) +{ + /* Not needed if the controller isn't running or it's already enabled */ + if (ehci->rh_state != EHCI_RH_RUNNING || + (ehci->enabled_hrtimer_events & + BIT(EHCI_HRTIMER_IO_WATCHDOG))) + return; + + /* + * Isochronous transfers always need the watchdog. + * For other sorts we use it only if the flag is set. + */ + if (ehci->isoc_count > 0 || (ehci->need_io_watchdog && + ehci->async_count + ehci->intr_count > 0)) + ehci_enable_event(ehci, EHCI_HRTIMER_IO_WATCHDOG, true); +} + + +/* + * Handler functions for the hrtimer event types. + * Keep this array in the same order as the event types indexed by + * enum ehci_hrtimer_event in ehci.h. + */ +static void (*event_handlers[])(struct ehci_hcd *) = { + ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ + ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ + ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ + ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ + end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */ + unlink_empty_async, /* EHCI_HRTIMER_ASYNC_UNLINKS */ + ehci_iaa_watchdog, /* EHCI_HRTIMER_IAA_WATCHDOG */ + ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ + ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ + ehci_work, /* EHCI_HRTIMER_IO_WATCHDOG */ +}; + +static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) +{ + struct ehci_hcd *ehci = container_of(t, struct ehci_hcd, hrtimer); + ktime_t now; + unsigned long events; + unsigned long flags; + unsigned e; + + spin_lock_irqsave(&ehci->lock, flags); + + events = ehci->enabled_hrtimer_events; + ehci->enabled_hrtimer_events = 0; + ehci->next_hrtimer_event = EHCI_HRTIMER_NO_EVENT; + + /* + * Check each pending event. If its time has expired, handle + * the event; otherwise re-enable it. + */ + now = ktime_get(); + for_each_set_bit(e, &events, EHCI_HRTIMER_NUM_EVENTS) { + if (now.tv64 >= ehci->hr_timeouts[e].tv64) + event_handlers[e](ehci); + else + ehci_enable_event(ehci, e, false); + } + + spin_unlock_irqrestore(&ehci->lock, flags); + return HRTIMER_NORESTART; +} diff --git a/drivers/usb/host/ehci-w90x900.c b/drivers/usb/host/ehci-w90x900.c new file mode 100644 index 00000000..59e0e24c --- /dev/null +++ b/drivers/usb/host/ehci-w90x900.c @@ -0,0 +1,179 @@ +/* + * linux/driver/usb/host/ehci-w90x900.c + * + * Copyright (c) 2008 Nuvoton technology corporation. + * + * Wan ZongShun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation;version 2 of the License. + * + */ + +#include + +/* enable phy0 and phy1 for w90p910 */ +#define ENPHY (0x01<<8) +#define PHY0_CTR (0xA4) +#define PHY1_CTR (0xA8) + +static int usb_w90x900_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct resource *res; + int retval = 0, irq; + unsigned long val; + + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + retval = -ENXIO; + goto err1; + } + + hcd = usb_create_hcd(driver, &pdev->dev, "w90x900 EHCI"); + if (!hcd) { + retval = -ENOMEM; + goto err1; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + retval = -EBUSY; + goto err2; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (hcd->regs == NULL) { + retval = -EFAULT; + goto err3; + } + + ehci = hcd_to_ehci(hcd); + ehci->caps = hcd->regs; + ehci->regs = hcd->regs + + HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + + /* enable PHY 0,1,the regs only apply to w90p910 + * 0xA4,0xA8 were offsets of PHY0 and PHY1 controller of + * w90p910 IC relative to ehci->regs. + */ + val = __raw_readl(ehci->regs+PHY0_CTR); + val |= ENPHY; + __raw_writel(val, ehci->regs+PHY0_CTR); + + val = __raw_readl(ehci->regs+PHY1_CTR); + val |= ENPHY; + __raw_writel(val, ehci->regs+PHY1_CTR); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto err4; + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval != 0) + goto err4; + + return retval; +err4: + iounmap(hcd->regs); +err3: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err2: + usb_put_hcd(hcd); +err1: + return retval; +} + +static +void usb_w90x900_remove(struct usb_hcd *hcd, struct platform_device *pdev) +{ + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + +static const struct hc_driver ehci_w90x900_hc_driver = { + .description = hcd_name, + .product_desc = "Nuvoton w90x900 EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_USB2|HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ehci_setup, + .start = ehci_run, + + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ehci_w90x900_probe(struct platform_device *pdev) +{ + if (usb_disabled()) + return -ENODEV; + + return usb_w90x900_probe(&ehci_w90x900_hc_driver, pdev); +} + +static int ehci_w90x900_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_w90x900_remove(hcd, pdev); + + return 0; +} + +static struct platform_driver ehci_hcd_w90x900_driver = { + .probe = ehci_w90x900_probe, + .remove = ehci_w90x900_remove, + .driver = { + .name = "w90x900-ehci", + .owner = THIS_MODULE, + }, +}; + +MODULE_AUTHOR("Wan ZongShun "); +MODULE_DESCRIPTION("w90p910 usb ehci driver!"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:w90p910-ehci"); diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c new file mode 100644 index 00000000..d845e3bc --- /dev/null +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -0,0 +1,254 @@ +/* + * EHCI HCD (Host Controller Driver) for USB. + * + * Bus Glue for Xilinx EHCI core on the of_platform bus + * + * Copyright (c) 2009 Xilinx, Inc. + * + * Based on "ehci-ppc-of.c" by Valentine Barshak + * and "ehci-ppc-soc.c" by Stefan Roese + * and "ohci-ppc-of.c" by Sylvain Munaut + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +#include +#include +#include + +/** + * ehci_xilinx_port_handed_over - hand the port out if failed to enable it + * @hcd: Pointer to the usb_hcd device to which the host controller bound + * @portnum:Port number to which the device is attached. + * + * This function is used as a place to tell the user that the Xilinx USB host + * controller does support LS devices. And in an HS only configuration, it + * does not support FS devices either. It is hoped that this can help a + * confused user. + * + * There are cases when the host controller fails to enable the port due to, + * for example, insufficient power that can be supplied to the device from + * the USB bus. In those cases, the messages printed here are not helpful. + */ +static int ehci_xilinx_port_handed_over(struct usb_hcd *hcd, int portnum) +{ + dev_warn(hcd->self.controller, "port %d cannot be enabled\n", portnum); + if (hcd->has_tt) { + dev_warn(hcd->self.controller, + "Maybe you have connected a low speed device?\n"); + + dev_warn(hcd->self.controller, + "We do not support low speed devices\n"); + } else { + dev_warn(hcd->self.controller, + "Maybe your device is not a high speed device?\n"); + dev_warn(hcd->self.controller, + "The USB host controller does not support full speed " + "nor low speed devices\n"); + dev_warn(hcd->self.controller, + "You can reconfigure the host controller to have " + "full speed support\n"); + } + + return 0; +} + + +static const struct hc_driver ehci_xilinx_of_hc_driver = { + .description = hcd_name, + .product_desc = "OF EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + .relinquish_port = NULL, + .port_handed_over = ehci_xilinx_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +/** + * ehci_hcd_xilinx_of_probe - Probe method for the USB host controller + * @op: pointer to the platform_device bound to the host controller + * + * This function requests resources and sets up appropriate properties for the + * host controller. Because the Xilinx USB host controller can be configured + * as HS only or HS/FS only, it checks the configuration in the device tree + * entry, and sets an appropriate value for hcd->has_tt. + */ +static int ehci_hcd_xilinx_of_probe(struct platform_device *op) +{ + struct device_node *dn = op->dev.of_node; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct resource res; + int irq; + int rv; + int *value; + + if (usb_disabled()) + return -ENODEV; + + dev_dbg(&op->dev, "initializing XILINX-OF USB Controller\n"); + + rv = of_address_to_resource(dn, 0, &res); + if (rv) + return rv; + + hcd = usb_create_hcd(&ehci_xilinx_of_hc_driver, &op->dev, + "XILINX-OF USB"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = res.start; + hcd->rsrc_len = resource_size(&res); + + irq = irq_of_parse_and_map(dn, 0); + if (!irq) { + printk(KERN_ERR "%s: irq_of_parse_and_map failed\n", __FILE__); + rv = -EBUSY; + goto err_irq; + } + + hcd->regs = devm_ioremap_resource(&op->dev, &res); + if (IS_ERR(hcd->regs)) { + rv = PTR_ERR(hcd->regs); + goto err_irq; + } + + ehci = hcd_to_ehci(hcd); + + /* This core always has big-endian register interface and uses + * big-endian memory descriptors. + */ + ehci->big_endian_mmio = 1; + ehci->big_endian_desc = 1; + + /* Check whether the FS support option is selected in the hardware. + */ + value = (int *)of_get_property(dn, "xlnx,support-usb-fs", NULL); + if (value && (*value == 1)) { + ehci_dbg(ehci, "USB host controller supports FS devices\n"); + hcd->has_tt = 1; + } else { + ehci_dbg(ehci, + "USB host controller is HS only\n"); + hcd->has_tt = 0; + } + + /* Debug registers are at the first 0x100 region + */ + ehci->caps = hcd->regs + 0x100; + + rv = usb_add_hcd(hcd, irq, 0); + if (rv == 0) + return 0; + +err_irq: + usb_put_hcd(hcd); + + return rv; +} + +/** + * ehci_hcd_xilinx_of_remove - shutdown hcd and release resources + * @op: pointer to platform_device structure that is to be removed + * + * Remove the hcd structure, and release resources that has been requested + * during probe. + */ +static int ehci_hcd_xilinx_of_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + dev_set_drvdata(&op->dev, NULL); + + dev_dbg(&op->dev, "stopping XILINX-OF USB Controller\n"); + + usb_remove_hcd(hcd); + + usb_put_hcd(hcd); + + return 0; +} + +/** + * ehci_hcd_xilinx_of_shutdown - shutdown the hcd + * @op: pointer to platform_device structure that is to be removed + * + * Properly shutdown the hcd, call driver's shutdown routine. + */ +static void ehci_hcd_xilinx_of_shutdown(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + + +static const struct of_device_id ehci_hcd_xilinx_of_match[] = { + {.compatible = "xlnx,xps-usb-host-1.00.a",}, + {}, +}; +MODULE_DEVICE_TABLE(of, ehci_hcd_xilinx_of_match); + +static struct platform_driver ehci_hcd_xilinx_of_driver = { + .probe = ehci_hcd_xilinx_of_probe, + .remove = ehci_hcd_xilinx_of_remove, + .shutdown = ehci_hcd_xilinx_of_shutdown, + .driver = { + .name = "xilinx-of-ehci", + .owner = THIS_MODULE, + .of_match_table = ehci_hcd_xilinx_of_match, + }, +}; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h new file mode 100644 index 00000000..03322d9c --- /dev/null +++ b/drivers/usb/host/ehci.h @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2001-2002 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_EHCI_HCD_H +#define __LINUX_EHCI_HCD_H + +/* definitions used for the EHCI driver */ + +/* + * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to + * __leXX (normally) or __beXX (given EHCI_BIG_ENDIAN_DESC), depending on + * the host controller implementation. + * + * To facilitate the strongest possible byte-order checking from "sparse" + * and so on, we use __leXX unless that's not practical. + */ +#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_DESC +typedef __u32 __bitwise __hc32; +typedef __u16 __bitwise __hc16; +#else +#define __hc32 __le32 +#define __hc16 __le16 +#endif + +/* statistics can be kept for tuning/monitoring */ +#ifdef DEBUG +#define EHCI_STATS +#endif + +struct ehci_stats { + /* irq usage */ + unsigned long normal; + unsigned long error; + unsigned long iaa; + unsigned long lost_iaa; + + /* termination of urbs from core */ + unsigned long complete; + unsigned long unlink; +}; + +/* ehci_hcd->lock guards shared data against other CPUs: + * ehci_hcd: async, unlink, periodic (and shadow), ... + * usb_host_endpoint: hcpriv + * ehci_qh: qh_next, qtd_list + * ehci_qtd: qtd_list + * + * Also, hold this lock when talking to HC registers or + * when updating hw_* fields in shared qh/qtd/... structures. + */ + +#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */ + +/* + * ehci_rh_state values of EHCI_RH_RUNNING or above mean that the + * controller may be doing DMA. Lower values mean there's no DMA. + */ +enum ehci_rh_state { + EHCI_RH_HALTED, + EHCI_RH_SUSPENDED, + EHCI_RH_RUNNING, + EHCI_RH_STOPPING +}; + +/* + * Timer events, ordered by increasing delay length. + * Always update event_delays_ns[] and event_handlers[] (defined in + * ehci-timer.c) in parallel with this list. + */ +enum ehci_hrtimer_event { + EHCI_HRTIMER_POLL_ASS, /* Poll for async schedule off */ + EHCI_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ + EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ + EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ + EHCI_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ + EHCI_HRTIMER_ASYNC_UNLINKS, /* Unlink empty async QHs */ + EHCI_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */ + EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ + EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ + EHCI_HRTIMER_IO_WATCHDOG, /* Check for missing IRQs */ + EHCI_HRTIMER_NUM_EVENTS /* Must come last */ +}; +#define EHCI_HRTIMER_NO_EVENT 99 + +struct ehci_hcd { /* one per controller */ + /* timing support */ + enum ehci_hrtimer_event next_hrtimer_event; + unsigned enabled_hrtimer_events; + ktime_t hr_timeouts[EHCI_HRTIMER_NUM_EVENTS]; + struct hrtimer hrtimer; + + int PSS_poll_count; + int ASS_poll_count; + int died_poll_count; + + /* glue to PCI and HCD framework */ + struct ehci_caps __iomem *caps; + struct ehci_regs __iomem *regs; + struct ehci_dbg_port __iomem *debug; + + __u32 hcs_params; /* cached register copy */ + spinlock_t lock; + enum ehci_rh_state rh_state; + + /* general schedule support */ + bool scanning:1; + bool need_rescan:1; + bool intr_unlinking:1; + bool iaa_in_progress:1; + bool async_unlinking:1; + bool shutdown:1; + struct ehci_qh *qh_scan_next; + + /* async schedule support */ + struct ehci_qh *async; + struct ehci_qh *dummy; /* For AMD quirk use */ + struct list_head async_unlink; + struct list_head async_idle; + unsigned async_unlink_cycle; + unsigned async_count; /* async activity count */ + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 /* some HCs can do less */ + unsigned periodic_size; + __hc32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + struct list_head intr_qh_list; + unsigned i_thresh; /* uframes HC might cache */ + + union ehci_shadow *pshadow; /* mirror hw periodic table */ + struct list_head intr_unlink; + unsigned intr_unlink_cycle; + unsigned now_frame; /* frame from HC hardware */ + unsigned last_iso_frame; /* last frame scanned for iso */ + unsigned intr_count; /* intr activity count */ + unsigned isoc_count; /* isoc activity count */ + unsigned periodic_count; /* periodic activity count */ + unsigned uframe_periodic_max; /* max periodic time per uframe */ + + + /* list of itds & sitds completed while now_frame was still active */ + struct list_head cached_itd_list; + struct ehci_itd *last_itd_to_free; + struct list_head cached_sitd_list; + struct ehci_sitd *last_sitd_to_free; + + /* per root hub port */ + unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; + + /* bit vectors (one bit per port) */ + unsigned long bus_suspended; /* which ports were + already suspended at the start of a bus suspend */ + unsigned long companion_ports; /* which ports are + dedicated to the companion controller */ + unsigned long owned_ports; /* which ports are + owned by the companion during a bus suspend */ + unsigned long port_c_suspend; /* which ports have + the change-suspend feature turned on */ + unsigned long suspended_ports; /* which ports are + suspended */ + unsigned long resuming_ports; /* which ports have + started to resume */ + + /* per-HC memory pools (could be per-bus, but ...) */ + struct dma_pool *qh_pool; /* qh per active urb */ + struct dma_pool *qtd_pool; /* one or more per qh */ + struct dma_pool *itd_pool; /* itd per iso urb */ + struct dma_pool *sitd_pool; /* sitd per split iso urb */ + + unsigned random_frame; + unsigned long next_statechange; + ktime_t last_periodic_enable; + u32 command; + + /* SILICON QUIRKS */ + unsigned no_selective_suspend:1; + unsigned has_fsl_port_bug:1; /* FreeScale */ + unsigned big_endian_mmio:1; + unsigned big_endian_desc:1; + unsigned big_endian_capbase:1; + unsigned has_amcc_usb23:1; + unsigned need_io_watchdog:1; + unsigned amd_pll_fix:1; + unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/ + unsigned has_synopsys_hc_bug:1; /* Synopsys HC */ + unsigned frame_index_bug:1; /* MosChip (AKA NetMos) */ + unsigned need_oc_pp_cycle:1; /* MPC834X port power */ + unsigned imx28_write_fix:1; /* For Freescale i.MX28 */ + + /* required for usb32 quirk */ + #define OHCI_CTRL_HCFS (3 << 6) + #define OHCI_USB_OPER (2 << 6) + #define OHCI_USB_SUSPEND (3 << 6) + + #define OHCI_HCCTRL_OFFSET 0x4 + #define OHCI_HCCTRL_LEN 0x4 + __hc32 *ohci_hcctrl_reg; + unsigned has_hostpc:1; + unsigned has_ppcd:1; /* support per-port change bits */ + u8 sbrn; /* packed release number */ + + /* irq statistics */ +#ifdef EHCI_STATS + struct ehci_stats stats; +# define COUNT(x) do { (x)++; } while (0) +#else +# define COUNT(x) do {} while (0) +#endif + + /* debug files */ +#ifdef DEBUG + struct dentry *debug_dir; +#endif + + /* platform-specific data -- must come last */ + unsigned long priv[0] __aligned(sizeof(s64)); +}; + +/* convert between an HCD pointer and the corresponding EHCI_HCD */ +static inline struct ehci_hcd *hcd_to_ehci (struct usb_hcd *hcd) +{ + return (struct ehci_hcd *) (hcd->hcd_priv); +} +static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) +{ + return container_of ((void *) ehci, struct usb_hcd, hcd_priv); +} + +/*-------------------------------------------------------------------------*/ + +#include + +/*-------------------------------------------------------------------------*/ + +#define QTD_NEXT(ehci, dma) cpu_to_hc32(ehci, (u32)dma) + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct ehci_qtd { + /* first part defined by EHCI spec */ + __hc32 hw_next; /* see EHCI 3.5.1 */ + __hc32 hw_alt_next; /* see EHCI 3.5.2 */ + __hc32 hw_token; /* see EHCI 3.5.3 */ +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + +#define ACTIVE_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_ACTIVE) +#define HALT_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_HALT) +#define STATUS_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_STS) + + __hc32 hw_buf [5]; /* see EHCI 3.5.4 */ + __hc32 hw_buf_hi [5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + struct urb *urb; /* qtd's urb */ + size_t length; /* length of buffer */ +} __attribute__ ((aligned (32))); + +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK(ehci) cpu_to_hc32 (ehci, ~0x1f) + +#define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1) + +/*-------------------------------------------------------------------------*/ + +/* type tag from {qh,itd,sitd,fstn}->hw_next */ +#define Q_NEXT_TYPE(ehci,dma) ((dma) & cpu_to_hc32(ehci, 3 << 1)) + +/* + * Now the following defines are not converted using the + * cpu_to_le32() macro anymore, since we have to support + * "dynamic" switching between be and le support, so that the driver + * can be used on one system with SoC EHCI controller using big-endian + * descriptors as well as a normal little-endian PCI EHCI controller. + */ +/* values for that type tag */ +#define Q_TYPE_ITD (0 << 1) +#define Q_TYPE_QH (1 << 1) +#define Q_TYPE_SITD (2 << 1) +#define Q_TYPE_FSTN (3 << 1) + +/* next async queue entry, or pointer to interrupt/periodic QH */ +#define QH_NEXT(ehci,dma) (cpu_to_hc32(ehci, (((u32)dma)&~0x01f)|Q_TYPE_QH)) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define EHCI_LIST_END(ehci) cpu_to_hc32(ehci, 1) /* "null pointer" to hw */ + +/* + * Entries in periodic shadow table are pointers to one of four kinds + * of data structure. That's dictated by the hardware; a type tag is + * encoded in the low bits of the hardware's periodic schedule. Use + * Q_NEXT_TYPE to get the tag. + * + * For entries in the async schedule, the type tag always says "qh". + */ +union ehci_shadow { + struct ehci_qh *qh; /* Q_TYPE_QH */ + struct ehci_itd *itd; /* Q_TYPE_ITD */ + struct ehci_sitd *sitd; /* Q_TYPE_SITD */ + struct ehci_fstn *fstn; /* Q_TYPE_FSTN */ + __hc32 *hw_next; /* (all types) */ + void *ptr; +}; + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + +/* first part defined by EHCI spec */ +struct ehci_qh_hw { + __hc32 hw_next; /* see EHCI 3.6.1 */ + __hc32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_CONTROL_EP (1 << 27) /* FS/LS control endpoint */ +#define QH_HEAD (1 << 15) /* Head of async reclamation list */ +#define QH_TOGGLE_CTL (1 << 14) /* Data toggle control */ +#define QH_HIGH_SPEED (2 << 12) /* Endpoint speed */ +#define QH_LOW_SPEED (1 << 12) +#define QH_FULL_SPEED (0 << 12) +#define QH_INACTIVATE (1 << 7) /* Inactivate on next transaction */ + __hc32 hw_info2; /* see EHCI 3.6.2 */ +#define QH_SMASK 0x000000ff +#define QH_CMASK 0x0000ff00 +#define QH_HUBADDR 0x007f0000 +#define QH_HUBPORT 0x3f800000 +#define QH_MULT 0xc0000000 + __hc32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct ehci_qtd) */ + __hc32 hw_qtd_next; + __hc32 hw_alt_next; + __hc32 hw_token; + __hc32 hw_buf [5]; + __hc32 hw_buf_hi [5]; +} __attribute__ ((aligned(32))); + +struct ehci_qh { + struct ehci_qh_hw *hw; /* Must come first */ + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union ehci_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + struct list_head intr_node; /* list of intr QHs */ + struct ehci_qtd *dummy; + struct list_head unlink_node; + + unsigned unlink_cycle; + + u8 qh_state; +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ +#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on unlink q */ +#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ + + u8 xacterrs; /* XactErr retry counter */ +#define QH_XACTERR_MAX 32 /* XactErr retry limit */ + + /* periodic schedule info */ + u8 usecs; /* intr bandwidth */ + u8 gap_uf; /* uframes split/csplit gap */ + u8 c_usecs; /* ... split completion bw */ + u16 tt_usecs; /* tt downstream bandwidth */ + unsigned short period; /* polling interval */ + unsigned short start; /* where polling starts */ +#define NO_FRAME ((unsigned short)~0) /* pick new start */ + + struct usb_device *dev; /* access to TT */ + unsigned is_out:1; /* bulk or intr OUT */ + unsigned clearing_tt:1; /* Clear-TT-Buf in progress */ + unsigned dequeue_during_giveback:1; + unsigned exception:1; /* got a fault, or an unlink + was requested */ +}; + +/*-------------------------------------------------------------------------*/ + +/* description of one iso transaction (up to 3 KB data if highspeed) */ +struct ehci_iso_packet { + /* These will be copied to iTD when scheduling */ + u64 bufp; /* itd->hw_bufp{,_hi}[pg] |= */ + __hc32 transaction; /* itd->hw_transaction[i] |= */ + u8 cross; /* buf crosses pages */ + /* for full speed OUT splits */ + u32 buf1; +}; + +/* temporary schedule data for packets from iso urbs (both speeds) + * each packet is one logical usb transaction to the device (not TT), + * beginning at stream->next_uframe + */ +struct ehci_iso_sched { + struct list_head td_list; + unsigned span; + struct ehci_iso_packet packet [0]; +}; + +/* + * ehci_iso_stream - groups all (s)itds for this endpoint. + * acts like a qh would, if EHCI had them for ISO. + */ +struct ehci_iso_stream { + /* first field matches ehci_hq, but is NULL */ + struct ehci_qh_hw *hw; + + u8 bEndpointAddress; + u8 highspeed; + struct list_head td_list; /* queued itds/sitds */ + struct list_head free_list; /* list of unused itds/sitds */ + struct usb_device *udev; + struct usb_host_endpoint *ep; + + /* output of (re)scheduling */ + int next_uframe; + __hc32 splits; + + /* the rest is derived from the endpoint descriptor, + * trusting urb->interval == f(epdesc->bInterval) and + * including the extra info for hw_bufp[0..2] + */ + u8 usecs, c_usecs; + u16 interval; + u16 tt_usecs; + u16 maxp; + u16 raw_mask; + unsigned bandwidth; + + /* This is used to initialize iTD's hw_bufp fields */ + __hc32 buf0; + __hc32 buf1; + __hc32 buf2; + + /* this is used to initialize sITD's tt info */ + __hc32 address; +}; + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.3 + * Fig 3-4 "Isochronous Transaction Descriptor (iTD)" + * + * Schedule records for high speed iso xfers + */ +struct ehci_itd { + /* first part defined by EHCI spec */ + __hc32 hw_next; /* see EHCI 3.3.1 */ + __hc32 hw_transaction [8]; /* see EHCI 3.3.2 */ +#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ +#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ +#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ +#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ +#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) +#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ + +#define ITD_ACTIVE(ehci) cpu_to_hc32(ehci, EHCI_ISOC_ACTIVE) + + __hc32 hw_bufp [7]; /* see EHCI 3.3.3 */ + __hc32 hw_bufp_hi [7]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t itd_dma; /* for this itd */ + union ehci_shadow itd_next; /* ptr to periodic q entry */ + + struct urb *urb; + struct ehci_iso_stream *stream; /* endpoint's queue */ + struct list_head itd_list; /* list of stream's itds */ + + /* any/all hw_transactions here may be used by that urb */ + unsigned frame; /* where scheduled */ + unsigned pg; + unsigned index[8]; /* in urb->iso_frame_desc */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.4 + * siTD, aka split-transaction isochronous Transfer Descriptor + * ... describe full speed iso xfers through TT in hubs + * see Figure 3-5 "Split-transaction Isochronous Transaction Descriptor (siTD) + */ +struct ehci_sitd { + /* first part defined by EHCI spec */ + __hc32 hw_next; +/* uses bit field macros above - see EHCI 0.95 Table 3-8 */ + __hc32 hw_fullspeed_ep; /* EHCI table 3-9 */ + __hc32 hw_uframe; /* EHCI table 3-10 */ + __hc32 hw_results; /* EHCI table 3-11 */ +#define SITD_IOC (1 << 31) /* interrupt on completion */ +#define SITD_PAGE (1 << 30) /* buffer 0/1 */ +#define SITD_LENGTH(x) (0x3ff & ((x)>>16)) +#define SITD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define SITD_STS_ERR (1 << 6) /* error from TT */ +#define SITD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define SITD_STS_BABBLE (1 << 4) /* device was babbling */ +#define SITD_STS_XACT (1 << 3) /* illegal IN response */ +#define SITD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define SITD_STS_STS (1 << 1) /* split transaction state */ + +#define SITD_ACTIVE(ehci) cpu_to_hc32(ehci, SITD_STS_ACTIVE) + + __hc32 hw_buf [2]; /* EHCI table 3-12 */ + __hc32 hw_backpointer; /* EHCI table 3-13 */ + __hc32 hw_buf_hi [2]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t sitd_dma; + union ehci_shadow sitd_next; /* ptr to periodic q entry */ + + struct urb *urb; + struct ehci_iso_stream *stream; /* endpoint's queue */ + struct list_head sitd_list; /* list of stream's sitds */ + unsigned frame; + unsigned index; +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.96 Section 3.7 + * Periodic Frame Span Traversal Node (FSTN) + * + * Manages split interrupt transactions (using TT) that span frame boundaries + * into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN + * makes the HC jump (back) to a QH to scan for fs/ls QH completions until + * it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work. + */ +struct ehci_fstn { + __hc32 hw_next; /* any periodic q entry */ + __hc32 hw_prev; /* qh or EHCI_LIST_END */ + + /* the rest is HCD-private */ + dma_addr_t fstn_dma; + union ehci_shadow fstn_next; /* ptr to periodic q entry */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* Prepare the PORTSC wakeup flags during controller suspend/resume */ + +#define ehci_prepare_ports_for_controller_suspend(ehci, do_wakeup) \ + ehci_adjust_port_wakeup_flags(ehci, true, do_wakeup); + +#define ehci_prepare_ports_for_controller_resume(ehci) \ + ehci_adjust_port_wakeup_flags(ehci, false, false); + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_EHCI_ROOT_HUB_TT + +/* + * Some EHCI controllers have a Transaction Translator built into the + * root hub. This is a non-standard feature. Each controller will need + * to add code to the following inline functions, and call them as + * needed (mostly in root hub code). + */ + +#define ehci_is_TDI(e) (ehci_to_hcd(e)->has_tt) + +/* Returns the speed of a device attached to a port on the root hub. */ +static inline unsigned int +ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc) +{ + if (ehci_is_TDI(ehci)) { + switch ((portsc >> (ehci->has_hostpc ? 25 : 26)) & 3) { + case 0: + return 0; + case 1: + return USB_PORT_STAT_LOW_SPEED; + case 2: + default: + return USB_PORT_STAT_HIGH_SPEED; + } + } + return USB_PORT_STAT_HIGH_SPEED; +} + +#else + +#define ehci_is_TDI(e) (0) + +#define ehci_port_speed(ehci, portsc) USB_PORT_STAT_HIGH_SPEED +#endif + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PPC_83xx +/* Some Freescale processors have an erratum in which the TT + * port number in the queue head was 0..N-1 instead of 1..N. + */ +#define ehci_has_fsl_portno_bug(e) ((e)->has_fsl_port_bug) +#else +#define ehci_has_fsl_portno_bug(e) (0) +#endif + +/* + * While most USB host controllers implement their registers in + * little-endian format, a minority (celleb companion chip) implement + * them in big endian format. + * + * This attempts to support either format at compile time without a + * runtime penalty, or both formats with the additional overhead + * of checking a flag bit. + * + * ehci_big_endian_capbase is a special quirk for controllers that + * implement the HC capability registers as separate registers and not + * as fields of a 32-bit register. + */ + +#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO +#define ehci_big_endian_mmio(e) ((e)->big_endian_mmio) +#define ehci_big_endian_capbase(e) ((e)->big_endian_capbase) +#else +#define ehci_big_endian_mmio(e) 0 +#define ehci_big_endian_capbase(e) 0 +#endif + +/* + * Big-endian read/write functions are arch-specific. + * Other arches can be added if/when they're needed. + */ +#if defined(CONFIG_ARM) && defined(CONFIG_ARCH_IXP4XX) +#define readl_be(addr) __raw_readl((__force unsigned *)addr) +#define writel_be(val, addr) __raw_writel(val, (__force unsigned *)addr) +#endif + +static inline unsigned int ehci_readl(const struct ehci_hcd *ehci, + __u32 __iomem * regs) +{ +#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO + return ehci_big_endian_mmio(ehci) ? + readl_be(regs) : + readl(regs); +#else + return readl(regs); +#endif +} + +#ifdef CONFIG_SOC_IMX28 +static inline void imx28_ehci_writel(const unsigned int val, + volatile __u32 __iomem *addr) +{ + __asm__ ("swp %0, %0, [%1]" : : "r"(val), "r"(addr)); +} +#else +static inline void imx28_ehci_writel(const unsigned int val, + volatile __u32 __iomem *addr) +{ +} +#endif +static inline void ehci_writel(const struct ehci_hcd *ehci, + const unsigned int val, __u32 __iomem *regs) +{ +#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO + ehci_big_endian_mmio(ehci) ? + writel_be(val, regs) : + writel(val, regs); +#else + if (ehci->imx28_write_fix) + imx28_ehci_writel(val, regs); + else + writel(val, regs); +#endif +} + +/* + * On certain ppc-44x SoC there is a HW issue, that could only worked around with + * explicit suspend/operate of OHCI. This function hereby makes sense only on that arch. + * Other common bits are dependent on has_amcc_usb23 quirk flag. + */ +#ifdef CONFIG_44x +static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) +{ + u32 hc_control; + + hc_control = (readl_be(ehci->ohci_hcctrl_reg) & ~OHCI_CTRL_HCFS); + if (operational) + hc_control |= OHCI_USB_OPER; + else + hc_control |= OHCI_USB_SUSPEND; + + writel_be(hc_control, ehci->ohci_hcctrl_reg); + (void) readl_be(ehci->ohci_hcctrl_reg); +} +#else +static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) +{ } +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * The AMCC 440EPx not only implements its EHCI registers in big-endian + * format, but also its DMA data structures (descriptors). + * + * EHCI controllers accessed through PCI work normally (little-endian + * everywhere), so we won't bother supporting a BE-only mode for now. + */ +#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_DESC +#define ehci_big_endian_desc(e) ((e)->big_endian_desc) + +/* cpu to ehci */ +static inline __hc32 cpu_to_hc32 (const struct ehci_hcd *ehci, const u32 x) +{ + return ehci_big_endian_desc(ehci) + ? (__force __hc32)cpu_to_be32(x) + : (__force __hc32)cpu_to_le32(x); +} + +/* ehci to cpu */ +static inline u32 hc32_to_cpu (const struct ehci_hcd *ehci, const __hc32 x) +{ + return ehci_big_endian_desc(ehci) + ? be32_to_cpu((__force __be32)x) + : le32_to_cpu((__force __le32)x); +} + +static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x) +{ + return ehci_big_endian_desc(ehci) + ? be32_to_cpup((__force __be32 *)x) + : le32_to_cpup((__force __le32 *)x); +} + +#else + +/* cpu to ehci */ +static inline __hc32 cpu_to_hc32 (const struct ehci_hcd *ehci, const u32 x) +{ + return cpu_to_le32(x); +} + +/* ehci to cpu */ +static inline u32 hc32_to_cpu (const struct ehci_hcd *ehci, const __hc32 x) +{ + return le32_to_cpu(x); +} + +static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x) +{ + return le32_to_cpup(x); +} + +#endif + +/*-------------------------------------------------------------------------*/ + +#define ehci_dbg(ehci, fmt, args...) \ + dev_dbg(ehci_to_hcd(ehci)->self.controller , fmt , ## args) +#define ehci_err(ehci, fmt, args...) \ + dev_err(ehci_to_hcd(ehci)->self.controller , fmt , ## args) +#define ehci_info(ehci, fmt, args...) \ + dev_info(ehci_to_hcd(ehci)->self.controller , fmt , ## args) +#define ehci_warn(ehci, fmt, args...) \ + dev_warn(ehci_to_hcd(ehci)->self.controller , fmt , ## args) + +#ifdef VERBOSE_DEBUG +# define ehci_vdbg ehci_dbg +#else + static inline void ehci_vdbg(struct ehci_hcd *ehci, ...) {} +#endif + +#ifndef DEBUG +#define STUB_DEBUG_FILES +#endif /* DEBUG */ + +/*-------------------------------------------------------------------------*/ + +/* Declarations of things exported for use by ehci platform drivers */ + +struct ehci_driver_overrides { + size_t extra_priv_size; + int (*reset)(struct usb_hcd *hcd); +}; + +extern void ehci_init_driver(struct hc_driver *drv, + const struct ehci_driver_overrides *over); +extern int ehci_setup(struct usb_hcd *hcd); + +#ifdef CONFIG_PM +extern int ehci_suspend(struct usb_hcd *hcd, bool do_wakeup); +extern int ehci_resume(struct usb_hcd *hcd, bool hibernated); +#endif /* CONFIG_PM */ + +#endif /* __LINUX_EHCI_HCD_H */ diff --git a/drivers/usb/host/fhci-dbg.c b/drivers/usb/host/fhci-dbg.c new file mode 100644 index 00000000..f238cb37 --- /dev/null +++ b/drivers/usb/host/fhci-dbg.c @@ -0,0 +1,139 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include "fhci.h" + +void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er) +{ + int i; + + if (usb_er == -1) { + fhci->usb_irq_stat[12]++; + return; + } + + for (i = 0; i < 12; ++i) { + if (usb_er & (1 << i)) + fhci->usb_irq_stat[i]++; + } +} + +static int fhci_dfs_regs_show(struct seq_file *s, void *v) +{ + struct fhci_hcd *fhci = s->private; + struct qe_usb_ctlr __iomem *regs = fhci->regs; + + seq_printf(s, + "mode: 0x%x\n" "addr: 0x%x\n" + "command: 0x%x\n" "ep0: 0x%x\n" + "event: 0x%x\n" "mask: 0x%x\n" + "status: 0x%x\n" "SOF timer: %d\n" + "frame number: %d\n" + "lines status: 0x%x\n", + in_8(®s->usb_usmod), in_8(®s->usb_usadr), + in_8(®s->usb_uscom), in_be16(®s->usb_usep[0]), + in_be16(®s->usb_usber), in_be16(®s->usb_usbmr), + in_8(®s->usb_usbs), in_be16(®s->usb_ussft), + in_be16(®s->usb_usfrn), + fhci_ioports_check_bus_state(fhci)); + + return 0; +} + +static int fhci_dfs_irq_stat_show(struct seq_file *s, void *v) +{ + struct fhci_hcd *fhci = s->private; + int *usb_irq_stat = fhci->usb_irq_stat; + + seq_printf(s, + "RXB: %d\n" "TXB: %d\n" "BSY: %d\n" + "SOF: %d\n" "TXE0: %d\n" "TXE1: %d\n" + "TXE2: %d\n" "TXE3: %d\n" "IDLE: %d\n" + "RESET: %d\n" "SFT: %d\n" "MSF: %d\n" + "IDLE_ONLY: %d\n", + usb_irq_stat[0], usb_irq_stat[1], usb_irq_stat[2], + usb_irq_stat[3], usb_irq_stat[4], usb_irq_stat[5], + usb_irq_stat[6], usb_irq_stat[7], usb_irq_stat[8], + usb_irq_stat[9], usb_irq_stat[10], usb_irq_stat[11], + usb_irq_stat[12]); + + return 0; +} + +static int fhci_dfs_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, fhci_dfs_regs_show, inode->i_private); +} + +static int fhci_dfs_irq_stat_open(struct inode *inode, struct file *file) +{ + return single_open(file, fhci_dfs_irq_stat_show, inode->i_private); +} + +static const struct file_operations fhci_dfs_regs_fops = { + .open = fhci_dfs_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations fhci_dfs_irq_stat_fops = { + .open = fhci_dfs_irq_stat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void fhci_dfs_create(struct fhci_hcd *fhci) +{ + struct device *dev = fhci_to_hcd(fhci)->self.controller; + + fhci->dfs_root = debugfs_create_dir(dev_name(dev), usb_debug_root); + if (!fhci->dfs_root) { + WARN_ON(1); + return; + } + + fhci->dfs_regs = debugfs_create_file("regs", S_IFREG | S_IRUGO, + fhci->dfs_root, fhci, &fhci_dfs_regs_fops); + + fhci->dfs_irq_stat = debugfs_create_file("irq_stat", + S_IFREG | S_IRUGO, fhci->dfs_root, fhci, + &fhci_dfs_irq_stat_fops); + + WARN_ON(!fhci->dfs_regs || !fhci->dfs_irq_stat); +} + +void fhci_dfs_destroy(struct fhci_hcd *fhci) +{ + if (!fhci->dfs_root) + return; + + if (fhci->dfs_irq_stat) + debugfs_remove(fhci->dfs_irq_stat); + + if (fhci->dfs_regs) + debugfs_remove(fhci->dfs_regs); + + debugfs_remove(fhci->dfs_root); +} diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c new file mode 100644 index 00000000..0b465425 --- /dev/null +++ b/drivers/usb/host/fhci-hcd.c @@ -0,0 +1,833 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fhci.h" + +void fhci_start_sof_timer(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + /* clear frame_n */ + out_be16(&fhci->pram->frame_num, 0); + + out_be16(&fhci->regs->usb_ussft, 0); + setbits8(&fhci->regs->usb_usmod, USB_MODE_SFTE); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +void fhci_stop_sof_timer(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + clrbits8(&fhci->regs->usb_usmod, USB_MODE_SFTE); + gtm_stop_timer16(fhci->timer); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +u16 fhci_get_sof_timer_count(struct fhci_usb *usb) +{ + return be16_to_cpu(in_be16(&usb->fhci->regs->usb_ussft) / 12); +} + +/* initialize the endpoint zero */ +static u32 endpoint_zero_init(struct fhci_usb *usb, + enum fhci_mem_alloc data_mem, + u32 ring_len) +{ + u32 rc; + + rc = fhci_create_ep(usb, data_mem, ring_len); + if (rc) + return rc; + + /* inilialize endpoint registers */ + fhci_init_ep_registers(usb, usb->ep0, data_mem); + + return 0; +} + +/* enable the USB interrupts */ +void fhci_usb_enable_interrupt(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (usb->intr_nesting_cnt == 1) { + /* initialize the USB interrupt */ + enable_irq(fhci_to_hcd(fhci)->irq); + + /* initialize the event register and mask register */ + out_be16(&usb->fhci->regs->usb_usber, 0xffff); + out_be16(&usb->fhci->regs->usb_usbmr, usb->saved_msk); + + /* enable the timer interrupts */ + enable_irq(fhci->timer->irq); + } else if (usb->intr_nesting_cnt > 1) + fhci_info(fhci, "unbalanced USB interrupts nesting\n"); + usb->intr_nesting_cnt--; +} + +/* disable the usb interrupt */ +void fhci_usb_disable_interrupt(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (usb->intr_nesting_cnt == 0) { + /* disable the timer interrupt */ + disable_irq_nosync(fhci->timer->irq); + + /* disable the usb interrupt */ + disable_irq_nosync(fhci_to_hcd(fhci)->irq); + out_be16(&usb->fhci->regs->usb_usbmr, 0); + } + usb->intr_nesting_cnt++; +} + +/* enable the USB controller */ +static u32 fhci_usb_enable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + out_be16(&usb->fhci->regs->usb_usber, 0xffff); + out_be16(&usb->fhci->regs->usb_usbmr, usb->saved_msk); + setbits8(&usb->fhci->regs->usb_usmod, USB_MODE_EN); + + mdelay(100); + + return 0; +} + +/* disable the USB controller */ +static u32 fhci_usb_disable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + fhci_usb_disable_interrupt(usb); + fhci_port_disable(fhci); + + /* disable the usb controller */ + if (usb->port_status == FHCI_PORT_FULL || + usb->port_status == FHCI_PORT_LOW) + fhci_device_disconnected_interrupt(fhci); + + clrbits8(&usb->fhci->regs->usb_usmod, USB_MODE_EN); + + return 0; +} + +/* check the bus state by polling the QE bit on the IO ports */ +int fhci_ioports_check_bus_state(struct fhci_hcd *fhci) +{ + u8 bits = 0; + + /* check USBOE,if transmitting,exit */ + if (!gpio_get_value(fhci->gpios[GPIO_USBOE])) + return -1; + + /* check USBRP */ + if (gpio_get_value(fhci->gpios[GPIO_USBRP])) + bits |= 0x2; + + /* check USBRN */ + if (gpio_get_value(fhci->gpios[GPIO_USBRN])) + bits |= 0x1; + + return bits; +} + +static void fhci_mem_free(struct fhci_hcd *fhci) +{ + struct ed *ed; + struct ed *next_ed; + struct td *td; + struct td *next_td; + + list_for_each_entry_safe(ed, next_ed, &fhci->empty_eds, node) { + list_del(&ed->node); + kfree(ed); + } + + list_for_each_entry_safe(td, next_td, &fhci->empty_tds, node) { + list_del(&td->node); + kfree(td); + } + + kfree(fhci->vroot_hub); + fhci->vroot_hub = NULL; + + kfree(fhci->hc_list); + fhci->hc_list = NULL; +} + +static int fhci_mem_init(struct fhci_hcd *fhci) +{ + int i; + + fhci->hc_list = kzalloc(sizeof(*fhci->hc_list), GFP_KERNEL); + if (!fhci->hc_list) + goto err; + + INIT_LIST_HEAD(&fhci->hc_list->ctrl_list); + INIT_LIST_HEAD(&fhci->hc_list->bulk_list); + INIT_LIST_HEAD(&fhci->hc_list->iso_list); + INIT_LIST_HEAD(&fhci->hc_list->intr_list); + INIT_LIST_HEAD(&fhci->hc_list->done_list); + + fhci->vroot_hub = kzalloc(sizeof(*fhci->vroot_hub), GFP_KERNEL); + if (!fhci->vroot_hub) + goto err; + + INIT_LIST_HEAD(&fhci->empty_eds); + INIT_LIST_HEAD(&fhci->empty_tds); + + /* initialize work queue to handle done list */ + fhci_tasklet.data = (unsigned long)fhci; + fhci->process_done_task = &fhci_tasklet; + + for (i = 0; i < MAX_TDS; i++) { + struct td *td; + + td = kmalloc(sizeof(*td), GFP_KERNEL); + if (!td) + goto err; + fhci_recycle_empty_td(fhci, td); + } + for (i = 0; i < MAX_EDS; i++) { + struct ed *ed; + + ed = kmalloc(sizeof(*ed), GFP_KERNEL); + if (!ed) + goto err; + fhci_recycle_empty_ed(fhci, ed); + } + + fhci->active_urbs = 0; + return 0; +err: + fhci_mem_free(fhci); + return -ENOMEM; +} + +/* destroy the fhci_usb structure */ +static void fhci_usb_free(void *lld) +{ + struct fhci_usb *usb = lld; + struct fhci_hcd *fhci; + + if (usb) { + fhci = usb->fhci; + fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF); + fhci_ep0_free(usb); + kfree(usb->actual_frame); + kfree(usb); + } +} + +/* initialize the USB */ +static int fhci_usb_init(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + memset_io(usb->fhci->pram, 0, FHCI_PRAM_SIZE); + + usb->port_status = FHCI_PORT_DISABLED; + usb->max_frame_usage = FRAME_TIME_USAGE; + usb->sw_transaction_time = SW_FIX_TIME_BETWEEN_TRANSACTION; + + usb->actual_frame = kzalloc(sizeof(*usb->actual_frame), GFP_KERNEL); + if (!usb->actual_frame) { + fhci_usb_free(usb); + return -ENOMEM; + } + + INIT_LIST_HEAD(&usb->actual_frame->tds_list); + + /* initializing registers on chip, clear frame number */ + out_be16(&fhci->pram->frame_num, 0); + + /* clear rx state */ + out_be32(&fhci->pram->rx_state, 0); + + /* set mask register */ + usb->saved_msk = (USB_E_TXB_MASK | + USB_E_TXE1_MASK | + USB_E_IDLE_MASK | + USB_E_RESET_MASK | USB_E_SFT_MASK | USB_E_MSF_MASK); + + out_8(&usb->fhci->regs->usb_usmod, USB_MODE_HOST | USB_MODE_EN); + + /* clearing the mask register */ + out_be16(&usb->fhci->regs->usb_usbmr, 0); + + /* initialing the event register */ + out_be16(&usb->fhci->regs->usb_usber, 0xffff); + + if (endpoint_zero_init(usb, DEFAULT_DATA_MEM, DEFAULT_RING_LEN) != 0) { + fhci_usb_free(usb); + return -EINVAL; + } + + return 0; +} + +/* initialize the fhci_usb struct and the corresponding data staruct */ +static struct fhci_usb *fhci_create_lld(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb; + + /* allocate memory for SCC data structure */ + usb = kzalloc(sizeof(*usb), GFP_KERNEL); + if (!usb) { + fhci_err(fhci, "no memory for SCC data struct\n"); + return NULL; + } + + usb->fhci = fhci; + usb->hc_list = fhci->hc_list; + usb->vroot_hub = fhci->vroot_hub; + + usb->transfer_confirm = fhci_transfer_confirm_callback; + + return usb; +} + +static int fhci_start(struct usb_hcd *hcd) +{ + int ret; + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + ret = fhci_mem_init(fhci); + if (ret) { + fhci_err(fhci, "failed to allocate memory\n"); + goto err; + } + + fhci->usb_lld = fhci_create_lld(fhci); + if (!fhci->usb_lld) { + fhci_err(fhci, "low level driver config failed\n"); + ret = -ENOMEM; + goto err; + } + + ret = fhci_usb_init(fhci); + if (ret) { + fhci_err(fhci, "low level driver initialize failed\n"); + goto err; + } + + spin_lock_init(&fhci->lock); + + /* connect the virtual root hub */ + fhci->vroot_hub->dev_num = 1; /* this field may be needed to fix */ + fhci->vroot_hub->hub.wHubStatus = 0; + fhci->vroot_hub->hub.wHubChange = 0; + fhci->vroot_hub->port.wPortStatus = 0; + fhci->vroot_hub->port.wPortChange = 0; + + hcd->state = HC_STATE_RUNNING; + + /* + * From here on, khubd concurrently accesses the root + * hub; drivers will be talking to enumerated devices. + * (On restart paths, khubd already knows about the root + * hub and could find work as soon as we wrote FLAG_CF.) + * + * Before this point the HC was idle/ready. After, khubd + * and device drivers may start it running. + */ + fhci_usb_enable(fhci); + return 0; +err: + fhci_mem_free(fhci); + return ret; +} + +static void fhci_stop(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + fhci_usb_disable_interrupt(fhci->usb_lld); + fhci_usb_disable(fhci); + + fhci_usb_free(fhci->usb_lld); + fhci->usb_lld = NULL; + fhci_mem_free(fhci); +} + +static int fhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + u32 pipe = urb->pipe; + int ret; + int i; + int size = 0; + struct urb_priv *urb_priv; + unsigned long flags; + + switch (usb_pipetype(pipe)) { + case PIPE_CONTROL: + /* 1 td fro setup,1 for ack */ + size = 2; + case PIPE_BULK: + /* one td for every 4096 bytes(can be up to 8k) */ + size += urb->transfer_buffer_length / 4096; + /* ...add for any remaining bytes... */ + if ((urb->transfer_buffer_length % 4096) != 0) + size++; + /* ..and maybe a zero length packet to wrap it up */ + if (size == 0) + size++; + else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 + && (urb->transfer_buffer_length + % usb_maxpacket(urb->dev, pipe, + usb_pipeout(pipe))) != 0) + size++; + break; + case PIPE_ISOCHRONOUS: + size = urb->number_of_packets; + if (size <= 0) + return -EINVAL; + for (i = 0; i < urb->number_of_packets; i++) { + urb->iso_frame_desc[i].actual_length = 0; + urb->iso_frame_desc[i].status = (u32) (-EXDEV); + } + break; + case PIPE_INTERRUPT: + size = 1; + } + + /* allocate the private part of the URB */ + urb_priv = kzalloc(sizeof(*urb_priv), mem_flags); + if (!urb_priv) + return -ENOMEM; + + /* allocate the private part of the URB */ + urb_priv->tds = kcalloc(size, sizeof(*urb_priv->tds), mem_flags); + if (!urb_priv->tds) { + kfree(urb_priv); + return -ENOMEM; + } + + spin_lock_irqsave(&fhci->lock, flags); + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto err; + + /* fill the private part of the URB */ + urb_priv->num_of_tds = size; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->error_count = 0; + urb->hcpriv = urb_priv; + + fhci_queue_urb(fhci, urb); +err: + if (ret) { + kfree(urb_priv->tds); + kfree(urb_priv); + } + spin_unlock_irqrestore(&fhci->lock, flags); + return ret; +} + +/* dequeue FHCI URB */ +static int fhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb = fhci->usb_lld; + int ret = -EINVAL; + unsigned long flags; + + if (!urb || !urb->dev || !urb->dev->bus) + goto out; + + spin_lock_irqsave(&fhci->lock, flags); + + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) + goto out2; + + if (usb->port_status != FHCI_PORT_DISABLED) { + struct urb_priv *urb_priv; + + /* + * flag the urb's data for deletion in some upcoming + * SF interrupt's delete list processing + */ + urb_priv = urb->hcpriv; + + if (!urb_priv || (urb_priv->state == URB_DEL)) + goto out2; + + urb_priv->state = URB_DEL; + + /* already pending? */ + urb_priv->ed->state = FHCI_ED_URB_DEL; + } else { + fhci_urb_complete_free(fhci, urb); + } + +out2: + spin_unlock_irqrestore(&fhci->lock, flags); +out: + return ret; +} + +static void fhci_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fhci_hcd *fhci; + struct ed *ed; + unsigned long flags; + + fhci = hcd_to_fhci(hcd); + spin_lock_irqsave(&fhci->lock, flags); + ed = ep->hcpriv; + if (ed) { + while (ed->td_head != NULL) { + struct td *td = fhci_remove_td_from_ed(ed); + fhci_urb_complete_free(fhci, td->urb); + } + fhci_recycle_empty_ed(fhci, ed); + ep->hcpriv = NULL; + } + spin_unlock_irqrestore(&fhci->lock, flags); +} + +static int fhci_get_frame_number(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + return get_frame_num(fhci); +} + +static const struct hc_driver fhci_driver = { + .description = "fsl,usb-fhci", + .product_desc = "FHCI HOST Controller", + .hcd_priv_size = sizeof(struct fhci_hcd), + + /* generic hardware linkage */ + .irq = fhci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* basic lifecycle operation */ + .start = fhci_start, + .stop = fhci_stop, + + /* managing i/o requests and associated device resources */ + .urb_enqueue = fhci_urb_enqueue, + .urb_dequeue = fhci_urb_dequeue, + .endpoint_disable = fhci_endpoint_disable, + + /* scheduling support */ + .get_frame_number = fhci_get_frame_number, + + /* root hub support */ + .hub_status_data = fhci_hub_status_data, + .hub_control = fhci_hub_control, +}; + +static int of_fhci_probe(struct platform_device *ofdev) +{ + struct device *dev = &ofdev->dev; + struct device_node *node = dev->of_node; + struct usb_hcd *hcd; + struct fhci_hcd *fhci; + struct resource usb_regs; + unsigned long pram_addr; + unsigned int usb_irq; + const char *sprop; + const u32 *iprop; + int size; + int ret; + int i; + int j; + + if (usb_disabled()) + return -ENODEV; + + sprop = of_get_property(node, "mode", NULL); + if (sprop && strcmp(sprop, "host")) + return -ENODEV; + + hcd = usb_create_hcd(&fhci_driver, dev, dev_name(dev)); + if (!hcd) { + dev_err(dev, "could not create hcd\n"); + return -ENOMEM; + } + + fhci = hcd_to_fhci(hcd); + hcd->self.controller = dev; + dev_set_drvdata(dev, hcd); + + iprop = of_get_property(node, "hub-power-budget", &size); + if (iprop && size == sizeof(*iprop)) + hcd->power_budget = *iprop; + + /* FHCI registers. */ + ret = of_address_to_resource(node, 0, &usb_regs); + if (ret) { + dev_err(dev, "could not get regs\n"); + goto err_regs; + } + + hcd->regs = ioremap(usb_regs.start, resource_size(&usb_regs)); + if (!hcd->regs) { + dev_err(dev, "could not ioremap regs\n"); + ret = -ENOMEM; + goto err_regs; + } + fhci->regs = hcd->regs; + + /* Parameter RAM. */ + iprop = of_get_property(node, "reg", &size); + if (!iprop || size < sizeof(*iprop) * 4) { + dev_err(dev, "can't get pram offset\n"); + ret = -EINVAL; + goto err_pram; + } + + pram_addr = cpm_muram_alloc(FHCI_PRAM_SIZE, 64); + if (IS_ERR_VALUE(pram_addr)) { + dev_err(dev, "failed to allocate usb pram\n"); + ret = -ENOMEM; + goto err_pram; + } + + qe_issue_cmd(QE_ASSIGN_PAGE_TO_DEVICE, QE_CR_SUBBLOCK_USB, + QE_CR_PROTOCOL_UNSPECIFIED, pram_addr); + fhci->pram = cpm_muram_addr(pram_addr); + + /* GPIOs and pins */ + for (i = 0; i < NUM_GPIOS; i++) { + int gpio; + enum of_gpio_flags flags; + + gpio = of_get_gpio_flags(node, i, &flags); + fhci->gpios[i] = gpio; + fhci->alow_gpios[i] = flags & OF_GPIO_ACTIVE_LOW; + + if (!gpio_is_valid(gpio)) { + if (i < GPIO_SPEED) { + dev_err(dev, "incorrect GPIO%d: %d\n", + i, gpio); + goto err_gpios; + } else { + dev_info(dev, "assuming board doesn't have " + "%s gpio\n", i == GPIO_SPEED ? + "speed" : "power"); + continue; + } + } + + ret = gpio_request(gpio, dev_name(dev)); + if (ret) { + dev_err(dev, "failed to request gpio %d", i); + goto err_gpios; + } + + if (i >= GPIO_SPEED) { + ret = gpio_direction_output(gpio, 0); + if (ret) { + dev_err(dev, "failed to set gpio %d as " + "an output\n", i); + i++; + goto err_gpios; + } + } + } + + for (j = 0; j < NUM_PINS; j++) { + fhci->pins[j] = qe_pin_request(node, j); + if (IS_ERR(fhci->pins[j])) { + ret = PTR_ERR(fhci->pins[j]); + dev_err(dev, "can't get pin %d: %d\n", j, ret); + goto err_pins; + } + } + + /* Frame limit timer and its interrupt. */ + fhci->timer = gtm_get_timer16(); + if (IS_ERR(fhci->timer)) { + ret = PTR_ERR(fhci->timer); + dev_err(dev, "failed to request qe timer: %i", ret); + goto err_get_timer; + } + + ret = request_irq(fhci->timer->irq, fhci_frame_limit_timer_irq, + 0, "qe timer (usb)", hcd); + if (ret) { + dev_err(dev, "failed to request timer irq"); + goto err_timer_irq; + } + + /* USB Host interrupt. */ + usb_irq = irq_of_parse_and_map(node, 0); + if (usb_irq == NO_IRQ) { + dev_err(dev, "could not get usb irq\n"); + ret = -EINVAL; + goto err_usb_irq; + } + + /* Clocks. */ + sprop = of_get_property(node, "fsl,fullspeed-clock", NULL); + if (sprop) { + fhci->fullspeed_clk = qe_clock_source(sprop); + if (fhci->fullspeed_clk == QE_CLK_DUMMY) { + dev_err(dev, "wrong fullspeed-clock\n"); + ret = -EINVAL; + goto err_clocks; + } + } + + sprop = of_get_property(node, "fsl,lowspeed-clock", NULL); + if (sprop) { + fhci->lowspeed_clk = qe_clock_source(sprop); + if (fhci->lowspeed_clk == QE_CLK_DUMMY) { + dev_err(dev, "wrong lowspeed-clock\n"); + ret = -EINVAL; + goto err_clocks; + } + } + + if (fhci->fullspeed_clk == QE_CLK_NONE && + fhci->lowspeed_clk == QE_CLK_NONE) { + dev_err(dev, "no clocks specified\n"); + ret = -EINVAL; + goto err_clocks; + } + + dev_info(dev, "at 0x%p, irq %d\n", hcd->regs, usb_irq); + + fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF); + + /* Start with full-speed, if possible. */ + if (fhci->fullspeed_clk != QE_CLK_NONE) { + fhci_config_transceiver(fhci, FHCI_PORT_FULL); + qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK); + } else { + fhci_config_transceiver(fhci, FHCI_PORT_LOW); + qe_usb_clock_set(fhci->lowspeed_clk, USB_CLOCK >> 3); + } + + /* Clear and disable any pending interrupts. */ + out_be16(&fhci->regs->usb_usber, 0xffff); + out_be16(&fhci->regs->usb_usbmr, 0); + + ret = usb_add_hcd(hcd, usb_irq, 0); + if (ret < 0) + goto err_add_hcd; + + fhci_dfs_create(fhci); + + return 0; + +err_add_hcd: +err_clocks: + irq_dispose_mapping(usb_irq); +err_usb_irq: + free_irq(fhci->timer->irq, hcd); +err_timer_irq: + gtm_put_timer16(fhci->timer); +err_get_timer: +err_pins: + while (--j >= 0) + qe_pin_free(fhci->pins[j]); +err_gpios: + while (--i >= 0) { + if (gpio_is_valid(fhci->gpios[i])) + gpio_free(fhci->gpios[i]); + } + cpm_muram_free(pram_addr); +err_pram: + iounmap(hcd->regs); +err_regs: + usb_put_hcd(hcd); + return ret; +} + +static int fhci_remove(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int i; + int j; + + usb_remove_hcd(hcd); + free_irq(fhci->timer->irq, hcd); + gtm_put_timer16(fhci->timer); + cpm_muram_free(cpm_muram_offset(fhci->pram)); + for (i = 0; i < NUM_GPIOS; i++) { + if (!gpio_is_valid(fhci->gpios[i])) + continue; + gpio_free(fhci->gpios[i]); + } + for (j = 0; j < NUM_PINS; j++) + qe_pin_free(fhci->pins[j]); + fhci_dfs_destroy(fhci); + usb_put_hcd(hcd); + return 0; +} + +static int of_fhci_remove(struct platform_device *ofdev) +{ + return fhci_remove(&ofdev->dev); +} + +static const struct of_device_id of_fhci_match[] = { + { .compatible = "fsl,mpc8323-qe-usb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_fhci_match); + +static struct platform_driver of_fhci_driver = { + .driver = { + .name = "fsl,usb-fhci", + .owner = THIS_MODULE, + .of_match_table = of_fhci_match, + }, + .probe = of_fhci_probe, + .remove = of_fhci_remove, +}; + +module_platform_driver(of_fhci_driver); + +MODULE_DESCRIPTION("USB Freescale Host Controller Interface Driver"); +MODULE_AUTHOR("Shlomi Gridish , " + "Jerry Huang , " + "Anton Vorontsov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/fhci-hub.c b/drivers/usb/host/fhci-hub.c new file mode 100644 index 00000000..6af2512f --- /dev/null +++ b/drivers/usb/host/fhci-hub.c @@ -0,0 +1,345 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fhci.h" + +/* virtual root hub specific descriptor */ +static u8 root_hub_des[] = { + 0x09, /* blength */ + 0x29, /* bDescriptorType;hub-descriptor */ + 0x01, /* bNbrPorts */ + 0x00, /* wHubCharacteristics */ + 0x00, + 0x01, /* bPwrOn2pwrGood;2ms */ + 0x00, /* bHubContrCurrent;0mA */ + 0x00, /* DeviceRemoveable */ + 0xff, /* PortPwrCtrlMask */ +}; + +static void fhci_gpio_set_value(struct fhci_hcd *fhci, int gpio_nr, bool on) +{ + int gpio = fhci->gpios[gpio_nr]; + bool alow = fhci->alow_gpios[gpio_nr]; + + if (!gpio_is_valid(gpio)) + return; + + gpio_set_value(gpio, on ^ alow); + mdelay(5); +} + +void fhci_config_transceiver(struct fhci_hcd *fhci, + enum fhci_port_status status) +{ + fhci_dbg(fhci, "-> %s: %d\n", __func__, status); + + switch (status) { + case FHCI_PORT_POWER_OFF: + fhci_gpio_set_value(fhci, GPIO_POWER, false); + break; + case FHCI_PORT_DISABLED: + case FHCI_PORT_WAITING: + fhci_gpio_set_value(fhci, GPIO_POWER, true); + break; + case FHCI_PORT_LOW: + fhci_gpio_set_value(fhci, GPIO_SPEED, false); + break; + case FHCI_PORT_FULL: + fhci_gpio_set_value(fhci, GPIO_SPEED, true); + break; + default: + WARN_ON(1); + break; + } + + fhci_dbg(fhci, "<- %s: %d\n", __func__, status); +} + +/* disable the USB port by clearing the EN bit in the USBMOD register */ +void fhci_port_disable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = (struct fhci_usb *)fhci->usb_lld; + enum fhci_port_status port_status; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_stop_sof_timer(fhci); + + fhci_flush_all_transmissions(usb); + + fhci_usb_disable_interrupt((struct fhci_usb *)fhci->usb_lld); + port_status = usb->port_status; + usb->port_status = FHCI_PORT_DISABLED; + + /* Enable IDLE since we want to know if something comes along */ + usb->saved_msk |= USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_usbmr, usb->saved_msk); + + /* check if during the disconnection process attached new device */ + if (port_status == FHCI_PORT_WAITING) + fhci_device_connected_interrupt(fhci); + usb->vroot_hub->port.wPortStatus &= ~USB_PORT_STAT_ENABLE; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_ENABLE; + fhci_usb_enable_interrupt((struct fhci_usb *)fhci->usb_lld); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* enable the USB port by setting the EN bit in the USBMOD register */ +void fhci_port_enable(void *lld) +{ + struct fhci_usb *usb = (struct fhci_usb *)lld; + struct fhci_hcd *fhci = usb->fhci; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_config_transceiver(fhci, usb->port_status); + + if ((usb->port_status != FHCI_PORT_FULL) && + (usb->port_status != FHCI_PORT_LOW)) + fhci_start_sof_timer(fhci); + + usb->vroot_hub->port.wPortStatus |= USB_PORT_STAT_ENABLE; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_ENABLE; + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +void fhci_io_port_generate_reset(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + gpio_direction_output(fhci->gpios[GPIO_USBOE], 0); + gpio_direction_output(fhci->gpios[GPIO_USBTP], 0); + gpio_direction_output(fhci->gpios[GPIO_USBTN], 0); + + mdelay(5); + + qe_pin_set_dedicated(fhci->pins[PIN_USBOE]); + qe_pin_set_dedicated(fhci->pins[PIN_USBTP]); + qe_pin_set_dedicated(fhci->pins[PIN_USBTN]); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* generate the RESET condition on the bus */ +void fhci_port_reset(void *lld) +{ + struct fhci_usb *usb = (struct fhci_usb *)lld; + struct fhci_hcd *fhci = usb->fhci; + u8 mode; + u16 mask; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_stop_sof_timer(fhci); + /* disable the USB controller */ + mode = in_8(&fhci->regs->usb_usmod); + out_8(&fhci->regs->usb_usmod, mode & (~USB_MODE_EN)); + + /* disable idle interrupts */ + mask = in_be16(&fhci->regs->usb_usbmr); + out_be16(&fhci->regs->usb_usbmr, mask & (~USB_E_IDLE_MASK)); + + fhci_io_port_generate_reset(fhci); + + /* enable interrupt on this endpoint */ + out_be16(&fhci->regs->usb_usbmr, mask); + + /* enable the USB controller */ + mode = in_8(&fhci->regs->usb_usmod); + out_8(&fhci->regs->usb_usmod, mode | USB_MODE_EN); + fhci_start_sof_timer(fhci); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +int fhci_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int ret = 0; + unsigned long flags; + + fhci_dbg(fhci, "-> %s\n", __func__); + + spin_lock_irqsave(&fhci->lock, flags); + + if (fhci->vroot_hub->port.wPortChange & (USB_PORT_STAT_C_CONNECTION | + USB_PORT_STAT_C_ENABLE | USB_PORT_STAT_C_SUSPEND | + USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_OVERCURRENT)) { + *buf = 1 << 1; + ret = 1; + fhci_dbg(fhci, "-- %s\n", __func__); + } + + spin_unlock_irqrestore(&fhci->lock, flags); + + fhci_dbg(fhci, "<- %s\n", __func__); + + return ret; +} + +int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int retval = 0; + int len = 0; + struct usb_hub_status *hub_status; + struct usb_port_status *port_status; + unsigned long flags; + + spin_lock_irqsave(&fhci->lock, flags); + + fhci_dbg(fhci, "-> %s\n", __func__); + + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + break; + default: + goto error; + } + break; + case ClearPortFeature: + fhci->vroot_hub->feature &= (1 << wValue); + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_ENABLE; + fhci_port_disable(fhci); + break; + case USB_PORT_FEAT_C_ENABLE: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_ENABLE; + break; + case USB_PORT_FEAT_SUSPEND: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_SUSPEND; + fhci_stop_sof_timer(fhci); + break; + case USB_PORT_FEAT_C_SUSPEND: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_SUSPEND; + break; + case USB_PORT_FEAT_POWER: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_POWER; + fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF); + break; + case USB_PORT_FEAT_C_CONNECTION: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_CONNECTION; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_OVERCURRENT; + break; + case USB_PORT_FEAT_C_RESET: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_RESET; + break; + default: + goto error; + } + break; + case GetHubDescriptor: + memcpy(buf, root_hub_des, sizeof(root_hub_des)); + buf[3] = 0x11; /* per-port power, no ovrcrnt */ + len = (buf[0] < wLength) ? buf[0] : wLength; + break; + case GetHubStatus: + hub_status = (struct usb_hub_status *)buf; + hub_status->wHubStatus = + cpu_to_le16(fhci->vroot_hub->hub.wHubStatus); + hub_status->wHubChange = + cpu_to_le16(fhci->vroot_hub->hub.wHubChange); + len = 4; + break; + case GetPortStatus: + port_status = (struct usb_port_status *)buf; + port_status->wPortStatus = + cpu_to_le16(fhci->vroot_hub->port.wPortStatus); + port_status->wPortChange = + cpu_to_le16(fhci->vroot_hub->port.wPortChange); + len = 4; + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case SetPortFeature: + fhci->vroot_hub->feature |= (1 << wValue); + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_ENABLE; + fhci_port_enable(fhci->usb_lld); + break; + case USB_PORT_FEAT_SUSPEND: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_SUSPEND; + fhci_stop_sof_timer(fhci); + break; + case USB_PORT_FEAT_RESET: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_RESET; + fhci_port_reset(fhci->usb_lld); + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_ENABLE; + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_RESET; + break; + case USB_PORT_FEAT_POWER: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_POWER; + fhci_config_transceiver(fhci, FHCI_PORT_WAITING); + break; + default: + goto error; + } + break; + default: +error: + retval = -EPIPE; + } + + fhci_dbg(fhci, "<- %s\n", __func__); + + spin_unlock_irqrestore(&fhci->lock, flags); + + return retval; +} diff --git a/drivers/usb/host/fhci-mem.c b/drivers/usb/host/fhci-mem.c new file mode 100644 index 00000000..b0b88f57 --- /dev/null +++ b/drivers/usb/host/fhci-mem.c @@ -0,0 +1,114 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "fhci.h" + +static void init_td(struct td *td) +{ + memset(td, 0, sizeof(*td)); + INIT_LIST_HEAD(&td->node); + INIT_LIST_HEAD(&td->frame_lh); +} + +static void init_ed(struct ed *ed) +{ + memset(ed, 0, sizeof(*ed)); + INIT_LIST_HEAD(&ed->td_list); + INIT_LIST_HEAD(&ed->node); +} + +static struct td *get_empty_td(struct fhci_hcd *fhci) +{ + struct td *td; + + if (!list_empty(&fhci->empty_tds)) { + td = list_entry(fhci->empty_tds.next, struct td, node); + list_del(fhci->empty_tds.next); + } else { + td = kmalloc(sizeof(*td), GFP_ATOMIC); + if (!td) + fhci_err(fhci, "No memory to allocate to TD\n"); + else + init_td(td); + } + + return td; +} + +void fhci_recycle_empty_td(struct fhci_hcd *fhci, struct td *td) +{ + init_td(td); + list_add(&td->node, &fhci->empty_tds); +} + +struct ed *fhci_get_empty_ed(struct fhci_hcd *fhci) +{ + struct ed *ed; + + if (!list_empty(&fhci->empty_eds)) { + ed = list_entry(fhci->empty_eds.next, struct ed, node); + list_del(fhci->empty_eds.next); + } else { + ed = kmalloc(sizeof(*ed), GFP_ATOMIC); + if (!ed) + fhci_err(fhci, "No memory to allocate to ED\n"); + else + init_ed(ed); + } + + return ed; +} + +void fhci_recycle_empty_ed(struct fhci_hcd *fhci, struct ed *ed) +{ + init_ed(ed); + list_add(&ed->node, &fhci->empty_eds); +} + +struct td *fhci_td_fill(struct fhci_hcd *fhci, struct urb *urb, + struct urb_priv *urb_priv, struct ed *ed, u16 index, + enum fhci_ta_type type, int toggle, u8 *data, u32 len, + u16 interval, u16 start_frame, bool ioc) +{ + struct td *td = get_empty_td(fhci); + + if (!td) + return NULL; + + td->urb = urb; + td->ed = ed; + td->type = type; + td->toggle = toggle; + td->data = data; + td->len = len; + td->iso_index = index; + td->interval = interval; + td->start_frame = start_frame; + td->ioc = ioc; + td->status = USB_TD_OK; + + urb_priv->tds[index] = td; + + return td; +} diff --git a/drivers/usb/host/fhci-q.c b/drivers/usb/host/fhci-q.c new file mode 100644 index 00000000..03be7494 --- /dev/null +++ b/drivers/usb/host/fhci-q.c @@ -0,0 +1,285 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "fhci.h" + +/* maps the hardware error code to the USB error code */ +static int status_to_error(u32 status) +{ + if (status == USB_TD_OK) + return 0; + else if (status & USB_TD_RX_ER_CRC) + return -EILSEQ; + else if (status & USB_TD_RX_ER_NONOCT) + return -EPROTO; + else if (status & USB_TD_RX_ER_OVERUN) + return -ECOMM; + else if (status & USB_TD_RX_ER_BITSTUFF) + return -EPROTO; + else if (status & USB_TD_RX_ER_PID) + return -EILSEQ; + else if (status & (USB_TD_TX_ER_NAK | USB_TD_TX_ER_TIMEOUT)) + return -ETIMEDOUT; + else if (status & USB_TD_TX_ER_STALL) + return -EPIPE; + else if (status & USB_TD_TX_ER_UNDERUN) + return -ENOSR; + else if (status & USB_TD_RX_DATA_UNDERUN) + return -EREMOTEIO; + else if (status & USB_TD_RX_DATA_OVERUN) + return -EOVERFLOW; + else + return -EINVAL; +} + +void fhci_add_td_to_frame(struct fhci_time_frame *frame, struct td *td) +{ + list_add_tail(&td->frame_lh, &frame->tds_list); +} + +void fhci_add_tds_to_ed(struct ed *ed, struct td **td_list, int number) +{ + int i; + + for (i = 0; i < number; i++) { + struct td *td = td_list[i]; + list_add_tail(&td->node, &ed->td_list); + } + if (ed->td_head == NULL) + ed->td_head = td_list[0]; +} + +static struct td *peek_td_from_ed(struct ed *ed) +{ + struct td *td; + + if (!list_empty(&ed->td_list)) + td = list_entry(ed->td_list.next, struct td, node); + else + td = NULL; + + return td; +} + +struct td *fhci_remove_td_from_frame(struct fhci_time_frame *frame) +{ + struct td *td; + + if (!list_empty(&frame->tds_list)) { + td = list_entry(frame->tds_list.next, struct td, frame_lh); + list_del_init(frame->tds_list.next); + } else + td = NULL; + + return td; +} + +struct td *fhci_peek_td_from_frame(struct fhci_time_frame *frame) +{ + struct td *td; + + if (!list_empty(&frame->tds_list)) + td = list_entry(frame->tds_list.next, struct td, frame_lh); + else + td = NULL; + + return td; +} + +struct td *fhci_remove_td_from_ed(struct ed *ed) +{ + struct td *td; + + if (!list_empty(&ed->td_list)) { + td = list_entry(ed->td_list.next, struct td, node); + list_del_init(ed->td_list.next); + + /* if this TD was the ED's head, find next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, + node); + else + ed->td_head = NULL; + } else + td = NULL; + + return td; +} + +struct td *fhci_remove_td_from_done_list(struct fhci_controller_list *p_list) +{ + struct td *td; + + if (!list_empty(&p_list->done_list)) { + td = list_entry(p_list->done_list.next, struct td, node); + list_del_init(p_list->done_list.next); + } else + td = NULL; + + return td; +} + +void fhci_move_td_from_ed_to_done_list(struct fhci_usb *usb, struct ed *ed) +{ + struct td *td; + + td = ed->td_head; + list_del_init(&td->node); + + /* If this TD was the ED's head,find next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, node); + else { + ed->td_head = NULL; + ed->state = FHCI_ED_SKIP; + } + ed->toggle_carry = td->toggle; + list_add_tail(&td->node, &usb->hc_list->done_list); + if (td->ioc) + usb->transfer_confirm(usb->fhci); +} + +/* free done FHCI URB resource such as ED and TD */ +static void free_urb_priv(struct fhci_hcd *fhci, struct urb *urb) +{ + int i; + struct urb_priv *urb_priv = urb->hcpriv; + struct ed *ed = urb_priv->ed; + + for (i = 0; i < urb_priv->num_of_tds; i++) { + list_del_init(&urb_priv->tds[i]->node); + fhci_recycle_empty_td(fhci, urb_priv->tds[i]); + } + + /* if this TD was the ED's head,find the next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, node); + else + ed->td_head = NULL; + + kfree(urb_priv->tds); + kfree(urb_priv); + urb->hcpriv = NULL; + + /* if this TD was the ED's head,find next TD */ + if (ed->td_head == NULL) + list_del_init(&ed->node); + fhci->active_urbs--; +} + +/* this routine called to complete and free done URB */ +void fhci_urb_complete_free(struct fhci_hcd *fhci, struct urb *urb) +{ + free_urb_priv(fhci, urb); + + if (urb->status == -EINPROGRESS) { + if (urb->actual_length != urb->transfer_buffer_length && + urb->transfer_flags & URB_SHORT_NOT_OK) + urb->status = -EREMOTEIO; + else + urb->status = 0; + } + + usb_hcd_unlink_urb_from_ep(fhci_to_hcd(fhci), urb); + + spin_unlock(&fhci->lock); + + usb_hcd_giveback_urb(fhci_to_hcd(fhci), urb, urb->status); + + spin_lock(&fhci->lock); +} + +/* + * caculate transfer length/stats and update the urb + * Precondition: irqsafe(only for urb-?status locking) + */ +void fhci_done_td(struct urb *urb, struct td *td) +{ + struct ed *ed = td->ed; + u32 cc = td->status; + + /* ISO...drivers see per-TD length/status */ + if (ed->mode == FHCI_TF_ISO) { + u32 len; + if (!(urb->transfer_flags & URB_SHORT_NOT_OK && + cc == USB_TD_RX_DATA_UNDERUN)) + cc = USB_TD_OK; + + if (usb_pipeout(urb->pipe)) + len = urb->iso_frame_desc[td->iso_index].length; + else + len = td->actual_len; + + urb->actual_length += len; + urb->iso_frame_desc[td->iso_index].actual_length = len; + urb->iso_frame_desc[td->iso_index].status = + status_to_error(cc); + } + + /* BULK,INT,CONTROL... drivers see aggregate length/status, + * except that "setup" bytes aren't counted and "short" transfers + * might not be reported as errors. + */ + else { + if (td->error_cnt >= 3) + urb->error_count = 3; + + /* control endpoint only have soft stalls */ + + /* update packet status if needed(short may be ok) */ + if (!(urb->transfer_flags & URB_SHORT_NOT_OK) && + cc == USB_TD_RX_DATA_UNDERUN) { + ed->state = FHCI_ED_OPER; + cc = USB_TD_OK; + } + if (cc != USB_TD_OK) { + if (urb->status == -EINPROGRESS) + urb->status = status_to_error(cc); + } + + /* count all non-empty packets except control SETUP packet */ + if (td->type != FHCI_TA_SETUP || td->iso_index != 0) + urb->actual_length += td->actual_len; + } +} + +/* there are some pedning request to unlink */ +void fhci_del_ed_list(struct fhci_hcd *fhci, struct ed *ed) +{ + struct td *td = peek_td_from_ed(ed); + struct urb *urb = td->urb; + struct urb_priv *urb_priv = urb->hcpriv; + + if (urb_priv->state == URB_DEL) { + td = fhci_remove_td_from_ed(ed); + /* HC may have partly processed this TD */ + if (td->status != USB_TD_INPROGRESS) + fhci_done_td(urb, td); + + /* URB is done;clean up */ + if (++(urb_priv->tds_cnt) == urb_priv->num_of_tds) + fhci_urb_complete_free(fhci, urb); + } +} diff --git a/drivers/usb/host/fhci-sched.c b/drivers/usb/host/fhci-sched.c new file mode 100644 index 00000000..8f18538e --- /dev/null +++ b/drivers/usb/host/fhci-sched.c @@ -0,0 +1,894 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006, 2011. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fhci.h" + +static void recycle_frame(struct fhci_usb *usb, struct packet *pkt) +{ + pkt->data = NULL; + pkt->len = 0; + pkt->status = USB_TD_OK; + pkt->info = 0; + pkt->priv_data = NULL; + + cq_put(&usb->ep0->empty_frame_Q, pkt); +} + +/* confirm submitted packet */ +void fhci_transaction_confirm(struct fhci_usb *usb, struct packet *pkt) +{ + struct td *td; + struct packet *td_pkt; + struct ed *ed; + u32 trans_len; + bool td_done = false; + + td = fhci_remove_td_from_frame(usb->actual_frame); + td_pkt = td->pkt; + trans_len = pkt->len; + td->status = pkt->status; + if (td->type == FHCI_TA_IN && td_pkt->info & PKT_DUMMY_PACKET) { + if ((td->data + td->actual_len) && trans_len) + memcpy(td->data + td->actual_len, pkt->data, + trans_len); + cq_put(&usb->ep0->dummy_packets_Q, pkt->data); + } + + recycle_frame(usb, pkt); + + ed = td->ed; + if (ed->mode == FHCI_TF_ISO) { + if (ed->td_list.next->next != &ed->td_list) { + struct td *td_next = + list_entry(ed->td_list.next->next, struct td, + node); + + td_next->start_frame = usb->actual_frame->frame_num; + } + td->actual_len = trans_len; + td_done = true; + } else if ((td->status & USB_TD_ERROR) && + !(td->status & USB_TD_TX_ER_NAK)) { + /* + * There was an error on the transaction (but not NAK). + * If it is fatal error (data underrun, stall, bad pid or 3 + * errors exceeded), mark this TD as done. + */ + if ((td->status & USB_TD_RX_DATA_UNDERUN) || + (td->status & USB_TD_TX_ER_STALL) || + (td->status & USB_TD_RX_ER_PID) || + (++td->error_cnt >= 3)) { + ed->state = FHCI_ED_HALTED; + td_done = true; + + if (td->status & USB_TD_RX_DATA_UNDERUN) { + fhci_dbg(usb->fhci, "td err fu\n"); + td->toggle = !td->toggle; + td->actual_len += trans_len; + } else { + fhci_dbg(usb->fhci, "td err f!u\n"); + } + } else { + fhci_dbg(usb->fhci, "td err !f\n"); + /* it is not a fatal error -retry this transaction */ + td->nak_cnt = 0; + td->error_cnt++; + td->status = USB_TD_OK; + } + } else if (td->status & USB_TD_TX_ER_NAK) { + /* there was a NAK response */ + fhci_vdbg(usb->fhci, "td nack\n"); + td->nak_cnt++; + td->error_cnt = 0; + td->status = USB_TD_OK; + } else { + /* there was no error on transaction */ + td->error_cnt = 0; + td->nak_cnt = 0; + td->toggle = !td->toggle; + td->actual_len += trans_len; + + if (td->len == td->actual_len) + td_done = true; + } + + if (td_done) + fhci_move_td_from_ed_to_done_list(usb, ed); +} + +/* + * Flush all transmitted packets from BDs + * This routine is called when disabling the USB port to flush all + * transmissions that are already scheduled in the BDs + */ +void fhci_flush_all_transmissions(struct fhci_usb *usb) +{ + u8 mode; + struct td *td; + + mode = in_8(&usb->fhci->regs->usb_usmod); + clrbits8(&usb->fhci->regs->usb_usmod, USB_MODE_EN); + + fhci_flush_bds(usb); + + while ((td = fhci_peek_td_from_frame(usb->actual_frame)) != NULL) { + struct packet *pkt = td->pkt; + + pkt->status = USB_TD_TX_ER_TIMEOUT; + fhci_transaction_confirm(usb, pkt); + } + + usb->actual_frame->frame_status = FRAME_END_TRANSMISSION; + + /* reset the event register */ + out_be16(&usb->fhci->regs->usb_usber, 0xffff); + /* enable the USB controller */ + out_8(&usb->fhci->regs->usb_usmod, mode | USB_MODE_EN); +} + +/* + * This function forms the packet and transmit the packet. This function + * will handle all endpoint type:ISO,interrupt,control and bulk + */ +static int add_packet(struct fhci_usb *usb, struct ed *ed, struct td *td) +{ + u32 fw_transaction_time, len = 0; + struct packet *pkt; + u8 *data = NULL; + + /* calcalate data address,len and toggle and then add the transaction */ + if (td->toggle == USB_TD_TOGGLE_CARRY) + td->toggle = ed->toggle_carry; + + switch (ed->mode) { + case FHCI_TF_ISO: + len = td->len; + if (td->type != FHCI_TA_IN) + data = td->data; + break; + case FHCI_TF_CTRL: + case FHCI_TF_BULK: + len = min(td->len - td->actual_len, ed->max_pkt_size); + if (!((td->type == FHCI_TA_IN) && + ((len + td->actual_len) == td->len))) + data = td->data + td->actual_len; + break; + case FHCI_TF_INTR: + len = min(td->len, ed->max_pkt_size); + if (!((td->type == FHCI_TA_IN) && + ((td->len + CRC_SIZE) >= ed->max_pkt_size))) + data = td->data; + break; + default: + break; + } + + if (usb->port_status == FHCI_PORT_FULL) + fw_transaction_time = (((len + PROTOCOL_OVERHEAD) * 11) >> 4); + else + fw_transaction_time = ((len + PROTOCOL_OVERHEAD) * 6); + + /* check if there's enough space in this frame to submit this TD */ + if (usb->actual_frame->total_bytes + len + PROTOCOL_OVERHEAD >= + usb->max_bytes_per_frame) { + fhci_vdbg(usb->fhci, "not enough space in this frame: " + "%d %d %d\n", usb->actual_frame->total_bytes, len, + usb->max_bytes_per_frame); + return -1; + } + + /* check if there's enough time in this frame to submit this TD */ + if (usb->actual_frame->frame_status != FRAME_IS_PREPARED && + (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION || + (fw_transaction_time + usb->sw_transaction_time >= + 1000 - fhci_get_sof_timer_count(usb)))) { + fhci_dbg(usb->fhci, "not enough time in this frame\n"); + return -1; + } + + /* update frame object fields before transmitting */ + pkt = cq_get(&usb->ep0->empty_frame_Q); + if (!pkt) { + fhci_dbg(usb->fhci, "there is no empty frame\n"); + return -1; + } + td->pkt = pkt; + + pkt->info = 0; + if (data == NULL) { + data = cq_get(&usb->ep0->dummy_packets_Q); + BUG_ON(!data); + pkt->info = PKT_DUMMY_PACKET; + } + pkt->data = data; + pkt->len = len; + pkt->status = USB_TD_OK; + /* update TD status field before transmitting */ + td->status = USB_TD_INPROGRESS; + /* update actual frame time object with the actual transmission */ + usb->actual_frame->total_bytes += (len + PROTOCOL_OVERHEAD); + fhci_add_td_to_frame(usb->actual_frame, td); + + if (usb->port_status != FHCI_PORT_FULL && + usb->port_status != FHCI_PORT_LOW) { + pkt->status = USB_TD_TX_ER_TIMEOUT; + pkt->len = 0; + fhci_transaction_confirm(usb, pkt); + } else if (fhci_host_transaction(usb, pkt, td->type, ed->dev_addr, + ed->ep_addr, ed->mode, ed->speed, td->toggle)) { + /* remove TD from actual frame */ + list_del_init(&td->frame_lh); + td->status = USB_TD_OK; + if (pkt->info & PKT_DUMMY_PACKET) + cq_put(&usb->ep0->dummy_packets_Q, pkt->data); + recycle_frame(usb, pkt); + usb->actual_frame->total_bytes -= (len + PROTOCOL_OVERHEAD); + fhci_err(usb->fhci, "host transaction failed\n"); + return -1; + } + + return len; +} + +static void move_head_to_tail(struct list_head *list) +{ + struct list_head *node = list->next; + + if (!list_empty(list)) { + list_move_tail(node, list); + } +} + +/* + * This function goes through the endpoint list and schedules the + * transactions within this list + */ +static int scan_ed_list(struct fhci_usb *usb, + struct list_head *list, enum fhci_tf_mode list_type) +{ + static const int frame_part[4] = { + [FHCI_TF_CTRL] = MAX_BYTES_PER_FRAME, + [FHCI_TF_ISO] = (MAX_BYTES_PER_FRAME * + MAX_PERIODIC_FRAME_USAGE) / 100, + [FHCI_TF_BULK] = MAX_BYTES_PER_FRAME, + [FHCI_TF_INTR] = (MAX_BYTES_PER_FRAME * + MAX_PERIODIC_FRAME_USAGE) / 100 + }; + struct ed *ed; + struct td *td; + int ans = 1; + u32 save_transaction_time = usb->sw_transaction_time; + + list_for_each_entry(ed, list, node) { + td = ed->td_head; + + if (!td || (td && td->status == USB_TD_INPROGRESS)) + continue; + + if (ed->state != FHCI_ED_OPER) { + if (ed->state == FHCI_ED_URB_DEL) { + td->status = USB_TD_OK; + fhci_move_td_from_ed_to_done_list(usb, ed); + ed->state = FHCI_ED_SKIP; + } + continue; + } + + /* + * if it isn't interrupt pipe or it is not iso pipe and the + * interval time passed + */ + if ((list_type == FHCI_TF_INTR || list_type == FHCI_TF_ISO) && + (((usb->actual_frame->frame_num - + td->start_frame) & 0x7ff) < td->interval)) + continue; + + if (add_packet(usb, ed, td) < 0) + continue; + + /* update time stamps in the TD */ + td->start_frame = usb->actual_frame->frame_num; + usb->sw_transaction_time += save_transaction_time; + + if (usb->actual_frame->total_bytes >= + usb->max_bytes_per_frame) { + usb->actual_frame->frame_status = + FRAME_DATA_END_TRANSMISSION; + fhci_push_dummy_bd(usb->ep0); + ans = 0; + break; + } + + if (usb->actual_frame->total_bytes >= frame_part[list_type]) + break; + } + + /* be fair to each ED(move list head around) */ + move_head_to_tail(list); + usb->sw_transaction_time = save_transaction_time; + + return ans; +} + +static u32 rotate_frames(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (!list_empty(&usb->actual_frame->tds_list)) { + if ((((in_be16(&fhci->pram->frame_num) & 0x07ff) - + usb->actual_frame->frame_num) & 0x7ff) > 5) + fhci_flush_actual_frame(usb); + else + return -EINVAL; + } + + usb->actual_frame->frame_status = FRAME_IS_PREPARED; + usb->actual_frame->frame_num = in_be16(&fhci->pram->frame_num) & 0x7ff; + usb->actual_frame->total_bytes = 0; + + return 0; +} + +/* + * This function schedule the USB transaction and will process the + * endpoint in the following order: iso, interrupt, control and bulk. + */ +void fhci_schedule_transactions(struct fhci_usb *usb) +{ + int left = 1; + + if (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION) + if (rotate_frames(usb) != 0) + return; + + if (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION) + return; + + if (usb->actual_frame->total_bytes == 0) { + /* + * schedule the next available ISO transfer + *or next stage of the ISO transfer + */ + scan_ed_list(usb, &usb->hc_list->iso_list, FHCI_TF_ISO); + + /* + * schedule the next available interrupt transfer or + * the next stage of the interrupt transfer + */ + scan_ed_list(usb, &usb->hc_list->intr_list, FHCI_TF_INTR); + + /* + * schedule the next available control transfer + * or the next stage of the control transfer + */ + left = scan_ed_list(usb, &usb->hc_list->ctrl_list, + FHCI_TF_CTRL); + } + + /* + * schedule the next available bulk transfer or the next stage of the + * bulk transfer + */ + if (left > 0) + scan_ed_list(usb, &usb->hc_list->bulk_list, FHCI_TF_BULK); +} + +/* Handles SOF interrupt */ +static void sof_interrupt(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + if ((usb->port_status == FHCI_PORT_DISABLED) && + (usb->vroot_hub->port.wPortStatus & USB_PORT_STAT_CONNECTION) && + !(usb->vroot_hub->port.wPortChange & USB_PORT_STAT_C_CONNECTION)) { + if (usb->vroot_hub->port.wPortStatus & USB_PORT_STAT_LOW_SPEED) + usb->port_status = FHCI_PORT_LOW; + else + usb->port_status = FHCI_PORT_FULL; + /* Disable IDLE */ + usb->saved_msk &= ~USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_usbmr, usb->saved_msk); + } + + gtm_set_exact_timer16(fhci->timer, usb->max_frame_usage, false); + + fhci_host_transmit_actual_frame(usb); + usb->actual_frame->frame_status = FRAME_IS_TRANSMITTED; + + fhci_schedule_transactions(usb); +} + +/* Handles device disconnected interrupt on port */ +void fhci_device_disconnected_interrupt(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_usb_disable_interrupt(usb); + clrbits8(&usb->fhci->regs->usb_usmod, USB_MODE_LSS); + usb->port_status = FHCI_PORT_DISABLED; + + fhci_stop_sof_timer(fhci); + + /* Enable IDLE since we want to know if something comes along */ + usb->saved_msk |= USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_usbmr, usb->saved_msk); + + usb->vroot_hub->port.wPortStatus &= ~USB_PORT_STAT_CONNECTION; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = 0; + fhci_usb_enable_interrupt(usb); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* detect a new device connected on the USB port */ +void fhci_device_connected_interrupt(struct fhci_hcd *fhci) +{ + + struct fhci_usb *usb = fhci->usb_lld; + int state; + int ret; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_usb_disable_interrupt(usb); + state = fhci_ioports_check_bus_state(fhci); + + /* low-speed device was connected to the USB port */ + if (state == 1) { + ret = qe_usb_clock_set(fhci->lowspeed_clk, USB_CLOCK >> 3); + if (ret) { + fhci_warn(fhci, "Low-Speed device is not supported, " + "try use BRGx\n"); + goto out; + } + + usb->port_status = FHCI_PORT_LOW; + setbits8(&usb->fhci->regs->usb_usmod, USB_MODE_LSS); + usb->vroot_hub->port.wPortStatus |= + (USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_CONNECTION); + usb->vroot_hub->port.wPortChange |= + USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = + (MAX_BYTES_PER_FRAME >> 3) - 7; + fhci_port_enable(usb); + } else if (state == 2) { + ret = qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK); + if (ret) { + fhci_warn(fhci, "Full-Speed device is not supported, " + "try use CLKx\n"); + goto out; + } + + usb->port_status = FHCI_PORT_FULL; + clrbits8(&usb->fhci->regs->usb_usmod, USB_MODE_LSS); + usb->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_LOW_SPEED; + usb->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_CONNECTION; + usb->vroot_hub->port.wPortChange |= + USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = (MAX_BYTES_PER_FRAME - 15); + fhci_port_enable(usb); + } +out: + fhci_usb_enable_interrupt(usb); + fhci_dbg(fhci, "<- %s\n", __func__); +} + +irqreturn_t fhci_frame_limit_timer_irq(int irq, void *_hcd) +{ + struct usb_hcd *hcd = _hcd; + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb = fhci->usb_lld; + + spin_lock(&fhci->lock); + + gtm_set_exact_timer16(fhci->timer, 1000, false); + + if (usb->actual_frame->frame_status == FRAME_IS_TRANSMITTED) { + usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; + fhci_push_dummy_bd(usb->ep0); + } + + fhci_schedule_transactions(usb); + + spin_unlock(&fhci->lock); + + return IRQ_HANDLED; +} + +/* Cancel transmission on the USB endpoint */ +static void abort_transmission(struct fhci_usb *usb) +{ + fhci_dbg(usb->fhci, "-> %s\n", __func__); + /* issue stop Tx command */ + qe_issue_cmd(QE_USB_STOP_TX, QE_CR_SUBBLOCK_USB, EP_ZERO, 0); + /* flush Tx FIFOs */ + out_8(&usb->fhci->regs->usb_uscom, USB_CMD_FLUSH_FIFO | EP_ZERO); + udelay(1000); + /* reset Tx BDs */ + fhci_flush_bds(usb); + /* issue restart Tx command */ + qe_issue_cmd(QE_USB_RESTART_TX, QE_CR_SUBBLOCK_USB, EP_ZERO, 0); + fhci_dbg(usb->fhci, "<- %s\n", __func__); +} + +irqreturn_t fhci_irq(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb; + u16 usb_er = 0; + unsigned long flags; + + spin_lock_irqsave(&fhci->lock, flags); + + usb = fhci->usb_lld; + + usb_er |= in_be16(&usb->fhci->regs->usb_usber) & + in_be16(&usb->fhci->regs->usb_usbmr); + + /* clear event bits for next time */ + out_be16(&usb->fhci->regs->usb_usber, usb_er); + + fhci_dbg_isr(fhci, usb_er); + + if (usb_er & USB_E_RESET_MASK) { + if ((usb->port_status == FHCI_PORT_FULL) || + (usb->port_status == FHCI_PORT_LOW)) { + fhci_device_disconnected_interrupt(fhci); + usb_er &= ~USB_E_IDLE_MASK; + } else if (usb->port_status == FHCI_PORT_WAITING) { + usb->port_status = FHCI_PORT_DISCONNECTING; + + /* Turn on IDLE since we want to disconnect */ + usb->saved_msk |= USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_usber, + usb->saved_msk); + } else if (usb->port_status == FHCI_PORT_DISABLED) { + if (fhci_ioports_check_bus_state(fhci) == 1) + fhci_device_connected_interrupt(fhci); + } + usb_er &= ~USB_E_RESET_MASK; + } + + if (usb_er & USB_E_MSF_MASK) { + abort_transmission(fhci->usb_lld); + usb_er &= ~USB_E_MSF_MASK; + } + + if (usb_er & (USB_E_SOF_MASK | USB_E_SFT_MASK)) { + sof_interrupt(fhci); + usb_er &= ~(USB_E_SOF_MASK | USB_E_SFT_MASK); + } + + if (usb_er & USB_E_TXB_MASK) { + fhci_tx_conf_interrupt(fhci->usb_lld); + usb_er &= ~USB_E_TXB_MASK; + } + + if (usb_er & USB_E_TXE1_MASK) { + fhci_tx_conf_interrupt(fhci->usb_lld); + usb_er &= ~USB_E_TXE1_MASK; + } + + if (usb_er & USB_E_IDLE_MASK) { + if (usb->port_status == FHCI_PORT_DISABLED) { + usb_er &= ~USB_E_RESET_MASK; + fhci_device_connected_interrupt(fhci); + } else if (usb->port_status == + FHCI_PORT_DISCONNECTING) { + /* XXX usb->port_status = FHCI_PORT_WAITING; */ + /* Disable IDLE */ + usb->saved_msk &= ~USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_usbmr, + usb->saved_msk); + } else { + fhci_dbg_isr(fhci, -1); + } + + usb_er &= ~USB_E_IDLE_MASK; + } + + spin_unlock_irqrestore(&fhci->lock, flags); + + return IRQ_HANDLED; +} + + +/* + * Process normal completions(error or success) and clean the schedule. + * + * This is the main path for handing urbs back to drivers. The only other patth + * is process_del_list(),which unlinks URBs by scanning EDs,instead of scanning + * the (re-reversed) done list as this does. + */ +static void process_done_list(unsigned long data) +{ + struct urb *urb; + struct ed *ed; + struct td *td; + struct urb_priv *urb_priv; + struct fhci_hcd *fhci = (struct fhci_hcd *)data; + + disable_irq(fhci->timer->irq); + disable_irq(fhci_to_hcd(fhci)->irq); + spin_lock(&fhci->lock); + + td = fhci_remove_td_from_done_list(fhci->hc_list); + while (td != NULL) { + urb = td->urb; + urb_priv = urb->hcpriv; + ed = td->ed; + + /* update URB's length and status from TD */ + fhci_done_td(urb, td); + urb_priv->tds_cnt++; + + /* + * if all this urb's TDs are done, call complete() + * Interrupt transfers are the onley special case: + * they are reissued,until "deleted" by usb_unlink_urb + * (real work done in a SOF intr, by process_del_list) + */ + if (urb_priv->tds_cnt == urb_priv->num_of_tds) { + fhci_urb_complete_free(fhci, urb); + } else if (urb_priv->state == URB_DEL && + ed->state == FHCI_ED_SKIP) { + fhci_del_ed_list(fhci, ed); + ed->state = FHCI_ED_OPER; + } else if (ed->state == FHCI_ED_HALTED) { + urb_priv->state = URB_DEL; + ed->state = FHCI_ED_URB_DEL; + fhci_del_ed_list(fhci, ed); + ed->state = FHCI_ED_OPER; + } + + td = fhci_remove_td_from_done_list(fhci->hc_list); + } + + spin_unlock(&fhci->lock); + enable_irq(fhci->timer->irq); + enable_irq(fhci_to_hcd(fhci)->irq); +} + +DECLARE_TASKLET(fhci_tasklet, process_done_list, 0); + +/* transfer complted callback */ +u32 fhci_transfer_confirm_callback(struct fhci_hcd *fhci) +{ + if (!fhci->process_done_task->state) + tasklet_schedule(fhci->process_done_task); + return 0; +} + +/* + * adds urb to the endpoint descriptor list + * arguments: + * fhci data structure for the Low level host controller + * ep USB Host endpoint data structure + * urb USB request block data structure + */ +void fhci_queue_urb(struct fhci_hcd *fhci, struct urb *urb) +{ + struct ed *ed = urb->ep->hcpriv; + struct urb_priv *urb_priv = urb->hcpriv; + u32 data_len = urb->transfer_buffer_length; + int urb_state = 0; + int toggle = 0; + struct td *td; + u8 *data; + u16 cnt = 0; + + if (ed == NULL) { + ed = fhci_get_empty_ed(fhci); + ed->dev_addr = usb_pipedevice(urb->pipe); + ed->ep_addr = usb_pipeendpoint(urb->pipe); + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ed->mode = FHCI_TF_CTRL; + break; + case PIPE_BULK: + ed->mode = FHCI_TF_BULK; + break; + case PIPE_INTERRUPT: + ed->mode = FHCI_TF_INTR; + break; + case PIPE_ISOCHRONOUS: + ed->mode = FHCI_TF_ISO; + break; + default: + break; + } + ed->speed = (urb->dev->speed == USB_SPEED_LOW) ? + FHCI_LOW_SPEED : FHCI_FULL_SPEED; + ed->max_pkt_size = usb_maxpacket(urb->dev, + urb->pipe, usb_pipeout(urb->pipe)); + urb->ep->hcpriv = ed; + fhci_dbg(fhci, "new ep speed=%d max_pkt_size=%d\n", + ed->speed, ed->max_pkt_size); + } + + /* for ISO transfer calculate start frame index */ + if (ed->mode == FHCI_TF_ISO && urb->transfer_flags & URB_ISO_ASAP) + urb->start_frame = ed->td_head ? ed->last_iso + 1 : + get_frame_num(fhci); + + /* + * OHCI handles the DATA toggle itself,we just use the USB + * toggle bits + */ + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe))) + toggle = USB_TD_TOGGLE_CARRY; + else { + toggle = USB_TD_TOGGLE_DATA0; + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), 1); + } + + urb_priv->tds_cnt = 0; + urb_priv->ed = ed; + if (data_len > 0) + data = urb->transfer_buffer; + else + data = NULL; + + switch (ed->mode) { + case FHCI_TF_BULK: + if (urb->transfer_flags & URB_ZERO_PACKET && + urb->transfer_buffer_length > 0 && + ((urb->transfer_buffer_length % + usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe))) == 0)) + urb_state = US_BULK0; + while (data_len > 4096) { + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + cnt ? USB_TD_TOGGLE_CARRY : + toggle, + data, 4096, 0, 0, true); + data += 4096; + data_len -= 4096; + cnt++; + } + + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : FHCI_TA_IN, + cnt ? USB_TD_TOGGLE_CARRY : toggle, + data, data_len, 0, 0, true); + cnt++; + + if (urb->transfer_flags & URB_ZERO_PACKET && + cnt < urb_priv->num_of_tds) { + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_CARRY, NULL, 0, 0, 0, true); + cnt++; + } + break; + case FHCI_TF_INTR: + urb->start_frame = get_frame_num(fhci) + 1; + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : FHCI_TA_IN, + USB_TD_TOGGLE_DATA0, data, data_len, + urb->interval, urb->start_frame, true); + break; + case FHCI_TF_CTRL: + ed->dev_addr = usb_pipedevice(urb->pipe); + ed->max_pkt_size = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + /* setup stage */ + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, FHCI_TA_SETUP, + USB_TD_TOGGLE_DATA0, urb->setup_packet, 8, 0, 0, true); + + /* data stage */ + if (data_len > 0) { + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_DATA1, data, data_len, 0, 0, + true); + } + + /* status stage */ + if (data_len > 0) + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, + (usb_pipeout(urb->pipe) ? FHCI_TA_IN : + FHCI_TA_OUT), + USB_TD_TOGGLE_DATA1, data, 0, 0, 0, true); + else + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, + FHCI_TA_IN, + USB_TD_TOGGLE_DATA1, data, 0, 0, 0, true); + + urb_state = US_CTRL_SETUP; + break; + case FHCI_TF_ISO: + for (cnt = 0; cnt < urb->number_of_packets; cnt++) { + u16 frame = urb->start_frame; + + /* + * FIXME scheduling should handle frame counter + * roll-around ... exotic case (and OHCI has + * a 2^16 iso range, vs other HCs max of 2^10) + */ + frame += cnt * urb->interval; + frame &= 0x07ff; + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_DATA0, + data + urb->iso_frame_desc[cnt].offset, + urb->iso_frame_desc[cnt].length, + urb->interval, frame, true); + } + break; + default: + break; + } + + /* + * set the state of URB + * control pipe:3 states -- setup,data,status + * interrupt and bulk pipe:1 state -- data + */ + urb->pipe &= ~0x1f; + urb->pipe |= urb_state & 0x1f; + + urb_priv->state = URB_INPROGRESS; + + if (!ed->td_head) { + ed->state = FHCI_ED_OPER; + switch (ed->mode) { + case FHCI_TF_CTRL: + list_add(&ed->node, &fhci->hc_list->ctrl_list); + break; + case FHCI_TF_BULK: + list_add(&ed->node, &fhci->hc_list->bulk_list); + break; + case FHCI_TF_INTR: + list_add(&ed->node, &fhci->hc_list->intr_list); + break; + case FHCI_TF_ISO: + list_add(&ed->node, &fhci->hc_list->iso_list); + break; + default: + break; + } + } + + fhci_add_tds_to_ed(ed, urb_priv->tds, urb_priv->num_of_tds); + fhci->active_urbs++; +} diff --git a/drivers/usb/host/fhci-tds.c b/drivers/usb/host/fhci-tds.c new file mode 100644 index 00000000..1498061f --- /dev/null +++ b/drivers/usb/host/fhci-tds.c @@ -0,0 +1,626 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "fhci.h" + +#define DUMMY_BD_BUFFER 0xdeadbeef +#define DUMMY2_BD_BUFFER 0xbaadf00d + +/* Transaction Descriptors bits */ +#define TD_R 0x8000 /* ready bit */ +#define TD_W 0x2000 /* wrap bit */ +#define TD_I 0x1000 /* interrupt on completion */ +#define TD_L 0x0800 /* last */ +#define TD_TC 0x0400 /* transmit CRC */ +#define TD_CNF 0x0200 /* CNF - Must be always 1 */ +#define TD_LSP 0x0100 /* Low-speed transaction */ +#define TD_PID 0x00c0 /* packet id */ +#define TD_RXER 0x0020 /* Rx error or not */ + +#define TD_NAK 0x0010 /* No ack. */ +#define TD_STAL 0x0008 /* Stall received */ +#define TD_TO 0x0004 /* time out */ +#define TD_UN 0x0002 /* underrun */ +#define TD_NO 0x0010 /* Rx Non Octet Aligned Packet */ +#define TD_AB 0x0008 /* Frame Aborted */ +#define TD_CR 0x0004 /* CRC Error */ +#define TD_OV 0x0002 /* Overrun */ +#define TD_BOV 0x0001 /* Buffer Overrun */ + +#define TD_ERRORS (TD_NAK | TD_STAL | TD_TO | TD_UN | \ + TD_NO | TD_AB | TD_CR | TD_OV | TD_BOV) + +#define TD_PID_DATA0 0x0080 /* Data 0 toggle */ +#define TD_PID_DATA1 0x00c0 /* Data 1 toggle */ +#define TD_PID_TOGGLE 0x00c0 /* Data 0/1 toggle mask */ + +#define TD_TOK_SETUP 0x0000 +#define TD_TOK_OUT 0x4000 +#define TD_TOK_IN 0x8000 +#define TD_ISO 0x1000 +#define TD_ENDP 0x0780 +#define TD_ADDR 0x007f + +#define TD_ENDP_SHIFT 7 + +struct usb_td { + __be16 status; + __be16 length; + __be32 buf_ptr; + __be16 extra; + __be16 reserved; +}; + +static struct usb_td __iomem *next_bd(struct usb_td __iomem *base, + struct usb_td __iomem *td, + u16 status) +{ + if (status & TD_W) + return base; + else + return ++td; +} + +void fhci_push_dummy_bd(struct endpoint *ep) +{ + if (ep->already_pushed_dummy_bd == false) { + u16 td_status = in_be16(&ep->empty_td->status); + + out_be32(&ep->empty_td->buf_ptr, DUMMY_BD_BUFFER); + /* get the next TD in the ring */ + ep->empty_td = next_bd(ep->td_base, ep->empty_td, td_status); + ep->already_pushed_dummy_bd = true; + } +} + +/* destroy an USB endpoint */ +void fhci_ep0_free(struct fhci_usb *usb) +{ + struct endpoint *ep; + int size; + + ep = usb->ep0; + if (ep) { + if (ep->td_base) + cpm_muram_free(cpm_muram_offset(ep->td_base)); + + if (kfifo_initialized(&ep->conf_frame_Q)) { + size = cq_howmany(&ep->conf_frame_Q); + for (; size; size--) { + struct packet *pkt = cq_get(&ep->conf_frame_Q); + + kfree(pkt); + } + cq_delete(&ep->conf_frame_Q); + } + + if (kfifo_initialized(&ep->empty_frame_Q)) { + size = cq_howmany(&ep->empty_frame_Q); + for (; size; size--) { + struct packet *pkt = cq_get(&ep->empty_frame_Q); + + kfree(pkt); + } + cq_delete(&ep->empty_frame_Q); + } + + if (kfifo_initialized(&ep->dummy_packets_Q)) { + size = cq_howmany(&ep->dummy_packets_Q); + for (; size; size--) { + u8 *buff = cq_get(&ep->dummy_packets_Q); + + kfree(buff); + } + cq_delete(&ep->dummy_packets_Q); + } + + kfree(ep); + usb->ep0 = NULL; + } +} + +/* + * create the endpoint structure + * + * arguments: + * usb A pointer to the data structure of the USB + * data_mem The data memory partition(BUS) + * ring_len TD ring length + */ +u32 fhci_create_ep(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, + u32 ring_len) +{ + struct endpoint *ep; + struct usb_td __iomem *td; + unsigned long ep_offset; + char *err_for = "endpoint PRAM"; + int ep_mem_size; + u32 i; + + /* we need at least 3 TDs in the ring */ + if (!(ring_len > 2)) { + fhci_err(usb->fhci, "illegal TD ring length parameters\n"); + return -EINVAL; + } + + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + + ep_mem_size = ring_len * sizeof(*td) + sizeof(struct fhci_ep_pram); + ep_offset = cpm_muram_alloc(ep_mem_size, 32); + if (IS_ERR_VALUE(ep_offset)) + goto err; + ep->td_base = cpm_muram_addr(ep_offset); + + /* zero all queue pointers */ + if (cq_new(&ep->conf_frame_Q, ring_len + 2) || + cq_new(&ep->empty_frame_Q, ring_len + 2) || + cq_new(&ep->dummy_packets_Q, ring_len + 2)) { + err_for = "frame_queues"; + goto err; + } + + for (i = 0; i < (ring_len + 1); i++) { + struct packet *pkt; + u8 *buff; + + pkt = kmalloc(sizeof(*pkt), GFP_KERNEL); + if (!pkt) { + err_for = "frame"; + goto err; + } + + buff = kmalloc(1028 * sizeof(*buff), GFP_KERNEL); + if (!buff) { + kfree(pkt); + err_for = "buffer"; + goto err; + } + cq_put(&ep->empty_frame_Q, pkt); + cq_put(&ep->dummy_packets_Q, buff); + } + + /* we put the endpoint parameter RAM right behind the TD ring */ + ep->ep_pram_ptr = (void __iomem *)ep->td_base + sizeof(*td) * ring_len; + + ep->conf_td = ep->td_base; + ep->empty_td = ep->td_base; + + ep->already_pushed_dummy_bd = false; + + /* initialize tds */ + td = ep->td_base; + for (i = 0; i < ring_len; i++) { + out_be32(&td->buf_ptr, 0); + out_be16(&td->status, 0); + out_be16(&td->length, 0); + out_be16(&td->extra, 0); + td++; + } + td--; + out_be16(&td->status, TD_W); /* for last TD set Wrap bit */ + out_be16(&td->length, 0); + + /* endpoint structure has been created */ + usb->ep0 = ep; + + return 0; +err: + fhci_ep0_free(usb); + kfree(ep); + fhci_err(usb->fhci, "no memory for the %s\n", err_for); + return -ENOMEM; +} + +/* + * initialize the endpoint register according to the given parameters + * + * artuments: + * usb A pointer to the data strucutre of the USB + * ep A pointer to the endpoint structre + * data_mem The data memory partition(BUS) + */ +void fhci_init_ep_registers(struct fhci_usb *usb, struct endpoint *ep, + enum fhci_mem_alloc data_mem) +{ + u8 rt; + + /* set the endpoint registers according to the endpoint */ + out_be16(&usb->fhci->regs->usb_usep[0], + USB_TRANS_CTR | USB_EP_MF | USB_EP_RTE); + out_be16(&usb->fhci->pram->ep_ptr[0], + cpm_muram_offset(ep->ep_pram_ptr)); + + rt = (BUS_MODE_BO_BE | BUS_MODE_GBL); +#ifdef MULTI_DATA_BUS + if (data_mem == MEM_SECONDARY) + rt |= BUS_MODE_DTB; +#endif + out_8(&ep->ep_pram_ptr->rx_func_code, rt); + out_8(&ep->ep_pram_ptr->tx_func_code, rt); + out_be16(&ep->ep_pram_ptr->rx_buff_len, 1028); + out_be16(&ep->ep_pram_ptr->rx_base, 0); + out_be16(&ep->ep_pram_ptr->tx_base, cpm_muram_offset(ep->td_base)); + out_be16(&ep->ep_pram_ptr->rx_bd_ptr, 0); + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, cpm_muram_offset(ep->td_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); +} + +/* + * Collect the submitted frames and inform the application about them + * It is also preparing the TDs for new frames. If the Tx interrupts + * are disabled, the application should call that routine to get + * confirmation about the submitted frames. Otherwise, the routine is + * called from the interrupt service routine during the Tx interrupt. + * In that case the application is informed by calling the application + * specific 'fhci_transaction_confirm' routine + */ +static void fhci_td_transaction_confirm(struct fhci_usb *usb) +{ + struct endpoint *ep = usb->ep0; + struct packet *pkt; + struct usb_td __iomem *td; + u16 extra_data; + u16 td_status; + u16 td_length; + u32 buf; + + /* + * collect transmitted BDs from the chip. The routine clears all BDs + * with R bit = 0 and the pointer to data buffer is not NULL, that is + * BDs which point to the transmitted data buffer + */ + while (1) { + td = ep->conf_td; + td_status = in_be16(&td->status); + td_length = in_be16(&td->length); + buf = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + + /* check if the TD is empty */ + if (!(!(td_status & TD_R) && ((td_status & ~TD_W) || buf))) + break; + /* check if it is a dummy buffer */ + else if ((buf == DUMMY_BD_BUFFER) && !(td_status & ~TD_W)) + break; + + /* mark TD as empty */ + clrbits16(&td->status, ~TD_W); + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + /* advance the TD pointer */ + ep->conf_td = next_bd(ep->td_base, ep->conf_td, td_status); + + /* check if it is a dummy buffer(type2) */ + if ((buf == DUMMY2_BD_BUFFER) && !(td_status & ~TD_W)) + continue; + + pkt = cq_get(&ep->conf_frame_Q); + if (!pkt) + fhci_err(usb->fhci, "no frame to confirm\n"); + + if (td_status & TD_ERRORS) { + if (td_status & TD_RXER) { + if (td_status & TD_CR) + pkt->status = USB_TD_RX_ER_CRC; + else if (td_status & TD_AB) + pkt->status = USB_TD_RX_ER_BITSTUFF; + else if (td_status & TD_OV) + pkt->status = USB_TD_RX_ER_OVERUN; + else if (td_status & TD_BOV) + pkt->status = USB_TD_RX_DATA_OVERUN; + else if (td_status & TD_NO) + pkt->status = USB_TD_RX_ER_NONOCT; + else + fhci_err(usb->fhci, "illegal error " + "occurred\n"); + } else if (td_status & TD_NAK) + pkt->status = USB_TD_TX_ER_NAK; + else if (td_status & TD_TO) + pkt->status = USB_TD_TX_ER_TIMEOUT; + else if (td_status & TD_UN) + pkt->status = USB_TD_TX_ER_UNDERUN; + else if (td_status & TD_STAL) + pkt->status = USB_TD_TX_ER_STALL; + else + fhci_err(usb->fhci, "illegal error occurred\n"); + } else if ((extra_data & TD_TOK_IN) && + pkt->len > td_length - CRC_SIZE) { + pkt->status = USB_TD_RX_DATA_UNDERUN; + } + + if (extra_data & TD_TOK_IN) + pkt->len = td_length - CRC_SIZE; + else if (pkt->info & PKT_ZLP) + pkt->len = 0; + else + pkt->len = td_length; + + fhci_transaction_confirm(usb, pkt); + } +} + +/* + * Submitting a data frame to a specified endpoint of a USB device + * The frame is put in the driver's transmit queue for this endpoint + * + * Arguments: + * usb A pointer to the USB structure + * pkt A pointer to the user frame structure + * trans_type Transaction tyep - IN,OUT or SETUP + * dest_addr Device address - 0~127 + * dest_ep Endpoint number of the device - 0~16 + * trans_mode Pipe type - ISO,Interrupt,bulk or control + * dest_speed USB speed - Low speed or FULL speed + * data_toggle Data sequence toggle - 0 or 1 + */ +u32 fhci_host_transaction(struct fhci_usb *usb, + struct packet *pkt, + enum fhci_ta_type trans_type, + u8 dest_addr, + u8 dest_ep, + enum fhci_tf_mode trans_mode, + enum fhci_speed dest_speed, u8 data_toggle) +{ + struct endpoint *ep = usb->ep0; + struct usb_td __iomem *td; + u16 extra_data; + u16 td_status; + + fhci_usb_disable_interrupt(usb); + /* start from the next BD that should be filled */ + td = ep->empty_td; + td_status = in_be16(&td->status); + + if (td_status & TD_R && in_be16(&td->length)) { + /* if the TD is not free */ + fhci_usb_enable_interrupt(usb); + return -1; + } + + /* get the next TD in the ring */ + ep->empty_td = next_bd(ep->td_base, ep->empty_td, td_status); + fhci_usb_enable_interrupt(usb); + pkt->priv_data = td; + out_be32(&td->buf_ptr, virt_to_phys(pkt->data)); + /* sets up transaction parameters - addr,endp,dir,and type */ + extra_data = (dest_ep << TD_ENDP_SHIFT) | dest_addr; + switch (trans_type) { + case FHCI_TA_IN: + extra_data |= TD_TOK_IN; + break; + case FHCI_TA_OUT: + extra_data |= TD_TOK_OUT; + break; + case FHCI_TA_SETUP: + extra_data |= TD_TOK_SETUP; + break; + } + if (trans_mode == FHCI_TF_ISO) + extra_data |= TD_ISO; + out_be16(&td->extra, extra_data); + + /* sets up the buffer descriptor */ + td_status = ((td_status & TD_W) | TD_R | TD_L | TD_I | TD_CNF); + if (!(pkt->info & PKT_NO_CRC)) + td_status |= TD_TC; + + switch (trans_type) { + case FHCI_TA_IN: + if (data_toggle) + pkt->info |= PKT_PID_DATA1; + else + pkt->info |= PKT_PID_DATA0; + break; + default: + if (data_toggle) { + td_status |= TD_PID_DATA1; + pkt->info |= PKT_PID_DATA1; + } else { + td_status |= TD_PID_DATA0; + pkt->info |= PKT_PID_DATA0; + } + break; + } + + if ((dest_speed == FHCI_LOW_SPEED) && + (usb->port_status == FHCI_PORT_FULL)) + td_status |= TD_LSP; + + out_be16(&td->status, td_status); + + /* set up buffer length */ + if (trans_type == FHCI_TA_IN) + out_be16(&td->length, pkt->len + CRC_SIZE); + else + out_be16(&td->length, pkt->len); + + /* put the frame to the confirmation queue */ + cq_put(&ep->conf_frame_Q, pkt); + + if (cq_howmany(&ep->conf_frame_Q) == 1) + out_8(&usb->fhci->regs->usb_uscom, USB_CMD_STR_FIFO); + + return 0; +} + +/* Reset the Tx BD ring */ +void fhci_flush_bds(struct fhci_usb *usb) +{ + u16 extra_data; + u16 td_status; + u32 buf; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + td = ep->td_base; + while (1) { + td_status = in_be16(&td->status); + buf = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + + /* if the TD is not empty - we'll confirm it as Timeout */ + if (td_status & TD_R) + out_be16(&td->status, (td_status & ~TD_R) | TD_TO); + /* if this TD is dummy - let's skip this TD */ + else if (in_be32(&td->buf_ptr) == DUMMY_BD_BUFFER) + out_be32(&td->buf_ptr, DUMMY2_BD_BUFFER); + /* if this is the last TD - break */ + if (td_status & TD_W) + break; + + td++; + } + + fhci_td_transaction_confirm(usb); + + td = ep->td_base; + do { + out_be16(&td->status, 0); + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + td++; + } while (!(in_be16(&td->status) & TD_W)); + out_be16(&td->status, TD_W); /* for last TD set Wrap bit */ + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, + in_be16(&ep->ep_pram_ptr->tx_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); + out_be16(&ep->ep_pram_ptr->tx_cnt, 0); + ep->empty_td = ep->td_base; + ep->conf_td = ep->td_base; +} + +/* + * Flush all transmitted packets from TDs in the actual frame. + * This routine is called when something wrong with the controller and + * we want to get rid of the actual frame and start again next frame + */ +void fhci_flush_actual_frame(struct fhci_usb *usb) +{ + u8 mode; + u16 tb_ptr; + u16 extra_data; + u16 td_status; + u32 buf_ptr; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + /* disable the USB controller */ + mode = in_8(&usb->fhci->regs->usb_usmod); + out_8(&usb->fhci->regs->usb_usmod, mode & ~USB_MODE_EN); + + tb_ptr = in_be16(&ep->ep_pram_ptr->tx_bd_ptr); + td = cpm_muram_addr(tb_ptr); + td_status = in_be16(&td->status); + buf_ptr = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + do { + if (td_status & TD_R) { + out_be16(&td->status, (td_status & ~TD_R) | TD_TO); + } else { + out_be32(&td->buf_ptr, 0); + ep->already_pushed_dummy_bd = false; + break; + } + + /* advance the TD pointer */ + td = next_bd(ep->td_base, td, td_status); + td_status = in_be16(&td->status); + buf_ptr = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + } while ((td_status & TD_R) || buf_ptr); + + fhci_td_transaction_confirm(usb); + + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, + in_be16(&ep->ep_pram_ptr->tx_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); + out_be16(&ep->ep_pram_ptr->tx_cnt, 0); + ep->empty_td = ep->td_base; + ep->conf_td = ep->td_base; + + usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; + + /* reset the event register */ + out_be16(&usb->fhci->regs->usb_usber, 0xffff); + /* enable the USB controller */ + out_8(&usb->fhci->regs->usb_usmod, mode | USB_MODE_EN); +} + +/* handles Tx confirm and Tx error interrupt */ +void fhci_tx_conf_interrupt(struct fhci_usb *usb) +{ + fhci_td_transaction_confirm(usb); + + /* + * Schedule another transaction to this frame only if we have + * already confirmed all transaction in the frame. + */ + if (((fhci_get_sof_timer_count(usb) < usb->max_frame_usage) || + (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION)) && + (list_empty(&usb->actual_frame->tds_list))) + fhci_schedule_transactions(usb); +} + +void fhci_host_transmit_actual_frame(struct fhci_usb *usb) +{ + u16 tb_ptr; + u16 td_status; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + tb_ptr = in_be16(&ep->ep_pram_ptr->tx_bd_ptr); + td = cpm_muram_addr(tb_ptr); + + if (in_be32(&td->buf_ptr) == DUMMY_BD_BUFFER) { + struct usb_td __iomem *old_td = td; + + ep->already_pushed_dummy_bd = false; + td_status = in_be16(&td->status); + /* gets the next TD in the ring */ + td = next_bd(ep->td_base, td, td_status); + tb_ptr = cpm_muram_offset(td); + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, tb_ptr); + + /* start transmit only if we have something in the TDs */ + if (in_be16(&td->status) & TD_R) + out_8(&usb->fhci->regs->usb_uscom, USB_CMD_STR_FIFO); + + if (in_be32(&ep->conf_td->buf_ptr) == DUMMY_BD_BUFFER) { + out_be32(&old_td->buf_ptr, 0); + ep->conf_td = next_bd(ep->td_base, ep->conf_td, + td_status); + } else { + out_be32(&old_td->buf_ptr, DUMMY2_BD_BUFFER); + } + } +} diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h new file mode 100644 index 00000000..7cc1c32d --- /dev/null +++ b/drivers/usb/host/fhci.h @@ -0,0 +1,594 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __FHCI_H +#define __FHCI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_CLOCK 48000000 + +#define FHCI_PRAM_SIZE 0x100 + +#define MAX_EDS 32 +#define MAX_TDS 32 + + +/* CRC16 field size */ +#define CRC_SIZE 2 + +/* USB protocol overhead for each frame transmitted from the host */ +#define PROTOCOL_OVERHEAD 7 + +/* Packet structure, info field */ +#define PKT_PID_DATA0 0x80000000 /* PID - Data toggle zero */ +#define PKT_PID_DATA1 0x40000000 /* PID - Data toggle one */ +#define PKT_PID_SETUP 0x20000000 /* PID - Setup bit */ +#define PKT_SETUP_STATUS 0x10000000 /* Setup status bit */ +#define PKT_SETADDR_STATUS 0x08000000 /* Set address status bit */ +#define PKT_SET_HOST_LAST 0x04000000 /* Last data packet */ +#define PKT_HOST_DATA 0x02000000 /* Data packet */ +#define PKT_FIRST_IN_FRAME 0x01000000 /* First packet in the frame */ +#define PKT_TOKEN_FRAME 0x00800000 /* Token packet */ +#define PKT_ZLP 0x00400000 /* Zero length packet */ +#define PKT_IN_TOKEN_FRAME 0x00200000 /* IN token packet */ +#define PKT_OUT_TOKEN_FRAME 0x00100000 /* OUT token packet */ +#define PKT_SETUP_TOKEN_FRAME 0x00080000 /* SETUP token packet */ +#define PKT_STALL_FRAME 0x00040000 /* STALL packet */ +#define PKT_NACK_FRAME 0x00020000 /* NACK packet */ +#define PKT_NO_PID 0x00010000 /* No PID */ +#define PKT_NO_CRC 0x00008000 /* don't append CRC */ +#define PKT_HOST_COMMAND 0x00004000 /* Host command packet */ +#define PKT_DUMMY_PACKET 0x00002000 /* Dummy packet, used for mmm */ +#define PKT_LOW_SPEED_PACKET 0x00001000 /* Low-Speed packet */ + +#define TRANS_OK (0) +#define TRANS_INPROGRESS (-1) +#define TRANS_DISCARD (-2) +#define TRANS_FAIL (-3) + +#define PS_INT 0 +#define PS_DISCONNECTED 1 +#define PS_CONNECTED 2 +#define PS_READY 3 +#define PS_MISSING 4 + +/* Transfer Descriptor status field */ +#define USB_TD_OK 0x00000000 /* TD transmited or received ok */ +#define USB_TD_INPROGRESS 0x80000000 /* TD is being transmitted */ +#define USB_TD_RX_ER_NONOCT 0x40000000 /* Tx Non Octet Aligned Packet */ +#define USB_TD_RX_ER_BITSTUFF 0x20000000 /* Frame Aborted-Received pkt */ +#define USB_TD_RX_ER_CRC 0x10000000 /* CRC error */ +#define USB_TD_RX_ER_OVERUN 0x08000000 /* Over - run occurred */ +#define USB_TD_RX_ER_PID 0x04000000 /* wrong PID received */ +#define USB_TD_RX_DATA_UNDERUN 0x02000000 /* shorter than expected */ +#define USB_TD_RX_DATA_OVERUN 0x01000000 /* longer than expected */ +#define USB_TD_TX_ER_NAK 0x00800000 /* NAK handshake */ +#define USB_TD_TX_ER_STALL 0x00400000 /* STALL handshake */ +#define USB_TD_TX_ER_TIMEOUT 0x00200000 /* transmit time out */ +#define USB_TD_TX_ER_UNDERUN 0x00100000 /* transmit underrun */ + +#define USB_TD_ERROR (USB_TD_RX_ER_NONOCT | USB_TD_RX_ER_BITSTUFF | \ + USB_TD_RX_ER_CRC | USB_TD_RX_ER_OVERUN | USB_TD_RX_ER_PID | \ + USB_TD_RX_DATA_UNDERUN | USB_TD_RX_DATA_OVERUN | \ + USB_TD_TX_ER_NAK | USB_TD_TX_ER_STALL | \ + USB_TD_TX_ER_TIMEOUT | USB_TD_TX_ER_UNDERUN) + +/* Transfer Descriptor toggle field */ +#define USB_TD_TOGGLE_DATA0 0 +#define USB_TD_TOGGLE_DATA1 1 +#define USB_TD_TOGGLE_CARRY 2 + +/* #define MULTI_DATA_BUS */ + +/* Bus mode register RBMR/TBMR */ +#define BUS_MODE_GBL 0x20 /* Global snooping */ +#define BUS_MODE_BO 0x18 /* Byte ordering */ +#define BUS_MODE_BO_BE 0x10 /* Byte ordering - Big-endian */ +#define BUS_MODE_DTB 0x02 /* Data bus */ + +/* FHCI QE USB Register Description */ + +/* USB Mode Register bit define */ +#define USB_MODE_EN 0x01 +#define USB_MODE_HOST 0x02 +#define USB_MODE_TEST 0x04 +#define USB_MODE_SFTE 0x08 +#define USB_MODE_RESUME 0x40 +#define USB_MODE_LSS 0x80 + +/* USB Slave Address Register Mask */ +#define USB_SLVADDR_MASK 0x7F + +/* USB Endpoint register define */ +#define USB_EPNUM_MASK 0xF000 +#define USB_EPNUM_SHIFT 12 + +#define USB_TRANS_MODE_SHIFT 8 +#define USB_TRANS_CTR 0x0000 +#define USB_TRANS_INT 0x0100 +#define USB_TRANS_BULK 0x0200 +#define USB_TRANS_ISO 0x0300 + +#define USB_EP_MF 0x0020 +#define USB_EP_RTE 0x0010 + +#define USB_THS_SHIFT 2 +#define USB_THS_MASK 0x000c +#define USB_THS_NORMAL 0x0 +#define USB_THS_IGNORE_IN 0x0004 +#define USB_THS_NACK 0x0008 +#define USB_THS_STALL 0x000c + +#define USB_RHS_SHIFT 0 +#define USB_RHS_MASK 0x0003 +#define USB_RHS_NORMAL 0x0 +#define USB_RHS_IGNORE_OUT 0x0001 +#define USB_RHS_NACK 0x0002 +#define USB_RHS_STALL 0x0003 + +#define USB_RTHS_MASK 0x000f + +/* USB Command Register define */ +#define USB_CMD_STR_FIFO 0x80 +#define USB_CMD_FLUSH_FIFO 0x40 +#define USB_CMD_ISFT 0x20 +#define USB_CMD_DSFT 0x10 +#define USB_CMD_EP_MASK 0x03 + +/* USB Event and Mask Register define */ +#define USB_E_MSF_MASK 0x0800 +#define USB_E_SFT_MASK 0x0400 +#define USB_E_RESET_MASK 0x0200 +#define USB_E_IDLE_MASK 0x0100 +#define USB_E_TXE4_MASK 0x0080 +#define USB_E_TXE3_MASK 0x0040 +#define USB_E_TXE2_MASK 0x0020 +#define USB_E_TXE1_MASK 0x0010 +#define USB_E_SOF_MASK 0x0008 +#define USB_E_BSY_MASK 0x0004 +#define USB_E_TXB_MASK 0x0002 +#define USB_E_RXB_MASK 0x0001 + +/* Freescale USB HOST */ +struct fhci_pram { + __be16 ep_ptr[4]; /* Endpoint porter reg */ + __be32 rx_state; /* Rx internal state */ + __be32 rx_ptr; /* Rx internal data pointer */ + __be16 frame_num; /* Frame number */ + __be16 rx_cnt; /* Rx byte count */ + __be32 rx_temp; /* Rx temp */ + __be32 rx_data_temp; /* Rx data temp */ + __be16 rx_u_ptr; /* Rx microcode return address temp */ + u8 reserved1[2]; /* reserved area */ + __be32 sof_tbl; /* SOF lookup table pointer */ + u8 sof_u_crc_temp; /* SOF micorcode CRC5 temp reg */ + u8 reserved2[0xdb]; +}; + +/* Freescale USB Endpoint*/ +struct fhci_ep_pram { + __be16 rx_base; /* Rx BD base address */ + __be16 tx_base; /* Tx BD base address */ + u8 rx_func_code; /* Rx function code */ + u8 tx_func_code; /* Tx function code */ + __be16 rx_buff_len; /* Rx buffer length */ + __be16 rx_bd_ptr; /* Rx BD pointer */ + __be16 tx_bd_ptr; /* Tx BD pointer */ + __be32 tx_state; /* Tx internal state */ + __be32 tx_ptr; /* Tx internal data pointer */ + __be16 tx_crc; /* temp transmit CRC */ + __be16 tx_cnt; /* Tx byte count */ + __be32 tx_temp; /* Tx temp */ + __be16 tx_u_ptr; /* Tx microcode return address temp */ + __be16 reserved; +}; + +struct fhci_controller_list { + struct list_head ctrl_list; /* control endpoints */ + struct list_head bulk_list; /* bulk endpoints */ + struct list_head iso_list; /* isochronous endpoints */ + struct list_head intr_list; /* interruput endpoints */ + struct list_head done_list; /* done transfers */ +}; + +struct virtual_root_hub { + int dev_num; /* USB address of the root hub */ + u32 feature; /* indicates what feature has been set */ + struct usb_hub_status hub; + struct usb_port_status port; +}; + +enum fhci_gpios { + GPIO_USBOE = 0, + GPIO_USBTP, + GPIO_USBTN, + GPIO_USBRP, + GPIO_USBRN, + /* these are optional */ + GPIO_SPEED, + GPIO_POWER, + NUM_GPIOS, +}; + +enum fhci_pins { + PIN_USBOE = 0, + PIN_USBTP, + PIN_USBTN, + NUM_PINS, +}; + +struct fhci_hcd { + enum qe_clock fullspeed_clk; + enum qe_clock lowspeed_clk; + struct qe_pin *pins[NUM_PINS]; + int gpios[NUM_GPIOS]; + bool alow_gpios[NUM_GPIOS]; + + struct qe_usb_ctlr __iomem *regs; /* I/O memory used to communicate */ + struct fhci_pram __iomem *pram; /* Parameter RAM */ + struct gtm_timer *timer; + + spinlock_t lock; + struct fhci_usb *usb_lld; /* Low-level driver */ + struct virtual_root_hub *vroot_hub; /* the virtual root hub */ + int active_urbs; + struct fhci_controller_list *hc_list; + struct tasklet_struct *process_done_task; /* tasklet for done list */ + + struct list_head empty_eds; + struct list_head empty_tds; + +#ifdef CONFIG_FHCI_DEBUG + int usb_irq_stat[13]; + struct dentry *dfs_root; + struct dentry *dfs_regs; + struct dentry *dfs_irq_stat; +#endif +}; + +#define USB_FRAME_USAGE 90 +#define FRAME_TIME_USAGE (USB_FRAME_USAGE*10) /* frame time usage */ +#define SW_FIX_TIME_BETWEEN_TRANSACTION 150 /* SW */ +#define MAX_BYTES_PER_FRAME (USB_FRAME_USAGE*15) +#define MAX_PERIODIC_FRAME_USAGE 90 + +/* transaction type */ +enum fhci_ta_type { + FHCI_TA_IN = 0, /* input transaction */ + FHCI_TA_OUT, /* output transaction */ + FHCI_TA_SETUP, /* setup transaction */ +}; + +/* transfer mode */ +enum fhci_tf_mode { + FHCI_TF_CTRL = 0, + FHCI_TF_ISO, + FHCI_TF_BULK, + FHCI_TF_INTR, +}; + +enum fhci_speed { + FHCI_FULL_SPEED, + FHCI_LOW_SPEED, +}; + +/* endpoint state */ +enum fhci_ed_state { + FHCI_ED_NEW = 0, /* pipe is new */ + FHCI_ED_OPER, /* pipe is operating */ + FHCI_ED_URB_DEL, /* pipe is in hold because urb is being deleted */ + FHCI_ED_SKIP, /* skip this pipe */ + FHCI_ED_HALTED, /* pipe is halted */ +}; + +enum fhci_port_status { + FHCI_PORT_POWER_OFF = 0, + FHCI_PORT_DISABLED, + FHCI_PORT_DISCONNECTING, + FHCI_PORT_WAITING, /* waiting for connection */ + FHCI_PORT_FULL, /* full speed connected */ + FHCI_PORT_LOW, /* low speed connected */ +}; + +enum fhci_mem_alloc { + MEM_CACHABLE_SYS = 0x00000001, /* primary DDR,cachable */ + MEM_NOCACHE_SYS = 0x00000004, /* primary DDR,non-cachable */ + MEM_SECONDARY = 0x00000002, /* either secondary DDR or SDRAM */ + MEM_PRAM = 0x00000008, /* multi-user RAM identifier */ +}; + +/* USB default parameters*/ +#define DEFAULT_RING_LEN 8 +#define DEFAULT_DATA_MEM MEM_CACHABLE_SYS + +struct ed { + u8 dev_addr; /* device address */ + u8 ep_addr; /* endpoint address */ + enum fhci_tf_mode mode; /* USB transfer mode */ + enum fhci_speed speed; + unsigned int max_pkt_size; + enum fhci_ed_state state; + struct list_head td_list; /* a list of all queued TD to this pipe */ + struct list_head node; + + /* read only parameters, should be cleared upon initialization */ + u8 toggle_carry; /* toggle carry from the last TD submitted */ + u32 last_iso; /* time stamp of last queued ISO transfer */ + struct td *td_head; /* a pointer to the current TD handled */ +}; + +struct td { + void *data; /* a pointer to the data buffer */ + unsigned int len; /* length of the data to be submitted */ + unsigned int actual_len; /* actual bytes transferred on this td */ + enum fhci_ta_type type; /* transaction type */ + u8 toggle; /* toggle for next trans. within this TD */ + u16 iso_index; /* ISO transaction index */ + u16 start_frame; /* start frame time stamp */ + u16 interval; /* interval between trans. (for ISO/Intr) */ + u32 status; /* status of the TD */ + struct ed *ed; /* a handle to the corresponding ED */ + struct urb *urb; /* a handle to the corresponding URB */ + bool ioc; /* Inform On Completion */ + struct list_head node; + + /* read only parameters should be cleared upon initialization */ + struct packet *pkt; + int nak_cnt; + int error_cnt; + struct list_head frame_lh; +}; + +struct packet { + u8 *data; /* packet data */ + u32 len; /* packet length */ + u32 status; /* status of the packet - equivalent to the status + * field for the corresponding structure td */ + u32 info; /* packet information */ + void __iomem *priv_data; /* private data of the driver (TDs or BDs) */ +}; + +/* struct for each URB */ +#define URB_INPROGRESS 0 +#define URB_DEL 1 + +/* URB states (state field) */ +#define US_BULK 0 +#define US_BULK0 1 + +/* three setup states */ +#define US_CTRL_SETUP 2 +#define US_CTRL_DATA 1 +#define US_CTRL_ACK 0 + +#define EP_ZERO 0 + +struct urb_priv { + int num_of_tds; + int tds_cnt; + int state; + + struct td **tds; + struct ed *ed; + struct timer_list time_out; +}; + +struct endpoint { + /* Pointer to ep parameter RAM */ + struct fhci_ep_pram __iomem *ep_pram_ptr; + + /* Host transactions */ + struct usb_td __iomem *td_base; /* first TD in the ring */ + struct usb_td __iomem *conf_td; /* next TD for confirm after transac */ + struct usb_td __iomem *empty_td;/* next TD for new transaction req. */ + struct kfifo empty_frame_Q; /* Empty frames list to use */ + struct kfifo conf_frame_Q; /* frames passed to TDs,waiting for tx */ + struct kfifo dummy_packets_Q;/* dummy packets for the CRC overun */ + + bool already_pushed_dummy_bd; +}; + +/* struct for each 1mSec frame time */ +#define FRAME_IS_TRANSMITTED 0x00 +#define FRAME_TIMER_END_TRANSMISSION 0x01 +#define FRAME_DATA_END_TRANSMISSION 0x02 +#define FRAME_END_TRANSMISSION 0x03 +#define FRAME_IS_PREPARED 0x04 + +struct fhci_time_frame { + u16 frame_num; /* frame number */ + u16 total_bytes; /* total bytes submitted within this frame */ + u8 frame_status; /* flag that indicates to stop fill this frame */ + struct list_head tds_list; /* all tds of this frame */ +}; + +/* internal driver structure*/ +struct fhci_usb { + u16 saved_msk; /* saving of the USB mask register */ + struct endpoint *ep0; /* pointer for endpoint0 structure */ + int intr_nesting_cnt; /* interrupt nesting counter */ + u16 max_frame_usage; /* max frame time usage,in micro-sec */ + u16 max_bytes_per_frame; /* max byte can be tx in one time frame */ + u32 sw_transaction_time; /* sw complete trans time,in micro-sec */ + struct fhci_time_frame *actual_frame; + struct fhci_controller_list *hc_list; /* main structure for hc */ + struct virtual_root_hub *vroot_hub; + enum fhci_port_status port_status; /* v_rh port status */ + + u32 (*transfer_confirm)(struct fhci_hcd *fhci); + + struct fhci_hcd *fhci; +}; + +/* + * Various helpers and prototypes below. + */ + +static inline u16 get_frame_num(struct fhci_hcd *fhci) +{ + return in_be16(&fhci->pram->frame_num) & 0x07ff; +} + +#define fhci_dbg(fhci, fmt, args...) \ + dev_dbg(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_vdbg(fhci, fmt, args...) \ + dev_vdbg(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_err(fhci, fmt, args...) \ + dev_err(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_info(fhci, fmt, args...) \ + dev_info(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_warn(fhci, fmt, args...) \ + dev_warn(fhci_to_hcd(fhci)->self.controller, fmt, ##args) + +static inline struct fhci_hcd *hcd_to_fhci(struct usb_hcd *hcd) +{ + return (struct fhci_hcd *)hcd->hcd_priv; +} + +static inline struct usb_hcd *fhci_to_hcd(struct fhci_hcd *fhci) +{ + return container_of((void *)fhci, struct usb_hcd, hcd_priv); +} + +/* fifo of pointers */ +static inline int cq_new(struct kfifo *fifo, int size) +{ + return kfifo_alloc(fifo, size * sizeof(void *), GFP_KERNEL); +} + +static inline void cq_delete(struct kfifo *kfifo) +{ + kfifo_free(kfifo); +} + +static inline unsigned int cq_howmany(struct kfifo *kfifo) +{ + return kfifo_len(kfifo) / sizeof(void *); +} + +static inline int cq_put(struct kfifo *kfifo, void *p) +{ + return kfifo_in(kfifo, (void *)&p, sizeof(p)); +} + +static inline void *cq_get(struct kfifo *kfifo) +{ + unsigned int sz; + void *p; + + sz = kfifo_out(kfifo, (void *)&p, sizeof(p)); + if (sz != sizeof(p)) + return NULL; + + return p; +} + +/* fhci-hcd.c */ +void fhci_start_sof_timer(struct fhci_hcd *fhci); +void fhci_stop_sof_timer(struct fhci_hcd *fhci); +u16 fhci_get_sof_timer_count(struct fhci_usb *usb); +void fhci_usb_enable_interrupt(struct fhci_usb *usb); +void fhci_usb_disable_interrupt(struct fhci_usb *usb); +int fhci_ioports_check_bus_state(struct fhci_hcd *fhci); + +/* fhci-mem.c */ +void fhci_recycle_empty_td(struct fhci_hcd *fhci, struct td *td); +void fhci_recycle_empty_ed(struct fhci_hcd *fhci, struct ed *ed); +struct ed *fhci_get_empty_ed(struct fhci_hcd *fhci); +struct td *fhci_td_fill(struct fhci_hcd *fhci, struct urb *urb, + struct urb_priv *urb_priv, struct ed *ed, u16 index, + enum fhci_ta_type type, int toggle, u8 *data, u32 len, + u16 interval, u16 start_frame, bool ioc); +void fhci_add_tds_to_ed(struct ed *ed, struct td **td_list, int number); + +/* fhci-hub.c */ +void fhci_config_transceiver(struct fhci_hcd *fhci, + enum fhci_port_status status); +void fhci_port_disable(struct fhci_hcd *fhci); +void fhci_port_enable(void *lld); +void fhci_io_port_generate_reset(struct fhci_hcd *fhci); +void fhci_port_reset(void *lld); +int fhci_hub_status_data(struct usb_hcd *hcd, char *buf); +int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength); + +/* fhci-tds.c */ +void fhci_flush_bds(struct fhci_usb *usb); +void fhci_flush_actual_frame(struct fhci_usb *usb); +u32 fhci_host_transaction(struct fhci_usb *usb, struct packet *pkt, + enum fhci_ta_type trans_type, u8 dest_addr, + u8 dest_ep, enum fhci_tf_mode trans_mode, + enum fhci_speed dest_speed, u8 data_toggle); +void fhci_host_transmit_actual_frame(struct fhci_usb *usb); +void fhci_tx_conf_interrupt(struct fhci_usb *usb); +void fhci_push_dummy_bd(struct endpoint *ep); +u32 fhci_create_ep(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, + u32 ring_len); +void fhci_init_ep_registers(struct fhci_usb *usb, + struct endpoint *ep, + enum fhci_mem_alloc data_mem); +void fhci_ep0_free(struct fhci_usb *usb); + +/* fhci-sched.c */ +extern struct tasklet_struct fhci_tasklet; +void fhci_transaction_confirm(struct fhci_usb *usb, struct packet *pkt); +void fhci_flush_all_transmissions(struct fhci_usb *usb); +void fhci_schedule_transactions(struct fhci_usb *usb); +void fhci_device_connected_interrupt(struct fhci_hcd *fhci); +void fhci_device_disconnected_interrupt(struct fhci_hcd *fhci); +void fhci_queue_urb(struct fhci_hcd *fhci, struct urb *urb); +u32 fhci_transfer_confirm_callback(struct fhci_hcd *fhci); +irqreturn_t fhci_irq(struct usb_hcd *hcd); +irqreturn_t fhci_frame_limit_timer_irq(int irq, void *_hcd); + +/* fhci-q.h */ +void fhci_urb_complete_free(struct fhci_hcd *fhci, struct urb *urb); +struct td *fhci_remove_td_from_ed(struct ed *ed); +struct td *fhci_remove_td_from_frame(struct fhci_time_frame *frame); +void fhci_move_td_from_ed_to_done_list(struct fhci_usb *usb, struct ed *ed); +struct td *fhci_peek_td_from_frame(struct fhci_time_frame *frame); +void fhci_add_td_to_frame(struct fhci_time_frame *frame, struct td *td); +struct td *fhci_remove_td_from_done_list(struct fhci_controller_list *p_list); +void fhci_done_td(struct urb *urb, struct td *td); +void fhci_del_ed_list(struct fhci_hcd *fhci, struct ed *ed); + +#ifdef CONFIG_FHCI_DEBUG + +void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er); +void fhci_dfs_destroy(struct fhci_hcd *fhci); +void fhci_dfs_create(struct fhci_hcd *fhci); + +#else + +static inline void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er) {} +static inline void fhci_dfs_destroy(struct fhci_hcd *fhci) {} +static inline void fhci_dfs_create(struct fhci_hcd *fhci) {} + +#endif /* CONFIG_FHCI_DEBUG */ + +#endif /* __FHCI_H */ diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c new file mode 100644 index 00000000..11e0b79f --- /dev/null +++ b/drivers/usb/host/fsl-mph-dr-of.c @@ -0,0 +1,349 @@ +/* + * Setup platform devices needed by the Freescale multi-port host + * and/or dual-role USB controller modules based on the description + * in flat device tree. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct fsl_usb2_dev_data { + char *dr_mode; /* controller mode */ + char *drivers[3]; /* drivers to instantiate for this mode */ + enum fsl_usb2_operating_modes op_mode; /* operating mode */ +}; + +struct fsl_usb2_dev_data dr_mode_data[] = { + { + .dr_mode = "host", + .drivers = { "fsl-ehci", NULL, NULL, }, + .op_mode = FSL_USB2_DR_HOST, + }, + { + .dr_mode = "otg", + .drivers = { "fsl-usb2-otg", "fsl-ehci", "fsl-usb2-udc", }, + .op_mode = FSL_USB2_DR_OTG, + }, + { + .dr_mode = "peripheral", + .drivers = { "fsl-usb2-udc", NULL, NULL, }, + .op_mode = FSL_USB2_DR_DEVICE, + }, +}; + +struct fsl_usb2_dev_data *get_dr_mode_data(struct device_node *np) +{ + const unsigned char *prop; + int i; + + prop = of_get_property(np, "dr_mode", NULL); + if (prop) { + for (i = 0; i < ARRAY_SIZE(dr_mode_data); i++) { + if (!strcmp(prop, dr_mode_data[i].dr_mode)) + return &dr_mode_data[i]; + } + } + pr_warn("%s: Invalid 'dr_mode' property, fallback to host mode\n", + np->full_name); + return &dr_mode_data[0]; /* mode not specified, use host */ +} + +static enum fsl_usb2_phy_modes determine_usb_phy(const char *phy_type) +{ + if (!phy_type) + return FSL_USB2_PHY_NONE; + if (!strcasecmp(phy_type, "ulpi")) + return FSL_USB2_PHY_ULPI; + if (!strcasecmp(phy_type, "utmi")) + return FSL_USB2_PHY_UTMI; + if (!strcasecmp(phy_type, "utmi_wide")) + return FSL_USB2_PHY_UTMI_WIDE; + if (!strcasecmp(phy_type, "serial")) + return FSL_USB2_PHY_SERIAL; + + return FSL_USB2_PHY_NONE; +} + +struct platform_device *fsl_usb2_device_register( + struct platform_device *ofdev, + struct fsl_usb2_platform_data *pdata, + const char *name, int id) +{ + struct platform_device *pdev; + const struct resource *res = ofdev->resource; + unsigned int num = ofdev->num_resources; + int retval; + + pdev = platform_device_alloc(name, id); + if (!pdev) { + retval = -ENOMEM; + goto error; + } + + pdev->dev.parent = &ofdev->dev; + + pdev->dev.coherent_dma_mask = ofdev->dev.coherent_dma_mask; + *pdev->dev.dma_mask = *ofdev->dev.dma_mask; + + retval = platform_device_add_data(pdev, pdata, sizeof(*pdata)); + if (retval) + goto error; + + if (num) { + retval = platform_device_add_resources(pdev, res, num); + if (retval) + goto error; + } + + retval = platform_device_add(pdev); + if (retval) + goto error; + + return pdev; + +error: + platform_device_put(pdev); + return ERR_PTR(retval); +} + +static const struct of_device_id fsl_usb2_mph_dr_of_match[]; + +static int usb_get_ver_info(struct device_node *np) +{ + int ver = -1; + + /* + * returns 1 for usb controller version 1.6 + * returns 2 for usb controller version 2.2 + * returns 0 otherwise + */ + if (of_device_is_compatible(np, "fsl-usb2-dr")) { + if (of_device_is_compatible(np, "fsl-usb2-dr-v1.6")) + ver = FSL_USB_VER_1_6; + else if (of_device_is_compatible(np, "fsl-usb2-dr-v2.2")) + ver = FSL_USB_VER_2_2; + else if (of_device_is_compatible(np, "fsl-usb2-dr-v2.4")) + ver = FSL_USB_VER_2_4; + else /* for previous controller versions */ + ver = FSL_USB_VER_OLD; + + if (ver > -1) + return ver; + } + + if (of_device_is_compatible(np, "fsl,mpc5121-usb2-dr")) + return FSL_USB_VER_OLD; + + if (of_device_is_compatible(np, "fsl-usb2-mph")) { + if (of_device_is_compatible(np, "fsl-usb2-mph-v1.6")) + ver = FSL_USB_VER_1_6; + else if (of_device_is_compatible(np, "fsl-usb2-mph-v2.2")) + ver = FSL_USB_VER_2_2; + else /* for previous controller versions */ + ver = FSL_USB_VER_OLD; + } + + return ver; +} + +static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) +{ + struct device_node *np = ofdev->dev.of_node; + struct platform_device *usb_dev; + struct fsl_usb2_platform_data data, *pdata; + struct fsl_usb2_dev_data *dev_data; + const struct of_device_id *match; + const unsigned char *prop; + static unsigned int idx; + int i; + + if (!of_device_is_available(np)) + return -ENODEV; + + match = of_match_device(fsl_usb2_mph_dr_of_match, &ofdev->dev); + if (!match) + return -ENODEV; + + pdata = &data; + if (match->data) + memcpy(pdata, match->data, sizeof(data)); + else + memset(pdata, 0, sizeof(data)); + + dev_data = get_dr_mode_data(np); + + if (of_device_is_compatible(np, "fsl-usb2-mph")) { + if (of_get_property(np, "port0", NULL)) + pdata->port_enables |= FSL_USB2_PORT0_ENABLED; + + if (of_get_property(np, "port1", NULL)) + pdata->port_enables |= FSL_USB2_PORT1_ENABLED; + + pdata->operating_mode = FSL_USB2_MPH_HOST; + } else { + if (of_get_property(np, "fsl,invert-drvvbus", NULL)) + pdata->invert_drvvbus = 1; + + if (of_get_property(np, "fsl,invert-pwr-fault", NULL)) + pdata->invert_pwr_fault = 1; + + /* setup mode selected in the device tree */ + pdata->operating_mode = dev_data->op_mode; + } + + prop = of_get_property(np, "phy_type", NULL); + pdata->phy_mode = determine_usb_phy(prop); + pdata->controller_ver = usb_get_ver_info(np); + + if (pdata->have_sysif_regs) { + if (pdata->controller_ver < 0) { + dev_warn(&ofdev->dev, "Could not get controller version\n"); + return -ENODEV; + } + } + + for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) { + if (!dev_data->drivers[i]) + continue; + usb_dev = fsl_usb2_device_register(ofdev, pdata, + dev_data->drivers[i], idx); + if (IS_ERR(usb_dev)) { + dev_err(&ofdev->dev, "Can't register usb device\n"); + return PTR_ERR(usb_dev); + } + } + idx++; + return 0; +} + +static int __unregister_subdev(struct device *dev, void *d) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int fsl_usb2_mph_dr_of_remove(struct platform_device *ofdev) +{ + device_for_each_child(&ofdev->dev, NULL, __unregister_subdev); + return 0; +} + +#ifdef CONFIG_PPC_MPC512x + +#define USBGENCTRL 0x200 /* NOTE: big endian */ +#define GC_WU_INT_CLR (1 << 5) /* Wakeup int clear */ +#define GC_ULPI_SEL (1 << 4) /* ULPI i/f select (usb0 only)*/ +#define GC_PPP (1 << 3) /* Inv. Port Power Polarity */ +#define GC_PFP (1 << 2) /* Inv. Power Fault Polarity */ +#define GC_WU_ULPI_EN (1 << 1) /* Wakeup on ULPI event */ +#define GC_WU_IE (1 << 1) /* Wakeup interrupt enable */ + +#define ISIPHYCTRL 0x204 /* NOTE: big endian */ +#define PHYCTRL_PHYE (1 << 4) /* On-chip UTMI PHY enable */ +#define PHYCTRL_BSENH (1 << 3) /* Bit Stuff Enable High */ +#define PHYCTRL_BSEN (1 << 2) /* Bit Stuff Enable */ +#define PHYCTRL_LSFE (1 << 1) /* Line State Filter Enable */ +#define PHYCTRL_PXE (1 << 0) /* PHY oscillator enable */ + +int fsl_usb2_mpc5121_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + struct clk *clk; + char clk_name[10]; + int base, clk_num; + + base = pdev->resource->start & 0xf000; + if (base == 0x3000) + clk_num = 1; + else if (base == 0x4000) + clk_num = 2; + else + return -ENODEV; + + snprintf(clk_name, sizeof(clk_name), "usb%d_clk", clk_num); + clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clk\n"); + return PTR_ERR(clk); + } + + clk_enable(clk); + pdata->clk = clk; + + if (pdata->phy_mode == FSL_USB2_PHY_UTMI_WIDE) { + u32 reg = 0; + + if (pdata->invert_drvvbus) + reg |= GC_PPP; + + if (pdata->invert_pwr_fault) + reg |= GC_PFP; + + out_be32(pdata->regs + ISIPHYCTRL, PHYCTRL_PHYE | PHYCTRL_PXE); + out_be32(pdata->regs + USBGENCTRL, reg); + } + return 0; +} + +static void fsl_usb2_mpc5121_exit(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + pdata->regs = NULL; + + if (pdata->clk) { + clk_disable(pdata->clk); + clk_put(pdata->clk); + } +} + +static struct fsl_usb2_platform_data fsl_usb2_mpc5121_pd = { + .big_endian_desc = 1, + .big_endian_mmio = 1, + .es = 1, + .have_sysif_regs = 0, + .le_setup_buf = 1, + .init = fsl_usb2_mpc5121_init, + .exit = fsl_usb2_mpc5121_exit, +}; +#endif /* CONFIG_PPC_MPC512x */ + +static struct fsl_usb2_platform_data fsl_usb2_mpc8xxx_pd = { + .have_sysif_regs = 1, +}; + +static const struct of_device_id fsl_usb2_mph_dr_of_match[] = { + { .compatible = "fsl-usb2-mph", .data = &fsl_usb2_mpc8xxx_pd, }, + { .compatible = "fsl-usb2-dr", .data = &fsl_usb2_mpc8xxx_pd, }, +#ifdef CONFIG_PPC_MPC512x + { .compatible = "fsl,mpc5121-usb2-dr", .data = &fsl_usb2_mpc5121_pd, }, +#endif + {}, +}; + +static struct platform_driver fsl_usb2_mph_dr_driver = { + .driver = { + .name = "fsl-usb2-mph-dr", + .owner = THIS_MODULE, + .of_match_table = fsl_usb2_mph_dr_of_match, + }, + .probe = fsl_usb2_mph_dr_of_probe, + .remove = fsl_usb2_mph_dr_of_remove, +}; + +module_platform_driver(fsl_usb2_mph_dr_driver); + +MODULE_DESCRIPTION("FSL MPH DR OF devices driver"); +MODULE_AUTHOR("Anatolij Gustschin "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c new file mode 100644 index 00000000..104730da --- /dev/null +++ b/drivers/usb/host/hwa-hc.c @@ -0,0 +1,843 @@ +/* + * Host Wire Adapter: + * Driver glue, HWA-specific functions, bridges to WAHC and WUSBHC + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * The HWA driver is a simple layer that forwards requests to the WAHC + * (Wire Adater Host Controller) or WUSBHC (Wireless USB Host + * Controller) layers. + * + * Host Wire Adapter is the 'WUSB 1.0 standard' name for Wireless-USB + * Host Controller that is connected to your system via USB (a USB + * dongle that implements a USB host...). There is also a Device Wired + * Adaptor, DWA (Wireless USB hub) that uses the same mechanism for + * transferring data (it is after all a USB host connected via + * Wireless USB), we have a common layer called Wire Adapter Host + * Controller that does all the hard work. The WUSBHC (Wireless USB + * Host Controller) is the part common to WUSB Host Controllers, the + * HWA and the PCI-based one, that is implemented following the WHCI + * spec. All these layers are implemented in ../wusbcore. + * + * The main functions are hwahc_op_urb_{en,de}queue(), that pass the + * job of converting a URB to a Wire Adapter + * + * Entry points: + * + * hwahc_driver_*() Driver initialization, registration and + * teardown. + * + * hwahc_probe() New device came up, create an instance for + * it [from device enumeration]. + * + * hwahc_disconnect() Remove device instance [from device + * enumeration]. + * + * [__]hwahc_op_*() Host-Wire-Adaptor specific functions for + * starting/stopping/etc (some might be made also + * DWA). + */ +#include +#include +#include +#include +#include +#include +#include +#include "../wusbcore/wa-hc.h" +#include "../wusbcore/wusbhc.h" + +struct hwahc { + struct wusbhc wusbhc; /* has to be 1st */ + struct wahc wa; +}; + +/* + * FIXME should be wusbhc + * + * NOTE: we need to cache the Cluster ID because later...there is no + * way to get it :) + */ +static int __hwahc_set_cluster_id(struct hwahc *hwahc, u8 cluster_id) +{ + int result; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_CLUSTER_ID, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + cluster_id, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Cannot set WUSB Cluster ID to 0x%02x: %d\n", + cluster_id, result); + else + wusbhc->cluster_id = cluster_id; + dev_info(dev, "Wireless USB Cluster ID set to 0x%02x\n", cluster_id); + return result; +} + +static int __hwahc_op_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_NUM_DNTS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + interval << 8 | slots, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + +/* + * Reset a WUSB host controller and wait for it to complete doing it. + * + * @usb_hcd: Pointer to WUSB Host Controller instance. + * + */ +static int hwahc_op_reset(struct usb_hcd *usb_hcd) +{ + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + mutex_lock(&wusbhc->mutex); + wa_nep_disarm(&hwahc->wa); + result = __wa_set_feature(&hwahc->wa, WA_RESET); + if (result < 0) { + dev_err(dev, "error commanding HC to reset: %d\n", result); + goto error_unlock; + } + result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0); + if (result < 0) { + dev_err(dev, "error waiting for HC to reset: %d\n", result); + goto error_unlock; + } +error_unlock: + mutex_unlock(&wusbhc->mutex); + return result; +} + +/* + * FIXME: break this function up + */ +static int hwahc_op_start(struct usb_hcd *usb_hcd) +{ + u8 addr; + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + result = -ENOSPC; + mutex_lock(&wusbhc->mutex); + addr = wusb_cluster_id_get(); + if (addr == 0) + goto error_cluster_id_get; + result = __hwahc_set_cluster_id(hwahc, addr); + if (result < 0) + goto error_set_cluster_id; + + usb_hcd->uses_new_polling = 1; + set_bit(HCD_FLAG_POLL_RH, &usb_hcd->flags); + usb_hcd->state = HC_STATE_RUNNING; + result = 0; +out: + mutex_unlock(&wusbhc->mutex); + return result; + +error_set_cluster_id: + wusb_cluster_id_put(wusbhc->cluster_id); +error_cluster_id_get: + goto out; + +} + +/* + * No need to abort pipes, as when this is called, all the children + * has been disconnected and that has done it [through + * usb_disable_interface() -> usb_disable_endpoint() -> + * hwahc_op_ep_disable() - >rpipe_ep_disable()]. + */ +static void hwahc_op_stop(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + + mutex_lock(&wusbhc->mutex); + wusb_cluster_id_put(wusbhc->cluster_id); + mutex_unlock(&wusbhc->mutex); +} + +static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc); + return -ENOSYS; +} + +static int hwahc_op_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, + gfp_t gfp) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + return wa_urb_enqueue(&hwahc->wa, urb->ep, urb, gfp); +} + +static int hwahc_op_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, + int status) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + return wa_urb_dequeue(&hwahc->wa, urb); +} + +/* + * Release resources allocated for an endpoint + * + * If there is an associated rpipe to this endpoint, go ahead and put it. + */ +static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + rpipe_ep_disable(&hwahc->wa, ep); +} + +static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + result = __wa_set_feature(&hwahc->wa, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error commanding HC to start: %d\n", result); + goto error_stop; + } + result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error waiting for HC to start: %d\n", result); + goto error_stop; + } + result = wa_nep_arm(&hwahc->wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "cannot listen to notifications: %d\n", result); + goto error_stop; + } + return result; + +error_stop: + __wa_clear_feature(&hwahc->wa, WA_ENABLE); + return result; +} + +static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc, int delay) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + int ret; + + ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_CHAN_STOP, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + delay * 1000, + iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (ret == 0) + msleep(delay); + + wa_nep_disarm(&hwahc->wa); + __wa_stop(&hwahc->wa); +} + +/* + * Set the UWB MAS allocation for the WUSB cluster + * + * @stream_index: stream to use (-1 for cancelling the allocation) + * @mas: mas bitmap to use + */ +static int __hwahc_op_bwa_set(struct wusbhc *wusbhc, s8 stream_index, + const struct uwb_mas_bm *mas) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + u8 mas_le[UWB_NUM_MAS/8]; + + /* Set the stream index */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_STREAM_IDX, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + stream_index, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot set WUSB stream index: %d\n", result); + goto out; + } + uwb_mas_bm_copy_le(mas_le, mas); + /* Set the MAS allocation */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_WUSB_MAS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + mas_le, 32, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Cannot set WUSB MAS allocation: %d\n", result); +out: + return result; +} + +/* + * Add an IE to the host's MMC + * + * @interval: See WUSB1.0[8.5.3.1] + * @repeat_cnt: See WUSB1.0[8.5.3.1] + * @handle: See WUSB1.0[8.5.3.1] + * @wuie: Pointer to the header of the WUSB IE data to add. + * MUST BE allocated in a kmalloc buffer (no stack or + * vmalloc). + * + * NOTE: the format of the WUSB IEs for MMCs are different to the + * normal MBOA MAC IEs (IE Id + Length in MBOA MAC vs. Length + + * Id in WUSB IEs). Standards...you gotta love'em. + */ +static int __hwahc_op_mmcie_add(struct wusbhc *wusbhc, u8 interval, + u8 repeat_cnt, u8 handle, + struct wuie_hdr *wuie) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_ADD_MMC_IE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + interval << 8 | repeat_cnt, + handle << 8 | iface_no, + wuie, wuie->bLength, 1000 /* FIXME: arbitrary */); +} + +/* + * Remove an IE to the host's MMC + * + * @handle: See WUSB1.0[8.5.3.1] + */ +static int __hwahc_op_mmcie_rm(struct wusbhc *wusbhc, u8 handle) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_REMOVE_MMC_IE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, handle << 8 | iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + +/* + * Update device information for a given fake port + * + * @port_idx: Fake port to which device is connected (wusbhc index, not + * USB port number). + */ +static int __hwahc_op_dev_info_set(struct wusbhc *wusbhc, + struct wusb_dev *wusb_dev) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + struct hwa_dev_info *dev_info; + int ret; + + /* fill out the Device Info buffer and send it */ + dev_info = kzalloc(sizeof(struct hwa_dev_info), GFP_KERNEL); + if (!dev_info) + return -ENOMEM; + uwb_mas_bm_copy_le(dev_info->bmDeviceAvailability, + &wusb_dev->availability); + dev_info->bDeviceAddress = wusb_dev->addr; + + /* + * If the descriptors haven't been read yet, use a default PHY + * rate of 53.3 Mbit/s only. The correct value will be used + * when this will be called again as part of the + * authentication process (which occurs after the descriptors + * have been read). + */ + if (wusb_dev->wusb_cap_descr) + dev_info->wPHYRates = wusb_dev->wusb_cap_descr->wPHYRates; + else + dev_info->wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53); + + ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_DEV_INFO, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wusb_dev->port_idx << 8 | iface_no, + dev_info, sizeof(struct hwa_dev_info), + 1000 /* FIXME: arbitrary */); + kfree(dev_info); + return ret; +} + +/* + * Set host's idea of which encryption (and key) method to use when + * talking to ad evice on a given port. + * + * If key is NULL, it means disable encryption for that "virtual port" + * (used when we disconnect). + */ +static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *key, size_t key_size, + u8 key_idx) +{ + int result = -ENOMEM; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + struct usb_key_descriptor *keyd; + size_t keyd_len; + + keyd_len = sizeof(*keyd) + key_size; + keyd = kzalloc(keyd_len, GFP_KERNEL); + if (keyd == NULL) + return -ENOMEM; + + keyd->bLength = keyd_len; + keyd->bDescriptorType = USB_DT_KEY; + keyd->tTKID[0] = (tkid >> 0) & 0xff; + keyd->tTKID[1] = (tkid >> 8) & 0xff; + keyd->tTKID[2] = (tkid >> 16) & 0xff; + memcpy(keyd->bKeyData, key, key_size); + + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + USB_DT_KEY << 8 | key_idx, + port_idx << 8 | iface_no, + keyd, keyd_len, 1000 /* FIXME: arbitrary */); + + kzfree(keyd); /* clear keys etc. */ + return result; +} + +/* + * Set host's idea of which encryption (and key) method to use when + * talking to ad evice on a given port. + * + * If key is NULL, it means disable encryption for that "virtual port" + * (used when we disconnect). + */ +static int __hwahc_op_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *key, size_t key_size) +{ + int result = -ENOMEM; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + u8 encryption_value; + + /* Tell the host which key to use to talk to the device */ + if (key) { + u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_PTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + result = __hwahc_dev_set_key(wusbhc, port_idx, tkid, + key, key_size, key_idx); + if (result < 0) + goto error_set_key; + encryption_value = wusbhc->ccm1_etd->bEncryptionValue; + } else { + /* FIXME: this should come from wusbhc->etd[UNSECURE].value */ + encryption_value = 0; + } + + /* Set the encryption type for communicating with the device */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_ENCRYPTION, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + encryption_value, port_idx << 8 | iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(wusbhc->dev, "Can't set host's WUSB encryption for " + "port index %u to %s (value %d): %d\n", port_idx, + wusb_et_name(wusbhc->ccm1_etd->bEncryptionType), + wusbhc->ccm1_etd->bEncryptionValue, result); +error_set_key: + return result; +} + +/* + * Set host's GTK key + */ +static int __hwahc_op_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *key, size_t key_size) +{ + u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + return __hwahc_dev_set_key(wusbhc, 0, tkid, key, key_size, key_idx); +} + +/* + * Get the Wire Adapter class-specific descriptor + * + * NOTE: this descriptor comes with the big bundled configuration + * descriptor that includes the interfaces' and endpoints', so + * we just look for it in the cached copy kept by the USB stack. + * + * NOTE2: We convert LE fields to CPU order. + */ +static int wa_fill_descr(struct wahc *wa) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + char *itr; + struct usb_device *usb_dev = wa->usb_dev; + struct usb_descriptor_header *hdr; + struct usb_wa_descriptor *wa_descr; + size_t itr_size, actconfig_idx; + + actconfig_idx = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[actconfig_idx]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + while (itr_size >= sizeof(*hdr)) { + hdr = (struct usb_descriptor_header *) itr; + dev_dbg(dev, "Extra device descriptor: " + "type %02x/%u bytes @ %zu (%zu left)\n", + hdr->bDescriptorType, hdr->bLength, + (itr - usb_dev->rawdescriptors[actconfig_idx]), + itr_size); + if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER) + goto found; + itr += hdr->bLength; + itr_size -= hdr->bLength; + } + dev_err(dev, "cannot find Wire Adapter Class descriptor\n"); + return -ENODEV; + +found: + result = -EINVAL; + if (hdr->bLength > itr_size) { /* is it available? */ + dev_err(dev, "incomplete Wire Adapter Class descriptor " + "(%zu bytes left, %u needed)\n", + itr_size, hdr->bLength); + goto error; + } + if (hdr->bLength < sizeof(*wa->wa_descr)) { + dev_err(dev, "short Wire Adapter Class descriptor\n"); + goto error; + } + wa->wa_descr = wa_descr = (struct usb_wa_descriptor *) hdr; + /* Make LE fields CPU order */ + wa_descr->bcdWAVersion = le16_to_cpu(wa_descr->bcdWAVersion); + wa_descr->wNumRPipes = le16_to_cpu(wa_descr->wNumRPipes); + wa_descr->wRPipeMaxBlock = le16_to_cpu(wa_descr->wRPipeMaxBlock); + if (wa_descr->bcdWAVersion > 0x0100) + dev_warn(dev, "Wire Adapter v%d.%d newer than groked v1.0\n", + wa_descr->bcdWAVersion & 0xff00 >> 8, + wa_descr->bcdWAVersion & 0x00ff); + result = 0; +error: + return result; +} + +static struct hc_driver hwahc_hc_driver = { + .description = "hwa-hcd", + .product_desc = "Wireless USB HWA host controller", + .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd), + .irq = NULL, /* FIXME */ + .flags = HCD_USB2, /* FIXME */ + .reset = hwahc_op_reset, + .start = hwahc_op_start, + .stop = hwahc_op_stop, + .get_frame_number = hwahc_op_get_frame_number, + .urb_enqueue = hwahc_op_urb_enqueue, + .urb_dequeue = hwahc_op_urb_dequeue, + .endpoint_disable = hwahc_op_endpoint_disable, + + .hub_status_data = wusbhc_rh_status_data, + .hub_control = wusbhc_rh_control, + .bus_suspend = wusbhc_rh_suspend, + .bus_resume = wusbhc_rh_resume, + .start_port_reset = wusbhc_rh_start_port_reset, +}; + +static int hwahc_security_create(struct hwahc *hwahc) +{ + int result; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct usb_device *usb_dev = hwahc->wa.usb_dev; + struct device *dev = &usb_dev->dev; + struct usb_security_descriptor *secd; + struct usb_encryption_descriptor *etd; + void *itr, *top; + size_t itr_size, needed, bytes; + u8 index; + char buf[64]; + + /* Find the host's security descriptors in the config descr bundle */ + index = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[index]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + top = itr + itr_size; + result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index], + le16_to_cpu(usb_dev->actconfig->desc.wTotalLength), + USB_DT_SECURITY, (void **) &secd); + if (result == -1) { + dev_warn(dev, "BUG? WUSB host has no security descriptors\n"); + return 0; + } + needed = sizeof(*secd); + if (top - (void *)secd < needed) { + dev_err(dev, "BUG? Not enough data to process security " + "descriptor header (%zu bytes left vs %zu needed)\n", + top - (void *) secd, needed); + return 0; + } + needed = le16_to_cpu(secd->wTotalLength); + if (top - (void *)secd < needed) { + dev_err(dev, "BUG? Not enough data to process security " + "descriptors (%zu bytes left vs %zu needed)\n", + top - (void *) secd, needed); + return 0; + } + /* Walk over the sec descriptors and store CCM1's on wusbhc */ + itr = (void *) secd + sizeof(*secd); + top = (void *) secd + le16_to_cpu(secd->wTotalLength); + index = 0; + bytes = 0; + while (itr < top) { + etd = itr; + if (top - itr < sizeof(*etd)) { + dev_err(dev, "BUG: bad host security descriptor; " + "not enough data (%zu vs %zu left)\n", + top - itr, sizeof(*etd)); + break; + } + if (etd->bLength < sizeof(*etd)) { + dev_err(dev, "BUG: bad host encryption descriptor; " + "descriptor is too short " + "(%zu vs %zu needed)\n", + (size_t)etd->bLength, sizeof(*etd)); + break; + } + itr += etd->bLength; + bytes += snprintf(buf + bytes, sizeof(buf) - bytes, + "%s (0x%02x) ", + wusb_et_name(etd->bEncryptionType), + etd->bEncryptionValue); + wusbhc->ccm1_etd = etd; + } + dev_info(dev, "supported encryption types: %s\n", buf); + if (wusbhc->ccm1_etd == NULL) { + dev_err(dev, "E: host doesn't support CCM-1 crypto\n"); + return 0; + } + /* Pretty print what we support */ + return 0; +} + +static void hwahc_security_release(struct hwahc *hwahc) +{ + /* nothing to do here so far... */ +} + +static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface) +{ + int result; + struct device *dev = &iface->dev; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct wahc *wa = &hwahc->wa; + struct usb_device *usb_dev = interface_to_usbdev(iface); + + wa->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */ + wa->usb_iface = usb_get_intf(iface); + wusbhc->dev = dev; + wusbhc->uwb_rc = uwb_rc_get_by_grandpa(iface->dev.parent); + if (wusbhc->uwb_rc == NULL) { + result = -ENODEV; + dev_err(dev, "Cannot get associated UWB Host Controller\n"); + goto error_rc_get; + } + result = wa_fill_descr(wa); /* Get the device descriptor */ + if (result < 0) + goto error_fill_descriptor; + if (wa->wa_descr->bNumPorts > USB_MAXCHILDREN) { + dev_err(dev, "FIXME: USB_MAXCHILDREN too low for WUSB " + "adapter (%u ports)\n", wa->wa_descr->bNumPorts); + wusbhc->ports_max = USB_MAXCHILDREN; + } else { + wusbhc->ports_max = wa->wa_descr->bNumPorts; + } + wusbhc->mmcies_max = wa->wa_descr->bNumMMCIEs; + wusbhc->start = __hwahc_op_wusbhc_start; + wusbhc->stop = __hwahc_op_wusbhc_stop; + wusbhc->mmcie_add = __hwahc_op_mmcie_add; + wusbhc->mmcie_rm = __hwahc_op_mmcie_rm; + wusbhc->dev_info_set = __hwahc_op_dev_info_set; + wusbhc->bwa_set = __hwahc_op_bwa_set; + wusbhc->set_num_dnts = __hwahc_op_set_num_dnts; + wusbhc->set_ptk = __hwahc_op_set_ptk; + wusbhc->set_gtk = __hwahc_op_set_gtk; + result = hwahc_security_create(hwahc); + if (result < 0) { + dev_err(dev, "Can't initialize security: %d\n", result); + goto error_security_create; + } + wa->wusb = wusbhc; /* FIXME: ugly, need to fix */ + result = wusbhc_create(&hwahc->wusbhc); + if (result < 0) { + dev_err(dev, "Can't create WUSB HC structures: %d\n", result); + goto error_wusbhc_create; + } + result = wa_create(&hwahc->wa, iface); + if (result < 0) + goto error_wa_create; + return 0; + +error_wa_create: + wusbhc_destroy(&hwahc->wusbhc); +error_wusbhc_create: + /* WA Descr fill allocs no resources */ +error_security_create: +error_fill_descriptor: + uwb_rc_put(wusbhc->uwb_rc); +error_rc_get: + usb_put_intf(iface); + usb_put_dev(usb_dev); + return result; +} + +static void hwahc_destroy(struct hwahc *hwahc) +{ + struct wusbhc *wusbhc = &hwahc->wusbhc; + + mutex_lock(&wusbhc->mutex); + __wa_destroy(&hwahc->wa); + wusbhc_destroy(&hwahc->wusbhc); + hwahc_security_release(hwahc); + hwahc->wusbhc.dev = NULL; + uwb_rc_put(wusbhc->uwb_rc); + usb_put_intf(hwahc->wa.usb_iface); + usb_put_dev(hwahc->wa.usb_dev); + mutex_unlock(&wusbhc->mutex); +} + +static void hwahc_init(struct hwahc *hwahc) +{ + wa_init(&hwahc->wa); +} + +static int hwahc_probe(struct usb_interface *usb_iface, + const struct usb_device_id *id) +{ + int result; + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct hwahc *hwahc; + struct device *dev = &usb_iface->dev; + + result = -ENOMEM; + usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa"); + if (usb_hcd == NULL) { + dev_err(dev, "unable to allocate instance\n"); + goto error_alloc; + } + usb_hcd->wireless = 1; + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + hwahc = container_of(wusbhc, struct hwahc, wusbhc); + hwahc_init(hwahc); + result = hwahc_create(hwahc, usb_iface); + if (result < 0) { + dev_err(dev, "Cannot initialize internals: %d\n", result); + goto error_hwahc_create; + } + result = usb_add_hcd(usb_hcd, 0, 0); + if (result < 0) { + dev_err(dev, "Cannot add HCD: %d\n", result); + goto error_add_hcd; + } + result = wusbhc_b_create(&hwahc->wusbhc); + if (result < 0) { + dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result); + goto error_wusbhc_b_create; + } + return 0; + +error_wusbhc_b_create: + usb_remove_hcd(usb_hcd); +error_add_hcd: + hwahc_destroy(hwahc); +error_hwahc_create: + usb_put_hcd(usb_hcd); +error_alloc: + return result; +} + +static void hwahc_disconnect(struct usb_interface *usb_iface) +{ + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct hwahc *hwahc; + + usb_hcd = usb_get_intfdata(usb_iface); + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + wusbhc_b_destroy(&hwahc->wusbhc); + usb_remove_hcd(usb_hcd); + hwahc_destroy(hwahc); + usb_put_hcd(usb_hcd); +} + +static struct usb_device_id hwahc_id_table[] = { + /* FIXME: use class labels for this */ + { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), }, + {}, +}; +MODULE_DEVICE_TABLE(usb, hwahc_id_table); + +static struct usb_driver hwahc_driver = { + .name = "hwa-hc", + .probe = hwahc_probe, + .disconnect = hwahc_disconnect, + .id_table = hwahc_id_table, +}; + +module_usb_driver(hwahc_driver); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/imx21-dbg.c b/drivers/usb/host/imx21-dbg.c new file mode 100644 index 00000000..ec98ecee --- /dev/null +++ b/drivers/usb/host/imx21-dbg.c @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2009 by Martin Fuzzey + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of imx21-hcd.c */ + +#ifndef DEBUG + +static inline void create_debug_files(struct imx21 *imx21) { } +static inline void remove_debug_files(struct imx21 *imx21) { } +static inline void debug_urb_submitted(struct imx21 *imx21, struct urb *urb) {} +static inline void debug_urb_completed(struct imx21 *imx21, struct urb *urb, + int status) {} +static inline void debug_urb_unlinked(struct imx21 *imx21, struct urb *urb) {} +static inline void debug_urb_queued_for_etd(struct imx21 *imx21, + struct urb *urb) {} +static inline void debug_urb_queued_for_dmem(struct imx21 *imx21, + struct urb *urb) {} +static inline void debug_etd_allocated(struct imx21 *imx21) {} +static inline void debug_etd_freed(struct imx21 *imx21) {} +static inline void debug_dmem_allocated(struct imx21 *imx21, int size) {} +static inline void debug_dmem_freed(struct imx21 *imx21, int size) {} +static inline void debug_isoc_submitted(struct imx21 *imx21, + int frame, struct td *td) {} +static inline void debug_isoc_completed(struct imx21 *imx21, + int frame, struct td *td, int cc, int len) {} + +#else + +#include +#include + +static const char *dir_labels[] = { + "TD 0", + "OUT", + "IN", + "TD 1" +}; + +static const char *speed_labels[] = { + "Full", + "Low" +}; + +static const char *format_labels[] = { + "Control", + "ISO", + "Bulk", + "Interrupt" +}; + +static inline struct debug_stats *stats_for_urb(struct imx21 *imx21, + struct urb *urb) +{ + return usb_pipeisoc(urb->pipe) ? + &imx21->isoc_stats : &imx21->nonisoc_stats; +} + +static void debug_urb_submitted(struct imx21 *imx21, struct urb *urb) +{ + stats_for_urb(imx21, urb)->submitted++; +} + +static void debug_urb_completed(struct imx21 *imx21, struct urb *urb, int st) +{ + if (st) + stats_for_urb(imx21, urb)->completed_failed++; + else + stats_for_urb(imx21, urb)->completed_ok++; +} + +static void debug_urb_unlinked(struct imx21 *imx21, struct urb *urb) +{ + stats_for_urb(imx21, urb)->unlinked++; +} + +static void debug_urb_queued_for_etd(struct imx21 *imx21, struct urb *urb) +{ + stats_for_urb(imx21, urb)->queue_etd++; +} + +static void debug_urb_queued_for_dmem(struct imx21 *imx21, struct urb *urb) +{ + stats_for_urb(imx21, urb)->queue_dmem++; +} + +static inline void debug_etd_allocated(struct imx21 *imx21) +{ + imx21->etd_usage.maximum = max( + ++(imx21->etd_usage.value), + imx21->etd_usage.maximum); +} + +static inline void debug_etd_freed(struct imx21 *imx21) +{ + imx21->etd_usage.value--; +} + +static inline void debug_dmem_allocated(struct imx21 *imx21, int size) +{ + imx21->dmem_usage.value += size; + imx21->dmem_usage.maximum = max( + imx21->dmem_usage.value, + imx21->dmem_usage.maximum); +} + +static inline void debug_dmem_freed(struct imx21 *imx21, int size) +{ + imx21->dmem_usage.value -= size; +} + + +static void debug_isoc_submitted(struct imx21 *imx21, + int frame, struct td *td) +{ + struct debug_isoc_trace *trace = &imx21->isoc_trace[ + imx21->isoc_trace_index++]; + + imx21->isoc_trace_index %= ARRAY_SIZE(imx21->isoc_trace); + trace->schedule_frame = td->frame; + trace->submit_frame = frame; + trace->request_len = td->len; + trace->td = td; +} + +static inline void debug_isoc_completed(struct imx21 *imx21, + int frame, struct td *td, int cc, int len) +{ + struct debug_isoc_trace *trace, *trace_failed; + int i; + int found = 0; + + trace = imx21->isoc_trace; + for (i = 0; i < ARRAY_SIZE(imx21->isoc_trace); i++, trace++) { + if (trace->td == td) { + trace->done_frame = frame; + trace->done_len = len; + trace->cc = cc; + trace->td = NULL; + found = 1; + break; + } + } + + if (found && cc) { + trace_failed = &imx21->isoc_trace_failed[ + imx21->isoc_trace_index_failed++]; + + imx21->isoc_trace_index_failed %= ARRAY_SIZE( + imx21->isoc_trace_failed); + *trace_failed = *trace; + } +} + + +static char *format_ep(struct usb_host_endpoint *ep, char *buf, int bufsize) +{ + if (ep) + snprintf(buf, bufsize, "ep_%02x (type:%02X kaddr:%p)", + ep->desc.bEndpointAddress, + usb_endpoint_type(&ep->desc), + ep); + else + snprintf(buf, bufsize, "none"); + return buf; +} + +static char *format_etd_dword0(u32 value, char *buf, int bufsize) +{ + snprintf(buf, bufsize, + "addr=%d ep=%d dir=%s speed=%s format=%s halted=%d", + value & 0x7F, + (value >> DW0_ENDPNT) & 0x0F, + dir_labels[(value >> DW0_DIRECT) & 0x03], + speed_labels[(value >> DW0_SPEED) & 0x01], + format_labels[(value >> DW0_FORMAT) & 0x03], + (value >> DW0_HALTED) & 0x01); + return buf; +} + +static int debug_status_show(struct seq_file *s, void *v) +{ + struct imx21 *imx21 = s->private; + int etds_allocated = 0; + int etds_sw_busy = 0; + int etds_hw_busy = 0; + int dmem_blocks = 0; + int queued_for_etd = 0; + int queued_for_dmem = 0; + unsigned int dmem_bytes = 0; + int i; + struct etd_priv *etd; + u32 etd_enable_mask; + unsigned long flags; + struct imx21_dmem_area *dmem; + struct ep_priv *ep_priv; + + spin_lock_irqsave(&imx21->lock, flags); + + etd_enable_mask = readl(imx21->regs + USBH_ETDENSET); + for (i = 0, etd = imx21->etd; i < USB_NUM_ETD; i++, etd++) { + if (etd->alloc) + etds_allocated++; + if (etd->urb) + etds_sw_busy++; + if (etd_enable_mask & (1<dmem_list, list) { + dmem_bytes += dmem->size; + dmem_blocks++; + } + + list_for_each_entry(ep_priv, &imx21->queue_for_etd, queue) + queued_for_etd++; + + list_for_each_entry(etd, &imx21->queue_for_dmem, queue) + queued_for_dmem++; + + spin_unlock_irqrestore(&imx21->lock, flags); + + seq_printf(s, + "Frame: %d\n" + "ETDs allocated: %d/%d (max=%d)\n" + "ETDs in use sw: %d\n" + "ETDs in use hw: %d\n" + "DMEM allocated: %d/%d (max=%d)\n" + "DMEM blocks: %d\n" + "Queued waiting for ETD: %d\n" + "Queued waiting for DMEM: %d\n", + readl(imx21->regs + USBH_FRMNUB) & 0xFFFF, + etds_allocated, USB_NUM_ETD, imx21->etd_usage.maximum, + etds_sw_busy, + etds_hw_busy, + dmem_bytes, DMEM_SIZE, imx21->dmem_usage.maximum, + dmem_blocks, + queued_for_etd, + queued_for_dmem); + + return 0; +} + +static int debug_dmem_show(struct seq_file *s, void *v) +{ + struct imx21 *imx21 = s->private; + struct imx21_dmem_area *dmem; + unsigned long flags; + char ep_text[40]; + + spin_lock_irqsave(&imx21->lock, flags); + + list_for_each_entry(dmem, &imx21->dmem_list, list) + seq_printf(s, + "%04X: size=0x%X " + "ep=%s\n", + dmem->offset, dmem->size, + format_ep(dmem->ep, ep_text, sizeof(ep_text))); + + spin_unlock_irqrestore(&imx21->lock, flags); + + return 0; +} + +static int debug_etd_show(struct seq_file *s, void *v) +{ + struct imx21 *imx21 = s->private; + struct etd_priv *etd; + char buf[60]; + u32 dword; + int i, j; + unsigned long flags; + + spin_lock_irqsave(&imx21->lock, flags); + + for (i = 0, etd = imx21->etd; i < USB_NUM_ETD; i++, etd++) { + int state = -1; + struct urb_priv *urb_priv; + if (etd->urb) { + urb_priv = etd->urb->hcpriv; + if (urb_priv) + state = urb_priv->state; + } + + seq_printf(s, + "etd_num: %d\n" + "ep: %s\n" + "alloc: %d\n" + "len: %d\n" + "busy sw: %d\n" + "busy hw: %d\n" + "urb state: %d\n" + "current urb: %p\n", + + i, + format_ep(etd->ep, buf, sizeof(buf)), + etd->alloc, + etd->len, + etd->urb != NULL, + (readl(imx21->regs + USBH_ETDENSET) & (1 << i)) > 0, + state, + etd->urb); + + for (j = 0; j < 4; j++) { + dword = etd_readl(imx21, i, j); + switch (j) { + case 0: + format_etd_dword0(dword, buf, sizeof(buf)); + break; + case 2: + snprintf(buf, sizeof(buf), + "cc=0X%02X", dword >> DW2_COMPCODE); + break; + default: + *buf = 0; + break; + } + seq_printf(s, + "dword %d: submitted=%08X cur=%08X [%s]\n", + j, + etd->submitted_dwords[j], + dword, + buf); + } + seq_printf(s, "\n"); + } + + spin_unlock_irqrestore(&imx21->lock, flags); + + return 0; +} + +static void debug_statistics_show_one(struct seq_file *s, + const char *name, struct debug_stats *stats) +{ + seq_printf(s, "%s:\n" + "submitted URBs: %lu\n" + "completed OK: %lu\n" + "completed failed: %lu\n" + "unlinked: %lu\n" + "queued for ETD: %lu\n" + "queued for DMEM: %lu\n\n", + name, + stats->submitted, + stats->completed_ok, + stats->completed_failed, + stats->unlinked, + stats->queue_etd, + stats->queue_dmem); +} + +static int debug_statistics_show(struct seq_file *s, void *v) +{ + struct imx21 *imx21 = s->private; + unsigned long flags; + + spin_lock_irqsave(&imx21->lock, flags); + + debug_statistics_show_one(s, "nonisoc", &imx21->nonisoc_stats); + debug_statistics_show_one(s, "isoc", &imx21->isoc_stats); + seq_printf(s, "unblock kludge triggers: %lu\n", imx21->debug_unblocks); + spin_unlock_irqrestore(&imx21->lock, flags); + + return 0; +} + +static void debug_isoc_show_one(struct seq_file *s, + const char *name, int index, struct debug_isoc_trace *trace) +{ + seq_printf(s, "%s %d:\n" + "cc=0X%02X\n" + "scheduled frame %d (%d)\n" + "submitted frame %d (%d)\n" + "completed frame %d (%d)\n" + "requested length=%d\n" + "completed length=%d\n\n", + name, index, + trace->cc, + trace->schedule_frame, trace->schedule_frame & 0xFFFF, + trace->submit_frame, trace->submit_frame & 0xFFFF, + trace->done_frame, trace->done_frame & 0xFFFF, + trace->request_len, + trace->done_len); +} + +static int debug_isoc_show(struct seq_file *s, void *v) +{ + struct imx21 *imx21 = s->private; + struct debug_isoc_trace *trace; + unsigned long flags; + int i; + + spin_lock_irqsave(&imx21->lock, flags); + + trace = imx21->isoc_trace_failed; + for (i = 0; i < ARRAY_SIZE(imx21->isoc_trace_failed); i++, trace++) + debug_isoc_show_one(s, "isoc failed", i, trace); + + trace = imx21->isoc_trace; + for (i = 0; i < ARRAY_SIZE(imx21->isoc_trace); i++, trace++) + debug_isoc_show_one(s, "isoc", i, trace); + + spin_unlock_irqrestore(&imx21->lock, flags); + + return 0; +} + +static int debug_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_status_show, inode->i_private); +} + +static int debug_dmem_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_dmem_show, inode->i_private); +} + +static int debug_etd_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_etd_show, inode->i_private); +} + +static int debug_statistics_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_statistics_show, inode->i_private); +} + +static int debug_isoc_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_isoc_show, inode->i_private); +} + +static const struct file_operations debug_status_fops = { + .open = debug_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations debug_dmem_fops = { + .open = debug_dmem_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations debug_etd_fops = { + .open = debug_etd_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations debug_statistics_fops = { + .open = debug_statistics_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations debug_isoc_fops = { + .open = debug_isoc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void create_debug_files(struct imx21 *imx21) +{ + imx21->debug_root = debugfs_create_dir(dev_name(imx21->dev), NULL); + if (!imx21->debug_root) + goto failed_create_rootdir; + + if (!debugfs_create_file("status", S_IRUGO, + imx21->debug_root, imx21, &debug_status_fops)) + goto failed_create; + + if (!debugfs_create_file("dmem", S_IRUGO, + imx21->debug_root, imx21, &debug_dmem_fops)) + goto failed_create; + + if (!debugfs_create_file("etd", S_IRUGO, + imx21->debug_root, imx21, &debug_etd_fops)) + goto failed_create; + + if (!debugfs_create_file("statistics", S_IRUGO, + imx21->debug_root, imx21, &debug_statistics_fops)) + goto failed_create; + + if (!debugfs_create_file("isoc", S_IRUGO, + imx21->debug_root, imx21, &debug_isoc_fops)) + goto failed_create; + + return; + +failed_create: + debugfs_remove_recursive(imx21->debug_root); + +failed_create_rootdir: + imx21->debug_root = NULL; +} + + +static void remove_debug_files(struct imx21 *imx21) +{ + if (imx21->debug_root) { + debugfs_remove_recursive(imx21->debug_root); + imx21->debug_root = NULL; + } +} + +#endif + diff --git a/drivers/usb/host/imx21-hcd.c b/drivers/usb/host/imx21-hcd.c new file mode 100644 index 00000000..f0ebe8e7 --- /dev/null +++ b/drivers/usb/host/imx21-hcd.c @@ -0,0 +1,1933 @@ +/* + * USB Host Controller Driver for IMX21 + * + * Copyright (C) 2006 Loping Dog Embedded Systems + * Copyright (C) 2009 Martin Fuzzey + * Originally written by Jay Monkman + * Ported to 2.6.30, debugged and enhanced by Martin Fuzzey + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + + /* + * The i.MX21 USB hardware contains + * * 32 transfer descriptors (called ETDs) + * * 4Kb of Data memory + * + * The data memory is shared between the host and function controllers + * (but this driver only supports the host controller) + * + * So setting up a transfer involves: + * * Allocating a ETD + * * Fill in ETD with appropriate information + * * Allocating data memory (and putting the offset in the ETD) + * * Activate the ETD + * * Get interrupt when done. + * + * An ETD is assigned to each active endpoint. + * + * Low resource (ETD and Data memory) situations are handled differently for + * isochronous and non insosynchronous transactions : + * + * Non ISOC transfers are queued if either ETDs or Data memory are unavailable + * + * ISOC transfers use 2 ETDs per endpoint to achieve double buffering. + * They allocate both ETDs and Data memory during URB submission + * (and fail if unavailable). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx21-hcd.h" + +#ifdef DEBUG +#define DEBUG_LOG_FRAME(imx21, etd, event) \ + (etd)->event##_frame = readl((imx21)->regs + USBH_FRMNUB) +#else +#define DEBUG_LOG_FRAME(imx21, etd, event) do { } while (0) +#endif + +static const char hcd_name[] = "imx21-hcd"; + +static inline struct imx21 *hcd_to_imx21(struct usb_hcd *hcd) +{ + return (struct imx21 *)hcd->hcd_priv; +} + + +/* =========================================== */ +/* Hardware access helpers */ +/* =========================================== */ + +static inline void set_register_bits(struct imx21 *imx21, u32 offset, u32 mask) +{ + void __iomem *reg = imx21->regs + offset; + writel(readl(reg) | mask, reg); +} + +static inline void clear_register_bits(struct imx21 *imx21, + u32 offset, u32 mask) +{ + void __iomem *reg = imx21->regs + offset; + writel(readl(reg) & ~mask, reg); +} + +static inline void clear_toggle_bit(struct imx21 *imx21, u32 offset, u32 mask) +{ + void __iomem *reg = imx21->regs + offset; + + if (readl(reg) & mask) + writel(mask, reg); +} + +static inline void set_toggle_bit(struct imx21 *imx21, u32 offset, u32 mask) +{ + void __iomem *reg = imx21->regs + offset; + + if (!(readl(reg) & mask)) + writel(mask, reg); +} + +static void etd_writel(struct imx21 *imx21, int etd_num, int dword, u32 value) +{ + writel(value, imx21->regs + USB_ETD_DWORD(etd_num, dword)); +} + +static u32 etd_readl(struct imx21 *imx21, int etd_num, int dword) +{ + return readl(imx21->regs + USB_ETD_DWORD(etd_num, dword)); +} + +static inline int wrap_frame(int counter) +{ + return counter & 0xFFFF; +} + +static inline int frame_after(int frame, int after) +{ + /* handle wrapping like jiffies time_afer */ + return (s16)((s16)after - (s16)frame) < 0; +} + +static int imx21_hc_get_frame(struct usb_hcd *hcd) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + + return wrap_frame(readl(imx21->regs + USBH_FRMNUB)); +} + +static inline bool unsuitable_for_dma(dma_addr_t addr) +{ + return (addr & 3) != 0; +} + +#include "imx21-dbg.c" + +static void nonisoc_urb_completed_for_etd( + struct imx21 *imx21, struct etd_priv *etd, int status); +static void schedule_nonisoc_etd(struct imx21 *imx21, struct urb *urb); +static void free_dmem(struct imx21 *imx21, struct etd_priv *etd); + +/* =========================================== */ +/* ETD management */ +/* =========================================== */ + +static int alloc_etd(struct imx21 *imx21) +{ + int i; + struct etd_priv *etd = imx21->etd; + + for (i = 0; i < USB_NUM_ETD; i++, etd++) { + if (etd->alloc == 0) { + memset(etd, 0, sizeof(imx21->etd[0])); + etd->alloc = 1; + debug_etd_allocated(imx21); + return i; + } + } + return -1; +} + +static void disactivate_etd(struct imx21 *imx21, int num) +{ + int etd_mask = (1 << num); + struct etd_priv *etd = &imx21->etd[num]; + + writel(etd_mask, imx21->regs + USBH_ETDENCLR); + clear_register_bits(imx21, USBH_ETDDONEEN, etd_mask); + writel(etd_mask, imx21->regs + USB_ETDDMACHANLCLR); + clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask); + + etd->active_count = 0; + + DEBUG_LOG_FRAME(imx21, etd, disactivated); +} + +static void reset_etd(struct imx21 *imx21, int num) +{ + struct etd_priv *etd = imx21->etd + num; + int i; + + disactivate_etd(imx21, num); + + for (i = 0; i < 4; i++) + etd_writel(imx21, num, i, 0); + etd->urb = NULL; + etd->ep = NULL; + etd->td = NULL; + etd->bounce_buffer = NULL; +} + +static void free_etd(struct imx21 *imx21, int num) +{ + if (num < 0) + return; + + if (num >= USB_NUM_ETD) { + dev_err(imx21->dev, "BAD etd=%d!\n", num); + return; + } + if (imx21->etd[num].alloc == 0) { + dev_err(imx21->dev, "ETD %d already free!\n", num); + return; + } + + debug_etd_freed(imx21); + reset_etd(imx21, num); + memset(&imx21->etd[num], 0, sizeof(imx21->etd[0])); +} + + +static void setup_etd_dword0(struct imx21 *imx21, + int etd_num, struct urb *urb, u8 dir, u16 maxpacket) +{ + etd_writel(imx21, etd_num, 0, + ((u32) usb_pipedevice(urb->pipe)) << DW0_ADDRESS | + ((u32) usb_pipeendpoint(urb->pipe) << DW0_ENDPNT) | + ((u32) dir << DW0_DIRECT) | + ((u32) ((urb->dev->speed == USB_SPEED_LOW) ? + 1 : 0) << DW0_SPEED) | + ((u32) fmt_urb_to_etd[usb_pipetype(urb->pipe)] << DW0_FORMAT) | + ((u32) maxpacket << DW0_MAXPKTSIZ)); +} + +/** + * Copy buffer to data controller data memory. + * We cannot use memcpy_toio() because the hardware requires 32bit writes + */ +static void copy_to_dmem( + struct imx21 *imx21, int dmem_offset, void *src, int count) +{ + void __iomem *dmem = imx21->regs + USBOTG_DMEM + dmem_offset; + u32 word = 0; + u8 *p = src; + int byte = 0; + int i; + + for (i = 0; i < count; i++) { + byte = i % 4; + word += (*p++ << (byte * 8)); + if (byte == 3) { + writel(word, dmem); + dmem += 4; + word = 0; + } + } + + if (count && byte != 3) + writel(word, dmem); +} + +static void activate_etd(struct imx21 *imx21, int etd_num, u8 dir) +{ + u32 etd_mask = 1 << etd_num; + struct etd_priv *etd = &imx21->etd[etd_num]; + + if (etd->dma_handle && unsuitable_for_dma(etd->dma_handle)) { + /* For non aligned isoc the condition below is always true */ + if (etd->len <= etd->dmem_size) { + /* Fits into data memory, use PIO */ + if (dir != TD_DIR_IN) { + copy_to_dmem(imx21, + etd->dmem_offset, + etd->cpu_buffer, etd->len); + } + etd->dma_handle = 0; + + } else { + /* Too big for data memory, use bounce buffer */ + enum dma_data_direction dmadir; + + if (dir == TD_DIR_IN) { + dmadir = DMA_FROM_DEVICE; + etd->bounce_buffer = kmalloc(etd->len, + GFP_ATOMIC); + } else { + dmadir = DMA_TO_DEVICE; + etd->bounce_buffer = kmemdup(etd->cpu_buffer, + etd->len, + GFP_ATOMIC); + } + if (!etd->bounce_buffer) { + dev_err(imx21->dev, "failed bounce alloc\n"); + goto err_bounce_alloc; + } + + etd->dma_handle = + dma_map_single(imx21->dev, + etd->bounce_buffer, + etd->len, + dmadir); + if (dma_mapping_error(imx21->dev, etd->dma_handle)) { + dev_err(imx21->dev, "failed bounce map\n"); + goto err_bounce_map; + } + } + } + + clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask); + set_register_bits(imx21, USBH_ETDDONEEN, etd_mask); + clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask); + clear_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask); + + if (etd->dma_handle) { + set_register_bits(imx21, USB_ETDDMACHANLCLR, etd_mask); + clear_toggle_bit(imx21, USBH_XBUFSTAT, etd_mask); + clear_toggle_bit(imx21, USBH_YBUFSTAT, etd_mask); + writel(etd->dma_handle, imx21->regs + USB_ETDSMSA(etd_num)); + set_register_bits(imx21, USB_ETDDMAEN, etd_mask); + } else { + if (dir != TD_DIR_IN) { + /* need to set for ZLP and PIO */ + set_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask); + set_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask); + } + } + + DEBUG_LOG_FRAME(imx21, etd, activated); + +#ifdef DEBUG + if (!etd->active_count) { + int i; + etd->activated_frame = readl(imx21->regs + USBH_FRMNUB); + etd->disactivated_frame = -1; + etd->last_int_frame = -1; + etd->last_req_frame = -1; + + for (i = 0; i < 4; i++) + etd->submitted_dwords[i] = etd_readl(imx21, etd_num, i); + } +#endif + + etd->active_count = 1; + writel(etd_mask, imx21->regs + USBH_ETDENSET); + return; + +err_bounce_map: + kfree(etd->bounce_buffer); + +err_bounce_alloc: + free_dmem(imx21, etd); + nonisoc_urb_completed_for_etd(imx21, etd, -ENOMEM); +} + +/* =========================================== */ +/* Data memory management */ +/* =========================================== */ + +static int alloc_dmem(struct imx21 *imx21, unsigned int size, + struct usb_host_endpoint *ep) +{ + unsigned int offset = 0; + struct imx21_dmem_area *area; + struct imx21_dmem_area *tmp; + + size += (~size + 1) & 0x3; /* Round to 4 byte multiple */ + + if (size > DMEM_SIZE) { + dev_err(imx21->dev, "size=%d > DMEM_SIZE(%d)\n", + size, DMEM_SIZE); + return -EINVAL; + } + + list_for_each_entry(tmp, &imx21->dmem_list, list) { + if ((size + offset) < offset) + goto fail; + if ((size + offset) <= tmp->offset) + break; + offset = tmp->size + tmp->offset; + if ((offset + size) > DMEM_SIZE) + goto fail; + } + + area = kmalloc(sizeof(struct imx21_dmem_area), GFP_ATOMIC); + if (area == NULL) + return -ENOMEM; + + area->ep = ep; + area->offset = offset; + area->size = size; + list_add_tail(&area->list, &tmp->list); + debug_dmem_allocated(imx21, size); + return offset; + +fail: + return -ENOMEM; +} + +/* Memory now available for a queued ETD - activate it */ +static void activate_queued_etd(struct imx21 *imx21, + struct etd_priv *etd, u32 dmem_offset) +{ + struct urb_priv *urb_priv = etd->urb->hcpriv; + int etd_num = etd - &imx21->etd[0]; + u32 maxpacket = etd_readl(imx21, etd_num, 1) >> DW1_YBUFSRTAD; + u8 dir = (etd_readl(imx21, etd_num, 2) >> DW2_DIRPID) & 0x03; + + dev_dbg(imx21->dev, "activating queued ETD %d now DMEM available\n", + etd_num); + etd_writel(imx21, etd_num, 1, + ((dmem_offset + maxpacket) << DW1_YBUFSRTAD) | dmem_offset); + + etd->dmem_offset = dmem_offset; + urb_priv->active = 1; + activate_etd(imx21, etd_num, dir); +} + +static void free_dmem(struct imx21 *imx21, struct etd_priv *etd) +{ + struct imx21_dmem_area *area; + struct etd_priv *tmp; + int found = 0; + int offset; + + if (!etd->dmem_size) + return; + etd->dmem_size = 0; + + offset = etd->dmem_offset; + list_for_each_entry(area, &imx21->dmem_list, list) { + if (area->offset == offset) { + debug_dmem_freed(imx21, area->size); + list_del(&area->list); + kfree(area); + found = 1; + break; + } + } + + if (!found) { + dev_err(imx21->dev, + "Trying to free unallocated DMEM %d\n", offset); + return; + } + + /* Try again to allocate memory for anything we've queued */ + list_for_each_entry_safe(etd, tmp, &imx21->queue_for_dmem, queue) { + offset = alloc_dmem(imx21, etd->dmem_size, etd->ep); + if (offset >= 0) { + list_del(&etd->queue); + activate_queued_etd(imx21, etd, (u32)offset); + } + } +} + +static void free_epdmem(struct imx21 *imx21, struct usb_host_endpoint *ep) +{ + struct imx21_dmem_area *area, *tmp; + + list_for_each_entry_safe(area, tmp, &imx21->dmem_list, list) { + if (area->ep == ep) { + dev_err(imx21->dev, + "Active DMEM %d for disabled ep=%p\n", + area->offset, ep); + list_del(&area->list); + kfree(area); + } + } +} + + +/* =========================================== */ +/* End handling */ +/* =========================================== */ + +/* Endpoint now idle - release its ETD(s) or assign to queued request */ +static void ep_idle(struct imx21 *imx21, struct ep_priv *ep_priv) +{ + int i; + + for (i = 0; i < NUM_ISO_ETDS; i++) { + int etd_num = ep_priv->etd[i]; + struct etd_priv *etd; + if (etd_num < 0) + continue; + + etd = &imx21->etd[etd_num]; + ep_priv->etd[i] = -1; + + free_dmem(imx21, etd); /* for isoc */ + + if (list_empty(&imx21->queue_for_etd)) { + free_etd(imx21, etd_num); + continue; + } + + dev_dbg(imx21->dev, + "assigning idle etd %d for queued request\n", etd_num); + ep_priv = list_first_entry(&imx21->queue_for_etd, + struct ep_priv, queue); + list_del(&ep_priv->queue); + reset_etd(imx21, etd_num); + ep_priv->waiting_etd = 0; + ep_priv->etd[i] = etd_num; + + if (list_empty(&ep_priv->ep->urb_list)) { + dev_err(imx21->dev, "No urb for queued ep!\n"); + continue; + } + schedule_nonisoc_etd(imx21, list_first_entry( + &ep_priv->ep->urb_list, struct urb, urb_list)); + } +} + +static void urb_done(struct usb_hcd *hcd, struct urb *urb, int status) +__releases(imx21->lock) +__acquires(imx21->lock) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + struct ep_priv *ep_priv = urb->ep->hcpriv; + struct urb_priv *urb_priv = urb->hcpriv; + + debug_urb_completed(imx21, urb, status); + dev_vdbg(imx21->dev, "urb %p done %d\n", urb, status); + + kfree(urb_priv->isoc_td); + kfree(urb->hcpriv); + urb->hcpriv = NULL; + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock(&imx21->lock); + usb_hcd_giveback_urb(hcd, urb, status); + spin_lock(&imx21->lock); + if (list_empty(&ep_priv->ep->urb_list)) + ep_idle(imx21, ep_priv); +} + +static void nonisoc_urb_completed_for_etd( + struct imx21 *imx21, struct etd_priv *etd, int status) +{ + struct usb_host_endpoint *ep = etd->ep; + + urb_done(imx21->hcd, etd->urb, status); + etd->urb = NULL; + + if (!list_empty(&ep->urb_list)) { + struct urb *urb = list_first_entry( + &ep->urb_list, struct urb, urb_list); + + dev_vdbg(imx21->dev, "next URB %p\n", urb); + schedule_nonisoc_etd(imx21, urb); + } +} + + +/* =========================================== */ +/* ISOC Handling ... */ +/* =========================================== */ + +static void schedule_isoc_etds(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + struct ep_priv *ep_priv = ep->hcpriv; + struct etd_priv *etd; + struct urb_priv *urb_priv; + struct td *td; + int etd_num; + int i; + int cur_frame; + u8 dir; + + for (i = 0; i < NUM_ISO_ETDS; i++) { +too_late: + if (list_empty(&ep_priv->td_list)) + break; + + etd_num = ep_priv->etd[i]; + if (etd_num < 0) + break; + + etd = &imx21->etd[etd_num]; + if (etd->urb) + continue; + + td = list_entry(ep_priv->td_list.next, struct td, list); + list_del(&td->list); + urb_priv = td->urb->hcpriv; + + cur_frame = imx21_hc_get_frame(hcd); + if (frame_after(cur_frame, td->frame)) { + dev_dbg(imx21->dev, "isoc too late frame %d > %d\n", + cur_frame, td->frame); + urb_priv->isoc_status = -EXDEV; + td->urb->iso_frame_desc[ + td->isoc_index].actual_length = 0; + td->urb->iso_frame_desc[td->isoc_index].status = -EXDEV; + if (--urb_priv->isoc_remaining == 0) + urb_done(hcd, td->urb, urb_priv->isoc_status); + goto too_late; + } + + urb_priv->active = 1; + etd->td = td; + etd->ep = td->ep; + etd->urb = td->urb; + etd->len = td->len; + etd->dma_handle = td->dma_handle; + etd->cpu_buffer = td->cpu_buffer; + + debug_isoc_submitted(imx21, cur_frame, td); + + dir = usb_pipeout(td->urb->pipe) ? TD_DIR_OUT : TD_DIR_IN; + setup_etd_dword0(imx21, etd_num, td->urb, dir, etd->dmem_size); + etd_writel(imx21, etd_num, 1, etd->dmem_offset); + etd_writel(imx21, etd_num, 2, + (TD_NOTACCESSED << DW2_COMPCODE) | + ((td->frame & 0xFFFF) << DW2_STARTFRM)); + etd_writel(imx21, etd_num, 3, + (TD_NOTACCESSED << DW3_COMPCODE0) | + (td->len << DW3_PKTLEN0)); + + activate_etd(imx21, etd_num, dir); + } +} + +static void isoc_etd_done(struct usb_hcd *hcd, int etd_num) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + int etd_mask = 1 << etd_num; + struct etd_priv *etd = imx21->etd + etd_num; + struct urb *urb = etd->urb; + struct urb_priv *urb_priv = urb->hcpriv; + struct td *td = etd->td; + struct usb_host_endpoint *ep = etd->ep; + int isoc_index = td->isoc_index; + unsigned int pipe = urb->pipe; + int dir_in = usb_pipein(pipe); + int cc; + int bytes_xfrd; + + disactivate_etd(imx21, etd_num); + + cc = (etd_readl(imx21, etd_num, 3) >> DW3_COMPCODE0) & 0xf; + bytes_xfrd = etd_readl(imx21, etd_num, 3) & 0x3ff; + + /* Input doesn't always fill the buffer, don't generate an error + * when this happens. + */ + if (dir_in && (cc == TD_DATAUNDERRUN)) + cc = TD_CC_NOERROR; + + if (cc == TD_NOTACCESSED) + bytes_xfrd = 0; + + debug_isoc_completed(imx21, + imx21_hc_get_frame(hcd), td, cc, bytes_xfrd); + if (cc) { + urb_priv->isoc_status = -EXDEV; + dev_dbg(imx21->dev, + "bad iso cc=0x%X frame=%d sched frame=%d " + "cnt=%d len=%d urb=%p etd=%d index=%d\n", + cc, imx21_hc_get_frame(hcd), td->frame, + bytes_xfrd, td->len, urb, etd_num, isoc_index); + } + + if (dir_in) { + clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask); + if (!etd->dma_handle) + memcpy_fromio(etd->cpu_buffer, + imx21->regs + USBOTG_DMEM + etd->dmem_offset, + bytes_xfrd); + } + + urb->actual_length += bytes_xfrd; + urb->iso_frame_desc[isoc_index].actual_length = bytes_xfrd; + urb->iso_frame_desc[isoc_index].status = cc_to_error[cc]; + + etd->td = NULL; + etd->urb = NULL; + etd->ep = NULL; + + if (--urb_priv->isoc_remaining == 0) + urb_done(hcd, urb, urb_priv->isoc_status); + + schedule_isoc_etds(hcd, ep); +} + +static struct ep_priv *alloc_isoc_ep( + struct imx21 *imx21, struct usb_host_endpoint *ep) +{ + struct ep_priv *ep_priv; + int i; + + ep_priv = kzalloc(sizeof(struct ep_priv), GFP_ATOMIC); + if (!ep_priv) + return NULL; + + for (i = 0; i < NUM_ISO_ETDS; i++) + ep_priv->etd[i] = -1; + + INIT_LIST_HEAD(&ep_priv->td_list); + ep_priv->ep = ep; + ep->hcpriv = ep_priv; + return ep_priv; +} + +static int alloc_isoc_etds(struct imx21 *imx21, struct ep_priv *ep_priv) +{ + int i, j; + int etd_num; + + /* Allocate the ETDs if required */ + for (i = 0; i < NUM_ISO_ETDS; i++) { + if (ep_priv->etd[i] < 0) { + etd_num = alloc_etd(imx21); + if (etd_num < 0) + goto alloc_etd_failed; + + ep_priv->etd[i] = etd_num; + imx21->etd[etd_num].ep = ep_priv->ep; + } + } + return 0; + +alloc_etd_failed: + dev_err(imx21->dev, "isoc: Couldn't allocate etd\n"); + for (j = 0; j < i; j++) { + free_etd(imx21, ep_priv->etd[j]); + ep_priv->etd[j] = -1; + } + return -ENOMEM; +} + +static int imx21_hc_urb_enqueue_isoc(struct usb_hcd *hcd, + struct usb_host_endpoint *ep, + struct urb *urb, gfp_t mem_flags) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + struct urb_priv *urb_priv; + unsigned long flags; + struct ep_priv *ep_priv; + struct td *td = NULL; + int i; + int ret; + int cur_frame; + u16 maxpacket; + + urb_priv = kzalloc(sizeof(struct urb_priv), mem_flags); + if (urb_priv == NULL) + return -ENOMEM; + + urb_priv->isoc_td = kzalloc( + sizeof(struct td) * urb->number_of_packets, mem_flags); + if (urb_priv->isoc_td == NULL) { + ret = -ENOMEM; + goto alloc_td_failed; + } + + spin_lock_irqsave(&imx21->lock, flags); + + if (ep->hcpriv == NULL) { + ep_priv = alloc_isoc_ep(imx21, ep); + if (ep_priv == NULL) { + ret = -ENOMEM; + goto alloc_ep_failed; + } + } else { + ep_priv = ep->hcpriv; + } + + ret = alloc_isoc_etds(imx21, ep_priv); + if (ret) + goto alloc_etd_failed; + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto link_failed; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->error_count = 0; + urb->hcpriv = urb_priv; + urb_priv->ep = ep; + + /* allocate data memory for largest packets if not already done */ + maxpacket = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + for (i = 0; i < NUM_ISO_ETDS; i++) { + struct etd_priv *etd = &imx21->etd[ep_priv->etd[i]]; + + if (etd->dmem_size > 0 && etd->dmem_size < maxpacket) { + /* not sure if this can really occur.... */ + dev_err(imx21->dev, "increasing isoc buffer %d->%d\n", + etd->dmem_size, maxpacket); + ret = -EMSGSIZE; + goto alloc_dmem_failed; + } + + if (etd->dmem_size == 0) { + etd->dmem_offset = alloc_dmem(imx21, maxpacket, ep); + if (etd->dmem_offset < 0) { + dev_dbg(imx21->dev, "failed alloc isoc dmem\n"); + ret = -EAGAIN; + goto alloc_dmem_failed; + } + etd->dmem_size = maxpacket; + } + } + + /* calculate frame */ + cur_frame = imx21_hc_get_frame(hcd); + if (urb->transfer_flags & URB_ISO_ASAP) { + if (list_empty(&ep_priv->td_list)) + urb->start_frame = cur_frame + 5; + else + urb->start_frame = list_entry( + ep_priv->td_list.prev, + struct td, list)->frame + urb->interval; + } + urb->start_frame = wrap_frame(urb->start_frame); + if (frame_after(cur_frame, urb->start_frame)) { + dev_dbg(imx21->dev, + "enqueue: adjusting iso start %d (cur=%d) asap=%d\n", + urb->start_frame, cur_frame, + (urb->transfer_flags & URB_ISO_ASAP) != 0); + urb->start_frame = wrap_frame(cur_frame + 1); + } + + /* set up transfers */ + td = urb_priv->isoc_td; + for (i = 0; i < urb->number_of_packets; i++, td++) { + unsigned int offset = urb->iso_frame_desc[i].offset; + td->ep = ep; + td->urb = urb; + td->len = urb->iso_frame_desc[i].length; + td->isoc_index = i; + td->frame = wrap_frame(urb->start_frame + urb->interval * i); + td->dma_handle = urb->transfer_dma + offset; + td->cpu_buffer = urb->transfer_buffer + offset; + list_add_tail(&td->list, &ep_priv->td_list); + } + + urb_priv->isoc_remaining = urb->number_of_packets; + dev_vdbg(imx21->dev, "setup %d packets for iso frame %d->%d\n", + urb->number_of_packets, urb->start_frame, td->frame); + + debug_urb_submitted(imx21, urb); + schedule_isoc_etds(hcd, ep); + + spin_unlock_irqrestore(&imx21->lock, flags); + return 0; + +alloc_dmem_failed: + usb_hcd_unlink_urb_from_ep(hcd, urb); + +link_failed: +alloc_etd_failed: +alloc_ep_failed: + spin_unlock_irqrestore(&imx21->lock, flags); + kfree(urb_priv->isoc_td); + +alloc_td_failed: + kfree(urb_priv); + return ret; +} + +static void dequeue_isoc_urb(struct imx21 *imx21, + struct urb *urb, struct ep_priv *ep_priv) +{ + struct urb_priv *urb_priv = urb->hcpriv; + struct td *td, *tmp; + int i; + + if (urb_priv->active) { + for (i = 0; i < NUM_ISO_ETDS; i++) { + int etd_num = ep_priv->etd[i]; + if (etd_num != -1 && imx21->etd[etd_num].urb == urb) { + struct etd_priv *etd = imx21->etd + etd_num; + + reset_etd(imx21, etd_num); + free_dmem(imx21, etd); + } + } + } + + list_for_each_entry_safe(td, tmp, &ep_priv->td_list, list) { + if (td->urb == urb) { + dev_vdbg(imx21->dev, "removing td %p\n", td); + list_del(&td->list); + } + } +} + +/* =========================================== */ +/* NON ISOC Handling ... */ +/* =========================================== */ + +static void schedule_nonisoc_etd(struct imx21 *imx21, struct urb *urb) +{ + unsigned int pipe = urb->pipe; + struct urb_priv *urb_priv = urb->hcpriv; + struct ep_priv *ep_priv = urb_priv->ep->hcpriv; + int state = urb_priv->state; + int etd_num = ep_priv->etd[0]; + struct etd_priv *etd; + u32 count; + u16 etd_buf_size; + u16 maxpacket; + u8 dir; + u8 bufround; + u8 datatoggle; + u8 interval = 0; + u8 relpolpos = 0; + + if (etd_num < 0) { + dev_err(imx21->dev, "No valid ETD\n"); + return; + } + if (readl(imx21->regs + USBH_ETDENSET) & (1 << etd_num)) + dev_err(imx21->dev, "submitting to active ETD %d\n", etd_num); + + etd = &imx21->etd[etd_num]; + maxpacket = usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)); + if (!maxpacket) + maxpacket = 8; + + if (usb_pipecontrol(pipe) && (state != US_CTRL_DATA)) { + if (state == US_CTRL_SETUP) { + dir = TD_DIR_SETUP; + if (unsuitable_for_dma(urb->setup_dma)) + usb_hcd_unmap_urb_setup_for_dma(imx21->hcd, + urb); + etd->dma_handle = urb->setup_dma; + etd->cpu_buffer = urb->setup_packet; + bufround = 0; + count = 8; + datatoggle = TD_TOGGLE_DATA0; + } else { /* US_CTRL_ACK */ + dir = usb_pipeout(pipe) ? TD_DIR_IN : TD_DIR_OUT; + bufround = 0; + count = 0; + datatoggle = TD_TOGGLE_DATA1; + } + } else { + dir = usb_pipeout(pipe) ? TD_DIR_OUT : TD_DIR_IN; + bufround = (dir == TD_DIR_IN) ? 1 : 0; + if (unsuitable_for_dma(urb->transfer_dma)) + usb_hcd_unmap_urb_for_dma(imx21->hcd, urb); + + etd->dma_handle = urb->transfer_dma; + etd->cpu_buffer = urb->transfer_buffer; + if (usb_pipebulk(pipe) && (state == US_BULK0)) + count = 0; + else + count = urb->transfer_buffer_length; + + if (usb_pipecontrol(pipe)) { + datatoggle = TD_TOGGLE_DATA1; + } else { + if (usb_gettoggle( + urb->dev, + usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe))) + datatoggle = TD_TOGGLE_DATA1; + else + datatoggle = TD_TOGGLE_DATA0; + } + } + + etd->urb = urb; + etd->ep = urb_priv->ep; + etd->len = count; + + if (usb_pipeint(pipe)) { + interval = urb->interval; + relpolpos = (readl(imx21->regs + USBH_FRMNUB) + 1) & 0xff; + } + + /* Write ETD to device memory */ + setup_etd_dword0(imx21, etd_num, urb, dir, maxpacket); + + etd_writel(imx21, etd_num, 2, + (u32) interval << DW2_POLINTERV | + ((u32) relpolpos << DW2_RELPOLPOS) | + ((u32) dir << DW2_DIRPID) | + ((u32) bufround << DW2_BUFROUND) | + ((u32) datatoggle << DW2_DATATOG) | + ((u32) TD_NOTACCESSED << DW2_COMPCODE)); + + /* DMA will always transfer buffer size even if TOBYCNT in DWORD3 + is smaller. Make sure we don't overrun the buffer! + */ + if (count && count < maxpacket) + etd_buf_size = count; + else + etd_buf_size = maxpacket; + + etd_writel(imx21, etd_num, 3, + ((u32) (etd_buf_size - 1) << DW3_BUFSIZE) | (u32) count); + + if (!count) + etd->dma_handle = 0; + + /* allocate x and y buffer space at once */ + etd->dmem_size = (count > maxpacket) ? maxpacket * 2 : maxpacket; + etd->dmem_offset = alloc_dmem(imx21, etd->dmem_size, urb_priv->ep); + if (etd->dmem_offset < 0) { + /* Setup everything we can in HW and update when we get DMEM */ + etd_writel(imx21, etd_num, 1, (u32)maxpacket << 16); + + dev_dbg(imx21->dev, "Queuing etd %d for DMEM\n", etd_num); + debug_urb_queued_for_dmem(imx21, urb); + list_add_tail(&etd->queue, &imx21->queue_for_dmem); + return; + } + + etd_writel(imx21, etd_num, 1, + (((u32) etd->dmem_offset + (u32) maxpacket) << DW1_YBUFSRTAD) | + (u32) etd->dmem_offset); + + urb_priv->active = 1; + + /* enable the ETD to kick off transfer */ + dev_vdbg(imx21->dev, "Activating etd %d for %d bytes %s\n", + etd_num, count, dir != TD_DIR_IN ? "out" : "in"); + activate_etd(imx21, etd_num, dir); + +} + +static void nonisoc_etd_done(struct usb_hcd *hcd, int etd_num) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + struct etd_priv *etd = &imx21->etd[etd_num]; + struct urb *urb = etd->urb; + u32 etd_mask = 1 << etd_num; + struct urb_priv *urb_priv = urb->hcpriv; + int dir; + int cc; + u32 bytes_xfrd; + int etd_done; + + disactivate_etd(imx21, etd_num); + + dir = (etd_readl(imx21, etd_num, 0) >> DW0_DIRECT) & 0x3; + cc = (etd_readl(imx21, etd_num, 2) >> DW2_COMPCODE) & 0xf; + bytes_xfrd = etd->len - (etd_readl(imx21, etd_num, 3) & 0x1fffff); + + /* save toggle carry */ + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), + (etd_readl(imx21, etd_num, 0) >> DW0_TOGCRY) & 0x1); + + if (dir == TD_DIR_IN) { + clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask); + clear_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask); + + if (etd->bounce_buffer) { + memcpy(etd->cpu_buffer, etd->bounce_buffer, bytes_xfrd); + dma_unmap_single(imx21->dev, + etd->dma_handle, etd->len, DMA_FROM_DEVICE); + } else if (!etd->dma_handle && bytes_xfrd) {/* PIO */ + memcpy_fromio(etd->cpu_buffer, + imx21->regs + USBOTG_DMEM + etd->dmem_offset, + bytes_xfrd); + } + } + + kfree(etd->bounce_buffer); + etd->bounce_buffer = NULL; + free_dmem(imx21, etd); + + urb->error_count = 0; + if (!(urb->transfer_flags & URB_SHORT_NOT_OK) + && (cc == TD_DATAUNDERRUN)) + cc = TD_CC_NOERROR; + + if (cc != 0) + dev_vdbg(imx21->dev, "cc is 0x%x\n", cc); + + etd_done = (cc_to_error[cc] != 0); /* stop if error */ + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + switch (urb_priv->state) { + case US_CTRL_SETUP: + if (urb->transfer_buffer_length > 0) + urb_priv->state = US_CTRL_DATA; + else + urb_priv->state = US_CTRL_ACK; + break; + case US_CTRL_DATA: + urb->actual_length += bytes_xfrd; + urb_priv->state = US_CTRL_ACK; + break; + case US_CTRL_ACK: + etd_done = 1; + break; + default: + dev_err(imx21->dev, + "Invalid pipe state %d\n", urb_priv->state); + etd_done = 1; + break; + } + break; + + case PIPE_BULK: + urb->actual_length += bytes_xfrd; + if ((urb_priv->state == US_BULK) + && (urb->transfer_flags & URB_ZERO_PACKET) + && urb->transfer_buffer_length > 0 + && ((urb->transfer_buffer_length % + usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe))) == 0)) { + /* need a 0-packet */ + urb_priv->state = US_BULK0; + } else { + etd_done = 1; + } + break; + + case PIPE_INTERRUPT: + urb->actual_length += bytes_xfrd; + etd_done = 1; + break; + } + + if (etd_done) + nonisoc_urb_completed_for_etd(imx21, etd, cc_to_error[cc]); + else { + dev_vdbg(imx21->dev, "next state=%d\n", urb_priv->state); + schedule_nonisoc_etd(imx21, urb); + } +} + + +static struct ep_priv *alloc_ep(void) +{ + int i; + struct ep_priv *ep_priv; + + ep_priv = kzalloc(sizeof(struct ep_priv), GFP_ATOMIC); + if (!ep_priv) + return NULL; + + for (i = 0; i < NUM_ISO_ETDS; ++i) + ep_priv->etd[i] = -1; + + return ep_priv; +} + +static int imx21_hc_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, gfp_t mem_flags) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + struct usb_host_endpoint *ep = urb->ep; + struct urb_priv *urb_priv; + struct ep_priv *ep_priv; + struct etd_priv *etd; + int ret; + unsigned long flags; + + dev_vdbg(imx21->dev, + "enqueue urb=%p ep=%p len=%d " + "buffer=%p dma=%08X setupBuf=%p setupDma=%08X\n", + urb, ep, + urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma, + urb->setup_packet, urb->setup_dma); + + if (usb_pipeisoc(urb->pipe)) + return imx21_hc_urb_enqueue_isoc(hcd, ep, urb, mem_flags); + + urb_priv = kzalloc(sizeof(struct urb_priv), mem_flags); + if (!urb_priv) + return -ENOMEM; + + spin_lock_irqsave(&imx21->lock, flags); + + ep_priv = ep->hcpriv; + if (ep_priv == NULL) { + ep_priv = alloc_ep(); + if (!ep_priv) { + ret = -ENOMEM; + goto failed_alloc_ep; + } + ep->hcpriv = ep_priv; + ep_priv->ep = ep; + } + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto failed_link; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->error_count = 0; + urb->hcpriv = urb_priv; + urb_priv->ep = ep; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + urb_priv->state = US_CTRL_SETUP; + break; + case PIPE_BULK: + urb_priv->state = US_BULK; + break; + } + + debug_urb_submitted(imx21, urb); + if (ep_priv->etd[0] < 0) { + if (ep_priv->waiting_etd) { + dev_dbg(imx21->dev, + "no ETD available already queued %p\n", + ep_priv); + debug_urb_queued_for_etd(imx21, urb); + goto out; + } + ep_priv->etd[0] = alloc_etd(imx21); + if (ep_priv->etd[0] < 0) { + dev_dbg(imx21->dev, + "no ETD available queueing %p\n", ep_priv); + debug_urb_queued_for_etd(imx21, urb); + list_add_tail(&ep_priv->queue, &imx21->queue_for_etd); + ep_priv->waiting_etd = 1; + goto out; + } + } + + /* Schedule if no URB already active for this endpoint */ + etd = &imx21->etd[ep_priv->etd[0]]; + if (etd->urb == NULL) { + DEBUG_LOG_FRAME(imx21, etd, last_req); + schedule_nonisoc_etd(imx21, urb); + } + +out: + spin_unlock_irqrestore(&imx21->lock, flags); + return 0; + +failed_link: +failed_alloc_ep: + spin_unlock_irqrestore(&imx21->lock, flags); + kfree(urb_priv); + return ret; +} + +static int imx21_hc_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + unsigned long flags; + struct usb_host_endpoint *ep; + struct ep_priv *ep_priv; + struct urb_priv *urb_priv = urb->hcpriv; + int ret = -EINVAL; + + dev_vdbg(imx21->dev, "dequeue urb=%p iso=%d status=%d\n", + urb, usb_pipeisoc(urb->pipe), status); + + spin_lock_irqsave(&imx21->lock, flags); + + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) + goto fail; + ep = urb_priv->ep; + ep_priv = ep->hcpriv; + + debug_urb_unlinked(imx21, urb); + + if (usb_pipeisoc(urb->pipe)) { + dequeue_isoc_urb(imx21, urb, ep_priv); + schedule_isoc_etds(hcd, ep); + } else if (urb_priv->active) { + int etd_num = ep_priv->etd[0]; + if (etd_num != -1) { + struct etd_priv *etd = &imx21->etd[etd_num]; + + disactivate_etd(imx21, etd_num); + free_dmem(imx21, etd); + etd->urb = NULL; + kfree(etd->bounce_buffer); + etd->bounce_buffer = NULL; + } + } + + urb_done(hcd, urb, status); + + spin_unlock_irqrestore(&imx21->lock, flags); + return 0; + +fail: + spin_unlock_irqrestore(&imx21->lock, flags); + return ret; +} + +/* =========================================== */ +/* Interrupt dispatch */ +/* =========================================== */ + +static void process_etds(struct usb_hcd *hcd, struct imx21 *imx21, int sof) +{ + int etd_num; + int enable_sof_int = 0; + unsigned long flags; + + spin_lock_irqsave(&imx21->lock, flags); + + for (etd_num = 0; etd_num < USB_NUM_ETD; etd_num++) { + u32 etd_mask = 1 << etd_num; + u32 enabled = readl(imx21->regs + USBH_ETDENSET) & etd_mask; + u32 done = readl(imx21->regs + USBH_ETDDONESTAT) & etd_mask; + struct etd_priv *etd = &imx21->etd[etd_num]; + + + if (done) { + DEBUG_LOG_FRAME(imx21, etd, last_int); + } else { +/* + * Kludge warning! + * + * When multiple transfers are using the bus we sometimes get into a state + * where the transfer has completed (the CC field of the ETD is != 0x0F), + * the ETD has self disabled but the ETDDONESTAT flag is not set + * (and hence no interrupt occurs). + * This causes the transfer in question to hang. + * The kludge below checks for this condition at each SOF and processes any + * blocked ETDs (after an arbitrary 10 frame wait) + * + * With a single active transfer the usbtest test suite will run for days + * without the kludge. + * With other bus activity (eg mass storage) even just test1 will hang without + * the kludge. + */ + u32 dword0; + int cc; + + if (etd->active_count && !enabled) /* suspicious... */ + enable_sof_int = 1; + + if (!sof || enabled || !etd->active_count) + continue; + + cc = etd_readl(imx21, etd_num, 2) >> DW2_COMPCODE; + if (cc == TD_NOTACCESSED) + continue; + + if (++etd->active_count < 10) + continue; + + dword0 = etd_readl(imx21, etd_num, 0); + dev_dbg(imx21->dev, + "unblock ETD %d dev=0x%X ep=0x%X cc=0x%02X!\n", + etd_num, dword0 & 0x7F, + (dword0 >> DW0_ENDPNT) & 0x0F, + cc); + +#ifdef DEBUG + dev_dbg(imx21->dev, + "frame: act=%d disact=%d" + " int=%d req=%d cur=%d\n", + etd->activated_frame, + etd->disactivated_frame, + etd->last_int_frame, + etd->last_req_frame, + readl(imx21->regs + USBH_FRMNUB)); + imx21->debug_unblocks++; +#endif + etd->active_count = 0; +/* End of kludge */ + } + + if (etd->ep == NULL || etd->urb == NULL) { + dev_dbg(imx21->dev, + "Interrupt for unexpected etd %d" + " ep=%p urb=%p\n", + etd_num, etd->ep, etd->urb); + disactivate_etd(imx21, etd_num); + continue; + } + + if (usb_pipeisoc(etd->urb->pipe)) + isoc_etd_done(hcd, etd_num); + else + nonisoc_etd_done(hcd, etd_num); + } + + /* only enable SOF interrupt if it may be needed for the kludge */ + if (enable_sof_int) + set_register_bits(imx21, USBH_SYSIEN, USBH_SYSIEN_SOFINT); + else + clear_register_bits(imx21, USBH_SYSIEN, USBH_SYSIEN_SOFINT); + + + spin_unlock_irqrestore(&imx21->lock, flags); +} + +static irqreturn_t imx21_irq(struct usb_hcd *hcd) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + u32 ints = readl(imx21->regs + USBH_SYSISR); + + if (ints & USBH_SYSIEN_HERRINT) + dev_dbg(imx21->dev, "Scheduling error\n"); + + if (ints & USBH_SYSIEN_SORINT) + dev_dbg(imx21->dev, "Scheduling overrun\n"); + + if (ints & (USBH_SYSISR_DONEINT | USBH_SYSISR_SOFINT)) + process_etds(hcd, imx21, ints & USBH_SYSISR_SOFINT); + + writel(ints, imx21->regs + USBH_SYSISR); + return IRQ_HANDLED; +} + +static void imx21_hc_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + unsigned long flags; + struct ep_priv *ep_priv; + int i; + + if (ep == NULL) + return; + + spin_lock_irqsave(&imx21->lock, flags); + ep_priv = ep->hcpriv; + dev_vdbg(imx21->dev, "disable ep=%p, ep->hcpriv=%p\n", ep, ep_priv); + + if (!list_empty(&ep->urb_list)) + dev_dbg(imx21->dev, "ep's URB list is not empty\n"); + + if (ep_priv != NULL) { + for (i = 0; i < NUM_ISO_ETDS; i++) { + if (ep_priv->etd[i] > -1) + dev_dbg(imx21->dev, "free etd %d for disable\n", + ep_priv->etd[i]); + + free_etd(imx21, ep_priv->etd[i]); + } + kfree(ep_priv); + ep->hcpriv = NULL; + } + + for (i = 0; i < USB_NUM_ETD; i++) { + if (imx21->etd[i].alloc && imx21->etd[i].ep == ep) { + dev_err(imx21->dev, + "Active etd %d for disabled ep=%p!\n", i, ep); + free_etd(imx21, i); + } + } + free_epdmem(imx21, ep); + spin_unlock_irqrestore(&imx21->lock, flags); +} + +/* =========================================== */ +/* Hub handling */ +/* =========================================== */ + +static int get_hub_descriptor(struct usb_hcd *hcd, + struct usb_hub_descriptor *desc) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + desc->bDescriptorType = 0x29; /* HUB descriptor */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = readl(imx21->regs + USBH_ROOTHUBA) + & USBH_ROOTHUBA_NDNSTMPRT_MASK; + desc->bDescLength = 9; + desc->bPwrOn2PwrGood = 0; + desc->wHubCharacteristics = (__force __u16) cpu_to_le16( + 0x0002 | /* No power switching */ + 0x0010 | /* No over current protection */ + 0); + + desc->u.hs.DeviceRemovable[0] = 1 << 1; + desc->u.hs.DeviceRemovable[1] = ~0; + return 0; +} + +static int imx21_hc_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + int ports; + int changed = 0; + int i; + unsigned long flags; + + spin_lock_irqsave(&imx21->lock, flags); + ports = readl(imx21->regs + USBH_ROOTHUBA) + & USBH_ROOTHUBA_NDNSTMPRT_MASK; + if (ports > 7) { + ports = 7; + dev_err(imx21->dev, "ports %d > 7\n", ports); + } + for (i = 0; i < ports; i++) { + if (readl(imx21->regs + USBH_PORTSTAT(i)) & + (USBH_PORTSTAT_CONNECTSC | + USBH_PORTSTAT_PRTENBLSC | + USBH_PORTSTAT_PRTSTATSC | + USBH_PORTSTAT_OVRCURIC | + USBH_PORTSTAT_PRTRSTSC)) { + + changed = 1; + buf[0] |= 1 << (i + 1); + } + } + spin_unlock_irqrestore(&imx21->lock, flags); + + if (changed) + dev_info(imx21->dev, "Hub status changed\n"); + return changed; +} + +static int imx21_hc_hub_control(struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + int rc = 0; + u32 status_write = 0; + + switch (typeReq) { + case ClearHubFeature: + dev_dbg(imx21->dev, "ClearHubFeature\n"); + switch (wValue) { + case C_HUB_OVER_CURRENT: + dev_dbg(imx21->dev, " OVER_CURRENT\n"); + break; + case C_HUB_LOCAL_POWER: + dev_dbg(imx21->dev, " LOCAL_POWER\n"); + break; + default: + dev_dbg(imx21->dev, " unknown\n"); + rc = -EINVAL; + break; + } + break; + + case ClearPortFeature: + dev_dbg(imx21->dev, "ClearPortFeature\n"); + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + dev_dbg(imx21->dev, " ENABLE\n"); + status_write = USBH_PORTSTAT_CURCONST; + break; + case USB_PORT_FEAT_SUSPEND: + dev_dbg(imx21->dev, " SUSPEND\n"); + status_write = USBH_PORTSTAT_PRTOVRCURI; + break; + case USB_PORT_FEAT_POWER: + dev_dbg(imx21->dev, " POWER\n"); + status_write = USBH_PORTSTAT_LSDEVCON; + break; + case USB_PORT_FEAT_C_ENABLE: + dev_dbg(imx21->dev, " C_ENABLE\n"); + status_write = USBH_PORTSTAT_PRTENBLSC; + break; + case USB_PORT_FEAT_C_SUSPEND: + dev_dbg(imx21->dev, " C_SUSPEND\n"); + status_write = USBH_PORTSTAT_PRTSTATSC; + break; + case USB_PORT_FEAT_C_CONNECTION: + dev_dbg(imx21->dev, " C_CONNECTION\n"); + status_write = USBH_PORTSTAT_CONNECTSC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(imx21->dev, " C_OVER_CURRENT\n"); + status_write = USBH_PORTSTAT_OVRCURIC; + break; + case USB_PORT_FEAT_C_RESET: + dev_dbg(imx21->dev, " C_RESET\n"); + status_write = USBH_PORTSTAT_PRTRSTSC; + break; + default: + dev_dbg(imx21->dev, " unknown\n"); + rc = -EINVAL; + break; + } + + break; + + case GetHubDescriptor: + dev_dbg(imx21->dev, "GetHubDescriptor\n"); + rc = get_hub_descriptor(hcd, (void *)buf); + break; + + case GetHubStatus: + dev_dbg(imx21->dev, " GetHubStatus\n"); + *(__le32 *) buf = 0; + break; + + case GetPortStatus: + dev_dbg(imx21->dev, "GetPortStatus: port: %d, 0x%x\n", + wIndex, USBH_PORTSTAT(wIndex - 1)); + *(__le32 *) buf = readl(imx21->regs + + USBH_PORTSTAT(wIndex - 1)); + break; + + case SetHubFeature: + dev_dbg(imx21->dev, "SetHubFeature\n"); + switch (wValue) { + case C_HUB_OVER_CURRENT: + dev_dbg(imx21->dev, " OVER_CURRENT\n"); + break; + + case C_HUB_LOCAL_POWER: + dev_dbg(imx21->dev, " LOCAL_POWER\n"); + break; + default: + dev_dbg(imx21->dev, " unknown\n"); + rc = -EINVAL; + break; + } + + break; + + case SetPortFeature: + dev_dbg(imx21->dev, "SetPortFeature\n"); + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + dev_dbg(imx21->dev, " SUSPEND\n"); + status_write = USBH_PORTSTAT_PRTSUSPST; + break; + case USB_PORT_FEAT_POWER: + dev_dbg(imx21->dev, " POWER\n"); + status_write = USBH_PORTSTAT_PRTPWRST; + break; + case USB_PORT_FEAT_RESET: + dev_dbg(imx21->dev, " RESET\n"); + status_write = USBH_PORTSTAT_PRTRSTST; + break; + default: + dev_dbg(imx21->dev, " unknown\n"); + rc = -EINVAL; + break; + } + break; + + default: + dev_dbg(imx21->dev, " unknown\n"); + rc = -EINVAL; + break; + } + + if (status_write) + writel(status_write, imx21->regs + USBH_PORTSTAT(wIndex - 1)); + return rc; +} + +/* =========================================== */ +/* Host controller management */ +/* =========================================== */ + +static int imx21_hc_reset(struct usb_hcd *hcd) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + unsigned long timeout; + unsigned long flags; + + spin_lock_irqsave(&imx21->lock, flags); + + /* Reset the Host controller modules */ + writel(USBOTG_RST_RSTCTRL | USBOTG_RST_RSTRH | + USBOTG_RST_RSTHSIE | USBOTG_RST_RSTHC, + imx21->regs + USBOTG_RST_CTRL); + + /* Wait for reset to finish */ + timeout = jiffies + HZ; + while (readl(imx21->regs + USBOTG_RST_CTRL) != 0) { + if (time_after(jiffies, timeout)) { + spin_unlock_irqrestore(&imx21->lock, flags); + dev_err(imx21->dev, "timeout waiting for reset\n"); + return -ETIMEDOUT; + } + spin_unlock_irq(&imx21->lock); + schedule_timeout_uninterruptible(1); + spin_lock_irq(&imx21->lock); + } + spin_unlock_irqrestore(&imx21->lock, flags); + return 0; +} + +static int imx21_hc_start(struct usb_hcd *hcd) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + unsigned long flags; + int i, j; + u32 hw_mode = USBOTG_HWMODE_CRECFG_HOST; + u32 usb_control = 0; + + hw_mode |= ((imx21->pdata->host_xcvr << USBOTG_HWMODE_HOSTXCVR_SHIFT) & + USBOTG_HWMODE_HOSTXCVR_MASK); + hw_mode |= ((imx21->pdata->otg_xcvr << USBOTG_HWMODE_OTGXCVR_SHIFT) & + USBOTG_HWMODE_OTGXCVR_MASK); + + if (imx21->pdata->host1_txenoe) + usb_control |= USBCTRL_HOST1_TXEN_OE; + + if (!imx21->pdata->host1_xcverless) + usb_control |= USBCTRL_HOST1_BYP_TLL; + + if (imx21->pdata->otg_ext_xcvr) + usb_control |= USBCTRL_OTC_RCV_RXDP; + + + spin_lock_irqsave(&imx21->lock, flags); + + writel((USBOTG_CLK_CTRL_HST | USBOTG_CLK_CTRL_MAIN), + imx21->regs + USBOTG_CLK_CTRL); + writel(hw_mode, imx21->regs + USBOTG_HWMODE); + writel(usb_control, imx21->regs + USBCTRL); + writel(USB_MISCCONTROL_SKPRTRY | USB_MISCCONTROL_ARBMODE, + imx21->regs + USB_MISCCONTROL); + + /* Clear the ETDs */ + for (i = 0; i < USB_NUM_ETD; i++) + for (j = 0; j < 4; j++) + etd_writel(imx21, i, j, 0); + + /* Take the HC out of reset */ + writel(USBH_HOST_CTRL_HCUSBSTE_OPERATIONAL | USBH_HOST_CTRL_CTLBLKSR_1, + imx21->regs + USBH_HOST_CTRL); + + /* Enable ports */ + if (imx21->pdata->enable_otg_host) + writel(USBH_PORTSTAT_PRTPWRST | USBH_PORTSTAT_PRTENABST, + imx21->regs + USBH_PORTSTAT(0)); + + if (imx21->pdata->enable_host1) + writel(USBH_PORTSTAT_PRTPWRST | USBH_PORTSTAT_PRTENABST, + imx21->regs + USBH_PORTSTAT(1)); + + if (imx21->pdata->enable_host2) + writel(USBH_PORTSTAT_PRTPWRST | USBH_PORTSTAT_PRTENABST, + imx21->regs + USBH_PORTSTAT(2)); + + + hcd->state = HC_STATE_RUNNING; + + /* Enable host controller interrupts */ + set_register_bits(imx21, USBH_SYSIEN, + USBH_SYSIEN_HERRINT | + USBH_SYSIEN_DONEINT | USBH_SYSIEN_SORINT); + set_register_bits(imx21, USBOTG_CINT_STEN, USBOTG_HCINT); + + spin_unlock_irqrestore(&imx21->lock, flags); + + return 0; +} + +static void imx21_hc_stop(struct usb_hcd *hcd) +{ + struct imx21 *imx21 = hcd_to_imx21(hcd); + unsigned long flags; + + spin_lock_irqsave(&imx21->lock, flags); + + writel(0, imx21->regs + USBH_SYSIEN); + clear_register_bits(imx21, USBOTG_CINT_STEN, USBOTG_HCINT); + clear_register_bits(imx21, USBOTG_CLK_CTRL_HST | USBOTG_CLK_CTRL_MAIN, + USBOTG_CLK_CTRL); + spin_unlock_irqrestore(&imx21->lock, flags); +} + +/* =========================================== */ +/* Driver glue */ +/* =========================================== */ + +static struct hc_driver imx21_hc_driver = { + .description = hcd_name, + .product_desc = "IMX21 USB Host Controller", + .hcd_priv_size = sizeof(struct imx21), + + .flags = HCD_USB11, + .irq = imx21_irq, + + .reset = imx21_hc_reset, + .start = imx21_hc_start, + .stop = imx21_hc_stop, + + /* I/O requests */ + .urb_enqueue = imx21_hc_urb_enqueue, + .urb_dequeue = imx21_hc_urb_dequeue, + .endpoint_disable = imx21_hc_endpoint_disable, + + /* scheduling support */ + .get_frame_number = imx21_hc_get_frame, + + /* Root hub support */ + .hub_status_data = imx21_hc_hub_status_data, + .hub_control = imx21_hc_hub_control, + +}; + +static struct mx21_usbh_platform_data default_pdata = { + .host_xcvr = MX21_USBXCVR_TXDIF_RXDIF, + .otg_xcvr = MX21_USBXCVR_TXDIF_RXDIF, + .enable_host1 = 1, + .enable_host2 = 1, + .enable_otg_host = 1, + +}; + +static int imx21_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct imx21 *imx21 = hcd_to_imx21(hcd); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + remove_debug_files(imx21); + usb_remove_hcd(hcd); + + if (res != NULL) { + clk_disable_unprepare(imx21->clk); + clk_put(imx21->clk); + iounmap(imx21->regs); + release_mem_region(res->start, resource_size(res)); + } + + kfree(hcd); + return 0; +} + + +static int imx21_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct imx21 *imx21; + struct resource *res; + int ret; + int irq; + + printk(KERN_INFO "%s\n", imx21_hc_driver.product_desc); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENXIO; + + hcd = usb_create_hcd(&imx21_hc_driver, + &pdev->dev, dev_name(&pdev->dev)); + if (hcd == NULL) { + dev_err(&pdev->dev, "Cannot create hcd (%s)\n", + dev_name(&pdev->dev)); + return -ENOMEM; + } + + imx21 = hcd_to_imx21(hcd); + imx21->hcd = hcd; + imx21->dev = &pdev->dev; + imx21->pdata = pdev->dev.platform_data; + if (!imx21->pdata) + imx21->pdata = &default_pdata; + + spin_lock_init(&imx21->lock); + INIT_LIST_HEAD(&imx21->dmem_list); + INIT_LIST_HEAD(&imx21->queue_for_etd); + INIT_LIST_HEAD(&imx21->queue_for_dmem); + create_debug_files(imx21); + + res = request_mem_region(res->start, resource_size(res), hcd_name); + if (!res) { + ret = -EBUSY; + goto failed_request_mem; + } + + imx21->regs = ioremap(res->start, resource_size(res)); + if (imx21->regs == NULL) { + dev_err(imx21->dev, "Cannot map registers\n"); + ret = -ENOMEM; + goto failed_ioremap; + } + + /* Enable clocks source */ + imx21->clk = clk_get(imx21->dev, NULL); + if (IS_ERR(imx21->clk)) { + dev_err(imx21->dev, "no clock found\n"); + ret = PTR_ERR(imx21->clk); + goto failed_clock_get; + } + + ret = clk_set_rate(imx21->clk, clk_round_rate(imx21->clk, 48000000)); + if (ret) + goto failed_clock_set; + ret = clk_prepare_enable(imx21->clk); + if (ret) + goto failed_clock_enable; + + dev_info(imx21->dev, "Hardware HC revision: 0x%02X\n", + (readl(imx21->regs + USBOTG_HWMODE) >> 16) & 0xFF); + + ret = usb_add_hcd(hcd, irq, 0); + if (ret != 0) { + dev_err(imx21->dev, "usb_add_hcd() returned %d\n", ret); + goto failed_add_hcd; + } + + return 0; + +failed_add_hcd: + clk_disable_unprepare(imx21->clk); +failed_clock_enable: +failed_clock_set: + clk_put(imx21->clk); +failed_clock_get: + iounmap(imx21->regs); +failed_ioremap: + release_mem_region(res->start, resource_size(res)); +failed_request_mem: + remove_debug_files(imx21); + usb_put_hcd(hcd); + return ret; +} + +static struct platform_driver imx21_hcd_driver = { + .driver = { + .name = (char *)hcd_name, + }, + .probe = imx21_probe, + .remove = imx21_remove, + .suspend = NULL, + .resume = NULL, +}; + +module_platform_driver(imx21_hcd_driver); + +MODULE_DESCRIPTION("i.MX21 USB Host controller"); +MODULE_AUTHOR("Martin Fuzzey"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx21-hcd"); diff --git a/drivers/usb/host/imx21-hcd.h b/drivers/usb/host/imx21-hcd.h new file mode 100644 index 00000000..c005770a --- /dev/null +++ b/drivers/usb/host/imx21-hcd.h @@ -0,0 +1,440 @@ +/* + * Macros and prototypes for i.MX21 + * + * Copyright (C) 2006 Loping Dog Embedded Systems + * Copyright (C) 2009 Martin Fuzzey + * Originally written by Jay Monkman + * Ported to 2.6.30, debugged and enhanced by Martin Fuzzey + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_IMX21_HCD_H__ +#define __LINUX_IMX21_HCD_H__ + +#include + +#define NUM_ISO_ETDS 2 +#define USB_NUM_ETD 32 +#define DMEM_SIZE 4096 + +/* Register definitions */ +#define USBOTG_HWMODE 0x00 +#define USBOTG_HWMODE_ANASDBEN (1 << 14) +#define USBOTG_HWMODE_OTGXCVR_SHIFT 6 +#define USBOTG_HWMODE_OTGXCVR_MASK (3 << 6) +#define USBOTG_HWMODE_OTGXCVR_TD_RD (0 << 6) +#define USBOTG_HWMODE_OTGXCVR_TS_RD (2 << 6) +#define USBOTG_HWMODE_OTGXCVR_TD_RS (1 << 6) +#define USBOTG_HWMODE_OTGXCVR_TS_RS (3 << 6) +#define USBOTG_HWMODE_HOSTXCVR_SHIFT 4 +#define USBOTG_HWMODE_HOSTXCVR_MASK (3 << 4) +#define USBOTG_HWMODE_HOSTXCVR_TD_RD (0 << 4) +#define USBOTG_HWMODE_HOSTXCVR_TS_RD (2 << 4) +#define USBOTG_HWMODE_HOSTXCVR_TD_RS (1 << 4) +#define USBOTG_HWMODE_HOSTXCVR_TS_RS (3 << 4) +#define USBOTG_HWMODE_CRECFG_MASK (3 << 0) +#define USBOTG_HWMODE_CRECFG_HOST (1 << 0) +#define USBOTG_HWMODE_CRECFG_FUNC (2 << 0) +#define USBOTG_HWMODE_CRECFG_HNP (3 << 0) + +#define USBOTG_CINT_STAT 0x04 +#define USBOTG_CINT_STEN 0x08 +#define USBOTG_ASHNPINT (1 << 5) +#define USBOTG_ASFCINT (1 << 4) +#define USBOTG_ASHCINT (1 << 3) +#define USBOTG_SHNPINT (1 << 2) +#define USBOTG_FCINT (1 << 1) +#define USBOTG_HCINT (1 << 0) + +#define USBOTG_CLK_CTRL 0x0c +#define USBOTG_CLK_CTRL_FUNC (1 << 2) +#define USBOTG_CLK_CTRL_HST (1 << 1) +#define USBOTG_CLK_CTRL_MAIN (1 << 0) + +#define USBOTG_RST_CTRL 0x10 +#define USBOTG_RST_RSTI2C (1 << 15) +#define USBOTG_RST_RSTCTRL (1 << 5) +#define USBOTG_RST_RSTFC (1 << 4) +#define USBOTG_RST_RSTFSKE (1 << 3) +#define USBOTG_RST_RSTRH (1 << 2) +#define USBOTG_RST_RSTHSIE (1 << 1) +#define USBOTG_RST_RSTHC (1 << 0) + +#define USBOTG_FRM_INTVL 0x14 +#define USBOTG_FRM_REMAIN 0x18 +#define USBOTG_HNP_CSR 0x1c +#define USBOTG_HNP_ISR 0x2c +#define USBOTG_HNP_IEN 0x30 + +#define USBOTG_I2C_TXCVR_REG(x) (0x100 + (x)) +#define USBOTG_I2C_XCVR_DEVAD 0x118 +#define USBOTG_I2C_SEQ_OP_REG 0x119 +#define USBOTG_I2C_SEQ_RD_STARTAD 0x11a +#define USBOTG_I2C_OP_CTRL_REG 0x11b +#define USBOTG_I2C_SCLK_TO_SCK_HPER 0x11e +#define USBOTG_I2C_MASTER_INT_REG 0x11f + +#define USBH_HOST_CTRL 0x80 +#define USBH_HOST_CTRL_HCRESET (1 << 31) +#define USBH_HOST_CTRL_SCHDOVR(x) ((x) << 16) +#define USBH_HOST_CTRL_RMTWUEN (1 << 4) +#define USBH_HOST_CTRL_HCUSBSTE_RESET (0 << 2) +#define USBH_HOST_CTRL_HCUSBSTE_RESUME (1 << 2) +#define USBH_HOST_CTRL_HCUSBSTE_OPERATIONAL (2 << 2) +#define USBH_HOST_CTRL_HCUSBSTE_SUSPEND (3 << 2) +#define USBH_HOST_CTRL_CTLBLKSR_1 (0 << 0) +#define USBH_HOST_CTRL_CTLBLKSR_2 (1 << 0) +#define USBH_HOST_CTRL_CTLBLKSR_3 (2 << 0) +#define USBH_HOST_CTRL_CTLBLKSR_4 (3 << 0) + +#define USBH_SYSISR 0x88 +#define USBH_SYSISR_PSCINT (1 << 6) +#define USBH_SYSISR_FMOFINT (1 << 5) +#define USBH_SYSISR_HERRINT (1 << 4) +#define USBH_SYSISR_RESDETINT (1 << 3) +#define USBH_SYSISR_SOFINT (1 << 2) +#define USBH_SYSISR_DONEINT (1 << 1) +#define USBH_SYSISR_SORINT (1 << 0) + +#define USBH_SYSIEN 0x8c +#define USBH_SYSIEN_PSCINT (1 << 6) +#define USBH_SYSIEN_FMOFINT (1 << 5) +#define USBH_SYSIEN_HERRINT (1 << 4) +#define USBH_SYSIEN_RESDETINT (1 << 3) +#define USBH_SYSIEN_SOFINT (1 << 2) +#define USBH_SYSIEN_DONEINT (1 << 1) +#define USBH_SYSIEN_SORINT (1 << 0) + +#define USBH_XBUFSTAT 0x98 +#define USBH_YBUFSTAT 0x9c +#define USBH_XYINTEN 0xa0 +#define USBH_XFILLSTAT 0xa8 +#define USBH_YFILLSTAT 0xac +#define USBH_ETDENSET 0xc0 +#define USBH_ETDENCLR 0xc4 +#define USBH_IMMEDINT 0xcc +#define USBH_ETDDONESTAT 0xd0 +#define USBH_ETDDONEEN 0xd4 +#define USBH_FRMNUB 0xe0 +#define USBH_LSTHRESH 0xe4 + +#define USBH_ROOTHUBA 0xe8 +#define USBH_ROOTHUBA_PWRTOGOOD_MASK (0xff) +#define USBH_ROOTHUBA_PWRTOGOOD_SHIFT (24) +#define USBH_ROOTHUBA_NOOVRCURP (1 << 12) +#define USBH_ROOTHUBA_OVRCURPM (1 << 11) +#define USBH_ROOTHUBA_DEVTYPE (1 << 10) +#define USBH_ROOTHUBA_PWRSWTMD (1 << 9) +#define USBH_ROOTHUBA_NOPWRSWT (1 << 8) +#define USBH_ROOTHUBA_NDNSTMPRT_MASK (0xff) + +#define USBH_ROOTHUBB 0xec +#define USBH_ROOTHUBB_PRTPWRCM(x) (1 << ((x) + 16)) +#define USBH_ROOTHUBB_DEVREMOVE(x) (1 << (x)) + +#define USBH_ROOTSTAT 0xf0 +#define USBH_ROOTSTAT_CLRRMTWUE (1 << 31) +#define USBH_ROOTSTAT_OVRCURCHG (1 << 17) +#define USBH_ROOTSTAT_DEVCONWUE (1 << 15) +#define USBH_ROOTSTAT_OVRCURI (1 << 1) +#define USBH_ROOTSTAT_LOCPWRS (1 << 0) + +#define USBH_PORTSTAT(x) (0xf4 + ((x) * 4)) +#define USBH_PORTSTAT_PRTRSTSC (1 << 20) +#define USBH_PORTSTAT_OVRCURIC (1 << 19) +#define USBH_PORTSTAT_PRTSTATSC (1 << 18) +#define USBH_PORTSTAT_PRTENBLSC (1 << 17) +#define USBH_PORTSTAT_CONNECTSC (1 << 16) +#define USBH_PORTSTAT_LSDEVCON (1 << 9) +#define USBH_PORTSTAT_PRTPWRST (1 << 8) +#define USBH_PORTSTAT_PRTRSTST (1 << 4) +#define USBH_PORTSTAT_PRTOVRCURI (1 << 3) +#define USBH_PORTSTAT_PRTSUSPST (1 << 2) +#define USBH_PORTSTAT_PRTENABST (1 << 1) +#define USBH_PORTSTAT_CURCONST (1 << 0) + +#define USB_DMAREV 0x800 +#define USB_DMAINTSTAT 0x804 +#define USB_DMAINTSTAT_EPERR (1 << 1) +#define USB_DMAINTSTAT_ETDERR (1 << 0) + +#define USB_DMAINTEN 0x808 +#define USB_DMAINTEN_EPERRINTEN (1 << 1) +#define USB_DMAINTEN_ETDERRINTEN (1 << 0) + +#define USB_ETDDMAERSTAT 0x80c +#define USB_EPDMAERSTAT 0x810 +#define USB_ETDDMAEN 0x820 +#define USB_EPDMAEN 0x824 +#define USB_ETDDMAXTEN 0x828 +#define USB_EPDMAXTEN 0x82c +#define USB_ETDDMAENXYT 0x830 +#define USB_EPDMAENXYT 0x834 +#define USB_ETDDMABST4EN 0x838 +#define USB_EPDMABST4EN 0x83c + +#define USB_MISCCONTROL 0x840 +#define USB_MISCCONTROL_ISOPREVFRM (1 << 3) +#define USB_MISCCONTROL_SKPRTRY (1 << 2) +#define USB_MISCCONTROL_ARBMODE (1 << 1) +#define USB_MISCCONTROL_FILTCC (1 << 0) + +#define USB_ETDDMACHANLCLR 0x848 +#define USB_EPDMACHANLCLR 0x84c +#define USB_ETDSMSA(x) (0x900 + ((x) * 4)) +#define USB_EPSMSA(x) (0x980 + ((x) * 4)) +#define USB_ETDDMABUFPTR(x) (0xa00 + ((x) * 4)) +#define USB_EPDMABUFPTR(x) (0xa80 + ((x) * 4)) + +#define USB_ETD_DWORD(x, w) (0x200 + ((x) * 16) + ((w) * 4)) +#define DW0_ADDRESS 0 +#define DW0_ENDPNT 7 +#define DW0_DIRECT 11 +#define DW0_SPEED 13 +#define DW0_FORMAT 14 +#define DW0_MAXPKTSIZ 16 +#define DW0_HALTED 27 +#define DW0_TOGCRY 28 +#define DW0_SNDNAK 30 + +#define DW1_XBUFSRTAD 0 +#define DW1_YBUFSRTAD 16 + +#define DW2_RTRYDELAY 0 +#define DW2_POLINTERV 0 +#define DW2_STARTFRM 0 +#define DW2_RELPOLPOS 8 +#define DW2_DIRPID 16 +#define DW2_BUFROUND 18 +#define DW2_DELAYINT 19 +#define DW2_DATATOG 22 +#define DW2_ERRORCNT 24 +#define DW2_COMPCODE 28 + +#define DW3_TOTBYECNT 0 +#define DW3_PKTLEN0 0 +#define DW3_COMPCODE0 12 +#define DW3_PKTLEN1 16 +#define DW3_BUFSIZE 21 +#define DW3_COMPCODE1 28 + +#define USBCTRL 0x600 +#define USBCTRL_I2C_WU_INT_STAT (1 << 27) +#define USBCTRL_OTG_WU_INT_STAT (1 << 26) +#define USBCTRL_HOST_WU_INT_STAT (1 << 25) +#define USBCTRL_FNT_WU_INT_STAT (1 << 24) +#define USBCTRL_I2C_WU_INT_EN (1 << 19) +#define USBCTRL_OTG_WU_INT_EN (1 << 18) +#define USBCTRL_HOST_WU_INT_EN (1 << 17) +#define USBCTRL_FNT_WU_INT_EN (1 << 16) +#define USBCTRL_OTC_RCV_RXDP (1 << 13) +#define USBCTRL_HOST1_BYP_TLL (1 << 12) +#define USBCTRL_OTG_BYP_VAL(x) ((x) << 10) +#define USBCTRL_HOST1_BYP_VAL(x) ((x) << 8) +#define USBCTRL_OTG_PWR_MASK (1 << 6) +#define USBCTRL_HOST1_PWR_MASK (1 << 5) +#define USBCTRL_HOST2_PWR_MASK (1 << 4) +#define USBCTRL_USB_BYP (1 << 2) +#define USBCTRL_HOST1_TXEN_OE (1 << 1) + +#define USBOTG_DMEM 0x1000 + +/* Values in TD blocks */ +#define TD_DIR_SETUP 0 +#define TD_DIR_OUT 1 +#define TD_DIR_IN 2 +#define TD_FORMAT_CONTROL 0 +#define TD_FORMAT_ISO 1 +#define TD_FORMAT_BULK 2 +#define TD_FORMAT_INT 3 +#define TD_TOGGLE_CARRY 0 +#define TD_TOGGLE_DATA0 2 +#define TD_TOGGLE_DATA1 3 + +/* control transfer states */ +#define US_CTRL_SETUP 2 +#define US_CTRL_DATA 1 +#define US_CTRL_ACK 0 + +/* bulk transfer main state and 0-length packet */ +#define US_BULK 1 +#define US_BULK0 0 + +/*ETD format description*/ +#define IMX_FMT_CTRL 0x0 +#define IMX_FMT_ISO 0x1 +#define IMX_FMT_BULK 0x2 +#define IMX_FMT_INT 0x3 + +static char fmt_urb_to_etd[4] = { +/*PIPE_ISOCHRONOUS*/ IMX_FMT_ISO, +/*PIPE_INTERRUPT*/ IMX_FMT_INT, +/*PIPE_CONTROL*/ IMX_FMT_CTRL, +/*PIPE_BULK*/ IMX_FMT_BULK +}; + +/* condition (error) CC codes and mapping (OHCI like) */ + +#define TD_CC_NOERROR 0x00 +#define TD_CC_CRC 0x01 +#define TD_CC_BITSTUFFING 0x02 +#define TD_CC_DATATOGGLEM 0x03 +#define TD_CC_STALL 0x04 +#define TD_DEVNOTRESP 0x05 +#define TD_PIDCHECKFAIL 0x06 +/*#define TD_UNEXPECTEDPID 0x07 - reserved, not active on MX2*/ +#define TD_DATAOVERRUN 0x08 +#define TD_DATAUNDERRUN 0x09 +#define TD_BUFFEROVERRUN 0x0C +#define TD_BUFFERUNDERRUN 0x0D +#define TD_SCHEDULEOVERRUN 0x0E +#define TD_NOTACCESSED 0x0F + +static const int cc_to_error[16] = { + /* No Error */ 0, + /* CRC Error */ -EILSEQ, + /* Bit Stuff */ -EPROTO, + /* Data Togg */ -EILSEQ, + /* Stall */ -EPIPE, + /* DevNotResp */ -ETIMEDOUT, + /* PIDCheck */ -EPROTO, + /* UnExpPID */ -EPROTO, + /* DataOver */ -EOVERFLOW, + /* DataUnder */ -EREMOTEIO, + /* (for hw) */ -EIO, + /* (for hw) */ -EIO, + /* BufferOver */ -ECOMM, + /* BuffUnder */ -ENOSR, + /* (for HCD) */ -ENOSPC, + /* (for HCD) */ -EALREADY +}; + +/* HCD data associated with a usb core URB */ +struct urb_priv { + struct urb *urb; + struct usb_host_endpoint *ep; + int active; + int state; + struct td *isoc_td; + int isoc_remaining; + int isoc_status; +}; + +/* HCD data associated with a usb core endpoint */ +struct ep_priv { + struct usb_host_endpoint *ep; + struct list_head td_list; + struct list_head queue; + int etd[NUM_ISO_ETDS]; + int waiting_etd; +}; + +/* isoc packet */ +struct td { + struct list_head list; + struct urb *urb; + struct usb_host_endpoint *ep; + dma_addr_t dma_handle; + void *cpu_buffer; + int len; + int frame; + int isoc_index; +}; + +/* HCD data associated with a hardware ETD */ +struct etd_priv { + struct usb_host_endpoint *ep; + struct urb *urb; + struct td *td; + struct list_head queue; + dma_addr_t dma_handle; + void *cpu_buffer; + void *bounce_buffer; + int alloc; + int len; + int dmem_size; + int dmem_offset; + int active_count; +#ifdef DEBUG + int activated_frame; + int disactivated_frame; + int last_int_frame; + int last_req_frame; + u32 submitted_dwords[4]; +#endif +}; + +/* Hardware data memory info */ +struct imx21_dmem_area { + struct usb_host_endpoint *ep; + unsigned int offset; + unsigned int size; + struct list_head list; +}; + +#ifdef DEBUG +struct debug_usage_stats { + unsigned int value; + unsigned int maximum; +}; + +struct debug_stats { + unsigned long submitted; + unsigned long completed_ok; + unsigned long completed_failed; + unsigned long unlinked; + unsigned long queue_etd; + unsigned long queue_dmem; +}; + +struct debug_isoc_trace { + int schedule_frame; + int submit_frame; + int request_len; + int done_frame; + int done_len; + int cc; + struct td *td; +}; +#endif + +/* HCD data structure */ +struct imx21 { + spinlock_t lock; + struct device *dev; + struct usb_hcd *hcd; + struct mx21_usbh_platform_data *pdata; + struct list_head dmem_list; + struct list_head queue_for_etd; /* eps queued due to etd shortage */ + struct list_head queue_for_dmem; /* etds queued due to dmem shortage */ + struct etd_priv etd[USB_NUM_ETD]; + struct clk *clk; + void __iomem *regs; +#ifdef DEBUG + struct dentry *debug_root; + struct debug_stats nonisoc_stats; + struct debug_stats isoc_stats; + struct debug_usage_stats etd_usage; + struct debug_usage_stats dmem_usage; + struct debug_isoc_trace isoc_trace[20]; + struct debug_isoc_trace isoc_trace_failed[20]; + unsigned long debug_unblocks; + int isoc_trace_index; + int isoc_trace_index_failed; +#endif +}; + +#endif diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c new file mode 100644 index 00000000..b64e6616 --- /dev/null +++ b/drivers/usb/host/isp116x-hcd.c @@ -0,0 +1,1713 @@ +/* + * ISP116x HCD (Host Controller Driver) for USB. + * + * Derived from the SL811 HCD, rewritten for ISP116x. + * Copyright (C) 2005 Olav Kongas + * + * Portions: + * Copyright (C) 2004 Psion Teklogix (for NetBook PRO) + * Copyright (C) 2004 David Brownell + * + * Periodic scheduling is based on Roman's OHCI code + * Copyright (C) 1999 Roman Weissgaerber + * + */ + +/* + * The driver basically works. A number of people have used it with a range + * of devices. + * + * The driver passes all usbtests 1-14. + * + * Suspending/resuming of root hub via sysfs works. Remote wakeup works too. + * And suspending/resuming of platform device works too. Suspend/resume + * via HCD operations vector is not implemented. + * + * Iso transfer support is not implemented. Adding this would include + * implementing recovery from the failure to service the processed ITL + * fifo ram in time, which will involve chip reset. + * + * TODO: + + More testing of suspend/resume. +*/ + +/* + ISP116x chips require certain delays between accesses to its + registers. The following timing options exist. + + 1. Configure your memory controller (the best) + 2. Implement platform-specific delay function possibly + combined with configuring the memory controller; see + include/linux/usb-isp116x.h for more info. Some broken + memory controllers line LH7A400 SMC need this. Also, + uncomment for that to work the following + USE_PLATFORM_DELAY macro. + 3. Use ndelay (easiest, poorest). For that, uncomment + the following USE_NDELAY macro. +*/ +#define USE_PLATFORM_DELAY +//#define USE_NDELAY + +//#define DEBUG +//#define VERBOSE +/* Transfer descriptors. See dump_ptd() for printout format */ +//#define PTD_TRACE +/* enqueuing/finishing log of urbs */ +//#define URB_TRACE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "isp116x.h" + +#define DRIVER_VERSION "03 Nov 2005" +#define DRIVER_DESC "ISP116x USB Host Controller Driver" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static const char hcd_name[] = "isp116x-hcd"; + +/*-----------------------------------------------------------------*/ + +/* + Write len bytes to fifo, pad till 32-bit boundary + */ +static void write_ptddata_to_fifo(struct isp116x *isp116x, void *buf, int len) +{ + u8 *dp = (u8 *) buf; + u16 *dp2 = (u16 *) buf; + u16 w; + int quot = len % 4; + + /* buffer is already in 'usb data order', which is LE. */ + /* When reading buffer as u16, we have to take care byte order */ + /* doesn't get mixed up */ + + if ((unsigned long)dp2 & 1) { + /* not aligned */ + for (; len > 1; len -= 2) { + w = *dp++; + w |= *dp++ << 8; + isp116x_raw_write_data16(isp116x, w); + } + if (len) + isp116x_write_data16(isp116x, (u16) * dp); + } else { + /* aligned */ + for (; len > 1; len -= 2) { + /* Keep byte order ! */ + isp116x_raw_write_data16(isp116x, cpu_to_le16(*dp2++)); + } + + if (len) + isp116x_write_data16(isp116x, 0xff & *((u8 *) dp2)); + } + if (quot == 1 || quot == 2) + isp116x_raw_write_data16(isp116x, 0); +} + +/* + Read len bytes from fifo and then read till 32-bit boundary. + */ +static void read_ptddata_from_fifo(struct isp116x *isp116x, void *buf, int len) +{ + u8 *dp = (u8 *) buf; + u16 *dp2 = (u16 *) buf; + u16 w; + int quot = len % 4; + + /* buffer is already in 'usb data order', which is LE. */ + /* When reading buffer as u16, we have to take care byte order */ + /* doesn't get mixed up */ + + if ((unsigned long)dp2 & 1) { + /* not aligned */ + for (; len > 1; len -= 2) { + w = isp116x_raw_read_data16(isp116x); + *dp++ = w & 0xff; + *dp++ = (w >> 8) & 0xff; + } + + if (len) + *dp = 0xff & isp116x_read_data16(isp116x); + } else { + /* aligned */ + for (; len > 1; len -= 2) { + /* Keep byte order! */ + *dp2++ = le16_to_cpu(isp116x_raw_read_data16(isp116x)); + } + + if (len) + *(u8 *) dp2 = 0xff & isp116x_read_data16(isp116x); + } + if (quot == 1 || quot == 2) + isp116x_raw_read_data16(isp116x); +} + +/* + Write ptd's and data for scheduled transfers into + the fifo ram. Fifo must be empty and ready. +*/ +static void pack_fifo(struct isp116x *isp116x) +{ + struct isp116x_ep *ep; + struct ptd *ptd; + int buflen = isp116x->atl_last_dir == PTD_DIR_IN + ? isp116x->atl_bufshrt : isp116x->atl_buflen; + + isp116x_write_reg16(isp116x, HCuPINT, HCuPINT_AIIEOT); + isp116x_write_reg16(isp116x, HCXFERCTR, buflen); + isp116x_write_addr(isp116x, HCATLPORT | ISP116x_WRITE_OFFSET); + for (ep = isp116x->atl_active; ep; ep = ep->active) { + ptd = &ep->ptd; + dump_ptd(ptd); + dump_ptd_out_data(ptd, ep->data); + isp116x_write_data16(isp116x, ptd->count); + isp116x_write_data16(isp116x, ptd->mps); + isp116x_write_data16(isp116x, ptd->len); + isp116x_write_data16(isp116x, ptd->faddr); + buflen -= sizeof(struct ptd); + /* Skip writing data for last IN PTD */ + if (ep->active || (isp116x->atl_last_dir != PTD_DIR_IN)) { + write_ptddata_to_fifo(isp116x, ep->data, ep->length); + buflen -= ALIGN(ep->length, 4); + } + } + BUG_ON(buflen); +} + +/* + Read the processed ptd's and data from fifo ram back to + URBs' buffers. Fifo must be full and done +*/ +static void unpack_fifo(struct isp116x *isp116x) +{ + struct isp116x_ep *ep; + struct ptd *ptd; + int buflen = isp116x->atl_last_dir == PTD_DIR_IN + ? isp116x->atl_buflen : isp116x->atl_bufshrt; + + isp116x_write_reg16(isp116x, HCuPINT, HCuPINT_AIIEOT); + isp116x_write_reg16(isp116x, HCXFERCTR, buflen); + isp116x_write_addr(isp116x, HCATLPORT); + for (ep = isp116x->atl_active; ep; ep = ep->active) { + ptd = &ep->ptd; + ptd->count = isp116x_read_data16(isp116x); + ptd->mps = isp116x_read_data16(isp116x); + ptd->len = isp116x_read_data16(isp116x); + ptd->faddr = isp116x_read_data16(isp116x); + buflen -= sizeof(struct ptd); + /* Skip reading data for last Setup or Out PTD */ + if (ep->active || (isp116x->atl_last_dir == PTD_DIR_IN)) { + read_ptddata_from_fifo(isp116x, ep->data, ep->length); + buflen -= ALIGN(ep->length, 4); + } + dump_ptd(ptd); + dump_ptd_in_data(ptd, ep->data); + } + BUG_ON(buflen); +} + +/*---------------------------------------------------------------*/ + +/* + Set up PTD's. +*/ +static void preproc_atl_queue(struct isp116x *isp116x) +{ + struct isp116x_ep *ep; + struct urb *urb; + struct ptd *ptd; + u16 len; + + for (ep = isp116x->atl_active; ep; ep = ep->active) { + u16 toggle = 0, dir = PTD_DIR_SETUP; + + BUG_ON(list_empty(&ep->hep->urb_list)); + urb = container_of(ep->hep->urb_list.next, + struct urb, urb_list); + ptd = &ep->ptd; + len = ep->length; + ep->data = (unsigned char *)urb->transfer_buffer + + urb->actual_length; + + switch (ep->nextpid) { + case USB_PID_IN: + toggle = usb_gettoggle(urb->dev, ep->epnum, 0); + dir = PTD_DIR_IN; + break; + case USB_PID_OUT: + toggle = usb_gettoggle(urb->dev, ep->epnum, 1); + dir = PTD_DIR_OUT; + break; + case USB_PID_SETUP: + len = sizeof(struct usb_ctrlrequest); + ep->data = urb->setup_packet; + break; + case USB_PID_ACK: + toggle = 1; + len = 0; + dir = (urb->transfer_buffer_length + && usb_pipein(urb->pipe)) + ? PTD_DIR_OUT : PTD_DIR_IN; + break; + default: + ERR("%s %d: ep->nextpid %d\n", __func__, __LINE__, + ep->nextpid); + BUG(); + } + + ptd->count = PTD_CC_MSK | PTD_ACTIVE_MSK | PTD_TOGGLE(toggle); + ptd->mps = PTD_MPS(ep->maxpacket) + | PTD_SPD(urb->dev->speed == USB_SPEED_LOW) + | PTD_EP(ep->epnum); + ptd->len = PTD_LEN(len) | PTD_DIR(dir); + ptd->faddr = PTD_FA(usb_pipedevice(urb->pipe)); + if (!ep->active) { + ptd->mps |= PTD_LAST_MSK; + isp116x->atl_last_dir = dir; + } + isp116x->atl_bufshrt = sizeof(struct ptd) + isp116x->atl_buflen; + isp116x->atl_buflen = isp116x->atl_bufshrt + ALIGN(len, 4); + } +} + +/* + Take done or failed requests out of schedule. Give back + processed urbs. +*/ +static void finish_request(struct isp116x *isp116x, struct isp116x_ep *ep, + struct urb *urb, int status) +__releases(isp116x->lock) __acquires(isp116x->lock) +{ + unsigned i; + + ep->error_count = 0; + + if (usb_pipecontrol(urb->pipe)) + ep->nextpid = USB_PID_SETUP; + + urb_dbg(urb, "Finish"); + + usb_hcd_unlink_urb_from_ep(isp116x_to_hcd(isp116x), urb); + spin_unlock(&isp116x->lock); + usb_hcd_giveback_urb(isp116x_to_hcd(isp116x), urb, status); + spin_lock(&isp116x->lock); + + /* take idle endpoints out of the schedule */ + if (!list_empty(&ep->hep->urb_list)) + return; + + /* async deschedule */ + if (!list_empty(&ep->schedule)) { + list_del_init(&ep->schedule); + return; + } + + /* periodic deschedule */ + DBG("deschedule qh%d/%p branch %d\n", ep->period, ep, ep->branch); + for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) { + struct isp116x_ep *temp; + struct isp116x_ep **prev = &isp116x->periodic[i]; + + while (*prev && ((temp = *prev) != ep)) + prev = &temp->next; + if (*prev) + *prev = ep->next; + isp116x->load[i] -= ep->load; + } + ep->branch = PERIODIC_SIZE; + isp116x_to_hcd(isp116x)->self.bandwidth_allocated -= + ep->load / ep->period; + + /* switch irq type? */ + if (!--isp116x->periodic_count) { + isp116x->irqenb &= ~HCuPINT_SOF; + isp116x->irqenb |= HCuPINT_ATL; + } +} + +/* + Analyze transfer results, handle partial transfers and errors +*/ +static void postproc_atl_queue(struct isp116x *isp116x) +{ + struct isp116x_ep *ep; + struct urb *urb; + struct usb_device *udev; + struct ptd *ptd; + int short_not_ok; + int status; + u8 cc; + + for (ep = isp116x->atl_active; ep; ep = ep->active) { + BUG_ON(list_empty(&ep->hep->urb_list)); + urb = + container_of(ep->hep->urb_list.next, struct urb, urb_list); + udev = urb->dev; + ptd = &ep->ptd; + cc = PTD_GET_CC(ptd); + short_not_ok = 1; + status = -EINPROGRESS; + + /* Data underrun is special. For allowed underrun + we clear the error and continue as normal. For + forbidden underrun we finish the DATA stage + immediately while for control transfer, + we do a STATUS stage. */ + if (cc == TD_DATAUNDERRUN) { + if (!(urb->transfer_flags & URB_SHORT_NOT_OK) || + usb_pipecontrol(urb->pipe)) { + DBG("Allowed or control data underrun\n"); + cc = TD_CC_NOERROR; + short_not_ok = 0; + } else { + ep->error_count = 1; + usb_settoggle(udev, ep->epnum, + ep->nextpid == USB_PID_OUT, + PTD_GET_TOGGLE(ptd)); + urb->actual_length += PTD_GET_COUNT(ptd); + status = cc_to_error[TD_DATAUNDERRUN]; + goto done; + } + } + + if (cc != TD_CC_NOERROR && cc != TD_NOTACCESSED + && (++ep->error_count >= 3 || cc == TD_CC_STALL + || cc == TD_DATAOVERRUN)) { + status = cc_to_error[cc]; + if (ep->nextpid == USB_PID_ACK) + ep->nextpid = 0; + goto done; + } + /* According to usb spec, zero-length Int transfer signals + finishing of the urb. Hey, does this apply only + for IN endpoints? */ + if (usb_pipeint(urb->pipe) && !PTD_GET_LEN(ptd)) { + status = 0; + goto done; + } + + /* Relax after previously failed, but later succeeded + or correctly NAK'ed retransmission attempt */ + if (ep->error_count + && (cc == TD_CC_NOERROR || cc == TD_NOTACCESSED)) + ep->error_count = 0; + + /* Take into account idiosyncracies of the isp116x chip + regarding toggle bit for failed transfers */ + if (ep->nextpid == USB_PID_OUT) + usb_settoggle(udev, ep->epnum, 1, PTD_GET_TOGGLE(ptd) + ^ (ep->error_count > 0)); + else if (ep->nextpid == USB_PID_IN) + usb_settoggle(udev, ep->epnum, 0, PTD_GET_TOGGLE(ptd) + ^ (ep->error_count > 0)); + + switch (ep->nextpid) { + case USB_PID_IN: + case USB_PID_OUT: + urb->actual_length += PTD_GET_COUNT(ptd); + if (PTD_GET_ACTIVE(ptd) + || (cc != TD_CC_NOERROR && cc < 0x0E)) + break; + if (urb->transfer_buffer_length != urb->actual_length) { + if (short_not_ok) + break; + } else { + if (urb->transfer_flags & URB_ZERO_PACKET + && ep->nextpid == USB_PID_OUT + && !(PTD_GET_COUNT(ptd) % ep->maxpacket)) { + DBG("Zero packet requested\n"); + break; + } + } + /* All data for this URB is transferred, let's finish */ + if (usb_pipecontrol(urb->pipe)) + ep->nextpid = USB_PID_ACK; + else + status = 0; + break; + case USB_PID_SETUP: + if (PTD_GET_ACTIVE(ptd) + || (cc != TD_CC_NOERROR && cc < 0x0E)) + break; + if (urb->transfer_buffer_length == urb->actual_length) + ep->nextpid = USB_PID_ACK; + else if (usb_pipeout(urb->pipe)) { + usb_settoggle(udev, 0, 1, 1); + ep->nextpid = USB_PID_OUT; + } else { + usb_settoggle(udev, 0, 0, 1); + ep->nextpid = USB_PID_IN; + } + break; + case USB_PID_ACK: + if (PTD_GET_ACTIVE(ptd) + || (cc != TD_CC_NOERROR && cc < 0x0E)) + break; + status = 0; + ep->nextpid = 0; + break; + default: + BUG(); + } + + done: + if (status != -EINPROGRESS || urb->unlinked) + finish_request(isp116x, ep, urb, status); + } +} + +/* + Scan transfer lists, schedule transfers, send data off + to chip. + */ +static void start_atl_transfers(struct isp116x *isp116x) +{ + struct isp116x_ep *last_ep = NULL, *ep; + struct urb *urb; + u16 load = 0; + int len, index, speed, byte_time; + + if (atomic_read(&isp116x->atl_finishing)) + return; + + if (!HC_IS_RUNNING(isp116x_to_hcd(isp116x)->state)) + return; + + /* FIFO not empty? */ + if (isp116x_read_reg16(isp116x, HCBUFSTAT) & HCBUFSTAT_ATL_FULL) + return; + + isp116x->atl_active = NULL; + isp116x->atl_buflen = isp116x->atl_bufshrt = 0; + + /* Schedule int transfers */ + if (isp116x->periodic_count) { + isp116x->fmindex = index = + (isp116x->fmindex + 1) & (PERIODIC_SIZE - 1); + if ((load = isp116x->load[index])) { + /* Bring all int transfers for this frame + into the active queue */ + isp116x->atl_active = last_ep = + isp116x->periodic[index]; + while (last_ep->next) + last_ep = (last_ep->active = last_ep->next); + last_ep->active = NULL; + } + } + + /* Schedule control/bulk transfers */ + list_for_each_entry(ep, &isp116x->async, schedule) { + urb = container_of(ep->hep->urb_list.next, + struct urb, urb_list); + speed = urb->dev->speed; + byte_time = speed == USB_SPEED_LOW + ? BYTE_TIME_LOWSPEED : BYTE_TIME_FULLSPEED; + + if (ep->nextpid == USB_PID_SETUP) { + len = sizeof(struct usb_ctrlrequest); + } else if (ep->nextpid == USB_PID_ACK) { + len = 0; + } else { + /* Find current free length ... */ + len = (MAX_LOAD_LIMIT - load) / byte_time; + + /* ... then limit it to configured max size ... */ + len = min(len, speed == USB_SPEED_LOW ? + MAX_TRANSFER_SIZE_LOWSPEED : + MAX_TRANSFER_SIZE_FULLSPEED); + + /* ... and finally cut to the multiple of MaxPacketSize, + or to the real length if there's enough room. */ + if (len < + (urb->transfer_buffer_length - + urb->actual_length)) { + len -= len % ep->maxpacket; + if (!len) + continue; + } else + len = urb->transfer_buffer_length - + urb->actual_length; + BUG_ON(len < 0); + } + + load += len * byte_time; + if (load > MAX_LOAD_LIMIT) + break; + + ep->active = NULL; + ep->length = len; + if (last_ep) + last_ep->active = ep; + else + isp116x->atl_active = ep; + last_ep = ep; + } + + /* Avoid starving of endpoints */ + if ((&isp116x->async)->next != (&isp116x->async)->prev) + list_move(&isp116x->async, (&isp116x->async)->next); + + if (isp116x->atl_active) { + preproc_atl_queue(isp116x); + pack_fifo(isp116x); + } +} + +/* + Finish the processed transfers +*/ +static void finish_atl_transfers(struct isp116x *isp116x) +{ + if (!isp116x->atl_active) + return; + /* Fifo not ready? */ + if (!(isp116x_read_reg16(isp116x, HCBUFSTAT) & HCBUFSTAT_ATL_DONE)) + return; + + atomic_inc(&isp116x->atl_finishing); + unpack_fifo(isp116x); + postproc_atl_queue(isp116x); + atomic_dec(&isp116x->atl_finishing); +} + +static irqreturn_t isp116x_irq(struct usb_hcd *hcd) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + u16 irqstat; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&isp116x->lock); + isp116x_write_reg16(isp116x, HCuPINTENB, 0); + irqstat = isp116x_read_reg16(isp116x, HCuPINT); + isp116x_write_reg16(isp116x, HCuPINT, irqstat); + + if (irqstat & (HCuPINT_ATL | HCuPINT_SOF)) { + ret = IRQ_HANDLED; + finish_atl_transfers(isp116x); + } + + if (irqstat & HCuPINT_OPR) { + u32 intstat = isp116x_read_reg32(isp116x, HCINTSTAT); + isp116x_write_reg32(isp116x, HCINTSTAT, intstat); + if (intstat & HCINT_UE) { + ERR("Unrecoverable error, HC is dead!\n"); + /* IRQ's are off, we do no DMA, + perfectly ready to die ... */ + hcd->state = HC_STATE_HALT; + usb_hc_died(hcd); + ret = IRQ_HANDLED; + goto done; + } + if (intstat & HCINT_RHSC) + /* When root hub or any of its ports is going + to come out of suspend, it may take more + than 10ms for status bits to stabilize. */ + mod_timer(&hcd->rh_timer, jiffies + + msecs_to_jiffies(20) + 1); + if (intstat & HCINT_RD) { + DBG("---- remote wakeup\n"); + usb_hcd_resume_root_hub(hcd); + } + irqstat &= ~HCuPINT_OPR; + ret = IRQ_HANDLED; + } + + if (irqstat & (HCuPINT_ATL | HCuPINT_SOF)) { + start_atl_transfers(isp116x); + } + + isp116x_write_reg16(isp116x, HCuPINTENB, isp116x->irqenb); + done: + spin_unlock(&isp116x->lock); + return ret; +} + +/*-----------------------------------------------------------------*/ + +/* usb 1.1 says max 90% of a frame is available for periodic transfers. + * this driver doesn't promise that much since it's got to handle an + * IRQ per packet; irq handling latencies also use up that time. + */ + +/* out of 1000 us */ +#define MAX_PERIODIC_LOAD 600 +static int balance(struct isp116x *isp116x, u16 period, u16 load) +{ + int i, branch = -ENOSPC; + + /* search for the least loaded schedule branch of that period + which has enough bandwidth left unreserved. */ + for (i = 0; i < period; i++) { + if (branch < 0 || isp116x->load[branch] > isp116x->load[i]) { + int j; + + for (j = i; j < PERIODIC_SIZE; j += period) { + if ((isp116x->load[j] + load) + > MAX_PERIODIC_LOAD) + break; + } + if (j < PERIODIC_SIZE) + continue; + branch = i; + } + } + return branch; +} + +/* NB! ALL the code above this point runs with isp116x->lock + held, irqs off +*/ + +/*-----------------------------------------------------------------*/ + +static int isp116x_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + struct usb_device *udev = urb->dev; + unsigned int pipe = urb->pipe; + int is_out = !usb_pipein(pipe); + int type = usb_pipetype(pipe); + int epnum = usb_pipeendpoint(pipe); + struct usb_host_endpoint *hep = urb->ep; + struct isp116x_ep *ep = NULL; + unsigned long flags; + int i; + int ret = 0; + + urb_dbg(urb, "Enqueue"); + + if (type == PIPE_ISOCHRONOUS) { + ERR("Isochronous transfers not supported\n"); + urb_dbg(urb, "Refused to enqueue"); + return -ENXIO; + } + /* avoid all allocations within spinlocks: request or endpoint */ + if (!hep->hcpriv) { + ep = kzalloc(sizeof *ep, mem_flags); + if (!ep) + return -ENOMEM; + } + + spin_lock_irqsave(&isp116x->lock, flags); + if (!HC_IS_RUNNING(hcd->state)) { + kfree(ep); + ret = -ENODEV; + goto fail_not_linked; + } + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) { + kfree(ep); + goto fail_not_linked; + } + + if (hep->hcpriv) + ep = hep->hcpriv; + else { + INIT_LIST_HEAD(&ep->schedule); + ep->udev = udev; + ep->epnum = epnum; + ep->maxpacket = usb_maxpacket(udev, urb->pipe, is_out); + usb_settoggle(udev, epnum, is_out, 0); + + if (type == PIPE_CONTROL) { + ep->nextpid = USB_PID_SETUP; + } else if (is_out) { + ep->nextpid = USB_PID_OUT; + } else { + ep->nextpid = USB_PID_IN; + } + + if (urb->interval) { + /* + With INT URBs submitted, the driver works with SOF + interrupt enabled and ATL interrupt disabled. After + the PTDs are written to fifo ram, the chip starts + fifo processing and usb transfers after the next + SOF and continues until the transfers are finished + (succeeded or failed) or the frame ends. Therefore, + the transfers occur only in every second frame, + while fifo reading/writing and data processing + occur in every other second frame. */ + if (urb->interval < 2) + urb->interval = 2; + if (urb->interval > 2 * PERIODIC_SIZE) + urb->interval = 2 * PERIODIC_SIZE; + ep->period = urb->interval >> 1; + ep->branch = PERIODIC_SIZE; + ep->load = usb_calc_bus_time(udev->speed, + !is_out, + (type == PIPE_ISOCHRONOUS), + usb_maxpacket(udev, pipe, + is_out)) / + 1000; + } + hep->hcpriv = ep; + ep->hep = hep; + } + + /* maybe put endpoint into schedule */ + switch (type) { + case PIPE_CONTROL: + case PIPE_BULK: + if (list_empty(&ep->schedule)) + list_add_tail(&ep->schedule, &isp116x->async); + break; + case PIPE_INTERRUPT: + urb->interval = ep->period; + ep->length = min_t(u32, ep->maxpacket, + urb->transfer_buffer_length); + + /* urb submitted for already existing endpoint */ + if (ep->branch < PERIODIC_SIZE) + break; + + ep->branch = ret = balance(isp116x, ep->period, ep->load); + if (ret < 0) + goto fail; + ret = 0; + + urb->start_frame = (isp116x->fmindex & (PERIODIC_SIZE - 1)) + + ep->branch; + + /* sort each schedule branch by period (slow before fast) + to share the faster parts of the tree without needing + dummy/placeholder nodes */ + DBG("schedule qh%d/%p branch %d\n", ep->period, ep, ep->branch); + for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) { + struct isp116x_ep **prev = &isp116x->periodic[i]; + struct isp116x_ep *here = *prev; + + while (here && ep != here) { + if (ep->period > here->period) + break; + prev = &here->next; + here = *prev; + } + if (ep != here) { + ep->next = here; + *prev = ep; + } + isp116x->load[i] += ep->load; + } + hcd->self.bandwidth_allocated += ep->load / ep->period; + + /* switch over to SOFint */ + if (!isp116x->periodic_count++) { + isp116x->irqenb &= ~HCuPINT_ATL; + isp116x->irqenb |= HCuPINT_SOF; + isp116x_write_reg16(isp116x, HCuPINTENB, + isp116x->irqenb); + } + } + + urb->hcpriv = hep; + start_atl_transfers(isp116x); + + fail: + if (ret) + usb_hcd_unlink_urb_from_ep(hcd, urb); + fail_not_linked: + spin_unlock_irqrestore(&isp116x->lock, flags); + return ret; +} + +/* + Dequeue URBs. +*/ +static int isp116x_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + struct usb_host_endpoint *hep; + struct isp116x_ep *ep, *ep_act; + unsigned long flags; + int rc; + + spin_lock_irqsave(&isp116x->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + hep = urb->hcpriv; + ep = hep->hcpriv; + WARN_ON(hep != ep->hep); + + /* In front of queue? */ + if (ep->hep->urb_list.next == &urb->urb_list) + /* active? */ + for (ep_act = isp116x->atl_active; ep_act; + ep_act = ep_act->active) + if (ep_act == ep) { + VDBG("dequeue, urb %p active; wait for irq\n", + urb); + urb = NULL; + break; + } + + if (urb) + finish_request(isp116x, ep, urb, status); + done: + spin_unlock_irqrestore(&isp116x->lock, flags); + return rc; +} + +static void isp116x_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *hep) +{ + int i; + struct isp116x_ep *ep = hep->hcpriv; + + if (!ep) + return; + + /* assume we'd just wait for the irq */ + for (i = 0; i < 100 && !list_empty(&hep->urb_list); i++) + msleep(3); + if (!list_empty(&hep->urb_list)) + WARNING("ep %p not empty?\n", ep); + + kfree(ep); + hep->hcpriv = NULL; +} + +static int isp116x_get_frame(struct usb_hcd *hcd) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + u32 fmnum; + unsigned long flags; + + spin_lock_irqsave(&isp116x->lock, flags); + fmnum = isp116x_read_reg32(isp116x, HCFMNUM); + spin_unlock_irqrestore(&isp116x->lock, flags); + return (int)fmnum; +} + +/* + Adapted from ohci-hub.c. Currently we don't support autosuspend. +*/ +static int isp116x_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + int ports, i, changed = 0; + unsigned long flags; + + if (!HC_IS_RUNNING(hcd->state)) + return -ESHUTDOWN; + + /* Report no status change now, if we are scheduled to be + called later */ + if (timer_pending(&hcd->rh_timer)) + return 0; + + ports = isp116x->rhdesca & RH_A_NDP; + spin_lock_irqsave(&isp116x->lock, flags); + isp116x->rhstatus = isp116x_read_reg32(isp116x, HCRHSTATUS); + if (isp116x->rhstatus & (RH_HS_LPSC | RH_HS_OCIC)) + buf[0] = changed = 1; + else + buf[0] = 0; + + for (i = 0; i < ports; i++) { + u32 status = isp116x_read_reg32(isp116x, i ? HCRHPORT2 : HCRHPORT1); + + if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC + | RH_PS_OCIC | RH_PS_PRSC)) { + changed = 1; + buf[0] |= 1 << (i + 1); + } + } + spin_unlock_irqrestore(&isp116x->lock, flags); + return changed; +} + +static void isp116x_hub_descriptor(struct isp116x *isp116x, + struct usb_hub_descriptor *desc) +{ + u32 reg = isp116x->rhdesca; + + desc->bDescriptorType = 0x29; + desc->bDescLength = 9; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = (u8) (reg & 0x3); + /* Power switching, device type, overcurrent. */ + desc->wHubCharacteristics = cpu_to_le16((u16) ((reg >> 8) & 0x1f)); + desc->bPwrOn2PwrGood = (u8) ((reg >> 24) & 0xff); + /* ports removable, and legacy PortPwrCtrlMask */ + desc->u.hs.DeviceRemovable[0] = 0; + desc->u.hs.DeviceRemovable[1] = ~0; +} + +/* Perform reset of a given port. + It would be great to just start the reset and let the + USB core to clear the reset in due time. However, + root hub ports should be reset for at least 50 ms, while + our chip stays in reset for about 10 ms. I.e., we must + repeatedly reset it ourself here. +*/ +static inline void root_port_reset(struct isp116x *isp116x, unsigned port) +{ + u32 tmp; + unsigned long flags, t; + + /* Root hub reset should be 50 ms, but some devices + want it even longer. */ + t = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, t)) { + spin_lock_irqsave(&isp116x->lock, flags); + /* spin until any current reset finishes */ + for (;;) { + tmp = isp116x_read_reg32(isp116x, port ? + HCRHPORT2 : HCRHPORT1); + if (!(tmp & RH_PS_PRS)) + break; + udelay(500); + } + /* Don't reset a disconnected port */ + if (!(tmp & RH_PS_CCS)) { + spin_unlock_irqrestore(&isp116x->lock, flags); + break; + } + /* Reset lasts 10ms (claims datasheet) */ + isp116x_write_reg32(isp116x, port ? HCRHPORT2 : + HCRHPORT1, (RH_PS_PRS)); + spin_unlock_irqrestore(&isp116x->lock, flags); + msleep(10); + } +} + +/* Adapted from ohci-hub.c */ +static int isp116x_hub_control(struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + int ret = 0; + unsigned long flags; + int ports = isp116x->rhdesca & RH_A_NDP; + u32 tmp = 0; + + switch (typeReq) { + case ClearHubFeature: + DBG("ClearHubFeature: "); + switch (wValue) { + case C_HUB_OVER_CURRENT: + DBG("C_HUB_OVER_CURRENT\n"); + spin_lock_irqsave(&isp116x->lock, flags); + isp116x_write_reg32(isp116x, HCRHSTATUS, RH_HS_OCIC); + spin_unlock_irqrestore(&isp116x->lock, flags); + case C_HUB_LOCAL_POWER: + DBG("C_HUB_LOCAL_POWER\n"); + break; + default: + goto error; + } + break; + case SetHubFeature: + DBG("SetHubFeature: "); + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + DBG("C_HUB_OVER_CURRENT or C_HUB_LOCAL_POWER\n"); + break; + default: + goto error; + } + break; + case GetHubDescriptor: + DBG("GetHubDescriptor\n"); + isp116x_hub_descriptor(isp116x, + (struct usb_hub_descriptor *)buf); + break; + case GetHubStatus: + DBG("GetHubStatus\n"); + *(__le32 *) buf = 0; + break; + case GetPortStatus: + DBG("GetPortStatus\n"); + if (!wIndex || wIndex > ports) + goto error; + spin_lock_irqsave(&isp116x->lock, flags); + tmp = isp116x_read_reg32(isp116x, (--wIndex) ? HCRHPORT2 : HCRHPORT1); + spin_unlock_irqrestore(&isp116x->lock, flags); + *(__le32 *) buf = cpu_to_le32(tmp); + DBG("GetPortStatus: port[%d] %08x\n", wIndex + 1, tmp); + break; + case ClearPortFeature: + DBG("ClearPortFeature: "); + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + DBG("USB_PORT_FEAT_ENABLE\n"); + tmp = RH_PS_CCS; + break; + case USB_PORT_FEAT_C_ENABLE: + DBG("USB_PORT_FEAT_C_ENABLE\n"); + tmp = RH_PS_PESC; + break; + case USB_PORT_FEAT_SUSPEND: + DBG("USB_PORT_FEAT_SUSPEND\n"); + tmp = RH_PS_POCI; + break; + case USB_PORT_FEAT_C_SUSPEND: + DBG("USB_PORT_FEAT_C_SUSPEND\n"); + tmp = RH_PS_PSSC; + break; + case USB_PORT_FEAT_POWER: + DBG("USB_PORT_FEAT_POWER\n"); + tmp = RH_PS_LSDA; + break; + case USB_PORT_FEAT_C_CONNECTION: + DBG("USB_PORT_FEAT_C_CONNECTION\n"); + tmp = RH_PS_CSC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + DBG("USB_PORT_FEAT_C_OVER_CURRENT\n"); + tmp = RH_PS_OCIC; + break; + case USB_PORT_FEAT_C_RESET: + DBG("USB_PORT_FEAT_C_RESET\n"); + tmp = RH_PS_PRSC; + break; + default: + goto error; + } + spin_lock_irqsave(&isp116x->lock, flags); + isp116x_write_reg32(isp116x, wIndex + ? HCRHPORT2 : HCRHPORT1, tmp); + spin_unlock_irqrestore(&isp116x->lock, flags); + break; + case SetPortFeature: + DBG("SetPortFeature: "); + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + DBG("USB_PORT_FEAT_SUSPEND\n"); + spin_lock_irqsave(&isp116x->lock, flags); + isp116x_write_reg32(isp116x, wIndex + ? HCRHPORT2 : HCRHPORT1, RH_PS_PSS); + spin_unlock_irqrestore(&isp116x->lock, flags); + break; + case USB_PORT_FEAT_POWER: + DBG("USB_PORT_FEAT_POWER\n"); + spin_lock_irqsave(&isp116x->lock, flags); + isp116x_write_reg32(isp116x, wIndex + ? HCRHPORT2 : HCRHPORT1, RH_PS_PPS); + spin_unlock_irqrestore(&isp116x->lock, flags); + break; + case USB_PORT_FEAT_RESET: + DBG("USB_PORT_FEAT_RESET\n"); + root_port_reset(isp116x, wIndex); + break; + default: + goto error; + } + break; + + default: + error: + /* "protocol stall" on error */ + DBG("PROTOCOL STALL\n"); + ret = -EPIPE; + } + return ret; +} + +/*-----------------------------------------------------------------*/ + +#ifdef CONFIG_DEBUG_FS + +static void dump_irq(struct seq_file *s, char *label, u16 mask) +{ + seq_printf(s, "%s %04x%s%s%s%s%s%s\n", label, mask, + mask & HCuPINT_CLKRDY ? " clkrdy" : "", + mask & HCuPINT_SUSP ? " susp" : "", + mask & HCuPINT_OPR ? " opr" : "", + mask & HCuPINT_AIIEOT ? " eot" : "", + mask & HCuPINT_ATL ? " atl" : "", + mask & HCuPINT_SOF ? " sof" : ""); +} + +static void dump_int(struct seq_file *s, char *label, u32 mask) +{ + seq_printf(s, "%s %08x%s%s%s%s%s%s%s\n", label, mask, + mask & HCINT_MIE ? " MIE" : "", + mask & HCINT_RHSC ? " rhsc" : "", + mask & HCINT_FNO ? " fno" : "", + mask & HCINT_UE ? " ue" : "", + mask & HCINT_RD ? " rd" : "", + mask & HCINT_SF ? " sof" : "", mask & HCINT_SO ? " so" : ""); +} + +static int isp116x_show_dbg(struct seq_file *s, void *unused) +{ + struct isp116x *isp116x = s->private; + + seq_printf(s, "%s\n%s version %s\n", + isp116x_to_hcd(isp116x)->product_desc, hcd_name, + DRIVER_VERSION); + + if (HC_IS_SUSPENDED(isp116x_to_hcd(isp116x)->state)) { + seq_printf(s, "HCD is suspended\n"); + return 0; + } + if (!HC_IS_RUNNING(isp116x_to_hcd(isp116x)->state)) { + seq_printf(s, "HCD not running\n"); + return 0; + } + + spin_lock_irq(&isp116x->lock); + dump_irq(s, "hc_irq_enable", isp116x_read_reg16(isp116x, HCuPINTENB)); + dump_irq(s, "hc_irq_status", isp116x_read_reg16(isp116x, HCuPINT)); + dump_int(s, "hc_int_enable", isp116x_read_reg32(isp116x, HCINTENB)); + dump_int(s, "hc_int_status", isp116x_read_reg32(isp116x, HCINTSTAT)); + isp116x_show_regs_seq(isp116x, s); + spin_unlock_irq(&isp116x->lock); + seq_printf(s, "\n"); + + return 0; +} + +static int isp116x_open_seq(struct inode *inode, struct file *file) +{ + return single_open(file, isp116x_show_dbg, inode->i_private); +} + +static const struct file_operations isp116x_debug_fops = { + .open = isp116x_open_seq, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int create_debug_file(struct isp116x *isp116x) +{ + isp116x->dentry = debugfs_create_file(hcd_name, + S_IRUGO, NULL, isp116x, + &isp116x_debug_fops); + if (!isp116x->dentry) + return -ENOMEM; + return 0; +} + +static void remove_debug_file(struct isp116x *isp116x) +{ + debugfs_remove(isp116x->dentry); +} + +#else + +#define create_debug_file(d) 0 +#define remove_debug_file(d) do{}while(0) + +#endif /* CONFIG_DEBUG_FS */ + +/*-----------------------------------------------------------------*/ + +/* + Software reset - can be called from any contect. +*/ +static int isp116x_sw_reset(struct isp116x *isp116x) +{ + int retries = 15; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&isp116x->lock, flags); + isp116x_write_reg16(isp116x, HCSWRES, HCSWRES_MAGIC); + isp116x_write_reg32(isp116x, HCCMDSTAT, HCCMDSTAT_HCR); + while (--retries) { + /* It usually resets within 1 ms */ + mdelay(1); + if (!(isp116x_read_reg32(isp116x, HCCMDSTAT) & HCCMDSTAT_HCR)) + break; + } + if (!retries) { + ERR("Software reset timeout\n"); + ret = -ETIME; + } + spin_unlock_irqrestore(&isp116x->lock, flags); + return ret; +} + +static int isp116x_reset(struct usb_hcd *hcd) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + unsigned long t; + u16 clkrdy = 0; + int ret, timeout = 15 /* ms */ ; + + ret = isp116x_sw_reset(isp116x); + if (ret) + return ret; + + t = jiffies + msecs_to_jiffies(timeout); + while (time_before_eq(jiffies, t)) { + msleep(4); + spin_lock_irq(&isp116x->lock); + clkrdy = isp116x_read_reg16(isp116x, HCuPINT) & HCuPINT_CLKRDY; + spin_unlock_irq(&isp116x->lock); + if (clkrdy) + break; + } + if (!clkrdy) { + ERR("Clock not ready after %dms\n", timeout); + /* After sw_reset the clock won't report to be ready, if + H_WAKEUP pin is high. */ + ERR("Please make sure that the H_WAKEUP pin is pulled low!\n"); + ret = -ENODEV; + } + return ret; +} + +static void isp116x_stop(struct usb_hcd *hcd) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&isp116x->lock, flags); + isp116x_write_reg16(isp116x, HCuPINTENB, 0); + + /* Switch off ports' power, some devices don't come up + after next 'insmod' without this */ + val = isp116x_read_reg32(isp116x, HCRHDESCA); + val &= ~(RH_A_NPS | RH_A_PSM); + isp116x_write_reg32(isp116x, HCRHDESCA, val); + isp116x_write_reg32(isp116x, HCRHSTATUS, RH_HS_LPS); + spin_unlock_irqrestore(&isp116x->lock, flags); + + isp116x_sw_reset(isp116x); +} + +/* + Configure the chip. The chip must be successfully reset by now. +*/ +static int isp116x_start(struct usb_hcd *hcd) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + struct isp116x_platform_data *board = isp116x->board; + u32 val; + unsigned long flags; + + spin_lock_irqsave(&isp116x->lock, flags); + + /* clear interrupt status and disable all interrupt sources */ + isp116x_write_reg16(isp116x, HCuPINT, 0xff); + isp116x_write_reg16(isp116x, HCuPINTENB, 0); + + val = isp116x_read_reg16(isp116x, HCCHIPID); + if ((val & HCCHIPID_MASK) != HCCHIPID_MAGIC) { + ERR("Invalid chip ID %04x\n", val); + spin_unlock_irqrestore(&isp116x->lock, flags); + return -ENODEV; + } + + /* To be removed in future */ + hcd->uses_new_polling = 1; + + isp116x_write_reg16(isp116x, HCITLBUFLEN, ISP116x_ITL_BUFSIZE); + isp116x_write_reg16(isp116x, HCATLBUFLEN, ISP116x_ATL_BUFSIZE); + + /* ----- HW conf */ + val = HCHWCFG_INT_ENABLE | HCHWCFG_DBWIDTH(1); + if (board->sel15Kres) + val |= HCHWCFG_15KRSEL; + /* Remote wakeup won't work without working clock */ + if (board->remote_wakeup_enable) + val |= HCHWCFG_CLKNOTSTOP; + if (board->oc_enable) + val |= HCHWCFG_ANALOG_OC; + if (board->int_act_high) + val |= HCHWCFG_INT_POL; + if (board->int_edge_triggered) + val |= HCHWCFG_INT_TRIGGER; + isp116x_write_reg16(isp116x, HCHWCFG, val); + + /* ----- Root hub conf */ + val = (25 << 24) & RH_A_POTPGT; + /* AN10003_1.pdf recommends RH_A_NPS (no power switching) to + be always set. Yet, instead, we request individual port + power switching. */ + val |= RH_A_PSM; + /* Report overcurrent per port */ + val |= RH_A_OCPM; + isp116x_write_reg32(isp116x, HCRHDESCA, val); + isp116x->rhdesca = isp116x_read_reg32(isp116x, HCRHDESCA); + + val = RH_B_PPCM; + isp116x_write_reg32(isp116x, HCRHDESCB, val); + isp116x->rhdescb = isp116x_read_reg32(isp116x, HCRHDESCB); + + val = 0; + if (board->remote_wakeup_enable) { + if (!device_can_wakeup(hcd->self.controller)) + device_init_wakeup(hcd->self.controller, 1); + val |= RH_HS_DRWE; + } + isp116x_write_reg32(isp116x, HCRHSTATUS, val); + isp116x->rhstatus = isp116x_read_reg32(isp116x, HCRHSTATUS); + + isp116x_write_reg32(isp116x, HCFMINTVL, 0x27782edf); + + hcd->state = HC_STATE_RUNNING; + + /* Set up interrupts */ + isp116x->intenb = HCINT_MIE | HCINT_RHSC | HCINT_UE; + if (board->remote_wakeup_enable) + isp116x->intenb |= HCINT_RD; + isp116x->irqenb = HCuPINT_ATL | HCuPINT_OPR; /* | HCuPINT_SUSP; */ + isp116x_write_reg32(isp116x, HCINTENB, isp116x->intenb); + isp116x_write_reg16(isp116x, HCuPINTENB, isp116x->irqenb); + + /* Go operational */ + val = HCCONTROL_USB_OPER; + if (board->remote_wakeup_enable) + val |= HCCONTROL_RWE; + isp116x_write_reg32(isp116x, HCCONTROL, val); + + /* Disable ports to avoid race in device enumeration */ + isp116x_write_reg32(isp116x, HCRHPORT1, RH_PS_CCS); + isp116x_write_reg32(isp116x, HCRHPORT2, RH_PS_CCS); + + isp116x_show_regs_log(isp116x); + spin_unlock_irqrestore(&isp116x->lock, flags); + return 0; +} + +#ifdef CONFIG_PM + +static int isp116x_bus_suspend(struct usb_hcd *hcd) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + unsigned long flags; + u32 val; + int ret = 0; + + spin_lock_irqsave(&isp116x->lock, flags); + val = isp116x_read_reg32(isp116x, HCCONTROL); + + switch (val & HCCONTROL_HCFS) { + case HCCONTROL_USB_OPER: + spin_unlock_irqrestore(&isp116x->lock, flags); + val &= (~HCCONTROL_HCFS & ~HCCONTROL_RWE); + val |= HCCONTROL_USB_SUSPEND; + if (hcd->self.root_hub->do_remote_wakeup) + val |= HCCONTROL_RWE; + /* Wait for usb transfers to finish */ + msleep(2); + spin_lock_irqsave(&isp116x->lock, flags); + isp116x_write_reg32(isp116x, HCCONTROL, val); + spin_unlock_irqrestore(&isp116x->lock, flags); + /* Wait for devices to suspend */ + msleep(5); + break; + case HCCONTROL_USB_RESUME: + isp116x_write_reg32(isp116x, HCCONTROL, + (val & ~HCCONTROL_HCFS) | + HCCONTROL_USB_RESET); + case HCCONTROL_USB_RESET: + ret = -EBUSY; + default: /* HCCONTROL_USB_SUSPEND */ + spin_unlock_irqrestore(&isp116x->lock, flags); + break; + } + + return ret; +} + +static int isp116x_bus_resume(struct usb_hcd *hcd) +{ + struct isp116x *isp116x = hcd_to_isp116x(hcd); + u32 val; + + msleep(5); + spin_lock_irq(&isp116x->lock); + + val = isp116x_read_reg32(isp116x, HCCONTROL); + switch (val & HCCONTROL_HCFS) { + case HCCONTROL_USB_SUSPEND: + val &= ~HCCONTROL_HCFS; + val |= HCCONTROL_USB_RESUME; + isp116x_write_reg32(isp116x, HCCONTROL, val); + case HCCONTROL_USB_RESUME: + break; + case HCCONTROL_USB_OPER: + spin_unlock_irq(&isp116x->lock); + return 0; + default: + /* HCCONTROL_USB_RESET: this may happen, when during + suspension the HC lost power. Reinitialize completely */ + spin_unlock_irq(&isp116x->lock); + DBG("Chip has been reset while suspended. Reinit from scratch.\n"); + isp116x_reset(hcd); + isp116x_start(hcd); + isp116x_hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_POWER, 1, NULL, 0); + if ((isp116x->rhdesca & RH_A_NDP) == 2) + isp116x_hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_POWER, 2, NULL, 0); + return 0; + } + + val = isp116x->rhdesca & RH_A_NDP; + while (val--) { + u32 stat = + isp116x_read_reg32(isp116x, val ? HCRHPORT2 : HCRHPORT1); + /* force global, not selective, resume */ + if (!(stat & RH_PS_PSS)) + continue; + DBG("%s: Resuming port %d\n", __func__, val); + isp116x_write_reg32(isp116x, RH_PS_POCI, val + ? HCRHPORT2 : HCRHPORT1); + } + spin_unlock_irq(&isp116x->lock); + + hcd->state = HC_STATE_RESUMING; + msleep(20); + + /* Go operational */ + spin_lock_irq(&isp116x->lock); + val = isp116x_read_reg32(isp116x, HCCONTROL); + isp116x_write_reg32(isp116x, HCCONTROL, + (val & ~HCCONTROL_HCFS) | HCCONTROL_USB_OPER); + spin_unlock_irq(&isp116x->lock); + hcd->state = HC_STATE_RUNNING; + + return 0; +} + +#else + +#define isp116x_bus_suspend NULL +#define isp116x_bus_resume NULL + +#endif + +static struct hc_driver isp116x_hc_driver = { + .description = hcd_name, + .product_desc = "ISP116x Host Controller", + .hcd_priv_size = sizeof(struct isp116x), + + .irq = isp116x_irq, + .flags = HCD_USB11, + + .reset = isp116x_reset, + .start = isp116x_start, + .stop = isp116x_stop, + + .urb_enqueue = isp116x_urb_enqueue, + .urb_dequeue = isp116x_urb_dequeue, + .endpoint_disable = isp116x_endpoint_disable, + + .get_frame_number = isp116x_get_frame, + + .hub_status_data = isp116x_hub_status_data, + .hub_control = isp116x_hub_control, + .bus_suspend = isp116x_bus_suspend, + .bus_resume = isp116x_bus_resume, +}; + +/*----------------------------------------------------------------*/ + +static int isp116x_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct isp116x *isp116x; + struct resource *res; + + if (!hcd) + return 0; + isp116x = hcd_to_isp116x(hcd); + remove_debug_file(isp116x); + usb_remove_hcd(hcd); + + iounmap(isp116x->data_reg); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + release_mem_region(res->start, 2); + iounmap(isp116x->addr_reg); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, 2); + + usb_put_hcd(hcd); + return 0; +} + +static int isp116x_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct isp116x *isp116x; + struct resource *addr, *data, *ires; + void __iomem *addr_reg; + void __iomem *data_reg; + int irq; + int ret = 0; + unsigned long irqflags; + + if (usb_disabled()) + return -ENODEV; + + if (pdev->num_resources < 3) { + ret = -ENODEV; + goto err1; + } + + data = platform_get_resource(pdev, IORESOURCE_MEM, 0); + addr = platform_get_resource(pdev, IORESOURCE_MEM, 1); + ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + + if (!addr || !data || !ires) { + ret = -ENODEV; + goto err1; + } + + irq = ires->start; + irqflags = ires->flags & IRQF_TRIGGER_MASK; + + if (pdev->dev.dma_mask) { + DBG("DMA not supported\n"); + ret = -EINVAL; + goto err1; + } + + if (!request_mem_region(addr->start, 2, hcd_name)) { + ret = -EBUSY; + goto err1; + } + addr_reg = ioremap(addr->start, resource_size(addr)); + if (addr_reg == NULL) { + ret = -ENOMEM; + goto err2; + } + if (!request_mem_region(data->start, 2, hcd_name)) { + ret = -EBUSY; + goto err3; + } + data_reg = ioremap(data->start, resource_size(data)); + if (data_reg == NULL) { + ret = -ENOMEM; + goto err4; + } + + /* allocate and initialize hcd */ + hcd = usb_create_hcd(&isp116x_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + ret = -ENOMEM; + goto err5; + } + /* this rsrc_start is bogus */ + hcd->rsrc_start = addr->start; + isp116x = hcd_to_isp116x(hcd); + isp116x->data_reg = data_reg; + isp116x->addr_reg = addr_reg; + spin_lock_init(&isp116x->lock); + INIT_LIST_HEAD(&isp116x->async); + isp116x->board = pdev->dev.platform_data; + + if (!isp116x->board) { + ERR("Platform data structure not initialized\n"); + ret = -ENODEV; + goto err6; + } + if (isp116x_check_platform_delay(isp116x)) { + ERR("USE_PLATFORM_DELAY defined, but delay function not " + "implemented.\n"); + ERR("See comments in drivers/usb/host/isp116x-hcd.c\n"); + ret = -ENODEV; + goto err6; + } + + ret = usb_add_hcd(hcd, irq, irqflags); + if (ret) + goto err6; + + ret = create_debug_file(isp116x); + if (ret) { + ERR("Couldn't create debugfs entry\n"); + goto err7; + } + + return 0; + + err7: + usb_remove_hcd(hcd); + err6: + usb_put_hcd(hcd); + err5: + iounmap(data_reg); + err4: + release_mem_region(data->start, 2); + err3: + iounmap(addr_reg); + err2: + release_mem_region(addr->start, 2); + err1: + ERR("init error, %d\n", ret); + return ret; +} + +#ifdef CONFIG_PM +/* + Suspend of platform device +*/ +static int isp116x_suspend(struct platform_device *dev, pm_message_t state) +{ + VDBG("%s: state %x\n", __func__, state.event); + return 0; +} + +/* + Resume platform device +*/ +static int isp116x_resume(struct platform_device *dev) +{ + VDBG("%s\n", __func__); + return 0; +} + +#else + +#define isp116x_suspend NULL +#define isp116x_resume NULL + +#endif + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:isp116x-hcd"); + +static struct platform_driver isp116x_driver = { + .probe = isp116x_probe, + .remove = isp116x_remove, + .suspend = isp116x_suspend, + .resume = isp116x_resume, + .driver = { + .name = (char *)hcd_name, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(isp116x_driver); diff --git a/drivers/usb/host/isp116x.h b/drivers/usb/host/isp116x.h new file mode 100644 index 00000000..9a2c400e --- /dev/null +++ b/drivers/usb/host/isp116x.h @@ -0,0 +1,606 @@ +/* + * ISP116x register declarations and HCD data structures + * + * Copyright (C) 2005 Olav Kongas + * Portions: + * Copyright (C) 2004 Lothar Wassmann + * Copyright (C) 2004 Psion Teklogix + * Copyright (C) 2004 David Brownell + */ + +/* us of 1ms frame */ +#define MAX_LOAD_LIMIT 850 + +/* Full speed: max # of bytes to transfer for a single urb + at a time must be < 1024 && must be multiple of 64. + 832 allows transferring 4kiB within 5 frames. */ +#define MAX_TRANSFER_SIZE_FULLSPEED 832 + +/* Low speed: there is no reason to schedule in very big + chunks; often the requested long transfers are for + string descriptors containing short strings. */ +#define MAX_TRANSFER_SIZE_LOWSPEED 64 + +/* Bytetime (us), a rough indication of how much time it + would take to transfer a byte of useful data over USB */ +#define BYTE_TIME_FULLSPEED 1 +#define BYTE_TIME_LOWSPEED 20 + +/* Buffer sizes */ +#define ISP116x_BUF_SIZE 4096 +#define ISP116x_ITL_BUFSIZE 0 +#define ISP116x_ATL_BUFSIZE ((ISP116x_BUF_SIZE) - 2*(ISP116x_ITL_BUFSIZE)) + +#define ISP116x_WRITE_OFFSET 0x80 + +/*------------ ISP116x registers/bits ------------*/ +#define HCREVISION 0x00 +#define HCCONTROL 0x01 +#define HCCONTROL_HCFS (3 << 6) /* host controller + functional state */ +#define HCCONTROL_USB_RESET (0 << 6) +#define HCCONTROL_USB_RESUME (1 << 6) +#define HCCONTROL_USB_OPER (2 << 6) +#define HCCONTROL_USB_SUSPEND (3 << 6) +#define HCCONTROL_RWC (1 << 9) /* remote wakeup connected */ +#define HCCONTROL_RWE (1 << 10) /* remote wakeup enable */ +#define HCCMDSTAT 0x02 +#define HCCMDSTAT_HCR (1 << 0) /* host controller reset */ +#define HCCMDSTAT_SOC (3 << 16) /* scheduling overrun count */ +#define HCINTSTAT 0x03 +#define HCINT_SO (1 << 0) /* scheduling overrun */ +#define HCINT_WDH (1 << 1) /* writeback of done_head */ +#define HCINT_SF (1 << 2) /* start frame */ +#define HCINT_RD (1 << 3) /* resume detect */ +#define HCINT_UE (1 << 4) /* unrecoverable error */ +#define HCINT_FNO (1 << 5) /* frame number overflow */ +#define HCINT_RHSC (1 << 6) /* root hub status change */ +#define HCINT_OC (1 << 30) /* ownership change */ +#define HCINT_MIE (1 << 31) /* master interrupt enable */ +#define HCINTENB 0x04 +#define HCINTDIS 0x05 +#define HCFMINTVL 0x0d +#define HCFMREM 0x0e +#define HCFMNUM 0x0f +#define HCLSTHRESH 0x11 +#define HCRHDESCA 0x12 +#define RH_A_NDP (0x3 << 0) /* # downstream ports */ +#define RH_A_PSM (1 << 8) /* power switching mode */ +#define RH_A_NPS (1 << 9) /* no power switching */ +#define RH_A_DT (1 << 10) /* device type (mbz) */ +#define RH_A_OCPM (1 << 11) /* overcurrent protection + mode */ +#define RH_A_NOCP (1 << 12) /* no overcurrent protection */ +#define RH_A_POTPGT (0xff << 24) /* power on -> power good + time */ +#define HCRHDESCB 0x13 +#define RH_B_DR (0xffff << 0) /* device removable flags */ +#define RH_B_PPCM (0xffff << 16) /* port power control mask */ +#define HCRHSTATUS 0x14 +#define RH_HS_LPS (1 << 0) /* local power status */ +#define RH_HS_OCI (1 << 1) /* over current indicator */ +#define RH_HS_DRWE (1 << 15) /* device remote wakeup + enable */ +#define RH_HS_LPSC (1 << 16) /* local power status change */ +#define RH_HS_OCIC (1 << 17) /* over current indicator + change */ +#define RH_HS_CRWE (1 << 31) /* clear remote wakeup + enable */ +#define HCRHPORT1 0x15 +#define RH_PS_CCS (1 << 0) /* current connect status */ +#define RH_PS_PES (1 << 1) /* port enable status */ +#define RH_PS_PSS (1 << 2) /* port suspend status */ +#define RH_PS_POCI (1 << 3) /* port over current + indicator */ +#define RH_PS_PRS (1 << 4) /* port reset status */ +#define RH_PS_PPS (1 << 8) /* port power status */ +#define RH_PS_LSDA (1 << 9) /* low speed device attached */ +#define RH_PS_CSC (1 << 16) /* connect status change */ +#define RH_PS_PESC (1 << 17) /* port enable status change */ +#define RH_PS_PSSC (1 << 18) /* port suspend status + change */ +#define RH_PS_OCIC (1 << 19) /* over current indicator + change */ +#define RH_PS_PRSC (1 << 20) /* port reset status change */ +#define HCRHPORT_CLRMASK (0x1f << 16) +#define HCRHPORT2 0x16 +#define HCHWCFG 0x20 +#define HCHWCFG_15KRSEL (1 << 12) +#define HCHWCFG_CLKNOTSTOP (1 << 11) +#define HCHWCFG_ANALOG_OC (1 << 10) +#define HCHWCFG_DACK_MODE (1 << 8) +#define HCHWCFG_EOT_POL (1 << 7) +#define HCHWCFG_DACK_POL (1 << 6) +#define HCHWCFG_DREQ_POL (1 << 5) +#define HCHWCFG_DBWIDTH_MASK (0x03 << 3) +#define HCHWCFG_DBWIDTH(n) (((n) << 3) & HCHWCFG_DBWIDTH_MASK) +#define HCHWCFG_INT_POL (1 << 2) +#define HCHWCFG_INT_TRIGGER (1 << 1) +#define HCHWCFG_INT_ENABLE (1 << 0) +#define HCDMACFG 0x21 +#define HCDMACFG_BURST_LEN_MASK (0x03 << 5) +#define HCDMACFG_BURST_LEN(n) (((n) << 5) & HCDMACFG_BURST_LEN_MASK) +#define HCDMACFG_BURST_LEN_1 HCDMACFG_BURST_LEN(0) +#define HCDMACFG_BURST_LEN_4 HCDMACFG_BURST_LEN(1) +#define HCDMACFG_BURST_LEN_8 HCDMACFG_BURST_LEN(2) +#define HCDMACFG_DMA_ENABLE (1 << 4) +#define HCDMACFG_BUF_TYPE_MASK (0x07 << 1) +#define HCDMACFG_CTR_SEL (1 << 2) +#define HCDMACFG_ITLATL_SEL (1 << 1) +#define HCDMACFG_DMA_RW_SELECT (1 << 0) +#define HCXFERCTR 0x22 +#define HCuPINT 0x24 +#define HCuPINT_SOF (1 << 0) +#define HCuPINT_ATL (1 << 1) +#define HCuPINT_AIIEOT (1 << 2) +#define HCuPINT_OPR (1 << 4) +#define HCuPINT_SUSP (1 << 5) +#define HCuPINT_CLKRDY (1 << 6) +#define HCuPINTENB 0x25 +#define HCCHIPID 0x27 +#define HCCHIPID_MASK 0xff00 +#define HCCHIPID_MAGIC 0x6100 +#define HCSCRATCH 0x28 +#define HCSWRES 0x29 +#define HCSWRES_MAGIC 0x00f6 +#define HCITLBUFLEN 0x2a +#define HCATLBUFLEN 0x2b +#define HCBUFSTAT 0x2c +#define HCBUFSTAT_ITL0_FULL (1 << 0) +#define HCBUFSTAT_ITL1_FULL (1 << 1) +#define HCBUFSTAT_ATL_FULL (1 << 2) +#define HCBUFSTAT_ITL0_DONE (1 << 3) +#define HCBUFSTAT_ITL1_DONE (1 << 4) +#define HCBUFSTAT_ATL_DONE (1 << 5) +#define HCRDITL0LEN 0x2d +#define HCRDITL1LEN 0x2e +#define HCITLPORT 0x40 +#define HCATLPORT 0x41 + +/* Philips transfer descriptor */ +struct ptd { + u16 count; +#define PTD_COUNT_MSK (0x3ff << 0) +#define PTD_TOGGLE_MSK (1 << 10) +#define PTD_ACTIVE_MSK (1 << 11) +#define PTD_CC_MSK (0xf << 12) + u16 mps; +#define PTD_MPS_MSK (0x3ff << 0) +#define PTD_SPD_MSK (1 << 10) +#define PTD_LAST_MSK (1 << 11) +#define PTD_EP_MSK (0xf << 12) + u16 len; +#define PTD_LEN_MSK (0x3ff << 0) +#define PTD_DIR_MSK (3 << 10) +#define PTD_DIR_SETUP (0) +#define PTD_DIR_OUT (1) +#define PTD_DIR_IN (2) +#define PTD_B5_5_MSK (1 << 13) + u16 faddr; +#define PTD_FA_MSK (0x7f << 0) +#define PTD_FMT_MSK (1 << 7) +} __attribute__ ((packed, aligned(2))); + +/* PTD accessor macros. */ +#define PTD_GET_COUNT(p) (((p)->count & PTD_COUNT_MSK) >> 0) +#define PTD_COUNT(v) (((v) << 0) & PTD_COUNT_MSK) +#define PTD_GET_TOGGLE(p) (((p)->count & PTD_TOGGLE_MSK) >> 10) +#define PTD_TOGGLE(v) (((v) << 10) & PTD_TOGGLE_MSK) +#define PTD_GET_ACTIVE(p) (((p)->count & PTD_ACTIVE_MSK) >> 11) +#define PTD_ACTIVE(v) (((v) << 11) & PTD_ACTIVE_MSK) +#define PTD_GET_CC(p) (((p)->count & PTD_CC_MSK) >> 12) +#define PTD_CC(v) (((v) << 12) & PTD_CC_MSK) +#define PTD_GET_MPS(p) (((p)->mps & PTD_MPS_MSK) >> 0) +#define PTD_MPS(v) (((v) << 0) & PTD_MPS_MSK) +#define PTD_GET_SPD(p) (((p)->mps & PTD_SPD_MSK) >> 10) +#define PTD_SPD(v) (((v) << 10) & PTD_SPD_MSK) +#define PTD_GET_LAST(p) (((p)->mps & PTD_LAST_MSK) >> 11) +#define PTD_LAST(v) (((v) << 11) & PTD_LAST_MSK) +#define PTD_GET_EP(p) (((p)->mps & PTD_EP_MSK) >> 12) +#define PTD_EP(v) (((v) << 12) & PTD_EP_MSK) +#define PTD_GET_LEN(p) (((p)->len & PTD_LEN_MSK) >> 0) +#define PTD_LEN(v) (((v) << 0) & PTD_LEN_MSK) +#define PTD_GET_DIR(p) (((p)->len & PTD_DIR_MSK) >> 10) +#define PTD_DIR(v) (((v) << 10) & PTD_DIR_MSK) +#define PTD_GET_B5_5(p) (((p)->len & PTD_B5_5_MSK) >> 13) +#define PTD_B5_5(v) (((v) << 13) & PTD_B5_5_MSK) +#define PTD_GET_FA(p) (((p)->faddr & PTD_FA_MSK) >> 0) +#define PTD_FA(v) (((v) << 0) & PTD_FA_MSK) +#define PTD_GET_FMT(p) (((p)->faddr & PTD_FMT_MSK) >> 7) +#define PTD_FMT(v) (((v) << 7) & PTD_FMT_MSK) + +/* Hardware transfer status codes -- CC from ptd->count */ +#define TD_CC_NOERROR 0x00 +#define TD_CC_CRC 0x01 +#define TD_CC_BITSTUFFING 0x02 +#define TD_CC_DATATOGGLEM 0x03 +#define TD_CC_STALL 0x04 +#define TD_DEVNOTRESP 0x05 +#define TD_PIDCHECKFAIL 0x06 +#define TD_UNEXPECTEDPID 0x07 +#define TD_DATAOVERRUN 0x08 +#define TD_DATAUNDERRUN 0x09 + /* 0x0A, 0x0B reserved for hardware */ +#define TD_BUFFEROVERRUN 0x0C +#define TD_BUFFERUNDERRUN 0x0D + /* 0x0E, 0x0F reserved for HCD */ +#define TD_NOTACCESSED 0x0F + +/* map PTD status codes (CC) to errno values */ +static const int cc_to_error[16] = { + /* No Error */ 0, + /* CRC Error */ -EILSEQ, + /* Bit Stuff */ -EPROTO, + /* Data Togg */ -EILSEQ, + /* Stall */ -EPIPE, + /* DevNotResp */ -ETIME, + /* PIDCheck */ -EPROTO, + /* UnExpPID */ -EPROTO, + /* DataOver */ -EOVERFLOW, + /* DataUnder */ -EREMOTEIO, + /* (for hw) */ -EIO, + /* (for hw) */ -EIO, + /* BufferOver */ -ECOMM, + /* BuffUnder */ -ENOSR, + /* (for HCD) */ -EALREADY, + /* (for HCD) */ -EALREADY +}; + +/*--------------------------------------------------------------*/ + +#define LOG2_PERIODIC_SIZE 5 /* arbitrary; this matches OHCI */ +#define PERIODIC_SIZE (1 << LOG2_PERIODIC_SIZE) + +struct isp116x { + spinlock_t lock; + + void __iomem *addr_reg; + void __iomem *data_reg; + + struct isp116x_platform_data *board; + + struct dentry *dentry; + unsigned long stat1, stat2, stat4, stat8, stat16; + + /* HC registers */ + u32 intenb; /* "OHCI" interrupts */ + u16 irqenb; /* uP interrupts */ + + /* Root hub registers */ + u32 rhdesca; + u32 rhdescb; + u32 rhstatus; + + /* async schedule: control, bulk */ + struct list_head async; + + /* periodic schedule: int */ + u16 load[PERIODIC_SIZE]; + struct isp116x_ep *periodic[PERIODIC_SIZE]; + unsigned periodic_count; + u16 fmindex; + + /* Schedule for the current frame */ + struct isp116x_ep *atl_active; + int atl_buflen; + int atl_bufshrt; + int atl_last_dir; + atomic_t atl_finishing; +}; + +static inline struct isp116x *hcd_to_isp116x(struct usb_hcd *hcd) +{ + return (struct isp116x *)(hcd->hcd_priv); +} + +static inline struct usb_hcd *isp116x_to_hcd(struct isp116x *isp116x) +{ + return container_of((void *)isp116x, struct usb_hcd, hcd_priv); +} + +struct isp116x_ep { + struct usb_host_endpoint *hep; + struct usb_device *udev; + struct ptd ptd; + + u8 maxpacket; + u8 epnum; + u8 nextpid; + u16 error_count; + u16 length; /* of current packet */ + unsigned char *data; /* to databuf */ + /* queue of active EP's (the ones scheduled for the + current frame) */ + struct isp116x_ep *active; + + /* periodic schedule */ + u16 period; + u16 branch; + u16 load; + struct isp116x_ep *next; + + /* async schedule */ + struct list_head schedule; +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +#define DBG(stuff...) printk(KERN_DEBUG "116x: " stuff) +#else +#define DBG(stuff...) do{}while(0) +#endif + +#ifdef VERBOSE +# define VDBG DBG +#else +# define VDBG(stuff...) do{}while(0) +#endif + +#define ERR(stuff...) printk(KERN_ERR "116x: " stuff) +#define WARNING(stuff...) printk(KERN_WARNING "116x: " stuff) +#define INFO(stuff...) printk(KERN_INFO "116x: " stuff) + +/* ------------------------------------------------- */ + +#if defined(USE_PLATFORM_DELAY) +#if defined(USE_NDELAY) +#error USE_PLATFORM_DELAY and USE_NDELAY simultaneously defined. +#endif +#define isp116x_delay(h,d) (h)->board->delay( \ + isp116x_to_hcd(h)->self.controller,d) +#define isp116x_check_platform_delay(h) ((h)->board->delay == NULL) +#elif defined(USE_NDELAY) +#define isp116x_delay(h,d) ndelay(d) +#define isp116x_check_platform_delay(h) 0 +#else +#define isp116x_delay(h,d) do{}while(0) +#define isp116x_check_platform_delay(h) 0 +#endif + +#if defined(DEBUG) +#define IRQ_TEST() BUG_ON(!irqs_disabled()) +#else +#define IRQ_TEST() do{}while(0) +#endif + +static inline void isp116x_write_addr(struct isp116x *isp116x, unsigned reg) +{ + IRQ_TEST(); + writew(reg & 0xff, isp116x->addr_reg); + isp116x_delay(isp116x, 300); +} + +static inline void isp116x_write_data16(struct isp116x *isp116x, u16 val) +{ + writew(val, isp116x->data_reg); + isp116x_delay(isp116x, 150); +} + +static inline void isp116x_raw_write_data16(struct isp116x *isp116x, u16 val) +{ + __raw_writew(val, isp116x->data_reg); + isp116x_delay(isp116x, 150); +} + +static inline u16 isp116x_read_data16(struct isp116x *isp116x) +{ + u16 val; + + val = readw(isp116x->data_reg); + isp116x_delay(isp116x, 150); + return val; +} + +static inline u16 isp116x_raw_read_data16(struct isp116x *isp116x) +{ + u16 val; + + val = __raw_readw(isp116x->data_reg); + isp116x_delay(isp116x, 150); + return val; +} + +static inline void isp116x_write_data32(struct isp116x *isp116x, u32 val) +{ + writew(val & 0xffff, isp116x->data_reg); + isp116x_delay(isp116x, 150); + writew(val >> 16, isp116x->data_reg); + isp116x_delay(isp116x, 150); +} + +static inline u32 isp116x_read_data32(struct isp116x *isp116x) +{ + u32 val; + + val = (u32) readw(isp116x->data_reg); + isp116x_delay(isp116x, 150); + val |= ((u32) readw(isp116x->data_reg)) << 16; + isp116x_delay(isp116x, 150); + return val; +} + +/* Let's keep register access functions out of line. Hint: + we wait at least 150 ns at every access. +*/ +static u16 isp116x_read_reg16(struct isp116x *isp116x, unsigned reg) +{ + isp116x_write_addr(isp116x, reg); + return isp116x_read_data16(isp116x); +} + +static u32 isp116x_read_reg32(struct isp116x *isp116x, unsigned reg) +{ + isp116x_write_addr(isp116x, reg); + return isp116x_read_data32(isp116x); +} + +static void isp116x_write_reg16(struct isp116x *isp116x, unsigned reg, + unsigned val) +{ + isp116x_write_addr(isp116x, reg | ISP116x_WRITE_OFFSET); + isp116x_write_data16(isp116x, (u16) (val & 0xffff)); +} + +static void isp116x_write_reg32(struct isp116x *isp116x, unsigned reg, + unsigned val) +{ + isp116x_write_addr(isp116x, reg | ISP116x_WRITE_OFFSET); + isp116x_write_data32(isp116x, (u32) val); +} + +#define isp116x_show_reg_log(d,r,s) { \ + if ((r) < 0x20) { \ + DBG("%-12s[%02x]: %08x\n", #r, \ + r, isp116x_read_reg32(d, r)); \ + } else { \ + DBG("%-12s[%02x]: %04x\n", #r, \ + r, isp116x_read_reg16(d, r)); \ + } \ +} +#define isp116x_show_reg_seq(d,r,s) { \ + if ((r) < 0x20) { \ + seq_printf(s, "%-12s[%02x]: %08x\n", #r, \ + r, isp116x_read_reg32(d, r)); \ + } else { \ + seq_printf(s, "%-12s[%02x]: %04x\n", #r, \ + r, isp116x_read_reg16(d, r)); \ + } \ +} + +#define isp116x_show_regs(d,type,s) { \ + isp116x_show_reg_##type(d, HCREVISION, s); \ + isp116x_show_reg_##type(d, HCCONTROL, s); \ + isp116x_show_reg_##type(d, HCCMDSTAT, s); \ + isp116x_show_reg_##type(d, HCINTSTAT, s); \ + isp116x_show_reg_##type(d, HCINTENB, s); \ + isp116x_show_reg_##type(d, HCFMINTVL, s); \ + isp116x_show_reg_##type(d, HCFMREM, s); \ + isp116x_show_reg_##type(d, HCFMNUM, s); \ + isp116x_show_reg_##type(d, HCLSTHRESH, s); \ + isp116x_show_reg_##type(d, HCRHDESCA, s); \ + isp116x_show_reg_##type(d, HCRHDESCB, s); \ + isp116x_show_reg_##type(d, HCRHSTATUS, s); \ + isp116x_show_reg_##type(d, HCRHPORT1, s); \ + isp116x_show_reg_##type(d, HCRHPORT2, s); \ + isp116x_show_reg_##type(d, HCHWCFG, s); \ + isp116x_show_reg_##type(d, HCDMACFG, s); \ + isp116x_show_reg_##type(d, HCXFERCTR, s); \ + isp116x_show_reg_##type(d, HCuPINT, s); \ + isp116x_show_reg_##type(d, HCuPINTENB, s); \ + isp116x_show_reg_##type(d, HCCHIPID, s); \ + isp116x_show_reg_##type(d, HCSCRATCH, s); \ + isp116x_show_reg_##type(d, HCITLBUFLEN, s); \ + isp116x_show_reg_##type(d, HCATLBUFLEN, s); \ + isp116x_show_reg_##type(d, HCBUFSTAT, s); \ + isp116x_show_reg_##type(d, HCRDITL0LEN, s); \ + isp116x_show_reg_##type(d, HCRDITL1LEN, s); \ +} + +/* + Dump registers for debugfs. +*/ +static inline void isp116x_show_regs_seq(struct isp116x *isp116x, + struct seq_file *s) +{ + isp116x_show_regs(isp116x, seq, s); +} + +/* + Dump registers to syslog. +*/ +static inline void isp116x_show_regs_log(struct isp116x *isp116x) +{ + isp116x_show_regs(isp116x, log, NULL); +} + +#if defined(URB_TRACE) + +#define PIPETYPE(pipe) ({ char *__s; \ + if (usb_pipecontrol(pipe)) __s = "ctrl"; \ + else if (usb_pipeint(pipe)) __s = "int"; \ + else if (usb_pipebulk(pipe)) __s = "bulk"; \ + else __s = "iso"; \ + __s;}) +#define PIPEDIR(pipe) ({ usb_pipein(pipe) ? "in" : "out"; }) +#define URB_NOTSHORT(urb) ({ (urb)->transfer_flags & URB_SHORT_NOT_OK ? \ + "short_not_ok" : ""; }) + +/* print debug info about the URB */ +static void urb_dbg(struct urb *urb, char *msg) +{ + unsigned int pipe; + + if (!urb) { + DBG("%s: zero urb\n", msg); + return; + } + pipe = urb->pipe; + DBG("%s: FA %d ep%d%s %s: len %d/%d %s\n", msg, + usb_pipedevice(pipe), usb_pipeendpoint(pipe), + PIPEDIR(pipe), PIPETYPE(pipe), + urb->transfer_buffer_length, urb->actual_length, URB_NOTSHORT(urb)); +} + +#else + +#define urb_dbg(urb,msg) do{}while(0) + +#endif /* ! defined(URB_TRACE) */ + +#if defined(PTD_TRACE) + +#define PTD_DIR_STR(ptd) ({char __c; \ + switch(PTD_GET_DIR(ptd)){ \ + case 0: __c = 's'; break; \ + case 1: __c = 'o'; break; \ + default: __c = 'i'; break; \ + }; __c;}) + +/* + Dump PTD info. The code documents the format + perfectly, right :) +*/ +static inline void dump_ptd(struct ptd *ptd) +{ + printk(KERN_WARNING "td: %x %d%c%d %d,%d,%d %x %x%x%x\n", + PTD_GET_CC(ptd), PTD_GET_FA(ptd), + PTD_DIR_STR(ptd), PTD_GET_EP(ptd), + PTD_GET_COUNT(ptd), PTD_GET_LEN(ptd), PTD_GET_MPS(ptd), + PTD_GET_TOGGLE(ptd), PTD_GET_ACTIVE(ptd), + PTD_GET_SPD(ptd), PTD_GET_LAST(ptd)); +} + +static inline void dump_ptd_out_data(struct ptd *ptd, u8 * buf) +{ + int k; + + if (PTD_GET_DIR(ptd) != PTD_DIR_IN && PTD_GET_LEN(ptd)) { + printk(KERN_WARNING "-> "); + for (k = 0; k < PTD_GET_LEN(ptd); ++k) + printk("%02x ", ((u8 *) buf)[k]); + printk("\n"); + } +} + +static inline void dump_ptd_in_data(struct ptd *ptd, u8 * buf) +{ + int k; + + if (PTD_GET_DIR(ptd) == PTD_DIR_IN && PTD_GET_COUNT(ptd)) { + printk(KERN_WARNING "<- "); + for (k = 0; k < PTD_GET_COUNT(ptd); ++k) + printk("%02x ", ((u8 *) buf)[k]); + printk("\n"); + } + if (PTD_GET_LAST(ptd)) + printk(KERN_WARNING "-\n"); +} + +#else + +#define dump_ptd(ptd) do{}while(0) +#define dump_ptd_in_data(ptd,buf) do{}while(0) +#define dump_ptd_out_data(ptd,buf) do{}while(0) + +#endif /* ! defined(PTD_TRACE) */ diff --git a/drivers/usb/host/isp1362-hcd.c b/drivers/usb/host/isp1362-hcd.c new file mode 100644 index 00000000..b04e8ece --- /dev/null +++ b/drivers/usb/host/isp1362-hcd.c @@ -0,0 +1,2866 @@ +/* + * ISP1362 HCD (Host Controller Driver) for USB. + * + * Copyright (C) 2005 Lothar Wassmann + * + * Derived from the SL811 HCD, rewritten for ISP116x. + * Copyright (C) 2005 Olav Kongas + * + * Portions: + * Copyright (C) 2004 Psion Teklogix (for NetBook PRO) + * Copyright (C) 2004 David Brownell + */ + +/* + * The ISP1362 chip requires a large delay (300ns and 462ns) between + * accesses to the address and data register. + * The following timing options exist: + * + * 1. Configure your memory controller to add such delays if it can (the best) + * 2. Implement platform-specific delay function possibly + * combined with configuring the memory controller; see + * include/linux/usb_isp1362.h for more info. + * 3. Use ndelay (easiest, poorest). + * + * Use the corresponding macros USE_PLATFORM_DELAY and USE_NDELAY in the + * platform specific section of isp1362.h to select the appropriate variant. + * + * Also note that according to the Philips "ISP1362 Errata" document + * Rev 1.00 from 27 May data corruption may occur when the #WR signal + * is reasserted (even with #CS deasserted) within 132ns after a + * write cycle to any controller register. If the hardware doesn't + * implement the recommended fix (gating the #WR with #CS) software + * must ensure that no further write cycle (not necessarily to the chip!) + * is issued by the CPU within this interval. + + * For PXA25x this can be ensured by using VLIO with the maximum + * recovery time (MSCx = 0x7f8c) with a memory clock of 99.53 MHz. + */ + +#ifdef CONFIG_USB_DEBUG +# define ISP1362_DEBUG +#else +# undef ISP1362_DEBUG +#endif + +/* + * The PXA255 UDC apparently doesn't handle GET_STATUS, GET_CONFIG and + * GET_INTERFACE requests correctly when the SETUP and DATA stages of the + * requests are carried out in separate frames. This will delay any SETUP + * packets until the start of the next frame so that this situation is + * unlikely to occur (and makes usbtest happy running with a PXA255 target + * device). + */ +#undef BUGGY_PXA2XX_UDC_USBTEST + +#undef PTD_TRACE +#undef URB_TRACE +#undef VERBOSE +#undef REGISTERS + +/* This enables a memory test on the ISP1362 chip memory to make sure the + * chip access timing is correct. + */ +#undef CHIP_BUFFER_TEST + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static int dbg_level; +#ifdef ISP1362_DEBUG +module_param(dbg_level, int, 0644); +#else +module_param(dbg_level, int, 0); +#define STUB_DEBUG_FILE +#endif + +#include "../core/usb.h" +#include "isp1362.h" + + +#define DRIVER_VERSION "2005-04-04" +#define DRIVER_DESC "ISP1362 USB Host Controller Driver" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static const char hcd_name[] = "isp1362-hcd"; + +static void isp1362_hc_stop(struct usb_hcd *hcd); +static int isp1362_hc_start(struct usb_hcd *hcd); + +/*-------------------------------------------------------------------------*/ + +/* + * When called from the interrupthandler only isp1362_hcd->irqenb is modified, + * since the interrupt handler will write isp1362_hcd->irqenb to HCuPINT upon + * completion. + * We don't need a 'disable' counterpart, since interrupts will be disabled + * only by the interrupt handler. + */ +static inline void isp1362_enable_int(struct isp1362_hcd *isp1362_hcd, u16 mask) +{ + if ((isp1362_hcd->irqenb | mask) == isp1362_hcd->irqenb) + return; + if (mask & ~isp1362_hcd->irqenb) + isp1362_write_reg16(isp1362_hcd, HCuPINT, mask & ~isp1362_hcd->irqenb); + isp1362_hcd->irqenb |= mask; + if (isp1362_hcd->irq_active) + return; + isp1362_write_reg16(isp1362_hcd, HCuPINTENB, isp1362_hcd->irqenb); +} + +/*-------------------------------------------------------------------------*/ + +static inline struct isp1362_ep_queue *get_ptd_queue(struct isp1362_hcd *isp1362_hcd, + u16 offset) +{ + struct isp1362_ep_queue *epq = NULL; + + if (offset < isp1362_hcd->istl_queue[1].buf_start) + epq = &isp1362_hcd->istl_queue[0]; + else if (offset < isp1362_hcd->intl_queue.buf_start) + epq = &isp1362_hcd->istl_queue[1]; + else if (offset < isp1362_hcd->atl_queue.buf_start) + epq = &isp1362_hcd->intl_queue; + else if (offset < isp1362_hcd->atl_queue.buf_start + + isp1362_hcd->atl_queue.buf_size) + epq = &isp1362_hcd->atl_queue; + + if (epq) + DBG(1, "%s: PTD $%04x is on %s queue\n", __func__, offset, epq->name); + else + pr_warning("%s: invalid PTD $%04x\n", __func__, offset); + + return epq; +} + +static inline int get_ptd_offset(struct isp1362_ep_queue *epq, u8 index) +{ + int offset; + + if (index * epq->blk_size > epq->buf_size) { + pr_warning("%s: Bad %s index %d(%d)\n", __func__, epq->name, index, + epq->buf_size / epq->blk_size); + return -EINVAL; + } + offset = epq->buf_start + index * epq->blk_size; + DBG(3, "%s: %s PTD[%02x] # %04x\n", __func__, epq->name, index, offset); + + return offset; +} + +/*-------------------------------------------------------------------------*/ + +static inline u16 max_transfer_size(struct isp1362_ep_queue *epq, size_t size, + int mps) +{ + u16 xfer_size = min_t(size_t, MAX_XFER_SIZE, size); + + xfer_size = min_t(size_t, xfer_size, epq->buf_avail * epq->blk_size - PTD_HEADER_SIZE); + if (xfer_size < size && xfer_size % mps) + xfer_size -= xfer_size % mps; + + return xfer_size; +} + +static int claim_ptd_buffers(struct isp1362_ep_queue *epq, + struct isp1362_ep *ep, u16 len) +{ + int ptd_offset = -EINVAL; + int num_ptds = ((len + PTD_HEADER_SIZE - 1) / epq->blk_size) + 1; + int found; + + BUG_ON(len > epq->buf_size); + + if (!epq->buf_avail) + return -ENOMEM; + + if (ep->num_ptds) + pr_err("%s: %s len %d/%d num_ptds %d buf_map %08lx skip_map %08lx\n", __func__, + epq->name, len, epq->blk_size, num_ptds, epq->buf_map, epq->skip_map); + BUG_ON(ep->num_ptds != 0); + + found = bitmap_find_next_zero_area(&epq->buf_map, epq->buf_count, 0, + num_ptds, 0); + if (found >= epq->buf_count) + return -EOVERFLOW; + + DBG(1, "%s: Found %d PTDs[%d] for %d/%d byte\n", __func__, + num_ptds, found, len, (int)(epq->blk_size - PTD_HEADER_SIZE)); + ptd_offset = get_ptd_offset(epq, found); + WARN_ON(ptd_offset < 0); + ep->ptd_offset = ptd_offset; + ep->num_ptds += num_ptds; + epq->buf_avail -= num_ptds; + BUG_ON(epq->buf_avail > epq->buf_count); + ep->ptd_index = found; + bitmap_set(&epq->buf_map, found, num_ptds); + DBG(1, "%s: Done %s PTD[%d] $%04x, avail %d count %d claimed %d %08lx:%08lx\n", + __func__, epq->name, ep->ptd_index, ep->ptd_offset, + epq->buf_avail, epq->buf_count, num_ptds, epq->buf_map, epq->skip_map); + + return found; +} + +static inline void release_ptd_buffers(struct isp1362_ep_queue *epq, struct isp1362_ep *ep) +{ + int last = ep->ptd_index + ep->num_ptds; + + if (last > epq->buf_count) + pr_err("%s: ep %p req %d len %d %s PTD[%d] $%04x num_ptds %d buf_count %d buf_avail %d buf_map %08lx skip_map %08lx\n", + __func__, ep, ep->num_req, ep->length, epq->name, ep->ptd_index, + ep->ptd_offset, ep->num_ptds, epq->buf_count, epq->buf_avail, + epq->buf_map, epq->skip_map); + BUG_ON(last > epq->buf_count); + + bitmap_clear(&epq->buf_map, ep->ptd_index, ep->num_ptds); + bitmap_set(&epq->skip_map, ep->ptd_index, ep->num_ptds); + epq->buf_avail += ep->num_ptds; + epq->ptd_count--; + + BUG_ON(epq->buf_avail > epq->buf_count); + BUG_ON(epq->ptd_count > epq->buf_count); + + DBG(1, "%s: Done %s PTDs $%04x released %d avail %d count %d\n", + __func__, epq->name, + ep->ptd_offset, ep->num_ptds, epq->buf_avail, epq->buf_count); + DBG(1, "%s: buf_map %08lx skip_map %08lx\n", __func__, + epq->buf_map, epq->skip_map); + + ep->num_ptds = 0; + ep->ptd_offset = -EINVAL; + ep->ptd_index = -EINVAL; +} + +/*-------------------------------------------------------------------------*/ + +/* + Set up PTD's. +*/ +static void prepare_ptd(struct isp1362_hcd *isp1362_hcd, struct urb *urb, + struct isp1362_ep *ep, struct isp1362_ep_queue *epq, + u16 fno) +{ + struct ptd *ptd; + int toggle; + int dir; + u16 len; + size_t buf_len = urb->transfer_buffer_length - urb->actual_length; + + DBG(3, "%s: %s ep %p\n", __func__, epq->name, ep); + + ptd = &ep->ptd; + + ep->data = (unsigned char *)urb->transfer_buffer + urb->actual_length; + + switch (ep->nextpid) { + case USB_PID_IN: + toggle = usb_gettoggle(urb->dev, ep->epnum, 0); + dir = PTD_DIR_IN; + if (usb_pipecontrol(urb->pipe)) { + len = min_t(size_t, ep->maxpacket, buf_len); + } else if (usb_pipeisoc(urb->pipe)) { + len = min_t(size_t, urb->iso_frame_desc[fno].length, MAX_XFER_SIZE); + ep->data = urb->transfer_buffer + urb->iso_frame_desc[fno].offset; + } else + len = max_transfer_size(epq, buf_len, ep->maxpacket); + DBG(1, "%s: IN len %d/%d/%d from URB\n", __func__, len, ep->maxpacket, + (int)buf_len); + break; + case USB_PID_OUT: + toggle = usb_gettoggle(urb->dev, ep->epnum, 1); + dir = PTD_DIR_OUT; + if (usb_pipecontrol(urb->pipe)) + len = min_t(size_t, ep->maxpacket, buf_len); + else if (usb_pipeisoc(urb->pipe)) + len = min_t(size_t, urb->iso_frame_desc[0].length, MAX_XFER_SIZE); + else + len = max_transfer_size(epq, buf_len, ep->maxpacket); + if (len == 0) + pr_info("%s: Sending ZERO packet: %d\n", __func__, + urb->transfer_flags & URB_ZERO_PACKET); + DBG(1, "%s: OUT len %d/%d/%d from URB\n", __func__, len, ep->maxpacket, + (int)buf_len); + break; + case USB_PID_SETUP: + toggle = 0; + dir = PTD_DIR_SETUP; + len = sizeof(struct usb_ctrlrequest); + DBG(1, "%s: SETUP len %d\n", __func__, len); + ep->data = urb->setup_packet; + break; + case USB_PID_ACK: + toggle = 1; + len = 0; + dir = (urb->transfer_buffer_length && usb_pipein(urb->pipe)) ? + PTD_DIR_OUT : PTD_DIR_IN; + DBG(1, "%s: ACK len %d\n", __func__, len); + break; + default: + toggle = dir = len = 0; + pr_err("%s@%d: ep->nextpid %02x\n", __func__, __LINE__, ep->nextpid); + BUG_ON(1); + } + + ep->length = len; + if (!len) + ep->data = NULL; + + ptd->count = PTD_CC_MSK | PTD_ACTIVE_MSK | PTD_TOGGLE(toggle); + ptd->mps = PTD_MPS(ep->maxpacket) | PTD_SPD(urb->dev->speed == USB_SPEED_LOW) | + PTD_EP(ep->epnum); + ptd->len = PTD_LEN(len) | PTD_DIR(dir); + ptd->faddr = PTD_FA(usb_pipedevice(urb->pipe)); + + if (usb_pipeint(urb->pipe)) { + ptd->faddr |= PTD_SF_INT(ep->branch); + ptd->faddr |= PTD_PR(ep->interval ? __ffs(ep->interval) : 0); + } + if (usb_pipeisoc(urb->pipe)) + ptd->faddr |= PTD_SF_ISO(fno); + + DBG(1, "%s: Finished\n", __func__); +} + +static void isp1362_write_ptd(struct isp1362_hcd *isp1362_hcd, struct isp1362_ep *ep, + struct isp1362_ep_queue *epq) +{ + struct ptd *ptd = &ep->ptd; + int len = PTD_GET_DIR(ptd) == PTD_DIR_IN ? 0 : ep->length; + + _BUG_ON(ep->ptd_offset < 0); + + prefetch(ptd); + isp1362_write_buffer(isp1362_hcd, ptd, ep->ptd_offset, PTD_HEADER_SIZE); + if (len) + isp1362_write_buffer(isp1362_hcd, ep->data, + ep->ptd_offset + PTD_HEADER_SIZE, len); + + dump_ptd(ptd); + dump_ptd_out_data(ptd, ep->data); +} + +static void isp1362_read_ptd(struct isp1362_hcd *isp1362_hcd, struct isp1362_ep *ep, + struct isp1362_ep_queue *epq) +{ + struct ptd *ptd = &ep->ptd; + int act_len; + + WARN_ON(list_empty(&ep->active)); + BUG_ON(ep->ptd_offset < 0); + + list_del_init(&ep->active); + DBG(1, "%s: ep %p removed from active list %p\n", __func__, ep, &epq->active); + + prefetchw(ptd); + isp1362_read_buffer(isp1362_hcd, ptd, ep->ptd_offset, PTD_HEADER_SIZE); + dump_ptd(ptd); + act_len = PTD_GET_COUNT(ptd); + if (PTD_GET_DIR(ptd) != PTD_DIR_IN || act_len == 0) + return; + if (act_len > ep->length) + pr_err("%s: ep %p PTD $%04x act_len %d ep->length %d\n", __func__, ep, + ep->ptd_offset, act_len, ep->length); + BUG_ON(act_len > ep->length); + /* Only transfer the amount of data that has actually been overwritten + * in the chip buffer. We don't want any data that doesn't belong to the + * transfer to leak out of the chip to the callers transfer buffer! + */ + prefetchw(ep->data); + isp1362_read_buffer(isp1362_hcd, ep->data, + ep->ptd_offset + PTD_HEADER_SIZE, act_len); + dump_ptd_in_data(ptd, ep->data); +} + +/* + * INT PTDs will stay in the chip until data is available. + * This function will remove a PTD from the chip when the URB is dequeued. + * Must be called with the spinlock held and IRQs disabled + */ +static void remove_ptd(struct isp1362_hcd *isp1362_hcd, struct isp1362_ep *ep) + +{ + int index; + struct isp1362_ep_queue *epq; + + DBG(1, "%s: ep %p PTD[%d] $%04x\n", __func__, ep, ep->ptd_index, ep->ptd_offset); + BUG_ON(ep->ptd_offset < 0); + + epq = get_ptd_queue(isp1362_hcd, ep->ptd_offset); + BUG_ON(!epq); + + /* put ep in remove_list for cleanup */ + WARN_ON(!list_empty(&ep->remove_list)); + list_add_tail(&ep->remove_list, &isp1362_hcd->remove_list); + /* let SOF interrupt handle the cleanup */ + isp1362_enable_int(isp1362_hcd, HCuPINT_SOF); + + index = ep->ptd_index; + if (index < 0) + /* ISO queues don't have SKIP registers */ + return; + + DBG(1, "%s: Disabling PTD[%02x] $%04x %08lx|%08x\n", __func__, + index, ep->ptd_offset, epq->skip_map, 1 << index); + + /* prevent further processing of PTD (will be effective after next SOF) */ + epq->skip_map |= 1 << index; + if (epq == &isp1362_hcd->atl_queue) { + DBG(2, "%s: ATLSKIP = %08x -> %08lx\n", __func__, + isp1362_read_reg32(isp1362_hcd, HCATLSKIP), epq->skip_map); + isp1362_write_reg32(isp1362_hcd, HCATLSKIP, epq->skip_map); + if (~epq->skip_map == 0) + isp1362_clr_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_ATL_ACTIVE); + } else if (epq == &isp1362_hcd->intl_queue) { + DBG(2, "%s: INTLSKIP = %08x -> %08lx\n", __func__, + isp1362_read_reg32(isp1362_hcd, HCINTLSKIP), epq->skip_map); + isp1362_write_reg32(isp1362_hcd, HCINTLSKIP, epq->skip_map); + if (~epq->skip_map == 0) + isp1362_clr_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_INTL_ACTIVE); + } +} + +/* + Take done or failed requests out of schedule. Give back + processed urbs. +*/ +static void finish_request(struct isp1362_hcd *isp1362_hcd, struct isp1362_ep *ep, + struct urb *urb, int status) + __releases(isp1362_hcd->lock) + __acquires(isp1362_hcd->lock) +{ + urb->hcpriv = NULL; + ep->error_count = 0; + + if (usb_pipecontrol(urb->pipe)) + ep->nextpid = USB_PID_SETUP; + + URB_DBG("%s: req %d FA %d ep%d%s %s: len %d/%d %s stat %d\n", __func__, + ep->num_req, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + !usb_pipein(urb->pipe) ? "out" : "in", + usb_pipecontrol(urb->pipe) ? "ctrl" : + usb_pipeint(urb->pipe) ? "int" : + usb_pipebulk(urb->pipe) ? "bulk" : + "iso", + urb->actual_length, urb->transfer_buffer_length, + !(urb->transfer_flags & URB_SHORT_NOT_OK) ? + "short_ok" : "", urb->status); + + + usb_hcd_unlink_urb_from_ep(isp1362_hcd_to_hcd(isp1362_hcd), urb); + spin_unlock(&isp1362_hcd->lock); + usb_hcd_giveback_urb(isp1362_hcd_to_hcd(isp1362_hcd), urb, status); + spin_lock(&isp1362_hcd->lock); + + /* take idle endpoints out of the schedule right away */ + if (!list_empty(&ep->hep->urb_list)) + return; + + /* async deschedule */ + if (!list_empty(&ep->schedule)) { + list_del_init(&ep->schedule); + return; + } + + + if (ep->interval) { + /* periodic deschedule */ + DBG(1, "deschedule qh%d/%p branch %d load %d bandwidth %d -> %d\n", ep->interval, + ep, ep->branch, ep->load, + isp1362_hcd->load[ep->branch], + isp1362_hcd->load[ep->branch] - ep->load); + isp1362_hcd->load[ep->branch] -= ep->load; + ep->branch = PERIODIC_SIZE; + } +} + +/* + * Analyze transfer results, handle partial transfers and errors +*/ +static void postproc_ep(struct isp1362_hcd *isp1362_hcd, struct isp1362_ep *ep) +{ + struct urb *urb = get_urb(ep); + struct usb_device *udev; + struct ptd *ptd; + int short_ok; + u16 len; + int urbstat = -EINPROGRESS; + u8 cc; + + DBG(2, "%s: ep %p req %d\n", __func__, ep, ep->num_req); + + udev = urb->dev; + ptd = &ep->ptd; + cc = PTD_GET_CC(ptd); + if (cc == PTD_NOTACCESSED) { + pr_err("%s: req %d PTD %p Untouched by ISP1362\n", __func__, + ep->num_req, ptd); + cc = PTD_DEVNOTRESP; + } + + short_ok = !(urb->transfer_flags & URB_SHORT_NOT_OK); + len = urb->transfer_buffer_length - urb->actual_length; + + /* Data underrun is special. For allowed underrun + we clear the error and continue as normal. For + forbidden underrun we finish the DATA stage + immediately while for control transfer, + we do a STATUS stage. + */ + if (cc == PTD_DATAUNDERRUN) { + if (short_ok) { + DBG(1, "%s: req %d Allowed data underrun short_%sok %d/%d/%d byte\n", + __func__, ep->num_req, short_ok ? "" : "not_", + PTD_GET_COUNT(ptd), ep->maxpacket, len); + cc = PTD_CC_NOERROR; + urbstat = 0; + } else { + DBG(1, "%s: req %d Data Underrun %s nextpid %02x short_%sok %d/%d/%d byte\n", + __func__, ep->num_req, + usb_pipein(urb->pipe) ? "IN" : "OUT", ep->nextpid, + short_ok ? "" : "not_", + PTD_GET_COUNT(ptd), ep->maxpacket, len); + /* save the data underrun error code for later and + * proceed with the status stage + */ + urb->actual_length += PTD_GET_COUNT(ptd); + if (usb_pipecontrol(urb->pipe)) { + ep->nextpid = USB_PID_ACK; + BUG_ON(urb->actual_length > urb->transfer_buffer_length); + + if (urb->status == -EINPROGRESS) + urb->status = cc_to_error[PTD_DATAUNDERRUN]; + } else { + usb_settoggle(udev, ep->epnum, ep->nextpid == USB_PID_OUT, + PTD_GET_TOGGLE(ptd)); + urbstat = cc_to_error[PTD_DATAUNDERRUN]; + } + goto out; + } + } + + if (cc != PTD_CC_NOERROR) { + if (++ep->error_count >= 3 || cc == PTD_CC_STALL || cc == PTD_DATAOVERRUN) { + urbstat = cc_to_error[cc]; + DBG(1, "%s: req %d nextpid %02x, status %d, error %d, error_count %d\n", + __func__, ep->num_req, ep->nextpid, urbstat, cc, + ep->error_count); + } + goto out; + } + + switch (ep->nextpid) { + case USB_PID_OUT: + if (PTD_GET_COUNT(ptd) != ep->length) + pr_err("%s: count=%d len=%d\n", __func__, + PTD_GET_COUNT(ptd), ep->length); + BUG_ON(PTD_GET_COUNT(ptd) != ep->length); + urb->actual_length += ep->length; + BUG_ON(urb->actual_length > urb->transfer_buffer_length); + usb_settoggle(udev, ep->epnum, 1, PTD_GET_TOGGLE(ptd)); + if (urb->actual_length == urb->transfer_buffer_length) { + DBG(3, "%s: req %d xfer complete %d/%d status %d -> 0\n", __func__, + ep->num_req, len, ep->maxpacket, urbstat); + if (usb_pipecontrol(urb->pipe)) { + DBG(3, "%s: req %d %s Wait for ACK\n", __func__, + ep->num_req, + usb_pipein(urb->pipe) ? "IN" : "OUT"); + ep->nextpid = USB_PID_ACK; + } else { + if (len % ep->maxpacket || + !(urb->transfer_flags & URB_ZERO_PACKET)) { + urbstat = 0; + DBG(3, "%s: req %d URB %s status %d count %d/%d/%d\n", + __func__, ep->num_req, usb_pipein(urb->pipe) ? "IN" : "OUT", + urbstat, len, ep->maxpacket, urb->actual_length); + } + } + } + break; + case USB_PID_IN: + len = PTD_GET_COUNT(ptd); + BUG_ON(len > ep->length); + urb->actual_length += len; + BUG_ON(urb->actual_length > urb->transfer_buffer_length); + usb_settoggle(udev, ep->epnum, 0, PTD_GET_TOGGLE(ptd)); + /* if transfer completed or (allowed) data underrun */ + if ((urb->transfer_buffer_length == urb->actual_length) || + len % ep->maxpacket) { + DBG(3, "%s: req %d xfer complete %d/%d status %d -> 0\n", __func__, + ep->num_req, len, ep->maxpacket, urbstat); + if (usb_pipecontrol(urb->pipe)) { + DBG(3, "%s: req %d %s Wait for ACK\n", __func__, + ep->num_req, + usb_pipein(urb->pipe) ? "IN" : "OUT"); + ep->nextpid = USB_PID_ACK; + } else { + urbstat = 0; + DBG(3, "%s: req %d URB %s status %d count %d/%d/%d\n", + __func__, ep->num_req, usb_pipein(urb->pipe) ? "IN" : "OUT", + urbstat, len, ep->maxpacket, urb->actual_length); + } + } + break; + case USB_PID_SETUP: + if (urb->transfer_buffer_length == urb->actual_length) { + ep->nextpid = USB_PID_ACK; + } else if (usb_pipeout(urb->pipe)) { + usb_settoggle(udev, 0, 1, 1); + ep->nextpid = USB_PID_OUT; + } else { + usb_settoggle(udev, 0, 0, 1); + ep->nextpid = USB_PID_IN; + } + break; + case USB_PID_ACK: + DBG(3, "%s: req %d got ACK %d -> 0\n", __func__, ep->num_req, + urbstat); + WARN_ON(urbstat != -EINPROGRESS); + urbstat = 0; + ep->nextpid = 0; + break; + default: + BUG_ON(1); + } + + out: + if (urbstat != -EINPROGRESS) { + DBG(2, "%s: Finishing ep %p req %d urb %p status %d\n", __func__, + ep, ep->num_req, urb, urbstat); + finish_request(isp1362_hcd, ep, urb, urbstat); + } +} + +static void finish_unlinks(struct isp1362_hcd *isp1362_hcd) +{ + struct isp1362_ep *ep; + struct isp1362_ep *tmp; + + list_for_each_entry_safe(ep, tmp, &isp1362_hcd->remove_list, remove_list) { + struct isp1362_ep_queue *epq = + get_ptd_queue(isp1362_hcd, ep->ptd_offset); + int index = ep->ptd_index; + + BUG_ON(epq == NULL); + if (index >= 0) { + DBG(1, "%s: remove PTD[%d] $%04x\n", __func__, index, ep->ptd_offset); + BUG_ON(ep->num_ptds == 0); + release_ptd_buffers(epq, ep); + } + if (!list_empty(&ep->hep->urb_list)) { + struct urb *urb = get_urb(ep); + + DBG(1, "%s: Finishing req %d ep %p from remove_list\n", __func__, + ep->num_req, ep); + finish_request(isp1362_hcd, ep, urb, -ESHUTDOWN); + } + WARN_ON(list_empty(&ep->active)); + if (!list_empty(&ep->active)) { + list_del_init(&ep->active); + DBG(1, "%s: ep %p removed from active list\n", __func__, ep); + } + list_del_init(&ep->remove_list); + DBG(1, "%s: ep %p removed from remove_list\n", __func__, ep); + } + DBG(1, "%s: Done\n", __func__); +} + +static inline void enable_atl_transfers(struct isp1362_hcd *isp1362_hcd, int count) +{ + if (count > 0) { + if (count < isp1362_hcd->atl_queue.ptd_count) + isp1362_write_reg16(isp1362_hcd, HCATLDTC, count); + isp1362_enable_int(isp1362_hcd, HCuPINT_ATL); + isp1362_write_reg32(isp1362_hcd, HCATLSKIP, isp1362_hcd->atl_queue.skip_map); + isp1362_set_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_ATL_ACTIVE); + } else + isp1362_enable_int(isp1362_hcd, HCuPINT_SOF); +} + +static inline void enable_intl_transfers(struct isp1362_hcd *isp1362_hcd) +{ + isp1362_enable_int(isp1362_hcd, HCuPINT_INTL); + isp1362_set_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_INTL_ACTIVE); + isp1362_write_reg32(isp1362_hcd, HCINTLSKIP, isp1362_hcd->intl_queue.skip_map); +} + +static inline void enable_istl_transfers(struct isp1362_hcd *isp1362_hcd, int flip) +{ + isp1362_enable_int(isp1362_hcd, flip ? HCuPINT_ISTL1 : HCuPINT_ISTL0); + isp1362_set_mask16(isp1362_hcd, HCBUFSTAT, flip ? + HCBUFSTAT_ISTL1_FULL : HCBUFSTAT_ISTL0_FULL); +} + +static int submit_req(struct isp1362_hcd *isp1362_hcd, struct urb *urb, + struct isp1362_ep *ep, struct isp1362_ep_queue *epq) +{ + int index = epq->free_ptd; + + prepare_ptd(isp1362_hcd, urb, ep, epq, 0); + index = claim_ptd_buffers(epq, ep, ep->length); + if (index == -ENOMEM) { + DBG(1, "%s: req %d No free %s PTD available: %d, %08lx:%08lx\n", __func__, + ep->num_req, epq->name, ep->num_ptds, epq->buf_map, epq->skip_map); + return index; + } else if (index == -EOVERFLOW) { + DBG(1, "%s: req %d Not enough space for %d byte %s PTD %d %08lx:%08lx\n", + __func__, ep->num_req, ep->length, epq->name, ep->num_ptds, + epq->buf_map, epq->skip_map); + return index; + } else + BUG_ON(index < 0); + list_add_tail(&ep->active, &epq->active); + DBG(1, "%s: ep %p req %d len %d added to active list %p\n", __func__, + ep, ep->num_req, ep->length, &epq->active); + DBG(1, "%s: Submitting %s PTD $%04x for ep %p req %d\n", __func__, epq->name, + ep->ptd_offset, ep, ep->num_req); + isp1362_write_ptd(isp1362_hcd, ep, epq); + __clear_bit(ep->ptd_index, &epq->skip_map); + + return 0; +} + +static void start_atl_transfers(struct isp1362_hcd *isp1362_hcd) +{ + int ptd_count = 0; + struct isp1362_ep_queue *epq = &isp1362_hcd->atl_queue; + struct isp1362_ep *ep; + int defer = 0; + + if (atomic_read(&epq->finishing)) { + DBG(1, "%s: finish_transfers is active for %s\n", __func__, epq->name); + return; + } + + list_for_each_entry(ep, &isp1362_hcd->async, schedule) { + struct urb *urb = get_urb(ep); + int ret; + + if (!list_empty(&ep->active)) { + DBG(2, "%s: Skipping active %s ep %p\n", __func__, epq->name, ep); + continue; + } + + DBG(1, "%s: Processing %s ep %p req %d\n", __func__, epq->name, + ep, ep->num_req); + + ret = submit_req(isp1362_hcd, urb, ep, epq); + if (ret == -ENOMEM) { + defer = 1; + break; + } else if (ret == -EOVERFLOW) { + defer = 1; + continue; + } +#ifdef BUGGY_PXA2XX_UDC_USBTEST + defer = ep->nextpid == USB_PID_SETUP; +#endif + ptd_count++; + } + + /* Avoid starving of endpoints */ + if (isp1362_hcd->async.next != isp1362_hcd->async.prev) { + DBG(2, "%s: Cycling ASYNC schedule %d\n", __func__, ptd_count); + list_move(&isp1362_hcd->async, isp1362_hcd->async.next); + } + if (ptd_count || defer) + enable_atl_transfers(isp1362_hcd, defer ? 0 : ptd_count); + + epq->ptd_count += ptd_count; + if (epq->ptd_count > epq->stat_maxptds) { + epq->stat_maxptds = epq->ptd_count; + DBG(0, "%s: max_ptds: %d\n", __func__, epq->stat_maxptds); + } +} + +static void start_intl_transfers(struct isp1362_hcd *isp1362_hcd) +{ + int ptd_count = 0; + struct isp1362_ep_queue *epq = &isp1362_hcd->intl_queue; + struct isp1362_ep *ep; + + if (atomic_read(&epq->finishing)) { + DBG(1, "%s: finish_transfers is active for %s\n", __func__, epq->name); + return; + } + + list_for_each_entry(ep, &isp1362_hcd->periodic, schedule) { + struct urb *urb = get_urb(ep); + int ret; + + if (!list_empty(&ep->active)) { + DBG(1, "%s: Skipping active %s ep %p\n", __func__, + epq->name, ep); + continue; + } + + DBG(1, "%s: Processing %s ep %p req %d\n", __func__, + epq->name, ep, ep->num_req); + ret = submit_req(isp1362_hcd, urb, ep, epq); + if (ret == -ENOMEM) + break; + else if (ret == -EOVERFLOW) + continue; + ptd_count++; + } + + if (ptd_count) { + static int last_count; + + if (ptd_count != last_count) { + DBG(0, "%s: ptd_count: %d\n", __func__, ptd_count); + last_count = ptd_count; + } + enable_intl_transfers(isp1362_hcd); + } + + epq->ptd_count += ptd_count; + if (epq->ptd_count > epq->stat_maxptds) + epq->stat_maxptds = epq->ptd_count; +} + +static inline int next_ptd(struct isp1362_ep_queue *epq, struct isp1362_ep *ep) +{ + u16 ptd_offset = ep->ptd_offset; + int num_ptds = (ep->length + PTD_HEADER_SIZE + (epq->blk_size - 1)) / epq->blk_size; + + DBG(2, "%s: PTD offset $%04x + %04x => %d * %04x -> $%04x\n", __func__, ptd_offset, + ep->length, num_ptds, epq->blk_size, ptd_offset + num_ptds * epq->blk_size); + + ptd_offset += num_ptds * epq->blk_size; + if (ptd_offset < epq->buf_start + epq->buf_size) + return ptd_offset; + else + return -ENOMEM; +} + +static void start_iso_transfers(struct isp1362_hcd *isp1362_hcd) +{ + int ptd_count = 0; + int flip = isp1362_hcd->istl_flip; + struct isp1362_ep_queue *epq; + int ptd_offset; + struct isp1362_ep *ep; + struct isp1362_ep *tmp; + u16 fno = isp1362_read_reg32(isp1362_hcd, HCFMNUM); + + fill2: + epq = &isp1362_hcd->istl_queue[flip]; + if (atomic_read(&epq->finishing)) { + DBG(1, "%s: finish_transfers is active for %s\n", __func__, epq->name); + return; + } + + if (!list_empty(&epq->active)) + return; + + ptd_offset = epq->buf_start; + list_for_each_entry_safe(ep, tmp, &isp1362_hcd->isoc, schedule) { + struct urb *urb = get_urb(ep); + s16 diff = fno - (u16)urb->start_frame; + + DBG(1, "%s: Processing %s ep %p\n", __func__, epq->name, ep); + + if (diff > urb->number_of_packets) { + /* time frame for this URB has elapsed */ + finish_request(isp1362_hcd, ep, urb, -EOVERFLOW); + continue; + } else if (diff < -1) { + /* URB is not due in this frame or the next one. + * Comparing with '-1' instead of '0' accounts for double + * buffering in the ISP1362 which enables us to queue the PTD + * one frame ahead of time + */ + } else if (diff == -1) { + /* submit PTD's that are due in the next frame */ + prepare_ptd(isp1362_hcd, urb, ep, epq, fno); + if (ptd_offset + PTD_HEADER_SIZE + ep->length > + epq->buf_start + epq->buf_size) { + pr_err("%s: Not enough ISO buffer space for %d byte PTD\n", + __func__, ep->length); + continue; + } + ep->ptd_offset = ptd_offset; + list_add_tail(&ep->active, &epq->active); + + ptd_offset = next_ptd(epq, ep); + if (ptd_offset < 0) { + pr_warning("%s: req %d No more %s PTD buffers available\n", __func__, + ep->num_req, epq->name); + break; + } + } + } + list_for_each_entry(ep, &epq->active, active) { + if (epq->active.next == &ep->active) + ep->ptd.mps |= PTD_LAST_MSK; + isp1362_write_ptd(isp1362_hcd, ep, epq); + ptd_count++; + } + + if (ptd_count) + enable_istl_transfers(isp1362_hcd, flip); + + epq->ptd_count += ptd_count; + if (epq->ptd_count > epq->stat_maxptds) + epq->stat_maxptds = epq->ptd_count; + + /* check, whether the second ISTL buffer may also be filled */ + if (!(isp1362_read_reg16(isp1362_hcd, HCBUFSTAT) & + (flip ? HCBUFSTAT_ISTL0_FULL : HCBUFSTAT_ISTL1_FULL))) { + fno++; + ptd_count = 0; + flip = 1 - flip; + goto fill2; + } +} + +static void finish_transfers(struct isp1362_hcd *isp1362_hcd, unsigned long done_map, + struct isp1362_ep_queue *epq) +{ + struct isp1362_ep *ep; + struct isp1362_ep *tmp; + + if (list_empty(&epq->active)) { + DBG(1, "%s: Nothing to do for %s queue\n", __func__, epq->name); + return; + } + + DBG(1, "%s: Finishing %s transfers %08lx\n", __func__, epq->name, done_map); + + atomic_inc(&epq->finishing); + list_for_each_entry_safe(ep, tmp, &epq->active, active) { + int index = ep->ptd_index; + + DBG(1, "%s: Checking %s PTD[%02x] $%04x\n", __func__, epq->name, + index, ep->ptd_offset); + + BUG_ON(index < 0); + if (__test_and_clear_bit(index, &done_map)) { + isp1362_read_ptd(isp1362_hcd, ep, epq); + epq->free_ptd = index; + BUG_ON(ep->num_ptds == 0); + release_ptd_buffers(epq, ep); + + DBG(1, "%s: ep %p req %d removed from active list\n", __func__, + ep, ep->num_req); + if (!list_empty(&ep->remove_list)) { + list_del_init(&ep->remove_list); + DBG(1, "%s: ep %p removed from remove list\n", __func__, ep); + } + DBG(1, "%s: Postprocessing %s ep %p req %d\n", __func__, epq->name, + ep, ep->num_req); + postproc_ep(isp1362_hcd, ep); + } + if (!done_map) + break; + } + if (done_map) + pr_warning("%s: done_map not clear: %08lx:%08lx\n", __func__, done_map, + epq->skip_map); + atomic_dec(&epq->finishing); +} + +static void finish_iso_transfers(struct isp1362_hcd *isp1362_hcd, struct isp1362_ep_queue *epq) +{ + struct isp1362_ep *ep; + struct isp1362_ep *tmp; + + if (list_empty(&epq->active)) { + DBG(1, "%s: Nothing to do for %s queue\n", __func__, epq->name); + return; + } + + DBG(1, "%s: Finishing %s transfers\n", __func__, epq->name); + + atomic_inc(&epq->finishing); + list_for_each_entry_safe(ep, tmp, &epq->active, active) { + DBG(1, "%s: Checking PTD $%04x\n", __func__, ep->ptd_offset); + + isp1362_read_ptd(isp1362_hcd, ep, epq); + DBG(1, "%s: Postprocessing %s ep %p\n", __func__, epq->name, ep); + postproc_ep(isp1362_hcd, ep); + } + WARN_ON(epq->blk_size != 0); + atomic_dec(&epq->finishing); +} + +static irqreturn_t isp1362_irq(struct usb_hcd *hcd) +{ + int handled = 0; + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + u16 irqstat; + u16 svc_mask; + + spin_lock(&isp1362_hcd->lock); + + BUG_ON(isp1362_hcd->irq_active++); + + isp1362_write_reg16(isp1362_hcd, HCuPINTENB, 0); + + irqstat = isp1362_read_reg16(isp1362_hcd, HCuPINT); + DBG(3, "%s: got IRQ %04x:%04x\n", __func__, irqstat, isp1362_hcd->irqenb); + + /* only handle interrupts that are currently enabled */ + irqstat &= isp1362_hcd->irqenb; + isp1362_write_reg16(isp1362_hcd, HCuPINT, irqstat); + svc_mask = irqstat; + + if (irqstat & HCuPINT_SOF) { + isp1362_hcd->irqenb &= ~HCuPINT_SOF; + isp1362_hcd->irq_stat[ISP1362_INT_SOF]++; + handled = 1; + svc_mask &= ~HCuPINT_SOF; + DBG(3, "%s: SOF\n", __func__); + isp1362_hcd->fmindex = isp1362_read_reg32(isp1362_hcd, HCFMNUM); + if (!list_empty(&isp1362_hcd->remove_list)) + finish_unlinks(isp1362_hcd); + if (!list_empty(&isp1362_hcd->async) && !(irqstat & HCuPINT_ATL)) { + if (list_empty(&isp1362_hcd->atl_queue.active)) { + start_atl_transfers(isp1362_hcd); + } else { + isp1362_enable_int(isp1362_hcd, HCuPINT_ATL); + isp1362_write_reg32(isp1362_hcd, HCATLSKIP, + isp1362_hcd->atl_queue.skip_map); + isp1362_set_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_ATL_ACTIVE); + } + } + } + + if (irqstat & HCuPINT_ISTL0) { + isp1362_hcd->irq_stat[ISP1362_INT_ISTL0]++; + handled = 1; + svc_mask &= ~HCuPINT_ISTL0; + isp1362_clr_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_ISTL0_FULL); + DBG(1, "%s: ISTL0\n", __func__); + WARN_ON((int)!!isp1362_hcd->istl_flip); + WARN_ON(isp1362_read_reg16(isp1362_hcd, HCBUFSTAT) & + HCBUFSTAT_ISTL0_ACTIVE); + WARN_ON(!(isp1362_read_reg16(isp1362_hcd, HCBUFSTAT) & + HCBUFSTAT_ISTL0_DONE)); + isp1362_hcd->irqenb &= ~HCuPINT_ISTL0; + } + + if (irqstat & HCuPINT_ISTL1) { + isp1362_hcd->irq_stat[ISP1362_INT_ISTL1]++; + handled = 1; + svc_mask &= ~HCuPINT_ISTL1; + isp1362_clr_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_ISTL1_FULL); + DBG(1, "%s: ISTL1\n", __func__); + WARN_ON(!(int)isp1362_hcd->istl_flip); + WARN_ON(isp1362_read_reg16(isp1362_hcd, HCBUFSTAT) & + HCBUFSTAT_ISTL1_ACTIVE); + WARN_ON(!(isp1362_read_reg16(isp1362_hcd, HCBUFSTAT) & + HCBUFSTAT_ISTL1_DONE)); + isp1362_hcd->irqenb &= ~HCuPINT_ISTL1; + } + + if (irqstat & (HCuPINT_ISTL0 | HCuPINT_ISTL1)) { + WARN_ON((irqstat & (HCuPINT_ISTL0 | HCuPINT_ISTL1)) == + (HCuPINT_ISTL0 | HCuPINT_ISTL1)); + finish_iso_transfers(isp1362_hcd, + &isp1362_hcd->istl_queue[isp1362_hcd->istl_flip]); + start_iso_transfers(isp1362_hcd); + isp1362_hcd->istl_flip = 1 - isp1362_hcd->istl_flip; + } + + if (irqstat & HCuPINT_INTL) { + u32 done_map = isp1362_read_reg32(isp1362_hcd, HCINTLDONE); + u32 skip_map = isp1362_read_reg32(isp1362_hcd, HCINTLSKIP); + isp1362_hcd->irq_stat[ISP1362_INT_INTL]++; + + DBG(2, "%s: INTL\n", __func__); + + svc_mask &= ~HCuPINT_INTL; + + isp1362_write_reg32(isp1362_hcd, HCINTLSKIP, skip_map | done_map); + if (~(done_map | skip_map) == 0) + /* All PTDs are finished, disable INTL processing entirely */ + isp1362_clr_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_INTL_ACTIVE); + + handled = 1; + WARN_ON(!done_map); + if (done_map) { + DBG(3, "%s: INTL done_map %08x\n", __func__, done_map); + finish_transfers(isp1362_hcd, done_map, &isp1362_hcd->intl_queue); + start_intl_transfers(isp1362_hcd); + } + } + + if (irqstat & HCuPINT_ATL) { + u32 done_map = isp1362_read_reg32(isp1362_hcd, HCATLDONE); + u32 skip_map = isp1362_read_reg32(isp1362_hcd, HCATLSKIP); + isp1362_hcd->irq_stat[ISP1362_INT_ATL]++; + + DBG(2, "%s: ATL\n", __func__); + + svc_mask &= ~HCuPINT_ATL; + + isp1362_write_reg32(isp1362_hcd, HCATLSKIP, skip_map | done_map); + if (~(done_map | skip_map) == 0) + isp1362_clr_mask16(isp1362_hcd, HCBUFSTAT, HCBUFSTAT_ATL_ACTIVE); + if (done_map) { + DBG(3, "%s: ATL done_map %08x\n", __func__, done_map); + finish_transfers(isp1362_hcd, done_map, &isp1362_hcd->atl_queue); + start_atl_transfers(isp1362_hcd); + } + handled = 1; + } + + if (irqstat & HCuPINT_OPR) { + u32 intstat = isp1362_read_reg32(isp1362_hcd, HCINTSTAT); + isp1362_hcd->irq_stat[ISP1362_INT_OPR]++; + + svc_mask &= ~HCuPINT_OPR; + DBG(2, "%s: OPR %08x:%08x\n", __func__, intstat, isp1362_hcd->intenb); + intstat &= isp1362_hcd->intenb; + if (intstat & OHCI_INTR_UE) { + pr_err("Unrecoverable error\n"); + /* FIXME: do here reset or cleanup or whatever */ + } + if (intstat & OHCI_INTR_RHSC) { + isp1362_hcd->rhstatus = isp1362_read_reg32(isp1362_hcd, HCRHSTATUS); + isp1362_hcd->rhport[0] = isp1362_read_reg32(isp1362_hcd, HCRHPORT1); + isp1362_hcd->rhport[1] = isp1362_read_reg32(isp1362_hcd, HCRHPORT2); + } + if (intstat & OHCI_INTR_RD) { + pr_info("%s: RESUME DETECTED\n", __func__); + isp1362_show_reg(isp1362_hcd, HCCONTROL); + usb_hcd_resume_root_hub(hcd); + } + isp1362_write_reg32(isp1362_hcd, HCINTSTAT, intstat); + irqstat &= ~HCuPINT_OPR; + handled = 1; + } + + if (irqstat & HCuPINT_SUSP) { + isp1362_hcd->irq_stat[ISP1362_INT_SUSP]++; + handled = 1; + svc_mask &= ~HCuPINT_SUSP; + + pr_info("%s: SUSPEND IRQ\n", __func__); + } + + if (irqstat & HCuPINT_CLKRDY) { + isp1362_hcd->irq_stat[ISP1362_INT_CLKRDY]++; + handled = 1; + isp1362_hcd->irqenb &= ~HCuPINT_CLKRDY; + svc_mask &= ~HCuPINT_CLKRDY; + pr_info("%s: CLKRDY IRQ\n", __func__); + } + + if (svc_mask) + pr_err("%s: Unserviced interrupt(s) %04x\n", __func__, svc_mask); + + isp1362_write_reg16(isp1362_hcd, HCuPINTENB, isp1362_hcd->irqenb); + isp1362_hcd->irq_active--; + spin_unlock(&isp1362_hcd->lock); + + return IRQ_RETVAL(handled); +} + +/*-------------------------------------------------------------------------*/ + +#define MAX_PERIODIC_LOAD 900 /* out of 1000 usec */ +static int balance(struct isp1362_hcd *isp1362_hcd, u16 interval, u16 load) +{ + int i, branch = -ENOSPC; + + /* search for the least loaded schedule branch of that interval + * which has enough bandwidth left unreserved. + */ + for (i = 0; i < interval; i++) { + if (branch < 0 || isp1362_hcd->load[branch] > isp1362_hcd->load[i]) { + int j; + + for (j = i; j < PERIODIC_SIZE; j += interval) { + if ((isp1362_hcd->load[j] + load) > MAX_PERIODIC_LOAD) { + pr_err("%s: new load %d load[%02x] %d max %d\n", __func__, + load, j, isp1362_hcd->load[j], MAX_PERIODIC_LOAD); + break; + } + } + if (j < PERIODIC_SIZE) + continue; + branch = i; + } + } + return branch; +} + +/* NB! ALL the code above this point runs with isp1362_hcd->lock + held, irqs off +*/ + +/*-------------------------------------------------------------------------*/ + +static int isp1362_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + struct usb_device *udev = urb->dev; + unsigned int pipe = urb->pipe; + int is_out = !usb_pipein(pipe); + int type = usb_pipetype(pipe); + int epnum = usb_pipeendpoint(pipe); + struct usb_host_endpoint *hep = urb->ep; + struct isp1362_ep *ep = NULL; + unsigned long flags; + int retval = 0; + + DBG(3, "%s: urb %p\n", __func__, urb); + + if (type == PIPE_ISOCHRONOUS) { + pr_err("Isochronous transfers not supported\n"); + return -ENOSPC; + } + + URB_DBG("%s: FA %d ep%d%s %s: len %d %s%s\n", __func__, + usb_pipedevice(pipe), epnum, + is_out ? "out" : "in", + usb_pipecontrol(pipe) ? "ctrl" : + usb_pipeint(pipe) ? "int" : + usb_pipebulk(pipe) ? "bulk" : + "iso", + urb->transfer_buffer_length, + (urb->transfer_flags & URB_ZERO_PACKET) ? "ZERO_PACKET " : "", + !(urb->transfer_flags & URB_SHORT_NOT_OK) ? + "short_ok" : ""); + + /* avoid all allocations within spinlocks: request or endpoint */ + if (!hep->hcpriv) { + ep = kzalloc(sizeof *ep, mem_flags); + if (!ep) + return -ENOMEM; + } + spin_lock_irqsave(&isp1362_hcd->lock, flags); + + /* don't submit to a dead or disabled port */ + if (!((isp1362_hcd->rhport[0] | isp1362_hcd->rhport[1]) & + USB_PORT_STAT_ENABLE) || + !HC_IS_RUNNING(hcd->state)) { + kfree(ep); + retval = -ENODEV; + goto fail_not_linked; + } + + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) { + kfree(ep); + goto fail_not_linked; + } + + if (hep->hcpriv) { + ep = hep->hcpriv; + } else { + INIT_LIST_HEAD(&ep->schedule); + INIT_LIST_HEAD(&ep->active); + INIT_LIST_HEAD(&ep->remove_list); + ep->udev = usb_get_dev(udev); + ep->hep = hep; + ep->epnum = epnum; + ep->maxpacket = usb_maxpacket(udev, urb->pipe, is_out); + ep->ptd_offset = -EINVAL; + ep->ptd_index = -EINVAL; + usb_settoggle(udev, epnum, is_out, 0); + + if (type == PIPE_CONTROL) + ep->nextpid = USB_PID_SETUP; + else if (is_out) + ep->nextpid = USB_PID_OUT; + else + ep->nextpid = USB_PID_IN; + + switch (type) { + case PIPE_ISOCHRONOUS: + case PIPE_INTERRUPT: + if (urb->interval > PERIODIC_SIZE) + urb->interval = PERIODIC_SIZE; + ep->interval = urb->interval; + ep->branch = PERIODIC_SIZE; + ep->load = usb_calc_bus_time(udev->speed, !is_out, + (type == PIPE_ISOCHRONOUS), + usb_maxpacket(udev, pipe, is_out)) / 1000; + break; + } + hep->hcpriv = ep; + } + ep->num_req = isp1362_hcd->req_serial++; + + /* maybe put endpoint into schedule */ + switch (type) { + case PIPE_CONTROL: + case PIPE_BULK: + if (list_empty(&ep->schedule)) { + DBG(1, "%s: Adding ep %p req %d to async schedule\n", + __func__, ep, ep->num_req); + list_add_tail(&ep->schedule, &isp1362_hcd->async); + } + break; + case PIPE_ISOCHRONOUS: + case PIPE_INTERRUPT: + urb->interval = ep->interval; + + /* urb submitted for already existing EP */ + if (ep->branch < PERIODIC_SIZE) + break; + + retval = balance(isp1362_hcd, ep->interval, ep->load); + if (retval < 0) { + pr_err("%s: balance returned %d\n", __func__, retval); + goto fail; + } + ep->branch = retval; + retval = 0; + isp1362_hcd->fmindex = isp1362_read_reg32(isp1362_hcd, HCFMNUM); + DBG(1, "%s: Current frame %04x branch %02x start_frame %04x(%04x)\n", + __func__, isp1362_hcd->fmindex, ep->branch, + ((isp1362_hcd->fmindex + PERIODIC_SIZE - 1) & + ~(PERIODIC_SIZE - 1)) + ep->branch, + (isp1362_hcd->fmindex & (PERIODIC_SIZE - 1)) + ep->branch); + + if (list_empty(&ep->schedule)) { + if (type == PIPE_ISOCHRONOUS) { + u16 frame = isp1362_hcd->fmindex; + + frame += max_t(u16, 8, ep->interval); + frame &= ~(ep->interval - 1); + frame |= ep->branch; + if (frame_before(frame, isp1362_hcd->fmindex)) + frame += ep->interval; + urb->start_frame = frame; + + DBG(1, "%s: Adding ep %p to isoc schedule\n", __func__, ep); + list_add_tail(&ep->schedule, &isp1362_hcd->isoc); + } else { + DBG(1, "%s: Adding ep %p to periodic schedule\n", __func__, ep); + list_add_tail(&ep->schedule, &isp1362_hcd->periodic); + } + } else + DBG(1, "%s: ep %p already scheduled\n", __func__, ep); + + DBG(2, "%s: load %d bandwidth %d -> %d\n", __func__, + ep->load / ep->interval, isp1362_hcd->load[ep->branch], + isp1362_hcd->load[ep->branch] + ep->load); + isp1362_hcd->load[ep->branch] += ep->load; + } + + urb->hcpriv = hep; + ALIGNSTAT(isp1362_hcd, urb->transfer_buffer); + + switch (type) { + case PIPE_CONTROL: + case PIPE_BULK: + start_atl_transfers(isp1362_hcd); + break; + case PIPE_INTERRUPT: + start_intl_transfers(isp1362_hcd); + break; + case PIPE_ISOCHRONOUS: + start_iso_transfers(isp1362_hcd); + break; + default: + BUG(); + } + fail: + if (retval) + usb_hcd_unlink_urb_from_ep(hcd, urb); + + + fail_not_linked: + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + if (retval) + DBG(0, "%s: urb %p failed with %d\n", __func__, urb, retval); + return retval; +} + +static int isp1362_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + struct usb_host_endpoint *hep; + unsigned long flags; + struct isp1362_ep *ep; + int retval = 0; + + DBG(3, "%s: urb %p\n", __func__, urb); + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + retval = usb_hcd_check_unlink_urb(hcd, urb, status); + if (retval) + goto done; + + hep = urb->hcpriv; + + if (!hep) { + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + return -EIDRM; + } + + ep = hep->hcpriv; + if (ep) { + /* In front of queue? */ + if (ep->hep->urb_list.next == &urb->urb_list) { + if (!list_empty(&ep->active)) { + DBG(1, "%s: urb %p ep %p req %d active PTD[%d] $%04x\n", __func__, + urb, ep, ep->num_req, ep->ptd_index, ep->ptd_offset); + /* disable processing and queue PTD for removal */ + remove_ptd(isp1362_hcd, ep); + urb = NULL; + } + } + if (urb) { + DBG(1, "%s: Finishing ep %p req %d\n", __func__, ep, + ep->num_req); + finish_request(isp1362_hcd, ep, urb, status); + } else + DBG(1, "%s: urb %p active; wait4irq\n", __func__, urb); + } else { + pr_warning("%s: No EP in URB %p\n", __func__, urb); + retval = -EINVAL; + } +done: + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + DBG(3, "%s: exit\n", __func__); + + return retval; +} + +static void isp1362_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) +{ + struct isp1362_ep *ep = hep->hcpriv; + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + unsigned long flags; + + DBG(1, "%s: ep %p\n", __func__, ep); + if (!ep) + return; + spin_lock_irqsave(&isp1362_hcd->lock, flags); + if (!list_empty(&hep->urb_list)) { + if (!list_empty(&ep->active) && list_empty(&ep->remove_list)) { + DBG(1, "%s: Removing ep %p req %d PTD[%d] $%04x\n", __func__, + ep, ep->num_req, ep->ptd_index, ep->ptd_offset); + remove_ptd(isp1362_hcd, ep); + pr_info("%s: Waiting for Interrupt to clean up\n", __func__); + } + } + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + /* Wait for interrupt to clear out active list */ + while (!list_empty(&ep->active)) + msleep(1); + + DBG(1, "%s: Freeing EP %p\n", __func__, ep); + + usb_put_dev(ep->udev); + kfree(ep); + hep->hcpriv = NULL; +} + +static int isp1362_get_frame(struct usb_hcd *hcd) +{ + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + u32 fmnum; + unsigned long flags; + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + fmnum = isp1362_read_reg32(isp1362_hcd, HCFMNUM); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + return (int)fmnum; +} + +/*-------------------------------------------------------------------------*/ + +/* Adapted from ohci-hub.c */ +static int isp1362_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + int ports, i, changed = 0; + unsigned long flags; + + if (!HC_IS_RUNNING(hcd->state)) + return -ESHUTDOWN; + + /* Report no status change now, if we are scheduled to be + called later */ + if (timer_pending(&hcd->rh_timer)) + return 0; + + ports = isp1362_hcd->rhdesca & RH_A_NDP; + BUG_ON(ports > 2); + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + /* init status */ + if (isp1362_hcd->rhstatus & (RH_HS_LPSC | RH_HS_OCIC)) + buf[0] = changed = 1; + else + buf[0] = 0; + + for (i = 0; i < ports; i++) { + u32 status = isp1362_hcd->rhport[i]; + + if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC | + RH_PS_OCIC | RH_PS_PRSC)) { + changed = 1; + buf[0] |= 1 << (i + 1); + continue; + } + + if (!(status & RH_PS_CCS)) + continue; + } + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + return changed; +} + +static void isp1362_hub_descriptor(struct isp1362_hcd *isp1362_hcd, + struct usb_hub_descriptor *desc) +{ + u32 reg = isp1362_hcd->rhdesca; + + DBG(3, "%s: enter\n", __func__); + + desc->bDescriptorType = 0x29; + desc->bDescLength = 9; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = reg & 0x3; + /* Power switching, device type, overcurrent. */ + desc->wHubCharacteristics = cpu_to_le16((reg >> 8) & 0x1f); + DBG(0, "%s: hubcharacteristics = %02x\n", __func__, cpu_to_le16((reg >> 8) & 0x1f)); + desc->bPwrOn2PwrGood = (reg >> 24) & 0xff; + /* ports removable, and legacy PortPwrCtrlMask */ + desc->u.hs.DeviceRemovable[0] = desc->bNbrPorts == 1 ? 1 << 1 : 3 << 1; + desc->u.hs.DeviceRemovable[1] = ~0; + + DBG(3, "%s: exit\n", __func__); +} + +/* Adapted from ohci-hub.c */ +static int isp1362_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + int retval = 0; + unsigned long flags; + unsigned long t1; + int ports = isp1362_hcd->rhdesca & RH_A_NDP; + u32 tmp = 0; + + switch (typeReq) { + case ClearHubFeature: + DBG(0, "ClearHubFeature: "); + switch (wValue) { + case C_HUB_OVER_CURRENT: + _DBG(0, "C_HUB_OVER_CURRENT\n"); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_reg32(isp1362_hcd, HCRHSTATUS, RH_HS_OCIC); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + case C_HUB_LOCAL_POWER: + _DBG(0, "C_HUB_LOCAL_POWER\n"); + break; + default: + goto error; + } + break; + case SetHubFeature: + DBG(0, "SetHubFeature: "); + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + _DBG(0, "C_HUB_OVER_CURRENT or C_HUB_LOCAL_POWER\n"); + break; + default: + goto error; + } + break; + case GetHubDescriptor: + DBG(0, "GetHubDescriptor\n"); + isp1362_hub_descriptor(isp1362_hcd, (struct usb_hub_descriptor *)buf); + break; + case GetHubStatus: + DBG(0, "GetHubStatus\n"); + put_unaligned(cpu_to_le32(0), (__le32 *) buf); + break; + case GetPortStatus: +#ifndef VERBOSE + DBG(0, "GetPortStatus\n"); +#endif + if (!wIndex || wIndex > ports) + goto error; + tmp = isp1362_hcd->rhport[--wIndex]; + put_unaligned(cpu_to_le32(tmp), (__le32 *) buf); + break; + case ClearPortFeature: + DBG(0, "ClearPortFeature: "); + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + _DBG(0, "USB_PORT_FEAT_ENABLE\n"); + tmp = RH_PS_CCS; + break; + case USB_PORT_FEAT_C_ENABLE: + _DBG(0, "USB_PORT_FEAT_C_ENABLE\n"); + tmp = RH_PS_PESC; + break; + case USB_PORT_FEAT_SUSPEND: + _DBG(0, "USB_PORT_FEAT_SUSPEND\n"); + tmp = RH_PS_POCI; + break; + case USB_PORT_FEAT_C_SUSPEND: + _DBG(0, "USB_PORT_FEAT_C_SUSPEND\n"); + tmp = RH_PS_PSSC; + break; + case USB_PORT_FEAT_POWER: + _DBG(0, "USB_PORT_FEAT_POWER\n"); + tmp = RH_PS_LSDA; + + break; + case USB_PORT_FEAT_C_CONNECTION: + _DBG(0, "USB_PORT_FEAT_C_CONNECTION\n"); + tmp = RH_PS_CSC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + _DBG(0, "USB_PORT_FEAT_C_OVER_CURRENT\n"); + tmp = RH_PS_OCIC; + break; + case USB_PORT_FEAT_C_RESET: + _DBG(0, "USB_PORT_FEAT_C_RESET\n"); + tmp = RH_PS_PRSC; + break; + default: + goto error; + } + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_reg32(isp1362_hcd, HCRHPORT1 + wIndex, tmp); + isp1362_hcd->rhport[wIndex] = + isp1362_read_reg32(isp1362_hcd, HCRHPORT1 + wIndex); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + break; + case SetPortFeature: + DBG(0, "SetPortFeature: "); + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + _DBG(0, "USB_PORT_FEAT_SUSPEND\n"); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_reg32(isp1362_hcd, HCRHPORT1 + wIndex, RH_PS_PSS); + isp1362_hcd->rhport[wIndex] = + isp1362_read_reg32(isp1362_hcd, HCRHPORT1 + wIndex); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + break; + case USB_PORT_FEAT_POWER: + _DBG(0, "USB_PORT_FEAT_POWER\n"); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_reg32(isp1362_hcd, HCRHPORT1 + wIndex, RH_PS_PPS); + isp1362_hcd->rhport[wIndex] = + isp1362_read_reg32(isp1362_hcd, HCRHPORT1 + wIndex); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + break; + case USB_PORT_FEAT_RESET: + _DBG(0, "USB_PORT_FEAT_RESET\n"); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + + t1 = jiffies + msecs_to_jiffies(USB_RESET_WIDTH); + while (time_before(jiffies, t1)) { + /* spin until any current reset finishes */ + for (;;) { + tmp = isp1362_read_reg32(isp1362_hcd, HCRHPORT1 + wIndex); + if (!(tmp & RH_PS_PRS)) + break; + udelay(500); + } + if (!(tmp & RH_PS_CCS)) + break; + /* Reset lasts 10ms (claims datasheet) */ + isp1362_write_reg32(isp1362_hcd, HCRHPORT1 + wIndex, (RH_PS_PRS)); + + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + msleep(10); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + } + + isp1362_hcd->rhport[wIndex] = isp1362_read_reg32(isp1362_hcd, + HCRHPORT1 + wIndex); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + break; + default: + goto error; + } + break; + + default: + error: + /* "protocol stall" on error */ + _DBG(0, "PROTOCOL STALL\n"); + retval = -EPIPE; + } + + return retval; +} + +#ifdef CONFIG_PM +static int isp1362_bus_suspend(struct usb_hcd *hcd) +{ + int status = 0; + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + unsigned long flags; + + if (time_before(jiffies, isp1362_hcd->next_statechange)) + msleep(5); + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + + isp1362_hcd->hc_control = isp1362_read_reg32(isp1362_hcd, HCCONTROL); + switch (isp1362_hcd->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_RESUME: + DBG(0, "%s: resume/suspend?\n", __func__); + isp1362_hcd->hc_control &= ~OHCI_CTRL_HCFS; + isp1362_hcd->hc_control |= OHCI_USB_RESET; + isp1362_write_reg32(isp1362_hcd, HCCONTROL, isp1362_hcd->hc_control); + /* FALL THROUGH */ + case OHCI_USB_RESET: + status = -EBUSY; + pr_warning("%s: needs reinit!\n", __func__); + goto done; + case OHCI_USB_SUSPEND: + pr_warning("%s: already suspended?\n", __func__); + goto done; + } + DBG(0, "%s: suspend root hub\n", __func__); + + /* First stop any processing */ + hcd->state = HC_STATE_QUIESCING; + if (!list_empty(&isp1362_hcd->atl_queue.active) || + !list_empty(&isp1362_hcd->intl_queue.active) || + !list_empty(&isp1362_hcd->istl_queue[0] .active) || + !list_empty(&isp1362_hcd->istl_queue[1] .active)) { + int limit; + + isp1362_write_reg32(isp1362_hcd, HCATLSKIP, ~0); + isp1362_write_reg32(isp1362_hcd, HCINTLSKIP, ~0); + isp1362_write_reg16(isp1362_hcd, HCBUFSTAT, 0); + isp1362_write_reg16(isp1362_hcd, HCuPINTENB, 0); + isp1362_write_reg32(isp1362_hcd, HCINTSTAT, OHCI_INTR_SF); + + DBG(0, "%s: stopping schedules ...\n", __func__); + limit = 2000; + while (limit > 0) { + udelay(250); + limit -= 250; + if (isp1362_read_reg32(isp1362_hcd, HCINTSTAT) & OHCI_INTR_SF) + break; + } + mdelay(7); + if (isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_ATL) { + u32 done_map = isp1362_read_reg32(isp1362_hcd, HCATLDONE); + finish_transfers(isp1362_hcd, done_map, &isp1362_hcd->atl_queue); + } + if (isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_INTL) { + u32 done_map = isp1362_read_reg32(isp1362_hcd, HCINTLDONE); + finish_transfers(isp1362_hcd, done_map, &isp1362_hcd->intl_queue); + } + if (isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_ISTL0) + finish_iso_transfers(isp1362_hcd, &isp1362_hcd->istl_queue[0]); + if (isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_ISTL1) + finish_iso_transfers(isp1362_hcd, &isp1362_hcd->istl_queue[1]); + } + DBG(0, "%s: HCINTSTAT: %08x\n", __func__, + isp1362_read_reg32(isp1362_hcd, HCINTSTAT)); + isp1362_write_reg32(isp1362_hcd, HCINTSTAT, + isp1362_read_reg32(isp1362_hcd, HCINTSTAT)); + + /* Suspend hub */ + isp1362_hcd->hc_control = OHCI_USB_SUSPEND; + isp1362_show_reg(isp1362_hcd, HCCONTROL); + isp1362_write_reg32(isp1362_hcd, HCCONTROL, isp1362_hcd->hc_control); + isp1362_show_reg(isp1362_hcd, HCCONTROL); + +#if 1 + isp1362_hcd->hc_control = isp1362_read_reg32(isp1362_hcd, HCCONTROL); + if ((isp1362_hcd->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_SUSPEND) { + pr_err("%s: controller won't suspend %08x\n", __func__, + isp1362_hcd->hc_control); + status = -EBUSY; + } else +#endif + { + /* no resumes until devices finish suspending */ + isp1362_hcd->next_statechange = jiffies + msecs_to_jiffies(5); + } +done: + if (status == 0) { + hcd->state = HC_STATE_SUSPENDED; + DBG(0, "%s: HCD suspended: %08x\n", __func__, + isp1362_read_reg32(isp1362_hcd, HCCONTROL)); + } + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + return status; +} + +static int isp1362_bus_resume(struct usb_hcd *hcd) +{ + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + u32 port; + unsigned long flags; + int status = -EINPROGRESS; + + if (time_before(jiffies, isp1362_hcd->next_statechange)) + msleep(5); + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_hcd->hc_control = isp1362_read_reg32(isp1362_hcd, HCCONTROL); + pr_info("%s: HCCONTROL: %08x\n", __func__, isp1362_hcd->hc_control); + if (hcd->state == HC_STATE_RESUMING) { + pr_warning("%s: duplicate resume\n", __func__); + status = 0; + } else + switch (isp1362_hcd->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_SUSPEND: + DBG(0, "%s: resume root hub\n", __func__); + isp1362_hcd->hc_control &= ~OHCI_CTRL_HCFS; + isp1362_hcd->hc_control |= OHCI_USB_RESUME; + isp1362_write_reg32(isp1362_hcd, HCCONTROL, isp1362_hcd->hc_control); + break; + case OHCI_USB_RESUME: + /* HCFS changes sometime after INTR_RD */ + DBG(0, "%s: remote wakeup\n", __func__); + break; + case OHCI_USB_OPER: + DBG(0, "%s: odd resume\n", __func__); + status = 0; + hcd->self.root_hub->dev.power.power_state = PMSG_ON; + break; + default: /* RESET, we lost power */ + DBG(0, "%s: root hub hardware reset\n", __func__); + status = -EBUSY; + } + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + if (status == -EBUSY) { + DBG(0, "%s: Restarting HC\n", __func__); + isp1362_hc_stop(hcd); + return isp1362_hc_start(hcd); + } + if (status != -EINPROGRESS) + return status; + spin_lock_irqsave(&isp1362_hcd->lock, flags); + port = isp1362_read_reg32(isp1362_hcd, HCRHDESCA) & RH_A_NDP; + while (port--) { + u32 stat = isp1362_read_reg32(isp1362_hcd, HCRHPORT1 + port); + + /* force global, not selective, resume */ + if (!(stat & RH_PS_PSS)) { + DBG(0, "%s: Not Resuming RH port %d\n", __func__, port); + continue; + } + DBG(0, "%s: Resuming RH port %d\n", __func__, port); + isp1362_write_reg32(isp1362_hcd, HCRHPORT1 + port, RH_PS_POCI); + } + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + /* Some controllers (lucent) need extra-long delays */ + hcd->state = HC_STATE_RESUMING; + mdelay(20 /* usb 11.5.1.10 */ + 15); + + isp1362_hcd->hc_control = OHCI_USB_OPER; + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_show_reg(isp1362_hcd, HCCONTROL); + isp1362_write_reg32(isp1362_hcd, HCCONTROL, isp1362_hcd->hc_control); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + /* TRSMRCY */ + msleep(10); + + /* keep it alive for ~5x suspend + resume costs */ + isp1362_hcd->next_statechange = jiffies + msecs_to_jiffies(250); + + hcd->self.root_hub->dev.power.power_state = PMSG_ON; + hcd->state = HC_STATE_RUNNING; + return 0; +} +#else +#define isp1362_bus_suspend NULL +#define isp1362_bus_resume NULL +#endif + +/*-------------------------------------------------------------------------*/ + +#ifdef STUB_DEBUG_FILE + +static inline void create_debug_file(struct isp1362_hcd *isp1362_hcd) +{ +} +static inline void remove_debug_file(struct isp1362_hcd *isp1362_hcd) +{ +} + +#else + +#include +#include + +static void dump_irq(struct seq_file *s, char *label, u16 mask) +{ + seq_printf(s, "%-15s %04x%s%s%s%s%s%s\n", label, mask, + mask & HCuPINT_CLKRDY ? " clkrdy" : "", + mask & HCuPINT_SUSP ? " susp" : "", + mask & HCuPINT_OPR ? " opr" : "", + mask & HCuPINT_EOT ? " eot" : "", + mask & HCuPINT_ATL ? " atl" : "", + mask & HCuPINT_SOF ? " sof" : ""); +} + +static void dump_int(struct seq_file *s, char *label, u32 mask) +{ + seq_printf(s, "%-15s %08x%s%s%s%s%s%s%s\n", label, mask, + mask & OHCI_INTR_MIE ? " MIE" : "", + mask & OHCI_INTR_RHSC ? " rhsc" : "", + mask & OHCI_INTR_FNO ? " fno" : "", + mask & OHCI_INTR_UE ? " ue" : "", + mask & OHCI_INTR_RD ? " rd" : "", + mask & OHCI_INTR_SF ? " sof" : "", + mask & OHCI_INTR_SO ? " so" : ""); +} + +static void dump_ctrl(struct seq_file *s, char *label, u32 mask) +{ + seq_printf(s, "%-15s %08x%s%s%s\n", label, mask, + mask & OHCI_CTRL_RWC ? " rwc" : "", + mask & OHCI_CTRL_RWE ? " rwe" : "", + ({ + char *hcfs; + switch (mask & OHCI_CTRL_HCFS) { + case OHCI_USB_OPER: + hcfs = " oper"; + break; + case OHCI_USB_RESET: + hcfs = " reset"; + break; + case OHCI_USB_RESUME: + hcfs = " resume"; + break; + case OHCI_USB_SUSPEND: + hcfs = " suspend"; + break; + default: + hcfs = " ?"; + } + hcfs; + })); +} + +static void dump_regs(struct seq_file *s, struct isp1362_hcd *isp1362_hcd) +{ + seq_printf(s, "HCREVISION [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCREVISION), + isp1362_read_reg32(isp1362_hcd, HCREVISION)); + seq_printf(s, "HCCONTROL [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCCONTROL), + isp1362_read_reg32(isp1362_hcd, HCCONTROL)); + seq_printf(s, "HCCMDSTAT [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCCMDSTAT), + isp1362_read_reg32(isp1362_hcd, HCCMDSTAT)); + seq_printf(s, "HCINTSTAT [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCINTSTAT), + isp1362_read_reg32(isp1362_hcd, HCINTSTAT)); + seq_printf(s, "HCINTENB [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCINTENB), + isp1362_read_reg32(isp1362_hcd, HCINTENB)); + seq_printf(s, "HCFMINTVL [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCFMINTVL), + isp1362_read_reg32(isp1362_hcd, HCFMINTVL)); + seq_printf(s, "HCFMREM [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCFMREM), + isp1362_read_reg32(isp1362_hcd, HCFMREM)); + seq_printf(s, "HCFMNUM [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCFMNUM), + isp1362_read_reg32(isp1362_hcd, HCFMNUM)); + seq_printf(s, "HCLSTHRESH [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCLSTHRESH), + isp1362_read_reg32(isp1362_hcd, HCLSTHRESH)); + seq_printf(s, "HCRHDESCA [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCRHDESCA), + isp1362_read_reg32(isp1362_hcd, HCRHDESCA)); + seq_printf(s, "HCRHDESCB [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCRHDESCB), + isp1362_read_reg32(isp1362_hcd, HCRHDESCB)); + seq_printf(s, "HCRHSTATUS [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCRHSTATUS), + isp1362_read_reg32(isp1362_hcd, HCRHSTATUS)); + seq_printf(s, "HCRHPORT1 [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCRHPORT1), + isp1362_read_reg32(isp1362_hcd, HCRHPORT1)); + seq_printf(s, "HCRHPORT2 [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCRHPORT2), + isp1362_read_reg32(isp1362_hcd, HCRHPORT2)); + seq_printf(s, "\n"); + seq_printf(s, "HCHWCFG [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCHWCFG), + isp1362_read_reg16(isp1362_hcd, HCHWCFG)); + seq_printf(s, "HCDMACFG [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCDMACFG), + isp1362_read_reg16(isp1362_hcd, HCDMACFG)); + seq_printf(s, "HCXFERCTR [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCXFERCTR), + isp1362_read_reg16(isp1362_hcd, HCXFERCTR)); + seq_printf(s, "HCuPINT [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCuPINT), + isp1362_read_reg16(isp1362_hcd, HCuPINT)); + seq_printf(s, "HCuPINTENB [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCuPINTENB), + isp1362_read_reg16(isp1362_hcd, HCuPINTENB)); + seq_printf(s, "HCCHIPID [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCCHIPID), + isp1362_read_reg16(isp1362_hcd, HCCHIPID)); + seq_printf(s, "HCSCRATCH [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCSCRATCH), + isp1362_read_reg16(isp1362_hcd, HCSCRATCH)); + seq_printf(s, "HCBUFSTAT [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCBUFSTAT), + isp1362_read_reg16(isp1362_hcd, HCBUFSTAT)); + seq_printf(s, "HCDIRADDR [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCDIRADDR), + isp1362_read_reg32(isp1362_hcd, HCDIRADDR)); +#if 0 + seq_printf(s, "HCDIRDATA [%02x] %04x\n", ISP1362_REG_NO(HCDIRDATA), + isp1362_read_reg16(isp1362_hcd, HCDIRDATA)); +#endif + seq_printf(s, "HCISTLBUFSZ[%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCISTLBUFSZ), + isp1362_read_reg16(isp1362_hcd, HCISTLBUFSZ)); + seq_printf(s, "HCISTLRATE [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCISTLRATE), + isp1362_read_reg16(isp1362_hcd, HCISTLRATE)); + seq_printf(s, "\n"); + seq_printf(s, "HCINTLBUFSZ[%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCINTLBUFSZ), + isp1362_read_reg16(isp1362_hcd, HCINTLBUFSZ)); + seq_printf(s, "HCINTLBLKSZ[%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCINTLBLKSZ), + isp1362_read_reg16(isp1362_hcd, HCINTLBLKSZ)); + seq_printf(s, "HCINTLDONE [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCINTLDONE), + isp1362_read_reg32(isp1362_hcd, HCINTLDONE)); + seq_printf(s, "HCINTLSKIP [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCINTLSKIP), + isp1362_read_reg32(isp1362_hcd, HCINTLSKIP)); + seq_printf(s, "HCINTLLAST [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCINTLLAST), + isp1362_read_reg32(isp1362_hcd, HCINTLLAST)); + seq_printf(s, "HCINTLCURR [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCINTLCURR), + isp1362_read_reg16(isp1362_hcd, HCINTLCURR)); + seq_printf(s, "\n"); + seq_printf(s, "HCATLBUFSZ [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCATLBUFSZ), + isp1362_read_reg16(isp1362_hcd, HCATLBUFSZ)); + seq_printf(s, "HCATLBLKSZ [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCATLBLKSZ), + isp1362_read_reg16(isp1362_hcd, HCATLBLKSZ)); +#if 0 + seq_printf(s, "HCATLDONE [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCATLDONE), + isp1362_read_reg32(isp1362_hcd, HCATLDONE)); +#endif + seq_printf(s, "HCATLSKIP [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCATLSKIP), + isp1362_read_reg32(isp1362_hcd, HCATLSKIP)); + seq_printf(s, "HCATLLAST [%02x] %08x\n", ISP1362_REG_NO(ISP1362_REG_HCATLLAST), + isp1362_read_reg32(isp1362_hcd, HCATLLAST)); + seq_printf(s, "HCATLCURR [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCATLCURR), + isp1362_read_reg16(isp1362_hcd, HCATLCURR)); + seq_printf(s, "\n"); + seq_printf(s, "HCATLDTC [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCATLDTC), + isp1362_read_reg16(isp1362_hcd, HCATLDTC)); + seq_printf(s, "HCATLDTCTO [%02x] %04x\n", ISP1362_REG_NO(ISP1362_REG_HCATLDTCTO), + isp1362_read_reg16(isp1362_hcd, HCATLDTCTO)); +} + +static int proc_isp1362_show(struct seq_file *s, void *unused) +{ + struct isp1362_hcd *isp1362_hcd = s->private; + struct isp1362_ep *ep; + int i; + + seq_printf(s, "%s\n%s version %s\n", + isp1362_hcd_to_hcd(isp1362_hcd)->product_desc, hcd_name, DRIVER_VERSION); + + /* collect statistics to help estimate potential win for + * DMA engines that care about alignment (PXA) + */ + seq_printf(s, "alignment: 16b/%ld 8b/%ld 4b/%ld 2b/%ld 1b/%ld\n", + isp1362_hcd->stat16, isp1362_hcd->stat8, isp1362_hcd->stat4, + isp1362_hcd->stat2, isp1362_hcd->stat1); + seq_printf(s, "max # ptds in ATL fifo: %d\n", isp1362_hcd->atl_queue.stat_maxptds); + seq_printf(s, "max # ptds in INTL fifo: %d\n", isp1362_hcd->intl_queue.stat_maxptds); + seq_printf(s, "max # ptds in ISTL fifo: %d\n", + max(isp1362_hcd->istl_queue[0] .stat_maxptds, + isp1362_hcd->istl_queue[1] .stat_maxptds)); + + /* FIXME: don't show the following in suspended state */ + spin_lock_irq(&isp1362_hcd->lock); + + dump_irq(s, "hc_irq_enable", isp1362_read_reg16(isp1362_hcd, HCuPINTENB)); + dump_irq(s, "hc_irq_status", isp1362_read_reg16(isp1362_hcd, HCuPINT)); + dump_int(s, "ohci_int_enable", isp1362_read_reg32(isp1362_hcd, HCINTENB)); + dump_int(s, "ohci_int_status", isp1362_read_reg32(isp1362_hcd, HCINTSTAT)); + dump_ctrl(s, "ohci_control", isp1362_read_reg32(isp1362_hcd, HCCONTROL)); + + for (i = 0; i < NUM_ISP1362_IRQS; i++) + if (isp1362_hcd->irq_stat[i]) + seq_printf(s, "%-15s: %d\n", + ISP1362_INT_NAME(i), isp1362_hcd->irq_stat[i]); + + dump_regs(s, isp1362_hcd); + list_for_each_entry(ep, &isp1362_hcd->async, schedule) { + struct urb *urb; + + seq_printf(s, "%p, ep%d%s, maxpacket %d:\n", ep, ep->epnum, + ({ + char *s; + switch (ep->nextpid) { + case USB_PID_IN: + s = "in"; + break; + case USB_PID_OUT: + s = "out"; + break; + case USB_PID_SETUP: + s = "setup"; + break; + case USB_PID_ACK: + s = "status"; + break; + default: + s = "?"; + break; + }; + s;}), ep->maxpacket) ; + list_for_each_entry(urb, &ep->hep->urb_list, urb_list) { + seq_printf(s, " urb%p, %d/%d\n", urb, + urb->actual_length, + urb->transfer_buffer_length); + } + } + if (!list_empty(&isp1362_hcd->async)) + seq_printf(s, "\n"); + dump_ptd_queue(&isp1362_hcd->atl_queue); + + seq_printf(s, "periodic size= %d\n", PERIODIC_SIZE); + + list_for_each_entry(ep, &isp1362_hcd->periodic, schedule) { + seq_printf(s, "branch:%2d load:%3d PTD[%d] $%04x:\n", ep->branch, + isp1362_hcd->load[ep->branch], ep->ptd_index, ep->ptd_offset); + + seq_printf(s, " %d/%p (%sdev%d ep%d%s max %d)\n", + ep->interval, ep, + (ep->udev->speed == USB_SPEED_FULL) ? "" : "ls ", + ep->udev->devnum, ep->epnum, + (ep->epnum == 0) ? "" : + ((ep->nextpid == USB_PID_IN) ? + "in" : "out"), ep->maxpacket); + } + dump_ptd_queue(&isp1362_hcd->intl_queue); + + seq_printf(s, "ISO:\n"); + + list_for_each_entry(ep, &isp1362_hcd->isoc, schedule) { + seq_printf(s, " %d/%p (%sdev%d ep%d%s max %d)\n", + ep->interval, ep, + (ep->udev->speed == USB_SPEED_FULL) ? "" : "ls ", + ep->udev->devnum, ep->epnum, + (ep->epnum == 0) ? "" : + ((ep->nextpid == USB_PID_IN) ? + "in" : "out"), ep->maxpacket); + } + + spin_unlock_irq(&isp1362_hcd->lock); + seq_printf(s, "\n"); + + return 0; +} + +static int proc_isp1362_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_isp1362_show, PDE_DATA(inode)); +} + +static const struct file_operations proc_ops = { + .open = proc_isp1362_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* expect just one isp1362_hcd per system */ +static const char proc_filename[] = "driver/isp1362"; + +static void create_debug_file(struct isp1362_hcd *isp1362_hcd) +{ + struct proc_dir_entry *pde; + + pde = proc_create_data(proc_filename, 0, NULL, &proc_ops, isp1362_hcd); + if (pde == NULL) { + pr_warning("%s: Failed to create debug file '%s'\n", __func__, proc_filename); + return; + } + isp1362_hcd->pde = pde; +} + +static void remove_debug_file(struct isp1362_hcd *isp1362_hcd) +{ + if (isp1362_hcd->pde) + remove_proc_entry(proc_filename, NULL); +} + +#endif + +/*-------------------------------------------------------------------------*/ + +static void __isp1362_sw_reset(struct isp1362_hcd *isp1362_hcd) +{ + int tmp = 20; + + isp1362_write_reg16(isp1362_hcd, HCSWRES, HCSWRES_MAGIC); + isp1362_write_reg32(isp1362_hcd, HCCMDSTAT, OHCI_HCR); + while (--tmp) { + mdelay(1); + if (!(isp1362_read_reg32(isp1362_hcd, HCCMDSTAT) & OHCI_HCR)) + break; + } + if (!tmp) + pr_err("Software reset timeout\n"); +} + +static void isp1362_sw_reset(struct isp1362_hcd *isp1362_hcd) +{ + unsigned long flags; + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + __isp1362_sw_reset(isp1362_hcd); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); +} + +static int isp1362_mem_config(struct usb_hcd *hcd) +{ + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + unsigned long flags; + u32 total; + u16 istl_size = ISP1362_ISTL_BUFSIZE; + u16 intl_blksize = ISP1362_INTL_BLKSIZE + PTD_HEADER_SIZE; + u16 intl_size = ISP1362_INTL_BUFFERS * intl_blksize; + u16 atl_blksize = ISP1362_ATL_BLKSIZE + PTD_HEADER_SIZE; + u16 atl_buffers = (ISP1362_BUF_SIZE - (istl_size + intl_size)) / atl_blksize; + u16 atl_size; + int i; + + WARN_ON(istl_size & 3); + WARN_ON(atl_blksize & 3); + WARN_ON(intl_blksize & 3); + WARN_ON(atl_blksize < PTD_HEADER_SIZE); + WARN_ON(intl_blksize < PTD_HEADER_SIZE); + + BUG_ON((unsigned)ISP1362_INTL_BUFFERS > 32); + if (atl_buffers > 32) + atl_buffers = 32; + atl_size = atl_buffers * atl_blksize; + total = atl_size + intl_size + istl_size; + dev_info(hcd->self.controller, "ISP1362 Memory usage:\n"); + dev_info(hcd->self.controller, " ISTL: 2 * %4d: %4d @ $%04x:$%04x\n", + istl_size / 2, istl_size, 0, istl_size / 2); + dev_info(hcd->self.controller, " INTL: %4d * (%3zu+8): %4d @ $%04x\n", + ISP1362_INTL_BUFFERS, intl_blksize - PTD_HEADER_SIZE, + intl_size, istl_size); + dev_info(hcd->self.controller, " ATL : %4d * (%3zu+8): %4d @ $%04x\n", + atl_buffers, atl_blksize - PTD_HEADER_SIZE, + atl_size, istl_size + intl_size); + dev_info(hcd->self.controller, " USED/FREE: %4d %4d\n", total, + ISP1362_BUF_SIZE - total); + + if (total > ISP1362_BUF_SIZE) { + dev_err(hcd->self.controller, "%s: Memory requested: %d, available %d\n", + __func__, total, ISP1362_BUF_SIZE); + return -ENOMEM; + } + + total = istl_size + intl_size + atl_size; + spin_lock_irqsave(&isp1362_hcd->lock, flags); + + for (i = 0; i < 2; i++) { + isp1362_hcd->istl_queue[i].buf_start = i * istl_size / 2, + isp1362_hcd->istl_queue[i].buf_size = istl_size / 2; + isp1362_hcd->istl_queue[i].blk_size = 4; + INIT_LIST_HEAD(&isp1362_hcd->istl_queue[i].active); + snprintf(isp1362_hcd->istl_queue[i].name, + sizeof(isp1362_hcd->istl_queue[i].name), "ISTL%d", i); + DBG(3, "%s: %5s buf $%04x %d\n", __func__, + isp1362_hcd->istl_queue[i].name, + isp1362_hcd->istl_queue[i].buf_start, + isp1362_hcd->istl_queue[i].buf_size); + } + isp1362_write_reg16(isp1362_hcd, HCISTLBUFSZ, istl_size / 2); + + isp1362_hcd->intl_queue.buf_start = istl_size; + isp1362_hcd->intl_queue.buf_size = intl_size; + isp1362_hcd->intl_queue.buf_count = ISP1362_INTL_BUFFERS; + isp1362_hcd->intl_queue.blk_size = intl_blksize; + isp1362_hcd->intl_queue.buf_avail = isp1362_hcd->intl_queue.buf_count; + isp1362_hcd->intl_queue.skip_map = ~0; + INIT_LIST_HEAD(&isp1362_hcd->intl_queue.active); + + isp1362_write_reg16(isp1362_hcd, HCINTLBUFSZ, + isp1362_hcd->intl_queue.buf_size); + isp1362_write_reg16(isp1362_hcd, HCINTLBLKSZ, + isp1362_hcd->intl_queue.blk_size - PTD_HEADER_SIZE); + isp1362_write_reg32(isp1362_hcd, HCINTLSKIP, ~0); + isp1362_write_reg32(isp1362_hcd, HCINTLLAST, + 1 << (ISP1362_INTL_BUFFERS - 1)); + + isp1362_hcd->atl_queue.buf_start = istl_size + intl_size; + isp1362_hcd->atl_queue.buf_size = atl_size; + isp1362_hcd->atl_queue.buf_count = atl_buffers; + isp1362_hcd->atl_queue.blk_size = atl_blksize; + isp1362_hcd->atl_queue.buf_avail = isp1362_hcd->atl_queue.buf_count; + isp1362_hcd->atl_queue.skip_map = ~0; + INIT_LIST_HEAD(&isp1362_hcd->atl_queue.active); + + isp1362_write_reg16(isp1362_hcd, HCATLBUFSZ, + isp1362_hcd->atl_queue.buf_size); + isp1362_write_reg16(isp1362_hcd, HCATLBLKSZ, + isp1362_hcd->atl_queue.blk_size - PTD_HEADER_SIZE); + isp1362_write_reg32(isp1362_hcd, HCATLSKIP, ~0); + isp1362_write_reg32(isp1362_hcd, HCATLLAST, + 1 << (atl_buffers - 1)); + + snprintf(isp1362_hcd->atl_queue.name, + sizeof(isp1362_hcd->atl_queue.name), "ATL"); + snprintf(isp1362_hcd->intl_queue.name, + sizeof(isp1362_hcd->intl_queue.name), "INTL"); + DBG(3, "%s: %5s buf $%04x %2d * %4d = %4d\n", __func__, + isp1362_hcd->intl_queue.name, + isp1362_hcd->intl_queue.buf_start, + ISP1362_INTL_BUFFERS, isp1362_hcd->intl_queue.blk_size, + isp1362_hcd->intl_queue.buf_size); + DBG(3, "%s: %5s buf $%04x %2d * %4d = %4d\n", __func__, + isp1362_hcd->atl_queue.name, + isp1362_hcd->atl_queue.buf_start, + atl_buffers, isp1362_hcd->atl_queue.blk_size, + isp1362_hcd->atl_queue.buf_size); + + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + return 0; +} + +static int isp1362_hc_reset(struct usb_hcd *hcd) +{ + int ret = 0; + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + unsigned long t; + unsigned long timeout = 100; + unsigned long flags; + int clkrdy = 0; + + pr_debug("%s:\n", __func__); + + if (isp1362_hcd->board && isp1362_hcd->board->reset) { + isp1362_hcd->board->reset(hcd->self.controller, 1); + msleep(20); + if (isp1362_hcd->board->clock) + isp1362_hcd->board->clock(hcd->self.controller, 1); + isp1362_hcd->board->reset(hcd->self.controller, 0); + } else + isp1362_sw_reset(isp1362_hcd); + + /* chip has been reset. First we need to see a clock */ + t = jiffies + msecs_to_jiffies(timeout); + while (!clkrdy && time_before_eq(jiffies, t)) { + spin_lock_irqsave(&isp1362_hcd->lock, flags); + clkrdy = isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_CLKRDY; + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + if (!clkrdy) + msleep(4); + } + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_reg16(isp1362_hcd, HCuPINT, HCuPINT_CLKRDY); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + if (!clkrdy) { + pr_err("Clock not ready after %lums\n", timeout); + ret = -ENODEV; + } + return ret; +} + +static void isp1362_hc_stop(struct usb_hcd *hcd) +{ + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + unsigned long flags; + u32 tmp; + + pr_debug("%s:\n", __func__); + + del_timer_sync(&hcd->rh_timer); + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + + isp1362_write_reg16(isp1362_hcd, HCuPINTENB, 0); + + /* Switch off power for all ports */ + tmp = isp1362_read_reg32(isp1362_hcd, HCRHDESCA); + tmp &= ~(RH_A_NPS | RH_A_PSM); + isp1362_write_reg32(isp1362_hcd, HCRHDESCA, tmp); + isp1362_write_reg32(isp1362_hcd, HCRHSTATUS, RH_HS_LPS); + + /* Reset the chip */ + if (isp1362_hcd->board && isp1362_hcd->board->reset) + isp1362_hcd->board->reset(hcd->self.controller, 1); + else + __isp1362_sw_reset(isp1362_hcd); + + if (isp1362_hcd->board && isp1362_hcd->board->clock) + isp1362_hcd->board->clock(hcd->self.controller, 0); + + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); +} + +#ifdef CHIP_BUFFER_TEST +static int isp1362_chip_test(struct isp1362_hcd *isp1362_hcd) +{ + int ret = 0; + u16 *ref; + unsigned long flags; + + ref = kmalloc(2 * ISP1362_BUF_SIZE, GFP_KERNEL); + if (ref) { + int offset; + u16 *tst = &ref[ISP1362_BUF_SIZE / 2]; + + for (offset = 0; offset < ISP1362_BUF_SIZE / 2; offset++) { + ref[offset] = ~offset; + tst[offset] = offset; + } + + for (offset = 0; offset < 4; offset++) { + int j; + + for (j = 0; j < 8; j++) { + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_buffer(isp1362_hcd, (u8 *)ref + offset, 0, j); + isp1362_read_buffer(isp1362_hcd, (u8 *)tst + offset, 0, j); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + if (memcmp(ref, tst, j)) { + ret = -ENODEV; + pr_err("%s: memory check with %d byte offset %d failed\n", + __func__, j, offset); + dump_data((u8 *)ref + offset, j); + dump_data((u8 *)tst + offset, j); + } + } + } + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_buffer(isp1362_hcd, ref, 0, ISP1362_BUF_SIZE); + isp1362_read_buffer(isp1362_hcd, tst, 0, ISP1362_BUF_SIZE); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + if (memcmp(ref, tst, ISP1362_BUF_SIZE)) { + ret = -ENODEV; + pr_err("%s: memory check failed\n", __func__); + dump_data((u8 *)tst, ISP1362_BUF_SIZE / 2); + } + + for (offset = 0; offset < 256; offset++) { + int test_size = 0; + + yield(); + + memset(tst, 0, ISP1362_BUF_SIZE); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_buffer(isp1362_hcd, tst, 0, ISP1362_BUF_SIZE); + isp1362_read_buffer(isp1362_hcd, tst, 0, ISP1362_BUF_SIZE); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + if (memcmp(tst, tst + (ISP1362_BUF_SIZE / (2 * sizeof(*tst))), + ISP1362_BUF_SIZE / 2)) { + pr_err("%s: Failed to clear buffer\n", __func__); + dump_data((u8 *)tst, ISP1362_BUF_SIZE); + break; + } + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_buffer(isp1362_hcd, ref, offset * 2, PTD_HEADER_SIZE); + isp1362_write_buffer(isp1362_hcd, ref + PTD_HEADER_SIZE / sizeof(*ref), + offset * 2 + PTD_HEADER_SIZE, test_size); + isp1362_read_buffer(isp1362_hcd, tst, offset * 2, + PTD_HEADER_SIZE + test_size); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + if (memcmp(ref, tst, PTD_HEADER_SIZE + test_size)) { + dump_data(((u8 *)ref) + offset, PTD_HEADER_SIZE + test_size); + dump_data((u8 *)tst, PTD_HEADER_SIZE + test_size); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_read_buffer(isp1362_hcd, tst, offset * 2, + PTD_HEADER_SIZE + test_size); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + if (memcmp(ref, tst, PTD_HEADER_SIZE + test_size)) { + ret = -ENODEV; + pr_err("%s: memory check with offset %02x failed\n", + __func__, offset); + break; + } + pr_warning("%s: memory check with offset %02x ok after second read\n", + __func__, offset); + } + } + kfree(ref); + } + return ret; +} +#endif + +static int isp1362_hc_start(struct usb_hcd *hcd) +{ + int ret; + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + struct isp1362_platform_data *board = isp1362_hcd->board; + u16 hwcfg; + u16 chipid; + unsigned long flags; + + pr_debug("%s:\n", __func__); + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + chipid = isp1362_read_reg16(isp1362_hcd, HCCHIPID); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + if ((chipid & HCCHIPID_MASK) != HCCHIPID_MAGIC) { + pr_err("%s: Invalid chip ID %04x\n", __func__, chipid); + return -ENODEV; + } + +#ifdef CHIP_BUFFER_TEST + ret = isp1362_chip_test(isp1362_hcd); + if (ret) + return -ENODEV; +#endif + spin_lock_irqsave(&isp1362_hcd->lock, flags); + /* clear interrupt status and disable all interrupt sources */ + isp1362_write_reg16(isp1362_hcd, HCuPINT, 0xff); + isp1362_write_reg16(isp1362_hcd, HCuPINTENB, 0); + + /* HW conf */ + hwcfg = HCHWCFG_INT_ENABLE | HCHWCFG_DBWIDTH(1); + if (board->sel15Kres) + hwcfg |= HCHWCFG_PULLDOWN_DS2 | + ((MAX_ROOT_PORTS > 1) ? HCHWCFG_PULLDOWN_DS1 : 0); + if (board->clknotstop) + hwcfg |= HCHWCFG_CLKNOTSTOP; + if (board->oc_enable) + hwcfg |= HCHWCFG_ANALOG_OC; + if (board->int_act_high) + hwcfg |= HCHWCFG_INT_POL; + if (board->int_edge_triggered) + hwcfg |= HCHWCFG_INT_TRIGGER; + if (board->dreq_act_high) + hwcfg |= HCHWCFG_DREQ_POL; + if (board->dack_act_high) + hwcfg |= HCHWCFG_DACK_POL; + isp1362_write_reg16(isp1362_hcd, HCHWCFG, hwcfg); + isp1362_show_reg(isp1362_hcd, HCHWCFG); + isp1362_write_reg16(isp1362_hcd, HCDMACFG, 0); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + ret = isp1362_mem_config(hcd); + if (ret) + return ret; + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + + /* Root hub conf */ + isp1362_hcd->rhdesca = 0; + if (board->no_power_switching) + isp1362_hcd->rhdesca |= RH_A_NPS; + if (board->power_switching_mode) + isp1362_hcd->rhdesca |= RH_A_PSM; + if (board->potpg) + isp1362_hcd->rhdesca |= (board->potpg << 24) & RH_A_POTPGT; + else + isp1362_hcd->rhdesca |= (25 << 24) & RH_A_POTPGT; + + isp1362_write_reg32(isp1362_hcd, HCRHDESCA, isp1362_hcd->rhdesca & ~RH_A_OCPM); + isp1362_write_reg32(isp1362_hcd, HCRHDESCA, isp1362_hcd->rhdesca | RH_A_OCPM); + isp1362_hcd->rhdesca = isp1362_read_reg32(isp1362_hcd, HCRHDESCA); + + isp1362_hcd->rhdescb = RH_B_PPCM; + isp1362_write_reg32(isp1362_hcd, HCRHDESCB, isp1362_hcd->rhdescb); + isp1362_hcd->rhdescb = isp1362_read_reg32(isp1362_hcd, HCRHDESCB); + + isp1362_read_reg32(isp1362_hcd, HCFMINTVL); + isp1362_write_reg32(isp1362_hcd, HCFMINTVL, (FSMP(FI) << 16) | FI); + isp1362_write_reg32(isp1362_hcd, HCLSTHRESH, LSTHRESH); + + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + isp1362_hcd->hc_control = OHCI_USB_OPER; + hcd->state = HC_STATE_RUNNING; + + spin_lock_irqsave(&isp1362_hcd->lock, flags); + /* Set up interrupts */ + isp1362_hcd->intenb = OHCI_INTR_MIE | OHCI_INTR_RHSC | OHCI_INTR_UE; + isp1362_hcd->intenb |= OHCI_INTR_RD; + isp1362_hcd->irqenb = HCuPINT_OPR | HCuPINT_SUSP; + isp1362_write_reg32(isp1362_hcd, HCINTENB, isp1362_hcd->intenb); + isp1362_write_reg16(isp1362_hcd, HCuPINTENB, isp1362_hcd->irqenb); + + /* Go operational */ + isp1362_write_reg32(isp1362_hcd, HCCONTROL, isp1362_hcd->hc_control); + /* enable global power */ + isp1362_write_reg32(isp1362_hcd, HCRHSTATUS, RH_HS_LPSC | RH_HS_DRWE); + + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct hc_driver isp1362_hc_driver = { + .description = hcd_name, + .product_desc = "ISP1362 Host Controller", + .hcd_priv_size = sizeof(struct isp1362_hcd), + + .irq = isp1362_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + .reset = isp1362_hc_reset, + .start = isp1362_hc_start, + .stop = isp1362_hc_stop, + + .urb_enqueue = isp1362_urb_enqueue, + .urb_dequeue = isp1362_urb_dequeue, + .endpoint_disable = isp1362_endpoint_disable, + + .get_frame_number = isp1362_get_frame, + + .hub_status_data = isp1362_hub_status_data, + .hub_control = isp1362_hub_control, + .bus_suspend = isp1362_bus_suspend, + .bus_resume = isp1362_bus_resume, +}; + +/*-------------------------------------------------------------------------*/ + +static int isp1362_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + struct resource *res; + + remove_debug_file(isp1362_hcd); + DBG(0, "%s: Removing HCD\n", __func__); + usb_remove_hcd(hcd); + + DBG(0, "%s: Unmapping data_reg @ %p\n", __func__, + isp1362_hcd->data_reg); + iounmap(isp1362_hcd->data_reg); + + DBG(0, "%s: Unmapping addr_reg @ %p\n", __func__, + isp1362_hcd->addr_reg); + iounmap(isp1362_hcd->addr_reg); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + DBG(0, "%s: release mem_region: %08lx\n", __func__, (long unsigned int)res->start); + if (res) + release_mem_region(res->start, resource_size(res)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + DBG(0, "%s: release mem_region: %08lx\n", __func__, (long unsigned int)res->start); + if (res) + release_mem_region(res->start, resource_size(res)); + + DBG(0, "%s: put_hcd\n", __func__); + usb_put_hcd(hcd); + DBG(0, "%s: Done\n", __func__); + + return 0; +} + +static int isp1362_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct isp1362_hcd *isp1362_hcd; + struct resource *addr, *data; + void __iomem *addr_reg; + void __iomem *data_reg; + int irq; + int retval = 0; + struct resource *irq_res; + unsigned int irq_flags = 0; + + if (usb_disabled()) + return -ENODEV; + + /* basic sanity checks first. board-specific init logic should + * have initialized this the three resources and probably board + * specific platform_data. we don't probe for IRQs, and do only + * minimal sanity checking. + */ + if (pdev->num_resources < 3) { + retval = -ENODEV; + goto err1; + } + + data = platform_get_resource(pdev, IORESOURCE_MEM, 0); + addr = platform_get_resource(pdev, IORESOURCE_MEM, 1); + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!addr || !data || !irq_res) { + retval = -ENODEV; + goto err1; + } + irq = irq_res->start; + + if (pdev->dev.dma_mask) { + DBG(1, "won't do DMA"); + retval = -ENODEV; + goto err1; + } + + if (!request_mem_region(addr->start, resource_size(addr), hcd_name)) { + retval = -EBUSY; + goto err1; + } + addr_reg = ioremap(addr->start, resource_size(addr)); + if (addr_reg == NULL) { + retval = -ENOMEM; + goto err2; + } + + if (!request_mem_region(data->start, resource_size(data), hcd_name)) { + retval = -EBUSY; + goto err3; + } + data_reg = ioremap(data->start, resource_size(data)); + if (data_reg == NULL) { + retval = -ENOMEM; + goto err4; + } + + /* allocate and initialize hcd */ + hcd = usb_create_hcd(&isp1362_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto err5; + } + hcd->rsrc_start = data->start; + isp1362_hcd = hcd_to_isp1362_hcd(hcd); + isp1362_hcd->data_reg = data_reg; + isp1362_hcd->addr_reg = addr_reg; + + isp1362_hcd->next_statechange = jiffies; + spin_lock_init(&isp1362_hcd->lock); + INIT_LIST_HEAD(&isp1362_hcd->async); + INIT_LIST_HEAD(&isp1362_hcd->periodic); + INIT_LIST_HEAD(&isp1362_hcd->isoc); + INIT_LIST_HEAD(&isp1362_hcd->remove_list); + isp1362_hcd->board = pdev->dev.platform_data; +#if USE_PLATFORM_DELAY + if (!isp1362_hcd->board->delay) { + dev_err(hcd->self.controller, "No platform delay function given\n"); + retval = -ENODEV; + goto err6; + } +#endif + + if (irq_res->flags & IORESOURCE_IRQ_HIGHEDGE) + irq_flags |= IRQF_TRIGGER_RISING; + if (irq_res->flags & IORESOURCE_IRQ_LOWEDGE) + irq_flags |= IRQF_TRIGGER_FALLING; + if (irq_res->flags & IORESOURCE_IRQ_HIGHLEVEL) + irq_flags |= IRQF_TRIGGER_HIGH; + if (irq_res->flags & IORESOURCE_IRQ_LOWLEVEL) + irq_flags |= IRQF_TRIGGER_LOW; + + retval = usb_add_hcd(hcd, irq, irq_flags | IRQF_SHARED); + if (retval != 0) + goto err6; + pr_info("%s, irq %d\n", hcd->product_desc, irq); + + create_debug_file(isp1362_hcd); + + return 0; + + err6: + DBG(0, "%s: Freeing dev %p\n", __func__, isp1362_hcd); + usb_put_hcd(hcd); + err5: + DBG(0, "%s: Unmapping data_reg @ %p\n", __func__, data_reg); + iounmap(data_reg); + err4: + DBG(0, "%s: Releasing mem region %08lx\n", __func__, (long unsigned int)data->start); + release_mem_region(data->start, resource_size(data)); + err3: + DBG(0, "%s: Unmapping addr_reg @ %p\n", __func__, addr_reg); + iounmap(addr_reg); + err2: + DBG(0, "%s: Releasing mem region %08lx\n", __func__, (long unsigned int)addr->start); + release_mem_region(addr->start, resource_size(addr)); + err1: + pr_err("%s: init error, %d\n", __func__, retval); + + return retval; +} + +#ifdef CONFIG_PM +static int isp1362_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + unsigned long flags; + int retval = 0; + + DBG(0, "%s: Suspending device\n", __func__); + + if (state.event == PM_EVENT_FREEZE) { + DBG(0, "%s: Suspending root hub\n", __func__); + retval = isp1362_bus_suspend(hcd); + } else { + DBG(0, "%s: Suspending RH ports\n", __func__); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_reg32(isp1362_hcd, HCRHSTATUS, RH_HS_LPS); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + } + if (retval == 0) + pdev->dev.power.power_state = state; + return retval; +} + +static int isp1362_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct isp1362_hcd *isp1362_hcd = hcd_to_isp1362_hcd(hcd); + unsigned long flags; + + DBG(0, "%s: Resuming\n", __func__); + + if (pdev->dev.power.power_state.event == PM_EVENT_SUSPEND) { + DBG(0, "%s: Resume RH ports\n", __func__); + spin_lock_irqsave(&isp1362_hcd->lock, flags); + isp1362_write_reg32(isp1362_hcd, HCRHSTATUS, RH_HS_LPSC); + spin_unlock_irqrestore(&isp1362_hcd->lock, flags); + return 0; + } + + pdev->dev.power.power_state = PMSG_ON; + + return isp1362_bus_resume(isp1362_hcd_to_hcd(isp1362_hcd)); +} +#else +#define isp1362_suspend NULL +#define isp1362_resume NULL +#endif + +static struct platform_driver isp1362_driver = { + .probe = isp1362_probe, + .remove = isp1362_remove, + + .suspend = isp1362_suspend, + .resume = isp1362_resume, + .driver = { + .name = (char *)hcd_name, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(isp1362_driver); diff --git a/drivers/usb/host/isp1362.h b/drivers/usb/host/isp1362.h new file mode 100644 index 00000000..0f97820e --- /dev/null +++ b/drivers/usb/host/isp1362.h @@ -0,0 +1,1057 @@ +/* + * ISP1362 HCD (Host Controller Driver) for USB. + * + * COPYRIGHT (C) by L. Wassmann + */ + +/* ------------------------------------------------------------------------- */ +/* + * Platform specific compile time options + */ +#if defined(CONFIG_BLACKFIN) + +#include +#define USE_32BIT 0 +#define MAX_ROOT_PORTS 2 +#define USE_PLATFORM_DELAY 0 +#define USE_NDELAY 1 + +#define DUMMY_DELAY_ACCESS \ + do { \ + bfin_read16(ASYNC_BANK0_BASE); \ + bfin_read16(ASYNC_BANK0_BASE); \ + bfin_read16(ASYNC_BANK0_BASE); \ + } while (0) + +#undef insw +#undef outsw + +#define insw delayed_insw +#define outsw delayed_outsw + +static inline void delayed_outsw(unsigned int addr, void *buf, int len) +{ + unsigned short *bp = (unsigned short *)buf; + while (len--) { + DUMMY_DELAY_ACCESS; + outw(*bp++, addr); + } +} + +static inline void delayed_insw(unsigned int addr, void *buf, int len) +{ + unsigned short *bp = (unsigned short *)buf; + while (len--) { + DUMMY_DELAY_ACCESS; + *bp++ = inw(addr); + } +} + +#else + +#define MAX_ROOT_PORTS 2 + +#define USE_32BIT 0 + +/* These options are mutually eclusive */ +#define USE_PLATFORM_DELAY 0 +#define USE_NDELAY 0 + +#define DUMMY_DELAY_ACCESS do {} while (0) + +#endif + + +/* ------------------------------------------------------------------------- */ + +#define USB_RESET_WIDTH 50 +#define MAX_XFER_SIZE 1023 + +/* Buffer sizes */ +#define ISP1362_BUF_SIZE 4096 +#define ISP1362_ISTL_BUFSIZE 512 +#define ISP1362_INTL_BLKSIZE 64 +#define ISP1362_INTL_BUFFERS 16 +#define ISP1362_ATL_BLKSIZE 64 + +#define ISP1362_REG_WRITE_OFFSET 0x80 + +#ifdef ISP1362_DEBUG +typedef const unsigned int isp1362_reg_t; + +#define REG_WIDTH_16 0x000 +#define REG_WIDTH_32 0x100 +#define REG_WIDTH_MASK 0x100 +#define REG_NO_MASK 0x0ff + +#define REG_ACCESS_R 0x200 +#define REG_ACCESS_W 0x400 +#define REG_ACCESS_RW 0x600 +#define REG_ACCESS_MASK 0x600 + +#define ISP1362_REG_NO(r) ((r) & REG_NO_MASK) + +#define _BUG_ON(x) BUG_ON(x) +#define _WARN_ON(x) WARN_ON(x) + +#define ISP1362_REG(name, addr, width, rw) \ +static isp1362_reg_t ISP1362_REG_##name = ((addr) | (width) | (rw)) + +#define REG_ACCESS_TEST(r) BUG_ON(((r) & ISP1362_REG_WRITE_OFFSET) && !((r) & REG_ACCESS_W)) +#define REG_WIDTH_TEST(r, w) BUG_ON(((r) & REG_WIDTH_MASK) != (w)) +#else +typedef const unsigned char isp1362_reg_t; +#define ISP1362_REG_NO(r) (r) +#define _BUG_ON(x) do {} while (0) +#define _WARN_ON(x) do {} while (0) + +#define ISP1362_REG(name, addr, width, rw) \ +static isp1362_reg_t ISP1362_REG_##name = addr + +#define REG_ACCESS_TEST(r) do {} while (0) +#define REG_WIDTH_TEST(r, w) do {} while (0) +#endif + +/* OHCI compatible registers */ +/* + * Note: Some of the ISP1362 'OHCI' registers implement only + * a subset of the bits defined in the OHCI spec. + * + * Bitmasks for the individual bits of these registers are defined in "ohci.h" + */ +ISP1362_REG(HCREVISION, 0x00, REG_WIDTH_32, REG_ACCESS_R); +ISP1362_REG(HCCONTROL, 0x01, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCCMDSTAT, 0x02, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCINTSTAT, 0x03, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCINTENB, 0x04, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCINTDIS, 0x05, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCFMINTVL, 0x0d, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCFMREM, 0x0e, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCFMNUM, 0x0f, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCLSTHRESH, 0x11, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCRHDESCA, 0x12, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCRHDESCB, 0x13, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCRHSTATUS, 0x14, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCRHPORT1, 0x15, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCRHPORT2, 0x16, REG_WIDTH_32, REG_ACCESS_RW); + +/* Philips ISP1362 specific registers */ +ISP1362_REG(HCHWCFG, 0x20, REG_WIDTH_16, REG_ACCESS_RW); +#define HCHWCFG_DISABLE_SUSPEND (1 << 15) +#define HCHWCFG_GLOBAL_PWRDOWN (1 << 14) +#define HCHWCFG_PULLDOWN_DS2 (1 << 13) +#define HCHWCFG_PULLDOWN_DS1 (1 << 12) +#define HCHWCFG_CLKNOTSTOP (1 << 11) +#define HCHWCFG_ANALOG_OC (1 << 10) +#define HCHWCFG_ONEINT (1 << 9) +#define HCHWCFG_DACK_MODE (1 << 8) +#define HCHWCFG_ONEDMA (1 << 7) +#define HCHWCFG_DACK_POL (1 << 6) +#define HCHWCFG_DREQ_POL (1 << 5) +#define HCHWCFG_DBWIDTH_MASK (0x03 << 3) +#define HCHWCFG_DBWIDTH(n) (((n) << 3) & HCHWCFG_DBWIDTH_MASK) +#define HCHWCFG_INT_POL (1 << 2) +#define HCHWCFG_INT_TRIGGER (1 << 1) +#define HCHWCFG_INT_ENABLE (1 << 0) + +ISP1362_REG(HCDMACFG, 0x21, REG_WIDTH_16, REG_ACCESS_RW); +#define HCDMACFG_CTR_ENABLE (1 << 7) +#define HCDMACFG_BURST_LEN_MASK (0x03 << 5) +#define HCDMACFG_BURST_LEN(n) (((n) << 5) & HCDMACFG_BURST_LEN_MASK) +#define HCDMACFG_BURST_LEN_1 HCDMACFG_BURST_LEN(0) +#define HCDMACFG_BURST_LEN_4 HCDMACFG_BURST_LEN(1) +#define HCDMACFG_BURST_LEN_8 HCDMACFG_BURST_LEN(2) +#define HCDMACFG_DMA_ENABLE (1 << 4) +#define HCDMACFG_BUF_TYPE_MASK (0x07 << 1) +#define HCDMACFG_BUF_TYPE(n) (((n) << 1) & HCDMACFG_BUF_TYPE_MASK) +#define HCDMACFG_BUF_ISTL0 HCDMACFG_BUF_TYPE(0) +#define HCDMACFG_BUF_ISTL1 HCDMACFG_BUF_TYPE(1) +#define HCDMACFG_BUF_INTL HCDMACFG_BUF_TYPE(2) +#define HCDMACFG_BUF_ATL HCDMACFG_BUF_TYPE(3) +#define HCDMACFG_BUF_DIRECT HCDMACFG_BUF_TYPE(4) +#define HCDMACFG_DMA_RW_SELECT (1 << 0) + +ISP1362_REG(HCXFERCTR, 0x22, REG_WIDTH_16, REG_ACCESS_RW); + +ISP1362_REG(HCuPINT, 0x24, REG_WIDTH_16, REG_ACCESS_RW); +#define HCuPINT_SOF (1 << 0) +#define HCuPINT_ISTL0 (1 << 1) +#define HCuPINT_ISTL1 (1 << 2) +#define HCuPINT_EOT (1 << 3) +#define HCuPINT_OPR (1 << 4) +#define HCuPINT_SUSP (1 << 5) +#define HCuPINT_CLKRDY (1 << 6) +#define HCuPINT_INTL (1 << 7) +#define HCuPINT_ATL (1 << 8) +#define HCuPINT_OTG (1 << 9) + +ISP1362_REG(HCuPINTENB, 0x25, REG_WIDTH_16, REG_ACCESS_RW); +/* same bit definitions apply as for HCuPINT */ + +ISP1362_REG(HCCHIPID, 0x27, REG_WIDTH_16, REG_ACCESS_R); +#define HCCHIPID_MASK 0xff00 +#define HCCHIPID_MAGIC 0x3600 + +ISP1362_REG(HCSCRATCH, 0x28, REG_WIDTH_16, REG_ACCESS_RW); + +ISP1362_REG(HCSWRES, 0x29, REG_WIDTH_16, REG_ACCESS_W); +#define HCSWRES_MAGIC 0x00f6 + +ISP1362_REG(HCBUFSTAT, 0x2c, REG_WIDTH_16, REG_ACCESS_RW); +#define HCBUFSTAT_ISTL0_FULL (1 << 0) +#define HCBUFSTAT_ISTL1_FULL (1 << 1) +#define HCBUFSTAT_INTL_ACTIVE (1 << 2) +#define HCBUFSTAT_ATL_ACTIVE (1 << 3) +#define HCBUFSTAT_RESET_HWPP (1 << 4) +#define HCBUFSTAT_ISTL0_ACTIVE (1 << 5) +#define HCBUFSTAT_ISTL1_ACTIVE (1 << 6) +#define HCBUFSTAT_ISTL0_DONE (1 << 8) +#define HCBUFSTAT_ISTL1_DONE (1 << 9) +#define HCBUFSTAT_PAIRED_PTDPP (1 << 10) + +ISP1362_REG(HCDIRADDR, 0x32, REG_WIDTH_32, REG_ACCESS_RW); +#define HCDIRADDR_ADDR_MASK 0x0000ffff +#define HCDIRADDR_ADDR(n) (((n) << 0) & HCDIRADDR_ADDR_MASK) +#define HCDIRADDR_COUNT_MASK 0xffff0000 +#define HCDIRADDR_COUNT(n) (((n) << 16) & HCDIRADDR_COUNT_MASK) +ISP1362_REG(HCDIRDATA, 0x45, REG_WIDTH_16, REG_ACCESS_RW); + +ISP1362_REG(HCISTLBUFSZ, 0x30, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCISTL0PORT, 0x40, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCISTL1PORT, 0x42, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCISTLRATE, 0x47, REG_WIDTH_16, REG_ACCESS_RW); + +ISP1362_REG(HCINTLBUFSZ, 0x33, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCINTLPORT, 0x43, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCINTLBLKSZ, 0x53, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCINTLDONE, 0x17, REG_WIDTH_32, REG_ACCESS_R); +ISP1362_REG(HCINTLSKIP, 0x18, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCINTLLAST, 0x19, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCINTLCURR, 0x1a, REG_WIDTH_16, REG_ACCESS_R); + +ISP1362_REG(HCATLBUFSZ, 0x34, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCATLPORT, 0x44, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCATLBLKSZ, 0x54, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCATLDONE, 0x1b, REG_WIDTH_32, REG_ACCESS_R); +ISP1362_REG(HCATLSKIP, 0x1c, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCATLLAST, 0x1d, REG_WIDTH_32, REG_ACCESS_RW); +ISP1362_REG(HCATLCURR, 0x1e, REG_WIDTH_16, REG_ACCESS_R); + +ISP1362_REG(HCATLDTC, 0x51, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(HCATLDTCTO, 0x52, REG_WIDTH_16, REG_ACCESS_RW); + + +ISP1362_REG(OTGCONTROL, 0x62, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(OTGSTATUS, 0x67, REG_WIDTH_16, REG_ACCESS_R); +ISP1362_REG(OTGINT, 0x68, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(OTGINTENB, 0x69, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(OTGTIMER, 0x6A, REG_WIDTH_16, REG_ACCESS_RW); +ISP1362_REG(OTGALTTMR, 0x6C, REG_WIDTH_16, REG_ACCESS_RW); + +/* Philips transfer descriptor, cpu-endian */ +struct ptd { + u16 count; +#define PTD_COUNT_MSK (0x3ff << 0) +#define PTD_TOGGLE_MSK (1 << 10) +#define PTD_ACTIVE_MSK (1 << 11) +#define PTD_CC_MSK (0xf << 12) + u16 mps; +#define PTD_MPS_MSK (0x3ff << 0) +#define PTD_SPD_MSK (1 << 10) +#define PTD_LAST_MSK (1 << 11) +#define PTD_EP_MSK (0xf << 12) + u16 len; +#define PTD_LEN_MSK (0x3ff << 0) +#define PTD_DIR_MSK (3 << 10) +#define PTD_DIR_SETUP (0) +#define PTD_DIR_OUT (1) +#define PTD_DIR_IN (2) + u16 faddr; +#define PTD_FA_MSK (0x7f << 0) +/* PTD Byte 7: [StartingFrame (if ISO PTD) | StartingFrame[0..4], PollingRate[0..2] (if INT PTD)] */ +#define PTD_SF_ISO_MSK (0xff << 8) +#define PTD_SF_INT_MSK (0x1f << 8) +#define PTD_PR_MSK (0x07 << 13) +} __attribute__ ((packed, aligned(2))); +#define PTD_HEADER_SIZE sizeof(struct ptd) + +/* ------------------------------------------------------------------------- */ +/* Copied from ohci.h: */ +/* + * Hardware transfer status codes -- CC from PTD + */ +#define PTD_CC_NOERROR 0x00 +#define PTD_CC_CRC 0x01 +#define PTD_CC_BITSTUFFING 0x02 +#define PTD_CC_DATATOGGLEM 0x03 +#define PTD_CC_STALL 0x04 +#define PTD_DEVNOTRESP 0x05 +#define PTD_PIDCHECKFAIL 0x06 +#define PTD_UNEXPECTEDPID 0x07 +#define PTD_DATAOVERRUN 0x08 +#define PTD_DATAUNDERRUN 0x09 + /* 0x0A, 0x0B reserved for hardware */ +#define PTD_BUFFEROVERRUN 0x0C +#define PTD_BUFFERUNDERRUN 0x0D + /* 0x0E, 0x0F reserved for HCD */ +#define PTD_NOTACCESSED 0x0F + + +/* map OHCI TD status codes (CC) to errno values */ +static const int cc_to_error[16] = { + /* No Error */ 0, + /* CRC Error */ -EILSEQ, + /* Bit Stuff */ -EPROTO, + /* Data Togg */ -EILSEQ, + /* Stall */ -EPIPE, + /* DevNotResp */ -ETIMEDOUT, + /* PIDCheck */ -EPROTO, + /* UnExpPID */ -EPROTO, + /* DataOver */ -EOVERFLOW, + /* DataUnder */ -EREMOTEIO, + /* (for hw) */ -EIO, + /* (for hw) */ -EIO, + /* BufferOver */ -ECOMM, + /* BuffUnder */ -ENOSR, + /* (for HCD) */ -EALREADY, + /* (for HCD) */ -EALREADY +}; + + +/* + * HcControl (control) register masks + */ +#define OHCI_CTRL_HCFS (3 << 6) /* host controller functional state */ +#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */ +#define OHCI_CTRL_RWE (1 << 10) /* remote wakeup enable */ + +/* pre-shifted values for HCFS */ +# define OHCI_USB_RESET (0 << 6) +# define OHCI_USB_RESUME (1 << 6) +# define OHCI_USB_OPER (2 << 6) +# define OHCI_USB_SUSPEND (3 << 6) + +/* + * HcCommandStatus (cmdstatus) register masks + */ +#define OHCI_HCR (1 << 0) /* host controller reset */ +#define OHCI_SOC (3 << 16) /* scheduling overrun count */ + +/* + * masks used with interrupt registers: + * HcInterruptStatus (intrstatus) + * HcInterruptEnable (intrenable) + * HcInterruptDisable (intrdisable) + */ +#define OHCI_INTR_SO (1 << 0) /* scheduling overrun */ +#define OHCI_INTR_WDH (1 << 1) /* writeback of done_head */ +#define OHCI_INTR_SF (1 << 2) /* start frame */ +#define OHCI_INTR_RD (1 << 3) /* resume detect */ +#define OHCI_INTR_UE (1 << 4) /* unrecoverable error */ +#define OHCI_INTR_FNO (1 << 5) /* frame number overflow */ +#define OHCI_INTR_RHSC (1 << 6) /* root hub status change */ +#define OHCI_INTR_OC (1 << 30) /* ownership change */ +#define OHCI_INTR_MIE (1 << 31) /* master interrupt enable */ + +/* roothub.portstatus [i] bits */ +#define RH_PS_CCS 0x00000001 /* current connect status */ +#define RH_PS_PES 0x00000002 /* port enable status*/ +#define RH_PS_PSS 0x00000004 /* port suspend status */ +#define RH_PS_POCI 0x00000008 /* port over current indicator */ +#define RH_PS_PRS 0x00000010 /* port reset status */ +#define RH_PS_PPS 0x00000100 /* port power status */ +#define RH_PS_LSDA 0x00000200 /* low speed device attached */ +#define RH_PS_CSC 0x00010000 /* connect status change */ +#define RH_PS_PESC 0x00020000 /* port enable status change */ +#define RH_PS_PSSC 0x00040000 /* port suspend status change */ +#define RH_PS_OCIC 0x00080000 /* over current indicator change */ +#define RH_PS_PRSC 0x00100000 /* port reset status change */ + +/* roothub.status bits */ +#define RH_HS_LPS 0x00000001 /* local power status */ +#define RH_HS_OCI 0x00000002 /* over current indicator */ +#define RH_HS_DRWE 0x00008000 /* device remote wakeup enable */ +#define RH_HS_LPSC 0x00010000 /* local power status change */ +#define RH_HS_OCIC 0x00020000 /* over current indicator change */ +#define RH_HS_CRWE 0x80000000 /* clear remote wakeup enable */ + +/* roothub.b masks */ +#define RH_B_DR 0x0000ffff /* device removable flags */ +#define RH_B_PPCM 0xffff0000 /* port power control mask */ + +/* roothub.a masks */ +#define RH_A_NDP (0xff << 0) /* number of downstream ports */ +#define RH_A_PSM (1 << 8) /* power switching mode */ +#define RH_A_NPS (1 << 9) /* no power switching */ +#define RH_A_DT (1 << 10) /* device type (mbz) */ +#define RH_A_OCPM (1 << 11) /* over current protection mode */ +#define RH_A_NOCP (1 << 12) /* no over current protection */ +#define RH_A_POTPGT (0xff << 24) /* power on to power good time */ + +#define FI 0x2edf /* 12000 bits per frame (-1) */ +#define FSMP(fi) (0x7fff & ((6 * ((fi) - 210)) / 7)) +#define LSTHRESH 0x628 /* lowspeed bit threshold */ + +/* ------------------------------------------------------------------------- */ + +/* PTD accessor macros. */ +#define PTD_GET_COUNT(p) (((p)->count & PTD_COUNT_MSK) >> 0) +#define PTD_COUNT(v) (((v) << 0) & PTD_COUNT_MSK) +#define PTD_GET_TOGGLE(p) (((p)->count & PTD_TOGGLE_MSK) >> 10) +#define PTD_TOGGLE(v) (((v) << 10) & PTD_TOGGLE_MSK) +#define PTD_GET_ACTIVE(p) (((p)->count & PTD_ACTIVE_MSK) >> 11) +#define PTD_ACTIVE(v) (((v) << 11) & PTD_ACTIVE_MSK) +#define PTD_GET_CC(p) (((p)->count & PTD_CC_MSK) >> 12) +#define PTD_CC(v) (((v) << 12) & PTD_CC_MSK) +#define PTD_GET_MPS(p) (((p)->mps & PTD_MPS_MSK) >> 0) +#define PTD_MPS(v) (((v) << 0) & PTD_MPS_MSK) +#define PTD_GET_SPD(p) (((p)->mps & PTD_SPD_MSK) >> 10) +#define PTD_SPD(v) (((v) << 10) & PTD_SPD_MSK) +#define PTD_GET_LAST(p) (((p)->mps & PTD_LAST_MSK) >> 11) +#define PTD_LAST(v) (((v) << 11) & PTD_LAST_MSK) +#define PTD_GET_EP(p) (((p)->mps & PTD_EP_MSK) >> 12) +#define PTD_EP(v) (((v) << 12) & PTD_EP_MSK) +#define PTD_GET_LEN(p) (((p)->len & PTD_LEN_MSK) >> 0) +#define PTD_LEN(v) (((v) << 0) & PTD_LEN_MSK) +#define PTD_GET_DIR(p) (((p)->len & PTD_DIR_MSK) >> 10) +#define PTD_DIR(v) (((v) << 10) & PTD_DIR_MSK) +#define PTD_GET_FA(p) (((p)->faddr & PTD_FA_MSK) >> 0) +#define PTD_FA(v) (((v) << 0) & PTD_FA_MSK) +#define PTD_GET_SF_INT(p) (((p)->faddr & PTD_SF_INT_MSK) >> 8) +#define PTD_SF_INT(v) (((v) << 8) & PTD_SF_INT_MSK) +#define PTD_GET_SF_ISO(p) (((p)->faddr & PTD_SF_ISO_MSK) >> 8) +#define PTD_SF_ISO(v) (((v) << 8) & PTD_SF_ISO_MSK) +#define PTD_GET_PR(p) (((p)->faddr & PTD_PR_MSK) >> 13) +#define PTD_PR(v) (((v) << 13) & PTD_PR_MSK) + +#define LOG2_PERIODIC_SIZE 5 /* arbitrary; this matches OHCI */ +#define PERIODIC_SIZE (1 << LOG2_PERIODIC_SIZE) + +struct isp1362_ep { + struct usb_host_endpoint *hep; + struct usb_device *udev; + + /* philips transfer descriptor */ + struct ptd ptd; + + u8 maxpacket; + u8 epnum; + u8 nextpid; + u16 error_count; + u16 length; /* of current packet */ + s16 ptd_offset; /* buffer offset in ISP1362 where + PTD has been stored + (for access thru HCDIRDATA) */ + int ptd_index; + int num_ptds; + void *data; /* to databuf */ + /* queue of active EPs (the ones transmitted to the chip) */ + struct list_head active; + + /* periodic schedule */ + u8 branch; + u16 interval; + u16 load; + u16 last_iso; + + /* async schedule */ + struct list_head schedule; /* list of all EPs that need processing */ + struct list_head remove_list; + int num_req; +}; + +struct isp1362_ep_queue { + struct list_head active; /* list of PTDs currently processed by HC */ + atomic_t finishing; + unsigned long buf_map; + unsigned long skip_map; + int free_ptd; + u16 buf_start; + u16 buf_size; + u16 blk_size; /* PTD buffer block size for ATL and INTL */ + u8 buf_count; + u8 buf_avail; + char name[16]; + + /* for statistical tracking */ + u8 stat_maxptds; /* Max # of ptds seen simultaneously in fifo */ + u8 ptd_count; /* number of ptds submitted to this queue */ +}; + +struct isp1362_hcd { + spinlock_t lock; + void __iomem *addr_reg; + void __iomem *data_reg; + + struct isp1362_platform_data *board; + + struct proc_dir_entry *pde; + unsigned long stat1, stat2, stat4, stat8, stat16; + + /* HC registers */ + u32 intenb; /* "OHCI" interrupts */ + u16 irqenb; /* uP interrupts */ + + /* Root hub registers */ + u32 rhdesca; + u32 rhdescb; + u32 rhstatus; + u32 rhport[MAX_ROOT_PORTS]; + unsigned long next_statechange; + + /* HC control reg shadow copy */ + u32 hc_control; + + /* async schedule: control, bulk */ + struct list_head async; + + /* periodic schedule: int */ + u16 load[PERIODIC_SIZE]; + struct list_head periodic; + u16 fmindex; + + /* periodic schedule: isochronous */ + struct list_head isoc; + unsigned int istl_flip:1; + unsigned int irq_active:1; + + /* Schedules for the current frame */ + struct isp1362_ep_queue atl_queue; + struct isp1362_ep_queue intl_queue; + struct isp1362_ep_queue istl_queue[2]; + + /* list of PTDs retrieved from HC */ + struct list_head remove_list; + enum { + ISP1362_INT_SOF, + ISP1362_INT_ISTL0, + ISP1362_INT_ISTL1, + ISP1362_INT_EOT, + ISP1362_INT_OPR, + ISP1362_INT_SUSP, + ISP1362_INT_CLKRDY, + ISP1362_INT_INTL, + ISP1362_INT_ATL, + ISP1362_INT_OTG, + NUM_ISP1362_IRQS + } IRQ_NAMES; + unsigned int irq_stat[NUM_ISP1362_IRQS]; + int req_serial; +}; + +static inline const char *ISP1362_INT_NAME(int n) +{ + switch (n) { + case ISP1362_INT_SOF: return "SOF"; + case ISP1362_INT_ISTL0: return "ISTL0"; + case ISP1362_INT_ISTL1: return "ISTL1"; + case ISP1362_INT_EOT: return "EOT"; + case ISP1362_INT_OPR: return "OPR"; + case ISP1362_INT_SUSP: return "SUSP"; + case ISP1362_INT_CLKRDY: return "CLKRDY"; + case ISP1362_INT_INTL: return "INTL"; + case ISP1362_INT_ATL: return "ATL"; + case ISP1362_INT_OTG: return "OTG"; + default: return "unknown"; + } +} + +static inline void ALIGNSTAT(struct isp1362_hcd *isp1362_hcd, void *ptr) +{ + unsigned long p = (unsigned long)ptr; + if (!(p & 0xf)) + isp1362_hcd->stat16++; + else if (!(p & 0x7)) + isp1362_hcd->stat8++; + else if (!(p & 0x3)) + isp1362_hcd->stat4++; + else if (!(p & 0x1)) + isp1362_hcd->stat2++; + else + isp1362_hcd->stat1++; +} + +static inline struct isp1362_hcd *hcd_to_isp1362_hcd(struct usb_hcd *hcd) +{ + return (struct isp1362_hcd *) (hcd->hcd_priv); +} + +static inline struct usb_hcd *isp1362_hcd_to_hcd(struct isp1362_hcd *isp1362_hcd) +{ + return container_of((void *)isp1362_hcd, struct usb_hcd, hcd_priv); +} + +#define frame_before(f1, f2) ((s16)((u16)f1 - (u16)f2) < 0) + +/* + * ISP1362 HW Interface + */ + +#ifdef ISP1362_DEBUG +#define DBG(level, fmt...) \ + do { \ + if (dbg_level > level) \ + pr_debug(fmt); \ + } while (0) +#define _DBG(level, fmt...) \ + do { \ + if (dbg_level > level) \ + printk(fmt); \ + } while (0) +#else +#define DBG(fmt...) do {} while (0) +#define _DBG DBG +#endif + +#ifdef VERBOSE +# define VDBG(fmt...) DBG(3, fmt) +#else +# define VDBG(fmt...) do {} while (0) +#endif + +#ifdef REGISTERS +# define RDBG(fmt...) DBG(1, fmt) +#else +# define RDBG(fmt...) do {} while (0) +#endif + +#ifdef URB_TRACE +#define URB_DBG(fmt...) DBG(0, fmt) +#else +#define URB_DBG(fmt...) do {} while (0) +#endif + + +#if USE_PLATFORM_DELAY +#if USE_NDELAY +#error USE_PLATFORM_DELAY and USE_NDELAY defined simultaneously. +#endif +#define isp1362_delay(h, d) (h)->board->delay(isp1362_hcd_to_hcd(h)->self.controller, d) +#elif USE_NDELAY +#define isp1362_delay(h, d) ndelay(d) +#else +#define isp1362_delay(h, d) do {} while (0) +#endif + +#define get_urb(ep) ({ \ + BUG_ON(list_empty(&ep->hep->urb_list)); \ + container_of(ep->hep->urb_list.next, struct urb, urb_list); \ +}) + +/* basic access functions for ISP1362 chip registers */ +/* NOTE: The contents of the address pointer register cannot be read back! The driver must ensure, + * that all register accesses are performed with interrupts disabled, since the interrupt + * handler has no way of restoring the previous state. + */ +static void isp1362_write_addr(struct isp1362_hcd *isp1362_hcd, isp1362_reg_t reg) +{ + /*_BUG_ON((reg & ISP1362_REG_WRITE_OFFSET) && !(reg & REG_ACCESS_W));*/ + REG_ACCESS_TEST(reg); + _BUG_ON(!irqs_disabled()); + DUMMY_DELAY_ACCESS; + writew(ISP1362_REG_NO(reg), isp1362_hcd->addr_reg); + DUMMY_DELAY_ACCESS; + isp1362_delay(isp1362_hcd, 1); +} + +static void isp1362_write_data16(struct isp1362_hcd *isp1362_hcd, u16 val) +{ + _BUG_ON(!irqs_disabled()); + DUMMY_DELAY_ACCESS; + writew(val, isp1362_hcd->data_reg); +} + +static u16 isp1362_read_data16(struct isp1362_hcd *isp1362_hcd) +{ + u16 val; + + _BUG_ON(!irqs_disabled()); + DUMMY_DELAY_ACCESS; + val = readw(isp1362_hcd->data_reg); + + return val; +} + +static void isp1362_write_data32(struct isp1362_hcd *isp1362_hcd, u32 val) +{ + _BUG_ON(!irqs_disabled()); +#if USE_32BIT + DUMMY_DELAY_ACCESS; + writel(val, isp1362_hcd->data_reg); +#else + DUMMY_DELAY_ACCESS; + writew((u16)val, isp1362_hcd->data_reg); + DUMMY_DELAY_ACCESS; + writew(val >> 16, isp1362_hcd->data_reg); +#endif +} + +static u32 isp1362_read_data32(struct isp1362_hcd *isp1362_hcd) +{ + u32 val; + + _BUG_ON(!irqs_disabled()); +#if USE_32BIT + DUMMY_DELAY_ACCESS; + val = readl(isp1362_hcd->data_reg); +#else + DUMMY_DELAY_ACCESS; + val = (u32)readw(isp1362_hcd->data_reg); + DUMMY_DELAY_ACCESS; + val |= (u32)readw(isp1362_hcd->data_reg) << 16; +#endif + return val; +} + +/* use readsw/writesw to access the fifo whenever possible */ +/* assume HCDIRDATA or XFERCTR & addr_reg have been set up */ +static void isp1362_read_fifo(struct isp1362_hcd *isp1362_hcd, void *buf, u16 len) +{ + u8 *dp = buf; + u16 data; + + if (!len) + return; + + _BUG_ON(!irqs_disabled()); + + RDBG("%s: Reading %d byte from fifo to mem @ %p\n", __func__, len, buf); +#if USE_32BIT + if (len >= 4) { + RDBG("%s: Using readsl for %d dwords\n", __func__, len >> 2); + readsl(isp1362_hcd->data_reg, dp, len >> 2); + dp += len & ~3; + len &= 3; + } +#endif + if (len >= 2) { + RDBG("%s: Using readsw for %d words\n", __func__, len >> 1); + insw((unsigned long)isp1362_hcd->data_reg, dp, len >> 1); + dp += len & ~1; + len &= 1; + } + + BUG_ON(len & ~1); + if (len > 0) { + data = isp1362_read_data16(isp1362_hcd); + RDBG("%s: Reading trailing byte %02x to mem @ %08x\n", __func__, + (u8)data, (u32)dp); + *dp = (u8)data; + } +} + +static void isp1362_write_fifo(struct isp1362_hcd *isp1362_hcd, void *buf, u16 len) +{ + u8 *dp = buf; + u16 data; + + if (!len) + return; + + if ((unsigned long)dp & 0x1) { + /* not aligned */ + for (; len > 1; len -= 2) { + data = *dp++; + data |= *dp++ << 8; + isp1362_write_data16(isp1362_hcd, data); + } + if (len) + isp1362_write_data16(isp1362_hcd, *dp); + return; + } + + _BUG_ON(!irqs_disabled()); + + RDBG("%s: Writing %d byte to fifo from memory @%p\n", __func__, len, buf); +#if USE_32BIT + if (len >= 4) { + RDBG("%s: Using writesl for %d dwords\n", __func__, len >> 2); + writesl(isp1362_hcd->data_reg, dp, len >> 2); + dp += len & ~3; + len &= 3; + } +#endif + if (len >= 2) { + RDBG("%s: Using writesw for %d words\n", __func__, len >> 1); + outsw((unsigned long)isp1362_hcd->data_reg, dp, len >> 1); + dp += len & ~1; + len &= 1; + } + + BUG_ON(len & ~1); + if (len > 0) { + /* finally write any trailing byte; we don't need to care + * about the high byte of the last word written + */ + data = (u16)*dp; + RDBG("%s: Sending trailing byte %02x from mem @ %08x\n", __func__, + data, (u32)dp); + isp1362_write_data16(isp1362_hcd, data); + } +} + +#define isp1362_read_reg16(d, r) ({ \ + u16 __v; \ + REG_WIDTH_TEST(ISP1362_REG_##r, REG_WIDTH_16); \ + isp1362_write_addr(d, ISP1362_REG_##r); \ + __v = isp1362_read_data16(d); \ + RDBG("%s: Read %04x from %s[%02x]\n", __func__, __v, #r, \ + ISP1362_REG_NO(ISP1362_REG_##r)); \ + __v; \ +}) + +#define isp1362_read_reg32(d, r) ({ \ + u32 __v; \ + REG_WIDTH_TEST(ISP1362_REG_##r, REG_WIDTH_32); \ + isp1362_write_addr(d, ISP1362_REG_##r); \ + __v = isp1362_read_data32(d); \ + RDBG("%s: Read %08x from %s[%02x]\n", __func__, __v, #r, \ + ISP1362_REG_NO(ISP1362_REG_##r)); \ + __v; \ +}) + +#define isp1362_write_reg16(d, r, v) { \ + REG_WIDTH_TEST(ISP1362_REG_##r, REG_WIDTH_16); \ + isp1362_write_addr(d, (ISP1362_REG_##r) | ISP1362_REG_WRITE_OFFSET); \ + isp1362_write_data16(d, (u16)(v)); \ + RDBG("%s: Wrote %04x to %s[%02x]\n", __func__, (u16)(v), #r, \ + ISP1362_REG_NO(ISP1362_REG_##r)); \ +} + +#define isp1362_write_reg32(d, r, v) { \ + REG_WIDTH_TEST(ISP1362_REG_##r, REG_WIDTH_32); \ + isp1362_write_addr(d, (ISP1362_REG_##r) | ISP1362_REG_WRITE_OFFSET); \ + isp1362_write_data32(d, (u32)(v)); \ + RDBG("%s: Wrote %08x to %s[%02x]\n", __func__, (u32)(v), #r, \ + ISP1362_REG_NO(ISP1362_REG_##r)); \ +} + +#define isp1362_set_mask16(d, r, m) { \ + u16 __v; \ + __v = isp1362_read_reg16(d, r); \ + if ((__v | m) != __v) \ + isp1362_write_reg16(d, r, __v | m); \ +} + +#define isp1362_clr_mask16(d, r, m) { \ + u16 __v; \ + __v = isp1362_read_reg16(d, r); \ + if ((__v & ~m) != __v) \ + isp1362_write_reg16(d, r, __v & ~m); \ +} + +#define isp1362_set_mask32(d, r, m) { \ + u32 __v; \ + __v = isp1362_read_reg32(d, r); \ + if ((__v | m) != __v) \ + isp1362_write_reg32(d, r, __v | m); \ +} + +#define isp1362_clr_mask32(d, r, m) { \ + u32 __v; \ + __v = isp1362_read_reg32(d, r); \ + if ((__v & ~m) != __v) \ + isp1362_write_reg32(d, r, __v & ~m); \ +} + +#ifdef ISP1362_DEBUG +#define isp1362_show_reg(d, r) { \ + if ((ISP1362_REG_##r & REG_WIDTH_MASK) == REG_WIDTH_32) \ + DBG(0, "%-12s[%02x]: %08x\n", #r, \ + ISP1362_REG_NO(ISP1362_REG_##r), isp1362_read_reg32(d, r)); \ + else \ + DBG(0, "%-12s[%02x]: %04x\n", #r, \ + ISP1362_REG_NO(ISP1362_REG_##r), isp1362_read_reg16(d, r)); \ +} +#else +#define isp1362_show_reg(d, r) do {} while (0) +#endif + +static void __attribute__((__unused__)) isp1362_show_regs(struct isp1362_hcd *isp1362_hcd) +{ + isp1362_show_reg(isp1362_hcd, HCREVISION); + isp1362_show_reg(isp1362_hcd, HCCONTROL); + isp1362_show_reg(isp1362_hcd, HCCMDSTAT); + isp1362_show_reg(isp1362_hcd, HCINTSTAT); + isp1362_show_reg(isp1362_hcd, HCINTENB); + isp1362_show_reg(isp1362_hcd, HCFMINTVL); + isp1362_show_reg(isp1362_hcd, HCFMREM); + isp1362_show_reg(isp1362_hcd, HCFMNUM); + isp1362_show_reg(isp1362_hcd, HCLSTHRESH); + isp1362_show_reg(isp1362_hcd, HCRHDESCA); + isp1362_show_reg(isp1362_hcd, HCRHDESCB); + isp1362_show_reg(isp1362_hcd, HCRHSTATUS); + isp1362_show_reg(isp1362_hcd, HCRHPORT1); + isp1362_show_reg(isp1362_hcd, HCRHPORT2); + + isp1362_show_reg(isp1362_hcd, HCHWCFG); + isp1362_show_reg(isp1362_hcd, HCDMACFG); + isp1362_show_reg(isp1362_hcd, HCXFERCTR); + isp1362_show_reg(isp1362_hcd, HCuPINT); + + if (in_interrupt()) + DBG(0, "%-12s[%02x]: %04x\n", "HCuPINTENB", + ISP1362_REG_NO(ISP1362_REG_HCuPINTENB), isp1362_hcd->irqenb); + else + isp1362_show_reg(isp1362_hcd, HCuPINTENB); + isp1362_show_reg(isp1362_hcd, HCCHIPID); + isp1362_show_reg(isp1362_hcd, HCSCRATCH); + isp1362_show_reg(isp1362_hcd, HCBUFSTAT); + isp1362_show_reg(isp1362_hcd, HCDIRADDR); + /* Access would advance fifo + * isp1362_show_reg(isp1362_hcd, HCDIRDATA); + */ + isp1362_show_reg(isp1362_hcd, HCISTLBUFSZ); + isp1362_show_reg(isp1362_hcd, HCISTLRATE); + isp1362_show_reg(isp1362_hcd, HCINTLBUFSZ); + isp1362_show_reg(isp1362_hcd, HCINTLBLKSZ); + isp1362_show_reg(isp1362_hcd, HCINTLDONE); + isp1362_show_reg(isp1362_hcd, HCINTLSKIP); + isp1362_show_reg(isp1362_hcd, HCINTLLAST); + isp1362_show_reg(isp1362_hcd, HCINTLCURR); + isp1362_show_reg(isp1362_hcd, HCATLBUFSZ); + isp1362_show_reg(isp1362_hcd, HCATLBLKSZ); + /* only valid after ATL_DONE interrupt + * isp1362_show_reg(isp1362_hcd, HCATLDONE); + */ + isp1362_show_reg(isp1362_hcd, HCATLSKIP); + isp1362_show_reg(isp1362_hcd, HCATLLAST); + isp1362_show_reg(isp1362_hcd, HCATLCURR); + isp1362_show_reg(isp1362_hcd, HCATLDTC); + isp1362_show_reg(isp1362_hcd, HCATLDTCTO); +} + +static void isp1362_write_diraddr(struct isp1362_hcd *isp1362_hcd, u16 offset, u16 len) +{ + _BUG_ON(offset & 1); + _BUG_ON(offset >= ISP1362_BUF_SIZE); + _BUG_ON(len > ISP1362_BUF_SIZE); + _BUG_ON(offset + len > ISP1362_BUF_SIZE); + len = (len + 1) & ~1; + + isp1362_clr_mask16(isp1362_hcd, HCDMACFG, HCDMACFG_CTR_ENABLE); + isp1362_write_reg32(isp1362_hcd, HCDIRADDR, + HCDIRADDR_ADDR(offset) | HCDIRADDR_COUNT(len)); +} + +static void isp1362_read_buffer(struct isp1362_hcd *isp1362_hcd, void *buf, u16 offset, int len) +{ + _BUG_ON(offset & 1); + + isp1362_write_diraddr(isp1362_hcd, offset, len); + + DBG(3, "%s: Reading %d byte from buffer @%04x to memory @ %p\n", + __func__, len, offset, buf); + + isp1362_write_reg16(isp1362_hcd, HCuPINT, HCuPINT_EOT); + _WARN_ON((isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_EOT)); + + isp1362_write_addr(isp1362_hcd, ISP1362_REG_HCDIRDATA); + + isp1362_read_fifo(isp1362_hcd, buf, len); + _WARN_ON(!(isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_EOT)); + isp1362_write_reg16(isp1362_hcd, HCuPINT, HCuPINT_EOT); + _WARN_ON((isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_EOT)); +} + +static void isp1362_write_buffer(struct isp1362_hcd *isp1362_hcd, void *buf, u16 offset, int len) +{ + _BUG_ON(offset & 1); + + isp1362_write_diraddr(isp1362_hcd, offset, len); + + DBG(3, "%s: Writing %d byte to buffer @%04x from memory @ %p\n", + __func__, len, offset, buf); + + isp1362_write_reg16(isp1362_hcd, HCuPINT, HCuPINT_EOT); + _WARN_ON((isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_EOT)); + + isp1362_write_addr(isp1362_hcd, ISP1362_REG_HCDIRDATA | ISP1362_REG_WRITE_OFFSET); + isp1362_write_fifo(isp1362_hcd, buf, len); + + _WARN_ON(!(isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_EOT)); + isp1362_write_reg16(isp1362_hcd, HCuPINT, HCuPINT_EOT); + _WARN_ON((isp1362_read_reg16(isp1362_hcd, HCuPINT) & HCuPINT_EOT)); +} + +static void __attribute__((unused)) dump_data(char *buf, int len) +{ + if (dbg_level > 0) { + int k; + int lf = 0; + + for (k = 0; k < len; ++k) { + if (!lf) + DBG(0, "%04x:", k); + printk(" %02x", ((u8 *) buf)[k]); + lf = 1; + if (!k) + continue; + if (k % 16 == 15) { + printk("\n"); + lf = 0; + continue; + } + if (k % 8 == 7) + printk(" "); + if (k % 4 == 3) + printk(" "); + } + if (lf) + printk("\n"); + } +} + +#if defined(ISP1362_DEBUG) && defined(PTD_TRACE) + +static void dump_ptd(struct ptd *ptd) +{ + DBG(0, "EP %p: CC=%x EP=%d DIR=%x CNT=%d LEN=%d MPS=%d TGL=%x ACT=%x FA=%d SPD=%x SF=%x PR=%x LST=%x\n", + container_of(ptd, struct isp1362_ep, ptd), + PTD_GET_CC(ptd), PTD_GET_EP(ptd), PTD_GET_DIR(ptd), + PTD_GET_COUNT(ptd), PTD_GET_LEN(ptd), PTD_GET_MPS(ptd), + PTD_GET_TOGGLE(ptd), PTD_GET_ACTIVE(ptd), PTD_GET_FA(ptd), + PTD_GET_SPD(ptd), PTD_GET_SF_INT(ptd), PTD_GET_PR(ptd), PTD_GET_LAST(ptd)); + DBG(0, " %04x %04x %04x %04x\n", ptd->count, ptd->mps, ptd->len, ptd->faddr); +} + +static void dump_ptd_out_data(struct ptd *ptd, u8 *buf) +{ + if (dbg_level > 0) { + if (PTD_GET_DIR(ptd) != PTD_DIR_IN && PTD_GET_LEN(ptd)) { + DBG(0, "--out->\n"); + dump_data(buf, PTD_GET_LEN(ptd)); + } + } +} + +static void dump_ptd_in_data(struct ptd *ptd, u8 *buf) +{ + if (dbg_level > 0) { + if (PTD_GET_DIR(ptd) == PTD_DIR_IN && PTD_GET_COUNT(ptd)) { + DBG(0, "<--in--\n"); + dump_data(buf, PTD_GET_COUNT(ptd)); + } + DBG(0, "-----\n"); + } +} + +static void dump_ptd_queue(struct isp1362_ep_queue *epq) +{ + struct isp1362_ep *ep; + int dbg = dbg_level; + + dbg_level = 1; + list_for_each_entry(ep, &epq->active, active) { + dump_ptd(&ep->ptd); + dump_data(ep->data, ep->length); + } + dbg_level = dbg; +} +#else +#define dump_ptd(ptd) do {} while (0) +#define dump_ptd_in_data(ptd, buf) do {} while (0) +#define dump_ptd_out_data(ptd, buf) do {} while (0) +#define dump_ptd_data(ptd, buf) do {} while (0) +#define dump_ptd_queue(epq) do {} while (0) +#endif diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c new file mode 100644 index 00000000..2facee53 --- /dev/null +++ b/drivers/usb/host/isp1760-hcd.c @@ -0,0 +1,2267 @@ +/* + * Driver for the NXP ISP1760 chip + * + * However, the code might contain some bugs. What doesn't work for sure is: + * - ISO + * - OTG + e The interrupt line is configured as active low, level. + * + * (c) 2007 Sebastian Siewior + * + * (c) 2011 Arvid Brodin + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-hcd.h" + +static struct kmem_cache *qtd_cachep; +static struct kmem_cache *qh_cachep; +static struct kmem_cache *urb_listitem_cachep; + +enum queue_head_types { + QH_CONTROL, + QH_BULK, + QH_INTERRUPT, + QH_END +}; + +struct isp1760_hcd { + u32 hcs_params; + spinlock_t lock; + struct slotinfo atl_slots[32]; + int atl_done_map; + struct slotinfo int_slots[32]; + int int_done_map; + struct memory_chunk memory_pool[BLOCKS]; + struct list_head qh_list[QH_END]; + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 + unsigned periodic_size; + unsigned i_thresh; + unsigned long reset_done; + unsigned long next_statechange; + unsigned int devflags; + + int rst_gpio; +}; + +static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) +{ + return (struct isp1760_hcd *) (hcd->hcd_priv); +} + +/* Section 2.2 Host Controller Capability Registers */ +#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ +#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ +#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ +#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ + +/* Section 2.3 Host Controller Operational Registers */ +#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ +#define STS_PCD (1<<2) /* port change detect */ +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC) + +struct isp1760_qtd { + u8 packet_type; + void *data_buffer; + u32 payload_addr; + + /* the rest is HCD-private */ + struct list_head qtd_list; + struct urb *urb; + size_t length; + size_t actual_length; + + /* QTD_ENQUEUED: waiting for transfer (inactive) */ + /* QTD_PAYLOAD_ALLOC: chip mem has been allocated for payload */ + /* QTD_XFER_STARTED: valid ptd has been written to isp176x - only + interrupt handler may touch this qtd! */ + /* QTD_XFER_COMPLETE: payload has been transferred successfully */ + /* QTD_RETIRE: transfer error/abort qtd */ +#define QTD_ENQUEUED 0 +#define QTD_PAYLOAD_ALLOC 1 +#define QTD_XFER_STARTED 2 +#define QTD_XFER_COMPLETE 3 +#define QTD_RETIRE 4 + u32 status; +}; + +/* Queue head, one for each active endpoint */ +struct isp1760_qh { + struct list_head qh_list; + struct list_head qtd_list; + u32 toggle; + u32 ping; + int slot; + int tt_buffer_dirty; /* See USB2.0 spec section 11.17.5 */ +}; + +struct urb_listitem { + struct list_head urb_list; + struct urb *urb; +}; + +/* + * Access functions for isp176x registers (addresses 0..0x03FF). + */ +static u32 reg_read32(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static void reg_write32(void __iomem *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +/* + * Access functions for isp176x memory (offset >= 0x0400). + * + * bank_reads8() reads memory locations prefetched by an earlier write to + * HC_MEMORY_REG (see isp176x datasheet). Unless you want to do fancy multi- + * bank optimizations, you should use the more generic mem_reads8() below. + * + * For access to ptd memory, use the specialized ptd_read() and ptd_write() + * below. + * + * These functions copy via MMIO data to/from the device. memcpy_{to|from}io() + * doesn't quite work because some people have to enforce 32-bit access + */ +static void bank_reads8(void __iomem *src_base, u32 src_offset, u32 bank_addr, + __u32 *dst, u32 bytes) +{ + __u32 __iomem *src; + u32 val; + __u8 *src_byteptr; + __u8 *dst_byteptr; + + src = src_base + (bank_addr | src_offset); + + if (src_offset < PAYLOAD_OFFSET) { + while (bytes >= 4) { + *dst = le32_to_cpu(__raw_readl(src)); + bytes -= 4; + src++; + dst++; + } + } else { + while (bytes >= 4) { + *dst = __raw_readl(src); + bytes -= 4; + src++; + dst++; + } + } + + if (!bytes) + return; + + /* in case we have 3, 2 or 1 by left. The dst buffer may not be fully + * allocated. + */ + if (src_offset < PAYLOAD_OFFSET) + val = le32_to_cpu(__raw_readl(src)); + else + val = __raw_readl(src); + + dst_byteptr = (void *) dst; + src_byteptr = (void *) &val; + while (bytes > 0) { + *dst_byteptr = *src_byteptr; + dst_byteptr++; + src_byteptr++; + bytes--; + } +} + +static void mem_reads8(void __iomem *src_base, u32 src_offset, void *dst, + u32 bytes) +{ + reg_write32(src_base, HC_MEMORY_REG, src_offset + ISP_BANK(0)); + ndelay(90); + bank_reads8(src_base, src_offset, ISP_BANK(0), dst, bytes); +} + +static void mem_writes8(void __iomem *dst_base, u32 dst_offset, + __u32 const *src, u32 bytes) +{ + __u32 __iomem *dst; + + dst = dst_base + dst_offset; + + if (dst_offset < PAYLOAD_OFFSET) { + while (bytes >= 4) { + __raw_writel(cpu_to_le32(*src), dst); + bytes -= 4; + src++; + dst++; + } + } else { + while (bytes >= 4) { + __raw_writel(*src, dst); + bytes -= 4; + src++; + dst++; + } + } + + if (!bytes) + return; + /* in case we have 3, 2 or 1 bytes left. The buffer is allocated and the + * extra bytes should not be read by the HW. + */ + + if (dst_offset < PAYLOAD_OFFSET) + __raw_writel(cpu_to_le32(*src), dst); + else + __raw_writel(*src, dst); +} + +/* + * Read and write ptds. 'ptd_offset' should be one of ISO_PTD_OFFSET, + * INT_PTD_OFFSET, and ATL_PTD_OFFSET. 'slot' should be less than 32. + */ +static void ptd_read(void __iomem *base, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + reg_write32(base, HC_MEMORY_REG, + ISP_BANK(0) + ptd_offset + slot*sizeof(*ptd)); + ndelay(90); + bank_reads8(base, ptd_offset + slot*sizeof(*ptd), ISP_BANK(0), + (void *) ptd, sizeof(*ptd)); +} + +static void ptd_write(void __iomem *base, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + mem_writes8(base, ptd_offset + slot*sizeof(*ptd) + sizeof(ptd->dw0), + &ptd->dw1, 7*sizeof(ptd->dw1)); + /* Make sure dw0 gets written last (after other dw's and after payload) + since it contains the enable bit */ + wmb(); + mem_writes8(base, ptd_offset + slot*sizeof(*ptd), &ptd->dw0, + sizeof(ptd->dw0)); +} + + +/* memory management of the 60kb on the chip from 0x1000 to 0xffff */ +static void init_memory(struct isp1760_hcd *priv) +{ + int i, curr; + u32 payload_addr; + + payload_addr = PAYLOAD_OFFSET; + for (i = 0; i < BLOCK_1_NUM; i++) { + priv->memory_pool[i].start = payload_addr; + priv->memory_pool[i].size = BLOCK_1_SIZE; + priv->memory_pool[i].free = 1; + payload_addr += priv->memory_pool[i].size; + } + + curr = i; + for (i = 0; i < BLOCK_2_NUM; i++) { + priv->memory_pool[curr + i].start = payload_addr; + priv->memory_pool[curr + i].size = BLOCK_2_SIZE; + priv->memory_pool[curr + i].free = 1; + payload_addr += priv->memory_pool[curr + i].size; + } + + curr = i; + for (i = 0; i < BLOCK_3_NUM; i++) { + priv->memory_pool[curr + i].start = payload_addr; + priv->memory_pool[curr + i].size = BLOCK_3_SIZE; + priv->memory_pool[curr + i].free = 1; + payload_addr += priv->memory_pool[curr + i].size; + } + + WARN_ON(payload_addr - priv->memory_pool[0].start > PAYLOAD_AREA_SIZE); +} + +static void alloc_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int i; + + WARN_ON(qtd->payload_addr); + + if (!qtd->length) + return; + + for (i = 0; i < BLOCKS; i++) { + if (priv->memory_pool[i].size >= qtd->length && + priv->memory_pool[i].free) { + priv->memory_pool[i].free = 0; + qtd->payload_addr = priv->memory_pool[i].start; + return; + } + } +} + +static void free_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int i; + + if (!qtd->payload_addr) + return; + + for (i = 0; i < BLOCKS; i++) { + if (priv->memory_pool[i].start == qtd->payload_addr) { + WARN_ON(priv->memory_pool[i].free); + priv->memory_pool[i].free = 1; + qtd->payload_addr = 0; + return; + } + } + + dev_err(hcd->self.controller, "%s: Invalid pointer: %08x\n", + __func__, qtd->payload_addr); + WARN_ON(1); + qtd->payload_addr = 0; +} + +static int handshake(struct usb_hcd *hcd, u32 reg, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = reg_read32(hcd->regs, reg); + if (result == ~0) + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct usb_hcd *hcd) +{ + int retval; + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + u32 command = reg_read32(hcd->regs, HC_USBCMD); + + command |= CMD_RESET; + reg_write32(hcd->regs, HC_USBCMD, command); + hcd->state = HC_STATE_HALT; + priv->next_statechange = jiffies; + retval = handshake(hcd, HC_USBCMD, + CMD_RESET, 0, 250 * 1000); + return retval; +} + +static struct isp1760_qh *qh_alloc(gfp_t flags) +{ + struct isp1760_qh *qh; + + qh = kmem_cache_zalloc(qh_cachep, flags); + if (!qh) + return NULL; + + INIT_LIST_HEAD(&qh->qh_list); + INIT_LIST_HEAD(&qh->qtd_list); + qh->slot = -1; + + return qh; +} + +static void qh_free(struct isp1760_qh *qh) +{ + WARN_ON(!list_empty(&qh->qtd_list)); + WARN_ON(qh->slot > -1); + kmem_cache_free(qh_cachep, qh); +} + +/* one-time init, only for memory state */ +static int priv_init(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 hcc_params; + int i; + + spin_lock_init(&priv->lock); + + for (i = 0; i < QH_END; i++) + INIT_LIST_HEAD(&priv->qh_list[i]); + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + priv->periodic_size = DEFAULT_I_TDPS; + + /* controllers may cache some of the periodic schedule ... */ + hcc_params = reg_read32(hcd->regs, HC_HCCPARAMS); + /* full frame cache */ + if (HCC_ISOC_CACHE(hcc_params)) + priv->i_thresh = 8; + else /* N microframes cached */ + priv->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + return 0; +} + +static int isp1760_hc_setup(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int result; + u32 scratch, hwmode; + + /* low-level chip reset */ + if (gpio_is_valid(priv->rst_gpio)) { + unsigned int rst_lvl; + + rst_lvl = (priv->devflags & + ISP1760_FLAG_RESET_ACTIVE_HIGH) ? 1 : 0; + + gpio_set_value(priv->rst_gpio, rst_lvl); + mdelay(50); + gpio_set_value(priv->rst_gpio, !rst_lvl); + } + + /* Setup HW Mode Control: This assumes a level active-low interrupt */ + hwmode = HW_DATA_BUS_32BIT; + + if (priv->devflags & ISP1760_FLAG_BUS_WIDTH_16) + hwmode &= ~HW_DATA_BUS_32BIT; + if (priv->devflags & ISP1760_FLAG_ANALOG_OC) + hwmode |= HW_ANA_DIGI_OC; + if (priv->devflags & ISP1760_FLAG_DACK_POL_HIGH) + hwmode |= HW_DACK_POL_HIGH; + if (priv->devflags & ISP1760_FLAG_DREQ_POL_HIGH) + hwmode |= HW_DREQ_POL_HIGH; + if (priv->devflags & ISP1760_FLAG_INTR_POL_HIGH) + hwmode |= HW_INTR_HIGH_ACT; + if (priv->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) + hwmode |= HW_INTR_EDGE_TRIG; + + /* + * We have to set this first in case we're in 16-bit mode. + * Write it twice to ensure correct upper bits if switching + * to 16-bit mode. + */ + reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); + + reg_write32(hcd->regs, HC_SCRATCH_REG, 0xdeadbabe); + /* Change bus pattern */ + scratch = reg_read32(hcd->regs, HC_CHIP_ID_REG); + scratch = reg_read32(hcd->regs, HC_SCRATCH_REG); + if (scratch != 0xdeadbabe) { + dev_err(hcd->self.controller, "Scratch test failed.\n"); + return -ENODEV; + } + + /* pre reset */ + reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, 0); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + + /* reset */ + reg_write32(hcd->regs, HC_RESET_REG, SW_RESET_RESET_ALL); + mdelay(100); + + reg_write32(hcd->regs, HC_RESET_REG, SW_RESET_RESET_HC); + mdelay(100); + + result = ehci_reset(hcd); + if (result) + return result; + + /* Step 11 passed */ + + dev_info(hcd->self.controller, "bus width: %d, oc: %s\n", + (priv->devflags & ISP1760_FLAG_BUS_WIDTH_16) ? + 16 : 32, (priv->devflags & ISP1760_FLAG_ANALOG_OC) ? + "analog" : "digital"); + + /* ATL reset */ + reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET); + mdelay(10); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); + + reg_write32(hcd->regs, HC_INTERRUPT_ENABLE, INTERRUPT_ENABLE_MASK); + + /* + * PORT 1 Control register of the ISP1760 is the OTG control + * register on ISP1761. Since there is no OTG or device controller + * support in this driver, we use port 1 as a "normal" USB host port on + * both chips. + */ + reg_write32(hcd->regs, HC_PORT1_CTRL, PORT1_POWER | PORT1_INIT2); + mdelay(10); + + priv->hcs_params = reg_read32(hcd->regs, HC_HCSPARAMS); + + return priv_init(hcd); +} + +static u32 base_to_chip(u32 base) +{ + return ((base - 0x400) >> 3); +} + +static int last_qtd_of_urb(struct isp1760_qtd *qtd, struct isp1760_qh *qh) +{ + struct urb *urb; + + if (list_is_last(&qtd->qtd_list, &qh->qtd_list)) + return 1; + + urb = qtd->urb; + qtd = list_entry(qtd->qtd_list.next, typeof(*qtd), qtd_list); + return (qtd->urb != urb); +} + +/* magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ + +static void create_ptd_atl(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + u32 maxpacket; + u32 multi; + u32 rl = RL_COUNTER; + u32 nak = NAK_COUNTER; + + memset(ptd, 0, sizeof(*ptd)); + + /* according to 3.6.2, max packet len can not be > 0x400 */ + maxpacket = usb_maxpacket(qtd->urb->dev, qtd->urb->pipe, + usb_pipeout(qtd->urb->pipe)); + multi = 1 + ((maxpacket >> 11) & 0x3); + maxpacket &= 0x7ff; + + /* DW0 */ + ptd->dw0 = DW0_VALID_BIT; + ptd->dw0 |= TO_DW0_LENGTH(qtd->length); + ptd->dw0 |= TO_DW0_MAXPACKET(maxpacket); + ptd->dw0 |= TO_DW0_ENDPOINT(usb_pipeendpoint(qtd->urb->pipe)); + + /* DW1 */ + ptd->dw1 = usb_pipeendpoint(qtd->urb->pipe) >> 1; + ptd->dw1 |= TO_DW1_DEVICE_ADDR(usb_pipedevice(qtd->urb->pipe)); + ptd->dw1 |= TO_DW1_PID_TOKEN(qtd->packet_type); + + if (usb_pipebulk(qtd->urb->pipe)) + ptd->dw1 |= DW1_TRANS_BULK; + else if (usb_pipeint(qtd->urb->pipe)) + ptd->dw1 |= DW1_TRANS_INT; + + if (qtd->urb->dev->speed != USB_SPEED_HIGH) { + /* split transaction */ + + ptd->dw1 |= DW1_TRANS_SPLIT; + if (qtd->urb->dev->speed == USB_SPEED_LOW) + ptd->dw1 |= DW1_SE_USB_LOSPEED; + + ptd->dw1 |= TO_DW1_PORT_NUM(qtd->urb->dev->ttport); + ptd->dw1 |= TO_DW1_HUB_NUM(qtd->urb->dev->tt->hub->devnum); + + /* SE bit for Split INT transfers */ + if (usb_pipeint(qtd->urb->pipe) && + (qtd->urb->dev->speed == USB_SPEED_LOW)) + ptd->dw1 |= 2 << 16; + + rl = 0; + nak = 0; + } else { + ptd->dw0 |= TO_DW0_MULTI(multi); + if (usb_pipecontrol(qtd->urb->pipe) || + usb_pipebulk(qtd->urb->pipe)) + ptd->dw3 |= TO_DW3_PING(qh->ping); + } + /* DW2 */ + ptd->dw2 = 0; + ptd->dw2 |= TO_DW2_DATA_START_ADDR(base_to_chip(qtd->payload_addr)); + ptd->dw2 |= TO_DW2_RL(rl); + + /* DW3 */ + ptd->dw3 |= TO_DW3_NAKCOUNT(nak); + ptd->dw3 |= TO_DW3_DATA_TOGGLE(qh->toggle); + if (usb_pipecontrol(qtd->urb->pipe)) { + if (qtd->data_buffer == qtd->urb->setup_packet) + ptd->dw3 &= ~TO_DW3_DATA_TOGGLE(1); + else if (last_qtd_of_urb(qtd, qh)) + ptd->dw3 |= TO_DW3_DATA_TOGGLE(1); + } + + ptd->dw3 |= DW3_ACTIVE_BIT; + /* Cerr */ + ptd->dw3 |= TO_DW3_CERR(ERR_COUNTER); +} + +static void transform_add_int(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + u32 usof; + u32 period; + + /* + * Most of this is guessing. ISP1761 datasheet is quite unclear, and + * the algorithm from the original Philips driver code, which was + * pretty much used in this driver before as well, is quite horrendous + * and, i believe, incorrect. The code below follows the datasheet and + * USB2.0 spec as far as I can tell, and plug/unplug seems to be much + * more reliable this way (fingers crossed...). + */ + + if (qtd->urb->dev->speed == USB_SPEED_HIGH) { + /* urb->interval is in units of microframes (1/8 ms) */ + period = qtd->urb->interval >> 3; + + if (qtd->urb->interval > 4) + usof = 0x01; /* One bit set => + interval 1 ms * uFrame-match */ + else if (qtd->urb->interval > 2) + usof = 0x22; /* Two bits set => interval 1/2 ms */ + else if (qtd->urb->interval > 1) + usof = 0x55; /* Four bits set => interval 1/4 ms */ + else + usof = 0xff; /* All bits set => interval 1/8 ms */ + } else { + /* urb->interval is in units of frames (1 ms) */ + period = qtd->urb->interval; + usof = 0x0f; /* Execute Start Split on any of the + four first uFrames */ + + /* + * First 8 bits in dw5 is uSCS and "specifies which uSOF the + * complete split needs to be sent. Valid only for IN." Also, + * "All bits can be set to one for every transfer." (p 82, + * ISP1761 data sheet.) 0x1c is from Philips driver. Where did + * that number come from? 0xff seems to work fine... + */ + /* ptd->dw5 = 0x1c; */ + ptd->dw5 = 0xff; /* Execute Complete Split on any uFrame */ + } + + period = period >> 1;/* Ensure equal or shorter period than requested */ + period &= 0xf8; /* Mask off too large values and lowest unused 3 bits */ + + ptd->dw2 |= period; + ptd->dw4 = usof; +} + +static void create_ptd_int(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + create_ptd_atl(qh, qtd, ptd); + transform_add_int(qh, qtd, ptd); +} + +static void isp1760_urb_done(struct usb_hcd *hcd, struct urb *urb) +__releases(priv->lock) +__acquires(priv->lock) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (!urb->unlinked) { + if (urb->status == -EINPROGRESS) + urb->status = 0; + } + + if (usb_pipein(urb->pipe) && usb_pipetype(urb->pipe) != PIPE_CONTROL) { + void *ptr; + for (ptr = urb->transfer_buffer; + ptr < urb->transfer_buffer + urb->transfer_buffer_length; + ptr += PAGE_SIZE) + flush_dcache_page(virt_to_page(ptr)); + } + + /* complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock(&priv->lock); + usb_hcd_giveback_urb(hcd, urb, urb->status); + spin_lock(&priv->lock); +} + +static struct isp1760_qtd *qtd_alloc(gfp_t flags, struct urb *urb, + u8 packet_type) +{ + struct isp1760_qtd *qtd; + + qtd = kmem_cache_zalloc(qtd_cachep, flags); + if (!qtd) + return NULL; + + INIT_LIST_HEAD(&qtd->qtd_list); + qtd->urb = urb; + qtd->packet_type = packet_type; + qtd->status = QTD_ENQUEUED; + qtd->actual_length = 0; + + return qtd; +} + +static void qtd_free(struct isp1760_qtd *qtd) +{ + WARN_ON(qtd->payload_addr); + kmem_cache_free(qtd_cachep, qtd); +} + +static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, + struct slotinfo *slots, struct isp1760_qtd *qtd, + struct isp1760_qh *qh, struct ptd *ptd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int skip_map; + + WARN_ON((slot < 0) || (slot > 31)); + WARN_ON(qtd->length && !qtd->payload_addr); + WARN_ON(slots[slot].qtd); + WARN_ON(slots[slot].qh); + WARN_ON(qtd->status != QTD_PAYLOAD_ALLOC); + + /* Make sure done map has not triggered from some unlinked transfer */ + if (ptd_offset == ATL_PTD_OFFSET) { + priv->atl_done_map |= reg_read32(hcd->regs, + HC_ATL_PTD_DONEMAP_REG); + priv->atl_done_map &= ~(1 << slot); + } else { + priv->int_done_map |= reg_read32(hcd->regs, + HC_INT_PTD_DONEMAP_REG); + priv->int_done_map &= ~(1 << slot); + } + + qh->slot = slot; + qtd->status = QTD_XFER_STARTED; + slots[slot].timestamp = jiffies; + slots[slot].qtd = qtd; + slots[slot].qh = qh; + ptd_write(hcd->regs, ptd_offset, slot, ptd); + + if (ptd_offset == ATL_PTD_OFFSET) { + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + skip_map &= ~(1 << qh->slot); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); + } else { + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + skip_map &= ~(1 << qh->slot); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); + } +} + +static int is_short_bulk(struct isp1760_qtd *qtd) +{ + return (usb_pipebulk(qtd->urb->pipe) && + (qtd->actual_length < qtd->length)); +} + +static void collect_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct list_head *urb_list) +{ + int last_qtd; + struct isp1760_qtd *qtd, *qtd_next; + struct urb_listitem *urb_listitem; + + list_for_each_entry_safe(qtd, qtd_next, &qh->qtd_list, qtd_list) { + if (qtd->status < QTD_XFER_COMPLETE) + break; + + last_qtd = last_qtd_of_urb(qtd, qh); + + if ((!last_qtd) && (qtd->status == QTD_RETIRE)) + qtd_next->status = QTD_RETIRE; + + if (qtd->status == QTD_XFER_COMPLETE) { + if (qtd->actual_length) { + switch (qtd->packet_type) { + case IN_PID: + mem_reads8(hcd->regs, qtd->payload_addr, + qtd->data_buffer, + qtd->actual_length); + /* Fall through (?) */ + case OUT_PID: + qtd->urb->actual_length += + qtd->actual_length; + /* Fall through ... */ + case SETUP_PID: + break; + } + } + + if (is_short_bulk(qtd)) { + if (qtd->urb->transfer_flags & URB_SHORT_NOT_OK) + qtd->urb->status = -EREMOTEIO; + if (!last_qtd) + qtd_next->status = QTD_RETIRE; + } + } + + if (qtd->payload_addr) + free_mem(hcd, qtd); + + if (last_qtd) { + if ((qtd->status == QTD_RETIRE) && + (qtd->urb->status == -EINPROGRESS)) + qtd->urb->status = -EPIPE; + /* Defer calling of urb_done() since it releases lock */ + urb_listitem = kmem_cache_zalloc(urb_listitem_cachep, + GFP_ATOMIC); + if (unlikely(!urb_listitem)) + break; /* Try again on next call */ + urb_listitem->urb = qtd->urb; + list_add_tail(&urb_listitem->urb_list, urb_list); + } + + list_del(&qtd->qtd_list); + qtd_free(qtd); + } +} + +#define ENQUEUE_DEPTH 2 +static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int ptd_offset; + struct slotinfo *slots; + int curr_slot, free_slot; + int n; + struct ptd ptd; + struct isp1760_qtd *qtd; + + if (unlikely(list_empty(&qh->qtd_list))) { + WARN_ON(1); + return; + } + + /* Make sure this endpoint's TT buffer is clean before queueing ptds */ + if (qh->tt_buffer_dirty) + return; + + if (usb_pipeint(list_entry(qh->qtd_list.next, struct isp1760_qtd, + qtd_list)->urb->pipe)) { + ptd_offset = INT_PTD_OFFSET; + slots = priv->int_slots; + } else { + ptd_offset = ATL_PTD_OFFSET; + slots = priv->atl_slots; + } + + free_slot = -1; + for (curr_slot = 0; curr_slot < 32; curr_slot++) { + if ((free_slot == -1) && (slots[curr_slot].qtd == NULL)) + free_slot = curr_slot; + if (slots[curr_slot].qh == qh) + break; + } + + n = 0; + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { + if (qtd->status == QTD_ENQUEUED) { + WARN_ON(qtd->payload_addr); + alloc_mem(hcd, qtd); + if ((qtd->length) && (!qtd->payload_addr)) + break; + + if ((qtd->length) && + ((qtd->packet_type == SETUP_PID) || + (qtd->packet_type == OUT_PID))) { + mem_writes8(hcd->regs, qtd->payload_addr, + qtd->data_buffer, qtd->length); + } + + qtd->status = QTD_PAYLOAD_ALLOC; + } + + if (qtd->status == QTD_PAYLOAD_ALLOC) { +/* + if ((curr_slot > 31) && (free_slot == -1)) + dev_dbg(hcd->self.controller, "%s: No slot " + "available for transfer\n", __func__); +*/ + /* Start xfer for this endpoint if not already done */ + if ((curr_slot > 31) && (free_slot > -1)) { + if (usb_pipeint(qtd->urb->pipe)) + create_ptd_int(qh, qtd, &ptd); + else + create_ptd_atl(qh, qtd, &ptd); + + start_bus_transfer(hcd, ptd_offset, free_slot, + slots, qtd, qh, &ptd); + curr_slot = free_slot; + } + + n++; + if (n >= ENQUEUE_DEPTH) + break; + } + } +} + +static void schedule_ptds(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv; + struct isp1760_qh *qh, *qh_next; + struct list_head *ep_queue; + LIST_HEAD(urb_list); + struct urb_listitem *urb_listitem, *urb_listitem_next; + int i; + + if (!hcd) { + WARN_ON(1); + return; + } + + priv = hcd_to_priv(hcd); + + /* + * check finished/retired xfers, transfer payloads, call urb_done() + */ + for (i = 0; i < QH_END; i++) { + ep_queue = &priv->qh_list[i]; + list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) { + collect_qtds(hcd, qh, &urb_list); + if (list_empty(&qh->qtd_list)) + list_del(&qh->qh_list); + } + } + + list_for_each_entry_safe(urb_listitem, urb_listitem_next, &urb_list, + urb_list) { + isp1760_urb_done(hcd, urb_listitem->urb); + kmem_cache_free(urb_listitem_cachep, urb_listitem); + } + + /* + * Schedule packets for transfer. + * + * According to USB2.0 specification: + * + * 1st prio: interrupt xfers, up to 80 % of bandwidth + * 2nd prio: control xfers + * 3rd prio: bulk xfers + * + * ... but let's use a simpler scheme here (mostly because ISP1761 doc + * is very unclear on how to prioritize traffic): + * + * 1) Enqueue any queued control transfers, as long as payload chip mem + * and PTD ATL slots are available. + * 2) Enqueue any queued INT transfers, as long as payload chip mem + * and PTD INT slots are available. + * 3) Enqueue any queued bulk transfers, as long as payload chip mem + * and PTD ATL slots are available. + * + * Use double buffering (ENQUEUE_DEPTH==2) as a compromise between + * conservation of chip mem and performance. + * + * I'm sure this scheme could be improved upon! + */ + for (i = 0; i < QH_END; i++) { + ep_queue = &priv->qh_list[i]; + list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) + enqueue_qtds(hcd, qh); + } +} + +#define PTD_STATE_QTD_DONE 1 +#define PTD_STATE_QTD_RELOAD 2 +#define PTD_STATE_URB_RETIRE 3 + +static int check_int_transfer(struct usb_hcd *hcd, struct ptd *ptd, + struct urb *urb) +{ + __dw dw4; + int i; + + dw4 = ptd->dw4; + dw4 >>= 8; + + /* FIXME: ISP1761 datasheet does not say what to do with these. Do we + need to handle these errors? Is it done in hardware? */ + + if (ptd->dw3 & DW3_HALT_BIT) { + + urb->status = -EPROTO; /* Default unknown error */ + + for (i = 0; i < 8; i++) { + switch (dw4 & 0x7) { + case INT_UNDERRUN: + dev_dbg(hcd->self.controller, "%s: underrun " + "during uFrame %d\n", + __func__, i); + urb->status = -ECOMM; /* Could not write data */ + break; + case INT_EXACT: + dev_dbg(hcd->self.controller, "%s: transaction " + "error during uFrame %d\n", + __func__, i); + urb->status = -EPROTO; /* timeout, bad CRC, PID + error etc. */ + break; + case INT_BABBLE: + dev_dbg(hcd->self.controller, "%s: babble " + "error during uFrame %d\n", + __func__, i); + urb->status = -EOVERFLOW; + break; + } + dw4 >>= 3; + } + + return PTD_STATE_URB_RETIRE; + } + + return PTD_STATE_QTD_DONE; +} + +static int check_atl_transfer(struct usb_hcd *hcd, struct ptd *ptd, + struct urb *urb) +{ + WARN_ON(!ptd); + if (ptd->dw3 & DW3_HALT_BIT) { + if (ptd->dw3 & DW3_BABBLE_BIT) + urb->status = -EOVERFLOW; + else if (FROM_DW3_CERR(ptd->dw3)) + urb->status = -EPIPE; /* Stall */ + else if (ptd->dw3 & DW3_ERROR_BIT) + urb->status = -EPROTO; /* XactErr */ + else + urb->status = -EPROTO; /* Unknown */ +/* + dev_dbg(hcd->self.controller, "%s: ptd error:\n" + " dw0: %08x dw1: %08x dw2: %08x dw3: %08x\n" + " dw4: %08x dw5: %08x dw6: %08x dw7: %08x\n", + __func__, + ptd->dw0, ptd->dw1, ptd->dw2, ptd->dw3, + ptd->dw4, ptd->dw5, ptd->dw6, ptd->dw7); +*/ + return PTD_STATE_URB_RETIRE; + } + + if ((ptd->dw3 & DW3_ERROR_BIT) && (ptd->dw3 & DW3_ACTIVE_BIT)) { + /* Transfer Error, *but* active and no HALT -> reload */ + dev_dbg(hcd->self.controller, "PID error; reloading ptd\n"); + return PTD_STATE_QTD_RELOAD; + } + + if (!FROM_DW3_NAKCOUNT(ptd->dw3) && (ptd->dw3 & DW3_ACTIVE_BIT)) { + /* + * NAKs are handled in HW by the chip. Usually if the + * device is not able to send data fast enough. + * This happens mostly on slower hardware. + */ + return PTD_STATE_QTD_RELOAD; + } + + return PTD_STATE_QTD_DONE; +} + +static void handle_done_ptds(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct ptd ptd; + struct isp1760_qh *qh; + int slot; + int state; + struct slotinfo *slots; + u32 ptd_offset; + struct isp1760_qtd *qtd; + int modified; + int skip_map; + + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + priv->int_done_map &= ~skip_map; + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + priv->atl_done_map &= ~skip_map; + + modified = priv->int_done_map || priv->atl_done_map; + + while (priv->int_done_map || priv->atl_done_map) { + if (priv->int_done_map) { + /* INT ptd */ + slot = __ffs(priv->int_done_map); + priv->int_done_map &= ~(1 << slot); + slots = priv->int_slots; + /* This should not trigger, and could be removed if + noone have any problems with it triggering: */ + if (!slots[slot].qh) { + WARN_ON(1); + continue; + } + ptd_offset = INT_PTD_OFFSET; + ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd); + state = check_int_transfer(hcd, &ptd, + slots[slot].qtd->urb); + } else { + /* ATL ptd */ + slot = __ffs(priv->atl_done_map); + priv->atl_done_map &= ~(1 << slot); + slots = priv->atl_slots; + /* This should not trigger, and could be removed if + noone have any problems with it triggering: */ + if (!slots[slot].qh) { + WARN_ON(1); + continue; + } + ptd_offset = ATL_PTD_OFFSET; + ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); + state = check_atl_transfer(hcd, &ptd, + slots[slot].qtd->urb); + } + + qtd = slots[slot].qtd; + slots[slot].qtd = NULL; + qh = slots[slot].qh; + slots[slot].qh = NULL; + qh->slot = -1; + + WARN_ON(qtd->status != QTD_XFER_STARTED); + + switch (state) { + case PTD_STATE_QTD_DONE: + if ((usb_pipeint(qtd->urb->pipe)) && + (qtd->urb->dev->speed != USB_SPEED_HIGH)) + qtd->actual_length = + FROM_DW3_SCS_NRBYTESTRANSFERRED(ptd.dw3); + else + qtd->actual_length = + FROM_DW3_NRBYTESTRANSFERRED(ptd.dw3); + + qtd->status = QTD_XFER_COMPLETE; + if (list_is_last(&qtd->qtd_list, &qh->qtd_list) || + is_short_bulk(qtd)) + qtd = NULL; + else + qtd = list_entry(qtd->qtd_list.next, + typeof(*qtd), qtd_list); + + qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); + qh->ping = FROM_DW3_PING(ptd.dw3); + break; + + case PTD_STATE_QTD_RELOAD: /* QTD_RETRY, for atls only */ + qtd->status = QTD_PAYLOAD_ALLOC; + ptd.dw0 |= DW0_VALID_BIT; + /* RL counter = ERR counter */ + ptd.dw3 &= ~TO_DW3_NAKCOUNT(0xf); + ptd.dw3 |= TO_DW3_NAKCOUNT(FROM_DW2_RL(ptd.dw2)); + ptd.dw3 &= ~TO_DW3_CERR(3); + ptd.dw3 |= TO_DW3_CERR(ERR_COUNTER); + qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); + qh->ping = FROM_DW3_PING(ptd.dw3); + break; + + case PTD_STATE_URB_RETIRE: + qtd->status = QTD_RETIRE; + if ((qtd->urb->dev->speed != USB_SPEED_HIGH) && + (qtd->urb->status != -EPIPE) && + (qtd->urb->status != -EREMOTEIO)) { + qh->tt_buffer_dirty = 1; + if (usb_hub_clear_tt_buffer(qtd->urb)) + /* Clear failed; let's hope things work + anyway */ + qh->tt_buffer_dirty = 0; + } + qtd = NULL; + qh->toggle = 0; + qh->ping = 0; + break; + + default: + WARN_ON(1); + continue; + } + + if (qtd && (qtd->status == QTD_PAYLOAD_ALLOC)) { + if (slots == priv->int_slots) { + if (state == PTD_STATE_QTD_RELOAD) + dev_err(hcd->self.controller, + "%s: PTD_STATE_QTD_RELOAD on " + "interrupt packet\n", __func__); + if (state != PTD_STATE_QTD_RELOAD) + create_ptd_int(qh, qtd, &ptd); + } else { + if (state != PTD_STATE_QTD_RELOAD) + create_ptd_atl(qh, qtd, &ptd); + } + + start_bus_transfer(hcd, ptd_offset, slot, slots, qtd, + qh, &ptd); + } + } + + if (modified) + schedule_ptds(hcd); +} + +static irqreturn_t isp1760_irq(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 imask; + irqreturn_t irqret = IRQ_NONE; + + spin_lock(&priv->lock); + + if (!(hcd->state & HC_STATE_RUNNING)) + goto leave; + + imask = reg_read32(hcd->regs, HC_INTERRUPT_REG); + if (unlikely(!imask)) + goto leave; + reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */ + + priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); + priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); + + handle_done_ptds(hcd); + + irqret = IRQ_HANDLED; +leave: + spin_unlock(&priv->lock); + + return irqret; +} + +/* + * Workaround for problem described in chip errata 2: + * + * Sometimes interrupts are not generated when ATL (not INT?) completion occurs. + * One solution suggested in the errata is to use SOF interrupts _instead_of_ + * ATL done interrupts (the "instead of" might be important since it seems + * enabling ATL interrupts also causes the chip to sometimes - rarely - "forget" + * to set the PTD's done bit in addition to not generating an interrupt!). + * + * So if we use SOF + ATL interrupts, we sometimes get stale PTDs since their + * done bit is not being set. This is bad - it blocks the endpoint until reboot. + * + * If we use SOF interrupts only, we get latency between ptd completion and the + * actual handling. This is very noticeable in testusb runs which takes several + * minutes longer without ATL interrupts. + * + * A better solution is to run the code below every SLOT_CHECK_PERIOD ms. If it + * finds active ATL slots which are older than SLOT_TIMEOUT ms, it checks the + * slot's ACTIVE and VALID bits. If these are not set, the ptd is considered + * completed and its done map bit is set. + * + * The values of SLOT_TIMEOUT and SLOT_CHECK_PERIOD have been arbitrarily chosen + * not to cause too much lag when this HW bug occurs, while still hopefully + * ensuring that the check does not falsely trigger. + */ +#define SLOT_TIMEOUT 300 +#define SLOT_CHECK_PERIOD 200 +static struct timer_list errata2_timer; + +static void errata2_function(unsigned long data) +{ + struct usb_hcd *hcd = (struct usb_hcd *) data; + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int slot; + struct ptd ptd; + unsigned long spinflags; + + spin_lock_irqsave(&priv->lock, spinflags); + + for (slot = 0; slot < 32; slot++) + if (priv->atl_slots[slot].qh && time_after(jiffies, + priv->atl_slots[slot].timestamp + + SLOT_TIMEOUT * HZ / 1000)) { + ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); + if (!FROM_DW0_VALID(ptd.dw0) && + !FROM_DW3_ACTIVE(ptd.dw3)) + priv->atl_done_map |= 1 << slot; + } + + if (priv->atl_done_map) + handle_done_ptds(hcd); + + spin_unlock_irqrestore(&priv->lock, spinflags); + + errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; + add_timer(&errata2_timer); +} + +static int isp1760_run(struct usb_hcd *hcd) +{ + int retval; + u32 temp; + u32 command; + u32 chipid; + + hcd->uses_new_polling = 1; + + hcd->state = HC_STATE_RUNNING; + + /* Set PTD interrupt AND & OR maps */ + reg_write32(hcd->regs, HC_ATL_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_ATL_IRQ_MASK_OR_REG, 0xffffffff); + reg_write32(hcd->regs, HC_INT_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_INT_IRQ_MASK_OR_REG, 0xffffffff); + reg_write32(hcd->regs, HC_ISO_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_ISO_IRQ_MASK_OR_REG, 0xffffffff); + /* step 23 passed */ + + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp | HW_GLOBAL_INTR_EN); + + command = reg_read32(hcd->regs, HC_USBCMD); + command &= ~(CMD_LRESET|CMD_RESET); + command |= CMD_RUN; + reg_write32(hcd->regs, HC_USBCMD, command); + + retval = handshake(hcd, HC_USBCMD, CMD_RUN, CMD_RUN, 250 * 1000); + if (retval) + return retval; + + /* + * XXX + * Spec says to write FLAG_CF as last config action, priv code grabs + * the semaphore while doing so. + */ + down_write(&ehci_cf_port_reset_rwsem); + reg_write32(hcd->regs, HC_CONFIGFLAG, FLAG_CF); + + retval = handshake(hcd, HC_CONFIGFLAG, FLAG_CF, FLAG_CF, 250 * 1000); + up_write(&ehci_cf_port_reset_rwsem); + if (retval) + return retval; + + init_timer(&errata2_timer); + errata2_timer.function = errata2_function; + errata2_timer.data = (unsigned long) hcd; + errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; + add_timer(&errata2_timer); + + chipid = reg_read32(hcd->regs, HC_CHIP_ID_REG); + dev_info(hcd->self.controller, "USB ISP %04x HW rev. %d started\n", + chipid & 0xffff, chipid >> 16); + + /* PTD Register Init Part 2, Step 28 */ + + /* Setup registers controlling PTD checking */ + reg_write32(hcd->regs, HC_ATL_PTD_LASTPTD_REG, 0x80000000); + reg_write32(hcd->regs, HC_INT_PTD_LASTPTD_REG, 0x80000000); + reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, + ATL_BUF_FILL | INT_BUF_FILL); + + /* GRR this is run-once init(), being done every time the HC starts. + * So long as they're part of class devices, we can't do it init() + * since the class device isn't created that early. + */ + return 0; +} + +static int qtd_fill(struct isp1760_qtd *qtd, void *databuffer, size_t len) +{ + qtd->data_buffer = databuffer; + + if (len > MAX_PAYLOAD_SIZE) + len = MAX_PAYLOAD_SIZE; + qtd->length = len; + + return qtd->length; +} + +static void qtd_list_free(struct list_head *qtd_list) +{ + struct isp1760_qtd *qtd, *qtd_next; + + list_for_each_entry_safe(qtd, qtd_next, qtd_list, qtd_list) { + list_del(&qtd->qtd_list); + qtd_free(qtd); + } +} + +/* + * Packetize urb->transfer_buffer into list of packets of size wMaxPacketSize. + * Also calculate the PID type (SETUP/IN/OUT) for each packet. + */ +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) +static void packetize_urb(struct usb_hcd *hcd, + struct urb *urb, struct list_head *head, gfp_t flags) +{ + struct isp1760_qtd *qtd; + void *buf; + int len, maxpacketsize; + u8 packet_type; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + + if (!urb->transfer_buffer && urb->transfer_buffer_length) { + /* XXX This looks like usb storage / SCSI bug */ + dev_err(hcd->self.controller, + "buf is null, dma is %08lx len is %d\n", + (long unsigned)urb->transfer_dma, + urb->transfer_buffer_length); + WARN_ON(1); + } + + if (usb_pipein(urb->pipe)) + packet_type = IN_PID; + else + packet_type = OUT_PID; + + if (usb_pipecontrol(urb->pipe)) { + qtd = qtd_alloc(flags, urb, SETUP_PID); + if (!qtd) + goto cleanup; + qtd_fill(qtd, urb->setup_packet, sizeof(struct usb_ctrlrequest)); + list_add_tail(&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (urb->transfer_buffer_length == 0) + packet_type = IN_PID; + } + + maxpacketsize = max_packet(usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe))); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + buf = urb->transfer_buffer; + len = urb->transfer_buffer_length; + + for (;;) { + int this_qtd_len; + + qtd = qtd_alloc(flags, urb, packet_type); + if (!qtd) + goto cleanup; + this_qtd_len = qtd_fill(qtd, buf, len); + list_add_tail(&qtd->qtd_list, head); + + len -= this_qtd_len; + buf += this_qtd_len; + + if (len <= 0) + break; + } + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (urb->transfer_buffer_length != 0) { + int one_more = 0; + + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + if (packet_type == IN_PID) + packet_type = OUT_PID; + else + packet_type = IN_PID; + } else if (usb_pipebulk(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % + maxpacketsize)) { + one_more = 1; + } + if (one_more) { + qtd = qtd_alloc(flags, urb, packet_type); + if (!qtd) + goto cleanup; + + /* never any data in such packets */ + qtd_fill(qtd, NULL, 0); + list_add_tail(&qtd->qtd_list, head); + } + } + + return; + +cleanup: + qtd_list_free(head); +} + +static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct list_head *ep_queue; + struct isp1760_qh *qh, *qhit; + unsigned long spinflags; + LIST_HEAD(new_qtds); + int retval; + int qh_in_queue; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ep_queue = &priv->qh_list[QH_CONTROL]; + break; + case PIPE_BULK: + ep_queue = &priv->qh_list[QH_BULK]; + break; + case PIPE_INTERRUPT: + if (urb->interval < 0) + return -EINVAL; + /* FIXME: Check bandwidth */ + ep_queue = &priv->qh_list[QH_INTERRUPT]; + break; + case PIPE_ISOCHRONOUS: + dev_err(hcd->self.controller, "%s: isochronous USB packets " + "not yet supported\n", + __func__); + return -EPIPE; + default: + dev_err(hcd->self.controller, "%s: unknown pipe type\n", + __func__); + return -EPIPE; + } + + if (usb_pipein(urb->pipe)) + urb->actual_length = 0; + + packetize_urb(hcd, urb, &new_qtds, mem_flags); + if (list_empty(&new_qtds)) + return -ENOMEM; + + retval = 0; + spin_lock_irqsave(&priv->lock, spinflags); + + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + retval = -ESHUTDOWN; + qtd_list_free(&new_qtds); + goto out; + } + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) { + qtd_list_free(&new_qtds); + goto out; + } + + qh = urb->ep->hcpriv; + if (qh) { + qh_in_queue = 0; + list_for_each_entry(qhit, ep_queue, qh_list) { + if (qhit == qh) { + qh_in_queue = 1; + break; + } + } + if (!qh_in_queue) + list_add_tail(&qh->qh_list, ep_queue); + } else { + qh = qh_alloc(GFP_ATOMIC); + if (!qh) { + retval = -ENOMEM; + usb_hcd_unlink_urb_from_ep(hcd, urb); + qtd_list_free(&new_qtds); + goto out; + } + list_add_tail(&qh->qh_list, ep_queue); + urb->ep->hcpriv = qh; + } + + list_splice_tail(&new_qtds, &qh->qtd_list); + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); + return retval; +} + +static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, + struct isp1760_qh *qh) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int skip_map; + + WARN_ON(qh->slot == -1); + + /* We need to forcefully reclaim the slot since some transfers never + return, e.g. interrupt transfers and NAKed bulk transfers. */ + if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe)) { + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + skip_map |= (1 << qh->slot); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); + priv->atl_slots[qh->slot].qh = NULL; + priv->atl_slots[qh->slot].qtd = NULL; + } else { + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + skip_map |= (1 << qh->slot); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); + priv->int_slots[qh->slot].qh = NULL; + priv->int_slots[qh->slot].qtd = NULL; + } + + qh->slot = -1; +} + +/* + * Retire the qtds beginning at 'qtd' and belonging all to the same urb, killing + * any active transfer belonging to the urb in the process. + */ +static void dequeue_urb_from_qtd(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd) +{ + struct urb *urb; + int urb_was_running; + + urb = qtd->urb; + urb_was_running = 0; + list_for_each_entry_from(qtd, &qh->qtd_list, qtd_list) { + if (qtd->urb != urb) + break; + + if (qtd->status >= QTD_XFER_STARTED) + urb_was_running = 1; + if (last_qtd_of_urb(qtd, qh) && + (qtd->status >= QTD_XFER_COMPLETE)) + urb_was_running = 0; + + if (qtd->status == QTD_XFER_STARTED) + kill_transfer(hcd, urb, qh); + qtd->status = QTD_RETIRE; + } + + if ((urb->dev->speed != USB_SPEED_HIGH) && urb_was_running) { + qh->tt_buffer_dirty = 1; + if (usb_hub_clear_tt_buffer(urb)) + /* Clear failed; let's hope things work anyway */ + qh->tt_buffer_dirty = 0; + } +} + +static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + unsigned long spinflags; + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + int retval = 0; + + spin_lock_irqsave(&priv->lock, spinflags); + retval = usb_hcd_check_unlink_urb(hcd, urb, status); + if (retval) + goto out; + + qh = urb->ep->hcpriv; + if (!qh) { + retval = -EINVAL; + goto out; + } + + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) + if (qtd->urb == urb) { + dequeue_urb_from_qtd(hcd, qh, qtd); + list_move(&qtd->qtd_list, &qh->qtd_list); + break; + } + + urb->status = status; + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); + return retval; +} + +static void isp1760_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + unsigned long spinflags; + struct isp1760_qh *qh, *qh_iter; + int i; + + spin_lock_irqsave(&priv->lock, spinflags); + + qh = ep->hcpriv; + if (!qh) + goto out; + + WARN_ON(!list_empty(&qh->qtd_list)); + + for (i = 0; i < QH_END; i++) + list_for_each_entry(qh_iter, &priv->qh_list[i], qh_list) + if (qh_iter == qh) { + list_del(&qh_iter->qh_list); + i = QH_END; + break; + } + qh_free(qh); + ep->hcpriv = NULL; + + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); +} + +static int isp1760_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 temp, status = 0; + u32 mask; + int retval = 1; + unsigned long flags; + + /* if !PM_RUNTIME, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(hcd->state)) + return 0; + + /* init status to no-changes */ + buf[0] = 0; + mask = PORT_CSC; + + spin_lock_irqsave(&priv->lock, flags); + temp = reg_read32(hcd->regs, HC_PORTSC1); + + if (temp & PORT_OWNER) { + if (temp & PORT_CSC) { + temp &= ~PORT_CSC; + reg_write32(hcd->regs, HC_PORTSC1, temp); + goto done; + } + } + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if ((temp & mask) != 0 + || ((temp & PORT_RESUME) != 0 + && time_after_eq(jiffies, + priv->reset_done))) { + buf [0] |= 1 << (0 + 1); + status = STS_PCD; + } + /* FIXME autosuspend idle root hubs */ +done: + spin_unlock_irqrestore(&priv->lock, flags); + return status ? retval : 0; +} + +static void isp1760_hub_descriptor(struct isp1760_hcd *priv, + struct usb_hub_descriptor *desc) +{ + int ports = HCS_N_PORTS(priv->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + /* priv 1.0, 2.3.9 says 20ms max */ + desc->bPwrOn2PwrGood = 10; + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + /* per-port overcurrent reporting */ + temp = 0x0008; + if (HCS_PPC(priv->hcs_params)) + /* per-port power control */ + temp |= 0x0001; + else + /* no power switching */ + temp |= 0x0002; + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) + +static int check_reset_complete(struct usb_hcd *hcd, int index, + int port_status) +{ + if (!(port_status & PORT_CONNECT)) + return port_status; + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + + dev_info(hcd->self.controller, + "port %d full speed --> companion\n", + index + 1); + + port_status |= PORT_OWNER; + port_status &= ~PORT_RWC_BITS; + reg_write32(hcd->regs, HC_PORTSC1, port_status); + + } else + dev_info(hcd->self.controller, "port %d high speed\n", + index + 1); + + return port_status; +} + +static int isp1760_hub_control(struct usb_hcd *hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int ports = HCS_N_PORTS(priv->hcs_params); + u32 temp, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave(&priv->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = reg_read32(hcd->regs, HC_PORTSC1); + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, khubd needs to be able to clear + * the port-change status bits (especially + * USB_PORT_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_PE); + break; + case USB_PORT_FEAT_C_ENABLE: + /* XXX error? */ + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + + if (temp & PORT_SUSPEND) { + if ((temp & PORT_PE) == 0) + goto error; + /* resume signaling for 20 msec */ + temp &= ~(PORT_RWC_BITS); + reg_write32(hcd->regs, HC_PORTSC1, + temp | PORT_RESUME); + priv->reset_done = jiffies + + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + /* we auto-clear this feature */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(priv->hcs_params)) + reg_write32(hcd->regs, HC_PORTSC1, + temp & ~PORT_POWER); + break; + case USB_PORT_FEAT_C_CONNECTION: + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_CSC); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + /* XXX error ?*/ + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + reg_read32(hcd->regs, HC_USBCMD); + break; + case GetHubDescriptor: + isp1760_hub_descriptor(priv, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset(buf, 0, 4); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = reg_read32(hcd->regs, HC_PORTSC1); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + dev_err(hcd->self.controller, "Port resume should be skipped.\n"); + + /* Remote Wakeup received? */ + if (!priv->reset_done) { + /* resume signaling for 20 msec */ + priv->reset_done = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&hcd->rh_timer, priv->reset_done); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + priv->reset_done)) { + status |= USB_PORT_STAT_C_SUSPEND << 16; + priv->reset_done = 0; + + /* stop resume signaling */ + temp = reg_read32(hcd->regs, HC_PORTSC1); + reg_write32(hcd->regs, HC_PORTSC1, + temp & ~(PORT_RWC_BITS | PORT_RESUME)); + retval = handshake(hcd, HC_PORTSC1, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + dev_err(hcd->self.controller, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && time_after_eq(jiffies, + priv->reset_done)) { + status |= USB_PORT_STAT_C_RESET << 16; + priv->reset_done = 0; + + /* force reset to complete */ + reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_RESET); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(hcd, HC_PORTSC1, + PORT_RESET, 0, 750); + if (retval != 0) { + dev_err(hcd->self.controller, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete(hcd, wIndex, + reg_read32(hcd->regs, HC_PORTSC1)); + } + /* + * Even if OWNER is set, there's no harm letting khubd + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_OWNER) + dev_err(hcd->self.controller, "PORT_OWNER is set\n"); + + if (temp & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + /* status may be from integrated TT */ + status |= USB_PORT_STAT_HIGH_SPEED; + } + if (temp & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + if (temp & (PORT_SUSPEND|PORT_RESUME)) + status |= USB_PORT_STAT_SUSPEND; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (temp & PORT_POWER) + status |= USB_PORT_STAT_POWER; + + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = reg_read32(hcd->regs, HC_PORTSC1); + if (temp & PORT_OWNER) + break; + +/* temp &= ~PORT_RWC_BITS; */ + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_PE); + break; + + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_SUSPEND); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(priv->hcs_params)) + reg_write32(hcd->regs, HC_PORTSC1, + temp | PORT_POWER); + break; + case USB_PORT_FEAT_RESET: + if (temp & PORT_RESUME) + goto error; + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT + && PORT_USB11(temp)) { + temp |= PORT_OWNER; + } else { + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + priv->reset_done = jiffies + + msecs_to_jiffies(50); + } + reg_write32(hcd->regs, HC_PORTSC1, temp); + break; + default: + goto error; + } + reg_read32(hcd->regs, HC_USBCMD); + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&priv->lock, flags); + return retval; +} + +static int isp1760_get_frame(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 fr; + + fr = reg_read32(hcd->regs, HC_FRINDEX); + return (fr >> 3) % priv->periodic_size; +} + +static void isp1760_stop(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 temp; + + del_timer(&errata2_timer); + + isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1, + NULL, 0); + mdelay(20); + + spin_lock_irq(&priv->lock); + ehci_reset(hcd); + /* Disable IRQ */ + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); + spin_unlock_irq(&priv->lock); + + reg_write32(hcd->regs, HC_CONFIGFLAG, 0); +} + +static void isp1760_shutdown(struct usb_hcd *hcd) +{ + u32 command, temp; + + isp1760_stop(hcd); + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); + + command = reg_read32(hcd->regs, HC_USBCMD); + command &= ~CMD_RUN; + reg_write32(hcd->regs, HC_USBCMD, command); +} + +static void isp1760_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct isp1760_qh *qh = ep->hcpriv; + unsigned long spinflags; + + if (!qh) + return; + + spin_lock_irqsave(&priv->lock, spinflags); + qh->tt_buffer_dirty = 0; + schedule_ptds(hcd); + spin_unlock_irqrestore(&priv->lock, spinflags); +} + + +static const struct hc_driver isp1760_hc_driver = { + .description = "isp1760-hcd", + .product_desc = "NXP ISP1760 USB Host Controller", + .hcd_priv_size = sizeof(struct isp1760_hcd), + .irq = isp1760_irq, + .flags = HCD_MEMORY | HCD_USB2, + .reset = isp1760_hc_setup, + .start = isp1760_run, + .stop = isp1760_stop, + .shutdown = isp1760_shutdown, + .urb_enqueue = isp1760_urb_enqueue, + .urb_dequeue = isp1760_urb_dequeue, + .endpoint_disable = isp1760_endpoint_disable, + .get_frame_number = isp1760_get_frame, + .hub_status_data = isp1760_hub_status_data, + .hub_control = isp1760_hub_control, + .clear_tt_buffer_complete = isp1760_clear_tt_buffer_complete, +}; + +int __init init_kmem_once(void) +{ + urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", + sizeof(struct urb_listitem), 0, SLAB_TEMPORARY | + SLAB_MEM_SPREAD, NULL); + + if (!urb_listitem_cachep) + return -ENOMEM; + + qtd_cachep = kmem_cache_create("isp1760_qtd", + sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY | + SLAB_MEM_SPREAD, NULL); + + if (!qtd_cachep) + return -ENOMEM; + + qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh), + 0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL); + + if (!qh_cachep) { + kmem_cache_destroy(qtd_cachep); + return -ENOMEM; + } + + return 0; +} + +void deinit_kmem_cache(void) +{ + kmem_cache_destroy(qtd_cachep); + kmem_cache_destroy(qh_cachep); + kmem_cache_destroy(urb_listitem_cachep); +} + +struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, + int irq, unsigned long irqflags, + int rst_gpio, + struct device *dev, const char *busname, + unsigned int devflags) +{ + struct usb_hcd *hcd; + struct isp1760_hcd *priv; + int ret; + + if (usb_disabled()) + return ERR_PTR(-ENODEV); + + /* prevent usb-core allocating DMA pages */ + dev->dma_mask = NULL; + + hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); + if (!hcd) + return ERR_PTR(-ENOMEM); + + priv = hcd_to_priv(hcd); + priv->devflags = devflags; + priv->rst_gpio = rst_gpio; + init_memory(priv); + hcd->regs = ioremap(res_start, res_len); + if (!hcd->regs) { + ret = -EIO; + goto err_put; + } + + hcd->irq = irq; + hcd->rsrc_start = res_start; + hcd->rsrc_len = res_len; + + ret = usb_add_hcd(hcd, irq, irqflags); + if (ret) + goto err_unmap; + + return hcd; + +err_unmap: + iounmap(hcd->regs); + +err_put: + usb_put_hcd(hcd); + + return ERR_PTR(ret); +} + +MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); +MODULE_AUTHOR("Sebastian Siewior "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h new file mode 100644 index 00000000..33dc79cc --- /dev/null +++ b/drivers/usb/host/isp1760-hcd.h @@ -0,0 +1,208 @@ +#ifndef _ISP1760_HCD_H_ +#define _ISP1760_HCD_H_ + +/* exports for if */ +struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, + int irq, unsigned long irqflags, + int rst_gpio, + struct device *dev, const char *busname, + unsigned int devflags); +int init_kmem_once(void); +void deinit_kmem_cache(void); + +/* EHCI capability registers */ +#define HC_CAPLENGTH 0x00 +#define HC_HCSPARAMS 0x04 +#define HC_HCCPARAMS 0x08 + +/* EHCI operational registers */ +#define HC_USBCMD 0x20 +#define HC_USBSTS 0x24 +#define HC_FRINDEX 0x2c +#define HC_CONFIGFLAG 0x60 +#define HC_PORTSC1 0x64 +#define HC_ISO_PTD_DONEMAP_REG 0x130 +#define HC_ISO_PTD_SKIPMAP_REG 0x134 +#define HC_ISO_PTD_LASTPTD_REG 0x138 +#define HC_INT_PTD_DONEMAP_REG 0x140 +#define HC_INT_PTD_SKIPMAP_REG 0x144 +#define HC_INT_PTD_LASTPTD_REG 0x148 +#define HC_ATL_PTD_DONEMAP_REG 0x150 +#define HC_ATL_PTD_SKIPMAP_REG 0x154 +#define HC_ATL_PTD_LASTPTD_REG 0x158 + +/* Configuration Register */ +#define HC_HW_MODE_CTRL 0x300 +#define ALL_ATX_RESET (1 << 31) +#define HW_ANA_DIGI_OC (1 << 15) +#define HW_DATA_BUS_32BIT (1 << 8) +#define HW_DACK_POL_HIGH (1 << 6) +#define HW_DREQ_POL_HIGH (1 << 5) +#define HW_INTR_HIGH_ACT (1 << 2) +#define HW_INTR_EDGE_TRIG (1 << 1) +#define HW_GLOBAL_INTR_EN (1 << 0) + +#define HC_CHIP_ID_REG 0x304 +#define HC_SCRATCH_REG 0x308 + +#define HC_RESET_REG 0x30c +#define SW_RESET_RESET_HC (1 << 1) +#define SW_RESET_RESET_ALL (1 << 0) + +#define HC_BUFFER_STATUS_REG 0x334 +#define ISO_BUF_FILL (1 << 2) +#define INT_BUF_FILL (1 << 1) +#define ATL_BUF_FILL (1 << 0) + +#define HC_MEMORY_REG 0x33c +#define ISP_BANK(x) ((x) << 16) + +#define HC_PORT1_CTRL 0x374 +#define PORT1_POWER (3 << 3) +#define PORT1_INIT1 (1 << 7) +#define PORT1_INIT2 (1 << 23) +#define HW_OTG_CTRL_SET 0x374 +#define HW_OTG_CTRL_CLR 0x376 + +/* Interrupt Register */ +#define HC_INTERRUPT_REG 0x310 + +#define HC_INTERRUPT_ENABLE 0x314 +#define HC_ISO_INT (1 << 9) +#define HC_ATL_INT (1 << 8) +#define HC_INTL_INT (1 << 7) +#define HC_EOT_INT (1 << 3) +#define HC_SOT_INT (1 << 1) +#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) + +#define HC_ISO_IRQ_MASK_OR_REG 0x318 +#define HC_INT_IRQ_MASK_OR_REG 0x31C +#define HC_ATL_IRQ_MASK_OR_REG 0x320 +#define HC_ISO_IRQ_MASK_AND_REG 0x324 +#define HC_INT_IRQ_MASK_AND_REG 0x328 +#define HC_ATL_IRQ_MASK_AND_REG 0x32C + +/* urb state*/ +#define DELETE_URB (0x0008) +#define NO_TRANSFER_ACTIVE (0xffffffff) + +/* Philips Proprietary Transfer Descriptor (PTD) */ +typedef __u32 __bitwise __dw; +struct ptd { + __dw dw0; + __dw dw1; + __dw dw2; + __dw dw3; + __dw dw4; + __dw dw5; + __dw dw6; + __dw dw7; +}; +#define PTD_OFFSET 0x0400 +#define ISO_PTD_OFFSET 0x0400 +#define INT_PTD_OFFSET 0x0800 +#define ATL_PTD_OFFSET 0x0c00 +#define PAYLOAD_OFFSET 0x1000 + +struct slotinfo { + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + unsigned long timestamp; +}; + + +typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd); + +/* + * Device flags that can vary from board to board. All of these + * indicate the most "atypical" case, so that a devflags of 0 is + * a sane default configuration. + */ +#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ +#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ +#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ +#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ +#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ +#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ +#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ +#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ +#define ISP1760_FLAG_RESET_ACTIVE_HIGH 0x80000000 /* RESET GPIO active high */ + +/* chip memory management */ +struct memory_chunk { + unsigned int start; + unsigned int size; + unsigned int free; +}; + +/* + * 60kb divided in: + * - 32 blocks @ 256 bytes + * - 20 blocks @ 1024 bytes + * - 4 blocks @ 8192 bytes + */ + +#define BLOCK_1_NUM 32 +#define BLOCK_2_NUM 20 +#define BLOCK_3_NUM 4 + +#define BLOCK_1_SIZE 256 +#define BLOCK_2_SIZE 1024 +#define BLOCK_3_SIZE 8192 +#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) +#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE +#define PAYLOAD_AREA_SIZE 0xf000 + +/* ATL */ +/* DW0 */ +#define DW0_VALID_BIT 1 +#define FROM_DW0_VALID(x) ((x) & 0x01) +#define TO_DW0_LENGTH(x) (((u32) x) << 3) +#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) +#define TO_DW0_MULTI(x) (((u32) x) << 29) +#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) +/* DW1 */ +#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) +#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) +#define DW1_TRANS_BULK ((u32) 2 << 12) +#define DW1_TRANS_INT ((u32) 3 << 12) +#define DW1_TRANS_SPLIT ((u32) 1 << 14) +#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) +#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) +#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) +/* DW2 */ +#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) +#define TO_DW2_RL(x) ((x) << 25) +#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) +/* DW3 */ +#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) +#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) +#define TO_DW3_NAKCOUNT(x) ((x) << 19) +#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) +#define TO_DW3_CERR(x) ((x) << 23) +#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) +#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) +#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) +#define TO_DW3_PING(x) ((x) << 26) +#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) +#define DW3_ERROR_BIT (1 << 28) +#define DW3_BABBLE_BIT (1 << 29) +#define DW3_HALT_BIT (1 << 30) +#define DW3_ACTIVE_BIT (1 << 31) +#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) + +#define INT_UNDERRUN (1 << 2) +#define INT_BABBLE (1 << 1) +#define INT_EXACT (1 << 0) + +#define SETUP_PID (2) +#define IN_PID (1) +#define OUT_PID (0) + +/* Errata 1 */ +#define RL_COUNTER (0) +#define NAK_COUNTER (0) +#define ERR_COUNTER (2) + +#endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c new file mode 100644 index 00000000..a13709ee --- /dev/null +++ b/drivers/usb/host/isp1760-if.c @@ -0,0 +1,480 @@ +/* + * Glue code for the ISP1760 driver and bus + * Currently there is support for + * - OpenFirmware + * - PCI + * - PDEV (generic platform device centralized driver model) + * + * (c) 2007 Sebastian Siewior + * + */ + +#include +#include +#include +#include +#include +#include + +#include "isp1760-hcd.h" + +#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) +#include +#include +#include +#include +#include +#include +#endif + +#ifdef CONFIG_PCI +#include +#endif + +#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) +struct isp1760 { + struct usb_hcd *hcd; + int rst_gpio; +}; + +static int of_isp1760_probe(struct platform_device *dev) +{ + struct isp1760 *drvdata; + struct device_node *dp = dev->dev.of_node; + struct resource *res; + struct resource memory; + int virq; + resource_size_t res_len; + int ret; + unsigned int devflags = 0; + enum of_gpio_flags gpio_flags; + u32 bus_width = 0; + + drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + ret = of_address_to_resource(dp, 0, &memory); + if (ret) { + ret = -ENXIO; + goto free_data; + } + + res_len = resource_size(&memory); + + res = request_mem_region(memory.start, res_len, dev_name(&dev->dev)); + if (!res) { + ret = -EBUSY; + goto free_data; + } + + virq = irq_of_parse_and_map(dp, 0); + if (!virq) { + ret = -ENODEV; + goto release_reg; + } + + if (of_device_is_compatible(dp, "nxp,usb-isp1761")) + devflags |= ISP1760_FLAG_ISP1761; + + /* Some systems wire up only 16 of the 32 data lines */ + of_property_read_u32(dp, "bus-width", &bus_width); + if (bus_width == 16) + devflags |= ISP1760_FLAG_BUS_WIDTH_16; + + if (of_get_property(dp, "port1-otg", NULL) != NULL) + devflags |= ISP1760_FLAG_OTG_EN; + + if (of_get_property(dp, "analog-oc", NULL) != NULL) + devflags |= ISP1760_FLAG_ANALOG_OC; + + if (of_get_property(dp, "dack-polarity", NULL) != NULL) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + + if (of_get_property(dp, "dreq-polarity", NULL) != NULL) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + + drvdata->rst_gpio = of_get_gpio_flags(dp, 0, &gpio_flags); + if (gpio_is_valid(drvdata->rst_gpio)) { + ret = gpio_request(drvdata->rst_gpio, dev_name(&dev->dev)); + if (!ret) { + if (!(gpio_flags & OF_GPIO_ACTIVE_LOW)) { + devflags |= ISP1760_FLAG_RESET_ACTIVE_HIGH; + gpio_direction_output(drvdata->rst_gpio, 0); + } else { + gpio_direction_output(drvdata->rst_gpio, 1); + } + } else { + drvdata->rst_gpio = ret; + } + } + + drvdata->hcd = isp1760_register(memory.start, res_len, virq, + IRQF_SHARED, drvdata->rst_gpio, + &dev->dev, dev_name(&dev->dev), + devflags); + if (IS_ERR(drvdata->hcd)) { + ret = PTR_ERR(drvdata->hcd); + goto free_gpio; + } + + dev_set_drvdata(&dev->dev, drvdata); + return ret; + +free_gpio: + if (gpio_is_valid(drvdata->rst_gpio)) + gpio_free(drvdata->rst_gpio); +release_reg: + release_mem_region(memory.start, res_len); +free_data: + kfree(drvdata); + return ret; +} + +static int of_isp1760_remove(struct platform_device *dev) +{ + struct isp1760 *drvdata = dev_get_drvdata(&dev->dev); + + dev_set_drvdata(&dev->dev, NULL); + + usb_remove_hcd(drvdata->hcd); + iounmap(drvdata->hcd->regs); + release_mem_region(drvdata->hcd->rsrc_start, drvdata->hcd->rsrc_len); + usb_put_hcd(drvdata->hcd); + + if (gpio_is_valid(drvdata->rst_gpio)) + gpio_free(drvdata->rst_gpio); + + kfree(drvdata); + return 0; +} + +static const struct of_device_id of_isp1760_match[] = { + { + .compatible = "nxp,usb-isp1760", + }, + { + .compatible = "nxp,usb-isp1761", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_isp1760_match); + +static struct platform_driver isp1760_of_driver = { + .driver = { + .name = "nxp-isp1760", + .owner = THIS_MODULE, + .of_match_table = of_isp1760_match, + }, + .probe = of_isp1760_probe, + .remove = of_isp1760_remove, +}; +#endif + +#ifdef CONFIG_PCI +static int isp1761_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + u8 latency, limit; + __u32 reg_data; + int retry_count; + struct usb_hcd *hcd; + unsigned int devflags = 0; + int ret_status = 0; + + resource_size_t pci_mem_phy0; + resource_size_t memlength; + + u8 __iomem *chip_addr; + u8 __iomem *iobase; + resource_size_t nxp_pci_io_base; + resource_size_t iolength; + + if (usb_disabled()) + return -ENODEV; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + if (!dev->irq) + return -ENODEV; + + /* Grab the PLX PCI mem maped port start address we need */ + nxp_pci_io_base = pci_resource_start(dev, 0); + iolength = pci_resource_len(dev, 0); + + if (!request_mem_region(nxp_pci_io_base, iolength, "ISP1761 IO MEM")) { + printk(KERN_ERR "request region #1\n"); + return -EBUSY; + } + + iobase = ioremap_nocache(nxp_pci_io_base, iolength); + if (!iobase) { + printk(KERN_ERR "ioremap #1\n"); + ret_status = -ENOMEM; + goto cleanup1; + } + /* Grab the PLX PCI shared memory of the ISP 1761 we need */ + pci_mem_phy0 = pci_resource_start(dev, 3); + memlength = pci_resource_len(dev, 3); + if (memlength < 0xffff) { + printk(KERN_ERR "memory length for this resource is wrong\n"); + ret_status = -ENOMEM; + goto cleanup2; + } + + if (!request_mem_region(pci_mem_phy0, memlength, "ISP-PCI")) { + printk(KERN_ERR "host controller already in use\n"); + ret_status = -EBUSY; + goto cleanup2; + } + + /* map available memory */ + chip_addr = ioremap_nocache(pci_mem_phy0,memlength); + if (!chip_addr) { + printk(KERN_ERR "Error ioremap failed\n"); + ret_status = -ENOMEM; + goto cleanup3; + } + + /* bad pci latencies can contribute to overruns */ + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &latency); + if (latency) { + pci_read_config_byte(dev, PCI_MAX_LAT, &limit); + if (limit && limit < latency) + pci_write_config_byte(dev, PCI_LATENCY_TIMER, limit); + } + + /* Try to check whether we can access Scratch Register of + * Host Controller or not. The initial PCI access is retried until + * local init for the PCI bridge is completed + */ + retry_count = 20; + reg_data = 0; + while ((reg_data != 0xFACE) && retry_count) { + /*by default host is in 16bit mode, so + * io operations at this stage must be 16 bit + * */ + writel(0xface, chip_addr + HC_SCRATCH_REG); + udelay(100); + reg_data = readl(chip_addr + HC_SCRATCH_REG) & 0x0000ffff; + retry_count--; + } + + iounmap(chip_addr); + + /* Host Controller presence is detected by writing to scratch register + * and reading back and checking the contents are same or not + */ + if (reg_data != 0xFACE) { + dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); + ret_status = -ENOMEM; + goto cleanup3; + } + + pci_set_master(dev); + + /* configure PLX PCI chip to pass interrupts */ +#define PLX_INT_CSR_REG 0x68 + reg_data = readl(iobase + PLX_INT_CSR_REG); + reg_data |= 0x900; + writel(reg_data, iobase + PLX_INT_CSR_REG); + + dev->dev.dma_mask = NULL; + hcd = isp1760_register(pci_mem_phy0, memlength, dev->irq, + IRQF_SHARED, -ENOENT, &dev->dev, dev_name(&dev->dev), + devflags); + if (IS_ERR(hcd)) { + ret_status = -ENODEV; + goto cleanup3; + } + + /* done with PLX IO access */ + iounmap(iobase); + release_mem_region(nxp_pci_io_base, iolength); + + pci_set_drvdata(dev, hcd); + return 0; + +cleanup3: + release_mem_region(pci_mem_phy0, memlength); +cleanup2: + iounmap(iobase); +cleanup1: + release_mem_region(nxp_pci_io_base, iolength); + return ret_status; +} + +static void isp1761_pci_remove(struct pci_dev *dev) +{ + struct usb_hcd *hcd; + + hcd = pci_get_drvdata(dev); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + pci_disable_device(dev); +} + +static void isp1761_pci_shutdown(struct pci_dev *dev) +{ + printk(KERN_ERR "ips1761_pci_shutdown\n"); +} + +static const struct pci_device_id isp1760_plx [] = { + { + .class = PCI_CLASS_BRIDGE_OTHER << 8, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x5406, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = 0x9054, + }, + { } +}; +MODULE_DEVICE_TABLE(pci, isp1760_plx); + +static struct pci_driver isp1761_pci_driver = { + .name = "isp1760", + .id_table = isp1760_plx, + .probe = isp1761_pci_probe, + .remove = isp1761_pci_remove, + .shutdown = isp1761_pci_shutdown, +}; +#endif + +static int isp1760_plat_probe(struct platform_device *pdev) +{ + int ret = 0; + struct usb_hcd *hcd; + struct resource *mem_res; + struct resource *irq_res; + resource_size_t mem_size; + struct isp1760_platform_data *priv = pdev->dev.platform_data; + unsigned int devflags = 0; + unsigned long irqflags = IRQF_SHARED; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + pr_warning("isp1760: Memory resource not available\n"); + ret = -ENODEV; + goto out; + } + mem_size = resource_size(mem_res); + if (!request_mem_region(mem_res->start, mem_size, "isp1760")) { + pr_warning("isp1760: Cannot reserve the memory resource\n"); + ret = -EBUSY; + goto out; + } + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq_res) { + pr_warning("isp1760: IRQ resource not available\n"); + ret = -ENODEV; + goto cleanup; + } + + irqflags |= irq_res->flags & IRQF_TRIGGER_MASK; + + if (priv) { + if (priv->is_isp1761) + devflags |= ISP1760_FLAG_ISP1761; + if (priv->bus_width_16) + devflags |= ISP1760_FLAG_BUS_WIDTH_16; + if (priv->port1_otg) + devflags |= ISP1760_FLAG_OTG_EN; + if (priv->analog_oc) + devflags |= ISP1760_FLAG_ANALOG_OC; + if (priv->dack_polarity_high) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + if (priv->dreq_polarity_high) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + } + + hcd = isp1760_register(mem_res->start, mem_size, irq_res->start, + irqflags, -ENOENT, + &pdev->dev, dev_name(&pdev->dev), devflags); + + dev_set_drvdata(&pdev->dev, hcd); + + if (IS_ERR(hcd)) { + pr_warning("isp1760: Failed to register the HCD device\n"); + ret = -ENODEV; + goto cleanup; + } + + pr_info("ISP1760 USB device initialised\n"); + return ret; + +cleanup: + release_mem_region(mem_res->start, mem_size); +out: + return ret; +} + +static int isp1760_plat_remove(struct platform_device *pdev) +{ + struct resource *mem_res; + resource_size_t mem_size; + struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); + + usb_remove_hcd(hcd); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mem_size = resource_size(mem_res); + release_mem_region(mem_res->start, mem_size); + + usb_put_hcd(hcd); + + return 0; +} + +static struct platform_driver isp1760_plat_driver = { + .probe = isp1760_plat_probe, + .remove = isp1760_plat_remove, + .driver = { + .name = "isp1760", + }, +}; + +static int __init isp1760_init(void) +{ + int ret, any_ret = -ENODEV; + + init_kmem_once(); + + ret = platform_driver_register(&isp1760_plat_driver); + if (!ret) + any_ret = 0; +#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) + ret = platform_driver_register(&isp1760_of_driver); + if (!ret) + any_ret = 0; +#endif +#ifdef CONFIG_PCI + ret = pci_register_driver(&isp1761_pci_driver); + if (!ret) + any_ret = 0; +#endif + + if (any_ret) + deinit_kmem_cache(); + return any_ret; +} +module_init(isp1760_init); + +static void __exit isp1760_exit(void) +{ + platform_driver_unregister(&isp1760_plat_driver); +#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) + platform_driver_unregister(&isp1760_of_driver); +#endif +#ifdef CONFIG_PCI + pci_unregister_driver(&isp1761_pci_driver); +#endif + deinit_kmem_cache(); +} +module_exit(isp1760_exit); diff --git a/drivers/usb/host/octeon2-common.c b/drivers/usb/host/octeon2-common.c new file mode 100644 index 00000000..d9df423f --- /dev/null +++ b/drivers/usb/host/octeon2-common.c @@ -0,0 +1,200 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2010, 2011 Cavium Networks + */ + +#include +#include +#include + +#include +#include + +static DEFINE_MUTEX(octeon2_usb_clocks_mutex); + +static int octeon2_usb_clock_start_cnt; + +void octeon2_usb_clocks_start(void) +{ + u64 div; + union cvmx_uctlx_if_ena if_ena; + union cvmx_uctlx_clk_rst_ctl clk_rst_ctl; + union cvmx_uctlx_uphy_ctl_status uphy_ctl_status; + union cvmx_uctlx_uphy_portx_ctl_status port_ctl_status; + int i; + unsigned long io_clk_64_to_ns; + + + mutex_lock(&octeon2_usb_clocks_mutex); + + octeon2_usb_clock_start_cnt++; + if (octeon2_usb_clock_start_cnt != 1) + goto exit; + + io_clk_64_to_ns = 64000000000ull / octeon_get_io_clock_rate(); + + /* + * Step 1: Wait for voltages stable. That surely happened + * before starting the kernel. + * + * Step 2: Enable SCLK of UCTL by writing UCTL0_IF_ENA[EN] = 1 + */ + if_ena.u64 = 0; + if_ena.s.en = 1; + cvmx_write_csr(CVMX_UCTLX_IF_ENA(0), if_ena.u64); + + /* Step 3: Configure the reference clock, PHY, and HCLK */ + clk_rst_ctl.u64 = cvmx_read_csr(CVMX_UCTLX_CLK_RST_CTL(0)); + + /* + * If the UCTL looks like it has already been started, skip + * the initialization, otherwise bus errors are obtained. + */ + if (clk_rst_ctl.s.hrst) + goto end_clock; + /* 3a */ + clk_rst_ctl.s.p_por = 1; + clk_rst_ctl.s.hrst = 0; + clk_rst_ctl.s.p_prst = 0; + clk_rst_ctl.s.h_clkdiv_rst = 0; + clk_rst_ctl.s.o_clkdiv_rst = 0; + clk_rst_ctl.s.h_clkdiv_en = 0; + clk_rst_ctl.s.o_clkdiv_en = 0; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + + /* 3b */ + /* 12MHz crystal. */ + clk_rst_ctl.s.p_refclk_sel = 0; + clk_rst_ctl.s.p_refclk_div = 0; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + + /* 3c */ + div = octeon_get_io_clock_rate() / 130000000ull; + + switch (div) { + case 0: + div = 1; + break; + case 1: + case 2: + case 3: + case 4: + break; + case 5: + div = 4; + break; + case 6: + case 7: + div = 6; + break; + case 8: + case 9: + case 10: + case 11: + div = 8; + break; + default: + div = 12; + break; + } + clk_rst_ctl.s.h_div = div; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + /* Read it back, */ + clk_rst_ctl.u64 = cvmx_read_csr(CVMX_UCTLX_CLK_RST_CTL(0)); + clk_rst_ctl.s.h_clkdiv_en = 1; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + /* 3d */ + clk_rst_ctl.s.h_clkdiv_rst = 1; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + + /* 3e: delay 64 io clocks */ + ndelay(io_clk_64_to_ns); + + /* + * Step 4: Program the power-on reset field in the UCTL + * clock-reset-control register. + */ + clk_rst_ctl.s.p_por = 0; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + + /* Step 5: Wait 1 ms for the PHY clock to start. */ + mdelay(1); + + /* + * Step 6: Program the reset input from automatic test + * equipment field in the UPHY CSR + */ + uphy_ctl_status.u64 = cvmx_read_csr(CVMX_UCTLX_UPHY_CTL_STATUS(0)); + uphy_ctl_status.s.ate_reset = 1; + cvmx_write_csr(CVMX_UCTLX_UPHY_CTL_STATUS(0), uphy_ctl_status.u64); + + /* Step 7: Wait for at least 10ns. */ + ndelay(10); + + /* Step 8: Clear the ATE_RESET field in the UPHY CSR. */ + uphy_ctl_status.s.ate_reset = 0; + cvmx_write_csr(CVMX_UCTLX_UPHY_CTL_STATUS(0), uphy_ctl_status.u64); + + /* + * Step 9: Wait for at least 20ns for UPHY to output PHY clock + * signals and OHCI_CLK48 + */ + ndelay(20); + + /* Step 10: Configure the OHCI_CLK48 and OHCI_CLK12 clocks. */ + /* 10a */ + clk_rst_ctl.s.o_clkdiv_rst = 1; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + + /* 10b */ + clk_rst_ctl.s.o_clkdiv_en = 1; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + + /* 10c */ + ndelay(io_clk_64_to_ns); + + /* + * Step 11: Program the PHY reset field: + * UCTL0_CLK_RST_CTL[P_PRST] = 1 + */ + clk_rst_ctl.s.p_prst = 1; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + + /* Step 12: Wait 1 uS. */ + udelay(1); + + /* Step 13: Program the HRESET_N field: UCTL0_CLK_RST_CTL[HRST] = 1 */ + clk_rst_ctl.s.hrst = 1; + cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); + +end_clock: + /* Now we can set some other registers. */ + + for (i = 0; i <= 1; i++) { + port_ctl_status.u64 = + cvmx_read_csr(CVMX_UCTLX_UPHY_PORTX_CTL_STATUS(i, 0)); + /* Set txvreftune to 15 to obtain compliant 'eye' diagram. */ + port_ctl_status.s.txvreftune = 15; + port_ctl_status.s.txrisetune = 1; + port_ctl_status.s.txpreemphasistune = 1; + cvmx_write_csr(CVMX_UCTLX_UPHY_PORTX_CTL_STATUS(i, 0), + port_ctl_status.u64); + } + + /* Set uSOF cycle period to 60,000 bits. */ + cvmx_write_csr(CVMX_UCTLX_EHCI_FLA(0), 0x20ull); +exit: + mutex_unlock(&octeon2_usb_clocks_mutex); +} +EXPORT_SYMBOL(octeon2_usb_clocks_start); + +void octeon2_usb_clocks_stop(void) +{ + mutex_lock(&octeon2_usb_clocks_mutex); + octeon2_usb_clock_start_cnt--; + mutex_unlock(&octeon2_usb_clocks_mutex); +} +EXPORT_SYMBOL(octeon2_usb_clocks_stop); diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c new file mode 100644 index 00000000..2ee1496d --- /dev/null +++ b/drivers/usb/host/ohci-at91.c @@ -0,0 +1,729 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * Copyright (C) 2004 SAN People (Pty) Ltd. + * Copyright (C) 2005 Thibaut VARENE + * + * AT91 Bus Glue + * + * Based on fragments of 2.4 driver by Rick Bronson. + * Based on ohci-omap.c + * + * This file is licenced under the GPL. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#ifndef CONFIG_ARCH_AT91 +#error "CONFIG_ARCH_AT91 must be defined." +#endif + +#define valid_port(index) ((index) >= 0 && (index) < AT91_MAX_USBH_PORTS) +#define at91_for_each_port(index) \ + for ((index) = 0; (index) < AT91_MAX_USBH_PORTS; (index)++) + +/* interface and function clocks; sometimes also an AHB clock */ +static struct clk *iclk, *fclk, *hclk; +static int clocked; + +extern int usb_disabled(void); + +/*-------------------------------------------------------------------------*/ + +static void at91_start_clock(void) +{ + clk_enable(hclk); + clk_enable(iclk); + clk_enable(fclk); + clocked = 1; +} + +static void at91_stop_clock(void) +{ + clk_disable(fclk); + clk_disable(iclk); + clk_disable(hclk); + clocked = 0; +} + +static void at91_start_hc(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_regs __iomem *regs = hcd->regs; + + dev_dbg(&pdev->dev, "start\n"); + + /* + * Start the USB clocks. + */ + at91_start_clock(); + + /* + * The USB host controller must remain in reset. + */ + writel(0, ®s->control); +} + +static void at91_stop_hc(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_regs __iomem *regs = hcd->regs; + + dev_dbg(&pdev->dev, "stop\n"); + + /* + * Put the USB host controller into reset. + */ + writel(0, ®s->control); + + /* + * Stop the USB clocks. + */ + at91_stop_clock(); +} + + +/*-------------------------------------------------------------------------*/ + +static void usb_hcd_at91_remove (struct usb_hcd *, struct platform_device *); + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + + +/** + * usb_hcd_at91_probe - initialize AT91-based HCDs + * Context: !in_interrupt() + * + * 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 usb_hcd_at91_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + int retval; + struct usb_hcd *hcd = NULL; + + if (pdev->num_resources != 2) { + pr_debug("hcd probe: invalid num_resources"); + return -ENODEV; + } + + if ((pdev->resource[0].flags != IORESOURCE_MEM) + || (pdev->resource[1].flags != IORESOURCE_IRQ)) { + pr_debug("hcd probe: invalid resource type\n"); + return -ENODEV; + } + + hcd = usb_create_hcd(driver, &pdev->dev, "at91"); + if (!hcd) + return -ENOMEM; + hcd->rsrc_start = pdev->resource[0].start; + hcd->rsrc_len = resource_size(&pdev->resource[0]); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + pr_debug("request_mem_region failed\n"); + retval = -EBUSY; + goto err1; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + pr_debug("ioremap failed\n"); + retval = -EIO; + goto err2; + } + + iclk = clk_get(&pdev->dev, "ohci_clk"); + if (IS_ERR(iclk)) { + dev_err(&pdev->dev, "failed to get ohci_clk\n"); + retval = PTR_ERR(iclk); + goto err3; + } + fclk = clk_get(&pdev->dev, "uhpck"); + if (IS_ERR(fclk)) { + dev_err(&pdev->dev, "failed to get uhpck\n"); + retval = PTR_ERR(fclk); + goto err4; + } + hclk = clk_get(&pdev->dev, "hclk"); + if (IS_ERR(hclk)) { + dev_err(&pdev->dev, "failed to get hclk\n"); + retval = PTR_ERR(hclk); + goto err5; + } + + at91_start_hc(pdev); + ohci_hcd_init(hcd_to_ohci(hcd)); + + retval = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_SHARED); + if (retval == 0) + return retval; + + /* Error handling */ + at91_stop_hc(pdev); + + clk_put(hclk); + err5: + clk_put(fclk); + err4: + clk_put(iclk); + + err3: + iounmap(hcd->regs); + + err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + + err1: + usb_put_hcd(hcd); + return retval; +} + + +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_at91_remove - shutdown processing for AT91-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_at91_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, "rmmod" or something similar. + * + */ +static void usb_hcd_at91_remove(struct usb_hcd *hcd, + struct platform_device *pdev) +{ + usb_remove_hcd(hcd); + at91_stop_hc(pdev); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + clk_put(hclk); + clk_put(fclk); + clk_put(iclk); + fclk = iclk = hclk = NULL; + + dev_set_drvdata(&pdev->dev, NULL); +} + +/*-------------------------------------------------------------------------*/ + +static int +ohci_at91_reset (struct usb_hcd *hcd) +{ + struct at91_usbh_data *board = hcd->self.controller->platform_data; + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + ohci->num_ports = board->ports; + return 0; +} + +static int +ohci_at91_start (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + + if ((ret = ohci_run(ohci)) < 0) { + dev_err(hcd->self.controller, "can't start %s\n", + hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + return 0; +} + +static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable) +{ + if (!valid_port(port)) + return; + + if (!gpio_is_valid(pdata->vbus_pin[port])) + return; + + gpio_set_value(pdata->vbus_pin[port], + pdata->vbus_pin_active_low[port] ^ enable); +} + +static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port) +{ + if (!valid_port(port)) + return -EINVAL; + + if (!gpio_is_valid(pdata->vbus_pin[port])) + return -EINVAL; + + return gpio_get_value(pdata->vbus_pin[port]) ^ + pdata->vbus_pin_active_low[port]; +} + +/* + * Update the status data from the hub with the over-current indicator change. + */ +static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct at91_usbh_data *pdata = hcd->self.controller->platform_data; + int length = ohci_hub_status_data(hcd, buf); + int port; + + at91_for_each_port(port) { + if (pdata->overcurrent_changed[port]) { + if (!length) + length = 1; + buf[0] |= 1 << (port + 1); + } + } + + return length; +} + +/* + * Look at the control requests to the root hub and see if we need to override. + */ +static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct at91_usbh_data *pdata = hcd->self.controller->platform_data; + struct usb_hub_descriptor *desc; + int ret = -EINVAL; + u32 *data = (u32 *)buf; + + dev_dbg(hcd->self.controller, + "ohci_at91_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n", + hcd, typeReq, wValue, wIndex, buf, wLength); + + wIndex--; + + switch (typeReq) { + case SetPortFeature: + if (wValue == USB_PORT_FEAT_POWER) { + dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n"); + if (valid_port(wIndex)) { + ohci_at91_usb_set_power(pdata, wIndex, 1); + ret = 0; + } + + goto out; + } + break; + + case ClearPortFeature: + switch (wValue) { + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(hcd->self.controller, + "ClearPortFeature: C_OVER_CURRENT\n"); + + if (valid_port(wIndex)) { + pdata->overcurrent_changed[wIndex] = 0; + pdata->overcurrent_status[wIndex] = 0; + } + + goto out; + + case USB_PORT_FEAT_OVER_CURRENT: + dev_dbg(hcd->self.controller, + "ClearPortFeature: OVER_CURRENT\n"); + + if (valid_port(wIndex)) + pdata->overcurrent_status[wIndex] = 0; + + goto out; + + case USB_PORT_FEAT_POWER: + dev_dbg(hcd->self.controller, + "ClearPortFeature: POWER\n"); + + if (valid_port(wIndex)) { + ohci_at91_usb_set_power(pdata, wIndex, 0); + return 0; + } + } + break; + } + + ret = ohci_hub_control(hcd, typeReq, wValue, wIndex + 1, buf, wLength); + if (ret) + goto out; + + switch (typeReq) { + case GetHubDescriptor: + + /* update the hub's descriptor */ + + desc = (struct usb_hub_descriptor *)buf; + + dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n", + desc->wHubCharacteristics); + + /* remove the old configurations for power-switching, and + * over-current protection, and insert our new configuration + */ + + desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM); + desc->wHubCharacteristics |= cpu_to_le16(0x0001); + + if (pdata->overcurrent_supported) { + desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM); + desc->wHubCharacteristics |= cpu_to_le16(0x0008|0x0001); + } + + dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n", + desc->wHubCharacteristics); + + return ret; + + case GetPortStatus: + /* check port status */ + + dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex); + + if (valid_port(wIndex)) { + if (!ohci_at91_usb_get_power(pdata, wIndex)) + *data &= ~cpu_to_le32(RH_PS_PPS); + + if (pdata->overcurrent_changed[wIndex]) + *data |= cpu_to_le32(RH_PS_OCIC); + + if (pdata->overcurrent_status[wIndex]) + *data |= cpu_to_le32(RH_PS_POCI); + } + } + + out: + return ret; +} + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ohci_at91_hc_driver = { + .description = hcd_name, + .product_desc = "AT91 OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ohci_at91_reset, + .start = ohci_at91_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_at91_hub_status_data, + .hub_control = ohci_at91_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + +static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct at91_usbh_data *pdata = pdev->dev.platform_data; + int val, gpio, port; + + /* From the GPIO notifying the over-current situation, find + * out the corresponding port */ + at91_for_each_port(port) { + if (gpio_is_valid(pdata->overcurrent_pin[port]) && + gpio_to_irq(pdata->overcurrent_pin[port]) == irq) { + gpio = pdata->overcurrent_pin[port]; + break; + } + } + + if (port == AT91_MAX_USBH_PORTS) { + dev_err(& pdev->dev, "overcurrent interrupt from unknown GPIO\n"); + return IRQ_HANDLED; + } + + val = gpio_get_value(gpio); + + /* When notified of an over-current situation, disable power + on the corresponding port, and mark this port in + over-current. */ + if (!val) { + ohci_at91_usb_set_power(pdata, port, 0); + pdata->overcurrent_status[port] = 1; + pdata->overcurrent_changed[port] = 1; + } + + dev_dbg(& pdev->dev, "overcurrent situation %s\n", + val ? "exited" : "notified"); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_OF +static const struct of_device_id at91_ohci_dt_ids[] = { + { .compatible = "atmel,at91rm9200-ohci" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, at91_ohci_dt_ids); + +static int ohci_at91_of_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int i, gpio; + enum of_gpio_flags flags; + struct at91_usbh_data *pdata; + u32 ports; + + if (!np) + return 0; + + /* 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. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (!of_property_read_u32(np, "num-ports", &ports)) + pdata->ports = ports; + + at91_for_each_port(i) { + gpio = of_get_named_gpio_flags(np, "atmel,vbus-gpio", i, &flags); + pdata->vbus_pin[i] = gpio; + if (!gpio_is_valid(gpio)) + continue; + pdata->vbus_pin_active_low[i] = flags & OF_GPIO_ACTIVE_LOW; + } + + at91_for_each_port(i) + pdata->overcurrent_pin[i] = + of_get_named_gpio_flags(np, "atmel,oc-gpio", i, &flags); + + pdev->dev.platform_data = pdata; + + return 0; +} +#else +static int ohci_at91_of_init(struct platform_device *pdev) +{ + return 0; +} +#endif + +/*-------------------------------------------------------------------------*/ + +static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) +{ + struct at91_usbh_data *pdata; + int i; + int gpio; + int ret; + + ret = ohci_at91_of_init(pdev); + if (ret) + return ret; + + pdata = pdev->dev.platform_data; + + if (pdata) { + at91_for_each_port(i) { + /* + * do not configure PIO if not in relation with + * real USB port on board + */ + if (i >= pdata->ports) { + pdata->vbus_pin[i] = -EINVAL; + pdata->overcurrent_pin[i] = -EINVAL; + break; + } + + if (!gpio_is_valid(pdata->vbus_pin[i])) + continue; + gpio = pdata->vbus_pin[i]; + + ret = gpio_request(gpio, "ohci_vbus"); + if (ret) { + dev_err(&pdev->dev, + "can't request vbus gpio %d\n", gpio); + continue; + } + ret = gpio_direction_output(gpio, + !pdata->vbus_pin_active_low[i]); + if (ret) { + dev_err(&pdev->dev, + "can't put vbus gpio %d as output %d\n", + gpio, !pdata->vbus_pin_active_low[i]); + gpio_free(gpio); + continue; + } + + ohci_at91_usb_set_power(pdata, i, 1); + } + + at91_for_each_port(i) { + if (!gpio_is_valid(pdata->overcurrent_pin[i])) + continue; + gpio = pdata->overcurrent_pin[i]; + + ret = gpio_request(gpio, "ohci_overcurrent"); + if (ret) { + dev_err(&pdev->dev, + "can't request overcurrent gpio %d\n", + gpio); + continue; + } + + ret = gpio_direction_input(gpio); + if (ret) { + dev_err(&pdev->dev, + "can't configure overcurrent gpio %d as input\n", + gpio); + gpio_free(gpio); + continue; + } + + ret = request_irq(gpio_to_irq(gpio), + ohci_hcd_at91_overcurrent_irq, + IRQF_SHARED, "ohci_overcurrent", pdev); + if (ret) { + gpio_free(gpio); + dev_err(&pdev->dev, + "can't get gpio IRQ for overcurrent\n"); + } + } + } + + device_init_wakeup(&pdev->dev, 1); + return usb_hcd_at91_probe(&ohci_at91_hc_driver, pdev); +} + +static int ohci_hcd_at91_drv_remove(struct platform_device *pdev) +{ + struct at91_usbh_data *pdata = pdev->dev.platform_data; + int i; + + if (pdata) { + at91_for_each_port(i) { + if (!gpio_is_valid(pdata->vbus_pin[i])) + continue; + ohci_at91_usb_set_power(pdata, i, 0); + gpio_free(pdata->vbus_pin[i]); + } + + at91_for_each_port(i) { + if (!gpio_is_valid(pdata->overcurrent_pin[i])) + continue; + free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev); + gpio_free(pdata->overcurrent_pin[i]); + } + } + + device_init_wakeup(&pdev->dev, 0); + usb_hcd_at91_remove(platform_get_drvdata(pdev), pdev); + return 0; +} + +#ifdef CONFIG_PM + +static int +ohci_hcd_at91_drv_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(hcd->irq); + + /* + * The integrated transceivers seem unable to notice disconnect, + * reconnect, or wakeup without the 48 MHz clock active. so for + * correctness, always discard connection state (using reset). + * + * REVISIT: some boards will be able to turn VBUS off... + */ + if (at91_suspend_entering_slow_clock()) { + ohci_usb_reset (ohci); + /* flush the writes */ + (void) ohci_readl (ohci, &ohci->regs->control); + at91_stop_clock(); + } + + return 0; +} + +static int ohci_hcd_at91_drv_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(hcd->irq); + + if (!clocked) + at91_start_clock(); + + ohci_resume(hcd, false); + return 0; +} +#else +#define ohci_hcd_at91_drv_suspend NULL +#define ohci_hcd_at91_drv_resume NULL +#endif + +MODULE_ALIAS("platform:at91_ohci"); + +static struct platform_driver ohci_hcd_at91_driver = { + .probe = ohci_hcd_at91_drv_probe, + .remove = ohci_hcd_at91_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .suspend = ohci_hcd_at91_drv_suspend, + .resume = ohci_hcd_at91_drv_resume, + .driver = { + .name = "at91_ohci", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(at91_ohci_dt_ids), + }, +}; diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c new file mode 100644 index 00000000..0b815a85 --- /dev/null +++ b/drivers/usb/host/ohci-da8xx.c @@ -0,0 +1,458 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * TI DA8xx (OMAP-L1x) Bus Glue + * + * Derived from: ohci-omap.c and ohci-s3c2410.c + * Copyright (C) 2008-2009 MontaVista Software, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include + +#include +#include + +#ifndef CONFIG_ARCH_DAVINCI_DA8XX +#error "This file is DA8xx bus glue. Define CONFIG_ARCH_DAVINCI_DA8XX." +#endif + +#define CFGCHIP2 DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP2_REG) + +static struct clk *usb11_clk; +static struct clk *usb20_clk; + +/* Over-current indicator change bitmask */ +static volatile u16 ocic_mask; + +static void ohci_da8xx_clock(int on) +{ + u32 cfgchip2; + + cfgchip2 = __raw_readl(CFGCHIP2); + if (on) { + clk_enable(usb11_clk); + + /* + * If USB 1.1 reference clock is sourced from USB 2.0 PHY, we + * need to enable the USB 2.0 module clocking, start its PHY, + * and not allow it to stop the clock during USB 2.0 suspend. + */ + if (!(cfgchip2 & CFGCHIP2_USB1PHYCLKMUX)) { + clk_enable(usb20_clk); + + cfgchip2 &= ~(CFGCHIP2_RESET | CFGCHIP2_PHYPWRDN); + cfgchip2 |= CFGCHIP2_PHY_PLLON; + __raw_writel(cfgchip2, CFGCHIP2); + + pr_info("Waiting for USB PHY clock good...\n"); + while (!(__raw_readl(CFGCHIP2) & CFGCHIP2_PHYCLKGD)) + cpu_relax(); + } + + /* Enable USB 1.1 PHY */ + cfgchip2 |= CFGCHIP2_USB1SUSPENDM; + } else { + clk_disable(usb11_clk); + if (!(cfgchip2 & CFGCHIP2_USB1PHYCLKMUX)) + clk_disable(usb20_clk); + + /* Disable USB 1.1 PHY */ + cfgchip2 &= ~CFGCHIP2_USB1SUSPENDM; + } + __raw_writel(cfgchip2, CFGCHIP2); +} + +/* + * Handle the port over-current indicator change. + */ +static void ohci_da8xx_ocic_handler(struct da8xx_ohci_root_hub *hub, + unsigned port) +{ + ocic_mask |= 1 << port; + + /* Once over-current is detected, the port needs to be powered down */ + if (hub->get_oci(port) > 0) + hub->set_power(port, 0); +} + +static int ohci_da8xx_init(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + struct da8xx_ohci_root_hub *hub = dev->platform_data; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int result; + u32 rh_a; + + dev_dbg(dev, "starting USB controller\n"); + + ohci_da8xx_clock(1); + + /* + * DA8xx only have 1 port connected to the pins but the HC root hub + * register A reports 2 ports, thus we'll have to override it... + */ + ohci->num_ports = 1; + + result = ohci_init(ohci); + if (result < 0) + return result; + + /* + * Since we're providing a board-specific root hub port power control + * and over-current reporting, we have to override the HC root hub A + * register's default value, so that ohci_hub_control() could return + * the correct hub descriptor... + */ + rh_a = ohci_readl(ohci, &ohci->regs->roothub.a); + if (hub->set_power) { + rh_a &= ~RH_A_NPS; + rh_a |= RH_A_PSM; + } + if (hub->get_oci) { + rh_a &= ~RH_A_NOCP; + rh_a |= RH_A_OCPM; + } + rh_a &= ~RH_A_POTPGT; + rh_a |= hub->potpgt << 24; + ohci_writel(ohci, rh_a, &ohci->regs->roothub.a); + + return result; +} + +static void ohci_da8xx_stop(struct usb_hcd *hcd) +{ + ohci_stop(hcd); + ohci_da8xx_clock(0); +} + +static int ohci_da8xx_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int result; + + result = ohci_run(ohci); + if (result < 0) + ohci_da8xx_stop(hcd); + + return result; +} + +/* + * Update the status data from the hub with the over-current indicator change. + */ +static int ohci_da8xx_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + int length = ohci_hub_status_data(hcd, buf); + + /* See if we have OCIC bit set on port 1 */ + if (ocic_mask & (1 << 1)) { + dev_dbg(hcd->self.controller, "over-current indicator change " + "on port 1\n"); + + if (!length) + length = 1; + + buf[0] |= 1 << 1; + } + return length; +} + +/* + * Look at the control requests to the root hub and see if we need to override. + */ +static int ohci_da8xx_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct device *dev = hcd->self.controller; + struct da8xx_ohci_root_hub *hub = dev->platform_data; + int temp; + + switch (typeReq) { + case GetPortStatus: + /* Check the port number */ + if (wIndex != 1) + break; + + dev_dbg(dev, "GetPortStatus(%u)\n", wIndex); + + temp = roothub_portstatus(hcd_to_ohci(hcd), wIndex - 1); + + /* The port power status (PPS) bit defaults to 1 */ + if (hub->get_power && hub->get_power(wIndex) == 0) + temp &= ~RH_PS_PPS; + + /* The port over-current indicator (POCI) bit is always 0 */ + if (hub->get_oci && hub->get_oci(wIndex) > 0) + temp |= RH_PS_POCI; + + /* The over-current indicator change (OCIC) bit is 0 too */ + if (ocic_mask & (1 << wIndex)) + temp |= RH_PS_OCIC; + + put_unaligned(cpu_to_le32(temp), (__le32 *)buf); + return 0; + case SetPortFeature: + temp = 1; + goto check_port; + case ClearPortFeature: + temp = 0; + +check_port: + /* Check the port number */ + if (wIndex != 1) + break; + + switch (wValue) { + case USB_PORT_FEAT_POWER: + dev_dbg(dev, "%sPortFeature(%u): %s\n", + temp ? "Set" : "Clear", wIndex, "POWER"); + + if (!hub->set_power) + return -EPIPE; + + return hub->set_power(wIndex, temp) ? -EPIPE : 0; + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(dev, "%sPortFeature(%u): %s\n", + temp ? "Set" : "Clear", wIndex, + "C_OVER_CURRENT"); + + if (temp) + ocic_mask |= 1 << wIndex; + else + ocic_mask &= ~(1 << wIndex); + return 0; + } + } + + return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); +} + +static const struct hc_driver ohci_da8xx_hc_driver = { + .description = hcd_name, + .product_desc = "DA8xx OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ohci_da8xx_init, + .start = ohci_da8xx_start, + .stop = ohci_da8xx_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_da8xx_hub_status_data, + .hub_control = ohci_da8xx_hub_control, + +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + + +/** + * usb_hcd_da8xx_probe - initialize DA8xx-based HCDs + * Context: !in_interrupt() + * + * 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 usb_hcd_da8xx_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + struct da8xx_ohci_root_hub *hub = pdev->dev.platform_data; + struct usb_hcd *hcd; + struct resource *mem; + int error, irq; + + if (hub == NULL) + return -ENODEV; + + usb11_clk = clk_get(&pdev->dev, "usb11"); + if (IS_ERR(usb11_clk)) + return PTR_ERR(usb11_clk); + + usb20_clk = clk_get(&pdev->dev, "usb20"); + if (IS_ERR(usb20_clk)) { + error = PTR_ERR(usb20_clk); + goto err0; + } + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + error = -ENOMEM; + goto err1; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + error = -ENODEV; + goto err2; + } + hcd->rsrc_start = mem->start; + hcd->rsrc_len = resource_size(mem); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + error = -EBUSY; + goto err2; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + error = -ENOMEM; + goto err3; + } + + ohci_hcd_init(hcd_to_ohci(hcd)); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + error = -ENODEV; + goto err4; + } + error = usb_add_hcd(hcd, irq, 0); + if (error) + goto err4; + + if (hub->ocic_notify) { + error = hub->ocic_notify(ohci_da8xx_ocic_handler); + if (!error) + return 0; + } + + usb_remove_hcd(hcd); +err4: + iounmap(hcd->regs); +err3: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err2: + usb_put_hcd(hcd); +err1: + clk_put(usb20_clk); +err0: + clk_put(usb11_clk); + return error; +} + +/** + * usb_hcd_da8xx_remove - shutdown processing for DA8xx-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_da8xx_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + */ +static inline void +usb_hcd_da8xx_remove(struct usb_hcd *hcd, struct platform_device *pdev) +{ + struct da8xx_ohci_root_hub *hub = pdev->dev.platform_data; + + hub->ocic_notify(NULL); + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + clk_put(usb20_clk); + clk_put(usb11_clk); +} + +static int ohci_hcd_da8xx_drv_probe(struct platform_device *dev) +{ + return usb_hcd_da8xx_probe(&ohci_da8xx_hc_driver, dev); +} + +static int ohci_hcd_da8xx_drv_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + usb_hcd_da8xx_remove(hcd, dev); + platform_set_drvdata(dev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int ohci_da8xx_suspend(struct platform_device *dev, pm_message_t message) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ohci_da8xx_clock(0); + hcd->state = HC_STATE_SUSPENDED; + dev->dev.power.power_state = PMSG_SUSPEND; + return 0; +} + +static int ohci_da8xx_resume(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ohci_da8xx_clock(1); + dev->dev.power.power_state = PMSG_ON; + usb_hcd_resume_root_hub(hcd); + return 0; +} +#endif + +/* + * Driver definition to register with platform structure. + */ +static struct platform_driver ohci_hcd_da8xx_driver = { + .probe = ohci_hcd_da8xx_drv_probe, + .remove = ohci_hcd_da8xx_drv_remove, + .shutdown = usb_hcd_platform_shutdown, +#ifdef CONFIG_PM + .suspend = ohci_da8xx_suspend, + .resume = ohci_da8xx_resume, +#endif + .driver = { + .owner = THIS_MODULE, + .name = "ohci", + }, +}; + +MODULE_ALIAS("platform:ohci"); diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c new file mode 100644 index 00000000..31b81f9e --- /dev/null +++ b/drivers/usb/host/ohci-dbg.c @@ -0,0 +1,877 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * + * This file is licenced under the GPL. + */ + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG + +#define edstring(ed_type) ({ char *temp; \ + switch (ed_type) { \ + case PIPE_CONTROL: temp = "ctrl"; break; \ + case PIPE_BULK: temp = "bulk"; break; \ + case PIPE_INTERRUPT: temp = "intr"; break; \ + default: temp = "isoc"; break; \ + }; temp;}) +#define pipestring(pipe) edstring(usb_pipetype(pipe)) + +/* debug| print the main components of an URB + * small: 0) header + data packets 1) just header + */ +static void __maybe_unused +urb_print(struct urb * urb, char * str, int small, int status) +{ + unsigned int pipe= urb->pipe; + + if (!urb->dev || !urb->dev->bus) { + printk(KERN_DEBUG "%s URB: no dev\n", str); + return; + } + +#ifndef OHCI_VERBOSE_DEBUG + if (status != 0) +#endif + printk(KERN_DEBUG "%s %p dev=%d ep=%d%s-%s flags=%x len=%d/%d stat=%d\n", + str, + urb, + usb_pipedevice (pipe), + usb_pipeendpoint (pipe), + usb_pipeout (pipe)? "out" : "in", + pipestring (pipe), + urb->transfer_flags, + urb->actual_length, + urb->transfer_buffer_length, + status); + +#ifdef OHCI_VERBOSE_DEBUG + if (!small) { + int i, len; + + if (usb_pipecontrol (pipe)) { + printk (KERN_DEBUG "%s: setup(8):", __FILE__); + for (i = 0; i < 8 ; i++) + printk (" %02x", ((__u8 *) urb->setup_packet) [i]); + printk ("\n"); + } + if (urb->transfer_buffer_length > 0 && urb->transfer_buffer) { + printk (KERN_DEBUG "%s: data(%d/%d):", __FILE__, + urb->actual_length, + urb->transfer_buffer_length); + len = usb_pipeout (pipe)? + urb->transfer_buffer_length: urb->actual_length; + for (i = 0; i < 16 && i < len; i++) + printk (" %02x", ((__u8 *) urb->transfer_buffer) [i]); + printk ("%s stat:%d\n", i < len? "...": "", status); + } + } +#endif +} + +#define ohci_dbg_sw(ohci, next, size, format, arg...) \ + do { \ + if (next != NULL) { \ + unsigned s_len; \ + s_len = scnprintf (*next, *size, format, ## arg ); \ + *size -= s_len; *next += s_len; \ + } else \ + ohci_dbg(ohci,format, ## arg ); \ + } while (0); + +/* Version for use where "next" is the address of a local variable */ +#define ohci_dbg_nosw(ohci, next, size, format, arg...) \ + do { \ + unsigned s_len; \ + s_len = scnprintf(*next, *size, format, ## arg); \ + *size -= s_len; *next += s_len; \ + } while (0); + + +static void ohci_dump_intr_mask ( + struct ohci_hcd *ohci, + char *label, + u32 mask, + char **next, + unsigned *size) +{ + ohci_dbg_sw (ohci, next, size, "%s 0x%08x%s%s%s%s%s%s%s%s%s\n", + label, + mask, + (mask & OHCI_INTR_MIE) ? " MIE" : "", + (mask & OHCI_INTR_OC) ? " OC" : "", + (mask & OHCI_INTR_RHSC) ? " RHSC" : "", + (mask & OHCI_INTR_FNO) ? " FNO" : "", + (mask & OHCI_INTR_UE) ? " UE" : "", + (mask & OHCI_INTR_RD) ? " RD" : "", + (mask & OHCI_INTR_SF) ? " SF" : "", + (mask & OHCI_INTR_WDH) ? " WDH" : "", + (mask & OHCI_INTR_SO) ? " SO" : "" + ); +} + +static void maybe_print_eds ( + struct ohci_hcd *ohci, + char *label, + u32 value, + char **next, + unsigned *size) +{ + if (value) + ohci_dbg_sw (ohci, next, size, "%s %08x\n", label, value); +} + +static char *hcfs2string (int state) +{ + switch (state) { + case OHCI_USB_RESET: return "reset"; + case OHCI_USB_RESUME: return "resume"; + case OHCI_USB_OPER: return "operational"; + case OHCI_USB_SUSPEND: return "suspend"; + } + return "?"; +} + +static const char *rh_state_string(struct ohci_hcd *ohci) +{ + switch (ohci->rh_state) { + case OHCI_RH_HALTED: + return "halted"; + case OHCI_RH_SUSPENDED: + return "suspended"; + case OHCI_RH_RUNNING: + return "running"; + } + return "?"; +} + +// dump control and status registers +static void +ohci_dump_status (struct ohci_hcd *controller, char **next, unsigned *size) +{ + struct ohci_regs __iomem *regs = controller->regs; + u32 temp; + + temp = ohci_readl (controller, ®s->revision) & 0xff; + ohci_dbg_sw (controller, next, size, + "OHCI %d.%d, %s legacy support registers, rh state %s\n", + 0x03 & (temp >> 4), (temp & 0x0f), + (temp & 0x0100) ? "with" : "NO", + rh_state_string(controller)); + + temp = ohci_readl (controller, ®s->control); + ohci_dbg_sw (controller, next, size, + "control 0x%03x%s%s%s HCFS=%s%s%s%s%s CBSR=%d\n", + temp, + (temp & OHCI_CTRL_RWE) ? " RWE" : "", + (temp & OHCI_CTRL_RWC) ? " RWC" : "", + (temp & OHCI_CTRL_IR) ? " IR" : "", + hcfs2string (temp & OHCI_CTRL_HCFS), + (temp & OHCI_CTRL_BLE) ? " BLE" : "", + (temp & OHCI_CTRL_CLE) ? " CLE" : "", + (temp & OHCI_CTRL_IE) ? " IE" : "", + (temp & OHCI_CTRL_PLE) ? " PLE" : "", + temp & OHCI_CTRL_CBSR + ); + + temp = ohci_readl (controller, ®s->cmdstatus); + ohci_dbg_sw (controller, next, size, + "cmdstatus 0x%05x SOC=%d%s%s%s%s\n", temp, + (temp & OHCI_SOC) >> 16, + (temp & OHCI_OCR) ? " OCR" : "", + (temp & OHCI_BLF) ? " BLF" : "", + (temp & OHCI_CLF) ? " CLF" : "", + (temp & OHCI_HCR) ? " HCR" : "" + ); + + ohci_dump_intr_mask (controller, "intrstatus", + ohci_readl (controller, ®s->intrstatus), + next, size); + ohci_dump_intr_mask (controller, "intrenable", + ohci_readl (controller, ®s->intrenable), + next, size); + // intrdisable always same as intrenable + + maybe_print_eds (controller, "ed_periodcurrent", + ohci_readl (controller, ®s->ed_periodcurrent), + next, size); + + maybe_print_eds (controller, "ed_controlhead", + ohci_readl (controller, ®s->ed_controlhead), + next, size); + maybe_print_eds (controller, "ed_controlcurrent", + ohci_readl (controller, ®s->ed_controlcurrent), + next, size); + + maybe_print_eds (controller, "ed_bulkhead", + ohci_readl (controller, ®s->ed_bulkhead), + next, size); + maybe_print_eds (controller, "ed_bulkcurrent", + ohci_readl (controller, ®s->ed_bulkcurrent), + next, size); + + maybe_print_eds (controller, "donehead", + ohci_readl (controller, ®s->donehead), next, size); +} + +#define dbg_port_sw(hc,num,value,next,size) \ + ohci_dbg_sw (hc, next, size, \ + "roothub.portstatus [%d] " \ + "0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \ + num, temp, \ + (temp & RH_PS_PRSC) ? " PRSC" : "", \ + (temp & RH_PS_OCIC) ? " OCIC" : "", \ + (temp & RH_PS_PSSC) ? " PSSC" : "", \ + (temp & RH_PS_PESC) ? " PESC" : "", \ + (temp & RH_PS_CSC) ? " CSC" : "", \ + \ + (temp & RH_PS_LSDA) ? " LSDA" : "", \ + (temp & RH_PS_PPS) ? " PPS" : "", \ + (temp & RH_PS_PRS) ? " PRS" : "", \ + (temp & RH_PS_POCI) ? " POCI" : "", \ + (temp & RH_PS_PSS) ? " PSS" : "", \ + \ + (temp & RH_PS_PES) ? " PES" : "", \ + (temp & RH_PS_CCS) ? " CCS" : "" \ + ); + + +static void +ohci_dump_roothub ( + struct ohci_hcd *controller, + int verbose, + char **next, + unsigned *size) +{ + u32 temp, i; + + temp = roothub_a (controller); + if (temp == ~(u32)0) + return; + + if (verbose) { + ohci_dbg_sw (controller, next, size, + "roothub.a %08x POTPGT=%d%s%s%s%s%s NDP=%d(%d)\n", temp, + ((temp & RH_A_POTPGT) >> 24) & 0xff, + (temp & RH_A_NOCP) ? " NOCP" : "", + (temp & RH_A_OCPM) ? " OCPM" : "", + (temp & RH_A_DT) ? " DT" : "", + (temp & RH_A_NPS) ? " NPS" : "", + (temp & RH_A_PSM) ? " PSM" : "", + (temp & RH_A_NDP), controller->num_ports + ); + temp = roothub_b (controller); + ohci_dbg_sw (controller, next, size, + "roothub.b %08x PPCM=%04x DR=%04x\n", + temp, + (temp & RH_B_PPCM) >> 16, + (temp & RH_B_DR) + ); + temp = roothub_status (controller); + ohci_dbg_sw (controller, next, size, + "roothub.status %08x%s%s%s%s%s%s\n", + temp, + (temp & RH_HS_CRWE) ? " CRWE" : "", + (temp & RH_HS_OCIC) ? " OCIC" : "", + (temp & RH_HS_LPSC) ? " LPSC" : "", + (temp & RH_HS_DRWE) ? " DRWE" : "", + (temp & RH_HS_OCI) ? " OCI" : "", + (temp & RH_HS_LPS) ? " LPS" : "" + ); + } + + for (i = 0; i < controller->num_ports; i++) { + temp = roothub_portstatus (controller, i); + dbg_port_sw (controller, i, temp, next, size); + } +} + +static void ohci_dump (struct ohci_hcd *controller, int verbose) +{ + ohci_dbg (controller, "OHCI controller state\n"); + + // dumps some of the state we know about + ohci_dump_status (controller, NULL, NULL); + if (controller->hcca) + ohci_dbg (controller, + "hcca frame #%04x\n", ohci_frame_no(controller)); + ohci_dump_roothub (controller, 1, NULL, NULL); +} + +static const char data0 [] = "DATA0"; +static const char data1 [] = "DATA1"; + +static void ohci_dump_td (const struct ohci_hcd *ohci, const char *label, + const struct td *td) +{ + u32 tmp = hc32_to_cpup (ohci, &td->hwINFO); + + ohci_dbg (ohci, "%s td %p%s; urb %p index %d; hw next td %08x\n", + label, td, + (tmp & TD_DONE) ? " (DONE)" : "", + td->urb, td->index, + hc32_to_cpup (ohci, &td->hwNextTD)); + if ((tmp & TD_ISO) == 0) { + const char *toggle, *pid; + u32 cbp, be; + + switch (tmp & TD_T) { + case TD_T_DATA0: toggle = data0; break; + case TD_T_DATA1: toggle = data1; break; + case TD_T_TOGGLE: toggle = "(CARRY)"; break; + default: toggle = "(?)"; break; + } + switch (tmp & TD_DP) { + case TD_DP_SETUP: pid = "SETUP"; break; + case TD_DP_IN: pid = "IN"; break; + case TD_DP_OUT: pid = "OUT"; break; + default: pid = "(bad pid)"; break; + } + ohci_dbg (ohci, " info %08x CC=%x %s DI=%d %s %s\n", tmp, + TD_CC_GET(tmp), /* EC, */ toggle, + (tmp & TD_DI) >> 21, pid, + (tmp & TD_R) ? "R" : ""); + cbp = hc32_to_cpup (ohci, &td->hwCBP); + be = hc32_to_cpup (ohci, &td->hwBE); + ohci_dbg (ohci, " cbp %08x be %08x (len %d)\n", cbp, be, + cbp ? (be + 1 - cbp) : 0); + } else { + unsigned i; + ohci_dbg (ohci, " info %08x CC=%x FC=%d DI=%d SF=%04x\n", tmp, + TD_CC_GET(tmp), + (tmp >> 24) & 0x07, + (tmp & TD_DI) >> 21, + tmp & 0x0000ffff); + ohci_dbg (ohci, " bp0 %08x be %08x\n", + hc32_to_cpup (ohci, &td->hwCBP) & ~0x0fff, + hc32_to_cpup (ohci, &td->hwBE)); + for (i = 0; i < MAXPSW; i++) { + u16 psw = ohci_hwPSW (ohci, td, i); + int cc = (psw >> 12) & 0x0f; + ohci_dbg (ohci, " psw [%d] = %2x, CC=%x %s=%d\n", i, + psw, cc, + (cc >= 0x0e) ? "OFFSET" : "SIZE", + psw & 0x0fff); + } + } +} + +/* caller MUST own hcd spinlock if verbose is set! */ +static void __maybe_unused +ohci_dump_ed (const struct ohci_hcd *ohci, const char *label, + const struct ed *ed, int verbose) +{ + u32 tmp = hc32_to_cpu (ohci, ed->hwINFO); + char *type = ""; + + ohci_dbg (ohci, "%s, ed %p state 0x%x type %s; next ed %08x\n", + label, + ed, ed->state, edstring (ed->type), + hc32_to_cpup (ohci, &ed->hwNextED)); + switch (tmp & (ED_IN|ED_OUT)) { + case ED_OUT: type = "-OUT"; break; + case ED_IN: type = "-IN"; break; + /* else from TDs ... control */ + } + ohci_dbg (ohci, + " info %08x MAX=%d%s%s%s%s EP=%d%s DEV=%d\n", tmp, + 0x03ff & (tmp >> 16), + (tmp & ED_DEQUEUE) ? " DQ" : "", + (tmp & ED_ISO) ? " ISO" : "", + (tmp & ED_SKIP) ? " SKIP" : "", + (tmp & ED_LOWSPEED) ? " LOW" : "", + 0x000f & (tmp >> 7), + type, + 0x007f & tmp); + tmp = hc32_to_cpup (ohci, &ed->hwHeadP); + ohci_dbg (ohci, " tds: head %08x %s%s tail %08x%s\n", + tmp, + (tmp & ED_C) ? data1 : data0, + (tmp & ED_H) ? " HALT" : "", + hc32_to_cpup (ohci, &ed->hwTailP), + verbose ? "" : " (not listing)"); + if (verbose) { + struct list_head *tmp; + + /* use ed->td_list because HC concurrently modifies + * hwNextTD as it accumulates ed_donelist. + */ + list_for_each (tmp, &ed->td_list) { + struct td *td; + td = list_entry (tmp, struct td, td_list); + ohci_dump_td (ohci, " ->", td); + } + } +} + +#else +static inline void ohci_dump (struct ohci_hcd *controller, int verbose) {} + +#undef OHCI_VERBOSE_DEBUG + +#endif /* DEBUG */ + +/*-------------------------------------------------------------------------*/ + +#ifdef STUB_DEBUG_FILES + +static inline void create_debug_files (struct ohci_hcd *bus) { } +static inline void remove_debug_files (struct ohci_hcd *bus) { } + +#else + +static int debug_async_open(struct inode *, struct file *); +static int debug_periodic_open(struct inode *, struct file *); +static int debug_registers_open(struct inode *, struct file *); +static int debug_async_open(struct inode *, struct file *); +static ssize_t debug_output(struct file*, char __user*, size_t, loff_t*); +static int debug_close(struct inode *, struct file *); + +static const struct file_operations debug_async_fops = { + .owner = THIS_MODULE, + .open = debug_async_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_periodic_fops = { + .owner = THIS_MODULE, + .open = debug_periodic_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_registers_fops = { + .owner = THIS_MODULE, + .open = debug_registers_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; + +static struct dentry *ohci_debug_root; + +struct debug_buffer { + ssize_t (*fill_func)(struct debug_buffer *); /* fill method */ + struct ohci_hcd *ohci; + struct mutex mutex; /* protect filling of buffer */ + size_t count; /* number of characters filled into buffer */ + char *page; +}; + +static ssize_t +show_list (struct ohci_hcd *ohci, char *buf, size_t count, struct ed *ed) +{ + unsigned temp, size = count; + + if (!ed) + return 0; + + /* print first --> last */ + while (ed->ed_prev) + ed = ed->ed_prev; + + /* dump a snapshot of the bulk or control schedule */ + while (ed) { + u32 info = hc32_to_cpu (ohci, ed->hwINFO); + u32 headp = hc32_to_cpu (ohci, ed->hwHeadP); + struct list_head *entry; + struct td *td; + + temp = scnprintf (buf, size, + "ed/%p %cs dev%d ep%d%s max %d %08x%s%s %s", + ed, + (info & ED_LOWSPEED) ? 'l' : 'f', + info & 0x7f, + (info >> 7) & 0xf, + (info & ED_IN) ? "in" : "out", + 0x03ff & (info >> 16), + info, + (info & ED_SKIP) ? " s" : "", + (headp & ED_H) ? " H" : "", + (headp & ED_C) ? data1 : data0); + size -= temp; + buf += temp; + + list_for_each (entry, &ed->td_list) { + u32 cbp, be; + + td = list_entry (entry, struct td, td_list); + info = hc32_to_cpup (ohci, &td->hwINFO); + cbp = hc32_to_cpup (ohci, &td->hwCBP); + be = hc32_to_cpup (ohci, &td->hwBE); + temp = scnprintf (buf, size, + "\n\ttd %p %s %d cc=%x urb %p (%08x)", + td, + ({ char *pid; + switch (info & TD_DP) { + case TD_DP_SETUP: pid = "setup"; break; + case TD_DP_IN: pid = "in"; break; + case TD_DP_OUT: pid = "out"; break; + default: pid = "(?)"; break; + } pid;}), + cbp ? (be + 1 - cbp) : 0, + TD_CC_GET (info), td->urb, info); + size -= temp; + buf += temp; + } + + temp = scnprintf (buf, size, "\n"); + size -= temp; + buf += temp; + + ed = ed->ed_next; + } + return count - size; +} + +static ssize_t fill_async_buffer(struct debug_buffer *buf) +{ + struct ohci_hcd *ohci; + size_t temp; + unsigned long flags; + + ohci = buf->ohci; + + /* display control and bulk lists together, for simplicity */ + spin_lock_irqsave (&ohci->lock, flags); + temp = show_list(ohci, buf->page, buf->count, ohci->ed_controltail); + temp += show_list(ohci, buf->page + temp, buf->count - temp, + ohci->ed_bulktail); + spin_unlock_irqrestore (&ohci->lock, flags); + + return temp; +} + +#define DBG_SCHED_LIMIT 64 + +static ssize_t fill_periodic_buffer(struct debug_buffer *buf) +{ + struct ohci_hcd *ohci; + struct ed **seen, *ed; + unsigned long flags; + unsigned temp, size, seen_count; + char *next; + unsigned i; + + if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, GFP_ATOMIC))) + return 0; + seen_count = 0; + + ohci = buf->ohci; + next = buf->page; + size = PAGE_SIZE; + + temp = scnprintf (next, size, "size = %d\n", NUM_INTS); + size -= temp; + next += temp; + + /* dump a snapshot of the periodic schedule (and load) */ + spin_lock_irqsave (&ohci->lock, flags); + for (i = 0; i < NUM_INTS; i++) { + if (!(ed = ohci->periodic [i])) + continue; + + temp = scnprintf (next, size, "%2d [%3d]:", i, ohci->load [i]); + size -= temp; + next += temp; + + do { + temp = scnprintf (next, size, " ed%d/%p", + ed->interval, ed); + size -= temp; + next += temp; + for (temp = 0; temp < seen_count; temp++) { + if (seen [temp] == ed) + break; + } + + /* show more info the first time around */ + if (temp == seen_count) { + u32 info = hc32_to_cpu (ohci, ed->hwINFO); + struct list_head *entry; + unsigned qlen = 0; + + /* qlen measured here in TDs, not urbs */ + list_for_each (entry, &ed->td_list) + qlen++; + + temp = scnprintf (next, size, + " (%cs dev%d ep%d%s-%s qlen %u" + " max %d %08x%s%s)", + (info & ED_LOWSPEED) ? 'l' : 'f', + info & 0x7f, + (info >> 7) & 0xf, + (info & ED_IN) ? "in" : "out", + (info & ED_ISO) ? "iso" : "int", + qlen, + 0x03ff & (info >> 16), + info, + (info & ED_SKIP) ? " K" : "", + (ed->hwHeadP & + cpu_to_hc32(ohci, ED_H)) ? + " H" : ""); + size -= temp; + next += temp; + + if (seen_count < DBG_SCHED_LIMIT) + seen [seen_count++] = ed; + + ed = ed->ed_next; + + } else { + /* we've seen it and what's after */ + temp = 0; + ed = NULL; + } + + } while (ed); + + temp = scnprintf (next, size, "\n"); + size -= temp; + next += temp; + } + spin_unlock_irqrestore (&ohci->lock, flags); + kfree (seen); + + return PAGE_SIZE - size; +} +#undef DBG_SCHED_LIMIT + +static ssize_t fill_registers_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct ohci_hcd *ohci; + struct ohci_regs __iomem *regs; + unsigned long flags; + unsigned temp, size; + char *next; + u32 rdata; + + ohci = buf->ohci; + hcd = ohci_to_hcd(ohci); + regs = ohci->regs; + next = buf->page; + size = PAGE_SIZE; + + spin_lock_irqsave (&ohci->lock, flags); + + /* dump driver info, then registers in spec order */ + + ohci_dbg_nosw(ohci, &next, &size, + "bus %s, device %s\n" + "%s\n" + "%s\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc, + hcd_name); + + if (!HCD_HW_ACCESSIBLE(hcd)) { + size -= scnprintf (next, size, + "SUSPENDED (no register access)\n"); + goto done; + } + + ohci_dump_status(ohci, &next, &size); + + /* hcca */ + if (ohci->hcca) + ohci_dbg_nosw(ohci, &next, &size, + "hcca frame 0x%04x\n", ohci_frame_no(ohci)); + + /* other registers mostly affect frame timings */ + rdata = ohci_readl (ohci, ®s->fminterval); + temp = scnprintf (next, size, + "fmintvl 0x%08x %sFSMPS=0x%04x FI=0x%04x\n", + rdata, (rdata >> 31) ? "FIT " : "", + (rdata >> 16) & 0xefff, rdata & 0xffff); + size -= temp; + next += temp; + + rdata = ohci_readl (ohci, ®s->fmremaining); + temp = scnprintf (next, size, "fmremaining 0x%08x %sFR=0x%04x\n", + rdata, (rdata >> 31) ? "FRT " : "", + rdata & 0x3fff); + size -= temp; + next += temp; + + rdata = ohci_readl (ohci, ®s->periodicstart); + temp = scnprintf (next, size, "periodicstart 0x%04x\n", + rdata & 0x3fff); + size -= temp; + next += temp; + + rdata = ohci_readl (ohci, ®s->lsthresh); + temp = scnprintf (next, size, "lsthresh 0x%04x\n", + rdata & 0x3fff); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "hub poll timer %s\n", + HCD_POLL_RH(ohci_to_hcd(ohci)) ? "ON" : "off"); + size -= temp; + next += temp; + + /* roothub */ + ohci_dump_roothub (ohci, 1, &next, &size); + +done: + spin_unlock_irqrestore (&ohci->lock, flags); + + return PAGE_SIZE - size; +} + +static struct debug_buffer *alloc_buffer(struct ohci_hcd *ohci, + ssize_t (*fill_func)(struct debug_buffer *)) +{ + struct debug_buffer *buf; + + buf = kzalloc(sizeof(struct debug_buffer), GFP_KERNEL); + + if (buf) { + buf->ohci = ohci; + buf->fill_func = fill_func; + mutex_init(&buf->mutex); + } + + return buf; +} + +static int fill_buffer(struct debug_buffer *buf) +{ + int ret = 0; + + if (!buf->page) + buf->page = (char *)get_zeroed_page(GFP_KERNEL); + + if (!buf->page) { + ret = -ENOMEM; + goto out; + } + + ret = buf->fill_func(buf); + + if (ret >= 0) { + buf->count = ret; + ret = 0; + } + +out: + return ret; +} + +static ssize_t debug_output(struct file *file, char __user *user_buf, + size_t len, loff_t *offset) +{ + struct debug_buffer *buf = file->private_data; + int ret = 0; + + mutex_lock(&buf->mutex); + if (buf->count == 0) { + ret = fill_buffer(buf); + if (ret != 0) { + mutex_unlock(&buf->mutex); + goto out; + } + } + mutex_unlock(&buf->mutex); + + ret = simple_read_from_buffer(user_buf, len, offset, + buf->page, buf->count); + +out: + return ret; + +} + +static int debug_close(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf = file->private_data; + + if (buf) { + if (buf->page) + free_page((unsigned long)buf->page); + kfree(buf); + } + + return 0; +} +static int debug_async_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static int debug_periodic_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_periodic_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static int debug_registers_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_registers_buffer); + + return file->private_data ? 0 : -ENOMEM; +} +static inline void create_debug_files (struct ohci_hcd *ohci) +{ + struct usb_bus *bus = &ohci_to_hcd(ohci)->self; + + ohci->debug_dir = debugfs_create_dir(bus->bus_name, ohci_debug_root); + if (!ohci->debug_dir) + goto dir_error; + + ohci->debug_async = debugfs_create_file("async", S_IRUGO, + ohci->debug_dir, ohci, + &debug_async_fops); + if (!ohci->debug_async) + goto async_error; + + ohci->debug_periodic = debugfs_create_file("periodic", S_IRUGO, + ohci->debug_dir, ohci, + &debug_periodic_fops); + if (!ohci->debug_periodic) + goto periodic_error; + + ohci->debug_registers = debugfs_create_file("registers", S_IRUGO, + ohci->debug_dir, ohci, + &debug_registers_fops); + if (!ohci->debug_registers) + goto registers_error; + + ohci_dbg (ohci, "created debug files\n"); + return; + +registers_error: + debugfs_remove(ohci->debug_periodic); +periodic_error: + debugfs_remove(ohci->debug_async); +async_error: + debugfs_remove(ohci->debug_dir); +dir_error: + ohci->debug_periodic = NULL; + ohci->debug_async = NULL; + ohci->debug_dir = NULL; +} + +static inline void remove_debug_files (struct ohci_hcd *ohci) +{ + debugfs_remove(ohci->debug_registers); + debugfs_remove(ohci->debug_periodic); + debugfs_remove(ohci->debug_async); + debugfs_remove(ohci->debug_dir); +} + +#endif + +/*-------------------------------------------------------------------------*/ + diff --git a/drivers/usb/host/ohci-ep93xx.c b/drivers/usb/host/ohci-ep93xx.c new file mode 100644 index 00000000..8704e9fa --- /dev/null +++ b/drivers/usb/host/ohci-ep93xx.c @@ -0,0 +1,217 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * + * Bus Glue for ep93xx. + * + * Written by Christopher Hoover + * Based on fragments of previous driver by Russell King et al. + * + * Modified for LH7A404 from ohci-sa1111.c + * by Durgesh Pattamatta + * + * Modified for pxa27x from ohci-lh7a404.c + * by Nick Bane 26-8-2004 + * + * Modified for ep93xx from ohci-pxa27x.c + * by Lennert Buytenhek 28-2-2006 + * Based on an earlier driver by Ray Lehtiniemi + * + * This file is licenced under the GPL. + */ + +#include +#include +#include +#include + +static struct clk *usb_host_clock; + +static void ep93xx_start_hc(struct device *dev) +{ + clk_enable(usb_host_clock); +} + +static void ep93xx_stop_hc(struct device *dev) +{ + clk_disable(usb_host_clock); +} + +static int usb_hcd_ep93xx_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + int retval; + struct usb_hcd *hcd; + + if (pdev->resource[1].flags != IORESOURCE_IRQ) { + dev_dbg(&pdev->dev, "resource[1] is not IORESOURCE_IRQ\n"); + return -ENOMEM; + } + + hcd = usb_create_hcd(driver, &pdev->dev, "ep93xx"); + if (hcd == NULL) + return -ENOMEM; + + hcd->rsrc_start = pdev->resource[0].start; + hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1; + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + usb_put_hcd(hcd); + retval = -EBUSY; + goto err1; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (hcd->regs == NULL) { + dev_dbg(&pdev->dev, "ioremap failed\n"); + retval = -ENOMEM; + goto err2; + } + + usb_host_clock = clk_get(&pdev->dev, NULL); + if (IS_ERR(usb_host_clock)) { + dev_dbg(&pdev->dev, "clk_get failed\n"); + retval = PTR_ERR(usb_host_clock); + goto err3; + } + + ep93xx_start_hc(&pdev->dev); + + ohci_hcd_init(hcd_to_ohci(hcd)); + + retval = usb_add_hcd(hcd, pdev->resource[1].start, 0); + if (retval == 0) + return retval; + + ep93xx_stop_hc(&pdev->dev); +err3: + iounmap(hcd->regs); +err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err1: + usb_put_hcd(hcd); + + return retval; +} + +static void usb_hcd_ep93xx_remove(struct usb_hcd *hcd, + struct platform_device *pdev) +{ + usb_remove_hcd(hcd); + ep93xx_stop_hc(&pdev->dev); + clk_put(usb_host_clock); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + +static int ohci_ep93xx_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + if ((ret = ohci_run(ohci)) < 0) { + dev_err(hcd->self.controller, "can't start %s\n", + hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + +static struct hc_driver ohci_ep93xx_hc_driver = { + .description = hcd_name, + .product_desc = "EP93xx OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + .start = ohci_ep93xx_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + .get_frame_number = ohci_get_frame, + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +extern int usb_disabled(void); + +static int ohci_hcd_ep93xx_drv_probe(struct platform_device *pdev) +{ + int ret; + + ret = -ENODEV; + if (!usb_disabled()) + ret = usb_hcd_ep93xx_probe(&ohci_ep93xx_hc_driver, pdev); + + return ret; +} + +static int ohci_hcd_ep93xx_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_hcd_ep93xx_remove(hcd, pdev); + + return 0; +} + +#ifdef CONFIG_PM +static int ohci_hcd_ep93xx_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ep93xx_stop_hc(&pdev->dev); + return 0; +} + +static int ohci_hcd_ep93xx_drv_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ep93xx_start_hc(&pdev->dev); + + ohci_resume(hcd, false); + return 0; +} +#endif + + +static struct platform_driver ohci_hcd_ep93xx_driver = { + .probe = ohci_hcd_ep93xx_drv_probe, + .remove = ohci_hcd_ep93xx_drv_remove, + .shutdown = usb_hcd_platform_shutdown, +#ifdef CONFIG_PM + .suspend = ohci_hcd_ep93xx_drv_suspend, + .resume = ohci_hcd_ep93xx_drv_resume, +#endif + .driver = { + .name = "ep93xx-ohci", + .owner = THIS_MODULE, + }, +}; + +MODULE_ALIAS("platform:ep93xx-ohci"); diff --git a/drivers/usb/host/ohci-exynos.c b/drivers/usb/host/ohci-exynos.c new file mode 100644 index 00000000..b0b542c1 --- /dev/null +++ b/drivers/usb/host/ohci-exynos.c @@ -0,0 +1,336 @@ +/* + * SAMSUNG EXYNOS USB HOST OHCI Controller + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Author: Jingoo Han + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +struct exynos_ohci_hcd { + struct device *dev; + struct usb_hcd *hcd; + struct clk *clk; + struct usb_phy *phy; + struct usb_otg *otg; + struct exynos4_ohci_platdata *pdata; +}; + +static void exynos_ohci_phy_enable(struct exynos_ohci_hcd *exynos_ohci) +{ + struct platform_device *pdev = to_platform_device(exynos_ohci->dev); + + if (exynos_ohci->phy) + usb_phy_init(exynos_ohci->phy); + else if (exynos_ohci->pdata && exynos_ohci->pdata->phy_init) + exynos_ohci->pdata->phy_init(pdev, USB_PHY_TYPE_HOST); +} + +static void exynos_ohci_phy_disable(struct exynos_ohci_hcd *exynos_ohci) +{ + struct platform_device *pdev = to_platform_device(exynos_ohci->dev); + + if (exynos_ohci->phy) + usb_phy_shutdown(exynos_ohci->phy); + else if (exynos_ohci->pdata && exynos_ohci->pdata->phy_exit) + exynos_ohci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST); +} + +static int ohci_exynos_reset(struct usb_hcd *hcd) +{ + return ohci_init(hcd_to_ohci(hcd)); +} + +static int ohci_exynos_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ohci_dbg(ohci, "ohci_exynos_start, ohci:%p", ohci); + + ret = ohci_run(ohci); + if (ret < 0) { + dev_err(hcd->self.controller, "can't start %s\n", + hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + +static const struct hc_driver exynos_ohci_hc_driver = { + .description = hcd_name, + .product_desc = "EXYNOS OHCI Host Controller", + .hcd_priv_size = sizeof(struct ohci_hcd), + + .irq = ohci_irq, + .flags = HCD_MEMORY|HCD_USB11, + + .reset = ohci_exynos_reset, + .start = ohci_exynos_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + .get_frame_number = ohci_get_frame, + + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +static int exynos_ohci_probe(struct platform_device *pdev) +{ + struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data; + struct exynos_ohci_hcd *exynos_ohci; + struct usb_hcd *hcd; + struct ohci_hcd *ohci; + struct resource *res; + struct usb_phy *phy; + int irq; + int err; + + /* + * 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 move to full device tree support this will vanish off. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + exynos_ohci = devm_kzalloc(&pdev->dev, sizeof(struct exynos_ohci_hcd), + GFP_KERNEL); + if (!exynos_ohci) + return -ENOMEM; + + if (of_device_is_compatible(pdev->dev.of_node, + "samsung,exynos5440-ohci")) + goto skip_phy; + + phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + if (IS_ERR(phy)) { + /* Fallback to pdata */ + if (!pdata) { + dev_warn(&pdev->dev, "no platform data or transceiver defined\n"); + return -EPROBE_DEFER; + } else { + exynos_ohci->pdata = pdata; + } + } else { + exynos_ohci->phy = phy; + exynos_ohci->otg = phy->otg; + } + +skip_phy: + + exynos_ohci->dev = &pdev->dev; + + hcd = usb_create_hcd(&exynos_ohci_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + return -ENOMEM; + } + + exynos_ohci->hcd = hcd; + exynos_ohci->clk = devm_clk_get(&pdev->dev, "usbhost"); + + if (IS_ERR(exynos_ohci->clk)) { + dev_err(&pdev->dev, "Failed to get usbhost clock\n"); + err = PTR_ERR(exynos_ohci->clk); + goto fail_clk; + } + + err = clk_prepare_enable(exynos_ohci->clk); + if (err) + goto fail_clk; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + err = -ENXIO; + goto fail_io; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = devm_ioremap(&pdev->dev, res->start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + err = -ENOMEM; + goto fail_io; + } + + irq = platform_get_irq(pdev, 0); + if (!irq) { + dev_err(&pdev->dev, "Failed to get IRQ\n"); + err = -ENODEV; + goto fail_io; + } + + if (exynos_ohci->otg) + exynos_ohci->otg->set_host(exynos_ohci->otg, + &exynos_ohci->hcd->self); + + exynos_ohci_phy_enable(exynos_ohci); + + ohci = hcd_to_ohci(hcd); + ohci_hcd_init(ohci); + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) { + dev_err(&pdev->dev, "Failed to add USB HCD\n"); + goto fail_add_hcd; + } + + platform_set_drvdata(pdev, exynos_ohci); + + return 0; + +fail_add_hcd: + exynos_ohci_phy_disable(exynos_ohci); +fail_io: + clk_disable_unprepare(exynos_ohci->clk); +fail_clk: + usb_put_hcd(hcd); + return err; +} + +static int exynos_ohci_remove(struct platform_device *pdev) +{ + struct exynos_ohci_hcd *exynos_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = exynos_ohci->hcd; + + usb_remove_hcd(hcd); + + if (exynos_ohci->otg) + exynos_ohci->otg->set_host(exynos_ohci->otg, + &exynos_ohci->hcd->self); + + exynos_ohci_phy_disable(exynos_ohci); + + clk_disable_unprepare(exynos_ohci->clk); + + usb_put_hcd(hcd); + + return 0; +} + +static void exynos_ohci_shutdown(struct platform_device *pdev) +{ + struct exynos_ohci_hcd *exynos_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = exynos_ohci->hcd; + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +#ifdef CONFIG_PM +static int exynos_ohci_suspend(struct device *dev) +{ + struct exynos_ohci_hcd *exynos_ohci = dev_get_drvdata(dev); + struct usb_hcd *hcd = exynos_ohci->hcd; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + unsigned long flags; + int rc = 0; + + /* + * Root hub was already suspended. Disable irq emission and + * mark HW unaccessible, bail out if RH has been resumed. Use + * the spinlock to properly synchronize with possible pending + * RH suspend or resume activity. + */ + spin_lock_irqsave(&ohci->lock, flags); + if (ohci->rh_state != OHCI_RH_SUSPENDED && + ohci->rh_state != OHCI_RH_HALTED) { + rc = -EINVAL; + goto fail; + } + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + if (exynos_ohci->otg) + exynos_ohci->otg->set_host(exynos_ohci->otg, + &exynos_ohci->hcd->self); + + exynos_ohci_phy_disable(exynos_ohci); + + clk_disable_unprepare(exynos_ohci->clk); + +fail: + spin_unlock_irqrestore(&ohci->lock, flags); + + return rc; +} + +static int exynos_ohci_resume(struct device *dev) +{ + struct exynos_ohci_hcd *exynos_ohci = dev_get_drvdata(dev); + struct usb_hcd *hcd = exynos_ohci->hcd; + + clk_prepare_enable(exynos_ohci->clk); + + if (exynos_ohci->otg) + exynos_ohci->otg->set_host(exynos_ohci->otg, + &exynos_ohci->hcd->self); + + exynos_ohci_phy_enable(exynos_ohci); + + ohci_resume(hcd, false); + + return 0; +} +#else +#define exynos_ohci_suspend NULL +#define exynos_ohci_resume NULL +#endif + +static const struct dev_pm_ops exynos_ohci_pm_ops = { + .suspend = exynos_ohci_suspend, + .resume = exynos_ohci_resume, +}; + +#ifdef CONFIG_OF +static const struct of_device_id exynos_ohci_match[] = { + { .compatible = "samsung,exynos4210-ohci" }, + { .compatible = "samsung,exynos5440-ohci" }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_ohci_match); +#endif + +static struct platform_driver exynos_ohci_driver = { + .probe = exynos_ohci_probe, + .remove = exynos_ohci_remove, + .shutdown = exynos_ohci_shutdown, + .driver = { + .name = "exynos-ohci", + .owner = THIS_MODULE, + .pm = &exynos_ohci_pm_ops, + .of_match_table = of_match_ptr(exynos_ohci_match), + } +}; + +MODULE_ALIAS("platform:exynos-ohci"); +MODULE_AUTHOR("Jingoo Han "); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c new file mode 100644 index 00000000..8c57e970 --- /dev/null +++ b/drivers/usb/host/ohci-hcd.c @@ -0,0 +1,1449 @@ +/* + * Open Host Controller Interface (OHCI) driver for USB. + * + * Maintainer: Alan Stern + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2004 David Brownell + * + * [ Initialisation is based on Linus' ] + * [ uhci code and gregs ohci fragments ] + * [ (C) Copyright 1999 Linus Torvalds ] + * [ (C) Copyright 1999 Gregory P. Smith] + * + * + * OHCI is the main "non-Intel/VIA" standard for USB 1.1 host controller + * interfaces (though some non-x86 Intel chips use it). It supports + * smarter hardware than UHCI. A download link for the spec available + * through the http://www.usb.org website. + * + * This file is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define DRIVER_AUTHOR "Roman Weissgaerber, David Brownell" +#define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver" + +/*-------------------------------------------------------------------------*/ + +#undef OHCI_VERBOSE_DEBUG /* not always helpful */ + +/* For initializing controller (mask in an HCFS mode too) */ +#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR +#define OHCI_INTR_INIT \ + (OHCI_INTR_MIE | OHCI_INTR_RHSC | OHCI_INTR_UE \ + | OHCI_INTR_RD | OHCI_INTR_WDH) + +#ifdef __hppa__ +/* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ +#define IR_DISABLE +#endif + +#ifdef CONFIG_ARCH_OMAP +/* OMAP doesn't support IR (no SMM; not needed) */ +#define IR_DISABLE +#endif + +/*-------------------------------------------------------------------------*/ + +static const char hcd_name [] = "ohci_hcd"; + +#define STATECHANGE_DELAY msecs_to_jiffies(300) + +#include "ohci.h" +#include "pci-quirks.h" + +static void ohci_dump (struct ohci_hcd *ohci, int verbose); +static int ohci_init (struct ohci_hcd *ohci); +static void ohci_stop (struct usb_hcd *hcd); + +#if defined(CONFIG_PM) || defined(CONFIG_PCI) +static int ohci_restart (struct ohci_hcd *ohci); +#endif + +#ifdef CONFIG_PCI +static void sb800_prefetch(struct ohci_hcd *ohci, int on); +#else +static inline void sb800_prefetch(struct ohci_hcd *ohci, int on) +{ + return; +} +#endif + + +#include "ohci-hub.c" +#include "ohci-dbg.c" +#include "ohci-mem.c" +#include "ohci-q.c" + + +/* + * On architectures with edge-triggered interrupts we must never return + * IRQ_NONE. + */ +#if defined(CONFIG_SA1111) /* ... or other edge-triggered systems */ +#define IRQ_NOTMINE IRQ_HANDLED +#else +#define IRQ_NOTMINE IRQ_NONE +#endif + + +/* Some boards misreport power switching/overcurrent */ +static bool distrust_firmware = 1; +module_param (distrust_firmware, bool, 0); +MODULE_PARM_DESC (distrust_firmware, + "true to distrust firmware power/overcurrent setup"); + +/* Some boards leave IR set wrongly, since they fail BIOS/SMM handshakes */ +static bool no_handshake = 0; +module_param (no_handshake, bool, 0); +MODULE_PARM_DESC (no_handshake, "true (not default) disables BIOS handshake"); + +/*-------------------------------------------------------------------------*/ + +/* + * queue up an urb for anything except the root hub + */ +static int ohci_urb_enqueue ( + struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags +) { + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct ed *ed; + urb_priv_t *urb_priv; + unsigned int pipe = urb->pipe; + int i, size = 0; + unsigned long flags; + int retval = 0; + +#ifdef OHCI_VERBOSE_DEBUG + urb_print(urb, "SUB", usb_pipein(pipe), -EINPROGRESS); +#endif + + /* every endpoint has a ed, locate and maybe (re)initialize it */ + if (! (ed = ed_get (ohci, urb->ep, urb->dev, pipe, urb->interval))) + return -ENOMEM; + + /* for the private part of the URB we need the number of TDs (size) */ + switch (ed->type) { + case PIPE_CONTROL: + /* td_submit_urb() doesn't yet handle these */ + if (urb->transfer_buffer_length > 4096) + return -EMSGSIZE; + + /* 1 TD for setup, 1 for ACK, plus ... */ + size = 2; + /* FALLTHROUGH */ + // case PIPE_INTERRUPT: + // case PIPE_BULK: + default: + /* one TD for every 4096 Bytes (can be up to 8K) */ + size += urb->transfer_buffer_length / 4096; + /* ... and for any remaining bytes ... */ + if ((urb->transfer_buffer_length % 4096) != 0) + size++; + /* ... and maybe a zero length packet to wrap it up */ + if (size == 0) + size++; + else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 + && (urb->transfer_buffer_length + % usb_maxpacket (urb->dev, pipe, + usb_pipeout (pipe))) == 0) + size++; + break; + case PIPE_ISOCHRONOUS: /* number of packets from URB */ + size = urb->number_of_packets; + break; + } + + /* allocate the private part of the URB */ + urb_priv = kzalloc (sizeof (urb_priv_t) + size * sizeof (struct td *), + mem_flags); + if (!urb_priv) + return -ENOMEM; + INIT_LIST_HEAD (&urb_priv->pending); + urb_priv->length = size; + urb_priv->ed = ed; + + /* allocate the TDs (deferring hash chain updates) */ + for (i = 0; i < size; i++) { + urb_priv->td [i] = td_alloc (ohci, mem_flags); + if (!urb_priv->td [i]) { + urb_priv->length = i; + urb_free_priv (ohci, urb_priv); + return -ENOMEM; + } + } + + spin_lock_irqsave (&ohci->lock, flags); + + /* don't submit to a dead HC */ + if (!HCD_HW_ACCESSIBLE(hcd)) { + retval = -ENODEV; + goto fail; + } + if (ohci->rh_state != OHCI_RH_RUNNING) { + retval = -ENODEV; + goto fail; + } + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) + goto fail; + + /* schedule the ed if needed */ + if (ed->state == ED_IDLE) { + retval = ed_schedule (ohci, ed); + if (retval < 0) { + usb_hcd_unlink_urb_from_ep(hcd, urb); + goto fail; + } + if (ed->type == PIPE_ISOCHRONOUS) { + u16 frame = ohci_frame_no(ohci); + + /* delay a few frames before the first TD */ + frame += max_t (u16, 8, ed->interval); + frame &= ~(ed->interval - 1); + frame |= ed->branch; + urb->start_frame = frame; + ed->last_iso = frame + ed->interval * (size - 1); + } + } else if (ed->type == PIPE_ISOCHRONOUS) { + u16 next = ohci_frame_no(ohci) + 1; + u16 frame = ed->last_iso + ed->interval; + u16 length = ed->interval * (size - 1); + + /* Behind the scheduling threshold? */ + if (unlikely(tick_before(frame, next))) { + + /* URB_ISO_ASAP: Round up to the first available slot */ + if (urb->transfer_flags & URB_ISO_ASAP) { + frame += (next - frame + ed->interval - 1) & + -ed->interval; + + /* + * Not ASAP: Use the next slot in the stream, + * no matter what. + */ + } else { + /* + * Some OHCI hardware doesn't handle late TDs + * correctly. After retiring them it proceeds + * to the next ED instead of the next TD. + * Therefore we have to omit the late TDs + * entirely. + */ + urb_priv->td_cnt = DIV_ROUND_UP( + (u16) (next - frame), + ed->interval); + if (urb_priv->td_cnt >= urb_priv->length) { + ++urb_priv->td_cnt; /* Mark it */ + ohci_dbg(ohci, "iso underrun %p (%u+%u < %u)\n", + urb, frame, length, + next); + } + } + } + urb->start_frame = frame; + ed->last_iso = frame + length; + } + + /* fill the TDs and link them to the ed; and + * enable that part of the schedule, if needed + * and update count of queued periodic urbs + */ + urb->hcpriv = urb_priv; + td_submit_urb (ohci, urb); + +fail: + if (retval) + urb_free_priv (ohci, urb_priv); + spin_unlock_irqrestore (&ohci->lock, flags); + return retval; +} + +/* + * decouple the URB from the HC queues (TDs, urb_priv). + * reporting is always done + * asynchronously, and we might be dealing with an urb that's + * partially transferred, or an ED with other urbs being unlinked. + */ +static int ohci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + unsigned long flags; + int rc; + +#ifdef OHCI_VERBOSE_DEBUG + urb_print(urb, "UNLINK", 1, status); +#endif + + spin_lock_irqsave (&ohci->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) { + ; /* Do nothing */ + } else if (ohci->rh_state == OHCI_RH_RUNNING) { + urb_priv_t *urb_priv; + + /* Unless an IRQ completed the unlink while it was being + * handed to us, flag it for unlink and giveback, and force + * some upcoming INTR_SF to call finish_unlinks() + */ + urb_priv = urb->hcpriv; + if (urb_priv) { + if (urb_priv->ed->state == ED_OPER) + start_ed_unlink (ohci, urb_priv->ed); + } + } else { + /* + * with HC dead, we won't respect hc queue pointers + * any more ... just clean up every urb's memory. + */ + if (urb->hcpriv) + finish_urb(ohci, urb, status); + } + spin_unlock_irqrestore (&ohci->lock, flags); + return rc; +} + +/*-------------------------------------------------------------------------*/ + +/* frees config/altsetting state for endpoints, + * including ED memory, dummy TD, and bulk/intr data toggle + */ + +static void +ohci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + unsigned long flags; + struct ed *ed = ep->hcpriv; + unsigned limit = 1000; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + + if (!ed) + return; + +rescan: + spin_lock_irqsave (&ohci->lock, flags); + + if (ohci->rh_state != OHCI_RH_RUNNING) { +sanitize: + ed->state = ED_IDLE; + if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT) + ohci->eds_scheduled--; + finish_unlinks (ohci, 0); + } + + switch (ed->state) { + case ED_UNLINK: /* wait for hw to finish? */ + /* major IRQ delivery trouble loses INTR_SF too... */ + if (limit-- == 0) { + ohci_warn(ohci, "ED unlink timeout\n"); + if (quirk_zfmicro(ohci)) { + ohci_warn(ohci, "Attempting ZF TD recovery\n"); + ohci->ed_to_check = ed; + ohci->zf_delay = 2; + } + goto sanitize; + } + spin_unlock_irqrestore (&ohci->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case ED_IDLE: /* fully unlinked */ + if (list_empty (&ed->td_list)) { + td_free (ohci, ed->dummy); + ed_free (ohci, ed); + break; + } + /* else FALL THROUGH */ + default: + /* caller was supposed to have unlinked any requests; + * that's not our job. can't recover; must leak ed. + */ + ohci_err (ohci, "leak ed %p (#%02x) state %d%s\n", + ed, ep->desc.bEndpointAddress, ed->state, + list_empty (&ed->td_list) ? "" : " (has tds)"); + td_free (ohci, ed->dummy); + break; + } + ep->hcpriv = NULL; + spin_unlock_irqrestore (&ohci->lock, flags); +} + +static int ohci_get_frame (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + return ohci_frame_no(ohci); +} + +static void ohci_usb_reset (struct ohci_hcd *ohci) +{ + ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); + ohci->hc_control &= OHCI_CTRL_RWC; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + ohci->rh_state = OHCI_RH_HALTED; +} + +/* ohci_shutdown forcibly disables IRQs and DMA, helping kexec and + * other cases where the next software may expect clean state from the + * "firmware". this is bus-neutral, unlike shutdown() methods. + */ +static void +ohci_shutdown (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci; + + ohci = hcd_to_ohci (hcd); + ohci_writel(ohci, (u32) ~0, &ohci->regs->intrdisable); + + /* Software reset, after which the controller goes into SUSPEND */ + ohci_writel(ohci, OHCI_HCR, &ohci->regs->cmdstatus); + ohci_readl(ohci, &ohci->regs->cmdstatus); /* flush the writes */ + udelay(10); + + ohci_writel(ohci, ohci->fminterval, &ohci->regs->fminterval); +} + +static int check_ed(struct ohci_hcd *ohci, struct ed *ed) +{ + return (hc32_to_cpu(ohci, ed->hwINFO) & ED_IN) != 0 + && (hc32_to_cpu(ohci, ed->hwHeadP) & TD_MASK) + == (hc32_to_cpu(ohci, ed->hwTailP) & TD_MASK) + && !list_empty(&ed->td_list); +} + +/* ZF Micro watchdog timer callback. The ZF Micro chipset sometimes completes + * an interrupt TD but neglects to add it to the donelist. On systems with + * this chipset, we need to periodically check the state of the queues to look + * for such "lost" TDs. + */ +static void unlink_watchdog_func(unsigned long _ohci) +{ + unsigned long flags; + unsigned max; + unsigned seen_count = 0; + unsigned i; + struct ed **seen = NULL; + struct ohci_hcd *ohci = (struct ohci_hcd *) _ohci; + + spin_lock_irqsave(&ohci->lock, flags); + max = ohci->eds_scheduled; + if (!max) + goto done; + + if (ohci->ed_to_check) + goto out; + + seen = kcalloc(max, sizeof *seen, GFP_ATOMIC); + if (!seen) + goto out; + + for (i = 0; i < NUM_INTS; i++) { + struct ed *ed = ohci->periodic[i]; + + while (ed) { + unsigned temp; + + /* scan this branch of the periodic schedule tree */ + for (temp = 0; temp < seen_count; temp++) { + if (seen[temp] == ed) { + /* we've checked it and what's after */ + ed = NULL; + break; + } + } + if (!ed) + break; + seen[seen_count++] = ed; + if (!check_ed(ohci, ed)) { + ed = ed->ed_next; + continue; + } + + /* HC's TD list is empty, but HCD sees at least one + * TD that's not been sent through the donelist. + */ + ohci->ed_to_check = ed; + ohci->zf_delay = 2; + + /* The HC may wait until the next frame to report the + * TD as done through the donelist and INTR_WDH. (We + * just *assume* it's not a multi-TD interrupt URB; + * those could defer the IRQ more than one frame, using + * DI...) Check again after the next INTR_SF. + */ + ohci_writel(ohci, OHCI_INTR_SF, + &ohci->regs->intrstatus); + ohci_writel(ohci, OHCI_INTR_SF, + &ohci->regs->intrenable); + + /* flush those writes */ + (void) ohci_readl(ohci, &ohci->regs->control); + + goto out; + } + } +out: + kfree(seen); + if (ohci->eds_scheduled) + mod_timer(&ohci->unlink_watchdog, round_jiffies(jiffies + HZ)); +done: + spin_unlock_irqrestore(&ohci->lock, flags); +} + +/*-------------------------------------------------------------------------* + * HC functions + *-------------------------------------------------------------------------*/ + +/* init memory, and kick BIOS/SMM off */ + +static int ohci_init (struct ohci_hcd *ohci) +{ + int ret; + struct usb_hcd *hcd = ohci_to_hcd(ohci); + + if (distrust_firmware) + ohci->flags |= OHCI_QUIRK_HUB_POWER; + + ohci->rh_state = OHCI_RH_HALTED; + ohci->regs = hcd->regs; + + /* REVISIT this BIOS handshake is now moved into PCI "quirks", and + * was never needed for most non-PCI systems ... remove the code? + */ + +#ifndef IR_DISABLE + /* SMM owns the HC? not for long! */ + if (!no_handshake && ohci_readl (ohci, + &ohci->regs->control) & OHCI_CTRL_IR) { + u32 temp; + + ohci_dbg (ohci, "USB HC TakeOver from BIOS/SMM\n"); + + /* this timeout is arbitrary. we make it long, so systems + * depending on usb keyboards may be usable even if the + * BIOS/SMM code seems pretty broken. + */ + temp = 500; /* arbitrary: five seconds */ + + ohci_writel (ohci, OHCI_INTR_OC, &ohci->regs->intrenable); + ohci_writel (ohci, OHCI_OCR, &ohci->regs->cmdstatus); + while (ohci_readl (ohci, &ohci->regs->control) & OHCI_CTRL_IR) { + msleep (10); + if (--temp == 0) { + ohci_err (ohci, "USB HC takeover failed!" + " (BIOS/SMM bug)\n"); + return -EBUSY; + } + } + ohci_usb_reset (ohci); + } +#endif + + /* Disable HC interrupts */ + ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); + + /* flush the writes, and save key bits like RWC */ + if (ohci_readl (ohci, &ohci->regs->control) & OHCI_CTRL_RWC) + ohci->hc_control |= OHCI_CTRL_RWC; + + /* Read the number of ports unless overridden */ + if (ohci->num_ports == 0) + ohci->num_ports = roothub_a(ohci) & RH_A_NDP; + + if (ohci->hcca) + return 0; + + ohci->hcca = dma_alloc_coherent (hcd->self.controller, + sizeof *ohci->hcca, &ohci->hcca_dma, 0); + if (!ohci->hcca) + return -ENOMEM; + + if ((ret = ohci_mem_init (ohci)) < 0) + ohci_stop (hcd); + else { + create_debug_files (ohci); + } + + return ret; +} + +/*-------------------------------------------------------------------------*/ + +/* Start an OHCI controller, set the BUS operational + * resets USB and controller + * enable interrupts + */ +static int ohci_run (struct ohci_hcd *ohci) +{ + u32 mask, val; + int first = ohci->fminterval == 0; + struct usb_hcd *hcd = ohci_to_hcd(ohci); + + ohci->rh_state = OHCI_RH_HALTED; + + /* boot firmware should have set this up (5.1.1.3.1) */ + if (first) { + + val = ohci_readl (ohci, &ohci->regs->fminterval); + ohci->fminterval = val & 0x3fff; + if (ohci->fminterval != FI) + ohci_dbg (ohci, "fminterval delta %d\n", + ohci->fminterval - FI); + ohci->fminterval |= FSMP (ohci->fminterval) << 16; + /* also: power/overcurrent flags in roothub.a */ + } + + /* Reset USB nearly "by the book". RemoteWakeupConnected has + * to be checked in case boot firmware (BIOS/SMM/...) has set up + * wakeup in a way the bus isn't aware of (e.g., legacy PCI PM). + * If the bus glue detected wakeup capability then it should + * already be enabled; if so we'll just enable it again. + */ + if ((ohci->hc_control & OHCI_CTRL_RWC) != 0) + device_set_wakeup_capable(hcd->self.controller, 1); + + switch (ohci->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_OPER: + val = 0; + break; + case OHCI_USB_SUSPEND: + case OHCI_USB_RESUME: + ohci->hc_control &= OHCI_CTRL_RWC; + ohci->hc_control |= OHCI_USB_RESUME; + val = 10 /* msec wait */; + break; + // case OHCI_USB_RESET: + default: + ohci->hc_control &= OHCI_CTRL_RWC; + ohci->hc_control |= OHCI_USB_RESET; + val = 50 /* msec wait */; + break; + } + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + // flush the writes + (void) ohci_readl (ohci, &ohci->regs->control); + msleep(val); + + memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); + + /* 2msec timelimit here means no irqs/preempt */ + spin_lock_irq (&ohci->lock); + +retry: + /* HC Reset requires max 10 us delay */ + ohci_writel (ohci, OHCI_HCR, &ohci->regs->cmdstatus); + val = 30; /* ... allow extra time */ + while ((ohci_readl (ohci, &ohci->regs->cmdstatus) & OHCI_HCR) != 0) { + if (--val == 0) { + spin_unlock_irq (&ohci->lock); + ohci_err (ohci, "USB HC reset timed out!\n"); + return -1; + } + udelay (1); + } + + /* now we're in the SUSPEND state ... must go OPERATIONAL + * within 2msec else HC enters RESUME + * + * ... but some hardware won't init fmInterval "by the book" + * (SiS, OPTi ...), so reset again instead. SiS doesn't need + * this if we write fmInterval after we're OPERATIONAL. + * Unclear about ALi, ServerWorks, and others ... this could + * easily be a longstanding bug in chip init on Linux. + */ + if (ohci->flags & OHCI_QUIRK_INITRESET) { + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + // flush those writes + (void) ohci_readl (ohci, &ohci->regs->control); + } + + /* Tell the controller where the control and bulk lists are + * The lists are empty now. */ + ohci_writel (ohci, 0, &ohci->regs->ed_controlhead); + ohci_writel (ohci, 0, &ohci->regs->ed_bulkhead); + + /* a reset clears this */ + ohci_writel (ohci, (u32) ohci->hcca_dma, &ohci->regs->hcca); + + periodic_reinit (ohci); + + /* some OHCI implementations are finicky about how they init. + * bogus values here mean not even enumeration could work. + */ + if ((ohci_readl (ohci, &ohci->regs->fminterval) & 0x3fff0000) == 0 + || !ohci_readl (ohci, &ohci->regs->periodicstart)) { + if (!(ohci->flags & OHCI_QUIRK_INITRESET)) { + ohci->flags |= OHCI_QUIRK_INITRESET; + ohci_dbg (ohci, "enabling initreset quirk\n"); + goto retry; + } + spin_unlock_irq (&ohci->lock); + ohci_err (ohci, "init err (%08x %04x)\n", + ohci_readl (ohci, &ohci->regs->fminterval), + ohci_readl (ohci, &ohci->regs->periodicstart)); + return -EOVERFLOW; + } + + /* use rhsc irqs after khubd is fully initialized */ + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + hcd->uses_new_polling = 1; + + /* start controller operations */ + ohci->hc_control &= OHCI_CTRL_RWC; + ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + ohci->rh_state = OHCI_RH_RUNNING; + + /* wake on ConnectStatusChange, matching external hubs */ + ohci_writel (ohci, RH_HS_DRWE, &ohci->regs->roothub.status); + + /* Choose the interrupts we care about now, others later on demand */ + mask = OHCI_INTR_INIT; + ohci_writel (ohci, ~0, &ohci->regs->intrstatus); + ohci_writel (ohci, mask, &ohci->regs->intrenable); + + /* handle root hub init quirks ... */ + val = roothub_a (ohci); + val &= ~(RH_A_PSM | RH_A_OCPM); + if (ohci->flags & OHCI_QUIRK_SUPERIO) { + /* NSC 87560 and maybe others */ + val |= RH_A_NOCP; + val &= ~(RH_A_POTPGT | RH_A_NPS); + ohci_writel (ohci, val, &ohci->regs->roothub.a); + } else if ((ohci->flags & OHCI_QUIRK_AMD756) || + (ohci->flags & OHCI_QUIRK_HUB_POWER)) { + /* hub power always on; required for AMD-756 and some + * Mac platforms. ganged overcurrent reporting, if any. + */ + val |= RH_A_NPS; + ohci_writel (ohci, val, &ohci->regs->roothub.a); + } + ohci_writel (ohci, RH_HS_LPSC, &ohci->regs->roothub.status); + ohci_writel (ohci, (val & RH_A_NPS) ? 0 : RH_B_PPCM, + &ohci->regs->roothub.b); + // flush those writes + (void) ohci_readl (ohci, &ohci->regs->control); + + ohci->next_statechange = jiffies + STATECHANGE_DELAY; + spin_unlock_irq (&ohci->lock); + + // POTPGT delay is bits 24-31, in 2 ms units. + mdelay ((val >> 23) & 0x1fe); + + if (quirk_zfmicro(ohci)) { + /* Create timer to watch for bad queue state on ZF Micro */ + setup_timer(&ohci->unlink_watchdog, unlink_watchdog_func, + (unsigned long) ohci); + + ohci->eds_scheduled = 0; + ohci->ed_to_check = NULL; + } + + ohci_dump (ohci, 1); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* an interrupt happens */ + +static irqreturn_t ohci_irq (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct ohci_regs __iomem *regs = ohci->regs; + int ints; + + /* Read interrupt status (and flush pending writes). We ignore the + * optimization of checking the LSB of hcca->done_head; it doesn't + * work on all systems (edge triggering for OHCI can be a factor). + */ + ints = ohci_readl(ohci, ®s->intrstatus); + + /* Check for an all 1's result which is a typical consequence + * of dead, unclocked, or unplugged (CardBus...) devices + */ + if (ints == ~(u32)0) { + ohci->rh_state = OHCI_RH_HALTED; + ohci_dbg (ohci, "device removed!\n"); + usb_hc_died(hcd); + return IRQ_HANDLED; + } + + /* We only care about interrupts that are enabled */ + ints &= ohci_readl(ohci, ®s->intrenable); + + /* interrupt for some other device? */ + if (ints == 0 || unlikely(ohci->rh_state == OHCI_RH_HALTED)) + return IRQ_NOTMINE; + + if (ints & OHCI_INTR_UE) { + // e.g. due to PCI Master/Target Abort + if (quirk_nec(ohci)) { + /* Workaround for a silicon bug in some NEC chips used + * in Apple's PowerBooks. Adapted from Darwin code. + */ + ohci_err (ohci, "OHCI Unrecoverable Error, scheduling NEC chip restart\n"); + + ohci_writel (ohci, OHCI_INTR_UE, ®s->intrdisable); + + schedule_work (&ohci->nec_work); + } else { + ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); + ohci->rh_state = OHCI_RH_HALTED; + usb_hc_died(hcd); + } + + ohci_dump (ohci, 1); + ohci_usb_reset (ohci); + } + + if (ints & OHCI_INTR_RHSC) { + ohci_vdbg(ohci, "rhsc\n"); + ohci->next_statechange = jiffies + STATECHANGE_DELAY; + ohci_writel(ohci, OHCI_INTR_RD | OHCI_INTR_RHSC, + ®s->intrstatus); + + /* NOTE: Vendors didn't always make the same implementation + * choices for RHSC. Many followed the spec; RHSC triggers + * on an edge, like setting and maybe clearing a port status + * change bit. With others it's level-triggered, active + * until khubd clears all the port status change bits. We'll + * always disable it here and rely on polling until khubd + * re-enables it. + */ + ohci_writel(ohci, OHCI_INTR_RHSC, ®s->intrdisable); + usb_hcd_poll_rh_status(hcd); + } + + /* For connect and disconnect events, we expect the controller + * to turn on RHSC along with RD. But for remote wakeup events + * this might not happen. + */ + else if (ints & OHCI_INTR_RD) { + ohci_vdbg(ohci, "resume detect\n"); + ohci_writel(ohci, OHCI_INTR_RD, ®s->intrstatus); + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + if (ohci->autostop) { + spin_lock (&ohci->lock); + ohci_rh_resume (ohci); + spin_unlock (&ohci->lock); + } else + usb_hcd_resume_root_hub(hcd); + } + + if (ints & OHCI_INTR_WDH) { + spin_lock (&ohci->lock); + dl_done_list (ohci); + spin_unlock (&ohci->lock); + } + + if (quirk_zfmicro(ohci) && (ints & OHCI_INTR_SF)) { + spin_lock(&ohci->lock); + if (ohci->ed_to_check) { + struct ed *ed = ohci->ed_to_check; + + if (check_ed(ohci, ed)) { + /* HC thinks the TD list is empty; HCD knows + * at least one TD is outstanding + */ + if (--ohci->zf_delay == 0) { + struct td *td = list_entry( + ed->td_list.next, + struct td, td_list); + ohci_warn(ohci, + "Reclaiming orphan TD %p\n", + td); + takeback_td(ohci, td); + ohci->ed_to_check = NULL; + } + } else + ohci->ed_to_check = NULL; + } + spin_unlock(&ohci->lock); + } + + /* could track INTR_SO to reduce available PCI/... bandwidth */ + + /* handle any pending URB/ED unlinks, leaving INTR_SF enabled + * when there's still unlinking to be done (next frame). + */ + spin_lock (&ohci->lock); + if (ohci->ed_rm_list) + finish_unlinks (ohci, ohci_frame_no(ohci)); + if ((ints & OHCI_INTR_SF) != 0 + && !ohci->ed_rm_list + && !ohci->ed_to_check + && ohci->rh_state == OHCI_RH_RUNNING) + ohci_writel (ohci, OHCI_INTR_SF, ®s->intrdisable); + spin_unlock (&ohci->lock); + + if (ohci->rh_state == OHCI_RH_RUNNING) { + ohci_writel (ohci, ints, ®s->intrstatus); + ohci_writel (ohci, OHCI_INTR_MIE, ®s->intrenable); + // flush those writes + (void) ohci_readl (ohci, &ohci->regs->control); + } + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static void ohci_stop (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci_dump (ohci, 1); + + if (quirk_nec(ohci)) + flush_work(&ohci->nec_work); + + ohci_usb_reset (ohci); + ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); + free_irq(hcd->irq, hcd); + hcd->irq = 0; + + if (quirk_zfmicro(ohci)) + del_timer(&ohci->unlink_watchdog); + if (quirk_amdiso(ohci)) + usb_amd_dev_put(); + + remove_debug_files (ohci); + ohci_mem_cleanup (ohci); + if (ohci->hcca) { + dma_free_coherent (hcd->self.controller, + sizeof *ohci->hcca, + ohci->hcca, ohci->hcca_dma); + ohci->hcca = NULL; + ohci->hcca_dma = 0; + } +} + +/*-------------------------------------------------------------------------*/ + +#if defined(CONFIG_PM) || defined(CONFIG_PCI) + +/* must not be called from interrupt context */ +static int ohci_restart (struct ohci_hcd *ohci) +{ + int temp; + int i; + struct urb_priv *priv; + + spin_lock_irq(&ohci->lock); + ohci->rh_state = OHCI_RH_HALTED; + + /* Recycle any "live" eds/tds (and urbs). */ + if (!list_empty (&ohci->pending)) + ohci_dbg(ohci, "abort schedule...\n"); + list_for_each_entry (priv, &ohci->pending, pending) { + struct urb *urb = priv->td[0]->urb; + struct ed *ed = priv->ed; + + switch (ed->state) { + case ED_OPER: + ed->state = ED_UNLINK; + ed->hwINFO |= cpu_to_hc32(ohci, ED_DEQUEUE); + ed_deschedule (ohci, ed); + + ed->ed_next = ohci->ed_rm_list; + ed->ed_prev = NULL; + ohci->ed_rm_list = ed; + /* FALLTHROUGH */ + case ED_UNLINK: + break; + default: + ohci_dbg(ohci, "bogus ed %p state %d\n", + ed, ed->state); + } + + if (!urb->unlinked) + urb->unlinked = -ESHUTDOWN; + } + finish_unlinks (ohci, 0); + spin_unlock_irq(&ohci->lock); + + /* paranoia, in case that didn't work: */ + + /* empty the interrupt branches */ + for (i = 0; i < NUM_INTS; i++) ohci->load [i] = 0; + for (i = 0; i < NUM_INTS; i++) ohci->hcca->int_table [i] = 0; + + /* no EDs to remove */ + ohci->ed_rm_list = NULL; + + /* empty control and bulk lists */ + ohci->ed_controltail = NULL; + ohci->ed_bulktail = NULL; + + if ((temp = ohci_run (ohci)) < 0) { + ohci_err (ohci, "can't restart, %d\n", temp); + return temp; + } + ohci_dbg(ohci, "restart complete\n"); + return 0; +} + +#endif + +#ifdef CONFIG_PM + +static int __maybe_unused ohci_suspend(struct usb_hcd *hcd, bool do_wakeup) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + unsigned long flags; + + /* Disable irq emission and mark HW unaccessible. Use + * the spinlock to properly synchronize with possible pending + * RH suspend or resume activity. + */ + spin_lock_irqsave (&ohci->lock, flags); + ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); + (void)ohci_readl(ohci, &ohci->regs->intrdisable); + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irqrestore (&ohci->lock, flags); + + return 0; +} + + +static int __maybe_unused ohci_resume(struct usb_hcd *hcd, bool hibernated) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int port; + bool need_reinit = false; + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + /* Make sure resume from hibernation re-enumerates everything */ + if (hibernated) + ohci_usb_reset(ohci); + + /* See if the controller is already running or has been reset */ + ohci->hc_control = ohci_readl(ohci, &ohci->regs->control); + if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) { + need_reinit = true; + } else { + switch (ohci->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_OPER: + case OHCI_USB_RESET: + need_reinit = true; + } + } + + /* If needed, reinitialize and suspend the root hub */ + if (need_reinit) { + spin_lock_irq(&ohci->lock); + ohci_rh_resume(ohci); + ohci_rh_suspend(ohci, 0); + spin_unlock_irq(&ohci->lock); + } + + /* Normally just turn on port power and enable interrupts */ + else { + ohci_dbg(ohci, "powerup ports\n"); + for (port = 0; port < ohci->num_ports; port++) + ohci_writel(ohci, RH_PS_PPS, + &ohci->regs->roothub.portstatus[port]); + + ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrenable); + ohci_readl(ohci, &ohci->regs->intrenable); + msleep(20); + } + + usb_hcd_resume_root_hub(hcd); + + return 0; +} + +#endif + +/*-------------------------------------------------------------------------*/ + +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE ("GPL"); + +#ifdef CONFIG_PCI +#include "ohci-pci.c" +#define PCI_DRIVER ohci_pci_driver +#endif + +#if defined(CONFIG_ARCH_SA1100) && defined(CONFIG_SA1111) +#include "ohci-sa1111.c" +#define SA1111_DRIVER ohci_hcd_sa1111_driver +#endif + +#if defined(CONFIG_ARCH_S3C24XX) || defined(CONFIG_ARCH_S3C64XX) +#include "ohci-s3c2410.c" +#define S3C2410_PLATFORM_DRIVER ohci_hcd_s3c2410_driver +#endif + +#ifdef CONFIG_USB_OHCI_EXYNOS +#include "ohci-exynos.c" +#define EXYNOS_PLATFORM_DRIVER exynos_ohci_driver +#endif + +#ifdef CONFIG_USB_OHCI_HCD_OMAP1 +#include "ohci-omap.c" +#define OMAP1_PLATFORM_DRIVER ohci_hcd_omap_driver +#endif + +#ifdef CONFIG_USB_OHCI_HCD_OMAP3 +#include "ohci-omap3.c" +#define OMAP3_PLATFORM_DRIVER ohci_hcd_omap3_driver +#endif + +#if defined(CONFIG_PXA27x) || defined(CONFIG_PXA3xx) +#include "ohci-pxa27x.c" +#define PLATFORM_DRIVER ohci_hcd_pxa27x_driver +#endif + +#ifdef CONFIG_ARCH_EP93XX +#include "ohci-ep93xx.c" +#define EP93XX_PLATFORM_DRIVER ohci_hcd_ep93xx_driver +#endif + +#ifdef CONFIG_ARCH_AT91 +#include "ohci-at91.c" +#define AT91_PLATFORM_DRIVER ohci_hcd_at91_driver +#endif + +#ifdef CONFIG_ARCH_LPC32XX +#include "ohci-nxp.c" +#define NXP_PLATFORM_DRIVER usb_hcd_nxp_driver +#endif + +#ifdef CONFIG_ARCH_DAVINCI_DA8XX +#include "ohci-da8xx.c" +#define DAVINCI_PLATFORM_DRIVER ohci_hcd_da8xx_driver +#endif + +#ifdef CONFIG_USB_OHCI_HCD_PPC_OF +#include "ohci-ppc-of.c" +#define OF_PLATFORM_DRIVER ohci_hcd_ppc_of_driver +#endif + +#ifdef CONFIG_PLAT_SPEAR +#include "ohci-spear.c" +#define SPEAR_PLATFORM_DRIVER spear_ohci_hcd_driver +#endif + +#ifdef CONFIG_PPC_PS3 +#include "ohci-ps3.c" +#define PS3_SYSTEM_BUS_DRIVER ps3_ohci_driver +#endif + +#ifdef CONFIG_MFD_SM501 +#include "ohci-sm501.c" +#define SM501_OHCI_DRIVER ohci_hcd_sm501_driver +#endif + +#ifdef CONFIG_MFD_TC6393XB +#include "ohci-tmio.c" +#define TMIO_OHCI_DRIVER ohci_hcd_tmio_driver +#endif + +#ifdef CONFIG_MACH_JZ4740 +#include "ohci-jz4740.c" +#define PLATFORM_DRIVER ohci_hcd_jz4740_driver +#endif + +#ifdef CONFIG_USB_OCTEON_OHCI +#include "ohci-octeon.c" +#define PLATFORM_DRIVER ohci_octeon_driver +#endif + +#ifdef CONFIG_TILE_USB +#include "ohci-tilegx.c" +#define PLATFORM_DRIVER ohci_hcd_tilegx_driver +#endif + +#ifdef CONFIG_USB_OHCI_HCD_PLATFORM +#include "ohci-platform.c" +#define PLATFORM_DRIVER ohci_platform_driver +#endif + +static int __init ohci_hcd_mod_init(void) +{ + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + printk(KERN_INFO "%s: " DRIVER_DESC "\n", hcd_name); + pr_debug ("%s: block sizes: ed %Zd td %Zd\n", hcd_name, + sizeof (struct ed), sizeof (struct td)); + set_bit(USB_OHCI_LOADED, &usb_hcds_loaded); + +#ifdef DEBUG + ohci_debug_root = debugfs_create_dir("ohci", usb_debug_root); + if (!ohci_debug_root) { + retval = -ENOENT; + goto error_debug; + } +#endif + +#ifdef PS3_SYSTEM_BUS_DRIVER + retval = ps3_ohci_driver_register(&PS3_SYSTEM_BUS_DRIVER); + if (retval < 0) + goto error_ps3; +#endif + +#ifdef PLATFORM_DRIVER + retval = platform_driver_register(&PLATFORM_DRIVER); + if (retval < 0) + goto error_platform; +#endif + +#ifdef OMAP1_PLATFORM_DRIVER + retval = platform_driver_register(&OMAP1_PLATFORM_DRIVER); + if (retval < 0) + goto error_omap1_platform; +#endif + +#ifdef OMAP3_PLATFORM_DRIVER + retval = platform_driver_register(&OMAP3_PLATFORM_DRIVER); + if (retval < 0) + goto error_omap3_platform; +#endif + +#ifdef OF_PLATFORM_DRIVER + retval = platform_driver_register(&OF_PLATFORM_DRIVER); + if (retval < 0) + goto error_of_platform; +#endif + +#ifdef SA1111_DRIVER + retval = sa1111_driver_register(&SA1111_DRIVER); + if (retval < 0) + goto error_sa1111; +#endif + +#ifdef PCI_DRIVER + retval = pci_register_driver(&PCI_DRIVER); + if (retval < 0) + goto error_pci; +#endif + +#ifdef SM501_OHCI_DRIVER + retval = platform_driver_register(&SM501_OHCI_DRIVER); + if (retval < 0) + goto error_sm501; +#endif + +#ifdef TMIO_OHCI_DRIVER + retval = platform_driver_register(&TMIO_OHCI_DRIVER); + if (retval < 0) + goto error_tmio; +#endif + +#ifdef S3C2410_PLATFORM_DRIVER + retval = platform_driver_register(&S3C2410_PLATFORM_DRIVER); + if (retval < 0) + goto error_s3c2410; +#endif + +#ifdef EXYNOS_PLATFORM_DRIVER + retval = platform_driver_register(&EXYNOS_PLATFORM_DRIVER); + if (retval < 0) + goto error_exynos; +#endif + +#ifdef EP93XX_PLATFORM_DRIVER + retval = platform_driver_register(&EP93XX_PLATFORM_DRIVER); + if (retval < 0) + goto error_ep93xx; +#endif + +#ifdef AT91_PLATFORM_DRIVER + retval = platform_driver_register(&AT91_PLATFORM_DRIVER); + if (retval < 0) + goto error_at91; +#endif + +#ifdef NXP_PLATFORM_DRIVER + retval = platform_driver_register(&NXP_PLATFORM_DRIVER); + if (retval < 0) + goto error_nxp; +#endif + +#ifdef DAVINCI_PLATFORM_DRIVER + retval = platform_driver_register(&DAVINCI_PLATFORM_DRIVER); + if (retval < 0) + goto error_davinci; +#endif + +#ifdef SPEAR_PLATFORM_DRIVER + retval = platform_driver_register(&SPEAR_PLATFORM_DRIVER); + if (retval < 0) + goto error_spear; +#endif + + return retval; + + /* Error path */ +#ifdef SPEAR_PLATFORM_DRIVER + platform_driver_unregister(&SPEAR_PLATFORM_DRIVER); + error_spear: +#endif +#ifdef DAVINCI_PLATFORM_DRIVER + platform_driver_unregister(&DAVINCI_PLATFORM_DRIVER); + error_davinci: +#endif +#ifdef NXP_PLATFORM_DRIVER + platform_driver_unregister(&NXP_PLATFORM_DRIVER); + error_nxp: +#endif +#ifdef AT91_PLATFORM_DRIVER + platform_driver_unregister(&AT91_PLATFORM_DRIVER); + error_at91: +#endif +#ifdef EP93XX_PLATFORM_DRIVER + platform_driver_unregister(&EP93XX_PLATFORM_DRIVER); + error_ep93xx: +#endif +#ifdef EXYNOS_PLATFORM_DRIVER + platform_driver_unregister(&EXYNOS_PLATFORM_DRIVER); + error_exynos: +#endif +#ifdef S3C2410_PLATFORM_DRIVER + platform_driver_unregister(&S3C2410_PLATFORM_DRIVER); + error_s3c2410: +#endif +#ifdef TMIO_OHCI_DRIVER + platform_driver_unregister(&TMIO_OHCI_DRIVER); + error_tmio: +#endif +#ifdef SM501_OHCI_DRIVER + platform_driver_unregister(&SM501_OHCI_DRIVER); + error_sm501: +#endif +#ifdef PCI_DRIVER + pci_unregister_driver(&PCI_DRIVER); + error_pci: +#endif +#ifdef SA1111_DRIVER + sa1111_driver_unregister(&SA1111_DRIVER); + error_sa1111: +#endif +#ifdef OF_PLATFORM_DRIVER + platform_driver_unregister(&OF_PLATFORM_DRIVER); + error_of_platform: +#endif +#ifdef OMAP3_PLATFORM_DRIVER + platform_driver_unregister(&OMAP3_PLATFORM_DRIVER); + error_omap3_platform: +#endif +#ifdef OMAP1_PLATFORM_DRIVER + platform_driver_unregister(&OMAP1_PLATFORM_DRIVER); + error_omap1_platform: +#endif +#ifdef PLATFORM_DRIVER + platform_driver_unregister(&PLATFORM_DRIVER); + error_platform: +#endif +#ifdef PS3_SYSTEM_BUS_DRIVER + ps3_ohci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); + error_ps3: +#endif +#ifdef DEBUG + debugfs_remove(ohci_debug_root); + ohci_debug_root = NULL; + error_debug: +#endif + + clear_bit(USB_OHCI_LOADED, &usb_hcds_loaded); + return retval; +} +module_init(ohci_hcd_mod_init); + +static void __exit ohci_hcd_mod_exit(void) +{ +#ifdef SPEAR_PLATFORM_DRIVER + platform_driver_unregister(&SPEAR_PLATFORM_DRIVER); +#endif +#ifdef DAVINCI_PLATFORM_DRIVER + platform_driver_unregister(&DAVINCI_PLATFORM_DRIVER); +#endif +#ifdef NXP_PLATFORM_DRIVER + platform_driver_unregister(&NXP_PLATFORM_DRIVER); +#endif +#ifdef AT91_PLATFORM_DRIVER + platform_driver_unregister(&AT91_PLATFORM_DRIVER); +#endif +#ifdef EP93XX_PLATFORM_DRIVER + platform_driver_unregister(&EP93XX_PLATFORM_DRIVER); +#endif +#ifdef EXYNOS_PLATFORM_DRIVER + platform_driver_unregister(&EXYNOS_PLATFORM_DRIVER); +#endif +#ifdef S3C2410_PLATFORM_DRIVER + platform_driver_unregister(&S3C2410_PLATFORM_DRIVER); +#endif +#ifdef TMIO_OHCI_DRIVER + platform_driver_unregister(&TMIO_OHCI_DRIVER); +#endif +#ifdef SM501_OHCI_DRIVER + platform_driver_unregister(&SM501_OHCI_DRIVER); +#endif +#ifdef PCI_DRIVER + pci_unregister_driver(&PCI_DRIVER); +#endif +#ifdef SA1111_DRIVER + sa1111_driver_unregister(&SA1111_DRIVER); +#endif +#ifdef OF_PLATFORM_DRIVER + platform_driver_unregister(&OF_PLATFORM_DRIVER); +#endif +#ifdef OMAP3_PLATFORM_DRIVER + platform_driver_unregister(&OMAP3_PLATFORM_DRIVER); +#endif +#ifdef OMAP1_PLATFORM_DRIVER + platform_driver_unregister(&OMAP1_PLATFORM_DRIVER); +#endif +#ifdef PLATFORM_DRIVER + platform_driver_unregister(&PLATFORM_DRIVER); +#endif +#ifdef PS3_SYSTEM_BUS_DRIVER + ps3_ohci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); +#endif +#ifdef DEBUG + debugfs_remove(ohci_debug_root); +#endif + clear_bit(USB_OHCI_LOADED, &usb_hcds_loaded); +} +module_exit(ohci_hcd_mod_exit); + diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c new file mode 100644 index 00000000..cd908066 --- /dev/null +++ b/drivers/usb/host/ohci-hub.c @@ -0,0 +1,795 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2004 David Brownell + * + * This file is licenced under GPL + */ + +/*-------------------------------------------------------------------------*/ + +/* + * OHCI Root Hub ... the nonsharable stuff + */ + +#define dbg_port(hc,label,num,value) \ + ohci_dbg (hc, \ + "%s roothub.portstatus [%d] " \ + "= 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", \ + label, num, temp, \ + (temp & RH_PS_PRSC) ? " PRSC" : "", \ + (temp & RH_PS_OCIC) ? " OCIC" : "", \ + (temp & RH_PS_PSSC) ? " PSSC" : "", \ + (temp & RH_PS_PESC) ? " PESC" : "", \ + (temp & RH_PS_CSC) ? " CSC" : "", \ + \ + (temp & RH_PS_LSDA) ? " LSDA" : "", \ + (temp & RH_PS_PPS) ? " PPS" : "", \ + (temp & RH_PS_PRS) ? " PRS" : "", \ + (temp & RH_PS_POCI) ? " POCI" : "", \ + (temp & RH_PS_PSS) ? " PSS" : "", \ + \ + (temp & RH_PS_PES) ? " PES" : "", \ + (temp & RH_PS_CCS) ? " CCS" : "" \ + ); + +/*-------------------------------------------------------------------------*/ + +#define OHCI_SCHED_ENABLES \ + (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE) + +static void dl_done_list (struct ohci_hcd *); +static void finish_unlinks (struct ohci_hcd *, u16); + +#ifdef CONFIG_PM +static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop) +__releases(ohci->lock) +__acquires(ohci->lock) +{ + int status = 0; + + ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); + switch (ohci->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_RESUME: + ohci_dbg (ohci, "resume/suspend?\n"); + ohci->hc_control &= ~OHCI_CTRL_HCFS; + ohci->hc_control |= OHCI_USB_RESET; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); + /* FALL THROUGH */ + case OHCI_USB_RESET: + status = -EBUSY; + ohci_dbg (ohci, "needs reinit!\n"); + goto done; + case OHCI_USB_SUSPEND: + if (!ohci->autostop) { + ohci_dbg (ohci, "already suspended\n"); + goto done; + } + } + ohci_dbg (ohci, "%s root hub\n", + autostop ? "auto-stop" : "suspend"); + + /* First stop any processing */ + if (!autostop && (ohci->hc_control & OHCI_SCHED_ENABLES)) { + ohci->hc_control &= ~OHCI_SCHED_ENABLES; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); + ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrstatus); + + /* sched disables take effect on the next frame, + * then the last WDH could take 6+ msec + */ + ohci_dbg (ohci, "stopping schedules ...\n"); + ohci->autostop = 0; + spin_unlock_irq (&ohci->lock); + msleep (8); + spin_lock_irq (&ohci->lock); + } + dl_done_list (ohci); + finish_unlinks (ohci, ohci_frame_no(ohci)); + + /* + * Some controllers don't handle "global" suspend properly if + * there are unsuspended ports. For these controllers, put all + * the enabled ports into suspend before suspending the root hub. + */ + if (ohci->flags & OHCI_QUIRK_GLOBAL_SUSPEND) { + __hc32 __iomem *portstat = ohci->regs->roothub.portstatus; + int i; + unsigned temp; + + for (i = 0; i < ohci->num_ports; (++i, ++portstat)) { + temp = ohci_readl(ohci, portstat); + if ((temp & (RH_PS_PES | RH_PS_PSS)) == + RH_PS_PES) + ohci_writel(ohci, RH_PS_PSS, portstat); + } + } + + /* maybe resume can wake root hub */ + if (ohci_to_hcd(ohci)->self.root_hub->do_remote_wakeup || autostop) { + ohci->hc_control |= OHCI_CTRL_RWE; + } else { + ohci_writel(ohci, OHCI_INTR_RHSC | OHCI_INTR_RD, + &ohci->regs->intrdisable); + ohci->hc_control &= ~OHCI_CTRL_RWE; + } + + /* Suspend hub ... this is the "global (to this bus) suspend" mode, + * which doesn't imply ports will first be individually suspended. + */ + ohci->hc_control &= ~OHCI_CTRL_HCFS; + ohci->hc_control |= OHCI_USB_SUSPEND; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); + + /* no resumes until devices finish suspending */ + if (!autostop) { + ohci->next_statechange = jiffies + msecs_to_jiffies (5); + ohci->autostop = 0; + ohci->rh_state = OHCI_RH_SUSPENDED; + } + +done: + return status; +} + +static inline struct ed *find_head (struct ed *ed) +{ + /* for bulk and control lists */ + while (ed->ed_prev) + ed = ed->ed_prev; + return ed; +} + +/* caller has locked the root hub */ +static int ohci_rh_resume (struct ohci_hcd *ohci) +__releases(ohci->lock) +__acquires(ohci->lock) +{ + struct usb_hcd *hcd = ohci_to_hcd (ohci); + u32 temp, enables; + int status = -EINPROGRESS; + int autostopped = ohci->autostop; + + ohci->autostop = 0; + ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); + + if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) { + /* this can happen after resuming a swsusp snapshot */ + if (ohci->rh_state != OHCI_RH_RUNNING) { + ohci_dbg (ohci, "BIOS/SMM active, control %03x\n", + ohci->hc_control); + status = -EBUSY; + /* this happens when pmcore resumes HC then root */ + } else { + ohci_dbg (ohci, "duplicate resume\n"); + status = 0; + } + } else switch (ohci->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_SUSPEND: + ohci->hc_control &= ~(OHCI_CTRL_HCFS|OHCI_SCHED_ENABLES); + ohci->hc_control |= OHCI_USB_RESUME; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); + ohci_dbg (ohci, "%s root hub\n", + autostopped ? "auto-start" : "resume"); + break; + case OHCI_USB_RESUME: + /* HCFS changes sometime after INTR_RD */ + ohci_dbg(ohci, "%swakeup root hub\n", + autostopped ? "auto-" : ""); + break; + case OHCI_USB_OPER: + /* this can happen after resuming a swsusp snapshot */ + ohci_dbg (ohci, "snapshot resume? reinit\n"); + status = -EBUSY; + break; + default: /* RESET, we lost power */ + ohci_dbg (ohci, "lost power\n"); + status = -EBUSY; + } + if (status == -EBUSY) { + if (!autostopped) { + spin_unlock_irq (&ohci->lock); + (void) ohci_init (ohci); + status = ohci_restart (ohci); + + usb_root_hub_lost_power(hcd->self.root_hub); + + spin_lock_irq (&ohci->lock); + } + return status; + } + if (status != -EINPROGRESS) + return status; + if (autostopped) + goto skip_resume; + spin_unlock_irq (&ohci->lock); + + /* Some controllers (lucent erratum) need extra-long delays */ + msleep (20 /* usb 11.5.1.10 */ + 12 /* 32 msec counter */ + 1); + + temp = ohci_readl (ohci, &ohci->regs->control); + temp &= OHCI_CTRL_HCFS; + if (temp != OHCI_USB_RESUME) { + ohci_err (ohci, "controller won't resume\n"); + spin_lock_irq(&ohci->lock); + return -EBUSY; + } + + /* disable old schedule state, reinit from scratch */ + ohci_writel (ohci, 0, &ohci->regs->ed_controlhead); + ohci_writel (ohci, 0, &ohci->regs->ed_controlcurrent); + ohci_writel (ohci, 0, &ohci->regs->ed_bulkhead); + ohci_writel (ohci, 0, &ohci->regs->ed_bulkcurrent); + ohci_writel (ohci, 0, &ohci->regs->ed_periodcurrent); + ohci_writel (ohci, (u32) ohci->hcca_dma, &ohci->regs->hcca); + + /* Sometimes PCI D3 suspend trashes frame timings ... */ + periodic_reinit (ohci); + + /* the following code is executed with ohci->lock held and + * irqs disabled if and only if autostopped is true + */ + +skip_resume: + /* interrupts might have been disabled */ + ohci_writel (ohci, OHCI_INTR_INIT, &ohci->regs->intrenable); + if (ohci->ed_rm_list) + ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable); + + /* Then re-enable operations */ + ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); + if (!autostopped) + msleep (3); + + temp = ohci->hc_control; + temp &= OHCI_CTRL_RWC; + temp |= OHCI_CONTROL_INIT | OHCI_USB_OPER; + ohci->hc_control = temp; + ohci_writel (ohci, temp, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); + + /* TRSMRCY */ + if (!autostopped) { + msleep (10); + spin_lock_irq (&ohci->lock); + } + /* now ohci->lock is always held and irqs are always disabled */ + + /* keep it alive for more than ~5x suspend + resume costs */ + ohci->next_statechange = jiffies + STATECHANGE_DELAY; + + /* maybe turn schedules back on */ + enables = 0; + temp = 0; + if (!ohci->ed_rm_list) { + if (ohci->ed_controltail) { + ohci_writel (ohci, + find_head (ohci->ed_controltail)->dma, + &ohci->regs->ed_controlhead); + enables |= OHCI_CTRL_CLE; + temp |= OHCI_CLF; + } + if (ohci->ed_bulktail) { + ohci_writel (ohci, find_head (ohci->ed_bulktail)->dma, + &ohci->regs->ed_bulkhead); + enables |= OHCI_CTRL_BLE; + temp |= OHCI_BLF; + } + } + if (hcd->self.bandwidth_isoc_reqs || hcd->self.bandwidth_int_reqs) + enables |= OHCI_CTRL_PLE|OHCI_CTRL_IE; + if (enables) { + ohci_dbg (ohci, "restarting schedules ... %08x\n", enables); + ohci->hc_control |= enables; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + if (temp) + ohci_writel (ohci, temp, &ohci->regs->cmdstatus); + (void) ohci_readl (ohci, &ohci->regs->control); + } + + ohci->rh_state = OHCI_RH_RUNNING; + return 0; +} + +static int ohci_bus_suspend (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int rc; + + spin_lock_irq (&ohci->lock); + + if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) + rc = -ESHUTDOWN; + else + rc = ohci_rh_suspend (ohci, 0); + spin_unlock_irq (&ohci->lock); + return rc; +} + +static int ohci_bus_resume (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int rc; + + if (time_before (jiffies, ohci->next_statechange)) + msleep(5); + + spin_lock_irq (&ohci->lock); + + if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) + rc = -ESHUTDOWN; + else + rc = ohci_rh_resume (ohci); + spin_unlock_irq (&ohci->lock); + + /* poll until we know a device is connected or we autostop */ + if (rc == 0) + usb_hcd_poll_rh_status(hcd); + return rc; +} + +/* Carry out polling-, autostop-, and autoresume-related state changes */ +static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed, + int any_connected, int rhsc_status) +{ + int poll_rh = 1; + int rhsc_enable; + + /* Some broken controllers never turn off RHSC in the interrupt + * status register. For their sake we won't re-enable RHSC + * interrupts if the interrupt bit is already active. + */ + rhsc_enable = ohci_readl(ohci, &ohci->regs->intrenable) & + OHCI_INTR_RHSC; + + switch (ohci->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_OPER: + /* If no status changes are pending, enable RHSC interrupts. */ + if (!rhsc_enable && !rhsc_status && !changed) { + rhsc_enable = OHCI_INTR_RHSC; + ohci_writel(ohci, rhsc_enable, &ohci->regs->intrenable); + } + + /* Keep on polling until we know a device is connected + * and RHSC is enabled, or until we autostop. + */ + if (!ohci->autostop) { + if (any_connected || + !device_may_wakeup(&ohci_to_hcd(ohci) + ->self.root_hub->dev)) { + if (rhsc_enable) + poll_rh = 0; + } else { + ohci->autostop = 1; + ohci->next_statechange = jiffies + HZ; + } + + /* if no devices have been attached for one second, autostop */ + } else { + if (changed || any_connected) { + ohci->autostop = 0; + ohci->next_statechange = jiffies + + STATECHANGE_DELAY; + } else if (time_after_eq(jiffies, + ohci->next_statechange) + && !ohci->ed_rm_list + && !(ohci->hc_control & + OHCI_SCHED_ENABLES)) { + ohci_rh_suspend(ohci, 1); + if (rhsc_enable) + poll_rh = 0; + } + } + break; + + case OHCI_USB_SUSPEND: + case OHCI_USB_RESUME: + /* if there is a port change, autostart or ask to be resumed */ + if (changed) { + if (ohci->autostop) + ohci_rh_resume(ohci); + else + usb_hcd_resume_root_hub(ohci_to_hcd(ohci)); + + /* If remote wakeup is disabled, stop polling */ + } else if (!ohci->autostop && + !ohci_to_hcd(ohci)->self.root_hub-> + do_remote_wakeup) { + poll_rh = 0; + + } else { + /* If no status changes are pending, + * enable RHSC interrupts + */ + if (!rhsc_enable && !rhsc_status) { + rhsc_enable = OHCI_INTR_RHSC; + ohci_writel(ohci, rhsc_enable, + &ohci->regs->intrenable); + } + /* Keep polling until RHSC is enabled */ + if (rhsc_enable) + poll_rh = 0; + } + break; + } + return poll_rh; +} + +#else /* CONFIG_PM */ + +static inline int ohci_rh_resume(struct ohci_hcd *ohci) +{ + return 0; +} + +/* Carry out polling-related state changes. + * autostop isn't used when CONFIG_PM is turned off. + */ +static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed, + int any_connected, int rhsc_status) +{ + /* If RHSC is enabled, don't poll */ + if (ohci_readl(ohci, &ohci->regs->intrenable) & OHCI_INTR_RHSC) + return 0; + + /* If status changes are pending, continue polling. + * Conversely, if no status changes are pending but the RHSC + * status bit was set, then RHSC may be broken so continue polling. + */ + if (changed || rhsc_status) + return 1; + + /* It's safe to re-enable RHSC interrupts */ + ohci_writel(ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable); + return 0; +} + +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int +ohci_hub_status_data (struct usb_hcd *hcd, char *buf) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int i, changed = 0, length = 1; + int any_connected = 0; + int rhsc_status; + unsigned long flags; + + spin_lock_irqsave (&ohci->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) + goto done; + + /* undocumented erratum seen on at least rev D */ + if ((ohci->flags & OHCI_QUIRK_AMD756) + && (roothub_a (ohci) & RH_A_NDP) > MAX_ROOT_PORTS) { + ohci_warn (ohci, "bogus NDP, rereads as NDP=%d\n", + ohci_readl (ohci, &ohci->regs->roothub.a) & RH_A_NDP); + /* retry later; "should not happen" */ + goto done; + } + + /* init status */ + if (roothub_status (ohci) & (RH_HS_LPSC | RH_HS_OCIC)) + buf [0] = changed = 1; + else + buf [0] = 0; + if (ohci->num_ports > 7) { + buf [1] = 0; + length++; + } + + /* Clear the RHSC status flag before reading the port statuses */ + ohci_writel(ohci, OHCI_INTR_RHSC, &ohci->regs->intrstatus); + rhsc_status = ohci_readl(ohci, &ohci->regs->intrstatus) & + OHCI_INTR_RHSC; + + /* look at each port */ + for (i = 0; i < ohci->num_ports; i++) { + u32 status = roothub_portstatus (ohci, i); + + /* can't autostop if ports are connected */ + any_connected |= (status & RH_PS_CCS); + + if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC + | RH_PS_OCIC | RH_PS_PRSC)) { + changed = 1; + if (i < 7) + buf [0] |= 1 << (i + 1); + else + buf [1] |= 1 << (i - 7); + } + } + + if (ohci_root_hub_state_changes(ohci, changed, + any_connected, rhsc_status)) + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + else + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + + +done: + spin_unlock_irqrestore (&ohci->lock, flags); + + return changed ? length : 0; +} + +/*-------------------------------------------------------------------------*/ + +static void +ohci_hub_descriptor ( + struct ohci_hcd *ohci, + struct usb_hub_descriptor *desc +) { + u32 rh = roothub_a (ohci); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = (rh & RH_A_POTPGT) >> 24; + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ohci->num_ports; + temp = 1 + (ohci->num_ports / 8); + desc->bDescLength = 7 + 2 * temp; + + temp = 0; + if (rh & RH_A_NPS) /* no power switching? */ + temp |= 0x0002; + if (rh & RH_A_PSM) /* per-port power switching? */ + temp |= 0x0001; + if (rh & RH_A_NOCP) /* no overcurrent reporting? */ + temp |= 0x0010; + else if (rh & RH_A_OCPM) /* per-port overcurrent reporting? */ + temp |= 0x0008; + desc->wHubCharacteristics = (__force __u16)cpu_to_hc16(ohci, temp); + + /* ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + rh = roothub_b (ohci); + memset(desc->u.hs.DeviceRemovable, 0xff, + sizeof(desc->u.hs.DeviceRemovable)); + desc->u.hs.DeviceRemovable[0] = rh & RH_B_DR; + if (ohci->num_ports > 7) { + desc->u.hs.DeviceRemovable[1] = (rh & RH_B_DR) >> 8; + desc->u.hs.DeviceRemovable[2] = 0xff; + } else + desc->u.hs.DeviceRemovable[1] = 0xff; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_OTG + +static int ohci_start_port_reset (struct usb_hcd *hcd, unsigned port) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + u32 status; + + if (!port) + return -EINVAL; + port--; + + /* start port reset before HNP protocol times out */ + status = ohci_readl(ohci, &ohci->regs->roothub.portstatus [port]); + if (!(status & RH_PS_CCS)) + return -ENODEV; + + /* khubd will finish the reset later */ + ohci_writel(ohci, RH_PS_PRS, &ohci->regs->roothub.portstatus [port]); + return 0; +} + +#else + +#define ohci_start_port_reset NULL + +#endif + +/*-------------------------------------------------------------------------*/ + + +/* See usb 7.1.7.5: root hubs must issue at least 50 msec reset signaling, + * not necessarily continuous ... to guard against resume signaling. + */ +#define PORT_RESET_MSEC 50 + +/* this timer value might be vendor-specific ... */ +#define PORT_RESET_HW_MSEC 10 + +/* wrap-aware logic morphed from */ +#define tick_before(t1,t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0) + +/* called from some task, normally khubd */ +static inline int root_port_reset (struct ohci_hcd *ohci, unsigned port) +{ + __hc32 __iomem *portstat = &ohci->regs->roothub.portstatus [port]; + u32 temp = 0; + u16 now = ohci_readl(ohci, &ohci->regs->fmnumber); + u16 reset_done = now + PORT_RESET_MSEC; + int limit_1 = DIV_ROUND_UP(PORT_RESET_MSEC, PORT_RESET_HW_MSEC); + + /* build a "continuous enough" reset signal, with up to + * 3msec gap between pulses. scheduler HZ==100 must work; + * this might need to be deadline-scheduled. + */ + do { + int limit_2; + + /* spin until any current reset finishes */ + limit_2 = PORT_RESET_HW_MSEC * 2; + while (--limit_2 >= 0) { + temp = ohci_readl (ohci, portstat); + /* handle e.g. CardBus eject */ + if (temp == ~(u32)0) + return -ESHUTDOWN; + if (!(temp & RH_PS_PRS)) + break; + udelay (500); + } + + /* timeout (a hardware error) has been observed when + * EHCI sets CF while this driver is resetting a port; + * presumably other disconnect paths might do it too. + */ + if (limit_2 < 0) { + ohci_dbg(ohci, + "port[%d] reset timeout, stat %08x\n", + port, temp); + break; + } + + if (!(temp & RH_PS_CCS)) + break; + if (temp & RH_PS_PRSC) + ohci_writel (ohci, RH_PS_PRSC, portstat); + + /* start the next reset, sleep till it's probably done */ + ohci_writel (ohci, RH_PS_PRS, portstat); + msleep(PORT_RESET_HW_MSEC); + now = ohci_readl(ohci, &ohci->regs->fmnumber); + } while (tick_before(now, reset_done) && --limit_1 >= 0); + + /* caller synchronizes using PRSC ... and handles PRS + * still being set when this returns. + */ + + return 0; +} + +static int ohci_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ports = ohci->num_ports; + u32 temp; + int retval = 0; + + if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) + return -ESHUTDOWN; + + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + ohci_writel (ohci, RH_HS_OCIC, + &ohci->regs->roothub.status); + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + temp = RH_PS_CCS; + break; + case USB_PORT_FEAT_C_ENABLE: + temp = RH_PS_PESC; + break; + case USB_PORT_FEAT_SUSPEND: + temp = RH_PS_POCI; + break; + case USB_PORT_FEAT_C_SUSPEND: + temp = RH_PS_PSSC; + break; + case USB_PORT_FEAT_POWER: + temp = RH_PS_LSDA; + break; + case USB_PORT_FEAT_C_CONNECTION: + temp = RH_PS_CSC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + temp = RH_PS_OCIC; + break; + case USB_PORT_FEAT_C_RESET: + temp = RH_PS_PRSC; + break; + default: + goto error; + } + ohci_writel (ohci, temp, + &ohci->regs->roothub.portstatus [wIndex]); + // ohci_readl (ohci, &ohci->regs->roothub.portstatus [wIndex]); + break; + case GetHubDescriptor: + ohci_hub_descriptor (ohci, (struct usb_hub_descriptor *) buf); + break; + case GetHubStatus: + temp = roothub_status (ohci) & ~(RH_HS_CRWE | RH_HS_DRWE); + put_unaligned_le32(temp, buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = roothub_portstatus (ohci, wIndex); + put_unaligned_le32(temp, buf); + +#ifndef OHCI_VERBOSE_DEBUG + if (*(u16*)(buf+2)) /* only if wPortChange is interesting */ +#endif + dbg_port (ohci, "GetStatus", wIndex, temp); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + // FIXME: this can be cleared, yes? + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case SetPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: +#ifdef CONFIG_USB_OTG + if (hcd->self.otg_port == (wIndex + 1) + && hcd->self.b_hnp_enable) + ohci->start_hnp(ohci); + else +#endif + ohci_writel (ohci, RH_PS_PSS, + &ohci->regs->roothub.portstatus [wIndex]); + break; + case USB_PORT_FEAT_POWER: + ohci_writel (ohci, RH_PS_PPS, + &ohci->regs->roothub.portstatus [wIndex]); + break; + case USB_PORT_FEAT_RESET: + retval = root_port_reset (ohci, wIndex); + break; + default: + goto error; + } + break; + + default: +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + return retval; +} + diff --git a/drivers/usb/host/ohci-jz4740.c b/drivers/usb/host/ohci-jz4740.c new file mode 100644 index 00000000..8062bb9d --- /dev/null +++ b/drivers/usb/host/ohci-jz4740.c @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +struct jz4740_ohci_hcd { + struct ohci_hcd ohci_hcd; + + struct regulator *vbus; + bool vbus_enabled; + struct clk *clk; +}; + +static inline struct jz4740_ohci_hcd *hcd_to_jz4740_hcd(struct usb_hcd *hcd) +{ + return (struct jz4740_ohci_hcd *)(hcd->hcd_priv); +} + +static inline struct usb_hcd *jz4740_hcd_to_hcd(struct jz4740_ohci_hcd *jz4740_ohci) +{ + return container_of((void *)jz4740_ohci, struct usb_hcd, hcd_priv); +} + +static int ohci_jz4740_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_init(ohci); + if (ret < 0) + return ret; + + ohci->num_ports = 1; + + ret = ohci_run(ohci); + if (ret < 0) { + dev_err(hcd->self.controller, "Can not start %s", + hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + return 0; +} + +static int ohci_jz4740_set_vbus_power(struct jz4740_ohci_hcd *jz4740_ohci, + bool enabled) +{ + int ret = 0; + + if (!jz4740_ohci->vbus) + return 0; + + if (enabled && !jz4740_ohci->vbus_enabled) { + ret = regulator_enable(jz4740_ohci->vbus); + if (ret) + dev_err(jz4740_hcd_to_hcd(jz4740_ohci)->self.controller, + "Could not power vbus\n"); + } else if (!enabled && jz4740_ohci->vbus_enabled) { + ret = regulator_disable(jz4740_ohci->vbus); + } + + if (ret == 0) + jz4740_ohci->vbus_enabled = enabled; + + return ret; +} + +static int ohci_jz4740_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd); + int ret; + + switch (typeReq) { + case SetHubFeature: + if (wValue == USB_PORT_FEAT_POWER) + ret = ohci_jz4740_set_vbus_power(jz4740_ohci, true); + break; + case ClearHubFeature: + if (wValue == USB_PORT_FEAT_POWER) + ret = ohci_jz4740_set_vbus_power(jz4740_ohci, false); + break; + } + + if (ret) + return ret; + + return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); +} + + +static const struct hc_driver ohci_jz4740_hc_driver = { + .description = hcd_name, + .product_desc = "JZ4740 OHCI", + .hcd_priv_size = sizeof(struct jz4740_ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .start = ohci_jz4740_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_jz4740_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + + +static int jz4740_ohci_probe(struct platform_device *pdev) +{ + int ret; + struct usb_hcd *hcd; + struct jz4740_ohci_hcd *jz4740_ohci; + struct resource *res; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) { + dev_err(&pdev->dev, "Failed to get platform resource\n"); + return -ENOENT; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get platform irq\n"); + return irq; + } + + hcd = usb_create_hcd(&ohci_jz4740_hc_driver, &pdev->dev, "jz4740"); + if (!hcd) { + dev_err(&pdev->dev, "Failed to create hcd.\n"); + return -ENOMEM; + } + + jz4740_ohci = hcd_to_jz4740_hcd(hcd); + + res = request_mem_region(res->start, resource_size(res), hcd_name); + if (!res) { + dev_err(&pdev->dev, "Failed to request mem region.\n"); + ret = -EBUSY; + goto err_free; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = ioremap(res->start, resource_size(res)); + + if (!hcd->regs) { + dev_err(&pdev->dev, "Failed to ioremap registers.\n"); + ret = -EBUSY; + goto err_release_mem; + } + + jz4740_ohci->clk = clk_get(&pdev->dev, "uhc"); + if (IS_ERR(jz4740_ohci->clk)) { + ret = PTR_ERR(jz4740_ohci->clk); + dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); + goto err_iounmap; + } + + jz4740_ohci->vbus = regulator_get(&pdev->dev, "vbus"); + if (IS_ERR(jz4740_ohci->vbus)) + jz4740_ohci->vbus = NULL; + + + clk_set_rate(jz4740_ohci->clk, 48000000); + clk_enable(jz4740_ohci->clk); + if (jz4740_ohci->vbus) + ohci_jz4740_set_vbus_power(jz4740_ohci, true); + + platform_set_drvdata(pdev, hcd); + + ohci_hcd_init(hcd_to_ohci(hcd)); + + ret = usb_add_hcd(hcd, irq, 0); + if (ret) { + dev_err(&pdev->dev, "Failed to add hcd: %d\n", ret); + goto err_disable; + } + + return 0; + +err_disable: + platform_set_drvdata(pdev, NULL); + if (jz4740_ohci->vbus) { + regulator_disable(jz4740_ohci->vbus); + regulator_put(jz4740_ohci->vbus); + } + clk_disable(jz4740_ohci->clk); + + clk_put(jz4740_ohci->clk); +err_iounmap: + iounmap(hcd->regs); +err_release_mem: + release_mem_region(res->start, resource_size(res)); +err_free: + usb_put_hcd(hcd); + + return ret; +} + +static int jz4740_ohci_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd); + + usb_remove_hcd(hcd); + + platform_set_drvdata(pdev, NULL); + + if (jz4740_ohci->vbus) { + regulator_disable(jz4740_ohci->vbus); + regulator_put(jz4740_ohci->vbus); + } + + clk_disable(jz4740_ohci->clk); + clk_put(jz4740_ohci->clk); + + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + + usb_put_hcd(hcd); + + return 0; +} + +static struct platform_driver ohci_hcd_jz4740_driver = { + .probe = jz4740_ohci_probe, + .remove = jz4740_ohci_remove, + .driver = { + .name = "jz4740-ohci", + .owner = THIS_MODULE, + }, +}; + +MODULE_ALIAS("platform:jz4740-ohci"); diff --git a/drivers/usb/host/ohci-mem.c b/drivers/usb/host/ohci-mem.c new file mode 100644 index 00000000..2f20d3dc --- /dev/null +++ b/drivers/usb/host/ohci-mem.c @@ -0,0 +1,138 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * + * This file is licenced under the GPL. + */ + +/*-------------------------------------------------------------------------*/ + +/* + * OHCI deals with three types of memory: + * - data used only by the HCD ... kmalloc is fine + * - async and periodic schedules, shared by HC and HCD ... these + * need to use dma_pool or dma_alloc_coherent + * - driver buffers, read/written by HC ... the hcd glue or the + * device driver provides us with dma addresses + * + * There's also "register" data, which is memory mapped. + * No memory seen by this driver (or any HCD) may be paged out. + */ + +/*-------------------------------------------------------------------------*/ + +static void ohci_hcd_init (struct ohci_hcd *ohci) +{ + ohci->next_statechange = jiffies; + spin_lock_init (&ohci->lock); + INIT_LIST_HEAD (&ohci->pending); +} + +/*-------------------------------------------------------------------------*/ + +static int ohci_mem_init (struct ohci_hcd *ohci) +{ + ohci->td_cache = dma_pool_create ("ohci_td", + ohci_to_hcd(ohci)->self.controller, + sizeof (struct td), + 32 /* byte alignment */, + 0 /* no page-crossing issues */); + if (!ohci->td_cache) + return -ENOMEM; + ohci->ed_cache = dma_pool_create ("ohci_ed", + ohci_to_hcd(ohci)->self.controller, + sizeof (struct ed), + 16 /* byte alignment */, + 0 /* no page-crossing issues */); + if (!ohci->ed_cache) { + dma_pool_destroy (ohci->td_cache); + return -ENOMEM; + } + return 0; +} + +static void ohci_mem_cleanup (struct ohci_hcd *ohci) +{ + if (ohci->td_cache) { + dma_pool_destroy (ohci->td_cache); + ohci->td_cache = NULL; + } + if (ohci->ed_cache) { + dma_pool_destroy (ohci->ed_cache); + ohci->ed_cache = NULL; + } +} + +/*-------------------------------------------------------------------------*/ + +/* ohci "done list" processing needs this mapping */ +static inline struct td * +dma_to_td (struct ohci_hcd *hc, dma_addr_t td_dma) +{ + struct td *td; + + td_dma &= TD_MASK; + td = hc->td_hash [TD_HASH_FUNC(td_dma)]; + while (td && td->td_dma != td_dma) + td = td->td_hash; + return td; +} + +/* TDs ... */ +static struct td * +td_alloc (struct ohci_hcd *hc, gfp_t mem_flags) +{ + dma_addr_t dma; + struct td *td; + + td = dma_pool_alloc (hc->td_cache, mem_flags, &dma); + if (td) { + /* in case hc fetches it, make it look dead */ + memset (td, 0, sizeof *td); + td->hwNextTD = cpu_to_hc32 (hc, dma); + td->td_dma = dma; + /* hashed in td_fill */ + } + return td; +} + +static void +td_free (struct ohci_hcd *hc, struct td *td) +{ + struct td **prev = &hc->td_hash [TD_HASH_FUNC (td->td_dma)]; + + while (*prev && *prev != td) + prev = &(*prev)->td_hash; + if (*prev) + *prev = td->td_hash; + else if ((td->hwINFO & cpu_to_hc32(hc, TD_DONE)) != 0) + ohci_dbg (hc, "no hash for td %p\n", td); + dma_pool_free (hc->td_cache, td, td->td_dma); +} + +/*-------------------------------------------------------------------------*/ + +/* EDs ... */ +static struct ed * +ed_alloc (struct ohci_hcd *hc, gfp_t mem_flags) +{ + dma_addr_t dma; + struct ed *ed; + + ed = dma_pool_alloc (hc->ed_cache, mem_flags, &dma); + if (ed) { + memset (ed, 0, sizeof (*ed)); + INIT_LIST_HEAD (&ed->td_list); + ed->dma = dma; + } + return ed; +} + +static void +ed_free (struct ohci_hcd *hc, struct ed *ed) +{ + dma_pool_free (hc->ed_cache, ed, ed->dma); +} + diff --git a/drivers/usb/host/ohci-nxp.c b/drivers/usb/host/ohci-nxp.c new file mode 100644 index 00000000..5d7eb72c --- /dev/null +++ b/drivers/usb/host/ohci-nxp.c @@ -0,0 +1,388 @@ +/* + * driver for NXP USB Host devices + * + * Currently supported OHCI host devices: + * - NXP LPC32xx + * + * Authors: Dmitry Chigirev + * Vitaly Wool + * + * register initialization is based on code examples provided by Philips + * Copyright (c) 2005 Koninklijke Philips Electronics N.V. + * + * NOTE: This driver does not have suspend/resume functionality + * This driver is intended for engineering development purposes only + * + * 2005-2006 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define USB_CONFIG_BASE 0x31020000 +#define PWRMAN_BASE 0x40004000 + +#define USB_CTRL IO_ADDRESS(PWRMAN_BASE + 0x64) + +/* USB_CTRL bit defines */ +#define USB_SLAVE_HCLK_EN (1 << 24) +#define USB_DEV_NEED_CLK_EN (1 << 22) +#define USB_HOST_NEED_CLK_EN (1 << 21) +#define PAD_CONTROL_LAST_DRIVEN (1 << 19) + +#define USB_OTG_STAT_CONTROL IO_ADDRESS(USB_CONFIG_BASE + 0x110) + +/* USB_OTG_STAT_CONTROL bit defines */ +#define TRANSPARENT_I2C_EN (1 << 7) +#define HOST_EN (1 << 0) + +/* On LPC32xx, those are undefined */ +#ifndef start_int_set_falling_edge +#define start_int_set_falling_edge(irq) +#define start_int_set_rising_edge(irq) +#define start_int_ack(irq) +#define start_int_mask(irq) +#define start_int_umask(irq) +#endif + +static struct i2c_client *isp1301_i2c_client; + +extern int usb_disabled(void); + +static struct clk *usb_pll_clk; +static struct clk *usb_dev_clk; +static struct clk *usb_otg_clk; + +static void isp1301_configure_lpc32xx(void) +{ + /* LPC32XX only supports DAT_SE0 USB mode */ + /* This sequence is important */ + + /* Disable transparent UART mode first */ + i2c_smbus_write_byte_data(isp1301_i2c_client, + (ISP1301_I2C_MODE_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), + MC1_UART_EN); + i2c_smbus_write_byte_data(isp1301_i2c_client, + (ISP1301_I2C_MODE_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), + ~MC1_SPEED_REG); + i2c_smbus_write_byte_data(isp1301_i2c_client, + ISP1301_I2C_MODE_CONTROL_1, MC1_SPEED_REG); + i2c_smbus_write_byte_data(isp1301_i2c_client, + (ISP1301_I2C_MODE_CONTROL_2 | ISP1301_I2C_REG_CLEAR_ADDR), + ~0); + i2c_smbus_write_byte_data(isp1301_i2c_client, + ISP1301_I2C_MODE_CONTROL_2, + (MC2_BI_DI | MC2_PSW_EN | MC2_SPD_SUSP_CTRL)); + i2c_smbus_write_byte_data(isp1301_i2c_client, + (ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), ~0); + i2c_smbus_write_byte_data(isp1301_i2c_client, + ISP1301_I2C_MODE_CONTROL_1, MC1_DAT_SE0); + i2c_smbus_write_byte_data(isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1, + (OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN)); + i2c_smbus_write_byte_data(isp1301_i2c_client, + (ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), + (OTG1_DM_PULLUP | OTG1_DP_PULLUP)); + i2c_smbus_write_byte_data(isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_LATCH | ISP1301_I2C_REG_CLEAR_ADDR, ~0); + i2c_smbus_write_byte_data(isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_FALLING | ISP1301_I2C_REG_CLEAR_ADDR, + ~0); + i2c_smbus_write_byte_data(isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_RISING | ISP1301_I2C_REG_CLEAR_ADDR, ~0); + + /* Enable usb_need_clk clock after transceiver is initialized */ + __raw_writel(__raw_readl(USB_CTRL) | USB_HOST_NEED_CLK_EN, USB_CTRL); + + printk(KERN_INFO "ISP1301 Vendor ID : 0x%04x\n", + i2c_smbus_read_word_data(isp1301_i2c_client, 0x00)); + printk(KERN_INFO "ISP1301 Product ID : 0x%04x\n", + i2c_smbus_read_word_data(isp1301_i2c_client, 0x02)); + printk(KERN_INFO "ISP1301 Version ID : 0x%04x\n", + i2c_smbus_read_word_data(isp1301_i2c_client, 0x14)); +} + +static void isp1301_configure(void) +{ + isp1301_configure_lpc32xx(); +} + +static inline void isp1301_vbus_on(void) +{ + i2c_smbus_write_byte_data(isp1301_i2c_client, ISP1301_I2C_OTG_CONTROL_1, + OTG1_VBUS_DRV); +} + +static inline void isp1301_vbus_off(void) +{ + i2c_smbus_write_byte_data(isp1301_i2c_client, + ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR, + OTG1_VBUS_DRV); +} + +static void nxp_start_hc(void) +{ + unsigned long tmp = __raw_readl(USB_OTG_STAT_CONTROL) | HOST_EN; + __raw_writel(tmp, USB_OTG_STAT_CONTROL); + isp1301_vbus_on(); +} + +static void nxp_stop_hc(void) +{ + unsigned long tmp; + isp1301_vbus_off(); + tmp = __raw_readl(USB_OTG_STAT_CONTROL) & ~HOST_EN; + __raw_writel(tmp, USB_OTG_STAT_CONTROL); +} + +static int ohci_nxp_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + if ((ret = ohci_run(ohci)) < 0) { + dev_err(hcd->self.controller, "can't start\n"); + ohci_stop(hcd); + return ret; + } + return 0; +} + +static const struct hc_driver ohci_nxp_hc_driver = { + .description = hcd_name, + .product_desc = "nxp OHCI", + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + .hcd_priv_size = sizeof(struct ohci_hcd), + /* + * basic lifecycle operations + */ + .start = ohci_nxp_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +static int usb_hcd_nxp_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd = 0; + struct ohci_hcd *ohci; + const struct hc_driver *driver = &ohci_nxp_hc_driver; + struct resource *res; + int ret = 0, irq; + struct device_node *isp1301_node; + + if (pdev->dev.of_node) { + isp1301_node = of_parse_phandle(pdev->dev.of_node, + "transceiver", 0); + } else { + isp1301_node = NULL; + } + + isp1301_i2c_client = isp1301_get_client(isp1301_node); + if (!isp1301_i2c_client) { + return -EPROBE_DEFER; + } + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + + dev_dbg(&pdev->dev, "%s: " DRIVER_DESC " (nxp)\n", hcd_name); + if (usb_disabled()) { + dev_err(&pdev->dev, "USB is disabled\n"); + ret = -ENODEV; + goto fail_disable; + } + + /* Enable AHB slave USB clock, needed for further USB clock control */ + __raw_writel(USB_SLAVE_HCLK_EN | PAD_CONTROL_LAST_DRIVEN, USB_CTRL); + + /* Enable USB PLL */ + usb_pll_clk = clk_get(&pdev->dev, "ck_pll5"); + if (IS_ERR(usb_pll_clk)) { + dev_err(&pdev->dev, "failed to acquire USB PLL\n"); + ret = PTR_ERR(usb_pll_clk); + goto fail_pll; + } + + ret = clk_enable(usb_pll_clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to start USB PLL\n"); + goto fail_pllen; + } + + ret = clk_set_rate(usb_pll_clk, 48000); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set USB clock rate\n"); + goto fail_rate; + } + + /* Enable USB device clock */ + usb_dev_clk = clk_get(&pdev->dev, "ck_usbd"); + if (IS_ERR(usb_dev_clk)) { + dev_err(&pdev->dev, "failed to acquire USB DEV Clock\n"); + ret = PTR_ERR(usb_dev_clk); + goto fail_dev; + } + + ret = clk_enable(usb_dev_clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to start USB DEV Clock\n"); + goto fail_deven; + } + + /* Enable USB otg clocks */ + usb_otg_clk = clk_get(&pdev->dev, "ck_usb_otg"); + if (IS_ERR(usb_otg_clk)) { + dev_err(&pdev->dev, "failed to acquire USB DEV Clock\n"); + ret = PTR_ERR(usb_otg_clk); + goto fail_otg; + } + + __raw_writel(__raw_readl(USB_CTRL) | USB_HOST_NEED_CLK_EN, USB_CTRL); + + ret = clk_enable(usb_otg_clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to start USB DEV Clock\n"); + goto fail_otgen; + } + + isp1301_configure(); + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Failed to allocate HC buffer\n"); + ret = -ENOMEM; + goto fail_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto fail_resource; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENXIO; + goto fail_resource; + } + + nxp_start_hc(); + platform_set_drvdata(pdev, hcd); + ohci = hcd_to_ohci(hcd); + ohci_hcd_init(ohci); + + dev_info(&pdev->dev, "at 0x%p, irq %d\n", hcd->regs, hcd->irq); + ret = usb_add_hcd(hcd, irq, 0); + if (ret == 0) + return ret; + + nxp_stop_hc(); +fail_resource: + usb_put_hcd(hcd); +fail_hcd: + clk_disable(usb_otg_clk); +fail_otgen: + clk_put(usb_otg_clk); +fail_otg: + clk_disable(usb_dev_clk); +fail_deven: + clk_put(usb_dev_clk); +fail_dev: +fail_rate: + clk_disable(usb_pll_clk); +fail_pllen: + clk_put(usb_pll_clk); +fail_pll: +fail_disable: + isp1301_i2c_client = NULL; + return ret; +} + +static int usb_hcd_nxp_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + nxp_stop_hc(); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + clk_disable(usb_pll_clk); + clk_put(usb_pll_clk); + clk_disable(usb_dev_clk); + clk_put(usb_dev_clk); + i2c_unregister_device(isp1301_i2c_client); + isp1301_i2c_client = NULL; + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:usb-ohci"); + +#ifdef CONFIG_OF +static const struct of_device_id usb_hcd_nxp_match[] = { + { .compatible = "nxp,ohci-nxp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, usb_hcd_nxp_match); +#endif + +static struct platform_driver usb_hcd_nxp_driver = { + .driver = { + .name = "usb-ohci", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(usb_hcd_nxp_match), + }, + .probe = usb_hcd_nxp_probe, + .remove = usb_hcd_nxp_remove, +}; + diff --git a/drivers/usb/host/ohci-octeon.c b/drivers/usb/host/ohci-octeon.c new file mode 100644 index 00000000..d44430d0 --- /dev/null +++ b/drivers/usb/host/ohci-octeon.c @@ -0,0 +1,214 @@ +/* + * EHCI HCD glue for Cavium Octeon II SOCs. + * + * Loosely based on ehci-au1xxx.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2010 Cavium Networks + * + */ + +#include + +#include +#include + +#define OCTEON_OHCI_HCD_NAME "octeon-ohci" + +/* Common clock init code. */ +void octeon2_usb_clocks_start(void); +void octeon2_usb_clocks_stop(void); + +static void ohci_octeon_hw_start(void) +{ + union cvmx_uctlx_ohci_ctl ohci_ctl; + + octeon2_usb_clocks_start(); + + ohci_ctl.u64 = cvmx_read_csr(CVMX_UCTLX_OHCI_CTL(0)); + ohci_ctl.s.l2c_addr_msb = 0; + ohci_ctl.s.l2c_buff_emod = 1; /* Byte swapped. */ + ohci_ctl.s.l2c_desc_emod = 1; /* Byte swapped. */ + cvmx_write_csr(CVMX_UCTLX_OHCI_CTL(0), ohci_ctl.u64); + +} + +static void ohci_octeon_hw_stop(void) +{ + /* Undo ohci_octeon_start() */ + octeon2_usb_clocks_stop(); +} + +static int ohci_octeon_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_init(ohci); + + if (ret < 0) + return ret; + + ret = ohci_run(ohci); + + if (ret < 0) { + ohci_err(ohci, "can't start %s", hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + +static const struct hc_driver ohci_octeon_hc_driver = { + .description = hcd_name, + .product_desc = "Octeon OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .start = ohci_octeon_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, + + .start_port_reset = ohci_start_port_reset, +}; + +static int ohci_octeon_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct ohci_hcd *ohci; + void *reg_base; + struct resource *res_mem; + int irq; + int ret; + + if (usb_disabled()) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No irq assigned\n"); + return -ENODEV; + } + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res_mem == NULL) { + dev_err(&pdev->dev, "No register space assigned\n"); + return -ENODEV; + } + + /* Ohci is a 32-bit device. */ + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + + hcd = usb_create_hcd(&ohci_octeon_hc_driver, &pdev->dev, "octeon"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + OCTEON_OHCI_HCD_NAME)) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto err1; + } + + reg_base = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!reg_base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err2; + } + + ohci_octeon_hw_start(); + + hcd->regs = reg_base; + + ohci = hcd_to_ohci(hcd); + + /* Octeon OHCI matches CPU endianness. */ +#ifdef __BIG_ENDIAN + ohci->flags |= OHCI_QUIRK_BE_MMIO; +#endif + + ohci_hcd_init(ohci); + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) { + dev_dbg(&pdev->dev, "failed to add hcd with err %d\n", ret); + goto err3; + } + + platform_set_drvdata(pdev, hcd); + + return 0; + +err3: + ohci_octeon_hw_stop(); + + iounmap(hcd->regs); +err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err1: + usb_put_hcd(hcd); + return ret; +} + +static int ohci_octeon_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + + ohci_octeon_hw_stop(); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver ohci_octeon_driver = { + .probe = ohci_octeon_drv_probe, + .remove = ohci_octeon_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = OCTEON_OHCI_HCD_NAME, + .owner = THIS_MODULE, + } +}; + +MODULE_ALIAS("platform:" OCTEON_OHCI_HCD_NAME); diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c new file mode 100644 index 00000000..b1d32fb4 --- /dev/null +++ b/drivers/usb/host/ohci-omap.c @@ -0,0 +1,557 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2005 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * + * OMAP Bus Glue + * + * Modified for OMAP by Tony Lindgren + * Based on the 2.4 OMAP OHCI driver originally done by MontaVista Software Inc. + * and on ohci-sa1111.c by Christopher Hoover + * + * This file is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + + +/* OMAP-1510 OHCI has its own MMU for DMA */ +#define OMAP1510_LB_MEMSIZE 32 /* Should be same as SDRAM size */ +#define OMAP1510_LB_CLOCK_DIV 0xfffec10c +#define OMAP1510_LB_MMU_CTL 0xfffec208 +#define OMAP1510_LB_MMU_LCK 0xfffec224 +#define OMAP1510_LB_MMU_LD_TLB 0xfffec228 +#define OMAP1510_LB_MMU_CAM_H 0xfffec22c +#define OMAP1510_LB_MMU_CAM_L 0xfffec230 +#define OMAP1510_LB_MMU_RAM_H 0xfffec234 +#define OMAP1510_LB_MMU_RAM_L 0xfffec238 + + +#ifndef CONFIG_ARCH_OMAP +#error "This file is OMAP bus glue. CONFIG_OMAP must be defined." +#endif + +#ifdef CONFIG_TPS65010 +#include +#else + +#define LOW 0 +#define HIGH 1 + +#define GPIO1 1 + +static inline int tps65010_set_gpio_out_value(unsigned gpio, unsigned value) +{ + return 0; +} + +#endif + +extern int usb_disabled(void); +extern int ocpi_enable(void); + +static struct clk *usb_host_ck; +static struct clk *usb_dc_ck; +static int host_enabled; +static int host_initialized; + +static void omap_ohci_clock_power(int on) +{ + if (on) { + clk_enable(usb_dc_ck); + clk_enable(usb_host_ck); + /* guesstimate for T5 == 1x 32K clock + APLL lock time */ + udelay(100); + } else { + clk_disable(usb_host_ck); + clk_disable(usb_dc_ck); + } +} + +/* + * Board specific gang-switched transceiver power on/off. + * NOTE: OSK supplies power from DC, not battery. + */ +static int omap_ohci_transceiver_power(int on) +{ + if (on) { + if (machine_is_omap_innovator() && cpu_is_omap1510()) + __raw_writeb(__raw_readb(INNOVATOR_FPGA_CAM_USB_CONTROL) + | ((1 << 5/*usb1*/) | (1 << 3/*usb2*/)), + INNOVATOR_FPGA_CAM_USB_CONTROL); + else if (machine_is_omap_osk()) + tps65010_set_gpio_out_value(GPIO1, LOW); + } else { + if (machine_is_omap_innovator() && cpu_is_omap1510()) + __raw_writeb(__raw_readb(INNOVATOR_FPGA_CAM_USB_CONTROL) + & ~((1 << 5/*usb1*/) | (1 << 3/*usb2*/)), + INNOVATOR_FPGA_CAM_USB_CONTROL); + else if (machine_is_omap_osk()) + tps65010_set_gpio_out_value(GPIO1, HIGH); + } + + return 0; +} + +#ifdef CONFIG_ARCH_OMAP15XX +/* + * OMAP-1510 specific Local Bus clock on/off + */ +static int omap_1510_local_bus_power(int on) +{ + if (on) { + omap_writel((1 << 1) | (1 << 0), OMAP1510_LB_MMU_CTL); + udelay(200); + } else { + omap_writel(0, OMAP1510_LB_MMU_CTL); + } + + return 0; +} + +/* + * OMAP-1510 specific Local Bus initialization + * NOTE: This assumes 32MB memory size in OMAP1510LB_MEMSIZE. + * See also arch/mach-omap/memory.h for __virt_to_dma() and + * __dma_to_virt() which need to match with the physical + * Local Bus address below. + */ +static int omap_1510_local_bus_init(void) +{ + unsigned int tlb; + unsigned long lbaddr, physaddr; + + omap_writel((omap_readl(OMAP1510_LB_CLOCK_DIV) & 0xfffffff8) | 0x4, + OMAP1510_LB_CLOCK_DIV); + + /* Configure the Local Bus MMU table */ + for (tlb = 0; tlb < OMAP1510_LB_MEMSIZE; tlb++) { + lbaddr = tlb * 0x00100000 + OMAP1510_LB_OFFSET; + physaddr = tlb * 0x00100000 + PHYS_OFFSET; + omap_writel((lbaddr & 0x0fffffff) >> 22, OMAP1510_LB_MMU_CAM_H); + omap_writel(((lbaddr & 0x003ffc00) >> 6) | 0xc, + OMAP1510_LB_MMU_CAM_L); + omap_writel(physaddr >> 16, OMAP1510_LB_MMU_RAM_H); + omap_writel((physaddr & 0x0000fc00) | 0x300, OMAP1510_LB_MMU_RAM_L); + omap_writel(tlb << 4, OMAP1510_LB_MMU_LCK); + omap_writel(0x1, OMAP1510_LB_MMU_LD_TLB); + } + + /* Enable the walking table */ + omap_writel(omap_readl(OMAP1510_LB_MMU_CTL) | (1 << 3), OMAP1510_LB_MMU_CTL); + udelay(200); + + return 0; +} +#else +#define omap_1510_local_bus_power(x) {} +#define omap_1510_local_bus_init() {} +#endif + +#ifdef CONFIG_USB_OTG + +static void start_hnp(struct ohci_hcd *ohci) +{ + struct usb_hcd *hcd = ohci_to_hcd(ohci); + const unsigned port = hcd->self.otg_port - 1; + unsigned long flags; + u32 l; + + otg_start_hnp(hcd->phy->otg); + + local_irq_save(flags); + hcd->phy->state = OTG_STATE_A_SUSPEND; + writel (RH_PS_PSS, &ohci->regs->roothub.portstatus [port]); + l = omap_readl(OTG_CTRL); + l &= ~OTG_A_BUSREQ; + omap_writel(l, OTG_CTRL); + local_irq_restore(flags); +} + +#endif + +/*-------------------------------------------------------------------------*/ + +static int ohci_omap_init(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct omap_usb_config *config = hcd->self.controller->platform_data; + int need_transceiver = (config->otg != 0); + int ret; + + dev_dbg(hcd->self.controller, "starting USB Controller\n"); + + if (config->otg) { + ohci_to_hcd(ohci)->self.otg_port = config->otg; + /* default/minimum OTG power budget: 8 mA */ + ohci_to_hcd(ohci)->power_budget = 8; + } + + /* boards can use OTG transceivers in non-OTG modes */ + need_transceiver = need_transceiver + || machine_is_omap_h2() || machine_is_omap_h3(); + + /* XXX OMAP16xx only */ + if (config->ocpi_enable) + config->ocpi_enable(); + +#ifdef CONFIG_USB_OTG + if (need_transceiver) { + hcd->phy = usb_get_phy(USB_PHY_TYPE_USB2); + if (!IS_ERR_OR_NULL(hcd->phy)) { + int status = otg_set_host(hcd->phy->otg, + &ohci_to_hcd(ohci)->self); + dev_dbg(hcd->self.controller, "init %s phy, status %d\n", + hcd->phy->label, status); + if (status) { + usb_put_phy(hcd->phy); + return status; + } + } else { + dev_err(hcd->self.controller, "can't find phy\n"); + return -ENODEV; + } + ohci->start_hnp = start_hnp; + } +#endif + + omap_ohci_clock_power(1); + + if (cpu_is_omap15xx()) { + omap_1510_local_bus_power(1); + omap_1510_local_bus_init(); + } + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + /* board-specific power switching and overcurrent support */ + if (machine_is_omap_osk() || machine_is_omap_innovator()) { + u32 rh = roothub_a (ohci); + + /* power switching (ganged by default) */ + rh &= ~RH_A_NPS; + + /* TPS2045 switch for internal transceiver (port 1) */ + if (machine_is_omap_osk()) { + ohci_to_hcd(ohci)->power_budget = 250; + + rh &= ~RH_A_NOCP; + + /* gpio9 for overcurrent detction */ + omap_cfg_reg(W8_1610_GPIO9); + gpio_request(9, "OHCI overcurrent"); + gpio_direction_input(9); + + /* for paranoia's sake: disable USB.PUEN */ + omap_cfg_reg(W4_USB_HIGHZ); + } + ohci_writel(ohci, rh, &ohci->regs->roothub.a); + ohci->flags &= ~OHCI_QUIRK_HUB_POWER; + } else if (machine_is_nokia770()) { + /* We require a self-powered hub, which should have + * plenty of power. */ + ohci_to_hcd(ohci)->power_budget = 0; + } + + /* FIXME khubd hub requests should manage power switching */ + omap_ohci_transceiver_power(1); + + /* board init will have already handled HMC and mux setup. + * any external transceiver should already be initialized + * too, so all configured ports use the right signaling now. + */ + + return 0; +} + +static void ohci_omap_stop(struct usb_hcd *hcd) +{ + dev_dbg(hcd->self.controller, "stopping USB Controller\n"); + ohci_stop(hcd); + omap_ohci_clock_power(0); +} + + +/*-------------------------------------------------------------------------*/ + +/** + * usb_hcd_omap_probe - initialize OMAP-based HCDs + * Context: !in_interrupt() + * + * 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 usb_hcd_omap_probe (const struct hc_driver *driver, + struct platform_device *pdev) +{ + int retval, irq; + struct usb_hcd *hcd = 0; + struct ohci_hcd *ohci; + + if (pdev->num_resources != 2) { + printk(KERN_ERR "hcd probe: invalid num_resources: %i\n", + pdev->num_resources); + return -ENODEV; + } + + if (pdev->resource[0].flags != IORESOURCE_MEM + || pdev->resource[1].flags != IORESOURCE_IRQ) { + printk(KERN_ERR "hcd probe: invalid resource type\n"); + return -ENODEV; + } + + usb_host_ck = clk_get(&pdev->dev, "usb_hhc_ck"); + if (IS_ERR(usb_host_ck)) + return PTR_ERR(usb_host_ck); + + if (!cpu_is_omap15xx()) + usb_dc_ck = clk_get(&pdev->dev, "usb_dc_ck"); + else + usb_dc_ck = clk_get(&pdev->dev, "lb_ck"); + + if (IS_ERR(usb_dc_ck)) { + clk_put(usb_host_ck); + return PTR_ERR(usb_dc_ck); + } + + + hcd = usb_create_hcd (driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto err0; + } + hcd->rsrc_start = pdev->resource[0].start; + hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + retval = -EBUSY; + goto err1; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "can't ioremap OHCI HCD\n"); + retval = -ENOMEM; + goto err2; + } + + ohci = hcd_to_ohci(hcd); + ohci_hcd_init(ohci); + + host_initialized = 0; + host_enabled = 1; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + retval = -ENXIO; + goto err3; + } + retval = usb_add_hcd(hcd, irq, 0); + if (retval) + goto err3; + + host_initialized = 1; + + if (!host_enabled) + omap_ohci_clock_power(0); + + return 0; +err3: + iounmap(hcd->regs); +err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err1: + usb_put_hcd(hcd); +err0: + clk_put(usb_dc_ck); + clk_put(usb_host_ck); + return retval; +} + + +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_omap_remove - shutdown processing for OMAP-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_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 inline void +usb_hcd_omap_remove (struct usb_hcd *hcd, struct platform_device *pdev) +{ + usb_remove_hcd(hcd); + if (!IS_ERR_OR_NULL(hcd->phy)) { + (void) otg_set_host(hcd->phy->otg, 0); + usb_put_phy(hcd->phy); + } + if (machine_is_omap_osk()) + gpio_free(9); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + clk_put(usb_dc_ck); + clk_put(usb_host_ck); +} + +/*-------------------------------------------------------------------------*/ + +static int +ohci_omap_start (struct usb_hcd *hcd) +{ + struct omap_usb_config *config; + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + + if (!host_enabled) + return 0; + config = hcd->self.controller->platform_data; + if (config->otg || config->rwc) { + ohci->hc_control = OHCI_CTRL_RWC; + writel(OHCI_CTRL_RWC, &ohci->regs->control); + } + + if ((ret = ohci_run (ohci)) < 0) { + dev_err(hcd->self.controller, "can't start\n"); + ohci_stop (hcd); + return ret; + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ohci_omap_hc_driver = { + .description = hcd_name, + .product_desc = "OMAP OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ohci_omap_init, + .start = ohci_omap_start, + .stop = ohci_omap_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + +static int ohci_hcd_omap_drv_probe(struct platform_device *dev) +{ + return usb_hcd_omap_probe(&ohci_omap_hc_driver, dev); +} + +static int ohci_hcd_omap_drv_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + usb_hcd_omap_remove(hcd, dev); + platform_set_drvdata(dev, NULL); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +static int ohci_omap_suspend(struct platform_device *dev, pm_message_t message) +{ + struct ohci_hcd *ohci = hcd_to_ohci(platform_get_drvdata(dev)); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + omap_ohci_clock_power(0); + return 0; +} + +static int ohci_omap_resume(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + omap_ohci_clock_power(1); + ohci_resume(hcd, false); + return 0; +} + +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * Driver definition to register with the OMAP bus + */ +static struct platform_driver ohci_hcd_omap_driver = { + .probe = ohci_hcd_omap_drv_probe, + .remove = ohci_hcd_omap_drv_remove, + .shutdown = usb_hcd_platform_shutdown, +#ifdef CONFIG_PM + .suspend = ohci_omap_suspend, + .resume = ohci_omap_resume, +#endif + .driver = { + .owner = THIS_MODULE, + .name = "ohci", + }, +}; + +MODULE_ALIAS("platform:ohci"); diff --git a/drivers/usb/host/ohci-omap3.c b/drivers/usb/host/ohci-omap3.c new file mode 100644 index 00000000..8663851c --- /dev/null +++ b/drivers/usb/host/ohci-omap3.c @@ -0,0 +1,260 @@ +/* + * ohci-omap3.c - driver for OHCI on OMAP3 and later processors + * + * Bus Glue for OMAP3 USBHOST 3 port OHCI controller + * This controller is also used in later OMAPs and AM35x chips + * + * Copyright (C) 2007-2010 Texas Instruments, Inc. + * Author: Vikram Pandita + * Author: Anand Gadiyar + * Author: Keshava Munegowda + * + * Based on ehci-omap.c and some other ohci glue layers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * TODO (last updated Feb 27, 2011): + * - add kernel-doc + */ + +#include +#include +#include +#include + +/*-------------------------------------------------------------------------*/ + +static int ohci_omap3_init(struct usb_hcd *hcd) +{ + dev_dbg(hcd->self.controller, "starting OHCI controller\n"); + + return ohci_init(hcd_to_ohci(hcd)); +} + +/*-------------------------------------------------------------------------*/ + +static int ohci_omap3_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + /* + * RemoteWakeupConnected has to be set explicitly before + * calling ohci_run. The reset value of RWC is 0. + */ + ohci->hc_control = OHCI_CTRL_RWC; + writel(OHCI_CTRL_RWC, &ohci->regs->control); + + ret = ohci_run(ohci); + + if (ret < 0) { + dev_err(hcd->self.controller, "can't start\n"); + ohci_stop(hcd); + } + + return ret; +} + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ohci_omap3_hc_driver = { + .description = hcd_name, + .product_desc = "OMAP3 OHCI Host Controller", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ohci_omap3_init, + .start = ohci_omap3_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + +/* + * configure so an HC device and id are always provided + * always called with process context; sleeping is OK + */ + +/** + * ohci_hcd_omap3_probe - initialize OMAP-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 ohci_hcd_omap3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = NULL; + void __iomem *regs = NULL; + struct resource *res; + int ret = -ENODEV; + int irq; + + if (usb_disabled()) + return -ENODEV; + + if (!dev->parent) { + dev_err(dev, "Missing parent device\n"); + return -ENODEV; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "OHCI irq failed\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "UHH OHCI get resource failed\n"); + return -ENOMEM; + } + + regs = ioremap(res->start, resource_size(res)); + if (!regs) { + dev_err(dev, "UHH OHCI ioremap failed\n"); + return -ENOMEM; + } + + /* + * 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. + */ + if (!dev->dma_mask) + dev->dma_mask = &dev->coherent_dma_mask; + if (!dev->coherent_dma_mask) + dev->coherent_dma_mask = DMA_BIT_MASK(32); + + hcd = usb_create_hcd(&ohci_omap3_hc_driver, dev, + dev_name(dev)); + if (!hcd) { + dev_err(dev, "usb_create_hcd failed\n"); + goto err_io; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = regs; + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + ohci_hcd_init(hcd_to_ohci(hcd)); + + ret = usb_add_hcd(hcd, irq, 0); + if (ret) { + dev_dbg(dev, "failed to add hcd with err %d\n", ret); + goto err_add_hcd; + } + + return 0; + +err_add_hcd: + pm_runtime_put_sync(dev); + usb_put_hcd(hcd); + +err_io: + iounmap(regs); + + return ret; +} + +/* + * may be called without controller electrically present + * may be called with controller, bus, and devices active + */ + +/** + * ohci_hcd_omap3_remove - shutdown processing for OHCI HCDs + * @pdev: USB Host Controller being removed + * + * Reverses the effect of ohci_hcd_omap3_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + */ +static int ohci_hcd_omap3_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = dev_get_drvdata(dev); + + iounmap(hcd->regs); + usb_remove_hcd(hcd); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + usb_put_hcd(hcd); + return 0; +} + +static void ohci_hcd_omap3_shutdown(struct platform_device *pdev) +{ + struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static const struct of_device_id omap_ohci_dt_ids[] = { + { .compatible = "ti,ohci-omap3" }, + { } +}; + +MODULE_DEVICE_TABLE(of, omap_ohci_dt_ids); + +static struct platform_driver ohci_hcd_omap3_driver = { + .probe = ohci_hcd_omap3_probe, + .remove = ohci_hcd_omap3_remove, + .shutdown = ohci_hcd_omap3_shutdown, + .driver = { + .name = "ohci-omap3", + .of_match_table = of_match_ptr(omap_ohci_dt_ids), + }, +}; + +MODULE_ALIAS("platform:ohci-omap3"); +MODULE_AUTHOR("Anand Gadiyar "); diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c new file mode 100644 index 00000000..67af8eef --- /dev/null +++ b/drivers/usb/host/ohci-pci.c @@ -0,0 +1,380 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * + * [ Initialisation is based on Linus' ] + * [ uhci code and gregs ohci fragments ] + * [ (C) Copyright 1999 Linus Torvalds ] + * [ (C) Copyright 1999 Gregory P. Smith] + * + * PCI Bus Glue + * + * This file is licenced under the GPL. + */ + +#ifndef CONFIG_PCI +#error "This file is PCI bus glue. CONFIG_PCI must be defined." +#endif + +#include +#include + + +/*-------------------------------------------------------------------------*/ + +static int broken_suspend(struct usb_hcd *hcd) +{ + device_init_wakeup(&hcd->self.root_hub->dev, 0); + return 0; +} + +/* AMD 756, for most chips (early revs), corrupts register + * values on read ... so enable the vendor workaround. + */ +static int ohci_quirk_amd756(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci->flags = OHCI_QUIRK_AMD756; + ohci_dbg (ohci, "AMD756 erratum 4 workaround\n"); + + /* also erratum 10 (suspend/resume issues) */ + return broken_suspend(hcd); +} + +/* Apple's OHCI driver has a lot of bizarre workarounds + * for this chip. Evidently control and bulk lists + * can get confused. (B&W G3 models, and ...) + */ +static int ohci_quirk_opti(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci_dbg (ohci, "WARNING: OPTi workarounds unavailable\n"); + + return 0; +} + +/* Check for NSC87560. We have to look at the bridge (fn1) to + * identify the USB (fn2). This quirk might apply to more or + * even all NSC stuff. + */ +static int ohci_quirk_ns(struct usb_hcd *hcd) +{ + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + struct pci_dev *b; + + b = pci_get_slot (pdev->bus, PCI_DEVFN (PCI_SLOT (pdev->devfn), 1)); + if (b && b->device == PCI_DEVICE_ID_NS_87560_LIO + && b->vendor == PCI_VENDOR_ID_NS) { + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci->flags |= OHCI_QUIRK_SUPERIO; + ohci_dbg (ohci, "Using NSC SuperIO setup\n"); + } + pci_dev_put(b); + + return 0; +} + +/* Check for Compaq's ZFMicro chipset, which needs short + * delays before control or bulk queues get re-activated + * in finish_unlinks() + */ +static int ohci_quirk_zfmicro(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci->flags |= OHCI_QUIRK_ZFMICRO; + ohci_dbg(ohci, "enabled Compaq ZFMicro chipset quirks\n"); + + return 0; +} + +/* Check for Toshiba SCC OHCI which has big endian registers + * and little endian in memory data structures + */ +static int ohci_quirk_toshiba_scc(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + /* That chip is only present in the southbridge of some + * cell based platforms which are supposed to select + * CONFIG_USB_OHCI_BIG_ENDIAN_MMIO. We verify here if + * that was the case though. + */ +#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO + ohci->flags |= OHCI_QUIRK_BE_MMIO; + ohci_dbg (ohci, "enabled big endian Toshiba quirk\n"); + return 0; +#else + ohci_err (ohci, "unsupported big endian Toshiba quirk\n"); + return -ENXIO; +#endif +} + +/* Check for NEC chip and apply quirk for allegedly lost interrupts. + */ + +static void ohci_quirk_nec_worker(struct work_struct *work) +{ + struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work); + int status; + + status = ohci_init(ohci); + if (status != 0) { + ohci_err(ohci, "Restarting NEC controller failed in %s, %d\n", + "ohci_init", status); + return; + } + + status = ohci_restart(ohci); + if (status != 0) + ohci_err(ohci, "Restarting NEC controller failed in %s, %d\n", + "ohci_restart", status); +} + +static int ohci_quirk_nec(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci->flags |= OHCI_QUIRK_NEC; + INIT_WORK(&ohci->nec_work, ohci_quirk_nec_worker); + ohci_dbg (ohci, "enabled NEC chipset lost interrupt quirk\n"); + + return 0; +} + +static int ohci_quirk_amd700(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct pci_dev *amd_smbus_dev; + u8 rev; + + if (usb_amd_find_chipset_info()) + ohci->flags |= OHCI_QUIRK_AMD_PLL; + + amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI, + PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); + if (!amd_smbus_dev) + return 0; + + rev = amd_smbus_dev->revision; + + /* SB800 needs pre-fetch fix */ + if ((rev >= 0x40) && (rev <= 0x4f)) { + ohci->flags |= OHCI_QUIRK_AMD_PREFETCH; + ohci_dbg(ohci, "enabled AMD prefetch quirk\n"); + } + + pci_dev_put(amd_smbus_dev); + amd_smbus_dev = NULL; + + ohci->flags |= OHCI_QUIRK_GLOBAL_SUSPEND; + return 0; +} + +static void sb800_prefetch(struct ohci_hcd *ohci, int on) +{ + struct pci_dev *pdev; + u16 misc; + + pdev = to_pci_dev(ohci_to_hcd(ohci)->self.controller); + pci_read_config_word(pdev, 0x50, &misc); + if (on == 0) + pci_write_config_word(pdev, 0x50, misc & 0xfcff); + else + pci_write_config_word(pdev, 0x50, misc | 0x0300); +} + +/* List of quirks for OHCI */ +static const struct pci_device_id ohci_pci_quirks[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x740c), + .driver_data = (unsigned long)ohci_quirk_amd756, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_OPTI, 0xc861), + .driver_data = (unsigned long)ohci_quirk_opti, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_ANY_ID), + .driver_data = (unsigned long)ohci_quirk_ns, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xa0f8), + .driver_data = (unsigned long)ohci_quirk_zfmicro, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA_2, 0x01b6), + .driver_data = (unsigned long)ohci_quirk_toshiba_scc, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_USB), + .driver_data = (unsigned long)ohci_quirk_nec, + }, + { + /* Toshiba portege 4000 */ + .vendor = PCI_VENDOR_ID_AL, + .device = 0x5237, + .subvendor = PCI_VENDOR_ID_TOSHIBA, + .subdevice = 0x0004, + .driver_data = (unsigned long) broken_suspend, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_ITE, 0x8152), + .driver_data = (unsigned long) broken_suspend, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4397), + .driver_data = (unsigned long)ohci_quirk_amd700, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4398), + .driver_data = (unsigned long)ohci_quirk_amd700, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4399), + .driver_data = (unsigned long)ohci_quirk_amd700, + }, + + /* FIXME for some of the early AMD 760 southbridges, OHCI + * won't work at all. blacklist them. + */ + + {}, +}; + +static int ohci_pci_reset (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret = 0; + + if (hcd->self.controller) { + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + const struct pci_device_id *quirk_id; + + quirk_id = pci_match_id(ohci_pci_quirks, pdev); + if (quirk_id != NULL) { + int (*quirk)(struct usb_hcd *ohci); + quirk = (void *)quirk_id->driver_data; + ret = quirk(hcd); + } + } + if (ret == 0) { + ohci_hcd_init (ohci); + return ohci_init (ohci); + } + return ret; +} + + +static int ohci_pci_start (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + +#ifdef CONFIG_PM /* avoid warnings about unused pdev */ + if (hcd->self.controller) { + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + + /* RWC may not be set for add-in PCI cards, since boot + * firmware probably ignored them. This transfers PCI + * PM wakeup capabilities. + */ + if (device_can_wakeup(&pdev->dev)) + ohci->hc_control |= OHCI_CTRL_RWC; + } +#endif /* CONFIG_PM */ + + ret = ohci_run (ohci); + if (ret < 0) { + ohci_err (ohci, "can't start\n"); + ohci_stop (hcd); + } + return ret; +} + + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ohci_pci_hc_driver = { + .description = hcd_name, + .product_desc = "OHCI Host Controller", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_MEMORY | HCD_USB11, + + /* + * basic lifecycle operations + */ + .reset = ohci_pci_reset, + .start = ohci_pci_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + +#ifdef CONFIG_PM + .pci_suspend = ohci_suspend, + .pci_resume = ohci_resume, +#endif + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + + +static const struct pci_device_id pci_ids [] = { { + /* handle any USB OHCI controller */ + PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_OHCI, ~0), + .driver_data = (unsigned long) &ohci_pci_hc_driver, + }, { + /* The device in the ConneXT I/O hub has no class reg */ + PCI_VDEVICE(STMICRO, PCI_DEVICE_ID_STMICRO_USB_OHCI), + .driver_data = (unsigned long) &ohci_pci_hc_driver, + }, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE (pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver ohci_pci_driver = { + .name = (char *) hcd_name, + .id_table = pci_ids, + + .probe = usb_hcd_pci_probe, + .remove = usb_hcd_pci_remove, + .shutdown = usb_hcd_pci_shutdown, + +#ifdef CONFIG_PM + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, +#endif +}; diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c new file mode 100644 index 00000000..c3e7287f --- /dev/null +++ b/drivers/usb/host/ohci-platform.c @@ -0,0 +1,225 @@ +/* + * Generic platform ohci driver + * + * Copyright 2007 Michael Buesch + * Copyright 2011-2012 Hauke Mehrtens + * + * Derived from the OCHI-SSB driver + * Derived from the OHCI-PCI driver + * Copyright 1999 Roman Weissgaerber + * Copyright 2000-2002 David Brownell + * Copyright 1999 Linus Torvalds + * Copyright 1999 Gregory P. Smith + * + * Licensed under the GNU/GPL. See COPYING for details. + */ +#include +#include +#include + +static int ohci_platform_reset(struct usb_hcd *hcd) +{ + struct platform_device *pdev = to_platform_device(hcd->self.controller); + struct usb_ohci_pdata *pdata = pdev->dev.platform_data; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int err; + + if (pdata->big_endian_desc) + ohci->flags |= OHCI_QUIRK_BE_DESC; + if (pdata->big_endian_mmio) + ohci->flags |= OHCI_QUIRK_BE_MMIO; + if (pdata->no_big_frame_no) + ohci->flags |= OHCI_QUIRK_FRAME_NO; + + ohci_hcd_init(ohci); + + if (pdata->num_ports) + ohci->num_ports = pdata->num_ports; + + err = ohci_init(ohci); + + return err; +} + +static int ohci_platform_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int err; + + err = ohci_run(ohci); + if (err < 0) { + ohci_err(ohci, "can't start\n"); + ohci_stop(hcd); + } + + return err; +} + +static const struct hc_driver ohci_platform_hc_driver = { + .description = hcd_name, + .product_desc = "Generic Platform OHCI Controller", + .hcd_priv_size = sizeof(struct ohci_hcd), + + .irq = ohci_irq, + .flags = HCD_MEMORY | HCD_USB11, + + .reset = ohci_platform_reset, + .start = ohci_platform_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + .get_frame_number = ohci_get_frame, + + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + + .start_port_reset = ohci_start_port_reset, +}; + +static int ohci_platform_probe(struct platform_device *dev) +{ + struct usb_hcd *hcd; + struct resource *res_mem; + struct usb_ohci_pdata *pdata = dev->dev.platform_data; + int irq; + int err = -ENOMEM; + + if (!pdata) { + WARN_ON(1); + return -ENODEV; + } + + if (usb_disabled()) + return -ENODEV; + + irq = platform_get_irq(dev, 0); + if (irq < 0) { + dev_err(&dev->dev, "no irq provided"); + return irq; + } + + res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res_mem) { + dev_err(&dev->dev, "no memory resource provided"); + return -ENXIO; + } + + if (pdata->power_on) { + err = pdata->power_on(dev); + if (err < 0) + return err; + } + + hcd = usb_create_hcd(&ohci_platform_hc_driver, &dev->dev, + dev_name(&dev->dev)); + if (!hcd) { + err = -ENOMEM; + goto err_power; + } + + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + + hcd->regs = devm_ioremap_resource(&dev->dev, res_mem); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto err_put_hcd; + } + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_put_hcd; + + platform_set_drvdata(dev, hcd); + + return err; + +err_put_hcd: + usb_put_hcd(hcd); +err_power: + if (pdata->power_off) + pdata->power_off(dev); + + return err; +} + +static int ohci_platform_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct usb_ohci_pdata *pdata = dev->dev.platform_data; + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + platform_set_drvdata(dev, NULL); + + if (pdata->power_off) + pdata->power_off(dev); + + return 0; +} + +#ifdef CONFIG_PM + +static int ohci_platform_suspend(struct device *dev) +{ + struct usb_ohci_pdata *pdata = dev->platform_data; + struct platform_device *pdev = + container_of(dev, struct platform_device, dev); + + if (pdata->power_suspend) + pdata->power_suspend(pdev); + + return 0; +} + +static int ohci_platform_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ohci_pdata *pdata = dev->platform_data; + struct platform_device *pdev = + container_of(dev, struct platform_device, dev); + + if (pdata->power_on) { + int err = pdata->power_on(pdev); + if (err < 0) + return err; + } + + ohci_resume(hcd, false); + return 0; +} + +#else /* !CONFIG_PM */ +#define ohci_platform_suspend NULL +#define ohci_platform_resume NULL +#endif /* CONFIG_PM */ + +static const struct platform_device_id ohci_platform_table[] = { + { "ohci-platform", 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, ohci_platform_table); + +static const struct dev_pm_ops ohci_platform_pm_ops = { + .suspend = ohci_platform_suspend, + .resume = ohci_platform_resume, +}; + +static struct platform_driver ohci_platform_driver = { + .id_table = ohci_platform_table, + .probe = ohci_platform_probe, + .remove = ohci_platform_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "ohci-platform", + .pm = &ohci_platform_pm_ops, + } +}; diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c new file mode 100644 index 00000000..64c2ed9f --- /dev/null +++ b/drivers/usb/host/ohci-ppc-of.c @@ -0,0 +1,254 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * (C) Copyright 2006 Sylvain Munaut + * + * Bus glue for OHCI HC on the of_platform bus + * + * Modified for of_platform bus from ohci-sa1111.c + * + * This file is licenced under the GPL. + */ + +#include +#include + +#include + + +static int +ohci_ppc_of_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + if ((ret = ohci_run(ohci)) < 0) { + dev_err(hcd->self.controller, "can't start %s\n", + hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + +static const struct hc_driver ohci_ppc_of_hc_driver = { + .description = hcd_name, + .product_desc = "OF OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .start = ohci_ppc_of_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + + +static int ohci_hcd_ppc_of_probe(struct platform_device *op) +{ + struct device_node *dn = op->dev.of_node; + struct usb_hcd *hcd; + struct ohci_hcd *ohci; + struct resource res; + int irq; + + int rv; + int is_bigendian; + struct device_node *np; + + if (usb_disabled()) + return -ENODEV; + + is_bigendian = + of_device_is_compatible(dn, "ohci-bigendian") || + of_device_is_compatible(dn, "ohci-be"); + + dev_dbg(&op->dev, "initializing PPC-OF USB Controller\n"); + + rv = of_address_to_resource(dn, 0, &res); + if (rv) + return rv; + + hcd = usb_create_hcd(&ohci_ppc_of_hc_driver, &op->dev, "PPC-OF USB"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = res.start; + hcd->rsrc_len = resource_size(&res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + printk(KERN_ERR "%s: request_mem_region failed\n", __FILE__); + rv = -EBUSY; + goto err_rmr; + } + + irq = irq_of_parse_and_map(dn, 0); + if (irq == NO_IRQ) { + printk(KERN_ERR "%s: irq_of_parse_and_map failed\n", __FILE__); + rv = -EBUSY; + goto err_irq; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + printk(KERN_ERR "%s: ioremap failed\n", __FILE__); + rv = -ENOMEM; + goto err_ioremap; + } + + ohci = hcd_to_ohci(hcd); + if (is_bigendian) { + ohci->flags |= OHCI_QUIRK_BE_MMIO | OHCI_QUIRK_BE_DESC; + if (of_device_is_compatible(dn, "fsl,mpc5200-ohci")) + ohci->flags |= OHCI_QUIRK_FRAME_NO; + if (of_device_is_compatible(dn, "mpc5200-ohci")) + ohci->flags |= OHCI_QUIRK_FRAME_NO; + } + + ohci_hcd_init(ohci); + + rv = usb_add_hcd(hcd, irq, 0); + if (rv == 0) + return 0; + + /* by now, 440epx is known to show usb_23 erratum */ + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ehci-440epx"); + + /* Work around - At this point ohci_run has executed, the + * controller is running, everything, the root ports, etc., is + * set up. If the ehci driver is loaded, put the ohci core in + * the suspended state. The ehci driver will bring it out of + * suspended state when / if a non-high speed USB device is + * attached to the USB Host port. If the ehci driver is not + * loaded, do nothing. request_mem_region is used to test if + * the ehci driver is loaded. + */ + if (np != NULL) { + if (!of_address_to_resource(np, 0, &res)) { + if (!request_mem_region(res.start, 0x4, hcd_name)) { + writel_be((readl_be(&ohci->regs->control) | + OHCI_USB_SUSPEND), &ohci->regs->control); + (void) readl_be(&ohci->regs->control); + } else + release_mem_region(res.start, 0x4); + } else + pr_debug("%s: cannot get ehci offset from fdt\n", __FILE__); + } + + iounmap(hcd->regs); +err_ioremap: + irq_dispose_mapping(irq); +err_irq: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err_rmr: + usb_put_hcd(hcd); + + return rv; +} + +static int ohci_hcd_ppc_of_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + dev_set_drvdata(&op->dev, NULL); + + dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); + + usb_remove_hcd(hcd); + + iounmap(hcd->regs); + irq_dispose_mapping(hcd->irq); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + + usb_put_hcd(hcd); + + return 0; +} + +static void ohci_hcd_ppc_of_shutdown(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + + +static const struct of_device_id ohci_hcd_ppc_of_match[] = { +#ifdef CONFIG_USB_OHCI_HCD_PPC_OF_BE + { + .name = "usb", + .compatible = "ohci-bigendian", + }, + { + .name = "usb", + .compatible = "ohci-be", + }, +#endif +#ifdef CONFIG_USB_OHCI_HCD_PPC_OF_LE + { + .name = "usb", + .compatible = "ohci-littledian", + }, + { + .name = "usb", + .compatible = "ohci-le", + }, +#endif + {}, +}; +MODULE_DEVICE_TABLE(of, ohci_hcd_ppc_of_match); + +#if !defined(CONFIG_USB_OHCI_HCD_PPC_OF_BE) && \ + !defined(CONFIG_USB_OHCI_HCD_PPC_OF_LE) +#error "No endianness selected for ppc-of-ohci" +#endif + + +static struct platform_driver ohci_hcd_ppc_of_driver = { + .probe = ohci_hcd_ppc_of_probe, + .remove = ohci_hcd_ppc_of_remove, + .shutdown = ohci_hcd_ppc_of_shutdown, + .driver = { + .name = "ppc-of-ohci", + .owner = THIS_MODULE, + .of_match_table = ohci_hcd_ppc_of_match, + }, +}; + diff --git a/drivers/usb/host/ohci-ps3.c b/drivers/usb/host/ohci-ps3.c new file mode 100644 index 00000000..7d35cd9e --- /dev/null +++ b/drivers/usb/host/ohci-ps3.c @@ -0,0 +1,250 @@ +/* + * PS3 OHCI Host Controller driver + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +static int ps3_ohci_hc_reset(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci->flags |= OHCI_QUIRK_BE_MMIO; + ohci_hcd_init(ohci); + return ohci_init(ohci); +} + +static int ps3_ohci_hc_start(struct usb_hcd *hcd) +{ + int result; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + /* Handle root hub init quirk in spider south bridge. */ + /* Also set PwrOn2PwrGood to 0x7f (254ms). */ + + ohci_writel(ohci, 0x7f000000 | RH_A_PSM | RH_A_OCPM, + &ohci->regs->roothub.a); + ohci_writel(ohci, 0x00060000, &ohci->regs->roothub.b); + + result = ohci_run(ohci); + + if (result < 0) { + dev_err(hcd->self.controller, "can't start %s\n", + hcd->self.bus_name); + ohci_stop(hcd); + } + + return result; +} + +static const struct hc_driver ps3_ohci_hc_driver = { + .description = hcd_name, + .product_desc = "PS3 OHCI Host Controller", + .hcd_priv_size = sizeof(struct ohci_hcd), + .irq = ohci_irq, + .flags = HCD_MEMORY | HCD_USB11, + .reset = ps3_ohci_hc_reset, + .start = ps3_ohci_hc_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + .get_frame_number = ohci_get_frame, + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, + .start_port_reset = ohci_start_port_reset, +#if defined(CONFIG_PM) + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif +}; + +static int ps3_ohci_probe(struct ps3_system_bus_device *dev) +{ + int result; + struct usb_hcd *hcd; + unsigned int virq; + static u64 dummy_mask = DMA_BIT_MASK(32); + + if (usb_disabled()) { + result = -ENODEV; + goto fail_start; + } + + result = ps3_open_hv_device(dev); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_open_hv_device failed: %s\n", + __func__, __LINE__, ps3_result(result)); + result = -EPERM; + goto fail_open; + } + + result = ps3_dma_region_create(dev->d_region); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_dma_region_create failed: " + "(%d)\n", __func__, __LINE__, result); + BUG_ON("check region type"); + goto fail_dma_region; + } + + result = ps3_mmio_region_create(dev->m_region); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_map_mmio_region failed\n", + __func__, __LINE__); + result = -EPERM; + goto fail_mmio_region; + } + + dev_dbg(&dev->core, "%s:%d: mmio mapped_addr %lxh\n", __func__, + __LINE__, dev->m_region->lpar_addr); + + result = ps3_io_irq_setup(PS3_BINDING_CPU_ANY, dev->interrupt_id, &virq); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_construct_io_irq(%d) failed.\n", + __func__, __LINE__, virq); + result = -EPERM; + goto fail_irq; + } + + dev->core.dma_mask = &dummy_mask; /* FIXME: for improper usb code */ + + hcd = usb_create_hcd(&ps3_ohci_hc_driver, &dev->core, dev_name(&dev->core)); + + if (!hcd) { + dev_dbg(&dev->core, "%s:%d: usb_create_hcd failed\n", __func__, + __LINE__); + result = -ENOMEM; + goto fail_create_hcd; + } + + hcd->rsrc_start = dev->m_region->lpar_addr; + hcd->rsrc_len = dev->m_region->len; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) + dev_dbg(&dev->core, "%s:%d: request_mem_region failed\n", + __func__, __LINE__); + + hcd->regs = ioremap(dev->m_region->lpar_addr, dev->m_region->len); + + if (!hcd->regs) { + dev_dbg(&dev->core, "%s:%d: ioremap failed\n", __func__, + __LINE__); + result = -EPERM; + goto fail_ioremap; + } + + dev_dbg(&dev->core, "%s:%d: hcd->rsrc_start %lxh\n", __func__, __LINE__, + (unsigned long)hcd->rsrc_start); + dev_dbg(&dev->core, "%s:%d: hcd->rsrc_len %lxh\n", __func__, __LINE__, + (unsigned long)hcd->rsrc_len); + dev_dbg(&dev->core, "%s:%d: hcd->regs %lxh\n", __func__, __LINE__, + (unsigned long)hcd->regs); + dev_dbg(&dev->core, "%s:%d: virq %lu\n", __func__, __LINE__, + (unsigned long)virq); + + ps3_system_bus_set_drvdata(dev, hcd); + + result = usb_add_hcd(hcd, virq, 0); + + if (result) { + dev_dbg(&dev->core, "%s:%d: usb_add_hcd failed (%d)\n", + __func__, __LINE__, result); + goto fail_add_hcd; + } + + return result; + +fail_add_hcd: + iounmap(hcd->regs); +fail_ioremap: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +fail_create_hcd: + ps3_io_irq_destroy(virq); +fail_irq: + ps3_free_mmio_region(dev->m_region); +fail_mmio_region: + ps3_dma_region_free(dev->d_region); +fail_dma_region: + ps3_close_hv_device(dev); +fail_open: +fail_start: + return result; +} + +static int ps3_ohci_remove(struct ps3_system_bus_device *dev) +{ + unsigned int tmp; + struct usb_hcd *hcd = ps3_system_bus_get_drvdata(dev); + + BUG_ON(!hcd); + + dev_dbg(&dev->core, "%s:%d: regs %p\n", __func__, __LINE__, hcd->regs); + dev_dbg(&dev->core, "%s:%d: irq %u\n", __func__, __LINE__, hcd->irq); + + tmp = hcd->irq; + + ohci_shutdown(hcd); + usb_remove_hcd(hcd); + + ps3_system_bus_set_drvdata(dev, NULL); + + BUG_ON(!hcd->regs); + iounmap(hcd->regs); + + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + ps3_io_irq_destroy(tmp); + ps3_free_mmio_region(dev->m_region); + + ps3_dma_region_free(dev->d_region); + ps3_close_hv_device(dev); + + return 0; +} + +static int __init ps3_ohci_driver_register(struct ps3_system_bus_driver *drv) +{ + return firmware_has_feature(FW_FEATURE_PS3_LV1) + ? ps3_system_bus_driver_register(drv) + : 0; +} + +static void ps3_ohci_driver_unregister(struct ps3_system_bus_driver *drv) +{ + if (firmware_has_feature(FW_FEATURE_PS3_LV1)) + ps3_system_bus_driver_unregister(drv); +} + +MODULE_ALIAS(PS3_MODULE_ALIAS_OHCI); + +static struct ps3_system_bus_driver ps3_ohci_driver = { + .core.name = "ps3-ohci-driver", + .core.owner = THIS_MODULE, + .match_id = PS3_MATCH_ID_OHCI, + .probe = ps3_ohci_probe, + .remove = ps3_ohci_remove, + .shutdown = ps3_ohci_remove, +}; diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c new file mode 100644 index 00000000..279b2ef1 --- /dev/null +++ b/drivers/usb/host/ohci-pxa27x.c @@ -0,0 +1,620 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * + * Bus Glue for pxa27x + * + * Written by Christopher Hoover + * Based on fragments of previous driver by Russell King et al. + * + * Modified for LH7A404 from ohci-sa1111.c + * by Durgesh Pattamatta + * + * Modified for pxa27x from ohci-lh7a404.c + * by Nick Bane 26-8-2004 + * + * This file is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * UHC: USB Host Controller (OHCI-like) register definitions + */ +#define UHCREV (0x0000) /* UHC HCI Spec Revision */ +#define UHCHCON (0x0004) /* UHC Host Control Register */ +#define UHCCOMS (0x0008) /* UHC Command Status Register */ +#define UHCINTS (0x000C) /* UHC Interrupt Status Register */ +#define UHCINTE (0x0010) /* UHC Interrupt Enable */ +#define UHCINTD (0x0014) /* UHC Interrupt Disable */ +#define UHCHCCA (0x0018) /* UHC Host Controller Comm. Area */ +#define UHCPCED (0x001C) /* UHC Period Current Endpt Descr */ +#define UHCCHED (0x0020) /* UHC Control Head Endpt Descr */ +#define UHCCCED (0x0024) /* UHC Control Current Endpt Descr */ +#define UHCBHED (0x0028) /* UHC Bulk Head Endpt Descr */ +#define UHCBCED (0x002C) /* UHC Bulk Current Endpt Descr */ +#define UHCDHEAD (0x0030) /* UHC Done Head */ +#define UHCFMI (0x0034) /* UHC Frame Interval */ +#define UHCFMR (0x0038) /* UHC Frame Remaining */ +#define UHCFMN (0x003C) /* UHC Frame Number */ +#define UHCPERS (0x0040) /* UHC Periodic Start */ +#define UHCLS (0x0044) /* UHC Low Speed Threshold */ + +#define UHCRHDA (0x0048) /* UHC Root Hub Descriptor A */ +#define UHCRHDA_NOCP (1 << 12) /* No over current protection */ +#define UHCRHDA_OCPM (1 << 11) /* Over Current Protection Mode */ +#define UHCRHDA_POTPGT(x) \ + (((x) & 0xff) << 24) /* Power On To Power Good Time */ + +#define UHCRHDB (0x004C) /* UHC Root Hub Descriptor B */ +#define UHCRHS (0x0050) /* UHC Root Hub Status */ +#define UHCRHPS1 (0x0054) /* UHC Root Hub Port 1 Status */ +#define UHCRHPS2 (0x0058) /* UHC Root Hub Port 2 Status */ +#define UHCRHPS3 (0x005C) /* UHC Root Hub Port 3 Status */ + +#define UHCSTAT (0x0060) /* UHC Status Register */ +#define UHCSTAT_UPS3 (1 << 16) /* USB Power Sense Port3 */ +#define UHCSTAT_SBMAI (1 << 15) /* System Bus Master Abort Interrupt*/ +#define UHCSTAT_SBTAI (1 << 14) /* System Bus Target Abort Interrupt*/ +#define UHCSTAT_UPRI (1 << 13) /* USB Port Resume Interrupt */ +#define UHCSTAT_UPS2 (1 << 12) /* USB Power Sense Port 2 */ +#define UHCSTAT_UPS1 (1 << 11) /* USB Power Sense Port 1 */ +#define UHCSTAT_HTA (1 << 10) /* HCI Target Abort */ +#define UHCSTAT_HBA (1 << 8) /* HCI Buffer Active */ +#define UHCSTAT_RWUE (1 << 7) /* HCI Remote Wake Up Event */ + +#define UHCHR (0x0064) /* UHC Reset Register */ +#define UHCHR_SSEP3 (1 << 11) /* Sleep Standby Enable for Port3 */ +#define UHCHR_SSEP2 (1 << 10) /* Sleep Standby Enable for Port2 */ +#define UHCHR_SSEP1 (1 << 9) /* Sleep Standby Enable for Port1 */ +#define UHCHR_PCPL (1 << 7) /* Power control polarity low */ +#define UHCHR_PSPL (1 << 6) /* Power sense polarity low */ +#define UHCHR_SSE (1 << 5) /* Sleep Standby Enable */ +#define UHCHR_UIT (1 << 4) /* USB Interrupt Test */ +#define UHCHR_SSDC (1 << 3) /* Simulation Scale Down Clock */ +#define UHCHR_CGR (1 << 2) /* Clock Generation Reset */ +#define UHCHR_FHR (1 << 1) /* Force Host Controller Reset */ +#define UHCHR_FSBIR (1 << 0) /* Force System Bus Iface Reset */ + +#define UHCHIE (0x0068) /* UHC Interrupt Enable Register*/ +#define UHCHIE_UPS3IE (1 << 14) /* Power Sense Port3 IntEn */ +#define UHCHIE_UPRIE (1 << 13) /* Port Resume IntEn */ +#define UHCHIE_UPS2IE (1 << 12) /* Power Sense Port2 IntEn */ +#define UHCHIE_UPS1IE (1 << 11) /* Power Sense Port1 IntEn */ +#define UHCHIE_TAIE (1 << 10) /* HCI Interface Transfer Abort + Interrupt Enable*/ +#define UHCHIE_HBAIE (1 << 8) /* HCI Buffer Active IntEn */ +#define UHCHIE_RWIE (1 << 7) /* Remote Wake-up IntEn */ + +#define UHCHIT (0x006C) /* UHC Interrupt Test register */ + +#define PXA_UHC_MAX_PORTNUM 3 + +struct pxa27x_ohci { + /* must be 1st member here for hcd_to_ohci() to work */ + struct ohci_hcd ohci; + + struct device *dev; + struct clk *clk; + void __iomem *mmio_base; +}; + +#define to_pxa27x_ohci(hcd) (struct pxa27x_ohci *)hcd_to_ohci(hcd) + +/* + PMM_NPS_MODE -- PMM Non-power switching mode + Ports are powered continuously. + + PMM_GLOBAL_MODE -- PMM global switching mode + All ports are powered at the same time. + + PMM_PERPORT_MODE -- PMM per port switching mode + Ports are powered individually. + */ +static int pxa27x_ohci_select_pmm(struct pxa27x_ohci *ohci, int mode) +{ + uint32_t uhcrhda = __raw_readl(ohci->mmio_base + UHCRHDA); + uint32_t uhcrhdb = __raw_readl(ohci->mmio_base + UHCRHDB); + + switch (mode) { + case PMM_NPS_MODE: + uhcrhda |= RH_A_NPS; + break; + case PMM_GLOBAL_MODE: + uhcrhda &= ~(RH_A_NPS & RH_A_PSM); + break; + case PMM_PERPORT_MODE: + uhcrhda &= ~(RH_A_NPS); + uhcrhda |= RH_A_PSM; + + /* Set port power control mask bits, only 3 ports. */ + uhcrhdb |= (0x7<<17); + break; + default: + printk( KERN_ERR + "Invalid mode %d, set to non-power switch mode.\n", + mode ); + + uhcrhda |= RH_A_NPS; + } + + __raw_writel(uhcrhda, ohci->mmio_base + UHCRHDA); + __raw_writel(uhcrhdb, ohci->mmio_base + UHCRHDB); + return 0; +} + +extern int usb_disabled(void); + +/*-------------------------------------------------------------------------*/ + +static inline void pxa27x_setup_hc(struct pxa27x_ohci *ohci, + struct pxaohci_platform_data *inf) +{ + uint32_t uhchr = __raw_readl(ohci->mmio_base + UHCHR); + uint32_t uhcrhda = __raw_readl(ohci->mmio_base + UHCRHDA); + + if (inf->flags & ENABLE_PORT1) + uhchr &= ~UHCHR_SSEP1; + + if (inf->flags & ENABLE_PORT2) + uhchr &= ~UHCHR_SSEP2; + + if (inf->flags & ENABLE_PORT3) + uhchr &= ~UHCHR_SSEP3; + + if (inf->flags & POWER_CONTROL_LOW) + uhchr |= UHCHR_PCPL; + + if (inf->flags & POWER_SENSE_LOW) + uhchr |= UHCHR_PSPL; + + if (inf->flags & NO_OC_PROTECTION) + uhcrhda |= UHCRHDA_NOCP; + else + uhcrhda &= ~UHCRHDA_NOCP; + + if (inf->flags & OC_MODE_PERPORT) + uhcrhda |= UHCRHDA_OCPM; + else + uhcrhda &= ~UHCRHDA_OCPM; + + if (inf->power_on_delay) { + uhcrhda &= ~UHCRHDA_POTPGT(0xff); + uhcrhda |= UHCRHDA_POTPGT(inf->power_on_delay / 2); + } + + __raw_writel(uhchr, ohci->mmio_base + UHCHR); + __raw_writel(uhcrhda, ohci->mmio_base + UHCRHDA); +} + +static inline void pxa27x_reset_hc(struct pxa27x_ohci *ohci) +{ + uint32_t uhchr = __raw_readl(ohci->mmio_base + UHCHR); + + __raw_writel(uhchr | UHCHR_FHR, ohci->mmio_base + UHCHR); + udelay(11); + __raw_writel(uhchr & ~UHCHR_FHR, ohci->mmio_base + UHCHR); +} + +#ifdef CONFIG_PXA27x +extern void pxa27x_clear_otgph(void); +#else +#define pxa27x_clear_otgph() do {} while (0) +#endif + +static int pxa27x_start_hc(struct pxa27x_ohci *ohci, struct device *dev) +{ + int retval = 0; + struct pxaohci_platform_data *inf; + uint32_t uhchr; + + inf = dev->platform_data; + + clk_prepare_enable(ohci->clk); + + pxa27x_reset_hc(ohci); + + uhchr = __raw_readl(ohci->mmio_base + UHCHR) | UHCHR_FSBIR; + __raw_writel(uhchr, ohci->mmio_base + UHCHR); + + while (__raw_readl(ohci->mmio_base + UHCHR) & UHCHR_FSBIR) + cpu_relax(); + + pxa27x_setup_hc(ohci, inf); + + if (inf->init) + retval = inf->init(dev); + + if (retval < 0) + return retval; + + if (cpu_is_pxa3xx()) + pxa3xx_u2d_start_hc(&ohci_to_hcd(&ohci->ohci)->self); + + uhchr = __raw_readl(ohci->mmio_base + UHCHR) & ~UHCHR_SSE; + __raw_writel(uhchr, ohci->mmio_base + UHCHR); + __raw_writel(UHCHIE_UPRIE | UHCHIE_RWIE, ohci->mmio_base + UHCHIE); + + /* Clear any OTG Pin Hold */ + pxa27x_clear_otgph(); + return 0; +} + +static void pxa27x_stop_hc(struct pxa27x_ohci *ohci, struct device *dev) +{ + struct pxaohci_platform_data *inf; + uint32_t uhccoms; + + inf = dev->platform_data; + + if (cpu_is_pxa3xx()) + pxa3xx_u2d_stop_hc(&ohci_to_hcd(&ohci->ohci)->self); + + if (inf->exit) + inf->exit(dev); + + pxa27x_reset_hc(ohci); + + /* Host Controller Reset */ + uhccoms = __raw_readl(ohci->mmio_base + UHCCOMS) | 0x01; + __raw_writel(uhccoms, ohci->mmio_base + UHCCOMS); + udelay(10); + + clk_disable_unprepare(ohci->clk); +} + +#ifdef CONFIG_OF +static const struct of_device_id pxa_ohci_dt_ids[] = { + { .compatible = "marvell,pxa-ohci" }, + { } +}; + +MODULE_DEVICE_TABLE(of, pxa_ohci_dt_ids); + +static int ohci_pxa_of_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct pxaohci_platform_data *pdata; + u32 tmp; + + if (!np) + return 0; + + /* 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. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (of_get_property(np, "marvell,enable-port1", NULL)) + pdata->flags |= ENABLE_PORT1; + if (of_get_property(np, "marvell,enable-port2", NULL)) + pdata->flags |= ENABLE_PORT2; + if (of_get_property(np, "marvell,enable-port3", NULL)) + pdata->flags |= ENABLE_PORT3; + if (of_get_property(np, "marvell,port-sense-low", NULL)) + pdata->flags |= POWER_SENSE_LOW; + if (of_get_property(np, "marvell,power-control-low", NULL)) + pdata->flags |= POWER_CONTROL_LOW; + if (of_get_property(np, "marvell,no-oc-protection", NULL)) + pdata->flags |= NO_OC_PROTECTION; + if (of_get_property(np, "marvell,oc-mode-perport", NULL)) + pdata->flags |= OC_MODE_PERPORT; + if (!of_property_read_u32(np, "marvell,power-on-delay", &tmp)) + pdata->power_on_delay = tmp; + if (!of_property_read_u32(np, "marvell,port-mode", &tmp)) + pdata->port_mode = tmp; + if (!of_property_read_u32(np, "marvell,power-budget", &tmp)) + pdata->power_budget = tmp; + + pdev->dev.platform_data = pdata; + + return 0; +} +#else +static int ohci_pxa_of_init(struct platform_device *pdev) +{ + return 0; +} +#endif + +/*-------------------------------------------------------------------------*/ + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + + +/** + * usb_hcd_pxa27x_probe - initialize pxa27x-based HCDs + * Context: !in_interrupt() + * + * 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. + * + */ +int usb_hcd_pxa27x_probe (const struct hc_driver *driver, struct platform_device *pdev) +{ + int retval, irq; + struct usb_hcd *hcd; + struct pxaohci_platform_data *inf; + struct pxa27x_ohci *ohci; + struct resource *r; + struct clk *usb_clk; + + retval = ohci_pxa_of_init(pdev); + if (retval) + return retval; + + inf = pdev->dev.platform_data; + + if (!inf) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + pr_err("no resource of IORESOURCE_IRQ"); + return -ENXIO; + } + + usb_clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(usb_clk)) + return PTR_ERR(usb_clk); + + hcd = usb_create_hcd (driver, &pdev->dev, "pxa27x"); + if (!hcd) { + retval = -ENOMEM; + goto err0; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + pr_err("no resource of IORESOURCE_MEM"); + retval = -ENXIO; + goto err1; + } + + hcd->rsrc_start = r->start; + hcd->rsrc_len = resource_size(r); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + pr_debug("request_mem_region failed"); + retval = -EBUSY; + goto err1; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + pr_debug("ioremap failed"); + retval = -ENOMEM; + goto err2; + } + + /* initialize "struct pxa27x_ohci" */ + ohci = (struct pxa27x_ohci *)hcd_to_ohci(hcd); + ohci->dev = &pdev->dev; + ohci->clk = usb_clk; + ohci->mmio_base = (void __iomem *)hcd->regs; + + if ((retval = pxa27x_start_hc(ohci, &pdev->dev)) < 0) { + pr_debug("pxa27x_start_hc failed"); + goto err3; + } + + /* Select Power Management Mode */ + pxa27x_ohci_select_pmm(ohci, inf->port_mode); + + if (inf->power_budget) + hcd->power_budget = inf->power_budget; + + ohci_hcd_init(hcd_to_ohci(hcd)); + + retval = usb_add_hcd(hcd, irq, 0); + if (retval == 0) + return retval; + + pxa27x_stop_hc(ohci, &pdev->dev); + err3: + iounmap(hcd->regs); + err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + err1: + usb_put_hcd(hcd); + err0: + clk_put(usb_clk); + return retval; +} + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_pxa27x_remove - shutdown processing for pxa27x-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_pxa27x_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + */ +void usb_hcd_pxa27x_remove (struct usb_hcd *hcd, struct platform_device *pdev) +{ + struct pxa27x_ohci *ohci = to_pxa27x_ohci(hcd); + + usb_remove_hcd(hcd); + pxa27x_stop_hc(ohci, &pdev->dev); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + clk_put(ohci->clk); +} + +/*-------------------------------------------------------------------------*/ + +static int +ohci_pxa27x_start (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + + ohci_dbg (ohci, "ohci_pxa27x_start, ohci:%p", ohci); + + /* The value of NDP in roothub_a is incorrect on this hardware */ + ohci->num_ports = 3; + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + if ((ret = ohci_run (ohci)) < 0) { + dev_err(hcd->self.controller, "can't start %s", + hcd->self.bus_name); + ohci_stop (hcd); + return ret; + } + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ohci_pxa27x_hc_driver = { + .description = hcd_name, + .product_desc = "PXA27x OHCI", + .hcd_priv_size = sizeof(struct pxa27x_ohci), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .start = ohci_pxa27x_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + +static int ohci_hcd_pxa27x_drv_probe(struct platform_device *pdev) +{ + pr_debug ("In ohci_hcd_pxa27x_drv_probe"); + + if (usb_disabled()) + return -ENODEV; + + return usb_hcd_pxa27x_probe(&ohci_pxa27x_hc_driver, pdev); +} + +static int ohci_hcd_pxa27x_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_hcd_pxa27x_remove(hcd, pdev); + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +static int ohci_hcd_pxa27x_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct pxa27x_ohci *ohci = to_pxa27x_ohci(hcd); + + if (time_before(jiffies, ohci->ohci.next_statechange)) + msleep(5); + ohci->ohci.next_statechange = jiffies; + + pxa27x_stop_hc(ohci, dev); + return 0; +} + +static int ohci_hcd_pxa27x_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct pxa27x_ohci *ohci = to_pxa27x_ohci(hcd); + struct pxaohci_platform_data *inf = dev->platform_data; + int status; + + if (time_before(jiffies, ohci->ohci.next_statechange)) + msleep(5); + ohci->ohci.next_statechange = jiffies; + + if ((status = pxa27x_start_hc(ohci, dev)) < 0) + return status; + + /* Select Power Management Mode */ + pxa27x_ohci_select_pmm(ohci, inf->port_mode); + + ohci_resume(hcd, false); + return 0; +} + +static const struct dev_pm_ops ohci_hcd_pxa27x_pm_ops = { + .suspend = ohci_hcd_pxa27x_drv_suspend, + .resume = ohci_hcd_pxa27x_drv_resume, +}; +#endif + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:pxa27x-ohci"); + +static struct platform_driver ohci_hcd_pxa27x_driver = { + .probe = ohci_hcd_pxa27x_drv_probe, + .remove = ohci_hcd_pxa27x_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "pxa27x-ohci", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pxa_ohci_dt_ids), +#ifdef CONFIG_PM + .pm = &ohci_hcd_pxa27x_pm_ops, +#endif + }, +}; + diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c new file mode 100644 index 00000000..1e1563da --- /dev/null +++ b/drivers/usb/host/ohci-q.c @@ -0,0 +1,1184 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * + * This file is licenced under the GPL. + */ + +#include +#include + +static void urb_free_priv (struct ohci_hcd *hc, urb_priv_t *urb_priv) +{ + int last = urb_priv->length - 1; + + if (last >= 0) { + int i; + struct td *td; + + for (i = 0; i <= last; i++) { + td = urb_priv->td [i]; + if (td) + td_free (hc, td); + } + } + + list_del (&urb_priv->pending); + kfree (urb_priv); +} + +/*-------------------------------------------------------------------------*/ + +/* + * URB goes back to driver, and isn't reissued. + * It's completely gone from HC data structures. + * PRECONDITION: ohci lock held, irqs blocked. + */ +static void +finish_urb(struct ohci_hcd *ohci, struct urb *urb, int status) +__releases(ohci->lock) +__acquires(ohci->lock) +{ + struct usb_host_endpoint *ep = urb->ep; + struct urb_priv *urb_priv; + + // ASSERT (urb->hcpriv != 0); + + restart: + urb_free_priv (ohci, urb->hcpriv); + urb->hcpriv = NULL; + if (likely(status == -EINPROGRESS)) + status = 0; + + switch (usb_pipetype (urb->pipe)) { + case PIPE_ISOCHRONOUS: + ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs--; + if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0) { + if (quirk_amdiso(ohci)) + usb_amd_quirk_pll_enable(); + if (quirk_amdprefetch(ohci)) + sb800_prefetch(ohci, 0); + } + break; + case PIPE_INTERRUPT: + ohci_to_hcd(ohci)->self.bandwidth_int_reqs--; + break; + } + +#ifdef OHCI_VERBOSE_DEBUG + urb_print(urb, "RET", usb_pipeout (urb->pipe), status); +#endif + + /* urb->complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(ohci_to_hcd(ohci), urb); + spin_unlock (&ohci->lock); + usb_hcd_giveback_urb(ohci_to_hcd(ohci), urb, status); + spin_lock (&ohci->lock); + + /* stop periodic dma if it's not needed */ + if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0 + && ohci_to_hcd(ohci)->self.bandwidth_int_reqs == 0) { + ohci->hc_control &= ~(OHCI_CTRL_PLE|OHCI_CTRL_IE); + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + } + + /* + * An isochronous URB that is sumitted too late won't have any TDs + * (marked by the fact that the td_cnt value is larger than the + * actual number of TDs). If the next URB on this endpoint is like + * that, give it back now. + */ + if (!list_empty(&ep->urb_list)) { + urb = list_first_entry(&ep->urb_list, struct urb, urb_list); + urb_priv = urb->hcpriv; + if (urb_priv->td_cnt > urb_priv->length) { + status = 0; + goto restart; + } + } +} + + +/*-------------------------------------------------------------------------* + * ED handling functions + *-------------------------------------------------------------------------*/ + +/* search for the right schedule branch to use for a periodic ed. + * does some load balancing; returns the branch, or negative errno. + */ +static int balance (struct ohci_hcd *ohci, int interval, int load) +{ + int i, branch = -ENOSPC; + + /* iso periods can be huge; iso tds specify frame numbers */ + if (interval > NUM_INTS) + interval = NUM_INTS; + + /* search for the least loaded schedule branch of that period + * that has enough bandwidth left unreserved. + */ + for (i = 0; i < interval ; i++) { + if (branch < 0 || ohci->load [branch] > ohci->load [i]) { + int j; + + /* usb 1.1 says 90% of one frame */ + for (j = i; j < NUM_INTS; j += interval) { + if ((ohci->load [j] + load) > 900) + break; + } + if (j < NUM_INTS) + continue; + branch = i; + } + } + return branch; +} + +/*-------------------------------------------------------------------------*/ + +/* both iso and interrupt requests have periods; this routine puts them + * into the schedule tree in the apppropriate place. most iso devices use + * 1msec periods, but that's not required. + */ +static void periodic_link (struct ohci_hcd *ohci, struct ed *ed) +{ + unsigned i; + + ohci_vdbg (ohci, "link %sed %p branch %d [%dus.], interval %d\n", + (ed->hwINFO & cpu_to_hc32 (ohci, ED_ISO)) ? "iso " : "", + ed, ed->branch, ed->load, ed->interval); + + for (i = ed->branch; i < NUM_INTS; i += ed->interval) { + struct ed **prev = &ohci->periodic [i]; + __hc32 *prev_p = &ohci->hcca->int_table [i]; + struct ed *here = *prev; + + /* sorting each branch by period (slow before fast) + * lets us share the faster parts of the tree. + * (plus maybe: put interrupt eds before iso) + */ + while (here && ed != here) { + if (ed->interval > here->interval) + break; + prev = &here->ed_next; + prev_p = &here->hwNextED; + here = *prev; + } + if (ed != here) { + ed->ed_next = here; + if (here) + ed->hwNextED = *prev_p; + wmb (); + *prev = ed; + *prev_p = cpu_to_hc32(ohci, ed->dma); + wmb(); + } + ohci->load [i] += ed->load; + } + ohci_to_hcd(ohci)->self.bandwidth_allocated += ed->load / ed->interval; +} + +/* link an ed into one of the HC chains */ + +static int ed_schedule (struct ohci_hcd *ohci, struct ed *ed) +{ + int branch; + + ed->state = ED_OPER; + ed->ed_prev = NULL; + ed->ed_next = NULL; + ed->hwNextED = 0; + if (quirk_zfmicro(ohci) + && (ed->type == PIPE_INTERRUPT) + && !(ohci->eds_scheduled++)) + mod_timer(&ohci->unlink_watchdog, round_jiffies(jiffies + HZ)); + wmb (); + + /* we care about rm_list when setting CLE/BLE in case the HC was at + * work on some TD when CLE/BLE was turned off, and isn't quiesced + * yet. finish_unlinks() restarts as needed, some upcoming INTR_SF. + * + * control and bulk EDs are doubly linked (ed_next, ed_prev), but + * periodic ones are singly linked (ed_next). that's because the + * periodic schedule encodes a tree like figure 3-5 in the ohci + * spec: each qh can have several "previous" nodes, and the tree + * doesn't have unused/idle descriptors. + */ + switch (ed->type) { + case PIPE_CONTROL: + if (ohci->ed_controltail == NULL) { + WARN_ON (ohci->hc_control & OHCI_CTRL_CLE); + ohci_writel (ohci, ed->dma, + &ohci->regs->ed_controlhead); + } else { + ohci->ed_controltail->ed_next = ed; + ohci->ed_controltail->hwNextED = cpu_to_hc32 (ohci, + ed->dma); + } + ed->ed_prev = ohci->ed_controltail; + if (!ohci->ed_controltail && !ohci->ed_rm_list) { + wmb(); + ohci->hc_control |= OHCI_CTRL_CLE; + ohci_writel (ohci, 0, &ohci->regs->ed_controlcurrent); + ohci_writel (ohci, ohci->hc_control, + &ohci->regs->control); + } + ohci->ed_controltail = ed; + break; + + case PIPE_BULK: + if (ohci->ed_bulktail == NULL) { + WARN_ON (ohci->hc_control & OHCI_CTRL_BLE); + ohci_writel (ohci, ed->dma, &ohci->regs->ed_bulkhead); + } else { + ohci->ed_bulktail->ed_next = ed; + ohci->ed_bulktail->hwNextED = cpu_to_hc32 (ohci, + ed->dma); + } + ed->ed_prev = ohci->ed_bulktail; + if (!ohci->ed_bulktail && !ohci->ed_rm_list) { + wmb(); + ohci->hc_control |= OHCI_CTRL_BLE; + ohci_writel (ohci, 0, &ohci->regs->ed_bulkcurrent); + ohci_writel (ohci, ohci->hc_control, + &ohci->regs->control); + } + ohci->ed_bulktail = ed; + break; + + // case PIPE_INTERRUPT: + // case PIPE_ISOCHRONOUS: + default: + branch = balance (ohci, ed->interval, ed->load); + if (branch < 0) { + ohci_dbg (ohci, + "ERR %d, interval %d msecs, load %d\n", + branch, ed->interval, ed->load); + // FIXME if there are TDs queued, fail them! + return branch; + } + ed->branch = branch; + periodic_link (ohci, ed); + } + + /* the HC may not see the schedule updates yet, but if it does + * then they'll be properly ordered. + */ + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* scan the periodic table to find and unlink this ED */ +static void periodic_unlink (struct ohci_hcd *ohci, struct ed *ed) +{ + int i; + + for (i = ed->branch; i < NUM_INTS; i += ed->interval) { + struct ed *temp; + struct ed **prev = &ohci->periodic [i]; + __hc32 *prev_p = &ohci->hcca->int_table [i]; + + while (*prev && (temp = *prev) != ed) { + prev_p = &temp->hwNextED; + prev = &temp->ed_next; + } + if (*prev) { + *prev_p = ed->hwNextED; + *prev = ed->ed_next; + } + ohci->load [i] -= ed->load; + } + ohci_to_hcd(ohci)->self.bandwidth_allocated -= ed->load / ed->interval; + + ohci_vdbg (ohci, "unlink %sed %p branch %d [%dus.], interval %d\n", + (ed->hwINFO & cpu_to_hc32 (ohci, ED_ISO)) ? "iso " : "", + ed, ed->branch, ed->load, ed->interval); +} + +/* unlink an ed from one of the HC chains. + * just the link to the ed is unlinked. + * the link from the ed still points to another operational ed or 0 + * so the HC can eventually finish the processing of the unlinked ed + * (assuming it already started that, which needn't be true). + * + * ED_UNLINK is a transient state: the HC may still see this ED, but soon + * it won't. ED_SKIP means the HC will finish its current transaction, + * but won't start anything new. The TD queue may still grow; device + * drivers don't know about this HCD-internal state. + * + * When the HC can't see the ED, something changes ED_UNLINK to one of: + * + * - ED_OPER: when there's any request queued, the ED gets rescheduled + * immediately. HC should be working on them. + * + * - ED_IDLE: when there's no TD queue or the HC isn't running. + * + * When finish_unlinks() runs later, after SOF interrupt, it will often + * complete one or more URB unlinks before making that state change. + */ +static void ed_deschedule (struct ohci_hcd *ohci, struct ed *ed) +{ + ed->hwINFO |= cpu_to_hc32 (ohci, ED_SKIP); + wmb (); + ed->state = ED_UNLINK; + + /* To deschedule something from the control or bulk list, just + * clear CLE/BLE and wait. There's no safe way to scrub out list + * head/current registers until later, and "later" isn't very + * tightly specified. Figure 6-5 and Section 6.4.2.2 show how + * the HC is reading the ED queues (while we modify them). + * + * For now, ed_schedule() is "later". It might be good paranoia + * to scrub those registers in finish_unlinks(), in case of bugs + * that make the HC try to use them. + */ + switch (ed->type) { + case PIPE_CONTROL: + /* remove ED from the HC's list: */ + if (ed->ed_prev == NULL) { + if (!ed->hwNextED) { + ohci->hc_control &= ~OHCI_CTRL_CLE; + ohci_writel (ohci, ohci->hc_control, + &ohci->regs->control); + // a ohci_readl() later syncs CLE with the HC + } else + ohci_writel (ohci, + hc32_to_cpup (ohci, &ed->hwNextED), + &ohci->regs->ed_controlhead); + } else { + ed->ed_prev->ed_next = ed->ed_next; + ed->ed_prev->hwNextED = ed->hwNextED; + } + /* remove ED from the HCD's list: */ + if (ohci->ed_controltail == ed) { + ohci->ed_controltail = ed->ed_prev; + if (ohci->ed_controltail) + ohci->ed_controltail->ed_next = NULL; + } else if (ed->ed_next) { + ed->ed_next->ed_prev = ed->ed_prev; + } + break; + + case PIPE_BULK: + /* remove ED from the HC's list: */ + if (ed->ed_prev == NULL) { + if (!ed->hwNextED) { + ohci->hc_control &= ~OHCI_CTRL_BLE; + ohci_writel (ohci, ohci->hc_control, + &ohci->regs->control); + // a ohci_readl() later syncs BLE with the HC + } else + ohci_writel (ohci, + hc32_to_cpup (ohci, &ed->hwNextED), + &ohci->regs->ed_bulkhead); + } else { + ed->ed_prev->ed_next = ed->ed_next; + ed->ed_prev->hwNextED = ed->hwNextED; + } + /* remove ED from the HCD's list: */ + if (ohci->ed_bulktail == ed) { + ohci->ed_bulktail = ed->ed_prev; + if (ohci->ed_bulktail) + ohci->ed_bulktail->ed_next = NULL; + } else if (ed->ed_next) { + ed->ed_next->ed_prev = ed->ed_prev; + } + break; + + // case PIPE_INTERRUPT: + // case PIPE_ISOCHRONOUS: + default: + periodic_unlink (ohci, ed); + break; + } +} + + +/*-------------------------------------------------------------------------*/ + +/* get and maybe (re)init an endpoint. init _should_ be done only as part + * of enumeration, usb_set_configuration() or usb_set_interface(). + */ +static struct ed *ed_get ( + struct ohci_hcd *ohci, + struct usb_host_endpoint *ep, + struct usb_device *udev, + unsigned int pipe, + int interval +) { + struct ed *ed; + unsigned long flags; + + spin_lock_irqsave (&ohci->lock, flags); + + if (!(ed = ep->hcpriv)) { + struct td *td; + int is_out; + u32 info; + + ed = ed_alloc (ohci, GFP_ATOMIC); + if (!ed) { + /* out of memory */ + goto done; + } + + /* dummy td; end of td list for ed */ + td = td_alloc (ohci, GFP_ATOMIC); + if (!td) { + /* out of memory */ + ed_free (ohci, ed); + ed = NULL; + goto done; + } + ed->dummy = td; + ed->hwTailP = cpu_to_hc32 (ohci, td->td_dma); + ed->hwHeadP = ed->hwTailP; /* ED_C, ED_H zeroed */ + ed->state = ED_IDLE; + + is_out = !(ep->desc.bEndpointAddress & USB_DIR_IN); + + /* FIXME usbcore changes dev->devnum before SET_ADDRESS + * succeeds ... otherwise we wouldn't need "pipe". + */ + info = usb_pipedevice (pipe); + ed->type = usb_pipetype(pipe); + + info |= (ep->desc.bEndpointAddress & ~USB_DIR_IN) << 7; + info |= usb_endpoint_maxp(&ep->desc) << 16; + if (udev->speed == USB_SPEED_LOW) + info |= ED_LOWSPEED; + /* only control transfers store pids in tds */ + if (ed->type != PIPE_CONTROL) { + info |= is_out ? ED_OUT : ED_IN; + if (ed->type != PIPE_BULK) { + /* periodic transfers... */ + if (ed->type == PIPE_ISOCHRONOUS) + info |= ED_ISO; + else if (interval > 32) /* iso can be bigger */ + interval = 32; + ed->interval = interval; + ed->load = usb_calc_bus_time ( + udev->speed, !is_out, + ed->type == PIPE_ISOCHRONOUS, + usb_endpoint_maxp(&ep->desc)) + / 1000; + } + } + ed->hwINFO = cpu_to_hc32(ohci, info); + + ep->hcpriv = ed; + } + +done: + spin_unlock_irqrestore (&ohci->lock, flags); + return ed; +} + +/*-------------------------------------------------------------------------*/ + +/* request unlinking of an endpoint from an operational HC. + * put the ep on the rm_list + * real work is done at the next start frame (SF) hardware interrupt + * caller guarantees HCD is running, so hardware access is safe, + * and that ed->state is ED_OPER + */ +static void start_ed_unlink (struct ohci_hcd *ohci, struct ed *ed) +{ + ed->hwINFO |= cpu_to_hc32 (ohci, ED_DEQUEUE); + ed_deschedule (ohci, ed); + + /* rm_list is just singly linked, for simplicity */ + ed->ed_next = ohci->ed_rm_list; + ed->ed_prev = NULL; + ohci->ed_rm_list = ed; + + /* enable SOF interrupt */ + ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrstatus); + ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable); + // flush those writes, and get latest HCCA contents + (void) ohci_readl (ohci, &ohci->regs->control); + + /* SF interrupt might get delayed; record the frame counter value that + * indicates when the HC isn't looking at it, so concurrent unlinks + * behave. frame_no wraps every 2^16 msec, and changes right before + * SF is triggered. + */ + ed->tick = ohci_frame_no(ohci) + 1; + +} + +/*-------------------------------------------------------------------------* + * TD handling functions + *-------------------------------------------------------------------------*/ + +/* enqueue next TD for this URB (OHCI spec 5.2.8.2) */ + +static void +td_fill (struct ohci_hcd *ohci, u32 info, + dma_addr_t data, int len, + struct urb *urb, int index) +{ + struct td *td, *td_pt; + struct urb_priv *urb_priv = urb->hcpriv; + int is_iso = info & TD_ISO; + int hash; + + // ASSERT (index < urb_priv->length); + + /* aim for only one interrupt per urb. mostly applies to control + * and iso; other urbs rarely need more than one TD per urb. + * this way, only final tds (or ones with an error) cause IRQs. + * at least immediately; use DI=6 in case any control request is + * tempted to die part way through. (and to force the hc to flush + * its donelist soonish, even on unlink paths.) + * + * NOTE: could delay interrupts even for the last TD, and get fewer + * interrupts ... increasing per-urb latency by sharing interrupts. + * Drivers that queue bulk urbs may request that behavior. + */ + if (index != (urb_priv->length - 1) + || (urb->transfer_flags & URB_NO_INTERRUPT)) + info |= TD_DI_SET (6); + + /* use this td as the next dummy */ + td_pt = urb_priv->td [index]; + + /* fill the old dummy TD */ + td = urb_priv->td [index] = urb_priv->ed->dummy; + urb_priv->ed->dummy = td_pt; + + td->ed = urb_priv->ed; + td->next_dl_td = NULL; + td->index = index; + td->urb = urb; + td->data_dma = data; + if (!len) + data = 0; + + td->hwINFO = cpu_to_hc32 (ohci, info); + if (is_iso) { + td->hwCBP = cpu_to_hc32 (ohci, data & 0xFFFFF000); + *ohci_hwPSWp(ohci, td, 0) = cpu_to_hc16 (ohci, + (data & 0x0FFF) | 0xE000); + } else { + td->hwCBP = cpu_to_hc32 (ohci, data); + } + if (data) + td->hwBE = cpu_to_hc32 (ohci, data + len - 1); + else + td->hwBE = 0; + td->hwNextTD = cpu_to_hc32 (ohci, td_pt->td_dma); + + /* append to queue */ + list_add_tail (&td->td_list, &td->ed->td_list); + + /* hash it for later reverse mapping */ + hash = TD_HASH_FUNC (td->td_dma); + td->td_hash = ohci->td_hash [hash]; + ohci->td_hash [hash] = td; + + /* HC might read the TD (or cachelines) right away ... */ + wmb (); + td->ed->hwTailP = td->hwNextTD; +} + +/*-------------------------------------------------------------------------*/ + +/* Prepare all TDs of a transfer, and queue them onto the ED. + * Caller guarantees HC is active. + * Usually the ED is already on the schedule, so TDs might be + * processed as soon as they're queued. + */ +static void td_submit_urb ( + struct ohci_hcd *ohci, + struct urb *urb +) { + struct urb_priv *urb_priv = urb->hcpriv; + dma_addr_t data; + int data_len = urb->transfer_buffer_length; + int cnt = 0; + u32 info = 0; + int is_out = usb_pipeout (urb->pipe); + int periodic = 0; + + /* OHCI handles the bulk/interrupt data toggles itself. We just + * use the device toggle bits for resetting, and rely on the fact + * that resetting toggle is meaningless if the endpoint is active. + */ + if (!usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), is_out)) { + usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), + is_out, 1); + urb_priv->ed->hwHeadP &= ~cpu_to_hc32 (ohci, ED_C); + } + + list_add (&urb_priv->pending, &ohci->pending); + + if (data_len) + data = urb->transfer_dma; + else + data = 0; + + /* NOTE: TD_CC is set so we can tell which TDs the HC processed by + * using TD_CC_GET, as well as by seeing them on the done list. + * (CC = NotAccessed ... 0x0F, or 0x0E in PSWs for ISO.) + */ + switch (urb_priv->ed->type) { + + /* Bulk and interrupt are identical except for where in the schedule + * their EDs live. + */ + case PIPE_INTERRUPT: + /* ... and periodic urbs have extra accounting */ + periodic = ohci_to_hcd(ohci)->self.bandwidth_int_reqs++ == 0 + && ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0; + /* FALLTHROUGH */ + case PIPE_BULK: + info = is_out + ? TD_T_TOGGLE | TD_CC | TD_DP_OUT + : TD_T_TOGGLE | TD_CC | TD_DP_IN; + /* TDs _could_ transfer up to 8K each */ + while (data_len > 4096) { + td_fill (ohci, info, data, 4096, urb, cnt); + data += 4096; + data_len -= 4096; + cnt++; + } + /* maybe avoid ED halt on final TD short read */ + if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) + info |= TD_R; + td_fill (ohci, info, data, data_len, urb, cnt); + cnt++; + if ((urb->transfer_flags & URB_ZERO_PACKET) + && cnt < urb_priv->length) { + td_fill (ohci, info, 0, 0, urb, cnt); + cnt++; + } + /* maybe kickstart bulk list */ + if (urb_priv->ed->type == PIPE_BULK) { + wmb (); + ohci_writel (ohci, OHCI_BLF, &ohci->regs->cmdstatus); + } + break; + + /* control manages DATA0/DATA1 toggle per-request; SETUP resets it, + * any DATA phase works normally, and the STATUS ack is special. + */ + case PIPE_CONTROL: + info = TD_CC | TD_DP_SETUP | TD_T_DATA0; + td_fill (ohci, info, urb->setup_dma, 8, urb, cnt++); + if (data_len > 0) { + info = TD_CC | TD_R | TD_T_DATA1; + info |= is_out ? TD_DP_OUT : TD_DP_IN; + /* NOTE: mishandles transfers >8K, some >4K */ + td_fill (ohci, info, data, data_len, urb, cnt++); + } + info = (is_out || data_len == 0) + ? TD_CC | TD_DP_IN | TD_T_DATA1 + : TD_CC | TD_DP_OUT | TD_T_DATA1; + td_fill (ohci, info, data, 0, urb, cnt++); + /* maybe kickstart control list */ + wmb (); + ohci_writel (ohci, OHCI_CLF, &ohci->regs->cmdstatus); + break; + + /* ISO has no retransmit, so no toggle; and it uses special TDs. + * Each TD could handle multiple consecutive frames (interval 1); + * we could often reduce the number of TDs here. + */ + case PIPE_ISOCHRONOUS: + for (cnt = urb_priv->td_cnt; cnt < urb->number_of_packets; + cnt++) { + int frame = urb->start_frame; + + // FIXME scheduling should handle frame counter + // roll-around ... exotic case (and OHCI has + // a 2^16 iso range, vs other HCs max of 2^10) + frame += cnt * urb->interval; + frame &= 0xffff; + td_fill (ohci, TD_CC | TD_ISO | frame, + data + urb->iso_frame_desc [cnt].offset, + urb->iso_frame_desc [cnt].length, urb, cnt); + } + if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0) { + if (quirk_amdiso(ohci)) + usb_amd_quirk_pll_disable(); + if (quirk_amdprefetch(ohci)) + sb800_prefetch(ohci, 1); + } + periodic = ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs++ == 0 + && ohci_to_hcd(ohci)->self.bandwidth_int_reqs == 0; + break; + } + + /* start periodic dma if needed */ + if (periodic) { + wmb (); + ohci->hc_control |= OHCI_CTRL_PLE|OHCI_CTRL_IE; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + } + + // ASSERT (urb_priv->length == cnt); +} + +/*-------------------------------------------------------------------------* + * Done List handling functions + *-------------------------------------------------------------------------*/ + +/* calculate transfer length/status and update the urb */ +static int td_done(struct ohci_hcd *ohci, struct urb *urb, struct td *td) +{ + u32 tdINFO = hc32_to_cpup (ohci, &td->hwINFO); + int cc = 0; + int status = -EINPROGRESS; + + list_del (&td->td_list); + + /* ISO ... drivers see per-TD length/status */ + if (tdINFO & TD_ISO) { + u16 tdPSW = ohci_hwPSW(ohci, td, 0); + int dlen = 0; + + /* NOTE: assumes FC in tdINFO == 0, and that + * only the first of 0..MAXPSW psws is used. + */ + + cc = (tdPSW >> 12) & 0xF; + if (tdINFO & TD_CC) /* hc didn't touch? */ + return status; + + if (usb_pipeout (urb->pipe)) + dlen = urb->iso_frame_desc [td->index].length; + else { + /* short reads are always OK for ISO */ + if (cc == TD_DATAUNDERRUN) + cc = TD_CC_NOERROR; + dlen = tdPSW & 0x3ff; + } + urb->actual_length += dlen; + urb->iso_frame_desc [td->index].actual_length = dlen; + urb->iso_frame_desc [td->index].status = cc_to_error [cc]; + + if (cc != TD_CC_NOERROR) + ohci_vdbg (ohci, + "urb %p iso td %p (%d) len %d cc %d\n", + urb, td, 1 + td->index, dlen, cc); + + /* BULK, INT, CONTROL ... drivers see aggregate length/status, + * except that "setup" bytes aren't counted and "short" transfers + * might not be reported as errors. + */ + } else { + int type = usb_pipetype (urb->pipe); + u32 tdBE = hc32_to_cpup (ohci, &td->hwBE); + + cc = TD_CC_GET (tdINFO); + + /* update packet status if needed (short is normally ok) */ + if (cc == TD_DATAUNDERRUN + && !(urb->transfer_flags & URB_SHORT_NOT_OK)) + cc = TD_CC_NOERROR; + if (cc != TD_CC_NOERROR && cc < 0x0E) + status = cc_to_error[cc]; + + /* count all non-empty packets except control SETUP packet */ + if ((type != PIPE_CONTROL || td->index != 0) && tdBE != 0) { + if (td->hwCBP == 0) + urb->actual_length += tdBE - td->data_dma + 1; + else + urb->actual_length += + hc32_to_cpup (ohci, &td->hwCBP) + - td->data_dma; + } + + if (cc != TD_CC_NOERROR && cc < 0x0E) + ohci_vdbg (ohci, + "urb %p td %p (%d) cc %d, len=%d/%d\n", + urb, td, 1 + td->index, cc, + urb->actual_length, + urb->transfer_buffer_length); + } + return status; +} + +/*-------------------------------------------------------------------------*/ + +static void ed_halted(struct ohci_hcd *ohci, struct td *td, int cc) +{ + struct urb *urb = td->urb; + urb_priv_t *urb_priv = urb->hcpriv; + struct ed *ed = td->ed; + struct list_head *tmp = td->td_list.next; + __hc32 toggle = ed->hwHeadP & cpu_to_hc32 (ohci, ED_C); + + /* clear ed halt; this is the td that caused it, but keep it inactive + * until its urb->complete() has a chance to clean up. + */ + ed->hwINFO |= cpu_to_hc32 (ohci, ED_SKIP); + wmb (); + ed->hwHeadP &= ~cpu_to_hc32 (ohci, ED_H); + + /* Get rid of all later tds from this urb. We don't have + * to be careful: no errors and nothing was transferred. + * Also patch the ed so it looks as if those tds completed normally. + */ + while (tmp != &ed->td_list) { + struct td *next; + + next = list_entry (tmp, struct td, td_list); + tmp = next->td_list.next; + + if (next->urb != urb) + break; + + /* NOTE: if multi-td control DATA segments get supported, + * this urb had one of them, this td wasn't the last td + * in that segment (TD_R clear), this ed halted because + * of a short read, _and_ URB_SHORT_NOT_OK is clear ... + * then we need to leave the control STATUS packet queued + * and clear ED_SKIP. + */ + + list_del(&next->td_list); + urb_priv->td_cnt++; + ed->hwHeadP = next->hwNextTD | toggle; + } + + /* help for troubleshooting: report anything that + * looks odd ... that doesn't include protocol stalls + * (or maybe some other things) + */ + switch (cc) { + case TD_DATAUNDERRUN: + if ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0) + break; + /* fallthrough */ + case TD_CC_STALL: + if (usb_pipecontrol (urb->pipe)) + break; + /* fallthrough */ + default: + ohci_dbg (ohci, + "urb %p path %s ep%d%s %08x cc %d --> status %d\n", + urb, urb->dev->devpath, + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + hc32_to_cpu (ohci, td->hwINFO), + cc, cc_to_error [cc]); + } +} + +/* replies to the request have to be on a FIFO basis so + * we unreverse the hc-reversed done-list + */ +static struct td *dl_reverse_done_list (struct ohci_hcd *ohci) +{ + u32 td_dma; + struct td *td_rev = NULL; + struct td *td = NULL; + + td_dma = hc32_to_cpup (ohci, &ohci->hcca->done_head); + ohci->hcca->done_head = 0; + wmb(); + + /* get TD from hc's singly linked list, and + * prepend to ours. ed->td_list changes later. + */ + while (td_dma) { + int cc; + + td = dma_to_td (ohci, td_dma); + if (!td) { + ohci_err (ohci, "bad entry %8x\n", td_dma); + break; + } + + td->hwINFO |= cpu_to_hc32 (ohci, TD_DONE); + cc = TD_CC_GET (hc32_to_cpup (ohci, &td->hwINFO)); + + /* Non-iso endpoints can halt on error; un-halt, + * and dequeue any other TDs from this urb. + * No other TD could have caused the halt. + */ + if (cc != TD_CC_NOERROR + && (td->ed->hwHeadP & cpu_to_hc32 (ohci, ED_H))) + ed_halted(ohci, td, cc); + + td->next_dl_td = td_rev; + td_rev = td; + td_dma = hc32_to_cpup (ohci, &td->hwNextTD); + } + return td_rev; +} + +/*-------------------------------------------------------------------------*/ + +/* there are some urbs/eds to unlink; called in_irq(), with HCD locked */ +static void +finish_unlinks (struct ohci_hcd *ohci, u16 tick) +{ + struct ed *ed, **last; + +rescan_all: + for (last = &ohci->ed_rm_list, ed = *last; ed != NULL; ed = *last) { + struct list_head *entry, *tmp; + int completed, modified; + __hc32 *prev; + + /* Is this ED already invisible to the hardware? */ + if (ed->state == ED_IDLE) + goto ed_idle; + + /* only take off EDs that the HC isn't using, accounting for + * frame counter wraps and EDs with partially retired TDs + */ + if (likely(ohci->rh_state == OHCI_RH_RUNNING)) { + if (tick_before (tick, ed->tick)) { +skip_ed: + last = &ed->ed_next; + continue; + } + + if (!list_empty (&ed->td_list)) { + struct td *td; + u32 head; + + td = list_entry (ed->td_list.next, struct td, + td_list); + head = hc32_to_cpu (ohci, ed->hwHeadP) & + TD_MASK; + + /* INTR_WDH may need to clean up first */ + if (td->td_dma != head) { + if (ed == ohci->ed_to_check) + ohci->ed_to_check = NULL; + else + goto skip_ed; + } + } + } + + /* ED's now officially unlinked, hc doesn't see */ + ed->state = ED_IDLE; + if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT) + ohci->eds_scheduled--; + ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H); + ed->hwNextED = 0; + wmb(); + ed->hwINFO &= ~cpu_to_hc32(ohci, ED_SKIP | ED_DEQUEUE); +ed_idle: + + /* reentrancy: if we drop the schedule lock, someone might + * have modified this list. normally it's just prepending + * entries (which we'd ignore), but paranoia won't hurt. + */ + modified = 0; + + /* unlink urbs as requested, but rescan the list after + * we call a completion since it might have unlinked + * another (earlier) urb + * + * When we get here, the HC doesn't see this ed. But it + * must not be rescheduled until all completed URBs have + * been given back to the driver. + */ +rescan_this: + completed = 0; + prev = &ed->hwHeadP; + list_for_each_safe (entry, tmp, &ed->td_list) { + struct td *td; + struct urb *urb; + urb_priv_t *urb_priv; + __hc32 savebits; + u32 tdINFO; + + td = list_entry (entry, struct td, td_list); + urb = td->urb; + urb_priv = td->urb->hcpriv; + + if (!urb->unlinked) { + prev = &td->hwNextTD; + continue; + } + + /* patch pointer hc uses */ + savebits = *prev & ~cpu_to_hc32 (ohci, TD_MASK); + *prev = td->hwNextTD | savebits; + + /* If this was unlinked, the TD may not have been + * retired ... so manually save the data toggle. + * The controller ignores the value we save for + * control and ISO endpoints. + */ + tdINFO = hc32_to_cpup(ohci, &td->hwINFO); + if ((tdINFO & TD_T) == TD_T_DATA0) + ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_C); + else if ((tdINFO & TD_T) == TD_T_DATA1) + ed->hwHeadP |= cpu_to_hc32(ohci, ED_C); + + /* HC may have partly processed this TD */ + td_done (ohci, urb, td); + urb_priv->td_cnt++; + + /* if URB is done, clean up */ + if (urb_priv->td_cnt >= urb_priv->length) { + modified = completed = 1; + finish_urb(ohci, urb, 0); + } + } + if (completed && !list_empty (&ed->td_list)) + goto rescan_this; + + /* + * If no TDs are queued, take ED off the ed_rm_list. + * Otherwise, if the HC is running, reschedule. + * If not, leave it on the list for further dequeues. + */ + if (list_empty(&ed->td_list)) { + *last = ed->ed_next; + ed->ed_next = NULL; + } else if (ohci->rh_state == OHCI_RH_RUNNING) { + *last = ed->ed_next; + ed->ed_next = NULL; + ed_schedule(ohci, ed); + } else { + last = &ed->ed_next; + } + + if (modified) + goto rescan_all; + } + + /* maybe reenable control and bulk lists */ + if (ohci->rh_state == OHCI_RH_RUNNING && !ohci->ed_rm_list) { + u32 command = 0, control = 0; + + if (ohci->ed_controltail) { + command |= OHCI_CLF; + if (quirk_zfmicro(ohci)) + mdelay(1); + if (!(ohci->hc_control & OHCI_CTRL_CLE)) { + control |= OHCI_CTRL_CLE; + ohci_writel (ohci, 0, + &ohci->regs->ed_controlcurrent); + } + } + if (ohci->ed_bulktail) { + command |= OHCI_BLF; + if (quirk_zfmicro(ohci)) + mdelay(1); + if (!(ohci->hc_control & OHCI_CTRL_BLE)) { + control |= OHCI_CTRL_BLE; + ohci_writel (ohci, 0, + &ohci->regs->ed_bulkcurrent); + } + } + + /* CLE/BLE to enable, CLF/BLF to (maybe) kickstart */ + if (control) { + ohci->hc_control |= control; + if (quirk_zfmicro(ohci)) + mdelay(1); + ohci_writel (ohci, ohci->hc_control, + &ohci->regs->control); + } + if (command) { + if (quirk_zfmicro(ohci)) + mdelay(1); + ohci_writel (ohci, command, &ohci->regs->cmdstatus); + } + } +} + + + +/*-------------------------------------------------------------------------*/ + +/* + * Used to take back a TD from the host controller. This would normally be + * called from within dl_done_list, however it may be called directly if the + * HC no longer sees the TD and it has not appeared on the donelist (after + * two frames). This bug has been observed on ZF Micro systems. + */ +static void takeback_td(struct ohci_hcd *ohci, struct td *td) +{ + struct urb *urb = td->urb; + urb_priv_t *urb_priv = urb->hcpriv; + struct ed *ed = td->ed; + int status; + + /* update URB's length and status from TD */ + status = td_done(ohci, urb, td); + urb_priv->td_cnt++; + + /* If all this urb's TDs are done, call complete() */ + if (urb_priv->td_cnt >= urb_priv->length) + finish_urb(ohci, urb, status); + + /* clean schedule: unlink EDs that are no longer busy */ + if (list_empty(&ed->td_list)) { + if (ed->state == ED_OPER) + start_ed_unlink(ohci, ed); + + /* ... reenabling halted EDs only after fault cleanup */ + } else if ((ed->hwINFO & cpu_to_hc32(ohci, ED_SKIP | ED_DEQUEUE)) + == cpu_to_hc32(ohci, ED_SKIP)) { + td = list_entry(ed->td_list.next, struct td, td_list); + if (!(td->hwINFO & cpu_to_hc32(ohci, TD_DONE))) { + ed->hwINFO &= ~cpu_to_hc32(ohci, ED_SKIP); + /* ... hc may need waking-up */ + switch (ed->type) { + case PIPE_CONTROL: + ohci_writel(ohci, OHCI_CLF, + &ohci->regs->cmdstatus); + break; + case PIPE_BULK: + ohci_writel(ohci, OHCI_BLF, + &ohci->regs->cmdstatus); + break; + } + } + } +} + +/* + * Process normal completions (error or success) and clean the schedules. + * + * This is the main path for handing urbs back to drivers. The only other + * normal path is finish_unlinks(), which unlinks URBs using ed_rm_list, + * instead of scanning the (re-reversed) donelist as this does. There's + * an abnormal path too, handling a quirk in some Compaq silicon: URBs + * with TDs that appear to be orphaned are directly reclaimed. + */ +static void +dl_done_list (struct ohci_hcd *ohci) +{ + struct td *td = dl_reverse_done_list (ohci); + + while (td) { + struct td *td_next = td->next_dl_td; + struct ed *ed = td->ed; + + /* + * Some OHCI controllers (NVIDIA for sure, maybe others) + * occasionally forget to add TDs to the done queue. Since + * TDs for a given endpoint are always processed in order, + * if we find a TD on the donelist then all of its + * predecessors must be finished as well. + */ + for (;;) { + struct td *td2; + + td2 = list_first_entry(&ed->td_list, struct td, + td_list); + if (td2 == td) + break; + takeback_td(ohci, td2); + } + + takeback_td(ohci, td); + td = td_next; + } +} diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c new file mode 100644 index 00000000..e125770b --- /dev/null +++ b/drivers/usb/host/ohci-s3c2410.c @@ -0,0 +1,535 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * + * USB Bus Glue for Samsung S3C2410 + * + * Written by Christopher Hoover + * Based on fragments of previous driver by Russell King et al. + * + * Modified for S3C2410 from ohci-sa1111.c, ohci-omap.c and ohci-lh7a40.c + * by Ben Dooks, + * Copyright (C) 2004 Simtec Electronics + * + * Thanks to basprog@mail.ru for updates to newer kernels + * + * This file is licenced under the GPL. +*/ + +#include +#include +#include + +#define valid_port(idx) ((idx) == 1 || (idx) == 2) + +/* clock device associated with the hcd */ + +static struct clk *clk; +static struct clk *usb_clk; + +/* forward definitions */ + +static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc); + +/* conversion functions */ + +static struct s3c2410_hcd_info *to_s3c2410_info(struct usb_hcd *hcd) +{ + return hcd->self.controller->platform_data; +} + +static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd) +{ + struct s3c2410_hcd_info *info = dev->dev.platform_data; + + dev_dbg(&dev->dev, "s3c2410_start_hc:\n"); + + clk_enable(usb_clk); + mdelay(2); /* let the bus clock stabilise */ + + clk_enable(clk); + + if (info != NULL) { + info->hcd = hcd; + info->report_oc = s3c2410_hcd_oc; + + if (info->enable_oc != NULL) + (info->enable_oc)(info, 1); + } +} + +static void s3c2410_stop_hc(struct platform_device *dev) +{ + struct s3c2410_hcd_info *info = dev->dev.platform_data; + + dev_dbg(&dev->dev, "s3c2410_stop_hc:\n"); + + if (info != NULL) { + info->report_oc = NULL; + info->hcd = NULL; + + if (info->enable_oc != NULL) + (info->enable_oc)(info, 0); + } + + clk_disable(clk); + clk_disable(usb_clk); +} + +/* ohci_s3c2410_hub_status_data + * + * update the status data from the hub with anything that + * has been detected by our system +*/ + +static int +ohci_s3c2410_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct s3c2410_hcd_info *info = to_s3c2410_info(hcd); + struct s3c2410_hcd_port *port; + int orig; + int portno; + + orig = ohci_hub_status_data(hcd, buf); + + if (info == NULL) + return orig; + + port = &info->port[0]; + + /* mark any changed port as changed */ + + for (portno = 0; portno < 2; port++, portno++) { + if (port->oc_changed == 1 && + port->flags & S3C_HCDFLG_USED) { + dev_dbg(hcd->self.controller, + "oc change on port %d\n", portno); + + if (orig < 1) + orig = 1; + + buf[0] |= 1<<(portno+1); + } + } + + return orig; +} + +/* s3c2410_usb_set_power + * + * configure the power on a port, by calling the platform device + * routine registered with the platform device +*/ + +static void s3c2410_usb_set_power(struct s3c2410_hcd_info *info, + int port, int to) +{ + if (info == NULL) + return; + + if (info->power_control != NULL) { + info->port[port-1].power = to; + (info->power_control)(port-1, to); + } +} + +/* ohci_s3c2410_hub_control + * + * look at control requests to the hub, and see if we need + * to take any action or over-ride the results from the + * request. +*/ + +static int ohci_s3c2410_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength) +{ + struct s3c2410_hcd_info *info = to_s3c2410_info(hcd); + struct usb_hub_descriptor *desc; + int ret = -EINVAL; + u32 *data = (u32 *)buf; + + dev_dbg(hcd->self.controller, + "s3c2410_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n", + hcd, typeReq, wValue, wIndex, buf, wLength); + + /* if we are only an humble host without any special capabilities + * process the request straight away and exit */ + + if (info == NULL) { + ret = ohci_hub_control(hcd, typeReq, wValue, + wIndex, buf, wLength); + goto out; + } + + /* check the request to see if it needs handling */ + + switch (typeReq) { + case SetPortFeature: + if (wValue == USB_PORT_FEAT_POWER) { + dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n"); + s3c2410_usb_set_power(info, wIndex, 1); + goto out; + } + break; + + case ClearPortFeature: + switch (wValue) { + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(hcd->self.controller, + "ClearPortFeature: C_OVER_CURRENT\n"); + + if (valid_port(wIndex)) { + info->port[wIndex-1].oc_changed = 0; + info->port[wIndex-1].oc_status = 0; + } + + goto out; + + case USB_PORT_FEAT_OVER_CURRENT: + dev_dbg(hcd->self.controller, + "ClearPortFeature: OVER_CURRENT\n"); + + if (valid_port(wIndex)) + info->port[wIndex-1].oc_status = 0; + + goto out; + + case USB_PORT_FEAT_POWER: + dev_dbg(hcd->self.controller, + "ClearPortFeature: POWER\n"); + + if (valid_port(wIndex)) { + s3c2410_usb_set_power(info, wIndex, 0); + return 0; + } + } + break; + } + + ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); + if (ret) + goto out; + + switch (typeReq) { + case GetHubDescriptor: + + /* update the hub's descriptor */ + + desc = (struct usb_hub_descriptor *)buf; + + if (info->power_control == NULL) + return ret; + + dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n", + desc->wHubCharacteristics); + + /* remove the old configurations for power-switching, and + * over-current protection, and insert our new configuration + */ + + desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM); + desc->wHubCharacteristics |= cpu_to_le16(0x0001); + + if (info->enable_oc) { + desc->wHubCharacteristics &= ~cpu_to_le16( + HUB_CHAR_OCPM); + desc->wHubCharacteristics |= cpu_to_le16( + 0x0008 | + 0x0001); + } + + dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n", + desc->wHubCharacteristics); + + return ret; + + case GetPortStatus: + /* check port status */ + + dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex); + + if (valid_port(wIndex)) { + if (info->port[wIndex-1].oc_changed) + *data |= cpu_to_le32(RH_PS_OCIC); + + if (info->port[wIndex-1].oc_status) + *data |= cpu_to_le32(RH_PS_POCI); + } + } + + out: + return ret; +} + +/* s3c2410_hcd_oc + * + * handle an over-current report +*/ + +static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc) +{ + struct s3c2410_hcd_port *port; + struct usb_hcd *hcd; + unsigned long flags; + int portno; + + if (info == NULL) + return; + + port = &info->port[0]; + hcd = info->hcd; + + local_irq_save(flags); + + for (portno = 0; portno < 2; port++, portno++) { + if (port_oc & (1<flags & S3C_HCDFLG_USED) { + port->oc_status = 1; + port->oc_changed = 1; + + /* ok, once over-current is detected, + the port needs to be powered down */ + s3c2410_usb_set_power(info, portno+1, 0); + } + } + + local_irq_restore(flags); +} + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/* + * usb_hcd_s3c2410_remove - shutdown processing for HCD + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_3c2410_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * +*/ + +static void +usb_hcd_s3c2410_remove(struct usb_hcd *hcd, struct platform_device *dev) +{ + usb_remove_hcd(hcd); + s3c2410_stop_hc(dev); + usb_put_hcd(hcd); +} + +/** + * usb_hcd_s3c2410_probe - initialize S3C2410-based HCDs + * Context: !in_interrupt() + * + * 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 usb_hcd_s3c2410_probe(const struct hc_driver *driver, + struct platform_device *dev) +{ + struct usb_hcd *hcd = NULL; + int retval; + + s3c2410_usb_set_power(dev->dev.platform_data, 1, 1); + s3c2410_usb_set_power(dev->dev.platform_data, 2, 1); + + hcd = usb_create_hcd(driver, &dev->dev, "s3c24xx"); + if (hcd == NULL) + return -ENOMEM; + + hcd->rsrc_start = dev->resource[0].start; + hcd->rsrc_len = resource_size(&dev->resource[0]); + + hcd->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto err_put; + } + + clk = devm_clk_get(&dev->dev, "usb-host"); + if (IS_ERR(clk)) { + dev_err(&dev->dev, "cannot get usb-host clock\n"); + retval = PTR_ERR(clk); + goto err_put; + } + + usb_clk = devm_clk_get(&dev->dev, "usb-bus-host"); + if (IS_ERR(usb_clk)) { + dev_err(&dev->dev, "cannot get usb-bus-host clock\n"); + retval = PTR_ERR(usb_clk); + goto err_put; + } + + s3c2410_start_hc(dev, hcd); + + ohci_hcd_init(hcd_to_ohci(hcd)); + + retval = usb_add_hcd(hcd, dev->resource[1].start, 0); + if (retval != 0) + goto err_ioremap; + + return 0; + + err_ioremap: + s3c2410_stop_hc(dev); + + err_put: + usb_put_hcd(hcd); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static int +ohci_s3c2410_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_init(ohci); + if (ret < 0) + return ret; + + ret = ohci_run(ohci); + if (ret < 0) { + dev_err(hcd->self.controller, "can't start %s\n", + hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + + +static const struct hc_driver ohci_s3c2410_hc_driver = { + .description = hcd_name, + .product_desc = "S3C24XX OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .start = ohci_s3c2410_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_s3c2410_hub_status_data, + .hub_control = ohci_s3c2410_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/* device driver */ + +static int ohci_hcd_s3c2410_drv_probe(struct platform_device *pdev) +{ + return usb_hcd_s3c2410_probe(&ohci_s3c2410_hc_driver, pdev); +} + +static int ohci_hcd_s3c2410_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_hcd_s3c2410_remove(hcd, pdev); + return 0; +} + +#ifdef CONFIG_PM +static int ohci_hcd_s3c2410_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct platform_device *pdev = to_platform_device(dev); + unsigned long flags; + int rc = 0; + + /* + * Root hub was already suspended. Disable irq emission and + * mark HW unaccessible, bail out if RH has been resumed. Use + * the spinlock to properly synchronize with possible pending + * RH suspend or resume activity. + */ + spin_lock_irqsave(&ohci->lock, flags); + if (ohci->rh_state != OHCI_RH_SUSPENDED) { + rc = -EINVAL; + goto bail; + } + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + s3c2410_stop_hc(pdev); +bail: + spin_unlock_irqrestore(&ohci->lock, flags); + + return rc; +} + +static int ohci_hcd_s3c2410_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct platform_device *pdev = to_platform_device(dev); + + s3c2410_start_hc(pdev, hcd); + + ohci_resume(hcd, false); + + return 0; +} +#else +#define ohci_hcd_s3c2410_drv_suspend NULL +#define ohci_hcd_s3c2410_drv_resume NULL +#endif + +static const struct dev_pm_ops ohci_hcd_s3c2410_pm_ops = { + .suspend = ohci_hcd_s3c2410_drv_suspend, + .resume = ohci_hcd_s3c2410_drv_resume, +}; + +static struct platform_driver ohci_hcd_s3c2410_driver = { + .probe = ohci_hcd_s3c2410_drv_probe, + .remove = ohci_hcd_s3c2410_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "s3c2410-ohci", + .pm = &ohci_hcd_s3c2410_pm_ops, + }, +}; + +MODULE_ALIAS("platform:s3c2410-ohci"); diff --git a/drivers/usb/host/ohci-sa1111.c b/drivers/usb/host/ohci-sa1111.c new file mode 100644 index 00000000..17b2a7da --- /dev/null +++ b/drivers/usb/host/ohci-sa1111.c @@ -0,0 +1,257 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * + * SA1111 Bus Glue + * + * Written by Christopher Hoover + * Based on fragments of previous driver by Russell King et al. + * + * This file is licenced under the GPL. + */ + +#include +#include +#include +#include + +#ifndef CONFIG_SA1111 +#error "This file is SA-1111 bus glue. CONFIG_SA1111 must be defined." +#endif + +#define USB_STATUS 0x0118 +#define USB_RESET 0x011c +#define USB_IRQTEST 0x0120 + +#define USB_RESET_FORCEIFRESET (1 << 0) +#define USB_RESET_FORCEHCRESET (1 << 1) +#define USB_RESET_CLKGENRESET (1 << 2) +#define USB_RESET_SIMSCALEDOWN (1 << 3) +#define USB_RESET_USBINTTEST (1 << 4) +#define USB_RESET_SLEEPSTBYEN (1 << 5) +#define USB_RESET_PWRSENSELOW (1 << 6) +#define USB_RESET_PWRCTRLLOW (1 << 7) + +#define USB_STATUS_IRQHCIRMTWKUP (1 << 7) +#define USB_STATUS_IRQHCIBUFFACC (1 << 8) +#define USB_STATUS_NIRQHCIM (1 << 9) +#define USB_STATUS_NHCIMFCLR (1 << 10) +#define USB_STATUS_USBPWRSENSE (1 << 11) + +#if 0 +static void dump_hci_status(struct usb_hcd *hcd, const char *label) +{ + unsigned long status = sa1111_readl(hcd->regs + USB_STATUS); + + printk(KERN_DEBUG "%s USB_STATUS = { %s%s%s%s%s}\n", label, + ((status & USB_STATUS_IRQHCIRMTWKUP) ? "IRQHCIRMTWKUP " : ""), + ((status & USB_STATUS_IRQHCIBUFFACC) ? "IRQHCIBUFFACC " : ""), + ((status & USB_STATUS_NIRQHCIM) ? "" : "IRQHCIM "), + ((status & USB_STATUS_NHCIMFCLR) ? "" : "HCIMFCLR "), + ((status & USB_STATUS_USBPWRSENSE) ? "USBPWRSENSE " : "")); +} +#endif + +static int ohci_sa1111_reset(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci_hcd_init(ohci); + return ohci_init(ohci); +} + +static int ohci_sa1111_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_run(ohci); + if (ret < 0) { + ohci_err(ohci, "can't start\n"); + ohci_stop(hcd); + } + return ret; +} + +static const struct hc_driver ohci_sa1111_hc_driver = { + .description = hcd_name, + .product_desc = "SA-1111 OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ohci_sa1111_reset, + .start = ohci_sa1111_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +static int sa1111_start_hc(struct sa1111_dev *dev) +{ + unsigned int usb_rst = 0; + int ret; + + dev_dbg(&dev->dev, "starting SA-1111 OHCI USB Controller\n"); + + if (machine_is_xp860() || + machine_has_neponset() || + machine_is_pfs168() || + machine_is_badge4()) + usb_rst = USB_RESET_PWRSENSELOW | USB_RESET_PWRCTRLLOW; + + /* + * Configure the power sense and control lines. Place the USB + * host controller in reset. + */ + sa1111_writel(usb_rst | USB_RESET_FORCEIFRESET | USB_RESET_FORCEHCRESET, + dev->mapbase + USB_RESET); + + /* + * Now, carefully enable the USB clock, and take + * the USB host controller out of reset. + */ + ret = sa1111_enable_device(dev); + if (ret == 0) { + udelay(11); + sa1111_writel(usb_rst, dev->mapbase + USB_RESET); + } + + return ret; +} + +static void sa1111_stop_hc(struct sa1111_dev *dev) +{ + unsigned int usb_rst; + + dev_dbg(&dev->dev, "stopping SA-1111 OHCI USB Controller\n"); + + /* + * Put the USB host controller into reset. + */ + usb_rst = sa1111_readl(dev->mapbase + USB_RESET); + sa1111_writel(usb_rst | USB_RESET_FORCEIFRESET | USB_RESET_FORCEHCRESET, + dev->mapbase + USB_RESET); + + /* + * Stop the USB clock. + */ + sa1111_disable_device(dev); +} + +/** + * ohci_hcd_sa1111_probe - initialize SA-1111-based HCDs + * + * Allocates basic resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it. + */ +static int ohci_hcd_sa1111_probe(struct sa1111_dev *dev) +{ + struct usb_hcd *hcd; + int ret; + + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&ohci_sa1111_hc_driver, &dev->dev, "sa1111"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = dev->res.start; + hcd->rsrc_len = resource_size(&dev->res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + dev_dbg(&dev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto err1; + } + + hcd->regs = dev->mapbase; + + ret = sa1111_start_hc(dev); + if (ret) + goto err2; + + ret = usb_add_hcd(hcd, dev->irq[1], 0); + if (ret == 0) + return ret; + + sa1111_stop_hc(dev); + err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + err1: + usb_put_hcd(hcd); + return ret; +} + +/** + * ohci_hcd_sa1111_remove - shutdown processing for SA-1111-based HCDs + * @dev: USB Host Controller being removed + * + * Reverses the effect of ohci_hcd_sa1111_probe(), first invoking + * the HCD's stop() method. + */ +static int ohci_hcd_sa1111_remove(struct sa1111_dev *dev) +{ + struct usb_hcd *hcd = sa1111_get_drvdata(dev); + + usb_remove_hcd(hcd); + sa1111_stop_hc(dev); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + return 0; +} + +static void ohci_hcd_sa1111_shutdown(struct sa1111_dev *dev) +{ + struct usb_hcd *hcd = sa1111_get_drvdata(dev); + + if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + hcd->driver->shutdown(hcd); + sa1111_stop_hc(dev); + } +} + +static struct sa1111_driver ohci_hcd_sa1111_driver = { + .drv = { + .name = "sa1111-ohci", + .owner = THIS_MODULE, + }, + .devid = SA1111_DEVID_USB, + .probe = ohci_hcd_sa1111_probe, + .remove = ohci_hcd_sa1111_remove, + .shutdown = ohci_hcd_sa1111_shutdown, +}; diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c new file mode 100644 index 00000000..3b5b908f --- /dev/null +++ b/drivers/usb/host/ohci-sm501.c @@ -0,0 +1,265 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2005 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * (C) Copyright 2008 Magnus Damm + * + * SM501 Bus Glue - based on ohci-omap.c + * + * This file is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include + +static int ohci_sm501_init(struct usb_hcd *hcd) +{ + return ohci_init(hcd_to_ohci(hcd)); +} + +static int ohci_sm501_start(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + int ret; + + ret = ohci_run(hcd_to_ohci(hcd)); + if (ret < 0) { + dev_err(dev, "can't start %s", hcd->self.bus_name); + ohci_stop(hcd); + } + + return ret; +} + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ohci_sm501_hc_driver = { + .description = hcd_name, + .product_desc = "SM501 OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY | HCD_LOCAL_MEM, + + /* + * basic lifecycle operations + */ + .reset = ohci_sm501_init, + .start = ohci_sm501_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + +static int ohci_hcd_sm501_drv_probe(struct platform_device *pdev) +{ + const struct hc_driver *driver = &ohci_sm501_hc_driver; + struct device *dev = &pdev->dev; + struct resource *res, *mem; + int retval, irq; + struct usb_hcd *hcd = NULL; + + irq = retval = platform_get_irq(pdev, 0); + if (retval < 0) + goto err0; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (mem == NULL) { + dev_err(dev, "no resource definition for memory\n"); + retval = -ENOENT; + goto err0; + } + + if (!request_mem_region(mem->start, resource_size(mem), pdev->name)) { + dev_err(dev, "request_mem_region failed\n"); + retval = -EBUSY; + goto err0; + } + + /* The sm501 chip is equipped with local memory that may be used + * by on-chip devices such as the video controller and the usb host. + * This driver uses dma_declare_coherent_memory() to make sure + * usb allocations with dma_alloc_coherent() allocate from + * this local memory. The dma_handle returned by dma_alloc_coherent() + * will be an offset starting from 0 for the first local memory byte. + * + * So as long as data is allocated using dma_alloc_coherent() all is + * fine. This is however not always the case - buffers may be allocated + * using kmalloc() - so the usb core needs to be told that it must copy + * data into our local memory if the buffers happen to be placed in + * regular memory. The HCD_LOCAL_MEM flag does just that. + */ + + if (!dma_declare_coherent_memory(dev, mem->start, + mem->start - mem->parent->start, + resource_size(mem), + DMA_MEMORY_MAP | + DMA_MEMORY_EXCLUSIVE)) { + dev_err(dev, "cannot declare coherent memory\n"); + retval = -ENXIO; + goto err1; + } + + /* allocate, reserve and remap resources for registers */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(dev, "no resource definition for registers\n"); + retval = -ENOENT; + goto err2; + } + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto err2; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, pdev->name)) { + dev_err(dev, "request_mem_region failed\n"); + retval = -EBUSY; + goto err3; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (hcd->regs == NULL) { + dev_err(dev, "cannot remap registers\n"); + retval = -ENXIO; + goto err4; + } + + ohci_hcd_init(hcd_to_ohci(hcd)); + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) + goto err5; + + /* enable power and unmask interrupts */ + + sm501_unit_power(dev->parent, SM501_GATE_USB_HOST, 1); + sm501_modify_reg(dev->parent, SM501_IRQ_MASK, 1 << 6, 0); + + return 0; +err5: + iounmap(hcd->regs); +err4: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err3: + usb_put_hcd(hcd); +err2: + dma_release_declared_memory(dev); +err1: + release_mem_region(mem->start, resource_size(mem)); +err0: + return retval; +} + +static int ohci_hcd_sm501_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct resource *mem; + + usb_remove_hcd(hcd); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + dma_release_declared_memory(&pdev->dev); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (mem) + release_mem_region(mem->start, resource_size(mem)); + + /* mask interrupts and disable power */ + + sm501_modify_reg(pdev->dev.parent, SM501_IRQ_MASK, 0, 1 << 6); + sm501_unit_power(pdev->dev.parent, SM501_GATE_USB_HOST, 0); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int ohci_sm501_suspend(struct platform_device *pdev, pm_message_t msg) +{ + struct device *dev = &pdev->dev; + struct ohci_hcd *ohci = hcd_to_ohci(platform_get_drvdata(pdev)); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + sm501_unit_power(dev->parent, SM501_GATE_USB_HOST, 0); + return 0; +} + +static int ohci_sm501_resume(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + sm501_unit_power(dev->parent, SM501_GATE_USB_HOST, 1); + ohci_resume(hcd, false); + return 0; +} +#else +#define ohci_sm501_suspend NULL +#define ohci_sm501_resume NULL +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * Driver definition to register with the SM501 bus + */ +static struct platform_driver ohci_hcd_sm501_driver = { + .probe = ohci_hcd_sm501_drv_probe, + .remove = ohci_hcd_sm501_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .suspend = ohci_sm501_suspend, + .resume = ohci_sm501_resume, + .driver = { + .owner = THIS_MODULE, + .name = "sm501-usb", + }, +}; +MODULE_ALIAS("platform:sm501-usb"); diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c new file mode 100644 index 00000000..3e19e017 --- /dev/null +++ b/drivers/usb/host/ohci-spear.c @@ -0,0 +1,239 @@ +/* +* OHCI HCD (Host Controller Driver) for USB. +* +* Copyright (C) 2010 ST Microelectronics. +* Deepak Sikri +* +* Based on various ohci-*.c drivers +* +* This file is licensed under the terms of the GNU General Public +* License version 2. This program is licensed "as is" without any +* warranty of any kind, whether express or implied. +*/ + +#include +#include +#include +#include + +struct spear_ohci { + struct ohci_hcd ohci; + struct clk *clk; +}; + +#define to_spear_ohci(hcd) (struct spear_ohci *)hcd_to_ohci(hcd) + +static void spear_start_ohci(struct spear_ohci *ohci) +{ + clk_prepare_enable(ohci->clk); +} + +static void spear_stop_ohci(struct spear_ohci *ohci) +{ + clk_disable_unprepare(ohci->clk); +} + +static int ohci_spear_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_init(ohci); + if (ret < 0) + return ret; + ohci->regs = hcd->regs; + + ret = ohci_run(ohci); + if (ret < 0) { + dev_err(hcd->self.controller, "can't start\n"); + ohci_stop(hcd); + return ret; + } + + create_debug_files(ohci); + +#ifdef DEBUG + ohci_dump(ohci, 1); +#endif + return 0; +} + +static const struct hc_driver ohci_spear_hc_driver = { + .description = hcd_name, + .product_desc = "SPEAr OHCI", + .hcd_priv_size = sizeof(struct spear_ohci), + + /* generic hardware linkage */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* basic lifecycle operations */ + .start = ohci_spear_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + + /* managing i/o requests and associated device resources */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* scheduling support */ + .get_frame_number = ohci_get_frame, + + /* root hub support */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, + + .start_port_reset = ohci_start_port_reset, +}; + +static int spear_ohci_hcd_drv_probe(struct platform_device *pdev) +{ + const struct hc_driver *driver = &ohci_spear_hc_driver; + struct usb_hcd *hcd = NULL; + struct clk *usbh_clk; + struct spear_ohci *ohci_p; + struct resource *res; + int retval, irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + retval = irq; + goto fail; + } + + /* + * 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. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + usbh_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(usbh_clk)) { + dev_err(&pdev->dev, "Error getting interface clock\n"); + retval = PTR_ERR(usbh_clk); + goto fail; + } + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto fail; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + retval = -ENODEV; + goto err_put_hcd; + } + + hcd->rsrc_start = pdev->resource[0].start; + hcd->rsrc_len = resource_size(res); + if (!devm_request_mem_region(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len, + hcd_name)) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + retval = -EBUSY; + goto err_put_hcd; + } + + hcd->regs = devm_ioremap(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_dbg(&pdev->dev, "ioremap failed\n"); + retval = -ENOMEM; + goto err_put_hcd; + } + + ohci_p = (struct spear_ohci *)hcd_to_ohci(hcd); + ohci_p->clk = usbh_clk; + spear_start_ohci(ohci_p); + ohci_hcd_init(hcd_to_ohci(hcd)); + + retval = usb_add_hcd(hcd, platform_get_irq(pdev, 0), 0); + if (retval == 0) + return retval; + + spear_stop_ohci(ohci_p); +err_put_hcd: + usb_put_hcd(hcd); +fail: + dev_err(&pdev->dev, "init fail, %d\n", retval); + + return retval; +} + +static int spear_ohci_hcd_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct spear_ohci *ohci_p = to_spear_ohci(hcd); + + usb_remove_hcd(hcd); + if (ohci_p->clk) + spear_stop_ohci(ohci_p); + + usb_put_hcd(hcd); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +#if defined(CONFIG_PM) +static int spear_ohci_hcd_drv_suspend(struct platform_device *dev, + pm_message_t message) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct spear_ohci *ohci_p = to_spear_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + spear_stop_ohci(ohci_p); + return 0; +} + +static int spear_ohci_hcd_drv_resume(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct spear_ohci *ohci_p = to_spear_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + spear_start_ohci(ohci_p); + ohci_resume(hcd, false); + return 0; +} +#endif + +static struct of_device_id spear_ohci_id_table[] = { + { .compatible = "st,spear600-ohci", }, + { }, +}; + +/* Driver definition to register with the platform bus */ +static struct platform_driver spear_ohci_hcd_driver = { + .probe = spear_ohci_hcd_drv_probe, + .remove = spear_ohci_hcd_drv_remove, +#ifdef CONFIG_PM + .suspend = spear_ohci_hcd_drv_suspend, + .resume = spear_ohci_hcd_drv_resume, +#endif + .driver = { + .owner = THIS_MODULE, + .name = "spear-ohci", + .of_match_table = of_match_ptr(spear_ohci_id_table), + }, +}; + +MODULE_ALIAS("platform:spear-ohci"); diff --git a/drivers/usb/host/ohci-tilegx.c b/drivers/usb/host/ohci-tilegx.c new file mode 100644 index 00000000..ea73009d --- /dev/null +++ b/drivers/usb/host/ohci-tilegx.c @@ -0,0 +1,206 @@ +/* + * Copyright 2012 Tilera Corporation. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for + * more details. + */ + +/* + * Tilera TILE-Gx USB OHCI host controller driver. + */ + +#include +#include +#include +#include + +#include + +#include +#include + +static void tilegx_start_ohc(void) +{ +} + +static void tilegx_stop_ohc(void) +{ +} + +static int tilegx_ohci_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_init(ohci); + if (ret < 0) + return ret; + + ret = ohci_run(ohci); + if (ret < 0) { + dev_err(hcd->self.controller, "can't start %s\n", + hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + +static const struct hc_driver ohci_tilegx_hc_driver = { + .description = hcd_name, + .product_desc = "Tile-Gx OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * Generic hardware linkage. + */ + .irq = ohci_irq, + .flags = HCD_MEMORY | HCD_LOCAL_MEM | HCD_USB11, + + /* + * Basic lifecycle operations. + */ + .start = tilegx_ohci_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * Managing I/O requests and associated device resources. + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * Scheduling support. + */ + .get_frame_number = ohci_get_frame, + + /* + * Root hub support. + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, + .start_port_reset = ohci_start_port_reset, +}; + +static int ohci_hcd_tilegx_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct tilegx_usb_platform_data *pdata = pdev->dev.platform_data; + pte_t pte = { 0 }; + int my_cpu = smp_processor_id(); + int ret; + + if (usb_disabled()) + return -ENODEV; + + /* + * Try to initialize our GXIO context; if we can't, the device + * doesn't exist. + */ + if (gxio_usb_host_init(&pdata->usb_ctx, pdata->dev_index, 0) != 0) + return -ENXIO; + + hcd = usb_create_hcd(&ohci_tilegx_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + ret = -ENOMEM; + goto err_hcd; + } + + /* + * We don't use rsrc_start to map in our registers, but seems like + * we ought to set it to something, so we use the register VA. + */ + hcd->rsrc_start = + (ulong) gxio_usb_host_get_reg_start(&pdata->usb_ctx); + hcd->rsrc_len = gxio_usb_host_get_reg_len(&pdata->usb_ctx); + hcd->regs = gxio_usb_host_get_reg_start(&pdata->usb_ctx); + + tilegx_start_ohc(); + + /* Create our IRQs and register them. */ + pdata->irq = create_irq(); + if (pdata->irq < 0) { + ret = -ENXIO; + goto err_no_irq; + } + + tile_irq_activate(pdata->irq, TILE_IRQ_PERCPU); + + /* Configure interrupts. */ + ret = gxio_usb_host_cfg_interrupt(&pdata->usb_ctx, + cpu_x(my_cpu), cpu_y(my_cpu), + KERNEL_PL, pdata->irq); + if (ret) { + ret = -ENXIO; + goto err_have_irq; + } + + /* Register all of our memory. */ + pte = pte_set_home(pte, PAGE_HOME_HASH); + ret = gxio_usb_host_register_client_memory(&pdata->usb_ctx, pte, 0); + if (ret) { + ret = -ENXIO; + goto err_have_irq; + } + + ohci_hcd_init(hcd_to_ohci(hcd)); + + ret = usb_add_hcd(hcd, pdata->irq, IRQF_SHARED); + if (ret == 0) { + platform_set_drvdata(pdev, hcd); + return ret; + } + +err_have_irq: + destroy_irq(pdata->irq); +err_no_irq: + tilegx_stop_ohc(); + usb_put_hcd(hcd); +err_hcd: + gxio_usb_host_destroy(&pdata->usb_ctx); + return ret; +} + +static int ohci_hcd_tilegx_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct tilegx_usb_platform_data* pdata = pdev->dev.platform_data; + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + tilegx_stop_ohc(); + gxio_usb_host_destroy(&pdata->usb_ctx); + destroy_irq(pdata->irq); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void ohci_hcd_tilegx_drv_shutdown(struct platform_device *pdev) +{ + usb_hcd_platform_shutdown(pdev); + ohci_hcd_tilegx_drv_remove(pdev); +} + +static struct platform_driver ohci_hcd_tilegx_driver = { + .probe = ohci_hcd_tilegx_drv_probe, + .remove = ohci_hcd_tilegx_drv_remove, + .shutdown = ohci_hcd_tilegx_drv_shutdown, + .driver = { + .name = "tilegx-ohci", + .owner = THIS_MODULE, + } +}; + +MODULE_ALIAS("platform:tilegx-ohci"); diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c new file mode 100644 index 00000000..5e3a6deb --- /dev/null +++ b/drivers/usb/host/ohci-tmio.c @@ -0,0 +1,375 @@ +/* + * OHCI HCD(Host Controller Driver) for USB. + * + *(C) Copyright 1999 Roman Weissgaerber + *(C) Copyright 2000-2002 David Brownell + *(C) Copyright 2002 Hewlett-Packard Company + * + * Bus glue for Toshiba Mobile IO(TMIO) Controller's OHCI core + * (C) Copyright 2005 Chris Humbert + * (C) Copyright 2007, 2008 Dmitry Baryshkov + * + * This is known to work with the following variants: + * TC6393XB revision 3 (32kB SRAM) + * + * The TMIO's OHCI core DMAs through a small internal buffer that + * is directly addressable by the CPU. + * + * Written from sparse documentation from Toshiba and Sharp's driver + * for the 2.4 kernel, + * usb-ohci-tc6393.c(C) Copyright 2004 Lineo Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/*#include +#include +#include +#include +#include +#include */ +#include +#include +#include +#include + +/*-------------------------------------------------------------------------*/ + +/* + * USB Host Controller Configuration Register + */ +#define CCR_REVID 0x08 /* b Revision ID */ +#define CCR_BASE 0x10 /* l USB Control Register Base Address Low */ +#define CCR_ILME 0x40 /* b Internal Local Memory Enable */ +#define CCR_PM 0x4c /* w Power Management */ +#define CCR_INTC 0x50 /* b INT Control */ +#define CCR_LMW1L 0x54 /* w Local Memory Window 1 LMADRS Low */ +#define CCR_LMW1H 0x56 /* w Local Memory Window 1 LMADRS High */ +#define CCR_LMW1BL 0x58 /* w Local Memory Window 1 Base Address Low */ +#define CCR_LMW1BH 0x5A /* w Local Memory Window 1 Base Address High */ +#define CCR_LMW2L 0x5C /* w Local Memory Window 2 LMADRS Low */ +#define CCR_LMW2H 0x5E /* w Local Memory Window 2 LMADRS High */ +#define CCR_LMW2BL 0x60 /* w Local Memory Window 2 Base Address Low */ +#define CCR_LMW2BH 0x62 /* w Local Memory Window 2 Base Address High */ +#define CCR_MISC 0xFC /* b MISC */ + +#define CCR_PM_GKEN 0x0001 +#define CCR_PM_CKRNEN 0x0002 +#define CCR_PM_USBPW1 0x0004 +#define CCR_PM_USBPW2 0x0008 +#define CCR_PM_USBPW3 0x0008 +#define CCR_PM_PMEE 0x0100 +#define CCR_PM_PMES 0x8000 + +/*-------------------------------------------------------------------------*/ + +struct tmio_hcd { + void __iomem *ccr; + spinlock_t lock; /* protects RMW cycles */ +}; + +#define hcd_to_tmio(hcd) ((struct tmio_hcd *)(hcd_to_ohci(hcd) + 1)) + +/*-------------------------------------------------------------------------*/ + +static void tmio_write_pm(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + u16 pm; + unsigned long flags; + + spin_lock_irqsave(&tmio->lock, flags); + + pm = CCR_PM_GKEN | CCR_PM_CKRNEN | + CCR_PM_PMEE | CCR_PM_PMES; + + tmio_iowrite16(pm, tmio->ccr + CCR_PM); + spin_unlock_irqrestore(&tmio->lock, flags); +} + +static void tmio_stop_hc(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + u16 pm; + + pm = CCR_PM_GKEN | CCR_PM_CKRNEN; + switch (ohci->num_ports) { + default: + dev_err(&dev->dev, "Unsupported amount of ports: %d\n", ohci->num_ports); + case 3: + pm |= CCR_PM_USBPW3; + case 2: + pm |= CCR_PM_USBPW2; + case 1: + pm |= CCR_PM_USBPW1; + } + tmio_iowrite8(0, tmio->ccr + CCR_INTC); + tmio_iowrite8(0, tmio->ccr + CCR_ILME); + tmio_iowrite16(0, tmio->ccr + CCR_BASE); + tmio_iowrite16(0, tmio->ccr + CCR_BASE + 2); + tmio_iowrite16(pm, tmio->ccr + CCR_PM); +} + +static void tmio_start_hc(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + unsigned long base = hcd->rsrc_start; + + tmio_write_pm(dev); + tmio_iowrite16(base, tmio->ccr + CCR_BASE); + tmio_iowrite16(base >> 16, tmio->ccr + CCR_BASE + 2); + tmio_iowrite8(1, tmio->ccr + CCR_ILME); + tmio_iowrite8(2, tmio->ccr + CCR_INTC); + + dev_info(&dev->dev, "revision %d @ 0x%08llx, irq %d\n", + tmio_ioread8(tmio->ccr + CCR_REVID), + (u64) hcd->rsrc_start, hcd->irq); +} + +static int ohci_tmio_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + if ((ret = ohci_run(ohci)) < 0) { + dev_err(hcd->self.controller, "can't start %s\n", + hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + +static const struct hc_driver ohci_tmio_hc_driver = { + .description = hcd_name, + .product_desc = "TMIO OHCI USB Host Controller", + .hcd_priv_size = sizeof(struct ohci_hcd) + sizeof (struct tmio_hcd), + + /* generic hardware linkage */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY | HCD_LOCAL_MEM, + + /* basic lifecycle operations */ + .start = ohci_tmio_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* managing i/o requests and associated device resources */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* scheduling support */ + .get_frame_number = ohci_get_frame, + + /* root hub support */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ +static struct platform_driver ohci_hcd_tmio_driver; + +static int ohci_hcd_tmio_drv_probe(struct platform_device *dev) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + struct resource *regs = platform_get_resource(dev, IORESOURCE_MEM, 0); + struct resource *config = platform_get_resource(dev, IORESOURCE_MEM, 1); + struct resource *sram = platform_get_resource(dev, IORESOURCE_MEM, 2); + int irq = platform_get_irq(dev, 0); + struct tmio_hcd *tmio; + struct ohci_hcd *ohci; + struct usb_hcd *hcd; + int ret; + + if (usb_disabled()) + return -ENODEV; + + if (!cell) + return -EINVAL; + + hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev_name(&dev->dev)); + if (!hcd) { + ret = -ENOMEM; + goto err_usb_create_hcd; + } + + hcd->rsrc_start = regs->start; + hcd->rsrc_len = resource_size(regs); + + tmio = hcd_to_tmio(hcd); + + spin_lock_init(&tmio->lock); + + tmio->ccr = ioremap(config->start, resource_size(config)); + if (!tmio->ccr) { + ret = -ENOMEM; + goto err_ioremap_ccr; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + ret = -ENOMEM; + goto err_ioremap_regs; + } + + if (!dma_declare_coherent_memory(&dev->dev, sram->start, + sram->start, + resource_size(sram), + DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE)) { + ret = -EBUSY; + goto err_dma_declare; + } + + if (cell->enable) { + ret = cell->enable(dev); + if (ret) + goto err_enable; + } + + tmio_start_hc(dev); + ohci = hcd_to_ohci(hcd); + ohci_hcd_init(ohci); + + ret = usb_add_hcd(hcd, irq, 0); + if (ret) + goto err_add_hcd; + + if (ret == 0) + return ret; + + usb_remove_hcd(hcd); + +err_add_hcd: + tmio_stop_hc(dev); + if (cell->disable) + cell->disable(dev); +err_enable: + dma_release_declared_memory(&dev->dev); +err_dma_declare: + iounmap(hcd->regs); +err_ioremap_regs: + iounmap(tmio->ccr); +err_ioremap_ccr: + usb_put_hcd(hcd); +err_usb_create_hcd: + + return ret; +} + +static int ohci_hcd_tmio_drv_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + const struct mfd_cell *cell = mfd_get_cell(dev); + + usb_remove_hcd(hcd); + tmio_stop_hc(dev); + if (cell->disable) + cell->disable(dev); + dma_release_declared_memory(&dev->dev); + iounmap(hcd->regs); + iounmap(tmio->ccr); + usb_put_hcd(hcd); + + platform_set_drvdata(dev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int ohci_hcd_tmio_drv_suspend(struct platform_device *dev, pm_message_t state) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + unsigned long flags; + u8 misc; + int ret; + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + spin_lock_irqsave(&tmio->lock, flags); + + misc = tmio_ioread8(tmio->ccr + CCR_MISC); + misc |= 1 << 3; /* USSUSP */ + tmio_iowrite8(misc, tmio->ccr + CCR_MISC); + + spin_unlock_irqrestore(&tmio->lock, flags); + + if (cell->suspend) { + ret = cell->suspend(dev); + if (ret) + return ret; + } + return 0; +} + +static int ohci_hcd_tmio_drv_resume(struct platform_device *dev) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct tmio_hcd *tmio = hcd_to_tmio(hcd); + unsigned long flags; + u8 misc; + int ret; + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + if (cell->resume) { + ret = cell->resume(dev); + if (ret) + return ret; + } + + tmio_start_hc(dev); + + spin_lock_irqsave(&tmio->lock, flags); + + misc = tmio_ioread8(tmio->ccr + CCR_MISC); + misc &= ~(1 << 3); /* USSUSP */ + tmio_iowrite8(misc, tmio->ccr + CCR_MISC); + + spin_unlock_irqrestore(&tmio->lock, flags); + + ohci_resume(hcd, false); + + return 0; +} +#else +#define ohci_hcd_tmio_drv_suspend NULL +#define ohci_hcd_tmio_drv_resume NULL +#endif + +static struct platform_driver ohci_hcd_tmio_driver = { + .probe = ohci_hcd_tmio_drv_probe, + .remove = ohci_hcd_tmio_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .suspend = ohci_hcd_tmio_drv_suspend, + .resume = ohci_hcd_tmio_drv_resume, + .driver = { + .name = "tmio-ohci", + .owner = THIS_MODULE, + }, +}; diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h new file mode 100644 index 00000000..f2521f31 --- /dev/null +++ b/drivers/usb/host/ohci.h @@ -0,0 +1,722 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * + * This file is licenced under the GPL. + */ + +/* + * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to + * __leXX (normally) or __beXX (given OHCI_BIG_ENDIAN), depending on the + * host controller implementation. + */ +typedef __u32 __bitwise __hc32; +typedef __u16 __bitwise __hc16; + +/* + * OHCI Endpoint Descriptor (ED) ... holds TD queue + * See OHCI spec, section 4.2 + * + * This is a "Queue Head" for those transfers, which is why + * both EHCI and UHCI call similar structures a "QH". + */ +struct ed { + /* first fields are hardware-specified */ + __hc32 hwINFO; /* endpoint config bitmap */ + /* info bits defined by hcd */ +#define ED_DEQUEUE (1 << 27) + /* info bits defined by the hardware */ +#define ED_ISO (1 << 15) +#define ED_SKIP (1 << 14) +#define ED_LOWSPEED (1 << 13) +#define ED_OUT (0x01 << 11) +#define ED_IN (0x02 << 11) + __hc32 hwTailP; /* tail of TD list */ + __hc32 hwHeadP; /* head of TD list (hc r/w) */ +#define ED_C (0x02) /* toggle carry */ +#define ED_H (0x01) /* halted */ + __hc32 hwNextED; /* next ED in list */ + + /* rest are purely for the driver's use */ + dma_addr_t dma; /* addr of ED */ + struct td *dummy; /* next TD to activate */ + + /* host's view of schedule */ + struct ed *ed_next; /* on schedule or rm_list */ + struct ed *ed_prev; /* for non-interrupt EDs */ + struct list_head td_list; /* "shadow list" of our TDs */ + + /* create --> IDLE --> OPER --> ... --> IDLE --> destroy + * usually: OPER --> UNLINK --> (IDLE | OPER) --> ... + */ + u8 state; /* ED_{IDLE,UNLINK,OPER} */ +#define ED_IDLE 0x00 /* NOT linked to HC */ +#define ED_UNLINK 0x01 /* being unlinked from hc */ +#define ED_OPER 0x02 /* IS linked to hc */ + + u8 type; /* PIPE_{BULK,...} */ + + /* periodic scheduling params (for intr and iso) */ + u8 branch; + u16 interval; + u16 load; + u16 last_iso; /* iso only */ + + /* HC may see EDs on rm_list until next frame (frame_no == tick) */ + u16 tick; +} __attribute__ ((aligned(16))); + +#define ED_MASK ((u32)~0x0f) /* strip hw status in low addr bits */ + + +/* + * OHCI Transfer Descriptor (TD) ... one per transfer segment + * See OHCI spec, sections 4.3.1 (general = control/bulk/interrupt) + * and 4.3.2 (iso) + */ +struct td { + /* first fields are hardware-specified */ + __hc32 hwINFO; /* transfer info bitmask */ + + /* hwINFO bits for both general and iso tds: */ +#define TD_CC 0xf0000000 /* condition code */ +#define TD_CC_GET(td_p) ((td_p >>28) & 0x0f) +//#define TD_CC_SET(td_p, cc) (td_p) = ((td_p) & 0x0fffffff) | (((cc) & 0x0f) << 28) +#define TD_DI 0x00E00000 /* frames before interrupt */ +#define TD_DI_SET(X) (((X) & 0x07)<< 21) + /* these two bits are available for definition/use by HCDs in both + * general and iso tds ... others are available for only one type + */ +#define TD_DONE 0x00020000 /* retired to donelist */ +#define TD_ISO 0x00010000 /* copy of ED_ISO */ + + /* hwINFO bits for general tds: */ +#define TD_EC 0x0C000000 /* error count */ +#define TD_T 0x03000000 /* data toggle state */ +#define TD_T_DATA0 0x02000000 /* DATA0 */ +#define TD_T_DATA1 0x03000000 /* DATA1 */ +#define TD_T_TOGGLE 0x00000000 /* uses ED_C */ +#define TD_DP 0x00180000 /* direction/pid */ +#define TD_DP_SETUP 0x00000000 /* SETUP pid */ +#define TD_DP_IN 0x00100000 /* IN pid */ +#define TD_DP_OUT 0x00080000 /* OUT pid */ + /* 0x00180000 rsvd */ +#define TD_R 0x00040000 /* round: short packets OK? */ + + /* (no hwINFO #defines yet for iso tds) */ + + __hc32 hwCBP; /* Current Buffer Pointer (or 0) */ + __hc32 hwNextTD; /* Next TD Pointer */ + __hc32 hwBE; /* Memory Buffer End Pointer */ + + /* PSW is only for ISO. Only 1 PSW entry is used, but on + * big-endian PPC hardware that's the second entry. + */ +#define MAXPSW 2 + __hc16 hwPSW [MAXPSW]; + + /* rest are purely for the driver's use */ + __u8 index; + struct ed *ed; + struct td *td_hash; /* dma-->td hashtable */ + struct td *next_dl_td; + struct urb *urb; + + dma_addr_t td_dma; /* addr of this TD */ + dma_addr_t data_dma; /* addr of data it points to */ + + struct list_head td_list; /* "shadow list", TDs on same ED */ +} __attribute__ ((aligned(32))); /* c/b/i need 16; only iso needs 32 */ + +#define TD_MASK ((u32)~0x1f) /* strip hw status in low addr bits */ + +/* + * Hardware transfer status codes -- CC from td->hwINFO or td->hwPSW + */ +#define TD_CC_NOERROR 0x00 +#define TD_CC_CRC 0x01 +#define TD_CC_BITSTUFFING 0x02 +#define TD_CC_DATATOGGLEM 0x03 +#define TD_CC_STALL 0x04 +#define TD_DEVNOTRESP 0x05 +#define TD_PIDCHECKFAIL 0x06 +#define TD_UNEXPECTEDPID 0x07 +#define TD_DATAOVERRUN 0x08 +#define TD_DATAUNDERRUN 0x09 + /* 0x0A, 0x0B reserved for hardware */ +#define TD_BUFFEROVERRUN 0x0C +#define TD_BUFFERUNDERRUN 0x0D + /* 0x0E, 0x0F reserved for HCD */ +#define TD_NOTACCESSED 0x0F + + +/* map OHCI TD status codes (CC) to errno values */ +static const int cc_to_error [16] = { + /* No Error */ 0, + /* CRC Error */ -EILSEQ, + /* Bit Stuff */ -EPROTO, + /* Data Togg */ -EILSEQ, + /* Stall */ -EPIPE, + /* DevNotResp */ -ETIME, + /* PIDCheck */ -EPROTO, + /* UnExpPID */ -EPROTO, + /* DataOver */ -EOVERFLOW, + /* DataUnder */ -EREMOTEIO, + /* (for hw) */ -EIO, + /* (for hw) */ -EIO, + /* BufferOver */ -ECOMM, + /* BuffUnder */ -ENOSR, + /* (for HCD) */ -EALREADY, + /* (for HCD) */ -EALREADY +}; + + +/* + * The HCCA (Host Controller Communications Area) is a 256 byte + * structure defined section 4.4.1 of the OHCI spec. The HC is + * told the base address of it. It must be 256-byte aligned. + */ +struct ohci_hcca { +#define NUM_INTS 32 + __hc32 int_table [NUM_INTS]; /* periodic schedule */ + + /* + * OHCI defines u16 frame_no, followed by u16 zero pad. + * Since some processors can't do 16 bit bus accesses, + * portable access must be a 32 bits wide. + */ + __hc32 frame_no; /* current frame number */ + __hc32 done_head; /* info returned for an interrupt */ + u8 reserved_for_hc [116]; + u8 what [4]; /* spec only identifies 252 bytes :) */ +} __attribute__ ((aligned(256))); + +/* + * This is the structure of the OHCI controller's memory mapped I/O region. + * You must use readl() and writel() (in ) to access these fields!! + * Layout is in section 7 (and appendix B) of the spec. + */ +struct ohci_regs { + /* control and status registers (section 7.1) */ + __hc32 revision; + __hc32 control; + __hc32 cmdstatus; + __hc32 intrstatus; + __hc32 intrenable; + __hc32 intrdisable; + + /* memory pointers (section 7.2) */ + __hc32 hcca; + __hc32 ed_periodcurrent; + __hc32 ed_controlhead; + __hc32 ed_controlcurrent; + __hc32 ed_bulkhead; + __hc32 ed_bulkcurrent; + __hc32 donehead; + + /* frame counters (section 7.3) */ + __hc32 fminterval; + __hc32 fmremaining; + __hc32 fmnumber; + __hc32 periodicstart; + __hc32 lsthresh; + + /* Root hub ports (section 7.4) */ + struct ohci_roothub_regs { + __hc32 a; + __hc32 b; + __hc32 status; +#define MAX_ROOT_PORTS 15 /* maximum OHCI root hub ports (RH_A_NDP) */ + __hc32 portstatus [MAX_ROOT_PORTS]; + } roothub; + + /* and optional "legacy support" registers (appendix B) at 0x0100 */ + +} __attribute__ ((aligned(32))); + + +/* OHCI CONTROL AND STATUS REGISTER MASKS */ + +/* + * HcControl (control) register masks + */ +#define OHCI_CTRL_CBSR (3 << 0) /* control/bulk service ratio */ +#define OHCI_CTRL_PLE (1 << 2) /* periodic list enable */ +#define OHCI_CTRL_IE (1 << 3) /* isochronous enable */ +#define OHCI_CTRL_CLE (1 << 4) /* control list enable */ +#define OHCI_CTRL_BLE (1 << 5) /* bulk list enable */ +#define OHCI_CTRL_HCFS (3 << 6) /* host controller functional state */ +#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ +#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */ +#define OHCI_CTRL_RWE (1 << 10) /* remote wakeup enable */ + +/* pre-shifted values for HCFS */ +# define OHCI_USB_RESET (0 << 6) +# define OHCI_USB_RESUME (1 << 6) +# define OHCI_USB_OPER (2 << 6) +# define OHCI_USB_SUSPEND (3 << 6) + +/* + * HcCommandStatus (cmdstatus) register masks + */ +#define OHCI_HCR (1 << 0) /* host controller reset */ +#define OHCI_CLF (1 << 1) /* control list filled */ +#define OHCI_BLF (1 << 2) /* bulk list filled */ +#define OHCI_OCR (1 << 3) /* ownership change request */ +#define OHCI_SOC (3 << 16) /* scheduling overrun count */ + +/* + * masks used with interrupt registers: + * HcInterruptStatus (intrstatus) + * HcInterruptEnable (intrenable) + * HcInterruptDisable (intrdisable) + */ +#define OHCI_INTR_SO (1 << 0) /* scheduling overrun */ +#define OHCI_INTR_WDH (1 << 1) /* writeback of done_head */ +#define OHCI_INTR_SF (1 << 2) /* start frame */ +#define OHCI_INTR_RD (1 << 3) /* resume detect */ +#define OHCI_INTR_UE (1 << 4) /* unrecoverable error */ +#define OHCI_INTR_FNO (1 << 5) /* frame number overflow */ +#define OHCI_INTR_RHSC (1 << 6) /* root hub status change */ +#define OHCI_INTR_OC (1 << 30) /* ownership change */ +#define OHCI_INTR_MIE (1 << 31) /* master interrupt enable */ + + +/* OHCI ROOT HUB REGISTER MASKS */ + +/* roothub.portstatus [i] bits */ +#define RH_PS_CCS 0x00000001 /* current connect status */ +#define RH_PS_PES 0x00000002 /* port enable status*/ +#define RH_PS_PSS 0x00000004 /* port suspend status */ +#define RH_PS_POCI 0x00000008 /* port over current indicator */ +#define RH_PS_PRS 0x00000010 /* port reset status */ +#define RH_PS_PPS 0x00000100 /* port power status */ +#define RH_PS_LSDA 0x00000200 /* low speed device attached */ +#define RH_PS_CSC 0x00010000 /* connect status change */ +#define RH_PS_PESC 0x00020000 /* port enable status change */ +#define RH_PS_PSSC 0x00040000 /* port suspend status change */ +#define RH_PS_OCIC 0x00080000 /* over current indicator change */ +#define RH_PS_PRSC 0x00100000 /* port reset status change */ + +/* roothub.status bits */ +#define RH_HS_LPS 0x00000001 /* local power status */ +#define RH_HS_OCI 0x00000002 /* over current indicator */ +#define RH_HS_DRWE 0x00008000 /* device remote wakeup enable */ +#define RH_HS_LPSC 0x00010000 /* local power status change */ +#define RH_HS_OCIC 0x00020000 /* over current indicator change */ +#define RH_HS_CRWE 0x80000000 /* clear remote wakeup enable */ + +/* roothub.b masks */ +#define RH_B_DR 0x0000ffff /* device removable flags */ +#define RH_B_PPCM 0xffff0000 /* port power control mask */ + +/* roothub.a masks */ +#define RH_A_NDP (0xff << 0) /* number of downstream ports */ +#define RH_A_PSM (1 << 8) /* power switching mode */ +#define RH_A_NPS (1 << 9) /* no power switching */ +#define RH_A_DT (1 << 10) /* device type (mbz) */ +#define RH_A_OCPM (1 << 11) /* over current protection mode */ +#define RH_A_NOCP (1 << 12) /* no over current protection */ +#define RH_A_POTPGT (0xff << 24) /* power on to power good time */ + + +/* hcd-private per-urb state */ +typedef struct urb_priv { + struct ed *ed; + u16 length; // # tds in this request + u16 td_cnt; // tds already serviced + struct list_head pending; + struct td *td [0]; // all TDs in this request + +} urb_priv_t; + +#define TD_HASH_SIZE 64 /* power'o'two */ +// sizeof (struct td) ~= 64 == 2^6 ... +#define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 6)) % TD_HASH_SIZE) + + +/* + * This is the full ohci controller description + * + * Note how the "proper" USB information is just + * a subset of what the full implementation needs. (Linus) + */ + +enum ohci_rh_state { + OHCI_RH_HALTED, + OHCI_RH_SUSPENDED, + OHCI_RH_RUNNING +}; + +struct ohci_hcd { + spinlock_t lock; + + /* + * I/O memory used to communicate with the HC (dma-consistent) + */ + struct ohci_regs __iomem *regs; + + /* + * main memory used to communicate with the HC (dma-consistent). + * hcd adds to schedule for a live hc any time, but removals finish + * only at the start of the next frame. + */ + struct ohci_hcca *hcca; + dma_addr_t hcca_dma; + + struct ed *ed_rm_list; /* to be removed */ + + struct ed *ed_bulktail; /* last in bulk list */ + struct ed *ed_controltail; /* last in ctrl list */ + struct ed *periodic [NUM_INTS]; /* shadow int_table */ + + void (*start_hnp)(struct ohci_hcd *ohci); + + /* + * memory management for queue data structures + */ + struct dma_pool *td_cache; + struct dma_pool *ed_cache; + struct td *td_hash [TD_HASH_SIZE]; + struct list_head pending; + + /* + * driver state + */ + enum ohci_rh_state rh_state; + int num_ports; + int load [NUM_INTS]; + u32 hc_control; /* copy of hc control reg */ + unsigned long next_statechange; /* suspend/resume */ + u32 fminterval; /* saved register */ + unsigned autostop:1; /* rh auto stopping/stopped */ + + unsigned long flags; /* for HC bugs */ +#define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */ +#define OHCI_QUIRK_SUPERIO 0x02 /* natsemi */ +#define OHCI_QUIRK_INITRESET 0x04 /* SiS, OPTi, ... */ +#define OHCI_QUIRK_BE_DESC 0x08 /* BE descriptors */ +#define OHCI_QUIRK_BE_MMIO 0x10 /* BE registers */ +#define OHCI_QUIRK_ZFMICRO 0x20 /* Compaq ZFMicro chipset*/ +#define OHCI_QUIRK_NEC 0x40 /* lost interrupts */ +#define OHCI_QUIRK_FRAME_NO 0x80 /* no big endian frame_no shift */ +#define OHCI_QUIRK_HUB_POWER 0x100 /* distrust firmware power/oc setup */ +#define OHCI_QUIRK_AMD_PLL 0x200 /* AMD PLL quirk*/ +#define OHCI_QUIRK_AMD_PREFETCH 0x400 /* pre-fetch for ISO transfer */ +#define OHCI_QUIRK_GLOBAL_SUSPEND 0x800 /* must suspend ports */ + + // there are also chip quirks/bugs in init logic + + struct work_struct nec_work; /* Worker for NEC quirk */ + + /* Needed for ZF Micro quirk */ + struct timer_list unlink_watchdog; + unsigned eds_scheduled; + struct ed *ed_to_check; + unsigned zf_delay; + +#ifdef DEBUG + struct dentry *debug_dir; + struct dentry *debug_async; + struct dentry *debug_periodic; + struct dentry *debug_registers; +#endif +}; + +#ifdef CONFIG_PCI +static inline int quirk_nec(struct ohci_hcd *ohci) +{ + return ohci->flags & OHCI_QUIRK_NEC; +} +static inline int quirk_zfmicro(struct ohci_hcd *ohci) +{ + return ohci->flags & OHCI_QUIRK_ZFMICRO; +} +static inline int quirk_amdiso(struct ohci_hcd *ohci) +{ + return ohci->flags & OHCI_QUIRK_AMD_PLL; +} +static inline int quirk_amdprefetch(struct ohci_hcd *ohci) +{ + return ohci->flags & OHCI_QUIRK_AMD_PREFETCH; +} +#else +static inline int quirk_nec(struct ohci_hcd *ohci) +{ + return 0; +} +static inline int quirk_zfmicro(struct ohci_hcd *ohci) +{ + return 0; +} +static inline int quirk_amdiso(struct ohci_hcd *ohci) +{ + return 0; +} +static inline int quirk_amdprefetch(struct ohci_hcd *ohci) +{ + return 0; +} +#endif + +/* convert between an hcd pointer and the corresponding ohci_hcd */ +static inline struct ohci_hcd *hcd_to_ohci (struct usb_hcd *hcd) +{ + return (struct ohci_hcd *) (hcd->hcd_priv); +} +static inline struct usb_hcd *ohci_to_hcd (const struct ohci_hcd *ohci) +{ + return container_of ((void *) ohci, struct usb_hcd, hcd_priv); +} + +/*-------------------------------------------------------------------------*/ + +#ifndef DEBUG +#define STUB_DEBUG_FILES +#endif /* DEBUG */ + +#define ohci_dbg(ohci, fmt, args...) \ + dev_dbg (ohci_to_hcd(ohci)->self.controller , fmt , ## args ) +#define ohci_err(ohci, fmt, args...) \ + dev_err (ohci_to_hcd(ohci)->self.controller , fmt , ## args ) +#define ohci_info(ohci, fmt, args...) \ + dev_info (ohci_to_hcd(ohci)->self.controller , fmt , ## args ) +#define ohci_warn(ohci, fmt, args...) \ + dev_warn (ohci_to_hcd(ohci)->self.controller , fmt , ## args ) + +#ifdef OHCI_VERBOSE_DEBUG +# define ohci_vdbg ohci_dbg +#else +# define ohci_vdbg(ohci, fmt, args...) do { } while (0) +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * While most USB host controllers implement their registers and + * in-memory communication descriptors in little-endian format, + * a minority (notably the IBM STB04XXX and the Motorola MPC5200 + * processors) implement them in big endian format. + * + * In addition some more exotic implementations like the Toshiba + * Spider (aka SCC) cell southbridge are "mixed" endian, that is, + * they have a different endianness for registers vs. in-memory + * descriptors. + * + * This attempts to support either format at compile time without a + * runtime penalty, or both formats with the additional overhead + * of checking a flag bit. + * + * That leads to some tricky Kconfig rules howevber. There are + * different defaults based on some arch/ppc platforms, though + * the basic rules are: + * + * Controller type Kconfig options needed + * --------------- ---------------------- + * little endian CONFIG_USB_OHCI_LITTLE_ENDIAN + * + * fully big endian CONFIG_USB_OHCI_BIG_ENDIAN_DESC _and_ + * CONFIG_USB_OHCI_BIG_ENDIAN_MMIO + * + * mixed endian CONFIG_USB_OHCI_LITTLE_ENDIAN _and_ + * CONFIG_USB_OHCI_BIG_ENDIAN_{MMIO,DESC} + * + * (If you have a mixed endian controller, you -must- also define + * CONFIG_USB_OHCI_LITTLE_ENDIAN or things will not work when building + * both your mixed endian and a fully big endian controller support in + * the same kernel image). + */ + +#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_DESC +#ifdef CONFIG_USB_OHCI_LITTLE_ENDIAN +#define big_endian_desc(ohci) (ohci->flags & OHCI_QUIRK_BE_DESC) +#else +#define big_endian_desc(ohci) 1 /* only big endian */ +#endif +#else +#define big_endian_desc(ohci) 0 /* only little endian */ +#endif + +#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO +#ifdef CONFIG_USB_OHCI_LITTLE_ENDIAN +#define big_endian_mmio(ohci) (ohci->flags & OHCI_QUIRK_BE_MMIO) +#else +#define big_endian_mmio(ohci) 1 /* only big endian */ +#endif +#else +#define big_endian_mmio(ohci) 0 /* only little endian */ +#endif + +/* + * Big-endian read/write functions are arch-specific. + * Other arches can be added if/when they're needed. + * + */ +static inline unsigned int _ohci_readl (const struct ohci_hcd *ohci, + __hc32 __iomem * regs) +{ +#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO + return big_endian_mmio(ohci) ? + readl_be (regs) : + readl (regs); +#else + return readl (regs); +#endif +} + +static inline void _ohci_writel (const struct ohci_hcd *ohci, + const unsigned int val, __hc32 __iomem *regs) +{ +#ifdef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO + big_endian_mmio(ohci) ? + writel_be (val, regs) : + writel (val, regs); +#else + writel (val, regs); +#endif +} + +#define ohci_readl(o,r) _ohci_readl(o,r) +#define ohci_writel(o,v,r) _ohci_writel(o,v,r) + + +/*-------------------------------------------------------------------------*/ + +/* cpu to ohci */ +static inline __hc16 cpu_to_hc16 (const struct ohci_hcd *ohci, const u16 x) +{ + return big_endian_desc(ohci) ? + (__force __hc16)cpu_to_be16(x) : + (__force __hc16)cpu_to_le16(x); +} + +static inline __hc16 cpu_to_hc16p (const struct ohci_hcd *ohci, const u16 *x) +{ + return big_endian_desc(ohci) ? + cpu_to_be16p(x) : + cpu_to_le16p(x); +} + +static inline __hc32 cpu_to_hc32 (const struct ohci_hcd *ohci, const u32 x) +{ + return big_endian_desc(ohci) ? + (__force __hc32)cpu_to_be32(x) : + (__force __hc32)cpu_to_le32(x); +} + +static inline __hc32 cpu_to_hc32p (const struct ohci_hcd *ohci, const u32 *x) +{ + return big_endian_desc(ohci) ? + cpu_to_be32p(x) : + cpu_to_le32p(x); +} + +/* ohci to cpu */ +static inline u16 hc16_to_cpu (const struct ohci_hcd *ohci, const __hc16 x) +{ + return big_endian_desc(ohci) ? + be16_to_cpu((__force __be16)x) : + le16_to_cpu((__force __le16)x); +} + +static inline u16 hc16_to_cpup (const struct ohci_hcd *ohci, const __hc16 *x) +{ + return big_endian_desc(ohci) ? + be16_to_cpup((__force __be16 *)x) : + le16_to_cpup((__force __le16 *)x); +} + +static inline u32 hc32_to_cpu (const struct ohci_hcd *ohci, const __hc32 x) +{ + return big_endian_desc(ohci) ? + be32_to_cpu((__force __be32)x) : + le32_to_cpu((__force __le32)x); +} + +static inline u32 hc32_to_cpup (const struct ohci_hcd *ohci, const __hc32 *x) +{ + return big_endian_desc(ohci) ? + be32_to_cpup((__force __be32 *)x) : + le32_to_cpup((__force __le32 *)x); +} + +/*-------------------------------------------------------------------------*/ + +/* HCCA frame number is 16 bits, but is accessed as 32 bits since not all + * hardware handles 16 bit reads. That creates a different confusion on + * some big-endian SOC implementations. Same thing happens with PSW access. + */ + +#ifdef CONFIG_PPC_MPC52xx +#define big_endian_frame_no_quirk(ohci) (ohci->flags & OHCI_QUIRK_FRAME_NO) +#else +#define big_endian_frame_no_quirk(ohci) 0 +#endif + +static inline u16 ohci_frame_no(const struct ohci_hcd *ohci) +{ + u32 tmp; + if (big_endian_desc(ohci)) { + tmp = be32_to_cpup((__force __be32 *)&ohci->hcca->frame_no); + if (!big_endian_frame_no_quirk(ohci)) + tmp >>= 16; + } else + tmp = le32_to_cpup((__force __le32 *)&ohci->hcca->frame_no); + + return (u16)tmp; +} + +static inline __hc16 *ohci_hwPSWp(const struct ohci_hcd *ohci, + const struct td *td, int index) +{ + return (__hc16 *)(big_endian_desc(ohci) ? + &td->hwPSW[index ^ 1] : &td->hwPSW[index]); +} + +static inline u16 ohci_hwPSW(const struct ohci_hcd *ohci, + const struct td *td, int index) +{ + return hc16_to_cpup(ohci, ohci_hwPSWp(ohci, td, index)); +} + +/*-------------------------------------------------------------------------*/ + +#define FI 0x2edf /* 12000 bits per frame (-1) */ +#define FSMP(fi) (0x7fff & ((6 * ((fi) - 210)) / 7)) +#define FIT (1 << 31) +#define LSTHRESH 0x628 /* lowspeed bit threshold */ + +static inline void periodic_reinit (struct ohci_hcd *ohci) +{ + u32 fi = ohci->fminterval & 0x03fff; + u32 fit = ohci_readl(ohci, &ohci->regs->fminterval) & FIT; + + ohci_writel (ohci, (fit ^ FIT) | ohci->fminterval, + &ohci->regs->fminterval); + ohci_writel (ohci, ((9 * fi) / 10) & 0x3fff, + &ohci->regs->periodicstart); +} + +/* AMD-756 (D2 rev) reports corrupt register contents in some cases. + * The erratum (#4) description is incorrect. AMD's workaround waits + * till some bits (mostly reserved) are clear; ok for all revs. + */ +#define read_roothub(hc, register, mask) ({ \ + u32 temp = ohci_readl (hc, &hc->regs->roothub.register); \ + if (temp == -1) \ + hc->rh_state = OHCI_RH_HALTED; \ + else if (hc->flags & OHCI_QUIRK_AMD756) \ + while (temp & mask) \ + temp = ohci_readl (hc, &hc->regs->roothub.register); \ + temp; }) + +static inline u32 roothub_a (struct ohci_hcd *hc) + { return read_roothub (hc, a, 0xfc0fe000); } +static inline u32 roothub_b (struct ohci_hcd *hc) + { return ohci_readl (hc, &hc->regs->roothub.b); } +static inline u32 roothub_status (struct ohci_hcd *hc) + { return ohci_readl (hc, &hc->regs->roothub.status); } +static inline u32 roothub_portstatus (struct ohci_hcd *hc, int i) + { return read_roothub (hc, portstatus [i], 0xffe0fce0); } diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c new file mode 100644 index 00000000..0f401dbf --- /dev/null +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -0,0 +1,3958 @@ +/* + * Copyright (c) 2008 Rodolfo Giometti + * Copyright (c) 2008 Eurotech S.p.A. + * + * This code is *strongly* based on EHCI-HCD code by David Brownell since + * the chip is a quasi-EHCI compatible. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "oxu210hp.h" + +#define DRIVER_VERSION "0.0.50" + +/* + * Main defines + */ + +#define oxu_dbg(oxu, fmt, args...) \ + dev_dbg(oxu_to_hcd(oxu)->self.controller , fmt , ## args) +#define oxu_err(oxu, fmt, args...) \ + dev_err(oxu_to_hcd(oxu)->self.controller , fmt , ## args) +#define oxu_info(oxu, fmt, args...) \ + dev_info(oxu_to_hcd(oxu)->self.controller , fmt , ## args) + +static inline struct usb_hcd *oxu_to_hcd(struct oxu_hcd *oxu) +{ + return container_of((void *) oxu, struct usb_hcd, hcd_priv); +} + +static inline struct oxu_hcd *hcd_to_oxu(struct usb_hcd *hcd) +{ + return (struct oxu_hcd *) (hcd->hcd_priv); +} + +/* + * Debug stuff + */ + +#undef OXU_URB_TRACE +#undef OXU_VERBOSE_DEBUG + +#ifdef OXU_VERBOSE_DEBUG +#define oxu_vdbg oxu_dbg +#else +#define oxu_vdbg(oxu, fmt, args...) /* Nop */ +#endif + +#ifdef DEBUG + +static int __attribute__((__unused__)) +dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) +{ + return scnprintf(buf, len, "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", status, + (status & STS_ASS) ? " Async" : "", + (status & STS_PSS) ? " Periodic" : "", + (status & STS_RECL) ? " Recl" : "", + (status & STS_HALT) ? " Halt" : "", + (status & STS_IAA) ? " IAA" : "", + (status & STS_FATAL) ? " FATAL" : "", + (status & STS_FLR) ? " FLR" : "", + (status & STS_PCD) ? " PCD" : "", + (status & STS_ERR) ? " ERR" : "", + (status & STS_INT) ? " INT" : "" + ); +} + +static int __attribute__((__unused__)) +dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) +{ + return scnprintf(buf, len, "%s%sintrenable %02x%s%s%s%s%s%s", + label, label[0] ? " " : "", enable, + (enable & STS_IAA) ? " IAA" : "", + (enable & STS_FATAL) ? " FATAL" : "", + (enable & STS_FLR) ? " FLR" : "", + (enable & STS_PCD) ? " PCD" : "", + (enable & STS_ERR) ? " ERR" : "", + (enable & STS_INT) ? " INT" : "" + ); +} + +static const char *const fls_strings[] = + { "1024", "512", "256", "??" }; + +static int dbg_command_buf(char *buf, unsigned len, + const char *label, u32 command) +{ + return scnprintf(buf, len, + "%s%scommand %06x %s=%d ithresh=%d%s%s%s%s period=%s%s %s", + label, label[0] ? " " : "", command, + (command & CMD_PARK) ? "park" : "(park)", + CMD_PARK_CNT(command), + (command >> 16) & 0x3f, + (command & CMD_LRESET) ? " LReset" : "", + (command & CMD_IAAD) ? " IAAD" : "", + (command & CMD_ASE) ? " Async" : "", + (command & CMD_PSE) ? " Periodic" : "", + fls_strings[(command >> 2) & 0x3], + (command & CMD_RESET) ? " Reset" : "", + (command & CMD_RUN) ? "RUN" : "HALT" + ); +} + +static int dbg_port_buf(char *buf, unsigned len, const char *label, + int port, u32 status) +{ + char *sig; + + /* signaling state */ + switch (status & (3 << 10)) { + case 0 << 10: + sig = "se0"; + break; + case 1 << 10: + sig = "k"; /* low speed */ + break; + case 2 << 10: + sig = "j"; + break; + default: + sig = "?"; + break; + } + + return scnprintf(buf, len, + "%s%sport %d status %06x%s%s sig=%s%s%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", port, status, + (status & PORT_POWER) ? " POWER" : "", + (status & PORT_OWNER) ? " OWNER" : "", + sig, + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_OCC) ? " OCC" : "", + (status & PORT_OC) ? " OC" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : "" + ); +} + +#else + +static inline int __attribute__((__unused__)) +dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_command_buf(char *buf, unsigned len, const char *label, u32 command) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_port_buf(char *buf, unsigned len, const char *label, int port, u32 status) +{ return 0; } + +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ +#define dbg_status(oxu, label, status) { \ + char _buf[80]; \ + dbg_status_buf(_buf, sizeof _buf, label, status); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +#define dbg_cmd(oxu, label, command) { \ + char _buf[80]; \ + dbg_command_buf(_buf, sizeof _buf, label, command); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +#define dbg_port(oxu, label, port, status) { \ + char _buf[80]; \ + dbg_port_buf(_buf, sizeof _buf, label, port, status); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +/* + * Module parameters + */ + +/* Initial IRQ latency: faster than hw default */ +static int log2_irq_thresh; /* 0 to 6 */ +module_param(log2_irq_thresh, int, S_IRUGO); +MODULE_PARM_DESC(log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +/* Initial park setting: slower than hw default */ +static unsigned park; +module_param(park, uint, S_IRUGO); +MODULE_PARM_DESC(park, "park setting; 1-3 back-to-back async packets"); + +/* For flakey hardware, ignore overcurrent indicators */ +static bool ignore_oc; +module_param(ignore_oc, bool, S_IRUGO); +MODULE_PARM_DESC(ignore_oc, "ignore bogus hardware overcurrent indications"); + + +static void ehci_work(struct oxu_hcd *oxu); +static int oxu_hub_control(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); + +/* + * Local functions + */ + +/* Low level read/write registers functions */ +static inline u32 oxu_readl(void *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void oxu_writel(void *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +static inline void timer_action_done(struct oxu_hcd *oxu, + enum ehci_timer_action action) +{ + clear_bit(action, &oxu->actions); +} + +static inline void timer_action(struct oxu_hcd *oxu, + enum ehci_timer_action action) +{ + if (!test_and_set_bit(action, &oxu->actions)) { + unsigned long t; + + switch (action) { + case TIMER_IAA_WATCHDOG: + t = EHCI_IAA_JIFFIES; + break; + case TIMER_IO_WATCHDOG: + t = EHCI_IO_JIFFIES; + break; + case TIMER_ASYNC_OFF: + t = EHCI_ASYNC_JIFFIES; + break; + case TIMER_ASYNC_SHRINK: + default: + t = EHCI_SHRINK_JIFFIES; + break; + } + t += jiffies; + /* all timings except IAA watchdog can be overridden. + * async queue SHRINK often precedes IAA. while it's ready + * to go OFF neither can matter, and afterwards the IO + * watchdog stops unless there's still periodic traffic. + */ + if (action != TIMER_IAA_WATCHDOG + && t > oxu->watchdog.expires + && timer_pending(&oxu->watchdog)) + return; + mod_timer(&oxu->watchdog, t); + } +} + +/* + * handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + * + * That last failure should_only happen in cases like physical cardbus eject + * before driver shutdown. But it also seems to be caused by bugs in cardbus + * bridge shutdown: shutting down the bridge before the devices using it. + */ +static int handshake(struct oxu_hcd *oxu, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = readl(ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* Force HC to halt state from unknown (EHCI spec section 2.3) */ +static int ehci_halt(struct oxu_hcd *oxu) +{ + u32 temp = readl(&oxu->regs->status); + + /* disable any irqs left enabled by previous code */ + writel(0, &oxu->regs->intr_enable); + + if ((temp & STS_HALT) != 0) + return 0; + + temp = readl(&oxu->regs->command); + temp &= ~CMD_RUN; + writel(temp, &oxu->regs->command); + return handshake(oxu, &oxu->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* Put TDI/ARC silicon into EHCI mode */ +static void tdi_reset(struct oxu_hcd *oxu) +{ + u32 __iomem *reg_ptr; + u32 tmp; + + reg_ptr = (u32 __iomem *)(((u8 __iomem *)oxu->regs) + 0x68); + tmp = readl(reg_ptr); + tmp |= 0x3; + writel(tmp, reg_ptr); +} + +/* Reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct oxu_hcd *oxu) +{ + int retval; + u32 command = readl(&oxu->regs->command); + + command |= CMD_RESET; + dbg_cmd(oxu, "reset", command); + writel(command, &oxu->regs->command); + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + oxu->next_statechange = jiffies; + retval = handshake(oxu, &oxu->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (retval) + return retval; + + tdi_reset(oxu); + + return retval; +} + +/* Idle the controller (from running) */ +static void ehci_quiesce(struct oxu_hcd *oxu) +{ + u32 temp; + +#ifdef DEBUG + if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + BUG(); +#endif + + /* wait for any schedule enables/disables to take effect */ + temp = readl(&oxu->regs->command) << 10; + temp &= STS_ASS | STS_PSS; + if (handshake(oxu, &oxu->regs->status, STS_ASS | STS_PSS, + temp, 16 * 125) != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return; + } + + /* then disable anything that's still active */ + temp = readl(&oxu->regs->command); + temp &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); + writel(temp, &oxu->regs->command); + + /* hardware can take 16 microframes to turn off ... */ + if (handshake(oxu, &oxu->regs->status, STS_ASS | STS_PSS, + 0, 16 * 125) != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return; + } +} + +static int check_reset_complete(struct oxu_hcd *oxu, int index, + u32 __iomem *status_reg, int port_status) +{ + if (!(port_status & PORT_CONNECT)) { + oxu->reset_done[index] = 0; + return port_status; + } + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + oxu_dbg(oxu, "Failed to enable port %d on root hub TT\n", + index+1); + return port_status; + } else + oxu_dbg(oxu, "port %d high speed\n", index + 1); + + return port_status; +} + +static void ehci_hub_descriptor(struct oxu_hcd *oxu, + struct usb_hub_descriptor *desc) +{ + int ports = HCS_N_PORTS(oxu->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* oxu 1.0, 2.3.9 says 20ms max */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + if (HCS_PPC(oxu->hcs_params)) + temp |= 0x0001; /* per-port power control */ + else + temp |= 0x0002; /* no power switching */ + desc->wHubCharacteristics = (__force __u16)cpu_to_le16(temp); +} + + +/* Allocate an OXU210HP on-chip memory data buffer + * + * An on-chip memory data buffer is required for each OXU210HP USB transfer. + * Each transfer descriptor has one or more on-chip memory data buffers. + * + * Data buffers are allocated from a fix sized pool of data blocks. + * To minimise fragmentation and give reasonable memory utlisation, + * data buffers are allocated with sizes the power of 2 multiples of + * the block size, starting on an address a multiple of the allocated size. + * + * FIXME: callers of this function require a buffer to be allocated for + * len=0. This is a waste of on-chip memory and should be fix. Then this + * function should be changed to not allocate a buffer for len=0. + */ +static int oxu_buf_alloc(struct oxu_hcd *oxu, struct ehci_qtd *qtd, int len) +{ + int n_blocks; /* minium blocks needed to hold len */ + int a_blocks; /* blocks allocated */ + int i, j; + + /* Don't allocte bigger than supported */ + if (len > BUFFER_SIZE * BUFFER_NUM) { + oxu_err(oxu, "buffer too big (%d)\n", len); + return -ENOMEM; + } + + spin_lock(&oxu->mem_lock); + + /* Number of blocks needed to hold len */ + n_blocks = (len + BUFFER_SIZE - 1) / BUFFER_SIZE; + + /* Round the number of blocks up to the power of 2 */ + for (a_blocks = 1; a_blocks < n_blocks; a_blocks <<= 1) + ; + + /* Find a suitable available data buffer */ + for (i = 0; i < BUFFER_NUM; + i += max(a_blocks, (int)oxu->db_used[i])) { + + /* Check all the required blocks are available */ + for (j = 0; j < a_blocks; j++) + if (oxu->db_used[i + j]) + break; + + if (j != a_blocks) + continue; + + /* Allocate blocks found! */ + qtd->buffer = (void *) &oxu->mem->db_pool[i]; + qtd->buffer_dma = virt_to_phys(qtd->buffer); + + qtd->qtd_buffer_len = BUFFER_SIZE * a_blocks; + oxu->db_used[i] = a_blocks; + + spin_unlock(&oxu->mem_lock); + + return 0; + } + + /* Failed */ + + spin_unlock(&oxu->mem_lock); + + return -ENOMEM; +} + +static void oxu_buf_free(struct oxu_hcd *oxu, struct ehci_qtd *qtd) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = (qtd->buffer - (void *) &oxu->mem->db_pool[0]) + / BUFFER_SIZE; + oxu->db_used[index] = 0; + qtd->qtd_buffer_len = 0; + qtd->buffer_dma = 0; + qtd->buffer = NULL; + + spin_unlock(&oxu->mem_lock); +} + +static inline void ehci_qtd_init(struct ehci_qtd *qtd, dma_addr_t dma) +{ + memset(qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_token = cpu_to_le32(QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD(&qtd->qtd_list); +} + +static inline void oxu_qtd_free(struct oxu_hcd *oxu, struct ehci_qtd *qtd) +{ + int index; + + if (qtd->buffer) + oxu_buf_free(oxu, qtd); + + spin_lock(&oxu->mem_lock); + + index = qtd - &oxu->mem->qtd_pool[0]; + oxu->qtd_used[index] = 0; + + spin_unlock(&oxu->mem_lock); +} + +static struct ehci_qtd *ehci_qtd_alloc(struct oxu_hcd *oxu) +{ + int i; + struct ehci_qtd *qtd = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < QTD_NUM; i++) + if (!oxu->qtd_used[i]) + break; + + if (i < QTD_NUM) { + qtd = (struct ehci_qtd *) &oxu->mem->qtd_pool[i]; + memset(qtd, 0, sizeof *qtd); + + qtd->hw_token = cpu_to_le32(QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD(&qtd->qtd_list); + + qtd->qtd_dma = virt_to_phys(qtd); + + oxu->qtd_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return qtd; +} + +static void oxu_qh_free(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = qh - &oxu->mem->qh_pool[0]; + oxu->qh_used[index] = 0; + + spin_unlock(&oxu->mem_lock); +} + +static void qh_destroy(struct kref *kref) +{ + struct ehci_qh *qh = container_of(kref, struct ehci_qh, kref); + struct oxu_hcd *oxu = qh->oxu; + + /* clean qtds first, and know this is not linked */ + if (!list_empty(&qh->qtd_list) || qh->qh_next.ptr) { + oxu_dbg(oxu, "unused qh not empty!\n"); + BUG(); + } + if (qh->dummy) + oxu_qtd_free(oxu, qh->dummy); + oxu_qh_free(oxu, qh); +} + +static struct ehci_qh *oxu_qh_alloc(struct oxu_hcd *oxu) +{ + int i; + struct ehci_qh *qh = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < QHEAD_NUM; i++) + if (!oxu->qh_used[i]) + break; + + if (i < QHEAD_NUM) { + qh = (struct ehci_qh *) &oxu->mem->qh_pool[i]; + memset(qh, 0, sizeof *qh); + + kref_init(&qh->kref); + qh->oxu = oxu; + qh->qh_dma = virt_to_phys(qh); + INIT_LIST_HEAD(&qh->qtd_list); + + /* dummy td enables safe urb queuing */ + qh->dummy = ehci_qtd_alloc(oxu); + if (qh->dummy == NULL) { + oxu_dbg(oxu, "no dummy td\n"); + oxu->qh_used[i] = 0; + qh = NULL; + goto unlock; + } + + oxu->qh_used[i] = 1; + } +unlock: + spin_unlock(&oxu->mem_lock); + + return qh; +} + +/* to share a qh (cpu threads, or hc) */ +static inline struct ehci_qh *qh_get(struct ehci_qh *qh) +{ + kref_get(&qh->kref); + return qh; +} + +static inline void qh_put(struct ehci_qh *qh) +{ + kref_put(&qh->kref, qh_destroy); +} + +static void oxu_murb_free(struct oxu_hcd *oxu, struct oxu_murb *murb) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = murb - &oxu->murb_pool[0]; + oxu->murb_used[index] = 0; + + spin_unlock(&oxu->mem_lock); +} + +static struct oxu_murb *oxu_murb_alloc(struct oxu_hcd *oxu) + +{ + int i; + struct oxu_murb *murb = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < MURB_NUM; i++) + if (!oxu->murb_used[i]) + break; + + if (i < MURB_NUM) { + murb = &(oxu->murb_pool)[i]; + + oxu->murb_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return murb; +} + +/* The queue heads and transfer descriptors are managed from pools tied + * to each of the "per device" structures. + * This is the initialisation and cleanup code. + */ +static void ehci_mem_cleanup(struct oxu_hcd *oxu) +{ + kfree(oxu->murb_pool); + oxu->murb_pool = NULL; + + if (oxu->async) + qh_put(oxu->async); + oxu->async = NULL; + + del_timer(&oxu->urb_timer); + + oxu->periodic = NULL; + + /* shadow periodic table */ + kfree(oxu->pshadow); + oxu->pshadow = NULL; +} + +/* Remember to add cleanup code (above) if you add anything here. + */ +static int ehci_mem_init(struct oxu_hcd *oxu, gfp_t flags) +{ + int i; + + for (i = 0; i < oxu->periodic_size; i++) + oxu->mem->frame_list[i] = EHCI_LIST_END; + for (i = 0; i < QHEAD_NUM; i++) + oxu->qh_used[i] = 0; + for (i = 0; i < QTD_NUM; i++) + oxu->qtd_used[i] = 0; + + oxu->murb_pool = kcalloc(MURB_NUM, sizeof(struct oxu_murb), flags); + if (!oxu->murb_pool) + goto fail; + + for (i = 0; i < MURB_NUM; i++) + oxu->murb_used[i] = 0; + + oxu->async = oxu_qh_alloc(oxu); + if (!oxu->async) + goto fail; + + oxu->periodic = (__le32 *) &oxu->mem->frame_list; + oxu->periodic_dma = virt_to_phys(oxu->periodic); + + for (i = 0; i < oxu->periodic_size; i++) + oxu->periodic[i] = EHCI_LIST_END; + + /* software shadow of hardware table */ + oxu->pshadow = kcalloc(oxu->periodic_size, sizeof(void *), flags); + if (oxu->pshadow != NULL) + return 0; + +fail: + oxu_dbg(oxu, "couldn't init memory\n"); + ehci_mem_cleanup(oxu); + return -ENOMEM; +} + +/* Fill a qtd, returning how much of the buffer we were able to queue up. + */ +static int qtd_fill(struct ehci_qtd *qtd, dma_addr_t buf, size_t len, + int token, int maxpacket) +{ + int i, count; + u64 addr = buf; + + /* one buffer entry per 4K ... first might be short or unaligned */ + qtd->hw_buf[0] = cpu_to_le32((u32)addr); + qtd->hw_buf_hi[0] = cpu_to_le32((u32)(addr >> 32)); + count = 0x1000 - (buf & 0x0fff); /* rest of that page */ + if (likely(len < count)) /* ... iff needed */ + count = len; + else { + buf += 0x1000; + buf &= ~0x0fff; + + /* per-qtd limit: from 16K to 20K (best alignment) */ + for (i = 1; count < len && i < 5; i++) { + addr = buf; + qtd->hw_buf[i] = cpu_to_le32((u32)addr); + qtd->hw_buf_hi[i] = cpu_to_le32((u32)(addr >> 32)); + buf += 0x1000; + if ((count + 0x1000) < len) + count += 0x1000; + else + count = len; + } + + /* short packets may only terminate transfers */ + if (count != len) + count -= (count % maxpacket); + } + qtd->hw_token = cpu_to_le32((count << 16) | token); + qtd->length = count; + + return count; +} + +static inline void qh_update(struct oxu_hcd *oxu, + struct ehci_qh *qh, struct ehci_qtd *qtd) +{ + /* writes to an active overlay are unsafe */ + BUG_ON(qh->qh_state != QH_STATE_IDLE); + + qh->hw_qtd_next = QTD_NEXT(qtd->qtd_dma); + qh->hw_alt_next = EHCI_LIST_END; + + /* Except for control endpoints, we make hardware maintain data + * toggle (like OHCI) ... here (re)initialize the toggle in the QH, + * and set the pseudo-toggle in udev. Only usb_clear_halt() will + * ever clear it. + */ + if (!(qh->hw_info1 & cpu_to_le32(1 << 14))) { + unsigned is_out, epnum; + + is_out = !(qtd->hw_token & cpu_to_le32(1 << 8)); + epnum = (le32_to_cpup(&qh->hw_info1) >> 8) & 0x0f; + if (unlikely(!usb_gettoggle(qh->dev, epnum, is_out))) { + qh->hw_token &= ~cpu_to_le32(QTD_TOGGLE); + usb_settoggle(qh->dev, epnum, is_out, 1); + } + } + + /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ + wmb(); + qh->hw_token &= cpu_to_le32(QTD_TOGGLE | QTD_STS_PING); +} + +/* If it weren't for a common silicon quirk (writing the dummy into the qh + * overlay, so qh->hw_token wrongly becomes inactive/halted), only fault + * recovery (including urb dequeue) would need software changes to a QH... + */ +static void qh_refresh(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + struct ehci_qtd *qtd; + + if (list_empty(&qh->qtd_list)) + qtd = qh->dummy; + else { + qtd = list_entry(qh->qtd_list.next, + struct ehci_qtd, qtd_list); + /* first qtd may already be partially processed */ + if (cpu_to_le32(qtd->qtd_dma) == qh->hw_current) + qtd = NULL; + } + + if (qtd) + qh_update(oxu, qh, qtd); +} + +static void qtd_copy_status(struct oxu_hcd *oxu, struct urb *urb, + size_t length, u32 token) +{ + /* count IN/OUT bytes, not SETUP (even short packets) */ + if (likely(QTD_PID(token) != 2)) + urb->actual_length += length - QTD_LENGTH(token); + + /* don't modify error codes */ + if (unlikely(urb->status != -EINPROGRESS)) + return; + + /* force cleanup after short read; not always an error */ + if (unlikely(IS_SHORT_READ(token))) + urb->status = -EREMOTEIO; + + /* serious "can't proceed" faults reported by the hardware */ + if (token & QTD_STS_HALT) { + if (token & QTD_STS_BABBLE) { + /* FIXME "must" disable babbling device's port too */ + urb->status = -EOVERFLOW; + } else if (token & QTD_STS_MMF) { + /* fs/ls interrupt xfer missed the complete-split */ + urb->status = -EPROTO; + } else if (token & QTD_STS_DBE) { + urb->status = (QTD_PID(token) == 1) /* IN ? */ + ? -ENOSR /* hc couldn't read data */ + : -ECOMM; /* hc couldn't write data */ + } else if (token & QTD_STS_XACT) { + /* timeout, bad crc, wrong PID, etc; retried */ + if (QTD_CERR(token)) + urb->status = -EPIPE; + else { + oxu_dbg(oxu, "devpath %s ep%d%s 3strikes\n", + urb->dev->devpath, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out"); + urb->status = -EPROTO; + } + /* CERR nonzero + no errors + halt --> stall */ + } else if (QTD_CERR(token)) + urb->status = -EPIPE; + else /* unknown */ + urb->status = -EPROTO; + + oxu_vdbg(oxu, "dev%d ep%d%s qtd token %08x --> status %d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + token, urb->status); + } +} + +static void ehci_urb_done(struct oxu_hcd *oxu, struct urb *urb) +__releases(oxu->lock) +__acquires(oxu->lock) +{ + if (likely(urb->hcpriv != NULL)) { + struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; + + /* S-mask in a QH means it's an interrupt urb */ + if ((qh->hw_info2 & cpu_to_le32(QH_SMASK)) != 0) { + + /* ... update hc-wide periodic stats (for usbfs) */ + oxu_to_hcd(oxu)->self.bandwidth_int_reqs--; + } + qh_put(qh); + } + + urb->hcpriv = NULL; + switch (urb->status) { + case -EINPROGRESS: /* success */ + urb->status = 0; + default: /* fault */ + break; + case -EREMOTEIO: /* fault or normal */ + if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) + urb->status = 0; + break; + case -ECONNRESET: /* canceled */ + case -ENOENT: + break; + } + +#ifdef OXU_URB_TRACE + oxu_dbg(oxu, "%s %s urb %p ep%d%s status %d len %d/%d\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + urb->status, + urb->actual_length, urb->transfer_buffer_length); +#endif + + /* complete() can reenter this HCD */ + spin_unlock(&oxu->lock); + usb_hcd_giveback_urb(oxu_to_hcd(oxu), urb, urb->status); + spin_lock(&oxu->lock); +} + +static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh); +static void unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh); + +static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh); +static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh); + +#define HALT_BIT cpu_to_le32(QTD_STS_HALT) + +/* Process and free completed qtds for a qh, returning URBs to drivers. + * Chases up to qh->hw_current. Returns number of completions called, + * indicating how much "real" work we did. + */ +static unsigned qh_completions(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + struct ehci_qtd *last = NULL, *end = qh->dummy; + struct list_head *entry, *tmp; + int stopped; + unsigned count = 0; + int do_status = 0; + u8 state; + struct oxu_murb *murb = NULL; + + if (unlikely(list_empty(&qh->qtd_list))) + return count; + + /* completions (or tasks on other cpus) must never clobber HALT + * till we've gone through and cleaned everything up, even when + * they add urbs to this qh's queue or mark them for unlinking. + * + * NOTE: unlinking expects to be done in queue order. + */ + state = qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + stopped = (state == QH_STATE_IDLE); + + /* remove de-activated QTDs from front of queue. + * after faults (including short reads), cleanup this urb + * then let the queue advance. + * if queue is stopped, handles unlinks. + */ + list_for_each_safe(entry, tmp, &qh->qtd_list) { + struct ehci_qtd *qtd; + struct urb *urb; + u32 token = 0; + + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + urb = qtd->urb; + + /* Clean up any state from previous QTD ...*/ + if (last) { + if (likely(last->urb != urb)) { + if (last->urb->complete == NULL) { + murb = (struct oxu_murb *) last->urb; + last->urb = murb->main; + if (murb->last) { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_murb_free(oxu, murb); + } else { + ehci_urb_done(oxu, last->urb); + count++; + } + } + oxu_qtd_free(oxu, last); + last = NULL; + } + + /* ignore urbs submitted during completions we reported */ + if (qtd == end) + break; + + /* hardware copies qtd out of qh overlay */ + rmb(); + token = le32_to_cpu(qtd->hw_token); + + /* always clean up qtds the hc de-activated */ + if ((token & QTD_STS_ACTIVE) == 0) { + + if ((token & QTD_STS_HALT) != 0) { + stopped = 1; + + /* magic dummy for some short reads; qh won't advance. + * that silicon quirk can kick in with this dummy too. + */ + } else if (IS_SHORT_READ(token) && + !(qtd->hw_alt_next & EHCI_LIST_END)) { + stopped = 1; + goto halt; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely(!stopped && + HC_IS_RUNNING(oxu_to_hcd(oxu)->state))) { + break; + + } else { + stopped = 1; + + if (unlikely(!HC_IS_RUNNING(oxu_to_hcd(oxu)->state))) + urb->status = -ESHUTDOWN; + + /* ignore active urbs unless some previous qtd + * for the urb faulted (including short read) or + * its urb was canceled. we may patch qh or qtds. + */ + if (likely(urb->status == -EINPROGRESS)) + continue; + + /* issue status after short control reads */ + if (unlikely(do_status != 0) + && QTD_PID(token) == 0 /* OUT */) { + do_status = 0; + continue; + } + + /* token in overlay may be most current */ + if (state == QH_STATE_IDLE + && cpu_to_le32(qtd->qtd_dma) + == qh->hw_current) + token = le32_to_cpu(qh->hw_token); + + /* force halt for unlinked or blocked qh, so we'll + * patch the qh later and so that completions can't + * activate it while we "know" it's stopped. + */ + if ((HALT_BIT & qh->hw_token) == 0) { +halt: + qh->hw_token |= HALT_BIT; + wmb(); + } + } + + /* Remove it from the queue */ + qtd_copy_status(oxu, urb->complete ? + urb : ((struct oxu_murb *) urb)->main, + qtd->length, token); + if ((usb_pipein(qtd->urb->pipe)) && + (NULL != qtd->transfer_buffer)) + memcpy(qtd->transfer_buffer, qtd->buffer, qtd->length); + do_status = (urb->status == -EREMOTEIO) + && usb_pipecontrol(urb->pipe); + + if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { + last = list_entry(qtd->qtd_list.prev, + struct ehci_qtd, qtd_list); + last->hw_next = qtd->hw_next; + } + list_del(&qtd->qtd_list); + last = qtd; + } + + /* last urb's completion might still need calling */ + if (likely(last != NULL)) { + if (last->urb->complete == NULL) { + murb = (struct oxu_murb *) last->urb; + last->urb = murb->main; + if (murb->last) { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_murb_free(oxu, murb); + } else { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_qtd_free(oxu, last); + } + + /* restore original state; caller must unlink or relink */ + qh->qh_state = state; + + /* be sure the hardware's done with the qh before refreshing + * it after fault cleanup, or recovering from silicon wrongly + * overlaying the dummy qtd (which reduces DMA chatter). + */ + if (stopped != 0 || qh->hw_qtd_next == EHCI_LIST_END) { + switch (state) { + case QH_STATE_IDLE: + qh_refresh(oxu, qh); + break; + case QH_STATE_LINKED: + /* should be rare for periodic transfers, + * except maybe high bandwidth ... + */ + if ((cpu_to_le32(QH_SMASK) + & qh->hw_info2) != 0) { + intr_deschedule(oxu, qh); + (void) qh_schedule(oxu, qh); + } else + unlink_async(oxu, qh); + break; + /* otherwise, unlink already started */ + } + } + + return count; +} + +/* High bandwidth multiplier, as encoded in highspeed endpoint descriptors */ +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) +/* ... and packet size, for any kind of endpoint descriptor */ +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/* Reverse of qh_urb_transaction: free a list of TDs. + * used for cleanup after errors, before HC sees an URB's TDs. + */ +static void qtd_list_free(struct oxu_hcd *oxu, + struct urb *urb, struct list_head *qtd_list) +{ + struct list_head *entry, *temp; + + list_for_each_safe(entry, temp, qtd_list) { + struct ehci_qtd *qtd; + + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + list_del(&qtd->qtd_list); + oxu_qtd_free(oxu, qtd); + } +} + +/* Create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head *qh_urb_transaction(struct oxu_hcd *oxu, + struct urb *urb, + struct list_head *head, + gfp_t flags) +{ + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, maxpacket; + int is_input; + u32 token; + void *transfer_buf = NULL; + int ret; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + return NULL; + list_add_tail(&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + len = urb->transfer_buffer_length; + is_input = usb_pipein(urb->pipe); + if (!urb->transfer_buffer && urb->transfer_buffer_length && is_input) + urb->transfer_buffer = phys_to_virt(urb->transfer_dma); + + if (usb_pipecontrol(urb->pipe)) { + /* SETUP pid */ + ret = oxu_buf_alloc(oxu, qtd, sizeof(struct usb_ctrlrequest)); + if (ret) + goto cleanup; + + qtd_fill(qtd, qtd->buffer_dma, sizeof(struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + memcpy(qtd->buffer, qtd->urb->setup_packet, + sizeof(struct usb_ctrlrequest)); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (len == 0) + token |= (1 /* "in" */ << 8); + } + + /* + * Data transfer stage: buffer setup + */ + + ret = oxu_buf_alloc(oxu, qtd, len); + if (ret) + goto cleanup; + + buf = qtd->buffer_dma; + transfer_buf = urb->transfer_buffer; + + if (!is_input) + memcpy(qtd->buffer, qtd->urb->transfer_buffer, len); + + if (is_input) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input)); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + this_qtd_len = qtd_fill(qtd, buf, len, token, maxpacket); + qtd->transfer_buffer = transfer_buf; + len -= this_qtd_len; + buf += this_qtd_len; + transfer_buf += this_qtd_len; + if (is_input) + qtd->hw_alt_next = oxu->async->hw_alt_next; + + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely(len <= 0)) + break; + + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + if (likely(len > 0)) { + ret = oxu_buf_alloc(oxu, qtd, len); + if (ret) + goto cleanup; + } + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + } + + /* unless the bulk/interrupt caller wants a chance to clean + * up after short reads, hc should advance qh past this urb + */ + if (likely((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 + || usb_pipecontrol(urb->pipe))) + qtd->hw_alt_next = EHCI_LIST_END; + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (likely(urb->transfer_buffer_length != 0)) { + int one_more = 0; + + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipebulk(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill(qtd, 0, 0, token, 0); + } + } + + /* by default, enable interrupt on urb completion */ + qtd->hw_token |= cpu_to_le32(QTD_IOC); + return head; + +cleanup: + qtd_list_free(oxu, urb, head); + return NULL; +} + +/* Each QH holds a qtd list; a QH is used for everything except iso. + * + * For interrupt urbs, the scheduler must set the microframe scheduling + * mask(s) each time the QH gets scheduled. For highspeed, that's + * just one microframe in the s-mask. For split interrupt transactions + * there are additional complications: c-mask, maybe FSTNs. + */ +static struct ehci_qh *qh_make(struct oxu_hcd *oxu, + struct urb *urb, gfp_t flags) +{ + struct ehci_qh *qh = oxu_qh_alloc(oxu); + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + + if (!qh) + return qh; + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint(urb->pipe) << 8; + info1 |= usb_pipedevice(urb->pipe) << 0; + + is_input = usb_pipein(urb->pipe); + type = usb_pipetype(urb->pipe); + maxp = usb_maxpacket(urb->dev, urb->pipe, !is_input); + + /* Compute interrupt scheduling parameters just once, and save. + * - allowing for high bandwidth, how many nsec/uframe are used? + * - split transactions need a second CSPLIT uframe; same question + * - splits also need a schedule gap (for full/low speed I/O) + * - qh has a polling interval + * + * For control/bulk requests, the HC or TT handles these. + */ + if (type == PIPE_INTERRUPT) { + qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, + is_input, 0, + hb_mult(maxp) * max_packet(maxp))); + qh->start = NO_FRAME; + + if (urb->dev->speed == USB_SPEED_HIGH) { + qh->c_usecs = 0; + qh->gap_uf = 0; + + qh->period = urb->interval >> 3; + if (qh->period == 0 && urb->interval != 1) { + /* NOTE interval 2 or 4 uframes could work. + * But interval 1 scheduling is simpler, and + * includes high bandwidth. + */ + oxu_dbg(oxu, "intr period %d uframes, NYET!\n", + urb->interval); + goto done; + } + } else { + struct usb_tt *tt = urb->dev->tt; + int think_time; + + /* gap is f(FS/LS transfer times) */ + qh->gap_uf = 1 + usb_calc_bus_time(urb->dev->speed, + is_input, 0, maxp) / (125 * 1000); + + /* FIXME this just approximates SPLIT/CSPLIT times */ + if (is_input) { /* SPLIT, gap, CSPLIT+DATA */ + qh->c_usecs = qh->usecs + HS_USECS(0); + qh->usecs = HS_USECS(1); + } else { /* SPLIT+DATA, gap, CSPLIT */ + qh->usecs += HS_USECS(1); + qh->c_usecs = HS_USECS(0); + } + + think_time = tt ? tt->think_time : 0; + qh->tt_usecs = NS_TO_US(think_time + + usb_calc_bus_time(urb->dev->speed, + is_input, 0, max_packet(maxp))); + qh->period = urb->interval; + } + } + + /* support for tt scheduling, and access to toggles */ + qh->dev = urb->dev; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= (1 << 12); /* EPS "low" */ + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) + info1 |= (EHCI_TUNE_RL_TT << 28); + if (type == PIPE_CONTROL) { + info1 |= (1 << 27); /* for TT */ + info1 |= 1 << 14; /* toggle from qtd */ + } + info1 |= maxp << 16; + + info2 |= (EHCI_TUNE_MULT_TT << 30); + info2 |= urb->dev->ttport << 23; + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ + + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= (2 << 12); /* EPS "high" */ + if (type == PIPE_CONTROL) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= 1 << 14; /* toggle from qtd */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 512 << 16; /* usb2 fixed maxpacket */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= max_packet(maxp) << 16; + info2 |= hb_mult(maxp) << 30; + } + break; + default: + oxu_dbg(oxu, "bogus dev %p speed %d\n", urb->dev, urb->dev->speed); +done: + qh_put(qh); + return NULL; + } + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ + + /* init as live, toggle clear, advance to dummy */ + qh->qh_state = QH_STATE_IDLE; + qh->hw_info1 = cpu_to_le32(info1); + qh->hw_info2 = cpu_to_le32(info2); + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), !is_input, 1); + qh_refresh(oxu, qh); + return qh; +} + +/* Move qh (and its qtds) onto async queue; maybe enable queue. + */ +static void qh_link_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + __le32 dma = QH_NEXT(qh->qh_dma); + struct ehci_qh *head; + + /* (re)start the async schedule? */ + head = oxu->async; + timer_action_done(oxu, TIMER_ASYNC_OFF); + if (!head->qh_next.qh) { + u32 cmd = readl(&oxu->regs->command); + + if (!(cmd & CMD_ASE)) { + /* in case a clear of CMD_ASE didn't take yet */ + (void)handshake(oxu, &oxu->regs->status, + STS_ASS, 0, 150); + cmd |= CMD_ASE | CMD_RUN; + writel(cmd, &oxu->regs->command); + oxu_to_hcd(oxu)->state = HC_STATE_RUNNING; + /* posted write need not be known to HC yet ... */ + } + } + + /* clear halt and/or toggle; and maybe recover from silicon quirk */ + if (qh->qh_state == QH_STATE_IDLE) + qh_refresh(oxu, qh); + + /* splice right after start */ + qh->qh_next = head->qh_next; + qh->hw_next = head->hw_next; + wmb(); + + head->qh_next.qh = qh; + head->hw_next = dma; + + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ +} + +#define QH_ADDR_MASK cpu_to_le32(0x7f) + +/* + * For control/bulk/interrupt, return QH with these TDs appended. + * Allocates and initializes the QH if necessary. + * Returns null if it can't allocate a QH it needs to. + * If the QH has TDs (urbs) already, that's great. + */ +static struct ehci_qh *qh_append_tds(struct oxu_hcd *oxu, + struct urb *urb, struct list_head *qtd_list, + int epnum, void **ptr) +{ + struct ehci_qh *qh = NULL; + + qh = (struct ehci_qh *) *ptr; + if (unlikely(qh == NULL)) { + /* can't sleep here, we have oxu->lock... */ + qh = qh_make(oxu, urb, GFP_ATOMIC); + *ptr = qh; + } + if (likely(qh != NULL)) { + struct ehci_qtd *qtd; + + if (unlikely(list_empty(qtd_list))) + qtd = NULL; + else + qtd = list_entry(qtd_list->next, struct ehci_qtd, + qtd_list); + + /* control qh may need patching ... */ + if (unlikely(epnum == 0)) { + + /* usb_reset_device() briefly reverts to address 0 */ + if (usb_pipedevice(urb->pipe) == 0) + qh->hw_info1 &= ~QH_ADDR_MASK; + } + + /* just one way to queue requests: swap with the dummy qtd. + * only hc or qh_refresh() ever modify the overlay. + */ + if (likely(qtd != NULL)) { + struct ehci_qtd *dummy; + dma_addr_t dma; + __le32 token; + + /* to avoid racing the HC, use the dummy td instead of + * the first td of our list (becomes new dummy). both + * tds stay deactivated until we're done, when the + * HC is allowed to fetch the old dummy (4.10.2). + */ + token = qtd->hw_token; + qtd->hw_token = HALT_BIT; + wmb(); + dummy = qh->dummy; + + dma = dummy->qtd_dma; + *dummy = *qtd; + dummy->qtd_dma = dma; + + list_del(&qtd->qtd_list); + list_add(&dummy->qtd_list, qtd_list); + list_splice(qtd_list, qh->qtd_list.prev); + + ehci_qtd_init(qtd, qtd->qtd_dma); + qh->dummy = qtd; + + /* hc must see the new dummy at list end */ + dma = qtd->qtd_dma; + qtd = list_entry(qh->qtd_list.prev, + struct ehci_qtd, qtd_list); + qtd->hw_next = QTD_NEXT(dma); + + /* let the hc process these next qtds */ + dummy->hw_token = (token & ~(0x80)); + wmb(); + dummy->hw_token = token; + + urb->hcpriv = qh_get(qh); + } + } + return qh; +} + +static int submit_async(struct oxu_hcd *oxu, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + struct ehci_qtd *qtd; + int epnum; + unsigned long flags; + struct ehci_qh *qh = NULL; + int rc = 0; + + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + epnum = urb->ep->desc.bEndpointAddress; + +#ifdef OXU_URB_TRACE + oxu_dbg(oxu, "%s %s urb %p ep%d%s len %d, qtd %p [qh %p]\n", + __func__, urb->dev->devpath, urb, + epnum & 0x0f, (epnum & USB_DIR_IN) ? "in" : "out", + urb->transfer_buffer_length, + qtd, urb->ep->hcpriv); +#endif + + spin_lock_irqsave(&oxu->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(oxu_to_hcd(oxu)))) { + rc = -ESHUTDOWN; + goto done; + } + + qh = qh_append_tds(oxu, urb, qtd_list, epnum, &urb->ep->hcpriv); + if (unlikely(qh == NULL)) { + rc = -ENOMEM; + goto done; + } + + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + if (likely(qh->qh_state == QH_STATE_IDLE)) + qh_link_async(oxu, qh_get(qh)); +done: + spin_unlock_irqrestore(&oxu->lock, flags); + if (unlikely(qh == NULL)) + qtd_list_free(oxu, urb, qtd_list); + return rc; +} + +/* The async qh for the qtds being reclaimed are now unlinked from the HC */ + +static void end_unlink_async(struct oxu_hcd *oxu) +{ + struct ehci_qh *qh = oxu->reclaim; + struct ehci_qh *next; + + timer_action_done(oxu, TIMER_IAA_WATCHDOG); + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + qh_put(qh); /* refcount from reclaim */ + + /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ + next = qh->reclaim; + oxu->reclaim = next; + oxu->reclaim_ready = 0; + qh->reclaim = NULL; + + qh_completions(oxu, qh); + + if (!list_empty(&qh->qtd_list) + && HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + qh_link_async(oxu, qh); + else { + qh_put(qh); /* refcount from async list */ + + /* it's not free to turn the async schedule on/off; leave it + * active but idle for a while once it empties. + */ + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state) + && oxu->async->qh_next.qh == NULL) + timer_action(oxu, TIMER_ASYNC_OFF); + } + + if (next) { + oxu->reclaim = NULL; + start_unlink_async(oxu, next); + } +} + +/* makes sure the async qh will become idle */ +/* caller must own oxu->lock */ + +static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int cmd = readl(&oxu->regs->command); + struct ehci_qh *prev; + +#ifdef DEBUG + assert_spin_locked(&oxu->lock); + if (oxu->reclaim || (qh->qh_state != QH_STATE_LINKED + && qh->qh_state != QH_STATE_UNLINK_WAIT)) + BUG(); +#endif + + /* stop async schedule right now? */ + if (unlikely(qh == oxu->async)) { + /* can't get here without STS_ASS set */ + if (oxu_to_hcd(oxu)->state != HC_STATE_HALT + && !oxu->reclaim) { + /* ... and CMD_IAAD clear */ + writel(cmd & ~CMD_ASE, &oxu->regs->command); + wmb(); + /* handshake later, if we need to */ + timer_action_done(oxu, TIMER_ASYNC_OFF); + } + return; + } + + qh->qh_state = QH_STATE_UNLINK; + oxu->reclaim = qh = qh_get(qh); + + prev = oxu->async; + while (prev->qh_next.qh != qh) + prev = prev->qh_next.qh; + + prev->hw_next = qh->hw_next; + prev->qh_next = qh->qh_next; + wmb(); + + if (unlikely(oxu_to_hcd(oxu)->state == HC_STATE_HALT)) { + /* if (unlikely(qh->reclaim != 0)) + * this will recurse, probably not much + */ + end_unlink_async(oxu); + return; + } + + oxu->reclaim_ready = 0; + cmd |= CMD_IAAD; + writel(cmd, &oxu->regs->command); + (void) readl(&oxu->regs->command); + timer_action(oxu, TIMER_IAA_WATCHDOG); +} + +static void scan_async(struct oxu_hcd *oxu) +{ + struct ehci_qh *qh; + enum ehci_timer_action action = TIMER_IO_WATCHDOG; + + if (!++(oxu->stamp)) + oxu->stamp++; + timer_action_done(oxu, TIMER_ASYNC_SHRINK); +rescan: + qh = oxu->async->qh_next.qh; + if (likely(qh != NULL)) { + do { + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list) + && qh->stamp != oxu->stamp) { + int temp; + + /* unlinks could happen here; completion + * reporting drops the lock. rescan using + * the latest schedule, but don't rescan + * qhs we already finished (no looping). + */ + qh = qh_get(qh); + qh->stamp = oxu->stamp; + temp = qh_completions(oxu, qh); + qh_put(qh); + if (temp != 0) + goto rescan; + } + + /* unlink idle entries, reducing HC PCI usage as well + * as HCD schedule-scanning costs. delay for any qh + * we just scanned, there's a not-unusual case that it + * doesn't stay idle for long. + * (plus, avoids some kind of re-activation race.) + */ + if (list_empty(&qh->qtd_list)) { + if (qh->stamp == oxu->stamp) + action = TIMER_ASYNC_SHRINK; + else if (!oxu->reclaim + && qh->qh_state == QH_STATE_LINKED) + start_unlink_async(oxu, qh); + } + + qh = qh->qh_next.qh; + } while (qh); + } + if (action == TIMER_ASYNC_SHRINK) + timer_action(oxu, TIMER_ASYNC_SHRINK); +} + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd/sitd + * @tag: hardware tag for type of this record + */ +static union ehci_shadow *periodic_next_shadow(union ehci_shadow *periodic, + __le32 tag) +{ + switch (tag) { + default: + case Q_TYPE_QH: + return &periodic->qh->qh_next; + } +} + +/* caller must hold oxu->lock */ +static void periodic_unlink(struct oxu_hcd *oxu, unsigned frame, void *ptr) +{ + union ehci_shadow *prev_p = &oxu->pshadow[frame]; + __le32 *hw_p = &oxu->periodic[frame]; + union ehci_shadow here = *prev_p; + + /* find predecessor of "ptr"; hw and shadow lists are in sync */ + while (here.ptr && here.ptr != ptr) { + prev_p = periodic_next_shadow(prev_p, Q_NEXT_TYPE(*hw_p)); + hw_p = here.hw_next; + here = *prev_p; + } + /* an interrupt entry (at list end) could have been shared */ + if (!here.ptr) + return; + + /* update shadow and hardware lists ... the old "next" pointers + * from ptr may still be in use, the caller updates them. + */ + *prev_p = *periodic_next_shadow(&here, Q_NEXT_TYPE(*hw_p)); + *hw_p = *here.hw_next; +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short periodic_usecs(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe) +{ + __le32 *hw_p = &oxu->periodic[frame]; + union ehci_shadow *q = &oxu->pshadow[frame]; + unsigned usecs = 0; + + while (q->ptr) { + switch (Q_NEXT_TYPE(*hw_p)) { + case Q_TYPE_QH: + default: + /* is it in the S-mask? */ + if (q->qh->hw_info2 & cpu_to_le32(1 << uframe)) + usecs += q->qh->usecs; + /* ... or C-mask? */ + if (q->qh->hw_info2 & cpu_to_le32(1 << (8 + uframe))) + usecs += q->qh->c_usecs; + hw_p = &q->qh->hw_next; + q = &q->qh->qh_next; + break; + } + } +#ifdef DEBUG + if (usecs > 100) + oxu_err(oxu, "uframe %d sched overrun: %d usecs\n", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +static int enable_periodic(struct oxu_hcd *oxu) +{ + u32 cmd; + int status; + + /* did clearing PSE did take effect yet? + * takes effect only at frame boundaries... + */ + status = handshake(oxu, &oxu->regs->status, STS_PSS, 0, 9 * 125); + if (status != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + usb_hc_died(oxu_to_hcd(oxu)); + return status; + } + + cmd = readl(&oxu->regs->command) | CMD_PSE; + writel(cmd, &oxu->regs->command); + /* posted write ... PSS happens later */ + oxu_to_hcd(oxu)->state = HC_STATE_RUNNING; + + /* make sure ehci_work scans these */ + oxu->next_uframe = readl(&oxu->regs->frame_index) + % (oxu->periodic_size << 3); + return 0; +} + +static int disable_periodic(struct oxu_hcd *oxu) +{ + u32 cmd; + int status; + + /* did setting PSE not take effect yet? + * takes effect only at frame boundaries... + */ + status = handshake(oxu, &oxu->regs->status, STS_PSS, STS_PSS, 9 * 125); + if (status != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + usb_hc_died(oxu_to_hcd(oxu)); + return status; + } + + cmd = readl(&oxu->regs->command) & ~CMD_PSE; + writel(cmd, &oxu->regs->command); + /* posted write ... */ + + oxu->next_uframe = -1; + return 0; +} + +/* periodic schedule slots have iso tds (normal or split) first, then a + * sparse tree for active interrupt transfers. + * + * this just links in a qh; caller guarantees uframe masks are set right. + * no FSTN support (yet; oxu 0.96+) + */ +static int qh_link_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned i; + unsigned period = qh->period; + + dev_dbg(&qh->dev->dev, + "link qh%d-%04x/%p start %d [%d/%d us]\n", + period, le32_to_cpup(&qh->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* high bandwidth, or otherwise every microframe */ + if (period == 0) + period = 1; + + for (i = qh->start; i < oxu->periodic_size; i += period) { + union ehci_shadow *prev = &oxu->pshadow[i]; + __le32 *hw_p = &oxu->periodic[i]; + union ehci_shadow here = *prev; + __le32 type = 0; + + /* skip the iso nodes at list head */ + while (here.ptr) { + type = Q_NEXT_TYPE(*hw_p); + if (type == Q_TYPE_QH) + break; + prev = periodic_next_shadow(prev, type); + hw_p = &here.qh->hw_next; + here = *prev; + } + + /* sorting each branch by period (slow-->fast) + * enables sharing interior tree nodes + */ + while (here.ptr && qh != here.qh) { + if (qh->period > here.qh->period) + break; + prev = &here.qh->qh_next; + hw_p = &here.qh->hw_next; + here = *prev; + } + /* link in this qh, unless some earlier pass did that */ + if (qh != here.qh) { + qh->qh_next = here; + if (here.qh) + qh->hw_next = *hw_p; + wmb(); + prev->qh = qh; + *hw_p = QH_NEXT(qh->qh_dma); + } + } + qh->qh_state = QH_STATE_LINKED; + qh_get(qh); + + /* update per-qh bandwidth for usbfs */ + oxu_to_hcd(oxu)->self.bandwidth_allocated += qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + /* maybe enable periodic schedule processing */ + if (!oxu->periodic_sched++) + return enable_periodic(oxu); + + return 0; +} + +static void qh_unlink_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned i; + unsigned period; + + /* FIXME: + * IF this isn't high speed + * and this qh is active in the current uframe + * (and overlay token SplitXstate is false?) + * THEN + * qh->hw_info1 |= cpu_to_le32(1 << 7 "ignore"); + */ + + /* high bandwidth, or otherwise part of every microframe */ + period = qh->period; + if (period == 0) + period = 1; + + for (i = qh->start; i < oxu->periodic_size; i += period) + periodic_unlink(oxu, i, qh); + + /* update per-qh bandwidth for usbfs */ + oxu_to_hcd(oxu)->self.bandwidth_allocated -= qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + dev_dbg(&qh->dev->dev, + "unlink qh%d-%04x/%p start %d [%d/%d us]\n", + qh->period, + le32_to_cpup(&qh->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* qh->qh_next still "live" to HC */ + qh->qh_state = QH_STATE_UNLINK; + qh->qh_next.ptr = NULL; + qh_put(qh); + + /* maybe turn off periodic schedule */ + oxu->periodic_sched--; + if (!oxu->periodic_sched) + (void) disable_periodic(oxu); +} + +static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned wait; + + qh_unlink_periodic(oxu, qh); + + /* simple/paranoid: always delay, expecting the HC needs to read + * qh->hw_next or finish a writeback after SPLIT/CSPLIT ... and + * expect khubd to clean up after any CSPLITs we won't issue. + * active high speed queues may need bigger delays... + */ + if (list_empty(&qh->qtd_list) + || (cpu_to_le32(QH_CMASK) & qh->hw_info2) != 0) + wait = 2; + else + wait = 55; /* worst case: 3 * 1024 */ + + udelay(wait); + qh->qh_state = QH_STATE_IDLE; + qh->hw_next = EHCI_LIST_END; + wmb(); +} + +static int check_period(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe, + unsigned period, unsigned usecs) +{ + int claimed; + + /* complete split running into next frame? + * given FSTN support, we could sometimes check... + */ + if (uframe >= 8) + return 0; + + /* + * 80% periodic == 100 usec/uframe available + * convert "usecs we need" to "max already claimed" + */ + usecs = 100 - usecs; + + /* we "know" 2 and 4 uframe intervals were rejected; so + * for period 0, check _every_ microframe in the schedule. + */ + if (unlikely(period == 0)) { + do { + for (uframe = 0; uframe < 7; uframe++) { + claimed = periodic_usecs(oxu, frame, uframe); + if (claimed > usecs) + return 0; + } + } while ((frame += 1) < oxu->periodic_size); + + /* just check the specified uframe, at that period */ + } else { + do { + claimed = periodic_usecs(oxu, frame, uframe); + if (claimed > usecs) + return 0; + } while ((frame += period) < oxu->periodic_size); + } + + return 1; +} + +static int check_intr_schedule(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe, + const struct ehci_qh *qh, __le32 *c_maskp) +{ + int retval = -ENOSPC; + + if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period(oxu, frame, uframe, qh->period, qh->usecs)) + goto done; + if (!qh->c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + +done: + return retval; +} + +/* "first fit" scheduling policy used the first time through, + * or when the previous schedule slot can't be re-used. + */ +static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int status; + unsigned uframe; + __le32 c_mask; + unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ + + qh_refresh(oxu, qh); + qh->hw_next = EHCI_LIST_END; + frame = qh->start; + + /* reuse the previous schedule slots, if we can */ + if (frame < qh->period) { + uframe = ffs(le32_to_cpup(&qh->hw_info2) & QH_SMASK); + status = check_intr_schedule(oxu, frame, --uframe, + qh, &c_mask); + } else { + uframe = 0; + c_mask = 0; + status = -ENOSPC; + } + + /* else scan the schedule to find a group of slots such that all + * uframes have enough periodic bandwidth available. + */ + if (status) { + /* "normal" case, uframing flexible except with splits */ + if (qh->period) { + frame = qh->period - 1; + do { + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule(oxu, + frame, uframe, qh, + &c_mask); + if (status == 0) + break; + } + } while (status && frame--); + + /* qh->period == 0 means every uframe */ + } else { + frame = 0; + status = check_intr_schedule(oxu, 0, 0, qh, &c_mask); + } + if (status) + goto done; + qh->start = frame; + + /* reset S-frame and (maybe) C-frame masks */ + qh->hw_info2 &= cpu_to_le32(~(QH_CMASK | QH_SMASK)); + qh->hw_info2 |= qh->period + ? cpu_to_le32(1 << uframe) + : cpu_to_le32(QH_SMASK); + qh->hw_info2 |= c_mask; + } else + oxu_dbg(oxu, "reused qh %p schedule\n", qh); + + /* stuff into the periodic schedule */ + status = qh_link_periodic(oxu, qh); +done: + return status; +} + +static int intr_submit(struct oxu_hcd *oxu, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + unsigned epnum; + unsigned long flags; + struct ehci_qh *qh; + int status = 0; + struct list_head empty; + + /* get endpoint and transfer/schedule data */ + epnum = urb->ep->desc.bEndpointAddress; + + spin_lock_irqsave(&oxu->lock, flags); + + if (unlikely(!HCD_HW_ACCESSIBLE(oxu_to_hcd(oxu)))) { + status = -ESHUTDOWN; + goto done; + } + + /* get qh and force any scheduling errors */ + INIT_LIST_HEAD(&empty); + qh = qh_append_tds(oxu, urb, &empty, epnum, &urb->ep->hcpriv); + if (qh == NULL) { + status = -ENOMEM; + goto done; + } + if (qh->qh_state == QH_STATE_IDLE) { + status = qh_schedule(oxu, qh); + if (status != 0) + goto done; + } + + /* then queue the urb's tds to the qh */ + qh = qh_append_tds(oxu, urb, qtd_list, epnum, &urb->ep->hcpriv); + BUG_ON(qh == NULL); + + /* ... update usbfs periodic stats */ + oxu_to_hcd(oxu)->self.bandwidth_int_reqs++; + +done: + spin_unlock_irqrestore(&oxu->lock, flags); + if (status) + qtd_list_free(oxu, urb, qtd_list); + + return status; +} + +static inline int itd_submit(struct oxu_hcd *oxu, struct urb *urb, + gfp_t mem_flags) +{ + oxu_dbg(oxu, "iso support is missing!\n"); + return -ENOSYS; +} + +static inline int sitd_submit(struct oxu_hcd *oxu, struct urb *urb, + gfp_t mem_flags) +{ + oxu_dbg(oxu, "split iso support is missing!\n"); + return -ENOSYS; +} + +static void scan_periodic(struct oxu_hcd *oxu) +{ + unsigned frame, clock, now_uframe, mod; + unsigned modified; + + mod = oxu->periodic_size << 3; + + /* + * When running, scan from last scan point up to "now" + * else clean up by scanning everything that's left. + * Touches as few pages as possible: cache-friendly. + */ + now_uframe = oxu->next_uframe; + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + clock = readl(&oxu->regs->frame_index); + else + clock = now_uframe + mod - 1; + clock %= mod; + + for (;;) { + union ehci_shadow q, *q_p; + __le32 type, *hw_p; + unsigned uframes; + + /* don't scan past the live uframe */ + frame = now_uframe >> 3; + if (frame == (clock >> 3)) + uframes = now_uframe & 0x07; + else { + /* safe to scan the whole frame at once */ + now_uframe |= 0x07; + uframes = 8; + } + +restart: + /* scan each element in frame's queue for completions */ + q_p = &oxu->pshadow[frame]; + hw_p = &oxu->periodic[frame]; + q.ptr = q_p->ptr; + type = Q_NEXT_TYPE(*hw_p); + modified = 0; + + while (q.ptr != NULL) { + union ehci_shadow temp; + int live; + + live = HC_IS_RUNNING(oxu_to_hcd(oxu)->state); + switch (type) { + case Q_TYPE_QH: + /* handle any completions */ + temp.qh = qh_get(q.qh); + type = Q_NEXT_TYPE(q.qh->hw_next); + q = q.qh->qh_next; + modified = qh_completions(oxu, temp.qh); + if (unlikely(list_empty(&temp.qh->qtd_list))) + intr_deschedule(oxu, temp.qh); + qh_put(temp.qh); + break; + default: + oxu_dbg(oxu, "corrupt type %d frame %d shadow %p\n", + type, frame, q.ptr); + q.ptr = NULL; + } + + /* assume completion callbacks modify the queue */ + if (unlikely(modified)) + goto restart; + } + + /* Stop when we catch up to the HC */ + + /* FIXME: this assumes we won't get lapped when + * latencies climb; that should be rare, but... + * detect it, and just go all the way around. + * FLR might help detect this case, so long as latencies + * don't exceed periodic_size msec (default 1.024 sec). + */ + + /* FIXME: likewise assumes HC doesn't halt mid-scan */ + + if (now_uframe == clock) { + unsigned now; + + if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + break; + oxu->next_uframe = now_uframe; + now = readl(&oxu->regs->frame_index) % mod; + if (now_uframe == now) + break; + + /* rescan the rest of this frame, then ... */ + clock = now; + } else { + now_uframe++; + now_uframe %= mod; + } + } +} + +/* On some systems, leaving remote wakeup enabled prevents system shutdown. + * The firmware seems to think that powering off is a wakeup event! + * This routine turns off remote wakeup and everything else, on all ports. + */ +static void ehci_turn_off_all_ports(struct oxu_hcd *oxu) +{ + int port = HCS_N_PORTS(oxu->hcs_params); + + while (port--) + writel(PORT_RWC_BITS, &oxu->regs->port_status[port]); +} + +static void ehci_port_power(struct oxu_hcd *oxu, int is_on) +{ + unsigned port; + + if (!HCS_PPC(oxu->hcs_params)) + return; + + oxu_dbg(oxu, "...power%s ports...\n", is_on ? "up" : "down"); + for (port = HCS_N_PORTS(oxu->hcs_params); port > 0; ) + (void) oxu_hub_control(oxu_to_hcd(oxu), + is_on ? SetPortFeature : ClearPortFeature, + USB_PORT_FEAT_POWER, + port--, NULL, 0); + msleep(20); +} + +/* Called from some interrupts, timers, and so on. + * It calls driver completion functions, after dropping oxu->lock. + */ +static void ehci_work(struct oxu_hcd *oxu) +{ + timer_action_done(oxu, TIMER_IO_WATCHDOG); + if (oxu->reclaim_ready) + end_unlink_async(oxu); + + /* another CPU may drop oxu->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (oxu->scanning) + return; + oxu->scanning = 1; + scan_async(oxu); + if (oxu->next_uframe != -1) + scan_periodic(oxu); + oxu->scanning = 0; + + /* the IO watchdog guards against hardware or driver bugs that + * misplace IRQs, and should let us run completely without IRQs. + * such lossage has been observed on both VT6202 and VT8235. + */ + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state) && + (oxu->async->qh_next.ptr != NULL || + oxu->periodic_sched != 0)) + timer_action(oxu, TIMER_IO_WATCHDOG); +} + +static void unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + /* if we need to use IAA and it's busy, defer */ + if (qh->qh_state == QH_STATE_LINKED + && oxu->reclaim + && HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) { + struct ehci_qh *last; + + for (last = oxu->reclaim; + last->reclaim; + last = last->reclaim) + continue; + qh->qh_state = QH_STATE_UNLINK_WAIT; + last->reclaim = qh; + + /* bypass IAA if the hc can't care */ + } else if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state) && oxu->reclaim) + end_unlink_async(oxu); + + /* something else might have unlinked the qh by now */ + if (qh->qh_state == QH_STATE_LINKED) + start_unlink_async(oxu, qh); +} + +/* + * USB host controller methods + */ + +static irqreturn_t oxu210_hcd_irq(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 status, pcd_status = 0; + int bh; + + spin_lock(&oxu->lock); + + status = readl(&oxu->regs->status); + + /* e.g. cardbus physical eject */ + if (status == ~(u32) 0) { + oxu_dbg(oxu, "device removed\n"); + goto dead; + } + + /* Shared IRQ? */ + status &= INTR_MASK; + if (!status || unlikely(hcd->state == HC_STATE_HALT)) { + spin_unlock(&oxu->lock); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + writel(status, &oxu->regs->status); + readl(&oxu->regs->command); /* unblock posted write */ + bh = 0; + +#ifdef OXU_VERBOSE_DEBUG + /* unrequested/ignored: Frame List Rollover */ + dbg_status(oxu, "irq", status); +#endif + + /* INT, ERR, and IAA interrupt rates can be throttled */ + + /* normal [4.15.1.2] or error [4.15.1.1] completion */ + if (likely((status & (STS_INT|STS_ERR)) != 0)) + bh = 1; + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + oxu->reclaim_ready = 1; + bh = 1; + } + + /* remote wakeup [4.3.1] */ + if (status & STS_PCD) { + unsigned i = HCS_N_PORTS(oxu->hcs_params); + pcd_status = status; + + /* resume root hub? */ + if (!(readl(&oxu->regs->command) & CMD_RUN)) + usb_hcd_resume_root_hub(hcd); + + while (i--) { + int pstatus = readl(&oxu->regs->port_status[i]); + + if (pstatus & PORT_OWNER) + continue; + if (!(pstatus & PORT_RESUME) + || oxu->reset_done[i] != 0) + continue; + + /* start 20 msec resume signaling from this port, + * and make khubd collect PORT_STAT_C_SUSPEND to + * stop that signaling. + */ + oxu->reset_done[i] = jiffies + msecs_to_jiffies(20); + oxu_dbg(oxu, "port %d remote wakeup\n", i + 1); + mod_timer(&hcd->rh_timer, oxu->reset_done[i]); + } + } + + /* PCI errors [4.15.2.4] */ + if (unlikely((status & STS_FATAL) != 0)) { + /* bogus "fatal" IRQs appear on some chips... why? */ + status = readl(&oxu->regs->status); + dbg_cmd(oxu, "fatal", readl(&oxu->regs->command)); + dbg_status(oxu, "fatal", status); + if (status & STS_HALT) { + oxu_err(oxu, "fatal error\n"); +dead: + ehci_reset(oxu); + writel(0, &oxu->regs->configured_flag); + usb_hc_died(hcd); + /* generic layer kills/unlinks all urbs, then + * uses oxu_stop to clean up the rest + */ + bh = 1; + } + } + + if (bh) + ehci_work(oxu); + spin_unlock(&oxu->lock); + if (pcd_status & STS_PCD) + usb_hcd_poll_rh_status(hcd); + return IRQ_HANDLED; +} + +static irqreturn_t oxu_irq(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ret = IRQ_HANDLED; + + u32 status = oxu_readl(hcd->regs, OXU_CHIPIRQSTATUS); + u32 enable = oxu_readl(hcd->regs, OXU_CHIPIRQEN_SET); + + /* Disable all interrupt */ + oxu_writel(hcd->regs, OXU_CHIPIRQEN_CLR, enable); + + if ((oxu->is_otg && (status & OXU_USBOTGI)) || + (!oxu->is_otg && (status & OXU_USBSPHI))) + oxu210_hcd_irq(hcd); + else + ret = IRQ_NONE; + + /* Enable all interrupt back */ + oxu_writel(hcd->regs, OXU_CHIPIRQEN_SET, enable); + + return ret; +} + +static void oxu_watchdog(unsigned long param) +{ + struct oxu_hcd *oxu = (struct oxu_hcd *) param; + unsigned long flags; + + spin_lock_irqsave(&oxu->lock, flags); + + /* lost IAA irqs wedge things badly; seen with a vt8235 */ + if (oxu->reclaim) { + u32 status = readl(&oxu->regs->status); + if (status & STS_IAA) { + oxu_vdbg(oxu, "lost IAA\n"); + writel(STS_IAA, &oxu->regs->status); + oxu->reclaim_ready = 1; + } + } + + /* stop async processing after it's idled a bit */ + if (test_bit(TIMER_ASYNC_OFF, &oxu->actions)) + start_unlink_async(oxu, oxu->async); + + /* oxu could run by timer, without IRQs ... */ + ehci_work(oxu); + + spin_unlock_irqrestore(&oxu->lock, flags); +} + +/* One-time init, only for memory state. + */ +static int oxu_hcd_init(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp; + int retval; + u32 hcc_params; + + spin_lock_init(&oxu->lock); + + init_timer(&oxu->watchdog); + oxu->watchdog.function = oxu_watchdog; + oxu->watchdog.data = (unsigned long) oxu; + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + oxu->periodic_size = DEFAULT_I_TDPS; + retval = ehci_mem_init(oxu, GFP_KERNEL); + if (retval < 0) + return retval; + + /* controllers may cache some of the periodic schedule ... */ + hcc_params = readl(&oxu->caps->hcc_params); + if (HCC_ISOC_CACHE(hcc_params)) /* full frame cache */ + oxu->i_thresh = 8; + else /* N microframes cached */ + oxu->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + oxu->reclaim = NULL; + oxu->reclaim_ready = 0; + oxu->next_uframe = -1; + + /* + * dedicate a qh for the async ring head, since we couldn't unlink + * a 'real' qh without stopping the async schedule [4.8]. use it + * as the 'reclamation list head' too. + * its dummy is used in hw_alt_next of many tds, to prevent the qh + * from automatically advancing to the next td after short reads. + */ + oxu->async->qh_next.qh = NULL; + oxu->async->hw_next = QH_NEXT(oxu->async->qh_dma); + oxu->async->hw_info1 = cpu_to_le32(QH_HEAD); + oxu->async->hw_token = cpu_to_le32(QTD_STS_HALT); + oxu->async->hw_qtd_next = EHCI_LIST_END; + oxu->async->qh_state = QH_STATE_LINKED; + oxu->async->hw_alt_next = QTD_NEXT(oxu->async->dummy->qtd_dma); + + /* clear interrupt enables, set irq latency */ + if (log2_irq_thresh < 0 || log2_irq_thresh > 6) + log2_irq_thresh = 0; + temp = 1 << (16 + log2_irq_thresh); + if (HCC_CANPARK(hcc_params)) { + /* HW default park == 3, on hardware that supports it (like + * NVidia and ALI silicon), maximizes throughput on the async + * schedule by avoiding QH fetches between transfers. + * + * With fast usb storage devices and NForce2, "park" seems to + * make problems: throughput reduction (!), data errors... + */ + if (park) { + park = min(park, (unsigned) 3); + temp |= CMD_PARK; + temp |= park << 8; + } + oxu_dbg(oxu, "park %d\n", park); + } + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + temp &= ~(3 << 2); + temp |= (EHCI_TUNE_FLS << 2); + } + oxu->command = temp; + + return 0; +} + +/* Called during probe() after chip reset completes. + */ +static int oxu_reset(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ret; + + spin_lock_init(&oxu->mem_lock); + INIT_LIST_HEAD(&oxu->urb_list); + oxu->urb_len = 0; + + /* FIMXE */ + hcd->self.controller->dma_mask = NULL; + + if (oxu->is_otg) { + oxu->caps = hcd->regs + OXU_OTG_CAP_OFFSET; + oxu->regs = hcd->regs + OXU_OTG_CAP_OFFSET + \ + HC_LENGTH(readl(&oxu->caps->hc_capbase)); + + oxu->mem = hcd->regs + OXU_SPH_MEM; + } else { + oxu->caps = hcd->regs + OXU_SPH_CAP_OFFSET; + oxu->regs = hcd->regs + OXU_SPH_CAP_OFFSET + \ + HC_LENGTH(readl(&oxu->caps->hc_capbase)); + + oxu->mem = hcd->regs + OXU_OTG_MEM; + } + + oxu->hcs_params = readl(&oxu->caps->hcs_params); + oxu->sbrn = 0x20; + + ret = oxu_hcd_init(hcd); + if (ret) + return ret; + + return 0; +} + +static int oxu_run(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int retval; + u32 temp, hcc_params; + + hcd->uses_new_polling = 1; + + /* EHCI spec section 4.1 */ + retval = ehci_reset(oxu); + if (retval != 0) { + ehci_mem_cleanup(oxu); + return retval; + } + writel(oxu->periodic_dma, &oxu->regs->frame_list); + writel((u32) oxu->async->qh_dma, &oxu->regs->async_next); + + /* hcc_params controls whether oxu->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * pci_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like pci_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dma_supported(), so + * drivers can pass this info along ... like NETIF_F_HIGHDMA, + * Scsi_Host.highmem_io, and so forth. It's readonly to all + * host side drivers though. + */ + hcc_params = readl(&oxu->caps->hcc_params); + if (HCC_64BIT_ADDR(hcc_params)) + writel(0, &oxu->regs->segment); + + oxu->command &= ~(CMD_LRESET | CMD_IAAD | CMD_PSE | + CMD_ASE | CMD_RESET); + oxu->command |= CMD_RUN; + writel(oxu->command, &oxu->regs->command); + dbg_cmd(oxu, "init", oxu->command); + + /* + * Start, enabling full USB 2.0 functionality ... usb 1.1 devices + * are explicitly handed to companion controller(s), so no TT is + * involved with the root hub. (Except where one is integrated, + * and there's no companion controller unless maybe for USB OTG.) + */ + hcd->state = HC_STATE_RUNNING; + writel(FLAG_CF, &oxu->regs->configured_flag); + readl(&oxu->regs->command); /* unblock posted writes */ + + temp = HC_VERSION(readl(&oxu->caps->hc_capbase)); + oxu_info(oxu, "USB %x.%x started, quasi-EHCI %x.%02x, driver %s%s\n", + ((oxu->sbrn & 0xf0)>>4), (oxu->sbrn & 0x0f), + temp >> 8, temp & 0xff, DRIVER_VERSION, + ignore_oc ? ", overcurrent ignored" : ""); + + writel(INTR_MASK, &oxu->regs->intr_enable); /* Turn On Interrupts */ + + return 0; +} + +static void oxu_stop(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + /* Turn off port power on all root hub ports. */ + ehci_port_power(oxu, 0); + + /* no more interrupts ... */ + del_timer_sync(&oxu->watchdog); + + spin_lock_irq(&oxu->lock); + if (HC_IS_RUNNING(hcd->state)) + ehci_quiesce(oxu); + + ehci_reset(oxu); + writel(0, &oxu->regs->intr_enable); + spin_unlock_irq(&oxu->lock); + + /* let companion controllers work when we aren't */ + writel(0, &oxu->regs->configured_flag); + + /* root hub is shut down separately (first, when possible) */ + spin_lock_irq(&oxu->lock); + if (oxu->async) + ehci_work(oxu); + spin_unlock_irq(&oxu->lock); + ehci_mem_cleanup(oxu); + + dbg_status(oxu, "oxu_stop completed", readl(&oxu->regs->status)); +} + +/* Kick in for silicon on any bus (not just pci, etc). + * This forcibly disables dma and IRQs, helping kexec and other cases + * where the next system software may expect clean state. + */ +static void oxu_shutdown(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + (void) ehci_halt(oxu); + ehci_turn_off_all_ports(oxu); + + /* make BIOS/etc use companion controller during reboot */ + writel(0, &oxu->regs->configured_flag); + + /* unblock posted writes */ + readl(&oxu->regs->configured_flag); +} + +/* Non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + * + * urb + dev is in hcd.self.controller.urb_list + * we're queueing TDs onto software and hardware lists + * + * hcd-specific init for hcpriv hasn't been done yet + * + * NOTE: control, bulk, and interrupt share the same code to append TDs + * to a (possibly active) QH, and the same QH scanning code. + */ +static int __oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct list_head qtd_list; + + INIT_LIST_HEAD(&qtd_list); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + default: + if (!qh_urb_transaction(oxu, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return submit_async(oxu, urb, &qtd_list, mem_flags); + + case PIPE_INTERRUPT: + if (!qh_urb_transaction(oxu, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit(oxu, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: + if (urb->dev->speed == USB_SPEED_HIGH) + return itd_submit(oxu, urb, mem_flags); + else + return sitd_submit(oxu, urb, mem_flags); + } +} + +/* This function is responsible for breaking URBs with big data size + * into smaller size and processing small urbs in sequence. + */ +static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int num, rem; + int transfer_buffer_length; + void *transfer_buffer; + struct urb *murb; + int i, ret; + + /* If not bulk pipe just enqueue the URB */ + if (!usb_pipebulk(urb->pipe)) + return __oxu_urb_enqueue(hcd, urb, mem_flags); + + /* Otherwise we should verify the USB transfer buffer size! */ + transfer_buffer = urb->transfer_buffer; + transfer_buffer_length = urb->transfer_buffer_length; + + num = urb->transfer_buffer_length / 4096; + rem = urb->transfer_buffer_length % 4096; + if (rem != 0) + num++; + + /* If URB is smaller than 4096 bytes just enqueue it! */ + if (num == 1) + return __oxu_urb_enqueue(hcd, urb, mem_flags); + + /* Ok, we have more job to do! :) */ + + for (i = 0; i < num - 1; i++) { + /* Get free micro URB poll till a free urb is received */ + + do { + murb = (struct urb *) oxu_murb_alloc(oxu); + if (!murb) + schedule(); + } while (!murb); + + /* Coping the urb */ + memcpy(murb, urb, sizeof(struct urb)); + + murb->transfer_buffer_length = 4096; + murb->transfer_buffer = transfer_buffer + i * 4096; + + /* Null pointer for the encodes that this is a micro urb */ + murb->complete = NULL; + + ((struct oxu_murb *) murb)->main = urb; + ((struct oxu_murb *) murb)->last = 0; + + /* This loop is to guarantee urb to be processed when there's + * not enough resources at a particular time by retrying. + */ + do { + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + if (ret) + schedule(); + } while (ret); + } + + /* Last urb requires special handling */ + + /* Get free micro URB poll till a free urb is received */ + do { + murb = (struct urb *) oxu_murb_alloc(oxu); + if (!murb) + schedule(); + } while (!murb); + + /* Coping the urb */ + memcpy(murb, urb, sizeof(struct urb)); + + murb->transfer_buffer_length = rem > 0 ? rem : 4096; + murb->transfer_buffer = transfer_buffer + (num - 1) * 4096; + + /* Null pointer for the encodes that this is a micro urb */ + murb->complete = NULL; + + ((struct oxu_murb *) murb)->main = urb; + ((struct oxu_murb *) murb)->last = 1; + + do { + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + if (ret) + schedule(); + } while (ret); + + return ret; +} + +/* Remove from hardware lists. + * Completions normally happen asynchronously + */ +static int oxu_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct ehci_qh *qh; + unsigned long flags; + + spin_lock_irqsave(&oxu->lock, flags); + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + default: + qh = (struct ehci_qh *) urb->hcpriv; + if (!qh) + break; + unlink_async(oxu, qh); + break; + + case PIPE_INTERRUPT: + qh = (struct ehci_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + intr_deschedule(oxu, qh); + /* FALL THROUGH */ + case QH_STATE_IDLE: + qh_completions(oxu, qh); + break; + default: + oxu_dbg(oxu, "bogus qh %p state %d\n", + qh, qh->qh_state); + goto done; + } + + /* reschedule QH iff another request is queued */ + if (!list_empty(&qh->qtd_list) + && HC_IS_RUNNING(hcd->state)) { + int status; + + status = qh_schedule(oxu, qh); + spin_unlock_irqrestore(&oxu->lock, flags); + + if (status != 0) { + /* shouldn't happen often, but ... + * FIXME kill those tds' urbs + */ + dev_err(hcd->self.controller, + "can't reschedule qh %p, err %d\n", qh, + status); + } + return status; + } + break; + } +done: + spin_unlock_irqrestore(&oxu->lock, flags); + return 0; +} + +/* Bulk qh holds the data toggle */ +static void oxu_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + unsigned long flags; + struct ehci_qh *qh, *tmp; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +rescan: + spin_lock_irqsave(&oxu->lock, flags); + qh = ep->hcpriv; + if (!qh) + goto done; + + /* endpoints can be iso streams. for now, we don't + * accelerate iso completions ... so spin a while. + */ + if (qh->hw_info1 == 0) { + oxu_vdbg(oxu, "iso delay\n"); + goto idle_timeout; + } + + if (!HC_IS_RUNNING(hcd->state)) + qh->qh_state = QH_STATE_IDLE; + switch (qh->qh_state) { + case QH_STATE_LINKED: + for (tmp = oxu->async->qh_next.qh; + tmp && tmp != qh; + tmp = tmp->qh_next.qh) + continue; + /* periodic qh self-unlinks on empty */ + if (!tmp) + goto nogood; + unlink_async(oxu, qh); + /* FALL THROUGH */ + case QH_STATE_UNLINK: /* wait for hw to finish? */ +idle_timeout: + spin_unlock_irqrestore(&oxu->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case QH_STATE_IDLE: /* fully unlinked */ + if (list_empty(&qh->qtd_list)) { + qh_put(qh); + break; + } + /* else FALL THROUGH */ + default: +nogood: + /* caller was supposed to have unlinked any requests; + * that's not our job. just leak this memory. + */ + oxu_err(oxu, "qh %p (#%02x) state %d%s\n", + qh, ep->desc.bEndpointAddress, qh->qh_state, + list_empty(&qh->qtd_list) ? "" : "(has tds)"); + break; + } + ep->hcpriv = NULL; +done: + spin_unlock_irqrestore(&oxu->lock, flags); +} + +static int oxu_get_frame(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + return (readl(&oxu->regs->frame_index) >> 3) % + oxu->periodic_size; +} + +/* Build "status change" packet (one or two bytes) from HC registers */ +static int oxu_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp, mask, status = 0; + int ports, i, retval = 1; + unsigned long flags; + + /* if !PM_RUNTIME, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(hcd->state)) + return 0; + + /* init status to no-changes */ + buf[0] = 0; + ports = HCS_N_PORTS(oxu->hcs_params); + if (ports > 7) { + buf[1] = 0; + retval++; + } + + /* Some boards (mostly VIA?) report bogus overcurrent indications, + * causing massive log spam unless we completely ignore them. It + * may be relevant that VIA VT8235 controllers, where PORT_POWER is + * always set, seem to clear PORT_OCC and PORT_CSC when writing to + * PORT_POWER; that's surprising, but maybe within-spec. + */ + if (!ignore_oc) + mask = PORT_CSC | PORT_PEC | PORT_OCC; + else + mask = PORT_CSC | PORT_PEC; + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave(&oxu->lock, flags); + for (i = 0; i < ports; i++) { + temp = readl(&oxu->regs->port_status[i]); + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if (!(temp & PORT_CONNECT)) + oxu->reset_done[i] = 0; + if ((temp & mask) != 0 || ((temp & PORT_RESUME) != 0 && + time_after_eq(jiffies, oxu->reset_done[i]))) { + if (i < 7) + buf[0] |= 1 << (i + 1); + else + buf[1] |= 1 << (i - 7); + status = STS_PCD; + } + } + /* FIXME autosuspend idle root hubs */ + spin_unlock_irqrestore(&oxu->lock, flags); + return status ? retval : 0; +} + +/* Returns the speed of a device attached to a port on the root hub. */ +static inline unsigned int oxu_port_speed(struct oxu_hcd *oxu, + unsigned int portsc) +{ + switch ((portsc >> 26) & 3) { + case 0: + return 0; + case 1: + return USB_PORT_STAT_LOW_SPEED; + case 2: + default: + return USB_PORT_STAT_HIGH_SPEED; + } +} + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) +static int oxu_hub_control(struct usb_hcd *hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ports = HCS_N_PORTS(oxu->hcs_params); + u32 __iomem *status_reg = &oxu->regs->port_status[wIndex - 1]; + u32 temp, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave(&oxu->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl(status_reg); + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, khubd needs to be able to clear + * the port-change status bits (especially + * USB_PORT_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + writel(temp & ~PORT_PE, status_reg); + break; + case USB_PORT_FEAT_C_ENABLE: + writel((temp & ~PORT_RWC_BITS) | PORT_PEC, status_reg); + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + if (temp & PORT_SUSPEND) { + if ((temp & PORT_PE) == 0) + goto error; + /* resume signaling for 20 msec */ + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + writel(temp | PORT_RESUME, status_reg); + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + /* we auto-clear this feature */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(oxu->hcs_params)) + writel(temp & ~(PORT_RWC_BITS | PORT_POWER), + status_reg); + break; + case USB_PORT_FEAT_C_CONNECTION: + writel((temp & ~PORT_RWC_BITS) | PORT_CSC, status_reg); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + writel((temp & ~PORT_RWC_BITS) | PORT_OCC, status_reg); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + readl(&oxu->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + ehci_hub_descriptor(oxu, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset(buf, 0, 4); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = readl(status_reg); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + if (temp & PORT_PEC) + status |= USB_PORT_STAT_C_ENABLE << 16; + if ((temp & PORT_OCC) && !ignore_oc) + status |= USB_PORT_STAT_C_OVERCURRENT << 16; + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + + /* Remote Wakeup received? */ + if (!oxu->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&oxu_to_hcd(oxu)->rh_timer, + oxu->reset_done[wIndex]); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + oxu->reset_done[wIndex])) { + status |= USB_PORT_STAT_C_SUSPEND << 16; + oxu->reset_done[wIndex] = 0; + + /* stop resume signaling */ + temp = readl(status_reg); + writel(temp & ~(PORT_RWC_BITS | PORT_RESUME), + status_reg); + retval = handshake(oxu, status_reg, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + oxu_err(oxu, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && time_after_eq(jiffies, + oxu->reset_done[wIndex])) { + status |= USB_PORT_STAT_C_RESET << 16; + oxu->reset_done[wIndex] = 0; + + /* force reset to complete */ + writel(temp & ~(PORT_RWC_BITS | PORT_RESET), + status_reg); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(oxu, status_reg, + PORT_RESET, 0, 750); + if (retval != 0) { + oxu_err(oxu, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete(oxu, wIndex, status_reg, + readl(status_reg)); + } + + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &oxu->companion_ports)) { + temp &= ~PORT_RWC_BITS; + temp |= PORT_OWNER; + writel(temp, status_reg); + oxu_dbg(oxu, "port %d --> companion\n", wIndex + 1); + temp = readl(status_reg); + } + + /* + * Even if OWNER is set, there's no harm letting khubd + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + /* status may be from integrated TT */ + status |= oxu_port_speed(oxu, temp); + } + if (temp & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + if (temp & (PORT_SUSPEND|PORT_RESUME)) + status |= USB_PORT_STAT_SUSPEND; + if (temp & PORT_OC) + status |= USB_PORT_STAT_OVERCURRENT; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (temp & PORT_POWER) + status |= USB_PORT_STAT_POWER; + +#ifndef OXU_VERBOSE_DEBUG + if (status & ~0xffff) /* only if wPortChange is interesting */ +#endif + dbg_port(oxu, "GetStatus", wIndex + 1, temp); + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl(status_reg); + if (temp & PORT_OWNER) + break; + + temp &= ~PORT_RWC_BITS; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + if (device_may_wakeup(&hcd->self.root_hub->dev)) + temp |= PORT_WAKE_BITS; + writel(temp | PORT_SUSPEND, status_reg); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(oxu->hcs_params)) + writel(temp | PORT_POWER, status_reg); + break; + case USB_PORT_FEAT_RESET: + if (temp & PORT_RESUME) + goto error; + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + oxu_vdbg(oxu, "port %d reset\n", wIndex + 1); + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(50); + writel(temp, status_reg); + break; + + /* For downstream facing ports (these): one hub port is put + * into test mode according to USB2 11.24.2.13, then the hub + * must be reset (which for root hub now means rmmod+modprobe, + * or else system reboot). See EHCI 2.3.9 and 4.14 for info + * about the EHCI-specific stuff. + */ + case USB_PORT_FEAT_TEST: + if (!selector || selector > 5) + goto error; + ehci_quiesce(oxu); + ehci_halt(oxu); + temp |= selector << 16; + writel(temp, status_reg); + break; + + default: + goto error; + } + readl(&oxu->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&oxu->lock, flags); + return retval; +} + +#ifdef CONFIG_PM + +static int oxu_bus_suspend(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int port; + int mask; + + oxu_dbg(oxu, "suspend root hub\n"); + + if (time_before(jiffies, oxu->next_statechange)) + msleep(5); + + port = HCS_N_PORTS(oxu->hcs_params); + spin_lock_irq(&oxu->lock); + + /* stop schedules, clean any completed work */ + if (HC_IS_RUNNING(hcd->state)) { + ehci_quiesce(oxu); + hcd->state = HC_STATE_QUIESCING; + } + oxu->command = readl(&oxu->regs->command); + if (oxu->reclaim) + oxu->reclaim_ready = 1; + ehci_work(oxu); + + /* Unlike other USB host controller types, EHCI doesn't have + * any notion of "global" or bus-wide suspend. The driver has + * to manually suspend all the active unsuspended ports, and + * then manually resume them in the bus_resume() routine. + */ + oxu->bus_suspended = 0; + while (port--) { + u32 __iomem *reg = &oxu->regs->port_status[port]; + u32 t1 = readl(reg) & ~PORT_RWC_BITS; + u32 t2 = t1; + + /* keep track of which ports we suspend */ + if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) && + !(t1 & PORT_SUSPEND)) { + t2 |= PORT_SUSPEND; + set_bit(port, &oxu->bus_suspended); + } + + /* enable remote wakeup on all ports */ + if (device_may_wakeup(&hcd->self.root_hub->dev)) + t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E; + else + t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E); + + if (t1 != t2) { + oxu_vdbg(oxu, "port %d, %08x -> %08x\n", + port + 1, t1, t2); + writel(t2, reg); + } + } + + /* turn off now-idle HC */ + del_timer_sync(&oxu->watchdog); + ehci_halt(oxu); + hcd->state = HC_STATE_SUSPENDED; + + /* allow remote wakeup */ + mask = INTR_MASK; + if (!device_may_wakeup(&hcd->self.root_hub->dev)) + mask &= ~STS_PCD; + writel(mask, &oxu->regs->intr_enable); + readl(&oxu->regs->intr_enable); + + oxu->next_statechange = jiffies + msecs_to_jiffies(10); + spin_unlock_irq(&oxu->lock); + return 0; +} + +/* Caller has locked the root hub, and should reset/reinit on error */ +static int oxu_bus_resume(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp; + int i; + + if (time_before(jiffies, oxu->next_statechange)) + msleep(5); + spin_lock_irq(&oxu->lock); + + /* Ideally and we've got a real resume here, and no port's power + * was lost. (For PCI, that means Vaux was maintained.) But we + * could instead be restoring a swsusp snapshot -- so that BIOS was + * the last user of the controller, not reset/pm hardware keeping + * state we gave to it. + */ + temp = readl(&oxu->regs->intr_enable); + oxu_dbg(oxu, "resume root hub%s\n", temp ? "" : " after power loss"); + + /* at least some APM implementations will try to deliver + * IRQs right away, so delay them until we're ready. + */ + writel(0, &oxu->regs->intr_enable); + + /* re-init operational registers */ + writel(0, &oxu->regs->segment); + writel(oxu->periodic_dma, &oxu->regs->frame_list); + writel((u32) oxu->async->qh_dma, &oxu->regs->async_next); + + /* restore CMD_RUN, framelist size, and irq threshold */ + writel(oxu->command, &oxu->regs->command); + + /* Some controller/firmware combinations need a delay during which + * they set up the port statuses. See Bugzilla #8190. */ + mdelay(8); + + /* manually resume the ports we suspended during bus_suspend() */ + i = HCS_N_PORTS(oxu->hcs_params); + while (i--) { + temp = readl(&oxu->regs->port_status[i]); + temp &= ~(PORT_RWC_BITS + | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E); + if (test_bit(i, &oxu->bus_suspended) && (temp & PORT_SUSPEND)) { + oxu->reset_done[i] = jiffies + msecs_to_jiffies(20); + temp |= PORT_RESUME; + } + writel(temp, &oxu->regs->port_status[i]); + } + i = HCS_N_PORTS(oxu->hcs_params); + mdelay(20); + while (i--) { + temp = readl(&oxu->regs->port_status[i]); + if (test_bit(i, &oxu->bus_suspended) && (temp & PORT_SUSPEND)) { + temp &= ~(PORT_RWC_BITS | PORT_RESUME); + writel(temp, &oxu->regs->port_status[i]); + oxu_vdbg(oxu, "resumed port %d\n", i + 1); + } + } + (void) readl(&oxu->regs->command); + + /* maybe re-activate the schedule(s) */ + temp = 0; + if (oxu->async->qh_next.qh) + temp |= CMD_ASE; + if (oxu->periodic_sched) + temp |= CMD_PSE; + if (temp) { + oxu->command |= temp; + writel(oxu->command, &oxu->regs->command); + } + + oxu->next_statechange = jiffies + msecs_to_jiffies(5); + hcd->state = HC_STATE_RUNNING; + + /* Now we can safely re-enable irqs */ + writel(INTR_MASK, &oxu->regs->intr_enable); + + spin_unlock_irq(&oxu->lock); + return 0; +} + +#else + +static int oxu_bus_suspend(struct usb_hcd *hcd) +{ + return 0; +} + +static int oxu_bus_resume(struct usb_hcd *hcd) +{ + return 0; +} + +#endif /* CONFIG_PM */ + +static const struct hc_driver oxu_hc_driver = { + .description = "oxu210hp_hcd", + .product_desc = "oxu210hp HCD", + .hcd_priv_size = sizeof(struct oxu_hcd), + + /* + * Generic hardware linkage + */ + .irq = oxu_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * Basic lifecycle operations + */ + .reset = oxu_reset, + .start = oxu_run, + .stop = oxu_stop, + .shutdown = oxu_shutdown, + + /* + * Managing i/o requests and associated device resources + */ + .urb_enqueue = oxu_urb_enqueue, + .urb_dequeue = oxu_urb_dequeue, + .endpoint_disable = oxu_endpoint_disable, + + /* + * Scheduling support + */ + .get_frame_number = oxu_get_frame, + + /* + * Root hub support + */ + .hub_status_data = oxu_hub_status_data, + .hub_control = oxu_hub_control, + .bus_suspend = oxu_bus_suspend, + .bus_resume = oxu_bus_resume, +}; + +/* + * Module stuff + */ + +static void oxu_configuration(struct platform_device *pdev, void *base) +{ + u32 tmp; + + /* Initialize top level registers. + * First write ever + */ + oxu_writel(base, OXU_HOSTIFCONFIG, 0x0000037D); + oxu_writel(base, OXU_SOFTRESET, OXU_SRESET); + oxu_writel(base, OXU_HOSTIFCONFIG, 0x0000037D); + + tmp = oxu_readl(base, OXU_PIOBURSTREADCTRL); + oxu_writel(base, OXU_PIOBURSTREADCTRL, tmp | 0x0040); + + oxu_writel(base, OXU_ASO, OXU_SPHPOEN | OXU_OVRCCURPUPDEN | + OXU_COMPARATOR | OXU_ASO_OP); + + tmp = oxu_readl(base, OXU_CLKCTRL_SET); + oxu_writel(base, OXU_CLKCTRL_SET, tmp | OXU_SYSCLKEN | OXU_USBOTGCLKEN); + + /* Clear all top interrupt enable */ + oxu_writel(base, OXU_CHIPIRQEN_CLR, 0xff); + + /* Clear all top interrupt status */ + oxu_writel(base, OXU_CHIPIRQSTATUS, 0xff); + + /* Enable all needed top interrupt except OTG SPH core */ + oxu_writel(base, OXU_CHIPIRQEN_SET, OXU_USBSPHLPWUI | OXU_USBOTGLPWUI); +} + +static int oxu_verify_id(struct platform_device *pdev, void *base) +{ + u32 id; + static const char * const bo[] = { + "reserved", + "128-pin LQFP", + "84-pin TFBGA", + "reserved", + }; + + /* Read controller signature register to find a match */ + id = oxu_readl(base, OXU_DEVICEID); + dev_info(&pdev->dev, "device ID %x\n", id); + if ((id & OXU_REV_MASK) != (OXU_REV_2100 << OXU_REV_SHIFT)) + return -1; + + dev_info(&pdev->dev, "found device %x %s (%04x:%04x)\n", + id >> OXU_REV_SHIFT, + bo[(id & OXU_BO_MASK) >> OXU_BO_SHIFT], + (id & OXU_MAJ_REV_MASK) >> OXU_MAJ_REV_SHIFT, + (id & OXU_MIN_REV_MASK) >> OXU_MIN_REV_SHIFT); + + return 0; +} + +static const struct hc_driver oxu_hc_driver; +static struct usb_hcd *oxu_create(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void *base, int irq, int otg) +{ + struct device *dev = &pdev->dev; + + struct usb_hcd *hcd; + struct oxu_hcd *oxu; + int ret; + + /* Set endian mode and host mode */ + oxu_writel(base + (otg ? OXU_OTG_CORE_OFFSET : OXU_SPH_CORE_OFFSET), + OXU_USBMODE, + OXU_CM_HOST_ONLY | OXU_ES_LITTLE | OXU_VBPS); + + hcd = usb_create_hcd(&oxu_hc_driver, dev, + otg ? "oxu210hp_otg" : "oxu210hp_sph"); + if (!hcd) + return ERR_PTR(-ENOMEM); + + hcd->rsrc_start = memstart; + hcd->rsrc_len = memlen; + hcd->regs = base; + hcd->irq = irq; + hcd->state = HC_STATE_HALT; + + oxu = hcd_to_oxu(hcd); + oxu->is_otg = otg; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret < 0) + return ERR_PTR(ret); + + return hcd; +} + +static int oxu_init(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void *base, int irq) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + struct usb_hcd *hcd; + int ret; + + /* First time configuration at start up */ + oxu_configuration(pdev, base); + + ret = oxu_verify_id(pdev, base); + if (ret) { + dev_err(&pdev->dev, "no devices found!\n"); + return -ENODEV; + } + + /* Create the OTG controller */ + hcd = oxu_create(pdev, memstart, memlen, base, irq, 1); + if (IS_ERR(hcd)) { + dev_err(&pdev->dev, "cannot create OTG controller!\n"); + ret = PTR_ERR(hcd); + goto error_create_otg; + } + info->hcd[0] = hcd; + + /* Create the SPH host controller */ + hcd = oxu_create(pdev, memstart, memlen, base, irq, 0); + if (IS_ERR(hcd)) { + dev_err(&pdev->dev, "cannot create SPH controller!\n"); + ret = PTR_ERR(hcd); + goto error_create_sph; + } + info->hcd[1] = hcd; + + oxu_writel(base, OXU_CHIPIRQEN_SET, + oxu_readl(base, OXU_CHIPIRQEN_SET) | 3); + + return 0; + +error_create_sph: + usb_remove_hcd(info->hcd[0]); + usb_put_hcd(info->hcd[0]); + +error_create_otg: + return ret; +} + +static int oxu_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + void *base; + unsigned long memstart, memlen; + int irq, ret; + struct oxu_info *info; + + if (usb_disabled()) + return -ENODEV; + + /* + * Get the platform resources + */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "no IRQ! Check %s setup!\n", dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res->start; + dev_dbg(&pdev->dev, "IRQ resource %d\n", irq); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no registers address! Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + memstart = res->start; + memlen = resource_size(res); + dev_dbg(&pdev->dev, "MEM resource %lx-%lx\n", memstart, memlen); + if (!request_mem_region(memstart, memlen, + oxu_hc_driver.description)) { + dev_dbg(&pdev->dev, "memory area already in use\n"); + return -EBUSY; + } + + ret = irq_set_irq_type(irq, IRQF_TRIGGER_FALLING); + if (ret) { + dev_err(&pdev->dev, "error setting irq type\n"); + ret = -EFAULT; + goto error_set_irq_type; + } + + base = ioremap(memstart, memlen); + if (!base) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + ret = -EFAULT; + goto error_ioremap; + } + + /* Allocate a driver data struct to hold useful info for both + * SPH & OTG devices + */ + info = kzalloc(sizeof(struct oxu_info), GFP_KERNEL); + if (!info) { + dev_dbg(&pdev->dev, "error allocating memory\n"); + ret = -EFAULT; + goto error_alloc; + } + platform_set_drvdata(pdev, info); + + ret = oxu_init(pdev, memstart, memlen, base, irq); + if (ret < 0) { + dev_dbg(&pdev->dev, "cannot init USB devices\n"); + goto error_init; + } + + dev_info(&pdev->dev, "devices enabled and running\n"); + platform_set_drvdata(pdev, info); + + return 0; + +error_init: + kfree(info); + platform_set_drvdata(pdev, NULL); + +error_alloc: + iounmap(base); + +error_set_irq_type: +error_ioremap: + release_mem_region(memstart, memlen); + + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), ret); + return ret; +} + +static void oxu_remove(struct platform_device *pdev, struct usb_hcd *hcd) +{ + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +static int oxu_drv_remove(struct platform_device *pdev) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + unsigned long memstart = info->hcd[0]->rsrc_start, + memlen = info->hcd[0]->rsrc_len; + void *base = info->hcd[0]->regs; + + oxu_remove(pdev, info->hcd[0]); + oxu_remove(pdev, info->hcd[1]); + + iounmap(base); + release_mem_region(memstart, memlen); + + kfree(info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void oxu_drv_shutdown(struct platform_device *pdev) +{ + oxu_drv_remove(pdev); +} + +#if 0 +/* FIXME: TODO */ +static int oxu_drv_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return 0; +} + +static int oxu_drv_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return 0; +} +#else +#define oxu_drv_suspend NULL +#define oxu_drv_resume NULL +#endif + +static struct platform_driver oxu_driver = { + .probe = oxu_drv_probe, + .remove = oxu_drv_remove, + .shutdown = oxu_drv_shutdown, + .suspend = oxu_drv_suspend, + .resume = oxu_drv_resume, + .driver = { + .name = "oxu210hp-hcd", + .bus = &platform_bus_type + } +}; + +module_platform_driver(oxu_driver); + +MODULE_DESCRIPTION("Oxford OXU210HP HCD driver - ver. " DRIVER_VERSION); +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/oxu210hp.h b/drivers/usb/host/oxu210hp.h new file mode 100644 index 00000000..1c216ad9 --- /dev/null +++ b/drivers/usb/host/oxu210hp.h @@ -0,0 +1,447 @@ +/* + * Host interface registers + */ + +#define OXU_DEVICEID 0x00 + #define OXU_REV_MASK 0xffff0000 + #define OXU_REV_SHIFT 16 + #define OXU_REV_2100 0x2100 + #define OXU_BO_SHIFT 8 + #define OXU_BO_MASK (0x3 << OXU_BO_SHIFT) + #define OXU_MAJ_REV_SHIFT 4 + #define OXU_MAJ_REV_MASK (0xf << OXU_MAJ_REV_SHIFT) + #define OXU_MIN_REV_SHIFT 0 + #define OXU_MIN_REV_MASK (0xf << OXU_MIN_REV_SHIFT) +#define OXU_HOSTIFCONFIG 0x04 +#define OXU_SOFTRESET 0x08 + #define OXU_SRESET (1 << 0) + +#define OXU_PIOBURSTREADCTRL 0x0C + +#define OXU_CHIPIRQSTATUS 0x10 +#define OXU_CHIPIRQEN_SET 0x14 +#define OXU_CHIPIRQEN_CLR 0x18 + #define OXU_USBSPHLPWUI 0x00000080 + #define OXU_USBOTGLPWUI 0x00000040 + #define OXU_USBSPHI 0x00000002 + #define OXU_USBOTGI 0x00000001 + +#define OXU_CLKCTRL_SET 0x1C + #define OXU_SYSCLKEN 0x00000008 + #define OXU_USBSPHCLKEN 0x00000002 + #define OXU_USBOTGCLKEN 0x00000001 + +#define OXU_ASO 0x68 + #define OXU_SPHPOEN 0x00000100 + #define OXU_OVRCCURPUPDEN 0x00000800 + #define OXU_ASO_OP (1 << 10) + #define OXU_COMPARATOR 0x000004000 + +#define OXU_USBMODE 0x1A8 + #define OXU_VBPS 0x00000020 + #define OXU_ES_LITTLE 0x00000000 + #define OXU_CM_HOST_ONLY 0x00000003 + +/* + * Proper EHCI structs & defines + */ + +/* Magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ + +struct oxu_hcd; + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct ehci_caps { + /* these fields are specified as 8 and 16 bit registers, + * but some hosts can't perform 8 or 16 bit PCI accesses. + */ + u32 hc_capbase; +#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ +#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ +#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ +#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ +#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ +#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ +#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ +#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ +#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ +#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ + u8 portroute[8]; /* nibbles for routing - offset 0xC */ +} __attribute__ ((packed)); + + +/* Section 2.3 Host Controller Operational Registers */ +struct ehci_regs { + /* USBCMD: offset 0x00 */ + u32 command; +/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ +#define CMD_PARK (1<<11) /* enable "park" on async qh */ +#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ +#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ +#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ +#define CMD_ASE (1<<5) /* async schedule enable */ +#define CMD_PSE (1<<4) /* periodic schedule enable */ +/* 3:2 is periodic frame list size */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ + + /* USBSTS: offset 0x04 */ + u32 status; +#define STS_ASS (1<<15) /* Async Schedule Status */ +#define STS_PSS (1<<14) /* Periodic Schedule Status */ +#define STS_RECL (1<<13) /* Reclamation */ +#define STS_HALT (1<<12) /* Not running (any reason) */ +/* some bits reserved */ + /* these STS_* flags are also intr_enable bits (USBINTR) */ +#define STS_IAA (1<<5) /* Interrupted on async advance */ +#define STS_FATAL (1<<4) /* such as some PCI access errors */ +#define STS_FLR (1<<3) /* frame list rolled over */ +#define STS_PCD (1<<2) /* port change detect */ +#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ +#define STS_INT (1<<0) /* "normal" completion (short, ...) */ + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + + /* USBINTR: offset 0x08 */ + u32 intr_enable; + + /* FRINDEX: offset 0x0C */ + u32 frame_index; /* current microframe number */ + /* CTRLDSSEGMENT: offset 0x10 */ + u32 segment; /* address bits 63:32 if needed */ + /* PERIODICLISTBASE: offset 0x14 */ + u32 frame_list; /* points to periodic list */ + /* ASYNCLISTADDR: offset 0x18 */ + u32 async_next; /* address of next async queue head */ + + u32 reserved[9]; + + /* CONFIGFLAG: offset 0x40 */ + u32 configured_flag; +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + + /* PORTSC: offset 0x44 */ + u32 port_status[0]; /* up to N_PORTS */ +/* 31:23 reserved */ +#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ +#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ +#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ +/* 19:16 for port testing */ +#define PORT_LED_OFF (0<<14) +#define PORT_LED_AMBER (1<<14) +#define PORT_LED_GREEN (2<<14) +#define PORT_LED_MASK (3<<14) +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ +/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ +/* 9 reserved */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_OCC (1<<5) /* over current change */ +#define PORT_OC (1<<4) /* over current active */ +#define PORT_PEC (1<<3) /* port enable change */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) +} __attribute__ ((packed)); + +/* Appendix C, Debug port ... intended for use with special "debug devices" + * that can help if there's no serial console. (nonstandard enumeration.) + */ +struct ehci_dbg_port { + u32 control; +#define DBGP_OWNER (1<<30) +#define DBGP_ENABLED (1<<28) +#define DBGP_DONE (1<<16) +#define DBGP_INUSE (1<<10) +#define DBGP_ERRCODE(x) (((x)>>7)&0x07) +# define DBGP_ERR_BAD 1 +# define DBGP_ERR_SIGNAL 2 +#define DBGP_ERROR (1<<6) +#define DBGP_GO (1<<5) +#define DBGP_OUT (1<<4) +#define DBGP_LEN(x) (((x)>>0)&0x0f) + u32 pids; +#define DBGP_PID_GET(x) (((x)>>16)&0xff) +#define DBGP_PID_SET(data, tok) (((data)<<8)|(tok)) + u32 data03; + u32 data47; + u32 address; +#define DBGP_EPADDR(dev, ep) (((dev)<<8)|(ep)) +} __attribute__ ((packed)); + + +#define QTD_NEXT(dma) cpu_to_le32((u32)dma) + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct ehci_qtd { + /* first part defined by EHCI spec */ + __le32 hw_next; /* see EHCI 3.5.1 */ + __le32 hw_alt_next; /* see EHCI 3.5.2 */ + __le32 hw_token; /* see EHCI 3.5.3 */ +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + __le32 hw_buf[5]; /* see EHCI 3.5.4 */ + __le32 hw_buf_hi[5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + struct urb *urb; /* qtd's urb */ + size_t length; /* length of buffer */ + + u32 qtd_buffer_len; + void *buffer; + dma_addr_t buffer_dma; + void *transfer_buffer; + void *transfer_dma; +} __attribute__ ((aligned(32))); + +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK cpu_to_le32 (~0x1f) + +#define IS_SHORT_READ(token) (QTD_LENGTH(token) != 0 && QTD_PID(token) == 1) + +/* Type tag from {qh, itd, sitd, fstn}->hw_next */ +#define Q_NEXT_TYPE(dma) ((dma) & cpu_to_le32 (3 << 1)) + +/* values for that type tag */ +#define Q_TYPE_QH cpu_to_le32 (1 << 1) + +/* next async queue entry, or pointer to interrupt/periodic QH */ +#define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define EHCI_LIST_END cpu_to_le32(1) /* "null pointer" to hw */ + +/* + * Entries in periodic shadow table are pointers to one of four kinds + * of data structure. That's dictated by the hardware; a type tag is + * encoded in the low bits of the hardware's periodic schedule. Use + * Q_NEXT_TYPE to get the tag. + * + * For entries in the async schedule, the type tag always says "qh". + */ +union ehci_shadow { + struct ehci_qh *qh; /* Q_TYPE_QH */ + __le32 *hw_next; /* (all types) */ + void *ptr; +}; + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + +struct ehci_qh { + /* first part defined by EHCI spec */ + __le32 hw_next; /* see EHCI 3.6.1 */ + __le32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_HEAD 0x00008000 + __le32 hw_info2; /* see EHCI 3.6.2 */ +#define QH_SMASK 0x000000ff +#define QH_CMASK 0x0000ff00 +#define QH_HUBADDR 0x007f0000 +#define QH_HUBPORT 0x3f800000 +#define QH_MULT 0xc0000000 + __le32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct ehci_qtd) */ + __le32 hw_qtd_next; + __le32 hw_alt_next; + __le32 hw_token; + __le32 hw_buf[5]; + __le32 hw_buf_hi[5]; + + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union ehci_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + struct ehci_qtd *dummy; + struct ehci_qh *reclaim; /* next to reclaim */ + + struct oxu_hcd *oxu; + struct kref kref; + unsigned stamp; + + u8 qh_state; +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ +#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */ +#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ + + /* periodic schedule info */ + u8 usecs; /* intr bandwidth */ + u8 gap_uf; /* uframes split/csplit gap */ + u8 c_usecs; /* ... split completion bw */ + u16 tt_usecs; /* tt downstream bandwidth */ + unsigned short period; /* polling interval */ + unsigned short start; /* where polling starts */ +#define NO_FRAME ((unsigned short)~0) /* pick new start */ + struct usb_device *dev; /* access to TT */ +} __attribute__ ((aligned(32))); + +/* + * Proper OXU210HP structs + */ + +#define OXU_OTG_CORE_OFFSET 0x00400 +#define OXU_OTG_CAP_OFFSET (OXU_OTG_CORE_OFFSET + 0x100) +#define OXU_SPH_CORE_OFFSET 0x00800 +#define OXU_SPH_CAP_OFFSET (OXU_SPH_CORE_OFFSET + 0x100) + +#define OXU_OTG_MEM 0xE000 +#define OXU_SPH_MEM 0x16000 + +/* Only how many elements & element structure are specifies here. */ +/* 2 host controllers are enabled - total size <= 28 kbytes */ +#define DEFAULT_I_TDPS 1024 +#define QHEAD_NUM 16 +#define QTD_NUM 32 +#define SITD_NUM 8 +#define MURB_NUM 8 + +#define BUFFER_NUM 8 +#define BUFFER_SIZE 512 + +struct oxu_info { + struct usb_hcd *hcd[2]; +}; + +struct oxu_buf { + u8 buffer[BUFFER_SIZE]; +} __attribute__ ((aligned(BUFFER_SIZE))); + +struct oxu_onchip_mem { + struct oxu_buf db_pool[BUFFER_NUM]; + + u32 frame_list[DEFAULT_I_TDPS]; + struct ehci_qh qh_pool[QHEAD_NUM]; + struct ehci_qtd qtd_pool[QTD_NUM]; +} __attribute__ ((aligned(4 << 10))); + +#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */ + +struct oxu_murb { + struct urb urb; + struct urb *main; + u8 last; +}; + +struct oxu_hcd { /* one per controller */ + unsigned int is_otg:1; + + u8 qh_used[QHEAD_NUM]; + u8 qtd_used[QTD_NUM]; + u8 db_used[BUFFER_NUM]; + u8 murb_used[MURB_NUM]; + + struct oxu_onchip_mem __iomem *mem; + spinlock_t mem_lock; + + struct timer_list urb_timer; + + struct ehci_caps __iomem *caps; + struct ehci_regs __iomem *regs; + + __u32 hcs_params; /* cached register copy */ + spinlock_t lock; + + /* async schedule support */ + struct ehci_qh *async; + struct ehci_qh *reclaim; + unsigned reclaim_ready:1; + unsigned scanning:1; + + /* periodic schedule support */ + unsigned periodic_size; + __le32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + unsigned i_thresh; /* uframes HC might cache */ + + union ehci_shadow *pshadow; /* mirror hw periodic table */ + int next_uframe; /* scan periodic, start here */ + unsigned periodic_sched; /* periodic activity count */ + + /* per root hub port */ + unsigned long reset_done[EHCI_MAX_ROOT_PORTS]; + /* bit vectors (one bit per port) */ + unsigned long bus_suspended; /* which ports were + * already suspended at the + * start of a bus suspend + */ + unsigned long companion_ports;/* which ports are dedicated + * to the companion controller + */ + + struct timer_list watchdog; + unsigned long actions; + unsigned stamp; + unsigned long next_statechange; + u32 command; + + /* SILICON QUIRKS */ + struct list_head urb_list; /* this is the head to urb + * queue that didn't get enough + * resources + */ + struct oxu_murb *murb_pool; /* murb per split big urb */ + unsigned urb_len; + + u8 sbrn; /* packed release number */ +}; + +#define EHCI_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ +#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ +#define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */ + +enum ehci_timer_action { + TIMER_IO_WATCHDOG, + TIMER_IAA_WATCHDOG, + TIMER_ASYNC_SHRINK, + TIMER_ASYNC_OFF, +}; + +#include diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c new file mode 100644 index 00000000..9cfe3af3 --- /dev/null +++ b/drivers/usb/host/pci-quirks.c @@ -0,0 +1,989 @@ +/* + * This file contains code to reset and initialize USB host controllers. + * Some of it includes work-arounds for PCI hardware and BIOS quirks. + * It may need to run early during booting -- before USB would normally + * initialize -- to ensure that Linux doesn't use any legacy modes. + * + * Copyright (c) 1999 Martin Mares + * (and others) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pci-quirks.h" +#include "xhci-ext-caps.h" + + +#define UHCI_USBLEGSUP 0xc0 /* legacy support */ +#define UHCI_USBCMD 0 /* command register */ +#define UHCI_USBINTR 4 /* interrupt register */ +#define UHCI_USBLEGSUP_RWC 0x8f00 /* the R/WC bits */ +#define UHCI_USBLEGSUP_RO 0x5040 /* R/O and reserved bits */ +#define UHCI_USBCMD_RUN 0x0001 /* RUN/STOP bit */ +#define UHCI_USBCMD_HCRESET 0x0002 /* Host Controller reset */ +#define UHCI_USBCMD_EGSM 0x0008 /* Global Suspend Mode */ +#define UHCI_USBCMD_CONFIGURE 0x0040 /* Config Flag */ +#define UHCI_USBINTR_RESUME 0x0002 /* Resume interrupt enable */ + +#define OHCI_CONTROL 0x04 +#define OHCI_CMDSTATUS 0x08 +#define OHCI_INTRSTATUS 0x0c +#define OHCI_INTRENABLE 0x10 +#define OHCI_INTRDISABLE 0x14 +#define OHCI_FMINTERVAL 0x34 +#define OHCI_HCFS (3 << 6) /* hc functional state */ +#define OHCI_HCR (1 << 0) /* host controller reset */ +#define OHCI_OCR (1 << 3) /* ownership change request */ +#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */ +#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ +#define OHCI_INTR_OC (1 << 30) /* ownership change */ + +#define EHCI_HCC_PARAMS 0x08 /* extended capabilities */ +#define EHCI_USBCMD 0 /* command register */ +#define EHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ +#define EHCI_USBSTS 4 /* status register */ +#define EHCI_USBSTS_HALTED (1 << 12) /* HCHalted bit */ +#define EHCI_USBINTR 8 /* interrupt register */ +#define EHCI_CONFIGFLAG 0x40 /* configured flag register */ +#define EHCI_USBLEGSUP 0 /* legacy support register */ +#define EHCI_USBLEGSUP_BIOS (1 << 16) /* BIOS semaphore */ +#define EHCI_USBLEGSUP_OS (1 << 24) /* OS semaphore */ +#define EHCI_USBLEGCTLSTS 4 /* legacy control/status */ +#define EHCI_USBLEGCTLSTS_SOOE (1 << 13) /* SMI on ownership change */ + +/* AMD quirk use */ +#define AB_REG_BAR_LOW 0xe0 +#define AB_REG_BAR_HIGH 0xe1 +#define AB_REG_BAR_SB700 0xf0 +#define AB_INDX(addr) ((addr) + 0x00) +#define AB_DATA(addr) ((addr) + 0x04) +#define AX_INDXC 0x30 +#define AX_DATAC 0x34 + +#define NB_PCIE_INDX_ADDR 0xe0 +#define NB_PCIE_INDX_DATA 0xe4 +#define PCIE_P_CNTL 0x10040 +#define BIF_NB 0x10002 +#define NB_PIF0_PWRDOWN_0 0x01100012 +#define NB_PIF0_PWRDOWN_1 0x01100013 + +#define USB_INTEL_XUSB2PR 0xD0 +#define USB_INTEL_USB2PRM 0xD4 +#define USB_INTEL_USB3_PSSEN 0xD8 +#define USB_INTEL_USB3PRM 0xDC + +static struct amd_chipset_info { + struct pci_dev *nb_dev; + struct pci_dev *smbus_dev; + int nb_type; + int sb_type; + int isoc_reqs; + int probe_count; + int probe_result; +} amd_chipset; + +static DEFINE_SPINLOCK(amd_lock); + +int usb_amd_find_chipset_info(void) +{ + u8 rev = 0; + unsigned long flags; + struct amd_chipset_info info; + int ret; + + spin_lock_irqsave(&amd_lock, flags); + + /* probe only once */ + if (amd_chipset.probe_count > 0) { + amd_chipset.probe_count++; + spin_unlock_irqrestore(&amd_lock, flags); + return amd_chipset.probe_result; + } + memset(&info, 0, sizeof(info)); + spin_unlock_irqrestore(&amd_lock, flags); + + info.smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI, 0x4385, NULL); + if (info.smbus_dev) { + rev = info.smbus_dev->revision; + if (rev >= 0x40) + info.sb_type = 1; + else if (rev >= 0x30 && rev <= 0x3b) + info.sb_type = 3; + } else { + info.smbus_dev = pci_get_device(PCI_VENDOR_ID_AMD, + 0x780b, NULL); + if (!info.smbus_dev) { + ret = 0; + goto commit; + } + + rev = info.smbus_dev->revision; + if (rev >= 0x11 && rev <= 0x18) + info.sb_type = 2; + } + + if (info.sb_type == 0) { + if (info.smbus_dev) { + pci_dev_put(info.smbus_dev); + info.smbus_dev = NULL; + } + ret = 0; + goto commit; + } + + info.nb_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x9601, NULL); + if (info.nb_dev) { + info.nb_type = 1; + } else { + info.nb_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x1510, NULL); + if (info.nb_dev) { + info.nb_type = 2; + } else { + info.nb_dev = pci_get_device(PCI_VENDOR_ID_AMD, + 0x9600, NULL); + if (info.nb_dev) + info.nb_type = 3; + } + } + + ret = info.probe_result = 1; + printk(KERN_DEBUG "QUIRK: Enable AMD PLL fix\n"); + +commit: + + spin_lock_irqsave(&amd_lock, flags); + if (amd_chipset.probe_count > 0) { + /* race - someone else was faster - drop devices */ + + /* Mark that we where here */ + amd_chipset.probe_count++; + ret = amd_chipset.probe_result; + + spin_unlock_irqrestore(&amd_lock, flags); + + if (info.nb_dev) + pci_dev_put(info.nb_dev); + if (info.smbus_dev) + pci_dev_put(info.smbus_dev); + + } else { + /* no race - commit the result */ + info.probe_count++; + amd_chipset = info; + spin_unlock_irqrestore(&amd_lock, flags); + } + + return ret; +} +EXPORT_SYMBOL_GPL(usb_amd_find_chipset_info); + +/* + * The hardware normally enables the A-link power management feature, which + * lets the system lower the power consumption in idle states. + * + * This USB quirk prevents the link going into that lower power state + * during isochronous transfers. + * + * Without this quirk, isochronous stream on OHCI/EHCI/xHCI controllers of + * some AMD platforms may stutter or have breaks occasionally. + */ +static void usb_amd_quirk_pll(int disable) +{ + u32 addr, addr_low, addr_high, val; + u32 bit = disable ? 0 : 1; + unsigned long flags; + + spin_lock_irqsave(&amd_lock, flags); + + if (disable) { + amd_chipset.isoc_reqs++; + if (amd_chipset.isoc_reqs > 1) { + spin_unlock_irqrestore(&amd_lock, flags); + return; + } + } else { + amd_chipset.isoc_reqs--; + if (amd_chipset.isoc_reqs > 0) { + spin_unlock_irqrestore(&amd_lock, flags); + return; + } + } + + if (amd_chipset.sb_type == 1 || amd_chipset.sb_type == 2) { + outb_p(AB_REG_BAR_LOW, 0xcd6); + addr_low = inb_p(0xcd7); + outb_p(AB_REG_BAR_HIGH, 0xcd6); + addr_high = inb_p(0xcd7); + addr = addr_high << 8 | addr_low; + + outl_p(0x30, AB_INDX(addr)); + outl_p(0x40, AB_DATA(addr)); + outl_p(0x34, AB_INDX(addr)); + val = inl_p(AB_DATA(addr)); + } else if (amd_chipset.sb_type == 3) { + pci_read_config_dword(amd_chipset.smbus_dev, + AB_REG_BAR_SB700, &addr); + outl(AX_INDXC, AB_INDX(addr)); + outl(0x40, AB_DATA(addr)); + outl(AX_DATAC, AB_INDX(addr)); + val = inl(AB_DATA(addr)); + } else { + spin_unlock_irqrestore(&amd_lock, flags); + return; + } + + if (disable) { + val &= ~0x08; + val |= (1 << 4) | (1 << 9); + } else { + val |= 0x08; + val &= ~((1 << 4) | (1 << 9)); + } + outl_p(val, AB_DATA(addr)); + + if (!amd_chipset.nb_dev) { + spin_unlock_irqrestore(&amd_lock, flags); + return; + } + + if (amd_chipset.nb_type == 1 || amd_chipset.nb_type == 3) { + addr = PCIE_P_CNTL; + pci_write_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_ADDR, addr); + pci_read_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_DATA, &val); + + val &= ~(1 | (1 << 3) | (1 << 4) | (1 << 9) | (1 << 12)); + val |= bit | (bit << 3) | (bit << 12); + val |= ((!bit) << 4) | ((!bit) << 9); + pci_write_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_DATA, val); + + addr = BIF_NB; + pci_write_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_ADDR, addr); + pci_read_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_DATA, &val); + val &= ~(1 << 8); + val |= bit << 8; + + pci_write_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_DATA, val); + } else if (amd_chipset.nb_type == 2) { + addr = NB_PIF0_PWRDOWN_0; + pci_write_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_ADDR, addr); + pci_read_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_DATA, &val); + if (disable) + val &= ~(0x3f << 7); + else + val |= 0x3f << 7; + + pci_write_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_DATA, val); + + addr = NB_PIF0_PWRDOWN_1; + pci_write_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_ADDR, addr); + pci_read_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_DATA, &val); + if (disable) + val &= ~(0x3f << 7); + else + val |= 0x3f << 7; + + pci_write_config_dword(amd_chipset.nb_dev, + NB_PCIE_INDX_DATA, val); + } + + spin_unlock_irqrestore(&amd_lock, flags); + return; +} + +void usb_amd_quirk_pll_disable(void) +{ + usb_amd_quirk_pll(1); +} +EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_disable); + +void usb_amd_quirk_pll_enable(void) +{ + usb_amd_quirk_pll(0); +} +EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_enable); + +void usb_amd_dev_put(void) +{ + struct pci_dev *nb, *smbus; + unsigned long flags; + + spin_lock_irqsave(&amd_lock, flags); + + amd_chipset.probe_count--; + if (amd_chipset.probe_count > 0) { + spin_unlock_irqrestore(&amd_lock, flags); + return; + } + + /* save them to pci_dev_put outside of spinlock */ + nb = amd_chipset.nb_dev; + smbus = amd_chipset.smbus_dev; + + amd_chipset.nb_dev = NULL; + amd_chipset.smbus_dev = NULL; + amd_chipset.nb_type = 0; + amd_chipset.sb_type = 0; + amd_chipset.isoc_reqs = 0; + amd_chipset.probe_result = 0; + + spin_unlock_irqrestore(&amd_lock, flags); + + if (nb) + pci_dev_put(nb); + if (smbus) + pci_dev_put(smbus); +} +EXPORT_SYMBOL_GPL(usb_amd_dev_put); + +/* + * Make sure the controller is completely inactive, unable to + * generate interrupts or do DMA. + */ +void uhci_reset_hc(struct pci_dev *pdev, unsigned long base) +{ + /* Turn off PIRQ enable and SMI enable. (This also turns off the + * BIOS's USB Legacy Support.) Turn off all the R/WC bits too. + */ + pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC); + + /* Reset the HC - this will force us to get a + * new notification of any already connected + * ports due to the virtual disconnect that it + * implies. + */ + outw(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD); + mb(); + udelay(5); + if (inw(base + UHCI_USBCMD) & UHCI_USBCMD_HCRESET) + dev_warn(&pdev->dev, "HCRESET not completed yet!\n"); + + /* Just to be safe, disable interrupt requests and + * make sure the controller is stopped. + */ + outw(0, base + UHCI_USBINTR); + outw(0, base + UHCI_USBCMD); +} +EXPORT_SYMBOL_GPL(uhci_reset_hc); + +/* + * Initialize a controller that was newly discovered or has just been + * resumed. In either case we can't be sure of its previous state. + * + * Returns: 1 if the controller was reset, 0 otherwise. + */ +int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base) +{ + u16 legsup; + unsigned int cmd, intr; + + /* + * When restarting a suspended controller, we expect all the + * settings to be the same as we left them: + * + * PIRQ and SMI disabled, no R/W bits set in USBLEGSUP; + * Controller is stopped and configured with EGSM set; + * No interrupts enabled except possibly Resume Detect. + * + * If any of these conditions are violated we do a complete reset. + */ + pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup); + if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) { + dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n", + __func__, legsup); + goto reset_needed; + } + + cmd = inw(base + UHCI_USBCMD); + if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) || + !(cmd & UHCI_USBCMD_EGSM)) { + dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n", + __func__, cmd); + goto reset_needed; + } + + intr = inw(base + UHCI_USBINTR); + if (intr & (~UHCI_USBINTR_RESUME)) { + dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n", + __func__, intr); + goto reset_needed; + } + return 0; + +reset_needed: + dev_dbg(&pdev->dev, "Performing full reset\n"); + uhci_reset_hc(pdev, base); + return 1; +} +EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc); + +static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask) +{ + u16 cmd; + return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask); +} + +#define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO) +#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY) + +static void quirk_usb_handoff_uhci(struct pci_dev *pdev) +{ + unsigned long base = 0; + int i; + + if (!pio_enabled(pdev)) + return; + + for (i = 0; i < PCI_ROM_RESOURCE; i++) + if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) { + base = pci_resource_start(pdev, i); + break; + } + + if (base) + uhci_check_and_reset_hc(pdev, base); +} + +static int mmio_resource_enabled(struct pci_dev *pdev, int idx) +{ + return pci_resource_start(pdev, idx) && mmio_enabled(pdev); +} + +static void quirk_usb_handoff_ohci(struct pci_dev *pdev) +{ + void __iomem *base; + u32 control; + u32 fminterval; + int cnt; + + if (!mmio_resource_enabled(pdev, 0)) + return; + + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; + + control = readl(base + OHCI_CONTROL); + +/* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ +#ifdef __hppa__ +#define OHCI_CTRL_MASK (OHCI_CTRL_RWC | OHCI_CTRL_IR) +#else +#define OHCI_CTRL_MASK OHCI_CTRL_RWC + + if (control & OHCI_CTRL_IR) { + int wait_time = 500; /* arbitrary; 5 seconds */ + writel(OHCI_INTR_OC, base + OHCI_INTRENABLE); + writel(OHCI_OCR, base + OHCI_CMDSTATUS); + while (wait_time > 0 && + readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { + wait_time -= 10; + msleep(10); + } + if (wait_time <= 0) + dev_warn(&pdev->dev, "OHCI: BIOS handoff failed" + " (BIOS bug?) %08x\n", + readl(base + OHCI_CONTROL)); + } +#endif + + /* disable interrupts */ + writel((u32) ~0, base + OHCI_INTRDISABLE); + + /* Reset the USB bus, if the controller isn't already in RESET */ + if (control & OHCI_HCFS) { + /* Go into RESET, preserving RWC (and possibly IR) */ + writel(control & OHCI_CTRL_MASK, base + OHCI_CONTROL); + readl(base + OHCI_CONTROL); + + /* drive bus reset for at least 50 ms (7.1.7.5) */ + msleep(50); + } + + /* software reset of the controller, preserving HcFmInterval */ + fminterval = readl(base + OHCI_FMINTERVAL); + writel(OHCI_HCR, base + OHCI_CMDSTATUS); + + /* reset requires max 10 us delay */ + for (cnt = 30; cnt > 0; --cnt) { /* ... allow extra time */ + if ((readl(base + OHCI_CMDSTATUS) & OHCI_HCR) == 0) + break; + udelay(1); + } + writel(fminterval, base + OHCI_FMINTERVAL); + + /* Now the controller is safely in SUSPEND and nothing can wake it up */ + iounmap(base); +} + +static const struct dmi_system_id ehci_dmi_nohandoff_table[] = { + { + /* Pegatron Lucid (ExoPC) */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "EXOPG06411"), + DMI_MATCH(DMI_BIOS_VERSION, "Lucid-CE-133"), + }, + }, + { + /* Pegatron Lucid (Ordissimo AIRIS) */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "M11JB"), + DMI_MATCH(DMI_BIOS_VERSION, "Lucid-"), + }, + }, + { + /* Pegatron Lucid (Ordissimo) */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "Ordissimo"), + DMI_MATCH(DMI_BIOS_VERSION, "Lucid-"), + }, + }, + { + /* HASEE E200 */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HASEE"), + DMI_MATCH(DMI_BOARD_NAME, "E210"), + DMI_MATCH(DMI_BIOS_VERSION, "6.00"), + }, + }, + { } +}; + +static void ehci_bios_handoff(struct pci_dev *pdev, + void __iomem *op_reg_base, + u32 cap, u8 offset) +{ + int try_handoff = 1, tried_handoff = 0; + + /* + * The Pegatron Lucid tablet sporadically waits for 98 seconds trying + * the handoff on its unused controller. Skip it. + * + * The HASEE E200 hangs when the semaphore is set (bugzilla #77021). + */ + if (pdev->vendor == 0x8086 && (pdev->device == 0x283a || + pdev->device == 0x27cc)) { + if (dmi_check_system(ehci_dmi_nohandoff_table)) + try_handoff = 0; + } + + if (try_handoff && (cap & EHCI_USBLEGSUP_BIOS)) { + dev_dbg(&pdev->dev, "EHCI: BIOS handoff\n"); + +#if 0 +/* aleksey_gorelov@phoenix.com reports that some systems need SMI forced on, + * but that seems dubious in general (the BIOS left it off intentionally) + * and is known to prevent some systems from booting. so we won't do this + * unless maybe we can determine when we're on a system that needs SMI forced. + */ + /* BIOS workaround (?): be sure the pre-Linux code + * receives the SMI + */ + pci_read_config_dword(pdev, offset + EHCI_USBLEGCTLSTS, &val); + pci_write_config_dword(pdev, offset + EHCI_USBLEGCTLSTS, + val | EHCI_USBLEGCTLSTS_SOOE); +#endif + + /* some systems get upset if this semaphore is + * set for any other reason than forcing a BIOS + * handoff.. + */ + pci_write_config_byte(pdev, offset + 3, 1); + } + + /* if boot firmware now owns EHCI, spin till it hands it over. */ + if (try_handoff) { + int msec = 1000; + while ((cap & EHCI_USBLEGSUP_BIOS) && (msec > 0)) { + tried_handoff = 1; + msleep(10); + msec -= 10; + pci_read_config_dword(pdev, offset, &cap); + } + } + + if (cap & EHCI_USBLEGSUP_BIOS) { + /* well, possibly buggy BIOS... try to shut it down, + * and hope nothing goes too wrong + */ + if (try_handoff) + dev_warn(&pdev->dev, "EHCI: BIOS handoff failed" + " (BIOS bug?) %08x\n", cap); + pci_write_config_byte(pdev, offset + 2, 0); + } + + /* just in case, always disable EHCI SMIs */ + pci_write_config_dword(pdev, offset + EHCI_USBLEGCTLSTS, 0); + + /* If the BIOS ever owned the controller then we can't expect + * any power sessions to remain intact. + */ + if (tried_handoff) + writel(0, op_reg_base + EHCI_CONFIGFLAG); +} + +static void quirk_usb_disable_ehci(struct pci_dev *pdev) +{ + void __iomem *base, *op_reg_base; + u32 hcc_params, cap, val; + u8 offset, cap_length; + int wait_time, count = 256/4; + + if (!mmio_resource_enabled(pdev, 0)) + return; + + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; + + cap_length = readb(base); + op_reg_base = base + cap_length; + + /* EHCI 0.96 and later may have "extended capabilities" + * spec section 5.1 explains the bios handoff, e.g. for + * booting from USB disk or using a usb keyboard + */ + hcc_params = readl(base + EHCI_HCC_PARAMS); + offset = (hcc_params >> 8) & 0xff; + while (offset && --count) { + pci_read_config_dword(pdev, offset, &cap); + + switch (cap & 0xff) { + case 1: + ehci_bios_handoff(pdev, op_reg_base, cap, offset); + break; + case 0: /* Illegal reserved cap, set cap=0 so we exit */ + cap = 0; /* then fallthrough... */ + default: + dev_warn(&pdev->dev, "EHCI: unrecognized capability " + "%02x\n", cap & 0xff); + } + offset = (cap >> 8) & 0xff; + } + if (!count) + dev_printk(KERN_DEBUG, &pdev->dev, "EHCI: capability loop?\n"); + + /* + * halt EHCI & disable its interrupts in any case + */ + val = readl(op_reg_base + EHCI_USBSTS); + if ((val & EHCI_USBSTS_HALTED) == 0) { + val = readl(op_reg_base + EHCI_USBCMD); + val &= ~EHCI_USBCMD_RUN; + writel(val, op_reg_base + EHCI_USBCMD); + + wait_time = 2000; + do { + writel(0x3f, op_reg_base + EHCI_USBSTS); + udelay(100); + wait_time -= 100; + val = readl(op_reg_base + EHCI_USBSTS); + if ((val == ~(u32)0) || (val & EHCI_USBSTS_HALTED)) { + break; + } + } while (wait_time > 0); + } + writel(0, op_reg_base + EHCI_USBINTR); + writel(0x3f, op_reg_base + EHCI_USBSTS); + + iounmap(base); +} + +/* + * handshake - spin reading a register until handshake completes + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @wait_usec: timeout in microseconds + * @delay_usec: delay in microseconds to wait between polling + * + * Polls a register every delay_usec microseconds. + * Returns 0 when the mask bits have the value done. + * Returns -ETIMEDOUT if this condition is not true after + * wait_usec microseconds have passed. + */ +static int handshake(void __iomem *ptr, u32 mask, u32 done, + int wait_usec, int delay_usec) +{ + u32 result; + + do { + result = readl(ptr); + result &= mask; + if (result == done) + return 0; + udelay(delay_usec); + wait_usec -= delay_usec; + } while (wait_usec > 0); + return -ETIMEDOUT; +} + +#define PCI_DEVICE_ID_INTEL_LYNX_POINT_XHCI 0x8C31 +#define PCI_DEVICE_ID_INTEL_LYNX_POINT_LP_XHCI 0x9C31 + +bool usb_is_intel_ppt_switchable_xhci(struct pci_dev *pdev) +{ + return pdev->class == PCI_CLASS_SERIAL_USB_XHCI && + pdev->vendor == PCI_VENDOR_ID_INTEL && + pdev->device == PCI_DEVICE_ID_INTEL_PANTHERPOINT_XHCI; +} + +/* The Intel Lynx Point chipset also has switchable ports. */ +bool usb_is_intel_lpt_switchable_xhci(struct pci_dev *pdev) +{ + return pdev->class == PCI_CLASS_SERIAL_USB_XHCI && + pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_LYNX_POINT_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_LYNX_POINT_LP_XHCI); +} + +bool usb_is_intel_switchable_xhci(struct pci_dev *pdev) +{ + return usb_is_intel_ppt_switchable_xhci(pdev) || + usb_is_intel_lpt_switchable_xhci(pdev); +} +EXPORT_SYMBOL_GPL(usb_is_intel_switchable_xhci); + +/* + * Intel's Panther Point chipset has two host controllers (EHCI and xHCI) that + * share some number of ports. These ports can be switched between either + * controller. Not all of the ports under the EHCI host controller may be + * switchable. + * + * The ports should be switched over to xHCI before PCI probes for any device + * start. This avoids active devices under EHCI being disconnected during the + * port switchover, which could cause loss of data on USB storage devices, or + * failed boot when the root file system is on a USB mass storage device and is + * enumerated under EHCI first. + * + * We write into the xHC's PCI configuration space in some Intel-specific + * registers to switch the ports over. The USB 3.0 terminations and the USB + * 2.0 data wires are switched separately. We want to enable the SuperSpeed + * terminations before switching the USB 2.0 wires over, so that USB 3.0 + * devices connect at SuperSpeed, rather than at USB 2.0 speeds. + */ +void usb_enable_xhci_ports(struct pci_dev *xhci_pdev) +{ + u32 ports_available; + + /* Don't switchover the ports if the user hasn't compiled the xHCI + * driver. Otherwise they will see "dead" USB ports that don't power + * the devices. + */ + if (!IS_ENABLED(CONFIG_USB_XHCI_HCD)) { + dev_warn(&xhci_pdev->dev, + "CONFIG_USB_XHCI_HCD is turned off, " + "defaulting to EHCI.\n"); + dev_warn(&xhci_pdev->dev, + "USB 3.0 devices will work at USB 2.0 speeds.\n"); + usb_disable_xhci_ports(xhci_pdev); + return; + } + + /* Read USB3PRM, the USB 3.0 Port Routing Mask Register + * Indicate the ports that can be changed from OS. + */ + pci_read_config_dword(xhci_pdev, USB_INTEL_USB3PRM, + &ports_available); + + dev_dbg(&xhci_pdev->dev, "Configurable ports to enable SuperSpeed: 0x%x\n", + ports_available); + + /* Write USB3_PSSEN, the USB 3.0 Port SuperSpeed Enable + * Register, to turn on SuperSpeed terminations for the + * switchable ports. + */ + pci_write_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, + cpu_to_le32(ports_available)); + + pci_read_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, + &ports_available); + dev_dbg(&xhci_pdev->dev, "USB 3.0 ports that are now enabled " + "under xHCI: 0x%x\n", ports_available); + + /* Read XUSB2PRM, xHCI USB 2.0 Port Routing Mask Register + * Indicate the USB 2.0 ports to be controlled by the xHCI host. + */ + + pci_read_config_dword(xhci_pdev, USB_INTEL_USB2PRM, + &ports_available); + + dev_dbg(&xhci_pdev->dev, "Configurable USB 2.0 ports to hand over to xCHI: 0x%x\n", + ports_available); + + /* Write XUSB2PR, the xHC USB 2.0 Port Routing Register, to + * switch the USB 2.0 power and data lines over to the xHCI + * host. + */ + pci_write_config_dword(xhci_pdev, USB_INTEL_XUSB2PR, + cpu_to_le32(ports_available)); + + pci_read_config_dword(xhci_pdev, USB_INTEL_XUSB2PR, + &ports_available); + dev_dbg(&xhci_pdev->dev, "USB 2.0 ports that are now switched over " + "to xHCI: 0x%x\n", ports_available); +} +EXPORT_SYMBOL_GPL(usb_enable_xhci_ports); + +void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) +{ + pci_write_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, 0x0); + pci_write_config_dword(xhci_pdev, USB_INTEL_XUSB2PR, 0x0); +} +EXPORT_SYMBOL_GPL(usb_disable_xhci_ports); + +/** + * PCI Quirks for xHCI. + * + * Takes care of the handoff between the Pre-OS (i.e. BIOS) and the OS. + * It signals to the BIOS that the OS wants control of the host controller, + * and then waits 5 seconds for the BIOS to hand over control. + * If we timeout, assume the BIOS is broken and take control anyway. + */ +static void quirk_usb_handoff_xhci(struct pci_dev *pdev) +{ + void __iomem *base; + int ext_cap_offset; + void __iomem *op_reg_base; + u32 val; + int timeout; + int len = pci_resource_len(pdev, 0); + + if (!mmio_resource_enabled(pdev, 0)) + return; + + base = ioremap_nocache(pci_resource_start(pdev, 0), len); + if (base == NULL) + return; + + /* + * Find the Legacy Support Capability register - + * this is optional for xHCI host controllers. + */ + ext_cap_offset = xhci_find_next_cap_offset(base, XHCI_HCC_PARAMS_OFFSET); + do { + if ((ext_cap_offset + sizeof(val)) > len) { + /* We're reading garbage from the controller */ + dev_warn(&pdev->dev, + "xHCI controller failing to respond"); + return; + } + + if (!ext_cap_offset) + /* We've reached the end of the extended capabilities */ + goto hc_init; + + val = readl(base + ext_cap_offset); + if (XHCI_EXT_CAPS_ID(val) == XHCI_EXT_CAPS_LEGACY) + break; + ext_cap_offset = xhci_find_next_cap_offset(base, ext_cap_offset); + } while (1); + + /* If the BIOS owns the HC, signal that the OS wants it, and wait */ + if (val & XHCI_HC_BIOS_OWNED) { + writel(val | XHCI_HC_OS_OWNED, base + ext_cap_offset); + + /* Wait for 5 seconds with 10 microsecond polling interval */ + timeout = handshake(base + ext_cap_offset, XHCI_HC_BIOS_OWNED, + 0, 5000, 10); + + /* Assume a buggy BIOS and take HC ownership anyway */ + if (timeout) { + dev_warn(&pdev->dev, "xHCI BIOS handoff failed" + " (BIOS bug ?) %08x\n", val); + writel(val & ~XHCI_HC_BIOS_OWNED, base + ext_cap_offset); + } + } + + val = readl(base + ext_cap_offset + XHCI_LEGACY_CONTROL_OFFSET); + /* Mask off (turn off) any enabled SMIs */ + val &= XHCI_LEGACY_DISABLE_SMI; + /* Mask all SMI events bits, RW1C */ + val |= XHCI_LEGACY_SMI_EVENTS; + /* Disable any BIOS SMIs and clear all SMI events*/ + writel(val, base + ext_cap_offset + XHCI_LEGACY_CONTROL_OFFSET); + +hc_init: + if (usb_is_intel_switchable_xhci(pdev)) + usb_enable_xhci_ports(pdev); + + op_reg_base = base + XHCI_HC_LENGTH(readl(base)); + + /* Wait for the host controller to be ready before writing any + * operational or runtime registers. Wait 5 seconds and no more. + */ + timeout = handshake(op_reg_base + XHCI_STS_OFFSET, XHCI_STS_CNR, 0, + 5000, 10); + /* Assume a buggy HC and start HC initialization anyway */ + if (timeout) { + val = readl(op_reg_base + XHCI_STS_OFFSET); + dev_warn(&pdev->dev, + "xHCI HW not ready after 5 sec (HC bug?) " + "status = 0x%x\n", val); + } + + /* Send the halt and disable interrupts command */ + val = readl(op_reg_base + XHCI_CMD_OFFSET); + val &= ~(XHCI_CMD_RUN | XHCI_IRQS); + writel(val, op_reg_base + XHCI_CMD_OFFSET); + + /* Wait for the HC to halt - poll every 125 usec (one microframe). */ + timeout = handshake(op_reg_base + XHCI_STS_OFFSET, XHCI_STS_HALT, 1, + XHCI_MAX_HALT_USEC, 125); + if (timeout) { + val = readl(op_reg_base + XHCI_STS_OFFSET); + dev_warn(&pdev->dev, + "xHCI HW did not halt within %d usec " + "status = 0x%x\n", XHCI_MAX_HALT_USEC, val); + } + + iounmap(base); +} + +static void quirk_usb_early_handoff(struct pci_dev *pdev) +{ + /* Skip Netlogic mips SoC's internal PCI USB controller. + * This device does not need/support EHCI/OHCI handoff + */ + if (pdev->vendor == 0x184e) /* vendor Netlogic */ + return; + if (pdev->class != PCI_CLASS_SERIAL_USB_UHCI && + pdev->class != PCI_CLASS_SERIAL_USB_OHCI && + pdev->class != PCI_CLASS_SERIAL_USB_EHCI && + pdev->class != PCI_CLASS_SERIAL_USB_XHCI) + return; + + if (pci_enable_device(pdev) < 0) { + dev_warn(&pdev->dev, "Can't enable PCI device, " + "BIOS handoff failed.\n"); + return; + } + if (pdev->class == PCI_CLASS_SERIAL_USB_UHCI) + quirk_usb_handoff_uhci(pdev); + else if (pdev->class == PCI_CLASS_SERIAL_USB_OHCI) + quirk_usb_handoff_ohci(pdev); + else if (pdev->class == PCI_CLASS_SERIAL_USB_EHCI) + quirk_usb_disable_ehci(pdev); + else if (pdev->class == PCI_CLASS_SERIAL_USB_XHCI) + quirk_usb_handoff_xhci(pdev); + pci_disable_device(pdev); +} +DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_SERIAL_USB, 8, quirk_usb_early_handoff); diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h new file mode 100644 index 00000000..7f69a391 --- /dev/null +++ b/drivers/usb/host/pci-quirks.h @@ -0,0 +1,21 @@ +#ifndef __LINUX_USB_PCI_QUIRKS_H +#define __LINUX_USB_PCI_QUIRKS_H + +#ifdef CONFIG_PCI +void uhci_reset_hc(struct pci_dev *pdev, unsigned long base); +int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base); +int usb_amd_find_chipset_info(void); +void usb_amd_dev_put(void); +void usb_amd_quirk_pll_disable(void); +void usb_amd_quirk_pll_enable(void); +bool usb_is_intel_switchable_xhci(struct pci_dev *pdev); +void usb_enable_xhci_ports(struct pci_dev *xhci_pdev); +void usb_disable_xhci_ports(struct pci_dev *xhci_pdev); +#else +static inline void usb_amd_quirk_pll_disable(void) {} +static inline void usb_amd_quirk_pll_enable(void) {} +static inline void usb_amd_dev_put(void) {} +static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {} +#endif /* CONFIG_PCI */ + +#endif /* __LINUX_USB_PCI_QUIRKS_H */ diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c new file mode 100644 index 00000000..a6fd8f53 --- /dev/null +++ b/drivers/usb/host/r8a66597-hcd.c @@ -0,0 +1,2543 @@ +/* + * R8A66597 HCD (Host Controller Driver) + * + * Copyright (C) 2006-2007 Renesas Solutions Corp. + * Portions Copyright (C) 2004 Psion Teklogix (for NetBook PRO) + * Portions Copyright (C) 2004-2005 David Brownell + * Portions Copyright (C) 1999 Roman Weissgaerber + * + * Author : Yoshihiro Shimoda + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "r8a66597.h" + +MODULE_DESCRIPTION("R8A66597 USB Host Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_ALIAS("platform:r8a66597_hcd"); + +#define DRIVER_VERSION "2009-05-26" + +static const char hcd_name[] = "r8a66597_hcd"; + +static void packet_write(struct r8a66597 *r8a66597, u16 pipenum); +static int r8a66597_get_frame(struct usb_hcd *hcd); + +/* this function must be called with interrupt disabled */ +static void enable_pipe_irq(struct r8a66597 *r8a66597, u16 pipenum, + unsigned long reg) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, INTENB0); + r8a66597_bclr(r8a66597, BEMPE | NRDYE | BRDYE, INTENB0); + r8a66597_bset(r8a66597, 1 << pipenum, reg); + r8a66597_write(r8a66597, tmp, INTENB0); +} + +/* this function must be called with interrupt disabled */ +static void disable_pipe_irq(struct r8a66597 *r8a66597, u16 pipenum, + unsigned long reg) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, INTENB0); + r8a66597_bclr(r8a66597, BEMPE | NRDYE | BRDYE, INTENB0); + r8a66597_bclr(r8a66597, 1 << pipenum, reg); + r8a66597_write(r8a66597, tmp, INTENB0); +} + +static void set_devadd_reg(struct r8a66597 *r8a66597, u8 r8a66597_address, + u16 usbspd, u8 upphub, u8 hubport, int port) +{ + u16 val; + unsigned long devadd_reg = get_devadd_addr(r8a66597_address); + + val = (upphub << 11) | (hubport << 8) | (usbspd << 6) | (port & 0x0001); + r8a66597_write(r8a66597, val, devadd_reg); +} + +static int r8a66597_clock_enable(struct r8a66597 *r8a66597) +{ + u16 tmp; + int i = 0; + + if (r8a66597->pdata->on_chip) { + clk_enable(r8a66597->clk); + do { + r8a66597_write(r8a66597, SCKE, SYSCFG0); + tmp = r8a66597_read(r8a66597, SYSCFG0); + if (i++ > 1000) { + printk(KERN_ERR "r8a66597: reg access fail.\n"); + return -ENXIO; + } + } while ((tmp & SCKE) != SCKE); + r8a66597_write(r8a66597, 0x04, 0x02); + } else { + do { + r8a66597_write(r8a66597, USBE, SYSCFG0); + tmp = r8a66597_read(r8a66597, SYSCFG0); + if (i++ > 1000) { + printk(KERN_ERR "r8a66597: reg access fail.\n"); + return -ENXIO; + } + } while ((tmp & USBE) != USBE); + r8a66597_bclr(r8a66597, USBE, SYSCFG0); + r8a66597_mdfy(r8a66597, get_xtal_from_pdata(r8a66597->pdata), + XTAL, SYSCFG0); + + i = 0; + r8a66597_bset(r8a66597, XCKE, SYSCFG0); + do { + msleep(1); + tmp = r8a66597_read(r8a66597, SYSCFG0); + if (i++ > 500) { + printk(KERN_ERR "r8a66597: reg access fail.\n"); + return -ENXIO; + } + } while ((tmp & SCKE) != SCKE); + } + + return 0; +} + +static void r8a66597_clock_disable(struct r8a66597 *r8a66597) +{ + r8a66597_bclr(r8a66597, SCKE, SYSCFG0); + udelay(1); + + if (r8a66597->pdata->on_chip) { + clk_disable(r8a66597->clk); + } else { + r8a66597_bclr(r8a66597, PLLC, SYSCFG0); + r8a66597_bclr(r8a66597, XCKE, SYSCFG0); + r8a66597_bclr(r8a66597, USBE, SYSCFG0); + } +} + +static void r8a66597_enable_port(struct r8a66597 *r8a66597, int port) +{ + u16 val; + + val = port ? DRPD : DCFM | DRPD; + r8a66597_bset(r8a66597, val, get_syscfg_reg(port)); + r8a66597_bset(r8a66597, HSE, get_syscfg_reg(port)); + + r8a66597_write(r8a66597, BURST | CPU_ADR_RD_WR, get_dmacfg_reg(port)); + r8a66597_bclr(r8a66597, DTCHE, get_intenb_reg(port)); + r8a66597_bset(r8a66597, ATTCHE, get_intenb_reg(port)); +} + +static void r8a66597_disable_port(struct r8a66597 *r8a66597, int port) +{ + u16 val, tmp; + + r8a66597_write(r8a66597, 0, get_intenb_reg(port)); + r8a66597_write(r8a66597, 0, get_intsts_reg(port)); + + r8a66597_port_power(r8a66597, port, 0); + + do { + tmp = r8a66597_read(r8a66597, SOFCFG) & EDGESTS; + udelay(640); + } while (tmp == EDGESTS); + + val = port ? DRPD : DCFM | DRPD; + r8a66597_bclr(r8a66597, val, get_syscfg_reg(port)); + r8a66597_bclr(r8a66597, HSE, get_syscfg_reg(port)); +} + +static int enable_controller(struct r8a66597 *r8a66597) +{ + int ret, port; + u16 vif = r8a66597->pdata->vif ? LDRV : 0; + u16 irq_sense = r8a66597->irq_sense_low ? INTL : 0; + u16 endian = r8a66597->pdata->endian ? BIGEND : 0; + + ret = r8a66597_clock_enable(r8a66597); + if (ret < 0) + return ret; + + r8a66597_bset(r8a66597, vif & LDRV, PINCFG); + r8a66597_bset(r8a66597, USBE, SYSCFG0); + + r8a66597_bset(r8a66597, BEMPE | NRDYE | BRDYE, INTENB0); + r8a66597_bset(r8a66597, irq_sense & INTL, SOFCFG); + r8a66597_bset(r8a66597, BRDY0, BRDYENB); + r8a66597_bset(r8a66597, BEMP0, BEMPENB); + + r8a66597_bset(r8a66597, endian & BIGEND, CFIFOSEL); + r8a66597_bset(r8a66597, endian & BIGEND, D0FIFOSEL); + r8a66597_bset(r8a66597, endian & BIGEND, D1FIFOSEL); + r8a66597_bset(r8a66597, TRNENSEL, SOFCFG); + + r8a66597_bset(r8a66597, SIGNE | SACKE, INTENB1); + + for (port = 0; port < r8a66597->max_root_hub; port++) + r8a66597_enable_port(r8a66597, port); + + return 0; +} + +static void disable_controller(struct r8a66597 *r8a66597) +{ + int port; + + /* disable interrupts */ + r8a66597_write(r8a66597, 0, INTENB0); + r8a66597_write(r8a66597, 0, INTENB1); + r8a66597_write(r8a66597, 0, BRDYENB); + r8a66597_write(r8a66597, 0, BEMPENB); + r8a66597_write(r8a66597, 0, NRDYENB); + + /* clear status */ + r8a66597_write(r8a66597, 0, BRDYSTS); + r8a66597_write(r8a66597, 0, NRDYSTS); + r8a66597_write(r8a66597, 0, BEMPSTS); + + for (port = 0; port < r8a66597->max_root_hub; port++) + r8a66597_disable_port(r8a66597, port); + + r8a66597_clock_disable(r8a66597); +} + +static int get_parent_r8a66597_address(struct r8a66597 *r8a66597, + struct usb_device *udev) +{ + struct r8a66597_device *dev; + + if (udev->parent && udev->parent->devnum != 1) + udev = udev->parent; + + dev = dev_get_drvdata(&udev->dev); + if (dev) + return dev->address; + else + return 0; +} + +static int is_child_device(char *devpath) +{ + return (devpath[2] ? 1 : 0); +} + +static int is_hub_limit(char *devpath) +{ + return ((strlen(devpath) >= 4) ? 1 : 0); +} + +static void get_port_number(struct r8a66597 *r8a66597, + char *devpath, u16 *root_port, u16 *hub_port) +{ + if (root_port) { + *root_port = (devpath[0] & 0x0F) - 1; + if (*root_port >= r8a66597->max_root_hub) + printk(KERN_ERR "r8a66597: Illegal root port number.\n"); + } + if (hub_port) + *hub_port = devpath[2] & 0x0F; +} + +static u16 get_r8a66597_usb_speed(enum usb_device_speed speed) +{ + u16 usbspd = 0; + + switch (speed) { + case USB_SPEED_LOW: + usbspd = LSMODE; + break; + case USB_SPEED_FULL: + usbspd = FSMODE; + break; + case USB_SPEED_HIGH: + usbspd = HSMODE; + break; + default: + printk(KERN_ERR "r8a66597: unknown speed\n"); + break; + } + + return usbspd; +} + +static void set_child_connect_map(struct r8a66597 *r8a66597, int address) +{ + int idx; + + idx = address / 32; + r8a66597->child_connect_map[idx] |= 1 << (address % 32); +} + +static void put_child_connect_map(struct r8a66597 *r8a66597, int address) +{ + int idx; + + idx = address / 32; + r8a66597->child_connect_map[idx] &= ~(1 << (address % 32)); +} + +static void set_pipe_reg_addr(struct r8a66597_pipe *pipe, u8 dma_ch) +{ + u16 pipenum = pipe->info.pipenum; + const unsigned long fifoaddr[] = {D0FIFO, D1FIFO, CFIFO}; + const unsigned long fifosel[] = {D0FIFOSEL, D1FIFOSEL, CFIFOSEL}; + const unsigned long fifoctr[] = {D0FIFOCTR, D1FIFOCTR, CFIFOCTR}; + + if (dma_ch > R8A66597_PIPE_NO_DMA) /* dma fifo not use? */ + dma_ch = R8A66597_PIPE_NO_DMA; + + pipe->fifoaddr = fifoaddr[dma_ch]; + pipe->fifosel = fifosel[dma_ch]; + pipe->fifoctr = fifoctr[dma_ch]; + + if (pipenum == 0) + pipe->pipectr = DCPCTR; + else + pipe->pipectr = get_pipectr_addr(pipenum); + + if (check_bulk_or_isoc(pipenum)) { + pipe->pipetre = get_pipetre_addr(pipenum); + pipe->pipetrn = get_pipetrn_addr(pipenum); + } else { + pipe->pipetre = 0; + pipe->pipetrn = 0; + } +} + +static struct r8a66597_device * +get_urb_to_r8a66597_dev(struct r8a66597 *r8a66597, struct urb *urb) +{ + if (usb_pipedevice(urb->pipe) == 0) + return &r8a66597->device0; + + return dev_get_drvdata(&urb->dev->dev); +} + +static int make_r8a66597_device(struct r8a66597 *r8a66597, + struct urb *urb, u8 addr) +{ + struct r8a66597_device *dev; + int usb_address = urb->setup_packet[2]; /* urb->pipe is address 0 */ + + dev = kzalloc(sizeof(struct r8a66597_device), GFP_ATOMIC); + if (dev == NULL) + return -ENOMEM; + + dev_set_drvdata(&urb->dev->dev, dev); + dev->udev = urb->dev; + dev->address = addr; + dev->usb_address = usb_address; + dev->state = USB_STATE_ADDRESS; + dev->ep_in_toggle = 0; + dev->ep_out_toggle = 0; + INIT_LIST_HEAD(&dev->device_list); + list_add_tail(&dev->device_list, &r8a66597->child_device); + + get_port_number(r8a66597, urb->dev->devpath, + &dev->root_port, &dev->hub_port); + if (!is_child_device(urb->dev->devpath)) + r8a66597->root_hub[dev->root_port].dev = dev; + + set_devadd_reg(r8a66597, dev->address, + get_r8a66597_usb_speed(urb->dev->speed), + get_parent_r8a66597_address(r8a66597, urb->dev), + dev->hub_port, dev->root_port); + + return 0; +} + +/* this function must be called with interrupt disabled */ +static u8 alloc_usb_address(struct r8a66597 *r8a66597, struct urb *urb) +{ + u8 addr; /* R8A66597's address */ + struct r8a66597_device *dev; + + if (is_hub_limit(urb->dev->devpath)) { + dev_err(&urb->dev->dev, "External hub limit reached.\n"); + return 0; + } + + dev = get_urb_to_r8a66597_dev(r8a66597, urb); + if (dev && dev->state >= USB_STATE_ADDRESS) + return dev->address; + + for (addr = 1; addr <= R8A66597_MAX_DEVICE; addr++) { + if (r8a66597->address_map & (1 << addr)) + continue; + + dev_dbg(&urb->dev->dev, "alloc_address: r8a66597_addr=%d\n", addr); + r8a66597->address_map |= 1 << addr; + + if (make_r8a66597_device(r8a66597, urb, addr) < 0) + return 0; + + return addr; + } + + dev_err(&urb->dev->dev, + "cannot communicate with a USB device more than 10.(%x)\n", + r8a66597->address_map); + + return 0; +} + +/* this function must be called with interrupt disabled */ +static void free_usb_address(struct r8a66597 *r8a66597, + struct r8a66597_device *dev, int reset) +{ + int port; + + if (!dev) + return; + + dev_dbg(&dev->udev->dev, "free_addr: addr=%d\n", dev->address); + + dev->state = USB_STATE_DEFAULT; + r8a66597->address_map &= ~(1 << dev->address); + dev->address = 0; + /* + * Only when resetting USB, it is necessary to erase drvdata. When + * a usb device with usb hub is disconnect, "dev->udev" is already + * freed on usb_desconnect(). So we cannot access the data. + */ + if (reset) + dev_set_drvdata(&dev->udev->dev, NULL); + list_del(&dev->device_list); + kfree(dev); + + for (port = 0; port < r8a66597->max_root_hub; port++) { + if (r8a66597->root_hub[port].dev == dev) { + r8a66597->root_hub[port].dev = NULL; + break; + } + } +} + +static void r8a66597_reg_wait(struct r8a66597 *r8a66597, unsigned long reg, + u16 mask, u16 loop) +{ + u16 tmp; + int i = 0; + + do { + tmp = r8a66597_read(r8a66597, reg); + if (i++ > 1000000) { + printk(KERN_ERR "r8a66597: register%lx, loop %x " + "is timeout\n", reg, loop); + break; + } + ndelay(1); + } while ((tmp & mask) != loop); +} + +/* this function must be called with interrupt disabled */ +static void pipe_start(struct r8a66597 *r8a66597, struct r8a66597_pipe *pipe) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, pipe->pipectr) & PID; + if ((pipe->info.pipenum != 0) & ((tmp & PID_STALL) != 0)) /* stall? */ + r8a66597_mdfy(r8a66597, PID_NAK, PID, pipe->pipectr); + r8a66597_mdfy(r8a66597, PID_BUF, PID, pipe->pipectr); +} + +/* this function must be called with interrupt disabled */ +static void pipe_stop(struct r8a66597 *r8a66597, struct r8a66597_pipe *pipe) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, pipe->pipectr) & PID; + if ((tmp & PID_STALL11) != PID_STALL11) /* force stall? */ + r8a66597_mdfy(r8a66597, PID_STALL, PID, pipe->pipectr); + r8a66597_mdfy(r8a66597, PID_NAK, PID, pipe->pipectr); + r8a66597_reg_wait(r8a66597, pipe->pipectr, PBUSY, 0); +} + +/* this function must be called with interrupt disabled */ +static void clear_all_buffer(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe) +{ + u16 tmp; + + if (!pipe || pipe->info.pipenum == 0) + return; + + pipe_stop(r8a66597, pipe); + r8a66597_bset(r8a66597, ACLRM, pipe->pipectr); + tmp = r8a66597_read(r8a66597, pipe->pipectr); + tmp = r8a66597_read(r8a66597, pipe->pipectr); + tmp = r8a66597_read(r8a66597, pipe->pipectr); + r8a66597_bclr(r8a66597, ACLRM, pipe->pipectr); +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_pipe_toggle(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, int toggle) +{ + if (toggle) + r8a66597_bset(r8a66597, SQSET, pipe->pipectr); + else + r8a66597_bset(r8a66597, SQCLR, pipe->pipectr); +} + +static inline unsigned short mbw_value(struct r8a66597 *r8a66597) +{ + if (r8a66597->pdata->on_chip) + return MBW_32; + else + return MBW_16; +} + +/* this function must be called with interrupt disabled */ +static inline void cfifo_change(struct r8a66597 *r8a66597, u16 pipenum) +{ + unsigned short mbw = mbw_value(r8a66597); + + r8a66597_mdfy(r8a66597, mbw | pipenum, mbw | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, pipenum); +} + +/* this function must be called with interrupt disabled */ +static inline void fifo_change_from_pipe(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe) +{ + unsigned short mbw = mbw_value(r8a66597); + + cfifo_change(r8a66597, 0); + r8a66597_mdfy(r8a66597, mbw | 0, mbw | CURPIPE, D0FIFOSEL); + r8a66597_mdfy(r8a66597, mbw | 0, mbw | CURPIPE, D1FIFOSEL); + + r8a66597_mdfy(r8a66597, mbw | pipe->info.pipenum, mbw | CURPIPE, + pipe->fifosel); + r8a66597_reg_wait(r8a66597, pipe->fifosel, CURPIPE, pipe->info.pipenum); +} + +static u16 r8a66597_get_pipenum(struct urb *urb, struct usb_host_endpoint *hep) +{ + struct r8a66597_pipe *pipe = hep->hcpriv; + + if (usb_pipeendpoint(urb->pipe) == 0) + return 0; + else + return pipe->info.pipenum; +} + +static u16 get_urb_to_r8a66597_addr(struct r8a66597 *r8a66597, struct urb *urb) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + + return (usb_pipedevice(urb->pipe) == 0) ? 0 : dev->address; +} + +static unsigned short *get_toggle_pointer(struct r8a66597_device *dev, + int urb_pipe) +{ + if (!dev) + return NULL; + + return usb_pipein(urb_pipe) ? &dev->ep_in_toggle : &dev->ep_out_toggle; +} + +/* this function must be called with interrupt disabled */ +static void pipe_toggle_set(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, + struct urb *urb, int set) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + unsigned char endpoint = usb_pipeendpoint(urb->pipe); + unsigned short *toggle = get_toggle_pointer(dev, urb->pipe); + + if (!toggle) + return; + + if (set) + *toggle |= 1 << endpoint; + else + *toggle &= ~(1 << endpoint); +} + +/* this function must be called with interrupt disabled */ +static void pipe_toggle_save(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, + struct urb *urb) +{ + if (r8a66597_read(r8a66597, pipe->pipectr) & SQMON) + pipe_toggle_set(r8a66597, pipe, urb, 1); + else + pipe_toggle_set(r8a66597, pipe, urb, 0); +} + +/* this function must be called with interrupt disabled */ +static void pipe_toggle_restore(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, + struct urb *urb) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + unsigned char endpoint = usb_pipeendpoint(urb->pipe); + unsigned short *toggle = get_toggle_pointer(dev, urb->pipe); + + if (!toggle) + return; + + r8a66597_pipe_toggle(r8a66597, pipe, *toggle & (1 << endpoint)); +} + +/* this function must be called with interrupt disabled */ +static void pipe_buffer_setting(struct r8a66597 *r8a66597, + struct r8a66597_pipe_info *info) +{ + u16 val = 0; + + if (info->pipenum == 0) + return; + + r8a66597_bset(r8a66597, ACLRM, get_pipectr_addr(info->pipenum)); + r8a66597_bclr(r8a66597, ACLRM, get_pipectr_addr(info->pipenum)); + r8a66597_write(r8a66597, info->pipenum, PIPESEL); + if (!info->dir_in) + val |= R8A66597_DIR; + if (info->type == R8A66597_BULK && info->dir_in) + val |= R8A66597_DBLB | R8A66597_SHTNAK; + val |= info->type | info->epnum; + r8a66597_write(r8a66597, val, PIPECFG); + + r8a66597_write(r8a66597, (info->buf_bsize << 10) | (info->bufnum), + PIPEBUF); + r8a66597_write(r8a66597, make_devsel(info->address) | info->maxpacket, + PIPEMAXP); + r8a66597_write(r8a66597, info->interval, PIPEPERI); +} + +/* this function must be called with interrupt disabled */ +static void pipe_setting(struct r8a66597 *r8a66597, struct r8a66597_td *td) +{ + struct r8a66597_pipe_info *info; + struct urb *urb = td->urb; + + if (td->pipenum > 0) { + info = &td->pipe->info; + cfifo_change(r8a66597, 0); + pipe_buffer_setting(r8a66597, info); + + if (!usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe)) && + !usb_pipecontrol(urb->pipe)) { + r8a66597_pipe_toggle(r8a66597, td->pipe, 0); + pipe_toggle_set(r8a66597, td->pipe, urb, 0); + clear_all_buffer(r8a66597, td->pipe); + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), 1); + } + pipe_toggle_restore(r8a66597, td->pipe, urb); + } +} + +/* this function must be called with interrupt disabled */ +static u16 get_empty_pipenum(struct r8a66597 *r8a66597, + struct usb_endpoint_descriptor *ep) +{ + u16 array[R8A66597_MAX_NUM_PIPE], i = 0, min; + + memset(array, 0, sizeof(array)); + switch (usb_endpoint_type(ep)) { + case USB_ENDPOINT_XFER_BULK: + if (usb_endpoint_dir_in(ep)) + array[i++] = 4; + else { + array[i++] = 3; + array[i++] = 5; + } + break; + case USB_ENDPOINT_XFER_INT: + if (usb_endpoint_dir_in(ep)) { + array[i++] = 6; + array[i++] = 7; + array[i++] = 8; + } else + array[i++] = 9; + break; + case USB_ENDPOINT_XFER_ISOC: + if (usb_endpoint_dir_in(ep)) + array[i++] = 2; + else + array[i++] = 1; + break; + default: + printk(KERN_ERR "r8a66597: Illegal type\n"); + return 0; + } + + i = 1; + min = array[0]; + while (array[i] != 0) { + if (r8a66597->pipe_cnt[min] > r8a66597->pipe_cnt[array[i]]) + min = array[i]; + i++; + } + + return min; +} + +static u16 get_r8a66597_type(__u8 type) +{ + u16 r8a66597_type; + + switch (type) { + case USB_ENDPOINT_XFER_BULK: + r8a66597_type = R8A66597_BULK; + break; + case USB_ENDPOINT_XFER_INT: + r8a66597_type = R8A66597_INT; + break; + case USB_ENDPOINT_XFER_ISOC: + r8a66597_type = R8A66597_ISO; + break; + default: + printk(KERN_ERR "r8a66597: Illegal type\n"); + r8a66597_type = 0x0000; + break; + } + + return r8a66597_type; +} + +static u16 get_bufnum(u16 pipenum) +{ + u16 bufnum = 0; + + if (pipenum == 0) + bufnum = 0; + else if (check_bulk_or_isoc(pipenum)) + bufnum = 8 + (pipenum - 1) * R8A66597_BUF_BSIZE*2; + else if (check_interrupt(pipenum)) + bufnum = 4 + (pipenum - 6); + else + printk(KERN_ERR "r8a66597: Illegal pipenum (%d)\n", pipenum); + + return bufnum; +} + +static u16 get_buf_bsize(u16 pipenum) +{ + u16 buf_bsize = 0; + + if (pipenum == 0) + buf_bsize = 3; + else if (check_bulk_or_isoc(pipenum)) + buf_bsize = R8A66597_BUF_BSIZE - 1; + else if (check_interrupt(pipenum)) + buf_bsize = 0; + else + printk(KERN_ERR "r8a66597: Illegal pipenum (%d)\n", pipenum); + + return buf_bsize; +} + +/* this function must be called with interrupt disabled */ +static void enable_r8a66597_pipe_dma(struct r8a66597 *r8a66597, + struct r8a66597_device *dev, + struct r8a66597_pipe *pipe, + struct urb *urb) +{ + int i; + struct r8a66597_pipe_info *info = &pipe->info; + unsigned short mbw = mbw_value(r8a66597); + + /* pipe dma is only for external controlles */ + if (r8a66597->pdata->on_chip) + return; + + if ((pipe->info.pipenum != 0) && (info->type != R8A66597_INT)) { + for (i = 0; i < R8A66597_MAX_DMA_CHANNEL; i++) { + if ((r8a66597->dma_map & (1 << i)) != 0) + continue; + + dev_info(&dev->udev->dev, + "address %d, EndpointAddress 0x%02x use " + "DMA FIFO\n", usb_pipedevice(urb->pipe), + info->dir_in ? + USB_ENDPOINT_DIR_MASK + info->epnum + : info->epnum); + + r8a66597->dma_map |= 1 << i; + dev->dma_map |= 1 << i; + set_pipe_reg_addr(pipe, i); + + cfifo_change(r8a66597, 0); + r8a66597_mdfy(r8a66597, mbw | pipe->info.pipenum, + mbw | CURPIPE, pipe->fifosel); + + r8a66597_reg_wait(r8a66597, pipe->fifosel, CURPIPE, + pipe->info.pipenum); + r8a66597_bset(r8a66597, BCLR, pipe->fifoctr); + break; + } + } +} + +/* this function must be called with interrupt disabled */ +static void enable_r8a66597_pipe(struct r8a66597 *r8a66597, struct urb *urb, + struct usb_host_endpoint *hep, + struct r8a66597_pipe_info *info) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + struct r8a66597_pipe *pipe = hep->hcpriv; + + dev_dbg(&dev->udev->dev, "enable_pipe:\n"); + + pipe->info = *info; + set_pipe_reg_addr(pipe, R8A66597_PIPE_NO_DMA); + r8a66597->pipe_cnt[pipe->info.pipenum]++; + dev->pipe_cnt[pipe->info.pipenum]++; + + enable_r8a66597_pipe_dma(r8a66597, dev, pipe, urb); +} + +static void r8a66597_urb_done(struct r8a66597 *r8a66597, struct urb *urb, + int status) +__releases(r8a66597->lock) +__acquires(r8a66597->lock) +{ + if (usb_pipein(urb->pipe) && usb_pipetype(urb->pipe) != PIPE_CONTROL) { + void *ptr; + + for (ptr = urb->transfer_buffer; + ptr < urb->transfer_buffer + urb->transfer_buffer_length; + ptr += PAGE_SIZE) + flush_dcache_page(virt_to_page(ptr)); + } + + usb_hcd_unlink_urb_from_ep(r8a66597_to_hcd(r8a66597), urb); + spin_unlock(&r8a66597->lock); + usb_hcd_giveback_urb(r8a66597_to_hcd(r8a66597), urb, status); + spin_lock(&r8a66597->lock); +} + +/* this function must be called with interrupt disabled */ +static void force_dequeue(struct r8a66597 *r8a66597, u16 pipenum, u16 address) +{ + struct r8a66597_td *td, *next; + struct urb *urb; + struct list_head *list = &r8a66597->pipe_queue[pipenum]; + + if (list_empty(list)) + return; + + list_for_each_entry_safe(td, next, list, queue) { + if (td->address != address) + continue; + + urb = td->urb; + list_del(&td->queue); + kfree(td); + + if (urb) + r8a66597_urb_done(r8a66597, urb, -ENODEV); + + break; + } +} + +/* this function must be called with interrupt disabled */ +static void disable_r8a66597_pipe_all(struct r8a66597 *r8a66597, + struct r8a66597_device *dev) +{ + int check_ep0 = 0; + u16 pipenum; + + if (!dev) + return; + + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + if (!dev->pipe_cnt[pipenum]) + continue; + + if (!check_ep0) { + check_ep0 = 1; + force_dequeue(r8a66597, 0, dev->address); + } + + r8a66597->pipe_cnt[pipenum] -= dev->pipe_cnt[pipenum]; + dev->pipe_cnt[pipenum] = 0; + force_dequeue(r8a66597, pipenum, dev->address); + } + + dev_dbg(&dev->udev->dev, "disable_pipe\n"); + + r8a66597->dma_map &= ~(dev->dma_map); + dev->dma_map = 0; +} + +static u16 get_interval(struct urb *urb, __u8 interval) +{ + u16 time = 1; + int i; + + if (urb->dev->speed == USB_SPEED_HIGH) { + if (interval > IITV) + time = IITV; + else + time = interval ? interval - 1 : 0; + } else { + if (interval > 128) { + time = IITV; + } else { + /* calculate the nearest value for PIPEPERI */ + for (i = 0; i < 7; i++) { + if ((1 << i) < interval && + (1 << (i + 1) > interval)) + time = 1 << i; + } + } + } + + return time; +} + +static unsigned long get_timer_interval(struct urb *urb, __u8 interval) +{ + __u8 i; + unsigned long time = 1; + + if (usb_pipeisoc(urb->pipe)) + return 0; + + if (get_r8a66597_usb_speed(urb->dev->speed) == HSMODE) { + for (i = 0; i < (interval - 1); i++) + time *= 2; + time = time * 125 / 1000; /* uSOF -> msec */ + } else { + time = interval; + } + + return time; +} + +/* this function must be called with interrupt disabled */ +static void init_pipe_info(struct r8a66597 *r8a66597, struct urb *urb, + struct usb_host_endpoint *hep, + struct usb_endpoint_descriptor *ep) +{ + struct r8a66597_pipe_info info; + + info.pipenum = get_empty_pipenum(r8a66597, ep); + info.address = get_urb_to_r8a66597_addr(r8a66597, urb); + info.epnum = usb_endpoint_num(ep); + info.maxpacket = usb_endpoint_maxp(ep); + info.type = get_r8a66597_type(usb_endpoint_type(ep)); + info.bufnum = get_bufnum(info.pipenum); + info.buf_bsize = get_buf_bsize(info.pipenum); + if (info.type == R8A66597_BULK) { + info.interval = 0; + info.timer_interval = 0; + } else { + info.interval = get_interval(urb, ep->bInterval); + info.timer_interval = get_timer_interval(urb, ep->bInterval); + } + if (usb_endpoint_dir_in(ep)) + info.dir_in = 1; + else + info.dir_in = 0; + + enable_r8a66597_pipe(r8a66597, urb, hep, &info); +} + +static void init_pipe_config(struct r8a66597 *r8a66597, struct urb *urb) +{ + struct r8a66597_device *dev; + + dev = get_urb_to_r8a66597_dev(r8a66597, urb); + dev->state = USB_STATE_CONFIGURED; +} + +static void pipe_irq_enable(struct r8a66597 *r8a66597, struct urb *urb, + u16 pipenum) +{ + if (pipenum == 0 && usb_pipeout(urb->pipe)) + enable_irq_empty(r8a66597, pipenum); + else + enable_irq_ready(r8a66597, pipenum); + + if (!usb_pipeisoc(urb->pipe)) + enable_irq_nrdy(r8a66597, pipenum); +} + +static void pipe_irq_disable(struct r8a66597 *r8a66597, u16 pipenum) +{ + disable_irq_ready(r8a66597, pipenum); + disable_irq_nrdy(r8a66597, pipenum); +} + +static void r8a66597_root_hub_start_polling(struct r8a66597 *r8a66597) +{ + mod_timer(&r8a66597->rh_timer, + jiffies + msecs_to_jiffies(R8A66597_RH_POLL_TIME)); +} + +static void start_root_hub_sampling(struct r8a66597 *r8a66597, int port, + int connect) +{ + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + + rh->old_syssts = r8a66597_read(r8a66597, get_syssts_reg(port)) & LNST; + rh->scount = R8A66597_MAX_SAMPLING; + if (connect) + rh->port |= USB_PORT_STAT_CONNECTION; + else + rh->port &= ~USB_PORT_STAT_CONNECTION; + rh->port |= USB_PORT_STAT_C_CONNECTION << 16; + + r8a66597_root_hub_start_polling(r8a66597); +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_check_syssts(struct r8a66597 *r8a66597, int port, + u16 syssts) +__releases(r8a66597->lock) +__acquires(r8a66597->lock) +{ + if (syssts == SE0) { + r8a66597_write(r8a66597, ~ATTCH, get_intsts_reg(port)); + r8a66597_bset(r8a66597, ATTCHE, get_intenb_reg(port)); + } else { + if (syssts == FS_JSTS) + r8a66597_bset(r8a66597, HSE, get_syscfg_reg(port)); + else if (syssts == LS_JSTS) + r8a66597_bclr(r8a66597, HSE, get_syscfg_reg(port)); + + r8a66597_write(r8a66597, ~DTCH, get_intsts_reg(port)); + r8a66597_bset(r8a66597, DTCHE, get_intenb_reg(port)); + + if (r8a66597->bus_suspended) + usb_hcd_resume_root_hub(r8a66597_to_hcd(r8a66597)); + } + + spin_unlock(&r8a66597->lock); + usb_hcd_poll_rh_status(r8a66597_to_hcd(r8a66597)); + spin_lock(&r8a66597->lock); +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_usb_connect(struct r8a66597 *r8a66597, int port) +{ + u16 speed = get_rh_usb_speed(r8a66597, port); + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + + rh->port &= ~(USB_PORT_STAT_HIGH_SPEED | USB_PORT_STAT_LOW_SPEED); + if (speed == HSMODE) + rh->port |= USB_PORT_STAT_HIGH_SPEED; + else if (speed == LSMODE) + rh->port |= USB_PORT_STAT_LOW_SPEED; + + rh->port &= ~USB_PORT_STAT_RESET; + rh->port |= USB_PORT_STAT_ENABLE; +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_usb_disconnect(struct r8a66597 *r8a66597, int port) +{ + struct r8a66597_device *dev = r8a66597->root_hub[port].dev; + + disable_r8a66597_pipe_all(r8a66597, dev); + free_usb_address(r8a66597, dev, 0); + + start_root_hub_sampling(r8a66597, port, 0); +} + +/* this function must be called with interrupt disabled */ +static void prepare_setup_packet(struct r8a66597 *r8a66597, + struct r8a66597_td *td) +{ + int i; + __le16 *p = (__le16 *)td->urb->setup_packet; + unsigned long setup_addr = USBREQ; + + r8a66597_write(r8a66597, make_devsel(td->address) | td->maxpacket, + DCPMAXP); + r8a66597_write(r8a66597, ~(SIGN | SACK), INTSTS1); + + for (i = 0; i < 4; i++) { + r8a66597_write(r8a66597, le16_to_cpu(p[i]), setup_addr); + setup_addr += 2; + } + r8a66597_write(r8a66597, SUREQ, DCPCTR); +} + +/* this function must be called with interrupt disabled */ +static void prepare_packet_read(struct r8a66597 *r8a66597, + struct r8a66597_td *td) +{ + struct urb *urb = td->urb; + + if (usb_pipecontrol(urb->pipe)) { + r8a66597_bclr(r8a66597, R8A66597_DIR, DCPCFG); + r8a66597_mdfy(r8a66597, 0, ISEL | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + if (urb->actual_length == 0) { + r8a66597_pipe_toggle(r8a66597, td->pipe, 1); + r8a66597_write(r8a66597, BCLR, CFIFOCTR); + } + pipe_irq_disable(r8a66597, td->pipenum); + pipe_start(r8a66597, td->pipe); + pipe_irq_enable(r8a66597, urb, td->pipenum); + } else { + if (urb->actual_length == 0) { + pipe_irq_disable(r8a66597, td->pipenum); + pipe_setting(r8a66597, td); + pipe_stop(r8a66597, td->pipe); + r8a66597_write(r8a66597, ~(1 << td->pipenum), BRDYSTS); + + if (td->pipe->pipetre) { + r8a66597_write(r8a66597, TRCLR, + td->pipe->pipetre); + r8a66597_write(r8a66597, + DIV_ROUND_UP + (urb->transfer_buffer_length, + td->maxpacket), + td->pipe->pipetrn); + r8a66597_bset(r8a66597, TRENB, + td->pipe->pipetre); + } + + pipe_start(r8a66597, td->pipe); + pipe_irq_enable(r8a66597, urb, td->pipenum); + } + } +} + +/* this function must be called with interrupt disabled */ +static void prepare_packet_write(struct r8a66597 *r8a66597, + struct r8a66597_td *td) +{ + u16 tmp; + struct urb *urb = td->urb; + + if (usb_pipecontrol(urb->pipe)) { + pipe_stop(r8a66597, td->pipe); + r8a66597_bset(r8a66597, R8A66597_DIR, DCPCFG); + r8a66597_mdfy(r8a66597, ISEL, ISEL | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + if (urb->actual_length == 0) { + r8a66597_pipe_toggle(r8a66597, td->pipe, 1); + r8a66597_write(r8a66597, BCLR, CFIFOCTR); + } + } else { + if (urb->actual_length == 0) + pipe_setting(r8a66597, td); + if (td->pipe->pipetre) + r8a66597_bclr(r8a66597, TRENB, td->pipe->pipetre); + } + r8a66597_write(r8a66597, ~(1 << td->pipenum), BRDYSTS); + + fifo_change_from_pipe(r8a66597, td->pipe); + tmp = r8a66597_read(r8a66597, td->pipe->fifoctr); + if (unlikely((tmp & FRDY) == 0)) + pipe_irq_enable(r8a66597, urb, td->pipenum); + else + packet_write(r8a66597, td->pipenum); + pipe_start(r8a66597, td->pipe); +} + +/* this function must be called with interrupt disabled */ +static void prepare_status_packet(struct r8a66597 *r8a66597, + struct r8a66597_td *td) +{ + struct urb *urb = td->urb; + + r8a66597_pipe_toggle(r8a66597, td->pipe, 1); + pipe_stop(r8a66597, td->pipe); + + if (urb->setup_packet[0] & USB_ENDPOINT_DIR_MASK) { + r8a66597_bset(r8a66597, R8A66597_DIR, DCPCFG); + r8a66597_mdfy(r8a66597, ISEL, ISEL | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + r8a66597_write(r8a66597, ~BEMP0, BEMPSTS); + r8a66597_write(r8a66597, BCLR | BVAL, CFIFOCTR); + enable_irq_empty(r8a66597, 0); + } else { + r8a66597_bclr(r8a66597, R8A66597_DIR, DCPCFG); + r8a66597_mdfy(r8a66597, 0, ISEL | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + r8a66597_write(r8a66597, BCLR, CFIFOCTR); + enable_irq_ready(r8a66597, 0); + } + enable_irq_nrdy(r8a66597, 0); + pipe_start(r8a66597, td->pipe); +} + +static int is_set_address(unsigned char *setup_packet) +{ + if (((setup_packet[0] & USB_TYPE_MASK) == USB_TYPE_STANDARD) && + setup_packet[1] == USB_REQ_SET_ADDRESS) + return 1; + else + return 0; +} + +/* this function must be called with interrupt disabled */ +static int start_transfer(struct r8a66597 *r8a66597, struct r8a66597_td *td) +{ + BUG_ON(!td); + + switch (td->type) { + case USB_PID_SETUP: + if (is_set_address(td->urb->setup_packet)) { + td->set_address = 1; + td->urb->setup_packet[2] = alloc_usb_address(r8a66597, + td->urb); + if (td->urb->setup_packet[2] == 0) + return -EPIPE; + } + prepare_setup_packet(r8a66597, td); + break; + case USB_PID_IN: + prepare_packet_read(r8a66597, td); + break; + case USB_PID_OUT: + prepare_packet_write(r8a66597, td); + break; + case USB_PID_ACK: + prepare_status_packet(r8a66597, td); + break; + default: + printk(KERN_ERR "r8a66597: invalid type.\n"); + break; + } + + return 0; +} + +static int check_transfer_finish(struct r8a66597_td *td, struct urb *urb) +{ + if (usb_pipeisoc(urb->pipe)) { + if (urb->number_of_packets == td->iso_cnt) + return 1; + } + + /* control or bulk or interrupt */ + if ((urb->transfer_buffer_length <= urb->actual_length) || + (td->short_packet) || (td->zero_packet)) + return 1; + + return 0; +} + +/* this function must be called with interrupt disabled */ +static void set_td_timer(struct r8a66597 *r8a66597, struct r8a66597_td *td) +{ + unsigned long time; + + BUG_ON(!td); + + if (!list_empty(&r8a66597->pipe_queue[td->pipenum]) && + !usb_pipecontrol(td->urb->pipe) && usb_pipein(td->urb->pipe)) { + r8a66597->timeout_map |= 1 << td->pipenum; + switch (usb_pipetype(td->urb->pipe)) { + case PIPE_INTERRUPT: + case PIPE_ISOCHRONOUS: + time = 30; + break; + default: + time = 300; + break; + } + + mod_timer(&r8a66597->td_timer[td->pipenum], + jiffies + msecs_to_jiffies(time)); + } +} + +/* this function must be called with interrupt disabled */ +static void finish_request(struct r8a66597 *r8a66597, struct r8a66597_td *td, + u16 pipenum, struct urb *urb, int status) +__releases(r8a66597->lock) __acquires(r8a66597->lock) +{ + int restart = 0; + struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); + + r8a66597->timeout_map &= ~(1 << pipenum); + + if (likely(td)) { + if (td->set_address && (status != 0 || urb->unlinked)) + r8a66597->address_map &= ~(1 << urb->setup_packet[2]); + + pipe_toggle_save(r8a66597, td->pipe, urb); + list_del(&td->queue); + kfree(td); + } + + if (!list_empty(&r8a66597->pipe_queue[pipenum])) + restart = 1; + + if (likely(urb)) { + if (usb_pipeisoc(urb->pipe)) + urb->start_frame = r8a66597_get_frame(hcd); + + r8a66597_urb_done(r8a66597, urb, status); + } + + if (restart) { + td = r8a66597_get_td(r8a66597, pipenum); + if (unlikely(!td)) + return; + + start_transfer(r8a66597, td); + set_td_timer(r8a66597, td); + } +} + +static void packet_read(struct r8a66597 *r8a66597, u16 pipenum) +{ + u16 tmp; + int rcv_len, bufsize, urb_len, size; + u16 *buf; + struct r8a66597_td *td = r8a66597_get_td(r8a66597, pipenum); + struct urb *urb; + int finish = 0; + int status = 0; + + if (unlikely(!td)) + return; + urb = td->urb; + + fifo_change_from_pipe(r8a66597, td->pipe); + tmp = r8a66597_read(r8a66597, td->pipe->fifoctr); + if (unlikely((tmp & FRDY) == 0)) { + pipe_stop(r8a66597, td->pipe); + pipe_irq_disable(r8a66597, pipenum); + printk(KERN_ERR "r8a66597: in fifo not ready (%d)\n", pipenum); + finish_request(r8a66597, td, pipenum, td->urb, -EPIPE); + return; + } + + /* prepare parameters */ + rcv_len = tmp & DTLN; + if (usb_pipeisoc(urb->pipe)) { + buf = (u16 *)(urb->transfer_buffer + + urb->iso_frame_desc[td->iso_cnt].offset); + urb_len = urb->iso_frame_desc[td->iso_cnt].length; + } else { + buf = (void *)urb->transfer_buffer + urb->actual_length; + urb_len = urb->transfer_buffer_length - urb->actual_length; + } + bufsize = min(urb_len, (int) td->maxpacket); + if (rcv_len <= bufsize) { + size = rcv_len; + } else { + size = bufsize; + status = -EOVERFLOW; + finish = 1; + } + + /* update parameters */ + urb->actual_length += size; + if (rcv_len == 0) + td->zero_packet = 1; + if (rcv_len < bufsize) { + td->short_packet = 1; + } + if (usb_pipeisoc(urb->pipe)) { + urb->iso_frame_desc[td->iso_cnt].actual_length = size; + urb->iso_frame_desc[td->iso_cnt].status = status; + td->iso_cnt++; + finish = 0; + } + + /* check transfer finish */ + if (finish || check_transfer_finish(td, urb)) { + pipe_stop(r8a66597, td->pipe); + pipe_irq_disable(r8a66597, pipenum); + finish = 1; + } + + /* read fifo */ + if (urb->transfer_buffer) { + if (size == 0) + r8a66597_write(r8a66597, BCLR, td->pipe->fifoctr); + else + r8a66597_read_fifo(r8a66597, td->pipe->fifoaddr, + buf, size); + } + + if (finish && pipenum != 0) + finish_request(r8a66597, td, pipenum, urb, status); +} + +static void packet_write(struct r8a66597 *r8a66597, u16 pipenum) +{ + u16 tmp; + int bufsize, size; + u16 *buf; + struct r8a66597_td *td = r8a66597_get_td(r8a66597, pipenum); + struct urb *urb; + + if (unlikely(!td)) + return; + urb = td->urb; + + fifo_change_from_pipe(r8a66597, td->pipe); + tmp = r8a66597_read(r8a66597, td->pipe->fifoctr); + if (unlikely((tmp & FRDY) == 0)) { + pipe_stop(r8a66597, td->pipe); + pipe_irq_disable(r8a66597, pipenum); + printk(KERN_ERR "r8a66597: out fifo not ready (%d)\n", pipenum); + finish_request(r8a66597, td, pipenum, urb, -EPIPE); + return; + } + + /* prepare parameters */ + bufsize = td->maxpacket; + if (usb_pipeisoc(urb->pipe)) { + buf = (u16 *)(urb->transfer_buffer + + urb->iso_frame_desc[td->iso_cnt].offset); + size = min(bufsize, + (int)urb->iso_frame_desc[td->iso_cnt].length); + } else { + buf = (u16 *)(urb->transfer_buffer + urb->actual_length); + size = min_t(u32, bufsize, + urb->transfer_buffer_length - urb->actual_length); + } + + /* write fifo */ + if (pipenum > 0) + r8a66597_write(r8a66597, ~(1 << pipenum), BEMPSTS); + if (urb->transfer_buffer) { + r8a66597_write_fifo(r8a66597, td->pipe, buf, size); + if (!usb_pipebulk(urb->pipe) || td->maxpacket != size) + r8a66597_write(r8a66597, BVAL, td->pipe->fifoctr); + } + + /* update parameters */ + urb->actual_length += size; + if (usb_pipeisoc(urb->pipe)) { + urb->iso_frame_desc[td->iso_cnt].actual_length = size; + urb->iso_frame_desc[td->iso_cnt].status = 0; + td->iso_cnt++; + } + + /* check transfer finish */ + if (check_transfer_finish(td, urb)) { + disable_irq_ready(r8a66597, pipenum); + enable_irq_empty(r8a66597, pipenum); + if (!usb_pipeisoc(urb->pipe)) + enable_irq_nrdy(r8a66597, pipenum); + } else + pipe_irq_enable(r8a66597, urb, pipenum); +} + + +static void check_next_phase(struct r8a66597 *r8a66597, int status) +{ + struct r8a66597_td *td = r8a66597_get_td(r8a66597, 0); + struct urb *urb; + u8 finish = 0; + + if (unlikely(!td)) + return; + urb = td->urb; + + switch (td->type) { + case USB_PID_IN: + case USB_PID_OUT: + if (check_transfer_finish(td, urb)) + td->type = USB_PID_ACK; + break; + case USB_PID_SETUP: + if (urb->transfer_buffer_length == urb->actual_length) + td->type = USB_PID_ACK; + else if (usb_pipeout(urb->pipe)) + td->type = USB_PID_OUT; + else + td->type = USB_PID_IN; + break; + case USB_PID_ACK: + finish = 1; + break; + } + + if (finish || status != 0 || urb->unlinked) + finish_request(r8a66597, td, 0, urb, status); + else + start_transfer(r8a66597, td); +} + +static int get_urb_error(struct r8a66597 *r8a66597, u16 pipenum) +{ + struct r8a66597_td *td = r8a66597_get_td(r8a66597, pipenum); + + if (td) { + u16 pid = r8a66597_read(r8a66597, td->pipe->pipectr) & PID; + + if (pid == PID_NAK) + return -ECONNRESET; + else + return -EPIPE; + } + return 0; +} + +static void irq_pipe_ready(struct r8a66597 *r8a66597) +{ + u16 check; + u16 pipenum; + u16 mask; + struct r8a66597_td *td; + + mask = r8a66597_read(r8a66597, BRDYSTS) + & r8a66597_read(r8a66597, BRDYENB); + r8a66597_write(r8a66597, ~mask, BRDYSTS); + if (mask & BRDY0) { + td = r8a66597_get_td(r8a66597, 0); + if (td && td->type == USB_PID_IN) + packet_read(r8a66597, 0); + else + pipe_irq_disable(r8a66597, 0); + check_next_phase(r8a66597, 0); + } + + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if (mask & check) { + td = r8a66597_get_td(r8a66597, pipenum); + if (unlikely(!td)) + continue; + + if (td->type == USB_PID_IN) + packet_read(r8a66597, pipenum); + else if (td->type == USB_PID_OUT) + packet_write(r8a66597, pipenum); + } + } +} + +static void irq_pipe_empty(struct r8a66597 *r8a66597) +{ + u16 tmp; + u16 check; + u16 pipenum; + u16 mask; + struct r8a66597_td *td; + + mask = r8a66597_read(r8a66597, BEMPSTS) + & r8a66597_read(r8a66597, BEMPENB); + r8a66597_write(r8a66597, ~mask, BEMPSTS); + if (mask & BEMP0) { + cfifo_change(r8a66597, 0); + td = r8a66597_get_td(r8a66597, 0); + if (td && td->type != USB_PID_OUT) + disable_irq_empty(r8a66597, 0); + check_next_phase(r8a66597, 0); + } + + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if (mask & check) { + struct r8a66597_td *td; + td = r8a66597_get_td(r8a66597, pipenum); + if (unlikely(!td)) + continue; + + tmp = r8a66597_read(r8a66597, td->pipe->pipectr); + if ((tmp & INBUFM) == 0) { + disable_irq_empty(r8a66597, pipenum); + pipe_irq_disable(r8a66597, pipenum); + finish_request(r8a66597, td, pipenum, td->urb, + 0); + } + } + } +} + +static void irq_pipe_nrdy(struct r8a66597 *r8a66597) +{ + u16 check; + u16 pipenum; + u16 mask; + int status; + + mask = r8a66597_read(r8a66597, NRDYSTS) + & r8a66597_read(r8a66597, NRDYENB); + r8a66597_write(r8a66597, ~mask, NRDYSTS); + if (mask & NRDY0) { + cfifo_change(r8a66597, 0); + status = get_urb_error(r8a66597, 0); + pipe_irq_disable(r8a66597, 0); + check_next_phase(r8a66597, status); + } + + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if (mask & check) { + struct r8a66597_td *td; + td = r8a66597_get_td(r8a66597, pipenum); + if (unlikely(!td)) + continue; + + status = get_urb_error(r8a66597, pipenum); + pipe_irq_disable(r8a66597, pipenum); + pipe_stop(r8a66597, td->pipe); + finish_request(r8a66597, td, pipenum, td->urb, status); + } + } +} + +static irqreturn_t r8a66597_irq(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + u16 intsts0, intsts1, intsts2; + u16 intenb0, intenb1, intenb2; + u16 mask0, mask1, mask2; + int status; + + spin_lock(&r8a66597->lock); + + intsts0 = r8a66597_read(r8a66597, INTSTS0); + intsts1 = r8a66597_read(r8a66597, INTSTS1); + intsts2 = r8a66597_read(r8a66597, INTSTS2); + intenb0 = r8a66597_read(r8a66597, INTENB0); + intenb1 = r8a66597_read(r8a66597, INTENB1); + intenb2 = r8a66597_read(r8a66597, INTENB2); + + mask2 = intsts2 & intenb2; + mask1 = intsts1 & intenb1; + mask0 = intsts0 & intenb0 & (BEMP | NRDY | BRDY); + if (mask2) { + if (mask2 & ATTCH) { + r8a66597_write(r8a66597, ~ATTCH, INTSTS2); + r8a66597_bclr(r8a66597, ATTCHE, INTENB2); + + /* start usb bus sampling */ + start_root_hub_sampling(r8a66597, 1, 1); + } + if (mask2 & DTCH) { + r8a66597_write(r8a66597, ~DTCH, INTSTS2); + r8a66597_bclr(r8a66597, DTCHE, INTENB2); + r8a66597_usb_disconnect(r8a66597, 1); + } + if (mask2 & BCHG) { + r8a66597_write(r8a66597, ~BCHG, INTSTS2); + r8a66597_bclr(r8a66597, BCHGE, INTENB2); + usb_hcd_resume_root_hub(r8a66597_to_hcd(r8a66597)); + } + } + + if (mask1) { + if (mask1 & ATTCH) { + r8a66597_write(r8a66597, ~ATTCH, INTSTS1); + r8a66597_bclr(r8a66597, ATTCHE, INTENB1); + + /* start usb bus sampling */ + start_root_hub_sampling(r8a66597, 0, 1); + } + if (mask1 & DTCH) { + r8a66597_write(r8a66597, ~DTCH, INTSTS1); + r8a66597_bclr(r8a66597, DTCHE, INTENB1); + r8a66597_usb_disconnect(r8a66597, 0); + } + if (mask1 & BCHG) { + r8a66597_write(r8a66597, ~BCHG, INTSTS1); + r8a66597_bclr(r8a66597, BCHGE, INTENB1); + usb_hcd_resume_root_hub(r8a66597_to_hcd(r8a66597)); + } + + if (mask1 & SIGN) { + r8a66597_write(r8a66597, ~SIGN, INTSTS1); + status = get_urb_error(r8a66597, 0); + check_next_phase(r8a66597, status); + } + if (mask1 & SACK) { + r8a66597_write(r8a66597, ~SACK, INTSTS1); + check_next_phase(r8a66597, 0); + } + } + if (mask0) { + if (mask0 & BRDY) + irq_pipe_ready(r8a66597); + if (mask0 & BEMP) + irq_pipe_empty(r8a66597); + if (mask0 & NRDY) + irq_pipe_nrdy(r8a66597); + } + + spin_unlock(&r8a66597->lock); + return IRQ_HANDLED; +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_root_hub_control(struct r8a66597 *r8a66597, int port) +{ + u16 tmp; + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + + if (rh->port & USB_PORT_STAT_RESET) { + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + tmp = r8a66597_read(r8a66597, dvstctr_reg); + if ((tmp & USBRST) == USBRST) { + r8a66597_mdfy(r8a66597, UACT, USBRST | UACT, + dvstctr_reg); + r8a66597_root_hub_start_polling(r8a66597); + } else + r8a66597_usb_connect(r8a66597, port); + } + + if (!(rh->port & USB_PORT_STAT_CONNECTION)) { + r8a66597_write(r8a66597, ~ATTCH, get_intsts_reg(port)); + r8a66597_bset(r8a66597, ATTCHE, get_intenb_reg(port)); + } + + if (rh->scount > 0) { + tmp = r8a66597_read(r8a66597, get_syssts_reg(port)) & LNST; + if (tmp == rh->old_syssts) { + rh->scount--; + if (rh->scount == 0) + r8a66597_check_syssts(r8a66597, port, tmp); + else + r8a66597_root_hub_start_polling(r8a66597); + } else { + rh->scount = R8A66597_MAX_SAMPLING; + rh->old_syssts = tmp; + r8a66597_root_hub_start_polling(r8a66597); + } + } +} + +static void r8a66597_interval_timer(unsigned long _r8a66597) +{ + struct r8a66597 *r8a66597 = (struct r8a66597 *)_r8a66597; + unsigned long flags; + u16 pipenum; + struct r8a66597_td *td; + + spin_lock_irqsave(&r8a66597->lock, flags); + + for (pipenum = 0; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + if (!(r8a66597->interval_map & (1 << pipenum))) + continue; + if (timer_pending(&r8a66597->interval_timer[pipenum])) + continue; + + td = r8a66597_get_td(r8a66597, pipenum); + if (td) + start_transfer(r8a66597, td); + } + + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +static void r8a66597_td_timer(unsigned long _r8a66597) +{ + struct r8a66597 *r8a66597 = (struct r8a66597 *)_r8a66597; + unsigned long flags; + u16 pipenum; + struct r8a66597_td *td, *new_td = NULL; + struct r8a66597_pipe *pipe; + + spin_lock_irqsave(&r8a66597->lock, flags); + for (pipenum = 0; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + if (!(r8a66597->timeout_map & (1 << pipenum))) + continue; + if (timer_pending(&r8a66597->td_timer[pipenum])) + continue; + + td = r8a66597_get_td(r8a66597, pipenum); + if (!td) { + r8a66597->timeout_map &= ~(1 << pipenum); + continue; + } + + if (td->urb->actual_length) { + set_td_timer(r8a66597, td); + break; + } + + pipe = td->pipe; + pipe_stop(r8a66597, pipe); + + new_td = td; + do { + list_move_tail(&new_td->queue, + &r8a66597->pipe_queue[pipenum]); + new_td = r8a66597_get_td(r8a66597, pipenum); + if (!new_td) { + new_td = td; + break; + } + } while (td != new_td && td->address == new_td->address); + + start_transfer(r8a66597, new_td); + + if (td == new_td) + r8a66597->timeout_map &= ~(1 << pipenum); + else + set_td_timer(r8a66597, new_td); + break; + } + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +static void r8a66597_timer(unsigned long _r8a66597) +{ + struct r8a66597 *r8a66597 = (struct r8a66597 *)_r8a66597; + unsigned long flags; + int port; + + spin_lock_irqsave(&r8a66597->lock, flags); + + for (port = 0; port < r8a66597->max_root_hub; port++) + r8a66597_root_hub_control(r8a66597, port); + + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +static int check_pipe_config(struct r8a66597 *r8a66597, struct urb *urb) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + + if (dev && dev->address && dev->state != USB_STATE_CONFIGURED && + (urb->dev->state == USB_STATE_CONFIGURED)) + return 1; + else + return 0; +} + +static int r8a66597_start(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + + hcd->state = HC_STATE_RUNNING; + return enable_controller(r8a66597); +} + +static void r8a66597_stop(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + + disable_controller(r8a66597); +} + +static void set_address_zero(struct r8a66597 *r8a66597, struct urb *urb) +{ + unsigned int usb_address = usb_pipedevice(urb->pipe); + u16 root_port, hub_port; + + if (usb_address == 0) { + get_port_number(r8a66597, urb->dev->devpath, + &root_port, &hub_port); + set_devadd_reg(r8a66597, 0, + get_r8a66597_usb_speed(urb->dev->speed), + get_parent_r8a66597_address(r8a66597, urb->dev), + hub_port, root_port); + } +} + +static struct r8a66597_td *r8a66597_make_td(struct r8a66597 *r8a66597, + struct urb *urb, + struct usb_host_endpoint *hep) +{ + struct r8a66597_td *td; + u16 pipenum; + + td = kzalloc(sizeof(struct r8a66597_td), GFP_ATOMIC); + if (td == NULL) + return NULL; + + pipenum = r8a66597_get_pipenum(urb, hep); + td->pipenum = pipenum; + td->pipe = hep->hcpriv; + td->urb = urb; + td->address = get_urb_to_r8a66597_addr(r8a66597, urb); + td->maxpacket = usb_maxpacket(urb->dev, urb->pipe, + !usb_pipein(urb->pipe)); + if (usb_pipecontrol(urb->pipe)) + td->type = USB_PID_SETUP; + else if (usb_pipein(urb->pipe)) + td->type = USB_PID_IN; + else + td->type = USB_PID_OUT; + INIT_LIST_HEAD(&td->queue); + + return td; +} + +static int r8a66597_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + struct usb_host_endpoint *hep = urb->ep; + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + struct r8a66597_td *td = NULL; + int ret, request = 0; + unsigned long flags; + + spin_lock_irqsave(&r8a66597->lock, flags); + if (!get_urb_to_r8a66597_dev(r8a66597, urb)) { + ret = -ENODEV; + goto error_not_linked; + } + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto error_not_linked; + + if (!hep->hcpriv) { + hep->hcpriv = kzalloc(sizeof(struct r8a66597_pipe), + GFP_ATOMIC); + if (!hep->hcpriv) { + ret = -ENOMEM; + goto error; + } + set_pipe_reg_addr(hep->hcpriv, R8A66597_PIPE_NO_DMA); + if (usb_pipeendpoint(urb->pipe)) + init_pipe_info(r8a66597, urb, hep, &hep->desc); + } + + if (unlikely(check_pipe_config(r8a66597, urb))) + init_pipe_config(r8a66597, urb); + + set_address_zero(r8a66597, urb); + td = r8a66597_make_td(r8a66597, urb, hep); + if (td == NULL) { + ret = -ENOMEM; + goto error; + } + if (list_empty(&r8a66597->pipe_queue[td->pipenum])) + request = 1; + list_add_tail(&td->queue, &r8a66597->pipe_queue[td->pipenum]); + urb->hcpriv = td; + + if (request) { + if (td->pipe->info.timer_interval) { + r8a66597->interval_map |= 1 << td->pipenum; + mod_timer(&r8a66597->interval_timer[td->pipenum], + jiffies + msecs_to_jiffies( + td->pipe->info.timer_interval)); + } else { + ret = start_transfer(r8a66597, td); + if (ret < 0) { + list_del(&td->queue); + kfree(td); + } + } + } else + set_td_timer(r8a66597, td); + +error: + if (ret) + usb_hcd_unlink_urb_from_ep(hcd, urb); +error_not_linked: + spin_unlock_irqrestore(&r8a66597->lock, flags); + return ret; +} + +static int r8a66597_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + struct r8a66597_td *td; + unsigned long flags; + int rc; + + spin_lock_irqsave(&r8a66597->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + if (urb->hcpriv) { + td = urb->hcpriv; + pipe_stop(r8a66597, td->pipe); + pipe_irq_disable(r8a66597, td->pipenum); + disable_irq_empty(r8a66597, td->pipenum); + finish_request(r8a66597, td, td->pipenum, urb, status); + } + done: + spin_unlock_irqrestore(&r8a66597->lock, flags); + return rc; +} + +static void r8a66597_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *hep) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + struct r8a66597_pipe *pipe = (struct r8a66597_pipe *)hep->hcpriv; + struct r8a66597_td *td; + struct urb *urb = NULL; + u16 pipenum; + unsigned long flags; + + if (pipe == NULL) + return; + pipenum = pipe->info.pipenum; + + if (pipenum == 0) { + kfree(hep->hcpriv); + hep->hcpriv = NULL; + return; + } + + spin_lock_irqsave(&r8a66597->lock, flags); + pipe_stop(r8a66597, pipe); + pipe_irq_disable(r8a66597, pipenum); + disable_irq_empty(r8a66597, pipenum); + td = r8a66597_get_td(r8a66597, pipenum); + if (td) + urb = td->urb; + finish_request(r8a66597, td, pipenum, urb, -ESHUTDOWN); + kfree(hep->hcpriv); + hep->hcpriv = NULL; + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +static int r8a66597_get_frame(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + return r8a66597_read(r8a66597, FRMNUM) & 0x03FF; +} + +static void collect_usb_address_map(struct usb_device *udev, unsigned long *map) +{ + int chix; + struct usb_device *childdev; + + if (udev->state == USB_STATE_CONFIGURED && + udev->parent && udev->parent->devnum > 1 && + udev->parent->descriptor.bDeviceClass == USB_CLASS_HUB) + map[udev->devnum/32] |= (1 << (udev->devnum % 32)); + + usb_hub_for_each_child(udev, chix, childdev) + collect_usb_address_map(childdev, map); +} + +/* this function must be called with interrupt disabled */ +static struct r8a66597_device *get_r8a66597_device(struct r8a66597 *r8a66597, + int addr) +{ + struct r8a66597_device *dev; + struct list_head *list = &r8a66597->child_device; + + list_for_each_entry(dev, list, device_list) { + if (dev->usb_address != addr) + continue; + + return dev; + } + + printk(KERN_ERR "r8a66597: get_r8a66597_device fail.(%d)\n", addr); + return NULL; +} + +static void update_usb_address_map(struct r8a66597 *r8a66597, + struct usb_device *root_hub, + unsigned long *map) +{ + int i, j, addr; + unsigned long diff; + unsigned long flags; + + for (i = 0; i < 4; i++) { + diff = r8a66597->child_connect_map[i] ^ map[i]; + if (!diff) + continue; + + for (j = 0; j < 32; j++) { + if (!(diff & (1 << j))) + continue; + + addr = i * 32 + j; + if (map[i] & (1 << j)) + set_child_connect_map(r8a66597, addr); + else { + struct r8a66597_device *dev; + + spin_lock_irqsave(&r8a66597->lock, flags); + dev = get_r8a66597_device(r8a66597, addr); + disable_r8a66597_pipe_all(r8a66597, dev); + free_usb_address(r8a66597, dev, 0); + put_child_connect_map(r8a66597, addr); + spin_unlock_irqrestore(&r8a66597->lock, flags); + } + } + } +} + +static void r8a66597_check_detect_child(struct r8a66597 *r8a66597, + struct usb_hcd *hcd) +{ + struct usb_bus *bus; + unsigned long now_map[4]; + + memset(now_map, 0, sizeof(now_map)); + + list_for_each_entry(bus, &usb_bus_list, bus_list) { + if (!bus->root_hub) + continue; + + if (bus->busnum != hcd->self.busnum) + continue; + + collect_usb_address_map(bus->root_hub, now_map); + update_usb_address_map(r8a66597, bus->root_hub, now_map); + } +} + +static int r8a66597_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + unsigned long flags; + int i; + + r8a66597_check_detect_child(r8a66597, hcd); + + spin_lock_irqsave(&r8a66597->lock, flags); + + *buf = 0; /* initialize (no change) */ + + for (i = 0; i < r8a66597->max_root_hub; i++) { + if (r8a66597->root_hub[i].port & 0xffff0000) + *buf |= 1 << (i + 1); + } + + spin_unlock_irqrestore(&r8a66597->lock, flags); + + return (*buf != 0); +} + +static void r8a66597_hub_descriptor(struct r8a66597 *r8a66597, + struct usb_hub_descriptor *desc) +{ + desc->bDescriptorType = 0x29; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = r8a66597->max_root_hub; + desc->bDescLength = 9; + desc->bPwrOn2PwrGood = 0; + desc->wHubCharacteristics = cpu_to_le16(0x0011); + desc->u.hs.DeviceRemovable[0] = + ((1 << r8a66597->max_root_hub) - 1) << 1; + desc->u.hs.DeviceRemovable[1] = ~0; +} + +static int r8a66597_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + int ret; + int port = (wIndex & 0x00FF) - 1; + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + unsigned long flags; + + ret = 0; + + spin_lock_irqsave(&r8a66597->lock, flags); + switch (typeReq) { + case ClearHubFeature: + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (wIndex > r8a66597->max_root_hub) + goto error; + if (wLength != 0) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + rh->port &= ~USB_PORT_STAT_POWER; + break; + case USB_PORT_FEAT_SUSPEND: + break; + case USB_PORT_FEAT_POWER: + r8a66597_port_power(r8a66597, port, 0); + break; + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_RESET: + break; + default: + goto error; + } + rh->port &= ~(1 << wValue); + break; + case GetHubDescriptor: + r8a66597_hub_descriptor(r8a66597, + (struct usb_hub_descriptor *)buf); + break; + case GetHubStatus: + *buf = 0x00; + break; + case GetPortStatus: + if (wIndex > r8a66597->max_root_hub) + goto error; + *(__le32 *)buf = cpu_to_le32(rh->port); + break; + case SetPortFeature: + if (wIndex > r8a66597->max_root_hub) + goto error; + if (wLength != 0) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + break; + case USB_PORT_FEAT_POWER: + r8a66597_port_power(r8a66597, port, 1); + rh->port |= USB_PORT_STAT_POWER; + break; + case USB_PORT_FEAT_RESET: { + struct r8a66597_device *dev = rh->dev; + + rh->port |= USB_PORT_STAT_RESET; + + disable_r8a66597_pipe_all(r8a66597, dev); + free_usb_address(r8a66597, dev, 1); + + r8a66597_mdfy(r8a66597, USBRST, USBRST | UACT, + get_dvstctr_reg(port)); + mod_timer(&r8a66597->rh_timer, + jiffies + msecs_to_jiffies(50)); + } + break; + default: + goto error; + } + rh->port |= 1 << wValue; + break; + default: +error: + ret = -EPIPE; + break; + } + + spin_unlock_irqrestore(&r8a66597->lock, flags); + return ret; +} + +#if defined(CONFIG_PM) +static int r8a66597_bus_suspend(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + int port; + + dev_dbg(&r8a66597->device0.udev->dev, "%s\n", __func__); + + for (port = 0; port < r8a66597->max_root_hub; port++) { + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + if (!(rh->port & USB_PORT_STAT_ENABLE)) + continue; + + dev_dbg(&rh->dev->udev->dev, "suspend port = %d\n", port); + r8a66597_bclr(r8a66597, UACT, dvstctr_reg); /* suspend */ + rh->port |= USB_PORT_STAT_SUSPEND; + + if (rh->dev->udev->do_remote_wakeup) { + msleep(3); /* waiting last SOF */ + r8a66597_bset(r8a66597, RWUPE, dvstctr_reg); + r8a66597_write(r8a66597, ~BCHG, get_intsts_reg(port)); + r8a66597_bset(r8a66597, BCHGE, get_intenb_reg(port)); + } + } + + r8a66597->bus_suspended = 1; + + return 0; +} + +static int r8a66597_bus_resume(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + int port; + + dev_dbg(&r8a66597->device0.udev->dev, "%s\n", __func__); + + for (port = 0; port < r8a66597->max_root_hub; port++) { + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + if (!(rh->port & USB_PORT_STAT_SUSPEND)) + continue; + + dev_dbg(&rh->dev->udev->dev, "resume port = %d\n", port); + rh->port &= ~USB_PORT_STAT_SUSPEND; + rh->port |= USB_PORT_STAT_C_SUSPEND << 16; + r8a66597_mdfy(r8a66597, RESUME, RESUME | UACT, dvstctr_reg); + msleep(50); + r8a66597_mdfy(r8a66597, UACT, RESUME | UACT, dvstctr_reg); + } + + return 0; + +} +#else +#define r8a66597_bus_suspend NULL +#define r8a66597_bus_resume NULL +#endif + +static struct hc_driver r8a66597_hc_driver = { + .description = hcd_name, + .hcd_priv_size = sizeof(struct r8a66597), + .irq = r8a66597_irq, + + /* + * generic hardware linkage + */ + .flags = HCD_USB2, + + .start = r8a66597_start, + .stop = r8a66597_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = r8a66597_urb_enqueue, + .urb_dequeue = r8a66597_urb_dequeue, + .endpoint_disable = r8a66597_endpoint_disable, + + /* + * periodic schedule support + */ + .get_frame_number = r8a66597_get_frame, + + /* + * root hub support + */ + .hub_status_data = r8a66597_hub_status_data, + .hub_control = r8a66597_hub_control, + .bus_suspend = r8a66597_bus_suspend, + .bus_resume = r8a66597_bus_resume, +}; + +#if defined(CONFIG_PM) +static int r8a66597_suspend(struct device *dev) +{ + struct r8a66597 *r8a66597 = dev_get_drvdata(dev); + int port; + + dev_dbg(dev, "%s\n", __func__); + + disable_controller(r8a66597); + + for (port = 0; port < r8a66597->max_root_hub; port++) { + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + + rh->port = 0x00000000; + } + + return 0; +} + +static int r8a66597_resume(struct device *dev) +{ + struct r8a66597 *r8a66597 = dev_get_drvdata(dev); + struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); + + dev_dbg(dev, "%s\n", __func__); + + enable_controller(r8a66597); + usb_root_hub_lost_power(hcd->self.root_hub); + + return 0; +} + +static const struct dev_pm_ops r8a66597_dev_pm_ops = { + .suspend = r8a66597_suspend, + .resume = r8a66597_resume, + .poweroff = r8a66597_suspend, + .restore = r8a66597_resume, +}; + +#define R8A66597_DEV_PM_OPS (&r8a66597_dev_pm_ops) +#else /* if defined(CONFIG_PM) */ +#define R8A66597_DEV_PM_OPS NULL +#endif + +static int r8a66597_remove(struct platform_device *pdev) +{ + struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev); + struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); + + del_timer_sync(&r8a66597->rh_timer); + usb_remove_hcd(hcd); + iounmap(r8a66597->reg); + if (r8a66597->pdata->on_chip) + clk_put(r8a66597->clk); + usb_put_hcd(hcd); + return 0; +} + +static int r8a66597_probe(struct platform_device *pdev) +{ + char clk_name[8]; + struct resource *res = NULL, *ires; + int irq = -1; + void __iomem *reg = NULL; + struct usb_hcd *hcd = NULL; + struct r8a66597 *r8a66597; + int ret = 0; + int i; + unsigned long irq_trigger; + + if (usb_disabled()) + return -ENODEV; + + if (pdev->dev.dma_mask) { + ret = -EINVAL; + dev_err(&pdev->dev, "dma not supported\n"); + goto clean_up; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + dev_err(&pdev->dev, "platform_get_resource error.\n"); + goto clean_up; + } + + ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!ires) { + ret = -ENODEV; + dev_err(&pdev->dev, + "platform_get_resource IORESOURCE_IRQ error.\n"); + goto clean_up; + } + + irq = ires->start; + irq_trigger = ires->flags & IRQF_TRIGGER_MASK; + + reg = ioremap(res->start, resource_size(res)); + if (reg == NULL) { + ret = -ENOMEM; + dev_err(&pdev->dev, "ioremap error.\n"); + goto clean_up; + } + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + ret = -ENODEV; + goto clean_up; + } + + /* initialize hcd */ + hcd = usb_create_hcd(&r8a66597_hc_driver, &pdev->dev, (char *)hcd_name); + if (!hcd) { + ret = -ENOMEM; + dev_err(&pdev->dev, "Failed to create hcd\n"); + goto clean_up; + } + r8a66597 = hcd_to_r8a66597(hcd); + memset(r8a66597, 0, sizeof(struct r8a66597)); + dev_set_drvdata(&pdev->dev, r8a66597); + r8a66597->pdata = pdev->dev.platform_data; + r8a66597->irq_sense_low = irq_trigger == IRQF_TRIGGER_LOW; + + if (r8a66597->pdata->on_chip) { + snprintf(clk_name, sizeof(clk_name), "usb%d", pdev->id); + r8a66597->clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(r8a66597->clk)) { + dev_err(&pdev->dev, "cannot get clock \"%s\"\n", + clk_name); + ret = PTR_ERR(r8a66597->clk); + goto clean_up2; + } + r8a66597->max_root_hub = 1; + } else + r8a66597->max_root_hub = 2; + + spin_lock_init(&r8a66597->lock); + init_timer(&r8a66597->rh_timer); + r8a66597->rh_timer.function = r8a66597_timer; + r8a66597->rh_timer.data = (unsigned long)r8a66597; + r8a66597->reg = reg; + + /* make sure no interrupts are pending */ + ret = r8a66597_clock_enable(r8a66597); + if (ret < 0) + goto clean_up3; + disable_controller(r8a66597); + + for (i = 0; i < R8A66597_MAX_NUM_PIPE; i++) { + INIT_LIST_HEAD(&r8a66597->pipe_queue[i]); + init_timer(&r8a66597->td_timer[i]); + r8a66597->td_timer[i].function = r8a66597_td_timer; + r8a66597->td_timer[i].data = (unsigned long)r8a66597; + setup_timer(&r8a66597->interval_timer[i], + r8a66597_interval_timer, + (unsigned long)r8a66597); + } + INIT_LIST_HEAD(&r8a66597->child_device); + + hcd->rsrc_start = res->start; + hcd->has_tt = 1; + + ret = usb_add_hcd(hcd, irq, irq_trigger); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to add hcd\n"); + goto clean_up3; + } + + return 0; + +clean_up3: + if (r8a66597->pdata->on_chip) + clk_put(r8a66597->clk); +clean_up2: + usb_put_hcd(hcd); + +clean_up: + if (reg) + iounmap(reg); + + return ret; +} + +static struct platform_driver r8a66597_driver = { + .probe = r8a66597_probe, + .remove = r8a66597_remove, + .driver = { + .name = (char *) hcd_name, + .owner = THIS_MODULE, + .pm = R8A66597_DEV_PM_OPS, + }, +}; + +module_platform_driver(r8a66597_driver); diff --git a/drivers/usb/host/r8a66597.h b/drivers/usb/host/r8a66597.h new file mode 100644 index 00000000..672cea30 --- /dev/null +++ b/drivers/usb/host/r8a66597.h @@ -0,0 +1,345 @@ +/* + * R8A66597 HCD (Host Controller Driver) + * + * Copyright (C) 2006-2007 Renesas Solutions Corp. + * Portions Copyright (C) 2004 Psion Teklogix (for NetBook PRO) + * Portions Copyright (C) 2004-2005 David Brownell + * Portions Copyright (C) 1999 Roman Weissgaerber + * + * Author : Yoshihiro Shimoda + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __R8A66597_H__ +#define __R8A66597_H__ + +#include +#include + +#define R8A66597_MAX_NUM_PIPE 10 +#define R8A66597_BUF_BSIZE 8 +#define R8A66597_MAX_DEVICE 10 +#define R8A66597_MAX_ROOT_HUB 2 +#define R8A66597_MAX_SAMPLING 5 +#define R8A66597_RH_POLL_TIME 10 +#define R8A66597_MAX_DMA_CHANNEL 2 +#define R8A66597_PIPE_NO_DMA R8A66597_MAX_DMA_CHANNEL +#define check_bulk_or_isoc(pipenum) ((pipenum >= 1 && pipenum <= 5)) +#define check_interrupt(pipenum) ((pipenum >= 6 && pipenum <= 9)) +#define make_devsel(addr) (addr << 12) + +struct r8a66597_pipe_info { + unsigned long timer_interval; + u16 pipenum; + u16 address; /* R8A66597 HCD usb address */ + u16 epnum; + u16 maxpacket; + u16 type; + u16 bufnum; + u16 buf_bsize; + u16 interval; + u16 dir_in; +}; + +struct r8a66597_pipe { + struct r8a66597_pipe_info info; + + unsigned long fifoaddr; + unsigned long fifosel; + unsigned long fifoctr; + unsigned long pipectr; + unsigned long pipetre; + unsigned long pipetrn; +}; + +struct r8a66597_td { + struct r8a66597_pipe *pipe; + struct urb *urb; + struct list_head queue; + + u16 type; + u16 pipenum; + int iso_cnt; + + u16 address; /* R8A66597's USB address */ + u16 maxpacket; + + unsigned zero_packet:1; + unsigned short_packet:1; + unsigned set_address:1; +}; + +struct r8a66597_device { + u16 address; /* R8A66597's USB address */ + u16 hub_port; + u16 root_port; + + unsigned short ep_in_toggle; + unsigned short ep_out_toggle; + unsigned char pipe_cnt[R8A66597_MAX_NUM_PIPE]; + unsigned char dma_map; + + enum usb_device_state state; + + struct usb_device *udev; + int usb_address; + struct list_head device_list; +}; + +struct r8a66597_root_hub { + u32 port; + u16 old_syssts; + int scount; + + struct r8a66597_device *dev; +}; + +struct r8a66597 { + spinlock_t lock; + void __iomem *reg; + struct clk *clk; + struct r8a66597_platdata *pdata; + struct r8a66597_device device0; + struct r8a66597_root_hub root_hub[R8A66597_MAX_ROOT_HUB]; + struct list_head pipe_queue[R8A66597_MAX_NUM_PIPE]; + + struct timer_list rh_timer; + struct timer_list td_timer[R8A66597_MAX_NUM_PIPE]; + struct timer_list interval_timer[R8A66597_MAX_NUM_PIPE]; + + unsigned short address_map; + unsigned short timeout_map; + unsigned short interval_map; + unsigned char pipe_cnt[R8A66597_MAX_NUM_PIPE]; + unsigned char dma_map; + unsigned int max_root_hub; + + struct list_head child_device; + unsigned long child_connect_map[4]; + + unsigned bus_suspended:1; + unsigned irq_sense_low:1; +}; + +static inline struct r8a66597 *hcd_to_r8a66597(struct usb_hcd *hcd) +{ + return (struct r8a66597 *)(hcd->hcd_priv); +} + +static inline struct usb_hcd *r8a66597_to_hcd(struct r8a66597 *r8a66597) +{ + return container_of((void *)r8a66597, struct usb_hcd, hcd_priv); +} + +static inline struct r8a66597_td *r8a66597_get_td(struct r8a66597 *r8a66597, + u16 pipenum) +{ + if (unlikely(list_empty(&r8a66597->pipe_queue[pipenum]))) + return NULL; + + return list_entry(r8a66597->pipe_queue[pipenum].next, + struct r8a66597_td, queue); +} + +static inline struct urb *r8a66597_get_urb(struct r8a66597 *r8a66597, + u16 pipenum) +{ + struct r8a66597_td *td; + + td = r8a66597_get_td(r8a66597, pipenum); + return (td ? td->urb : NULL); +} + +static inline u16 r8a66597_read(struct r8a66597 *r8a66597, unsigned long offset) +{ + return ioread16(r8a66597->reg + offset); +} + +static inline void r8a66597_read_fifo(struct r8a66597 *r8a66597, + unsigned long offset, u16 *buf, + int len) +{ + void __iomem *fifoaddr = r8a66597->reg + offset; + unsigned long count; + + if (r8a66597->pdata->on_chip) { + count = len / 4; + ioread32_rep(fifoaddr, buf, count); + + if (len & 0x00000003) { + unsigned long tmp = ioread32(fifoaddr); + memcpy((unsigned char *)buf + count * 4, &tmp, + len & 0x03); + } + } else { + len = (len + 1) / 2; + ioread16_rep(fifoaddr, buf, len); + } +} + +static inline void r8a66597_write(struct r8a66597 *r8a66597, u16 val, + unsigned long offset) +{ + iowrite16(val, r8a66597->reg + offset); +} + +static inline void r8a66597_mdfy(struct r8a66597 *r8a66597, + u16 val, u16 pat, unsigned long offset) +{ + u16 tmp; + tmp = r8a66597_read(r8a66597, offset); + tmp = tmp & (~pat); + tmp = tmp | val; + r8a66597_write(r8a66597, tmp, offset); +} + +#define r8a66597_bclr(r8a66597, val, offset) \ + r8a66597_mdfy(r8a66597, 0, val, offset) +#define r8a66597_bset(r8a66597, val, offset) \ + r8a66597_mdfy(r8a66597, val, 0, offset) + +static inline void r8a66597_write_fifo(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, u16 *buf, + int len) +{ + void __iomem *fifoaddr = r8a66597->reg + pipe->fifoaddr; + unsigned long count; + unsigned char *pb; + int i; + + if (r8a66597->pdata->on_chip) { + count = len / 4; + iowrite32_rep(fifoaddr, buf, count); + + if (len & 0x00000003) { + pb = (unsigned char *)buf + count * 4; + for (i = 0; i < (len & 0x00000003); i++) { + if (r8a66597_read(r8a66597, CFIFOSEL) & BIGEND) + iowrite8(pb[i], fifoaddr + i); + else + iowrite8(pb[i], fifoaddr + 3 - i); + } + } + } else { + int odd = len & 0x0001; + + len = len / 2; + iowrite16_rep(fifoaddr, buf, len); + if (unlikely(odd)) { + buf = &buf[len]; + if (r8a66597->pdata->wr0_shorted_to_wr1) + r8a66597_bclr(r8a66597, MBW_16, pipe->fifosel); + iowrite8((unsigned char)*buf, fifoaddr); + if (r8a66597->pdata->wr0_shorted_to_wr1) + r8a66597_bset(r8a66597, MBW_16, pipe->fifosel); + } + } +} + +static inline unsigned long get_syscfg_reg(int port) +{ + return port == 0 ? SYSCFG0 : SYSCFG1; +} + +static inline unsigned long get_syssts_reg(int port) +{ + return port == 0 ? SYSSTS0 : SYSSTS1; +} + +static inline unsigned long get_dvstctr_reg(int port) +{ + return port == 0 ? DVSTCTR0 : DVSTCTR1; +} + +static inline unsigned long get_dmacfg_reg(int port) +{ + return port == 0 ? DMA0CFG : DMA1CFG; +} + +static inline unsigned long get_intenb_reg(int port) +{ + return port == 0 ? INTENB1 : INTENB2; +} + +static inline unsigned long get_intsts_reg(int port) +{ + return port == 0 ? INTSTS1 : INTSTS2; +} + +static inline u16 get_rh_usb_speed(struct r8a66597 *r8a66597, int port) +{ + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + return r8a66597_read(r8a66597, dvstctr_reg) & RHST; +} + +static inline void r8a66597_port_power(struct r8a66597 *r8a66597, int port, + int power) +{ + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + if (r8a66597->pdata->port_power) { + r8a66597->pdata->port_power(port, power); + } else { + if (power) + r8a66597_bset(r8a66597, VBOUT, dvstctr_reg); + else + r8a66597_bclr(r8a66597, VBOUT, dvstctr_reg); + } +} + +static inline u16 get_xtal_from_pdata(struct r8a66597_platdata *pdata) +{ + u16 clock = 0; + + switch (pdata->xtal) { + case R8A66597_PLATDATA_XTAL_12MHZ: + clock = XTAL12; + break; + case R8A66597_PLATDATA_XTAL_24MHZ: + clock = XTAL24; + break; + case R8A66597_PLATDATA_XTAL_48MHZ: + clock = XTAL48; + break; + default: + printk(KERN_ERR "r8a66597: platdata clock is wrong.\n"); + break; + } + + return clock; +} + +#define get_pipectr_addr(pipenum) (PIPE1CTR + (pipenum - 1) * 2) +#define get_pipetre_addr(pipenum) (PIPE1TRE + (pipenum - 1) * 4) +#define get_pipetrn_addr(pipenum) (PIPE1TRN + (pipenum - 1) * 4) +#define get_devadd_addr(address) (DEVADD0 + address * 2) + +#define enable_irq_ready(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, BRDYENB) +#define disable_irq_ready(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, BRDYENB) +#define enable_irq_empty(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, BEMPENB) +#define disable_irq_empty(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, BEMPENB) +#define enable_irq_nrdy(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, NRDYENB) +#define disable_irq_nrdy(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, NRDYENB) + +#endif /* __R8A66597_H__ */ + diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c new file mode 100644 index 00000000..b2ec7fe7 --- /dev/null +++ b/drivers/usb/host/sl811-hcd.c @@ -0,0 +1,1822 @@ +/* + * SL811HS HCD (Host Controller Driver) for USB. + * + * Copyright (C) 2004 Psion Teklogix (for NetBook PRO) + * Copyright (C) 2004-2005 David Brownell + * + * Periodic scheduling is based on Roman's OHCI code + * Copyright (C) 1999 Roman Weissgaerber + * + * The SL811HS controller handles host side USB (like the SL11H, but with + * another register set and SOF generation) as well as peripheral side USB + * (like the SL811S). This driver version doesn't implement the Gadget API + * for the peripheral role; or OTG (that'd need much external circuitry). + * + * For documentation, see the SL811HS spec and the "SL811HS Embedded Host" + * document (providing significant pieces missing from that spec); plus + * the SL811S spec if you want peripheral side info. + */ + +/* + * Status: Passed basic stress testing, works with hubs, mice, keyboards, + * and usb-storage. + * + * TODO: + * - usb suspend/resume triggered by sl811 (with PM_RUNTIME) + * - various issues noted in the code + * - performance work; use both register banks; ... + * - use urb->iso_frame_desc[] with ISO transfers + */ + +#undef VERBOSE +#undef PACKET_TRACE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sl811.h" + + +MODULE_DESCRIPTION("SL811HS USB Host Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sl811-hcd"); + +#define DRIVER_VERSION "19 May 2005" + + +#ifndef DEBUG +# define STUB_DEBUG_FILE +#endif + +/* for now, use only one transfer register bank */ +#undef USE_B + +// #define QUIRK2 +#define QUIRK3 + +static const char hcd_name[] = "sl811-hcd"; + +/*-------------------------------------------------------------------------*/ + +static void port_power(struct sl811 *sl811, int is_on) +{ + struct usb_hcd *hcd = sl811_to_hcd(sl811); + + /* hub is inactive unless the port is powered */ + if (is_on) { + if (sl811->port1 & USB_PORT_STAT_POWER) + return; + + sl811->port1 = USB_PORT_STAT_POWER; + sl811->irq_enable = SL11H_INTMASK_INSRMV; + } else { + sl811->port1 = 0; + sl811->irq_enable = 0; + hcd->state = HC_STATE_HALT; + } + sl811->ctrl1 = 0; + sl811_write(sl811, SL11H_IRQ_ENABLE, 0); + sl811_write(sl811, SL11H_IRQ_STATUS, ~0); + + if (sl811->board && sl811->board->port_power) { + /* switch VBUS, at 500mA unless hub power budget gets set */ + DBG("power %s\n", is_on ? "on" : "off"); + sl811->board->port_power(hcd->self.controller, is_on); + } + + /* reset as thoroughly as we can */ + if (sl811->board && sl811->board->reset) + sl811->board->reset(hcd->self.controller); + else { + sl811_write(sl811, SL11H_CTLREG1, SL11H_CTL1MASK_SE0); + mdelay(20); + } + + sl811_write(sl811, SL11H_IRQ_ENABLE, 0); + sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); + sl811_write(sl811, SL811HS_CTLREG2, SL811HS_CTL2_INIT); + sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); + + // if !is_on, put into lowpower mode now +} + +/*-------------------------------------------------------------------------*/ + +/* This is a PIO-only HCD. Queueing appends URBs to the endpoint's queue, + * and may start I/O. Endpoint queues are scanned during completion irq + * handlers (one per packet: ACK, NAK, faults, etc) and urb cancellation. + * + * Using an external DMA engine to copy a packet at a time could work, + * though setup/teardown costs may be too big to make it worthwhile. + */ + +/* SETUP starts a new control request. Devices are not allowed to + * STALL or NAK these; they must cancel any pending control requests. + */ +static void setup_packet( + struct sl811 *sl811, + struct sl811h_ep *ep, + struct urb *urb, + u8 bank, + u8 control +) +{ + u8 addr; + u8 len; + void __iomem *data_reg; + + addr = SL811HS_PACKET_BUF(bank == 0); + len = sizeof(struct usb_ctrlrequest); + data_reg = sl811->data_reg; + sl811_write_buf(sl811, addr, urb->setup_packet, len); + + /* autoincrementing */ + sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); + writeb(len, data_reg); + writeb(SL_SETUP /* | ep->epnum */, data_reg); + writeb(usb_pipedevice(urb->pipe), data_reg); + + /* always OUT/data0 */ + sl811_write(sl811, bank + SL11H_HOSTCTLREG, + control | SL11H_HCTLMASK_OUT); + ep->length = 0; + PACKET("SETUP qh%p\n", ep); +} + +/* STATUS finishes control requests, often after IN or OUT data packets */ +static void status_packet( + struct sl811 *sl811, + struct sl811h_ep *ep, + struct urb *urb, + u8 bank, + u8 control +) +{ + int do_out; + void __iomem *data_reg; + + do_out = urb->transfer_buffer_length && usb_pipein(urb->pipe); + data_reg = sl811->data_reg; + + /* autoincrementing */ + sl811_write(sl811, bank + SL11H_BUFADDRREG, 0); + writeb(0, data_reg); + writeb((do_out ? SL_OUT : SL_IN) /* | ep->epnum */, data_reg); + writeb(usb_pipedevice(urb->pipe), data_reg); + + /* always data1; sometimes IN */ + control |= SL11H_HCTLMASK_TOGGLE; + if (do_out) + control |= SL11H_HCTLMASK_OUT; + sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); + ep->length = 0; + PACKET("STATUS%s/%s qh%p\n", ep->nak_count ? "/retry" : "", + do_out ? "out" : "in", ep); +} + +/* IN packets can be used with any type of endpoint. here we just + * start the transfer, data from the peripheral may arrive later. + * urb->iso_frame_desc is currently ignored here... + */ +static void in_packet( + struct sl811 *sl811, + struct sl811h_ep *ep, + struct urb *urb, + u8 bank, + u8 control +) +{ + u8 addr; + u8 len; + void __iomem *data_reg; + + /* avoid losing data on overflow */ + len = ep->maxpacket; + addr = SL811HS_PACKET_BUF(bank == 0); + if (!(control & SL11H_HCTLMASK_ISOCH) + && usb_gettoggle(urb->dev, ep->epnum, 0)) + control |= SL11H_HCTLMASK_TOGGLE; + data_reg = sl811->data_reg; + + /* autoincrementing */ + sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); + writeb(len, data_reg); + writeb(SL_IN | ep->epnum, data_reg); + writeb(usb_pipedevice(urb->pipe), data_reg); + + sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); + ep->length = min_t(u32, len, + urb->transfer_buffer_length - urb->actual_length); + PACKET("IN%s/%d qh%p len%d\n", ep->nak_count ? "/retry" : "", + !!usb_gettoggle(urb->dev, ep->epnum, 0), ep, len); +} + +/* OUT packets can be used with any type of endpoint. + * urb->iso_frame_desc is currently ignored here... + */ +static void out_packet( + struct sl811 *sl811, + struct sl811h_ep *ep, + struct urb *urb, + u8 bank, + u8 control +) +{ + void *buf; + u8 addr; + u8 len; + void __iomem *data_reg; + + buf = urb->transfer_buffer + urb->actual_length; + prefetch(buf); + + len = min_t(u32, ep->maxpacket, + urb->transfer_buffer_length - urb->actual_length); + + if (!(control & SL11H_HCTLMASK_ISOCH) + && usb_gettoggle(urb->dev, ep->epnum, 1)) + control |= SL11H_HCTLMASK_TOGGLE; + addr = SL811HS_PACKET_BUF(bank == 0); + data_reg = sl811->data_reg; + + sl811_write_buf(sl811, addr, buf, len); + + /* autoincrementing */ + sl811_write(sl811, bank + SL11H_BUFADDRREG, addr); + writeb(len, data_reg); + writeb(SL_OUT | ep->epnum, data_reg); + writeb(usb_pipedevice(urb->pipe), data_reg); + + sl811_write(sl811, bank + SL11H_HOSTCTLREG, + control | SL11H_HCTLMASK_OUT); + ep->length = len; + PACKET("OUT%s/%d qh%p len%d\n", ep->nak_count ? "/retry" : "", + !!usb_gettoggle(urb->dev, ep->epnum, 1), ep, len); +} + +/*-------------------------------------------------------------------------*/ + +/* caller updates on-chip enables later */ + +static inline void sofirq_on(struct sl811 *sl811) +{ + if (sl811->irq_enable & SL11H_INTMASK_SOFINTR) + return; + VDBG("sof irq on\n"); + sl811->irq_enable |= SL11H_INTMASK_SOFINTR; +} + +static inline void sofirq_off(struct sl811 *sl811) +{ + if (!(sl811->irq_enable & SL11H_INTMASK_SOFINTR)) + return; + VDBG("sof irq off\n"); + sl811->irq_enable &= ~SL11H_INTMASK_SOFINTR; +} + +/*-------------------------------------------------------------------------*/ + +/* pick the next endpoint for a transaction, and issue it. + * frames start with periodic transfers (after whatever is pending + * from the previous frame), and the rest of the time is async + * transfers, scheduled round-robin. + */ +static struct sl811h_ep *start(struct sl811 *sl811, u8 bank) +{ + struct sl811h_ep *ep; + struct urb *urb; + int fclock; + u8 control; + + /* use endpoint at schedule head */ + if (sl811->next_periodic) { + ep = sl811->next_periodic; + sl811->next_periodic = ep->next; + } else { + if (sl811->next_async) + ep = sl811->next_async; + else if (!list_empty(&sl811->async)) + ep = container_of(sl811->async.next, + struct sl811h_ep, schedule); + else { + /* could set up the first fullspeed periodic + * transfer for the next frame ... + */ + return NULL; + } + +#ifdef USE_B + if ((bank && sl811->active_b == ep) || sl811->active_a == ep) + return NULL; +#endif + + if (ep->schedule.next == &sl811->async) + sl811->next_async = NULL; + else + sl811->next_async = container_of(ep->schedule.next, + struct sl811h_ep, schedule); + } + + if (unlikely(list_empty(&ep->hep->urb_list))) { + DBG("empty %p queue?\n", ep); + return NULL; + } + + urb = container_of(ep->hep->urb_list.next, struct urb, urb_list); + control = ep->defctrl; + + /* if this frame doesn't have enough time left to transfer this + * packet, wait till the next frame. too-simple algorithm... + */ + fclock = sl811_read(sl811, SL11H_SOFTMRREG) << 6; + fclock -= 100; /* setup takes not much time */ + if (urb->dev->speed == USB_SPEED_LOW) { + if (control & SL11H_HCTLMASK_PREAMBLE) { + /* also note erratum 1: some hubs won't work */ + fclock -= 800; + } + fclock -= ep->maxpacket << 8; + + /* erratum 2: AFTERSOF only works for fullspeed */ + if (fclock < 0) { + if (ep->period) + sl811->stat_overrun++; + sofirq_on(sl811); + return NULL; + } + } else { + fclock -= 12000 / 19; /* 19 64byte packets/msec */ + if (fclock < 0) { + if (ep->period) + sl811->stat_overrun++; + control |= SL11H_HCTLMASK_AFTERSOF; + + /* throttle bulk/control irq noise */ + } else if (ep->nak_count) + control |= SL11H_HCTLMASK_AFTERSOF; + } + + + switch (ep->nextpid) { + case USB_PID_IN: + in_packet(sl811, ep, urb, bank, control); + break; + case USB_PID_OUT: + out_packet(sl811, ep, urb, bank, control); + break; + case USB_PID_SETUP: + setup_packet(sl811, ep, urb, bank, control); + break; + case USB_PID_ACK: /* for control status */ + status_packet(sl811, ep, urb, bank, control); + break; + default: + DBG("bad ep%p pid %02x\n", ep, ep->nextpid); + ep = NULL; + } + return ep; +} + +#define MIN_JIFFIES ((msecs_to_jiffies(2) > 1) ? msecs_to_jiffies(2) : 2) + +static inline void start_transfer(struct sl811 *sl811) +{ + if (sl811->port1 & USB_PORT_STAT_SUSPEND) + return; + if (sl811->active_a == NULL) { + sl811->active_a = start(sl811, SL811_EP_A(SL811_HOST_BUF)); + if (sl811->active_a != NULL) + sl811->jiffies_a = jiffies + MIN_JIFFIES; + } +#ifdef USE_B + if (sl811->active_b == NULL) { + sl811->active_b = start(sl811, SL811_EP_B(SL811_HOST_BUF)); + if (sl811->active_b != NULL) + sl811->jiffies_b = jiffies + MIN_JIFFIES; + } +#endif +} + +static void finish_request( + struct sl811 *sl811, + struct sl811h_ep *ep, + struct urb *urb, + int status +) __releases(sl811->lock) __acquires(sl811->lock) +{ + unsigned i; + + if (usb_pipecontrol(urb->pipe)) + ep->nextpid = USB_PID_SETUP; + + usb_hcd_unlink_urb_from_ep(sl811_to_hcd(sl811), urb); + spin_unlock(&sl811->lock); + usb_hcd_giveback_urb(sl811_to_hcd(sl811), urb, status); + spin_lock(&sl811->lock); + + /* leave active endpoints in the schedule */ + if (!list_empty(&ep->hep->urb_list)) + return; + + /* async deschedule? */ + if (!list_empty(&ep->schedule)) { + list_del_init(&ep->schedule); + if (ep == sl811->next_async) + sl811->next_async = NULL; + return; + } + + /* periodic deschedule */ + DBG("deschedule qh%d/%p branch %d\n", ep->period, ep, ep->branch); + for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) { + struct sl811h_ep *temp; + struct sl811h_ep **prev = &sl811->periodic[i]; + + while (*prev && ((temp = *prev) != ep)) + prev = &temp->next; + if (*prev) + *prev = ep->next; + sl811->load[i] -= ep->load; + } + ep->branch = PERIODIC_SIZE; + sl811->periodic_count--; + sl811_to_hcd(sl811)->self.bandwidth_allocated + -= ep->load / ep->period; + if (ep == sl811->next_periodic) + sl811->next_periodic = ep->next; + + /* we might turn SOFs back on again for the async schedule */ + if (sl811->periodic_count == 0) + sofirq_off(sl811); +} + +static void +done(struct sl811 *sl811, struct sl811h_ep *ep, u8 bank) +{ + u8 status; + struct urb *urb; + int urbstat = -EINPROGRESS; + + if (unlikely(!ep)) + return; + + status = sl811_read(sl811, bank + SL11H_PKTSTATREG); + + urb = container_of(ep->hep->urb_list.next, struct urb, urb_list); + + /* we can safely ignore NAKs */ + if (status & SL11H_STATMASK_NAK) { + // PACKET("...NAK_%02x qh%p\n", bank, ep); + if (!ep->period) + ep->nak_count++; + ep->error_count = 0; + + /* ACK advances transfer, toggle, and maybe queue */ + } else if (status & SL11H_STATMASK_ACK) { + struct usb_device *udev = urb->dev; + int len; + unsigned char *buf; + + /* urb->iso_frame_desc is currently ignored here... */ + + ep->nak_count = ep->error_count = 0; + switch (ep->nextpid) { + case USB_PID_OUT: + // PACKET("...ACK/out_%02x qh%p\n", bank, ep); + urb->actual_length += ep->length; + usb_dotoggle(udev, ep->epnum, 1); + if (urb->actual_length + == urb->transfer_buffer_length) { + if (usb_pipecontrol(urb->pipe)) + ep->nextpid = USB_PID_ACK; + + /* some bulk protocols terminate OUT transfers + * by a short packet, using ZLPs not padding. + */ + else if (ep->length < ep->maxpacket + || !(urb->transfer_flags + & URB_ZERO_PACKET)) + urbstat = 0; + } + break; + case USB_PID_IN: + // PACKET("...ACK/in_%02x qh%p\n", bank, ep); + buf = urb->transfer_buffer + urb->actual_length; + prefetchw(buf); + len = ep->maxpacket - sl811_read(sl811, + bank + SL11H_XFERCNTREG); + if (len > ep->length) { + len = ep->length; + urbstat = -EOVERFLOW; + } + urb->actual_length += len; + sl811_read_buf(sl811, SL811HS_PACKET_BUF(bank == 0), + buf, len); + usb_dotoggle(udev, ep->epnum, 0); + if (urbstat == -EINPROGRESS && + (len < ep->maxpacket || + urb->actual_length == + urb->transfer_buffer_length)) { + if (usb_pipecontrol(urb->pipe)) + ep->nextpid = USB_PID_ACK; + else + urbstat = 0; + } + break; + case USB_PID_SETUP: + // PACKET("...ACK/setup_%02x qh%p\n", bank, ep); + if (urb->transfer_buffer_length == urb->actual_length) + ep->nextpid = USB_PID_ACK; + else if (usb_pipeout(urb->pipe)) { + usb_settoggle(udev, 0, 1, 1); + ep->nextpid = USB_PID_OUT; + } else { + usb_settoggle(udev, 0, 0, 1); + ep->nextpid = USB_PID_IN; + } + break; + case USB_PID_ACK: + // PACKET("...ACK/status_%02x qh%p\n", bank, ep); + urbstat = 0; + break; + } + + /* STALL stops all transfers */ + } else if (status & SL11H_STATMASK_STALL) { + PACKET("...STALL_%02x qh%p\n", bank, ep); + ep->nak_count = ep->error_count = 0; + urbstat = -EPIPE; + + /* error? retry, until "3 strikes" */ + } else if (++ep->error_count >= 3) { + if (status & SL11H_STATMASK_TMOUT) + urbstat = -ETIME; + else if (status & SL11H_STATMASK_OVF) + urbstat = -EOVERFLOW; + else + urbstat = -EPROTO; + ep->error_count = 0; + PACKET("...3STRIKES_%02x %02x qh%p stat %d\n", + bank, status, ep, urbstat); + } + + if (urbstat != -EINPROGRESS || urb->unlinked) + finish_request(sl811, ep, urb, urbstat); +} + +static inline u8 checkdone(struct sl811 *sl811) +{ + u8 ctl; + u8 irqstat = 0; + + if (sl811->active_a && time_before_eq(sl811->jiffies_a, jiffies)) { + ctl = sl811_read(sl811, SL811_EP_A(SL11H_HOSTCTLREG)); + if (ctl & SL11H_HCTLMASK_ARM) + sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 0); + DBG("%s DONE_A: ctrl %02x sts %02x\n", + (ctl & SL11H_HCTLMASK_ARM) ? "timeout" : "lost", + ctl, + sl811_read(sl811, SL811_EP_A(SL11H_PKTSTATREG))); + irqstat |= SL11H_INTMASK_DONE_A; + } +#ifdef USE_B + if (sl811->active_b && time_before_eq(sl811->jiffies_b, jiffies)) { + ctl = sl811_read(sl811, SL811_EP_B(SL11H_HOSTCTLREG)); + if (ctl & SL11H_HCTLMASK_ARM) + sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), 0); + DBG("%s DONE_B: ctrl %02x sts %02x\n", + (ctl & SL11H_HCTLMASK_ARM) ? "timeout" : "lost", + ctl, + sl811_read(sl811, SL811_EP_B(SL11H_PKTSTATREG))); + irqstat |= SL11H_INTMASK_DONE_A; + } +#endif + return irqstat; +} + +static irqreturn_t sl811h_irq(struct usb_hcd *hcd) +{ + struct sl811 *sl811 = hcd_to_sl811(hcd); + u8 irqstat; + irqreturn_t ret = IRQ_NONE; + unsigned retries = 5; + + spin_lock(&sl811->lock); + +retry: + irqstat = sl811_read(sl811, SL11H_IRQ_STATUS) & ~SL11H_INTMASK_DP; + if (irqstat) { + sl811_write(sl811, SL11H_IRQ_STATUS, irqstat); + irqstat &= sl811->irq_enable; + } + +#ifdef QUIRK2 + /* this may no longer be necessary ... */ + if (irqstat == 0) { + irqstat = checkdone(sl811); + if (irqstat) + sl811->stat_lost++; + } +#endif + + /* USB packets, not necessarily handled in the order they're + * issued ... that's fine if they're different endpoints. + */ + if (irqstat & SL11H_INTMASK_DONE_A) { + done(sl811, sl811->active_a, SL811_EP_A(SL811_HOST_BUF)); + sl811->active_a = NULL; + sl811->stat_a++; + } +#ifdef USE_B + if (irqstat & SL11H_INTMASK_DONE_B) { + done(sl811, sl811->active_b, SL811_EP_B(SL811_HOST_BUF)); + sl811->active_b = NULL; + sl811->stat_b++; + } +#endif + if (irqstat & SL11H_INTMASK_SOFINTR) { + unsigned index; + + index = sl811->frame++ % (PERIODIC_SIZE - 1); + sl811->stat_sof++; + + /* be graceful about almost-inevitable periodic schedule + * overruns: continue the previous frame's transfers iff + * this one has nothing scheduled. + */ + if (sl811->next_periodic) { + // ERR("overrun to slot %d\n", index); + sl811->stat_overrun++; + } + if (sl811->periodic[index]) + sl811->next_periodic = sl811->periodic[index]; + } + + /* khubd manages debouncing and wakeup */ + if (irqstat & SL11H_INTMASK_INSRMV) { + sl811->stat_insrmv++; + + /* most stats are reset for each VBUS session */ + sl811->stat_wake = 0; + sl811->stat_sof = 0; + sl811->stat_a = 0; + sl811->stat_b = 0; + sl811->stat_lost = 0; + + sl811->ctrl1 = 0; + sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); + + sl811->irq_enable = SL11H_INTMASK_INSRMV; + sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); + + /* usbcore nukes other pending transactions on disconnect */ + if (sl811->active_a) { + sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 0); + finish_request(sl811, sl811->active_a, + container_of(sl811->active_a + ->hep->urb_list.next, + struct urb, urb_list), + -ESHUTDOWN); + sl811->active_a = NULL; + } +#ifdef USE_B + if (sl811->active_b) { + sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), 0); + finish_request(sl811, sl811->active_b, + container_of(sl811->active_b + ->hep->urb_list.next, + struct urb, urb_list), + NULL, -ESHUTDOWN); + sl811->active_b = NULL; + } +#endif + + /* port status seems weird until after reset, so + * force the reset and make khubd clean up later. + */ + if (irqstat & SL11H_INTMASK_RD) + sl811->port1 &= ~USB_PORT_STAT_CONNECTION; + else + sl811->port1 |= USB_PORT_STAT_CONNECTION; + + sl811->port1 |= USB_PORT_STAT_C_CONNECTION << 16; + + } else if (irqstat & SL11H_INTMASK_RD) { + if (sl811->port1 & USB_PORT_STAT_SUSPEND) { + DBG("wakeup\n"); + sl811->port1 |= USB_PORT_STAT_C_SUSPEND << 16; + sl811->stat_wake++; + } else + irqstat &= ~SL11H_INTMASK_RD; + } + + if (irqstat) { + if (sl811->port1 & USB_PORT_STAT_ENABLE) + start_transfer(sl811); + ret = IRQ_HANDLED; + if (retries--) + goto retry; + } + + if (sl811->periodic_count == 0 && list_empty(&sl811->async)) + sofirq_off(sl811); + sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); + + spin_unlock(&sl811->lock); + + return ret; +} + +/*-------------------------------------------------------------------------*/ + +/* usb 1.1 says max 90% of a frame is available for periodic transfers. + * this driver doesn't promise that much since it's got to handle an + * IRQ per packet; irq handling latencies also use up that time. + * + * NOTE: the periodic schedule is a sparse tree, with the load for + * each branch minimized. see fig 3.5 in the OHCI spec for example. + */ +#define MAX_PERIODIC_LOAD 500 /* out of 1000 usec */ + +static int balance(struct sl811 *sl811, u16 period, u16 load) +{ + int i, branch = -ENOSPC; + + /* search for the least loaded schedule branch of that period + * which has enough bandwidth left unreserved. + */ + for (i = 0; i < period ; i++) { + if (branch < 0 || sl811->load[branch] > sl811->load[i]) { + int j; + + for (j = i; j < PERIODIC_SIZE; j += period) { + if ((sl811->load[j] + load) + > MAX_PERIODIC_LOAD) + break; + } + if (j < PERIODIC_SIZE) + continue; + branch = i; + } + } + return branch; +} + +/*-------------------------------------------------------------------------*/ + +static int sl811h_urb_enqueue( + struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags +) { + struct sl811 *sl811 = hcd_to_sl811(hcd); + struct usb_device *udev = urb->dev; + unsigned int pipe = urb->pipe; + int is_out = !usb_pipein(pipe); + int type = usb_pipetype(pipe); + int epnum = usb_pipeendpoint(pipe); + struct sl811h_ep *ep = NULL; + unsigned long flags; + int i; + int retval; + struct usb_host_endpoint *hep = urb->ep; + +#ifndef CONFIG_USB_SL811_HCD_ISO + if (type == PIPE_ISOCHRONOUS) + return -ENOSPC; +#endif + + /* avoid all allocations within spinlocks */ + if (!hep->hcpriv) { + ep = kzalloc(sizeof *ep, mem_flags); + if (ep == NULL) + return -ENOMEM; + } + + spin_lock_irqsave(&sl811->lock, flags); + + /* don't submit to a dead or disabled port */ + if (!(sl811->port1 & USB_PORT_STAT_ENABLE) + || !HC_IS_RUNNING(hcd->state)) { + retval = -ENODEV; + kfree(ep); + goto fail_not_linked; + } + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) { + kfree(ep); + goto fail_not_linked; + } + + if (hep->hcpriv) { + kfree(ep); + ep = hep->hcpriv; + } else if (!ep) { + retval = -ENOMEM; + goto fail; + + } else { + INIT_LIST_HEAD(&ep->schedule); + ep->udev = udev; + ep->epnum = epnum; + ep->maxpacket = usb_maxpacket(udev, urb->pipe, is_out); + ep->defctrl = SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENABLE; + usb_settoggle(udev, epnum, is_out, 0); + + if (type == PIPE_CONTROL) + ep->nextpid = USB_PID_SETUP; + else if (is_out) + ep->nextpid = USB_PID_OUT; + else + ep->nextpid = USB_PID_IN; + + if (ep->maxpacket > H_MAXPACKET) { + /* iso packets up to 240 bytes could work... */ + DBG("dev %d ep%d maxpacket %d\n", + udev->devnum, epnum, ep->maxpacket); + retval = -EINVAL; + kfree(ep); + goto fail; + } + + if (udev->speed == USB_SPEED_LOW) { + /* send preamble for external hub? */ + if (!(sl811->ctrl1 & SL11H_CTL1MASK_LSPD)) + ep->defctrl |= SL11H_HCTLMASK_PREAMBLE; + } + switch (type) { + case PIPE_ISOCHRONOUS: + case PIPE_INTERRUPT: + if (urb->interval > PERIODIC_SIZE) + urb->interval = PERIODIC_SIZE; + ep->period = urb->interval; + ep->branch = PERIODIC_SIZE; + if (type == PIPE_ISOCHRONOUS) + ep->defctrl |= SL11H_HCTLMASK_ISOCH; + ep->load = usb_calc_bus_time(udev->speed, !is_out, + (type == PIPE_ISOCHRONOUS), + usb_maxpacket(udev, pipe, is_out)) + / 1000; + break; + } + + ep->hep = hep; + hep->hcpriv = ep; + } + + /* maybe put endpoint into schedule */ + switch (type) { + case PIPE_CONTROL: + case PIPE_BULK: + if (list_empty(&ep->schedule)) + list_add_tail(&ep->schedule, &sl811->async); + break; + case PIPE_ISOCHRONOUS: + case PIPE_INTERRUPT: + urb->interval = ep->period; + if (ep->branch < PERIODIC_SIZE) { + /* NOTE: the phase is correct here, but the value + * needs offsetting by the transfer queue depth. + * All current drivers ignore start_frame, so this + * is unlikely to ever matter... + */ + urb->start_frame = (sl811->frame & (PERIODIC_SIZE - 1)) + + ep->branch; + break; + } + + retval = balance(sl811, ep->period, ep->load); + if (retval < 0) + goto fail; + ep->branch = retval; + retval = 0; + urb->start_frame = (sl811->frame & (PERIODIC_SIZE - 1)) + + ep->branch; + + /* sort each schedule branch by period (slow before fast) + * to share the faster parts of the tree without needing + * dummy/placeholder nodes + */ + DBG("schedule qh%d/%p branch %d\n", ep->period, ep, ep->branch); + for (i = ep->branch; i < PERIODIC_SIZE; i += ep->period) { + struct sl811h_ep **prev = &sl811->periodic[i]; + struct sl811h_ep *here = *prev; + + while (here && ep != here) { + if (ep->period > here->period) + break; + prev = &here->next; + here = *prev; + } + if (ep != here) { + ep->next = here; + *prev = ep; + } + sl811->load[i] += ep->load; + } + sl811->periodic_count++; + hcd->self.bandwidth_allocated += ep->load / ep->period; + sofirq_on(sl811); + } + + urb->hcpriv = hep; + start_transfer(sl811); + sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); +fail: + if (retval) + usb_hcd_unlink_urb_from_ep(hcd, urb); +fail_not_linked: + spin_unlock_irqrestore(&sl811->lock, flags); + return retval; +} + +static int sl811h_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct sl811 *sl811 = hcd_to_sl811(hcd); + struct usb_host_endpoint *hep; + unsigned long flags; + struct sl811h_ep *ep; + int retval; + + spin_lock_irqsave(&sl811->lock, flags); + retval = usb_hcd_check_unlink_urb(hcd, urb, status); + if (retval) + goto fail; + + hep = urb->hcpriv; + ep = hep->hcpriv; + if (ep) { + /* finish right away if this urb can't be active ... + * note that some drivers wrongly expect delays + */ + if (ep->hep->urb_list.next != &urb->urb_list) { + /* not front of queue? never active */ + + /* for active transfers, we expect an IRQ */ + } else if (sl811->active_a == ep) { + if (time_before_eq(sl811->jiffies_a, jiffies)) { + /* happens a lot with lowspeed?? */ + DBG("giveup on DONE_A: ctrl %02x sts %02x\n", + sl811_read(sl811, + SL811_EP_A(SL11H_HOSTCTLREG)), + sl811_read(sl811, + SL811_EP_A(SL11H_PKTSTATREG))); + sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), + 0); + sl811->active_a = NULL; + } else + urb = NULL; +#ifdef USE_B + } else if (sl811->active_b == ep) { + if (time_before_eq(sl811->jiffies_a, jiffies)) { + /* happens a lot with lowspeed?? */ + DBG("giveup on DONE_B: ctrl %02x sts %02x\n", + sl811_read(sl811, + SL811_EP_B(SL11H_HOSTCTLREG)), + sl811_read(sl811, + SL811_EP_B(SL11H_PKTSTATREG))); + sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), + 0); + sl811->active_b = NULL; + } else + urb = NULL; +#endif + } else { + /* front of queue for inactive endpoint */ + } + + if (urb) + finish_request(sl811, ep, urb, 0); + else + VDBG("dequeue, urb %p active %s; wait4irq\n", urb, + (sl811->active_a == ep) ? "A" : "B"); + } else + retval = -EINVAL; + fail: + spin_unlock_irqrestore(&sl811->lock, flags); + return retval; +} + +static void +sl811h_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) +{ + struct sl811h_ep *ep = hep->hcpriv; + + if (!ep) + return; + + /* assume we'd just wait for the irq */ + if (!list_empty(&hep->urb_list)) + msleep(3); + if (!list_empty(&hep->urb_list)) + WARNING("ep %p not empty?\n", ep); + + kfree(ep); + hep->hcpriv = NULL; +} + +static int +sl811h_get_frame(struct usb_hcd *hcd) +{ + struct sl811 *sl811 = hcd_to_sl811(hcd); + + /* wrong except while periodic transfers are scheduled; + * never matches the on-the-wire frame; + * subject to overruns. + */ + return sl811->frame; +} + + +/*-------------------------------------------------------------------------*/ + +/* the virtual root hub timer IRQ checks for hub status */ +static int +sl811h_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct sl811 *sl811 = hcd_to_sl811(hcd); +#ifdef QUIRK3 + unsigned long flags; + + /* non-SMP HACK: use root hub timer as i/o watchdog + * this seems essential when SOF IRQs aren't in use... + */ + local_irq_save(flags); + if (!timer_pending(&sl811->timer)) { + if (sl811h_irq( /* ~0, */ hcd) != IRQ_NONE) + sl811->stat_lost++; + } + local_irq_restore(flags); +#endif + + if (!(sl811->port1 & (0xffff << 16))) + return 0; + + /* tell khubd port 1 changed */ + *buf = (1 << 1); + return 1; +} + +static void +sl811h_hub_descriptor ( + struct sl811 *sl811, + struct usb_hub_descriptor *desc +) { + u16 temp = 0; + + desc->bDescriptorType = 0x29; + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = 1; + desc->bDescLength = 9; + + /* per-port power switching (gang of one!), or none */ + desc->bPwrOn2PwrGood = 0; + if (sl811->board && sl811->board->port_power) { + desc->bPwrOn2PwrGood = sl811->board->potpg; + if (!desc->bPwrOn2PwrGood) + desc->bPwrOn2PwrGood = 10; + temp = 0x0001; + } else + temp = 0x0002; + + /* no overcurrent errors detection/handling */ + temp |= 0x0010; + + desc->wHubCharacteristics = cpu_to_le16(temp); + + /* ports removable, and legacy PortPwrCtrlMask */ + desc->u.hs.DeviceRemovable[0] = 0 << 1; + desc->u.hs.DeviceRemovable[1] = ~0; +} + +static void +sl811h_timer(unsigned long _sl811) +{ + struct sl811 *sl811 = (void *) _sl811; + unsigned long flags; + u8 irqstat; + u8 signaling = sl811->ctrl1 & SL11H_CTL1MASK_FORCE; + const u32 mask = USB_PORT_STAT_CONNECTION + | USB_PORT_STAT_ENABLE + | USB_PORT_STAT_LOW_SPEED; + + spin_lock_irqsave(&sl811->lock, flags); + + /* stop special signaling */ + sl811->ctrl1 &= ~SL11H_CTL1MASK_FORCE; + sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); + udelay(3); + + irqstat = sl811_read(sl811, SL11H_IRQ_STATUS); + + switch (signaling) { + case SL11H_CTL1MASK_SE0: + DBG("end reset\n"); + sl811->port1 = (USB_PORT_STAT_C_RESET << 16) + | USB_PORT_STAT_POWER; + sl811->ctrl1 = 0; + /* don't wrongly ack RD */ + if (irqstat & SL11H_INTMASK_INSRMV) + irqstat &= ~SL11H_INTMASK_RD; + break; + case SL11H_CTL1MASK_K: + DBG("end resume\n"); + sl811->port1 &= ~USB_PORT_STAT_SUSPEND; + break; + default: + DBG("odd timer signaling: %02x\n", signaling); + break; + } + sl811_write(sl811, SL11H_IRQ_STATUS, irqstat); + + if (irqstat & SL11H_INTMASK_RD) { + /* usbcore nukes all pending transactions on disconnect */ + if (sl811->port1 & USB_PORT_STAT_CONNECTION) + sl811->port1 |= (USB_PORT_STAT_C_CONNECTION << 16) + | (USB_PORT_STAT_C_ENABLE << 16); + sl811->port1 &= ~mask; + sl811->irq_enable = SL11H_INTMASK_INSRMV; + } else { + sl811->port1 |= mask; + if (irqstat & SL11H_INTMASK_DP) + sl811->port1 &= ~USB_PORT_STAT_LOW_SPEED; + sl811->irq_enable = SL11H_INTMASK_INSRMV | SL11H_INTMASK_RD; + } + + if (sl811->port1 & USB_PORT_STAT_CONNECTION) { + u8 ctrl2 = SL811HS_CTL2_INIT; + + sl811->irq_enable |= SL11H_INTMASK_DONE_A; +#ifdef USE_B + sl811->irq_enable |= SL11H_INTMASK_DONE_B; +#endif + if (sl811->port1 & USB_PORT_STAT_LOW_SPEED) { + sl811->ctrl1 |= SL11H_CTL1MASK_LSPD; + ctrl2 |= SL811HS_CTL2MASK_DSWAP; + } + + /* start SOFs flowing, kickstarting with A registers */ + sl811->ctrl1 |= SL11H_CTL1MASK_SOF_ENA; + sl811_write(sl811, SL11H_SOFLOWREG, 0xe0); + sl811_write(sl811, SL811HS_CTLREG2, ctrl2); + + /* autoincrementing */ + sl811_write(sl811, SL811_EP_A(SL11H_BUFLNTHREG), 0); + writeb(SL_SOF, sl811->data_reg); + writeb(0, sl811->data_reg); + sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), + SL11H_HCTLMASK_ARM); + + /* khubd provides debounce delay */ + } else { + sl811->ctrl1 = 0; + } + sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); + + /* reenable irqs */ + sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable); + spin_unlock_irqrestore(&sl811->lock, flags); +} + +static int +sl811h_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct sl811 *sl811 = hcd_to_sl811(hcd); + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&sl811->lock, flags); + + switch (typeReq) { + case ClearHubFeature: + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (wIndex != 1 || wLength != 0) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + sl811->port1 &= USB_PORT_STAT_POWER; + sl811->ctrl1 = 0; + sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); + sl811->irq_enable = SL11H_INTMASK_INSRMV; + sl811_write(sl811, SL11H_IRQ_ENABLE, + sl811->irq_enable); + break; + case USB_PORT_FEAT_SUSPEND: + if (!(sl811->port1 & USB_PORT_STAT_SUSPEND)) + break; + + /* 20 msec of resume/K signaling, other irqs blocked */ + DBG("start resume...\n"); + sl811->irq_enable = 0; + sl811_write(sl811, SL11H_IRQ_ENABLE, + sl811->irq_enable); + sl811->ctrl1 |= SL11H_CTL1MASK_K; + sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); + + mod_timer(&sl811->timer, jiffies + + msecs_to_jiffies(20)); + break; + case USB_PORT_FEAT_POWER: + port_power(sl811, 0); + break; + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_RESET: + break; + default: + goto error; + } + sl811->port1 &= ~(1 << wValue); + break; + case GetHubDescriptor: + sl811h_hub_descriptor(sl811, (struct usb_hub_descriptor *) buf); + break; + case GetHubStatus: + put_unaligned_le32(0, buf); + break; + case GetPortStatus: + if (wIndex != 1) + goto error; + put_unaligned_le32(sl811->port1, buf); + +#ifndef VERBOSE + if (*(u16*)(buf+2)) /* only if wPortChange is interesting */ +#endif + DBG("GetPortStatus %08x\n", sl811->port1); + break; + case SetPortFeature: + if (wIndex != 1 || wLength != 0) + goto error; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if (sl811->port1 & USB_PORT_STAT_RESET) + goto error; + if (!(sl811->port1 & USB_PORT_STAT_ENABLE)) + goto error; + + DBG("suspend...\n"); + sl811->ctrl1 &= ~SL11H_CTL1MASK_SOF_ENA; + sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); + break; + case USB_PORT_FEAT_POWER: + port_power(sl811, 1); + break; + case USB_PORT_FEAT_RESET: + if (sl811->port1 & USB_PORT_STAT_SUSPEND) + goto error; + if (!(sl811->port1 & USB_PORT_STAT_POWER)) + break; + + /* 50 msec of reset/SE0 signaling, irqs blocked */ + sl811->irq_enable = 0; + sl811_write(sl811, SL11H_IRQ_ENABLE, + sl811->irq_enable); + sl811->ctrl1 = SL11H_CTL1MASK_SE0; + sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1); + sl811->port1 |= USB_PORT_STAT_RESET; + mod_timer(&sl811->timer, jiffies + + msecs_to_jiffies(50)); + break; + default: + goto error; + } + sl811->port1 |= 1 << wValue; + break; + + default: +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + + spin_unlock_irqrestore(&sl811->lock, flags); + return retval; +} + +#ifdef CONFIG_PM + +static int +sl811h_bus_suspend(struct usb_hcd *hcd) +{ + // SOFs off + DBG("%s\n", __func__); + return 0; +} + +static int +sl811h_bus_resume(struct usb_hcd *hcd) +{ + // SOFs on + DBG("%s\n", __func__); + return 0; +} + +#else + +#define sl811h_bus_suspend NULL +#define sl811h_bus_resume NULL + +#endif + + +/*-------------------------------------------------------------------------*/ + +#ifdef STUB_DEBUG_FILE + +static inline void create_debug_file(struct sl811 *sl811) { } +static inline void remove_debug_file(struct sl811 *sl811) { } + +#else + +#include +#include + +static void dump_irq(struct seq_file *s, char *label, u8 mask) +{ + seq_printf(s, "%s %02x%s%s%s%s%s%s\n", label, mask, + (mask & SL11H_INTMASK_DONE_A) ? " done_a" : "", + (mask & SL11H_INTMASK_DONE_B) ? " done_b" : "", + (mask & SL11H_INTMASK_SOFINTR) ? " sof" : "", + (mask & SL11H_INTMASK_INSRMV) ? " ins/rmv" : "", + (mask & SL11H_INTMASK_RD) ? " rd" : "", + (mask & SL11H_INTMASK_DP) ? " dp" : ""); +} + +static int proc_sl811h_show(struct seq_file *s, void *unused) +{ + struct sl811 *sl811 = s->private; + struct sl811h_ep *ep; + unsigned i; + + seq_printf(s, "%s\n%s version %s\nportstatus[1] = %08x\n", + sl811_to_hcd(sl811)->product_desc, + hcd_name, DRIVER_VERSION, + sl811->port1); + + seq_printf(s, "insert/remove: %ld\n", sl811->stat_insrmv); + seq_printf(s, "current session: done_a %ld done_b %ld " + "wake %ld sof %ld overrun %ld lost %ld\n\n", + sl811->stat_a, sl811->stat_b, + sl811->stat_wake, sl811->stat_sof, + sl811->stat_overrun, sl811->stat_lost); + + spin_lock_irq(&sl811->lock); + + if (sl811->ctrl1 & SL11H_CTL1MASK_SUSPEND) + seq_printf(s, "(suspended)\n\n"); + else { + u8 t = sl811_read(sl811, SL11H_CTLREG1); + + seq_printf(s, "ctrl1 %02x%s%s%s%s\n", t, + (t & SL11H_CTL1MASK_SOF_ENA) ? " sofgen" : "", + ({char *s; switch (t & SL11H_CTL1MASK_FORCE) { + case SL11H_CTL1MASK_NORMAL: s = ""; break; + case SL11H_CTL1MASK_SE0: s = " se0/reset"; break; + case SL11H_CTL1MASK_K: s = " k/resume"; break; + default: s = "j"; break; + }; s; }), + (t & SL11H_CTL1MASK_LSPD) ? " lowspeed" : "", + (t & SL11H_CTL1MASK_SUSPEND) ? " suspend" : ""); + + dump_irq(s, "irq_enable", + sl811_read(sl811, SL11H_IRQ_ENABLE)); + dump_irq(s, "irq_status", + sl811_read(sl811, SL11H_IRQ_STATUS)); + seq_printf(s, "frame clocks remaining: %d\n", + sl811_read(sl811, SL11H_SOFTMRREG) << 6); + } + + seq_printf(s, "A: qh%p ctl %02x sts %02x\n", sl811->active_a, + sl811_read(sl811, SL811_EP_A(SL11H_HOSTCTLREG)), + sl811_read(sl811, SL811_EP_A(SL11H_PKTSTATREG))); + seq_printf(s, "B: qh%p ctl %02x sts %02x\n", sl811->active_b, + sl811_read(sl811, SL811_EP_B(SL11H_HOSTCTLREG)), + sl811_read(sl811, SL811_EP_B(SL11H_PKTSTATREG))); + seq_printf(s, "\n"); + list_for_each_entry (ep, &sl811->async, schedule) { + struct urb *urb; + + seq_printf(s, "%s%sqh%p, ep%d%s, maxpacket %d" + " nak %d err %d\n", + (ep == sl811->active_a) ? "(A) " : "", + (ep == sl811->active_b) ? "(B) " : "", + ep, ep->epnum, + ({ char *s; switch (ep->nextpid) { + case USB_PID_IN: s = "in"; break; + case USB_PID_OUT: s = "out"; break; + case USB_PID_SETUP: s = "setup"; break; + case USB_PID_ACK: s = "status"; break; + default: s = "?"; break; + }; s;}), + ep->maxpacket, + ep->nak_count, ep->error_count); + list_for_each_entry (urb, &ep->hep->urb_list, urb_list) { + seq_printf(s, " urb%p, %d/%d\n", urb, + urb->actual_length, + urb->transfer_buffer_length); + } + } + if (!list_empty(&sl811->async)) + seq_printf(s, "\n"); + + seq_printf(s, "periodic size= %d\n", PERIODIC_SIZE); + + for (i = 0; i < PERIODIC_SIZE; i++) { + ep = sl811->periodic[i]; + if (!ep) + continue; + seq_printf(s, "%2d [%3d]:\n", i, sl811->load[i]); + + /* DUMB: prints shared entries multiple times */ + do { + seq_printf(s, + " %s%sqh%d/%p (%sdev%d ep%d%s max %d) " + "err %d\n", + (ep == sl811->active_a) ? "(A) " : "", + (ep == sl811->active_b) ? "(B) " : "", + ep->period, ep, + (ep->udev->speed == USB_SPEED_FULL) + ? "" : "ls ", + ep->udev->devnum, ep->epnum, + (ep->epnum == 0) ? "" + : ((ep->nextpid == USB_PID_IN) + ? "in" + : "out"), + ep->maxpacket, ep->error_count); + ep = ep->next; + } while (ep); + } + + spin_unlock_irq(&sl811->lock); + seq_printf(s, "\n"); + + return 0; +} + +static int proc_sl811h_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_sl811h_show, PDE_DATA(inode)); +} + +static const struct file_operations proc_ops = { + .open = proc_sl811h_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* expect just one sl811 per system */ +static const char proc_filename[] = "driver/sl811h"; + +static void create_debug_file(struct sl811 *sl811) +{ + sl811->pde = proc_create_data(proc_filename, 0, NULL, &proc_ops, sl811); +} + +static void remove_debug_file(struct sl811 *sl811) +{ + if (sl811->pde) + remove_proc_entry(proc_filename, NULL); +} + +#endif + +/*-------------------------------------------------------------------------*/ + +static void +sl811h_stop(struct usb_hcd *hcd) +{ + struct sl811 *sl811 = hcd_to_sl811(hcd); + unsigned long flags; + + del_timer_sync(&hcd->rh_timer); + + spin_lock_irqsave(&sl811->lock, flags); + port_power(sl811, 0); + spin_unlock_irqrestore(&sl811->lock, flags); +} + +static int +sl811h_start(struct usb_hcd *hcd) +{ + struct sl811 *sl811 = hcd_to_sl811(hcd); + + /* chip has been reset, VBUS power is off */ + hcd->state = HC_STATE_RUNNING; + + if (sl811->board) { + if (!device_can_wakeup(hcd->self.controller)) + device_init_wakeup(hcd->self.controller, + sl811->board->can_wakeup); + hcd->power_budget = sl811->board->power * 2; + } + + /* enable power and interrupts */ + port_power(sl811, 1); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct hc_driver sl811h_hc_driver = { + .description = hcd_name, + .hcd_priv_size = sizeof(struct sl811), + + /* + * generic hardware linkage + */ + .irq = sl811h_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* Basic lifecycle operations */ + .start = sl811h_start, + .stop = sl811h_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = sl811h_urb_enqueue, + .urb_dequeue = sl811h_urb_dequeue, + .endpoint_disable = sl811h_endpoint_disable, + + /* + * periodic schedule support + */ + .get_frame_number = sl811h_get_frame, + + /* + * root hub support + */ + .hub_status_data = sl811h_hub_status_data, + .hub_control = sl811h_hub_control, + .bus_suspend = sl811h_bus_suspend, + .bus_resume = sl811h_bus_resume, +}; + +/*-------------------------------------------------------------------------*/ + +static int +sl811h_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct sl811 *sl811 = hcd_to_sl811(hcd); + struct resource *res; + + remove_debug_file(sl811); + usb_remove_hcd(hcd); + + /* some platforms may use IORESOURCE_IO */ + res = platform_get_resource(dev, IORESOURCE_MEM, 1); + if (res) + iounmap(sl811->data_reg); + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (res) + iounmap(sl811->addr_reg); + + usb_put_hcd(hcd); + return 0; +} + +static int +sl811h_probe(struct platform_device *dev) +{ + struct usb_hcd *hcd; + struct sl811 *sl811; + struct resource *addr, *data, *ires; + int irq; + void __iomem *addr_reg; + void __iomem *data_reg; + int retval; + u8 tmp, ioaddr = 0; + unsigned long irqflags; + + if (usb_disabled()) + return -ENODEV; + + /* basic sanity checks first. board-specific init logic should + * have initialized these three resources and probably board + * specific platform_data. we don't probe for IRQs, and do only + * minimal sanity checking. + */ + ires = platform_get_resource(dev, IORESOURCE_IRQ, 0); + if (dev->num_resources < 3 || !ires) + return -ENODEV; + + irq = ires->start; + irqflags = ires->flags & IRQF_TRIGGER_MASK; + + /* refuse to confuse usbcore */ + if (dev->dev.dma_mask) { + DBG("no we won't dma\n"); + return -EINVAL; + } + + /* the chip may be wired for either kind of addressing */ + addr = platform_get_resource(dev, IORESOURCE_MEM, 0); + data = platform_get_resource(dev, IORESOURCE_MEM, 1); + retval = -EBUSY; + if (!addr || !data) { + addr = platform_get_resource(dev, IORESOURCE_IO, 0); + data = platform_get_resource(dev, IORESOURCE_IO, 1); + if (!addr || !data) + return -ENODEV; + ioaddr = 1; + /* + * NOTE: 64-bit resource->start is getting truncated + * to avoid compiler warning, assuming that ->start + * is always 32-bit for this case + */ + addr_reg = (void __iomem *) (unsigned long) addr->start; + data_reg = (void __iomem *) (unsigned long) data->start; + } else { + addr_reg = ioremap(addr->start, 1); + if (addr_reg == NULL) { + retval = -ENOMEM; + goto err2; + } + + data_reg = ioremap(data->start, 1); + if (data_reg == NULL) { + retval = -ENOMEM; + goto err4; + } + } + + /* allocate and initialize hcd */ + hcd = usb_create_hcd(&sl811h_hc_driver, &dev->dev, dev_name(&dev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto err5; + } + hcd->rsrc_start = addr->start; + sl811 = hcd_to_sl811(hcd); + + spin_lock_init(&sl811->lock); + INIT_LIST_HEAD(&sl811->async); + sl811->board = dev->dev.platform_data; + init_timer(&sl811->timer); + sl811->timer.function = sl811h_timer; + sl811->timer.data = (unsigned long) sl811; + sl811->addr_reg = addr_reg; + sl811->data_reg = data_reg; + + spin_lock_irq(&sl811->lock); + port_power(sl811, 0); + spin_unlock_irq(&sl811->lock); + msleep(200); + + tmp = sl811_read(sl811, SL11H_HWREVREG); + switch (tmp >> 4) { + case 1: + hcd->product_desc = "SL811HS v1.2"; + break; + case 2: + hcd->product_desc = "SL811HS v1.5"; + break; + default: + /* reject case 0, SL11S is less functional */ + DBG("chiprev %02x\n", tmp); + retval = -ENXIO; + goto err6; + } + + /* The chip's IRQ is level triggered, active high. A requirement + * for platform device setup is to cope with things like signal + * inverters (e.g. CF is active low) or working only with edge + * triggers (e.g. most ARM CPUs). Initial driver stress testing + * was on a system with single edge triggering, so most sorts of + * triggering arrangement should work. + * + * Use resource IRQ flags if set by platform device setup. + */ + irqflags |= IRQF_SHARED; + retval = usb_add_hcd(hcd, irq, irqflags); + if (retval != 0) + goto err6; + + create_debug_file(sl811); + return retval; + + err6: + usb_put_hcd(hcd); + err5: + if (!ioaddr) + iounmap(data_reg); + err4: + if (!ioaddr) + iounmap(addr_reg); + err2: + DBG("init error, %d\n", retval); + return retval; +} + +#ifdef CONFIG_PM + +/* for this device there's no useful distinction between the controller + * and its root hub, except that the root hub only gets direct PM calls + * when CONFIG_PM_RUNTIME is enabled. + */ + +static int +sl811h_suspend(struct platform_device *dev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct sl811 *sl811 = hcd_to_sl811(hcd); + int retval = 0; + + switch (state.event) { + case PM_EVENT_FREEZE: + retval = sl811h_bus_suspend(hcd); + break; + case PM_EVENT_SUSPEND: + case PM_EVENT_HIBERNATE: + case PM_EVENT_PRETHAW: /* explicitly discard hw state */ + port_power(sl811, 0); + break; + } + return retval; +} + +static int +sl811h_resume(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct sl811 *sl811 = hcd_to_sl811(hcd); + + /* with no "check to see if VBUS is still powered" board hook, + * let's assume it'd only be powered to enable remote wakeup. + */ + if (!sl811->port1 || !device_can_wakeup(&hcd->self.root_hub->dev)) { + sl811->port1 = 0; + port_power(sl811, 1); + usb_root_hub_lost_power(hcd->self.root_hub); + return 0; + } + + return sl811h_bus_resume(hcd); +} + +#else + +#define sl811h_suspend NULL +#define sl811h_resume NULL + +#endif + + +/* this driver is exported so sl811_cs can depend on it */ +struct platform_driver sl811h_driver = { + .probe = sl811h_probe, + .remove = sl811h_remove, + + .suspend = sl811h_suspend, + .resume = sl811h_resume, + .driver = { + .name = (char *) hcd_name, + .owner = THIS_MODULE, + }, +}; +EXPORT_SYMBOL(sl811h_driver); + +module_platform_driver(sl811h_driver); diff --git a/drivers/usb/host/sl811.h b/drivers/usb/host/sl811.h new file mode 100644 index 00000000..b6b8c1f2 --- /dev/null +++ b/drivers/usb/host/sl811.h @@ -0,0 +1,266 @@ +/* + * SL811HS register declarations and HCD data structures + * + * Copyright (C) 2004 Psion Teklogix + * Copyright (C) 2004 David Brownell + * Copyright (C) 2001 Cypress Semiconductor Inc. + */ + +/* + * SL811HS has transfer registers, and control registers. In host/master + * mode one set of registers is used; in peripheral/slave mode, another. + * - SL11H only has some "A" transfer registers from 0x00-0x04 + * - SL811HS also has "B" registers from 0x08-0x0c + * - SL811S (or HS in slave mode) has four A+B sets, at 00, 10, 20, 30 + */ + +#define SL811_EP_A(base) ((base) + 0) +#define SL811_EP_B(base) ((base) + 8) + +#define SL811_HOST_BUF 0x00 +#define SL811_PERIPH_EP0 0x00 +#define SL811_PERIPH_EP1 0x10 +#define SL811_PERIPH_EP2 0x20 +#define SL811_PERIPH_EP3 0x30 + + +/* TRANSFER REGISTERS: host and peripheral sides are similar + * except for the control models (master vs slave). + */ +#define SL11H_HOSTCTLREG 0 +# define SL11H_HCTLMASK_ARM 0x01 +# define SL11H_HCTLMASK_ENABLE 0x02 +# define SL11H_HCTLMASK_IN 0x00 +# define SL11H_HCTLMASK_OUT 0x04 +# define SL11H_HCTLMASK_ISOCH 0x10 +# define SL11H_HCTLMASK_AFTERSOF 0x20 +# define SL11H_HCTLMASK_TOGGLE 0x40 +# define SL11H_HCTLMASK_PREAMBLE 0x80 +#define SL11H_BUFADDRREG 1 +#define SL11H_BUFLNTHREG 2 +#define SL11H_PKTSTATREG 3 /* read */ +# define SL11H_STATMASK_ACK 0x01 +# define SL11H_STATMASK_ERROR 0x02 +# define SL11H_STATMASK_TMOUT 0x04 +# define SL11H_STATMASK_SEQ 0x08 +# define SL11H_STATMASK_SETUP 0x10 +# define SL11H_STATMASK_OVF 0x20 +# define SL11H_STATMASK_NAK 0x40 +# define SL11H_STATMASK_STALL 0x80 +#define SL11H_PIDEPREG 3 /* write */ +# define SL_SETUP 0xd0 +# define SL_IN 0x90 +# define SL_OUT 0x10 +# define SL_SOF 0x50 +# define SL_PREAMBLE 0xc0 +# define SL_NAK 0xa0 +# define SL_STALL 0xe0 +# define SL_DATA0 0x30 +# define SL_DATA1 0xb0 +#define SL11H_XFERCNTREG 4 /* read */ +#define SL11H_DEVADDRREG 4 /* write */ + + +/* CONTROL REGISTERS: host and peripheral are very different. + */ +#define SL11H_CTLREG1 5 +# define SL11H_CTL1MASK_SOF_ENA 0x01 +# define SL11H_CTL1MASK_FORCE 0x18 +# define SL11H_CTL1MASK_NORMAL 0x00 +# define SL11H_CTL1MASK_SE0 0x08 /* reset */ +# define SL11H_CTL1MASK_J 0x10 +# define SL11H_CTL1MASK_K 0x18 /* resume */ +# define SL11H_CTL1MASK_LSPD 0x20 +# define SL11H_CTL1MASK_SUSPEND 0x40 +#define SL11H_IRQ_ENABLE 6 +# define SL11H_INTMASK_DONE_A 0x01 +# define SL11H_INTMASK_DONE_B 0x02 +# define SL11H_INTMASK_SOFINTR 0x10 +# define SL11H_INTMASK_INSRMV 0x20 /* to/from SE0 */ +# define SL11H_INTMASK_RD 0x40 +# define SL11H_INTMASK_DP 0x80 /* only in INTSTATREG */ +#define SL11S_ADDRESS 7 + +/* 0x08-0x0c are for the B buffer (not in SL11) */ + +#define SL11H_IRQ_STATUS 0x0D /* write to ack */ +#define SL11H_HWREVREG 0x0E /* read */ +# define SL11H_HWRMASK_HWREV 0xF0 +#define SL11H_SOFLOWREG 0x0E /* write */ +#define SL11H_SOFTMRREG 0x0F /* read */ + +/* a write to this register enables SL811HS features. + * HOST flag presumably overrides the chip input signal? + */ +#define SL811HS_CTLREG2 0x0F +# define SL811HS_CTL2MASK_SOF_MASK 0x3F +# define SL811HS_CTL2MASK_DSWAP 0x40 +# define SL811HS_CTL2MASK_HOST 0x80 + +#define SL811HS_CTL2_INIT (SL811HS_CTL2MASK_HOST | 0x2e) + + +/* DATA BUFFERS: registers from 0x10..0xff are for data buffers; + * that's 240 bytes, which we'll split evenly between A and B sides. + * Only ISO can use more than 64 bytes per packet. + * (The SL11S has 0x40..0xff for buffers.) + */ +#define H_MAXPACKET 120 /* bytes in A or B fifos */ + +#define SL11H_DATA_START 0x10 +#define SL811HS_PACKET_BUF(is_a) ((is_a) \ + ? SL11H_DATA_START \ + : (SL11H_DATA_START + H_MAXPACKET)) + +/*-------------------------------------------------------------------------*/ + +#define LOG2_PERIODIC_SIZE 5 /* arbitrary; this matches OHCI */ +#define PERIODIC_SIZE (1 << LOG2_PERIODIC_SIZE) + +struct sl811 { + spinlock_t lock; + void __iomem *addr_reg; + void __iomem *data_reg; + struct sl811_platform_data *board; + struct proc_dir_entry *pde; + + unsigned long stat_insrmv; + unsigned long stat_wake; + unsigned long stat_sof; + unsigned long stat_a; + unsigned long stat_b; + unsigned long stat_lost; + unsigned long stat_overrun; + + /* sw model */ + struct timer_list timer; + struct sl811h_ep *next_periodic; + struct sl811h_ep *next_async; + + struct sl811h_ep *active_a; + unsigned long jiffies_a; + struct sl811h_ep *active_b; + unsigned long jiffies_b; + + u32 port1; + u8 ctrl1, ctrl2, irq_enable; + u16 frame; + + /* async schedule: control, bulk */ + struct list_head async; + + /* periodic schedule: interrupt, iso */ + u16 load[PERIODIC_SIZE]; + struct sl811h_ep *periodic[PERIODIC_SIZE]; + unsigned periodic_count; +}; + +static inline struct sl811 *hcd_to_sl811(struct usb_hcd *hcd) +{ + return (struct sl811 *) (hcd->hcd_priv); +} + +static inline struct usb_hcd *sl811_to_hcd(struct sl811 *sl811) +{ + return container_of((void *) sl811, struct usb_hcd, hcd_priv); +} + +struct sl811h_ep { + struct usb_host_endpoint *hep; + struct usb_device *udev; + + u8 defctrl; + u8 maxpacket; + u8 epnum; + u8 nextpid; + + u16 error_count; + u16 nak_count; + u16 length; /* of current packet */ + + /* periodic schedule */ + u16 period; + u16 branch; + u16 load; + struct sl811h_ep *next; + + /* async schedule */ + struct list_head schedule; +}; + +/*-------------------------------------------------------------------------*/ + +/* These register utilities should work for the SL811S register API too + * NOTE: caller must hold sl811->lock. + */ + +static inline u8 sl811_read(struct sl811 *sl811, int reg) +{ + writeb(reg, sl811->addr_reg); + return readb(sl811->data_reg); +} + +static inline void sl811_write(struct sl811 *sl811, int reg, u8 val) +{ + writeb(reg, sl811->addr_reg); + writeb(val, sl811->data_reg); +} + +static inline void +sl811_write_buf(struct sl811 *sl811, int addr, const void *buf, size_t count) +{ + const u8 *data; + void __iomem *data_reg; + + if (!count) + return; + writeb(addr, sl811->addr_reg); + + data = buf; + data_reg = sl811->data_reg; + do { + writeb(*data++, data_reg); + } while (--count); +} + +static inline void +sl811_read_buf(struct sl811 *sl811, int addr, void *buf, size_t count) +{ + u8 *data; + void __iomem *data_reg; + + if (!count) + return; + writeb(addr, sl811->addr_reg); + + data = buf; + data_reg = sl811->data_reg; + do { + *data++ = readb(data_reg); + } while (--count); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +#define DBG(stuff...) printk(KERN_DEBUG "sl811: " stuff) +#else +#define DBG(stuff...) do{}while(0) +#endif + +#ifdef VERBOSE +# define VDBG DBG +#else +# define VDBG(stuff...) do{}while(0) +#endif + +#ifdef PACKET_TRACE +# define PACKET VDBG +#else +# define PACKET(stuff...) do{}while(0) +#endif + +#define ERR(stuff...) printk(KERN_ERR "sl811: " stuff) +#define WARNING(stuff...) printk(KERN_WARNING "sl811: " stuff) +#define INFO(stuff...) printk(KERN_INFO "sl811: " stuff) + diff --git a/drivers/usb/host/sl811_cs.c b/drivers/usb/host/sl811_cs.c new file mode 100644 index 00000000..469564e5 --- /dev/null +++ b/drivers/usb/host/sl811_cs.c @@ -0,0 +1,203 @@ +/* + * PCMCIA driver for SL811HS (as found in REX-CFU1U) + * Filename: sl811_cs.c + * Author: Yukio Yamamoto + * + * Port to sl811-hcd and 2.6.x by + * Botond Botyanszki + * Simon Pickering + * + * Last update: 2005-05-12 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +MODULE_AUTHOR("Botond Botyanszki"); +MODULE_DESCRIPTION("REX-CFU1U PCMCIA driver for 2.6"); +MODULE_LICENSE("GPL"); + + +/*====================================================================*/ +/* MACROS */ +/*====================================================================*/ + +#define INFO(args...) printk(KERN_INFO "sl811_cs: " args) + +/*====================================================================*/ +/* VARIABLES */ +/*====================================================================*/ + +typedef struct local_info_t { + struct pcmcia_device *p_dev; +} local_info_t; + +static void sl811_cs_release(struct pcmcia_device * link); + +/*====================================================================*/ + +static void release_platform_dev(struct device * dev) +{ + dev_dbg(dev, "sl811_cs platform_dev release\n"); + dev->parent = NULL; +} + +static struct sl811_platform_data platform_data = { + .potpg = 100, + .power = 50, /* == 100mA */ + // .reset = ... FIXME: invoke CF reset on the card +}; + +static struct resource resources[] = { + [0] = { + .flags = IORESOURCE_IRQ, + }, + [1] = { + // .name = "address", + .flags = IORESOURCE_IO, + }, + [2] = { + // .name = "data", + .flags = IORESOURCE_IO, + }, +}; + +extern struct platform_driver sl811h_driver; + +static struct platform_device platform_dev = { + .id = -1, + .dev = { + .platform_data = &platform_data, + .release = release_platform_dev, + }, + .resource = resources, + .num_resources = ARRAY_SIZE(resources), +}; + +static int sl811_hc_init(struct device *parent, resource_size_t base_addr, + int irq) +{ + if (platform_dev.dev.parent) + return -EBUSY; + platform_dev.dev.parent = parent; + + /* finish seting up the platform device */ + resources[0].start = irq; + + resources[1].start = base_addr; + resources[1].end = base_addr; + + resources[2].start = base_addr + 1; + resources[2].end = base_addr + 1; + + /* The driver core will probe for us. We know sl811-hcd has been + * initialized already because of the link order dependency created + * by referencing "sl811h_driver". + */ + platform_dev.name = sl811h_driver.driver.name; + return platform_device_register(&platform_dev); +} + +/*====================================================================*/ + +static void sl811_cs_detach(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "sl811_cs_detach\n"); + + sl811_cs_release(link); + + /* This points to the parent local_info_t struct */ + kfree(link->priv); +} + +static void sl811_cs_release(struct pcmcia_device * link) +{ + dev_dbg(&link->dev, "sl811_cs_release\n"); + + pcmcia_disable_device(link); + platform_device_unregister(&platform_dev); +} + +static int sl811_cs_config_check(struct pcmcia_device *p_dev, void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +} + + +static int sl811_cs_config(struct pcmcia_device *link) +{ + struct device *parent = &link->dev; + int ret; + + dev_dbg(&link->dev, "sl811_cs_config\n"); + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP | + CONF_AUTO_CHECK_VCC | CONF_AUTO_SET_IO; + + if (pcmcia_loop_config(link, sl811_cs_config_check, NULL)) + goto failed; + + /* require an IRQ and two registers */ + if (resource_size(link->resource[0]) < 2) + goto failed; + + if (!link->irq) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + if (sl811_hc_init(parent, link->resource[0]->start, link->irq) + < 0) { +failed: + printk(KERN_WARNING "sl811_cs_config failed\n"); + sl811_cs_release(link); + return -ENODEV; + } + return 0; +} + +static int sl811_cs_probe(struct pcmcia_device *link) +{ + local_info_t *local; + + local = kzalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) + return -ENOMEM; + local->p_dev = link; + link->priv = local; + + return sl811_cs_config(link); +} + +static const struct pcmcia_device_id sl811_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0xc015, 0x0001), /* RATOC USB HOST CF+ Card */ + PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, sl811_ids); + +static struct pcmcia_driver sl811_cs_driver = { + .owner = THIS_MODULE, + .name = "sl811_cs", + .probe = sl811_cs_probe, + .remove = sl811_cs_detach, + .id_table = sl811_ids, +}; +module_pcmcia_driver(sl811_cs_driver); diff --git a/drivers/usb/host/ssb-hcd.c b/drivers/usb/host/ssb-hcd.c new file mode 100644 index 00000000..74af2c62 --- /dev/null +++ b/drivers/usb/host/ssb-hcd.c @@ -0,0 +1,279 @@ +/* + * Sonics Silicon Backplane + * Broadcom USB-core driver (SSB bus glue) + * + * Copyright 2011-2012 Hauke Mehrtens + * + * Based on ssb-ohci driver + * Copyright 2007 Michael Buesch + * + * Derived from the OHCI-PCI driver + * Copyright 1999 Roman Weissgaerber + * Copyright 2000-2002 David Brownell + * Copyright 1999 Linus Torvalds + * Copyright 1999 Gregory P. Smith + * + * Derived from the USBcore related parts of Broadcom-SB + * Copyright 2005-2011 Broadcom Corporation + * + * Licensed under the GNU/GPL. See COPYING for details. + */ +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Hauke Mehrtens"); +MODULE_DESCRIPTION("Common USB driver for SSB Bus"); +MODULE_LICENSE("GPL"); + +#define SSB_HCD_TMSLOW_HOSTMODE (1 << 29) + +struct ssb_hcd_device { + struct platform_device *ehci_dev; + struct platform_device *ohci_dev; + + u32 enable_flags; +}; + +static void ssb_hcd_5354wa(struct ssb_device *dev) +{ +#ifdef CONFIG_SSB_DRIVER_MIPS + /* Work around for 5354 failures */ + if (dev->id.revision == 2 && dev->bus->chip_id == 0x5354) { + /* Change syn01 reg */ + ssb_write32(dev, 0x894, 0x00fe00fe); + + /* Change syn03 reg */ + ssb_write32(dev, 0x89c, ssb_read32(dev, 0x89c) | 0x1); + } +#endif +} + +static void ssb_hcd_usb20wa(struct ssb_device *dev) +{ + if (dev->id.coreid == SSB_DEV_USB20_HOST) { + /* + * USB 2.0 special considerations: + * + * In addition to the standard SSB reset sequence, the Host + * Control Register must be programmed to bring the USB core + * and various phy components out of reset. + */ + ssb_write32(dev, 0x200, 0x7ff); + + /* Change Flush control reg */ + ssb_write32(dev, 0x400, ssb_read32(dev, 0x400) & ~8); + ssb_read32(dev, 0x400); + + /* Change Shim control reg */ + ssb_write32(dev, 0x304, ssb_read32(dev, 0x304) & ~0x100); + ssb_read32(dev, 0x304); + + udelay(1); + + ssb_hcd_5354wa(dev); + } +} + +/* based on arch/mips/brcm-boards/bcm947xx/pcibios.c */ +static u32 ssb_hcd_init_chip(struct ssb_device *dev) +{ + u32 flags = 0; + + if (dev->id.coreid == SSB_DEV_USB11_HOSTDEV) + /* Put the device into host-mode. */ + flags |= SSB_HCD_TMSLOW_HOSTMODE; + + ssb_device_enable(dev, flags); + + ssb_hcd_usb20wa(dev); + + return flags; +} + +static const struct usb_ehci_pdata ehci_pdata = { +}; + +static const struct usb_ohci_pdata ohci_pdata = { +}; + +static struct platform_device *ssb_hcd_create_pdev(struct ssb_device *dev, bool ohci, u32 addr, u32 len) +{ + struct platform_device *hci_dev; + struct resource hci_res[2]; + int ret = -ENOMEM; + + memset(hci_res, 0, sizeof(hci_res)); + + hci_res[0].start = addr; + hci_res[0].end = hci_res[0].start + len - 1; + hci_res[0].flags = IORESOURCE_MEM; + + hci_res[1].start = dev->irq; + hci_res[1].flags = IORESOURCE_IRQ; + + hci_dev = platform_device_alloc(ohci ? "ohci-platform" : + "ehci-platform" , 0); + if (!hci_dev) + return NULL; + + hci_dev->dev.parent = dev->dev; + hci_dev->dev.dma_mask = &hci_dev->dev.coherent_dma_mask; + + ret = platform_device_add_resources(hci_dev, hci_res, + ARRAY_SIZE(hci_res)); + if (ret) + goto err_alloc; + if (ohci) + ret = platform_device_add_data(hci_dev, &ohci_pdata, + sizeof(ohci_pdata)); + else + ret = platform_device_add_data(hci_dev, &ehci_pdata, + sizeof(ehci_pdata)); + if (ret) + goto err_alloc; + ret = platform_device_add(hci_dev); + if (ret) + goto err_alloc; + + return hci_dev; + +err_alloc: + platform_device_put(hci_dev); + return ERR_PTR(ret); +} + +static int ssb_hcd_probe(struct ssb_device *dev, + const struct ssb_device_id *id) +{ + int err, tmp; + int start, len; + u16 chipid_top; + u16 coreid = dev->id.coreid; + struct ssb_hcd_device *usb_dev; + + /* USBcores are only connected on embedded devices. */ + chipid_top = (dev->bus->chip_id & 0xFF00); + if (chipid_top != 0x4700 && chipid_top != 0x5300) + return -ENODEV; + + /* TODO: Probably need checks here; is the core connected? */ + + if (dma_set_mask(dev->dma_dev, DMA_BIT_MASK(32)) || + dma_set_coherent_mask(dev->dma_dev, DMA_BIT_MASK(32))) + return -EOPNOTSUPP; + + usb_dev = kzalloc(sizeof(struct ssb_hcd_device), GFP_KERNEL); + if (!usb_dev) + return -ENOMEM; + + /* We currently always attach SSB_DEV_USB11_HOSTDEV + * as HOST OHCI. If we want to attach it as Client device, + * we must branch here and call into the (yet to + * be written) Client mode driver. Same for remove(). */ + usb_dev->enable_flags = ssb_hcd_init_chip(dev); + + tmp = ssb_read32(dev, SSB_ADMATCH0); + + start = ssb_admatch_base(tmp); + len = (coreid == SSB_DEV_USB20_HOST) ? 0x800 : ssb_admatch_size(tmp); + usb_dev->ohci_dev = ssb_hcd_create_pdev(dev, true, start, len); + if (IS_ERR(usb_dev->ohci_dev)) { + err = PTR_ERR(usb_dev->ohci_dev); + goto err_free_usb_dev; + } + + if (coreid == SSB_DEV_USB20_HOST) { + start = ssb_admatch_base(tmp) + 0x800; /* ehci core offset */ + usb_dev->ehci_dev = ssb_hcd_create_pdev(dev, false, start, len); + if (IS_ERR(usb_dev->ehci_dev)) { + err = PTR_ERR(usb_dev->ehci_dev); + goto err_unregister_ohci_dev; + } + } + + ssb_set_drvdata(dev, usb_dev); + return 0; + +err_unregister_ohci_dev: + platform_device_unregister(usb_dev->ohci_dev); +err_free_usb_dev: + kfree(usb_dev); + return err; +} + +static void ssb_hcd_remove(struct ssb_device *dev) +{ + struct ssb_hcd_device *usb_dev = ssb_get_drvdata(dev); + struct platform_device *ohci_dev = usb_dev->ohci_dev; + struct platform_device *ehci_dev = usb_dev->ehci_dev; + + if (ohci_dev) + platform_device_unregister(ohci_dev); + if (ehci_dev) + platform_device_unregister(ehci_dev); + + ssb_device_disable(dev, 0); +} + +static void ssb_hcd_shutdown(struct ssb_device *dev) +{ + ssb_device_disable(dev, 0); +} + +#ifdef CONFIG_PM + +static int ssb_hcd_suspend(struct ssb_device *dev, pm_message_t state) +{ + ssb_device_disable(dev, 0); + + return 0; +} + +static int ssb_hcd_resume(struct ssb_device *dev) +{ + struct ssb_hcd_device *usb_dev = ssb_get_drvdata(dev); + + ssb_device_enable(dev, usb_dev->enable_flags); + + return 0; +} + +#else /* !CONFIG_PM */ +#define ssb_hcd_suspend NULL +#define ssb_hcd_resume NULL +#endif /* CONFIG_PM */ + +static const struct ssb_device_id ssb_hcd_table[] = { + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB11_HOSTDEV, SSB_ANY_REV), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB11_HOST, SSB_ANY_REV), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB20_HOST, SSB_ANY_REV), + SSB_DEVTABLE_END +}; +MODULE_DEVICE_TABLE(ssb, ssb_hcd_table); + +static struct ssb_driver ssb_hcd_driver = { + .name = KBUILD_MODNAME, + .id_table = ssb_hcd_table, + .probe = ssb_hcd_probe, + .remove = ssb_hcd_remove, + .shutdown = ssb_hcd_shutdown, + .suspend = ssb_hcd_suspend, + .resume = ssb_hcd_resume, +}; + +static int __init ssb_hcd_init(void) +{ + return ssb_driver_register(&ssb_hcd_driver); +} +module_init(ssb_hcd_init); + +static void __exit ssb_hcd_exit(void) +{ + ssb_driver_unregister(&ssb_hcd_driver); +} +module_exit(ssb_hcd_exit); diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c new file mode 100644 index 00000000..5c124bf5 --- /dev/null +++ b/drivers/usb/host/u132-hcd.c @@ -0,0 +1,3261 @@ +/* +* Host Controller Driver for the Elan Digital Systems U132 adapter +* +* Copyright(C) 2006 Elan Digital Systems Limited +* http://www.elandigitalsystems.com +* +* Author and Maintainer - Tony Olech - Elan Digital Systems +* tony.olech@elandigitalsystems.com +* +* This program is free software;you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation, version 2. +* +* +* This driver was written by Tony Olech(tony.olech@elandigitalsystems.com) +* based on various USB host drivers in the 2.6.15 linux kernel +* with constant reference to the 3rd Edition of Linux Device Drivers +* published by O'Reilly +* +* The U132 adapter is a USB to CardBus adapter specifically designed +* for PC cards that contain an OHCI host controller. Typical PC cards +* are the Orange Mobile 3G Option GlobeTrotter Fusion card. +* +* The U132 adapter will *NOT *work with PC cards that do not contain +* an OHCI controller. A simple way to test whether a PC card has an +* OHCI controller as an interface is to insert the PC card directly +* into a laptop(or desktop) with a CardBus slot and if "lspci" shows +* a new USB controller and "lsusb -v" shows a new OHCI Host Controller +* then there is a good chance that the U132 adapter will support the +* PC card.(you also need the specific client driver for the PC card) +* +* Please inform the Author and Maintainer about any PC cards that +* contain OHCI Host Controller and work when directly connected to +* an embedded CardBus slot but do not work when they are connected +* via an ELAN U132 adapter. +* +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* FIXME ohci.h is ONLY for internal use by the OHCI driver. + * If you're going to try stuff like this, you need to split + * out shareable stuff (register declarations?) into its own + * file, maybe name + */ + +#include "ohci.h" +#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR +#define OHCI_INTR_INIT (OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_RD | \ + OHCI_INTR_WDH) +MODULE_AUTHOR("Tony Olech - Elan Digital Systems Limited"); +MODULE_DESCRIPTION("U132 USB Host Controller Driver"); +MODULE_LICENSE("GPL"); +#define INT_MODULE_PARM(n, v) static int n = v;module_param(n, int, 0444) +INT_MODULE_PARM(testing, 0); +/* Some boards misreport power switching/overcurrent*/ +static bool distrust_firmware = 1; +module_param(distrust_firmware, bool, 0); +MODULE_PARM_DESC(distrust_firmware, "true to distrust firmware power/overcurren" + "t setup"); +static DECLARE_WAIT_QUEUE_HEAD(u132_hcd_wait); +/* +* u132_module_lock exists to protect access to global variables +* +*/ +static struct mutex u132_module_lock; +static int u132_exiting; +static int u132_instances; +static struct list_head u132_static_list; +/* +* end of the global variables protected by u132_module_lock +*/ +static struct workqueue_struct *workqueue; +#define MAX_U132_PORTS 7 +#define MAX_U132_ADDRS 128 +#define MAX_U132_UDEVS 4 +#define MAX_U132_ENDPS 100 +#define MAX_U132_RINGS 4 +static const char *cc_to_text[16] = { + "No Error ", + "CRC Error ", + "Bit Stuff ", + "Data Togg ", + "Stall ", + "DevNotResp ", + "PIDCheck ", + "UnExpPID ", + "DataOver ", + "DataUnder ", + "(for hw) ", + "(for hw) ", + "BufferOver ", + "BuffUnder ", + "(for HCD) ", + "(for HCD) " +}; +struct u132_port { + struct u132 *u132; + int reset; + int enable; + int power; + int Status; +}; +struct u132_addr { + u8 address; +}; +struct u132_udev { + struct kref kref; + struct usb_device *usb_device; + u8 enumeration; + u8 udev_number; + u8 usb_addr; + u8 portnumber; + u8 endp_number_in[16]; + u8 endp_number_out[16]; +}; +#define ENDP_QUEUE_SHIFT 3 +#define ENDP_QUEUE_SIZE (1<platform_dev, offsetof(struct \ + ohci_regs, member), 0, data); +#define u132_write_pcimem(u132, member, data) \ + usb_ftdi_elan_write_pcimem(u132->platform_dev, offsetof(struct \ + ohci_regs, member), 0, data); +static inline struct u132 *udev_to_u132(struct u132_udev *udev) +{ + u8 udev_number = udev->udev_number; + return container_of(udev, struct u132, udev[udev_number]); +} + +static inline struct u132 *hcd_to_u132(struct usb_hcd *hcd) +{ + return (struct u132 *)(hcd->hcd_priv); +} + +static inline struct usb_hcd *u132_to_hcd(struct u132 *u132) +{ + return container_of((void *)u132, struct usb_hcd, hcd_priv); +} + +static inline void u132_disable(struct u132 *u132) +{ + u132_to_hcd(u132)->state = HC_STATE_HALT; +} + + +#define kref_to_u132(d) container_of(d, struct u132, kref) +#define kref_to_u132_endp(d) container_of(d, struct u132_endp, kref) +#define kref_to_u132_udev(d) container_of(d, struct u132_udev, kref) +#include "../misc/usb_u132.h" +static const char hcd_name[] = "u132_hcd"; +#define PORT_C_MASK ((USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE | \ + USB_PORT_STAT_C_SUSPEND | USB_PORT_STAT_C_OVERCURRENT | \ + USB_PORT_STAT_C_RESET) << 16) +static void u132_hcd_delete(struct kref *kref) +{ + struct u132 *u132 = kref_to_u132(kref); + struct platform_device *pdev = u132->platform_dev; + struct usb_hcd *hcd = u132_to_hcd(u132); + u132->going += 1; + mutex_lock(&u132_module_lock); + list_del_init(&u132->u132_list); + u132_instances -= 1; + mutex_unlock(&u132_module_lock); + dev_warn(&u132->platform_dev->dev, "FREEING the hcd=%p and thus the u13" + "2=%p going=%d pdev=%p\n", hcd, u132, u132->going, pdev); + usb_put_hcd(hcd); +} + +static inline void u132_u132_put_kref(struct u132 *u132) +{ + kref_put(&u132->kref, u132_hcd_delete); +} + +static inline void u132_u132_init_kref(struct u132 *u132) +{ + kref_init(&u132->kref); +} + +static void u132_udev_delete(struct kref *kref) +{ + struct u132_udev *udev = kref_to_u132_udev(kref); + udev->udev_number = 0; + udev->usb_device = NULL; + udev->usb_addr = 0; + udev->enumeration = 0; +} + +static inline void u132_udev_put_kref(struct u132 *u132, struct u132_udev *udev) +{ + kref_put(&udev->kref, u132_udev_delete); +} + +static inline void u132_udev_get_kref(struct u132 *u132, struct u132_udev *udev) +{ + kref_get(&udev->kref); +} + +static inline void u132_udev_init_kref(struct u132 *u132, + struct u132_udev *udev) +{ + kref_init(&udev->kref); +} + +static inline void u132_ring_put_kref(struct u132 *u132, struct u132_ring *ring) +{ + kref_put(&u132->kref, u132_hcd_delete); +} + +static void u132_ring_requeue_work(struct u132 *u132, struct u132_ring *ring, + unsigned int delta) +{ + if (delta > 0) { + if (queue_delayed_work(workqueue, &ring->scheduler, delta)) + return; + } else if (queue_delayed_work(workqueue, &ring->scheduler, 0)) + return; + kref_put(&u132->kref, u132_hcd_delete); +} + +static void u132_ring_queue_work(struct u132 *u132, struct u132_ring *ring, + unsigned int delta) +{ + kref_get(&u132->kref); + u132_ring_requeue_work(u132, ring, delta); +} + +static void u132_ring_cancel_work(struct u132 *u132, struct u132_ring *ring) +{ + if (cancel_delayed_work(&ring->scheduler)) + kref_put(&u132->kref, u132_hcd_delete); +} + +static void u132_endp_delete(struct kref *kref) +{ + struct u132_endp *endp = kref_to_u132_endp(kref); + struct u132 *u132 = endp->u132; + u8 usb_addr = endp->usb_addr; + u8 usb_endp = endp->usb_endp; + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + u8 endp_number = endp->endp_number; + struct usb_host_endpoint *hep = endp->hep; + struct u132_ring *ring = endp->ring; + struct list_head *head = &endp->endp_ring; + ring->length -= 1; + if (endp == ring->curr_endp) { + if (list_empty(head)) { + ring->curr_endp = NULL; + list_del(head); + } else { + struct u132_endp *next_endp = list_entry(head->next, + struct u132_endp, endp_ring); + ring->curr_endp = next_endp; + list_del(head); + } + } else + list_del(head); + if (endp->input) { + udev->endp_number_in[usb_endp] = 0; + u132_udev_put_kref(u132, udev); + } + if (endp->output) { + udev->endp_number_out[usb_endp] = 0; + u132_udev_put_kref(u132, udev); + } + u132->endp[endp_number - 1] = NULL; + hep->hcpriv = NULL; + kfree(endp); + u132_u132_put_kref(u132); +} + +static inline void u132_endp_put_kref(struct u132 *u132, struct u132_endp *endp) +{ + kref_put(&endp->kref, u132_endp_delete); +} + +static inline void u132_endp_get_kref(struct u132 *u132, struct u132_endp *endp) +{ + kref_get(&endp->kref); +} + +static inline void u132_endp_init_kref(struct u132 *u132, + struct u132_endp *endp) +{ + kref_init(&endp->kref); + kref_get(&u132->kref); +} + +static void u132_endp_queue_work(struct u132 *u132, struct u132_endp *endp, + unsigned int delta) +{ + if (queue_delayed_work(workqueue, &endp->scheduler, delta)) + kref_get(&endp->kref); +} + +static void u132_endp_cancel_work(struct u132 *u132, struct u132_endp *endp) +{ + if (cancel_delayed_work(&endp->scheduler)) + kref_put(&endp->kref, u132_endp_delete); +} + +static inline void u132_monitor_put_kref(struct u132 *u132) +{ + kref_put(&u132->kref, u132_hcd_delete); +} + +static void u132_monitor_queue_work(struct u132 *u132, unsigned int delta) +{ + if (queue_delayed_work(workqueue, &u132->monitor, delta)) + kref_get(&u132->kref); +} + +static void u132_monitor_requeue_work(struct u132 *u132, unsigned int delta) +{ + if (!queue_delayed_work(workqueue, &u132->monitor, delta)) + kref_put(&u132->kref, u132_hcd_delete); +} + +static void u132_monitor_cancel_work(struct u132 *u132) +{ + if (cancel_delayed_work(&u132->monitor)) + kref_put(&u132->kref, u132_hcd_delete); +} + +static int read_roothub_info(struct u132 *u132) +{ + u32 revision; + int retval; + retval = u132_read_pcimem(u132, revision, &revision); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d accessing device co" + "ntrol\n", retval); + return retval; + } else if ((revision & 0xFF) == 0x10) { + } else if ((revision & 0xFF) == 0x11) { + } else { + dev_err(&u132->platform_dev->dev, "device revision is not valid" + " %08X\n", revision); + return -ENODEV; + } + retval = u132_read_pcimem(u132, control, &u132->hc_control); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d accessing device co" + "ntrol\n", retval); + return retval; + } + retval = u132_read_pcimem(u132, roothub.status, + &u132->hc_roothub_status); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d accessing device re" + "g roothub.status\n", retval); + return retval; + } + retval = u132_read_pcimem(u132, roothub.a, &u132->hc_roothub_a); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d accessing device re" + "g roothub.a\n", retval); + return retval; + } + { + int I = u132->num_ports; + int i = 0; + while (I-- > 0) { + retval = u132_read_pcimem(u132, roothub.portstatus[i], + &u132->hc_roothub_portstatus[i]); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d acc" + "essing device roothub.portstatus[%d]\n" + , retval, i); + return retval; + } else + i += 1; + } + } + return 0; +} + +static void u132_hcd_monitor_work(struct work_struct *work) +{ + struct u132 *u132 = container_of(work, struct u132, monitor.work); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + u132_monitor_put_kref(u132); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + u132_monitor_put_kref(u132); + return; + } else { + int retval; + mutex_lock(&u132->sw_lock); + retval = read_roothub_info(u132); + if (retval) { + struct usb_hcd *hcd = u132_to_hcd(u132); + u132_disable(u132); + u132->going = 1; + mutex_unlock(&u132->sw_lock); + usb_hc_died(hcd); + ftdi_elan_gone_away(u132->platform_dev); + u132_monitor_put_kref(u132); + return; + } else { + u132_monitor_requeue_work(u132, 500); + mutex_unlock(&u132->sw_lock); + return; + } + } +} + +static void u132_hcd_giveback_urb(struct u132 *u132, struct u132_endp *endp, + struct urb *urb, int status) +{ + struct u132_ring *ring; + unsigned long irqs; + struct usb_hcd *hcd = u132_to_hcd(u132); + urb->error_count = 0; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + usb_hcd_unlink_urb_from_ep(hcd, urb); + endp->queue_next += 1; + if (ENDP_QUEUE_SIZE > --endp->queue_size) { + endp->active = 0; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + } else { + struct list_head *next = endp->urb_more.next; + struct u132_urbq *urbq = list_entry(next, struct u132_urbq, + urb_more); + list_del(next); + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = + urbq->urb; + endp->active = 0; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + kfree(urbq); + } + mutex_lock(&u132->scheduler_lock); + ring = endp->ring; + ring->in_use = 0; + u132_ring_cancel_work(u132, ring); + u132_ring_queue_work(u132, ring, 0); + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + usb_hcd_giveback_urb(hcd, urb, status); +} + +static void u132_hcd_forget_urb(struct u132 *u132, struct u132_endp *endp, + struct urb *urb, int status) +{ + u132_endp_put_kref(u132, endp); +} + +static void u132_hcd_abandon_urb(struct u132 *u132, struct u132_endp *endp, + struct urb *urb, int status) +{ + unsigned long irqs; + struct usb_hcd *hcd = u132_to_hcd(u132); + urb->error_count = 0; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + usb_hcd_unlink_urb_from_ep(hcd, urb); + endp->queue_next += 1; + if (ENDP_QUEUE_SIZE > --endp->queue_size) { + endp->active = 0; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + } else { + struct list_head *next = endp->urb_more.next; + struct u132_urbq *urbq = list_entry(next, struct u132_urbq, + urb_more); + list_del(next); + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = + urbq->urb; + endp->active = 0; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + kfree(urbq); + } + usb_hcd_giveback_urb(hcd, urb, status); +} + +static inline int edset_input(struct u132 *u132, struct u132_ring *ring, + struct u132_endp *endp, struct urb *urb, u8 address, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + return usb_ftdi_elan_edset_input(u132->platform_dev, ring->number, endp, + urb, address, endp->usb_endp, toggle_bits, callback); +} + +static inline int edset_setup(struct u132 *u132, struct u132_ring *ring, + struct u132_endp *endp, struct urb *urb, u8 address, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + return usb_ftdi_elan_edset_setup(u132->platform_dev, ring->number, endp, + urb, address, endp->usb_endp, toggle_bits, callback); +} + +static inline int edset_single(struct u132 *u132, struct u132_ring *ring, + struct u132_endp *endp, struct urb *urb, u8 address, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + return usb_ftdi_elan_edset_single(u132->platform_dev, ring->number, + endp, urb, address, endp->usb_endp, toggle_bits, callback); +} + +static inline int edset_output(struct u132 *u132, struct u132_ring *ring, + struct u132_endp *endp, struct urb *urb, u8 address, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + return usb_ftdi_elan_edset_output(u132->platform_dev, ring->number, + endp, urb, address, endp->usb_endp, toggle_bits, callback); +} + + +/* +* must not LOCK sw_lock +* +*/ +static void u132_hcd_interrupt_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + struct u132_ring *ring = endp->ring; + u8 *u = urb->transfer_buffer + urb->actual_length; + u8 *b = buf; + int L = len; + + while (L-- > 0) + *u++ = *b++; + + urb->actual_length += len; + if ((condition_code == TD_CC_NOERROR) && + (urb->transfer_buffer_length > urb->actual_length)) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + if (urb->actual_length > 0) { + int retval; + mutex_unlock(&u132->scheduler_lock); + retval = edset_single(u132, ring, endp, urb, + address, endp->toggle_bits, + u132_hcd_interrupt_recv); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, + retval); + } else { + ring->in_use = 0; + endp->active = 0; + endp->jiffies = jiffies + + msecs_to_jiffies(urb->interval); + u132_ring_cancel_work(u132, ring); + u132_ring_queue_work(u132, ring, 0); + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + } + return; + } else if ((condition_code == TD_DATAUNDERRUN) && + ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0)) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + if (condition_code == TD_CC_NOERROR) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, + 0, 1 & toggle_bits); + } else if (condition_code == TD_CC_STALL) { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, endp->usb_endp, + 0, 0); + } else { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, endp->usb_endp, + 0, 0); + dev_err(&u132->platform_dev->dev, "urb=%p givin" + "g back INTERRUPT %s\n", urb, + cc_to_text[condition_code]); + } + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_bulk_output_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + struct u132_ring *ring = endp->ring; + urb->actual_length += len; + endp->toggle_bits = toggle_bits; + if (urb->transfer_buffer_length > urb->actual_length) { + int retval; + mutex_unlock(&u132->scheduler_lock); + retval = edset_output(u132, ring, endp, urb, address, + endp->toggle_bits, u132_hcd_bulk_output_sent); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_bulk_input_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + struct u132_ring *ring = endp->ring; + u8 *u = urb->transfer_buffer + urb->actual_length; + u8 *b = buf; + int L = len; + + while (L-- > 0) + *u++ = *b++; + + urb->actual_length += len; + if ((condition_code == TD_CC_NOERROR) && + (urb->transfer_buffer_length > urb->actual_length)) { + int retval; + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + mutex_unlock(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, address, + endp->usb_endp, endp->toggle_bits, + u132_hcd_bulk_input_recv); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else if (condition_code == TD_CC_NOERROR) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } else if ((condition_code == TD_DATAUNDERRUN) && + ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0)) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else if (condition_code == TD_DATAUNDERRUN) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + dev_warn(&u132->platform_dev->dev, "urb=%p(SHORT NOT OK" + ") giving back BULK IN %s\n", urb, + cc_to_text[condition_code]); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else if (condition_code == TD_CC_STALL) { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, 0); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } else { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, 0); + dev_err(&u132->platform_dev->dev, "urb=%p giving back B" + "ULK IN code=%d %s\n", urb, condition_code, + cc_to_text[condition_code]); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_configure_empty_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_configure_input_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + struct u132_ring *ring = endp->ring; + u8 *u = urb->transfer_buffer; + u8 *b = buf; + int L = len; + + while (L-- > 0) + *u++ = *b++; + + urb->actual_length = len; + if ((condition_code == TD_CC_NOERROR) || ((condition_code == + TD_DATAUNDERRUN) && ((urb->transfer_flags & + URB_SHORT_NOT_OK) == 0))) { + int retval; + mutex_unlock(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_empty(u132->platform_dev, + ring->number, endp, urb, address, + endp->usb_endp, 0x3, + u132_hcd_configure_empty_sent); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else if (condition_code == TD_CC_STALL) { + mutex_unlock(&u132->scheduler_lock); + dev_warn(&u132->platform_dev->dev, "giving back SETUP I" + "NPUT STALL urb %p\n", urb); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } else { + mutex_unlock(&u132->scheduler_lock); + dev_err(&u132->platform_dev->dev, "giving back SETUP IN" + "PUT %s urb %p\n", cc_to_text[condition_code], + urb); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_configure_empty_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_configure_setup_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + if (usb_pipein(urb->pipe)) { + int retval; + struct u132_ring *ring = endp->ring; + mutex_unlock(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, address, + endp->usb_endp, 0, + u132_hcd_configure_input_recv); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + int retval; + struct u132_ring *ring = endp->ring; + mutex_unlock(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, address, + endp->usb_endp, 0, + u132_hcd_configure_empty_recv); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_enumeration_empty_recv(void *data, struct urb *urb, + u8 *buf, int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + u132->addr[0].address = 0; + endp->usb_addr = udev->usb_addr; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_enumeration_address_sent(void *data, struct urb *urb, + u8 *buf, int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + int retval; + struct u132_ring *ring = endp->ring; + mutex_unlock(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, 0, endp->usb_endp, 0, + u132_hcd_enumeration_empty_recv); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_initial_empty_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_initial_input_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + int retval; + struct u132_ring *ring = endp->ring; + u8 *u = urb->transfer_buffer; + u8 *b = buf; + int L = len; + + while (L-- > 0) + *u++ = *b++; + + urb->actual_length = len; + mutex_unlock(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_empty(u132->platform_dev, + ring->number, endp, urb, address, endp->usb_endp, 0x3, + u132_hcd_initial_empty_sent); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +static void u132_hcd_initial_setup_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + mutex_lock(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (!urb->unlinked) { + int retval; + struct u132_ring *ring = endp->ring; + mutex_unlock(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, address, endp->usb_endp, 0, + u132_hcd_initial_input_recv); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p " + "unlinked=%d\n", urb, urb->unlinked); + mutex_unlock(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } +} + +/* +* this work function is only executed from the work queue +* +*/ +static void u132_hcd_ring_work_scheduler(struct work_struct *work) +{ + struct u132_ring *ring = + container_of(work, struct u132_ring, scheduler.work); + struct u132 *u132 = ring->u132; + mutex_lock(&u132->scheduler_lock); + if (ring->in_use) { + mutex_unlock(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } else if (ring->curr_endp) { + struct u132_endp *last_endp = ring->curr_endp; + struct list_head *scan; + struct list_head *head = &last_endp->endp_ring; + unsigned long wakeup = 0; + list_for_each(scan, head) { + struct u132_endp *endp = list_entry(scan, + struct u132_endp, endp_ring); + if (endp->queue_next == endp->queue_last) { + } else if ((endp->delayed == 0) + || time_after_eq(jiffies, endp->jiffies)) { + ring->curr_endp = endp; + u132_endp_cancel_work(u132, last_endp); + u132_endp_queue_work(u132, last_endp, 0); + mutex_unlock(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } else { + unsigned long delta = endp->jiffies - jiffies; + if (delta > wakeup) + wakeup = delta; + } + } + if (last_endp->queue_next == last_endp->queue_last) { + } else if ((last_endp->delayed == 0) || time_after_eq(jiffies, + last_endp->jiffies)) { + u132_endp_cancel_work(u132, last_endp); + u132_endp_queue_work(u132, last_endp, 0); + mutex_unlock(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } else { + unsigned long delta = last_endp->jiffies - jiffies; + if (delta > wakeup) + wakeup = delta; + } + if (wakeup > 0) { + u132_ring_requeue_work(u132, ring, wakeup); + mutex_unlock(&u132->scheduler_lock); + return; + } else { + mutex_unlock(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } + } else { + mutex_unlock(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } +} + +static void u132_hcd_endp_work_scheduler(struct work_struct *work) +{ + struct u132_ring *ring; + struct u132_endp *endp = + container_of(work, struct u132_endp, scheduler.work); + struct u132 *u132 = endp->u132; + mutex_lock(&u132->scheduler_lock); + ring = endp->ring; + if (endp->edset_flush) { + endp->edset_flush = 0; + if (endp->dequeueing) + usb_ftdi_elan_edset_flush(u132->platform_dev, + ring->number, endp); + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (endp->active) { + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (ring->in_use) { + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (endp->queue_next == endp->queue_last) { + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (endp->pipetype == PIPE_INTERRUPT) { + u8 address = u132->addr[endp->usb_addr].address; + if (ring->in_use) { + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else { + int retval; + struct urb *urb = endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + mutex_unlock(&u132->scheduler_lock); + retval = edset_single(u132, ring, endp, urb, address, + endp->toggle_bits, u132_hcd_interrupt_recv); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } + } else if (endp->pipetype == PIPE_CONTROL) { + u8 address = u132->addr[endp->usb_addr].address; + if (ring->in_use) { + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (address == 0) { + int retval; + struct urb *urb = endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + mutex_unlock(&u132->scheduler_lock); + retval = edset_setup(u132, ring, endp, urb, address, + 0x2, u132_hcd_initial_setup_sent); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else if (endp->usb_addr == 0) { + int retval; + struct urb *urb = endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + mutex_unlock(&u132->scheduler_lock); + retval = edset_setup(u132, ring, endp, urb, 0, 0x2, + u132_hcd_enumeration_address_sent); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + int retval; + struct urb *urb = endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_next]; + address = u132->addr[endp->usb_addr].address; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + mutex_unlock(&u132->scheduler_lock); + retval = edset_setup(u132, ring, endp, urb, address, + 0x2, u132_hcd_configure_setup_sent); + if (retval != 0) + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } + } else { + if (endp->input) { + u8 address = u132->addr[endp->usb_addr].address; + if (ring->in_use) { + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else { + int retval; + struct urb *urb = endp->urb_list[ + ENDP_QUEUE_MASK & endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + mutex_unlock(&u132->scheduler_lock); + retval = edset_input(u132, ring, endp, urb, + address, endp->toggle_bits, + u132_hcd_bulk_input_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, + retval); + return; + } + } else { /* output pipe */ + u8 address = u132->addr[endp->usb_addr].address; + if (ring->in_use) { + mutex_unlock(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else { + int retval; + struct urb *urb = endp->urb_list[ + ENDP_QUEUE_MASK & endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + mutex_unlock(&u132->scheduler_lock); + retval = edset_output(u132, ring, endp, urb, + address, endp->toggle_bits, + u132_hcd_bulk_output_sent); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, + retval); + return; + } + } + } +} +#ifdef CONFIG_PM + +static void port_power(struct u132 *u132, int pn, int is_on) +{ + u132->port[pn].power = is_on; +} + +#endif + +static void u132_power(struct u132 *u132, int is_on) +{ + struct usb_hcd *hcd = u132_to_hcd(u132) + ; /* hub is inactive unless the port is powered */ + if (is_on) { + if (u132->power) + return; + u132->power = 1; + } else { + u132->power = 0; + hcd->state = HC_STATE_HALT; + } +} + +static int u132_periodic_reinit(struct u132 *u132) +{ + int retval; + u32 fi = u132->hc_fminterval & 0x03fff; + u32 fit; + u32 fminterval; + retval = u132_read_pcimem(u132, fminterval, &fminterval); + if (retval) + return retval; + fit = fminterval & FIT; + retval = u132_write_pcimem(u132, fminterval, + (fit ^ FIT) | u132->hc_fminterval); + if (retval) + return retval; + retval = u132_write_pcimem(u132, periodicstart, + ((9 * fi) / 10) & 0x3fff); + if (retval) + return retval; + return 0; +} + +static char *hcfs2string(int state) +{ + switch (state) { + case OHCI_USB_RESET: + return "reset"; + case OHCI_USB_RESUME: + return "resume"; + case OHCI_USB_OPER: + return "operational"; + case OHCI_USB_SUSPEND: + return "suspend"; + } + return "?"; +} + +static int u132_init(struct u132 *u132) +{ + int retval; + u32 control; + u132_disable(u132); + u132->next_statechange = jiffies; + retval = u132_write_pcimem(u132, intrdisable, OHCI_INTR_MIE); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + if (u132->num_ports == 0) { + u32 rh_a = -1; + retval = u132_read_pcimem(u132, roothub.a, &rh_a); + if (retval) + return retval; + u132->num_ports = rh_a & RH_A_NDP; + retval = read_roothub_info(u132); + if (retval) + return retval; + } + if (u132->num_ports > MAX_U132_PORTS) + return -EINVAL; + + return 0; +} + + +/* Start an OHCI controller, set the BUS operational +* resets USB and controller +* enable interrupts +*/ +static int u132_run(struct u132 *u132) +{ + int retval; + u32 control; + u32 status; + u32 fminterval; + u32 periodicstart; + u32 cmdstatus; + u32 roothub_a; + int mask = OHCI_INTR_INIT; + int first = u132->hc_fminterval == 0; + int sleep_time = 0; + int reset_timeout = 30; /* ... allow extra time */ + u132_disable(u132); + if (first) { + u32 temp; + retval = u132_read_pcimem(u132, fminterval, &temp); + if (retval) + return retval; + u132->hc_fminterval = temp & 0x3fff; + u132->hc_fminterval |= FSMP(u132->hc_fminterval) << 16; + } + retval = u132_read_pcimem(u132, control, &u132->hc_control); + if (retval) + return retval; + dev_info(&u132->platform_dev->dev, "resetting from state '%s', control " + "= %08X\n", hcfs2string(u132->hc_control & OHCI_CTRL_HCFS), + u132->hc_control); + switch (u132->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_OPER: + sleep_time = 0; + break; + case OHCI_USB_SUSPEND: + case OHCI_USB_RESUME: + u132->hc_control &= OHCI_CTRL_RWC; + u132->hc_control |= OHCI_USB_RESUME; + sleep_time = 10; + break; + default: + u132->hc_control &= OHCI_CTRL_RWC; + u132->hc_control |= OHCI_USB_RESET; + sleep_time = 50; + break; + } + retval = u132_write_pcimem(u132, control, u132->hc_control); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + msleep(sleep_time); + retval = u132_read_pcimem(u132, roothub.a, &roothub_a); + if (retval) + return retval; + if (!(roothub_a & RH_A_NPS)) { + int temp; /* power down each port */ + for (temp = 0; temp < u132->num_ports; temp++) { + retval = u132_write_pcimem(u132, + roothub.portstatus[temp], RH_PS_LSDA); + if (retval) + return retval; + } + } + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; +retry: + retval = u132_read_pcimem(u132, cmdstatus, &status); + if (retval) + return retval; + retval = u132_write_pcimem(u132, cmdstatus, OHCI_HCR); + if (retval) + return retval; +extra: { + retval = u132_read_pcimem(u132, cmdstatus, &status); + if (retval) + return retval; + if (0 != (status & OHCI_HCR)) { + if (--reset_timeout == 0) { + dev_err(&u132->platform_dev->dev, "USB HC reset" + " timed out!\n"); + return -ENODEV; + } else { + msleep(5); + goto extra; + } + } + } + if (u132->flags & OHCI_QUIRK_INITRESET) { + retval = u132_write_pcimem(u132, control, u132->hc_control); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + } + retval = u132_write_pcimem(u132, ed_controlhead, 0x00000000); + if (retval) + return retval; + retval = u132_write_pcimem(u132, ed_bulkhead, 0x11000000); + if (retval) + return retval; + retval = u132_write_pcimem(u132, hcca, 0x00000000); + if (retval) + return retval; + retval = u132_periodic_reinit(u132); + if (retval) + return retval; + retval = u132_read_pcimem(u132, fminterval, &fminterval); + if (retval) + return retval; + retval = u132_read_pcimem(u132, periodicstart, &periodicstart); + if (retval) + return retval; + if (0 == (fminterval & 0x3fff0000) || 0 == periodicstart) { + if (!(u132->flags & OHCI_QUIRK_INITRESET)) { + u132->flags |= OHCI_QUIRK_INITRESET; + goto retry; + } else + dev_err(&u132->platform_dev->dev, "init err(%08x %04x)" + "\n", fminterval, periodicstart); + } /* start controller operations */ + u132->hc_control &= OHCI_CTRL_RWC; + u132->hc_control |= OHCI_CONTROL_INIT | OHCI_CTRL_BLE | OHCI_USB_OPER; + retval = u132_write_pcimem(u132, control, u132->hc_control); + if (retval) + return retval; + retval = u132_write_pcimem(u132, cmdstatus, OHCI_BLF); + if (retval) + return retval; + retval = u132_read_pcimem(u132, cmdstatus, &cmdstatus); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + u132_to_hcd(u132)->state = HC_STATE_RUNNING; + retval = u132_write_pcimem(u132, roothub.status, RH_HS_DRWE); + if (retval) + return retval; + retval = u132_write_pcimem(u132, intrstatus, mask); + if (retval) + return retval; + retval = u132_write_pcimem(u132, intrdisable, + OHCI_INTR_MIE | OHCI_INTR_OC | OHCI_INTR_RHSC | OHCI_INTR_FNO | + OHCI_INTR_UE | OHCI_INTR_RD | OHCI_INTR_SF | OHCI_INTR_WDH | + OHCI_INTR_SO); + if (retval) + return retval; /* handle root hub init quirks ... */ + retval = u132_read_pcimem(u132, roothub.a, &roothub_a); + if (retval) + return retval; + roothub_a &= ~(RH_A_PSM | RH_A_OCPM); + if (u132->flags & OHCI_QUIRK_SUPERIO) { + roothub_a |= RH_A_NOCP; + roothub_a &= ~(RH_A_POTPGT | RH_A_NPS); + retval = u132_write_pcimem(u132, roothub.a, roothub_a); + if (retval) + return retval; + } else if ((u132->flags & OHCI_QUIRK_AMD756) || distrust_firmware) { + roothub_a |= RH_A_NPS; + retval = u132_write_pcimem(u132, roothub.a, roothub_a); + if (retval) + return retval; + } + retval = u132_write_pcimem(u132, roothub.status, RH_HS_LPSC); + if (retval) + return retval; + retval = u132_write_pcimem(u132, roothub.b, + (roothub_a & RH_A_NPS) ? 0 : RH_B_PPCM); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + mdelay((roothub_a >> 23) & 0x1fe); + u132_to_hcd(u132)->state = HC_STATE_RUNNING; + return 0; +} + +static void u132_hcd_stop(struct usb_hcd *hcd) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "u132 device %p(hcd=%p) has b" + "een removed %d\n", u132, hcd, u132->going); + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device hcd=%p is being remov" + "ed\n", hcd); + } else { + mutex_lock(&u132->sw_lock); + msleep(100); + u132_power(u132, 0); + mutex_unlock(&u132->sw_lock); + } +} + +static int u132_hcd_start(struct usb_hcd *hcd) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else if (hcd->self.controller) { + int retval; + struct platform_device *pdev = + to_platform_device(hcd->self.controller); + u16 vendor = ((struct u132_platform_data *) + (pdev->dev.platform_data))->vendor; + u16 device = ((struct u132_platform_data *) + (pdev->dev.platform_data))->device; + mutex_lock(&u132->sw_lock); + msleep(10); + if (vendor == PCI_VENDOR_ID_AMD && device == 0x740c) { + u132->flags = OHCI_QUIRK_AMD756; + } else if (vendor == PCI_VENDOR_ID_OPTI && device == 0xc861) { + dev_err(&u132->platform_dev->dev, "WARNING: OPTi workar" + "ounds unavailable\n"); + } else if (vendor == PCI_VENDOR_ID_COMPAQ && device == 0xa0f8) + u132->flags |= OHCI_QUIRK_ZFMICRO; + retval = u132_run(u132); + if (retval) { + u132_disable(u132); + u132->going = 1; + } + msleep(100); + mutex_unlock(&u132->sw_lock); + return retval; + } else { + dev_err(&u132->platform_dev->dev, "platform_device missing\n"); + return -ENODEV; + } +} + +static int u132_hcd_reset(struct usb_hcd *hcd) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int retval; + mutex_lock(&u132->sw_lock); + retval = u132_init(u132); + if (retval) { + u132_disable(u132); + u132->going = 1; + } + mutex_unlock(&u132->sw_lock); + return retval; + } +} + +static int create_endpoint_and_queue_int(struct u132 *u132, + struct u132_udev *udev, struct urb *urb, + struct usb_device *usb_dev, u8 usb_addr, u8 usb_endp, u8 address, + gfp_t mem_flags) +{ + struct u132_ring *ring; + unsigned long irqs; + int rc; + u8 endp_number; + struct u132_endp *endp = kmalloc(sizeof(struct u132_endp), mem_flags); + + if (!endp) + return -ENOMEM; + + spin_lock_init(&endp->queue_lock.slock); + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + rc = usb_hcd_link_urb_to_ep(u132_to_hcd(u132), urb); + if (rc) { + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + kfree(endp); + return rc; + } + + endp_number = ++u132->num_endpoints; + urb->ep->hcpriv = u132->endp[endp_number - 1] = endp; + INIT_DELAYED_WORK(&endp->scheduler, u132_hcd_endp_work_scheduler); + INIT_LIST_HEAD(&endp->urb_more); + ring = endp->ring = &u132->ring[0]; + if (ring->curr_endp) { + list_add_tail(&endp->endp_ring, &ring->curr_endp->endp_ring); + } else { + INIT_LIST_HEAD(&endp->endp_ring); + ring->curr_endp = endp; + } + ring->length += 1; + endp->dequeueing = 0; + endp->edset_flush = 0; + endp->active = 0; + endp->delayed = 0; + endp->endp_number = endp_number; + endp->u132 = u132; + endp->hep = urb->ep; + endp->pipetype = usb_pipetype(urb->pipe); + u132_endp_init_kref(u132, endp); + if (usb_pipein(urb->pipe)) { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, usb_endp, 0, 0); + endp->input = 1; + endp->output = 0; + udev->endp_number_in[usb_endp] = endp_number; + u132_udev_get_kref(u132, udev); + } else { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, usb_endp, 1, 0); + endp->input = 0; + endp->output = 1; + udev->endp_number_out[usb_endp] = endp_number; + u132_udev_get_kref(u132, udev); + } + urb->hcpriv = u132; + endp->delayed = 1; + endp->jiffies = jiffies + msecs_to_jiffies(urb->interval); + endp->udev_number = address; + endp->usb_addr = usb_addr; + endp->usb_endp = usb_endp; + endp->queue_size = 1; + endp->queue_last = 0; + endp->queue_next = 0; + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_endp_queue_work(u132, endp, msecs_to_jiffies(urb->interval)); + return 0; +} + +static int queue_int_on_old_endpoint(struct u132 *u132, + struct u132_udev *udev, struct urb *urb, + struct usb_device *usb_dev, struct u132_endp *endp, u8 usb_addr, + u8 usb_endp, u8 address) +{ + urb->hcpriv = u132; + endp->delayed = 1; + endp->jiffies = jiffies + msecs_to_jiffies(urb->interval); + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + } else { + struct u132_urbq *urbq = kmalloc(sizeof(struct u132_urbq), + GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, &endp->urb_more); + urbq->urb = urb; + } + } + return 0; +} + +static int create_endpoint_and_queue_bulk(struct u132 *u132, + struct u132_udev *udev, struct urb *urb, + struct usb_device *usb_dev, u8 usb_addr, u8 usb_endp, u8 address, + gfp_t mem_flags) +{ + int ring_number; + struct u132_ring *ring; + unsigned long irqs; + int rc; + u8 endp_number; + struct u132_endp *endp = kmalloc(sizeof(struct u132_endp), mem_flags); + + if (!endp) + return -ENOMEM; + + spin_lock_init(&endp->queue_lock.slock); + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + rc = usb_hcd_link_urb_to_ep(u132_to_hcd(u132), urb); + if (rc) { + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + kfree(endp); + return rc; + } + + endp_number = ++u132->num_endpoints; + urb->ep->hcpriv = u132->endp[endp_number - 1] = endp; + INIT_DELAYED_WORK(&endp->scheduler, u132_hcd_endp_work_scheduler); + INIT_LIST_HEAD(&endp->urb_more); + endp->dequeueing = 0; + endp->edset_flush = 0; + endp->active = 0; + endp->delayed = 0; + endp->endp_number = endp_number; + endp->u132 = u132; + endp->hep = urb->ep; + endp->pipetype = usb_pipetype(urb->pipe); + u132_endp_init_kref(u132, endp); + if (usb_pipein(urb->pipe)) { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, usb_endp, 0, 0); + ring_number = 3; + endp->input = 1; + endp->output = 0; + udev->endp_number_in[usb_endp] = endp_number; + u132_udev_get_kref(u132, udev); + } else { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, usb_endp, 1, 0); + ring_number = 2; + endp->input = 0; + endp->output = 1; + udev->endp_number_out[usb_endp] = endp_number; + u132_udev_get_kref(u132, udev); + } + ring = endp->ring = &u132->ring[ring_number - 1]; + if (ring->curr_endp) { + list_add_tail(&endp->endp_ring, &ring->curr_endp->endp_ring); + } else { + INIT_LIST_HEAD(&endp->endp_ring); + ring->curr_endp = endp; + } + ring->length += 1; + urb->hcpriv = u132; + endp->udev_number = address; + endp->usb_addr = usb_addr; + endp->usb_endp = usb_endp; + endp->queue_size = 1; + endp->queue_last = 0; + endp->queue_next = 0; + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_endp_queue_work(u132, endp, 0); + return 0; +} + +static int queue_bulk_on_old_endpoint(struct u132 *u132, struct u132_udev *udev, + struct urb *urb, + struct usb_device *usb_dev, struct u132_endp *endp, u8 usb_addr, + u8 usb_endp, u8 address) +{ + urb->hcpriv = u132; + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + } else { + struct u132_urbq *urbq = kmalloc(sizeof(struct u132_urbq), + GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, &endp->urb_more); + urbq->urb = urb; + } + } + return 0; +} + +static int create_endpoint_and_queue_control(struct u132 *u132, + struct urb *urb, + struct usb_device *usb_dev, u8 usb_addr, u8 usb_endp, + gfp_t mem_flags) +{ + struct u132_ring *ring; + unsigned long irqs; + int rc; + u8 endp_number; + struct u132_endp *endp = kmalloc(sizeof(struct u132_endp), mem_flags); + + if (!endp) + return -ENOMEM; + + spin_lock_init(&endp->queue_lock.slock); + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + rc = usb_hcd_link_urb_to_ep(u132_to_hcd(u132), urb); + if (rc) { + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + kfree(endp); + return rc; + } + + endp_number = ++u132->num_endpoints; + urb->ep->hcpriv = u132->endp[endp_number - 1] = endp; + INIT_DELAYED_WORK(&endp->scheduler, u132_hcd_endp_work_scheduler); + INIT_LIST_HEAD(&endp->urb_more); + ring = endp->ring = &u132->ring[0]; + if (ring->curr_endp) { + list_add_tail(&endp->endp_ring, &ring->curr_endp->endp_ring); + } else { + INIT_LIST_HEAD(&endp->endp_ring); + ring->curr_endp = endp; + } + ring->length += 1; + endp->dequeueing = 0; + endp->edset_flush = 0; + endp->active = 0; + endp->delayed = 0; + endp->endp_number = endp_number; + endp->u132 = u132; + endp->hep = urb->ep; + u132_endp_init_kref(u132, endp); + u132_endp_get_kref(u132, endp); + if (usb_addr == 0) { + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + endp->udev_number = address; + endp->usb_addr = usb_addr; + endp->usb_endp = usb_endp; + endp->input = 1; + endp->output = 1; + endp->pipetype = usb_pipetype(urb->pipe); + u132_udev_init_kref(u132, udev); + u132_udev_get_kref(u132, udev); + udev->endp_number_in[usb_endp] = endp_number; + udev->endp_number_out[usb_endp] = endp_number; + urb->hcpriv = u132; + endp->queue_size = 1; + endp->queue_last = 0; + endp->queue_next = 0; + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_endp_queue_work(u132, endp, 0); + return 0; + } else { /*(usb_addr > 0) */ + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + endp->udev_number = address; + endp->usb_addr = usb_addr; + endp->usb_endp = usb_endp; + endp->input = 1; + endp->output = 1; + endp->pipetype = usb_pipetype(urb->pipe); + u132_udev_get_kref(u132, udev); + udev->enumeration = 2; + udev->endp_number_in[usb_endp] = endp_number; + udev->endp_number_out[usb_endp] = endp_number; + urb->hcpriv = u132; + endp->queue_size = 1; + endp->queue_last = 0; + endp->queue_next = 0; + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_endp_queue_work(u132, endp, 0); + return 0; + } +} + +static int queue_control_on_old_endpoint(struct u132 *u132, + struct urb *urb, + struct usb_device *usb_dev, struct u132_endp *endp, u8 usb_addr, + u8 usb_endp) +{ + if (usb_addr == 0) { + if (usb_pipein(urb->pipe)) { + urb->hcpriv = u132; + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_last++] = urb; + } else { + struct u132_urbq *urbq = + kmalloc(sizeof(struct u132_urbq), + GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, + &endp->urb_more); + urbq->urb = urb; + } + } + return 0; + } else { /* usb_pipeout(urb->pipe) */ + struct u132_addr *addr = &u132->addr[usb_dev->devnum]; + int I = MAX_U132_UDEVS; + int i = 0; + while (--I > 0) { + struct u132_udev *udev = &u132->udev[++i]; + if (udev->usb_device) { + continue; + } else { + udev->enumeration = 1; + u132->addr[0].address = i; + endp->udev_number = i; + udev->udev_number = i; + udev->usb_addr = usb_dev->devnum; + u132_udev_init_kref(u132, udev); + udev->endp_number_in[usb_endp] = + endp->endp_number; + u132_udev_get_kref(u132, udev); + udev->endp_number_out[usb_endp] = + endp->endp_number; + udev->usb_device = usb_dev; + ((u8 *) (urb->setup_packet))[2] = + addr->address = i; + u132_udev_get_kref(u132, udev); + break; + } + } + if (I == 0) { + dev_err(&u132->platform_dev->dev, "run out of d" + "evice space\n"); + return -EINVAL; + } + urb->hcpriv = u132; + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_last++] = urb; + } else { + struct u132_urbq *urbq = + kmalloc(sizeof(struct u132_urbq), + GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, + &endp->urb_more); + urbq->urb = urb; + } + } + return 0; + } + } else { /*(usb_addr > 0) */ + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + urb->hcpriv = u132; + if (udev->enumeration != 2) + udev->enumeration = 2; + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = + urb; + } else { + struct u132_urbq *urbq = + kmalloc(sizeof(struct u132_urbq), GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, &endp->urb_more); + urbq->urb = urb; + } + } + return 0; + } +} + +static int u132_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (irqs_disabled()) { + if (__GFP_WAIT & mem_flags) { + printk(KERN_ERR "invalid context for function that migh" + "t sleep\n"); + return -EINVAL; + } + } + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed " + "urb=%p\n", urb); + return -ESHUTDOWN; + } else { + u8 usb_addr = usb_pipedevice(urb->pipe); + u8 usb_endp = usb_pipeendpoint(urb->pipe); + struct usb_device *usb_dev = urb->dev; + if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + struct u132_endp *endp = urb->ep->hcpriv; + urb->actual_length = 0; + if (endp) { + unsigned long irqs; + int retval; + spin_lock_irqsave(&endp->queue_lock.slock, + irqs); + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval == 0) { + retval = queue_int_on_old_endpoint( + u132, udev, urb, + usb_dev, endp, + usb_addr, usb_endp, + address); + if (retval) + usb_hcd_unlink_urb_from_ep( + hcd, urb); + } + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + if (retval) { + return retval; + } else { + u132_endp_queue_work(u132, endp, + msecs_to_jiffies(urb->interval)) + ; + return 0; + } + } else if (u132->num_endpoints == MAX_U132_ENDPS) { + return -EINVAL; + } else { /*(endp == NULL) */ + return create_endpoint_and_queue_int(u132, udev, + urb, usb_dev, usb_addr, + usb_endp, address, mem_flags); + } + } else if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + dev_err(&u132->platform_dev->dev, "the hardware does no" + "t support PIPE_ISOCHRONOUS\n"); + return -EINVAL; + } else if (usb_pipetype(urb->pipe) == PIPE_BULK) { + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + struct u132_endp *endp = urb->ep->hcpriv; + urb->actual_length = 0; + if (endp) { + unsigned long irqs; + int retval; + spin_lock_irqsave(&endp->queue_lock.slock, + irqs); + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval == 0) { + retval = queue_bulk_on_old_endpoint( + u132, udev, urb, + usb_dev, endp, + usb_addr, usb_endp, + address); + if (retval) + usb_hcd_unlink_urb_from_ep( + hcd, urb); + } + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + if (retval) { + return retval; + } else { + u132_endp_queue_work(u132, endp, 0); + return 0; + } + } else if (u132->num_endpoints == MAX_U132_ENDPS) { + return -EINVAL; + } else + return create_endpoint_and_queue_bulk(u132, + udev, urb, usb_dev, usb_addr, + usb_endp, address, mem_flags); + } else { + struct u132_endp *endp = urb->ep->hcpriv; + u16 urb_size = 8; + u8 *b = urb->setup_packet; + int i = 0; + char data[30 * 3 + 4]; + char *d = data; + int m = (sizeof(data) - 1) / 3; + int l = 0; + data[0] = 0; + while (urb_size-- > 0) { + if (i > m) { + } else if (i++ < m) { + int w = sprintf(d, " %02X", *b++); + d += w; + l += w; + } else + d += sprintf(d, " .."); + } + if (endp) { + unsigned long irqs; + int retval; + spin_lock_irqsave(&endp->queue_lock.slock, + irqs); + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval == 0) { + retval = queue_control_on_old_endpoint( + u132, urb, usb_dev, + endp, usb_addr, + usb_endp); + if (retval) + usb_hcd_unlink_urb_from_ep( + hcd, urb); + } + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + if (retval) { + return retval; + } else { + u132_endp_queue_work(u132, endp, 0); + return 0; + } + } else if (u132->num_endpoints == MAX_U132_ENDPS) { + return -EINVAL; + } else + return create_endpoint_and_queue_control(u132, + urb, usb_dev, usb_addr, usb_endp, + mem_flags); + } + } +} + +static int dequeue_from_overflow_chain(struct u132 *u132, + struct u132_endp *endp, struct urb *urb) +{ + struct list_head *scan; + struct list_head *head = &endp->urb_more; + list_for_each(scan, head) { + struct u132_urbq *urbq = list_entry(scan, struct u132_urbq, + urb_more); + if (urbq->urb == urb) { + struct usb_hcd *hcd = u132_to_hcd(u132); + list_del(scan); + endp->queue_size -= 1; + urb->error_count = 0; + usb_hcd_giveback_urb(hcd, urb, 0); + return 0; + } else + continue; + } + dev_err(&u132->platform_dev->dev, "urb=%p not found in endp[%d]=%p ring" + "[%d] %c%c usb_endp=%d usb_addr=%d size=%d next=%04X last=%04X" + "\n", urb, endp->endp_number, endp, endp->ring->number, + endp->input ? 'I' : ' ', endp->output ? 'O' : ' ', + endp->usb_endp, endp->usb_addr, endp->queue_size, + endp->queue_next, endp->queue_last); + return -EINVAL; +} + +static int u132_endp_urb_dequeue(struct u132 *u132, struct u132_endp *endp, + struct urb *urb, int status) +{ + unsigned long irqs; + int rc; + + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + rc = usb_hcd_check_unlink_urb(u132_to_hcd(u132), urb, status); + if (rc) { + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + return rc; + } + if (endp->queue_size == 0) { + dev_err(&u132->platform_dev->dev, "urb=%p not found in endp[%d]" + "=%p ring[%d] %c%c usb_endp=%d usb_addr=%d\n", urb, + endp->endp_number, endp, endp->ring->number, + endp->input ? 'I' : ' ', endp->output ? 'O' : ' ', + endp->usb_endp, endp->usb_addr); + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + return -EINVAL; + } + if (urb == endp->urb_list[ENDP_QUEUE_MASK & endp->queue_next]) { + if (endp->active) { + endp->dequeueing = 1; + endp->edset_flush = 1; + u132_endp_queue_work(u132, endp, 0); + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + return 0; + } else { + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_hcd_abandon_urb(u132, endp, urb, status); + return 0; + } + } else { + u16 queue_list = 0; + u16 queue_size = endp->queue_size; + u16 queue_scan = endp->queue_next; + struct urb **urb_slot = NULL; + while (++queue_list < ENDP_QUEUE_SIZE && --queue_size > 0) { + if (urb == endp->urb_list[ENDP_QUEUE_MASK & + ++queue_scan]) { + urb_slot = &endp->urb_list[ENDP_QUEUE_MASK & + queue_scan]; + break; + } else + continue; + } + while (++queue_list < ENDP_QUEUE_SIZE && --queue_size > 0) { + *urb_slot = endp->urb_list[ENDP_QUEUE_MASK & + ++queue_scan]; + urb_slot = &endp->urb_list[ENDP_QUEUE_MASK & + queue_scan]; + } + if (urb_slot) { + struct usb_hcd *hcd = u132_to_hcd(u132); + + usb_hcd_unlink_urb_from_ep(hcd, urb); + endp->queue_size -= 1; + if (list_empty(&endp->urb_more)) { + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + } else { + struct list_head *next = endp->urb_more.next; + struct u132_urbq *urbq = list_entry(next, + struct u132_urbq, urb_more); + list_del(next); + *urb_slot = urbq->urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + kfree(urbq); + } urb->error_count = 0; + usb_hcd_giveback_urb(hcd, urb, status); + return 0; + } else if (list_empty(&endp->urb_more)) { + dev_err(&u132->platform_dev->dev, "urb=%p not found in " + "endp[%d]=%p ring[%d] %c%c usb_endp=%d usb_addr" + "=%d size=%d next=%04X last=%04X\n", urb, + endp->endp_number, endp, endp->ring->number, + endp->input ? 'I' : ' ', + endp->output ? 'O' : ' ', endp->usb_endp, + endp->usb_addr, endp->queue_size, + endp->queue_next, endp->queue_last); + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + return -EINVAL; + } else { + int retval; + + usb_hcd_unlink_urb_from_ep(u132_to_hcd(u132), urb); + retval = dequeue_from_overflow_chain(u132, endp, + urb); + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + return retval; + } + } +} + +static int u132_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 2) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else { + u8 usb_addr = usb_pipedevice(urb->pipe); + u8 usb_endp = usb_pipeendpoint(urb->pipe); + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + if (usb_pipein(urb->pipe)) { + u8 endp_number = udev->endp_number_in[usb_endp]; + struct u132_endp *endp = u132->endp[endp_number - 1]; + return u132_endp_urb_dequeue(u132, endp, urb, status); + } else { + u8 endp_number = udev->endp_number_out[usb_endp]; + struct u132_endp *endp = u132->endp[endp_number - 1]; + return u132_endp_urb_dequeue(u132, endp, urb, status); + } + } +} + +static void u132_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *hep) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 2) { + dev_err(&u132->platform_dev->dev, "u132 device %p(hcd=%p hep=%p" + ") has been removed %d\n", u132, hcd, hep, + u132->going); + } else { + struct u132_endp *endp = hep->hcpriv; + if (endp) + u132_endp_put_kref(u132, endp); + } +} + +static int u132_get_frame(struct usb_hcd *hcd) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int frame = 0; + dev_err(&u132->platform_dev->dev, "TODO: u132_get_frame\n"); + msleep(100); + return frame; + } +} + +static int u132_roothub_descriptor(struct u132 *u132, + struct usb_hub_descriptor *desc) +{ + int retval; + u16 temp; + u32 rh_a = -1; + u32 rh_b = -1; + retval = u132_read_pcimem(u132, roothub.a, &rh_a); + if (retval) + return retval; + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = (rh_a & RH_A_POTPGT) >> 24; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = u132->num_ports; + temp = 1 + (u132->num_ports / 8); + desc->bDescLength = 7 + 2 * temp; + temp = 0; + if (rh_a & RH_A_NPS) + temp |= 0x0002; + if (rh_a & RH_A_PSM) + temp |= 0x0001; + if (rh_a & RH_A_NOCP) + temp |= 0x0010; + else if (rh_a & RH_A_OCPM) + temp |= 0x0008; + desc->wHubCharacteristics = cpu_to_le16(temp); + retval = u132_read_pcimem(u132, roothub.b, &rh_b); + if (retval) + return retval; + memset(desc->u.hs.DeviceRemovable, 0xff, + sizeof(desc->u.hs.DeviceRemovable)); + desc->u.hs.DeviceRemovable[0] = rh_b & RH_B_DR; + if (u132->num_ports > 7) { + desc->u.hs.DeviceRemovable[1] = (rh_b & RH_B_DR) >> 8; + desc->u.hs.DeviceRemovable[2] = 0xff; + } else + desc->u.hs.DeviceRemovable[1] = 0xff; + return 0; +} + +static int u132_roothub_status(struct u132 *u132, __le32 *desc) +{ + u32 rh_status = -1; + int ret_status = u132_read_pcimem(u132, roothub.status, &rh_status); + *desc = cpu_to_le32(rh_status); + return ret_status; +} + +static int u132_roothub_portstatus(struct u132 *u132, __le32 *desc, u16 wIndex) +{ + if (wIndex == 0 || wIndex > u132->num_ports) { + return -EINVAL; + } else { + int port = wIndex - 1; + u32 rh_portstatus = -1; + int ret_portstatus = u132_read_pcimem(u132, + roothub.portstatus[port], &rh_portstatus); + *desc = cpu_to_le32(rh_portstatus); + if (*(u16 *) (desc + 2)) { + dev_info(&u132->platform_dev->dev, "Port %d Status Chan" + "ge = %08X\n", port, *desc); + } + return ret_portstatus; + } +} + + +/* this timer value might be vendor-specific ... */ +#define PORT_RESET_HW_MSEC 10 +#define PORT_RESET_MSEC 10 +/* wrap-aware logic morphed from */ +#define tick_before(t1, t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0) +static int u132_roothub_portreset(struct u132 *u132, int port_index) +{ + int retval; + u32 fmnumber; + u16 now; + u16 reset_done; + retval = u132_read_pcimem(u132, fmnumber, &fmnumber); + if (retval) + return retval; + now = fmnumber; + reset_done = now + PORT_RESET_MSEC; + do { + u32 portstat; + do { + retval = u132_read_pcimem(u132, + roothub.portstatus[port_index], &portstat); + if (retval) + return retval; + if (RH_PS_PRS & portstat) + continue; + else + break; + } while (tick_before(now, reset_done)); + if (RH_PS_PRS & portstat) + return -ENODEV; + if (RH_PS_CCS & portstat) { + if (RH_PS_PRSC & portstat) { + retval = u132_write_pcimem(u132, + roothub.portstatus[port_index], + RH_PS_PRSC); + if (retval) + return retval; + } + } else + break; /* start the next reset, + sleep till it's probably done */ + retval = u132_write_pcimem(u132, roothub.portstatus[port_index], + RH_PS_PRS); + if (retval) + return retval; + msleep(PORT_RESET_HW_MSEC); + retval = u132_read_pcimem(u132, fmnumber, &fmnumber); + if (retval) + return retval; + now = fmnumber; + } while (tick_before(now, reset_done)); + return 0; +} + +static int u132_roothub_setportfeature(struct u132 *u132, u16 wValue, + u16 wIndex) +{ + if (wIndex == 0 || wIndex > u132->num_ports) { + return -EINVAL; + } else { + int retval; + int port_index = wIndex - 1; + struct u132_port *port = &u132->port[port_index]; + port->Status &= ~(1 << wValue); + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + retval = u132_write_pcimem(u132, + roothub.portstatus[port_index], RH_PS_PSS); + if (retval) + return retval; + return 0; + case USB_PORT_FEAT_POWER: + retval = u132_write_pcimem(u132, + roothub.portstatus[port_index], RH_PS_PPS); + if (retval) + return retval; + return 0; + case USB_PORT_FEAT_RESET: + retval = u132_roothub_portreset(u132, port_index); + if (retval) + return retval; + return 0; + default: + return -EPIPE; + } + } +} + +static int u132_roothub_clearportfeature(struct u132 *u132, u16 wValue, + u16 wIndex) +{ + if (wIndex == 0 || wIndex > u132->num_ports) { + return -EINVAL; + } else { + int port_index = wIndex - 1; + u32 temp; + int retval; + struct u132_port *port = &u132->port[port_index]; + port->Status &= ~(1 << wValue); + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + temp = RH_PS_CCS; + break; + case USB_PORT_FEAT_C_ENABLE: + temp = RH_PS_PESC; + break; + case USB_PORT_FEAT_SUSPEND: + temp = RH_PS_POCI; + if ((u132->hc_control & OHCI_CTRL_HCFS) + != OHCI_USB_OPER) { + dev_err(&u132->platform_dev->dev, "TODO resume_" + "root_hub\n"); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + temp = RH_PS_PSSC; + break; + case USB_PORT_FEAT_POWER: + temp = RH_PS_LSDA; + break; + case USB_PORT_FEAT_C_CONNECTION: + temp = RH_PS_CSC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + temp = RH_PS_OCIC; + break; + case USB_PORT_FEAT_C_RESET: + temp = RH_PS_PRSC; + break; + default: + return -EPIPE; + } + retval = u132_write_pcimem(u132, roothub.portstatus[port_index], + temp); + if (retval) + return retval; + return 0; + } +} + + +/* the virtual root hub timer IRQ checks for hub status*/ +static int u132_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device hcd=%p has been remov" + "ed %d\n", hcd, u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device hcd=%p is being remov" + "ed\n", hcd); + return -ESHUTDOWN; + } else { + int i, changed = 0, length = 1; + if (u132->flags & OHCI_QUIRK_AMD756) { + if ((u132->hc_roothub_a & RH_A_NDP) > MAX_ROOT_PORTS) { + dev_err(&u132->platform_dev->dev, "bogus NDP, r" + "ereads as NDP=%d\n", + u132->hc_roothub_a & RH_A_NDP); + goto done; + } + } + if (u132->hc_roothub_status & (RH_HS_LPSC | RH_HS_OCIC)) + buf[0] = changed = 1; + else + buf[0] = 0; + if (u132->num_ports > 7) { + buf[1] = 0; + length++; + } + for (i = 0; i < u132->num_ports; i++) { + if (u132->hc_roothub_portstatus[i] & (RH_PS_CSC | + RH_PS_PESC | RH_PS_PSSC | RH_PS_OCIC | + RH_PS_PRSC)) { + changed = 1; + if (i < 7) + buf[0] |= 1 << (i + 1); + else + buf[1] |= 1 << (i - 7); + continue; + } + if (!(u132->hc_roothub_portstatus[i] & RH_PS_CCS)) + continue; + + if ((u132->hc_roothub_portstatus[i] & RH_PS_PSS)) + continue; + } +done: + return changed ? length : 0; + } +} + +static int u132_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int retval = 0; + mutex_lock(&u132->sw_lock); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto stall; + } + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto stall; + } + break; + case ClearPortFeature:{ + retval = u132_roothub_clearportfeature(u132, + wValue, wIndex); + if (retval) + goto error; + break; + } + case GetHubDescriptor:{ + retval = u132_roothub_descriptor(u132, + (struct usb_hub_descriptor *)buf); + if (retval) + goto error; + break; + } + case GetHubStatus:{ + retval = u132_roothub_status(u132, + (__le32 *) buf); + if (retval) + goto error; + break; + } + case GetPortStatus:{ + retval = u132_roothub_portstatus(u132, + (__le32 *) buf, wIndex); + if (retval) + goto error; + break; + } + case SetPortFeature:{ + retval = u132_roothub_setportfeature(u132, + wValue, wIndex); + if (retval) + goto error; + break; + } + default: + goto stall; + error: + u132_disable(u132); + u132->going = 1; + break; + stall: + retval = -EPIPE; + break; + } + mutex_unlock(&u132->sw_lock); + return retval; + } +} + +static int u132_start_port_reset(struct usb_hcd *hcd, unsigned port_num) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else + return 0; +} + + +#ifdef CONFIG_PM +static int u132_bus_suspend(struct usb_hcd *hcd) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else + return 0; +} + +static int u132_bus_resume(struct usb_hcd *hcd) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else + return 0; +} + +#else +#define u132_bus_suspend NULL +#define u132_bus_resume NULL +#endif +static struct hc_driver u132_hc_driver = { + .description = hcd_name, + .hcd_priv_size = sizeof(struct u132), + .irq = NULL, + .flags = HCD_USB11 | HCD_MEMORY, + .reset = u132_hcd_reset, + .start = u132_hcd_start, + .stop = u132_hcd_stop, + .urb_enqueue = u132_urb_enqueue, + .urb_dequeue = u132_urb_dequeue, + .endpoint_disable = u132_endpoint_disable, + .get_frame_number = u132_get_frame, + .hub_status_data = u132_hub_status_data, + .hub_control = u132_hub_control, + .bus_suspend = u132_bus_suspend, + .bus_resume = u132_bus_resume, + .start_port_reset = u132_start_port_reset, +}; + +/* +* This function may be called by the USB core whilst the "usb_all_devices_rwsem" +* is held for writing, thus this module must not call usb_remove_hcd() +* synchronously - but instead should immediately stop activity to the +* device and asynchronously call usb_remove_hcd() +*/ +static int u132_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + if (hcd) { + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going++ > 1) { + dev_err(&u132->platform_dev->dev, "already being remove" + "d\n"); + return -ENODEV; + } else { + int rings = MAX_U132_RINGS; + int endps = MAX_U132_ENDPS; + dev_err(&u132->platform_dev->dev, "removing device u132" + ".%d\n", u132->sequence_num); + msleep(100); + mutex_lock(&u132->sw_lock); + u132_monitor_cancel_work(u132); + while (rings-- > 0) { + struct u132_ring *ring = &u132->ring[rings]; + u132_ring_cancel_work(u132, ring); + } while (endps-- > 0) { + struct u132_endp *endp = u132->endp[endps]; + if (endp) + u132_endp_cancel_work(u132, endp); + } + u132->going += 1; + printk(KERN_INFO "removing device u132.%d\n", + u132->sequence_num); + mutex_unlock(&u132->sw_lock); + usb_remove_hcd(hcd); + u132_u132_put_kref(u132); + return 0; + } + } else + return 0; +} + +static void u132_initialise(struct u132 *u132, struct platform_device *pdev) +{ + int rings = MAX_U132_RINGS; + int ports = MAX_U132_PORTS; + int addrs = MAX_U132_ADDRS; + int udevs = MAX_U132_UDEVS; + int endps = MAX_U132_ENDPS; + u132->board = pdev->dev.platform_data; + u132->platform_dev = pdev; + u132->power = 0; + u132->reset = 0; + mutex_init(&u132->sw_lock); + mutex_init(&u132->scheduler_lock); + while (rings-- > 0) { + struct u132_ring *ring = &u132->ring[rings]; + ring->u132 = u132; + ring->number = rings + 1; + ring->length = 0; + ring->curr_endp = NULL; + INIT_DELAYED_WORK(&ring->scheduler, + u132_hcd_ring_work_scheduler); + } + mutex_lock(&u132->sw_lock); + INIT_DELAYED_WORK(&u132->monitor, u132_hcd_monitor_work); + while (ports-- > 0) { + struct u132_port *port = &u132->port[ports]; + port->u132 = u132; + port->reset = 0; + port->enable = 0; + port->power = 0; + port->Status = 0; + } + while (addrs-- > 0) { + struct u132_addr *addr = &u132->addr[addrs]; + addr->address = 0; + } + while (udevs-- > 0) { + struct u132_udev *udev = &u132->udev[udevs]; + int i = ARRAY_SIZE(udev->endp_number_in); + int o = ARRAY_SIZE(udev->endp_number_out); + udev->usb_device = NULL; + udev->udev_number = 0; + udev->usb_addr = 0; + udev->portnumber = 0; + while (i-- > 0) + udev->endp_number_in[i] = 0; + + while (o-- > 0) + udev->endp_number_out[o] = 0; + + } + while (endps-- > 0) + u132->endp[endps] = NULL; + + mutex_unlock(&u132->sw_lock); +} + +static int u132_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + int retval; + u32 control; + u32 rh_a = -1; + u32 num_ports; + + msleep(100); + if (u132_exiting > 0) + return -ENODEV; + + retval = ftdi_write_pcimem(pdev, intrdisable, OHCI_INTR_MIE); + if (retval) + return retval; + retval = ftdi_read_pcimem(pdev, control, &control); + if (retval) + return retval; + retval = ftdi_read_pcimem(pdev, roothub.a, &rh_a); + if (retval) + return retval; + num_ports = rh_a & RH_A_NDP; /* refuse to confuse usbcore */ + if (pdev->dev.dma_mask) + return -EINVAL; + + hcd = usb_create_hcd(&u132_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + printk(KERN_ERR "failed to create the usb hcd struct for U132\n" + ); + ftdi_elan_gone_away(pdev); + return -ENOMEM; + } else { + struct u132 *u132 = hcd_to_u132(hcd); + retval = 0; + hcd->rsrc_start = 0; + mutex_lock(&u132_module_lock); + list_add_tail(&u132->u132_list, &u132_static_list); + u132->sequence_num = ++u132_instances; + mutex_unlock(&u132_module_lock); + u132_u132_init_kref(u132); + u132_initialise(u132, pdev); + hcd->product_desc = "ELAN U132 Host Controller"; + retval = usb_add_hcd(hcd, 0, 0); + if (retval != 0) { + dev_err(&u132->platform_dev->dev, "init error %d\n", + retval); + u132_u132_put_kref(u132); + return retval; + } else { + u132_monitor_queue_work(u132, 100); + return 0; + } + } +} + + +#ifdef CONFIG_PM +/* + * for this device there's no useful distinction between the controller + * and its root hub, except that the root hub only gets direct PM calls + * when CONFIG_PM_RUNTIME is enabled. + */ +static int u132_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int retval = 0, ports; + + switch (state.event) { + case PM_EVENT_FREEZE: + retval = u132_bus_suspend(hcd); + break; + case PM_EVENT_SUSPEND: + case PM_EVENT_HIBERNATE: + ports = MAX_U132_PORTS; + while (ports-- > 0) { + port_power(u132, ports, 0); + } + break; + } + return retval; + } +} + +static int u132_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int retval = 0; + if (!u132->port[0].power) { + int ports = MAX_U132_PORTS; + while (ports-- > 0) { + port_power(u132, ports, 1); + } + retval = 0; + } else { + retval = u132_bus_resume(hcd); + } + return retval; + } +} + +#else +#define u132_suspend NULL +#define u132_resume NULL +#endif +/* +* this driver is loaded explicitly by ftdi_u132 +* +* the platform_driver struct is static because it is per type of module +*/ +static struct platform_driver u132_platform_driver = { + .probe = u132_probe, + .remove = u132_remove, + .suspend = u132_suspend, + .resume = u132_resume, + .driver = { + .name = (char *)hcd_name, + .owner = THIS_MODULE, + }, +}; +static int __init u132_hcd_init(void) +{ + int retval; + INIT_LIST_HEAD(&u132_static_list); + u132_instances = 0; + u132_exiting = 0; + mutex_init(&u132_module_lock); + if (usb_disabled()) + return -ENODEV; + printk(KERN_INFO "driver %s\n", hcd_name); + workqueue = create_singlethread_workqueue("u132"); + retval = platform_driver_register(&u132_platform_driver); + return retval; +} + + +module_init(u132_hcd_init); +static void __exit u132_hcd_exit(void) +{ + struct u132 *u132; + struct u132 *temp; + mutex_lock(&u132_module_lock); + u132_exiting += 1; + mutex_unlock(&u132_module_lock); + list_for_each_entry_safe(u132, temp, &u132_static_list, u132_list) { + platform_device_unregister(u132->platform_dev); + } + platform_driver_unregister(&u132_platform_driver); + printk(KERN_INFO "u132-hcd driver deregistered\n"); + wait_event(u132_hcd_wait, u132_instances == 0); + flush_workqueue(workqueue); + destroy_workqueue(workqueue); +} + + +module_exit(u132_hcd_exit); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:u132_hcd"); diff --git a/drivers/usb/host/uhci-debug.c b/drivers/usb/host/uhci-debug.c new file mode 100644 index 00000000..45573754 --- /dev/null +++ b/drivers/usb/host/uhci-debug.c @@ -0,0 +1,655 @@ +/* + * UHCI-specific debugging code. Invaluable when something + * goes wrong, but don't get in my face. + * + * Kernel visible pointers are surrounded in []s and bus + * visible pointers are surrounded in ()s + * + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 1999-2001 Johannes Erdfelt + */ + +#include +#include +#include +#include + +#include "uhci-hcd.h" + +#define EXTRA_SPACE 1024 + +static struct dentry *uhci_debugfs_root; + +#ifdef DEBUG + +/* Handle REALLY large printks so we don't overflow buffers */ +static void lprintk(char *buf) +{ + char *p; + + /* Just write one line at a time */ + while (buf) { + p = strchr(buf, '\n'); + if (p) + *p = 0; + printk(KERN_DEBUG "%s\n", buf); + buf = p; + if (buf) + buf++; + } +} + +static int uhci_show_td(struct uhci_hcd *uhci, struct uhci_td *td, char *buf, + int len, int space) +{ + char *out = buf; + char *spid; + u32 status, token; + + status = td_status(uhci, td); + out += sprintf(out, "%*s[%p] link (%08x) ", space, "", td, + hc32_to_cpu(uhci, td->link)); + out += sprintf(out, "e%d %s%s%s%s%s%s%s%s%s%sLength=%x ", + ((status >> 27) & 3), + (status & TD_CTRL_SPD) ? "SPD " : "", + (status & TD_CTRL_LS) ? "LS " : "", + (status & TD_CTRL_IOC) ? "IOC " : "", + (status & TD_CTRL_ACTIVE) ? "Active " : "", + (status & TD_CTRL_STALLED) ? "Stalled " : "", + (status & TD_CTRL_DBUFERR) ? "DataBufErr " : "", + (status & TD_CTRL_BABBLE) ? "Babble " : "", + (status & TD_CTRL_NAK) ? "NAK " : "", + (status & TD_CTRL_CRCTIMEO) ? "CRC/Timeo " : "", + (status & TD_CTRL_BITSTUFF) ? "BitStuff " : "", + status & 0x7ff); + if (out - buf > len) + goto done; + + token = td_token(uhci, td); + switch (uhci_packetid(token)) { + case USB_PID_SETUP: + spid = "SETUP"; + break; + case USB_PID_OUT: + spid = "OUT"; + break; + case USB_PID_IN: + spid = "IN"; + break; + default: + spid = "?"; + break; + } + + out += sprintf(out, "MaxLen=%x DT%d EndPt=%x Dev=%x, PID=%x(%s) ", + token >> 21, + ((token >> 19) & 1), + (token >> 15) & 15, + (token >> 8) & 127, + (token & 0xff), + spid); + out += sprintf(out, "(buf=%08x)\n", hc32_to_cpu(uhci, td->buffer)); + +done: + if (out - buf > len) + out += sprintf(out, " ...\n"); + return out - buf; +} + +static int uhci_show_urbp(struct uhci_hcd *uhci, struct urb_priv *urbp, + char *buf, int len, int space) +{ + char *out = buf; + struct uhci_td *td; + int i, nactive, ninactive; + char *ptype; + + + out += sprintf(out, "urb_priv [%p] ", urbp); + out += sprintf(out, "urb [%p] ", urbp->urb); + out += sprintf(out, "qh [%p] ", urbp->qh); + out += sprintf(out, "Dev=%d ", usb_pipedevice(urbp->urb->pipe)); + out += sprintf(out, "EP=%x(%s) ", usb_pipeendpoint(urbp->urb->pipe), + (usb_pipein(urbp->urb->pipe) ? "IN" : "OUT")); + if (out - buf > len) + goto done; + + switch (usb_pipetype(urbp->urb->pipe)) { + case PIPE_ISOCHRONOUS: ptype = "ISO"; break; + case PIPE_INTERRUPT: ptype = "INT"; break; + case PIPE_BULK: ptype = "BLK"; break; + default: + case PIPE_CONTROL: ptype = "CTL"; break; + } + + out += sprintf(out, "%s%s", ptype, (urbp->fsbr ? " FSBR" : "")); + out += sprintf(out, " Actlen=%d%s", urbp->urb->actual_length, + (urbp->qh->type == USB_ENDPOINT_XFER_CONTROL ? + "-8" : "")); + + if (urbp->urb->unlinked) + out += sprintf(out, " Unlinked=%d", urbp->urb->unlinked); + out += sprintf(out, "\n"); + + if (out - buf > len) + goto done; + + i = nactive = ninactive = 0; + list_for_each_entry(td, &urbp->td_list, list) { + if (urbp->qh->type != USB_ENDPOINT_XFER_ISOC && + (++i <= 10 || debug > 2)) { + out += sprintf(out, "%*s%d: ", space + 2, "", i); + out += uhci_show_td(uhci, td, out, + len - (out - buf), 0); + if (out - buf > len) + goto tail; + } else { + if (td_status(uhci, td) & TD_CTRL_ACTIVE) + ++nactive; + else + ++ninactive; + } + } + if (nactive + ninactive > 0) + out += sprintf(out, + "%*s[skipped %d inactive and %d active TDs]\n", + space, "", ninactive, nactive); +done: + if (out - buf > len) + out += sprintf(out, " ...\n"); +tail: + return out - buf; +} + +static int uhci_show_qh(struct uhci_hcd *uhci, + struct uhci_qh *qh, char *buf, int len, int space) +{ + char *out = buf; + int i, nurbs; + __hc32 element = qh_element(qh); + char *qtype; + + switch (qh->type) { + case USB_ENDPOINT_XFER_ISOC: qtype = "ISO"; break; + case USB_ENDPOINT_XFER_INT: qtype = "INT"; break; + case USB_ENDPOINT_XFER_BULK: qtype = "BLK"; break; + case USB_ENDPOINT_XFER_CONTROL: qtype = "CTL"; break; + default: qtype = "Skel" ; break; + } + + out += sprintf(out, "%*s[%p] %s QH link (%08x) element (%08x)\n", + space, "", qh, qtype, + hc32_to_cpu(uhci, qh->link), + hc32_to_cpu(uhci, element)); + if (qh->type == USB_ENDPOINT_XFER_ISOC) + out += sprintf(out, + "%*s period %d phase %d load %d us, frame %x desc [%p]\n", + space, "", qh->period, qh->phase, qh->load, + qh->iso_frame, qh->iso_packet_desc); + else if (qh->type == USB_ENDPOINT_XFER_INT) + out += sprintf(out, "%*s period %d phase %d load %d us\n", + space, "", qh->period, qh->phase, qh->load); + if (out - buf > len) + goto done; + + if (element & UHCI_PTR_QH(uhci)) + out += sprintf(out, "%*s Element points to QH (bug?)\n", space, ""); + + if (element & UHCI_PTR_DEPTH(uhci)) + out += sprintf(out, "%*s Depth traverse\n", space, ""); + + if (element & cpu_to_hc32(uhci, 8)) + out += sprintf(out, "%*s Bit 3 set (bug?)\n", space, ""); + + if (!(element & ~(UHCI_PTR_QH(uhci) | UHCI_PTR_DEPTH(uhci)))) + out += sprintf(out, "%*s Element is NULL (bug?)\n", space, ""); + + if (out - buf > len) + goto done; + + if (list_empty(&qh->queue)) { + out += sprintf(out, "%*s queue is empty\n", space, ""); + if (qh == uhci->skel_async_qh) { + out += uhci_show_td(uhci, uhci->term_td, out, + len - (out - buf), 0); + if (out - buf > len) + goto tail; + } + } else { + struct urb_priv *urbp = list_entry(qh->queue.next, + struct urb_priv, node); + struct uhci_td *td = list_entry(urbp->td_list.next, + struct uhci_td, list); + + if (element != LINK_TO_TD(uhci, td)) + out += sprintf(out, "%*s Element != First TD\n", + space, ""); + i = nurbs = 0; + list_for_each_entry(urbp, &qh->queue, node) { + if (++i <= 10) { + out += uhci_show_urbp(uhci, urbp, out, + len - (out - buf), space + 2); + if (out - buf > len) + goto tail; + } + else + ++nurbs; + } + if (nurbs > 0) + out += sprintf(out, "%*s Skipped %d URBs\n", + space, "", nurbs); + } + + if (out - buf > len) + goto done; + + if (qh->dummy_td) { + out += sprintf(out, "%*s Dummy TD\n", space, ""); + out += uhci_show_td(uhci, qh->dummy_td, out, + len - (out - buf), 0); + if (out - buf > len) + goto tail; + } + +done: + if (out - buf > len) + out += sprintf(out, " ...\n"); +tail: + return out - buf; +} + +static int uhci_show_sc(int port, unsigned short status, char *buf) +{ + return sprintf(buf, " stat%d = %04x %s%s%s%s%s%s%s%s%s%s\n", + port, + status, + (status & USBPORTSC_SUSP) ? " Suspend" : "", + (status & USBPORTSC_OCC) ? " OverCurrentChange" : "", + (status & USBPORTSC_OC) ? " OverCurrent" : "", + (status & USBPORTSC_PR) ? " Reset" : "", + (status & USBPORTSC_LSDA) ? " LowSpeed" : "", + (status & USBPORTSC_RD) ? " ResumeDetect" : "", + (status & USBPORTSC_PEC) ? " EnableChange" : "", + (status & USBPORTSC_PE) ? " Enabled" : "", + (status & USBPORTSC_CSC) ? " ConnectChange" : "", + (status & USBPORTSC_CCS) ? " Connected" : ""); +} + +static int uhci_show_root_hub_state(struct uhci_hcd *uhci, char *buf) +{ + char *rh_state; + + switch (uhci->rh_state) { + case UHCI_RH_RESET: + rh_state = "reset"; break; + case UHCI_RH_SUSPENDED: + rh_state = "suspended"; break; + case UHCI_RH_AUTO_STOPPED: + rh_state = "auto-stopped"; break; + case UHCI_RH_RESUMING: + rh_state = "resuming"; break; + case UHCI_RH_SUSPENDING: + rh_state = "suspending"; break; + case UHCI_RH_RUNNING: + rh_state = "running"; break; + case UHCI_RH_RUNNING_NODEVS: + rh_state = "running, no devs"; break; + default: + rh_state = "?"; break; + } + return sprintf(buf, "Root-hub state: %s FSBR: %d\n", + rh_state, uhci->fsbr_is_on); +} + +static int uhci_show_status(struct uhci_hcd *uhci, char *buf, int len) +{ + char *out = buf; + unsigned short usbcmd, usbstat, usbint, usbfrnum; + unsigned int flbaseadd; + unsigned char sof; + unsigned short portsc1, portsc2; + + + usbcmd = uhci_readw(uhci, 0); + usbstat = uhci_readw(uhci, 2); + usbint = uhci_readw(uhci, 4); + usbfrnum = uhci_readw(uhci, 6); + flbaseadd = uhci_readl(uhci, 8); + sof = uhci_readb(uhci, 12); + portsc1 = uhci_readw(uhci, 16); + portsc2 = uhci_readw(uhci, 18); + + out += sprintf(out, " usbcmd = %04x %s%s%s%s%s%s%s%s\n", + usbcmd, + (usbcmd & USBCMD_MAXP) ? "Maxp64 " : "Maxp32 ", + (usbcmd & USBCMD_CF) ? "CF " : "", + (usbcmd & USBCMD_SWDBG) ? "SWDBG " : "", + (usbcmd & USBCMD_FGR) ? "FGR " : "", + (usbcmd & USBCMD_EGSM) ? "EGSM " : "", + (usbcmd & USBCMD_GRESET) ? "GRESET " : "", + (usbcmd & USBCMD_HCRESET) ? "HCRESET " : "", + (usbcmd & USBCMD_RS) ? "RS " : ""); + if (out - buf > len) + goto done; + + out += sprintf(out, " usbstat = %04x %s%s%s%s%s%s\n", + usbstat, + (usbstat & USBSTS_HCH) ? "HCHalted " : "", + (usbstat & USBSTS_HCPE) ? "HostControllerProcessError " : "", + (usbstat & USBSTS_HSE) ? "HostSystemError " : "", + (usbstat & USBSTS_RD) ? "ResumeDetect " : "", + (usbstat & USBSTS_ERROR) ? "USBError " : "", + (usbstat & USBSTS_USBINT) ? "USBINT " : ""); + if (out - buf > len) + goto done; + + out += sprintf(out, " usbint = %04x\n", usbint); + out += sprintf(out, " usbfrnum = (%d)%03x\n", (usbfrnum >> 10) & 1, + 0xfff & (4*(unsigned int)usbfrnum)); + out += sprintf(out, " flbaseadd = %08x\n", flbaseadd); + out += sprintf(out, " sof = %02x\n", sof); + if (out - buf > len) + goto done; + + out += uhci_show_sc(1, portsc1, out); + if (out - buf > len) + goto done; + + out += uhci_show_sc(2, portsc2, out); + if (out - buf > len) + goto done; + + out += sprintf(out, + "Most recent frame: %x (%d) Last ISO frame: %x (%d)\n", + uhci->frame_number, uhci->frame_number & 1023, + uhci->last_iso_frame, uhci->last_iso_frame & 1023); + +done: + if (out - buf > len) + out += sprintf(out, " ...\n"); + return out - buf; +} + +static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len) +{ + char *out = buf; + int i, j; + struct uhci_qh *qh; + struct uhci_td *td; + struct list_head *tmp, *head; + int nframes, nerrs; + __hc32 link; + __hc32 fsbr_link; + + static const char * const qh_names[] = { + "unlink", "iso", "int128", "int64", "int32", "int16", + "int8", "int4", "int2", "async", "term" + }; + + out += uhci_show_root_hub_state(uhci, out); + if (out - buf > len) + goto done; + out += sprintf(out, "HC status\n"); + out += uhci_show_status(uhci, out, len - (out - buf)); + if (out - buf > len) + goto tail; + + out += sprintf(out, "Periodic load table\n"); + for (i = 0; i < MAX_PHASE; ++i) { + out += sprintf(out, "\t%d", uhci->load[i]); + if (i % 8 == 7) + *out++ = '\n'; + } + out += sprintf(out, "Total: %d, #INT: %d, #ISO: %d\n", + uhci->total_load, + uhci_to_hcd(uhci)->self.bandwidth_int_reqs, + uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs); + if (debug <= 1) + goto tail; + + out += sprintf(out, "Frame List\n"); + nframes = 10; + nerrs = 0; + for (i = 0; i < UHCI_NUMFRAMES; ++i) { + __hc32 qh_dma; + + if (out - buf > len) + goto done; + j = 0; + td = uhci->frame_cpu[i]; + link = uhci->frame[i]; + if (!td) + goto check_link; + + if (nframes > 0) { + out += sprintf(out, "- Frame %d -> (%08x)\n", + i, hc32_to_cpu(uhci, link)); + j = 1; + } + + head = &td->fl_list; + tmp = head; + do { + td = list_entry(tmp, struct uhci_td, fl_list); + tmp = tmp->next; + if (link != LINK_TO_TD(uhci, td)) { + if (nframes > 0) { + out += sprintf(out, + " link does not match list entry!\n"); + if (out - buf > len) + goto done; + } else + ++nerrs; + } + if (nframes > 0) { + out += uhci_show_td(uhci, td, out, + len - (out - buf), 4); + if (out - buf > len) + goto tail; + } + link = td->link; + } while (tmp != head); + +check_link: + qh_dma = uhci_frame_skel_link(uhci, i); + if (link != qh_dma) { + if (nframes > 0) { + if (!j) { + out += sprintf(out, + "- Frame %d -> (%08x)\n", + i, hc32_to_cpu(uhci, link)); + j = 1; + } + out += sprintf(out, + " link does not match QH (%08x)!\n", + hc32_to_cpu(uhci, qh_dma)); + if (out - buf > len) + goto done; + } else + ++nerrs; + } + nframes -= j; + } + if (nerrs > 0) + out += sprintf(out, "Skipped %d bad links\n", nerrs); + + out += sprintf(out, "Skeleton QHs\n"); + + if (out - buf > len) + goto done; + + fsbr_link = 0; + for (i = 0; i < UHCI_NUM_SKELQH; ++i) { + int cnt = 0; + + qh = uhci->skelqh[i]; + out += sprintf(out, "- skel_%s_qh\n", qh_names[i]); + out += uhci_show_qh(uhci, qh, out, len - (out - buf), 4); + if (out - buf > len) + goto tail; + + /* Last QH is the Terminating QH, it's different */ + if (i == SKEL_TERM) { + if (qh_element(qh) != LINK_TO_TD(uhci, uhci->term_td)) { + out += sprintf(out, + " skel_term_qh element is not set to term_td!\n"); + if (out - buf > len) + goto done; + } + link = fsbr_link; + if (!link) + link = LINK_TO_QH(uhci, uhci->skel_term_qh); + goto check_qh_link; + } + + head = &qh->node; + tmp = head->next; + + while (tmp != head) { + qh = list_entry(tmp, struct uhci_qh, node); + tmp = tmp->next; + if (++cnt <= 10) { + out += uhci_show_qh(uhci, qh, out, + len - (out - buf), 4); + if (out - buf > len) + goto tail; + } + if (!fsbr_link && qh->skel >= SKEL_FSBR) + fsbr_link = LINK_TO_QH(uhci, qh); + } + if ((cnt -= 10) > 0) + out += sprintf(out, " Skipped %d QHs\n", cnt); + + link = UHCI_PTR_TERM(uhci); + if (i <= SKEL_ISO) + ; + else if (i < SKEL_ASYNC) + link = LINK_TO_QH(uhci, uhci->skel_async_qh); + else if (!uhci->fsbr_is_on) + ; + else + link = LINK_TO_QH(uhci, uhci->skel_term_qh); +check_qh_link: + if (qh->link != link) + out += sprintf(out, + " last QH not linked to next skeleton!\n"); + + if (out - buf > len) + goto done; + } + +done: + if (out - buf > len) + out += sprintf(out, " ...\n"); +tail: + return out - buf; +} + +#ifdef CONFIG_DEBUG_FS + +#define MAX_OUTPUT (64 * 1024) + +struct uhci_debug { + int size; + char *data; +}; + +static int uhci_debug_open(struct inode *inode, struct file *file) +{ + struct uhci_hcd *uhci = inode->i_private; + struct uhci_debug *up; + unsigned long flags; + + up = kmalloc(sizeof(*up), GFP_KERNEL); + if (!up) + return -ENOMEM; + + up->data = kmalloc(MAX_OUTPUT, GFP_KERNEL); + if (!up->data) { + kfree(up); + return -ENOMEM; + } + + up->size = 0; + spin_lock_irqsave(&uhci->lock, flags); + if (uhci->is_initialized) + up->size = uhci_sprint_schedule(uhci, up->data, + MAX_OUTPUT - EXTRA_SPACE); + spin_unlock_irqrestore(&uhci->lock, flags); + + file->private_data = up; + + return 0; +} + +static loff_t uhci_debug_lseek(struct file *file, loff_t off, int whence) +{ + struct uhci_debug *up; + loff_t new = -1; + + up = file->private_data; + + /* + * XXX: atomic 64bit seek access, but that needs to be fixed in the VFS + */ + switch (whence) { + case 0: + new = off; + break; + case 1: + new = file->f_pos + off; + break; + } + + if (new < 0 || new > up->size) + return -EINVAL; + + return (file->f_pos = new); +} + +static ssize_t uhci_debug_read(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct uhci_debug *up = file->private_data; + return simple_read_from_buffer(buf, nbytes, ppos, up->data, up->size); +} + +static int uhci_debug_release(struct inode *inode, struct file *file) +{ + struct uhci_debug *up = file->private_data; + + kfree(up->data); + kfree(up); + + return 0; +} + +static const struct file_operations uhci_debug_operations = { + .owner = THIS_MODULE, + .open = uhci_debug_open, + .llseek = uhci_debug_lseek, + .read = uhci_debug_read, + .release = uhci_debug_release, +}; +#define UHCI_DEBUG_OPS + +#endif /* CONFIG_DEBUG_FS */ + +#else /* DEBUG */ + +static inline void lprintk(char *buf) +{} + +static inline int uhci_show_qh(struct uhci_hcd *uhci, + struct uhci_qh *qh, char *buf, int len, int space) +{ + return 0; +} + +static inline int uhci_sprint_schedule(struct uhci_hcd *uhci, + char *buf, int len) +{ + return 0; +} + +#endif diff --git a/drivers/usb/host/uhci-grlib.c b/drivers/usb/host/uhci-grlib.c new file mode 100644 index 00000000..511bfc46 --- /dev/null +++ b/drivers/usb/host/uhci-grlib.c @@ -0,0 +1,208 @@ +/* + * UHCI HCD (Host Controller Driver) for GRLIB GRUSBHC + * + * Copyright (c) 2011 Jan Andersson + * + * This file is based on UHCI PCI HCD: + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com + * (C) Copyright 1999 Randy Dunlap + * (C) Copyright 1999 Georg Acher, acher@in.tum.de + * (C) Copyright 1999 Deti Fliegl, deti@fliegl.de + * (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch + * (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at + * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface + * support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). + * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) + * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu + */ + +#include +#include +#include + +static int uhci_grlib_init(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + /* + * Probe to determine the endianness of the controller. + * We know that bit 7 of the PORTSC1 register is always set + * and bit 15 is always clear. If uhci_readw() yields a value + * with bit 7 (0x80) turned on then the current little-endian + * setting is correct. Otherwise we assume the value was + * byte-swapped; hence the register interface and presumably + * also the descriptors are big-endian. + */ + if (!(uhci_readw(uhci, USBPORTSC1) & 0x80)) { + uhci->big_endian_mmio = 1; + uhci->big_endian_desc = 1; + } + + uhci->rh_numports = uhci_count_ports(hcd); + + /* Set up pointers to to generic functions */ + uhci->reset_hc = uhci_generic_reset_hc; + uhci->check_and_reset_hc = uhci_generic_check_and_reset_hc; + /* No special actions need to be taken for the functions below */ + uhci->configure_hc = NULL; + uhci->resume_detect_interrupts_are_broken = NULL; + uhci->global_suspend_mode_is_broken = NULL; + + /* Reset if the controller isn't already safely quiescent. */ + check_and_reset_hc(uhci); + return 0; +} + +static const struct hc_driver uhci_grlib_hc_driver = { + .description = hcd_name, + .product_desc = "GRLIB GRUSBHC UHCI Host Controller", + .hcd_priv_size = sizeof(struct uhci_hcd), + + /* Generic hardware linkage */ + .irq = uhci_irq, + .flags = HCD_MEMORY | HCD_USB11, + + /* Basic lifecycle operations */ + .reset = uhci_grlib_init, + .start = uhci_start, +#ifdef CONFIG_PM + .pci_suspend = NULL, + .pci_resume = NULL, + .bus_suspend = uhci_rh_suspend, + .bus_resume = uhci_rh_resume, +#endif + .stop = uhci_stop, + + .urb_enqueue = uhci_urb_enqueue, + .urb_dequeue = uhci_urb_dequeue, + + .endpoint_disable = uhci_hcd_endpoint_disable, + .get_frame_number = uhci_hcd_get_frame_number, + + .hub_status_data = uhci_hub_status_data, + .hub_control = uhci_hub_control, +}; + + +static int uhci_hcd_grlib_probe(struct platform_device *op) +{ + struct device_node *dn = op->dev.of_node; + struct usb_hcd *hcd; + struct uhci_hcd *uhci = NULL; + struct resource res; + int irq; + int rv; + + if (usb_disabled()) + return -ENODEV; + + dev_dbg(&op->dev, "initializing GRUSBHC UHCI USB Controller\n"); + + rv = of_address_to_resource(dn, 0, &res); + if (rv) + return rv; + + /* usb_create_hcd requires dma_mask != NULL */ + op->dev.dma_mask = &op->dev.coherent_dma_mask; + hcd = usb_create_hcd(&uhci_grlib_hc_driver, &op->dev, + "GRUSBHC UHCI USB"); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = res.start; + hcd->rsrc_len = resource_size(&res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + printk(KERN_ERR "%s: request_mem_region failed\n", __FILE__); + rv = -EBUSY; + goto err_rmr; + } + + irq = irq_of_parse_and_map(dn, 0); + if (irq == NO_IRQ) { + printk(KERN_ERR "%s: irq_of_parse_and_map failed\n", __FILE__); + rv = -EBUSY; + goto err_irq; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + printk(KERN_ERR "%s: ioremap failed\n", __FILE__); + rv = -ENOMEM; + goto err_ioremap; + } + + uhci = hcd_to_uhci(hcd); + + uhci->regs = hcd->regs; + + rv = usb_add_hcd(hcd, irq, 0); + if (rv) + goto err_uhci; + + return 0; + +err_uhci: + iounmap(hcd->regs); +err_ioremap: + irq_dispose_mapping(irq); +err_irq: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err_rmr: + usb_put_hcd(hcd); + + return rv; +} + +static int uhci_hcd_grlib_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + dev_set_drvdata(&op->dev, NULL); + + dev_dbg(&op->dev, "stopping GRLIB GRUSBHC UHCI USB Controller\n"); + + usb_remove_hcd(hcd); + + iounmap(hcd->regs); + irq_dispose_mapping(hcd->irq); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + + usb_put_hcd(hcd); + + return 0; +} + +/* Make sure the controller is quiescent and that we're not using it + * any more. This is mainly for the benefit of programs which, like kexec, + * expect the hardware to be idle: not doing DMA or generating IRQs. + * + * This routine may be called in a damaged or failing kernel. Hence we + * do not acquire the spinlock before shutting down the controller. + */ +static void uhci_hcd_grlib_shutdown(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + uhci_hc_died(hcd_to_uhci(hcd)); +} + +static const struct of_device_id uhci_hcd_grlib_of_match[] = { + { .name = "GAISLER_UHCI", }, + { .name = "01_027", }, + {}, +}; +MODULE_DEVICE_TABLE(of, uhci_hcd_grlib_of_match); + + +static struct platform_driver uhci_grlib_driver = { + .probe = uhci_hcd_grlib_probe, + .remove = uhci_hcd_grlib_remove, + .shutdown = uhci_hcd_grlib_shutdown, + .driver = { + .name = "grlib-uhci", + .owner = THIS_MODULE, + .of_match_table = uhci_hcd_grlib_of_match, + }, +}; diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c new file mode 100644 index 00000000..4a86b637 --- /dev/null +++ b/drivers/usb/host/uhci-hcd.c @@ -0,0 +1,939 @@ +/* + * Universal Host Controller Interface driver for USB. + * + * Maintainer: Alan Stern + * + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com + * (C) Copyright 1999 Randy Dunlap + * (C) Copyright 1999 Georg Acher, acher@in.tum.de + * (C) Copyright 1999 Deti Fliegl, deti@fliegl.de + * (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch + * (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at + * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface + * support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). + * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) + * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu + * + * Intel documents this fairly well, and as far as I know there + * are no royalties or anything like that, but even so there are + * people who decided that they want to do the same thing in a + * completely different way. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "uhci-hcd.h" + +/* + * Version Information + */ +#define DRIVER_AUTHOR \ + "Linus 'Frodo Rabbit' Torvalds, Johannes Erdfelt, " \ + "Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, " \ + "Roman Weissgaerber, Alan Stern" +#define DRIVER_DESC "USB Universal Host Controller Interface driver" + +/* for flakey hardware, ignore overcurrent indicators */ +static bool ignore_oc; +module_param(ignore_oc, bool, S_IRUGO); +MODULE_PARM_DESC(ignore_oc, "ignore hardware overcurrent indications"); + +/* + * debug = 0, no debugging messages + * debug = 1, dump failed URBs except for stalls + * debug = 2, dump all failed URBs (including stalls) + * show all queues in /sys/kernel/debug/uhci/[pci_addr] + * debug = 3, show all TDs in URBs when dumping + */ +#ifdef DEBUG +#define DEBUG_CONFIGURED 1 +static int debug = 1; +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level"); + +#else +#define DEBUG_CONFIGURED 0 +#define debug 0 +#endif + +static char *errbuf; +#define ERRBUF_LEN (32 * 1024) + +static struct kmem_cache *uhci_up_cachep; /* urb_priv */ + +static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state); +static void wakeup_rh(struct uhci_hcd *uhci); +static void uhci_get_current_frame_number(struct uhci_hcd *uhci); + +/* + * Calculate the link pointer DMA value for the first Skeleton QH in a frame. + */ +static __hc32 uhci_frame_skel_link(struct uhci_hcd *uhci, int frame) +{ + int skelnum; + + /* + * The interrupt queues will be interleaved as evenly as possible. + * There's not much to be done about period-1 interrupts; they have + * to occur in every frame. But we can schedule period-2 interrupts + * in odd-numbered frames, period-4 interrupts in frames congruent + * to 2 (mod 4), and so on. This way each frame only has two + * interrupt QHs, which will help spread out bandwidth utilization. + * + * ffs (Find First bit Set) does exactly what we need: + * 1,3,5,... => ffs = 0 => use period-2 QH = skelqh[8], + * 2,6,10,... => ffs = 1 => use period-4 QH = skelqh[7], etc. + * ffs >= 7 => not on any high-period queue, so use + * period-1 QH = skelqh[9]. + * Add in UHCI_NUMFRAMES to insure at least one bit is set. + */ + skelnum = 8 - (int) __ffs(frame | UHCI_NUMFRAMES); + if (skelnum <= 1) + skelnum = 9; + return LINK_TO_QH(uhci, uhci->skelqh[skelnum]); +} + +#include "uhci-debug.c" +#include "uhci-q.c" +#include "uhci-hub.c" + +/* + * Finish up a host controller reset and update the recorded state. + */ +static void finish_reset(struct uhci_hcd *uhci) +{ + int port; + + /* HCRESET doesn't affect the Suspend, Reset, and Resume Detect + * bits in the port status and control registers. + * We have to clear them by hand. + */ + for (port = 0; port < uhci->rh_numports; ++port) + uhci_writew(uhci, 0, USBPORTSC1 + (port * 2)); + + uhci->port_c_suspend = uhci->resuming_ports = 0; + uhci->rh_state = UHCI_RH_RESET; + uhci->is_stopped = UHCI_IS_STOPPED; + clear_bit(HCD_FLAG_POLL_RH, &uhci_to_hcd(uhci)->flags); +} + +/* + * Last rites for a defunct/nonfunctional controller + * or one we don't want to use any more. + */ +static void uhci_hc_died(struct uhci_hcd *uhci) +{ + uhci_get_current_frame_number(uhci); + uhci->reset_hc(uhci); + finish_reset(uhci); + uhci->dead = 1; + + /* The current frame may already be partway finished */ + ++uhci->frame_number; +} + +/* + * Initialize a controller that was newly discovered or has lost power + * or otherwise been reset while it was suspended. In none of these cases + * can we be sure of its previous state. + */ +static void check_and_reset_hc(struct uhci_hcd *uhci) +{ + if (uhci->check_and_reset_hc(uhci)) + finish_reset(uhci); +} + +#if defined(CONFIG_USB_UHCI_SUPPORT_NON_PCI_HC) +/* + * The two functions below are generic reset functions that are used on systems + * that do not have keyboard and mouse legacy support. We assume that we are + * running on such a system if CONFIG_USB_UHCI_SUPPORT_NON_PCI_HC is defined. + */ + +/* + * Make sure the controller is completely inactive, unable to + * generate interrupts or do DMA. + */ +static void uhci_generic_reset_hc(struct uhci_hcd *uhci) +{ + /* Reset the HC - this will force us to get a + * new notification of any already connected + * ports due to the virtual disconnect that it + * implies. + */ + uhci_writew(uhci, USBCMD_HCRESET, USBCMD); + mb(); + udelay(5); + if (uhci_readw(uhci, USBCMD) & USBCMD_HCRESET) + dev_warn(uhci_dev(uhci), "HCRESET not completed yet!\n"); + + /* Just to be safe, disable interrupt requests and + * make sure the controller is stopped. + */ + uhci_writew(uhci, 0, USBINTR); + uhci_writew(uhci, 0, USBCMD); +} + +/* + * Initialize a controller that was newly discovered or has just been + * resumed. In either case we can't be sure of its previous state. + * + * Returns: 1 if the controller was reset, 0 otherwise. + */ +static int uhci_generic_check_and_reset_hc(struct uhci_hcd *uhci) +{ + unsigned int cmd, intr; + + /* + * When restarting a suspended controller, we expect all the + * settings to be the same as we left them: + * + * Controller is stopped and configured with EGSM set; + * No interrupts enabled except possibly Resume Detect. + * + * If any of these conditions are violated we do a complete reset. + */ + + cmd = uhci_readw(uhci, USBCMD); + if ((cmd & USBCMD_RS) || !(cmd & USBCMD_CF) || !(cmd & USBCMD_EGSM)) { + dev_dbg(uhci_dev(uhci), "%s: cmd = 0x%04x\n", + __func__, cmd); + goto reset_needed; + } + + intr = uhci_readw(uhci, USBINTR); + if (intr & (~USBINTR_RESUME)) { + dev_dbg(uhci_dev(uhci), "%s: intr = 0x%04x\n", + __func__, intr); + goto reset_needed; + } + return 0; + +reset_needed: + dev_dbg(uhci_dev(uhci), "Performing full reset\n"); + uhci_generic_reset_hc(uhci); + return 1; +} +#endif /* CONFIG_USB_UHCI_SUPPORT_NON_PCI_HC */ + +/* + * Store the basic register settings needed by the controller. + */ +static void configure_hc(struct uhci_hcd *uhci) +{ + /* Set the frame length to the default: 1 ms exactly */ + uhci_writeb(uhci, USBSOF_DEFAULT, USBSOF); + + /* Store the frame list base address */ + uhci_writel(uhci, uhci->frame_dma_handle, USBFLBASEADD); + + /* Set the current frame number */ + uhci_writew(uhci, uhci->frame_number & UHCI_MAX_SOF_NUMBER, + USBFRNUM); + + /* perform any arch/bus specific configuration */ + if (uhci->configure_hc) + uhci->configure_hc(uhci); +} + +static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci) +{ + /* If we have to ignore overcurrent events then almost by definition + * we can't depend on resume-detect interrupts. */ + if (ignore_oc) + return 1; + + return uhci->resume_detect_interrupts_are_broken ? + uhci->resume_detect_interrupts_are_broken(uhci) : 0; +} + +static int global_suspend_mode_is_broken(struct uhci_hcd *uhci) +{ + return uhci->global_suspend_mode_is_broken ? + uhci->global_suspend_mode_is_broken(uhci) : 0; +} + +static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state) +__releases(uhci->lock) +__acquires(uhci->lock) +{ + int auto_stop; + int int_enable, egsm_enable, wakeup_enable; + struct usb_device *rhdev = uhci_to_hcd(uhci)->self.root_hub; + + auto_stop = (new_state == UHCI_RH_AUTO_STOPPED); + dev_dbg(&rhdev->dev, "%s%s\n", __func__, + (auto_stop ? " (auto-stop)" : "")); + + /* Start off by assuming Resume-Detect interrupts and EGSM work + * and that remote wakeups should be enabled. + */ + egsm_enable = USBCMD_EGSM; + int_enable = USBINTR_RESUME; + wakeup_enable = 1; + + /* + * In auto-stop mode, we must be able to detect new connections. + * The user can force us to poll by disabling remote wakeup; + * otherwise we will use the EGSM/RD mechanism. + */ + if (auto_stop) { + if (!device_may_wakeup(&rhdev->dev)) + egsm_enable = int_enable = 0; + } + +#ifdef CONFIG_PM + /* + * In bus-suspend mode, we use the wakeup setting specified + * for the root hub. + */ + else { + if (!rhdev->do_remote_wakeup) + wakeup_enable = 0; + } +#endif + + /* + * UHCI doesn't distinguish between wakeup requests from downstream + * devices and local connect/disconnect events. There's no way to + * enable one without the other; both are controlled by EGSM. Thus + * if wakeups are disallowed then EGSM must be turned off -- in which + * case remote wakeup requests from downstream during system sleep + * will be lost. + * + * In addition, if EGSM is broken then we can't use it. Likewise, + * if Resume-Detect interrupts are broken then we can't use them. + * + * Finally, neither EGSM nor RD is useful by itself. Without EGSM, + * the RD status bit will never get set. Without RD, the controller + * won't generate interrupts to tell the system about wakeup events. + */ + if (!wakeup_enable || global_suspend_mode_is_broken(uhci) || + resume_detect_interrupts_are_broken(uhci)) + egsm_enable = int_enable = 0; + + uhci->RD_enable = !!int_enable; + uhci_writew(uhci, int_enable, USBINTR); + uhci_writew(uhci, egsm_enable | USBCMD_CF, USBCMD); + mb(); + udelay(5); + + /* If we're auto-stopping then no devices have been attached + * for a while, so there shouldn't be any active URBs and the + * controller should stop after a few microseconds. Otherwise + * we will give the controller one frame to stop. + */ + if (!auto_stop && !(uhci_readw(uhci, USBSTS) & USBSTS_HCH)) { + uhci->rh_state = UHCI_RH_SUSPENDING; + spin_unlock_irq(&uhci->lock); + msleep(1); + spin_lock_irq(&uhci->lock); + if (uhci->dead) + return; + } + if (!(uhci_readw(uhci, USBSTS) & USBSTS_HCH)) + dev_warn(uhci_dev(uhci), "Controller not stopped yet!\n"); + + uhci_get_current_frame_number(uhci); + + uhci->rh_state = new_state; + uhci->is_stopped = UHCI_IS_STOPPED; + + /* + * If remote wakeup is enabled but either EGSM or RD interrupts + * doesn't work, then we won't get an interrupt when a wakeup event + * occurs. Thus the suspended root hub needs to be polled. + */ + if (wakeup_enable && (!int_enable || !egsm_enable)) + set_bit(HCD_FLAG_POLL_RH, &uhci_to_hcd(uhci)->flags); + else + clear_bit(HCD_FLAG_POLL_RH, &uhci_to_hcd(uhci)->flags); + + uhci_scan_schedule(uhci); + uhci_fsbr_off(uhci); +} + +static void start_rh(struct uhci_hcd *uhci) +{ + uhci->is_stopped = 0; + + /* Mark it configured and running with a 64-byte max packet. + * All interrupts are enabled, even though RESUME won't do anything. + */ + uhci_writew(uhci, USBCMD_RS | USBCMD_CF | USBCMD_MAXP, USBCMD); + uhci_writew(uhci, USBINTR_TIMEOUT | USBINTR_RESUME | + USBINTR_IOC | USBINTR_SP, USBINTR); + mb(); + uhci->rh_state = UHCI_RH_RUNNING; + set_bit(HCD_FLAG_POLL_RH, &uhci_to_hcd(uhci)->flags); +} + +static void wakeup_rh(struct uhci_hcd *uhci) +__releases(uhci->lock) +__acquires(uhci->lock) +{ + dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev, + "%s%s\n", __func__, + uhci->rh_state == UHCI_RH_AUTO_STOPPED ? + " (auto-start)" : ""); + + /* If we are auto-stopped then no devices are attached so there's + * no need for wakeup signals. Otherwise we send Global Resume + * for 20 ms. + */ + if (uhci->rh_state == UHCI_RH_SUSPENDED) { + unsigned egsm; + + /* Keep EGSM on if it was set before */ + egsm = uhci_readw(uhci, USBCMD) & USBCMD_EGSM; + uhci->rh_state = UHCI_RH_RESUMING; + uhci_writew(uhci, USBCMD_FGR | USBCMD_CF | egsm, USBCMD); + spin_unlock_irq(&uhci->lock); + msleep(20); + spin_lock_irq(&uhci->lock); + if (uhci->dead) + return; + + /* End Global Resume and wait for EOP to be sent */ + uhci_writew(uhci, USBCMD_CF, USBCMD); + mb(); + udelay(4); + if (uhci_readw(uhci, USBCMD) & USBCMD_FGR) + dev_warn(uhci_dev(uhci), "FGR not stopped yet!\n"); + } + + start_rh(uhci); + + /* Restart root hub polling */ + mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies); +} + +static irqreturn_t uhci_irq(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + unsigned short status; + + /* + * Read the interrupt status, and write it back to clear the + * interrupt cause. Contrary to the UHCI specification, the + * "HC Halted" status bit is persistent: it is RO, not R/WC. + */ + status = uhci_readw(uhci, USBSTS); + if (!(status & ~USBSTS_HCH)) /* shared interrupt, not mine */ + return IRQ_NONE; + uhci_writew(uhci, status, USBSTS); /* Clear it */ + + spin_lock(&uhci->lock); + if (unlikely(!uhci->is_initialized)) /* not yet configured */ + goto done; + + if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) { + if (status & USBSTS_HSE) + dev_err(uhci_dev(uhci), + "host system error, PCI problems?\n"); + if (status & USBSTS_HCPE) + dev_err(uhci_dev(uhci), + "host controller process error, something bad happened!\n"); + if (status & USBSTS_HCH) { + if (uhci->rh_state >= UHCI_RH_RUNNING) { + dev_err(uhci_dev(uhci), + "host controller halted, very bad!\n"); + if (debug > 1 && errbuf) { + /* Print the schedule for debugging */ + uhci_sprint_schedule(uhci, errbuf, + ERRBUF_LEN - EXTRA_SPACE); + lprintk(errbuf); + } + uhci_hc_died(uhci); + usb_hc_died(hcd); + + /* Force a callback in case there are + * pending unlinks */ + mod_timer(&hcd->rh_timer, jiffies); + } + } + } + + if (status & USBSTS_RD) { + spin_unlock(&uhci->lock); + usb_hcd_poll_rh_status(hcd); + } else { + uhci_scan_schedule(uhci); + done: + spin_unlock(&uhci->lock); + } + + return IRQ_HANDLED; +} + +/* + * Store the current frame number in uhci->frame_number if the controller + * is running. Expand from 11 bits (of which we use only 10) to a + * full-sized integer. + * + * Like many other parts of the driver, this code relies on being polled + * more than once per second as long as the controller is running. + */ +static void uhci_get_current_frame_number(struct uhci_hcd *uhci) +{ + if (!uhci->is_stopped) { + unsigned delta; + + delta = (uhci_readw(uhci, USBFRNUM) - uhci->frame_number) & + (UHCI_NUMFRAMES - 1); + uhci->frame_number += delta; + } +} + +/* + * De-allocate all resources + */ +static void release_uhci(struct uhci_hcd *uhci) +{ + int i; + + if (DEBUG_CONFIGURED) { + spin_lock_irq(&uhci->lock); + uhci->is_initialized = 0; + spin_unlock_irq(&uhci->lock); + + debugfs_remove(uhci->dentry); + } + + for (i = 0; i < UHCI_NUM_SKELQH; i++) + uhci_free_qh(uhci, uhci->skelqh[i]); + + uhci_free_td(uhci, uhci->term_td); + + dma_pool_destroy(uhci->qh_pool); + + dma_pool_destroy(uhci->td_pool); + + kfree(uhci->frame_cpu); + + dma_free_coherent(uhci_dev(uhci), + UHCI_NUMFRAMES * sizeof(*uhci->frame), + uhci->frame, uhci->frame_dma_handle); +} + +/* + * Allocate a frame list, and then setup the skeleton + * + * The hardware doesn't really know any difference + * in the queues, but the order does matter for the + * protocols higher up. The order in which the queues + * are encountered by the hardware is: + * + * - All isochronous events are handled before any + * of the queues. We don't do that here, because + * we'll create the actual TD entries on demand. + * - The first queue is the high-period interrupt queue. + * - The second queue is the period-1 interrupt and async + * (low-speed control, full-speed control, then bulk) queue. + * - The third queue is the terminating bandwidth reclamation queue, + * which contains no members, loops back to itself, and is present + * only when FSBR is on and there are no full-speed control or bulk QHs. + */ +static int uhci_start(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + int retval = -EBUSY; + int i; + struct dentry __maybe_unused *dentry; + + hcd->uses_new_polling = 1; + /* Accept arbitrarily long scatter-gather lists */ + if (!(hcd->driver->flags & HCD_LOCAL_MEM)) + hcd->self.sg_tablesize = ~0; + + spin_lock_init(&uhci->lock); + setup_timer(&uhci->fsbr_timer, uhci_fsbr_timeout, + (unsigned long) uhci); + INIT_LIST_HEAD(&uhci->idle_qh_list); + init_waitqueue_head(&uhci->waitqh); + +#ifdef UHCI_DEBUG_OPS + dentry = debugfs_create_file(hcd->self.bus_name, + S_IFREG|S_IRUGO|S_IWUSR, uhci_debugfs_root, + uhci, &uhci_debug_operations); + if (!dentry) { + dev_err(uhci_dev(uhci), "couldn't create uhci debugfs entry\n"); + return -ENOMEM; + } + uhci->dentry = dentry; +#endif + + uhci->frame = dma_alloc_coherent(uhci_dev(uhci), + UHCI_NUMFRAMES * sizeof(*uhci->frame), + &uhci->frame_dma_handle, 0); + if (!uhci->frame) { + dev_err(uhci_dev(uhci), + "unable to allocate consistent memory for frame list\n"); + goto err_alloc_frame; + } + memset(uhci->frame, 0, UHCI_NUMFRAMES * sizeof(*uhci->frame)); + + uhci->frame_cpu = kcalloc(UHCI_NUMFRAMES, sizeof(*uhci->frame_cpu), + GFP_KERNEL); + if (!uhci->frame_cpu) { + dev_err(uhci_dev(uhci), + "unable to allocate memory for frame pointers\n"); + goto err_alloc_frame_cpu; + } + + uhci->td_pool = dma_pool_create("uhci_td", uhci_dev(uhci), + sizeof(struct uhci_td), 16, 0); + if (!uhci->td_pool) { + dev_err(uhci_dev(uhci), "unable to create td dma_pool\n"); + goto err_create_td_pool; + } + + uhci->qh_pool = dma_pool_create("uhci_qh", uhci_dev(uhci), + sizeof(struct uhci_qh), 16, 0); + if (!uhci->qh_pool) { + dev_err(uhci_dev(uhci), "unable to create qh dma_pool\n"); + goto err_create_qh_pool; + } + + uhci->term_td = uhci_alloc_td(uhci); + if (!uhci->term_td) { + dev_err(uhci_dev(uhci), "unable to allocate terminating TD\n"); + goto err_alloc_term_td; + } + + for (i = 0; i < UHCI_NUM_SKELQH; i++) { + uhci->skelqh[i] = uhci_alloc_qh(uhci, NULL, NULL); + if (!uhci->skelqh[i]) { + dev_err(uhci_dev(uhci), "unable to allocate QH\n"); + goto err_alloc_skelqh; + } + } + + /* + * 8 Interrupt queues; link all higher int queues to int1 = async + */ + for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i) + uhci->skelqh[i]->link = LINK_TO_QH(uhci, uhci->skel_async_qh); + uhci->skel_async_qh->link = UHCI_PTR_TERM(uhci); + uhci->skel_term_qh->link = LINK_TO_QH(uhci, uhci->skel_term_qh); + + /* This dummy TD is to work around a bug in Intel PIIX controllers */ + uhci_fill_td(uhci, uhci->term_td, 0, uhci_explen(0) | + (0x7f << TD_TOKEN_DEVADDR_SHIFT) | USB_PID_IN, 0); + uhci->term_td->link = UHCI_PTR_TERM(uhci); + uhci->skel_async_qh->element = uhci->skel_term_qh->element = + LINK_TO_TD(uhci, uhci->term_td); + + /* + * Fill the frame list: make all entries point to the proper + * interrupt queue. + */ + for (i = 0; i < UHCI_NUMFRAMES; i++) { + + /* Only place we don't use the frame list routines */ + uhci->frame[i] = uhci_frame_skel_link(uhci, i); + } + + /* + * Some architectures require a full mb() to enforce completion of + * the memory writes above before the I/O transfers in configure_hc(). + */ + mb(); + + spin_lock_irq(&uhci->lock); + configure_hc(uhci); + uhci->is_initialized = 1; + start_rh(uhci); + spin_unlock_irq(&uhci->lock); + return 0; + +/* + * error exits: + */ +err_alloc_skelqh: + for (i = 0; i < UHCI_NUM_SKELQH; i++) { + if (uhci->skelqh[i]) + uhci_free_qh(uhci, uhci->skelqh[i]); + } + + uhci_free_td(uhci, uhci->term_td); + +err_alloc_term_td: + dma_pool_destroy(uhci->qh_pool); + +err_create_qh_pool: + dma_pool_destroy(uhci->td_pool); + +err_create_td_pool: + kfree(uhci->frame_cpu); + +err_alloc_frame_cpu: + dma_free_coherent(uhci_dev(uhci), + UHCI_NUMFRAMES * sizeof(*uhci->frame), + uhci->frame, uhci->frame_dma_handle); + +err_alloc_frame: + debugfs_remove(uhci->dentry); + + return retval; +} + +static void uhci_stop(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + spin_lock_irq(&uhci->lock); + if (HCD_HW_ACCESSIBLE(hcd) && !uhci->dead) + uhci_hc_died(uhci); + uhci_scan_schedule(uhci); + spin_unlock_irq(&uhci->lock); + synchronize_irq(hcd->irq); + + del_timer_sync(&uhci->fsbr_timer); + release_uhci(uhci); +} + +#ifdef CONFIG_PM +static int uhci_rh_suspend(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + int rc = 0; + + spin_lock_irq(&uhci->lock); + if (!HCD_HW_ACCESSIBLE(hcd)) + rc = -ESHUTDOWN; + else if (uhci->dead) + ; /* Dead controllers tell no tales */ + + /* Once the controller is stopped, port resumes that are already + * in progress won't complete. Hence if remote wakeup is enabled + * for the root hub and any ports are in the middle of a resume or + * remote wakeup, we must fail the suspend. + */ + else if (hcd->self.root_hub->do_remote_wakeup && + uhci->resuming_ports) { + dev_dbg(uhci_dev(uhci), + "suspend failed because a port is resuming\n"); + rc = -EBUSY; + } else + suspend_rh(uhci, UHCI_RH_SUSPENDED); + spin_unlock_irq(&uhci->lock); + return rc; +} + +static int uhci_rh_resume(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + int rc = 0; + + spin_lock_irq(&uhci->lock); + if (!HCD_HW_ACCESSIBLE(hcd)) + rc = -ESHUTDOWN; + else if (!uhci->dead) + wakeup_rh(uhci); + spin_unlock_irq(&uhci->lock); + return rc; +} + +#endif + +/* Wait until a particular device/endpoint's QH is idle, and free it */ +static void uhci_hcd_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *hep) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + struct uhci_qh *qh; + + spin_lock_irq(&uhci->lock); + qh = (struct uhci_qh *) hep->hcpriv; + if (qh == NULL) + goto done; + + while (qh->state != QH_STATE_IDLE) { + ++uhci->num_waiting; + spin_unlock_irq(&uhci->lock); + wait_event_interruptible(uhci->waitqh, + qh->state == QH_STATE_IDLE); + spin_lock_irq(&uhci->lock); + --uhci->num_waiting; + } + + uhci_free_qh(uhci, qh); +done: + spin_unlock_irq(&uhci->lock); +} + +static int uhci_hcd_get_frame_number(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + unsigned frame_number; + unsigned delta; + + /* Minimize latency by avoiding the spinlock */ + frame_number = uhci->frame_number; + barrier(); + delta = (uhci_readw(uhci, USBFRNUM) - frame_number) & + (UHCI_NUMFRAMES - 1); + return frame_number + delta; +} + +/* Determines number of ports on controller */ +static int uhci_count_ports(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + unsigned io_size = (unsigned) hcd->rsrc_len; + int port; + + /* The UHCI spec says devices must have 2 ports, and goes on to say + * they may have more but gives no way to determine how many there + * are. However according to the UHCI spec, Bit 7 of the port + * status and control register is always set to 1. So we try to + * use this to our advantage. Another common failure mode when + * a nonexistent register is addressed is to return all ones, so + * we test for that also. + */ + for (port = 0; port < (io_size - USBPORTSC1) / 2; port++) { + unsigned int portstatus; + + portstatus = uhci_readw(uhci, USBPORTSC1 + (port * 2)); + if (!(portstatus & 0x0080) || portstatus == 0xffff) + break; + } + if (debug) + dev_info(uhci_dev(uhci), "detected %d ports\n", port); + + /* Anything greater than 7 is weird so we'll ignore it. */ + if (port > UHCI_RH_MAXCHILD) { + dev_info(uhci_dev(uhci), + "port count misdetected? forcing to 2 ports\n"); + port = 2; + } + + return port; +} + +static const char hcd_name[] = "uhci_hcd"; + +#ifdef CONFIG_PCI +#include "uhci-pci.c" +#define PCI_DRIVER uhci_pci_driver +#endif + +#ifdef CONFIG_SPARC_LEON +#include "uhci-grlib.c" +#define PLATFORM_DRIVER uhci_grlib_driver +#endif + +#ifdef CONFIG_USB_UHCI_PLATFORM +#include "uhci-platform.c" +#define PLATFORM_DRIVER uhci_platform_driver +#endif + +#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) +#error "missing bus glue for uhci-hcd" +#endif + +static int __init uhci_hcd_init(void) +{ + int retval = -ENOMEM; + + if (usb_disabled()) + return -ENODEV; + + printk(KERN_INFO "uhci_hcd: " DRIVER_DESC "%s\n", + ignore_oc ? ", overcurrent ignored" : ""); + set_bit(USB_UHCI_LOADED, &usb_hcds_loaded); + + if (DEBUG_CONFIGURED) { + errbuf = kmalloc(ERRBUF_LEN, GFP_KERNEL); + if (!errbuf) + goto errbuf_failed; + uhci_debugfs_root = debugfs_create_dir("uhci", usb_debug_root); + if (!uhci_debugfs_root) + goto debug_failed; + } + + uhci_up_cachep = kmem_cache_create("uhci_urb_priv", + sizeof(struct urb_priv), 0, 0, NULL); + if (!uhci_up_cachep) + goto up_failed; + +#ifdef PLATFORM_DRIVER + retval = platform_driver_register(&PLATFORM_DRIVER); + if (retval < 0) + goto clean0; +#endif + +#ifdef PCI_DRIVER + retval = pci_register_driver(&PCI_DRIVER); + if (retval < 0) + goto clean1; +#endif + + return 0; + +#ifdef PCI_DRIVER +clean1: +#endif +#ifdef PLATFORM_DRIVER + platform_driver_unregister(&PLATFORM_DRIVER); +clean0: +#endif + kmem_cache_destroy(uhci_up_cachep); + +up_failed: + debugfs_remove(uhci_debugfs_root); + +debug_failed: + kfree(errbuf); + +errbuf_failed: + + clear_bit(USB_UHCI_LOADED, &usb_hcds_loaded); + return retval; +} + +static void __exit uhci_hcd_cleanup(void) +{ +#ifdef PLATFORM_DRIVER + platform_driver_unregister(&PLATFORM_DRIVER); +#endif +#ifdef PCI_DRIVER + pci_unregister_driver(&PCI_DRIVER); +#endif + kmem_cache_destroy(uhci_up_cachep); + debugfs_remove(uhci_debugfs_root); + kfree(errbuf); + clear_bit(USB_UHCI_LOADED, &usb_hcds_loaded); +} + +module_init(uhci_hcd_init); +module_exit(uhci_hcd_cleanup); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h new file mode 100644 index 00000000..6f986d82 --- /dev/null +++ b/drivers/usb/host/uhci-hcd.h @@ -0,0 +1,660 @@ +#ifndef __LINUX_UHCI_HCD_H +#define __LINUX_UHCI_HCD_H + +#include +#include + +#define usb_packetid(pipe) (usb_pipein(pipe) ? USB_PID_IN : USB_PID_OUT) +#define PIPE_DEVEP_MASK 0x0007ff00 + + +/* + * Universal Host Controller Interface data structures and defines + */ + +/* Command register */ +#define USBCMD 0 +#define USBCMD_RS 0x0001 /* Run/Stop */ +#define USBCMD_HCRESET 0x0002 /* Host reset */ +#define USBCMD_GRESET 0x0004 /* Global reset */ +#define USBCMD_EGSM 0x0008 /* Global Suspend Mode */ +#define USBCMD_FGR 0x0010 /* Force Global Resume */ +#define USBCMD_SWDBG 0x0020 /* SW Debug mode */ +#define USBCMD_CF 0x0040 /* Config Flag (sw only) */ +#define USBCMD_MAXP 0x0080 /* Max Packet (0 = 32, 1 = 64) */ + +/* Status register */ +#define USBSTS 2 +#define USBSTS_USBINT 0x0001 /* Interrupt due to IOC */ +#define USBSTS_ERROR 0x0002 /* Interrupt due to error */ +#define USBSTS_RD 0x0004 /* Resume Detect */ +#define USBSTS_HSE 0x0008 /* Host System Error: PCI problems */ +#define USBSTS_HCPE 0x0010 /* Host Controller Process Error: + * the schedule is buggy */ +#define USBSTS_HCH 0x0020 /* HC Halted */ + +/* Interrupt enable register */ +#define USBINTR 4 +#define USBINTR_TIMEOUT 0x0001 /* Timeout/CRC error enable */ +#define USBINTR_RESUME 0x0002 /* Resume interrupt enable */ +#define USBINTR_IOC 0x0004 /* Interrupt On Complete enable */ +#define USBINTR_SP 0x0008 /* Short packet interrupt enable */ + +#define USBFRNUM 6 +#define USBFLBASEADD 8 +#define USBSOF 12 +#define USBSOF_DEFAULT 64 /* Frame length is exactly 1 ms */ + +/* USB port status and control registers */ +#define USBPORTSC1 16 +#define USBPORTSC2 18 +#define USBPORTSC_CCS 0x0001 /* Current Connect Status + * ("device present") */ +#define USBPORTSC_CSC 0x0002 /* Connect Status Change */ +#define USBPORTSC_PE 0x0004 /* Port Enable */ +#define USBPORTSC_PEC 0x0008 /* Port Enable Change */ +#define USBPORTSC_DPLUS 0x0010 /* D+ high (line status) */ +#define USBPORTSC_DMINUS 0x0020 /* D- high (line status) */ +#define USBPORTSC_RD 0x0040 /* Resume Detect */ +#define USBPORTSC_RES1 0x0080 /* reserved, always 1 */ +#define USBPORTSC_LSDA 0x0100 /* Low Speed Device Attached */ +#define USBPORTSC_PR 0x0200 /* Port Reset */ +/* OC and OCC from Intel 430TX and later (not UHCI 1.1d spec) */ +#define USBPORTSC_OC 0x0400 /* Over Current condition */ +#define USBPORTSC_OCC 0x0800 /* Over Current Change R/WC */ +#define USBPORTSC_SUSP 0x1000 /* Suspend */ +#define USBPORTSC_RES2 0x2000 /* reserved, write zeroes */ +#define USBPORTSC_RES3 0x4000 /* reserved, write zeroes */ +#define USBPORTSC_RES4 0x8000 /* reserved, write zeroes */ + +/* PCI legacy support register */ +#define USBLEGSUP 0xc0 +#define USBLEGSUP_DEFAULT 0x2000 /* only PIRQ enable set */ +#define USBLEGSUP_RWC 0x8f00 /* the R/WC bits */ +#define USBLEGSUP_RO 0x5040 /* R/O and reserved bits */ + +/* PCI Intel-specific resume-enable register */ +#define USBRES_INTEL 0xc4 +#define USBPORT1EN 0x01 +#define USBPORT2EN 0x02 + +#define UHCI_PTR_BITS(uhci) cpu_to_hc32((uhci), 0x000F) +#define UHCI_PTR_TERM(uhci) cpu_to_hc32((uhci), 0x0001) +#define UHCI_PTR_QH(uhci) cpu_to_hc32((uhci), 0x0002) +#define UHCI_PTR_DEPTH(uhci) cpu_to_hc32((uhci), 0x0004) +#define UHCI_PTR_BREADTH(uhci) cpu_to_hc32((uhci), 0x0000) + +#define UHCI_NUMFRAMES 1024 /* in the frame list [array] */ +#define UHCI_MAX_SOF_NUMBER 2047 /* in an SOF packet */ +#define CAN_SCHEDULE_FRAMES 1000 /* how far in the future frames + * can be scheduled */ +#define MAX_PHASE 32 /* Periodic scheduling length */ + +/* When no queues need Full-Speed Bandwidth Reclamation, + * delay this long before turning FSBR off */ +#define FSBR_OFF_DELAY msecs_to_jiffies(10) + +/* If a queue hasn't advanced after this much time, assume it is stuck */ +#define QH_WAIT_TIMEOUT msecs_to_jiffies(200) + + +/* + * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to + * __leXX (normally) or __beXX (given UHCI_BIG_ENDIAN_DESC), depending on + * the host controller implementation. + * + * To facilitate the strongest possible byte-order checking from "sparse" + * and so on, we use __leXX unless that's not practical. + */ +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_DESC +typedef __u32 __bitwise __hc32; +typedef __u16 __bitwise __hc16; +#else +#define __hc32 __le32 +#define __hc16 __le16 +#endif + +/* + * Queue Headers + */ + +/* + * One role of a QH is to hold a queue of TDs for some endpoint. One QH goes + * with each endpoint, and qh->element (updated by the HC) is either: + * - the next unprocessed TD in the endpoint's queue, or + * - UHCI_PTR_TERM (when there's no more traffic for this endpoint). + * + * The other role of a QH is to serve as a "skeleton" framelist entry, so we + * can easily splice a QH for some endpoint into the schedule at the right + * place. Then qh->element is UHCI_PTR_TERM. + * + * In the schedule, qh->link maintains a list of QHs seen by the HC: + * skel1 --> ep1-qh --> ep2-qh --> ... --> skel2 --> ... + * + * qh->node is the software equivalent of qh->link. The differences + * are that the software list is doubly-linked and QHs in the UNLINKING + * state are on the software list but not the hardware schedule. + * + * For bookkeeping purposes we maintain QHs even for Isochronous endpoints, + * but they never get added to the hardware schedule. + */ +#define QH_STATE_IDLE 1 /* QH is not being used */ +#define QH_STATE_UNLINKING 2 /* QH has been removed from the + * schedule but the hardware may + * still be using it */ +#define QH_STATE_ACTIVE 3 /* QH is on the schedule */ + +struct uhci_qh { + /* Hardware fields */ + __hc32 link; /* Next QH in the schedule */ + __hc32 element; /* Queue element (TD) pointer */ + + /* Software fields */ + dma_addr_t dma_handle; + + struct list_head node; /* Node in the list of QHs */ + struct usb_host_endpoint *hep; /* Endpoint information */ + struct usb_device *udev; + struct list_head queue; /* Queue of urbps for this QH */ + struct uhci_td *dummy_td; /* Dummy TD to end the queue */ + struct uhci_td *post_td; /* Last TD completed */ + + struct usb_iso_packet_descriptor *iso_packet_desc; + /* Next urb->iso_frame_desc entry */ + unsigned long advance_jiffies; /* Time of last queue advance */ + unsigned int unlink_frame; /* When the QH was unlinked */ + unsigned int period; /* For Interrupt and Isochronous QHs */ + short phase; /* Between 0 and period-1 */ + short load; /* Periodic time requirement, in us */ + unsigned int iso_frame; /* Frame # for iso_packet_desc */ + + int state; /* QH_STATE_xxx; see above */ + int type; /* Queue type (control, bulk, etc) */ + int skel; /* Skeleton queue number */ + + unsigned int initial_toggle:1; /* Endpoint's current toggle value */ + unsigned int needs_fixup:1; /* Must fix the TD toggle values */ + unsigned int is_stopped:1; /* Queue was stopped by error/unlink */ + unsigned int wait_expired:1; /* QH_WAIT_TIMEOUT has expired */ + unsigned int bandwidth_reserved:1; /* Periodic bandwidth has + * been allocated */ +} __attribute__((aligned(16))); + +/* + * We need a special accessor for the element pointer because it is + * subject to asynchronous updates by the controller. + */ +#define qh_element(qh) ACCESS_ONCE((qh)->element) + +#define LINK_TO_QH(uhci, qh) (UHCI_PTR_QH((uhci)) | \ + cpu_to_hc32((uhci), (qh)->dma_handle)) + + +/* + * Transfer Descriptors + */ + +/* + * for TD : + */ +#define TD_CTRL_SPD (1 << 29) /* Short Packet Detect */ +#define TD_CTRL_C_ERR_MASK (3 << 27) /* Error Counter bits */ +#define TD_CTRL_C_ERR_SHIFT 27 +#define TD_CTRL_LS (1 << 26) /* Low Speed Device */ +#define TD_CTRL_IOS (1 << 25) /* Isochronous Select */ +#define TD_CTRL_IOC (1 << 24) /* Interrupt on Complete */ +#define TD_CTRL_ACTIVE (1 << 23) /* TD Active */ +#define TD_CTRL_STALLED (1 << 22) /* TD Stalled */ +#define TD_CTRL_DBUFERR (1 << 21) /* Data Buffer Error */ +#define TD_CTRL_BABBLE (1 << 20) /* Babble Detected */ +#define TD_CTRL_NAK (1 << 19) /* NAK Received */ +#define TD_CTRL_CRCTIMEO (1 << 18) /* CRC/Time Out Error */ +#define TD_CTRL_BITSTUFF (1 << 17) /* Bit Stuff Error */ +#define TD_CTRL_ACTLEN_MASK 0x7FF /* actual length, encoded as n - 1 */ + +#define uhci_maxerr(err) ((err) << TD_CTRL_C_ERR_SHIFT) +#define uhci_status_bits(ctrl_sts) ((ctrl_sts) & 0xF60000) +#define uhci_actual_length(ctrl_sts) (((ctrl_sts) + 1) & \ + TD_CTRL_ACTLEN_MASK) /* 1-based */ + +/* + * for TD : (a.k.a. Token) + */ +#define td_token(uhci, td) hc32_to_cpu((uhci), (td)->token) +#define TD_TOKEN_DEVADDR_SHIFT 8 +#define TD_TOKEN_TOGGLE_SHIFT 19 +#define TD_TOKEN_TOGGLE (1 << 19) +#define TD_TOKEN_EXPLEN_SHIFT 21 +#define TD_TOKEN_EXPLEN_MASK 0x7FF /* expected length, encoded as n-1 */ +#define TD_TOKEN_PID_MASK 0xFF + +#define uhci_explen(len) ((((len) - 1) & TD_TOKEN_EXPLEN_MASK) << \ + TD_TOKEN_EXPLEN_SHIFT) + +#define uhci_expected_length(token) ((((token) >> TD_TOKEN_EXPLEN_SHIFT) + \ + 1) & TD_TOKEN_EXPLEN_MASK) +#define uhci_toggle(token) (((token) >> TD_TOKEN_TOGGLE_SHIFT) & 1) +#define uhci_endpoint(token) (((token) >> 15) & 0xf) +#define uhci_devaddr(token) (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7f) +#define uhci_devep(token) (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7ff) +#define uhci_packetid(token) ((token) & TD_TOKEN_PID_MASK) +#define uhci_packetout(token) (uhci_packetid(token) != USB_PID_IN) +#define uhci_packetin(token) (uhci_packetid(token) == USB_PID_IN) + +/* + * The documentation says "4 words for hardware, 4 words for software". + * + * That's silly, the hardware doesn't care. The hardware only cares that + * the hardware words are 16-byte aligned, and we can have any amount of + * sw space after the TD entry. + * + * td->link points to either another TD (not necessarily for the same urb or + * even the same endpoint), or nothing (PTR_TERM), or a QH. + */ +struct uhci_td { + /* Hardware fields */ + __hc32 link; + __hc32 status; + __hc32 token; + __hc32 buffer; + + /* Software fields */ + dma_addr_t dma_handle; + + struct list_head list; + + int frame; /* for iso: what frame? */ + struct list_head fl_list; +} __attribute__((aligned(16))); + +/* + * We need a special accessor for the control/status word because it is + * subject to asynchronous updates by the controller. + */ +#define td_status(uhci, td) hc32_to_cpu((uhci), \ + ACCESS_ONCE((td)->status)) + +#define LINK_TO_TD(uhci, td) (cpu_to_hc32((uhci), (td)->dma_handle)) + + +/* + * Skeleton Queue Headers + */ + +/* + * The UHCI driver uses QHs with Interrupt, Control and Bulk URBs for + * automatic queuing. To make it easy to insert entries into the schedule, + * we have a skeleton of QHs for each predefined Interrupt latency. + * Asynchronous QHs (low-speed control, full-speed control, and bulk) + * go onto the period-1 interrupt list, since they all get accessed on + * every frame. + * + * When we want to add a new QH, we add it to the list starting from the + * appropriate skeleton QH. For instance, the schedule can look like this: + * + * skel int128 QH + * dev 1 interrupt QH + * dev 5 interrupt QH + * skel int64 QH + * skel int32 QH + * ... + * skel int1 + async QH + * dev 5 low-speed control QH + * dev 1 bulk QH + * dev 2 bulk QH + * + * There is a special terminating QH used to keep full-speed bandwidth + * reclamation active when no full-speed control or bulk QHs are linked + * into the schedule. It has an inactive TD (to work around a PIIX bug, + * see the Intel errata) and it points back to itself. + * + * There's a special skeleton QH for Isochronous QHs which never appears + * on the schedule. Isochronous TDs go on the schedule before the + * the skeleton QHs. The hardware accesses them directly rather than + * through their QH, which is used only for bookkeeping purposes. + * While the UHCI spec doesn't forbid the use of QHs for Isochronous, + * it doesn't use them either. And the spec says that queues never + * advance on an error completion status, which makes them totally + * unsuitable for Isochronous transfers. + * + * There's also a special skeleton QH used for QHs which are in the process + * of unlinking and so may still be in use by the hardware. It too never + * appears on the schedule. + */ + +#define UHCI_NUM_SKELQH 11 +#define SKEL_UNLINK 0 +#define skel_unlink_qh skelqh[SKEL_UNLINK] +#define SKEL_ISO 1 +#define skel_iso_qh skelqh[SKEL_ISO] + /* int128, int64, ..., int1 = 2, 3, ..., 9 */ +#define SKEL_INDEX(exponent) (9 - exponent) +#define SKEL_ASYNC 9 +#define skel_async_qh skelqh[SKEL_ASYNC] +#define SKEL_TERM 10 +#define skel_term_qh skelqh[SKEL_TERM] + +/* The following entries refer to sublists of skel_async_qh */ +#define SKEL_LS_CONTROL 20 +#define SKEL_FS_CONTROL 21 +#define SKEL_FSBR SKEL_FS_CONTROL +#define SKEL_BULK 22 + +/* + * The UHCI controller and root hub + */ + +/* + * States for the root hub: + * + * To prevent "bouncing" in the presence of electrical noise, + * when there are no devices attached we delay for 1 second in the + * RUNNING_NODEVS state before switching to the AUTO_STOPPED state. + * + * (Note that the AUTO_STOPPED state won't be necessary once the hub + * driver learns to autosuspend.) + */ +enum uhci_rh_state { + /* In the following states the HC must be halted. + * These two must come first. */ + UHCI_RH_RESET, + UHCI_RH_SUSPENDED, + + UHCI_RH_AUTO_STOPPED, + UHCI_RH_RESUMING, + + /* In this state the HC changes from running to halted, + * so it can legally appear either way. */ + UHCI_RH_SUSPENDING, + + /* In the following states it's an error if the HC is halted. + * These two must come last. */ + UHCI_RH_RUNNING, /* The normal state */ + UHCI_RH_RUNNING_NODEVS, /* Running with no devices attached */ +}; + +/* + * The full UHCI controller information: + */ +struct uhci_hcd { + + /* debugfs */ + struct dentry *dentry; + + /* Grabbed from PCI */ + unsigned long io_addr; + + /* Used when registers are memory mapped */ + void __iomem *regs; + + struct dma_pool *qh_pool; + struct dma_pool *td_pool; + + struct uhci_td *term_td; /* Terminating TD, see UHCI bug */ + struct uhci_qh *skelqh[UHCI_NUM_SKELQH]; /* Skeleton QHs */ + struct uhci_qh *next_qh; /* Next QH to scan */ + + spinlock_t lock; + + dma_addr_t frame_dma_handle; /* Hardware frame list */ + __hc32 *frame; + void **frame_cpu; /* CPU's frame list */ + + enum uhci_rh_state rh_state; + unsigned long auto_stop_time; /* When to AUTO_STOP */ + + unsigned int frame_number; /* As of last check */ + unsigned int is_stopped; +#define UHCI_IS_STOPPED 9999 /* Larger than a frame # */ + unsigned int last_iso_frame; /* Frame of last scan */ + unsigned int cur_iso_frame; /* Frame for current scan */ + + unsigned int scan_in_progress:1; /* Schedule scan is running */ + unsigned int need_rescan:1; /* Redo the schedule scan */ + unsigned int dead:1; /* Controller has died */ + unsigned int RD_enable:1; /* Suspended root hub with + Resume-Detect interrupts + enabled */ + unsigned int is_initialized:1; /* Data structure is usable */ + unsigned int fsbr_is_on:1; /* FSBR is turned on */ + unsigned int fsbr_is_wanted:1; /* Does any URB want FSBR? */ + unsigned int fsbr_expiring:1; /* FSBR is timing out */ + + struct timer_list fsbr_timer; /* For turning off FBSR */ + + /* Silicon quirks */ + unsigned int oc_low:1; /* OverCurrent bit active low */ + unsigned int wait_for_hp:1; /* Wait for HP port reset */ + unsigned int big_endian_mmio:1; /* Big endian registers */ + unsigned int big_endian_desc:1; /* Big endian descriptors */ + + /* Support for port suspend/resume/reset */ + unsigned long port_c_suspend; /* Bit-arrays of ports */ + unsigned long resuming_ports; + unsigned long ports_timeout; /* Time to stop signalling */ + + struct list_head idle_qh_list; /* Where the idle QHs live */ + + int rh_numports; /* Number of root-hub ports */ + + wait_queue_head_t waitqh; /* endpoint_disable waiters */ + int num_waiting; /* Number of waiters */ + + int total_load; /* Sum of array values */ + short load[MAX_PHASE]; /* Periodic allocations */ + + /* Reset host controller */ + void (*reset_hc) (struct uhci_hcd *uhci); + int (*check_and_reset_hc) (struct uhci_hcd *uhci); + /* configure_hc should perform arch specific settings, if needed */ + void (*configure_hc) (struct uhci_hcd *uhci); + /* Check for broken resume detect interrupts */ + int (*resume_detect_interrupts_are_broken) (struct uhci_hcd *uhci); + /* Check for broken global suspend */ + int (*global_suspend_mode_is_broken) (struct uhci_hcd *uhci); +}; + +/* Convert between a usb_hcd pointer and the corresponding uhci_hcd */ +static inline struct uhci_hcd *hcd_to_uhci(struct usb_hcd *hcd) +{ + return (struct uhci_hcd *) (hcd->hcd_priv); +} +static inline struct usb_hcd *uhci_to_hcd(struct uhci_hcd *uhci) +{ + return container_of((void *) uhci, struct usb_hcd, hcd_priv); +} + +#define uhci_dev(u) (uhci_to_hcd(u)->self.controller) + +/* Utility macro for comparing frame numbers */ +#define uhci_frame_before_eq(f1, f2) (0 <= (int) ((f2) - (f1))) + + +/* + * Private per-URB data + */ +struct urb_priv { + struct list_head node; /* Node in the QH's urbp list */ + + struct urb *urb; + + struct uhci_qh *qh; /* QH for this URB */ + struct list_head td_list; + + unsigned fsbr:1; /* URB wants FSBR */ +}; + + +/* Some special IDs */ + +#define PCI_VENDOR_ID_GENESYS 0x17a0 +#define PCI_DEVICE_ID_GL880S_UHCI 0x8083 + +/* + * Functions used to access controller registers. The UCHI spec says that host + * controller I/O registers are mapped into PCI I/O space. For non-PCI hosts + * we use memory mapped registers. + */ + +#ifndef CONFIG_USB_UHCI_SUPPORT_NON_PCI_HC +/* Support PCI only */ +static inline u32 uhci_readl(const struct uhci_hcd *uhci, int reg) +{ + return inl(uhci->io_addr + reg); +} + +static inline void uhci_writel(const struct uhci_hcd *uhci, u32 val, int reg) +{ + outl(val, uhci->io_addr + reg); +} + +static inline u16 uhci_readw(const struct uhci_hcd *uhci, int reg) +{ + return inw(uhci->io_addr + reg); +} + +static inline void uhci_writew(const struct uhci_hcd *uhci, u16 val, int reg) +{ + outw(val, uhci->io_addr + reg); +} + +static inline u8 uhci_readb(const struct uhci_hcd *uhci, int reg) +{ + return inb(uhci->io_addr + reg); +} + +static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) +{ + outb(val, uhci->io_addr + reg); +} + +#else +/* Support non-PCI host controllers */ +#ifdef CONFIG_PCI +/* Support PCI and non-PCI host controllers */ +#define uhci_has_pci_registers(u) ((u)->io_addr != 0) +#else +/* Support non-PCI host controllers only */ +#define uhci_has_pci_registers(u) 0 +#endif + +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO +/* Support (non-PCI) big endian host controllers */ +#define uhci_big_endian_mmio(u) ((u)->big_endian_mmio) +#else +#define uhci_big_endian_mmio(u) 0 +#endif + +static inline u32 uhci_readl(const struct uhci_hcd *uhci, int reg) +{ + if (uhci_has_pci_registers(uhci)) + return inl(uhci->io_addr + reg); +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO + else if (uhci_big_endian_mmio(uhci)) + return readl_be(uhci->regs + reg); +#endif + else + return readl(uhci->regs + reg); +} + +static inline void uhci_writel(const struct uhci_hcd *uhci, u32 val, int reg) +{ + if (uhci_has_pci_registers(uhci)) + outl(val, uhci->io_addr + reg); +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO + else if (uhci_big_endian_mmio(uhci)) + writel_be(val, uhci->regs + reg); +#endif + else + writel(val, uhci->regs + reg); +} + +static inline u16 uhci_readw(const struct uhci_hcd *uhci, int reg) +{ + if (uhci_has_pci_registers(uhci)) + return inw(uhci->io_addr + reg); +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO + else if (uhci_big_endian_mmio(uhci)) + return readw_be(uhci->regs + reg); +#endif + else + return readw(uhci->regs + reg); +} + +static inline void uhci_writew(const struct uhci_hcd *uhci, u16 val, int reg) +{ + if (uhci_has_pci_registers(uhci)) + outw(val, uhci->io_addr + reg); +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO + else if (uhci_big_endian_mmio(uhci)) + writew_be(val, uhci->regs + reg); +#endif + else + writew(val, uhci->regs + reg); +} + +static inline u8 uhci_readb(const struct uhci_hcd *uhci, int reg) +{ + if (uhci_has_pci_registers(uhci)) + return inb(uhci->io_addr + reg); +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO + else if (uhci_big_endian_mmio(uhci)) + return readb_be(uhci->regs + reg); +#endif + else + return readb(uhci->regs + reg); +} + +static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) +{ + if (uhci_has_pci_registers(uhci)) + outb(val, uhci->io_addr + reg); +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO + else if (uhci_big_endian_mmio(uhci)) + writeb_be(val, uhci->regs + reg); +#endif + else + writeb(val, uhci->regs + reg); +} +#endif /* CONFIG_USB_UHCI_SUPPORT_NON_PCI_HC */ + +/* + * The GRLIB GRUSBHC controller can use big endian format for its descriptors. + * + * UHCI controllers accessed through PCI work normally (little-endian + * everywhere), so we don't bother supporting a BE-only mode. + */ +#ifdef CONFIG_USB_UHCI_BIG_ENDIAN_DESC +#define uhci_big_endian_desc(u) ((u)->big_endian_desc) + +/* cpu to uhci */ +static inline __hc32 cpu_to_hc32(const struct uhci_hcd *uhci, const u32 x) +{ + return uhci_big_endian_desc(uhci) + ? (__force __hc32)cpu_to_be32(x) + : (__force __hc32)cpu_to_le32(x); +} + +/* uhci to cpu */ +static inline u32 hc32_to_cpu(const struct uhci_hcd *uhci, const __hc32 x) +{ + return uhci_big_endian_desc(uhci) + ? be32_to_cpu((__force __be32)x) + : le32_to_cpu((__force __le32)x); +} + +#else +/* cpu to uhci */ +static inline __hc32 cpu_to_hc32(const struct uhci_hcd *uhci, const u32 x) +{ + return cpu_to_le32(x); +} + +/* uhci to cpu */ +static inline u32 hc32_to_cpu(const struct uhci_hcd *uhci, const __hc32 x) +{ + return le32_to_cpu(x); +} +#endif + +#endif diff --git a/drivers/usb/host/uhci-hub.c b/drivers/usb/host/uhci-hub.c new file mode 100644 index 00000000..9189bc98 --- /dev/null +++ b/drivers/usb/host/uhci-hub.c @@ -0,0 +1,418 @@ +/* + * Universal Host Controller Interface driver for USB. + * + * Maintainer: Alan Stern + * + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com + * (C) Copyright 1999 Randy Dunlap + * (C) Copyright 1999 Georg Acher, acher@in.tum.de + * (C) Copyright 1999 Deti Fliegl, deti@fliegl.de + * (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch + * (C) Copyright 2004 Alan Stern, stern@rowland.harvard.edu + */ + +static const __u8 root_hub_hub_des[] = +{ + 0x09, /* __u8 bLength; */ + 0x29, /* __u8 bDescriptorType; Hub-descriptor */ + 0x02, /* __u8 bNbrPorts; */ + 0x0a, /* __u16 wHubCharacteristics; */ + 0x00, /* (per-port OC, no power switching) */ + 0x01, /* __u8 bPwrOn2pwrGood; 2ms */ + 0x00, /* __u8 bHubContrCurrent; 0 mA */ + 0x00, /* __u8 DeviceRemovable; *** 7 Ports max */ + 0xff /* __u8 PortPwrCtrlMask; *** 7 ports max */ +}; + +#define UHCI_RH_MAXCHILD 7 + +/* must write as zeroes */ +#define WZ_BITS (USBPORTSC_RES2 | USBPORTSC_RES3 | USBPORTSC_RES4) + +/* status change bits: nonzero writes will clear */ +#define RWC_BITS (USBPORTSC_OCC | USBPORTSC_PEC | USBPORTSC_CSC) + +/* suspend/resume bits: port suspended or port resuming */ +#define SUSPEND_BITS (USBPORTSC_SUSP | USBPORTSC_RD) + +/* A port that either is connected or has a changed-bit set will prevent + * us from AUTO_STOPPING. + */ +static int any_ports_active(struct uhci_hcd *uhci) +{ + int port; + + for (port = 0; port < uhci->rh_numports; ++port) { + if ((uhci_readw(uhci, USBPORTSC1 + port * 2) & + (USBPORTSC_CCS | RWC_BITS)) || + test_bit(port, &uhci->port_c_suspend)) + return 1; + } + return 0; +} + +static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf) +{ + int port; + int mask = RWC_BITS; + + /* Some boards (both VIA and Intel apparently) report bogus + * overcurrent indications, causing massive log spam unless + * we completely ignore them. This doesn't seem to be a problem + * with the chipset so much as with the way it is connected on + * the motherboard; if the overcurrent input is left to float + * then it may constantly register false positives. */ + if (ignore_oc) + mask &= ~USBPORTSC_OCC; + + *buf = 0; + for (port = 0; port < uhci->rh_numports; ++port) { + if ((uhci_readw(uhci, USBPORTSC1 + port * 2) & mask) || + test_bit(port, &uhci->port_c_suspend)) + *buf |= (1 << (port + 1)); + } + return !!*buf; +} + +#define OK(x) len = (x); break + +#define CLR_RH_PORTSTAT(x) \ + status = uhci_readw(uhci, port_addr); \ + status &= ~(RWC_BITS|WZ_BITS); \ + status &= ~(x); \ + status |= RWC_BITS & (x); \ + uhci_writew(uhci, status, port_addr) + +#define SET_RH_PORTSTAT(x) \ + status = uhci_readw(uhci, port_addr); \ + status |= (x); \ + status &= ~(RWC_BITS|WZ_BITS); \ + uhci_writew(uhci, status, port_addr) + +/* UHCI controllers don't automatically stop resume signalling after 20 msec, + * so we have to poll and check timeouts in order to take care of it. + */ +static void uhci_finish_suspend(struct uhci_hcd *uhci, int port, + unsigned long port_addr) +{ + int status; + int i; + + if (uhci_readw(uhci, port_addr) & SUSPEND_BITS) { + CLR_RH_PORTSTAT(SUSPEND_BITS); + if (test_bit(port, &uhci->resuming_ports)) + set_bit(port, &uhci->port_c_suspend); + + /* The controller won't actually turn off the RD bit until + * it has had a chance to send a low-speed EOP sequence, + * which is supposed to take 3 bit times (= 2 microseconds). + * Experiments show that some controllers take longer, so + * we'll poll for completion. */ + for (i = 0; i < 10; ++i) { + if (!(uhci_readw(uhci, port_addr) & SUSPEND_BITS)) + break; + udelay(1); + } + } + clear_bit(port, &uhci->resuming_ports); + usb_hcd_end_port_resume(&uhci_to_hcd(uhci)->self, port); +} + +/* Wait for the UHCI controller in HP's iLO2 server management chip. + * It can take up to 250 us to finish a reset and set the CSC bit. + */ +static void wait_for_HP(struct uhci_hcd *uhci, unsigned long port_addr) +{ + int i; + + for (i = 10; i < 250; i += 10) { + if (uhci_readw(uhci, port_addr) & USBPORTSC_CSC) + return; + udelay(10); + } + /* Log a warning? */ +} + +static void uhci_check_ports(struct uhci_hcd *uhci) +{ + unsigned int port; + unsigned long port_addr; + int status; + + for (port = 0; port < uhci->rh_numports; ++port) { + port_addr = USBPORTSC1 + 2 * port; + status = uhci_readw(uhci, port_addr); + if (unlikely(status & USBPORTSC_PR)) { + if (time_after_eq(jiffies, uhci->ports_timeout)) { + CLR_RH_PORTSTAT(USBPORTSC_PR); + udelay(10); + + /* HP's server management chip requires + * a longer delay. */ + if (uhci->wait_for_hp) + wait_for_HP(uhci, port_addr); + + /* If the port was enabled before, turning + * reset on caused a port enable change. + * Turning reset off causes a port connect + * status change. Clear these changes. */ + CLR_RH_PORTSTAT(USBPORTSC_CSC | USBPORTSC_PEC); + SET_RH_PORTSTAT(USBPORTSC_PE); + } + } + if (unlikely(status & USBPORTSC_RD)) { + if (!test_bit(port, &uhci->resuming_ports)) { + + /* Port received a wakeup request */ + set_bit(port, &uhci->resuming_ports); + uhci->ports_timeout = jiffies + + msecs_to_jiffies(25); + usb_hcd_start_port_resume( + &uhci_to_hcd(uhci)->self, port); + + /* Make sure we see the port again + * after the resuming period is over. */ + mod_timer(&uhci_to_hcd(uhci)->rh_timer, + uhci->ports_timeout); + } else if (time_after_eq(jiffies, + uhci->ports_timeout)) { + uhci_finish_suspend(uhci, port, port_addr); + } + } + } +} + +static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + unsigned long flags; + int status = 0; + + spin_lock_irqsave(&uhci->lock, flags); + + uhci_scan_schedule(uhci); + if (!HCD_HW_ACCESSIBLE(hcd) || uhci->dead) + goto done; + uhci_check_ports(uhci); + + status = get_hub_status_data(uhci, buf); + + switch (uhci->rh_state) { + case UHCI_RH_SUSPENDED: + /* if port change, ask to be resumed */ + if (status || uhci->resuming_ports) { + status = 1; + usb_hcd_resume_root_hub(hcd); + } + break; + + case UHCI_RH_AUTO_STOPPED: + /* if port change, auto start */ + if (status) + wakeup_rh(uhci); + break; + + case UHCI_RH_RUNNING: + /* are any devices attached? */ + if (!any_ports_active(uhci)) { + uhci->rh_state = UHCI_RH_RUNNING_NODEVS; + uhci->auto_stop_time = jiffies + HZ; + } + break; + + case UHCI_RH_RUNNING_NODEVS: + /* auto-stop if nothing connected for 1 second */ + if (any_ports_active(uhci)) + uhci->rh_state = UHCI_RH_RUNNING; + else if (time_after_eq(jiffies, uhci->auto_stop_time) && + !uhci->wait_for_hp) + suspend_rh(uhci, UHCI_RH_AUTO_STOPPED); + break; + + default: + break; + } + +done: + spin_unlock_irqrestore(&uhci->lock, flags); + return status; +} + +/* size of returned buffer is part of USB spec */ +static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + int status, lstatus, retval = 0, len = 0; + unsigned int port = wIndex - 1; + unsigned long port_addr = USBPORTSC1 + 2 * port; + u16 wPortChange, wPortStatus; + unsigned long flags; + + if (!HCD_HW_ACCESSIBLE(hcd) || uhci->dead) + return -ETIMEDOUT; + + spin_lock_irqsave(&uhci->lock, flags); + switch (typeReq) { + + case GetHubStatus: + *(__le32 *)buf = cpu_to_le32(0); + OK(4); /* hub power */ + case GetPortStatus: + if (port >= uhci->rh_numports) + goto err; + + uhci_check_ports(uhci); + status = uhci_readw(uhci, port_addr); + + /* Intel controllers report the OverCurrent bit active on. + * VIA controllers report it active off, so we'll adjust the + * bit value. (It's not standardized in the UHCI spec.) + */ + if (uhci->oc_low) + status ^= USBPORTSC_OC; + + /* UHCI doesn't support C_RESET (always false) */ + wPortChange = lstatus = 0; + if (status & USBPORTSC_CSC) + wPortChange |= USB_PORT_STAT_C_CONNECTION; + if (status & USBPORTSC_PEC) + wPortChange |= USB_PORT_STAT_C_ENABLE; + if ((status & USBPORTSC_OCC) && !ignore_oc) + wPortChange |= USB_PORT_STAT_C_OVERCURRENT; + + if (test_bit(port, &uhci->port_c_suspend)) { + wPortChange |= USB_PORT_STAT_C_SUSPEND; + lstatus |= 1; + } + if (test_bit(port, &uhci->resuming_ports)) + lstatus |= 4; + + /* UHCI has no power switching (always on) */ + wPortStatus = USB_PORT_STAT_POWER; + if (status & USBPORTSC_CCS) + wPortStatus |= USB_PORT_STAT_CONNECTION; + if (status & USBPORTSC_PE) { + wPortStatus |= USB_PORT_STAT_ENABLE; + if (status & SUSPEND_BITS) + wPortStatus |= USB_PORT_STAT_SUSPEND; + } + if (status & USBPORTSC_OC) + wPortStatus |= USB_PORT_STAT_OVERCURRENT; + if (status & USBPORTSC_PR) + wPortStatus |= USB_PORT_STAT_RESET; + if (status & USBPORTSC_LSDA) + wPortStatus |= USB_PORT_STAT_LOW_SPEED; + + if (wPortChange) + dev_dbg(uhci_dev(uhci), "port %d portsc %04x,%02x\n", + wIndex, status, lstatus); + + *(__le16 *)buf = cpu_to_le16(wPortStatus); + *(__le16 *)(buf + 2) = cpu_to_le16(wPortChange); + OK(4); + case SetHubFeature: /* We don't implement these */ + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + OK(0); + default: + goto err; + } + break; + case SetPortFeature: + if (port >= uhci->rh_numports) + goto err; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + SET_RH_PORTSTAT(USBPORTSC_SUSP); + OK(0); + case USB_PORT_FEAT_RESET: + SET_RH_PORTSTAT(USBPORTSC_PR); + + /* Reset terminates Resume signalling */ + uhci_finish_suspend(uhci, port, port_addr); + + /* USB v2.0 7.1.7.5 */ + uhci->ports_timeout = jiffies + msecs_to_jiffies(50); + OK(0); + case USB_PORT_FEAT_POWER: + /* UHCI has no power switching */ + OK(0); + default: + goto err; + } + break; + case ClearPortFeature: + if (port >= uhci->rh_numports) + goto err; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + CLR_RH_PORTSTAT(USBPORTSC_PE); + + /* Disable terminates Resume signalling */ + uhci_finish_suspend(uhci, port, port_addr); + OK(0); + case USB_PORT_FEAT_C_ENABLE: + CLR_RH_PORTSTAT(USBPORTSC_PEC); + OK(0); + case USB_PORT_FEAT_SUSPEND: + if (!(uhci_readw(uhci, port_addr) & USBPORTSC_SUSP)) { + + /* Make certain the port isn't suspended */ + uhci_finish_suspend(uhci, port, port_addr); + } else if (!test_and_set_bit(port, + &uhci->resuming_ports)) { + SET_RH_PORTSTAT(USBPORTSC_RD); + + /* The controller won't allow RD to be set + * if the port is disabled. When this happens + * just skip the Resume signalling. + */ + if (!(uhci_readw(uhci, port_addr) & + USBPORTSC_RD)) + uhci_finish_suspend(uhci, port, + port_addr); + else + /* USB v2.0 7.1.7.7 */ + uhci->ports_timeout = jiffies + + msecs_to_jiffies(20); + } + OK(0); + case USB_PORT_FEAT_C_SUSPEND: + clear_bit(port, &uhci->port_c_suspend); + OK(0); + case USB_PORT_FEAT_POWER: + /* UHCI has no power switching */ + goto err; + case USB_PORT_FEAT_C_CONNECTION: + CLR_RH_PORTSTAT(USBPORTSC_CSC); + OK(0); + case USB_PORT_FEAT_C_OVER_CURRENT: + CLR_RH_PORTSTAT(USBPORTSC_OCC); + OK(0); + case USB_PORT_FEAT_C_RESET: + /* this driver won't report these */ + OK(0); + default: + goto err; + } + break; + case GetHubDescriptor: + len = min_t(unsigned int, sizeof(root_hub_hub_des), wLength); + memcpy(buf, root_hub_hub_des, len); + if (len > 2) + buf[2] = uhci->rh_numports; + OK(len); + default: +err: + retval = -EPIPE; + } + spin_unlock_irqrestore(&uhci->lock, flags); + + return retval; +} diff --git a/drivers/usb/host/uhci-pci.c b/drivers/usb/host/uhci-pci.c new file mode 100644 index 00000000..0f228c46 --- /dev/null +++ b/drivers/usb/host/uhci-pci.c @@ -0,0 +1,301 @@ +/* + * UHCI HCD (Host Controller Driver) PCI Bus Glue. + * + * Extracted from uhci-hcd.c: + * Maintainer: Alan Stern + * + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com + * (C) Copyright 1999 Randy Dunlap + * (C) Copyright 1999 Georg Acher, acher@in.tum.de + * (C) Copyright 1999 Deti Fliegl, deti@fliegl.de + * (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch + * (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at + * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface + * support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). + * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) + * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu + */ + +#include "pci-quirks.h" + +/* + * Make sure the controller is completely inactive, unable to + * generate interrupts or do DMA. + */ +static void uhci_pci_reset_hc(struct uhci_hcd *uhci) +{ + uhci_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr); +} + +/* + * Initialize a controller that was newly discovered or has just been + * resumed. In either case we can't be sure of its previous state. + * + * Returns: 1 if the controller was reset, 0 otherwise. + */ +static int uhci_pci_check_and_reset_hc(struct uhci_hcd *uhci) +{ + return uhci_check_and_reset_hc(to_pci_dev(uhci_dev(uhci)), + uhci->io_addr); +} + +/* + * Store the basic register settings needed by the controller. + * This function is called at the end of configure_hc in uhci-hcd.c. + */ +static void uhci_pci_configure_hc(struct uhci_hcd *uhci) +{ + struct pci_dev *pdev = to_pci_dev(uhci_dev(uhci)); + + /* Enable PIRQ */ + pci_write_config_word(pdev, USBLEGSUP, USBLEGSUP_DEFAULT); + + /* Disable platform-specific non-PME# wakeup */ + if (pdev->vendor == PCI_VENDOR_ID_INTEL) + pci_write_config_byte(pdev, USBRES_INTEL, 0); +} + +static int uhci_pci_resume_detect_interrupts_are_broken(struct uhci_hcd *uhci) +{ + int port; + + switch (to_pci_dev(uhci_dev(uhci))->vendor) { + default: + break; + + case PCI_VENDOR_ID_GENESYS: + /* Genesys Logic's GL880S controllers don't generate + * resume-detect interrupts. + */ + return 1; + + case PCI_VENDOR_ID_INTEL: + /* Some of Intel's USB controllers have a bug that causes + * resume-detect interrupts if any port has an over-current + * condition. To make matters worse, some motherboards + * hardwire unused USB ports' over-current inputs active! + * To prevent problems, we will not enable resume-detect + * interrupts if any ports are OC. + */ + for (port = 0; port < uhci->rh_numports; ++port) { + if (inw(uhci->io_addr + USBPORTSC1 + port * 2) & + USBPORTSC_OC) + return 1; + } + break; + } + return 0; +} + +static int uhci_pci_global_suspend_mode_is_broken(struct uhci_hcd *uhci) +{ + int port; + const char *sys_info; + static const char bad_Asus_board[] = "A7V8X"; + + /* One of Asus's motherboards has a bug which causes it to + * wake up immediately from suspend-to-RAM if any of the ports + * are connected. In such cases we will not set EGSM. + */ + sys_info = dmi_get_system_info(DMI_BOARD_NAME); + if (sys_info && !strcmp(sys_info, bad_Asus_board)) { + for (port = 0; port < uhci->rh_numports; ++port) { + if (inw(uhci->io_addr + USBPORTSC1 + port * 2) & + USBPORTSC_CCS) + return 1; + } + } + + return 0; +} + +static int uhci_pci_init(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + uhci->io_addr = (unsigned long) hcd->rsrc_start; + + uhci->rh_numports = uhci_count_ports(hcd); + + /* Intel controllers report the OverCurrent bit active on. + * VIA controllers report it active off, so we'll adjust the + * bit value. (It's not standardized in the UHCI spec.) + */ + if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_VIA) + uhci->oc_low = 1; + + /* HP's server management chip requires a longer port reset delay. */ + if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_HP) + uhci->wait_for_hp = 1; + + /* Set up pointers to PCI-specific functions */ + uhci->reset_hc = uhci_pci_reset_hc; + uhci->check_and_reset_hc = uhci_pci_check_and_reset_hc; + uhci->configure_hc = uhci_pci_configure_hc; + uhci->resume_detect_interrupts_are_broken = + uhci_pci_resume_detect_interrupts_are_broken; + uhci->global_suspend_mode_is_broken = + uhci_pci_global_suspend_mode_is_broken; + + + /* Kick BIOS off this hardware and reset if the controller + * isn't already safely quiescent. + */ + check_and_reset_hc(uhci); + return 0; +} + +/* Make sure the controller is quiescent and that we're not using it + * any more. This is mainly for the benefit of programs which, like kexec, + * expect the hardware to be idle: not doing DMA or generating IRQs. + * + * This routine may be called in a damaged or failing kernel. Hence we + * do not acquire the spinlock before shutting down the controller. + */ +static void uhci_shutdown(struct pci_dev *pdev) +{ + struct usb_hcd *hcd = pci_get_drvdata(pdev); + + uhci_hc_died(hcd_to_uhci(hcd)); +} + +#ifdef CONFIG_PM + +static int uhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + struct pci_dev *pdev = to_pci_dev(uhci_dev(uhci)); + int rc = 0; + + dev_dbg(uhci_dev(uhci), "%s\n", __func__); + + spin_lock_irq(&uhci->lock); + if (!HCD_HW_ACCESSIBLE(hcd) || uhci->dead) + goto done_okay; /* Already suspended or dead */ + + if (uhci->rh_state > UHCI_RH_SUSPENDED) { + dev_warn(uhci_dev(uhci), "Root hub isn't suspended!\n"); + rc = -EBUSY; + goto done; + }; + + /* All PCI host controllers are required to disable IRQ generation + * at the source, so we must turn off PIRQ. + */ + pci_write_config_word(pdev, USBLEGSUP, 0); + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + + /* Enable platform-specific non-PME# wakeup */ + if (do_wakeup) { + if (pdev->vendor == PCI_VENDOR_ID_INTEL) + pci_write_config_byte(pdev, USBRES_INTEL, + USBPORT1EN | USBPORT2EN); + } + +done_okay: + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); +done: + spin_unlock_irq(&uhci->lock); + return rc; +} + +static int uhci_pci_resume(struct usb_hcd *hcd, bool hibernated) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + dev_dbg(uhci_dev(uhci), "%s\n", __func__); + + /* Since we aren't in D3 any more, it's safe to set this flag + * even if the controller was dead. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + spin_lock_irq(&uhci->lock); + + /* Make sure resume from hibernation re-enumerates everything */ + if (hibernated) { + uhci->reset_hc(uhci); + finish_reset(uhci); + } + + /* The firmware may have changed the controller settings during + * a system wakeup. Check it and reconfigure to avoid problems. + */ + else { + check_and_reset_hc(uhci); + } + configure_hc(uhci); + + /* Tell the core if the controller had to be reset */ + if (uhci->rh_state == UHCI_RH_RESET) + usb_root_hub_lost_power(hcd->self.root_hub); + + spin_unlock_irq(&uhci->lock); + + /* If interrupts don't work and remote wakeup is enabled then + * the suspended root hub needs to be polled. + */ + if (!uhci->RD_enable && hcd->self.root_hub->do_remote_wakeup) + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + + /* Does the root hub have a port wakeup pending? */ + usb_hcd_poll_rh_status(hcd); + return 0; +} + +#endif + +static const struct hc_driver uhci_driver = { + .description = hcd_name, + .product_desc = "UHCI Host Controller", + .hcd_priv_size = sizeof(struct uhci_hcd), + + /* Generic hardware linkage */ + .irq = uhci_irq, + .flags = HCD_USB11, + + /* Basic lifecycle operations */ + .reset = uhci_pci_init, + .start = uhci_start, +#ifdef CONFIG_PM + .pci_suspend = uhci_pci_suspend, + .pci_resume = uhci_pci_resume, + .bus_suspend = uhci_rh_suspend, + .bus_resume = uhci_rh_resume, +#endif + .stop = uhci_stop, + + .urb_enqueue = uhci_urb_enqueue, + .urb_dequeue = uhci_urb_dequeue, + + .endpoint_disable = uhci_hcd_endpoint_disable, + .get_frame_number = uhci_hcd_get_frame_number, + + .hub_status_data = uhci_hub_status_data, + .hub_control = uhci_hub_control, +}; + +static DEFINE_PCI_DEVICE_TABLE(uhci_pci_ids) = { { + /* handle any USB UHCI controller */ + PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_UHCI, ~0), + .driver_data = (unsigned long) &uhci_driver, + }, { /* end: all zeroes */ } +}; + +MODULE_DEVICE_TABLE(pci, uhci_pci_ids); + +static struct pci_driver uhci_pci_driver = { + .name = (char *)hcd_name, + .id_table = uhci_pci_ids, + + .probe = usb_hcd_pci_probe, + .remove = usb_hcd_pci_remove, + .shutdown = uhci_shutdown, + +#ifdef CONFIG_PM + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, +#endif +}; diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c new file mode 100644 index 00000000..f1db61ad --- /dev/null +++ b/drivers/usb/host/uhci-platform.c @@ -0,0 +1,166 @@ +/* + * Generic UHCI HCD (Host Controller Driver) for Platform Devices + * + * Copyright (c) 2011 Tony Prisk + * + * This file is based on uhci-grlib.c + * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu + */ + +#include +#include + +static int uhci_platform_init(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + uhci->rh_numports = uhci_count_ports(hcd); + + /* Set up pointers to to generic functions */ + uhci->reset_hc = uhci_generic_reset_hc; + uhci->check_and_reset_hc = uhci_generic_check_and_reset_hc; + + /* No special actions need to be taken for the functions below */ + uhci->configure_hc = NULL; + uhci->resume_detect_interrupts_are_broken = NULL; + uhci->global_suspend_mode_is_broken = NULL; + + /* Reset if the controller isn't already safely quiescent. */ + check_and_reset_hc(uhci); + return 0; +} + +static const struct hc_driver uhci_platform_hc_driver = { + .description = hcd_name, + .product_desc = "Generic UHCI Host Controller", + .hcd_priv_size = sizeof(struct uhci_hcd), + + /* Generic hardware linkage */ + .irq = uhci_irq, + .flags = HCD_MEMORY | HCD_USB11, + + /* Basic lifecycle operations */ + .reset = uhci_platform_init, + .start = uhci_start, +#ifdef CONFIG_PM + .pci_suspend = NULL, + .pci_resume = NULL, + .bus_suspend = uhci_rh_suspend, + .bus_resume = uhci_rh_resume, +#endif + .stop = uhci_stop, + + .urb_enqueue = uhci_urb_enqueue, + .urb_dequeue = uhci_urb_dequeue, + + .endpoint_disable = uhci_hcd_endpoint_disable, + .get_frame_number = uhci_hcd_get_frame_number, + + .hub_status_data = uhci_hub_status_data, + .hub_control = uhci_hub_control, +}; + +static int uhci_hcd_platform_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct uhci_hcd *uhci; + struct resource *res; + int ret; + + if (usb_disabled()) + return -ENODEV; + + /* + * 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. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + hcd = usb_create_hcd(&uhci_platform_hc_driver, &pdev->dev, + pdev->name); + if (!hcd) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + pr_err("%s: request_mem_region failed\n", __func__); + ret = -EBUSY; + goto err_rmr; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + pr_err("%s: ioremap failed\n", __func__); + ret = -ENOMEM; + goto err_irq; + } + uhci = hcd_to_uhci(hcd); + + uhci->regs = hcd->regs; + + ret = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_DISABLED | + IRQF_SHARED); + if (ret) + goto err_uhci; + + return 0; + +err_uhci: + iounmap(hcd->regs); +err_irq: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err_rmr: + usb_put_hcd(hcd); + + return ret; +} + +static int uhci_hcd_platform_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +/* Make sure the controller is quiescent and that we're not using it + * any more. This is mainly for the benefit of programs which, like kexec, + * expect the hardware to be idle: not doing DMA or generating IRQs. + * + * This routine may be called in a damaged or failing kernel. Hence we + * do not acquire the spinlock before shutting down the controller. + */ +static void uhci_hcd_platform_shutdown(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + uhci_hc_died(hcd_to_uhci(hcd)); +} + +static const struct of_device_id platform_uhci_ids[] = { + { .compatible = "platform-uhci", }, + {} +}; + +static struct platform_driver uhci_platform_driver = { + .probe = uhci_hcd_platform_probe, + .remove = uhci_hcd_platform_remove, + .shutdown = uhci_hcd_platform_shutdown, + .driver = { + .name = "platform-uhci", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(platform_uhci_ids), + }, +}; diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c new file mode 100644 index 00000000..da6f56d9 --- /dev/null +++ b/drivers/usb/host/uhci-q.c @@ -0,0 +1,1794 @@ +/* + * Universal Host Controller Interface driver for USB. + * + * Maintainer: Alan Stern + * + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com + * (C) Copyright 1999 Randy Dunlap + * (C) Copyright 1999 Georg Acher, acher@in.tum.de + * (C) Copyright 1999 Deti Fliegl, deti@fliegl.de + * (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch + * (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at + * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface + * support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). + * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) + * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu + */ + + +/* + * Technically, updating td->status here is a race, but it's not really a + * problem. The worst that can happen is that we set the IOC bit again + * generating a spurious interrupt. We could fix this by creating another + * QH and leaving the IOC bit always set, but then we would have to play + * games with the FSBR code to make sure we get the correct order in all + * the cases. I don't think it's worth the effort + */ +static void uhci_set_next_interrupt(struct uhci_hcd *uhci) +{ + if (uhci->is_stopped) + mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies); + uhci->term_td->status |= cpu_to_hc32(uhci, TD_CTRL_IOC); +} + +static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci) +{ + uhci->term_td->status &= ~cpu_to_hc32(uhci, TD_CTRL_IOC); +} + + +/* + * Full-Speed Bandwidth Reclamation (FSBR). + * We turn on FSBR whenever a queue that wants it is advancing, + * and leave it on for a short time thereafter. + */ +static void uhci_fsbr_on(struct uhci_hcd *uhci) +{ + struct uhci_qh *lqh; + + /* The terminating skeleton QH always points back to the first + * FSBR QH. Make the last async QH point to the terminating + * skeleton QH. */ + uhci->fsbr_is_on = 1; + lqh = list_entry(uhci->skel_async_qh->node.prev, + struct uhci_qh, node); + lqh->link = LINK_TO_QH(uhci, uhci->skel_term_qh); +} + +static void uhci_fsbr_off(struct uhci_hcd *uhci) +{ + struct uhci_qh *lqh; + + /* Remove the link from the last async QH to the terminating + * skeleton QH. */ + uhci->fsbr_is_on = 0; + lqh = list_entry(uhci->skel_async_qh->node.prev, + struct uhci_qh, node); + lqh->link = UHCI_PTR_TERM(uhci); +} + +static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb) +{ + struct urb_priv *urbp = urb->hcpriv; + + if (!(urb->transfer_flags & URB_NO_FSBR)) + urbp->fsbr = 1; +} + +static void uhci_urbp_wants_fsbr(struct uhci_hcd *uhci, struct urb_priv *urbp) +{ + if (urbp->fsbr) { + uhci->fsbr_is_wanted = 1; + if (!uhci->fsbr_is_on) + uhci_fsbr_on(uhci); + else if (uhci->fsbr_expiring) { + uhci->fsbr_expiring = 0; + del_timer(&uhci->fsbr_timer); + } + } +} + +static void uhci_fsbr_timeout(unsigned long _uhci) +{ + struct uhci_hcd *uhci = (struct uhci_hcd *) _uhci; + unsigned long flags; + + spin_lock_irqsave(&uhci->lock, flags); + if (uhci->fsbr_expiring) { + uhci->fsbr_expiring = 0; + uhci_fsbr_off(uhci); + } + spin_unlock_irqrestore(&uhci->lock, flags); +} + + +static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci) +{ + dma_addr_t dma_handle; + struct uhci_td *td; + + td = dma_pool_alloc(uhci->td_pool, GFP_ATOMIC, &dma_handle); + if (!td) + return NULL; + + td->dma_handle = dma_handle; + td->frame = -1; + + INIT_LIST_HEAD(&td->list); + INIT_LIST_HEAD(&td->fl_list); + + return td; +} + +static void uhci_free_td(struct uhci_hcd *uhci, struct uhci_td *td) +{ + if (!list_empty(&td->list)) + dev_WARN(uhci_dev(uhci), "td %p still in list!\n", td); + if (!list_empty(&td->fl_list)) + dev_WARN(uhci_dev(uhci), "td %p still in fl_list!\n", td); + + dma_pool_free(uhci->td_pool, td, td->dma_handle); +} + +static inline void uhci_fill_td(struct uhci_hcd *uhci, struct uhci_td *td, + u32 status, u32 token, u32 buffer) +{ + td->status = cpu_to_hc32(uhci, status); + td->token = cpu_to_hc32(uhci, token); + td->buffer = cpu_to_hc32(uhci, buffer); +} + +static void uhci_add_td_to_urbp(struct uhci_td *td, struct urb_priv *urbp) +{ + list_add_tail(&td->list, &urbp->td_list); +} + +static void uhci_remove_td_from_urbp(struct uhci_td *td) +{ + list_del_init(&td->list); +} + +/* + * We insert Isochronous URBs directly into the frame list at the beginning + */ +static inline void uhci_insert_td_in_frame_list(struct uhci_hcd *uhci, + struct uhci_td *td, unsigned framenum) +{ + framenum &= (UHCI_NUMFRAMES - 1); + + td->frame = framenum; + + /* Is there a TD already mapped there? */ + if (uhci->frame_cpu[framenum]) { + struct uhci_td *ftd, *ltd; + + ftd = uhci->frame_cpu[framenum]; + ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list); + + list_add_tail(&td->fl_list, &ftd->fl_list); + + td->link = ltd->link; + wmb(); + ltd->link = LINK_TO_TD(uhci, td); + } else { + td->link = uhci->frame[framenum]; + wmb(); + uhci->frame[framenum] = LINK_TO_TD(uhci, td); + uhci->frame_cpu[framenum] = td; + } +} + +static inline void uhci_remove_td_from_frame_list(struct uhci_hcd *uhci, + struct uhci_td *td) +{ + /* If it's not inserted, don't remove it */ + if (td->frame == -1) { + WARN_ON(!list_empty(&td->fl_list)); + return; + } + + if (uhci->frame_cpu[td->frame] == td) { + if (list_empty(&td->fl_list)) { + uhci->frame[td->frame] = td->link; + uhci->frame_cpu[td->frame] = NULL; + } else { + struct uhci_td *ntd; + + ntd = list_entry(td->fl_list.next, + struct uhci_td, + fl_list); + uhci->frame[td->frame] = LINK_TO_TD(uhci, ntd); + uhci->frame_cpu[td->frame] = ntd; + } + } else { + struct uhci_td *ptd; + + ptd = list_entry(td->fl_list.prev, struct uhci_td, fl_list); + ptd->link = td->link; + } + + list_del_init(&td->fl_list); + td->frame = -1; +} + +static inline void uhci_remove_tds_from_frame(struct uhci_hcd *uhci, + unsigned int framenum) +{ + struct uhci_td *ftd, *ltd; + + framenum &= (UHCI_NUMFRAMES - 1); + + ftd = uhci->frame_cpu[framenum]; + if (ftd) { + ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list); + uhci->frame[framenum] = ltd->link; + uhci->frame_cpu[framenum] = NULL; + + while (!list_empty(&ftd->fl_list)) + list_del_init(ftd->fl_list.prev); + } +} + +/* + * Remove all the TDs for an Isochronous URB from the frame list + */ +static void uhci_unlink_isochronous_tds(struct uhci_hcd *uhci, struct urb *urb) +{ + struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv; + struct uhci_td *td; + + list_for_each_entry(td, &urbp->td_list, list) + uhci_remove_td_from_frame_list(uhci, td); +} + +static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci, + struct usb_device *udev, struct usb_host_endpoint *hep) +{ + dma_addr_t dma_handle; + struct uhci_qh *qh; + + qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle); + if (!qh) + return NULL; + + memset(qh, 0, sizeof(*qh)); + qh->dma_handle = dma_handle; + + qh->element = UHCI_PTR_TERM(uhci); + qh->link = UHCI_PTR_TERM(uhci); + + INIT_LIST_HEAD(&qh->queue); + INIT_LIST_HEAD(&qh->node); + + if (udev) { /* Normal QH */ + qh->type = usb_endpoint_type(&hep->desc); + if (qh->type != USB_ENDPOINT_XFER_ISOC) { + qh->dummy_td = uhci_alloc_td(uhci); + if (!qh->dummy_td) { + dma_pool_free(uhci->qh_pool, qh, dma_handle); + return NULL; + } + } + qh->state = QH_STATE_IDLE; + qh->hep = hep; + qh->udev = udev; + hep->hcpriv = qh; + + if (qh->type == USB_ENDPOINT_XFER_INT || + qh->type == USB_ENDPOINT_XFER_ISOC) + qh->load = usb_calc_bus_time(udev->speed, + usb_endpoint_dir_in(&hep->desc), + qh->type == USB_ENDPOINT_XFER_ISOC, + usb_endpoint_maxp(&hep->desc)) + / 1000 + 1; + + } else { /* Skeleton QH */ + qh->state = QH_STATE_ACTIVE; + qh->type = -1; + } + return qh; +} + +static void uhci_free_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + WARN_ON(qh->state != QH_STATE_IDLE && qh->udev); + if (!list_empty(&qh->queue)) + dev_WARN(uhci_dev(uhci), "qh %p list not empty!\n", qh); + + list_del(&qh->node); + if (qh->udev) { + qh->hep->hcpriv = NULL; + if (qh->dummy_td) + uhci_free_td(uhci, qh->dummy_td); + } + dma_pool_free(uhci->qh_pool, qh, qh->dma_handle); +} + +/* + * When a queue is stopped and a dequeued URB is given back, adjust + * the previous TD link (if the URB isn't first on the queue) or + * save its toggle value (if it is first and is currently executing). + * + * Returns 0 if the URB should not yet be given back, 1 otherwise. + */ +static int uhci_cleanup_queue(struct uhci_hcd *uhci, struct uhci_qh *qh, + struct urb *urb) +{ + struct urb_priv *urbp = urb->hcpriv; + struct uhci_td *td; + int ret = 1; + + /* Isochronous pipes don't use toggles and their TD link pointers + * get adjusted during uhci_urb_dequeue(). But since their queues + * cannot truly be stopped, we have to watch out for dequeues + * occurring after the nominal unlink frame. */ + if (qh->type == USB_ENDPOINT_XFER_ISOC) { + ret = (uhci->frame_number + uhci->is_stopped != + qh->unlink_frame); + goto done; + } + + /* If the URB isn't first on its queue, adjust the link pointer + * of the last TD in the previous URB. The toggle doesn't need + * to be saved since this URB can't be executing yet. */ + if (qh->queue.next != &urbp->node) { + struct urb_priv *purbp; + struct uhci_td *ptd; + + purbp = list_entry(urbp->node.prev, struct urb_priv, node); + WARN_ON(list_empty(&purbp->td_list)); + ptd = list_entry(purbp->td_list.prev, struct uhci_td, + list); + td = list_entry(urbp->td_list.prev, struct uhci_td, + list); + ptd->link = td->link; + goto done; + } + + /* If the QH element pointer is UHCI_PTR_TERM then then currently + * executing URB has already been unlinked, so this one isn't it. */ + if (qh_element(qh) == UHCI_PTR_TERM(uhci)) + goto done; + qh->element = UHCI_PTR_TERM(uhci); + + /* Control pipes don't have to worry about toggles */ + if (qh->type == USB_ENDPOINT_XFER_CONTROL) + goto done; + + /* Save the next toggle value */ + WARN_ON(list_empty(&urbp->td_list)); + td = list_entry(urbp->td_list.next, struct uhci_td, list); + qh->needs_fixup = 1; + qh->initial_toggle = uhci_toggle(td_token(uhci, td)); + +done: + return ret; +} + +/* + * Fix up the data toggles for URBs in a queue, when one of them + * terminates early (short transfer, error, or dequeued). + */ +static void uhci_fixup_toggles(struct uhci_hcd *uhci, struct uhci_qh *qh, + int skip_first) +{ + struct urb_priv *urbp = NULL; + struct uhci_td *td; + unsigned int toggle = qh->initial_toggle; + unsigned int pipe; + + /* Fixups for a short transfer start with the second URB in the + * queue (the short URB is the first). */ + if (skip_first) + urbp = list_entry(qh->queue.next, struct urb_priv, node); + + /* When starting with the first URB, if the QH element pointer is + * still valid then we know the URB's toggles are okay. */ + else if (qh_element(qh) != UHCI_PTR_TERM(uhci)) + toggle = 2; + + /* Fix up the toggle for the URBs in the queue. Normally this + * loop won't run more than once: When an error or short transfer + * occurs, the queue usually gets emptied. */ + urbp = list_prepare_entry(urbp, &qh->queue, node); + list_for_each_entry_continue(urbp, &qh->queue, node) { + + /* If the first TD has the right toggle value, we don't + * need to change any toggles in this URB */ + td = list_entry(urbp->td_list.next, struct uhci_td, list); + if (toggle > 1 || uhci_toggle(td_token(uhci, td)) == toggle) { + td = list_entry(urbp->td_list.prev, struct uhci_td, + list); + toggle = uhci_toggle(td_token(uhci, td)) ^ 1; + + /* Otherwise all the toggles in the URB have to be switched */ + } else { + list_for_each_entry(td, &urbp->td_list, list) { + td->token ^= cpu_to_hc32(uhci, + TD_TOKEN_TOGGLE); + toggle ^= 1; + } + } + } + + wmb(); + pipe = list_entry(qh->queue.next, struct urb_priv, node)->urb->pipe; + usb_settoggle(qh->udev, usb_pipeendpoint(pipe), + usb_pipeout(pipe), toggle); + qh->needs_fixup = 0; +} + +/* + * Link an Isochronous QH into its skeleton's list + */ +static inline void link_iso(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + list_add_tail(&qh->node, &uhci->skel_iso_qh->node); + + /* Isochronous QHs aren't linked by the hardware */ +} + +/* + * Link a high-period interrupt QH into the schedule at the end of its + * skeleton's list + */ +static void link_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + struct uhci_qh *pqh; + + list_add_tail(&qh->node, &uhci->skelqh[qh->skel]->node); + + pqh = list_entry(qh->node.prev, struct uhci_qh, node); + qh->link = pqh->link; + wmb(); + pqh->link = LINK_TO_QH(uhci, qh); +} + +/* + * Link a period-1 interrupt or async QH into the schedule at the + * correct spot in the async skeleton's list, and update the FSBR link + */ +static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + struct uhci_qh *pqh; + __hc32 link_to_new_qh; + + /* Find the predecessor QH for our new one and insert it in the list. + * The list of QHs is expected to be short, so linear search won't + * take too long. */ + list_for_each_entry_reverse(pqh, &uhci->skel_async_qh->node, node) { + if (pqh->skel <= qh->skel) + break; + } + list_add(&qh->node, &pqh->node); + + /* Link it into the schedule */ + qh->link = pqh->link; + wmb(); + link_to_new_qh = LINK_TO_QH(uhci, qh); + pqh->link = link_to_new_qh; + + /* If this is now the first FSBR QH, link the terminating skeleton + * QH to it. */ + if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR) + uhci->skel_term_qh->link = link_to_new_qh; +} + +/* + * Put a QH on the schedule in both hardware and software + */ +static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + WARN_ON(list_empty(&qh->queue)); + + /* Set the element pointer if it isn't set already. + * This isn't needed for Isochronous queues, but it doesn't hurt. */ + if (qh_element(qh) == UHCI_PTR_TERM(uhci)) { + struct urb_priv *urbp = list_entry(qh->queue.next, + struct urb_priv, node); + struct uhci_td *td = list_entry(urbp->td_list.next, + struct uhci_td, list); + + qh->element = LINK_TO_TD(uhci, td); + } + + /* Treat the queue as if it has just advanced */ + qh->wait_expired = 0; + qh->advance_jiffies = jiffies; + + if (qh->state == QH_STATE_ACTIVE) + return; + qh->state = QH_STATE_ACTIVE; + + /* Move the QH from its old list to the correct spot in the appropriate + * skeleton's list */ + if (qh == uhci->next_qh) + uhci->next_qh = list_entry(qh->node.next, struct uhci_qh, + node); + list_del(&qh->node); + + if (qh->skel == SKEL_ISO) + link_iso(uhci, qh); + else if (qh->skel < SKEL_ASYNC) + link_interrupt(uhci, qh); + else + link_async(uhci, qh); +} + +/* + * Unlink a high-period interrupt QH from the schedule + */ +static void unlink_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + struct uhci_qh *pqh; + + pqh = list_entry(qh->node.prev, struct uhci_qh, node); + pqh->link = qh->link; + mb(); +} + +/* + * Unlink a period-1 interrupt or async QH from the schedule + */ +static void unlink_async(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + struct uhci_qh *pqh; + __hc32 link_to_next_qh = qh->link; + + pqh = list_entry(qh->node.prev, struct uhci_qh, node); + pqh->link = link_to_next_qh; + + /* If this was the old first FSBR QH, link the terminating skeleton + * QH to the next (new first FSBR) QH. */ + if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR) + uhci->skel_term_qh->link = link_to_next_qh; + mb(); +} + +/* + * Take a QH off the hardware schedule + */ +static void uhci_unlink_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + if (qh->state == QH_STATE_UNLINKING) + return; + WARN_ON(qh->state != QH_STATE_ACTIVE || !qh->udev); + qh->state = QH_STATE_UNLINKING; + + /* Unlink the QH from the schedule and record when we did it */ + if (qh->skel == SKEL_ISO) + ; + else if (qh->skel < SKEL_ASYNC) + unlink_interrupt(uhci, qh); + else + unlink_async(uhci, qh); + + uhci_get_current_frame_number(uhci); + qh->unlink_frame = uhci->frame_number; + + /* Force an interrupt so we know when the QH is fully unlinked */ + if (list_empty(&uhci->skel_unlink_qh->node) || uhci->is_stopped) + uhci_set_next_interrupt(uhci); + + /* Move the QH from its old list to the end of the unlinking list */ + if (qh == uhci->next_qh) + uhci->next_qh = list_entry(qh->node.next, struct uhci_qh, + node); + list_move_tail(&qh->node, &uhci->skel_unlink_qh->node); +} + +/* + * When we and the controller are through with a QH, it becomes IDLE. + * This happens when a QH has been off the schedule (on the unlinking + * list) for more than one frame, or when an error occurs while adding + * the first URB onto a new QH. + */ +static void uhci_make_qh_idle(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + WARN_ON(qh->state == QH_STATE_ACTIVE); + + if (qh == uhci->next_qh) + uhci->next_qh = list_entry(qh->node.next, struct uhci_qh, + node); + list_move(&qh->node, &uhci->idle_qh_list); + qh->state = QH_STATE_IDLE; + + /* Now that the QH is idle, its post_td isn't being used */ + if (qh->post_td) { + uhci_free_td(uhci, qh->post_td); + qh->post_td = NULL; + } + + /* If anyone is waiting for a QH to become idle, wake them up */ + if (uhci->num_waiting) + wake_up_all(&uhci->waitqh); +} + +/* + * Find the highest existing bandwidth load for a given phase and period. + */ +static int uhci_highest_load(struct uhci_hcd *uhci, int phase, int period) +{ + int highest_load = uhci->load[phase]; + + for (phase += period; phase < MAX_PHASE; phase += period) + highest_load = max_t(int, highest_load, uhci->load[phase]); + return highest_load; +} + +/* + * Set qh->phase to the optimal phase for a periodic transfer and + * check whether the bandwidth requirement is acceptable. + */ +static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + int minimax_load; + + /* Find the optimal phase (unless it is already set) and get + * its load value. */ + if (qh->phase >= 0) + minimax_load = uhci_highest_load(uhci, qh->phase, qh->period); + else { + int phase, load; + int max_phase = min_t(int, MAX_PHASE, qh->period); + + qh->phase = 0; + minimax_load = uhci_highest_load(uhci, qh->phase, qh->period); + for (phase = 1; phase < max_phase; ++phase) { + load = uhci_highest_load(uhci, phase, qh->period); + if (load < minimax_load) { + minimax_load = load; + qh->phase = phase; + } + } + } + + /* Maximum allowable periodic bandwidth is 90%, or 900 us per frame */ + if (minimax_load + qh->load > 900) { + dev_dbg(uhci_dev(uhci), "bandwidth allocation failed: " + "period %d, phase %d, %d + %d us\n", + qh->period, qh->phase, minimax_load, qh->load); + return -ENOSPC; + } + return 0; +} + +/* + * Reserve a periodic QH's bandwidth in the schedule + */ +static void uhci_reserve_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + int i; + int load = qh->load; + char *p = "??"; + + for (i = qh->phase; i < MAX_PHASE; i += qh->period) { + uhci->load[i] += load; + uhci->total_load += load; + } + uhci_to_hcd(uhci)->self.bandwidth_allocated = + uhci->total_load / MAX_PHASE; + switch (qh->type) { + case USB_ENDPOINT_XFER_INT: + ++uhci_to_hcd(uhci)->self.bandwidth_int_reqs; + p = "INT"; + break; + case USB_ENDPOINT_XFER_ISOC: + ++uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs; + p = "ISO"; + break; + } + qh->bandwidth_reserved = 1; + dev_dbg(uhci_dev(uhci), + "%s dev %d ep%02x-%s, period %d, phase %d, %d us\n", + "reserve", qh->udev->devnum, + qh->hep->desc.bEndpointAddress, p, + qh->period, qh->phase, load); +} + +/* + * Release a periodic QH's bandwidth reservation + */ +static void uhci_release_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + int i; + int load = qh->load; + char *p = "??"; + + for (i = qh->phase; i < MAX_PHASE; i += qh->period) { + uhci->load[i] -= load; + uhci->total_load -= load; + } + uhci_to_hcd(uhci)->self.bandwidth_allocated = + uhci->total_load / MAX_PHASE; + switch (qh->type) { + case USB_ENDPOINT_XFER_INT: + --uhci_to_hcd(uhci)->self.bandwidth_int_reqs; + p = "INT"; + break; + case USB_ENDPOINT_XFER_ISOC: + --uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs; + p = "ISO"; + break; + } + qh->bandwidth_reserved = 0; + dev_dbg(uhci_dev(uhci), + "%s dev %d ep%02x-%s, period %d, phase %d, %d us\n", + "release", qh->udev->devnum, + qh->hep->desc.bEndpointAddress, p, + qh->period, qh->phase, load); +} + +static inline struct urb_priv *uhci_alloc_urb_priv(struct uhci_hcd *uhci, + struct urb *urb) +{ + struct urb_priv *urbp; + + urbp = kmem_cache_zalloc(uhci_up_cachep, GFP_ATOMIC); + if (!urbp) + return NULL; + + urbp->urb = urb; + urb->hcpriv = urbp; + + INIT_LIST_HEAD(&urbp->node); + INIT_LIST_HEAD(&urbp->td_list); + + return urbp; +} + +static void uhci_free_urb_priv(struct uhci_hcd *uhci, + struct urb_priv *urbp) +{ + struct uhci_td *td, *tmp; + + if (!list_empty(&urbp->node)) + dev_WARN(uhci_dev(uhci), "urb %p still on QH's list!\n", + urbp->urb); + + list_for_each_entry_safe(td, tmp, &urbp->td_list, list) { + uhci_remove_td_from_urbp(td); + uhci_free_td(uhci, td); + } + + kmem_cache_free(uhci_up_cachep, urbp); +} + +/* + * Map status to standard result codes + * + * is (td_status(uhci, td) & 0xF60000), a.k.a. + * uhci_status_bits(td_status(uhci, td)). + * Note: does not include the TD_CTRL_NAK bit. + * is True for output TDs and False for input TDs. + */ +static int uhci_map_status(int status, int dir_out) +{ + if (!status) + return 0; + if (status & TD_CTRL_BITSTUFF) /* Bitstuff error */ + return -EPROTO; + if (status & TD_CTRL_CRCTIMEO) { /* CRC/Timeout */ + if (dir_out) + return -EPROTO; + else + return -EILSEQ; + } + if (status & TD_CTRL_BABBLE) /* Babble */ + return -EOVERFLOW; + if (status & TD_CTRL_DBUFERR) /* Buffer error */ + return -ENOSR; + if (status & TD_CTRL_STALLED) /* Stalled */ + return -EPIPE; + return 0; +} + +/* + * Control transfers + */ +static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb, + struct uhci_qh *qh) +{ + struct uhci_td *td; + unsigned long destination, status; + int maxsze = usb_endpoint_maxp(&qh->hep->desc); + int len = urb->transfer_buffer_length; + dma_addr_t data = urb->transfer_dma; + __hc32 *plink; + struct urb_priv *urbp = urb->hcpriv; + int skel; + + /* The "pipe" thing contains the destination in bits 8--18 */ + destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP; + + /* 3 errors, dummy TD remains inactive */ + status = uhci_maxerr(3); + if (urb->dev->speed == USB_SPEED_LOW) + status |= TD_CTRL_LS; + + /* + * Build the TD for the control request setup packet + */ + td = qh->dummy_td; + uhci_add_td_to_urbp(td, urbp); + uhci_fill_td(uhci, td, status, destination | uhci_explen(8), + urb->setup_dma); + plink = &td->link; + status |= TD_CTRL_ACTIVE; + + /* + * If direction is "send", change the packet ID from SETUP (0x2D) + * to OUT (0xE1). Else change it from SETUP to IN (0x69) and + * set Short Packet Detect (SPD) for all data packets. + * + * 0-length transfers always get treated as "send". + */ + if (usb_pipeout(urb->pipe) || len == 0) + destination ^= (USB_PID_SETUP ^ USB_PID_OUT); + else { + destination ^= (USB_PID_SETUP ^ USB_PID_IN); + status |= TD_CTRL_SPD; + } + + /* + * Build the DATA TDs + */ + while (len > 0) { + int pktsze = maxsze; + + if (len <= pktsze) { /* The last data packet */ + pktsze = len; + status &= ~TD_CTRL_SPD; + } + + td = uhci_alloc_td(uhci); + if (!td) + goto nomem; + *plink = LINK_TO_TD(uhci, td); + + /* Alternate Data0/1 (start with Data1) */ + destination ^= TD_TOKEN_TOGGLE; + + uhci_add_td_to_urbp(td, urbp); + uhci_fill_td(uhci, td, status, + destination | uhci_explen(pktsze), data); + plink = &td->link; + + data += pktsze; + len -= pktsze; + } + + /* + * Build the final TD for control status + */ + td = uhci_alloc_td(uhci); + if (!td) + goto nomem; + *plink = LINK_TO_TD(uhci, td); + + /* Change direction for the status transaction */ + destination ^= (USB_PID_IN ^ USB_PID_OUT); + destination |= TD_TOKEN_TOGGLE; /* End in Data1 */ + + uhci_add_td_to_urbp(td, urbp); + uhci_fill_td(uhci, td, status | TD_CTRL_IOC, + destination | uhci_explen(0), 0); + plink = &td->link; + + /* + * Build the new dummy TD and activate the old one + */ + td = uhci_alloc_td(uhci); + if (!td) + goto nomem; + *plink = LINK_TO_TD(uhci, td); + + uhci_fill_td(uhci, td, 0, USB_PID_OUT | uhci_explen(0), 0); + wmb(); + qh->dummy_td->status |= cpu_to_hc32(uhci, TD_CTRL_ACTIVE); + qh->dummy_td = td; + + /* Low-speed transfers get a different queue, and won't hog the bus. + * Also, some devices enumerate better without FSBR; the easiest way + * to do that is to put URBs on the low-speed queue while the device + * isn't in the CONFIGURED state. */ + if (urb->dev->speed == USB_SPEED_LOW || + urb->dev->state != USB_STATE_CONFIGURED) + skel = SKEL_LS_CONTROL; + else { + skel = SKEL_FS_CONTROL; + uhci_add_fsbr(uhci, urb); + } + if (qh->state != QH_STATE_ACTIVE) + qh->skel = skel; + return 0; + +nomem: + /* Remove the dummy TD from the td_list so it doesn't get freed */ + uhci_remove_td_from_urbp(qh->dummy_td); + return -ENOMEM; +} + +/* + * Common submit for bulk and interrupt + */ +static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb, + struct uhci_qh *qh) +{ + struct uhci_td *td; + unsigned long destination, status; + int maxsze = usb_endpoint_maxp(&qh->hep->desc); + int len = urb->transfer_buffer_length; + int this_sg_len; + dma_addr_t data; + __hc32 *plink; + struct urb_priv *urbp = urb->hcpriv; + unsigned int toggle; + struct scatterlist *sg; + int i; + + if (len < 0) + return -EINVAL; + + /* The "pipe" thing contains the destination in bits 8--18 */ + destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe); + toggle = usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe)); + + /* 3 errors, dummy TD remains inactive */ + status = uhci_maxerr(3); + if (urb->dev->speed == USB_SPEED_LOW) + status |= TD_CTRL_LS; + if (usb_pipein(urb->pipe)) + status |= TD_CTRL_SPD; + + i = urb->num_mapped_sgs; + if (len > 0 && i > 0) { + sg = urb->sg; + data = sg_dma_address(sg); + + /* urb->transfer_buffer_length may be smaller than the + * size of the scatterlist (or vice versa) + */ + this_sg_len = min_t(int, sg_dma_len(sg), len); + } else { + sg = NULL; + data = urb->transfer_dma; + this_sg_len = len; + } + /* + * Build the DATA TDs + */ + plink = NULL; + td = qh->dummy_td; + for (;;) { /* Allow zero length packets */ + int pktsze = maxsze; + + if (len <= pktsze) { /* The last packet */ + pktsze = len; + if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) + status &= ~TD_CTRL_SPD; + } + + if (plink) { + td = uhci_alloc_td(uhci); + if (!td) + goto nomem; + *plink = LINK_TO_TD(uhci, td); + } + uhci_add_td_to_urbp(td, urbp); + uhci_fill_td(uhci, td, status, + destination | uhci_explen(pktsze) | + (toggle << TD_TOKEN_TOGGLE_SHIFT), + data); + plink = &td->link; + status |= TD_CTRL_ACTIVE; + + toggle ^= 1; + data += pktsze; + this_sg_len -= pktsze; + len -= maxsze; + if (this_sg_len <= 0) { + if (--i <= 0 || len <= 0) + break; + sg = sg_next(sg); + data = sg_dma_address(sg); + this_sg_len = min_t(int, sg_dma_len(sg), len); + } + } + + /* + * URB_ZERO_PACKET means adding a 0-length packet, if direction + * is OUT and the transfer_length was an exact multiple of maxsze, + * hence (len = transfer_length - N * maxsze) == 0 + * however, if transfer_length == 0, the zero packet was already + * prepared above. + */ + if ((urb->transfer_flags & URB_ZERO_PACKET) && + usb_pipeout(urb->pipe) && len == 0 && + urb->transfer_buffer_length > 0) { + td = uhci_alloc_td(uhci); + if (!td) + goto nomem; + *plink = LINK_TO_TD(uhci, td); + + uhci_add_td_to_urbp(td, urbp); + uhci_fill_td(uhci, td, status, + destination | uhci_explen(0) | + (toggle << TD_TOKEN_TOGGLE_SHIFT), + data); + plink = &td->link; + + toggle ^= 1; + } + + /* Set the interrupt-on-completion flag on the last packet. + * A more-or-less typical 4 KB URB (= size of one memory page) + * will require about 3 ms to transfer; that's a little on the + * fast side but not enough to justify delaying an interrupt + * more than 2 or 3 URBs, so we will ignore the URB_NO_INTERRUPT + * flag setting. */ + td->status |= cpu_to_hc32(uhci, TD_CTRL_IOC); + + /* + * Build the new dummy TD and activate the old one + */ + td = uhci_alloc_td(uhci); + if (!td) + goto nomem; + *plink = LINK_TO_TD(uhci, td); + + uhci_fill_td(uhci, td, 0, USB_PID_OUT | uhci_explen(0), 0); + wmb(); + qh->dummy_td->status |= cpu_to_hc32(uhci, TD_CTRL_ACTIVE); + qh->dummy_td = td; + + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), toggle); + return 0; + +nomem: + /* Remove the dummy TD from the td_list so it doesn't get freed */ + uhci_remove_td_from_urbp(qh->dummy_td); + return -ENOMEM; +} + +static int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb, + struct uhci_qh *qh) +{ + int ret; + + /* Can't have low-speed bulk transfers */ + if (urb->dev->speed == USB_SPEED_LOW) + return -EINVAL; + + if (qh->state != QH_STATE_ACTIVE) + qh->skel = SKEL_BULK; + ret = uhci_submit_common(uhci, urb, qh); + if (ret == 0) + uhci_add_fsbr(uhci, urb); + return ret; +} + +static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb, + struct uhci_qh *qh) +{ + int ret; + + /* USB 1.1 interrupt transfers only involve one packet per interval. + * Drivers can submit URBs of any length, but longer ones will need + * multiple intervals to complete. + */ + + if (!qh->bandwidth_reserved) { + int exponent; + + /* Figure out which power-of-two queue to use */ + for (exponent = 7; exponent >= 0; --exponent) { + if ((1 << exponent) <= urb->interval) + break; + } + if (exponent < 0) + return -EINVAL; + + /* If the slot is full, try a lower period */ + do { + qh->period = 1 << exponent; + qh->skel = SKEL_INDEX(exponent); + + /* For now, interrupt phase is fixed by the layout + * of the QH lists. + */ + qh->phase = (qh->period / 2) & (MAX_PHASE - 1); + ret = uhci_check_bandwidth(uhci, qh); + } while (ret != 0 && --exponent >= 0); + if (ret) + return ret; + } else if (qh->period > urb->interval) + return -EINVAL; /* Can't decrease the period */ + + ret = uhci_submit_common(uhci, urb, qh); + if (ret == 0) { + urb->interval = qh->period; + if (!qh->bandwidth_reserved) + uhci_reserve_bandwidth(uhci, qh); + } + return ret; +} + +/* + * Fix up the data structures following a short transfer + */ +static int uhci_fixup_short_transfer(struct uhci_hcd *uhci, + struct uhci_qh *qh, struct urb_priv *urbp) +{ + struct uhci_td *td; + struct list_head *tmp; + int ret; + + td = list_entry(urbp->td_list.prev, struct uhci_td, list); + if (qh->type == USB_ENDPOINT_XFER_CONTROL) { + + /* When a control transfer is short, we have to restart + * the queue at the status stage transaction, which is + * the last TD. */ + WARN_ON(list_empty(&urbp->td_list)); + qh->element = LINK_TO_TD(uhci, td); + tmp = td->list.prev; + ret = -EINPROGRESS; + + } else { + + /* When a bulk/interrupt transfer is short, we have to + * fix up the toggles of the following URBs on the queue + * before restarting the queue at the next URB. */ + qh->initial_toggle = + uhci_toggle(td_token(uhci, qh->post_td)) ^ 1; + uhci_fixup_toggles(uhci, qh, 1); + + if (list_empty(&urbp->td_list)) + td = qh->post_td; + qh->element = td->link; + tmp = urbp->td_list.prev; + ret = 0; + } + + /* Remove all the TDs we skipped over, from tmp back to the start */ + while (tmp != &urbp->td_list) { + td = list_entry(tmp, struct uhci_td, list); + tmp = tmp->prev; + + uhci_remove_td_from_urbp(td); + uhci_free_td(uhci, td); + } + return ret; +} + +/* + * Common result for control, bulk, and interrupt + */ +static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb) +{ + struct urb_priv *urbp = urb->hcpriv; + struct uhci_qh *qh = urbp->qh; + struct uhci_td *td, *tmp; + unsigned status; + int ret = 0; + + list_for_each_entry_safe(td, tmp, &urbp->td_list, list) { + unsigned int ctrlstat; + int len; + + ctrlstat = td_status(uhci, td); + status = uhci_status_bits(ctrlstat); + if (status & TD_CTRL_ACTIVE) + return -EINPROGRESS; + + len = uhci_actual_length(ctrlstat); + urb->actual_length += len; + + if (status) { + ret = uhci_map_status(status, + uhci_packetout(td_token(uhci, td))); + if ((debug == 1 && ret != -EPIPE) || debug > 1) { + /* Some debugging code */ + dev_dbg(&urb->dev->dev, + "%s: failed with status %x\n", + __func__, status); + + if (debug > 1 && errbuf) { + /* Print the chain for debugging */ + uhci_show_qh(uhci, urbp->qh, errbuf, + ERRBUF_LEN - EXTRA_SPACE, 0); + lprintk(errbuf); + } + } + + /* Did we receive a short packet? */ + } else if (len < uhci_expected_length(td_token(uhci, td))) { + + /* For control transfers, go to the status TD if + * this isn't already the last data TD */ + if (qh->type == USB_ENDPOINT_XFER_CONTROL) { + if (td->list.next != urbp->td_list.prev) + ret = 1; + } + + /* For bulk and interrupt, this may be an error */ + else if (urb->transfer_flags & URB_SHORT_NOT_OK) + ret = -EREMOTEIO; + + /* Fixup needed only if this isn't the URB's last TD */ + else if (&td->list != urbp->td_list.prev) + ret = 1; + } + + uhci_remove_td_from_urbp(td); + if (qh->post_td) + uhci_free_td(uhci, qh->post_td); + qh->post_td = td; + + if (ret != 0) + goto err; + } + return ret; + +err: + if (ret < 0) { + /* Note that the queue has stopped and save + * the next toggle value */ + qh->element = UHCI_PTR_TERM(uhci); + qh->is_stopped = 1; + qh->needs_fixup = (qh->type != USB_ENDPOINT_XFER_CONTROL); + qh->initial_toggle = uhci_toggle(td_token(uhci, td)) ^ + (ret == -EREMOTEIO); + + } else /* Short packet received */ + ret = uhci_fixup_short_transfer(uhci, qh, urbp); + return ret; +} + +/* + * Isochronous transfers + */ +static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb, + struct uhci_qh *qh) +{ + struct uhci_td *td = NULL; /* Since urb->number_of_packets > 0 */ + int i; + unsigned frame, next; + unsigned long destination, status; + struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv; + + /* Values must not be too big (could overflow below) */ + if (urb->interval >= UHCI_NUMFRAMES || + urb->number_of_packets >= UHCI_NUMFRAMES) + return -EFBIG; + + uhci_get_current_frame_number(uhci); + + /* Check the period and figure out the starting frame number */ + if (!qh->bandwidth_reserved) { + qh->period = urb->interval; + qh->phase = -1; /* Find the best phase */ + i = uhci_check_bandwidth(uhci, qh); + if (i) + return i; + + /* Allow a little time to allocate the TDs */ + next = uhci->frame_number + 10; + frame = qh->phase; + + /* Round up to the first available slot */ + frame += (next - frame + qh->period - 1) & -qh->period; + + } else if (qh->period != urb->interval) { + return -EINVAL; /* Can't change the period */ + + } else { + next = uhci->frame_number + 1; + + /* Find the next unused frame */ + if (list_empty(&qh->queue)) { + frame = qh->iso_frame; + } else { + struct urb *lurb; + + lurb = list_entry(qh->queue.prev, + struct urb_priv, node)->urb; + frame = lurb->start_frame + + lurb->number_of_packets * + lurb->interval; + } + + /* Fell behind? */ + if (!uhci_frame_before_eq(next, frame)) { + + /* USB_ISO_ASAP: Round up to the first available slot */ + if (urb->transfer_flags & URB_ISO_ASAP) + frame += (next - frame + qh->period - 1) & + -qh->period; + + /* + * Not ASAP: Use the next slot in the stream, + * no matter what. + */ + else if (!uhci_frame_before_eq(next, + frame + (urb->number_of_packets - 1) * + qh->period)) + dev_dbg(uhci_dev(uhci), "iso underrun %p (%u+%u < %u)\n", + urb, frame, + (urb->number_of_packets - 1) * + qh->period, + next); + } + } + + /* Make sure we won't have to go too far into the future */ + if (uhci_frame_before_eq(uhci->last_iso_frame + UHCI_NUMFRAMES, + frame + urb->number_of_packets * urb->interval)) + return -EFBIG; + urb->start_frame = frame; + + status = TD_CTRL_ACTIVE | TD_CTRL_IOS; + destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe); + + for (i = 0; i < urb->number_of_packets; i++) { + td = uhci_alloc_td(uhci); + if (!td) + return -ENOMEM; + + uhci_add_td_to_urbp(td, urbp); + uhci_fill_td(uhci, td, status, destination | + uhci_explen(urb->iso_frame_desc[i].length), + urb->transfer_dma + + urb->iso_frame_desc[i].offset); + } + + /* Set the interrupt-on-completion flag on the last packet. */ + td->status |= cpu_to_hc32(uhci, TD_CTRL_IOC); + + /* Add the TDs to the frame list */ + frame = urb->start_frame; + list_for_each_entry(td, &urbp->td_list, list) { + uhci_insert_td_in_frame_list(uhci, td, frame); + frame += qh->period; + } + + if (list_empty(&qh->queue)) { + qh->iso_packet_desc = &urb->iso_frame_desc[0]; + qh->iso_frame = urb->start_frame; + } + + qh->skel = SKEL_ISO; + if (!qh->bandwidth_reserved) + uhci_reserve_bandwidth(uhci, qh); + return 0; +} + +static int uhci_result_isochronous(struct uhci_hcd *uhci, struct urb *urb) +{ + struct uhci_td *td, *tmp; + struct urb_priv *urbp = urb->hcpriv; + struct uhci_qh *qh = urbp->qh; + + list_for_each_entry_safe(td, tmp, &urbp->td_list, list) { + unsigned int ctrlstat; + int status; + int actlength; + + if (uhci_frame_before_eq(uhci->cur_iso_frame, qh->iso_frame)) + return -EINPROGRESS; + + uhci_remove_tds_from_frame(uhci, qh->iso_frame); + + ctrlstat = td_status(uhci, td); + if (ctrlstat & TD_CTRL_ACTIVE) { + status = -EXDEV; /* TD was added too late? */ + } else { + status = uhci_map_status(uhci_status_bits(ctrlstat), + usb_pipeout(urb->pipe)); + actlength = uhci_actual_length(ctrlstat); + + urb->actual_length += actlength; + qh->iso_packet_desc->actual_length = actlength; + qh->iso_packet_desc->status = status; + } + if (status) + urb->error_count++; + + uhci_remove_td_from_urbp(td); + uhci_free_td(uhci, td); + qh->iso_frame += qh->period; + ++qh->iso_packet_desc; + } + return 0; +} + +static int uhci_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, gfp_t mem_flags) +{ + int ret; + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + unsigned long flags; + struct urb_priv *urbp; + struct uhci_qh *qh; + + spin_lock_irqsave(&uhci->lock, flags); + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto done_not_linked; + + ret = -ENOMEM; + urbp = uhci_alloc_urb_priv(uhci, urb); + if (!urbp) + goto done; + + if (urb->ep->hcpriv) + qh = urb->ep->hcpriv; + else { + qh = uhci_alloc_qh(uhci, urb->dev, urb->ep); + if (!qh) + goto err_no_qh; + } + urbp->qh = qh; + + switch (qh->type) { + case USB_ENDPOINT_XFER_CONTROL: + ret = uhci_submit_control(uhci, urb, qh); + break; + case USB_ENDPOINT_XFER_BULK: + ret = uhci_submit_bulk(uhci, urb, qh); + break; + case USB_ENDPOINT_XFER_INT: + ret = uhci_submit_interrupt(uhci, urb, qh); + break; + case USB_ENDPOINT_XFER_ISOC: + urb->error_count = 0; + ret = uhci_submit_isochronous(uhci, urb, qh); + break; + } + if (ret != 0) + goto err_submit_failed; + + /* Add this URB to the QH */ + list_add_tail(&urbp->node, &qh->queue); + + /* If the new URB is the first and only one on this QH then either + * the QH is new and idle or else it's unlinked and waiting to + * become idle, so we can activate it right away. But only if the + * queue isn't stopped. */ + if (qh->queue.next == &urbp->node && !qh->is_stopped) { + uhci_activate_qh(uhci, qh); + uhci_urbp_wants_fsbr(uhci, urbp); + } + goto done; + +err_submit_failed: + if (qh->state == QH_STATE_IDLE) + uhci_make_qh_idle(uhci, qh); /* Reclaim unused QH */ +err_no_qh: + uhci_free_urb_priv(uhci, urbp); +done: + if (ret) + usb_hcd_unlink_urb_from_ep(hcd, urb); +done_not_linked: + spin_unlock_irqrestore(&uhci->lock, flags); + return ret; +} + +static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + unsigned long flags; + struct uhci_qh *qh; + int rc; + + spin_lock_irqsave(&uhci->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + qh = ((struct urb_priv *) urb->hcpriv)->qh; + + /* Remove Isochronous TDs from the frame list ASAP */ + if (qh->type == USB_ENDPOINT_XFER_ISOC) { + uhci_unlink_isochronous_tds(uhci, urb); + mb(); + + /* If the URB has already started, update the QH unlink time */ + uhci_get_current_frame_number(uhci); + if (uhci_frame_before_eq(urb->start_frame, uhci->frame_number)) + qh->unlink_frame = uhci->frame_number; + } + + uhci_unlink_qh(uhci, qh); + +done: + spin_unlock_irqrestore(&uhci->lock, flags); + return rc; +} + +/* + * Finish unlinking an URB and give it back + */ +static void uhci_giveback_urb(struct uhci_hcd *uhci, struct uhci_qh *qh, + struct urb *urb, int status) +__releases(uhci->lock) +__acquires(uhci->lock) +{ + struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv; + + if (qh->type == USB_ENDPOINT_XFER_CONTROL) { + + /* Subtract off the length of the SETUP packet from + * urb->actual_length. + */ + urb->actual_length -= min_t(u32, 8, urb->actual_length); + } + + /* When giving back the first URB in an Isochronous queue, + * reinitialize the QH's iso-related members for the next URB. */ + else if (qh->type == USB_ENDPOINT_XFER_ISOC && + urbp->node.prev == &qh->queue && + urbp->node.next != &qh->queue) { + struct urb *nurb = list_entry(urbp->node.next, + struct urb_priv, node)->urb; + + qh->iso_packet_desc = &nurb->iso_frame_desc[0]; + qh->iso_frame = nurb->start_frame; + } + + /* Take the URB off the QH's queue. If the queue is now empty, + * this is a perfect time for a toggle fixup. */ + list_del_init(&urbp->node); + if (list_empty(&qh->queue) && qh->needs_fixup) { + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), qh->initial_toggle); + qh->needs_fixup = 0; + } + + uhci_free_urb_priv(uhci, urbp); + usb_hcd_unlink_urb_from_ep(uhci_to_hcd(uhci), urb); + + spin_unlock(&uhci->lock); + usb_hcd_giveback_urb(uhci_to_hcd(uhci), urb, status); + spin_lock(&uhci->lock); + + /* If the queue is now empty, we can unlink the QH and give up its + * reserved bandwidth. */ + if (list_empty(&qh->queue)) { + uhci_unlink_qh(uhci, qh); + if (qh->bandwidth_reserved) + uhci_release_bandwidth(uhci, qh); + } +} + +/* + * Scan the URBs in a QH's queue + */ +#define QH_FINISHED_UNLINKING(qh) \ + (qh->state == QH_STATE_UNLINKING && \ + uhci->frame_number + uhci->is_stopped != qh->unlink_frame) + +static void uhci_scan_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + struct urb_priv *urbp; + struct urb *urb; + int status; + + while (!list_empty(&qh->queue)) { + urbp = list_entry(qh->queue.next, struct urb_priv, node); + urb = urbp->urb; + + if (qh->type == USB_ENDPOINT_XFER_ISOC) + status = uhci_result_isochronous(uhci, urb); + else + status = uhci_result_common(uhci, urb); + if (status == -EINPROGRESS) + break; + + /* Dequeued but completed URBs can't be given back unless + * the QH is stopped or has finished unlinking. */ + if (urb->unlinked) { + if (QH_FINISHED_UNLINKING(qh)) + qh->is_stopped = 1; + else if (!qh->is_stopped) + return; + } + + uhci_giveback_urb(uhci, qh, urb, status); + if (status < 0) + break; + } + + /* If the QH is neither stopped nor finished unlinking (normal case), + * our work here is done. */ + if (QH_FINISHED_UNLINKING(qh)) + qh->is_stopped = 1; + else if (!qh->is_stopped) + return; + + /* Otherwise give back each of the dequeued URBs */ +restart: + list_for_each_entry(urbp, &qh->queue, node) { + urb = urbp->urb; + if (urb->unlinked) { + + /* Fix up the TD links and save the toggles for + * non-Isochronous queues. For Isochronous queues, + * test for too-recent dequeues. */ + if (!uhci_cleanup_queue(uhci, qh, urb)) { + qh->is_stopped = 0; + return; + } + uhci_giveback_urb(uhci, qh, urb, 0); + goto restart; + } + } + qh->is_stopped = 0; + + /* There are no more dequeued URBs. If there are still URBs on the + * queue, the QH can now be re-activated. */ + if (!list_empty(&qh->queue)) { + if (qh->needs_fixup) + uhci_fixup_toggles(uhci, qh, 0); + + /* If the first URB on the queue wants FSBR but its time + * limit has expired, set the next TD to interrupt on + * completion before reactivating the QH. */ + urbp = list_entry(qh->queue.next, struct urb_priv, node); + if (urbp->fsbr && qh->wait_expired) { + struct uhci_td *td = list_entry(urbp->td_list.next, + struct uhci_td, list); + + td->status |= cpu_to_hc32(uhci, TD_CTRL_IOC); + } + + uhci_activate_qh(uhci, qh); + } + + /* The queue is empty. The QH can become idle if it is fully + * unlinked. */ + else if (QH_FINISHED_UNLINKING(qh)) + uhci_make_qh_idle(uhci, qh); +} + +/* + * Check for queues that have made some forward progress. + * Returns 0 if the queue is not Isochronous, is ACTIVE, and + * has not advanced since last examined; 1 otherwise. + * + * Early Intel controllers have a bug which causes qh->element sometimes + * not to advance when a TD completes successfully. The queue remains + * stuck on the inactive completed TD. We detect such cases and advance + * the element pointer by hand. + */ +static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh) +{ + struct urb_priv *urbp = NULL; + struct uhci_td *td; + int ret = 1; + unsigned status; + + if (qh->type == USB_ENDPOINT_XFER_ISOC) + goto done; + + /* Treat an UNLINKING queue as though it hasn't advanced. + * This is okay because reactivation will treat it as though + * it has advanced, and if it is going to become IDLE then + * this doesn't matter anyway. Furthermore it's possible + * for an UNLINKING queue not to have any URBs at all, or + * for its first URB not to have any TDs (if it was dequeued + * just as it completed). So it's not easy in any case to + * test whether such queues have advanced. */ + if (qh->state != QH_STATE_ACTIVE) { + urbp = NULL; + status = 0; + + } else { + urbp = list_entry(qh->queue.next, struct urb_priv, node); + td = list_entry(urbp->td_list.next, struct uhci_td, list); + status = td_status(uhci, td); + if (!(status & TD_CTRL_ACTIVE)) { + + /* We're okay, the queue has advanced */ + qh->wait_expired = 0; + qh->advance_jiffies = jiffies; + goto done; + } + ret = uhci->is_stopped; + } + + /* The queue hasn't advanced; check for timeout */ + if (qh->wait_expired) + goto done; + + if (time_after(jiffies, qh->advance_jiffies + QH_WAIT_TIMEOUT)) { + + /* Detect the Intel bug and work around it */ + if (qh->post_td && qh_element(qh) == + LINK_TO_TD(uhci, qh->post_td)) { + qh->element = qh->post_td->link; + qh->advance_jiffies = jiffies; + ret = 1; + goto done; + } + + qh->wait_expired = 1; + + /* If the current URB wants FSBR, unlink it temporarily + * so that we can safely set the next TD to interrupt on + * completion. That way we'll know as soon as the queue + * starts moving again. */ + if (urbp && urbp->fsbr && !(status & TD_CTRL_IOC)) + uhci_unlink_qh(uhci, qh); + + } else { + /* Unmoving but not-yet-expired queues keep FSBR alive */ + if (urbp) + uhci_urbp_wants_fsbr(uhci, urbp); + } + +done: + return ret; +} + +/* + * Process events in the schedule, but only in one thread at a time + */ +static void uhci_scan_schedule(struct uhci_hcd *uhci) +{ + int i; + struct uhci_qh *qh; + + /* Don't allow re-entrant calls */ + if (uhci->scan_in_progress) { + uhci->need_rescan = 1; + return; + } + uhci->scan_in_progress = 1; +rescan: + uhci->need_rescan = 0; + uhci->fsbr_is_wanted = 0; + + uhci_clear_next_interrupt(uhci); + uhci_get_current_frame_number(uhci); + uhci->cur_iso_frame = uhci->frame_number; + + /* Go through all the QH queues and process the URBs in each one */ + for (i = 0; i < UHCI_NUM_SKELQH - 1; ++i) { + uhci->next_qh = list_entry(uhci->skelqh[i]->node.next, + struct uhci_qh, node); + while ((qh = uhci->next_qh) != uhci->skelqh[i]) { + uhci->next_qh = list_entry(qh->node.next, + struct uhci_qh, node); + + if (uhci_advance_check(uhci, qh)) { + uhci_scan_qh(uhci, qh); + if (qh->state == QH_STATE_ACTIVE) { + uhci_urbp_wants_fsbr(uhci, + list_entry(qh->queue.next, struct urb_priv, node)); + } + } + } + } + + uhci->last_iso_frame = uhci->cur_iso_frame; + if (uhci->need_rescan) + goto rescan; + uhci->scan_in_progress = 0; + + if (uhci->fsbr_is_on && !uhci->fsbr_is_wanted && + !uhci->fsbr_expiring) { + uhci->fsbr_expiring = 1; + mod_timer(&uhci->fsbr_timer, jiffies + FSBR_OFF_DELAY); + } + + if (list_empty(&uhci->skel_unlink_qh->node)) + uhci_clear_next_interrupt(uhci); + else + uhci_set_next_interrupt(uhci); +} diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild new file mode 100644 index 00000000..26df0138 --- /dev/null +++ b/drivers/usb/host/whci/Kbuild @@ -0,0 +1,12 @@ +obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o + +whci-hcd-y := \ + asl.o \ + debug.o \ + hcd.o \ + hw.o \ + init.o \ + int.o \ + pzl.o \ + qset.o \ + wusb.o diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c new file mode 100644 index 00000000..77324930 --- /dev/null +++ b/drivers/usb/host/whci/asl.c @@ -0,0 +1,389 @@ +/* + * Wireless Host Controller (WHC) asynchronous schedule management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset, + struct whc_qset **next, struct whc_qset **prev) +{ + struct list_head *n, *p; + + BUG_ON(list_empty(&whc->async_list)); + + n = qset->list_node.next; + if (n == &whc->async_list) + n = n->next; + p = qset->list_node.prev; + if (p == &whc->async_list) + p = p->prev; + + *next = container_of(n, struct whc_qset, list_node); + *prev = container_of(p, struct whc_qset, list_node); + +} + +static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset) +{ + list_move(&qset->list_node, &whc->async_list); + qset->in_sw_list = true; +} + +static void asl_qset_insert(struct whc *whc, struct whc_qset *qset) +{ + struct whc_qset *next, *prev; + + qset_clear(whc, qset); + + /* Link into ASL. */ + qset_get_next_prev(whc, qset, &next, &prev); + whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma); + whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma); + qset->in_hw_list = true; +} + +static void asl_qset_remove(struct whc *whc, struct whc_qset *qset) +{ + struct whc_qset *prev, *next; + + qset_get_next_prev(whc, qset, &next, &prev); + + list_move(&qset->list_node, &whc->async_removed_list); + qset->in_sw_list = false; + + /* + * No more qsets in the ASL? The caller must stop the ASL as + * it's no longer valid. + */ + if (list_empty(&whc->async_list)) + return; + + /* Remove from ASL. */ + whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma); + qset->in_hw_list = false; +} + +/** + * process_qset - process any recently inactivated or halted qTDs in a + * qset. + * + * After inactive qTDs are removed, new qTDs can be added if the + * urb queue still contains URBs. + * + * Returns any additional WUSBCMD bits for the ASL sync command (i.e., + * WUSBCMD_ASYNC_QSET_RM if a halted qset was removed). + */ +static uint32_t process_qset(struct whc *whc, struct whc_qset *qset) +{ + enum whc_update update = 0; + uint32_t status = 0; + + while (qset->ntds) { + struct whc_qtd *td; + int t; + + t = qset->td_start; + td = &qset->qtd[qset->td_start]; + status = le32_to_cpu(td->status); + + /* + * Nothing to do with a still active qTD. + */ + if (status & QTD_STS_ACTIVE) + break; + + if (status & QTD_STS_HALTED) { + /* Ug, an error. */ + process_halted_qtd(whc, qset, td); + /* A halted qTD always triggers an update + because the qset was either removed or + reactivated. */ + update |= WHC_UPDATE_UPDATED; + goto done; + } + + /* Mmm, a completed qTD. */ + process_inactive_qtd(whc, qset, td); + } + + if (!qset->remove) + update |= qset_add_qtds(whc, qset); + +done: + /* + * Remove this qset from the ASL if requested, but only if has + * no qTDs. + */ + if (qset->remove && qset->ntds == 0) { + asl_qset_remove(whc, qset); + update |= WHC_UPDATE_REMOVED; + } + return update; +} + +void asl_start(struct whc *whc) +{ + struct whc_qset *qset; + + qset = list_first_entry(&whc->async_list, struct whc_qset, list_node); + + le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR); + + whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED, + 1000, "start ASL"); +} + +void asl_stop(struct whc *whc) +{ + whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_ASYNC_SCHED, 0, + 1000, "stop ASL"); +} + +/** + * asl_update - request an ASL update and wait for the hardware to be synced + * @whc: the WHCI HC + * @wusbcmd: WUSBCMD value to start the update. + * + * If the WUSB HC is inactive (i.e., the ASL is stopped) then the + * update must be skipped as the hardware may not respond to update + * requests. + */ +void asl_update(struct whc *whc, uint32_t wusbcmd) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + long t; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + t = wait_event_timeout( + whc->async_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0, + msecs_to_jiffies(1000)); + if (t == 0) + whc_hw_error(whc, "ASL update timeout"); + } + mutex_unlock(&wusbhc->mutex); +} + +/** + * scan_async_work - scan the ASL for qsets to process. + * + * Process each qset in the ASL in turn and then signal the WHC that + * the ASL has been updated. + * + * Then start, stop or update the asynchronous schedule as required. + */ +void scan_async_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, async_work); + struct whc_qset *qset, *t; + enum whc_update update = 0; + + spin_lock_irq(&whc->lock); + + /* + * Transerve the software list backwards so new qsets can be + * safely inserted into the ASL without making it non-circular. + */ + list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) { + if (!qset->in_hw_list) { + asl_qset_insert(whc, qset); + update |= WHC_UPDATE_ADDED; + } + + update |= process_qset(whc, qset); + } + + spin_unlock_irq(&whc->lock); + + if (update) { + uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB; + if (update & WHC_UPDATE_REMOVED) + wusbcmd |= WUSBCMD_ASYNC_QSET_RM; + asl_update(whc, wusbcmd); + } + + /* + * Now that the ASL is updated, complete the removal of any + * removed qsets. + * + * If the qset was to be reset, do so and reinsert it into the + * ASL if it has pending transfers. + */ + spin_lock_irq(&whc->lock); + + list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) { + qset_remove_complete(whc, qset); + if (qset->reset) { + qset_reset(whc, qset); + if (!list_empty(&qset->stds)) { + asl_qset_insert_begin(whc, qset); + queue_work(whc->workqueue, &whc->async_work); + } + } + } + + spin_unlock_irq(&whc->lock); +} + +/** + * asl_urb_enqueue - queue an URB onto the asynchronous list (ASL). + * @whc: the WHCI host controller + * @urb: the URB to enqueue + * @mem_flags: flags for any memory allocations + * + * The qset for the endpoint is obtained and the urb queued on to it. + * + * Work is scheduled to update the hardware's view of the ASL. + */ +int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags) +{ + struct whc_qset *qset; + int err; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb); + if (err < 0) { + spin_unlock_irqrestore(&whc->lock, flags); + return err; + } + + qset = get_qset(whc, urb, GFP_ATOMIC); + if (qset == NULL) + err = -ENOMEM; + else + err = qset_add_urb(whc, qset, urb, GFP_ATOMIC); + if (!err) { + if (!qset->in_sw_list && !qset->remove) + asl_qset_insert_begin(whc, qset); + } else + usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb); + + spin_unlock_irqrestore(&whc->lock, flags); + + if (!err) + queue_work(whc->workqueue, &whc->async_work); + + return err; +} + +/** + * asl_urb_dequeue - remove an URB (qset) from the async list. + * @whc: the WHCI host controller + * @urb: the URB to dequeue + * @status: the current status of the URB + * + * URBs that do yet have qTDs can simply be removed from the software + * queue, otherwise the qset must be removed from the ASL so the qTDs + * can be removed. + */ +int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status) +{ + struct whc_urb *wurb = urb->hcpriv; + struct whc_qset *qset = wurb->qset; + struct whc_std *std, *t; + bool has_qtd = false; + int ret; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status); + if (ret < 0) + goto out; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) { + if (std->qtd) + has_qtd = true; + qset_free_std(whc, std); + } else + std->qtd = NULL; /* so this std is re-added when the qset is */ + } + + if (has_qtd) { + asl_qset_remove(whc, qset); + wurb->status = status; + wurb->is_async = true; + queue_work(whc->workqueue, &wurb->dequeue_work); + } else + qset_remove_urb(whc, qset, urb, status); +out: + spin_unlock_irqrestore(&whc->lock, flags); + + return ret; +} + +/** + * asl_qset_delete - delete a qset from the ASL + */ +void asl_qset_delete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 1; + queue_work(whc->workqueue, &whc->async_work); + qset_delete(whc, qset); +} + +/** + * asl_init - initialize the asynchronous schedule list + * + * A dummy qset with no qTDs is added to the ASL to simplify removing + * qsets (no need to stop the ASL when the last qset is removed). + */ +int asl_init(struct whc *whc) +{ + struct whc_qset *qset; + + qset = qset_alloc(whc, GFP_KERNEL); + if (qset == NULL) + return -ENOMEM; + + asl_qset_insert_begin(whc, qset); + asl_qset_insert(whc, qset); + + return 0; +} + +/** + * asl_clean_up - free ASL resources + * + * The ASL is stopped and empty except for the dummy qset. + */ +void asl_clean_up(struct whc *whc) +{ + struct whc_qset *qset; + + if (!list_empty(&whc->async_list)) { + qset = list_first_entry(&whc->async_list, struct whc_qset, list_node); + list_del(&qset->list_node); + qset_free(whc, qset); + } +} diff --git a/drivers/usb/host/whci/debug.c b/drivers/usb/host/whci/debug.c new file mode 100644 index 00000000..ba61dae9 --- /dev/null +++ b/drivers/usb/host/whci/debug.c @@ -0,0 +1,203 @@ +/* + * Wireless Host Controller (WHC) debug. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +struct whc_dbg { + struct dentry *di_f; + struct dentry *asl_f; + struct dentry *pzl_f; +}; + +static void qset_print(struct seq_file *s, struct whc_qset *qset) +{ + static const char *qh_type[] = { + "ctrl", "isoc", "bulk", "intr", "rsvd", "rsvd", "rsvd", "lpintr", }; + struct whc_std *std; + struct urb *urb = NULL; + int i; + + seq_printf(s, "qset %08x", (u32)qset->qset_dma); + if (&qset->list_node == qset->whc->async_list.prev) { + seq_printf(s, " (dummy)\n"); + } else { + seq_printf(s, " ep%d%s-%s maxpkt: %d\n", + qset->qh.info1 & 0x0f, + (qset->qh.info1 >> 4) & 0x1 ? "in" : "out", + qh_type[(qset->qh.info1 >> 5) & 0x7], + (qset->qh.info1 >> 16) & 0xffff); + } + seq_printf(s, " -> %08x\n", (u32)qset->qh.link); + seq_printf(s, " info: %08x %08x %08x\n", + qset->qh.info1, qset->qh.info2, qset->qh.info3); + seq_printf(s, " sts: %04x errs: %d curwin: %08x\n", + qset->qh.status, qset->qh.err_count, qset->qh.cur_window); + seq_printf(s, " TD: sts: %08x opts: %08x\n", + qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options); + + for (i = 0; i < WHCI_QSET_TD_MAX; i++) { + seq_printf(s, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n", + i == qset->td_start ? 'S' : ' ', + i == qset->td_end ? 'E' : ' ', + i, qset->qtd[i].status, qset->qtd[i].options, + (u32)qset->qtd[i].page_list_ptr); + } + seq_printf(s, " ntds: %d\n", qset->ntds); + list_for_each_entry(std, &qset->stds, list_node) { + if (urb != std->urb) { + urb = std->urb; + seq_printf(s, " urb %p transferred: %d bytes\n", urb, + urb->actual_length); + } + if (std->qtd) + seq_printf(s, " sTD[%td]: %zu bytes @ %08x\n", + std->qtd - &qset->qtd[0], + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + else + seq_printf(s, " sTD[-]: %zd bytes @ %08x\n", + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + } +} + +static int di_print(struct seq_file *s, void *p) +{ + struct whc *whc = s->private; + char buf[72]; + int d; + + for (d = 0; d < whc->n_devices; d++) { + struct di_buf_entry *di = &whc->di_buf[d]; + + bitmap_scnprintf(buf, sizeof(buf), + (unsigned long *)di->availability_info, UWB_NUM_MAS); + + seq_printf(s, "DI[%d]\n", d); + seq_printf(s, " availability: %s\n", buf); + seq_printf(s, " %c%c key idx: %d dev addr: %d\n", + (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ', + (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ', + (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8, + (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK)); + } + return 0; +} + +static int asl_print(struct seq_file *s, void *p) +{ + struct whc *whc = s->private; + struct whc_qset *qset; + + list_for_each_entry(qset, &whc->async_list, list_node) { + qset_print(s, qset); + } + + return 0; +} + +static int pzl_print(struct seq_file *s, void *p) +{ + struct whc *whc = s->private; + struct whc_qset *qset; + int period; + + for (period = 0; period < 5; period++) { + seq_printf(s, "Period %d\n", period); + list_for_each_entry(qset, &whc->periodic_list[period], list_node) { + qset_print(s, qset); + } + } + return 0; +} + +static int di_open(struct inode *inode, struct file *file) +{ + return single_open(file, di_print, inode->i_private); +} + +static int asl_open(struct inode *inode, struct file *file) +{ + return single_open(file, asl_print, inode->i_private); +} + +static int pzl_open(struct inode *inode, struct file *file) +{ + return single_open(file, pzl_print, inode->i_private); +} + +static const struct file_operations di_fops = { + .open = di_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations asl_fops = { + .open = asl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations pzl_fops = { + .open = pzl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +void whc_dbg_init(struct whc *whc) +{ + if (whc->wusbhc.pal.debugfs_dir == NULL) + return; + + whc->dbg = kzalloc(sizeof(struct whc_dbg), GFP_KERNEL); + if (whc->dbg == NULL) + return; + + whc->dbg->di_f = debugfs_create_file("di", 0444, + whc->wusbhc.pal.debugfs_dir, whc, + &di_fops); + whc->dbg->asl_f = debugfs_create_file("asl", 0444, + whc->wusbhc.pal.debugfs_dir, whc, + &asl_fops); + whc->dbg->pzl_f = debugfs_create_file("pzl", 0444, + whc->wusbhc.pal.debugfs_dir, whc, + &pzl_fops); +} + +void whc_dbg_clean_up(struct whc *whc) +{ + if (whc->dbg) { + debugfs_remove(whc->dbg->pzl_f); + debugfs_remove(whc->dbg->asl_f); + debugfs_remove(whc->dbg->di_f); + kfree(whc->dbg); + } +} diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c new file mode 100644 index 00000000..c3a64781 --- /dev/null +++ b/drivers/usb/host/whci/hcd.c @@ -0,0 +1,368 @@ +/* + * Wireless Host Controller (WHC) driver. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +/* + * One time initialization. + * + * Nothing to do here. + */ +static int whc_reset(struct usb_hcd *usb_hcd) +{ + return 0; +} + +/* + * Start the wireless host controller. + * + * Start device notification. + * + * Put hc into run state, set DNTS parameters. + */ +static int whc_start(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + u8 bcid; + int ret; + + mutex_lock(&wusbhc->mutex); + + le_writel(WUSBINTR_GEN_CMD_DONE + | WUSBINTR_HOST_ERR + | WUSBINTR_ASYNC_SCHED_SYNCED + | WUSBINTR_DNTS_INT + | WUSBINTR_ERR_INT + | WUSBINTR_INT, + whc->base + WUSBINTR); + + /* set cluster ID */ + bcid = wusb_cluster_id_get(); + ret = whc_set_cluster_id(whc, bcid); + if (ret < 0) + goto out; + wusbhc->cluster_id = bcid; + + /* start HC */ + whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN); + + usb_hcd->uses_new_polling = 1; + set_bit(HCD_FLAG_POLL_RH, &usb_hcd->flags); + usb_hcd->state = HC_STATE_RUNNING; + +out: + mutex_unlock(&wusbhc->mutex); + return ret; +} + + +/* + * Stop the wireless host controller. + * + * Stop device notification. + * + * Wait for pending transfer to stop? Put hc into stop state? + */ +static void whc_stop(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + + mutex_lock(&wusbhc->mutex); + + /* stop HC */ + le_writel(0, whc->base + WUSBINTR); + whc_write_wusbcmd(whc, WUSBCMD_RUN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_HCHALTED, WUSBSTS_HCHALTED, + 100, "HC to halt"); + + wusb_cluster_id_put(wusbhc->cluster_id); + + mutex_unlock(&wusbhc->mutex); +} + +static int whc_get_frame_number(struct usb_hcd *usb_hcd) +{ + /* Frame numbers are not applicable to WUSB. */ + return -ENOSYS; +} + + +/* + * Queue an URB to the ASL or PZL + */ +static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + ret = pzl_urb_enqueue(whc, urb, mem_flags); + break; + case PIPE_ISOCHRONOUS: + dev_err(&whc->umc->dev, "isochronous transfers unsupported\n"); + ret = -ENOTSUPP; + break; + case PIPE_CONTROL: + case PIPE_BULK: + default: + ret = asl_urb_enqueue(whc, urb, mem_flags); + break; + }; + + return ret; +} + +/* + * Remove a queued URB from the ASL or PZL. + */ +static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + ret = pzl_urb_dequeue(whc, urb, status); + break; + case PIPE_ISOCHRONOUS: + ret = -ENOTSUPP; + break; + case PIPE_CONTROL: + case PIPE_BULK: + default: + ret = asl_urb_dequeue(whc, urb, status); + break; + }; + + return ret; +} + +/* + * Wait for all URBs to the endpoint to be completed, then delete the + * qset. + */ +static void whc_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + struct whc_qset *qset; + + qset = ep->hcpriv; + if (qset) { + ep->hcpriv = NULL; + if (usb_endpoint_xfer_bulk(&ep->desc) + || usb_endpoint_xfer_control(&ep->desc)) + asl_qset_delete(whc, qset); + else + pzl_qset_delete(whc, qset); + } +} + +static void whc_endpoint_reset(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + struct whc_qset *qset; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + qset = ep->hcpriv; + if (qset) { + qset->remove = 1; + qset->reset = 1; + + if (usb_endpoint_xfer_bulk(&ep->desc) + || usb_endpoint_xfer_control(&ep->desc)) + queue_work(whc->workqueue, &whc->async_work); + else + queue_work(whc->workqueue, &whc->periodic_work); + } + + spin_unlock_irqrestore(&whc->lock, flags); +} + + +static struct hc_driver whc_hc_driver = { + .description = "whci-hcd", + .product_desc = "Wireless host controller", + .hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd), + .irq = whc_int_handler, + .flags = HCD_USB2, + + .reset = whc_reset, + .start = whc_start, + .stop = whc_stop, + .get_frame_number = whc_get_frame_number, + .urb_enqueue = whc_urb_enqueue, + .urb_dequeue = whc_urb_dequeue, + .endpoint_disable = whc_endpoint_disable, + .endpoint_reset = whc_endpoint_reset, + + .hub_status_data = wusbhc_rh_status_data, + .hub_control = wusbhc_rh_control, + .bus_suspend = wusbhc_rh_suspend, + .bus_resume = wusbhc_rh_resume, + .start_port_reset = wusbhc_rh_start_port_reset, +}; + +static int whc_probe(struct umc_dev *umc) +{ + int ret; + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct whc *whc; + struct device *dev = &umc->dev; + + usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci"); + if (usb_hcd == NULL) { + dev_err(dev, "unable to create hcd\n"); + return -ENOMEM; + } + + usb_hcd->wireless = 1; + usb_hcd->self.sg_tablesize = 2048; /* somewhat arbitrary */ + + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + whc = wusbhc_to_whc(wusbhc); + whc->umc = umc; + + ret = whc_init(whc); + if (ret) + goto error; + + wusbhc->dev = dev; + wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent); + if (!wusbhc->uwb_rc) { + ret = -ENODEV; + dev_err(dev, "cannot get radio controller\n"); + goto error; + } + + if (whc->n_devices > USB_MAXCHILDREN) { + dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n", + whc->n_devices); + wusbhc->ports_max = USB_MAXCHILDREN; + } else + wusbhc->ports_max = whc->n_devices; + wusbhc->mmcies_max = whc->n_mmc_ies; + wusbhc->start = whc_wusbhc_start; + wusbhc->stop = whc_wusbhc_stop; + wusbhc->mmcie_add = whc_mmcie_add; + wusbhc->mmcie_rm = whc_mmcie_rm; + wusbhc->dev_info_set = whc_dev_info_set; + wusbhc->bwa_set = whc_bwa_set; + wusbhc->set_num_dnts = whc_set_num_dnts; + wusbhc->set_ptk = whc_set_ptk; + wusbhc->set_gtk = whc_set_gtk; + + ret = wusbhc_create(wusbhc); + if (ret) + goto error_wusbhc_create; + + ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED); + if (ret) { + dev_err(dev, "cannot add HCD: %d\n", ret); + goto error_usb_add_hcd; + } + + ret = wusbhc_b_create(wusbhc); + if (ret) { + dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret); + goto error_wusbhc_b_create; + } + + whc_dbg_init(whc); + + return 0; + +error_wusbhc_b_create: + usb_remove_hcd(usb_hcd); +error_usb_add_hcd: + wusbhc_destroy(wusbhc); +error_wusbhc_create: + uwb_rc_put(wusbhc->uwb_rc); +error: + whc_clean_up(whc); + if (usb_hcd) + usb_put_hcd(usb_hcd); + return ret; +} + + +static void whc_remove(struct umc_dev *umc) +{ + struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev); + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + + if (usb_hcd) { + whc_dbg_clean_up(whc); + wusbhc_b_destroy(wusbhc); + usb_remove_hcd(usb_hcd); + wusbhc_destroy(wusbhc); + uwb_rc_put(wusbhc->uwb_rc); + whc_clean_up(whc); + usb_put_hcd(usb_hcd); + } +} + +static struct umc_driver whci_hc_driver = { + .name = "whci-hcd", + .cap_id = UMC_CAP_ID_WHCI_WUSB_HC, + .probe = whc_probe, + .remove = whc_remove, +}; + +static int __init whci_hc_driver_init(void) +{ + return umc_driver_register(&whci_hc_driver); +} +module_init(whci_hc_driver_init); + +static void __exit whci_hc_driver_exit(void) +{ + umc_driver_unregister(&whci_hc_driver); +} +module_exit(whci_hc_driver_exit); + +/* PCI device ID's that we handle (so it gets loaded) */ +static struct pci_device_id __used whci_hcd_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, + { /* empty last entry */ } +}; +MODULE_DEVICE_TABLE(pci, whci_hcd_id_table); + +MODULE_DESCRIPTION("WHCI Wireless USB host controller driver"); +MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c new file mode 100644 index 00000000..6afa2e37 --- /dev/null +++ b/drivers/usb/host/whci/hw.c @@ -0,0 +1,104 @@ +/* + * Wireless Host Controller (WHC) hardware access helpers. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val) +{ + unsigned long flags; + u32 cmd; + + spin_lock_irqsave(&whc->lock, flags); + + cmd = le_readl(whc->base + WUSBCMD); + cmd = (cmd & ~mask) | val; + le_writel(cmd, whc->base + WUSBCMD); + + spin_unlock_irqrestore(&whc->lock, flags); +} + +/** + * whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register + * @whc: the WHCI HC + * @cmd: command to start. + * @params: parameters for the command (the WUSBGENCMDPARAMS register value). + * @addr: pointer to any data for the command (may be NULL). + * @len: length of the data (if any). + */ +int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len) +{ + unsigned long flags; + dma_addr_t dma_addr; + int t; + int ret = 0; + + mutex_lock(&whc->mutex); + + /* Wait for previous command to complete. */ + t = wait_event_timeout(whc->cmd_wq, + (le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0, + WHC_GENCMD_TIMEOUT_MS); + if (t == 0) { + dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n", + le_readl(whc->base + WUSBGENCMDSTS), + le_readl(whc->base + WUSBGENCMDPARAMS)); + ret = -ETIMEDOUT; + goto out; + } + + if (addr) { + memcpy(whc->gen_cmd_buf, addr, len); + dma_addr = whc->gen_cmd_buf_dma; + } else + dma_addr = 0; + + /* Poke registers to start cmd. */ + spin_lock_irqsave(&whc->lock, flags); + + le_writel(params, whc->base + WUSBGENCMDPARAMS); + le_writeq(dma_addr, whc->base + WUSBGENADDR); + + le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd, + whc->base + WUSBGENCMDSTS); + + spin_unlock_irqrestore(&whc->lock, flags); +out: + mutex_unlock(&whc->mutex); + + return ret; +} + +/** + * whc_hw_error - recover from a hardware error + * @whc: the WHCI HC that broke. + * @reason: a description of the failure. + * + * Recover from broken hardware with a full reset. + */ +void whc_hw_error(struct whc *whc, const char *reason) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + + dev_err(&whc->umc->dev, "hardware error: %s\n", reason); + wusbhc_reset_all(wusbhc); +} diff --git a/drivers/usb/host/whci/init.c b/drivers/usb/host/whci/init.c new file mode 100644 index 00000000..d3e13b64 --- /dev/null +++ b/drivers/usb/host/whci/init.c @@ -0,0 +1,189 @@ +/* + * Wireless Host Controller (WHC) initialization. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +/* + * Reset the host controller. + */ +static void whc_hw_reset(struct whc *whc) +{ + le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD); + whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0, + 100, "reset"); +} + +static void whc_hw_init_di_buf(struct whc *whc) +{ + int d; + + /* Disable all entries in the Device Information buffer. */ + for (d = 0; d < whc->n_devices; d++) + whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE; + + le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR); +} + +static void whc_hw_init_dn_buf(struct whc *whc) +{ + /* Clear the Device Notification buffer to ensure the V (valid) + * bits are clear. */ + memset(whc->dn_buf, 0, 4096); + + le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR); +} + +int whc_init(struct whc *whc) +{ + u32 whcsparams; + int ret, i; + resource_size_t start, len; + + spin_lock_init(&whc->lock); + mutex_init(&whc->mutex); + init_waitqueue_head(&whc->cmd_wq); + init_waitqueue_head(&whc->async_list_wq); + init_waitqueue_head(&whc->periodic_list_wq); + whc->workqueue = create_singlethread_workqueue(dev_name(&whc->umc->dev)); + if (whc->workqueue == NULL) { + ret = -ENOMEM; + goto error; + } + INIT_WORK(&whc->dn_work, whc_dn_work); + + INIT_WORK(&whc->async_work, scan_async_work); + INIT_LIST_HEAD(&whc->async_list); + INIT_LIST_HEAD(&whc->async_removed_list); + + INIT_WORK(&whc->periodic_work, scan_periodic_work); + for (i = 0; i < 5; i++) + INIT_LIST_HEAD(&whc->periodic_list[i]); + INIT_LIST_HEAD(&whc->periodic_removed_list); + + /* Map HC registers. */ + start = whc->umc->resource.start; + len = whc->umc->resource.end - start + 1; + if (!request_mem_region(start, len, "whci-hc")) { + dev_err(&whc->umc->dev, "can't request HC region\n"); + ret = -EBUSY; + goto error; + } + whc->base_phys = start; + whc->base = ioremap(start, len); + if (!whc->base) { + dev_err(&whc->umc->dev, "ioremap\n"); + ret = -ENOMEM; + goto error; + } + + whc_hw_reset(whc); + + /* Read maximum number of devices, keys and MMC IEs. */ + whcsparams = le_readl(whc->base + WHCSPARAMS); + whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams); + whc->n_keys = WHCSPARAMS_TO_N_KEYS(whcsparams); + whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams); + + dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n", + whc->n_devices, whc->n_keys, whc->n_mmc_ies); + + whc->qset_pool = dma_pool_create("qset", &whc->umc->dev, + sizeof(struct whc_qset), 64, 0); + if (whc->qset_pool == NULL) { + ret = -ENOMEM; + goto error; + } + + ret = asl_init(whc); + if (ret < 0) + goto error; + ret = pzl_init(whc); + if (ret < 0) + goto error; + + /* Allocate and initialize a buffer for generic commands, the + Device Information buffer, and the Device Notification + buffer. */ + + whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN, + &whc->gen_cmd_buf_dma, GFP_KERNEL); + if (whc->gen_cmd_buf == NULL) { + ret = -ENOMEM; + goto error; + } + + whc->dn_buf = dma_alloc_coherent(&whc->umc->dev, + sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES, + &whc->dn_buf_dma, GFP_KERNEL); + if (!whc->dn_buf) { + ret = -ENOMEM; + goto error; + } + whc_hw_init_dn_buf(whc); + + whc->di_buf = dma_alloc_coherent(&whc->umc->dev, + sizeof(struct di_buf_entry) * whc->n_devices, + &whc->di_buf_dma, GFP_KERNEL); + if (!whc->di_buf) { + ret = -ENOMEM; + goto error; + } + whc_hw_init_di_buf(whc); + + return 0; + +error: + whc_clean_up(whc); + return ret; +} + +void whc_clean_up(struct whc *whc) +{ + resource_size_t len; + + if (whc->di_buf) + dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices, + whc->di_buf, whc->di_buf_dma); + if (whc->dn_buf) + dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES, + whc->dn_buf, whc->dn_buf_dma); + if (whc->gen_cmd_buf) + dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN, + whc->gen_cmd_buf, whc->gen_cmd_buf_dma); + + pzl_clean_up(whc); + asl_clean_up(whc); + + if (whc->qset_pool) + dma_pool_destroy(whc->qset_pool); + + len = resource_size(&whc->umc->resource); + if (whc->base) + iounmap(whc->base); + if (whc->base_phys) + release_mem_region(whc->base_phys, len); + + if (whc->workqueue) + destroy_workqueue(whc->workqueue); +} diff --git a/drivers/usb/host/whci/int.c b/drivers/usb/host/whci/int.c new file mode 100644 index 00000000..6aae7002 --- /dev/null +++ b/drivers/usb/host/whci/int.c @@ -0,0 +1,94 @@ +/* + * Wireless Host Controller (WHC) interrupt handling. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +static void transfer_done(struct whc *whc) +{ + queue_work(whc->workqueue, &whc->async_work); + queue_work(whc->workqueue, &whc->periodic_work); +} + +irqreturn_t whc_int_handler(struct usb_hcd *hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 sts; + + sts = le_readl(whc->base + WUSBSTS); + if (!(sts & WUSBSTS_INT_MASK)) + return IRQ_NONE; + le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS); + + if (sts & WUSBSTS_GEN_CMD_DONE) + wake_up(&whc->cmd_wq); + + if (sts & WUSBSTS_HOST_ERR) + dev_err(&whc->umc->dev, "FIXME: host system error\n"); + + if (sts & WUSBSTS_ASYNC_SCHED_SYNCED) + wake_up(&whc->async_list_wq); + + if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED) + wake_up(&whc->periodic_list_wq); + + if (sts & WUSBSTS_DNTS_INT) + queue_work(whc->workqueue, &whc->dn_work); + + /* + * A transfer completed (see [WHCI] section 4.7.1.2 for when + * this occurs). + */ + if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT)) + transfer_done(whc); + + return IRQ_HANDLED; +} + +static int process_dn_buf(struct whc *whc) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + struct dn_buf_entry *dn; + int processed = 0; + + for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) { + if (dn->status & WHC_DN_STATUS_VALID) { + wusbhc_handle_dn(wusbhc, dn->src_addr, + (struct wusb_dn_hdr *)dn->dn_data, + dn->msg_size); + dn->status &= ~WHC_DN_STATUS_VALID; + processed++; + } + } + return processed; +} + +void whc_dn_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, dn_work); + int processed; + + do { + processed = process_dn_buf(whc); + } while (processed); +} diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c new file mode 100644 index 00000000..33c5580b --- /dev/null +++ b/drivers/usb/host/whci/pzl.c @@ -0,0 +1,417 @@ +/* + * Wireless Host Controller (WHC) periodic schedule management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +static void update_pzl_pointers(struct whc *whc, int period, u64 addr) +{ + switch (period) { + case 0: + whc_qset_set_link_ptr(&whc->pz_list[0], addr); + whc_qset_set_link_ptr(&whc->pz_list[2], addr); + whc_qset_set_link_ptr(&whc->pz_list[4], addr); + whc_qset_set_link_ptr(&whc->pz_list[6], addr); + whc_qset_set_link_ptr(&whc->pz_list[8], addr); + whc_qset_set_link_ptr(&whc->pz_list[10], addr); + whc_qset_set_link_ptr(&whc->pz_list[12], addr); + whc_qset_set_link_ptr(&whc->pz_list[14], addr); + break; + case 1: + whc_qset_set_link_ptr(&whc->pz_list[1], addr); + whc_qset_set_link_ptr(&whc->pz_list[5], addr); + whc_qset_set_link_ptr(&whc->pz_list[9], addr); + whc_qset_set_link_ptr(&whc->pz_list[13], addr); + break; + case 2: + whc_qset_set_link_ptr(&whc->pz_list[3], addr); + whc_qset_set_link_ptr(&whc->pz_list[11], addr); + break; + case 3: + whc_qset_set_link_ptr(&whc->pz_list[7], addr); + break; + case 4: + whc_qset_set_link_ptr(&whc->pz_list[15], addr); + break; + } +} + +/* + * Return the 'period' to use for this qset. The minimum interval for + * the endpoint is used so whatever urbs are submitted the device is + * polled often enough. + */ +static int qset_get_period(struct whc *whc, struct whc_qset *qset) +{ + uint8_t bInterval = qset->ep->desc.bInterval; + + if (bInterval < 6) + bInterval = 6; + if (bInterval > 10) + bInterval = 10; + return bInterval - 6; +} + +static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset) +{ + int period; + + period = qset_get_period(whc, qset); + + qset_clear(whc, qset); + list_move(&qset->list_node, &whc->periodic_list[period]); + qset->in_sw_list = true; +} + +static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset) +{ + list_move(&qset->list_node, &whc->periodic_removed_list); + qset->in_hw_list = false; + qset->in_sw_list = false; +} + +/** + * pzl_process_qset - process any recently inactivated or halted qTDs + * in a qset. + * + * After inactive qTDs are removed, new qTDs can be added if the + * urb queue still contains URBs. + * + * Returns the schedule updates required. + */ +static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset) +{ + enum whc_update update = 0; + uint32_t status = 0; + + while (qset->ntds) { + struct whc_qtd *td; + int t; + + t = qset->td_start; + td = &qset->qtd[qset->td_start]; + status = le32_to_cpu(td->status); + + /* + * Nothing to do with a still active qTD. + */ + if (status & QTD_STS_ACTIVE) + break; + + if (status & QTD_STS_HALTED) { + /* Ug, an error. */ + process_halted_qtd(whc, qset, td); + /* A halted qTD always triggers an update + because the qset was either removed or + reactivated. */ + update |= WHC_UPDATE_UPDATED; + goto done; + } + + /* Mmm, a completed qTD. */ + process_inactive_qtd(whc, qset, td); + } + + if (!qset->remove) + update |= qset_add_qtds(whc, qset); + +done: + /* + * If there are no qTDs in this qset, remove it from the PZL. + */ + if (qset->remove && qset->ntds == 0) { + pzl_qset_remove(whc, qset); + update |= WHC_UPDATE_REMOVED; + } + + return update; +} + +/** + * pzl_start - start the periodic schedule + * @whc: the WHCI host controller + * + * The PZL must be valid (e.g., all entries in the list should have + * the T bit set). + */ +void pzl_start(struct whc *whc) +{ + le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE); + + whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED, + 1000, "start PZL"); +} + +/** + * pzl_stop - stop the periodic schedule + * @whc: the WHCI host controller + */ +void pzl_stop(struct whc *whc) +{ + whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_PERIODIC_SCHED, 0, + 1000, "stop PZL"); +} + +/** + * pzl_update - request a PZL update and wait for the hardware to be synced + * @whc: the WHCI HC + * @wusbcmd: WUSBCMD value to start the update. + * + * If the WUSB HC is inactive (i.e., the PZL is stopped) then the + * update must be skipped as the hardware may not respond to update + * requests. + */ +void pzl_update(struct whc *whc, uint32_t wusbcmd) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + long t; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + t = wait_event_timeout( + whc->periodic_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0, + msecs_to_jiffies(1000)); + if (t == 0) + whc_hw_error(whc, "PZL update timeout"); + } + mutex_unlock(&wusbhc->mutex); +} + +static void update_pzl_hw_view(struct whc *whc) +{ + struct whc_qset *qset, *t; + int period; + u64 tmp_qh = 0; + + for (period = 0; period < 5; period++) { + list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { + whc_qset_set_link_ptr(&qset->qh.link, tmp_qh); + tmp_qh = qset->qset_dma; + qset->in_hw_list = true; + } + update_pzl_pointers(whc, period, tmp_qh); + } +} + +/** + * scan_periodic_work - scan the PZL for qsets to process. + * + * Process each qset in the PZL in turn and then signal the WHC that + * the PZL has been updated. + * + * Then start, stop or update the periodic schedule as required. + */ +void scan_periodic_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, periodic_work); + struct whc_qset *qset, *t; + enum whc_update update = 0; + int period; + + spin_lock_irq(&whc->lock); + + for (period = 4; period >= 0; period--) { + list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { + if (!qset->in_hw_list) + update |= WHC_UPDATE_ADDED; + update |= pzl_process_qset(whc, qset); + } + } + + if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED)) + update_pzl_hw_view(whc); + + spin_unlock_irq(&whc->lock); + + if (update) { + uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB; + if (update & WHC_UPDATE_REMOVED) + wusbcmd |= WUSBCMD_PERIODIC_QSET_RM; + pzl_update(whc, wusbcmd); + } + + /* + * Now that the PZL is updated, complete the removal of any + * removed qsets. + * + * If the qset was to be reset, do so and reinsert it into the + * PZL if it has pending transfers. + */ + spin_lock_irq(&whc->lock); + + list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) { + qset_remove_complete(whc, qset); + if (qset->reset) { + qset_reset(whc, qset); + if (!list_empty(&qset->stds)) { + qset_insert_in_sw_list(whc, qset); + queue_work(whc->workqueue, &whc->periodic_work); + } + } + } + + spin_unlock_irq(&whc->lock); +} + +/** + * pzl_urb_enqueue - queue an URB onto the periodic list (PZL) + * @whc: the WHCI host controller + * @urb: the URB to enqueue + * @mem_flags: flags for any memory allocations + * + * The qset for the endpoint is obtained and the urb queued on to it. + * + * Work is scheduled to update the hardware's view of the PZL. + */ +int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags) +{ + struct whc_qset *qset; + int err; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb); + if (err < 0) { + spin_unlock_irqrestore(&whc->lock, flags); + return err; + } + + qset = get_qset(whc, urb, GFP_ATOMIC); + if (qset == NULL) + err = -ENOMEM; + else + err = qset_add_urb(whc, qset, urb, GFP_ATOMIC); + if (!err) { + if (!qset->in_sw_list && !qset->remove) + qset_insert_in_sw_list(whc, qset); + } else + usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb); + + spin_unlock_irqrestore(&whc->lock, flags); + + if (!err) + queue_work(whc->workqueue, &whc->periodic_work); + + return err; +} + +/** + * pzl_urb_dequeue - remove an URB (qset) from the periodic list + * @whc: the WHCI host controller + * @urb: the URB to dequeue + * @status: the current status of the URB + * + * URBs that do yet have qTDs can simply be removed from the software + * queue, otherwise the qset must be removed so the qTDs can be safely + * removed. + */ +int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status) +{ + struct whc_urb *wurb = urb->hcpriv; + struct whc_qset *qset = wurb->qset; + struct whc_std *std, *t; + bool has_qtd = false; + int ret; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status); + if (ret < 0) + goto out; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) { + if (std->qtd) + has_qtd = true; + qset_free_std(whc, std); + } else + std->qtd = NULL; /* so this std is re-added when the qset is */ + } + + if (has_qtd) { + pzl_qset_remove(whc, qset); + update_pzl_hw_view(whc); + wurb->status = status; + wurb->is_async = false; + queue_work(whc->workqueue, &wurb->dequeue_work); + } else + qset_remove_urb(whc, qset, urb, status); +out: + spin_unlock_irqrestore(&whc->lock, flags); + + return ret; +} + +/** + * pzl_qset_delete - delete a qset from the PZL + */ +void pzl_qset_delete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 1; + queue_work(whc->workqueue, &whc->periodic_work); + qset_delete(whc, qset); +} + +/** + * pzl_init - initialize the periodic zone list + * @whc: the WHCI host controller + */ +int pzl_init(struct whc *whc) +{ + int i; + + whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16, + &whc->pz_list_dma, GFP_KERNEL); + if (whc->pz_list == NULL) + return -ENOMEM; + + /* Set T bit on all elements in PZL. */ + for (i = 0; i < 16; i++) + whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T); + + le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE); + + return 0; +} + +/** + * pzl_clean_up - free PZL resources + * @whc: the WHCI host controller + * + * The PZL is stopped and empty. + */ +void pzl_clean_up(struct whc *whc) +{ + if (whc->pz_list) + dma_free_coherent(&whc->umc->dev, sizeof(u64) * 16, whc->pz_list, + whc->pz_list_dma); +} diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c new file mode 100644 index 00000000..dc31c425 --- /dev/null +++ b/drivers/usb/host/whci/qset.c @@ -0,0 +1,835 @@ +/* + * Wireless Host Controller (WHC) qset management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags) +{ + struct whc_qset *qset; + dma_addr_t dma; + + qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma); + if (qset == NULL) + return NULL; + memset(qset, 0, sizeof(struct whc_qset)); + + qset->qset_dma = dma; + qset->whc = whc; + + INIT_LIST_HEAD(&qset->list_node); + INIT_LIST_HEAD(&qset->stds); + + return qset; +} + +/** + * qset_fill_qh - fill the static endpoint state in a qset's QHead + * @qset: the qset whose QH needs initializing with static endpoint + * state + * @urb: an urb for a transfer to this endpoint + */ +static void qset_fill_qh(struct whc *whc, struct whc_qset *qset, struct urb *urb) +{ + struct usb_device *usb_dev = urb->dev; + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + struct usb_wireless_ep_comp_descriptor *epcd; + bool is_out; + uint8_t phy_rate; + + is_out = usb_pipeout(urb->pipe); + + qset->max_packet = le16_to_cpu(urb->ep->desc.wMaxPacketSize); + + epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra; + if (epcd) { + qset->max_seq = epcd->bMaxSequence; + qset->max_burst = epcd->bMaxBurst; + } else { + qset->max_seq = 2; + qset->max_burst = 1; + } + + /* + * Initial PHY rate is 53.3 Mbit/s for control endpoints or + * the maximum supported by the device for other endpoints + * (unless limited by the user). + */ + if (usb_pipecontrol(urb->pipe)) + phy_rate = UWB_PHY_RATE_53; + else { + uint16_t phy_rates; + + phy_rates = le16_to_cpu(wusb_dev->wusb_cap_descr->wPHYRates); + phy_rate = fls(phy_rates) - 1; + if (phy_rate > whc->wusbhc.phy_rate) + phy_rate = whc->wusbhc.phy_rate; + } + + qset->qh.info1 = cpu_to_le32( + QH_INFO1_EP(usb_pipeendpoint(urb->pipe)) + | (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN) + | usb_pipe_to_qh_type(urb->pipe) + | QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum)) + | QH_INFO1_MAX_PKT_LEN(qset->max_packet) + ); + qset->qh.info2 = cpu_to_le32( + QH_INFO2_BURST(qset->max_burst) + | QH_INFO2_DBP(0) + | QH_INFO2_MAX_COUNT(3) + | QH_INFO2_MAX_RETRY(3) + | QH_INFO2_MAX_SEQ(qset->max_seq - 1) + ); + /* FIXME: where can we obtain these Tx parameters from? Why + * doesn't the chip know what Tx power to use? It knows the Rx + * strength and can presumably guess the Tx power required + * from that? */ + qset->qh.info3 = cpu_to_le32( + QH_INFO3_TX_RATE(phy_rate) + | QH_INFO3_TX_PWR(0) /* 0 == max power */ + ); + + qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1); +} + +/** + * qset_clear - clear fields in a qset so it may be reinserted into a + * schedule. + * + * The sequence number and current window are not cleared (see + * qset_reset()). + */ +void qset_clear(struct whc *whc, struct whc_qset *qset) +{ + qset->td_start = qset->td_end = qset->ntds = 0; + + qset->qh.link = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T); + qset->qh.status = qset->qh.status & QH_STATUS_SEQ_MASK; + qset->qh.err_count = 0; + qset->qh.scratch[0] = 0; + qset->qh.scratch[1] = 0; + qset->qh.scratch[2] = 0; + + memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay)); + + init_completion(&qset->remove_complete); +} + +/** + * qset_reset - reset endpoint state in a qset. + * + * Clears the sequence number and current window. This qset must not + * be in the ASL or PZL. + */ +void qset_reset(struct whc *whc, struct whc_qset *qset) +{ + qset->reset = 0; + + qset->qh.status &= ~QH_STATUS_SEQ_MASK; + qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1); +} + +/** + * get_qset - get the qset for an async endpoint + * + * A new qset is created if one does not already exist. + */ +struct whc_qset *get_qset(struct whc *whc, struct urb *urb, + gfp_t mem_flags) +{ + struct whc_qset *qset; + + qset = urb->ep->hcpriv; + if (qset == NULL) { + qset = qset_alloc(whc, mem_flags); + if (qset == NULL) + return NULL; + + qset->ep = urb->ep; + urb->ep->hcpriv = qset; + qset_fill_qh(whc, qset, urb); + } + return qset; +} + +void qset_remove_complete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 0; + list_del_init(&qset->list_node); + complete(&qset->remove_complete); +} + +/** + * qset_add_qtds - add qTDs for an URB to a qset + * + * Returns true if the list (ASL/PZL) must be updated because (for a + * WHCI 0.95 controller) an activated qTD was pointed to be iCur. + */ +enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset) +{ + struct whc_std *std; + enum whc_update update = 0; + + list_for_each_entry(std, &qset->stds, list_node) { + struct whc_qtd *qtd; + uint32_t status; + + if (qset->ntds >= WHCI_QSET_TD_MAX + || (qset->pause_after_urb && std->urb != qset->pause_after_urb)) + break; + + if (std->qtd) + continue; /* already has a qTD */ + + qtd = std->qtd = &qset->qtd[qset->td_end]; + + /* Fill in setup bytes for control transfers. */ + if (usb_pipecontrol(std->urb->pipe)) + memcpy(qtd->setup, std->urb->setup_packet, 8); + + status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len); + + if (whc_std_last(std) && usb_pipeout(std->urb->pipe)) + status |= QTD_STS_LAST_PKT; + + /* + * For an IN transfer the iAlt field should be set so + * the h/w will automatically advance to the next + * transfer. However, if there are 8 or more TDs + * remaining in this transfer then iAlt cannot be set + * as it could point to somewhere in this transfer. + */ + if (std->ntds_remaining < WHCI_QSET_TD_MAX) { + int ialt; + ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX; + status |= QTD_STS_IALT(ialt); + } else if (usb_pipein(std->urb->pipe)) + qset->pause_after_urb = std->urb; + + if (std->num_pointers) + qtd->options = cpu_to_le32(QTD_OPT_IOC); + else + qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL); + qtd->page_list_ptr = cpu_to_le64(std->dma_addr); + + qtd->status = cpu_to_le32(status); + + if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end) + update = WHC_UPDATE_UPDATED; + + if (++qset->td_end >= WHCI_QSET_TD_MAX) + qset->td_end = 0; + qset->ntds++; + } + + return update; +} + +/** + * qset_remove_qtd - remove the first qTD from a qset. + * + * The qTD might be still active (if it's part of a IN URB that + * resulted in a short read) so ensure it's deactivated. + */ +static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset) +{ + qset->qtd[qset->td_start].status = 0; + + if (++qset->td_start >= WHCI_QSET_TD_MAX) + qset->td_start = 0; + qset->ntds--; +} + +static void qset_copy_bounce_to_sg(struct whc *whc, struct whc_std *std) +{ + struct scatterlist *sg; + void *bounce; + size_t remaining, offset; + + bounce = std->bounce_buf; + remaining = std->len; + + sg = std->bounce_sg; + offset = std->bounce_offset; + + while (remaining) { + size_t len; + + len = min(sg->length - offset, remaining); + memcpy(sg_virt(sg) + offset, bounce, len); + + bounce += len; + remaining -= len; + + offset += len; + if (offset >= sg->length) { + sg = sg_next(sg); + offset = 0; + } + } + +} + +/** + * qset_free_std - remove an sTD and free it. + * @whc: the WHCI host controller + * @std: the sTD to remove and free. + */ +void qset_free_std(struct whc *whc, struct whc_std *std) +{ + list_del(&std->list_node); + if (std->bounce_buf) { + bool is_out = usb_pipeout(std->urb->pipe); + dma_addr_t dma_addr; + + if (std->num_pointers) + dma_addr = le64_to_cpu(std->pl_virt[0].buf_ptr); + else + dma_addr = std->dma_addr; + + dma_unmap_single(whc->wusbhc.dev, dma_addr, + std->len, is_out ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (!is_out) + qset_copy_bounce_to_sg(whc, std); + kfree(std->bounce_buf); + } + if (std->pl_virt) { + if (std->dma_addr) + dma_unmap_single(whc->wusbhc.dev, std->dma_addr, + std->num_pointers * sizeof(struct whc_page_list_entry), + DMA_TO_DEVICE); + kfree(std->pl_virt); + std->pl_virt = NULL; + } + kfree(std); +} + +/** + * qset_remove_qtds - remove an URB's qTDs (and sTDs). + */ +static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset, + struct urb *urb) +{ + struct whc_std *std, *t; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb != urb) + break; + if (std->qtd != NULL) + qset_remove_qtd(whc, qset); + qset_free_std(whc, std); + } +} + +/** + * qset_free_stds - free any remaining sTDs for an URB. + */ +static void qset_free_stds(struct whc_qset *qset, struct urb *urb) +{ + struct whc_std *std, *t; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(qset->whc, std); + } +} + +static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags) +{ + dma_addr_t dma_addr = std->dma_addr; + dma_addr_t sp, ep; + size_t pl_len; + int p; + + /* Short buffers don't need a page list. */ + if (std->len <= WHCI_PAGE_SIZE) { + std->num_pointers = 0; + return 0; + } + + sp = dma_addr & ~(WHCI_PAGE_SIZE-1); + ep = dma_addr + std->len; + std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE); + + pl_len = std->num_pointers * sizeof(struct whc_page_list_entry); + std->pl_virt = kmalloc(pl_len, mem_flags); + if (std->pl_virt == NULL) + return -ENOMEM; + std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE); + + for (p = 0; p < std->num_pointers; p++) { + std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr); + dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1); + } + + return 0; +} + +/** + * urb_dequeue_work - executes asl/pzl update and gives back the urb to the system. + */ +static void urb_dequeue_work(struct work_struct *work) +{ + struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work); + struct whc_qset *qset = wurb->qset; + struct whc *whc = qset->whc; + unsigned long flags; + + if (wurb->is_async == true) + asl_update(whc, WUSBCMD_ASYNC_UPDATED + | WUSBCMD_ASYNC_SYNCED_DB + | WUSBCMD_ASYNC_QSET_RM); + else + pzl_update(whc, WUSBCMD_PERIODIC_UPDATED + | WUSBCMD_PERIODIC_SYNCED_DB + | WUSBCMD_PERIODIC_QSET_RM); + + spin_lock_irqsave(&whc->lock, flags); + qset_remove_urb(whc, qset, wurb->urb, wurb->status); + spin_unlock_irqrestore(&whc->lock, flags); +} + +static struct whc_std *qset_new_std(struct whc *whc, struct whc_qset *qset, + struct urb *urb, gfp_t mem_flags) +{ + struct whc_std *std; + + std = kzalloc(sizeof(struct whc_std), mem_flags); + if (std == NULL) + return NULL; + + std->urb = urb; + std->qtd = NULL; + + INIT_LIST_HEAD(&std->list_node); + list_add_tail(&std->list_node, &qset->stds); + + return std; +} + +static int qset_add_urb_sg(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags) +{ + size_t remaining; + struct scatterlist *sg; + int i; + int ntds = 0; + struct whc_std *std = NULL; + struct whc_page_list_entry *new_pl_virt; + dma_addr_t prev_end = 0; + size_t pl_len; + int p = 0; + + remaining = urb->transfer_buffer_length; + + for_each_sg(urb->sg, sg, urb->num_mapped_sgs, i) { + dma_addr_t dma_addr; + size_t dma_remaining; + dma_addr_t sp, ep; + int num_pointers; + + if (remaining == 0) { + break; + } + + dma_addr = sg_dma_address(sg); + dma_remaining = min_t(size_t, sg_dma_len(sg), remaining); + + while (dma_remaining) { + size_t dma_len; + + /* + * We can use the previous std (if it exists) provided that: + * - the previous one ended on a page boundary. + * - the current one begins on a page boundary. + * - the previous one isn't full. + * + * If a new std is needed but the previous one + * was not a whole number of packets then this + * sg list cannot be mapped onto multiple + * qTDs. Return an error and let the caller + * sort it out. + */ + if (!std + || (prev_end & (WHCI_PAGE_SIZE-1)) + || (dma_addr & (WHCI_PAGE_SIZE-1)) + || std->len + WHCI_PAGE_SIZE > QTD_MAX_XFER_SIZE) { + if (std && std->len % qset->max_packet != 0) + return -EINVAL; + std = qset_new_std(whc, qset, urb, mem_flags); + if (std == NULL) { + return -ENOMEM; + } + ntds++; + p = 0; + } + + dma_len = dma_remaining; + + /* + * If the remainder of this element doesn't + * fit in a single qTD, limit the qTD to a + * whole number of packets. This allows the + * remainder to go into the next qTD. + */ + if (std->len + dma_len > QTD_MAX_XFER_SIZE) { + dma_len = (QTD_MAX_XFER_SIZE / qset->max_packet) + * qset->max_packet - std->len; + } + + std->len += dma_len; + std->ntds_remaining = -1; /* filled in later */ + + sp = dma_addr & ~(WHCI_PAGE_SIZE-1); + ep = dma_addr + dma_len; + num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE); + std->num_pointers += num_pointers; + + pl_len = std->num_pointers * sizeof(struct whc_page_list_entry); + + new_pl_virt = krealloc(std->pl_virt, pl_len, mem_flags); + if (new_pl_virt == NULL) { + kfree(std->pl_virt); + std->pl_virt = NULL; + return -ENOMEM; + } + std->pl_virt = new_pl_virt; + + for (;p < std->num_pointers; p++) { + std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr); + dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1); + } + + prev_end = dma_addr = ep; + dma_remaining -= dma_len; + remaining -= dma_len; + } + } + + /* Now the number of stds is know, go back and fill in + std->ntds_remaining. */ + list_for_each_entry(std, &qset->stds, list_node) { + if (std->ntds_remaining == -1) { + pl_len = std->num_pointers * sizeof(struct whc_page_list_entry); + std->ntds_remaining = ntds--; + std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, + pl_len, DMA_TO_DEVICE); + } + } + return 0; +} + +/** + * qset_add_urb_sg_linearize - add an urb with sg list, copying the data + * + * If the URB contains an sg list whose elements cannot be directly + * mapped to qTDs then the data must be transferred via bounce + * buffers. + */ +static int qset_add_urb_sg_linearize(struct whc *whc, struct whc_qset *qset, + struct urb *urb, gfp_t mem_flags) +{ + bool is_out = usb_pipeout(urb->pipe); + size_t max_std_len; + size_t remaining; + int ntds = 0; + struct whc_std *std = NULL; + void *bounce = NULL; + struct scatterlist *sg; + int i; + + /* limit maximum bounce buffer to 16 * 3.5 KiB ~= 28 k */ + max_std_len = qset->max_burst * qset->max_packet; + + remaining = urb->transfer_buffer_length; + + for_each_sg(urb->sg, sg, urb->num_mapped_sgs, i) { + size_t len; + size_t sg_remaining; + void *orig; + + if (remaining == 0) { + break; + } + + sg_remaining = min_t(size_t, remaining, sg->length); + orig = sg_virt(sg); + + while (sg_remaining) { + if (!std || std->len == max_std_len) { + std = qset_new_std(whc, qset, urb, mem_flags); + if (std == NULL) + return -ENOMEM; + std->bounce_buf = kmalloc(max_std_len, mem_flags); + if (std->bounce_buf == NULL) + return -ENOMEM; + std->bounce_sg = sg; + std->bounce_offset = orig - sg_virt(sg); + bounce = std->bounce_buf; + ntds++; + } + + len = min(sg_remaining, max_std_len - std->len); + + if (is_out) + memcpy(bounce, orig, len); + + std->len += len; + std->ntds_remaining = -1; /* filled in later */ + + bounce += len; + orig += len; + sg_remaining -= len; + remaining -= len; + } + } + + /* + * For each of the new sTDs, map the bounce buffers, create + * page lists (if necessary), and fill in std->ntds_remaining. + */ + list_for_each_entry(std, &qset->stds, list_node) { + if (std->ntds_remaining != -1) + continue; + + std->dma_addr = dma_map_single(&whc->umc->dev, std->bounce_buf, std->len, + is_out ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (qset_fill_page_list(whc, std, mem_flags) < 0) + return -ENOMEM; + + std->ntds_remaining = ntds--; + } + + return 0; +} + +/** + * qset_add_urb - add an urb to the qset's queue. + * + * The URB is chopped into sTDs, one for each qTD that will required. + * At least one qTD (and sTD) is required even if the transfer has no + * data (e.g., for some control transfers). + */ +int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags) +{ + struct whc_urb *wurb; + int remaining = urb->transfer_buffer_length; + u64 transfer_dma = urb->transfer_dma; + int ntds_remaining; + int ret; + + wurb = kzalloc(sizeof(struct whc_urb), mem_flags); + if (wurb == NULL) + goto err_no_mem; + urb->hcpriv = wurb; + wurb->qset = qset; + wurb->urb = urb; + INIT_WORK(&wurb->dequeue_work, urb_dequeue_work); + + if (urb->num_sgs) { + ret = qset_add_urb_sg(whc, qset, urb, mem_flags); + if (ret == -EINVAL) { + qset_free_stds(qset, urb); + ret = qset_add_urb_sg_linearize(whc, qset, urb, mem_flags); + } + if (ret < 0) + goto err_no_mem; + return 0; + } + + ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE); + if (ntds_remaining == 0) + ntds_remaining = 1; + + while (ntds_remaining) { + struct whc_std *std; + size_t std_len; + + std_len = remaining; + if (std_len > QTD_MAX_XFER_SIZE) + std_len = QTD_MAX_XFER_SIZE; + + std = qset_new_std(whc, qset, urb, mem_flags); + if (std == NULL) + goto err_no_mem; + + std->dma_addr = transfer_dma; + std->len = std_len; + std->ntds_remaining = ntds_remaining; + + if (qset_fill_page_list(whc, std, mem_flags) < 0) + goto err_no_mem; + + ntds_remaining--; + remaining -= std_len; + transfer_dma += std_len; + } + + return 0; + +err_no_mem: + qset_free_stds(qset, urb); + return -ENOMEM; +} + +/** + * qset_remove_urb - remove an URB from the urb queue. + * + * The URB is returned to the USB subsystem. + */ +void qset_remove_urb(struct whc *whc, struct whc_qset *qset, + struct urb *urb, int status) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + struct whc_urb *wurb = urb->hcpriv; + + usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb); + /* Drop the lock as urb->complete() may enqueue another urb. */ + spin_unlock(&whc->lock); + wusbhc_giveback_urb(wusbhc, urb, status); + spin_lock(&whc->lock); + + kfree(wurb); +} + +/** + * get_urb_status_from_qtd - get the completed urb status from qTD status + * @urb: completed urb + * @status: qTD status + */ +static int get_urb_status_from_qtd(struct urb *urb, u32 status) +{ + if (status & QTD_STS_HALTED) { + if (status & QTD_STS_DBE) + return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM; + else if (status & QTD_STS_BABBLE) + return -EOVERFLOW; + else if (status & QTD_STS_RCE) + return -ETIME; + return -EPIPE; + } + if (usb_pipein(urb->pipe) + && (urb->transfer_flags & URB_SHORT_NOT_OK) + && urb->actual_length < urb->transfer_buffer_length) + return -EREMOTEIO; + return 0; +} + +/** + * process_inactive_qtd - process an inactive (but not halted) qTD. + * + * Update the urb with the transfer bytes from the qTD, if the urb is + * completely transferred or (in the case of an IN only) the LPF is + * set, then the transfer is complete and the urb should be returned + * to the system. + */ +void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd) +{ + struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node); + struct urb *urb = std->urb; + uint32_t status; + bool complete; + + status = le32_to_cpu(qtd->status); + + urb->actual_length += std->len - QTD_STS_TO_LEN(status); + + if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT)) + complete = true; + else + complete = whc_std_last(std); + + qset_remove_qtd(whc, qset); + qset_free_std(whc, std); + + /* + * Transfers for this URB are complete? Then return it to the + * USB subsystem. + */ + if (complete) { + qset_remove_qtds(whc, qset, urb); + qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status)); + + /* + * If iAlt isn't valid then the hardware didn't + * advance iCur. Adjust the start and end pointers to + * match iCur. + */ + if (!(status & QTD_STS_IALT_VALID)) + qset->td_start = qset->td_end + = QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status)); + qset->pause_after_urb = NULL; + } +} + +/** + * process_halted_qtd - process a qset with a halted qtd + * + * Remove all the qTDs for the failed URB and return the failed URB to + * the USB subsystem. Then remove all other qTDs so the qset can be + * removed. + * + * FIXME: this is the point where rate adaptation can be done. If a + * transfer failed because it exceeded the maximum number of retries + * then it could be reactivated with a slower rate without having to + * remove the qset. + */ +void process_halted_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd) +{ + struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node); + struct urb *urb = std->urb; + int urb_status; + + urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status)); + + qset_remove_qtds(whc, qset, urb); + qset_remove_urb(whc, qset, urb, urb_status); + + list_for_each_entry(std, &qset->stds, list_node) { + if (qset->ntds == 0) + break; + qset_remove_qtd(whc, qset); + std->qtd = NULL; + } + + qset->remove = 1; +} + +void qset_free(struct whc *whc, struct whc_qset *qset) +{ + dma_pool_free(whc->qset_pool, qset, qset->qset_dma); +} + +/** + * qset_delete - wait for a qset to be unused, then free it. + */ +void qset_delete(struct whc *whc, struct whc_qset *qset) +{ + wait_for_completion(&qset->remove_complete); + qset_free(whc, qset); +} diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h new file mode 100644 index 00000000..c80c7d93 --- /dev/null +++ b/drivers/usb/host/whci/whcd.h @@ -0,0 +1,215 @@ +/* + * Wireless Host Controller (WHC) private header. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#ifndef __WHCD_H +#define __WHCD_H + +#include +#include +#include + +#include "whci-hc.h" + +/* Generic command timeout. */ +#define WHC_GENCMD_TIMEOUT_MS 100 + +struct whc_dbg; + +struct whc { + struct wusbhc wusbhc; + struct umc_dev *umc; + + resource_size_t base_phys; + void __iomem *base; + int irq; + + u8 n_devices; + u8 n_keys; + u8 n_mmc_ies; + + u64 *pz_list; + struct dn_buf_entry *dn_buf; + struct di_buf_entry *di_buf; + dma_addr_t pz_list_dma; + dma_addr_t dn_buf_dma; + dma_addr_t di_buf_dma; + + spinlock_t lock; + struct mutex mutex; + + void * gen_cmd_buf; + dma_addr_t gen_cmd_buf_dma; + wait_queue_head_t cmd_wq; + + struct workqueue_struct *workqueue; + struct work_struct dn_work; + + struct dma_pool *qset_pool; + + struct list_head async_list; + struct list_head async_removed_list; + wait_queue_head_t async_list_wq; + struct work_struct async_work; + + struct list_head periodic_list[5]; + struct list_head periodic_removed_list; + wait_queue_head_t periodic_list_wq; + struct work_struct periodic_work; + + struct whc_dbg *dbg; +}; + +#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc)) + +/** + * struct whc_std - a software TD. + * @urb: the URB this sTD is for. + * @offset: start of the URB's data for this TD. + * @len: the length of data in the associated TD. + * @ntds_remaining: number of TDs (starting from this one) in this transfer. + * + * @bounce_buf: a bounce buffer if the std was from an urb with a sg + * list that could not be mapped to qTDs directly. + * @bounce_sg: the first scatterlist element bounce_buf is for. + * @bounce_offset: the offset into bounce_sg for the start of bounce_buf. + * + * Queued URBs may require more TDs than are available in a qset so we + * use a list of these "software TDs" (sTDs) to hold per-TD data. + */ +struct whc_std { + struct urb *urb; + size_t len; + int ntds_remaining; + struct whc_qtd *qtd; + + struct list_head list_node; + int num_pointers; + dma_addr_t dma_addr; + struct whc_page_list_entry *pl_virt; + + void *bounce_buf; + struct scatterlist *bounce_sg; + unsigned bounce_offset; +}; + +/** + * struct whc_urb - per URB host controller structure. + * @urb: the URB this struct is for. + * @qset: the qset associated to the URB. + * @dequeue_work: the work to remove the URB when dequeued. + * @is_async: the URB belongs to async sheduler or not. + * @status: the status to be returned when calling wusbhc_giveback_urb. + */ +struct whc_urb { + struct urb *urb; + struct whc_qset *qset; + struct work_struct dequeue_work; + bool is_async; + int status; +}; + +/** + * whc_std_last - is this sTD the URB's last? + * @std: the sTD to check. + */ +static inline bool whc_std_last(struct whc_std *std) +{ + return std->ntds_remaining <= 1; +} + +enum whc_update { + WHC_UPDATE_ADDED = 0x01, + WHC_UPDATE_REMOVED = 0x02, + WHC_UPDATE_UPDATED = 0x04, +}; + +/* init.c */ +int whc_init(struct whc *whc); +void whc_clean_up(struct whc *whc); + +/* hw.c */ +void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val); +int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len); +void whc_hw_error(struct whc *whc, const char *reason); + +/* wusb.c */ +int whc_wusbhc_start(struct wusbhc *wusbhc); +void whc_wusbhc_stop(struct wusbhc *wusbhc, int delay); +int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie); +int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle); +int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm); +int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev); +int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots); +int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *ptk, size_t key_size); +int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *gtk, size_t key_size); +int whc_set_cluster_id(struct whc *whc, u8 bcid); + +/* int.c */ +irqreturn_t whc_int_handler(struct usb_hcd *hcd); +void whc_dn_work(struct work_struct *work); + +/* asl.c */ +void asl_start(struct whc *whc); +void asl_stop(struct whc *whc); +int asl_init(struct whc *whc); +void asl_clean_up(struct whc *whc); +int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags); +int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status); +void asl_qset_delete(struct whc *whc, struct whc_qset *qset); +void scan_async_work(struct work_struct *work); + +/* pzl.c */ +int pzl_init(struct whc *whc); +void pzl_clean_up(struct whc *whc); +void pzl_start(struct whc *whc); +void pzl_stop(struct whc *whc); +int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags); +int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status); +void pzl_qset_delete(struct whc *whc, struct whc_qset *qset); +void scan_periodic_work(struct work_struct *work); + +/* qset.c */ +struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags); +void qset_free(struct whc *whc, struct whc_qset *qset); +struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags); +void qset_delete(struct whc *whc, struct whc_qset *qset); +void qset_clear(struct whc *whc, struct whc_qset *qset); +void qset_reset(struct whc *whc, struct whc_qset *qset); +int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags); +void qset_free_std(struct whc *whc, struct whc_std *std); +void qset_remove_urb(struct whc *whc, struct whc_qset *qset, + struct urb *urb, int status); +void process_halted_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd); +void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd); +enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset); +void qset_remove_complete(struct whc *whc, struct whc_qset *qset); +void pzl_update(struct whc *whc, uint32_t wusbcmd); +void asl_update(struct whc *whc, uint32_t wusbcmd); + +/* debug.c */ +void whc_dbg_init(struct whc *whc); +void whc_dbg_clean_up(struct whc *whc); + +#endif /* #ifndef __WHCD_H */ diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h new file mode 100644 index 00000000..4d4cbc07 --- /dev/null +++ b/drivers/usb/host/whci/whci-hc.h @@ -0,0 +1,414 @@ +/* + * Wireless Host Controller (WHC) data structures. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#ifndef _WHCI_WHCI_HC_H +#define _WHCI_WHCI_HC_H + +#include + +/** + * WHCI_PAGE_SIZE - page size use by WHCI + * + * WHCI assumes that host system uses pages of 4096 octets. + */ +#define WHCI_PAGE_SIZE 4096 + + +/** + * QTD_MAX_TXFER_SIZE - max number of bytes to transfer with a single + * qtd. + * + * This is 2^20 - 1. + */ +#define QTD_MAX_XFER_SIZE 1048575 + + +/** + * struct whc_qtd - Queue Element Transfer Descriptors (qTD) + * + * This describes the data for a bulk, control or interrupt transfer. + * + * [WHCI] section 3.2.4 + */ +struct whc_qtd { + __le32 status; /*< remaining transfer len and transfer status */ + __le32 options; + __le64 page_list_ptr; /*< physical pointer to data buffer page list*/ + __u8 setup[8]; /*< setup data for control transfers */ +} __attribute__((packed)); + +#define QTD_STS_ACTIVE (1 << 31) /* enable execution of transaction */ +#define QTD_STS_HALTED (1 << 30) /* transfer halted */ +#define QTD_STS_DBE (1 << 29) /* data buffer error */ +#define QTD_STS_BABBLE (1 << 28) /* babble detected */ +#define QTD_STS_RCE (1 << 27) /* retry count exceeded */ +#define QTD_STS_LAST_PKT (1 << 26) /* set Last Packet Flag in WUSB header */ +#define QTD_STS_INACTIVE (1 << 25) /* queue set is marked inactive */ +#define QTD_STS_IALT_VALID (1 << 23) /* iAlt field is valid */ +#define QTD_STS_IALT(i) (QTD_STS_IALT_VALID | ((i) << 20)) /* iAlt field */ +#define QTD_STS_LEN(l) ((l) << 0) /* transfer length */ +#define QTD_STS_TO_LEN(s) ((s) & 0x000fffff) + +#define QTD_OPT_IOC (1 << 1) /* page_list_ptr points to buffer directly */ +#define QTD_OPT_SMALL (1 << 0) /* interrupt on complete */ + +/** + * struct whc_itd - Isochronous Queue Element Transfer Descriptors (iTD) + * + * This describes the data and other parameters for an isochronous + * transfer. + * + * [WHCI] section 3.2.5 + */ +struct whc_itd { + __le16 presentation_time; /*< presentation time for OUT transfers */ + __u8 num_segments; /*< number of data segments in segment list */ + __u8 status; /*< command execution status */ + __le32 options; /*< misc transfer options */ + __le64 page_list_ptr; /*< physical pointer to data buffer page list */ + __le64 seg_list_ptr; /*< physical pointer to segment list */ +} __attribute__((packed)); + +#define ITD_STS_ACTIVE (1 << 7) /* enable execution of transaction */ +#define ITD_STS_DBE (1 << 5) /* data buffer error */ +#define ITD_STS_BABBLE (1 << 4) /* babble detected */ +#define ITD_STS_INACTIVE (1 << 1) /* queue set is marked inactive */ + +#define ITD_OPT_IOC (1 << 1) /* interrupt on complete */ +#define ITD_OPT_SMALL (1 << 0) /* page_list_ptr points to buffer directly */ + +/** + * Page list entry. + * + * A TD's page list must contain sufficient page list entries for the + * total data length in the TD. + * + * [WHCI] section 3.2.4.3 + */ +struct whc_page_list_entry { + __le64 buf_ptr; /*< physical pointer to buffer */ +} __attribute__((packed)); + +/** + * struct whc_seg_list_entry - Segment list entry. + * + * Describes a portion of the data buffer described in the containing + * qTD's page list. + * + * seg_ptr = qtd->page_list_ptr[qtd->seg_list_ptr[seg].idx].buf_ptr + * + qtd->seg_list_ptr[seg].offset; + * + * Segments can't cross page boundries. + * + * [WHCI] section 3.2.5.5 + */ +struct whc_seg_list_entry { + __le16 len; /*< segment length */ + __u8 idx; /*< index into page list */ + __u8 status; /*< segment status */ + __le16 offset; /*< 12 bit offset into page */ +} __attribute__((packed)); + +/** + * struct whc_qhead - endpoint and status information for a qset. + * + * [WHCI] section 3.2.6 + */ +struct whc_qhead { + __le64 link; /*< next qset in list */ + __le32 info1; + __le32 info2; + __le32 info3; + __le16 status; + __le16 err_count; /*< transaction error count */ + __le32 cur_window; + __le32 scratch[3]; /*< h/w scratch area */ + union { + struct whc_qtd qtd; + struct whc_itd itd; + } overlay; +} __attribute__((packed)); + +#define QH_LINK_PTR_MASK (~0x03Full) +#define QH_LINK_PTR(ptr) ((ptr) & QH_LINK_PTR_MASK) +#define QH_LINK_IQS (1 << 4) /* isochronous queue set */ +#define QH_LINK_NTDS(n) (((n) - 1) << 1) /* number of TDs in queue set */ +#define QH_LINK_T (1 << 0) /* last queue set in periodic schedule list */ + +#define QH_INFO1_EP(e) ((e) << 0) /* endpoint number */ +#define QH_INFO1_DIR_IN (1 << 4) /* IN transfer */ +#define QH_INFO1_DIR_OUT (0 << 4) /* OUT transfer */ +#define QH_INFO1_TR_TYPE_CTRL (0x0 << 5) /* control transfer */ +#define QH_INFO1_TR_TYPE_ISOC (0x1 << 5) /* isochronous transfer */ +#define QH_INFO1_TR_TYPE_BULK (0x2 << 5) /* bulk transfer */ +#define QH_INFO1_TR_TYPE_INT (0x3 << 5) /* interrupt */ +#define QH_INFO1_TR_TYPE_LP_INT (0x7 << 5) /* low power interrupt */ +#define QH_INFO1_DEV_INFO_IDX(i) ((i) << 8) /* index into device info buffer */ +#define QH_INFO1_SET_INACTIVE (1 << 15) /* set inactive after transfer */ +#define QH_INFO1_MAX_PKT_LEN(l) ((l) << 16) /* maximum packet length */ + +#define QH_INFO2_BURST(b) ((b) << 0) /* maximum burst length */ +#define QH_INFO2_DBP(p) ((p) << 5) /* data burst policy (see [WUSB] table 5-7) */ +#define QH_INFO2_MAX_COUNT(c) ((c) << 8) /* max isoc/int pkts per zone */ +#define QH_INFO2_RQS (1 << 15) /* reactivate queue set */ +#define QH_INFO2_MAX_RETRY(r) ((r) << 16) /* maximum transaction retries */ +#define QH_INFO2_MAX_SEQ(s) ((s) << 20) /* maximum sequence number */ +#define QH_INFO3_MAX_DELAY(d) ((d) << 0) /* maximum stream delay in 125 us units (isoc only) */ +#define QH_INFO3_INTERVAL(i) ((i) << 16) /* segment interval in 125 us units (isoc only) */ + +#define QH_INFO3_TX_RATE(r) ((r) << 24) /* PHY rate (see [ECMA-368] section 10.3.1.1) */ +#define QH_INFO3_TX_PWR(p) ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */ + +#define QH_STATUS_FLOW_CTRL (1 << 15) +#define QH_STATUS_ICUR(i) ((i) << 5) +#define QH_STATUS_TO_ICUR(s) (((s) >> 5) & 0x7) +#define QH_STATUS_SEQ_MASK 0x1f + +/** + * usb_pipe_to_qh_type - USB core pipe type to QH transfer type + * + * Returns the QH type field for a USB core pipe type. + */ +static inline unsigned usb_pipe_to_qh_type(unsigned pipe) +{ + static const unsigned type[] = { + [PIPE_ISOCHRONOUS] = QH_INFO1_TR_TYPE_ISOC, + [PIPE_INTERRUPT] = QH_INFO1_TR_TYPE_INT, + [PIPE_CONTROL] = QH_INFO1_TR_TYPE_CTRL, + [PIPE_BULK] = QH_INFO1_TR_TYPE_BULK, + }; + return type[usb_pipetype(pipe)]; +} + +/** + * Maxiumum number of TDs in a qset. + */ +#define WHCI_QSET_TD_MAX 8 + +/** + * struct whc_qset - WUSB data transfers to a specific endpoint + * @qh: the QHead of this qset + * @qtd: up to 8 qTDs (for qsets for control, bulk and interrupt + * transfers) + * @itd: up to 8 iTDs (for qsets for isochronous transfers) + * @qset_dma: DMA address for this qset + * @whc: WHCI HC this qset is for + * @ep: endpoint + * @stds: list of sTDs queued to this qset + * @ntds: number of qTDs queued (not necessarily the same as nTDs + * field in the QH) + * @td_start: index of the first qTD in the list + * @td_end: index of next free qTD in the list (provided + * ntds < WHCI_QSET_TD_MAX) + * + * Queue Sets (qsets) are added to the asynchronous schedule list + * (ASL) or the periodic zone list (PZL). + * + * qsets may contain up to 8 TDs (either qTDs or iTDs as appropriate). + * Each TD may refer to at most 1 MiB of data. If a single transfer + * has > 8MiB of data, TDs can be reused as they are completed since + * the TD list is used as a circular buffer. Similarly, several + * (smaller) transfers may be queued in a qset. + * + * WHCI controllers may cache portions of the qsets in the ASL and + * PZL, requiring the WHCD to inform the WHC that the lists have been + * updated (fields changed or qsets inserted or removed). For safe + * insertion and removal of qsets from the lists the schedule must be + * stopped to avoid races in updating the QH link pointers. + * + * Since the HC is free to execute qsets in any order, all transfers + * to an endpoint should use the same qset to ensure transfers are + * executed in the order they're submitted. + * + * [WHCI] section 3.2.3 + */ +struct whc_qset { + struct whc_qhead qh; + union { + struct whc_qtd qtd[WHCI_QSET_TD_MAX]; + struct whc_itd itd[WHCI_QSET_TD_MAX]; + }; + + /* private data for WHCD */ + dma_addr_t qset_dma; + struct whc *whc; + struct usb_host_endpoint *ep; + struct list_head stds; + int ntds; + int td_start; + int td_end; + struct list_head list_node; + unsigned in_sw_list:1; + unsigned in_hw_list:1; + unsigned remove:1; + unsigned reset:1; + struct urb *pause_after_urb; + struct completion remove_complete; + uint16_t max_packet; + uint8_t max_burst; + uint8_t max_seq; +}; + +static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target) +{ + if (target) + *ptr = (*ptr & ~(QH_LINK_PTR_MASK | QH_LINK_T)) | QH_LINK_PTR(target); + else + *ptr = QH_LINK_T; +} + +/** + * struct di_buf_entry - Device Information (DI) buffer entry. + * + * There's one of these per connected device. + */ +struct di_buf_entry { + __le32 availability_info[8]; /*< MAS availability information, one MAS per bit */ + __le32 addr_sec_info; /*< addressing and security info */ + __le32 reserved[7]; +} __attribute__((packed)); + +#define WHC_DI_SECURE (1 << 31) +#define WHC_DI_DISABLE (1 << 30) +#define WHC_DI_KEY_IDX(k) ((k) << 8) +#define WHC_DI_KEY_IDX_MASK 0x0000ff00 +#define WHC_DI_DEV_ADDR(a) ((a) << 0) +#define WHC_DI_DEV_ADDR_MASK 0x000000ff + +/** + * struct dn_buf_entry - Device Notification (DN) buffer entry. + * + * [WHCI] section 3.2.8 + */ +struct dn_buf_entry { + __u8 msg_size; /*< number of octets of valid DN data */ + __u8 reserved1; + __u8 src_addr; /*< source address */ + __u8 status; /*< buffer entry status */ + __le32 tkid; /*< TKID for source device, valid if secure bit is set */ + __u8 dn_data[56]; /*< up to 56 octets of DN data */ +} __attribute__((packed)); + +#define WHC_DN_STATUS_VALID (1 << 7) /* buffer entry is valid */ +#define WHC_DN_STATUS_SECURE (1 << 6) /* notification received using secure frame */ + +#define WHC_N_DN_ENTRIES (4096 / sizeof(struct dn_buf_entry)) + +/* The Add MMC IE WUSB Generic Command may take up to 256 bytes of + data. [WHCI] section 2.4.7. */ +#define WHC_GEN_CMD_DATA_LEN 256 + +/* + * HC registers. + * + * [WHCI] section 2.4 + */ + +#define WHCIVERSION 0x00 + +#define WHCSPARAMS 0x04 +# define WHCSPARAMS_TO_N_MMC_IES(p) (((p) >> 16) & 0xff) +# define WHCSPARAMS_TO_N_KEYS(p) (((p) >> 8) & 0xff) +# define WHCSPARAMS_TO_N_DEVICES(p) (((p) >> 0) & 0x7f) + +#define WUSBCMD 0x08 +# define WUSBCMD_BCID(b) ((b) << 16) +# define WUSBCMD_BCID_MASK (0xff << 16) +# define WUSBCMD_ASYNC_QSET_RM (1 << 12) +# define WUSBCMD_PERIODIC_QSET_RM (1 << 11) +# define WUSBCMD_WUSBSI(s) ((s) << 8) +# define WUSBCMD_WUSBSI_MASK (0x7 << 8) +# define WUSBCMD_ASYNC_SYNCED_DB (1 << 7) +# define WUSBCMD_PERIODIC_SYNCED_DB (1 << 6) +# define WUSBCMD_ASYNC_UPDATED (1 << 5) +# define WUSBCMD_PERIODIC_UPDATED (1 << 4) +# define WUSBCMD_ASYNC_EN (1 << 3) +# define WUSBCMD_PERIODIC_EN (1 << 2) +# define WUSBCMD_WHCRESET (1 << 1) +# define WUSBCMD_RUN (1 << 0) + +#define WUSBSTS 0x0c +# define WUSBSTS_ASYNC_SCHED (1 << 15) +# define WUSBSTS_PERIODIC_SCHED (1 << 14) +# define WUSBSTS_DNTS_SCHED (1 << 13) +# define WUSBSTS_HCHALTED (1 << 12) +# define WUSBSTS_GEN_CMD_DONE (1 << 9) +# define WUSBSTS_CHAN_TIME_ROLLOVER (1 << 8) +# define WUSBSTS_DNTS_OVERFLOW (1 << 7) +# define WUSBSTS_BPST_ADJUSTMENT_CHANGED (1 << 6) +# define WUSBSTS_HOST_ERR (1 << 5) +# define WUSBSTS_ASYNC_SCHED_SYNCED (1 << 4) +# define WUSBSTS_PERIODIC_SCHED_SYNCED (1 << 3) +# define WUSBSTS_DNTS_INT (1 << 2) +# define WUSBSTS_ERR_INT (1 << 1) +# define WUSBSTS_INT (1 << 0) +# define WUSBSTS_INT_MASK 0x3ff + +#define WUSBINTR 0x10 +# define WUSBINTR_GEN_CMD_DONE (1 << 9) +# define WUSBINTR_CHAN_TIME_ROLLOVER (1 << 8) +# define WUSBINTR_DNTS_OVERFLOW (1 << 7) +# define WUSBINTR_BPST_ADJUSTMENT_CHANGED (1 << 6) +# define WUSBINTR_HOST_ERR (1 << 5) +# define WUSBINTR_ASYNC_SCHED_SYNCED (1 << 4) +# define WUSBINTR_PERIODIC_SCHED_SYNCED (1 << 3) +# define WUSBINTR_DNTS_INT (1 << 2) +# define WUSBINTR_ERR_INT (1 << 1) +# define WUSBINTR_INT (1 << 0) +# define WUSBINTR_ALL 0x3ff + +#define WUSBGENCMDSTS 0x14 +# define WUSBGENCMDSTS_ACTIVE (1 << 31) +# define WUSBGENCMDSTS_ERROR (1 << 24) +# define WUSBGENCMDSTS_IOC (1 << 23) +# define WUSBGENCMDSTS_MMCIE_ADD 0x01 +# define WUSBGENCMDSTS_MMCIE_RM 0x02 +# define WUSBGENCMDSTS_SET_MAS 0x03 +# define WUSBGENCMDSTS_CHAN_STOP 0x04 +# define WUSBGENCMDSTS_RWP_EN 0x05 + +#define WUSBGENCMDPARAMS 0x18 +#define WUSBGENADDR 0x20 +#define WUSBASYNCLISTADDR 0x28 +#define WUSBDNTSBUFADDR 0x30 +#define WUSBDEVICEINFOADDR 0x38 + +#define WUSBSETSECKEYCMD 0x40 +# define WUSBSETSECKEYCMD_SET (1 << 31) +# define WUSBSETSECKEYCMD_ERASE (1 << 30) +# define WUSBSETSECKEYCMD_GTK (1 << 8) +# define WUSBSETSECKEYCMD_IDX(i) ((i) << 0) + +#define WUSBTKID 0x44 +#define WUSBSECKEY 0x48 +#define WUSBPERIODICLISTBASE 0x58 +#define WUSBMASINDEX 0x60 + +#define WUSBDNTSCTRL 0x64 +# define WUSBDNTSCTRL_ACTIVE (1 << 31) +# define WUSBDNTSCTRL_INTERVAL(i) ((i) << 8) +# define WUSBDNTSCTRL_SLOTS(s) ((s) << 0) + +#define WUSBTIME 0x68 +# define WUSBTIME_CHANNEL_TIME_MASK 0x00ffffff + +#define WUSBBPST 0x6c +#define WUSBDIBUPDATED 0x70 + +#endif /* #ifndef _WHCI_WHCI_HC_H */ diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c new file mode 100644 index 00000000..f24efdeb --- /dev/null +++ b/drivers/usb/host/whci/wusb.c @@ -0,0 +1,222 @@ +/* + * Wireless Host Controller (WHC) WUSB operations. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +static int whc_update_di(struct whc *whc, int idx) +{ + int offset = idx / 32; + u32 bit = 1 << (idx % 32); + + le_writel(bit, whc->base + WUSBDIBUPDATED + offset); + + return whci_wait_for(&whc->umc->dev, + whc->base + WUSBDIBUPDATED + offset, bit, 0, + 100, "DI update"); +} + +/* + * WHCI starts MMCs based on there being a valid GTK so these need + * only start/stop the asynchronous and periodic schedules and send a + * channel stop command. + */ + +int whc_wusbhc_start(struct wusbhc *wusbhc) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + asl_start(whc); + pzl_start(whc); + + return 0; +} + +void whc_wusbhc_stop(struct wusbhc *wusbhc, int delay) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 stop_time, now_time; + int ret; + + pzl_stop(whc); + asl_stop(whc); + + now_time = le_readl(whc->base + WUSBTIME) & WUSBTIME_CHANNEL_TIME_MASK; + stop_time = (now_time + ((delay * 8) << 7)) & 0x00ffffff; + ret = whc_do_gencmd(whc, WUSBGENCMDSTS_CHAN_STOP, stop_time, NULL, 0); + if (ret == 0) + msleep(delay); +} + +int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 params; + + params = (interval << 24) + | (repeat_cnt << 16) + | (wuie->bLength << 8) + | handle; + + return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength); +} + +int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 params; + + params = handle; + + return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0); +} + +int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + if (stream_index >= 0) + whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index)); + + return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm)); +} + +int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + int idx = wusb_dev->port_idx; + struct di_buf_entry *di = &whc->di_buf[idx]; + int ret; + + mutex_lock(&whc->mutex); + + uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability); + di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK); + di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr); + + ret = whc_update_di(whc, idx); + + mutex_unlock(&whc->mutex); + + return ret; +} + +/* + * Set the number of Device Notification Time Slots (DNTS) and enable + * device notifications. + */ +int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 dntsctrl; + + dntsctrl = WUSBDNTSCTRL_ACTIVE + | WUSBDNTSCTRL_INTERVAL(interval) + | WUSBDNTSCTRL_SLOTS(slots); + + le_writel(dntsctrl, whc->base + WUSBDNTSCTRL); + + return 0; +} + +static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid, + const void *key, size_t key_size, bool is_gtk) +{ + uint32_t setkeycmd; + uint32_t seckey[4]; + int i; + int ret; + + memcpy(seckey, key, key_size); + setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index); + if (is_gtk) + setkeycmd |= WUSBSETSECKEYCMD_GTK; + + le_writel(tkid, whc->base + WUSBTKID); + for (i = 0; i < 4; i++) + le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i); + le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD); + + ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD, + WUSBSETSECKEYCMD_SET, 0, 100, "set key"); + + return ret; +} + +/** + * whc_set_ptk - set the PTK to use for a device. + * + * The index into the key table for this PTK is the same as the + * device's port index. + */ +int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *ptk, size_t key_size) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + struct di_buf_entry *di = &whc->di_buf[port_idx]; + int ret; + + mutex_lock(&whc->mutex); + + if (ptk) { + ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false); + if (ret) + goto out; + + di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK; + di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx); + } else + di->addr_sec_info &= ~WHC_DI_SECURE; + + ret = whc_update_di(whc, port_idx); +out: + mutex_unlock(&whc->mutex); + return ret; +} + +/** + * whc_set_gtk - set the GTK for subsequent broadcast packets + * + * The GTK is stored in the last entry in the key table (the previous + * N_DEVICES entries are for the per-device PTKs). + */ +int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *gtk, size_t key_size) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + mutex_lock(&whc->mutex); + + ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true); + + mutex_unlock(&whc->mutex); + + return ret; +} + +int whc_set_cluster_id(struct whc *whc, u8 bcid) +{ + whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid)); + return 0; +} diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c new file mode 100644 index 00000000..5f3a7c74 --- /dev/null +++ b/drivers/usb/host/xhci-dbg.c @@ -0,0 +1,574 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "xhci.h" + +#define XHCI_INIT_VALUE 0x0 + +/* Add verbose debugging later, just print everything for now */ + +void xhci_dbg_regs(struct xhci_hcd *xhci) +{ + u32 temp; + + xhci_dbg(xhci, "// xHCI capability registers at %p:\n", + xhci->cap_regs); + temp = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); + xhci_dbg(xhci, "// @%p = 0x%x (CAPLENGTH AND HCIVERSION)\n", + &xhci->cap_regs->hc_capbase, temp); + xhci_dbg(xhci, "// CAPLENGTH: 0x%x\n", + (unsigned int) HC_LENGTH(temp)); +#if 0 + xhci_dbg(xhci, "// HCIVERSION: 0x%x\n", + (unsigned int) HC_VERSION(temp)); +#endif + + xhci_dbg(xhci, "// xHCI operational registers at %p:\n", xhci->op_regs); + + temp = xhci_readl(xhci, &xhci->cap_regs->run_regs_off); + xhci_dbg(xhci, "// @%p = 0x%x RTSOFF\n", + &xhci->cap_regs->run_regs_off, + (unsigned int) temp & RTSOFF_MASK); + xhci_dbg(xhci, "// xHCI runtime registers at %p:\n", xhci->run_regs); + + temp = xhci_readl(xhci, &xhci->cap_regs->db_off); + xhci_dbg(xhci, "// @%p = 0x%x DBOFF\n", &xhci->cap_regs->db_off, temp); + xhci_dbg(xhci, "// Doorbell array at %p:\n", xhci->dba); +} + +static void xhci_print_cap_regs(struct xhci_hcd *xhci) +{ + u32 temp; + + xhci_dbg(xhci, "xHCI capability registers at %p:\n", xhci->cap_regs); + + temp = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); + xhci_dbg(xhci, "CAPLENGTH AND HCIVERSION 0x%x:\n", + (unsigned int) temp); + xhci_dbg(xhci, "CAPLENGTH: 0x%x\n", + (unsigned int) HC_LENGTH(temp)); + xhci_dbg(xhci, "HCIVERSION: 0x%x\n", + (unsigned int) HC_VERSION(temp)); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params1); + xhci_dbg(xhci, "HCSPARAMS 1: 0x%x\n", + (unsigned int) temp); + xhci_dbg(xhci, " Max device slots: %u\n", + (unsigned int) HCS_MAX_SLOTS(temp)); + xhci_dbg(xhci, " Max interrupters: %u\n", + (unsigned int) HCS_MAX_INTRS(temp)); + xhci_dbg(xhci, " Max ports: %u\n", + (unsigned int) HCS_MAX_PORTS(temp)); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params2); + xhci_dbg(xhci, "HCSPARAMS 2: 0x%x\n", + (unsigned int) temp); + xhci_dbg(xhci, " Isoc scheduling threshold: %u\n", + (unsigned int) HCS_IST(temp)); + xhci_dbg(xhci, " Maximum allowed segments in event ring: %u\n", + (unsigned int) HCS_ERST_MAX(temp)); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params3); + xhci_dbg(xhci, "HCSPARAMS 3 0x%x:\n", + (unsigned int) temp); + xhci_dbg(xhci, " Worst case U1 device exit latency: %u\n", + (unsigned int) HCS_U1_LATENCY(temp)); + xhci_dbg(xhci, " Worst case U2 device exit latency: %u\n", + (unsigned int) HCS_U2_LATENCY(temp)); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + xhci_dbg(xhci, "HCC PARAMS 0x%x:\n", (unsigned int) temp); + xhci_dbg(xhci, " HC generates %s bit addresses\n", + HCC_64BIT_ADDR(temp) ? "64" : "32"); + /* FIXME */ + xhci_dbg(xhci, " FIXME: more HCCPARAMS debugging\n"); + + temp = xhci_readl(xhci, &xhci->cap_regs->run_regs_off); + xhci_dbg(xhci, "RTSOFF 0x%x:\n", temp & RTSOFF_MASK); +} + +static void xhci_print_command_reg(struct xhci_hcd *xhci) +{ + u32 temp; + + temp = xhci_readl(xhci, &xhci->op_regs->command); + xhci_dbg(xhci, "USBCMD 0x%x:\n", temp); + xhci_dbg(xhci, " HC is %s\n", + (temp & CMD_RUN) ? "running" : "being stopped"); + xhci_dbg(xhci, " HC has %sfinished hard reset\n", + (temp & CMD_RESET) ? "not " : ""); + xhci_dbg(xhci, " Event Interrupts %s\n", + (temp & CMD_EIE) ? "enabled " : "disabled"); + xhci_dbg(xhci, " Host System Error Interrupts %s\n", + (temp & CMD_HSEIE) ? "enabled " : "disabled"); + xhci_dbg(xhci, " HC has %sfinished light reset\n", + (temp & CMD_LRESET) ? "not " : ""); +} + +static void xhci_print_status(struct xhci_hcd *xhci) +{ + u32 temp; + + temp = xhci_readl(xhci, &xhci->op_regs->status); + xhci_dbg(xhci, "USBSTS 0x%x:\n", temp); + xhci_dbg(xhci, " Event ring is %sempty\n", + (temp & STS_EINT) ? "not " : ""); + xhci_dbg(xhci, " %sHost System Error\n", + (temp & STS_FATAL) ? "WARNING: " : "No "); + xhci_dbg(xhci, " HC is %s\n", + (temp & STS_HALT) ? "halted" : "running"); +} + +static void xhci_print_op_regs(struct xhci_hcd *xhci) +{ + xhci_dbg(xhci, "xHCI operational registers at %p:\n", xhci->op_regs); + xhci_print_command_reg(xhci); + xhci_print_status(xhci); +} + +static void xhci_print_ports(struct xhci_hcd *xhci) +{ + __le32 __iomem *addr; + int i, j; + int ports; + char *names[NUM_PORT_REGS] = { + "status", + "power", + "link", + "reserved", + }; + + ports = HCS_MAX_PORTS(xhci->hcs_params1); + addr = &xhci->op_regs->port_status_base; + for (i = 0; i < ports; i++) { + for (j = 0; j < NUM_PORT_REGS; ++j) { + xhci_dbg(xhci, "%p port %s reg = 0x%x\n", + addr, names[j], + (unsigned int) xhci_readl(xhci, addr)); + addr++; + } + } +} + +void xhci_print_ir_set(struct xhci_hcd *xhci, int set_num) +{ + struct xhci_intr_reg __iomem *ir_set = &xhci->run_regs->ir_set[set_num]; + void __iomem *addr; + u32 temp; + u64 temp_64; + + addr = &ir_set->irq_pending; + temp = xhci_readl(xhci, addr); + if (temp == XHCI_INIT_VALUE) + return; + + xhci_dbg(xhci, " %p: ir_set[%i]\n", ir_set, set_num); + + xhci_dbg(xhci, " %p: ir_set.pending = 0x%x\n", addr, + (unsigned int)temp); + + addr = &ir_set->irq_control; + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, " %p: ir_set.control = 0x%x\n", addr, + (unsigned int)temp); + + addr = &ir_set->erst_size; + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, " %p: ir_set.erst_size = 0x%x\n", addr, + (unsigned int)temp); + + addr = &ir_set->rsvd; + temp = xhci_readl(xhci, addr); + if (temp != XHCI_INIT_VALUE) + xhci_dbg(xhci, " WARN: %p: ir_set.rsvd = 0x%x\n", + addr, (unsigned int)temp); + + addr = &ir_set->erst_base; + temp_64 = xhci_read_64(xhci, addr); + xhci_dbg(xhci, " %p: ir_set.erst_base = @%08llx\n", + addr, temp_64); + + addr = &ir_set->erst_dequeue; + temp_64 = xhci_read_64(xhci, addr); + xhci_dbg(xhci, " %p: ir_set.erst_dequeue = @%08llx\n", + addr, temp_64); +} + +void xhci_print_run_regs(struct xhci_hcd *xhci) +{ + u32 temp; + int i; + + xhci_dbg(xhci, "xHCI runtime registers at %p:\n", xhci->run_regs); + temp = xhci_readl(xhci, &xhci->run_regs->microframe_index); + xhci_dbg(xhci, " %p: Microframe index = 0x%x\n", + &xhci->run_regs->microframe_index, + (unsigned int) temp); + for (i = 0; i < 7; ++i) { + temp = xhci_readl(xhci, &xhci->run_regs->rsvd[i]); + if (temp != XHCI_INIT_VALUE) + xhci_dbg(xhci, " WARN: %p: Rsvd[%i] = 0x%x\n", + &xhci->run_regs->rsvd[i], + i, (unsigned int) temp); + } +} + +void xhci_print_registers(struct xhci_hcd *xhci) +{ + xhci_print_cap_regs(xhci); + xhci_print_op_regs(xhci); + xhci_print_ports(xhci); +} + +void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb) +{ + int i; + for (i = 0; i < 4; ++i) + xhci_dbg(xhci, "Offset 0x%x = 0x%x\n", + i*4, trb->generic.field[i]); +} + +/** + * Debug a transfer request block (TRB). + */ +void xhci_debug_trb(struct xhci_hcd *xhci, union xhci_trb *trb) +{ + u64 address; + u32 type = le32_to_cpu(trb->link.control) & TRB_TYPE_BITMASK; + + switch (type) { + case TRB_TYPE(TRB_LINK): + xhci_dbg(xhci, "Link TRB:\n"); + xhci_print_trb_offsets(xhci, trb); + + address = le64_to_cpu(trb->link.segment_ptr); + xhci_dbg(xhci, "Next ring segment DMA address = 0x%llx\n", address); + + xhci_dbg(xhci, "Interrupter target = 0x%x\n", + GET_INTR_TARGET(le32_to_cpu(trb->link.intr_target))); + xhci_dbg(xhci, "Cycle bit = %u\n", + le32_to_cpu(trb->link.control) & TRB_CYCLE); + xhci_dbg(xhci, "Toggle cycle bit = %u\n", + le32_to_cpu(trb->link.control) & LINK_TOGGLE); + xhci_dbg(xhci, "No Snoop bit = %u\n", + le32_to_cpu(trb->link.control) & TRB_NO_SNOOP); + break; + case TRB_TYPE(TRB_TRANSFER): + address = le64_to_cpu(trb->trans_event.buffer); + /* + * FIXME: look at flags to figure out if it's an address or if + * the data is directly in the buffer field. + */ + xhci_dbg(xhci, "DMA address or buffer contents= %llu\n", address); + break; + case TRB_TYPE(TRB_COMPLETION): + address = le64_to_cpu(trb->event_cmd.cmd_trb); + xhci_dbg(xhci, "Command TRB pointer = %llu\n", address); + xhci_dbg(xhci, "Completion status = %u\n", + GET_COMP_CODE(le32_to_cpu(trb->event_cmd.status))); + xhci_dbg(xhci, "Flags = 0x%x\n", + le32_to_cpu(trb->event_cmd.flags)); + break; + default: + xhci_dbg(xhci, "Unknown TRB with TRB type ID %u\n", + (unsigned int) type>>10); + xhci_print_trb_offsets(xhci, trb); + break; + } +} + +/** + * Debug a segment with an xHCI ring. + * + * @return The Link TRB of the segment, or NULL if there is no Link TRB + * (which is a bug, since all segments must have a Link TRB). + * + * Prints out all TRBs in the segment, even those after the Link TRB. + * + * XXX: should we print out TRBs that the HC owns? As long as we don't + * write, that should be fine... We shouldn't expect that the memory pointed to + * by the TRB is valid at all. Do we care about ones the HC owns? Probably, + * for HC debugging. + */ +void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg) +{ + int i; + u64 addr = seg->dma; + union xhci_trb *trb = seg->trbs; + + for (i = 0; i < TRBS_PER_SEGMENT; ++i) { + trb = &seg->trbs[i]; + xhci_dbg(xhci, "@%016llx %08x %08x %08x %08x\n", addr, + lower_32_bits(le64_to_cpu(trb->link.segment_ptr)), + upper_32_bits(le64_to_cpu(trb->link.segment_ptr)), + le32_to_cpu(trb->link.intr_target), + le32_to_cpu(trb->link.control)); + addr += sizeof(*trb); + } +} + +void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + xhci_dbg(xhci, "Ring deq = %p (virt), 0x%llx (dma)\n", + ring->dequeue, + (unsigned long long)xhci_trb_virt_to_dma(ring->deq_seg, + ring->dequeue)); + xhci_dbg(xhci, "Ring deq updated %u times\n", + ring->deq_updates); + xhci_dbg(xhci, "Ring enq = %p (virt), 0x%llx (dma)\n", + ring->enqueue, + (unsigned long long)xhci_trb_virt_to_dma(ring->enq_seg, + ring->enqueue)); + xhci_dbg(xhci, "Ring enq updated %u times\n", + ring->enq_updates); +} + +/** + * Debugging for an xHCI ring, which is a queue broken into multiple segments. + * + * Print out each segment in the ring. Check that the DMA address in + * each link segment actually matches the segment's stored DMA address. + * Check that the link end bit is only set at the end of the ring. + * Check that the dequeue and enqueue pointers point to real data in this ring + * (not some other ring). + */ +void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + /* FIXME: Throw an error if any segment doesn't have a Link TRB */ + struct xhci_segment *seg; + struct xhci_segment *first_seg = ring->first_seg; + xhci_debug_segment(xhci, first_seg); + + if (!ring->enq_updates && !ring->deq_updates) { + xhci_dbg(xhci, " Ring has not been updated\n"); + return; + } + for (seg = first_seg->next; seg != first_seg; seg = seg->next) + xhci_debug_segment(xhci, seg); +} + +void xhci_dbg_ep_rings(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + struct xhci_virt_ep *ep) +{ + int i; + struct xhci_ring *ring; + + if (ep->ep_state & EP_HAS_STREAMS) { + for (i = 1; i < ep->stream_info->num_streams; i++) { + ring = ep->stream_info->stream_rings[i]; + xhci_dbg(xhci, "Dev %d endpoint %d stream ID %d:\n", + slot_id, ep_index, i); + xhci_debug_segment(xhci, ring->deq_seg); + } + } else { + ring = ep->ring; + if (!ring) + return; + xhci_dbg(xhci, "Dev %d endpoint ring %d:\n", + slot_id, ep_index); + xhci_debug_segment(xhci, ring->deq_seg); + } +} + +void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst) +{ + u64 addr = erst->erst_dma_addr; + int i; + struct xhci_erst_entry *entry; + + for (i = 0; i < erst->num_entries; ++i) { + entry = &erst->entries[i]; + xhci_dbg(xhci, "@%016llx %08x %08x %08x %08x\n", + addr, + lower_32_bits(le64_to_cpu(entry->seg_addr)), + upper_32_bits(le64_to_cpu(entry->seg_addr)), + le32_to_cpu(entry->seg_size), + le32_to_cpu(entry->rsvd)); + addr += sizeof(*entry); + } +} + +void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci) +{ + u64 val; + + val = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + xhci_dbg(xhci, "// xHC command ring deq ptr low bits + flags = @%08x\n", + lower_32_bits(val)); + xhci_dbg(xhci, "// xHC command ring deq ptr high bits = @%08x\n", + upper_32_bits(val)); +} + +/* Print the last 32 bytes for 64-byte contexts */ +static void dbg_rsvd64(struct xhci_hcd *xhci, u64 *ctx, dma_addr_t dma) +{ + int i; + for (i = 0; i < 4; ++i) { + xhci_dbg(xhci, "@%p (virt) @%08llx " + "(dma) %#08llx - rsvd64[%d]\n", + &ctx[4 + i], (unsigned long long)dma, + ctx[4 + i], i); + dma += 8; + } +} + +char *xhci_get_slot_state(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx) +{ + struct xhci_slot_ctx *slot_ctx = xhci_get_slot_ctx(xhci, ctx); + + switch (GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state))) { + case SLOT_STATE_ENABLED: + return "enabled/disabled"; + case SLOT_STATE_DEFAULT: + return "default"; + case SLOT_STATE_ADDRESSED: + return "addressed"; + case SLOT_STATE_CONFIGURED: + return "configured"; + default: + return "reserved"; + } +} + +static void xhci_dbg_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx) +{ + /* Fields are 32 bits wide, DMA addresses are in bytes */ + int field_size = 32 / 8; + int i; + + struct xhci_slot_ctx *slot_ctx = xhci_get_slot_ctx(xhci, ctx); + dma_addr_t dma = ctx->dma + + ((unsigned long)slot_ctx - (unsigned long)ctx->bytes); + int csz = HCC_64BYTE_CONTEXT(xhci->hcc_params); + + xhci_dbg(xhci, "Slot Context:\n"); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_info\n", + &slot_ctx->dev_info, + (unsigned long long)dma, slot_ctx->dev_info); + dma += field_size; + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_info2\n", + &slot_ctx->dev_info2, + (unsigned long long)dma, slot_ctx->dev_info2); + dma += field_size; + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - tt_info\n", + &slot_ctx->tt_info, + (unsigned long long)dma, slot_ctx->tt_info); + dma += field_size; + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_state\n", + &slot_ctx->dev_state, + (unsigned long long)dma, slot_ctx->dev_state); + dma += field_size; + for (i = 0; i < 4; ++i) { + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd[%d]\n", + &slot_ctx->reserved[i], (unsigned long long)dma, + slot_ctx->reserved[i], i); + dma += field_size; + } + + if (csz) + dbg_rsvd64(xhci, (u64 *)slot_ctx, dma); +} + +static void xhci_dbg_ep_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx, + unsigned int last_ep) +{ + int i, j; + int last_ep_ctx = 31; + /* Fields are 32 bits wide, DMA addresses are in bytes */ + int field_size = 32 / 8; + int csz = HCC_64BYTE_CONTEXT(xhci->hcc_params); + + if (last_ep < 31) + last_ep_ctx = last_ep + 1; + for (i = 0; i < last_ep_ctx; ++i) { + struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, ctx, i); + dma_addr_t dma = ctx->dma + + ((unsigned long)ep_ctx - (unsigned long)ctx->bytes); + + xhci_dbg(xhci, "Endpoint %02d Context:\n", i); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info\n", + &ep_ctx->ep_info, + (unsigned long long)dma, ep_ctx->ep_info); + dma += field_size; + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info2\n", + &ep_ctx->ep_info2, + (unsigned long long)dma, ep_ctx->ep_info2); + dma += field_size; + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08llx - deq\n", + &ep_ctx->deq, + (unsigned long long)dma, ep_ctx->deq); + dma += 2*field_size; + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - tx_info\n", + &ep_ctx->tx_info, + (unsigned long long)dma, ep_ctx->tx_info); + dma += field_size; + for (j = 0; j < 3; ++j) { + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd[%d]\n", + &ep_ctx->reserved[j], + (unsigned long long)dma, + ep_ctx->reserved[j], j); + dma += field_size; + } + + if (csz) + dbg_rsvd64(xhci, (u64 *)ep_ctx, dma); + } +} + +void xhci_dbg_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx, + unsigned int last_ep) +{ + int i; + /* Fields are 32 bits wide, DMA addresses are in bytes */ + int field_size = 32 / 8; + dma_addr_t dma = ctx->dma; + int csz = HCC_64BYTE_CONTEXT(xhci->hcc_params); + + if (ctx->type == XHCI_CTX_TYPE_INPUT) { + struct xhci_input_control_ctx *ctrl_ctx = + xhci_get_input_control_ctx(xhci, ctx); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - drop flags\n", + &ctrl_ctx->drop_flags, (unsigned long long)dma, + ctrl_ctx->drop_flags); + dma += field_size; + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - add flags\n", + &ctrl_ctx->add_flags, (unsigned long long)dma, + ctrl_ctx->add_flags); + dma += field_size; + for (i = 0; i < 6; ++i) { + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd2[%d]\n", + &ctrl_ctx->rsvd2[i], (unsigned long long)dma, + ctrl_ctx->rsvd2[i], i); + dma += field_size; + } + + if (csz) + dbg_rsvd64(xhci, (u64 *)ctrl_ctx, dma); + } + + xhci_dbg_slot_ctx(xhci, ctx); + xhci_dbg_ep_ctx(xhci, ctx, last_ep); +} diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h new file mode 100644 index 00000000..377f4242 --- /dev/null +++ b/drivers/usb/host/xhci-ext-caps.h @@ -0,0 +1,155 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* Up to 16 ms to halt an HC */ +#define XHCI_MAX_HALT_USEC (16*1000) +/* HC not running - set to 1 when run/stop bit is cleared. */ +#define XHCI_STS_HALT (1<<0) + +/* HCCPARAMS offset from PCI base address */ +#define XHCI_HCC_PARAMS_OFFSET 0x10 +/* HCCPARAMS contains the first extended capability pointer */ +#define XHCI_HCC_EXT_CAPS(p) (((p)>>16)&0xffff) + +/* Command and Status registers offset from the Operational Registers address */ +#define XHCI_CMD_OFFSET 0x00 +#define XHCI_STS_OFFSET 0x04 + +#define XHCI_MAX_EXT_CAPS 50 + +/* Capability Register */ +/* bits 7:0 - how long is the Capabilities register */ +#define XHCI_HC_LENGTH(p) (((p)>>00)&0x00ff) + +/* Extended capability register fields */ +#define XHCI_EXT_CAPS_ID(p) (((p)>>0)&0xff) +#define XHCI_EXT_CAPS_NEXT(p) (((p)>>8)&0xff) +#define XHCI_EXT_CAPS_VAL(p) ((p)>>16) +/* Extended capability IDs - ID 0 reserved */ +#define XHCI_EXT_CAPS_LEGACY 1 +#define XHCI_EXT_CAPS_PROTOCOL 2 +#define XHCI_EXT_CAPS_PM 3 +#define XHCI_EXT_CAPS_VIRT 4 +#define XHCI_EXT_CAPS_ROUTE 5 +/* IDs 6-9 reserved */ +#define XHCI_EXT_CAPS_DEBUG 10 +/* USB Legacy Support Capability - section 7.1.1 */ +#define XHCI_HC_BIOS_OWNED (1 << 16) +#define XHCI_HC_OS_OWNED (1 << 24) + +/* USB Legacy Support Capability - section 7.1.1 */ +/* Add this offset, plus the value of xECP in HCCPARAMS to the base address */ +#define XHCI_LEGACY_SUPPORT_OFFSET (0x00) + +/* USB Legacy Support Control and Status Register - section 7.1.2 */ +/* Add this offset, plus the value of xECP in HCCPARAMS to the base address */ +#define XHCI_LEGACY_CONTROL_OFFSET (0x04) +/* bits 1:3, 5:12, and 17:19 need to be preserved; bits 21:28 should be zero */ +#define XHCI_LEGACY_DISABLE_SMI ((0x7 << 1) + (0xff << 5) + (0x7 << 17)) +#define XHCI_LEGACY_SMI_EVENTS (0x7 << 29) + +/* USB 2.0 xHCI 0.96 L1C capability - section 7.2.2.1.3.2 */ +#define XHCI_L1C (1 << 16) + +/* USB 2.0 xHCI 1.0 hardware LMP capability - section 7.2.2.1.3.2 */ +#define XHCI_HLC (1 << 19) + +/* command register values to disable interrupts and halt the HC */ +/* start/stop HC execution - do not write unless HC is halted*/ +#define XHCI_CMD_RUN (1 << 0) +/* Event Interrupt Enable - get irq when EINT bit is set in USBSTS register */ +#define XHCI_CMD_EIE (1 << 2) +/* Host System Error Interrupt Enable - get irq when HSEIE bit set in USBSTS */ +#define XHCI_CMD_HSEIE (1 << 3) +/* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ +#define XHCI_CMD_EWE (1 << 10) + +#define XHCI_IRQS (XHCI_CMD_EIE | XHCI_CMD_HSEIE | XHCI_CMD_EWE) + +/* true: Controller Not Ready to accept doorbell or op reg writes after reset */ +#define XHCI_STS_CNR (1 << 11) + +#include + +/** + * Return the next extended capability pointer register. + * + * @base PCI register base address. + * + * @ext_offset Offset of the 32-bit register that contains the extended + * capabilites pointer. If searching for the first extended capability, pass + * in XHCI_HCC_PARAMS_OFFSET. If searching for the next extended capability, + * pass in the offset of the current extended capability register. + * + * Returns 0 if there is no next extended capability register or returns the register offset + * from the PCI registers base address. + */ +static inline int xhci_find_next_cap_offset(void __iomem *base, int ext_offset) +{ + u32 next; + + next = readl(base + ext_offset); + + if (ext_offset == XHCI_HCC_PARAMS_OFFSET) { + /* Find the first extended capability */ + next = XHCI_HCC_EXT_CAPS(next); + ext_offset = 0; + } else { + /* Find the next extended capability */ + next = XHCI_EXT_CAPS_NEXT(next); + } + + if (!next) + return 0; + /* + * Address calculation from offset of extended capabilities + * (or HCCPARAMS) register - see section 5.3.6 and section 7. + */ + return ext_offset + (next << 2); +} + +/** + * Find the offset of the extended capabilities with capability ID id. + * + * @base PCI MMIO registers base address. + * @ext_offset Offset from base of the first extended capability to look at, + * or the address of HCCPARAMS. + * @id Extended capability ID to search for. + * + * This uses an arbitrary limit of XHCI_MAX_EXT_CAPS extended capabilities + * to make sure that the list doesn't contain a loop. + */ +static inline int xhci_find_ext_cap_by_id(void __iomem *base, int ext_offset, int id) +{ + u32 val; + int limit = XHCI_MAX_EXT_CAPS; + + while (ext_offset && limit > 0) { + val = readl(base + ext_offset); + if (XHCI_EXT_CAPS_ID(val) == id) + break; + ext_offset = xhci_find_next_cap_offset(base, ext_offset); + limit--; + } + if (limit > 0) + return ext_offset; + return 0; +} diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c new file mode 100644 index 00000000..d939376c --- /dev/null +++ b/drivers/usb/host/xhci-hub.c @@ -0,0 +1,1218 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include "xhci.h" + +#define PORT_WAKE_BITS (PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E) +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ + PORT_RC | PORT_PLC | PORT_PE) + +/* USB 3.0 BOS descriptor and a capability descriptor, combined */ +static u8 usb_bos_descriptor [] = { + USB_DT_BOS_SIZE, /* __u8 bLength, 5 bytes */ + USB_DT_BOS, /* __u8 bDescriptorType */ + 0x0F, 0x00, /* __le16 wTotalLength, 15 bytes */ + 0x1, /* __u8 bNumDeviceCaps */ + /* First device capability */ + USB_DT_USB_SS_CAP_SIZE, /* __u8 bLength, 10 bytes */ + USB_DT_DEVICE_CAPABILITY, /* Device Capability */ + USB_SS_CAP_TYPE, /* bDevCapabilityType, SUPERSPEED_USB */ + 0x00, /* bmAttributes, LTM off by default */ + USB_5GBPS_OPERATION, 0x00, /* wSpeedsSupported, 5Gbps only */ + 0x03, /* bFunctionalitySupport, + USB 3.0 speed only */ + 0x00, /* bU1DevExitLat, set later. */ + 0x00, 0x00 /* __le16 bU2DevExitLat, set later. */ +}; + + +static void xhci_common_hub_descriptor(struct xhci_hcd *xhci, + struct usb_hub_descriptor *desc, int ports) +{ + u16 temp; + + desc->bPwrOn2PwrGood = 10; /* xhci section 5.4.9 says 20ms max */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 0; + /* Bits 1:0 - support per-port power switching, or power always on */ + if (HCC_PPC(xhci->hcc_params)) + temp |= HUB_CHAR_INDV_PORT_LPSM; + else + temp |= HUB_CHAR_NO_LPSM; + /* Bit 2 - root hubs are not part of a compound device */ + /* Bits 4:3 - individual port over current protection */ + temp |= HUB_CHAR_INDV_PORT_OCPM; + /* Bits 6:5 - no TTs in root ports */ + /* Bit 7 - no port indicators */ + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +/* Fill in the USB 2.0 roothub descriptor */ +static void xhci_usb2_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, + struct usb_hub_descriptor *desc) +{ + int ports; + u16 temp; + __u8 port_removable[(USB_MAXCHILDREN + 1 + 7) / 8]; + u32 portsc; + unsigned int i; + + ports = xhci->num_usb2_ports; + + xhci_common_hub_descriptor(xhci, desc, ports); + desc->bDescriptorType = USB_DT_HUB; + temp = 1 + (ports / 8); + desc->bDescLength = USB_DT_HUB_NONVAR_SIZE + 2 * temp; + + /* The Device Removable bits are reported on a byte granularity. + * If the port doesn't exist within that byte, the bit is set to 0. + */ + memset(port_removable, 0, sizeof(port_removable)); + for (i = 0; i < ports; i++) { + portsc = xhci_readl(xhci, xhci->usb2_ports[i]); + /* If a device is removable, PORTSC reports a 0, same as in the + * hub descriptor DeviceRemovable bits. + */ + if (portsc & PORT_DEV_REMOVE) + /* This math is hairy because bit 0 of DeviceRemovable + * is reserved, and bit 1 is for port 1, etc. + */ + port_removable[(i + 1) / 8] |= 1 << ((i + 1) % 8); + } + + /* ch11.h defines a hub descriptor that has room for USB_MAXCHILDREN + * ports on it. The USB 2.0 specification says that there are two + * variable length fields at the end of the hub descriptor: + * DeviceRemovable and PortPwrCtrlMask. But since we can have less than + * USB_MAXCHILDREN ports, we may need to use the DeviceRemovable array + * to set PortPwrCtrlMask bits. PortPwrCtrlMask must always be set to + * 0xFF, so we initialize the both arrays (DeviceRemovable and + * PortPwrCtrlMask) to 0xFF. Then we set the DeviceRemovable for each + * set of ports that actually exist. + */ + memset(desc->u.hs.DeviceRemovable, 0xff, + sizeof(desc->u.hs.DeviceRemovable)); + memset(desc->u.hs.PortPwrCtrlMask, 0xff, + sizeof(desc->u.hs.PortPwrCtrlMask)); + + for (i = 0; i < (ports + 1 + 7) / 8; i++) + memset(&desc->u.hs.DeviceRemovable[i], port_removable[i], + sizeof(__u8)); +} + +/* Fill in the USB 3.0 roothub descriptor */ +static void xhci_usb3_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, + struct usb_hub_descriptor *desc) +{ + int ports; + u16 port_removable; + u32 portsc; + unsigned int i; + + ports = xhci->num_usb3_ports; + xhci_common_hub_descriptor(xhci, desc, ports); + desc->bDescriptorType = USB_DT_SS_HUB; + desc->bDescLength = USB_DT_SS_HUB_SIZE; + + /* header decode latency should be zero for roothubs, + * see section 4.23.5.2. + */ + desc->u.ss.bHubHdrDecLat = 0; + desc->u.ss.wHubDelay = 0; + + port_removable = 0; + /* bit 0 is reserved, bit 1 is for port 1, etc. */ + for (i = 0; i < ports; i++) { + portsc = xhci_readl(xhci, xhci->usb3_ports[i]); + if (portsc & PORT_DEV_REMOVE) + port_removable |= 1 << (i + 1); + } + + desc->u.ss.DeviceRemovable = cpu_to_le16(port_removable); +} + +static void xhci_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, + struct usb_hub_descriptor *desc) +{ + + if (hcd->speed == HCD_USB3) + xhci_usb3_hub_descriptor(hcd, xhci, desc); + else + xhci_usb2_hub_descriptor(hcd, xhci, desc); + +} + +static unsigned int xhci_port_speed(unsigned int port_status) +{ + if (DEV_LOWSPEED(port_status)) + return USB_PORT_STAT_LOW_SPEED; + if (DEV_HIGHSPEED(port_status)) + return USB_PORT_STAT_HIGH_SPEED; + /* + * FIXME: Yes, we should check for full speed, but the core uses that as + * a default in portspeed() in usb/core/hub.c (which is the only place + * USB_PORT_STAT_*_SPEED is used). + */ + return 0; +} + +/* + * These bits are Read Only (RO) and should be saved and written to the + * registers: 0, 3, 10:13, 30 + * connect status, over-current status, port speed, and device removable. + * connect status and port speed are also sticky - meaning they're in + * the AUX well and they aren't changed by a hot, warm, or cold reset. + */ +#define XHCI_PORT_RO ((1<<0) | (1<<3) | (0xf<<10) | (1<<30)) +/* + * These bits are RW; writing a 0 clears the bit, writing a 1 sets the bit: + * bits 5:8, 9, 14:15, 25:27 + * link state, port power, port indicator state, "wake on" enable state + */ +#define XHCI_PORT_RWS ((0xf<<5) | (1<<9) | (0x3<<14) | (0x7<<25)) +/* + * These bits are RW; writing a 1 sets the bit, writing a 0 has no effect: + * bit 4 (port reset) + */ +#define XHCI_PORT_RW1S ((1<<4)) +/* + * These bits are RW; writing a 1 clears the bit, writing a 0 has no effect: + * bits 1, 17, 18, 19, 20, 21, 22, 23 + * port enable/disable, and + * change bits: connect, PED, warm port reset changed (reserved zero for USB 2.0 ports), + * over-current, reset, link state, and L1 change + */ +#define XHCI_PORT_RW1CS ((1<<1) | (0x7f<<17)) +/* + * Bit 16 is RW, and writing a '1' to it causes the link state control to be + * latched in + */ +#define XHCI_PORT_RW ((1<<16)) +/* + * These bits are Reserved Zero (RsvdZ) and zero should be written to them: + * bits 2, 24, 28:31 + */ +#define XHCI_PORT_RZ ((1<<2) | (1<<24) | (0xf<<28)) + +/* + * Given a port state, this function returns a value that would result in the + * port being in the same state, if the value was written to the port status + * control register. + * Save Read Only (RO) bits and save read/write bits where + * writing a 0 clears the bit and writing a 1 sets the bit (RWS). + * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect. + */ +u32 xhci_port_state_to_neutral(u32 state) +{ + /* Save read-only status and port state */ + return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); +} + +/* + * find slot id based on port number. + * @port: The one-based port number from one of the two split roothubs. + */ +int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, + u16 port) +{ + int slot_id; + int i; + enum usb_device_speed speed; + + slot_id = 0; + for (i = 0; i < MAX_HC_SLOTS; i++) { + if (!xhci->devs[i]) + continue; + speed = xhci->devs[i]->udev->speed; + if (((speed == USB_SPEED_SUPER) == (hcd->speed == HCD_USB3)) + && xhci->devs[i]->fake_port == port) { + slot_id = i; + break; + } + } + + return slot_id; +} + +/* + * Stop device + * It issues stop endpoint command for EP 0 to 30. And wait the last command + * to complete. + * suspend will set to 1, if suspend bit need to set in command. + */ +static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend) +{ + struct xhci_virt_device *virt_dev; + struct xhci_command *cmd; + unsigned long flags; + int timeleft; + int ret; + int i; + + ret = 0; + virt_dev = xhci->devs[slot_id]; + cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO); + if (!cmd) { + xhci_dbg(xhci, "Couldn't allocate command structure.\n"); + return -ENOMEM; + } + + spin_lock_irqsave(&xhci->lock, flags); + for (i = LAST_EP_INDEX; i > 0; i--) { + if (virt_dev->eps[i].ring && virt_dev->eps[i].ring->dequeue) + xhci_queue_stop_endpoint(xhci, slot_id, i, suspend); + } + cmd->command_trb = xhci_find_next_enqueue(xhci->cmd_ring); + list_add_tail(&cmd->cmd_list, &virt_dev->cmd_list); + xhci_queue_stop_endpoint(xhci, slot_id, 0, suspend); + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Wait for last stop endpoint command to finish */ + timeleft = wait_for_completion_interruptible_timeout( + cmd->completion, + USB_CTRL_SET_TIMEOUT); + if (timeleft <= 0) { + xhci_warn(xhci, "%s while waiting for stop endpoint command\n", + timeleft == 0 ? "Timeout" : "Signal"); + spin_lock_irqsave(&xhci->lock, flags); + /* The timeout might have raced with the event ring handler, so + * only delete from the list if the item isn't poisoned. + */ + if (cmd->cmd_list.next != LIST_POISON1) + list_del(&cmd->cmd_list); + spin_unlock_irqrestore(&xhci->lock, flags); + ret = -ETIME; + goto command_cleanup; + } + +command_cleanup: + xhci_free_command(xhci, cmd); + return ret; +} + +/* + * Ring device, it rings the all doorbells unconditionally. + */ +void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) +{ + int i; + + for (i = 0; i < LAST_EP_INDEX + 1; i++) + if (xhci->devs[slot_id]->eps[i].ring && + xhci->devs[slot_id]->eps[i].ring->dequeue) + xhci_ring_ep_doorbell(xhci, slot_id, i, 0); + + return; +} + +static void xhci_disable_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, + u16 wIndex, __le32 __iomem *addr, u32 port_status) +{ + /* Don't allow the USB core to disable SuperSpeed ports. */ + if (hcd->speed == HCD_USB3) { + xhci_dbg(xhci, "Ignoring request to disable " + "SuperSpeed port.\n"); + return; + } + + /* Write 1 to disable the port */ + xhci_writel(xhci, port_status | PORT_PE, addr); + port_status = xhci_readl(xhci, addr); + xhci_dbg(xhci, "disable port, actual port %d status = 0x%x\n", + wIndex, port_status); +} + +static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, + u16 wIndex, __le32 __iomem *addr, u32 port_status) +{ + char *port_change_bit; + u32 status; + + switch (wValue) { + case USB_PORT_FEAT_C_RESET: + status = PORT_RC; + port_change_bit = "reset"; + break; + case USB_PORT_FEAT_C_BH_PORT_RESET: + status = PORT_WRC; + port_change_bit = "warm(BH) reset"; + break; + case USB_PORT_FEAT_C_CONNECTION: + status = PORT_CSC; + port_change_bit = "connect"; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + status = PORT_OCC; + port_change_bit = "over-current"; + break; + case USB_PORT_FEAT_C_ENABLE: + status = PORT_PEC; + port_change_bit = "enable/disable"; + break; + case USB_PORT_FEAT_C_SUSPEND: + status = PORT_PLC; + port_change_bit = "suspend/resume"; + break; + case USB_PORT_FEAT_C_PORT_LINK_STATE: + status = PORT_PLC; + port_change_bit = "link state"; + break; + default: + /* Should never happen */ + return; + } + /* Change bits are all write 1 to clear */ + xhci_writel(xhci, port_status | status, addr); + port_status = xhci_readl(xhci, addr); + xhci_dbg(xhci, "clear port %s change, actual port %d status = 0x%x\n", + port_change_bit, wIndex, port_status); +} + +static int xhci_get_ports(struct usb_hcd *hcd, __le32 __iomem ***port_array) +{ + int max_ports; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + if (hcd->speed == HCD_USB3) { + max_ports = xhci->num_usb3_ports; + *port_array = xhci->usb3_ports; + } else { + max_ports = xhci->num_usb2_ports; + *port_array = xhci->usb2_ports; + } + + return max_ports; +} + +void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array, + int port_id, u32 link_state) +{ + u32 temp; + + temp = xhci_readl(xhci, port_array[port_id]); + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | link_state; + xhci_writel(xhci, temp, port_array[port_id]); +} + +static void xhci_set_remote_wake_mask(struct xhci_hcd *xhci, + __le32 __iomem **port_array, int port_id, u16 wake_mask) +{ + u32 temp; + + temp = xhci_readl(xhci, port_array[port_id]); + temp = xhci_port_state_to_neutral(temp); + + if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_CONNECT) + temp |= PORT_WKCONN_E; + else + temp &= ~PORT_WKCONN_E; + + if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT) + temp |= PORT_WKDISC_E; + else + temp &= ~PORT_WKDISC_E; + + if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT) + temp |= PORT_WKOC_E; + else + temp &= ~PORT_WKOC_E; + + xhci_writel(xhci, temp, port_array[port_id]); +} + +/* Test and clear port RWC bit */ +void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array, + int port_id, u32 port_bit) +{ + u32 temp; + + temp = xhci_readl(xhci, port_array[port_id]); + if (temp & port_bit) { + temp = xhci_port_state_to_neutral(temp); + temp |= port_bit; + xhci_writel(xhci, temp, port_array[port_id]); + } +} + +/* Updates Link Status for super Speed port */ +static void xhci_hub_report_link_state(struct xhci_hcd *xhci, + u32 *status, u32 status_reg) +{ + u32 pls = status_reg & PORT_PLS_MASK; + + /* resume state is a xHCI internal state. + * Do not report it to usb core. + */ + if (pls == XDEV_RESUME) + return; + + /* When the CAS bit is set then warm reset + * should be performed on port + */ + if (status_reg & PORT_CAS) { + /* The CAS bit can be set while the port is + * in any link state. + * Only roothubs have CAS bit, so we + * pretend to be in compliance mode + * unless we're already in compliance + * or the inactive state. + */ + if (pls != USB_SS_PORT_LS_COMP_MOD && + pls != USB_SS_PORT_LS_SS_INACTIVE) { + pls = USB_SS_PORT_LS_COMP_MOD; + } + /* Return also connection bit - + * hub state machine resets port + * when this bit is set. + */ + pls |= USB_PORT_STAT_CONNECTION; + } else { + /* + * If CAS bit isn't set but the Port is already at + * Compliance Mode, fake a connection so the USB core + * notices the Compliance state and resets the port. + * This resolves an issue generated by the SN65LVPE502CP + * in which sometimes the port enters compliance mode + * caused by a delay on the host-device negotiation. + */ + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && + (pls == USB_SS_PORT_LS_COMP_MOD)) + pls |= USB_PORT_STAT_CONNECTION; + } + + /* update status field */ + *status |= pls; +} + +/* + * Function for Compliance Mode Quirk. + * + * This Function verifies if all xhc USB3 ports have entered U0, if so, + * the compliance mode timer is deleted. A port won't enter + * compliance mode if it has previously entered U0. + */ +void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, u16 wIndex) +{ + u32 all_ports_seen_u0 = ((1 << xhci->num_usb3_ports)-1); + bool port_in_u0 = ((status & PORT_PLS_MASK) == XDEV_U0); + + if (!(xhci->quirks & XHCI_COMP_MODE_QUIRK)) + return; + + if ((xhci->port_status_u0 != all_ports_seen_u0) && port_in_u0) { + xhci->port_status_u0 |= 1 << wIndex; + if (xhci->port_status_u0 == all_ports_seen_u0) { + del_timer_sync(&xhci->comp_mode_recovery_timer); + xhci_dbg(xhci, "All USB3 ports have entered U0 already!\n"); + xhci_dbg(xhci, "Compliance Mode Recovery Timer Deleted.\n"); + } + } +} + +int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int max_ports; + unsigned long flags; + u32 temp, status; + int retval = 0; + __le32 __iomem **port_array; + int slot_id; + struct xhci_bus_state *bus_state; + u16 link_state = 0; + u16 wake_mask = 0; + u16 timeout = 0; + + max_ports = xhci_get_ports(hcd, &port_array); + bus_state = &xhci->bus_state[hcd_index(hcd)]; + + spin_lock_irqsave(&xhci->lock, flags); + switch (typeReq) { + case GetHubStatus: + /* No power source, over-current reported per port */ + memset(buf, 0, 4); + break; + case GetHubDescriptor: + /* Check to make sure userspace is asking for the USB 3.0 hub + * descriptor for the USB 3.0 roothub. If not, we stall the + * endpoint, like external hubs do. + */ + if (hcd->speed == HCD_USB3 && + (wLength < USB_DT_SS_HUB_SIZE || + wValue != (USB_DT_SS_HUB << 8))) { + xhci_dbg(xhci, "Wrong hub descriptor type for " + "USB 3.0 roothub.\n"); + goto error; + } + xhci_hub_descriptor(hcd, xhci, + (struct usb_hub_descriptor *) buf); + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + if ((wValue & 0xff00) != (USB_DT_BOS << 8)) + goto error; + + if (hcd->speed != HCD_USB3) + goto error; + + /* Set the U1 and U2 exit latencies. */ + memcpy(buf, &usb_bos_descriptor, + USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE); + temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params3); + buf[12] = HCS_U1_LATENCY(temp); + put_unaligned_le16(HCS_U2_LATENCY(temp), &buf[13]); + + /* Indicate whether the host has LTM support. */ + temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + if (HCC_LTC(temp)) + buf[8] |= USB_LTM_SUPPORT; + + spin_unlock_irqrestore(&xhci->lock, flags); + return USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE; + case GetPortStatus: + if (!wIndex || wIndex > max_ports) + goto error; + wIndex--; + status = 0; + temp = xhci_readl(xhci, port_array[wIndex]); + if (temp == 0xffffffff) { + retval = -ENODEV; + break; + } + xhci_dbg(xhci, "get port status, actual port %d status = 0x%x\n", wIndex, temp); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + if (temp & PORT_PEC) + status |= USB_PORT_STAT_C_ENABLE << 16; + if ((temp & PORT_OCC)) + status |= USB_PORT_STAT_C_OVERCURRENT << 16; + if ((temp & PORT_RC)) + status |= USB_PORT_STAT_C_RESET << 16; + /* USB3.0 only */ + if (hcd->speed == HCD_USB3) { + if ((temp & PORT_PLC)) + status |= USB_PORT_STAT_C_LINK_STATE << 16; + if ((temp & PORT_WRC)) + status |= USB_PORT_STAT_C_BH_RESET << 16; + } + + if (hcd->speed != HCD_USB3) { + if ((temp & PORT_PLS_MASK) == XDEV_U3 + && (temp & PORT_POWER)) + status |= USB_PORT_STAT_SUSPEND; + } + if ((temp & PORT_PLS_MASK) == XDEV_RESUME && + !DEV_SUPERSPEED(temp)) { + if ((temp & PORT_RESET) || !(temp & PORT_PE)) + goto error; + if (time_after_eq(jiffies, + bus_state->resume_done[wIndex])) { + xhci_dbg(xhci, "Resume USB2 port %d\n", + wIndex + 1); + bus_state->resume_done[wIndex] = 0; + clear_bit(wIndex, &bus_state->resuming_ports); + xhci_set_link_state(xhci, port_array, wIndex, + XDEV_U0); + xhci_dbg(xhci, "set port %d resume\n", + wIndex + 1); + slot_id = xhci_find_slot_id_by_port(hcd, xhci, + wIndex + 1); + if (!slot_id) { + xhci_dbg(xhci, "slot_id is zero\n"); + goto error; + } + xhci_ring_device(xhci, slot_id); + bus_state->port_c_suspend |= 1 << wIndex; + bus_state->suspended_ports &= ~(1 << wIndex); + } else { + /* + * The resume has been signaling for less than + * 20ms. Report the port status as SUSPEND, + * let the usbcore check port status again + * and clear resume signaling later. + */ + status |= USB_PORT_STAT_SUSPEND; + } + } + if ((temp & PORT_PLS_MASK) == XDEV_U0 + && (temp & PORT_POWER) + && (bus_state->suspended_ports & (1 << wIndex))) { + bus_state->suspended_ports &= ~(1 << wIndex); + if (hcd->speed != HCD_USB3) + bus_state->port_c_suspend |= 1 << wIndex; + } + if (temp & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + status |= xhci_port_speed(temp); + } + if (temp & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + if (temp & PORT_OC) + status |= USB_PORT_STAT_OVERCURRENT; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (temp & PORT_POWER) { + if (hcd->speed == HCD_USB3) + status |= USB_SS_PORT_STAT_POWER; + else + status |= USB_PORT_STAT_POWER; + } + /* Update Port Link State for super speed ports*/ + if (hcd->speed == HCD_USB3) { + xhci_hub_report_link_state(xhci, &status, temp); + /* + * Verify if all USB3 Ports Have entered U0 already. + * Delete Compliance Mode Timer if so. + */ + xhci_del_comp_mod_timer(xhci, temp, wIndex); + } + if (bus_state->port_c_suspend & (1 << wIndex)) + status |= 1 << USB_PORT_FEAT_C_SUSPEND; + xhci_dbg(xhci, "Get port status returned 0x%x\n", status); + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetPortFeature: + if (wValue == USB_PORT_FEAT_LINK_STATE) + link_state = (wIndex & 0xff00) >> 3; + if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK) + wake_mask = wIndex & 0xff00; + /* The MSB of wIndex is the U1/U2 timeout */ + timeout = (wIndex & 0xff00) >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > max_ports) + goto error; + wIndex--; + temp = xhci_readl(xhci, port_array[wIndex]); + if (temp == 0xffffffff) { + retval = -ENODEV; + break; + } + temp = xhci_port_state_to_neutral(temp); + /* FIXME: What new port features do we need to support? */ + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + temp = xhci_readl(xhci, port_array[wIndex]); + if ((temp & PORT_PLS_MASK) != XDEV_U0) { + /* Resume the port to U0 first */ + xhci_set_link_state(xhci, port_array, wIndex, + XDEV_U0); + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(10); + spin_lock_irqsave(&xhci->lock, flags); + } + /* In spec software should not attempt to suspend + * a port unless the port reports that it is in the + * enabled (PED = ‘1’,PLS < ‘3’) state. + */ + temp = xhci_readl(xhci, port_array[wIndex]); + if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) + || (temp & PORT_PLS_MASK) >= XDEV_U3) { + xhci_warn(xhci, "USB core suspending device " + "not in U0/U1/U2.\n"); + goto error; + } + + slot_id = xhci_find_slot_id_by_port(hcd, xhci, + wIndex + 1); + if (!slot_id) { + xhci_warn(xhci, "slot_id is zero\n"); + goto error; + } + /* unlock to execute stop endpoint commands */ + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_stop_device(xhci, slot_id, 1); + spin_lock_irqsave(&xhci->lock, flags); + + xhci_set_link_state(xhci, port_array, wIndex, XDEV_U3); + + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(10); /* wait device to enter */ + spin_lock_irqsave(&xhci->lock, flags); + + temp = xhci_readl(xhci, port_array[wIndex]); + bus_state->suspended_ports |= 1 << wIndex; + break; + case USB_PORT_FEAT_LINK_STATE: + temp = xhci_readl(xhci, port_array[wIndex]); + + /* Disable port */ + if (link_state == USB_SS_PORT_LS_SS_DISABLED) { + xhci_dbg(xhci, "Disable port %d\n", wIndex); + temp = xhci_port_state_to_neutral(temp); + /* + * Clear all change bits, so that we get a new + * connection event. + */ + temp |= PORT_CSC | PORT_PEC | PORT_WRC | + PORT_OCC | PORT_RC | PORT_PLC | + PORT_CEC; + xhci_writel(xhci, temp | PORT_PE, + port_array[wIndex]); + temp = xhci_readl(xhci, port_array[wIndex]); + break; + } + + /* Put link in RxDetect (enable port) */ + if (link_state == USB_SS_PORT_LS_RX_DETECT) { + xhci_dbg(xhci, "Enable port %d\n", wIndex); + xhci_set_link_state(xhci, port_array, wIndex, + link_state); + temp = xhci_readl(xhci, port_array[wIndex]); + break; + } + + /* Software should not attempt to set + * port link state above '3' (U3) and the port + * must be enabled. + */ + if ((temp & PORT_PE) == 0 || + (link_state > USB_SS_PORT_LS_U3)) { + xhci_warn(xhci, "Cannot set link state.\n"); + goto error; + } + + if (link_state == USB_SS_PORT_LS_U3) { + slot_id = xhci_find_slot_id_by_port(hcd, xhci, + wIndex + 1); + if (slot_id) { + /* unlock to execute stop endpoint + * commands */ + spin_unlock_irqrestore(&xhci->lock, + flags); + xhci_stop_device(xhci, slot_id, 1); + spin_lock_irqsave(&xhci->lock, flags); + } + } + + xhci_set_link_state(xhci, port_array, wIndex, + link_state); + + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(20); /* wait device to enter */ + spin_lock_irqsave(&xhci->lock, flags); + + temp = xhci_readl(xhci, port_array[wIndex]); + if (link_state == USB_SS_PORT_LS_U3) + bus_state->suspended_ports |= 1 << wIndex; + break; + case USB_PORT_FEAT_POWER: + /* + * Turn on ports, even if there isn't per-port switching. + * HC will report connect events even before this is set. + * However, khubd will ignore the roothub events until + * the roothub is registered. + */ + xhci_writel(xhci, temp | PORT_POWER, + port_array[wIndex]); + + temp = xhci_readl(xhci, port_array[wIndex]); + xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n", wIndex, temp); + + spin_unlock_irqrestore(&xhci->lock, flags); + temp = usb_acpi_power_manageable(hcd->self.root_hub, + wIndex); + if (temp) + usb_acpi_set_power_state(hcd->self.root_hub, + wIndex, true); + spin_lock_irqsave(&xhci->lock, flags); + break; + case USB_PORT_FEAT_RESET: + temp = (temp | PORT_RESET); + xhci_writel(xhci, temp, port_array[wIndex]); + + temp = xhci_readl(xhci, port_array[wIndex]); + xhci_dbg(xhci, "set port reset, actual port %d status = 0x%x\n", wIndex, temp); + break; + case USB_PORT_FEAT_REMOTE_WAKE_MASK: + xhci_set_remote_wake_mask(xhci, port_array, + wIndex, wake_mask); + temp = xhci_readl(xhci, port_array[wIndex]); + xhci_dbg(xhci, "set port remote wake mask, " + "actual port %d status = 0x%x\n", + wIndex, temp); + break; + case USB_PORT_FEAT_BH_PORT_RESET: + temp |= PORT_WR; + xhci_writel(xhci, temp, port_array[wIndex]); + + temp = xhci_readl(xhci, port_array[wIndex]); + break; + case USB_PORT_FEAT_U1_TIMEOUT: + if (hcd->speed != HCD_USB3) + goto error; + temp = xhci_readl(xhci, port_array[wIndex] + 1); + temp &= ~PORT_U1_TIMEOUT_MASK; + temp |= PORT_U1_TIMEOUT(timeout); + xhci_writel(xhci, temp, port_array[wIndex] + 1); + break; + case USB_PORT_FEAT_U2_TIMEOUT: + if (hcd->speed != HCD_USB3) + goto error; + temp = xhci_readl(xhci, port_array[wIndex] + 1); + temp &= ~PORT_U2_TIMEOUT_MASK; + temp |= PORT_U2_TIMEOUT(timeout); + xhci_writel(xhci, temp, port_array[wIndex] + 1); + break; + default: + goto error; + } + /* unblock any posted writes */ + temp = xhci_readl(xhci, port_array[wIndex]); + break; + case ClearPortFeature: + if (!wIndex || wIndex > max_ports) + goto error; + wIndex--; + temp = xhci_readl(xhci, port_array[wIndex]); + if (temp == 0xffffffff) { + retval = -ENODEV; + break; + } + /* FIXME: What new port features do we need to support? */ + temp = xhci_port_state_to_neutral(temp); + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + temp = xhci_readl(xhci, port_array[wIndex]); + xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n"); + xhci_dbg(xhci, "PORTSC %04x\n", temp); + if (temp & PORT_RESET) + goto error; + if ((temp & PORT_PLS_MASK) == XDEV_U3) { + if ((temp & PORT_PE) == 0) + goto error; + + xhci_set_link_state(xhci, port_array, wIndex, + XDEV_RESUME); + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(20); + spin_lock_irqsave(&xhci->lock, flags); + xhci_set_link_state(xhci, port_array, wIndex, + XDEV_U0); + } + bus_state->port_c_suspend |= 1 << wIndex; + + slot_id = xhci_find_slot_id_by_port(hcd, xhci, + wIndex + 1); + if (!slot_id) { + xhci_dbg(xhci, "slot_id is zero\n"); + goto error; + } + xhci_ring_device(xhci, slot_id); + break; + case USB_PORT_FEAT_C_SUSPEND: + bus_state->port_c_suspend &= ~(1 << wIndex); + case USB_PORT_FEAT_C_RESET: + case USB_PORT_FEAT_C_BH_PORT_RESET: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_PORT_LINK_STATE: + xhci_clear_port_change_bit(xhci, wValue, wIndex, + port_array[wIndex], temp); + break; + case USB_PORT_FEAT_ENABLE: + xhci_disable_port(hcd, xhci, wIndex, + port_array[wIndex], temp); + break; + case USB_PORT_FEAT_POWER: + xhci_writel(xhci, temp & ~PORT_POWER, + port_array[wIndex]); + + spin_unlock_irqrestore(&xhci->lock, flags); + temp = usb_acpi_power_manageable(hcd->self.root_hub, + wIndex); + if (temp) + usb_acpi_set_power_state(hcd->self.root_hub, + wIndex, false); + spin_lock_irqsave(&xhci->lock, flags); + break; + default: + goto error; + } + break; + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&xhci->lock, flags); + return retval; +} + +/* + * Returns 0 if the status hasn't changed, or the number of bytes in buf. + * Ports are 0-indexed from the HCD point of view, + * and 1-indexed from the USB core pointer of view. + * + * Note that the status change bits will be cleared as soon as a port status + * change event is generated, so we use the saved status from that event. + */ +int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + unsigned long flags; + u32 temp, status; + u32 mask; + int i, retval; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int max_ports; + __le32 __iomem **port_array; + struct xhci_bus_state *bus_state; + bool reset_change = false; + + max_ports = xhci_get_ports(hcd, &port_array); + bus_state = &xhci->bus_state[hcd_index(hcd)]; + + /* Initial status is no changes */ + retval = (max_ports + 8) / 8; + memset(buf, 0, retval); + + /* + * Inform the usbcore about resume-in-progress by returning + * a non-zero value even if there are no status changes. + */ + status = bus_state->resuming_ports; + + mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC; + + spin_lock_irqsave(&xhci->lock, flags); + /* For each port, did anything change? If so, set that bit in buf. */ + for (i = 0; i < max_ports; i++) { + temp = xhci_readl(xhci, port_array[i]); + if (temp == 0xffffffff) { + retval = -ENODEV; + break; + } + if ((temp & mask) != 0 || + (bus_state->port_c_suspend & 1 << i) || + (bus_state->resume_done[i] && time_after_eq( + jiffies, bus_state->resume_done[i]))) { + buf[(i + 1) / 8] |= 1 << (i + 1) % 8; + status = 1; + } + if ((temp & PORT_RC)) + reset_change = true; + } + if (!status && !reset_change) { + xhci_dbg(xhci, "%s: stopping port polling.\n", __func__); + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + } + spin_unlock_irqrestore(&xhci->lock, flags); + return status ? retval : 0; +} + +#ifdef CONFIG_PM + +int xhci_bus_suspend(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int max_ports, port_index; + __le32 __iomem **port_array; + struct xhci_bus_state *bus_state; + unsigned long flags; + + max_ports = xhci_get_ports(hcd, &port_array); + bus_state = &xhci->bus_state[hcd_index(hcd)]; + + spin_lock_irqsave(&xhci->lock, flags); + + if (hcd->self.root_hub->do_remote_wakeup) { + if (bus_state->resuming_ports) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "suspend failed because " + "a port is resuming\n"); + return -EBUSY; + } + } + + port_index = max_ports; + bus_state->bus_suspended = 0; + while (port_index--) { + /* suspend the port if the port is not suspended */ + u32 t1, t2; + int slot_id; + + t1 = xhci_readl(xhci, port_array[port_index]); + t2 = xhci_port_state_to_neutral(t1); + + if ((t1 & PORT_PE) && !(t1 & PORT_PLS_MASK)) { + xhci_dbg(xhci, "port %d not suspended\n", port_index); + slot_id = xhci_find_slot_id_by_port(hcd, xhci, + port_index + 1); + if (slot_id) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_stop_device(xhci, slot_id, 1); + spin_lock_irqsave(&xhci->lock, flags); + } + t2 &= ~PORT_PLS_MASK; + t2 |= PORT_LINK_STROBE | XDEV_U3; + set_bit(port_index, &bus_state->bus_suspended); + } + /* USB core sets remote wake mask for USB 3.0 hubs, + * including the USB 3.0 roothub, but only if CONFIG_PM_RUNTIME + * is enabled, so also enable remote wake here. + */ + if (hcd->self.root_hub->do_remote_wakeup) { + if (t1 & PORT_CONNECT) { + t2 |= PORT_WKOC_E | PORT_WKDISC_E; + t2 &= ~PORT_WKCONN_E; + } else { + t2 |= PORT_WKOC_E | PORT_WKCONN_E; + t2 &= ~PORT_WKDISC_E; + } + } else + t2 &= ~PORT_WAKE_BITS; + + t1 = xhci_port_state_to_neutral(t1); + if (t1 != t2) + xhci_writel(xhci, t2, port_array[port_index]); + + if (hcd->speed != HCD_USB3) { + /* enable remote wake up for USB 2.0 */ + __le32 __iomem *addr; + u32 tmp; + + /* Add one to the port status register address to get + * the port power control register address. + */ + addr = port_array[port_index] + 1; + tmp = xhci_readl(xhci, addr); + tmp |= PORT_RWE; + xhci_writel(xhci, tmp, addr); + } + } + hcd->state = HC_STATE_SUSPENDED; + bus_state->next_statechange = jiffies + msecs_to_jiffies(10); + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; +} + +int xhci_bus_resume(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int max_ports, port_index; + __le32 __iomem **port_array; + struct xhci_bus_state *bus_state; + u32 temp; + unsigned long flags; + + max_ports = xhci_get_ports(hcd, &port_array); + bus_state = &xhci->bus_state[hcd_index(hcd)]; + + if (time_before(jiffies, bus_state->next_statechange)) + msleep(5); + + spin_lock_irqsave(&xhci->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) { + spin_unlock_irqrestore(&xhci->lock, flags); + return -ESHUTDOWN; + } + + /* delay the irqs */ + temp = xhci_readl(xhci, &xhci->op_regs->command); + temp &= ~CMD_EIE; + xhci_writel(xhci, temp, &xhci->op_regs->command); + + port_index = max_ports; + while (port_index--) { + /* Check whether need resume ports. If needed + resume port and disable remote wakeup */ + u32 temp; + int slot_id; + + temp = xhci_readl(xhci, port_array[port_index]); + if (DEV_SUPERSPEED(temp)) + temp &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); + else + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + if (test_bit(port_index, &bus_state->bus_suspended) && + (temp & PORT_PLS_MASK)) { + if (DEV_SUPERSPEED(temp)) { + xhci_set_link_state(xhci, port_array, + port_index, XDEV_U0); + } else { + xhci_set_link_state(xhci, port_array, + port_index, XDEV_RESUME); + + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(20); + spin_lock_irqsave(&xhci->lock, flags); + + xhci_set_link_state(xhci, port_array, + port_index, XDEV_U0); + } + /* wait for the port to enter U0 and report port link + * state change. + */ + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(20); + spin_lock_irqsave(&xhci->lock, flags); + + /* Clear PLC */ + xhci_test_and_clear_bit(xhci, port_array, port_index, + PORT_PLC); + + slot_id = xhci_find_slot_id_by_port(hcd, + xhci, port_index + 1); + if (slot_id) + xhci_ring_device(xhci, slot_id); + } else + xhci_writel(xhci, temp, port_array[port_index]); + + if (hcd->speed != HCD_USB3) { + /* disable remote wake up for USB 2.0 */ + __le32 __iomem *addr; + u32 tmp; + + /* Add one to the port status register address to get + * the port power control register address. + */ + addr = port_array[port_index] + 1; + tmp = xhci_readl(xhci, addr); + tmp &= ~PORT_RWE; + xhci_writel(xhci, tmp, addr); + } + } + + (void) xhci_readl(xhci, &xhci->op_regs->command); + + bus_state->next_statechange = jiffies + msecs_to_jiffies(5); + /* re-enable irqs */ + temp = xhci_readl(xhci, &xhci->op_regs->command); + temp |= CMD_EIE; + xhci_writel(xhci, temp, &xhci->op_regs->command); + temp = xhci_readl(xhci, &xhci->op_regs->command); + + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; +} + +#endif /* CONFIG_PM */ diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c new file mode 100644 index 00000000..677f0324 --- /dev/null +++ b/drivers/usb/host/xhci-mem.c @@ -0,0 +1,2475 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "xhci.h" + +/* + * Allocates a generic ring segment from the ring pool, sets the dma address, + * initializes the segment to zero, and sets the private next pointer to NULL. + * + * Section 4.11.1.1: + * "All components of all Command and Transfer TRBs shall be initialized to '0'" + */ +static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, + unsigned int cycle_state, gfp_t flags) +{ + struct xhci_segment *seg; + dma_addr_t dma; + int i; + + seg = kzalloc(sizeof *seg, flags); + if (!seg) + return NULL; + + seg->trbs = dma_pool_alloc(xhci->segment_pool, flags, &dma); + if (!seg->trbs) { + kfree(seg); + return NULL; + } + + memset(seg->trbs, 0, TRB_SEGMENT_SIZE); + /* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */ + if (cycle_state == 0) { + for (i = 0; i < TRBS_PER_SEGMENT; i++) + seg->trbs[i].link.control |= TRB_CYCLE; + } + seg->dma = dma; + seg->next = NULL; + + return seg; +} + +static void xhci_segment_free(struct xhci_hcd *xhci, struct xhci_segment *seg) +{ + if (seg->trbs) { + dma_pool_free(xhci->segment_pool, seg->trbs, seg->dma); + seg->trbs = NULL; + } + kfree(seg); +} + +static void xhci_free_segments_for_ring(struct xhci_hcd *xhci, + struct xhci_segment *first) +{ + struct xhci_segment *seg; + + seg = first->next; + while (seg != first) { + struct xhci_segment *next = seg->next; + xhci_segment_free(xhci, seg); + seg = next; + } + xhci_segment_free(xhci, first); +} + +/* + * Make the prev segment point to the next segment. + * + * Change the last TRB in the prev segment to be a Link TRB which points to the + * DMA address of the next segment. The caller needs to set any Link TRB + * related flags, such as End TRB, Toggle Cycle, and no snoop. + */ +static void xhci_link_segments(struct xhci_hcd *xhci, struct xhci_segment *prev, + struct xhci_segment *next, enum xhci_ring_type type) +{ + u32 val; + + if (!prev || !next) + return; + prev->next = next; + if (type != TYPE_EVENT) { + prev->trbs[TRBS_PER_SEGMENT-1].link.segment_ptr = + cpu_to_le64(next->dma); + + /* Set the last TRB in the segment to have a TRB type ID of Link TRB */ + val = le32_to_cpu(prev->trbs[TRBS_PER_SEGMENT-1].link.control); + val &= ~TRB_TYPE_BITMASK; + val |= TRB_TYPE(TRB_LINK); + /* Always set the chain bit with 0.95 hardware */ + /* Set chain bit for isoc rings on AMD 0.96 host */ + if (xhci_link_trb_quirk(xhci) || + (type == TYPE_ISOC && + (xhci->quirks & XHCI_AMD_0x96_HOST))) + val |= TRB_CHAIN; + prev->trbs[TRBS_PER_SEGMENT-1].link.control = cpu_to_le32(val); + } +} + +/* + * Link the ring to the new segments. + * Set Toggle Cycle for the new ring if needed. + */ +static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring, + struct xhci_segment *first, struct xhci_segment *last, + unsigned int num_segs) +{ + struct xhci_segment *next; + + if (!ring || !first || !last) + return; + + next = ring->enq_seg->next; + xhci_link_segments(xhci, ring->enq_seg, first, ring->type); + xhci_link_segments(xhci, last, next, ring->type); + ring->num_segs += num_segs; + ring->num_trbs_free += (TRBS_PER_SEGMENT - 1) * num_segs; + + if (ring->type != TYPE_EVENT && ring->enq_seg == ring->last_seg) { + ring->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control + &= ~cpu_to_le32(LINK_TOGGLE); + last->trbs[TRBS_PER_SEGMENT-1].link.control + |= cpu_to_le32(LINK_TOGGLE); + ring->last_seg = last; + } +} + +/* XXX: Do we need the hcd structure in all these functions? */ +void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + if (!ring) + return; + + if (ring->first_seg) + xhci_free_segments_for_ring(xhci, ring->first_seg); + + kfree(ring); +} + +static void xhci_initialize_ring_info(struct xhci_ring *ring, + unsigned int cycle_state) +{ + /* The ring is empty, so the enqueue pointer == dequeue pointer */ + ring->enqueue = ring->first_seg->trbs; + ring->enq_seg = ring->first_seg; + ring->dequeue = ring->enqueue; + ring->deq_seg = ring->first_seg; + /* The ring is initialized to 0. The producer must write 1 to the cycle + * bit to handover ownership of the TRB, so PCS = 1. The consumer must + * compare CCS to the cycle bit to check ownership, so CCS = 1. + * + * New rings are initialized with cycle state equal to 1; if we are + * handling ring expansion, set the cycle state equal to the old ring. + */ + ring->cycle_state = cycle_state; + /* Not necessary for new rings, but needed for re-initialized rings */ + ring->enq_updates = 0; + ring->deq_updates = 0; + + /* + * Each segment has a link TRB, and leave an extra TRB for SW + * accounting purpose + */ + ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1; +} + +/* Allocate segments and link them for a ring */ +static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci, + struct xhci_segment **first, struct xhci_segment **last, + unsigned int num_segs, unsigned int cycle_state, + enum xhci_ring_type type, gfp_t flags) +{ + struct xhci_segment *prev; + + prev = xhci_segment_alloc(xhci, cycle_state, flags); + if (!prev) + return -ENOMEM; + num_segs--; + + *first = prev; + while (num_segs > 0) { + struct xhci_segment *next; + + next = xhci_segment_alloc(xhci, cycle_state, flags); + if (!next) { + prev = *first; + while (prev) { + next = prev->next; + xhci_segment_free(xhci, prev); + prev = next; + } + return -ENOMEM; + } + xhci_link_segments(xhci, prev, next, type); + + prev = next; + num_segs--; + } + xhci_link_segments(xhci, prev, *first, type); + *last = prev; + + return 0; +} + +/** + * Create a new ring with zero or more segments. + * + * Link each segment together into a ring. + * Set the end flag and the cycle toggle bit on the last segment. + * See section 4.9.1 and figures 15 and 16. + */ +static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, + unsigned int num_segs, unsigned int cycle_state, + enum xhci_ring_type type, gfp_t flags) +{ + struct xhci_ring *ring; + int ret; + + ring = kzalloc(sizeof *(ring), flags); + if (!ring) + return NULL; + + ring->num_segs = num_segs; + INIT_LIST_HEAD(&ring->td_list); + ring->type = type; + if (num_segs == 0) + return ring; + + ret = xhci_alloc_segments_for_ring(xhci, &ring->first_seg, + &ring->last_seg, num_segs, cycle_state, type, flags); + if (ret) + goto fail; + + /* Only event ring does not use link TRB */ + if (type != TYPE_EVENT) { + /* See section 4.9.2.1 and 6.4.4.1 */ + ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= + cpu_to_le32(LINK_TOGGLE); + } + xhci_initialize_ring_info(ring, cycle_state); + return ring; + +fail: + kfree(ring); + return NULL; +} + +void xhci_free_or_cache_endpoint_ring(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + unsigned int ep_index) +{ + int rings_cached; + + rings_cached = virt_dev->num_rings_cached; + if (rings_cached < XHCI_MAX_RINGS_CACHED) { + virt_dev->ring_cache[rings_cached] = + virt_dev->eps[ep_index].ring; + virt_dev->num_rings_cached++; + xhci_dbg(xhci, "Cached old ring, " + "%d ring%s cached\n", + virt_dev->num_rings_cached, + (virt_dev->num_rings_cached > 1) ? "s" : ""); + } else { + xhci_ring_free(xhci, virt_dev->eps[ep_index].ring); + xhci_dbg(xhci, "Ring cache full (%d rings), " + "freeing ring\n", + virt_dev->num_rings_cached); + } + virt_dev->eps[ep_index].ring = NULL; +} + +/* Zero an endpoint ring (except for link TRBs) and move the enqueue and dequeue + * pointers to the beginning of the ring. + */ +static void xhci_reinit_cached_ring(struct xhci_hcd *xhci, + struct xhci_ring *ring, unsigned int cycle_state, + enum xhci_ring_type type) +{ + struct xhci_segment *seg = ring->first_seg; + int i; + + do { + memset(seg->trbs, 0, + sizeof(union xhci_trb)*TRBS_PER_SEGMENT); + if (cycle_state == 0) { + for (i = 0; i < TRBS_PER_SEGMENT; i++) + seg->trbs[i].link.control |= TRB_CYCLE; + } + /* All endpoint rings have link TRBs */ + xhci_link_segments(xhci, seg, seg->next, type); + seg = seg->next; + } while (seg != ring->first_seg); + ring->type = type; + xhci_initialize_ring_info(ring, cycle_state); + /* td list should be empty since all URBs have been cancelled, + * but just in case... + */ + INIT_LIST_HEAD(&ring->td_list); +} + +/* + * Expand an existing ring. + * Look for a cached ring or allocate a new ring which has same segment numbers + * and link the two rings. + */ +int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, + unsigned int num_trbs, gfp_t flags) +{ + struct xhci_segment *first; + struct xhci_segment *last; + unsigned int num_segs; + unsigned int num_segs_needed; + int ret; + + num_segs_needed = (num_trbs + (TRBS_PER_SEGMENT - 1) - 1) / + (TRBS_PER_SEGMENT - 1); + + /* Allocate number of segments we needed, or double the ring size */ + num_segs = ring->num_segs > num_segs_needed ? + ring->num_segs : num_segs_needed; + + ret = xhci_alloc_segments_for_ring(xhci, &first, &last, + num_segs, ring->cycle_state, ring->type, flags); + if (ret) + return -ENOMEM; + + xhci_link_rings(xhci, ring, first, last, num_segs); + xhci_dbg(xhci, "ring expansion succeed, now has %d segments\n", + ring->num_segs); + + return 0; +} + +#define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32) + +static struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, + int type, gfp_t flags) +{ + struct xhci_container_ctx *ctx = kzalloc(sizeof(*ctx), flags); + if (!ctx) + return NULL; + + BUG_ON((type != XHCI_CTX_TYPE_DEVICE) && (type != XHCI_CTX_TYPE_INPUT)); + ctx->type = type; + ctx->size = HCC_64BYTE_CONTEXT(xhci->hcc_params) ? 2048 : 1024; + if (type == XHCI_CTX_TYPE_INPUT) + ctx->size += CTX_SIZE(xhci->hcc_params); + + ctx->bytes = dma_pool_alloc(xhci->device_pool, flags, &ctx->dma); + if (!ctx->bytes) { + kfree(ctx); + return NULL; + } + memset(ctx->bytes, 0, ctx->size); + return ctx; +} + +static void xhci_free_container_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx) +{ + if (!ctx) + return; + dma_pool_free(xhci->device_pool, ctx->bytes, ctx->dma); + kfree(ctx); +} + +struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx) +{ + BUG_ON(ctx->type != XHCI_CTX_TYPE_INPUT); + return (struct xhci_input_control_ctx *)ctx->bytes; +} + +struct xhci_slot_ctx *xhci_get_slot_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx) +{ + if (ctx->type == XHCI_CTX_TYPE_DEVICE) + return (struct xhci_slot_ctx *)ctx->bytes; + + return (struct xhci_slot_ctx *) + (ctx->bytes + CTX_SIZE(xhci->hcc_params)); +} + +struct xhci_ep_ctx *xhci_get_ep_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx, + unsigned int ep_index) +{ + /* increment ep index by offset of start of ep ctx array */ + ep_index++; + if (ctx->type == XHCI_CTX_TYPE_INPUT) + ep_index++; + + return (struct xhci_ep_ctx *) + (ctx->bytes + (ep_index * CTX_SIZE(xhci->hcc_params))); +} + + +/***************** Streams structures manipulation *************************/ + +static void xhci_free_stream_ctx(struct xhci_hcd *xhci, + unsigned int num_stream_ctxs, + struct xhci_stream_ctx *stream_ctx, dma_addr_t dma) +{ + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + + if (num_stream_ctxs > MEDIUM_STREAM_ARRAY_SIZE) + dma_free_coherent(&pdev->dev, + sizeof(struct xhci_stream_ctx)*num_stream_ctxs, + stream_ctx, dma); + else if (num_stream_ctxs <= SMALL_STREAM_ARRAY_SIZE) + return dma_pool_free(xhci->small_streams_pool, + stream_ctx, dma); + else + return dma_pool_free(xhci->medium_streams_pool, + stream_ctx, dma); +} + +/* + * The stream context array for each endpoint with bulk streams enabled can + * vary in size, based on: + * - how many streams the endpoint supports, + * - the maximum primary stream array size the host controller supports, + * - and how many streams the device driver asks for. + * + * The stream context array must be a power of 2, and can be as small as + * 64 bytes or as large as 1MB. + */ +static struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci, + unsigned int num_stream_ctxs, dma_addr_t *dma, + gfp_t mem_flags) +{ + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + + if (num_stream_ctxs > MEDIUM_STREAM_ARRAY_SIZE) + return dma_alloc_coherent(&pdev->dev, + sizeof(struct xhci_stream_ctx)*num_stream_ctxs, + dma, mem_flags); + else if (num_stream_ctxs <= SMALL_STREAM_ARRAY_SIZE) + return dma_pool_alloc(xhci->small_streams_pool, + mem_flags, dma); + else + return dma_pool_alloc(xhci->medium_streams_pool, + mem_flags, dma); +} + +struct xhci_ring *xhci_dma_to_transfer_ring( + struct xhci_virt_ep *ep, + u64 address) +{ + if (ep->ep_state & EP_HAS_STREAMS) + return radix_tree_lookup(&ep->stream_info->trb_address_map, + address >> TRB_SEGMENT_SHIFT); + return ep->ring; +} + +/* Only use this when you know stream_info is valid */ +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING +static struct xhci_ring *dma_to_stream_ring( + struct xhci_stream_info *stream_info, + u64 address) +{ + return radix_tree_lookup(&stream_info->trb_address_map, + address >> TRB_SEGMENT_SHIFT); +} +#endif /* CONFIG_USB_XHCI_HCD_DEBUGGING */ + +struct xhci_ring *xhci_stream_id_to_ring( + struct xhci_virt_device *dev, + unsigned int ep_index, + unsigned int stream_id) +{ + struct xhci_virt_ep *ep = &dev->eps[ep_index]; + + if (stream_id == 0) + return ep->ring; + if (!ep->stream_info) + return NULL; + + if (stream_id > ep->stream_info->num_streams) + return NULL; + return ep->stream_info->stream_rings[stream_id]; +} + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING +static int xhci_test_radix_tree(struct xhci_hcd *xhci, + unsigned int num_streams, + struct xhci_stream_info *stream_info) +{ + u32 cur_stream; + struct xhci_ring *cur_ring; + u64 addr; + + for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { + struct xhci_ring *mapped_ring; + int trb_size = sizeof(union xhci_trb); + + cur_ring = stream_info->stream_rings[cur_stream]; + for (addr = cur_ring->first_seg->dma; + addr < cur_ring->first_seg->dma + TRB_SEGMENT_SIZE; + addr += trb_size) { + mapped_ring = dma_to_stream_ring(stream_info, addr); + if (cur_ring != mapped_ring) { + xhci_warn(xhci, "WARN: DMA address 0x%08llx " + "didn't map to stream ID %u; " + "mapped to ring %p\n", + (unsigned long long) addr, + cur_stream, + mapped_ring); + return -EINVAL; + } + } + /* One TRB after the end of the ring segment shouldn't return a + * pointer to the current ring (although it may be a part of a + * different ring). + */ + mapped_ring = dma_to_stream_ring(stream_info, addr); + if (mapped_ring != cur_ring) { + /* One TRB before should also fail */ + addr = cur_ring->first_seg->dma - trb_size; + mapped_ring = dma_to_stream_ring(stream_info, addr); + } + if (mapped_ring == cur_ring) { + xhci_warn(xhci, "WARN: Bad DMA address 0x%08llx " + "mapped to valid stream ID %u; " + "mapped ring = %p\n", + (unsigned long long) addr, + cur_stream, + mapped_ring); + return -EINVAL; + } + } + return 0; +} +#endif /* CONFIG_USB_XHCI_HCD_DEBUGGING */ + +/* + * Change an endpoint's internal structure so it supports stream IDs. The + * number of requested streams includes stream 0, which cannot be used by device + * drivers. + * + * The number of stream contexts in the stream context array may be bigger than + * the number of streams the driver wants to use. This is because the number of + * stream context array entries must be a power of two. + * + * We need a radix tree for mapping physical addresses of TRBs to which stream + * ID they belong to. We need to do this because the host controller won't tell + * us which stream ring the TRB came from. We could store the stream ID in an + * event data TRB, but that doesn't help us for the cancellation case, since the + * endpoint may stop before it reaches that event data TRB. + * + * The radix tree maps the upper portion of the TRB DMA address to a ring + * segment that has the same upper portion of DMA addresses. For example, say I + * have segments of size 1KB, that are always 64-byte aligned. A segment may + * start at 0x10c91000 and end at 0x10c913f0. If I use the upper 10 bits, the + * key to the stream ID is 0x43244. I can use the DMA address of the TRB to + * pass the radix tree a key to get the right stream ID: + * + * 0x10c90fff >> 10 = 0x43243 + * 0x10c912c0 >> 10 = 0x43244 + * 0x10c91400 >> 10 = 0x43245 + * + * Obviously, only those TRBs with DMA addresses that are within the segment + * will make the radix tree return the stream ID for that ring. + * + * Caveats for the radix tree: + * + * The radix tree uses an unsigned long as a key pair. On 32-bit systems, an + * unsigned long will be 32-bits; on a 64-bit system an unsigned long will be + * 64-bits. Since we only request 32-bit DMA addresses, we can use that as the + * key on 32-bit or 64-bit systems (it would also be fine if we asked for 64-bit + * PCI DMA addresses on a 64-bit system). There might be a problem on 32-bit + * extended systems (where the DMA address can be bigger than 32-bits), + * if we allow the PCI dma mask to be bigger than 32-bits. So don't do that. + */ +struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, + unsigned int num_stream_ctxs, + unsigned int num_streams, gfp_t mem_flags) +{ + struct xhci_stream_info *stream_info; + u32 cur_stream; + struct xhci_ring *cur_ring; + unsigned long key; + u64 addr; + int ret; + + xhci_dbg(xhci, "Allocating %u streams and %u " + "stream context array entries.\n", + num_streams, num_stream_ctxs); + if (xhci->cmd_ring_reserved_trbs == MAX_RSVD_CMD_TRBS) { + xhci_dbg(xhci, "Command ring has no reserved TRBs available\n"); + return NULL; + } + xhci->cmd_ring_reserved_trbs++; + + stream_info = kzalloc(sizeof(struct xhci_stream_info), mem_flags); + if (!stream_info) + goto cleanup_trbs; + + stream_info->num_streams = num_streams; + stream_info->num_stream_ctxs = num_stream_ctxs; + + /* Initialize the array of virtual pointers to stream rings. */ + stream_info->stream_rings = kzalloc( + sizeof(struct xhci_ring *)*num_streams, + mem_flags); + if (!stream_info->stream_rings) + goto cleanup_info; + + /* Initialize the array of DMA addresses for stream rings for the HW. */ + stream_info->stream_ctx_array = xhci_alloc_stream_ctx(xhci, + num_stream_ctxs, &stream_info->ctx_array_dma, + mem_flags); + if (!stream_info->stream_ctx_array) + goto cleanup_ctx; + memset(stream_info->stream_ctx_array, 0, + sizeof(struct xhci_stream_ctx)*num_stream_ctxs); + + /* Allocate everything needed to free the stream rings later */ + stream_info->free_streams_command = + xhci_alloc_command(xhci, true, true, mem_flags); + if (!stream_info->free_streams_command) + goto cleanup_ctx; + + INIT_RADIX_TREE(&stream_info->trb_address_map, GFP_ATOMIC); + + /* Allocate rings for all the streams that the driver will use, + * and add their segment DMA addresses to the radix tree. + * Stream 0 is reserved. + */ + for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { + stream_info->stream_rings[cur_stream] = + xhci_ring_alloc(xhci, 2, 1, TYPE_STREAM, mem_flags); + cur_ring = stream_info->stream_rings[cur_stream]; + if (!cur_ring) + goto cleanup_rings; + cur_ring->stream_id = cur_stream; + /* Set deq ptr, cycle bit, and stream context type */ + addr = cur_ring->first_seg->dma | + SCT_FOR_CTX(SCT_PRI_TR) | + cur_ring->cycle_state; + stream_info->stream_ctx_array[cur_stream].stream_ring = + cpu_to_le64(addr); + xhci_dbg(xhci, "Setting stream %d ring ptr to 0x%08llx\n", + cur_stream, (unsigned long long) addr); + + key = (unsigned long) + (cur_ring->first_seg->dma >> TRB_SEGMENT_SHIFT); + ret = radix_tree_insert(&stream_info->trb_address_map, + key, cur_ring); + if (ret) { + xhci_ring_free(xhci, cur_ring); + stream_info->stream_rings[cur_stream] = NULL; + goto cleanup_rings; + } + } + /* Leave the other unused stream ring pointers in the stream context + * array initialized to zero. This will cause the xHC to give us an + * error if the device asks for a stream ID we don't have setup (if it + * was any other way, the host controller would assume the ring is + * "empty" and wait forever for data to be queued to that stream ID). + */ +#if XHCI_DEBUG + /* Do a little test on the radix tree to make sure it returns the + * correct values. + */ + if (xhci_test_radix_tree(xhci, num_streams, stream_info)) + goto cleanup_rings; +#endif + + return stream_info; + +cleanup_rings: + for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { + cur_ring = stream_info->stream_rings[cur_stream]; + if (cur_ring) { + addr = cur_ring->first_seg->dma; + radix_tree_delete(&stream_info->trb_address_map, + addr >> TRB_SEGMENT_SHIFT); + xhci_ring_free(xhci, cur_ring); + stream_info->stream_rings[cur_stream] = NULL; + } + } + xhci_free_command(xhci, stream_info->free_streams_command); +cleanup_ctx: + kfree(stream_info->stream_rings); +cleanup_info: + kfree(stream_info); +cleanup_trbs: + xhci->cmd_ring_reserved_trbs--; + return NULL; +} +/* + * Sets the MaxPStreams field and the Linear Stream Array field. + * Sets the dequeue pointer to the stream context array. + */ +void xhci_setup_streams_ep_input_ctx(struct xhci_hcd *xhci, + struct xhci_ep_ctx *ep_ctx, + struct xhci_stream_info *stream_info) +{ + u32 max_primary_streams; + /* MaxPStreams is the number of stream context array entries, not the + * number we're actually using. Must be in 2^(MaxPstreams + 1) format. + * fls(0) = 0, fls(0x1) = 1, fls(0x10) = 2, fls(0x100) = 3, etc. + */ + max_primary_streams = fls(stream_info->num_stream_ctxs) - 2; + xhci_dbg(xhci, "Setting number of stream ctx array entries to %u\n", + 1 << (max_primary_streams + 1)); + ep_ctx->ep_info &= cpu_to_le32(~EP_MAXPSTREAMS_MASK); + ep_ctx->ep_info |= cpu_to_le32(EP_MAXPSTREAMS(max_primary_streams) + | EP_HAS_LSA); + ep_ctx->deq = cpu_to_le64(stream_info->ctx_array_dma); +} + +/* + * Sets the MaxPStreams field and the Linear Stream Array field to 0. + * Reinstalls the "normal" endpoint ring (at its previous dequeue mark, + * not at the beginning of the ring). + */ +void xhci_setup_no_streams_ep_input_ctx(struct xhci_hcd *xhci, + struct xhci_ep_ctx *ep_ctx, + struct xhci_virt_ep *ep) +{ + dma_addr_t addr; + ep_ctx->ep_info &= cpu_to_le32(~(EP_MAXPSTREAMS_MASK | EP_HAS_LSA)); + addr = xhci_trb_virt_to_dma(ep->ring->deq_seg, ep->ring->dequeue); + ep_ctx->deq = cpu_to_le64(addr | ep->ring->cycle_state); +} + +/* Frees all stream contexts associated with the endpoint, + * + * Caller should fix the endpoint context streams fields. + */ +void xhci_free_stream_info(struct xhci_hcd *xhci, + struct xhci_stream_info *stream_info) +{ + int cur_stream; + struct xhci_ring *cur_ring; + dma_addr_t addr; + + if (!stream_info) + return; + + for (cur_stream = 1; cur_stream < stream_info->num_streams; + cur_stream++) { + cur_ring = stream_info->stream_rings[cur_stream]; + if (cur_ring) { + addr = cur_ring->first_seg->dma; + radix_tree_delete(&stream_info->trb_address_map, + addr >> TRB_SEGMENT_SHIFT); + xhci_ring_free(xhci, cur_ring); + stream_info->stream_rings[cur_stream] = NULL; + } + } + xhci_free_command(xhci, stream_info->free_streams_command); + xhci->cmd_ring_reserved_trbs--; + if (stream_info->stream_ctx_array) + xhci_free_stream_ctx(xhci, + stream_info->num_stream_ctxs, + stream_info->stream_ctx_array, + stream_info->ctx_array_dma); + + if (stream_info) + kfree(stream_info->stream_rings); + kfree(stream_info); +} + + +/***************** Device context manipulation *************************/ + +static void xhci_init_endpoint_timer(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep) +{ + init_timer(&ep->stop_cmd_timer); + ep->stop_cmd_timer.data = (unsigned long) ep; + ep->stop_cmd_timer.function = xhci_stop_endpoint_command_watchdog; + ep->xhci = xhci; +} + +static void xhci_free_tt_info(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int slot_id) +{ + struct list_head *tt_list_head; + struct xhci_tt_bw_info *tt_info, *next; + bool slot_found = false; + + /* If the device never made it past the Set Address stage, + * it may not have the real_port set correctly. + */ + if (virt_dev->real_port == 0 || + virt_dev->real_port > HCS_MAX_PORTS(xhci->hcs_params1)) { + xhci_dbg(xhci, "Bad real port.\n"); + return; + } + + tt_list_head = &(xhci->rh_bw[virt_dev->real_port - 1].tts); + list_for_each_entry_safe(tt_info, next, tt_list_head, tt_list) { + /* Multi-TT hubs will have more than one entry */ + if (tt_info->slot_id == slot_id) { + slot_found = true; + list_del(&tt_info->tt_list); + kfree(tt_info); + } else if (slot_found) { + break; + } + } +} + +int xhci_alloc_tt_info(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct usb_device *hdev, + struct usb_tt *tt, gfp_t mem_flags) +{ + struct xhci_tt_bw_info *tt_info; + unsigned int num_ports; + int i, j; + + if (!tt->multi) + num_ports = 1; + else + num_ports = hdev->maxchild; + + for (i = 0; i < num_ports; i++, tt_info++) { + struct xhci_interval_bw_table *bw_table; + + tt_info = kzalloc(sizeof(*tt_info), mem_flags); + if (!tt_info) + goto free_tts; + INIT_LIST_HEAD(&tt_info->tt_list); + list_add(&tt_info->tt_list, + &xhci->rh_bw[virt_dev->real_port - 1].tts); + tt_info->slot_id = virt_dev->udev->slot_id; + if (tt->multi) + tt_info->ttport = i+1; + bw_table = &tt_info->bw_table; + for (j = 0; j < XHCI_MAX_INTERVAL; j++) + INIT_LIST_HEAD(&bw_table->interval_bw[j].endpoints); + } + return 0; + +free_tts: + xhci_free_tt_info(xhci, virt_dev, virt_dev->udev->slot_id); + return -ENOMEM; +} + + +/* All the xhci_tds in the ring's TD list should be freed at this point. + * Should be called with xhci->lock held if there is any chance the TT lists + * will be manipulated by the configure endpoint, allocate device, or update + * hub functions while this function is removing the TT entries from the list. + */ +void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_virt_device *dev; + int i; + int old_active_eps = 0; + + /* Slot ID 0 is reserved */ + if (slot_id == 0 || !xhci->devs[slot_id]) + return; + + dev = xhci->devs[slot_id]; + xhci->dcbaa->dev_context_ptrs[slot_id] = 0; + if (!dev) + return; + + if (dev->tt_info) + old_active_eps = dev->tt_info->active_eps; + + for (i = 0; i < 31; ++i) { + if (dev->eps[i].ring) + xhci_ring_free(xhci, dev->eps[i].ring); + if (dev->eps[i].stream_info) + xhci_free_stream_info(xhci, + dev->eps[i].stream_info); + /* Endpoints on the TT/root port lists should have been removed + * when usb_disable_device() was called for the device. + * We can't drop them anyway, because the udev might have gone + * away by this point, and we can't tell what speed it was. + */ + if (!list_empty(&dev->eps[i].bw_endpoint_list)) + xhci_warn(xhci, "Slot %u endpoint %u " + "not removed from BW list!\n", + slot_id, i); + } + /* If this is a hub, free the TT(s) from the TT list */ + xhci_free_tt_info(xhci, dev, slot_id); + /* If necessary, update the number of active TTs on this root port */ + xhci_update_tt_active_eps(xhci, dev, old_active_eps); + + if (dev->ring_cache) { + for (i = 0; i < dev->num_rings_cached; i++) + xhci_ring_free(xhci, dev->ring_cache[i]); + kfree(dev->ring_cache); + } + + if (dev->in_ctx) + xhci_free_container_ctx(xhci, dev->in_ctx); + if (dev->out_ctx) + xhci_free_container_ctx(xhci, dev->out_ctx); + + kfree(xhci->devs[slot_id]); + xhci->devs[slot_id] = NULL; +} + +int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, + struct usb_device *udev, gfp_t flags) +{ + struct xhci_virt_device *dev; + int i; + + /* Slot ID 0 is reserved */ + if (slot_id == 0 || xhci->devs[slot_id]) { + xhci_warn(xhci, "Bad Slot ID %d\n", slot_id); + return 0; + } + + xhci->devs[slot_id] = kzalloc(sizeof(*xhci->devs[slot_id]), flags); + if (!xhci->devs[slot_id]) + return 0; + dev = xhci->devs[slot_id]; + + /* Allocate the (output) device context that will be used in the HC. */ + dev->out_ctx = xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_DEVICE, flags); + if (!dev->out_ctx) + goto fail; + + xhci_dbg(xhci, "Slot %d output ctx = 0x%llx (dma)\n", slot_id, + (unsigned long long)dev->out_ctx->dma); + + /* Allocate the (input) device context for address device command */ + dev->in_ctx = xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_INPUT, flags); + if (!dev->in_ctx) + goto fail; + + xhci_dbg(xhci, "Slot %d input ctx = 0x%llx (dma)\n", slot_id, + (unsigned long long)dev->in_ctx->dma); + + /* Initialize the cancellation list and watchdog timers for each ep */ + for (i = 0; i < 31; i++) { + xhci_init_endpoint_timer(xhci, &dev->eps[i]); + INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); + INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); + } + + /* Allocate endpoint 0 ring */ + dev->eps[0].ring = xhci_ring_alloc(xhci, 2, 1, TYPE_CTRL, flags); + if (!dev->eps[0].ring) + goto fail; + + /* Allocate pointers to the ring cache */ + dev->ring_cache = kzalloc( + sizeof(struct xhci_ring *)*XHCI_MAX_RINGS_CACHED, + flags); + if (!dev->ring_cache) + goto fail; + dev->num_rings_cached = 0; + + init_completion(&dev->cmd_completion); + INIT_LIST_HEAD(&dev->cmd_list); + dev->udev = udev; + + /* Point to output device context in dcbaa. */ + xhci->dcbaa->dev_context_ptrs[slot_id] = cpu_to_le64(dev->out_ctx->dma); + xhci_dbg(xhci, "Set slot id %d dcbaa entry %p to 0x%llx\n", + slot_id, + &xhci->dcbaa->dev_context_ptrs[slot_id], + le64_to_cpu(xhci->dcbaa->dev_context_ptrs[slot_id])); + + return 1; +fail: + xhci_free_virt_device(xhci, slot_id); + return 0; +} + +void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci, + struct usb_device *udev) +{ + struct xhci_virt_device *virt_dev; + struct xhci_ep_ctx *ep0_ctx; + struct xhci_ring *ep_ring; + + virt_dev = xhci->devs[udev->slot_id]; + ep0_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, 0); + ep_ring = virt_dev->eps[0].ring; + /* + * FIXME we don't keep track of the dequeue pointer very well after a + * Set TR dequeue pointer, so we're setting the dequeue pointer of the + * host to our enqueue pointer. This should only be called after a + * configured device has reset, so all control transfers should have + * been completed or cancelled before the reset. + */ + ep0_ctx->deq = cpu_to_le64(xhci_trb_virt_to_dma(ep_ring->enq_seg, + ep_ring->enqueue) + | ep_ring->cycle_state); +} + +/* + * The xHCI roothub may have ports of differing speeds in any order in the port + * status registers. xhci->port_array provides an array of the port speed for + * each offset into the port status registers. + * + * The xHCI hardware wants to know the roothub port number that the USB device + * is attached to (or the roothub port its ancestor hub is attached to). All we + * know is the index of that port under either the USB 2.0 or the USB 3.0 + * roothub, but that doesn't give us the real index into the HW port status + * registers. Call xhci_find_raw_port_number() to get real index. + */ +static u32 xhci_find_real_port_number(struct xhci_hcd *xhci, + struct usb_device *udev) +{ + struct usb_device *top_dev; + struct usb_hcd *hcd; + + if (udev->speed == USB_SPEED_SUPER) + hcd = xhci->shared_hcd; + else + hcd = xhci->main_hcd; + + for (top_dev = udev; top_dev->parent && top_dev->parent->parent; + top_dev = top_dev->parent) + /* Found device below root hub */; + + return xhci_find_raw_port_number(hcd, top_dev->portnum); +} + +/* Setup an xHCI virtual device for a Set Address command */ +int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev) +{ + struct xhci_virt_device *dev; + struct xhci_ep_ctx *ep0_ctx; + struct xhci_slot_ctx *slot_ctx; + u32 port_num; + struct usb_device *top_dev; + + dev = xhci->devs[udev->slot_id]; + /* Slot ID 0 is reserved */ + if (udev->slot_id == 0 || !dev) { + xhci_warn(xhci, "Slot ID %d is not assigned to this device\n", + udev->slot_id); + return -EINVAL; + } + ep0_ctx = xhci_get_ep_ctx(xhci, dev->in_ctx, 0); + slot_ctx = xhci_get_slot_ctx(xhci, dev->in_ctx); + + /* 3) Only the control endpoint is valid - one endpoint context */ + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1) | udev->route); + switch (udev->speed) { + case USB_SPEED_SUPER: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SS); + break; + case USB_SPEED_HIGH: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_HS); + break; + case USB_SPEED_FULL: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_FS); + break; + case USB_SPEED_LOW: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_LS); + break; + case USB_SPEED_WIRELESS: + xhci_dbg(xhci, "FIXME xHCI doesn't support wireless speeds\n"); + return -EINVAL; + break; + default: + /* Speed was set earlier, this shouldn't happen. */ + BUG(); + } + /* Find the root hub port this device is under */ + port_num = xhci_find_real_port_number(xhci, udev); + if (!port_num) + return -EINVAL; + slot_ctx->dev_info2 |= cpu_to_le32(ROOT_HUB_PORT(port_num)); + /* Set the port number in the virtual_device to the faked port number */ + for (top_dev = udev; top_dev->parent && top_dev->parent->parent; + top_dev = top_dev->parent) + /* Found device below root hub */; + dev->fake_port = top_dev->portnum; + dev->real_port = port_num; + xhci_dbg(xhci, "Set root hub portnum to %d\n", port_num); + xhci_dbg(xhci, "Set fake root hub portnum to %d\n", dev->fake_port); + + /* Find the right bandwidth table that this device will be a part of. + * If this is a full speed device attached directly to a root port (or a + * decendent of one), it counts as a primary bandwidth domain, not a + * secondary bandwidth domain under a TT. An xhci_tt_info structure + * will never be created for the HS root hub. + */ + if (!udev->tt || !udev->tt->hub->parent) { + dev->bw_table = &xhci->rh_bw[port_num - 1].bw_table; + } else { + struct xhci_root_port_bw_info *rh_bw; + struct xhci_tt_bw_info *tt_bw; + + rh_bw = &xhci->rh_bw[port_num - 1]; + /* Find the right TT. */ + list_for_each_entry(tt_bw, &rh_bw->tts, tt_list) { + if (tt_bw->slot_id != udev->tt->hub->slot_id) + continue; + + if (!dev->udev->tt->multi || + (udev->tt->multi && + tt_bw->ttport == dev->udev->ttport)) { + dev->bw_table = &tt_bw->bw_table; + dev->tt_info = tt_bw; + break; + } + } + if (!dev->tt_info) + xhci_warn(xhci, "WARN: Didn't find a matching TT\n"); + } + + /* Is this a LS/FS device under an external HS hub? */ + if (udev->tt && udev->tt->hub->parent) { + slot_ctx->tt_info = cpu_to_le32(udev->tt->hub->slot_id | + (udev->ttport << 8)); + if (udev->tt->multi) + slot_ctx->dev_info |= cpu_to_le32(DEV_MTT); + } + xhci_dbg(xhci, "udev->tt = %p\n", udev->tt); + xhci_dbg(xhci, "udev->ttport = 0x%x\n", udev->ttport); + + /* Step 4 - ring already allocated */ + /* Step 5 */ + ep0_ctx->ep_info2 = cpu_to_le32(EP_TYPE(CTRL_EP)); + /* + * XXX: Not sure about wireless USB devices. + */ + switch (udev->speed) { + case USB_SPEED_SUPER: + ep0_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(512)); + break; + case USB_SPEED_HIGH: + /* USB core guesses at a 64-byte max packet first for FS devices */ + case USB_SPEED_FULL: + ep0_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(64)); + break; + case USB_SPEED_LOW: + ep0_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(8)); + break; + case USB_SPEED_WIRELESS: + xhci_dbg(xhci, "FIXME xHCI doesn't support wireless speeds\n"); + return -EINVAL; + break; + default: + /* New speed? */ + BUG(); + } + /* EP 0 can handle "burst" sizes of 1, so Max Burst Size field is 0 */ + ep0_ctx->ep_info2 |= cpu_to_le32(MAX_BURST(0) | ERROR_COUNT(3)); + + ep0_ctx->deq = cpu_to_le64(dev->eps[0].ring->first_seg->dma | + dev->eps[0].ring->cycle_state); + + /* Steps 7 and 8 were done in xhci_alloc_virt_device() */ + + return 0; +} + +/* + * Convert interval expressed as 2^(bInterval - 1) == interval into + * straight exponent value 2^n == interval. + * + */ +static unsigned int xhci_parse_exponent_interval(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + unsigned int interval; + + interval = clamp_val(ep->desc.bInterval, 1, 16) - 1; + if (interval != ep->desc.bInterval - 1) + dev_warn(&udev->dev, + "ep %#x - rounding interval to %d %sframes\n", + ep->desc.bEndpointAddress, + 1 << interval, + udev->speed == USB_SPEED_FULL ? "" : "micro"); + + if (udev->speed == USB_SPEED_FULL) { + /* + * Full speed isoc endpoints specify interval in frames, + * not microframes. We are using microframes everywhere, + * so adjust accordingly. + */ + interval += 3; /* 1 frame = 2^3 uframes */ + } + + return interval; +} + +/* + * Convert bInterval expressed in microframes (in 1-255 range) to exponent of + * microframes, rounded down to nearest power of 2. + */ +static unsigned int xhci_microframes_to_exponent(struct usb_device *udev, + struct usb_host_endpoint *ep, unsigned int desc_interval, + unsigned int min_exponent, unsigned int max_exponent) +{ + unsigned int interval; + + interval = fls(desc_interval) - 1; + interval = clamp_val(interval, min_exponent, max_exponent); + if ((1 << interval) != desc_interval) + dev_warn(&udev->dev, + "ep %#x - rounding interval to %d microframes, ep desc says %d microframes\n", + ep->desc.bEndpointAddress, + 1 << interval, + desc_interval); + + return interval; +} + +static unsigned int xhci_parse_microframe_interval(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + if (ep->desc.bInterval == 0) + return 0; + return xhci_microframes_to_exponent(udev, ep, + ep->desc.bInterval, 0, 15); +} + + +static unsigned int xhci_parse_frame_interval(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + return xhci_microframes_to_exponent(udev, ep, + ep->desc.bInterval * 8, 3, 10); +} + +/* Return the polling or NAK interval. + * + * The polling interval is expressed in "microframes". If xHCI's Interval field + * is set to N, it will service the endpoint every 2^(Interval)*125us. + * + * The NAK interval is one NAK per 1 to 255 microframes, or no NAKs if interval + * is set to 0. + */ +static unsigned int xhci_get_endpoint_interval(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + unsigned int interval = 0; + + switch (udev->speed) { + case USB_SPEED_HIGH: + /* Max NAK rate */ + if (usb_endpoint_xfer_control(&ep->desc) || + usb_endpoint_xfer_bulk(&ep->desc)) { + interval = xhci_parse_microframe_interval(udev, ep); + break; + } + /* Fall through - SS and HS isoc/int have same decoding */ + + case USB_SPEED_SUPER: + if (usb_endpoint_xfer_int(&ep->desc) || + usb_endpoint_xfer_isoc(&ep->desc)) { + interval = xhci_parse_exponent_interval(udev, ep); + } + break; + + case USB_SPEED_FULL: + if (usb_endpoint_xfer_isoc(&ep->desc)) { + interval = xhci_parse_exponent_interval(udev, ep); + break; + } + /* + * Fall through for interrupt endpoint interval decoding + * since it uses the same rules as low speed interrupt + * endpoints. + */ + + case USB_SPEED_LOW: + if (usb_endpoint_xfer_int(&ep->desc) || + usb_endpoint_xfer_isoc(&ep->desc)) { + + interval = xhci_parse_frame_interval(udev, ep); + } + break; + + default: + BUG(); + } + return EP_INTERVAL(interval); +} + +/* The "Mult" field in the endpoint context is only set for SuperSpeed isoc eps. + * High speed endpoint descriptors can define "the number of additional + * transaction opportunities per microframe", but that goes in the Max Burst + * endpoint context field. + */ +static u32 xhci_get_endpoint_mult(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + if (udev->speed != USB_SPEED_SUPER || + !usb_endpoint_xfer_isoc(&ep->desc)) + return 0; + return ep->ss_ep_comp.bmAttributes; +} + +static u32 xhci_get_endpoint_type(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + int in; + u32 type; + + in = usb_endpoint_dir_in(&ep->desc); + if (usb_endpoint_xfer_control(&ep->desc)) { + type = EP_TYPE(CTRL_EP); + } else if (usb_endpoint_xfer_bulk(&ep->desc)) { + if (in) + type = EP_TYPE(BULK_IN_EP); + else + type = EP_TYPE(BULK_OUT_EP); + } else if (usb_endpoint_xfer_isoc(&ep->desc)) { + if (in) + type = EP_TYPE(ISOC_IN_EP); + else + type = EP_TYPE(ISOC_OUT_EP); + } else if (usb_endpoint_xfer_int(&ep->desc)) { + if (in) + type = EP_TYPE(INT_IN_EP); + else + type = EP_TYPE(INT_OUT_EP); + } else { + BUG(); + } + return type; +} + +/* Return the maximum endpoint service interval time (ESIT) payload. + * Basically, this is the maxpacket size, multiplied by the burst size + * and mult size. + */ +static u32 xhci_get_max_esit_payload(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + int max_burst; + int max_packet; + + /* Only applies for interrupt or isochronous endpoints */ + if (usb_endpoint_xfer_control(&ep->desc) || + usb_endpoint_xfer_bulk(&ep->desc)) + return 0; + + if (udev->speed == USB_SPEED_SUPER) + return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); + + max_packet = GET_MAX_PACKET(usb_endpoint_maxp(&ep->desc)); + max_burst = (usb_endpoint_maxp(&ep->desc) & 0x1800) >> 11; + /* A 0 in max burst means 1 transfer per ESIT */ + return max_packet * (max_burst + 1); +} + +/* Set up an endpoint with one ring segment. Do not allocate stream rings. + * Drivers will have to call usb_alloc_streams() to do that. + */ +int xhci_endpoint_init(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct usb_device *udev, + struct usb_host_endpoint *ep, + gfp_t mem_flags) +{ + unsigned int ep_index; + struct xhci_ep_ctx *ep_ctx; + struct xhci_ring *ep_ring; + unsigned int max_packet; + unsigned int max_burst; + enum xhci_ring_type type; + u32 max_esit_payload; + + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + + type = usb_endpoint_type(&ep->desc); + /* Set up the endpoint ring */ + virt_dev->eps[ep_index].new_ring = + xhci_ring_alloc(xhci, 2, 1, type, mem_flags); + if (!virt_dev->eps[ep_index].new_ring) { + /* Attempt to use the ring cache */ + if (virt_dev->num_rings_cached == 0) + return -ENOMEM; + virt_dev->eps[ep_index].new_ring = + virt_dev->ring_cache[virt_dev->num_rings_cached]; + virt_dev->ring_cache[virt_dev->num_rings_cached] = NULL; + virt_dev->num_rings_cached--; + xhci_reinit_cached_ring(xhci, virt_dev->eps[ep_index].new_ring, + 1, type); + } + virt_dev->eps[ep_index].skip = false; + ep_ring = virt_dev->eps[ep_index].new_ring; + ep_ctx->deq = cpu_to_le64(ep_ring->first_seg->dma | ep_ring->cycle_state); + + ep_ctx->ep_info = cpu_to_le32(xhci_get_endpoint_interval(udev, ep) + | EP_MULT(xhci_get_endpoint_mult(udev, ep))); + + /* FIXME dig Mult and streams info out of ep companion desc */ + + /* Allow 3 retries for everything but isoc; + * CErr shall be set to 0 for Isoch endpoints. + */ + if (!usb_endpoint_xfer_isoc(&ep->desc)) + ep_ctx->ep_info2 = cpu_to_le32(ERROR_COUNT(3)); + else + ep_ctx->ep_info2 = cpu_to_le32(ERROR_COUNT(0)); + + ep_ctx->ep_info2 |= cpu_to_le32(xhci_get_endpoint_type(udev, ep)); + + /* Set the max packet size and max burst */ + max_packet = GET_MAX_PACKET(usb_endpoint_maxp(&ep->desc)); + max_burst = 0; + switch (udev->speed) { + case USB_SPEED_SUPER: + /* dig out max burst from ep companion desc */ + max_burst = ep->ss_ep_comp.bMaxBurst; + break; + case USB_SPEED_HIGH: + /* Some devices get this wrong */ + if (usb_endpoint_xfer_bulk(&ep->desc)) + max_packet = 512; + /* bits 11:12 specify the number of additional transaction + * opportunities per microframe (USB 2.0, section 9.6.6) + */ + if (usb_endpoint_xfer_isoc(&ep->desc) || + usb_endpoint_xfer_int(&ep->desc)) { + max_burst = (usb_endpoint_maxp(&ep->desc) + & 0x1800) >> 11; + } + break; + case USB_SPEED_FULL: + case USB_SPEED_LOW: + break; + default: + BUG(); + } + ep_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(max_packet) | + MAX_BURST(max_burst)); + max_esit_payload = xhci_get_max_esit_payload(xhci, udev, ep); + ep_ctx->tx_info = cpu_to_le32(MAX_ESIT_PAYLOAD_FOR_EP(max_esit_payload)); + + /* + * XXX no idea how to calculate the average TRB buffer length for bulk + * endpoints, as the driver gives us no clue how big each scatter gather + * list entry (or buffer) is going to be. + * + * For isochronous and interrupt endpoints, we set it to the max + * available, until we have new API in the USB core to allow drivers to + * declare how much bandwidth they actually need. + * + * Normally, it would be calculated by taking the total of the buffer + * lengths in the TD and then dividing by the number of TRBs in a TD, + * including link TRBs, No-op TRBs, and Event data TRBs. Since we don't + * use Event Data TRBs, and we don't chain in a link TRB on short + * transfers, we're basically dividing by 1. + * + * xHCI 1.0 specification indicates that the Average TRB Length should + * be set to 8 for control endpoints. + */ + if (usb_endpoint_xfer_control(&ep->desc) && xhci->hci_version == 0x100) + ep_ctx->tx_info |= cpu_to_le32(AVG_TRB_LENGTH_FOR_EP(8)); + else + ep_ctx->tx_info |= + cpu_to_le32(AVG_TRB_LENGTH_FOR_EP(max_esit_payload)); + + /* FIXME Debug endpoint context */ + return 0; +} + +void xhci_endpoint_zero(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct usb_host_endpoint *ep) +{ + unsigned int ep_index; + struct xhci_ep_ctx *ep_ctx; + + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = 0; + ep_ctx->deq = 0; + ep_ctx->tx_info = 0; + /* Don't free the endpoint ring until the set interface or configuration + * request succeeds. + */ +} + +void xhci_clear_endpoint_bw_info(struct xhci_bw_info *bw_info) +{ + bw_info->ep_interval = 0; + bw_info->mult = 0; + bw_info->num_packets = 0; + bw_info->max_packet_size = 0; + bw_info->type = 0; + bw_info->max_esit_payload = 0; +} + +void xhci_update_bw_info(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx, + struct xhci_input_control_ctx *ctrl_ctx, + struct xhci_virt_device *virt_dev) +{ + struct xhci_bw_info *bw_info; + struct xhci_ep_ctx *ep_ctx; + unsigned int ep_type; + int i; + + for (i = 1; i < 31; ++i) { + bw_info = &virt_dev->eps[i].bw_info; + + /* We can't tell what endpoint type is being dropped, but + * unconditionally clearing the bandwidth info for non-periodic + * endpoints should be harmless because the info will never be + * set in the first place. + */ + if (!EP_IS_ADDED(ctrl_ctx, i) && EP_IS_DROPPED(ctrl_ctx, i)) { + /* Dropped endpoint */ + xhci_clear_endpoint_bw_info(bw_info); + continue; + } + + if (EP_IS_ADDED(ctrl_ctx, i)) { + ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, i); + ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2)); + + /* Ignore non-periodic endpoints */ + if (ep_type != ISOC_OUT_EP && ep_type != INT_OUT_EP && + ep_type != ISOC_IN_EP && + ep_type != INT_IN_EP) + continue; + + /* Added or changed endpoint */ + bw_info->ep_interval = CTX_TO_EP_INTERVAL( + le32_to_cpu(ep_ctx->ep_info)); + /* Number of packets and mult are zero-based in the + * input context, but we want one-based for the + * interval table. + */ + bw_info->mult = CTX_TO_EP_MULT( + le32_to_cpu(ep_ctx->ep_info)) + 1; + bw_info->num_packets = CTX_TO_MAX_BURST( + le32_to_cpu(ep_ctx->ep_info2)) + 1; + bw_info->max_packet_size = MAX_PACKET_DECODED( + le32_to_cpu(ep_ctx->ep_info2)); + bw_info->type = ep_type; + bw_info->max_esit_payload = CTX_TO_MAX_ESIT_PAYLOAD( + le32_to_cpu(ep_ctx->tx_info)); + } + } +} + +/* Copy output xhci_ep_ctx to the input xhci_ep_ctx copy. + * Useful when you want to change one particular aspect of the endpoint and then + * issue a configure endpoint command. + */ +void xhci_endpoint_copy(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx, + struct xhci_container_ctx *out_ctx, + unsigned int ep_index) +{ + struct xhci_ep_ctx *out_ep_ctx; + struct xhci_ep_ctx *in_ep_ctx; + + out_ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, ep_index); + in_ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index); + + in_ep_ctx->ep_info = out_ep_ctx->ep_info; + in_ep_ctx->ep_info2 = out_ep_ctx->ep_info2; + in_ep_ctx->deq = out_ep_ctx->deq; + in_ep_ctx->tx_info = out_ep_ctx->tx_info; +} + +/* Copy output xhci_slot_ctx to the input xhci_slot_ctx. + * Useful when you want to change one particular aspect of the endpoint and then + * issue a configure endpoint command. Only the context entries field matters, + * but we'll copy the whole thing anyway. + */ +void xhci_slot_copy(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx, + struct xhci_container_ctx *out_ctx) +{ + struct xhci_slot_ctx *in_slot_ctx; + struct xhci_slot_ctx *out_slot_ctx; + + in_slot_ctx = xhci_get_slot_ctx(xhci, in_ctx); + out_slot_ctx = xhci_get_slot_ctx(xhci, out_ctx); + + in_slot_ctx->dev_info = out_slot_ctx->dev_info; + in_slot_ctx->dev_info2 = out_slot_ctx->dev_info2; + in_slot_ctx->tt_info = out_slot_ctx->tt_info; + in_slot_ctx->dev_state = out_slot_ctx->dev_state; +} + +/* Set up the scratchpad buffer array and scratchpad buffers, if needed. */ +static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) +{ + int i; + struct device *dev = xhci_to_hcd(xhci)->self.controller; + int num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2); + + xhci_dbg(xhci, "Allocating %d scratchpad buffers\n", num_sp); + + if (!num_sp) + return 0; + + xhci->scratchpad = kzalloc(sizeof(*xhci->scratchpad), flags); + if (!xhci->scratchpad) + goto fail_sp; + + xhci->scratchpad->sp_array = dma_alloc_coherent(dev, + num_sp * sizeof(u64), + &xhci->scratchpad->sp_dma, flags); + if (!xhci->scratchpad->sp_array) + goto fail_sp2; + + xhci->scratchpad->sp_buffers = kzalloc(sizeof(void *) * num_sp, flags); + if (!xhci->scratchpad->sp_buffers) + goto fail_sp3; + + xhci->scratchpad->sp_dma_buffers = + kzalloc(sizeof(dma_addr_t) * num_sp, flags); + + if (!xhci->scratchpad->sp_dma_buffers) + goto fail_sp4; + + xhci->dcbaa->dev_context_ptrs[0] = cpu_to_le64(xhci->scratchpad->sp_dma); + for (i = 0; i < num_sp; i++) { + dma_addr_t dma; + void *buf = dma_alloc_coherent(dev, xhci->page_size, &dma, + flags); + if (!buf) + goto fail_sp5; + + xhci->scratchpad->sp_array[i] = dma; + xhci->scratchpad->sp_buffers[i] = buf; + xhci->scratchpad->sp_dma_buffers[i] = dma; + } + + return 0; + + fail_sp5: + for (i = i - 1; i >= 0; i--) { + dma_free_coherent(dev, xhci->page_size, + xhci->scratchpad->sp_buffers[i], + xhci->scratchpad->sp_dma_buffers[i]); + } + kfree(xhci->scratchpad->sp_dma_buffers); + + fail_sp4: + kfree(xhci->scratchpad->sp_buffers); + + fail_sp3: + dma_free_coherent(dev, num_sp * sizeof(u64), + xhci->scratchpad->sp_array, + xhci->scratchpad->sp_dma); + + fail_sp2: + kfree(xhci->scratchpad); + xhci->scratchpad = NULL; + + fail_sp: + return -ENOMEM; +} + +static void scratchpad_free(struct xhci_hcd *xhci) +{ + int num_sp; + int i; + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + + if (!xhci->scratchpad) + return; + + num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2); + + for (i = 0; i < num_sp; i++) { + dma_free_coherent(&pdev->dev, xhci->page_size, + xhci->scratchpad->sp_buffers[i], + xhci->scratchpad->sp_dma_buffers[i]); + } + kfree(xhci->scratchpad->sp_dma_buffers); + kfree(xhci->scratchpad->sp_buffers); + dma_free_coherent(&pdev->dev, num_sp * sizeof(u64), + xhci->scratchpad->sp_array, + xhci->scratchpad->sp_dma); + kfree(xhci->scratchpad); + xhci->scratchpad = NULL; +} + +struct xhci_command *xhci_alloc_command(struct xhci_hcd *xhci, + bool allocate_in_ctx, bool allocate_completion, + gfp_t mem_flags) +{ + struct xhci_command *command; + + command = kzalloc(sizeof(*command), mem_flags); + if (!command) + return NULL; + + if (allocate_in_ctx) { + command->in_ctx = + xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_INPUT, + mem_flags); + if (!command->in_ctx) { + kfree(command); + return NULL; + } + } + + if (allocate_completion) { + command->completion = + kzalloc(sizeof(struct completion), mem_flags); + if (!command->completion) { + xhci_free_container_ctx(xhci, command->in_ctx); + kfree(command); + return NULL; + } + init_completion(command->completion); + } + + command->status = 0; + INIT_LIST_HEAD(&command->cmd_list); + return command; +} + +void xhci_urb_free_priv(struct xhci_hcd *xhci, struct urb_priv *urb_priv) +{ + if (urb_priv) { + kfree(urb_priv->td[0]); + kfree(urb_priv); + } +} + +void xhci_free_command(struct xhci_hcd *xhci, + struct xhci_command *command) +{ + xhci_free_container_ctx(xhci, + command->in_ctx); + kfree(command->completion); + kfree(command); +} + +void xhci_mem_cleanup(struct xhci_hcd *xhci) +{ + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + struct dev_info *dev_info, *next; + struct xhci_cd *cur_cd, *next_cd; + unsigned long flags; + int size; + int i, j, num_ports; + + /* Free the Event Ring Segment Table and the actual Event Ring */ + size = sizeof(struct xhci_erst_entry)*(xhci->erst.num_entries); + if (xhci->erst.entries) + dma_free_coherent(&pdev->dev, size, + xhci->erst.entries, xhci->erst.erst_dma_addr); + xhci->erst.entries = NULL; + xhci_dbg(xhci, "Freed ERST\n"); + if (xhci->event_ring) + xhci_ring_free(xhci, xhci->event_ring); + xhci->event_ring = NULL; + xhci_dbg(xhci, "Freed event ring\n"); + + if (xhci->lpm_command) + xhci_free_command(xhci, xhci->lpm_command); + xhci->cmd_ring_reserved_trbs = 0; + if (xhci->cmd_ring) + xhci_ring_free(xhci, xhci->cmd_ring); + xhci->cmd_ring = NULL; + xhci_dbg(xhci, "Freed command ring\n"); + list_for_each_entry_safe(cur_cd, next_cd, + &xhci->cancel_cmd_list, cancel_cmd_list) { + list_del(&cur_cd->cancel_cmd_list); + kfree(cur_cd); + } + + num_ports = HCS_MAX_PORTS(xhci->hcs_params1); + for (i = 0; i < num_ports && xhci->rh_bw; i++) { + struct xhci_interval_bw_table *bwt = &xhci->rh_bw[i].bw_table; + for (j = 0; j < XHCI_MAX_INTERVAL; j++) { + struct list_head *ep = &bwt->interval_bw[j].endpoints; + while (!list_empty(ep)) + list_del_init(ep->next); + } + } + + for (i = 1; i < MAX_HC_SLOTS; ++i) + xhci_free_virt_device(xhci, i); + + if (xhci->segment_pool) + dma_pool_destroy(xhci->segment_pool); + xhci->segment_pool = NULL; + xhci_dbg(xhci, "Freed segment pool\n"); + + if (xhci->device_pool) + dma_pool_destroy(xhci->device_pool); + xhci->device_pool = NULL; + xhci_dbg(xhci, "Freed device context pool\n"); + + if (xhci->small_streams_pool) + dma_pool_destroy(xhci->small_streams_pool); + xhci->small_streams_pool = NULL; + xhci_dbg(xhci, "Freed small stream array pool\n"); + + if (xhci->medium_streams_pool) + dma_pool_destroy(xhci->medium_streams_pool); + xhci->medium_streams_pool = NULL; + xhci_dbg(xhci, "Freed medium stream array pool\n"); + + if (xhci->dcbaa) + dma_free_coherent(&pdev->dev, sizeof(*xhci->dcbaa), + xhci->dcbaa, xhci->dcbaa->dma); + xhci->dcbaa = NULL; + + scratchpad_free(xhci); + + spin_lock_irqsave(&xhci->lock, flags); + list_for_each_entry_safe(dev_info, next, &xhci->lpm_failed_devs, list) { + list_del(&dev_info->list); + kfree(dev_info); + } + spin_unlock_irqrestore(&xhci->lock, flags); + + if (!xhci->rh_bw) + goto no_bw; + + for (i = 0; i < num_ports; i++) { + struct xhci_tt_bw_info *tt, *n; + list_for_each_entry_safe(tt, n, &xhci->rh_bw[i].tts, tt_list) { + list_del(&tt->tt_list); + kfree(tt); + } + } + +no_bw: + xhci->num_usb2_ports = 0; + xhci->num_usb3_ports = 0; + xhci->num_active_eps = 0; + kfree(xhci->usb2_ports); + kfree(xhci->usb3_ports); + kfree(xhci->port_array); + kfree(xhci->rh_bw); + + xhci->page_size = 0; + xhci->page_shift = 0; + xhci->bus_state[0].bus_suspended = 0; + xhci->bus_state[1].bus_suspended = 0; +} + +static int xhci_test_trb_in_td(struct xhci_hcd *xhci, + struct xhci_segment *input_seg, + union xhci_trb *start_trb, + union xhci_trb *end_trb, + dma_addr_t input_dma, + struct xhci_segment *result_seg, + char *test_name, int test_number) +{ + unsigned long long start_dma; + unsigned long long end_dma; + struct xhci_segment *seg; + + start_dma = xhci_trb_virt_to_dma(input_seg, start_trb); + end_dma = xhci_trb_virt_to_dma(input_seg, end_trb); + + seg = trb_in_td(input_seg, start_trb, end_trb, input_dma); + if (seg != result_seg) { + xhci_warn(xhci, "WARN: %s TRB math test %d failed!\n", + test_name, test_number); + xhci_warn(xhci, "Tested TRB math w/ seg %p and " + "input DMA 0x%llx\n", + input_seg, + (unsigned long long) input_dma); + xhci_warn(xhci, "starting TRB %p (0x%llx DMA), " + "ending TRB %p (0x%llx DMA)\n", + start_trb, start_dma, + end_trb, end_dma); + xhci_warn(xhci, "Expected seg %p, got seg %p\n", + result_seg, seg); + return -1; + } + return 0; +} + +/* TRB math checks for xhci_trb_in_td(), using the command and event rings. */ +static int xhci_check_trb_in_td_math(struct xhci_hcd *xhci, gfp_t mem_flags) +{ + struct { + dma_addr_t input_dma; + struct xhci_segment *result_seg; + } simple_test_vector [] = { + /* A zeroed DMA field should fail */ + { 0, NULL }, + /* One TRB before the ring start should fail */ + { xhci->event_ring->first_seg->dma - 16, NULL }, + /* One byte before the ring start should fail */ + { xhci->event_ring->first_seg->dma - 1, NULL }, + /* Starting TRB should succeed */ + { xhci->event_ring->first_seg->dma, xhci->event_ring->first_seg }, + /* Ending TRB should succeed */ + { xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 1)*16, + xhci->event_ring->first_seg }, + /* One byte after the ring end should fail */ + { xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 1)*16 + 1, NULL }, + /* One TRB after the ring end should fail */ + { xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT)*16, NULL }, + /* An address of all ones should fail */ + { (dma_addr_t) (~0), NULL }, + }; + struct { + struct xhci_segment *input_seg; + union xhci_trb *start_trb; + union xhci_trb *end_trb; + dma_addr_t input_dma; + struct xhci_segment *result_seg; + } complex_test_vector [] = { + /* Test feeding a valid DMA address from a different ring */ + { .input_seg = xhci->event_ring->first_seg, + .start_trb = xhci->event_ring->first_seg->trbs, + .end_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1], + .input_dma = xhci->cmd_ring->first_seg->dma, + .result_seg = NULL, + }, + /* Test feeding a valid end TRB from a different ring */ + { .input_seg = xhci->event_ring->first_seg, + .start_trb = xhci->event_ring->first_seg->trbs, + .end_trb = &xhci->cmd_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1], + .input_dma = xhci->cmd_ring->first_seg->dma, + .result_seg = NULL, + }, + /* Test feeding a valid start and end TRB from a different ring */ + { .input_seg = xhci->event_ring->first_seg, + .start_trb = xhci->cmd_ring->first_seg->trbs, + .end_trb = &xhci->cmd_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1], + .input_dma = xhci->cmd_ring->first_seg->dma, + .result_seg = NULL, + }, + /* TRB in this ring, but after this TD */ + { .input_seg = xhci->event_ring->first_seg, + .start_trb = &xhci->event_ring->first_seg->trbs[0], + .end_trb = &xhci->event_ring->first_seg->trbs[3], + .input_dma = xhci->event_ring->first_seg->dma + 4*16, + .result_seg = NULL, + }, + /* TRB in this ring, but before this TD */ + { .input_seg = xhci->event_ring->first_seg, + .start_trb = &xhci->event_ring->first_seg->trbs[3], + .end_trb = &xhci->event_ring->first_seg->trbs[6], + .input_dma = xhci->event_ring->first_seg->dma + 2*16, + .result_seg = NULL, + }, + /* TRB in this ring, but after this wrapped TD */ + { .input_seg = xhci->event_ring->first_seg, + .start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3], + .end_trb = &xhci->event_ring->first_seg->trbs[1], + .input_dma = xhci->event_ring->first_seg->dma + 2*16, + .result_seg = NULL, + }, + /* TRB in this ring, but before this wrapped TD */ + { .input_seg = xhci->event_ring->first_seg, + .start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3], + .end_trb = &xhci->event_ring->first_seg->trbs[1], + .input_dma = xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 4)*16, + .result_seg = NULL, + }, + /* TRB not in this ring, and we have a wrapped TD */ + { .input_seg = xhci->event_ring->first_seg, + .start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3], + .end_trb = &xhci->event_ring->first_seg->trbs[1], + .input_dma = xhci->cmd_ring->first_seg->dma + 2*16, + .result_seg = NULL, + }, + }; + + unsigned int num_tests; + int i, ret; + + num_tests = ARRAY_SIZE(simple_test_vector); + for (i = 0; i < num_tests; i++) { + ret = xhci_test_trb_in_td(xhci, + xhci->event_ring->first_seg, + xhci->event_ring->first_seg->trbs, + &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1], + simple_test_vector[i].input_dma, + simple_test_vector[i].result_seg, + "Simple", i); + if (ret < 0) + return ret; + } + + num_tests = ARRAY_SIZE(complex_test_vector); + for (i = 0; i < num_tests; i++) { + ret = xhci_test_trb_in_td(xhci, + complex_test_vector[i].input_seg, + complex_test_vector[i].start_trb, + complex_test_vector[i].end_trb, + complex_test_vector[i].input_dma, + complex_test_vector[i].result_seg, + "Complex", i); + if (ret < 0) + return ret; + } + xhci_dbg(xhci, "TRB math tests passed.\n"); + return 0; +} + +static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) +{ + u64 temp; + dma_addr_t deq; + + deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg, + xhci->event_ring->dequeue); + if (deq == 0 && !in_interrupt()) + xhci_warn(xhci, "WARN something wrong with SW event ring " + "dequeue ptr.\n"); + /* Update HC event ring dequeue pointer */ + temp = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + temp &= ERST_PTR_MASK; + /* Don't clear the EHB bit (which is RW1C) because + * there might be more events to service. + */ + temp &= ~ERST_EHB; + xhci_dbg(xhci, "// Write event ring dequeue pointer, " + "preserving EHB bit\n"); + xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp, + &xhci->ir_set->erst_dequeue); +} + +static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, + __le32 __iomem *addr, u8 major_revision) +{ + u32 temp, port_offset, port_count; + int i; + + if (major_revision > 0x03) { + xhci_warn(xhci, "Ignoring unknown port speed, " + "Ext Cap %p, revision = 0x%x\n", + addr, major_revision); + /* Ignoring port protocol we can't understand. FIXME */ + return; + } + + /* Port offset and count in the third dword, see section 7.2 */ + temp = xhci_readl(xhci, addr + 2); + port_offset = XHCI_EXT_PORT_OFF(temp); + port_count = XHCI_EXT_PORT_COUNT(temp); + xhci_dbg(xhci, "Ext Cap %p, port offset = %u, " + "count = %u, revision = 0x%x\n", + addr, port_offset, port_count, major_revision); + /* Port count includes the current port offset */ + if (port_offset == 0 || (port_offset + port_count - 1) > num_ports) + /* WTF? "Valid values are ‘1’ to MaxPorts" */ + return; + + /* Check the host's USB2 LPM capability */ + if ((xhci->hci_version == 0x96) && (major_revision != 0x03) && + (temp & XHCI_L1C)) { + xhci_dbg(xhci, "xHCI 0.96: support USB2 software lpm\n"); + xhci->sw_lpm_support = 1; + } + + if ((xhci->hci_version >= 0x100) && (major_revision != 0x03)) { + xhci_dbg(xhci, "xHCI 1.0: support USB2 software lpm\n"); + xhci->sw_lpm_support = 1; + if (temp & XHCI_HLC) { + xhci_dbg(xhci, "xHCI 1.0: support USB2 hardware lpm\n"); + xhci->hw_lpm_support = 1; + } + } + + port_offset--; + for (i = port_offset; i < (port_offset + port_count); i++) { + /* Duplicate entry. Ignore the port if the revisions differ. */ + if (xhci->port_array[i] != 0) { + xhci_warn(xhci, "Duplicate port entry, Ext Cap %p," + " port %u\n", addr, i); + xhci_warn(xhci, "Port was marked as USB %u, " + "duplicated as USB %u\n", + xhci->port_array[i], major_revision); + /* Only adjust the roothub port counts if we haven't + * found a similar duplicate. + */ + if (xhci->port_array[i] != major_revision && + xhci->port_array[i] != DUPLICATE_ENTRY) { + if (xhci->port_array[i] == 0x03) + xhci->num_usb3_ports--; + else + xhci->num_usb2_ports--; + xhci->port_array[i] = DUPLICATE_ENTRY; + } + /* FIXME: Should we disable the port? */ + continue; + } + xhci->port_array[i] = major_revision; + if (major_revision == 0x03) + xhci->num_usb3_ports++; + else + xhci->num_usb2_ports++; + } + /* FIXME: Should we disable ports not in the Extended Capabilities? */ +} + +/* + * Scan the Extended Capabilities for the "Supported Protocol Capabilities" that + * specify what speeds each port is supposed to be. We can't count on the port + * speed bits in the PORTSC register being correct until a device is connected, + * but we need to set up the two fake roothubs with the correct number of USB + * 3.0 and USB 2.0 ports at host controller initialization time. + */ +static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) +{ + __le32 __iomem *addr; + u32 offset; + unsigned int num_ports; + int i, j, port_index; + + addr = &xhci->cap_regs->hcc_params; + offset = XHCI_HCC_EXT_CAPS(xhci_readl(xhci, addr)); + if (offset == 0) { + xhci_err(xhci, "No Extended Capability registers, " + "unable to set up roothub.\n"); + return -ENODEV; + } + + num_ports = HCS_MAX_PORTS(xhci->hcs_params1); + xhci->port_array = kzalloc(sizeof(*xhci->port_array)*num_ports, flags); + if (!xhci->port_array) + return -ENOMEM; + + xhci->rh_bw = kzalloc(sizeof(*xhci->rh_bw)*num_ports, flags); + if (!xhci->rh_bw) + return -ENOMEM; + for (i = 0; i < num_ports; i++) { + struct xhci_interval_bw_table *bw_table; + + INIT_LIST_HEAD(&xhci->rh_bw[i].tts); + bw_table = &xhci->rh_bw[i].bw_table; + for (j = 0; j < XHCI_MAX_INTERVAL; j++) + INIT_LIST_HEAD(&bw_table->interval_bw[j].endpoints); + } + + /* + * For whatever reason, the first capability offset is from the + * capability register base, not from the HCCPARAMS register. + * See section 5.3.6 for offset calculation. + */ + addr = &xhci->cap_regs->hc_capbase + offset; + while (1) { + u32 cap_id; + + cap_id = xhci_readl(xhci, addr); + if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) + xhci_add_in_port(xhci, num_ports, addr, + (u8) XHCI_EXT_PORT_MAJOR(cap_id)); + offset = XHCI_EXT_CAPS_NEXT(cap_id); + if (!offset || (xhci->num_usb2_ports + xhci->num_usb3_ports) + == num_ports) + break; + /* + * Once you're into the Extended Capabilities, the offset is + * always relative to the register holding the offset. + */ + addr += offset; + } + + if (xhci->num_usb2_ports == 0 && xhci->num_usb3_ports == 0) { + xhci_warn(xhci, "No ports on the roothubs?\n"); + return -ENODEV; + } + xhci_dbg(xhci, "Found %u USB 2.0 ports and %u USB 3.0 ports.\n", + xhci->num_usb2_ports, xhci->num_usb3_ports); + + /* Place limits on the number of roothub ports so that the hub + * descriptors aren't longer than the USB core will allocate. + */ + if (xhci->num_usb3_ports > 15) { + xhci_dbg(xhci, "Limiting USB 3.0 roothub ports to 15.\n"); + xhci->num_usb3_ports = 15; + } + if (xhci->num_usb2_ports > USB_MAXCHILDREN) { + xhci_dbg(xhci, "Limiting USB 2.0 roothub ports to %u.\n", + USB_MAXCHILDREN); + xhci->num_usb2_ports = USB_MAXCHILDREN; + } + + /* + * Note we could have all USB 3.0 ports, or all USB 2.0 ports. + * Not sure how the USB core will handle a hub with no ports... + */ + if (xhci->num_usb2_ports) { + xhci->usb2_ports = kmalloc(sizeof(*xhci->usb2_ports)* + xhci->num_usb2_ports, flags); + if (!xhci->usb2_ports) + return -ENOMEM; + + port_index = 0; + for (i = 0; i < num_ports; i++) { + if (xhci->port_array[i] == 0x03 || + xhci->port_array[i] == 0 || + xhci->port_array[i] == DUPLICATE_ENTRY) + continue; + + xhci->usb2_ports[port_index] = + &xhci->op_regs->port_status_base + + NUM_PORT_REGS*i; + xhci_dbg(xhci, "USB 2.0 port at index %u, " + "addr = %p\n", i, + xhci->usb2_ports[port_index]); + port_index++; + if (port_index == xhci->num_usb2_ports) + break; + } + } + if (xhci->num_usb3_ports) { + xhci->usb3_ports = kmalloc(sizeof(*xhci->usb3_ports)* + xhci->num_usb3_ports, flags); + if (!xhci->usb3_ports) + return -ENOMEM; + + port_index = 0; + for (i = 0; i < num_ports; i++) + if (xhci->port_array[i] == 0x03) { + xhci->usb3_ports[port_index] = + &xhci->op_regs->port_status_base + + NUM_PORT_REGS*i; + xhci_dbg(xhci, "USB 3.0 port at index %u, " + "addr = %p\n", i, + xhci->usb3_ports[port_index]); + port_index++; + if (port_index == xhci->num_usb3_ports) + break; + } + } + return 0; +} + +int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) +{ + dma_addr_t dma; + struct device *dev = xhci_to_hcd(xhci)->self.controller; + unsigned int val, val2; + u64 val_64; + struct xhci_segment *seg; + u32 page_size, temp; + int i; + + INIT_LIST_HEAD(&xhci->lpm_failed_devs); + INIT_LIST_HEAD(&xhci->cancel_cmd_list); + + page_size = xhci_readl(xhci, &xhci->op_regs->page_size); + xhci_dbg(xhci, "Supported page size register = 0x%x\n", page_size); + for (i = 0; i < 16; i++) { + if ((0x1 & page_size) != 0) + break; + page_size = page_size >> 1; + } + if (i < 16) + xhci_dbg(xhci, "Supported page size of %iK\n", (1 << (i+12)) / 1024); + else + xhci_warn(xhci, "WARN: no supported page size\n"); + /* Use 4K pages, since that's common and the minimum the HC supports */ + xhci->page_shift = 12; + xhci->page_size = 1 << xhci->page_shift; + xhci_dbg(xhci, "HCD page size set to %iK\n", xhci->page_size / 1024); + + /* + * Program the Number of Device Slots Enabled field in the CONFIG + * register with the max value of slots the HC can handle. + */ + val = HCS_MAX_SLOTS(xhci_readl(xhci, &xhci->cap_regs->hcs_params1)); + xhci_dbg(xhci, "// xHC can handle at most %d device slots.\n", + (unsigned int) val); + val2 = xhci_readl(xhci, &xhci->op_regs->config_reg); + val |= (val2 & ~HCS_SLOTS_MASK); + xhci_dbg(xhci, "// Setting Max device slots reg = 0x%x.\n", + (unsigned int) val); + xhci_writel(xhci, val, &xhci->op_regs->config_reg); + + /* + * Section 5.4.8 - doorbell array must be + * "physically contiguous and 64-byte (cache line) aligned". + */ + xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, + GFP_KERNEL); + if (!xhci->dcbaa) + goto fail; + memset(xhci->dcbaa, 0, sizeof *(xhci->dcbaa)); + xhci->dcbaa->dma = dma; + xhci_dbg(xhci, "// Device context base array address = 0x%llx (DMA), %p (virt)\n", + (unsigned long long)xhci->dcbaa->dma, xhci->dcbaa); + xhci_write_64(xhci, dma, &xhci->op_regs->dcbaa_ptr); + + /* + * Initialize the ring segment pool. The ring must be a contiguous + * structure comprised of TRBs. The TRBs must be 16 byte aligned, + * however, the command ring segment needs 64-byte aligned segments, + * so we pick the greater alignment need. + */ + xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, + TRB_SEGMENT_SIZE, 64, xhci->page_size); + + /* See Table 46 and Note on Figure 55 */ + xhci->device_pool = dma_pool_create("xHCI input/output contexts", dev, + 2112, 64, xhci->page_size); + if (!xhci->segment_pool || !xhci->device_pool) + goto fail; + + /* Linear stream context arrays don't have any boundary restrictions, + * and only need to be 16-byte aligned. + */ + xhci->small_streams_pool = + dma_pool_create("xHCI 256 byte stream ctx arrays", + dev, SMALL_STREAM_ARRAY_SIZE, 16, 0); + xhci->medium_streams_pool = + dma_pool_create("xHCI 1KB stream ctx arrays", + dev, MEDIUM_STREAM_ARRAY_SIZE, 16, 0); + /* Any stream context array bigger than MEDIUM_STREAM_ARRAY_SIZE + * will be allocated with dma_alloc_coherent() + */ + + if (!xhci->small_streams_pool || !xhci->medium_streams_pool) + goto fail; + + /* Set up the command ring to have one segments for now. */ + xhci->cmd_ring = xhci_ring_alloc(xhci, 1, 1, TYPE_COMMAND, flags); + if (!xhci->cmd_ring) + goto fail; + xhci_dbg(xhci, "Allocated command ring at %p\n", xhci->cmd_ring); + xhci_dbg(xhci, "First segment DMA is 0x%llx\n", + (unsigned long long)xhci->cmd_ring->first_seg->dma); + + /* Set the address in the Command Ring Control register */ + val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | + (xhci->cmd_ring->first_seg->dma & (u64) ~CMD_RING_RSVD_BITS) | + xhci->cmd_ring->cycle_state; + xhci_dbg(xhci, "// Setting command ring address to 0x%x\n", val); + xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); + xhci_dbg_cmd_ptrs(xhci); + + xhci->lpm_command = xhci_alloc_command(xhci, true, true, flags); + if (!xhci->lpm_command) + goto fail; + + /* Reserve one command ring TRB for disabling LPM. + * Since the USB core grabs the shared usb_bus bandwidth mutex before + * disabling LPM, we only need to reserve one TRB for all devices. + */ + xhci->cmd_ring_reserved_trbs++; + + val = xhci_readl(xhci, &xhci->cap_regs->db_off); + val &= DBOFF_MASK; + xhci_dbg(xhci, "// Doorbell array is located at offset 0x%x" + " from cap regs base addr\n", val); + xhci->dba = (void __iomem *) xhci->cap_regs + val; + xhci_dbg_regs(xhci); + xhci_print_run_regs(xhci); + /* Set ir_set to interrupt register set 0 */ + xhci->ir_set = &xhci->run_regs->ir_set[0]; + + /* + * Event ring setup: Allocate a normal ring, but also setup + * the event ring segment table (ERST). Section 4.9.3. + */ + xhci_dbg(xhci, "// Allocating event ring\n"); + xhci->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT, + flags); + if (!xhci->event_ring) + goto fail; + if (xhci_check_trb_in_td_math(xhci, flags) < 0) + goto fail; + + xhci->erst.entries = dma_alloc_coherent(dev, + sizeof(struct xhci_erst_entry) * ERST_NUM_SEGS, &dma, + GFP_KERNEL); + if (!xhci->erst.entries) + goto fail; + xhci_dbg(xhci, "// Allocated event ring segment table at 0x%llx\n", + (unsigned long long)dma); + + memset(xhci->erst.entries, 0, sizeof(struct xhci_erst_entry)*ERST_NUM_SEGS); + xhci->erst.num_entries = ERST_NUM_SEGS; + xhci->erst.erst_dma_addr = dma; + xhci_dbg(xhci, "Set ERST to 0; private num segs = %i, virt addr = %p, dma addr = 0x%llx\n", + xhci->erst.num_entries, + xhci->erst.entries, + (unsigned long long)xhci->erst.erst_dma_addr); + + /* set ring base address and size for each segment table entry */ + for (val = 0, seg = xhci->event_ring->first_seg; val < ERST_NUM_SEGS; val++) { + struct xhci_erst_entry *entry = &xhci->erst.entries[val]; + entry->seg_addr = cpu_to_le64(seg->dma); + entry->seg_size = cpu_to_le32(TRBS_PER_SEGMENT); + entry->rsvd = 0; + seg = seg->next; + } + + /* set ERST count with the number of entries in the segment table */ + val = xhci_readl(xhci, &xhci->ir_set->erst_size); + val &= ERST_SIZE_MASK; + val |= ERST_NUM_SEGS; + xhci_dbg(xhci, "// Write ERST size = %i to ir_set 0 (some bits preserved)\n", + val); + xhci_writel(xhci, val, &xhci->ir_set->erst_size); + + xhci_dbg(xhci, "// Set ERST entries to point to event ring.\n"); + /* set the segment table base address */ + xhci_dbg(xhci, "// Set ERST base address for ir_set 0 = 0x%llx\n", + (unsigned long long)xhci->erst.erst_dma_addr); + val_64 = xhci_read_64(xhci, &xhci->ir_set->erst_base); + val_64 &= ERST_PTR_MASK; + val_64 |= (xhci->erst.erst_dma_addr & (u64) ~ERST_PTR_MASK); + xhci_write_64(xhci, val_64, &xhci->ir_set->erst_base); + + /* Set the event ring dequeue address */ + xhci_set_hc_event_deq(xhci); + xhci_dbg(xhci, "Wrote ERST address to ir_set 0.\n"); + xhci_print_ir_set(xhci, 0); + + /* + * XXX: Might need to set the Interrupter Moderation Register to + * something other than the default (~1ms minimum between interrupts). + * See section 5.5.1.2. + */ + init_completion(&xhci->addr_dev); + for (i = 0; i < MAX_HC_SLOTS; ++i) + xhci->devs[i] = NULL; + for (i = 0; i < USB_MAXCHILDREN; ++i) { + xhci->bus_state[0].resume_done[i] = 0; + xhci->bus_state[1].resume_done[i] = 0; + } + + if (scratchpad_alloc(xhci, flags)) + goto fail; + if (xhci_setup_port_arrays(xhci, flags)) + goto fail; + + /* Enable USB 3.0 device notifications for function remote wake, which + * is necessary for allowing USB 3.0 devices to do remote wakeup from + * U3 (device suspend). + */ + temp = xhci_readl(xhci, &xhci->op_regs->dev_notification); + temp &= ~DEV_NOTE_MASK; + temp |= DEV_NOTE_FWAKE; + xhci_writel(xhci, temp, &xhci->op_regs->dev_notification); + + return 0; + +fail: + xhci_warn(xhci, "Couldn't initialize memory\n"); + xhci_halt(xhci); + xhci_reset(xhci); + xhci_mem_cleanup(xhci); + return -ENOMEM; +} diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c new file mode 100644 index 00000000..0e57bcb8 --- /dev/null +++ b/drivers/usb/host/xhci-pci.c @@ -0,0 +1,379 @@ +/* + * xHCI host controller driver PCI Bus Glue. + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include "xhci.h" + +/* Device for a quirk */ +#define PCI_VENDOR_ID_FRESCO_LOGIC 0x1b73 +#define PCI_DEVICE_ID_FRESCO_LOGIC_PDK 0x1000 +#define PCI_DEVICE_ID_FRESCO_LOGIC_FL1400 0x1400 + +#define PCI_VENDOR_ID_ETRON 0x1b6f +#define PCI_DEVICE_ID_ASROCK_P67 0x7023 + +static const char hcd_name[] = "xhci_hcd"; + +/* called after powerup, by probe or system-pm "wakeup" */ +static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev) +{ + /* + * TODO: Implement finding debug ports later. + * TODO: see if there are any quirks that need to be added to handle + * new extended capabilities. + */ + + /* PCI Memory-Write-Invalidate cycle support is optional (uncommon) */ + if (!pci_set_mwi(pdev)) + xhci_dbg(xhci, "MWI active\n"); + + xhci_dbg(xhci, "Finished xhci_pci_reinit\n"); + return 0; +} + +static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + /* Look for vendor-specific quirks */ + if (pdev->vendor == PCI_VENDOR_ID_FRESCO_LOGIC && + (pdev->device == PCI_DEVICE_ID_FRESCO_LOGIC_PDK || + pdev->device == PCI_DEVICE_ID_FRESCO_LOGIC_FL1400)) { + if (pdev->device == PCI_DEVICE_ID_FRESCO_LOGIC_PDK && + pdev->revision == 0x0) { + xhci->quirks |= XHCI_RESET_EP_QUIRK; + xhci_dbg(xhci, "QUIRK: Fresco Logic xHC needs configure" + " endpoint cmd after reset endpoint\n"); + } + /* Fresco Logic confirms: all revisions of this chip do not + * support MSI, even though some of them claim to in their PCI + * capabilities. + */ + xhci->quirks |= XHCI_BROKEN_MSI; + xhci_dbg(xhci, "QUIRK: Fresco Logic revision %u " + "has broken MSI implementation\n", + pdev->revision); + xhci->quirks |= XHCI_TRUST_TX_LENGTH; + } + + if (pdev->vendor == PCI_VENDOR_ID_NEC) + xhci->quirks |= XHCI_NEC_HOST; + + if (pdev->vendor == PCI_VENDOR_ID_AMD && xhci->hci_version == 0x96) + xhci->quirks |= XHCI_AMD_0x96_HOST; + + /* AMD PLL quirk */ + if (pdev->vendor == PCI_VENDOR_ID_AMD && usb_amd_find_chipset_info()) + xhci->quirks |= XHCI_AMD_PLL_FIX; + + if (pdev->vendor == PCI_VENDOR_ID_AMD) + xhci->quirks |= XHCI_TRUST_TX_LENGTH; + + if (pdev->vendor == PCI_VENDOR_ID_INTEL) { + xhci->quirks |= XHCI_LPM_SUPPORT; + xhci->quirks |= XHCI_INTEL_HOST; + } + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + pdev->device == PCI_DEVICE_ID_INTEL_PANTHERPOINT_XHCI) { + xhci->quirks |= XHCI_EP_LIMIT_QUIRK; + xhci->limit_active_eps = 64; + xhci->quirks |= XHCI_SW_BW_CHECKING; + /* + * PPT desktop boards DH77EB and DH77DF will power back on after + * a few seconds of being shutdown. The fix for this is to + * switch the ports from xHCI to EHCI on shutdown. We can't use + * DMI information to find those particular boards (since each + * vendor will change the board name), so we have to key off all + * PPT chipsets. + */ + xhci->quirks |= XHCI_SPURIOUS_REBOOT; + xhci->quirks |= XHCI_AVOID_BEI; + } + if (pdev->vendor == PCI_VENDOR_ID_ETRON && + pdev->device == PCI_DEVICE_ID_ASROCK_P67) { + xhci->quirks |= XHCI_RESET_ON_RESUME; + xhci_dbg(xhci, "QUIRK: Resetting on resume\n"); + xhci->quirks |= XHCI_TRUST_TX_LENGTH; + } + if (pdev->vendor == PCI_VENDOR_ID_RENESAS && + pdev->device == 0x0015) + xhci->quirks |= XHCI_RESET_ON_RESUME; + if (pdev->vendor == PCI_VENDOR_ID_VIA) + xhci->quirks |= XHCI_RESET_ON_RESUME; +} + +/* called during probe() after chip reset completes */ +static int xhci_pci_setup(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci; + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + int retval; + + retval = xhci_gen_setup(hcd, xhci_pci_quirks); + if (retval) + return retval; + + xhci = hcd_to_xhci(hcd); + if (!usb_hcd_is_primary_hcd(hcd)) + return 0; + + pci_read_config_byte(pdev, XHCI_SBRN_OFFSET, &xhci->sbrn); + xhci_dbg(xhci, "Got SBRN %u\n", (unsigned int) xhci->sbrn); + + /* Find any debug ports */ + retval = xhci_pci_reinit(xhci, pdev); + if (!retval) + return retval; + + kfree(xhci); + return retval; +} + +/* + * We need to register our own PCI probe function (instead of the USB core's + * function) in order to create a second roothub under xHCI. + */ +static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int retval; + struct xhci_hcd *xhci; + struct hc_driver *driver; + struct usb_hcd *hcd; + + driver = (struct hc_driver *)id->driver_data; + + /* Prevent runtime suspending between USB-2 and USB-3 initialization */ + pm_runtime_get_noresume(&dev->dev); + + /* Register the USB 2.0 roothub. + * FIXME: USB core must know to register the USB 2.0 roothub first. + * This is sort of silly, because we could just set the HCD driver flags + * to say USB 2.0, but I'm not sure what the implications would be in + * the other parts of the HCD code. + */ + retval = usb_hcd_pci_probe(dev, id); + + if (retval) + goto put_runtime_pm; + + /* USB 2.0 roothub is stored in the PCI device now. */ + hcd = dev_get_drvdata(&dev->dev); + xhci = hcd_to_xhci(hcd); + xhci->shared_hcd = usb_create_shared_hcd(driver, &dev->dev, + pci_name(dev), hcd); + if (!xhci->shared_hcd) { + retval = -ENOMEM; + goto dealloc_usb2_hcd; + } + + /* Set the xHCI pointer before xhci_pci_setup() (aka hcd_driver.reset) + * is called by usb_add_hcd(). + */ + *((struct xhci_hcd **) xhci->shared_hcd->hcd_priv) = xhci; + + retval = usb_add_hcd(xhci->shared_hcd, dev->irq, + IRQF_SHARED); + if (retval) + goto put_usb3_hcd; + /* Roothub already marked as USB 3.0 speed */ + + /* We know the LPM timeout algorithms for this host, let the USB core + * enable and disable LPM for devices under the USB 3.0 roothub. + */ + if (xhci->quirks & XHCI_LPM_SUPPORT) + hcd_to_bus(xhci->shared_hcd)->root_hub->lpm_capable = 1; + + /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ + pm_runtime_put_noidle(&dev->dev); + + return 0; + +put_usb3_hcd: + usb_put_hcd(xhci->shared_hcd); +dealloc_usb2_hcd: + usb_hcd_pci_remove(dev); +put_runtime_pm: + pm_runtime_put_noidle(&dev->dev); + return retval; +} + +static void xhci_pci_remove(struct pci_dev *dev) +{ + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(pci_get_drvdata(dev)); + if (xhci->shared_hcd) { + usb_remove_hcd(xhci->shared_hcd); + usb_put_hcd(xhci->shared_hcd); + } + usb_hcd_pci_remove(dev); + kfree(xhci); +} + +#ifdef CONFIG_PM +static int xhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + + /* + * Systems with the TI redriver that loses port status change events + * need to have the registers polled during D3, so avoid D3cold. + */ + if (xhci_compliance_mode_recovery_timer_quirk_check()) + pdev->no_d3cold = true; + + return xhci_suspend(xhci); +} + +static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + int retval = 0; + + /* The BIOS on systems with the Intel Panther Point chipset may or may + * not support xHCI natively. That means that during system resume, it + * may switch the ports back to EHCI so that users can use their + * keyboard to select a kernel from GRUB after resume from hibernate. + * + * The BIOS is supposed to remember whether the OS had xHCI ports + * enabled before resume, and switch the ports back to xHCI when the + * BIOS/OS semaphore is written, but we all know we can't trust BIOS + * writers. + * + * Unconditionally switch the ports back to xHCI after a system resume. + * We can't tell whether the EHCI or xHCI controller will be resumed + * first, so we have to do the port switchover in both drivers. Writing + * a '1' to the port switchover registers should have no effect if the + * port was already switched over. + */ + if (usb_is_intel_switchable_xhci(pdev)) + usb_enable_xhci_ports(pdev); + + retval = xhci_resume(xhci, hibernated); + return retval; +} +#endif /* CONFIG_PM */ + +static const struct hc_driver xhci_pci_hc_driver = { + .description = hcd_name, + .product_desc = "xHCI Host Controller", + .hcd_priv_size = sizeof(struct xhci_hcd *), + + /* + * generic hardware linkage + */ + .irq = xhci_irq, + .flags = HCD_MEMORY | HCD_USB3 | HCD_SHARED, + + /* + * basic lifecycle operations + */ + .reset = xhci_pci_setup, + .start = xhci_run, +#ifdef CONFIG_PM + .pci_suspend = xhci_pci_suspend, + .pci_resume = xhci_pci_resume, +#endif + .stop = xhci_stop, + .shutdown = xhci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = xhci_urb_enqueue, + .urb_dequeue = xhci_urb_dequeue, + .alloc_dev = xhci_alloc_dev, + .free_dev = xhci_free_dev, + .alloc_streams = xhci_alloc_streams, + .free_streams = xhci_free_streams, + .add_endpoint = xhci_add_endpoint, + .drop_endpoint = xhci_drop_endpoint, + .endpoint_reset = xhci_endpoint_reset, + .check_bandwidth = xhci_check_bandwidth, + .reset_bandwidth = xhci_reset_bandwidth, + .address_device = xhci_address_device, + .update_hub_device = xhci_update_hub_device, + .reset_device = xhci_discover_or_reset_device, + + /* + * scheduling support + */ + .get_frame_number = xhci_get_frame, + + /* Root hub support */ + .hub_control = xhci_hub_control, + .hub_status_data = xhci_hub_status_data, + .bus_suspend = xhci_bus_suspend, + .bus_resume = xhci_bus_resume, + /* + * call back when device connected and addressed + */ + .update_device = xhci_update_device, + .set_usb2_hw_lpm = xhci_set_usb2_hardware_lpm, + .enable_usb3_lpm_timeout = xhci_enable_usb3_lpm_timeout, + .disable_usb3_lpm_timeout = xhci_disable_usb3_lpm_timeout, + .find_raw_port_number = xhci_find_raw_port_number, +}; + +/*-------------------------------------------------------------------------*/ + +/* PCI driver selection metadata; PCI hotplugging uses this */ +static const struct pci_device_id pci_ids[] = { { + /* handle any USB 3.0 xHCI controller */ + PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0), + .driver_data = (unsigned long) &xhci_pci_hc_driver, + }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver xhci_pci_driver = { + .name = (char *) hcd_name, + .id_table = pci_ids, + + .probe = xhci_pci_probe, + .remove = xhci_pci_remove, + /* suspend and resume implemented later */ + + .shutdown = usb_hcd_pci_shutdown, +#ifdef CONFIG_PM + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, +#endif +}; + +int __init xhci_register_pci(void) +{ + return pci_register_driver(&xhci_pci_driver); +} + +void xhci_unregister_pci(void) +{ + pci_unregister_driver(&xhci_pci_driver); +} diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c new file mode 100644 index 00000000..6e70ce97 --- /dev/null +++ b/drivers/usb/host/xhci-plat.c @@ -0,0 +1,206 @@ +/* + * xhci-plat.c - xHCI host controller driver platform Bus Glue. + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * Author: Sebastian Andrzej Siewior + * + * A lot of code borrowed from the Linux xHCI driver. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include + +#include "xhci.h" + +static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + /* + * As of now platform drivers don't provide MSI support so we ensure + * here that the generic code does not try to make a pci_dev from our + * dev struct in order to setup MSI + */ + xhci->quirks |= XHCI_PLAT; +} + +/* called during probe() after chip reset completes */ +static int xhci_plat_setup(struct usb_hcd *hcd) +{ + return xhci_gen_setup(hcd, xhci_plat_quirks); +} + +static const struct hc_driver xhci_plat_xhci_driver = { + .description = "xhci-hcd", + .product_desc = "xHCI Host Controller", + .hcd_priv_size = sizeof(struct xhci_hcd *), + + /* + * generic hardware linkage + */ + .irq = xhci_irq, + .flags = HCD_MEMORY | HCD_USB3 | HCD_SHARED, + + /* + * basic lifecycle operations + */ + .reset = xhci_plat_setup, + .start = xhci_run, + .stop = xhci_stop, + .shutdown = xhci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = xhci_urb_enqueue, + .urb_dequeue = xhci_urb_dequeue, + .alloc_dev = xhci_alloc_dev, + .free_dev = xhci_free_dev, + .alloc_streams = xhci_alloc_streams, + .free_streams = xhci_free_streams, + .add_endpoint = xhci_add_endpoint, + .drop_endpoint = xhci_drop_endpoint, + .endpoint_reset = xhci_endpoint_reset, + .check_bandwidth = xhci_check_bandwidth, + .reset_bandwidth = xhci_reset_bandwidth, + .address_device = xhci_address_device, + .update_hub_device = xhci_update_hub_device, + .reset_device = xhci_discover_or_reset_device, + + /* + * scheduling support + */ + .get_frame_number = xhci_get_frame, + + /* Root hub support */ + .hub_control = xhci_hub_control, + .hub_status_data = xhci_hub_status_data, + .bus_suspend = xhci_bus_suspend, + .bus_resume = xhci_bus_resume, +}; + +static int xhci_plat_probe(struct platform_device *pdev) +{ + const struct hc_driver *driver; + struct xhci_hcd *xhci; + struct resource *res; + struct usb_hcd *hcd; + int ret; + int irq; + + if (usb_disabled()) + return -ENODEV; + + driver = &xhci_plat_xhci_driver; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + driver->description)) { + dev_dbg(&pdev->dev, "controller already in use\n"); + ret = -EBUSY; + goto put_hcd; + } + + hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + ret = -EFAULT; + goto release_mem_region; + } + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) + goto unmap_registers; + + /* USB 2.0 roothub is stored in the platform_device now. */ + hcd = dev_get_drvdata(&pdev->dev); + xhci = hcd_to_xhci(hcd); + xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev, + dev_name(&pdev->dev), hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto dealloc_usb2_hcd; + } + + /* + * Set the xHCI pointer before xhci_plat_setup() (aka hcd_driver.reset) + * is called by usb_add_hcd(). + */ + *((struct xhci_hcd **) xhci->shared_hcd->hcd_priv) = xhci; + + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); + if (ret) + goto put_usb3_hcd; + + return 0; + +put_usb3_hcd: + usb_put_hcd(xhci->shared_hcd); + +dealloc_usb2_hcd: + usb_remove_hcd(hcd); + +unmap_registers: + iounmap(hcd->regs); + +release_mem_region: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + +put_hcd: + usb_put_hcd(hcd); + + return ret; +} + +static int xhci_plat_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + usb_remove_hcd(xhci->shared_hcd); + usb_put_hcd(xhci->shared_hcd); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + kfree(xhci); + + return 0; +} + +static struct platform_driver usb_xhci_driver = { + .probe = xhci_plat_probe, + .remove = xhci_plat_remove, + .driver = { + .name = "xhci-hcd", + }, +}; +MODULE_ALIAS("platform:xhci-hcd"); + +int xhci_register_plat(void) +{ + return platform_driver_register(&usb_xhci_driver); +} + +void xhci_unregister_plat(void) +{ + platform_driver_unregister(&usb_xhci_driver); +} diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c new file mode 100644 index 00000000..eb45ac84 --- /dev/null +++ b/drivers/usb/host/xhci-ring.c @@ -0,0 +1,4031 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Ring initialization rules: + * 1. Each segment is initialized to zero, except for link TRBs. + * 2. Ring cycle state = 0. This represents Producer Cycle State (PCS) or + * Consumer Cycle State (CCS), depending on ring function. + * 3. Enqueue pointer = dequeue pointer = address of first TRB in the segment. + * + * Ring behavior rules: + * 1. A ring is empty if enqueue == dequeue. This means there will always be at + * least one free TRB in the ring. This is useful if you want to turn that + * into a link TRB and expand the ring. + * 2. When incrementing an enqueue or dequeue pointer, if the next TRB is a + * link TRB, then load the pointer with the address in the link TRB. If the + * link TRB had its toggle bit set, you may need to update the ring cycle + * state (see cycle bit rules). You may have to do this multiple times + * until you reach a non-link TRB. + * 3. A ring is full if enqueue++ (for the definition of increment above) + * equals the dequeue pointer. + * + * Cycle bit rules: + * 1. When a consumer increments a dequeue pointer and encounters a toggle bit + * in a link TRB, it must toggle the ring cycle state. + * 2. When a producer increments an enqueue pointer and encounters a toggle bit + * in a link TRB, it must toggle the ring cycle state. + * + * Producer rules: + * 1. Check if ring is full before you enqueue. + * 2. Write the ring cycle state to the cycle bit in the TRB you're enqueuing. + * Update enqueue pointer between each write (which may update the ring + * cycle state). + * 3. Notify consumer. If SW is producer, it rings the doorbell for command + * and endpoint rings. If HC is the producer for the event ring, + * and it generates an interrupt according to interrupt modulation rules. + * + * Consumer rules: + * 1. Check if TRB belongs to you. If the cycle bit == your ring cycle state, + * the TRB is owned by the consumer. + * 2. Update dequeue pointer (which may update the ring cycle state) and + * continue processing TRBs until you reach a TRB which is not owned by you. + * 3. Notify the producer. SW is the consumer for the event ring, and it + * updates event ring dequeue pointer. HC is the consumer for the command and + * endpoint rings; it generates events on the event ring for these. + */ + +#include +#include +#include "xhci.h" + +static int handle_cmd_in_cmd_wait_list(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct xhci_event_cmd *event); + +/* + * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA + * address of the TRB. + */ +dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, + union xhci_trb *trb) +{ + unsigned long segment_offset; + + if (!seg || !trb || trb < seg->trbs) + return 0; + /* offset in TRBs */ + segment_offset = trb - seg->trbs; + if (segment_offset > TRBS_PER_SEGMENT) + return 0; + return seg->dma + (segment_offset * sizeof(*trb)); +} + +/* Does this link TRB point to the first segment in a ring, + * or was the previous TRB the last TRB on the last segment in the ERST? + */ +static bool last_trb_on_last_seg(struct xhci_hcd *xhci, struct xhci_ring *ring, + struct xhci_segment *seg, union xhci_trb *trb) +{ + if (ring == xhci->event_ring) + return (trb == &seg->trbs[TRBS_PER_SEGMENT]) && + (seg->next == xhci->event_ring->first_seg); + else + return le32_to_cpu(trb->link.control) & LINK_TOGGLE; +} + +/* Is this TRB a link TRB or was the last TRB the last TRB in this event ring + * segment? I.e. would the updated event TRB pointer step off the end of the + * event seg? + */ +static int last_trb(struct xhci_hcd *xhci, struct xhci_ring *ring, + struct xhci_segment *seg, union xhci_trb *trb) +{ + if (ring == xhci->event_ring) + return trb == &seg->trbs[TRBS_PER_SEGMENT]; + else + return TRB_TYPE_LINK_LE32(trb->link.control); +} + +static int enqueue_is_link_trb(struct xhci_ring *ring) +{ + struct xhci_link_trb *link = &ring->enqueue->link; + return TRB_TYPE_LINK_LE32(link->control); +} + +union xhci_trb *xhci_find_next_enqueue(struct xhci_ring *ring) +{ + /* Enqueue pointer can be left pointing to the link TRB, + * we must handle that + */ + if (TRB_TYPE_LINK_LE32(ring->enqueue->link.control)) + return ring->enq_seg->next->trbs; + return ring->enqueue; +} + +/* Updates trb to point to the next TRB in the ring, and updates seg if the next + * TRB is in a new segment. This does not skip over link TRBs, and it does not + * effect the ring dequeue or enqueue pointers. + */ +static void next_trb(struct xhci_hcd *xhci, + struct xhci_ring *ring, + struct xhci_segment **seg, + union xhci_trb **trb) +{ + if (last_trb(xhci, ring, *seg, *trb)) { + *seg = (*seg)->next; + *trb = ((*seg)->trbs); + } else { + (*trb)++; + } +} + +/* + * See Cycle bit rules. SW is the consumer for the event ring only. + * Don't make a ring full of link TRBs. That would be dumb and this would loop. + */ +static void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + unsigned long long addr; + + ring->deq_updates++; + + /* + * If this is not event ring, and the dequeue pointer + * is not on a link TRB, there is one more usable TRB + */ + if (ring->type != TYPE_EVENT && + !last_trb(xhci, ring, ring->deq_seg, ring->dequeue)) + ring->num_trbs_free++; + + do { + /* + * Update the dequeue pointer further if that was a link TRB or + * we're at the end of an event ring segment (which doesn't have + * link TRBS) + */ + if (last_trb(xhci, ring, ring->deq_seg, ring->dequeue)) { + if (ring->type == TYPE_EVENT && + last_trb_on_last_seg(xhci, ring, + ring->deq_seg, ring->dequeue)) { + ring->cycle_state = (ring->cycle_state ? 0 : 1); + } + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + } else { + ring->dequeue++; + } + } while (last_trb(xhci, ring, ring->deq_seg, ring->dequeue)); + + addr = (unsigned long long) xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue); +} + +/* + * See Cycle bit rules. SW is the consumer for the event ring only. + * Don't make a ring full of link TRBs. That would be dumb and this would loop. + * + * If we've just enqueued a TRB that is in the middle of a TD (meaning the + * chain bit is set), then set the chain bit in all the following link TRBs. + * If we've enqueued the last TRB in a TD, make sure the following link TRBs + * have their chain bit cleared (so that each Link TRB is a separate TD). + * + * Section 6.4.4.1 of the 0.95 spec says link TRBs cannot have the chain bit + * set, but other sections talk about dealing with the chain bit set. This was + * fixed in the 0.96 specification errata, but we have to assume that all 0.95 + * xHCI hardware can't handle the chain bit being cleared on a link TRB. + * + * @more_trbs_coming: Will you enqueue more TRBs before calling + * prepare_transfer()? + */ +static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, + bool more_trbs_coming) +{ + u32 chain; + union xhci_trb *next; + unsigned long long addr; + + chain = le32_to_cpu(ring->enqueue->generic.field[3]) & TRB_CHAIN; + /* If this is not event ring, there is one less usable TRB */ + if (ring->type != TYPE_EVENT && + !last_trb(xhci, ring, ring->enq_seg, ring->enqueue)) + ring->num_trbs_free--; + next = ++(ring->enqueue); + + ring->enq_updates++; + /* Update the dequeue pointer further if that was a link TRB or we're at + * the end of an event ring segment (which doesn't have link TRBS) + */ + while (last_trb(xhci, ring, ring->enq_seg, next)) { + if (ring->type != TYPE_EVENT) { + /* + * If the caller doesn't plan on enqueueing more + * TDs before ringing the doorbell, then we + * don't want to give the link TRB to the + * hardware just yet. We'll give the link TRB + * back in prepare_ring() just before we enqueue + * the TD at the top of the ring. + */ + if (!chain && !more_trbs_coming) + break; + + /* If we're not dealing with 0.95 hardware or + * isoc rings on AMD 0.96 host, + * carry over the chain bit of the previous TRB + * (which may mean the chain bit is cleared). + */ + if (!(ring->type == TYPE_ISOC && + (xhci->quirks & XHCI_AMD_0x96_HOST)) + && !xhci_link_trb_quirk(xhci)) { + next->link.control &= + cpu_to_le32(~TRB_CHAIN); + next->link.control |= + cpu_to_le32(chain); + } + /* Give this link TRB to the hardware */ + wmb(); + next->link.control ^= cpu_to_le32(TRB_CYCLE); + + /* Toggle the cycle bit after the last ring segment. */ + if (last_trb_on_last_seg(xhci, ring, ring->enq_seg, next)) { + ring->cycle_state = (ring->cycle_state ? 0 : 1); + } + } + ring->enq_seg = ring->enq_seg->next; + ring->enqueue = ring->enq_seg->trbs; + next = ring->enqueue; + } + addr = (unsigned long long) xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue); +} + +/* + * Check to see if there's room to enqueue num_trbs on the ring and make sure + * enqueue pointer will not advance into dequeue segment. See rules above. + */ +static inline int room_on_ring(struct xhci_hcd *xhci, struct xhci_ring *ring, + unsigned int num_trbs) +{ + int num_trbs_in_deq_seg; + + if (ring->num_trbs_free < num_trbs) + return 0; + + if (ring->type != TYPE_COMMAND && ring->type != TYPE_EVENT) { + num_trbs_in_deq_seg = ring->dequeue - ring->deq_seg->trbs; + if (ring->num_trbs_free < num_trbs + num_trbs_in_deq_seg) + return 0; + } + + return 1; +} + +/* Ring the host controller doorbell after placing a command on the ring */ +void xhci_ring_cmd_db(struct xhci_hcd *xhci) +{ + if (!(xhci->cmd_ring_state & CMD_RING_STATE_RUNNING)) + return; + + xhci_dbg(xhci, "// Ding dong!\n"); + xhci_writel(xhci, DB_VALUE_HOST, &xhci->dba->doorbell[0]); + /* Flush PCI posted writes */ + xhci_readl(xhci, &xhci->dba->doorbell[0]); +} + +static int xhci_abort_cmd_ring(struct xhci_hcd *xhci) +{ + u64 temp_64; + int ret; + + xhci_dbg(xhci, "Abort command ring\n"); + + if (!(xhci->cmd_ring_state & CMD_RING_STATE_RUNNING)) { + xhci_dbg(xhci, "The command ring isn't running, " + "Have the command ring been stopped?\n"); + return 0; + } + + temp_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + if (!(temp_64 & CMD_RING_RUNNING)) { + xhci_dbg(xhci, "Command ring had been stopped\n"); + return 0; + } + xhci->cmd_ring_state = CMD_RING_STATE_ABORTED; + xhci_write_64(xhci, temp_64 | CMD_RING_ABORT, + &xhci->op_regs->cmd_ring); + + /* Section 4.6.1.2 of xHCI 1.0 spec says software should + * time the completion od all xHCI commands, including + * the Command Abort operation. If software doesn't see + * CRR negated in a timely manner (e.g. longer than 5 + * seconds), then it should assume that the there are + * larger problems with the xHC and assert HCRST. + */ + ret = xhci_handshake(xhci, &xhci->op_regs->cmd_ring, + CMD_RING_RUNNING, 0, 5 * 1000 * 1000); + if (ret < 0) { + xhci_err(xhci, "Stopped the command ring failed, " + "maybe the host is dead\n"); + xhci->xhc_state |= XHCI_STATE_DYING; + xhci_quiesce(xhci); + xhci_halt(xhci); + return -ESHUTDOWN; + } + + return 0; +} + +static int xhci_queue_cd(struct xhci_hcd *xhci, + struct xhci_command *command, + union xhci_trb *cmd_trb) +{ + struct xhci_cd *cd; + cd = kzalloc(sizeof(struct xhci_cd), GFP_ATOMIC); + if (!cd) + return -ENOMEM; + INIT_LIST_HEAD(&cd->cancel_cmd_list); + + cd->command = command; + cd->cmd_trb = cmd_trb; + list_add_tail(&cd->cancel_cmd_list, &xhci->cancel_cmd_list); + + return 0; +} + +/* + * Cancel the command which has issue. + * + * Some commands may hang due to waiting for acknowledgement from + * usb device. It is outside of the xHC's ability to control and + * will cause the command ring is blocked. When it occurs software + * should intervene to recover the command ring. + * See Section 4.6.1.1 and 4.6.1.2 + */ +int xhci_cancel_cmd(struct xhci_hcd *xhci, struct xhci_command *command, + union xhci_trb *cmd_trb) +{ + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&xhci->lock, flags); + + if (xhci->xhc_state & XHCI_STATE_DYING) { + xhci_warn(xhci, "Abort the command ring," + " but the xHCI is dead.\n"); + retval = -ESHUTDOWN; + goto fail; + } + + /* queue the cmd desriptor to cancel_cmd_list */ + retval = xhci_queue_cd(xhci, command, cmd_trb); + if (retval) { + xhci_warn(xhci, "Queuing command descriptor failed.\n"); + goto fail; + } + + /* abort command ring */ + retval = xhci_abort_cmd_ring(xhci); + if (retval) { + xhci_err(xhci, "Abort command ring failed\n"); + if (unlikely(retval == -ESHUTDOWN)) { + spin_unlock_irqrestore(&xhci->lock, flags); + usb_hc_died(xhci_to_hcd(xhci)->primary_hcd); + xhci_dbg(xhci, "xHCI host controller is dead.\n"); + return retval; + } + } + +fail: + spin_unlock_irqrestore(&xhci->lock, flags); + return retval; +} + +void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index, + unsigned int stream_id) +{ + __le32 __iomem *db_addr = &xhci->dba->doorbell[slot_id]; + struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index]; + unsigned int ep_state = ep->ep_state; + + /* Don't ring the doorbell for this endpoint if there are pending + * cancellations because we don't want to interrupt processing. + * We don't want to restart any stream rings if there's a set dequeue + * pointer command pending because the device can choose to start any + * stream once the endpoint is on the HW schedule. + * FIXME - check all the stream rings for pending cancellations. + */ + if ((ep_state & EP_HALT_PENDING) || (ep_state & SET_DEQ_PENDING) || + (ep_state & EP_HALTED)) + return; + xhci_writel(xhci, DB_VALUE(ep_index, stream_id), db_addr); + /* The CPU has better things to do at this point than wait for a + * write-posting flush. It'll get there soon enough. + */ +} + +/* Ring the doorbell for any rings with pending URBs */ +static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index) +{ + unsigned int stream_id; + struct xhci_virt_ep *ep; + + ep = &xhci->devs[slot_id]->eps[ep_index]; + + /* A ring has pending URBs if its TD list is not empty */ + if (!(ep->ep_state & EP_HAS_STREAMS)) { + if (ep->ring && !(list_empty(&ep->ring->td_list))) + xhci_ring_ep_doorbell(xhci, slot_id, ep_index, 0); + return; + } + + for (stream_id = 1; stream_id < ep->stream_info->num_streams; + stream_id++) { + struct xhci_stream_info *stream_info = ep->stream_info; + if (!list_empty(&stream_info->stream_rings[stream_id]->td_list)) + xhci_ring_ep_doorbell(xhci, slot_id, ep_index, + stream_id); + } +} + +/* + * Find the segment that trb is in. Start searching in start_seg. + * If we must move past a segment that has a link TRB with a toggle cycle state + * bit set, then we will toggle the value pointed at by cycle_state. + */ +static struct xhci_segment *find_trb_seg( + struct xhci_segment *start_seg, + union xhci_trb *trb, int *cycle_state) +{ + struct xhci_segment *cur_seg = start_seg; + struct xhci_generic_trb *generic_trb; + + while (cur_seg->trbs > trb || + &cur_seg->trbs[TRBS_PER_SEGMENT - 1] < trb) { + generic_trb = &cur_seg->trbs[TRBS_PER_SEGMENT - 1].generic; + if (generic_trb->field[3] & cpu_to_le32(LINK_TOGGLE)) + *cycle_state ^= 0x1; + cur_seg = cur_seg->next; + if (cur_seg == start_seg) + /* Looped over the entire list. Oops! */ + return NULL; + } + return cur_seg; +} + + +static struct xhci_ring *xhci_triad_to_transfer_ring(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + unsigned int stream_id) +{ + struct xhci_virt_ep *ep; + + ep = &xhci->devs[slot_id]->eps[ep_index]; + /* Common case: no streams */ + if (!(ep->ep_state & EP_HAS_STREAMS)) + return ep->ring; + + if (stream_id == 0) { + xhci_warn(xhci, + "WARN: Slot ID %u, ep index %u has streams, " + "but URB has no stream ID.\n", + slot_id, ep_index); + return NULL; + } + + if (stream_id < ep->stream_info->num_streams) + return ep->stream_info->stream_rings[stream_id]; + + xhci_warn(xhci, + "WARN: Slot ID %u, ep index %u has " + "stream IDs 1 to %u allocated, " + "but stream ID %u is requested.\n", + slot_id, ep_index, + ep->stream_info->num_streams - 1, + stream_id); + return NULL; +} + +/* Get the right ring for the given URB. + * If the endpoint supports streams, boundary check the URB's stream ID. + * If the endpoint doesn't support streams, return the singular endpoint ring. + */ +static struct xhci_ring *xhci_urb_to_transfer_ring(struct xhci_hcd *xhci, + struct urb *urb) +{ + return xhci_triad_to_transfer_ring(xhci, urb->dev->slot_id, + xhci_get_endpoint_index(&urb->ep->desc), urb->stream_id); +} + +/* + * Move the xHC's endpoint ring dequeue pointer past cur_td. + * Record the new state of the xHC's endpoint ring dequeue segment, + * dequeue pointer, and new consumer cycle state in state. + * Update our internal representation of the ring's dequeue pointer. + * + * We do this in three jumps: + * - First we update our new ring state to be the same as when the xHC stopped. + * - Then we traverse the ring to find the segment that contains + * the last TRB in the TD. We toggle the xHC's new cycle state when we pass + * any link TRBs with the toggle cycle bit set. + * - Finally we move the dequeue state one TRB further, toggling the cycle bit + * if we've moved it past a link TRB with the toggle cycle bit set. + * + * Some of the uses of xhci_generic_trb are grotty, but if they're done + * with correct __le32 accesses they should work fine. Only users of this are + * in here. + */ +void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + unsigned int stream_id, struct xhci_td *cur_td, + struct xhci_dequeue_state *state) +{ + struct xhci_virt_device *dev = xhci->devs[slot_id]; + struct xhci_ring *ep_ring; + struct xhci_generic_trb *trb; + struct xhci_ep_ctx *ep_ctx; + dma_addr_t addr; + + ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, + ep_index, stream_id); + if (!ep_ring) { + xhci_warn(xhci, "WARN can't find new dequeue state " + "for invalid stream ID %u.\n", + stream_id); + return; + } + state->new_cycle_state = 0; + xhci_dbg(xhci, "Finding segment containing stopped TRB.\n"); + state->new_deq_seg = find_trb_seg(cur_td->start_seg, + dev->eps[ep_index].stopped_trb, + &state->new_cycle_state); + if (!state->new_deq_seg) { + WARN_ON(1); + return; + } + + /* Dig out the cycle state saved by the xHC during the stop ep cmd */ + xhci_dbg(xhci, "Finding endpoint context\n"); + ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); + state->new_cycle_state = 0x1 & le64_to_cpu(ep_ctx->deq); + + state->new_deq_ptr = cur_td->last_trb; + xhci_dbg(xhci, "Finding segment containing last TRB in TD.\n"); + state->new_deq_seg = find_trb_seg(state->new_deq_seg, + state->new_deq_ptr, + &state->new_cycle_state); + if (!state->new_deq_seg) { + WARN_ON(1); + return; + } + + trb = &state->new_deq_ptr->generic; + if (TRB_TYPE_LINK_LE32(trb->field[3]) && + (trb->field[3] & cpu_to_le32(LINK_TOGGLE))) + state->new_cycle_state ^= 0x1; + next_trb(xhci, ep_ring, &state->new_deq_seg, &state->new_deq_ptr); + + /* + * If there is only one segment in a ring, find_trb_seg()'s while loop + * will not run, and it will return before it has a chance to see if it + * needs to toggle the cycle bit. It can't tell if the stalled transfer + * ended just before the link TRB on a one-segment ring, or if the TD + * wrapped around the top of the ring, because it doesn't have the TD in + * question. Look for the one-segment case where stalled TRB's address + * is greater than the new dequeue pointer address. + */ + if (ep_ring->first_seg == ep_ring->first_seg->next && + state->new_deq_ptr < dev->eps[ep_index].stopped_trb) + state->new_cycle_state ^= 0x1; + xhci_dbg(xhci, "Cycle state = 0x%x\n", state->new_cycle_state); + + /* Don't update the ring cycle state for the producer (us). */ + xhci_dbg(xhci, "New dequeue segment = %p (virtual)\n", + state->new_deq_seg); + addr = xhci_trb_virt_to_dma(state->new_deq_seg, state->new_deq_ptr); + xhci_dbg(xhci, "New dequeue pointer = 0x%llx (DMA)\n", + (unsigned long long) addr); +} + +/* flip_cycle means flip the cycle bit of all but the first and last TRB. + * (The last TRB actually points to the ring enqueue pointer, which is not part + * of this TD.) This is used to remove partially enqueued isoc TDs from a ring. + */ +static void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, + struct xhci_td *cur_td, bool flip_cycle) +{ + struct xhci_segment *cur_seg; + union xhci_trb *cur_trb; + + for (cur_seg = cur_td->start_seg, cur_trb = cur_td->first_trb; + true; + next_trb(xhci, ep_ring, &cur_seg, &cur_trb)) { + if (TRB_TYPE_LINK_LE32(cur_trb->generic.field[3])) { + /* Unchain any chained Link TRBs, but + * leave the pointers intact. + */ + cur_trb->generic.field[3] &= cpu_to_le32(~TRB_CHAIN); + /* Flip the cycle bit (link TRBs can't be the first + * or last TRB). + */ + if (flip_cycle) + cur_trb->generic.field[3] ^= + cpu_to_le32(TRB_CYCLE); + xhci_dbg(xhci, "Cancel (unchain) link TRB\n"); + xhci_dbg(xhci, "Address = %p (0x%llx dma); " + "in seg %p (0x%llx dma)\n", + cur_trb, + (unsigned long long)xhci_trb_virt_to_dma(cur_seg, cur_trb), + cur_seg, + (unsigned long long)cur_seg->dma); + } else { + cur_trb->generic.field[0] = 0; + cur_trb->generic.field[1] = 0; + cur_trb->generic.field[2] = 0; + /* Preserve only the cycle bit of this TRB */ + cur_trb->generic.field[3] &= cpu_to_le32(TRB_CYCLE); + /* Flip the cycle bit except on the first or last TRB */ + if (flip_cycle && cur_trb != cur_td->first_trb && + cur_trb != cur_td->last_trb) + cur_trb->generic.field[3] ^= + cpu_to_le32(TRB_CYCLE); + cur_trb->generic.field[3] |= cpu_to_le32( + TRB_TYPE(TRB_TR_NOOP)); + xhci_dbg(xhci, "TRB to noop at offset 0x%llx\n", + (unsigned long long) + xhci_trb_virt_to_dma(cur_seg, cur_trb)); + } + if (cur_trb == cur_td->last_trb) + break; + } +} + +static int queue_set_tr_deq(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index, unsigned int stream_id, + struct xhci_segment *deq_seg, + union xhci_trb *deq_ptr, u32 cycle_state); + +void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + unsigned int stream_id, + struct xhci_dequeue_state *deq_state) +{ + struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index]; + + xhci_dbg(xhci, "Set TR Deq Ptr cmd, new deq seg = %p (0x%llx dma), " + "new deq ptr = %p (0x%llx dma), new cycle = %u\n", + deq_state->new_deq_seg, + (unsigned long long)deq_state->new_deq_seg->dma, + deq_state->new_deq_ptr, + (unsigned long long)xhci_trb_virt_to_dma(deq_state->new_deq_seg, deq_state->new_deq_ptr), + deq_state->new_cycle_state); + queue_set_tr_deq(xhci, slot_id, ep_index, stream_id, + deq_state->new_deq_seg, + deq_state->new_deq_ptr, + (u32) deq_state->new_cycle_state); + /* Stop the TD queueing code from ringing the doorbell until + * this command completes. The HC won't set the dequeue pointer + * if the ring is running, and ringing the doorbell starts the + * ring running. + */ + ep->ep_state |= SET_DEQ_PENDING; +} + +static void xhci_stop_watchdog_timer_in_irq(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep) +{ + ep->ep_state &= ~EP_HALT_PENDING; + /* Can't del_timer_sync in interrupt, so we attempt to cancel. If the + * timer is running on another CPU, we don't decrement stop_cmds_pending + * (since we didn't successfully stop the watchdog timer). + */ + if (del_timer(&ep->stop_cmd_timer)) + ep->stop_cmds_pending--; +} + +/* Must be called with xhci->lock held in interrupt context */ +static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci, + struct xhci_td *cur_td, int status, char *adjective) +{ + struct usb_hcd *hcd; + struct urb *urb; + struct urb_priv *urb_priv; + + urb = cur_td->urb; + urb_priv = urb->hcpriv; + urb_priv->td_cnt++; + hcd = bus_to_hcd(urb->dev->bus); + + /* Only giveback urb when this is the last td in urb */ + if (urb_priv->td_cnt == urb_priv->length) { + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs--; + if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) { + if (xhci->quirks & XHCI_AMD_PLL_FIX) + usb_amd_quirk_pll_enable(); + } + } + usb_hcd_unlink_urb_from_ep(hcd, urb); + + spin_unlock(&xhci->lock); + usb_hcd_giveback_urb(hcd, urb, status); + xhci_urb_free_priv(xhci, urb_priv); + spin_lock(&xhci->lock); + } +} + +/* + * When we get a command completion for a Stop Endpoint Command, we need to + * unlink any cancelled TDs from the ring. There are two ways to do that: + * + * 1. If the HW was in the middle of processing the TD that needs to be + * cancelled, then we must move the ring's dequeue pointer past the last TRB + * in the TD with a Set Dequeue Pointer Command. + * 2. Otherwise, we turn all the TRBs in the TD into No-op TRBs (with the chain + * bit cleared) so that the HW will skip over them. + */ +static void handle_stopped_endpoint(struct xhci_hcd *xhci, + union xhci_trb *trb, struct xhci_event_cmd *event) +{ + unsigned int slot_id; + unsigned int ep_index; + struct xhci_virt_device *virt_dev; + struct xhci_ring *ep_ring; + struct xhci_virt_ep *ep; + struct list_head *entry; + struct xhci_td *cur_td = NULL; + struct xhci_td *last_unlinked_td; + + struct xhci_dequeue_state deq_state; + + if (unlikely(TRB_TO_SUSPEND_PORT( + le32_to_cpu(xhci->cmd_ring->dequeue->generic.field[3])))) { + slot_id = TRB_TO_SLOT_ID( + le32_to_cpu(xhci->cmd_ring->dequeue->generic.field[3])); + virt_dev = xhci->devs[slot_id]; + if (virt_dev) + handle_cmd_in_cmd_wait_list(xhci, virt_dev, + event); + else + xhci_warn(xhci, "Stop endpoint command " + "completion for disabled slot %u\n", + slot_id); + return; + } + + memset(&deq_state, 0, sizeof(deq_state)); + slot_id = TRB_TO_SLOT_ID(le32_to_cpu(trb->generic.field[3])); + ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); + ep = &xhci->devs[slot_id]->eps[ep_index]; + + if (list_empty(&ep->cancelled_td_list)) { + xhci_stop_watchdog_timer_in_irq(xhci, ep); + ep->stopped_td = NULL; + ep->stopped_trb = NULL; + ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + return; + } + + /* Fix up the ep ring first, so HW stops executing cancelled TDs. + * We have the xHCI lock, so nothing can modify this list until we drop + * it. We're also in the event handler, so we can't get re-interrupted + * if another Stop Endpoint command completes + */ + list_for_each(entry, &ep->cancelled_td_list) { + cur_td = list_entry(entry, struct xhci_td, cancelled_td_list); + xhci_dbg(xhci, "Removing canceled TD starting at 0x%llx (dma).\n", + (unsigned long long)xhci_trb_virt_to_dma( + cur_td->start_seg, cur_td->first_trb)); + ep_ring = xhci_urb_to_transfer_ring(xhci, cur_td->urb); + if (!ep_ring) { + /* This shouldn't happen unless a driver is mucking + * with the stream ID after submission. This will + * leave the TD on the hardware ring, and the hardware + * will try to execute it, and may access a buffer + * that has already been freed. In the best case, the + * hardware will execute it, and the event handler will + * ignore the completion event for that TD, since it was + * removed from the td_list for that endpoint. In + * short, don't muck with the stream ID after + * submission. + */ + xhci_warn(xhci, "WARN Cancelled URB %p " + "has invalid stream ID %u.\n", + cur_td->urb, + cur_td->urb->stream_id); + goto remove_finished_td; + } + /* + * If we stopped on the TD we need to cancel, then we have to + * move the xHC endpoint ring dequeue pointer past this TD. + */ + if (cur_td == ep->stopped_td) + xhci_find_new_dequeue_state(xhci, slot_id, ep_index, + cur_td->urb->stream_id, + cur_td, &deq_state); + else + td_to_noop(xhci, ep_ring, cur_td, false); +remove_finished_td: + /* + * The event handler won't see a completion for this TD anymore, + * so remove it from the endpoint ring's TD list. Keep it in + * the cancelled TD list for URB completion later. + */ + list_del_init(&cur_td->td_list); + } + last_unlinked_td = cur_td; + xhci_stop_watchdog_timer_in_irq(xhci, ep); + + /* If necessary, queue a Set Transfer Ring Dequeue Pointer command */ + if (deq_state.new_deq_ptr && deq_state.new_deq_seg) { + xhci_queue_new_dequeue_state(xhci, + slot_id, ep_index, + ep->stopped_td->urb->stream_id, + &deq_state); + xhci_ring_cmd_db(xhci); + } else { + /* Otherwise ring the doorbell(s) to restart queued transfers */ + ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + /* Clear stopped_td and stopped_trb if endpoint is not halted */ + if (!(ep->ep_state & EP_HALTED)) { + ep->stopped_td = NULL; + ep->stopped_trb = NULL; + } + + /* + * Drop the lock and complete the URBs in the cancelled TD list. + * New TDs to be cancelled might be added to the end of the list before + * we can complete all the URBs for the TDs we already unlinked. + * So stop when we've completed the URB for the last TD we unlinked. + */ + do { + cur_td = list_entry(ep->cancelled_td_list.next, + struct xhci_td, cancelled_td_list); + list_del_init(&cur_td->cancelled_td_list); + + /* Clean up the cancelled URB */ + /* Doesn't matter what we pass for status, since the core will + * just overwrite it (because the URB has been unlinked). + */ + xhci_giveback_urb_in_irq(xhci, cur_td, 0, "cancelled"); + + /* Stop processing the cancelled list if the watchdog timer is + * running. + */ + if (xhci->xhc_state & XHCI_STATE_DYING) + return; + } while (cur_td != last_unlinked_td); + + /* Return to the event handler with xhci->lock re-acquired */ +} + +/* Watchdog timer function for when a stop endpoint command fails to complete. + * In this case, we assume the host controller is broken or dying or dead. The + * host may still be completing some other events, so we have to be careful to + * let the event ring handler and the URB dequeueing/enqueueing functions know + * through xhci->state. + * + * The timer may also fire if the host takes a very long time to respond to the + * command, and the stop endpoint command completion handler cannot delete the + * timer before the timer function is called. Another endpoint cancellation may + * sneak in before the timer function can grab the lock, and that may queue + * another stop endpoint command and add the timer back. So we cannot use a + * simple flag to say whether there is a pending stop endpoint command for a + * particular endpoint. + * + * Instead we use a combination of that flag and a counter for the number of + * pending stop endpoint commands. If the timer is the tail end of the last + * stop endpoint command, and the endpoint's command is still pending, we assume + * the host is dying. + */ +void xhci_stop_endpoint_command_watchdog(unsigned long arg) +{ + struct xhci_hcd *xhci; + struct xhci_virt_ep *ep; + struct xhci_virt_ep *temp_ep; + struct xhci_ring *ring; + struct xhci_td *cur_td; + int ret, i, j; + unsigned long flags; + + ep = (struct xhci_virt_ep *) arg; + xhci = ep->xhci; + + spin_lock_irqsave(&xhci->lock, flags); + + ep->stop_cmds_pending--; + if (xhci->xhc_state & XHCI_STATE_DYING) { + xhci_dbg(xhci, "Stop EP timer ran, but another timer marked " + "xHCI as DYING, exiting.\n"); + spin_unlock_irqrestore(&xhci->lock, flags); + return; + } + if (!(ep->stop_cmds_pending == 0 && (ep->ep_state & EP_HALT_PENDING))) { + xhci_dbg(xhci, "Stop EP timer ran, but no command pending, " + "exiting.\n"); + spin_unlock_irqrestore(&xhci->lock, flags); + return; + } + + xhci_warn(xhci, "xHCI host not responding to stop endpoint command.\n"); + xhci_warn(xhci, "Assuming host is dying, halting host.\n"); + /* Oops, HC is dead or dying or at least not responding to the stop + * endpoint command. + */ + xhci->xhc_state |= XHCI_STATE_DYING; + /* Disable interrupts from the host controller and start halting it */ + xhci_quiesce(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + ret = xhci_halt(xhci); + + spin_lock_irqsave(&xhci->lock, flags); + if (ret < 0) { + /* This is bad; the host is not responding to commands and it's + * not allowing itself to be halted. At least interrupts are + * disabled. If we call usb_hc_died(), it will attempt to + * disconnect all device drivers under this host. Those + * disconnect() methods will wait for all URBs to be unlinked, + * so we must complete them. + */ + xhci_warn(xhci, "Non-responsive xHCI host is not halting.\n"); + xhci_warn(xhci, "Completing active URBs anyway.\n"); + /* We could turn all TDs on the rings to no-ops. This won't + * help if the host has cached part of the ring, and is slow if + * we want to preserve the cycle bit. Skip it and hope the host + * doesn't touch the memory. + */ + } + for (i = 0; i < MAX_HC_SLOTS; i++) { + if (!xhci->devs[i]) + continue; + for (j = 0; j < 31; j++) { + temp_ep = &xhci->devs[i]->eps[j]; + ring = temp_ep->ring; + if (!ring) + continue; + xhci_dbg(xhci, "Killing URBs for slot ID %u, " + "ep index %u\n", i, j); + while (!list_empty(&ring->td_list)) { + cur_td = list_first_entry(&ring->td_list, + struct xhci_td, + td_list); + list_del_init(&cur_td->td_list); + if (!list_empty(&cur_td->cancelled_td_list)) + list_del_init(&cur_td->cancelled_td_list); + xhci_giveback_urb_in_irq(xhci, cur_td, + -ESHUTDOWN, "killed"); + } + while (!list_empty(&temp_ep->cancelled_td_list)) { + cur_td = list_first_entry( + &temp_ep->cancelled_td_list, + struct xhci_td, + cancelled_td_list); + list_del_init(&cur_td->cancelled_td_list); + xhci_giveback_urb_in_irq(xhci, cur_td, + -ESHUTDOWN, "killed"); + } + } + } + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "Calling usb_hc_died()\n"); + usb_hc_died(xhci_to_hcd(xhci)->primary_hcd); + xhci_dbg(xhci, "xHCI host controller is dead.\n"); +} + + +static void update_ring_for_set_deq_completion(struct xhci_hcd *xhci, + struct xhci_virt_device *dev, + struct xhci_ring *ep_ring, + unsigned int ep_index) +{ + union xhci_trb *dequeue_temp; + int num_trbs_free_temp; + bool revert = false; + + num_trbs_free_temp = ep_ring->num_trbs_free; + dequeue_temp = ep_ring->dequeue; + + /* If we get two back-to-back stalls, and the first stalled transfer + * ends just before a link TRB, the dequeue pointer will be left on + * the link TRB by the code in the while loop. So we have to update + * the dequeue pointer one segment further, or we'll jump off + * the segment into la-la-land. + */ + if (last_trb(xhci, ep_ring, ep_ring->deq_seg, ep_ring->dequeue)) { + ep_ring->deq_seg = ep_ring->deq_seg->next; + ep_ring->dequeue = ep_ring->deq_seg->trbs; + } + + while (ep_ring->dequeue != dev->eps[ep_index].queued_deq_ptr) { + /* We have more usable TRBs */ + ep_ring->num_trbs_free++; + ep_ring->dequeue++; + if (last_trb(xhci, ep_ring, ep_ring->deq_seg, + ep_ring->dequeue)) { + if (ep_ring->dequeue == + dev->eps[ep_index].queued_deq_ptr) + break; + ep_ring->deq_seg = ep_ring->deq_seg->next; + ep_ring->dequeue = ep_ring->deq_seg->trbs; + } + if (ep_ring->dequeue == dequeue_temp) { + revert = true; + break; + } + } + + if (revert) { + xhci_dbg(xhci, "Unable to find new dequeue pointer\n"); + ep_ring->num_trbs_free = num_trbs_free_temp; + } +} + +/* + * When we get a completion for a Set Transfer Ring Dequeue Pointer command, + * we need to clear the set deq pending flag in the endpoint ring state, so that + * the TD queueing code can ring the doorbell again. We also need to ring the + * endpoint doorbell to restart the ring, but only if there aren't more + * cancellations pending. + */ +static void handle_set_deq_completion(struct xhci_hcd *xhci, + struct xhci_event_cmd *event, + union xhci_trb *trb) +{ + unsigned int slot_id; + unsigned int ep_index; + unsigned int stream_id; + struct xhci_ring *ep_ring; + struct xhci_virt_device *dev; + struct xhci_ep_ctx *ep_ctx; + struct xhci_slot_ctx *slot_ctx; + + slot_id = TRB_TO_SLOT_ID(le32_to_cpu(trb->generic.field[3])); + ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); + stream_id = TRB_TO_STREAM_ID(le32_to_cpu(trb->generic.field[2])); + dev = xhci->devs[slot_id]; + + ep_ring = xhci_stream_id_to_ring(dev, ep_index, stream_id); + if (!ep_ring) { + xhci_warn(xhci, "WARN Set TR deq ptr command for " + "freed stream ID %u\n", + stream_id); + /* XXX: Harmless??? */ + dev->eps[ep_index].ep_state &= ~SET_DEQ_PENDING; + return; + } + + ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); + slot_ctx = xhci_get_slot_ctx(xhci, dev->out_ctx); + + if (GET_COMP_CODE(le32_to_cpu(event->status)) != COMP_SUCCESS) { + unsigned int ep_state; + unsigned int slot_state; + + switch (GET_COMP_CODE(le32_to_cpu(event->status))) { + case COMP_TRB_ERR: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd invalid because " + "of stream ID configuration\n"); + break; + case COMP_CTX_STATE: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed due " + "to incorrect slot or ep state.\n"); + ep_state = le32_to_cpu(ep_ctx->ep_info); + ep_state &= EP_STATE_MASK; + slot_state = le32_to_cpu(slot_ctx->dev_state); + slot_state = GET_SLOT_STATE(slot_state); + xhci_dbg(xhci, "Slot state = %u, EP state = %u\n", + slot_state, ep_state); + break; + case COMP_EBADSLT: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed because " + "slot %u was not enabled.\n", slot_id); + break; + default: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd with unknown " + "completion code of %u.\n", + GET_COMP_CODE(le32_to_cpu(event->status))); + break; + } + /* OK what do we do now? The endpoint state is hosed, and we + * should never get to this point if the synchronization between + * queueing, and endpoint state are correct. This might happen + * if the device gets disconnected after we've finished + * cancelling URBs, which might not be an error... + */ + } else { + xhci_dbg(xhci, "Successful Set TR Deq Ptr cmd, deq = @%08llx\n", + le64_to_cpu(ep_ctx->deq)); + if (xhci_trb_virt_to_dma(dev->eps[ep_index].queued_deq_seg, + dev->eps[ep_index].queued_deq_ptr) == + (le64_to_cpu(ep_ctx->deq) & ~(EP_CTX_CYCLE_MASK))) { + /* Update the ring's dequeue segment and dequeue pointer + * to reflect the new position. + */ + update_ring_for_set_deq_completion(xhci, dev, + ep_ring, ep_index); + } else { + xhci_warn(xhci, "Mismatch between completed Set TR Deq " + "Ptr command & xHCI internal state.\n"); + xhci_warn(xhci, "ep deq seg = %p, deq ptr = %p\n", + dev->eps[ep_index].queued_deq_seg, + dev->eps[ep_index].queued_deq_ptr); + } + } + + dev->eps[ep_index].ep_state &= ~SET_DEQ_PENDING; + dev->eps[ep_index].queued_deq_seg = NULL; + dev->eps[ep_index].queued_deq_ptr = NULL; + /* Restart any rings with pending URBs */ + ring_doorbell_for_active_rings(xhci, slot_id, ep_index); +} + +static void handle_reset_ep_completion(struct xhci_hcd *xhci, + struct xhci_event_cmd *event, + union xhci_trb *trb) +{ + int slot_id; + unsigned int ep_index; + + slot_id = TRB_TO_SLOT_ID(le32_to_cpu(trb->generic.field[3])); + ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); + /* This command will only fail if the endpoint wasn't halted, + * but we don't care. + */ + xhci_dbg(xhci, "Ignoring reset ep completion code of %u\n", + GET_COMP_CODE(le32_to_cpu(event->status))); + + /* HW with the reset endpoint quirk needs to have a configure endpoint + * command complete before the endpoint can be used. Queue that here + * because the HW can't handle two commands being queued in a row. + */ + if (xhci->quirks & XHCI_RESET_EP_QUIRK) { + xhci_dbg(xhci, "Queueing configure endpoint command\n"); + xhci_queue_configure_endpoint(xhci, + xhci->devs[slot_id]->in_ctx->dma, slot_id, + false); + xhci_ring_cmd_db(xhci); + } else { + /* Clear our internal halted state */ + xhci->devs[slot_id]->eps[ep_index].ep_state &= ~EP_HALTED; + } +} + +/* Complete the command and detele it from the devcie's command queue. + */ +static void xhci_complete_cmd_in_cmd_wait_list(struct xhci_hcd *xhci, + struct xhci_command *command, u32 status) +{ + command->status = status; + list_del(&command->cmd_list); + if (command->completion) + complete(command->completion); + else + xhci_free_command(xhci, command); +} + + +/* Check to see if a command in the device's command queue matches this one. + * Signal the completion or free the command, and return 1. Return 0 if the + * completed command isn't at the head of the command list. + */ +static int handle_cmd_in_cmd_wait_list(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct xhci_event_cmd *event) +{ + struct xhci_command *command; + + if (list_empty(&virt_dev->cmd_list)) + return 0; + + command = list_entry(virt_dev->cmd_list.next, + struct xhci_command, cmd_list); + if (xhci->cmd_ring->dequeue != command->command_trb) + return 0; + + xhci_complete_cmd_in_cmd_wait_list(xhci, command, + GET_COMP_CODE(le32_to_cpu(event->status))); + return 1; +} + +/* + * Finding the command trb need to be cancelled and modifying it to + * NO OP command. And if the command is in device's command wait + * list, finishing and freeing it. + * + * If we can't find the command trb, we think it had already been + * executed. + */ +static void xhci_cmd_to_noop(struct xhci_hcd *xhci, struct xhci_cd *cur_cd) +{ + struct xhci_segment *cur_seg; + union xhci_trb *cmd_trb; + u32 cycle_state; + + if (xhci->cmd_ring->dequeue == xhci->cmd_ring->enqueue) + return; + + /* find the current segment of command ring */ + cur_seg = find_trb_seg(xhci->cmd_ring->first_seg, + xhci->cmd_ring->dequeue, &cycle_state); + + if (!cur_seg) { + xhci_warn(xhci, "Command ring mismatch, dequeue = %p %llx (dma)\n", + xhci->cmd_ring->dequeue, + (unsigned long long) + xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, + xhci->cmd_ring->dequeue)); + xhci_debug_ring(xhci, xhci->cmd_ring); + xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring); + return; + } + + /* find the command trb matched by cd from command ring */ + for (cmd_trb = xhci->cmd_ring->dequeue; + cmd_trb != xhci->cmd_ring->enqueue; + next_trb(xhci, xhci->cmd_ring, &cur_seg, &cmd_trb)) { + /* If the trb is link trb, continue */ + if (TRB_TYPE_LINK_LE32(cmd_trb->generic.field[3])) + continue; + + if (cur_cd->cmd_trb == cmd_trb) { + + /* If the command in device's command list, we should + * finish it and free the command structure. + */ + if (cur_cd->command) + xhci_complete_cmd_in_cmd_wait_list(xhci, + cur_cd->command, COMP_CMD_STOP); + + /* get cycle state from the origin command trb */ + cycle_state = le32_to_cpu(cmd_trb->generic.field[3]) + & TRB_CYCLE; + + /* modify the command trb to NO OP command */ + cmd_trb->generic.field[0] = 0; + cmd_trb->generic.field[1] = 0; + cmd_trb->generic.field[2] = 0; + cmd_trb->generic.field[3] = cpu_to_le32( + TRB_TYPE(TRB_CMD_NOOP) | cycle_state); + break; + } + } +} + +static void xhci_cancel_cmd_in_cd_list(struct xhci_hcd *xhci) +{ + struct xhci_cd *cur_cd, *next_cd; + + if (list_empty(&xhci->cancel_cmd_list)) + return; + + list_for_each_entry_safe(cur_cd, next_cd, + &xhci->cancel_cmd_list, cancel_cmd_list) { + xhci_cmd_to_noop(xhci, cur_cd); + list_del(&cur_cd->cancel_cmd_list); + kfree(cur_cd); + } +} + +/* + * traversing the cancel_cmd_list. If the command descriptor according + * to cmd_trb is found, the function free it and return 1, otherwise + * return 0. + */ +static int xhci_search_cmd_trb_in_cd_list(struct xhci_hcd *xhci, + union xhci_trb *cmd_trb) +{ + struct xhci_cd *cur_cd, *next_cd; + + if (list_empty(&xhci->cancel_cmd_list)) + return 0; + + list_for_each_entry_safe(cur_cd, next_cd, + &xhci->cancel_cmd_list, cancel_cmd_list) { + if (cur_cd->cmd_trb == cmd_trb) { + if (cur_cd->command) + xhci_complete_cmd_in_cmd_wait_list(xhci, + cur_cd->command, COMP_CMD_STOP); + list_del(&cur_cd->cancel_cmd_list); + kfree(cur_cd); + return 1; + } + } + + return 0; +} + +/* + * If the cmd_trb_comp_code is COMP_CMD_ABORT, we just check whether the + * trb pointed by the command ring dequeue pointer is the trb we want to + * cancel or not. And if the cmd_trb_comp_code is COMP_CMD_STOP, we will + * traverse the cancel_cmd_list to trun the all of the commands according + * to command descriptor to NO-OP trb. + */ +static int handle_stopped_cmd_ring(struct xhci_hcd *xhci, + int cmd_trb_comp_code) +{ + int cur_trb_is_good = 0; + + /* Searching the cmd trb pointed by the command ring dequeue + * pointer in command descriptor list. If it is found, free it. + */ + cur_trb_is_good = xhci_search_cmd_trb_in_cd_list(xhci, + xhci->cmd_ring->dequeue); + + if (cmd_trb_comp_code == COMP_CMD_ABORT) + xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + else if (cmd_trb_comp_code == COMP_CMD_STOP) { + /* traversing the cancel_cmd_list and canceling + * the command according to command descriptor + */ + xhci_cancel_cmd_in_cd_list(xhci); + + xhci->cmd_ring_state = CMD_RING_STATE_RUNNING; + /* + * ring command ring doorbell again to restart the + * command ring + */ + if (xhci->cmd_ring->dequeue != xhci->cmd_ring->enqueue) + xhci_ring_cmd_db(xhci); + } + return cur_trb_is_good; +} + +static void handle_cmd_completion(struct xhci_hcd *xhci, + struct xhci_event_cmd *event) +{ + int slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + u64 cmd_dma; + dma_addr_t cmd_dequeue_dma; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_virt_device *virt_dev; + unsigned int ep_index; + struct xhci_ring *ep_ring; + unsigned int ep_state; + + cmd_dma = le64_to_cpu(event->cmd_trb); + cmd_dequeue_dma = xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, + xhci->cmd_ring->dequeue); + /* Is the command ring deq ptr out of sync with the deq seg ptr? */ + if (cmd_dequeue_dma == 0) { + xhci->error_bitmask |= 1 << 4; + return; + } + /* Does the DMA address match our internal dequeue pointer address? */ + if (cmd_dma != (u64) cmd_dequeue_dma) { + xhci->error_bitmask |= 1 << 5; + return; + } + + if ((GET_COMP_CODE(le32_to_cpu(event->status)) == COMP_CMD_ABORT) || + (GET_COMP_CODE(le32_to_cpu(event->status)) == COMP_CMD_STOP)) { + /* If the return value is 0, we think the trb pointed by + * command ring dequeue pointer is a good trb. The good + * trb means we don't want to cancel the trb, but it have + * been stopped by host. So we should handle it normally. + * Otherwise, driver should invoke inc_deq() and return. + */ + if (handle_stopped_cmd_ring(xhci, + GET_COMP_CODE(le32_to_cpu(event->status)))) { + inc_deq(xhci, xhci->cmd_ring); + return; + } + /* There is no command to handle if we get a stop event when the + * command ring is empty, event->cmd_trb points to the next + * unset command + */ + if (xhci->cmd_ring->dequeue == xhci->cmd_ring->enqueue) + return; + } + + switch (le32_to_cpu(xhci->cmd_ring->dequeue->generic.field[3]) + & TRB_TYPE_BITMASK) { + case TRB_TYPE(TRB_ENABLE_SLOT): + if (GET_COMP_CODE(le32_to_cpu(event->status)) == COMP_SUCCESS) + xhci->slot_id = slot_id; + else + xhci->slot_id = 0; + complete(&xhci->addr_dev); + break; + case TRB_TYPE(TRB_DISABLE_SLOT): + if (xhci->devs[slot_id]) { + if (xhci->quirks & XHCI_EP_LIMIT_QUIRK) + /* Delete default control endpoint resources */ + xhci_free_device_endpoint_resources(xhci, + xhci->devs[slot_id], true); + xhci_free_virt_device(xhci, slot_id); + } + break; + case TRB_TYPE(TRB_CONFIG_EP): + virt_dev = xhci->devs[slot_id]; + if (handle_cmd_in_cmd_wait_list(xhci, virt_dev, event)) + break; + /* + * Configure endpoint commands can come from the USB core + * configuration or alt setting changes, or because the HW + * needed an extra configure endpoint command after a reset + * endpoint command or streams were being configured. + * If the command was for a halted endpoint, the xHCI driver + * is not waiting on the configure endpoint command. + */ + ctrl_ctx = xhci_get_input_control_ctx(xhci, + virt_dev->in_ctx); + /* Input ctx add_flags are the endpoint index plus one */ + ep_index = xhci_last_valid_endpoint(le32_to_cpu(ctrl_ctx->add_flags)) - 1; + /* A usb_set_interface() call directly after clearing a halted + * condition may race on this quirky hardware. Not worth + * worrying about, since this is prototype hardware. Not sure + * if this will work for streams, but streams support was + * untested on this prototype. + */ + if (xhci->quirks & XHCI_RESET_EP_QUIRK && + ep_index != (unsigned int) -1 && + le32_to_cpu(ctrl_ctx->add_flags) - SLOT_FLAG == + le32_to_cpu(ctrl_ctx->drop_flags)) { + ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; + ep_state = xhci->devs[slot_id]->eps[ep_index].ep_state; + if (!(ep_state & EP_HALTED)) + goto bandwidth_change; + xhci_dbg(xhci, "Completed config ep cmd - " + "last ep index = %d, state = %d\n", + ep_index, ep_state); + /* Clear internal halted state and restart ring(s) */ + xhci->devs[slot_id]->eps[ep_index].ep_state &= + ~EP_HALTED; + ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + break; + } +bandwidth_change: + xhci_dbg(xhci, "Completed config ep cmd\n"); + xhci->devs[slot_id]->cmd_status = + GET_COMP_CODE(le32_to_cpu(event->status)); + complete(&xhci->devs[slot_id]->cmd_completion); + break; + case TRB_TYPE(TRB_EVAL_CONTEXT): + virt_dev = xhci->devs[slot_id]; + if (handle_cmd_in_cmd_wait_list(xhci, virt_dev, event)) + break; + xhci->devs[slot_id]->cmd_status = GET_COMP_CODE(le32_to_cpu(event->status)); + complete(&xhci->devs[slot_id]->cmd_completion); + break; + case TRB_TYPE(TRB_ADDR_DEV): + xhci->devs[slot_id]->cmd_status = GET_COMP_CODE(le32_to_cpu(event->status)); + complete(&xhci->addr_dev); + break; + case TRB_TYPE(TRB_STOP_RING): + handle_stopped_endpoint(xhci, xhci->cmd_ring->dequeue, event); + break; + case TRB_TYPE(TRB_SET_DEQ): + handle_set_deq_completion(xhci, event, xhci->cmd_ring->dequeue); + break; + case TRB_TYPE(TRB_CMD_NOOP): + break; + case TRB_TYPE(TRB_RESET_EP): + handle_reset_ep_completion(xhci, event, xhci->cmd_ring->dequeue); + break; + case TRB_TYPE(TRB_RESET_DEV): + xhci_dbg(xhci, "Completed reset device command.\n"); + slot_id = TRB_TO_SLOT_ID( + le32_to_cpu(xhci->cmd_ring->dequeue->generic.field[3])); + virt_dev = xhci->devs[slot_id]; + if (virt_dev) + handle_cmd_in_cmd_wait_list(xhci, virt_dev, event); + else + xhci_warn(xhci, "Reset device command completion " + "for disabled slot %u\n", slot_id); + break; + case TRB_TYPE(TRB_NEC_GET_FW): + if (!(xhci->quirks & XHCI_NEC_HOST)) { + xhci->error_bitmask |= 1 << 6; + break; + } + xhci_dbg(xhci, "NEC firmware version %2x.%02x\n", + NEC_FW_MAJOR(le32_to_cpu(event->status)), + NEC_FW_MINOR(le32_to_cpu(event->status))); + break; + default: + /* Skip over unknown commands on the event ring */ + xhci->error_bitmask |= 1 << 6; + break; + } + inc_deq(xhci, xhci->cmd_ring); +} + +static void handle_vendor_event(struct xhci_hcd *xhci, + union xhci_trb *event) +{ + u32 trb_type; + + trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(event->generic.field[3])); + xhci_dbg(xhci, "Vendor specific event TRB type = %u\n", trb_type); + if (trb_type == TRB_NEC_CMD_COMP && (xhci->quirks & XHCI_NEC_HOST)) + handle_cmd_completion(xhci, &event->event_cmd); +} + +/* @port_id: the one-based port ID from the hardware (indexed from array of all + * port registers -- USB 3.0 and USB 2.0). + * + * Returns a zero-based port number, which is suitable for indexing into each of + * the split roothubs' port arrays and bus state arrays. + * Add one to it in order to call xhci_find_slot_id_by_port. + */ +static unsigned int find_faked_portnum_from_hw_portnum(struct usb_hcd *hcd, + struct xhci_hcd *xhci, u32 port_id) +{ + unsigned int i; + unsigned int num_similar_speed_ports = 0; + + /* port_id from the hardware is 1-based, but port_array[], usb3_ports[], + * and usb2_ports are 0-based indexes. Count the number of similar + * speed ports, up to 1 port before this port. + */ + for (i = 0; i < (port_id - 1); i++) { + u8 port_speed = xhci->port_array[i]; + + /* + * Skip ports that don't have known speeds, or have duplicate + * Extended Capabilities port speed entries. + */ + if (port_speed == 0 || port_speed == DUPLICATE_ENTRY) + continue; + + /* + * USB 3.0 ports are always under a USB 3.0 hub. USB 2.0 and + * 1.1 ports are under the USB 2.0 hub. If the port speed + * matches the device speed, it's a similar speed port. + */ + if ((port_speed == 0x03) == (hcd->speed == HCD_USB3)) + num_similar_speed_ports++; + } + return num_similar_speed_ports; +} + +static void handle_device_notification(struct xhci_hcd *xhci, + union xhci_trb *event) +{ + u32 slot_id; + struct usb_device *udev; + + slot_id = TRB_TO_SLOT_ID(event->generic.field[3]); + if (!xhci->devs[slot_id]) { + xhci_warn(xhci, "Device Notification event for " + "unused slot %u\n", slot_id); + return; + } + + xhci_dbg(xhci, "Device Wake Notification event for slot ID %u\n", + slot_id); + udev = xhci->devs[slot_id]->udev; + if (udev && udev->parent) + usb_wakeup_notification(udev->parent, udev->portnum); +} + +static void handle_port_status(struct xhci_hcd *xhci, + union xhci_trb *event) +{ + struct usb_hcd *hcd; + u32 port_id; + u32 temp, temp1; + int max_ports; + int slot_id; + unsigned int faked_port_index; + u8 major_revision; + struct xhci_bus_state *bus_state; + __le32 __iomem **port_array; + bool bogus_port_status = false; + + /* Port status change events always have a successful completion code */ + if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) { + xhci_warn(xhci, "WARN: xHC returned failed port status event\n"); + xhci->error_bitmask |= 1 << 8; + } + port_id = GET_PORT_ID(le32_to_cpu(event->generic.field[0])); + xhci_dbg(xhci, "Port Status Change Event for port %d\n", port_id); + + max_ports = HCS_MAX_PORTS(xhci->hcs_params1); + if ((port_id <= 0) || (port_id > max_ports)) { + xhci_warn(xhci, "Invalid port id %d\n", port_id); + inc_deq(xhci, xhci->event_ring); + return; + } + + /* Figure out which usb_hcd this port is attached to: + * is it a USB 3.0 port or a USB 2.0/1.1 port? + */ + major_revision = xhci->port_array[port_id - 1]; + + /* Find the right roothub. */ + hcd = xhci_to_hcd(xhci); + if ((major_revision == 0x03) != (hcd->speed == HCD_USB3)) + hcd = xhci->shared_hcd; + + if (major_revision == 0) { + xhci_warn(xhci, "Event for port %u not in " + "Extended Capabilities, ignoring.\n", + port_id); + bogus_port_status = true; + goto cleanup; + } + if (major_revision == DUPLICATE_ENTRY) { + xhci_warn(xhci, "Event for port %u duplicated in" + "Extended Capabilities, ignoring.\n", + port_id); + bogus_port_status = true; + goto cleanup; + } + + /* + * Hardware port IDs reported by a Port Status Change Event include USB + * 3.0 and USB 2.0 ports. We want to check if the port has reported a + * resume event, but we first need to translate the hardware port ID + * into the index into the ports on the correct split roothub, and the + * correct bus_state structure. + */ + bus_state = &xhci->bus_state[hcd_index(hcd)]; + if (hcd->speed == HCD_USB3) + port_array = xhci->usb3_ports; + else + port_array = xhci->usb2_ports; + /* Find the faked port hub number */ + faked_port_index = find_faked_portnum_from_hw_portnum(hcd, xhci, + port_id); + + temp = xhci_readl(xhci, port_array[faked_port_index]); + if (hcd->state == HC_STATE_SUSPENDED) { + xhci_dbg(xhci, "resume root hub\n"); + usb_hcd_resume_root_hub(hcd); + } + + if ((temp & PORT_PLC) && (temp & PORT_PLS_MASK) == XDEV_RESUME) { + xhci_dbg(xhci, "port resume event for port %d\n", port_id); + + temp1 = xhci_readl(xhci, &xhci->op_regs->command); + if (!(temp1 & CMD_RUN)) { + xhci_warn(xhci, "xHC is not running.\n"); + goto cleanup; + } + + if (DEV_SUPERSPEED(temp)) { + xhci_dbg(xhci, "remote wake SS port %d\n", port_id); + /* Set a flag to say the port signaled remote wakeup, + * so we can tell the difference between the end of + * device and host initiated resume. + */ + bus_state->port_remote_wakeup |= 1 << faked_port_index; + xhci_test_and_clear_bit(xhci, port_array, + faked_port_index, PORT_PLC); + xhci_set_link_state(xhci, port_array, faked_port_index, + XDEV_U0); + /* Need to wait until the next link state change + * indicates the device is actually in U0. + */ + bogus_port_status = true; + goto cleanup; + } else { + xhci_dbg(xhci, "resume HS port %d\n", port_id); + bus_state->resume_done[faked_port_index] = jiffies + + msecs_to_jiffies(20); + set_bit(faked_port_index, &bus_state->resuming_ports); + mod_timer(&hcd->rh_timer, + bus_state->resume_done[faked_port_index]); + /* Do the rest in GetPortStatus */ + } + } + + if ((temp & PORT_PLC) && (temp & PORT_PLS_MASK) == XDEV_U0 && + DEV_SUPERSPEED(temp)) { + xhci_dbg(xhci, "resume SS port %d finished\n", port_id); + /* We've just brought the device into U0 through either the + * Resume state after a device remote wakeup, or through the + * U3Exit state after a host-initiated resume. If it's a device + * initiated remote wake, don't pass up the link state change, + * so the roothub behavior is consistent with external + * USB 3.0 hub behavior. + */ + slot_id = xhci_find_slot_id_by_port(hcd, xhci, + faked_port_index + 1); + if (slot_id && xhci->devs[slot_id]) + xhci_ring_device(xhci, slot_id); + if (bus_state->port_remote_wakeup & (1 << faked_port_index)) { + bus_state->port_remote_wakeup &= + ~(1 << faked_port_index); + xhci_test_and_clear_bit(xhci, port_array, + faked_port_index, PORT_PLC); + usb_wakeup_notification(hcd->self.root_hub, + faked_port_index + 1); + bogus_port_status = true; + goto cleanup; + } + } + + if (hcd->speed != HCD_USB3) + xhci_test_and_clear_bit(xhci, port_array, faked_port_index, + PORT_PLC); + +cleanup: + /* Update event ring dequeue pointer before dropping the lock */ + inc_deq(xhci, xhci->event_ring); + + /* Don't make the USB core poll the roothub if we got a bad port status + * change event. Besides, at that point we can't tell which roothub + * (USB 2.0 or USB 3.0) to kick. + */ + if (bogus_port_status) + return; + + /* + * xHCI port-status-change events occur when the "or" of all the + * status-change bits in the portsc register changes from 0 to 1. + * New status changes won't cause an event if any other change + * bits are still set. When an event occurs, switch over to + * polling to avoid losing status changes. + */ + xhci_dbg(xhci, "%s: starting port polling.\n", __func__); + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + spin_unlock(&xhci->lock); + /* Pass this up to the core */ + usb_hcd_poll_rh_status(hcd); + spin_lock(&xhci->lock); +} + +/* + * This TD is defined by the TRBs starting at start_trb in start_seg and ending + * at end_trb, which may be in another segment. If the suspect DMA address is a + * TRB in this TD, this function returns that TRB's segment. Otherwise it + * returns 0. + */ +struct xhci_segment *trb_in_td(struct xhci_segment *start_seg, + union xhci_trb *start_trb, + union xhci_trb *end_trb, + dma_addr_t suspect_dma) +{ + dma_addr_t start_dma; + dma_addr_t end_seg_dma; + dma_addr_t end_trb_dma; + struct xhci_segment *cur_seg; + + start_dma = xhci_trb_virt_to_dma(start_seg, start_trb); + cur_seg = start_seg; + + do { + if (start_dma == 0) + return NULL; + /* We may get an event for a Link TRB in the middle of a TD */ + end_seg_dma = xhci_trb_virt_to_dma(cur_seg, + &cur_seg->trbs[TRBS_PER_SEGMENT - 1]); + /* If the end TRB isn't in this segment, this is set to 0 */ + end_trb_dma = xhci_trb_virt_to_dma(cur_seg, end_trb); + + if (end_trb_dma > 0) { + /* The end TRB is in this segment, so suspect should be here */ + if (start_dma <= end_trb_dma) { + if (suspect_dma >= start_dma && suspect_dma <= end_trb_dma) + return cur_seg; + } else { + /* Case for one segment with + * a TD wrapped around to the top + */ + if ((suspect_dma >= start_dma && + suspect_dma <= end_seg_dma) || + (suspect_dma >= cur_seg->dma && + suspect_dma <= end_trb_dma)) + return cur_seg; + } + return NULL; + } else { + /* Might still be somewhere in this segment */ + if (suspect_dma >= start_dma && suspect_dma <= end_seg_dma) + return cur_seg; + } + cur_seg = cur_seg->next; + start_dma = xhci_trb_virt_to_dma(cur_seg, &cur_seg->trbs[0]); + } while (cur_seg != start_seg); + + return NULL; +} + +static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + unsigned int stream_id, + struct xhci_td *td, union xhci_trb *event_trb) +{ + struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index]; + ep->ep_state |= EP_HALTED; + ep->stopped_td = td; + ep->stopped_trb = event_trb; + ep->stopped_stream = stream_id; + + xhci_queue_reset_ep(xhci, slot_id, ep_index); + xhci_cleanup_stalled_ring(xhci, td->urb->dev, ep_index); + + ep->stopped_td = NULL; + ep->stopped_trb = NULL; + ep->stopped_stream = 0; + + xhci_ring_cmd_db(xhci); +} + +/* Check if an error has halted the endpoint ring. The class driver will + * cleanup the halt for a non-default control endpoint if we indicate a stall. + * However, a babble and other errors also halt the endpoint ring, and the class + * driver won't clear the halt in that case, so we need to issue a Set Transfer + * Ring Dequeue Pointer command manually. + */ +static int xhci_requires_manual_halt_cleanup(struct xhci_hcd *xhci, + struct xhci_ep_ctx *ep_ctx, + unsigned int trb_comp_code) +{ + /* TRB completion codes that may require a manual halt cleanup */ + if (trb_comp_code == COMP_TX_ERR || + trb_comp_code == COMP_BABBLE || + trb_comp_code == COMP_SPLIT_ERR) + /* The 0.96 spec says a babbling control endpoint + * is not halted. The 0.96 spec says it is. Some HW + * claims to be 0.95 compliant, but it halts the control + * endpoint anyway. Check if a babble halted the + * endpoint. + */ + if ((ep_ctx->ep_info & cpu_to_le32(EP_STATE_MASK)) == + cpu_to_le32(EP_STATE_HALTED)) + return 1; + + return 0; +} + +int xhci_is_vendor_info_code(struct xhci_hcd *xhci, unsigned int trb_comp_code) +{ + if (trb_comp_code >= 224 && trb_comp_code <= 255) { + /* Vendor defined "informational" completion code, + * treat as not-an-error. + */ + xhci_dbg(xhci, "Vendor defined info completion code %u\n", + trb_comp_code); + xhci_dbg(xhci, "Treating code as success.\n"); + return 1; + } + return 0; +} + +/* + * Finish the td processing, remove the td from td list; + * Return 1 if the urb can be given back. + */ +static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, + union xhci_trb *event_trb, struct xhci_transfer_event *event, + struct xhci_virt_ep *ep, int *status, bool skip) +{ + struct xhci_virt_device *xdev; + struct xhci_ring *ep_ring; + unsigned int slot_id; + int ep_index; + struct urb *urb = NULL; + struct xhci_ep_ctx *ep_ctx; + int ret = 0; + struct urb_priv *urb_priv; + u32 trb_comp_code; + + slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + xdev = xhci->devs[slot_id]; + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + + if (skip) + goto td_cleanup; + + if (trb_comp_code == COMP_STOP_INVAL || + trb_comp_code == COMP_STOP) { + /* The Endpoint Stop Command completion will take care of any + * stopped TDs. A stopped TD may be restarted, so don't update + * the ring dequeue pointer or take this TD off any lists yet. + */ + ep->stopped_td = td; + ep->stopped_trb = event_trb; + return 0; + } else { + if (trb_comp_code == COMP_STALL) { + /* The transfer is completed from the driver's + * perspective, but we need to issue a set dequeue + * command for this stalled endpoint to move the dequeue + * pointer past the TD. We can't do that here because + * the halt condition must be cleared first. Let the + * USB class driver clear the stall later. + */ + ep->stopped_td = td; + ep->stopped_trb = event_trb; + ep->stopped_stream = ep_ring->stream_id; + } else if (xhci_requires_manual_halt_cleanup(xhci, + ep_ctx, trb_comp_code)) { + /* Other types of errors halt the endpoint, but the + * class driver doesn't call usb_reset_endpoint() unless + * the error is -EPIPE. Clear the halted status in the + * xHCI hardware manually. + */ + xhci_cleanup_halted_endpoint(xhci, + slot_id, ep_index, ep_ring->stream_id, + td, event_trb); + } else { + /* Update ring dequeue pointer */ + while (ep_ring->dequeue != td->last_trb) + inc_deq(xhci, ep_ring); + inc_deq(xhci, ep_ring); + } + +td_cleanup: + /* Clean up the endpoint's TD list */ + urb = td->urb; + urb_priv = urb->hcpriv; + + /* Do one last check of the actual transfer length. + * If the host controller said we transferred more data than + * the buffer length, urb->actual_length will be a very big + * number (since it's unsigned). Play it safe and say we didn't + * transfer anything. + */ + if (urb->actual_length > urb->transfer_buffer_length) { + xhci_warn(xhci, "URB transfer length is wrong, " + "xHC issue? req. len = %u, " + "act. len = %u\n", + urb->transfer_buffer_length, + urb->actual_length); + urb->actual_length = 0; + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + *status = -EREMOTEIO; + else + *status = 0; + } + list_del_init(&td->td_list); + /* Was this TD slated to be cancelled but completed anyway? */ + if (!list_empty(&td->cancelled_td_list)) + list_del_init(&td->cancelled_td_list); + + urb_priv->td_cnt++; + /* Giveback the urb when all the tds are completed */ + if (urb_priv->td_cnt == urb_priv->length) { + ret = 1; + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs--; + if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs + == 0) { + if (xhci->quirks & XHCI_AMD_PLL_FIX) + usb_amd_quirk_pll_enable(); + } + } + } + } + + return ret; +} + +/* + * Process control tds, update urb status and actual_length. + */ +static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, + union xhci_trb *event_trb, struct xhci_transfer_event *event, + struct xhci_virt_ep *ep, int *status) +{ + struct xhci_virt_device *xdev; + struct xhci_ring *ep_ring; + unsigned int slot_id; + int ep_index; + struct xhci_ep_ctx *ep_ctx; + u32 trb_comp_code; + + slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + xdev = xhci->devs[slot_id]; + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + + switch (trb_comp_code) { + case COMP_SUCCESS: + if (event_trb == ep_ring->dequeue) { + xhci_warn(xhci, "WARN: Success on ctrl setup TRB " + "without IOC set??\n"); + *status = -ESHUTDOWN; + } else if (event_trb != td->last_trb) { + xhci_warn(xhci, "WARN: Success on ctrl data TRB " + "without IOC set??\n"); + *status = -ESHUTDOWN; + } else { + *status = 0; + } + break; + case COMP_SHORT_TX: + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + *status = -EREMOTEIO; + else + *status = 0; + break; + case COMP_STOP_INVAL: + case COMP_STOP: + return finish_td(xhci, td, event_trb, event, ep, status, false); + default: + if (!xhci_requires_manual_halt_cleanup(xhci, + ep_ctx, trb_comp_code)) + break; + xhci_dbg(xhci, "TRB error code %u, " + "halted endpoint index = %u\n", + trb_comp_code, ep_index); + /* else fall through */ + case COMP_STALL: + /* Did we transfer part of the data (middle) phase? */ + if (event_trb != ep_ring->dequeue && + event_trb != td->last_trb) + td->urb->actual_length = + td->urb->transfer_buffer_length - + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + else + td->urb->actual_length = 0; + + xhci_cleanup_halted_endpoint(xhci, + slot_id, ep_index, 0, td, event_trb); + return finish_td(xhci, td, event_trb, event, ep, status, true); + } + /* + * Did we transfer any data, despite the errors that might have + * happened? I.e. did we get past the setup stage? + */ + if (event_trb != ep_ring->dequeue) { + /* The event was for the status stage */ + if (event_trb == td->last_trb) { + if (td->urb->actual_length != 0) { + /* Don't overwrite a previously set error code + */ + if ((*status == -EINPROGRESS || *status == 0) && + (td->urb->transfer_flags + & URB_SHORT_NOT_OK)) + /* Did we already see a short data + * stage? */ + *status = -EREMOTEIO; + } else { + td->urb->actual_length = + td->urb->transfer_buffer_length; + } + } else { + /* Maybe the event was for the data stage? */ + td->urb->actual_length = + td->urb->transfer_buffer_length - + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + xhci_dbg(xhci, "Waiting for status " + "stage event\n"); + return 0; + } + } + + return finish_td(xhci, td, event_trb, event, ep, status, false); +} + +/* + * Process isochronous tds, update urb packet status and actual_length. + */ +static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, + union xhci_trb *event_trb, struct xhci_transfer_event *event, + struct xhci_virt_ep *ep, int *status) +{ + struct xhci_ring *ep_ring; + struct urb_priv *urb_priv; + int idx; + int len = 0; + union xhci_trb *cur_trb; + struct xhci_segment *cur_seg; + struct usb_iso_packet_descriptor *frame; + u32 trb_comp_code; + bool skip_td = false; + + ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + urb_priv = td->urb->hcpriv; + idx = urb_priv->td_cnt; + frame = &td->urb->iso_frame_desc[idx]; + + /* handle completion code */ + switch (trb_comp_code) { + case COMP_SUCCESS: + if (EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) == 0) { + frame->status = 0; + break; + } + if ((xhci->quirks & XHCI_TRUST_TX_LENGTH)) + trb_comp_code = COMP_SHORT_TX; + case COMP_SHORT_TX: + frame->status = td->urb->transfer_flags & URB_SHORT_NOT_OK ? + -EREMOTEIO : 0; + break; + case COMP_BW_OVER: + frame->status = -ECOMM; + skip_td = true; + break; + case COMP_BUFF_OVER: + case COMP_BABBLE: + frame->status = -EOVERFLOW; + skip_td = true; + break; + case COMP_DEV_ERR: + case COMP_STALL: + case COMP_TX_ERR: + frame->status = -EPROTO; + skip_td = true; + break; + case COMP_STOP: + case COMP_STOP_INVAL: + break; + default: + frame->status = -1; + break; + } + + if (trb_comp_code == COMP_SUCCESS || skip_td) { + frame->actual_length = frame->length; + td->urb->actual_length += frame->length; + } else { + for (cur_trb = ep_ring->dequeue, + cur_seg = ep_ring->deq_seg; cur_trb != event_trb; + next_trb(xhci, ep_ring, &cur_seg, &cur_trb)) { + if (!TRB_TYPE_NOOP_LE32(cur_trb->generic.field[3]) && + !TRB_TYPE_LINK_LE32(cur_trb->generic.field[3])) + len += TRB_LEN(le32_to_cpu(cur_trb->generic.field[2])); + } + len += TRB_LEN(le32_to_cpu(cur_trb->generic.field[2])) - + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + + if (trb_comp_code != COMP_STOP_INVAL) { + frame->actual_length = len; + td->urb->actual_length += len; + } + } + + return finish_td(xhci, td, event_trb, event, ep, status, false); +} + +static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, + struct xhci_transfer_event *event, + struct xhci_virt_ep *ep, int *status) +{ + struct xhci_ring *ep_ring; + struct urb_priv *urb_priv; + struct usb_iso_packet_descriptor *frame; + int idx; + + ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + urb_priv = td->urb->hcpriv; + idx = urb_priv->td_cnt; + frame = &td->urb->iso_frame_desc[idx]; + + /* The transfer is partly done. */ + frame->status = -EXDEV; + + /* calc actual length */ + frame->actual_length = 0; + + /* Update ring dequeue pointer */ + while (ep_ring->dequeue != td->last_trb) + inc_deq(xhci, ep_ring); + inc_deq(xhci, ep_ring); + + return finish_td(xhci, td, NULL, event, ep, status, true); +} + +/* + * Process bulk and interrupt tds, update urb status and actual_length. + */ +static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, + union xhci_trb *event_trb, struct xhci_transfer_event *event, + struct xhci_virt_ep *ep, int *status) +{ + struct xhci_ring *ep_ring; + union xhci_trb *cur_trb; + struct xhci_segment *cur_seg; + u32 trb_comp_code; + + ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + + switch (trb_comp_code) { + case COMP_SUCCESS: + /* Double check that the HW transferred everything. */ + if (event_trb != td->last_trb || + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) != 0) { + xhci_warn(xhci, "WARN Successful completion " + "on short TX\n"); + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + *status = -EREMOTEIO; + else + *status = 0; + if ((xhci->quirks & XHCI_TRUST_TX_LENGTH)) + trb_comp_code = COMP_SHORT_TX; + } else { + *status = 0; + } + break; + case COMP_SHORT_TX: + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + *status = -EREMOTEIO; + else + *status = 0; + break; + default: + /* Others already handled above */ + break; + } + if (trb_comp_code == COMP_SHORT_TX) + xhci_dbg(xhci, "ep %#x - asked for %d bytes, " + "%d bytes untransferred\n", + td->urb->ep->desc.bEndpointAddress, + td->urb->transfer_buffer_length, + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len))); + /* Fast path - was this the last TRB in the TD for this URB? */ + if (event_trb == td->last_trb) { + if (EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) != 0) { + td->urb->actual_length = + td->urb->transfer_buffer_length - + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + if (td->urb->transfer_buffer_length < + td->urb->actual_length) { + xhci_warn(xhci, "HC gave bad length " + "of %d bytes left\n", + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len))); + td->urb->actual_length = 0; + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + *status = -EREMOTEIO; + else + *status = 0; + } + /* Don't overwrite a previously set error code */ + if (*status == -EINPROGRESS) { + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + *status = -EREMOTEIO; + else + *status = 0; + } + } else { + td->urb->actual_length = + td->urb->transfer_buffer_length; + /* Ignore a short packet completion if the + * untransferred length was zero. + */ + if (*status == -EREMOTEIO) + *status = 0; + } + } else { + /* Slow path - walk the list, starting from the dequeue + * pointer, to get the actual length transferred. + */ + td->urb->actual_length = 0; + for (cur_trb = ep_ring->dequeue, cur_seg = ep_ring->deq_seg; + cur_trb != event_trb; + next_trb(xhci, ep_ring, &cur_seg, &cur_trb)) { + if (!TRB_TYPE_NOOP_LE32(cur_trb->generic.field[3]) && + !TRB_TYPE_LINK_LE32(cur_trb->generic.field[3])) + td->urb->actual_length += + TRB_LEN(le32_to_cpu(cur_trb->generic.field[2])); + } + /* If the ring didn't stop on a Link or No-op TRB, add + * in the actual bytes transferred from the Normal TRB + */ + if (trb_comp_code != COMP_STOP_INVAL) + td->urb->actual_length += + TRB_LEN(le32_to_cpu(cur_trb->generic.field[2])) - + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + } + + return finish_td(xhci, td, event_trb, event, ep, status, false); +} + +/* + * If this function returns an error condition, it means it got a Transfer + * event with a corrupted Slot ID, Endpoint ID, or TRB DMA address. + * At this point, the host controller is probably hosed and should be reset. + */ +static int handle_tx_event(struct xhci_hcd *xhci, + struct xhci_transfer_event *event) + __releases(&xhci->lock) + __acquires(&xhci->lock) +{ + struct xhci_virt_device *xdev; + struct xhci_virt_ep *ep; + struct xhci_ring *ep_ring; + unsigned int slot_id; + int ep_index; + struct xhci_td *td = NULL; + dma_addr_t event_dma; + struct xhci_segment *event_seg; + union xhci_trb *event_trb; + struct urb *urb = NULL; + int status = -EINPROGRESS; + struct urb_priv *urb_priv; + struct xhci_ep_ctx *ep_ctx; + struct list_head *tmp; + u32 trb_comp_code; + int ret = 0; + int td_num = 0; + + slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + xdev = xhci->devs[slot_id]; + if (!xdev) { + xhci_err(xhci, "ERROR Transfer event pointed to bad slot\n"); + xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", + (unsigned long long) xhci_trb_virt_to_dma( + xhci->event_ring->deq_seg, + xhci->event_ring->dequeue), + lower_32_bits(le64_to_cpu(event->buffer)), + upper_32_bits(le64_to_cpu(event->buffer)), + le32_to_cpu(event->transfer_len), + le32_to_cpu(event->flags)); + xhci_dbg(xhci, "Event ring:\n"); + xhci_debug_segment(xhci, xhci->event_ring->deq_seg); + return -ENODEV; + } + + /* Endpoint ID is 1 based, our index is zero based */ + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + ep = &xdev->eps[ep_index]; + ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); + if (!ep_ring || + (le32_to_cpu(ep_ctx->ep_info) & EP_STATE_MASK) == + EP_STATE_DISABLED) { + xhci_err(xhci, "ERROR Transfer event for disabled endpoint " + "or incorrect stream ring\n"); + xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", + (unsigned long long) xhci_trb_virt_to_dma( + xhci->event_ring->deq_seg, + xhci->event_ring->dequeue), + lower_32_bits(le64_to_cpu(event->buffer)), + upper_32_bits(le64_to_cpu(event->buffer)), + le32_to_cpu(event->transfer_len), + le32_to_cpu(event->flags)); + xhci_dbg(xhci, "Event ring:\n"); + xhci_debug_segment(xhci, xhci->event_ring->deq_seg); + return -ENODEV; + } + + /* Count current td numbers if ep->skip is set */ + if (ep->skip) { + list_for_each(tmp, &ep_ring->td_list) + td_num++; + } + + event_dma = le64_to_cpu(event->buffer); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + /* Look for common error cases */ + switch (trb_comp_code) { + /* Skip codes that require special handling depending on + * transfer type + */ + case COMP_SUCCESS: + if (EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) == 0) + break; + if (xhci->quirks & XHCI_TRUST_TX_LENGTH) + trb_comp_code = COMP_SHORT_TX; + else + xhci_warn_ratelimited(xhci, + "WARN Successful completion on short TX: needs XHCI_TRUST_TX_LENGTH quirk?\n"); + case COMP_SHORT_TX: + break; + case COMP_STOP: + xhci_dbg(xhci, "Stopped on Transfer TRB\n"); + break; + case COMP_STOP_INVAL: + xhci_dbg(xhci, "Stopped on No-op or Link TRB\n"); + break; + case COMP_STALL: + xhci_dbg(xhci, "Stalled endpoint\n"); + ep->ep_state |= EP_HALTED; + status = -EPIPE; + break; + case COMP_TRB_ERR: + xhci_warn(xhci, "WARN: TRB error on endpoint\n"); + status = -EILSEQ; + break; + case COMP_SPLIT_ERR: + case COMP_TX_ERR: + xhci_dbg(xhci, "Transfer error on endpoint\n"); + status = -EPROTO; + break; + case COMP_BABBLE: + xhci_dbg(xhci, "Babble error on endpoint\n"); + status = -EOVERFLOW; + break; + case COMP_DB_ERR: + xhci_warn(xhci, "WARN: HC couldn't access mem fast enough\n"); + status = -ENOSR; + break; + case COMP_BW_OVER: + xhci_warn(xhci, "WARN: bandwidth overrun event on endpoint\n"); + break; + case COMP_BUFF_OVER: + xhci_warn(xhci, "WARN: buffer overrun event on endpoint\n"); + break; + case COMP_UNDERRUN: + /* + * When the Isoch ring is empty, the xHC will generate + * a Ring Overrun Event for IN Isoch endpoint or Ring + * Underrun Event for OUT Isoch endpoint. + */ + xhci_dbg(xhci, "underrun event on endpoint\n"); + if (!list_empty(&ep_ring->td_list)) + xhci_dbg(xhci, "Underrun Event for slot %d ep %d " + "still with TDs queued?\n", + TRB_TO_SLOT_ID(le32_to_cpu(event->flags)), + ep_index); + goto cleanup; + case COMP_OVERRUN: + xhci_dbg(xhci, "overrun event on endpoint\n"); + if (!list_empty(&ep_ring->td_list)) + xhci_dbg(xhci, "Overrun Event for slot %d ep %d " + "still with TDs queued?\n", + TRB_TO_SLOT_ID(le32_to_cpu(event->flags)), + ep_index); + goto cleanup; + case COMP_DEV_ERR: + xhci_warn(xhci, "WARN: detect an incompatible device"); + status = -EPROTO; + break; + case COMP_MISSED_INT: + /* + * When encounter missed service error, one or more isoc tds + * may be missed by xHC. + * Set skip flag of the ep_ring; Complete the missed tds as + * short transfer when process the ep_ring next time. + */ + ep->skip = true; + xhci_dbg(xhci, "Miss service interval error, set skip flag\n"); + goto cleanup; + default: + if (xhci_is_vendor_info_code(xhci, trb_comp_code)) { + status = 0; + break; + } + xhci_warn(xhci, "ERROR Unknown event condition, HC probably " + "busted\n"); + goto cleanup; + } + + do { + /* This TRB should be in the TD at the head of this ring's + * TD list. + */ + if (list_empty(&ep_ring->td_list)) { + /* + * A stopped endpoint may generate an extra completion + * event if the device was suspended. Don't print + * warnings. + */ + if (!(trb_comp_code == COMP_STOP || + trb_comp_code == COMP_STOP_INVAL)) { + xhci_warn(xhci, "WARN Event TRB for slot %d ep %d with no TDs queued?\n", + TRB_TO_SLOT_ID(le32_to_cpu(event->flags)), + ep_index); + xhci_dbg(xhci, "Event TRB with TRB type ID %u\n", + (le32_to_cpu(event->flags) & + TRB_TYPE_BITMASK)>>10); + xhci_print_trb_offsets(xhci, (union xhci_trb *) event); + } + if (ep->skip) { + ep->skip = false; + xhci_dbg(xhci, "td_list is empty while skip " + "flag set. Clear skip flag.\n"); + } + ret = 0; + goto cleanup; + } + + /* We've skipped all the TDs on the ep ring when ep->skip set */ + if (ep->skip && td_num == 0) { + ep->skip = false; + xhci_dbg(xhci, "All tds on the ep_ring skipped. " + "Clear skip flag.\n"); + ret = 0; + goto cleanup; + } + + td = list_entry(ep_ring->td_list.next, struct xhci_td, td_list); + if (ep->skip) + td_num--; + + /* Is this a TRB in the currently executing TD? */ + event_seg = trb_in_td(ep_ring->deq_seg, ep_ring->dequeue, + td->last_trb, event_dma); + + /* + * Skip the Force Stopped Event. The event_trb(event_dma) of FSE + * is not in the current TD pointed by ep_ring->dequeue because + * that the hardware dequeue pointer still at the previous TRB + * of the current TD. The previous TRB maybe a Link TD or the + * last TRB of the previous TD. The command completion handle + * will take care the rest. + */ + if (!event_seg && (trb_comp_code == COMP_STOP || + trb_comp_code == COMP_STOP_INVAL)) { + ret = 0; + goto cleanup; + } + + if (!event_seg) { + if (!ep->skip || + !usb_endpoint_xfer_isoc(&td->urb->ep->desc)) { + /* Some host controllers give a spurious + * successful event after a short transfer. + * Ignore it. + */ + if ((xhci->quirks & XHCI_SPURIOUS_SUCCESS) && + ep_ring->last_td_was_short) { + ep_ring->last_td_was_short = false; + ret = 0; + goto cleanup; + } + /* HC is busted, give up! */ + xhci_err(xhci, + "ERROR Transfer event TRB DMA ptr not " + "part of current TD\n"); + return -ESHUTDOWN; + } + + ret = skip_isoc_td(xhci, td, event, ep, &status); + goto cleanup; + } + if (trb_comp_code == COMP_SHORT_TX) + ep_ring->last_td_was_short = true; + else + ep_ring->last_td_was_short = false; + + if (ep->skip) { + xhci_dbg(xhci, "Found td. Clear skip flag.\n"); + ep->skip = false; + } + + event_trb = &event_seg->trbs[(event_dma - event_seg->dma) / + sizeof(*event_trb)]; + /* + * No-op TRB should not trigger interrupts. + * If event_trb is a no-op TRB, it means the + * corresponding TD has been cancelled. Just ignore + * the TD. + */ + if (TRB_TYPE_NOOP_LE32(event_trb->generic.field[3])) { + xhci_dbg(xhci, + "event_trb is a no-op TRB. Skip it\n"); + goto cleanup; + } + + /* Now update the urb's actual_length and give back to + * the core + */ + if (usb_endpoint_xfer_control(&td->urb->ep->desc)) + ret = process_ctrl_td(xhci, td, event_trb, event, ep, + &status); + else if (usb_endpoint_xfer_isoc(&td->urb->ep->desc)) + ret = process_isoc_td(xhci, td, event_trb, event, ep, + &status); + else + ret = process_bulk_intr_td(xhci, td, event_trb, event, + ep, &status); + +cleanup: + /* + * Do not update event ring dequeue pointer if ep->skip is set. + * Will roll back to continue process missed tds. + */ + if (trb_comp_code == COMP_MISSED_INT || !ep->skip) { + inc_deq(xhci, xhci->event_ring); + } + + if (ret) { + urb = td->urb; + urb_priv = urb->hcpriv; + /* Leave the TD around for the reset endpoint function + * to use(but only if it's not a control endpoint, + * since we already queued the Set TR dequeue pointer + * command for stalled control endpoints). + */ + if (usb_endpoint_xfer_control(&urb->ep->desc) || + (trb_comp_code != COMP_STALL && + trb_comp_code != COMP_BABBLE)) + xhci_urb_free_priv(xhci, urb_priv); + else + kfree(urb_priv); + + usb_hcd_unlink_urb_from_ep(bus_to_hcd(urb->dev->bus), urb); + if ((urb->actual_length != urb->transfer_buffer_length && + (urb->transfer_flags & + URB_SHORT_NOT_OK)) || + (status != 0 && + !usb_endpoint_xfer_isoc(&urb->ep->desc))) + xhci_dbg(xhci, "Giveback URB %p, len = %d, " + "expected = %d, status = %d\n", + urb, urb->actual_length, + urb->transfer_buffer_length, + status); + spin_unlock(&xhci->lock); + /* EHCI, UHCI, and OHCI always unconditionally set the + * urb->status of an isochronous endpoint to 0. + */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + status = 0; + usb_hcd_giveback_urb(bus_to_hcd(urb->dev->bus), urb, status); + spin_lock(&xhci->lock); + } + + /* + * If ep->skip is set, it means there are missed tds on the + * endpoint ring need to take care of. + * Process them as short transfer until reach the td pointed by + * the event. + */ + } while (ep->skip && trb_comp_code != COMP_MISSED_INT); + + return 0; +} + +/* + * This function handles all OS-owned events on the event ring. It may drop + * xhci->lock between event processing (e.g. to pass up port status changes). + * Returns >0 for "possibly more events to process" (caller should call again), + * otherwise 0 if done. In future, <0 returns should indicate error code. + */ +static int xhci_handle_event(struct xhci_hcd *xhci) +{ + union xhci_trb *event; + int update_ptrs = 1; + int ret; + + if (!xhci->event_ring || !xhci->event_ring->dequeue) { + xhci->error_bitmask |= 1 << 1; + return 0; + } + + event = xhci->event_ring->dequeue; + /* Does the HC or OS own the TRB? */ + if ((le32_to_cpu(event->event_cmd.flags) & TRB_CYCLE) != + xhci->event_ring->cycle_state) { + xhci->error_bitmask |= 1 << 2; + return 0; + } + + /* + * Barrier between reading the TRB_CYCLE (valid) flag above and any + * speculative reads of the event's flags/data below. + */ + rmb(); + /* FIXME: Handle more event types. */ + switch ((le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK)) { + case TRB_TYPE(TRB_COMPLETION): + handle_cmd_completion(xhci, &event->event_cmd); + break; + case TRB_TYPE(TRB_PORT_STATUS): + handle_port_status(xhci, event); + update_ptrs = 0; + break; + case TRB_TYPE(TRB_TRANSFER): + ret = handle_tx_event(xhci, &event->trans_event); + if (ret < 0) + xhci->error_bitmask |= 1 << 9; + else + update_ptrs = 0; + break; + case TRB_TYPE(TRB_DEV_NOTE): + handle_device_notification(xhci, event); + break; + default: + if ((le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK) >= + TRB_TYPE(48)) + handle_vendor_event(xhci, event); + else + xhci->error_bitmask |= 1 << 3; + } + /* Any of the above functions may drop and re-acquire the lock, so check + * to make sure a watchdog timer didn't mark the host as non-responsive. + */ + if (xhci->xhc_state & XHCI_STATE_DYING) { + xhci_dbg(xhci, "xHCI host dying, returning from " + "event handler.\n"); + return 0; + } + + if (update_ptrs) + /* Update SW event ring dequeue pointer */ + inc_deq(xhci, xhci->event_ring); + + /* Are there more items on the event ring? Caller will call us again to + * check. + */ + return 1; +} + +/* + * xHCI spec says we can get an interrupt, and if the HC has an error condition, + * we might get bad data out of the event ring. Section 4.10.2.7 has a list of + * indicators of an event TRB error, but we check the status *first* to be safe. + */ +irqreturn_t xhci_irq(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + u32 status; + u64 temp_64; + union xhci_trb *event_ring_deq; + dma_addr_t deq; + + spin_lock(&xhci->lock); + /* Check if the xHC generated the interrupt, or the irq is shared */ + status = xhci_readl(xhci, &xhci->op_regs->status); + if (status == 0xffffffff) + goto hw_died; + + if (!(status & STS_EINT)) { + spin_unlock(&xhci->lock); + return IRQ_NONE; + } + if (status & STS_FATAL) { + xhci_warn(xhci, "WARNING: Host System Error\n"); + xhci_halt(xhci); +hw_died: + spin_unlock(&xhci->lock); + return -ESHUTDOWN; + } + + /* + * Clear the op reg interrupt status first, + * so we can receive interrupts from other MSI-X interrupters. + * Write 1 to clear the interrupt status. + */ + status |= STS_EINT; + xhci_writel(xhci, status, &xhci->op_regs->status); + /* FIXME when MSI-X is supported and there are multiple vectors */ + /* Clear the MSI-X event interrupt status */ + + if (hcd->irq) { + u32 irq_pending; + /* Acknowledge the PCI interrupt */ + irq_pending = xhci_readl(xhci, &xhci->ir_set->irq_pending); + irq_pending |= IMAN_IP; + xhci_writel(xhci, irq_pending, &xhci->ir_set->irq_pending); + } + + if (xhci->xhc_state & XHCI_STATE_DYING) { + xhci_dbg(xhci, "xHCI dying, ignoring interrupt. " + "Shouldn't IRQs be disabled?\n"); + /* Clear the event handler busy flag (RW1C); + * the event ring should be empty. + */ + temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + xhci_write_64(xhci, temp_64 | ERST_EHB, + &xhci->ir_set->erst_dequeue); + spin_unlock(&xhci->lock); + + return IRQ_HANDLED; + } + + event_ring_deq = xhci->event_ring->dequeue; + /* FIXME this should be a delayed service routine + * that clears the EHB. + */ + while (xhci_handle_event(xhci) > 0) {} + + temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + /* If necessary, update the HW's version of the event ring deq ptr. */ + if (event_ring_deq != xhci->event_ring->dequeue) { + deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg, + xhci->event_ring->dequeue); + if (deq == 0) + xhci_warn(xhci, "WARN something wrong with SW event " + "ring dequeue ptr.\n"); + /* Update HC event ring dequeue pointer */ + temp_64 &= ERST_PTR_MASK; + temp_64 |= ((u64) deq & (u64) ~ERST_PTR_MASK); + } + + /* Clear the event handler busy flag (RW1C); event ring is empty. */ + temp_64 |= ERST_EHB; + xhci_write_64(xhci, temp_64, &xhci->ir_set->erst_dequeue); + + spin_unlock(&xhci->lock); + + return IRQ_HANDLED; +} + +irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd) +{ + return xhci_irq(hcd); +} + +/**** Endpoint Ring Operations ****/ + +/* + * Generic function for queueing a TRB on a ring. + * The caller must have checked to make sure there's room on the ring. + * + * @more_trbs_coming: Will you enqueue more TRBs before calling + * prepare_transfer()? + */ +static void queue_trb(struct xhci_hcd *xhci, struct xhci_ring *ring, + bool more_trbs_coming, + u32 field1, u32 field2, u32 field3, u32 field4) +{ + struct xhci_generic_trb *trb; + + trb = &ring->enqueue->generic; + trb->field[0] = cpu_to_le32(field1); + trb->field[1] = cpu_to_le32(field2); + trb->field[2] = cpu_to_le32(field3); + trb->field[3] = cpu_to_le32(field4); + inc_enq(xhci, ring, more_trbs_coming); +} + +/* + * Does various checks on the endpoint ring, and makes it ready to queue num_trbs. + * FIXME allocate segments if the ring is full. + */ +static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, + u32 ep_state, unsigned int num_trbs, gfp_t mem_flags) +{ + unsigned int num_trbs_needed; + + /* Make sure the endpoint has been added to xHC schedule */ + switch (ep_state) { + case EP_STATE_DISABLED: + /* + * USB core changed config/interfaces without notifying us, + * or hardware is reporting the wrong state. + */ + xhci_warn(xhci, "WARN urb submitted to disabled ep\n"); + return -ENOENT; + case EP_STATE_ERROR: + xhci_warn(xhci, "WARN waiting for error on ep to be cleared\n"); + /* FIXME event handling code for error needs to clear it */ + /* XXX not sure if this should be -ENOENT or not */ + return -EINVAL; + case EP_STATE_HALTED: + xhci_dbg(xhci, "WARN halted endpoint, queueing URB anyway.\n"); + case EP_STATE_STOPPED: + case EP_STATE_RUNNING: + break; + default: + xhci_err(xhci, "ERROR unknown endpoint state for ep\n"); + /* + * FIXME issue Configure Endpoint command to try to get the HC + * back into a known state. + */ + return -EINVAL; + } + + while (1) { + if (room_on_ring(xhci, ep_ring, num_trbs)) + break; + + if (ep_ring == xhci->cmd_ring) { + xhci_err(xhci, "Do not support expand command ring\n"); + return -ENOMEM; + } + + xhci_dbg(xhci, "ERROR no room on ep ring, " + "try ring expansion\n"); + num_trbs_needed = num_trbs - ep_ring->num_trbs_free; + if (xhci_ring_expansion(xhci, ep_ring, num_trbs_needed, + mem_flags)) { + xhci_err(xhci, "Ring expansion failed\n"); + return -ENOMEM; + } + } + + if (enqueue_is_link_trb(ep_ring)) { + struct xhci_ring *ring = ep_ring; + union xhci_trb *next; + + next = ring->enqueue; + + while (last_trb(xhci, ring, ring->enq_seg, next)) { + /* If we're not dealing with 0.95 hardware or isoc rings + * on AMD 0.96 host, clear the chain bit. + */ + if (!xhci_link_trb_quirk(xhci) && + !(ring->type == TYPE_ISOC && + (xhci->quirks & XHCI_AMD_0x96_HOST))) + next->link.control &= cpu_to_le32(~TRB_CHAIN); + else + next->link.control |= cpu_to_le32(TRB_CHAIN); + + wmb(); + next->link.control ^= cpu_to_le32(TRB_CYCLE); + + /* Toggle the cycle bit after the last ring segment. */ + if (last_trb_on_last_seg(xhci, ring, ring->enq_seg, next)) { + ring->cycle_state = (ring->cycle_state ? 0 : 1); + } + ring->enq_seg = ring->enq_seg->next; + ring->enqueue = ring->enq_seg->trbs; + next = ring->enqueue; + } + } + + return 0; +} + +static int prepare_transfer(struct xhci_hcd *xhci, + struct xhci_virt_device *xdev, + unsigned int ep_index, + unsigned int stream_id, + unsigned int num_trbs, + struct urb *urb, + unsigned int td_index, + gfp_t mem_flags) +{ + int ret; + struct urb_priv *urb_priv; + struct xhci_td *td; + struct xhci_ring *ep_ring; + struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); + + ep_ring = xhci_stream_id_to_ring(xdev, ep_index, stream_id); + if (!ep_ring) { + xhci_dbg(xhci, "Can't prepare ring for bad stream ID %u\n", + stream_id); + return -EINVAL; + } + + ret = prepare_ring(xhci, ep_ring, + le32_to_cpu(ep_ctx->ep_info) & EP_STATE_MASK, + num_trbs, mem_flags); + if (ret) + return ret; + + urb_priv = urb->hcpriv; + td = urb_priv->td[td_index]; + + INIT_LIST_HEAD(&td->td_list); + INIT_LIST_HEAD(&td->cancelled_td_list); + + if (td_index == 0) { + ret = usb_hcd_link_urb_to_ep(bus_to_hcd(urb->dev->bus), urb); + if (unlikely(ret)) + return ret; + } + + td->urb = urb; + /* Add this TD to the tail of the endpoint ring's TD list */ + list_add_tail(&td->td_list, &ep_ring->td_list); + td->start_seg = ep_ring->enq_seg; + td->first_trb = ep_ring->enqueue; + + urb_priv->td[td_index] = td; + + return 0; +} + +static unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb) +{ + int num_sgs, num_trbs, running_total, temp, i; + struct scatterlist *sg; + + sg = NULL; + num_sgs = urb->num_mapped_sgs; + temp = urb->transfer_buffer_length; + + num_trbs = 0; + for_each_sg(urb->sg, sg, num_sgs, i) { + unsigned int len = sg_dma_len(sg); + + /* Scatter gather list entries may cross 64KB boundaries */ + running_total = TRB_MAX_BUFF_SIZE - + (sg_dma_address(sg) & (TRB_MAX_BUFF_SIZE - 1)); + running_total &= TRB_MAX_BUFF_SIZE - 1; + if (running_total != 0) + num_trbs++; + + /* How many more 64KB chunks to transfer, how many more TRBs? */ + while (running_total < sg_dma_len(sg) && running_total < temp) { + num_trbs++; + running_total += TRB_MAX_BUFF_SIZE; + } + len = min_t(int, len, temp); + temp -= len; + if (temp == 0) + break; + } + return num_trbs; +} + +static void check_trb_math(struct urb *urb, int num_trbs, int running_total) +{ + if (num_trbs != 0) + dev_err(&urb->dev->dev, "%s - ep %#x - Miscalculated number of " + "TRBs, %d left\n", __func__, + urb->ep->desc.bEndpointAddress, num_trbs); + if (running_total != urb->transfer_buffer_length) + dev_err(&urb->dev->dev, "%s - ep %#x - Miscalculated tx length, " + "queued %#x (%d), asked for %#x (%d)\n", + __func__, + urb->ep->desc.bEndpointAddress, + running_total, running_total, + urb->transfer_buffer_length, + urb->transfer_buffer_length); +} + +static void giveback_first_trb(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index, unsigned int stream_id, int start_cycle, + struct xhci_generic_trb *start_trb) +{ + /* + * Pass all the TRBs to the hardware at once and make sure this write + * isn't reordered. + */ + wmb(); + if (start_cycle) + start_trb->field[3] |= cpu_to_le32(start_cycle); + else + start_trb->field[3] &= cpu_to_le32(~TRB_CYCLE); + xhci_ring_ep_doorbell(xhci, slot_id, ep_index, stream_id); +} + +/* + * xHCI uses normal TRBs for both bulk and interrupt. When the interrupt + * endpoint is to be serviced, the xHC will consume (at most) one TD. A TD + * (comprised of sg list entries) can take several service intervals to + * transmit. + */ +int xhci_queue_intr_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, + xhci->devs[slot_id]->out_ctx, ep_index); + int xhci_interval; + int ep_interval; + + xhci_interval = EP_INTERVAL_TO_UFRAMES(le32_to_cpu(ep_ctx->ep_info)); + ep_interval = urb->interval; + /* Convert to microframes */ + if (urb->dev->speed == USB_SPEED_LOW || + urb->dev->speed == USB_SPEED_FULL) + ep_interval *= 8; + /* FIXME change this to a warning and a suggestion to use the new API + * to set the polling interval (once the API is added). + */ + if (xhci_interval != ep_interval) { + if (printk_ratelimit()) + dev_dbg(&urb->dev->dev, "Driver uses different interval" + " (%d microframe%s) than xHCI " + "(%d microframe%s)\n", + ep_interval, + ep_interval == 1 ? "" : "s", + xhci_interval, + xhci_interval == 1 ? "" : "s"); + urb->interval = xhci_interval; + /* Convert back to frames for LS/FS devices */ + if (urb->dev->speed == USB_SPEED_LOW || + urb->dev->speed == USB_SPEED_FULL) + urb->interval /= 8; + } + return xhci_queue_bulk_tx(xhci, mem_flags, urb, slot_id, ep_index); +} + +/* + * The TD size is the number of bytes remaining in the TD (including this TRB), + * right shifted by 10. + * It must fit in bits 21:17, so it can't be bigger than 31. + */ +static u32 xhci_td_remainder(unsigned int remainder) +{ + u32 max = (1 << (21 - 17 + 1)) - 1; + + if ((remainder >> 10) >= max) + return max << 17; + else + return (remainder >> 10) << 17; +} + +/* + * For xHCI 1.0 host controllers, TD size is the number of max packet sized + * packets remaining in the TD (*not* including this TRB). + * + * Total TD packet count = total_packet_count = + * DIV_ROUND_UP(TD size in bytes / wMaxPacketSize) + * + * Packets transferred up to and including this TRB = packets_transferred = + * rounddown(total bytes transferred including this TRB / wMaxPacketSize) + * + * TD size = total_packet_count - packets_transferred + * + * It must fit in bits 21:17, so it can't be bigger than 31. + * The last TRB in a TD must have the TD size set to zero. + */ +static u32 xhci_v1_0_td_remainder(int running_total, int trb_buff_len, + unsigned int total_packet_count, struct urb *urb, + unsigned int num_trbs_left) +{ + int packets_transferred; + + /* One TRB with a zero-length data packet. */ + if (num_trbs_left == 0 || (running_total == 0 && trb_buff_len == 0)) + return 0; + + /* All the TRB queueing functions don't count the current TRB in + * running_total. + */ + packets_transferred = (running_total + trb_buff_len) / + GET_MAX_PACKET(usb_endpoint_maxp(&urb->ep->desc)); + + if ((total_packet_count - packets_transferred) > 31) + return 31 << 17; + return (total_packet_count - packets_transferred) << 17; +} + +static int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + unsigned int num_trbs; + struct urb_priv *urb_priv; + struct xhci_td *td; + struct scatterlist *sg; + int num_sgs; + int trb_buff_len, this_sg_len, running_total; + unsigned int total_packet_count; + bool first_trb; + u64 addr; + bool more_trbs_coming; + + struct xhci_generic_trb *start_trb; + int start_cycle; + + ep_ring = xhci_urb_to_transfer_ring(xhci, urb); + if (!ep_ring) + return -EINVAL; + + num_trbs = count_sg_trbs_needed(xhci, urb); + num_sgs = urb->num_mapped_sgs; + total_packet_count = DIV_ROUND_UP(urb->transfer_buffer_length, + usb_endpoint_maxp(&urb->ep->desc)); + + trb_buff_len = prepare_transfer(xhci, xhci->devs[slot_id], + ep_index, urb->stream_id, + num_trbs, urb, 0, mem_flags); + if (trb_buff_len < 0) + return trb_buff_len; + + urb_priv = urb->hcpriv; + td = urb_priv->td[0]; + + /* + * Don't give the first TRB to the hardware (by toggling the cycle bit) + * until we've finished creating all the other TRBs. The ring's cycle + * state may change as we enqueue the other TRBs, so save it too. + */ + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + + running_total = 0; + /* + * How much data is in the first TRB? + * + * There are three forces at work for TRB buffer pointers and lengths: + * 1. We don't want to walk off the end of this sg-list entry buffer. + * 2. The transfer length that the driver requested may be smaller than + * the amount of memory allocated for this scatter-gather list. + * 3. TRBs buffers can't cross 64KB boundaries. + */ + sg = urb->sg; + addr = (u64) sg_dma_address(sg); + this_sg_len = sg_dma_len(sg); + trb_buff_len = TRB_MAX_BUFF_SIZE - (addr & (TRB_MAX_BUFF_SIZE - 1)); + trb_buff_len = min_t(int, trb_buff_len, this_sg_len); + if (trb_buff_len > urb->transfer_buffer_length) + trb_buff_len = urb->transfer_buffer_length; + + first_trb = true; + /* Queue the first TRB, even if it's zero-length */ + do { + u32 field = 0; + u32 length_field = 0; + u32 remainder = 0; + + /* Don't change the cycle bit of the first TRB until later */ + if (first_trb) { + first_trb = false; + if (start_cycle == 0) + field |= 0x1; + } else + field |= ep_ring->cycle_state; + + /* Chain all the TRBs together; clear the chain bit in the last + * TRB to indicate it's the last TRB in the chain. + */ + if (num_trbs > 1) { + field |= TRB_CHAIN; + } else { + /* FIXME - add check for ZERO_PACKET flag before this */ + td->last_trb = ep_ring->enqueue; + field |= TRB_IOC; + } + + /* Only set interrupt on short packet for IN endpoints */ + if (usb_urb_dir_in(urb)) + field |= TRB_ISP; + + if (TRB_MAX_BUFF_SIZE - + (addr & (TRB_MAX_BUFF_SIZE - 1)) < trb_buff_len) { + xhci_warn(xhci, "WARN: sg dma xfer crosses 64KB boundaries!\n"); + xhci_dbg(xhci, "Next boundary at %#x, end dma = %#x\n", + (unsigned int) (addr + TRB_MAX_BUFF_SIZE) & ~(TRB_MAX_BUFF_SIZE - 1), + (unsigned int) addr + trb_buff_len); + } + + /* Set the TRB length, TD size, and interrupter fields. */ + if (xhci->hci_version < 0x100) { + remainder = xhci_td_remainder( + urb->transfer_buffer_length - + running_total); + } else { + remainder = xhci_v1_0_td_remainder(running_total, + trb_buff_len, total_packet_count, urb, + num_trbs - 1); + } + length_field = TRB_LEN(trb_buff_len) | + remainder | + TRB_INTR_TARGET(0); + + if (num_trbs > 1) + more_trbs_coming = true; + else + more_trbs_coming = false; + queue_trb(xhci, ep_ring, more_trbs_coming, + lower_32_bits(addr), + upper_32_bits(addr), + length_field, + field | TRB_TYPE(TRB_NORMAL)); + --num_trbs; + running_total += trb_buff_len; + + /* Calculate length for next transfer -- + * Are we done queueing all the TRBs for this sg entry? + */ + this_sg_len -= trb_buff_len; + if (this_sg_len == 0) { + --num_sgs; + if (num_sgs == 0) + break; + sg = sg_next(sg); + addr = (u64) sg_dma_address(sg); + this_sg_len = sg_dma_len(sg); + } else { + addr += trb_buff_len; + } + + trb_buff_len = TRB_MAX_BUFF_SIZE - + (addr & (TRB_MAX_BUFF_SIZE - 1)); + trb_buff_len = min_t(int, trb_buff_len, this_sg_len); + if (running_total + trb_buff_len > urb->transfer_buffer_length) + trb_buff_len = + urb->transfer_buffer_length - running_total; + } while (running_total < urb->transfer_buffer_length); + + check_trb_math(urb, num_trbs, running_total); + giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id, + start_cycle, start_trb); + return 0; +} + +/* This is very similar to what ehci-q.c qtd_fill() does */ +int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + struct urb_priv *urb_priv; + struct xhci_td *td; + int num_trbs; + struct xhci_generic_trb *start_trb; + bool first_trb; + bool more_trbs_coming; + int start_cycle; + u32 field, length_field; + + int running_total, trb_buff_len, ret; + unsigned int total_packet_count; + u64 addr; + + if (urb->num_sgs) + return queue_bulk_sg_tx(xhci, mem_flags, urb, slot_id, ep_index); + + ep_ring = xhci_urb_to_transfer_ring(xhci, urb); + if (!ep_ring) + return -EINVAL; + + num_trbs = 0; + /* How much data is (potentially) left before the 64KB boundary? */ + running_total = TRB_MAX_BUFF_SIZE - + (urb->transfer_dma & (TRB_MAX_BUFF_SIZE - 1)); + running_total &= TRB_MAX_BUFF_SIZE - 1; + + /* If there's some data on this 64KB chunk, or we have to send a + * zero-length transfer, we need at least one TRB + */ + if (running_total != 0 || urb->transfer_buffer_length == 0) + num_trbs++; + /* How many more 64KB chunks to transfer, how many more TRBs? */ + while (running_total < urb->transfer_buffer_length) { + num_trbs++; + running_total += TRB_MAX_BUFF_SIZE; + } + /* FIXME: this doesn't deal with URB_ZERO_PACKET - need one more */ + + ret = prepare_transfer(xhci, xhci->devs[slot_id], + ep_index, urb->stream_id, + num_trbs, urb, 0, mem_flags); + if (ret < 0) + return ret; + + urb_priv = urb->hcpriv; + td = urb_priv->td[0]; + + /* + * Don't give the first TRB to the hardware (by toggling the cycle bit) + * until we've finished creating all the other TRBs. The ring's cycle + * state may change as we enqueue the other TRBs, so save it too. + */ + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + + running_total = 0; + total_packet_count = DIV_ROUND_UP(urb->transfer_buffer_length, + usb_endpoint_maxp(&urb->ep->desc)); + /* How much data is in the first TRB? */ + addr = (u64) urb->transfer_dma; + trb_buff_len = TRB_MAX_BUFF_SIZE - + (urb->transfer_dma & (TRB_MAX_BUFF_SIZE - 1)); + if (trb_buff_len > urb->transfer_buffer_length) + trb_buff_len = urb->transfer_buffer_length; + + first_trb = true; + + /* Queue the first TRB, even if it's zero-length */ + do { + u32 remainder = 0; + field = 0; + + /* Don't change the cycle bit of the first TRB until later */ + if (first_trb) { + first_trb = false; + if (start_cycle == 0) + field |= 0x1; + } else + field |= ep_ring->cycle_state; + + /* Chain all the TRBs together; clear the chain bit in the last + * TRB to indicate it's the last TRB in the chain. + */ + if (num_trbs > 1) { + field |= TRB_CHAIN; + } else { + /* FIXME - add check for ZERO_PACKET flag before this */ + td->last_trb = ep_ring->enqueue; + field |= TRB_IOC; + } + + /* Only set interrupt on short packet for IN endpoints */ + if (usb_urb_dir_in(urb)) + field |= TRB_ISP; + + /* Set the TRB length, TD size, and interrupter fields. */ + if (xhci->hci_version < 0x100) { + remainder = xhci_td_remainder( + urb->transfer_buffer_length - + running_total); + } else { + remainder = xhci_v1_0_td_remainder(running_total, + trb_buff_len, total_packet_count, urb, + num_trbs - 1); + } + length_field = TRB_LEN(trb_buff_len) | + remainder | + TRB_INTR_TARGET(0); + + if (num_trbs > 1) + more_trbs_coming = true; + else + more_trbs_coming = false; + queue_trb(xhci, ep_ring, more_trbs_coming, + lower_32_bits(addr), + upper_32_bits(addr), + length_field, + field | TRB_TYPE(TRB_NORMAL)); + --num_trbs; + running_total += trb_buff_len; + + /* Calculate length for next transfer */ + addr += trb_buff_len; + trb_buff_len = urb->transfer_buffer_length - running_total; + if (trb_buff_len > TRB_MAX_BUFF_SIZE) + trb_buff_len = TRB_MAX_BUFF_SIZE; + } while (running_total < urb->transfer_buffer_length); + + check_trb_math(urb, num_trbs, running_total); + giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id, + start_cycle, start_trb); + return 0; +} + +/* Caller must have locked xhci->lock */ +int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + int num_trbs; + int ret; + struct usb_ctrlrequest *setup; + struct xhci_generic_trb *start_trb; + int start_cycle; + u32 field, length_field; + struct urb_priv *urb_priv; + struct xhci_td *td; + + ep_ring = xhci_urb_to_transfer_ring(xhci, urb); + if (!ep_ring) + return -EINVAL; + + /* + * Need to copy setup packet into setup TRB, so we can't use the setup + * DMA address. + */ + if (!urb->setup_packet) + return -EINVAL; + + /* 1 TRB for setup, 1 for status */ + num_trbs = 2; + /* + * Don't need to check if we need additional event data and normal TRBs, + * since data in control transfers will never get bigger than 16MB + * XXX: can we get a buffer that crosses 64KB boundaries? + */ + if (urb->transfer_buffer_length > 0) + num_trbs++; + ret = prepare_transfer(xhci, xhci->devs[slot_id], + ep_index, urb->stream_id, + num_trbs, urb, 0, mem_flags); + if (ret < 0) + return ret; + + urb_priv = urb->hcpriv; + td = urb_priv->td[0]; + + /* + * Don't give the first TRB to the hardware (by toggling the cycle bit) + * until we've finished creating all the other TRBs. The ring's cycle + * state may change as we enqueue the other TRBs, so save it too. + */ + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + + /* Queue setup TRB - see section 6.4.1.2.1 */ + /* FIXME better way to translate setup_packet into two u32 fields? */ + setup = (struct usb_ctrlrequest *) urb->setup_packet; + field = 0; + field |= TRB_IDT | TRB_TYPE(TRB_SETUP); + if (start_cycle == 0) + field |= 0x1; + + /* xHCI 1.0 6.4.1.2.1: Transfer Type field */ + if (xhci->hci_version == 0x100) { + if (urb->transfer_buffer_length > 0) { + if (setup->bRequestType & USB_DIR_IN) + field |= TRB_TX_TYPE(TRB_DATA_IN); + else + field |= TRB_TX_TYPE(TRB_DATA_OUT); + } + } + + queue_trb(xhci, ep_ring, true, + setup->bRequestType | setup->bRequest << 8 | le16_to_cpu(setup->wValue) << 16, + le16_to_cpu(setup->wIndex) | le16_to_cpu(setup->wLength) << 16, + TRB_LEN(8) | TRB_INTR_TARGET(0), + /* Immediate data in pointer */ + field); + + /* If there's data, queue data TRBs */ + /* Only set interrupt on short packet for IN endpoints */ + if (usb_urb_dir_in(urb)) + field = TRB_ISP | TRB_TYPE(TRB_DATA); + else + field = TRB_TYPE(TRB_DATA); + + length_field = TRB_LEN(urb->transfer_buffer_length) | + xhci_td_remainder(urb->transfer_buffer_length) | + TRB_INTR_TARGET(0); + if (urb->transfer_buffer_length > 0) { + if (setup->bRequestType & USB_DIR_IN) + field |= TRB_DIR_IN; + queue_trb(xhci, ep_ring, true, + lower_32_bits(urb->transfer_dma), + upper_32_bits(urb->transfer_dma), + length_field, + field | ep_ring->cycle_state); + } + + /* Save the DMA address of the last TRB in the TD */ + td->last_trb = ep_ring->enqueue; + + /* Queue status TRB - see Table 7 and sections 4.11.2.2 and 6.4.1.2.3 */ + /* If the device sent data, the status stage is an OUT transfer */ + if (urb->transfer_buffer_length > 0 && setup->bRequestType & USB_DIR_IN) + field = 0; + else + field = TRB_DIR_IN; + queue_trb(xhci, ep_ring, false, + 0, + 0, + TRB_INTR_TARGET(0), + /* Event on completion */ + field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state); + + giveback_first_trb(xhci, slot_id, ep_index, 0, + start_cycle, start_trb); + return 0; +} + +static int count_isoc_trbs_needed(struct xhci_hcd *xhci, + struct urb *urb, int i) +{ + int num_trbs = 0; + u64 addr, td_len; + + addr = (u64) (urb->transfer_dma + urb->iso_frame_desc[i].offset); + td_len = urb->iso_frame_desc[i].length; + + num_trbs = DIV_ROUND_UP(td_len + (addr & (TRB_MAX_BUFF_SIZE - 1)), + TRB_MAX_BUFF_SIZE); + if (num_trbs == 0) + num_trbs++; + + return num_trbs; +} + +/* + * The transfer burst count field of the isochronous TRB defines the number of + * bursts that are required to move all packets in this TD. Only SuperSpeed + * devices can burst up to bMaxBurst number of packets per service interval. + * This field is zero based, meaning a value of zero in the field means one + * burst. Basically, for everything but SuperSpeed devices, this field will be + * zero. Only xHCI 1.0 host controllers support this field. + */ +static unsigned int xhci_get_burst_count(struct xhci_hcd *xhci, + struct usb_device *udev, + struct urb *urb, unsigned int total_packet_count) +{ + unsigned int max_burst; + + if (xhci->hci_version < 0x100 || udev->speed != USB_SPEED_SUPER) + return 0; + + max_burst = urb->ep->ss_ep_comp.bMaxBurst; + return DIV_ROUND_UP(total_packet_count, max_burst + 1) - 1; +} + +/* + * Returns the number of packets in the last "burst" of packets. This field is + * valid for all speeds of devices. USB 2.0 devices can only do one "burst", so + * the last burst packet count is equal to the total number of packets in the + * TD. SuperSpeed endpoints can have up to 3 bursts. All but the last burst + * must contain (bMaxBurst + 1) number of packets, but the last burst can + * contain 1 to (bMaxBurst + 1) packets. + */ +static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci, + struct usb_device *udev, + struct urb *urb, unsigned int total_packet_count) +{ + unsigned int max_burst; + unsigned int residue; + + if (xhci->hci_version < 0x100) + return 0; + + switch (udev->speed) { + case USB_SPEED_SUPER: + /* bMaxBurst is zero based: 0 means 1 packet per burst */ + max_burst = urb->ep->ss_ep_comp.bMaxBurst; + residue = total_packet_count % (max_burst + 1); + /* If residue is zero, the last burst contains (max_burst + 1) + * number of packets, but the TLBPC field is zero-based. + */ + if (residue == 0) + return max_burst; + return residue - 1; + default: + if (total_packet_count == 0) + return 0; + return total_packet_count - 1; + } +} + +/* This is for isoc transfer */ +static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + struct urb_priv *urb_priv; + struct xhci_td *td; + int num_tds, trbs_per_td; + struct xhci_generic_trb *start_trb; + bool first_trb; + int start_cycle; + u32 field, length_field; + int running_total, trb_buff_len, td_len, td_remain_len, ret; + u64 start_addr, addr; + int i, j; + bool more_trbs_coming; + + ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; + + num_tds = urb->number_of_packets; + if (num_tds < 1) { + xhci_dbg(xhci, "Isoc URB with zero packets?\n"); + return -EINVAL; + } + + start_addr = (u64) urb->transfer_dma; + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + + urb_priv = urb->hcpriv; + /* Queue the first TRB, even if it's zero-length */ + for (i = 0; i < num_tds; i++) { + unsigned int total_packet_count; + unsigned int burst_count; + unsigned int residue; + + first_trb = true; + running_total = 0; + addr = start_addr + urb->iso_frame_desc[i].offset; + td_len = urb->iso_frame_desc[i].length; + td_remain_len = td_len; + total_packet_count = DIV_ROUND_UP(td_len, + GET_MAX_PACKET( + usb_endpoint_maxp(&urb->ep->desc))); + /* A zero-length transfer still involves at least one packet. */ + if (total_packet_count == 0) + total_packet_count++; + burst_count = xhci_get_burst_count(xhci, urb->dev, urb, + total_packet_count); + residue = xhci_get_last_burst_packet_count(xhci, + urb->dev, urb, total_packet_count); + + trbs_per_td = count_isoc_trbs_needed(xhci, urb, i); + + ret = prepare_transfer(xhci, xhci->devs[slot_id], ep_index, + urb->stream_id, trbs_per_td, urb, i, mem_flags); + if (ret < 0) { + if (i == 0) + return ret; + goto cleanup; + } + + td = urb_priv->td[i]; + for (j = 0; j < trbs_per_td; j++) { + u32 remainder = 0; + field = 0; + + if (first_trb) { + field = TRB_TBC(burst_count) | + TRB_TLBPC(residue); + /* Queue the isoc TRB */ + field |= TRB_TYPE(TRB_ISOC); + /* Assume URB_ISO_ASAP is set */ + field |= TRB_SIA; + if (i == 0) { + if (start_cycle == 0) + field |= 0x1; + } else + field |= ep_ring->cycle_state; + first_trb = false; + } else { + /* Queue other normal TRBs */ + field |= TRB_TYPE(TRB_NORMAL); + field |= ep_ring->cycle_state; + } + + /* Only set interrupt on short packet for IN EPs */ + if (usb_urb_dir_in(urb)) + field |= TRB_ISP; + + /* Chain all the TRBs together; clear the chain bit in + * the last TRB to indicate it's the last TRB in the + * chain. + */ + if (j < trbs_per_td - 1) { + field |= TRB_CHAIN; + more_trbs_coming = true; + } else { + td->last_trb = ep_ring->enqueue; + field |= TRB_IOC; + if (xhci->hci_version == 0x100 && + !(xhci->quirks & + XHCI_AVOID_BEI)) { + /* Set BEI bit except for the last td */ + if (i < num_tds - 1) + field |= TRB_BEI; + } + more_trbs_coming = false; + } + + /* Calculate TRB length */ + trb_buff_len = TRB_MAX_BUFF_SIZE - + (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + if (trb_buff_len > td_remain_len) + trb_buff_len = td_remain_len; + + /* Set the TRB length, TD size, & interrupter fields. */ + if (xhci->hci_version < 0x100) { + remainder = xhci_td_remainder( + td_len - running_total); + } else { + remainder = xhci_v1_0_td_remainder( + running_total, trb_buff_len, + total_packet_count, urb, + (trbs_per_td - j - 1)); + } + length_field = TRB_LEN(trb_buff_len) | + remainder | + TRB_INTR_TARGET(0); + + queue_trb(xhci, ep_ring, more_trbs_coming, + lower_32_bits(addr), + upper_32_bits(addr), + length_field, + field); + running_total += trb_buff_len; + + addr += trb_buff_len; + td_remain_len -= trb_buff_len; + } + + /* Check TD length */ + if (running_total != td_len) { + xhci_err(xhci, "ISOC TD length unmatch\n"); + ret = -EINVAL; + goto cleanup; + } + } + + if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) { + if (xhci->quirks & XHCI_AMD_PLL_FIX) + usb_amd_quirk_pll_disable(); + } + xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs++; + + giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id, + start_cycle, start_trb); + return 0; +cleanup: + /* Clean up a partially enqueued isoc transfer. */ + + for (i--; i >= 0; i--) + list_del_init(&urb_priv->td[i]->td_list); + + /* Use the first TD as a temporary variable to turn the TDs we've queued + * into No-ops with a software-owned cycle bit. That way the hardware + * won't accidentally start executing bogus TDs when we partially + * overwrite them. td->first_trb and td->start_seg are already set. + */ + urb_priv->td[0]->last_trb = ep_ring->enqueue; + /* Every TRB except the first & last will have its cycle bit flipped. */ + td_to_noop(xhci, ep_ring, urb_priv->td[0], true); + + /* Reset the ring enqueue back to the first TRB and its cycle bit. */ + ep_ring->enqueue = urb_priv->td[0]->first_trb; + ep_ring->enq_seg = urb_priv->td[0]->start_seg; + ep_ring->cycle_state = start_cycle; + ep_ring->num_trbs_free = ep_ring->num_trbs_free_temp; + usb_hcd_unlink_urb_from_ep(bus_to_hcd(urb->dev->bus), urb); + return ret; +} + +/* + * Check transfer ring to guarantee there is enough room for the urb. + * Update ISO URB start_frame and interval. + * Update interval as xhci_queue_intr_tx does. Just use xhci frame_index to + * update the urb->start_frame by now. + * Always assume URB_ISO_ASAP set, and NEVER use urb->start_frame as input. + */ +int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_virt_device *xdev; + struct xhci_ring *ep_ring; + struct xhci_ep_ctx *ep_ctx; + int start_frame; + int xhci_interval; + int ep_interval; + int num_tds, num_trbs, i; + int ret; + + xdev = xhci->devs[slot_id]; + ep_ring = xdev->eps[ep_index].ring; + ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); + + num_trbs = 0; + num_tds = urb->number_of_packets; + for (i = 0; i < num_tds; i++) + num_trbs += count_isoc_trbs_needed(xhci, urb, i); + + /* Check the ring to guarantee there is enough room for the whole urb. + * Do not insert any td of the urb to the ring if the check failed. + */ + ret = prepare_ring(xhci, ep_ring, le32_to_cpu(ep_ctx->ep_info) & EP_STATE_MASK, + num_trbs, mem_flags); + if (ret) + return ret; + + start_frame = xhci_readl(xhci, &xhci->run_regs->microframe_index); + start_frame &= 0x3fff; + + urb->start_frame = start_frame; + if (urb->dev->speed == USB_SPEED_LOW || + urb->dev->speed == USB_SPEED_FULL) + urb->start_frame >>= 3; + + xhci_interval = EP_INTERVAL_TO_UFRAMES(le32_to_cpu(ep_ctx->ep_info)); + ep_interval = urb->interval; + /* Convert to microframes */ + if (urb->dev->speed == USB_SPEED_LOW || + urb->dev->speed == USB_SPEED_FULL) + ep_interval *= 8; + /* FIXME change this to a warning and a suggestion to use the new API + * to set the polling interval (once the API is added). + */ + if (xhci_interval != ep_interval) { + if (printk_ratelimit()) + dev_dbg(&urb->dev->dev, "Driver uses different interval" + " (%d microframe%s) than xHCI " + "(%d microframe%s)\n", + ep_interval, + ep_interval == 1 ? "" : "s", + xhci_interval, + xhci_interval == 1 ? "" : "s"); + urb->interval = xhci_interval; + /* Convert back to frames for LS/FS devices */ + if (urb->dev->speed == USB_SPEED_LOW || + urb->dev->speed == USB_SPEED_FULL) + urb->interval /= 8; + } + ep_ring->num_trbs_free_temp = ep_ring->num_trbs_free; + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); +} + +/**** Command Ring Operations ****/ + +/* Generic function for queueing a command TRB on the command ring. + * Check to make sure there's room on the command ring for one command TRB. + * Also check that there's room reserved for commands that must not fail. + * If this is a command that must not fail, meaning command_must_succeed = TRUE, + * then only check for the number of reserved spots. + * Don't decrement xhci->cmd_ring_reserved_trbs after we've queued the TRB + * because the command event handler may want to resubmit a failed command. + */ +static int queue_command(struct xhci_hcd *xhci, u32 field1, u32 field2, + u32 field3, u32 field4, bool command_must_succeed) +{ + int reserved_trbs = xhci->cmd_ring_reserved_trbs; + int ret; + + if (!command_must_succeed) + reserved_trbs++; + + ret = prepare_ring(xhci, xhci->cmd_ring, EP_STATE_RUNNING, + reserved_trbs, GFP_ATOMIC); + if (ret < 0) { + xhci_err(xhci, "ERR: No room for command on command ring\n"); + if (command_must_succeed) + xhci_err(xhci, "ERR: Reserved TRB counting for " + "unfailable commands failed.\n"); + return ret; + } + queue_trb(xhci, xhci->cmd_ring, false, field1, field2, field3, + field4 | xhci->cmd_ring->cycle_state); + return 0; +} + +/* Queue a slot enable or disable request on the command ring */ +int xhci_queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id) +{ + return queue_command(xhci, 0, 0, 0, + TRB_TYPE(trb_type) | SLOT_ID_FOR_TRB(slot_id), false); +} + +/* Queue an address device command TRB */ +int xhci_queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id) +{ + return queue_command(xhci, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_ADDR_DEV) | SLOT_ID_FOR_TRB(slot_id), + false); +} + +int xhci_queue_vendor_command(struct xhci_hcd *xhci, + u32 field1, u32 field2, u32 field3, u32 field4) +{ + return queue_command(xhci, field1, field2, field3, field4, false); +} + +/* Queue a reset device command TRB */ +int xhci_queue_reset_device(struct xhci_hcd *xhci, u32 slot_id) +{ + return queue_command(xhci, 0, 0, 0, + TRB_TYPE(TRB_RESET_DEV) | SLOT_ID_FOR_TRB(slot_id), + false); +} + +/* Queue a configure endpoint command TRB */ +int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id, bool command_must_succeed) +{ + return queue_command(xhci, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_CONFIG_EP) | SLOT_ID_FOR_TRB(slot_id), + command_must_succeed); +} + +/* Queue an evaluate context command TRB */ +int xhci_queue_evaluate_context(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id, bool command_must_succeed) +{ + return queue_command(xhci, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_EVAL_CONTEXT) | SLOT_ID_FOR_TRB(slot_id), + command_must_succeed); +} + +/* + * Suspend is set to indicate "Stop Endpoint Command" is being issued to stop + * activity on an endpoint that is about to be suspended. + */ +int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index, int suspend) +{ + u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); + u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); + u32 type = TRB_TYPE(TRB_STOP_RING); + u32 trb_suspend = SUSPEND_PORT_FOR_TRB(suspend); + + return queue_command(xhci, 0, 0, 0, + trb_slot_id | trb_ep_index | type | trb_suspend, false); +} + +/* Set Transfer Ring Dequeue Pointer command. + * This should not be used for endpoints that have streams enabled. + */ +static int queue_set_tr_deq(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index, unsigned int stream_id, + struct xhci_segment *deq_seg, + union xhci_trb *deq_ptr, u32 cycle_state) +{ + dma_addr_t addr; + u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); + u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); + u32 trb_stream_id = STREAM_ID_FOR_TRB(stream_id); + u32 type = TRB_TYPE(TRB_SET_DEQ); + struct xhci_virt_ep *ep; + + addr = xhci_trb_virt_to_dma(deq_seg, deq_ptr); + if (addr == 0) { + xhci_warn(xhci, "WARN Cannot submit Set TR Deq Ptr\n"); + xhci_warn(xhci, "WARN deq seg = %p, deq pt = %p\n", + deq_seg, deq_ptr); + return 0; + } + ep = &xhci->devs[slot_id]->eps[ep_index]; + if ((ep->ep_state & SET_DEQ_PENDING)) { + xhci_warn(xhci, "WARN Cannot submit Set TR Deq Ptr\n"); + xhci_warn(xhci, "A Set TR Deq Ptr command is pending.\n"); + return 0; + } + ep->queued_deq_seg = deq_seg; + ep->queued_deq_ptr = deq_ptr; + return queue_command(xhci, lower_32_bits(addr) | cycle_state, + upper_32_bits(addr), trb_stream_id, + trb_slot_id | trb_ep_index | type, false); +} + +int xhci_queue_reset_ep(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index) +{ + u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); + u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); + u32 type = TRB_TYPE(TRB_RESET_EP); + + return queue_command(xhci, 0, 0, 0, trb_slot_id | trb_ep_index | type, + false); +} diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c new file mode 100644 index 00000000..10223f2b --- /dev/null +++ b/drivers/usb/host/xhci.c @@ -0,0 +1,4813 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" + +#define DRIVER_AUTHOR "Sarah Sharp" +#define DRIVER_DESC "'eXtensible' Host Controller (xHC) Driver" + +/* Some 0.95 hardware can't handle the chain bit on a Link TRB being cleared */ +static int link_quirk; +module_param(link_quirk, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(link_quirk, "Don't clear the chain bit on a link TRB"); + +/* TODO: copied from ehci-hcd.c - can this be refactored? */ +/* + * xhci_handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + */ +int xhci_handshake(struct xhci_hcd *xhci, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = xhci_readl(xhci, ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* + * Disable interrupts and begin the xHCI halting process. + */ +void xhci_quiesce(struct xhci_hcd *xhci) +{ + u32 halted; + u32 cmd; + u32 mask; + + mask = ~(XHCI_IRQS); + halted = xhci_readl(xhci, &xhci->op_regs->status) & STS_HALT; + if (!halted) + mask &= ~CMD_RUN; + + cmd = xhci_readl(xhci, &xhci->op_regs->command); + cmd &= mask; + xhci_writel(xhci, cmd, &xhci->op_regs->command); +} + +/* + * Force HC into halt state. + * + * Disable any IRQs and clear the run/stop bit. + * HC will complete any current and actively pipelined transactions, and + * should halt within 16 ms of the run/stop bit being cleared. + * Read HC Halted bit in the status register to see when the HC is finished. + */ +int xhci_halt(struct xhci_hcd *xhci) +{ + int ret; + xhci_dbg(xhci, "// Halt the HC\n"); + xhci_quiesce(xhci); + + ret = xhci_handshake(xhci, &xhci->op_regs->status, + STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC); + if (!ret) { + xhci->xhc_state |= XHCI_STATE_HALTED; + xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + } else + xhci_warn(xhci, "Host not halted after %u microseconds.\n", + XHCI_MAX_HALT_USEC); + return ret; +} + +/* + * Set the run bit and wait for the host to be running. + */ +static int xhci_start(struct xhci_hcd *xhci) +{ + u32 temp; + int ret; + + temp = xhci_readl(xhci, &xhci->op_regs->command); + temp |= (CMD_RUN); + xhci_dbg(xhci, "// Turn on HC, cmd = 0x%x.\n", + temp); + xhci_writel(xhci, temp, &xhci->op_regs->command); + + /* + * Wait for the HCHalted Status bit to be 0 to indicate the host is + * running. + */ + ret = xhci_handshake(xhci, &xhci->op_regs->status, + STS_HALT, 0, XHCI_MAX_HALT_USEC); + if (ret == -ETIMEDOUT) + xhci_err(xhci, "Host took too long to start, " + "waited %u microseconds.\n", + XHCI_MAX_HALT_USEC); + if (!ret) + xhci->xhc_state &= ~XHCI_STATE_HALTED; + return ret; +} + +/* + * Reset a halted HC. + * + * This resets pipelines, timers, counters, state machines, etc. + * Transactions will be terminated immediately, and operational registers + * will be set to their defaults. + */ +int xhci_reset(struct xhci_hcd *xhci) +{ + u32 command; + u32 state; + int ret, i; + + state = xhci_readl(xhci, &xhci->op_regs->status); + if ((state & STS_HALT) == 0) { + xhci_warn(xhci, "Host controller not halted, aborting reset.\n"); + return 0; + } + + xhci_dbg(xhci, "// Reset the HC\n"); + command = xhci_readl(xhci, &xhci->op_regs->command); + command |= CMD_RESET; + xhci_writel(xhci, command, &xhci->op_regs->command); + + ret = xhci_handshake(xhci, &xhci->op_regs->command, + CMD_RESET, 0, 10 * 1000 * 1000); + if (ret) + return ret; + + xhci_dbg(xhci, "Wait for controller to be ready for doorbell rings\n"); + /* + * xHCI cannot write to any doorbells or operational registers other + * than status until the "Controller Not Ready" flag is cleared. + */ + ret = xhci_handshake(xhci, &xhci->op_regs->status, + STS_CNR, 0, 10 * 1000 * 1000); + + for (i = 0; i < 2; ++i) { + xhci->bus_state[i].port_c_suspend = 0; + xhci->bus_state[i].suspended_ports = 0; + xhci->bus_state[i].resuming_ports = 0; + } + + return ret; +} + +#ifdef CONFIG_PCI +static int xhci_free_msi(struct xhci_hcd *xhci) +{ + int i; + + if (!xhci->msix_entries) + return -EINVAL; + + for (i = 0; i < xhci->msix_count; i++) + if (xhci->msix_entries[i].vector) + free_irq(xhci->msix_entries[i].vector, + xhci_to_hcd(xhci)); + return 0; +} + +/* + * Set up MSI + */ +static int xhci_setup_msi(struct xhci_hcd *xhci) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + + ret = pci_enable_msi(pdev); + if (ret) { + xhci_dbg(xhci, "failed to allocate MSI entry\n"); + return ret; + } + + ret = request_irq(pdev->irq, (irq_handler_t)xhci_msi_irq, + 0, "xhci_hcd", xhci_to_hcd(xhci)); + if (ret) { + xhci_dbg(xhci, "disable MSI interrupt\n"); + pci_disable_msi(pdev); + } + + return ret; +} + +/* + * Free IRQs + * free all IRQs request + */ +static void xhci_free_irq(struct xhci_hcd *xhci) +{ + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + int ret; + + /* return if using legacy interrupt */ + if (xhci_to_hcd(xhci)->irq > 0) + return; + + ret = xhci_free_msi(xhci); + if (!ret) + return; + if (pdev->irq > 0) + free_irq(pdev->irq, xhci_to_hcd(xhci)); + + return; +} + +/* + * Set up MSI-X + */ +static int xhci_setup_msix(struct xhci_hcd *xhci) +{ + int i, ret = 0; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + + /* + * calculate number of msi-x vectors supported. + * - HCS_MAX_INTRS: the max number of interrupts the host can handle, + * with max number of interrupters based on the xhci HCSPARAMS1. + * - num_online_cpus: maximum msi-x vectors per CPUs core. + * Add additional 1 vector to ensure always available interrupt. + */ + xhci->msix_count = min(num_online_cpus() + 1, + HCS_MAX_INTRS(xhci->hcs_params1)); + + xhci->msix_entries = + kmalloc((sizeof(struct msix_entry))*xhci->msix_count, + GFP_KERNEL); + if (!xhci->msix_entries) { + xhci_err(xhci, "Failed to allocate MSI-X entries\n"); + return -ENOMEM; + } + + for (i = 0; i < xhci->msix_count; i++) { + xhci->msix_entries[i].entry = i; + xhci->msix_entries[i].vector = 0; + } + + ret = pci_enable_msix(pdev, xhci->msix_entries, xhci->msix_count); + if (ret) { + xhci_dbg(xhci, "Failed to enable MSI-X\n"); + goto free_entries; + } + + for (i = 0; i < xhci->msix_count; i++) { + ret = request_irq(xhci->msix_entries[i].vector, + (irq_handler_t)xhci_msi_irq, + 0, "xhci_hcd", xhci_to_hcd(xhci)); + if (ret) + goto disable_msix; + } + + hcd->msix_enabled = 1; + return ret; + +disable_msix: + xhci_dbg(xhci, "disable MSI-X interrupt\n"); + xhci_free_irq(xhci); + pci_disable_msix(pdev); +free_entries: + kfree(xhci->msix_entries); + xhci->msix_entries = NULL; + return ret; +} + +/* Free any IRQs and disable MSI-X */ +static void xhci_cleanup_msix(struct xhci_hcd *xhci) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + + if (xhci->quirks & XHCI_PLAT) + return; + + xhci_free_irq(xhci); + + if (xhci->msix_entries) { + pci_disable_msix(pdev); + kfree(xhci->msix_entries); + xhci->msix_entries = NULL; + } else { + pci_disable_msi(pdev); + } + + hcd->msix_enabled = 0; + return; +} + +static void xhci_msix_sync_irqs(struct xhci_hcd *xhci) +{ + int i; + + if (xhci->msix_entries) { + for (i = 0; i < xhci->msix_count; i++) + synchronize_irq(xhci->msix_entries[i].vector); + } +} + +static int xhci_try_enable_msi(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct pci_dev *pdev; + int ret; + + /* The xhci platform device has set up IRQs through usb_add_hcd. */ + if (xhci->quirks & XHCI_PLAT) + return 0; + + pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + /* + * Some Fresco Logic host controllers advertise MSI, but fail to + * generate interrupts. Don't even try to enable MSI. + */ + if (xhci->quirks & XHCI_BROKEN_MSI) + goto legacy_irq; + + /* unregister the legacy interrupt */ + if (hcd->irq) + free_irq(hcd->irq, hcd); + hcd->irq = 0; + + ret = xhci_setup_msix(xhci); + if (ret) + /* fall back to msi*/ + ret = xhci_setup_msi(xhci); + + if (!ret) + /* hcd->irq is 0, we have MSI */ + return 0; + + if (!pdev->irq) { + xhci_err(xhci, "No msi-x/msi found and no IRQ in BIOS\n"); + return -EINVAL; + } + + legacy_irq: + /* fall back to legacy interrupt*/ + ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED, + hcd->irq_descr, hcd); + if (ret) { + xhci_err(xhci, "request interrupt %d failed\n", + pdev->irq); + return ret; + } + hcd->irq = pdev->irq; + return 0; +} + +#else + +static inline int xhci_try_enable_msi(struct usb_hcd *hcd) +{ + return 0; +} + +static inline void xhci_cleanup_msix(struct xhci_hcd *xhci) +{ +} + +static inline void xhci_msix_sync_irqs(struct xhci_hcd *xhci) +{ +} + +#endif + +static void compliance_mode_recovery(unsigned long arg) +{ + struct xhci_hcd *xhci; + struct usb_hcd *hcd; + u32 temp; + int i; + + xhci = (struct xhci_hcd *)arg; + + for (i = 0; i < xhci->num_usb3_ports; i++) { + temp = xhci_readl(xhci, xhci->usb3_ports[i]); + if ((temp & PORT_PLS_MASK) == USB_SS_PORT_LS_COMP_MOD) { + /* + * Compliance Mode Detected. Letting USB Core + * handle the Warm Reset + */ + xhci_dbg(xhci, "Compliance mode detected->port %d\n", + i + 1); + xhci_dbg(xhci, "Attempting compliance mode recovery\n"); + hcd = xhci->shared_hcd; + + if (hcd->state == HC_STATE_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + + usb_hcd_poll_rh_status(hcd); + } + } + + if (xhci->port_status_u0 != ((1 << xhci->num_usb3_ports)-1)) + mod_timer(&xhci->comp_mode_recovery_timer, + jiffies + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS)); +} + +/* + * Quirk to work around issue generated by the SN65LVPE502CP USB3.0 re-driver + * that causes ports behind that hardware to enter compliance mode sometimes. + * The quirk creates a timer that polls every 2 seconds the link state of + * each host controller's port and recovers it by issuing a Warm reset + * if Compliance mode is detected, otherwise the port will become "dead" (no + * device connections or disconnections will be detected anymore). Becasue no + * status event is generated when entering compliance mode (per xhci spec), + * this quirk is needed on systems that have the failing hardware installed. + */ +static void compliance_mode_recovery_timer_init(struct xhci_hcd *xhci) +{ + xhci->port_status_u0 = 0; + init_timer(&xhci->comp_mode_recovery_timer); + + xhci->comp_mode_recovery_timer.data = (unsigned long) xhci; + xhci->comp_mode_recovery_timer.function = compliance_mode_recovery; + xhci->comp_mode_recovery_timer.expires = jiffies + + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS); + + set_timer_slack(&xhci->comp_mode_recovery_timer, + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS)); + add_timer(&xhci->comp_mode_recovery_timer); + xhci_dbg(xhci, "Compliance mode recovery timer initialized\n"); +} + +/* + * This function identifies the systems that have installed the SN65LVPE502CP + * USB3.0 re-driver and that need the Compliance Mode Quirk. + * Systems: + * Vendor: Hewlett-Packard -> System Models: Z420, Z620 and Z820 + */ +bool xhci_compliance_mode_recovery_timer_quirk_check(void) +{ + const char *dmi_product_name, *dmi_sys_vendor; + + dmi_product_name = dmi_get_system_info(DMI_PRODUCT_NAME); + dmi_sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + if (!dmi_product_name || !dmi_sys_vendor) + return false; + + if (!(strstr(dmi_sys_vendor, "Hewlett-Packard"))) + return false; + + if (strstr(dmi_product_name, "Z420") || + strstr(dmi_product_name, "Z620") || + strstr(dmi_product_name, "Z820") || + strstr(dmi_product_name, "Z1 Workstation")) + return true; + + return false; +} + +static int xhci_all_ports_seen_u0(struct xhci_hcd *xhci) +{ + return (xhci->port_status_u0 == ((1 << xhci->num_usb3_ports)-1)); +} + + +/* + * Initialize memory for HCD and xHC (one-time init). + * + * Program the PAGESIZE register, initialize the device context array, create + * device contexts (?), set up a command ring segment (or two?), create event + * ring (one for now). + */ +int xhci_init(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int retval = 0; + + xhci_dbg(xhci, "xhci_init\n"); + spin_lock_init(&xhci->lock); + if (xhci->hci_version == 0x95 && link_quirk) { + xhci_dbg(xhci, "QUIRK: Not clearing Link TRB chain bits.\n"); + xhci->quirks |= XHCI_LINK_TRB_QUIRK; + } else { + xhci_dbg(xhci, "xHCI doesn't need link TRB QUIRK\n"); + } + retval = xhci_mem_init(xhci, GFP_KERNEL); + xhci_dbg(xhci, "Finished xhci_init\n"); + + /* Initializing Compliance Mode Recovery Data If Needed */ + if (xhci_compliance_mode_recovery_timer_quirk_check()) { + xhci->quirks |= XHCI_COMP_MODE_QUIRK; + compliance_mode_recovery_timer_init(xhci); + } + + return retval; +} + +/*-------------------------------------------------------------------------*/ + + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING +static void xhci_event_ring_work(unsigned long arg) +{ + unsigned long flags; + int temp; + u64 temp_64; + struct xhci_hcd *xhci = (struct xhci_hcd *) arg; + int i, j; + + xhci_dbg(xhci, "Poll event ring: %lu\n", jiffies); + + spin_lock_irqsave(&xhci->lock, flags); + temp = xhci_readl(xhci, &xhci->op_regs->status); + xhci_dbg(xhci, "op reg status = 0x%x\n", temp); + if (temp == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING) || + (xhci->xhc_state & XHCI_STATE_HALTED)) { + xhci_dbg(xhci, "HW died, polling stopped.\n"); + spin_unlock_irqrestore(&xhci->lock, flags); + return; + } + + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); + xhci_dbg(xhci, "ir_set 0 pending = 0x%x\n", temp); + xhci_dbg(xhci, "HC error bitmask = 0x%x\n", xhci->error_bitmask); + xhci->error_bitmask = 0; + xhci_dbg(xhci, "Event ring:\n"); + xhci_debug_segment(xhci, xhci->event_ring->deq_seg); + xhci_dbg_ring_ptrs(xhci, xhci->event_ring); + temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + temp_64 &= ~ERST_PTR_MASK; + xhci_dbg(xhci, "ERST deq = 64'h%0lx\n", (long unsigned int) temp_64); + xhci_dbg(xhci, "Command ring:\n"); + xhci_debug_segment(xhci, xhci->cmd_ring->deq_seg); + xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring); + xhci_dbg_cmd_ptrs(xhci); + for (i = 0; i < MAX_HC_SLOTS; ++i) { + if (!xhci->devs[i]) + continue; + for (j = 0; j < 31; ++j) { + xhci_dbg_ep_rings(xhci, i, j, &xhci->devs[i]->eps[j]); + } + } + spin_unlock_irqrestore(&xhci->lock, flags); + + if (!xhci->zombie) + mod_timer(&xhci->event_ring_timer, jiffies + POLL_TIMEOUT * HZ); + else + xhci_dbg(xhci, "Quit polling the event ring.\n"); +} +#endif + +static int xhci_run_finished(struct xhci_hcd *xhci) +{ + if (xhci_start(xhci)) { + xhci_halt(xhci); + return -ENODEV; + } + xhci->shared_hcd->state = HC_STATE_RUNNING; + xhci->cmd_ring_state = CMD_RING_STATE_RUNNING; + + if (xhci->quirks & XHCI_NEC_HOST) + xhci_ring_cmd_db(xhci); + + xhci_dbg(xhci, "Finished xhci_run for USB3 roothub\n"); + return 0; +} + +/* + * Start the HC after it was halted. + * + * This function is called by the USB core when the HC driver is added. + * Its opposite is xhci_stop(). + * + * xhci_init() must be called once before this function can be called. + * Reset the HC, enable device slot contexts, program DCBAAP, and + * set command ring pointer and event ring pointer. + * + * Setup MSI-X vectors and enable interrupts. + */ +int xhci_run(struct usb_hcd *hcd) +{ + u32 temp; + u64 temp_64; + int ret; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + /* Start the xHCI host controller running only after the USB 2.0 roothub + * is setup. + */ + + hcd->uses_new_polling = 1; + if (!usb_hcd_is_primary_hcd(hcd)) + return xhci_run_finished(xhci); + + xhci_dbg(xhci, "xhci_run\n"); + + ret = xhci_try_enable_msi(hcd); + if (ret) + return ret; + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING + init_timer(&xhci->event_ring_timer); + xhci->event_ring_timer.data = (unsigned long) xhci; + xhci->event_ring_timer.function = xhci_event_ring_work; + /* Poll the event ring */ + xhci->event_ring_timer.expires = jiffies + POLL_TIMEOUT * HZ; + xhci->zombie = 0; + xhci_dbg(xhci, "Setting event ring polling timer\n"); + add_timer(&xhci->event_ring_timer); +#endif + + xhci_dbg(xhci, "Command ring memory map follows:\n"); + xhci_debug_ring(xhci, xhci->cmd_ring); + xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring); + xhci_dbg_cmd_ptrs(xhci); + + xhci_dbg(xhci, "ERST memory map follows:\n"); + xhci_dbg_erst(xhci, &xhci->erst); + xhci_dbg(xhci, "Event ring:\n"); + xhci_debug_ring(xhci, xhci->event_ring); + xhci_dbg_ring_ptrs(xhci, xhci->event_ring); + temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + temp_64 &= ~ERST_PTR_MASK; + xhci_dbg(xhci, "ERST deq = 64'h%0lx\n", (long unsigned int) temp_64); + + xhci_dbg(xhci, "// Set the interrupt modulation register\n"); + temp = xhci_readl(xhci, &xhci->ir_set->irq_control); + temp &= ~ER_IRQ_INTERVAL_MASK; + temp |= (u32) 160; + xhci_writel(xhci, temp, &xhci->ir_set->irq_control); + + /* Set the HCD state before we enable the irqs */ + temp = xhci_readl(xhci, &xhci->op_regs->command); + temp |= (CMD_EIE); + xhci_dbg(xhci, "// Enable interrupts, cmd = 0x%x.\n", + temp); + xhci_writel(xhci, temp, &xhci->op_regs->command); + + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); + xhci_dbg(xhci, "// Enabling event ring interrupter %p by writing 0x%x to irq_pending\n", + xhci->ir_set, (unsigned int) ER_IRQ_ENABLE(temp)); + xhci_writel(xhci, ER_IRQ_ENABLE(temp), + &xhci->ir_set->irq_pending); + xhci_print_ir_set(xhci, 0); + + if (xhci->quirks & XHCI_NEC_HOST) + xhci_queue_vendor_command(xhci, 0, 0, 0, + TRB_TYPE(TRB_NEC_GET_FW)); + + xhci_dbg(xhci, "Finished xhci_run for USB2 roothub\n"); + return 0; +} + +static void xhci_only_stop_hcd(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + spin_lock_irq(&xhci->lock); + xhci_halt(xhci); + + /* The shared_hcd is going to be deallocated shortly (the USB core only + * calls this function when allocation fails in usb_add_hcd(), or + * usb_remove_hcd() is called). So we need to unset xHCI's pointer. + */ + xhci->shared_hcd = NULL; + spin_unlock_irq(&xhci->lock); +} + +/* + * Stop xHCI driver. + * + * This function is called by the USB core when the HC driver is removed. + * Its opposite is xhci_run(). + * + * Disable device contexts, disable IRQs, and quiesce the HC. + * Reset the HC, finish any completed transactions, and cleanup memory. + */ +void xhci_stop(struct usb_hcd *hcd) +{ + u32 temp; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + if (!usb_hcd_is_primary_hcd(hcd)) { + xhci_only_stop_hcd(xhci->shared_hcd); + return; + } + + spin_lock_irq(&xhci->lock); + /* Make sure the xHC is halted for a USB3 roothub + * (xhci_stop() could be called as part of failed init). + */ + xhci_halt(xhci); + xhci_reset(xhci); + spin_unlock_irq(&xhci->lock); + + xhci_cleanup_msix(xhci); + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING + /* Tell the event ring poll function not to reschedule */ + xhci->zombie = 1; + del_timer_sync(&xhci->event_ring_timer); +#endif + + /* Deleting Compliance Mode Recovery Timer */ + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && + (!(xhci_all_ports_seen_u0(xhci)))) { + del_timer_sync(&xhci->comp_mode_recovery_timer); + xhci_dbg(xhci, "%s: compliance mode recovery timer deleted\n", + __func__); + } + + if (xhci->quirks & XHCI_AMD_PLL_FIX) + usb_amd_dev_put(); + + xhci_dbg(xhci, "// Disabling event ring interrupts\n"); + temp = xhci_readl(xhci, &xhci->op_regs->status); + xhci_writel(xhci, temp & ~STS_EINT, &xhci->op_regs->status); + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); + xhci_writel(xhci, ER_IRQ_DISABLE(temp), + &xhci->ir_set->irq_pending); + xhci_print_ir_set(xhci, 0); + + xhci_dbg(xhci, "cleaning up memory\n"); + xhci_mem_cleanup(xhci); + xhci_dbg(xhci, "xhci_stop completed - status = %x\n", + xhci_readl(xhci, &xhci->op_regs->status)); +} + +/* + * Shutdown HC (not bus-specific) + * + * This is called when the machine is rebooting or halting. We assume that the + * machine will be powered off, and the HC's internal state will be reset. + * Don't bother to free memory. + * + * This will only ever be called with the main usb_hcd (the USB3 roothub). + */ +void xhci_shutdown(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + if (xhci->quirks & XHCI_SPURIOUS_REBOOT) + usb_disable_xhci_ports(to_pci_dev(hcd->self.controller)); + + spin_lock_irq(&xhci->lock); + xhci_halt(xhci); + spin_unlock_irq(&xhci->lock); + + xhci_cleanup_msix(xhci); + + xhci_dbg(xhci, "xhci_shutdown completed - status = %x\n", + xhci_readl(xhci, &xhci->op_regs->status)); +} + +#ifdef CONFIG_PM +static void xhci_save_registers(struct xhci_hcd *xhci) +{ + xhci->s3.command = xhci_readl(xhci, &xhci->op_regs->command); + xhci->s3.dev_nt = xhci_readl(xhci, &xhci->op_regs->dev_notification); + xhci->s3.dcbaa_ptr = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); + xhci->s3.config_reg = xhci_readl(xhci, &xhci->op_regs->config_reg); + xhci->s3.erst_size = xhci_readl(xhci, &xhci->ir_set->erst_size); + xhci->s3.erst_base = xhci_read_64(xhci, &xhci->ir_set->erst_base); + xhci->s3.erst_dequeue = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + xhci->s3.irq_pending = xhci_readl(xhci, &xhci->ir_set->irq_pending); + xhci->s3.irq_control = xhci_readl(xhci, &xhci->ir_set->irq_control); +} + +static void xhci_restore_registers(struct xhci_hcd *xhci) +{ + xhci_writel(xhci, xhci->s3.command, &xhci->op_regs->command); + xhci_writel(xhci, xhci->s3.dev_nt, &xhci->op_regs->dev_notification); + xhci_write_64(xhci, xhci->s3.dcbaa_ptr, &xhci->op_regs->dcbaa_ptr); + xhci_writel(xhci, xhci->s3.config_reg, &xhci->op_regs->config_reg); + xhci_writel(xhci, xhci->s3.erst_size, &xhci->ir_set->erst_size); + xhci_write_64(xhci, xhci->s3.erst_base, &xhci->ir_set->erst_base); + xhci_write_64(xhci, xhci->s3.erst_dequeue, &xhci->ir_set->erst_dequeue); + xhci_writel(xhci, xhci->s3.irq_pending, &xhci->ir_set->irq_pending); + xhci_writel(xhci, xhci->s3.irq_control, &xhci->ir_set->irq_control); +} + +static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) +{ + u64 val_64; + + /* step 2: initialize command ring buffer */ + val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | + (xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, + xhci->cmd_ring->dequeue) & + (u64) ~CMD_RING_RSVD_BITS) | + xhci->cmd_ring->cycle_state; + xhci_dbg(xhci, "// Setting command ring address to 0x%llx\n", + (long unsigned long) val_64); + xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); +} + +/* + * The whole command ring must be cleared to zero when we suspend the host. + * + * The host doesn't save the command ring pointer in the suspend well, so we + * need to re-program it on resume. Unfortunately, the pointer must be 64-byte + * aligned, because of the reserved bits in the command ring dequeue pointer + * register. Therefore, we can't just set the dequeue pointer back in the + * middle of the ring (TRBs are 16-byte aligned). + */ +static void xhci_clear_command_ring(struct xhci_hcd *xhci) +{ + struct xhci_ring *ring; + struct xhci_segment *seg; + + ring = xhci->cmd_ring; + seg = ring->deq_seg; + do { + memset(seg->trbs, 0, + sizeof(union xhci_trb) * (TRBS_PER_SEGMENT - 1)); + seg->trbs[TRBS_PER_SEGMENT - 1].link.control &= + cpu_to_le32(~TRB_CYCLE); + seg = seg->next; + } while (seg != ring->deq_seg); + + /* Reset the software enqueue and dequeue pointers */ + ring->deq_seg = ring->first_seg; + ring->dequeue = ring->first_seg->trbs; + ring->enq_seg = ring->deq_seg; + ring->enqueue = ring->dequeue; + + ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1; + /* + * Ring is now zeroed, so the HW should look for change of ownership + * when the cycle bit is set to 1. + */ + ring->cycle_state = 1; + + /* + * Reset the hardware dequeue pointer. + * Yes, this will need to be re-written after resume, but we're paranoid + * and want to make sure the hardware doesn't access bogus memory + * because, say, the BIOS or an SMI started the host without changing + * the command ring pointers. + */ + xhci_set_cmd_ring_deq(xhci); +} + +/* + * Stop HC (not bus-specific) + * + * This is called when the machine transition into S3/S4 mode. + * + */ +int xhci_suspend(struct xhci_hcd *xhci) +{ + int rc = 0; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + u32 command; + + if (hcd->state != HC_STATE_SUSPENDED || + xhci->shared_hcd->state != HC_STATE_SUSPENDED) + return -EINVAL; + + /* Don't poll the roothubs on bus suspend. */ + xhci_dbg(xhci, "%s: stopping port polling.\n", __func__); + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + del_timer_sync(&hcd->rh_timer); + + spin_lock_irq(&xhci->lock); + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags); + /* step 1: stop endpoint */ + /* skipped assuming that port suspend has done */ + + /* step 2: clear Run/Stop bit */ + command = xhci_readl(xhci, &xhci->op_regs->command); + command &= ~CMD_RUN; + xhci_writel(xhci, command, &xhci->op_regs->command); + if (xhci_handshake(xhci, &xhci->op_regs->status, + STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC)) { + xhci_warn(xhci, "WARN: xHC CMD_RUN timeout\n"); + spin_unlock_irq(&xhci->lock); + return -ETIMEDOUT; + } + xhci_clear_command_ring(xhci); + + /* step 3: save registers */ + xhci_save_registers(xhci); + + /* step 4: set CSS flag */ + command = xhci_readl(xhci, &xhci->op_regs->command); + command |= CMD_CSS; + xhci_writel(xhci, command, &xhci->op_regs->command); + if (xhci_handshake(xhci, &xhci->op_regs->status, + STS_SAVE, 0, 10 * 1000)) { + xhci_warn(xhci, "WARN: xHC save state timeout\n"); + spin_unlock_irq(&xhci->lock); + return -ETIMEDOUT; + } + spin_unlock_irq(&xhci->lock); + + /* + * Deleting Compliance Mode Recovery Timer because the xHCI Host + * is about to be suspended. + */ + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && + (!(xhci_all_ports_seen_u0(xhci)))) { + del_timer_sync(&xhci->comp_mode_recovery_timer); + xhci_dbg(xhci, "%s: compliance mode recovery timer deleted\n", + __func__); + } + + /* step 5: remove core well power */ + /* synchronize irq when using MSI-X */ + xhci_msix_sync_irqs(xhci); + + return rc; +} + +/* + * start xHC (not bus-specific) + * + * This is called when the machine transition from S3/S4 mode. + * + */ +int xhci_resume(struct xhci_hcd *xhci, bool hibernated) +{ + u32 command, temp = 0, status; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct usb_hcd *secondary_hcd; + int retval = 0; + bool comp_timer_running = false; + + /* Wait a bit if either of the roothubs need to settle from the + * transition into bus suspend. + */ + if (time_before(jiffies, xhci->bus_state[0].next_statechange) || + time_before(jiffies, + xhci->bus_state[1].next_statechange)) + msleep(100); + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + set_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags); + + spin_lock_irq(&xhci->lock); + if (xhci->quirks & XHCI_RESET_ON_RESUME) + hibernated = true; + + if (!hibernated) { + /* step 1: restore register */ + xhci_restore_registers(xhci); + /* step 2: initialize command ring buffer */ + xhci_set_cmd_ring_deq(xhci); + /* step 3: restore state and start state*/ + /* step 3: set CRS flag */ + command = xhci_readl(xhci, &xhci->op_regs->command); + command |= CMD_CRS; + xhci_writel(xhci, command, &xhci->op_regs->command); + if (xhci_handshake(xhci, &xhci->op_regs->status, + STS_RESTORE, 0, 10 * 1000)) { + xhci_warn(xhci, "WARN: xHC restore state timeout\n"); + spin_unlock_irq(&xhci->lock); + return -ETIMEDOUT; + } + temp = xhci_readl(xhci, &xhci->op_regs->status); + } + + /* If restore operation fails, re-initialize the HC during resume */ + if ((temp & STS_SRE) || hibernated) { + + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && + !(xhci_all_ports_seen_u0(xhci))) { + del_timer_sync(&xhci->comp_mode_recovery_timer); + xhci_dbg(xhci, "Compliance Mode Recovery Timer deleted!\n"); + } + + /* Let the USB core know _both_ roothubs lost power. */ + usb_root_hub_lost_power(xhci->main_hcd->self.root_hub); + usb_root_hub_lost_power(xhci->shared_hcd->self.root_hub); + + xhci_dbg(xhci, "Stop HCD\n"); + xhci_halt(xhci); + xhci_reset(xhci); + spin_unlock_irq(&xhci->lock); + xhci_cleanup_msix(xhci); + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING + /* Tell the event ring poll function not to reschedule */ + xhci->zombie = 1; + del_timer_sync(&xhci->event_ring_timer); +#endif + + xhci_dbg(xhci, "// Disabling event ring interrupts\n"); + temp = xhci_readl(xhci, &xhci->op_regs->status); + xhci_writel(xhci, temp & ~STS_EINT, &xhci->op_regs->status); + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); + xhci_writel(xhci, ER_IRQ_DISABLE(temp), + &xhci->ir_set->irq_pending); + xhci_print_ir_set(xhci, 0); + + xhci_dbg(xhci, "cleaning up memory\n"); + xhci_mem_cleanup(xhci); + xhci_dbg(xhci, "xhci_stop completed - status = %x\n", + xhci_readl(xhci, &xhci->op_regs->status)); + + /* USB core calls the PCI reinit and start functions twice: + * first with the primary HCD, and then with the secondary HCD. + * If we don't do the same, the host will never be started. + */ + if (!usb_hcd_is_primary_hcd(hcd)) + secondary_hcd = hcd; + else + secondary_hcd = xhci->shared_hcd; + + xhci_dbg(xhci, "Initialize the xhci_hcd\n"); + retval = xhci_init(hcd->primary_hcd); + if (retval) + return retval; + comp_timer_running = true; + + xhci_dbg(xhci, "Start the primary HCD\n"); + retval = xhci_run(hcd->primary_hcd); + if (!retval) { + xhci_dbg(xhci, "Start the secondary HCD\n"); + retval = xhci_run(secondary_hcd); + } + hcd->state = HC_STATE_SUSPENDED; + xhci->shared_hcd->state = HC_STATE_SUSPENDED; + goto done; + } + + /* step 4: set Run/Stop bit */ + command = xhci_readl(xhci, &xhci->op_regs->command); + command |= CMD_RUN; + xhci_writel(xhci, command, &xhci->op_regs->command); + xhci_handshake(xhci, &xhci->op_regs->status, STS_HALT, + 0, 250 * 1000); + + /* step 5: walk topology and initialize portsc, + * portpmsc and portli + */ + /* this is done in bus_resume */ + + /* step 6: restart each of the previously + * Running endpoints by ringing their doorbells + */ + + spin_unlock_irq(&xhci->lock); + + done: + if (retval == 0) { + /* Resume root hubs only when have pending events. */ + status = readl(&xhci->op_regs->status); + if (status & STS_EINT) { + usb_hcd_resume_root_hub(hcd); + usb_hcd_resume_root_hub(xhci->shared_hcd); + } + } + + /* + * If system is subject to the Quirk, Compliance Mode Timer needs to + * be re-initialized Always after a system resume. Ports are subject + * to suffer the Compliance Mode issue again. It doesn't matter if + * ports have entered previously to U0 before system's suspension. + */ + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && !comp_timer_running) + compliance_mode_recovery_timer_init(xhci); + + /* Re-enable port polling. */ + xhci_dbg(xhci, "%s: starting port polling.\n", __func__); + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + usb_hcd_poll_rh_status(hcd); + + return retval; +} +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ + +/** + * xhci_get_endpoint_index - Used for passing endpoint bitmasks between the core and + * HCDs. Find the index for an endpoint given its descriptor. Use the return + * value to right shift 1 for the bitmask. + * + * Index = (epnum * 2) + direction - 1, + * where direction = 0 for OUT, 1 for IN. + * For control endpoints, the IN index is used (OUT index is unused), so + * index = (epnum * 2) + direction - 1 = (epnum * 2) + 1 - 1 = (epnum * 2) + */ +unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc) +{ + unsigned int index; + if (usb_endpoint_xfer_control(desc)) + index = (unsigned int) (usb_endpoint_num(desc)*2); + else + index = (unsigned int) (usb_endpoint_num(desc)*2) + + (usb_endpoint_dir_in(desc) ? 1 : 0) - 1; + return index; +} + +/* Find the flag for this endpoint (for use in the control context). Use the + * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is + * bit 1, etc. + */ +unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc) +{ + return 1 << (xhci_get_endpoint_index(desc) + 1); +} + +/* Find the flag for this endpoint (for use in the control context). Use the + * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is + * bit 1, etc. + */ +unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index) +{ + return 1 << (ep_index + 1); +} + +/* Compute the last valid endpoint context index. Basically, this is the + * endpoint index plus one. For slot contexts with more than valid endpoint, + * we find the most significant bit set in the added contexts flags. + * e.g. ep 1 IN (with epnum 0x81) => added_ctxs = 0b1000 + * fls(0b1000) = 4, but the endpoint context index is 3, so subtract one. + */ +unsigned int xhci_last_valid_endpoint(u32 added_ctxs) +{ + return fls(added_ctxs) - 1; +} + +/* Returns 1 if the arguments are OK; + * returns 0 this is a root hub; returns -EINVAL for NULL pointers. + */ +static int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep, int check_ep, bool check_virt_dev, + const char *func) { + struct xhci_hcd *xhci; + struct xhci_virt_device *virt_dev; + + if (!hcd || (check_ep && !ep) || !udev) { + printk(KERN_DEBUG "xHCI %s called with invalid args\n", + func); + return -EINVAL; + } + if (!udev->parent) { + printk(KERN_DEBUG "xHCI %s called for root hub\n", + func); + return 0; + } + + xhci = hcd_to_xhci(hcd); + if (check_virt_dev) { + if (!udev->slot_id || !xhci->devs[udev->slot_id]) { + printk(KERN_DEBUG "xHCI %s called with unaddressed " + "device\n", func); + return -EINVAL; + } + + virt_dev = xhci->devs[udev->slot_id]; + if (virt_dev->udev != udev) { + printk(KERN_DEBUG "xHCI %s called with udev and " + "virt_dev does not match\n", func); + return -EINVAL; + } + } + + if (xhci->xhc_state & XHCI_STATE_HALTED) + return -ENODEV; + + return 1; +} + +static int xhci_configure_endpoint(struct xhci_hcd *xhci, + struct usb_device *udev, struct xhci_command *command, + bool ctx_change, bool must_succeed); + +/* + * Full speed devices may have a max packet size greater than 8 bytes, but the + * USB core doesn't know that until it reads the first 8 bytes of the + * descriptor. If the usb_device's max packet size changes after that point, + * we need to issue an evaluate context command and wait on it. + */ +static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id, + unsigned int ep_index, struct urb *urb) +{ + struct xhci_container_ctx *in_ctx; + struct xhci_container_ctx *out_ctx; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_ep_ctx *ep_ctx; + int max_packet_size; + int hw_max_packet_size; + int ret = 0; + + out_ctx = xhci->devs[slot_id]->out_ctx; + ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, ep_index); + hw_max_packet_size = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2)); + max_packet_size = usb_endpoint_maxp(&urb->dev->ep0.desc); + if (hw_max_packet_size != max_packet_size) { + xhci_dbg(xhci, "Max Packet Size for ep 0 changed.\n"); + xhci_dbg(xhci, "Max packet size in usb_device = %d\n", + max_packet_size); + xhci_dbg(xhci, "Max packet size in xHCI HW = %d\n", + hw_max_packet_size); + xhci_dbg(xhci, "Issuing evaluate context command.\n"); + + /* Set up the modified control endpoint 0 */ + xhci_endpoint_copy(xhci, xhci->devs[slot_id]->in_ctx, + xhci->devs[slot_id]->out_ctx, ep_index); + in_ctx = xhci->devs[slot_id]->in_ctx; + ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index); + ep_ctx->ep_info2 &= cpu_to_le32(~MAX_PACKET_MASK); + ep_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(max_packet_size)); + + /* Set up the input context flags for the command */ + /* FIXME: This won't work if a non-default control endpoint + * changes max packet sizes. + */ + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + ctrl_ctx->add_flags = cpu_to_le32(EP0_FLAG); + ctrl_ctx->drop_flags = 0; + + xhci_dbg(xhci, "Slot %d input context\n", slot_id); + xhci_dbg_ctx(xhci, in_ctx, ep_index); + xhci_dbg(xhci, "Slot %d output context\n", slot_id); + xhci_dbg_ctx(xhci, out_ctx, ep_index); + + ret = xhci_configure_endpoint(xhci, urb->dev, NULL, + true, false); + + /* Clean up the input context for later use by bandwidth + * functions. + */ + ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG); + } + return ret; +} + +/* + * non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + */ +int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_td *buffer; + unsigned long flags; + int ret = 0; + unsigned int slot_id, ep_index; + struct urb_priv *urb_priv; + int size, i; + + if (!urb || xhci_check_args(hcd, urb->dev, urb->ep, + true, true, __func__) <= 0) + return -EINVAL; + + slot_id = urb->dev->slot_id; + ep_index = xhci_get_endpoint_index(&urb->ep->desc); + + if (!HCD_HW_ACCESSIBLE(hcd)) { + if (!in_interrupt()) + xhci_dbg(xhci, "urb submitted during PCI suspend\n"); + ret = -ESHUTDOWN; + goto exit; + } + + if (usb_endpoint_xfer_isoc(&urb->ep->desc)) + size = urb->number_of_packets; + else + size = 1; + + urb_priv = kzalloc(sizeof(struct urb_priv) + + size * sizeof(struct xhci_td *), mem_flags); + if (!urb_priv) + return -ENOMEM; + + buffer = kzalloc(size * sizeof(struct xhci_td), mem_flags); + if (!buffer) { + kfree(urb_priv); + return -ENOMEM; + } + + for (i = 0; i < size; i++) { + urb_priv->td[i] = buffer; + buffer++; + } + + urb_priv->length = size; + urb_priv->td_cnt = 0; + urb->hcpriv = urb_priv; + + if (usb_endpoint_xfer_control(&urb->ep->desc)) { + /* Check to see if the max packet size for the default control + * endpoint changed during FS device enumeration + */ + if (urb->dev->speed == USB_SPEED_FULL) { + ret = xhci_check_maxpacket(xhci, slot_id, + ep_index, urb); + if (ret < 0) { + xhci_urb_free_priv(xhci, urb_priv); + urb->hcpriv = NULL; + return ret; + } + } + + /* We have a spinlock and interrupts disabled, so we must pass + * atomic context to this function, which may allocate memory. + */ + spin_lock_irqsave(&xhci->lock, flags); + if (xhci->xhc_state & XHCI_STATE_DYING) + goto dying; + ret = xhci_queue_ctrl_tx(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + if (ret) + goto free_priv; + spin_unlock_irqrestore(&xhci->lock, flags); + } else if (usb_endpoint_xfer_bulk(&urb->ep->desc)) { + spin_lock_irqsave(&xhci->lock, flags); + if (xhci->xhc_state & XHCI_STATE_DYING) + goto dying; + if (xhci->devs[slot_id]->eps[ep_index].ep_state & + EP_GETTING_STREAMS) { + xhci_warn(xhci, "WARN: Can't enqueue URB while bulk ep " + "is transitioning to using streams.\n"); + ret = -EINVAL; + } else if (xhci->devs[slot_id]->eps[ep_index].ep_state & + EP_GETTING_NO_STREAMS) { + xhci_warn(xhci, "WARN: Can't enqueue URB while bulk ep " + "is transitioning to " + "not having streams.\n"); + ret = -EINVAL; + } else { + ret = xhci_queue_bulk_tx(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + } + if (ret) + goto free_priv; + spin_unlock_irqrestore(&xhci->lock, flags); + } else if (usb_endpoint_xfer_int(&urb->ep->desc)) { + spin_lock_irqsave(&xhci->lock, flags); + if (xhci->xhc_state & XHCI_STATE_DYING) + goto dying; + ret = xhci_queue_intr_tx(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + if (ret) + goto free_priv; + spin_unlock_irqrestore(&xhci->lock, flags); + } else { + spin_lock_irqsave(&xhci->lock, flags); + if (xhci->xhc_state & XHCI_STATE_DYING) + goto dying; + ret = xhci_queue_isoc_tx_prepare(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + if (ret) + goto free_priv; + spin_unlock_irqrestore(&xhci->lock, flags); + } +exit: + return ret; +dying: + xhci_dbg(xhci, "Ep 0x%x: URB %p submitted for " + "non-responsive xHCI host.\n", + urb->ep->desc.bEndpointAddress, urb); + ret = -ESHUTDOWN; +free_priv: + xhci_urb_free_priv(xhci, urb_priv); + urb->hcpriv = NULL; + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; +} + +/* Get the right ring for the given URB. + * If the endpoint supports streams, boundary check the URB's stream ID. + * If the endpoint doesn't support streams, return the singular endpoint ring. + */ +static struct xhci_ring *xhci_urb_to_transfer_ring(struct xhci_hcd *xhci, + struct urb *urb) +{ + unsigned int slot_id; + unsigned int ep_index; + unsigned int stream_id; + struct xhci_virt_ep *ep; + + slot_id = urb->dev->slot_id; + ep_index = xhci_get_endpoint_index(&urb->ep->desc); + stream_id = urb->stream_id; + ep = &xhci->devs[slot_id]->eps[ep_index]; + /* Common case: no streams */ + if (!(ep->ep_state & EP_HAS_STREAMS)) + return ep->ring; + + if (stream_id == 0) { + xhci_warn(xhci, + "WARN: Slot ID %u, ep index %u has streams, " + "but URB has no stream ID.\n", + slot_id, ep_index); + return NULL; + } + + if (stream_id < ep->stream_info->num_streams) + return ep->stream_info->stream_rings[stream_id]; + + xhci_warn(xhci, + "WARN: Slot ID %u, ep index %u has " + "stream IDs 1 to %u allocated, " + "but stream ID %u is requested.\n", + slot_id, ep_index, + ep->stream_info->num_streams - 1, + stream_id); + return NULL; +} + +/* + * Remove the URB's TD from the endpoint ring. This may cause the HC to stop + * USB transfers, potentially stopping in the middle of a TRB buffer. The HC + * should pick up where it left off in the TD, unless a Set Transfer Ring + * Dequeue Pointer is issued. + * + * The TRBs that make up the buffers for the canceled URB will be "removed" from + * the ring. Since the ring is a contiguous structure, they can't be physically + * removed. Instead, there are two options: + * + * 1) If the HC is in the middle of processing the URB to be canceled, we + * simply move the ring's dequeue pointer past those TRBs using the Set + * Transfer Ring Dequeue Pointer command. This will be the common case, + * when drivers timeout on the last submitted URB and attempt to cancel. + * + * 2) If the HC is in the middle of a different TD, we turn the TRBs into a + * series of 1-TRB transfer no-op TDs. (No-ops shouldn't be chained.) The + * HC will need to invalidate the any TRBs it has cached after the stop + * endpoint command, as noted in the xHCI 0.95 errata. + * + * 3) The TD may have completed by the time the Stop Endpoint Command + * completes, so software needs to handle that case too. + * + * This function should protect against the TD enqueueing code ringing the + * doorbell while this code is waiting for a Stop Endpoint command to complete. + * It also needs to account for multiple cancellations on happening at the same + * time for the same endpoint. + * + * Note that this function can be called in any context, or so says + * usb_hcd_unlink_urb() + */ +int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + unsigned long flags; + int ret, i; + u32 temp; + struct xhci_hcd *xhci; + struct urb_priv *urb_priv; + struct xhci_td *td; + unsigned int ep_index; + struct xhci_ring *ep_ring; + struct xhci_virt_ep *ep; + + xhci = hcd_to_xhci(hcd); + spin_lock_irqsave(&xhci->lock, flags); + /* Make sure the URB hasn't completed or been unlinked already */ + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret || !urb->hcpriv) + goto done; + temp = xhci_readl(xhci, &xhci->op_regs->status); + if (temp == 0xffffffff || (xhci->xhc_state & XHCI_STATE_HALTED)) { + xhci_dbg(xhci, "HW died, freeing TD.\n"); + urb_priv = urb->hcpriv; + for (i = urb_priv->td_cnt; i < urb_priv->length; i++) { + td = urb_priv->td[i]; + if (!list_empty(&td->td_list)) + list_del_init(&td->td_list); + if (!list_empty(&td->cancelled_td_list)) + list_del_init(&td->cancelled_td_list); + } + + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&xhci->lock, flags); + usb_hcd_giveback_urb(hcd, urb, -ESHUTDOWN); + xhci_urb_free_priv(xhci, urb_priv); + return ret; + } + if ((xhci->xhc_state & XHCI_STATE_DYING) || + (xhci->xhc_state & XHCI_STATE_HALTED)) { + xhci_dbg(xhci, "Ep 0x%x: URB %p to be canceled on " + "non-responsive xHCI host.\n", + urb->ep->desc.bEndpointAddress, urb); + /* Let the stop endpoint command watchdog timer (which set this + * state) finish cleaning up the endpoint TD lists. We must + * have caught it in the middle of dropping a lock and giving + * back an URB. + */ + goto done; + } + + ep_index = xhci_get_endpoint_index(&urb->ep->desc); + ep = &xhci->devs[urb->dev->slot_id]->eps[ep_index]; + ep_ring = xhci_urb_to_transfer_ring(xhci, urb); + if (!ep_ring) { + ret = -EINVAL; + goto done; + } + + urb_priv = urb->hcpriv; + i = urb_priv->td_cnt; + if (i < urb_priv->length) + xhci_dbg(xhci, "Cancel URB %p, dev %s, ep 0x%x, " + "starting at offset 0x%llx\n", + urb, urb->dev->devpath, + urb->ep->desc.bEndpointAddress, + (unsigned long long) xhci_trb_virt_to_dma( + urb_priv->td[i]->start_seg, + urb_priv->td[i]->first_trb)); + + for (; i < urb_priv->length; i++) { + td = urb_priv->td[i]; + list_add_tail(&td->cancelled_td_list, &ep->cancelled_td_list); + } + + /* Queue a stop endpoint command, but only if this is + * the first cancellation to be handled. + */ + if (!(ep->ep_state & EP_HALT_PENDING)) { + ep->ep_state |= EP_HALT_PENDING; + ep->stop_cmds_pending++; + ep->stop_cmd_timer.expires = jiffies + + XHCI_STOP_EP_CMD_TIMEOUT * HZ; + add_timer(&ep->stop_cmd_timer); + xhci_queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index, 0); + xhci_ring_cmd_db(xhci); + } +done: + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; +} + +/* Drop an endpoint from a new bandwidth configuration for this device. + * Only one call to this function is allowed per endpoint before + * check_bandwidth() or reset_bandwidth() must be called. + * A call to xhci_drop_endpoint() followed by a call to xhci_add_endpoint() will + * add the endpoint to the schedule with possibly new parameters denoted by a + * different endpoint descriptor in usb_host_endpoint. + * A call to xhci_add_endpoint() followed by a call to xhci_drop_endpoint() is + * not allowed. + * + * The USB core will not allow URBs to be queued to an endpoint that is being + * disabled, so there's no need for mutual exclusion to protect + * the xhci->devs[slot_id] structure. + */ +int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd *xhci; + struct xhci_container_ctx *in_ctx, *out_ctx; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_slot_ctx *slot_ctx; + unsigned int last_ctx; + unsigned int ep_index; + struct xhci_ep_ctx *ep_ctx; + u32 drop_flag; + u32 new_add_flags, new_drop_flags, new_slot_info; + int ret; + + ret = xhci_check_args(hcd, udev, ep, 1, true, __func__); + if (ret <= 0) + return ret; + xhci = hcd_to_xhci(hcd); + if (xhci->xhc_state & XHCI_STATE_DYING) + return -ENODEV; + + xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); + drop_flag = xhci_get_endpoint_flag(&ep->desc); + if (drop_flag == SLOT_FLAG || drop_flag == EP0_FLAG) { + xhci_dbg(xhci, "xHCI %s - can't drop slot or ep 0 %#x\n", + __func__, drop_flag); + return 0; + } + + in_ctx = xhci->devs[udev->slot_id]->in_ctx; + out_ctx = xhci->devs[udev->slot_id]->out_ctx; + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, ep_index); + /* If the HC already knows the endpoint is disabled, + * or the HCD has noted it is disabled, ignore this request + */ + if (((ep_ctx->ep_info & cpu_to_le32(EP_STATE_MASK)) == + cpu_to_le32(EP_STATE_DISABLED)) || + le32_to_cpu(ctrl_ctx->drop_flags) & + xhci_get_endpoint_flag(&ep->desc)) { + xhci_warn(xhci, "xHCI %s called with disabled ep %p\n", + __func__, ep); + return 0; + } + + ctrl_ctx->drop_flags |= cpu_to_le32(drop_flag); + new_drop_flags = le32_to_cpu(ctrl_ctx->drop_flags); + + ctrl_ctx->add_flags &= cpu_to_le32(~drop_flag); + new_add_flags = le32_to_cpu(ctrl_ctx->add_flags); + + last_ctx = xhci_last_valid_endpoint(le32_to_cpu(ctrl_ctx->add_flags)); + slot_ctx = xhci_get_slot_ctx(xhci, in_ctx); + /* Update the last valid endpoint context, if we deleted the last one */ + if ((le32_to_cpu(slot_ctx->dev_info) & LAST_CTX_MASK) > + LAST_CTX(last_ctx)) { + slot_ctx->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(last_ctx)); + } + new_slot_info = le32_to_cpu(slot_ctx->dev_info); + + xhci_endpoint_zero(xhci, xhci->devs[udev->slot_id], ep); + + xhci_dbg(xhci, "drop ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x, new slot info = %#x\n", + (unsigned int) ep->desc.bEndpointAddress, + udev->slot_id, + (unsigned int) new_drop_flags, + (unsigned int) new_add_flags, + (unsigned int) new_slot_info); + return 0; +} + +/* Add an endpoint to a new possible bandwidth configuration for this device. + * Only one call to this function is allowed per endpoint before + * check_bandwidth() or reset_bandwidth() must be called. + * A call to xhci_drop_endpoint() followed by a call to xhci_add_endpoint() will + * add the endpoint to the schedule with possibly new parameters denoted by a + * different endpoint descriptor in usb_host_endpoint. + * A call to xhci_add_endpoint() followed by a call to xhci_drop_endpoint() is + * not allowed. + * + * The USB core will not allow URBs to be queued to an endpoint until the + * configuration or alt setting is installed in the device, so there's no need + * for mutual exclusion to protect the xhci->devs[slot_id] structure. + */ +int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd *xhci; + struct xhci_container_ctx *in_ctx, *out_ctx; + unsigned int ep_index; + struct xhci_slot_ctx *slot_ctx; + struct xhci_input_control_ctx *ctrl_ctx; + u32 added_ctxs; + unsigned int last_ctx; + u32 new_add_flags, new_drop_flags, new_slot_info; + struct xhci_virt_device *virt_dev; + int ret = 0; + + ret = xhci_check_args(hcd, udev, ep, 1, true, __func__); + if (ret <= 0) { + /* So we won't queue a reset ep command for a root hub */ + ep->hcpriv = NULL; + return ret; + } + xhci = hcd_to_xhci(hcd); + if (xhci->xhc_state & XHCI_STATE_DYING) + return -ENODEV; + + added_ctxs = xhci_get_endpoint_flag(&ep->desc); + last_ctx = xhci_last_valid_endpoint(added_ctxs); + if (added_ctxs == SLOT_FLAG || added_ctxs == EP0_FLAG) { + /* FIXME when we have to issue an evaluate endpoint command to + * deal with ep0 max packet size changing once we get the + * descriptors + */ + xhci_dbg(xhci, "xHCI %s - can't add slot or ep 0 %#x\n", + __func__, added_ctxs); + return 0; + } + + virt_dev = xhci->devs[udev->slot_id]; + in_ctx = virt_dev->in_ctx; + out_ctx = virt_dev->out_ctx; + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + ep_index = xhci_get_endpoint_index(&ep->desc); + + /* If this endpoint is already in use, and the upper layers are trying + * to add it again without dropping it, reject the addition. + */ + if (virt_dev->eps[ep_index].ring && + !(le32_to_cpu(ctrl_ctx->drop_flags) & + xhci_get_endpoint_flag(&ep->desc))) { + xhci_warn(xhci, "Trying to add endpoint 0x%x " + "without dropping it.\n", + (unsigned int) ep->desc.bEndpointAddress); + return -EINVAL; + } + + /* If the HCD has already noted the endpoint is enabled, + * ignore this request. + */ + if (le32_to_cpu(ctrl_ctx->add_flags) & + xhci_get_endpoint_flag(&ep->desc)) { + xhci_warn(xhci, "xHCI %s called with enabled ep %p\n", + __func__, ep); + return 0; + } + + /* + * Configuration and alternate setting changes must be done in + * process context, not interrupt context (or so documenation + * for usb_set_interface() and usb_set_configuration() claim). + */ + if (xhci_endpoint_init(xhci, virt_dev, udev, ep, GFP_NOIO) < 0) { + dev_dbg(&udev->dev, "%s - could not initialize ep %#x\n", + __func__, ep->desc.bEndpointAddress); + return -ENOMEM; + } + + ctrl_ctx->add_flags |= cpu_to_le32(added_ctxs); + new_add_flags = le32_to_cpu(ctrl_ctx->add_flags); + + /* If xhci_endpoint_disable() was called for this endpoint, but the + * xHC hasn't been notified yet through the check_bandwidth() call, + * this re-adds a new state for the endpoint from the new endpoint + * descriptors. We must drop and re-add this endpoint, so we leave the + * drop flags alone. + */ + new_drop_flags = le32_to_cpu(ctrl_ctx->drop_flags); + + slot_ctx = xhci_get_slot_ctx(xhci, in_ctx); + /* Update the last valid endpoint context, if we just added one past */ + if ((le32_to_cpu(slot_ctx->dev_info) & LAST_CTX_MASK) < + LAST_CTX(last_ctx)) { + slot_ctx->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(last_ctx)); + } + new_slot_info = le32_to_cpu(slot_ctx->dev_info); + + /* Store the usb_device pointer for later use */ + ep->hcpriv = udev; + + xhci_dbg(xhci, "add ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x, new slot info = %#x\n", + (unsigned int) ep->desc.bEndpointAddress, + udev->slot_id, + (unsigned int) new_drop_flags, + (unsigned int) new_add_flags, + (unsigned int) new_slot_info); + return 0; +} + +static void xhci_zero_in_ctx(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev) +{ + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_ep_ctx *ep_ctx; + struct xhci_slot_ctx *slot_ctx; + int i; + + /* When a device's add flag and drop flag are zero, any subsequent + * configure endpoint command will leave that endpoint's state + * untouched. Make sure we don't leave any old state in the input + * endpoint contexts. + */ + ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + ctrl_ctx->drop_flags = 0; + ctrl_ctx->add_flags = 0; + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + slot_ctx->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + /* Endpoint 0 is always valid */ + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1)); + for (i = 1; i < 31; ++i) { + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, i); + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = 0; + ep_ctx->deq = 0; + ep_ctx->tx_info = 0; + } +} + +static int xhci_configure_endpoint_result(struct xhci_hcd *xhci, + struct usb_device *udev, u32 *cmd_status) +{ + int ret; + + switch (*cmd_status) { + case COMP_ENOMEM: + dev_warn(&udev->dev, "Not enough host controller resources " + "for new device state.\n"); + ret = -ENOMEM; + /* FIXME: can we allocate more resources for the HC? */ + break; + case COMP_BW_ERR: + case COMP_2ND_BW_ERR: + dev_warn(&udev->dev, "Not enough bandwidth " + "for new device state.\n"); + ret = -ENOSPC; + /* FIXME: can we go back to the old state? */ + break; + case COMP_TRB_ERR: + /* the HCD set up something wrong */ + dev_warn(&udev->dev, "ERROR: Endpoint drop flag = 0, " + "add flag = 1, " + "and endpoint is not disabled.\n"); + ret = -EINVAL; + break; + case COMP_DEV_ERR: + dev_warn(&udev->dev, "ERROR: Incompatible device for endpoint " + "configure command.\n"); + ret = -ENODEV; + break; + case COMP_SUCCESS: + dev_dbg(&udev->dev, "Successful Endpoint Configure command\n"); + ret = 0; + break; + default: + xhci_err(xhci, "ERROR: unexpected command completion " + "code 0x%x.\n", *cmd_status); + ret = -EINVAL; + break; + } + return ret; +} + +static int xhci_evaluate_context_result(struct xhci_hcd *xhci, + struct usb_device *udev, u32 *cmd_status) +{ + int ret; + struct xhci_virt_device *virt_dev = xhci->devs[udev->slot_id]; + + switch (*cmd_status) { + case COMP_EINVAL: + dev_warn(&udev->dev, "WARN: xHCI driver setup invalid evaluate " + "context command.\n"); + ret = -EINVAL; + break; + case COMP_EBADSLT: + dev_warn(&udev->dev, "WARN: slot not enabled for" + "evaluate context command.\n"); + ret = -EINVAL; + break; + case COMP_CTX_STATE: + dev_warn(&udev->dev, "WARN: invalid context state for " + "evaluate context command.\n"); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, 1); + ret = -EINVAL; + break; + case COMP_DEV_ERR: + dev_warn(&udev->dev, "ERROR: Incompatible device for evaluate " + "context command.\n"); + ret = -ENODEV; + break; + case COMP_MEL_ERR: + /* Max Exit Latency too large error */ + dev_warn(&udev->dev, "WARN: Max Exit Latency too large\n"); + ret = -EINVAL; + break; + case COMP_SUCCESS: + dev_dbg(&udev->dev, "Successful evaluate context command\n"); + ret = 0; + break; + default: + xhci_err(xhci, "ERROR: unexpected command completion " + "code 0x%x.\n", *cmd_status); + ret = -EINVAL; + break; + } + return ret; +} + +static u32 xhci_count_num_new_endpoints(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx) +{ + struct xhci_input_control_ctx *ctrl_ctx; + u32 valid_add_flags; + u32 valid_drop_flags; + + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + /* Ignore the slot flag (bit 0), and the default control endpoint flag + * (bit 1). The default control endpoint is added during the Address + * Device command and is never removed until the slot is disabled. + */ + valid_add_flags = ctrl_ctx->add_flags >> 2; + valid_drop_flags = ctrl_ctx->drop_flags >> 2; + + /* Use hweight32 to count the number of ones in the add flags, or + * number of endpoints added. Don't count endpoints that are changed + * (both added and dropped). + */ + return hweight32(valid_add_flags) - + hweight32(valid_add_flags & valid_drop_flags); +} + +static unsigned int xhci_count_num_dropped_endpoints(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx) +{ + struct xhci_input_control_ctx *ctrl_ctx; + u32 valid_add_flags; + u32 valid_drop_flags; + + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + valid_add_flags = ctrl_ctx->add_flags >> 2; + valid_drop_flags = ctrl_ctx->drop_flags >> 2; + + return hweight32(valid_drop_flags) - + hweight32(valid_add_flags & valid_drop_flags); +} + +/* + * We need to reserve the new number of endpoints before the configure endpoint + * command completes. We can't subtract the dropped endpoints from the number + * of active endpoints until the command completes because we can oversubscribe + * the host in this case: + * + * - the first configure endpoint command drops more endpoints than it adds + * - a second configure endpoint command that adds more endpoints is queued + * - the first configure endpoint command fails, so the config is unchanged + * - the second command may succeed, even though there isn't enough resources + * + * Must be called with xhci->lock held. + */ +static int xhci_reserve_host_resources(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx) +{ + u32 added_eps; + + added_eps = xhci_count_num_new_endpoints(xhci, in_ctx); + if (xhci->num_active_eps + added_eps > xhci->limit_active_eps) { + xhci_dbg(xhci, "Not enough ep ctxs: " + "%u active, need to add %u, limit is %u.\n", + xhci->num_active_eps, added_eps, + xhci->limit_active_eps); + return -ENOMEM; + } + xhci->num_active_eps += added_eps; + xhci_dbg(xhci, "Adding %u ep ctxs, %u now active.\n", added_eps, + xhci->num_active_eps); + return 0; +} + +/* + * The configure endpoint was failed by the xHC for some other reason, so we + * need to revert the resources that failed configuration would have used. + * + * Must be called with xhci->lock held. + */ +static void xhci_free_host_resources(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx) +{ + u32 num_failed_eps; + + num_failed_eps = xhci_count_num_new_endpoints(xhci, in_ctx); + xhci->num_active_eps -= num_failed_eps; + xhci_dbg(xhci, "Removing %u failed ep ctxs, %u now active.\n", + num_failed_eps, + xhci->num_active_eps); +} + +/* + * Now that the command has completed, clean up the active endpoint count by + * subtracting out the endpoints that were dropped (but not changed). + * + * Must be called with xhci->lock held. + */ +static void xhci_finish_resource_reservation(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx) +{ + u32 num_dropped_eps; + + num_dropped_eps = xhci_count_num_dropped_endpoints(xhci, in_ctx); + xhci->num_active_eps -= num_dropped_eps; + if (num_dropped_eps) + xhci_dbg(xhci, "Removing %u dropped ep ctxs, %u now active.\n", + num_dropped_eps, + xhci->num_active_eps); +} + +static unsigned int xhci_get_block_size(struct usb_device *udev) +{ + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + return FS_BLOCK; + case USB_SPEED_HIGH: + return HS_BLOCK; + case USB_SPEED_SUPER: + return SS_BLOCK; + case USB_SPEED_UNKNOWN: + case USB_SPEED_WIRELESS: + default: + /* Should never happen */ + return 1; + } +} + +static unsigned int +xhci_get_largest_overhead(struct xhci_interval_bw *interval_bw) +{ + if (interval_bw->overhead[LS_OVERHEAD_TYPE]) + return LS_OVERHEAD; + if (interval_bw->overhead[FS_OVERHEAD_TYPE]) + return FS_OVERHEAD; + return HS_OVERHEAD; +} + +/* If we are changing a LS/FS device under a HS hub, + * make sure (if we are activating a new TT) that the HS bus has enough + * bandwidth for this new TT. + */ +static int xhci_check_tt_bw_table(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int old_active_eps) +{ + struct xhci_interval_bw_table *bw_table; + struct xhci_tt_bw_info *tt_info; + + /* Find the bandwidth table for the root port this TT is attached to. */ + bw_table = &xhci->rh_bw[virt_dev->real_port - 1].bw_table; + tt_info = virt_dev->tt_info; + /* If this TT already had active endpoints, the bandwidth for this TT + * has already been added. Removing all periodic endpoints (and thus + * making the TT enactive) will only decrease the bandwidth used. + */ + if (old_active_eps) + return 0; + if (old_active_eps == 0 && tt_info->active_eps != 0) { + if (bw_table->bw_used + TT_HS_OVERHEAD > HS_BW_LIMIT) + return -ENOMEM; + return 0; + } + /* Not sure why we would have no new active endpoints... + * + * Maybe because of an Evaluate Context change for a hub update or a + * control endpoint 0 max packet size change? + * FIXME: skip the bandwidth calculation in that case. + */ + return 0; +} + +static int xhci_check_ss_bw(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev) +{ + unsigned int bw_reserved; + + bw_reserved = DIV_ROUND_UP(SS_BW_RESERVED*SS_BW_LIMIT_IN, 100); + if (virt_dev->bw_table->ss_bw_in > (SS_BW_LIMIT_IN - bw_reserved)) + return -ENOMEM; + + bw_reserved = DIV_ROUND_UP(SS_BW_RESERVED*SS_BW_LIMIT_OUT, 100); + if (virt_dev->bw_table->ss_bw_out > (SS_BW_LIMIT_OUT - bw_reserved)) + return -ENOMEM; + + return 0; +} + +/* + * This algorithm is a very conservative estimate of the worst-case scheduling + * scenario for any one interval. The hardware dynamically schedules the + * packets, so we can't tell which microframe could be the limiting factor in + * the bandwidth scheduling. This only takes into account periodic endpoints. + * + * Obviously, we can't solve an NP complete problem to find the minimum worst + * case scenario. Instead, we come up with an estimate that is no less than + * the worst case bandwidth used for any one microframe, but may be an + * over-estimate. + * + * We walk the requirements for each endpoint by interval, starting with the + * smallest interval, and place packets in the schedule where there is only one + * possible way to schedule packets for that interval. In order to simplify + * this algorithm, we record the largest max packet size for each interval, and + * assume all packets will be that size. + * + * For interval 0, we obviously must schedule all packets for each interval. + * The bandwidth for interval 0 is just the amount of data to be transmitted + * (the sum of all max ESIT payload sizes, plus any overhead per packet times + * the number of packets). + * + * For interval 1, we have two possible microframes to schedule those packets + * in. For this algorithm, if we can schedule the same number of packets for + * each possible scheduling opportunity (each microframe), we will do so. The + * remaining number of packets will be saved to be transmitted in the gaps in + * the next interval's scheduling sequence. + * + * As we move those remaining packets to be scheduled with interval 2 packets, + * we have to double the number of remaining packets to transmit. This is + * because the intervals are actually powers of 2, and we would be transmitting + * the previous interval's packets twice in this interval. We also have to be + * sure that when we look at the largest max packet size for this interval, we + * also look at the largest max packet size for the remaining packets and take + * the greater of the two. + * + * The algorithm continues to evenly distribute packets in each scheduling + * opportunity, and push the remaining packets out, until we get to the last + * interval. Then those packets and their associated overhead are just added + * to the bandwidth used. + */ +static int xhci_check_bw_table(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int old_active_eps) +{ + unsigned int bw_reserved; + unsigned int max_bandwidth; + unsigned int bw_used; + unsigned int block_size; + struct xhci_interval_bw_table *bw_table; + unsigned int packet_size = 0; + unsigned int overhead = 0; + unsigned int packets_transmitted = 0; + unsigned int packets_remaining = 0; + unsigned int i; + + if (virt_dev->udev->speed == USB_SPEED_SUPER) + return xhci_check_ss_bw(xhci, virt_dev); + + if (virt_dev->udev->speed == USB_SPEED_HIGH) { + max_bandwidth = HS_BW_LIMIT; + /* Convert percent of bus BW reserved to blocks reserved */ + bw_reserved = DIV_ROUND_UP(HS_BW_RESERVED * max_bandwidth, 100); + } else { + max_bandwidth = FS_BW_LIMIT; + bw_reserved = DIV_ROUND_UP(FS_BW_RESERVED * max_bandwidth, 100); + } + + bw_table = virt_dev->bw_table; + /* We need to translate the max packet size and max ESIT payloads into + * the units the hardware uses. + */ + block_size = xhci_get_block_size(virt_dev->udev); + + /* If we are manipulating a LS/FS device under a HS hub, double check + * that the HS bus has enough bandwidth if we are activing a new TT. + */ + if (virt_dev->tt_info) { + xhci_dbg(xhci, "Recalculating BW for rootport %u\n", + virt_dev->real_port); + if (xhci_check_tt_bw_table(xhci, virt_dev, old_active_eps)) { + xhci_warn(xhci, "Not enough bandwidth on HS bus for " + "newly activated TT.\n"); + return -ENOMEM; + } + xhci_dbg(xhci, "Recalculating BW for TT slot %u port %u\n", + virt_dev->tt_info->slot_id, + virt_dev->tt_info->ttport); + } else { + xhci_dbg(xhci, "Recalculating BW for rootport %u\n", + virt_dev->real_port); + } + + /* Add in how much bandwidth will be used for interval zero, or the + * rounded max ESIT payload + number of packets * largest overhead. + */ + bw_used = DIV_ROUND_UP(bw_table->interval0_esit_payload, block_size) + + bw_table->interval_bw[0].num_packets * + xhci_get_largest_overhead(&bw_table->interval_bw[0]); + + for (i = 1; i < XHCI_MAX_INTERVAL; i++) { + unsigned int bw_added; + unsigned int largest_mps; + unsigned int interval_overhead; + + /* + * How many packets could we transmit in this interval? + * If packets didn't fit in the previous interval, we will need + * to transmit that many packets twice within this interval. + */ + packets_remaining = 2 * packets_remaining + + bw_table->interval_bw[i].num_packets; + + /* Find the largest max packet size of this or the previous + * interval. + */ + if (list_empty(&bw_table->interval_bw[i].endpoints)) + largest_mps = 0; + else { + struct xhci_virt_ep *virt_ep; + struct list_head *ep_entry; + + ep_entry = bw_table->interval_bw[i].endpoints.next; + virt_ep = list_entry(ep_entry, + struct xhci_virt_ep, bw_endpoint_list); + /* Convert to blocks, rounding up */ + largest_mps = DIV_ROUND_UP( + virt_ep->bw_info.max_packet_size, + block_size); + } + if (largest_mps > packet_size) + packet_size = largest_mps; + + /* Use the larger overhead of this or the previous interval. */ + interval_overhead = xhci_get_largest_overhead( + &bw_table->interval_bw[i]); + if (interval_overhead > overhead) + overhead = interval_overhead; + + /* How many packets can we evenly distribute across + * (1 << (i + 1)) possible scheduling opportunities? + */ + packets_transmitted = packets_remaining >> (i + 1); + + /* Add in the bandwidth used for those scheduled packets */ + bw_added = packets_transmitted * (overhead + packet_size); + + /* How many packets do we have remaining to transmit? */ + packets_remaining = packets_remaining % (1 << (i + 1)); + + /* What largest max packet size should those packets have? */ + /* If we've transmitted all packets, don't carry over the + * largest packet size. + */ + if (packets_remaining == 0) { + packet_size = 0; + overhead = 0; + } else if (packets_transmitted > 0) { + /* Otherwise if we do have remaining packets, and we've + * scheduled some packets in this interval, take the + * largest max packet size from endpoints with this + * interval. + */ + packet_size = largest_mps; + overhead = interval_overhead; + } + /* Otherwise carry over packet_size and overhead from the last + * time we had a remainder. + */ + bw_used += bw_added; + if (bw_used > max_bandwidth) { + xhci_warn(xhci, "Not enough bandwidth. " + "Proposed: %u, Max: %u\n", + bw_used, max_bandwidth); + return -ENOMEM; + } + } + /* + * Ok, we know we have some packets left over after even-handedly + * scheduling interval 15. We don't know which microframes they will + * fit into, so we over-schedule and say they will be scheduled every + * microframe. + */ + if (packets_remaining > 0) + bw_used += overhead + packet_size; + + if (!virt_dev->tt_info && virt_dev->udev->speed == USB_SPEED_HIGH) { + unsigned int port_index = virt_dev->real_port - 1; + + /* OK, we're manipulating a HS device attached to a + * root port bandwidth domain. Include the number of active TTs + * in the bandwidth used. + */ + bw_used += TT_HS_OVERHEAD * + xhci->rh_bw[port_index].num_active_tts; + } + + xhci_dbg(xhci, "Final bandwidth: %u, Limit: %u, Reserved: %u, " + "Available: %u " "percent\n", + bw_used, max_bandwidth, bw_reserved, + (max_bandwidth - bw_used - bw_reserved) * 100 / + max_bandwidth); + + bw_used += bw_reserved; + if (bw_used > max_bandwidth) { + xhci_warn(xhci, "Not enough bandwidth. Proposed: %u, Max: %u\n", + bw_used, max_bandwidth); + return -ENOMEM; + } + + bw_table->bw_used = bw_used; + return 0; +} + +static bool xhci_is_async_ep(unsigned int ep_type) +{ + return (ep_type != ISOC_OUT_EP && ep_type != INT_OUT_EP && + ep_type != ISOC_IN_EP && + ep_type != INT_IN_EP); +} + +static bool xhci_is_sync_in_ep(unsigned int ep_type) +{ + return (ep_type == ISOC_IN_EP || ep_type == INT_IN_EP); +} + +static unsigned int xhci_get_ss_bw_consumed(struct xhci_bw_info *ep_bw) +{ + unsigned int mps = DIV_ROUND_UP(ep_bw->max_packet_size, SS_BLOCK); + + if (ep_bw->ep_interval == 0) + return SS_OVERHEAD_BURST + + (ep_bw->mult * ep_bw->num_packets * + (SS_OVERHEAD + mps)); + return DIV_ROUND_UP(ep_bw->mult * ep_bw->num_packets * + (SS_OVERHEAD + mps + SS_OVERHEAD_BURST), + 1 << ep_bw->ep_interval); + +} + +void xhci_drop_ep_from_interval_table(struct xhci_hcd *xhci, + struct xhci_bw_info *ep_bw, + struct xhci_interval_bw_table *bw_table, + struct usb_device *udev, + struct xhci_virt_ep *virt_ep, + struct xhci_tt_bw_info *tt_info) +{ + struct xhci_interval_bw *interval_bw; + int normalized_interval; + + if (xhci_is_async_ep(ep_bw->type)) + return; + + if (udev->speed == USB_SPEED_SUPER) { + if (xhci_is_sync_in_ep(ep_bw->type)) + xhci->devs[udev->slot_id]->bw_table->ss_bw_in -= + xhci_get_ss_bw_consumed(ep_bw); + else + xhci->devs[udev->slot_id]->bw_table->ss_bw_out -= + xhci_get_ss_bw_consumed(ep_bw); + return; + } + + /* SuperSpeed endpoints never get added to intervals in the table, so + * this check is only valid for HS/FS/LS devices. + */ + if (list_empty(&virt_ep->bw_endpoint_list)) + return; + /* For LS/FS devices, we need to translate the interval expressed in + * microframes to frames. + */ + if (udev->speed == USB_SPEED_HIGH) + normalized_interval = ep_bw->ep_interval; + else + normalized_interval = ep_bw->ep_interval - 3; + + if (normalized_interval == 0) + bw_table->interval0_esit_payload -= ep_bw->max_esit_payload; + interval_bw = &bw_table->interval_bw[normalized_interval]; + interval_bw->num_packets -= ep_bw->num_packets; + switch (udev->speed) { + case USB_SPEED_LOW: + interval_bw->overhead[LS_OVERHEAD_TYPE] -= 1; + break; + case USB_SPEED_FULL: + interval_bw->overhead[FS_OVERHEAD_TYPE] -= 1; + break; + case USB_SPEED_HIGH: + interval_bw->overhead[HS_OVERHEAD_TYPE] -= 1; + break; + case USB_SPEED_SUPER: + case USB_SPEED_UNKNOWN: + case USB_SPEED_WIRELESS: + /* Should never happen because only LS/FS/HS endpoints will get + * added to the endpoint list. + */ + return; + } + if (tt_info) + tt_info->active_eps -= 1; + list_del_init(&virt_ep->bw_endpoint_list); +} + +static void xhci_add_ep_to_interval_table(struct xhci_hcd *xhci, + struct xhci_bw_info *ep_bw, + struct xhci_interval_bw_table *bw_table, + struct usb_device *udev, + struct xhci_virt_ep *virt_ep, + struct xhci_tt_bw_info *tt_info) +{ + struct xhci_interval_bw *interval_bw; + struct xhci_virt_ep *smaller_ep; + int normalized_interval; + + if (xhci_is_async_ep(ep_bw->type)) + return; + + if (udev->speed == USB_SPEED_SUPER) { + if (xhci_is_sync_in_ep(ep_bw->type)) + xhci->devs[udev->slot_id]->bw_table->ss_bw_in += + xhci_get_ss_bw_consumed(ep_bw); + else + xhci->devs[udev->slot_id]->bw_table->ss_bw_out += + xhci_get_ss_bw_consumed(ep_bw); + return; + } + + /* For LS/FS devices, we need to translate the interval expressed in + * microframes to frames. + */ + if (udev->speed == USB_SPEED_HIGH) + normalized_interval = ep_bw->ep_interval; + else + normalized_interval = ep_bw->ep_interval - 3; + + if (normalized_interval == 0) + bw_table->interval0_esit_payload += ep_bw->max_esit_payload; + interval_bw = &bw_table->interval_bw[normalized_interval]; + interval_bw->num_packets += ep_bw->num_packets; + switch (udev->speed) { + case USB_SPEED_LOW: + interval_bw->overhead[LS_OVERHEAD_TYPE] += 1; + break; + case USB_SPEED_FULL: + interval_bw->overhead[FS_OVERHEAD_TYPE] += 1; + break; + case USB_SPEED_HIGH: + interval_bw->overhead[HS_OVERHEAD_TYPE] += 1; + break; + case USB_SPEED_SUPER: + case USB_SPEED_UNKNOWN: + case USB_SPEED_WIRELESS: + /* Should never happen because only LS/FS/HS endpoints will get + * added to the endpoint list. + */ + return; + } + + if (tt_info) + tt_info->active_eps += 1; + /* Insert the endpoint into the list, largest max packet size first. */ + list_for_each_entry(smaller_ep, &interval_bw->endpoints, + bw_endpoint_list) { + if (ep_bw->max_packet_size >= + smaller_ep->bw_info.max_packet_size) { + /* Add the new ep before the smaller endpoint */ + list_add_tail(&virt_ep->bw_endpoint_list, + &smaller_ep->bw_endpoint_list); + return; + } + } + /* Add the new endpoint at the end of the list. */ + list_add_tail(&virt_ep->bw_endpoint_list, + &interval_bw->endpoints); +} + +void xhci_update_tt_active_eps(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int old_active_eps) +{ + struct xhci_root_port_bw_info *rh_bw_info; + if (!virt_dev->tt_info) + return; + + rh_bw_info = &xhci->rh_bw[virt_dev->real_port - 1]; + if (old_active_eps == 0 && + virt_dev->tt_info->active_eps != 0) { + rh_bw_info->num_active_tts += 1; + rh_bw_info->bw_table.bw_used += TT_HS_OVERHEAD; + } else if (old_active_eps != 0 && + virt_dev->tt_info->active_eps == 0) { + rh_bw_info->num_active_tts -= 1; + rh_bw_info->bw_table.bw_used -= TT_HS_OVERHEAD; + } +} + +static int xhci_reserve_bandwidth(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct xhci_container_ctx *in_ctx) +{ + struct xhci_bw_info ep_bw_info[31]; + int i; + struct xhci_input_control_ctx *ctrl_ctx; + int old_active_eps = 0; + + if (virt_dev->tt_info) + old_active_eps = virt_dev->tt_info->active_eps; + + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + + for (i = 0; i < 31; i++) { + if (!EP_IS_ADDED(ctrl_ctx, i) && !EP_IS_DROPPED(ctrl_ctx, i)) + continue; + + /* Make a copy of the BW info in case we need to revert this */ + memcpy(&ep_bw_info[i], &virt_dev->eps[i].bw_info, + sizeof(ep_bw_info[i])); + /* Drop the endpoint from the interval table if the endpoint is + * being dropped or changed. + */ + if (EP_IS_DROPPED(ctrl_ctx, i)) + xhci_drop_ep_from_interval_table(xhci, + &virt_dev->eps[i].bw_info, + virt_dev->bw_table, + virt_dev->udev, + &virt_dev->eps[i], + virt_dev->tt_info); + } + /* Overwrite the information stored in the endpoints' bw_info */ + xhci_update_bw_info(xhci, virt_dev->in_ctx, ctrl_ctx, virt_dev); + for (i = 0; i < 31; i++) { + /* Add any changed or added endpoints to the interval table */ + if (EP_IS_ADDED(ctrl_ctx, i)) + xhci_add_ep_to_interval_table(xhci, + &virt_dev->eps[i].bw_info, + virt_dev->bw_table, + virt_dev->udev, + &virt_dev->eps[i], + virt_dev->tt_info); + } + + if (!xhci_check_bw_table(xhci, virt_dev, old_active_eps)) { + /* Ok, this fits in the bandwidth we have. + * Update the number of active TTs. + */ + xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); + return 0; + } + + /* We don't have enough bandwidth for this, revert the stored info. */ + for (i = 0; i < 31; i++) { + if (!EP_IS_ADDED(ctrl_ctx, i) && !EP_IS_DROPPED(ctrl_ctx, i)) + continue; + + /* Drop the new copies of any added or changed endpoints from + * the interval table. + */ + if (EP_IS_ADDED(ctrl_ctx, i)) { + xhci_drop_ep_from_interval_table(xhci, + &virt_dev->eps[i].bw_info, + virt_dev->bw_table, + virt_dev->udev, + &virt_dev->eps[i], + virt_dev->tt_info); + } + /* Revert the endpoint back to its old information */ + memcpy(&virt_dev->eps[i].bw_info, &ep_bw_info[i], + sizeof(ep_bw_info[i])); + /* Add any changed or dropped endpoints back into the table */ + if (EP_IS_DROPPED(ctrl_ctx, i)) + xhci_add_ep_to_interval_table(xhci, + &virt_dev->eps[i].bw_info, + virt_dev->bw_table, + virt_dev->udev, + &virt_dev->eps[i], + virt_dev->tt_info); + } + return -ENOMEM; +} + + +/* Issue a configure endpoint command or evaluate context command + * and wait for it to finish. + */ +static int xhci_configure_endpoint(struct xhci_hcd *xhci, + struct usb_device *udev, + struct xhci_command *command, + bool ctx_change, bool must_succeed) +{ + int ret; + int timeleft; + unsigned long flags; + struct xhci_container_ctx *in_ctx; + struct completion *cmd_completion; + u32 *cmd_status; + struct xhci_virt_device *virt_dev; + union xhci_trb *cmd_trb; + + spin_lock_irqsave(&xhci->lock, flags); + virt_dev = xhci->devs[udev->slot_id]; + + if (command) + in_ctx = command->in_ctx; + else + in_ctx = virt_dev->in_ctx; + + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK) && + xhci_reserve_host_resources(xhci, in_ctx)) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "Not enough host resources, " + "active endpoint contexts = %u\n", + xhci->num_active_eps); + return -ENOMEM; + } + if ((xhci->quirks & XHCI_SW_BW_CHECKING) && + xhci_reserve_bandwidth(xhci, virt_dev, in_ctx)) { + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) + xhci_free_host_resources(xhci, in_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "Not enough bandwidth\n"); + return -ENOMEM; + } + + if (command) { + cmd_completion = command->completion; + cmd_status = &command->status; + command->command_trb = xhci_find_next_enqueue(xhci->cmd_ring); + list_add_tail(&command->cmd_list, &virt_dev->cmd_list); + } else { + cmd_completion = &virt_dev->cmd_completion; + cmd_status = &virt_dev->cmd_status; + } + init_completion(cmd_completion); + + cmd_trb = xhci_find_next_enqueue(xhci->cmd_ring); + if (!ctx_change) + ret = xhci_queue_configure_endpoint(xhci, in_ctx->dma, + udev->slot_id, must_succeed); + else + ret = xhci_queue_evaluate_context(xhci, in_ctx->dma, + udev->slot_id, must_succeed); + if (ret < 0) { + if (command) + list_del(&command->cmd_list); + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) + xhci_free_host_resources(xhci, in_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME allocate a new ring segment\n"); + return -ENOMEM; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Wait for the configure endpoint command to complete */ + timeleft = wait_for_completion_interruptible_timeout( + cmd_completion, + XHCI_CMD_DEFAULT_TIMEOUT); + if (timeleft <= 0) { + xhci_warn(xhci, "%s while waiting for %s command\n", + timeleft == 0 ? "Timeout" : "Signal", + ctx_change == 0 ? + "configure endpoint" : + "evaluate context"); + /* cancel the configure endpoint command */ + ret = xhci_cancel_cmd(xhci, command, cmd_trb); + if (ret < 0) + return ret; + return -ETIME; + } + + if (!ctx_change) + ret = xhci_configure_endpoint_result(xhci, udev, cmd_status); + else + ret = xhci_evaluate_context_result(xhci, udev, cmd_status); + + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) { + spin_lock_irqsave(&xhci->lock, flags); + /* If the command failed, remove the reserved resources. + * Otherwise, clean up the estimate to include dropped eps. + */ + if (ret) + xhci_free_host_resources(xhci, in_ctx); + else + xhci_finish_resource_reservation(xhci, in_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + } + return ret; +} + +/* Called after one or more calls to xhci_add_endpoint() or + * xhci_drop_endpoint(). If this call fails, the USB core is expected + * to call xhci_reset_bandwidth(). + * + * Since we are in the middle of changing either configuration or + * installing a new alt setting, the USB core won't allow URBs to be + * enqueued for any endpoint on the old config or interface. Nothing + * else should be touching the xhci->devs[slot_id] structure, so we + * don't need to take the xhci->lock for manipulating that. + */ +int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) +{ + int i; + int ret = 0; + struct xhci_hcd *xhci; + struct xhci_virt_device *virt_dev; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_slot_ctx *slot_ctx; + + ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); + if (ret <= 0) + return ret; + xhci = hcd_to_xhci(hcd); + if (xhci->xhc_state & XHCI_STATE_DYING) + return -ENODEV; + + xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); + virt_dev = xhci->devs[udev->slot_id]; + + /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ + ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + ctrl_ctx->add_flags &= cpu_to_le32(~EP0_FLAG); + ctrl_ctx->drop_flags &= cpu_to_le32(~(SLOT_FLAG | EP0_FLAG)); + + /* Don't issue the command if there's no endpoints to update. */ + if (ctrl_ctx->add_flags == cpu_to_le32(SLOT_FLAG) && + ctrl_ctx->drop_flags == 0) + return 0; + + xhci_dbg(xhci, "New Input Control Context:\n"); + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + xhci_dbg_ctx(xhci, virt_dev->in_ctx, + LAST_CTX_TO_EP_NUM(le32_to_cpu(slot_ctx->dev_info))); + + ret = xhci_configure_endpoint(xhci, udev, NULL, + false, false); + if (ret) { + /* Callee should call reset_bandwidth() */ + return ret; + } + + xhci_dbg(xhci, "Output context after successful config ep cmd:\n"); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, + LAST_CTX_TO_EP_NUM(le32_to_cpu(slot_ctx->dev_info))); + + /* Free any rings that were dropped, but not changed. */ + for (i = 1; i < 31; ++i) { + if ((le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) && + !(le32_to_cpu(ctrl_ctx->add_flags) & (1 << (i + 1)))) + xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + } + xhci_zero_in_ctx(xhci, virt_dev); + /* + * Install any rings for completely new endpoints or changed endpoints, + * and free or cache any old rings from changed endpoints. + */ + for (i = 1; i < 31; ++i) { + if (!virt_dev->eps[i].new_ring) + continue; + /* Only cache or free the old ring if it exists. + * It may not if this is the first add of an endpoint. + */ + if (virt_dev->eps[i].ring) { + xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + } + virt_dev->eps[i].ring = virt_dev->eps[i].new_ring; + virt_dev->eps[i].new_ring = NULL; + } + + return ret; +} + +void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd *xhci; + struct xhci_virt_device *virt_dev; + int i, ret; + + ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); + if (ret <= 0) + return; + xhci = hcd_to_xhci(hcd); + + xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); + virt_dev = xhci->devs[udev->slot_id]; + /* Free any rings allocated for added endpoints */ + for (i = 0; i < 31; ++i) { + if (virt_dev->eps[i].new_ring) { + xhci_ring_free(xhci, virt_dev->eps[i].new_ring); + virt_dev->eps[i].new_ring = NULL; + } + } + xhci_zero_in_ctx(xhci, virt_dev); +} + +static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx, + struct xhci_container_ctx *out_ctx, + u32 add_flags, u32 drop_flags) +{ + struct xhci_input_control_ctx *ctrl_ctx; + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + ctrl_ctx->add_flags = cpu_to_le32(add_flags); + ctrl_ctx->drop_flags = cpu_to_le32(drop_flags); + xhci_slot_copy(xhci, in_ctx, out_ctx); + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + + xhci_dbg(xhci, "Input Context:\n"); + xhci_dbg_ctx(xhci, in_ctx, xhci_last_valid_endpoint(add_flags)); +} + +static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + struct xhci_dequeue_state *deq_state) +{ + struct xhci_container_ctx *in_ctx; + struct xhci_ep_ctx *ep_ctx; + u32 added_ctxs; + dma_addr_t addr; + + xhci_endpoint_copy(xhci, xhci->devs[slot_id]->in_ctx, + xhci->devs[slot_id]->out_ctx, ep_index); + in_ctx = xhci->devs[slot_id]->in_ctx; + ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index); + addr = xhci_trb_virt_to_dma(deq_state->new_deq_seg, + deq_state->new_deq_ptr); + if (addr == 0) { + xhci_warn(xhci, "WARN Cannot submit config ep after " + "reset ep command\n"); + xhci_warn(xhci, "WARN deq seg = %p, deq ptr = %p\n", + deq_state->new_deq_seg, + deq_state->new_deq_ptr); + return; + } + ep_ctx->deq = cpu_to_le64(addr | deq_state->new_cycle_state); + + added_ctxs = xhci_get_endpoint_flag_from_index(ep_index); + xhci_setup_input_ctx_for_config_ep(xhci, xhci->devs[slot_id]->in_ctx, + xhci->devs[slot_id]->out_ctx, added_ctxs, added_ctxs); +} + +void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, + struct usb_device *udev, unsigned int ep_index) +{ + struct xhci_dequeue_state deq_state; + struct xhci_virt_ep *ep; + + xhci_dbg(xhci, "Cleaning up stalled endpoint ring\n"); + ep = &xhci->devs[udev->slot_id]->eps[ep_index]; + /* We need to move the HW's dequeue pointer past this TD, + * or it will attempt to resend it on the next doorbell ring. + */ + xhci_find_new_dequeue_state(xhci, udev->slot_id, + ep_index, ep->stopped_stream, ep->stopped_td, + &deq_state); + + /* HW with the reset endpoint quirk will use the saved dequeue state to + * issue a configure endpoint command later. + */ + if (!(xhci->quirks & XHCI_RESET_EP_QUIRK)) { + xhci_dbg(xhci, "Queueing new dequeue state\n"); + xhci_queue_new_dequeue_state(xhci, udev->slot_id, + ep_index, ep->stopped_stream, &deq_state); + } else { + /* Better hope no one uses the input context between now and the + * reset endpoint completion! + * XXX: No idea how this hardware will react when stream rings + * are enabled. + */ + xhci_dbg(xhci, "Setting up input context for " + "configure endpoint command\n"); + xhci_setup_input_ctx_for_quirk(xhci, udev->slot_id, + ep_index, &deq_state); + } +} + +/* Deal with stalled endpoints. The core should have sent the control message + * to clear the halt condition. However, we need to make the xHCI hardware + * reset its sequence number, since a device will expect a sequence number of + * zero after the halt condition is cleared. + * Context: in_interrupt + */ +void xhci_endpoint_reset(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd *xhci; + struct usb_device *udev; + unsigned int ep_index; + unsigned long flags; + int ret; + struct xhci_virt_ep *virt_ep; + + xhci = hcd_to_xhci(hcd); + udev = (struct usb_device *) ep->hcpriv; + /* Called with a root hub endpoint (or an endpoint that wasn't added + * with xhci_add_endpoint() + */ + if (!ep->hcpriv) + return; + ep_index = xhci_get_endpoint_index(&ep->desc); + virt_ep = &xhci->devs[udev->slot_id]->eps[ep_index]; + if (!virt_ep->stopped_td) { + xhci_dbg(xhci, "Endpoint 0x%x not halted, refusing to reset.\n", + ep->desc.bEndpointAddress); + return; + } + if (usb_endpoint_xfer_control(&ep->desc)) { + xhci_dbg(xhci, "Control endpoint stall already handled.\n"); + return; + } + + xhci_dbg(xhci, "Queueing reset endpoint command\n"); + spin_lock_irqsave(&xhci->lock, flags); + ret = xhci_queue_reset_ep(xhci, udev->slot_id, ep_index); + /* + * Can't change the ring dequeue pointer until it's transitioned to the + * stopped state, which is only upon a successful reset endpoint + * command. Better hope that last command worked! + */ + if (!ret) { + xhci_cleanup_stalled_ring(xhci, udev, ep_index); + kfree(virt_ep->stopped_td); + xhci_ring_cmd_db(xhci); + } + virt_ep->stopped_td = NULL; + virt_ep->stopped_trb = NULL; + virt_ep->stopped_stream = 0; + spin_unlock_irqrestore(&xhci->lock, flags); + + if (ret) + xhci_warn(xhci, "FIXME allocate a new ring segment\n"); +} + +static int xhci_check_streams_endpoint(struct xhci_hcd *xhci, + struct usb_device *udev, struct usb_host_endpoint *ep, + unsigned int slot_id) +{ + int ret; + unsigned int ep_index; + unsigned int ep_state; + + if (!ep) + return -EINVAL; + ret = xhci_check_args(xhci_to_hcd(xhci), udev, ep, 1, true, __func__); + if (ret <= 0) + return -EINVAL; + if (ep->ss_ep_comp.bmAttributes == 0) { + xhci_warn(xhci, "WARN: SuperSpeed Endpoint Companion" + " descriptor for ep 0x%x does not support streams\n", + ep->desc.bEndpointAddress); + return -EINVAL; + } + + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_state = xhci->devs[slot_id]->eps[ep_index].ep_state; + if (ep_state & EP_HAS_STREAMS || + ep_state & EP_GETTING_STREAMS) { + xhci_warn(xhci, "WARN: SuperSpeed bulk endpoint 0x%x " + "already has streams set up.\n", + ep->desc.bEndpointAddress); + xhci_warn(xhci, "Send email to xHCI maintainer and ask for " + "dynamic stream context array reallocation.\n"); + return -EINVAL; + } + if (!list_empty(&xhci->devs[slot_id]->eps[ep_index].ring->td_list)) { + xhci_warn(xhci, "Cannot setup streams for SuperSpeed bulk " + "endpoint 0x%x; URBs are pending.\n", + ep->desc.bEndpointAddress); + return -EINVAL; + } + return 0; +} + +static void xhci_calculate_streams_entries(struct xhci_hcd *xhci, + unsigned int *num_streams, unsigned int *num_stream_ctxs) +{ + unsigned int max_streams; + + /* The stream context array size must be a power of two */ + *num_stream_ctxs = roundup_pow_of_two(*num_streams); + /* + * Find out how many primary stream array entries the host controller + * supports. Later we may use secondary stream arrays (similar to 2nd + * level page entries), but that's an optional feature for xHCI host + * controllers. xHCs must support at least 4 stream IDs. + */ + max_streams = HCC_MAX_PSA(xhci->hcc_params); + if (*num_stream_ctxs > max_streams) { + xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n", + max_streams); + *num_stream_ctxs = max_streams; + *num_streams = max_streams; + } +} + +/* Returns an error code if one of the endpoint already has streams. + * This does not change any data structures, it only checks and gathers + * information. + */ +static int xhci_calculate_streams_and_bitmask(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int *num_streams, u32 *changed_ep_bitmask) +{ + unsigned int max_streams; + unsigned int endpoint_flag; + int i; + int ret; + + for (i = 0; i < num_eps; i++) { + ret = xhci_check_streams_endpoint(xhci, udev, + eps[i], udev->slot_id); + if (ret < 0) + return ret; + + max_streams = usb_ss_max_streams(&eps[i]->ss_ep_comp); + if (max_streams < (*num_streams - 1)) { + xhci_dbg(xhci, "Ep 0x%x only supports %u stream IDs.\n", + eps[i]->desc.bEndpointAddress, + max_streams); + *num_streams = max_streams+1; + } + + endpoint_flag = xhci_get_endpoint_flag(&eps[i]->desc); + if (*changed_ep_bitmask & endpoint_flag) + return -EINVAL; + *changed_ep_bitmask |= endpoint_flag; + } + return 0; +} + +static u32 xhci_calculate_no_streams_bitmask(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps) +{ + u32 changed_ep_bitmask = 0; + unsigned int slot_id; + unsigned int ep_index; + unsigned int ep_state; + int i; + + slot_id = udev->slot_id; + if (!xhci->devs[slot_id]) + return 0; + + for (i = 0; i < num_eps; i++) { + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + ep_state = xhci->devs[slot_id]->eps[ep_index].ep_state; + /* Are streams already being freed for the endpoint? */ + if (ep_state & EP_GETTING_NO_STREAMS) { + xhci_warn(xhci, "WARN Can't disable streams for " + "endpoint 0x%x\n, " + "streams are being disabled already.", + eps[i]->desc.bEndpointAddress); + return 0; + } + /* Are there actually any streams to free? */ + if (!(ep_state & EP_HAS_STREAMS) && + !(ep_state & EP_GETTING_STREAMS)) { + xhci_warn(xhci, "WARN Can't disable streams for " + "endpoint 0x%x\n, " + "streams are already disabled!", + eps[i]->desc.bEndpointAddress); + xhci_warn(xhci, "WARN xhci_free_streams() called " + "with non-streams endpoint\n"); + return 0; + } + changed_ep_bitmask |= xhci_get_endpoint_flag(&eps[i]->desc); + } + return changed_ep_bitmask; +} + +/* + * The USB device drivers use this function (though the HCD interface in USB + * core) to prepare a set of bulk endpoints to use streams. Streams are used to + * coordinate mass storage command queueing across multiple endpoints (basically + * a stream ID == a task ID). + * + * Setting up streams involves allocating the same size stream context array + * for each endpoint and issuing a configure endpoint command for all endpoints. + * + * Don't allow the call to succeed if one endpoint only supports one stream + * (which means it doesn't support streams at all). + * + * Drivers may get less stream IDs than they asked for, if the host controller + * hardware or endpoints claim they can't support the number of requested + * stream IDs. + */ +int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int num_streams, gfp_t mem_flags) +{ + int i, ret; + struct xhci_hcd *xhci; + struct xhci_virt_device *vdev; + struct xhci_command *config_cmd; + unsigned int ep_index; + unsigned int num_stream_ctxs; + unsigned long flags; + u32 changed_ep_bitmask = 0; + + if (!eps) + return -EINVAL; + + /* Add one to the number of streams requested to account for + * stream 0 that is reserved for xHCI usage. + */ + num_streams += 1; + xhci = hcd_to_xhci(hcd); + xhci_dbg(xhci, "Driver wants %u stream IDs (including stream 0).\n", + num_streams); + + config_cmd = xhci_alloc_command(xhci, true, true, mem_flags); + if (!config_cmd) { + xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); + return -ENOMEM; + } + + /* Check to make sure all endpoints are not already configured for + * streams. While we're at it, find the maximum number of streams that + * all the endpoints will support and check for duplicate endpoints. + */ + spin_lock_irqsave(&xhci->lock, flags); + ret = xhci_calculate_streams_and_bitmask(xhci, udev, eps, + num_eps, &num_streams, &changed_ep_bitmask); + if (ret < 0) { + xhci_free_command(xhci, config_cmd); + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; + } + if (num_streams <= 1) { + xhci_warn(xhci, "WARN: endpoints can't handle " + "more than one stream.\n"); + xhci_free_command(xhci, config_cmd); + spin_unlock_irqrestore(&xhci->lock, flags); + return -EINVAL; + } + vdev = xhci->devs[udev->slot_id]; + /* Mark each endpoint as being in transition, so + * xhci_urb_enqueue() will reject all URBs. + */ + for (i = 0; i < num_eps; i++) { + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + vdev->eps[ep_index].ep_state |= EP_GETTING_STREAMS; + } + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Setup internal data structures and allocate HW data structures for + * streams (but don't install the HW structures in the input context + * until we're sure all memory allocation succeeded). + */ + xhci_calculate_streams_entries(xhci, &num_streams, &num_stream_ctxs); + xhci_dbg(xhci, "Need %u stream ctx entries for %u stream IDs.\n", + num_stream_ctxs, num_streams); + + for (i = 0; i < num_eps; i++) { + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + vdev->eps[ep_index].stream_info = xhci_alloc_stream_info(xhci, + num_stream_ctxs, + num_streams, mem_flags); + if (!vdev->eps[ep_index].stream_info) + goto cleanup; + /* Set maxPstreams in endpoint context and update deq ptr to + * point to stream context array. FIXME + */ + } + + /* Set up the input context for a configure endpoint command. */ + for (i = 0; i < num_eps; i++) { + struct xhci_ep_ctx *ep_ctx; + + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + ep_ctx = xhci_get_ep_ctx(xhci, config_cmd->in_ctx, ep_index); + + xhci_endpoint_copy(xhci, config_cmd->in_ctx, + vdev->out_ctx, ep_index); + xhci_setup_streams_ep_input_ctx(xhci, ep_ctx, + vdev->eps[ep_index].stream_info); + } + /* Tell the HW to drop its old copy of the endpoint context info + * and add the updated copy from the input context. + */ + xhci_setup_input_ctx_for_config_ep(xhci, config_cmd->in_ctx, + vdev->out_ctx, changed_ep_bitmask, changed_ep_bitmask); + + /* Issue and wait for the configure endpoint command */ + ret = xhci_configure_endpoint(xhci, udev, config_cmd, + false, false); + + /* xHC rejected the configure endpoint command for some reason, so we + * leave the old ring intact and free our internal streams data + * structure. + */ + if (ret < 0) + goto cleanup; + + spin_lock_irqsave(&xhci->lock, flags); + for (i = 0; i < num_eps; i++) { + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + vdev->eps[ep_index].ep_state &= ~EP_GETTING_STREAMS; + xhci_dbg(xhci, "Slot %u ep ctx %u now has streams.\n", + udev->slot_id, ep_index); + vdev->eps[ep_index].ep_state |= EP_HAS_STREAMS; + } + xhci_free_command(xhci, config_cmd); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Subtract 1 for stream 0, which drivers can't use */ + return num_streams - 1; + +cleanup: + /* If it didn't work, free the streams! */ + for (i = 0; i < num_eps; i++) { + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + xhci_free_stream_info(xhci, vdev->eps[ep_index].stream_info); + vdev->eps[ep_index].stream_info = NULL; + /* FIXME Unset maxPstreams in endpoint context and + * update deq ptr to point to normal string ring. + */ + vdev->eps[ep_index].ep_state &= ~EP_GETTING_STREAMS; + vdev->eps[ep_index].ep_state &= ~EP_HAS_STREAMS; + xhci_endpoint_zero(xhci, vdev, eps[i]); + } + xhci_free_command(xhci, config_cmd); + return -ENOMEM; +} + +/* Transition the endpoint from using streams to being a "normal" endpoint + * without streams. + * + * Modify the endpoint context state, submit a configure endpoint command, + * and free all endpoint rings for streams if that completes successfully. + */ +int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags) +{ + int i, ret; + struct xhci_hcd *xhci; + struct xhci_virt_device *vdev; + struct xhci_command *command; + unsigned int ep_index; + unsigned long flags; + u32 changed_ep_bitmask; + + xhci = hcd_to_xhci(hcd); + vdev = xhci->devs[udev->slot_id]; + + /* Set up a configure endpoint command to remove the streams rings */ + spin_lock_irqsave(&xhci->lock, flags); + changed_ep_bitmask = xhci_calculate_no_streams_bitmask(xhci, + udev, eps, num_eps); + if (changed_ep_bitmask == 0) { + spin_unlock_irqrestore(&xhci->lock, flags); + return -EINVAL; + } + + /* Use the xhci_command structure from the first endpoint. We may have + * allocated too many, but the driver may call xhci_free_streams() for + * each endpoint it grouped into one call to xhci_alloc_streams(). + */ + ep_index = xhci_get_endpoint_index(&eps[0]->desc); + command = vdev->eps[ep_index].stream_info->free_streams_command; + for (i = 0; i < num_eps; i++) { + struct xhci_ep_ctx *ep_ctx; + + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + ep_ctx = xhci_get_ep_ctx(xhci, command->in_ctx, ep_index); + xhci->devs[udev->slot_id]->eps[ep_index].ep_state |= + EP_GETTING_NO_STREAMS; + + xhci_endpoint_copy(xhci, command->in_ctx, + vdev->out_ctx, ep_index); + xhci_setup_no_streams_ep_input_ctx(xhci, ep_ctx, + &vdev->eps[ep_index]); + } + xhci_setup_input_ctx_for_config_ep(xhci, command->in_ctx, + vdev->out_ctx, changed_ep_bitmask, changed_ep_bitmask); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Issue and wait for the configure endpoint command, + * which must succeed. + */ + ret = xhci_configure_endpoint(xhci, udev, command, + false, true); + + /* xHC rejected the configure endpoint command for some reason, so we + * leave the streams rings intact. + */ + if (ret < 0) + return ret; + + spin_lock_irqsave(&xhci->lock, flags); + for (i = 0; i < num_eps; i++) { + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + xhci_free_stream_info(xhci, vdev->eps[ep_index].stream_info); + vdev->eps[ep_index].stream_info = NULL; + /* FIXME Unset maxPstreams in endpoint context and + * update deq ptr to point to normal string ring. + */ + vdev->eps[ep_index].ep_state &= ~EP_GETTING_NO_STREAMS; + vdev->eps[ep_index].ep_state &= ~EP_HAS_STREAMS; + } + spin_unlock_irqrestore(&xhci->lock, flags); + + return 0; +} + +/* + * Deletes endpoint resources for endpoints that were active before a Reset + * Device command, or a Disable Slot command. The Reset Device command leaves + * the control endpoint intact, whereas the Disable Slot command deletes it. + * + * Must be called with xhci->lock held. + */ +void xhci_free_device_endpoint_resources(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, bool drop_control_ep) +{ + int i; + unsigned int num_dropped_eps = 0; + unsigned int drop_flags = 0; + + for (i = (drop_control_ep ? 0 : 1); i < 31; i++) { + if (virt_dev->eps[i].ring) { + drop_flags |= 1 << i; + num_dropped_eps++; + } + } + xhci->num_active_eps -= num_dropped_eps; + if (num_dropped_eps) + xhci_dbg(xhci, "Dropped %u ep ctxs, flags = 0x%x, " + "%u now active.\n", + num_dropped_eps, drop_flags, + xhci->num_active_eps); +} + +/* + * This submits a Reset Device Command, which will set the device state to 0, + * set the device address to 0, and disable all the endpoints except the default + * control endpoint. The USB core should come back and call + * xhci_address_device(), and then re-set up the configuration. If this is + * called because of a usb_reset_and_verify_device(), then the old alternate + * settings will be re-installed through the normal bandwidth allocation + * functions. + * + * Wait for the Reset Device command to finish. Remove all structures + * associated with the endpoints that were disabled. Clear the input device + * structure? Cache the rings? Reset the control endpoint 0 max packet size? + * + * If the virt_dev to be reset does not exist or does not match the udev, + * it means the device is lost, possibly due to the xHC restore error and + * re-initialization during S3/S4. In this case, call xhci_alloc_dev() to + * re-allocate the device. + */ +int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + int ret, i; + unsigned long flags; + struct xhci_hcd *xhci; + unsigned int slot_id; + struct xhci_virt_device *virt_dev; + struct xhci_command *reset_device_cmd; + int timeleft; + int last_freed_endpoint; + struct xhci_slot_ctx *slot_ctx; + int old_active_eps = 0; + + ret = xhci_check_args(hcd, udev, NULL, 0, false, __func__); + if (ret <= 0) + return ret; + xhci = hcd_to_xhci(hcd); + slot_id = udev->slot_id; + virt_dev = xhci->devs[slot_id]; + if (!virt_dev) { + xhci_dbg(xhci, "The device to be reset with slot ID %u does " + "not exist. Re-allocate the device\n", slot_id); + ret = xhci_alloc_dev(hcd, udev); + if (ret == 1) + return 0; + else + return -EINVAL; + } + + if (virt_dev->udev != udev) { + /* If the virt_dev and the udev does not match, this virt_dev + * may belong to another udev. + * Re-allocate the device. + */ + xhci_dbg(xhci, "The device to be reset with slot ID %u does " + "not match the udev. Re-allocate the device\n", + slot_id); + ret = xhci_alloc_dev(hcd, udev); + if (ret == 1) + return 0; + else + return -EINVAL; + } + + /* If device is not setup, there is no point in resetting it */ + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + if (GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)) == + SLOT_STATE_DISABLED) + return 0; + + xhci_dbg(xhci, "Resetting device with slot ID %u\n", slot_id); + /* Allocate the command structure that holds the struct completion. + * Assume we're in process context, since the normal device reset + * process has to wait for the device anyway. Storage devices are + * reset as part of error handling, so use GFP_NOIO instead of + * GFP_KERNEL. + */ + reset_device_cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO); + if (!reset_device_cmd) { + xhci_dbg(xhci, "Couldn't allocate command structure.\n"); + return -ENOMEM; + } + + /* Attempt to submit the Reset Device command to the command ring */ + spin_lock_irqsave(&xhci->lock, flags); + reset_device_cmd->command_trb = xhci_find_next_enqueue(xhci->cmd_ring); + + list_add_tail(&reset_device_cmd->cmd_list, &virt_dev->cmd_list); + ret = xhci_queue_reset_device(xhci, slot_id); + if (ret) { + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + list_del(&reset_device_cmd->cmd_list); + spin_unlock_irqrestore(&xhci->lock, flags); + goto command_cleanup; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Wait for the Reset Device command to finish */ + timeleft = wait_for_completion_interruptible_timeout( + reset_device_cmd->completion, + USB_CTRL_SET_TIMEOUT); + if (timeleft <= 0) { + xhci_warn(xhci, "%s while waiting for reset device command\n", + timeleft == 0 ? "Timeout" : "Signal"); + spin_lock_irqsave(&xhci->lock, flags); + /* The timeout might have raced with the event ring handler, so + * only delete from the list if the item isn't poisoned. + */ + if (reset_device_cmd->cmd_list.next != LIST_POISON1) + list_del(&reset_device_cmd->cmd_list); + spin_unlock_irqrestore(&xhci->lock, flags); + ret = -ETIME; + goto command_cleanup; + } + + /* The Reset Device command can't fail, according to the 0.95/0.96 spec, + * unless we tried to reset a slot ID that wasn't enabled, + * or the device wasn't in the addressed or configured state. + */ + ret = reset_device_cmd->status; + switch (ret) { + case COMP_EBADSLT: /* 0.95 completion code for bad slot ID */ + case COMP_CTX_STATE: /* 0.96 completion code for same thing */ + xhci_info(xhci, "Can't reset device (slot ID %u) in %s state\n", + slot_id, + xhci_get_slot_state(xhci, virt_dev->out_ctx)); + xhci_info(xhci, "Not freeing device rings.\n"); + /* Don't treat this as an error. May change my mind later. */ + ret = 0; + goto command_cleanup; + case COMP_SUCCESS: + xhci_dbg(xhci, "Successful reset device command.\n"); + break; + default: + if (xhci_is_vendor_info_code(xhci, ret)) + break; + xhci_warn(xhci, "Unknown completion code %u for " + "reset device command.\n", ret); + ret = -EINVAL; + goto command_cleanup; + } + + /* Free up host controller endpoint resources */ + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) { + spin_lock_irqsave(&xhci->lock, flags); + /* Don't delete the default control endpoint resources */ + xhci_free_device_endpoint_resources(xhci, virt_dev, false); + spin_unlock_irqrestore(&xhci->lock, flags); + } + + /* Everything but endpoint 0 is disabled, so free or cache the rings. */ + last_freed_endpoint = 1; + for (i = 1; i < 31; ++i) { + struct xhci_virt_ep *ep = &virt_dev->eps[i]; + + if (ep->ep_state & EP_HAS_STREAMS) { + xhci_free_stream_info(xhci, ep->stream_info); + ep->stream_info = NULL; + ep->ep_state &= ~EP_HAS_STREAMS; + } + + if (ep->ring) { + xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + last_freed_endpoint = i; + } + if (!list_empty(&virt_dev->eps[i].bw_endpoint_list)) + xhci_drop_ep_from_interval_table(xhci, + &virt_dev->eps[i].bw_info, + virt_dev->bw_table, + udev, + &virt_dev->eps[i], + virt_dev->tt_info); + xhci_clear_endpoint_bw_info(&virt_dev->eps[i].bw_info); + } + /* If necessary, update the number of active TTs on this root port */ + xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); + + xhci_dbg(xhci, "Output context after successful reset device cmd:\n"); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, last_freed_endpoint); + ret = 0; + +command_cleanup: + xhci_free_command(xhci, reset_device_cmd); + return ret; +} + +/* + * At this point, the struct usb_device is about to go away, the device has + * disconnected, and all traffic has been stopped and the endpoints have been + * disabled. Free any HC data structures associated with that device. + */ +void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_virt_device *virt_dev; + struct device *dev = hcd->self.controller; + unsigned long flags; + u32 state; + int i, ret; + +#ifndef CONFIG_USB_DEFAULT_PERSIST + /* + * We called pm_runtime_get_noresume when the device was attached. + * Decrement the counter here to allow controller to runtime suspend + * if no devices remain. + */ + if (xhci->quirks & XHCI_RESET_ON_RESUME) + pm_runtime_put_noidle(dev); +#endif + + ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); + /* If the host is halted due to driver unload, we still need to free the + * device. + */ + if (ret <= 0 && ret != -ENODEV) + return; + + virt_dev = xhci->devs[udev->slot_id]; + + /* Stop any wayward timer functions (which may grab the lock) */ + for (i = 0; i < 31; ++i) { + virt_dev->eps[i].ep_state &= ~EP_HALT_PENDING; + del_timer_sync(&virt_dev->eps[i].stop_cmd_timer); + } + + if (udev->usb2_hw_lpm_enabled) { + xhci_set_usb2_hardware_lpm(hcd, udev, 0); + udev->usb2_hw_lpm_enabled = 0; + } + + spin_lock_irqsave(&xhci->lock, flags); + /* Don't disable the slot if the host controller is dead. */ + state = xhci_readl(xhci, &xhci->op_regs->status); + if (state == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING) || + (xhci->xhc_state & XHCI_STATE_HALTED)) { + xhci_free_virt_device(xhci, udev->slot_id); + spin_unlock_irqrestore(&xhci->lock, flags); + return; + } + + if (xhci_queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + return; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + /* + * Event command completion handler will free any data structures + * associated with the slot. XXX Can free sleep? + */ +} + +/* + * Checks if we have enough host controller resources for the default control + * endpoint. + * + * Must be called with xhci->lock held. + */ +static int xhci_reserve_host_control_ep_resources(struct xhci_hcd *xhci) +{ + if (xhci->num_active_eps + 1 > xhci->limit_active_eps) { + xhci_dbg(xhci, "Not enough ep ctxs: " + "%u active, need to add 1, limit is %u.\n", + xhci->num_active_eps, xhci->limit_active_eps); + return -ENOMEM; + } + xhci->num_active_eps += 1; + xhci_dbg(xhci, "Adding 1 ep ctx, %u now active.\n", + xhci->num_active_eps); + return 0; +} + + +/* + * Returns 0 if the xHC ran out of device slots, the Enable Slot command + * timed out, or allocating memory failed. Returns 1 on success. + */ +int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct device *dev = hcd->self.controller; + unsigned long flags; + int timeleft; + int ret; + union xhci_trb *cmd_trb; + + spin_lock_irqsave(&xhci->lock, flags); + cmd_trb = xhci_find_next_enqueue(xhci->cmd_ring); + ret = xhci_queue_slot_control(xhci, TRB_ENABLE_SLOT, 0); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + return 0; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* XXX: how much time for xHC slot assignment? */ + timeleft = wait_for_completion_interruptible_timeout(&xhci->addr_dev, + XHCI_CMD_DEFAULT_TIMEOUT); + if (timeleft <= 0) { + xhci_warn(xhci, "%s while waiting for a slot\n", + timeleft == 0 ? "Timeout" : "Signal"); + /* cancel the enable slot request */ + return xhci_cancel_cmd(xhci, NULL, cmd_trb); + } + + if (!xhci->slot_id) { + xhci_err(xhci, "Error while assigning device slot ID\n"); + return 0; + } + + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) { + spin_lock_irqsave(&xhci->lock, flags); + ret = xhci_reserve_host_control_ep_resources(xhci); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "Not enough host resources, " + "active endpoint contexts = %u\n", + xhci->num_active_eps); + goto disable_slot; + } + spin_unlock_irqrestore(&xhci->lock, flags); + } + /* Use GFP_NOIO, since this function can be called from + * xhci_discover_or_reset_device(), which may be called as part of + * mass storage driver error handling. + */ + if (!xhci_alloc_virt_device(xhci, xhci->slot_id, udev, GFP_NOIO)) { + xhci_warn(xhci, "Could not allocate xHCI USB device data structures\n"); + goto disable_slot; + } + udev->slot_id = xhci->slot_id; + +#ifndef CONFIG_USB_DEFAULT_PERSIST + /* + * If resetting upon resume, we can't put the controller into runtime + * suspend if there is a device attached. + */ + if (xhci->quirks & XHCI_RESET_ON_RESUME) + pm_runtime_get_noresume(dev); +#endif + + /* Is this a LS or FS device under a HS hub? */ + /* Hub or peripherial? */ + return 1; + +disable_slot: + /* Disable slot, if we can do it without mem alloc */ + spin_lock_irqsave(&xhci->lock, flags); + if (!xhci_queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; +} + +/* + * Issue an Address Device command (which will issue a SetAddress request to + * the device). + * We should be protected by the usb_address0_mutex in khubd's hub_port_init, so + * we should only issue and wait on one address command at the same time. + * + * We add one to the device address issued by the hardware because the USB core + * uses address 1 for the root hubs (even though they're not really devices). + */ +int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + unsigned long flags; + int timeleft; + struct xhci_virt_device *virt_dev; + int ret = 0; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_slot_ctx *slot_ctx; + struct xhci_input_control_ctx *ctrl_ctx; + u64 temp_64; + union xhci_trb *cmd_trb; + + if (!udev->slot_id) { + xhci_dbg(xhci, "Bad Slot ID %d\n", udev->slot_id); + return -EINVAL; + } + + virt_dev = xhci->devs[udev->slot_id]; + + if (WARN_ON(!virt_dev)) { + /* + * In plug/unplug torture test with an NEC controller, + * a zero-dereference was observed once due to virt_dev = 0. + * Print useful debug rather than crash if it is observed again! + */ + xhci_warn(xhci, "Virt dev invalid for slot_id 0x%x!\n", + udev->slot_id); + return -EINVAL; + } + + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + /* + * If this is the first Set Address since device plug-in or + * virt_device realloaction after a resume with an xHCI power loss, + * then set up the slot context. + */ + if (!slot_ctx->dev_info) + xhci_setup_addressable_virt_dev(xhci, udev); + /* Otherwise, update the control endpoint ring enqueue pointer. */ + else + xhci_copy_ep0_dequeue_into_input_ctx(xhci, udev); + ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); + ctrl_ctx->drop_flags = 0; + + xhci_dbg(xhci, "Slot ID %d Input Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->in_ctx, 2); + + spin_lock_irqsave(&xhci->lock, flags); + cmd_trb = xhci_find_next_enqueue(xhci->cmd_ring); + ret = xhci_queue_address_device(xhci, virt_dev->in_ctx->dma, + udev->slot_id); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + return ret; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* ctrl tx can take up to 5 sec; XXX: need more time for xHC? */ + timeleft = wait_for_completion_interruptible_timeout(&xhci->addr_dev, + XHCI_CMD_DEFAULT_TIMEOUT); + /* FIXME: From section 4.3.4: "Software shall be responsible for timing + * the SetAddress() "recovery interval" required by USB and aborting the + * command on a timeout. + */ + if (timeleft <= 0) { + xhci_warn(xhci, "%s while waiting for address device command\n", + timeleft == 0 ? "Timeout" : "Signal"); + /* cancel the address device command */ + ret = xhci_cancel_cmd(xhci, NULL, cmd_trb); + if (ret < 0) + return ret; + return -ETIME; + } + + switch (virt_dev->cmd_status) { + case COMP_CTX_STATE: + case COMP_EBADSLT: + xhci_err(xhci, "Setup ERROR: address device command for slot %d.\n", + udev->slot_id); + ret = -EINVAL; + break; + case COMP_TX_ERR: + dev_warn(&udev->dev, "Device not responding to set address.\n"); + ret = -EPROTO; + break; + case COMP_DEV_ERR: + dev_warn(&udev->dev, "ERROR: Incompatible device for address " + "device command.\n"); + ret = -ENODEV; + break; + case COMP_SUCCESS: + xhci_dbg(xhci, "Successful Address Device command\n"); + break; + default: + xhci_err(xhci, "ERROR: unexpected command completion " + "code 0x%x.\n", virt_dev->cmd_status); + xhci_dbg(xhci, "Slot ID %d Output Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, 2); + ret = -EINVAL; + break; + } + if (ret) { + return ret; + } + temp_64 = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); + xhci_dbg(xhci, "Op regs DCBAA ptr = %#016llx\n", temp_64); + xhci_dbg(xhci, "Slot ID %d dcbaa entry @%p = %#016llx\n", + udev->slot_id, + &xhci->dcbaa->dev_context_ptrs[udev->slot_id], + (unsigned long long) + le64_to_cpu(xhci->dcbaa->dev_context_ptrs[udev->slot_id])); + xhci_dbg(xhci, "Output Context DMA address = %#08llx\n", + (unsigned long long)virt_dev->out_ctx->dma); + xhci_dbg(xhci, "Slot ID %d Input Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->in_ctx, 2); + xhci_dbg(xhci, "Slot ID %d Output Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, 2); + /* + * USB core uses address 1 for the roothubs, so we add one to the + * address given back to us by the HC. + */ + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + /* Use kernel assigned address for devices; store xHC assigned + * address locally. */ + virt_dev->address = (le32_to_cpu(slot_ctx->dev_state) & DEV_ADDR_MASK) + + 1; + /* Zero the input context control for later use */ + ctrl_ctx->add_flags = 0; + ctrl_ctx->drop_flags = 0; + + xhci_dbg(xhci, "Internal device address = %d\n", virt_dev->address); + + return 0; +} + +/* + * Transfer the port index into real index in the HW port status + * registers. Caculate offset between the port's PORTSC register + * and port status base. Divide the number of per port register + * to get the real index. The raw port number bases 1. + */ +int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + __le32 __iomem *base_addr = &xhci->op_regs->port_status_base; + __le32 __iomem *addr; + int raw_port; + + if (hcd->speed != HCD_USB3) + addr = xhci->usb2_ports[port1 - 1]; + else + addr = xhci->usb3_ports[port1 - 1]; + + raw_port = (addr - base_addr)/NUM_PORT_REGS + 1; + return raw_port; +} + +#ifdef CONFIG_PM_RUNTIME + +/* BESL to HIRD Encoding array for USB2 LPM */ +static int xhci_besl_encoding[16] = {125, 150, 200, 300, 400, 500, 1000, 2000, + 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000}; + +/* Calculate HIRD/BESL for USB2 PORTPMSC*/ +static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, + struct usb_device *udev) +{ + int u2del, besl, besl_host; + int besl_device = 0; + u32 field; + + u2del = HCS_U2_LATENCY(xhci->hcs_params3); + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + + if (field & USB_BESL_SUPPORT) { + for (besl_host = 0; besl_host < 16; besl_host++) { + if (xhci_besl_encoding[besl_host] >= u2del) + break; + } + /* Use baseline BESL value as default */ + if (field & USB_BESL_BASELINE_VALID) + besl_device = USB_GET_BESL_BASELINE(field); + else if (field & USB_BESL_DEEP_VALID) + besl_device = USB_GET_BESL_DEEP(field); + } else { + if (u2del <= 50) + besl_host = 0; + else + besl_host = (u2del - 51) / 75 + 1; + } + + besl = besl_host + besl_device; + if (besl > 15) + besl = 15; + + return besl; +} + +static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd, + struct usb_device *udev) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct dev_info *dev_info; + __le32 __iomem **port_array; + __le32 __iomem *addr, *pm_addr; + u32 temp, dev_id; + unsigned int port_num; + unsigned long flags; + int hird; + int ret; + + if (hcd->speed == HCD_USB3 || !xhci->sw_lpm_support || + !udev->lpm_capable) + return -EINVAL; + + /* we only support lpm for non-hub device connected to root hub yet */ + if (!udev->parent || udev->parent->parent || + udev->descriptor.bDeviceClass == USB_CLASS_HUB) + return -EINVAL; + + spin_lock_irqsave(&xhci->lock, flags); + + /* Look for devices in lpm_failed_devs list */ + dev_id = le16_to_cpu(udev->descriptor.idVendor) << 16 | + le16_to_cpu(udev->descriptor.idProduct); + list_for_each_entry(dev_info, &xhci->lpm_failed_devs, list) { + if (dev_info->dev_id == dev_id) { + ret = -EINVAL; + goto finish; + } + } + + port_array = xhci->usb2_ports; + port_num = udev->portnum - 1; + + if (port_num > HCS_MAX_PORTS(xhci->hcs_params1)) { + xhci_dbg(xhci, "invalid port number %d\n", udev->portnum); + ret = -EINVAL; + goto finish; + } + + /* + * Test USB 2.0 software LPM. + * FIXME: some xHCI 1.0 hosts may implement a new register to set up + * hardware-controlled USB 2.0 LPM. See section 5.4.11 and 4.23.5.1.1.1 + * in the June 2011 errata release. + */ + xhci_dbg(xhci, "test port %d software LPM\n", port_num); + /* + * Set L1 Device Slot and HIRD/BESL. + * Check device's USB 2.0 extension descriptor to determine whether + * HIRD or BESL shoule be used. See USB2.0 LPM errata. + */ + pm_addr = port_array[port_num] + 1; + hird = xhci_calculate_hird_besl(xhci, udev); + temp = PORT_L1DS(udev->slot_id) | PORT_HIRD(hird); + xhci_writel(xhci, temp, pm_addr); + + /* Set port link state to U2(L1) */ + addr = port_array[port_num]; + xhci_set_link_state(xhci, port_array, port_num, XDEV_U2); + + /* wait for ACK */ + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(10); + spin_lock_irqsave(&xhci->lock, flags); + + /* Check L1 Status */ + ret = xhci_handshake(xhci, pm_addr, + PORT_L1S_MASK, PORT_L1S_SUCCESS, 125); + if (ret != -ETIMEDOUT) { + /* enter L1 successfully */ + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, "port %d entered L1 state, port status 0x%x\n", + port_num, temp); + ret = 0; + } else { + temp = xhci_readl(xhci, pm_addr); + xhci_dbg(xhci, "port %d software lpm failed, L1 status %d\n", + port_num, temp & PORT_L1S_MASK); + ret = -EINVAL; + } + + /* Resume the port */ + xhci_set_link_state(xhci, port_array, port_num, XDEV_U0); + + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(10); + spin_lock_irqsave(&xhci->lock, flags); + + /* Clear PLC */ + xhci_test_and_clear_bit(xhci, port_array, port_num, PORT_PLC); + + /* Check PORTSC to make sure the device is in the right state */ + if (!ret) { + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, "resumed port %d status 0x%x\n", port_num, temp); + if (!(temp & PORT_CONNECT) || !(temp & PORT_PE) || + (temp & PORT_PLS_MASK) != XDEV_U0) { + xhci_dbg(xhci, "port L1 resume fail\n"); + ret = -EINVAL; + } + } + + if (ret) { + /* Insert dev to lpm_failed_devs list */ + xhci_warn(xhci, "device LPM test failed, may disconnect and " + "re-enumerate\n"); + dev_info = kzalloc(sizeof(struct dev_info), GFP_ATOMIC); + if (!dev_info) { + ret = -ENOMEM; + goto finish; + } + dev_info->dev_id = dev_id; + INIT_LIST_HEAD(&dev_info->list); + list_add(&dev_info->list, &xhci->lpm_failed_devs); + } else { + xhci_ring_device(xhci, udev->slot_id); + } + +finish: + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; +} + +int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, + struct usb_device *udev, int enable) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + __le32 __iomem **port_array; + __le32 __iomem *pm_addr; + u32 temp; + unsigned int port_num; + unsigned long flags; + int hird; + + if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support || + !udev->lpm_capable) + return -EPERM; + + if (!udev->parent || udev->parent->parent || + udev->descriptor.bDeviceClass == USB_CLASS_HUB) + return -EPERM; + + if (udev->usb2_hw_lpm_capable != 1) + return -EPERM; + + spin_lock_irqsave(&xhci->lock, flags); + + port_array = xhci->usb2_ports; + port_num = udev->portnum - 1; + pm_addr = port_array[port_num] + 1; + temp = xhci_readl(xhci, pm_addr); + + xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n", + enable ? "enable" : "disable", port_num); + + hird = xhci_calculate_hird_besl(xhci, udev); + + if (enable) { + temp &= ~PORT_HIRD_MASK; + temp |= PORT_HIRD(hird) | PORT_RWE; + xhci_writel(xhci, temp, pm_addr); + temp = xhci_readl(xhci, pm_addr); + temp |= PORT_HLE; + xhci_writel(xhci, temp, pm_addr); + } else { + temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK); + xhci_writel(xhci, temp, pm_addr); + } + + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; +} + +int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + + ret = xhci_usb2_software_lpm_test(hcd, udev); + if (!ret) { + xhci_dbg(xhci, "software LPM test succeed\n"); + if (xhci->hw_lpm_support == 1) { + udev->usb2_hw_lpm_capable = 1; + ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1); + if (!ret) + udev->usb2_hw_lpm_enabled = 1; + } + } + + return 0; +} + +#else + +int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, + struct usb_device *udev, int enable) +{ + return 0; +} + +int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + return 0; +} + +#endif /* CONFIG_PM_RUNTIME */ + +/*---------------------- USB 3.0 Link PM functions ------------------------*/ + +#ifdef CONFIG_PM +/* Service interval in nanoseconds = 2^(bInterval - 1) * 125us * 1000ns / 1us */ +static unsigned long long xhci_service_interval_to_ns( + struct usb_endpoint_descriptor *desc) +{ + return (1ULL << (desc->bInterval - 1)) * 125 * 1000; +} + +static u16 xhci_get_timeout_no_hub_lpm(struct usb_device *udev, + enum usb3_link_state state) +{ + unsigned long long sel; + unsigned long long pel; + unsigned int max_sel_pel; + char *state_name; + + switch (state) { + case USB3_LPM_U1: + /* Convert SEL and PEL stored in nanoseconds to microseconds */ + sel = DIV_ROUND_UP(udev->u1_params.sel, 1000); + pel = DIV_ROUND_UP(udev->u1_params.pel, 1000); + max_sel_pel = USB3_LPM_MAX_U1_SEL_PEL; + state_name = "U1"; + break; + case USB3_LPM_U2: + sel = DIV_ROUND_UP(udev->u2_params.sel, 1000); + pel = DIV_ROUND_UP(udev->u2_params.pel, 1000); + max_sel_pel = USB3_LPM_MAX_U2_SEL_PEL; + state_name = "U2"; + break; + default: + dev_warn(&udev->dev, "%s: Can't get timeout for non-U1 or U2 state.\n", + __func__); + return USB3_LPM_DISABLED; + } + + if (sel <= max_sel_pel && pel <= max_sel_pel) + return USB3_LPM_DEVICE_INITIATED; + + if (sel > max_sel_pel) + dev_dbg(&udev->dev, "Device-initiated %s disabled " + "due to long SEL %llu ms\n", + state_name, sel); + else + dev_dbg(&udev->dev, "Device-initiated %s disabled " + "due to long PEL %llu\n ms", + state_name, pel); + return USB3_LPM_DISABLED; +} + +/* Returns the hub-encoded U1 timeout value. + * The U1 timeout should be the maximum of the following values: + * - For control endpoints, U1 system exit latency (SEL) * 3 + * - For bulk endpoints, U1 SEL * 5 + * - For interrupt endpoints: + * - Notification EPs, U1 SEL * 3 + * - Periodic EPs, max(105% of bInterval, U1 SEL * 2) + * - For isochronous endpoints, max(105% of bInterval, U1 SEL * 2) + */ +static u16 xhci_calculate_intel_u1_timeout(struct usb_device *udev, + struct usb_endpoint_descriptor *desc) +{ + unsigned long long timeout_ns; + int ep_type; + int intr_type; + + ep_type = usb_endpoint_type(desc); + switch (ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + timeout_ns = udev->u1_params.sel * 3; + break; + case USB_ENDPOINT_XFER_BULK: + timeout_ns = udev->u1_params.sel * 5; + break; + case USB_ENDPOINT_XFER_INT: + intr_type = usb_endpoint_interrupt_type(desc); + if (intr_type == USB_ENDPOINT_INTR_NOTIFICATION) { + timeout_ns = udev->u1_params.sel * 3; + break; + } + /* Otherwise the calculation is the same as isoc eps */ + case USB_ENDPOINT_XFER_ISOC: + timeout_ns = xhci_service_interval_to_ns(desc); + timeout_ns = DIV_ROUND_UP_ULL(timeout_ns * 105, 100); + if (timeout_ns < udev->u1_params.sel * 2) + timeout_ns = udev->u1_params.sel * 2; + break; + default: + return 0; + } + + /* The U1 timeout is encoded in 1us intervals. */ + timeout_ns = DIV_ROUND_UP_ULL(timeout_ns, 1000); + /* Don't return a timeout of zero, because that's USB3_LPM_DISABLED. */ + if (timeout_ns == USB3_LPM_DISABLED) + timeout_ns++; + + /* If the necessary timeout value is bigger than what we can set in the + * USB 3.0 hub, we have to disable hub-initiated U1. + */ + if (timeout_ns <= USB3_LPM_U1_MAX_TIMEOUT) + return timeout_ns; + dev_dbg(&udev->dev, "Hub-initiated U1 disabled " + "due to long timeout %llu ms\n", timeout_ns); + return xhci_get_timeout_no_hub_lpm(udev, USB3_LPM_U1); +} + +/* Returns the hub-encoded U2 timeout value. + * The U2 timeout should be the maximum of: + * - 10 ms (to avoid the bandwidth impact on the scheduler) + * - largest bInterval of any active periodic endpoint (to avoid going + * into lower power link states between intervals). + * - the U2 Exit Latency of the device + */ +static u16 xhci_calculate_intel_u2_timeout(struct usb_device *udev, + struct usb_endpoint_descriptor *desc) +{ + unsigned long long timeout_ns; + unsigned long long u2_del_ns; + + timeout_ns = 10 * 1000 * 1000; + + if ((usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) && + (xhci_service_interval_to_ns(desc) > timeout_ns)) + timeout_ns = xhci_service_interval_to_ns(desc); + + u2_del_ns = le16_to_cpu(udev->bos->ss_cap->bU2DevExitLat) * 1000ULL; + if (u2_del_ns > timeout_ns) + timeout_ns = u2_del_ns; + + /* The U2 timeout is encoded in 256us intervals */ + timeout_ns = DIV_ROUND_UP_ULL(timeout_ns, 256 * 1000); + /* If the necessary timeout value is bigger than what we can set in the + * USB 3.0 hub, we have to disable hub-initiated U2. + */ + if (timeout_ns <= USB3_LPM_U2_MAX_TIMEOUT) + return timeout_ns; + dev_dbg(&udev->dev, "Hub-initiated U2 disabled " + "due to long timeout %llu ms\n", timeout_ns); + return xhci_get_timeout_no_hub_lpm(udev, USB3_LPM_U2); +} + +static u16 xhci_call_host_update_timeout_for_endpoint(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_endpoint_descriptor *desc, + enum usb3_link_state state, + u16 *timeout) +{ + if (state == USB3_LPM_U1) { + if (xhci->quirks & XHCI_INTEL_HOST) + return xhci_calculate_intel_u1_timeout(udev, desc); + } else { + if (xhci->quirks & XHCI_INTEL_HOST) + return xhci_calculate_intel_u2_timeout(udev, desc); + } + + return USB3_LPM_DISABLED; +} + +static int xhci_update_timeout_for_endpoint(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_endpoint_descriptor *desc, + enum usb3_link_state state, + u16 *timeout) +{ + u16 alt_timeout; + + alt_timeout = xhci_call_host_update_timeout_for_endpoint(xhci, udev, + desc, state, timeout); + + /* If we found we can't enable hub-initiated LPM, or + * the U1 or U2 exit latency was too high to allow + * device-initiated LPM as well, just stop searching. + */ + if (alt_timeout == USB3_LPM_DISABLED || + alt_timeout == USB3_LPM_DEVICE_INITIATED) { + *timeout = alt_timeout; + return -E2BIG; + } + if (alt_timeout > *timeout) + *timeout = alt_timeout; + return 0; +} + +static int xhci_update_timeout_for_interface(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_host_interface *alt, + enum usb3_link_state state, + u16 *timeout) +{ + int j; + + for (j = 0; j < alt->desc.bNumEndpoints; j++) { + if (xhci_update_timeout_for_endpoint(xhci, udev, + &alt->endpoint[j].desc, state, timeout)) + return -E2BIG; + continue; + } + return 0; +} + +static int xhci_check_intel_tier_policy(struct usb_device *udev, + enum usb3_link_state state) +{ + struct usb_device *parent; + unsigned int num_hubs; + + if (state == USB3_LPM_U2) + return 0; + + /* Don't enable U1 if the device is on a 2nd tier hub or lower. */ + for (parent = udev->parent, num_hubs = 0; parent->parent; + parent = parent->parent) + num_hubs++; + + if (num_hubs < 2) + return 0; + + dev_dbg(&udev->dev, "Disabling U1 link state for device" + " below second-tier hub.\n"); + dev_dbg(&udev->dev, "Plug device into first-tier hub " + "to decrease power consumption.\n"); + return -E2BIG; +} + +static int xhci_check_tier_policy(struct xhci_hcd *xhci, + struct usb_device *udev, + enum usb3_link_state state) +{ + if (xhci->quirks & XHCI_INTEL_HOST) + return xhci_check_intel_tier_policy(udev, state); + return -EINVAL; +} + +/* Returns the U1 or U2 timeout that should be enabled. + * If the tier check or timeout setting functions return with a non-zero exit + * code, that means the timeout value has been finalized and we shouldn't look + * at any more endpoints. + */ +static u16 xhci_calculate_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct usb_host_config *config; + char *state_name; + int i; + u16 timeout = USB3_LPM_DISABLED; + + if (state == USB3_LPM_U1) + state_name = "U1"; + else if (state == USB3_LPM_U2) + state_name = "U2"; + else { + dev_warn(&udev->dev, "Can't enable unknown link state %i\n", + state); + return timeout; + } + + if (xhci_check_tier_policy(xhci, udev, state) < 0) + return timeout; + + /* Gather some information about the currently installed configuration + * and alternate interface settings. + */ + if (xhci_update_timeout_for_endpoint(xhci, udev, &udev->ep0.desc, + state, &timeout)) + return timeout; + + config = udev->actconfig; + if (!config) + return timeout; + + for (i = 0; i < USB_MAXINTERFACES; i++) { + struct usb_driver *driver; + struct usb_interface *intf = config->interface[i]; + + if (!intf) + continue; + + /* Check if any currently bound drivers want hub-initiated LPM + * disabled. + */ + if (intf->dev.driver) { + driver = to_usb_driver(intf->dev.driver); + if (driver && driver->disable_hub_initiated_lpm) { + dev_dbg(&udev->dev, "Hub-initiated %s disabled " + "at request of driver %s\n", + state_name, driver->name); + return xhci_get_timeout_no_hub_lpm(udev, state); + } + } + + /* Not sure how this could happen... */ + if (!intf->cur_altsetting) + continue; + + if (xhci_update_timeout_for_interface(xhci, udev, + intf->cur_altsetting, + state, &timeout)) + return timeout; + } + return timeout; +} + +/* + * Issue an Evaluate Context command to change the Maximum Exit Latency in the + * slot context. If that succeeds, store the new MEL in the xhci_virt_device. + */ +static int xhci_change_max_exit_latency(struct xhci_hcd *xhci, + struct usb_device *udev, u16 max_exit_latency) +{ + struct xhci_virt_device *virt_dev; + struct xhci_command *command; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_slot_ctx *slot_ctx; + unsigned long flags; + int ret; + + spin_lock_irqsave(&xhci->lock, flags); + + virt_dev = xhci->devs[udev->slot_id]; + + /* + * virt_dev might not exists yet if xHC resumed from hibernate (S4) and + * xHC was re-initialized. Exit latency will be set later after + * hub_port_finish_reset() is done and xhci->devs[] are re-allocated + */ + + if (!virt_dev || max_exit_latency == virt_dev->current_mel) { + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; + } + + /* Attempt to issue an Evaluate Context command to change the MEL. */ + command = xhci->lpm_command; + xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + + ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx); + slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT)); + slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency); + + xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n"); + xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, command->in_ctx, 0); + + /* Issue and wait for the evaluate context command. */ + ret = xhci_configure_endpoint(xhci, udev, command, + true, true); + xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0); + + if (!ret) { + spin_lock_irqsave(&xhci->lock, flags); + virt_dev->current_mel = max_exit_latency; + spin_unlock_irqrestore(&xhci->lock, flags); + } + return ret; +} + +static int calculate_max_exit_latency(struct usb_device *udev, + enum usb3_link_state state_changed, + u16 hub_encoded_timeout) +{ + unsigned long long u1_mel_us = 0; + unsigned long long u2_mel_us = 0; + unsigned long long mel_us = 0; + bool disabling_u1; + bool disabling_u2; + bool enabling_u1; + bool enabling_u2; + + disabling_u1 = (state_changed == USB3_LPM_U1 && + hub_encoded_timeout == USB3_LPM_DISABLED); + disabling_u2 = (state_changed == USB3_LPM_U2 && + hub_encoded_timeout == USB3_LPM_DISABLED); + + enabling_u1 = (state_changed == USB3_LPM_U1 && + hub_encoded_timeout != USB3_LPM_DISABLED); + enabling_u2 = (state_changed == USB3_LPM_U2 && + hub_encoded_timeout != USB3_LPM_DISABLED); + + /* If U1 was already enabled and we're not disabling it, + * or we're going to enable U1, account for the U1 max exit latency. + */ + if ((udev->u1_params.timeout != USB3_LPM_DISABLED && !disabling_u1) || + enabling_u1) + u1_mel_us = DIV_ROUND_UP(udev->u1_params.mel, 1000); + if ((udev->u2_params.timeout != USB3_LPM_DISABLED && !disabling_u2) || + enabling_u2) + u2_mel_us = DIV_ROUND_UP(udev->u2_params.mel, 1000); + + if (u1_mel_us > u2_mel_us) + mel_us = u1_mel_us; + else + mel_us = u2_mel_us; + /* xHCI host controller max exit latency field is only 16 bits wide. */ + if (mel_us > MAX_EXIT) { + dev_warn(&udev->dev, "Link PM max exit latency of %lluus " + "is too big.\n", mel_us); + return -E2BIG; + } + return mel_us; +} + +/* Returns the USB3 hub-encoded value for the U1/U2 timeout. */ +int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state) +{ + struct xhci_hcd *xhci; + u16 hub_encoded_timeout; + int mel; + int ret; + + xhci = hcd_to_xhci(hcd); + /* The LPM timeout values are pretty host-controller specific, so don't + * enable hub-initiated timeouts unless the vendor has provided + * information about their timeout algorithm. + */ + if (!xhci || !(xhci->quirks & XHCI_LPM_SUPPORT) || + !xhci->devs[udev->slot_id]) + return USB3_LPM_DISABLED; + + hub_encoded_timeout = xhci_calculate_lpm_timeout(hcd, udev, state); + mel = calculate_max_exit_latency(udev, state, hub_encoded_timeout); + if (mel < 0) { + /* Max Exit Latency is too big, disable LPM. */ + hub_encoded_timeout = USB3_LPM_DISABLED; + mel = 0; + } + + ret = xhci_change_max_exit_latency(xhci, udev, mel); + if (ret) + return ret; + return hub_encoded_timeout; +} + +int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state) +{ + struct xhci_hcd *xhci; + u16 mel; + int ret; + + xhci = hcd_to_xhci(hcd); + if (!xhci || !(xhci->quirks & XHCI_LPM_SUPPORT) || + !xhci->devs[udev->slot_id]) + return 0; + + mel = calculate_max_exit_latency(udev, state, USB3_LPM_DISABLED); + ret = xhci_change_max_exit_latency(xhci, udev, mel); + if (ret) + return ret; + return 0; +} +#else /* CONFIG_PM */ + +int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state) +{ + return USB3_LPM_DISABLED; +} + +int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state) +{ + return 0; +} +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ + +/* Once a hub descriptor is fetched for a device, we need to update the xHC's + * internal data structures for the device. + */ +int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, + struct usb_tt *tt, gfp_t mem_flags) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_virt_device *vdev; + struct xhci_command *config_cmd; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_slot_ctx *slot_ctx; + unsigned long flags; + unsigned think_time; + int ret; + + /* Ignore root hubs */ + if (!hdev->parent) + return 0; + + vdev = xhci->devs[hdev->slot_id]; + if (!vdev) { + xhci_warn(xhci, "Cannot update hub desc for unknown device.\n"); + return -EINVAL; + } + config_cmd = xhci_alloc_command(xhci, true, true, mem_flags); + if (!config_cmd) { + xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); + return -ENOMEM; + } + + spin_lock_irqsave(&xhci->lock, flags); + if (hdev->speed == USB_SPEED_HIGH && + xhci_alloc_tt_info(xhci, vdev, hdev, tt, GFP_ATOMIC)) { + xhci_dbg(xhci, "Could not allocate xHCI TT structure.\n"); + xhci_free_command(xhci, config_cmd); + spin_unlock_irqrestore(&xhci->lock, flags); + return -ENOMEM; + } + + xhci_slot_copy(xhci, config_cmd->in_ctx, vdev->out_ctx); + ctrl_ctx = xhci_get_input_control_ctx(xhci, config_cmd->in_ctx); + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + slot_ctx = xhci_get_slot_ctx(xhci, config_cmd->in_ctx); + slot_ctx->dev_info |= cpu_to_le32(DEV_HUB); + if (tt->multi) + slot_ctx->dev_info |= cpu_to_le32(DEV_MTT); + if (xhci->hci_version > 0x95) { + xhci_dbg(xhci, "xHCI version %x needs hub " + "TT think time and number of ports\n", + (unsigned int) xhci->hci_version); + slot_ctx->dev_info2 |= cpu_to_le32(XHCI_MAX_PORTS(hdev->maxchild)); + /* Set TT think time - convert from ns to FS bit times. + * 0 = 8 FS bit times, 1 = 16 FS bit times, + * 2 = 24 FS bit times, 3 = 32 FS bit times. + * + * xHCI 1.0: this field shall be 0 if the device is not a + * High-spped hub. + */ + think_time = tt->think_time; + if (think_time != 0) + think_time = (think_time / 666) - 1; + if (xhci->hci_version < 0x100 || hdev->speed == USB_SPEED_HIGH) + slot_ctx->tt_info |= + cpu_to_le32(TT_THINK_TIME(think_time)); + } else { + xhci_dbg(xhci, "xHCI version %x doesn't need hub " + "TT think time or number of ports\n", + (unsigned int) xhci->hci_version); + } + slot_ctx->dev_state = 0; + spin_unlock_irqrestore(&xhci->lock, flags); + + xhci_dbg(xhci, "Set up %s for hub device.\n", + (xhci->hci_version > 0x95) ? + "configure endpoint" : "evaluate context"); + xhci_dbg(xhci, "Slot %u Input Context:\n", hdev->slot_id); + xhci_dbg_ctx(xhci, config_cmd->in_ctx, 0); + + /* Issue and wait for the configure endpoint or + * evaluate context command. + */ + if (xhci->hci_version > 0x95) + ret = xhci_configure_endpoint(xhci, hdev, config_cmd, + false, false); + else + ret = xhci_configure_endpoint(xhci, hdev, config_cmd, + true, false); + + xhci_dbg(xhci, "Slot %u Output Context:\n", hdev->slot_id); + xhci_dbg_ctx(xhci, vdev->out_ctx, 0); + + xhci_free_command(xhci, config_cmd); + return ret; +} + +int xhci_get_frame(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + /* EHCI mods by the periodic size. Why? */ + return xhci_readl(xhci, &xhci->run_regs->microframe_index) >> 3; +} + +int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) +{ + struct xhci_hcd *xhci; + struct device *dev = hcd->self.controller; + int retval; + u32 temp; + + /* Accept arbitrarily long scatter-gather lists */ + hcd->self.sg_tablesize = ~0; + /* XHCI controllers don't stop the ep queue on short packets :| */ + hcd->self.no_stop_on_short = 1; + + if (usb_hcd_is_primary_hcd(hcd)) { + xhci = kzalloc(sizeof(struct xhci_hcd), GFP_KERNEL); + if (!xhci) + return -ENOMEM; + *((struct xhci_hcd **) hcd->hcd_priv) = xhci; + xhci->main_hcd = hcd; + /* Mark the first roothub as being USB 2.0. + * The xHCI driver will register the USB 3.0 roothub. + */ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + /* + * USB 2.0 roothub under xHCI has an integrated TT, + * (rate matching hub) as opposed to having an OHCI/UHCI + * companion controller. + */ + hcd->has_tt = 1; + } else { + /* xHCI private pointer was set in xhci_pci_probe for the second + * registered roothub. + */ + xhci = hcd_to_xhci(hcd); + temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + if (HCC_64BIT_ADDR(temp)) { + xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n"); + dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64)); + } else { + dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32)); + } + return 0; + } + + xhci->cap_regs = hcd->regs; + xhci->op_regs = hcd->regs + + HC_LENGTH(xhci_readl(xhci, &xhci->cap_regs->hc_capbase)); + xhci->run_regs = hcd->regs + + (xhci_readl(xhci, &xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + xhci->hcs_params1 = xhci_readl(xhci, &xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = xhci_readl(xhci, &xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = xhci_readl(xhci, &xhci->cap_regs->hcs_params3); + xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); + xhci->hci_version = HC_VERSION(xhci->hcc_params); + xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + xhci_print_registers(xhci); + + get_quirks(dev, xhci); + + /* In xhci controllers which follow xhci 1.0 spec gives a spurious + * success event after a short transfer. This quirk will ignore such + * spurious event. + */ + if (xhci->hci_version > 0x96) + xhci->quirks |= XHCI_SPURIOUS_SUCCESS; + + /* Make sure the HC is halted. */ + retval = xhci_halt(xhci); + if (retval) + goto error; + + xhci_dbg(xhci, "Resetting HCD\n"); + /* Reset the internal HC memory state and registers. */ + retval = xhci_reset(xhci); + if (retval) + goto error; + xhci_dbg(xhci, "Reset complete\n"); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + if (HCC_64BIT_ADDR(temp)) { + xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n"); + dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64)); + } else { + dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32)); + } + + xhci_dbg(xhci, "Calling HCD init\n"); + /* Initialize HCD and host controller data structures. */ + retval = xhci_init(hcd); + if (retval) + goto error; + xhci_dbg(xhci, "Called HCD init\n"); + return 0; +error: + kfree(xhci); + return retval; +} + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); + +static int __init xhci_hcd_init(void) +{ + int retval; + + retval = xhci_register_pci(); + if (retval < 0) { + printk(KERN_DEBUG "Problem registering PCI driver."); + return retval; + } + retval = xhci_register_plat(); + if (retval < 0) { + printk(KERN_DEBUG "Problem registering platform driver."); + goto unreg_pci; + } + /* + * Check the compiler generated sizes of structures that must be laid + * out in specific ways for hardware access. + */ + BUILD_BUG_ON(sizeof(struct xhci_doorbell_array) != 256*32/8); + BUILD_BUG_ON(sizeof(struct xhci_slot_ctx) != 8*32/8); + BUILD_BUG_ON(sizeof(struct xhci_ep_ctx) != 8*32/8); + /* xhci_device_control has eight fields, and also + * embeds one xhci_slot_ctx and 31 xhci_ep_ctx + */ + BUILD_BUG_ON(sizeof(struct xhci_stream_ctx) != 4*32/8); + BUILD_BUG_ON(sizeof(union xhci_trb) != 4*32/8); + BUILD_BUG_ON(sizeof(struct xhci_erst_entry) != 4*32/8); + BUILD_BUG_ON(sizeof(struct xhci_cap_regs) != 7*32/8); + BUILD_BUG_ON(sizeof(struct xhci_intr_reg) != 8*32/8); + /* xhci_run_regs has eight fields and embeds 128 xhci_intr_regs */ + BUILD_BUG_ON(sizeof(struct xhci_run_regs) != (8+8*128)*32/8); + return 0; +unreg_pci: + xhci_unregister_pci(); + return retval; +} +module_init(xhci_hcd_init); + +static void __exit xhci_hcd_cleanup(void) +{ + xhci_unregister_pci(); + xhci_unregister_plat(); +} +module_exit(xhci_hcd_cleanup); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h new file mode 100644 index 00000000..627fcd93 --- /dev/null +++ b/drivers/usb/host/xhci.h @@ -0,0 +1,1861 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_XHCI_HCD_H +#define __LINUX_XHCI_HCD_H + +#include +#include +#include +#include + +/* Code sharing between pci-quirks and xhci hcd */ +#include "xhci-ext-caps.h" +#include "pci-quirks.h" + +/* xHCI PCI Configuration Registers */ +#define XHCI_SBRN_OFFSET (0x60) + +/* Max number of USB devices for any host controller - limit in section 6.1 */ +#define MAX_HC_SLOTS 256 +/* Section 5.3.3 - MaxPorts */ +#define MAX_HC_PORTS 127 + +/* + * xHCI register interface. + * This corresponds to the eXtensible Host Controller Interface (xHCI) + * Revision 0.95 specification + */ + +/** + * struct xhci_cap_regs - xHCI Host Controller Capability Registers. + * @hc_capbase: length of the capabilities register and HC version number + * @hcs_params1: HCSPARAMS1 - Structural Parameters 1 + * @hcs_params2: HCSPARAMS2 - Structural Parameters 2 + * @hcs_params3: HCSPARAMS3 - Structural Parameters 3 + * @hcc_params: HCCPARAMS - Capability Parameters + * @db_off: DBOFF - Doorbell array offset + * @run_regs_off: RTSOFF - Runtime register space offset + */ +struct xhci_cap_regs { + __le32 hc_capbase; + __le32 hcs_params1; + __le32 hcs_params2; + __le32 hcs_params3; + __le32 hcc_params; + __le32 db_off; + __le32 run_regs_off; + /* Reserved up to (CAPLENGTH - 0x1C) */ +}; + +/* hc_capbase bitmasks */ +/* bits 7:0 - how long is the Capabilities register */ +#define HC_LENGTH(p) XHCI_HC_LENGTH(p) +/* bits 31:16 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) + +/* HCSPARAMS1 - hcs_params1 - bitmasks */ +/* bits 0:7, Max Device Slots */ +#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +#define HCS_SLOTS_MASK 0xff +/* bits 8:18, Max Interrupters */ +#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +/* bits 24:31, Max Ports - max value is 0x7F = 127 ports */ +#define HCS_MAX_PORTS(p) (((p) >> 24) & 0x7f) + +/* HCSPARAMS2 - hcs_params2 - bitmasks */ +/* bits 0:3, frames or uframes that SW needs to queue transactions + * ahead of the HW to meet periodic deadlines */ +#define HCS_IST(p) (((p) >> 0) & 0xf) +/* bits 4:7, max number of Event Ring segments */ +#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) +/* bit 26 Scratchpad restore - for save/restore HW state - not used yet */ +/* bits 27:31 number of Scratchpad buffers SW must allocate for the HW */ +#define HCS_MAX_SCRATCHPAD(p) (((p) >> 27) & 0x1f) + +/* HCSPARAMS3 - hcs_params3 - bitmasks */ +/* bits 0:7, Max U1 to U0 latency for the roothub ports */ +#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) +/* bits 16:31, Max U2 to U0 latency for the roothub ports */ +#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) + +/* HCCPARAMS - hcc_params - bitmasks */ +/* true: HC can use 64-bit address pointers */ +#define HCC_64BIT_ADDR(p) ((p) & (1 << 0)) +/* true: HC can do bandwidth negotiation */ +#define HCC_BANDWIDTH_NEG(p) ((p) & (1 << 1)) +/* true: HC uses 64-byte Device Context structures + * FIXME 64-byte context structures aren't supported yet. + */ +#define HCC_64BYTE_CONTEXT(p) ((p) & (1 << 2)) +/* true: HC has port power switches */ +#define HCC_PPC(p) ((p) & (1 << 3)) +/* true: HC has port indicators */ +#define HCS_INDICATOR(p) ((p) & (1 << 4)) +/* true: HC has Light HC Reset Capability */ +#define HCC_LIGHT_RESET(p) ((p) & (1 << 5)) +/* true: HC supports latency tolerance messaging */ +#define HCC_LTC(p) ((p) & (1 << 6)) +/* true: no secondary Stream ID Support */ +#define HCC_NSS(p) ((p) & (1 << 7)) +/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15 */ +#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) +/* Extended Capabilities pointer from PCI base - section 5.3.6 */ +#define HCC_EXT_CAPS(p) XHCI_HCC_EXT_CAPS(p) + +/* db_off bitmask - bits 0:1 reserved */ +#define DBOFF_MASK (~0x3) + +/* run_regs_off bitmask - bits 0:4 reserved */ +#define RTSOFF_MASK (~0x1f) + + +/* Number of registers per port */ +#define NUM_PORT_REGS 4 + +/** + * struct xhci_op_regs - xHCI Host Controller Operational Registers. + * @command: USBCMD - xHC command register + * @status: USBSTS - xHC status register + * @page_size: This indicates the page size that the host controller + * supports. If bit n is set, the HC supports a page size + * of 2^(n+12), up to a 128MB page size. + * 4K is the minimum page size. + * @cmd_ring: CRP - 64-bit Command Ring Pointer + * @dcbaa_ptr: DCBAAP - 64-bit Device Context Base Address Array Pointer + * @config_reg: CONFIG - Configure Register + * @port_status_base: PORTSCn - base address for Port Status and Control + * Each port has a Port Status and Control register, + * followed by a Port Power Management Status and Control + * register, a Port Link Info register, and a reserved + * register. + * @port_power_base: PORTPMSCn - base address for + * Port Power Management Status and Control + * @port_link_base: PORTLIn - base address for Port Link Info (current + * Link PM state and control) for USB 2.1 and USB 3.0 + * devices. + */ +struct xhci_op_regs { + __le32 command; + __le32 status; + __le32 page_size; + __le32 reserved1; + __le32 reserved2; + __le32 dev_notification; + __le64 cmd_ring; + /* rsvd: offset 0x20-2F */ + __le32 reserved3[4]; + __le64 dcbaa_ptr; + __le32 config_reg; + /* rsvd: offset 0x3C-3FF */ + __le32 reserved4[241]; + /* port 1 registers, which serve as a base address for other ports */ + __le32 port_status_base; + __le32 port_power_base; + __le32 port_link_base; + __le32 reserved5; + /* registers for ports 2-255 */ + __le32 reserved6[NUM_PORT_REGS*254]; +}; + +/* USBCMD - USB command - command bitmasks */ +/* start/stop HC execution - do not write unless HC is halted*/ +#define CMD_RUN XHCI_CMD_RUN +/* Reset HC - resets internal HC state machine and all registers (except + * PCI config regs). HC does NOT drive a USB reset on the downstream ports. + * The xHCI driver must reinitialize the xHC after setting this bit. + */ +#define CMD_RESET (1 << 1) +/* Event Interrupt Enable - a '1' allows interrupts from the host controller */ +#define CMD_EIE XHCI_CMD_EIE +/* Host System Error Interrupt Enable - get out-of-band signal for HC errors */ +#define CMD_HSEIE XHCI_CMD_HSEIE +/* bits 4:6 are reserved (and should be preserved on writes). */ +/* light reset (port status stays unchanged) - reset completed when this is 0 */ +#define CMD_LRESET (1 << 7) +/* host controller save/restore state. */ +#define CMD_CSS (1 << 8) +#define CMD_CRS (1 << 9) +/* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ +#define CMD_EWE XHCI_CMD_EWE +/* MFINDEX power management - '1' means xHC can stop MFINDEX counter if all root + * hubs are in U3 (selective suspend), disconnect, disabled, or powered-off. + * '0' means the xHC can power it off if all ports are in the disconnect, + * disabled, or powered-off state. + */ +#define CMD_PM_INDEX (1 << 11) +/* bits 12:31 are reserved (and should be preserved on writes). */ + +/* IMAN - Interrupt Management Register */ +#define IMAN_IE (1 << 1) +#define IMAN_IP (1 << 0) + +/* USBSTS - USB status - status bitmasks */ +/* HC not running - set to 1 when run/stop bit is cleared. */ +#define STS_HALT XHCI_STS_HALT +/* serious error, e.g. PCI parity error. The HC will clear the run/stop bit. */ +#define STS_FATAL (1 << 2) +/* event interrupt - clear this prior to clearing any IP flags in IR set*/ +#define STS_EINT (1 << 3) +/* port change detect */ +#define STS_PORT (1 << 4) +/* bits 5:7 reserved and zeroed */ +/* save state status - '1' means xHC is saving state */ +#define STS_SAVE (1 << 8) +/* restore state status - '1' means xHC is restoring state */ +#define STS_RESTORE (1 << 9) +/* true: save or restore error */ +#define STS_SRE (1 << 10) +/* true: Controller Not Ready to accept doorbell or op reg writes after reset */ +#define STS_CNR XHCI_STS_CNR +/* true: internal Host Controller Error - SW needs to reset and reinitialize */ +#define STS_HCE (1 << 12) +/* bits 13:31 reserved and should be preserved */ + +/* + * DNCTRL - Device Notification Control Register - dev_notification bitmasks + * Generate a device notification event when the HC sees a transaction with a + * notification type that matches a bit set in this bit field. + */ +#define DEV_NOTE_MASK (0xffff) +#define ENABLE_DEV_NOTE(x) (1 << (x)) +/* Most of the device notification types should only be used for debug. + * SW does need to pay attention to function wake notifications. + */ +#define DEV_NOTE_FWAKE ENABLE_DEV_NOTE(1) + +/* CRCR - Command Ring Control Register - cmd_ring bitmasks */ +/* bit 0 is the command ring cycle state */ +/* stop ring operation after completion of the currently executing command */ +#define CMD_RING_PAUSE (1 << 1) +/* stop ring immediately - abort the currently executing command */ +#define CMD_RING_ABORT (1 << 2) +/* true: command ring is running */ +#define CMD_RING_RUNNING (1 << 3) +/* bits 4:5 reserved and should be preserved */ +/* Command Ring pointer - bit mask for the lower 32 bits. */ +#define CMD_RING_RSVD_BITS (0x3f) + +/* CONFIG - Configure Register - config_reg bitmasks */ +/* bits 0:7 - maximum number of device slots enabled (NumSlotsEn) */ +#define MAX_DEVS(p) ((p) & 0xff) +/* bits 8:31 - reserved and should be preserved */ + +/* PORTSC - Port Status and Control Register - port_status_base bitmasks */ +/* true: device connected */ +#define PORT_CONNECT (1 << 0) +/* true: port enabled */ +#define PORT_PE (1 << 1) +/* bit 2 reserved and zeroed */ +/* true: port has an over-current condition */ +#define PORT_OC (1 << 3) +/* true: port reset signaling asserted */ +#define PORT_RESET (1 << 4) +/* Port Link State - bits 5:8 + * A read gives the current link PM state of the port, + * a write with Link State Write Strobe set sets the link state. + */ +#define PORT_PLS_MASK (0xf << 5) +#define XDEV_U0 (0x0 << 5) +#define XDEV_U2 (0x2 << 5) +#define XDEV_U3 (0x3 << 5) +#define XDEV_RESUME (0xf << 5) +/* true: port has power (see HCC_PPC) */ +#define PORT_POWER (1 << 9) +/* bits 10:13 indicate device speed: + * 0 - undefined speed - port hasn't be initialized by a reset yet + * 1 - full speed + * 2 - low speed + * 3 - high speed + * 4 - super speed + * 5-15 reserved + */ +#define DEV_SPEED_MASK (0xf << 10) +#define XDEV_FS (0x1 << 10) +#define XDEV_LS (0x2 << 10) +#define XDEV_HS (0x3 << 10) +#define XDEV_SS (0x4 << 10) +#define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0<<10)) +#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) +#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_LS) +#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) +#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) +/* Bits 20:23 in the Slot Context are the speed for the device */ +#define SLOT_SPEED_FS (XDEV_FS << 10) +#define SLOT_SPEED_LS (XDEV_LS << 10) +#define SLOT_SPEED_HS (XDEV_HS << 10) +#define SLOT_SPEED_SS (XDEV_SS << 10) +/* Port Indicator Control */ +#define PORT_LED_OFF (0 << 14) +#define PORT_LED_AMBER (1 << 14) +#define PORT_LED_GREEN (2 << 14) +#define PORT_LED_MASK (3 << 14) +/* Port Link State Write Strobe - set this when changing link state */ +#define PORT_LINK_STROBE (1 << 16) +/* true: connect status change */ +#define PORT_CSC (1 << 17) +/* true: port enable change */ +#define PORT_PEC (1 << 18) +/* true: warm reset for a USB 3.0 device is done. A "hot" reset puts the port + * into an enabled state, and the device into the default state. A "warm" reset + * also resets the link, forcing the device through the link training sequence. + * SW can also look at the Port Reset register to see when warm reset is done. + */ +#define PORT_WRC (1 << 19) +/* true: over-current change */ +#define PORT_OCC (1 << 20) +/* true: reset change - 1 to 0 transition of PORT_RESET */ +#define PORT_RC (1 << 21) +/* port link status change - set on some port link state transitions: + * Transition Reason + * ------------------------------------------------------------------------------ + * - U3 to Resume Wakeup signaling from a device + * - Resume to Recovery to U0 USB 3.0 device resume + * - Resume to U0 USB 2.0 device resume + * - U3 to Recovery to U0 Software resume of USB 3.0 device complete + * - U3 to U0 Software resume of USB 2.0 device complete + * - U2 to U0 L1 resume of USB 2.1 device complete + * - U0 to U0 (???) L1 entry rejection by USB 2.1 device + * - U0 to disabled L1 entry error with USB 2.1 device + * - Any state to inactive Error on USB 3.0 port + */ +#define PORT_PLC (1 << 22) +/* port configure error change - port failed to configure its link partner */ +#define PORT_CEC (1 << 23) +/* Cold Attach Status - xHC can set this bit to report device attached during + * Sx state. Warm port reset should be perfomed to clear this bit and move port + * to connected state. + */ +#define PORT_CAS (1 << 24) +/* wake on connect (enable) */ +#define PORT_WKCONN_E (1 << 25) +/* wake on disconnect (enable) */ +#define PORT_WKDISC_E (1 << 26) +/* wake on over-current (enable) */ +#define PORT_WKOC_E (1 << 27) +/* bits 28:29 reserved */ +/* true: device is removable - for USB 3.0 roothub emulation */ +#define PORT_DEV_REMOVE (1 << 30) +/* Initiate a warm port reset - complete when PORT_WRC is '1' */ +#define PORT_WR (1 << 31) + +/* We mark duplicate entries with -1 */ +#define DUPLICATE_ENTRY ((u8)(-1)) + +/* Port Power Management Status and Control - port_power_base bitmasks */ +/* Inactivity timer value for transitions into U1, in microseconds. + * Timeout can be up to 127us. 0xFF means an infinite timeout. + */ +#define PORT_U1_TIMEOUT(p) ((p) & 0xff) +#define PORT_U1_TIMEOUT_MASK 0xff +/* Inactivity timer value for transitions into U2 */ +#define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) +#define PORT_U2_TIMEOUT_MASK (0xff << 8) +/* Bits 24:31 for port testing */ + +/* USB2 Protocol PORTSPMSC */ +#define PORT_L1S_MASK 7 +#define PORT_L1S_SUCCESS 1 +#define PORT_RWE (1 << 3) +#define PORT_HIRD(p) (((p) & 0xf) << 4) +#define PORT_HIRD_MASK (0xf << 4) +#define PORT_L1DS(p) (((p) & 0xff) << 8) +#define PORT_HLE (1 << 16) + +/** + * struct xhci_intr_reg - Interrupt Register Set + * @irq_pending: IMAN - Interrupt Management Register. Used to enable + * interrupts and check for pending interrupts. + * @irq_control: IMOD - Interrupt Moderation Register. + * Used to throttle interrupts. + * @erst_size: Number of segments in the Event Ring Segment Table (ERST). + * @erst_base: ERST base address. + * @erst_dequeue: Event ring dequeue pointer. + * + * Each interrupter (defined by a MSI-X vector) has an event ring and an Event + * Ring Segment Table (ERST) associated with it. The event ring is comprised of + * multiple segments of the same size. The HC places events on the ring and + * "updates the Cycle bit in the TRBs to indicate to software the current + * position of the Enqueue Pointer." The HCD (Linux) processes those events and + * updates the dequeue pointer. + */ +struct xhci_intr_reg { + __le32 irq_pending; + __le32 irq_control; + __le32 erst_size; + __le32 rsvd; + __le64 erst_base; + __le64 erst_dequeue; +}; + +/* irq_pending bitmasks */ +#define ER_IRQ_PENDING(p) ((p) & 0x1) +/* bits 2:31 need to be preserved */ +/* THIS IS BUGGY - FIXME - IP IS WRITE 1 TO CLEAR */ +#define ER_IRQ_CLEAR(p) ((p) & 0xfffffffe) +#define ER_IRQ_ENABLE(p) ((ER_IRQ_CLEAR(p)) | 0x2) +#define ER_IRQ_DISABLE(p) ((ER_IRQ_CLEAR(p)) & ~(0x2)) + +/* irq_control bitmasks */ +/* Minimum interval between interrupts (in 250ns intervals). The interval + * between interrupts will be longer if there are no events on the event ring. + * Default is 4000 (1 ms). + */ +#define ER_IRQ_INTERVAL_MASK (0xffff) +/* Counter used to count down the time to the next interrupt - HW use only */ +#define ER_IRQ_COUNTER_MASK (0xffff << 16) + +/* erst_size bitmasks */ +/* Preserve bits 16:31 of erst_size */ +#define ERST_SIZE_MASK (0xffff << 16) + +/* erst_dequeue bitmasks */ +/* Dequeue ERST Segment Index (DESI) - Segment number (or alias) + * where the current dequeue pointer lies. This is an optional HW hint. + */ +#define ERST_DESI_MASK (0x7) +/* Event Handler Busy (EHB) - is the event ring scheduled to be serviced by + * a work queue (or delayed service routine)? + */ +#define ERST_EHB (1 << 3) +#define ERST_PTR_MASK (0xf) + +/** + * struct xhci_run_regs + * @microframe_index: + * MFINDEX - current microframe number + * + * Section 5.5 Host Controller Runtime Registers: + * "Software should read and write these registers using only Dword (32 bit) + * or larger accesses" + */ +struct xhci_run_regs { + __le32 microframe_index; + __le32 rsvd[7]; + struct xhci_intr_reg ir_set[128]; +}; + +/** + * struct doorbell_array + * + * Bits 0 - 7: Endpoint target + * Bits 8 - 15: RsvdZ + * Bits 16 - 31: Stream ID + * + * Section 5.6 + */ +struct xhci_doorbell_array { + __le32 doorbell[256]; +}; + +#define DB_VALUE(ep, stream) ((((ep) + 1) & 0xff) | ((stream) << 16)) +#define DB_VALUE_HOST 0x00000000 + +/** + * struct xhci_protocol_caps + * @revision: major revision, minor revision, capability ID, + * and next capability pointer. + * @name_string: Four ASCII characters to say which spec this xHC + * follows, typically "USB ". + * @port_info: Port offset, count, and protocol-defined information. + */ +struct xhci_protocol_caps { + u32 revision; + u32 name_string; + u32 port_info; +}; + +#define XHCI_EXT_PORT_MAJOR(x) (((x) >> 24) & 0xff) +#define XHCI_EXT_PORT_OFF(x) ((x) & 0xff) +#define XHCI_EXT_PORT_COUNT(x) (((x) >> 8) & 0xff) + +/** + * struct xhci_container_ctx + * @type: Type of context. Used to calculated offsets to contained contexts. + * @size: Size of the context data + * @bytes: The raw context data given to HW + * @dma: dma address of the bytes + * + * Represents either a Device or Input context. Holds a pointer to the raw + * memory used for the context (bytes) and dma address of it (dma). + */ +struct xhci_container_ctx { + unsigned type; +#define XHCI_CTX_TYPE_DEVICE 0x1 +#define XHCI_CTX_TYPE_INPUT 0x2 + + int size; + + u8 *bytes; + dma_addr_t dma; +}; + +/** + * struct xhci_slot_ctx + * @dev_info: Route string, device speed, hub info, and last valid endpoint + * @dev_info2: Max exit latency for device number, root hub port number + * @tt_info: tt_info is used to construct split transaction tokens + * @dev_state: slot state and device address + * + * Slot Context - section 6.2.1.1. This assumes the HC uses 32-byte context + * structures. If the HC uses 64-byte contexts, there is an additional 32 bytes + * reserved at the end of the slot context for HC internal use. + */ +struct xhci_slot_ctx { + __le32 dev_info; + __le32 dev_info2; + __le32 tt_info; + __le32 dev_state; + /* offset 0x10 to 0x1f reserved for HC internal use */ + __le32 reserved[4]; +}; + +/* dev_info bitmasks */ +/* Route String - 0:19 */ +#define ROUTE_STRING_MASK (0xfffff) +/* Device speed - values defined by PORTSC Device Speed field - 20:23 */ +#define DEV_SPEED (0xf << 20) +/* bit 24 reserved */ +/* Is this LS/FS device connected through a HS hub? - bit 25 */ +#define DEV_MTT (0x1 << 25) +/* Set if the device is a hub - bit 26 */ +#define DEV_HUB (0x1 << 26) +/* Index of the last valid endpoint context in this device context - 27:31 */ +#define LAST_CTX_MASK (0x1f << 27) +#define LAST_CTX(p) ((p) << 27) +#define LAST_CTX_TO_EP_NUM(p) (((p) >> 27) - 1) +#define SLOT_FLAG (1 << 0) +#define EP0_FLAG (1 << 1) + +/* dev_info2 bitmasks */ +/* Max Exit Latency (ms) - worst case time to wake up all links in dev path */ +#define MAX_EXIT (0xffff) +/* Root hub port number that is needed to access the USB device */ +#define ROOT_HUB_PORT(p) (((p) & 0xff) << 16) +#define DEVINFO_TO_ROOT_HUB_PORT(p) (((p) >> 16) & 0xff) +/* Maximum number of ports under a hub device */ +#define XHCI_MAX_PORTS(p) (((p) & 0xff) << 24) + +/* tt_info bitmasks */ +/* + * TT Hub Slot ID - for low or full speed devices attached to a high-speed hub + * The Slot ID of the hub that isolates the high speed signaling from + * this low or full-speed device. '0' if attached to root hub port. + */ +#define TT_SLOT (0xff) +/* + * The number of the downstream facing port of the high-speed hub + * '0' if the device is not low or full speed. + */ +#define TT_PORT (0xff << 8) +#define TT_THINK_TIME(p) (((p) & 0x3) << 16) + +/* dev_state bitmasks */ +/* USB device address - assigned by the HC */ +#define DEV_ADDR_MASK (0xff) +/* bits 8:26 reserved */ +/* Slot state */ +#define SLOT_STATE (0x1f << 27) +#define GET_SLOT_STATE(p) (((p) & (0x1f << 27)) >> 27) + +#define SLOT_STATE_DISABLED 0 +#define SLOT_STATE_ENABLED SLOT_STATE_DISABLED +#define SLOT_STATE_DEFAULT 1 +#define SLOT_STATE_ADDRESSED 2 +#define SLOT_STATE_CONFIGURED 3 + +/** + * struct xhci_ep_ctx + * @ep_info: endpoint state, streams, mult, and interval information. + * @ep_info2: information on endpoint type, max packet size, max burst size, + * error count, and whether the HC will force an event for all + * transactions. + * @deq: 64-bit ring dequeue pointer address. If the endpoint only + * defines one stream, this points to the endpoint transfer ring. + * Otherwise, it points to a stream context array, which has a + * ring pointer for each flow. + * @tx_info: + * Average TRB lengths for the endpoint ring and + * max payload within an Endpoint Service Interval Time (ESIT). + * + * Endpoint Context - section 6.2.1.2. This assumes the HC uses 32-byte context + * structures. If the HC uses 64-byte contexts, there is an additional 32 bytes + * reserved at the end of the endpoint context for HC internal use. + */ +struct xhci_ep_ctx { + __le32 ep_info; + __le32 ep_info2; + __le64 deq; + __le32 tx_info; + /* offset 0x14 - 0x1f reserved for HC internal use */ + __le32 reserved[3]; +}; + +/* ep_info bitmasks */ +/* + * Endpoint State - bits 0:2 + * 0 - disabled + * 1 - running + * 2 - halted due to halt condition - ok to manipulate endpoint ring + * 3 - stopped + * 4 - TRB error + * 5-7 - reserved + */ +#define EP_STATE_MASK (0xf) +#define EP_STATE_DISABLED 0 +#define EP_STATE_RUNNING 1 +#define EP_STATE_HALTED 2 +#define EP_STATE_STOPPED 3 +#define EP_STATE_ERROR 4 +/* Mult - Max number of burtst within an interval, in EP companion desc. */ +#define EP_MULT(p) (((p) & 0x3) << 8) +#define CTX_TO_EP_MULT(p) (((p) >> 8) & 0x3) +/* bits 10:14 are Max Primary Streams */ +/* bit 15 is Linear Stream Array */ +/* Interval - period between requests to an endpoint - 125u increments. */ +#define EP_INTERVAL(p) (((p) & 0xff) << 16) +#define EP_INTERVAL_TO_UFRAMES(p) (1 << (((p) >> 16) & 0xff)) +#define CTX_TO_EP_INTERVAL(p) (((p) >> 16) & 0xff) +#define EP_MAXPSTREAMS_MASK (0x1f << 10) +#define EP_MAXPSTREAMS(p) (((p) << 10) & EP_MAXPSTREAMS_MASK) +/* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */ +#define EP_HAS_LSA (1 << 15) + +/* ep_info2 bitmasks */ +/* + * Force Event - generate transfer events for all TRBs for this endpoint + * This will tell the HC to ignore the IOC and ISP flags (for debugging only). + */ +#define FORCE_EVENT (0x1) +#define ERROR_COUNT(p) (((p) & 0x3) << 1) +#define CTX_TO_EP_TYPE(p) (((p) >> 3) & 0x7) +#define EP_TYPE(p) ((p) << 3) +#define ISOC_OUT_EP 1 +#define BULK_OUT_EP 2 +#define INT_OUT_EP 3 +#define CTRL_EP 4 +#define ISOC_IN_EP 5 +#define BULK_IN_EP 6 +#define INT_IN_EP 7 +/* bit 6 reserved */ +/* bit 7 is Host Initiate Disable - for disabling stream selection */ +#define MAX_BURST(p) (((p)&0xff) << 8) +#define CTX_TO_MAX_BURST(p) (((p) >> 8) & 0xff) +#define MAX_PACKET(p) (((p)&0xffff) << 16) +#define MAX_PACKET_MASK (0xffff << 16) +#define MAX_PACKET_DECODED(p) (((p) >> 16) & 0xffff) + +/* Get max packet size from ep desc. Bit 10..0 specify the max packet size. + * USB2.0 spec 9.6.6. + */ +#define GET_MAX_PACKET(p) ((p) & 0x7ff) + +/* tx_info bitmasks */ +#define AVG_TRB_LENGTH_FOR_EP(p) ((p) & 0xffff) +#define MAX_ESIT_PAYLOAD_FOR_EP(p) (((p) & 0xffff) << 16) +#define CTX_TO_MAX_ESIT_PAYLOAD(p) (((p) >> 16) & 0xffff) + +/* deq bitmasks */ +#define EP_CTX_CYCLE_MASK (1 << 0) + + +/** + * struct xhci_input_control_context + * Input control context; see section 6.2.5. + * + * @drop_context: set the bit of the endpoint context you want to disable + * @add_context: set the bit of the endpoint context you want to enable + */ +struct xhci_input_control_ctx { + __le32 drop_flags; + __le32 add_flags; + __le32 rsvd2[6]; +}; + +#define EP_IS_ADDED(ctrl_ctx, i) \ + (le32_to_cpu(ctrl_ctx->add_flags) & (1 << (i + 1))) +#define EP_IS_DROPPED(ctrl_ctx, i) \ + (le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) + +/* Represents everything that is needed to issue a command on the command ring. + * It's useful to pre-allocate these for commands that cannot fail due to + * out-of-memory errors, like freeing streams. + */ +struct xhci_command { + /* Input context for changing device state */ + struct xhci_container_ctx *in_ctx; + u32 status; + /* If completion is null, no one is waiting on this command + * and the structure can be freed after the command completes. + */ + struct completion *completion; + union xhci_trb *command_trb; + struct list_head cmd_list; +}; + +/* drop context bitmasks */ +#define DROP_EP(x) (0x1 << x) +/* add context bitmasks */ +#define ADD_EP(x) (0x1 << x) + +struct xhci_stream_ctx { + /* 64-bit stream ring address, cycle state, and stream type */ + __le64 stream_ring; + /* offset 0x14 - 0x1f reserved for HC internal use */ + __le32 reserved[2]; +}; + +/* Stream Context Types (section 6.4.1) - bits 3:1 of stream ctx deq ptr */ +#define SCT_FOR_CTX(p) (((p) << 1) & 0x7) +/* Secondary stream array type, dequeue pointer is to a transfer ring */ +#define SCT_SEC_TR 0 +/* Primary stream array type, dequeue pointer is to a transfer ring */ +#define SCT_PRI_TR 1 +/* Dequeue pointer is for a secondary stream array (SSA) with 8 entries */ +#define SCT_SSA_8 2 +#define SCT_SSA_16 3 +#define SCT_SSA_32 4 +#define SCT_SSA_64 5 +#define SCT_SSA_128 6 +#define SCT_SSA_256 7 + +/* Assume no secondary streams for now */ +struct xhci_stream_info { + struct xhci_ring **stream_rings; + /* Number of streams, including stream 0 (which drivers can't use) */ + unsigned int num_streams; + /* The stream context array may be bigger than + * the number of streams the driver asked for + */ + struct xhci_stream_ctx *stream_ctx_array; + unsigned int num_stream_ctxs; + dma_addr_t ctx_array_dma; + /* For mapping physical TRB addresses to segments in stream rings */ + struct radix_tree_root trb_address_map; + struct xhci_command *free_streams_command; +}; + +#define SMALL_STREAM_ARRAY_SIZE 256 +#define MEDIUM_STREAM_ARRAY_SIZE 1024 + +/* Some Intel xHCI host controllers need software to keep track of the bus + * bandwidth. Keep track of endpoint info here. Each root port is allocated + * the full bus bandwidth. We must also treat TTs (including each port under a + * multi-TT hub) as a separate bandwidth domain. The direct memory interface + * (DMI) also limits the total bandwidth (across all domains) that can be used. + */ +struct xhci_bw_info { + /* ep_interval is zero-based */ + unsigned int ep_interval; + /* mult and num_packets are one-based */ + unsigned int mult; + unsigned int num_packets; + unsigned int max_packet_size; + unsigned int max_esit_payload; + unsigned int type; +}; + +/* "Block" sizes in bytes the hardware uses for different device speeds. + * The logic in this part of the hardware limits the number of bits the hardware + * can use, so must represent bandwidth in a less precise manner to mimic what + * the scheduler hardware computes. + */ +#define FS_BLOCK 1 +#define HS_BLOCK 4 +#define SS_BLOCK 16 +#define DMI_BLOCK 32 + +/* Each device speed has a protocol overhead (CRC, bit stuffing, etc) associated + * with each byte transferred. SuperSpeed devices have an initial overhead to + * set up bursts. These are in blocks, see above. LS overhead has already been + * translated into FS blocks. + */ +#define DMI_OVERHEAD 8 +#define DMI_OVERHEAD_BURST 4 +#define SS_OVERHEAD 8 +#define SS_OVERHEAD_BURST 32 +#define HS_OVERHEAD 26 +#define FS_OVERHEAD 20 +#define LS_OVERHEAD 128 +/* The TTs need to claim roughly twice as much bandwidth (94 bytes per + * microframe ~= 24Mbps) of the HS bus as the devices can actually use because + * of overhead associated with split transfers crossing microframe boundaries. + * 31 blocks is pure protocol overhead. + */ +#define TT_HS_OVERHEAD (31 + 94) +#define TT_DMI_OVERHEAD (25 + 12) + +/* Bandwidth limits in blocks */ +#define FS_BW_LIMIT 1285 +#define TT_BW_LIMIT 1320 +#define HS_BW_LIMIT 1607 +#define SS_BW_LIMIT_IN 3906 +#define DMI_BW_LIMIT_IN 3906 +#define SS_BW_LIMIT_OUT 3906 +#define DMI_BW_LIMIT_OUT 3906 + +/* Percentage of bus bandwidth reserved for non-periodic transfers */ +#define FS_BW_RESERVED 10 +#define HS_BW_RESERVED 20 +#define SS_BW_RESERVED 10 + +struct xhci_virt_ep { + struct xhci_ring *ring; + /* Related to endpoints that are configured to use stream IDs only */ + struct xhci_stream_info *stream_info; + /* Temporary storage in case the configure endpoint command fails and we + * have to restore the device state to the previous state + */ + struct xhci_ring *new_ring; + unsigned int ep_state; +#define SET_DEQ_PENDING (1 << 0) +#define EP_HALTED (1 << 1) /* For stall handling */ +#define EP_HALT_PENDING (1 << 2) /* For URB cancellation */ +/* Transitioning the endpoint to using streams, don't enqueue URBs */ +#define EP_GETTING_STREAMS (1 << 3) +#define EP_HAS_STREAMS (1 << 4) +/* Transitioning the endpoint to not using streams, don't enqueue URBs */ +#define EP_GETTING_NO_STREAMS (1 << 5) + /* ---- Related to URB cancellation ---- */ + struct list_head cancelled_td_list; + /* The TRB that was last reported in a stopped endpoint ring */ + union xhci_trb *stopped_trb; + struct xhci_td *stopped_td; + unsigned int stopped_stream; + /* Watchdog timer for stop endpoint command to cancel URBs */ + struct timer_list stop_cmd_timer; + int stop_cmds_pending; + struct xhci_hcd *xhci; + /* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue + * command. We'll need to update the ring's dequeue segment and dequeue + * pointer after the command completes. + */ + struct xhci_segment *queued_deq_seg; + union xhci_trb *queued_deq_ptr; + /* + * Sometimes the xHC can not process isochronous endpoint ring quickly + * enough, and it will miss some isoc tds on the ring and generate + * a Missed Service Error Event. + * Set skip flag when receive a Missed Service Error Event and + * process the missed tds on the endpoint ring. + */ + bool skip; + /* Bandwidth checking storage */ + struct xhci_bw_info bw_info; + struct list_head bw_endpoint_list; +}; + +enum xhci_overhead_type { + LS_OVERHEAD_TYPE = 0, + FS_OVERHEAD_TYPE, + HS_OVERHEAD_TYPE, +}; + +struct xhci_interval_bw { + unsigned int num_packets; + /* Sorted by max packet size. + * Head of the list is the greatest max packet size. + */ + struct list_head endpoints; + /* How many endpoints of each speed are present. */ + unsigned int overhead[3]; +}; + +#define XHCI_MAX_INTERVAL 16 + +struct xhci_interval_bw_table { + unsigned int interval0_esit_payload; + struct xhci_interval_bw interval_bw[XHCI_MAX_INTERVAL]; + /* Includes reserved bandwidth for async endpoints */ + unsigned int bw_used; + unsigned int ss_bw_in; + unsigned int ss_bw_out; +}; + + +struct xhci_virt_device { + struct usb_device *udev; + /* + * Commands to the hardware are passed an "input context" that + * tells the hardware what to change in its data structures. + * The hardware will return changes in an "output context" that + * software must allocate for the hardware. We need to keep + * track of input and output contexts separately because + * these commands might fail and we don't trust the hardware. + */ + struct xhci_container_ctx *out_ctx; + /* Used for addressing devices and configuration changes */ + struct xhci_container_ctx *in_ctx; + /* Rings saved to ensure old alt settings can be re-instated */ + struct xhci_ring **ring_cache; + int num_rings_cached; + /* Store xHC assigned device address */ + int address; +#define XHCI_MAX_RINGS_CACHED 31 + struct xhci_virt_ep eps[31]; + struct completion cmd_completion; + /* Status of the last command issued for this device */ + u32 cmd_status; + struct list_head cmd_list; + u8 fake_port; + u8 real_port; + struct xhci_interval_bw_table *bw_table; + struct xhci_tt_bw_info *tt_info; + /* The current max exit latency for the enabled USB3 link states. */ + u16 current_mel; +}; + +/* + * For each roothub, keep track of the bandwidth information for each periodic + * interval. + * + * If a high speed hub is attached to the roothub, each TT associated with that + * hub is a separate bandwidth domain. The interval information for the + * endpoints on the devices under that TT will appear in the TT structure. + */ +struct xhci_root_port_bw_info { + struct list_head tts; + unsigned int num_active_tts; + struct xhci_interval_bw_table bw_table; +}; + +struct xhci_tt_bw_info { + struct list_head tt_list; + int slot_id; + int ttport; + struct xhci_interval_bw_table bw_table; + int active_eps; +}; + + +/** + * struct xhci_device_context_array + * @dev_context_ptr array of 64-bit DMA addresses for device contexts + */ +struct xhci_device_context_array { + /* 64-bit device addresses; we only write 32-bit addresses */ + __le64 dev_context_ptrs[MAX_HC_SLOTS]; + /* private xHCD pointers */ + dma_addr_t dma; +}; +/* TODO: write function to set the 64-bit device DMA address */ +/* + * TODO: change this to be dynamically sized at HC mem init time since the HC + * might not be able to handle the maximum number of devices possible. + */ + + +struct xhci_transfer_event { + /* 64-bit buffer address, or immediate data */ + __le64 buffer; + __le32 transfer_len; + /* This field is interpreted differently based on the type of TRB */ + __le32 flags; +}; + +/* Transfer event TRB length bit mask */ +/* bits 0:23 */ +#define EVENT_TRB_LEN(p) ((p) & 0xffffff) + +/** Transfer Event bit fields **/ +#define TRB_TO_EP_ID(p) (((p) >> 16) & 0x1f) + +/* Completion Code - only applicable for some types of TRBs */ +#define COMP_CODE_MASK (0xff << 24) +#define GET_COMP_CODE(p) (((p) & COMP_CODE_MASK) >> 24) +#define COMP_SUCCESS 1 +/* Data Buffer Error */ +#define COMP_DB_ERR 2 +/* Babble Detected Error */ +#define COMP_BABBLE 3 +/* USB Transaction Error */ +#define COMP_TX_ERR 4 +/* TRB Error - some TRB field is invalid */ +#define COMP_TRB_ERR 5 +/* Stall Error - USB device is stalled */ +#define COMP_STALL 6 +/* Resource Error - HC doesn't have memory for that device configuration */ +#define COMP_ENOMEM 7 +/* Bandwidth Error - not enough room in schedule for this dev config */ +#define COMP_BW_ERR 8 +/* No Slots Available Error - HC ran out of device slots */ +#define COMP_ENOSLOTS 9 +/* Invalid Stream Type Error */ +#define COMP_STREAM_ERR 10 +/* Slot Not Enabled Error - doorbell rung for disabled device slot */ +#define COMP_EBADSLT 11 +/* Endpoint Not Enabled Error */ +#define COMP_EBADEP 12 +/* Short Packet */ +#define COMP_SHORT_TX 13 +/* Ring Underrun - doorbell rung for an empty isoc OUT ep ring */ +#define COMP_UNDERRUN 14 +/* Ring Overrun - isoc IN ep ring is empty when ep is scheduled to RX */ +#define COMP_OVERRUN 15 +/* Virtual Function Event Ring Full Error */ +#define COMP_VF_FULL 16 +/* Parameter Error - Context parameter is invalid */ +#define COMP_EINVAL 17 +/* Bandwidth Overrun Error - isoc ep exceeded its allocated bandwidth */ +#define COMP_BW_OVER 18 +/* Context State Error - illegal context state transition requested */ +#define COMP_CTX_STATE 19 +/* No Ping Response Error - HC didn't get PING_RESPONSE in time to TX */ +#define COMP_PING_ERR 20 +/* Event Ring is full */ +#define COMP_ER_FULL 21 +/* Incompatible Device Error */ +#define COMP_DEV_ERR 22 +/* Missed Service Error - HC couldn't service an isoc ep within interval */ +#define COMP_MISSED_INT 23 +/* Successfully stopped command ring */ +#define COMP_CMD_STOP 24 +/* Successfully aborted current command and stopped command ring */ +#define COMP_CMD_ABORT 25 +/* Stopped - transfer was terminated by a stop endpoint command */ +#define COMP_STOP 26 +/* Same as COMP_EP_STOPPED, but the transferred length in the event is invalid */ +#define COMP_STOP_INVAL 27 +/* Control Abort Error - Debug Capability - control pipe aborted */ +#define COMP_DBG_ABORT 28 +/* Max Exit Latency Too Large Error */ +#define COMP_MEL_ERR 29 +/* TRB type 30 reserved */ +/* Isoc Buffer Overrun - an isoc IN ep sent more data than could fit in TD */ +#define COMP_BUFF_OVER 31 +/* Event Lost Error - xHC has an "internal event overrun condition" */ +#define COMP_ISSUES 32 +/* Undefined Error - reported when other error codes don't apply */ +#define COMP_UNKNOWN 33 +/* Invalid Stream ID Error */ +#define COMP_STRID_ERR 34 +/* Secondary Bandwidth Error - may be returned by a Configure Endpoint cmd */ +#define COMP_2ND_BW_ERR 35 +/* Split Transaction Error */ +#define COMP_SPLIT_ERR 36 + +struct xhci_link_trb { + /* 64-bit segment pointer*/ + __le64 segment_ptr; + __le32 intr_target; + __le32 control; +}; + +/* control bitfields */ +#define LINK_TOGGLE (0x1<<1) + +/* Command completion event TRB */ +struct xhci_event_cmd { + /* Pointer to command TRB, or the value passed by the event data trb */ + __le64 cmd_trb; + __le32 status; + __le32 flags; +}; + +/* flags bitmasks */ +/* bits 16:23 are the virtual function ID */ +/* bits 24:31 are the slot ID */ +#define TRB_TO_SLOT_ID(p) (((p) & (0xff<<24)) >> 24) +#define SLOT_ID_FOR_TRB(p) (((p) & 0xff) << 24) + +/* Stop Endpoint TRB - ep_index to endpoint ID for this TRB */ +#define TRB_TO_EP_INDEX(p) ((((p) & (0x1f << 16)) >> 16) - 1) +#define EP_ID_FOR_TRB(p) ((((p) + 1) & 0x1f) << 16) + +#define SUSPEND_PORT_FOR_TRB(p) (((p) & 1) << 23) +#define TRB_TO_SUSPEND_PORT(p) (((p) & (1 << 23)) >> 23) +#define LAST_EP_INDEX 30 + +/* Set TR Dequeue Pointer command TRB fields */ +#define TRB_TO_STREAM_ID(p) ((((p) & (0xffff << 16)) >> 16)) +#define STREAM_ID_FOR_TRB(p) ((((p)) & 0xffff) << 16) + + +/* Port Status Change Event TRB fields */ +/* Port ID - bits 31:24 */ +#define GET_PORT_ID(p) (((p) & (0xff << 24)) >> 24) + +/* Normal TRB fields */ +/* transfer_len bitmasks - bits 0:16 */ +#define TRB_LEN(p) ((p) & 0x1ffff) +/* Interrupter Target - which MSI-X vector to target the completion event at */ +#define TRB_INTR_TARGET(p) (((p) & 0x3ff) << 22) +#define GET_INTR_TARGET(p) (((p) >> 22) & 0x3ff) +#define TRB_TBC(p) (((p) & 0x3) << 7) +#define TRB_TLBPC(p) (((p) & 0xf) << 16) + +/* Cycle bit - indicates TRB ownership by HC or HCD */ +#define TRB_CYCLE (1<<0) +/* + * Force next event data TRB to be evaluated before task switch. + * Used to pass OS data back after a TD completes. + */ +#define TRB_ENT (1<<1) +/* Interrupt on short packet */ +#define TRB_ISP (1<<2) +/* Set PCIe no snoop attribute */ +#define TRB_NO_SNOOP (1<<3) +/* Chain multiple TRBs into a TD */ +#define TRB_CHAIN (1<<4) +/* Interrupt on completion */ +#define TRB_IOC (1<<5) +/* The buffer pointer contains immediate data */ +#define TRB_IDT (1<<6) + +/* Block Event Interrupt */ +#define TRB_BEI (1<<9) + +/* Control transfer TRB specific fields */ +#define TRB_DIR_IN (1<<16) +#define TRB_TX_TYPE(p) ((p) << 16) +#define TRB_DATA_OUT 2 +#define TRB_DATA_IN 3 + +/* Isochronous TRB specific fields */ +#define TRB_SIA (1<<31) + +struct xhci_generic_trb { + __le32 field[4]; +}; + +union xhci_trb { + struct xhci_link_trb link; + struct xhci_transfer_event trans_event; + struct xhci_event_cmd event_cmd; + struct xhci_generic_trb generic; +}; + +/* TRB bit mask */ +#define TRB_TYPE_BITMASK (0xfc00) +#define TRB_TYPE(p) ((p) << 10) +#define TRB_FIELD_TO_TYPE(p) (((p) & TRB_TYPE_BITMASK) >> 10) +/* TRB type IDs */ +/* bulk, interrupt, isoc scatter/gather, and control data stage */ +#define TRB_NORMAL 1 +/* setup stage for control transfers */ +#define TRB_SETUP 2 +/* data stage for control transfers */ +#define TRB_DATA 3 +/* status stage for control transfers */ +#define TRB_STATUS 4 +/* isoc transfers */ +#define TRB_ISOC 5 +/* TRB for linking ring segments */ +#define TRB_LINK 6 +#define TRB_EVENT_DATA 7 +/* Transfer Ring No-op (not for the command ring) */ +#define TRB_TR_NOOP 8 +/* Command TRBs */ +/* Enable Slot Command */ +#define TRB_ENABLE_SLOT 9 +/* Disable Slot Command */ +#define TRB_DISABLE_SLOT 10 +/* Address Device Command */ +#define TRB_ADDR_DEV 11 +/* Configure Endpoint Command */ +#define TRB_CONFIG_EP 12 +/* Evaluate Context Command */ +#define TRB_EVAL_CONTEXT 13 +/* Reset Endpoint Command */ +#define TRB_RESET_EP 14 +/* Stop Transfer Ring Command */ +#define TRB_STOP_RING 15 +/* Set Transfer Ring Dequeue Pointer Command */ +#define TRB_SET_DEQ 16 +/* Reset Device Command */ +#define TRB_RESET_DEV 17 +/* Force Event Command (opt) */ +#define TRB_FORCE_EVENT 18 +/* Negotiate Bandwidth Command (opt) */ +#define TRB_NEG_BANDWIDTH 19 +/* Set Latency Tolerance Value Command (opt) */ +#define TRB_SET_LT 20 +/* Get port bandwidth Command */ +#define TRB_GET_BW 21 +/* Force Header Command - generate a transaction or link management packet */ +#define TRB_FORCE_HEADER 22 +/* No-op Command - not for transfer rings */ +#define TRB_CMD_NOOP 23 +/* TRB IDs 24-31 reserved */ +/* Event TRBS */ +/* Transfer Event */ +#define TRB_TRANSFER 32 +/* Command Completion Event */ +#define TRB_COMPLETION 33 +/* Port Status Change Event */ +#define TRB_PORT_STATUS 34 +/* Bandwidth Request Event (opt) */ +#define TRB_BANDWIDTH_EVENT 35 +/* Doorbell Event (opt) */ +#define TRB_DOORBELL 36 +/* Host Controller Event */ +#define TRB_HC_EVENT 37 +/* Device Notification Event - device sent function wake notification */ +#define TRB_DEV_NOTE 38 +/* MFINDEX Wrap Event - microframe counter wrapped */ +#define TRB_MFINDEX_WRAP 39 +/* TRB IDs 40-47 reserved, 48-63 is vendor-defined */ + +/* Nec vendor-specific command completion event. */ +#define TRB_NEC_CMD_COMP 48 +/* Get NEC firmware revision. */ +#define TRB_NEC_GET_FW 49 + +#define TRB_TYPE_LINK(x) (((x) & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK)) +/* Above, but for __le32 types -- can avoid work by swapping constants: */ +#define TRB_TYPE_LINK_LE32(x) (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == \ + cpu_to_le32(TRB_TYPE(TRB_LINK))) +#define TRB_TYPE_NOOP_LE32(x) (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == \ + cpu_to_le32(TRB_TYPE(TRB_TR_NOOP))) + +#define NEC_FW_MINOR(p) (((p) >> 0) & 0xff) +#define NEC_FW_MAJOR(p) (((p) >> 8) & 0xff) + +/* + * TRBS_PER_SEGMENT must be a multiple of 4, + * since the command ring is 64-byte aligned. + * It must also be greater than 16. + */ +#define TRBS_PER_SEGMENT 64 +/* Allow two commands + a link TRB, along with any reserved command TRBs */ +#define MAX_RSVD_CMD_TRBS (TRBS_PER_SEGMENT - 3) +#define TRB_SEGMENT_SIZE (TRBS_PER_SEGMENT*16) +#define TRB_SEGMENT_SHIFT (ilog2(TRB_SEGMENT_SIZE)) +/* TRB buffer pointers can't cross 64KB boundaries */ +#define TRB_MAX_BUFF_SHIFT 16 +#define TRB_MAX_BUFF_SIZE (1 << TRB_MAX_BUFF_SHIFT) + +struct xhci_segment { + union xhci_trb *trbs; + /* private to HCD */ + struct xhci_segment *next; + dma_addr_t dma; +}; + +struct xhci_td { + struct list_head td_list; + struct list_head cancelled_td_list; + struct urb *urb; + struct xhci_segment *start_seg; + union xhci_trb *first_trb; + union xhci_trb *last_trb; +}; + +/* xHCI command default timeout value */ +#define XHCI_CMD_DEFAULT_TIMEOUT (5 * HZ) + +/* command descriptor */ +struct xhci_cd { + struct list_head cancel_cmd_list; + struct xhci_command *command; + union xhci_trb *cmd_trb; +}; + +struct xhci_dequeue_state { + struct xhci_segment *new_deq_seg; + union xhci_trb *new_deq_ptr; + int new_cycle_state; +}; + +enum xhci_ring_type { + TYPE_CTRL = 0, + TYPE_ISOC, + TYPE_BULK, + TYPE_INTR, + TYPE_STREAM, + TYPE_COMMAND, + TYPE_EVENT, +}; + +struct xhci_ring { + struct xhci_segment *first_seg; + struct xhci_segment *last_seg; + union xhci_trb *enqueue; + struct xhci_segment *enq_seg; + unsigned int enq_updates; + union xhci_trb *dequeue; + struct xhci_segment *deq_seg; + unsigned int deq_updates; + struct list_head td_list; + /* + * Write the cycle state into the TRB cycle field to give ownership of + * the TRB to the host controller (if we are the producer), or to check + * if we own the TRB (if we are the consumer). See section 4.9.1. + */ + u32 cycle_state; + unsigned int stream_id; + unsigned int num_segs; + unsigned int num_trbs_free; + unsigned int num_trbs_free_temp; + enum xhci_ring_type type; + bool last_td_was_short; +}; + +struct xhci_erst_entry { + /* 64-bit event ring segment address */ + __le64 seg_addr; + __le32 seg_size; + /* Set to zero */ + __le32 rsvd; +}; + +struct xhci_erst { + struct xhci_erst_entry *entries; + unsigned int num_entries; + /* xhci->event_ring keeps track of segment dma addresses */ + dma_addr_t erst_dma_addr; + /* Num entries the ERST can contain */ + unsigned int erst_size; +}; + +struct xhci_scratchpad { + u64 *sp_array; + dma_addr_t sp_dma; + void **sp_buffers; + dma_addr_t *sp_dma_buffers; +}; + +struct urb_priv { + int length; + int td_cnt; + struct xhci_td *td[0]; +}; + +/* + * Each segment table entry is 4*32bits long. 1K seems like an ok size: + * (1K bytes * 8bytes/bit) / (4*32 bits) = 64 segment entries in the table, + * meaning 64 ring segments. + * Initial allocated size of the ERST, in number of entries */ +#define ERST_NUM_SEGS 1 +/* Initial allocated size of the ERST, in number of entries */ +#define ERST_SIZE 64 +/* Initial number of event segment rings allocated */ +#define ERST_ENTRIES 1 +/* Poll every 60 seconds */ +#define POLL_TIMEOUT 60 +/* Stop endpoint command timeout (secs) for URB cancellation watchdog timer */ +#define XHCI_STOP_EP_CMD_TIMEOUT 5 +/* XXX: Make these module parameters */ + +struct s3_save { + u32 command; + u32 dev_nt; + u64 dcbaa_ptr; + u32 config_reg; + u32 irq_pending; + u32 irq_control; + u32 erst_size; + u64 erst_base; + u64 erst_dequeue; +}; + +/* Use for lpm */ +struct dev_info { + u32 dev_id; + struct list_head list; +}; + +struct xhci_bus_state { + unsigned long bus_suspended; + unsigned long next_statechange; + + /* Port suspend arrays are indexed by the portnum of the fake roothub */ + /* ports suspend status arrays - max 31 ports for USB2, 15 for USB3 */ + u32 port_c_suspend; + u32 suspended_ports; + u32 port_remote_wakeup; + unsigned long resume_done[USB_MAXCHILDREN]; + /* which ports have started to resume */ + unsigned long resuming_ports; +}; + +static inline unsigned int hcd_index(struct usb_hcd *hcd) +{ + if (hcd->speed == HCD_USB3) + return 0; + else + return 1; +} + +/* There is one xhci_hcd structure per controller */ +struct xhci_hcd { + struct usb_hcd *main_hcd; + struct usb_hcd *shared_hcd; + /* glue to PCI and HCD framework */ + struct xhci_cap_regs __iomem *cap_regs; + struct xhci_op_regs __iomem *op_regs; + struct xhci_run_regs __iomem *run_regs; + struct xhci_doorbell_array __iomem *dba; + /* Our HCD's current interrupter register set */ + struct xhci_intr_reg __iomem *ir_set; + + /* Cached register copies of read-only HC data */ + __u32 hcs_params1; + __u32 hcs_params2; + __u32 hcs_params3; + __u32 hcc_params; + + spinlock_t lock; + + /* packed release number */ + u8 sbrn; + u16 hci_version; + u8 max_slots; + u8 max_interrupters; + u8 max_ports; + u8 isoc_threshold; + int event_ring_max; + int addr_64; + /* 4KB min, 128MB max */ + int page_size; + /* Valid values are 12 to 20, inclusive */ + int page_shift; + /* msi-x vectors */ + int msix_count; + struct msix_entry *msix_entries; + /* data structures */ + struct xhci_device_context_array *dcbaa; + struct xhci_ring *cmd_ring; + unsigned int cmd_ring_state; +#define CMD_RING_STATE_RUNNING (1 << 0) +#define CMD_RING_STATE_ABORTED (1 << 1) +#define CMD_RING_STATE_STOPPED (1 << 2) + struct list_head cancel_cmd_list; + unsigned int cmd_ring_reserved_trbs; + struct xhci_ring *event_ring; + struct xhci_erst erst; + /* Scratchpad */ + struct xhci_scratchpad *scratchpad; + /* Store LPM test failed devices' information */ + struct list_head lpm_failed_devs; + + /* slot enabling and address device helpers */ + struct completion addr_dev; + int slot_id; + /* For USB 3.0 LPM enable/disable. */ + struct xhci_command *lpm_command; + /* Internal mirror of the HW's dcbaa */ + struct xhci_virt_device *devs[MAX_HC_SLOTS]; + /* For keeping track of bandwidth domains per roothub. */ + struct xhci_root_port_bw_info *rh_bw; + + /* DMA pools */ + struct dma_pool *device_pool; + struct dma_pool *segment_pool; + struct dma_pool *small_streams_pool; + struct dma_pool *medium_streams_pool; + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING + /* Poll the rings - for debugging */ + struct timer_list event_ring_timer; + int zombie; +#endif + /* Host controller watchdog timer structures */ + unsigned int xhc_state; + + u32 command; + struct s3_save s3; +/* Host controller is dying - not responding to commands. "I'm not dead yet!" + * + * xHC interrupts have been disabled and a watchdog timer will (or has already) + * halt the xHCI host, and complete all URBs with an -ESHUTDOWN code. Any code + * that sees this status (other than the timer that set it) should stop touching + * hardware immediately. Interrupt handlers should return immediately when + * they see this status (any time they drop and re-acquire xhci->lock). + * xhci_urb_dequeue() should call usb_hcd_check_unlink_urb() and return without + * putting the TD on the canceled list, etc. + * + * There are no reports of xHCI host controllers that display this issue. + */ +#define XHCI_STATE_DYING (1 << 0) +#define XHCI_STATE_HALTED (1 << 1) + /* Statistics */ + int error_bitmask; + unsigned int quirks; +#define XHCI_LINK_TRB_QUIRK (1 << 0) +#define XHCI_RESET_EP_QUIRK (1 << 1) +#define XHCI_NEC_HOST (1 << 2) +#define XHCI_AMD_PLL_FIX (1 << 3) +#define XHCI_SPURIOUS_SUCCESS (1 << 4) +/* + * Certain Intel host controllers have a limit to the number of endpoint + * contexts they can handle. Ideally, they would signal that they can't handle + * anymore endpoint contexts by returning a Resource Error for the Configure + * Endpoint command, but they don't. Instead they expect software to keep track + * of the number of active endpoints for them, across configure endpoint + * commands, reset device commands, disable slot commands, and address device + * commands. + */ +#define XHCI_EP_LIMIT_QUIRK (1 << 5) +#define XHCI_BROKEN_MSI (1 << 6) +#define XHCI_RESET_ON_RESUME (1 << 7) +#define XHCI_SW_BW_CHECKING (1 << 8) +#define XHCI_AMD_0x96_HOST (1 << 9) +#define XHCI_TRUST_TX_LENGTH (1 << 10) +#define XHCI_LPM_SUPPORT (1 << 11) +#define XHCI_INTEL_HOST (1 << 12) +#define XHCI_SPURIOUS_REBOOT (1 << 13) +#define XHCI_COMP_MODE_QUIRK (1 << 14) +#define XHCI_AVOID_BEI (1 << 15) +#define XHCI_PLAT (1 << 16) + unsigned int num_active_eps; + unsigned int limit_active_eps; + /* There are two roothubs to keep track of bus suspend info for */ + struct xhci_bus_state bus_state[2]; + /* Is each xHCI roothub port a USB 3.0, USB 2.0, or USB 1.1 port? */ + u8 *port_array; + /* Array of pointers to USB 3.0 PORTSC registers */ + __le32 __iomem **usb3_ports; + unsigned int num_usb3_ports; + /* Array of pointers to USB 2.0 PORTSC registers */ + __le32 __iomem **usb2_ports; + unsigned int num_usb2_ports; + /* support xHCI 0.96 spec USB2 software LPM */ + unsigned sw_lpm_support:1; + /* support xHCI 1.0 spec USB2 hardware LPM */ + unsigned hw_lpm_support:1; + /* Compliance Mode Recovery Data */ + struct timer_list comp_mode_recovery_timer; + u32 port_status_u0; +/* Compliance Mode Timer Triggered every 2 seconds */ +#define COMP_MODE_RCVRY_MSECS 2000 +}; + +/* convert between an HCD pointer and the corresponding EHCI_HCD */ +static inline struct xhci_hcd *hcd_to_xhci(struct usb_hcd *hcd) +{ + return *((struct xhci_hcd **) (hcd->hcd_priv)); +} + +static inline struct usb_hcd *xhci_to_hcd(struct xhci_hcd *xhci) +{ + return xhci->main_hcd; +} + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING +#define XHCI_DEBUG 1 +#else +#define XHCI_DEBUG 0 +#endif + +#define xhci_dbg(xhci, fmt, args...) \ + do { if (XHCI_DEBUG) dev_dbg(xhci_to_hcd(xhci)->self.controller , fmt , ## args); } while (0) +#define xhci_info(xhci, fmt, args...) \ + do { if (XHCI_DEBUG) dev_info(xhci_to_hcd(xhci)->self.controller , fmt , ## args); } while (0) +#define xhci_err(xhci, fmt, args...) \ + dev_err(xhci_to_hcd(xhci)->self.controller , fmt , ## args) +#define xhci_warn(xhci, fmt, args...) \ + dev_warn(xhci_to_hcd(xhci)->self.controller , fmt , ## args) +#define xhci_warn_ratelimited(xhci, fmt, args...) \ + dev_warn_ratelimited(xhci_to_hcd(xhci)->self.controller , fmt , ## args) + +/* TODO: copied from ehci.h - can be refactored? */ +/* xHCI spec says all registers are little endian */ +static inline unsigned int xhci_readl(const struct xhci_hcd *xhci, + __le32 __iomem *regs) +{ + return readl(regs); +} +static inline void xhci_writel(struct xhci_hcd *xhci, + const unsigned int val, __le32 __iomem *regs) +{ + writel(val, regs); +} + +/* + * Registers should always be accessed with double word or quad word accesses. + * + * Some xHCI implementations may support 64-bit address pointers. Registers + * with 64-bit address pointers should be written to with dword accesses by + * writing the low dword first (ptr[0]), then the high dword (ptr[1]) second. + * xHCI implementations that do not support 64-bit address pointers will ignore + * the high dword, and write order is irrelevant. + */ +static inline u64 xhci_read_64(const struct xhci_hcd *xhci, + __le64 __iomem *regs) +{ + __u32 __iomem *ptr = (__u32 __iomem *) regs; + u64 val_lo = readl(ptr); + u64 val_hi = readl(ptr + 1); + return val_lo + (val_hi << 32); +} +static inline void xhci_write_64(struct xhci_hcd *xhci, + const u64 val, __le64 __iomem *regs) +{ + __u32 __iomem *ptr = (__u32 __iomem *) regs; + u32 val_lo = lower_32_bits(val); + u32 val_hi = upper_32_bits(val); + + writel(val_lo, ptr); + writel(val_hi, ptr + 1); +} + +static inline int xhci_link_trb_quirk(struct xhci_hcd *xhci) +{ + return xhci->quirks & XHCI_LINK_TRB_QUIRK; +} + +/* xHCI debugging */ +void xhci_print_ir_set(struct xhci_hcd *xhci, int set_num); +void xhci_print_registers(struct xhci_hcd *xhci); +void xhci_dbg_regs(struct xhci_hcd *xhci); +void xhci_print_run_regs(struct xhci_hcd *xhci); +void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb); +void xhci_debug_trb(struct xhci_hcd *xhci, union xhci_trb *trb); +void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg); +void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring); +void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst); +void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci); +void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring); +void xhci_dbg_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, unsigned int last_ep); +char *xhci_get_slot_state(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx); +void xhci_dbg_ep_rings(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + struct xhci_virt_ep *ep); + +/* xHCI memory management */ +void xhci_mem_cleanup(struct xhci_hcd *xhci); +int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags); +void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id); +int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, struct usb_device *udev, gfp_t flags); +int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev); +void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci, + struct usb_device *udev); +unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc); +unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc); +unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index); +unsigned int xhci_last_valid_endpoint(u32 added_ctxs); +void xhci_endpoint_zero(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_host_endpoint *ep); +void xhci_drop_ep_from_interval_table(struct xhci_hcd *xhci, + struct xhci_bw_info *ep_bw, + struct xhci_interval_bw_table *bw_table, + struct usb_device *udev, + struct xhci_virt_ep *virt_ep, + struct xhci_tt_bw_info *tt_info); +void xhci_update_tt_active_eps(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int old_active_eps); +void xhci_clear_endpoint_bw_info(struct xhci_bw_info *bw_info); +void xhci_update_bw_info(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx, + struct xhci_input_control_ctx *ctrl_ctx, + struct xhci_virt_device *virt_dev); +void xhci_endpoint_copy(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx, + struct xhci_container_ctx *out_ctx, + unsigned int ep_index); +void xhci_slot_copy(struct xhci_hcd *xhci, + struct xhci_container_ctx *in_ctx, + struct xhci_container_ctx *out_ctx); +int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, + struct usb_device *udev, struct usb_host_endpoint *ep, + gfp_t mem_flags); +void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring); +int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, + unsigned int num_trbs, gfp_t flags); +void xhci_free_or_cache_endpoint_ring(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + unsigned int ep_index); +struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, + unsigned int num_stream_ctxs, + unsigned int num_streams, gfp_t flags); +void xhci_free_stream_info(struct xhci_hcd *xhci, + struct xhci_stream_info *stream_info); +void xhci_setup_streams_ep_input_ctx(struct xhci_hcd *xhci, + struct xhci_ep_ctx *ep_ctx, + struct xhci_stream_info *stream_info); +void xhci_setup_no_streams_ep_input_ctx(struct xhci_hcd *xhci, + struct xhci_ep_ctx *ep_ctx, + struct xhci_virt_ep *ep); +void xhci_free_device_endpoint_resources(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, bool drop_control_ep); +struct xhci_ring *xhci_dma_to_transfer_ring( + struct xhci_virt_ep *ep, + u64 address); +struct xhci_ring *xhci_stream_id_to_ring( + struct xhci_virt_device *dev, + unsigned int ep_index, + unsigned int stream_id); +struct xhci_command *xhci_alloc_command(struct xhci_hcd *xhci, + bool allocate_in_ctx, bool allocate_completion, + gfp_t mem_flags); +void xhci_urb_free_priv(struct xhci_hcd *xhci, struct urb_priv *urb_priv); +void xhci_free_command(struct xhci_hcd *xhci, + struct xhci_command *command); + +#ifdef CONFIG_PCI +/* xHCI PCI glue */ +int xhci_register_pci(void); +void xhci_unregister_pci(void); +#else +static inline int xhci_register_pci(void) { return 0; } +static inline void xhci_unregister_pci(void) {} +#endif + +#if defined(CONFIG_USB_XHCI_PLATFORM) \ + || defined(CONFIG_USB_XHCI_PLATFORM_MODULE) +int xhci_register_plat(void); +void xhci_unregister_plat(void); +#else +static inline int xhci_register_plat(void) +{ return 0; } +static inline void xhci_unregister_plat(void) +{ } +#endif + +/* xHCI host controller glue */ +typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); +int xhci_handshake(struct xhci_hcd *xhci, void __iomem *ptr, + u32 mask, u32 done, int usec); +void xhci_quiesce(struct xhci_hcd *xhci); +int xhci_halt(struct xhci_hcd *xhci); +int xhci_reset(struct xhci_hcd *xhci); +int xhci_init(struct usb_hcd *hcd); +int xhci_run(struct usb_hcd *hcd); +void xhci_stop(struct usb_hcd *hcd); +void xhci_shutdown(struct usb_hcd *hcd); +int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks); + +#ifdef CONFIG_PM +int xhci_suspend(struct xhci_hcd *xhci); +int xhci_resume(struct xhci_hcd *xhci, bool hibernated); +#else +#define xhci_suspend NULL +#define xhci_resume NULL +#endif + +int xhci_get_frame(struct usb_hcd *hcd); +irqreturn_t xhci_irq(struct usb_hcd *hcd); +irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd); +int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev); +void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); +int xhci_alloc_tt_info(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct usb_device *hdev, + struct usb_tt *tt, gfp_t mem_flags); +int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int num_streams, gfp_t mem_flags); +int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags); +int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev); +int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev); +int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, + struct usb_device *udev, int enable); +int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, + struct usb_tt *tt, gfp_t mem_flags); +int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); +int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); +int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep); +int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep); +void xhci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep); +int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev); +int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); +void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); + +/* xHCI ring, segment, TRB, and TD functions */ +dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); +struct xhci_segment *trb_in_td(struct xhci_segment *start_seg, + union xhci_trb *start_trb, union xhci_trb *end_trb, + dma_addr_t suspect_dma); +int xhci_is_vendor_info_code(struct xhci_hcd *xhci, unsigned int trb_comp_code); +void xhci_ring_cmd_db(struct xhci_hcd *xhci); +int xhci_queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); +int xhci_queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id); +int xhci_queue_vendor_command(struct xhci_hcd *xhci, + u32 field1, u32 field2, u32 field3, u32 field4); +int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index, int suspend); +int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, + int slot_id, unsigned int ep_index); +int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, + int slot_id, unsigned int ep_index); +int xhci_queue_intr_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, + int slot_id, unsigned int ep_index); +int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index); +int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id, bool command_must_succeed); +int xhci_queue_evaluate_context(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id, bool command_must_succeed); +int xhci_queue_reset_ep(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index); +int xhci_queue_reset_device(struct xhci_hcd *xhci, u32 slot_id); +void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + unsigned int stream_id, struct xhci_td *cur_td, + struct xhci_dequeue_state *state); +void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + unsigned int stream_id, + struct xhci_dequeue_state *deq_state); +void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, + struct usb_device *udev, unsigned int ep_index); +void xhci_queue_config_ep_quirk(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + struct xhci_dequeue_state *deq_state); +void xhci_stop_endpoint_command_watchdog(unsigned long arg); +int xhci_cancel_cmd(struct xhci_hcd *xhci, struct xhci_command *command, + union xhci_trb *cmd_trb); +void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, + unsigned int ep_index, unsigned int stream_id); +union xhci_trb *xhci_find_next_enqueue(struct xhci_ring *ring); + +/* xHCI roothub code */ +void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array, + int port_id, u32 link_state); +int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state); +int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state); +void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array, + int port_id, u32 port_bit); +int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); +int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); +int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1); + +#ifdef CONFIG_PM +int xhci_bus_suspend(struct usb_hcd *hcd); +int xhci_bus_resume(struct usb_hcd *hcd); +#else +#define xhci_bus_suspend NULL +#define xhci_bus_resume NULL +#endif /* CONFIG_PM */ + +u32 xhci_port_state_to_neutral(u32 state); +int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, + u16 port); +void xhci_ring_device(struct xhci_hcd *xhci, int slot_id); + +/* xHCI contexts */ +struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); +struct xhci_slot_ctx *xhci_get_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); +struct xhci_ep_ctx *xhci_get_ep_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, unsigned int ep_index); + +/* xHCI quirks */ +bool xhci_compliance_mode_recovery_timer_quirk_check(void); + +#endif /* __LINUX_XHCI_HCD_H */ diff --git a/drivers/usb/image/Kconfig b/drivers/usb/image/Kconfig new file mode 100644 index 00000000..320d368c --- /dev/null +++ b/drivers/usb/image/Kconfig @@ -0,0 +1,28 @@ +# +# USB Imaging devices configuration +# +comment "USB Imaging devices" + +config USB_MDC800 + tristate "USB Mustek MDC800 Digital Camera support" + ---help--- + Say Y here if you want to connect this type of still camera to + your computer's USB port. This driver can be used with gphoto 0.4.3 + and higher (look at ). + To use it create a device node with "mknod /dev/mustek c 180 32" and + configure it in your software. + + To compile this driver as a module, choose M here: the + module will be called mdc800. + +config USB_MICROTEK + tristate "Microtek X6USB scanner support" + depends on SCSI + help + Say Y here if you want support for the Microtek X6USB and + possibly the Phantom 336CX, Phantom C6 and ScanMaker V6U(S)L. + Support for anything but the X6 is experimental. + Please report failures and successes. + The scanner will appear as a scsi generic device to the rest + of the system. Scsi support is required. + This driver can be compiled as a module, called microtek. diff --git a/drivers/usb/image/Makefile b/drivers/usb/image/Makefile new file mode 100644 index 00000000..4148ae30 --- /dev/null +++ b/drivers/usb/image/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for USB Image drivers +# + +obj-$(CONFIG_USB_MDC800) += mdc800.o +obj-$(CONFIG_USB_MICROTEK) += microtek.o diff --git a/drivers/usb/image/mdc800.c b/drivers/usb/image/mdc800.c new file mode 100644 index 00000000..7121b500 --- /dev/null +++ b/drivers/usb/image/mdc800.c @@ -0,0 +1,1086 @@ +/* + * copyright (C) 1999/2000 by Henning Zabel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +/* + * USB-Kernel Driver for the Mustek MDC800 Digital Camera + * (c) 1999/2000 Henning Zabel + * + * + * The driver brings the USB functions of the MDC800 to Linux. + * To use the Camera you must support the USB Protocol of the camera + * to the Kernel Node. + * The Driver uses a misc device Node. Create it with : + * mknod /dev/mustek c 180 32 + * + * The driver supports only one camera. + * + * Fix: mdc800 used sleep_on and slept with io_lock held. + * Converted sleep_on to waitqueues with schedule_timeout and made io_lock + * a semaphore from a spinlock. + * by Oliver Neukum + * (02/12/2001) + * + * Identify version on module load. + * (08/04/2001) gb + * + * version 0.7.5 + * Fixed potential SMP races with Spinlocks. + * Thanks to Oliver Neukum who + * noticed the race conditions. + * (30/10/2000) + * + * Fixed: Setting urb->dev before submitting urb. + * by Greg KH + * (13/10/2000) + * + * version 0.7.3 + * bugfix : The mdc800->state field gets set to READY after the + * the diconnect function sets it to NOT_CONNECTED. This makes the + * driver running like the camera is connected and causes some + * hang ups. + * + * version 0.7.1 + * MOD_INC and MOD_DEC are changed in usb_probe to prevent load/unload + * problems when compiled as Module. + * (04/04/2000) + * + * The mdc800 driver gets assigned the USB Minor 32-47. The Registration + * was updated to use these values. + * (26/03/2000) + * + * The Init und Exit Module Function are updated. + * (01/03/2000) + * + * version 0.7.0 + * Rewrite of the driver : The driver now uses URB's. The old stuff + * has been removed. + * + * version 0.6.0 + * Rewrite of this driver: The Emulation of the rs232 protocoll + * has been removed from the driver. A special executeCommand function + * for this driver is included to gphoto. + * The driver supports two kind of communication to bulk endpoints. + * Either with the dev->bus->ops->bulk... or with callback function. + * (09/11/1999) + * + * version 0.5.0: + * first Version that gets a version number. Most of the needed + * functions work. + * (20/10/1999) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.7.5 (30/10/2000)" +#define DRIVER_AUTHOR "Henning Zabel " +#define DRIVER_DESC "USB Driver for Mustek MDC800 Digital Camera" + +/* Vendor and Product Information */ +#define MDC800_VENDOR_ID 0x055f +#define MDC800_PRODUCT_ID 0xa800 + +/* Timeouts (msec) */ +#define TO_DOWNLOAD_GET_READY 1500 +#define TO_DOWNLOAD_GET_BUSY 1500 +#define TO_WRITE_GET_READY 1000 +#define TO_DEFAULT_COMMAND 5000 +#define TO_READ_FROM_IRQ TO_DEFAULT_COMMAND +#define TO_GET_READY TO_DEFAULT_COMMAND + +/* Minor Number of the device (create with mknod /dev/mustek c 180 32) */ +#define MDC800_DEVICE_MINOR_BASE 32 + + +/************************************************************************** + Data and structs +***************************************************************************/ + + +typedef enum { + NOT_CONNECTED, READY, WORKING, DOWNLOAD +} mdc800_state; + + +/* Data for the driver */ +struct mdc800_data +{ + struct usb_device * dev; // Device Data + mdc800_state state; + + unsigned int endpoint [4]; + + struct urb * irq_urb; + wait_queue_head_t irq_wait; + int irq_woken; + char* irq_urb_buffer; + + int camera_busy; // is camera busy ? + int camera_request_ready; // Status to synchronize with irq + char camera_response [8]; // last Bytes send after busy + + struct urb * write_urb; + char* write_urb_buffer; + wait_queue_head_t write_wait; + int written; + + + struct urb * download_urb; + char* download_urb_buffer; + wait_queue_head_t download_wait; + int downloaded; + int download_left; // Bytes left to download ? + + + /* Device Data */ + char out [64]; // Answer Buffer + int out_ptr; // Index to the first not readen byte + int out_count; // Bytes in the buffer + + int open; // Camera device open ? + struct mutex io_lock; // IO -lock + + char in [8]; // Command Input Buffer + int in_count; + + int pic_index; // Cache for the Imagesize (-1 for nothing cached ) + int pic_len; + int minor; +}; + + +/* Specification of the Endpoints */ +static struct usb_endpoint_descriptor mdc800_ed [4] = +{ + { + .bLength = 0, + .bDescriptorType = 0, + .bEndpointAddress = 0x01, + .bmAttributes = 0x02, + .wMaxPacketSize = cpu_to_le16(8), + .bInterval = 0, + .bRefresh = 0, + .bSynchAddress = 0, + }, + { + .bLength = 0, + .bDescriptorType = 0, + .bEndpointAddress = 0x82, + .bmAttributes = 0x03, + .wMaxPacketSize = cpu_to_le16(8), + .bInterval = 0, + .bRefresh = 0, + .bSynchAddress = 0, + }, + { + .bLength = 0, + .bDescriptorType = 0, + .bEndpointAddress = 0x03, + .bmAttributes = 0x02, + .wMaxPacketSize = cpu_to_le16(64), + .bInterval = 0, + .bRefresh = 0, + .bSynchAddress = 0, + }, + { + .bLength = 0, + .bDescriptorType = 0, + .bEndpointAddress = 0x84, + .bmAttributes = 0x02, + .wMaxPacketSize = cpu_to_le16(64), + .bInterval = 0, + .bRefresh = 0, + .bSynchAddress = 0, + }, +}; + +/* The Variable used by the driver */ +static struct mdc800_data* mdc800; + + +/*************************************************************************** + The USB Part of the driver +****************************************************************************/ + +static int mdc800_endpoint_equals (struct usb_endpoint_descriptor *a,struct usb_endpoint_descriptor *b) +{ + return ( + ( a->bEndpointAddress == b->bEndpointAddress ) + && ( a->bmAttributes == b->bmAttributes ) + && ( a->wMaxPacketSize == b->wMaxPacketSize ) + ); +} + + +/* + * Checks whether the camera responds busy + */ +static int mdc800_isBusy (char* ch) +{ + int i=0; + while (i<8) + { + if (ch [i] != (char)0x99) + return 0; + i++; + } + return 1; +} + + +/* + * Checks whether the Camera is ready + */ +static int mdc800_isReady (char *ch) +{ + int i=0; + while (i<8) + { + if (ch [i] != (char)0xbb) + return 0; + i++; + } + return 1; +} + + + +/* + * USB IRQ Handler for InputLine + */ +static void mdc800_usb_irq (struct urb *urb) +{ + int data_received=0, wake_up; + unsigned char* b=urb->transfer_buffer; + struct mdc800_data* mdc800=urb->context; + struct device *dev = &mdc800->dev->dev; + int status = urb->status; + + if (status >= 0) { + if (mdc800_isBusy (b)) + { + if (!mdc800->camera_busy) + { + mdc800->camera_busy=1; + dev_dbg(dev, "gets busy\n"); + } + } + else + { + if (mdc800->camera_busy && mdc800_isReady (b)) + { + mdc800->camera_busy=0; + dev_dbg(dev, "gets ready\n"); + } + } + if (!(mdc800_isBusy (b) || mdc800_isReady (b))) + { + /* Store Data in camera_answer field */ + dev_dbg(dev, "%i %i %i %i %i %i %i %i \n",b[0],b[1],b[2],b[3],b[4],b[5],b[6],b[7]); + + memcpy (mdc800->camera_response,b,8); + data_received=1; + } + } + wake_up= ( mdc800->camera_request_ready > 0 ) + && + ( + ((mdc800->camera_request_ready == 1) && (!mdc800->camera_busy)) + || + ((mdc800->camera_request_ready == 2) && data_received) + || + ((mdc800->camera_request_ready == 3) && (mdc800->camera_busy)) + || + (status < 0) + ); + + if (wake_up) + { + mdc800->camera_request_ready=0; + mdc800->irq_woken=1; + wake_up (&mdc800->irq_wait); + } +} + + +/* + * Waits a while until the irq responds that camera is ready + * + * mode : 0: Wait for camera gets ready + * 1: Wait for receiving data + * 2: Wait for camera gets busy + * + * msec: Time to wait + */ +static int mdc800_usb_waitForIRQ (int mode, int msec) +{ + mdc800->camera_request_ready=1+mode; + + wait_event_timeout(mdc800->irq_wait, mdc800->irq_woken, msec*HZ/1000); + mdc800->irq_woken = 0; + + if (mdc800->camera_request_ready>0) + { + mdc800->camera_request_ready=0; + dev_err(&mdc800->dev->dev, "timeout waiting for camera.\n"); + return -1; + } + + if (mdc800->state == NOT_CONNECTED) + { + printk(KERN_WARNING "mdc800: Camera gets disconnected " + "during waiting for irq.\n"); + mdc800->camera_request_ready=0; + return -2; + } + + return 0; +} + + +/* + * The write_urb callback function + */ +static void mdc800_usb_write_notify (struct urb *urb) +{ + struct mdc800_data* mdc800=urb->context; + int status = urb->status; + + if (status != 0) + dev_err(&mdc800->dev->dev, + "writing command fails (status=%i)\n", status); + else + mdc800->state=READY; + mdc800->written = 1; + wake_up (&mdc800->write_wait); +} + + +/* + * The download_urb callback function + */ +static void mdc800_usb_download_notify (struct urb *urb) +{ + struct mdc800_data* mdc800=urb->context; + int status = urb->status; + + if (status == 0) { + /* Fill output buffer with these data */ + memcpy (mdc800->out, urb->transfer_buffer, 64); + mdc800->out_count=64; + mdc800->out_ptr=0; + mdc800->download_left-=64; + if (mdc800->download_left == 0) + { + mdc800->state=READY; + } + } else { + dev_err(&mdc800->dev->dev, + "request bytes fails (status:%i)\n", status); + } + mdc800->downloaded = 1; + wake_up (&mdc800->download_wait); +} + + +/*************************************************************************** + Probing for the Camera + ***************************************************************************/ + +static struct usb_driver mdc800_usb_driver; +static const struct file_operations mdc800_device_ops; +static struct usb_class_driver mdc800_class = { + .name = "mdc800%d", + .fops = &mdc800_device_ops, + .minor_base = MDC800_DEVICE_MINOR_BASE, +}; + + +/* + * Callback to search the Mustek MDC800 on the USB Bus + */ +static int mdc800_usb_probe (struct usb_interface *intf, + const struct usb_device_id *id) +{ + int i,j; + struct usb_host_interface *intf_desc; + struct usb_device *dev = interface_to_usbdev (intf); + int irq_interval=0; + int retval; + + dev_dbg(&intf->dev, "(%s) called.\n", __func__); + + + if (mdc800->dev != NULL) + { + dev_warn(&intf->dev, "only one Mustek MDC800 is supported.\n"); + return -ENODEV; + } + + if (dev->descriptor.bNumConfigurations != 1) + { + dev_err(&intf->dev, + "probe fails -> wrong Number of Configuration\n"); + return -ENODEV; + } + intf_desc = intf->cur_altsetting; + + if ( + ( intf_desc->desc.bInterfaceClass != 0xff ) + || ( intf_desc->desc.bInterfaceSubClass != 0 ) + || ( intf_desc->desc.bInterfaceProtocol != 0 ) + || ( intf_desc->desc.bNumEndpoints != 4) + ) + { + dev_err(&intf->dev, "probe fails -> wrong Interface\n"); + return -ENODEV; + } + + /* Check the Endpoints */ + for (i=0; i<4; i++) + { + mdc800->endpoint[i]=-1; + for (j=0; j<4; j++) + { + if (mdc800_endpoint_equals (&intf_desc->endpoint [j].desc,&mdc800_ed [i])) + { + mdc800->endpoint[i]=intf_desc->endpoint [j].desc.bEndpointAddress ; + if (i==1) + { + irq_interval=intf_desc->endpoint [j].desc.bInterval; + } + } + } + if (mdc800->endpoint[i] == -1) + { + dev_err(&intf->dev, "probe fails -> Wrong Endpoints.\n"); + return -ENODEV; + } + } + + + dev_info(&intf->dev, "Found Mustek MDC800 on USB.\n"); + + mutex_lock(&mdc800->io_lock); + + retval = usb_register_dev(intf, &mdc800_class); + if (retval) { + dev_err(&intf->dev, "Not able to get a minor for this device.\n"); + mutex_unlock(&mdc800->io_lock); + return -ENODEV; + } + + mdc800->dev=dev; + mdc800->open=0; + + /* Setup URB Structs */ + usb_fill_int_urb ( + mdc800->irq_urb, + mdc800->dev, + usb_rcvintpipe (mdc800->dev,mdc800->endpoint [1]), + mdc800->irq_urb_buffer, + 8, + mdc800_usb_irq, + mdc800, + irq_interval + ); + + usb_fill_bulk_urb ( + mdc800->write_urb, + mdc800->dev, + usb_sndbulkpipe (mdc800->dev, mdc800->endpoint[0]), + mdc800->write_urb_buffer, + 8, + mdc800_usb_write_notify, + mdc800 + ); + + usb_fill_bulk_urb ( + mdc800->download_urb, + mdc800->dev, + usb_rcvbulkpipe (mdc800->dev, mdc800->endpoint [3]), + mdc800->download_urb_buffer, + 64, + mdc800_usb_download_notify, + mdc800 + ); + + mdc800->state=READY; + + mutex_unlock(&mdc800->io_lock); + + usb_set_intfdata(intf, mdc800); + return 0; +} + + +/* + * Disconnect USB device (maybe the MDC800) + */ +static void mdc800_usb_disconnect (struct usb_interface *intf) +{ + struct mdc800_data* mdc800 = usb_get_intfdata(intf); + + dev_dbg(&intf->dev, "(%s) called\n", __func__); + + if (mdc800) { + if (mdc800->state == NOT_CONNECTED) + return; + + usb_deregister_dev(intf, &mdc800_class); + + /* must be under lock to make sure no URB + is submitted after usb_kill_urb() */ + mutex_lock(&mdc800->io_lock); + mdc800->state=NOT_CONNECTED; + + usb_kill_urb(mdc800->irq_urb); + usb_kill_urb(mdc800->write_urb); + usb_kill_urb(mdc800->download_urb); + mutex_unlock(&mdc800->io_lock); + + mdc800->dev = NULL; + usb_set_intfdata(intf, NULL); + } + dev_info(&intf->dev, "Mustek MDC800 disconnected from USB.\n"); +} + + +/*************************************************************************** + The Misc device Part (file_operations) +****************************************************************************/ + +/* + * This Function calc the Answersize for a command. + */ +static int mdc800_getAnswerSize (char command) +{ + switch ((unsigned char) command) + { + case 0x2a: + case 0x49: + case 0x51: + case 0x0d: + case 0x20: + case 0x07: + case 0x01: + case 0x25: + case 0x00: + return 8; + + case 0x05: + case 0x3e: + return mdc800->pic_len; + + case 0x09: + return 4096; + + default: + return 0; + } +} + + +/* + * Init the device: (1) alloc mem (2) Increase MOD Count .. + */ +static int mdc800_device_open (struct inode* inode, struct file *file) +{ + int retval=0; + int errn=0; + + mutex_lock(&mdc800->io_lock); + + if (mdc800->state == NOT_CONNECTED) + { + errn=-EBUSY; + goto error_out; + } + if (mdc800->open) + { + errn=-EBUSY; + goto error_out; + } + + mdc800->in_count=0; + mdc800->out_count=0; + mdc800->out_ptr=0; + mdc800->pic_index=0; + mdc800->pic_len=-1; + mdc800->download_left=0; + + mdc800->camera_busy=0; + mdc800->camera_request_ready=0; + + retval=0; + mdc800->irq_urb->dev = mdc800->dev; + retval = usb_submit_urb (mdc800->irq_urb, GFP_KERNEL); + if (retval) { + dev_err(&mdc800->dev->dev, + "request USB irq fails (submit_retval=%i).\n", retval); + errn = -EIO; + goto error_out; + } + + mdc800->open=1; + dev_dbg(&mdc800->dev->dev, "Mustek MDC800 device opened.\n"); + +error_out: + mutex_unlock(&mdc800->io_lock); + return errn; +} + + +/* + * Close the Camera and release Memory + */ +static int mdc800_device_release (struct inode* inode, struct file *file) +{ + int retval=0; + + mutex_lock(&mdc800->io_lock); + if (mdc800->open && (mdc800->state != NOT_CONNECTED)) + { + usb_kill_urb(mdc800->irq_urb); + usb_kill_urb(mdc800->write_urb); + usb_kill_urb(mdc800->download_urb); + mdc800->open=0; + } + else + { + retval=-EIO; + } + + mutex_unlock(&mdc800->io_lock); + return retval; +} + + +/* + * The Device read callback Function + */ +static ssize_t mdc800_device_read (struct file *file, char __user *buf, size_t len, loff_t *pos) +{ + size_t left=len, sts=len; /* single transfer size */ + char __user *ptr = buf; + int retval; + + mutex_lock(&mdc800->io_lock); + if (mdc800->state == NOT_CONNECTED) + { + mutex_unlock(&mdc800->io_lock); + return -EBUSY; + } + if (mdc800->state == WORKING) + { + printk(KERN_WARNING "mdc800: Illegal State \"working\"" + "reached during read ?!\n"); + mutex_unlock(&mdc800->io_lock); + return -EBUSY; + } + if (!mdc800->open) + { + mutex_unlock(&mdc800->io_lock); + return -EBUSY; + } + + while (left) + { + if (signal_pending (current)) + { + mutex_unlock(&mdc800->io_lock); + return -EINTR; + } + + sts=left > (mdc800->out_count-mdc800->out_ptr)?mdc800->out_count-mdc800->out_ptr:left; + + if (sts <= 0) + { + /* Too less Data in buffer */ + if (mdc800->state == DOWNLOAD) + { + mdc800->out_count=0; + mdc800->out_ptr=0; + + /* Download -> Request new bytes */ + mdc800->download_urb->dev = mdc800->dev; + retval = usb_submit_urb (mdc800->download_urb, GFP_KERNEL); + if (retval) { + dev_err(&mdc800->dev->dev, + "Can't submit download urb " + "(retval=%i)\n", retval); + mutex_unlock(&mdc800->io_lock); + return len-left; + } + wait_event_timeout(mdc800->download_wait, mdc800->downloaded, + TO_DOWNLOAD_GET_READY*HZ/1000); + mdc800->downloaded = 0; + if (mdc800->download_urb->status != 0) + { + dev_err(&mdc800->dev->dev, + "request download-bytes fails " + "(status=%i)\n", + mdc800->download_urb->status); + mutex_unlock(&mdc800->io_lock); + return len-left; + } + } + else + { + /* No more bytes -> that's an error*/ + mutex_unlock(&mdc800->io_lock); + return -EIO; + } + } + else + { + /* Copy Bytes */ + if (copy_to_user(ptr, &mdc800->out [mdc800->out_ptr], + sts)) { + mutex_unlock(&mdc800->io_lock); + return -EFAULT; + } + ptr+=sts; + left-=sts; + mdc800->out_ptr+=sts; + } + } + + mutex_unlock(&mdc800->io_lock); + return len-left; +} + + +/* + * The Device write callback Function + * If a 8Byte Command is received, it will be send to the camera. + * After this the driver initiates the request for the answer or + * just waits until the camera becomes ready. + */ +static ssize_t mdc800_device_write (struct file *file, const char __user *buf, size_t len, loff_t *pos) +{ + size_t i=0; + int retval; + + mutex_lock(&mdc800->io_lock); + if (mdc800->state != READY) + { + mutex_unlock(&mdc800->io_lock); + return -EBUSY; + } + if (!mdc800->open ) + { + mutex_unlock(&mdc800->io_lock); + return -EBUSY; + } + + while (iio_lock); + return -EINTR; + } + + if(get_user(c, buf+i)) + { + mutex_unlock(&mdc800->io_lock); + return -EFAULT; + } + + /* check for command start */ + if (c == 0x55) + { + mdc800->in_count=0; + mdc800->out_count=0; + mdc800->out_ptr=0; + mdc800->download_left=0; + } + + /* save command byte */ + if (mdc800->in_count < 8) + { + mdc800->in[mdc800->in_count] = c; + mdc800->in_count++; + } + else + { + mutex_unlock(&mdc800->io_lock); + return -EIO; + } + + /* Command Buffer full ? -> send it to camera */ + if (mdc800->in_count == 8) + { + int answersize; + + if (mdc800_usb_waitForIRQ (0,TO_GET_READY)) + { + dev_err(&mdc800->dev->dev, + "Camera didn't get ready.\n"); + mutex_unlock(&mdc800->io_lock); + return -EIO; + } + + answersize=mdc800_getAnswerSize (mdc800->in[1]); + + mdc800->state=WORKING; + memcpy (mdc800->write_urb->transfer_buffer, mdc800->in,8); + mdc800->write_urb->dev = mdc800->dev; + retval = usb_submit_urb (mdc800->write_urb, GFP_KERNEL); + if (retval) { + dev_err(&mdc800->dev->dev, + "submitting write urb fails " + "(retval=%i)\n", retval); + mutex_unlock(&mdc800->io_lock); + return -EIO; + } + wait_event_timeout(mdc800->write_wait, mdc800->written, TO_WRITE_GET_READY*HZ/1000); + mdc800->written = 0; + if (mdc800->state == WORKING) + { + usb_kill_urb(mdc800->write_urb); + mutex_unlock(&mdc800->io_lock); + return -EIO; + } + + switch ((unsigned char) mdc800->in[1]) + { + case 0x05: /* Download Image */ + case 0x3e: /* Take shot in Fine Mode (WCam Mode) */ + if (mdc800->pic_len < 0) + { + dev_err(&mdc800->dev->dev, + "call 0x07 before " + "0x05,0x3e\n"); + mdc800->state=READY; + mutex_unlock(&mdc800->io_lock); + return -EIO; + } + mdc800->pic_len=-1; + + case 0x09: /* Download Thumbnail */ + mdc800->download_left=answersize+64; + mdc800->state=DOWNLOAD; + mdc800_usb_waitForIRQ (0,TO_DOWNLOAD_GET_BUSY); + break; + + + default: + if (answersize) + { + + if (mdc800_usb_waitForIRQ (1,TO_READ_FROM_IRQ)) + { + dev_err(&mdc800->dev->dev, "requesting answer from irq fails\n"); + mutex_unlock(&mdc800->io_lock); + return -EIO; + } + + /* Write dummy data, (this is ugly but part of the USB Protocol */ + /* if you use endpoint 1 as bulk and not as irq) */ + memcpy (mdc800->out, mdc800->camera_response,8); + + /* This is the interpreted answer */ + memcpy (&mdc800->out[8], mdc800->camera_response,8); + + mdc800->out_ptr=0; + mdc800->out_count=16; + + /* Cache the Imagesize, if command was getImageSize */ + if (mdc800->in [1] == (char) 0x07) + { + mdc800->pic_len=(int) 65536*(unsigned char) mdc800->camera_response[0]+256*(unsigned char) mdc800->camera_response[1]+(unsigned char) mdc800->camera_response[2]; + + dev_dbg(&mdc800->dev->dev, "cached imagesize = %i\n", mdc800->pic_len); + } + + } + else + { + if (mdc800_usb_waitForIRQ (0,TO_DEFAULT_COMMAND)) + { + dev_err(&mdc800->dev->dev, "Command Timeout.\n"); + mutex_unlock(&mdc800->io_lock); + return -EIO; + } + } + mdc800->state=READY; + break; + } + } + i++; + } + mutex_unlock(&mdc800->io_lock); + return i; +} + + +/*************************************************************************** + Init and Cleanup this driver (Structs and types) +****************************************************************************/ + +/* File Operations of this drivers */ +static const struct file_operations mdc800_device_ops = +{ + .owner = THIS_MODULE, + .read = mdc800_device_read, + .write = mdc800_device_write, + .open = mdc800_device_open, + .release = mdc800_device_release, + .llseek = noop_llseek, +}; + + + +static const struct usb_device_id mdc800_table[] = { + { USB_DEVICE(MDC800_VENDOR_ID, MDC800_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, mdc800_table); +/* + * USB Driver Struct for this device + */ +static struct usb_driver mdc800_usb_driver = +{ + .name = "mdc800", + .probe = mdc800_usb_probe, + .disconnect = mdc800_usb_disconnect, + .id_table = mdc800_table +}; + + + +/************************************************************************ + Init and Cleanup this driver (Main Functions) +*************************************************************************/ + +static int __init usb_mdc800_init (void) +{ + int retval = -ENODEV; + /* Allocate Memory */ + mdc800=kzalloc (sizeof (struct mdc800_data), GFP_KERNEL); + if (!mdc800) + goto cleanup_on_fail; + + mdc800->dev = NULL; + mdc800->state=NOT_CONNECTED; + mutex_init (&mdc800->io_lock); + + init_waitqueue_head (&mdc800->irq_wait); + init_waitqueue_head (&mdc800->write_wait); + init_waitqueue_head (&mdc800->download_wait); + + mdc800->irq_woken = 0; + mdc800->downloaded = 0; + mdc800->written = 0; + + mdc800->irq_urb_buffer=kmalloc (8, GFP_KERNEL); + if (!mdc800->irq_urb_buffer) + goto cleanup_on_fail; + mdc800->write_urb_buffer=kmalloc (8, GFP_KERNEL); + if (!mdc800->write_urb_buffer) + goto cleanup_on_fail; + mdc800->download_urb_buffer=kmalloc (64, GFP_KERNEL); + if (!mdc800->download_urb_buffer) + goto cleanup_on_fail; + + mdc800->irq_urb=usb_alloc_urb (0, GFP_KERNEL); + if (!mdc800->irq_urb) + goto cleanup_on_fail; + mdc800->download_urb=usb_alloc_urb (0, GFP_KERNEL); + if (!mdc800->download_urb) + goto cleanup_on_fail; + mdc800->write_urb=usb_alloc_urb (0, GFP_KERNEL); + if (!mdc800->write_urb) + goto cleanup_on_fail; + + /* Register the driver */ + retval = usb_register(&mdc800_usb_driver); + if (retval) + goto cleanup_on_fail; + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" + DRIVER_DESC "\n"); + + return 0; + + /* Clean driver up, when something fails */ + +cleanup_on_fail: + + if (mdc800 != NULL) + { + printk(KERN_ERR "mdc800: can't alloc memory!\n"); + + kfree(mdc800->download_urb_buffer); + kfree(mdc800->write_urb_buffer); + kfree(mdc800->irq_urb_buffer); + + usb_free_urb(mdc800->write_urb); + usb_free_urb(mdc800->download_urb); + usb_free_urb(mdc800->irq_urb); + + kfree (mdc800); + } + mdc800 = NULL; + return retval; +} + + +static void __exit usb_mdc800_cleanup (void) +{ + usb_deregister (&mdc800_usb_driver); + + usb_free_urb (mdc800->irq_urb); + usb_free_urb (mdc800->download_urb); + usb_free_urb (mdc800->write_urb); + + kfree (mdc800->irq_urb_buffer); + kfree (mdc800->write_urb_buffer); + kfree (mdc800->download_urb_buffer); + + kfree (mdc800); + mdc800 = NULL; +} + +module_init (usb_mdc800_init); +module_exit (usb_mdc800_cleanup); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c new file mode 100644 index 00000000..9c0f8cab --- /dev/null +++ b/drivers/usb/image/microtek.c @@ -0,0 +1,816 @@ +/* Driver for Microtek Scanmaker X6 USB scanner, and possibly others. + * + * (C) Copyright 2000 John Fremlin + * (C) Copyright 2000 Oliver Neukum + * + * Parts shamelessly stolen from usb-storage and copyright by their + * authors. Thanks to Matt Dharm for giving us permission! + * + * This driver implements a SCSI host controller driver and a USB + * device driver. To avoid confusion, all the USB related stuff is + * prefixed by mts_usb_ and all the SCSI stuff by mts_scsi_. + * + * Microtek (www.microtek.com) did not release the specifications for + * their USB protocol to us, so we had to reverse engineer them. We + * don't know for which models they are valid. + * + * The X6 USB has three bulk endpoints, one output (0x1) down which + * commands and outgoing data are sent, and two input: 0x82 from which + * normal data is read from the scanner (in packets of maximum 32 + * bytes) and from which the status byte is read, and 0x83 from which + * the results of a scan (or preview) are read in up to 64 * 1024 byte + * chunks by the Windows driver. We don't know how much it is possible + * to read at a time from 0x83. + * + * It seems possible to read (with URB transfers) everything from 0x82 + * in one go, without bothering to read in 32 byte chunks. + * + * There seems to be an optimisation of a further READ implicit if + * you simply read from 0x83. + * + * Guessed protocol: + * + * Send raw SCSI command to EP 0x1 + * + * If there is data to receive: + * If the command was READ datatype=image: + * Read a lot of data from EP 0x83 + * Else: + * Read data from EP 0x82 + * Else: + * If there is data to transmit: + * Write it to EP 0x1 + * + * Read status byte from EP 0x82 + * + * References: + * + * The SCSI command set for the scanner is available from + * ftp://ftp.microtek.com/microtek/devpack/ + * + * Microtek NV sent us a more up to date version of the document. If + * you want it, just send mail. + * + * Status: + * + * Untested with multiple scanners. + * Untested on SMP. + * Untested on a bigendian machine. + * + * History: + * + * 20000417 starting history + * 20000417 fixed load oops + * 20000417 fixed unload oops + * 20000419 fixed READ IMAGE detection + * 20000424 started conversion to use URBs + * 20000502 handled short transfers as errors + * 20000513 rename and organisation of functions (john) + * 20000513 added IDs for all products supported by Windows driver (john) + * 20000514 Rewrote mts_scsi_queuecommand to use URBs (john) + * 20000514 Version 0.0.8j + * 20000514 Fix reporting of non-existent devices to SCSI layer (john) + * 20000514 Added MTS_DEBUG_INT (john) + * 20000514 Changed "usb-microtek" to "microtek" for consistency (john) + * 20000514 Stupid bug fixes (john) + * 20000514 Version 0.0.9j + * 20000515 Put transfer context and URB in mts_desc (john) + * 20000515 Added prelim turn off debugging support (john) + * 20000515 Version 0.0.10j + * 20000515 Fixed up URB allocation (clear URB on alloc) (john) + * 20000515 Version 0.0.11j + * 20000516 Removed unnecessary spinlock in mts_transfer_context (john) + * 20000516 Removed unnecessary up on instance lock in mts_remove_nolock (john) + * 20000516 Implemented (badly) scsi_abort (john) + * 20000516 Version 0.0.12j + * 20000517 Hopefully removed mts_remove_nolock quasideadlock (john) + * 20000517 Added mts_debug_dump to print ll USB info (john) + * 20000518 Tweaks and documentation updates (john) + * 20000518 Version 0.0.13j + * 20000518 Cleaned up abort handling (john) + * 20000523 Removed scsi_command and various scsi_..._resets (john) + * 20000523 Added unlink URB on scsi_abort, now OHCI supports it (john) + * 20000523 Fixed last tiresome compile warning (john) + * 20000523 Version 0.0.14j (though version 0.1 has come out?) + * 20000602 Added primitive reset + * 20000602 Version 0.2.0 + * 20000603 various cosmetic changes + * 20000603 Version 0.2.1 + * 20000620 minor cosmetic changes + * 20000620 Version 0.2.2 + * 20000822 Hopefully fixed deadlock in mts_remove_nolock() + * 20000822 Fixed minor race in mts_transfer_cleanup() + * 20000822 Fixed deadlock on submission error in queuecommand + * 20000822 Version 0.2.3 + * 20000913 Reduced module size if debugging is off + * 20000913 Version 0.2.4 + * 20010210 New abort logic + * 20010210 Version 0.3.0 + * 20010217 Merged scatter/gather + * 20010218 Version 0.4.0 + * 20010218 Cosmetic fixes + * 20010218 Version 0.4.1 + * 20010306 Abort while using scatter/gather + * 20010306 Version 0.4.2 + * 20010311 Remove all timeouts and tidy up generally (john) + * 20010320 check return value of scsi_register() + * 20010320 Version 0.4.3 + * 20010408 Identify version on module load. + * 20011003 Fix multiple requests + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../../scsi/scsi.h" +#include + +#include "microtek.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.4.3" +#define DRIVER_AUTHOR "John Fremlin , Oliver Neukum " +#define DRIVER_DESC "Microtek Scanmaker X6 USB scanner driver" + +/* Should we do debugging? */ + +//#define MTS_DO_DEBUG + +/* USB layer driver interface */ + +static int mts_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id); +static void mts_usb_disconnect(struct usb_interface *intf); + +static const struct usb_device_id mts_usb_ids[]; + +static struct usb_driver mts_usb_driver = { + .name = "microtekX6", + .probe = mts_usb_probe, + .disconnect = mts_usb_disconnect, + .id_table = mts_usb_ids, +}; + + +/* Internal driver stuff */ + +#define MTS_VERSION "0.4.3" +#define MTS_NAME "microtek usb (rev " MTS_VERSION "): " + +#define MTS_WARNING(x...) \ + printk( KERN_WARNING MTS_NAME x ) +#define MTS_ERROR(x...) \ + printk( KERN_ERR MTS_NAME x ) +#define MTS_INT_ERROR(x...) \ + MTS_ERROR(x) +#define MTS_MESSAGE(x...) \ + printk( KERN_INFO MTS_NAME x ) + +#if defined MTS_DO_DEBUG + +#define MTS_DEBUG(x...) \ + printk( KERN_DEBUG MTS_NAME x ) + +#define MTS_DEBUG_GOT_HERE() \ + MTS_DEBUG("got to %s:%d (%s)\n", __FILE__, (int)__LINE__, __func__ ) +#define MTS_DEBUG_INT() \ + do { MTS_DEBUG_GOT_HERE(); \ + MTS_DEBUG("transfer = 0x%x context = 0x%x\n",(int)transfer,(int)context ); \ + MTS_DEBUG("status = 0x%x data-length = 0x%x sent = 0x%x\n",transfer->status,(int)context->data_length, (int)transfer->actual_length ); \ + mts_debug_dump(context->instance);\ + } while(0) +#else + +#define MTS_NUL_STATEMENT do { } while(0) + +#define MTS_DEBUG(x...) MTS_NUL_STATEMENT +#define MTS_DEBUG_GOT_HERE() MTS_NUL_STATEMENT +#define MTS_DEBUG_INT() MTS_NUL_STATEMENT + +#endif + + + +#define MTS_INT_INIT()\ + struct mts_transfer_context* context = (struct mts_transfer_context*)transfer->context; \ + MTS_DEBUG_INT();\ + +#ifdef MTS_DO_DEBUG + +static inline void mts_debug_dump(struct mts_desc* desc) { + MTS_DEBUG("desc at 0x%x: toggle = %02x%02x\n", + (int)desc, + (int)desc->usb_dev->toggle[1],(int)desc->usb_dev->toggle[0] + ); + MTS_DEBUG("ep_out=%x ep_response=%x ep_image=%x\n", + usb_sndbulkpipe(desc->usb_dev,desc->ep_out), + usb_rcvbulkpipe(desc->usb_dev,desc->ep_response), + usb_rcvbulkpipe(desc->usb_dev,desc->ep_image) + ); +} + + +static inline void mts_show_command(struct scsi_cmnd *srb) +{ + char *what = NULL; + + switch (srb->cmnd[0]) { + case TEST_UNIT_READY: what = "TEST_UNIT_READY"; break; + case REZERO_UNIT: what = "REZERO_UNIT"; break; + case REQUEST_SENSE: what = "REQUEST_SENSE"; break; + case FORMAT_UNIT: what = "FORMAT_UNIT"; break; + case READ_BLOCK_LIMITS: what = "READ_BLOCK_LIMITS"; break; + case REASSIGN_BLOCKS: what = "REASSIGN_BLOCKS"; break; + case READ_6: what = "READ_6"; break; + case WRITE_6: what = "WRITE_6"; break; + case SEEK_6: what = "SEEK_6"; break; + case READ_REVERSE: what = "READ_REVERSE"; break; + case WRITE_FILEMARKS: what = "WRITE_FILEMARKS"; break; + case SPACE: what = "SPACE"; break; + case INQUIRY: what = "INQUIRY"; break; + case RECOVER_BUFFERED_DATA: what = "RECOVER_BUFFERED_DATA"; break; + case MODE_SELECT: what = "MODE_SELECT"; break; + case RESERVE: what = "RESERVE"; break; + case RELEASE: what = "RELEASE"; break; + case COPY: what = "COPY"; break; + case ERASE: what = "ERASE"; break; + case MODE_SENSE: what = "MODE_SENSE"; break; + case START_STOP: what = "START_STOP"; break; + case RECEIVE_DIAGNOSTIC: what = "RECEIVE_DIAGNOSTIC"; break; + case SEND_DIAGNOSTIC: what = "SEND_DIAGNOSTIC"; break; + case ALLOW_MEDIUM_REMOVAL: what = "ALLOW_MEDIUM_REMOVAL"; break; + case SET_WINDOW: what = "SET_WINDOW"; break; + case READ_CAPACITY: what = "READ_CAPACITY"; break; + case READ_10: what = "READ_10"; break; + case WRITE_10: what = "WRITE_10"; break; + case SEEK_10: what = "SEEK_10"; break; + case WRITE_VERIFY: what = "WRITE_VERIFY"; break; + case VERIFY: what = "VERIFY"; break; + case SEARCH_HIGH: what = "SEARCH_HIGH"; break; + case SEARCH_EQUAL: what = "SEARCH_EQUAL"; break; + case SEARCH_LOW: what = "SEARCH_LOW"; break; + case SET_LIMITS: what = "SET_LIMITS"; break; + case READ_POSITION: what = "READ_POSITION"; break; + case SYNCHRONIZE_CACHE: what = "SYNCHRONIZE_CACHE"; break; + case LOCK_UNLOCK_CACHE: what = "LOCK_UNLOCK_CACHE"; break; + case READ_DEFECT_DATA: what = "READ_DEFECT_DATA"; break; + case MEDIUM_SCAN: what = "MEDIUM_SCAN"; break; + case COMPARE: what = "COMPARE"; break; + case COPY_VERIFY: what = "COPY_VERIFY"; break; + case WRITE_BUFFER: what = "WRITE_BUFFER"; break; + case READ_BUFFER: what = "READ_BUFFER"; break; + case UPDATE_BLOCK: what = "UPDATE_BLOCK"; break; + case READ_LONG: what = "READ_LONG"; break; + case WRITE_LONG: what = "WRITE_LONG"; break; + case CHANGE_DEFINITION: what = "CHANGE_DEFINITION"; break; + case WRITE_SAME: what = "WRITE_SAME"; break; + case READ_TOC: what = "READ_TOC"; break; + case LOG_SELECT: what = "LOG_SELECT"; break; + case LOG_SENSE: what = "LOG_SENSE"; break; + case MODE_SELECT_10: what = "MODE_SELECT_10"; break; + case MODE_SENSE_10: what = "MODE_SENSE_10"; break; + case MOVE_MEDIUM: what = "MOVE_MEDIUM"; break; + case READ_12: what = "READ_12"; break; + case WRITE_12: what = "WRITE_12"; break; + case WRITE_VERIFY_12: what = "WRITE_VERIFY_12"; break; + case SEARCH_HIGH_12: what = "SEARCH_HIGH_12"; break; + case SEARCH_EQUAL_12: what = "SEARCH_EQUAL_12"; break; + case SEARCH_LOW_12: what = "SEARCH_LOW_12"; break; + case READ_ELEMENT_STATUS: what = "READ_ELEMENT_STATUS"; break; + case SEND_VOLUME_TAG: what = "SEND_VOLUME_TAG"; break; + case WRITE_LONG_2: what = "WRITE_LONG_2"; break; + default: + MTS_DEBUG("can't decode command\n"); + goto out; + break; + } + MTS_DEBUG( "Command %s (%d bytes)\n", what, srb->cmd_len); + + out: + MTS_DEBUG( " %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + srb->cmnd[0], srb->cmnd[1], srb->cmnd[2], srb->cmnd[3], srb->cmnd[4], srb->cmnd[5], + srb->cmnd[6], srb->cmnd[7], srb->cmnd[8], srb->cmnd[9]); +} + +#else + +static inline void mts_show_command(struct scsi_cmnd * dummy) +{ +} + +static inline void mts_debug_dump(struct mts_desc* dummy) +{ +} + +#endif + +static inline void mts_urb_abort(struct mts_desc* desc) { + MTS_DEBUG_GOT_HERE(); + mts_debug_dump(desc); + + usb_kill_urb( desc->urb ); +} + +static int mts_slave_alloc (struct scsi_device *s) +{ + s->inquiry_len = 0x24; + return 0; +} + +static int mts_slave_configure (struct scsi_device *s) +{ + blk_queue_dma_alignment(s->request_queue, (512 - 1)); + return 0; +} + +static int mts_scsi_abort(struct scsi_cmnd *srb) +{ + struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); + + MTS_DEBUG_GOT_HERE(); + + mts_urb_abort(desc); + + return FAILED; +} + +static int mts_scsi_host_reset(struct scsi_cmnd *srb) +{ + struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); + int result; + + MTS_DEBUG_GOT_HERE(); + mts_debug_dump(desc); + + result = usb_lock_device_for_reset(desc->usb_dev, desc->usb_intf); + if (result == 0) { + result = usb_reset_device(desc->usb_dev); + usb_unlock_device(desc->usb_dev); + } + return result ? FAILED : SUCCESS; +} + +static int +mts_scsi_queuecommand(struct Scsi_Host *shost, struct scsi_cmnd *srb); + +static void mts_transfer_cleanup( struct urb *transfer ); +static void mts_do_sg(struct urb * transfer); + +static inline +void mts_int_submit_urb (struct urb* transfer, + int pipe, + void* data, + unsigned length, + usb_complete_t callback ) +/* Interrupt context! */ + +/* Holding transfer->context->lock! */ +{ + int res; + + MTS_INT_INIT(); + + usb_fill_bulk_urb(transfer, + context->instance->usb_dev, + pipe, + data, + length, + callback, + context + ); + + res = usb_submit_urb( transfer, GFP_ATOMIC ); + if ( unlikely(res) ) { + MTS_INT_ERROR( "could not submit URB! Error was %d\n",(int)res ); + context->srb->result = DID_ERROR << 16; + mts_transfer_cleanup(transfer); + } +} + + +static void mts_transfer_cleanup( struct urb *transfer ) +/* Interrupt context! */ +{ + MTS_INT_INIT(); + + if ( likely(context->final_callback != NULL) ) + context->final_callback(context->srb); +} + +static void mts_transfer_done( struct urb *transfer ) +{ + MTS_INT_INIT(); + + context->srb->result &= MTS_SCSI_ERR_MASK; + context->srb->result |= (unsigned)(*context->scsi_status)<<1; + + mts_transfer_cleanup(transfer); +} + + +static void mts_get_status( struct urb *transfer ) +/* Interrupt context! */ +{ + MTS_INT_INIT(); + + mts_int_submit_urb(transfer, + usb_rcvbulkpipe(context->instance->usb_dev, + context->instance->ep_response), + context->scsi_status, + 1, + mts_transfer_done ); +} + +static void mts_data_done( struct urb* transfer ) +/* Interrupt context! */ +{ + int status = transfer->status; + MTS_INT_INIT(); + + if ( context->data_length != transfer->actual_length ) { + scsi_set_resid(context->srb, context->data_length - + transfer->actual_length); + } else if ( unlikely(status) ) { + context->srb->result = (status == -ENOENT ? DID_ABORT : DID_ERROR)<<16; + } + + mts_get_status(transfer); +} + + +static void mts_command_done( struct urb *transfer ) +/* Interrupt context! */ +{ + int status = transfer->status; + MTS_INT_INIT(); + + if ( unlikely(status) ) { + if (status == -ENOENT) { + /* We are being killed */ + MTS_DEBUG_GOT_HERE(); + context->srb->result = DID_ABORT<<16; + } else { + /* A genuine error has occurred */ + MTS_DEBUG_GOT_HERE(); + + context->srb->result = DID_ERROR<<16; + } + mts_transfer_cleanup(transfer); + + return; + } + + if (context->srb->cmnd[0] == REQUEST_SENSE) { + mts_int_submit_urb(transfer, + context->data_pipe, + context->srb->sense_buffer, + context->data_length, + mts_data_done); + } else { if ( context->data ) { + mts_int_submit_urb(transfer, + context->data_pipe, + context->data, + context->data_length, + scsi_sg_count(context->srb) > 1 ? + mts_do_sg : mts_data_done); + } else { + mts_get_status(transfer); + } + } +} + +static void mts_do_sg (struct urb* transfer) +{ + struct scatterlist * sg; + int status = transfer->status; + MTS_INT_INIT(); + + MTS_DEBUG("Processing fragment %d of %d\n", context->fragment, + scsi_sg_count(context->srb)); + + if (unlikely(status)) { + context->srb->result = (status == -ENOENT ? DID_ABORT : DID_ERROR)<<16; + mts_transfer_cleanup(transfer); + } + + sg = scsi_sglist(context->srb); + context->fragment++; + mts_int_submit_urb(transfer, + context->data_pipe, + sg_virt(&sg[context->fragment]), + sg[context->fragment].length, + context->fragment + 1 == scsi_sg_count(context->srb) ? + mts_data_done : mts_do_sg); +} + +static const u8 mts_read_image_sig[] = { 0x28, 00, 00, 00 }; +static const u8 mts_read_image_sig_len = 4; +static const unsigned char mts_direction[256/8] = { + 0x28, 0x81, 0x14, 0x14, 0x20, 0x01, 0x90, 0x77, + 0x0C, 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +#define MTS_DIRECTION_IS_IN(x) ((mts_direction[x>>3] >> (x & 7)) & 1) + +static void +mts_build_transfer_context(struct scsi_cmnd *srb, struct mts_desc* desc) +{ + int pipe; + struct scatterlist * sg; + + MTS_DEBUG_GOT_HERE(); + + desc->context.instance = desc; + desc->context.srb = srb; + desc->context.fragment = 0; + + if (!scsi_bufflen(srb)) { + desc->context.data = NULL; + desc->context.data_length = 0; + return; + } else { + sg = scsi_sglist(srb); + desc->context.data = sg_virt(&sg[0]); + desc->context.data_length = sg[0].length; + } + + + /* can't rely on srb->sc_data_direction */ + + /* Brutally ripped from usb-storage */ + + if ( !memcmp( srb->cmnd, mts_read_image_sig, mts_read_image_sig_len ) +) { pipe = usb_rcvbulkpipe(desc->usb_dev,desc->ep_image); + MTS_DEBUG( "transferring from desc->ep_image == %d\n", + (int)desc->ep_image ); + } else if ( MTS_DIRECTION_IS_IN(srb->cmnd[0]) ) { + pipe = usb_rcvbulkpipe(desc->usb_dev,desc->ep_response); + MTS_DEBUG( "transferring from desc->ep_response == %d\n", + (int)desc->ep_response); + } else { + MTS_DEBUG("transferring to desc->ep_out == %d\n", + (int)desc->ep_out); + pipe = usb_sndbulkpipe(desc->usb_dev,desc->ep_out); + } + desc->context.data_pipe = pipe; +} + + +static int +mts_scsi_queuecommand_lck(struct scsi_cmnd *srb, mts_scsi_cmnd_callback callback) +{ + struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); + int err = 0; + int res; + + MTS_DEBUG_GOT_HERE(); + mts_show_command(srb); + mts_debug_dump(desc); + + if ( srb->device->lun || srb->device->id || srb->device->channel ) { + + MTS_DEBUG("Command to LUN=%d ID=%d CHANNEL=%d from SCSI layer\n",(int)srb->device->lun,(int)srb->device->id, (int)srb->device->channel ); + + MTS_DEBUG("this device doesn't exist\n"); + + srb->result = DID_BAD_TARGET << 16; + + if(likely(callback != NULL)) + callback(srb); + + goto out; + } + + + usb_fill_bulk_urb(desc->urb, + desc->usb_dev, + usb_sndbulkpipe(desc->usb_dev,desc->ep_out), + srb->cmnd, + srb->cmd_len, + mts_command_done, + &desc->context + ); + + + mts_build_transfer_context( srb, desc ); + desc->context.final_callback = callback; + + /* here we need ATOMIC as we are called with the iolock */ + res=usb_submit_urb(desc->urb, GFP_ATOMIC); + + if(unlikely(res)){ + MTS_ERROR("error %d submitting URB\n",(int)res); + srb->result = DID_ERROR << 16; + + if(likely(callback != NULL)) + callback(srb); + + } +out: + return err; +} + +static DEF_SCSI_QCMD(mts_scsi_queuecommand) + +static struct scsi_host_template mts_scsi_host_template = { + .module = THIS_MODULE, + .name = "microtekX6", + .proc_name = "microtekX6", + .queuecommand = mts_scsi_queuecommand, + .eh_abort_handler = mts_scsi_abort, + .eh_host_reset_handler = mts_scsi_host_reset, + .sg_tablesize = SG_ALL, + .can_queue = 1, + .this_id = -1, + .cmd_per_lun = 1, + .use_clustering = 1, + .emulated = 1, + .slave_alloc = mts_slave_alloc, + .slave_configure = mts_slave_configure, + .max_sectors= 256, /* 128 K */ +}; + +/* The entries of microtek_table must correspond, line-by-line to + the entries of mts_supported_products[]. */ + +static const struct usb_device_id mts_usb_ids[] = +{ + { USB_DEVICE(0x4ce, 0x0300) }, + { USB_DEVICE(0x5da, 0x0094) }, + { USB_DEVICE(0x5da, 0x0099) }, + { USB_DEVICE(0x5da, 0x009a) }, + { USB_DEVICE(0x5da, 0x00a0) }, + { USB_DEVICE(0x5da, 0x00a3) }, + { USB_DEVICE(0x5da, 0x80a3) }, + { USB_DEVICE(0x5da, 0x80ac) }, + { USB_DEVICE(0x5da, 0x00b6) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, mts_usb_ids); + + +static int mts_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int i; + int ep_out = -1; + int ep_in_set[3]; /* this will break if we have more than three endpoints + which is why we check */ + int *ep_in_current = ep_in_set; + int err_retval = -ENOMEM; + + struct mts_desc * new_desc; + struct usb_device *dev = interface_to_usbdev (intf); + + /* the current altsetting on the interface we're probing */ + struct usb_host_interface *altsetting; + + MTS_DEBUG_GOT_HERE(); + MTS_DEBUG( "usb-device descriptor at %x\n", (int)dev ); + + MTS_DEBUG( "product id = 0x%x, vendor id = 0x%x\n", + le16_to_cpu(dev->descriptor.idProduct), + le16_to_cpu(dev->descriptor.idVendor) ); + + MTS_DEBUG_GOT_HERE(); + + /* the current altsetting on the interface we're probing */ + altsetting = intf->cur_altsetting; + + + /* Check if the config is sane */ + + if ( altsetting->desc.bNumEndpoints != MTS_EP_TOTAL ) { + MTS_WARNING( "expecting %d got %d endpoints! Bailing out.\n", + (int)MTS_EP_TOTAL, (int)altsetting->desc.bNumEndpoints ); + return -ENODEV; + } + + for( i = 0; i < altsetting->desc.bNumEndpoints; i++ ) { + if ((altsetting->endpoint[i].desc.bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) { + + MTS_WARNING( "can only deal with bulk endpoints; endpoint %d is not bulk.\n", + (int)altsetting->endpoint[i].desc.bEndpointAddress ); + } else { + if (altsetting->endpoint[i].desc.bEndpointAddress & + USB_DIR_IN) + *ep_in_current++ + = altsetting->endpoint[i].desc.bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + else { + if ( ep_out != -1 ) { + MTS_WARNING( "can only deal with one output endpoints. Bailing out." ); + return -ENODEV; + } + + ep_out = altsetting->endpoint[i].desc.bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + } + } + + } + + + if ( ep_out == -1 ) { + MTS_WARNING( "couldn't find an output bulk endpoint. Bailing out.\n" ); + return -ENODEV; + } + + + new_desc = kzalloc(sizeof(struct mts_desc), GFP_KERNEL); + if (!new_desc) + goto out; + + new_desc->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!new_desc->urb) + goto out_kfree; + + new_desc->context.scsi_status = kmalloc(1, GFP_KERNEL); + if (!new_desc->context.scsi_status) + goto out_free_urb; + + new_desc->usb_dev = dev; + new_desc->usb_intf = intf; + + /* endpoints */ + new_desc->ep_out = ep_out; + new_desc->ep_response = ep_in_set[0]; + new_desc->ep_image = ep_in_set[1]; + + if ( new_desc->ep_out != MTS_EP_OUT ) + MTS_WARNING( "will this work? Command EP is not usually %d\n", + (int)new_desc->ep_out ); + + if ( new_desc->ep_response != MTS_EP_RESPONSE ) + MTS_WARNING( "will this work? Response EP is not usually %d\n", + (int)new_desc->ep_response ); + + if ( new_desc->ep_image != MTS_EP_IMAGE ) + MTS_WARNING( "will this work? Image data EP is not usually %d\n", + (int)new_desc->ep_image ); + + new_desc->host = scsi_host_alloc(&mts_scsi_host_template, + sizeof(new_desc)); + if (!new_desc->host) + goto out_kfree2; + + new_desc->host->hostdata[0] = (unsigned long)new_desc; + if (scsi_add_host(new_desc->host, &dev->dev)) { + err_retval = -EIO; + goto out_host_put; + } + scsi_scan_host(new_desc->host); + + usb_set_intfdata(intf, new_desc); + return 0; + + out_host_put: + scsi_host_put(new_desc->host); + out_kfree2: + kfree(new_desc->context.scsi_status); + out_free_urb: + usb_free_urb(new_desc->urb); + out_kfree: + kfree(new_desc); + out: + return err_retval; +} + +static void mts_usb_disconnect (struct usb_interface *intf) +{ + struct mts_desc *desc = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + + usb_kill_urb(desc->urb); + scsi_remove_host(desc->host); + + scsi_host_put(desc->host); + usb_free_urb(desc->urb); + kfree(desc->context.scsi_status); + kfree(desc); +} + +module_usb_driver(mts_usb_driver); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/image/microtek.h b/drivers/usb/image/microtek.h new file mode 100644 index 00000000..ccce318f --- /dev/null +++ b/drivers/usb/image/microtek.h @@ -0,0 +1,54 @@ + /* + * Driver for Microtek Scanmaker X6 USB scanner and possibly others. + * + * (C) Copyright 2000 John Fremlin + * (C) Copyright 2000 Oliver Neukum + * + * See microtek.c for history + * + */ + +typedef void (*mts_scsi_cmnd_callback)(struct scsi_cmnd *); + + +struct mts_transfer_context +{ + struct mts_desc* instance; + mts_scsi_cmnd_callback final_callback; + struct scsi_cmnd *srb; + + void* data; + unsigned data_length; + int data_pipe; + int fragment; + + u8 *scsi_status; /* status returned from ep_response after command completion */ +}; + + +struct mts_desc { + struct mts_desc *next; + struct mts_desc *prev; + + struct usb_device *usb_dev; + struct usb_interface *usb_intf; + + /* Endpoint addresses */ + u8 ep_out; + u8 ep_response; + u8 ep_image; + + struct Scsi_Host * host; + + struct urb *urb; + struct mts_transfer_context context; +}; + + +#define MTS_EP_OUT 0x1 +#define MTS_EP_RESPONSE 0x2 +#define MTS_EP_IMAGE 0x3 +#define MTS_EP_TOTAL 0x3 + +#define MTS_SCSI_ERR_MASK ~0x3fu + diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig new file mode 100644 index 00000000..a51e7d6a --- /dev/null +++ b/drivers/usb/misc/Kconfig @@ -0,0 +1,237 @@ +# +# USB Miscellaneous driver configuration +# +comment "USB Miscellaneous drivers" + +config USB_EMI62 + tristate "EMI 6|2m USB Audio interface support" + ---help--- + This driver loads firmware to Emagic EMI 6|2m low latency USB + Audio and Midi interface. + + After firmware load the device is handled with standard linux + USB Audio driver. + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called audio. If you want to compile it as a + module, say M here and read . + +config USB_EMI26 + tristate "EMI 2|6 USB Audio interface support" + ---help--- + This driver loads firmware to Emagic EMI 2|6 low latency USB + Audio interface. + + After firmware load the device is handled with standard linux + USB Audio driver. + + To compile this driver as a module, choose M here: the + module will be called emi26. + +config USB_ADUTUX + tristate "ADU devices from Ontrak Control Systems" + help + Say Y if you want to use an ADU device from Ontrak Control + Systems. + + To compile this driver as a module, choose M here. The module + will be called adutux. + +config USB_SEVSEG + tristate "USB 7-Segment LED Display" + help + Say Y here if you have a USB 7-Segment Display by Delcom + + To compile this driver as a module, choose M here: the + module will be called usbsevseg. + +config USB_RIO500 + tristate "USB Diamond Rio500 support" + help + Say Y here if you want to connect a USB Rio500 mp3 player to your + computer's USB port. Please read + for more information. + + To compile this driver as a module, choose M here: the + module will be called rio500. + +config USB_LEGOTOWER + tristate "USB Lego Infrared Tower support" + help + Say Y here if you want to connect a USB Lego Infrared Tower to your + computer's USB port. + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called legousbtower. If you want to compile it as + a module, say M here and read + . + +config USB_LCD + tristate "USB LCD driver support" + help + Say Y here if you want to connect an USBLCD to your computer's + USB port. The USBLCD is a small USB interface board for + alphanumeric LCD modules. See for more + information. + + To compile this driver as a module, choose M here: the + module will be called usblcd. + +config USB_LED + tristate "USB LED driver support" + help + Say Y here if you want to connect an USBLED device to your + computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called usbled. + +config USB_CYPRESS_CY7C63 + tristate "Cypress CY7C63xxx USB driver support" + help + Say Y here if you want to connect a Cypress CY7C63xxx + micro controller to your computer's USB port. Currently this + driver supports the pre-programmed devices (incl. firmware) + by AK Modul-Bus Computer GmbH. + + Please see: http://www.ak-modul-bus.de/stat/mikrocontroller.html + + To compile this driver as a module, choose M here: the + module will be called cypress_cy7c63. + +config USB_CYTHERM + tristate "Cypress USB thermometer driver support" + help + Say Y here if you want to connect a Cypress USB thermometer + device to your computer's USB port. This device is also known + as the Cypress USB Starter kit or demo board. The Elektor + magazine published a modified version of this device in issue + #291. + + To compile this driver as a module, choose M here: the + module will be called cytherm. + +config USB_IDMOUSE + tristate "Siemens ID USB Mouse Fingerprint sensor support" + help + Say Y here if you want to use the fingerprint sensor on + the Siemens ID Mouse. There is also a Siemens ID Mouse + _Professional_, which has not been tested with this driver, + but uses the same sensor and may therefore work. + + This driver creates an entry "/dev/idmouseX" or "/dev/usb/idmouseX", + which can be used by, e.g.,"cat /dev/idmouse0 > fingerprint.pnm". + + See also . + +config USB_FTDI_ELAN + tristate "Elan PCMCIA CardBus Adapter USB Client" + default M + help + ELAN's Uxxx series of adapters are USB to PCMCIA CardBus adapters. + Currently only the U132 adapter is available. + + The U132 is specifically designed for CardBus PC cards that contain + an OHCI host controller. Typical PC cards are the Orange Mobile 3G + Option GlobeTrotter Fusion card. The U132 adapter will *NOT* work + with PC cards that do not contain an OHCI controller. To use a U132 + adapter you will need this "ftdi-elan" module as well as the "u132-hcd" + module which is a USB host controller driver that talks to the OHCI + controller within CardBus card that are inserted in the U132 adapter. + + This driver has been tested with a CardBus OHCI USB adapter, and + worked with a USB PEN Drive inserted into the first USB port of + the PCCARD. A rather pointless thing to do, but useful for testing. + + See also the USB_U132_HCD entry "Elan U132 Adapter Host Controller" + + It is safe to say M here. + +config USB_APPLEDISPLAY + tristate "Apple Cinema Display support" + select BACKLIGHT_LCD_SUPPORT + select BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to control the backlight of Apple Cinema + Displays over USB. This driver provides a sysfs interface. + +source "drivers/usb/misc/sisusbvga/Kconfig" + +config USB_LD + tristate "USB LD driver" + help + This driver is for generic USB devices that use interrupt transfers, + like LD Didactic's USB devices. + + To compile this driver as a module, choose M here: the + module will be called ldusb. + +config USB_TRANCEVIBRATOR + tristate "PlayStation 2 Trance Vibrator driver support" + help + Say Y here if you want to connect a PlayStation 2 Trance Vibrator + device to your computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called trancevibrator. + +config USB_IOWARRIOR + tristate "IO Warrior driver support" + help + Say Y here if you want to support the IO Warrior devices from Code + Mercenaries. This includes support for the following devices: + IO Warrior 40 + IO Warrior 24 + IO Warrior 56 + IO Warrior 24 Power Vampire + + To compile this driver as a module, choose M here: the + module will be called iowarrior. + +config USB_TEST + tristate "USB testing driver" + help + This driver is for testing host controller software. It is used + with specialized device firmware for regression and stress testing, + to help prevent problems from cropping up with "real" drivers. + + See for more information, + including sample test device firmware and "how to use it". + +config USB_ISIGHTFW + tristate "iSight firmware loading support" + select FW_LOADER + help + This driver loads firmware for USB Apple iSight cameras, allowing + them to be driven by the USB video class driver available at + http://linux-uvc.berlios.de + + The firmware for this driver must be extracted from the MacOS + driver beforehand. Tools for doing so are available at + http://bersace03.free.fr + +config USB_YUREX + tristate "USB YUREX driver support" + help + Say Y here if you want to connect a YUREX to your computer's + USB port. The YUREX is a leg-shakes sensor. See + for further information. + This driver supports read/write of leg-shakes counter and + fasync for the counter update via a device file /dev/yurex*. + + To compile this driver as a module, choose M here: the + module will be called yurex. + +config USB_EZUSB_FX2 + tristate "Functions for loading firmware on EZUSB chips" + help + Say Y here if you need EZUSB device support. + (Cypress FX/FX2/FX2LP microcontrollers) + +config USB_HSIC_USB3503 + tristate "USB3503 HSIC to USB20 Driver" + depends on I2C + help + This option enables support for SMSC USB3503 HSIC to USB 2.0 Driver. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile new file mode 100644 index 00000000..3e1bd70b --- /dev/null +++ b/drivers/usb/misc/Makefile @@ -0,0 +1,31 @@ +# +# Makefile for the rest of the USB drivers +# (the ones that don't fit into any other categories) +# + +ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG + +obj-$(CONFIG_USB_ADUTUX) += adutux.o +obj-$(CONFIG_USB_APPLEDISPLAY) += appledisplay.o +obj-$(CONFIG_USB_CYPRESS_CY7C63) += cypress_cy7c63.o +obj-$(CONFIG_USB_CYTHERM) += cytherm.o +obj-$(CONFIG_USB_EMI26) += emi26.o +obj-$(CONFIG_USB_EMI62) += emi62.o +obj-$(CONFIG_USB_EZUSB_FX2) += ezusb.o +obj-$(CONFIG_USB_FTDI_ELAN) += ftdi-elan.o +obj-$(CONFIG_USB_IDMOUSE) += idmouse.o +obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o +obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o +obj-$(CONFIG_USB_LCD) += usblcd.o +obj-$(CONFIG_USB_LD) += ldusb.o +obj-$(CONFIG_USB_LED) += usbled.o +obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o +obj-$(CONFIG_USB_RIO500) += rio500.o +obj-$(CONFIG_USB_TEST) += usbtest.o +obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o +obj-$(CONFIG_USB_USS720) += uss720.o +obj-$(CONFIG_USB_SEVSEG) += usbsevseg.o +obj-$(CONFIG_USB_YUREX) += yurex.o +obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o + +obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c new file mode 100644 index 00000000..284b8546 --- /dev/null +++ b/drivers/usb/misc/adutux.c @@ -0,0 +1,892 @@ +/* + * adutux - driver for ADU devices from Ontrak Control Systems + * This is an experimental driver. Use at your own risk. + * This driver is not supported by Ontrak Control Systems. + * + * Copyright (c) 2003 John Homppi (SCO, leave this notice here) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * derived from the Lego USB Tower driver 0.56: + * Copyright (c) 2003 David Glance + * 2001 Juergen Stuber + * that was derived from USB Skeleton driver - 0.5 + * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_USB_DEBUG +static int debug = 5; +#else +static int debug = 1; +#endif + +/* Use our own dbg macro */ +#undef dbg +#define dbg(lvl, format, arg...) \ +do { \ + if (debug >= lvl) \ + printk(KERN_DEBUG "%s: " format "\n", __FILE__, ##arg); \ +} while (0) + + +/* Version Information */ +#define DRIVER_VERSION "v0.0.13" +#define DRIVER_AUTHOR "John Homppi" +#define DRIVER_DESC "adutux (see www.ontrak.net)" + +/* Module parameters */ +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +/* Define these values to match your device */ +#define ADU_VENDOR_ID 0x0a07 +#define ADU_PRODUCT_ID 0x0064 + +/* table of devices that work with this driver */ +static const struct usb_device_id device_table[] = { + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID) }, /* ADU100 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+20) }, /* ADU120 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+30) }, /* ADU130 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+100) }, /* ADU200 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+108) }, /* ADU208 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+118) }, /* ADU218 */ + { }/* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, device_table); + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define ADU_MINOR_BASE 0 +#else +#define ADU_MINOR_BASE 67 +#endif + +/* we can have up to this number of device plugged in at once */ +#define MAX_DEVICES 16 + +#define COMMAND_TIMEOUT (2*HZ) /* 60 second timeout for a command */ + +/* + * The locking scheme is a vanilla 3-lock: + * adu_device.buflock: A spinlock, covers what IRQs touch. + * adutux_mutex: A Static lock to cover open_count. It would also cover + * any globals, but we don't have them in 2.6. + * adu_device.mtx: A mutex to hold across sleepers like copy_from_user. + * It covers all of adu_device, except the open_count + * and what .buflock covers. + */ + +/* Structure to hold all of our device specific stuff */ +struct adu_device { + struct mutex mtx; + struct usb_device* udev; /* save off the usb device pointer */ + struct usb_interface* interface; + unsigned int minor; /* the starting minor number for this device */ + char serial_number[8]; + + int open_count; /* number of times this port has been opened */ + + char* read_buffer_primary; + int read_buffer_length; + char* read_buffer_secondary; + int secondary_head; + int secondary_tail; + spinlock_t buflock; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + char* interrupt_in_buffer; + struct usb_endpoint_descriptor* interrupt_in_endpoint; + struct urb* interrupt_in_urb; + int read_urb_finished; + + char* interrupt_out_buffer; + struct usb_endpoint_descriptor* interrupt_out_endpoint; + struct urb* interrupt_out_urb; + int out_urb_finished; +}; + +static DEFINE_MUTEX(adutux_mutex); + +static struct usb_driver adu_driver; + +static void adu_debug_data(int level, const char *function, int size, + const unsigned char *data) +{ + int i; + + if (debug < level) + return; + + printk(KERN_DEBUG "%s: %s - length = %d, data = ", + __FILE__, function, size); + for (i = 0; i < size; ++i) + printk("%.2x ", data[i]); + printk("\n"); +} + +/** + * adu_abort_transfers + * aborts transfers and frees associated data structures + */ +static void adu_abort_transfers(struct adu_device *dev) +{ + unsigned long flags; + + dbg(2," %s : enter", __func__); + + if (dev->udev == NULL) { + dbg(1," %s : udev is null", __func__); + goto exit; + } + + /* shutdown transfer */ + + /* XXX Anchor these instead */ + spin_lock_irqsave(&dev->buflock, flags); + if (!dev->read_urb_finished) { + spin_unlock_irqrestore(&dev->buflock, flags); + usb_kill_urb(dev->interrupt_in_urb); + } else + spin_unlock_irqrestore(&dev->buflock, flags); + + spin_lock_irqsave(&dev->buflock, flags); + if (!dev->out_urb_finished) { + spin_unlock_irqrestore(&dev->buflock, flags); + usb_kill_urb(dev->interrupt_out_urb); + } else + spin_unlock_irqrestore(&dev->buflock, flags); + +exit: + dbg(2," %s : leave", __func__); +} + +static void adu_delete(struct adu_device *dev) +{ + dbg(2, "%s enter", __func__); + + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->read_buffer_primary); + kfree(dev->read_buffer_secondary); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + kfree(dev); + + dbg(2, "%s : leave", __func__); +} + +static void adu_interrupt_in_callback(struct urb *urb) +{ + struct adu_device *dev = urb->context; + int status = urb->status; + + dbg(4," %s : enter, status %d", __func__, status); + adu_debug_data(5, __func__, urb->actual_length, + urb->transfer_buffer); + + spin_lock(&dev->buflock); + + if (status != 0) { + if ((status != -ENOENT) && (status != -ECONNRESET) && + (status != -ESHUTDOWN)) { + dbg(1," %s : nonzero status received: %d", + __func__, status); + } + goto exit; + } + + if (urb->actual_length > 0 && dev->interrupt_in_buffer[0] != 0x00) { + if (dev->read_buffer_length < + (4 * usb_endpoint_maxp(dev->interrupt_in_endpoint)) - + (urb->actual_length)) { + memcpy (dev->read_buffer_primary + + dev->read_buffer_length, + dev->interrupt_in_buffer, urb->actual_length); + + dev->read_buffer_length += urb->actual_length; + dbg(2," %s reading %d ", __func__, + urb->actual_length); + } else { + dbg(1," %s : read_buffer overflow", __func__); + } + } + +exit: + dev->read_urb_finished = 1; + spin_unlock(&dev->buflock); + /* always wake up so we recover from errors */ + wake_up_interruptible(&dev->read_wait); + adu_debug_data(5, __func__, urb->actual_length, + urb->transfer_buffer); + dbg(4," %s : leave, status %d", __func__, status); +} + +static void adu_interrupt_out_callback(struct urb *urb) +{ + struct adu_device *dev = urb->context; + int status = urb->status; + + dbg(4," %s : enter, status %d", __func__, status); + adu_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer); + + if (status != 0) { + if ((status != -ENOENT) && + (status != -ECONNRESET)) { + dbg(1, " %s :nonzero status received: %d", + __func__, status); + } + goto exit; + } + + spin_lock(&dev->buflock); + dev->out_urb_finished = 1; + wake_up(&dev->write_wait); + spin_unlock(&dev->buflock); +exit: + + adu_debug_data(5, __func__, urb->actual_length, + urb->transfer_buffer); + dbg(4," %s : leave, status %d", __func__, status); +} + +static int adu_open(struct inode *inode, struct file *file) +{ + struct adu_device *dev = NULL; + struct usb_interface *interface; + int subminor; + int retval; + + dbg(2,"%s : enter", __func__); + + subminor = iminor(inode); + + if ((retval = mutex_lock_interruptible(&adutux_mutex))) { + dbg(2, "%s : mutex lock failed", __func__); + goto exit_no_lock; + } + + interface = usb_find_interface(&adu_driver, subminor); + if (!interface) { + printk(KERN_ERR "adutux: %s - error, can't find device for " + "minor %d\n", __func__, subminor); + retval = -ENODEV; + goto exit_no_device; + } + + dev = usb_get_intfdata(interface); + if (!dev || !dev->udev) { + retval = -ENODEV; + goto exit_no_device; + } + + /* check that nobody else is using the device */ + if (dev->open_count) { + retval = -EBUSY; + goto exit_no_device; + } + + ++dev->open_count; + dbg(2,"%s : open count %d", __func__, dev->open_count); + + /* save device in the file's private structure */ + file->private_data = dev; + + /* initialize in direction */ + dev->read_buffer_length = 0; + + /* fixup first read by having urb waiting for it */ + usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, + usb_rcvintpipe(dev->udev, + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + usb_endpoint_maxp(dev->interrupt_in_endpoint), + adu_interrupt_in_callback, dev, + dev->interrupt_in_endpoint->bInterval); + dev->read_urb_finished = 0; + if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL)) + dev->read_urb_finished = 1; + /* we ignore failure */ + /* end of fixup for first read */ + + /* initialize out direction */ + dev->out_urb_finished = 1; + + retval = 0; + +exit_no_device: + mutex_unlock(&adutux_mutex); +exit_no_lock: + dbg(2,"%s : leave, return value %d ", __func__, retval); + return retval; +} + +static void adu_release_internal(struct adu_device *dev) +{ + dbg(2," %s : enter", __func__); + + /* decrement our usage count for the device */ + --dev->open_count; + dbg(2," %s : open count %d", __func__, dev->open_count); + if (dev->open_count <= 0) { + adu_abort_transfers(dev); + dev->open_count = 0; + } + + dbg(2," %s : leave", __func__); +} + +static int adu_release(struct inode *inode, struct file *file) +{ + struct adu_device *dev; + int retval = 0; + + dbg(2," %s : enter", __func__); + + if (file == NULL) { + dbg(1," %s : file is NULL", __func__); + retval = -ENODEV; + goto exit; + } + + dev = file->private_data; + if (dev == NULL) { + dbg(1," %s : object is NULL", __func__); + retval = -ENODEV; + goto exit; + } + + mutex_lock(&adutux_mutex); /* not interruptible */ + + if (dev->open_count <= 0) { + dbg(1," %s : device not opened", __func__); + retval = -ENODEV; + goto unlock; + } + + adu_release_internal(dev); + if (dev->udev == NULL) { + /* the device was unplugged before the file was released */ + if (!dev->open_count) /* ... and we're the last user */ + adu_delete(dev); + } +unlock: + mutex_unlock(&adutux_mutex); +exit: + dbg(2," %s : leave, return value %d", __func__, retval); + return retval; +} + +static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, + loff_t *ppos) +{ + struct adu_device *dev; + size_t bytes_read = 0; + size_t bytes_to_read = count; + int i; + int retval = 0; + int timeout = 0; + int should_submit = 0; + unsigned long flags; + DECLARE_WAITQUEUE(wait, current); + + dbg(2," %s : enter, count = %Zd, file=%p", __func__, count, file); + + dev = file->private_data; + dbg(2," %s : dev=%p", __func__, dev); + + if (mutex_lock_interruptible(&dev->mtx)) + return -ERESTARTSYS; + + /* verify that the device wasn't unplugged */ + if (dev->udev == NULL) { + retval = -ENODEV; + printk(KERN_ERR "adutux: No device or device unplugged %d\n", + retval); + goto exit; + } + + /* verify that some data was requested */ + if (count == 0) { + dbg(1," %s : read request of 0 bytes", __func__); + goto exit; + } + + timeout = COMMAND_TIMEOUT; + dbg(2," %s : about to start looping", __func__); + while (bytes_to_read) { + int data_in_secondary = dev->secondary_tail - dev->secondary_head; + dbg(2," %s : while, data_in_secondary=%d, status=%d", + __func__, data_in_secondary, + dev->interrupt_in_urb->status); + + if (data_in_secondary) { + /* drain secondary buffer */ + int amount = bytes_to_read < data_in_secondary ? bytes_to_read : data_in_secondary; + i = copy_to_user(buffer, dev->read_buffer_secondary+dev->secondary_head, amount); + if (i) { + retval = -EFAULT; + goto exit; + } + dev->secondary_head += (amount - i); + bytes_read += (amount - i); + bytes_to_read -= (amount - i); + if (i) { + retval = bytes_read ? bytes_read : -EFAULT; + goto exit; + } + } else { + /* we check the primary buffer */ + spin_lock_irqsave (&dev->buflock, flags); + if (dev->read_buffer_length) { + /* we secure access to the primary */ + char *tmp; + dbg(2," %s : swap, read_buffer_length = %d", + __func__, dev->read_buffer_length); + tmp = dev->read_buffer_secondary; + dev->read_buffer_secondary = dev->read_buffer_primary; + dev->read_buffer_primary = tmp; + dev->secondary_head = 0; + dev->secondary_tail = dev->read_buffer_length; + dev->read_buffer_length = 0; + spin_unlock_irqrestore(&dev->buflock, flags); + /* we have a free buffer so use it */ + should_submit = 1; + } else { + /* even the primary was empty - we may need to do IO */ + if (!dev->read_urb_finished) { + /* somebody is doing IO */ + spin_unlock_irqrestore(&dev->buflock, flags); + dbg(2," %s : submitted already", __func__); + } else { + /* we must initiate input */ + dbg(2," %s : initiate input", __func__); + dev->read_urb_finished = 0; + spin_unlock_irqrestore(&dev->buflock, flags); + + usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, + usb_rcvintpipe(dev->udev, + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + usb_endpoint_maxp(dev->interrupt_in_endpoint), + adu_interrupt_in_callback, + dev, + dev->interrupt_in_endpoint->bInterval); + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev->read_urb_finished = 1; + if (retval == -ENOMEM) { + retval = bytes_read ? bytes_read : -ENOMEM; + } + dbg(2," %s : submit failed", __func__); + goto exit; + } + } + + /* we wait for I/O to complete */ + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&dev->read_wait, &wait); + spin_lock_irqsave(&dev->buflock, flags); + if (!dev->read_urb_finished) { + spin_unlock_irqrestore(&dev->buflock, flags); + timeout = schedule_timeout(COMMAND_TIMEOUT); + } else { + spin_unlock_irqrestore(&dev->buflock, flags); + set_current_state(TASK_RUNNING); + } + remove_wait_queue(&dev->read_wait, &wait); + + if (timeout <= 0) { + dbg(2," %s : timeout", __func__); + retval = bytes_read ? bytes_read : -ETIMEDOUT; + goto exit; + } + + if (signal_pending(current)) { + dbg(2," %s : signal pending", __func__); + retval = bytes_read ? bytes_read : -EINTR; + goto exit; + } + } + } + } + + retval = bytes_read; + /* if the primary buffer is empty then use it */ + spin_lock_irqsave(&dev->buflock, flags); + if (should_submit && dev->read_urb_finished) { + dev->read_urb_finished = 0; + spin_unlock_irqrestore(&dev->buflock, flags); + usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, + usb_rcvintpipe(dev->udev, + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + usb_endpoint_maxp(dev->interrupt_in_endpoint), + adu_interrupt_in_callback, + dev, + dev->interrupt_in_endpoint->bInterval); + if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL) != 0) + dev->read_urb_finished = 1; + /* we ignore failure */ + } else { + spin_unlock_irqrestore(&dev->buflock, flags); + } + +exit: + /* unlock the device */ + mutex_unlock(&dev->mtx); + + dbg(2," %s : leave, return value %d", __func__, retval); + return retval; +} + +static ssize_t adu_write(struct file *file, const __user char *buffer, + size_t count, loff_t *ppos) +{ + DECLARE_WAITQUEUE(waita, current); + struct adu_device *dev; + size_t bytes_written = 0; + size_t bytes_to_write; + size_t buffer_size; + unsigned long flags; + int retval; + + dbg(2," %s : enter, count = %Zd", __func__, count); + + dev = file->private_data; + + retval = mutex_lock_interruptible(&dev->mtx); + if (retval) + goto exit_nolock; + + /* verify that the device wasn't unplugged */ + if (dev->udev == NULL) { + retval = -ENODEV; + printk(KERN_ERR "adutux: No device or device unplugged %d\n", + retval); + goto exit; + } + + /* verify that we actually have some data to write */ + if (count == 0) { + dbg(1," %s : write request of 0 bytes", __func__); + goto exit; + } + + while (count > 0) { + add_wait_queue(&dev->write_wait, &waita); + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&dev->buflock, flags); + if (!dev->out_urb_finished) { + spin_unlock_irqrestore(&dev->buflock, flags); + + mutex_unlock(&dev->mtx); + if (signal_pending(current)) { + dbg(1," %s : interrupted", __func__); + set_current_state(TASK_RUNNING); + retval = -EINTR; + goto exit_onqueue; + } + if (schedule_timeout(COMMAND_TIMEOUT) == 0) { + dbg(1, "%s - command timed out.", __func__); + retval = -ETIMEDOUT; + goto exit_onqueue; + } + remove_wait_queue(&dev->write_wait, &waita); + retval = mutex_lock_interruptible(&dev->mtx); + if (retval) { + retval = bytes_written ? bytes_written : retval; + goto exit_nolock; + } + + dbg(4," %s : in progress, count = %Zd", __func__, count); + } else { + spin_unlock_irqrestore(&dev->buflock, flags); + set_current_state(TASK_RUNNING); + remove_wait_queue(&dev->write_wait, &waita); + dbg(4," %s : sending, count = %Zd", __func__, count); + + /* write the data into interrupt_out_buffer from userspace */ + buffer_size = usb_endpoint_maxp(dev->interrupt_out_endpoint); + bytes_to_write = count > buffer_size ? buffer_size : count; + dbg(4," %s : buffer_size = %Zd, count = %Zd, bytes_to_write = %Zd", + __func__, buffer_size, count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write) != 0) { + retval = -EFAULT; + goto exit; + } + + /* send off the urb */ + usb_fill_int_urb( + dev->interrupt_out_urb, + dev->udev, + usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress), + dev->interrupt_out_buffer, + bytes_to_write, + adu_interrupt_out_callback, + dev, + dev->interrupt_out_endpoint->bInterval); + dev->interrupt_out_urb->actual_length = bytes_to_write; + dev->out_urb_finished = 0; + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval < 0) { + dev->out_urb_finished = 1; + dev_err(&dev->udev->dev, "Couldn't submit " + "interrupt_out_urb %d\n", retval); + goto exit; + } + + buffer += bytes_to_write; + count -= bytes_to_write; + + bytes_written += bytes_to_write; + } + } + mutex_unlock(&dev->mtx); + return bytes_written; + +exit: + mutex_unlock(&dev->mtx); +exit_nolock: + dbg(2," %s : leave, return value %d", __func__, retval); + return retval; + +exit_onqueue: + remove_wait_queue(&dev->write_wait, &waita); + return retval; +} + +/* file operations needed when we register this driver */ +static const struct file_operations adu_fops = { + .owner = THIS_MODULE, + .read = adu_read, + .write = adu_write, + .open = adu_open, + .release = adu_release, + .llseek = noop_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with devfs and the driver core + */ +static struct usb_class_driver adu_class = { + .name = "usb/adutux%d", + .fops = &adu_fops, + .minor_base = ADU_MINOR_BASE, +}; + +/** + * adu_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int adu_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct adu_device *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int retval = -ENODEV; + int in_end_size; + int out_end_size; + int i; + + dbg(2," %s : enter", __func__); + + if (udev == NULL) { + dev_err(&interface->dev, "udev is NULL.\n"); + goto exit; + } + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct adu_device), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + retval = -ENOMEM; + goto exit; + } + + mutex_init(&dev->mtx); + spin_lock_init(&dev->buflock); + dev->udev = udev; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + iface_desc = &interface->altsetting[0]; + + /* set up the endpoint information */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) + dev->interrupt_in_endpoint = endpoint; + + if (usb_endpoint_is_int_out(endpoint)) + dev->interrupt_out_endpoint = endpoint; + } + if (dev->interrupt_in_endpoint == NULL) { + dev_err(&interface->dev, "interrupt in endpoint not found\n"); + goto error; + } + if (dev->interrupt_out_endpoint == NULL) { + dev_err(&interface->dev, "interrupt out endpoint not found\n"); + goto error; + } + + in_end_size = usb_endpoint_maxp(dev->interrupt_in_endpoint); + out_end_size = usb_endpoint_maxp(dev->interrupt_out_endpoint); + + dev->read_buffer_primary = kmalloc((4 * in_end_size), GFP_KERNEL); + if (!dev->read_buffer_primary) { + dev_err(&interface->dev, "Couldn't allocate read_buffer_primary\n"); + retval = -ENOMEM; + goto error; + } + + /* debug code prime the buffer */ + memset(dev->read_buffer_primary, 'a', in_end_size); + memset(dev->read_buffer_primary + in_end_size, 'b', in_end_size); + memset(dev->read_buffer_primary + (2 * in_end_size), 'c', in_end_size); + memset(dev->read_buffer_primary + (3 * in_end_size), 'd', in_end_size); + + dev->read_buffer_secondary = kmalloc((4 * in_end_size), GFP_KERNEL); + if (!dev->read_buffer_secondary) { + dev_err(&interface->dev, "Couldn't allocate read_buffer_secondary\n"); + retval = -ENOMEM; + goto error; + } + + /* debug code prime the buffer */ + memset(dev->read_buffer_secondary, 'e', in_end_size); + memset(dev->read_buffer_secondary + in_end_size, 'f', in_end_size); + memset(dev->read_buffer_secondary + (2 * in_end_size), 'g', in_end_size); + memset(dev->read_buffer_secondary + (3 * in_end_size), 'h', in_end_size); + + dev->interrupt_in_buffer = kmalloc(in_end_size, GFP_KERNEL); + if (!dev->interrupt_in_buffer) { + dev_err(&interface->dev, "Couldn't allocate interrupt_in_buffer\n"); + goto error; + } + + /* debug code prime the buffer */ + memset(dev->interrupt_in_buffer, 'i', in_end_size); + + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) { + dev_err(&interface->dev, "Couldn't allocate interrupt_in_urb\n"); + goto error; + } + dev->interrupt_out_buffer = kmalloc(out_end_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) { + dev_err(&interface->dev, "Couldn't allocate interrupt_out_buffer\n"); + goto error; + } + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) { + dev_err(&interface->dev, "Couldn't allocate interrupt_out_urb\n"); + goto error; + } + + if (!usb_string(udev, udev->descriptor.iSerialNumber, dev->serial_number, + sizeof(dev->serial_number))) { + dev_err(&interface->dev, "Could not retrieve serial number\n"); + goto error; + } + dbg(2," %s : serial_number=%s", __func__, dev->serial_number); + + /* we can register the device now, as it is ready */ + usb_set_intfdata(interface, dev); + + retval = usb_register_dev(interface, &adu_class); + + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&interface->dev, "Not able to get a minor for this device.\n"); + usb_set_intfdata(interface, NULL); + goto error; + } + + dev->minor = interface->minor; + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "ADU%d %s now attached to /dev/usb/adutux%d\n", + udev->descriptor.idProduct, dev->serial_number, + (dev->minor - ADU_MINOR_BASE)); +exit: + dbg(2," %s : leave, return value %p (dev)", __func__, dev); + + return retval; + +error: + adu_delete(dev); + return retval; +} + +/** + * adu_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void adu_disconnect(struct usb_interface *interface) +{ + struct adu_device *dev; + int minor; + + dbg(2," %s : enter", __func__); + + dev = usb_get_intfdata(interface); + + mutex_lock(&dev->mtx); /* not interruptible */ + dev->udev = NULL; /* poison */ + minor = dev->minor; + usb_deregister_dev(interface, &adu_class); + mutex_unlock(&dev->mtx); + + mutex_lock(&adutux_mutex); + usb_set_intfdata(interface, NULL); + + /* if the device is not opened, then we clean up right now */ + dbg(2," %s : open count %d", __func__, dev->open_count); + if (!dev->open_count) + adu_delete(dev); + + mutex_unlock(&adutux_mutex); + + dev_info(&interface->dev, "ADU device adutux%d now disconnected\n", + (minor - ADU_MINOR_BASE)); + + dbg(2," %s : leave", __func__); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver adu_driver = { + .name = "adutux", + .probe = adu_probe, + .disconnect = adu_disconnect, + .id_table = device_table, +}; + +module_usb_driver(adu_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c new file mode 100644 index 00000000..ba6a5d6e --- /dev/null +++ b/drivers/usb/misc/appledisplay.c @@ -0,0 +1,382 @@ +/* + * Apple Cinema Display driver + * + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + * + * Thanks to Caskey L. Dickson for his work with acdctl. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APPLE_VENDOR_ID 0x05AC + +#define USB_REQ_GET_REPORT 0x01 +#define USB_REQ_SET_REPORT 0x09 + +#define ACD_USB_TIMEOUT 250 + +#define ACD_USB_EDID 0x0302 +#define ACD_USB_BRIGHTNESS 0x0310 + +#define ACD_BTN_NONE 0 +#define ACD_BTN_BRIGHT_UP 3 +#define ACD_BTN_BRIGHT_DOWN 4 + +#define ACD_URB_BUFFER_LEN 2 +#define ACD_MSG_BUFFER_LEN 2 + +#define APPLEDISPLAY_DEVICE(prod) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_PROTOCOL, \ + .idVendor = APPLE_VENDOR_ID, \ + .idProduct = (prod), \ + .bInterfaceClass = USB_CLASS_HID, \ + .bInterfaceProtocol = 0x00 + +/* table of devices that work with this driver */ +static const struct usb_device_id appledisplay_table[] = { + { APPLEDISPLAY_DEVICE(0x9218) }, + { APPLEDISPLAY_DEVICE(0x9219) }, + { APPLEDISPLAY_DEVICE(0x921c) }, + { APPLEDISPLAY_DEVICE(0x921d) }, + { APPLEDISPLAY_DEVICE(0x9236) }, + + /* Terminating entry */ + { } +}; +MODULE_DEVICE_TABLE(usb, appledisplay_table); + +/* Structure to hold all of our device specific stuff */ +struct appledisplay { + struct usb_device *udev; /* usb device */ + struct urb *urb; /* usb request block */ + struct backlight_device *bd; /* backlight device */ + u8 *urbdata; /* interrupt URB data buffer */ + u8 *msgdata; /* control message data buffer */ + + struct delayed_work work; + int button_pressed; + spinlock_t lock; +}; + +static atomic_t count_displays = ATOMIC_INIT(0); +static struct workqueue_struct *wq; + +static void appledisplay_complete(struct urb *urb) +{ + struct appledisplay *pdata = urb->context; + struct device *dev = &pdata->udev->dev; + unsigned long flags; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -EOVERFLOW: + dev_err(dev, + "OVERFLOW with data length %d, actual length is %d\n", + ACD_URB_BUFFER_LEN, pdata->urb->actual_length); + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* This urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shuttingdown with status: %d\n", + __func__, status); + return; + default: + dev_dbg(dev, "%s - nonzero urb status received: %d/n", + __func__, status); + goto exit; + } + + spin_lock_irqsave(&pdata->lock, flags); + + switch(pdata->urbdata[1]) { + case ACD_BTN_BRIGHT_UP: + case ACD_BTN_BRIGHT_DOWN: + pdata->button_pressed = 1; + queue_delayed_work(wq, &pdata->work, 0); + break; + case ACD_BTN_NONE: + default: + pdata->button_pressed = 0; + break; + } + + spin_unlock_irqrestore(&pdata->lock, flags); + +exit: + retval = usb_submit_urb(pdata->urb, GFP_ATOMIC); + if (retval) { + dev_err(dev, "%s - usb_submit_urb failed with result %d\n", + __func__, retval); + } +} + +static int appledisplay_bl_update_status(struct backlight_device *bd) +{ + struct appledisplay *pdata = bl_get_data(bd); + int retval; + + pdata->msgdata[0] = 0x10; + pdata->msgdata[1] = bd->props.brightness; + + retval = usb_control_msg( + pdata->udev, + usb_sndctrlpipe(pdata->udev, 0), + USB_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ACD_USB_BRIGHTNESS, + 0, + pdata->msgdata, 2, + ACD_USB_TIMEOUT); + + return retval; +} + +static int appledisplay_bl_get_brightness(struct backlight_device *bd) +{ + struct appledisplay *pdata = bl_get_data(bd); + int retval; + + retval = usb_control_msg( + pdata->udev, + usb_rcvctrlpipe(pdata->udev, 0), + USB_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ACD_USB_BRIGHTNESS, + 0, + pdata->msgdata, 2, + ACD_USB_TIMEOUT); + + if (retval < 0) + return retval; + else + return pdata->msgdata[1]; +} + +static const struct backlight_ops appledisplay_bl_data = { + .get_brightness = appledisplay_bl_get_brightness, + .update_status = appledisplay_bl_update_status, +}; + +static void appledisplay_work(struct work_struct *work) +{ + struct appledisplay *pdata = + container_of(work, struct appledisplay, work.work); + int retval; + + retval = appledisplay_bl_get_brightness(pdata->bd); + if (retval >= 0) + pdata->bd->props.brightness = retval; + + /* Poll again in about 125ms if there's still a button pressed */ + if (pdata->button_pressed) + schedule_delayed_work(&pdata->work, HZ / 8); +} + +static int appledisplay_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct backlight_properties props; + struct appledisplay *pdata; + struct usb_device *udev = interface_to_usbdev(iface); + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int int_in_endpointAddr = 0; + int i, retval = -ENOMEM, brightness; + char bl_name[20]; + + /* set up the endpoint information */ + /* use only the first interrupt-in endpoint */ + iface_desc = iface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + if (!int_in_endpointAddr && usb_endpoint_is_int_in(endpoint)) { + /* we found an interrupt in endpoint */ + int_in_endpointAddr = endpoint->bEndpointAddress; + break; + } + } + if (!int_in_endpointAddr) { + dev_err(&iface->dev, "Could not find int-in endpoint\n"); + return -EIO; + } + + /* allocate memory for our device state and initialize it */ + pdata = kzalloc(sizeof(struct appledisplay), GFP_KERNEL); + if (!pdata) { + retval = -ENOMEM; + dev_err(&iface->dev, "Out of memory\n"); + goto error; + } + + pdata->udev = udev; + + spin_lock_init(&pdata->lock); + INIT_DELAYED_WORK(&pdata->work, appledisplay_work); + + /* Allocate buffer for control messages */ + pdata->msgdata = kmalloc(ACD_MSG_BUFFER_LEN, GFP_KERNEL); + if (!pdata->msgdata) { + retval = -ENOMEM; + dev_err(&iface->dev, + "Allocating buffer for control messages failed\n"); + goto error; + } + + /* Allocate interrupt URB */ + pdata->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pdata->urb) { + retval = -ENOMEM; + dev_err(&iface->dev, "Allocating URB failed\n"); + goto error; + } + + /* Allocate buffer for interrupt data */ + pdata->urbdata = usb_alloc_coherent(pdata->udev, ACD_URB_BUFFER_LEN, + GFP_KERNEL, &pdata->urb->transfer_dma); + if (!pdata->urbdata) { + retval = -ENOMEM; + dev_err(&iface->dev, "Allocating URB buffer failed\n"); + goto error; + } + + /* Configure interrupt URB */ + usb_fill_int_urb(pdata->urb, udev, + usb_rcvintpipe(udev, int_in_endpointAddr), + pdata->urbdata, ACD_URB_BUFFER_LEN, appledisplay_complete, + pdata, 1); + if (usb_submit_urb(pdata->urb, GFP_KERNEL)) { + retval = -EIO; + dev_err(&iface->dev, "Submitting URB failed\n"); + goto error; + } + + /* Register backlight device */ + snprintf(bl_name, sizeof(bl_name), "appledisplay%d", + atomic_inc_return(&count_displays) - 1); + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 0xff; + pdata->bd = backlight_device_register(bl_name, NULL, pdata, + &appledisplay_bl_data, &props); + if (IS_ERR(pdata->bd)) { + dev_err(&iface->dev, "Backlight registration failed\n"); + retval = PTR_ERR(pdata->bd); + goto error; + } + + /* Try to get brightness */ + brightness = appledisplay_bl_get_brightness(pdata->bd); + + if (brightness < 0) { + retval = brightness; + dev_err(&iface->dev, + "Error while getting initial brightness: %d\n", retval); + goto error; + } + + /* Set brightness in backlight device */ + pdata->bd->props.brightness = brightness; + + /* save our data pointer in the interface device */ + usb_set_intfdata(iface, pdata); + + printk(KERN_INFO "appledisplay: Apple Cinema Display connected\n"); + + return 0; + +error: + if (pdata) { + if (pdata->urb) { + usb_kill_urb(pdata->urb); + if (pdata->urbdata) + usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN, + pdata->urbdata, pdata->urb->transfer_dma); + usb_free_urb(pdata->urb); + } + if (pdata->bd && !IS_ERR(pdata->bd)) + backlight_device_unregister(pdata->bd); + kfree(pdata->msgdata); + } + usb_set_intfdata(iface, NULL); + kfree(pdata); + return retval; +} + +static void appledisplay_disconnect(struct usb_interface *iface) +{ + struct appledisplay *pdata = usb_get_intfdata(iface); + + if (pdata) { + usb_kill_urb(pdata->urb); + cancel_delayed_work(&pdata->work); + backlight_device_unregister(pdata->bd); + usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN, + pdata->urbdata, pdata->urb->transfer_dma); + usb_free_urb(pdata->urb); + kfree(pdata->msgdata); + kfree(pdata); + } + + printk(KERN_INFO "appledisplay: Apple Cinema Display disconnected\n"); +} + +static struct usb_driver appledisplay_driver = { + .name = "appledisplay", + .probe = appledisplay_probe, + .disconnect = appledisplay_disconnect, + .id_table = appledisplay_table, +}; + +static int __init appledisplay_init(void) +{ + wq = create_singlethread_workqueue("appledisplay"); + if (!wq) { + printk(KERN_ERR "appledisplay: Could not create work queue\n"); + return -ENOMEM; + } + + return usb_register(&appledisplay_driver); +} + +static void __exit appledisplay_exit(void) +{ + flush_workqueue(wq); + destroy_workqueue(wq); + usb_deregister(&appledisplay_driver); +} + +MODULE_AUTHOR("Michael Hanselmann"); +MODULE_DESCRIPTION("Apple Cinema Display driver"); +MODULE_LICENSE("GPL"); + +module_init(appledisplay_init); +module_exit(appledisplay_exit); diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c new file mode 100644 index 00000000..3f7c1a92 --- /dev/null +++ b/drivers/usb/misc/cypress_cy7c63.c @@ -0,0 +1,279 @@ +/* +* cypress_cy7c63.c +* +* Copyright (c) 2006-2007 Oliver Bock (bock@tfh-berlin.de) +* +* This driver is based on the Cypress USB Driver by Marcus Maul +* (cyport) and the 2.0 version of Greg Kroah-Hartman's +* USB Skeleton driver. +* +* This is a generic driver for the Cypress CY7C63xxx family. +* For the time being it enables you to read from and write to +* the single I/O ports of the device. +* +* Supported vendors: AK Modul-Bus Computer GmbH +* (Firmware "Port-Chip") +* +* Supported devices: CY7C63001A-PC +* CY7C63001C-PXC +* CY7C63001C-SXC +* +* Supported functions: Read/Write Ports +* +* +* For up-to-date information please visit: +* http://www.obock.de/kernel/cypress +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation, version 2. +*/ + +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Oliver Bock (bock@tfh-berlin.de)" +#define DRIVER_DESC "Cypress CY7C63xxx USB driver" + +#define CYPRESS_VENDOR_ID 0xa2c +#define CYPRESS_PRODUCT_ID 0x8 + +#define CYPRESS_READ_PORT 0x4 +#define CYPRESS_WRITE_PORT 0x5 + +#define CYPRESS_READ_RAM 0x2 +#define CYPRESS_WRITE_RAM 0x3 +#define CYPRESS_READ_ROM 0x1 + +#define CYPRESS_READ_PORT_ID0 0 +#define CYPRESS_WRITE_PORT_ID0 0 +#define CYPRESS_READ_PORT_ID1 0x2 +#define CYPRESS_WRITE_PORT_ID1 1 + +#define CYPRESS_MAX_REQSIZE 8 + + +/* table of devices that work with this driver */ +static const struct usb_device_id cypress_table[] = { + { USB_DEVICE(CYPRESS_VENDOR_ID, CYPRESS_PRODUCT_ID) }, + { } +}; +MODULE_DEVICE_TABLE(usb, cypress_table); + +/* structure to hold all of our device specific stuff */ +struct cypress { + struct usb_device * udev; + unsigned char port[2]; +}; + +/* used to send usb control messages to device */ +static int vendor_command(struct cypress *dev, unsigned char request, + unsigned char address, unsigned char data) +{ + int retval = 0; + unsigned int pipe; + unsigned char *iobuf; + + /* allocate some memory for the i/o buffer*/ + iobuf = kzalloc(CYPRESS_MAX_REQSIZE, GFP_KERNEL); + if (!iobuf) { + dev_err(&dev->udev->dev, "Out of memory!\n"); + retval = -ENOMEM; + goto error; + } + + dev_dbg(&dev->udev->dev, "Sending usb_control_msg (data: %d)\n", data); + + /* prepare usb control message and send it upstream */ + pipe = usb_rcvctrlpipe(dev->udev, 0); + retval = usb_control_msg(dev->udev, pipe, request, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, + address, data, iobuf, CYPRESS_MAX_REQSIZE, + USB_CTRL_GET_TIMEOUT); + + /* store returned data (more READs to be added) */ + switch (request) { + case CYPRESS_READ_PORT: + if (address == CYPRESS_READ_PORT_ID0) { + dev->port[0] = iobuf[1]; + dev_dbg(&dev->udev->dev, + "READ_PORT0 returned: %d\n", + dev->port[0]); + } + else if (address == CYPRESS_READ_PORT_ID1) { + dev->port[1] = iobuf[1]; + dev_dbg(&dev->udev->dev, + "READ_PORT1 returned: %d\n", + dev->port[1]); + } + break; + } + + kfree(iobuf); +error: + return retval; +} + +/* write port value */ +static ssize_t write_port(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count, + int port_num, int write_id) +{ + int value = -1; + int result = 0; + + struct usb_interface *intf = to_usb_interface(dev); + struct cypress *cyp = usb_get_intfdata(intf); + + dev_dbg(&cyp->udev->dev, "WRITE_PORT%d called\n", port_num); + + /* validate input data */ + if (sscanf(buf, "%d", &value) < 1) { + result = -EINVAL; + goto error; + } + if (value < 0 || value > 255) { + result = -EINVAL; + goto error; + } + + result = vendor_command(cyp, CYPRESS_WRITE_PORT, write_id, + (unsigned char)value); + + dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); +error: + return result < 0 ? result : count; +} + +/* attribute callback handler (write) */ +static ssize_t set_port0_handler(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return write_port(dev, attr, buf, count, 0, CYPRESS_WRITE_PORT_ID0); +} + +/* attribute callback handler (write) */ +static ssize_t set_port1_handler(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return write_port(dev, attr, buf, count, 1, CYPRESS_WRITE_PORT_ID1); +} + +/* read port value */ +static ssize_t read_port(struct device *dev, struct device_attribute *attr, + char *buf, int port_num, int read_id) +{ + int result = 0; + + struct usb_interface *intf = to_usb_interface(dev); + struct cypress *cyp = usb_get_intfdata(intf); + + dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); + + result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); + + dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); + + return sprintf(buf, "%d", cyp->port[port_num]); +} + +/* attribute callback handler (read) */ +static ssize_t get_port0_handler(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_port(dev, attr, buf, 0, CYPRESS_READ_PORT_ID0); +} + +/* attribute callback handler (read) */ +static ssize_t get_port1_handler(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_port(dev, attr, buf, 1, CYPRESS_READ_PORT_ID1); +} + +static DEVICE_ATTR(port0, S_IRUGO | S_IWUSR, get_port0_handler, set_port0_handler); + +static DEVICE_ATTR(port1, S_IRUGO | S_IWUSR, get_port1_handler, set_port1_handler); + + +static int cypress_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct cypress *dev = NULL; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "Out of memory!\n"); + goto error_mem; + } + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* create device attribute files */ + retval = device_create_file(&interface->dev, &dev_attr_port0); + if (retval) + goto error; + retval = device_create_file(&interface->dev, &dev_attr_port1); + if (retval) + goto error; + + /* let the user know that the device is now attached */ + dev_info(&interface->dev, + "Cypress CY7C63xxx device now attached\n"); + return 0; + +error: + device_remove_file(&interface->dev, &dev_attr_port0); + device_remove_file(&interface->dev, &dev_attr_port1); + usb_set_intfdata(interface, NULL); + usb_put_dev(dev->udev); + kfree(dev); + +error_mem: + return retval; +} + +static void cypress_disconnect(struct usb_interface *interface) +{ + struct cypress *dev; + + dev = usb_get_intfdata(interface); + + /* remove device attribute files */ + device_remove_file(&interface->dev, &dev_attr_port0); + device_remove_file(&interface->dev, &dev_attr_port1); + /* the intfdata can be set to NULL only after the + * device files have been removed */ + usb_set_intfdata(interface, NULL); + + usb_put_dev(dev->udev); + + dev_info(&interface->dev, + "Cypress CY7C63xxx device now disconnected\n"); + + kfree(dev); +} + +static struct usb_driver cypress_driver = { + .name = "cypress_cy7c63", + .probe = cypress_probe, + .disconnect = cypress_disconnect, + .id_table = cypress_table, +}; + +module_usb_driver(cypress_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/cytherm.c b/drivers/usb/misc/cytherm.c new file mode 100644 index 00000000..5b9831b9 --- /dev/null +++ b/drivers/usb/misc/cytherm.c @@ -0,0 +1,424 @@ +/* -*- linux-c -*- + * Cypress USB Thermometer driver + * + * Copyright (c) 2004 Erik Rigtorp + * + * This driver works with Elektor magazine USB Interface as published in + * issue #291. It should also work with the original starter kit/demo board + * from Cypress. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + + +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION "v1.0" +#define DRIVER_AUTHOR "Erik Rigtorp" +#define DRIVER_DESC "Cypress USB Thermometer driver" + +#define USB_SKEL_VENDOR_ID 0x04b4 +#define USB_SKEL_PRODUCT_ID 0x0002 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, + { } +}; +MODULE_DEVICE_TABLE (usb, id_table); + +/* Structure to hold all of our device specific stuff */ +struct usb_cytherm { + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ + int brightness; +}; + + +/* local function prototypes */ +static int cytherm_probe(struct usb_interface *interface, + const struct usb_device_id *id); +static void cytherm_disconnect(struct usb_interface *interface); + + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver cytherm_driver = { + .name = "cytherm", + .probe = cytherm_probe, + .disconnect = cytherm_disconnect, + .id_table = id_table, +}; + +/* Vendor requests */ +/* They all operate on one byte at a time */ +#define PING 0x00 +#define READ_ROM 0x01 /* Reads form ROM, value = address */ +#define READ_RAM 0x02 /* Reads form RAM, value = address */ +#define WRITE_RAM 0x03 /* Write to RAM, value = address, index = data */ +#define READ_PORT 0x04 /* Reads from port, value = address */ +#define WRITE_PORT 0x05 /* Write to port, value = address, index = data */ + + +/* Send a vendor command to device */ +static int vendor_command(struct usb_device *dev, unsigned char request, + unsigned char value, unsigned char index, + void *buf, int size) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + request, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, + value, + index, buf, size, + USB_CTRL_GET_TIMEOUT); +} + + + +#define BRIGHTNESS 0x2c /* RAM location for brightness value */ +#define BRIGHTNESS_SEM 0x2b /* RAM location for brightness semaphore */ + +static ssize_t show_brightness(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + return sprintf(buf, "%i", cytherm->brightness); +} + +static ssize_t set_brightness(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + unsigned char *buffer; + int retval; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) { + dev_err(&cytherm->udev->dev, "out of memory\n"); + return 0; + } + + cytherm->brightness = simple_strtoul(buf, NULL, 10); + + if (cytherm->brightness > 0xFF) + cytherm->brightness = 0xFF; + else if (cytherm->brightness < 0) + cytherm->brightness = 0; + + /* Set brightness */ + retval = vendor_command(cytherm->udev, WRITE_RAM, BRIGHTNESS, + cytherm->brightness, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + /* Inform µC that we have changed the brightness setting */ + retval = vendor_command(cytherm->udev, WRITE_RAM, BRIGHTNESS_SEM, + 0x01, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + kfree(buffer); + + return count; +} + +static DEVICE_ATTR(brightness, S_IRUGO | S_IWUSR | S_IWGRP, + show_brightness, set_brightness); + + +#define TEMP 0x33 /* RAM location for temperature */ +#define SIGN 0x34 /* RAM location for temperature sign */ + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + int retval; + unsigned char *buffer; + + int temp, sign; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) { + dev_err(&cytherm->udev->dev, "out of memory\n"); + return 0; + } + + /* read temperature */ + retval = vendor_command(cytherm->udev, READ_RAM, TEMP, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + temp = buffer[1]; + + /* read sign */ + retval = vendor_command(cytherm->udev, READ_RAM, SIGN, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + sign = buffer[1]; + + kfree(buffer); + + return sprintf(buf, "%c%i.%i", sign ? '-' : '+', temp >> 1, + 5*(temp - ((temp >> 1) << 1))); +} + + +static ssize_t set_temp(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return count; +} + +static DEVICE_ATTR(temp, S_IRUGO, show_temp, set_temp); + + +#define BUTTON 0x7a + +static ssize_t show_button(struct device *dev, struct device_attribute *attr, char *buf) +{ + + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + int retval; + unsigned char *buffer; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) { + dev_err(&cytherm->udev->dev, "out of memory\n"); + return 0; + } + + /* check button */ + retval = vendor_command(cytherm->udev, READ_RAM, BUTTON, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + retval = buffer[1]; + + kfree(buffer); + + if (retval) + return sprintf(buf, "1"); + else + return sprintf(buf, "0"); +} + + +static ssize_t set_button(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + return count; +} + +static DEVICE_ATTR(button, S_IRUGO, show_button, set_button); + + +static ssize_t show_port0(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + int retval; + unsigned char *buffer; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) { + dev_err(&cytherm->udev->dev, "out of memory\n"); + return 0; + } + + retval = vendor_command(cytherm->udev, READ_PORT, 0, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + retval = buffer[1]; + + kfree(buffer); + + return sprintf(buf, "%d", retval); +} + + +static ssize_t set_port0(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + unsigned char *buffer; + int retval; + int tmp; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) { + dev_err(&cytherm->udev->dev, "out of memory\n"); + return 0; + } + + tmp = simple_strtoul(buf, NULL, 10); + + if (tmp > 0xFF) + tmp = 0xFF; + else if (tmp < 0) + tmp = 0; + + retval = vendor_command(cytherm->udev, WRITE_PORT, 0, + tmp, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + kfree(buffer); + + return count; +} + +static DEVICE_ATTR(port0, S_IRUGO | S_IWUSR | S_IWGRP, show_port0, set_port0); + +static ssize_t show_port1(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + int retval; + unsigned char *buffer; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) { + dev_err(&cytherm->udev->dev, "out of memory\n"); + return 0; + } + + retval = vendor_command(cytherm->udev, READ_PORT, 1, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + retval = buffer[1]; + + kfree(buffer); + + return sprintf(buf, "%d", retval); +} + + +static ssize_t set_port1(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + unsigned char *buffer; + int retval; + int tmp; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) { + dev_err(&cytherm->udev->dev, "out of memory\n"); + return 0; + } + + tmp = simple_strtoul(buf, NULL, 10); + + if (tmp > 0xFF) + tmp = 0xFF; + else if (tmp < 0) + tmp = 0; + + retval = vendor_command(cytherm->udev, WRITE_PORT, 1, + tmp, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + kfree(buffer); + + return count; +} + +static DEVICE_ATTR(port1, S_IRUGO | S_IWUSR | S_IWGRP, show_port1, set_port1); + + + +static int cytherm_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_cytherm *dev = NULL; + int retval = -ENOMEM; + + dev = kzalloc (sizeof(struct usb_cytherm), GFP_KERNEL); + if (dev == NULL) { + dev_err (&interface->dev, "Out of memory\n"); + goto error_mem; + } + + dev->udev = usb_get_dev(udev); + + usb_set_intfdata (interface, dev); + + dev->brightness = 0xFF; + + retval = device_create_file(&interface->dev, &dev_attr_brightness); + if (retval) + goto error; + retval = device_create_file(&interface->dev, &dev_attr_temp); + if (retval) + goto error; + retval = device_create_file(&interface->dev, &dev_attr_button); + if (retval) + goto error; + retval = device_create_file(&interface->dev, &dev_attr_port0); + if (retval) + goto error; + retval = device_create_file(&interface->dev, &dev_attr_port1); + if (retval) + goto error; + + dev_info (&interface->dev, + "Cypress thermometer device now attached\n"); + return 0; +error: + device_remove_file(&interface->dev, &dev_attr_brightness); + device_remove_file(&interface->dev, &dev_attr_temp); + device_remove_file(&interface->dev, &dev_attr_button); + device_remove_file(&interface->dev, &dev_attr_port0); + device_remove_file(&interface->dev, &dev_attr_port1); + usb_set_intfdata (interface, NULL); + usb_put_dev(dev->udev); + kfree(dev); +error_mem: + return retval; +} + +static void cytherm_disconnect(struct usb_interface *interface) +{ + struct usb_cytherm *dev; + + dev = usb_get_intfdata (interface); + + device_remove_file(&interface->dev, &dev_attr_brightness); + device_remove_file(&interface->dev, &dev_attr_temp); + device_remove_file(&interface->dev, &dev_attr_button); + device_remove_file(&interface->dev, &dev_attr_port0); + device_remove_file(&interface->dev, &dev_attr_port1); + + /* first remove the files, then NULL the pointer */ + usb_set_intfdata (interface, NULL); + + usb_put_dev(dev->udev); + + kfree(dev); + + dev_info(&interface->dev, "Cypress thermometer now disconnected\n"); +} + +module_usb_driver(cytherm_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/emi26.c b/drivers/usb/misc/emi26.c new file mode 100644 index 00000000..d65984de --- /dev/null +++ b/drivers/usb/misc/emi26.c @@ -0,0 +1,263 @@ +/* + * Emagic EMI 2|6 usb audio interface firmware loader. + * Copyright (C) 2002 + * Tapio Laxström (tapio.laxstrom@iptime.fi) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, as published by + * the Free Software Foundation, version 2. + * + * emi26.c,v 1.13 2002/03/08 13:10:26 tapio Exp + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EMI26_VENDOR_ID 0x086a /* Emagic Soft-und Hardware GmBH */ +#define EMI26_PRODUCT_ID 0x0100 /* EMI 2|6 without firmware */ +#define EMI26B_PRODUCT_ID 0x0102 /* EMI 2|6 without firmware */ + +#define ANCHOR_LOAD_INTERNAL 0xA0 /* Vendor specific request code for Anchor Upload/Download (This one is implemented in the core) */ +#define ANCHOR_LOAD_EXTERNAL 0xA3 /* This command is not implemented in the core. Requires firmware */ +#define ANCHOR_LOAD_FPGA 0xA5 /* This command is not implemented in the core. Requires firmware. Emagic extension */ +#define MAX_INTERNAL_ADDRESS 0x1B3F /* This is the highest internal RAM address for the AN2131Q */ +#define CPUCS_REG 0x7F92 /* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */ +#define INTERNAL_RAM(address) (address <= MAX_INTERNAL_ADDRESS) + +static int emi26_writememory( struct usb_device *dev, int address, + const unsigned char *data, int length, + __u8 bRequest); +static int emi26_set_reset(struct usb_device *dev, unsigned char reset_bit); +static int emi26_load_firmware (struct usb_device *dev); +static int emi26_probe(struct usb_interface *intf, const struct usb_device_id *id); +static void emi26_disconnect(struct usb_interface *intf); + +/* thanks to drivers/usb/serial/keyspan_pda.c code */ +static int emi26_writememory (struct usb_device *dev, int address, + const unsigned char *data, int length, + __u8 request) +{ + int result; + unsigned char *buffer = kmemdup(data, length, GFP_KERNEL); + + if (!buffer) { + dev_err(&dev->dev, "kmalloc(%d) failed.\n", length); + return -ENOMEM; + } + /* Note: usb_control_msg returns negative value on error or length of the + * data that was written! */ + result = usb_control_msg (dev, usb_sndctrlpipe(dev, 0), request, 0x40, address, 0, buffer, length, 300); + kfree (buffer); + return result; +} + +/* thanks to drivers/usb/serial/keyspan_pda.c code */ +static int emi26_set_reset (struct usb_device *dev, unsigned char reset_bit) +{ + int response; + dev_info(&dev->dev, "%s - %d\n", __func__, reset_bit); + /* printk(KERN_DEBUG "%s - %d", __func__, reset_bit); */ + response = emi26_writememory (dev, CPUCS_REG, &reset_bit, 1, 0xa0); + if (response < 0) { + dev_err(&dev->dev, "set_reset (%d) failed\n", reset_bit); + } + return response; +} + +#define FW_LOAD_SIZE 1023 + +static int emi26_load_firmware (struct usb_device *dev) +{ + const struct firmware *loader_fw = NULL; + const struct firmware *bitstream_fw = NULL; + const struct firmware *firmware_fw = NULL; + const struct ihex_binrec *rec; + int err = -ENOMEM; + int i; + __u32 addr; /* Address to write */ + __u8 *buf; + + buf = kmalloc(FW_LOAD_SIZE, GFP_KERNEL); + if (!buf) + goto wraperr; + + err = request_ihex_firmware(&loader_fw, "emi26/loader.fw", &dev->dev); + if (err) + goto nofw; + + err = request_ihex_firmware(&bitstream_fw, "emi26/bitstream.fw", + &dev->dev); + if (err) + goto nofw; + + err = request_ihex_firmware(&firmware_fw, "emi26/firmware.fw", + &dev->dev); + if (err) { + nofw: + dev_err(&dev->dev, "%s - request_firmware() failed\n", + __func__); + goto wraperr; + } + + /* Assert reset (stop the CPU in the EMI) */ + err = emi26_set_reset(dev,1); + if (err < 0) + goto wraperr; + + rec = (const struct ihex_binrec *)loader_fw->data; + /* 1. We need to put the loader for the FPGA into the EZ-USB */ + while (rec) { + err = emi26_writememory(dev, be32_to_cpu(rec->addr), + rec->data, be16_to_cpu(rec->len), + ANCHOR_LOAD_INTERNAL); + if (err < 0) + goto wraperr; + rec = ihex_next_binrec(rec); + } + + /* De-assert reset (let the CPU run) */ + err = emi26_set_reset(dev,0); + if (err < 0) + goto wraperr; + msleep(250); /* let device settle */ + + /* 2. We upload the FPGA firmware into the EMI + * Note: collect up to 1023 (yes!) bytes and send them with + * a single request. This is _much_ faster! */ + rec = (const struct ihex_binrec *)bitstream_fw->data; + do { + i = 0; + addr = be32_to_cpu(rec->addr); + + /* intel hex records are terminated with type 0 element */ + while (rec && (i + be16_to_cpu(rec->len) < FW_LOAD_SIZE)) { + memcpy(buf + i, rec->data, be16_to_cpu(rec->len)); + i += be16_to_cpu(rec->len); + rec = ihex_next_binrec(rec); + } + err = emi26_writememory(dev, addr, buf, i, ANCHOR_LOAD_FPGA); + if (err < 0) + goto wraperr; + } while (rec); + + /* Assert reset (stop the CPU in the EMI) */ + err = emi26_set_reset(dev,1); + if (err < 0) + goto wraperr; + + /* 3. We need to put the loader for the firmware into the EZ-USB (again...) */ + for (rec = (const struct ihex_binrec *)loader_fw->data; + rec; rec = ihex_next_binrec(rec)) { + err = emi26_writememory(dev, be32_to_cpu(rec->addr), + rec->data, be16_to_cpu(rec->len), + ANCHOR_LOAD_INTERNAL); + if (err < 0) + goto wraperr; + } + msleep(250); /* let device settle */ + + /* De-assert reset (let the CPU run) */ + err = emi26_set_reset(dev,0); + if (err < 0) + goto wraperr; + + /* 4. We put the part of the firmware that lies in the external RAM into the EZ-USB */ + + for (rec = (const struct ihex_binrec *)firmware_fw->data; + rec; rec = ihex_next_binrec(rec)) { + if (!INTERNAL_RAM(be32_to_cpu(rec->addr))) { + err = emi26_writememory(dev, be32_to_cpu(rec->addr), + rec->data, be16_to_cpu(rec->len), + ANCHOR_LOAD_EXTERNAL); + if (err < 0) + goto wraperr; + } + } + + /* Assert reset (stop the CPU in the EMI) */ + err = emi26_set_reset(dev,1); + if (err < 0) + goto wraperr; + + for (rec = (const struct ihex_binrec *)firmware_fw->data; + rec; rec = ihex_next_binrec(rec)) { + if (INTERNAL_RAM(be32_to_cpu(rec->addr))) { + err = emi26_writememory(dev, be32_to_cpu(rec->addr), + rec->data, be16_to_cpu(rec->len), + ANCHOR_LOAD_INTERNAL); + if (err < 0) + goto wraperr; + } + } + + /* De-assert reset (let the CPU run) */ + err = emi26_set_reset(dev,0); + if (err < 0) + goto wraperr; + msleep(250); /* let device settle */ + + /* return 1 to fail the driver inialization + * and give real driver change to load */ + err = 1; + +wraperr: + if (err < 0) + dev_err(&dev->dev,"%s - error loading firmware: error = %d\n", + __func__, err); + + release_firmware(loader_fw); + release_firmware(bitstream_fw); + release_firmware(firmware_fw); + + kfree(buf); + return err; +} + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(EMI26_VENDOR_ID, EMI26_PRODUCT_ID) }, + { USB_DEVICE(EMI26_VENDOR_ID, EMI26B_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, id_table); + +static int emi26_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + + dev_info(&intf->dev, "%s start\n", __func__); + + emi26_load_firmware(dev); + + /* do not return the driver context, let real audio driver do that */ + return -EIO; +} + +static void emi26_disconnect(struct usb_interface *intf) +{ +} + +static struct usb_driver emi26_driver = { + .name = "emi26 - firmware loader", + .probe = emi26_probe, + .disconnect = emi26_disconnect, + .id_table = id_table, +}; + +module_usb_driver(emi26_driver); + +MODULE_AUTHOR("Tapio Laxström"); +MODULE_DESCRIPTION("Emagic EMI 2|6 firmware loader."); +MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("emi26/loader.fw"); +MODULE_FIRMWARE("emi26/bitstream.fw"); +MODULE_FIRMWARE("emi26/firmware.fw"); +/* vi:ai:syntax=c:sw=8:ts=8:tw=80 + */ diff --git a/drivers/usb/misc/emi62.c b/drivers/usb/misc/emi62.c new file mode 100644 index 00000000..ae794b90 --- /dev/null +++ b/drivers/usb/misc/emi62.c @@ -0,0 +1,276 @@ +/* + * Emagic EMI 2|6 usb audio interface firmware loader. + * Copyright (C) 2002 + * Tapio Laxström (tapio.laxstrom@iptime.fi) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, as published by + * the Free Software Foundation, version 2. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* include firmware (variables)*/ + +/* FIXME: This is quick and dirty solution! */ +#define SPDIF /* if you want SPDIF comment next line */ +//#undef SPDIF /* if you want MIDI uncomment this line */ + +#ifdef SPDIF +#define FIRMWARE_FW "emi62/spdif.fw" +#else +#define FIRMWARE_FW "emi62/midi.fw" +#endif + +#define EMI62_VENDOR_ID 0x086a /* Emagic Soft-und Hardware GmBH */ +#define EMI62_PRODUCT_ID 0x0110 /* EMI 6|2m without firmware */ + +#define ANCHOR_LOAD_INTERNAL 0xA0 /* Vendor specific request code for Anchor Upload/Download (This one is implemented in the core) */ +#define ANCHOR_LOAD_EXTERNAL 0xA3 /* This command is not implemented in the core. Requires firmware */ +#define ANCHOR_LOAD_FPGA 0xA5 /* This command is not implemented in the core. Requires firmware. Emagic extension */ +#define MAX_INTERNAL_ADDRESS 0x1B3F /* This is the highest internal RAM address for the AN2131Q */ +#define CPUCS_REG 0x7F92 /* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */ +#define INTERNAL_RAM(address) (address <= MAX_INTERNAL_ADDRESS) + +static int emi62_writememory(struct usb_device *dev, int address, + const unsigned char *data, int length, + __u8 bRequest); +static int emi62_set_reset(struct usb_device *dev, unsigned char reset_bit); +static int emi62_load_firmware (struct usb_device *dev); +static int emi62_probe(struct usb_interface *intf, const struct usb_device_id *id); +static void emi62_disconnect(struct usb_interface *intf); + +/* thanks to drivers/usb/serial/keyspan_pda.c code */ +static int emi62_writememory(struct usb_device *dev, int address, + const unsigned char *data, int length, + __u8 request) +{ + int result; + unsigned char *buffer = kmemdup(data, length, GFP_KERNEL); + + if (!buffer) { + dev_err(&dev->dev, "kmalloc(%d) failed.\n", length); + return -ENOMEM; + } + /* Note: usb_control_msg returns negative value on error or length of the + * data that was written! */ + result = usb_control_msg (dev, usb_sndctrlpipe(dev, 0), request, 0x40, address, 0, buffer, length, 300); + kfree (buffer); + return result; +} + +/* thanks to drivers/usb/serial/keyspan_pda.c code */ +static int emi62_set_reset (struct usb_device *dev, unsigned char reset_bit) +{ + int response; + dev_info(&dev->dev, "%s - %d\n", __func__, reset_bit); + + response = emi62_writememory (dev, CPUCS_REG, &reset_bit, 1, 0xa0); + if (response < 0) + dev_err(&dev->dev, "set_reset (%d) failed\n", reset_bit); + return response; +} + +#define FW_LOAD_SIZE 1023 + +static int emi62_load_firmware (struct usb_device *dev) +{ + const struct firmware *loader_fw = NULL; + const struct firmware *bitstream_fw = NULL; + const struct firmware *firmware_fw = NULL; + const struct ihex_binrec *rec; + int err = -ENOMEM; + int i; + __u32 addr; /* Address to write */ + __u8 *buf; + + dev_dbg(&dev->dev, "load_firmware\n"); + buf = kmalloc(FW_LOAD_SIZE, GFP_KERNEL); + if (!buf) + goto wraperr; + + err = request_ihex_firmware(&loader_fw, "emi62/loader.fw", &dev->dev); + if (err) + goto nofw; + + err = request_ihex_firmware(&bitstream_fw, "emi62/bitstream.fw", + &dev->dev); + if (err) + goto nofw; + + err = request_ihex_firmware(&firmware_fw, FIRMWARE_FW, &dev->dev); + if (err) { + nofw: + goto wraperr; + } + + /* Assert reset (stop the CPU in the EMI) */ + err = emi62_set_reset(dev,1); + if (err < 0) + goto wraperr; + + rec = (const struct ihex_binrec *)loader_fw->data; + + /* 1. We need to put the loader for the FPGA into the EZ-USB */ + while (rec) { + err = emi62_writememory(dev, be32_to_cpu(rec->addr), + rec->data, be16_to_cpu(rec->len), + ANCHOR_LOAD_INTERNAL); + if (err < 0) + goto wraperr; + rec = ihex_next_binrec(rec); + } + + /* De-assert reset (let the CPU run) */ + err = emi62_set_reset(dev,0); + if (err < 0) + goto wraperr; + msleep(250); /* let device settle */ + + /* 2. We upload the FPGA firmware into the EMI + * Note: collect up to 1023 (yes!) bytes and send them with + * a single request. This is _much_ faster! */ + rec = (const struct ihex_binrec *)bitstream_fw->data; + do { + i = 0; + addr = be32_to_cpu(rec->addr); + + /* intel hex records are terminated with type 0 element */ + while (rec && (i + be16_to_cpu(rec->len) < FW_LOAD_SIZE)) { + memcpy(buf + i, rec->data, be16_to_cpu(rec->len)); + i += be16_to_cpu(rec->len); + rec = ihex_next_binrec(rec); + } + err = emi62_writememory(dev, addr, buf, i, ANCHOR_LOAD_FPGA); + if (err < 0) + goto wraperr; + } while (rec); + + /* Assert reset (stop the CPU in the EMI) */ + err = emi62_set_reset(dev,1); + if (err < 0) + goto wraperr; + + /* 3. We need to put the loader for the firmware into the EZ-USB (again...) */ + for (rec = (const struct ihex_binrec *)loader_fw->data; + rec; rec = ihex_next_binrec(rec)) { + err = emi62_writememory(dev, be32_to_cpu(rec->addr), + rec->data, be16_to_cpu(rec->len), + ANCHOR_LOAD_INTERNAL); + if (err < 0) + goto wraperr; + } + + /* De-assert reset (let the CPU run) */ + err = emi62_set_reset(dev,0); + if (err < 0) + goto wraperr; + msleep(250); /* let device settle */ + + /* 4. We put the part of the firmware that lies in the external RAM into the EZ-USB */ + + for (rec = (const struct ihex_binrec *)firmware_fw->data; + rec; rec = ihex_next_binrec(rec)) { + if (!INTERNAL_RAM(be32_to_cpu(rec->addr))) { + err = emi62_writememory(dev, be32_to_cpu(rec->addr), + rec->data, be16_to_cpu(rec->len), + ANCHOR_LOAD_EXTERNAL); + if (err < 0) + goto wraperr; + } + } + + /* Assert reset (stop the CPU in the EMI) */ + err = emi62_set_reset(dev,1); + if (err < 0) + goto wraperr; + + for (rec = (const struct ihex_binrec *)firmware_fw->data; + rec; rec = ihex_next_binrec(rec)) { + if (INTERNAL_RAM(be32_to_cpu(rec->addr))) { + err = emi62_writememory(dev, be32_to_cpu(rec->addr), + rec->data, be16_to_cpu(rec->len), + ANCHOR_LOAD_EXTERNAL); + if (err < 0) + goto wraperr; + } + } + + /* De-assert reset (let the CPU run) */ + err = emi62_set_reset(dev,0); + if (err < 0) + goto wraperr; + msleep(250); /* let device settle */ + + release_firmware(loader_fw); + release_firmware(bitstream_fw); + release_firmware(firmware_fw); + + kfree(buf); + + /* return 1 to fail the driver inialization + * and give real driver change to load */ + return 1; + +wraperr: + if (err < 0) + dev_err(&dev->dev,"%s - error loading firmware: error = %d\n", + __func__, err); + release_firmware(loader_fw); + release_firmware(bitstream_fw); + release_firmware(firmware_fw); + + kfree(buf); + dev_err(&dev->dev, "Error\n"); + return err; +} + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(EMI62_VENDOR_ID, EMI62_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, id_table); + +static int emi62_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + dev_dbg(&intf->dev, "emi62_probe\n"); + + dev_info(&intf->dev, "%s start\n", __func__); + + emi62_load_firmware(dev); + + /* do not return the driver context, let real audio driver do that */ + return -EIO; +} + +static void emi62_disconnect(struct usb_interface *intf) +{ +} + +static struct usb_driver emi62_driver = { + .name = "emi62 - firmware loader", + .probe = emi62_probe, + .disconnect = emi62_disconnect, + .id_table = id_table, +}; + +module_usb_driver(emi62_driver); + +MODULE_AUTHOR("Tapio Laxström"); +MODULE_DESCRIPTION("Emagic EMI 6|2m firmware loader."); +MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("emi62/loader.fw"); +MODULE_FIRMWARE("emi62/bitstream.fw"); +MODULE_FIRMWARE(FIRMWARE_FW); +/* vi:ai:syntax=c:sw=8:ts=8:tw=80 + */ diff --git a/drivers/usb/misc/ezusb.c b/drivers/usb/misc/ezusb.c new file mode 100644 index 00000000..e712afed --- /dev/null +++ b/drivers/usb/misc/ezusb.c @@ -0,0 +1,167 @@ +/* + * EZ-USB specific functions used by some of the USB to Serial drivers. + * + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct ezusb_fx_type { + /* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */ + unsigned short cpucs_reg; + unsigned short max_internal_adress; +}; + +static struct ezusb_fx_type ezusb_fx1 = { + .cpucs_reg = 0x7F92, + .max_internal_adress = 0x1B3F, +}; + +/* Commands for writing to memory */ +#define WRITE_INT_RAM 0xA0 +#define WRITE_EXT_RAM 0xA3 + +static int ezusb_writememory(struct usb_device *dev, int address, + unsigned char *data, int length, __u8 request) +{ + int result; + unsigned char *transfer_buffer; + + if (!dev) + return -ENODEV; + + transfer_buffer = kmemdup(data, length, GFP_KERNEL); + if (!transfer_buffer) { + dev_err(&dev->dev, "%s - kmalloc(%d) failed.\n", + __func__, length); + return -ENOMEM; + } + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + address, 0, transfer_buffer, length, 3000); + + kfree(transfer_buffer); + return result; +} + +static int ezusb_set_reset(struct usb_device *dev, unsigned short cpucs_reg, + unsigned char reset_bit) +{ + int response = ezusb_writememory(dev, cpucs_reg, &reset_bit, 1, WRITE_INT_RAM); + if (response < 0) + dev_err(&dev->dev, "%s-%d failed: %d\n", + __func__, reset_bit, response); + return response; +} + +int ezusb_fx1_set_reset(struct usb_device *dev, unsigned char reset_bit) +{ + return ezusb_set_reset(dev, ezusb_fx1.cpucs_reg, reset_bit); +} +EXPORT_SYMBOL_GPL(ezusb_fx1_set_reset); + +static int ezusb_ihex_firmware_download(struct usb_device *dev, + struct ezusb_fx_type fx, + const char *firmware_path) +{ + int ret = -ENOENT; + const struct firmware *firmware = NULL; + const struct ihex_binrec *record; + + if (request_ihex_firmware(&firmware, firmware_path, + &dev->dev)) { + dev_err(&dev->dev, + "%s - request \"%s\" failed\n", + __func__, firmware_path); + goto out; + } + + ret = ezusb_set_reset(dev, fx.cpucs_reg, 0); + if (ret < 0) + goto out; + + record = (const struct ihex_binrec *)firmware->data; + for (; record; record = ihex_next_binrec(record)) { + if (be32_to_cpu(record->addr) > fx.max_internal_adress) { + ret = ezusb_writememory(dev, be32_to_cpu(record->addr), + (unsigned char *)record->data, + be16_to_cpu(record->len), WRITE_EXT_RAM); + if (ret < 0) { + dev_err(&dev->dev, "%s - ezusb_writememory " + "failed writing internal memory " + "(%d %04X %p %d)\n", __func__, ret, + be32_to_cpu(record->addr), record->data, + be16_to_cpu(record->len)); + goto out; + } + } + } + + ret = ezusb_set_reset(dev, fx.cpucs_reg, 1); + if (ret < 0) + goto out; + record = (const struct ihex_binrec *)firmware->data; + for (; record; record = ihex_next_binrec(record)) { + if (be32_to_cpu(record->addr) <= fx.max_internal_adress) { + ret = ezusb_writememory(dev, be32_to_cpu(record->addr), + (unsigned char *)record->data, + be16_to_cpu(record->len), WRITE_INT_RAM); + if (ret < 0) { + dev_err(&dev->dev, "%s - ezusb_writememory " + "failed writing external memory " + "(%d %04X %p %d)\n", __func__, ret, + be32_to_cpu(record->addr), record->data, + be16_to_cpu(record->len)); + goto out; + } + } + } + ret = ezusb_set_reset(dev, fx.cpucs_reg, 0); +out: + release_firmware(firmware); + return ret; +} + +int ezusb_fx1_ihex_firmware_download(struct usb_device *dev, + const char *firmware_path) +{ + return ezusb_ihex_firmware_download(dev, ezusb_fx1, firmware_path); +} +EXPORT_SYMBOL_GPL(ezusb_fx1_ihex_firmware_download); + +#if 0 +/* + * Once someone one needs these fx2 functions, uncomment them + * and add them to ezusb.h and all should be good. + */ +static struct ezusb_fx_type ezusb_fx2 = { + .cpucs_reg = 0xE600, + .max_internal_adress = 0x3FFF, +}; + +int ezusb_fx2_set_reset(struct usb_device *dev, unsigned char reset_bit) +{ + return ezusb_set_reset(dev, ezusb_fx2.cpucs_reg, reset_bit); +} +EXPORT_SYMBOL_GPL(ezusb_fx2_set_reset); + +int ezusb_fx2_ihex_firmware_download(struct usb_device *dev, + const char *firmware_path) +{ + return ezusb_ihex_firmware_download(dev, ezusb_fx2, firmware_path); +} +EXPORT_SYMBOL_GPL(ezusb_fx2_ihex_firmware_download); +#endif + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/ftdi-elan.c b/drivers/usb/misc/ftdi-elan.c new file mode 100644 index 00000000..a4a3c7cd --- /dev/null +++ b/drivers/usb/misc/ftdi-elan.c @@ -0,0 +1,2946 @@ +/* +* USB FTDI client driver for Elan Digital Systems's Uxxx adapters +* +* Copyright(C) 2006 Elan Digital Systems Limited +* http://www.elandigitalsystems.com +* +* Author and Maintainer - Tony Olech - Elan Digital Systems +* tony.olech@elandigitalsystems.com +* +* This program is free software;you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation, version 2. +* +* +* This driver was written by Tony Olech(tony.olech@elandigitalsystems.com) +* based on various USB client drivers in the 2.6.15 linux kernel +* with constant reference to the 3rd Edition of Linux Device Drivers +* published by O'Reilly +* +* The U132 adapter is a USB to CardBus adapter specifically designed +* for PC cards that contain an OHCI host controller. Typical PC cards +* are the Orange Mobile 3G Option GlobeTrotter Fusion card. +* +* The U132 adapter will *NOT *work with PC cards that do not contain +* an OHCI controller. A simple way to test whether a PC card has an +* OHCI controller as an interface is to insert the PC card directly +* into a laptop(or desktop) with a CardBus slot and if "lspci" shows +* a new USB controller and "lsusb -v" shows a new OHCI Host Controller +* then there is a good chance that the U132 adapter will support the +* PC card.(you also need the specific client driver for the PC card) +* +* Please inform the Author and Maintainer about any PC cards that +* contain OHCI Host Controller and work when directly connected to +* an embedded CardBus slot but do not work when they are connected +* via an ELAN U132 adapter. +* +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +MODULE_AUTHOR("Tony Olech"); +MODULE_DESCRIPTION("FTDI ELAN driver"); +MODULE_LICENSE("GPL"); +#define INT_MODULE_PARM(n, v) static int n = v;module_param(n, int, 0444) +static bool distrust_firmware = 1; +module_param(distrust_firmware, bool, 0); +MODULE_PARM_DESC(distrust_firmware, "true to distrust firmware power/overcurren" + "t setup"); +extern struct platform_driver u132_platform_driver; +static struct workqueue_struct *status_queue; +static struct workqueue_struct *command_queue; +static struct workqueue_struct *respond_queue; +/* +* ftdi_module_lock exists to protect access to global variables +* +*/ +static struct mutex ftdi_module_lock; +static int ftdi_instances = 0; +static struct list_head ftdi_static_list; +/* +* end of the global variables protected by ftdi_module_lock +*/ +#include "usb_u132.h" +#include +#include + + /* FIXME ohci.h is ONLY for internal use by the OHCI driver. + * If you're going to try stuff like this, you need to split + * out shareable stuff (register declarations?) into its own + * file, maybe name + */ + +#include "../host/ohci.h" +/* Define these values to match your devices*/ +#define USB_FTDI_ELAN_VENDOR_ID 0x0403 +#define USB_FTDI_ELAN_PRODUCT_ID 0xd6ea +/* table of devices that work with this driver*/ +static const struct usb_device_id ftdi_elan_table[] = { + {USB_DEVICE(USB_FTDI_ELAN_VENDOR_ID, USB_FTDI_ELAN_PRODUCT_ID)}, + { /* Terminating entry */ } +}; + +MODULE_DEVICE_TABLE(usb, ftdi_elan_table); +/* only the jtag(firmware upgrade device) interface requires +* a device file and corresponding minor number, but the +* interface is created unconditionally - I suppose it could +* be configured or not according to a module parameter. +* But since we(now) require one interface per device, +* and since it unlikely that a normal installation would +* require more than a couple of elan-ftdi devices, 8 seems +* like a reasonable limit to have here, and if someone +* really requires more than 8 devices, then they can frig the +* code and recompile +*/ +#define USB_FTDI_ELAN_MINOR_BASE 192 +#define COMMAND_BITS 5 +#define COMMAND_SIZE (1<udev->dev, "FREEING ftdi=%p\n", ftdi); + usb_put_dev(ftdi->udev); + ftdi->disconnected += 1; + mutex_lock(&ftdi_module_lock); + list_del_init(&ftdi->ftdi_list); + ftdi_instances -= 1; + mutex_unlock(&ftdi_module_lock); + kfree(ftdi->bulk_in_buffer); + ftdi->bulk_in_buffer = NULL; +} + +static void ftdi_elan_put_kref(struct usb_ftdi *ftdi) +{ + kref_put(&ftdi->kref, ftdi_elan_delete); +} + +static void ftdi_elan_get_kref(struct usb_ftdi *ftdi) +{ + kref_get(&ftdi->kref); +} + +static void ftdi_elan_init_kref(struct usb_ftdi *ftdi) +{ + kref_init(&ftdi->kref); +} + +static void ftdi_status_requeue_work(struct usb_ftdi *ftdi, unsigned int delta) +{ + if (!queue_delayed_work(status_queue, &ftdi->status_work, delta)) + kref_put(&ftdi->kref, ftdi_elan_delete); +} + +static void ftdi_status_queue_work(struct usb_ftdi *ftdi, unsigned int delta) +{ + if (queue_delayed_work(status_queue, &ftdi->status_work, delta)) + kref_get(&ftdi->kref); +} + +static void ftdi_status_cancel_work(struct usb_ftdi *ftdi) +{ + if (cancel_delayed_work(&ftdi->status_work)) + kref_put(&ftdi->kref, ftdi_elan_delete); +} + +static void ftdi_command_requeue_work(struct usb_ftdi *ftdi, unsigned int delta) +{ + if (!queue_delayed_work(command_queue, &ftdi->command_work, delta)) + kref_put(&ftdi->kref, ftdi_elan_delete); +} + +static void ftdi_command_queue_work(struct usb_ftdi *ftdi, unsigned int delta) +{ + if (queue_delayed_work(command_queue, &ftdi->command_work, delta)) + kref_get(&ftdi->kref); +} + +static void ftdi_command_cancel_work(struct usb_ftdi *ftdi) +{ + if (cancel_delayed_work(&ftdi->command_work)) + kref_put(&ftdi->kref, ftdi_elan_delete); +} + +static void ftdi_response_requeue_work(struct usb_ftdi *ftdi, + unsigned int delta) +{ + if (!queue_delayed_work(respond_queue, &ftdi->respond_work, delta)) + kref_put(&ftdi->kref, ftdi_elan_delete); +} + +static void ftdi_respond_queue_work(struct usb_ftdi *ftdi, unsigned int delta) +{ + if (queue_delayed_work(respond_queue, &ftdi->respond_work, delta)) + kref_get(&ftdi->kref); +} + +static void ftdi_response_cancel_work(struct usb_ftdi *ftdi) +{ + if (cancel_delayed_work(&ftdi->respond_work)) + kref_put(&ftdi->kref, ftdi_elan_delete); +} + +void ftdi_elan_gone_away(struct platform_device *pdev) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + ftdi->gone_away += 1; + ftdi_elan_put_kref(ftdi); +} + + +EXPORT_SYMBOL_GPL(ftdi_elan_gone_away); +static void ftdi_release_platform_dev(struct device *dev) +{ + dev->parent = NULL; +} + +static void ftdi_elan_do_callback(struct usb_ftdi *ftdi, + struct u132_target *target, u8 *buffer, int length); +static void ftdi_elan_kick_command_queue(struct usb_ftdi *ftdi); +static void ftdi_elan_kick_respond_queue(struct usb_ftdi *ftdi); +static int ftdi_elan_setupOHCI(struct usb_ftdi *ftdi); +static int ftdi_elan_checkingPCI(struct usb_ftdi *ftdi); +static int ftdi_elan_enumeratePCI(struct usb_ftdi *ftdi); +static int ftdi_elan_synchronize(struct usb_ftdi *ftdi); +static int ftdi_elan_stuck_waiting(struct usb_ftdi *ftdi); +static int ftdi_elan_command_engine(struct usb_ftdi *ftdi); +static int ftdi_elan_respond_engine(struct usb_ftdi *ftdi); +static int ftdi_elan_hcd_init(struct usb_ftdi *ftdi) +{ + int result; + if (ftdi->platform_dev.dev.parent) + return -EBUSY; + ftdi_elan_get_kref(ftdi); + ftdi->platform_data.potpg = 100; + ftdi->platform_data.reset = NULL; + ftdi->platform_dev.id = ftdi->sequence_num; + ftdi->platform_dev.resource = ftdi->resources; + ftdi->platform_dev.num_resources = ARRAY_SIZE(ftdi->resources); + ftdi->platform_dev.dev.platform_data = &ftdi->platform_data; + ftdi->platform_dev.dev.parent = NULL; + ftdi->platform_dev.dev.release = ftdi_release_platform_dev; + ftdi->platform_dev.dev.dma_mask = NULL; + snprintf(ftdi->device_name, sizeof(ftdi->device_name), "u132_hcd"); + ftdi->platform_dev.name = ftdi->device_name; + dev_info(&ftdi->udev->dev, "requesting module '%s'\n", "u132_hcd"); + request_module("u132_hcd"); + dev_info(&ftdi->udev->dev, "registering '%s'\n", + ftdi->platform_dev.name); + result = platform_device_register(&ftdi->platform_dev); + return result; +} + +static void ftdi_elan_abandon_completions(struct usb_ftdi *ftdi) +{ + mutex_lock(&ftdi->u132_lock); + while (ftdi->respond_next > ftdi->respond_head) { + struct u132_respond *respond = &ftdi->respond[RESPOND_MASK & + ftdi->respond_head++]; + *respond->result = -ESHUTDOWN; + *respond->value = 0; + complete(&respond->wait_completion); + } mutex_unlock(&ftdi->u132_lock); +} + +static void ftdi_elan_abandon_targets(struct usb_ftdi *ftdi) +{ + int ed_number = 4; + mutex_lock(&ftdi->u132_lock); + while (ed_number-- > 0) { + struct u132_target *target = &ftdi->target[ed_number]; + if (target->active == 1) { + target->condition_code = TD_DEVNOTRESP; + mutex_unlock(&ftdi->u132_lock); + ftdi_elan_do_callback(ftdi, target, NULL, 0); + mutex_lock(&ftdi->u132_lock); + } + } + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + mutex_unlock(&ftdi->u132_lock); +} + +static void ftdi_elan_flush_targets(struct usb_ftdi *ftdi) +{ + int ed_number = 4; + mutex_lock(&ftdi->u132_lock); + while (ed_number-- > 0) { + struct u132_target *target = &ftdi->target[ed_number]; + target->abandoning = 1; + wait_1:if (target->active == 1) { + int command_size = ftdi->command_next - + ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x80 | (ed_number << 5) | 0x4; + command->length = 0x00; + command->address = 0x00; + command->width = 0x00; + command->follows = 0; + command->value = 0; + command->buffer = &command->value; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + mutex_lock(&ftdi->u132_lock); + goto wait_1; + } + } + wait_2:if (target->active == 1) { + int command_size = ftdi->command_next - + ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x90 | (ed_number << 5); + command->length = 0x00; + command->address = 0x00; + command->width = 0x00; + command->follows = 0; + command->value = 0; + command->buffer = &command->value; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + mutex_lock(&ftdi->u132_lock); + goto wait_2; + } + } + } + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + mutex_unlock(&ftdi->u132_lock); +} + +static void ftdi_elan_cancel_targets(struct usb_ftdi *ftdi) +{ + int ed_number = 4; + mutex_lock(&ftdi->u132_lock); + while (ed_number-- > 0) { + struct u132_target *target = &ftdi->target[ed_number]; + target->abandoning = 1; + wait:if (target->active == 1) { + int command_size = ftdi->command_next - + ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x80 | (ed_number << 5) | 0x4; + command->length = 0x00; + command->address = 0x00; + command->width = 0x00; + command->follows = 0; + command->value = 0; + command->buffer = &command->value; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + mutex_lock(&ftdi->u132_lock); + goto wait; + } + } + } + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + mutex_unlock(&ftdi->u132_lock); +} + +static void ftdi_elan_kick_command_queue(struct usb_ftdi *ftdi) +{ + ftdi_command_queue_work(ftdi, 0); +} + +static void ftdi_elan_command_work(struct work_struct *work) +{ + struct usb_ftdi *ftdi = + container_of(work, struct usb_ftdi, command_work.work); + + if (ftdi->disconnected > 0) { + ftdi_elan_put_kref(ftdi); + return; + } else { + int retval = ftdi_elan_command_engine(ftdi); + if (retval == -ESHUTDOWN) { + ftdi->disconnected += 1; + } else if (retval == -ENODEV) { + ftdi->disconnected += 1; + } else if (retval) + dev_err(&ftdi->udev->dev, "command error %d\n", retval); + ftdi_command_requeue_work(ftdi, msecs_to_jiffies(10)); + return; + } +} + +static void ftdi_elan_kick_respond_queue(struct usb_ftdi *ftdi) +{ + ftdi_respond_queue_work(ftdi, 0); +} + +static void ftdi_elan_respond_work(struct work_struct *work) +{ + struct usb_ftdi *ftdi = + container_of(work, struct usb_ftdi, respond_work.work); + if (ftdi->disconnected > 0) { + ftdi_elan_put_kref(ftdi); + return; + } else { + int retval = ftdi_elan_respond_engine(ftdi); + if (retval == 0) { + } else if (retval == -ESHUTDOWN) { + ftdi->disconnected += 1; + } else if (retval == -ENODEV) { + ftdi->disconnected += 1; + } else if (retval == -EILSEQ) { + ftdi->disconnected += 1; + } else { + ftdi->disconnected += 1; + dev_err(&ftdi->udev->dev, "respond error %d\n", retval); + } + if (ftdi->disconnected > 0) { + ftdi_elan_abandon_completions(ftdi); + ftdi_elan_abandon_targets(ftdi); + } + ftdi_response_requeue_work(ftdi, msecs_to_jiffies(10)); + return; + } +} + + +/* +* the sw_lock is initially held and will be freed +* after the FTDI has been synchronized +* +*/ +static void ftdi_elan_status_work(struct work_struct *work) +{ + struct usb_ftdi *ftdi = + container_of(work, struct usb_ftdi, status_work.work); + int work_delay_in_msec = 0; + if (ftdi->disconnected > 0) { + ftdi_elan_put_kref(ftdi); + return; + } else if (ftdi->synchronized == 0) { + down(&ftdi->sw_lock); + if (ftdi_elan_synchronize(ftdi) == 0) { + ftdi->synchronized = 1; + ftdi_command_queue_work(ftdi, 1); + ftdi_respond_queue_work(ftdi, 1); + up(&ftdi->sw_lock); + work_delay_in_msec = 100; + } else { + dev_err(&ftdi->udev->dev, "synchronize failed\n"); + up(&ftdi->sw_lock); + work_delay_in_msec = 10 *1000; + } + } else if (ftdi->stuck_status > 0) { + if (ftdi_elan_stuck_waiting(ftdi) == 0) { + ftdi->stuck_status = 0; + ftdi->synchronized = 0; + } else if ((ftdi->stuck_status++ % 60) == 1) { + dev_err(&ftdi->udev->dev, "WRONG type of card inserted " + "- please remove\n"); + } else + dev_err(&ftdi->udev->dev, "WRONG type of card inserted " + "- checked %d times\n", ftdi->stuck_status); + work_delay_in_msec = 100; + } else if (ftdi->enumerated == 0) { + if (ftdi_elan_enumeratePCI(ftdi) == 0) { + ftdi->enumerated = 1; + work_delay_in_msec = 250; + } else + work_delay_in_msec = 1000; + } else if (ftdi->initialized == 0) { + if (ftdi_elan_setupOHCI(ftdi) == 0) { + ftdi->initialized = 1; + work_delay_in_msec = 500; + } else { + dev_err(&ftdi->udev->dev, "initialized failed - trying " + "again in 10 seconds\n"); + work_delay_in_msec = 1 *1000; + } + } else if (ftdi->registered == 0) { + work_delay_in_msec = 10; + if (ftdi_elan_hcd_init(ftdi) == 0) { + ftdi->registered = 1; + } else + dev_err(&ftdi->udev->dev, "register failed\n"); + work_delay_in_msec = 250; + } else { + if (ftdi_elan_checkingPCI(ftdi) == 0) { + work_delay_in_msec = 250; + } else if (ftdi->controlreg & 0x00400000) { + if (ftdi->gone_away > 0) { + dev_err(&ftdi->udev->dev, "PCI device eject con" + "firmed platform_dev.dev.parent=%p plat" + "form_dev.dev=%p\n", + ftdi->platform_dev.dev.parent, + &ftdi->platform_dev.dev); + platform_device_unregister(&ftdi->platform_dev); + ftdi->platform_dev.dev.parent = NULL; + ftdi->registered = 0; + ftdi->enumerated = 0; + ftdi->card_ejected = 0; + ftdi->initialized = 0; + ftdi->gone_away = 0; + } else + ftdi_elan_flush_targets(ftdi); + work_delay_in_msec = 250; + } else { + dev_err(&ftdi->udev->dev, "PCI device has disappeared\n" + ); + ftdi_elan_cancel_targets(ftdi); + work_delay_in_msec = 500; + ftdi->enumerated = 0; + ftdi->initialized = 0; + } + } + if (ftdi->disconnected > 0) { + ftdi_elan_put_kref(ftdi); + return; + } else { + ftdi_status_requeue_work(ftdi, + msecs_to_jiffies(work_delay_in_msec)); + return; + } +} + + +/* +* file_operations for the jtag interface +* +* the usage count for the device is incremented on open() +* and decremented on release() +*/ +static int ftdi_elan_open(struct inode *inode, struct file *file) +{ + int subminor; + struct usb_interface *interface; + + subminor = iminor(inode); + interface = usb_find_interface(&ftdi_elan_driver, subminor); + + if (!interface) { + printk(KERN_ERR "can't find device for minor %d\n", subminor); + return -ENODEV; + } else { + struct usb_ftdi *ftdi = usb_get_intfdata(interface); + if (!ftdi) { + return -ENODEV; + } else { + if (down_interruptible(&ftdi->sw_lock)) { + return -EINTR; + } else { + ftdi_elan_get_kref(ftdi); + file->private_data = ftdi; + return 0; + } + } + } +} + +static int ftdi_elan_release(struct inode *inode, struct file *file) +{ + struct usb_ftdi *ftdi = file->private_data; + if (ftdi == NULL) + return -ENODEV; + up(&ftdi->sw_lock); /* decrement the count on our device */ + ftdi_elan_put_kref(ftdi); + return 0; +} + + +/* +* +* blocking bulk reads are used to get data from the device +* +*/ +static ssize_t ftdi_elan_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + char data[30 *3 + 4]; + char *d = data; + int m = (sizeof(data) - 1) / 3; + int bytes_read = 0; + int retry_on_empty = 10; + int retry_on_timeout = 5; + struct usb_ftdi *ftdi = file->private_data; + if (ftdi->disconnected > 0) { + return -ENODEV; + } + data[0] = 0; + have:if (ftdi->bulk_in_left > 0) { + if (count-- > 0) { + char *p = ++ftdi->bulk_in_last + ftdi->bulk_in_buffer; + ftdi->bulk_in_left -= 1; + if (bytes_read < m) { + d += sprintf(d, " %02X", 0x000000FF & *p); + } else if (bytes_read > m) { + } else + d += sprintf(d, " .."); + if (copy_to_user(buffer++, p, 1)) { + return -EFAULT; + } else { + bytes_read += 1; + goto have; + } + } else + return bytes_read; + } + more:if (count > 0) { + int packet_bytes = 0; + int retval = usb_bulk_msg(ftdi->udev, + usb_rcvbulkpipe(ftdi->udev, ftdi->bulk_in_endpointAddr), + ftdi->bulk_in_buffer, ftdi->bulk_in_size, + &packet_bytes, 50); + if (packet_bytes > 2) { + ftdi->bulk_in_left = packet_bytes - 2; + ftdi->bulk_in_last = 1; + goto have; + } else if (retval == -ETIMEDOUT) { + if (retry_on_timeout-- > 0) { + goto more; + } else if (bytes_read > 0) { + return bytes_read; + } else + return retval; + } else if (retval == 0) { + if (retry_on_empty-- > 0) { + goto more; + } else + return bytes_read; + } else + return retval; + } else + return bytes_read; +} + +static void ftdi_elan_write_bulk_callback(struct urb *urb) +{ + struct usb_ftdi *ftdi = urb->context; + int status = urb->status; + + if (status && !(status == -ENOENT || status == -ECONNRESET || + status == -ESHUTDOWN)) { + dev_err(&ftdi->udev->dev, "urb=%p write bulk status received: %" + "d\n", urb, status); + } + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); +} + +static int fill_buffer_with_all_queued_commands(struct usb_ftdi *ftdi, + char *buf, int command_size, int total_size) +{ + int ed_commands = 0; + int b = 0; + int I = command_size; + int i = ftdi->command_head; + while (I-- > 0) { + struct u132_command *command = &ftdi->command[COMMAND_MASK & + i++]; + int F = command->follows; + u8 *f = command->buffer; + if (command->header & 0x80) { + ed_commands |= 1 << (0x3 & (command->header >> 5)); + } + buf[b++] = command->header; + buf[b++] = (command->length >> 0) & 0x00FF; + buf[b++] = (command->length >> 8) & 0x00FF; + buf[b++] = command->address; + buf[b++] = command->width; + while (F-- > 0) { + buf[b++] = *f++; + } + } + return ed_commands; +} + +static int ftdi_elan_total_command_size(struct usb_ftdi *ftdi, int command_size) +{ + int total_size = 0; + int I = command_size; + int i = ftdi->command_head; + while (I-- > 0) { + struct u132_command *command = &ftdi->command[COMMAND_MASK & + i++]; + total_size += 5 + command->follows; + } return total_size; +} + +static int ftdi_elan_command_engine(struct usb_ftdi *ftdi) +{ + int retval; + char *buf; + int ed_commands; + int total_size; + struct urb *urb; + int command_size = ftdi->command_next - ftdi->command_head; + if (command_size == 0) + return 0; + total_size = ftdi_elan_total_command_size(ftdi, command_size); + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&ftdi->udev->dev, "could not get a urb to write %d comm" + "ands totaling %d bytes to the Uxxx\n", command_size, + total_size); + return -ENOMEM; + } + buf = usb_alloc_coherent(ftdi->udev, total_size, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + dev_err(&ftdi->udev->dev, "could not get a buffer to write %d c" + "ommands totaling %d bytes to the Uxxx\n", command_size, + total_size); + usb_free_urb(urb); + return -ENOMEM; + } + ed_commands = fill_buffer_with_all_queued_commands(ftdi, buf, + command_size, total_size); + usb_fill_bulk_urb(urb, ftdi->udev, usb_sndbulkpipe(ftdi->udev, + ftdi->bulk_out_endpointAddr), buf, total_size, + ftdi_elan_write_bulk_callback, ftdi); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + if (ed_commands) { + char diag[40 *3 + 4]; + char *d = diag; + int m = total_size; + u8 *c = buf; + int s = (sizeof(diag) - 1) / 3; + diag[0] = 0; + while (s-- > 0 && m-- > 0) { + if (s > 0 || m == 0) { + d += sprintf(d, " %02X", *c++); + } else + d += sprintf(d, " .."); + } + } + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + dev_err(&ftdi->udev->dev, "failed %d to submit urb %p to write " + "%d commands totaling %d bytes to the Uxxx\n", retval, + urb, command_size, total_size); + usb_free_coherent(ftdi->udev, total_size, buf, urb->transfer_dma); + usb_free_urb(urb); + return retval; + } + usb_free_urb(urb); /* release our reference to this urb, + the USB core will eventually free it entirely */ + ftdi->command_head += command_size; + ftdi_elan_kick_respond_queue(ftdi); + return 0; +} + +static void ftdi_elan_do_callback(struct usb_ftdi *ftdi, + struct u132_target *target, u8 *buffer, int length) +{ + struct urb *urb = target->urb; + int halted = target->halted; + int skipped = target->skipped; + int actual = target->actual; + int non_null = target->non_null; + int toggle_bits = target->toggle_bits; + int error_count = target->error_count; + int condition_code = target->condition_code; + int repeat_number = target->repeat_number; + void (*callback) (void *, struct urb *, u8 *, int, int, int, int, int, + int, int, int, int) = target->callback; + target->active -= 1; + target->callback = NULL; + (*callback) (target->endp, urb, buffer, length, toggle_bits, + error_count, condition_code, repeat_number, halted, skipped, + actual, non_null); +} + +static char *have_ed_set_response(struct usb_ftdi *ftdi, + struct u132_target *target, u16 ed_length, int ed_number, int ed_type, + char *b) +{ + int payload = (ed_length >> 0) & 0x07FF; + mutex_lock(&ftdi->u132_lock); + target->actual = 0; + target->non_null = (ed_length >> 15) & 0x0001; + target->repeat_number = (ed_length >> 11) & 0x000F; + if (ed_type == 0x02) { + if (payload == 0 || target->abandoning > 0) { + target->abandoning = 0; + mutex_unlock(&ftdi->u132_lock); + ftdi_elan_do_callback(ftdi, target, 4 + ftdi->response, + payload); + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + return ftdi->response; + } else { + ftdi->expected = 4 + payload; + ftdi->ed_found = 1; + mutex_unlock(&ftdi->u132_lock); + return b; + } + } else if (ed_type == 0x03) { + if (payload == 0 || target->abandoning > 0) { + target->abandoning = 0; + mutex_unlock(&ftdi->u132_lock); + ftdi_elan_do_callback(ftdi, target, 4 + ftdi->response, + payload); + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + return ftdi->response; + } else { + ftdi->expected = 4 + payload; + ftdi->ed_found = 1; + mutex_unlock(&ftdi->u132_lock); + return b; + } + } else if (ed_type == 0x01) { + target->abandoning = 0; + mutex_unlock(&ftdi->u132_lock); + ftdi_elan_do_callback(ftdi, target, 4 + ftdi->response, + payload); + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + return ftdi->response; + } else { + target->abandoning = 0; + mutex_unlock(&ftdi->u132_lock); + ftdi_elan_do_callback(ftdi, target, 4 + ftdi->response, + payload); + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + return ftdi->response; + } +} + +static char *have_ed_get_response(struct usb_ftdi *ftdi, + struct u132_target *target, u16 ed_length, int ed_number, int ed_type, + char *b) +{ + mutex_lock(&ftdi->u132_lock); + target->condition_code = TD_DEVNOTRESP; + target->actual = (ed_length >> 0) & 0x01FF; + target->non_null = (ed_length >> 15) & 0x0001; + target->repeat_number = (ed_length >> 11) & 0x000F; + mutex_unlock(&ftdi->u132_lock); + if (target->active) + ftdi_elan_do_callback(ftdi, target, NULL, 0); + target->abandoning = 0; + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + return ftdi->response; +} + + +/* +* The engine tries to empty the FTDI fifo +* +* all responses found in the fifo data are dispatched thus +* the response buffer can only ever hold a maximum sized +* response from the Uxxx. +* +*/ +static int ftdi_elan_respond_engine(struct usb_ftdi *ftdi) +{ + u8 *b = ftdi->response + ftdi->received; + int bytes_read = 0; + int retry_on_empty = 1; + int retry_on_timeout = 3; + int empty_packets = 0; + read:{ + int packet_bytes = 0; + int retval = usb_bulk_msg(ftdi->udev, + usb_rcvbulkpipe(ftdi->udev, ftdi->bulk_in_endpointAddr), + ftdi->bulk_in_buffer, ftdi->bulk_in_size, + &packet_bytes, 500); + char diag[30 *3 + 4]; + char *d = diag; + int m = packet_bytes; + u8 *c = ftdi->bulk_in_buffer; + int s = (sizeof(diag) - 1) / 3; + diag[0] = 0; + while (s-- > 0 && m-- > 0) { + if (s > 0 || m == 0) { + d += sprintf(d, " %02X", *c++); + } else + d += sprintf(d, " .."); + } + if (packet_bytes > 2) { + ftdi->bulk_in_left = packet_bytes - 2; + ftdi->bulk_in_last = 1; + goto have; + } else if (retval == -ETIMEDOUT) { + if (retry_on_timeout-- > 0) { + dev_err(&ftdi->udev->dev, "TIMED OUT with packe" + "t_bytes = %d with total %d bytes%s\n", + packet_bytes, bytes_read, diag); + goto more; + } else if (bytes_read > 0) { + dev_err(&ftdi->udev->dev, "ONLY %d bytes%s\n", + bytes_read, diag); + return -ENOMEM; + } else { + dev_err(&ftdi->udev->dev, "TIMED OUT with packe" + "t_bytes = %d with total %d bytes%s\n", + packet_bytes, bytes_read, diag); + return -ENOMEM; + } + } else if (retval == -EILSEQ) { + dev_err(&ftdi->udev->dev, "error = %d with packet_bytes" + " = %d with total %d bytes%s\n", retval, + packet_bytes, bytes_read, diag); + return retval; + } else if (retval) { + dev_err(&ftdi->udev->dev, "error = %d with packet_bytes" + " = %d with total %d bytes%s\n", retval, + packet_bytes, bytes_read, diag); + return retval; + } else if (packet_bytes == 2) { + unsigned char s0 = ftdi->bulk_in_buffer[0]; + unsigned char s1 = ftdi->bulk_in_buffer[1]; + empty_packets += 1; + if (s0 == 0x31 && s1 == 0x60) { + if (retry_on_empty-- > 0) { + goto more; + } else + return 0; + } else if (s0 == 0x31 && s1 == 0x00) { + if (retry_on_empty-- > 0) { + goto more; + } else + return 0; + } else { + if (retry_on_empty-- > 0) { + goto more; + } else + return 0; + } + } else if (packet_bytes == 1) { + if (retry_on_empty-- > 0) { + goto more; + } else + return 0; + } else { + if (retry_on_empty-- > 0) { + goto more; + } else + return 0; + } + } + more:{ + goto read; + } + have:if (ftdi->bulk_in_left > 0) { + u8 c = ftdi->bulk_in_buffer[++ftdi->bulk_in_last]; + bytes_read += 1; + ftdi->bulk_in_left -= 1; + if (ftdi->received == 0 && c == 0xFF) { + goto have; + } else + *b++ = c; + if (++ftdi->received < ftdi->expected) { + goto have; + } else if (ftdi->ed_found) { + int ed_number = (ftdi->response[0] >> 5) & 0x03; + u16 ed_length = (ftdi->response[2] << 8) | + ftdi->response[1]; + struct u132_target *target = &ftdi->target[ed_number]; + int payload = (ed_length >> 0) & 0x07FF; + char diag[30 *3 + 4]; + char *d = diag; + int m = payload; + u8 *c = 4 + ftdi->response; + int s = (sizeof(diag) - 1) / 3; + diag[0] = 0; + while (s-- > 0 && m-- > 0) { + if (s > 0 || m == 0) { + d += sprintf(d, " %02X", *c++); + } else + d += sprintf(d, " .."); + } + ftdi_elan_do_callback(ftdi, target, 4 + ftdi->response, + payload); + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + b = ftdi->response; + goto have; + } else if (ftdi->expected == 8) { + u8 buscmd; + int respond_head = ftdi->respond_head++; + struct u132_respond *respond = &ftdi->respond[ + RESPOND_MASK & respond_head]; + u32 data = ftdi->response[7]; + data <<= 8; + data |= ftdi->response[6]; + data <<= 8; + data |= ftdi->response[5]; + data <<= 8; + data |= ftdi->response[4]; + *respond->value = data; + *respond->result = 0; + complete(&respond->wait_completion); + ftdi->received = 0; + ftdi->expected = 4; + ftdi->ed_found = 0; + b = ftdi->response; + buscmd = (ftdi->response[0] >> 0) & 0x0F; + if (buscmd == 0x00) { + } else if (buscmd == 0x02) { + } else if (buscmd == 0x06) { + } else if (buscmd == 0x0A) { + } else + dev_err(&ftdi->udev->dev, "Uxxx unknown(%0X) va" + "lue = %08X\n", buscmd, data); + goto have; + } else { + if ((ftdi->response[0] & 0x80) == 0x00) { + ftdi->expected = 8; + goto have; + } else { + int ed_number = (ftdi->response[0] >> 5) & 0x03; + int ed_type = (ftdi->response[0] >> 0) & 0x03; + u16 ed_length = (ftdi->response[2] << 8) | + ftdi->response[1]; + struct u132_target *target = &ftdi->target[ + ed_number]; + target->halted = (ftdi->response[0] >> 3) & + 0x01; + target->skipped = (ftdi->response[0] >> 2) & + 0x01; + target->toggle_bits = (ftdi->response[3] >> 6) + & 0x03; + target->error_count = (ftdi->response[3] >> 4) + & 0x03; + target->condition_code = (ftdi->response[ + 3] >> 0) & 0x0F; + if ((ftdi->response[0] & 0x10) == 0x00) { + b = have_ed_set_response(ftdi, target, + ed_length, ed_number, ed_type, + b); + goto have; + } else { + b = have_ed_get_response(ftdi, target, + ed_length, ed_number, ed_type, + b); + goto have; + } + } + } + } else + goto more; +} + + +/* +* create a urb, and a buffer for it, and copy the data to the urb +* +*/ +static ssize_t ftdi_elan_write(struct file *file, + const char __user *user_buffer, size_t count, + loff_t *ppos) +{ + int retval = 0; + struct urb *urb; + char *buf; + struct usb_ftdi *ftdi = file->private_data; + + if (ftdi->disconnected > 0) { + return -ENODEV; + } + if (count == 0) { + goto exit; + } + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + retval = -ENOMEM; + goto error_1; + } + buf = usb_alloc_coherent(ftdi->udev, count, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + goto error_2; + } + if (copy_from_user(buf, user_buffer, count)) { + retval = -EFAULT; + goto error_3; + } + usb_fill_bulk_urb(urb, ftdi->udev, usb_sndbulkpipe(ftdi->udev, + ftdi->bulk_out_endpointAddr), buf, count, + ftdi_elan_write_bulk_callback, ftdi); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + dev_err(&ftdi->udev->dev, "failed submitting write urb, error %" + "d\n", retval); + goto error_3; + } + usb_free_urb(urb); + +exit: + return count; +error_3: + usb_free_coherent(ftdi->udev, count, buf, urb->transfer_dma); +error_2: + usb_free_urb(urb); +error_1: + return retval; +} + +static const struct file_operations ftdi_elan_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = ftdi_elan_read, + .write = ftdi_elan_write, + .open = ftdi_elan_open, + .release = ftdi_elan_release, +}; + +/* +* usb class driver info in order to get a minor number from the usb core, +* and to have the device registered with the driver core +*/ +static struct usb_class_driver ftdi_elan_jtag_class = { + .name = "ftdi-%d-jtag", + .fops = &ftdi_elan_fops, + .minor_base = USB_FTDI_ELAN_MINOR_BASE, +}; + +/* +* the following definitions are for the +* ELAN FPGA state machgine processor that +* lies on the other side of the FTDI chip +*/ +#define cPCIu132rd 0x0 +#define cPCIu132wr 0x1 +#define cPCIiord 0x2 +#define cPCIiowr 0x3 +#define cPCImemrd 0x6 +#define cPCImemwr 0x7 +#define cPCIcfgrd 0xA +#define cPCIcfgwr 0xB +#define cPCInull 0xF +#define cU132cmd_status 0x0 +#define cU132flash 0x1 +#define cPIDsetup 0x0 +#define cPIDout 0x1 +#define cPIDin 0x2 +#define cPIDinonce 0x3 +#define cCCnoerror 0x0 +#define cCCcrc 0x1 +#define cCCbitstuff 0x2 +#define cCCtoggle 0x3 +#define cCCstall 0x4 +#define cCCnoresp 0x5 +#define cCCbadpid1 0x6 +#define cCCbadpid2 0x7 +#define cCCdataoverrun 0x8 +#define cCCdataunderrun 0x9 +#define cCCbuffoverrun 0xC +#define cCCbuffunderrun 0xD +#define cCCnotaccessed 0xF +static int ftdi_elan_write_reg(struct usb_ftdi *ftdi, u32 data) +{ + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else { + int command_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x00 | cPCIu132wr; + command->length = 0x04; + command->address = 0x00; + command->width = 0x00; + command->follows = 4; + command->value = data; + command->buffer = &command->value; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +static int ftdi_elan_write_config(struct usb_ftdi *ftdi, int config_offset, + u8 width, u32 data) +{ + u8 addressofs = config_offset / 4; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else { + int command_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x00 | (cPCIcfgwr & 0x0F); + command->length = 0x04; + command->address = addressofs; + command->width = 0x00 | (width & 0x0F); + command->follows = 4; + command->value = data; + command->buffer = &command->value; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +static int ftdi_elan_write_pcimem(struct usb_ftdi *ftdi, int mem_offset, + u8 width, u32 data) +{ + u8 addressofs = mem_offset / 4; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else { + int command_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x00 | (cPCImemwr & 0x0F); + command->length = 0x04; + command->address = addressofs; + command->width = 0x00 | (width & 0x0F); + command->follows = 4; + command->value = data; + command->buffer = &command->value; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +int usb_ftdi_elan_write_pcimem(struct platform_device *pdev, int mem_offset, + u8 width, u32 data) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + return ftdi_elan_write_pcimem(ftdi, mem_offset, width, data); +} + + +EXPORT_SYMBOL_GPL(usb_ftdi_elan_write_pcimem); +static int ftdi_elan_read_reg(struct usb_ftdi *ftdi, u32 *data) +{ + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else { + int command_size; + int respond_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + respond_size = ftdi->respond_next - ftdi->respond_head; + if (command_size < COMMAND_SIZE && respond_size < RESPOND_SIZE) + { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + struct u132_respond *respond = &ftdi->respond[ + RESPOND_MASK & ftdi->respond_next]; + int result = -ENODEV; + respond->result = &result; + respond->header = command->header = 0x00 | cPCIu132rd; + command->length = 0x04; + respond->address = command->address = cU132cmd_status; + command->width = 0x00; + command->follows = 0; + command->value = 0; + command->buffer = NULL; + respond->value = data; + init_completion(&respond->wait_completion); + ftdi->command_next += 1; + ftdi->respond_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + wait_for_completion(&respond->wait_completion); + return result; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +static int ftdi_elan_read_config(struct usb_ftdi *ftdi, int config_offset, + u8 width, u32 *data) +{ + u8 addressofs = config_offset / 4; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else { + int command_size; + int respond_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + respond_size = ftdi->respond_next - ftdi->respond_head; + if (command_size < COMMAND_SIZE && respond_size < RESPOND_SIZE) + { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + struct u132_respond *respond = &ftdi->respond[ + RESPOND_MASK & ftdi->respond_next]; + int result = -ENODEV; + respond->result = &result; + respond->header = command->header = 0x00 | (cPCIcfgrd & + 0x0F); + command->length = 0x04; + respond->address = command->address = addressofs; + command->width = 0x00 | (width & 0x0F); + command->follows = 0; + command->value = 0; + command->buffer = NULL; + respond->value = data; + init_completion(&respond->wait_completion); + ftdi->command_next += 1; + ftdi->respond_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + wait_for_completion(&respond->wait_completion); + return result; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +static int ftdi_elan_read_pcimem(struct usb_ftdi *ftdi, int mem_offset, + u8 width, u32 *data) +{ + u8 addressofs = mem_offset / 4; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else { + int command_size; + int respond_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + respond_size = ftdi->respond_next - ftdi->respond_head; + if (command_size < COMMAND_SIZE && respond_size < RESPOND_SIZE) + { + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + struct u132_respond *respond = &ftdi->respond[ + RESPOND_MASK & ftdi->respond_next]; + int result = -ENODEV; + respond->result = &result; + respond->header = command->header = 0x00 | (cPCImemrd & + 0x0F); + command->length = 0x04; + respond->address = command->address = addressofs; + command->width = 0x00 | (width & 0x0F); + command->follows = 0; + command->value = 0; + command->buffer = NULL; + respond->value = data; + init_completion(&respond->wait_completion); + ftdi->command_next += 1; + ftdi->respond_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + wait_for_completion(&respond->wait_completion); + return result; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +int usb_ftdi_elan_read_pcimem(struct platform_device *pdev, int mem_offset, + u8 width, u32 *data) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + if (ftdi->initialized == 0) { + return -ENODEV; + } else + return ftdi_elan_read_pcimem(ftdi, mem_offset, width, data); +} + + +EXPORT_SYMBOL_GPL(usb_ftdi_elan_read_pcimem); +static int ftdi_elan_edset_setup(struct usb_ftdi *ftdi, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + u8 ed = ed_number - 1; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else if (ftdi->initialized == 0) { + return -ENODEV; + } else { + int command_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_target *target = &ftdi->target[ed]; + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x80 | (ed << 5); + command->length = 0x8007; + command->address = (toggle_bits << 6) | (ep_number << 2) + | (address << 0); + command->width = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + command->follows = 8; + command->value = 0; + command->buffer = urb->setup_packet; + target->callback = callback; + target->endp = endp; + target->urb = urb; + target->active = 1; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +int usb_ftdi_elan_edset_setup(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + return ftdi_elan_edset_setup(ftdi, ed_number, endp, urb, address, + ep_number, toggle_bits, callback); +} + + +EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_setup); +static int ftdi_elan_edset_input(struct usb_ftdi *ftdi, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + u8 ed = ed_number - 1; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else if (ftdi->initialized == 0) { + return -ENODEV; + } else { + int command_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_target *target = &ftdi->target[ed]; + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + u32 remaining_length = urb->transfer_buffer_length - + urb->actual_length; + command->header = 0x82 | (ed << 5); + if (remaining_length == 0) { + command->length = 0x0000; + } else if (remaining_length > 1024) { + command->length = 0x8000 | 1023; + } else + command->length = 0x8000 | (remaining_length - + 1); + command->address = (toggle_bits << 6) | (ep_number << 2) + | (address << 0); + command->width = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + command->follows = 0; + command->value = 0; + command->buffer = NULL; + target->callback = callback; + target->endp = endp; + target->urb = urb; + target->active = 1; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +int usb_ftdi_elan_edset_input(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + return ftdi_elan_edset_input(ftdi, ed_number, endp, urb, address, + ep_number, toggle_bits, callback); +} + + +EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_input); +static int ftdi_elan_edset_empty(struct usb_ftdi *ftdi, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + u8 ed = ed_number - 1; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else if (ftdi->initialized == 0) { + return -ENODEV; + } else { + int command_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_target *target = &ftdi->target[ed]; + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x81 | (ed << 5); + command->length = 0x0000; + command->address = (toggle_bits << 6) | (ep_number << 2) + | (address << 0); + command->width = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + command->follows = 0; + command->value = 0; + command->buffer = NULL; + target->callback = callback; + target->endp = endp; + target->urb = urb; + target->active = 1; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +int usb_ftdi_elan_edset_empty(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + return ftdi_elan_edset_empty(ftdi, ed_number, endp, urb, address, + ep_number, toggle_bits, callback); +} + + +EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_empty); +static int ftdi_elan_edset_output(struct usb_ftdi *ftdi, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + u8 ed = ed_number - 1; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else if (ftdi->initialized == 0) { + return -ENODEV; + } else { + int command_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + if (command_size < COMMAND_SIZE) { + u8 *b; + u16 urb_size; + int i = 0; + char data[30 *3 + 4]; + char *d = data; + int m = (sizeof(data) - 1) / 3; + int l = 0; + struct u132_target *target = &ftdi->target[ed]; + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x81 | (ed << 5); + command->address = (toggle_bits << 6) | (ep_number << 2) + | (address << 0); + command->width = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + command->follows = min_t(u32, 1024, + urb->transfer_buffer_length - + urb->actual_length); + command->value = 0; + command->buffer = urb->transfer_buffer + + urb->actual_length; + command->length = 0x8000 | (command->follows - 1); + b = command->buffer; + urb_size = command->follows; + data[0] = 0; + while (urb_size-- > 0) { + if (i > m) { + } else if (i++ < m) { + int w = sprintf(d, " %02X", *b++); + d += w; + l += w; + } else + d += sprintf(d, " .."); + } + target->callback = callback; + target->endp = endp; + target->urb = urb; + target->active = 1; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +int usb_ftdi_elan_edset_output(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + return ftdi_elan_edset_output(ftdi, ed_number, endp, urb, address, + ep_number, toggle_bits, callback); +} + + +EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_output); +static int ftdi_elan_edset_single(struct usb_ftdi *ftdi, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + u8 ed = ed_number - 1; + wait:if (ftdi->disconnected > 0) { + return -ENODEV; + } else if (ftdi->initialized == 0) { + return -ENODEV; + } else { + int command_size; + mutex_lock(&ftdi->u132_lock); + command_size = ftdi->command_next - ftdi->command_head; + if (command_size < COMMAND_SIZE) { + u32 remaining_length = urb->transfer_buffer_length - + urb->actual_length; + struct u132_target *target = &ftdi->target[ed]; + struct u132_command *command = &ftdi->command[ + COMMAND_MASK & ftdi->command_next]; + command->header = 0x83 | (ed << 5); + if (remaining_length == 0) { + command->length = 0x0000; + } else if (remaining_length > 1024) { + command->length = 0x8000 | 1023; + } else + command->length = 0x8000 | (remaining_length - + 1); + command->address = (toggle_bits << 6) | (ep_number << 2) + | (address << 0); + command->width = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + command->follows = 0; + command->value = 0; + command->buffer = NULL; + target->callback = callback; + target->endp = endp; + target->urb = urb; + target->active = 1; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + goto wait; + } + } +} + +int usb_ftdi_elan_edset_single(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + return ftdi_elan_edset_single(ftdi, ed_number, endp, urb, address, + ep_number, toggle_bits, callback); +} + + +EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_single); +static int ftdi_elan_edset_flush(struct usb_ftdi *ftdi, u8 ed_number, + void *endp) +{ + u8 ed = ed_number - 1; + if (ftdi->disconnected > 0) { + return -ENODEV; + } else if (ftdi->initialized == 0) { + return -ENODEV; + } else { + struct u132_target *target = &ftdi->target[ed]; + mutex_lock(&ftdi->u132_lock); + if (target->abandoning > 0) { + mutex_unlock(&ftdi->u132_lock); + return 0; + } else { + target->abandoning = 1; + wait_1:if (target->active == 1) { + int command_size = ftdi->command_next - + ftdi->command_head; + if (command_size < COMMAND_SIZE) { + struct u132_command *command = + &ftdi->command[COMMAND_MASK & + ftdi->command_next]; + command->header = 0x80 | (ed << 5) | + 0x4; + command->length = 0x00; + command->address = 0x00; + command->width = 0x00; + command->follows = 0; + command->value = 0; + command->buffer = &command->value; + ftdi->command_next += 1; + ftdi_elan_kick_command_queue(ftdi); + } else { + mutex_unlock(&ftdi->u132_lock); + msleep(100); + mutex_lock(&ftdi->u132_lock); + goto wait_1; + } + } + mutex_unlock(&ftdi->u132_lock); + return 0; + } + } +} + +int usb_ftdi_elan_edset_flush(struct platform_device *pdev, u8 ed_number, + void *endp) +{ + struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev); + return ftdi_elan_edset_flush(ftdi, ed_number, endp); +} + + +EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_flush); +static int ftdi_elan_flush_input_fifo(struct usb_ftdi *ftdi) +{ + int retry_on_empty = 10; + int retry_on_timeout = 5; + int retry_on_status = 20; + more:{ + int packet_bytes = 0; + int retval = usb_bulk_msg(ftdi->udev, + usb_rcvbulkpipe(ftdi->udev, ftdi->bulk_in_endpointAddr), + ftdi->bulk_in_buffer, ftdi->bulk_in_size, + &packet_bytes, 100); + if (packet_bytes > 2) { + char diag[30 *3 + 4]; + char *d = diag; + int m = (sizeof(diag) - 1) / 3; + char *b = ftdi->bulk_in_buffer; + int bytes_read = 0; + diag[0] = 0; + while (packet_bytes-- > 0) { + char c = *b++; + if (bytes_read < m) { + d += sprintf(d, " %02X", + 0x000000FF & c); + } else if (bytes_read > m) { + } else + d += sprintf(d, " .."); + bytes_read += 1; + continue; + } + goto more; + } else if (packet_bytes > 1) { + char s1 = ftdi->bulk_in_buffer[0]; + char s2 = ftdi->bulk_in_buffer[1]; + if (s1 == 0x31 && s2 == 0x60) { + return 0; + } else if (retry_on_status-- > 0) { + goto more; + } else { + dev_err(&ftdi->udev->dev, "STATUS ERROR retry l" + "imit reached\n"); + return -EFAULT; + } + } else if (packet_bytes > 0) { + char b1 = ftdi->bulk_in_buffer[0]; + dev_err(&ftdi->udev->dev, "only one byte flushed from F" + "TDI = %02X\n", b1); + if (retry_on_status-- > 0) { + goto more; + } else { + dev_err(&ftdi->udev->dev, "STATUS ERROR retry l" + "imit reached\n"); + return -EFAULT; + } + } else if (retval == -ETIMEDOUT) { + if (retry_on_timeout-- > 0) { + goto more; + } else { + dev_err(&ftdi->udev->dev, "TIMED OUT retry limi" + "t reached\n"); + return -ENOMEM; + } + } else if (retval == 0) { + if (retry_on_empty-- > 0) { + goto more; + } else { + dev_err(&ftdi->udev->dev, "empty packet retry l" + "imit reached\n"); + return -ENOMEM; + } + } else { + dev_err(&ftdi->udev->dev, "error = %d\n", retval); + return retval; + } + } + return -1; +} + + +/* +* send the long flush sequence +* +*/ +static int ftdi_elan_synchronize_flush(struct usb_ftdi *ftdi) +{ + int retval; + struct urb *urb; + char *buf; + int I = 257; + int i = 0; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&ftdi->udev->dev, "could not alloc a urb for flush sequ" + "ence\n"); + return -ENOMEM; + } + buf = usb_alloc_coherent(ftdi->udev, I, GFP_KERNEL, &urb->transfer_dma); + if (!buf) { + dev_err(&ftdi->udev->dev, "could not get a buffer for flush seq" + "uence\n"); + usb_free_urb(urb); + return -ENOMEM; + } + while (I-- > 0) + buf[i++] = 0x55; + usb_fill_bulk_urb(urb, ftdi->udev, usb_sndbulkpipe(ftdi->udev, + ftdi->bulk_out_endpointAddr), buf, i, + ftdi_elan_write_bulk_callback, ftdi); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + dev_err(&ftdi->udev->dev, "failed to submit urb containing the " + "flush sequence\n"); + usb_free_coherent(ftdi->udev, i, buf, urb->transfer_dma); + usb_free_urb(urb); + return -ENOMEM; + } + usb_free_urb(urb); + return 0; +} + + +/* +* send the reset sequence +* +*/ +static int ftdi_elan_synchronize_reset(struct usb_ftdi *ftdi) +{ + int retval; + struct urb *urb; + char *buf; + int I = 4; + int i = 0; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&ftdi->udev->dev, "could not get a urb for the reset se" + "quence\n"); + return -ENOMEM; + } + buf = usb_alloc_coherent(ftdi->udev, I, GFP_KERNEL, &urb->transfer_dma); + if (!buf) { + dev_err(&ftdi->udev->dev, "could not get a buffer for the reset" + " sequence\n"); + usb_free_urb(urb); + return -ENOMEM; + } + buf[i++] = 0x55; + buf[i++] = 0xAA; + buf[i++] = 0x5A; + buf[i++] = 0xA5; + usb_fill_bulk_urb(urb, ftdi->udev, usb_sndbulkpipe(ftdi->udev, + ftdi->bulk_out_endpointAddr), buf, i, + ftdi_elan_write_bulk_callback, ftdi); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + dev_err(&ftdi->udev->dev, "failed to submit urb containing the " + "reset sequence\n"); + usb_free_coherent(ftdi->udev, i, buf, urb->transfer_dma); + usb_free_urb(urb); + return -ENOMEM; + } + usb_free_urb(urb); + return 0; +} + +static int ftdi_elan_synchronize(struct usb_ftdi *ftdi) +{ + int retval; + int long_stop = 10; + int retry_on_timeout = 5; + int retry_on_empty = 10; + int err_count = 0; + retval = ftdi_elan_flush_input_fifo(ftdi); + if (retval) + return retval; + ftdi->bulk_in_left = 0; + ftdi->bulk_in_last = -1; + while (long_stop-- > 0) { + int read_stop; + int read_stuck; + retval = ftdi_elan_synchronize_flush(ftdi); + if (retval) + return retval; + retval = ftdi_elan_flush_input_fifo(ftdi); + if (retval) + return retval; + reset:retval = ftdi_elan_synchronize_reset(ftdi); + if (retval) + return retval; + read_stop = 100; + read_stuck = 10; + read:{ + int packet_bytes = 0; + retval = usb_bulk_msg(ftdi->udev, + usb_rcvbulkpipe(ftdi->udev, + ftdi->bulk_in_endpointAddr), + ftdi->bulk_in_buffer, ftdi->bulk_in_size, + &packet_bytes, 500); + if (packet_bytes > 2) { + char diag[30 *3 + 4]; + char *d = diag; + int m = (sizeof(diag) - 1) / 3; + char *b = ftdi->bulk_in_buffer; + int bytes_read = 0; + unsigned char c = 0; + diag[0] = 0; + while (packet_bytes-- > 0) { + c = *b++; + if (bytes_read < m) { + d += sprintf(d, " %02X", c); + } else if (bytes_read > m) { + } else + d += sprintf(d, " .."); + bytes_read += 1; + continue; + } + if (c == 0x7E) { + return 0; + } else { + if (c == 0x55) { + goto read; + } else if (read_stop-- > 0) { + goto read; + } else { + dev_err(&ftdi->udev->dev, "retr" + "y limit reached\n"); + continue; + } + } + } else if (packet_bytes > 1) { + unsigned char s1 = ftdi->bulk_in_buffer[0]; + unsigned char s2 = ftdi->bulk_in_buffer[1]; + if (s1 == 0x31 && s2 == 0x00) { + if (read_stuck-- > 0) { + goto read; + } else + goto reset; + } else if (s1 == 0x31 && s2 == 0x60) { + if (read_stop-- > 0) { + goto read; + } else { + dev_err(&ftdi->udev->dev, "retr" + "y limit reached\n"); + continue; + } + } else { + if (read_stop-- > 0) { + goto read; + } else { + dev_err(&ftdi->udev->dev, "retr" + "y limit reached\n"); + continue; + } + } + } else if (packet_bytes > 0) { + if (read_stop-- > 0) { + goto read; + } else { + dev_err(&ftdi->udev->dev, "retry limit " + "reached\n"); + continue; + } + } else if (retval == -ETIMEDOUT) { + if (retry_on_timeout-- > 0) { + goto read; + } else { + dev_err(&ftdi->udev->dev, "TIMED OUT re" + "try limit reached\n"); + continue; + } + } else if (retval == 0) { + if (retry_on_empty-- > 0) { + goto read; + } else { + dev_err(&ftdi->udev->dev, "empty packet" + " retry limit reached\n"); + continue; + } + } else { + err_count += 1; + dev_err(&ftdi->udev->dev, "error = %d\n", + retval); + if (read_stop-- > 0) { + goto read; + } else { + dev_err(&ftdi->udev->dev, "retry limit " + "reached\n"); + continue; + } + } + } + } + dev_err(&ftdi->udev->dev, "failed to synchronize\n"); + return -EFAULT; +} + +static int ftdi_elan_stuck_waiting(struct usb_ftdi *ftdi) +{ + int retry_on_empty = 10; + int retry_on_timeout = 5; + int retry_on_status = 50; + more:{ + int packet_bytes = 0; + int retval = usb_bulk_msg(ftdi->udev, + usb_rcvbulkpipe(ftdi->udev, ftdi->bulk_in_endpointAddr), + ftdi->bulk_in_buffer, ftdi->bulk_in_size, + &packet_bytes, 1000); + if (packet_bytes > 2) { + char diag[30 *3 + 4]; + char *d = diag; + int m = (sizeof(diag) - 1) / 3; + char *b = ftdi->bulk_in_buffer; + int bytes_read = 0; + diag[0] = 0; + while (packet_bytes-- > 0) { + char c = *b++; + if (bytes_read < m) { + d += sprintf(d, " %02X", + 0x000000FF & c); + } else if (bytes_read > m) { + } else + d += sprintf(d, " .."); + bytes_read += 1; + continue; + } + goto more; + } else if (packet_bytes > 1) { + char s1 = ftdi->bulk_in_buffer[0]; + char s2 = ftdi->bulk_in_buffer[1]; + if (s1 == 0x31 && s2 == 0x60) { + return 0; + } else if (retry_on_status-- > 0) { + msleep(5); + goto more; + } else + return -EFAULT; + } else if (packet_bytes > 0) { + char b1 = ftdi->bulk_in_buffer[0]; + dev_err(&ftdi->udev->dev, "only one byte flushed from F" + "TDI = %02X\n", b1); + if (retry_on_status-- > 0) { + msleep(5); + goto more; + } else { + dev_err(&ftdi->udev->dev, "STATUS ERROR retry l" + "imit reached\n"); + return -EFAULT; + } + } else if (retval == -ETIMEDOUT) { + if (retry_on_timeout-- > 0) { + goto more; + } else { + dev_err(&ftdi->udev->dev, "TIMED OUT retry limi" + "t reached\n"); + return -ENOMEM; + } + } else if (retval == 0) { + if (retry_on_empty-- > 0) { + goto more; + } else { + dev_err(&ftdi->udev->dev, "empty packet retry l" + "imit reached\n"); + return -ENOMEM; + } + } else { + dev_err(&ftdi->udev->dev, "error = %d\n", retval); + return -ENOMEM; + } + } + return -1; +} + +static int ftdi_elan_checkingPCI(struct usb_ftdi *ftdi) +{ + int UxxxStatus = ftdi_elan_read_reg(ftdi, &ftdi->controlreg); + if (UxxxStatus) + return UxxxStatus; + if (ftdi->controlreg & 0x00400000) { + if (ftdi->card_ejected) { + } else { + ftdi->card_ejected = 1; + dev_err(&ftdi->udev->dev, "CARD EJECTED - controlreg = " + "%08X\n", ftdi->controlreg); + } + return -ENODEV; + } else { + u8 fn = ftdi->function - 1; + int activePCIfn = fn << 8; + u32 pcidata; + u32 pciVID; + u32 pciPID; + int reg = 0; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + pciVID = pcidata & 0xFFFF; + pciPID = (pcidata >> 16) & 0xFFFF; + if (pciVID == ftdi->platform_data.vendor && pciPID == + ftdi->platform_data.device) { + return 0; + } else { + dev_err(&ftdi->udev->dev, "vendor=%04X pciVID=%04X devi" + "ce=%04X pciPID=%04X\n", + ftdi->platform_data.vendor, pciVID, + ftdi->platform_data.device, pciPID); + return -ENODEV; + } + } +} + + +#define ftdi_read_pcimem(ftdi, member, data) ftdi_elan_read_pcimem(ftdi, \ + offsetof(struct ohci_regs, member), 0, data); +#define ftdi_write_pcimem(ftdi, member, data) ftdi_elan_write_pcimem(ftdi, \ + offsetof(struct ohci_regs, member), 0, data); + +#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR +#define OHCI_INTR_INIT (OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_RD | \ + OHCI_INTR_WDH) +static int ftdi_elan_check_controller(struct usb_ftdi *ftdi, int quirk) +{ + int devices = 0; + int retval; + u32 hc_control; + int num_ports; + u32 control; + u32 rh_a = -1; + u32 status; + u32 fminterval; + u32 hc_fminterval; + u32 periodicstart; + u32 cmdstatus; + u32 roothub_a; + int mask = OHCI_INTR_INIT; + int sleep_time = 0; + int reset_timeout = 30; /* ... allow extra time */ + int temp; + retval = ftdi_write_pcimem(ftdi, intrdisable, OHCI_INTR_MIE); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, control, &control); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, roothub.a, &rh_a); + if (retval) + return retval; + num_ports = rh_a & RH_A_NDP; + retval = ftdi_read_pcimem(ftdi, fminterval, &hc_fminterval); + if (retval) + return retval; + hc_fminterval &= 0x3fff; + if (hc_fminterval != FI) { + } + hc_fminterval |= FSMP(hc_fminterval) << 16; + retval = ftdi_read_pcimem(ftdi, control, &hc_control); + if (retval) + return retval; + switch (hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_OPER: + sleep_time = 0; + break; + case OHCI_USB_SUSPEND: + case OHCI_USB_RESUME: + hc_control &= OHCI_CTRL_RWC; + hc_control |= OHCI_USB_RESUME; + sleep_time = 10; + break; + default: + hc_control &= OHCI_CTRL_RWC; + hc_control |= OHCI_USB_RESET; + sleep_time = 50; + break; + } + retval = ftdi_write_pcimem(ftdi, control, hc_control); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, control, &control); + if (retval) + return retval; + msleep(sleep_time); + retval = ftdi_read_pcimem(ftdi, roothub.a, &roothub_a); + if (retval) + return retval; + if (!(roothub_a & RH_A_NPS)) { /* power down each port */ + for (temp = 0; temp < num_ports; temp++) { + retval = ftdi_write_pcimem(ftdi, + roothub.portstatus[temp], RH_PS_LSDA); + if (retval) + return retval; + } + } + retval = ftdi_read_pcimem(ftdi, control, &control); + if (retval) + return retval; + retry:retval = ftdi_read_pcimem(ftdi, cmdstatus, &status); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, cmdstatus, OHCI_HCR); + if (retval) + return retval; + extra:{ + retval = ftdi_read_pcimem(ftdi, cmdstatus, &status); + if (retval) + return retval; + if (0 != (status & OHCI_HCR)) { + if (--reset_timeout == 0) { + dev_err(&ftdi->udev->dev, "USB HC reset timed o" + "ut!\n"); + return -ENODEV; + } else { + msleep(5); + goto extra; + } + } + } + if (quirk & OHCI_QUIRK_INITRESET) { + retval = ftdi_write_pcimem(ftdi, control, hc_control); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, control, &control); + if (retval) + return retval; + } + retval = ftdi_write_pcimem(ftdi, ed_controlhead, 0x00000000); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, ed_bulkhead, 0x11000000); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, hcca, 0x00000000); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, fminterval, &fminterval); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, fminterval, + ((fminterval & FIT) ^ FIT) | hc_fminterval); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, periodicstart, + ((9 *hc_fminterval) / 10) & 0x3fff); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, fminterval, &fminterval); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, periodicstart, &periodicstart); + if (retval) + return retval; + if (0 == (fminterval & 0x3fff0000) || 0 == periodicstart) { + if (!(quirk & OHCI_QUIRK_INITRESET)) { + quirk |= OHCI_QUIRK_INITRESET; + goto retry; + } else + dev_err(&ftdi->udev->dev, "init err(%08x %04x)\n", + fminterval, periodicstart); + } /* start controller operations */ + hc_control &= OHCI_CTRL_RWC; + hc_control |= OHCI_CONTROL_INIT | OHCI_CTRL_BLE | OHCI_USB_OPER; + retval = ftdi_write_pcimem(ftdi, control, hc_control); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, cmdstatus, OHCI_BLF); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, cmdstatus, &cmdstatus); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, control, &control); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, roothub.status, RH_HS_DRWE); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, intrstatus, mask); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, intrdisable, + OHCI_INTR_MIE | OHCI_INTR_OC | OHCI_INTR_RHSC | OHCI_INTR_FNO | + OHCI_INTR_UE | OHCI_INTR_RD | OHCI_INTR_SF | OHCI_INTR_WDH | + OHCI_INTR_SO); + if (retval) + return retval; /* handle root hub init quirks ... */ + retval = ftdi_read_pcimem(ftdi, roothub.a, &roothub_a); + if (retval) + return retval; + roothub_a &= ~(RH_A_PSM | RH_A_OCPM); + if (quirk & OHCI_QUIRK_SUPERIO) { + roothub_a |= RH_A_NOCP; + roothub_a &= ~(RH_A_POTPGT | RH_A_NPS); + retval = ftdi_write_pcimem(ftdi, roothub.a, roothub_a); + if (retval) + return retval; + } else if ((quirk & OHCI_QUIRK_AMD756) || distrust_firmware) { + roothub_a |= RH_A_NPS; + retval = ftdi_write_pcimem(ftdi, roothub.a, roothub_a); + if (retval) + return retval; + } + retval = ftdi_write_pcimem(ftdi, roothub.status, RH_HS_LPSC); + if (retval) + return retval; + retval = ftdi_write_pcimem(ftdi, roothub.b, + (roothub_a & RH_A_NPS) ? 0 : RH_B_PPCM); + if (retval) + return retval; + retval = ftdi_read_pcimem(ftdi, control, &control); + if (retval) + return retval; + mdelay((roothub_a >> 23) & 0x1fe); + for (temp = 0; temp < num_ports; temp++) { + u32 portstatus; + retval = ftdi_read_pcimem(ftdi, roothub.portstatus[temp], + &portstatus); + if (retval) + return retval; + if (1 & portstatus) + devices += 1; + } + return devices; +} + +static int ftdi_elan_setup_controller(struct usb_ftdi *ftdi, int fn) +{ + u32 latence_timer; + int UxxxStatus; + u32 pcidata; + int reg = 0; + int activePCIfn = fn << 8; + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000025FL | 0x2800); + if (UxxxStatus) + return UxxxStatus; + reg = 16; + UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0, + 0xFFFFFFFF); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0, + 0xF0000000); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + reg = 12; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &latence_timer); + if (UxxxStatus) + return UxxxStatus; + latence_timer &= 0xFFFF00FF; + latence_timer |= 0x00001600; + UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0x00, + latence_timer); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + reg = 4; + UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0x00, + 0x06); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + for (reg = 0; reg <= 0x54; reg += 4) { + UxxxStatus = ftdi_elan_read_pcimem(ftdi, reg, 0, &pcidata); + if (UxxxStatus) + return UxxxStatus; + } + return 0; +} + +static int ftdi_elan_close_controller(struct usb_ftdi *ftdi, int fn) +{ + u32 latence_timer; + int UxxxStatus; + u32 pcidata; + int reg = 0; + int activePCIfn = fn << 8; + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000025FL | 0x2800); + if (UxxxStatus) + return UxxxStatus; + reg = 16; + UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0, + 0xFFFFFFFF); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0, + 0x00000000); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + reg = 12; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &latence_timer); + if (UxxxStatus) + return UxxxStatus; + latence_timer &= 0xFFFF00FF; + latence_timer |= 0x00001600; + UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0x00, + latence_timer); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + reg = 4; + UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0x00, + 0x00); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + return 0; +} + +static int ftdi_elan_found_controller(struct usb_ftdi *ftdi, int fn, int quirk) +{ + int result; + int UxxxStatus; + UxxxStatus = ftdi_elan_setup_controller(ftdi, fn); + if (UxxxStatus) + return UxxxStatus; + result = ftdi_elan_check_controller(ftdi, quirk); + UxxxStatus = ftdi_elan_close_controller(ftdi, fn); + if (UxxxStatus) + return UxxxStatus; + return result; +} + +static int ftdi_elan_enumeratePCI(struct usb_ftdi *ftdi) +{ + u32 controlreg; + u8 sensebits; + int UxxxStatus; + UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x00000000L); + if (UxxxStatus) + return UxxxStatus; + msleep(750); + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x00000200L | 0x100); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x00000200L | 0x500); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000020CL | 0x000); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000020DL | 0x000); + if (UxxxStatus) + return UxxxStatus; + msleep(250); + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000020FL | 0x000); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000025FL | 0x800); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg); + if (UxxxStatus) + return UxxxStatus; + UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg); + if (UxxxStatus) + return UxxxStatus; + msleep(1000); + sensebits = (controlreg >> 16) & 0x000F; + if (0x0D == sensebits) + return 0; + else + return - ENXIO; +} + +static int ftdi_elan_setupOHCI(struct usb_ftdi *ftdi) +{ + int UxxxStatus; + u32 pcidata; + int reg = 0; + u8 fn; + int activePCIfn = 0; + int max_devices = 0; + int controllers = 0; + int unrecognized = 0; + ftdi->function = 0; + for (fn = 0; (fn < 4); fn++) { + u32 pciVID = 0; + u32 pciPID = 0; + int devices = 0; + activePCIfn = fn << 8; + UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, + &pcidata); + if (UxxxStatus) + return UxxxStatus; + pciVID = pcidata & 0xFFFF; + pciPID = (pcidata >> 16) & 0xFFFF; + if ((pciVID == PCI_VENDOR_ID_OPTI) && (pciPID == 0xc861)) { + devices = ftdi_elan_found_controller(ftdi, fn, 0); + controllers += 1; + } else if ((pciVID == PCI_VENDOR_ID_NEC) && (pciPID == 0x0035)) + { + devices = ftdi_elan_found_controller(ftdi, fn, 0); + controllers += 1; + } else if ((pciVID == PCI_VENDOR_ID_AL) && (pciPID == 0x5237)) { + devices = ftdi_elan_found_controller(ftdi, fn, 0); + controllers += 1; + } else if ((pciVID == PCI_VENDOR_ID_ATT) && (pciPID == 0x5802)) + { + devices = ftdi_elan_found_controller(ftdi, fn, 0); + controllers += 1; + } else if (pciVID == PCI_VENDOR_ID_AMD && pciPID == 0x740c) { + devices = ftdi_elan_found_controller(ftdi, fn, + OHCI_QUIRK_AMD756); + controllers += 1; + } else if (pciVID == PCI_VENDOR_ID_COMPAQ && pciPID == 0xa0f8) { + devices = ftdi_elan_found_controller(ftdi, fn, + OHCI_QUIRK_ZFMICRO); + controllers += 1; + } else if (0 == pcidata) { + } else + unrecognized += 1; + if (devices > max_devices) { + max_devices = devices; + ftdi->function = fn + 1; + ftdi->platform_data.vendor = pciVID; + ftdi->platform_data.device = pciPID; + } + } + if (ftdi->function > 0) { + UxxxStatus = ftdi_elan_setup_controller(ftdi, + ftdi->function - 1); + if (UxxxStatus) + return UxxxStatus; + return 0; + } else if (controllers > 0) { + return -ENXIO; + } else if (unrecognized > 0) { + return -ENXIO; + } else { + ftdi->enumerated = 0; + return -ENXIO; + } +} + + +/* +* we use only the first bulk-in and bulk-out endpoints +*/ +static int ftdi_elan_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + size_t buffer_size; + int i; + int retval = -ENOMEM; + struct usb_ftdi *ftdi; + + ftdi = kzalloc(sizeof(struct usb_ftdi), GFP_KERNEL); + if (!ftdi) { + printk(KERN_ERR "Out of memory\n"); + return -ENOMEM; + } + + mutex_lock(&ftdi_module_lock); + list_add_tail(&ftdi->ftdi_list, &ftdi_static_list); + ftdi->sequence_num = ++ftdi_instances; + mutex_unlock(&ftdi_module_lock); + ftdi_elan_init_kref(ftdi); + sema_init(&ftdi->sw_lock, 1); + ftdi->udev = usb_get_dev(interface_to_usbdev(interface)); + ftdi->interface = interface; + mutex_init(&ftdi->u132_lock); + ftdi->expected = 4; + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + if (!ftdi->bulk_in_endpointAddr && + usb_endpoint_is_bulk_in(endpoint)) { + buffer_size = usb_endpoint_maxp(endpoint); + ftdi->bulk_in_size = buffer_size; + ftdi->bulk_in_endpointAddr = endpoint->bEndpointAddress; + ftdi->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!ftdi->bulk_in_buffer) { + dev_err(&ftdi->udev->dev, "Could not allocate b" + "ulk_in_buffer\n"); + retval = -ENOMEM; + goto error; + } + } + if (!ftdi->bulk_out_endpointAddr && + usb_endpoint_is_bulk_out(endpoint)) { + ftdi->bulk_out_endpointAddr = + endpoint->bEndpointAddress; + } + } + if (!(ftdi->bulk_in_endpointAddr && ftdi->bulk_out_endpointAddr)) { + dev_err(&ftdi->udev->dev, "Could not find both bulk-in and bulk" + "-out endpoints\n"); + retval = -ENODEV; + goto error; + } + dev_info(&ftdi->udev->dev, "interface %d has I=%02X O=%02X\n", + iface_desc->desc.bInterfaceNumber, ftdi->bulk_in_endpointAddr, + ftdi->bulk_out_endpointAddr); + usb_set_intfdata(interface, ftdi); + if (iface_desc->desc.bInterfaceNumber == 0 && + ftdi->bulk_in_endpointAddr == 0x81 && + ftdi->bulk_out_endpointAddr == 0x02) { + retval = usb_register_dev(interface, &ftdi_elan_jtag_class); + if (retval) { + dev_err(&ftdi->udev->dev, "Not able to get a minor for " + "this device.\n"); + usb_set_intfdata(interface, NULL); + retval = -ENOMEM; + goto error; + } else { + ftdi->class = &ftdi_elan_jtag_class; + dev_info(&ftdi->udev->dev, "USB FDTI=%p JTAG interface " + "%d now attached to ftdi%d\n", ftdi, + iface_desc->desc.bInterfaceNumber, + interface->minor); + return 0; + } + } else if (iface_desc->desc.bInterfaceNumber == 1 && + ftdi->bulk_in_endpointAddr == 0x83 && + ftdi->bulk_out_endpointAddr == 0x04) { + ftdi->class = NULL; + dev_info(&ftdi->udev->dev, "USB FDTI=%p ELAN interface %d now a" + "ctivated\n", ftdi, iface_desc->desc.bInterfaceNumber); + INIT_DELAYED_WORK(&ftdi->status_work, ftdi_elan_status_work); + INIT_DELAYED_WORK(&ftdi->command_work, ftdi_elan_command_work); + INIT_DELAYED_WORK(&ftdi->respond_work, ftdi_elan_respond_work); + ftdi_status_queue_work(ftdi, msecs_to_jiffies(3 *1000)); + return 0; + } else { + dev_err(&ftdi->udev->dev, + "Could not find ELAN's U132 device\n"); + retval = -ENODEV; + goto error; + } + error:if (ftdi) { + ftdi_elan_put_kref(ftdi); + } + return retval; +} + +static void ftdi_elan_disconnect(struct usb_interface *interface) +{ + struct usb_ftdi *ftdi = usb_get_intfdata(interface); + ftdi->disconnected += 1; + if (ftdi->class) { + int minor = interface->minor; + struct usb_class_driver *class = ftdi->class; + usb_set_intfdata(interface, NULL); + usb_deregister_dev(interface, class); + dev_info(&ftdi->udev->dev, "USB FTDI U132 jtag interface on min" + "or %d now disconnected\n", minor); + } else { + ftdi_status_cancel_work(ftdi); + ftdi_command_cancel_work(ftdi); + ftdi_response_cancel_work(ftdi); + ftdi_elan_abandon_completions(ftdi); + ftdi_elan_abandon_targets(ftdi); + if (ftdi->registered) { + platform_device_unregister(&ftdi->platform_dev); + ftdi->synchronized = 0; + ftdi->enumerated = 0; + ftdi->initialized = 0; + ftdi->registered = 0; + } + flush_workqueue(status_queue); + flush_workqueue(command_queue); + flush_workqueue(respond_queue); + ftdi->disconnected += 1; + usb_set_intfdata(interface, NULL); + dev_info(&ftdi->udev->dev, "USB FTDI U132 host controller inter" + "face now disconnected\n"); + } + ftdi_elan_put_kref(ftdi); +} + +static struct usb_driver ftdi_elan_driver = { + .name = "ftdi-elan", + .probe = ftdi_elan_probe, + .disconnect = ftdi_elan_disconnect, + .id_table = ftdi_elan_table, +}; +static int __init ftdi_elan_init(void) +{ + int result; + printk(KERN_INFO "driver %s\n", ftdi_elan_driver.name); + mutex_init(&ftdi_module_lock); + INIT_LIST_HEAD(&ftdi_static_list); + status_queue = create_singlethread_workqueue("ftdi-status-control"); + if (!status_queue) + goto err_status_queue; + command_queue = create_singlethread_workqueue("ftdi-command-engine"); + if (!command_queue) + goto err_command_queue; + respond_queue = create_singlethread_workqueue("ftdi-respond-engine"); + if (!respond_queue) + goto err_respond_queue; + result = usb_register(&ftdi_elan_driver); + if (result) { + destroy_workqueue(status_queue); + destroy_workqueue(command_queue); + destroy_workqueue(respond_queue); + printk(KERN_ERR "usb_register failed. Error number %d\n", + result); + } + return result; + + err_respond_queue: + destroy_workqueue(command_queue); + err_command_queue: + destroy_workqueue(status_queue); + err_status_queue: + printk(KERN_ERR "%s couldn't create workqueue\n", ftdi_elan_driver.name); + return -ENOMEM; +} + +static void __exit ftdi_elan_exit(void) +{ + struct usb_ftdi *ftdi; + struct usb_ftdi *temp; + usb_deregister(&ftdi_elan_driver); + printk(KERN_INFO "ftdi_u132 driver deregistered\n"); + list_for_each_entry_safe(ftdi, temp, &ftdi_static_list, ftdi_list) { + ftdi_status_cancel_work(ftdi); + ftdi_command_cancel_work(ftdi); + ftdi_response_cancel_work(ftdi); + } flush_workqueue(status_queue); + destroy_workqueue(status_queue); + status_queue = NULL; + flush_workqueue(command_queue); + destroy_workqueue(command_queue); + command_queue = NULL; + flush_workqueue(respond_queue); + destroy_workqueue(respond_queue); + respond_queue = NULL; +} + + +module_init(ftdi_elan_init); +module_exit(ftdi_elan_exit); diff --git a/drivers/usb/misc/idmouse.c b/drivers/usb/misc/idmouse.c new file mode 100644 index 00000000..ce978384 --- /dev/null +++ b/drivers/usb/misc/idmouse.c @@ -0,0 +1,437 @@ +/* Siemens ID Mouse driver v0.6 + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + Copyright (C) 2004-5 by Florian 'Floe' Echtler + and Andreas 'ad' Deresch + + Derived from the USB Skeleton driver 1.1, + Copyright (C) 2003 Greg Kroah-Hartman (greg@kroah.com) + + Additional information provided by Martin Reising + + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* image constants */ +#define WIDTH 225 +#define HEIGHT 289 +#define HEADER "P5 225 289 255 " +#define IMGSIZE ((WIDTH * HEIGHT) + sizeof(HEADER)-1) + +/* version information */ +#define DRIVER_VERSION "0.6" +#define DRIVER_SHORT "idmouse" +#define DRIVER_AUTHOR "Florian 'Floe' Echtler " +#define DRIVER_DESC "Siemens ID Mouse FingerTIP Sensor Driver" + +/* minor number for misc USB devices */ +#define USB_IDMOUSE_MINOR_BASE 132 + +/* vendor and device IDs */ +#define ID_SIEMENS 0x0681 +#define ID_IDMOUSE 0x0005 +#define ID_CHERRY 0x0010 + +/* device ID table */ +static const struct usb_device_id idmouse_table[] = { + {USB_DEVICE(ID_SIEMENS, ID_IDMOUSE)}, /* Siemens ID Mouse (Professional) */ + {USB_DEVICE(ID_SIEMENS, ID_CHERRY )}, /* Cherry FingerTIP ID Board */ + {} /* terminating null entry */ +}; + +/* sensor commands */ +#define FTIP_RESET 0x20 +#define FTIP_ACQUIRE 0x21 +#define FTIP_RELEASE 0x22 +#define FTIP_BLINK 0x23 /* LSB of value = blink pulse width */ +#define FTIP_SCROLL 0x24 + +#define ftip_command(dev, command, value, index) \ + usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), command, \ + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, value, index, NULL, 0, 1000) + +MODULE_DEVICE_TABLE(usb, idmouse_table); +static DEFINE_MUTEX(open_disc_mutex); + +/* structure to hold all of our device specific stuff */ +struct usb_idmouse { + + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ + + unsigned char *bulk_in_buffer; /* the buffer to receive data */ + size_t bulk_in_size; /* the maximum bulk packet size */ + size_t orig_bi_size; /* same as above, but reported by the device */ + __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ + + int open; /* if the port is open or not */ + int present; /* if the device is not disconnected */ + struct mutex lock; /* locks this structure */ + +}; + +/* local function prototypes */ +static ssize_t idmouse_read(struct file *file, char __user *buffer, + size_t count, loff_t * ppos); + +static int idmouse_open(struct inode *inode, struct file *file); +static int idmouse_release(struct inode *inode, struct file *file); + +static int idmouse_probe(struct usb_interface *interface, + const struct usb_device_id *id); + +static void idmouse_disconnect(struct usb_interface *interface); +static int idmouse_suspend(struct usb_interface *intf, pm_message_t message); +static int idmouse_resume(struct usb_interface *intf); + +/* file operation pointers */ +static const struct file_operations idmouse_fops = { + .owner = THIS_MODULE, + .read = idmouse_read, + .open = idmouse_open, + .release = idmouse_release, + .llseek = default_llseek, +}; + +/* class driver information */ +static struct usb_class_driver idmouse_class = { + .name = "idmouse%d", + .fops = &idmouse_fops, + .minor_base = USB_IDMOUSE_MINOR_BASE, +}; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver idmouse_driver = { + .name = DRIVER_SHORT, + .probe = idmouse_probe, + .disconnect = idmouse_disconnect, + .suspend = idmouse_suspend, + .resume = idmouse_resume, + .reset_resume = idmouse_resume, + .id_table = idmouse_table, + .supports_autosuspend = 1, +}; + +static int idmouse_create_image(struct usb_idmouse *dev) +{ + int bytes_read; + int bulk_read; + int result; + + memcpy(dev->bulk_in_buffer, HEADER, sizeof(HEADER)-1); + bytes_read = sizeof(HEADER)-1; + + /* reset the device and set a fast blink rate */ + result = ftip_command(dev, FTIP_RELEASE, 0, 0); + if (result < 0) + goto reset; + result = ftip_command(dev, FTIP_BLINK, 1, 0); + if (result < 0) + goto reset; + + /* initialize the sensor - sending this command twice */ + /* significantly reduces the rate of failed reads */ + result = ftip_command(dev, FTIP_ACQUIRE, 0, 0); + if (result < 0) + goto reset; + result = ftip_command(dev, FTIP_ACQUIRE, 0, 0); + if (result < 0) + goto reset; + + /* start the readout - sending this command twice */ + /* presumably enables the high dynamic range mode */ + result = ftip_command(dev, FTIP_RESET, 0, 0); + if (result < 0) + goto reset; + result = ftip_command(dev, FTIP_RESET, 0, 0); + if (result < 0) + goto reset; + + /* loop over a blocking bulk read to get data from the device */ + while (bytes_read < IMGSIZE) { + result = usb_bulk_msg (dev->udev, + usb_rcvbulkpipe (dev->udev, dev->bulk_in_endpointAddr), + dev->bulk_in_buffer + bytes_read, + dev->bulk_in_size, &bulk_read, 5000); + if (result < 0) { + /* Maybe this error was caused by the increased packet size? */ + /* Reset to the original value and tell userspace to retry. */ + if (dev->bulk_in_size != dev->orig_bi_size) { + dev->bulk_in_size = dev->orig_bi_size; + result = -EAGAIN; + } + break; + } + if (signal_pending(current)) { + result = -EINTR; + break; + } + bytes_read += bulk_read; + } + + /* reset the device */ +reset: + ftip_command(dev, FTIP_RELEASE, 0, 0); + + /* check for valid image */ + /* right border should be black (0x00) */ + for (bytes_read = sizeof(HEADER)-1 + WIDTH-1; bytes_read < IMGSIZE; bytes_read += WIDTH) + if (dev->bulk_in_buffer[bytes_read] != 0x00) + return -EAGAIN; + + /* lower border should be white (0xFF) */ + for (bytes_read = IMGSIZE-WIDTH; bytes_read < IMGSIZE-1; bytes_read++) + if (dev->bulk_in_buffer[bytes_read] != 0xFF) + return -EAGAIN; + + /* should be IMGSIZE == 65040 */ + dev_dbg(&dev->interface->dev, "read %d bytes fingerprint data\n", + bytes_read); + return result; +} + +/* PM operations are nops as this driver does IO only during open() */ +static int idmouse_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +static int idmouse_resume(struct usb_interface *intf) +{ + return 0; +} + +static inline void idmouse_delete(struct usb_idmouse *dev) +{ + kfree(dev->bulk_in_buffer); + kfree(dev); +} + +static int idmouse_open(struct inode *inode, struct file *file) +{ + struct usb_idmouse *dev; + struct usb_interface *interface; + int result; + + /* get the interface from minor number and driver information */ + interface = usb_find_interface (&idmouse_driver, iminor (inode)); + if (!interface) + return -ENODEV; + + mutex_lock(&open_disc_mutex); + /* get the device information block from the interface */ + dev = usb_get_intfdata(interface); + if (!dev) { + mutex_unlock(&open_disc_mutex); + return -ENODEV; + } + + /* lock this device */ + mutex_lock(&dev->lock); + mutex_unlock(&open_disc_mutex); + + /* check if already open */ + if (dev->open) { + + /* already open, so fail */ + result = -EBUSY; + + } else { + + /* create a new image and check for success */ + result = usb_autopm_get_interface(interface); + if (result) + goto error; + result = idmouse_create_image (dev); + if (result) + goto error; + usb_autopm_put_interface(interface); + + /* increment our usage count for the driver */ + ++dev->open; + + /* save our object in the file's private structure */ + file->private_data = dev; + + } + +error: + + /* unlock this device */ + mutex_unlock(&dev->lock); + return result; +} + +static int idmouse_release(struct inode *inode, struct file *file) +{ + struct usb_idmouse *dev; + + dev = file->private_data; + + if (dev == NULL) + return -ENODEV; + + mutex_lock(&open_disc_mutex); + /* lock our device */ + mutex_lock(&dev->lock); + + /* are we really open? */ + if (dev->open <= 0) { + mutex_unlock(&dev->lock); + mutex_unlock(&open_disc_mutex); + return -ENODEV; + } + + --dev->open; + + if (!dev->present) { + /* the device was unplugged before the file was released */ + mutex_unlock(&dev->lock); + mutex_unlock(&open_disc_mutex); + idmouse_delete(dev); + } else { + mutex_unlock(&dev->lock); + mutex_unlock(&open_disc_mutex); + } + return 0; +} + +static ssize_t idmouse_read(struct file *file, char __user *buffer, size_t count, + loff_t * ppos) +{ + struct usb_idmouse *dev = file->private_data; + int result; + + /* lock this object */ + mutex_lock(&dev->lock); + + /* verify that the device wasn't unplugged */ + if (!dev->present) { + mutex_unlock(&dev->lock); + return -ENODEV; + } + + result = simple_read_from_buffer(buffer, count, ppos, + dev->bulk_in_buffer, IMGSIZE); + /* unlock the device */ + mutex_unlock(&dev->lock); + return result; +} + +static int idmouse_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_idmouse *dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int result; + + /* check if we have gotten the data or the hid interface */ + iface_desc = &interface->altsetting[0]; + if (iface_desc->desc.bInterfaceClass != 0x0A) + return -ENODEV; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + + mutex_init(&dev->lock); + dev->udev = udev; + dev->interface = interface; + + /* set up the endpoint information - use only the first bulk-in endpoint */ + endpoint = &iface_desc->endpoint[0].desc; + if (!dev->bulk_in_endpointAddr && usb_endpoint_is_bulk_in(endpoint)) { + /* we found a bulk in endpoint */ + dev->orig_bi_size = usb_endpoint_maxp(endpoint); + dev->bulk_in_size = 0x200; /* works _much_ faster */ + dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; + dev->bulk_in_buffer = + kmalloc(IMGSIZE + dev->bulk_in_size, GFP_KERNEL); + + if (!dev->bulk_in_buffer) { + dev_err(&interface->dev, "Unable to allocate input buffer.\n"); + idmouse_delete(dev); + return -ENOMEM; + } + } + + if (!(dev->bulk_in_endpointAddr)) { + dev_err(&interface->dev, "Unable to find bulk-in endpoint.\n"); + idmouse_delete(dev); + return -ENODEV; + } + /* allow device read, write and ioctl */ + dev->present = 1; + + /* we can register the device now, as it is ready */ + usb_set_intfdata(interface, dev); + result = usb_register_dev(interface, &idmouse_class); + if (result) { + /* something prevented us from registering this device */ + dev_err(&interface->dev, "Unble to allocate minor number.\n"); + usb_set_intfdata(interface, NULL); + idmouse_delete(dev); + return result; + } + + /* be noisy */ + dev_info(&interface->dev,"%s now attached\n",DRIVER_DESC); + + return 0; +} + +static void idmouse_disconnect(struct usb_interface *interface) +{ + struct usb_idmouse *dev; + + /* get device structure */ + dev = usb_get_intfdata(interface); + + /* give back our minor */ + usb_deregister_dev(interface, &idmouse_class); + + mutex_lock(&open_disc_mutex); + usb_set_intfdata(interface, NULL); + /* lock the device */ + mutex_lock(&dev->lock); + mutex_unlock(&open_disc_mutex); + + /* prevent device read, write and ioctl */ + dev->present = 0; + + /* if the device is opened, idmouse_release will clean this up */ + if (!dev->open) { + mutex_unlock(&dev->lock); + idmouse_delete(dev); + } else { + /* unlock */ + mutex_unlock(&dev->lock); + } + + dev_info(&interface->dev, "disconnected\n"); +} + +module_usb_driver(idmouse_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c new file mode 100644 index 00000000..d36f34e2 --- /dev/null +++ b/drivers/usb/misc/iowarrior.c @@ -0,0 +1,930 @@ +/* + * Native support for the I/O-Warrior USB devices + * + * Copyright (c) 2003-2005 Code Mercenaries GmbH + * written by Christian Lucht + * + * based on + + * usb-skeleton.c by Greg Kroah-Hartman + * brlvger.c by Stephane Dalton + * and St�hane Doyon + * + * Released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Version Information */ +#define DRIVER_VERSION "v0.4.0" +#define DRIVER_AUTHOR "Christian Lucht " +#define DRIVER_DESC "USB IO-Warrior driver (Linux 2.6.x)" + +#define USB_VENDOR_ID_CODEMERCS 1984 +/* low speed iowarrior */ +#define USB_DEVICE_ID_CODEMERCS_IOW40 0x1500 +#define USB_DEVICE_ID_CODEMERCS_IOW24 0x1501 +#define USB_DEVICE_ID_CODEMERCS_IOWPV1 0x1511 +#define USB_DEVICE_ID_CODEMERCS_IOWPV2 0x1512 +/* full speed iowarrior */ +#define USB_DEVICE_ID_CODEMERCS_IOW56 0x1503 + +/* Get a minor range for your devices from the usb maintainer */ +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define IOWARRIOR_MINOR_BASE 0 +#else +#define IOWARRIOR_MINOR_BASE 208 // SKELETON_MINOR_BASE 192 + 16, not official yet +#endif + +/* interrupt input queue size */ +#define MAX_INTERRUPT_BUFFER 16 +/* + maximum number of urbs that are submitted for writes at the same time, + this applies to the IOWarrior56 only! + IOWarrior24 and IOWarrior40 use synchronous usb_control_msg calls. +*/ +#define MAX_WRITES_IN_FLIGHT 4 + +/* Use our own dbg macro */ +#undef dbg +#define dbg( format, arg... ) do { if( debug ) printk( KERN_DEBUG __FILE__ ": " format "\n" , ## arg ); } while ( 0 ) + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* Module parameters */ +static DEFINE_MUTEX(iowarrior_mutex); +static bool debug = 0; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "debug=1 enables debugging messages"); + +static struct usb_driver iowarrior_driver; +static DEFINE_MUTEX(iowarrior_open_disc_lock); + +/*--------------*/ +/* data */ +/*--------------*/ + +/* Structure to hold all of our device specific stuff */ +struct iowarrior { + struct mutex mutex; /* locks this structure */ + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ + unsigned char minor; /* the starting minor number for this device */ + struct usb_endpoint_descriptor *int_out_endpoint; /* endpoint for reading (needed for IOW56 only) */ + struct usb_endpoint_descriptor *int_in_endpoint; /* endpoint for reading */ + struct urb *int_in_urb; /* the urb for reading data */ + unsigned char *int_in_buffer; /* buffer for data to be read */ + unsigned char serial_number; /* to detect lost packages */ + unsigned char *read_queue; /* size is MAX_INTERRUPT_BUFFER * packet size */ + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; /* wait-queue for writing to the device */ + atomic_t write_busy; /* number of write-urbs submitted */ + atomic_t read_idx; + atomic_t intr_idx; + spinlock_t intr_idx_lock; /* protects intr_idx */ + atomic_t overflow_flag; /* signals an index 'rollover' */ + int present; /* this is 1 as long as the device is connected */ + int opened; /* this is 1 if the device is currently open */ + char chip_serial[9]; /* the serial number string of the chip connected */ + int report_size; /* number of bytes in a report */ + u16 product_id; +}; + +/*--------------*/ +/* globals */ +/*--------------*/ + +/* + * USB spec identifies 5 second timeouts. + */ +#define GET_TIMEOUT 5 +#define USB_REQ_GET_REPORT 0x01 +//#if 0 +static int usb_get_report(struct usb_device *dev, + struct usb_host_interface *inter, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, (type << 8) + id, + inter->desc.bInterfaceNumber, buf, size, + GET_TIMEOUT*HZ); +} +//#endif + +#define USB_REQ_SET_REPORT 0x09 + +static int usb_set_report(struct usb_interface *intf, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(interface_to_usbdev(intf), + usb_sndctrlpipe(interface_to_usbdev(intf), 0), + USB_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (type << 8) + id, + intf->cur_altsetting->desc.bInterfaceNumber, buf, + size, HZ); +} + +/*---------------------*/ +/* driver registration */ +/*---------------------*/ +/* table of devices that work with this driver */ +static const struct usb_device_id iowarrior_ids[] = { + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW40)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW24)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOWPV1)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOWPV2)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW56)}, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, iowarrior_ids); + +/* + * USB callback handler for reading data + */ +static void iowarrior_callback(struct urb *urb) +{ + struct iowarrior *dev = urb->context; + int intr_idx; + int read_idx; + int aux_idx; + int offset; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: + goto exit; + } + + spin_lock(&dev->intr_idx_lock); + intr_idx = atomic_read(&dev->intr_idx); + /* aux_idx become previous intr_idx */ + aux_idx = (intr_idx == 0) ? (MAX_INTERRUPT_BUFFER - 1) : (intr_idx - 1); + read_idx = atomic_read(&dev->read_idx); + + /* queue is not empty and it's interface 0 */ + if ((intr_idx != read_idx) + && (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0)) { + /* + 1 for serial number */ + offset = aux_idx * (dev->report_size + 1); + if (!memcmp + (dev->read_queue + offset, urb->transfer_buffer, + dev->report_size)) { + /* equal values on interface 0 will be ignored */ + spin_unlock(&dev->intr_idx_lock); + goto exit; + } + } + + /* aux_idx become next intr_idx */ + aux_idx = (intr_idx == (MAX_INTERRUPT_BUFFER - 1)) ? 0 : (intr_idx + 1); + if (read_idx == aux_idx) { + /* queue full, dropping oldest input */ + read_idx = (++read_idx == MAX_INTERRUPT_BUFFER) ? 0 : read_idx; + atomic_set(&dev->read_idx, read_idx); + atomic_set(&dev->overflow_flag, 1); + } + + /* +1 for serial number */ + offset = intr_idx * (dev->report_size + 1); + memcpy(dev->read_queue + offset, urb->transfer_buffer, + dev->report_size); + *(dev->read_queue + offset + (dev->report_size)) = dev->serial_number++; + + atomic_set(&dev->intr_idx, aux_idx); + spin_unlock(&dev->intr_idx_lock); + /* tell the blocking read about the new data */ + wake_up_interruptible(&dev->read_wait); + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->interface->dev, "%s - usb_submit_urb failed with result %d\n", + __func__, retval); + +} + +/* + * USB Callback handler for write-ops + */ +static void iowarrior_write_callback(struct urb *urb) +{ + struct iowarrior *dev; + int status = urb->status; + + dev = urb->context; + /* sync/async unlink faults aren't errors */ + if (status && + !(status == -ENOENT || + status == -ECONNRESET || status == -ESHUTDOWN)) { + dbg("%s - nonzero write bulk status received: %d", + __func__, status); + } + /* free up our allocated buffer */ + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + /* tell a waiting writer the interrupt-out-pipe is available again */ + atomic_dec(&dev->write_busy); + wake_up_interruptible(&dev->write_wait); +} + +/** + * iowarrior_delete + */ +static inline void iowarrior_delete(struct iowarrior *dev) +{ + dbg("%s - minor %d", __func__, dev->minor); + kfree(dev->int_in_buffer); + usb_free_urb(dev->int_in_urb); + kfree(dev->read_queue); + kfree(dev); +} + +/*---------------------*/ +/* fops implementation */ +/*---------------------*/ + +static int read_index(struct iowarrior *dev) +{ + int intr_idx, read_idx; + + read_idx = atomic_read(&dev->read_idx); + intr_idx = atomic_read(&dev->intr_idx); + + return (read_idx == intr_idx ? -1 : read_idx); +} + +/** + * iowarrior_read + */ +static ssize_t iowarrior_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct iowarrior *dev; + int read_idx; + int offset; + + dev = file->private_data; + + /* verify that the device wasn't unplugged */ + if (dev == NULL || !dev->present) + return -ENODEV; + + dbg("%s - minor %d, count = %zd", __func__, dev->minor, count); + + /* read count must be packet size (+ time stamp) */ + if ((count != dev->report_size) + && (count != (dev->report_size + 1))) + return -EINVAL; + + /* repeat until no buffer overrun in callback handler occur */ + do { + atomic_set(&dev->overflow_flag, 0); + if ((read_idx = read_index(dev)) == -1) { + /* queue emty */ + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + else { + //next line will return when there is either new data, or the device is unplugged + int r = wait_event_interruptible(dev->read_wait, + (!dev->present + || (read_idx = + read_index + (dev)) != + -1)); + if (r) { + //we were interrupted by a signal + return -ERESTART; + } + if (!dev->present) { + //The device was unplugged + return -ENODEV; + } + if (read_idx == -1) { + // Can this happen ??? + return 0; + } + } + } + + offset = read_idx * (dev->report_size + 1); + if (copy_to_user(buffer, dev->read_queue + offset, count)) { + return -EFAULT; + } + } while (atomic_read(&dev->overflow_flag)); + + read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx; + atomic_set(&dev->read_idx, read_idx); + return count; +} + +/* + * iowarrior_write + */ +static ssize_t iowarrior_write(struct file *file, + const char __user *user_buffer, + size_t count, loff_t *ppos) +{ + struct iowarrior *dev; + int retval = 0; + char *buf = NULL; /* for IOW24 and IOW56 we need a buffer */ + struct urb *int_out_urb = NULL; + + dev = file->private_data; + + mutex_lock(&dev->mutex); + /* verify that the device wasn't unplugged */ + if (!dev->present) { + retval = -ENODEV; + goto exit; + } + dbg("%s - minor %d, count = %zd", __func__, dev->minor, count); + /* if count is 0 we're already done */ + if (count == 0) { + retval = 0; + goto exit; + } + /* We only accept full reports */ + if (count != dev->report_size) { + retval = -EINVAL; + goto exit; + } + switch (dev->product_id) { + case USB_DEVICE_ID_CODEMERCS_IOW24: + case USB_DEVICE_ID_CODEMERCS_IOWPV1: + case USB_DEVICE_ID_CODEMERCS_IOWPV2: + case USB_DEVICE_ID_CODEMERCS_IOW40: + /* IOW24 and IOW40 use a synchronous call */ + buf = kmalloc(count, GFP_KERNEL); + if (!buf) { + retval = -ENOMEM; + goto exit; + } + if (copy_from_user(buf, user_buffer, count)) { + retval = -EFAULT; + kfree(buf); + goto exit; + } + retval = usb_set_report(dev->interface, 2, 0, buf, count); + kfree(buf); + goto exit; + break; + case USB_DEVICE_ID_CODEMERCS_IOW56: + /* The IOW56 uses asynchronous IO and more urbs */ + if (atomic_read(&dev->write_busy) == MAX_WRITES_IN_FLIGHT) { + /* Wait until we are below the limit for submitted urbs */ + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto exit; + } else { + retval = wait_event_interruptible(dev->write_wait, + (!dev->present || (atomic_read (&dev-> write_busy) < MAX_WRITES_IN_FLIGHT))); + if (retval) { + /* we were interrupted by a signal */ + retval = -ERESTART; + goto exit; + } + if (!dev->present) { + /* The device was unplugged */ + retval = -ENODEV; + goto exit; + } + if (!dev->opened) { + /* We were closed while waiting for an URB */ + retval = -ENODEV; + goto exit; + } + } + } + atomic_inc(&dev->write_busy); + int_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!int_out_urb) { + retval = -ENOMEM; + dbg("%s Unable to allocate urb ", __func__); + goto error_no_urb; + } + buf = usb_alloc_coherent(dev->udev, dev->report_size, + GFP_KERNEL, &int_out_urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + dbg("%s Unable to allocate buffer ", __func__); + goto error_no_buffer; + } + usb_fill_int_urb(int_out_urb, dev->udev, + usb_sndintpipe(dev->udev, + dev->int_out_endpoint->bEndpointAddress), + buf, dev->report_size, + iowarrior_write_callback, dev, + dev->int_out_endpoint->bInterval); + int_out_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + if (copy_from_user(buf, user_buffer, count)) { + retval = -EFAULT; + goto error; + } + retval = usb_submit_urb(int_out_urb, GFP_KERNEL); + if (retval) { + dbg("%s submit error %d for urb nr.%d", __func__, + retval, atomic_read(&dev->write_busy)); + goto error; + } + /* submit was ok */ + retval = count; + usb_free_urb(int_out_urb); + goto exit; + break; + default: + /* what do we have here ? An unsupported Product-ID ? */ + dev_err(&dev->interface->dev, "%s - not supported for product=0x%x\n", + __func__, dev->product_id); + retval = -EFAULT; + goto exit; + break; + } +error: + usb_free_coherent(dev->udev, dev->report_size, buf, + int_out_urb->transfer_dma); +error_no_buffer: + usb_free_urb(int_out_urb); +error_no_urb: + atomic_dec(&dev->write_busy); + wake_up_interruptible(&dev->write_wait); +exit: + mutex_unlock(&dev->mutex); + return retval; +} + +/** + * iowarrior_ioctl + */ +static long iowarrior_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct iowarrior *dev = NULL; + __u8 *buffer; + __u8 __user *user_buffer; + int retval; + int io_res; /* checks for bytes read/written and copy_to/from_user results */ + + dev = file->private_data; + if (dev == NULL) { + return -ENODEV; + } + + buffer = kzalloc(dev->report_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + /* lock this object */ + mutex_lock(&iowarrior_mutex); + mutex_lock(&dev->mutex); + + /* verify that the device wasn't unplugged */ + if (!dev->present) { + retval = -ENODEV; + goto error_out; + } + + dbg("%s - minor %d, cmd 0x%.4x, arg %ld", __func__, dev->minor, cmd, + arg); + + retval = 0; + io_res = 0; + switch (cmd) { + case IOW_WRITE: + if (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24 || + dev->product_id == USB_DEVICE_ID_CODEMERCS_IOWPV1 || + dev->product_id == USB_DEVICE_ID_CODEMERCS_IOWPV2 || + dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW40) { + user_buffer = (__u8 __user *)arg; + io_res = copy_from_user(buffer, user_buffer, + dev->report_size); + if (io_res) { + retval = -EFAULT; + } else { + io_res = usb_set_report(dev->interface, 2, 0, + buffer, + dev->report_size); + if (io_res < 0) + retval = io_res; + } + } else { + retval = -EINVAL; + dev_err(&dev->interface->dev, + "ioctl 'IOW_WRITE' is not supported for product=0x%x.\n", + dev->product_id); + } + break; + case IOW_READ: + user_buffer = (__u8 __user *)arg; + io_res = usb_get_report(dev->udev, + dev->interface->cur_altsetting, 1, 0, + buffer, dev->report_size); + if (io_res < 0) + retval = io_res; + else { + io_res = copy_to_user(user_buffer, buffer, dev->report_size); + if (io_res) + retval = -EFAULT; + } + break; + case IOW_GETINFO: + { + /* Report available information for the device */ + struct iowarrior_info info; + /* needed for power consumption */ + struct usb_config_descriptor *cfg_descriptor = &dev->udev->actconfig->desc; + + memset(&info, 0, sizeof(info)); + /* directly from the descriptor */ + info.vendor = le16_to_cpu(dev->udev->descriptor.idVendor); + info.product = dev->product_id; + info.revision = le16_to_cpu(dev->udev->descriptor.bcdDevice); + + /* 0==UNKNOWN, 1==LOW(usb1.1) ,2=FULL(usb1.1), 3=HIGH(usb2.0) */ + info.speed = le16_to_cpu(dev->udev->speed); + info.if_num = dev->interface->cur_altsetting->desc.bInterfaceNumber; + info.report_size = dev->report_size; + + /* serial number string has been read earlier 8 chars or empty string */ + memcpy(info.serial, dev->chip_serial, + sizeof(dev->chip_serial)); + if (cfg_descriptor == NULL) { + info.power = -1; /* no information available */ + } else { + /* the MaxPower is stored in units of 2mA to make it fit into a byte-value */ + info.power = cfg_descriptor->bMaxPower * 2; + } + io_res = copy_to_user((struct iowarrior_info __user *)arg, &info, + sizeof(struct iowarrior_info)); + if (io_res) + retval = -EFAULT; + break; + } + default: + /* return that we did not understand this ioctl call */ + retval = -ENOTTY; + break; + } +error_out: + /* unlock the device */ + mutex_unlock(&dev->mutex); + mutex_unlock(&iowarrior_mutex); + kfree(buffer); + return retval; +} + +/** + * iowarrior_open + */ +static int iowarrior_open(struct inode *inode, struct file *file) +{ + struct iowarrior *dev = NULL; + struct usb_interface *interface; + int subminor; + int retval = 0; + + dbg("%s", __func__); + + mutex_lock(&iowarrior_mutex); + subminor = iminor(inode); + + interface = usb_find_interface(&iowarrior_driver, subminor); + if (!interface) { + mutex_unlock(&iowarrior_mutex); + printk(KERN_ERR "%s - error, can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + mutex_lock(&iowarrior_open_disc_lock); + dev = usb_get_intfdata(interface); + if (!dev) { + mutex_unlock(&iowarrior_open_disc_lock); + mutex_unlock(&iowarrior_mutex); + return -ENODEV; + } + + mutex_lock(&dev->mutex); + mutex_unlock(&iowarrior_open_disc_lock); + + /* Only one process can open each device, no sharing. */ + if (dev->opened) { + retval = -EBUSY; + goto out; + } + + /* setup interrupt handler for receiving values */ + if ((retval = usb_submit_urb(dev->int_in_urb, GFP_KERNEL)) < 0) { + dev_err(&interface->dev, "Error %d while submitting URB\n", retval); + retval = -EFAULT; + goto out; + } + /* increment our usage count for the driver */ + ++dev->opened; + /* save our object in the file's private structure */ + file->private_data = dev; + retval = 0; + +out: + mutex_unlock(&dev->mutex); + mutex_unlock(&iowarrior_mutex); + return retval; +} + +/** + * iowarrior_release + */ +static int iowarrior_release(struct inode *inode, struct file *file) +{ + struct iowarrior *dev; + int retval = 0; + + dev = file->private_data; + if (dev == NULL) { + return -ENODEV; + } + + dbg("%s - minor %d", __func__, dev->minor); + + /* lock our device */ + mutex_lock(&dev->mutex); + + if (dev->opened <= 0) { + retval = -ENODEV; /* close called more than once */ + mutex_unlock(&dev->mutex); + } else { + dev->opened = 0; /* we're closing now */ + retval = 0; + if (dev->present) { + /* + The device is still connected so we only shutdown + pending read-/write-ops. + */ + usb_kill_urb(dev->int_in_urb); + wake_up_interruptible(&dev->read_wait); + wake_up_interruptible(&dev->write_wait); + mutex_unlock(&dev->mutex); + } else { + /* The device was unplugged, cleanup resources */ + mutex_unlock(&dev->mutex); + iowarrior_delete(dev); + } + } + return retval; +} + +static unsigned iowarrior_poll(struct file *file, poll_table * wait) +{ + struct iowarrior *dev = file->private_data; + unsigned int mask = 0; + + if (!dev->present) + return POLLERR | POLLHUP; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (!dev->present) + return POLLERR | POLLHUP; + + if (read_index(dev) != -1) + mask |= POLLIN | POLLRDNORM; + + if (atomic_read(&dev->write_busy) < MAX_WRITES_IN_FLIGHT) + mask |= POLLOUT | POLLWRNORM; + return mask; +} + +/* + * File operations needed when we register this driver. + * This assumes that this driver NEEDS file operations, + * of course, which means that the driver is expected + * to have a node in the /dev directory. If the USB + * device were for a network interface then the driver + * would use "struct net_driver" instead, and a serial + * device would use "struct tty_driver". + */ +static const struct file_operations iowarrior_fops = { + .owner = THIS_MODULE, + .write = iowarrior_write, + .read = iowarrior_read, + .unlocked_ioctl = iowarrior_ioctl, + .open = iowarrior_open, + .release = iowarrior_release, + .poll = iowarrior_poll, + .llseek = noop_llseek, +}; + +static char *iowarrior_devnode(struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev)); +} + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with devfs and the driver core + */ +static struct usb_class_driver iowarrior_class = { + .name = "iowarrior%d", + .devnode = iowarrior_devnode, + .fops = &iowarrior_fops, + .minor_base = IOWARRIOR_MINOR_BASE, +}; + +/*---------------------------------*/ +/* probe and disconnect functions */ +/*---------------------------------*/ +/** + * iowarrior_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int iowarrior_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct iowarrior *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int i; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + return retval; + } + + mutex_init(&dev->mutex); + + atomic_set(&dev->intr_idx, 0); + atomic_set(&dev->read_idx, 0); + spin_lock_init(&dev->intr_idx_lock); + atomic_set(&dev->overflow_flag, 0); + init_waitqueue_head(&dev->read_wait); + atomic_set(&dev->write_busy, 0); + init_waitqueue_head(&dev->write_wait); + + dev->udev = udev; + dev->interface = interface; + + iface_desc = interface->cur_altsetting; + dev->product_id = le16_to_cpu(udev->descriptor.idProduct); + + /* set up the endpoint information */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) + dev->int_in_endpoint = endpoint; + if (usb_endpoint_is_int_out(endpoint)) + /* this one will match for the IOWarrior56 only */ + dev->int_out_endpoint = endpoint; + } + /* we have to check the report_size often, so remember it in the endianness suitable for our machine */ + dev->report_size = usb_endpoint_maxp(dev->int_in_endpoint); + if ((dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) && + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56)) + /* IOWarrior56 has wMaxPacketSize different from report size */ + dev->report_size = 7; + + /* create the urb and buffer for reading */ + dev->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->int_in_urb) { + dev_err(&interface->dev, "Couldn't allocate interrupt_in_urb\n"); + goto error; + } + dev->int_in_buffer = kmalloc(dev->report_size, GFP_KERNEL); + if (!dev->int_in_buffer) { + dev_err(&interface->dev, "Couldn't allocate int_in_buffer\n"); + goto error; + } + usb_fill_int_urb(dev->int_in_urb, dev->udev, + usb_rcvintpipe(dev->udev, + dev->int_in_endpoint->bEndpointAddress), + dev->int_in_buffer, dev->report_size, + iowarrior_callback, dev, + dev->int_in_endpoint->bInterval); + /* create an internal buffer for interrupt data from the device */ + dev->read_queue = + kmalloc(((dev->report_size + 1) * MAX_INTERRUPT_BUFFER), + GFP_KERNEL); + if (!dev->read_queue) { + dev_err(&interface->dev, "Couldn't allocate read_queue\n"); + goto error; + } + /* Get the serial-number of the chip */ + memset(dev->chip_serial, 0x00, sizeof(dev->chip_serial)); + usb_string(udev, udev->descriptor.iSerialNumber, dev->chip_serial, + sizeof(dev->chip_serial)); + if (strlen(dev->chip_serial) != 8) + memset(dev->chip_serial, 0x00, sizeof(dev->chip_serial)); + + /* Set the idle timeout to 0, if this is interface 0 */ + if (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) { + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x0A, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0, + 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + } + /* allow device read and ioctl */ + dev->present = 1; + + /* we can register the device now, as it is ready */ + usb_set_intfdata(interface, dev); + + retval = usb_register_dev(interface, &iowarrior_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&interface->dev, "Not able to get a minor for this device.\n"); + usb_set_intfdata(interface, NULL); + goto error; + } + + dev->minor = interface->minor; + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d " + "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial, + iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE); + return retval; + +error: + iowarrior_delete(dev); + return retval; +} + +/** + * iowarrior_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void iowarrior_disconnect(struct usb_interface *interface) +{ + struct iowarrior *dev; + int minor; + + dev = usb_get_intfdata(interface); + mutex_lock(&iowarrior_open_disc_lock); + usb_set_intfdata(interface, NULL); + + minor = dev->minor; + + /* give back our minor */ + usb_deregister_dev(interface, &iowarrior_class); + + mutex_lock(&dev->mutex); + + /* prevent device read, write and ioctl */ + dev->present = 0; + + mutex_unlock(&dev->mutex); + mutex_unlock(&iowarrior_open_disc_lock); + + if (dev->opened) { + /* There is a process that holds a filedescriptor to the device , + so we only shutdown read-/write-ops going on. + Deleting the device is postponed until close() was called. + */ + usb_kill_urb(dev->int_in_urb); + wake_up_interruptible(&dev->read_wait); + wake_up_interruptible(&dev->write_wait); + } else { + /* no process is using the device, cleanup now */ + iowarrior_delete(dev); + } + + dev_info(&interface->dev, "I/O-Warror #%d now disconnected\n", + minor - IOWARRIOR_MINOR_BASE); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver iowarrior_driver = { + .name = "iowarrior", + .probe = iowarrior_probe, + .disconnect = iowarrior_disconnect, + .id_table = iowarrior_ids, +}; + +module_usb_driver(iowarrior_driver); diff --git a/drivers/usb/misc/isight_firmware.c b/drivers/usb/misc/isight_firmware.c new file mode 100644 index 00000000..1c61830e --- /dev/null +++ b/drivers/usb/misc/isight_firmware.c @@ -0,0 +1,134 @@ +/* + * Driver for loading USB isight firmware + * + * Copyright (C) 2008 Matthew Garrett + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 2. + * + * The USB isight cameras in recent Apples are roughly compatible with the USB + * video class specification, and can be driven by uvcvideo. However, they + * need firmware to be loaded beforehand. After firmware loading, the device + * detaches from the USB bus and reattaches with a new device ID. It can then + * be claimed by the uvc driver. + * + * The firmware is non-free and must be extracted by the user. Tools to do this + * are available at http://bersace03.free.fr/ift/ + * + * The isight firmware loading was reverse engineered by Johannes Berg + * , and this driver is based on code by Ronald + * Bultje + */ + +#include +#include +#include +#include +#include + +static const struct usb_device_id id_table[] = { + {USB_DEVICE(0x05ac, 0x8300)}, + {}, +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static int isight_firmware_load(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + int llen, len, req, ret = 0; + const struct firmware *firmware; + unsigned char *buf = kmalloc(50, GFP_KERNEL); + unsigned char data[4]; + const u8 *ptr; + + if (!buf) + return -ENOMEM; + + if (request_firmware(&firmware, "isight.fw", &dev->dev) != 0) { + printk(KERN_ERR "Unable to load isight firmware\n"); + ret = -ENODEV; + goto out; + } + + ptr = firmware->data; + + buf[0] = 0x01; + if (usb_control_msg + (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, buf, 1, + 300) != 1) { + printk(KERN_ERR + "Failed to initialise isight firmware loader\n"); + ret = -ENODEV; + goto out; + } + + while (ptr+4 <= firmware->data+firmware->size) { + memcpy(data, ptr, 4); + len = (data[0] << 8 | data[1]); + req = (data[2] << 8 | data[3]); + ptr += 4; + + if (len == 0x8001) + break; /* success */ + else if (len == 0) + continue; + + for (; len > 0; req += 50) { + llen = min(len, 50); + len -= llen; + if (ptr+llen > firmware->data+firmware->size) { + printk(KERN_ERR + "Malformed isight firmware"); + ret = -ENODEV; + goto out; + } + memcpy(buf, ptr, llen); + + ptr += llen; + + if (usb_control_msg + (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, req, 0, + buf, llen, 300) != llen) { + printk(KERN_ERR + "Failed to load isight firmware\n"); + ret = -ENODEV; + goto out; + } + + } + } + + buf[0] = 0x00; + if (usb_control_msg + (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, buf, 1, + 300) != 1) { + printk(KERN_ERR "isight firmware loading completion failed\n"); + ret = -ENODEV; + } + +out: + kfree(buf); + release_firmware(firmware); + return ret; +} + +MODULE_FIRMWARE("isight.fw"); + +static void isight_firmware_disconnect(struct usb_interface *intf) +{ +} + +static struct usb_driver isight_firmware_driver = { + .name = "isight_firmware", + .probe = isight_firmware_load, + .disconnect = isight_firmware_disconnect, + .id_table = id_table, +}; + +module_usb_driver(isight_firmware_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matthew Garrett "); diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c new file mode 100644 index 00000000..ac762299 --- /dev/null +++ b/drivers/usb/misc/ldusb.c @@ -0,0 +1,828 @@ +/** + * Generic USB driver for report based interrupt in/out devices + * like LD Didactic's USB devices. LD Didactic's USB devices are + * HID devices which do not use HID report definitons (they use + * raw interrupt in and our reports only for communication). + * + * This driver uses a ring buffer for time critical reading of + * interrupt in reports and provides read and write methods for + * raw interrupt reports (similar to the Windows HID driver). + * Devices based on the book USB COMPLETE by Jan Axelson may need + * such a compatibility to the Windows HID driver. + * + * Copyright (C) 2005 Michael Hund + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * Derived from Lego USB Tower driver + * Copyright (C) 2003 David Glance + * 2001-2004 Juergen Stuber + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Define these values to match your devices */ +#define USB_VENDOR_ID_LD 0x0f11 /* USB Vendor ID of LD Didactic GmbH */ +#define USB_DEVICE_ID_LD_CASSY 0x1000 /* USB Product ID of CASSY-S modules with 8 bytes endpoint size */ +#define USB_DEVICE_ID_LD_CASSY2 0x1001 /* USB Product ID of CASSY-S modules with 64 bytes endpoint size */ +#define USB_DEVICE_ID_LD_POCKETCASSY 0x1010 /* USB Product ID of Pocket-CASSY */ +#define USB_DEVICE_ID_LD_POCKETCASSY2 0x1011 /* USB Product ID of Pocket-CASSY 2 (reserved) */ +#define USB_DEVICE_ID_LD_MOBILECASSY 0x1020 /* USB Product ID of Mobile-CASSY */ +#define USB_DEVICE_ID_LD_MOBILECASSY2 0x1021 /* USB Product ID of Mobile-CASSY 2 (reserved) */ +#define USB_DEVICE_ID_LD_MICROCASSYVOLTAGE 0x1031 /* USB Product ID of Micro-CASSY Voltage */ +#define USB_DEVICE_ID_LD_MICROCASSYCURRENT 0x1032 /* USB Product ID of Micro-CASSY Current */ +#define USB_DEVICE_ID_LD_MICROCASSYTIME 0x1033 /* USB Product ID of Micro-CASSY Time (reserved) */ +#define USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE 0x1035 /* USB Product ID of Micro-CASSY Temperature */ +#define USB_DEVICE_ID_LD_MICROCASSYPH 0x1038 /* USB Product ID of Micro-CASSY pH */ +#define USB_DEVICE_ID_LD_JWM 0x1080 /* USB Product ID of Joule and Wattmeter */ +#define USB_DEVICE_ID_LD_DMMP 0x1081 /* USB Product ID of Digital Multimeter P (reserved) */ +#define USB_DEVICE_ID_LD_UMIP 0x1090 /* USB Product ID of UMI P */ +#define USB_DEVICE_ID_LD_UMIC 0x10A0 /* USB Product ID of UMI C */ +#define USB_DEVICE_ID_LD_UMIB 0x10B0 /* USB Product ID of UMI B */ +#define USB_DEVICE_ID_LD_XRAY 0x1100 /* USB Product ID of X-Ray Apparatus 55481 */ +#define USB_DEVICE_ID_LD_XRAY2 0x1101 /* USB Product ID of X-Ray Apparatus 554800 */ +#define USB_DEVICE_ID_LD_XRAYCT 0x1110 /* USB Product ID of X-Ray Apparatus CT 554821*/ +#define USB_DEVICE_ID_LD_VIDEOCOM 0x1200 /* USB Product ID of VideoCom */ +#define USB_DEVICE_ID_LD_MOTOR 0x1210 /* USB Product ID of Motor (reserved) */ +#define USB_DEVICE_ID_LD_COM3LAB 0x2000 /* USB Product ID of COM3LAB */ +#define USB_DEVICE_ID_LD_TELEPORT 0x2010 /* USB Product ID of Terminal Adapter */ +#define USB_DEVICE_ID_LD_NETWORKANALYSER 0x2020 /* USB Product ID of Network Analyser */ +#define USB_DEVICE_ID_LD_POWERCONTROL 0x2030 /* USB Product ID of Converter Control Unit */ +#define USB_DEVICE_ID_LD_MACHINETEST 0x2040 /* USB Product ID of Machine Test System */ +#define USB_DEVICE_ID_LD_MOSTANALYSER 0x2050 /* USB Product ID of MOST Protocol Analyser */ +#define USB_DEVICE_ID_LD_MOSTANALYSER2 0x2051 /* USB Product ID of MOST Protocol Analyser 2 */ +#define USB_DEVICE_ID_LD_ABSESP 0x2060 /* USB Product ID of ABS ESP */ +#define USB_DEVICE_ID_LD_AUTODATABUS 0x2070 /* USB Product ID of Automotive Data Buses */ +#define USB_DEVICE_ID_LD_MCT 0x2080 /* USB Product ID of Microcontroller technique */ +#define USB_DEVICE_ID_LD_HYBRID 0x2090 /* USB Product ID of Automotive Hybrid */ +#define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0 /* USB Product ID of Heat control */ + +#define USB_VENDOR_ID_VERNIER 0x08f7 +#define USB_DEVICE_ID_VERNIER_GOTEMP 0x0002 +#define USB_DEVICE_ID_VERNIER_SKIP 0x0003 +#define USB_DEVICE_ID_VERNIER_CYCLOPS 0x0004 +#define USB_DEVICE_ID_VERNIER_LCSPEC 0x0006 + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_LD_MINOR_BASE 0 +#else +#define USB_LD_MINOR_BASE 176 +#endif + +/* table of devices that work with this driver */ +static const struct usb_device_id ld_usb_table[] = { + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYVOLTAGE) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYCURRENT) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYTIME) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYPH) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_JWM) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_DMMP) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIP) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIC) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIB) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_VIDEOCOM) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOTOR) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_COM3LAB) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_TELEPORT) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_NETWORKANALYSER) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERCONTROL) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETEST) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOSTANALYSER) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOSTANALYSER2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_ABSESP) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_AUTODATABUS) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MCT) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HYBRID) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HEATCONTROL) }, + { USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_GOTEMP) }, + { USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_SKIP) }, + { USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_CYCLOPS) }, + { USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_LCSPEC) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, ld_usb_table); +MODULE_VERSION("V0.14"); +MODULE_AUTHOR("Michael Hund "); +MODULE_DESCRIPTION("LD USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("LD USB Devices"); + +#ifdef CONFIG_USB_DEBUG + static int debug = 1; +#else + static int debug = 0; +#endif + +/* Use our own dbg macro */ +#define dbg_info(dev, format, arg...) do { if (debug) dev_info(dev , format , ## arg); } while (0) + +/* Module parameters */ +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +/* All interrupt in transfers are collected in a ring buffer to + * avoid racing conditions and get better performance of the driver. + */ +static int ring_buffer_size = 128; +module_param(ring_buffer_size, int, 0); +MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size in reports"); + +/* The write_buffer can contain more than one interrupt out transfer. + */ +static int write_buffer_size = 10; +module_param(write_buffer_size, int, 0); +MODULE_PARM_DESC(write_buffer_size, "Write buffer size in reports"); + +/* As of kernel version 2.6.4 ehci-hcd uses an + * "only one interrupt transfer per frame" shortcut + * to simplify the scheduling of periodic transfers. + * This conflicts with our standard 1ms intervals for in and out URBs. + * We use default intervals of 2ms for in and 2ms for out transfers, + * which should be fast enough. + * Increase the interval to allow more devices that do interrupt transfers, + * or set to 1 to use the standard interval from the endpoint descriptors. + */ +static int min_interrupt_in_interval = 2; +module_param(min_interrupt_in_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_in_interval, "Minimum interrupt in interval in ms"); + +static int min_interrupt_out_interval = 2; +module_param(min_interrupt_out_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_out_interval, "Minimum interrupt out interval in ms"); + +/* Structure to hold all of our device specific stuff */ +struct ld_usb { + struct mutex mutex; /* locks this structure */ + struct usb_interface* intf; /* save off the usb interface pointer */ + + int open_count; /* number of times this port has been opened */ + + char* ring_buffer; + unsigned int ring_head; + unsigned int ring_tail; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + char* interrupt_in_buffer; + struct usb_endpoint_descriptor* interrupt_in_endpoint; + struct urb* interrupt_in_urb; + int interrupt_in_interval; + size_t interrupt_in_endpoint_size; + int interrupt_in_running; + int interrupt_in_done; + int buffer_overflow; + spinlock_t rbsl; + + char* interrupt_out_buffer; + struct usb_endpoint_descriptor* interrupt_out_endpoint; + struct urb* interrupt_out_urb; + int interrupt_out_interval; + size_t interrupt_out_endpoint_size; + int interrupt_out_busy; +}; + +static struct usb_driver ld_usb_driver; + +/** + * ld_usb_abort_transfers + * aborts transfers and frees associated data structures + */ +static void ld_usb_abort_transfers(struct ld_usb *dev) +{ + /* shutdown transfer */ + if (dev->interrupt_in_running) { + dev->interrupt_in_running = 0; + if (dev->intf) + usb_kill_urb(dev->interrupt_in_urb); + } + if (dev->interrupt_out_busy) + if (dev->intf) + usb_kill_urb(dev->interrupt_out_urb); +} + +/** + * ld_usb_delete + */ +static void ld_usb_delete(struct ld_usb *dev) +{ + ld_usb_abort_transfers(dev); + + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->ring_buffer); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + kfree(dev); +} + +/** + * ld_usb_interrupt_in_callback + */ +static void ld_usb_interrupt_in_callback(struct urb *urb) +{ + struct ld_usb *dev = urb->context; + size_t *actual_buffer; + unsigned int next_ring_head; + int status = urb->status; + int retval; + + if (status) { + if (status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN) { + goto exit; + } else { + dbg_info(&dev->intf->dev, "%s: nonzero status received: %d\n", + __func__, status); + spin_lock(&dev->rbsl); + goto resubmit; /* maybe we can recover */ + } + } + + spin_lock(&dev->rbsl); + if (urb->actual_length > 0) { + next_ring_head = (dev->ring_head+1) % ring_buffer_size; + if (next_ring_head != dev->ring_tail) { + actual_buffer = (size_t*)(dev->ring_buffer + dev->ring_head*(sizeof(size_t)+dev->interrupt_in_endpoint_size)); + /* actual_buffer gets urb->actual_length + interrupt_in_buffer */ + *actual_buffer = urb->actual_length; + memcpy(actual_buffer+1, dev->interrupt_in_buffer, urb->actual_length); + dev->ring_head = next_ring_head; + dbg_info(&dev->intf->dev, "%s: received %d bytes\n", + __func__, urb->actual_length); + } else { + dev_warn(&dev->intf->dev, + "Ring buffer overflow, %d bytes dropped\n", + urb->actual_length); + dev->buffer_overflow = 1; + } + } + +resubmit: + /* resubmit if we're still running */ + if (dev->interrupt_in_running && !dev->buffer_overflow && dev->intf) { + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); + if (retval) { + dev_err(&dev->intf->dev, + "usb_submit_urb failed (%d)\n", retval); + dev->buffer_overflow = 1; + } + } + spin_unlock(&dev->rbsl); +exit: + dev->interrupt_in_done = 1; + wake_up_interruptible(&dev->read_wait); +} + +/** + * ld_usb_interrupt_out_callback + */ +static void ld_usb_interrupt_out_callback(struct urb *urb) +{ + struct ld_usb *dev = urb->context; + int status = urb->status; + + /* sync/async unlink faults aren't errors */ + if (status && !(status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN)) + dbg_info(&dev->intf->dev, + "%s - nonzero write interrupt status received: %d\n", + __func__, status); + + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); +} + +/** + * ld_usb_open + */ +static int ld_usb_open(struct inode *inode, struct file *file) +{ + struct ld_usb *dev; + int subminor; + int retval; + struct usb_interface *interface; + + nonseekable_open(inode, file); + subminor = iminor(inode); + + interface = usb_find_interface(&ld_usb_driver, subminor); + + if (!interface) { + printk(KERN_ERR "%s - error, can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + dev = usb_get_intfdata(interface); + + if (!dev) + return -ENODEV; + + /* lock this device */ + if (mutex_lock_interruptible(&dev->mutex)) + return -ERESTARTSYS; + + /* allow opening only once */ + if (dev->open_count) { + retval = -EBUSY; + goto unlock_exit; + } + dev->open_count = 1; + + /* initialize in direction */ + dev->ring_head = 0; + dev->ring_tail = 0; + dev->buffer_overflow = 0; + usb_fill_int_urb(dev->interrupt_in_urb, + interface_to_usbdev(interface), + usb_rcvintpipe(interface_to_usbdev(interface), + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + dev->interrupt_in_endpoint_size, + ld_usb_interrupt_in_callback, + dev, + dev->interrupt_in_interval); + + dev->interrupt_in_running = 1; + dev->interrupt_in_done = 0; + + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&interface->dev, "Couldn't submit interrupt_in_urb %d\n", retval); + dev->interrupt_in_running = 0; + dev->open_count = 0; + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + +unlock_exit: + mutex_unlock(&dev->mutex); + + return retval; +} + +/** + * ld_usb_release + */ +static int ld_usb_release(struct inode *inode, struct file *file) +{ + struct ld_usb *dev; + int retval = 0; + + dev = file->private_data; + + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + if (mutex_lock_interruptible(&dev->mutex)) { + retval = -ERESTARTSYS; + goto exit; + } + + if (dev->open_count != 1) { + retval = -ENODEV; + goto unlock_exit; + } + if (dev->intf == NULL) { + /* the device was unplugged before the file was released */ + mutex_unlock(&dev->mutex); + /* unlock here as ld_usb_delete frees dev */ + ld_usb_delete(dev); + goto exit; + } + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) + wait_event_interruptible_timeout(dev->write_wait, !dev->interrupt_out_busy, 2 * HZ); + ld_usb_abort_transfers(dev); + dev->open_count = 0; + +unlock_exit: + mutex_unlock(&dev->mutex); + +exit: + return retval; +} + +/** + * ld_usb_poll + */ +static unsigned int ld_usb_poll(struct file *file, poll_table *wait) +{ + struct ld_usb *dev; + unsigned int mask = 0; + + dev = file->private_data; + + if (!dev->intf) + return POLLERR | POLLHUP; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (dev->ring_head != dev->ring_tail) + mask |= POLLIN | POLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= POLLOUT | POLLWRNORM; + + return mask; +} + +/** + * ld_usb_read + */ +static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count, + loff_t *ppos) +{ + struct ld_usb *dev; + size_t *actual_buffer; + size_t bytes_to_read; + int retval = 0; + int rv; + + dev = file->private_data; + + /* verify that we actually have some data to read */ + if (count == 0) + goto exit; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->mutex)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + printk(KERN_ERR "ldusb: No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait for data */ + spin_lock_irq(&dev->rbsl); + if (dev->ring_head == dev->ring_tail) { + dev->interrupt_in_done = 0; + spin_unlock_irq(&dev->rbsl); + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done); + if (retval < 0) + goto unlock_exit; + } else { + spin_unlock_irq(&dev->rbsl); + } + + /* actual_buffer contains actual_length + interrupt_in_buffer */ + actual_buffer = (size_t*)(dev->ring_buffer + dev->ring_tail*(sizeof(size_t)+dev->interrupt_in_endpoint_size)); + bytes_to_read = min(count, *actual_buffer); + if (bytes_to_read < *actual_buffer) + dev_warn(&dev->intf->dev, "Read buffer overflow, %zd bytes dropped\n", + *actual_buffer-bytes_to_read); + + /* copy one interrupt_in_buffer from ring_buffer into userspace */ + if (copy_to_user(buffer, actual_buffer+1, bytes_to_read)) { + retval = -EFAULT; + goto unlock_exit; + } + dev->ring_tail = (dev->ring_tail+1) % ring_buffer_size; + + retval = bytes_to_read; + + spin_lock_irq(&dev->rbsl); + if (dev->buffer_overflow) { + dev->buffer_overflow = 0; + spin_unlock_irq(&dev->rbsl); + rv = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (rv < 0) + dev->buffer_overflow = 1; + } else { + spin_unlock_irq(&dev->rbsl); + } + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->mutex); + +exit: + return retval; +} + +/** + * ld_usb_write + */ +static ssize_t ld_usb_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct ld_usb *dev; + size_t bytes_to_write; + int retval = 0; + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->mutex)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + printk(KERN_ERR "ldusb: No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait until previous transfer is finished */ + if (dev->interrupt_out_busy) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->write_wait, !dev->interrupt_out_busy); + if (retval < 0) { + goto unlock_exit; + } + } + + /* write the data into interrupt_out_buffer from userspace */ + bytes_to_write = min(count, write_buffer_size*dev->interrupt_out_endpoint_size); + if (bytes_to_write < count) + dev_warn(&dev->intf->dev, "Write buffer overflow, %zd bytes dropped\n",count-bytes_to_write); + dbg_info(&dev->intf->dev, "%s: count = %zd, bytes_to_write = %zd\n", __func__, count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { + retval = -EFAULT; + goto unlock_exit; + } + + if (dev->interrupt_out_endpoint == NULL) { + /* try HID_REQ_SET_REPORT=9 on control_endpoint instead of interrupt_out_endpoint */ + retval = usb_control_msg(interface_to_usbdev(dev->intf), + usb_sndctrlpipe(interface_to_usbdev(dev->intf), 0), + 9, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 1 << 8, 0, + dev->interrupt_out_buffer, + bytes_to_write, + USB_CTRL_SET_TIMEOUT * HZ); + if (retval < 0) + dev_err(&dev->intf->dev, + "Couldn't submit HID_REQ_SET_REPORT %d\n", + retval); + goto unlock_exit; + } + + /* send off the urb */ + usb_fill_int_urb(dev->interrupt_out_urb, + interface_to_usbdev(dev->intf), + usb_sndintpipe(interface_to_usbdev(dev->intf), + dev->interrupt_out_endpoint->bEndpointAddress), + dev->interrupt_out_buffer, + bytes_to_write, + ld_usb_interrupt_out_callback, + dev, + dev->interrupt_out_interval); + + dev->interrupt_out_busy = 1; + wmb(); + + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval) { + dev->interrupt_out_busy = 0; + dev_err(&dev->intf->dev, + "Couldn't submit interrupt_out_urb %d\n", retval); + goto unlock_exit; + } + retval = bytes_to_write; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->mutex); + +exit: + return retval; +} + +/* file operations needed when we register this driver */ +static const struct file_operations ld_usb_fops = { + .owner = THIS_MODULE, + .read = ld_usb_read, + .write = ld_usb_write, + .open = ld_usb_open, + .release = ld_usb_release, + .poll = ld_usb_poll, + .llseek = no_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver ld_usb_class = { + .name = "ldusb%d", + .fops = &ld_usb_fops, + .minor_base = USB_LD_MINOR_BASE, +}; + +/** + * ld_usb_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct ld_usb *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + char *buffer; + int i; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&intf->dev, "Out of memory\n"); + goto exit; + } + mutex_init(&dev->mutex); + spin_lock_init(&dev->rbsl); + dev->intf = intf; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + /* workaround for early firmware versions on fast computers */ + if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VENDOR_ID_LD) && + ((le16_to_cpu(udev->descriptor.idProduct) == USB_DEVICE_ID_LD_CASSY) || + (le16_to_cpu(udev->descriptor.idProduct) == USB_DEVICE_ID_LD_COM3LAB)) && + (le16_to_cpu(udev->descriptor.bcdDevice) <= 0x103)) { + buffer = kmalloc(256, GFP_KERNEL); + if (buffer == NULL) { + dev_err(&intf->dev, "Couldn't allocate string buffer\n"); + goto error; + } + /* usb_string makes SETUP+STALL to leave always ControlReadLoop */ + usb_string(udev, 255, buffer, 256); + kfree(buffer); + } + + iface_desc = intf->cur_altsetting; + + /* set up the endpoint information */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) + dev->interrupt_in_endpoint = endpoint; + + if (usb_endpoint_is_int_out(endpoint)) + dev->interrupt_out_endpoint = endpoint; + } + if (dev->interrupt_in_endpoint == NULL) { + dev_err(&intf->dev, "Interrupt in endpoint not found\n"); + goto error; + } + if (dev->interrupt_out_endpoint == NULL) + dev_warn(&intf->dev, "Interrupt out endpoint not found (using control endpoint instead)\n"); + + dev->interrupt_in_endpoint_size = usb_endpoint_maxp(dev->interrupt_in_endpoint); + dev->ring_buffer = kmalloc(ring_buffer_size*(sizeof(size_t)+dev->interrupt_in_endpoint_size), GFP_KERNEL); + if (!dev->ring_buffer) { + dev_err(&intf->dev, "Couldn't allocate ring_buffer\n"); + goto error; + } + dev->interrupt_in_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_in_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_buffer\n"); + goto error; + } + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_urb\n"); + goto error; + } + dev->interrupt_out_endpoint_size = dev->interrupt_out_endpoint ? usb_endpoint_maxp(dev->interrupt_out_endpoint) : + udev->descriptor.bMaxPacketSize0; + dev->interrupt_out_buffer = kmalloc(write_buffer_size*dev->interrupt_out_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_buffer\n"); + goto error; + } + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_urb\n"); + goto error; + } + dev->interrupt_in_interval = min_interrupt_in_interval > dev->interrupt_in_endpoint->bInterval ? min_interrupt_in_interval : dev->interrupt_in_endpoint->bInterval; + if (dev->interrupt_out_endpoint) + dev->interrupt_out_interval = min_interrupt_out_interval > dev->interrupt_out_endpoint->bInterval ? min_interrupt_out_interval : dev->interrupt_out_endpoint->bInterval; + + /* we can register the device now, as it is ready */ + usb_set_intfdata(intf, dev); + + retval = usb_register_dev(intf, &ld_usb_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&intf->dev, "Not able to get a minor for this device.\n"); + usb_set_intfdata(intf, NULL); + goto error; + } + + /* let the user know what node this device is now attached to */ + dev_info(&intf->dev, "LD USB Device #%d now attached to major %d minor %d\n", + (intf->minor - USB_LD_MINOR_BASE), USB_MAJOR, intf->minor); + +exit: + return retval; + +error: + ld_usb_delete(dev); + + return retval; +} + +/** + * ld_usb_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void ld_usb_disconnect(struct usb_interface *intf) +{ + struct ld_usb *dev; + int minor; + + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + + minor = intf->minor; + + /* give back our minor */ + usb_deregister_dev(intf, &ld_usb_class); + + mutex_lock(&dev->mutex); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + mutex_unlock(&dev->mutex); + ld_usb_delete(dev); + } else { + dev->intf = NULL; + /* wake up pollers */ + wake_up_interruptible_all(&dev->read_wait); + wake_up_interruptible_all(&dev->write_wait); + mutex_unlock(&dev->mutex); + } + + dev_info(&intf->dev, "LD USB Device #%d now disconnected\n", + (minor - USB_LD_MINOR_BASE)); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver ld_usb_driver = { + .name = "ldusb", + .probe = ld_usb_probe, + .disconnect = ld_usb_disconnect, + .id_table = ld_usb_table, +}; + +module_usb_driver(ld_usb_driver); + diff --git a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c new file mode 100644 index 00000000..80894791 --- /dev/null +++ b/drivers/usb/misc/legousbtower.c @@ -0,0 +1,1054 @@ +/* + * LEGO USB Tower driver + * + * Copyright (C) 2003 David Glance + * 2001-2004 Juergen Stuber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * derived from USB Skeleton driver - 0.5 + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * + * History: + * + * 2001-10-13 - 0.1 js + * - first version + * 2001-11-03 - 0.2 js + * - simplified buffering, one-shot URBs for writing + * 2001-11-10 - 0.3 js + * - removed IOCTL (setting power/mode is more complicated, postponed) + * 2001-11-28 - 0.4 js + * - added vendor commands for mode of operation and power level in open + * 2001-12-04 - 0.5 js + * - set IR mode by default (by oversight 0.4 set VLL mode) + * 2002-01-11 - 0.5? pcchan + * - make read buffer reusable and work around bytes_to_write issue between + * uhci and legusbtower + * 2002-09-23 - 0.52 david (david@csse.uwa.edu.au) + * - imported into lejos project + * - changed wake_up to wake_up_interruptible + * - changed to use lego0 rather than tower0 + * - changed dbg() to use __func__ rather than deprecated __func__ + * 2003-01-12 - 0.53 david (david@csse.uwa.edu.au) + * - changed read and write to write everything or + * timeout (from a patch by Chris Riesen and Brett Thaeler driver) + * - added ioctl functionality to set timeouts + * 2003-07-18 - 0.54 davidgsf (david@csse.uwa.edu.au) + * - initial import into LegoUSB project + * - merge of existing LegoUSB.c driver + * 2003-07-18 - 0.56 davidgsf (david@csse.uwa.edu.au) + * - port to 2.6 style driver + * 2004-02-29 - 0.6 Juergen Stuber + * - fix locking + * - unlink read URBs which are no longer needed + * - allow increased buffer size, eliminates need for timeout on write + * - have read URB running continuously + * - added poll + * - forbid seeking + * - added nonblocking I/O + * - changed back __func__ to __func__ + * - read and log tower firmware version + * - reset tower on probe, avoids failure of first write + * 2004-03-09 - 0.7 Juergen Stuber + * - timeout read now only after inactivity, shorten default accordingly + * 2004-03-11 - 0.8 Juergen Stuber + * - log major, minor instead of possibly confusing device filename + * - whitespace cleanup + * 2004-03-12 - 0.9 Juergen Stuber + * - normalize whitespace in debug messages + * - take care about endianness in control message responses + * 2004-03-13 - 0.91 Juergen Stuber + * - make default intervals longer to accommodate current EHCI driver + * 2004-03-19 - 0.92 Juergen Stuber + * - replaced atomic_t by memory barriers + * 2004-04-21 - 0.93 Juergen Stuber + * - wait for completion of write urb in release (needed for remotecontrol) + * - corrected poll for write direction (missing negation) + * 2004-04-22 - 0.94 Juergen Stuber + * - make device locking interruptible + * 2004-04-30 - 0.95 Juergen Stuber + * - check for valid udev on resubmitting and unlinking urbs + * 2004-08-03 - 0.96 Juergen Stuber + * - move reset into open to clean out spurious data + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef CONFIG_USB_DEBUG + static int debug = 4; +#else + static int debug = 0; +#endif + +/* Use our own dbg macro */ +#undef dbg +#define dbg(lvl, format, arg...) \ +do { \ + if (debug >= lvl) \ + printk(KERN_DEBUG "%s: " format "\n", __FILE__, ##arg); \ +} while (0) + +/* Version Information */ +#define DRIVER_VERSION "v0.96" +#define DRIVER_AUTHOR "Juergen Stuber " +#define DRIVER_DESC "LEGO USB Tower Driver" + +/* Module parameters */ +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +/* The defaults are chosen to work with the latest versions of leJOS and NQC. + */ + +/* Some legacy software likes to receive packets in one piece. + * In this case read_buffer_size should exceed the maximal packet length + * (417 for datalog uploads), and packet_timeout should be set. + */ +static int read_buffer_size = 480; +module_param(read_buffer_size, int, 0); +MODULE_PARM_DESC(read_buffer_size, "Read buffer size"); + +/* Some legacy software likes to send packets in one piece. + * In this case write_buffer_size should exceed the maximal packet length + * (417 for firmware and program downloads). + * A problem with long writes is that the following read may time out + * if the software is not prepared to wait long enough. + */ +static int write_buffer_size = 480; +module_param(write_buffer_size, int, 0); +MODULE_PARM_DESC(write_buffer_size, "Write buffer size"); + +/* Some legacy software expects reads to contain whole LASM packets. + * To achieve this, characters which arrive before a packet timeout + * occurs will be returned in a single read operation. + * A problem with long reads is that the software may time out + * if it is not prepared to wait long enough. + * The packet timeout should be greater than the time between the + * reception of subsequent characters, which should arrive about + * every 5ms for the standard 2400 baud. + * Set it to 0 to disable. + */ +static int packet_timeout = 50; +module_param(packet_timeout, int, 0); +MODULE_PARM_DESC(packet_timeout, "Packet timeout in ms"); + +/* Some legacy software expects blocking reads to time out. + * Timeout occurs after the specified time of read and write inactivity. + * Set it to 0 to disable. + */ +static int read_timeout = 200; +module_param(read_timeout, int, 0); +MODULE_PARM_DESC(read_timeout, "Read timeout in ms"); + +/* As of kernel version 2.6.4 ehci-hcd uses an + * "only one interrupt transfer per frame" shortcut + * to simplify the scheduling of periodic transfers. + * This conflicts with our standard 1ms intervals for in and out URBs. + * We use default intervals of 2ms for in and 8ms for out transfers, + * which is fast enough for 2400 baud and allows a small additional load. + * Increase the interval to allow more devices that do interrupt transfers, + * or set to 0 to use the standard interval from the endpoint descriptors. + */ +static int interrupt_in_interval = 2; +module_param(interrupt_in_interval, int, 0); +MODULE_PARM_DESC(interrupt_in_interval, "Interrupt in interval in ms"); + +static int interrupt_out_interval = 8; +module_param(interrupt_out_interval, int, 0); +MODULE_PARM_DESC(interrupt_out_interval, "Interrupt out interval in ms"); + +/* Define these values to match your device */ +#define LEGO_USB_TOWER_VENDOR_ID 0x0694 +#define LEGO_USB_TOWER_PRODUCT_ID 0x0001 + +/* Vendor requests */ +#define LEGO_USB_TOWER_REQUEST_RESET 0x04 +#define LEGO_USB_TOWER_REQUEST_GET_VERSION 0xFD + +struct tower_reset_reply { + __le16 size; /* little-endian */ + __u8 err_code; + __u8 spare; +} __attribute__ ((packed)); + +struct tower_get_version_reply { + __le16 size; /* little-endian */ + __u8 err_code; + __u8 spare; + __u8 major; + __u8 minor; + __le16 build_no; /* little-endian */ +} __attribute__ ((packed)); + + +/* table of devices that work with this driver */ +static const struct usb_device_id tower_table[] = { + { USB_DEVICE(LEGO_USB_TOWER_VENDOR_ID, LEGO_USB_TOWER_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, tower_table); +static DEFINE_MUTEX(open_disc_mutex); + +#define LEGO_USB_TOWER_MINOR_BASE 160 + + +/* Structure to hold all of our device specific stuff */ +struct lego_usb_tower { + struct mutex lock; /* locks this structure */ + struct usb_device* udev; /* save off the usb device pointer */ + unsigned char minor; /* the starting minor number for this device */ + + int open_count; /* number of times this port has been opened */ + + char* read_buffer; + size_t read_buffer_length; /* this much came in */ + size_t read_packet_length; /* this much will be returned on read */ + spinlock_t read_buffer_lock; + int packet_timeout_jiffies; + unsigned long read_last_arrival; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + char* interrupt_in_buffer; + struct usb_endpoint_descriptor* interrupt_in_endpoint; + struct urb* interrupt_in_urb; + int interrupt_in_interval; + int interrupt_in_running; + int interrupt_in_done; + + char* interrupt_out_buffer; + struct usb_endpoint_descriptor* interrupt_out_endpoint; + struct urb* interrupt_out_urb; + int interrupt_out_interval; + int interrupt_out_busy; + +}; + + +/* local function prototypes */ +static ssize_t tower_read (struct file *file, char __user *buffer, size_t count, loff_t *ppos); +static ssize_t tower_write (struct file *file, const char __user *buffer, size_t count, loff_t *ppos); +static inline void tower_delete (struct lego_usb_tower *dev); +static int tower_open (struct inode *inode, struct file *file); +static int tower_release (struct inode *inode, struct file *file); +static unsigned int tower_poll (struct file *file, poll_table *wait); +static loff_t tower_llseek (struct file *file, loff_t off, int whence); + +static void tower_abort_transfers (struct lego_usb_tower *dev); +static void tower_check_for_read_packet (struct lego_usb_tower *dev); +static void tower_interrupt_in_callback (struct urb *urb); +static void tower_interrupt_out_callback (struct urb *urb); + +static int tower_probe (struct usb_interface *interface, const struct usb_device_id *id); +static void tower_disconnect (struct usb_interface *interface); + + +/* file operations needed when we register this driver */ +static const struct file_operations tower_fops = { + .owner = THIS_MODULE, + .read = tower_read, + .write = tower_write, + .open = tower_open, + .release = tower_release, + .poll = tower_poll, + .llseek = tower_llseek, +}; + +static char *legousbtower_devnode(struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev)); +} + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver tower_class = { + .name = "legousbtower%d", + .devnode = legousbtower_devnode, + .fops = &tower_fops, + .minor_base = LEGO_USB_TOWER_MINOR_BASE, +}; + + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver tower_driver = { + .name = "legousbtower", + .probe = tower_probe, + .disconnect = tower_disconnect, + .id_table = tower_table, +}; + + +/** + * lego_usb_tower_debug_data + */ +static inline void lego_usb_tower_debug_data (int level, const char *function, int size, const unsigned char *data) +{ + int i; + + if (debug < level) + return; + + printk (KERN_DEBUG "%s: %s - length = %d, data = ", __FILE__, function, size); + for (i = 0; i < size; ++i) { + printk ("%.2x ", data[i]); + } + printk ("\n"); +} + + +/** + * tower_delete + */ +static inline void tower_delete (struct lego_usb_tower *dev) +{ + dbg(2, "%s: enter", __func__); + + tower_abort_transfers (dev); + + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree (dev->read_buffer); + kfree (dev->interrupt_in_buffer); + kfree (dev->interrupt_out_buffer); + kfree (dev); + + dbg(2, "%s: leave", __func__); +} + + +/** + * tower_open + */ +static int tower_open (struct inode *inode, struct file *file) +{ + struct lego_usb_tower *dev = NULL; + int subminor; + int retval = 0; + struct usb_interface *interface; + struct tower_reset_reply reset_reply; + int result; + + dbg(2, "%s: enter", __func__); + + nonseekable_open(inode, file); + subminor = iminor(inode); + + interface = usb_find_interface (&tower_driver, subminor); + + if (!interface) { + printk(KERN_ERR "%s - error, can't find device for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + mutex_lock(&open_disc_mutex); + dev = usb_get_intfdata(interface); + + if (!dev) { + mutex_unlock(&open_disc_mutex); + retval = -ENODEV; + goto exit; + } + + /* lock this device */ + if (mutex_lock_interruptible(&dev->lock)) { + mutex_unlock(&open_disc_mutex); + retval = -ERESTARTSYS; + goto exit; + } + + + /* allow opening only once */ + if (dev->open_count) { + mutex_unlock(&open_disc_mutex); + retval = -EBUSY; + goto unlock_exit; + } + dev->open_count = 1; + mutex_unlock(&open_disc_mutex); + + /* reset the tower */ + result = usb_control_msg (dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + LEGO_USB_TOWER_REQUEST_RESET, + USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, + 0, + 0, + &reset_reply, + sizeof(reset_reply), + 1000); + if (result < 0) { + dev_err(&dev->udev->dev, + "LEGO USB Tower reset control request failed\n"); + retval = result; + goto unlock_exit; + } + + /* initialize in direction */ + dev->read_buffer_length = 0; + dev->read_packet_length = 0; + usb_fill_int_urb (dev->interrupt_in_urb, + dev->udev, + usb_rcvintpipe(dev->udev, dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + usb_endpoint_maxp(dev->interrupt_in_endpoint), + tower_interrupt_in_callback, + dev, + dev->interrupt_in_interval); + + dev->interrupt_in_running = 1; + dev->interrupt_in_done = 0; + mb(); + + retval = usb_submit_urb (dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&dev->udev->dev, + "Couldn't submit interrupt_in_urb %d\n", retval); + dev->interrupt_in_running = 0; + dev->open_count = 0; + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + +unlock_exit: + mutex_unlock(&dev->lock); + +exit: + dbg(2, "%s: leave, return value %d ", __func__, retval); + + return retval; +} + +/** + * tower_release + */ +static int tower_release (struct inode *inode, struct file *file) +{ + struct lego_usb_tower *dev; + int retval = 0; + + dbg(2, "%s: enter", __func__); + + dev = file->private_data; + + if (dev == NULL) { + dbg(1, "%s: object is NULL", __func__); + retval = -ENODEV; + goto exit_nolock; + } + + mutex_lock(&open_disc_mutex); + if (mutex_lock_interruptible(&dev->lock)) { + retval = -ERESTARTSYS; + goto exit; + } + + if (dev->open_count != 1) { + dbg(1, "%s: device not opened exactly once", __func__); + retval = -ENODEV; + goto unlock_exit; + } + if (dev->udev == NULL) { + /* the device was unplugged before the file was released */ + + /* unlock here as tower_delete frees dev */ + mutex_unlock(&dev->lock); + tower_delete (dev); + goto exit; + } + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) { + wait_event_interruptible_timeout (dev->write_wait, !dev->interrupt_out_busy, 2 * HZ); + } + tower_abort_transfers (dev); + dev->open_count = 0; + +unlock_exit: + mutex_unlock(&dev->lock); + +exit: + mutex_unlock(&open_disc_mutex); +exit_nolock: + dbg(2, "%s: leave, return value %d", __func__, retval); + return retval; +} + + +/** + * tower_abort_transfers + * aborts transfers and frees associated data structures + */ +static void tower_abort_transfers (struct lego_usb_tower *dev) +{ + dbg(2, "%s: enter", __func__); + + if (dev == NULL) { + dbg(1, "%s: dev is null", __func__); + goto exit; + } + + /* shutdown transfer */ + if (dev->interrupt_in_running) { + dev->interrupt_in_running = 0; + mb(); + if (dev->udev) + usb_kill_urb (dev->interrupt_in_urb); + } + if (dev->interrupt_out_busy && dev->udev) + usb_kill_urb(dev->interrupt_out_urb); + +exit: + dbg(2, "%s: leave", __func__); +} + + +/** + * tower_check_for_read_packet + * + * To get correct semantics for signals and non-blocking I/O + * with packetizing we pretend not to see any data in the read buffer + * until it has been there unchanged for at least + * dev->packet_timeout_jiffies, or until the buffer is full. + */ +static void tower_check_for_read_packet (struct lego_usb_tower *dev) +{ + spin_lock_irq (&dev->read_buffer_lock); + if (!packet_timeout + || time_after(jiffies, dev->read_last_arrival + dev->packet_timeout_jiffies) + || dev->read_buffer_length == read_buffer_size) { + dev->read_packet_length = dev->read_buffer_length; + } + dev->interrupt_in_done = 0; + spin_unlock_irq (&dev->read_buffer_lock); +} + + +/** + * tower_poll + */ +static unsigned int tower_poll (struct file *file, poll_table *wait) +{ + struct lego_usb_tower *dev; + unsigned int mask = 0; + + dbg(2, "%s: enter", __func__); + + dev = file->private_data; + + if (!dev->udev) + return POLLERR | POLLHUP; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + tower_check_for_read_packet(dev); + if (dev->read_packet_length > 0) { + mask |= POLLIN | POLLRDNORM; + } + if (!dev->interrupt_out_busy) { + mask |= POLLOUT | POLLWRNORM; + } + + dbg(2, "%s: leave, mask = %d", __func__, mask); + + return mask; +} + + +/** + * tower_llseek + */ +static loff_t tower_llseek (struct file *file, loff_t off, int whence) +{ + return -ESPIPE; /* unseekable */ +} + + +/** + * tower_read + */ +static ssize_t tower_read (struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct lego_usb_tower *dev; + size_t bytes_to_read; + int i; + int retval = 0; + unsigned long timeout = 0; + + dbg(2, "%s: enter, count = %Zd", __func__, count); + + dev = file->private_data; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->lock)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->udev == NULL) { + retval = -ENODEV; + printk(KERN_ERR "legousbtower: No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* verify that we actually have some data to read */ + if (count == 0) { + dbg(1, "%s: read request of 0 bytes", __func__); + goto unlock_exit; + } + + if (read_timeout) { + timeout = jiffies + read_timeout * HZ / 1000; + } + + /* wait for data */ + tower_check_for_read_packet (dev); + while (dev->read_packet_length == 0) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible_timeout(dev->read_wait, dev->interrupt_in_done, dev->packet_timeout_jiffies); + if (retval < 0) { + goto unlock_exit; + } + + /* reset read timeout during read or write activity */ + if (read_timeout + && (dev->read_buffer_length || dev->interrupt_out_busy)) { + timeout = jiffies + read_timeout * HZ / 1000; + } + /* check for read timeout */ + if (read_timeout && time_after (jiffies, timeout)) { + retval = -ETIMEDOUT; + goto unlock_exit; + } + tower_check_for_read_packet (dev); + } + + /* copy the data from read_buffer into userspace */ + bytes_to_read = min(count, dev->read_packet_length); + + if (copy_to_user (buffer, dev->read_buffer, bytes_to_read)) { + retval = -EFAULT; + goto unlock_exit; + } + + spin_lock_irq (&dev->read_buffer_lock); + dev->read_buffer_length -= bytes_to_read; + dev->read_packet_length -= bytes_to_read; + for (i=0; iread_buffer_length; i++) { + dev->read_buffer[i] = dev->read_buffer[i+bytes_to_read]; + } + spin_unlock_irq (&dev->read_buffer_lock); + + retval = bytes_to_read; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->lock); + +exit: + dbg(2, "%s: leave, return value %d", __func__, retval); + return retval; +} + + +/** + * tower_write + */ +static ssize_t tower_write (struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct lego_usb_tower *dev; + size_t bytes_to_write; + int retval = 0; + + dbg(2, "%s: enter, count = %Zd", __func__, count); + + dev = file->private_data; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->lock)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->udev == NULL) { + retval = -ENODEV; + printk(KERN_ERR "legousbtower: No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* verify that we actually have some data to write */ + if (count == 0) { + dbg(1, "%s: write request of 0 bytes", __func__); + goto unlock_exit; + } + + /* wait until previous transfer is finished */ + while (dev->interrupt_out_busy) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible (dev->write_wait, !dev->interrupt_out_busy); + if (retval) { + goto unlock_exit; + } + } + + /* write the data into interrupt_out_buffer from userspace */ + bytes_to_write = min_t(int, count, write_buffer_size); + dbg(4, "%s: count = %Zd, bytes_to_write = %Zd", __func__, count, bytes_to_write); + + if (copy_from_user (dev->interrupt_out_buffer, buffer, bytes_to_write)) { + retval = -EFAULT; + goto unlock_exit; + } + + /* send off the urb */ + usb_fill_int_urb(dev->interrupt_out_urb, + dev->udev, + usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress), + dev->interrupt_out_buffer, + bytes_to_write, + tower_interrupt_out_callback, + dev, + dev->interrupt_out_interval); + + dev->interrupt_out_busy = 1; + wmb(); + + retval = usb_submit_urb (dev->interrupt_out_urb, GFP_KERNEL); + if (retval) { + dev->interrupt_out_busy = 0; + dev_err(&dev->udev->dev, + "Couldn't submit interrupt_out_urb %d\n", retval); + goto unlock_exit; + } + retval = bytes_to_write; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->lock); + +exit: + dbg(2, "%s: leave, return value %d", __func__, retval); + + return retval; +} + + +/** + * tower_interrupt_in_callback + */ +static void tower_interrupt_in_callback (struct urb *urb) +{ + struct lego_usb_tower *dev = urb->context; + int status = urb->status; + int retval; + + dbg(4, "%s: enter, status %d", __func__, status); + + lego_usb_tower_debug_data(5, __func__, urb->actual_length, urb->transfer_buffer); + + if (status) { + if (status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN) { + goto exit; + } else { + dbg(1, "%s: nonzero status received: %d", __func__, status); + goto resubmit; /* maybe we can recover */ + } + } + + if (urb->actual_length > 0) { + spin_lock (&dev->read_buffer_lock); + if (dev->read_buffer_length + urb->actual_length < read_buffer_size) { + memcpy (dev->read_buffer + dev->read_buffer_length, + dev->interrupt_in_buffer, + urb->actual_length); + dev->read_buffer_length += urb->actual_length; + dev->read_last_arrival = jiffies; + dbg(3, "%s: received %d bytes", __func__, urb->actual_length); + } else { + printk(KERN_WARNING "%s: read_buffer overflow, %d bytes dropped", __func__, urb->actual_length); + } + spin_unlock (&dev->read_buffer_lock); + } + +resubmit: + /* resubmit if we're still running */ + if (dev->interrupt_in_running && dev->udev) { + retval = usb_submit_urb (dev->interrupt_in_urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->udev->dev, + "%s: usb_submit_urb failed (%d)\n", + __func__, retval); + } + +exit: + dev->interrupt_in_done = 1; + wake_up_interruptible (&dev->read_wait); + + lego_usb_tower_debug_data(5, __func__, urb->actual_length, urb->transfer_buffer); + dbg(4, "%s: leave, status %d", __func__, status); +} + + +/** + * tower_interrupt_out_callback + */ +static void tower_interrupt_out_callback (struct urb *urb) +{ + struct lego_usb_tower *dev = urb->context; + int status = urb->status; + + dbg(4, "%s: enter, status %d", __func__, status); + lego_usb_tower_debug_data(5, __func__, urb->actual_length, urb->transfer_buffer); + + /* sync/async unlink faults aren't errors */ + if (status && !(status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN)) { + dbg(1, "%s - nonzero write bulk status received: %d", + __func__, status); + } + + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); + + lego_usb_tower_debug_data(5, __func__, urb->actual_length, urb->transfer_buffer); + dbg(4, "%s: leave, status %d", __func__, status); +} + + +/** + * tower_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int tower_probe (struct usb_interface *interface, const struct usb_device_id *id) +{ + struct device *idev = &interface->dev; + struct usb_device *udev = interface_to_usbdev(interface); + struct lego_usb_tower *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor* endpoint; + struct tower_get_version_reply get_version_reply; + int i; + int retval = -ENOMEM; + int result; + + dbg(2, "%s: enter", __func__); + + /* allocate memory for our device state and initialize it */ + + dev = kmalloc (sizeof(struct lego_usb_tower), GFP_KERNEL); + + if (dev == NULL) { + dev_err(idev, "Out of memory\n"); + goto exit; + } + + mutex_init(&dev->lock); + + dev->udev = udev; + dev->open_count = 0; + + dev->read_buffer = NULL; + dev->read_buffer_length = 0; + dev->read_packet_length = 0; + spin_lock_init (&dev->read_buffer_lock); + dev->packet_timeout_jiffies = packet_timeout * HZ / 1000; + dev->read_last_arrival = jiffies; + + init_waitqueue_head (&dev->read_wait); + init_waitqueue_head (&dev->write_wait); + + dev->interrupt_in_buffer = NULL; + dev->interrupt_in_endpoint = NULL; + dev->interrupt_in_urb = NULL; + dev->interrupt_in_running = 0; + dev->interrupt_in_done = 0; + + dev->interrupt_out_buffer = NULL; + dev->interrupt_out_endpoint = NULL; + dev->interrupt_out_urb = NULL; + dev->interrupt_out_busy = 0; + + iface_desc = interface->cur_altsetting; + + /* set up the endpoint information */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_xfer_int(endpoint)) { + if (usb_endpoint_dir_in(endpoint)) + dev->interrupt_in_endpoint = endpoint; + else + dev->interrupt_out_endpoint = endpoint; + } + } + if(dev->interrupt_in_endpoint == NULL) { + dev_err(idev, "interrupt in endpoint not found\n"); + goto error; + } + if (dev->interrupt_out_endpoint == NULL) { + dev_err(idev, "interrupt out endpoint not found\n"); + goto error; + } + + dev->read_buffer = kmalloc (read_buffer_size, GFP_KERNEL); + if (!dev->read_buffer) { + dev_err(idev, "Couldn't allocate read_buffer\n"); + goto error; + } + dev->interrupt_in_buffer = kmalloc (usb_endpoint_maxp(dev->interrupt_in_endpoint), GFP_KERNEL); + if (!dev->interrupt_in_buffer) { + dev_err(idev, "Couldn't allocate interrupt_in_buffer\n"); + goto error; + } + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) { + dev_err(idev, "Couldn't allocate interrupt_in_urb\n"); + goto error; + } + dev->interrupt_out_buffer = kmalloc (write_buffer_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) { + dev_err(idev, "Couldn't allocate interrupt_out_buffer\n"); + goto error; + } + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) { + dev_err(idev, "Couldn't allocate interrupt_out_urb\n"); + goto error; + } + dev->interrupt_in_interval = interrupt_in_interval ? interrupt_in_interval : dev->interrupt_in_endpoint->bInterval; + dev->interrupt_out_interval = interrupt_out_interval ? interrupt_out_interval : dev->interrupt_out_endpoint->bInterval; + + /* we can register the device now, as it is ready */ + usb_set_intfdata (interface, dev); + + retval = usb_register_dev (interface, &tower_class); + + if (retval) { + /* something prevented us from registering this driver */ + dev_err(idev, "Not able to get a minor for this device.\n"); + usb_set_intfdata (interface, NULL); + goto error; + } + dev->minor = interface->minor; + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "LEGO USB Tower #%d now attached to major " + "%d minor %d\n", (dev->minor - LEGO_USB_TOWER_MINOR_BASE), + USB_MAJOR, dev->minor); + + /* get the firmware version and log it */ + result = usb_control_msg (udev, + usb_rcvctrlpipe(udev, 0), + LEGO_USB_TOWER_REQUEST_GET_VERSION, + USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, + 0, + 0, + &get_version_reply, + sizeof(get_version_reply), + 1000); + if (result < 0) { + dev_err(idev, "LEGO USB Tower get version control request failed\n"); + retval = result; + goto error; + } + dev_info(&interface->dev, "LEGO USB Tower firmware version is %d.%d " + "build %d\n", get_version_reply.major, + get_version_reply.minor, + le16_to_cpu(get_version_reply.build_no)); + + +exit: + dbg(2, "%s: leave, return value 0x%.8lx (dev)", __func__, (long) dev); + + return retval; + +error: + tower_delete(dev); + return retval; +} + + +/** + * tower_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void tower_disconnect (struct usb_interface *interface) +{ + struct lego_usb_tower *dev; + int minor; + + dbg(2, "%s: enter", __func__); + + dev = usb_get_intfdata (interface); + mutex_lock(&open_disc_mutex); + usb_set_intfdata (interface, NULL); + + minor = dev->minor; + + /* give back our minor */ + usb_deregister_dev (interface, &tower_class); + + mutex_lock(&dev->lock); + mutex_unlock(&open_disc_mutex); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + mutex_unlock(&dev->lock); + tower_delete (dev); + } else { + dev->udev = NULL; + /* wake up pollers */ + wake_up_interruptible_all(&dev->read_wait); + wake_up_interruptible_all(&dev->write_wait); + mutex_unlock(&dev->lock); + } + + dev_info(&interface->dev, "LEGO USB Tower #%d now disconnected\n", + (minor - LEGO_USB_TOWER_MINOR_BASE)); + + dbg(2, "%s: leave", __func__); +} + +module_usb_driver(tower_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif diff --git a/drivers/usb/misc/rio500.c b/drivers/usb/misc/rio500.c new file mode 100644 index 00000000..b9b356a9 --- /dev/null +++ b/drivers/usb/misc/rio500.c @@ -0,0 +1,554 @@ +/* -*- linux-c -*- */ + +/* + * Driver for USB Rio 500 + * + * Cesar Miquel (miquel@df.uba.ar) + * + * based on hp_scanner.c by David E. Nelson (dnelson@jump.net) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based upon mouse.c (Brad Keryan) and printer.c (Michael Gee). + * + * Changelog: + * 30/05/2003 replaced lock/unlock kernel with up/down + * Daniele Bellucci bellucda@tiscali.it + * */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rio500_usb.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.1" +#define DRIVER_AUTHOR "Cesar Miquel " +#define DRIVER_DESC "USB Rio 500 driver" + +#define RIO_MINOR 64 + +/* stall/wait timeout for rio */ +#define NAK_TIMEOUT (HZ) + +#define IBUF_SIZE 0x1000 + +/* Size of the rio buffer */ +#define OBUF_SIZE 0x10000 + +struct rio_usb_data { + struct usb_device *rio_dev; /* init: probe_rio */ + unsigned int ifnum; /* Interface number of the USB device */ + int isopen; /* nz if open */ + int present; /* Device is present on the bus */ + char *obuf, *ibuf; /* transfer buffers */ + char bulk_in_ep, bulk_out_ep; /* Endpoint assignments */ + wait_queue_head_t wait_q; /* for timeouts */ + struct mutex lock; /* general race avoidance */ +}; + +static DEFINE_MUTEX(rio500_mutex); +static struct rio_usb_data rio_instance; + +static int open_rio(struct inode *inode, struct file *file) +{ + struct rio_usb_data *rio = &rio_instance; + + /* against disconnect() */ + mutex_lock(&rio500_mutex); + mutex_lock(&(rio->lock)); + + if (rio->isopen || !rio->present) { + mutex_unlock(&(rio->lock)); + mutex_unlock(&rio500_mutex); + return -EBUSY; + } + rio->isopen = 1; + + init_waitqueue_head(&rio->wait_q); + + mutex_unlock(&(rio->lock)); + + dev_info(&rio->rio_dev->dev, "Rio opened.\n"); + mutex_unlock(&rio500_mutex); + + return 0; +} + +static int close_rio(struct inode *inode, struct file *file) +{ + struct rio_usb_data *rio = &rio_instance; + + rio->isopen = 0; + + dev_info(&rio->rio_dev->dev, "Rio closed.\n"); + return 0; +} + +static long ioctl_rio(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct RioCommand rio_cmd; + struct rio_usb_data *rio = &rio_instance; + void __user *data; + unsigned char *buffer; + int result, requesttype; + int retries; + int retval=0; + + mutex_lock(&(rio->lock)); + /* Sanity check to make sure rio is connected, powered, etc */ + if (rio->present == 0 || rio->rio_dev == NULL) { + retval = -ENODEV; + goto err_out; + } + + switch (cmd) { + case RIO_RECV_COMMAND: + data = (void __user *) arg; + if (data == NULL) + break; + if (copy_from_user(&rio_cmd, data, sizeof(struct RioCommand))) { + retval = -EFAULT; + goto err_out; + } + if (rio_cmd.length < 0 || rio_cmd.length > PAGE_SIZE) { + retval = -EINVAL; + goto err_out; + } + buffer = (unsigned char *) __get_free_page(GFP_KERNEL); + if (buffer == NULL) { + retval = -ENOMEM; + goto err_out; + } + if (copy_from_user(buffer, rio_cmd.buffer, rio_cmd.length)) { + retval = -EFAULT; + free_page((unsigned long) buffer); + goto err_out; + } + + requesttype = rio_cmd.requesttype | USB_DIR_IN | + USB_TYPE_VENDOR | USB_RECIP_DEVICE; + dev_dbg(&rio->rio_dev->dev, + "sending command:reqtype=%0x req=%0x value=%0x index=%0x len=%0x\n", + requesttype, rio_cmd.request, rio_cmd.value, + rio_cmd.index, rio_cmd.length); + /* Send rio control message */ + retries = 3; + while (retries) { + result = usb_control_msg(rio->rio_dev, + usb_rcvctrlpipe(rio-> rio_dev, 0), + rio_cmd.request, + requesttype, + rio_cmd.value, + rio_cmd.index, buffer, + rio_cmd.length, + jiffies_to_msecs(rio_cmd.timeout)); + if (result == -ETIMEDOUT) + retries--; + else if (result < 0) { + dev_err(&rio->rio_dev->dev, + "Error executing ioctrl. code = %d\n", + result); + retries = 0; + } else { + dev_dbg(&rio->rio_dev->dev, + "Executed ioctl. Result = %d (data=%02x)\n", + result, buffer[0]); + if (copy_to_user(rio_cmd.buffer, buffer, + rio_cmd.length)) { + free_page((unsigned long) buffer); + retval = -EFAULT; + goto err_out; + } + retries = 0; + } + + /* rio_cmd.buffer contains a raw stream of single byte + data which has been returned from rio. Data is + interpreted at application level. For data that + will be cast to data types longer than 1 byte, data + will be little_endian and will potentially need to + be swapped at the app level */ + + } + free_page((unsigned long) buffer); + break; + + case RIO_SEND_COMMAND: + data = (void __user *) arg; + if (data == NULL) + break; + if (copy_from_user(&rio_cmd, data, sizeof(struct RioCommand))) { + retval = -EFAULT; + goto err_out; + } + if (rio_cmd.length < 0 || rio_cmd.length > PAGE_SIZE) { + retval = -EINVAL; + goto err_out; + } + buffer = (unsigned char *) __get_free_page(GFP_KERNEL); + if (buffer == NULL) { + retval = -ENOMEM; + goto err_out; + } + if (copy_from_user(buffer, rio_cmd.buffer, rio_cmd.length)) { + free_page((unsigned long)buffer); + retval = -EFAULT; + goto err_out; + } + + requesttype = rio_cmd.requesttype | USB_DIR_OUT | + USB_TYPE_VENDOR | USB_RECIP_DEVICE; + dev_dbg(&rio->rio_dev->dev, + "sending command: reqtype=%0x req=%0x value=%0x index=%0x len=%0x\n", + requesttype, rio_cmd.request, rio_cmd.value, + rio_cmd.index, rio_cmd.length); + /* Send rio control message */ + retries = 3; + while (retries) { + result = usb_control_msg(rio->rio_dev, + usb_sndctrlpipe(rio-> rio_dev, 0), + rio_cmd.request, + requesttype, + rio_cmd.value, + rio_cmd.index, buffer, + rio_cmd.length, + jiffies_to_msecs(rio_cmd.timeout)); + if (result == -ETIMEDOUT) + retries--; + else if (result < 0) { + dev_err(&rio->rio_dev->dev, + "Error executing ioctrl. code = %d\n", + result); + retries = 0; + } else { + dev_dbg(&rio->rio_dev->dev, + "Executed ioctl. Result = %d\n", result); + retries = 0; + + } + + } + free_page((unsigned long) buffer); + break; + + default: + retval = -ENOTTY; + break; + } + + +err_out: + mutex_unlock(&(rio->lock)); + return retval; +} + +static ssize_t +write_rio(struct file *file, const char __user *buffer, + size_t count, loff_t * ppos) +{ + DEFINE_WAIT(wait); + struct rio_usb_data *rio = &rio_instance; + + unsigned long copy_size; + unsigned long bytes_written = 0; + unsigned int partial; + + int result = 0; + int maxretry; + int errn = 0; + int intr; + + intr = mutex_lock_interruptible(&(rio->lock)); + if (intr) + return -EINTR; + /* Sanity check to make sure rio is connected, powered, etc */ + if (rio->present == 0 || rio->rio_dev == NULL) { + mutex_unlock(&(rio->lock)); + return -ENODEV; + } + + + + do { + unsigned long thistime; + char *obuf = rio->obuf; + + thistime = copy_size = + (count >= OBUF_SIZE) ? OBUF_SIZE : count; + if (copy_from_user(rio->obuf, buffer, copy_size)) { + errn = -EFAULT; + goto error; + } + maxretry = 5; + while (thistime) { + if (!rio->rio_dev) { + errn = -ENODEV; + goto error; + } + if (signal_pending(current)) { + mutex_unlock(&(rio->lock)); + return bytes_written ? bytes_written : -EINTR; + } + + result = usb_bulk_msg(rio->rio_dev, + usb_sndbulkpipe(rio->rio_dev, 2), + obuf, thistime, &partial, 5000); + + dev_dbg(&rio->rio_dev->dev, + "write stats: result:%d thistime:%lu partial:%u\n", + result, thistime, partial); + + if (result == -ETIMEDOUT) { /* NAK - so hold for a while */ + if (!maxretry--) { + errn = -ETIME; + goto error; + } + prepare_to_wait(&rio->wait_q, &wait, TASK_INTERRUPTIBLE); + schedule_timeout(NAK_TIMEOUT); + finish_wait(&rio->wait_q, &wait); + continue; + } else if (!result && partial) { + obuf += partial; + thistime -= partial; + } else + break; + } + if (result) { + dev_err(&rio->rio_dev->dev, "Write Whoops - %x\n", + result); + errn = -EIO; + goto error; + } + bytes_written += copy_size; + count -= copy_size; + buffer += copy_size; + } while (count > 0); + + mutex_unlock(&(rio->lock)); + + return bytes_written ? bytes_written : -EIO; + +error: + mutex_unlock(&(rio->lock)); + return errn; +} + +static ssize_t +read_rio(struct file *file, char __user *buffer, size_t count, loff_t * ppos) +{ + DEFINE_WAIT(wait); + struct rio_usb_data *rio = &rio_instance; + ssize_t read_count; + unsigned int partial; + int this_read; + int result; + int maxretry = 10; + char *ibuf; + int intr; + + intr = mutex_lock_interruptible(&(rio->lock)); + if (intr) + return -EINTR; + /* Sanity check to make sure rio is connected, powered, etc */ + if (rio->present == 0 || rio->rio_dev == NULL) { + mutex_unlock(&(rio->lock)); + return -ENODEV; + } + + ibuf = rio->ibuf; + + read_count = 0; + + + while (count > 0) { + if (signal_pending(current)) { + mutex_unlock(&(rio->lock)); + return read_count ? read_count : -EINTR; + } + if (!rio->rio_dev) { + mutex_unlock(&(rio->lock)); + return -ENODEV; + } + this_read = (count >= IBUF_SIZE) ? IBUF_SIZE : count; + + result = usb_bulk_msg(rio->rio_dev, + usb_rcvbulkpipe(rio->rio_dev, 1), + ibuf, this_read, &partial, + 8000); + + dev_dbg(&rio->rio_dev->dev, + "read stats: result:%d this_read:%u partial:%u\n", + result, this_read, partial); + + if (partial) { + count = this_read = partial; + } else if (result == -ETIMEDOUT || result == 15) { /* FIXME: 15 ??? */ + if (!maxretry--) { + mutex_unlock(&(rio->lock)); + dev_err(&rio->rio_dev->dev, + "read_rio: maxretry timeout\n"); + return -ETIME; + } + prepare_to_wait(&rio->wait_q, &wait, TASK_INTERRUPTIBLE); + schedule_timeout(NAK_TIMEOUT); + finish_wait(&rio->wait_q, &wait); + continue; + } else if (result != -EREMOTEIO) { + mutex_unlock(&(rio->lock)); + dev_err(&rio->rio_dev->dev, + "Read Whoops - result:%u partial:%u this_read:%u\n", + result, partial, this_read); + return -EIO; + } else { + mutex_unlock(&(rio->lock)); + return (0); + } + + if (this_read) { + if (copy_to_user(buffer, ibuf, this_read)) { + mutex_unlock(&(rio->lock)); + return -EFAULT; + } + count -= this_read; + read_count += this_read; + buffer += this_read; + } + } + mutex_unlock(&(rio->lock)); + return read_count; +} + +static const struct file_operations usb_rio_fops = { + .owner = THIS_MODULE, + .read = read_rio, + .write = write_rio, + .unlocked_ioctl = ioctl_rio, + .open = open_rio, + .release = close_rio, + .llseek = noop_llseek, +}; + +static struct usb_class_driver usb_rio_class = { + .name = "rio500%d", + .fops = &usb_rio_fops, + .minor_base = RIO_MINOR, +}; + +static int probe_rio(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct rio_usb_data *rio = &rio_instance; + int retval; + + dev_info(&intf->dev, "USB Rio found at address %d\n", dev->devnum); + + retval = usb_register_dev(intf, &usb_rio_class); + if (retval) { + dev_err(&dev->dev, + "Not able to get a minor for this device.\n"); + return -ENOMEM; + } + + rio->rio_dev = dev; + + if (!(rio->obuf = kmalloc(OBUF_SIZE, GFP_KERNEL))) { + dev_err(&dev->dev, + "probe_rio: Not enough memory for the output buffer\n"); + usb_deregister_dev(intf, &usb_rio_class); + return -ENOMEM; + } + dev_dbg(&intf->dev, "obuf address:%p\n", rio->obuf); + + if (!(rio->ibuf = kmalloc(IBUF_SIZE, GFP_KERNEL))) { + dev_err(&dev->dev, + "probe_rio: Not enough memory for the input buffer\n"); + usb_deregister_dev(intf, &usb_rio_class); + kfree(rio->obuf); + return -ENOMEM; + } + dev_dbg(&intf->dev, "ibuf address:%p\n", rio->ibuf); + + mutex_init(&(rio->lock)); + + usb_set_intfdata (intf, rio); + rio->present = 1; + + return 0; +} + +static void disconnect_rio(struct usb_interface *intf) +{ + struct rio_usb_data *rio = usb_get_intfdata (intf); + + usb_set_intfdata (intf, NULL); + mutex_lock(&rio500_mutex); + if (rio) { + usb_deregister_dev(intf, &usb_rio_class); + + mutex_lock(&(rio->lock)); + if (rio->isopen) { + rio->isopen = 0; + /* better let it finish - the release will do whats needed */ + rio->rio_dev = NULL; + mutex_unlock(&(rio->lock)); + mutex_unlock(&rio500_mutex); + return; + } + kfree(rio->ibuf); + kfree(rio->obuf); + + dev_info(&intf->dev, "USB Rio disconnected.\n"); + + rio->present = 0; + mutex_unlock(&(rio->lock)); + } + mutex_unlock(&rio500_mutex); +} + +static const struct usb_device_id rio_table[] = { + { USB_DEVICE(0x0841, 1) }, /* Rio 500 */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, rio_table); + +static struct usb_driver rio_driver = { + .name = "rio500", + .probe = probe_rio, + .disconnect = disconnect_rio, + .id_table = rio_table, +}; + +module_usb_driver(rio_driver); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/misc/rio500_usb.h b/drivers/usb/misc/rio500_usb.h new file mode 100644 index 00000000..359abc98 --- /dev/null +++ b/drivers/usb/misc/rio500_usb.h @@ -0,0 +1,37 @@ +/* ---------------------------------------------------------------------- + + Copyright (C) 2000 Cesar Miquel (miquel@df.uba.ar) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + ---------------------------------------------------------------------- */ + + + +#define RIO_SEND_COMMAND 0x1 +#define RIO_RECV_COMMAND 0x2 + +#define RIO_DIR_OUT 0x0 +#define RIO_DIR_IN 0x1 + +struct RioCommand { + short length; + int request; + int requesttype; + int value; + int index; + void __user *buffer; + int timeout; +}; diff --git a/drivers/usb/misc/sisusbvga/Kconfig b/drivers/usb/misc/sisusbvga/Kconfig new file mode 100644 index 00000000..0d03a520 --- /dev/null +++ b/drivers/usb/misc/sisusbvga/Kconfig @@ -0,0 +1,46 @@ + +config USB_SISUSBVGA + tristate "USB 2.0 SVGA dongle support (Net2280/SiS315)" + depends on (USB_MUSB_HDRC || USB_EHCI_HCD) + ---help--- + Say Y here if you intend to attach a USB2VGA dongle based on a + Net2280 and a SiS315 chip. + + Note that this device requires a USB 2.0 host controller. It will not + work with USB 1.x controllers. + + To compile this driver as a module, choose M here; the module will be + called sisusbvga. If unsure, say N. + +config USB_SISUSBVGA_CON + bool "Text console and mode switching support" if USB_SISUSBVGA + depends on VT + select FONT_8x16 + ---help--- + Say Y here if you want a VGA text console via the USB dongle or + want to support userland applications that utilize the driver's + display mode switching capabilities. + + Note that this console supports VGA/EGA text mode only. + + By default, the console part of the driver will not kick in when + the driver is initialized. If you want the driver to take over + one or more of the consoles, you need to specify the number of + the first and last consoles (starting at 1) as driver parameters. + + For example, if the driver is compiled as a module: + + modprobe sisusbvga first=1 last=5 + + If you use hotplug, add this to your modutils config files with + the "options" keyword, such as eg. + + options sisusbvga first=1 last=5 + + If the driver is compiled into the kernel image, the parameters + must be given in the kernel command like, such as + + sisusbvga.first=1 sisusbvga.last=5 + + + diff --git a/drivers/usb/misc/sisusbvga/Makefile b/drivers/usb/misc/sisusbvga/Makefile new file mode 100644 index 00000000..3142476c --- /dev/null +++ b/drivers/usb/misc/sisusbvga/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the sisusb driver (if driver is inside kernel tree). +# + +obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga.o + +sisusbvga-y := sisusb.o sisusb_init.o sisusb_con.o diff --git a/drivers/usb/misc/sisusbvga/sisusb.c b/drivers/usb/misc/sisusbvga/sisusb.c new file mode 100644 index 00000000..0aef801e --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb.c @@ -0,0 +1,3287 @@ +/* + * sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles + * + * Main part + * + * Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, this code is licensed under the + * terms of the GPL v2. + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific psisusbr written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sisusb.h" +#include "sisusb_init.h" + +#ifdef INCL_SISUSB_CON +#include +#endif + +#define SISUSB_DONTSYNC + +/* Forward declarations / clean-up routines */ + +#ifdef INCL_SISUSB_CON +static int sisusb_first_vc = 0; +static int sisusb_last_vc = 0; +module_param_named(first, sisusb_first_vc, int, 0); +module_param_named(last, sisusb_last_vc, int, 0); +MODULE_PARM_DESC(first, "Number of first console to take over (1 - MAX_NR_CONSOLES)"); +MODULE_PARM_DESC(last, "Number of last console to take over (1 - MAX_NR_CONSOLES)"); +#endif + +static struct usb_driver sisusb_driver; + +static void +sisusb_free_buffers(struct sisusb_usb_data *sisusb) +{ + int i; + + for (i = 0; i < NUMOBUFS; i++) { + if (sisusb->obuf[i]) { + kfree(sisusb->obuf[i]); + sisusb->obuf[i] = NULL; + } + } + if (sisusb->ibuf) { + kfree(sisusb->ibuf); + sisusb->ibuf = NULL; + } +} + +static void +sisusb_free_urbs(struct sisusb_usb_data *sisusb) +{ + int i; + + for (i = 0; i < NUMOBUFS; i++) { + usb_free_urb(sisusb->sisurbout[i]); + sisusb->sisurbout[i] = NULL; + } + usb_free_urb(sisusb->sisurbin); + sisusb->sisurbin = NULL; +} + +/* Level 0: USB transport layer */ + +/* 1. out-bulks */ + +/* out-urb management */ + +/* Return 1 if all free, 0 otherwise */ +static int +sisusb_all_free(struct sisusb_usb_data *sisusb) +{ + int i; + + for (i = 0; i < sisusb->numobufs; i++) { + + if (sisusb->urbstatus[i] & SU_URB_BUSY) + return 0; + + } + + return 1; +} + +/* Kill all busy URBs */ +static void +sisusb_kill_all_busy(struct sisusb_usb_data *sisusb) +{ + int i; + + if (sisusb_all_free(sisusb)) + return; + + for (i = 0; i < sisusb->numobufs; i++) { + + if (sisusb->urbstatus[i] & SU_URB_BUSY) + usb_kill_urb(sisusb->sisurbout[i]); + + } +} + +/* Return 1 if ok, 0 if error (not all complete within timeout) */ +static int +sisusb_wait_all_out_complete(struct sisusb_usb_data *sisusb) +{ + int timeout = 5 * HZ, i = 1; + + wait_event_timeout(sisusb->wait_q, + (i = sisusb_all_free(sisusb)), + timeout); + + return i; +} + +static int +sisusb_outurb_available(struct sisusb_usb_data *sisusb) +{ + int i; + + for (i = 0; i < sisusb->numobufs; i++) { + + if ((sisusb->urbstatus[i] & (SU_URB_BUSY|SU_URB_ALLOC)) == 0) + return i; + + } + + return -1; +} + +static int +sisusb_get_free_outbuf(struct sisusb_usb_data *sisusb) +{ + int i, timeout = 5 * HZ; + + wait_event_timeout(sisusb->wait_q, + ((i = sisusb_outurb_available(sisusb)) >= 0), + timeout); + + return i; +} + +static int +sisusb_alloc_outbuf(struct sisusb_usb_data *sisusb) +{ + int i; + + i = sisusb_outurb_available(sisusb); + + if (i >= 0) + sisusb->urbstatus[i] |= SU_URB_ALLOC; + + return i; +} + +static void +sisusb_free_outbuf(struct sisusb_usb_data *sisusb, int index) +{ + if ((index >= 0) && (index < sisusb->numobufs)) + sisusb->urbstatus[index] &= ~SU_URB_ALLOC; +} + +/* completion callback */ + +static void +sisusb_bulk_completeout(struct urb *urb) +{ + struct sisusb_urb_context *context = urb->context; + struct sisusb_usb_data *sisusb; + + if (!context) + return; + + sisusb = context->sisusb; + + if (!sisusb || !sisusb->sisusb_dev || !sisusb->present) + return; + +#ifndef SISUSB_DONTSYNC + if (context->actual_length) + *(context->actual_length) += urb->actual_length; +#endif + + sisusb->urbstatus[context->urbindex] &= ~SU_URB_BUSY; + wake_up(&sisusb->wait_q); +} + +static int +sisusb_bulkout_msg(struct sisusb_usb_data *sisusb, int index, unsigned int pipe, void *data, + int len, int *actual_length, int timeout, unsigned int tflags) +{ + struct urb *urb = sisusb->sisurbout[index]; + int retval, byteswritten = 0; + + /* Set up URB */ + urb->transfer_flags = 0; + + usb_fill_bulk_urb(urb, sisusb->sisusb_dev, pipe, data, len, + sisusb_bulk_completeout, &sisusb->urbout_context[index]); + + urb->transfer_flags |= tflags; + urb->actual_length = 0; + + /* Set up context */ + sisusb->urbout_context[index].actual_length = (timeout) ? + NULL : actual_length; + + /* Declare this urb/buffer in use */ + sisusb->urbstatus[index] |= SU_URB_BUSY; + + /* Submit URB */ + retval = usb_submit_urb(urb, GFP_KERNEL); + + /* If OK, and if timeout > 0, wait for completion */ + if ((retval == 0) && timeout) { + wait_event_timeout(sisusb->wait_q, + (!(sisusb->urbstatus[index] & SU_URB_BUSY)), + timeout); + if (sisusb->urbstatus[index] & SU_URB_BUSY) { + /* URB timed out... kill it and report error */ + usb_kill_urb(urb); + retval = -ETIMEDOUT; + } else { + /* Otherwise, report urb status */ + retval = urb->status; + byteswritten = urb->actual_length; + } + } + + if (actual_length) + *actual_length = byteswritten; + + return retval; +} + +/* 2. in-bulks */ + +/* completion callback */ + +static void +sisusb_bulk_completein(struct urb *urb) +{ + struct sisusb_usb_data *sisusb = urb->context; + + if (!sisusb || !sisusb->sisusb_dev || !sisusb->present) + return; + + sisusb->completein = 1; + wake_up(&sisusb->wait_q); +} + +static int +sisusb_bulkin_msg(struct sisusb_usb_data *sisusb, unsigned int pipe, void *data, + int len, int *actual_length, int timeout, unsigned int tflags) +{ + struct urb *urb = sisusb->sisurbin; + int retval, readbytes = 0; + + urb->transfer_flags = 0; + + usb_fill_bulk_urb(urb, sisusb->sisusb_dev, pipe, data, len, + sisusb_bulk_completein, sisusb); + + urb->transfer_flags |= tflags; + urb->actual_length = 0; + + sisusb->completein = 0; + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval == 0) { + wait_event_timeout(sisusb->wait_q, sisusb->completein, timeout); + if (!sisusb->completein) { + /* URB timed out... kill it and report error */ + usb_kill_urb(urb); + retval = -ETIMEDOUT; + } else { + /* URB completed within timeout */ + retval = urb->status; + readbytes = urb->actual_length; + } + } + + if (actual_length) + *actual_length = readbytes; + + return retval; +} + + +/* Level 1: */ + +/* Send a bulk message of variable size + * + * To copy the data from userspace, give pointer to "userbuffer", + * to copy from (non-DMA) kernel memory, give "kernbuffer". If + * both of these are NULL, it is assumed, that the transfer + * buffer "sisusb->obuf[index]" is set up with the data to send. + * Index is ignored if either kernbuffer or userbuffer is set. + * If async is nonzero, URBs will be sent without waiting for + * completion of the previous URB. + * + * (return 0 on success) + */ + +static int sisusb_send_bulk_msg(struct sisusb_usb_data *sisusb, int ep, int len, + char *kernbuffer, const char __user *userbuffer, int index, + ssize_t *bytes_written, unsigned int tflags, int async) +{ + int result = 0, retry, count = len; + int passsize, thispass, transferred_len = 0; + int fromuser = (userbuffer != NULL) ? 1 : 0; + int fromkern = (kernbuffer != NULL) ? 1 : 0; + unsigned int pipe; + char *buffer; + + (*bytes_written) = 0; + + /* Sanity check */ + if (!sisusb || !sisusb->present || !sisusb->sisusb_dev) + return -ENODEV; + + /* If we copy data from kernel or userspace, force the + * allocation of a buffer/urb. If we have the data in + * the transfer buffer[index] already, reuse the buffer/URB + * if the length is > buffer size. (So, transmitting + * large data amounts directly from the transfer buffer + * treats the buffer as a ring buffer. However, we need + * to sync in this case.) + */ + if (fromuser || fromkern) + index = -1; + else if (len > sisusb->obufsize) + async = 0; + + pipe = usb_sndbulkpipe(sisusb->sisusb_dev, ep); + + do { + passsize = thispass = (sisusb->obufsize < count) ? + sisusb->obufsize : count; + + if (index < 0) + index = sisusb_get_free_outbuf(sisusb); + + if (index < 0) + return -EIO; + + buffer = sisusb->obuf[index]; + + if (fromuser) { + + if (copy_from_user(buffer, userbuffer, passsize)) + return -EFAULT; + + userbuffer += passsize; + + } else if (fromkern) { + + memcpy(buffer, kernbuffer, passsize); + kernbuffer += passsize; + + } + + retry = 5; + while (thispass) { + + if (!sisusb->sisusb_dev) + return -ENODEV; + + result = sisusb_bulkout_msg(sisusb, + index, + pipe, + buffer, + thispass, + &transferred_len, + async ? 0 : 5 * HZ, + tflags); + + if (result == -ETIMEDOUT) { + + /* Will not happen if async */ + if (!retry--) + return -ETIME; + + continue; + } + + if ((result == 0) && !async && transferred_len) { + + thispass -= transferred_len; + buffer += transferred_len; + + } else + break; + } + + if (result) + return result; + + (*bytes_written) += passsize; + count -= passsize; + + /* Force new allocation in next iteration */ + if (fromuser || fromkern) + index = -1; + + } while (count > 0); + + if (async) { +#ifdef SISUSB_DONTSYNC + (*bytes_written) = len; + /* Some URBs/buffers might be busy */ +#else + sisusb_wait_all_out_complete(sisusb); + (*bytes_written) = transferred_len; + /* All URBs and all buffers are available */ +#endif + } + + return ((*bytes_written) == len) ? 0 : -EIO; +} + +/* Receive a bulk message of variable size + * + * To copy the data to userspace, give pointer to "userbuffer", + * to copy to kernel memory, give "kernbuffer". One of them + * MUST be set. (There is no technique for letting the caller + * read directly from the ibuf.) + * + */ + +static int sisusb_recv_bulk_msg(struct sisusb_usb_data *sisusb, int ep, int len, + void *kernbuffer, char __user *userbuffer, ssize_t *bytes_read, + unsigned int tflags) +{ + int result = 0, retry, count = len; + int bufsize, thispass, transferred_len; + unsigned int pipe; + char *buffer; + + (*bytes_read) = 0; + + /* Sanity check */ + if (!sisusb || !sisusb->present || !sisusb->sisusb_dev) + return -ENODEV; + + pipe = usb_rcvbulkpipe(sisusb->sisusb_dev, ep); + buffer = sisusb->ibuf; + bufsize = sisusb->ibufsize; + + retry = 5; + +#ifdef SISUSB_DONTSYNC + if (!(sisusb_wait_all_out_complete(sisusb))) + return -EIO; +#endif + + while (count > 0) { + + if (!sisusb->sisusb_dev) + return -ENODEV; + + thispass = (bufsize < count) ? bufsize : count; + + result = sisusb_bulkin_msg(sisusb, + pipe, + buffer, + thispass, + &transferred_len, + 5 * HZ, + tflags); + + if (transferred_len) + thispass = transferred_len; + + else if (result == -ETIMEDOUT) { + + if (!retry--) + return -ETIME; + + continue; + + } else + return -EIO; + + + if (thispass) { + + (*bytes_read) += thispass; + count -= thispass; + + if (userbuffer) { + + if (copy_to_user(userbuffer, buffer, thispass)) + return -EFAULT; + + userbuffer += thispass; + + } else { + + memcpy(kernbuffer, buffer, thispass); + kernbuffer += thispass; + + } + + } + + } + + return ((*bytes_read) == len) ? 0 : -EIO; +} + +static int sisusb_send_packet(struct sisusb_usb_data *sisusb, int len, + struct sisusb_packet *packet) +{ + int ret; + ssize_t bytes_transferred = 0; + __le32 tmp; + + if (len == 6) + packet->data = 0; + +#ifdef SISUSB_DONTSYNC + if (!(sisusb_wait_all_out_complete(sisusb))) + return 1; +#endif + + /* Eventually correct endianness */ + SISUSB_CORRECT_ENDIANNESS_PACKET(packet); + + /* 1. send the packet */ + ret = sisusb_send_bulk_msg(sisusb, SISUSB_EP_GFX_OUT, len, + (char *)packet, NULL, 0, &bytes_transferred, 0, 0); + + if ((ret == 0) && (len == 6)) { + + /* 2. if packet len == 6, it means we read, so wait for 32bit + * return value and write it to packet->data + */ + ret = sisusb_recv_bulk_msg(sisusb, SISUSB_EP_GFX_IN, 4, + (char *)&tmp, NULL, &bytes_transferred, 0); + + packet->data = le32_to_cpu(tmp); + } + + return ret; +} + +static int sisusb_send_bridge_packet(struct sisusb_usb_data *sisusb, int len, + struct sisusb_packet *packet, + unsigned int tflags) +{ + int ret; + ssize_t bytes_transferred = 0; + __le32 tmp; + + if (len == 6) + packet->data = 0; + +#ifdef SISUSB_DONTSYNC + if (!(sisusb_wait_all_out_complete(sisusb))) + return 1; +#endif + + /* Eventually correct endianness */ + SISUSB_CORRECT_ENDIANNESS_PACKET(packet); + + /* 1. send the packet */ + ret = sisusb_send_bulk_msg(sisusb, SISUSB_EP_BRIDGE_OUT, len, + (char *)packet, NULL, 0, &bytes_transferred, tflags, 0); + + if ((ret == 0) && (len == 6)) { + + /* 2. if packet len == 6, it means we read, so wait for 32bit + * return value and write it to packet->data + */ + ret = sisusb_recv_bulk_msg(sisusb, SISUSB_EP_BRIDGE_IN, 4, + (char *)&tmp, NULL, &bytes_transferred, 0); + + packet->data = le32_to_cpu(tmp); + } + + return ret; +} + +/* access video memory and mmio (return 0 on success) */ + +/* Low level */ + +/* The following routines assume being used to transfer byte, word, + * long etc. + * This means that + * - the write routines expect "data" in machine endianness format. + * The data will be converted to leXX in sisusb_xxx_packet. + * - the read routines can expect read data in machine-endianess. + */ + +static int sisusb_write_memio_byte(struct sisusb_usb_data *sisusb, int type, + u32 addr, u8 data) +{ + struct sisusb_packet packet; + int ret; + + packet.header = (1 << (addr & 3)) | (type << 6); + packet.address = addr & ~3; + packet.data = data << ((addr & 3) << 3); + ret = sisusb_send_packet(sisusb, 10, &packet); + return ret; +} + +static int sisusb_write_memio_word(struct sisusb_usb_data *sisusb, int type, + u32 addr, u16 data) +{ + struct sisusb_packet packet; + int ret = 0; + + packet.address = addr & ~3; + + switch (addr & 3) { + case 0: + packet.header = (type << 6) | 0x0003; + packet.data = (u32)data; + ret = sisusb_send_packet(sisusb, 10, &packet); + break; + case 1: + packet.header = (type << 6) | 0x0006; + packet.data = (u32)data << 8; + ret = sisusb_send_packet(sisusb, 10, &packet); + break; + case 2: + packet.header = (type << 6) | 0x000c; + packet.data = (u32)data << 16; + ret = sisusb_send_packet(sisusb, 10, &packet); + break; + case 3: + packet.header = (type << 6) | 0x0008; + packet.data = (u32)data << 24; + ret = sisusb_send_packet(sisusb, 10, &packet); + packet.header = (type << 6) | 0x0001; + packet.address = (addr & ~3) + 4; + packet.data = (u32)data >> 8; + ret |= sisusb_send_packet(sisusb, 10, &packet); + } + + return ret; +} + +static int sisusb_write_memio_24bit(struct sisusb_usb_data *sisusb, int type, + u32 addr, u32 data) +{ + struct sisusb_packet packet; + int ret = 0; + + packet.address = addr & ~3; + + switch (addr & 3) { + case 0: + packet.header = (type << 6) | 0x0007; + packet.data = data & 0x00ffffff; + ret = sisusb_send_packet(sisusb, 10, &packet); + break; + case 1: + packet.header = (type << 6) | 0x000e; + packet.data = data << 8; + ret = sisusb_send_packet(sisusb, 10, &packet); + break; + case 2: + packet.header = (type << 6) | 0x000c; + packet.data = data << 16; + ret = sisusb_send_packet(sisusb, 10, &packet); + packet.header = (type << 6) | 0x0001; + packet.address = (addr & ~3) + 4; + packet.data = (data >> 16) & 0x00ff; + ret |= sisusb_send_packet(sisusb, 10, &packet); + break; + case 3: + packet.header = (type << 6) | 0x0008; + packet.data = data << 24; + ret = sisusb_send_packet(sisusb, 10, &packet); + packet.header = (type << 6) | 0x0003; + packet.address = (addr & ~3) + 4; + packet.data = (data >> 8) & 0xffff; + ret |= sisusb_send_packet(sisusb, 10, &packet); + } + + return ret; +} + +static int sisusb_write_memio_long(struct sisusb_usb_data *sisusb, int type, + u32 addr, u32 data) +{ + struct sisusb_packet packet; + int ret = 0; + + packet.address = addr & ~3; + + switch (addr & 3) { + case 0: + packet.header = (type << 6) | 0x000f; + packet.data = data; + ret = sisusb_send_packet(sisusb, 10, &packet); + break; + case 1: + packet.header = (type << 6) | 0x000e; + packet.data = data << 8; + ret = sisusb_send_packet(sisusb, 10, &packet); + packet.header = (type << 6) | 0x0001; + packet.address = (addr & ~3) + 4; + packet.data = data >> 24; + ret |= sisusb_send_packet(sisusb, 10, &packet); + break; + case 2: + packet.header = (type << 6) | 0x000c; + packet.data = data << 16; + ret = sisusb_send_packet(sisusb, 10, &packet); + packet.header = (type << 6) | 0x0003; + packet.address = (addr & ~3) + 4; + packet.data = data >> 16; + ret |= sisusb_send_packet(sisusb, 10, &packet); + break; + case 3: + packet.header = (type << 6) | 0x0008; + packet.data = data << 24; + ret = sisusb_send_packet(sisusb, 10, &packet); + packet.header = (type << 6) | 0x0007; + packet.address = (addr & ~3) + 4; + packet.data = data >> 8; + ret |= sisusb_send_packet(sisusb, 10, &packet); + } + + return ret; +} + +/* The xxx_bulk routines copy a buffer of variable size. They treat the + * buffer as chars, therefore lsb/msb has to be corrected if using the + * byte/word/long/etc routines for speed-up + * + * If data is from userland, set "userbuffer" (and clear "kernbuffer"), + * if data is in kernel space, set "kernbuffer" (and clear "userbuffer"); + * if neither "kernbuffer" nor "userbuffer" are given, it is assumed + * that the data already is in the transfer buffer "sisusb->obuf[index]". + */ + +static int sisusb_write_mem_bulk(struct sisusb_usb_data *sisusb, u32 addr, + char *kernbuffer, int length, + const char __user *userbuffer, int index, + ssize_t *bytes_written) +{ + struct sisusb_packet packet; + int ret = 0; + static int msgcount = 0; + u8 swap8, fromkern = kernbuffer ? 1 : 0; + u16 swap16; + u32 swap32, flag = (length >> 28) & 1; + char buf[4]; + + /* if neither kernbuffer not userbuffer are given, assume + * data in obuf + */ + if (!fromkern && !userbuffer) + kernbuffer = sisusb->obuf[index]; + + (*bytes_written = 0); + + length &= 0x00ffffff; + + while (length) { + + switch (length) { + + case 1: + if (userbuffer) { + if (get_user(swap8, (u8 __user *)userbuffer)) + return -EFAULT; + } else + swap8 = kernbuffer[0]; + + ret = sisusb_write_memio_byte(sisusb, + SISUSB_TYPE_MEM, + addr, swap8); + + if (!ret) + (*bytes_written)++; + + return ret; + + case 2: + if (userbuffer) { + if (get_user(swap16, (u16 __user *)userbuffer)) + return -EFAULT; + } else + swap16 = *((u16 *)kernbuffer); + + ret = sisusb_write_memio_word(sisusb, + SISUSB_TYPE_MEM, + addr, + swap16); + + if (!ret) + (*bytes_written) += 2; + + return ret; + + case 3: + if (userbuffer) { + if (copy_from_user(&buf, userbuffer, 3)) + return -EFAULT; +#ifdef __BIG_ENDIAN + swap32 = (buf[0] << 16) | + (buf[1] << 8) | + buf[2]; +#else + swap32 = (buf[2] << 16) | + (buf[1] << 8) | + buf[0]; +#endif + } else +#ifdef __BIG_ENDIAN + swap32 = (kernbuffer[0] << 16) | + (kernbuffer[1] << 8) | + kernbuffer[2]; +#else + swap32 = (kernbuffer[2] << 16) | + (kernbuffer[1] << 8) | + kernbuffer[0]; +#endif + + ret = sisusb_write_memio_24bit(sisusb, + SISUSB_TYPE_MEM, + addr, + swap32); + + if (!ret) + (*bytes_written) += 3; + + return ret; + + case 4: + if (userbuffer) { + if (get_user(swap32, (u32 __user *)userbuffer)) + return -EFAULT; + } else + swap32 = *((u32 *)kernbuffer); + + ret = sisusb_write_memio_long(sisusb, + SISUSB_TYPE_MEM, + addr, + swap32); + if (!ret) + (*bytes_written) += 4; + + return ret; + + default: + if ((length & ~3) > 0x10000) { + + packet.header = 0x001f; + packet.address = 0x000001d4; + packet.data = addr; + ret = sisusb_send_bridge_packet(sisusb, 10, + &packet, 0); + packet.header = 0x001f; + packet.address = 0x000001d0; + packet.data = (length & ~3); + ret |= sisusb_send_bridge_packet(sisusb, 10, + &packet, 0); + packet.header = 0x001f; + packet.address = 0x000001c0; + packet.data = flag | 0x16; + ret |= sisusb_send_bridge_packet(sisusb, 10, + &packet, 0); + if (userbuffer) { + ret |= sisusb_send_bulk_msg(sisusb, + SISUSB_EP_GFX_LBULK_OUT, + (length & ~3), + NULL, userbuffer, 0, + bytes_written, 0, 1); + userbuffer += (*bytes_written); + } else if (fromkern) { + ret |= sisusb_send_bulk_msg(sisusb, + SISUSB_EP_GFX_LBULK_OUT, + (length & ~3), + kernbuffer, NULL, 0, + bytes_written, 0, 1); + kernbuffer += (*bytes_written); + } else { + ret |= sisusb_send_bulk_msg(sisusb, + SISUSB_EP_GFX_LBULK_OUT, + (length & ~3), + NULL, NULL, index, + bytes_written, 0, 1); + kernbuffer += ((*bytes_written) & + (sisusb->obufsize-1)); + } + + } else { + + packet.header = 0x001f; + packet.address = 0x00000194; + packet.data = addr; + ret = sisusb_send_bridge_packet(sisusb, 10, + &packet, 0); + packet.header = 0x001f; + packet.address = 0x00000190; + packet.data = (length & ~3); + ret |= sisusb_send_bridge_packet(sisusb, 10, + &packet, 0); + if (sisusb->flagb0 != 0x16) { + packet.header = 0x001f; + packet.address = 0x00000180; + packet.data = flag | 0x16; + ret |= sisusb_send_bridge_packet(sisusb, 10, + &packet, 0); + sisusb->flagb0 = 0x16; + } + if (userbuffer) { + ret |= sisusb_send_bulk_msg(sisusb, + SISUSB_EP_GFX_BULK_OUT, + (length & ~3), + NULL, userbuffer, 0, + bytes_written, 0, 1); + userbuffer += (*bytes_written); + } else if (fromkern) { + ret |= sisusb_send_bulk_msg(sisusb, + SISUSB_EP_GFX_BULK_OUT, + (length & ~3), + kernbuffer, NULL, 0, + bytes_written, 0, 1); + kernbuffer += (*bytes_written); + } else { + ret |= sisusb_send_bulk_msg(sisusb, + SISUSB_EP_GFX_BULK_OUT, + (length & ~3), + NULL, NULL, index, + bytes_written, 0, 1); + kernbuffer += ((*bytes_written) & + (sisusb->obufsize-1)); + } + } + if (ret) { + msgcount++; + if (msgcount < 500) + dev_err(&sisusb->sisusb_dev->dev, "Wrote %zd of %d bytes, error %d\n", + *bytes_written, length, ret); + else if (msgcount == 500) + dev_err(&sisusb->sisusb_dev->dev, "Too many errors, logging stopped\n"); + } + addr += (*bytes_written); + length -= (*bytes_written); + } + + if (ret) + break; + + } + + return ret ? -EIO : 0; +} + +/* Remember: Read data in packet is in machine-endianess! So for + * byte, word, 24bit, long no endian correction is necessary. + */ + +static int sisusb_read_memio_byte(struct sisusb_usb_data *sisusb, int type, + u32 addr, u8 *data) +{ + struct sisusb_packet packet; + int ret; + + CLEARPACKET(&packet); + packet.header = (1 << (addr & 3)) | (type << 6); + packet.address = addr & ~3; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = (u8)(packet.data >> ((addr & 3) << 3)); + return ret; +} + +static int sisusb_read_memio_word(struct sisusb_usb_data *sisusb, int type, + u32 addr, u16 *data) +{ + struct sisusb_packet packet; + int ret = 0; + + CLEARPACKET(&packet); + + packet.address = addr & ~3; + + switch (addr & 3) { + case 0: + packet.header = (type << 6) | 0x0003; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = (u16)(packet.data); + break; + case 1: + packet.header = (type << 6) | 0x0006; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = (u16)(packet.data >> 8); + break; + case 2: + packet.header = (type << 6) | 0x000c; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = (u16)(packet.data >> 16); + break; + case 3: + packet.header = (type << 6) | 0x0008; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = (u16)(packet.data >> 24); + packet.header = (type << 6) | 0x0001; + packet.address = (addr & ~3) + 4; + ret |= sisusb_send_packet(sisusb, 6, &packet); + *data |= (u16)(packet.data << 8); + } + + return ret; +} + +static int sisusb_read_memio_24bit(struct sisusb_usb_data *sisusb, int type, + u32 addr, u32 *data) +{ + struct sisusb_packet packet; + int ret = 0; + + packet.address = addr & ~3; + + switch (addr & 3) { + case 0: + packet.header = (type << 6) | 0x0007; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data & 0x00ffffff; + break; + case 1: + packet.header = (type << 6) | 0x000e; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data >> 8; + break; + case 2: + packet.header = (type << 6) | 0x000c; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data >> 16; + packet.header = (type << 6) | 0x0001; + packet.address = (addr & ~3) + 4; + ret |= sisusb_send_packet(sisusb, 6, &packet); + *data |= ((packet.data & 0xff) << 16); + break; + case 3: + packet.header = (type << 6) | 0x0008; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data >> 24; + packet.header = (type << 6) | 0x0003; + packet.address = (addr & ~3) + 4; + ret |= sisusb_send_packet(sisusb, 6, &packet); + *data |= ((packet.data & 0xffff) << 8); + } + + return ret; +} + +static int sisusb_read_memio_long(struct sisusb_usb_data *sisusb, int type, + u32 addr, u32 *data) +{ + struct sisusb_packet packet; + int ret = 0; + + packet.address = addr & ~3; + + switch (addr & 3) { + case 0: + packet.header = (type << 6) | 0x000f; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data; + break; + case 1: + packet.header = (type << 6) | 0x000e; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data >> 8; + packet.header = (type << 6) | 0x0001; + packet.address = (addr & ~3) + 4; + ret |= sisusb_send_packet(sisusb, 6, &packet); + *data |= (packet.data << 24); + break; + case 2: + packet.header = (type << 6) | 0x000c; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data >> 16; + packet.header = (type << 6) | 0x0003; + packet.address = (addr & ~3) + 4; + ret |= sisusb_send_packet(sisusb, 6, &packet); + *data |= (packet.data << 16); + break; + case 3: + packet.header = (type << 6) | 0x0008; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data >> 24; + packet.header = (type << 6) | 0x0007; + packet.address = (addr & ~3) + 4; + ret |= sisusb_send_packet(sisusb, 6, &packet); + *data |= (packet.data << 8); + } + + return ret; +} + +static int sisusb_read_mem_bulk(struct sisusb_usb_data *sisusb, u32 addr, + char *kernbuffer, int length, + char __user *userbuffer, ssize_t *bytes_read) +{ + int ret = 0; + char buf[4]; + u16 swap16; + u32 swap32; + + (*bytes_read = 0); + + length &= 0x00ffffff; + + while (length) { + + switch (length) { + + case 1: + + ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_MEM, + addr, &buf[0]); + if (!ret) { + (*bytes_read)++; + if (userbuffer) { + if (put_user(buf[0], + (u8 __user *)userbuffer)) { + return -EFAULT; + } + } else { + kernbuffer[0] = buf[0]; + } + } + return ret; + + case 2: + ret |= sisusb_read_memio_word(sisusb, SISUSB_TYPE_MEM, + addr, &swap16); + if (!ret) { + (*bytes_read) += 2; + if (userbuffer) { + if (put_user(swap16, + (u16 __user *)userbuffer)) + return -EFAULT; + } else { + *((u16 *)kernbuffer) = swap16; + } + } + return ret; + + case 3: + ret |= sisusb_read_memio_24bit(sisusb, SISUSB_TYPE_MEM, + addr, &swap32); + if (!ret) { + (*bytes_read) += 3; +#ifdef __BIG_ENDIAN + buf[0] = (swap32 >> 16) & 0xff; + buf[1] = (swap32 >> 8) & 0xff; + buf[2] = swap32 & 0xff; +#else + buf[2] = (swap32 >> 16) & 0xff; + buf[1] = (swap32 >> 8) & 0xff; + buf[0] = swap32 & 0xff; +#endif + if (userbuffer) { + if (copy_to_user(userbuffer, &buf[0], 3)) + return -EFAULT; + } else { + kernbuffer[0] = buf[0]; + kernbuffer[1] = buf[1]; + kernbuffer[2] = buf[2]; + } + } + return ret; + + default: + ret |= sisusb_read_memio_long(sisusb, SISUSB_TYPE_MEM, + addr, &swap32); + if (!ret) { + (*bytes_read) += 4; + if (userbuffer) { + if (put_user(swap32, + (u32 __user *)userbuffer)) + return -EFAULT; + + userbuffer += 4; + } else { + *((u32 *)kernbuffer) = swap32; + kernbuffer += 4; + } + addr += 4; + length -= 4; + } + } + + if (ret) + break; + } + + return ret; +} + +/* High level: Gfx (indexed) register access */ + +#ifdef INCL_SISUSB_CON +int +sisusb_setreg(struct sisusb_usb_data *sisusb, int port, u8 data) +{ + return sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, data); +} + +int +sisusb_getreg(struct sisusb_usb_data *sisusb, int port, u8 *data) +{ + return sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port, data); +} +#endif + +int +sisusb_setidxreg(struct sisusb_usb_data *sisusb, int port, u8 index, u8 data) +{ + int ret; + ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, index); + ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, data); + return ret; +} + +int +sisusb_getidxreg(struct sisusb_usb_data *sisusb, int port, u8 index, u8 *data) +{ + int ret; + ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, index); + ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, data); + return ret; +} + +int +sisusb_setidxregandor(struct sisusb_usb_data *sisusb, int port, u8 idx, + u8 myand, u8 myor) +{ + int ret; + u8 tmp; + + ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, idx); + ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, &tmp); + tmp &= myand; + tmp |= myor; + ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, tmp); + return ret; +} + +static int +sisusb_setidxregmask(struct sisusb_usb_data *sisusb, int port, u8 idx, + u8 data, u8 mask) +{ + int ret; + u8 tmp; + ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, idx); + ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, &tmp); + tmp &= ~(mask); + tmp |= (data & mask); + ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, tmp); + return ret; +} + +int +sisusb_setidxregor(struct sisusb_usb_data *sisusb, int port, u8 index, u8 myor) +{ + return(sisusb_setidxregandor(sisusb, port, index, 0xff, myor)); +} + +int +sisusb_setidxregand(struct sisusb_usb_data *sisusb, int port, u8 idx, u8 myand) +{ + return(sisusb_setidxregandor(sisusb, port, idx, myand, 0x00)); +} + +/* Write/read video ram */ + +#ifdef INCL_SISUSB_CON +int +sisusb_writeb(struct sisusb_usb_data *sisusb, u32 adr, u8 data) +{ + return(sisusb_write_memio_byte(sisusb, SISUSB_TYPE_MEM, adr, data)); +} + +int +sisusb_readb(struct sisusb_usb_data *sisusb, u32 adr, u8 *data) +{ + return(sisusb_read_memio_byte(sisusb, SISUSB_TYPE_MEM, adr, data)); +} + +int +sisusb_copy_memory(struct sisusb_usb_data *sisusb, char *src, + u32 dest, int length, size_t *bytes_written) +{ + return(sisusb_write_mem_bulk(sisusb, dest, src, length, NULL, 0, bytes_written)); +} + +#ifdef SISUSBENDIANTEST +int +sisusb_read_memory(struct sisusb_usb_data *sisusb, char *dest, + u32 src, int length, size_t *bytes_written) +{ + return(sisusb_read_mem_bulk(sisusb, src, dest, length, NULL, bytes_written)); +} +#endif +#endif + +#ifdef SISUSBENDIANTEST +static void +sisusb_testreadwrite(struct sisusb_usb_data *sisusb) +{ + static char srcbuffer[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }; + char destbuffer[10]; + size_t dummy; + int i,j; + + sisusb_copy_memory(sisusb, srcbuffer, sisusb->vrambase, 7, &dummy); + + for(i = 1; i <= 7; i++) { + dev_dbg(&sisusb->sisusb_dev->dev, "sisusb: rwtest %d bytes\n", i); + sisusb_read_memory(sisusb, destbuffer, sisusb->vrambase, i, &dummy); + for(j = 0; j < i; j++) { + dev_dbg(&sisusb->sisusb_dev->dev, "rwtest read[%d] = %x\n", j, destbuffer[j]); + } + } +} +#endif + +/* access pci config registers (reg numbers 0, 4, 8, etc) */ + +static int +sisusb_write_pci_config(struct sisusb_usb_data *sisusb, int regnum, u32 data) +{ + struct sisusb_packet packet; + int ret; + + packet.header = 0x008f; + packet.address = regnum | 0x10000; + packet.data = data; + ret = sisusb_send_packet(sisusb, 10, &packet); + return ret; +} + +static int +sisusb_read_pci_config(struct sisusb_usb_data *sisusb, int regnum, u32 *data) +{ + struct sisusb_packet packet; + int ret; + + packet.header = 0x008f; + packet.address = (u32)regnum | 0x10000; + ret = sisusb_send_packet(sisusb, 6, &packet); + *data = packet.data; + return ret; +} + +/* Clear video RAM */ + +static int +sisusb_clear_vram(struct sisusb_usb_data *sisusb, u32 address, int length) +{ + int ret, i; + ssize_t j; + + if (address < sisusb->vrambase) + return 1; + + if (address >= sisusb->vrambase + sisusb->vramsize) + return 1; + + if (address + length > sisusb->vrambase + sisusb->vramsize) + length = sisusb->vrambase + sisusb->vramsize - address; + + if (length <= 0) + return 0; + + /* allocate free buffer/urb and clear the buffer */ + if ((i = sisusb_alloc_outbuf(sisusb)) < 0) + return -EBUSY; + + memset(sisusb->obuf[i], 0, sisusb->obufsize); + + /* We can write a length > buffer size here. The buffer + * data will simply be re-used (like a ring-buffer). + */ + ret = sisusb_write_mem_bulk(sisusb, address, NULL, length, NULL, i, &j); + + /* Free the buffer/urb */ + sisusb_free_outbuf(sisusb, i); + + return ret; +} + +/* Initialize the graphics core (return 0 on success) + * This resets the graphics hardware and puts it into + * a defined mode (640x480@60Hz) + */ + +#define GETREG(r,d) sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, r, d) +#define SETREG(r,d) sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, r, d) +#define SETIREG(r,i,d) sisusb_setidxreg(sisusb, r, i, d) +#define GETIREG(r,i,d) sisusb_getidxreg(sisusb, r, i, d) +#define SETIREGOR(r,i,o) sisusb_setidxregor(sisusb, r, i, o) +#define SETIREGAND(r,i,a) sisusb_setidxregand(sisusb, r, i, a) +#define SETIREGANDOR(r,i,a,o) sisusb_setidxregandor(sisusb, r, i, a, o) +#define READL(a,d) sisusb_read_memio_long(sisusb, SISUSB_TYPE_MEM, a, d) +#define WRITEL(a,d) sisusb_write_memio_long(sisusb, SISUSB_TYPE_MEM, a, d) +#define READB(a,d) sisusb_read_memio_byte(sisusb, SISUSB_TYPE_MEM, a, d) +#define WRITEB(a,d) sisusb_write_memio_byte(sisusb, SISUSB_TYPE_MEM, a, d) + +static int +sisusb_triggersr16(struct sisusb_usb_data *sisusb, u8 ramtype) +{ + int ret; + u8 tmp8; + + ret = GETIREG(SISSR, 0x16, &tmp8); + if (ramtype <= 1) { + tmp8 &= 0x3f; + ret |= SETIREG(SISSR, 0x16, tmp8); + tmp8 |= 0x80; + ret |= SETIREG(SISSR, 0x16, tmp8); + } else { + tmp8 |= 0xc0; + ret |= SETIREG(SISSR, 0x16, tmp8); + tmp8 &= 0x0f; + ret |= SETIREG(SISSR, 0x16, tmp8); + tmp8 |= 0x80; + ret |= SETIREG(SISSR, 0x16, tmp8); + tmp8 &= 0x0f; + ret |= SETIREG(SISSR, 0x16, tmp8); + tmp8 |= 0xd0; + ret |= SETIREG(SISSR, 0x16, tmp8); + tmp8 &= 0x0f; + ret |= SETIREG(SISSR, 0x16, tmp8); + tmp8 |= 0xa0; + ret |= SETIREG(SISSR, 0x16, tmp8); + } + return ret; +} + +static int +sisusb_getbuswidth(struct sisusb_usb_data *sisusb, int *bw, int *chab) +{ + int ret; + u8 ramtype, done = 0; + u32 t0, t1, t2, t3; + u32 ramptr = SISUSB_PCI_MEMBASE; + + ret = GETIREG(SISSR, 0x3a, &ramtype); + ramtype &= 3; + + ret |= SETIREG(SISSR, 0x13, 0x00); + + if (ramtype <= 1) { + ret |= SETIREG(SISSR, 0x14, 0x12); + ret |= SETIREGAND(SISSR, 0x15, 0xef); + } else { + ret |= SETIREG(SISSR, 0x14, 0x02); + } + + ret |= sisusb_triggersr16(sisusb, ramtype); + ret |= WRITEL(ramptr + 0, 0x01234567); + ret |= WRITEL(ramptr + 4, 0x456789ab); + ret |= WRITEL(ramptr + 8, 0x89abcdef); + ret |= WRITEL(ramptr + 12, 0xcdef0123); + ret |= WRITEL(ramptr + 16, 0x55555555); + ret |= WRITEL(ramptr + 20, 0x55555555); + ret |= WRITEL(ramptr + 24, 0xffffffff); + ret |= WRITEL(ramptr + 28, 0xffffffff); + ret |= READL(ramptr + 0, &t0); + ret |= READL(ramptr + 4, &t1); + ret |= READL(ramptr + 8, &t2); + ret |= READL(ramptr + 12, &t3); + + if (ramtype <= 1) { + + *chab = 0; *bw = 64; + + if ((t3 != 0xcdef0123) || (t2 != 0x89abcdef)) { + if ((t1 == 0x456789ab) && (t0 == 0x01234567)) { + *chab = 0; *bw = 64; + ret |= SETIREGAND(SISSR, 0x14, 0xfd); + } + } + if ((t1 != 0x456789ab) || (t0 != 0x01234567)) { + *chab = 1; *bw = 64; + ret |= SETIREGANDOR(SISSR, 0x14, 0xfc,0x01); + + ret |= sisusb_triggersr16(sisusb, ramtype); + ret |= WRITEL(ramptr + 0, 0x89abcdef); + ret |= WRITEL(ramptr + 4, 0xcdef0123); + ret |= WRITEL(ramptr + 8, 0x55555555); + ret |= WRITEL(ramptr + 12, 0x55555555); + ret |= WRITEL(ramptr + 16, 0xaaaaaaaa); + ret |= WRITEL(ramptr + 20, 0xaaaaaaaa); + ret |= READL(ramptr + 4, &t1); + + if (t1 != 0xcdef0123) { + *bw = 32; + ret |= SETIREGOR(SISSR, 0x15, 0x10); + } + } + + } else { + + *chab = 0; *bw = 64; /* default: cha, bw = 64 */ + + done = 0; + + if (t1 == 0x456789ab) { + if (t0 == 0x01234567) { + *chab = 0; *bw = 64; + done = 1; + } + } else { + if (t0 == 0x01234567) { + *chab = 0; *bw = 32; + ret |= SETIREG(SISSR, 0x14, 0x00); + done = 1; + } + } + + if (!done) { + ret |= SETIREG(SISSR, 0x14, 0x03); + ret |= sisusb_triggersr16(sisusb, ramtype); + + ret |= WRITEL(ramptr + 0, 0x01234567); + ret |= WRITEL(ramptr + 4, 0x456789ab); + ret |= WRITEL(ramptr + 8, 0x89abcdef); + ret |= WRITEL(ramptr + 12, 0xcdef0123); + ret |= WRITEL(ramptr + 16, 0x55555555); + ret |= WRITEL(ramptr + 20, 0x55555555); + ret |= WRITEL(ramptr + 24, 0xffffffff); + ret |= WRITEL(ramptr + 28, 0xffffffff); + ret |= READL(ramptr + 0, &t0); + ret |= READL(ramptr + 4, &t1); + + if (t1 == 0x456789ab) { + if (t0 == 0x01234567) { + *chab = 1; *bw = 64; + return ret; + } /* else error */ + } else { + if (t0 == 0x01234567) { + *chab = 1; *bw = 32; + ret |= SETIREG(SISSR, 0x14, 0x01); + } /* else error */ + } + } + } + return ret; +} + +static int +sisusb_verify_mclk(struct sisusb_usb_data *sisusb) +{ + int ret = 0; + u32 ramptr = SISUSB_PCI_MEMBASE; + u8 tmp1, tmp2, i, j; + + ret |= WRITEB(ramptr, 0xaa); + ret |= WRITEB(ramptr + 16, 0x55); + ret |= READB(ramptr, &tmp1); + ret |= READB(ramptr + 16, &tmp2); + if ((tmp1 != 0xaa) || (tmp2 != 0x55)) { + for (i = 0, j = 16; i < 2; i++, j += 16) { + ret |= GETIREG(SISSR, 0x21, &tmp1); + ret |= SETIREGAND(SISSR, 0x21, (tmp1 & 0xfb)); + ret |= SETIREGOR(SISSR, 0x3c, 0x01); /* not on 330 */ + ret |= SETIREGAND(SISSR, 0x3c, 0xfe); /* not on 330 */ + ret |= SETIREG(SISSR, 0x21, tmp1); + ret |= WRITEB(ramptr + 16 + j, j); + ret |= READB(ramptr + 16 + j, &tmp1); + if (tmp1 == j) { + ret |= WRITEB(ramptr + j, j); + break; + } + } + } + return ret; +} + +static int +sisusb_set_rank(struct sisusb_usb_data *sisusb, int *iret, int index, + u8 rankno, u8 chab, const u8 dramtype[][5], + int bw) +{ + int ret = 0, ranksize; + u8 tmp; + + *iret = 0; + + if ((rankno == 2) && (dramtype[index][0] == 2)) + return ret; + + ranksize = dramtype[index][3] / 2 * bw / 32; + + if ((ranksize * rankno) > 128) + return ret; + + tmp = 0; + while ((ranksize >>= 1) > 0) tmp += 0x10; + tmp |= ((rankno - 1) << 2); + tmp |= ((bw / 64) & 0x02); + tmp |= (chab & 0x01); + + ret = SETIREG(SISSR, 0x14, tmp); + ret |= sisusb_triggersr16(sisusb, 0); /* sic! */ + + *iret = 1; + + return ret; +} + +static int +sisusb_check_rbc(struct sisusb_usb_data *sisusb, int *iret, u32 inc, int testn) +{ + int ret = 0, i; + u32 j, tmp; + + *iret = 0; + + for (i = 0, j = 0; i < testn; i++) { + ret |= WRITEL(sisusb->vrambase + j, j); + j += inc; + } + + for (i = 0, j = 0; i < testn; i++) { + ret |= READL(sisusb->vrambase + j, &tmp); + if (tmp != j) return ret; + j += inc; + } + + *iret = 1; + return ret; +} + +static int +sisusb_check_ranks(struct sisusb_usb_data *sisusb, int *iret, int rankno, + int idx, int bw, const u8 rtype[][5]) +{ + int ret = 0, i, i2ret; + u32 inc; + + *iret = 0; + + for (i = rankno; i >= 1; i--) { + inc = 1 << (rtype[idx][2] + + rtype[idx][1] + + rtype[idx][0] + + bw / 64 + i); + ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 2); + if (!i2ret) + return ret; + } + + inc = 1 << (rtype[idx][2] + bw / 64 + 2); + ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 4); + if (!i2ret) + return ret; + + inc = 1 << (10 + bw / 64); + ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 2); + if (!i2ret) + return ret; + + *iret = 1; + return ret; +} + +static int +sisusb_get_sdram_size(struct sisusb_usb_data *sisusb, int *iret, int bw, + int chab) +{ + int ret = 0, i2ret = 0, i, j; + static const u8 sdramtype[13][5] = { + { 2, 12, 9, 64, 0x35 }, + { 1, 13, 9, 64, 0x44 }, + { 2, 12, 8, 32, 0x31 }, + { 2, 11, 9, 32, 0x25 }, + { 1, 12, 9, 32, 0x34 }, + { 1, 13, 8, 32, 0x40 }, + { 2, 11, 8, 16, 0x21 }, + { 1, 12, 8, 16, 0x30 }, + { 1, 11, 9, 16, 0x24 }, + { 1, 11, 8, 8, 0x20 }, + { 2, 9, 8, 4, 0x01 }, + { 1, 10, 8, 4, 0x10 }, + { 1, 9, 8, 2, 0x00 } + }; + + *iret = 1; /* error */ + + for (i = 0; i < 13; i++) { + ret |= SETIREGANDOR(SISSR, 0x13, 0x80, sdramtype[i][4]); + for (j = 2; j > 0; j--) { + ret |= sisusb_set_rank(sisusb, &i2ret, i, j, + chab, sdramtype, bw); + if (!i2ret) + continue; + + ret |= sisusb_check_ranks(sisusb, &i2ret, j, i, + bw, sdramtype); + if (i2ret) { + *iret = 0; /* ram size found */ + return ret; + } + } + } + + return ret; +} + +static int +sisusb_setup_screen(struct sisusb_usb_data *sisusb, int clrall, int drwfr) +{ + int ret = 0; + u32 address; + int i, length, modex, modey, bpp; + + modex = 640; modey = 480; bpp = 2; + + address = sisusb->vrambase; /* Clear video ram */ + + if (clrall) + length = sisusb->vramsize; + else + length = modex * bpp * modey; + + ret = sisusb_clear_vram(sisusb, address, length); + + if (!ret && drwfr) { + for (i = 0; i < modex; i++) { + address = sisusb->vrambase + (i * bpp); + ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, + address, 0xf100); + address += (modex * (modey-1) * bpp); + ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, + address, 0xf100); + } + for (i = 0; i < modey; i++) { + address = sisusb->vrambase + ((i * modex) * bpp); + ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, + address, 0xf100); + address += ((modex - 1) * bpp); + ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM, + address, 0xf100); + } + } + + return ret; +} + +static int +sisusb_set_default_mode(struct sisusb_usb_data *sisusb, int touchengines) +{ + int ret = 0, i, j, modex, modey, bpp, du; + u8 sr31, cr63, tmp8; + static const char attrdata[] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x01,0x00,0x00,0x00 + }; + static const char crtcrdata[] = { + 0x5f,0x4f,0x50,0x82,0x54,0x80,0x0b,0x3e, + 0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00, + 0xea,0x8c,0xdf,0x28,0x40,0xe7,0x04,0xa3, + 0xff + }; + static const char grcdata[] = { + 0x00,0x00,0x00,0x00,0x00,0x40,0x05,0x0f, + 0xff + }; + static const char crtcdata[] = { + 0x5f,0x4f,0x4f,0x83,0x55,0x81,0x0b,0x3e, + 0xe9,0x8b,0xdf,0xe8,0x0c,0x00,0x00,0x05, + 0x00 + }; + + modex = 640; modey = 480; bpp = 2; + + GETIREG(SISSR, 0x31, &sr31); + GETIREG(SISCR, 0x63, &cr63); + SETIREGOR(SISSR, 0x01, 0x20); + SETIREG(SISCR, 0x63, cr63 & 0xbf); + SETIREGOR(SISCR, 0x17, 0x80); + SETIREGOR(SISSR, 0x1f, 0x04); + SETIREGAND(SISSR, 0x07, 0xfb); + SETIREG(SISSR, 0x00, 0x03); /* seq */ + SETIREG(SISSR, 0x01, 0x21); + SETIREG(SISSR, 0x02, 0x0f); + SETIREG(SISSR, 0x03, 0x00); + SETIREG(SISSR, 0x04, 0x0e); + SETREG(SISMISCW, 0x23); /* misc */ + for (i = 0; i <= 0x18; i++) { /* crtc */ + SETIREG(SISCR, i, crtcrdata[i]); + } + for (i = 0; i <= 0x13; i++) { /* att */ + GETREG(SISINPSTAT, &tmp8); + SETREG(SISAR, i); + SETREG(SISAR, attrdata[i]); + } + GETREG(SISINPSTAT, &tmp8); + SETREG(SISAR, 0x14); + SETREG(SISAR, 0x00); + GETREG(SISINPSTAT, &tmp8); + SETREG(SISAR, 0x20); + GETREG(SISINPSTAT, &tmp8); + for (i = 0; i <= 0x08; i++) { /* grc */ + SETIREG(SISGR, i, grcdata[i]); + } + SETIREGAND(SISGR, 0x05, 0xbf); + for (i = 0x0A; i <= 0x0E; i++) { /* clr ext */ + SETIREG(SISSR, i, 0x00); + } + SETIREGAND(SISSR, 0x37, 0xfe); + SETREG(SISMISCW, 0xef); /* sync */ + SETIREG(SISCR, 0x11, 0x00); /* crtc */ + for (j = 0x00, i = 0; i <= 7; i++, j++) { + SETIREG(SISCR, j, crtcdata[i]); + } + for (j = 0x10; i <= 10; i++, j++) { + SETIREG(SISCR, j, crtcdata[i]); + } + for (j = 0x15; i <= 12; i++, j++) { + SETIREG(SISCR, j, crtcdata[i]); + } + for (j = 0x0A; i <= 15; i++, j++) { + SETIREG(SISSR, j, crtcdata[i]); + } + SETIREG(SISSR, 0x0E, (crtcdata[16] & 0xE0)); + SETIREGANDOR(SISCR, 0x09, 0x5f, ((crtcdata[16] & 0x01) << 5)); + SETIREG(SISCR, 0x14, 0x4f); + du = (modex / 16) * (bpp * 2); /* offset/pitch */ + if (modex % 16) du += bpp; + SETIREGANDOR(SISSR, 0x0e, 0xf0, ((du >> 8) & 0x0f)); + SETIREG(SISCR, 0x13, (du & 0xff)); + du <<= 5; + tmp8 = du >> 8; + if (du & 0xff) tmp8++; + SETIREG(SISSR, 0x10, tmp8); + SETIREG(SISSR, 0x31, 0x00); /* VCLK */ + SETIREG(SISSR, 0x2b, 0x1b); + SETIREG(SISSR, 0x2c, 0xe1); + SETIREG(SISSR, 0x2d, 0x01); + SETIREGAND(SISSR, 0x3d, 0xfe); /* FIFO */ + SETIREG(SISSR, 0x08, 0xae); + SETIREGAND(SISSR, 0x09, 0xf0); + SETIREG(SISSR, 0x08, 0x34); + SETIREGOR(SISSR, 0x3d, 0x01); + SETIREGAND(SISSR, 0x1f, 0x3f); /* mode regs */ + SETIREGANDOR(SISSR, 0x06, 0xc0, 0x0a); + SETIREG(SISCR, 0x19, 0x00); + SETIREGAND(SISCR, 0x1a, 0xfc); + SETIREGAND(SISSR, 0x0f, 0xb7); + SETIREGAND(SISSR, 0x31, 0xfb); + SETIREGANDOR(SISSR, 0x21, 0x1f, 0xa0); + SETIREGAND(SISSR, 0x32, 0xf3); + SETIREGANDOR(SISSR, 0x07, 0xf8, 0x03); + SETIREG(SISCR, 0x52, 0x6c); + + SETIREG(SISCR, 0x0d, 0x00); /* adjust frame */ + SETIREG(SISCR, 0x0c, 0x00); + SETIREG(SISSR, 0x0d, 0x00); + SETIREGAND(SISSR, 0x37, 0xfe); + + SETIREG(SISCR, 0x32, 0x20); + SETIREGAND(SISSR, 0x01, 0xdf); /* enable display */ + SETIREG(SISCR, 0x63, (cr63 & 0xbf)); + SETIREG(SISSR, 0x31, (sr31 & 0xfb)); + + if (touchengines) { + SETIREG(SISSR, 0x20, 0xa1); /* enable engines */ + SETIREGOR(SISSR, 0x1e, 0x5a); + + SETIREG(SISSR, 0x26, 0x01); /* disable cmdqueue */ + SETIREG(SISSR, 0x27, 0x1f); + SETIREG(SISSR, 0x26, 0x00); + } + + SETIREG(SISCR, 0x34, 0x44); /* we just set std mode #44 */ + + return ret; +} + +static int +sisusb_init_gfxcore(struct sisusb_usb_data *sisusb) +{ + int ret = 0, i, j, bw, chab, iret, retry = 3; + u8 tmp8, ramtype; + u32 tmp32; + static const char mclktable[] = { + 0x3b, 0x22, 0x01, 143, + 0x3b, 0x22, 0x01, 143, + 0x3b, 0x22, 0x01, 143, + 0x3b, 0x22, 0x01, 143 + }; + static const char eclktable[] = { + 0x3b, 0x22, 0x01, 143, + 0x3b, 0x22, 0x01, 143, + 0x3b, 0x22, 0x01, 143, + 0x3b, 0x22, 0x01, 143 + }; + static const char ramtypetable1[] = { + 0x00, 0x04, 0x60, 0x60, + 0x0f, 0x0f, 0x1f, 0x1f, + 0xba, 0xba, 0xba, 0xba, + 0xa9, 0xa9, 0xac, 0xac, + 0xa0, 0xa0, 0xa0, 0xa8, + 0x00, 0x00, 0x02, 0x02, + 0x30, 0x30, 0x40, 0x40 + }; + static const char ramtypetable2[] = { + 0x77, 0x77, 0x44, 0x44, + 0x77, 0x77, 0x44, 0x44, + 0x00, 0x00, 0x00, 0x00, + 0x5b, 0x5b, 0xab, 0xab, + 0x00, 0x00, 0xf0, 0xf8 + }; + + while (retry--) { + + /* Enable VGA */ + ret = GETREG(SISVGAEN, &tmp8); + ret |= SETREG(SISVGAEN, (tmp8 | 0x01)); + + /* Enable GPU access to VRAM */ + ret |= GETREG(SISMISCR, &tmp8); + ret |= SETREG(SISMISCW, (tmp8 | 0x01)); + + if (ret) continue; + + /* Reset registers */ + ret |= SETIREGAND(SISCR, 0x5b, 0xdf); + ret |= SETIREG(SISSR, 0x05, 0x86); + ret |= SETIREGOR(SISSR, 0x20, 0x01); + + ret |= SETREG(SISMISCW, 0x67); + + for (i = 0x06; i <= 0x1f; i++) { + ret |= SETIREG(SISSR, i, 0x00); + } + for (i = 0x21; i <= 0x27; i++) { + ret |= SETIREG(SISSR, i, 0x00); + } + for (i = 0x31; i <= 0x3d; i++) { + ret |= SETIREG(SISSR, i, 0x00); + } + for (i = 0x12; i <= 0x1b; i++) { + ret |= SETIREG(SISSR, i, 0x00); + } + for (i = 0x79; i <= 0x7c; i++) { + ret |= SETIREG(SISCR, i, 0x00); + } + + if (ret) continue; + + ret |= SETIREG(SISCR, 0x63, 0x80); + + ret |= GETIREG(SISSR, 0x3a, &ramtype); + ramtype &= 0x03; + + ret |= SETIREG(SISSR, 0x28, mclktable[ramtype * 4]); + ret |= SETIREG(SISSR, 0x29, mclktable[(ramtype * 4) + 1]); + ret |= SETIREG(SISSR, 0x2a, mclktable[(ramtype * 4) + 2]); + + ret |= SETIREG(SISSR, 0x2e, eclktable[ramtype * 4]); + ret |= SETIREG(SISSR, 0x2f, eclktable[(ramtype * 4) + 1]); + ret |= SETIREG(SISSR, 0x30, eclktable[(ramtype * 4) + 2]); + + ret |= SETIREG(SISSR, 0x07, 0x18); + ret |= SETIREG(SISSR, 0x11, 0x0f); + + if (ret) continue; + + for (i = 0x15, j = 0; i <= 0x1b; i++, j++) { + ret |= SETIREG(SISSR, i, ramtypetable1[(j*4) + ramtype]); + } + for (i = 0x40, j = 0; i <= 0x44; i++, j++) { + ret |= SETIREG(SISCR, i, ramtypetable2[(j*4) + ramtype]); + } + + ret |= SETIREG(SISCR, 0x49, 0xaa); + + ret |= SETIREG(SISSR, 0x1f, 0x00); + ret |= SETIREG(SISSR, 0x20, 0xa0); + ret |= SETIREG(SISSR, 0x23, 0xf6); + ret |= SETIREG(SISSR, 0x24, 0x0d); + ret |= SETIREG(SISSR, 0x25, 0x33); + + ret |= SETIREG(SISSR, 0x11, 0x0f); + + ret |= SETIREGOR(SISPART1, 0x2f, 0x01); + + ret |= SETIREGAND(SISCAP, 0x3f, 0xef); + + if (ret) continue; + + ret |= SETIREG(SISPART1, 0x00, 0x00); + + ret |= GETIREG(SISSR, 0x13, &tmp8); + tmp8 >>= 4; + + ret |= SETIREG(SISPART1, 0x02, 0x00); + ret |= SETIREG(SISPART1, 0x2e, 0x08); + + ret |= sisusb_read_pci_config(sisusb, 0x50, &tmp32); + tmp32 &= 0x00f00000; + tmp8 = (tmp32 == 0x100000) ? 0x33 : 0x03; + ret |= SETIREG(SISSR, 0x25, tmp8); + tmp8 = (tmp32 == 0x100000) ? 0xaa : 0x88; + ret |= SETIREG(SISCR, 0x49, tmp8); + + ret |= SETIREG(SISSR, 0x27, 0x1f); + ret |= SETIREG(SISSR, 0x31, 0x00); + ret |= SETIREG(SISSR, 0x32, 0x11); + ret |= SETIREG(SISSR, 0x33, 0x00); + + if (ret) continue; + + ret |= SETIREG(SISCR, 0x83, 0x00); + + ret |= sisusb_set_default_mode(sisusb, 0); + + ret |= SETIREGAND(SISSR, 0x21, 0xdf); + ret |= SETIREGOR(SISSR, 0x01, 0x20); + ret |= SETIREGOR(SISSR, 0x16, 0x0f); + + ret |= sisusb_triggersr16(sisusb, ramtype); + + /* Disable refresh */ + ret |= SETIREGAND(SISSR, 0x17, 0xf8); + ret |= SETIREGOR(SISSR, 0x19, 0x03); + + ret |= sisusb_getbuswidth(sisusb, &bw, &chab); + ret |= sisusb_verify_mclk(sisusb); + + if (ramtype <= 1) { + ret |= sisusb_get_sdram_size(sisusb, &iret, bw, chab); + if (iret) { + dev_err(&sisusb->sisusb_dev->dev,"RAM size detection failed, assuming 8MB video RAM\n"); + ret |= SETIREG(SISSR,0x14,0x31); + /* TODO */ + } + } else { + dev_err(&sisusb->sisusb_dev->dev, "DDR RAM device found, assuming 8MB video RAM\n"); + ret |= SETIREG(SISSR,0x14,0x31); + /* *** TODO *** */ + } + + /* Enable refresh */ + ret |= SETIREG(SISSR, 0x16, ramtypetable1[4 + ramtype]); + ret |= SETIREG(SISSR, 0x17, ramtypetable1[8 + ramtype]); + ret |= SETIREG(SISSR, 0x19, ramtypetable1[16 + ramtype]); + + ret |= SETIREGOR(SISSR, 0x21, 0x20); + + ret |= SETIREG(SISSR, 0x22, 0xfb); + ret |= SETIREG(SISSR, 0x21, 0xa5); + + if (ret == 0) + break; + } + + return ret; +} + +#undef SETREG +#undef GETREG +#undef SETIREG +#undef GETIREG +#undef SETIREGOR +#undef SETIREGAND +#undef SETIREGANDOR +#undef READL +#undef WRITEL + +static void +sisusb_get_ramconfig(struct sisusb_usb_data *sisusb) +{ + u8 tmp8, tmp82, ramtype; + int bw = 0; + char *ramtypetext1 = NULL; + const char *ramtypetext2[] = { "SDR SDRAM", "SDR SGRAM", + "DDR SDRAM", "DDR SGRAM" }; + static const int busSDR[4] = {64, 64, 128, 128}; + static const int busDDR[4] = {32, 32, 64, 64}; + static const int busDDRA[4] = {64+32, 64+32 , (64+32)*2, (64+32)*2}; + + sisusb_getidxreg(sisusb, SISSR, 0x14, &tmp8); + sisusb_getidxreg(sisusb, SISSR, 0x15, &tmp82); + sisusb_getidxreg(sisusb, SISSR, 0x3a, &ramtype); + sisusb->vramsize = (1 << ((tmp8 & 0xf0) >> 4)) * 1024 * 1024; + ramtype &= 0x03; + switch ((tmp8 >> 2) & 0x03) { + case 0: ramtypetext1 = "1 ch/1 r"; + if (tmp82 & 0x10) { + bw = 32; + } else { + bw = busSDR[(tmp8 & 0x03)]; + } + break; + case 1: ramtypetext1 = "1 ch/2 r"; + sisusb->vramsize <<= 1; + bw = busSDR[(tmp8 & 0x03)]; + break; + case 2: ramtypetext1 = "asymmeric"; + sisusb->vramsize += sisusb->vramsize/2; + bw = busDDRA[(tmp8 & 0x03)]; + break; + case 3: ramtypetext1 = "2 channel"; + sisusb->vramsize <<= 1; + bw = busDDR[(tmp8 & 0x03)]; + break; + } + + dev_info(&sisusb->sisusb_dev->dev, "%dMB %s %s, bus width %d\n", (sisusb->vramsize >> 20), ramtypetext1, + ramtypetext2[ramtype], bw); +} + +static int +sisusb_do_init_gfxdevice(struct sisusb_usb_data *sisusb) +{ + struct sisusb_packet packet; + int ret; + u32 tmp32; + + /* Do some magic */ + packet.header = 0x001f; + packet.address = 0x00000324; + packet.data = 0x00000004; + ret = sisusb_send_bridge_packet(sisusb, 10, &packet, 0); + + packet.header = 0x001f; + packet.address = 0x00000364; + packet.data = 0x00000004; + ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); + + packet.header = 0x001f; + packet.address = 0x00000384; + packet.data = 0x00000004; + ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); + + packet.header = 0x001f; + packet.address = 0x00000100; + packet.data = 0x00000700; + ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); + + packet.header = 0x000f; + packet.address = 0x00000004; + ret |= sisusb_send_bridge_packet(sisusb, 6, &packet, 0); + packet.data |= 0x17; + ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); + + /* Init BAR 0 (VRAM) */ + ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32); + ret |= sisusb_write_pci_config(sisusb, 0x10, 0xfffffff0); + ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32); + tmp32 &= 0x0f; + tmp32 |= SISUSB_PCI_MEMBASE; + ret |= sisusb_write_pci_config(sisusb, 0x10, tmp32); + + /* Init BAR 1 (MMIO) */ + ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32); + ret |= sisusb_write_pci_config(sisusb, 0x14, 0xfffffff0); + ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32); + tmp32 &= 0x0f; + tmp32 |= SISUSB_PCI_MMIOBASE; + ret |= sisusb_write_pci_config(sisusb, 0x14, tmp32); + + /* Init BAR 2 (i/o ports) */ + ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32); + ret |= sisusb_write_pci_config(sisusb, 0x18, 0xfffffff0); + ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32); + tmp32 &= 0x0f; + tmp32 |= SISUSB_PCI_IOPORTBASE; + ret |= sisusb_write_pci_config(sisusb, 0x18, tmp32); + + /* Enable memory and i/o access */ + ret |= sisusb_read_pci_config(sisusb, 0x04, &tmp32); + tmp32 |= 0x3; + ret |= sisusb_write_pci_config(sisusb, 0x04, tmp32); + + if (ret == 0) { + /* Some further magic */ + packet.header = 0x001f; + packet.address = 0x00000050; + packet.data = 0x000000ff; + ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0); + } + + return ret; +} + +/* Initialize the graphics device (return 0 on success) + * This initializes the net2280 as well as the PCI registers + * of the graphics board. + */ + +static int +sisusb_init_gfxdevice(struct sisusb_usb_data *sisusb, int initscreen) +{ + int ret = 0, test = 0; + u32 tmp32; + + if (sisusb->devinit == 1) { + /* Read PCI BARs and see if they have been set up */ + ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32); + if (ret) return ret; + if ((tmp32 & 0xfffffff0) == SISUSB_PCI_MEMBASE) test++; + + ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32); + if (ret) return ret; + if ((tmp32 & 0xfffffff0) == SISUSB_PCI_MMIOBASE) test++; + + ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32); + if (ret) return ret; + if ((tmp32 & 0xfffffff0) == SISUSB_PCI_IOPORTBASE) test++; + } + + /* No? So reset the device */ + if ((sisusb->devinit == 0) || (test != 3)) { + + ret |= sisusb_do_init_gfxdevice(sisusb); + + if (ret == 0) + sisusb->devinit = 1; + + } + + if (sisusb->devinit) { + /* Initialize the graphics core */ + if (sisusb_init_gfxcore(sisusb) == 0) { + sisusb->gfxinit = 1; + sisusb_get_ramconfig(sisusb); + ret |= sisusb_set_default_mode(sisusb, 1); + ret |= sisusb_setup_screen(sisusb, 1, initscreen); + } + } + + return ret; +} + + +#ifdef INCL_SISUSB_CON + +/* Set up default text mode: + - Set text mode (0x03) + - Upload default font + - Upload user font (if available) +*/ + +int +sisusb_reset_text_mode(struct sisusb_usb_data *sisusb, int init) +{ + int ret = 0, slot = sisusb->font_slot, i; + const struct font_desc *myfont; + u8 *tempbuf; + u16 *tempbufb; + size_t written; + static const char bootstring[] = "SiSUSB VGA text console, (C) 2005 Thomas Winischhofer."; + static const char bootlogo[] = "(o_ //\\ V_/_"; + + /* sisusb->lock is down */ + + if (!sisusb->SiS_Pr) + return 1; + + sisusb->SiS_Pr->IOAddress = SISUSB_PCI_IOPORTBASE + 0x30; + sisusb->SiS_Pr->sisusb = (void *)sisusb; + + /* Set mode 0x03 */ + SiSUSBSetMode(sisusb->SiS_Pr, 0x03); + + if (!(myfont = find_font("VGA8x16"))) + return 1; + + if (!(tempbuf = vmalloc(8192))) + return 1; + + for (i = 0; i < 256; i++) + memcpy(tempbuf + (i * 32), myfont->data + (i * 16), 16); + + /* Upload default font */ + ret = sisusbcon_do_font_op(sisusb, 1, 0, tempbuf, 8192, 0, 1, NULL, 16, 0); + + vfree(tempbuf); + + /* Upload user font (and reset current slot) */ + if (sisusb->font_backup) { + ret |= sisusbcon_do_font_op(sisusb, 1, 2, sisusb->font_backup, + 8192, sisusb->font_backup_512, 1, NULL, + sisusb->font_backup_height, 0); + if (slot != 2) + sisusbcon_do_font_op(sisusb, 1, 0, NULL, 0, 0, 1, + NULL, 16, 0); + } + + if (init && !sisusb->scrbuf) { + + if ((tempbuf = vmalloc(8192))) { + + i = 4096; + tempbufb = (u16 *)tempbuf; + while (i--) + *(tempbufb++) = 0x0720; + + i = 0; + tempbufb = (u16 *)tempbuf; + while (bootlogo[i]) { + *(tempbufb++) = 0x0700 | bootlogo[i++]; + if (!(i % 4)) + tempbufb += 76; + } + + i = 0; + tempbufb = (u16 *)tempbuf + 6; + while (bootstring[i]) + *(tempbufb++) = 0x0700 | bootstring[i++]; + + ret |= sisusb_copy_memory(sisusb, tempbuf, + sisusb->vrambase, 8192, &written); + + vfree(tempbuf); + + } + + } else if (sisusb->scrbuf) { + + ret |= sisusb_copy_memory(sisusb, (char *)sisusb->scrbuf, + sisusb->vrambase, sisusb->scrbuf_size, &written); + + } + + if (sisusb->sisusb_cursor_size_from >= 0 && + sisusb->sisusb_cursor_size_to >= 0) { + sisusb_setidxreg(sisusb, SISCR, 0x0a, + sisusb->sisusb_cursor_size_from); + sisusb_setidxregandor(sisusb, SISCR, 0x0b, 0xe0, + sisusb->sisusb_cursor_size_to); + } else { + sisusb_setidxreg(sisusb, SISCR, 0x0a, 0x2d); + sisusb_setidxreg(sisusb, SISCR, 0x0b, 0x0e); + sisusb->sisusb_cursor_size_to = -1; + } + + slot = sisusb->sisusb_cursor_loc; + if(slot < 0) slot = 0; + + sisusb->sisusb_cursor_loc = -1; + sisusb->bad_cursor_pos = 1; + + sisusb_set_cursor(sisusb, slot); + + sisusb_setidxreg(sisusb, SISCR, 0x0c, (sisusb->cur_start_addr >> 8)); + sisusb_setidxreg(sisusb, SISCR, 0x0d, (sisusb->cur_start_addr & 0xff)); + + sisusb->textmodedestroyed = 0; + + /* sisusb->lock is down */ + + return ret; +} + +#endif + +/* fops */ + +static int +sisusb_open(struct inode *inode, struct file *file) +{ + struct sisusb_usb_data *sisusb; + struct usb_interface *interface; + int subminor = iminor(inode); + + if (!(interface = usb_find_interface(&sisusb_driver, subminor))) { + return -ENODEV; + } + + if (!(sisusb = usb_get_intfdata(interface))) { + return -ENODEV; + } + + mutex_lock(&sisusb->lock); + + if (!sisusb->present || !sisusb->ready) { + mutex_unlock(&sisusb->lock); + return -ENODEV; + } + + if (sisusb->isopen) { + mutex_unlock(&sisusb->lock); + return -EBUSY; + } + + if (!sisusb->devinit) { + if (sisusb->sisusb_dev->speed == USB_SPEED_HIGH || + sisusb->sisusb_dev->speed == USB_SPEED_SUPER) { + if (sisusb_init_gfxdevice(sisusb, 0)) { + mutex_unlock(&sisusb->lock); + dev_err(&sisusb->sisusb_dev->dev, "Failed to initialize device\n"); + return -EIO; + } + } else { + mutex_unlock(&sisusb->lock); + dev_err(&sisusb->sisusb_dev->dev, "Device not attached to USB 2.0 hub\n"); + return -EIO; + } + } + + /* Increment usage count for our sisusb */ + kref_get(&sisusb->kref); + + sisusb->isopen = 1; + + file->private_data = sisusb; + + mutex_unlock(&sisusb->lock); + + return 0; +} + +void +sisusb_delete(struct kref *kref) +{ + struct sisusb_usb_data *sisusb = to_sisusb_dev(kref); + + if (!sisusb) + return; + + if (sisusb->sisusb_dev) + usb_put_dev(sisusb->sisusb_dev); + + sisusb->sisusb_dev = NULL; + sisusb_free_buffers(sisusb); + sisusb_free_urbs(sisusb); +#ifdef INCL_SISUSB_CON + kfree(sisusb->SiS_Pr); +#endif + kfree(sisusb); +} + +static int +sisusb_release(struct inode *inode, struct file *file) +{ + struct sisusb_usb_data *sisusb; + + if (!(sisusb = file->private_data)) + return -ENODEV; + + mutex_lock(&sisusb->lock); + + if (sisusb->present) { + /* Wait for all URBs to finish if device still present */ + if (!sisusb_wait_all_out_complete(sisusb)) + sisusb_kill_all_busy(sisusb); + } + + sisusb->isopen = 0; + file->private_data = NULL; + + mutex_unlock(&sisusb->lock); + + /* decrement the usage count on our device */ + kref_put(&sisusb->kref, sisusb_delete); + + return 0; +} + +static ssize_t +sisusb_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct sisusb_usb_data *sisusb; + ssize_t bytes_read = 0; + int errno = 0; + u8 buf8; + u16 buf16; + u32 buf32, address; + + if (!(sisusb = file->private_data)) + return -ENODEV; + + mutex_lock(&sisusb->lock); + + /* Sanity check */ + if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { + mutex_unlock(&sisusb->lock); + return -ENODEV; + } + + if ((*ppos) >= SISUSB_PCI_PSEUDO_IOPORTBASE && + (*ppos) < SISUSB_PCI_PSEUDO_IOPORTBASE + 128) { + + address = (*ppos) - + SISUSB_PCI_PSEUDO_IOPORTBASE + + SISUSB_PCI_IOPORTBASE; + + /* Read i/o ports + * Byte, word and long(32) can be read. As this + * emulates inX instructions, the data returned is + * in machine-endianness. + */ + switch (count) { + + case 1: + if (sisusb_read_memio_byte(sisusb, + SISUSB_TYPE_IO, + address, &buf8)) + errno = -EIO; + else if (put_user(buf8, (u8 __user *)buffer)) + errno = -EFAULT; + else + bytes_read = 1; + + break; + + case 2: + if (sisusb_read_memio_word(sisusb, + SISUSB_TYPE_IO, + address, &buf16)) + errno = -EIO; + else if (put_user(buf16, (u16 __user *)buffer)) + errno = -EFAULT; + else + bytes_read = 2; + + break; + + case 4: + if (sisusb_read_memio_long(sisusb, + SISUSB_TYPE_IO, + address, &buf32)) + errno = -EIO; + else if (put_user(buf32, (u32 __user *)buffer)) + errno = -EFAULT; + else + bytes_read = 4; + + break; + + default: + errno = -EIO; + + } + + } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MEMBASE && + (*ppos) < SISUSB_PCI_PSEUDO_MEMBASE + sisusb->vramsize) { + + address = (*ppos) - + SISUSB_PCI_PSEUDO_MEMBASE + + SISUSB_PCI_MEMBASE; + + /* Read video ram + * Remember: Data delivered is never endian-corrected + */ + errno = sisusb_read_mem_bulk(sisusb, address, + NULL, count, buffer, &bytes_read); + + if (bytes_read) + errno = bytes_read; + + } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MMIOBASE && + (*ppos) < SISUSB_PCI_PSEUDO_MMIOBASE + SISUSB_PCI_MMIOSIZE) { + + address = (*ppos) - + SISUSB_PCI_PSEUDO_MMIOBASE + + SISUSB_PCI_MMIOBASE; + + /* Read MMIO + * Remember: Data delivered is never endian-corrected + */ + errno = sisusb_read_mem_bulk(sisusb, address, + NULL, count, buffer, &bytes_read); + + if (bytes_read) + errno = bytes_read; + + } else if ((*ppos) >= SISUSB_PCI_PSEUDO_PCIBASE && + (*ppos) <= SISUSB_PCI_PSEUDO_PCIBASE + 0x5c) { + + if (count != 4) { + mutex_unlock(&sisusb->lock); + return -EINVAL; + } + + address = (*ppos) - SISUSB_PCI_PSEUDO_PCIBASE; + + /* Read PCI config register + * Return value delivered in machine endianness. + */ + if (sisusb_read_pci_config(sisusb, address, &buf32)) + errno = -EIO; + else if (put_user(buf32, (u32 __user *)buffer)) + errno = -EFAULT; + else + bytes_read = 4; + + } else { + + errno = -EBADFD; + + } + + (*ppos) += bytes_read; + + mutex_unlock(&sisusb->lock); + + return errno ? errno : bytes_read; +} + +static ssize_t +sisusb_write(struct file *file, const char __user *buffer, size_t count, + loff_t *ppos) +{ + struct sisusb_usb_data *sisusb; + int errno = 0; + ssize_t bytes_written = 0; + u8 buf8; + u16 buf16; + u32 buf32, address; + + if (!(sisusb = file->private_data)) + return -ENODEV; + + mutex_lock(&sisusb->lock); + + /* Sanity check */ + if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { + mutex_unlock(&sisusb->lock); + return -ENODEV; + } + + if ((*ppos) >= SISUSB_PCI_PSEUDO_IOPORTBASE && + (*ppos) < SISUSB_PCI_PSEUDO_IOPORTBASE + 128) { + + address = (*ppos) - + SISUSB_PCI_PSEUDO_IOPORTBASE + + SISUSB_PCI_IOPORTBASE; + + /* Write i/o ports + * Byte, word and long(32) can be written. As this + * emulates outX instructions, the data is expected + * in machine-endianness. + */ + switch (count) { + + case 1: + if (get_user(buf8, (u8 __user *)buffer)) + errno = -EFAULT; + else if (sisusb_write_memio_byte(sisusb, + SISUSB_TYPE_IO, + address, buf8)) + errno = -EIO; + else + bytes_written = 1; + + break; + + case 2: + if (get_user(buf16, (u16 __user *)buffer)) + errno = -EFAULT; + else if (sisusb_write_memio_word(sisusb, + SISUSB_TYPE_IO, + address, buf16)) + errno = -EIO; + else + bytes_written = 2; + + break; + + case 4: + if (get_user(buf32, (u32 __user *)buffer)) + errno = -EFAULT; + else if (sisusb_write_memio_long(sisusb, + SISUSB_TYPE_IO, + address, buf32)) + errno = -EIO; + else + bytes_written = 4; + + break; + + default: + errno = -EIO; + } + + } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MEMBASE && + (*ppos) < SISUSB_PCI_PSEUDO_MEMBASE + sisusb->vramsize) { + + address = (*ppos) - + SISUSB_PCI_PSEUDO_MEMBASE + + SISUSB_PCI_MEMBASE; + + /* Write video ram. + * Buffer is copied 1:1, therefore, on big-endian + * machines, the data must be swapped by userland + * in advance (if applicable; no swapping in 8bpp + * mode or if YUV data is being transferred). + */ + errno = sisusb_write_mem_bulk(sisusb, address, NULL, + count, buffer, 0, &bytes_written); + + if (bytes_written) + errno = bytes_written; + + } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MMIOBASE && + (*ppos) < SISUSB_PCI_PSEUDO_MMIOBASE + SISUSB_PCI_MMIOSIZE) { + + address = (*ppos) - + SISUSB_PCI_PSEUDO_MMIOBASE + + SISUSB_PCI_MMIOBASE; + + /* Write MMIO. + * Buffer is copied 1:1, therefore, on big-endian + * machines, the data must be swapped by userland + * in advance. + */ + errno = sisusb_write_mem_bulk(sisusb, address, NULL, + count, buffer, 0, &bytes_written); + + if (bytes_written) + errno = bytes_written; + + } else if ((*ppos) >= SISUSB_PCI_PSEUDO_PCIBASE && + (*ppos) <= SISUSB_PCI_PSEUDO_PCIBASE + SISUSB_PCI_PCONFSIZE) { + + if (count != 4) { + mutex_unlock(&sisusb->lock); + return -EINVAL; + } + + address = (*ppos) - SISUSB_PCI_PSEUDO_PCIBASE; + + /* Write PCI config register. + * Given value expected in machine endianness. + */ + if (get_user(buf32, (u32 __user *)buffer)) + errno = -EFAULT; + else if (sisusb_write_pci_config(sisusb, address, buf32)) + errno = -EIO; + else + bytes_written = 4; + + + } else { + + /* Error */ + errno = -EBADFD; + + } + + (*ppos) += bytes_written; + + mutex_unlock(&sisusb->lock); + + return errno ? errno : bytes_written; +} + +static loff_t +sisusb_lseek(struct file *file, loff_t offset, int orig) +{ + struct sisusb_usb_data *sisusb; + loff_t ret; + + if (!(sisusb = file->private_data)) + return -ENODEV; + + mutex_lock(&sisusb->lock); + + /* Sanity check */ + if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { + mutex_unlock(&sisusb->lock); + return -ENODEV; + } + + switch (orig) { + case 0: + file->f_pos = offset; + ret = file->f_pos; + /* never negative, no force_successful_syscall needed */ + break; + case 1: + file->f_pos += offset; + ret = file->f_pos; + /* never negative, no force_successful_syscall needed */ + break; + default: + /* seeking relative to "end of file" is not supported */ + ret = -EINVAL; + } + + mutex_unlock(&sisusb->lock); + return ret; +} + +static int +sisusb_handle_command(struct sisusb_usb_data *sisusb, struct sisusb_command *y, + unsigned long arg) +{ + int retval, port, length; + u32 address; + + /* All our commands require the device + * to be initialized. + */ + if (!sisusb->devinit) + return -ENODEV; + + port = y->data3 - + SISUSB_PCI_PSEUDO_IOPORTBASE + + SISUSB_PCI_IOPORTBASE; + + switch (y->operation) { + case SUCMD_GET: + retval = sisusb_getidxreg(sisusb, port, + y->data0, &y->data1); + if (!retval) { + if (copy_to_user((void __user *)arg, y, + sizeof(*y))) + retval = -EFAULT; + } + break; + + case SUCMD_SET: + retval = sisusb_setidxreg(sisusb, port, + y->data0, y->data1); + break; + + case SUCMD_SETOR: + retval = sisusb_setidxregor(sisusb, port, + y->data0, y->data1); + break; + + case SUCMD_SETAND: + retval = sisusb_setidxregand(sisusb, port, + y->data0, y->data1); + break; + + case SUCMD_SETANDOR: + retval = sisusb_setidxregandor(sisusb, port, + y->data0, y->data1, y->data2); + break; + + case SUCMD_SETMASK: + retval = sisusb_setidxregmask(sisusb, port, + y->data0, y->data1, y->data2); + break; + + case SUCMD_CLRSCR: + /* Gfx core must be initialized */ + if (!sisusb->gfxinit) + return -ENODEV; + + length = (y->data0 << 16) | (y->data1 << 8) | y->data2; + address = y->data3 - + SISUSB_PCI_PSEUDO_MEMBASE + + SISUSB_PCI_MEMBASE; + retval = sisusb_clear_vram(sisusb, address, length); + break; + + case SUCMD_HANDLETEXTMODE: + retval = 0; +#ifdef INCL_SISUSB_CON + /* Gfx core must be initialized, SiS_Pr must exist */ + if (!sisusb->gfxinit || !sisusb->SiS_Pr) + return -ENODEV; + + switch (y->data0) { + case 0: + retval = sisusb_reset_text_mode(sisusb, 0); + break; + case 1: + sisusb->textmodedestroyed = 1; + break; + } +#endif + break; + +#ifdef INCL_SISUSB_CON + case SUCMD_SETMODE: + /* Gfx core must be initialized, SiS_Pr must exist */ + if (!sisusb->gfxinit || !sisusb->SiS_Pr) + return -ENODEV; + + retval = 0; + + sisusb->SiS_Pr->IOAddress = SISUSB_PCI_IOPORTBASE + 0x30; + sisusb->SiS_Pr->sisusb = (void *)sisusb; + + if (SiSUSBSetMode(sisusb->SiS_Pr, y->data3)) + retval = -EINVAL; + + break; + + case SUCMD_SETVESAMODE: + /* Gfx core must be initialized, SiS_Pr must exist */ + if (!sisusb->gfxinit || !sisusb->SiS_Pr) + return -ENODEV; + + retval = 0; + + sisusb->SiS_Pr->IOAddress = SISUSB_PCI_IOPORTBASE + 0x30; + sisusb->SiS_Pr->sisusb = (void *)sisusb; + + if (SiSUSBSetVESAMode(sisusb->SiS_Pr, y->data3)) + retval = -EINVAL; + + break; +#endif + + default: + retval = -EINVAL; + } + + if (retval > 0) + retval = -EIO; + + return retval; +} + +static long +sisusb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct sisusb_usb_data *sisusb; + struct sisusb_info x; + struct sisusb_command y; + long retval = 0; + u32 __user *argp = (u32 __user *)arg; + + if (!(sisusb = file->private_data)) + return -ENODEV; + + mutex_lock(&sisusb->lock); + + /* Sanity check */ + if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { + retval = -ENODEV; + goto err_out; + } + + switch (cmd) { + + case SISUSB_GET_CONFIG_SIZE: + + if (put_user(sizeof(x), argp)) + retval = -EFAULT; + + break; + + case SISUSB_GET_CONFIG: + + x.sisusb_id = SISUSB_ID; + x.sisusb_version = SISUSB_VERSION; + x.sisusb_revision = SISUSB_REVISION; + x.sisusb_patchlevel = SISUSB_PATCHLEVEL; + x.sisusb_gfxinit = sisusb->gfxinit; + x.sisusb_vrambase = SISUSB_PCI_PSEUDO_MEMBASE; + x.sisusb_mmiobase = SISUSB_PCI_PSEUDO_MMIOBASE; + x.sisusb_iobase = SISUSB_PCI_PSEUDO_IOPORTBASE; + x.sisusb_pcibase = SISUSB_PCI_PSEUDO_PCIBASE; + x.sisusb_vramsize = sisusb->vramsize; + x.sisusb_minor = sisusb->minor; + x.sisusb_fbdevactive= 0; +#ifdef INCL_SISUSB_CON + x.sisusb_conactive = sisusb->haveconsole ? 1 : 0; +#else + x.sisusb_conactive = 0; +#endif + memset(x.sisusb_reserved, 0, sizeof(x.sisusb_reserved)); + + if (copy_to_user((void __user *)arg, &x, sizeof(x))) + retval = -EFAULT; + + break; + + case SISUSB_COMMAND: + + if (copy_from_user(&y, (void __user *)arg, sizeof(y))) + retval = -EFAULT; + else + retval = sisusb_handle_command(sisusb, &y, arg); + + break; + + default: + retval = -ENOTTY; + break; + } + +err_out: + mutex_unlock(&sisusb->lock); + return retval; +} + +#ifdef SISUSB_NEW_CONFIG_COMPAT +static long +sisusb_compat_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + long retval; + + switch (cmd) { + case SISUSB_GET_CONFIG_SIZE: + case SISUSB_GET_CONFIG: + case SISUSB_COMMAND: + retval = sisusb_ioctl(f, cmd, arg); + return retval; + + default: + return -ENOIOCTLCMD; + } +} +#endif + +static const struct file_operations usb_sisusb_fops = { + .owner = THIS_MODULE, + .open = sisusb_open, + .release = sisusb_release, + .read = sisusb_read, + .write = sisusb_write, + .llseek = sisusb_lseek, +#ifdef SISUSB_NEW_CONFIG_COMPAT + .compat_ioctl = sisusb_compat_ioctl, +#endif + .unlocked_ioctl = sisusb_ioctl +}; + +static struct usb_class_driver usb_sisusb_class = { + .name = "sisusbvga%d", + .fops = &usb_sisusb_fops, + .minor_base = SISUSB_MINOR +}; + +static int sisusb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct sisusb_usb_data *sisusb; + int retval = 0, i; + + dev_info(&dev->dev, "USB2VGA dongle found at address %d\n", + dev->devnum); + + /* Allocate memory for our private */ + if (!(sisusb = kzalloc(sizeof(*sisusb), GFP_KERNEL))) { + dev_err(&dev->dev, "Failed to allocate memory for private data\n"); + return -ENOMEM; + } + kref_init(&sisusb->kref); + + mutex_init(&(sisusb->lock)); + + /* Register device */ + if ((retval = usb_register_dev(intf, &usb_sisusb_class))) { + dev_err(&sisusb->sisusb_dev->dev, "Failed to get a minor for device %d\n", + dev->devnum); + retval = -ENODEV; + goto error_1; + } + + sisusb->sisusb_dev = dev; + sisusb->minor = intf->minor; + sisusb->vrambase = SISUSB_PCI_MEMBASE; + sisusb->mmiobase = SISUSB_PCI_MMIOBASE; + sisusb->mmiosize = SISUSB_PCI_MMIOSIZE; + sisusb->ioportbase = SISUSB_PCI_IOPORTBASE; + /* Everything else is zero */ + + /* Allocate buffers */ + sisusb->ibufsize = SISUSB_IBUF_SIZE; + if (!(sisusb->ibuf = kmalloc(SISUSB_IBUF_SIZE, GFP_KERNEL))) { + dev_err(&sisusb->sisusb_dev->dev, "Failed to allocate memory for input buffer"); + retval = -ENOMEM; + goto error_2; + } + + sisusb->numobufs = 0; + sisusb->obufsize = SISUSB_OBUF_SIZE; + for (i = 0; i < NUMOBUFS; i++) { + if (!(sisusb->obuf[i] = kmalloc(SISUSB_OBUF_SIZE, GFP_KERNEL))) { + if (i == 0) { + dev_err(&sisusb->sisusb_dev->dev, "Failed to allocate memory for output buffer\n"); + retval = -ENOMEM; + goto error_3; + } + break; + } else + sisusb->numobufs++; + + } + + /* Allocate URBs */ + if (!(sisusb->sisurbin = usb_alloc_urb(0, GFP_KERNEL))) { + dev_err(&sisusb->sisusb_dev->dev, "Failed to allocate URBs\n"); + retval = -ENOMEM; + goto error_3; + } + sisusb->completein = 1; + + for (i = 0; i < sisusb->numobufs; i++) { + if (!(sisusb->sisurbout[i] = usb_alloc_urb(0, GFP_KERNEL))) { + dev_err(&sisusb->sisusb_dev->dev, "Failed to allocate URBs\n"); + retval = -ENOMEM; + goto error_4; + } + sisusb->urbout_context[i].sisusb = (void *)sisusb; + sisusb->urbout_context[i].urbindex = i; + sisusb->urbstatus[i] = 0; + } + + dev_info(&sisusb->sisusb_dev->dev, "Allocated %d output buffers\n", sisusb->numobufs); + +#ifdef INCL_SISUSB_CON + /* Allocate our SiS_Pr */ + if (!(sisusb->SiS_Pr = kmalloc(sizeof(struct SiS_Private), GFP_KERNEL))) { + dev_err(&sisusb->sisusb_dev->dev, "Failed to allocate SiS_Pr\n"); + } +#endif + + /* Do remaining init stuff */ + + init_waitqueue_head(&sisusb->wait_q); + + usb_set_intfdata(intf, sisusb); + + usb_get_dev(sisusb->sisusb_dev); + + sisusb->present = 1; + + if (dev->speed == USB_SPEED_HIGH || dev->speed == USB_SPEED_SUPER) { + int initscreen = 1; +#ifdef INCL_SISUSB_CON + if (sisusb_first_vc > 0 && + sisusb_last_vc > 0 && + sisusb_first_vc <= sisusb_last_vc && + sisusb_last_vc <= MAX_NR_CONSOLES) + initscreen = 0; +#endif + if (sisusb_init_gfxdevice(sisusb, initscreen)) + dev_err(&sisusb->sisusb_dev->dev, "Failed to early initialize device\n"); + + } else + dev_info(&sisusb->sisusb_dev->dev, "Not attached to USB 2.0 hub, deferring init\n"); + + sisusb->ready = 1; + +#ifdef SISUSBENDIANTEST + dev_dbg(&sisusb->sisusb_dev->dev, "*** RWTEST ***\n"); + sisusb_testreadwrite(sisusb); + dev_dbg(&sisusb->sisusb_dev->dev, "*** RWTEST END ***\n"); +#endif + +#ifdef INCL_SISUSB_CON + sisusb_console_init(sisusb, sisusb_first_vc, sisusb_last_vc); +#endif + + return 0; + +error_4: + sisusb_free_urbs(sisusb); +error_3: + sisusb_free_buffers(sisusb); +error_2: + usb_deregister_dev(intf, &usb_sisusb_class); +error_1: + kfree(sisusb); + return retval; +} + +static void sisusb_disconnect(struct usb_interface *intf) +{ + struct sisusb_usb_data *sisusb; + + /* This should *not* happen */ + if (!(sisusb = usb_get_intfdata(intf))) + return; + +#ifdef INCL_SISUSB_CON + sisusb_console_exit(sisusb); +#endif + + usb_deregister_dev(intf, &usb_sisusb_class); + + mutex_lock(&sisusb->lock); + + /* Wait for all URBs to complete and kill them in case (MUST do) */ + if (!sisusb_wait_all_out_complete(sisusb)) + sisusb_kill_all_busy(sisusb); + + usb_set_intfdata(intf, NULL); + + sisusb->present = 0; + sisusb->ready = 0; + + mutex_unlock(&sisusb->lock); + + /* decrement our usage count */ + kref_put(&sisusb->kref, sisusb_delete); +} + +static const struct usb_device_id sisusb_table[] = { + { USB_DEVICE(0x0711, 0x0550) }, + { USB_DEVICE(0x0711, 0x0900) }, + { USB_DEVICE(0x0711, 0x0901) }, + { USB_DEVICE(0x0711, 0x0902) }, + { USB_DEVICE(0x0711, 0x0903) }, + { USB_DEVICE(0x0711, 0x0918) }, + { USB_DEVICE(0x0711, 0x0920) }, + { USB_DEVICE(0x0711, 0x0950) }, + { USB_DEVICE(0x0711, 0x5200) }, + { USB_DEVICE(0x182d, 0x021c) }, + { USB_DEVICE(0x182d, 0x0269) }, + { } +}; + +MODULE_DEVICE_TABLE (usb, sisusb_table); + +static struct usb_driver sisusb_driver = { + .name = "sisusb", + .probe = sisusb_probe, + .disconnect = sisusb_disconnect, + .id_table = sisusb_table, +}; + +static int __init usb_sisusb_init(void) +{ + +#ifdef INCL_SISUSB_CON + sisusb_init_concode(); +#endif + + return usb_register(&sisusb_driver); +} + +static void __exit usb_sisusb_exit(void) +{ + usb_deregister(&sisusb_driver); +} + +module_init(usb_sisusb_init); +module_exit(usb_sisusb_exit); + +MODULE_AUTHOR("Thomas Winischhofer "); +MODULE_DESCRIPTION("sisusbvga - Driver for Net2280/SiS315-based USB2VGA dongles"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/misc/sisusbvga/sisusb.h b/drivers/usb/misc/sisusbvga/sisusb.h new file mode 100644 index 00000000..55492a59 --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb.h @@ -0,0 +1,311 @@ +/* + * sisusb - usb kernel driver for Net2280/SiS315 based USB2VGA dongles + * + * Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, this code is licensed under the + * terms of the GPL v2. + * + * Otherwise, the following license terms apply: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1) Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2) Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3) The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer + * + */ + +#ifndef _SISUSB_H_ +#define _SISUSB_H_ + +#ifdef CONFIG_COMPAT +#define SISUSB_NEW_CONFIG_COMPAT +#endif + +#include + +/* For older kernels, support for text consoles is by default + * off. To enable text console support, change the following: + */ +/* #define CONFIG_USB_SISUSBVGA_CON */ + +/* Version Information */ + +#define SISUSB_VERSION 0 +#define SISUSB_REVISION 0 +#define SISUSB_PATCHLEVEL 8 + +/* Include console and mode switching code? */ + +#ifdef CONFIG_USB_SISUSBVGA_CON +#define INCL_SISUSB_CON 1 +#endif + +#include +#include +#include "sisusb_struct.h" + +/* USB related */ + +#define SISUSB_MINOR 133 /* official */ + +/* Size of the sisusb input/output buffers */ +#define SISUSB_IBUF_SIZE 0x01000 +#define SISUSB_OBUF_SIZE 0x10000 /* fixed */ + +#define NUMOBUFS 8 /* max number of output buffers/output URBs */ + +/* About endianness: + * + * 1) I/O ports, PCI config registers. The read/write() + * calls emulate inX/outX. Hence, the data is + * expected/delivered in machine endiannes by this + * driver. + * 2) Video memory. The data is copied 1:1. There is + * no swapping. Ever. This means for userland that + * the data has to be prepared properly. (Hint: + * think graphics data format, command queue, + * hardware cursor.) + * 3) MMIO. Data is copied 1:1. MMIO must be swapped + * properly by userland. + * + */ + +#ifdef __BIG_ENDIAN +#define SISUSB_CORRECT_ENDIANNESS_PACKET(p) \ + do { \ + p->header = cpu_to_le16(p->header); \ + p->address = cpu_to_le32(p->address); \ + p->data = cpu_to_le32(p->data); \ + } while(0) +#else +#define SISUSB_CORRECT_ENDIANNESS_PACKET(p) +#endif + +struct sisusb_usb_data; + +struct sisusb_urb_context { /* urb->context for outbound bulk URBs */ + struct sisusb_usb_data *sisusb; + int urbindex; + int *actual_length; +}; + +struct sisusb_usb_data { + struct usb_device *sisusb_dev; + struct usb_interface *interface; + struct kref kref; + wait_queue_head_t wait_q; /* for syncind and timeouts */ + struct mutex lock; /* general race avoidance */ + unsigned int ifnum; /* interface number of the USB device */ + int minor; /* minor (for logging clarity) */ + int isopen; /* !=0 if open */ + int present; /* !=0 if device is present on the bus */ + int ready; /* !=0 if device is ready for userland */ + int numobufs; /* number of obufs = number of out urbs */ + char *obuf[NUMOBUFS], *ibuf; /* transfer buffers */ + int obufsize, ibufsize; + struct urb *sisurbout[NUMOBUFS]; + struct urb *sisurbin; + unsigned char urbstatus[NUMOBUFS]; + unsigned char completein; + struct sisusb_urb_context urbout_context[NUMOBUFS]; + unsigned long flagb0; + unsigned long vrambase; /* framebuffer base */ + unsigned int vramsize; /* framebuffer size (bytes) */ + unsigned long mmiobase; + unsigned int mmiosize; + unsigned long ioportbase; + unsigned char devinit; /* device initialized? */ + unsigned char gfxinit; /* graphics core initialized? */ + unsigned short chipid, chipvendor; + unsigned short chiprevision; +#ifdef INCL_SISUSB_CON + struct SiS_Private *SiS_Pr; + unsigned long scrbuf; + unsigned int scrbuf_size; + int haveconsole, con_first, con_last; + int havethisconsole[MAX_NR_CONSOLES]; + int textmodedestroyed; + unsigned int sisusb_num_columns; /* real number, not vt's idea */ + int cur_start_addr, con_rolled_over; + int sisusb_cursor_loc, bad_cursor_pos; + int sisusb_cursor_size_from; + int sisusb_cursor_size_to; + int current_font_height, current_font_512; + int font_backup_size, font_backup_height, font_backup_512; + char *font_backup; + int font_slot; + struct vc_data *sisusb_display_fg; + int is_gfx; + int con_blanked; +#endif +}; + +#define to_sisusb_dev(d) container_of(d, struct sisusb_usb_data, kref) + +/* USB transport related */ + +/* urbstatus */ +#define SU_URB_BUSY 1 +#define SU_URB_ALLOC 2 + +/* Endpoints */ + +#define SISUSB_EP_GFX_IN 0x0e /* gfx std packet out(0e)/in(8e) */ +#define SISUSB_EP_GFX_OUT 0x0e + +#define SISUSB_EP_GFX_BULK_OUT 0x01 /* gfx mem bulk out/in */ +#define SISUSB_EP_GFX_BULK_IN 0x02 /* ? 2 is "OUT" ? */ + +#define SISUSB_EP_GFX_LBULK_OUT 0x03 /* gfx large mem bulk out */ + +#define SISUSB_EP_UNKNOWN_04 0x04 /* ? 4 is "OUT" ? - unused */ + +#define SISUSB_EP_BRIDGE_IN 0x0d /* Net2280 out(0d)/in(8d) */ +#define SISUSB_EP_BRIDGE_OUT 0x0d + +#define SISUSB_TYPE_MEM 0 +#define SISUSB_TYPE_IO 1 + +struct sisusb_packet { + unsigned short header; + u32 address; + u32 data; +} __attribute__ ((__packed__)); + +#define CLEARPACKET(packet) memset(packet, 0, 10) + +/* PCI bridge related */ + +#define SISUSB_PCI_MEMBASE 0xd0000000 +#define SISUSB_PCI_MMIOBASE 0xe4000000 +#define SISUSB_PCI_IOPORTBASE 0x0000d000 + +#define SISUSB_PCI_PSEUDO_MEMBASE 0x10000000 +#define SISUSB_PCI_PSEUDO_MMIOBASE 0x20000000 +#define SISUSB_PCI_PSEUDO_IOPORTBASE 0x0000d000 +#define SISUSB_PCI_PSEUDO_PCIBASE 0x00010000 + +#define SISUSB_PCI_MMIOSIZE (128*1024) +#define SISUSB_PCI_PCONFSIZE 0x5c + +/* graphics core related */ + +#define AROFFSET 0x40 +#define ARROFFSET 0x41 +#define GROFFSET 0x4e +#define SROFFSET 0x44 +#define CROFFSET 0x54 +#define MISCROFFSET 0x4c +#define MISCWOFFSET 0x42 +#define INPUTSTATOFFSET 0x5A +#define PART1OFFSET 0x04 +#define PART2OFFSET 0x10 +#define PART3OFFSET 0x12 +#define PART4OFFSET 0x14 +#define PART5OFFSET 0x16 +#define CAPTUREOFFSET 0x00 +#define VIDEOOFFSET 0x02 +#define COLREGOFFSET 0x48 +#define PELMASKOFFSET 0x46 +#define VGAENABLE 0x43 + +#define SISAR SISUSB_PCI_IOPORTBASE + AROFFSET +#define SISARR SISUSB_PCI_IOPORTBASE + ARROFFSET +#define SISGR SISUSB_PCI_IOPORTBASE + GROFFSET +#define SISSR SISUSB_PCI_IOPORTBASE + SROFFSET +#define SISCR SISUSB_PCI_IOPORTBASE + CROFFSET +#define SISMISCR SISUSB_PCI_IOPORTBASE + MISCROFFSET +#define SISMISCW SISUSB_PCI_IOPORTBASE + MISCWOFFSET +#define SISINPSTAT SISUSB_PCI_IOPORTBASE + INPUTSTATOFFSET +#define SISPART1 SISUSB_PCI_IOPORTBASE + PART1OFFSET +#define SISPART2 SISUSB_PCI_IOPORTBASE + PART2OFFSET +#define SISPART3 SISUSB_PCI_IOPORTBASE + PART3OFFSET +#define SISPART4 SISUSB_PCI_IOPORTBASE + PART4OFFSET +#define SISPART5 SISUSB_PCI_IOPORTBASE + PART5OFFSET +#define SISCAP SISUSB_PCI_IOPORTBASE + CAPTUREOFFSET +#define SISVID SISUSB_PCI_IOPORTBASE + VIDEOOFFSET +#define SISCOLIDXR SISUSB_PCI_IOPORTBASE + COLREGOFFSET - 1 +#define SISCOLIDX SISUSB_PCI_IOPORTBASE + COLREGOFFSET +#define SISCOLDATA SISUSB_PCI_IOPORTBASE + COLREGOFFSET + 1 +#define SISCOL2IDX SISPART5 +#define SISCOL2DATA SISPART5 + 1 +#define SISPEL SISUSB_PCI_IOPORTBASE + PELMASKOFFSET +#define SISVGAEN SISUSB_PCI_IOPORTBASE + VGAENABLE +#define SISDACA SISCOLIDX +#define SISDACD SISCOLDATA + +/* ioctl related */ + +/* Structure argument for SISUSB_GET_INFO ioctl */ +struct sisusb_info { + __u32 sisusb_id; /* for identifying sisusb */ +#define SISUSB_ID 0x53495355 /* Identify myself with 'SISU' */ + __u8 sisusb_version; + __u8 sisusb_revision; + __u8 sisusb_patchlevel; + __u8 sisusb_gfxinit; /* graphics core initialized? */ + + __u32 sisusb_vrambase; + __u32 sisusb_mmiobase; + __u32 sisusb_iobase; + __u32 sisusb_pcibase; + + __u32 sisusb_vramsize; /* framebuffer size in bytes */ + + __u32 sisusb_minor; + + __u32 sisusb_fbdevactive; /* != 0 if framebuffer device active */ + + __u32 sisusb_conactive; /* != 0 if console driver active */ + + __u8 sisusb_reserved[28]; /* for future use */ +}; + +struct sisusb_command { + __u8 operation; /* see below */ + __u8 data0; /* operation dependent */ + __u8 data1; /* operation dependent */ + __u8 data2; /* operation dependent */ + __u32 data3; /* operation dependent */ + __u32 data4; /* for future use */ +}; + +#define SUCMD_GET 0x01 /* for all: data0 = index, data3 = port */ +#define SUCMD_SET 0x02 /* data1 = value */ +#define SUCMD_SETOR 0x03 /* data1 = or */ +#define SUCMD_SETAND 0x04 /* data1 = and */ +#define SUCMD_SETANDOR 0x05 /* data1 = and, data2 = or */ +#define SUCMD_SETMASK 0x06 /* data1 = data, data2 = mask */ + +#define SUCMD_CLRSCR 0x07 /* data0:1:2 = length, data3 = address */ + +#define SUCMD_HANDLETEXTMODE 0x08 /* Reset/destroy text mode */ + +#define SUCMD_SETMODE 0x09 /* Set a display mode (data3 = SiS mode) */ +#define SUCMD_SETVESAMODE 0x0a /* Set a display mode (data3 = VESA mode) */ + +#define SISUSB_COMMAND _IOWR(0xF3,0x3D,struct sisusb_command) +#define SISUSB_GET_CONFIG_SIZE _IOR(0xF3,0x3E,__u32) +#define SISUSB_GET_CONFIG _IOR(0xF3,0x3F,struct sisusb_info) + +#endif /* SISUSB_H */ diff --git a/drivers/usb/misc/sisusbvga/sisusb_con.c b/drivers/usb/misc/sisusbvga/sisusb_con.c new file mode 100644 index 00000000..411e605f --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb_con.c @@ -0,0 +1,1564 @@ +/* + * sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles + * + * VGA text mode console part + * + * Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, this code is licensed under the + * terms of the GPL v2. + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific psisusbr written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer + * + * Portions based on vgacon.c which are + * Created 28 Sep 1997 by Geert Uytterhoeven + * Rewritten by Martin Mares , July 1998 + * based on code Copyright (C) 1991, 1992 Linus Torvalds + * 1995 Jay Estabrook + * + * A note on using in_atomic() in here: We can't handle console + * calls from non-schedulable context due to our USB-dependend + * nature. For now, this driver just ignores any calls if it + * detects this state. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sisusb.h" +#include "sisusb_init.h" + +#ifdef INCL_SISUSB_CON + +#define sisusbcon_writew(val, addr) (*(addr) = (val)) +#define sisusbcon_readw(addr) (*(addr)) +#define sisusbcon_memmovew(d, s, c) memmove(d, s, c) +#define sisusbcon_memcpyw(d, s, c) memcpy(d, s, c) + +/* vc_data -> sisusb conversion table */ +static struct sisusb_usb_data *mysisusbs[MAX_NR_CONSOLES]; + +/* Forward declaration */ +static const struct consw sisusb_con; + +static inline void +sisusbcon_memsetw(u16 *s, u16 c, unsigned int count) +{ + count /= 2; + while (count--) + sisusbcon_writew(c, s++); +} + +static inline void +sisusb_initialize(struct sisusb_usb_data *sisusb) +{ + /* Reset cursor and start address */ + if (sisusb_setidxreg(sisusb, SISCR, 0x0c, 0x00)) + return; + if (sisusb_setidxreg(sisusb, SISCR, 0x0d, 0x00)) + return; + if (sisusb_setidxreg(sisusb, SISCR, 0x0e, 0x00)) + return; + sisusb_setidxreg(sisusb, SISCR, 0x0f, 0x00); +} + +static inline void +sisusbcon_set_start_address(struct sisusb_usb_data *sisusb, struct vc_data *c) +{ + sisusb->cur_start_addr = (c->vc_visible_origin - sisusb->scrbuf) / 2; + + sisusb_setidxreg(sisusb, SISCR, 0x0c, (sisusb->cur_start_addr >> 8)); + sisusb_setidxreg(sisusb, SISCR, 0x0d, (sisusb->cur_start_addr & 0xff)); +} + +void +sisusb_set_cursor(struct sisusb_usb_data *sisusb, unsigned int location) +{ + if (sisusb->sisusb_cursor_loc == location) + return; + + sisusb->sisusb_cursor_loc = location; + + /* Hardware bug: Text cursor appears twice or not at all + * at some positions. Work around it with the cursor skew + * bits. + */ + + if ((location & 0x0007) == 0x0007) { + sisusb->bad_cursor_pos = 1; + location--; + if (sisusb_setidxregandor(sisusb, SISCR, 0x0b, 0x1f, 0x20)) + return; + } else if (sisusb->bad_cursor_pos) { + if (sisusb_setidxregand(sisusb, SISCR, 0x0b, 0x1f)) + return; + sisusb->bad_cursor_pos = 0; + } + + if (sisusb_setidxreg(sisusb, SISCR, 0x0e, (location >> 8))) + return; + sisusb_setidxreg(sisusb, SISCR, 0x0f, (location & 0xff)); +} + +static inline struct sisusb_usb_data * +sisusb_get_sisusb(unsigned short console) +{ + return mysisusbs[console]; +} + +static inline int +sisusb_sisusb_valid(struct sisusb_usb_data *sisusb) +{ + if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) + return 0; + + return 1; +} + +static struct sisusb_usb_data * +sisusb_get_sisusb_lock_and_check(unsigned short console) +{ + struct sisusb_usb_data *sisusb; + + /* We can't handle console calls in non-schedulable + * context due to our locks and the USB transport. + * So we simply ignore them. This should only affect + * some calls to printk. + */ + if (in_atomic()) + return NULL; + + if (!(sisusb = sisusb_get_sisusb(console))) + return NULL; + + mutex_lock(&sisusb->lock); + + if (!sisusb_sisusb_valid(sisusb) || + !sisusb->havethisconsole[console]) { + mutex_unlock(&sisusb->lock); + return NULL; + } + + return sisusb; +} + +static int +sisusb_is_inactive(struct vc_data *c, struct sisusb_usb_data *sisusb) +{ + if (sisusb->is_gfx || + sisusb->textmodedestroyed || + c->vc_mode != KD_TEXT) + return 1; + + return 0; +} + +/* con_startup console interface routine */ +static const char * +sisusbcon_startup(void) +{ + return "SISUSBCON"; +} + +/* con_init console interface routine */ +static void +sisusbcon_init(struct vc_data *c, int init) +{ + struct sisusb_usb_data *sisusb; + int cols, rows; + + /* This is called by take_over_console(), + * ie by us/under our control. It is + * only called after text mode and fonts + * are set up/restored. + */ + + if (!(sisusb = sisusb_get_sisusb(c->vc_num))) + return; + + mutex_lock(&sisusb->lock); + + if (!sisusb_sisusb_valid(sisusb)) { + mutex_unlock(&sisusb->lock); + return; + } + + c->vc_can_do_color = 1; + + c->vc_complement_mask = 0x7700; + + c->vc_hi_font_mask = sisusb->current_font_512 ? 0x0800 : 0; + + sisusb->haveconsole = 1; + + sisusb->havethisconsole[c->vc_num] = 1; + + /* We only support 640x400 */ + c->vc_scan_lines = 400; + + c->vc_font.height = sisusb->current_font_height; + + /* We only support width = 8 */ + cols = 80; + rows = c->vc_scan_lines / c->vc_font.height; + + /* Increment usage count for our sisusb. + * Doing so saves us from upping/downing + * the disconnect semaphore; we can't + * lose our sisusb until this is undone + * in con_deinit. For all other console + * interface functions, it suffices to + * use sisusb->lock and do a quick check + * of sisusb for device disconnection. + */ + kref_get(&sisusb->kref); + + if (!*c->vc_uni_pagedir_loc) + con_set_default_unimap(c); + + mutex_unlock(&sisusb->lock); + + if (init) { + c->vc_cols = cols; + c->vc_rows = rows; + } else + vc_resize(c, cols, rows); +} + +/* con_deinit console interface routine */ +static void +sisusbcon_deinit(struct vc_data *c) +{ + struct sisusb_usb_data *sisusb; + int i; + + /* This is called by take_over_console() + * and others, ie not under our control. + */ + + if (!(sisusb = sisusb_get_sisusb(c->vc_num))) + return; + + mutex_lock(&sisusb->lock); + + /* Clear ourselves in mysisusbs */ + mysisusbs[c->vc_num] = NULL; + + sisusb->havethisconsole[c->vc_num] = 0; + + /* Free our font buffer if all consoles are gone */ + if (sisusb->font_backup) { + for(i = 0; i < MAX_NR_CONSOLES; i++) { + if (sisusb->havethisconsole[c->vc_num]) + break; + } + if (i == MAX_NR_CONSOLES) { + vfree(sisusb->font_backup); + sisusb->font_backup = NULL; + } + } + + mutex_unlock(&sisusb->lock); + + /* decrement the usage count on our sisusb */ + kref_put(&sisusb->kref, sisusb_delete); +} + +/* interface routine */ +static u8 +sisusbcon_build_attr(struct vc_data *c, u8 color, u8 intensity, + u8 blink, u8 underline, u8 reverse, u8 unused) +{ + u8 attr = color; + + if (underline) + attr = (attr & 0xf0) | c->vc_ulcolor; + else if (intensity == 0) + attr = (attr & 0xf0) | c->vc_halfcolor; + + if (reverse) + attr = ((attr) & 0x88) | + ((((attr) >> 4) | + ((attr) << 4)) & 0x77); + + if (blink) + attr ^= 0x80; + + if (intensity == 2) + attr ^= 0x08; + + return attr; +} + +/* Interface routine */ +static void +sisusbcon_invert_region(struct vc_data *vc, u16 *p, int count) +{ + /* Invert a region. This is called with a pointer + * to the console's internal screen buffer. So we + * simply do the inversion there and rely on + * a call to putc(s) to update the real screen. + */ + + while (count--) { + u16 a = sisusbcon_readw(p); + + a = ((a) & 0x88ff) | + (((a) & 0x7000) >> 4) | + (((a) & 0x0700) << 4); + + sisusbcon_writew(a, p++); + } +} + +#define SISUSB_VADDR(x,y) \ + ((u16 *)c->vc_origin + \ + (y) * sisusb->sisusb_num_columns + \ + (x)) + +#define SISUSB_HADDR(x,y) \ + ((u16 *)(sisusb->vrambase + (c->vc_origin - sisusb->scrbuf)) + \ + (y) * sisusb->sisusb_num_columns + \ + (x)) + +/* Interface routine */ +static void +sisusbcon_putc(struct vc_data *c, int ch, int y, int x) +{ + struct sisusb_usb_data *sisusb; + ssize_t written; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return; + + /* sisusb->lock is down */ + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return; + } + + + sisusb_copy_memory(sisusb, (char *)SISUSB_VADDR(x, y), + (long)SISUSB_HADDR(x, y), 2, &written); + + mutex_unlock(&sisusb->lock); +} + +/* Interface routine */ +static void +sisusbcon_putcs(struct vc_data *c, const unsigned short *s, + int count, int y, int x) +{ + struct sisusb_usb_data *sisusb; + ssize_t written; + u16 *dest; + int i; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return; + + /* sisusb->lock is down */ + + /* Need to put the characters into the buffer ourselves, + * because the vt does this AFTER calling us. + */ + + dest = SISUSB_VADDR(x, y); + + for (i = count; i > 0; i--) + sisusbcon_writew(sisusbcon_readw(s++), dest++); + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return; + } + + sisusb_copy_memory(sisusb, (char *)SISUSB_VADDR(x, y), + (long)SISUSB_HADDR(x, y), count * 2, &written); + + mutex_unlock(&sisusb->lock); +} + +/* Interface routine */ +static void +sisusbcon_clear(struct vc_data *c, int y, int x, int height, int width) +{ + struct sisusb_usb_data *sisusb; + u16 eattr = c->vc_video_erase_char; + ssize_t written; + int i, length, cols; + u16 *dest; + + if (width <= 0 || height <= 0) + return; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return; + + /* sisusb->lock is down */ + + /* Need to clear buffer ourselves, because the vt does + * this AFTER calling us. + */ + + dest = SISUSB_VADDR(x, y); + + cols = sisusb->sisusb_num_columns; + + if (width > cols) + width = cols; + + if (x == 0 && width >= c->vc_cols) { + + sisusbcon_memsetw(dest, eattr, height * cols * 2); + + } else { + + for (i = height; i > 0; i--, dest += cols) + sisusbcon_memsetw(dest, eattr, width * 2); + + } + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return; + } + + length = ((height * cols) - x - (cols - width - x)) * 2; + + + sisusb_copy_memory(sisusb, (unsigned char *)SISUSB_VADDR(x, y), + (long)SISUSB_HADDR(x, y), length, &written); + + mutex_unlock(&sisusb->lock); +} + +/* Interface routine */ +static void +sisusbcon_bmove(struct vc_data *c, int sy, int sx, + int dy, int dx, int height, int width) +{ + struct sisusb_usb_data *sisusb; + ssize_t written; + int cols, length; + + if (width <= 0 || height <= 0) + return; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return; + + /* sisusb->lock is down */ + + cols = sisusb->sisusb_num_columns; + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return; + } + + length = ((height * cols) - dx - (cols - width - dx)) * 2; + + + sisusb_copy_memory(sisusb, (unsigned char *)SISUSB_VADDR(dx, dy), + (long)SISUSB_HADDR(dx, dy), length, &written); + + mutex_unlock(&sisusb->lock); +} + +/* interface routine */ +static int +sisusbcon_switch(struct vc_data *c) +{ + struct sisusb_usb_data *sisusb; + ssize_t written; + int length; + + /* Returnvalue 0 means we have fully restored screen, + * and vt doesn't need to call do_update_region(). + * Returnvalue != 0 naturally means the opposite. + */ + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return 0; + + /* sisusb->lock is down */ + + /* Don't write to screen if in gfx mode */ + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return 0; + } + + /* That really should not happen. It would mean we are + * being called while the vc is using its private buffer + * as origin. + */ + if (c->vc_origin == (unsigned long)c->vc_screenbuf) { + mutex_unlock(&sisusb->lock); + dev_dbg(&sisusb->sisusb_dev->dev, "ASSERT ORIGIN != SCREENBUF!\n"); + return 0; + } + + /* Check that we don't copy too much */ + length = min((int)c->vc_screenbuf_size, + (int)(sisusb->scrbuf + sisusb->scrbuf_size - c->vc_origin)); + + /* Restore the screen contents */ + sisusbcon_memcpyw((u16 *)c->vc_origin, (u16 *)c->vc_screenbuf, + length); + + sisusb_copy_memory(sisusb, (unsigned char *)c->vc_origin, + (long)SISUSB_HADDR(0, 0), + length, &written); + + mutex_unlock(&sisusb->lock); + + return 0; +} + +/* interface routine */ +static void +sisusbcon_save_screen(struct vc_data *c) +{ + struct sisusb_usb_data *sisusb; + int length; + + /* Save the current screen contents to vc's private + * buffer. + */ + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return; + + /* sisusb->lock is down */ + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return; + } + + /* Check that we don't copy too much */ + length = min((int)c->vc_screenbuf_size, + (int)(sisusb->scrbuf + sisusb->scrbuf_size - c->vc_origin)); + + /* Save the screen contents to vc's private buffer */ + sisusbcon_memcpyw((u16 *)c->vc_screenbuf, (u16 *)c->vc_origin, + length); + + mutex_unlock(&sisusb->lock); +} + +/* interface routine */ +static int +sisusbcon_set_palette(struct vc_data *c, unsigned char *table) +{ + struct sisusb_usb_data *sisusb; + int i, j; + + /* Return value not used by vt */ + + if (!CON_IS_VISIBLE(c)) + return -EINVAL; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return -EINVAL; + + /* sisusb->lock is down */ + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return -EINVAL; + } + + for (i = j = 0; i < 16; i++) { + if (sisusb_setreg(sisusb, SISCOLIDX, table[i])) + break; + if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2)) + break; + if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2)) + break; + if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2)) + break; + } + + mutex_unlock(&sisusb->lock); + + return 0; +} + +/* interface routine */ +static int +sisusbcon_blank(struct vc_data *c, int blank, int mode_switch) +{ + struct sisusb_usb_data *sisusb; + u8 sr1, cr17, pmreg, cr63; + ssize_t written; + int ret = 0; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return 0; + + /* sisusb->lock is down */ + + if (mode_switch) + sisusb->is_gfx = blank ? 1 : 0; + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return 0; + } + + switch (blank) { + + case 1: /* Normal blanking: Clear screen */ + case -1: + sisusbcon_memsetw((u16 *)c->vc_origin, + c->vc_video_erase_char, + c->vc_screenbuf_size); + sisusb_copy_memory(sisusb, + (unsigned char *)c->vc_origin, + (u32)(sisusb->vrambase + + (c->vc_origin - sisusb->scrbuf)), + c->vc_screenbuf_size, &written); + sisusb->con_blanked = 1; + ret = 1; + break; + + default: /* VESA blanking */ + switch (blank) { + case 0: /* Unblank */ + sr1 = 0x00; + cr17 = 0x80; + pmreg = 0x00; + cr63 = 0x00; + ret = 1; + sisusb->con_blanked = 0; + break; + case VESA_VSYNC_SUSPEND + 1: + sr1 = 0x20; + cr17 = 0x80; + pmreg = 0x80; + cr63 = 0x40; + break; + case VESA_HSYNC_SUSPEND + 1: + sr1 = 0x20; + cr17 = 0x80; + pmreg = 0x40; + cr63 = 0x40; + break; + case VESA_POWERDOWN + 1: + sr1 = 0x20; + cr17 = 0x00; + pmreg = 0xc0; + cr63 = 0x40; + break; + default: + mutex_unlock(&sisusb->lock); + return -EINVAL; + } + + sisusb_setidxregandor(sisusb, SISSR, 0x01, ~0x20, sr1); + sisusb_setidxregandor(sisusb, SISCR, 0x17, 0x7f, cr17); + sisusb_setidxregandor(sisusb, SISSR, 0x1f, 0x3f, pmreg); + sisusb_setidxregandor(sisusb, SISCR, 0x63, 0xbf, cr63); + + } + + mutex_unlock(&sisusb->lock); + + return ret; +} + +/* interface routine */ +static int +sisusbcon_scrolldelta(struct vc_data *c, int lines) +{ + struct sisusb_usb_data *sisusb; + int margin = c->vc_size_row * 4; + int ul, we, p, st; + + /* The return value does not seem to be used */ + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return 0; + + /* sisusb->lock is down */ + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return 0; + } + + if (!lines) /* Turn scrollback off */ + c->vc_visible_origin = c->vc_origin; + else { + + if (sisusb->con_rolled_over > + (c->vc_scr_end - sisusb->scrbuf) + margin) { + + ul = c->vc_scr_end - sisusb->scrbuf; + we = sisusb->con_rolled_over + c->vc_size_row; + + } else { + + ul = 0; + we = sisusb->scrbuf_size; + + } + + p = (c->vc_visible_origin - sisusb->scrbuf - ul + we) % we + + lines * c->vc_size_row; + + st = (c->vc_origin - sisusb->scrbuf - ul + we) % we; + + if (st < 2 * margin) + margin = 0; + + if (p < margin) + p = 0; + + if (p > st - margin) + p = st; + + c->vc_visible_origin = sisusb->scrbuf + (p + ul) % we; + } + + sisusbcon_set_start_address(sisusb, c); + + mutex_unlock(&sisusb->lock); + + return 1; +} + +/* Interface routine */ +static void +sisusbcon_cursor(struct vc_data *c, int mode) +{ + struct sisusb_usb_data *sisusb; + int from, to, baseline; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return; + + /* sisusb->lock is down */ + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return; + } + + if (c->vc_origin != c->vc_visible_origin) { + c->vc_visible_origin = c->vc_origin; + sisusbcon_set_start_address(sisusb, c); + } + + if (mode == CM_ERASE) { + sisusb_setidxregor(sisusb, SISCR, 0x0a, 0x20); + sisusb->sisusb_cursor_size_to = -1; + mutex_unlock(&sisusb->lock); + return; + } + + sisusb_set_cursor(sisusb, (c->vc_pos - sisusb->scrbuf) / 2); + + baseline = c->vc_font.height - (c->vc_font.height < 10 ? 1 : 2); + + switch (c->vc_cursor_type & 0x0f) { + case CUR_BLOCK: from = 1; + to = c->vc_font.height; + break; + case CUR_TWO_THIRDS: from = c->vc_font.height / 3; + to = baseline; + break; + case CUR_LOWER_HALF: from = c->vc_font.height / 2; + to = baseline; + break; + case CUR_LOWER_THIRD: from = (c->vc_font.height * 2) / 3; + to = baseline; + break; + case CUR_NONE: from = 31; + to = 30; + break; + default: + case CUR_UNDERLINE: from = baseline - 1; + to = baseline; + break; + } + + if (sisusb->sisusb_cursor_size_from != from || + sisusb->sisusb_cursor_size_to != to) { + + sisusb_setidxreg(sisusb, SISCR, 0x0a, from); + sisusb_setidxregandor(sisusb, SISCR, 0x0b, 0xe0, to); + + sisusb->sisusb_cursor_size_from = from; + sisusb->sisusb_cursor_size_to = to; + } + + mutex_unlock(&sisusb->lock); +} + +static int +sisusbcon_scroll_area(struct vc_data *c, struct sisusb_usb_data *sisusb, + int t, int b, int dir, int lines) +{ + int cols = sisusb->sisusb_num_columns; + int length = ((b - t) * cols) * 2; + u16 eattr = c->vc_video_erase_char; + ssize_t written; + + /* sisusb->lock is down */ + + /* Scroll an area which does not match the + * visible screen's dimensions. This needs + * to be done separately, as it does not + * use hardware panning. + */ + + switch (dir) { + + case SM_UP: + sisusbcon_memmovew(SISUSB_VADDR(0, t), + SISUSB_VADDR(0, t + lines), + (b - t - lines) * cols * 2); + sisusbcon_memsetw(SISUSB_VADDR(0, b - lines), eattr, + lines * cols * 2); + break; + + case SM_DOWN: + sisusbcon_memmovew(SISUSB_VADDR(0, t + lines), + SISUSB_VADDR(0, t), + (b - t - lines) * cols * 2); + sisusbcon_memsetw(SISUSB_VADDR(0, t), eattr, + lines * cols * 2); + break; + } + + sisusb_copy_memory(sisusb, (char *)SISUSB_VADDR(0, t), + (long)SISUSB_HADDR(0, t), length, &written); + + mutex_unlock(&sisusb->lock); + + return 1; +} + +/* Interface routine */ +static int +sisusbcon_scroll(struct vc_data *c, int t, int b, int dir, int lines) +{ + struct sisusb_usb_data *sisusb; + u16 eattr = c->vc_video_erase_char; + ssize_t written; + int copyall = 0; + unsigned long oldorigin; + unsigned int delta = lines * c->vc_size_row; + u32 originoffset; + + /* Returning != 0 means we have done the scrolling successfully. + * Returning 0 makes vt do the scrolling on its own. + * Note that con_scroll is only called if the console is + * visible. In that case, the origin should be our buffer, + * not the vt's private one. + */ + + if (!lines) + return 1; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return 0; + + /* sisusb->lock is down */ + + if (sisusb_is_inactive(c, sisusb)) { + mutex_unlock(&sisusb->lock); + return 0; + } + + /* Special case */ + if (t || b != c->vc_rows) + return sisusbcon_scroll_area(c, sisusb, t, b, dir, lines); + + if (c->vc_origin != c->vc_visible_origin) { + c->vc_visible_origin = c->vc_origin; + sisusbcon_set_start_address(sisusb, c); + } + + /* limit amount to maximum realistic size */ + if (lines > c->vc_rows) + lines = c->vc_rows; + + oldorigin = c->vc_origin; + + switch (dir) { + + case SM_UP: + + if (c->vc_scr_end + delta >= + sisusb->scrbuf + sisusb->scrbuf_size) { + sisusbcon_memcpyw((u16 *)sisusb->scrbuf, + (u16 *)(oldorigin + delta), + c->vc_screenbuf_size - delta); + c->vc_origin = sisusb->scrbuf; + sisusb->con_rolled_over = oldorigin - sisusb->scrbuf; + copyall = 1; + } else + c->vc_origin += delta; + + sisusbcon_memsetw( + (u16 *)(c->vc_origin + c->vc_screenbuf_size - delta), + eattr, delta); + + break; + + case SM_DOWN: + + if (oldorigin - delta < sisusb->scrbuf) { + sisusbcon_memmovew((u16 *)(sisusb->scrbuf + + sisusb->scrbuf_size - + c->vc_screenbuf_size + + delta), + (u16 *)oldorigin, + c->vc_screenbuf_size - delta); + c->vc_origin = sisusb->scrbuf + + sisusb->scrbuf_size - + c->vc_screenbuf_size; + sisusb->con_rolled_over = 0; + copyall = 1; + } else + c->vc_origin -= delta; + + c->vc_scr_end = c->vc_origin + c->vc_screenbuf_size; + + scr_memsetw((u16 *)(c->vc_origin), eattr, delta); + + break; + } + + originoffset = (u32)(c->vc_origin - sisusb->scrbuf); + + if (copyall) + sisusb_copy_memory(sisusb, + (char *)c->vc_origin, + (u32)(sisusb->vrambase + originoffset), + c->vc_screenbuf_size, &written); + else if (dir == SM_UP) + sisusb_copy_memory(sisusb, + (char *)c->vc_origin + c->vc_screenbuf_size - delta, + (u32)sisusb->vrambase + originoffset + + c->vc_screenbuf_size - delta, + delta, &written); + else + sisusb_copy_memory(sisusb, + (char *)c->vc_origin, + (u32)(sisusb->vrambase + originoffset), + delta, &written); + + c->vc_scr_end = c->vc_origin + c->vc_screenbuf_size; + c->vc_visible_origin = c->vc_origin; + + sisusbcon_set_start_address(sisusb, c); + + c->vc_pos = c->vc_pos - oldorigin + c->vc_origin; + + mutex_unlock(&sisusb->lock); + + return 1; +} + +/* Interface routine */ +static int +sisusbcon_set_origin(struct vc_data *c) +{ + struct sisusb_usb_data *sisusb; + + /* Returning != 0 means we were successful. + * Returning 0 will vt make to use its own + * screenbuffer as the origin. + */ + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return 0; + + /* sisusb->lock is down */ + + if (sisusb_is_inactive(c, sisusb) || sisusb->con_blanked) { + mutex_unlock(&sisusb->lock); + return 0; + } + + c->vc_origin = c->vc_visible_origin = sisusb->scrbuf; + + sisusbcon_set_start_address(sisusb, c); + + sisusb->con_rolled_over = 0; + + mutex_unlock(&sisusb->lock); + + return 1; +} + +/* Interface routine */ +static int +sisusbcon_resize(struct vc_data *c, unsigned int newcols, unsigned int newrows, + unsigned int user) +{ + struct sisusb_usb_data *sisusb; + int fh; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return -ENODEV; + + fh = sisusb->current_font_height; + + mutex_unlock(&sisusb->lock); + + /* We are quite unflexible as regards resizing. The vt code + * handles sizes where the line length isn't equal the pitch + * quite badly. As regards the rows, our panning tricks only + * work well if the number of rows equals the visible number + * of rows. + */ + + if (newcols != 80 || c->vc_scan_lines / fh != newrows) + return -EINVAL; + + return 0; +} + +int +sisusbcon_do_font_op(struct sisusb_usb_data *sisusb, int set, int slot, + u8 *arg, int cmapsz, int ch512, int dorecalc, + struct vc_data *c, int fh, int uplock) +{ + int font_select = 0x00, i, err = 0; + u32 offset = 0; + u8 dummy; + + /* sisusb->lock is down */ + + /* + * The default font is kept in slot 0. + * A user font is loaded in slot 2 (256 ch) + * or 2+3 (512 ch). + */ + + if ((slot != 0 && slot != 2) || !fh) { + if (uplock) + mutex_unlock(&sisusb->lock); + return -EINVAL; + } + + if (set) + sisusb->font_slot = slot; + + /* Default font is always 256 */ + if (slot == 0) + ch512 = 0; + else + offset = 4 * cmapsz; + + font_select = (slot == 0) ? 0x00 : (ch512 ? 0x0e : 0x0a); + + err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x01); /* Reset */ + err |= sisusb_setidxreg(sisusb, SISSR, 0x02, 0x04); /* Write to plane 2 */ + err |= sisusb_setidxreg(sisusb, SISSR, 0x04, 0x07); /* Memory mode a0-bf */ + err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x03); /* Reset */ + + if (err) + goto font_op_error; + + err |= sisusb_setidxreg(sisusb, SISGR, 0x04, 0x03); /* Select plane read 2 */ + err |= sisusb_setidxreg(sisusb, SISGR, 0x05, 0x00); /* Disable odd/even */ + err |= sisusb_setidxreg(sisusb, SISGR, 0x06, 0x00); /* Address range a0-bf */ + + if (err) + goto font_op_error; + + if (arg) { + if (set) + for (i = 0; i < cmapsz; i++) { + err |= sisusb_writeb(sisusb, + sisusb->vrambase + offset + i, + arg[i]); + if (err) + break; + } + else + for (i = 0; i < cmapsz; i++) { + err |= sisusb_readb(sisusb, + sisusb->vrambase + offset + i, + &arg[i]); + if (err) + break; + } + + /* + * In 512-character mode, the character map is not contiguous if + * we want to remain EGA compatible -- which we do + */ + + if (ch512) { + if (set) + for (i = 0; i < cmapsz; i++) { + err |= sisusb_writeb(sisusb, + sisusb->vrambase + offset + + (2 * cmapsz) + i, + arg[cmapsz + i]); + if (err) + break; + } + else + for (i = 0; i < cmapsz; i++) { + err |= sisusb_readb(sisusb, + sisusb->vrambase + offset + + (2 * cmapsz) + i, + &arg[cmapsz + i]); + if (err) + break; + } + } + } + + if (err) + goto font_op_error; + + err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x01); /* Reset */ + err |= sisusb_setidxreg(sisusb, SISSR, 0x02, 0x03); /* Write to planes 0+1 */ + err |= sisusb_setidxreg(sisusb, SISSR, 0x04, 0x03); /* Memory mode a0-bf */ + if (set) + sisusb_setidxreg(sisusb, SISSR, 0x03, font_select); + err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x03); /* Reset end */ + + if (err) + goto font_op_error; + + err |= sisusb_setidxreg(sisusb, SISGR, 0x04, 0x00); /* Select plane read 0 */ + err |= sisusb_setidxreg(sisusb, SISGR, 0x05, 0x10); /* Enable odd/even */ + err |= sisusb_setidxreg(sisusb, SISGR, 0x06, 0x06); /* Address range b8-bf */ + + if (err) + goto font_op_error; + + if ((set) && (ch512 != sisusb->current_font_512)) { + + /* Font is shared among all our consoles. + * And so is the hi_font_mask. + */ + for (i = 0; i < MAX_NR_CONSOLES; i++) { + struct vc_data *d = vc_cons[i].d; + if (d && d->vc_sw == &sisusb_con) + d->vc_hi_font_mask = ch512 ? 0x0800 : 0; + } + + sisusb->current_font_512 = ch512; + + /* color plane enable register: + 256-char: enable intensity bit + 512-char: disable intensity bit */ + sisusb_getreg(sisusb, SISINPSTAT, &dummy); + sisusb_setreg(sisusb, SISAR, 0x12); + sisusb_setreg(sisusb, SISAR, ch512 ? 0x07 : 0x0f); + + sisusb_getreg(sisusb, SISINPSTAT, &dummy); + sisusb_setreg(sisusb, SISAR, 0x20); + sisusb_getreg(sisusb, SISINPSTAT, &dummy); + } + + if (dorecalc) { + + /* + * Adjust the screen to fit a font of a certain height + */ + + unsigned char ovr, vde, fsr; + int rows = 0, maxscan = 0; + + if (c) { + + /* Number of video rows */ + rows = c->vc_scan_lines / fh; + /* Scan lines to actually display-1 */ + maxscan = rows * fh - 1; + + /*printk(KERN_DEBUG "sisusb recalc rows %d maxscan %d fh %d sl %d\n", + rows, maxscan, fh, c->vc_scan_lines);*/ + + sisusb_getidxreg(sisusb, SISCR, 0x07, &ovr); + vde = maxscan & 0xff; + ovr = (ovr & 0xbd) | + ((maxscan & 0x100) >> 7) | + ((maxscan & 0x200) >> 3); + sisusb_setidxreg(sisusb, SISCR, 0x07, ovr); + sisusb_setidxreg(sisusb, SISCR, 0x12, vde); + + } + + sisusb_getidxreg(sisusb, SISCR, 0x09, &fsr); + fsr = (fsr & 0xe0) | (fh - 1); + sisusb_setidxreg(sisusb, SISCR, 0x09, fsr); + sisusb->current_font_height = fh; + + sisusb->sisusb_cursor_size_from = -1; + sisusb->sisusb_cursor_size_to = -1; + + } + + if (uplock) + mutex_unlock(&sisusb->lock); + + if (dorecalc && c) { + int rows = c->vc_scan_lines / fh; + + /* Now adjust our consoles' size */ + + for (i = 0; i < MAX_NR_CONSOLES; i++) { + struct vc_data *vc = vc_cons[i].d; + + if (vc && vc->vc_sw == &sisusb_con) { + if (CON_IS_VISIBLE(vc)) { + vc->vc_sw->con_cursor(vc, CM_DRAW); + } + vc->vc_font.height = fh; + vc_resize(vc, 0, rows); + } + } + } + + return 0; + +font_op_error: + if (uplock) + mutex_unlock(&sisusb->lock); + + return -EIO; +} + +/* Interface routine */ +static int +sisusbcon_font_set(struct vc_data *c, struct console_font *font, + unsigned flags) +{ + struct sisusb_usb_data *sisusb; + unsigned charcount = font->charcount; + + if (font->width != 8 || (charcount != 256 && charcount != 512)) + return -EINVAL; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return -ENODEV; + + /* sisusb->lock is down */ + + /* Save the user-provided font into a buffer. This + * is used for restoring text mode after quitting + * from X and for the con_getfont routine. + */ + if (sisusb->font_backup) { + if (sisusb->font_backup_size < charcount) { + vfree(sisusb->font_backup); + sisusb->font_backup = NULL; + } + } + + if (!sisusb->font_backup) + sisusb->font_backup = vmalloc(charcount * 32); + + if (sisusb->font_backup) { + memcpy(sisusb->font_backup, font->data, charcount * 32); + sisusb->font_backup_size = charcount; + sisusb->font_backup_height = font->height; + sisusb->font_backup_512 = (charcount == 512) ? 1 : 0; + } + + /* do_font_op ups sisusb->lock */ + + return sisusbcon_do_font_op(sisusb, 1, 2, font->data, + 8192, (charcount == 512), + (!(flags & KD_FONT_FLAG_DONT_RECALC)) ? 1 : 0, + c, font->height, 1); +} + +/* Interface routine */ +static int +sisusbcon_font_get(struct vc_data *c, struct console_font *font) +{ + struct sisusb_usb_data *sisusb; + + if (!(sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num))) + return -ENODEV; + + /* sisusb->lock is down */ + + font->width = 8; + font->height = c->vc_font.height; + font->charcount = 256; + + if (!font->data) { + mutex_unlock(&sisusb->lock); + return 0; + } + + if (!sisusb->font_backup) { + mutex_unlock(&sisusb->lock); + return -ENODEV; + } + + /* Copy 256 chars only, like vgacon */ + memcpy(font->data, sisusb->font_backup, 256 * 32); + + mutex_unlock(&sisusb->lock); + + return 0; +} + +/* + * The console `switch' structure for the sisusb console + */ + +static const struct consw sisusb_con = { + .owner = THIS_MODULE, + .con_startup = sisusbcon_startup, + .con_init = sisusbcon_init, + .con_deinit = sisusbcon_deinit, + .con_clear = sisusbcon_clear, + .con_putc = sisusbcon_putc, + .con_putcs = sisusbcon_putcs, + .con_cursor = sisusbcon_cursor, + .con_scroll = sisusbcon_scroll, + .con_bmove = sisusbcon_bmove, + .con_switch = sisusbcon_switch, + .con_blank = sisusbcon_blank, + .con_font_set = sisusbcon_font_set, + .con_font_get = sisusbcon_font_get, + .con_set_palette = sisusbcon_set_palette, + .con_scrolldelta = sisusbcon_scrolldelta, + .con_build_attr = sisusbcon_build_attr, + .con_invert_region = sisusbcon_invert_region, + .con_set_origin = sisusbcon_set_origin, + .con_save_screen = sisusbcon_save_screen, + .con_resize = sisusbcon_resize, +}; + +/* Our very own dummy console driver */ + +static const char *sisusbdummycon_startup(void) +{ + return "SISUSBVGADUMMY"; +} + +static void sisusbdummycon_init(struct vc_data *vc, int init) +{ + vc->vc_can_do_color = 1; + if (init) { + vc->vc_cols = 80; + vc->vc_rows = 25; + } else + vc_resize(vc, 80, 25); +} + +static int sisusbdummycon_dummy(void) +{ + return 0; +} + +#define SISUSBCONDUMMY (void *)sisusbdummycon_dummy + +static const struct consw sisusb_dummy_con = { + .owner = THIS_MODULE, + .con_startup = sisusbdummycon_startup, + .con_init = sisusbdummycon_init, + .con_deinit = SISUSBCONDUMMY, + .con_clear = SISUSBCONDUMMY, + .con_putc = SISUSBCONDUMMY, + .con_putcs = SISUSBCONDUMMY, + .con_cursor = SISUSBCONDUMMY, + .con_scroll = SISUSBCONDUMMY, + .con_bmove = SISUSBCONDUMMY, + .con_switch = SISUSBCONDUMMY, + .con_blank = SISUSBCONDUMMY, + .con_font_set = SISUSBCONDUMMY, + .con_font_get = SISUSBCONDUMMY, + .con_font_default = SISUSBCONDUMMY, + .con_font_copy = SISUSBCONDUMMY, + .con_set_palette = SISUSBCONDUMMY, + .con_scrolldelta = SISUSBCONDUMMY, +}; + +int +sisusb_console_init(struct sisusb_usb_data *sisusb, int first, int last) +{ + int i, ret; + + mutex_lock(&sisusb->lock); + + /* Erm.. that should not happen */ + if (sisusb->haveconsole || !sisusb->SiS_Pr) { + mutex_unlock(&sisusb->lock); + return 1; + } + + sisusb->con_first = first; + sisusb->con_last = last; + + if (first > last || + first > MAX_NR_CONSOLES || + last > MAX_NR_CONSOLES) { + mutex_unlock(&sisusb->lock); + return 1; + } + + /* If gfxcore not initialized or no consoles given, quit graciously */ + if (!sisusb->gfxinit || first < 1 || last < 1) { + mutex_unlock(&sisusb->lock); + return 0; + } + + sisusb->sisusb_cursor_loc = -1; + sisusb->sisusb_cursor_size_from = -1; + sisusb->sisusb_cursor_size_to = -1; + + /* Set up text mode (and upload default font) */ + if (sisusb_reset_text_mode(sisusb, 1)) { + mutex_unlock(&sisusb->lock); + dev_err(&sisusb->sisusb_dev->dev, "Failed to set up text mode\n"); + return 1; + } + + /* Initialize some gfx registers */ + sisusb_initialize(sisusb); + + for (i = first - 1; i <= last - 1; i++) { + /* Save sisusb for our interface routines */ + mysisusbs[i] = sisusb; + } + + /* Initial console setup */ + sisusb->sisusb_num_columns = 80; + + /* Use a 32K buffer (matches b8000-bffff area) */ + sisusb->scrbuf_size = 32 * 1024; + + /* Allocate screen buffer */ + if (!(sisusb->scrbuf = (unsigned long)vmalloc(sisusb->scrbuf_size))) { + mutex_unlock(&sisusb->lock); + dev_err(&sisusb->sisusb_dev->dev, "Failed to allocate screen buffer\n"); + return 1; + } + + mutex_unlock(&sisusb->lock); + + /* Now grab the desired console(s) */ + ret = take_over_console(&sisusb_con, first - 1, last - 1, 0); + + if (!ret) + sisusb->haveconsole = 1; + else { + for (i = first - 1; i <= last - 1; i++) + mysisusbs[i] = NULL; + } + + return ret; +} + +void +sisusb_console_exit(struct sisusb_usb_data *sisusb) +{ + int i; + + /* This is called if the device is disconnected + * and while disconnect and lock semaphores + * are up. This should be save because we + * can't lose our sisusb any other way but by + * disconnection (and hence, the disconnect + * sema is for protecting all other access + * functions from disconnection, not the + * other way round). + */ + + /* Now what do we do in case of disconnection: + * One alternative would be to simply call + * give_up_console(). Nah, not a good idea. + * give_up_console() is obviously buggy as it + * only discards the consw pointer from the + * driver_map, but doesn't adapt vc->vc_sw + * of the affected consoles. Hence, the next + * call to any of the console functions will + * eventually take a trip to oops county. + * Also, give_up_console for some reason + * doesn't decrement our module refcount. + * Instead, we switch our consoles to a private + * dummy console. This, of course, keeps our + * refcount up as well, but it works perfectly. + */ + + if (sisusb->haveconsole) { + for (i = 0; i < MAX_NR_CONSOLES; i++) + if (sisusb->havethisconsole[i]) + take_over_console(&sisusb_dummy_con, i, i, 0); + /* At this point, con_deinit for all our + * consoles is executed by take_over_console(). + */ + sisusb->haveconsole = 0; + } + + vfree((void *)sisusb->scrbuf); + sisusb->scrbuf = 0; + + vfree(sisusb->font_backup); + sisusb->font_backup = NULL; +} + +void __init sisusb_init_concode(void) +{ + int i; + + for (i = 0; i < MAX_NR_CONSOLES; i++) + mysisusbs[i] = NULL; +} + +#endif /* INCL_CON */ + + + diff --git a/drivers/usb/misc/sisusbvga/sisusb_init.c b/drivers/usb/misc/sisusbvga/sisusb_init.c new file mode 100644 index 00000000..cb8a3d91 --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb_init.c @@ -0,0 +1,959 @@ +/* + * sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles + * + * Display mode initializing code + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, this code is licensed under the + * terms of the GPL v2. + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer + * + */ + +#include +#include +#include +#include +#include +#include + +#include "sisusb.h" + +#ifdef INCL_SISUSB_CON + +#include "sisusb_init.h" + +/*********************************************/ +/* POINTER INITIALIZATION */ +/*********************************************/ + +static void SiSUSB_InitPtr(struct SiS_Private *SiS_Pr) +{ + SiS_Pr->SiS_ModeResInfo = SiSUSB_ModeResInfo; + SiS_Pr->SiS_StandTable = SiSUSB_StandTable; + + SiS_Pr->SiS_SModeIDTable = SiSUSB_SModeIDTable; + SiS_Pr->SiS_EModeIDTable = SiSUSB_EModeIDTable; + SiS_Pr->SiS_RefIndex = SiSUSB_RefIndex; + SiS_Pr->SiS_CRT1Table = SiSUSB_CRT1Table; + + SiS_Pr->SiS_VCLKData = SiSUSB_VCLKData; +} + +/*********************************************/ +/* HELPER: SetReg, GetReg */ +/*********************************************/ + +static void +SiS_SetReg(struct SiS_Private *SiS_Pr, unsigned long port, + unsigned short index, unsigned short data) +{ + sisusb_setidxreg(SiS_Pr->sisusb, port, index, data); +} + +static void +SiS_SetRegByte(struct SiS_Private *SiS_Pr, unsigned long port, + unsigned short data) +{ + sisusb_setreg(SiS_Pr->sisusb, port, data); +} + +static unsigned char +SiS_GetReg(struct SiS_Private *SiS_Pr, unsigned long port, unsigned short index) +{ + u8 data; + + sisusb_getidxreg(SiS_Pr->sisusb, port, index, &data); + + return data; +} + +static unsigned char +SiS_GetRegByte(struct SiS_Private *SiS_Pr, unsigned long port) +{ + u8 data; + + sisusb_getreg(SiS_Pr->sisusb, port, &data); + + return data; +} + +static void +SiS_SetRegANDOR(struct SiS_Private *SiS_Pr, unsigned long port, + unsigned short index, unsigned short DataAND, + unsigned short DataOR) +{ + sisusb_setidxregandor(SiS_Pr->sisusb, port, index, DataAND, DataOR); +} + +static void +SiS_SetRegAND(struct SiS_Private *SiS_Pr, unsigned long port, + unsigned short index, unsigned short DataAND) +{ + sisusb_setidxregand(SiS_Pr->sisusb, port, index, DataAND); +} + +static void +SiS_SetRegOR(struct SiS_Private *SiS_Pr, unsigned long port, + unsigned short index, unsigned short DataOR) +{ + sisusb_setidxregor(SiS_Pr->sisusb, port, index, DataOR); +} + +/*********************************************/ +/* HELPER: DisplayOn, DisplayOff */ +/*********************************************/ + +static void SiS_DisplayOn(struct SiS_Private *SiS_Pr) +{ + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x01, 0xDF); +} + +/*********************************************/ +/* HELPER: Init Port Addresses */ +/*********************************************/ + +static void SiSUSBRegInit(struct SiS_Private *SiS_Pr, unsigned long BaseAddr) +{ + SiS_Pr->SiS_P3c4 = BaseAddr + 0x14; + SiS_Pr->SiS_P3d4 = BaseAddr + 0x24; + SiS_Pr->SiS_P3c0 = BaseAddr + 0x10; + SiS_Pr->SiS_P3ce = BaseAddr + 0x1e; + SiS_Pr->SiS_P3c2 = BaseAddr + 0x12; + SiS_Pr->SiS_P3ca = BaseAddr + 0x1a; + SiS_Pr->SiS_P3c6 = BaseAddr + 0x16; + SiS_Pr->SiS_P3c7 = BaseAddr + 0x17; + SiS_Pr->SiS_P3c8 = BaseAddr + 0x18; + SiS_Pr->SiS_P3c9 = BaseAddr + 0x19; + SiS_Pr->SiS_P3cb = BaseAddr + 0x1b; + SiS_Pr->SiS_P3cc = BaseAddr + 0x1c; + SiS_Pr->SiS_P3cd = BaseAddr + 0x1d; + SiS_Pr->SiS_P3da = BaseAddr + 0x2a; + SiS_Pr->SiS_Part1Port = BaseAddr + SIS_CRT2_PORT_04; +} + +/*********************************************/ +/* HELPER: GetSysFlags */ +/*********************************************/ + +static void SiS_GetSysFlags(struct SiS_Private *SiS_Pr) +{ + SiS_Pr->SiS_MyCR63 = 0x63; +} + +/*********************************************/ +/* HELPER: Init PCI & Engines */ +/*********************************************/ + +static void SiSInitPCIetc(struct SiS_Private *SiS_Pr) +{ + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x20, 0xa1); + /* - Enable 2D (0x40) + * - Enable 3D (0x02) + * - Enable 3D vertex command fetch (0x10) + * - Enable 3D command parser (0x08) + * - Enable 3D G/L transformation engine (0x80) + */ + SiS_SetRegOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x1E, 0xDA); +} + +/*********************************************/ +/* HELPER: SET SEGMENT REGISTERS */ +/*********************************************/ + +static void SiS_SetSegRegLower(struct SiS_Private *SiS_Pr, unsigned short value) +{ + unsigned short temp; + + value &= 0x00ff; + temp = SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3cb) & 0xf0; + temp |= (value >> 4); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3cb, temp); + temp = SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3cd) & 0xf0; + temp |= (value & 0x0f); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3cd, temp); +} + +static void SiS_SetSegRegUpper(struct SiS_Private *SiS_Pr, unsigned short value) +{ + unsigned short temp; + + value &= 0x00ff; + temp = SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3cb) & 0x0f; + temp |= (value & 0xf0); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3cb, temp); + temp = SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3cd) & 0x0f; + temp |= (value << 4); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3cd, temp); +} + +static void SiS_SetSegmentReg(struct SiS_Private *SiS_Pr, unsigned short value) +{ + SiS_SetSegRegLower(SiS_Pr, value); + SiS_SetSegRegUpper(SiS_Pr, value); +} + +static void SiS_ResetSegmentReg(struct SiS_Private *SiS_Pr) +{ + SiS_SetSegmentReg(SiS_Pr, 0); +} + +static void +SiS_SetSegmentRegOver(struct SiS_Private *SiS_Pr, unsigned short value) +{ + unsigned short temp = value >> 8; + + temp &= 0x07; + temp |= (temp << 4); + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x1d, temp); + SiS_SetSegmentReg(SiS_Pr, value); +} + +static void SiS_ResetSegmentRegOver(struct SiS_Private *SiS_Pr) +{ + SiS_SetSegmentRegOver(SiS_Pr, 0); +} + +static void SiS_ResetSegmentRegisters(struct SiS_Private *SiS_Pr) +{ + SiS_ResetSegmentReg(SiS_Pr); + SiS_ResetSegmentRegOver(SiS_Pr); +} + +/*********************************************/ +/* HELPER: SearchModeID */ +/*********************************************/ + +static int +SiS_SearchModeID(struct SiS_Private *SiS_Pr, unsigned short *ModeNo, + unsigned short *ModeIdIndex) +{ + if ((*ModeNo) <= 0x13) { + + if ((*ModeNo) != 0x03) + return 0; + + (*ModeIdIndex) = 0; + + } else { + + for (*ModeIdIndex = 0;; (*ModeIdIndex)++) { + + if (SiS_Pr->SiS_EModeIDTable[*ModeIdIndex].Ext_ModeID == + (*ModeNo)) + break; + + if (SiS_Pr->SiS_EModeIDTable[*ModeIdIndex].Ext_ModeID == + 0xFF) + return 0; + } + + } + + return 1; +} + +/*********************************************/ +/* HELPER: ENABLE CRT1 */ +/*********************************************/ + +static void SiS_HandleCRT1(struct SiS_Private *SiS_Pr) +{ + /* Enable CRT1 gating */ + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3d4, SiS_Pr->SiS_MyCR63, 0xbf); +} + +/*********************************************/ +/* HELPER: GetColorDepth */ +/*********************************************/ + +static unsigned short +SiS_GetColorDepth(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex) +{ + static const unsigned short ColorDepth[6] = { 1, 2, 4, 4, 6, 8 }; + unsigned short modeflag; + short index; + + if (ModeNo <= 0x13) { + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + } else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + } + + index = (modeflag & ModeTypeMask) - ModeEGA; + if (index < 0) + index = 0; + return ColorDepth[index]; +} + +/*********************************************/ +/* HELPER: GetOffset */ +/*********************************************/ + +static unsigned short +SiS_GetOffset(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short rrti) +{ + unsigned short xres, temp, colordepth, infoflag; + + infoflag = SiS_Pr->SiS_RefIndex[rrti].Ext_InfoFlag; + xres = SiS_Pr->SiS_RefIndex[rrti].XRes; + + colordepth = SiS_GetColorDepth(SiS_Pr, ModeNo, ModeIdIndex); + + temp = xres / 16; + + if (infoflag & InterlaceMode) + temp <<= 1; + + temp *= colordepth; + + if (xres % 16) + temp += (colordepth >> 1); + + return temp; +} + +/*********************************************/ +/* SEQ */ +/*********************************************/ + +static void +SiS_SetSeqRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char SRdata; + int i; + + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x00, 0x03); + + SRdata = SiS_Pr->SiS_StandTable[StandTableIndex].SR[0] | 0x20; + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x01, SRdata); + + for (i = 2; i <= 4; i++) { + SRdata = SiS_Pr->SiS_StandTable[StandTableIndex].SR[i - 1]; + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, i, SRdata); + } +} + +/*********************************************/ +/* MISC */ +/*********************************************/ + +static void +SiS_SetMiscRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char Miscdata = SiS_Pr->SiS_StandTable[StandTableIndex].MISC; + + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c2, Miscdata); +} + +/*********************************************/ +/* CRTC */ +/*********************************************/ + +static void +SiS_SetCRTCRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char CRTCdata; + unsigned short i; + + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3d4, 0x11, 0x7f); + + for (i = 0; i <= 0x18; i++) { + CRTCdata = SiS_Pr->SiS_StandTable[StandTableIndex].CRTC[i]; + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, i, CRTCdata); + } +} + +/*********************************************/ +/* ATT */ +/*********************************************/ + +static void +SiS_SetATTRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char ARdata; + unsigned short i; + + for (i = 0; i <= 0x13; i++) { + ARdata = SiS_Pr->SiS_StandTable[StandTableIndex].ATTR[i]; + SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3da); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, i); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, ARdata); + } + SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3da); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, 0x14); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, 0x00); + + SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3da); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, 0x20); + SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3da); +} + +/*********************************************/ +/* GRC */ +/*********************************************/ + +static void +SiS_SetGRCRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex) +{ + unsigned char GRdata; + unsigned short i; + + for (i = 0; i <= 0x08; i++) { + GRdata = SiS_Pr->SiS_StandTable[StandTableIndex].GRC[i]; + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3ce, i, GRdata); + } + + if (SiS_Pr->SiS_ModeType > ModeVGA) { + /* 256 color disable */ + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3ce, 0x05, 0xBF); + } +} + +/*********************************************/ +/* CLEAR EXTENDED REGISTERS */ +/*********************************************/ + +static void SiS_ClearExt1Regs(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + int i; + + for (i = 0x0A; i <= 0x0E; i++) { + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, i, 0x00); + } + + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x37, 0xFE); +} + +/*********************************************/ +/* Get rate index */ +/*********************************************/ + +static unsigned short +SiS_GetRatePtr(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex) +{ + unsigned short rrti, i, index, temp; + + if (ModeNo <= 0x13) + return 0xFFFF; + + index = SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x33) & 0x0F; + if (index > 0) + index--; + + rrti = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].REFindex; + ModeNo = SiS_Pr->SiS_RefIndex[rrti].ModeID; + + i = 0; + do { + if (SiS_Pr->SiS_RefIndex[rrti + i].ModeID != ModeNo) + break; + + temp = + SiS_Pr->SiS_RefIndex[rrti + i].Ext_InfoFlag & ModeTypeMask; + if (temp < SiS_Pr->SiS_ModeType) + break; + + i++; + index--; + } while (index != 0xFFFF); + + i--; + + return (rrti + i); +} + +/*********************************************/ +/* SYNC */ +/*********************************************/ + +static void SiS_SetCRT1Sync(struct SiS_Private *SiS_Pr, unsigned short rrti) +{ + unsigned short sync = SiS_Pr->SiS_RefIndex[rrti].Ext_InfoFlag >> 8; + sync &= 0xC0; + sync |= 0x2f; + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c2, sync); +} + +/*********************************************/ +/* CRTC/2 */ +/*********************************************/ + +static void +SiS_SetCRT1CRTC(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short rrti) +{ + unsigned char index; + unsigned short temp, i, j, modeflag; + + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3d4, 0x11, 0x7f); + + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + + index = SiS_Pr->SiS_RefIndex[rrti].Ext_CRT1CRTC; + + for (i = 0, j = 0; i <= 7; i++, j++) { + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, j, + SiS_Pr->SiS_CRT1Table[index].CR[i]); + } + for (j = 0x10; i <= 10; i++, j++) { + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, j, + SiS_Pr->SiS_CRT1Table[index].CR[i]); + } + for (j = 0x15; i <= 12; i++, j++) { + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, j, + SiS_Pr->SiS_CRT1Table[index].CR[i]); + } + for (j = 0x0A; i <= 15; i++, j++) { + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, j, + SiS_Pr->SiS_CRT1Table[index].CR[i]); + } + + temp = SiS_Pr->SiS_CRT1Table[index].CR[16] & 0xE0; + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0E, temp); + + temp = ((SiS_Pr->SiS_CRT1Table[index].CR[16]) & 0x01) << 5; + if (modeflag & DoubleScanMode) + temp |= 0x80; + SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3d4, 0x09, 0x5F, temp); + + if (SiS_Pr->SiS_ModeType > ModeVGA) + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x14, 0x4F); +} + +/*********************************************/ +/* OFFSET & PITCH */ +/*********************************************/ +/* (partly overruled by SetPitch() in XF86) */ +/*********************************************/ + +static void +SiS_SetCRT1Offset(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short rrti) +{ + unsigned short du = SiS_GetOffset(SiS_Pr, ModeNo, ModeIdIndex, rrti); + unsigned short infoflag = SiS_Pr->SiS_RefIndex[rrti].Ext_InfoFlag; + unsigned short temp; + + temp = (du >> 8) & 0x0f; + SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0E, 0xF0, temp); + + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x13, (du & 0xFF)); + + if (infoflag & InterlaceMode) + du >>= 1; + + du <<= 5; + temp = (du >> 8) & 0xff; + if (du & 0xff) + temp++; + temp++; + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x10, temp); +} + +/*********************************************/ +/* VCLK */ +/*********************************************/ + +static void +SiS_SetCRT1VCLK(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short rrti) +{ + unsigned short index = SiS_Pr->SiS_RefIndex[rrti].Ext_CRTVCLK; + unsigned short clka = SiS_Pr->SiS_VCLKData[index].SR2B; + unsigned short clkb = SiS_Pr->SiS_VCLKData[index].SR2C; + + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x31, 0xCF); + + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x2B, clka); + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x2C, clkb); + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x2D, 0x01); +} + +/*********************************************/ +/* FIFO */ +/*********************************************/ + +static void +SiS_SetCRT1FIFO_310(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short mi) +{ + unsigned short modeflag = SiS_Pr->SiS_EModeIDTable[mi].Ext_ModeFlag; + + /* disable auto-threshold */ + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x3D, 0xFE); + + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x08, 0xAE); + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x09, 0xF0); + + if (ModeNo <= 0x13) + return; + + if ((!(modeflag & DoubleScanMode)) || (!(modeflag & HalfDCLK))) { + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x08, 0x34); + SiS_SetRegOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x3D, 0x01); + } +} + +/*********************************************/ +/* MODE REGISTERS */ +/*********************************************/ + +static void +SiS_SetVCLKState(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short rrti) +{ + unsigned short data = 0, VCLK = 0, index = 0; + + if (ModeNo > 0x13) { + index = SiS_Pr->SiS_RefIndex[rrti].Ext_CRTVCLK; + VCLK = SiS_Pr->SiS_VCLKData[index].CLOCK; + } + + if (VCLK >= 166) + data |= 0x0c; + SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x32, 0xf3, data); + + if (VCLK >= 166) + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x1f, 0xe7); + + /* DAC speed */ + data = 0x03; + if (VCLK >= 260) + data = 0x00; + else if (VCLK >= 160) + data = 0x01; + else if (VCLK >= 135) + data = 0x02; + + SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x07, 0xF8, data); +} + +static void +SiS_SetCRT1ModeRegs(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex, unsigned short rrti) +{ + unsigned short data, infoflag = 0, modeflag; + + if (ModeNo <= 0x13) + modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag; + else { + modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag; + infoflag = SiS_Pr->SiS_RefIndex[rrti].Ext_InfoFlag; + } + + /* Disable DPMS */ + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x1F, 0x3F); + + data = 0; + if (ModeNo > 0x13) { + if (SiS_Pr->SiS_ModeType > ModeEGA) { + data |= 0x02; + data |= ((SiS_Pr->SiS_ModeType - ModeVGA) << 2); + } + if (infoflag & InterlaceMode) + data |= 0x20; + } + SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x06, 0xC0, data); + + data = 0; + if (infoflag & InterlaceMode) { + /* data = (Hsync / 8) - ((Htotal / 8) / 2) + 3 */ + unsigned short hrs = + (SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x04) | + ((SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0b) & 0xc0) << 2)) + - 3; + unsigned short hto = + (SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x00) | + ((SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0b) & 0x03) << 8)) + + 5; + data = hrs - (hto >> 1) + 3; + } + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x19, (data & 0xFF)); + SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3d4, 0x1a, 0xFC, (data >> 8)); + + if (modeflag & HalfDCLK) + SiS_SetRegOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x01, 0x08); + + data = 0; + if (modeflag & LineCompareOff) + data = 0x08; + SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0F, 0xB7, data); + + if ((SiS_Pr->SiS_ModeType == ModeEGA) && (ModeNo > 0x13)) + SiS_SetRegOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0F, 0x40); + + SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x31, 0xfb); + + data = 0x60; + if (SiS_Pr->SiS_ModeType != ModeText) { + data ^= 0x60; + if (SiS_Pr->SiS_ModeType != ModeEGA) + data ^= 0xA0; + } + SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x21, 0x1F, data); + + SiS_SetVCLKState(SiS_Pr, ModeNo, rrti); + + if (SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x31) & 0x40) + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x52, 0x2c); + else + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x52, 0x6c); +} + +/*********************************************/ +/* LOAD DAC */ +/*********************************************/ + +static void +SiS_WriteDAC(struct SiS_Private *SiS_Pr, unsigned long DACData, + unsigned short shiftflag, unsigned short dl, unsigned short ah, + unsigned short al, unsigned short dh) +{ + unsigned short d1, d2, d3; + + switch (dl) { + case 0: + d1 = dh; + d2 = ah; + d3 = al; + break; + case 1: + d1 = ah; + d2 = al; + d3 = dh; + break; + default: + d1 = al; + d2 = dh; + d3 = ah; + } + SiS_SetRegByte(SiS_Pr, DACData, (d1 << shiftflag)); + SiS_SetRegByte(SiS_Pr, DACData, (d2 << shiftflag)); + SiS_SetRegByte(SiS_Pr, DACData, (d3 << shiftflag)); +} + +static void +SiS_LoadDAC(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short mi) +{ + unsigned short data, data2, time, i, j, k, m, n, o; + unsigned short si, di, bx, sf; + unsigned long DACAddr, DACData; + const unsigned char *table = NULL; + + if (ModeNo < 0x13) + data = SiS_Pr->SiS_SModeIDTable[mi].St_ModeFlag; + else + data = SiS_Pr->SiS_EModeIDTable[mi].Ext_ModeFlag; + + data &= DACInfoFlag; + + j = time = 64; + if (data == 0x00) + table = SiS_MDA_DAC; + else if (data == 0x08) + table = SiS_CGA_DAC; + else if (data == 0x10) + table = SiS_EGA_DAC; + else { + j = 16; + time = 256; + table = SiS_VGA_DAC; + } + + DACAddr = SiS_Pr->SiS_P3c8; + DACData = SiS_Pr->SiS_P3c9; + sf = 0; + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c6, 0xFF); + + SiS_SetRegByte(SiS_Pr, DACAddr, 0x00); + + for (i = 0; i < j; i++) { + data = table[i]; + for (k = 0; k < 3; k++) { + data2 = 0; + if (data & 0x01) + data2 += 0x2A; + if (data & 0x02) + data2 += 0x15; + SiS_SetRegByte(SiS_Pr, DACData, (data2 << sf)); + data >>= 2; + } + } + + if (time == 256) { + for (i = 16; i < 32; i++) { + data = table[i] << sf; + for (k = 0; k < 3; k++) + SiS_SetRegByte(SiS_Pr, DACData, data); + } + si = 32; + for (m = 0; m < 9; m++) { + di = si; + bx = si + 4; + for (n = 0; n < 3; n++) { + for (o = 0; o < 5; o++) { + SiS_WriteDAC(SiS_Pr, DACData, sf, n, + table[di], table[bx], + table[si]); + si++; + } + si -= 2; + for (o = 0; o < 3; o++) { + SiS_WriteDAC(SiS_Pr, DACData, sf, n, + table[di], table[si], + table[bx]); + si--; + } + } + si += 5; + } + } +} + +/*********************************************/ +/* SET CRT1 REGISTER GROUP */ +/*********************************************/ + +static void +SiS_SetCRT1Group(struct SiS_Private *SiS_Pr, unsigned short ModeNo, + unsigned short ModeIdIndex) +{ + unsigned short StandTableIndex, rrti; + + SiS_Pr->SiS_CRT1Mode = ModeNo; + + if (ModeNo <= 0x13) + StandTableIndex = 0; + else + StandTableIndex = 1; + + SiS_ResetSegmentRegisters(SiS_Pr); + SiS_SetSeqRegs(SiS_Pr, StandTableIndex); + SiS_SetMiscRegs(SiS_Pr, StandTableIndex); + SiS_SetCRTCRegs(SiS_Pr, StandTableIndex); + SiS_SetATTRegs(SiS_Pr, StandTableIndex); + SiS_SetGRCRegs(SiS_Pr, StandTableIndex); + SiS_ClearExt1Regs(SiS_Pr, ModeNo); + + rrti = SiS_GetRatePtr(SiS_Pr, ModeNo, ModeIdIndex); + + if (rrti != 0xFFFF) { + SiS_SetCRT1Sync(SiS_Pr, rrti); + SiS_SetCRT1CRTC(SiS_Pr, ModeNo, ModeIdIndex, rrti); + SiS_SetCRT1Offset(SiS_Pr, ModeNo, ModeIdIndex, rrti); + SiS_SetCRT1VCLK(SiS_Pr, ModeNo, rrti); + } + + SiS_SetCRT1FIFO_310(SiS_Pr, ModeNo, ModeIdIndex); + + SiS_SetCRT1ModeRegs(SiS_Pr, ModeNo, ModeIdIndex, rrti); + + SiS_LoadDAC(SiS_Pr, ModeNo, ModeIdIndex); + + SiS_DisplayOn(SiS_Pr); +} + +/*********************************************/ +/* SiSSetMode() */ +/*********************************************/ + +int SiSUSBSetMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo) +{ + unsigned short ModeIdIndex; + unsigned long BaseAddr = SiS_Pr->IOAddress; + + SiSUSB_InitPtr(SiS_Pr); + SiSUSBRegInit(SiS_Pr, BaseAddr); + SiS_GetSysFlags(SiS_Pr); + + if (!(SiS_SearchModeID(SiS_Pr, &ModeNo, &ModeIdIndex))) + return 0; + + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x05, 0x86); + + SiSInitPCIetc(SiS_Pr); + + ModeNo &= 0x7f; + + SiS_Pr->SiS_ModeType = + SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag & ModeTypeMask; + + SiS_Pr->SiS_SetFlag = LowModeTests; + + /* Set mode on CRT1 */ + SiS_SetCRT1Group(SiS_Pr, ModeNo, ModeIdIndex); + + SiS_HandleCRT1(SiS_Pr); + + SiS_DisplayOn(SiS_Pr); + SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c6, 0xFF); + + /* Store mode number */ + SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x34, ModeNo); + + return 1; +} + +int SiSUSBSetVESAMode(struct SiS_Private *SiS_Pr, unsigned short VModeNo) +{ + unsigned short ModeNo = 0; + int i; + + SiSUSB_InitPtr(SiS_Pr); + + if (VModeNo == 0x03) { + + ModeNo = 0x03; + + } else { + + i = 0; + do { + + if (SiS_Pr->SiS_EModeIDTable[i].Ext_VESAID == VModeNo) { + ModeNo = SiS_Pr->SiS_EModeIDTable[i].Ext_ModeID; + break; + } + + } while (SiS_Pr->SiS_EModeIDTable[i++].Ext_ModeID != 0xff); + + } + + if (!ModeNo) + return 0; + + return SiSUSBSetMode(SiS_Pr, ModeNo); +} + +#endif /* INCL_SISUSB_CON */ diff --git a/drivers/usb/misc/sisusbvga/sisusb_init.h b/drivers/usb/misc/sisusbvga/sisusb_init.h new file mode 100644 index 00000000..c46ce42d --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb_init.h @@ -0,0 +1,841 @@ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Data and prototypes for init.c + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * This program is free software; you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation; either version 2 of the named License, + * * or any later version. + * * + * * This program is distributed in the hope that 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. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program; if not, write to the Free Software + * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer + * + */ + +#ifndef _SISUSB_INIT_H_ +#define _SISUSB_INIT_H_ + +/* SiS_ModeType */ +#define ModeText 0x00 +#define ModeCGA 0x01 +#define ModeEGA 0x02 +#define ModeVGA 0x03 +#define Mode15Bpp 0x04 +#define Mode16Bpp 0x05 +#define Mode24Bpp 0x06 +#define Mode32Bpp 0x07 + +#define ModeTypeMask 0x07 +#define IsTextMode 0x07 + +#define DACInfoFlag 0x0018 +#define MemoryInfoFlag 0x01E0 +#define MemorySizeShift 5 + +/* modeflag */ +#define Charx8Dot 0x0200 +#define LineCompareOff 0x0400 +#define CRT2Mode 0x0800 +#define HalfDCLK 0x1000 +#define NoSupportSimuTV 0x2000 +#define NoSupportLCDScale 0x4000 /* SiS bridge: No scaling possible (no matter what panel) */ +#define DoubleScanMode 0x8000 + +/* Infoflag */ +#define SupportTV 0x0008 +#define SupportTV1024 0x0800 +#define SupportCHTV 0x0800 +#define Support64048060Hz 0x0800 /* Special for 640x480 LCD */ +#define SupportHiVision 0x0010 +#define SupportYPbPr750p 0x1000 +#define SupportLCD 0x0020 +#define SupportRAMDAC2 0x0040 /* All (<= 100Mhz) */ +#define SupportRAMDAC2_135 0x0100 /* All except DH (<= 135Mhz) */ +#define SupportRAMDAC2_162 0x0200 /* B, C (<= 162Mhz) */ +#define SupportRAMDAC2_202 0x0400 /* C (<= 202Mhz) */ +#define InterlaceMode 0x0080 +#define SyncPP 0x0000 +#define SyncPN 0x4000 +#define SyncNP 0x8000 +#define SyncNN 0xc000 + +/* SetFlag */ +#define ProgrammingCRT2 0x0001 +#define LowModeTests 0x0002 +#define LCDVESATiming 0x0008 +#define EnableLVDSDDA 0x0010 +#define SetDispDevSwitchFlag 0x0020 +#define CheckWinDos 0x0040 +#define SetDOSMode 0x0080 + +/* Index in ModeResInfo table */ +#define SIS_RI_320x200 0 +#define SIS_RI_320x240 1 +#define SIS_RI_320x400 2 +#define SIS_RI_400x300 3 +#define SIS_RI_512x384 4 +#define SIS_RI_640x400 5 +#define SIS_RI_640x480 6 +#define SIS_RI_800x600 7 +#define SIS_RI_1024x768 8 +#define SIS_RI_1280x1024 9 +#define SIS_RI_1600x1200 10 +#define SIS_RI_1920x1440 11 +#define SIS_RI_2048x1536 12 +#define SIS_RI_720x480 13 +#define SIS_RI_720x576 14 +#define SIS_RI_1280x960 15 +#define SIS_RI_800x480 16 +#define SIS_RI_1024x576 17 +#define SIS_RI_1280x720 18 +#define SIS_RI_856x480 19 +#define SIS_RI_1280x768 20 +#define SIS_RI_1400x1050 21 +#define SIS_RI_1152x864 22 /* Up to here SiS conforming */ +#define SIS_RI_848x480 23 +#define SIS_RI_1360x768 24 +#define SIS_RI_1024x600 25 +#define SIS_RI_1152x768 26 +#define SIS_RI_768x576 27 +#define SIS_RI_1360x1024 28 +#define SIS_RI_1680x1050 29 +#define SIS_RI_1280x800 30 +#define SIS_RI_1920x1080 31 +#define SIS_RI_960x540 32 +#define SIS_RI_960x600 33 + +#define SIS_VIDEO_CAPTURE 0x00 - 0x30 +#define SIS_VIDEO_PLAYBACK 0x02 - 0x30 +#define SIS_CRT2_PORT_04 0x04 - 0x30 + +/* Mode numbers */ +static const unsigned short ModeIndex_320x200[] = { 0x59, 0x41, 0x00, 0x4f }; +static const unsigned short ModeIndex_320x240[] = { 0x50, 0x56, 0x00, 0x53 }; +static const unsigned short ModeIndex_400x300[] = { 0x51, 0x57, 0x00, 0x54 }; +static const unsigned short ModeIndex_512x384[] = { 0x52, 0x58, 0x00, 0x5c }; +static const unsigned short ModeIndex_640x400[] = { 0x2f, 0x5d, 0x00, 0x5e }; +static const unsigned short ModeIndex_640x480[] = { 0x2e, 0x44, 0x00, 0x62 }; +static const unsigned short ModeIndex_720x480[] = { 0x31, 0x33, 0x00, 0x35 }; +static const unsigned short ModeIndex_720x576[] = { 0x32, 0x34, 0x00, 0x36 }; +static const unsigned short ModeIndex_768x576[] = { 0x5f, 0x60, 0x00, 0x61 }; +static const unsigned short ModeIndex_800x480[] = { 0x70, 0x7a, 0x00, 0x76 }; +static const unsigned short ModeIndex_800x600[] = { 0x30, 0x47, 0x00, 0x63 }; +static const unsigned short ModeIndex_848x480[] = { 0x39, 0x3b, 0x00, 0x3e }; +static const unsigned short ModeIndex_856x480[] = { 0x3f, 0x42, 0x00, 0x45 }; +static const unsigned short ModeIndex_960x540[] = { 0x1d, 0x1e, 0x00, 0x1f }; +static const unsigned short ModeIndex_960x600[] = { 0x20, 0x21, 0x00, 0x22 }; +static const unsigned short ModeIndex_1024x768[] = { 0x38, 0x4a, 0x00, 0x64 }; +static const unsigned short ModeIndex_1024x576[] = { 0x71, 0x74, 0x00, 0x77 }; +static const unsigned short ModeIndex_1152x864[] = { 0x29, 0x2a, 0x00, 0x2b }; +static const unsigned short ModeIndex_1280x720[] = { 0x79, 0x75, 0x00, 0x78 }; +static const unsigned short ModeIndex_1280x768[] = { 0x23, 0x24, 0x00, 0x25 }; +static const unsigned short ModeIndex_1280x1024[] = { 0x3a, 0x4d, 0x00, 0x65 }; + +static const unsigned char SiS_MDA_DAC[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F +}; + +static const unsigned char SiS_CGA_DAC[] = { + 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15, + 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15, + 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F, + 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F, + 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15, + 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15, + 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F, + 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F +}; + +static const unsigned char SiS_EGA_DAC[] = { + 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x05, 0x15, + 0x20, 0x30, 0x24, 0x34, 0x21, 0x31, 0x25, 0x35, + 0x08, 0x18, 0x0C, 0x1C, 0x09, 0x19, 0x0D, 0x1D, + 0x28, 0x38, 0x2C, 0x3C, 0x29, 0x39, 0x2D, 0x3D, + 0x02, 0x12, 0x06, 0x16, 0x03, 0x13, 0x07, 0x17, + 0x22, 0x32, 0x26, 0x36, 0x23, 0x33, 0x27, 0x37, + 0x0A, 0x1A, 0x0E, 0x1E, 0x0B, 0x1B, 0x0F, 0x1F, + 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F +}; + +static const unsigned char SiS_VGA_DAC[] = { + 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15, + 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F, + 0x00, 0x05, 0x08, 0x0B, 0x0E, 0x11, 0x14, 0x18, + 0x1C, 0x20, 0x24, 0x28, 0x2D, 0x32, 0x38, 0x3F, + 0x00, 0x10, 0x1F, 0x2F, 0x3F, 0x1F, 0x27, 0x2F, + 0x37, 0x3F, 0x2D, 0x31, 0x36, 0x3A, 0x3F, 0x00, + 0x07, 0x0E, 0x15, 0x1C, 0x0E, 0x11, 0x15, 0x18, + 0x1C, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x00, 0x04, + 0x08, 0x0C, 0x10, 0x08, 0x0A, 0x0C, 0x0E, 0x10, + 0x0B, 0x0C, 0x0D, 0x0F, 0x10 +}; + +static const struct SiS_St SiSUSB_SModeIDTable[] = { + {0x03, 0x0010, 0x18, 0x02, 0x02, 0x00, 0x01, 0x03, 0x40}, + {0xff, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +static const struct SiS_StResInfo_S SiSUSB_StResInfo[] = { + {640, 400}, + {640, 350}, + {720, 400}, + {720, 350}, + {640, 480} +}; + +static const struct SiS_ModeResInfo SiSUSB_ModeResInfo[] = { + {320, 200, 8, 8}, /* 0x00 */ + {320, 240, 8, 8}, /* 0x01 */ + {320, 400, 8, 8}, /* 0x02 */ + {400, 300, 8, 8}, /* 0x03 */ + {512, 384, 8, 8}, /* 0x04 */ + {640, 400, 8, 16}, /* 0x05 */ + {640, 480, 8, 16}, /* 0x06 */ + {800, 600, 8, 16}, /* 0x07 */ + {1024, 768, 8, 16}, /* 0x08 */ + {1280, 1024, 8, 16}, /* 0x09 */ + {1600, 1200, 8, 16}, /* 0x0a */ + {1920, 1440, 8, 16}, /* 0x0b */ + {2048, 1536, 8, 16}, /* 0x0c */ + {720, 480, 8, 16}, /* 0x0d */ + {720, 576, 8, 16}, /* 0x0e */ + {1280, 960, 8, 16}, /* 0x0f */ + {800, 480, 8, 16}, /* 0x10 */ + {1024, 576, 8, 16}, /* 0x11 */ + {1280, 720, 8, 16}, /* 0x12 */ + {856, 480, 8, 16}, /* 0x13 */ + {1280, 768, 8, 16}, /* 0x14 */ + {1400, 1050, 8, 16}, /* 0x15 */ + {1152, 864, 8, 16}, /* 0x16 */ + {848, 480, 8, 16}, /* 0x17 */ + {1360, 768, 8, 16}, /* 0x18 */ + {1024, 600, 8, 16}, /* 0x19 */ + {1152, 768, 8, 16}, /* 0x1a */ + {768, 576, 8, 16}, /* 0x1b */ + {1360, 1024, 8, 16}, /* 0x1c */ + {1680, 1050, 8, 16}, /* 0x1d */ + {1280, 800, 8, 16}, /* 0x1e */ + {1920, 1080, 8, 16}, /* 0x1f */ + {960, 540, 8, 16}, /* 0x20 */ + {960, 600, 8, 16} /* 0x21 */ +}; + +static const struct SiS_StandTable SiSUSB_StandTable[] = { + /* MD_3_400 - mode 0x03 - 400 */ + { + 0x50, 0x18, 0x10, 0x1000, + {0x00, 0x03, 0x00, 0x02}, + 0x67, + {0x5f, 0x4f, 0x50, 0x82, 0x55, 0x81, 0xbf, 0x1f, + 0x00, 0x4f, 0x0d, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x9c, 0x8e, 0x8f, 0x28, 0x1f, 0x96, 0xb9, 0xa3, + 0xff}, + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x0c, 0x00, 0x0f, 0x08}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0e, 0x00, 0xff} + }, + /* Generic for VGA and higher */ + { + 0x00, 0x00, 0x00, 0x0000, + {0x01, 0x0f, 0x00, 0x0e}, + 0x23, + {0x5f, 0x4f, 0x50, 0x82, 0x54, 0x80, 0x0b, 0x3e, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xea, 0x8c, 0xdf, 0x28, 0x40, 0xe7, 0x04, 0xa3, + 0xff}, + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x01, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0f, 0xff} + } +}; + +static const struct SiS_Ext SiSUSB_EModeIDTable[] = { + {0x2e, 0x0a1b, 0x0101, SIS_RI_640x480, 0x00, 0x00, 0x05, 0x05, 0x08, 2}, /* 640x480x8 */ + {0x2f, 0x0a1b, 0x0100, SIS_RI_640x400, 0x00, 0x00, 0x05, 0x05, 0x10, 0}, /* 640x400x8 */ + {0x30, 0x2a1b, 0x0103, SIS_RI_800x600, 0x00, 0x00, 0x07, 0x06, 0x00, 3}, /* 800x600x8 */ + {0x31, 0x4a1b, 0x0000, SIS_RI_720x480, 0x00, 0x00, 0x06, 0x06, 0x11, -1}, /* 720x480x8 */ + {0x32, 0x4a1b, 0x0000, SIS_RI_720x576, 0x00, 0x00, 0x06, 0x06, 0x12, -1}, /* 720x576x8 */ + {0x33, 0x4a1d, 0x0000, SIS_RI_720x480, 0x00, 0x00, 0x06, 0x06, 0x11, -1}, /* 720x480x16 */ + {0x34, 0x6a1d, 0x0000, SIS_RI_720x576, 0x00, 0x00, 0x06, 0x06, 0x12, -1}, /* 720x576x16 */ + {0x35, 0x4a1f, 0x0000, SIS_RI_720x480, 0x00, 0x00, 0x06, 0x06, 0x11, -1}, /* 720x480x32 */ + {0x36, 0x6a1f, 0x0000, SIS_RI_720x576, 0x00, 0x00, 0x06, 0x06, 0x12, -1}, /* 720x576x32 */ + {0x38, 0x0a1b, 0x0105, SIS_RI_1024x768, 0x00, 0x00, 0x08, 0x07, 0x13, 4}, /* 1024x768x8 */ + {0x3a, 0x0e3b, 0x0107, SIS_RI_1280x1024, 0x00, 0x00, 0x00, 0x00, 0x2f, 8}, /* 1280x1024x8 */ + {0x41, 0x9a1d, 0x010e, SIS_RI_320x200, 0x00, 0x00, 0x04, 0x04, 0x1a, 0}, /* 320x200x16 */ + {0x44, 0x0a1d, 0x0111, SIS_RI_640x480, 0x00, 0x00, 0x05, 0x05, 0x08, 2}, /* 640x480x16 */ + {0x47, 0x2a1d, 0x0114, SIS_RI_800x600, 0x00, 0x00, 0x07, 0x06, 0x00, 3}, /* 800x600x16 */ + {0x4a, 0x0a3d, 0x0117, SIS_RI_1024x768, 0x00, 0x00, 0x08, 0x07, 0x13, 4}, /* 1024x768x16 */ + {0x4d, 0x0e7d, 0x011a, SIS_RI_1280x1024, 0x00, 0x00, 0x00, 0x00, 0x2f, 8}, /* 1280x1024x16 */ + {0x50, 0x9a1b, 0x0132, SIS_RI_320x240, 0x00, 0x00, 0x04, 0x04, 0x1b, 2}, /* 320x240x8 */ + {0x51, 0xba1b, 0x0133, SIS_RI_400x300, 0x00, 0x00, 0x07, 0x07, 0x1c, 3}, /* 400x300x8 */ + {0x52, 0xba1b, 0x0134, SIS_RI_512x384, 0x00, 0x00, 0x00, 0x00, 0x1d, 4}, /* 512x384x8 */ + {0x56, 0x9a1d, 0x0135, SIS_RI_320x240, 0x00, 0x00, 0x04, 0x04, 0x1b, 2}, /* 320x240x16 */ + {0x57, 0xba1d, 0x0136, SIS_RI_400x300, 0x00, 0x00, 0x07, 0x07, 0x1c, 3}, /* 400x300x16 */ + {0x58, 0xba1d, 0x0137, SIS_RI_512x384, 0x00, 0x00, 0x00, 0x00, 0x1d, 4}, /* 512x384x16 */ + {0x59, 0x9a1b, 0x0138, SIS_RI_320x200, 0x00, 0x00, 0x04, 0x04, 0x1a, 0}, /* 320x200x8 */ + {0x5c, 0xba1f, 0x0000, SIS_RI_512x384, 0x00, 0x00, 0x00, 0x00, 0x1d, 4}, /* 512x384x32 */ + {0x5d, 0x0a1d, 0x0139, SIS_RI_640x400, 0x00, 0x00, 0x05, 0x07, 0x10, 0}, /* 640x400x16 */ + {0x5e, 0x0a1f, 0x0000, SIS_RI_640x400, 0x00, 0x00, 0x05, 0x07, 0x10, 0}, /* 640x400x32 */ + {0x62, 0x0a3f, 0x013a, SIS_RI_640x480, 0x00, 0x00, 0x05, 0x05, 0x08, 2}, /* 640x480x32 */ + {0x63, 0x2a3f, 0x013b, SIS_RI_800x600, 0x00, 0x00, 0x07, 0x06, 0x00, 3}, /* 800x600x32 */ + {0x64, 0x0a7f, 0x013c, SIS_RI_1024x768, 0x00, 0x00, 0x08, 0x07, 0x13, 4}, /* 1024x768x32 */ + {0x65, 0x0eff, 0x013d, SIS_RI_1280x1024, 0x00, 0x00, 0x00, 0x00, 0x2f, 8}, /* 1280x1024x32 */ + {0x70, 0x6a1b, 0x0000, SIS_RI_800x480, 0x00, 0x00, 0x07, 0x07, 0x1e, -1}, /* 800x480x8 */ + {0x71, 0x4a1b, 0x0000, SIS_RI_1024x576, 0x00, 0x00, 0x00, 0x00, 0x21, -1}, /* 1024x576x8 */ + {0x74, 0x4a1d, 0x0000, SIS_RI_1024x576, 0x00, 0x00, 0x00, 0x00, 0x21, -1}, /* 1024x576x16 */ + {0x75, 0x0a3d, 0x0000, SIS_RI_1280x720, 0x00, 0x00, 0x00, 0x00, 0x24, 5}, /* 1280x720x16 */ + {0x76, 0x6a1f, 0x0000, SIS_RI_800x480, 0x00, 0x00, 0x07, 0x07, 0x1e, -1}, /* 800x480x32 */ + {0x77, 0x4a1f, 0x0000, SIS_RI_1024x576, 0x00, 0x00, 0x00, 0x00, 0x21, -1}, /* 1024x576x32 */ + {0x78, 0x0a3f, 0x0000, SIS_RI_1280x720, 0x00, 0x00, 0x00, 0x00, 0x24, 5}, /* 1280x720x32 */ + {0x79, 0x0a3b, 0x0000, SIS_RI_1280x720, 0x00, 0x00, 0x00, 0x00, 0x24, 5}, /* 1280x720x8 */ + {0x7a, 0x6a1d, 0x0000, SIS_RI_800x480, 0x00, 0x00, 0x07, 0x07, 0x1e, -1}, /* 800x480x16 */ + {0x23, 0x0e3b, 0x0000, SIS_RI_1280x768, 0x00, 0x00, 0x00, 0x00, 0x27, 6}, /* 1280x768x8 */ + {0x24, 0x0e7d, 0x0000, SIS_RI_1280x768, 0x00, 0x00, 0x00, 0x00, 0x27, 6}, /* 1280x768x16 */ + {0x25, 0x0eff, 0x0000, SIS_RI_1280x768, 0x00, 0x00, 0x00, 0x00, 0x27, 6}, /* 1280x768x32 */ + {0x39, 0x6a1b, 0x0000, SIS_RI_848x480, 0x00, 0x00, 0x00, 0x00, 0x28, -1}, /* 848x480 */ + {0x3b, 0x6a3d, 0x0000, SIS_RI_848x480, 0x00, 0x00, 0x00, 0x00, 0x28, + -1}, + {0x3e, 0x6a7f, 0x0000, SIS_RI_848x480, 0x00, 0x00, 0x00, 0x00, 0x28, + -1}, + {0x3f, 0x6a1b, 0x0000, SIS_RI_856x480, 0x00, 0x00, 0x00, 0x00, 0x2a, -1}, /* 856x480 */ + {0x42, 0x6a3d, 0x0000, SIS_RI_856x480, 0x00, 0x00, 0x00, 0x00, 0x2a, + -1}, + {0x45, 0x6a7f, 0x0000, SIS_RI_856x480, 0x00, 0x00, 0x00, 0x00, 0x2a, + -1}, + {0x4f, 0x9a1f, 0x0000, SIS_RI_320x200, 0x00, 0x00, 0x04, 0x04, 0x1a, 0}, /* 320x200x32 */ + {0x53, 0x9a1f, 0x0000, SIS_RI_320x240, 0x00, 0x00, 0x04, 0x04, 0x1b, 2}, /* 320x240x32 */ + {0x54, 0xba1f, 0x0000, SIS_RI_400x300, 0x00, 0x00, 0x07, 0x07, 0x1c, 3}, /* 400x300x32 */ + {0x5f, 0x6a1b, 0x0000, SIS_RI_768x576, 0x00, 0x00, 0x06, 0x06, 0x2c, -1}, /* 768x576 */ + {0x60, 0x6a1d, 0x0000, SIS_RI_768x576, 0x00, 0x00, 0x06, 0x06, 0x2c, + -1}, + {0x61, 0x6a3f, 0x0000, SIS_RI_768x576, 0x00, 0x00, 0x06, 0x06, 0x2c, + -1}, + {0x1d, 0x6a1b, 0x0000, SIS_RI_960x540, 0x00, 0x00, 0x00, 0x00, 0x2d, -1}, /* 960x540 */ + {0x1e, 0x6a3d, 0x0000, SIS_RI_960x540, 0x00, 0x00, 0x00, 0x00, 0x2d, + -1}, + {0x1f, 0x6a7f, 0x0000, SIS_RI_960x540, 0x00, 0x00, 0x00, 0x00, 0x2d, + -1}, + {0x20, 0x6a1b, 0x0000, SIS_RI_960x600, 0x00, 0x00, 0x00, 0x00, 0x2e, -1}, /* 960x600 */ + {0x21, 0x6a3d, 0x0000, SIS_RI_960x600, 0x00, 0x00, 0x00, 0x00, 0x2e, + -1}, + {0x22, 0x6a7f, 0x0000, SIS_RI_960x600, 0x00, 0x00, 0x00, 0x00, 0x2e, + -1}, + {0x29, 0x4e1b, 0x0000, SIS_RI_1152x864, 0x00, 0x00, 0x00, 0x00, 0x33, -1}, /* 1152x864 */ + {0x2a, 0x4e3d, 0x0000, SIS_RI_1152x864, 0x00, 0x00, 0x00, 0x00, 0x33, + -1}, + {0x2b, 0x4e7f, 0x0000, SIS_RI_1152x864, 0x00, 0x00, 0x00, 0x00, 0x33, + -1}, + {0xff, 0x0000, 0x0000, 0, 0x00, 0x00, 0x00, 0x00, 0x00, -1} +}; + +static const struct SiS_Ext2 SiSUSB_RefIndex[] = { + {0x085f, 0x0d, 0x03, 0x05, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x0 */ + {0x0067, 0x0e, 0x04, 0x05, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x1 */ + {0x0067, 0x0f, 0x08, 0x48, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x2 */ + {0x0067, 0x10, 0x07, 0x8b, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x3 */ + {0x0047, 0x11, 0x0a, 0x00, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x4 */ + {0x0047, 0x12, 0x0d, 0x00, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x5 */ + {0x0047, 0x13, 0x13, 0x00, 0x05, 0x30, 800, 600, 0x20, 0x00, 0x00}, /* 0x6 */ + {0x0107, 0x14, 0x1c, 0x00, 0x05, 0x30, 800, 600, 0x20, 0x00, 0x00}, /* 0x7 */ + {0xc85f, 0x05, 0x00, 0x04, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0x8 */ + {0xc067, 0x06, 0x02, 0x04, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0x9 */ + {0xc067, 0x07, 0x02, 0x47, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xa */ + {0xc067, 0x08, 0x03, 0x8a, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xb */ + {0xc047, 0x09, 0x05, 0x00, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xc */ + {0xc047, 0x0a, 0x09, 0x00, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xd */ + {0xc047, 0x0b, 0x0e, 0x00, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xe */ + {0xc047, 0x0c, 0x15, 0x00, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xf */ + {0x487f, 0x04, 0x00, 0x00, 0x00, 0x2f, 640, 400, 0x30, 0x55, 0x6e}, /* 0x10 */ + {0xc06f, 0x3c, 0x01, 0x06, 0x13, 0x31, 720, 480, 0x30, 0x00, 0x00}, /* 0x11 */ + {0x006f, 0x3d, 0x6f, 0x06, 0x14, 0x32, 720, 576, 0x30, 0x00, 0x00}, /* 0x12 (6f was 03) */ + {0x0087, 0x15, 0x06, 0x00, 0x06, 0x38, 1024, 768, 0x30, 0x00, 0x00}, /* 0x13 */ + {0xc877, 0x16, 0x0b, 0x06, 0x06, 0x38, 1024, 768, 0x20, 0x00, 0x00}, /* 0x14 */ + {0xc067, 0x17, 0x0f, 0x49, 0x06, 0x38, 1024, 768, 0x20, 0x00, 0x00}, /* 0x15 */ + {0x0067, 0x18, 0x11, 0x00, 0x06, 0x38, 1024, 768, 0x20, 0x00, 0x00}, /* 0x16 */ + {0x0047, 0x19, 0x16, 0x8c, 0x06, 0x38, 1024, 768, 0x20, 0x00, 0x00}, /* 0x17 */ + {0x0107, 0x1a, 0x1b, 0x00, 0x06, 0x38, 1024, 768, 0x10, 0x00, 0x00}, /* 0x18 */ + {0x0107, 0x1b, 0x1f, 0x00, 0x06, 0x38, 1024, 768, 0x10, 0x00, 0x00}, /* 0x19 */ + {0x407f, 0x00, 0x00, 0x00, 0x00, 0x41, 320, 200, 0x30, 0x56, 0x4e}, /* 0x1a */ + {0xc07f, 0x01, 0x00, 0x04, 0x04, 0x50, 320, 240, 0x30, 0x00, 0x00}, /* 0x1b */ + {0x007f, 0x02, 0x04, 0x05, 0x05, 0x51, 400, 300, 0x30, 0x00, 0x00}, /* 0x1c */ + {0xc077, 0x03, 0x0b, 0x06, 0x06, 0x52, 512, 384, 0x30, 0x00, 0x00}, /* 0x1d */ + {0x0077, 0x32, 0x40, 0x08, 0x18, 0x70, 800, 480, 0x30, 0x00, 0x00}, /* 0x1e */ + {0x0047, 0x33, 0x07, 0x08, 0x18, 0x70, 800, 480, 0x30, 0x00, 0x00}, /* 0x1f */ + {0x0047, 0x34, 0x0a, 0x08, 0x18, 0x70, 800, 480, 0x30, 0x00, 0x00}, /* 0x20 */ + {0x0077, 0x35, 0x0b, 0x09, 0x19, 0x71, 1024, 576, 0x30, 0x00, 0x00}, /* 0x21 */ + {0x0047, 0x36, 0x11, 0x09, 0x19, 0x71, 1024, 576, 0x30, 0x00, 0x00}, /* 0x22 */ + {0x0047, 0x37, 0x16, 0x09, 0x19, 0x71, 1024, 576, 0x30, 0x00, 0x00}, /* 0x23 */ + {0x1137, 0x38, 0x19, 0x0a, 0x0c, 0x75, 1280, 720, 0x30, 0x00, 0x00}, /* 0x24 */ + {0x1107, 0x39, 0x1e, 0x0a, 0x0c, 0x75, 1280, 720, 0x30, 0x00, 0x00}, /* 0x25 */ + {0x1307, 0x3a, 0x20, 0x0a, 0x0c, 0x75, 1280, 720, 0x30, 0x00, 0x00}, /* 0x26 */ + {0x0077, 0x42, 0x5b, 0x08, 0x11, 0x23, 1280, 768, 0x30, 0x00, 0x00}, /* 0x27 */ + {0x0087, 0x45, 0x57, 0x00, 0x16, 0x39, 848, 480, 0x30, 0x00, 0x00}, /* 0x28 38Hzi */ + {0xc067, 0x46, 0x55, 0x0b, 0x16, 0x39, 848, 480, 0x30, 0x00, 0x00}, /* 0x29 848x480-60Hz */ + {0x0087, 0x47, 0x57, 0x00, 0x17, 0x3f, 856, 480, 0x30, 0x00, 0x00}, /* 0x2a 856x480-38Hzi */ + {0xc067, 0x48, 0x57, 0x00, 0x17, 0x3f, 856, 480, 0x30, 0x00, 0x00}, /* 0x2b 856x480-60Hz */ + {0x006f, 0x4d, 0x71, 0x06, 0x15, 0x5f, 768, 576, 0x30, 0x00, 0x00}, /* 0x2c 768x576-56Hz */ + {0x0067, 0x52, 0x6a, 0x00, 0x1c, 0x1d, 960, 540, 0x30, 0x00, 0x00}, /* 0x2d 960x540 60Hz */ + {0x0077, 0x53, 0x6b, 0x0b, 0x1d, 0x20, 960, 600, 0x30, 0x00, 0x00}, /* 0x2e 960x600 60Hz */ + {0x0087, 0x1c, 0x11, 0x00, 0x07, 0x3a, 1280, 1024, 0x30, 0x00, 0x00}, /* 0x2f */ + {0x0137, 0x1d, 0x19, 0x07, 0x07, 0x3a, 1280, 1024, 0x00, 0x00, 0x00}, /* 0x30 */ + {0x0107, 0x1e, 0x1e, 0x00, 0x07, 0x3a, 1280, 1024, 0x00, 0x00, 0x00}, /* 0x31 */ + {0x0207, 0x1f, 0x20, 0x00, 0x07, 0x3a, 1280, 1024, 0x00, 0x00, 0x00}, /* 0x32 */ + {0x0127, 0x54, 0x6d, 0x00, 0x1a, 0x29, 1152, 864, 0x30, 0x00, 0x00}, /* 0x33 1152x864-60Hz */ + {0x0127, 0x44, 0x19, 0x00, 0x1a, 0x29, 1152, 864, 0x30, 0x00, 0x00}, /* 0x34 1152x864-75Hz */ + {0x0127, 0x4a, 0x1e, 0x00, 0x1a, 0x29, 1152, 864, 0x30, 0x00, 0x00}, /* 0x35 1152x864-85Hz */ + {0xffff, 0x00, 0x00, 0x00, 0x00, 0x00, 0, 0, 0, 0x00, 0x00} +}; + +static const struct SiS_CRT1Table SiSUSB_CRT1Table[] = { + {{0x2d, 0x27, 0x28, 0x90, 0x2c, 0x80, 0xbf, 0x1f, + 0x9c, 0x8e, 0x8f, 0x96, 0xb9, 0x30, 0x00, 0x00, + 0x00}}, /* 0x0 */ + {{0x2d, 0x27, 0x28, 0x90, 0x2c, 0x80, 0x0b, 0x3e, + 0xe9, 0x8b, 0xdf, 0xe7, 0x04, 0x00, 0x00, 0x00, + 0x00}}, /* 0x1 */ + {{0x3d, 0x31, 0x31, 0x81, 0x37, 0x1f, 0x72, 0xf0, + 0x58, 0x8c, 0x57, 0x57, 0x73, 0x20, 0x00, 0x05, + 0x01}}, /* 0x2 */ + {{0x4f, 0x3f, 0x3f, 0x93, 0x45, 0x0d, 0x24, 0xf5, + 0x02, 0x88, 0xff, 0xff, 0x25, 0x10, 0x00, 0x01, + 0x01}}, /* 0x3 */ + {{0x5f, 0x4f, 0x50, 0x82, 0x55, 0x81, 0xbf, 0x1f, + 0x9c, 0x8e, 0x8f, 0x96, 0xb9, 0x30, 0x00, 0x05, + 0x00}}, /* 0x4 */ + {{0x5f, 0x4f, 0x4f, 0x83, 0x55, 0x81, 0x0b, 0x3e, + 0xe9, 0x8b, 0xdf, 0xe8, 0x0c, 0x00, 0x00, 0x05, + 0x00}}, /* 0x5 */ + {{0x63, 0x4f, 0x4f, 0x87, 0x56, 0x9b, 0x06, 0x3e, + 0xe8, 0x8a, 0xdf, 0xe7, 0x07, 0x00, 0x00, 0x01, + 0x00}}, /* 0x6 */ + {{0x64, 0x4f, 0x4f, 0x88, 0x55, 0x9d, 0xf2, 0x1f, + 0xe0, 0x83, 0xdf, 0xdf, 0xf3, 0x10, 0x00, 0x01, + 0x00}}, /* 0x7 */ + {{0x63, 0x4f, 0x4f, 0x87, 0x5a, 0x81, 0xfb, 0x1f, + 0xe0, 0x83, 0xdf, 0xdf, 0xfc, 0x10, 0x00, 0x05, + 0x00}}, /* 0x8 */ + {{0x65, 0x4f, 0x4f, 0x89, 0x58, 0x80, 0xfb, 0x1f, + 0xe0, 0x83, 0xdf, 0xdf, 0xfc, 0x10, 0x00, 0x05, + 0x61}}, /* 0x9 */ + {{0x65, 0x4f, 0x4f, 0x89, 0x58, 0x80, 0x01, 0x3e, + 0xe0, 0x83, 0xdf, 0xdf, 0x02, 0x00, 0x00, 0x05, + 0x61}}, /* 0xa */ + {{0x67, 0x4f, 0x4f, 0x8b, 0x58, 0x81, 0x0d, 0x3e, + 0xe0, 0x83, 0xdf, 0xdf, 0x0e, 0x00, 0x00, 0x05, + 0x61}}, /* 0xb */ + {{0x65, 0x4f, 0x4f, 0x89, 0x57, 0x9f, 0xfb, 0x1f, + 0xe6, 0x8a, 0xdf, 0xdf, 0xfc, 0x10, 0x00, 0x01, + 0x00}}, /* 0xc */ + {{0x7b, 0x63, 0x63, 0x9f, 0x6a, 0x93, 0x6f, 0xf0, + 0x58, 0x8a, 0x57, 0x57, 0x70, 0x20, 0x00, 0x05, + 0x01}}, /* 0xd */ + {{0x7f, 0x63, 0x63, 0x83, 0x6c, 0x1c, 0x72, 0xf0, + 0x58, 0x8c, 0x57, 0x57, 0x73, 0x20, 0x00, 0x06, + 0x01}}, /* 0xe */ + {{0x7d, 0x63, 0x63, 0x81, 0x6e, 0x1d, 0x98, 0xf0, + 0x7c, 0x82, 0x57, 0x57, 0x99, 0x00, 0x00, 0x06, + 0x01}}, /* 0xf */ + {{0x7f, 0x63, 0x63, 0x83, 0x69, 0x13, 0x6f, 0xf0, + 0x58, 0x8b, 0x57, 0x57, 0x70, 0x20, 0x00, 0x06, + 0x01}}, /* 0x10 */ + {{0x7e, 0x63, 0x63, 0x82, 0x6b, 0x13, 0x75, 0xf0, + 0x58, 0x8b, 0x57, 0x57, 0x76, 0x20, 0x00, 0x06, + 0x01}}, /* 0x11 */ + {{0x81, 0x63, 0x63, 0x85, 0x6d, 0x18, 0x7a, 0xf0, + 0x58, 0x8b, 0x57, 0x57, 0x7b, 0x20, 0x00, 0x06, + 0x61}}, /* 0x12 */ + {{0x83, 0x63, 0x63, 0x87, 0x6e, 0x19, 0x81, 0xf0, + 0x58, 0x8b, 0x57, 0x57, 0x82, 0x20, 0x00, 0x06, + 0x61}}, /* 0x13 */ + {{0x85, 0x63, 0x63, 0x89, 0x6f, 0x1a, 0x91, 0xf0, + 0x58, 0x8b, 0x57, 0x57, 0x92, 0x20, 0x00, 0x06, + 0x61}}, /* 0x14 */ + {{0x99, 0x7f, 0x7f, 0x9d, 0x84, 0x1a, 0x96, 0x1f, + 0x7f, 0x83, 0x7f, 0x7f, 0x97, 0x10, 0x00, 0x02, + 0x00}}, /* 0x15 */ + {{0xa3, 0x7f, 0x7f, 0x87, 0x86, 0x97, 0x24, 0xf5, + 0x02, 0x88, 0xff, 0xff, 0x25, 0x10, 0x00, 0x02, + 0x01}}, /* 0x16 */ + {{0xa1, 0x7f, 0x7f, 0x85, 0x86, 0x97, 0x24, 0xf5, + 0x02, 0x88, 0xff, 0xff, 0x25, 0x10, 0x00, 0x02, + 0x01}}, /* 0x17 */ + {{0x9f, 0x7f, 0x7f, 0x83, 0x85, 0x91, 0x1e, 0xf5, + 0x00, 0x83, 0xff, 0xff, 0x1f, 0x10, 0x00, 0x02, + 0x01}}, /* 0x18 */ + {{0xa7, 0x7f, 0x7f, 0x8b, 0x89, 0x95, 0x26, 0xf5, + 0x00, 0x83, 0xff, 0xff, 0x27, 0x10, 0x00, 0x02, + 0x01}}, /* 0x19 */ + {{0xa9, 0x7f, 0x7f, 0x8d, 0x8c, 0x9a, 0x2c, 0xf5, + 0x00, 0x83, 0xff, 0xff, 0x2d, 0x14, 0x00, 0x02, + 0x62}}, /* 0x1a */ + {{0xab, 0x7f, 0x7f, 0x8f, 0x8d, 0x9b, 0x35, 0xf5, + 0x00, 0x83, 0xff, 0xff, 0x36, 0x14, 0x00, 0x02, + 0x62}}, /* 0x1b */ + {{0xcf, 0x9f, 0x9f, 0x93, 0xb2, 0x01, 0x14, 0xba, + 0x00, 0x83, 0xff, 0xff, 0x15, 0x00, 0x00, 0x03, + 0x00}}, /* 0x1c */ + {{0xce, 0x9f, 0x9f, 0x92, 0xa9, 0x17, 0x28, 0x5a, + 0x00, 0x83, 0xff, 0xff, 0x29, 0x09, 0x00, 0x07, + 0x01}}, /* 0x1d */ + {{0xce, 0x9f, 0x9f, 0x92, 0xa5, 0x17, 0x28, 0x5a, + 0x00, 0x83, 0xff, 0xff, 0x29, 0x09, 0x00, 0x07, + 0x01}}, /* 0x1e */ + {{0xd3, 0x9f, 0x9f, 0x97, 0xab, 0x1f, 0x2e, 0x5a, + 0x00, 0x83, 0xff, 0xff, 0x2f, 0x09, 0x00, 0x07, + 0x01}}, /* 0x1f */ + {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10, + 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04, + 0x00}}, /* 0x20 */ + {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10, + 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04, + 0x00}}, /* 0x21 */ + {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10, + 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04, + 0x00}}, /* 0x22 */ + {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10, + 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04, + 0x00}}, /* 0x23 */ + {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10, + 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04, + 0x00}}, /* 0x24 */ + {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10, + 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04, + 0x00}}, /* 0x25 */ + {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10, + 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04, + 0x00}}, /* 0x26 */ + {{0x40, 0xef, 0xef, 0x84, 0x03, 0x1d, 0xda, 0x1f, + 0xa0, 0x83, 0x9f, 0x9f, 0xdb, 0x1f, 0x41, 0x01, + 0x00}}, /* 0x27 */ + {{0x43, 0xef, 0xef, 0x87, 0x06, 0x00, 0xd4, 0x1f, + 0xa0, 0x83, 0x9f, 0x9f, 0xd5, 0x1f, 0x41, 0x05, + 0x63}}, /* 0x28 */ + {{0x45, 0xef, 0xef, 0x89, 0x07, 0x01, 0xd9, 0x1f, + 0xa0, 0x83, 0x9f, 0x9f, 0xda, 0x1f, 0x41, 0x05, + 0x63}}, /* 0x29 */ + {{0x40, 0xef, 0xef, 0x84, 0x03, 0x1d, 0xda, 0x1f, + 0xa0, 0x83, 0x9f, 0x9f, 0xdb, 0x1f, 0x41, 0x01, + 0x00}}, /* 0x2a */ + {{0x40, 0xef, 0xef, 0x84, 0x03, 0x1d, 0xda, 0x1f, + 0xa0, 0x83, 0x9f, 0x9f, 0xdb, 0x1f, 0x41, 0x01, + 0x00}}, /* 0x2b */ + {{0x40, 0xef, 0xef, 0x84, 0x03, 0x1d, 0xda, 0x1f, + 0xa0, 0x83, 0x9f, 0x9f, 0xdb, 0x1f, 0x41, 0x01, + 0x00}}, /* 0x2c */ + {{0x59, 0xff, 0xff, 0x9d, 0x17, 0x13, 0x33, 0xba, + 0x00, 0x83, 0xff, 0xff, 0x34, 0x0f, 0x41, 0x05, + 0x44}}, /* 0x2d */ + {{0x5b, 0xff, 0xff, 0x9f, 0x18, 0x14, 0x38, 0xba, + 0x00, 0x83, 0xff, 0xff, 0x39, 0x0f, 0x41, 0x05, + 0x44}}, /* 0x2e */ + {{0x5b, 0xff, 0xff, 0x9f, 0x18, 0x14, 0x3d, 0xba, + 0x00, 0x83, 0xff, 0xff, 0x3e, 0x0f, 0x41, 0x05, + 0x44}}, /* 0x2f */ + {{0x5d, 0xff, 0xff, 0x81, 0x19, 0x95, 0x41, 0xba, + 0x00, 0x84, 0xff, 0xff, 0x42, 0x0f, 0x41, 0x05, + 0x44}}, /* 0x30 */ + {{0x55, 0xff, 0xff, 0x99, 0x0d, 0x0c, 0x3e, 0xba, + 0x00, 0x84, 0xff, 0xff, 0x3f, 0x0f, 0x41, 0x05, + 0x00}}, /* 0x31 */ + {{0x7f, 0x63, 0x63, 0x83, 0x6c, 0x1c, 0x72, 0xba, + 0x27, 0x8b, 0xdf, 0xdf, 0x73, 0x00, 0x00, 0x06, + 0x01}}, /* 0x32 */ + {{0x7f, 0x63, 0x63, 0x83, 0x69, 0x13, 0x6f, 0xba, + 0x26, 0x89, 0xdf, 0xdf, 0x6f, 0x00, 0x00, 0x06, + 0x01}}, /* 0x33 */ + {{0x7f, 0x63, 0x63, 0x82, 0x6b, 0x13, 0x75, 0xba, + 0x29, 0x8c, 0xdf, 0xdf, 0x75, 0x00, 0x00, 0x06, + 0x01}}, /* 0x34 */ + {{0xa3, 0x7f, 0x7f, 0x87, 0x86, 0x97, 0x24, 0xf1, + 0xaf, 0x85, 0x3f, 0x3f, 0x25, 0x30, 0x00, 0x02, + 0x01}}, /* 0x35 */ + {{0x9f, 0x7f, 0x7f, 0x83, 0x85, 0x91, 0x1e, 0xf1, + 0xad, 0x81, 0x3f, 0x3f, 0x1f, 0x30, 0x00, 0x02, + 0x01}}, /* 0x36 */ + {{0xa7, 0x7f, 0x7f, 0x88, 0x89, 0x95, 0x26, 0xf1, + 0xb1, 0x85, 0x3f, 0x3f, 0x27, 0x30, 0x00, 0x02, + 0x01}}, /* 0x37 */ + {{0xce, 0x9f, 0x9f, 0x92, 0xa9, 0x17, 0x28, 0xc4, + 0x7a, 0x8e, 0xcf, 0xcf, 0x29, 0x21, 0x00, 0x07, + 0x01}}, /* 0x38 */ + {{0xce, 0x9f, 0x9f, 0x92, 0xa5, 0x17, 0x28, 0xd4, + 0x7a, 0x8e, 0xcf, 0xcf, 0x29, 0x21, 0x00, 0x07, + 0x01}}, /* 0x39 */ + {{0xd3, 0x9f, 0x9f, 0x97, 0xab, 0x1f, 0x2e, 0xd4, + 0x7d, 0x81, 0xcf, 0xcf, 0x2f, 0x21, 0x00, 0x07, + 0x01}}, /* 0x3a */ + {{0xdc, 0x9f, 0x9f, 0x80, 0xaf, 0x9d, 0xe6, 0xff, + 0xc0, 0x83, 0xbf, 0xbf, 0xe7, 0x10, 0x00, 0x07, + 0x01}}, /* 0x3b */ + {{0x6b, 0x59, 0x59, 0x8f, 0x5e, 0x8c, 0x0b, 0x3e, + 0xe9, 0x8b, 0xdf, 0xe7, 0x04, 0x00, 0x00, 0x05, + 0x00}}, /* 0x3c */ + {{0x6d, 0x59, 0x59, 0x91, 0x60, 0x89, 0x53, 0xf0, + 0x41, 0x84, 0x3f, 0x3f, 0x54, 0x00, 0x00, 0x05, + 0x41}}, /* 0x3d */ + {{0x86, 0x6a, 0x6a, 0x8a, 0x74, 0x06, 0x8c, 0x15, + 0x4f, 0x83, 0xef, 0xef, 0x8d, 0x30, 0x00, 0x02, + 0x00}}, /* 0x3e */ + {{0x81, 0x6a, 0x6a, 0x85, 0x70, 0x00, 0x0f, 0x3e, + 0xeb, 0x8e, 0xdf, 0xdf, 0x10, 0x00, 0x00, 0x02, + 0x00}}, /* 0x3f */ + {{0xa3, 0x7f, 0x7f, 0x87, 0x86, 0x97, 0x1e, 0xf1, + 0xae, 0x85, 0x57, 0x57, 0x1f, 0x30, 0x00, 0x02, + 0x01}}, /* 0x40 */ + {{0xa3, 0x7f, 0x7f, 0x87, 0x86, 0x97, 0x24, 0xf5, + 0x02, 0x88, 0xff, 0xff, 0x25, 0x10, 0x00, 0x02, + 0x01}}, /* 0x41 */ + {{0xce, 0x9f, 0x9f, 0x92, 0xa9, 0x17, 0x20, 0xf5, + 0x03, 0x88, 0xff, 0xff, 0x21, 0x10, 0x00, 0x07, + 0x01}}, /* 0x42 */ + {{0xe6, 0xae, 0xae, 0x8a, 0xbd, 0x90, 0x3d, 0x10, + 0x1a, 0x8d, 0x19, 0x19, 0x3e, 0x2f, 0x00, 0x03, + 0x00}}, /* 0x43 */ + {{0xc3, 0x8f, 0x8f, 0x87, 0x9b, 0x0b, 0x82, 0xef, + 0x60, 0x83, 0x5f, 0x5f, 0x83, 0x10, 0x00, 0x07, + 0x01}}, /* 0x44 */ + {{0x86, 0x69, 0x69, 0x8A, 0x74, 0x06, 0x8C, 0x15, + 0x4F, 0x83, 0xEF, 0xEF, 0x8D, 0x30, 0x00, 0x02, + 0x00}}, /* 0x45 */ + {{0x83, 0x69, 0x69, 0x87, 0x6f, 0x1d, 0x03, 0x3E, + 0xE5, 0x8d, 0xDF, 0xe4, 0x04, 0x00, 0x00, 0x06, + 0x00}}, /* 0x46 */ + {{0x86, 0x6A, 0x6A, 0x8A, 0x74, 0x06, 0x8C, 0x15, + 0x4F, 0x83, 0xEF, 0xEF, 0x8D, 0x30, 0x00, 0x02, + 0x00}}, /* 0x47 */ + {{0x81, 0x6A, 0x6A, 0x85, 0x70, 0x00, 0x0F, 0x3E, + 0xEB, 0x8E, 0xDF, 0xDF, 0x10, 0x00, 0x00, 0x02, + 0x00}}, /* 0x48 */ + {{0xdd, 0xa9, 0xa9, 0x81, 0xb4, 0x97, 0x26, 0xfd, + 0x01, 0x8d, 0xff, 0x00, 0x27, 0x10, 0x00, 0x03, + 0x01}}, /* 0x49 */ + {{0xd9, 0x8f, 0x8f, 0x9d, 0xba, 0x0a, 0x8a, 0xff, + 0x60, 0x8b, 0x5f, 0x5f, 0x8b, 0x10, 0x00, 0x03, + 0x01}}, /* 0x4a */ + {{0xea, 0xae, 0xae, 0x8e, 0xba, 0x82, 0x40, 0x10, + 0x1b, 0x87, 0x19, 0x1a, 0x41, 0x0f, 0x00, 0x03, + 0x00}}, /* 0x4b */ + {{0xd3, 0x9f, 0x9f, 0x97, 0xab, 0x1f, 0xf1, 0xff, + 0xc0, 0x83, 0xbf, 0xbf, 0xf2, 0x10, 0x00, 0x07, + 0x01}}, /* 0x4c */ + {{0x75, 0x5f, 0x5f, 0x99, 0x66, 0x90, 0x53, 0xf0, + 0x41, 0x84, 0x3f, 0x3f, 0x54, 0x00, 0x00, 0x05, + 0x41}}, + {{0x2d, 0x27, 0x28, 0x90, 0x2c, 0x80, 0x0b, 0x3e, + 0xe9, 0x8b, 0xdf, 0xe7, 0x04, 0x00, 0x00, 0x00, + 0x00}}, /* 0x4e */ + {{0xcd, 0x9f, 0x9f, 0x91, 0xab, 0x1c, 0x3a, 0xff, + 0x20, 0x83, 0x1f, 0x1f, 0x3b, 0x10, 0x00, 0x07, + 0x21}}, /* 0x4f */ + {{0x15, 0xd1, 0xd1, 0x99, 0xe2, 0x19, 0x3d, 0x10, + 0x1a, 0x8d, 0x19, 0x19, 0x3e, 0x2f, 0x01, 0x0c, + 0x20}}, /* 0x50 */ + {{0x0e, 0xef, 0xef, 0x92, 0xfe, 0x03, 0x30, 0xf0, + 0x1e, 0x83, 0x1b, 0x1c, 0x31, 0x00, 0x01, 0x00, + 0x61}}, /* 0x51 */ + {{0x85, 0x77, 0x77, 0x89, 0x7d, 0x01, 0x31, 0xf0, + 0x1e, 0x84, 0x1b, 0x1c, 0x32, 0x00, 0x00, 0x02, + 0x41}}, /* 0x52 */ + {{0x87, 0x77, 0x77, 0x8b, 0x81, 0x0b, 0x68, 0xf0, + 0x5a, 0x80, 0x57, 0x57, 0x69, 0x00, 0x00, 0x02, + 0x01}}, /* 0x53 */ + {{0xcd, 0x8f, 0x8f, 0x91, 0x9b, 0x1b, 0x7a, 0xff, + 0x64, 0x8c, 0x5f, 0x62, 0x7b, 0x10, 0x00, 0x07, + 0x41}} /* 0x54 */ +}; + +static const struct SiS_VCLKData SiSUSB_VCLKData[] = { + {0x1b, 0xe1, 25}, /* 0x00 */ + {0x4e, 0xe4, 28}, /* 0x01 */ + {0x57, 0xe4, 31}, /* 0x02 */ + {0xc3, 0xc8, 36}, /* 0x03 */ + {0x42, 0xe2, 40}, /* 0x04 */ + {0xfe, 0xcd, 43}, /* 0x05 */ + {0x5d, 0xc4, 44}, /* 0x06 */ + {0x52, 0xe2, 49}, /* 0x07 */ + {0x53, 0xe2, 50}, /* 0x08 */ + {0x74, 0x67, 52}, /* 0x09 */ + {0x6d, 0x66, 56}, /* 0x0a */ + {0x5a, 0x64, 65}, /* 0x0b */ + {0x46, 0x44, 67}, /* 0x0c */ + {0xb1, 0x46, 68}, /* 0x0d */ + {0xd3, 0x4a, 72}, /* 0x0e */ + {0x29, 0x61, 75}, /* 0x0f */ + {0x6e, 0x46, 76}, /* 0x10 */ + {0x2b, 0x61, 78}, /* 0x11 */ + {0x31, 0x42, 79}, /* 0x12 */ + {0xab, 0x44, 83}, /* 0x13 */ + {0x46, 0x25, 84}, /* 0x14 */ + {0x78, 0x29, 86}, /* 0x15 */ + {0x62, 0x44, 94}, /* 0x16 */ + {0x2b, 0x41, 104}, /* 0x17 */ + {0x3a, 0x23, 105}, /* 0x18 */ + {0x70, 0x44, 108}, /* 0x19 */ + {0x3c, 0x23, 109}, /* 0x1a */ + {0x5e, 0x43, 113}, /* 0x1b */ + {0xbc, 0x44, 116}, /* 0x1c */ + {0xe0, 0x46, 132}, /* 0x1d */ + {0x54, 0x42, 135}, /* 0x1e */ + {0xea, 0x2a, 139}, /* 0x1f */ + {0x41, 0x22, 157}, /* 0x20 */ + {0x70, 0x24, 162}, /* 0x21 */ + {0x30, 0x21, 175}, /* 0x22 */ + {0x4e, 0x22, 189}, /* 0x23 */ + {0xde, 0x26, 194}, /* 0x24 */ + {0x62, 0x06, 202}, /* 0x25 */ + {0x3f, 0x03, 229}, /* 0x26 */ + {0xb8, 0x06, 234}, /* 0x27 */ + {0x34, 0x02, 253}, /* 0x28 */ + {0x58, 0x04, 255}, /* 0x29 */ + {0x24, 0x01, 265}, /* 0x2a */ + {0x9b, 0x02, 267}, /* 0x2b */ + {0x70, 0x05, 270}, /* 0x2c */ + {0x25, 0x01, 272}, /* 0x2d */ + {0x9c, 0x02, 277}, /* 0x2e */ + {0x27, 0x01, 286}, /* 0x2f */ + {0x3c, 0x02, 291}, /* 0x30 */ + {0xef, 0x0a, 292}, /* 0x31 */ + {0xf6, 0x0a, 310}, /* 0x32 */ + {0x95, 0x01, 315}, /* 0x33 */ + {0xf0, 0x09, 324}, /* 0x34 */ + {0xfe, 0x0a, 331}, /* 0x35 */ + {0xf3, 0x09, 332}, /* 0x36 */ + {0xea, 0x08, 340}, /* 0x37 */ + {0xe8, 0x07, 376}, /* 0x38 */ + {0xde, 0x06, 389}, /* 0x39 */ + {0x52, 0x2a, 54}, /* 0x3a 301 TV */ + {0x52, 0x6a, 27}, /* 0x3b 301 TV */ + {0x62, 0x24, 70}, /* 0x3c 301 TV */ + {0x62, 0x64, 70}, /* 0x3d 301 TV */ + {0xa8, 0x4c, 30}, /* 0x3e 301 TV */ + {0x20, 0x26, 33}, /* 0x3f 301 TV */ + {0x31, 0xc2, 39}, /* 0x40 */ + {0x60, 0x36, 30}, /* 0x41 Chrontel */ + {0x40, 0x4a, 28}, /* 0x42 Chrontel */ + {0x9f, 0x46, 44}, /* 0x43 Chrontel */ + {0x97, 0x2c, 26}, /* 0x44 */ + {0x44, 0xe4, 25}, /* 0x45 Chrontel */ + {0x7e, 0x32, 47}, /* 0x46 Chrontel */ + {0x8a, 0x24, 31}, /* 0x47 Chrontel */ + {0x97, 0x2c, 26}, /* 0x48 Chrontel */ + {0xce, 0x3c, 39}, /* 0x49 */ + {0x52, 0x4a, 36}, /* 0x4a Chrontel */ + {0x34, 0x61, 95}, /* 0x4b */ + {0x78, 0x27, 108}, /* 0x4c - was 102 */ + {0x66, 0x43, 123}, /* 0x4d Modes 0x26-0x28 (1400x1050) */ + {0x41, 0x4e, 21}, /* 0x4e */ + {0xa1, 0x4a, 29}, /* 0x4f Chrontel */ + {0x19, 0x42, 42}, /* 0x50 */ + {0x54, 0x46, 58}, /* 0x51 Chrontel */ + {0x25, 0x42, 61}, /* 0x52 */ + {0x44, 0x44, 66}, /* 0x53 Chrontel */ + {0x3a, 0x62, 70}, /* 0x54 Chrontel */ + {0x62, 0xc6, 34}, /* 0x55 848x480-60 */ + {0x6a, 0xc6, 37}, /* 0x56 848x480-75 - TEMP */ + {0xbf, 0xc8, 35}, /* 0x57 856x480-38i,60 */ + {0x30, 0x23, 88}, /* 0x58 1360x768-62 (is 60Hz!) */ + {0x52, 0x07, 149}, /* 0x59 1280x960-85 */ + {0x56, 0x07, 156}, /* 0x5a 1400x1050-75 */ + {0x70, 0x29, 81}, /* 0x5b 1280x768 LCD */ + {0x45, 0x25, 83}, /* 0x5c 1280x800 */ + {0x70, 0x0a, 147}, /* 0x5d 1680x1050 */ + {0x70, 0x24, 162}, /* 0x5e 1600x1200 */ + {0x5a, 0x64, 65}, /* 0x5f 1280x720 - temp */ + {0x63, 0x46, 68}, /* 0x60 1280x768_2 */ + {0x31, 0x42, 79}, /* 0x61 1280x768_3 - temp */ + {0, 0, 0}, /* 0x62 - custom (will be filled out at run-time) */ + {0x5a, 0x64, 65}, /* 0x63 1280x720 (LCD LVDS) */ + {0x70, 0x28, 90}, /* 0x64 1152x864@60 */ + {0x41, 0xc4, 32}, /* 0x65 848x480@60 */ + {0x5c, 0xc6, 32}, /* 0x66 856x480@60 */ + {0x76, 0xe7, 27}, /* 0x67 720x480@60 */ + {0x5f, 0xc6, 33}, /* 0x68 720/768x576@60 */ + {0x52, 0x27, 75}, /* 0x69 1920x1080i 60Hz interlaced */ + {0x7c, 0x6b, 38}, /* 0x6a 960x540@60 */ + {0xe3, 0x56, 41}, /* 0x6b 960x600@60 */ + {0x45, 0x25, 83}, /* 0x6c 1280x800 */ + {0x70, 0x28, 90}, /* 0x6d 1152x864@60 */ + {0x15, 0xe1, 20}, /* 0x6e 640x400@60 (fake, not actually used) */ + {0x5f, 0xc6, 33}, /* 0x6f 720x576@60 */ + {0x37, 0x5a, 10}, /* 0x70 320x200@60 (fake, not actually used) */ + {0x2b, 0xc2, 35} /* 0x71 768@576@60 */ +}; + +int SiSUSBSetMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo); +int SiSUSBSetVESAMode(struct SiS_Private *SiS_Pr, unsigned short VModeNo); + +extern int sisusb_setreg(struct sisusb_usb_data *sisusb, int port, u8 data); +extern int sisusb_getreg(struct sisusb_usb_data *sisusb, int port, u8 * data); +extern int sisusb_setidxreg(struct sisusb_usb_data *sisusb, int port, + u8 index, u8 data); +extern int sisusb_getidxreg(struct sisusb_usb_data *sisusb, int port, + u8 index, u8 * data); +extern int sisusb_setidxregandor(struct sisusb_usb_data *sisusb, int port, + u8 idx, u8 myand, u8 myor); +extern int sisusb_setidxregor(struct sisusb_usb_data *sisusb, int port, + u8 index, u8 myor); +extern int sisusb_setidxregand(struct sisusb_usb_data *sisusb, int port, + u8 idx, u8 myand); + +void sisusb_delete(struct kref *kref); +int sisusb_writeb(struct sisusb_usb_data *sisusb, u32 adr, u8 data); +int sisusb_readb(struct sisusb_usb_data *sisusb, u32 adr, u8 * data); +int sisusb_copy_memory(struct sisusb_usb_data *sisusb, char *src, + u32 dest, int length, size_t * bytes_written); +int sisusb_reset_text_mode(struct sisusb_usb_data *sisusb, int init); +int sisusbcon_do_font_op(struct sisusb_usb_data *sisusb, int set, int slot, + u8 * arg, int cmapsz, int ch512, int dorecalc, + struct vc_data *c, int fh, int uplock); +void sisusb_set_cursor(struct sisusb_usb_data *sisusb, unsigned int location); +int sisusb_console_init(struct sisusb_usb_data *sisusb, int first, int last); +void sisusb_console_exit(struct sisusb_usb_data *sisusb); +void sisusb_init_concode(void); + +#endif diff --git a/drivers/usb/misc/sisusbvga/sisusb_struct.h b/drivers/usb/misc/sisusbvga/sisusb_struct.h new file mode 100644 index 00000000..1c4240e8 --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb_struct.h @@ -0,0 +1,161 @@ +/* + * General structure definitions for universal mode switching modules + * + * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria + * + * If distributed as part of the Linux kernel, the following license terms + * apply: + * + * * This program is free software; you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation; either version 2 of the named License, + * * or any later version. + * * + * * This program is distributed in the hope that 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. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program; if not, write to the Free Software + * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA + * + * Otherwise, the following license terms apply: + * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions + * * are met: + * * 1) Redistributions of source code must retain the above copyright + * * notice, this list of conditions and the following disclaimer. + * * 2) Redistributions in binary form must reproduce the above copyright + * * notice, this list of conditions and the following disclaimer in the + * * documentation and/or other materials provided with the distribution. + * * 3) The name of the author may not be used to endorse or promote products + * * derived from this software without specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Thomas Winischhofer + * + */ + +#ifndef _SISUSB_STRUCT_H_ +#define _SISUSB_STRUCT_H_ + +struct SiS_St { + unsigned char St_ModeID; + unsigned short St_ModeFlag; + unsigned char St_StTableIndex; + unsigned char St_CRT2CRTC; + unsigned char St_ResInfo; + unsigned char VB_StTVFlickerIndex; + unsigned char VB_StTVEdgeIndex; + unsigned char VB_StTVYFilterIndex; + unsigned char St_PDC; +}; + +struct SiS_StandTable { + unsigned char CRT_COLS; + unsigned char ROWS; + unsigned char CHAR_HEIGHT; + unsigned short CRT_LEN; + unsigned char SR[4]; + unsigned char MISC; + unsigned char CRTC[0x19]; + unsigned char ATTR[0x14]; + unsigned char GRC[9]; +}; + +struct SiS_StResInfo_S { + unsigned short HTotal; + unsigned short VTotal; +}; + +struct SiS_Ext { + unsigned char Ext_ModeID; + unsigned short Ext_ModeFlag; + unsigned short Ext_VESAID; + unsigned char Ext_RESINFO; + unsigned char VB_ExtTVFlickerIndex; + unsigned char VB_ExtTVEdgeIndex; + unsigned char VB_ExtTVYFilterIndex; + unsigned char VB_ExtTVYFilterIndexROM661; + unsigned char REFindex; + char ROMMODEIDX661; +}; + +struct SiS_Ext2 { + unsigned short Ext_InfoFlag; + unsigned char Ext_CRT1CRTC; + unsigned char Ext_CRTVCLK; + unsigned char Ext_CRT2CRTC; + unsigned char Ext_CRT2CRTC_NS; + unsigned char ModeID; + unsigned short XRes; + unsigned short YRes; + unsigned char Ext_PDC; + unsigned char Ext_FakeCRT2CRTC; + unsigned char Ext_FakeCRT2Clk; +}; + +struct SiS_CRT1Table { + unsigned char CR[17]; +}; + +struct SiS_VCLKData { + unsigned char SR2B, SR2C; + unsigned short CLOCK; +}; + +struct SiS_ModeResInfo { + unsigned short HTotal; + unsigned short VTotal; + unsigned char XChar; + unsigned char YChar; +}; + +struct SiS_Private { + void *sisusb; + + unsigned long IOAddress; + + unsigned long SiS_P3c4; + unsigned long SiS_P3d4; + unsigned long SiS_P3c0; + unsigned long SiS_P3ce; + unsigned long SiS_P3c2; + unsigned long SiS_P3ca; + unsigned long SiS_P3c6; + unsigned long SiS_P3c7; + unsigned long SiS_P3c8; + unsigned long SiS_P3c9; + unsigned long SiS_P3cb; + unsigned long SiS_P3cc; + unsigned long SiS_P3cd; + unsigned long SiS_P3da; + unsigned long SiS_Part1Port; + + unsigned char SiS_MyCR63; + unsigned short SiS_CRT1Mode; + unsigned short SiS_ModeType; + unsigned short SiS_SetFlag; + + const struct SiS_StandTable *SiS_StandTable; + const struct SiS_St *SiS_SModeIDTable; + const struct SiS_Ext *SiS_EModeIDTable; + const struct SiS_Ext2 *SiS_RefIndex; + const struct SiS_CRT1Table *SiS_CRT1Table; + const struct SiS_VCLKData *SiS_VCLKData; + const struct SiS_ModeResInfo *SiS_ModeResInfo; +}; + +#endif diff --git a/drivers/usb/misc/trancevibrator.c b/drivers/usb/misc/trancevibrator.c new file mode 100644 index 00000000..741efed4 --- /dev/null +++ b/drivers/usb/misc/trancevibrator.c @@ -0,0 +1,144 @@ +/* + * PlayStation 2 Trance Vibrator driver + * + * Copyright (C) 2006 Sam Hocevar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Standard include files */ +#include +#include +#include +#include +#include +#include + +/* Version Information */ +#define DRIVER_VERSION "v1.1" +#define DRIVER_AUTHOR "Sam Hocevar, sam@zoy.org" +#define DRIVER_DESC "PlayStation 2 Trance Vibrator driver" + +#define TRANCEVIBRATOR_VENDOR_ID 0x0b49 /* ASCII Corporation */ +#define TRANCEVIBRATOR_PRODUCT_ID 0x064f /* Trance Vibrator */ + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(TRANCEVIBRATOR_VENDOR_ID, TRANCEVIBRATOR_PRODUCT_ID) }, + { }, +}; +MODULE_DEVICE_TABLE (usb, id_table); + +/* Driver-local specific stuff */ +struct trancevibrator { + struct usb_device *udev; + unsigned int speed; +}; + +static ssize_t show_speed(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct trancevibrator *tv = usb_get_intfdata(intf); + + return sprintf(buf, "%d\n", tv->speed); +} + +static ssize_t set_speed(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct trancevibrator *tv = usb_get_intfdata(intf); + int temp, retval, old; + + temp = simple_strtoul(buf, NULL, 10); + if (temp > 255) + temp = 255; + else if (temp < 0) + temp = 0; + old = tv->speed; + tv->speed = temp; + + dev_dbg(&tv->udev->dev, "speed = %d\n", tv->speed); + + /* Set speed */ + retval = usb_control_msg(tv->udev, usb_sndctrlpipe(tv->udev, 0), + 0x01, /* vendor request: set speed */ + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, + tv->speed, /* speed value */ + 0, NULL, 0, USB_CTRL_GET_TIMEOUT); + if (retval) { + tv->speed = old; + dev_dbg(&tv->udev->dev, "retval = %d\n", retval); + return retval; + } + return count; +} + +static DEVICE_ATTR(speed, S_IRUGO | S_IWUSR, show_speed, set_speed); + +static int tv_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct trancevibrator *dev; + int retval; + + dev = kzalloc(sizeof(struct trancevibrator), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + retval = -ENOMEM; + goto error; + } + + dev->udev = usb_get_dev(udev); + usb_set_intfdata(interface, dev); + retval = device_create_file(&interface->dev, &dev_attr_speed); + if (retval) + goto error_create_file; + + return 0; + +error_create_file: + usb_put_dev(udev); + usb_set_intfdata(interface, NULL); +error: + kfree(dev); + return retval; +} + +static void tv_disconnect(struct usb_interface *interface) +{ + struct trancevibrator *dev; + + dev = usb_get_intfdata (interface); + device_remove_file(&interface->dev, &dev_attr_speed); + usb_set_intfdata(interface, NULL); + usb_put_dev(dev->udev); + kfree(dev); +} + +/* USB subsystem object */ +static struct usb_driver tv_driver = { + .name = "trancevibrator", + .probe = tv_probe, + .disconnect = tv_disconnect, + .id_table = id_table, +}; + +module_usb_driver(tv_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c new file mode 100644 index 00000000..d3a1cce1 --- /dev/null +++ b/drivers/usb/misc/usb3503.c @@ -0,0 +1,314 @@ +/* + * Driver for SMSC USB3503 USB 2.0 hub controller driver + * + * Copyright (c) 2012-2013 Dongjin Kim (tobetter@gmail.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB3503_VIDL 0x00 +#define USB3503_VIDM 0x01 +#define USB3503_PIDL 0x02 +#define USB3503_PIDM 0x03 +#define USB3503_DIDL 0x04 +#define USB3503_DIDM 0x05 + +#define USB3503_CFG1 0x06 +#define USB3503_SELF_BUS_PWR (1 << 7) + +#define USB3503_CFG2 0x07 +#define USB3503_CFG3 0x08 +#define USB3503_NRD 0x09 + +#define USB3503_PDS 0x0a +#define USB3503_PORT1 (1 << 1) +#define USB3503_PORT2 (1 << 2) +#define USB3503_PORT3 (1 << 3) + +#define USB3503_SP_ILOCK 0xe7 +#define USB3503_SPILOCK_CONNECT (1 << 1) +#define USB3503_SPILOCK_CONFIG (1 << 0) + +#define USB3503_CFGP 0xee +#define USB3503_CLKSUSP (1 << 7) + +struct usb3503 { + enum usb3503_mode mode; + struct i2c_client *client; + int gpio_intn; + int gpio_reset; + int gpio_connect; +}; + +static int usb3503_write_register(struct i2c_client *client, + char reg, char data) +{ + return i2c_smbus_write_byte_data(client, reg, data); +} + +static int usb3503_read_register(struct i2c_client *client, char reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int usb3503_set_bits(struct i2c_client *client, char reg, char req) +{ + int err; + + err = usb3503_read_register(client, reg); + if (err < 0) + return err; + + err = usb3503_write_register(client, reg, err | req); + if (err < 0) + return err; + + return 0; +} + +static int usb3503_clear_bits(struct i2c_client *client, char reg, char req) +{ + int err; + + err = usb3503_read_register(client, reg); + if (err < 0) + return err; + + err = usb3503_write_register(client, reg, err & ~req); + if (err < 0) + return err; + + return 0; +} + +static int usb3503_reset(int gpio_reset, int state) +{ + if (gpio_is_valid(gpio_reset)) + gpio_set_value(gpio_reset, state); + + /* Wait RefClk when RESET_N is released, otherwise Hub will + * not transition to Hub Communication Stage. + */ + if (state) + msleep(100); + + return 0; +} + +static int usb3503_switch_mode(struct usb3503 *hub, enum usb3503_mode mode) +{ + struct i2c_client *i2c = hub->client; + int err = 0; + + switch (mode) { + case USB3503_MODE_HUB: + usb3503_reset(hub->gpio_reset, 1); + + /* SP_ILOCK: set connect_n, config_n for config */ + err = usb3503_write_register(i2c, USB3503_SP_ILOCK, + (USB3503_SPILOCK_CONNECT + | USB3503_SPILOCK_CONFIG)); + if (err < 0) { + dev_err(&i2c->dev, "SP_ILOCK failed (%d)\n", err); + goto err_hubmode; + } + + /* PDS : Port2,3 Disable For Self Powered Operation */ + err = usb3503_set_bits(i2c, USB3503_PDS, + (USB3503_PORT2 | USB3503_PORT3)); + if (err < 0) { + dev_err(&i2c->dev, "PDS failed (%d)\n", err); + goto err_hubmode; + } + + /* CFG1 : SELF_BUS_PWR -> Self-Powerd operation */ + err = usb3503_set_bits(i2c, USB3503_CFG1, USB3503_SELF_BUS_PWR); + if (err < 0) { + dev_err(&i2c->dev, "CFG1 failed (%d)\n", err); + goto err_hubmode; + } + + /* SP_LOCK: clear connect_n, config_n for hub connect */ + err = usb3503_clear_bits(i2c, USB3503_SP_ILOCK, + (USB3503_SPILOCK_CONNECT + | USB3503_SPILOCK_CONFIG)); + if (err < 0) { + dev_err(&i2c->dev, "SP_ILOCK failed (%d)\n", err); + goto err_hubmode; + } + + hub->mode = mode; + dev_info(&i2c->dev, "switched to HUB mode\n"); + break; + + case USB3503_MODE_STANDBY: + usb3503_reset(hub->gpio_reset, 0); + + hub->mode = mode; + dev_info(&i2c->dev, "switched to STANDBY mode\n"); + break; + + default: + dev_err(&i2c->dev, "unknown mode is request\n"); + err = -EINVAL; + break; + } + +err_hubmode: + return err; +} + +static int usb3503_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct usb3503_platform_data *pdata = i2c->dev.platform_data; + struct device_node *np = i2c->dev.of_node; + struct usb3503 *hub; + int err = -ENOMEM; + u32 mode = USB3503_MODE_UNKNOWN; + + hub = kzalloc(sizeof(struct usb3503), GFP_KERNEL); + if (!hub) { + dev_err(&i2c->dev, "private data alloc fail\n"); + return err; + } + + i2c_set_clientdata(i2c, hub); + hub->client = i2c; + + if (pdata) { + hub->gpio_intn = pdata->gpio_intn; + hub->gpio_connect = pdata->gpio_connect; + hub->gpio_reset = pdata->gpio_reset; + hub->mode = pdata->initial_mode; + } else if (np) { + hub->gpio_intn = of_get_named_gpio(np, "connect-gpios", 0); + if (hub->gpio_intn == -EPROBE_DEFER) + return -EPROBE_DEFER; + hub->gpio_connect = of_get_named_gpio(np, "intn-gpios", 0); + if (hub->gpio_connect == -EPROBE_DEFER) + return -EPROBE_DEFER; + hub->gpio_reset = of_get_named_gpio(np, "reset-gpios", 0); + if (hub->gpio_reset == -EPROBE_DEFER) + return -EPROBE_DEFER; + of_property_read_u32(np, "initial-mode", &mode); + hub->mode = mode; + } + + if (gpio_is_valid(hub->gpio_intn)) { + err = gpio_request_one(hub->gpio_intn, + GPIOF_OUT_INIT_HIGH, "usb3503 intn"); + if (err) { + dev_err(&i2c->dev, + "unable to request GPIO %d as connect pin (%d)\n", + hub->gpio_intn, err); + goto err_out; + } + } + + if (gpio_is_valid(hub->gpio_connect)) { + err = gpio_request_one(hub->gpio_connect, + GPIOF_OUT_INIT_HIGH, "usb3503 connect"); + if (err) { + dev_err(&i2c->dev, + "unable to request GPIO %d as connect pin (%d)\n", + hub->gpio_connect, err); + goto err_gpio_connect; + } + } + + if (gpio_is_valid(hub->gpio_reset)) { + err = gpio_request_one(hub->gpio_reset, + GPIOF_OUT_INIT_LOW, "usb3503 reset"); + if (err) { + dev_err(&i2c->dev, + "unable to request GPIO %d as reset pin (%d)\n", + hub->gpio_reset, err); + goto err_gpio_reset; + } + } + + usb3503_switch_mode(hub, hub->mode); + + dev_info(&i2c->dev, "%s: probed on %s mode\n", __func__, + (hub->mode == USB3503_MODE_HUB) ? "hub" : "standby"); + + return 0; + +err_gpio_reset: + if (gpio_is_valid(hub->gpio_connect)) + gpio_free(hub->gpio_connect); +err_gpio_connect: + if (gpio_is_valid(hub->gpio_intn)) + gpio_free(hub->gpio_intn); +err_out: + kfree(hub); + + return err; +} + +static int usb3503_remove(struct i2c_client *i2c) +{ + struct usb3503 *hub = i2c_get_clientdata(i2c); + + if (gpio_is_valid(hub->gpio_intn)) + gpio_free(hub->gpio_intn); + if (gpio_is_valid(hub->gpio_connect)) + gpio_free(hub->gpio_connect); + if (gpio_is_valid(hub->gpio_reset)) + gpio_free(hub->gpio_reset); + + kfree(hub); + + return 0; +} + +static const struct i2c_device_id usb3503_id[] = { + { USB3503_I2C_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, usb3503_id); + +#ifdef CONFIG_OF +static const struct of_device_id usb3503_of_match[] = { + { .compatible = "smsc,usb3503", }, + {}, +}; +MODULE_DEVICE_TABLE(of, usb3503_of_match); +#endif + +static struct i2c_driver usb3503_driver = { + .driver = { + .name = USB3503_I2C_NAME, + .of_match_table = of_match_ptr(usb3503_of_match), + }, + .probe = usb3503_probe, + .remove = usb3503_remove, + .id_table = usb3503_id, +}; + +module_i2c_driver(usb3503_driver); + +MODULE_AUTHOR("Dongjin Kim "); +MODULE_DESCRIPTION("USB3503 USB HUB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb_u132.h b/drivers/usb/misc/usb_u132.h new file mode 100644 index 00000000..dc2e5a31 --- /dev/null +++ b/drivers/usb/misc/usb_u132.h @@ -0,0 +1,101 @@ +/* +* Common Header File for the Elan Digital Systems U132 adapter +* this file should be included by both the "ftdi-u132" and +* the "u132-hcd" modules. +* +* Copyright(C) 2006 Elan Digital Systems Limited +*(http://www.elandigitalsystems.com) +* +* Author and Maintainer - Tony Olech - Elan Digital Systems +*(tony.olech@elandigitalsystems.com) +* +* This program is free software;you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation, version 2. +* +* +* The driver was written by Tony Olech(tony.olech@elandigitalsystems.com) +* based on various USB client drivers in the 2.6.15 linux kernel +* with constant reference to the 3rd Edition of Linux Device Drivers +* published by O'Reilly +* +* The U132 adapter is a USB to CardBus adapter specifically designed +* for PC cards that contain an OHCI host controller. Typical PC cards +* are the Orange Mobile 3G Option GlobeTrotter Fusion card. +* +* The U132 adapter will *NOT *work with PC cards that do not contain +* an OHCI controller. A simple way to test whether a PC card has an +* OHCI controller as an interface is to insert the PC card directly +* into a laptop(or desktop) with a CardBus slot and if "lspci" shows +* a new USB controller and "lsusb -v" shows a new OHCI Host Controller +* then there is a good chance that the U132 adapter will support the +* PC card.(you also need the specific client driver for the PC card) +* +* Please inform the Author and Maintainer about any PC cards that +* contain OHCI Host Controller and work when directly connected to +* an embedded CardBus slot but do not work when they are connected +* via an ELAN U132 adapter. +* +* The driver consists of two modules, the "ftdi-u132" module is +* a USB client driver that interfaces to the FTDI chip within +* the U132 adapter manufactured by Elan Digital Systems, and the +* "u132-hcd" module is a USB host controller driver that talks +* to the OHCI controller within CardBus card that are inserted +* in the U132 adapter. +* +* The "ftdi-u132" module should be loaded automatically by the +* hot plug system when the U132 adapter is plugged in. The module +* initialises the adapter which mostly consists of synchronising +* the FTDI chip, before continuously polling the adapter to detect +* PC card insertions. As soon as a PC card containing a recognised +* OHCI controller is seen the "ftdi-u132" module explicitly requests +* the kernel to load the "u132-hcd" module. +* +* The "ftdi-u132" module provides the interface to the inserted +* PC card and the "u132-hcd" module uses the API to send and receive +* data. The API features call-backs, so that part of the "u132-hcd" +* module code will run in the context of one of the kernel threads +* of the "ftdi-u132" module. +* +*/ +int ftdi_elan_switch_on_diagnostics(int number); +void ftdi_elan_gone_away(struct platform_device *pdev); +void start_usb_lock_device_tracing(void); +struct u132_platform_data { + u16 vendor; + u16 device; + u8 potpg; + void (*port_power) (struct device *dev, int is_on); + void (*reset) (struct device *dev); +}; +int usb_ftdi_elan_edset_single(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)); +int usb_ftdi_elan_edset_output(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)); +int usb_ftdi_elan_edset_empty(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)); +int usb_ftdi_elan_edset_input(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)); +int usb_ftdi_elan_edset_setup(struct platform_device *pdev, u8 ed_number, + void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)); +int usb_ftdi_elan_edset_flush(struct platform_device *pdev, u8 ed_number, + void *endp); +int usb_ftdi_elan_read_pcimem(struct platform_device *pdev, int mem_offset, + u8 width, u32 *data); +int usb_ftdi_elan_write_pcimem(struct platform_device *pdev, int mem_offset, + u8 width, u32 data); diff --git a/drivers/usb/misc/usblcd.c b/drivers/usb/misc/usblcd.c new file mode 100644 index 00000000..89927bcf --- /dev/null +++ b/drivers/usb/misc/usblcd.c @@ -0,0 +1,461 @@ +/***************************************************************************** + * USBLCD Kernel Driver * + * Version 1.05 * + * (C) 2005 Georges Toth * + * * + * This file is licensed under the GPL. See COPYING in the package. * + * Based on usb-skeleton.c 2.0 by Greg Kroah-Hartman (greg@kroah.com) * + * * + * * + * 28.02.05 Complete rewrite of the original usblcd.c driver, * + * based on usb_skeleton.c. * + * This new driver allows more than one USB-LCD to be connected * + * and controlled, at once * + *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION "USBLCD Driver Version 1.05" + +#define USBLCD_MINOR 144 + +#define IOCTL_GET_HARD_VERSION 1 +#define IOCTL_GET_DRV_VERSION 2 + + +static DEFINE_MUTEX(lcd_mutex); +static const struct usb_device_id id_table[] = { + { .idVendor = 0x10D2, .match_flags = USB_DEVICE_ID_MATCH_VENDOR, }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static DEFINE_MUTEX(open_disc_mutex); + + +struct usb_lcd { + struct usb_device *udev; /* init: probe_lcd */ + struct usb_interface *interface; /* the interface for + this device */ + unsigned char *bulk_in_buffer; /* the buffer to receive + data */ + size_t bulk_in_size; /* the size of the + receive buffer */ + __u8 bulk_in_endpointAddr; /* the address of the + bulk in endpoint */ + __u8 bulk_out_endpointAddr; /* the address of the + bulk out endpoint */ + struct kref kref; + struct semaphore limit_sem; /* to stop writes at + full throttle from + using up all RAM */ + struct usb_anchor submitted; /* URBs to wait for + before suspend */ +}; +#define to_lcd_dev(d) container_of(d, struct usb_lcd, kref) + +#define USB_LCD_CONCURRENT_WRITES 5 + +static struct usb_driver lcd_driver; + + +static void lcd_delete(struct kref *kref) +{ + struct usb_lcd *dev = to_lcd_dev(kref); + + usb_put_dev(dev->udev); + kfree(dev->bulk_in_buffer); + kfree(dev); +} + + +static int lcd_open(struct inode *inode, struct file *file) +{ + struct usb_lcd *dev; + struct usb_interface *interface; + int subminor, r; + + mutex_lock(&lcd_mutex); + subminor = iminor(inode); + + interface = usb_find_interface(&lcd_driver, subminor); + if (!interface) { + mutex_unlock(&lcd_mutex); + printk(KERN_ERR "USBLCD: %s - error, can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + mutex_lock(&open_disc_mutex); + dev = usb_get_intfdata(interface); + if (!dev) { + mutex_unlock(&open_disc_mutex); + mutex_unlock(&lcd_mutex); + return -ENODEV; + } + + /* increment our usage count for the device */ + kref_get(&dev->kref); + mutex_unlock(&open_disc_mutex); + + /* grab a power reference */ + r = usb_autopm_get_interface(interface); + if (r < 0) { + kref_put(&dev->kref, lcd_delete); + mutex_unlock(&lcd_mutex); + return r; + } + + /* save our object in the file's private structure */ + file->private_data = dev; + mutex_unlock(&lcd_mutex); + + return 0; +} + +static int lcd_release(struct inode *inode, struct file *file) +{ + struct usb_lcd *dev; + + dev = file->private_data; + if (dev == NULL) + return -ENODEV; + + /* decrement the count on our device */ + usb_autopm_put_interface(dev->interface); + kref_put(&dev->kref, lcd_delete); + return 0; +} + +static ssize_t lcd_read(struct file *file, char __user * buffer, + size_t count, loff_t *ppos) +{ + struct usb_lcd *dev; + int retval = 0; + int bytes_read; + + dev = file->private_data; + + /* do a blocking bulk read to get data from the device */ + retval = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->bulk_in_endpointAddr), + dev->bulk_in_buffer, + min(dev->bulk_in_size, count), + &bytes_read, 10000); + + /* if the read was successful, copy the data to userspace */ + if (!retval) { + if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read)) + retval = -EFAULT; + else + retval = bytes_read; + } + + return retval; +} + +static long lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usb_lcd *dev; + u16 bcdDevice; + char buf[30]; + + dev = file->private_data; + if (dev == NULL) + return -ENODEV; + + switch (cmd) { + case IOCTL_GET_HARD_VERSION: + mutex_lock(&lcd_mutex); + bcdDevice = le16_to_cpu((dev->udev)->descriptor.bcdDevice); + sprintf(buf, "%1d%1d.%1d%1d", + (bcdDevice & 0xF000)>>12, + (bcdDevice & 0xF00)>>8, + (bcdDevice & 0xF0)>>4, + (bcdDevice & 0xF)); + mutex_unlock(&lcd_mutex); + if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0) + return -EFAULT; + break; + case IOCTL_GET_DRV_VERSION: + sprintf(buf, DRIVER_VERSION); + if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0) + return -EFAULT; + break; + default: + return -ENOTTY; + break; + } + + return 0; +} + +static void lcd_write_bulk_callback(struct urb *urb) +{ + struct usb_lcd *dev; + int status = urb->status; + + dev = urb->context; + + /* sync/async unlink faults aren't errors */ + if (status && + !(status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN)) { + dev_dbg(&dev->interface->dev, + "nonzero write bulk status received: %d\n", status); + } + + /* free up our allocated buffer */ + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + up(&dev->limit_sem); +} + +static ssize_t lcd_write(struct file *file, const char __user * user_buffer, + size_t count, loff_t *ppos) +{ + struct usb_lcd *dev; + int retval = 0, r; + struct urb *urb = NULL; + char *buf = NULL; + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + r = down_interruptible(&dev->limit_sem); + if (r < 0) + return -EINTR; + + /* create a urb, and a buffer for it, and copy the data to the urb */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + retval = -ENOMEM; + goto err_no_buf; + } + + buf = usb_alloc_coherent(dev->udev, count, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + goto error; + } + + if (copy_from_user(buf, user_buffer, count)) { + retval = -EFAULT; + goto error; + } + + /* initialize the urb properly */ + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, + dev->bulk_out_endpointAddr), + buf, count, lcd_write_bulk_callback, dev); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + usb_anchor_urb(urb, &dev->submitted); + + /* send the data out the bulk port */ + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + dev_err(&dev->udev->dev, + "%s - failed submitting write urb, error %d\n", + __func__, retval); + goto error_unanchor; + } + + /* release our reference to this urb, + the USB core will eventually free it entirely */ + usb_free_urb(urb); + +exit: + return count; +error_unanchor: + usb_unanchor_urb(urb); +error: + usb_free_coherent(dev->udev, count, buf, urb->transfer_dma); + usb_free_urb(urb); +err_no_buf: + up(&dev->limit_sem); + return retval; +} + +static const struct file_operations lcd_fops = { + .owner = THIS_MODULE, + .read = lcd_read, + .write = lcd_write, + .open = lcd_open, + .unlocked_ioctl = lcd_ioctl, + .release = lcd_release, + .llseek = noop_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver lcd_class = { + .name = "lcd%d", + .fops = &lcd_fops, + .minor_base = USBLCD_MINOR, +}; + +static int lcd_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_lcd *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + size_t buffer_size; + int i; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + goto error; + } + kref_init(&dev->kref); + sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES); + init_usb_anchor(&dev->submitted); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + if (le16_to_cpu(dev->udev->descriptor.idProduct) != 0x0001) { + dev_warn(&interface->dev, "USBLCD model not supported.\n"); + retval = -ENODEV; + goto error; + } + + /* set up the endpoint information */ + /* use only the first bulk-in and bulk-out endpoints */ + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (!dev->bulk_in_endpointAddr && + usb_endpoint_is_bulk_in(endpoint)) { + /* we found a bulk in endpoint */ + buffer_size = usb_endpoint_maxp(endpoint); + dev->bulk_in_size = buffer_size; + dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + dev_err(&interface->dev, + "Could not allocate bulk_in_buffer\n"); + goto error; + } + } + + if (!dev->bulk_out_endpointAddr && + usb_endpoint_is_bulk_out(endpoint)) { + /* we found a bulk out endpoint */ + dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; + } + } + if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { + dev_err(&interface->dev, + "Could not find both bulk-in and bulk-out endpoints\n"); + goto error; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &lcd_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&interface->dev, + "Not able to get a minor for this device.\n"); + usb_set_intfdata(interface, NULL); + goto error; + } + + i = le16_to_cpu(dev->udev->descriptor.bcdDevice); + + dev_info(&interface->dev, "USBLCD Version %1d%1d.%1d%1d found " + "at address %d\n", (i & 0xF000)>>12, (i & 0xF00)>>8, + (i & 0xF0)>>4, (i & 0xF), dev->udev->devnum); + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "USB LCD device now attached to USBLCD-%d\n", + interface->minor); + return 0; + +error: + if (dev) + kref_put(&dev->kref, lcd_delete); + return retval; +} + +static void lcd_draw_down(struct usb_lcd *dev) +{ + int time; + + time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000); + if (!time) + usb_kill_anchored_urbs(&dev->submitted); +} + +static int lcd_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_lcd *dev = usb_get_intfdata(intf); + + if (!dev) + return 0; + lcd_draw_down(dev); + return 0; +} + +static int lcd_resume(struct usb_interface *intf) +{ + return 0; +} + +static void lcd_disconnect(struct usb_interface *interface) +{ + struct usb_lcd *dev; + int minor = interface->minor; + + mutex_lock(&open_disc_mutex); + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + mutex_unlock(&open_disc_mutex); + + /* give back our minor */ + usb_deregister_dev(interface, &lcd_class); + + /* decrement our usage count */ + kref_put(&dev->kref, lcd_delete); + + dev_info(&interface->dev, "USB LCD #%d now disconnected\n", minor); +} + +static struct usb_driver lcd_driver = { + .name = "usblcd", + .probe = lcd_probe, + .disconnect = lcd_disconnect, + .suspend = lcd_suspend, + .resume = lcd_resume, + .id_table = id_table, + .supports_autosuspend = 1, +}; + +module_usb_driver(lcd_driver); + +MODULE_AUTHOR("Georges Toth "); +MODULE_DESCRIPTION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usbled.c b/drivers/usb/misc/usbled.c new file mode 100644 index 00000000..12d03e7a --- /dev/null +++ b/drivers/usb/misc/usbled.c @@ -0,0 +1,240 @@ +/* + * USB LED driver + * + * Copyright (C) 2004 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include +#include + + +#define DRIVER_AUTHOR "Greg Kroah-Hartman, greg@kroah.com" +#define DRIVER_DESC "USB LED Driver" + +enum led_type { + DELCOM_VISUAL_SIGNAL_INDICATOR, + DREAM_CHEEKY_WEBMAIL_NOTIFIER, +}; + +/* table of devices that work with this driver */ +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x0fc5, 0x1223), + .driver_info = DELCOM_VISUAL_SIGNAL_INDICATOR }, + { USB_DEVICE(0x1d34, 0x0004), + .driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER }, + { USB_DEVICE(0x1d34, 0x000a), + .driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct usb_led { + struct usb_device *udev; + unsigned char blue; + unsigned char red; + unsigned char green; + enum led_type type; +}; + +static void change_color(struct usb_led *led) +{ + int retval = 0; + unsigned char *buffer; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) { + dev_err(&led->udev->dev, "out of memory\n"); + return; + } + + switch (led->type) { + case DELCOM_VISUAL_SIGNAL_INDICATOR: { + unsigned char color = 0x07; + + if (led->blue) + color &= ~0x04; + if (led->red) + color &= ~0x02; + if (led->green) + color &= ~0x01; + dev_dbg(&led->udev->dev, + "blue = %d, red = %d, green = %d, color = %.2x\n", + led->blue, led->red, led->green, color); + + retval = usb_control_msg(led->udev, + usb_sndctrlpipe(led->udev, 0), + 0x12, + 0xc8, + (0x02 * 0x100) + 0x0a, + (0x00 * 0x100) + color, + buffer, + 8, + 2000); + break; + } + + case DREAM_CHEEKY_WEBMAIL_NOTIFIER: + dev_dbg(&led->udev->dev, + "red = %d, green = %d, blue = %d\n", + led->red, led->green, led->blue); + + buffer[0] = led->red; + buffer[1] = led->green; + buffer[2] = led->blue; + buffer[3] = buffer[4] = buffer[5] = 0; + buffer[6] = 0x1a; + buffer[7] = 0x05; + + retval = usb_control_msg(led->udev, + usb_sndctrlpipe(led->udev, 0), + 0x09, + 0x21, + 0x200, + 0, + buffer, + 8, + 2000); + break; + + default: + dev_err(&led->udev->dev, "unknown device type %d\n", led->type); + } + + if (retval) + dev_dbg(&led->udev->dev, "retval = %d\n", retval); + kfree(buffer); +} + +#define show_set(value) \ +static ssize_t show_##value(struct device *dev, struct device_attribute *attr,\ + char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_led *led = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%d\n", led->value); \ +} \ +static ssize_t set_##value(struct device *dev, struct device_attribute *attr,\ + const char *buf, size_t count) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_led *led = usb_get_intfdata(intf); \ + int temp = simple_strtoul(buf, NULL, 10); \ + \ + led->value = temp; \ + change_color(led); \ + return count; \ +} \ +static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, show_##value, set_##value); +show_set(blue); +show_set(red); +show_set(green); + +static int led_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_led *dev = NULL; + int retval = -ENOMEM; + + dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "out of memory\n"); + goto error_mem; + } + + dev->udev = usb_get_dev(udev); + dev->type = id->driver_info; + + usb_set_intfdata(interface, dev); + + retval = device_create_file(&interface->dev, &dev_attr_blue); + if (retval) + goto error; + retval = device_create_file(&interface->dev, &dev_attr_red); + if (retval) + goto error; + retval = device_create_file(&interface->dev, &dev_attr_green); + if (retval) + goto error; + + if (dev->type == DREAM_CHEEKY_WEBMAIL_NOTIFIER) { + unsigned char *enable; + + enable = kmemdup("\x1f\x02\0\x5f\0\0\x1a\x03", 8, GFP_KERNEL); + if (!enable) { + dev_err(&interface->dev, "out of memory\n"); + retval = -ENOMEM; + goto error; + } + + retval = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + 0x09, + 0x21, + 0x200, + 0, + enable, + 8, + 2000); + + kfree(enable); + if (retval != 8) + goto error; + } + + dev_info(&interface->dev, "USB LED device now attached\n"); + return 0; + +error: + device_remove_file(&interface->dev, &dev_attr_blue); + device_remove_file(&interface->dev, &dev_attr_red); + device_remove_file(&interface->dev, &dev_attr_green); + usb_set_intfdata(interface, NULL); + usb_put_dev(dev->udev); + kfree(dev); +error_mem: + return retval; +} + +static void led_disconnect(struct usb_interface *interface) +{ + struct usb_led *dev; + + dev = usb_get_intfdata(interface); + + device_remove_file(&interface->dev, &dev_attr_blue); + device_remove_file(&interface->dev, &dev_attr_red); + device_remove_file(&interface->dev, &dev_attr_green); + + /* first remove the files, then set the pointer to NULL */ + usb_set_intfdata(interface, NULL); + + usb_put_dev(dev->udev); + + kfree(dev); + + dev_info(&interface->dev, "USB LED now disconnected\n"); +} + +static struct usb_driver led_driver = { + .name = "usbled", + .probe = led_probe, + .disconnect = led_disconnect, + .id_table = id_table, +}; + +module_usb_driver(led_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usbsevseg.c b/drivers/usb/misc/usbsevseg.c new file mode 100644 index 00000000..b2d82b93 --- /dev/null +++ b/drivers/usb/misc/usbsevseg.c @@ -0,0 +1,444 @@ +/* + * USB 7 Segment Driver + * + * Copyright (C) 2008 Harrison Metzger + * Based on usbled.c by Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include +#include +#include + + +#define DRIVER_AUTHOR "Harrison Metzger " +#define DRIVER_DESC "USB 7 Segment Driver" + +#define VENDOR_ID 0x0fc5 +#define PRODUCT_ID 0x1227 +#define MAXLEN 8 + +/* table of devices that work with this driver */ +static const struct usb_device_id id_table[] = { + { USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* the different text display modes the device is capable of */ +static char *display_textmodes[] = {"raw", "hex", "ascii", NULL}; + +struct usb_sevsegdev { + struct usb_device *udev; + struct usb_interface *intf; + + u8 powered; + u8 mode_msb; + u8 mode_lsb; + u8 decimals[MAXLEN]; + u8 textmode; + u8 text[MAXLEN]; + u16 textlength; + + u8 shadow_power; /* for PM */ + u8 has_interface_pm; +}; + +/* sysfs_streq can't replace this completely + * If the device was in hex mode, and the user wanted a 0, + * if str commands are used, we would assume the end of string + * so mem commands are used. + */ +inline size_t my_memlen(const char *buf, size_t count) +{ + if (count > 0 && buf[count-1] == '\n') + return count - 1; + else + return count; +} + +static void update_display_powered(struct usb_sevsegdev *mydev) +{ + int rc; + + if (mydev->powered && !mydev->has_interface_pm) { + rc = usb_autopm_get_interface(mydev->intf); + if (rc < 0) + return; + mydev->has_interface_pm = 1; + } + + if (mydev->shadow_power != 1) + return; + + rc = usb_control_msg(mydev->udev, + usb_sndctrlpipe(mydev->udev, 0), + 0x12, + 0x48, + (80 * 0x100) + 10, /* (power mode) */ + (0x00 * 0x100) + (mydev->powered ? 1 : 0), + NULL, + 0, + 2000); + if (rc < 0) + dev_dbg(&mydev->udev->dev, "power retval = %d\n", rc); + + if (!mydev->powered && mydev->has_interface_pm) { + usb_autopm_put_interface(mydev->intf); + mydev->has_interface_pm = 0; + } +} + +static void update_display_mode(struct usb_sevsegdev *mydev) +{ + int rc; + + if(mydev->shadow_power != 1) + return; + + rc = usb_control_msg(mydev->udev, + usb_sndctrlpipe(mydev->udev, 0), + 0x12, + 0x48, + (82 * 0x100) + 10, /* (set mode) */ + (mydev->mode_msb * 0x100) + mydev->mode_lsb, + NULL, + 0, + 2000); + + if (rc < 0) + dev_dbg(&mydev->udev->dev, "mode retval = %d\n", rc); +} + +static void update_display_visual(struct usb_sevsegdev *mydev, gfp_t mf) +{ + int rc; + int i; + unsigned char *buffer; + u8 decimals = 0; + + if(mydev->shadow_power != 1) + return; + + buffer = kzalloc(MAXLEN, mf); + if (!buffer) { + dev_err(&mydev->udev->dev, "out of memory\n"); + return; + } + + /* The device is right to left, where as you write left to right */ + for (i = 0; i < mydev->textlength; i++) + buffer[i] = mydev->text[mydev->textlength-1-i]; + + rc = usb_control_msg(mydev->udev, + usb_sndctrlpipe(mydev->udev, 0), + 0x12, + 0x48, + (85 * 0x100) + 10, /* (write text) */ + (0 * 0x100) + mydev->textmode, /* mode */ + buffer, + mydev->textlength, + 2000); + + if (rc < 0) + dev_dbg(&mydev->udev->dev, "write retval = %d\n", rc); + + kfree(buffer); + + /* The device is right to left, where as you write left to right */ + for (i = 0; i < sizeof(mydev->decimals); i++) + decimals |= mydev->decimals[i] << i; + + rc = usb_control_msg(mydev->udev, + usb_sndctrlpipe(mydev->udev, 0), + 0x12, + 0x48, + (86 * 0x100) + 10, /* (set decimal) */ + (0 * 0x100) + decimals, /* decimals */ + NULL, + 0, + 2000); + + if (rc < 0) + dev_dbg(&mydev->udev->dev, "decimal retval = %d\n", rc); +} + +#define MYDEV_ATTR_SIMPLE_UNSIGNED(name, update_fcn) \ +static ssize_t show_attr_##name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%u\n", mydev->name); \ +} \ + \ +static ssize_t set_attr_##name(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); \ + \ + mydev->name = simple_strtoul(buf, NULL, 10); \ + update_fcn(mydev); \ + \ + return count; \ +} \ +static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, show_attr_##name, set_attr_##name); + +static ssize_t show_attr_text(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + + return snprintf(buf, mydev->textlength, "%s\n", mydev->text); +} + +static ssize_t set_attr_text(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + size_t end = my_memlen(buf, count); + + if (end > sizeof(mydev->text)) + return -EINVAL; + + memset(mydev->text, 0, sizeof(mydev->text)); + mydev->textlength = end; + + if (end > 0) + memcpy(mydev->text, buf, end); + + update_display_visual(mydev, GFP_KERNEL); + return count; +} + +static DEVICE_ATTR(text, S_IRUGO | S_IWUSR, show_attr_text, set_attr_text); + +static ssize_t show_attr_decimals(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + int i; + int pos; + + for (i = 0; i < sizeof(mydev->decimals); i++) { + pos = sizeof(mydev->decimals) - 1 - i; + if (mydev->decimals[i] == 0) + buf[pos] = '0'; + else if (mydev->decimals[i] == 1) + buf[pos] = '1'; + else + buf[pos] = 'x'; + } + + buf[sizeof(mydev->decimals)] = '\n'; + return sizeof(mydev->decimals) + 1; +} + +static ssize_t set_attr_decimals(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + size_t end = my_memlen(buf, count); + int i; + + if (end > sizeof(mydev->decimals)) + return -EINVAL; + + for (i = 0; i < end; i++) + if (buf[i] != '0' && buf[i] != '1') + return -EINVAL; + + memset(mydev->decimals, 0, sizeof(mydev->decimals)); + for (i = 0; i < end; i++) + if (buf[i] == '1') + mydev->decimals[end-1-i] = 1; + + update_display_visual(mydev, GFP_KERNEL); + + return count; +} + +static DEVICE_ATTR(decimals, S_IRUGO | S_IWUSR, show_attr_decimals, set_attr_decimals); + +static ssize_t show_attr_textmode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + int i; + + buf[0] = 0; + + for (i = 0; display_textmodes[i]; i++) { + if (mydev->textmode == i) { + strcat(buf, " ["); + strcat(buf, display_textmodes[i]); + strcat(buf, "] "); + } else { + strcat(buf, " "); + strcat(buf, display_textmodes[i]); + strcat(buf, " "); + } + } + strcat(buf, "\n"); + + + return strlen(buf); +} + +static ssize_t set_attr_textmode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + int i; + + for (i = 0; display_textmodes[i]; i++) { + if (sysfs_streq(display_textmodes[i], buf)) { + mydev->textmode = i; + update_display_visual(mydev, GFP_KERNEL); + return count; + } + } + + return -EINVAL; +} + +static DEVICE_ATTR(textmode, S_IRUGO | S_IWUSR, show_attr_textmode, set_attr_textmode); + + +MYDEV_ATTR_SIMPLE_UNSIGNED(powered, update_display_powered); +MYDEV_ATTR_SIMPLE_UNSIGNED(mode_msb, update_display_mode); +MYDEV_ATTR_SIMPLE_UNSIGNED(mode_lsb, update_display_mode); + +static struct attribute *dev_attrs[] = { + &dev_attr_powered.attr, + &dev_attr_text.attr, + &dev_attr_textmode.attr, + &dev_attr_decimals.attr, + &dev_attr_mode_msb.attr, + &dev_attr_mode_lsb.attr, + NULL +}; + +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; + +static int sevseg_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_sevsegdev *mydev = NULL; + int rc = -ENOMEM; + + mydev = kzalloc(sizeof(struct usb_sevsegdev), GFP_KERNEL); + if (mydev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + goto error_mem; + } + + mydev->udev = usb_get_dev(udev); + mydev->intf = interface; + usb_set_intfdata(interface, mydev); + + /* PM */ + mydev->shadow_power = 1; /* currently active */ + mydev->has_interface_pm = 0; /* have not issued autopm_get */ + + /*set defaults */ + mydev->textmode = 0x02; /* ascii mode */ + mydev->mode_msb = 0x06; /* 6 characters */ + mydev->mode_lsb = 0x3f; /* scanmode for 6 chars */ + + rc = sysfs_create_group(&interface->dev.kobj, &dev_attr_grp); + if (rc) + goto error; + + dev_info(&interface->dev, "USB 7 Segment device now attached\n"); + return 0; + +error: + usb_set_intfdata(interface, NULL); + usb_put_dev(mydev->udev); + kfree(mydev); +error_mem: + return rc; +} + +static void sevseg_disconnect(struct usb_interface *interface) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(interface); + sysfs_remove_group(&interface->dev.kobj, &dev_attr_grp); + usb_set_intfdata(interface, NULL); + usb_put_dev(mydev->udev); + kfree(mydev); + dev_info(&interface->dev, "USB 7 Segment now disconnected\n"); +} + +static int sevseg_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(intf); + mydev->shadow_power = 0; + + return 0; +} + +static int sevseg_resume(struct usb_interface *intf) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(intf); + mydev->shadow_power = 1; + update_display_mode(mydev); + update_display_visual(mydev, GFP_NOIO); + + return 0; +} + +static int sevseg_reset_resume(struct usb_interface *intf) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(intf); + mydev->shadow_power = 1; + update_display_mode(mydev); + update_display_visual(mydev, GFP_NOIO); + + return 0; +} + +static struct usb_driver sevseg_driver = { + .name = "usbsevseg", + .probe = sevseg_probe, + .disconnect = sevseg_disconnect, + .suspend = sevseg_suspend, + .resume = sevseg_resume, + .reset_resume = sevseg_reset_resume, + .id_table = id_table, + .supports_autosuspend = 1, +}; + +module_usb_driver(sevseg_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c new file mode 100644 index 00000000..98438b90 --- /dev/null +++ b/drivers/usb/misc/usbtest.c @@ -0,0 +1,2576 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIMPLE_IO_TIMEOUT 10000 /* in milliseconds */ + +/*-------------------------------------------------------------------------*/ + +static int override_alt = -1; +module_param_named(alt, override_alt, int, 0644); +MODULE_PARM_DESC(alt, ">= 0 to override altsetting selection"); + +/*-------------------------------------------------------------------------*/ + +/* FIXME make these public somewhere; usbdevfs.h? */ +struct usbtest_param { + /* inputs */ + unsigned test_num; /* 0..(TEST_CASES-1) */ + unsigned iterations; + unsigned length; + unsigned vary; + unsigned sglen; + + /* outputs */ + struct timeval duration; +}; +#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param) + +/*-------------------------------------------------------------------------*/ + +#define GENERIC /* let probe() bind using module params */ + +/* Some devices that can be used for testing will have "real" drivers. + * Entries for those need to be enabled here by hand, after disabling + * that "real" driver. + */ +//#define IBOT2 /* grab iBOT2 webcams */ +//#define KEYSPAN_19Qi /* grab un-renumerated serial adapter */ + +/*-------------------------------------------------------------------------*/ + +struct usbtest_info { + const char *name; + u8 ep_in; /* bulk/intr source */ + u8 ep_out; /* bulk/intr sink */ + unsigned autoconf:1; + unsigned ctrl_out:1; + unsigned iso:1; /* try iso in/out */ + int alt; +}; + +/* this is accessed only through usbfs ioctl calls. + * one ioctl to issue a test ... one lock per device. + * tests create other threads if they need them. + * urbs and buffers are allocated dynamically, + * and data generated deterministically. + */ +struct usbtest_dev { + struct usb_interface *intf; + struct usbtest_info *info; + int in_pipe; + int out_pipe; + int in_iso_pipe; + int out_iso_pipe; + struct usb_endpoint_descriptor *iso_in, *iso_out; + struct mutex lock; + +#define TBUF_SIZE 256 + u8 *buf; +}; + +static struct usb_device *testdev_to_usbdev(struct usbtest_dev *test) +{ + return interface_to_usbdev(test->intf); +} + +/* set up all urbs so they can be used with either bulk or interrupt */ +#define INTERRUPT_RATE 1 /* msec/transfer */ + +#define ERROR(tdev, fmt, args...) \ + dev_err(&(tdev)->intf->dev , fmt , ## args) +#define WARNING(tdev, fmt, args...) \ + dev_warn(&(tdev)->intf->dev , fmt , ## args) + +#define GUARD_BYTE 0xA5 + +/*-------------------------------------------------------------------------*/ + +static int +get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf) +{ + int tmp; + struct usb_host_interface *alt; + struct usb_host_endpoint *in, *out; + struct usb_host_endpoint *iso_in, *iso_out; + struct usb_device *udev; + + for (tmp = 0; tmp < intf->num_altsetting; tmp++) { + unsigned ep; + + in = out = NULL; + iso_in = iso_out = NULL; + alt = intf->altsetting + tmp; + + if (override_alt >= 0 && + override_alt != alt->desc.bAlternateSetting) + continue; + + /* take the first altsetting with in-bulk + out-bulk; + * ignore other endpoints and altsettings. + */ + for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { + struct usb_host_endpoint *e; + + e = alt->endpoint + ep; + switch (e->desc.bmAttributes) { + case USB_ENDPOINT_XFER_BULK: + break; + case USB_ENDPOINT_XFER_ISOC: + if (dev->info->iso) + goto try_iso; + /* FALLTHROUGH */ + default: + continue; + } + if (usb_endpoint_dir_in(&e->desc)) { + if (!in) + in = e; + } else { + if (!out) + out = e; + } + continue; +try_iso: + if (usb_endpoint_dir_in(&e->desc)) { + if (!iso_in) + iso_in = e; + } else { + if (!iso_out) + iso_out = e; + } + } + if ((in && out) || iso_in || iso_out) + goto found; + } + return -EINVAL; + +found: + udev = testdev_to_usbdev(dev); + dev->info->alt = alt->desc.bAlternateSetting; + if (alt->desc.bAlternateSetting != 0) { + tmp = usb_set_interface(udev, + alt->desc.bInterfaceNumber, + alt->desc.bAlternateSetting); + if (tmp < 0) + return tmp; + } + + if (in) { + dev->in_pipe = usb_rcvbulkpipe(udev, + in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->out_pipe = usb_sndbulkpipe(udev, + out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + } + if (iso_in) { + dev->iso_in = &iso_in->desc; + dev->in_iso_pipe = usb_rcvisocpipe(udev, + iso_in->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + } + + if (iso_out) { + dev->iso_out = &iso_out->desc; + dev->out_iso_pipe = usb_sndisocpipe(udev, + iso_out->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* Support for testing basic non-queued I/O streams. + * + * These just package urbs as requests that can be easily canceled. + * Each urb's data buffer is dynamically allocated; callers can fill + * them with non-zero test data (or test for it) when appropriate. + */ + +static void simple_callback(struct urb *urb) +{ + complete(urb->context); +} + +static struct urb *usbtest_alloc_urb( + struct usb_device *udev, + int pipe, + unsigned long bytes, + unsigned transfer_flags, + unsigned offset) +{ + struct urb *urb; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return urb; + usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, simple_callback, NULL); + urb->interval = (udev->speed == USB_SPEED_HIGH) + ? (INTERRUPT_RATE << 3) + : INTERRUPT_RATE; + urb->transfer_flags = transfer_flags; + if (usb_pipein(pipe)) + urb->transfer_flags |= URB_SHORT_NOT_OK; + + if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + urb->transfer_buffer = usb_alloc_coherent(udev, bytes + offset, + GFP_KERNEL, &urb->transfer_dma); + else + urb->transfer_buffer = kmalloc(bytes + offset, GFP_KERNEL); + + if (!urb->transfer_buffer) { + usb_free_urb(urb); + return NULL; + } + + /* To test unaligned transfers add an offset and fill the + unused memory with a guard value */ + if (offset) { + memset(urb->transfer_buffer, GUARD_BYTE, offset); + urb->transfer_buffer += offset; + if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + urb->transfer_dma += offset; + } + + /* For inbound transfers use guard byte so that test fails if + data not correctly copied */ + memset(urb->transfer_buffer, + usb_pipein(urb->pipe) ? GUARD_BYTE : 0, + bytes); + return urb; +} + +static struct urb *simple_alloc_urb( + struct usb_device *udev, + int pipe, + unsigned long bytes) +{ + return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0); +} + +static unsigned pattern; +static unsigned mod_pattern; +module_param_named(pattern, mod_pattern, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mod_pattern, "i/o pattern (0 == zeroes)"); + +static inline void simple_fill_buf(struct urb *urb) +{ + unsigned i; + u8 *buf = urb->transfer_buffer; + unsigned len = urb->transfer_buffer_length; + + switch (pattern) { + default: + /* FALLTHROUGH */ + case 0: + memset(buf, 0, len); + break; + case 1: /* mod63 */ + for (i = 0; i < len; i++) + *buf++ = (u8) (i % 63); + break; + } +} + +static inline unsigned long buffer_offset(void *buf) +{ + return (unsigned long)buf & (ARCH_KMALLOC_MINALIGN - 1); +} + +static int check_guard_bytes(struct usbtest_dev *tdev, struct urb *urb) +{ + u8 *buf = urb->transfer_buffer; + u8 *guard = buf - buffer_offset(buf); + unsigned i; + + for (i = 0; guard < buf; i++, guard++) { + if (*guard != GUARD_BYTE) { + ERROR(tdev, "guard byte[%d] %d (not %d)\n", + i, *guard, GUARD_BYTE); + return -EINVAL; + } + } + return 0; +} + +static int simple_check_buf(struct usbtest_dev *tdev, struct urb *urb) +{ + unsigned i; + u8 expected; + u8 *buf = urb->transfer_buffer; + unsigned len = urb->actual_length; + + int ret = check_guard_bytes(tdev, urb); + if (ret) + return ret; + + for (i = 0; i < len; i++, buf++) { + switch (pattern) { + /* all-zeroes has no synchronization issues */ + case 0: + expected = 0; + break; + /* mod63 stays in sync with short-terminated transfers, + * or otherwise when host and gadget agree on how large + * each usb transfer request should be. resync is done + * with set_interface or set_config. + */ + case 1: /* mod63 */ + expected = i % 63; + break; + /* always fail unsupported patterns */ + default: + expected = !*buf; + break; + } + if (*buf == expected) + continue; + ERROR(tdev, "buf[%d] = %d (not %d)\n", i, *buf, expected); + return -EINVAL; + } + return 0; +} + +static void simple_free_urb(struct urb *urb) +{ + unsigned long offset = buffer_offset(urb->transfer_buffer); + + if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + usb_free_coherent( + urb->dev, + urb->transfer_buffer_length + offset, + urb->transfer_buffer - offset, + urb->transfer_dma - offset); + else + kfree(urb->transfer_buffer - offset); + usb_free_urb(urb); +} + +static int simple_io( + struct usbtest_dev *tdev, + struct urb *urb, + int iterations, + int vary, + int expected, + const char *label +) +{ + struct usb_device *udev = urb->dev; + int max = urb->transfer_buffer_length; + struct completion completion; + int retval = 0; + unsigned long expire; + + urb->context = &completion; + while (retval == 0 && iterations-- > 0) { + init_completion(&completion); + if (usb_pipeout(urb->pipe)) { + simple_fill_buf(urb); + urb->transfer_flags |= URB_ZERO_PACKET; + } + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval != 0) + break; + + expire = msecs_to_jiffies(SIMPLE_IO_TIMEOUT); + if (!wait_for_completion_timeout(&completion, expire)) { + usb_kill_urb(urb); + retval = (urb->status == -ENOENT ? + -ETIMEDOUT : urb->status); + } else { + retval = urb->status; + } + + urb->dev = udev; + if (retval == 0 && usb_pipein(urb->pipe)) + retval = simple_check_buf(tdev, urb); + + if (vary) { + int len = urb->transfer_buffer_length; + + len += vary; + len %= max; + if (len == 0) + len = (vary < max) ? vary : max; + urb->transfer_buffer_length = len; + } + + /* FIXME if endpoint halted, clear halt (and log) */ + } + urb->transfer_buffer_length = max; + + if (expected != retval) + dev_err(&udev->dev, + "%s failed, iterations left %d, status %d (not %d)\n", + label, iterations, retval, expected); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +/* We use scatterlist primitives to test queued I/O. + * Yes, this also tests the scatterlist primitives. + */ + +static void free_sglist(struct scatterlist *sg, int nents) +{ + unsigned i; + + if (!sg) + return; + for (i = 0; i < nents; i++) { + if (!sg_page(&sg[i])) + continue; + kfree(sg_virt(&sg[i])); + } + kfree(sg); +} + +static struct scatterlist * +alloc_sglist(int nents, int max, int vary) +{ + struct scatterlist *sg; + unsigned i; + unsigned size = max; + + if (max == 0) + return NULL; + + sg = kmalloc_array(nents, sizeof *sg, GFP_KERNEL); + if (!sg) + return NULL; + sg_init_table(sg, nents); + + for (i = 0; i < nents; i++) { + char *buf; + unsigned j; + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) { + free_sglist(sg, i); + return NULL; + } + + /* kmalloc pages are always physically contiguous! */ + sg_set_buf(&sg[i], buf, size); + + switch (pattern) { + case 0: + /* already zeroed */ + break; + case 1: + for (j = 0; j < size; j++) + *buf++ = (u8) (j % 63); + break; + } + + if (vary) { + size += vary; + size %= max; + if (size == 0) + size = (vary < max) ? vary : max; + } + } + + return sg; +} + +static void sg_timeout(unsigned long _req) +{ + struct usb_sg_request *req = (struct usb_sg_request *) _req; + + req->status = -ETIMEDOUT; + usb_sg_cancel(req); +} + +static int perform_sglist( + struct usbtest_dev *tdev, + unsigned iterations, + int pipe, + struct usb_sg_request *req, + struct scatterlist *sg, + int nents +) +{ + struct usb_device *udev = testdev_to_usbdev(tdev); + int retval = 0; + struct timer_list sg_timer; + + setup_timer_on_stack(&sg_timer, sg_timeout, (unsigned long) req); + + while (retval == 0 && iterations-- > 0) { + retval = usb_sg_init(req, udev, pipe, + (udev->speed == USB_SPEED_HIGH) + ? (INTERRUPT_RATE << 3) + : INTERRUPT_RATE, + sg, nents, 0, GFP_KERNEL); + + if (retval) + break; + mod_timer(&sg_timer, jiffies + + msecs_to_jiffies(SIMPLE_IO_TIMEOUT)); + usb_sg_wait(req); + del_timer_sync(&sg_timer); + retval = req->status; + + /* FIXME check resulting data pattern */ + + /* FIXME if endpoint halted, clear halt (and log) */ + } + + /* FIXME for unlink or fault handling tests, don't report + * failure if retval is as we expected ... + */ + if (retval) + ERROR(tdev, "perform_sglist failed, " + "iterations left %d, status %d\n", + iterations, retval); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +/* unqueued control message testing + * + * there's a nice set of device functional requirements in chapter 9 of the + * usb 2.0 spec, which we can apply to ANY device, even ones that don't use + * special test firmware. + * + * we know the device is configured (or suspended) by the time it's visible + * through usbfs. we can't change that, so we won't test enumeration (which + * worked 'well enough' to get here, this time), power management (ditto), + * or remote wakeup (which needs human interaction). + */ + +static unsigned realworld = 1; +module_param(realworld, uint, 0); +MODULE_PARM_DESC(realworld, "clear to demand stricter spec compliance"); + +static int get_altsetting(struct usbtest_dev *dev) +{ + struct usb_interface *iface = dev->intf; + struct usb_device *udev = interface_to_usbdev(iface); + int retval; + + retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_INTERFACE, USB_DIR_IN|USB_RECIP_INTERFACE, + 0, iface->altsetting[0].desc.bInterfaceNumber, + dev->buf, 1, USB_CTRL_GET_TIMEOUT); + switch (retval) { + case 1: + return dev->buf[0]; + case 0: + retval = -ERANGE; + /* FALLTHROUGH */ + default: + return retval; + } +} + +static int set_altsetting(struct usbtest_dev *dev, int alternate) +{ + struct usb_interface *iface = dev->intf; + struct usb_device *udev; + + if (alternate < 0 || alternate >= 256) + return -EINVAL; + + udev = interface_to_usbdev(iface); + return usb_set_interface(udev, + iface->altsetting[0].desc.bInterfaceNumber, + alternate); +} + +static int is_good_config(struct usbtest_dev *tdev, int len) +{ + struct usb_config_descriptor *config; + + if (len < sizeof *config) + return 0; + config = (struct usb_config_descriptor *) tdev->buf; + + switch (config->bDescriptorType) { + case USB_DT_CONFIG: + case USB_DT_OTHER_SPEED_CONFIG: + if (config->bLength != 9) { + ERROR(tdev, "bogus config descriptor length\n"); + return 0; + } + /* this bit 'must be 1' but often isn't */ + if (!realworld && !(config->bmAttributes & 0x80)) { + ERROR(tdev, "high bit of config attributes not set\n"); + return 0; + } + if (config->bmAttributes & 0x1f) { /* reserved == 0 */ + ERROR(tdev, "reserved config bits set\n"); + return 0; + } + break; + default: + return 0; + } + + if (le16_to_cpu(config->wTotalLength) == len) /* read it all */ + return 1; + if (le16_to_cpu(config->wTotalLength) >= TBUF_SIZE) /* max partial read */ + return 1; + ERROR(tdev, "bogus config descriptor read size\n"); + return 0; +} + +/* sanity test for standard requests working with usb_control_mesg() and some + * of the utility functions which use it. + * + * this doesn't test how endpoint halts behave or data toggles get set, since + * we won't do I/O to bulk/interrupt endpoints here (which is how to change + * halt or toggle). toggle testing is impractical without support from hcds. + * + * this avoids failing devices linux would normally work with, by not testing + * config/altsetting operations for devices that only support their defaults. + * such devices rarely support those needless operations. + * + * NOTE that since this is a sanity test, it's not examining boundary cases + * to see if usbcore, hcd, and device all behave right. such testing would + * involve varied read sizes and other operation sequences. + */ +static int ch9_postconfig(struct usbtest_dev *dev) +{ + struct usb_interface *iface = dev->intf; + struct usb_device *udev = interface_to_usbdev(iface); + int i, alt, retval; + + /* [9.2.3] if there's more than one altsetting, we need to be able to + * set and get each one. mostly trusts the descriptors from usbcore. + */ + for (i = 0; i < iface->num_altsetting; i++) { + + /* 9.2.3 constrains the range here */ + alt = iface->altsetting[i].desc.bAlternateSetting; + if (alt < 0 || alt >= iface->num_altsetting) { + dev_err(&iface->dev, + "invalid alt [%d].bAltSetting = %d\n", + i, alt); + } + + /* [real world] get/set unimplemented if there's only one */ + if (realworld && iface->num_altsetting == 1) + continue; + + /* [9.4.10] set_interface */ + retval = set_altsetting(dev, alt); + if (retval) { + dev_err(&iface->dev, "can't set_interface = %d, %d\n", + alt, retval); + return retval; + } + + /* [9.4.4] get_interface always works */ + retval = get_altsetting(dev); + if (retval != alt) { + dev_err(&iface->dev, "get alt should be %d, was %d\n", + alt, retval); + return (retval < 0) ? retval : -EDOM; + } + + } + + /* [real world] get_config unimplemented if there's only one */ + if (!realworld || udev->descriptor.bNumConfigurations != 1) { + int expected = udev->actconfig->desc.bConfigurationValue; + + /* [9.4.2] get_configuration always works + * ... although some cheap devices (like one TI Hub I've got) + * won't return config descriptors except before set_config. + */ + retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_CONFIGURATION, + USB_DIR_IN | USB_RECIP_DEVICE, + 0, 0, dev->buf, 1, USB_CTRL_GET_TIMEOUT); + if (retval != 1 || dev->buf[0] != expected) { + dev_err(&iface->dev, "get config --> %d %d (1 %d)\n", + retval, dev->buf[0], expected); + return (retval < 0) ? retval : -EDOM; + } + } + + /* there's always [9.4.3] a device descriptor [9.6.1] */ + retval = usb_get_descriptor(udev, USB_DT_DEVICE, 0, + dev->buf, sizeof udev->descriptor); + if (retval != sizeof udev->descriptor) { + dev_err(&iface->dev, "dev descriptor --> %d\n", retval); + return (retval < 0) ? retval : -EDOM; + } + + /* there's always [9.4.3] at least one config descriptor [9.6.3] */ + for (i = 0; i < udev->descriptor.bNumConfigurations; i++) { + retval = usb_get_descriptor(udev, USB_DT_CONFIG, i, + dev->buf, TBUF_SIZE); + if (!is_good_config(dev, retval)) { + dev_err(&iface->dev, + "config [%d] descriptor --> %d\n", + i, retval); + return (retval < 0) ? retval : -EDOM; + } + + /* FIXME cross-checking udev->config[i] to make sure usbcore + * parsed it right (etc) would be good testing paranoia + */ + } + + /* and sometimes [9.2.6.6] speed dependent descriptors */ + if (le16_to_cpu(udev->descriptor.bcdUSB) == 0x0200) { + struct usb_qualifier_descriptor *d = NULL; + + /* device qualifier [9.6.2] */ + retval = usb_get_descriptor(udev, + USB_DT_DEVICE_QUALIFIER, 0, dev->buf, + sizeof(struct usb_qualifier_descriptor)); + if (retval == -EPIPE) { + if (udev->speed == USB_SPEED_HIGH) { + dev_err(&iface->dev, + "hs dev qualifier --> %d\n", + retval); + return (retval < 0) ? retval : -EDOM; + } + /* usb2.0 but not high-speed capable; fine */ + } else if (retval != sizeof(struct usb_qualifier_descriptor)) { + dev_err(&iface->dev, "dev qualifier --> %d\n", retval); + return (retval < 0) ? retval : -EDOM; + } else + d = (struct usb_qualifier_descriptor *) dev->buf; + + /* might not have [9.6.2] any other-speed configs [9.6.4] */ + if (d) { + unsigned max = d->bNumConfigurations; + for (i = 0; i < max; i++) { + retval = usb_get_descriptor(udev, + USB_DT_OTHER_SPEED_CONFIG, i, + dev->buf, TBUF_SIZE); + if (!is_good_config(dev, retval)) { + dev_err(&iface->dev, + "other speed config --> %d\n", + retval); + return (retval < 0) ? retval : -EDOM; + } + } + } + } + /* FIXME fetch strings from at least the device descriptor */ + + /* [9.4.5] get_status always works */ + retval = usb_get_status(udev, USB_RECIP_DEVICE, 0, dev->buf); + if (retval != 2) { + dev_err(&iface->dev, "get dev status --> %d\n", retval); + return (retval < 0) ? retval : -EDOM; + } + + /* FIXME configuration.bmAttributes says if we could try to set/clear + * the device's remote wakeup feature ... if we can, test that here + */ + + retval = usb_get_status(udev, USB_RECIP_INTERFACE, + iface->altsetting[0].desc.bInterfaceNumber, dev->buf); + if (retval != 2) { + dev_err(&iface->dev, "get interface status --> %d\n", retval); + return (retval < 0) ? retval : -EDOM; + } + /* FIXME get status for each endpoint in the interface */ + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* use ch9 requests to test whether: + * (a) queues work for control, keeping N subtests queued and + * active (auto-resubmit) for M loops through the queue. + * (b) protocol stalls (control-only) will autorecover. + * it's not like bulk/intr; no halt clearing. + * (c) short control reads are reported and handled. + * (d) queues are always processed in-order + */ + +struct ctrl_ctx { + spinlock_t lock; + struct usbtest_dev *dev; + struct completion complete; + unsigned count; + unsigned pending; + int status; + struct urb **urb; + struct usbtest_param *param; + int last; +}; + +#define NUM_SUBCASES 15 /* how many test subcases here? */ + +struct subcase { + struct usb_ctrlrequest setup; + int number; + int expected; +}; + +static void ctrl_complete(struct urb *urb) +{ + struct ctrl_ctx *ctx = urb->context; + struct usb_ctrlrequest *reqp; + struct subcase *subcase; + int status = urb->status; + + reqp = (struct usb_ctrlrequest *)urb->setup_packet; + subcase = container_of(reqp, struct subcase, setup); + + spin_lock(&ctx->lock); + ctx->count--; + ctx->pending--; + + /* queue must transfer and complete in fifo order, unless + * usb_unlink_urb() is used to unlink something not at the + * physical queue head (not tested). + */ + if (subcase->number > 0) { + if ((subcase->number - ctx->last) != 1) { + ERROR(ctx->dev, + "subcase %d completed out of order, last %d\n", + subcase->number, ctx->last); + status = -EDOM; + ctx->last = subcase->number; + goto error; + } + } + ctx->last = subcase->number; + + /* succeed or fault in only one way? */ + if (status == subcase->expected) + status = 0; + + /* async unlink for cleanup? */ + else if (status != -ECONNRESET) { + + /* some faults are allowed, not required */ + if (subcase->expected > 0 && ( + ((status == -subcase->expected /* happened */ + || status == 0)))) /* didn't */ + status = 0; + /* sometimes more than one fault is allowed */ + else if (subcase->number == 12 && status == -EPIPE) + status = 0; + else + ERROR(ctx->dev, "subtest %d error, status %d\n", + subcase->number, status); + } + + /* unexpected status codes mean errors; ideally, in hardware */ + if (status) { +error: + if (ctx->status == 0) { + int i; + + ctx->status = status; + ERROR(ctx->dev, "control queue %02x.%02x, err %d, " + "%d left, subcase %d, len %d/%d\n", + reqp->bRequestType, reqp->bRequest, + status, ctx->count, subcase->number, + urb->actual_length, + urb->transfer_buffer_length); + + /* FIXME this "unlink everything" exit route should + * be a separate test case. + */ + + /* unlink whatever's still pending */ + for (i = 1; i < ctx->param->sglen; i++) { + struct urb *u = ctx->urb[ + (i + subcase->number) + % ctx->param->sglen]; + + if (u == urb || !u->dev) + continue; + spin_unlock(&ctx->lock); + status = usb_unlink_urb(u); + spin_lock(&ctx->lock); + switch (status) { + case -EINPROGRESS: + case -EBUSY: + case -EIDRM: + continue; + default: + ERROR(ctx->dev, "urb unlink --> %d\n", + status); + } + } + status = ctx->status; + } + } + + /* resubmit if we need to, else mark this as done */ + if ((status == 0) && (ctx->pending < ctx->count)) { + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status != 0) { + ERROR(ctx->dev, + "can't resubmit ctrl %02x.%02x, err %d\n", + reqp->bRequestType, reqp->bRequest, status); + urb->dev = NULL; + } else + ctx->pending++; + } else + urb->dev = NULL; + + /* signal completion when nothing's queued */ + if (ctx->pending == 0) + complete(&ctx->complete); + spin_unlock(&ctx->lock); +} + +static int +test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param *param) +{ + struct usb_device *udev = testdev_to_usbdev(dev); + struct urb **urb; + struct ctrl_ctx context; + int i; + + if (param->sglen == 0 || param->iterations > UINT_MAX / param->sglen) + return -EOPNOTSUPP; + + spin_lock_init(&context.lock); + context.dev = dev; + init_completion(&context.complete); + context.count = param->sglen * param->iterations; + context.pending = 0; + context.status = -ENOMEM; + context.param = param; + context.last = -1; + + /* allocate and init the urbs we'll queue. + * as with bulk/intr sglists, sglen is the queue depth; it also + * controls which subtests run (more tests than sglen) or rerun. + */ + urb = kcalloc(param->sglen, sizeof(struct urb *), GFP_KERNEL); + if (!urb) + return -ENOMEM; + for (i = 0; i < param->sglen; i++) { + int pipe = usb_rcvctrlpipe(udev, 0); + unsigned len; + struct urb *u; + struct usb_ctrlrequest req; + struct subcase *reqp; + + /* sign of this variable means: + * -: tested code must return this (negative) error code + * +: tested code may return this (negative too) error code + */ + int expected = 0; + + /* requests here are mostly expected to succeed on any + * device, but some are chosen to trigger protocol stalls + * or short reads. + */ + memset(&req, 0, sizeof req); + req.bRequest = USB_REQ_GET_DESCRIPTOR; + req.bRequestType = USB_DIR_IN|USB_RECIP_DEVICE; + + switch (i % NUM_SUBCASES) { + case 0: /* get device descriptor */ + req.wValue = cpu_to_le16(USB_DT_DEVICE << 8); + len = sizeof(struct usb_device_descriptor); + break; + case 1: /* get first config descriptor (only) */ + req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); + len = sizeof(struct usb_config_descriptor); + break; + case 2: /* get altsetting (OFTEN STALLS) */ + req.bRequest = USB_REQ_GET_INTERFACE; + req.bRequestType = USB_DIR_IN|USB_RECIP_INTERFACE; + /* index = 0 means first interface */ + len = 1; + expected = EPIPE; + break; + case 3: /* get interface status */ + req.bRequest = USB_REQ_GET_STATUS; + req.bRequestType = USB_DIR_IN|USB_RECIP_INTERFACE; + /* interface 0 */ + len = 2; + break; + case 4: /* get device status */ + req.bRequest = USB_REQ_GET_STATUS; + req.bRequestType = USB_DIR_IN|USB_RECIP_DEVICE; + len = 2; + break; + case 5: /* get device qualifier (MAY STALL) */ + req.wValue = cpu_to_le16 (USB_DT_DEVICE_QUALIFIER << 8); + len = sizeof(struct usb_qualifier_descriptor); + if (udev->speed != USB_SPEED_HIGH) + expected = EPIPE; + break; + case 6: /* get first config descriptor, plus interface */ + req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); + len = sizeof(struct usb_config_descriptor); + len += sizeof(struct usb_interface_descriptor); + break; + case 7: /* get interface descriptor (ALWAYS STALLS) */ + req.wValue = cpu_to_le16 (USB_DT_INTERFACE << 8); + /* interface == 0 */ + len = sizeof(struct usb_interface_descriptor); + expected = -EPIPE; + break; + /* NOTE: two consecutive stalls in the queue here. + * that tests fault recovery a bit more aggressively. */ + case 8: /* clear endpoint halt (MAY STALL) */ + req.bRequest = USB_REQ_CLEAR_FEATURE; + req.bRequestType = USB_RECIP_ENDPOINT; + /* wValue 0 == ep halt */ + /* wIndex 0 == ep0 (shouldn't halt!) */ + len = 0; + pipe = usb_sndctrlpipe(udev, 0); + expected = EPIPE; + break; + case 9: /* get endpoint status */ + req.bRequest = USB_REQ_GET_STATUS; + req.bRequestType = USB_DIR_IN|USB_RECIP_ENDPOINT; + /* endpoint 0 */ + len = 2; + break; + case 10: /* trigger short read (EREMOTEIO) */ + req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); + len = 1024; + expected = -EREMOTEIO; + break; + /* NOTE: two consecutive _different_ faults in the queue. */ + case 11: /* get endpoint descriptor (ALWAYS STALLS) */ + req.wValue = cpu_to_le16(USB_DT_ENDPOINT << 8); + /* endpoint == 0 */ + len = sizeof(struct usb_interface_descriptor); + expected = EPIPE; + break; + /* NOTE: sometimes even a third fault in the queue! */ + case 12: /* get string 0 descriptor (MAY STALL) */ + req.wValue = cpu_to_le16(USB_DT_STRING << 8); + /* string == 0, for language IDs */ + len = sizeof(struct usb_interface_descriptor); + /* may succeed when > 4 languages */ + expected = EREMOTEIO; /* or EPIPE, if no strings */ + break; + case 13: /* short read, resembling case 10 */ + req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); + /* last data packet "should" be DATA1, not DATA0 */ + if (udev->speed == USB_SPEED_SUPER) + len = 1024 - 512; + else + len = 1024 - udev->descriptor.bMaxPacketSize0; + expected = -EREMOTEIO; + break; + case 14: /* short read; try to fill the last packet */ + req.wValue = cpu_to_le16((USB_DT_DEVICE << 8) | 0); + /* device descriptor size == 18 bytes */ + len = udev->descriptor.bMaxPacketSize0; + if (udev->speed == USB_SPEED_SUPER) + len = 512; + switch (len) { + case 8: + len = 24; + break; + case 16: + len = 32; + break; + } + expected = -EREMOTEIO; + break; + default: + ERROR(dev, "bogus number of ctrl queue testcases!\n"); + context.status = -EINVAL; + goto cleanup; + } + req.wLength = cpu_to_le16(len); + urb[i] = u = simple_alloc_urb(udev, pipe, len); + if (!u) + goto cleanup; + + reqp = kmalloc(sizeof *reqp, GFP_KERNEL); + if (!reqp) + goto cleanup; + reqp->setup = req; + reqp->number = i % NUM_SUBCASES; + reqp->expected = expected; + u->setup_packet = (char *) &reqp->setup; + + u->context = &context; + u->complete = ctrl_complete; + } + + /* queue the urbs */ + context.urb = urb; + spin_lock_irq(&context.lock); + for (i = 0; i < param->sglen; i++) { + context.status = usb_submit_urb(urb[i], GFP_ATOMIC); + if (context.status != 0) { + ERROR(dev, "can't submit urb[%d], status %d\n", + i, context.status); + context.count = context.pending; + break; + } + context.pending++; + } + spin_unlock_irq(&context.lock); + + /* FIXME set timer and time out; provide a disconnect hook */ + + /* wait for the last one to complete */ + if (context.pending > 0) + wait_for_completion(&context.complete); + +cleanup: + for (i = 0; i < param->sglen; i++) { + if (!urb[i]) + continue; + urb[i]->dev = udev; + kfree(urb[i]->setup_packet); + simple_free_urb(urb[i]); + } + kfree(urb); + return context.status; +} +#undef NUM_SUBCASES + + +/*-------------------------------------------------------------------------*/ + +static void unlink1_callback(struct urb *urb) +{ + int status = urb->status; + + /* we "know" -EPIPE (stall) never happens */ + if (!status) + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + urb->status = status; + complete(urb->context); + } +} + +static int unlink1(struct usbtest_dev *dev, int pipe, int size, int async) +{ + struct urb *urb; + struct completion completion; + int retval = 0; + + init_completion(&completion); + urb = simple_alloc_urb(testdev_to_usbdev(dev), pipe, size); + if (!urb) + return -ENOMEM; + urb->context = &completion; + urb->complete = unlink1_callback; + + if (usb_pipeout(urb->pipe)) { + simple_fill_buf(urb); + urb->transfer_flags |= URB_ZERO_PACKET; + } + + /* keep the endpoint busy. there are lots of hc/hcd-internal + * states, and testing should get to all of them over time. + * + * FIXME want additional tests for when endpoint is STALLing + * due to errors, or is just NAKing requests. + */ + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval != 0) { + dev_err(&dev->intf->dev, "submit fail %d\n", retval); + return retval; + } + + /* unlinking that should always work. variable delay tests more + * hcd states and code paths, even with little other system load. + */ + msleep(jiffies % (2 * INTERRUPT_RATE)); + if (async) { + while (!completion_done(&completion)) { + retval = usb_unlink_urb(urb); + + switch (retval) { + case -EBUSY: + case -EIDRM: + /* we can't unlink urbs while they're completing + * or if they've completed, and we haven't + * resubmitted. "normal" drivers would prevent + * resubmission, but since we're testing unlink + * paths, we can't. + */ + ERROR(dev, "unlink retry\n"); + continue; + case 0: + case -EINPROGRESS: + break; + + default: + dev_err(&dev->intf->dev, + "unlink fail %d\n", retval); + return retval; + } + + break; + } + } else + usb_kill_urb(urb); + + wait_for_completion(&completion); + retval = urb->status; + simple_free_urb(urb); + + if (async) + return (retval == -ECONNRESET) ? 0 : retval - 1000; + else + return (retval == -ENOENT || retval == -EPERM) ? + 0 : retval - 2000; +} + +static int unlink_simple(struct usbtest_dev *dev, int pipe, int len) +{ + int retval = 0; + + /* test sync and async paths */ + retval = unlink1(dev, pipe, len, 1); + if (!retval) + retval = unlink1(dev, pipe, len, 0); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +struct queued_ctx { + struct completion complete; + atomic_t pending; + unsigned num; + int status; + struct urb **urbs; +}; + +static void unlink_queued_callback(struct urb *urb) +{ + int status = urb->status; + struct queued_ctx *ctx = urb->context; + + if (ctx->status) + goto done; + if (urb == ctx->urbs[ctx->num - 4] || urb == ctx->urbs[ctx->num - 2]) { + if (status == -ECONNRESET) + goto done; + /* What error should we report if the URB completed normally? */ + } + if (status != 0) + ctx->status = status; + + done: + if (atomic_dec_and_test(&ctx->pending)) + complete(&ctx->complete); +} + +static int unlink_queued(struct usbtest_dev *dev, int pipe, unsigned num, + unsigned size) +{ + struct queued_ctx ctx; + struct usb_device *udev = testdev_to_usbdev(dev); + void *buf; + dma_addr_t buf_dma; + int i; + int retval = -ENOMEM; + + init_completion(&ctx.complete); + atomic_set(&ctx.pending, 1); /* One more than the actual value */ + ctx.num = num; + ctx.status = 0; + + buf = usb_alloc_coherent(udev, size, GFP_KERNEL, &buf_dma); + if (!buf) + return retval; + memset(buf, 0, size); + + /* Allocate and init the urbs we'll queue */ + ctx.urbs = kcalloc(num, sizeof(struct urb *), GFP_KERNEL); + if (!ctx.urbs) + goto free_buf; + for (i = 0; i < num; i++) { + ctx.urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!ctx.urbs[i]) + goto free_urbs; + usb_fill_bulk_urb(ctx.urbs[i], udev, pipe, buf, size, + unlink_queued_callback, &ctx); + ctx.urbs[i]->transfer_dma = buf_dma; + ctx.urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + + if (usb_pipeout(ctx.urbs[i]->pipe)) { + simple_fill_buf(ctx.urbs[i]); + ctx.urbs[i]->transfer_flags |= URB_ZERO_PACKET; + } + } + + /* Submit all the URBs and then unlink URBs num - 4 and num - 2. */ + for (i = 0; i < num; i++) { + atomic_inc(&ctx.pending); + retval = usb_submit_urb(ctx.urbs[i], GFP_KERNEL); + if (retval != 0) { + dev_err(&dev->intf->dev, "submit urbs[%d] fail %d\n", + i, retval); + atomic_dec(&ctx.pending); + ctx.status = retval; + break; + } + } + if (i == num) { + usb_unlink_urb(ctx.urbs[num - 4]); + usb_unlink_urb(ctx.urbs[num - 2]); + } else { + while (--i >= 0) + usb_unlink_urb(ctx.urbs[i]); + } + + if (atomic_dec_and_test(&ctx.pending)) /* The extra count */ + complete(&ctx.complete); + wait_for_completion(&ctx.complete); + retval = ctx.status; + + free_urbs: + for (i = 0; i < num; i++) + usb_free_urb(ctx.urbs[i]); + kfree(ctx.urbs); + free_buf: + usb_free_coherent(udev, size, buf, buf_dma); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static int verify_not_halted(struct usbtest_dev *tdev, int ep, struct urb *urb) +{ + int retval; + u16 status; + + /* shouldn't look or act halted */ + retval = usb_get_status(urb->dev, USB_RECIP_ENDPOINT, ep, &status); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't get no-halt status, %d\n", + ep, retval); + return retval; + } + if (status != 0) { + ERROR(tdev, "ep %02x bogus status: %04x != 0\n", ep, status); + return -EINVAL; + } + retval = simple_io(tdev, urb, 1, 0, 0, __func__); + if (retval != 0) + return -EINVAL; + return 0; +} + +static int verify_halted(struct usbtest_dev *tdev, int ep, struct urb *urb) +{ + int retval; + u16 status; + + /* should look and act halted */ + retval = usb_get_status(urb->dev, USB_RECIP_ENDPOINT, ep, &status); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't get halt status, %d\n", + ep, retval); + return retval; + } + le16_to_cpus(&status); + if (status != 1) { + ERROR(tdev, "ep %02x bogus status: %04x != 1\n", ep, status); + return -EINVAL; + } + retval = simple_io(tdev, urb, 1, 0, -EPIPE, __func__); + if (retval != -EPIPE) + return -EINVAL; + retval = simple_io(tdev, urb, 1, 0, -EPIPE, "verify_still_halted"); + if (retval != -EPIPE) + return -EINVAL; + return 0; +} + +static int test_halt(struct usbtest_dev *tdev, int ep, struct urb *urb) +{ + int retval; + + /* shouldn't look or act halted now */ + retval = verify_not_halted(tdev, ep, urb); + if (retval < 0) + return retval; + + /* set halt (protocol test only), verify it worked */ + retval = usb_control_msg(urb->dev, usb_sndctrlpipe(urb->dev, 0), + USB_REQ_SET_FEATURE, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, ep, + NULL, 0, USB_CTRL_SET_TIMEOUT); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't set halt, %d\n", ep, retval); + return retval; + } + retval = verify_halted(tdev, ep, urb); + if (retval < 0) + return retval; + + /* clear halt (tests API + protocol), verify it worked */ + retval = usb_clear_halt(urb->dev, urb->pipe); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't clear halt, %d\n", ep, retval); + return retval; + } + retval = verify_not_halted(tdev, ep, urb); + if (retval < 0) + return retval; + + /* NOTE: could also verify SET_INTERFACE clear halts ... */ + + return 0; +} + +static int halt_simple(struct usbtest_dev *dev) +{ + int ep; + int retval = 0; + struct urb *urb; + struct usb_device *udev = testdev_to_usbdev(dev); + + if (udev->speed == USB_SPEED_SUPER) + urb = simple_alloc_urb(udev, 0, 1024); + else + urb = simple_alloc_urb(udev, 0, 512); + if (urb == NULL) + return -ENOMEM; + + if (dev->in_pipe) { + ep = usb_pipeendpoint(dev->in_pipe) | USB_DIR_IN; + urb->pipe = dev->in_pipe; + retval = test_halt(dev, ep, urb); + if (retval < 0) + goto done; + } + + if (dev->out_pipe) { + ep = usb_pipeendpoint(dev->out_pipe); + urb->pipe = dev->out_pipe; + retval = test_halt(dev, ep, urb); + } +done: + simple_free_urb(urb); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* Control OUT tests use the vendor control requests from Intel's + * USB 2.0 compliance test device: write a buffer, read it back. + * + * Intel's spec only _requires_ that it work for one packet, which + * is pretty weak. Some HCDs place limits here; most devices will + * need to be able to handle more than one OUT data packet. We'll + * try whatever we're told to try. + */ +static int ctrl_out(struct usbtest_dev *dev, + unsigned count, unsigned length, unsigned vary, unsigned offset) +{ + unsigned i, j, len; + int retval; + u8 *buf; + char *what = "?"; + struct usb_device *udev; + + if (length < 1 || length > 0xffff || vary >= length) + return -EINVAL; + + buf = kmalloc(length + offset, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf += offset; + udev = testdev_to_usbdev(dev); + len = length; + retval = 0; + + /* NOTE: hardware might well act differently if we pushed it + * with lots back-to-back queued requests. + */ + for (i = 0; i < count; i++) { + /* write patterned data */ + for (j = 0; j < len; j++) + buf[j] = i + j; + retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x5b, USB_DIR_OUT|USB_TYPE_VENDOR, + 0, 0, buf, len, USB_CTRL_SET_TIMEOUT); + if (retval != len) { + what = "write"; + if (retval >= 0) { + ERROR(dev, "ctrl_out, wlen %d (expected %d)\n", + retval, len); + retval = -EBADMSG; + } + break; + } + + /* read it back -- assuming nothing intervened!! */ + retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + 0x5c, USB_DIR_IN|USB_TYPE_VENDOR, + 0, 0, buf, len, USB_CTRL_GET_TIMEOUT); + if (retval != len) { + what = "read"; + if (retval >= 0) { + ERROR(dev, "ctrl_out, rlen %d (expected %d)\n", + retval, len); + retval = -EBADMSG; + } + break; + } + + /* fail if we can't verify */ + for (j = 0; j < len; j++) { + if (buf[j] != (u8) (i + j)) { + ERROR(dev, "ctrl_out, byte %d is %d not %d\n", + j, buf[j], (u8) i + j); + retval = -EBADMSG; + break; + } + } + if (retval < 0) { + what = "verify"; + break; + } + + len += vary; + + /* [real world] the "zero bytes IN" case isn't really used. + * hardware can easily trip up in this weird case, since its + * status stage is IN, not OUT like other ep0in transfers. + */ + if (len > length) + len = realworld ? 1 : 0; + } + + if (retval < 0) + ERROR(dev, "ctrl_out %s failed, code %d, count %d\n", + what, retval, i); + + kfree(buf - offset); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* ISO tests ... mimics common usage + * - buffer length is split into N packets (mostly maxpacket sized) + * - multi-buffers according to sglen + */ + +struct iso_context { + unsigned count; + unsigned pending; + spinlock_t lock; + struct completion done; + int submit_error; + unsigned long errors; + unsigned long packet_count; + struct usbtest_dev *dev; +}; + +static void iso_callback(struct urb *urb) +{ + struct iso_context *ctx = urb->context; + + spin_lock(&ctx->lock); + ctx->count--; + + ctx->packet_count += urb->number_of_packets; + if (urb->error_count > 0) + ctx->errors += urb->error_count; + else if (urb->status != 0) + ctx->errors += urb->number_of_packets; + else if (urb->actual_length != urb->transfer_buffer_length) + ctx->errors++; + else if (check_guard_bytes(ctx->dev, urb) != 0) + ctx->errors++; + + if (urb->status == 0 && ctx->count > (ctx->pending - 1) + && !ctx->submit_error) { + int status = usb_submit_urb(urb, GFP_ATOMIC); + switch (status) { + case 0: + goto done; + default: + dev_err(&ctx->dev->intf->dev, + "iso resubmit err %d\n", + status); + /* FALLTHROUGH */ + case -ENODEV: /* disconnected */ + case -ESHUTDOWN: /* endpoint disabled */ + ctx->submit_error = 1; + break; + } + } + + ctx->pending--; + if (ctx->pending == 0) { + if (ctx->errors) + dev_err(&ctx->dev->intf->dev, + "iso test, %lu errors out of %lu\n", + ctx->errors, ctx->packet_count); + complete(&ctx->done); + } +done: + spin_unlock(&ctx->lock); +} + +static struct urb *iso_alloc_urb( + struct usb_device *udev, + int pipe, + struct usb_endpoint_descriptor *desc, + long bytes, + unsigned offset +) +{ + struct urb *urb; + unsigned i, maxp, packets; + + if (bytes < 0 || !desc) + return NULL; + maxp = 0x7ff & usb_endpoint_maxp(desc); + maxp *= 1 + (0x3 & (usb_endpoint_maxp(desc) >> 11)); + packets = DIV_ROUND_UP(bytes, maxp); + + urb = usb_alloc_urb(packets, GFP_KERNEL); + if (!urb) + return urb; + urb->dev = udev; + urb->pipe = pipe; + + urb->number_of_packets = packets; + urb->transfer_buffer_length = bytes; + urb->transfer_buffer = usb_alloc_coherent(udev, bytes + offset, + GFP_KERNEL, + &urb->transfer_dma); + if (!urb->transfer_buffer) { + usb_free_urb(urb); + return NULL; + } + if (offset) { + memset(urb->transfer_buffer, GUARD_BYTE, offset); + urb->transfer_buffer += offset; + urb->transfer_dma += offset; + } + /* For inbound transfers use guard byte so that test fails if + data not correctly copied */ + memset(urb->transfer_buffer, + usb_pipein(urb->pipe) ? GUARD_BYTE : 0, + bytes); + + for (i = 0; i < packets; i++) { + /* here, only the last packet will be short */ + urb->iso_frame_desc[i].length = min((unsigned) bytes, maxp); + bytes -= urb->iso_frame_desc[i].length; + + urb->iso_frame_desc[i].offset = maxp * i; + } + + urb->complete = iso_callback; + /* urb->context = SET BY CALLER */ + urb->interval = 1 << (desc->bInterval - 1); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + return urb; +} + +static int +test_iso_queue(struct usbtest_dev *dev, struct usbtest_param *param, + int pipe, struct usb_endpoint_descriptor *desc, unsigned offset) +{ + struct iso_context context; + struct usb_device *udev; + unsigned i; + unsigned long packets = 0; + int status = 0; + struct urb *urbs[10]; /* FIXME no limit */ + + if (param->sglen > 10) + return -EDOM; + + memset(&context, 0, sizeof context); + context.count = param->iterations * param->sglen; + context.dev = dev; + init_completion(&context.done); + spin_lock_init(&context.lock); + + memset(urbs, 0, sizeof urbs); + udev = testdev_to_usbdev(dev); + dev_info(&dev->intf->dev, + "... iso period %d %sframes, wMaxPacket %04x\n", + 1 << (desc->bInterval - 1), + (udev->speed == USB_SPEED_HIGH) ? "micro" : "", + usb_endpoint_maxp(desc)); + + for (i = 0; i < param->sglen; i++) { + urbs[i] = iso_alloc_urb(udev, pipe, desc, + param->length, offset); + if (!urbs[i]) { + status = -ENOMEM; + goto fail; + } + packets += urbs[i]->number_of_packets; + urbs[i]->context = &context; + } + packets *= param->iterations; + dev_info(&dev->intf->dev, + "... total %lu msec (%lu packets)\n", + (packets * (1 << (desc->bInterval - 1))) + / ((udev->speed == USB_SPEED_HIGH) ? 8 : 1), + packets); + + spin_lock_irq(&context.lock); + for (i = 0; i < param->sglen; i++) { + ++context.pending; + status = usb_submit_urb(urbs[i], GFP_ATOMIC); + if (status < 0) { + ERROR(dev, "submit iso[%d], error %d\n", i, status); + if (i == 0) { + spin_unlock_irq(&context.lock); + goto fail; + } + + simple_free_urb(urbs[i]); + urbs[i] = NULL; + context.pending--; + context.submit_error = 1; + break; + } + } + spin_unlock_irq(&context.lock); + + wait_for_completion(&context.done); + + for (i = 0; i < param->sglen; i++) { + if (urbs[i]) + simple_free_urb(urbs[i]); + } + /* + * Isochronous transfers are expected to fail sometimes. As an + * arbitrary limit, we will report an error if any submissions + * fail or if the transfer failure rate is > 10%. + */ + if (status != 0) + ; + else if (context.submit_error) + status = -EACCES; + else if (context.errors > context.packet_count / 10) + status = -EIO; + return status; + +fail: + for (i = 0; i < param->sglen; i++) { + if (urbs[i]) + simple_free_urb(urbs[i]); + } + return status; +} + +static int test_unaligned_bulk( + struct usbtest_dev *tdev, + int pipe, + unsigned length, + int iterations, + unsigned transfer_flags, + const char *label) +{ + int retval; + struct urb *urb = usbtest_alloc_urb( + testdev_to_usbdev(tdev), pipe, length, transfer_flags, 1); + + if (!urb) + return -ENOMEM; + + retval = simple_io(tdev, urb, iterations, 0, 0, label); + simple_free_urb(urb); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* We only have this one interface to user space, through usbfs. + * User mode code can scan usbfs to find N different devices (maybe on + * different busses) to use when testing, and allocate one thread per + * test. So discovery is simplified, and we have no device naming issues. + * + * Don't use these only as stress/load tests. Use them along with with + * other USB bus activity: plugging, unplugging, mousing, mp3 playback, + * video capture, and so on. Run different tests at different times, in + * different sequences. Nothing here should interact with other devices, + * except indirectly by consuming USB bandwidth and CPU resources for test + * threads and request completion. But the only way to know that for sure + * is to test when HC queues are in use by many devices. + * + * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(), + * it locks out usbcore in certain code paths. Notably, if you disconnect + * the device-under-test, khubd will wait block forever waiting for the + * ioctl to complete ... so that usb_disconnect() can abort the pending + * urbs and then call usbtest_disconnect(). To abort a test, you're best + * off just killing the userspace task and waiting for it to exit. + */ + +static int +usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) +{ + struct usbtest_dev *dev = usb_get_intfdata(intf); + struct usb_device *udev = testdev_to_usbdev(dev); + struct usbtest_param *param = buf; + int retval = -EOPNOTSUPP; + struct urb *urb; + struct scatterlist *sg; + struct usb_sg_request req; + struct timeval start; + unsigned i; + + /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ + + pattern = mod_pattern; + + if (code != USBTEST_REQUEST) + return -EOPNOTSUPP; + + if (param->iterations <= 0) + return -EINVAL; + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + + /* FIXME: What if a system sleep starts while a test is running? */ + + /* some devices, like ez-usb default devices, need a non-default + * altsetting to have any active endpoints. some tests change + * altsettings; force a default so most tests don't need to check. + */ + if (dev->info->alt >= 0) { + int res; + + if (intf->altsetting->desc.bInterfaceNumber) { + mutex_unlock(&dev->lock); + return -ENODEV; + } + res = set_altsetting(dev, dev->info->alt); + if (res) { + dev_err(&intf->dev, + "set altsetting to %d failed, %d\n", + dev->info->alt, res); + mutex_unlock(&dev->lock); + return res; + } + } + + /* + * Just a bunch of test cases that every HCD is expected to handle. + * + * Some may need specific firmware, though it'd be good to have + * one firmware image to handle all the test cases. + * + * FIXME add more tests! cancel requests, verify the data, control + * queueing, concurrent read+write threads, and so on. + */ + do_gettimeofday(&start); + switch (param->test_num) { + + case 0: + dev_info(&intf->dev, "TEST 0: NOP\n"); + retval = 0; + break; + + /* Simple non-queued bulk I/O tests */ + case 1: + if (dev->out_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 1: write %d bytes %u times\n", + param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->out_pipe, param->length); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk sink (maybe accepts short writes) */ + retval = simple_io(dev, urb, param->iterations, 0, 0, "test1"); + simple_free_urb(urb); + break; + case 2: + if (dev->in_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 2: read %d bytes %u times\n", + param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->in_pipe, param->length); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk source (maybe generates short writes) */ + retval = simple_io(dev, urb, param->iterations, 0, 0, "test2"); + simple_free_urb(urb); + break; + case 3: + if (dev->out_pipe == 0 || param->vary == 0) + break; + dev_info(&intf->dev, + "TEST 3: write/%d 0..%d bytes %u times\n", + param->vary, param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->out_pipe, param->length); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk sink (maybe accepts short writes) */ + retval = simple_io(dev, urb, param->iterations, param->vary, + 0, "test3"); + simple_free_urb(urb); + break; + case 4: + if (dev->in_pipe == 0 || param->vary == 0) + break; + dev_info(&intf->dev, + "TEST 4: read/%d 0..%d bytes %u times\n", + param->vary, param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->in_pipe, param->length); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk source (maybe generates short writes) */ + retval = simple_io(dev, urb, param->iterations, param->vary, + 0, "test4"); + simple_free_urb(urb); + break; + + /* Queued bulk I/O tests */ + case 5: + if (dev->out_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 5: write %d sglists %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + sg = alloc_sglist(param->sglen, param->length, 0); + if (!sg) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk sink (maybe accepts short writes) */ + retval = perform_sglist(dev, param->iterations, dev->out_pipe, + &req, sg, param->sglen); + free_sglist(sg, param->sglen); + break; + + case 6: + if (dev->in_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 6: read %d sglists %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + sg = alloc_sglist(param->sglen, param->length, 0); + if (!sg) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk source (maybe generates short writes) */ + retval = perform_sglist(dev, param->iterations, dev->in_pipe, + &req, sg, param->sglen); + free_sglist(sg, param->sglen); + break; + case 7: + if (dev->out_pipe == 0 || param->sglen == 0 || param->vary == 0) + break; + dev_info(&intf->dev, + "TEST 7: write/%d %d sglists %d entries 0..%d bytes\n", + param->vary, param->iterations, + param->sglen, param->length); + sg = alloc_sglist(param->sglen, param->length, param->vary); + if (!sg) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk sink (maybe accepts short writes) */ + retval = perform_sglist(dev, param->iterations, dev->out_pipe, + &req, sg, param->sglen); + free_sglist(sg, param->sglen); + break; + case 8: + if (dev->in_pipe == 0 || param->sglen == 0 || param->vary == 0) + break; + dev_info(&intf->dev, + "TEST 8: read/%d %d sglists %d entries 0..%d bytes\n", + param->vary, param->iterations, + param->sglen, param->length); + sg = alloc_sglist(param->sglen, param->length, param->vary); + if (!sg) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk source (maybe generates short writes) */ + retval = perform_sglist(dev, param->iterations, dev->in_pipe, + &req, sg, param->sglen); + free_sglist(sg, param->sglen); + break; + + /* non-queued sanity tests for control (chapter 9 subset) */ + case 9: + retval = 0; + dev_info(&intf->dev, + "TEST 9: ch9 (subset) control tests, %d times\n", + param->iterations); + for (i = param->iterations; retval == 0 && i--; /* NOP */) + retval = ch9_postconfig(dev); + if (retval) + dev_err(&intf->dev, "ch9 subset failed, " + "iterations left %d\n", i); + break; + + /* queued control messaging */ + case 10: + retval = 0; + dev_info(&intf->dev, + "TEST 10: queue %d control calls, %d times\n", + param->sglen, + param->iterations); + retval = test_ctrl_queue(dev, param); + break; + + /* simple non-queued unlinks (ring with one urb) */ + case 11: + if (dev->in_pipe == 0 || !param->length) + break; + retval = 0; + dev_info(&intf->dev, "TEST 11: unlink %d reads of %d\n", + param->iterations, param->length); + for (i = param->iterations; retval == 0 && i--; /* NOP */) + retval = unlink_simple(dev, dev->in_pipe, + param->length); + if (retval) + dev_err(&intf->dev, "unlink reads failed %d, " + "iterations left %d\n", retval, i); + break; + case 12: + if (dev->out_pipe == 0 || !param->length) + break; + retval = 0; + dev_info(&intf->dev, "TEST 12: unlink %d writes of %d\n", + param->iterations, param->length); + for (i = param->iterations; retval == 0 && i--; /* NOP */) + retval = unlink_simple(dev, dev->out_pipe, + param->length); + if (retval) + dev_err(&intf->dev, "unlink writes failed %d, " + "iterations left %d\n", retval, i); + break; + + /* ep halt tests */ + case 13: + if (dev->out_pipe == 0 && dev->in_pipe == 0) + break; + retval = 0; + dev_info(&intf->dev, "TEST 13: set/clear %d halts\n", + param->iterations); + for (i = param->iterations; retval == 0 && i--; /* NOP */) + retval = halt_simple(dev); + + if (retval) + ERROR(dev, "halts failed, iterations left %d\n", i); + break; + + /* control write tests */ + case 14: + if (!dev->info->ctrl_out) + break; + dev_info(&intf->dev, "TEST 14: %d ep0out, %d..%d vary %d\n", + param->iterations, + realworld ? 1 : 0, param->length, + param->vary); + retval = ctrl_out(dev, param->iterations, + param->length, param->vary, 0); + break; + + /* iso write tests */ + case 15: + if (dev->out_iso_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 15: write %d iso, %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + /* FIRMWARE: iso sink */ + retval = test_iso_queue(dev, param, + dev->out_iso_pipe, dev->iso_out, 0); + break; + + /* iso read tests */ + case 16: + if (dev->in_iso_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 16: read %d iso, %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + /* FIRMWARE: iso source */ + retval = test_iso_queue(dev, param, + dev->in_iso_pipe, dev->iso_in, 0); + break; + + /* FIXME scatterlist cancel (needs helper thread) */ + + /* Tests for bulk I/O using DMA mapping by core and odd address */ + case 17: + if (dev->out_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 17: write odd addr %d bytes %u times core map\n", + param->length, param->iterations); + + retval = test_unaligned_bulk( + dev, dev->out_pipe, + param->length, param->iterations, + 0, "test17"); + break; + + case 18: + if (dev->in_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 18: read odd addr %d bytes %u times core map\n", + param->length, param->iterations); + + retval = test_unaligned_bulk( + dev, dev->in_pipe, + param->length, param->iterations, + 0, "test18"); + break; + + /* Tests for bulk I/O using premapped coherent buffer and odd address */ + case 19: + if (dev->out_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 19: write odd addr %d bytes %u times premapped\n", + param->length, param->iterations); + + retval = test_unaligned_bulk( + dev, dev->out_pipe, + param->length, param->iterations, + URB_NO_TRANSFER_DMA_MAP, "test19"); + break; + + case 20: + if (dev->in_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 20: read odd addr %d bytes %u times premapped\n", + param->length, param->iterations); + + retval = test_unaligned_bulk( + dev, dev->in_pipe, + param->length, param->iterations, + URB_NO_TRANSFER_DMA_MAP, "test20"); + break; + + /* control write tests with unaligned buffer */ + case 21: + if (!dev->info->ctrl_out) + break; + dev_info(&intf->dev, + "TEST 21: %d ep0out odd addr, %d..%d vary %d\n", + param->iterations, + realworld ? 1 : 0, param->length, + param->vary); + retval = ctrl_out(dev, param->iterations, + param->length, param->vary, 1); + break; + + /* unaligned iso tests */ + case 22: + if (dev->out_iso_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 22: write %d iso odd, %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + retval = test_iso_queue(dev, param, + dev->out_iso_pipe, dev->iso_out, 1); + break; + + case 23: + if (dev->in_iso_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 23: read %d iso odd, %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + retval = test_iso_queue(dev, param, + dev->in_iso_pipe, dev->iso_in, 1); + break; + + /* unlink URBs from a bulk-OUT queue */ + case 24: + if (dev->out_pipe == 0 || !param->length || param->sglen < 4) + break; + retval = 0; + dev_info(&intf->dev, "TEST 24: unlink from %d queues of " + "%d %d-byte writes\n", + param->iterations, param->sglen, param->length); + for (i = param->iterations; retval == 0 && i > 0; --i) { + retval = unlink_queued(dev, dev->out_pipe, + param->sglen, param->length); + if (retval) { + dev_err(&intf->dev, + "unlink queued writes failed %d, " + "iterations left %d\n", retval, i); + break; + } + } + break; + + } + do_gettimeofday(¶m->duration); + param->duration.tv_sec -= start.tv_sec; + param->duration.tv_usec -= start.tv_usec; + if (param->duration.tv_usec < 0) { + param->duration.tv_usec += 1000 * 1000; + param->duration.tv_sec -= 1; + } + mutex_unlock(&dev->lock); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static unsigned force_interrupt; +module_param(force_interrupt, uint, 0); +MODULE_PARM_DESC(force_interrupt, "0 = test default; else interrupt"); + +#ifdef GENERIC +static unsigned short vendor; +module_param(vendor, ushort, 0); +MODULE_PARM_DESC(vendor, "vendor code (from usb-if)"); + +static unsigned short product; +module_param(product, ushort, 0); +MODULE_PARM_DESC(product, "product code (from vendor)"); +#endif + +static int +usbtest_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev; + struct usbtest_dev *dev; + struct usbtest_info *info; + char *rtest, *wtest; + char *irtest, *iwtest; + + udev = interface_to_usbdev(intf); + +#ifdef GENERIC + /* specify devices by module parameters? */ + if (id->match_flags == 0) { + /* vendor match required, product match optional */ + if (!vendor || le16_to_cpu(udev->descriptor.idVendor) != (u16)vendor) + return -ENODEV; + if (product && le16_to_cpu(udev->descriptor.idProduct) != (u16)product) + return -ENODEV; + dev_info(&intf->dev, "matched module params, " + "vend=0x%04x prod=0x%04x\n", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + } +#endif + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + info = (struct usbtest_info *) id->driver_info; + dev->info = info; + mutex_init(&dev->lock); + + dev->intf = intf; + + /* cacheline-aligned scratch for i/o */ + dev->buf = kmalloc(TBUF_SIZE, GFP_KERNEL); + if (dev->buf == NULL) { + kfree(dev); + return -ENOMEM; + } + + /* NOTE this doesn't yet test the handful of difference that are + * visible with high speed interrupts: bigger maxpacket (1K) and + * "high bandwidth" modes (up to 3 packets/uframe). + */ + rtest = wtest = ""; + irtest = iwtest = ""; + if (force_interrupt || udev->speed == USB_SPEED_LOW) { + if (info->ep_in) { + dev->in_pipe = usb_rcvintpipe(udev, info->ep_in); + rtest = " intr-in"; + } + if (info->ep_out) { + dev->out_pipe = usb_sndintpipe(udev, info->ep_out); + wtest = " intr-out"; + } + } else { + if (override_alt >= 0 || info->autoconf) { + int status; + + status = get_endpoints(dev, intf); + if (status < 0) { + WARNING(dev, "couldn't get endpoints, %d\n", + status); + kfree(dev->buf); + kfree(dev); + return status; + } + /* may find bulk or ISO pipes */ + } else { + if (info->ep_in) + dev->in_pipe = usb_rcvbulkpipe(udev, + info->ep_in); + if (info->ep_out) + dev->out_pipe = usb_sndbulkpipe(udev, + info->ep_out); + } + if (dev->in_pipe) + rtest = " bulk-in"; + if (dev->out_pipe) + wtest = " bulk-out"; + if (dev->in_iso_pipe) + irtest = " iso-in"; + if (dev->out_iso_pipe) + iwtest = " iso-out"; + } + + usb_set_intfdata(intf, dev); + dev_info(&intf->dev, "%s\n", info->name); + dev_info(&intf->dev, "%s {control%s%s%s%s%s} tests%s\n", + usb_speed_string(udev->speed), + info->ctrl_out ? " in/out" : "", + rtest, wtest, + irtest, iwtest, + info->alt >= 0 ? " (+alt)" : ""); + return 0; +} + +static int usbtest_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +static int usbtest_resume(struct usb_interface *intf) +{ + return 0; +} + + +static void usbtest_disconnect(struct usb_interface *intf) +{ + struct usbtest_dev *dev = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + dev_dbg(&intf->dev, "disconnect\n"); + kfree(dev); +} + +/* Basic testing only needs a device that can source or sink bulk traffic. + * Any device can test control transfers (default with GENERIC binding). + * + * Several entries work with the default EP0 implementation that's built + * into EZ-USB chips. There's a default vendor ID which can be overridden + * by (very) small config EEPROMS, but otherwise all these devices act + * identically until firmware is loaded: only EP0 works. It turns out + * to be easy to make other endpoints work, without modifying that EP0 + * behavior. For now, we expect that kind of firmware. + */ + +/* an21xx or fx versions of ez-usb */ +static struct usbtest_info ez1_info = { + .name = "EZ-USB device", + .ep_in = 2, + .ep_out = 2, + .alt = 1, +}; + +/* fx2 version of ez-usb */ +static struct usbtest_info ez2_info = { + .name = "FX2 device", + .ep_in = 6, + .ep_out = 2, + .alt = 1, +}; + +/* ezusb family device with dedicated usb test firmware, + */ +static struct usbtest_info fw_info = { + .name = "usb test device", + .ep_in = 2, + .ep_out = 2, + .alt = 1, + .autoconf = 1, /* iso and ctrl_out need autoconf */ + .ctrl_out = 1, + .iso = 1, /* iso_ep's are #8 in/out */ +}; + +/* peripheral running Linux and 'zero.c' test firmware, or + * its user-mode cousin. different versions of this use + * different hardware with the same vendor/product codes. + * host side MUST rely on the endpoint descriptors. + */ +static struct usbtest_info gz_info = { + .name = "Linux gadget zero", + .autoconf = 1, + .ctrl_out = 1, + .iso = 1, + .alt = 0, +}; + +static struct usbtest_info um_info = { + .name = "Linux user mode test driver", + .autoconf = 1, + .alt = -1, +}; + +static struct usbtest_info um2_info = { + .name = "Linux user mode ISO test driver", + .autoconf = 1, + .iso = 1, + .alt = -1, +}; + +#ifdef IBOT2 +/* this is a nice source of high speed bulk data; + * uses an FX2, with firmware provided in the device + */ +static struct usbtest_info ibot2_info = { + .name = "iBOT2 webcam", + .ep_in = 2, + .alt = -1, +}; +#endif + +#ifdef GENERIC +/* we can use any device to test control traffic */ +static struct usbtest_info generic_info = { + .name = "Generic USB device", + .alt = -1, +}; +#endif + + +static const struct usb_device_id id_table[] = { + + /*-------------------------------------------------------------*/ + + /* EZ-USB devices which download firmware to replace (or in our + * case augment) the default device implementation. + */ + + /* generic EZ-USB FX controller */ + { USB_DEVICE(0x0547, 0x2235), + .driver_info = (unsigned long) &ez1_info, + }, + + /* CY3671 development board with EZ-USB FX */ + { USB_DEVICE(0x0547, 0x0080), + .driver_info = (unsigned long) &ez1_info, + }, + + /* generic EZ-USB FX2 controller (or development board) */ + { USB_DEVICE(0x04b4, 0x8613), + .driver_info = (unsigned long) &ez2_info, + }, + + /* re-enumerated usb test device firmware */ + { USB_DEVICE(0xfff0, 0xfff0), + .driver_info = (unsigned long) &fw_info, + }, + + /* "Gadget Zero" firmware runs under Linux */ + { USB_DEVICE(0x0525, 0xa4a0), + .driver_info = (unsigned long) &gz_info, + }, + + /* so does a user-mode variant */ + { USB_DEVICE(0x0525, 0xa4a4), + .driver_info = (unsigned long) &um_info, + }, + + /* ... and a user-mode variant that talks iso */ + { USB_DEVICE(0x0525, 0xa4a3), + .driver_info = (unsigned long) &um2_info, + }, + +#ifdef KEYSPAN_19Qi + /* Keyspan 19qi uses an21xx (original EZ-USB) */ + /* this does not coexist with the real Keyspan 19qi driver! */ + { USB_DEVICE(0x06cd, 0x010b), + .driver_info = (unsigned long) &ez1_info, + }, +#endif + + /*-------------------------------------------------------------*/ + +#ifdef IBOT2 + /* iBOT2 makes a nice source of high speed bulk-in data */ + /* this does not coexist with a real iBOT2 driver! */ + { USB_DEVICE(0x0b62, 0x0059), + .driver_info = (unsigned long) &ibot2_info, + }, +#endif + + /*-------------------------------------------------------------*/ + +#ifdef GENERIC + /* module params can specify devices to use for control tests */ + { .driver_info = (unsigned long) &generic_info, }, +#endif + + /*-------------------------------------------------------------*/ + + { } +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_driver usbtest_driver = { + .name = "usbtest", + .id_table = id_table, + .probe = usbtest_probe, + .unlocked_ioctl = usbtest_ioctl, + .disconnect = usbtest_disconnect, + .suspend = usbtest_suspend, + .resume = usbtest_resume, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init usbtest_init(void) +{ +#ifdef GENERIC + if (vendor) + pr_debug("params: vend=0x%04x prod=0x%04x\n", vendor, product); +#endif + return usb_register(&usbtest_driver); +} +module_init(usbtest_init); + +static void __exit usbtest_exit(void) +{ + usb_deregister(&usbtest_driver); +} +module_exit(usbtest_exit); + +MODULE_DESCRIPTION("USB Core/HCD Testing Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c new file mode 100644 index 00000000..e129cf66 --- /dev/null +++ b/drivers/usb/misc/uss720.c @@ -0,0 +1,832 @@ +/*****************************************************************************/ + +/* + * uss720.c -- USS720 USB Parport Cable. + * + * Copyright (C) 1999, 2005, 2010 + * Thomas Sailer (t.sailer@alumni.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based on parport_pc.c + * + * History: + * 0.1 04.08.1999 Created + * 0.2 07.08.1999 Some fixes mainly suggested by Tim Waugh + * Interrupt handling currently disabled because + * usb_request_irq crashes somewhere within ohci.c + * for no apparent reason (that is for me, anyway) + * ECP currently untested + * 0.3 10.08.1999 fixing merge errors + * 0.4 13.08.1999 Added Vendor/Product ID of Brad Hard's cable + * 0.5 20.09.1999 usb_control_msg wrapper used + * Nov01.2000 usb_device_table support by Adam J. Richter + * 08.04.2001 Identify version on module load. gb + * 0.6 02.09.2005 Fix "scheduling in interrupt" problem by making save/restore + * context asynchronous + * + */ + +/*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.6" +#define DRIVER_AUTHOR "Thomas M. Sailer, t.sailer@alumni.ethz.ch" +#define DRIVER_DESC "USB Parport Cable driver for Cables using the Lucent Technologies USS720 Chip" + +/* --------------------------------------------------------------------- */ + +struct parport_uss720_private { + struct usb_device *usbdev; + struct parport *pp; + struct kref ref_count; + __u8 reg[7]; /* USB registers */ + struct list_head asynclist; + spinlock_t asynclock; +}; + +struct uss720_async_request { + struct parport_uss720_private *priv; + struct kref ref_count; + struct list_head asynclist; + struct completion compl; + struct urb *urb; + struct usb_ctrlrequest dr; + __u8 reg[7]; +}; + +/* --------------------------------------------------------------------- */ + +static void destroy_priv(struct kref *kref) +{ + struct parport_uss720_private *priv = container_of(kref, struct parport_uss720_private, ref_count); + + dev_dbg(&priv->usbdev->dev, "destroying priv datastructure\n"); + usb_put_dev(priv->usbdev); + kfree(priv); +} + +static void destroy_async(struct kref *kref) +{ + struct uss720_async_request *rq = container_of(kref, struct uss720_async_request, ref_count); + struct parport_uss720_private *priv = rq->priv; + unsigned long flags; + + if (likely(rq->urb)) + usb_free_urb(rq->urb); + spin_lock_irqsave(&priv->asynclock, flags); + list_del_init(&rq->asynclist); + spin_unlock_irqrestore(&priv->asynclock, flags); + kfree(rq); + kref_put(&priv->ref_count, destroy_priv); +} + +/* --------------------------------------------------------------------- */ + +static void async_complete(struct urb *urb) +{ + struct uss720_async_request *rq; + struct parport *pp; + struct parport_uss720_private *priv; + int status = urb->status; + + rq = urb->context; + priv = rq->priv; + pp = priv->pp; + if (status) { + dev_err(&urb->dev->dev, "async_complete: urb error %d\n", + status); + } else if (rq->dr.bRequest == 3) { + memcpy(priv->reg, rq->reg, sizeof(priv->reg)); +#if 0 + dev_dbg(&priv->usbdev->dev, + "async_complete regs %02x %02x %02x %02x %02x %02x %02x\n", + (unsigned int)priv->reg[0], (unsigned int)priv->reg[1], + (unsigned int)priv->reg[2], (unsigned int)priv->reg[3], + (unsigned int)priv->reg[4], (unsigned int)priv->reg[5], + (unsigned int)priv->reg[6]); +#endif + /* if nAck interrupts are enabled and we have an interrupt, call the interrupt procedure */ + if (rq->reg[2] & rq->reg[1] & 0x10 && pp) + parport_generic_irq(pp); + } + complete(&rq->compl); + kref_put(&rq->ref_count, destroy_async); +} + +static struct uss720_async_request *submit_async_request(struct parport_uss720_private *priv, + __u8 request, __u8 requesttype, __u16 value, __u16 index, + gfp_t mem_flags) +{ + struct usb_device *usbdev; + struct uss720_async_request *rq; + unsigned long flags; + int ret; + + if (!priv) + return NULL; + usbdev = priv->usbdev; + if (!usbdev) + return NULL; + rq = kmalloc(sizeof(struct uss720_async_request), mem_flags); + if (!rq) { + dev_err(&usbdev->dev, "submit_async_request out of memory\n"); + return NULL; + } + kref_init(&rq->ref_count); + INIT_LIST_HEAD(&rq->asynclist); + init_completion(&rq->compl); + kref_get(&priv->ref_count); + rq->priv = priv; + rq->urb = usb_alloc_urb(0, mem_flags); + if (!rq->urb) { + kref_put(&rq->ref_count, destroy_async); + dev_err(&usbdev->dev, "submit_async_request out of memory\n"); + return NULL; + } + rq->dr.bRequestType = requesttype; + rq->dr.bRequest = request; + rq->dr.wValue = cpu_to_le16(value); + rq->dr.wIndex = cpu_to_le16(index); + rq->dr.wLength = cpu_to_le16((request == 3) ? sizeof(rq->reg) : 0); + usb_fill_control_urb(rq->urb, usbdev, (requesttype & 0x80) ? usb_rcvctrlpipe(usbdev, 0) : usb_sndctrlpipe(usbdev, 0), + (unsigned char *)&rq->dr, + (request == 3) ? rq->reg : NULL, (request == 3) ? sizeof(rq->reg) : 0, async_complete, rq); + /* rq->urb->transfer_flags |= URB_ASYNC_UNLINK; */ + spin_lock_irqsave(&priv->asynclock, flags); + list_add_tail(&rq->asynclist, &priv->asynclist); + spin_unlock_irqrestore(&priv->asynclock, flags); + kref_get(&rq->ref_count); + ret = usb_submit_urb(rq->urb, mem_flags); + if (!ret) + return rq; + destroy_async(&rq->ref_count); + dev_err(&usbdev->dev, "submit_async_request submit_urb failed with %d\n", ret); + return NULL; +} + +static unsigned int kill_all_async_requests_priv(struct parport_uss720_private *priv) +{ + struct uss720_async_request *rq; + unsigned long flags; + unsigned int ret = 0; + + spin_lock_irqsave(&priv->asynclock, flags); + list_for_each_entry(rq, &priv->asynclist, asynclist) { + usb_unlink_urb(rq->urb); + ret++; + } + spin_unlock_irqrestore(&priv->asynclock, flags); + return ret; +} + +/* --------------------------------------------------------------------- */ + +static int get_1284_register(struct parport *pp, unsigned char reg, unsigned char *val, gfp_t mem_flags) +{ + struct parport_uss720_private *priv; + struct uss720_async_request *rq; + static const unsigned char regindex[9] = { + 4, 0, 1, 5, 5, 0, 2, 3, 6 + }; + int ret; + + if (!pp) + return -EIO; + priv = pp->private_data; + rq = submit_async_request(priv, 3, 0xc0, ((unsigned int)reg) << 8, 0, mem_flags); + if (!rq) { + dev_err(&priv->usbdev->dev, "get_1284_register(%u) failed", + (unsigned int)reg); + return -EIO; + } + if (!val) { + kref_put(&rq->ref_count, destroy_async); + return 0; + } + if (wait_for_completion_timeout(&rq->compl, HZ)) { + ret = rq->urb->status; + *val = priv->reg[(reg >= 9) ? 0 : regindex[reg]]; + if (ret) + printk(KERN_WARNING "get_1284_register: " + "usb error %d\n", ret); + kref_put(&rq->ref_count, destroy_async); + return ret; + } + printk(KERN_WARNING "get_1284_register timeout\n"); + kill_all_async_requests_priv(priv); + return -EIO; +} + +static int set_1284_register(struct parport *pp, unsigned char reg, unsigned char val, gfp_t mem_flags) +{ + struct parport_uss720_private *priv; + struct uss720_async_request *rq; + + if (!pp) + return -EIO; + priv = pp->private_data; + rq = submit_async_request(priv, 4, 0x40, (((unsigned int)reg) << 8) | val, 0, mem_flags); + if (!rq) { + dev_err(&priv->usbdev->dev, "set_1284_register(%u,%u) failed", + (unsigned int)reg, (unsigned int)val); + return -EIO; + } + kref_put(&rq->ref_count, destroy_async); + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* ECR modes */ +#define ECR_SPP 00 +#define ECR_PS2 01 +#define ECR_PPF 02 +#define ECR_ECP 03 +#define ECR_EPP 04 + +/* Safely change the mode bits in the ECR */ +static int change_mode(struct parport *pp, int m) +{ + struct parport_uss720_private *priv = pp->private_data; + int mode; + __u8 reg; + + if (get_1284_register(pp, 6, ®, GFP_KERNEL)) + return -EIO; + /* Bits <7:5> contain the mode. */ + mode = (priv->reg[2] >> 5) & 0x7; + if (mode == m) + return 0; + /* We have to go through mode 000 or 001 */ + if (mode > ECR_PS2 && m > ECR_PS2) + if (change_mode(pp, ECR_PS2)) + return -EIO; + + if (m <= ECR_PS2 && !(priv->reg[1] & 0x20)) { + /* This mode resets the FIFO, so we may + * have to wait for it to drain first. */ + unsigned long expire = jiffies + pp->physport->cad->timeout; + switch (mode) { + case ECR_PPF: /* Parallel Port FIFO mode */ + case ECR_ECP: /* ECP Parallel Port mode */ + /* Poll slowly. */ + for (;;) { + if (get_1284_register(pp, 6, ®, GFP_KERNEL)) + return -EIO; + if (priv->reg[2] & 0x01) + break; + if (time_after_eq (jiffies, expire)) + /* The FIFO is stuck. */ + return -EBUSY; + msleep_interruptible(10); + if (signal_pending (current)) + break; + } + } + } + /* Set the mode. */ + if (set_1284_register(pp, 6, m << 5, GFP_KERNEL)) + return -EIO; + if (get_1284_register(pp, 6, ®, GFP_KERNEL)) + return -EIO; + return 0; +} + +/* + * Clear TIMEOUT BIT in EPP MODE + */ +static int clear_epp_timeout(struct parport *pp) +{ + unsigned char stat; + + if (get_1284_register(pp, 1, &stat, GFP_KERNEL)) + return 1; + return stat & 1; +} + +/* + * Access functions. + */ +#if 0 +static int uss720_irq(int usbstatus, void *buffer, int len, void *dev_id) +{ + struct parport *pp = (struct parport *)dev_id; + struct parport_uss720_private *priv = pp->private_data; + + if (usbstatus != 0 || len < 4 || !buffer) + return 1; + memcpy(priv->reg, buffer, 4); + /* if nAck interrupts are enabled and we have an interrupt, call the interrupt procedure */ + if (priv->reg[2] & priv->reg[1] & 0x10) + parport_generic_irq(pp); + return 1; +} +#endif + +static void parport_uss720_write_data(struct parport *pp, unsigned char d) +{ + set_1284_register(pp, 0, d, GFP_KERNEL); +} + +static unsigned char parport_uss720_read_data(struct parport *pp) +{ + unsigned char ret; + + if (get_1284_register(pp, 0, &ret, GFP_KERNEL)) + return 0; + return ret; +} + +static void parport_uss720_write_control(struct parport *pp, unsigned char d) +{ + struct parport_uss720_private *priv = pp->private_data; + + d = (d & 0xf) | (priv->reg[1] & 0xf0); + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static unsigned char parport_uss720_read_control(struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + return priv->reg[1] & 0xf; /* Use soft copy */ +} + +static unsigned char parport_uss720_frob_control(struct parport *pp, unsigned char mask, unsigned char val) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + mask &= 0x0f; + val &= 0x0f; + d = (priv->reg[1] & (~mask)) ^ val; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return 0; + priv->reg[1] = d; + return d & 0xf; +} + +static unsigned char parport_uss720_read_status(struct parport *pp) +{ + unsigned char ret; + + if (get_1284_register(pp, 1, &ret, GFP_KERNEL)) + return 0; + return ret & 0xf8; +} + +static void parport_uss720_disable_irq(struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + d = priv->reg[1] & ~0x10; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static void parport_uss720_enable_irq(struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + d = priv->reg[1] | 0x10; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static void parport_uss720_data_forward (struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + d = priv->reg[1] & ~0x20; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static void parport_uss720_data_reverse (struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + d = priv->reg[1] | 0x20; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static void parport_uss720_init_state(struct pardevice *dev, struct parport_state *s) +{ + s->u.pc.ctr = 0xc | (dev->irq_func ? 0x10 : 0x0); + s->u.pc.ecr = 0x24; +} + +static void parport_uss720_save_state(struct parport *pp, struct parport_state *s) +{ + struct parport_uss720_private *priv = pp->private_data; + +#if 0 + if (get_1284_register(pp, 2, NULL, GFP_ATOMIC)) + return; +#endif + s->u.pc.ctr = priv->reg[1]; + s->u.pc.ecr = priv->reg[2]; +} + +static void parport_uss720_restore_state(struct parport *pp, struct parport_state *s) +{ + struct parport_uss720_private *priv = pp->private_data; + + set_1284_register(pp, 2, s->u.pc.ctr, GFP_ATOMIC); + set_1284_register(pp, 6, s->u.pc.ecr, GFP_ATOMIC); + get_1284_register(pp, 2, NULL, GFP_ATOMIC); + priv->reg[1] = s->u.pc.ctr; + priv->reg[2] = s->u.pc.ecr; +} + +static size_t parport_uss720_epp_read_data(struct parport *pp, void *buf, size_t length, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + size_t got = 0; + + if (change_mode(pp, ECR_EPP)) + return 0; + for (; got < length; got++) { + if (get_1284_register(pp, 4, (char *)buf, GFP_KERNEL)) + break; + buf++; + if (priv->reg[0] & 0x01) { + clear_epp_timeout(pp); + break; + } + } + change_mode(pp, ECR_PS2); + return got; +} + +static size_t parport_uss720_epp_write_data(struct parport *pp, const void *buf, size_t length, int flags) +{ +#if 0 + struct parport_uss720_private *priv = pp->private_data; + size_t written = 0; + + if (change_mode(pp, ECR_EPP)) + return 0; + for (; written < length; written++) { + if (set_1284_register(pp, 4, (char *)buf, GFP_KERNEL)) + break; + ((char*)buf)++; + if (get_1284_register(pp, 1, NULL, GFP_KERNEL)) + break; + if (priv->reg[0] & 0x01) { + clear_epp_timeout(pp); + break; + } + } + change_mode(pp, ECR_PS2); + return written; +#else + struct parport_uss720_private *priv = pp->private_data; + struct usb_device *usbdev = priv->usbdev; + int rlen; + int i; + + if (!usbdev) + return 0; + if (change_mode(pp, ECR_EPP)) + return 0; + i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buf, length, &rlen, 20000); + if (i) + printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %Zu rlen %u\n", buf, length, rlen); + change_mode(pp, ECR_PS2); + return rlen; +#endif +} + +static size_t parport_uss720_epp_read_addr(struct parport *pp, void *buf, size_t length, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + size_t got = 0; + + if (change_mode(pp, ECR_EPP)) + return 0; + for (; got < length; got++) { + if (get_1284_register(pp, 3, (char *)buf, GFP_KERNEL)) + break; + buf++; + if (priv->reg[0] & 0x01) { + clear_epp_timeout(pp); + break; + } + } + change_mode(pp, ECR_PS2); + return got; +} + +static size_t parport_uss720_epp_write_addr(struct parport *pp, const void *buf, size_t length, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + size_t written = 0; + + if (change_mode(pp, ECR_EPP)) + return 0; + for (; written < length; written++) { + if (set_1284_register(pp, 3, *(char *)buf, GFP_KERNEL)) + break; + buf++; + if (get_1284_register(pp, 1, NULL, GFP_KERNEL)) + break; + if (priv->reg[0] & 0x01) { + clear_epp_timeout(pp); + break; + } + } + change_mode(pp, ECR_PS2); + return written; +} + +static size_t parport_uss720_ecp_write_data(struct parport *pp, const void *buffer, size_t len, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + struct usb_device *usbdev = priv->usbdev; + int rlen; + int i; + + if (!usbdev) + return 0; + if (change_mode(pp, ECR_ECP)) + return 0; + i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buffer, len, &rlen, 20000); + if (i) + printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %Zu rlen %u\n", buffer, len, rlen); + change_mode(pp, ECR_PS2); + return rlen; +} + +static size_t parport_uss720_ecp_read_data(struct parport *pp, void *buffer, size_t len, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + struct usb_device *usbdev = priv->usbdev; + int rlen; + int i; + + if (!usbdev) + return 0; + if (change_mode(pp, ECR_ECP)) + return 0; + i = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), buffer, len, &rlen, 20000); + if (i) + printk(KERN_ERR "uss720: recvbulk ep 2 buf %p len %Zu rlen %u\n", buffer, len, rlen); + change_mode(pp, ECR_PS2); + return rlen; +} + +static size_t parport_uss720_ecp_write_addr(struct parport *pp, const void *buffer, size_t len, int flags) +{ + size_t written = 0; + + if (change_mode(pp, ECR_ECP)) + return 0; + for (; written < len; written++) { + if (set_1284_register(pp, 5, *(char *)buffer, GFP_KERNEL)) + break; + buffer++; + } + change_mode(pp, ECR_PS2); + return written; +} + +static size_t parport_uss720_write_compat(struct parport *pp, const void *buffer, size_t len, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + struct usb_device *usbdev = priv->usbdev; + int rlen; + int i; + + if (!usbdev) + return 0; + if (change_mode(pp, ECR_PPF)) + return 0; + i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buffer, len, &rlen, 20000); + if (i) + printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %Zu rlen %u\n", buffer, len, rlen); + change_mode(pp, ECR_PS2); + return rlen; +} + +/* --------------------------------------------------------------------- */ + +static struct parport_operations parport_uss720_ops = +{ + .owner = THIS_MODULE, + .write_data = parport_uss720_write_data, + .read_data = parport_uss720_read_data, + + .write_control = parport_uss720_write_control, + .read_control = parport_uss720_read_control, + .frob_control = parport_uss720_frob_control, + + .read_status = parport_uss720_read_status, + + .enable_irq = parport_uss720_enable_irq, + .disable_irq = parport_uss720_disable_irq, + + .data_forward = parport_uss720_data_forward, + .data_reverse = parport_uss720_data_reverse, + + .init_state = parport_uss720_init_state, + .save_state = parport_uss720_save_state, + .restore_state = parport_uss720_restore_state, + + .epp_write_data = parport_uss720_epp_write_data, + .epp_read_data = parport_uss720_epp_read_data, + .epp_write_addr = parport_uss720_epp_write_addr, + .epp_read_addr = parport_uss720_epp_read_addr, + + .ecp_write_data = parport_uss720_ecp_write_data, + .ecp_read_data = parport_uss720_ecp_read_data, + .ecp_write_addr = parport_uss720_ecp_write_addr, + + .compat_write_data = parport_uss720_write_compat, + .nibble_read_data = parport_ieee1284_read_nibble, + .byte_read_data = parport_ieee1284_read_byte, +}; + +/* --------------------------------------------------------------------- */ + +static int uss720_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usbdev = usb_get_dev(interface_to_usbdev(intf)); + struct usb_host_interface *interface; + struct usb_host_endpoint *endpoint; + struct parport_uss720_private *priv; + struct parport *pp; + unsigned char reg; + int i; + + dev_dbg(&intf->dev, "probe: vendor id 0x%x, device id 0x%x\n", + le16_to_cpu(usbdev->descriptor.idVendor), + le16_to_cpu(usbdev->descriptor.idProduct)); + + /* our known interfaces have 3 alternate settings */ + if (intf->num_altsetting != 3) { + usb_put_dev(usbdev); + return -ENODEV; + } + i = usb_set_interface(usbdev, intf->altsetting->desc.bInterfaceNumber, 2); + dev_dbg(&intf->dev, "set interface result %d\n", i); + + interface = intf->cur_altsetting; + + /* + * Allocate parport interface + */ + if (!(priv = kzalloc(sizeof(struct parport_uss720_private), GFP_KERNEL))) { + usb_put_dev(usbdev); + return -ENOMEM; + } + priv->pp = NULL; + priv->usbdev = usbdev; + kref_init(&priv->ref_count); + spin_lock_init(&priv->asynclock); + INIT_LIST_HEAD(&priv->asynclist); + if (!(pp = parport_register_port(0, PARPORT_IRQ_NONE, PARPORT_DMA_NONE, &parport_uss720_ops))) { + printk(KERN_WARNING "uss720: could not register parport\n"); + goto probe_abort; + } + + priv->pp = pp; + pp->private_data = priv; + pp->modes = PARPORT_MODE_PCSPP | PARPORT_MODE_TRISTATE | PARPORT_MODE_EPP | PARPORT_MODE_ECP | PARPORT_MODE_COMPAT; + + /* set the USS720 control register to manual mode, no ECP compression, enable all ints */ + set_1284_register(pp, 7, 0x00, GFP_KERNEL); + set_1284_register(pp, 6, 0x30, GFP_KERNEL); /* PS/2 mode */ + set_1284_register(pp, 2, 0x0c, GFP_KERNEL); + /* debugging */ + get_1284_register(pp, 0, ®, GFP_KERNEL); + dev_dbg(&intf->dev, "reg: %02x %02x %02x %02x %02x %02x %02x\n", + priv->reg[0], priv->reg[1], priv->reg[2], priv->reg[3], + priv->reg[4], priv->reg[5], priv->reg[6]); + + endpoint = &interface->endpoint[2]; + dev_dbg(&intf->dev, "epaddr %d interval %d\n", + endpoint->desc.bEndpointAddress, endpoint->desc.bInterval); + parport_announce_port(pp); + + usb_set_intfdata(intf, pp); + return 0; + +probe_abort: + kill_all_async_requests_priv(priv); + kref_put(&priv->ref_count, destroy_priv); + return -ENODEV; +} + +static void uss720_disconnect(struct usb_interface *intf) +{ + struct parport *pp = usb_get_intfdata(intf); + struct parport_uss720_private *priv; + struct usb_device *usbdev; + + dev_dbg(&intf->dev, "disconnect\n"); + usb_set_intfdata(intf, NULL); + if (pp) { + priv = pp->private_data; + usbdev = priv->usbdev; + priv->usbdev = NULL; + priv->pp = NULL; + dev_dbg(&intf->dev, "parport_remove_port\n"); + parport_remove_port(pp); + parport_put_port(pp); + kill_all_async_requests_priv(priv); + kref_put(&priv->ref_count, destroy_priv); + } + dev_dbg(&intf->dev, "disconnect done\n"); +} + +/* table of cables that work through this driver */ +static const struct usb_device_id uss720_table[] = { + { USB_DEVICE(0x047e, 0x1001) }, + { USB_DEVICE(0x0557, 0x2001) }, + { USB_DEVICE(0x0729, 0x1284) }, + { USB_DEVICE(0x1293, 0x0002) }, + { USB_DEVICE(0x050d, 0x0002) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, uss720_table); + + +static struct usb_driver uss720_driver = { + .name = "uss720", + .probe = uss720_probe, + .disconnect = uss720_disconnect, + .id_table = uss720_table, +}; + +/* --------------------------------------------------------------------- */ + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static int __init uss720_init(void) +{ + int retval; + retval = usb_register(&uss720_driver); + if (retval) + goto out; + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" + DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": NOTE: this is a special purpose " + "driver to allow nonstandard\n"); + printk(KERN_INFO KBUILD_MODNAME ": protocols (eg. bitbang) over " + "USS720 usb to parallel cables\n"); + printk(KERN_INFO KBUILD_MODNAME ": If you just want to connect to a " + "printer, use usblp instead\n"); +out: + return retval; +} + +static void __exit uss720_cleanup(void) +{ + usb_deregister(&uss720_driver); +} + +module_init(uss720_init); +module_exit(uss720_cleanup); + +/* --------------------------------------------------------------------- */ diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c new file mode 100644 index 00000000..b6ab515b --- /dev/null +++ b/drivers/usb/misc/yurex.c @@ -0,0 +1,548 @@ +/* + * Driver for Meywa-Denki & KAYAC YUREX + * + * Copyright (C) 2010 Tomoki Sekiyama (tomoki.sekiyama@gmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Tomoki Sekiyama" +#define DRIVER_DESC "Driver for Meywa-Denki & KAYAC YUREX" + +#define YUREX_VENDOR_ID 0x0c45 +#define YUREX_PRODUCT_ID 0x1010 + +#define CMD_ACK '!' +#define CMD_ANIMATE 'A' +#define CMD_COUNT 'C' +#define CMD_LED 'L' +#define CMD_READ 'R' +#define CMD_SET 'S' +#define CMD_VERSION 'V' +#define CMD_EOF 0x0d +#define CMD_PADDING 0xff + +#define YUREX_BUF_SIZE 8 +#define YUREX_WRITE_TIMEOUT (HZ*2) + +/* table of devices that work with this driver */ +static struct usb_device_id yurex_table[] = { + { USB_DEVICE(YUREX_VENDOR_ID, YUREX_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, yurex_table); + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define YUREX_MINOR_BASE 0 +#else +#define YUREX_MINOR_BASE 192 +#endif + +/* Structure to hold all of our device specific stuff */ +struct usb_yurex { + struct usb_device *udev; + struct usb_interface *interface; + __u8 int_in_endpointAddr; + struct urb *urb; /* URB for interrupt in */ + unsigned char *int_buffer; /* buffer for intterupt in */ + struct urb *cntl_urb; /* URB for control msg */ + struct usb_ctrlrequest *cntl_req; /* req for control msg */ + unsigned char *cntl_buffer; /* buffer for control msg */ + + struct kref kref; + struct mutex io_mutex; + struct fasync_struct *async_queue; + wait_queue_head_t waitq; + + spinlock_t lock; + __s64 bbu; /* BBU from device */ +}; +#define to_yurex_dev(d) container_of(d, struct usb_yurex, kref) + +static struct usb_driver yurex_driver; +static const struct file_operations yurex_fops; + + +static void yurex_control_callback(struct urb *urb) +{ + struct usb_yurex *dev = urb->context; + int status = urb->status; + + if (status) { + dev_err(&urb->dev->dev, "%s - control failed: %d\n", + __func__, status); + wake_up_interruptible(&dev->waitq); + return; + } + /* on success, sender woken up by CMD_ACK int in, or timeout */ +} + +static void yurex_delete(struct kref *kref) +{ + struct usb_yurex *dev = to_yurex_dev(kref); + + dev_dbg(&dev->interface->dev, "%s\n", __func__); + + usb_put_dev(dev->udev); + if (dev->cntl_urb) { + usb_kill_urb(dev->cntl_urb); + kfree(dev->cntl_req); + if (dev->cntl_buffer) + usb_free_coherent(dev->udev, YUREX_BUF_SIZE, + dev->cntl_buffer, dev->cntl_urb->transfer_dma); + usb_free_urb(dev->cntl_urb); + } + if (dev->urb) { + usb_kill_urb(dev->urb); + if (dev->int_buffer) + usb_free_coherent(dev->udev, YUREX_BUF_SIZE, + dev->int_buffer, dev->urb->transfer_dma); + usb_free_urb(dev->urb); + } + kfree(dev); +} + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver yurex_class = { + .name = "yurex%d", + .fops = &yurex_fops, + .minor_base = YUREX_MINOR_BASE, +}; + +static void yurex_interrupt(struct urb *urb) +{ + struct usb_yurex *dev = urb->context; + unsigned char *buf = dev->int_buffer; + int status = urb->status; + unsigned long flags; + int retval, i; + + switch (status) { + case 0: /*success*/ + break; + case -EOVERFLOW: + dev_err(&dev->interface->dev, + "%s - overflow with length %d, actual length is %d\n", + __func__, YUREX_BUF_SIZE, dev->urb->actual_length); + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -EILSEQ: + /* The device is terminated, clean up */ + return; + default: + dev_err(&dev->interface->dev, + "%s - unknown status received: %d\n", __func__, status); + goto exit; + } + + /* handle received message */ + switch (buf[0]) { + case CMD_COUNT: + case CMD_READ: + if (buf[6] == CMD_EOF) { + spin_lock_irqsave(&dev->lock, flags); + dev->bbu = 0; + for (i = 1; i < 6; i++) { + dev->bbu += buf[i]; + if (i != 5) + dev->bbu <<= 8; + } + dev_dbg(&dev->interface->dev, "%s count: %lld\n", + __func__, dev->bbu); + spin_unlock_irqrestore(&dev->lock, flags); + + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + } + else + dev_dbg(&dev->interface->dev, + "data format error - no EOF\n"); + break; + case CMD_ACK: + dev_dbg(&dev->interface->dev, "%s ack: %c\n", + __func__, buf[1]); + wake_up_interruptible(&dev->waitq); + break; + } + +exit: + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) { + dev_err(&dev->interface->dev, "%s - usb_submit_urb failed: %d\n", + __func__, retval); + } +} + +static int yurex_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_yurex *dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int retval = -ENOMEM; + int i; + DEFINE_WAIT(wait); + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + dev_err(&interface->dev, "Out of memory\n"); + goto error; + } + kref_init(&dev->kref); + mutex_init(&dev->io_mutex); + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->waitq); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* set up the endpoint information */ + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) { + dev->int_in_endpointAddr = endpoint->bEndpointAddress; + break; + } + } + if (!dev->int_in_endpointAddr) { + retval = -ENODEV; + dev_err(&interface->dev, "Could not find endpoints\n"); + goto error; + } + + + /* allocate control URB */ + dev->cntl_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->cntl_urb) { + dev_err(&interface->dev, "Could not allocate control URB\n"); + goto error; + } + + /* allocate buffer for control req */ + dev->cntl_req = kmalloc(YUREX_BUF_SIZE, GFP_KERNEL); + if (!dev->cntl_req) { + dev_err(&interface->dev, "Could not allocate cntl_req\n"); + goto error; + } + + /* allocate buffer for control msg */ + dev->cntl_buffer = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE, + GFP_KERNEL, + &dev->cntl_urb->transfer_dma); + if (!dev->cntl_buffer) { + dev_err(&interface->dev, "Could not allocate cntl_buffer\n"); + goto error; + } + + /* configure control URB */ + dev->cntl_req->bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE; + dev->cntl_req->bRequest = HID_REQ_SET_REPORT; + dev->cntl_req->wValue = cpu_to_le16((HID_OUTPUT_REPORT + 1) << 8); + dev->cntl_req->wIndex = cpu_to_le16(iface_desc->desc.bInterfaceNumber); + dev->cntl_req->wLength = cpu_to_le16(YUREX_BUF_SIZE); + + usb_fill_control_urb(dev->cntl_urb, dev->udev, + usb_sndctrlpipe(dev->udev, 0), + (void *)dev->cntl_req, dev->cntl_buffer, + YUREX_BUF_SIZE, yurex_control_callback, dev); + dev->cntl_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + + /* allocate interrupt URB */ + dev->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb) { + dev_err(&interface->dev, "Could not allocate URB\n"); + goto error; + } + + /* allocate buffer for interrupt in */ + dev->int_buffer = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE, + GFP_KERNEL, &dev->urb->transfer_dma); + if (!dev->int_buffer) { + dev_err(&interface->dev, "Could not allocate int_buffer\n"); + goto error; + } + + /* configure interrupt URB */ + usb_fill_int_urb(dev->urb, dev->udev, + usb_rcvintpipe(dev->udev, dev->int_in_endpointAddr), + dev->int_buffer, YUREX_BUF_SIZE, yurex_interrupt, + dev, 1); + dev->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + if (usb_submit_urb(dev->urb, GFP_KERNEL)) { + retval = -EIO; + dev_err(&interface->dev, "Could not submitting URB\n"); + goto error; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &yurex_class); + if (retval) { + dev_err(&interface->dev, + "Not able to get a minor for this device.\n"); + usb_set_intfdata(interface, NULL); + goto error; + } + + dev->bbu = -1; + + dev_info(&interface->dev, + "USB YUREX device now attached to Yurex #%d\n", + interface->minor); + + return 0; + +error: + if (dev) + /* this frees allocated memory */ + kref_put(&dev->kref, yurex_delete); + return retval; +} + +static void yurex_disconnect(struct usb_interface *interface) +{ + struct usb_yurex *dev; + int minor = interface->minor; + + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + /* give back our minor */ + usb_deregister_dev(interface, &yurex_class); + + /* prevent more I/O from starting */ + mutex_lock(&dev->io_mutex); + dev->interface = NULL; + mutex_unlock(&dev->io_mutex); + + /* wakeup waiters */ + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + wake_up_interruptible(&dev->waitq); + + /* decrement our usage count */ + kref_put(&dev->kref, yurex_delete); + + dev_info(&interface->dev, "USB YUREX #%d now disconnected\n", minor); +} + +static struct usb_driver yurex_driver = { + .name = "yurex", + .probe = yurex_probe, + .disconnect = yurex_disconnect, + .id_table = yurex_table, +}; + + +static int yurex_fasync(int fd, struct file *file, int on) +{ + struct usb_yurex *dev; + + dev = (struct usb_yurex *)file->private_data; + return fasync_helper(fd, file, on, &dev->async_queue); +} + +static int yurex_open(struct inode *inode, struct file *file) +{ + struct usb_yurex *dev; + struct usb_interface *interface; + int subminor; + int retval = 0; + + subminor = iminor(inode); + + interface = usb_find_interface(&yurex_driver, subminor); + if (!interface) { + printk(KERN_ERR "%s - error, can't find device for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* save our object in the file's private structure */ + mutex_lock(&dev->io_mutex); + file->private_data = dev; + mutex_unlock(&dev->io_mutex); + +exit: + return retval; +} + +static int yurex_release(struct inode *inode, struct file *file) +{ + struct usb_yurex *dev; + + dev = (struct usb_yurex *)file->private_data; + if (dev == NULL) + return -ENODEV; + + /* decrement the count on our device */ + kref_put(&dev->kref, yurex_delete); + return 0; +} + +static ssize_t yurex_read(struct file *file, char *buffer, size_t count, loff_t *ppos) +{ + struct usb_yurex *dev; + int retval = 0; + int bytes_read = 0; + char in_buffer[20]; + unsigned long flags; + + dev = (struct usb_yurex *)file->private_data; + + mutex_lock(&dev->io_mutex); + if (!dev->interface) { /* already disconnected */ + retval = -ENODEV; + goto exit; + } + + spin_lock_irqsave(&dev->lock, flags); + bytes_read = snprintf(in_buffer, 20, "%lld\n", dev->bbu); + spin_unlock_irqrestore(&dev->lock, flags); + + if (*ppos < bytes_read) { + if (copy_to_user(buffer, in_buffer + *ppos, bytes_read - *ppos)) + retval = -EFAULT; + else { + retval = bytes_read - *ppos; + *ppos += bytes_read; + } + } + +exit: + mutex_unlock(&dev->io_mutex); + return retval; +} + +static ssize_t yurex_write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos) +{ + struct usb_yurex *dev; + int i, set = 0, retval = 0; + char buffer[16]; + char *data = buffer; + unsigned long long c, c2 = 0; + signed long timeout = 0; + DEFINE_WAIT(wait); + + count = min(sizeof(buffer), count); + dev = (struct usb_yurex *)file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto error; + + mutex_lock(&dev->io_mutex); + if (!dev->interface) { /* alreaday disconnected */ + mutex_unlock(&dev->io_mutex); + retval = -ENODEV; + goto error; + } + + if (copy_from_user(buffer, user_buffer, count)) { + mutex_unlock(&dev->io_mutex); + retval = -EFAULT; + goto error; + } + memset(dev->cntl_buffer, CMD_PADDING, YUREX_BUF_SIZE); + + switch (buffer[0]) { + case CMD_ANIMATE: + case CMD_LED: + dev->cntl_buffer[0] = buffer[0]; + dev->cntl_buffer[1] = buffer[1]; + dev->cntl_buffer[2] = CMD_EOF; + break; + case CMD_READ: + case CMD_VERSION: + dev->cntl_buffer[0] = buffer[0]; + dev->cntl_buffer[1] = 0x00; + dev->cntl_buffer[2] = CMD_EOF; + break; + case CMD_SET: + data++; + /* FALL THROUGH */ + case '0' ... '9': + set = 1; + c = c2 = simple_strtoull(data, NULL, 0); + dev->cntl_buffer[0] = CMD_SET; + for (i = 1; i < 6; i++) { + dev->cntl_buffer[i] = (c>>32) & 0xff; + c <<= 8; + } + buffer[6] = CMD_EOF; + break; + default: + mutex_unlock(&dev->io_mutex); + return -EINVAL; + } + + /* send the data as the control msg */ + prepare_to_wait(&dev->waitq, &wait, TASK_INTERRUPTIBLE); + dev_dbg(&dev->interface->dev, "%s - submit %c\n", __func__, + dev->cntl_buffer[0]); + retval = usb_submit_urb(dev->cntl_urb, GFP_KERNEL); + if (retval >= 0) + timeout = schedule_timeout(YUREX_WRITE_TIMEOUT); + finish_wait(&dev->waitq, &wait); + + mutex_unlock(&dev->io_mutex); + + if (retval < 0) { + dev_err(&dev->interface->dev, + "%s - failed to send bulk msg, error %d\n", + __func__, retval); + goto error; + } + if (set && timeout) + dev->bbu = c2; + return timeout ? count : -EIO; + +error: + return retval; +} + +static const struct file_operations yurex_fops = { + .owner = THIS_MODULE, + .read = yurex_read, + .write = yurex_write, + .open = yurex_open, + .release = yurex_release, + .fasync = yurex_fasync, + .llseek = default_llseek, +}; + +module_usb_driver(yurex_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/mon/Kconfig b/drivers/usb/mon/Kconfig new file mode 100644 index 00000000..5c6ffa2a --- /dev/null +++ b/drivers/usb/mon/Kconfig @@ -0,0 +1,12 @@ +# +# USB Monitor configuration +# + +config USB_MON + tristate "USB Monitor" + help + If you select this option, a component which captures the USB traffic + between peripheral-specific drivers and HC drivers will be built. + For more information, see . + + If unsure, say Y, if allowed, otherwise M. diff --git a/drivers/usb/mon/Makefile b/drivers/usb/mon/Makefile new file mode 100644 index 00000000..8ed24ab0 --- /dev/null +++ b/drivers/usb/mon/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for USB monitor +# + +usbmon-y := mon_main.o mon_stat.o mon_text.o mon_bin.o + +obj-$(CONFIG_USB_MON) += usbmon.o diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c new file mode 100644 index 00000000..9a62e89d --- /dev/null +++ b/drivers/usb/mon/mon_bin.c @@ -0,0 +1,1389 @@ +/* + * The USB Monitor, inspired by Dave Harding's USBMon. + * + * This is a binary format reader. + * + * Copyright (C) 2006 Paolo Abeni (paolo.abeni@email.it) + * Copyright (C) 2006,2007 Pete Zaitcev (zaitcev@redhat.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "usb_mon.h" + +/* + * Defined by USB 2.0 clause 9.3, table 9.2. + */ +#define SETUP_LEN 8 + +/* ioctl macros */ +#define MON_IOC_MAGIC 0x92 + +#define MON_IOCQ_URB_LEN _IO(MON_IOC_MAGIC, 1) +/* #2 used to be MON_IOCX_URB, removed before it got into Linus tree */ +#define MON_IOCG_STATS _IOR(MON_IOC_MAGIC, 3, struct mon_bin_stats) +#define MON_IOCT_RING_SIZE _IO(MON_IOC_MAGIC, 4) +#define MON_IOCQ_RING_SIZE _IO(MON_IOC_MAGIC, 5) +#define MON_IOCX_GET _IOW(MON_IOC_MAGIC, 6, struct mon_bin_get) +#define MON_IOCX_MFETCH _IOWR(MON_IOC_MAGIC, 7, struct mon_bin_mfetch) +#define MON_IOCH_MFLUSH _IO(MON_IOC_MAGIC, 8) +/* #9 was MON_IOCT_SETAPI */ +#define MON_IOCX_GETX _IOW(MON_IOC_MAGIC, 10, struct mon_bin_get) + +#ifdef CONFIG_COMPAT +#define MON_IOCX_GET32 _IOW(MON_IOC_MAGIC, 6, struct mon_bin_get32) +#define MON_IOCX_MFETCH32 _IOWR(MON_IOC_MAGIC, 7, struct mon_bin_mfetch32) +#define MON_IOCX_GETX32 _IOW(MON_IOC_MAGIC, 10, struct mon_bin_get32) +#endif + +/* + * Some architectures have enormous basic pages (16KB for ia64, 64KB for ppc). + * But it's all right. Just use a simple way to make sure the chunk is never + * smaller than a page. + * + * N.B. An application does not know our chunk size. + * + * Woops, get_zeroed_page() returns a single page. I guess we're stuck with + * page-sized chunks for the time being. + */ +#define CHUNK_SIZE PAGE_SIZE +#define CHUNK_ALIGN(x) (((x)+CHUNK_SIZE-1) & ~(CHUNK_SIZE-1)) + +/* + * The magic limit was calculated so that it allows the monitoring + * application to pick data once in two ticks. This way, another application, + * which presumably drives the bus, gets to hog CPU, yet we collect our data. + * If HZ is 100, a 480 mbit/s bus drives 614 KB every jiffy. USB has an + * enormous overhead built into the bus protocol, so we need about 1000 KB. + * + * This is still too much for most cases, where we just snoop a few + * descriptor fetches for enumeration. So, the default is a "reasonable" + * amount for systems with HZ=250 and incomplete bus saturation. + * + * XXX What about multi-megabyte URBs which take minutes to transfer? + */ +#define BUFF_MAX CHUNK_ALIGN(1200*1024) +#define BUFF_DFL CHUNK_ALIGN(300*1024) +#define BUFF_MIN CHUNK_ALIGN(8*1024) + +/* + * The per-event API header (2 per URB). + * + * This structure is seen in userland as defined by the documentation. + */ +struct mon_bin_hdr { + u64 id; /* URB ID - from submission to callback */ + unsigned char type; /* Same as in text API; extensible. */ + unsigned char xfer_type; /* ISO, Intr, Control, Bulk */ + unsigned char epnum; /* Endpoint number and transfer direction */ + unsigned char devnum; /* Device address */ + unsigned short busnum; /* Bus number */ + char flag_setup; + char flag_data; + s64 ts_sec; /* gettimeofday */ + s32 ts_usec; /* gettimeofday */ + int status; + unsigned int len_urb; /* Length of data (submitted or actual) */ + unsigned int len_cap; /* Delivered length */ + union { + unsigned char setup[SETUP_LEN]; /* Only for Control S-type */ + struct iso_rec { + int error_count; + int numdesc; + } iso; + } s; + int interval; + int start_frame; + unsigned int xfer_flags; + unsigned int ndesc; /* Actual number of ISO descriptors */ +}; + +/* + * ISO vector, packed into the head of data stream. + * This has to take 16 bytes to make sure that the end of buffer + * wrap is not happening in the middle of a descriptor. + */ +struct mon_bin_isodesc { + int iso_status; + unsigned int iso_off; + unsigned int iso_len; + u32 _pad; +}; + +/* per file statistic */ +struct mon_bin_stats { + u32 queued; + u32 dropped; +}; + +struct mon_bin_get { + struct mon_bin_hdr __user *hdr; /* Can be 48 bytes or 64. */ + void __user *data; + size_t alloc; /* Length of data (can be zero) */ +}; + +struct mon_bin_mfetch { + u32 __user *offvec; /* Vector of events fetched */ + u32 nfetch; /* Number of events to fetch (out: fetched) */ + u32 nflush; /* Number of events to flush */ +}; + +#ifdef CONFIG_COMPAT +struct mon_bin_get32 { + u32 hdr32; + u32 data32; + u32 alloc32; +}; + +struct mon_bin_mfetch32 { + u32 offvec32; + u32 nfetch32; + u32 nflush32; +}; +#endif + +/* Having these two values same prevents wrapping of the mon_bin_hdr */ +#define PKT_ALIGN 64 +#define PKT_SIZE 64 + +#define PKT_SZ_API0 48 /* API 0 (2.6.20) size */ +#define PKT_SZ_API1 64 /* API 1 size: extra fields */ + +#define ISODESC_MAX 128 /* Same number as usbfs allows, 2048 bytes. */ + +/* max number of USB bus supported */ +#define MON_BIN_MAX_MINOR 128 + +/* + * The buffer: map of used pages. + */ +struct mon_pgmap { + struct page *pg; + unsigned char *ptr; /* XXX just use page_to_virt everywhere? */ +}; + +/* + * This gets associated with an open file struct. + */ +struct mon_reader_bin { + /* The buffer: one per open. */ + spinlock_t b_lock; /* Protect b_cnt, b_in */ + unsigned int b_size; /* Current size of the buffer - bytes */ + unsigned int b_cnt; /* Bytes used */ + unsigned int b_in, b_out; /* Offsets into buffer - bytes */ + unsigned int b_read; /* Amount of read data in curr. pkt. */ + struct mon_pgmap *b_vec; /* The map array */ + wait_queue_head_t b_wait; /* Wait for data here */ + + struct mutex fetch_lock; /* Protect b_read, b_out */ + int mmap_active; + + /* A list of these is needed for "bus 0". Some time later. */ + struct mon_reader r; + + /* Stats */ + unsigned int cnt_lost; +}; + +static inline struct mon_bin_hdr *MON_OFF2HDR(const struct mon_reader_bin *rp, + unsigned int offset) +{ + return (struct mon_bin_hdr *) + (rp->b_vec[offset / CHUNK_SIZE].ptr + offset % CHUNK_SIZE); +} + +#define MON_RING_EMPTY(rp) ((rp)->b_cnt == 0) + +static unsigned char xfer_to_pipe[4] = { + PIPE_CONTROL, PIPE_ISOCHRONOUS, PIPE_BULK, PIPE_INTERRUPT +}; + +static struct class *mon_bin_class; +static dev_t mon_bin_dev0; +static struct cdev mon_bin_cdev; + +static void mon_buff_area_fill(const struct mon_reader_bin *rp, + unsigned int offset, unsigned int size); +static int mon_bin_wait_event(struct file *file, struct mon_reader_bin *rp); +static int mon_alloc_buff(struct mon_pgmap *map, int npages); +static void mon_free_buff(struct mon_pgmap *map, int npages); + +/* + * This is a "chunked memcpy". It does not manipulate any counters. + */ +static unsigned int mon_copy_to_buff(const struct mon_reader_bin *this, + unsigned int off, const unsigned char *from, unsigned int length) +{ + unsigned int step_len; + unsigned char *buf; + unsigned int in_page; + + while (length) { + /* + * Determine step_len. + */ + step_len = length; + in_page = CHUNK_SIZE - (off & (CHUNK_SIZE-1)); + if (in_page < step_len) + step_len = in_page; + + /* + * Copy data and advance pointers. + */ + buf = this->b_vec[off / CHUNK_SIZE].ptr + off % CHUNK_SIZE; + memcpy(buf, from, step_len); + if ((off += step_len) >= this->b_size) off = 0; + from += step_len; + length -= step_len; + } + return off; +} + +/* + * This is a little worse than the above because it's "chunked copy_to_user". + * The return value is an error code, not an offset. + */ +static int copy_from_buf(const struct mon_reader_bin *this, unsigned int off, + char __user *to, int length) +{ + unsigned int step_len; + unsigned char *buf; + unsigned int in_page; + + while (length) { + /* + * Determine step_len. + */ + step_len = length; + in_page = CHUNK_SIZE - (off & (CHUNK_SIZE-1)); + if (in_page < step_len) + step_len = in_page; + + /* + * Copy data and advance pointers. + */ + buf = this->b_vec[off / CHUNK_SIZE].ptr + off % CHUNK_SIZE; + if (copy_to_user(to, buf, step_len)) + return -EINVAL; + if ((off += step_len) >= this->b_size) off = 0; + to += step_len; + length -= step_len; + } + return 0; +} + +/* + * Allocate an (aligned) area in the buffer. + * This is called under b_lock. + * Returns ~0 on failure. + */ +static unsigned int mon_buff_area_alloc(struct mon_reader_bin *rp, + unsigned int size) +{ + unsigned int offset; + + size = (size + PKT_ALIGN-1) & ~(PKT_ALIGN-1); + if (rp->b_cnt + size > rp->b_size) + return ~0; + offset = rp->b_in; + rp->b_cnt += size; + if ((rp->b_in += size) >= rp->b_size) + rp->b_in -= rp->b_size; + return offset; +} + +/* + * This is the same thing as mon_buff_area_alloc, only it does not allow + * buffers to wrap. This is needed by applications which pass references + * into mmap-ed buffers up their stacks (libpcap can do that). + * + * Currently, we always have the header stuck with the data, although + * it is not strictly speaking necessary. + * + * When a buffer would wrap, we place a filler packet to mark the space. + */ +static unsigned int mon_buff_area_alloc_contiguous(struct mon_reader_bin *rp, + unsigned int size) +{ + unsigned int offset; + unsigned int fill_size; + + size = (size + PKT_ALIGN-1) & ~(PKT_ALIGN-1); + if (rp->b_cnt + size > rp->b_size) + return ~0; + if (rp->b_in + size > rp->b_size) { + /* + * This would wrap. Find if we still have space after + * skipping to the end of the buffer. If we do, place + * a filler packet and allocate a new packet. + */ + fill_size = rp->b_size - rp->b_in; + if (rp->b_cnt + size + fill_size > rp->b_size) + return ~0; + mon_buff_area_fill(rp, rp->b_in, fill_size); + + offset = 0; + rp->b_in = size; + rp->b_cnt += size + fill_size; + } else if (rp->b_in + size == rp->b_size) { + offset = rp->b_in; + rp->b_in = 0; + rp->b_cnt += size; + } else { + offset = rp->b_in; + rp->b_in += size; + rp->b_cnt += size; + } + return offset; +} + +/* + * Return a few (kilo-)bytes to the head of the buffer. + * This is used if a data fetch fails. + */ +static void mon_buff_area_shrink(struct mon_reader_bin *rp, unsigned int size) +{ + + /* size &= ~(PKT_ALIGN-1); -- we're called with aligned size */ + rp->b_cnt -= size; + if (rp->b_in < size) + rp->b_in += rp->b_size; + rp->b_in -= size; +} + +/* + * This has to be called under both b_lock and fetch_lock, because + * it accesses both b_cnt and b_out. + */ +static void mon_buff_area_free(struct mon_reader_bin *rp, unsigned int size) +{ + + size = (size + PKT_ALIGN-1) & ~(PKT_ALIGN-1); + rp->b_cnt -= size; + if ((rp->b_out += size) >= rp->b_size) + rp->b_out -= rp->b_size; +} + +static void mon_buff_area_fill(const struct mon_reader_bin *rp, + unsigned int offset, unsigned int size) +{ + struct mon_bin_hdr *ep; + + ep = MON_OFF2HDR(rp, offset); + memset(ep, 0, PKT_SIZE); + ep->type = '@'; + ep->len_cap = size - PKT_SIZE; +} + +static inline char mon_bin_get_setup(unsigned char *setupb, + const struct urb *urb, char ev_type) +{ + + if (urb->setup_packet == NULL) + return 'Z'; + memcpy(setupb, urb->setup_packet, SETUP_LEN); + return 0; +} + +static unsigned int mon_bin_get_data(const struct mon_reader_bin *rp, + unsigned int offset, struct urb *urb, unsigned int length, + char *flag) +{ + int i; + struct scatterlist *sg; + unsigned int this_len; + + *flag = 0; + if (urb->num_sgs == 0) { + if (urb->transfer_buffer == NULL) { + *flag = 'Z'; + return length; + } + mon_copy_to_buff(rp, offset, urb->transfer_buffer, length); + length = 0; + + } else { + /* If IOMMU coalescing occurred, we cannot trust sg_page */ + if (urb->transfer_flags & URB_DMA_SG_COMBINED) { + *flag = 'D'; + return length; + } + + /* Copy up to the first non-addressable segment */ + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + if (length == 0 || PageHighMem(sg_page(sg))) + break; + this_len = min_t(unsigned int, sg->length, length); + offset = mon_copy_to_buff(rp, offset, sg_virt(sg), + this_len); + length -= this_len; + } + if (i == 0) + *flag = 'D'; + } + + return length; +} + +/* + * This is the look-ahead pass in case of 'C Zi', when actual_length cannot + * be used to determine the length of the whole contiguous buffer. + */ +static unsigned int mon_bin_collate_isodesc(const struct mon_reader_bin *rp, + struct urb *urb, unsigned int ndesc) +{ + struct usb_iso_packet_descriptor *fp; + unsigned int length; + + length = 0; + fp = urb->iso_frame_desc; + while (ndesc-- != 0) { + if (fp->actual_length != 0) { + if (fp->offset + fp->actual_length > length) + length = fp->offset + fp->actual_length; + } + fp++; + } + return length; +} + +static void mon_bin_get_isodesc(const struct mon_reader_bin *rp, + unsigned int offset, struct urb *urb, char ev_type, unsigned int ndesc) +{ + struct mon_bin_isodesc *dp; + struct usb_iso_packet_descriptor *fp; + + fp = urb->iso_frame_desc; + while (ndesc-- != 0) { + dp = (struct mon_bin_isodesc *) + (rp->b_vec[offset / CHUNK_SIZE].ptr + offset % CHUNK_SIZE); + dp->iso_status = fp->status; + dp->iso_off = fp->offset; + dp->iso_len = (ev_type == 'S') ? fp->length : fp->actual_length; + dp->_pad = 0; + if ((offset += sizeof(struct mon_bin_isodesc)) >= rp->b_size) + offset = 0; + fp++; + } +} + +static void mon_bin_event(struct mon_reader_bin *rp, struct urb *urb, + char ev_type, int status) +{ + const struct usb_endpoint_descriptor *epd = &urb->ep->desc; + struct timeval ts; + unsigned long flags; + unsigned int urb_length; + unsigned int offset; + unsigned int length; + unsigned int delta; + unsigned int ndesc, lendesc; + unsigned char dir; + struct mon_bin_hdr *ep; + char data_tag = 0; + + do_gettimeofday(&ts); + + spin_lock_irqsave(&rp->b_lock, flags); + + /* + * Find the maximum allowable length, then allocate space. + */ + urb_length = (ev_type == 'S') ? + urb->transfer_buffer_length : urb->actual_length; + length = urb_length; + + if (usb_endpoint_xfer_isoc(epd)) { + if (urb->number_of_packets < 0) { + ndesc = 0; + } else if (urb->number_of_packets >= ISODESC_MAX) { + ndesc = ISODESC_MAX; + } else { + ndesc = urb->number_of_packets; + } + if (ev_type == 'C' && usb_urb_dir_in(urb)) + length = mon_bin_collate_isodesc(rp, urb, ndesc); + } else { + ndesc = 0; + } + lendesc = ndesc*sizeof(struct mon_bin_isodesc); + + /* not an issue unless there's a subtle bug in a HCD somewhere */ + if (length >= urb->transfer_buffer_length) + length = urb->transfer_buffer_length; + + if (length >= rp->b_size/5) + length = rp->b_size/5; + + if (usb_urb_dir_in(urb)) { + if (ev_type == 'S') { + length = 0; + data_tag = '<'; + } + /* Cannot rely on endpoint number in case of control ep.0 */ + dir = USB_DIR_IN; + } else { + if (ev_type == 'C') { + length = 0; + data_tag = '>'; + } + dir = 0; + } + + if (rp->mmap_active) { + offset = mon_buff_area_alloc_contiguous(rp, + length + PKT_SIZE + lendesc); + } else { + offset = mon_buff_area_alloc(rp, length + PKT_SIZE + lendesc); + } + if (offset == ~0) { + rp->cnt_lost++; + spin_unlock_irqrestore(&rp->b_lock, flags); + return; + } + + ep = MON_OFF2HDR(rp, offset); + if ((offset += PKT_SIZE) >= rp->b_size) offset = 0; + + /* + * Fill the allocated area. + */ + memset(ep, 0, PKT_SIZE); + ep->type = ev_type; + ep->xfer_type = xfer_to_pipe[usb_endpoint_type(epd)]; + ep->epnum = dir | usb_endpoint_num(epd); + ep->devnum = urb->dev->devnum; + ep->busnum = urb->dev->bus->busnum; + ep->id = (unsigned long) urb; + ep->ts_sec = ts.tv_sec; + ep->ts_usec = ts.tv_usec; + ep->status = status; + ep->len_urb = urb_length; + ep->len_cap = length + lendesc; + ep->xfer_flags = urb->transfer_flags; + + if (usb_endpoint_xfer_int(epd)) { + ep->interval = urb->interval; + } else if (usb_endpoint_xfer_isoc(epd)) { + ep->interval = urb->interval; + ep->start_frame = urb->start_frame; + ep->s.iso.error_count = urb->error_count; + ep->s.iso.numdesc = urb->number_of_packets; + } + + if (usb_endpoint_xfer_control(epd) && ev_type == 'S') { + ep->flag_setup = mon_bin_get_setup(ep->s.setup, urb, ev_type); + } else { + ep->flag_setup = '-'; + } + + if (ndesc != 0) { + ep->ndesc = ndesc; + mon_bin_get_isodesc(rp, offset, urb, ev_type, ndesc); + if ((offset += lendesc) >= rp->b_size) + offset -= rp->b_size; + } + + if (length != 0) { + length = mon_bin_get_data(rp, offset, urb, length, + &ep->flag_data); + if (length > 0) { + delta = (ep->len_cap + PKT_ALIGN-1) & ~(PKT_ALIGN-1); + ep->len_cap -= length; + delta -= (ep->len_cap + PKT_ALIGN-1) & ~(PKT_ALIGN-1); + mon_buff_area_shrink(rp, delta); + } + } else { + ep->flag_data = data_tag; + } + + spin_unlock_irqrestore(&rp->b_lock, flags); + + wake_up(&rp->b_wait); +} + +static void mon_bin_submit(void *data, struct urb *urb) +{ + struct mon_reader_bin *rp = data; + mon_bin_event(rp, urb, 'S', -EINPROGRESS); +} + +static void mon_bin_complete(void *data, struct urb *urb, int status) +{ + struct mon_reader_bin *rp = data; + mon_bin_event(rp, urb, 'C', status); +} + +static void mon_bin_error(void *data, struct urb *urb, int error) +{ + struct mon_reader_bin *rp = data; + struct timeval ts; + unsigned long flags; + unsigned int offset; + struct mon_bin_hdr *ep; + + do_gettimeofday(&ts); + + spin_lock_irqsave(&rp->b_lock, flags); + + offset = mon_buff_area_alloc(rp, PKT_SIZE); + if (offset == ~0) { + /* Not incrementing cnt_lost. Just because. */ + spin_unlock_irqrestore(&rp->b_lock, flags); + return; + } + + ep = MON_OFF2HDR(rp, offset); + + memset(ep, 0, PKT_SIZE); + ep->type = 'E'; + ep->xfer_type = xfer_to_pipe[usb_endpoint_type(&urb->ep->desc)]; + ep->epnum = usb_urb_dir_in(urb) ? USB_DIR_IN : 0; + ep->epnum |= usb_endpoint_num(&urb->ep->desc); + ep->devnum = urb->dev->devnum; + ep->busnum = urb->dev->bus->busnum; + ep->id = (unsigned long) urb; + ep->ts_sec = ts.tv_sec; + ep->ts_usec = ts.tv_usec; + ep->status = error; + + ep->flag_setup = '-'; + ep->flag_data = 'E'; + + spin_unlock_irqrestore(&rp->b_lock, flags); + + wake_up(&rp->b_wait); +} + +static int mon_bin_open(struct inode *inode, struct file *file) +{ + struct mon_bus *mbus; + struct mon_reader_bin *rp; + size_t size; + int rc; + + mutex_lock(&mon_lock); + if ((mbus = mon_bus_lookup(iminor(inode))) == NULL) { + mutex_unlock(&mon_lock); + return -ENODEV; + } + if (mbus != &mon_bus0 && mbus->u_bus == NULL) { + printk(KERN_ERR TAG ": consistency error on open\n"); + mutex_unlock(&mon_lock); + return -ENODEV; + } + + rp = kzalloc(sizeof(struct mon_reader_bin), GFP_KERNEL); + if (rp == NULL) { + rc = -ENOMEM; + goto err_alloc; + } + spin_lock_init(&rp->b_lock); + init_waitqueue_head(&rp->b_wait); + mutex_init(&rp->fetch_lock); + rp->b_size = BUFF_DFL; + + size = sizeof(struct mon_pgmap) * (rp->b_size/CHUNK_SIZE); + if ((rp->b_vec = kzalloc(size, GFP_KERNEL)) == NULL) { + rc = -ENOMEM; + goto err_allocvec; + } + + if ((rc = mon_alloc_buff(rp->b_vec, rp->b_size/CHUNK_SIZE)) < 0) + goto err_allocbuff; + + rp->r.m_bus = mbus; + rp->r.r_data = rp; + rp->r.rnf_submit = mon_bin_submit; + rp->r.rnf_error = mon_bin_error; + rp->r.rnf_complete = mon_bin_complete; + + mon_reader_add(mbus, &rp->r); + + file->private_data = rp; + mutex_unlock(&mon_lock); + return 0; + +err_allocbuff: + kfree(rp->b_vec); +err_allocvec: + kfree(rp); +err_alloc: + mutex_unlock(&mon_lock); + return rc; +} + +/* + * Extract an event from buffer and copy it to user space. + * Wait if there is no event ready. + * Returns zero or error. + */ +static int mon_bin_get_event(struct file *file, struct mon_reader_bin *rp, + struct mon_bin_hdr __user *hdr, unsigned int hdrbytes, + void __user *data, unsigned int nbytes) +{ + unsigned long flags; + struct mon_bin_hdr *ep; + size_t step_len; + unsigned int offset; + int rc; + + mutex_lock(&rp->fetch_lock); + + if ((rc = mon_bin_wait_event(file, rp)) < 0) { + mutex_unlock(&rp->fetch_lock); + return rc; + } + + ep = MON_OFF2HDR(rp, rp->b_out); + + if (copy_to_user(hdr, ep, hdrbytes)) { + mutex_unlock(&rp->fetch_lock); + return -EFAULT; + } + + step_len = min(ep->len_cap, nbytes); + if ((offset = rp->b_out + PKT_SIZE) >= rp->b_size) offset = 0; + + if (copy_from_buf(rp, offset, data, step_len)) { + mutex_unlock(&rp->fetch_lock); + return -EFAULT; + } + + spin_lock_irqsave(&rp->b_lock, flags); + mon_buff_area_free(rp, PKT_SIZE + ep->len_cap); + spin_unlock_irqrestore(&rp->b_lock, flags); + rp->b_read = 0; + + mutex_unlock(&rp->fetch_lock); + return 0; +} + +static int mon_bin_release(struct inode *inode, struct file *file) +{ + struct mon_reader_bin *rp = file->private_data; + struct mon_bus* mbus = rp->r.m_bus; + + mutex_lock(&mon_lock); + + if (mbus->nreaders <= 0) { + printk(KERN_ERR TAG ": consistency error on close\n"); + mutex_unlock(&mon_lock); + return 0; + } + mon_reader_del(mbus, &rp->r); + + mon_free_buff(rp->b_vec, rp->b_size/CHUNK_SIZE); + kfree(rp->b_vec); + kfree(rp); + + mutex_unlock(&mon_lock); + return 0; +} + +static ssize_t mon_bin_read(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct mon_reader_bin *rp = file->private_data; + unsigned int hdrbytes = PKT_SZ_API0; + unsigned long flags; + struct mon_bin_hdr *ep; + unsigned int offset; + size_t step_len; + char *ptr; + ssize_t done = 0; + int rc; + + mutex_lock(&rp->fetch_lock); + + if ((rc = mon_bin_wait_event(file, rp)) < 0) { + mutex_unlock(&rp->fetch_lock); + return rc; + } + + ep = MON_OFF2HDR(rp, rp->b_out); + + if (rp->b_read < hdrbytes) { + step_len = min(nbytes, (size_t)(hdrbytes - rp->b_read)); + ptr = ((char *)ep) + rp->b_read; + if (step_len && copy_to_user(buf, ptr, step_len)) { + mutex_unlock(&rp->fetch_lock); + return -EFAULT; + } + nbytes -= step_len; + buf += step_len; + rp->b_read += step_len; + done += step_len; + } + + if (rp->b_read >= hdrbytes) { + step_len = ep->len_cap; + step_len -= rp->b_read - hdrbytes; + if (step_len > nbytes) + step_len = nbytes; + offset = rp->b_out + PKT_SIZE; + offset += rp->b_read - hdrbytes; + if (offset >= rp->b_size) + offset -= rp->b_size; + if (copy_from_buf(rp, offset, buf, step_len)) { + mutex_unlock(&rp->fetch_lock); + return -EFAULT; + } + nbytes -= step_len; + buf += step_len; + rp->b_read += step_len; + done += step_len; + } + + /* + * Check if whole packet was read, and if so, jump to the next one. + */ + if (rp->b_read >= hdrbytes + ep->len_cap) { + spin_lock_irqsave(&rp->b_lock, flags); + mon_buff_area_free(rp, PKT_SIZE + ep->len_cap); + spin_unlock_irqrestore(&rp->b_lock, flags); + rp->b_read = 0; + } + + mutex_unlock(&rp->fetch_lock); + return done; +} + +/* + * Remove at most nevents from chunked buffer. + * Returns the number of removed events. + */ +static int mon_bin_flush(struct mon_reader_bin *rp, unsigned nevents) +{ + unsigned long flags; + struct mon_bin_hdr *ep; + int i; + + mutex_lock(&rp->fetch_lock); + spin_lock_irqsave(&rp->b_lock, flags); + for (i = 0; i < nevents; ++i) { + if (MON_RING_EMPTY(rp)) + break; + + ep = MON_OFF2HDR(rp, rp->b_out); + mon_buff_area_free(rp, PKT_SIZE + ep->len_cap); + } + spin_unlock_irqrestore(&rp->b_lock, flags); + rp->b_read = 0; + mutex_unlock(&rp->fetch_lock); + return i; +} + +/* + * Fetch at most max event offsets into the buffer and put them into vec. + * The events are usually freed later with mon_bin_flush. + * Return the effective number of events fetched. + */ +static int mon_bin_fetch(struct file *file, struct mon_reader_bin *rp, + u32 __user *vec, unsigned int max) +{ + unsigned int cur_out; + unsigned int bytes, avail; + unsigned int size; + unsigned int nevents; + struct mon_bin_hdr *ep; + unsigned long flags; + int rc; + + mutex_lock(&rp->fetch_lock); + + if ((rc = mon_bin_wait_event(file, rp)) < 0) { + mutex_unlock(&rp->fetch_lock); + return rc; + } + + spin_lock_irqsave(&rp->b_lock, flags); + avail = rp->b_cnt; + spin_unlock_irqrestore(&rp->b_lock, flags); + + cur_out = rp->b_out; + nevents = 0; + bytes = 0; + while (bytes < avail) { + if (nevents >= max) + break; + + ep = MON_OFF2HDR(rp, cur_out); + if (put_user(cur_out, &vec[nevents])) { + mutex_unlock(&rp->fetch_lock); + return -EFAULT; + } + + nevents++; + size = ep->len_cap + PKT_SIZE; + size = (size + PKT_ALIGN-1) & ~(PKT_ALIGN-1); + if ((cur_out += size) >= rp->b_size) + cur_out -= rp->b_size; + bytes += size; + } + + mutex_unlock(&rp->fetch_lock); + return nevents; +} + +/* + * Count events. This is almost the same as the above mon_bin_fetch, + * only we do not store offsets into user vector, and we have no limit. + */ +static int mon_bin_queued(struct mon_reader_bin *rp) +{ + unsigned int cur_out; + unsigned int bytes, avail; + unsigned int size; + unsigned int nevents; + struct mon_bin_hdr *ep; + unsigned long flags; + + mutex_lock(&rp->fetch_lock); + + spin_lock_irqsave(&rp->b_lock, flags); + avail = rp->b_cnt; + spin_unlock_irqrestore(&rp->b_lock, flags); + + cur_out = rp->b_out; + nevents = 0; + bytes = 0; + while (bytes < avail) { + ep = MON_OFF2HDR(rp, cur_out); + + nevents++; + size = ep->len_cap + PKT_SIZE; + size = (size + PKT_ALIGN-1) & ~(PKT_ALIGN-1); + if ((cur_out += size) >= rp->b_size) + cur_out -= rp->b_size; + bytes += size; + } + + mutex_unlock(&rp->fetch_lock); + return nevents; +} + +/* + */ +static long mon_bin_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct mon_reader_bin *rp = file->private_data; + // struct mon_bus* mbus = rp->r.m_bus; + int ret = 0; + struct mon_bin_hdr *ep; + unsigned long flags; + + switch (cmd) { + + case MON_IOCQ_URB_LEN: + /* + * N.B. This only returns the size of data, without the header. + */ + spin_lock_irqsave(&rp->b_lock, flags); + if (!MON_RING_EMPTY(rp)) { + ep = MON_OFF2HDR(rp, rp->b_out); + ret = ep->len_cap; + } + spin_unlock_irqrestore(&rp->b_lock, flags); + break; + + case MON_IOCQ_RING_SIZE: + ret = rp->b_size; + break; + + case MON_IOCT_RING_SIZE: + /* + * Changing the buffer size will flush it's contents; the new + * buffer is allocated before releasing the old one to be sure + * the device will stay functional also in case of memory + * pressure. + */ + { + int size; + struct mon_pgmap *vec; + + if (arg < BUFF_MIN || arg > BUFF_MAX) + return -EINVAL; + + size = CHUNK_ALIGN(arg); + if ((vec = kzalloc(sizeof(struct mon_pgmap) * (size/CHUNK_SIZE), + GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + break; + } + + ret = mon_alloc_buff(vec, size/CHUNK_SIZE); + if (ret < 0) { + kfree(vec); + break; + } + + mutex_lock(&rp->fetch_lock); + spin_lock_irqsave(&rp->b_lock, flags); + mon_free_buff(rp->b_vec, rp->b_size/CHUNK_SIZE); + kfree(rp->b_vec); + rp->b_vec = vec; + rp->b_size = size; + rp->b_read = rp->b_in = rp->b_out = rp->b_cnt = 0; + rp->cnt_lost = 0; + spin_unlock_irqrestore(&rp->b_lock, flags); + mutex_unlock(&rp->fetch_lock); + } + break; + + case MON_IOCH_MFLUSH: + ret = mon_bin_flush(rp, arg); + break; + + case MON_IOCX_GET: + case MON_IOCX_GETX: + { + struct mon_bin_get getb; + + if (copy_from_user(&getb, (void __user *)arg, + sizeof(struct mon_bin_get))) + return -EFAULT; + + if (getb.alloc > 0x10000000) /* Want to cast to u32 */ + return -EINVAL; + ret = mon_bin_get_event(file, rp, getb.hdr, + (cmd == MON_IOCX_GET)? PKT_SZ_API0: PKT_SZ_API1, + getb.data, (unsigned int)getb.alloc); + } + break; + + case MON_IOCX_MFETCH: + { + struct mon_bin_mfetch mfetch; + struct mon_bin_mfetch __user *uptr; + + uptr = (struct mon_bin_mfetch __user *)arg; + + if (copy_from_user(&mfetch, uptr, sizeof(mfetch))) + return -EFAULT; + + if (mfetch.nflush) { + ret = mon_bin_flush(rp, mfetch.nflush); + if (ret < 0) + return ret; + if (put_user(ret, &uptr->nflush)) + return -EFAULT; + } + ret = mon_bin_fetch(file, rp, mfetch.offvec, mfetch.nfetch); + if (ret < 0) + return ret; + if (put_user(ret, &uptr->nfetch)) + return -EFAULT; + ret = 0; + } + break; + + case MON_IOCG_STATS: { + struct mon_bin_stats __user *sp; + unsigned int nevents; + unsigned int ndropped; + + spin_lock_irqsave(&rp->b_lock, flags); + ndropped = rp->cnt_lost; + rp->cnt_lost = 0; + spin_unlock_irqrestore(&rp->b_lock, flags); + nevents = mon_bin_queued(rp); + + sp = (struct mon_bin_stats __user *)arg; + if (put_user(ndropped, &sp->dropped)) + return -EFAULT; + if (put_user(nevents, &sp->queued)) + return -EFAULT; + + } + break; + + default: + return -ENOTTY; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long mon_bin_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct mon_reader_bin *rp = file->private_data; + int ret; + + switch (cmd) { + + case MON_IOCX_GET32: + case MON_IOCX_GETX32: + { + struct mon_bin_get32 getb; + + if (copy_from_user(&getb, (void __user *)arg, + sizeof(struct mon_bin_get32))) + return -EFAULT; + + ret = mon_bin_get_event(file, rp, compat_ptr(getb.hdr32), + (cmd == MON_IOCX_GET32)? PKT_SZ_API0: PKT_SZ_API1, + compat_ptr(getb.data32), getb.alloc32); + if (ret < 0) + return ret; + } + return 0; + + case MON_IOCX_MFETCH32: + { + struct mon_bin_mfetch32 mfetch; + struct mon_bin_mfetch32 __user *uptr; + + uptr = (struct mon_bin_mfetch32 __user *) compat_ptr(arg); + + if (copy_from_user(&mfetch, uptr, sizeof(mfetch))) + return -EFAULT; + + if (mfetch.nflush32) { + ret = mon_bin_flush(rp, mfetch.nflush32); + if (ret < 0) + return ret; + if (put_user(ret, &uptr->nflush32)) + return -EFAULT; + } + ret = mon_bin_fetch(file, rp, compat_ptr(mfetch.offvec32), + mfetch.nfetch32); + if (ret < 0) + return ret; + if (put_user(ret, &uptr->nfetch32)) + return -EFAULT; + } + return 0; + + case MON_IOCG_STATS: + return mon_bin_ioctl(file, cmd, (unsigned long) compat_ptr(arg)); + + case MON_IOCQ_URB_LEN: + case MON_IOCQ_RING_SIZE: + case MON_IOCT_RING_SIZE: + case MON_IOCH_MFLUSH: + return mon_bin_ioctl(file, cmd, arg); + + default: + ; + } + return -ENOTTY; +} +#endif /* CONFIG_COMPAT */ + +static unsigned int +mon_bin_poll(struct file *file, struct poll_table_struct *wait) +{ + struct mon_reader_bin *rp = file->private_data; + unsigned int mask = 0; + unsigned long flags; + + if (file->f_mode & FMODE_READ) + poll_wait(file, &rp->b_wait, wait); + + spin_lock_irqsave(&rp->b_lock, flags); + if (!MON_RING_EMPTY(rp)) + mask |= POLLIN | POLLRDNORM; /* readable */ + spin_unlock_irqrestore(&rp->b_lock, flags); + return mask; +} + +/* + * open and close: just keep track of how many times the device is + * mapped, to use the proper memory allocation function. + */ +static void mon_bin_vma_open(struct vm_area_struct *vma) +{ + struct mon_reader_bin *rp = vma->vm_private_data; + rp->mmap_active++; +} + +static void mon_bin_vma_close(struct vm_area_struct *vma) +{ + struct mon_reader_bin *rp = vma->vm_private_data; + rp->mmap_active--; +} + +/* + * Map ring pages to user space. + */ +static int mon_bin_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct mon_reader_bin *rp = vma->vm_private_data; + unsigned long offset, chunk_idx; + struct page *pageptr; + + offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= rp->b_size) + return VM_FAULT_SIGBUS; + chunk_idx = offset / CHUNK_SIZE; + pageptr = rp->b_vec[chunk_idx].pg; + get_page(pageptr); + vmf->page = pageptr; + return 0; +} + +static const struct vm_operations_struct mon_bin_vm_ops = { + .open = mon_bin_vma_open, + .close = mon_bin_vma_close, + .fault = mon_bin_vma_fault, +}; + +static int mon_bin_mmap(struct file *filp, struct vm_area_struct *vma) +{ + /* don't do anything here: "fault" will set up page table entries */ + vma->vm_ops = &mon_bin_vm_ops; + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_private_data = filp->private_data; + mon_bin_vma_open(vma); + return 0; +} + +static const struct file_operations mon_fops_binary = { + .owner = THIS_MODULE, + .open = mon_bin_open, + .llseek = no_llseek, + .read = mon_bin_read, + /* .write = mon_text_write, */ + .poll = mon_bin_poll, + .unlocked_ioctl = mon_bin_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = mon_bin_compat_ioctl, +#endif + .release = mon_bin_release, + .mmap = mon_bin_mmap, +}; + +static int mon_bin_wait_event(struct file *file, struct mon_reader_bin *rp) +{ + DECLARE_WAITQUEUE(waita, current); + unsigned long flags; + + add_wait_queue(&rp->b_wait, &waita); + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&rp->b_lock, flags); + while (MON_RING_EMPTY(rp)) { + spin_unlock_irqrestore(&rp->b_lock, flags); + + if (file->f_flags & O_NONBLOCK) { + set_current_state(TASK_RUNNING); + remove_wait_queue(&rp->b_wait, &waita); + return -EWOULDBLOCK; /* Same as EAGAIN in Linux */ + } + schedule(); + if (signal_pending(current)) { + remove_wait_queue(&rp->b_wait, &waita); + return -EINTR; + } + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&rp->b_lock, flags); + } + spin_unlock_irqrestore(&rp->b_lock, flags); + + set_current_state(TASK_RUNNING); + remove_wait_queue(&rp->b_wait, &waita); + return 0; +} + +static int mon_alloc_buff(struct mon_pgmap *map, int npages) +{ + int n; + unsigned long vaddr; + + for (n = 0; n < npages; n++) { + vaddr = get_zeroed_page(GFP_KERNEL); + if (vaddr == 0) { + while (n-- != 0) + free_page((unsigned long) map[n].ptr); + return -ENOMEM; + } + map[n].ptr = (unsigned char *) vaddr; + map[n].pg = virt_to_page((void *) vaddr); + } + return 0; +} + +static void mon_free_buff(struct mon_pgmap *map, int npages) +{ + int n; + + for (n = 0; n < npages; n++) + free_page((unsigned long) map[n].ptr); +} + +int mon_bin_add(struct mon_bus *mbus, const struct usb_bus *ubus) +{ + struct device *dev; + unsigned minor = ubus? ubus->busnum: 0; + + if (minor >= MON_BIN_MAX_MINOR) + return 0; + + dev = device_create(mon_bin_class, ubus ? ubus->controller : NULL, + MKDEV(MAJOR(mon_bin_dev0), minor), NULL, + "usbmon%d", minor); + if (IS_ERR(dev)) + return 0; + + mbus->classdev = dev; + return 1; +} + +void mon_bin_del(struct mon_bus *mbus) +{ + device_destroy(mon_bin_class, mbus->classdev->devt); +} + +int __init mon_bin_init(void) +{ + int rc; + + mon_bin_class = class_create(THIS_MODULE, "usbmon"); + if (IS_ERR(mon_bin_class)) { + rc = PTR_ERR(mon_bin_class); + goto err_class; + } + + rc = alloc_chrdev_region(&mon_bin_dev0, 0, MON_BIN_MAX_MINOR, "usbmon"); + if (rc < 0) + goto err_dev; + + cdev_init(&mon_bin_cdev, &mon_fops_binary); + mon_bin_cdev.owner = THIS_MODULE; + + rc = cdev_add(&mon_bin_cdev, mon_bin_dev0, MON_BIN_MAX_MINOR); + if (rc < 0) + goto err_add; + + return 0; + +err_add: + unregister_chrdev_region(mon_bin_dev0, MON_BIN_MAX_MINOR); +err_dev: + class_destroy(mon_bin_class); +err_class: + return rc; +} + +void mon_bin_exit(void) +{ + cdev_del(&mon_bin_cdev); + unregister_chrdev_region(mon_bin_dev0, MON_BIN_MAX_MINOR); + class_destroy(mon_bin_class); +} diff --git a/drivers/usb/mon/mon_main.c b/drivers/usb/mon/mon_main.c new file mode 100644 index 00000000..10405119 --- /dev/null +++ b/drivers/usb/mon/mon_main.c @@ -0,0 +1,431 @@ +/* + * The USB Monitor, inspired by Dave Harding's USBMon. + * + * mon_main.c: Main file, module initiation and exit, registrations, etc. + * + * Copyright (C) 2005 Pete Zaitcev (zaitcev@redhat.com) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "usb_mon.h" + + +static void mon_stop(struct mon_bus *mbus); +static void mon_dissolve(struct mon_bus *mbus, struct usb_bus *ubus); +static void mon_bus_drop(struct kref *r); +static void mon_bus_init(struct usb_bus *ubus); + +DEFINE_MUTEX(mon_lock); + +struct mon_bus mon_bus0; /* Pseudo bus meaning "all buses" */ +static LIST_HEAD(mon_buses); /* All buses we know: struct mon_bus */ + +/* + * Link a reader into the bus. + * + * This must be called with mon_lock taken because of mbus->ref. + */ +void mon_reader_add(struct mon_bus *mbus, struct mon_reader *r) +{ + unsigned long flags; + struct list_head *p; + + spin_lock_irqsave(&mbus->lock, flags); + if (mbus->nreaders == 0) { + if (mbus == &mon_bus0) { + list_for_each (p, &mon_buses) { + struct mon_bus *m1; + m1 = list_entry(p, struct mon_bus, bus_link); + m1->u_bus->monitored = 1; + } + } else { + mbus->u_bus->monitored = 1; + } + } + mbus->nreaders++; + list_add_tail(&r->r_link, &mbus->r_list); + spin_unlock_irqrestore(&mbus->lock, flags); + + kref_get(&mbus->ref); +} + +/* + * Unlink reader from the bus. + * + * This is called with mon_lock taken, so we can decrement mbus->ref. + */ +void mon_reader_del(struct mon_bus *mbus, struct mon_reader *r) +{ + unsigned long flags; + + spin_lock_irqsave(&mbus->lock, flags); + list_del(&r->r_link); + --mbus->nreaders; + if (mbus->nreaders == 0) + mon_stop(mbus); + spin_unlock_irqrestore(&mbus->lock, flags); + + kref_put(&mbus->ref, mon_bus_drop); +} + +/* + */ +static void mon_bus_submit(struct mon_bus *mbus, struct urb *urb) +{ + unsigned long flags; + struct list_head *pos; + struct mon_reader *r; + + spin_lock_irqsave(&mbus->lock, flags); + mbus->cnt_events++; + list_for_each (pos, &mbus->r_list) { + r = list_entry(pos, struct mon_reader, r_link); + r->rnf_submit(r->r_data, urb); + } + spin_unlock_irqrestore(&mbus->lock, flags); +} + +static void mon_submit(struct usb_bus *ubus, struct urb *urb) +{ + struct mon_bus *mbus; + + if ((mbus = ubus->mon_bus) != NULL) + mon_bus_submit(mbus, urb); + mon_bus_submit(&mon_bus0, urb); +} + +/* + */ +static void mon_bus_submit_error(struct mon_bus *mbus, struct urb *urb, int error) +{ + unsigned long flags; + struct list_head *pos; + struct mon_reader *r; + + spin_lock_irqsave(&mbus->lock, flags); + mbus->cnt_events++; + list_for_each (pos, &mbus->r_list) { + r = list_entry(pos, struct mon_reader, r_link); + r->rnf_error(r->r_data, urb, error); + } + spin_unlock_irqrestore(&mbus->lock, flags); +} + +static void mon_submit_error(struct usb_bus *ubus, struct urb *urb, int error) +{ + struct mon_bus *mbus; + + if ((mbus = ubus->mon_bus) != NULL) + mon_bus_submit_error(mbus, urb, error); + mon_bus_submit_error(&mon_bus0, urb, error); +} + +/* + */ +static void mon_bus_complete(struct mon_bus *mbus, struct urb *urb, int status) +{ + unsigned long flags; + struct list_head *pos; + struct mon_reader *r; + + spin_lock_irqsave(&mbus->lock, flags); + mbus->cnt_events++; + list_for_each (pos, &mbus->r_list) { + r = list_entry(pos, struct mon_reader, r_link); + r->rnf_complete(r->r_data, urb, status); + } + spin_unlock_irqrestore(&mbus->lock, flags); +} + +static void mon_complete(struct usb_bus *ubus, struct urb *urb, int status) +{ + struct mon_bus *mbus; + + if ((mbus = ubus->mon_bus) != NULL) + mon_bus_complete(mbus, urb, status); + mon_bus_complete(&mon_bus0, urb, status); +} + +/* int (*unlink_urb) (struct urb *urb, int status); */ + +/* + * Stop monitoring. + */ +static void mon_stop(struct mon_bus *mbus) +{ + struct usb_bus *ubus; + struct list_head *p; + + if (mbus == &mon_bus0) { + list_for_each (p, &mon_buses) { + mbus = list_entry(p, struct mon_bus, bus_link); + /* + * We do not change nreaders here, so rely on mon_lock. + */ + if (mbus->nreaders == 0 && (ubus = mbus->u_bus) != NULL) + ubus->monitored = 0; + } + } else { + /* + * A stop can be called for a dissolved mon_bus in case of + * a reader staying across an rmmod foo_hcd, so test ->u_bus. + */ + if (mon_bus0.nreaders == 0 && (ubus = mbus->u_bus) != NULL) { + ubus->monitored = 0; + mb(); + } + } +} + +/* + * Add a USB bus (usually by a modprobe foo-hcd) + * + * This does not return an error code because the core cannot care less + * if monitoring is not established. + */ +static void mon_bus_add(struct usb_bus *ubus) +{ + mon_bus_init(ubus); + mutex_lock(&mon_lock); + if (mon_bus0.nreaders != 0) + ubus->monitored = 1; + mutex_unlock(&mon_lock); +} + +/* + * Remove a USB bus (either from rmmod foo-hcd or from a hot-remove event). + */ +static void mon_bus_remove(struct usb_bus *ubus) +{ + struct mon_bus *mbus = ubus->mon_bus; + + mutex_lock(&mon_lock); + list_del(&mbus->bus_link); + if (mbus->text_inited) + mon_text_del(mbus); + if (mbus->bin_inited) + mon_bin_del(mbus); + + mon_dissolve(mbus, ubus); + kref_put(&mbus->ref, mon_bus_drop); + mutex_unlock(&mon_lock); +} + +static int mon_notify(struct notifier_block *self, unsigned long action, + void *dev) +{ + switch (action) { + case USB_BUS_ADD: + mon_bus_add(dev); + break; + case USB_BUS_REMOVE: + mon_bus_remove(dev); + } + return NOTIFY_OK; +} + +static struct notifier_block mon_nb = { + .notifier_call = mon_notify, +}; + +/* + * Ops + */ +static struct usb_mon_operations mon_ops_0 = { + .urb_submit = mon_submit, + .urb_submit_error = mon_submit_error, + .urb_complete = mon_complete, +}; + +/* + * Tear usb_bus and mon_bus apart. + */ +static void mon_dissolve(struct mon_bus *mbus, struct usb_bus *ubus) +{ + + if (ubus->monitored) { + ubus->monitored = 0; + mb(); + } + + ubus->mon_bus = NULL; + mbus->u_bus = NULL; + mb(); + + /* We want synchronize_irq() here, but that needs an argument. */ +} + +/* + */ +static void mon_bus_drop(struct kref *r) +{ + struct mon_bus *mbus = container_of(r, struct mon_bus, ref); + kfree(mbus); +} + +/* + * Initialize a bus for us: + * - allocate mon_bus + * - refcount USB bus struct + * - link + */ +static void mon_bus_init(struct usb_bus *ubus) +{ + struct mon_bus *mbus; + + if ((mbus = kzalloc(sizeof(struct mon_bus), GFP_KERNEL)) == NULL) + goto err_alloc; + kref_init(&mbus->ref); + spin_lock_init(&mbus->lock); + INIT_LIST_HEAD(&mbus->r_list); + + /* + * We don't need to take a reference to ubus, because we receive + * a notification if the bus is about to be removed. + */ + mbus->u_bus = ubus; + ubus->mon_bus = mbus; + + mbus->text_inited = mon_text_add(mbus, ubus); + mbus->bin_inited = mon_bin_add(mbus, ubus); + + mutex_lock(&mon_lock); + list_add_tail(&mbus->bus_link, &mon_buses); + mutex_unlock(&mon_lock); + return; + +err_alloc: + return; +} + +static void mon_bus0_init(void) +{ + struct mon_bus *mbus = &mon_bus0; + + kref_init(&mbus->ref); + spin_lock_init(&mbus->lock); + INIT_LIST_HEAD(&mbus->r_list); + + mbus->text_inited = mon_text_add(mbus, NULL); + mbus->bin_inited = mon_bin_add(mbus, NULL); +} + +/* + * Search a USB bus by number. Notice that USB bus numbers start from one, + * which we may later use to identify "all" with zero. + * + * This function must be called with mon_lock held. + * + * This is obviously inefficient and may be revised in the future. + */ +struct mon_bus *mon_bus_lookup(unsigned int num) +{ + struct list_head *p; + struct mon_bus *mbus; + + if (num == 0) { + return &mon_bus0; + } + list_for_each (p, &mon_buses) { + mbus = list_entry(p, struct mon_bus, bus_link); + if (mbus->u_bus->busnum == num) { + return mbus; + } + } + return NULL; +} + +static int __init mon_init(void) +{ + struct usb_bus *ubus; + int rc; + + if ((rc = mon_text_init()) != 0) + goto err_text; + if ((rc = mon_bin_init()) != 0) + goto err_bin; + + mon_bus0_init(); + + if (usb_mon_register(&mon_ops_0) != 0) { + printk(KERN_NOTICE TAG ": unable to register with the core\n"); + rc = -ENODEV; + goto err_reg; + } + // MOD_INC_USE_COUNT(which_module?); + + mutex_lock(&usb_bus_list_lock); + list_for_each_entry (ubus, &usb_bus_list, bus_list) { + mon_bus_init(ubus); + } + usb_register_notify(&mon_nb); + mutex_unlock(&usb_bus_list_lock); + return 0; + +err_reg: + mon_bin_exit(); +err_bin: + mon_text_exit(); +err_text: + return rc; +} + +static void __exit mon_exit(void) +{ + struct mon_bus *mbus; + struct list_head *p; + + usb_unregister_notify(&mon_nb); + usb_mon_deregister(); + + mutex_lock(&mon_lock); + + while (!list_empty(&mon_buses)) { + p = mon_buses.next; + mbus = list_entry(p, struct mon_bus, bus_link); + list_del(p); + + if (mbus->text_inited) + mon_text_del(mbus); + if (mbus->bin_inited) + mon_bin_del(mbus); + + /* + * This never happens, because the open/close paths in + * file level maintain module use counters and so rmmod fails + * before reaching here. However, better be safe... + */ + if (mbus->nreaders) { + printk(KERN_ERR TAG + ": Outstanding opens (%d) on usb%d, leaking...\n", + mbus->nreaders, mbus->u_bus->busnum); + atomic_set(&mbus->ref.refcount, 2); /* Force leak */ + } + + mon_dissolve(mbus, mbus->u_bus); + kref_put(&mbus->ref, mon_bus_drop); + } + + mbus = &mon_bus0; + if (mbus->text_inited) + mon_text_del(mbus); + if (mbus->bin_inited) + mon_bin_del(mbus); + + mutex_unlock(&mon_lock); + + mon_text_exit(); + mon_bin_exit(); +} + +module_init(mon_init); +module_exit(mon_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/mon/mon_stat.c b/drivers/usb/mon/mon_stat.c new file mode 100644 index 00000000..ebd6189a --- /dev/null +++ b/drivers/usb/mon/mon_stat.c @@ -0,0 +1,69 @@ +/* + * The USB Monitor, inspired by Dave Harding's USBMon. + * + * This is the 's' or 'stat' reader which debugs usbmon itself. + * Note that this code blows through locks, so make sure that + * /dbg/usbmon/0s is well protected from non-root users. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "usb_mon.h" + +#define STAT_BUF_SIZE 80 + +struct snap { + int slen; + char str[STAT_BUF_SIZE]; +}; + +static int mon_stat_open(struct inode *inode, struct file *file) +{ + struct mon_bus *mbus; + struct snap *sp; + + if ((sp = kmalloc(sizeof(struct snap), GFP_KERNEL)) == NULL) + return -ENOMEM; + + mbus = inode->i_private; + + sp->slen = snprintf(sp->str, STAT_BUF_SIZE, + "nreaders %d events %u text_lost %u\n", + mbus->nreaders, mbus->cnt_events, mbus->cnt_text_lost); + + file->private_data = sp; + return 0; +} + +static ssize_t mon_stat_read(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct snap *sp = file->private_data; + + return simple_read_from_buffer(buf, nbytes, ppos, sp->str, sp->slen); +} + +static int mon_stat_release(struct inode *inode, struct file *file) +{ + struct snap *sp = file->private_data; + file->private_data = NULL; + kfree(sp); + return 0; +} + +const struct file_operations mon_fops_stat = { + .owner = THIS_MODULE, + .open = mon_stat_open, + .llseek = no_llseek, + .read = mon_stat_read, + /* .write = mon_stat_write, */ + /* .poll = mon_stat_poll, */ + /* .unlocked_ioctl = mon_stat_ioctl, */ + .release = mon_stat_release, +}; diff --git a/drivers/usb/mon/mon_text.c b/drivers/usb/mon/mon_text.c new file mode 100644 index 00000000..ad408251 --- /dev/null +++ b/drivers/usb/mon/mon_text.c @@ -0,0 +1,761 @@ +/* + * The USB Monitor, inspired by Dave Harding's USBMon. + * + * This is a text format reader. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb_mon.h" + +/* + * No, we do not want arbitrarily long data strings. + * Use the binary interface if you want to capture bulk data! + */ +#define DATA_MAX 32 + +/* + * Defined by USB 2.0 clause 9.3, table 9.2. + */ +#define SETUP_MAX 8 + +/* + * This limit exists to prevent OOMs when the user process stops reading. + * If usbmon were available to unprivileged processes, it might be open + * to a local DoS. But we have to keep to root in order to prevent + * password sniffing from HID devices. + */ +#define EVENT_MAX (4*PAGE_SIZE / sizeof(struct mon_event_text)) + +/* + * Potentially unlimited number; we limit it for similar allocations. + * The usbfs limits this to 128, but we're not quite as generous. + */ +#define ISODESC_MAX 5 + +#define PRINTF_DFL 250 /* with 5 ISOs segs */ + +struct mon_iso_desc { + int status; + unsigned int offset; + unsigned int length; /* Unsigned here, signed in URB. Historic. */ +}; + +struct mon_event_text { + struct list_head e_link; + int type; /* submit, complete, etc. */ + unsigned long id; /* From pointer, most of the time */ + unsigned int tstamp; + int busnum; + char devnum; + char epnum; + char is_in; + char xfertype; + int length; /* Depends on type: xfer length or act length */ + int status; + int interval; + int start_frame; + int error_count; + char setup_flag; + char data_flag; + int numdesc; /* Full number */ + struct mon_iso_desc isodesc[ISODESC_MAX]; + unsigned char setup[SETUP_MAX]; + unsigned char data[DATA_MAX]; +}; + +#define SLAB_NAME_SZ 30 +struct mon_reader_text { + struct kmem_cache *e_slab; + int nevents; + struct list_head e_list; + struct mon_reader r; /* In C, parent class can be placed anywhere */ + + wait_queue_head_t wait; + int printf_size; + char *printf_buf; + struct mutex printf_lock; + + char slab_name[SLAB_NAME_SZ]; +}; + +static struct dentry *mon_dir; /* Usually /sys/kernel/debug/usbmon */ + +static void mon_text_ctor(void *); + +struct mon_text_ptr { + int cnt, limit; + char *pbuf; +}; + +static struct mon_event_text * + mon_text_read_wait(struct mon_reader_text *rp, struct file *file); +static void mon_text_read_head_t(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep); +static void mon_text_read_head_u(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep); +static void mon_text_read_statset(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep); +static void mon_text_read_intstat(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep); +static void mon_text_read_isostat(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep); +static void mon_text_read_isodesc(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep); +static void mon_text_read_data(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep); + +/* + * mon_text_submit + * mon_text_complete + * + * May be called from an interrupt. + * + * This is called with the whole mon_bus locked, so no additional lock. + */ + +static inline char mon_text_get_setup(struct mon_event_text *ep, + struct urb *urb, char ev_type, struct mon_bus *mbus) +{ + + if (ep->xfertype != USB_ENDPOINT_XFER_CONTROL || ev_type != 'S') + return '-'; + + if (urb->setup_packet == NULL) + return 'Z'; /* '0' would be not as pretty. */ + + memcpy(ep->setup, urb->setup_packet, SETUP_MAX); + return 0; +} + +static inline char mon_text_get_data(struct mon_event_text *ep, struct urb *urb, + int len, char ev_type, struct mon_bus *mbus) +{ + void *src; + + if (len <= 0) + return 'L'; + if (len >= DATA_MAX) + len = DATA_MAX; + + if (ep->is_in) { + if (ev_type != 'C') + return '<'; + } else { + if (ev_type != 'S') + return '>'; + } + + if (urb->num_sgs == 0) { + src = urb->transfer_buffer; + if (src == NULL) + return 'Z'; /* '0' would be not as pretty. */ + } else { + struct scatterlist *sg = urb->sg; + + if (PageHighMem(sg_page(sg))) + return 'D'; + + /* For the text interface we copy only the first sg buffer */ + len = min_t(int, sg->length, len); + src = sg_virt(sg); + } + + memcpy(ep->data, src, len); + return 0; +} + +static inline unsigned int mon_get_timestamp(void) +{ + struct timeval tval; + unsigned int stamp; + + do_gettimeofday(&tval); + stamp = tval.tv_sec & 0xFFF; /* 2^32 = 4294967296. Limit to 4096s. */ + stamp = stamp * 1000000 + tval.tv_usec; + return stamp; +} + +static void mon_text_event(struct mon_reader_text *rp, struct urb *urb, + char ev_type, int status) +{ + struct mon_event_text *ep; + unsigned int stamp; + struct usb_iso_packet_descriptor *fp; + struct mon_iso_desc *dp; + int i, ndesc; + + stamp = mon_get_timestamp(); + + if (rp->nevents >= EVENT_MAX || + (ep = kmem_cache_alloc(rp->e_slab, GFP_ATOMIC)) == NULL) { + rp->r.m_bus->cnt_text_lost++; + return; + } + + ep->type = ev_type; + ep->id = (unsigned long) urb; + ep->busnum = urb->dev->bus->busnum; + ep->devnum = urb->dev->devnum; + ep->epnum = usb_endpoint_num(&urb->ep->desc); + ep->xfertype = usb_endpoint_type(&urb->ep->desc); + ep->is_in = usb_urb_dir_in(urb); + ep->tstamp = stamp; + ep->length = (ev_type == 'S') ? + urb->transfer_buffer_length : urb->actual_length; + /* Collecting status makes debugging sense for submits, too */ + ep->status = status; + + if (ep->xfertype == USB_ENDPOINT_XFER_INT) { + ep->interval = urb->interval; + } else if (ep->xfertype == USB_ENDPOINT_XFER_ISOC) { + ep->interval = urb->interval; + ep->start_frame = urb->start_frame; + ep->error_count = urb->error_count; + } + ep->numdesc = urb->number_of_packets; + if (ep->xfertype == USB_ENDPOINT_XFER_ISOC && + urb->number_of_packets > 0) { + if ((ndesc = urb->number_of_packets) > ISODESC_MAX) + ndesc = ISODESC_MAX; + fp = urb->iso_frame_desc; + dp = ep->isodesc; + for (i = 0; i < ndesc; i++) { + dp->status = fp->status; + dp->offset = fp->offset; + dp->length = (ev_type == 'S') ? + fp->length : fp->actual_length; + fp++; + dp++; + } + /* Wasteful, but simple to understand: ISO 'C' is sparse. */ + if (ev_type == 'C') + ep->length = urb->transfer_buffer_length; + } + + ep->setup_flag = mon_text_get_setup(ep, urb, ev_type, rp->r.m_bus); + ep->data_flag = mon_text_get_data(ep, urb, ep->length, ev_type, + rp->r.m_bus); + + rp->nevents++; + list_add_tail(&ep->e_link, &rp->e_list); + wake_up(&rp->wait); +} + +static void mon_text_submit(void *data, struct urb *urb) +{ + struct mon_reader_text *rp = data; + mon_text_event(rp, urb, 'S', -EINPROGRESS); +} + +static void mon_text_complete(void *data, struct urb *urb, int status) +{ + struct mon_reader_text *rp = data; + mon_text_event(rp, urb, 'C', status); +} + +static void mon_text_error(void *data, struct urb *urb, int error) +{ + struct mon_reader_text *rp = data; + struct mon_event_text *ep; + + if (rp->nevents >= EVENT_MAX || + (ep = kmem_cache_alloc(rp->e_slab, GFP_ATOMIC)) == NULL) { + rp->r.m_bus->cnt_text_lost++; + return; + } + + ep->type = 'E'; + ep->id = (unsigned long) urb; + ep->busnum = urb->dev->bus->busnum; + ep->devnum = urb->dev->devnum; + ep->epnum = usb_endpoint_num(&urb->ep->desc); + ep->xfertype = usb_endpoint_type(&urb->ep->desc); + ep->is_in = usb_urb_dir_in(urb); + ep->tstamp = mon_get_timestamp(); + ep->length = 0; + ep->status = error; + + ep->setup_flag = '-'; + ep->data_flag = 'E'; + + rp->nevents++; + list_add_tail(&ep->e_link, &rp->e_list); + wake_up(&rp->wait); +} + +/* + * Fetch next event from the circular buffer. + */ +static struct mon_event_text *mon_text_fetch(struct mon_reader_text *rp, + struct mon_bus *mbus) +{ + struct list_head *p; + unsigned long flags; + + spin_lock_irqsave(&mbus->lock, flags); + if (list_empty(&rp->e_list)) { + spin_unlock_irqrestore(&mbus->lock, flags); + return NULL; + } + p = rp->e_list.next; + list_del(p); + --rp->nevents; + spin_unlock_irqrestore(&mbus->lock, flags); + return list_entry(p, struct mon_event_text, e_link); +} + +/* + */ +static int mon_text_open(struct inode *inode, struct file *file) +{ + struct mon_bus *mbus; + struct mon_reader_text *rp; + int rc; + + mutex_lock(&mon_lock); + mbus = inode->i_private; + + rp = kzalloc(sizeof(struct mon_reader_text), GFP_KERNEL); + if (rp == NULL) { + rc = -ENOMEM; + goto err_alloc; + } + INIT_LIST_HEAD(&rp->e_list); + init_waitqueue_head(&rp->wait); + mutex_init(&rp->printf_lock); + + rp->printf_size = PRINTF_DFL; + rp->printf_buf = kmalloc(rp->printf_size, GFP_KERNEL); + if (rp->printf_buf == NULL) { + rc = -ENOMEM; + goto err_alloc_pr; + } + + rp->r.m_bus = mbus; + rp->r.r_data = rp; + rp->r.rnf_submit = mon_text_submit; + rp->r.rnf_error = mon_text_error; + rp->r.rnf_complete = mon_text_complete; + + snprintf(rp->slab_name, SLAB_NAME_SZ, "mon_text_%p", rp); + rp->e_slab = kmem_cache_create(rp->slab_name, + sizeof(struct mon_event_text), sizeof(long), 0, + mon_text_ctor); + if (rp->e_slab == NULL) { + rc = -ENOMEM; + goto err_slab; + } + + mon_reader_add(mbus, &rp->r); + + file->private_data = rp; + mutex_unlock(&mon_lock); + return 0; + +// err_busy: +// kmem_cache_destroy(rp->e_slab); +err_slab: + kfree(rp->printf_buf); +err_alloc_pr: + kfree(rp); +err_alloc: + mutex_unlock(&mon_lock); + return rc; +} + +/* + * For simplicity, we read one record in one system call and throw out + * what does not fit. This means that the following does not work: + * dd if=/dbg/usbmon/0t bs=10 + * Also, we do not allow seeks and do not bother advancing the offset. + */ +static ssize_t mon_text_read_t(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct mon_reader_text *rp = file->private_data; + struct mon_event_text *ep; + struct mon_text_ptr ptr; + + if (IS_ERR(ep = mon_text_read_wait(rp, file))) + return PTR_ERR(ep); + mutex_lock(&rp->printf_lock); + ptr.cnt = 0; + ptr.pbuf = rp->printf_buf; + ptr.limit = rp->printf_size; + + mon_text_read_head_t(rp, &ptr, ep); + mon_text_read_statset(rp, &ptr, ep); + ptr.cnt += snprintf(ptr.pbuf + ptr.cnt, ptr.limit - ptr.cnt, + " %d", ep->length); + mon_text_read_data(rp, &ptr, ep); + + if (copy_to_user(buf, rp->printf_buf, ptr.cnt)) + ptr.cnt = -EFAULT; + mutex_unlock(&rp->printf_lock); + kmem_cache_free(rp->e_slab, ep); + return ptr.cnt; +} + +static ssize_t mon_text_read_u(struct file *file, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct mon_reader_text *rp = file->private_data; + struct mon_event_text *ep; + struct mon_text_ptr ptr; + + if (IS_ERR(ep = mon_text_read_wait(rp, file))) + return PTR_ERR(ep); + mutex_lock(&rp->printf_lock); + ptr.cnt = 0; + ptr.pbuf = rp->printf_buf; + ptr.limit = rp->printf_size; + + mon_text_read_head_u(rp, &ptr, ep); + if (ep->type == 'E') { + mon_text_read_statset(rp, &ptr, ep); + } else if (ep->xfertype == USB_ENDPOINT_XFER_ISOC) { + mon_text_read_isostat(rp, &ptr, ep); + mon_text_read_isodesc(rp, &ptr, ep); + } else if (ep->xfertype == USB_ENDPOINT_XFER_INT) { + mon_text_read_intstat(rp, &ptr, ep); + } else { + mon_text_read_statset(rp, &ptr, ep); + } + ptr.cnt += snprintf(ptr.pbuf + ptr.cnt, ptr.limit - ptr.cnt, + " %d", ep->length); + mon_text_read_data(rp, &ptr, ep); + + if (copy_to_user(buf, rp->printf_buf, ptr.cnt)) + ptr.cnt = -EFAULT; + mutex_unlock(&rp->printf_lock); + kmem_cache_free(rp->e_slab, ep); + return ptr.cnt; +} + +static struct mon_event_text *mon_text_read_wait(struct mon_reader_text *rp, + struct file *file) +{ + struct mon_bus *mbus = rp->r.m_bus; + DECLARE_WAITQUEUE(waita, current); + struct mon_event_text *ep; + + add_wait_queue(&rp->wait, &waita); + set_current_state(TASK_INTERRUPTIBLE); + while ((ep = mon_text_fetch(rp, mbus)) == NULL) { + if (file->f_flags & O_NONBLOCK) { + set_current_state(TASK_RUNNING); + remove_wait_queue(&rp->wait, &waita); + return ERR_PTR(-EWOULDBLOCK); + } + /* + * We do not count nwaiters, because ->release is supposed + * to be called when all openers are gone only. + */ + schedule(); + if (signal_pending(current)) { + remove_wait_queue(&rp->wait, &waita); + return ERR_PTR(-EINTR); + } + set_current_state(TASK_INTERRUPTIBLE); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&rp->wait, &waita); + return ep; +} + +static void mon_text_read_head_t(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep) +{ + char udir, utype; + + udir = (ep->is_in ? 'i' : 'o'); + switch (ep->xfertype) { + case USB_ENDPOINT_XFER_ISOC: utype = 'Z'; break; + case USB_ENDPOINT_XFER_INT: utype = 'I'; break; + case USB_ENDPOINT_XFER_CONTROL: utype = 'C'; break; + default: /* PIPE_BULK */ utype = 'B'; + } + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + "%lx %u %c %c%c:%03u:%02u", + ep->id, ep->tstamp, ep->type, + utype, udir, ep->devnum, ep->epnum); +} + +static void mon_text_read_head_u(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep) +{ + char udir, utype; + + udir = (ep->is_in ? 'i' : 'o'); + switch (ep->xfertype) { + case USB_ENDPOINT_XFER_ISOC: utype = 'Z'; break; + case USB_ENDPOINT_XFER_INT: utype = 'I'; break; + case USB_ENDPOINT_XFER_CONTROL: utype = 'C'; break; + default: /* PIPE_BULK */ utype = 'B'; + } + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + "%lx %u %c %c%c:%d:%03u:%u", + ep->id, ep->tstamp, ep->type, + utype, udir, ep->busnum, ep->devnum, ep->epnum); +} + +static void mon_text_read_statset(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep) +{ + + if (ep->setup_flag == 0) { /* Setup packet is present and captured */ + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " s %02x %02x %04x %04x %04x", + ep->setup[0], + ep->setup[1], + (ep->setup[3] << 8) | ep->setup[2], + (ep->setup[5] << 8) | ep->setup[4], + (ep->setup[7] << 8) | ep->setup[6]); + } else if (ep->setup_flag != '-') { /* Unable to capture setup packet */ + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %c __ __ ____ ____ ____", ep->setup_flag); + } else { /* No setup for this kind of URB */ + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %d", ep->status); + } +} + +static void mon_text_read_intstat(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep) +{ + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %d:%d", ep->status, ep->interval); +} + +static void mon_text_read_isostat(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep) +{ + if (ep->type == 'S') { + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %d:%d:%d", ep->status, ep->interval, ep->start_frame); + } else { + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %d:%d:%d:%d", + ep->status, ep->interval, ep->start_frame, ep->error_count); + } +} + +static void mon_text_read_isodesc(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep) +{ + int ndesc; /* Display this many */ + int i; + const struct mon_iso_desc *dp; + + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %d", ep->numdesc); + ndesc = ep->numdesc; + if (ndesc > ISODESC_MAX) + ndesc = ISODESC_MAX; + if (ndesc < 0) + ndesc = 0; + dp = ep->isodesc; + for (i = 0; i < ndesc; i++) { + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %d:%u:%u", dp->status, dp->offset, dp->length); + dp++; + } +} + +static void mon_text_read_data(struct mon_reader_text *rp, + struct mon_text_ptr *p, const struct mon_event_text *ep) +{ + int data_len, i; + + if ((data_len = ep->length) > 0) { + if (ep->data_flag == 0) { + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " ="); + if (data_len >= DATA_MAX) + data_len = DATA_MAX; + for (i = 0; i < data_len; i++) { + if (i % 4 == 0) { + p->cnt += snprintf(p->pbuf + p->cnt, + p->limit - p->cnt, + " "); + } + p->cnt += snprintf(p->pbuf + p->cnt, + p->limit - p->cnt, + "%02x", ep->data[i]); + } + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + "\n"); + } else { + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %c\n", ep->data_flag); + } + } else { + p->cnt += snprintf(p->pbuf + p->cnt, p->limit - p->cnt, "\n"); + } +} + +static int mon_text_release(struct inode *inode, struct file *file) +{ + struct mon_reader_text *rp = file->private_data; + struct mon_bus *mbus; + /* unsigned long flags; */ + struct list_head *p; + struct mon_event_text *ep; + + mutex_lock(&mon_lock); + mbus = inode->i_private; + + if (mbus->nreaders <= 0) { + printk(KERN_ERR TAG ": consistency error on close\n"); + mutex_unlock(&mon_lock); + return 0; + } + mon_reader_del(mbus, &rp->r); + + /* + * In theory, e_list is protected by mbus->lock. However, + * after mon_reader_del has finished, the following is the case: + * - we are not on reader list anymore, so new events won't be added; + * - whole mbus may be dropped if it was orphaned. + * So, we better not touch mbus. + */ + /* spin_lock_irqsave(&mbus->lock, flags); */ + while (!list_empty(&rp->e_list)) { + p = rp->e_list.next; + ep = list_entry(p, struct mon_event_text, e_link); + list_del(p); + --rp->nevents; + kmem_cache_free(rp->e_slab, ep); + } + /* spin_unlock_irqrestore(&mbus->lock, flags); */ + + kmem_cache_destroy(rp->e_slab); + kfree(rp->printf_buf); + kfree(rp); + + mutex_unlock(&mon_lock); + return 0; +} + +static const struct file_operations mon_fops_text_t = { + .owner = THIS_MODULE, + .open = mon_text_open, + .llseek = no_llseek, + .read = mon_text_read_t, + .release = mon_text_release, +}; + +static const struct file_operations mon_fops_text_u = { + .owner = THIS_MODULE, + .open = mon_text_open, + .llseek = no_llseek, + .read = mon_text_read_u, + .release = mon_text_release, +}; + +int mon_text_add(struct mon_bus *mbus, const struct usb_bus *ubus) +{ + struct dentry *d; + enum { NAMESZ = 10 }; + char name[NAMESZ]; + int busnum = ubus? ubus->busnum: 0; + int rc; + + if (mon_dir == NULL) + return 0; + + if (ubus != NULL) { + rc = snprintf(name, NAMESZ, "%dt", busnum); + if (rc <= 0 || rc >= NAMESZ) + goto err_print_t; + d = debugfs_create_file(name, 0600, mon_dir, mbus, + &mon_fops_text_t); + if (d == NULL) + goto err_create_t; + mbus->dent_t = d; + } + + rc = snprintf(name, NAMESZ, "%du", busnum); + if (rc <= 0 || rc >= NAMESZ) + goto err_print_u; + d = debugfs_create_file(name, 0600, mon_dir, mbus, &mon_fops_text_u); + if (d == NULL) + goto err_create_u; + mbus->dent_u = d; + + rc = snprintf(name, NAMESZ, "%ds", busnum); + if (rc <= 0 || rc >= NAMESZ) + goto err_print_s; + d = debugfs_create_file(name, 0600, mon_dir, mbus, &mon_fops_stat); + if (d == NULL) + goto err_create_s; + mbus->dent_s = d; + + return 1; + +err_create_s: +err_print_s: + debugfs_remove(mbus->dent_u); + mbus->dent_u = NULL; +err_create_u: +err_print_u: + if (ubus != NULL) { + debugfs_remove(mbus->dent_t); + mbus->dent_t = NULL; + } +err_create_t: +err_print_t: + return 0; +} + +void mon_text_del(struct mon_bus *mbus) +{ + debugfs_remove(mbus->dent_u); + if (mbus->dent_t != NULL) + debugfs_remove(mbus->dent_t); + debugfs_remove(mbus->dent_s); +} + +/* + * Slab interface: constructor. + */ +static void mon_text_ctor(void *mem) +{ + /* + * Nothing to initialize. No, really! + * So, we fill it with garbage to emulate a reused object. + */ + memset(mem, 0xe5, sizeof(struct mon_event_text)); +} + +int __init mon_text_init(void) +{ + struct dentry *mondir; + + mondir = debugfs_create_dir("usbmon", usb_debug_root); + if (IS_ERR(mondir)) { + /* debugfs not available, but we can use usbmon without it */ + return 0; + } + if (mondir == NULL) { + printk(KERN_NOTICE TAG ": unable to create usbmon directory\n"); + return -ENOMEM; + } + mon_dir = mondir; + return 0; +} + +void mon_text_exit(void) +{ + debugfs_remove(mon_dir); +} diff --git a/drivers/usb/mon/usb_mon.h b/drivers/usb/mon/usb_mon.h new file mode 100644 index 00000000..df9a4df3 --- /dev/null +++ b/drivers/usb/mon/usb_mon.h @@ -0,0 +1,75 @@ +/* + * The USB Monitor, inspired by Dave Harding's USBMon. + * + * Copyright (C) 2005 Pete Zaitcev (zaitcev@redhat.com) + */ + +#ifndef __USB_MON_H +#define __USB_MON_H + +#include +#include +#include +/* #include */ /* We use struct pointers only in this header */ + +#define TAG "usbmon" + +struct mon_bus { + struct list_head bus_link; + spinlock_t lock; + struct usb_bus *u_bus; + + int text_inited; + int bin_inited; + struct dentry *dent_s; /* Debugging file */ + struct dentry *dent_t; /* Text interface file */ + struct dentry *dent_u; /* Second text interface file */ + struct device *classdev; /* Device in usbmon class */ + + /* Ref */ + int nreaders; /* Under mon_lock AND mbus->lock */ + struct list_head r_list; /* Chain of readers (usually one) */ + struct kref ref; /* Under mon_lock */ + + /* Stats */ + unsigned int cnt_events; + unsigned int cnt_text_lost; +}; + +/* + * An instance of a process which opened a file (but can fork later) + */ +struct mon_reader { + struct list_head r_link; + struct mon_bus *m_bus; + void *r_data; /* Use container_of instead? */ + + void (*rnf_submit)(void *data, struct urb *urb); + void (*rnf_error)(void *data, struct urb *urb, int error); + void (*rnf_complete)(void *data, struct urb *urb, int status); +}; + +void mon_reader_add(struct mon_bus *mbus, struct mon_reader *r); +void mon_reader_del(struct mon_bus *mbus, struct mon_reader *r); + +struct mon_bus *mon_bus_lookup(unsigned int num); + +int /*bool*/ mon_text_add(struct mon_bus *mbus, const struct usb_bus *ubus); +void mon_text_del(struct mon_bus *mbus); +int /*bool*/ mon_bin_add(struct mon_bus *mbus, const struct usb_bus *ubus); +void mon_bin_del(struct mon_bus *mbus); + +int __init mon_text_init(void); +void mon_text_exit(void); +int __init mon_bin_init(void); +void mon_bin_exit(void); + +/* + */ +extern struct mutex mon_lock; + +extern const struct file_operations mon_fops_stat; + +extern struct mon_bus mon_bus0; /* Only for redundant checks */ + +#endif /* __USB_MON_H */ diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig new file mode 100644 index 00000000..06f8d29a --- /dev/null +++ b/drivers/usb/musb/Kconfig @@ -0,0 +1,119 @@ +# +# USB Dual Role (OTG-ready) Controller Drivers +# for silicon based on Mentor Graphics INVENTRA designs +# + +# (M)HDRC = (Multipoint) Highspeed Dual-Role Controller +config USB_MUSB_HDRC + tristate 'Inventra Highspeed Dual Role Controller (TI, ADI, ...)' + depends on USB_GADGET + help + Say Y here if your system has a dual role high speed USB + controller based on the Mentor Graphics silicon IP. Then + configure options to match your silicon and the board + it's being used with, including the USB peripheral role, + or the USB host role, or both. + + Texas Instruments families using this IP include DaVinci + (35x, 644x ...), OMAP 243x, OMAP 3, and TUSB 6010. + + Analog Devices parts using this IP include Blackfin BF54x, + BF525 and BF527. + + If you do not know what this is, please say N. + + To compile this driver as a module, choose M here; the + module will be called "musb-hdrc". + +if USB_MUSB_HDRC + +choice + prompt "Platform Glue Layer" + +config USB_MUSB_DAVINCI + tristate "DaVinci" + depends on ARCH_DAVINCI_DMx + depends on BROKEN + +config USB_MUSB_DA8XX + tristate "DA8xx/OMAP-L1x" + depends on ARCH_DAVINCI_DA8XX + depends on BROKEN + +config USB_MUSB_TUSB6010 + tristate "TUSB6010" + depends on GENERIC_HARDIRQS + +config USB_MUSB_OMAP2PLUS + tristate "OMAP2430 and onwards" + depends on ARCH_OMAP2PLUS + +config USB_MUSB_AM35X + tristate "AM35x" + depends on ARCH_OMAP + +config USB_MUSB_DSPS + tristate "TI DSPS platforms" + +config USB_MUSB_BLACKFIN + tristate "Blackfin" + depends on (BF54x && !BF544) || (BF52x && ! BF522 && !BF523) + +config USB_MUSB_UX500 + tristate "U8500 and U5500" + +endchoice + +choice + prompt 'MUSB DMA mode' + default MUSB_PIO_ONLY if ARCH_MULTIPLATFORM + default USB_UX500_DMA if USB_MUSB_UX500 + default USB_INVENTRA_DMA if USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN + default USB_TI_CPPI_DMA if USB_MUSB_DAVINCI + default USB_TUSB_OMAP_DMA if USB_MUSB_TUSB6010 + default MUSB_PIO_ONLY if USB_MUSB_TUSB6010 || USB_MUSB_DA8XX || USB_MUSB_AM35X \ + || USB_MUSB_DSPS + help + Unfortunately, only one option can be enabled here. Ideally one + should be able to build all these drivers into one kernel to + allow using DMA on multiplatform kernels. + +config USB_UX500_DMA + bool 'ST Ericsson U8500 and U5500' + depends on USB_MUSB_UX500 + help + Enable DMA transfers on UX500 platforms. + +config USB_INVENTRA_DMA + bool 'Inventra' + depends on USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN + help + Enable DMA transfers using Mentor's engine. + +config USB_TI_CPPI_DMA + bool 'TI CPPI (Davinci)' + depends on USB_MUSB_DAVINCI + help + Enable DMA transfers when TI CPPI DMA is available. + +config USB_TUSB_OMAP_DMA + bool 'TUSB 6010' + depends on USB_MUSB_TUSB6010 + depends on ARCH_OMAP + help + Enable DMA transfers on TUSB 6010 when OMAP DMA is available. + +config MUSB_PIO_ONLY + bool 'Disable DMA (always use PIO)' + help + All data is copied between memory and FIFO by the CPU. + DMA controllers are ignored. + + Do not choose this unless DMA support for your SOC or board + is unavailable (or unstable). When DMA is enabled at compile time, + you can still disable it at run time using the "use_dma=n" module + parameter. + +endchoice + +endif # USB_MUSB_HDRC diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile new file mode 100644 index 00000000..3b858715 --- /dev/null +++ b/drivers/usb/musb/Makefile @@ -0,0 +1,31 @@ +# +# for USB OTG silicon based on Mentor Graphics INVENTRA designs +# + +obj-$(CONFIG_USB_MUSB_HDRC) += musb_hdrc.o + +musb_hdrc-y := musb_core.o + +musb_hdrc-y += musb_gadget_ep0.o musb_gadget.o +musb_hdrc-y += musb_virthub.o musb_host.o +musb_hdrc-$(CONFIG_DEBUG_FS) += musb_debugfs.o + +# Hardware Glue Layer +obj-$(CONFIG_USB_MUSB_OMAP2PLUS) += omap2430.o +obj-$(CONFIG_USB_MUSB_AM35X) += am35x.o +obj-$(CONFIG_USB_MUSB_DSPS) += musb_dsps.o +obj-$(CONFIG_USB_MUSB_TUSB6010) += tusb6010.o +obj-$(CONFIG_USB_MUSB_DAVINCI) += davinci.o +obj-$(CONFIG_USB_MUSB_DA8XX) += da8xx.o +obj-$(CONFIG_USB_MUSB_BLACKFIN) += blackfin.o +obj-$(CONFIG_USB_MUSB_UX500) += ux500.o + +# the kconfig must guarantee that only one of the +# possible I/O schemes will be enabled at a time ... +# PIO only, or DMA (several potential schemes). +# though PIO is always there to back up DMA, and for ep0 + +musb_hdrc-$(CONFIG_USB_INVENTRA_DMA) += musbhsdma.o +musb_hdrc-$(CONFIG_USB_TI_CPPI_DMA) += cppi_dma.o +musb_hdrc-$(CONFIG_USB_TUSB_OMAP_DMA) += tusb6010_omap.o +musb_hdrc-$(CONFIG_USB_UX500_DMA) += ux500_dma.o diff --git a/drivers/usb/musb/am35x.c b/drivers/usb/musb/am35x.c new file mode 100644 index 00000000..2231850c --- /dev/null +++ b/drivers/usb/musb/am35x.c @@ -0,0 +1,641 @@ +/* + * Texas Instruments AM35x "glue layer" + * + * Copyright (c) 2010, by Texas Instruments + * + * Based on the DA8xx "glue layer" code. + * Copyright (c) 2008-2009, MontaVista Software, Inc. + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + * + * The Inventra Controller Driver for Linux is distributed in + * the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" + +/* + * AM35x specific definitions + */ +/* USB 2.0 OTG module registers */ +#define USB_REVISION_REG 0x00 +#define USB_CTRL_REG 0x04 +#define USB_STAT_REG 0x08 +#define USB_EMULATION_REG 0x0c +/* 0x10 Reserved */ +#define USB_AUTOREQ_REG 0x14 +#define USB_SRP_FIX_TIME_REG 0x18 +#define USB_TEARDOWN_REG 0x1c +#define EP_INTR_SRC_REG 0x20 +#define EP_INTR_SRC_SET_REG 0x24 +#define EP_INTR_SRC_CLEAR_REG 0x28 +#define EP_INTR_MASK_REG 0x2c +#define EP_INTR_MASK_SET_REG 0x30 +#define EP_INTR_MASK_CLEAR_REG 0x34 +#define EP_INTR_SRC_MASKED_REG 0x38 +#define CORE_INTR_SRC_REG 0x40 +#define CORE_INTR_SRC_SET_REG 0x44 +#define CORE_INTR_SRC_CLEAR_REG 0x48 +#define CORE_INTR_MASK_REG 0x4c +#define CORE_INTR_MASK_SET_REG 0x50 +#define CORE_INTR_MASK_CLEAR_REG 0x54 +#define CORE_INTR_SRC_MASKED_REG 0x58 +/* 0x5c Reserved */ +#define USB_END_OF_INTR_REG 0x60 + +/* Control register bits */ +#define AM35X_SOFT_RESET_MASK 1 + +/* USB interrupt register bits */ +#define AM35X_INTR_USB_SHIFT 16 +#define AM35X_INTR_USB_MASK (0x1ff << AM35X_INTR_USB_SHIFT) +#define AM35X_INTR_DRVVBUS 0x100 +#define AM35X_INTR_RX_SHIFT 16 +#define AM35X_INTR_TX_SHIFT 0 +#define AM35X_TX_EP_MASK 0xffff /* EP0 + 15 Tx EPs */ +#define AM35X_RX_EP_MASK 0xfffe /* 15 Rx EPs */ +#define AM35X_TX_INTR_MASK (AM35X_TX_EP_MASK << AM35X_INTR_TX_SHIFT) +#define AM35X_RX_INTR_MASK (AM35X_RX_EP_MASK << AM35X_INTR_RX_SHIFT) + +#define USB_MENTOR_CORE_OFFSET 0x400 + +struct am35x_glue { + struct device *dev; + struct platform_device *musb; + struct clk *phy_clk; + struct clk *clk; +}; +#define glue_to_musb(g) platform_get_drvdata(g->musb) + +/* + * am35x_musb_enable - enable interrupts + */ +static void am35x_musb_enable(struct musb *musb) +{ + void __iomem *reg_base = musb->ctrl_base; + u32 epmask; + + /* Workaround: setup IRQs through both register sets. */ + epmask = ((musb->epmask & AM35X_TX_EP_MASK) << AM35X_INTR_TX_SHIFT) | + ((musb->epmask & AM35X_RX_EP_MASK) << AM35X_INTR_RX_SHIFT); + + musb_writel(reg_base, EP_INTR_MASK_SET_REG, epmask); + musb_writel(reg_base, CORE_INTR_MASK_SET_REG, AM35X_INTR_USB_MASK); + + /* Force the DRVVBUS IRQ so we can start polling for ID change. */ + musb_writel(reg_base, CORE_INTR_SRC_SET_REG, + AM35X_INTR_DRVVBUS << AM35X_INTR_USB_SHIFT); +} + +/* + * am35x_musb_disable - disable HDRC and flush interrupts + */ +static void am35x_musb_disable(struct musb *musb) +{ + void __iomem *reg_base = musb->ctrl_base; + + musb_writel(reg_base, CORE_INTR_MASK_CLEAR_REG, AM35X_INTR_USB_MASK); + musb_writel(reg_base, EP_INTR_MASK_CLEAR_REG, + AM35X_TX_INTR_MASK | AM35X_RX_INTR_MASK); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + musb_writel(reg_base, USB_END_OF_INTR_REG, 0); +} + +#define portstate(stmt) stmt + +static void am35x_musb_set_vbus(struct musb *musb, int is_on) +{ + WARN_ON(is_on && is_peripheral_active(musb)); +} + +#define POLL_SECONDS 2 + +static struct timer_list otg_workaround; + +static void otg_timer(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + void __iomem *mregs = musb->mregs; + u8 devctl; + unsigned long flags; + + /* + * We poll because AM35x's won't expose several OTG-critical + * status change events (from the transceiver) otherwise. + */ + devctl = musb_readb(mregs, MUSB_DEVCTL); + dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl, + usb_otg_state_string(musb->xceiv->state)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_BCON: + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + break; + case OTG_STATE_A_WAIT_VFALL: + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + musb_writel(musb->ctrl_base, CORE_INTR_SRC_SET_REG, + MUSB_INTR_VBUSERROR << AM35X_INTR_USB_SHIFT); + break; + case OTG_STATE_B_IDLE: + devctl = musb_readb(mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + else + musb->xceiv->state = OTG_STATE_A_IDLE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +static void am35x_musb_try_idle(struct musb *musb, unsigned long timeout) +{ + static unsigned long last_timer; + + 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->state == OTG_STATE_A_WAIT_BCON)) { + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->state)); + del_timer(&otg_workaround); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout) && timer_pending(&otg_workaround)) { + dev_dbg(musb->controller, "Longer idle timer already pending, ignoring...\n"); + return; + } + last_timer = timeout; + + dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n", + usb_otg_state_string(musb->xceiv->state), + jiffies_to_msecs(timeout - jiffies)); + mod_timer(&otg_workaround, timeout); +} + +static irqreturn_t am35x_musb_interrupt(int irq, void *hci) +{ + struct musb *musb = hci; + void __iomem *reg_base = musb->ctrl_base; + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct omap_musb_board_data *data = plat->board_data; + struct usb_otg *otg = musb->xceiv->otg; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + u32 epintr, usbintr; + + spin_lock_irqsave(&musb->lock, flags); + + /* Get endpoint interrupts */ + epintr = musb_readl(reg_base, EP_INTR_SRC_MASKED_REG); + + if (epintr) { + musb_writel(reg_base, EP_INTR_SRC_CLEAR_REG, epintr); + + musb->int_rx = + (epintr & AM35X_RX_INTR_MASK) >> AM35X_INTR_RX_SHIFT; + musb->int_tx = + (epintr & AM35X_TX_INTR_MASK) >> AM35X_INTR_TX_SHIFT; + } + + /* Get usb core interrupts */ + usbintr = musb_readl(reg_base, CORE_INTR_SRC_MASKED_REG); + if (!usbintr && !epintr) + goto eoi; + + if (usbintr) { + musb_writel(reg_base, CORE_INTR_SRC_CLEAR_REG, usbintr); + + musb->int_usb = + (usbintr & AM35X_INTR_USB_MASK) >> AM35X_INTR_USB_SHIFT; + } + /* + * DRVVBUS IRQs are the only proxy we have (a very poor one!) for + * AM35x's missing ID change IRQ. We need an ID change IRQ to + * switch appropriately between halves of the OTG state machine. + * Managing DEVCTL.SESSION per Mentor docs requires that we know its + * value but DEVCTL.BDEVICE is invalid without DEVCTL.SESSION set. + * Also, DRVVBUS pulses for SRP (but not at 5V) ... + */ + if (usbintr & (AM35X_INTR_DRVVBUS << AM35X_INTR_USB_SHIFT)) { + int drvvbus = musb_readl(reg_base, USB_STAT_REG); + void __iomem *mregs = musb->mregs; + u8 devctl = musb_readb(mregs, MUSB_DEVCTL); + int err; + + err = musb->int_usb & MUSB_INTR_VBUSERROR; + if (err) { + /* + * The Mentor core doesn't debounce VBUS as needed + * to cope with device connect current spikes. This + * means it's not uncommon for bus-powered devices + * to get VBUS errors during enumeration. + * + * This is a workaround, but newer RTL from Mentor + * seems to allow a better one: "re"-starting sessions + * without waiting for VBUS to stop registering in + * devctl. + */ + musb->int_usb &= ~MUSB_INTR_VBUSERROR; + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + WARNING("VBUS error workaround (delay coming)\n"); + } else if (drvvbus) { + MUSB_HST_MODE(musb); + otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + portstate(musb->port1_status |= USB_PORT_STAT_POWER); + del_timer(&otg_workaround); + } else { + musb->is_active = 0; + MUSB_DEV_MODE(musb); + otg->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; + portstate(musb->port1_status &= ~USB_PORT_STAT_POWER); + } + + /* NOTE: this must complete power-on within 100 ms. */ + dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n", + drvvbus ? "on" : "off", + usb_otg_state_string(musb->xceiv->state), + err ? " ERROR" : "", + devctl); + ret = IRQ_HANDLED; + } + + /* 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); + +eoi: + /* EOI needs to be written for the IRQ to be re-asserted. */ + if (ret == IRQ_HANDLED || epintr || usbintr) { + /* clear level interrupt */ + if (data->clear_irq) + data->clear_irq(); + /* write EOI */ + musb_writel(reg_base, USB_END_OF_INTR_REG, 0); + } + + /* Poll for ID change */ + if (musb->xceiv->state == OTG_STATE_B_IDLE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static int am35x_musb_set_mode(struct musb *musb, u8 musb_mode) +{ + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct omap_musb_board_data *data = plat->board_data; + int retval = 0; + + if (data->set_mode) + data->set_mode(musb_mode); + else + retval = -EIO; + + return retval; +} + +static int am35x_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct omap_musb_board_data *data = plat->board_data; + void __iomem *reg_base = musb->ctrl_base; + u32 rev; + + musb->mregs += USB_MENTOR_CORE_OFFSET; + + /* Returns zero if e.g. not clocked */ + rev = musb_readl(reg_base, USB_REVISION_REG); + if (!rev) + return -ENODEV; + + usb_nop_xceiv_register(); + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) + return -EPROBE_DEFER; + + setup_timer(&otg_workaround, otg_timer, (unsigned long) musb); + + /* Reset the musb */ + if (data->reset) + data->reset(); + + /* Reset the controller */ + musb_writel(reg_base, USB_CTRL_REG, AM35X_SOFT_RESET_MASK); + + /* Start the on-chip PHY and its PLL. */ + if (data->set_phy_power) + data->set_phy_power(1); + + msleep(5); + + musb->isr = am35x_musb_interrupt; + + /* clear level interrupt */ + if (data->clear_irq) + data->clear_irq(); + + return 0; +} + +static int am35x_musb_exit(struct musb *musb) +{ + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct omap_musb_board_data *data = plat->board_data; + + del_timer_sync(&otg_workaround); + + /* Shutdown the on-chip PHY and its PLL. */ + if (data->set_phy_power) + data->set_phy_power(0); + + usb_put_phy(musb->xceiv); + usb_nop_xceiv_unregister(); + + return 0; +} + +/* AM35x supports only 32bit read operation */ +void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) +{ + void __iomem *fifo = hw_ep->fifo; + u32 val; + int i; + + /* Read for 32bit-aligned destination address */ + if (likely((0x03 & (unsigned long) dst) == 0) && len >= 4) { + readsl(fifo, dst, len >> 2); + dst += len & ~0x03; + len &= 0x03; + } + /* + * Now read the remaining 1 to 3 byte or complete length if + * unaligned address. + */ + if (len > 4) { + for (i = 0; i < (len >> 2); i++) { + *(u32 *) dst = musb_readl(fifo, 0); + dst += 4; + } + len &= 0x03; + } + if (len > 0) { + val = musb_readl(fifo, 0); + memcpy(dst, &val, len); + } +} + +static const struct musb_platform_ops am35x_ops = { + .init = am35x_musb_init, + .exit = am35x_musb_exit, + + .enable = am35x_musb_enable, + .disable = am35x_musb_disable, + + .set_mode = am35x_musb_set_mode, + .try_idle = am35x_musb_try_idle, + + .set_vbus = am35x_musb_set_vbus, +}; + +static u64 am35x_dmamask = DMA_BIT_MASK(32); + +static int am35x_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct platform_device *musb; + struct am35x_glue *glue; + + struct clk *phy_clk; + struct clk *clk; + + int ret = -ENOMEM; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(&pdev->dev, "failed to allocate musb device\n"); + goto err1; + } + + phy_clk = clk_get(&pdev->dev, "fck"); + if (IS_ERR(phy_clk)) { + dev_err(&pdev->dev, "failed to get PHY clock\n"); + ret = PTR_ERR(phy_clk); + goto err3; + } + + clk = clk_get(&pdev->dev, "ick"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err4; + } + + ret = clk_enable(phy_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable PHY clock\n"); + goto err5; + } + + ret = clk_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err6; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &am35x_dmamask; + musb->dev.coherent_dma_mask = am35x_dmamask; + + glue->dev = &pdev->dev; + glue->musb = musb; + glue->phy_clk = phy_clk; + glue->clk = clk; + + pdata->platform_ops = &am35x_ops; + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(&pdev->dev, "failed to add resources\n"); + goto err7; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err7; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err7; + } + + return 0; + +err7: + clk_disable(clk); + +err6: + clk_disable(phy_clk); + +err5: + clk_put(clk); + +err4: + clk_put(phy_clk); + +err3: + platform_device_put(musb); + +err1: + kfree(glue); + +err0: + return ret; +} + +static int am35x_remove(struct platform_device *pdev) +{ + struct am35x_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + clk_disable(glue->clk); + clk_disable(glue->phy_clk); + clk_put(glue->clk); + clk_put(glue->phy_clk); + kfree(glue); + + return 0; +} + +#ifdef CONFIG_PM +static int am35x_suspend(struct device *dev) +{ + struct am35x_glue *glue = dev_get_drvdata(dev); + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct omap_musb_board_data *data = plat->board_data; + + /* Shutdown the on-chip PHY and its PLL. */ + if (data->set_phy_power) + data->set_phy_power(0); + + clk_disable(glue->phy_clk); + clk_disable(glue->clk); + + return 0; +} + +static int am35x_resume(struct device *dev) +{ + struct am35x_glue *glue = dev_get_drvdata(dev); + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct omap_musb_board_data *data = plat->board_data; + int ret; + + /* Start the on-chip PHY and its PLL. */ + if (data->set_phy_power) + data->set_phy_power(1); + + ret = clk_enable(glue->phy_clk); + if (ret) { + dev_err(dev, "failed to enable PHY clock\n"); + return ret; + } + + ret = clk_enable(glue->clk); + if (ret) { + dev_err(dev, "failed to enable clock\n"); + return ret; + } + + return 0; +} + +static struct dev_pm_ops am35x_pm_ops = { + .suspend = am35x_suspend, + .resume = am35x_resume, +}; + +#define DEV_PM_OPS &am35x_pm_ops +#else +#define DEV_PM_OPS NULL +#endif + +static struct platform_driver am35x_driver = { + .probe = am35x_probe, + .remove = am35x_remove, + .driver = { + .name = "musb-am35x", + .pm = DEV_PM_OPS, + }, +}; + +MODULE_DESCRIPTION("AM35x MUSB Glue Layer"); +MODULE_AUTHOR("Ajay Kumar Gupta "); +MODULE_LICENSE("GPL v2"); +module_platform_driver(am35x_driver); diff --git a/drivers/usb/musb/blackfin.c b/drivers/usb/musb/blackfin.c new file mode 100644 index 00000000..5e63b160 --- /dev/null +++ b/drivers/usb/musb/blackfin.c @@ -0,0 +1,573 @@ +/* + * MUSB OTG controller driver for Blackfin Processors + * + * Copyright 2006-2008 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "musb_core.h" +#include "musbhsdma.h" +#include "blackfin.h" + +struct bfin_glue { + struct device *dev; + struct platform_device *musb; +}; +#define glue_to_musb(g) platform_get_drvdata(g->musb) + +/* + * Load an endpoint's FIFO + */ +void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) +{ + struct musb *musb = hw_ep->musb; + void __iomem *fifo = hw_ep->fifo; + void __iomem *epio = hw_ep->regs; + u8 epnum = hw_ep->epnum; + + prefetch((u8 *)src); + + musb_writew(epio, MUSB_TXCOUNT, len); + + dev_dbg(musb->controller, "TX ep%d fifo %p count %d buf %p, epio %p\n", + hw_ep->epnum, fifo, len, src, epio); + + dump_fifo_data(src, len); + + if (!ANOMALY_05000380 && epnum != 0) { + u16 dma_reg; + + flush_dcache_range((unsigned long)src, + (unsigned long)(src + len)); + + /* Setup DMA address register */ + dma_reg = (u32)src; + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_LOW), dma_reg); + SSYNC(); + + dma_reg = (u32)src >> 16; + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_HIGH), dma_reg); + SSYNC(); + + /* Setup DMA count register */ + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_LOW), len); + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_HIGH), 0); + SSYNC(); + + /* Enable the DMA */ + dma_reg = (epnum << 4) | DMA_ENA | INT_ENA | DIRECTION; + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), dma_reg); + SSYNC(); + + /* Wait for compelete */ + while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << epnum))) + cpu_relax(); + + /* acknowledge dma interrupt */ + bfin_write_USB_DMA_INTERRUPT(1 << epnum); + SSYNC(); + + /* Reset DMA */ + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), 0); + SSYNC(); + } else { + SSYNC(); + + if (unlikely((unsigned long)src & 0x01)) + outsw_8((unsigned long)fifo, src, (len + 1) >> 1); + else + outsw((unsigned long)fifo, src, (len + 1) >> 1); + } +} +/* + * Unload an endpoint's FIFO + */ +void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) +{ + struct musb *musb = hw_ep->musb; + void __iomem *fifo = hw_ep->fifo; + u8 epnum = hw_ep->epnum; + + if (ANOMALY_05000467 && epnum != 0) { + u16 dma_reg; + + invalidate_dcache_range((unsigned long)dst, + (unsigned long)(dst + len)); + + /* Setup DMA address register */ + dma_reg = (u32)dst; + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_LOW), dma_reg); + SSYNC(); + + dma_reg = (u32)dst >> 16; + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_HIGH), dma_reg); + SSYNC(); + + /* Setup DMA count register */ + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_LOW), len); + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_HIGH), 0); + SSYNC(); + + /* Enable the DMA */ + dma_reg = (epnum << 4) | DMA_ENA | INT_ENA; + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), dma_reg); + SSYNC(); + + /* Wait for compelete */ + while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << epnum))) + cpu_relax(); + + /* acknowledge dma interrupt */ + bfin_write_USB_DMA_INTERRUPT(1 << epnum); + SSYNC(); + + /* Reset DMA */ + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), 0); + SSYNC(); + } else { + SSYNC(); + /* Read the last byte of packet with odd size from address fifo + 4 + * to trigger 1 byte access to EP0 FIFO. + */ + if (len == 1) + *dst = (u8)inw((unsigned long)fifo + 4); + else { + if (unlikely((unsigned long)dst & 0x01)) + insw_8((unsigned long)fifo, dst, len >> 1); + else + insw((unsigned long)fifo, dst, len >> 1); + + if (len & 0x01) + *(dst + len - 1) = (u8)inw((unsigned long)fifo + 4); + } + } + dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n", + 'R', hw_ep->epnum, fifo, len, dst); + + dump_fifo_data(dst, len); +} + +static irqreturn_t blackfin_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + struct musb *musb = __hci; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb || musb->int_tx || musb->int_rx) { + musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb); + musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx); + musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx); + retval = musb_interrupt(musb); + } + + /* Start sampling ID pin, when plug is removed from MUSB */ + if ((musb->xceiv->state == OTG_STATE_B_IDLE + || musb->xceiv->state == OTG_STATE_A_WAIT_BCON) || + (musb->int_usb & MUSB_INTR_DISCONNECT && is_host_active(musb))) { + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); + musb->a_wait_bcon = TIMER_DELAY; + } + + spin_unlock_irqrestore(&musb->lock, flags); + + return retval; +} + +static void musb_conn_timer_handler(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + unsigned long flags; + u16 val; + static u8 toggle; + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->state) { + case OTG_STATE_A_IDLE: + case OTG_STATE_A_WAIT_BCON: + /* Start a new session */ + val = musb_readw(musb->mregs, MUSB_DEVCTL); + val &= ~MUSB_DEVCTL_SESSION; + musb_writew(musb->mregs, MUSB_DEVCTL, val); + val |= MUSB_DEVCTL_SESSION; + musb_writew(musb->mregs, MUSB_DEVCTL, val); + /* Check if musb is host or peripheral. */ + val = musb_readw(musb->mregs, MUSB_DEVCTL); + + if (!(val & MUSB_DEVCTL_BDEVICE)) { + gpio_set_value(musb->config->gpio_vrsel, 1); + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + } else { + gpio_set_value(musb->config->gpio_vrsel, 0); + /* Ignore VBUSERROR and SUSPEND IRQ */ + val = musb_readb(musb->mregs, MUSB_INTRUSBE); + val &= ~MUSB_INTR_VBUSERROR; + musb_writeb(musb->mregs, MUSB_INTRUSBE, val); + + val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR; + musb_writeb(musb->mregs, MUSB_INTRUSB, val); + musb->xceiv->state = OTG_STATE_B_IDLE; + } + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); + break; + case OTG_STATE_B_IDLE: + /* + * Start a new session. It seems that MUSB needs taking + * some time to recognize the type of the plug inserted? + */ + val = musb_readw(musb->mregs, MUSB_DEVCTL); + val |= MUSB_DEVCTL_SESSION; + musb_writew(musb->mregs, MUSB_DEVCTL, val); + val = musb_readw(musb->mregs, MUSB_DEVCTL); + + if (!(val & MUSB_DEVCTL_BDEVICE)) { + gpio_set_value(musb->config->gpio_vrsel, 1); + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + } else { + gpio_set_value(musb->config->gpio_vrsel, 0); + + /* Ignore VBUSERROR and SUSPEND IRQ */ + val = musb_readb(musb->mregs, MUSB_INTRUSBE); + val &= ~MUSB_INTR_VBUSERROR; + musb_writeb(musb->mregs, MUSB_INTRUSBE, val); + + val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR; + musb_writeb(musb->mregs, MUSB_INTRUSB, val); + + /* Toggle the Soft Conn bit, so that we can response to + * the inserting of either A-plug or B-plug. + */ + if (toggle) { + val = musb_readb(musb->mregs, MUSB_POWER); + val &= ~MUSB_POWER_SOFTCONN; + musb_writeb(musb->mregs, MUSB_POWER, val); + toggle = 0; + } else { + val = musb_readb(musb->mregs, MUSB_POWER); + val |= MUSB_POWER_SOFTCONN; + musb_writeb(musb->mregs, MUSB_POWER, val); + toggle = 1; + } + /* The delay time is set to 1/4 second by default, + * shortening it, if accelerating A-plug detection + * is needed in OTG mode. + */ + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY / 4); + } + break; + default: + dev_dbg(musb->controller, "%s state not handled\n", + usb_otg_state_string(musb->xceiv->state)); + break; + } + spin_unlock_irqrestore(&musb->lock, flags); + + dev_dbg(musb->controller, "state is %s\n", + usb_otg_state_string(musb->xceiv->state)); +} + +static void bfin_musb_enable(struct musb *musb) +{ + /* REVISIT is this really correct ? */ +} + +static void bfin_musb_disable(struct musb *musb) +{ +} + +static void bfin_musb_set_vbus(struct musb *musb, int is_on) +{ + int value = musb->config->gpio_vrsel_active; + if (!is_on) + value = !value; + gpio_set_value(musb->config->gpio_vrsel, value); + + dev_dbg(musb->controller, "VBUS %s, devctl %02x " + /* otg %3x conf %08x prcm %08x */ "\n", + usb_otg_state_string(musb->xceiv->state), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} + +static int bfin_musb_set_power(struct usb_phy *x, unsigned mA) +{ + return 0; +} + +static int bfin_musb_vbus_status(struct musb *musb) +{ + return 0; +} + +static int bfin_musb_set_mode(struct musb *musb, u8 musb_mode) +{ + return -EIO; +} + +static int bfin_musb_adjust_channel_params(struct dma_channel *channel, + u16 packet_sz, u8 *mode, + dma_addr_t *dma_addr, u32 *len) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + + /* + * Anomaly 05000450 might cause data corruption when using DMA + * MODE 1 transmits with short packet. So to work around this, + * we truncate all MODE 1 transfers down to a multiple of the + * max packet size, and then do the last short packet transfer + * (if there is any) using MODE 0. + */ + if (ANOMALY_05000450) { + if (musb_channel->transmit && *mode == 1) + *len = *len - (*len % packet_sz); + } + + return 0; +} + +static void bfin_musb_reg_init(struct musb *musb) +{ + if (ANOMALY_05000346) { + bfin_write_USB_APHY_CALIB(ANOMALY_05000346_value); + SSYNC(); + } + + if (ANOMALY_05000347) { + bfin_write_USB_APHY_CNTRL(0x0); + SSYNC(); + } + + /* Configure PLL oscillator register */ + bfin_write_USB_PLLOSC_CTRL(0x3080 | + ((480/musb->config->clkin) << 1)); + SSYNC(); + + bfin_write_USB_SRP_CLKDIV((get_sclk()/1000) / 32 - 1); + SSYNC(); + + bfin_write_USB_EP_NI0_RXMAXP(64); + SSYNC(); + + bfin_write_USB_EP_NI0_TXMAXP(64); + SSYNC(); + + /* Route INTRUSB/INTR_RX/INTR_TX to USB_INT0*/ + bfin_write_USB_GLOBINTR(0x7); + SSYNC(); + + bfin_write_USB_GLOBAL_CTL(GLOBAL_ENA | EP1_TX_ENA | EP2_TX_ENA | + EP3_TX_ENA | EP4_TX_ENA | EP5_TX_ENA | + EP6_TX_ENA | EP7_TX_ENA | EP1_RX_ENA | + EP2_RX_ENA | EP3_RX_ENA | EP4_RX_ENA | + EP5_RX_ENA | EP6_RX_ENA | EP7_RX_ENA); + SSYNC(); +} + +static int bfin_musb_init(struct musb *musb) +{ + + /* + * Rev 1.0 BF549 EZ-KITs require PE7 to be high for both DEVICE + * and OTG HOST modes, while rev 1.1 and greater require PE7 to + * be low for DEVICE mode and high for HOST mode. We set it high + * here because we are in host mode + */ + + if (gpio_request(musb->config->gpio_vrsel, "USB_VRSEL")) { + printk(KERN_ERR "Failed ro request USB_VRSEL GPIO_%d\n", + musb->config->gpio_vrsel); + return -ENODEV; + } + gpio_direction_output(musb->config->gpio_vrsel, 0); + + usb_nop_xceiv_register(); + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) { + gpio_free(musb->config->gpio_vrsel); + return -EPROBE_DEFER; + } + + bfin_musb_reg_init(musb); + + setup_timer(&musb_conn_timer, musb_conn_timer_handler, + (unsigned long) musb); + + musb->xceiv->set_power = bfin_musb_set_power; + + musb->isr = blackfin_interrupt; + musb->double_buffer_not_ok = true; + + return 0; +} + +static int bfin_musb_exit(struct musb *musb) +{ + gpio_free(musb->config->gpio_vrsel); + + usb_put_phy(musb->xceiv); + usb_nop_xceiv_unregister(); + return 0; +} + +static const struct musb_platform_ops bfin_ops = { + .init = bfin_musb_init, + .exit = bfin_musb_exit, + + .enable = bfin_musb_enable, + .disable = bfin_musb_disable, + + .set_mode = bfin_musb_set_mode, + + .vbus_status = bfin_musb_vbus_status, + .set_vbus = bfin_musb_set_vbus, + + .adjust_channel_params = bfin_musb_adjust_channel_params, +}; + +static u64 bfin_dmamask = DMA_BIT_MASK(32); + +static int bfin_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct platform_device *musb; + struct bfin_glue *glue; + + int ret = -ENOMEM; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(&pdev->dev, "failed to allocate musb device\n"); + goto err1; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &bfin_dmamask; + musb->dev.coherent_dma_mask = bfin_dmamask; + + glue->dev = &pdev->dev; + glue->musb = musb; + + pdata->platform_ops = &bfin_ops; + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(&pdev->dev, "failed to add resources\n"); + goto err3; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err3; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err3; + } + + return 0; + +err3: + platform_device_put(musb); + +err1: + kfree(glue); + +err0: + return ret; +} + +static int bfin_remove(struct platform_device *pdev) +{ + struct bfin_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + kfree(glue); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_suspend(struct device *dev) +{ + struct bfin_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + if (is_host_active(musb)) + /* + * During hibernate gpio_vrsel will change from high to low + * low which will generate wakeup event resume the system + * immediately. Set it to 0 before hibernate to avoid this + * wakeup event. + */ + gpio_set_value(musb->config->gpio_vrsel, 0); + + return 0; +} + +static int bfin_resume(struct device *dev) +{ + struct bfin_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + bfin_musb_reg_init(musb); + + return 0; +} + +static struct dev_pm_ops bfin_pm_ops = { + .suspend = bfin_suspend, + .resume = bfin_resume, +}; + +#define DEV_PM_OPS &bfin_pm_ops +#else +#define DEV_PM_OPS NULL +#endif + +static struct platform_driver bfin_driver = { + .probe = bfin_probe, + .remove = __exit_p(bfin_remove), + .driver = { + .name = "musb-blackfin", + .pm = DEV_PM_OPS, + }, +}; + +MODULE_DESCRIPTION("Blackfin MUSB Glue Layer"); +MODULE_AUTHOR("Bryan Wy "); +MODULE_LICENSE("GPL v2"); +module_platform_driver(bfin_driver); diff --git a/drivers/usb/musb/blackfin.h b/drivers/usb/musb/blackfin.h new file mode 100644 index 00000000..c84dae54 --- /dev/null +++ b/drivers/usb/musb/blackfin.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 by Analog Devices, Inc. + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + */ + +#ifndef __MUSB_BLACKFIN_H__ +#define __MUSB_BLACKFIN_H__ + +/* + * Blackfin specific definitions + */ + +/* Anomalies notes: + * + * 05000450 - USB DMA Mode 1 Short Packet Data Corruption: + * MUSB driver is designed to transfer buffer of N * maxpacket size + * in DMA mode 1 and leave the rest of the data to the next + * transfer in DMA mode 0, so we never transmit a short packet in + * DMA mode 1. + * + * 05000463 - This anomaly doesn't affect this driver since it + * never uses L1 or L2 memory as data destination. + * + * 05000464 - This anomaly doesn't affect this driver since it + * never uses L1 or L2 memory as data source. + * + * 05000465 - The anomaly can be seen when SCLK is over 100 MHz, and there is + * no way to workaround for bulk endpoints. Since the wMaxPackSize + * of bulk is less than or equal to 512, while the fifo size of + * endpoint 5, 6, 7 is 1024, the double buffer mode is enabled + * automatically when these endpoints are used for bulk OUT. + * + * 05000466 - This anomaly doesn't affect this driver since it never mixes + * concurrent DMA and core accesses to the TX endpoint FIFOs. + * + * 05000467 - The workaround for this anomaly will introduce another + * anomaly - 05000465. + */ + +/* The Mentor USB DMA engine on BF52x (silicon v0.0 and v0.1) seems to be + * unstable in host mode. This may be caused by Anomaly 05000380. After + * digging out the root cause, we will change this number accordingly. + * So, need to either use silicon v0.2+ or disable DMA mode in MUSB. + */ +#if ANOMALY_05000380 && defined(CONFIG_BF52x) && \ + !defined(CONFIG_MUSB_PIO_ONLY) +# error "Please use PIO mode in MUSB driver on bf52x chip v0.0 and v0.1" +#endif + +#undef DUMP_FIFO_DATA +#ifdef DUMP_FIFO_DATA +static void dump_fifo_data(u8 *buf, u16 len) +{ + u8 *tmp = buf; + int i; + + for (i = 0; i < len; i++) { + if (!(i % 16) && i) + pr_debug("\n"); + pr_debug("%02x ", *tmp++); + } + pr_debug("\n"); +} +#else +#define dump_fifo_data(buf, len) do {} while (0) +#endif + + +#define USB_DMA_BASE USB_DMA_INTERRUPT +#define USB_DMAx_CTRL 0x04 +#define USB_DMAx_ADDR_LOW 0x08 +#define USB_DMAx_ADDR_HIGH 0x0C +#define USB_DMAx_COUNT_LOW 0x10 +#define USB_DMAx_COUNT_HIGH 0x14 + +#define USB_DMA_REG(ep, reg) (USB_DMA_BASE + 0x20 * ep + reg) + +/* Almost 1 second */ +#define TIMER_DELAY (1 * HZ) + +static struct timer_list musb_conn_timer; + +#endif /* __MUSB_BLACKFIN_H__ */ diff --git a/drivers/usb/musb/cppi_dma.c b/drivers/usb/musb/cppi_dma.c new file mode 100644 index 00000000..9db211ee --- /dev/null +++ b/drivers/usb/musb/cppi_dma.c @@ -0,0 +1,1553 @@ +/* + * Copyright (C) 2005-2006 by Texas Instruments + * + * This file implements a DMA interface using TI's CPPI DMA. + * For now it's DaVinci-only, but CPPI isn't specific to DaVinci or USB. + * The TUSB6020, using VLYNQ, has CPPI that looks much like DaVinci. + */ + +#include +#include +#include +#include + +#include "musb_core.h" +#include "musb_debug.h" +#include "cppi_dma.h" + + +/* CPPI DMA status 7-mar-2006: + * + * - See musb_{host,gadget}.c for more info + * + * - Correct RX DMA generally forces the engine into irq-per-packet mode, + * which can easily saturate the CPU under non-mass-storage loads. + * + * NOTES 24-aug-2006 (2.6.18-rc4): + * + * - peripheral RXDMA wedged in a test with packets of length 512/512/1. + * evidently after the 1 byte packet was received and acked, the queue + * of BDs got garbaged so it wouldn't empty the fifo. (rxcsr 0x2003, + * and RX DMA0: 4 left, 80000000 8feff880, 8feff860 8feff860; 8f321401 + * 004001ff 00000001 .. 8feff860) Host was just getting NAKed on tx + * of its next (512 byte) packet. IRQ issues? + * + * REVISIT: the "transfer DMA" glue between CPPI and USB fifos will + * evidently also directly update the RX and TX CSRs ... so audit all + * host and peripheral side DMA code to avoid CSR access after DMA has + * been started. + */ + +/* REVISIT now we can avoid preallocating these descriptors; or + * more simply, switch to a global freelist not per-channel ones. + * Note: at full speed, 64 descriptors == 4K bulk data. + */ +#define NUM_TXCHAN_BD 64 +#define NUM_RXCHAN_BD 64 + +static inline void cpu_drain_writebuffer(void) +{ + wmb(); +#ifdef CONFIG_CPU_ARM926T + /* REVISIT this "should not be needed", + * but lack of it sure seemed to hurt ... + */ + asm("mcr p15, 0, r0, c7, c10, 4 @ drain write buffer\n"); +#endif +} + +static inline struct cppi_descriptor *cppi_bd_alloc(struct cppi_channel *c) +{ + struct cppi_descriptor *bd = c->freelist; + + if (bd) + c->freelist = bd->next; + return bd; +} + +static inline void +cppi_bd_free(struct cppi_channel *c, struct cppi_descriptor *bd) +{ + if (!bd) + return; + bd->next = c->freelist; + c->freelist = bd; +} + +/* + * Start DMA controller + * + * Initialize the DMA controller as necessary. + */ + +/* zero out entire rx state RAM entry for the channel */ +static void cppi_reset_rx(struct cppi_rx_stateram __iomem *rx) +{ + musb_writel(&rx->rx_skipbytes, 0, 0); + musb_writel(&rx->rx_head, 0, 0); + musb_writel(&rx->rx_sop, 0, 0); + musb_writel(&rx->rx_current, 0, 0); + musb_writel(&rx->rx_buf_current, 0, 0); + musb_writel(&rx->rx_len_len, 0, 0); + musb_writel(&rx->rx_cnt_cnt, 0, 0); +} + +/* zero out entire tx state RAM entry for the channel */ +static void cppi_reset_tx(struct cppi_tx_stateram __iomem *tx, u32 ptr) +{ + musb_writel(&tx->tx_head, 0, 0); + musb_writel(&tx->tx_buf, 0, 0); + musb_writel(&tx->tx_current, 0, 0); + musb_writel(&tx->tx_buf_current, 0, 0); + musb_writel(&tx->tx_info, 0, 0); + musb_writel(&tx->tx_rem_len, 0, 0); + /* musb_writel(&tx->tx_dummy, 0, 0); */ + musb_writel(&tx->tx_complete, 0, ptr); +} + +static void cppi_pool_init(struct cppi *cppi, struct cppi_channel *c) +{ + int j; + + /* initialize channel fields */ + c->head = NULL; + c->tail = NULL; + c->last_processed = NULL; + c->channel.status = MUSB_DMA_STATUS_UNKNOWN; + c->controller = cppi; + c->is_rndis = 0; + c->freelist = NULL; + + /* build the BD Free list for the channel */ + for (j = 0; j < NUM_TXCHAN_BD + 1; j++) { + struct cppi_descriptor *bd; + dma_addr_t dma; + + bd = dma_pool_alloc(cppi->pool, GFP_KERNEL, &dma); + bd->dma = dma; + cppi_bd_free(c, bd); + } +} + +static int cppi_channel_abort(struct dma_channel *); + +static void cppi_pool_free(struct cppi_channel *c) +{ + struct cppi *cppi = c->controller; + struct cppi_descriptor *bd; + + (void) cppi_channel_abort(&c->channel); + c->channel.status = MUSB_DMA_STATUS_UNKNOWN; + c->controller = NULL; + + /* free all its bds */ + bd = c->last_processed; + do { + if (bd) + dma_pool_free(cppi->pool, bd, bd->dma); + bd = cppi_bd_alloc(c); + } while (bd); + c->last_processed = NULL; +} + +static int cppi_controller_start(struct dma_controller *c) +{ + struct cppi *controller; + void __iomem *tibase; + int i; + + controller = container_of(c, struct cppi, controller); + + /* do whatever is necessary to start controller */ + for (i = 0; i < ARRAY_SIZE(controller->tx); i++) { + controller->tx[i].transmit = true; + controller->tx[i].index = i; + } + for (i = 0; i < ARRAY_SIZE(controller->rx); i++) { + controller->rx[i].transmit = false; + controller->rx[i].index = i; + } + + /* setup BD list on a per channel basis */ + for (i = 0; i < ARRAY_SIZE(controller->tx); i++) + cppi_pool_init(controller, controller->tx + i); + for (i = 0; i < ARRAY_SIZE(controller->rx); i++) + cppi_pool_init(controller, controller->rx + i); + + tibase = controller->tibase; + INIT_LIST_HEAD(&controller->tx_complete); + + /* initialise tx/rx channel head pointers to zero */ + for (i = 0; i < ARRAY_SIZE(controller->tx); i++) { + struct cppi_channel *tx_ch = controller->tx + i; + struct cppi_tx_stateram __iomem *tx; + + INIT_LIST_HEAD(&tx_ch->tx_complete); + + tx = tibase + DAVINCI_TXCPPI_STATERAM_OFFSET(i); + tx_ch->state_ram = tx; + cppi_reset_tx(tx, 0); + } + for (i = 0; i < ARRAY_SIZE(controller->rx); i++) { + struct cppi_channel *rx_ch = controller->rx + i; + struct cppi_rx_stateram __iomem *rx; + + INIT_LIST_HEAD(&rx_ch->tx_complete); + + rx = tibase + DAVINCI_RXCPPI_STATERAM_OFFSET(i); + rx_ch->state_ram = rx; + cppi_reset_rx(rx); + } + + /* enable individual cppi channels */ + musb_writel(tibase, DAVINCI_TXCPPI_INTENAB_REG, + DAVINCI_DMA_ALL_CHANNELS_ENABLE); + musb_writel(tibase, DAVINCI_RXCPPI_INTENAB_REG, + DAVINCI_DMA_ALL_CHANNELS_ENABLE); + + /* enable tx/rx CPPI control */ + musb_writel(tibase, DAVINCI_TXCPPI_CTRL_REG, DAVINCI_DMA_CTRL_ENABLE); + musb_writel(tibase, DAVINCI_RXCPPI_CTRL_REG, DAVINCI_DMA_CTRL_ENABLE); + + /* disable RNDIS mode, also host rx RNDIS autorequest */ + musb_writel(tibase, DAVINCI_RNDIS_REG, 0); + musb_writel(tibase, DAVINCI_AUTOREQ_REG, 0); + + return 0; +} + +/* + * Stop DMA controller + * + * De-Init the DMA controller as necessary. + */ + +static int cppi_controller_stop(struct dma_controller *c) +{ + struct cppi *controller; + void __iomem *tibase; + int i; + struct musb *musb; + + controller = container_of(c, struct cppi, controller); + musb = controller->musb; + + tibase = controller->tibase; + /* DISABLE INDIVIDUAL CHANNEL Interrupts */ + musb_writel(tibase, DAVINCI_TXCPPI_INTCLR_REG, + DAVINCI_DMA_ALL_CHANNELS_ENABLE); + musb_writel(tibase, DAVINCI_RXCPPI_INTCLR_REG, + DAVINCI_DMA_ALL_CHANNELS_ENABLE); + + dev_dbg(musb->controller, "Tearing down RX and TX Channels\n"); + for (i = 0; i < ARRAY_SIZE(controller->tx); i++) { + /* FIXME restructure of txdma to use bds like rxdma */ + controller->tx[i].last_processed = NULL; + cppi_pool_free(controller->tx + i); + } + for (i = 0; i < ARRAY_SIZE(controller->rx); i++) + cppi_pool_free(controller->rx + i); + + /* in Tx Case proper teardown is supported. We resort to disabling + * Tx/Rx CPPI after cleanup of Tx channels. Before TX teardown is + * complete TX CPPI cannot be disabled. + */ + /*disable tx/rx cppi */ + musb_writel(tibase, DAVINCI_TXCPPI_CTRL_REG, DAVINCI_DMA_CTRL_DISABLE); + musb_writel(tibase, DAVINCI_RXCPPI_CTRL_REG, DAVINCI_DMA_CTRL_DISABLE); + + return 0; +} + +/* While dma channel is allocated, we only want the core irqs active + * for fault reports, otherwise we'd get irqs that we don't care about. + * Except for TX irqs, where dma done != fifo empty and reusable ... + * + * NOTE: docs don't say either way, but irq masking **enables** irqs. + * + * REVISIT same issue applies to pure PIO usage too, and non-cppi dma... + */ +static inline void core_rxirq_disable(void __iomem *tibase, unsigned epnum) +{ + musb_writel(tibase, DAVINCI_USB_INT_MASK_CLR_REG, 1 << (epnum + 8)); +} + +static inline void core_rxirq_enable(void __iomem *tibase, unsigned epnum) +{ + musb_writel(tibase, DAVINCI_USB_INT_MASK_SET_REG, 1 << (epnum + 8)); +} + + +/* + * Allocate a CPPI Channel for DMA. With CPPI, channels are bound to + * each transfer direction of a non-control endpoint, so allocating + * (and deallocating) is mostly a way to notice bad housekeeping on + * the software side. We assume the irqs are always active. + */ +static struct dma_channel * +cppi_channel_allocate(struct dma_controller *c, + struct musb_hw_ep *ep, u8 transmit) +{ + struct cppi *controller; + u8 index; + struct cppi_channel *cppi_ch; + void __iomem *tibase; + struct musb *musb; + + controller = container_of(c, struct cppi, controller); + tibase = controller->tibase; + musb = controller->musb; + + /* ep0 doesn't use DMA; remember cppi indices are 0..N-1 */ + index = ep->epnum - 1; + + /* return the corresponding CPPI Channel Handle, and + * probably disable the non-CPPI irq until we need it. + */ + if (transmit) { + if (index >= ARRAY_SIZE(controller->tx)) { + dev_dbg(musb->controller, "no %cX%d CPPI channel\n", 'T', index); + return NULL; + } + cppi_ch = controller->tx + index; + } else { + if (index >= ARRAY_SIZE(controller->rx)) { + dev_dbg(musb->controller, "no %cX%d CPPI channel\n", 'R', index); + return NULL; + } + cppi_ch = controller->rx + index; + core_rxirq_disable(tibase, ep->epnum); + } + + /* REVISIT make this an error later once the same driver code works + * with the other DMA engine too + */ + if (cppi_ch->hw_ep) + dev_dbg(musb->controller, "re-allocating DMA%d %cX channel %p\n", + index, transmit ? 'T' : 'R', cppi_ch); + cppi_ch->hw_ep = ep; + cppi_ch->channel.status = MUSB_DMA_STATUS_FREE; + cppi_ch->channel.max_len = 0x7fffffff; + + dev_dbg(musb->controller, "Allocate CPPI%d %cX\n", index, transmit ? 'T' : 'R'); + return &cppi_ch->channel; +} + +/* Release a CPPI Channel. */ +static void cppi_channel_release(struct dma_channel *channel) +{ + struct cppi_channel *c; + void __iomem *tibase; + + /* REVISIT: for paranoia, check state and abort if needed... */ + + c = container_of(channel, struct cppi_channel, channel); + tibase = c->controller->tibase; + if (!c->hw_ep) + dev_dbg(c->controller->musb->controller, + "releasing idle DMA channel %p\n", c); + else if (!c->transmit) + core_rxirq_enable(tibase, c->index + 1); + + /* for now, leave its cppi IRQ enabled (we won't trigger it) */ + c->hw_ep = NULL; + channel->status = MUSB_DMA_STATUS_UNKNOWN; +} + +/* Context: controller irqlocked */ +static void +cppi_dump_rx(int level, struct cppi_channel *c, const char *tag) +{ + void __iomem *base = c->controller->mregs; + struct cppi_rx_stateram __iomem *rx = c->state_ram; + + musb_ep_select(base, c->index + 1); + + dev_dbg(c->controller->musb->controller, + "RX DMA%d%s: %d left, csr %04x, " + "%08x H%08x S%08x C%08x, " + "B%08x L%08x %08x .. %08x" + "\n", + c->index, tag, + musb_readl(c->controller->tibase, + DAVINCI_RXCPPI_BUFCNT0_REG + 4 * c->index), + musb_readw(c->hw_ep->regs, MUSB_RXCSR), + + musb_readl(&rx->rx_skipbytes, 0), + musb_readl(&rx->rx_head, 0), + musb_readl(&rx->rx_sop, 0), + musb_readl(&rx->rx_current, 0), + + musb_readl(&rx->rx_buf_current, 0), + musb_readl(&rx->rx_len_len, 0), + musb_readl(&rx->rx_cnt_cnt, 0), + musb_readl(&rx->rx_complete, 0) + ); +} + +/* Context: controller irqlocked */ +static void +cppi_dump_tx(int level, struct cppi_channel *c, const char *tag) +{ + void __iomem *base = c->controller->mregs; + struct cppi_tx_stateram __iomem *tx = c->state_ram; + + musb_ep_select(base, c->index + 1); + + dev_dbg(c->controller->musb->controller, + "TX DMA%d%s: csr %04x, " + "H%08x S%08x C%08x %08x, " + "F%08x L%08x .. %08x" + "\n", + c->index, tag, + musb_readw(c->hw_ep->regs, MUSB_TXCSR), + + musb_readl(&tx->tx_head, 0), + musb_readl(&tx->tx_buf, 0), + musb_readl(&tx->tx_current, 0), + musb_readl(&tx->tx_buf_current, 0), + + musb_readl(&tx->tx_info, 0), + musb_readl(&tx->tx_rem_len, 0), + /* dummy/unused word 6 */ + musb_readl(&tx->tx_complete, 0) + ); +} + +/* Context: controller irqlocked */ +static inline void +cppi_rndis_update(struct cppi_channel *c, int is_rx, + void __iomem *tibase, int is_rndis) +{ + /* we may need to change the rndis flag for this cppi channel */ + if (c->is_rndis != is_rndis) { + u32 value = musb_readl(tibase, DAVINCI_RNDIS_REG); + u32 temp = 1 << (c->index); + + if (is_rx) + temp <<= 16; + if (is_rndis) + value |= temp; + else + value &= ~temp; + musb_writel(tibase, DAVINCI_RNDIS_REG, value); + c->is_rndis = is_rndis; + } +} + +static void cppi_dump_rxbd(const char *tag, struct cppi_descriptor *bd) +{ + pr_debug("RXBD/%s %08x: " + "nxt %08x buf %08x off.blen %08x opt.plen %08x\n", + tag, bd->dma, + bd->hw_next, bd->hw_bufp, bd->hw_off_len, + bd->hw_options); +} + +static void cppi_dump_rxq(int level, const char *tag, struct cppi_channel *rx) +{ + struct cppi_descriptor *bd; + + cppi_dump_rx(level, rx, tag); + if (rx->last_processed) + cppi_dump_rxbd("last", rx->last_processed); + for (bd = rx->head; bd; bd = bd->next) + cppi_dump_rxbd("active", bd); +} + + +/* NOTE: DaVinci autoreq is ignored except for host side "RNDIS" mode RX; + * so we won't ever use it (see "CPPI RX Woes" below). + */ +static inline int cppi_autoreq_update(struct cppi_channel *rx, + void __iomem *tibase, int onepacket, unsigned n_bds) +{ + u32 val; + +#ifdef RNDIS_RX_IS_USABLE + u32 tmp; + /* assert(is_host_active(musb)) */ + + /* start from "AutoReq never" */ + tmp = musb_readl(tibase, DAVINCI_AUTOREQ_REG); + val = tmp & ~((0x3) << (rx->index * 2)); + + /* HCD arranged reqpkt for packet #1. we arrange int + * for all but the last one, maybe in two segments. + */ + if (!onepacket) { +#if 0 + /* use two segments, autoreq "all" then the last "never" */ + val |= ((0x3) << (rx->index * 2)); + n_bds--; +#else + /* one segment, autoreq "all-but-last" */ + val |= ((0x1) << (rx->index * 2)); +#endif + } + + if (val != tmp) { + int n = 100; + + /* make sure that autoreq is updated before continuing */ + musb_writel(tibase, DAVINCI_AUTOREQ_REG, val); + do { + tmp = musb_readl(tibase, DAVINCI_AUTOREQ_REG); + if (tmp == val) + break; + cpu_relax(); + } while (n-- > 0); + } +#endif + + /* REQPKT is turned off after each segment */ + if (n_bds && rx->channel.actual_len) { + void __iomem *regs = rx->hw_ep->regs; + + val = musb_readw(regs, MUSB_RXCSR); + if (!(val & MUSB_RXCSR_H_REQPKT)) { + val |= MUSB_RXCSR_H_REQPKT | MUSB_RXCSR_H_WZC_BITS; + musb_writew(regs, MUSB_RXCSR, val); + /* flush writebuffer */ + val = musb_readw(regs, MUSB_RXCSR); + } + } + return n_bds; +} + + +/* Buffer enqueuing Logic: + * + * - RX builds new queues each time, to help handle routine "early + * termination" cases (faults, including errors and short reads) + * more correctly. + * + * - for now, TX reuses the same queue of BDs every time + * + * REVISIT long term, we want a normal dynamic model. + * ... the goal will be to append to the + * existing queue, processing completed "dma buffers" (segments) on the fly. + * + * Otherwise we force an IRQ latency between requests, which slows us a lot + * (especially in "transparent" dma). Unfortunately that model seems to be + * inherent in the DMA model from the Mentor code, except in the rare case + * of transfers big enough (~128+ KB) that we could append "middle" segments + * in the TX paths. (RX can't do this, see below.) + * + * That's true even in the CPPI- friendly iso case, where most urbs have + * several small segments provided in a group and where the "packet at a time" + * "transparent" DMA model is always correct, even on the RX side. + */ + +/* + * CPPI TX: + * ======== + * TX is a lot more reasonable than RX; it doesn't need to run in + * irq-per-packet mode very often. RNDIS mode seems to behave too + * (except how it handles the exactly-N-packets case). Building a + * txdma queue with multiple requests (urb or usb_request) looks + * like it would work ... but fault handling would need much testing. + * + * The main issue with TX mode RNDIS relates to transfer lengths that + * are an exact multiple of the packet length. It appears that there's + * a hiccup in that case (maybe the DMA completes before the ZLP gets + * written?) boiling down to not being able to rely on CPPI writing any + * terminating zero length packet before the next transfer is written. + * So that's punted to PIO; better yet, gadget drivers can avoid it. + * + * Plus, there's allegedly an undocumented constraint that rndis transfer + * length be a multiple of 64 bytes ... but the chip doesn't act that + * way, and we really don't _want_ that behavior anyway. + * + * On TX, "transparent" mode works ... although experiments have shown + * problems trying to use the SOP/EOP bits in different USB packets. + * + * REVISIT try to handle terminating zero length packets using CPPI + * instead of doing it by PIO after an IRQ. (Meanwhile, make Ethernet + * links avoid that issue by forcing them to avoid zlps.) + */ +static void +cppi_next_tx_segment(struct musb *musb, struct cppi_channel *tx) +{ + unsigned maxpacket = tx->maxpacket; + dma_addr_t addr = tx->buf_dma + tx->offset; + size_t length = tx->buf_len - tx->offset; + struct cppi_descriptor *bd; + unsigned n_bds; + unsigned i; + struct cppi_tx_stateram __iomem *tx_ram = tx->state_ram; + int rndis; + + /* TX can use the CPPI "rndis" mode, where we can probably fit this + * transfer in one BD and one IRQ. The only time we would NOT want + * to use it is when hardware constraints prevent it, or if we'd + * trigger the "send a ZLP?" confusion. + */ + rndis = (maxpacket & 0x3f) == 0 + && length > maxpacket + && length < 0xffff + && (length % maxpacket) != 0; + + if (rndis) { + maxpacket = length; + n_bds = 1; + } else { + n_bds = length / maxpacket; + if (!length || (length % maxpacket)) + n_bds++; + n_bds = min(n_bds, (unsigned) NUM_TXCHAN_BD); + length = min(n_bds * maxpacket, length); + } + + dev_dbg(musb->controller, "TX DMA%d, pktSz %d %s bds %d dma 0x%llx len %u\n", + tx->index, + maxpacket, + rndis ? "rndis" : "transparent", + n_bds, + (unsigned long long)addr, length); + + cppi_rndis_update(tx, 0, musb->ctrl_base, rndis); + + /* assuming here that channel_program is called during + * transfer initiation ... current code maintains state + * for one outstanding request only (no queues, not even + * the implicit ones of an iso urb). + */ + + bd = tx->freelist; + tx->head = bd; + tx->last_processed = NULL; + + /* FIXME use BD pool like RX side does, and just queue + * the minimum number for this request. + */ + + /* Prepare queue of BDs first, then hand it to hardware. + * All BDs except maybe the last should be of full packet + * size; for RNDIS there _is_ only that last packet. + */ + for (i = 0; i < n_bds; ) { + if (++i < n_bds && bd->next) + bd->hw_next = bd->next->dma; + else + bd->hw_next = 0; + + bd->hw_bufp = tx->buf_dma + tx->offset; + + /* FIXME set EOP only on the last packet, + * SOP only on the first ... avoid IRQs + */ + if ((tx->offset + maxpacket) <= tx->buf_len) { + tx->offset += maxpacket; + bd->hw_off_len = maxpacket; + bd->hw_options = CPPI_SOP_SET | CPPI_EOP_SET + | CPPI_OWN_SET | maxpacket; + } else { + /* only this one may be a partial USB Packet */ + u32 partial_len; + + partial_len = tx->buf_len - tx->offset; + tx->offset = tx->buf_len; + bd->hw_off_len = partial_len; + + bd->hw_options = CPPI_SOP_SET | CPPI_EOP_SET + | CPPI_OWN_SET | partial_len; + if (partial_len == 0) + bd->hw_options |= CPPI_ZERO_SET; + } + + dev_dbg(musb->controller, "TXBD %p: nxt %08x buf %08x len %04x opt %08x\n", + bd, bd->hw_next, bd->hw_bufp, + bd->hw_off_len, bd->hw_options); + + /* update the last BD enqueued to the list */ + tx->tail = bd; + bd = bd->next; + } + + /* BDs live in DMA-coherent memory, but writes might be pending */ + cpu_drain_writebuffer(); + + /* Write to the HeadPtr in state RAM to trigger */ + musb_writel(&tx_ram->tx_head, 0, (u32)tx->freelist->dma); + + cppi_dump_tx(5, tx, "/S"); +} + +/* + * CPPI RX Woes: + * ============= + * Consider a 1KB bulk RX buffer in two scenarios: (a) it's fed two 300 byte + * packets back-to-back, and (b) it's fed two 512 byte packets back-to-back. + * (Full speed transfers have similar scenarios.) + * + * The correct behavior for Linux is that (a) fills the buffer with 300 bytes, + * and the next packet goes into a buffer that's queued later; while (b) fills + * the buffer with 1024 bytes. How to do that with CPPI? + * + * - RX queues in "rndis" mode -- one single BD -- handle (a) correctly, but + * (b) loses **BADLY** because nothing (!) happens when that second packet + * fills the buffer, much less when a third one arrives. (Which makes this + * not a "true" RNDIS mode. In the RNDIS protocol short-packet termination + * is optional, and it's fine if peripherals -- not hosts! -- pad messages + * out to end-of-buffer. Standard PCI host controller DMA descriptors + * implement that mode by default ... which is no accident.) + * + * - RX queues in "transparent" mode -- two BDs with 512 bytes each -- have + * converse problems: (b) is handled right, but (a) loses badly. CPPI RX + * ignores SOP/EOP markings and processes both of those BDs; so both packets + * are loaded into the buffer (with a 212 byte gap between them), and the next + * buffer queued will NOT get its 300 bytes of data. (It seems like SOP/EOP + * are intended as outputs for RX queues, not inputs...) + * + * - A variant of "transparent" mode -- one BD at a time -- is the only way to + * reliably make both cases work, with software handling both cases correctly + * and at the significant penalty of needing an IRQ per packet. (The lack of + * I/O overlap can be slightly ameliorated by enabling double buffering.) + * + * So how to get rid of IRQ-per-packet? The transparent multi-BD case could + * be used in special cases like mass storage, which sets URB_SHORT_NOT_OK + * (or maybe its peripheral side counterpart) to flag (a) scenarios as errors + * with guaranteed driver level fault recovery and scrubbing out what's left + * of that garbaged datastream. + * + * But there seems to be no way to identify the cases where CPPI RNDIS mode + * is appropriate -- which do NOT include RNDIS host drivers, but do include + * the CDC Ethernet driver! -- and the documentation is incomplete/wrong. + * So we can't _ever_ use RX RNDIS mode ... except by using a heuristic + * that applies best on the peripheral side (and which could fail rudely). + * + * Leaving only "transparent" mode; we avoid multi-bd modes in almost all + * cases other than mass storage class. Otherwise we're correct but slow, + * since CPPI penalizes our need for a "true RNDIS" default mode. + */ + + +/* Heuristic, intended to kick in for ethernet/rndis peripheral ONLY + * + * IFF + * (a) peripheral mode ... since rndis peripherals could pad their + * writes to hosts, causing i/o failure; or we'd have to cope with + * a largely unknowable variety of host side protocol variants + * (b) and short reads are NOT errors ... since full reads would + * cause those same i/o failures + * (c) and read length is + * - less than 64KB (max per cppi descriptor) + * - not a multiple of 4096 (g_zero default, full reads typical) + * - N (>1) packets long, ditto (full reads not EXPECTED) + * THEN + * try rx rndis mode + * + * Cost of heuristic failing: RXDMA wedges at the end of transfers that + * fill out the whole buffer. Buggy host side usb network drivers could + * trigger that, but "in the field" such bugs seem to be all but unknown. + * + * So this module parameter lets the heuristic be disabled. When using + * gadgetfs, the heuristic will probably need to be disabled. + */ +static bool cppi_rx_rndis = 1; + +module_param(cppi_rx_rndis, bool, 0); +MODULE_PARM_DESC(cppi_rx_rndis, "enable/disable RX RNDIS heuristic"); + + +/** + * cppi_next_rx_segment - dma read for the next chunk of a buffer + * @musb: the controller + * @rx: dma channel + * @onepacket: true unless caller treats short reads as errors, and + * performs fault recovery above usbcore. + * Context: controller irqlocked + * + * See above notes about why we can't use multi-BD RX queues except in + * rare cases (mass storage class), and can never use the hardware "rndis" + * mode (since it's not a "true" RNDIS mode) with complete safety.. + * + * It's ESSENTIAL that callers specify "onepacket" mode unless they kick in + * code to recover from corrupted datastreams after each short transfer. + */ +static void +cppi_next_rx_segment(struct musb *musb, struct cppi_channel *rx, int onepacket) +{ + unsigned maxpacket = rx->maxpacket; + dma_addr_t addr = rx->buf_dma + rx->offset; + size_t length = rx->buf_len - rx->offset; + struct cppi_descriptor *bd, *tail; + unsigned n_bds; + unsigned i; + void __iomem *tibase = musb->ctrl_base; + int is_rndis = 0; + struct cppi_rx_stateram __iomem *rx_ram = rx->state_ram; + struct cppi_descriptor *d; + + if (onepacket) { + /* almost every USB driver, host or peripheral side */ + n_bds = 1; + + /* maybe apply the heuristic above */ + if (cppi_rx_rndis + && is_peripheral_active(musb) + && length > maxpacket + && (length & ~0xffff) == 0 + && (length & 0x0fff) != 0 + && (length & (maxpacket - 1)) == 0) { + maxpacket = length; + is_rndis = 1; + } + } else { + /* virtually nothing except mass storage class */ + if (length > 0xffff) { + n_bds = 0xffff / maxpacket; + length = n_bds * maxpacket; + } else { + n_bds = length / maxpacket; + if (length % maxpacket) + n_bds++; + } + if (n_bds == 1) + onepacket = 1; + else + n_bds = min(n_bds, (unsigned) NUM_RXCHAN_BD); + } + + /* In host mode, autorequest logic can generate some IN tokens; it's + * tricky since we can't leave REQPKT set in RXCSR after the transfer + * finishes. So: multipacket transfers involve two or more segments. + * And always at least two IRQs ... RNDIS mode is not an option. + */ + if (is_host_active(musb)) + n_bds = cppi_autoreq_update(rx, tibase, onepacket, n_bds); + + cppi_rndis_update(rx, 1, musb->ctrl_base, is_rndis); + + length = min(n_bds * maxpacket, length); + + dev_dbg(musb->controller, "RX DMA%d seg, maxp %d %s bds %d (cnt %d) " + "dma 0x%llx len %u %u/%u\n", + rx->index, maxpacket, + onepacket + ? (is_rndis ? "rndis" : "onepacket") + : "multipacket", + n_bds, + musb_readl(tibase, + DAVINCI_RXCPPI_BUFCNT0_REG + (rx->index * 4)) + & 0xffff, + (unsigned long long)addr, length, + rx->channel.actual_len, rx->buf_len); + + /* only queue one segment at a time, since the hardware prevents + * correct queue shutdown after unexpected short packets + */ + bd = cppi_bd_alloc(rx); + rx->head = bd; + + /* Build BDs for all packets in this segment */ + for (i = 0, tail = NULL; bd && i < n_bds; i++, tail = bd) { + u32 bd_len; + + if (i) { + bd = cppi_bd_alloc(rx); + if (!bd) + break; + tail->next = bd; + tail->hw_next = bd->dma; + } + bd->hw_next = 0; + + /* all but the last packet will be maxpacket size */ + if (maxpacket < length) + bd_len = maxpacket; + else + bd_len = length; + + bd->hw_bufp = addr; + addr += bd_len; + rx->offset += bd_len; + + bd->hw_off_len = (0 /*offset*/ << 16) + bd_len; + bd->buflen = bd_len; + + bd->hw_options = CPPI_OWN_SET | (i == 0 ? length : 0); + length -= bd_len; + } + + /* we always expect at least one reusable BD! */ + if (!tail) { + WARNING("rx dma%d -- no BDs? need %d\n", rx->index, n_bds); + return; + } else if (i < n_bds) + WARNING("rx dma%d -- only %d of %d BDs\n", rx->index, i, n_bds); + + tail->next = NULL; + tail->hw_next = 0; + + bd = rx->head; + rx->tail = tail; + + /* short reads and other faults should terminate this entire + * dma segment. we want one "dma packet" per dma segment, not + * one per USB packet, terminating the whole queue at once... + * NOTE that current hardware seems to ignore SOP and EOP. + */ + bd->hw_options |= CPPI_SOP_SET; + tail->hw_options |= CPPI_EOP_SET; + + for (d = rx->head; d; d = d->next) + cppi_dump_rxbd("S", d); + + /* in case the preceding transfer left some state... */ + tail = rx->last_processed; + if (tail) { + tail->next = bd; + tail->hw_next = bd->dma; + } + + core_rxirq_enable(tibase, rx->index + 1); + + /* BDs live in DMA-coherent memory, but writes might be pending */ + cpu_drain_writebuffer(); + + /* REVISIT specs say to write this AFTER the BUFCNT register + * below ... but that loses badly. + */ + musb_writel(&rx_ram->rx_head, 0, bd->dma); + + /* bufferCount must be at least 3, and zeroes on completion + * unless it underflows below zero, or stops at two, or keeps + * growing ... grr. + */ + i = musb_readl(tibase, + DAVINCI_RXCPPI_BUFCNT0_REG + (rx->index * 4)) + & 0xffff; + + if (!i) + musb_writel(tibase, + DAVINCI_RXCPPI_BUFCNT0_REG + (rx->index * 4), + n_bds + 2); + else if (n_bds > (i - 3)) + musb_writel(tibase, + DAVINCI_RXCPPI_BUFCNT0_REG + (rx->index * 4), + n_bds - (i - 3)); + + i = musb_readl(tibase, + DAVINCI_RXCPPI_BUFCNT0_REG + (rx->index * 4)) + & 0xffff; + if (i < (2 + n_bds)) { + dev_dbg(musb->controller, "bufcnt%d underrun - %d (for %d)\n", + rx->index, i, n_bds); + musb_writel(tibase, + DAVINCI_RXCPPI_BUFCNT0_REG + (rx->index * 4), + n_bds + 2); + } + + cppi_dump_rx(4, rx, "/S"); +} + +/** + * cppi_channel_program - program channel for data transfer + * @ch: the channel + * @maxpacket: max packet size + * @mode: For RX, 1 unless the usb protocol driver promised to treat + * all short reads as errors and kick in high level fault recovery. + * For TX, ignored because of RNDIS mode races/glitches. + * @dma_addr: dma address of buffer + * @len: length of buffer + * Context: controller irqlocked + */ +static int cppi_channel_program(struct dma_channel *ch, + u16 maxpacket, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + struct cppi_channel *cppi_ch; + struct cppi *controller; + struct musb *musb; + + cppi_ch = container_of(ch, struct cppi_channel, channel); + controller = cppi_ch->controller; + musb = controller->musb; + + switch (ch->status) { + case MUSB_DMA_STATUS_BUS_ABORT: + case MUSB_DMA_STATUS_CORE_ABORT: + /* fault irq handler should have handled cleanup */ + WARNING("%cX DMA%d not cleaned up after abort!\n", + cppi_ch->transmit ? 'T' : 'R', + cppi_ch->index); + /* WARN_ON(1); */ + break; + case MUSB_DMA_STATUS_BUSY: + WARNING("program active channel? %cX DMA%d\n", + cppi_ch->transmit ? 'T' : 'R', + cppi_ch->index); + /* WARN_ON(1); */ + break; + case MUSB_DMA_STATUS_UNKNOWN: + dev_dbg(musb->controller, "%cX DMA%d not allocated!\n", + cppi_ch->transmit ? 'T' : 'R', + cppi_ch->index); + /* FALLTHROUGH */ + case MUSB_DMA_STATUS_FREE: + break; + } + + ch->status = MUSB_DMA_STATUS_BUSY; + + /* set transfer parameters, then queue up its first segment */ + cppi_ch->buf_dma = dma_addr; + cppi_ch->offset = 0; + cppi_ch->maxpacket = maxpacket; + cppi_ch->buf_len = len; + cppi_ch->channel.actual_len = 0; + + /* TX channel? or RX? */ + if (cppi_ch->transmit) + cppi_next_tx_segment(musb, cppi_ch); + else + cppi_next_rx_segment(musb, cppi_ch, mode); + + return true; +} + +static bool cppi_rx_scan(struct cppi *cppi, unsigned ch) +{ + struct cppi_channel *rx = &cppi->rx[ch]; + struct cppi_rx_stateram __iomem *state = rx->state_ram; + struct cppi_descriptor *bd; + struct cppi_descriptor *last = rx->last_processed; + bool completed = false; + bool acked = false; + int i; + dma_addr_t safe2ack; + void __iomem *regs = rx->hw_ep->regs; + struct musb *musb = cppi->musb; + + cppi_dump_rx(6, rx, "/K"); + + bd = last ? last->next : rx->head; + if (!bd) + return false; + + /* run through all completed BDs */ + for (i = 0, safe2ack = musb_readl(&state->rx_complete, 0); + (safe2ack || completed) && bd && i < NUM_RXCHAN_BD; + i++, bd = bd->next) { + u16 len; + + /* catch latest BD writes from CPPI */ + rmb(); + if (!completed && (bd->hw_options & CPPI_OWN_SET)) + break; + + dev_dbg(musb->controller, "C/RXBD %llx: nxt %08x buf %08x " + "off.len %08x opt.len %08x (%d)\n", + (unsigned long long)bd->dma, bd->hw_next, bd->hw_bufp, + bd->hw_off_len, bd->hw_options, + rx->channel.actual_len); + + /* actual packet received length */ + if ((bd->hw_options & CPPI_SOP_SET) && !completed) + len = bd->hw_off_len & CPPI_RECV_PKTLEN_MASK; + else + len = 0; + + if (bd->hw_options & CPPI_EOQ_MASK) + completed = true; + + if (!completed && len < bd->buflen) { + /* NOTE: when we get a short packet, RXCSR_H_REQPKT + * must have been cleared, and no more DMA packets may + * active be in the queue... TI docs didn't say, but + * CPPI ignores those BDs even though OWN is still set. + */ + completed = true; + dev_dbg(musb->controller, "rx short %d/%d (%d)\n", + len, bd->buflen, + rx->channel.actual_len); + } + + /* If we got here, we expect to ack at least one BD; meanwhile + * CPPI may completing other BDs while we scan this list... + * + * RACE: we can notice OWN cleared before CPPI raises the + * matching irq by writing that BD as the completion pointer. + * In such cases, stop scanning and wait for the irq, avoiding + * lost acks and states where BD ownership is unclear. + */ + if (bd->dma == safe2ack) { + musb_writel(&state->rx_complete, 0, safe2ack); + safe2ack = musb_readl(&state->rx_complete, 0); + acked = true; + if (bd->dma == safe2ack) + safe2ack = 0; + } + + rx->channel.actual_len += len; + + cppi_bd_free(rx, last); + last = bd; + + /* stop scanning on end-of-segment */ + if (bd->hw_next == 0) + completed = true; + } + rx->last_processed = last; + + /* dma abort, lost ack, or ... */ + if (!acked && last) { + int csr; + + if (safe2ack == 0 || safe2ack == rx->last_processed->dma) + musb_writel(&state->rx_complete, 0, safe2ack); + if (safe2ack == 0) { + cppi_bd_free(rx, last); + rx->last_processed = NULL; + + /* if we land here on the host side, H_REQPKT will + * be clear and we need to restart the queue... + */ + WARN_ON(rx->head); + } + musb_ep_select(cppi->mregs, rx->index + 1); + csr = musb_readw(regs, MUSB_RXCSR); + if (csr & MUSB_RXCSR_DMAENAB) { + dev_dbg(musb->controller, "list%d %p/%p, last %llx%s, csr %04x\n", + rx->index, + rx->head, rx->tail, + rx->last_processed + ? (unsigned long long) + rx->last_processed->dma + : 0, + completed ? ", completed" : "", + csr); + cppi_dump_rxq(4, "/what?", rx); + } + } + if (!completed) { + int csr; + + rx->head = bd; + + /* REVISIT seems like "autoreq all but EOP" doesn't... + * setting it here "should" be racey, but seems to work + */ + csr = musb_readw(rx->hw_ep->regs, MUSB_RXCSR); + if (is_host_active(cppi->musb) + && bd + && !(csr & MUSB_RXCSR_H_REQPKT)) { + csr |= MUSB_RXCSR_H_REQPKT; + musb_writew(regs, MUSB_RXCSR, + MUSB_RXCSR_H_WZC_BITS | csr); + csr = musb_readw(rx->hw_ep->regs, MUSB_RXCSR); + } + } else { + rx->head = NULL; + rx->tail = NULL; + } + + cppi_dump_rx(6, rx, completed ? "/completed" : "/cleaned"); + return completed; +} + +irqreturn_t cppi_interrupt(int irq, void *dev_id) +{ + struct musb *musb = dev_id; + struct cppi *cppi; + void __iomem *tibase; + struct musb_hw_ep *hw_ep = NULL; + u32 rx, tx; + int i, index; + unsigned long uninitialized_var(flags); + + cppi = container_of(musb->dma_controller, struct cppi, controller); + if (cppi->irq) + spin_lock_irqsave(&musb->lock, flags); + + tibase = musb->ctrl_base; + + tx = musb_readl(tibase, DAVINCI_TXCPPI_MASKED_REG); + rx = musb_readl(tibase, DAVINCI_RXCPPI_MASKED_REG); + + if (!tx && !rx) { + if (cppi->irq) + spin_unlock_irqrestore(&musb->lock, flags); + return IRQ_NONE; + } + + dev_dbg(musb->controller, "CPPI IRQ Tx%x Rx%x\n", tx, rx); + + /* process TX channels */ + for (index = 0; tx; tx = tx >> 1, index++) { + struct cppi_channel *tx_ch; + struct cppi_tx_stateram __iomem *tx_ram; + bool completed = false; + struct cppi_descriptor *bd; + + if (!(tx & 1)) + continue; + + tx_ch = cppi->tx + index; + tx_ram = tx_ch->state_ram; + + /* FIXME need a cppi_tx_scan() routine, which + * can also be called from abort code + */ + + cppi_dump_tx(5, tx_ch, "/E"); + + bd = tx_ch->head; + + /* + * If Head is null then this could mean that a abort interrupt + * that needs to be acknowledged. + */ + if (NULL == bd) { + dev_dbg(musb->controller, "null BD\n"); + musb_writel(&tx_ram->tx_complete, 0, 0); + continue; + } + + /* run through all completed BDs */ + for (i = 0; !completed && bd && i < NUM_TXCHAN_BD; + i++, bd = bd->next) { + u16 len; + + /* catch latest BD writes from CPPI */ + rmb(); + if (bd->hw_options & CPPI_OWN_SET) + break; + + dev_dbg(musb->controller, "C/TXBD %p n %x b %x off %x opt %x\n", + bd, bd->hw_next, bd->hw_bufp, + bd->hw_off_len, bd->hw_options); + + len = bd->hw_off_len & CPPI_BUFFER_LEN_MASK; + tx_ch->channel.actual_len += len; + + tx_ch->last_processed = bd; + + /* write completion register to acknowledge + * processing of completed BDs, and possibly + * release the IRQ; EOQ might not be set ... + * + * REVISIT use the same ack strategy as rx + * + * REVISIT have observed bit 18 set; huh?? + */ + /* if ((bd->hw_options & CPPI_EOQ_MASK)) */ + musb_writel(&tx_ram->tx_complete, 0, bd->dma); + + /* stop scanning on end-of-segment */ + if (bd->hw_next == 0) + completed = true; + } + + /* on end of segment, maybe go to next one */ + if (completed) { + /* cppi_dump_tx(4, tx_ch, "/complete"); */ + + /* transfer more, or report completion */ + if (tx_ch->offset >= tx_ch->buf_len) { + tx_ch->head = NULL; + tx_ch->tail = NULL; + tx_ch->channel.status = MUSB_DMA_STATUS_FREE; + + hw_ep = tx_ch->hw_ep; + + musb_dma_completion(musb, index + 1, 1); + + } else { + /* Bigger transfer than we could fit in + * that first batch of descriptors... + */ + cppi_next_tx_segment(musb, tx_ch); + } + } else + tx_ch->head = bd; + } + + /* Start processing the RX block */ + for (index = 0; rx; rx = rx >> 1, index++) { + + if (rx & 1) { + struct cppi_channel *rx_ch; + + rx_ch = cppi->rx + index; + + /* let incomplete dma segments finish */ + if (!cppi_rx_scan(cppi, index)) + continue; + + /* start another dma segment if needed */ + if (rx_ch->channel.actual_len != rx_ch->buf_len + && rx_ch->channel.actual_len + == rx_ch->offset) { + cppi_next_rx_segment(musb, rx_ch, 1); + continue; + } + + /* all segments completed! */ + rx_ch->channel.status = MUSB_DMA_STATUS_FREE; + + hw_ep = rx_ch->hw_ep; + + core_rxirq_disable(tibase, index + 1); + musb_dma_completion(musb, index + 1, 0); + } + } + + /* write to CPPI EOI register to re-enable interrupts */ + musb_writel(tibase, DAVINCI_CPPI_EOI_REG, 0); + + if (cppi->irq) + spin_unlock_irqrestore(&musb->lock, flags); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(cppi_interrupt); + +/* Instantiate a software object representing a DMA controller. */ +struct dma_controller *dma_controller_create(struct musb *musb, void __iomem *mregs) +{ + struct cppi *controller; + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev); + int irq = platform_get_irq_byname(pdev, "dma"); + + controller = kzalloc(sizeof *controller, GFP_KERNEL); + if (!controller) + return NULL; + + controller->mregs = mregs; + controller->tibase = mregs - DAVINCI_BASE_OFFSET; + + controller->musb = musb; + controller->controller.start = cppi_controller_start; + controller->controller.stop = cppi_controller_stop; + controller->controller.channel_alloc = cppi_channel_allocate; + controller->controller.channel_release = cppi_channel_release; + controller->controller.channel_program = cppi_channel_program; + controller->controller.channel_abort = cppi_channel_abort; + + /* NOTE: allocating from on-chip SRAM would give the least + * contention for memory access, if that ever matters here. + */ + + /* setup BufferPool */ + controller->pool = dma_pool_create("cppi", + controller->musb->controller, + sizeof(struct cppi_descriptor), + CPPI_DESCRIPTOR_ALIGN, 0); + if (!controller->pool) { + kfree(controller); + return NULL; + } + + if (irq > 0) { + if (request_irq(irq, cppi_interrupt, 0, "cppi-dma", musb)) { + dev_err(dev, "request_irq %d failed!\n", irq); + dma_controller_destroy(&controller->controller); + return NULL; + } + controller->irq = irq; + } + + return &controller->controller; +} + +/* + * Destroy a previously-instantiated DMA controller. + */ +void dma_controller_destroy(struct dma_controller *c) +{ + struct cppi *cppi; + + cppi = container_of(c, struct cppi, controller); + + if (cppi->irq) + free_irq(cppi->irq, cppi->musb); + + /* assert: caller stopped the controller first */ + dma_pool_destroy(cppi->pool); + + kfree(cppi); +} + +/* + * Context: controller irqlocked, endpoint selected + */ +static int cppi_channel_abort(struct dma_channel *channel) +{ + struct cppi_channel *cppi_ch; + struct cppi *controller; + void __iomem *mbase; + void __iomem *tibase; + void __iomem *regs; + u32 value; + struct cppi_descriptor *queue; + + cppi_ch = container_of(channel, struct cppi_channel, channel); + + controller = cppi_ch->controller; + + switch (channel->status) { + case MUSB_DMA_STATUS_BUS_ABORT: + case MUSB_DMA_STATUS_CORE_ABORT: + /* from RX or TX fault irq handler */ + case MUSB_DMA_STATUS_BUSY: + /* the hardware needs shutting down */ + regs = cppi_ch->hw_ep->regs; + break; + case MUSB_DMA_STATUS_UNKNOWN: + case MUSB_DMA_STATUS_FREE: + return 0; + default: + return -EINVAL; + } + + if (!cppi_ch->transmit && cppi_ch->head) + cppi_dump_rxq(3, "/abort", cppi_ch); + + mbase = controller->mregs; + tibase = controller->tibase; + + queue = cppi_ch->head; + cppi_ch->head = NULL; + cppi_ch->tail = NULL; + + /* REVISIT should rely on caller having done this, + * and caller should rely on us not changing it. + * peripheral code is safe ... check host too. + */ + musb_ep_select(mbase, cppi_ch->index + 1); + + if (cppi_ch->transmit) { + struct cppi_tx_stateram __iomem *tx_ram; + /* REVISIT put timeouts on these controller handshakes */ + + cppi_dump_tx(6, cppi_ch, " (teardown)"); + + /* teardown DMA engine then usb core */ + do { + value = musb_readl(tibase, DAVINCI_TXCPPI_TEAR_REG); + } while (!(value & CPPI_TEAR_READY)); + musb_writel(tibase, DAVINCI_TXCPPI_TEAR_REG, cppi_ch->index); + + tx_ram = cppi_ch->state_ram; + do { + value = musb_readl(&tx_ram->tx_complete, 0); + } while (0xFFFFFFFC != value); + + /* FIXME clean up the transfer state ... here? + * the completion routine should get called with + * an appropriate status code. + */ + + value = musb_readw(regs, MUSB_TXCSR); + value &= ~MUSB_TXCSR_DMAENAB; + value |= MUSB_TXCSR_FLUSHFIFO; + musb_writew(regs, MUSB_TXCSR, value); + musb_writew(regs, MUSB_TXCSR, value); + + /* + * 1. Write to completion Ptr value 0x1(bit 0 set) + * (write back mode) + * 2. Wait for abort interrupt and then put the channel in + * compare mode by writing 1 to the tx_complete register. + */ + cppi_reset_tx(tx_ram, 1); + cppi_ch->head = NULL; + musb_writel(&tx_ram->tx_complete, 0, 1); + cppi_dump_tx(5, cppi_ch, " (done teardown)"); + + /* REVISIT tx side _should_ clean up the same way + * as the RX side ... this does no cleanup at all! + */ + + } else /* RX */ { + u16 csr; + + /* NOTE: docs don't guarantee any of this works ... we + * expect that if the usb core stops telling the cppi core + * to pull more data from it, then it'll be safe to flush + * current RX DMA state iff any pending fifo transfer is done. + */ + + core_rxirq_disable(tibase, cppi_ch->index + 1); + + /* for host, ensure ReqPkt is never set again */ + if (is_host_active(cppi_ch->controller->musb)) { + value = musb_readl(tibase, DAVINCI_AUTOREQ_REG); + value &= ~((0x3) << (cppi_ch->index * 2)); + musb_writel(tibase, DAVINCI_AUTOREQ_REG, value); + } + + csr = musb_readw(regs, MUSB_RXCSR); + + /* for host, clear (just) ReqPkt at end of current packet(s) */ + if (is_host_active(cppi_ch->controller->musb)) { + csr |= MUSB_RXCSR_H_WZC_BITS; + csr &= ~MUSB_RXCSR_H_REQPKT; + } else + csr |= MUSB_RXCSR_P_WZC_BITS; + + /* clear dma enable */ + csr &= ~(MUSB_RXCSR_DMAENAB); + musb_writew(regs, MUSB_RXCSR, csr); + csr = musb_readw(regs, MUSB_RXCSR); + + /* Quiesce: wait for current dma to finish (if not cleanup). + * We can't use bit zero of stateram->rx_sop, since that + * refers to an entire "DMA packet" not just emptying the + * current fifo. Most segments need multiple usb packets. + */ + if (channel->status == MUSB_DMA_STATUS_BUSY) + udelay(50); + + /* scan the current list, reporting any data that was + * transferred and acking any IRQ + */ + cppi_rx_scan(controller, cppi_ch->index); + + /* clobber the existing state once it's idle + * + * NOTE: arguably, we should also wait for all the other + * RX channels to quiesce (how??) and then temporarily + * disable RXCPPI_CTRL_REG ... but it seems that we can + * rely on the controller restarting from state ram, with + * only RXCPPI_BUFCNT state being bogus. BUFCNT will + * correct itself after the next DMA transfer though. + * + * REVISIT does using rndis mode change that? + */ + cppi_reset_rx(cppi_ch->state_ram); + + /* next DMA request _should_ load cppi head ptr */ + + /* ... we don't "free" that list, only mutate it in place. */ + cppi_dump_rx(5, cppi_ch, " (done abort)"); + + /* clean up previously pending bds */ + cppi_bd_free(cppi_ch, cppi_ch->last_processed); + cppi_ch->last_processed = NULL; + + while (queue) { + struct cppi_descriptor *tmp = queue->next; + + cppi_bd_free(cppi_ch, queue); + queue = tmp; + } + } + + channel->status = MUSB_DMA_STATUS_FREE; + cppi_ch->buf_dma = 0; + cppi_ch->offset = 0; + cppi_ch->buf_len = 0; + cppi_ch->maxpacket = 0; + return 0; +} + +/* TBD Queries: + * + * Power Management ... probably turn off cppi during suspend, restart; + * check state ram? Clocking is presumably shared with usb core. + */ diff --git a/drivers/usb/musb/cppi_dma.h b/drivers/usb/musb/cppi_dma.h new file mode 100644 index 00000000..59bf949e --- /dev/null +++ b/drivers/usb/musb/cppi_dma.h @@ -0,0 +1,134 @@ +/* Copyright (C) 2005-2006 by Texas Instruments */ + +#ifndef _CPPI_DMA_H_ +#define _CPPI_DMA_H_ + +#include +#include +#include +#include + +#include "musb_dma.h" +#include "musb_core.h" + + +/* FIXME fully isolate CPPI from DaVinci ... the "CPPI generic" registers + * would seem to be shared with the TUSB6020 (over VLYNQ). + */ + +#include "davinci.h" + + +/* CPPI RX/TX state RAM */ + +struct cppi_tx_stateram { + u32 tx_head; /* "DMA packet" head descriptor */ + u32 tx_buf; + u32 tx_current; /* current descriptor */ + u32 tx_buf_current; + u32 tx_info; /* flags, remaining buflen */ + u32 tx_rem_len; + u32 tx_dummy; /* unused */ + u32 tx_complete; +}; + +struct cppi_rx_stateram { + u32 rx_skipbytes; + u32 rx_head; + u32 rx_sop; /* "DMA packet" head descriptor */ + u32 rx_current; /* current descriptor */ + u32 rx_buf_current; + u32 rx_len_len; + u32 rx_cnt_cnt; + u32 rx_complete; +}; + +/* hw_options bits in CPPI buffer descriptors */ +#define CPPI_SOP_SET ((u32)(1 << 31)) +#define CPPI_EOP_SET ((u32)(1 << 30)) +#define CPPI_OWN_SET ((u32)(1 << 29)) /* owned by cppi */ +#define CPPI_EOQ_MASK ((u32)(1 << 28)) +#define CPPI_ZERO_SET ((u32)(1 << 23)) /* rx saw zlp; tx issues one */ +#define CPPI_RXABT_MASK ((u32)(1 << 19)) /* need more rx buffers */ + +#define CPPI_RECV_PKTLEN_MASK 0xFFFF +#define CPPI_BUFFER_LEN_MASK 0xFFFF + +#define CPPI_TEAR_READY ((u32)(1 << 31)) + +/* CPPI data structure definitions */ + +#define CPPI_DESCRIPTOR_ALIGN 16 /* bytes; 5-dec docs say 4-byte align */ + +struct cppi_descriptor { + /* hardware overlay */ + u32 hw_next; /* next buffer descriptor Pointer */ + u32 hw_bufp; /* i/o buffer pointer */ + u32 hw_off_len; /* buffer_offset16, buffer_length16 */ + u32 hw_options; /* flags: SOP, EOP etc*/ + + struct cppi_descriptor *next; + dma_addr_t dma; /* address of this descriptor */ + u32 buflen; /* for RX: original buffer length */ +} __attribute__ ((aligned(CPPI_DESCRIPTOR_ALIGN))); + + +struct cppi; + +/* CPPI Channel Control structure */ +struct cppi_channel { + struct dma_channel channel; + + /* back pointer to the DMA controller structure */ + struct cppi *controller; + + /* which direction of which endpoint? */ + struct musb_hw_ep *hw_ep; + bool transmit; + u8 index; + + /* DMA modes: RNDIS or "transparent" */ + u8 is_rndis; + + /* book keeping for current transfer request */ + dma_addr_t buf_dma; + u32 buf_len; + u32 maxpacket; + u32 offset; /* dma requested */ + + void __iomem *state_ram; /* CPPI state */ + + struct cppi_descriptor *freelist; + + /* BD management fields */ + struct cppi_descriptor *head; + struct cppi_descriptor *tail; + struct cppi_descriptor *last_processed; + + /* use tx_complete in host role to track endpoints waiting for + * FIFONOTEMPTY to clear. + */ + struct list_head tx_complete; +}; + +/* CPPI DMA controller object */ +struct cppi { + struct dma_controller controller; + struct musb *musb; + void __iomem *mregs; /* Mentor regs */ + void __iomem *tibase; /* TI/CPPI regs */ + + int irq; + + struct cppi_channel tx[4]; + struct cppi_channel rx[4]; + + struct dma_pool *pool; + + struct list_head tx_complete; +}; + +/* CPPI IRQ handler */ +extern irqreturn_t cppi_interrupt(int, void *); + +#endif /* end of ifndef _CPPI_DMA_H_ */ diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c new file mode 100644 index 00000000..b903b744 --- /dev/null +++ b/drivers/usb/musb/da8xx.c @@ -0,0 +1,584 @@ +/* + * Texas Instruments DA8xx/OMAP-L1x "glue layer" + * + * Copyright (c) 2008-2009 MontaVista Software, Inc. + * + * Based on the DaVinci "glue layer" code. + * Copyright (C) 2005-2006 by Texas Instruments + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + * + * The Inventra Controller Driver for Linux is distributed in + * the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "musb_core.h" + +/* + * DA8XX specific definitions + */ + +/* USB 2.0 OTG module registers */ +#define DA8XX_USB_REVISION_REG 0x00 +#define DA8XX_USB_CTRL_REG 0x04 +#define DA8XX_USB_STAT_REG 0x08 +#define DA8XX_USB_EMULATION_REG 0x0c +#define DA8XX_USB_MODE_REG 0x10 /* Transparent, CDC, [Generic] RNDIS */ +#define DA8XX_USB_AUTOREQ_REG 0x14 +#define DA8XX_USB_SRP_FIX_TIME_REG 0x18 +#define DA8XX_USB_TEARDOWN_REG 0x1c +#define DA8XX_USB_INTR_SRC_REG 0x20 +#define DA8XX_USB_INTR_SRC_SET_REG 0x24 +#define DA8XX_USB_INTR_SRC_CLEAR_REG 0x28 +#define DA8XX_USB_INTR_MASK_REG 0x2c +#define DA8XX_USB_INTR_MASK_SET_REG 0x30 +#define DA8XX_USB_INTR_MASK_CLEAR_REG 0x34 +#define DA8XX_USB_INTR_SRC_MASKED_REG 0x38 +#define DA8XX_USB_END_OF_INTR_REG 0x3c +#define DA8XX_USB_GENERIC_RNDIS_EP_SIZE_REG(n) (0x50 + (((n) - 1) << 2)) + +/* Control register bits */ +#define DA8XX_SOFT_RESET_MASK 1 + +#define DA8XX_USB_TX_EP_MASK 0x1f /* EP0 + 4 Tx EPs */ +#define DA8XX_USB_RX_EP_MASK 0x1e /* 4 Rx EPs */ + +/* USB interrupt register bits */ +#define DA8XX_INTR_USB_SHIFT 16 +#define DA8XX_INTR_USB_MASK (0x1ff << DA8XX_INTR_USB_SHIFT) /* 8 Mentor */ + /* interrupts and DRVVBUS interrupt */ +#define DA8XX_INTR_DRVVBUS 0x100 +#define DA8XX_INTR_RX_SHIFT 8 +#define DA8XX_INTR_RX_MASK (DA8XX_USB_RX_EP_MASK << DA8XX_INTR_RX_SHIFT) +#define DA8XX_INTR_TX_SHIFT 0 +#define DA8XX_INTR_TX_MASK (DA8XX_USB_TX_EP_MASK << DA8XX_INTR_TX_SHIFT) + +#define DA8XX_MENTOR_CORE_OFFSET 0x400 + +#define CFGCHIP2 IO_ADDRESS(DA8XX_SYSCFG0_BASE + DA8XX_CFGCHIP2_REG) + +struct da8xx_glue { + struct device *dev; + struct platform_device *musb; + struct clk *clk; +}; + +/* + * REVISIT (PM): we should be able to keep the PHY in low power mode most + * of the time (24 MHz oscillator and PLL off, etc.) by setting POWER.D0 + * and, when in host mode, autosuspending idle root ports... PHY_PLLON + * (overriding SUSPENDM?) then likely needs to stay off. + */ + +static inline void phy_on(void) +{ + u32 cfgchip2 = __raw_readl(CFGCHIP2); + + /* + * Start the on-chip PHY and its PLL. + */ + cfgchip2 &= ~(CFGCHIP2_RESET | CFGCHIP2_PHYPWRDN | CFGCHIP2_OTGPWRDN); + cfgchip2 |= CFGCHIP2_PHY_PLLON; + __raw_writel(cfgchip2, CFGCHIP2); + + pr_info("Waiting for USB PHY clock good...\n"); + while (!(__raw_readl(CFGCHIP2) & CFGCHIP2_PHYCLKGD)) + cpu_relax(); +} + +static inline void phy_off(void) +{ + u32 cfgchip2 = __raw_readl(CFGCHIP2); + + /* + * Ensure that USB 1.1 reference clock is not being sourced from + * USB 2.0 PHY. Otherwise do not power down the PHY. + */ + if (!(cfgchip2 & CFGCHIP2_USB1PHYCLKMUX) && + (cfgchip2 & CFGCHIP2_USB1SUSPENDM)) { + pr_warning("USB 1.1 clocked from USB 2.0 PHY -- " + "can't power it down\n"); + return; + } + + /* + * Power down the on-chip PHY. + */ + cfgchip2 |= CFGCHIP2_PHYPWRDN | CFGCHIP2_OTGPWRDN; + __raw_writel(cfgchip2, CFGCHIP2); +} + +/* + * Because we don't set CTRL.UINT, it's "important" to: + * - not read/write INTRUSB/INTRUSBE (except during + * initial setup, as a workaround); + * - use INTSET/INTCLR instead. + */ + +/** + * da8xx_musb_enable - enable interrupts + */ +static void da8xx_musb_enable(struct musb *musb) +{ + void __iomem *reg_base = musb->ctrl_base; + u32 mask; + + /* Workaround: setup IRQs through both register sets. */ + mask = ((musb->epmask & DA8XX_USB_TX_EP_MASK) << DA8XX_INTR_TX_SHIFT) | + ((musb->epmask & DA8XX_USB_RX_EP_MASK) << DA8XX_INTR_RX_SHIFT) | + DA8XX_INTR_USB_MASK; + musb_writel(reg_base, DA8XX_USB_INTR_MASK_SET_REG, mask); + + /* Force the DRVVBUS IRQ so we can start polling for ID change. */ + musb_writel(reg_base, DA8XX_USB_INTR_SRC_SET_REG, + DA8XX_INTR_DRVVBUS << DA8XX_INTR_USB_SHIFT); +} + +/** + * da8xx_musb_disable - disable HDRC and flush interrupts + */ +static void da8xx_musb_disable(struct musb *musb) +{ + void __iomem *reg_base = musb->ctrl_base; + + musb_writel(reg_base, DA8XX_USB_INTR_MASK_CLEAR_REG, + DA8XX_INTR_USB_MASK | + DA8XX_INTR_TX_MASK | DA8XX_INTR_RX_MASK); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0); +} + +#define portstate(stmt) stmt + +static void da8xx_musb_set_vbus(struct musb *musb, int is_on) +{ + WARN_ON(is_on && is_peripheral_active(musb)); +} + +#define POLL_SECONDS 2 + +static struct timer_list otg_workaround; + +static void otg_timer(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + void __iomem *mregs = musb->mregs; + u8 devctl; + unsigned long flags; + + /* + * We poll because DaVinci's won't expose several OTG-critical + * status change events (from the transceiver) otherwise. + */ + devctl = musb_readb(mregs, MUSB_DEVCTL); + dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl, + usb_otg_state_string(musb->xceiv->state)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_BCON: + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + break; + case OTG_STATE_A_WAIT_VFALL: + /* + * Wait till VBUS falls below SessionEnd (~0.2 V); the 1.3 + * RTL seems to mis-handle session "start" otherwise (or in + * our case "recover"), in routine "VBUS was valid by the time + * VBUSERR got reported during enumeration" cases. + */ + if (devctl & MUSB_DEVCTL_VBUS) { + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + break; + } + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + musb_writel(musb->ctrl_base, DA8XX_USB_INTR_SRC_SET_REG, + MUSB_INTR_VBUSERROR << DA8XX_INTR_USB_SHIFT); + break; + case OTG_STATE_B_IDLE: + /* + * There's no ID-changed IRQ, so we have no good way to tell + * when to switch to the A-Default state machine (by setting + * the DEVCTL.Session bit). + * + * Workaround: whenever we're in B_IDLE, try setting the + * session flag every few seconds. If it works, ID was + * grounded and we're now in the A-Default state machine. + * + * NOTE: setting the session flag is _supposed_ to trigger + * SRP but clearly it doesn't. + */ + musb_writeb(mregs, MUSB_DEVCTL, devctl | MUSB_DEVCTL_SESSION); + devctl = musb_readb(mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + else + musb->xceiv->state = OTG_STATE_A_IDLE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +static void da8xx_musb_try_idle(struct musb *musb, unsigned long timeout) +{ + static unsigned long last_timer; + + 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->state == OTG_STATE_A_WAIT_BCON)) { + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->state)); + del_timer(&otg_workaround); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout) && timer_pending(&otg_workaround)) { + dev_dbg(musb->controller, "Longer idle timer already pending, ignoring...\n"); + return; + } + last_timer = timeout; + + dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n", + usb_otg_state_string(musb->xceiv->state), + jiffies_to_msecs(timeout - jiffies)); + mod_timer(&otg_workaround, timeout); +} + +static irqreturn_t da8xx_musb_interrupt(int irq, void *hci) +{ + struct musb *musb = hci; + void __iomem *reg_base = musb->ctrl_base; + struct usb_otg *otg = musb->xceiv->otg; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + u32 status; + + spin_lock_irqsave(&musb->lock, flags); + + /* + * NOTE: DA8XX shadows the Mentor IRQs. Don't manage them through + * the Mentor registers (except for setup), use the TI ones and EOI. + */ + + /* Acknowledge and handle non-CPPI interrupts */ + status = musb_readl(reg_base, DA8XX_USB_INTR_SRC_MASKED_REG); + if (!status) + goto eoi; + + musb_writel(reg_base, DA8XX_USB_INTR_SRC_CLEAR_REG, status); + dev_dbg(musb->controller, "USB IRQ %08x\n", status); + + musb->int_rx = (status & DA8XX_INTR_RX_MASK) >> DA8XX_INTR_RX_SHIFT; + musb->int_tx = (status & DA8XX_INTR_TX_MASK) >> DA8XX_INTR_TX_SHIFT; + musb->int_usb = (status & DA8XX_INTR_USB_MASK) >> DA8XX_INTR_USB_SHIFT; + + /* + * DRVVBUS IRQs are the only proxy we have (a very poor one!) for + * DA8xx's missing ID change IRQ. We need an ID change IRQ to + * switch appropriately between halves of the OTG state machine. + * Managing DEVCTL.Session per Mentor docs requires that we know its + * value but DEVCTL.BDevice is invalid without DEVCTL.Session set. + * Also, DRVVBUS pulses for SRP (but not at 5 V)... + */ + if (status & (DA8XX_INTR_DRVVBUS << DA8XX_INTR_USB_SHIFT)) { + int drvvbus = musb_readl(reg_base, DA8XX_USB_STAT_REG); + void __iomem *mregs = musb->mregs; + u8 devctl = musb_readb(mregs, MUSB_DEVCTL); + int err; + + err = musb->int_usb & MUSB_INTR_VBUSERROR; + if (err) { + /* + * The Mentor core doesn't debounce VBUS as needed + * to cope with device connect current spikes. This + * means it's not uncommon for bus-powered devices + * to get VBUS errors during enumeration. + * + * This is a workaround, but newer RTL from Mentor + * seems to allow a better one: "re"-starting sessions + * without waiting for VBUS to stop registering in + * devctl. + */ + musb->int_usb &= ~MUSB_INTR_VBUSERROR; + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + WARNING("VBUS error workaround (delay coming)\n"); + } else if (drvvbus) { + MUSB_HST_MODE(musb); + otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + portstate(musb->port1_status |= USB_PORT_STAT_POWER); + del_timer(&otg_workaround); + } else { + musb->is_active = 0; + MUSB_DEV_MODE(musb); + otg->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; + portstate(musb->port1_status &= ~USB_PORT_STAT_POWER); + } + + dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n", + drvvbus ? "on" : "off", + usb_otg_state_string(musb->xceiv->state), + err ? " ERROR" : "", + devctl); + ret = IRQ_HANDLED; + } + + if (musb->int_tx || musb->int_rx || musb->int_usb) + ret |= musb_interrupt(musb); + + eoi: + /* EOI needs to be written for the IRQ to be re-asserted. */ + if (ret == IRQ_HANDLED || status) + musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0); + + /* Poll for ID change */ + if (musb->xceiv->state == OTG_STATE_B_IDLE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static int da8xx_musb_set_mode(struct musb *musb, u8 musb_mode) +{ + u32 cfgchip2 = __raw_readl(CFGCHIP2); + + cfgchip2 &= ~CFGCHIP2_OTGMODE; + switch (musb_mode) { + case MUSB_HOST: /* Force VBUS valid, ID = 0 */ + cfgchip2 |= CFGCHIP2_FORCE_HOST; + break; + case MUSB_PERIPHERAL: /* Force VBUS valid, ID = 1 */ + cfgchip2 |= CFGCHIP2_FORCE_DEVICE; + break; + case MUSB_OTG: /* Don't override the VBUS/ID comparators */ + cfgchip2 |= CFGCHIP2_NO_OVERRIDE; + break; + default: + dev_dbg(musb->controller, "Trying to set unsupported mode %u\n", musb_mode); + } + + __raw_writel(cfgchip2, CFGCHIP2); + return 0; +} + +static int da8xx_musb_init(struct musb *musb) +{ + void __iomem *reg_base = musb->ctrl_base; + u32 rev; + int ret = -ENODEV; + + musb->mregs += DA8XX_MENTOR_CORE_OFFSET; + + /* Returns zero if e.g. not clocked */ + rev = musb_readl(reg_base, DA8XX_USB_REVISION_REG); + if (!rev) + goto fail; + + usb_nop_xceiv_register(); + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) { + ret = -EPROBE_DEFER; + goto fail; + } + + setup_timer(&otg_workaround, otg_timer, (unsigned long)musb); + + /* Reset the controller */ + musb_writel(reg_base, DA8XX_USB_CTRL_REG, DA8XX_SOFT_RESET_MASK); + + /* Start the on-chip PHY and its PLL. */ + phy_on(); + + msleep(5); + + /* NOTE: IRQs are in mixed mode, not bypass to pure MUSB */ + pr_debug("DA8xx OTG revision %08x, PHY %03x, control %02x\n", + rev, __raw_readl(CFGCHIP2), + musb_readb(reg_base, DA8XX_USB_CTRL_REG)); + + musb->isr = da8xx_musb_interrupt; + return 0; +fail: + return ret; +} + +static int da8xx_musb_exit(struct musb *musb) +{ + del_timer_sync(&otg_workaround); + + phy_off(); + + usb_put_phy(musb->xceiv); + usb_nop_xceiv_unregister(); + + return 0; +} + +static const struct musb_platform_ops da8xx_ops = { + .init = da8xx_musb_init, + .exit = da8xx_musb_exit, + + .enable = da8xx_musb_enable, + .disable = da8xx_musb_disable, + + .set_mode = da8xx_musb_set_mode, + .try_idle = da8xx_musb_try_idle, + + .set_vbus = da8xx_musb_set_vbus, +}; + +static u64 da8xx_dmamask = DMA_BIT_MASK(32); + +static int da8xx_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct platform_device *musb; + struct da8xx_glue *glue; + + struct clk *clk; + + int ret = -ENOMEM; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(&pdev->dev, "failed to allocate musb device\n"); + goto err1; + } + + clk = clk_get(&pdev->dev, "usb20"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err3; + } + + ret = clk_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err4; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &da8xx_dmamask; + musb->dev.coherent_dma_mask = da8xx_dmamask; + + glue->dev = &pdev->dev; + glue->musb = musb; + glue->clk = clk; + + pdata->platform_ops = &da8xx_ops; + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(&pdev->dev, "failed to add resources\n"); + goto err5; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err5; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err5; + } + + return 0; + +err5: + clk_disable(clk); + +err4: + clk_put(clk); + +err3: + platform_device_put(musb); + +err1: + kfree(glue); + +err0: + return ret; +} + +static int da8xx_remove(struct platform_device *pdev) +{ + struct da8xx_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + clk_disable(glue->clk); + clk_put(glue->clk); + kfree(glue); + + return 0; +} + +static struct platform_driver da8xx_driver = { + .probe = da8xx_probe, + .remove = da8xx_remove, + .driver = { + .name = "musb-da8xx", + }, +}; + +MODULE_DESCRIPTION("DA8xx/OMAP-L1x MUSB Glue Layer"); +MODULE_AUTHOR("Sergei Shtylyov "); +MODULE_LICENSE("GPL v2"); +module_platform_driver(da8xx_driver); diff --git a/drivers/usb/musb/davinci.c b/drivers/usb/musb/davinci.c new file mode 100644 index 00000000..bea6cc35 --- /dev/null +++ b/drivers/usb/musb/davinci.c @@ -0,0 +1,616 @@ +/* + * Copyright (C) 2005-2006 by Texas Instruments + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + * + * The Inventra Controller Driver for Linux is distributed in + * the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "musb_core.h" + +#ifdef CONFIG_MACH_DAVINCI_EVM +#define GPIO_nVBUS_DRV 160 +#endif + +#include "davinci.h" +#include "cppi_dma.h" + + +#define USB_PHY_CTRL IO_ADDRESS(USBPHY_CTL_PADDR) +#define DM355_DEEPSLEEP IO_ADDRESS(DM355_DEEPSLEEP_PADDR) + +struct davinci_glue { + struct device *dev; + struct platform_device *musb; + struct clk *clk; +}; + +/* REVISIT (PM) we should be able to keep the PHY in low power mode most + * of the time (24 MHZ oscillator and PLL off, etc) by setting POWER.D0 + * and, when in host mode, autosuspending idle root ports... PHYPLLON + * (overriding SUSPENDM?) then likely needs to stay off. + */ + +static inline void phy_on(void) +{ + u32 phy_ctrl = __raw_readl(USB_PHY_CTRL); + + /* power everything up; start the on-chip PHY and its PLL */ + phy_ctrl &= ~(USBPHY_OSCPDWN | USBPHY_OTGPDWN | USBPHY_PHYPDWN); + phy_ctrl |= USBPHY_SESNDEN | USBPHY_VBDTCTEN | USBPHY_PHYPLLON; + __raw_writel(phy_ctrl, USB_PHY_CTRL); + + /* wait for PLL to lock before proceeding */ + while ((__raw_readl(USB_PHY_CTRL) & USBPHY_PHYCLKGD) == 0) + cpu_relax(); +} + +static inline void phy_off(void) +{ + u32 phy_ctrl = __raw_readl(USB_PHY_CTRL); + + /* powerdown the on-chip PHY, its PLL, and the OTG block */ + phy_ctrl &= ~(USBPHY_SESNDEN | USBPHY_VBDTCTEN | USBPHY_PHYPLLON); + phy_ctrl |= USBPHY_OSCPDWN | USBPHY_OTGPDWN | USBPHY_PHYPDWN; + __raw_writel(phy_ctrl, USB_PHY_CTRL); +} + +static int dma_off = 1; + +static void davinci_musb_enable(struct musb *musb) +{ + u32 tmp, old, val; + + /* workaround: setup irqs through both register sets */ + tmp = (musb->epmask & DAVINCI_USB_TX_ENDPTS_MASK) + << DAVINCI_USB_TXINT_SHIFT; + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp); + old = tmp; + tmp = (musb->epmask & (0xfffe & DAVINCI_USB_RX_ENDPTS_MASK)) + << DAVINCI_USB_RXINT_SHIFT; + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp); + tmp |= old; + + val = ~MUSB_INTR_SOF; + tmp |= ((val & 0x01ff) << DAVINCI_USB_USBINT_SHIFT); + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_SET_REG, tmp); + + if (is_dma_capable() && !dma_off) + printk(KERN_WARNING "%s %s: dma not reactivated\n", + __FILE__, __func__); + else + dma_off = 0; + + /* force a DRVVBUS irq so we can start polling for ID change */ + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_SET_REG, + DAVINCI_INTR_DRVVBUS << DAVINCI_USB_USBINT_SHIFT); +} + +/* + * Disable the HDRC and flush interrupts + */ +static void davinci_musb_disable(struct musb *musb) +{ + /* because we don't set CTRLR.UINT, "important" to: + * - not read/write INTRUSB/INTRUSBE + * - (except during initial setup, as workaround) + * - use INTSETR/INTCLRR instead + */ + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_MASK_CLR_REG, + DAVINCI_USB_USBINT_MASK + | DAVINCI_USB_TXINT_MASK + | DAVINCI_USB_RXINT_MASK); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + musb_writel(musb->ctrl_base, DAVINCI_USB_EOI_REG, 0); + + if (is_dma_capable() && !dma_off) + WARNING("dma still active\n"); +} + + +#define portstate(stmt) stmt + +/* + * VBUS SWITCHING IS BOARD-SPECIFIC ... at least for the DM6446 EVM, + * which doesn't wire DRVVBUS to the FET that switches it. Unclear + * if that's a problem with the DM6446 chip or just with that board. + * + * In either case, the DM355 EVM automates DRVVBUS the normal way, + * when J10 is out, and TI documents it as handling OTG. + */ + +#ifdef CONFIG_MACH_DAVINCI_EVM + +static int vbus_state = -1; + +/* I2C operations are always synchronous, and require a task context. + * With unloaded systems, using the shared workqueue seems to suffice + * to satisfy the 100msec A_WAIT_VRISE timeout... + */ +static void evm_deferred_drvvbus(struct work_struct *ignored) +{ + gpio_set_value_cansleep(GPIO_nVBUS_DRV, vbus_state); + vbus_state = !vbus_state; +} + +#endif /* EVM */ + +static void davinci_musb_source_power(struct musb *musb, int is_on, int immediate) +{ +#ifdef CONFIG_MACH_DAVINCI_EVM + if (is_on) + is_on = 1; + + if (vbus_state == is_on) + return; + vbus_state = !is_on; /* 0/1 vs "-1 == unknown/init" */ + + if (machine_is_davinci_evm()) { + static DECLARE_WORK(evm_vbus_work, evm_deferred_drvvbus); + + if (immediate) + gpio_set_value_cansleep(GPIO_nVBUS_DRV, vbus_state); + else + schedule_work(&evm_vbus_work); + } + if (immediate) + vbus_state = is_on; +#endif +} + +static void davinci_musb_set_vbus(struct musb *musb, int is_on) +{ + WARN_ON(is_on && is_peripheral_active(musb)); + davinci_musb_source_power(musb, is_on, 0); +} + + +#define POLL_SECONDS 2 + +static struct timer_list otg_workaround; + +static void otg_timer(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + void __iomem *mregs = musb->mregs; + u8 devctl; + unsigned long flags; + + /* We poll because DaVinci's won't expose several OTG-critical + * status change events (from the transceiver) otherwise. + */ + devctl = musb_readb(mregs, MUSB_DEVCTL); + dev_dbg(musb->controller, "poll devctl %02x (%s)\n", devctl, + usb_otg_state_string(musb->xceiv->state)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_VFALL: + /* Wait till VBUS falls below SessionEnd (~0.2V); the 1.3 RTL + * seems to mis-handle session "start" otherwise (or in our + * case "recover"), in routine "VBUS was valid by the time + * VBUSERR got reported during enumeration" cases. + */ + if (devctl & MUSB_DEVCTL_VBUS) { + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + break; + } + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + musb_writel(musb->ctrl_base, DAVINCI_USB_INT_SET_REG, + MUSB_INTR_VBUSERROR << DAVINCI_USB_USBINT_SHIFT); + break; + case OTG_STATE_B_IDLE: + /* + * There's no ID-changed IRQ, so we have no good way to tell + * when to switch to the A-Default state machine (by setting + * the DEVCTL.SESSION flag). + * + * Workaround: whenever we're in B_IDLE, try setting the + * session flag every few seconds. If it works, ID was + * grounded and we're now in the A-Default state machine. + * + * NOTE setting the session flag is _supposed_ to trigger + * SRP, but clearly it doesn't. + */ + musb_writeb(mregs, MUSB_DEVCTL, + devctl | MUSB_DEVCTL_SESSION); + devctl = musb_readb(mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + else + musb->xceiv->state = OTG_STATE_A_IDLE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +static irqreturn_t davinci_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + struct musb *musb = __hci; + struct usb_otg *otg = musb->xceiv->otg; + void __iomem *tibase = musb->ctrl_base; + struct cppi *cppi; + u32 tmp; + + spin_lock_irqsave(&musb->lock, flags); + + /* NOTE: DaVinci shadows the Mentor IRQs. Don't manage them through + * the Mentor registers (except for setup), use the TI ones and EOI. + * + * Docs describe irq "vector" registers associated with the CPPI and + * USB EOI registers. These hold a bitmask corresponding to the + * current IRQ, not an irq handler address. Would using those bits + * resolve some of the races observed in this dispatch code?? + */ + + /* CPPI interrupts share the same IRQ line, but have their own + * mask, state, "vector", and EOI registers. + */ + cppi = container_of(musb->dma_controller, struct cppi, controller); + if (is_cppi_enabled() && musb->dma_controller && !cppi->irq) + retval = cppi_interrupt(irq, __hci); + + /* ack and handle non-CPPI interrupts */ + tmp = musb_readl(tibase, DAVINCI_USB_INT_SRC_MASKED_REG); + musb_writel(tibase, DAVINCI_USB_INT_SRC_CLR_REG, tmp); + dev_dbg(musb->controller, "IRQ %08x\n", tmp); + + musb->int_rx = (tmp & DAVINCI_USB_RXINT_MASK) + >> DAVINCI_USB_RXINT_SHIFT; + musb->int_tx = (tmp & DAVINCI_USB_TXINT_MASK) + >> DAVINCI_USB_TXINT_SHIFT; + musb->int_usb = (tmp & DAVINCI_USB_USBINT_MASK) + >> DAVINCI_USB_USBINT_SHIFT; + + /* DRVVBUS irqs are the only proxy we have (a very poor one!) for + * DaVinci's missing ID change IRQ. We need an ID change IRQ to + * switch appropriately between halves of the OTG state machine. + * Managing DEVCTL.SESSION per Mentor docs requires we know its + * value, but DEVCTL.BDEVICE is invalid without DEVCTL.SESSION set. + * Also, DRVVBUS pulses for SRP (but not at 5V) ... + */ + if (tmp & (DAVINCI_INTR_DRVVBUS << DAVINCI_USB_USBINT_SHIFT)) { + int drvvbus = musb_readl(tibase, DAVINCI_USB_STAT_REG); + void __iomem *mregs = musb->mregs; + u8 devctl = musb_readb(mregs, MUSB_DEVCTL); + int err = musb->int_usb & MUSB_INTR_VBUSERROR; + + err = musb->int_usb & MUSB_INTR_VBUSERROR; + if (err) { + /* The Mentor core doesn't debounce VBUS as needed + * to cope with device connect current spikes. This + * means it's not uncommon for bus-powered devices + * to get VBUS errors during enumeration. + * + * This is a workaround, but newer RTL from Mentor + * seems to allow a better one: "re"starting sessions + * without waiting (on EVM, a **long** time) for VBUS + * to stop registering in devctl. + */ + musb->int_usb &= ~MUSB_INTR_VBUSERROR; + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + WARNING("VBUS error workaround (delay coming)\n"); + } else if (drvvbus) { + MUSB_HST_MODE(musb); + otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + portstate(musb->port1_status |= USB_PORT_STAT_POWER); + del_timer(&otg_workaround); + } else { + musb->is_active = 0; + MUSB_DEV_MODE(musb); + otg->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; + portstate(musb->port1_status &= ~USB_PORT_STAT_POWER); + } + + /* NOTE: this must complete poweron within 100 msec + * (OTG_TIME_A_WAIT_VRISE) but we don't check for that. + */ + davinci_musb_source_power(musb, drvvbus, 0); + dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n", + drvvbus ? "on" : "off", + usb_otg_state_string(musb->xceiv->state), + err ? " ERROR" : "", + devctl); + retval = IRQ_HANDLED; + } + + if (musb->int_tx || musb->int_rx || musb->int_usb) + retval |= musb_interrupt(musb); + + /* irq stays asserted until EOI is written */ + musb_writel(tibase, DAVINCI_USB_EOI_REG, 0); + + /* poll for ID change */ + if (musb->xceiv->state == OTG_STATE_B_IDLE) + mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + return retval; +} + +static int davinci_musb_set_mode(struct musb *musb, u8 mode) +{ + /* EVM can't do this (right?) */ + return -EIO; +} + +static int davinci_musb_init(struct musb *musb) +{ + void __iomem *tibase = musb->ctrl_base; + u32 revision; + int ret = -ENODEV; + + usb_nop_xceiv_register(); + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) { + ret = -EPROBE_DEFER; + goto unregister; + } + + musb->mregs += DAVINCI_BASE_OFFSET; + + /* returns zero if e.g. not clocked */ + revision = musb_readl(tibase, DAVINCI_USB_VERSION_REG); + if (revision == 0) + goto fail; + + setup_timer(&otg_workaround, otg_timer, (unsigned long) musb); + + davinci_musb_source_power(musb, 0, 1); + + /* dm355 EVM swaps D+/D- for signal integrity, and + * is clocked from the main 24 MHz crystal. + */ + if (machine_is_davinci_dm355_evm()) { + u32 phy_ctrl = __raw_readl(USB_PHY_CTRL); + + phy_ctrl &= ~(3 << 9); + phy_ctrl |= USBPHY_DATAPOL; + __raw_writel(phy_ctrl, USB_PHY_CTRL); + } + + /* On dm355, the default-A state machine needs DRVVBUS control. + * If we won't be a host, there's no need to turn it on. + */ + if (cpu_is_davinci_dm355()) { + u32 deepsleep = __raw_readl(DM355_DEEPSLEEP); + + deepsleep &= ~DRVVBUS_FORCE; + __raw_writel(deepsleep, DM355_DEEPSLEEP); + } + + /* reset the controller */ + musb_writel(tibase, DAVINCI_USB_CTRL_REG, 0x1); + + /* start the on-chip PHY and its PLL */ + phy_on(); + + msleep(5); + + /* NOTE: irqs are in mixed mode, not bypass to pure-musb */ + pr_debug("DaVinci OTG revision %08x phy %03x control %02x\n", + revision, __raw_readl(USB_PHY_CTRL), + musb_readb(tibase, DAVINCI_USB_CTRL_REG)); + + musb->isr = davinci_musb_interrupt; + return 0; + +fail: + usb_put_phy(musb->xceiv); +unregister: + usb_nop_xceiv_unregister(); + return ret; +} + +static int davinci_musb_exit(struct musb *musb) +{ + del_timer_sync(&otg_workaround); + + /* force VBUS off */ + if (cpu_is_davinci_dm355()) { + u32 deepsleep = __raw_readl(DM355_DEEPSLEEP); + + deepsleep &= ~DRVVBUS_FORCE; + deepsleep |= DRVVBUS_OVERRIDE; + __raw_writel(deepsleep, DM355_DEEPSLEEP); + } + + davinci_musb_source_power(musb, 0 /*off*/, 1); + + /* delay, to avoid problems with module reload */ + if (musb->xceiv->otg->default_a) { + int maxdelay = 30; + u8 devctl, warn = 0; + + /* if there's no peripheral connected, this can take a + * long time to fall, especially on EVM with huge C133. + */ + do { + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (!(devctl & MUSB_DEVCTL_VBUS)) + break; + if ((devctl & MUSB_DEVCTL_VBUS) != warn) { + warn = devctl & MUSB_DEVCTL_VBUS; + dev_dbg(musb->controller, "VBUS %d\n", + warn >> MUSB_DEVCTL_VBUS_SHIFT); + } + msleep(1000); + maxdelay--; + } while (maxdelay > 0); + + /* in OTG mode, another host might be connected */ + if (devctl & MUSB_DEVCTL_VBUS) + dev_dbg(musb->controller, "VBUS off timeout (devctl %02x)\n", devctl); + } + + phy_off(); + + usb_put_phy(musb->xceiv); + usb_nop_xceiv_unregister(); + + return 0; +} + +static const struct musb_platform_ops davinci_ops = { + .init = davinci_musb_init, + .exit = davinci_musb_exit, + + .enable = davinci_musb_enable, + .disable = davinci_musb_disable, + + .set_mode = davinci_musb_set_mode, + + .set_vbus = davinci_musb_set_vbus, +}; + +static u64 davinci_dmamask = DMA_BIT_MASK(32); + +static int davinci_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct platform_device *musb; + struct davinci_glue *glue; + struct clk *clk; + + int ret = -ENOMEM; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(&pdev->dev, "failed to allocate musb device\n"); + goto err1; + } + + clk = clk_get(&pdev->dev, "usb"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err3; + } + + ret = clk_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err4; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &davinci_dmamask; + musb->dev.coherent_dma_mask = davinci_dmamask; + + glue->dev = &pdev->dev; + glue->musb = musb; + glue->clk = clk; + + pdata->platform_ops = &davinci_ops; + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(&pdev->dev, "failed to add resources\n"); + goto err5; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err5; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err5; + } + + return 0; + +err5: + clk_disable(clk); + +err4: + clk_put(clk); + +err3: + platform_device_put(musb); + +err1: + kfree(glue); + +err0: + return ret; +} + +static int davinci_remove(struct platform_device *pdev) +{ + struct davinci_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + clk_disable(glue->clk); + clk_put(glue->clk); + kfree(glue); + + return 0; +} + +static struct platform_driver davinci_driver = { + .probe = davinci_probe, + .remove = davinci_remove, + .driver = { + .name = "musb-davinci", + }, +}; + +MODULE_DESCRIPTION("DaVinci MUSB Glue Layer"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("GPL v2"); +module_platform_driver(davinci_driver); diff --git a/drivers/usb/musb/davinci.h b/drivers/usb/musb/davinci.h new file mode 100644 index 00000000..371baa0e --- /dev/null +++ b/drivers/usb/musb/davinci.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2005-2006 by Texas Instruments + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + */ + +#ifndef __MUSB_HDRDF_H__ +#define __MUSB_HDRDF_H__ + +/* + * DaVinci-specific definitions + */ + +/* Integrated highspeed/otg PHY */ +#define USBPHY_CTL_PADDR 0x01c40034 +#define USBPHY_DATAPOL BIT(11) /* (dm355) switch D+/D- */ +#define USBPHY_PHYCLKGD BIT(8) +#define USBPHY_SESNDEN BIT(7) /* v(sess_end) comparator */ +#define USBPHY_VBDTCTEN BIT(6) /* v(bus) comparator */ +#define USBPHY_VBUSSENS BIT(5) /* (dm355,ro) is vbus > 0.5V */ +#define USBPHY_PHYPLLON BIT(4) /* override pll suspend */ +#define USBPHY_CLKO1SEL BIT(3) +#define USBPHY_OSCPDWN BIT(2) +#define USBPHY_OTGPDWN BIT(1) +#define USBPHY_PHYPDWN BIT(0) + +#define DM355_DEEPSLEEP_PADDR 0x01c40048 +#define DRVVBUS_FORCE BIT(2) +#define DRVVBUS_OVERRIDE BIT(1) + +/* For now include usb OTG module registers here */ +#define DAVINCI_USB_VERSION_REG 0x00 +#define DAVINCI_USB_CTRL_REG 0x04 +#define DAVINCI_USB_STAT_REG 0x08 +#define DAVINCI_RNDIS_REG 0x10 +#define DAVINCI_AUTOREQ_REG 0x14 +#define DAVINCI_USB_INT_SOURCE_REG 0x20 +#define DAVINCI_USB_INT_SET_REG 0x24 +#define DAVINCI_USB_INT_SRC_CLR_REG 0x28 +#define DAVINCI_USB_INT_MASK_REG 0x2c +#define DAVINCI_USB_INT_MASK_SET_REG 0x30 +#define DAVINCI_USB_INT_MASK_CLR_REG 0x34 +#define DAVINCI_USB_INT_SRC_MASKED_REG 0x38 +#define DAVINCI_USB_EOI_REG 0x3c +#define DAVINCI_USB_EOI_INTVEC 0x40 + +/* BEGIN CPPI-generic (?) */ + +/* CPPI related registers */ +#define DAVINCI_TXCPPI_CTRL_REG 0x80 +#define DAVINCI_TXCPPI_TEAR_REG 0x84 +#define DAVINCI_CPPI_EOI_REG 0x88 +#define DAVINCI_CPPI_INTVEC_REG 0x8c +#define DAVINCI_TXCPPI_MASKED_REG 0x90 +#define DAVINCI_TXCPPI_RAW_REG 0x94 +#define DAVINCI_TXCPPI_INTENAB_REG 0x98 +#define DAVINCI_TXCPPI_INTCLR_REG 0x9c + +#define DAVINCI_RXCPPI_CTRL_REG 0xC0 +#define DAVINCI_RXCPPI_MASKED_REG 0xD0 +#define DAVINCI_RXCPPI_RAW_REG 0xD4 +#define DAVINCI_RXCPPI_INTENAB_REG 0xD8 +#define DAVINCI_RXCPPI_INTCLR_REG 0xDC + +#define DAVINCI_RXCPPI_BUFCNT0_REG 0xE0 +#define DAVINCI_RXCPPI_BUFCNT1_REG 0xE4 +#define DAVINCI_RXCPPI_BUFCNT2_REG 0xE8 +#define DAVINCI_RXCPPI_BUFCNT3_REG 0xEC + +/* CPPI state RAM entries */ +#define DAVINCI_CPPI_STATERAM_BASE_OFFSET 0x100 + +#define DAVINCI_TXCPPI_STATERAM_OFFSET(chnum) \ + (DAVINCI_CPPI_STATERAM_BASE_OFFSET + ((chnum) * 0x40)) +#define DAVINCI_RXCPPI_STATERAM_OFFSET(chnum) \ + (DAVINCI_CPPI_STATERAM_BASE_OFFSET + 0x20 + ((chnum) * 0x40)) + +/* CPPI masks */ +#define DAVINCI_DMA_CTRL_ENABLE 1 +#define DAVINCI_DMA_CTRL_DISABLE 0 + +#define DAVINCI_DMA_ALL_CHANNELS_ENABLE 0xF +#define DAVINCI_DMA_ALL_CHANNELS_DISABLE 0xF + +/* END CPPI-generic (?) */ + +#define DAVINCI_USB_TX_ENDPTS_MASK 0x1f /* ep0 + 4 tx */ +#define DAVINCI_USB_RX_ENDPTS_MASK 0x1e /* 4 rx */ + +#define DAVINCI_USB_USBINT_SHIFT 16 +#define DAVINCI_USB_TXINT_SHIFT 0 +#define DAVINCI_USB_RXINT_SHIFT 8 + +#define DAVINCI_INTR_DRVVBUS 0x0100 + +#define DAVINCI_USB_USBINT_MASK 0x01ff0000 /* 8 Mentor, DRVVBUS */ +#define DAVINCI_USB_TXINT_MASK \ + (DAVINCI_USB_TX_ENDPTS_MASK << DAVINCI_USB_TXINT_SHIFT) +#define DAVINCI_USB_RXINT_MASK \ + (DAVINCI_USB_RX_ENDPTS_MASK << DAVINCI_USB_RXINT_SHIFT) + +#define DAVINCI_BASE_OFFSET 0x400 + +#endif /* __MUSB_HDRDF_H__ */ diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c new file mode 100644 index 00000000..da0caf3f --- /dev/null +++ b/drivers/usb/musb/musb_core.c @@ -0,0 +1,2300 @@ +/* + * MUSB OTG driver core code + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * Inventra (Multipoint) Dual-Role Controller Driver for Linux. + * + * This consists of a Host Controller Driver (HCD) and a peripheral + * controller driver implementing the "Gadget" API; OTG support is + * in the works. These are normal Linux-USB controller drivers which + * use IRQs and have no dedicated thread. + * + * This version of the driver has only been used with products from + * Texas Instruments. Those products integrate the Inventra logic + * with other DMA, IRQ, and bus modules, as well as other logic that + * needs to be reflected in this driver. + * + * + * NOTE: the original Mentor code here was pretty much a collection + * of mechanisms that don't seem to have been fully integrated/working + * for *any* Linux kernel version. This version aims at Linux 2.6.now, + * Key open issues include: + * + * - Lack of host-side transaction scheduling, for all transfer types. + * The hardware doesn't do it; instead, software must. + * + * This is not an issue for OTG devices that don't support external + * hubs, but for more "normal" USB hosts it's a user issue that the + * "multipoint" support doesn't scale in the expected ways. That + * includes DaVinci EVM in a common non-OTG mode. + * + * * Control and bulk use dedicated endpoints, and there's as + * yet no mechanism to either (a) reclaim the hardware when + * peripherals are NAKing, which gets complicated with bulk + * endpoints, or (b) use more than a single bulk endpoint in + * each direction. + * + * RESULT: one device may be perceived as blocking another one. + * + * * Interrupt and isochronous will dynamically allocate endpoint + * hardware, but (a) there's no record keeping for bandwidth; + * (b) in the common case that few endpoints are available, there + * is no mechanism to reuse endpoints to talk to multiple devices. + * + * RESULT: At one extreme, bandwidth can be overcommitted in + * some hardware configurations, no faults will be reported. + * At the other extreme, the bandwidth capabilities which do + * exist tend to be severely undercommitted. You can't yet hook + * up both a keyboard and a mouse to an external USB hub. + */ + +/* + * This gets many kinds of configuration information: + * - Kconfig for everything user-configurable + * - platform_device for addressing, irq, and platform_data + * - platform_data is mostly for board-specific informarion + * (plus recentrly, SOC or family details) + * + * Most of the conditional compilation will (someday) vanish. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" + +#define TA_WAIT_BCON(m) max_t(int, (m)->a_wait_bcon, OTG_TIME_A_WAIT_BCON) + + +#define DRIVER_AUTHOR "Mentor Graphics, Texas Instruments, Nokia" +#define DRIVER_DESC "Inventra Dual-Role USB Controller Driver" + +#define MUSB_VERSION "6.0" + +#define DRIVER_INFO DRIVER_DESC ", v" MUSB_VERSION + +#define MUSB_DRIVER_NAME "musb-hdrc" +const char musb_driver_name[] = MUSB_DRIVER_NAME; + +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" MUSB_DRIVER_NAME); + + +/*-------------------------------------------------------------------------*/ + +static inline struct musb *dev_to_musb(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +/*-------------------------------------------------------------------------*/ + +#ifndef CONFIG_BLACKFIN +static int musb_ulpi_read(struct usb_phy *phy, u32 offset) +{ + void __iomem *addr = phy->io_priv; + int i = 0; + u8 r; + u8 power; + int ret; + + pm_runtime_get_sync(phy->io_dev); + + /* Make sure the transceiver is not in low power mode */ + power = musb_readb(addr, MUSB_POWER); + power &= ~MUSB_POWER_SUSPENDM; + musb_writeb(addr, MUSB_POWER, power); + + /* REVISIT: musbhdrc_ulpi_an.pdf recommends setting the + * ULPICarKitControlDisableUTMI after clearing POWER_SUSPENDM. + */ + + musb_writeb(addr, MUSB_ULPI_REG_ADDR, (u8)offset); + musb_writeb(addr, MUSB_ULPI_REG_CONTROL, + MUSB_ULPI_REG_REQ | MUSB_ULPI_RDN_WR); + + while (!(musb_readb(addr, MUSB_ULPI_REG_CONTROL) + & MUSB_ULPI_REG_CMPLT)) { + i++; + if (i == 10000) { + ret = -ETIMEDOUT; + goto out; + } + + } + r = musb_readb(addr, MUSB_ULPI_REG_CONTROL); + r &= ~MUSB_ULPI_REG_CMPLT; + musb_writeb(addr, MUSB_ULPI_REG_CONTROL, r); + + ret = musb_readb(addr, MUSB_ULPI_REG_DATA); + +out: + pm_runtime_put(phy->io_dev); + + return ret; +} + +static int musb_ulpi_write(struct usb_phy *phy, u32 offset, u32 data) +{ + void __iomem *addr = phy->io_priv; + int i = 0; + u8 r = 0; + u8 power; + int ret = 0; + + pm_runtime_get_sync(phy->io_dev); + + /* Make sure the transceiver is not in low power mode */ + power = musb_readb(addr, MUSB_POWER); + power &= ~MUSB_POWER_SUSPENDM; + musb_writeb(addr, MUSB_POWER, power); + + musb_writeb(addr, MUSB_ULPI_REG_ADDR, (u8)offset); + musb_writeb(addr, MUSB_ULPI_REG_DATA, (u8)data); + musb_writeb(addr, MUSB_ULPI_REG_CONTROL, MUSB_ULPI_REG_REQ); + + while (!(musb_readb(addr, MUSB_ULPI_REG_CONTROL) + & MUSB_ULPI_REG_CMPLT)) { + i++; + if (i == 10000) { + ret = -ETIMEDOUT; + goto out; + } + } + + r = musb_readb(addr, MUSB_ULPI_REG_CONTROL); + r &= ~MUSB_ULPI_REG_CMPLT; + musb_writeb(addr, MUSB_ULPI_REG_CONTROL, r); + +out: + pm_runtime_put(phy->io_dev); + + return ret; +} +#else +#define musb_ulpi_read NULL +#define musb_ulpi_write NULL +#endif + +static struct usb_phy_io_ops musb_ulpi_access = { + .read = musb_ulpi_read, + .write = musb_ulpi_write, +}; + +/*-------------------------------------------------------------------------*/ + +#if !defined(CONFIG_USB_MUSB_TUSB6010) && !defined(CONFIG_USB_MUSB_BLACKFIN) + +/* + * Load an endpoint's FIFO + */ +void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) +{ + struct musb *musb = hw_ep->musb; + void __iomem *fifo = hw_ep->fifo; + + if (unlikely(len == 0)) + return; + + prefetch((u8 *)src); + + dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n", + 'T', hw_ep->epnum, fifo, len, src); + + /* we can't assume unaligned reads work */ + if (likely((0x01 & (unsigned long) src) == 0)) { + u16 index = 0; + + /* best case is 32bit-aligned source address */ + if ((0x02 & (unsigned long) src) == 0) { + if (len >= 4) { + iowrite32_rep(fifo, src + index, len >> 2); + index += len & ~0x03; + } + if (len & 0x02) { + musb_writew(fifo, 0, *(u16 *)&src[index]); + index += 2; + } + } else { + if (len >= 2) { + iowrite16_rep(fifo, src + index, len >> 1); + index += len & ~0x01; + } + } + if (len & 0x01) + musb_writeb(fifo, 0, src[index]); + } else { + /* byte aligned */ + iowrite8_rep(fifo, src, len); + } +} + +#if !defined(CONFIG_USB_MUSB_AM35X) +/* + * Unload an endpoint's FIFO + */ +void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) +{ + struct musb *musb = hw_ep->musb; + void __iomem *fifo = hw_ep->fifo; + + if (unlikely(len == 0)) + return; + + dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n", + 'R', hw_ep->epnum, fifo, len, dst); + + /* we can't assume unaligned writes work */ + if (likely((0x01 & (unsigned long) dst) == 0)) { + u16 index = 0; + + /* best case is 32bit-aligned destination address */ + if ((0x02 & (unsigned long) dst) == 0) { + if (len >= 4) { + ioread32_rep(fifo, dst, len >> 2); + index = len & ~0x03; + } + if (len & 0x02) { + *(u16 *)&dst[index] = musb_readw(fifo, 0); + index += 2; + } + } else { + if (len >= 2) { + ioread16_rep(fifo, dst, len >> 1); + index = len & ~0x01; + } + } + if (len & 0x01) + dst[index] = musb_readb(fifo, 0); + } else { + /* byte aligned */ + ioread8_rep(fifo, dst, len); + } +} +#endif + +#endif /* normal PIO */ + + +/*-------------------------------------------------------------------------*/ + +/* for high speed test mode; see USB 2.0 spec 7.1.20 */ +static const u8 musb_test_packet[53] = { + /* implicit SYNC then DATA0 to start */ + + /* JKJKJKJK x9 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* JJKKJJKK x8 */ + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + /* JJJJKKKK x8 */ + 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, + /* JJJJJJJKKKKKKK x8 */ + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* JJJJJJJK x8 */ + 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, + /* JKKKKKKK x10, JK */ + 0xfc, 0x7e, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0x7e + + /* implicit CRC16 then EOP to end */ +}; + +void musb_load_testpacket(struct musb *musb) +{ + void __iomem *regs = musb->endpoints[0].regs; + + musb_ep_select(musb->mregs, 0); + musb_write_fifo(musb->control_ep, + sizeof(musb_test_packet), musb_test_packet); + musb_writew(regs, MUSB_CSR0, MUSB_CSR0_TXPKTRDY); +} + +/*-------------------------------------------------------------------------*/ + +/* + * Handles OTG hnp timeouts, such as b_ase0_brst + */ +static void musb_otg_timer_func(unsigned long data) +{ + struct musb *musb = (struct musb *)data; + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->state) { + case OTG_STATE_B_WAIT_ACON: + dev_dbg(musb->controller, "HNP: b_wait_acon timeout; back to b_peripheral\n"); + musb_g_disconnect(musb); + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + musb->is_active = 0; + break; + case OTG_STATE_A_SUSPEND: + case OTG_STATE_A_WAIT_BCON: + dev_dbg(musb->controller, "HNP: %s timeout\n", + usb_otg_state_string(musb->xceiv->state)); + musb_platform_set_vbus(musb, 0); + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; + break; + default: + dev_dbg(musb->controller, "HNP: Unhandled mode %s\n", + usb_otg_state_string(musb->xceiv->state)); + } + musb->ignore_disconnect = 0; + spin_unlock_irqrestore(&musb->lock, flags); +} + +/* + * Stops the HNP transition. Caller must take care of locking. + */ +void musb_hnp_stop(struct musb *musb) +{ + struct usb_hcd *hcd = musb_to_hcd(musb); + void __iomem *mbase = musb->mregs; + u8 reg; + + dev_dbg(musb->controller, "HNP: stop from %s\n", + usb_otg_state_string(musb->xceiv->state)); + + switch (musb->xceiv->state) { + case OTG_STATE_A_PERIPHERAL: + musb_g_disconnect(musb); + dev_dbg(musb->controller, "HNP: back to %s\n", + usb_otg_state_string(musb->xceiv->state)); + break; + case OTG_STATE_B_HOST: + dev_dbg(musb->controller, "HNP: Disabling HR\n"); + hcd->self.is_b_host = 0; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + MUSB_DEV_MODE(musb); + reg = musb_readb(mbase, MUSB_POWER); + reg |= MUSB_POWER_SUSPENDM; + musb_writeb(mbase, MUSB_POWER, reg); + /* REVISIT: Start SESSION_REQUEST here? */ + break; + default: + dev_dbg(musb->controller, "HNP: Stopping in unknown state %s\n", + usb_otg_state_string(musb->xceiv->state)); + } + + /* + * When returning to A state after HNP, avoid hub_port_rebounce(), + * which cause occasional OPT A "Did not receive reset after connect" + * errors. + */ + musb->port1_status &= ~(USB_PORT_STAT_C_CONNECTION << 16); +} + +/* + * Interrupt Service Routine to record USB "global" interrupts. + * Since these do not happen often and signify things of + * paramount importance, it seems OK to check them individually; + * the order of the tests is specified in the manual + * + * @param musb instance pointer + * @param int_usb register contents + * @param devctl + * @param power + */ + +static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, + u8 devctl) +{ + irqreturn_t handled = IRQ_NONE; + + dev_dbg(musb->controller, "<== DevCtl=%02x, int_usb=0x%x\n", devctl, + int_usb); + + /* in host mode, the peripheral may issue remote wakeup. + * in peripheral mode, the host may resume the link. + * spurious RESUME irqs happen too, paired with SUSPEND. + */ + if (int_usb & MUSB_INTR_RESUME) { + handled = IRQ_HANDLED; + dev_dbg(musb->controller, "RESUME (%s)\n", usb_otg_state_string(musb->xceiv->state)); + + if (devctl & MUSB_DEVCTL_HM) { + void __iomem *mbase = musb->mregs; + u8 power; + + switch (musb->xceiv->state) { + case OTG_STATE_A_SUSPEND: + /* remote wakeup? later, GetPortStatus + * will stop RESUME signaling + */ + + power = musb_readb(musb->mregs, MUSB_POWER); + if (power & MUSB_POWER_SUSPENDM) { + /* spurious */ + musb->int_usb &= ~MUSB_INTR_SUSPEND; + dev_dbg(musb->controller, "Spurious SUSPENDM\n"); + break; + } + + power &= ~MUSB_POWER_SUSPENDM; + musb_writeb(mbase, MUSB_POWER, + power | MUSB_POWER_RESUME); + + musb->port1_status |= + (USB_PORT_STAT_C_SUSPEND << 16) + | MUSB_PORT_STAT_RESUME; + musb->rh_timer = jiffies + + msecs_to_jiffies(20); + + musb->xceiv->state = OTG_STATE_A_HOST; + musb->is_active = 1; + usb_hcd_resume_root_hub(musb_to_hcd(musb)); + break; + case OTG_STATE_B_WAIT_ACON: + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + musb->is_active = 1; + MUSB_DEV_MODE(musb); + break; + default: + WARNING("bogus %s RESUME (%s)\n", + "host", + usb_otg_state_string(musb->xceiv->state)); + } + } else { + switch (musb->xceiv->state) { + case OTG_STATE_A_SUSPEND: + /* possibly DISCONNECT is upcoming */ + musb->xceiv->state = OTG_STATE_A_HOST; + usb_hcd_resume_root_hub(musb_to_hcd(musb)); + break; + case OTG_STATE_B_WAIT_ACON: + case OTG_STATE_B_PERIPHERAL: + /* disconnect while suspended? we may + * not get a disconnect irq... + */ + if ((devctl & MUSB_DEVCTL_VBUS) + != (3 << MUSB_DEVCTL_VBUS_SHIFT) + ) { + musb->int_usb |= MUSB_INTR_DISCONNECT; + musb->int_usb &= ~MUSB_INTR_SUSPEND; + break; + } + musb_g_resume(musb); + break; + case OTG_STATE_B_IDLE: + musb->int_usb &= ~MUSB_INTR_SUSPEND; + break; + default: + WARNING("bogus %s RESUME (%s)\n", + "peripheral", + usb_otg_state_string(musb->xceiv->state)); + } + } + } + + /* see manual for the order of the tests */ + if (int_usb & MUSB_INTR_SESSREQ) { + void __iomem *mbase = musb->mregs; + + if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS + && (devctl & MUSB_DEVCTL_BDEVICE)) { + dev_dbg(musb->controller, "SessReq while on B state\n"); + return IRQ_HANDLED; + } + + dev_dbg(musb->controller, "SESSION_REQUEST (%s)\n", + usb_otg_state_string(musb->xceiv->state)); + + /* IRQ arrives from ID pin sense or (later, if VBUS power + * is removed) SRP. responses are time critical: + * - turn on VBUS (with silicon-specific mechanism) + * - go through A_WAIT_VRISE + * - ... to A_WAIT_BCON. + * a_wait_vrise_tmout triggers VBUS_ERROR transitions + */ + musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION); + musb->ep0_stage = MUSB_EP0_START; + musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + musb_platform_set_vbus(musb, 1); + + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_VBUSERROR) { + int ignore = 0; + + /* During connection as an A-Device, we may see a short + * current spikes causing voltage drop, because of cable + * and peripheral capacitance combined with vbus draw. + * (So: less common with truly self-powered devices, where + * vbus doesn't act like a power supply.) + * + * Such spikes are short; usually less than ~500 usec, max + * of ~2 msec. That is, they're not sustained overcurrent + * errors, though they're reported using VBUSERROR irqs. + * + * Workarounds: (a) hardware: use self powered devices. + * (b) software: ignore non-repeated VBUS errors. + * + * REVISIT: do delays from lots of DEBUG_KERNEL checks + * make trouble here, keeping VBUS < 4.4V ? + */ + switch (musb->xceiv->state) { + case OTG_STATE_A_HOST: + /* recovery is dicey once we've gotten past the + * initial stages of enumeration, but if VBUS + * stayed ok at the other end of the link, and + * another reset is due (at least for high speed, + * to redo the chirp etc), it might work OK... + */ + case OTG_STATE_A_WAIT_BCON: + case OTG_STATE_A_WAIT_VRISE: + if (musb->vbuserr_retry) { + void __iomem *mbase = musb->mregs; + + musb->vbuserr_retry--; + ignore = 1; + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(mbase, MUSB_DEVCTL, devctl); + } else { + musb->port1_status |= + USB_PORT_STAT_OVERCURRENT + | (USB_PORT_STAT_C_OVERCURRENT << 16); + } + break; + default: + break; + } + + dev_printk(ignore ? KERN_DEBUG : KERN_ERR, musb->controller, + "VBUS_ERROR in %s (%02x, %s), retry #%d, port1 %08x\n", + usb_otg_state_string(musb->xceiv->state), + devctl, + ({ char *s; + switch (devctl & MUSB_DEVCTL_VBUS) { + case 0 << MUSB_DEVCTL_VBUS_SHIFT: + s = "vbuserr_retry, + musb->port1_status); + + /* go through A_WAIT_VFALL then start a new session */ + if (!ignore) + musb_platform_set_vbus(musb, 0); + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_SUSPEND) { + dev_dbg(musb->controller, "SUSPEND (%s) devctl %02x\n", + usb_otg_state_string(musb->xceiv->state), devctl); + handled = IRQ_HANDLED; + + switch (musb->xceiv->state) { + case OTG_STATE_A_PERIPHERAL: + /* We also come here if the cable is removed, since + * this silicon doesn't report ID-no-longer-grounded. + * + * We depend on T(a_wait_bcon) to shut us down, and + * hope users don't do anything dicey during this + * undesired detour through A_WAIT_BCON. + */ + musb_hnp_stop(musb); + usb_hcd_resume_root_hub(musb_to_hcd(musb)); + musb_root_disconnect(musb); + musb_platform_try_idle(musb, jiffies + + msecs_to_jiffies(musb->a_wait_bcon + ? : OTG_TIME_A_WAIT_BCON)); + + break; + case OTG_STATE_B_IDLE: + if (!musb->is_active) + break; + case OTG_STATE_B_PERIPHERAL: + musb_g_suspend(musb); + musb->is_active = musb->g.b_hnp_enable; + if (musb->is_active) { + musb->xceiv->state = OTG_STATE_B_WAIT_ACON; + dev_dbg(musb->controller, "HNP: Setting timer for b_ase0_brst\n"); + mod_timer(&musb->otg_timer, jiffies + + msecs_to_jiffies( + OTG_TIME_B_ASE0_BRST)); + } + break; + case OTG_STATE_A_WAIT_BCON: + if (musb->a_wait_bcon != 0) + musb_platform_try_idle(musb, jiffies + + msecs_to_jiffies(musb->a_wait_bcon)); + break; + case OTG_STATE_A_HOST: + musb->xceiv->state = OTG_STATE_A_SUSPEND; + musb->is_active = musb_to_hcd(musb)->self.b_hnp_enable; + break; + case OTG_STATE_B_HOST: + /* Transition to B_PERIPHERAL, see 6.8.2.6 p 44 */ + dev_dbg(musb->controller, "REVISIT: SUSPEND as B_HOST\n"); + break; + default: + /* "should not happen" */ + musb->is_active = 0; + break; + } + } + + if (int_usb & MUSB_INTR_CONNECT) { + struct usb_hcd *hcd = musb_to_hcd(musb); + + handled = IRQ_HANDLED; + musb->is_active = 1; + + musb->ep0_stage = MUSB_EP0_START; + + /* flush endpoints when transitioning from Device Mode */ + if (is_peripheral_active(musb)) { + /* REVISIT HNP; just force disconnect */ + } + musb->intrtxe = musb->epmask; + musb_writew(musb->mregs, MUSB_INTRTXE, musb->intrtxe); + musb->intrrxe = musb->epmask & 0xfffe; + musb_writew(musb->mregs, MUSB_INTRRXE, musb->intrrxe); + musb_writeb(musb->mregs, MUSB_INTRUSBE, 0xf7); + musb->port1_status &= ~(USB_PORT_STAT_LOW_SPEED + |USB_PORT_STAT_HIGH_SPEED + |USB_PORT_STAT_ENABLE + ); + musb->port1_status |= USB_PORT_STAT_CONNECTION + |(USB_PORT_STAT_C_CONNECTION << 16); + + /* high vs full speed is just a guess until after reset */ + if (devctl & MUSB_DEVCTL_LSDEV) + musb->port1_status |= USB_PORT_STAT_LOW_SPEED; + + /* indicate new connection to OTG machine */ + switch (musb->xceiv->state) { + case OTG_STATE_B_PERIPHERAL: + if (int_usb & MUSB_INTR_SUSPEND) { + dev_dbg(musb->controller, "HNP: SUSPEND+CONNECT, now b_host\n"); + int_usb &= ~MUSB_INTR_SUSPEND; + goto b_host; + } else + dev_dbg(musb->controller, "CONNECT as b_peripheral???\n"); + break; + case OTG_STATE_B_WAIT_ACON: + dev_dbg(musb->controller, "HNP: CONNECT, now b_host\n"); +b_host: + musb->xceiv->state = OTG_STATE_B_HOST; + hcd->self.is_b_host = 1; + musb->ignore_disconnect = 0; + del_timer(&musb->otg_timer); + break; + default: + if ((devctl & MUSB_DEVCTL_VBUS) + == (3 << MUSB_DEVCTL_VBUS_SHIFT)) { + musb->xceiv->state = OTG_STATE_A_HOST; + hcd->self.is_b_host = 0; + } + break; + } + + /* poke the root hub */ + MUSB_HST_MODE(musb); + if (hcd->status_urb) + usb_hcd_poll_rh_status(hcd); + else + usb_hcd_resume_root_hub(hcd); + + dev_dbg(musb->controller, "CONNECT (%s) devctl %02x\n", + usb_otg_state_string(musb->xceiv->state), devctl); + } + + if ((int_usb & MUSB_INTR_DISCONNECT) && !musb->ignore_disconnect) { + dev_dbg(musb->controller, "DISCONNECT (%s) as %s, devctl %02x\n", + usb_otg_state_string(musb->xceiv->state), + MUSB_MODE(musb), devctl); + handled = IRQ_HANDLED; + + switch (musb->xceiv->state) { + case OTG_STATE_A_HOST: + case OTG_STATE_A_SUSPEND: + usb_hcd_resume_root_hub(musb_to_hcd(musb)); + musb_root_disconnect(musb); + if (musb->a_wait_bcon != 0) + musb_platform_try_idle(musb, jiffies + + msecs_to_jiffies(musb->a_wait_bcon)); + break; + case OTG_STATE_B_HOST: + /* REVISIT this behaves for "real disconnect" + * cases; make sure the other transitions from + * from B_HOST act right too. The B_HOST code + * in hnp_stop() is currently not used... + */ + musb_root_disconnect(musb); + musb_to_hcd(musb)->self.is_b_host = 0; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + MUSB_DEV_MODE(musb); + musb_g_disconnect(musb); + break; + case OTG_STATE_A_PERIPHERAL: + musb_hnp_stop(musb); + musb_root_disconnect(musb); + /* FALLTHROUGH */ + case OTG_STATE_B_WAIT_ACON: + /* FALLTHROUGH */ + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_IDLE: + musb_g_disconnect(musb); + break; + default: + WARNING("unhandled DISCONNECT transition (%s)\n", + usb_otg_state_string(musb->xceiv->state)); + break; + } + } + + /* mentor saves a bit: bus reset and babble share the same irq. + * only host sees babble; only peripheral sees bus reset. + */ + if (int_usb & MUSB_INTR_RESET) { + handled = IRQ_HANDLED; + if ((devctl & MUSB_DEVCTL_HM) != 0) { + /* + * Looks like non-HS BABBLE can be ignored, but + * HS BABBLE is an error condition. For HS the solution + * is to avoid babble in the first place and fix what + * caused BABBLE. When HS BABBLE happens we can only + * stop the session. + */ + if (devctl & (MUSB_DEVCTL_FSDEV | MUSB_DEVCTL_LSDEV)) + dev_dbg(musb->controller, "BABBLE devctl: %02x\n", devctl); + else { + ERR("Stopping host session -- babble\n"); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + } + } else { + dev_dbg(musb->controller, "BUS RESET as %s\n", + usb_otg_state_string(musb->xceiv->state)); + switch (musb->xceiv->state) { + case OTG_STATE_A_SUSPEND: + /* We need to ignore disconnect on suspend + * otherwise tusb 2.0 won't reconnect after a + * power cycle, which breaks otg compliance. + */ + musb->ignore_disconnect = 1; + musb_g_reset(musb); + /* FALLTHROUGH */ + case OTG_STATE_A_WAIT_BCON: /* OPT TD.4.7-900ms */ + /* never use invalid T(a_wait_bcon) */ + dev_dbg(musb->controller, "HNP: in %s, %d msec timeout\n", + usb_otg_state_string(musb->xceiv->state), + TA_WAIT_BCON(musb)); + mod_timer(&musb->otg_timer, jiffies + + msecs_to_jiffies(TA_WAIT_BCON(musb))); + break; + case OTG_STATE_A_PERIPHERAL: + musb->ignore_disconnect = 0; + del_timer(&musb->otg_timer); + musb_g_reset(musb); + break; + case OTG_STATE_B_WAIT_ACON: + dev_dbg(musb->controller, "HNP: RESET (%s), to b_peripheral\n", + usb_otg_state_string(musb->xceiv->state)); + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + musb_g_reset(musb); + break; + case OTG_STATE_B_IDLE: + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + /* FALLTHROUGH */ + case OTG_STATE_B_PERIPHERAL: + musb_g_reset(musb); + break; + default: + dev_dbg(musb->controller, "Unhandled BUS RESET as %s\n", + usb_otg_state_string(musb->xceiv->state)); + } + } + } + +#if 0 +/* REVISIT ... this would be for multiplexing periodic endpoints, or + * supporting transfer phasing to prevent exceeding ISO bandwidth + * limits of a given frame or microframe. + * + * It's not needed for peripheral side, which dedicates endpoints; + * though it _might_ use SOF irqs for other purposes. + * + * And it's not currently needed for host side, which also dedicates + * endpoints, relies on TX/RX interval registers, and isn't claimed + * to support ISO transfers yet. + */ + if (int_usb & MUSB_INTR_SOF) { + void __iomem *mbase = musb->mregs; + struct musb_hw_ep *ep; + u8 epnum; + u16 frame; + + dev_dbg(musb->controller, "START_OF_FRAME\n"); + handled = IRQ_HANDLED; + + /* start any periodic Tx transfers waiting for current frame */ + frame = musb_readw(mbase, MUSB_FRAME); + ep = musb->endpoints; + for (epnum = 1; (epnum < musb->nr_endpoints) + && (musb->epmask >= (1 << epnum)); + epnum++, ep++) { + /* + * FIXME handle framecounter wraps (12 bits) + * eliminate duplicated StartUrb logic + */ + if (ep->dwWaitFrame >= frame) { + ep->dwWaitFrame = 0; + pr_debug("SOF --> periodic TX%s on %d\n", + ep->tx_channel ? " DMA" : "", + epnum); + if (!ep->tx_channel) + musb_h_tx_start(musb, epnum); + else + cppi_hostdma_start(musb, epnum); + } + } /* end of for loop */ + } +#endif + + schedule_work(&musb->irq_work); + + return handled; +} + +/*-------------------------------------------------------------------------*/ + +/* +* Program the HDRC to start (enable interrupts, dma, etc.). +*/ +void musb_start(struct musb *musb) +{ + void __iomem *regs = musb->mregs; + u8 devctl = musb_readb(regs, MUSB_DEVCTL); + + dev_dbg(musb->controller, "<== devctl %02x\n", devctl); + + /* Set INT enable registers, enable interrupts */ + musb->intrtxe = musb->epmask; + musb_writew(regs, MUSB_INTRTXE, musb->intrtxe); + musb->intrrxe = musb->epmask & 0xfffe; + musb_writew(regs, MUSB_INTRRXE, musb->intrrxe); + musb_writeb(regs, MUSB_INTRUSBE, 0xf7); + + musb_writeb(regs, MUSB_TESTMODE, 0); + + /* put into basic highspeed mode and start session */ + musb_writeb(regs, MUSB_POWER, MUSB_POWER_ISOUPDATE + | MUSB_POWER_HSENAB + /* ENSUSPEND wedges tusb */ + /* | MUSB_POWER_ENSUSPEND */ + ); + + musb->is_active = 0; + devctl = musb_readb(regs, MUSB_DEVCTL); + devctl &= ~MUSB_DEVCTL_SESSION; + + /* session started after: + * (a) ID-grounded irq, host mode; + * (b) vbus present/connect IRQ, peripheral mode; + * (c) peripheral initiates, using SRP + */ + if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) + musb->is_active = 1; + else + devctl |= MUSB_DEVCTL_SESSION; + + musb_platform_enable(musb); + musb_writeb(regs, MUSB_DEVCTL, devctl); +} + + +static void musb_generic_disable(struct musb *musb) +{ + void __iomem *mbase = musb->mregs; + u16 temp; + + /* disable interrupts */ + musb_writeb(mbase, MUSB_INTRUSBE, 0); + musb->intrtxe = 0; + musb_writew(mbase, MUSB_INTRTXE, 0); + musb->intrrxe = 0; + musb_writew(mbase, MUSB_INTRRXE, 0); + + /* off */ + musb_writeb(mbase, MUSB_DEVCTL, 0); + + /* flush pending interrupts */ + temp = musb_readb(mbase, MUSB_INTRUSB); + temp = musb_readw(mbase, MUSB_INTRTX); + temp = musb_readw(mbase, MUSB_INTRRX); + +} + +/* + * Make the HDRC stop (disable interrupts, etc.); + * reversible by musb_start + * called on gadget driver unregister + * with controller locked, irqs blocked + * acts as a NOP unless some role activated the hardware + */ +void musb_stop(struct musb *musb) +{ + /* stop IRQs, timers, ... */ + musb_platform_disable(musb); + musb_generic_disable(musb); + dev_dbg(musb->controller, "HDRC disabled\n"); + + /* FIXME + * - mark host and/or peripheral drivers unusable/inactive + * - disable DMA (and enable it in HdrcStart) + * - make sure we can musb_start() after musb_stop(); with + * OTG mode, gadget driver module rmmod/modprobe cycles that + * - ... + */ + musb_platform_try_idle(musb, 0); +} + +static void musb_shutdown(struct platform_device *pdev) +{ + struct musb *musb = dev_to_musb(&pdev->dev); + unsigned long flags; + + pm_runtime_get_sync(musb->controller); + + musb_gadget_cleanup(musb); + + spin_lock_irqsave(&musb->lock, flags); + musb_platform_disable(musb); + musb_generic_disable(musb); + spin_unlock_irqrestore(&musb->lock, flags); + + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + musb_platform_exit(musb); + + pm_runtime_put(musb->controller); + /* FIXME power down */ +} + + +/*-------------------------------------------------------------------------*/ + +/* + * The silicon either has hard-wired endpoint configurations, or else + * "dynamic fifo" sizing. The driver has support for both, though at this + * writing only the dynamic sizing is very well tested. Since we switched + * away from compile-time hardware parameters, we can no longer rely on + * dead code elimination to leave only the relevant one in the object file. + * + * We don't currently use dynamic fifo setup capability to do anything + * more than selecting one of a bunch of predefined configurations. + */ +#if defined(CONFIG_USB_MUSB_TUSB6010) \ + || defined(CONFIG_USB_MUSB_TUSB6010_MODULE) \ + || defined(CONFIG_USB_MUSB_OMAP2PLUS) \ + || defined(CONFIG_USB_MUSB_OMAP2PLUS_MODULE) \ + || defined(CONFIG_USB_MUSB_AM35X) \ + || defined(CONFIG_USB_MUSB_AM35X_MODULE) \ + || defined(CONFIG_USB_MUSB_DSPS) \ + || defined(CONFIG_USB_MUSB_DSPS_MODULE) +static ushort fifo_mode = 4; +#elif defined(CONFIG_USB_MUSB_UX500) \ + || defined(CONFIG_USB_MUSB_UX500_MODULE) +static ushort fifo_mode = 5; +#else +static ushort fifo_mode = 2; +#endif + +/* "modprobe ... fifo_mode=1" etc */ +module_param(fifo_mode, ushort, 0); +MODULE_PARM_DESC(fifo_mode, "initial endpoint configuration"); + +/* + * tables defining fifo_mode values. define more if you like. + * for host side, make sure both halves of ep1 are set up. + */ + +/* mode 0 - fits in 2KB */ +static struct musb_fifo_cfg mode_0_cfg[] = { +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 2, .style = FIFO_RXTX, .maxpacket = 512, }, +{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, }, +}; + +/* mode 1 - fits in 4KB */ +static struct musb_fifo_cfg mode_1_cfg[] = { +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 2, .style = FIFO_RXTX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, }, +}; + +/* mode 2 - fits in 4KB */ +static struct musb_fifo_cfg mode_2_cfg[] = { +{ .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_RXTX, .maxpacket = 256, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, }, +}; + +/* mode 3 - fits in 4KB */ +static struct musb_fifo_cfg mode_3_cfg[] = { +{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, .mode = BUF_DOUBLE, }, +{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, }, +}; + +/* mode 4 - fits in 16KB */ +static struct musb_fifo_cfg mode_4_cfg[] = { +{ .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, }, +{ .hw_ep_num = 8, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 8, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 9, .style = FIFO_TX, .maxpacket = 512, }, +{ .hw_ep_num = 9, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 10, .style = FIFO_TX, .maxpacket = 256, }, +{ .hw_ep_num = 10, .style = FIFO_RX, .maxpacket = 64, }, +{ .hw_ep_num = 11, .style = FIFO_TX, .maxpacket = 256, }, +{ .hw_ep_num = 11, .style = FIFO_RX, .maxpacket = 64, }, +{ .hw_ep_num = 12, .style = FIFO_TX, .maxpacket = 256, }, +{ .hw_ep_num = 12, .style = FIFO_RX, .maxpacket = 64, }, +{ .hw_ep_num = 13, .style = FIFO_RXTX, .maxpacket = 4096, }, +{ .hw_ep_num = 14, .style = FIFO_RXTX, .maxpacket = 1024, }, +{ .hw_ep_num = 15, .style = FIFO_RXTX, .maxpacket = 1024, }, +}; + +/* mode 5 - fits in 8KB */ +static struct musb_fifo_cfg mode_5_cfg[] = { +{ .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 = 32, }, +{ .hw_ep_num = 6, .style = FIFO_RX, .maxpacket = 32, }, +{ .hw_ep_num = 7, .style = FIFO_TX, .maxpacket = 32, }, +{ .hw_ep_num = 7, .style = FIFO_RX, .maxpacket = 32, }, +{ .hw_ep_num = 8, .style = FIFO_TX, .maxpacket = 32, }, +{ .hw_ep_num = 8, .style = FIFO_RX, .maxpacket = 32, }, +{ .hw_ep_num = 9, .style = FIFO_TX, .maxpacket = 32, }, +{ .hw_ep_num = 9, .style = FIFO_RX, .maxpacket = 32, }, +{ .hw_ep_num = 10, .style = FIFO_TX, .maxpacket = 32, }, +{ .hw_ep_num = 10, .style = FIFO_RX, .maxpacket = 32, }, +{ .hw_ep_num = 11, .style = FIFO_TX, .maxpacket = 32, }, +{ .hw_ep_num = 11, .style = FIFO_RX, .maxpacket = 32, }, +{ .hw_ep_num = 12, .style = FIFO_TX, .maxpacket = 32, }, +{ .hw_ep_num = 12, .style = FIFO_RX, .maxpacket = 32, }, +{ .hw_ep_num = 13, .style = FIFO_RXTX, .maxpacket = 512, }, +{ .hw_ep_num = 14, .style = FIFO_RXTX, .maxpacket = 1024, }, +{ .hw_ep_num = 15, .style = FIFO_RXTX, .maxpacket = 1024, }, +}; + +/* + * configure a fifo; for non-shared endpoints, this may be called + * once for a tx fifo and once for an rx fifo. + * + * returns negative errno or offset for next fifo. + */ +static int +fifo_setup(struct musb *musb, struct musb_hw_ep *hw_ep, + const struct musb_fifo_cfg *cfg, u16 offset) +{ + void __iomem *mbase = musb->mregs; + int size = 0; + u16 maxpacket = cfg->maxpacket; + u16 c_off = offset >> 3; + u8 c_size; + + /* expect hw_ep has already been zero-initialized */ + + size = ffs(max(maxpacket, (u16) 8)) - 1; + maxpacket = 1 << size; + + c_size = size - 3; + if (cfg->mode == BUF_DOUBLE) { + if ((offset + (maxpacket << 1)) > + (1 << (musb->config->ram_bits + 2))) + return -EMSGSIZE; + c_size |= MUSB_FIFOSZ_DPB; + } else { + if ((offset + maxpacket) > (1 << (musb->config->ram_bits + 2))) + return -EMSGSIZE; + } + + /* configure the FIFO */ + musb_writeb(mbase, MUSB_INDEX, hw_ep->epnum); + + /* EP0 reserved endpoint for control, bidirectional; + * EP1 reserved for bulk, two unidirection halves. + */ + if (hw_ep->epnum == 1) + musb->bulk_ep = hw_ep; + /* REVISIT error check: be sure ep0 can both rx and tx ... */ + switch (cfg->style) { + case FIFO_TX: + musb_write_txfifosz(mbase, c_size); + musb_write_txfifoadd(mbase, c_off); + hw_ep->tx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_tx = maxpacket; + break; + case FIFO_RX: + musb_write_rxfifosz(mbase, c_size); + musb_write_rxfifoadd(mbase, c_off); + hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_rx = maxpacket; + break; + case FIFO_RXTX: + musb_write_txfifosz(mbase, c_size); + musb_write_txfifoadd(mbase, c_off); + hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_rx = maxpacket; + + musb_write_rxfifosz(mbase, c_size); + musb_write_rxfifoadd(mbase, c_off); + hw_ep->tx_double_buffered = hw_ep->rx_double_buffered; + hw_ep->max_packet_sz_tx = maxpacket; + + hw_ep->is_shared_fifo = true; + break; + } + + /* NOTE rx and tx endpoint irqs aren't managed separately, + * which happens to be ok + */ + musb->epmask |= (1 << hw_ep->epnum); + + return offset + (maxpacket << ((c_size & MUSB_FIFOSZ_DPB) ? 1 : 0)); +} + +static struct musb_fifo_cfg ep0_cfg = { + .style = FIFO_RXTX, .maxpacket = 64, +}; + +static int ep_config_from_table(struct musb *musb) +{ + const struct musb_fifo_cfg *cfg; + unsigned i, n; + int offset; + struct musb_hw_ep *hw_ep = musb->endpoints; + + if (musb->config->fifo_cfg) { + cfg = musb->config->fifo_cfg; + n = musb->config->fifo_cfg_size; + goto done; + } + + switch (fifo_mode) { + default: + fifo_mode = 0; + /* FALLTHROUGH */ + case 0: + cfg = mode_0_cfg; + n = ARRAY_SIZE(mode_0_cfg); + break; + case 1: + cfg = mode_1_cfg; + n = ARRAY_SIZE(mode_1_cfg); + break; + case 2: + cfg = mode_2_cfg; + n = ARRAY_SIZE(mode_2_cfg); + break; + case 3: + cfg = mode_3_cfg; + n = ARRAY_SIZE(mode_3_cfg); + break; + case 4: + cfg = mode_4_cfg; + n = ARRAY_SIZE(mode_4_cfg); + break; + case 5: + cfg = mode_5_cfg; + n = ARRAY_SIZE(mode_5_cfg); + break; + } + + printk(KERN_DEBUG "%s: setup fifo_mode %d\n", + musb_driver_name, fifo_mode); + + +done: + offset = fifo_setup(musb, hw_ep, &ep0_cfg, 0); + /* assert(offset > 0) */ + + /* NOTE: for RTL versions >= 1.400 EPINFO and RAMINFO would + * be better than static musb->config->num_eps and DYN_FIFO_SIZE... + */ + + for (i = 0; i < n; i++) { + u8 epn = cfg->hw_ep_num; + + if (epn >= musb->config->num_eps) { + pr_debug("%s: invalid ep %d\n", + musb_driver_name, epn); + return -EINVAL; + } + offset = fifo_setup(musb, hw_ep + epn, cfg++, offset); + if (offset < 0) { + pr_debug("%s: mem overrun, ep %d\n", + musb_driver_name, epn); + return offset; + } + epn++; + musb->nr_endpoints = max(epn, musb->nr_endpoints); + } + + printk(KERN_DEBUG "%s: %d/%d max ep, %d/%d memory\n", + musb_driver_name, + n + 1, musb->config->num_eps * 2 - 1, + offset, (1 << (musb->config->ram_bits + 2))); + + if (!musb->bulk_ep) { + pr_debug("%s: missing bulk\n", musb_driver_name); + return -EINVAL; + } + + return 0; +} + + +/* + * ep_config_from_hw - when MUSB_C_DYNFIFO_DEF is false + * @param musb the controller + */ +static int ep_config_from_hw(struct musb *musb) +{ + u8 epnum = 0; + struct musb_hw_ep *hw_ep; + void __iomem *mbase = musb->mregs; + int ret = 0; + + dev_dbg(musb->controller, "<== static silicon ep config\n"); + + /* FIXME pick up ep0 maxpacket size */ + + for (epnum = 1; epnum < musb->config->num_eps; epnum++) { + musb_ep_select(mbase, epnum); + hw_ep = musb->endpoints + epnum; + + ret = musb_read_fifosize(musb, hw_ep, epnum); + if (ret < 0) + break; + + /* FIXME set up hw_ep->{rx,tx}_double_buffered */ + + /* pick an RX/TX endpoint for bulk */ + if (hw_ep->max_packet_sz_tx < 512 + || hw_ep->max_packet_sz_rx < 512) + continue; + + /* REVISIT: this algorithm is lazy, we should at least + * try to pick a double buffered endpoint. + */ + if (musb->bulk_ep) + continue; + musb->bulk_ep = hw_ep; + } + + if (!musb->bulk_ep) { + pr_debug("%s: missing bulk\n", musb_driver_name); + return -EINVAL; + } + + return 0; +} + +enum { MUSB_CONTROLLER_MHDRC, MUSB_CONTROLLER_HDRC, }; + +/* Initialize MUSB (M)HDRC part of the USB hardware subsystem; + * configure endpoints, or take their config from silicon + */ +static int musb_core_init(u16 musb_type, struct musb *musb) +{ + u8 reg; + char *type; + char aInfo[90], aRevision[32], aDate[12]; + void __iomem *mbase = musb->mregs; + int status = 0; + int i; + + /* log core options (read using indexed model) */ + reg = musb_read_configdata(mbase); + + strcpy(aInfo, (reg & MUSB_CONFIGDATA_UTMIDW) ? "UTMI-16" : "UTMI-8"); + if (reg & MUSB_CONFIGDATA_DYNFIFO) { + strcat(aInfo, ", dyn FIFOs"); + musb->dyn_fifo = true; + } + if (reg & MUSB_CONFIGDATA_MPRXE) { + strcat(aInfo, ", bulk combine"); + musb->bulk_combine = true; + } + if (reg & MUSB_CONFIGDATA_MPTXE) { + strcat(aInfo, ", bulk split"); + musb->bulk_split = true; + } + if (reg & MUSB_CONFIGDATA_HBRXE) { + strcat(aInfo, ", HB-ISO Rx"); + musb->hb_iso_rx = true; + } + if (reg & MUSB_CONFIGDATA_HBTXE) { + strcat(aInfo, ", HB-ISO Tx"); + musb->hb_iso_tx = true; + } + if (reg & MUSB_CONFIGDATA_SOFTCONE) + strcat(aInfo, ", SoftConn"); + + printk(KERN_DEBUG "%s: ConfigData=0x%02x (%s)\n", + musb_driver_name, reg, aInfo); + + aDate[0] = 0; + if (MUSB_CONTROLLER_MHDRC == musb_type) { + musb->is_multipoint = 1; + type = "M"; + } else { + musb->is_multipoint = 0; + type = ""; +#ifndef CONFIG_USB_OTG_BLACKLIST_HUB + printk(KERN_ERR + "%s: kernel must blacklist external hubs\n", + musb_driver_name); +#endif + } + + /* log release info */ + musb->hwvers = musb_read_hwvers(mbase); + snprintf(aRevision, 32, "%d.%d%s", MUSB_HWVERS_MAJOR(musb->hwvers), + MUSB_HWVERS_MINOR(musb->hwvers), + (musb->hwvers & MUSB_HWVERS_RC) ? "RC" : ""); + printk(KERN_DEBUG "%s: %sHDRC RTL version %s %s\n", + musb_driver_name, type, aRevision, aDate); + + /* configure ep0 */ + musb_configure_ep0(musb); + + /* discover endpoint configuration */ + musb->nr_endpoints = 1; + musb->epmask = 1; + + if (musb->dyn_fifo) + status = ep_config_from_table(musb); + else + status = ep_config_from_hw(musb); + + if (status < 0) + return status; + + /* finish init, and print endpoint config */ + for (i = 0; i < musb->nr_endpoints; i++) { + struct musb_hw_ep *hw_ep = musb->endpoints + i; + + hw_ep->fifo = MUSB_FIFO_OFFSET(i) + mbase; +#if defined(CONFIG_USB_MUSB_TUSB6010) || defined (CONFIG_USB_MUSB_TUSB6010_MODULE) + hw_ep->fifo_async = musb->async + 0x400 + MUSB_FIFO_OFFSET(i); + hw_ep->fifo_sync = musb->sync + 0x400 + MUSB_FIFO_OFFSET(i); + hw_ep->fifo_sync_va = + musb->sync_va + 0x400 + MUSB_FIFO_OFFSET(i); + + if (i == 0) + hw_ep->conf = mbase - 0x400 + TUSB_EP0_CONF; + else + hw_ep->conf = mbase + 0x400 + (((i - 1) & 0xf) << 2); +#endif + + hw_ep->regs = MUSB_EP_OFFSET(i, 0) + mbase; + hw_ep->target_regs = musb_read_target_reg_base(i, mbase); + hw_ep->rx_reinit = 1; + hw_ep->tx_reinit = 1; + + if (hw_ep->max_packet_sz_tx) { + dev_dbg(musb->controller, + "%s: hw_ep %d%s, %smax %d\n", + musb_driver_name, i, + hw_ep->is_shared_fifo ? "shared" : "tx", + hw_ep->tx_double_buffered + ? "doublebuffer, " : "", + hw_ep->max_packet_sz_tx); + } + if (hw_ep->max_packet_sz_rx && !hw_ep->is_shared_fifo) { + dev_dbg(musb->controller, + "%s: hw_ep %d%s, %smax %d\n", + musb_driver_name, i, + "rx", + hw_ep->rx_double_buffered + ? "doublebuffer, " : "", + hw_ep->max_packet_sz_rx); + } + if (!(hw_ep->max_packet_sz_tx || hw_ep->max_packet_sz_rx)) + dev_dbg(musb->controller, "hw_ep %d not configured\n", i); + } + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * handle all the irqs defined by the HDRC core. for now we expect: other + * irq sources (phy, dma, etc) will be handled first, musb->int_* values + * will be assigned, and the irq will already have been acked. + * + * called in irq context with spinlock held, irqs blocked + */ +irqreturn_t musb_interrupt(struct musb *musb) +{ + irqreturn_t retval = IRQ_NONE; + u8 devctl; + int ep_num; + u32 reg; + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + dev_dbg(musb->controller, "** IRQ %s usb%04x tx%04x rx%04x\n", + (devctl & MUSB_DEVCTL_HM) ? "host" : "peripheral", + musb->int_usb, musb->int_tx, musb->int_rx); + + /* the core can interrupt us for multiple reasons; docs have + * a generic interrupt flowchart to follow + */ + if (musb->int_usb) + retval |= musb_stage0_irq(musb, musb->int_usb, + devctl); + + /* "stage 1" is handling endpoint irqs */ + + /* handle endpoint 0 first */ + if (musb->int_tx & 1) { + if (devctl & MUSB_DEVCTL_HM) + retval |= musb_h_ep0_irq(musb); + else + retval |= musb_g_ep0_irq(musb); + } + + /* RX on endpoints 1-15 */ + reg = musb->int_rx >> 1; + ep_num = 1; + while (reg) { + if (reg & 1) { + /* musb_ep_select(musb->mregs, ep_num); */ + /* REVISIT just retval = ep->rx_irq(...) */ + retval = IRQ_HANDLED; + if (devctl & MUSB_DEVCTL_HM) + musb_host_rx(musb, ep_num); + else + musb_g_rx(musb, ep_num); + } + + reg >>= 1; + ep_num++; + } + + /* TX on endpoints 1-15 */ + reg = musb->int_tx >> 1; + ep_num = 1; + while (reg) { + if (reg & 1) { + /* musb_ep_select(musb->mregs, ep_num); */ + /* REVISIT just retval |= ep->tx_irq(...) */ + retval = IRQ_HANDLED; + if (devctl & MUSB_DEVCTL_HM) + musb_host_tx(musb, ep_num); + else + musb_g_tx(musb, ep_num); + } + reg >>= 1; + ep_num++; + } + + return retval; +} +EXPORT_SYMBOL_GPL(musb_interrupt); + +#ifndef CONFIG_MUSB_PIO_ONLY +static bool use_dma = 1; + +/* "modprobe ... use_dma=0" etc */ +module_param(use_dma, bool, 0); +MODULE_PARM_DESC(use_dma, "enable/disable use of DMA"); + +void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit) +{ + u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + /* called with controller lock already held */ + + if (!epnum) { +#ifndef CONFIG_USB_TUSB_OMAP_DMA + if (!is_cppi_enabled()) { + /* endpoint 0 */ + if (devctl & MUSB_DEVCTL_HM) + musb_h_ep0_irq(musb); + else + musb_g_ep0_irq(musb); + } +#endif + } else { + /* endpoints 1..15 */ + if (transmit) { + if (devctl & MUSB_DEVCTL_HM) + musb_host_tx(musb, epnum); + else + musb_g_tx(musb, epnum); + } else { + /* receive */ + if (devctl & MUSB_DEVCTL_HM) + musb_host_rx(musb, epnum); + else + musb_g_rx(musb, epnum); + } + } +} +EXPORT_SYMBOL_GPL(musb_dma_completion); + +#else +#define use_dma 0 +#endif + +/*-------------------------------------------------------------------------*/ + +static ssize_t +musb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&musb->lock, flags); + ret = sprintf(buf, "%s\n", usb_otg_state_string(musb->xceiv->state)); + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static ssize_t +musb_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + int status; + + spin_lock_irqsave(&musb->lock, flags); + if (sysfs_streq(buf, "host")) + status = musb_platform_set_mode(musb, MUSB_HOST); + else if (sysfs_streq(buf, "peripheral")) + status = musb_platform_set_mode(musb, MUSB_PERIPHERAL); + else if (sysfs_streq(buf, "otg")) + status = musb_platform_set_mode(musb, MUSB_OTG); + else + status = -EINVAL; + spin_unlock_irqrestore(&musb->lock, flags); + + return (status == 0) ? n : status; +} +static DEVICE_ATTR(mode, 0644, musb_mode_show, musb_mode_store); + +static ssize_t +musb_vbus_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + unsigned long val; + + if (sscanf(buf, "%lu", &val) < 1) { + dev_err(dev, "Invalid VBUS timeout ms value\n"); + return -EINVAL; + } + + spin_lock_irqsave(&musb->lock, flags); + /* force T(a_wait_bcon) to be zero/unlimited *OR* valid */ + musb->a_wait_bcon = val ? max_t(int, val, OTG_TIME_A_WAIT_BCON) : 0 ; + if (musb->xceiv->state == OTG_STATE_A_WAIT_BCON) + musb->is_active = 0; + musb_platform_try_idle(musb, jiffies + msecs_to_jiffies(val)); + spin_unlock_irqrestore(&musb->lock, flags); + + return n; +} + +static ssize_t +musb_vbus_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + unsigned long val; + int vbus; + + spin_lock_irqsave(&musb->lock, flags); + val = musb->a_wait_bcon; + /* FIXME get_vbus_status() is normally #defined as false... + * and is effectively TUSB-specific. + */ + vbus = musb_platform_get_vbus_status(musb); + spin_unlock_irqrestore(&musb->lock, flags); + + return sprintf(buf, "Vbus %s, timeout %lu msec\n", + vbus ? "on" : "off", val); +} +static DEVICE_ATTR(vbus, 0644, musb_vbus_show, musb_vbus_store); + +/* Gadget drivers can't know that a host is connected so they might want + * to start SRP, but users can. This allows userspace to trigger SRP. + */ +static ssize_t +musb_srp_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct musb *musb = dev_to_musb(dev); + unsigned short srp; + + if (sscanf(buf, "%hu", &srp) != 1 + || (srp != 1)) { + dev_err(dev, "SRP: Value must be 1\n"); + return -EINVAL; + } + + if (srp == 1) + musb_g_wakeup(musb); + + return n; +} +static DEVICE_ATTR(srp, 0644, NULL, musb_srp_store); + +static struct attribute *musb_attributes[] = { + &dev_attr_mode.attr, + &dev_attr_vbus.attr, + &dev_attr_srp.attr, + NULL +}; + +static const struct attribute_group musb_attr_group = { + .attrs = musb_attributes, +}; + +/* Only used to provide driver mode change events */ +static void musb_irq_work(struct work_struct *data) +{ + struct musb *musb = container_of(data, struct musb, irq_work); + + if (musb->xceiv->state != musb->xceiv_old_state) { + musb->xceiv_old_state = musb->xceiv->state; + sysfs_notify(&musb->controller->kobj, NULL, "mode"); + } +} + +/* -------------------------------------------------------------------------- + * Init support + */ + +static struct musb *allocate_instance(struct device *dev, + struct musb_hdrc_config *config, void __iomem *mbase) +{ + struct musb *musb; + struct musb_hw_ep *ep; + int epnum; + struct usb_hcd *hcd; + + hcd = usb_create_hcd(&musb_hc_driver, dev, dev_name(dev)); + if (!hcd) + return NULL; + /* usbcore sets dev->driver_data to hcd, and sometimes uses that... */ + + musb = hcd_to_musb(hcd); + INIT_LIST_HEAD(&musb->control); + INIT_LIST_HEAD(&musb->in_bulk); + INIT_LIST_HEAD(&musb->out_bulk); + + hcd->uses_new_polling = 1; + hcd->has_tt = 1; + + musb->vbuserr_retry = VBUSERR_RETRY_COUNT; + musb->a_wait_bcon = OTG_TIME_A_WAIT_BCON; + dev_set_drvdata(dev, musb); + musb->mregs = mbase; + musb->ctrl_base = mbase; + musb->nIrq = -ENODEV; + musb->config = config; + BUG_ON(musb->config->num_eps > MUSB_C_NUM_EPS); + for (epnum = 0, ep = musb->endpoints; + epnum < musb->config->num_eps; + epnum++, ep++) { + ep->musb = musb; + ep->epnum = epnum; + } + + musb->controller = dev; + + return musb; +} + +static void musb_free(struct musb *musb) +{ + /* this has multiple entry modes. it handles fault cleanup after + * probe(), where things may be partially set up, as well as rmmod + * cleanup after everything's been de-activated. + */ + +#ifdef CONFIG_SYSFS + sysfs_remove_group(&musb->controller->kobj, &musb_attr_group); +#endif + + if (musb->nIrq >= 0) { + if (musb->irq_wake) + disable_irq_wake(musb->nIrq); + free_irq(musb->nIrq, musb); + } + if (is_dma_capable() && musb->dma_controller) { + struct dma_controller *c = musb->dma_controller; + + (void) c->stop(c); + dma_controller_destroy(c); + } + + usb_put_hcd(musb_to_hcd(musb)); +} + +/* + * Perform generic per-controller initialization. + * + * @dev: the controller (already clocked, etc) + * @nIrq: IRQ number + * @ctrl: virtual address of controller registers, + * not yet corrected for platform-specific offsets + */ +static int +musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) +{ + int status; + struct musb *musb; + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct usb_hcd *hcd; + + /* The driver might handle more features than the board; OK. + * Fail when the board needs a feature that's not enabled. + */ + if (!plat) { + dev_dbg(dev, "no platform_data?\n"); + status = -ENODEV; + goto fail0; + } + + /* allocate */ + musb = allocate_instance(dev, plat->config, ctrl); + if (!musb) { + status = -ENOMEM; + goto fail0; + } + + pm_runtime_use_autosuspend(musb->controller); + pm_runtime_set_autosuspend_delay(musb->controller, 200); + pm_runtime_enable(musb->controller); + + spin_lock_init(&musb->lock); + musb->board_set_power = plat->set_power; + musb->min_power = plat->min_power; + musb->ops = plat->platform_ops; + + /* The musb_platform_init() call: + * - adjusts musb->mregs + * - sets the musb->isr + * - may initialize an integrated tranceiver + * - initializes musb->xceiv, usually by otg_get_phy() + * - stops powering VBUS + * + * There are various transceiver configurations. Blackfin, + * DaVinci, TUSB60x0, and others integrate them. OMAP3 uses + * external/discrete ones in various flavors (twl4030 family, + * isp1504, non-OTG, etc) mostly hooking up through ULPI. + */ + status = musb_platform_init(musb); + if (status < 0) + goto fail1; + + if (!musb->isr) { + status = -ENODEV; + goto fail2; + } + + if (!musb->xceiv->io_ops) { + musb->xceiv->io_dev = musb->controller; + musb->xceiv->io_priv = musb->mregs; + musb->xceiv->io_ops = &musb_ulpi_access; + } + + pm_runtime_get_sync(musb->controller); + +#ifndef CONFIG_MUSB_PIO_ONLY + if (use_dma && dev->dma_mask) { + struct dma_controller *c; + + c = dma_controller_create(musb, musb->mregs); + musb->dma_controller = c; + if (c) + (void) c->start(c); + } +#endif + /* ideally this would be abstracted in platform setup */ + if (!is_dma_capable() || !musb->dma_controller) + dev->dma_mask = NULL; + + /* be sure interrupts are disabled before connecting ISR */ + musb_platform_disable(musb); + musb_generic_disable(musb); + + /* setup musb parts of the core (especially endpoints) */ + status = musb_core_init(plat->config->multipoint + ? MUSB_CONTROLLER_MHDRC + : MUSB_CONTROLLER_HDRC, musb); + if (status < 0) + goto fail3; + + setup_timer(&musb->otg_timer, musb_otg_timer_func, (unsigned long) musb); + + /* Init IRQ workqueue before request_irq */ + INIT_WORK(&musb->irq_work, musb_irq_work); + + /* attach to the IRQ */ + if (request_irq(nIrq, musb->isr, 0, dev_name(dev), musb)) { + dev_err(dev, "request_irq %d failed!\n", nIrq); + status = -ENODEV; + goto fail3; + } + musb->nIrq = nIrq; + /* FIXME this handles wakeup irqs wrong */ + if (enable_irq_wake(nIrq) == 0) { + musb->irq_wake = 1; + device_init_wakeup(dev, 1); + } else { + musb->irq_wake = 0; + } + + /* host side needs more setup */ + hcd = musb_to_hcd(musb); + otg_set_host(musb->xceiv->otg, &hcd->self); + hcd->self.otg_port = 1; + musb->xceiv->otg->host = &hcd->self; + hcd->power_budget = 2 * (plat->power ? : 250); + + /* program PHY to use external vBus if required */ + if (plat->extvbus) { + u8 busctl = musb_read_ulpi_buscontrol(musb->mregs); + busctl |= MUSB_ULPI_USE_EXTVBUS; + musb_write_ulpi_buscontrol(musb->mregs, busctl); + } + + if (musb->xceiv->otg->default_a) { + MUSB_HST_MODE(musb); + musb->xceiv->state = OTG_STATE_A_IDLE; + } else { + MUSB_DEV_MODE(musb); + musb->xceiv->state = OTG_STATE_B_IDLE; + } + + status = musb_gadget_setup(musb); + + if (status < 0) + goto fail3; + + status = musb_init_debugfs(musb); + if (status < 0) + goto fail4; + + status = sysfs_create_group(&musb->controller->kobj, &musb_attr_group); + if (status) + goto fail5; + + pm_runtime_put(musb->controller); + + return 0; + +fail5: + musb_exit_debugfs(musb); + +fail4: + musb_gadget_cleanup(musb); + +fail3: + pm_runtime_put_sync(musb->controller); + +fail2: + if (musb->irq_wake) + device_init_wakeup(dev, 0); + musb_platform_exit(musb); + +fail1: + pm_runtime_disable(musb->controller); + dev_err(musb->controller, + "musb_init_controller failed with status %d\n", status); + + musb_free(musb); + +fail0: + + return status; + +} + +/*-------------------------------------------------------------------------*/ + +/* all implementations (PCI bridge to FPGA, VLYNQ, etc) should just + * bridge to a platform device; this driver then suffices. + */ +static int musb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int irq = platform_get_irq_byname(pdev, "mc"); + struct resource *iomem; + void __iomem *base; + + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iomem || irq <= 0) + return -ENODEV; + + base = devm_ioremap_resource(dev, iomem); + if (IS_ERR(base)) + return PTR_ERR(base); + + return musb_init_controller(dev, irq, base); +} + +static int musb_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct musb *musb = dev_to_musb(dev); + + /* this gets called on rmmod. + * - Host mode: host may still be active + * - Peripheral mode: peripheral is deactivated (or never-activated) + * - OTG mode: both roles are deactivated (or never-activated) + */ + musb_exit_debugfs(musb); + musb_shutdown(pdev); + + musb_free(musb); + device_init_wakeup(dev, 0); +#ifndef CONFIG_MUSB_PIO_ONLY + dma_set_mask(dev, *dev->parent->dma_mask); +#endif + return 0; +} + +#ifdef CONFIG_PM + +static void musb_save_context(struct musb *musb) +{ + int i; + void __iomem *musb_base = musb->mregs; + void __iomem *epio; + + musb->context.frame = musb_readw(musb_base, MUSB_FRAME); + musb->context.testmode = musb_readb(musb_base, MUSB_TESTMODE); + musb->context.busctl = musb_read_ulpi_buscontrol(musb->mregs); + musb->context.power = musb_readb(musb_base, MUSB_POWER); + musb->context.intrusbe = musb_readb(musb_base, MUSB_INTRUSBE); + musb->context.index = musb_readb(musb_base, MUSB_INDEX); + musb->context.devctl = musb_readb(musb_base, MUSB_DEVCTL); + + for (i = 0; i < musb->config->num_eps; ++i) { + struct musb_hw_ep *hw_ep; + + hw_ep = &musb->endpoints[i]; + if (!hw_ep) + continue; + + epio = hw_ep->regs; + if (!epio) + continue; + + musb_writeb(musb_base, MUSB_INDEX, i); + musb->context.index_regs[i].txmaxp = + musb_readw(epio, MUSB_TXMAXP); + musb->context.index_regs[i].txcsr = + musb_readw(epio, MUSB_TXCSR); + musb->context.index_regs[i].rxmaxp = + musb_readw(epio, MUSB_RXMAXP); + musb->context.index_regs[i].rxcsr = + musb_readw(epio, MUSB_RXCSR); + + if (musb->dyn_fifo) { + musb->context.index_regs[i].txfifoadd = + musb_read_txfifoadd(musb_base); + musb->context.index_regs[i].rxfifoadd = + musb_read_rxfifoadd(musb_base); + musb->context.index_regs[i].txfifosz = + musb_read_txfifosz(musb_base); + musb->context.index_regs[i].rxfifosz = + musb_read_rxfifosz(musb_base); + } + + musb->context.index_regs[i].txtype = + musb_readb(epio, MUSB_TXTYPE); + musb->context.index_regs[i].txinterval = + musb_readb(epio, MUSB_TXINTERVAL); + musb->context.index_regs[i].rxtype = + musb_readb(epio, MUSB_RXTYPE); + musb->context.index_regs[i].rxinterval = + musb_readb(epio, MUSB_RXINTERVAL); + + musb->context.index_regs[i].txfunaddr = + musb_read_txfunaddr(musb_base, i); + musb->context.index_regs[i].txhubaddr = + musb_read_txhubaddr(musb_base, i); + musb->context.index_regs[i].txhubport = + musb_read_txhubport(musb_base, i); + + musb->context.index_regs[i].rxfunaddr = + musb_read_rxfunaddr(musb_base, i); + musb->context.index_regs[i].rxhubaddr = + musb_read_rxhubaddr(musb_base, i); + musb->context.index_regs[i].rxhubport = + musb_read_rxhubport(musb_base, i); + } +} + +static void musb_restore_context(struct musb *musb) +{ + int i; + void __iomem *musb_base = musb->mregs; + void __iomem *ep_target_regs; + void __iomem *epio; + + musb_writew(musb_base, MUSB_FRAME, musb->context.frame); + musb_writeb(musb_base, MUSB_TESTMODE, musb->context.testmode); + musb_write_ulpi_buscontrol(musb->mregs, musb->context.busctl); + musb_writeb(musb_base, MUSB_POWER, musb->context.power); + musb_writew(musb_base, MUSB_INTRTXE, musb->intrtxe); + musb_writew(musb_base, MUSB_INTRRXE, musb->intrrxe); + musb_writeb(musb_base, MUSB_INTRUSBE, musb->context.intrusbe); + musb_writeb(musb_base, MUSB_DEVCTL, musb->context.devctl); + + for (i = 0; i < musb->config->num_eps; ++i) { + struct musb_hw_ep *hw_ep; + + hw_ep = &musb->endpoints[i]; + if (!hw_ep) + continue; + + epio = hw_ep->regs; + if (!epio) + continue; + + musb_writeb(musb_base, MUSB_INDEX, i); + musb_writew(epio, MUSB_TXMAXP, + musb->context.index_regs[i].txmaxp); + musb_writew(epio, MUSB_TXCSR, + musb->context.index_regs[i].txcsr); + musb_writew(epio, MUSB_RXMAXP, + musb->context.index_regs[i].rxmaxp); + musb_writew(epio, MUSB_RXCSR, + musb->context.index_regs[i].rxcsr); + + if (musb->dyn_fifo) { + musb_write_txfifosz(musb_base, + musb->context.index_regs[i].txfifosz); + musb_write_rxfifosz(musb_base, + musb->context.index_regs[i].rxfifosz); + musb_write_txfifoadd(musb_base, + musb->context.index_regs[i].txfifoadd); + musb_write_rxfifoadd(musb_base, + musb->context.index_regs[i].rxfifoadd); + } + + musb_writeb(epio, MUSB_TXTYPE, + musb->context.index_regs[i].txtype); + musb_writeb(epio, MUSB_TXINTERVAL, + musb->context.index_regs[i].txinterval); + musb_writeb(epio, MUSB_RXTYPE, + musb->context.index_regs[i].rxtype); + musb_writeb(epio, MUSB_RXINTERVAL, + + musb->context.index_regs[i].rxinterval); + musb_write_txfunaddr(musb_base, i, + musb->context.index_regs[i].txfunaddr); + musb_write_txhubaddr(musb_base, i, + musb->context.index_regs[i].txhubaddr); + musb_write_txhubport(musb_base, i, + musb->context.index_regs[i].txhubport); + + ep_target_regs = + musb_read_target_reg_base(i, musb_base); + + musb_write_rxfunaddr(ep_target_regs, + musb->context.index_regs[i].rxfunaddr); + musb_write_rxhubaddr(ep_target_regs, + musb->context.index_regs[i].rxhubaddr); + musb_write_rxhubport(ep_target_regs, + musb->context.index_regs[i].rxhubport); + } + musb_writeb(musb_base, MUSB_INDEX, musb->context.index); +} + +static int musb_suspend(struct device *dev) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + + if (is_peripheral_active(musb)) { + /* FIXME force disconnect unless we know USB will wake + * the system up quickly enough to respond ... + */ + } else if (is_host_active(musb)) { + /* we know all the children are suspended; sometimes + * they will even be wakeup-enabled. + */ + } + + spin_unlock_irqrestore(&musb->lock, flags); + return 0; +} + +static int musb_resume_noirq(struct device *dev) +{ + /* for static cmos like DaVinci, register values were preserved + * unless for some reason the whole soc powered down or the USB + * module got reset through the PSC (vs just being disabled). + */ + return 0; +} + +static int musb_runtime_suspend(struct device *dev) +{ + struct musb *musb = dev_to_musb(dev); + + musb_save_context(musb); + + return 0; +} + +static int musb_runtime_resume(struct device *dev) +{ + struct musb *musb = dev_to_musb(dev); + static int first = 1; + + /* + * When pm_runtime_get_sync called for the first time in driver + * init, some of the structure is still not initialized which is + * used in restore function. But clock needs to be + * enabled before any register access, so + * pm_runtime_get_sync has to be called. + * Also context restore without save does not make + * any sense + */ + if (!first) + musb_restore_context(musb); + first = 0; + + return 0; +} + +static const struct dev_pm_ops musb_dev_pm_ops = { + .suspend = musb_suspend, + .resume_noirq = musb_resume_noirq, + .runtime_suspend = musb_runtime_suspend, + .runtime_resume = musb_runtime_resume, +}; + +#define MUSB_DEV_PM_OPS (&musb_dev_pm_ops) +#else +#define MUSB_DEV_PM_OPS NULL +#endif + +static struct platform_driver musb_driver = { + .driver = { + .name = (char *)musb_driver_name, + .bus = &platform_bus_type, + .owner = THIS_MODULE, + .pm = MUSB_DEV_PM_OPS, + }, + .probe = musb_probe, + .remove = musb_remove, + .shutdown = musb_shutdown, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init musb_init(void) +{ + if (usb_disabled()) + return 0; + + return platform_driver_register(&musb_driver); +} +module_init(musb_init); + +static void __exit musb_cleanup(void) +{ + platform_driver_unregister(&musb_driver); +} +module_exit(musb_cleanup); diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h new file mode 100644 index 00000000..7fb4819a --- /dev/null +++ b/drivers/usb/musb/musb_core.h @@ -0,0 +1,592 @@ +/* + * MUSB OTG driver defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __MUSB_CORE_H__ +#define __MUSB_CORE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct musb; +struct musb_hw_ep; +struct musb_ep; + +/* Helper defines for struct musb->hwvers */ +#define MUSB_HWVERS_MAJOR(x) ((x >> 10) & 0x1f) +#define MUSB_HWVERS_MINOR(x) (x & 0x3ff) +#define MUSB_HWVERS_RC 0x8000 +#define MUSB_HWVERS_1300 0x52C +#define MUSB_HWVERS_1400 0x590 +#define MUSB_HWVERS_1800 0x720 +#define MUSB_HWVERS_1900 0x784 +#define MUSB_HWVERS_2000 0x800 + +#include "musb_debug.h" +#include "musb_dma.h" + +#include "musb_io.h" +#include "musb_regs.h" + +#include "musb_gadget.h" +#include +#include "musb_host.h" + +/* NOTE: otg and peripheral-only state machines start at B_IDLE. + * OTG or host-only go to A_IDLE when ID is sensed. + */ +#define is_peripheral_active(m) (!(m)->is_host) +#define is_host_active(m) ((m)->is_host) + +#ifdef CONFIG_PROC_FS +#include +#define MUSB_CONFIG_PROC_FS +#endif + +/****************************** PERIPHERAL ROLE *****************************/ + +extern irqreturn_t musb_g_ep0_irq(struct musb *); +extern void musb_g_tx(struct musb *, u8); +extern void musb_g_rx(struct musb *, u8); +extern void musb_g_reset(struct musb *); +extern void musb_g_suspend(struct musb *); +extern void musb_g_resume(struct musb *); +extern void musb_g_wakeup(struct musb *); +extern void musb_g_disconnect(struct musb *); + +/****************************** HOST ROLE ***********************************/ + +extern irqreturn_t musb_h_ep0_irq(struct musb *); +extern void musb_host_tx(struct musb *, u8); +extern void musb_host_rx(struct musb *, u8); + +/****************************** CONSTANTS ********************************/ + +#ifndef MUSB_C_NUM_EPS +#define MUSB_C_NUM_EPS ((u8)16) +#endif + +#ifndef MUSB_MAX_END0_PACKET +#define MUSB_MAX_END0_PACKET ((u16)MUSB_EP0_FIFOSIZE) +#endif + +/* host side ep0 states */ +enum musb_h_ep0_state { + MUSB_EP0_IDLE, + MUSB_EP0_START, /* expect ack of setup */ + MUSB_EP0_IN, /* expect IN DATA */ + MUSB_EP0_OUT, /* expect ack of OUT DATA */ + MUSB_EP0_STATUS, /* expect ack of STATUS */ +} __attribute__ ((packed)); + +/* peripheral side ep0 states */ +enum musb_g_ep0_state { + MUSB_EP0_STAGE_IDLE, /* idle, waiting for SETUP */ + MUSB_EP0_STAGE_SETUP, /* received SETUP */ + MUSB_EP0_STAGE_TX, /* IN data */ + MUSB_EP0_STAGE_RX, /* OUT data */ + MUSB_EP0_STAGE_STATUSIN, /* (after OUT data) */ + MUSB_EP0_STAGE_STATUSOUT, /* (after IN data) */ + MUSB_EP0_STAGE_ACKWAIT, /* after zlp, before statusin */ +} __attribute__ ((packed)); + +/* + * OTG protocol constants. See USB OTG 1.3 spec, + * sections 5.5 "Device Timings" and 6.6.5 "Timers". + */ +#define OTG_TIME_A_WAIT_VRISE 100 /* msec (max) */ +#define OTG_TIME_A_WAIT_BCON 1100 /* min 1 second */ +#define OTG_TIME_A_AIDL_BDIS 200 /* min 200 msec */ +#define OTG_TIME_B_ASE0_BRST 100 /* min 3.125 ms */ + + +/*************************** REGISTER ACCESS ********************************/ + +/* Endpoint registers (other than dynfifo setup) can be accessed either + * directly with the "flat" model, or after setting up an index register. + */ + +#if defined(CONFIG_ARCH_DAVINCI) || defined(CONFIG_SOC_OMAP2430) \ + || defined(CONFIG_SOC_OMAP3430) || defined(CONFIG_BLACKFIN) \ + || defined(CONFIG_ARCH_OMAP4) +/* REVISIT indexed access seemed to + * misbehave (on DaVinci) for at least peripheral IN ... + */ +#define MUSB_FLAT_REG +#endif + +/* TUSB mapping: "flat" plus ep0 special cases */ +#if defined(CONFIG_USB_MUSB_TUSB6010) || \ + defined(CONFIG_USB_MUSB_TUSB6010_MODULE) +#define musb_ep_select(_mbase, _epnum) \ + musb_writeb((_mbase), MUSB_INDEX, (_epnum)) +#define MUSB_EP_OFFSET MUSB_TUSB_OFFSET + +/* "flat" mapping: each endpoint has its own i/o address */ +#elif defined(MUSB_FLAT_REG) +#define musb_ep_select(_mbase, _epnum) (((void)(_mbase)), ((void)(_epnum))) +#define MUSB_EP_OFFSET MUSB_FLAT_OFFSET + +/* "indexed" mapping: INDEX register controls register bank select */ +#else +#define musb_ep_select(_mbase, _epnum) \ + musb_writeb((_mbase), MUSB_INDEX, (_epnum)) +#define MUSB_EP_OFFSET MUSB_INDEXED_OFFSET +#endif + +/****************************** FUNCTIONS ********************************/ + +#define MUSB_HST_MODE(_musb)\ + { (_musb)->is_host = true; } +#define MUSB_DEV_MODE(_musb) \ + { (_musb)->is_host = false; } + +#define test_devctl_hst_mode(_x) \ + (musb_readb((_x)->mregs, MUSB_DEVCTL)&MUSB_DEVCTL_HM) + +#define MUSB_MODE(musb) ((musb)->is_host ? "Host" : "Peripheral") + +/******************************** TYPES *************************************/ + +/** + * struct musb_platform_ops - Operations passed to musb_core by HW glue layer + * @init: turns on clocks, sets up platform-specific registers, etc + * @exit: undoes @init + * @set_mode: forcefully changes operating mode + * @try_ilde: tries to idle the IP + * @vbus_status: returns vbus status if possible + * @set_vbus: forces vbus status + * @adjust_channel_params: pre check for standard dma channel_program func + */ +struct musb_platform_ops { + int (*init)(struct musb *musb); + int (*exit)(struct musb *musb); + + void (*enable)(struct musb *musb); + void (*disable)(struct musb *musb); + + int (*set_mode)(struct musb *musb, u8 mode); + void (*try_idle)(struct musb *musb, unsigned long timeout); + + int (*vbus_status)(struct musb *musb); + void (*set_vbus)(struct musb *musb, int on); + + int (*adjust_channel_params)(struct dma_channel *channel, + u16 packet_sz, u8 *mode, + dma_addr_t *dma_addr, u32 *len); +}; + +/* + * struct musb_hw_ep - endpoint hardware (bidirectional) + * + * Ordered slightly for better cacheline locality. + */ +struct musb_hw_ep { + struct musb *musb; + void __iomem *fifo; + void __iomem *regs; + +#if defined(CONFIG_USB_MUSB_TUSB6010) || \ + defined(CONFIG_USB_MUSB_TUSB6010_MODULE) + void __iomem *conf; +#endif + + /* index in musb->endpoints[] */ + u8 epnum; + + /* hardware configuration, possibly dynamic */ + bool is_shared_fifo; + bool tx_double_buffered; + bool rx_double_buffered; + u16 max_packet_sz_tx; + u16 max_packet_sz_rx; + + struct dma_channel *tx_channel; + struct dma_channel *rx_channel; + +#if defined(CONFIG_USB_MUSB_TUSB6010) || \ + defined(CONFIG_USB_MUSB_TUSB6010_MODULE) + /* TUSB has "asynchronous" and "synchronous" dma modes */ + dma_addr_t fifo_async; + dma_addr_t fifo_sync; + void __iomem *fifo_sync_va; +#endif + + void __iomem *target_regs; + + /* currently scheduled peripheral endpoint */ + struct musb_qh *in_qh; + struct musb_qh *out_qh; + + u8 rx_reinit; + u8 tx_reinit; + + /* peripheral side */ + struct musb_ep ep_in; /* TX */ + struct musb_ep ep_out; /* RX */ +}; + +static inline struct musb_request *next_in_request(struct musb_hw_ep *hw_ep) +{ + return next_request(&hw_ep->ep_in); +} + +static inline struct musb_request *next_out_request(struct musb_hw_ep *hw_ep) +{ + return next_request(&hw_ep->ep_out); +} + +struct musb_csr_regs { + /* FIFO registers */ + u16 txmaxp, txcsr, rxmaxp, rxcsr; + u16 rxfifoadd, txfifoadd; + u8 txtype, txinterval, rxtype, rxinterval; + u8 rxfifosz, txfifosz; + u8 txfunaddr, txhubaddr, txhubport; + u8 rxfunaddr, rxhubaddr, rxhubport; +}; + +struct musb_context_registers { + + u8 power; + u8 intrusbe; + u16 frame; + u8 index, testmode; + + u8 devctl, busctl, misc; + u32 otg_interfsel; + + struct musb_csr_regs index_regs[MUSB_C_NUM_EPS]; +}; + +/* + * struct musb - Driver instance data. + */ +struct musb { + /* device lock */ + spinlock_t lock; + + const struct musb_platform_ops *ops; + struct musb_context_registers context; + + irqreturn_t (*isr)(int, void *); + struct work_struct irq_work; + u16 hwvers; + + u16 intrrxe; + u16 intrtxe; +/* this hub status bit is reserved by USB 2.0 and not seen by usbcore */ +#define MUSB_PORT_STAT_RESUME (1 << 31) + + u32 port1_status; + + unsigned long rh_timer; + + enum musb_h_ep0_state ep0_stage; + + /* bulk traffic normally dedicates endpoint hardware, and each + * direction has its own ring of host side endpoints. + * we try to progress the transfer at the head of each endpoint's + * queue until it completes or NAKs too much; then we try the next + * endpoint. + */ + struct musb_hw_ep *bulk_ep; + + struct list_head control; /* of musb_qh */ + struct list_head in_bulk; /* of musb_qh */ + struct list_head out_bulk; /* of musb_qh */ + + struct timer_list otg_timer; + struct notifier_block nb; + + struct dma_controller *dma_controller; + + struct device *controller; + void __iomem *ctrl_base; + void __iomem *mregs; + +#if defined(CONFIG_USB_MUSB_TUSB6010) || \ + defined(CONFIG_USB_MUSB_TUSB6010_MODULE) + dma_addr_t async; + dma_addr_t sync; + void __iomem *sync_va; +#endif + + /* passed down from chip/board specific irq handlers */ + u8 int_usb; + u16 int_rx; + u16 int_tx; + + struct usb_phy *xceiv; + + int nIrq; + unsigned irq_wake:1; + + struct musb_hw_ep endpoints[MUSB_C_NUM_EPS]; +#define control_ep endpoints + +#define VBUSERR_RETRY_COUNT 3 + u16 vbuserr_retry; + u16 epmask; + u8 nr_endpoints; + + int (*board_set_power)(int state); + + u8 min_power; /* vbus for periph, in mA/2 */ + + bool is_host; + + int a_wait_bcon; /* VBUS timeout in msecs */ + unsigned long idle_timeout; /* Next timeout in jiffies */ + + /* active means connected and not suspended */ + unsigned is_active:1; + + unsigned is_multipoint:1; + unsigned ignore_disconnect:1; /* during bus resets */ + + unsigned hb_iso_rx:1; /* high bandwidth iso rx? */ + unsigned hb_iso_tx:1; /* high bandwidth iso tx? */ + unsigned dyn_fifo:1; /* dynamic FIFO supported? */ + + unsigned bulk_split:1; +#define can_bulk_split(musb,type) \ + (((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_split) + + unsigned bulk_combine:1; +#define can_bulk_combine(musb,type) \ + (((type) == USB_ENDPOINT_XFER_BULK) && (musb)->bulk_combine) + + /* is_suspended means USB B_PERIPHERAL suspend */ + unsigned is_suspended:1; + + /* may_wakeup means remote wakeup is enabled */ + unsigned may_wakeup:1; + + /* is_self_powered is reported in device status and the + * config descriptor. is_bus_powered means B_PERIPHERAL + * draws some VBUS current; both can be true. + */ + unsigned is_self_powered:1; + unsigned is_bus_powered:1; + + unsigned set_address:1; + unsigned test_mode:1; + unsigned softconnect:1; + + u8 address; + u8 test_mode_nr; + u16 ackpend; /* ep0 */ + enum musb_g_ep0_state ep0_state; + struct usb_gadget g; /* the gadget */ + struct usb_gadget_driver *gadget_driver; /* its driver */ + + /* + * FIXME: Remove this flag. + * + * This is only added to allow Blackfin to work + * with current driver. For some unknown reason + * Blackfin doesn't work with double buffering + * and that's enabled by default. + * + * We added this flag to forcefully disable double + * buffering until we get it working. + */ + unsigned double_buffer_not_ok:1; + + struct musb_hdrc_config *config; + +#ifdef MUSB_CONFIG_PROC_FS + struct proc_dir_entry *proc_entry; +#endif + int xceiv_old_state; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; +#endif +}; + +static inline struct musb *gadget_to_musb(struct usb_gadget *g) +{ + return container_of(g, struct musb, g); +} + +#ifdef CONFIG_BLACKFIN +static inline int musb_read_fifosize(struct musb *musb, + struct musb_hw_ep *hw_ep, u8 epnum) +{ + musb->nr_endpoints++; + musb->epmask |= (1 << epnum); + + if (epnum < 5) { + hw_ep->max_packet_sz_tx = 128; + hw_ep->max_packet_sz_rx = 128; + } else { + hw_ep->max_packet_sz_tx = 1024; + hw_ep->max_packet_sz_rx = 1024; + } + hw_ep->is_shared_fifo = false; + + return 0; +} + +static inline void musb_configure_ep0(struct musb *musb) +{ + musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE; + musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE; + musb->endpoints[0].is_shared_fifo = true; +} + +#else + +static inline int musb_read_fifosize(struct musb *musb, + struct musb_hw_ep *hw_ep, u8 epnum) +{ + void __iomem *mbase = musb->mregs; + u8 reg = 0; + + /* read from core using indexed model */ + reg = musb_readb(mbase, MUSB_EP_OFFSET(epnum, MUSB_FIFOSIZE)); + /* 0's returned when no more endpoints */ + if (!reg) + return -ENODEV; + + musb->nr_endpoints++; + musb->epmask |= (1 << epnum); + + hw_ep->max_packet_sz_tx = 1 << (reg & 0x0f); + + /* shared TX/RX FIFO? */ + if ((reg & 0xf0) == 0xf0) { + hw_ep->max_packet_sz_rx = hw_ep->max_packet_sz_tx; + hw_ep->is_shared_fifo = true; + return 0; + } else { + hw_ep->max_packet_sz_rx = 1 << ((reg & 0xf0) >> 4); + hw_ep->is_shared_fifo = false; + } + + return 0; +} + +static inline void musb_configure_ep0(struct musb *musb) +{ + musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE; + musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE; + musb->endpoints[0].is_shared_fifo = true; +} +#endif /* CONFIG_BLACKFIN */ + + +/***************************** Glue it together *****************************/ + +extern const char musb_driver_name[]; + +extern void musb_start(struct musb *musb); +extern void musb_stop(struct musb *musb); + +extern void musb_write_fifo(struct musb_hw_ep *ep, u16 len, const u8 *src); +extern void musb_read_fifo(struct musb_hw_ep *ep, u16 len, u8 *dst); + +extern void musb_load_testpacket(struct musb *); + +extern irqreturn_t musb_interrupt(struct musb *); + +extern void musb_hnp_stop(struct musb *musb); + +static inline void musb_platform_set_vbus(struct musb *musb, int is_on) +{ + if (musb->ops->set_vbus) + musb->ops->set_vbus(musb, is_on); +} + +static inline void musb_platform_enable(struct musb *musb) +{ + if (musb->ops->enable) + musb->ops->enable(musb); +} + +static inline void musb_platform_disable(struct musb *musb) +{ + if (musb->ops->disable) + musb->ops->disable(musb); +} + +static inline int musb_platform_set_mode(struct musb *musb, u8 mode) +{ + if (!musb->ops->set_mode) + return 0; + + return musb->ops->set_mode(musb, mode); +} + +static inline void musb_platform_try_idle(struct musb *musb, + unsigned long timeout) +{ + if (musb->ops->try_idle) + musb->ops->try_idle(musb, timeout); +} + +static inline int musb_platform_get_vbus_status(struct musb *musb) +{ + if (!musb->ops->vbus_status) + return 0; + + return musb->ops->vbus_status(musb); +} + +static inline int musb_platform_init(struct musb *musb) +{ + if (!musb->ops->init) + return -EINVAL; + + return musb->ops->init(musb); +} + +static inline int musb_platform_exit(struct musb *musb) +{ + if (!musb->ops->exit) + return -EINVAL; + + return musb->ops->exit(musb); +} + +#endif /* __MUSB_CORE_H__ */ diff --git a/drivers/usb/musb/musb_debug.h b/drivers/usb/musb/musb_debug.h new file mode 100644 index 00000000..27ba8f79 --- /dev/null +++ b/drivers/usb/musb/musb_debug.h @@ -0,0 +1,58 @@ +/* + * MUSB OTG driver debug defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __MUSB_LINUX_DEBUG_H__ +#define __MUSB_LINUX_DEBUG_H__ + +#define yprintk(facility, format, args...) \ + do { printk(facility "%s %d: " format , \ + __func__, __LINE__ , ## args); } while (0) +#define WARNING(fmt, args...) yprintk(KERN_WARNING, fmt, ## args) +#define INFO(fmt, args...) yprintk(KERN_INFO, fmt, ## args) +#define ERR(fmt, args...) yprintk(KERN_ERR, fmt, ## args) + +#ifdef CONFIG_DEBUG_FS +int musb_init_debugfs(struct musb *musb); +void musb_exit_debugfs(struct musb *musb); +#else +static inline int musb_init_debugfs(struct musb *musb) +{ + return 0; +} +static inline void musb_exit_debugfs(struct musb *musb) +{ +} +#endif + +#endif /* __MUSB_LINUX_DEBUG_H__ */ diff --git a/drivers/usb/musb/musb_debugfs.c b/drivers/usb/musb/musb_debugfs.c new file mode 100644 index 00000000..4c216790 --- /dev/null +++ b/drivers/usb/musb/musb_debugfs.c @@ -0,0 +1,276 @@ +/* + * MUSB OTG driver debugfs support + * + * Copyright 2010 Nokia Corporation + * Contact: Felipe Balbi + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include + +#include + +#include "musb_core.h" +#include "musb_debug.h" + +struct musb_register_map { + char *name; + unsigned offset; + unsigned size; +}; + +static const struct musb_register_map musb_regmap[] = { + { "FAddr", 0x00, 8 }, + { "Power", 0x01, 8 }, + { "Frame", 0x0c, 16 }, + { "Index", 0x0e, 8 }, + { "Testmode", 0x0f, 8 }, + { "TxMaxPp", 0x10, 16 }, + { "TxCSRp", 0x12, 16 }, + { "RxMaxPp", 0x14, 16 }, + { "RxCSR", 0x16, 16 }, + { "RxCount", 0x18, 16 }, + { "ConfigData", 0x1f, 8 }, + { "DevCtl", 0x60, 8 }, + { "MISC", 0x61, 8 }, + { "TxFIFOsz", 0x62, 8 }, + { "RxFIFOsz", 0x63, 8 }, + { "TxFIFOadd", 0x64, 16 }, + { "RxFIFOadd", 0x66, 16 }, + { "VControl", 0x68, 32 }, + { "HWVers", 0x6C, 16 }, + { "EPInfo", 0x78, 8 }, + { "RAMInfo", 0x79, 8 }, + { "LinkInfo", 0x7A, 8 }, + { "VPLen", 0x7B, 8 }, + { "HS_EOF1", 0x7C, 8 }, + { "FS_EOF1", 0x7D, 8 }, + { "LS_EOF1", 0x7E, 8 }, + { "SOFT_RST", 0x7F, 8 }, + { "DMA_CNTLch0", 0x204, 16 }, + { "DMA_ADDRch0", 0x208, 32 }, + { "DMA_COUNTch0", 0x20C, 32 }, + { "DMA_CNTLch1", 0x214, 16 }, + { "DMA_ADDRch1", 0x218, 32 }, + { "DMA_COUNTch1", 0x21C, 32 }, + { "DMA_CNTLch2", 0x224, 16 }, + { "DMA_ADDRch2", 0x228, 32 }, + { "DMA_COUNTch2", 0x22C, 32 }, + { "DMA_CNTLch3", 0x234, 16 }, + { "DMA_ADDRch3", 0x238, 32 }, + { "DMA_COUNTch3", 0x23C, 32 }, + { "DMA_CNTLch4", 0x244, 16 }, + { "DMA_ADDRch4", 0x248, 32 }, + { "DMA_COUNTch4", 0x24C, 32 }, + { "DMA_CNTLch5", 0x254, 16 }, + { "DMA_ADDRch5", 0x258, 32 }, + { "DMA_COUNTch5", 0x25C, 32 }, + { "DMA_CNTLch6", 0x264, 16 }, + { "DMA_ADDRch6", 0x268, 32 }, + { "DMA_COUNTch6", 0x26C, 32 }, + { "DMA_CNTLch7", 0x274, 16 }, + { "DMA_ADDRch7", 0x278, 32 }, + { "DMA_COUNTch7", 0x27C, 32 }, + { } /* Terminating Entry */ +}; + +static int musb_regdump_show(struct seq_file *s, void *unused) +{ + struct musb *musb = s->private; + unsigned i; + + seq_printf(s, "MUSB (M)HDRC Register Dump\n"); + + for (i = 0; i < ARRAY_SIZE(musb_regmap); i++) { + switch (musb_regmap[i].size) { + case 8: + seq_printf(s, "%-12s: %02x\n", musb_regmap[i].name, + musb_readb(musb->mregs, musb_regmap[i].offset)); + break; + case 16: + seq_printf(s, "%-12s: %04x\n", musb_regmap[i].name, + musb_readw(musb->mregs, musb_regmap[i].offset)); + break; + case 32: + seq_printf(s, "%-12s: %08x\n", musb_regmap[i].name, + musb_readl(musb->mregs, musb_regmap[i].offset)); + break; + } + } + + return 0; +} + +static int musb_regdump_open(struct inode *inode, struct file *file) +{ + return single_open(file, musb_regdump_show, inode->i_private); +} + +static int musb_test_mode_show(struct seq_file *s, void *unused) +{ + struct musb *musb = s->private; + unsigned test; + + test = musb_readb(musb->mregs, MUSB_TESTMODE); + + if (test & MUSB_TEST_FORCE_HOST) + seq_printf(s, "force host\n"); + + if (test & MUSB_TEST_FIFO_ACCESS) + seq_printf(s, "fifo access\n"); + + if (test & MUSB_TEST_FORCE_FS) + seq_printf(s, "force full-speed\n"); + + if (test & MUSB_TEST_FORCE_HS) + seq_printf(s, "force high-speed\n"); + + if (test & MUSB_TEST_PACKET) + seq_printf(s, "test packet\n"); + + if (test & MUSB_TEST_K) + seq_printf(s, "test K\n"); + + if (test & MUSB_TEST_J) + seq_printf(s, "test J\n"); + + if (test & MUSB_TEST_SE0_NAK) + seq_printf(s, "test SE0 NAK\n"); + + return 0; +} + +static const struct file_operations musb_regdump_fops = { + .open = musb_regdump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int musb_test_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, musb_test_mode_show, inode->i_private); +} + +static ssize_t musb_test_mode_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct musb *musb = s->private; + u8 test = 0; + char buf[18]; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "force host", 9)) + test = MUSB_TEST_FORCE_HOST; + + if (!strncmp(buf, "fifo access", 11)) + test = MUSB_TEST_FIFO_ACCESS; + + if (!strncmp(buf, "force full-speed", 15)) + test = MUSB_TEST_FORCE_FS; + + if (!strncmp(buf, "force high-speed", 15)) + test = MUSB_TEST_FORCE_HS; + + if (!strncmp(buf, "test packet", 10)) { + test = MUSB_TEST_PACKET; + musb_load_testpacket(musb); + } + + if (!strncmp(buf, "test K", 6)) + test = MUSB_TEST_K; + + if (!strncmp(buf, "test J", 6)) + test = MUSB_TEST_J; + + if (!strncmp(buf, "test SE0 NAK", 12)) + test = MUSB_TEST_SE0_NAK; + + musb_writeb(musb->mregs, MUSB_TESTMODE, test); + + return count; +} + +static const struct file_operations musb_test_mode_fops = { + .open = musb_test_mode_open, + .write = musb_test_mode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int musb_init_debugfs(struct musb *musb) +{ + struct dentry *root; + struct dentry *file; + int ret; + + root = debugfs_create_dir(dev_name(musb->controller), NULL); + if (!root) { + ret = -ENOMEM; + goto err0; + } + + file = debugfs_create_file("regdump", S_IRUGO, root, musb, + &musb_regdump_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + file = debugfs_create_file("testmode", S_IRUGO | S_IWUSR, + root, musb, &musb_test_mode_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + musb->debugfs_root = root; + + return 0; + +err1: + debugfs_remove_recursive(root); + +err0: + return ret; +} + +void /* __init_or_exit */ musb_exit_debugfs(struct musb *musb) +{ + debugfs_remove_recursive(musb->debugfs_root); +} diff --git a/drivers/usb/musb/musb_dma.h b/drivers/usb/musb/musb_dma.h new file mode 100644 index 00000000..1b6b827b --- /dev/null +++ b/drivers/usb/musb/musb_dma.h @@ -0,0 +1,185 @@ +/* + * MUSB OTG driver DMA controller abstraction + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __MUSB_DMA_H__ +#define __MUSB_DMA_H__ + +struct musb_hw_ep; + +/* + * DMA Controller Abstraction + * + * DMA Controllers are abstracted to allow use of a variety of different + * implementations of DMA, as allowed by the Inventra USB cores. On the + * host side, usbcore sets up the DMA mappings and flushes caches; on the + * peripheral side, the gadget controller driver does. Responsibilities + * of a DMA controller driver include: + * + * - Handling the details of moving multiple USB packets + * in cooperation with the Inventra USB core, including especially + * the correct RX side treatment of short packets and buffer-full + * states (both of which terminate transfers). + * + * - Knowing the correlation between dma channels and the + * Inventra core's local endpoint resources and data direction. + * + * - Maintaining a list of allocated/available channels. + * + * - Updating channel status on interrupts, + * whether shared with the Inventra core or separate. + */ + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +#ifndef CONFIG_MUSB_PIO_ONLY +#define is_dma_capable() (1) +#else +#define is_dma_capable() (0) +#endif + +#ifdef CONFIG_USB_TI_CPPI_DMA +#define is_cppi_enabled() 1 +#else +#define is_cppi_enabled() 0 +#endif + +#ifdef CONFIG_USB_TUSB_OMAP_DMA +#define tusb_dma_omap() 1 +#else +#define tusb_dma_omap() 0 +#endif + +/* Anomaly 05000456 - USB Receive Interrupt Is Not Generated in DMA Mode 1 + * Only allow DMA mode 1 to be used when the USB will actually generate the + * interrupts we expect. + */ +#ifdef CONFIG_BLACKFIN +# undef USE_MODE1 +# if !ANOMALY_05000456 +# define USE_MODE1 +# endif +#endif + +/* + * DMA channel status ... updated by the dma controller driver whenever that + * status changes, and protected by the overall controller spinlock. + */ +enum dma_channel_status { + /* unallocated */ + MUSB_DMA_STATUS_UNKNOWN, + /* allocated ... but not busy, no errors */ + MUSB_DMA_STATUS_FREE, + /* busy ... transactions are active */ + MUSB_DMA_STATUS_BUSY, + /* transaction(s) aborted due to ... dma or memory bus error */ + MUSB_DMA_STATUS_BUS_ABORT, + /* transaction(s) aborted due to ... core error or USB fault */ + MUSB_DMA_STATUS_CORE_ABORT +}; + +struct dma_controller; + +/** + * struct dma_channel - A DMA channel. + * @private_data: channel-private data + * @max_len: the maximum number of bytes the channel can move in one + * transaction (typically representing many USB maximum-sized packets) + * @actual_len: how many bytes have been transferred + * @status: current channel status (updated e.g. on interrupt) + * @desired_mode: true if mode 1 is desired; false if mode 0 is desired + * + * channels are associated with an endpoint for the duration of at least + * one usb transfer. + */ +struct dma_channel { + void *private_data; + /* FIXME not void* private_data, but a dma_controller * */ + size_t max_len; + size_t actual_len; + enum dma_channel_status status; + bool desired_mode; +}; + +/* + * dma_channel_status - return status of dma channel + * @c: the channel + * + * Returns the software's view of the channel status. If that status is BUSY + * then it's possible that the hardware has completed (or aborted) a transfer, + * so the driver needs to update that status. + */ +static inline enum dma_channel_status +dma_channel_status(struct dma_channel *c) +{ + return (is_dma_capable() && c) ? c->status : MUSB_DMA_STATUS_UNKNOWN; +} + +/** + * struct dma_controller - A DMA Controller. + * @start: call this to start a DMA controller; + * return 0 on success, else negative errno + * @stop: call this to stop a DMA controller + * return 0 on success, else negative errno + * @channel_alloc: call this to allocate a DMA channel + * @channel_release: call this to release a DMA channel + * @channel_abort: call this to abort a pending DMA transaction, + * returning it to FREE (but allocated) state + * + * Controllers manage dma channels. + */ +struct dma_controller { + int (*start)(struct dma_controller *); + int (*stop)(struct dma_controller *); + struct dma_channel *(*channel_alloc)(struct dma_controller *, + struct musb_hw_ep *, u8 is_tx); + void (*channel_release)(struct dma_channel *); + int (*channel_program)(struct dma_channel *channel, + u16 maxpacket, u8 mode, + dma_addr_t dma_addr, + u32 length); + int (*channel_abort)(struct dma_channel *); + int (*is_compatible)(struct dma_channel *channel, + u16 maxpacket, + void *buf, u32 length); +}; + +/* called after channel_program(), may indicate a fault */ +extern void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit); + + +extern struct dma_controller *dma_controller_create(struct musb *, void __iomem *); + +extern void dma_controller_destroy(struct dma_controller *); + +#endif /* __MUSB_DMA_H__ */ diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c new file mode 100644 index 00000000..e1b661d0 --- /dev/null +++ b/drivers/usb/musb/musb_dsps.c @@ -0,0 +1,797 @@ +/* + * Texas Instruments DSPS platforms "glue layer" + * + * Copyright (C) 2012, by Texas Instruments + * + * Based on the am35x "glue layer" code. + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + * + * The Inventra Controller Driver for Linux is distributed in + * the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + * musb_dsps.c will be a common file for all the TI DSPS platforms + * such as dm64x, dm36x, dm35x, da8x, am35x and ti81x. + * For now only ti81x is using this and in future davinci.c, am35x.c + * da8xx.c would be merged to this file after testing. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "musb_core.h" + +#ifdef CONFIG_OF +static const struct of_device_id musb_dsps_of_match[]; +#endif + +/** + * avoid using musb_readx()/musb_writex() as glue layer should not be + * dependent on musb core layer symbols. + */ +static inline u8 dsps_readb(const void __iomem *addr, unsigned offset) + { return __raw_readb(addr + offset); } + +static inline u32 dsps_readl(const void __iomem *addr, unsigned offset) + { return __raw_readl(addr + offset); } + +static inline void dsps_writeb(void __iomem *addr, unsigned offset, u8 data) + { __raw_writeb(data, addr + offset); } + +static inline void dsps_writel(void __iomem *addr, unsigned offset, u32 data) + { __raw_writel(data, addr + offset); } + +/** + * DSPS musb wrapper register offset. + * FIXME: This should be expanded to have all the wrapper registers from TI DSPS + * musb ips. + */ +struct dsps_musb_wrapper { + u16 revision; + u16 control; + u16 status; + u16 eoi; + u16 epintr_set; + u16 epintr_clear; + u16 epintr_status; + u16 coreintr_set; + u16 coreintr_clear; + u16 coreintr_status; + u16 phy_utmi; + u16 mode; + + /* bit positions for control */ + unsigned reset:5; + + /* bit positions for interrupt */ + unsigned usb_shift:5; + u32 usb_mask; + u32 usb_bitmap; + unsigned drvvbus:5; + + unsigned txep_shift:5; + u32 txep_mask; + u32 txep_bitmap; + + unsigned rxep_shift:5; + u32 rxep_mask; + u32 rxep_bitmap; + + /* bit positions for phy_utmi */ + unsigned otg_disable:5; + + /* bit positions for mode */ + unsigned iddig:5; + /* miscellaneous stuff */ + u32 musb_core_offset; + u8 poll_seconds; + /* number of musb instances */ + u8 instances; +}; + +/** + * DSPS glue structure. + */ +struct dsps_glue { + struct device *dev; + struct platform_device *musb[2]; /* child musb pdev */ + const struct dsps_musb_wrapper *wrp; /* wrapper register offsets */ + struct timer_list timer[2]; /* otg_workaround timer */ + unsigned long last_timer[2]; /* last timer data for each instance */ + u32 __iomem *usb_ctrl[2]; +}; + +#define DSPS_AM33XX_CONTROL_MODULE_PHYS_0 0x44e10620 +#define DSPS_AM33XX_CONTROL_MODULE_PHYS_1 0x44e10628 + +static const resource_size_t dsps_control_module_phys[] = { + DSPS_AM33XX_CONTROL_MODULE_PHYS_0, + DSPS_AM33XX_CONTROL_MODULE_PHYS_1, +}; + +#define USBPHY_CM_PWRDN (1 << 0) +#define USBPHY_OTG_PWRDN (1 << 1) +#define USBPHY_OTGVDET_EN (1 << 19) +#define USBPHY_OTGSESSEND_EN (1 << 20) + +/** + * musb_dsps_phy_control - phy on/off + * @glue: struct dsps_glue * + * @id: musb instance + * @on: flag for phy to be switched on or off + * + * This is to enable the PHY using usb_ctrl register in system control + * module space. + * + * XXX: This function will be removed once we have a seperate driver for + * control module + */ +static void musb_dsps_phy_control(struct dsps_glue *glue, u8 id, u8 on) +{ + u32 usbphycfg; + + usbphycfg = readl(glue->usb_ctrl[id]); + + if (on) { + usbphycfg &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN); + usbphycfg |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN; + } else { + usbphycfg |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN; + } + + writel(usbphycfg, glue->usb_ctrl[id]); +} +/** + * dsps_musb_enable - enable interrupts + */ +static void dsps_musb_enable(struct musb *musb) +{ + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev->parent); + struct dsps_glue *glue = platform_get_drvdata(pdev); + const struct dsps_musb_wrapper *wrp = glue->wrp; + void __iomem *reg_base = musb->ctrl_base; + u32 epmask, coremask; + + /* Workaround: setup IRQs through both register sets. */ + epmask = ((musb->epmask & wrp->txep_mask) << wrp->txep_shift) | + ((musb->epmask & wrp->rxep_mask) << wrp->rxep_shift); + coremask = (wrp->usb_bitmap & ~MUSB_INTR_SOF); + + dsps_writel(reg_base, wrp->epintr_set, epmask); + dsps_writel(reg_base, wrp->coreintr_set, coremask); + /* Force the DRVVBUS IRQ so we can start polling for ID change. */ + dsps_writel(reg_base, wrp->coreintr_set, + (1 << wrp->drvvbus) << wrp->usb_shift); +} + +/** + * dsps_musb_disable - disable HDRC and flush interrupts + */ +static void dsps_musb_disable(struct musb *musb) +{ + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev->parent); + struct dsps_glue *glue = platform_get_drvdata(pdev); + const struct dsps_musb_wrapper *wrp = glue->wrp; + void __iomem *reg_base = musb->ctrl_base; + + dsps_writel(reg_base, wrp->coreintr_clear, wrp->usb_bitmap); + dsps_writel(reg_base, wrp->epintr_clear, + wrp->txep_bitmap | wrp->rxep_bitmap); + dsps_writeb(musb->mregs, MUSB_DEVCTL, 0); + dsps_writel(reg_base, wrp->eoi, 0); +} + +static void otg_timer(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + void __iomem *mregs = musb->mregs; + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev); + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + const struct dsps_musb_wrapper *wrp = glue->wrp; + u8 devctl; + unsigned long flags; + + /* + * We poll because DSPS IP's won't expose several OTG-critical + * status change events (from the transceiver) otherwise. + */ + devctl = dsps_readb(mregs, MUSB_DEVCTL); + dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl, + usb_otg_state_string(musb->xceiv->state)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_BCON: + devctl &= ~MUSB_DEVCTL_SESSION; + dsps_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + devctl = dsps_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + break; + case OTG_STATE_A_WAIT_VFALL: + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + dsps_writel(musb->ctrl_base, wrp->coreintr_set, + MUSB_INTR_VBUSERROR << wrp->usb_shift); + break; + case OTG_STATE_B_IDLE: + devctl = dsps_readb(mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) + mod_timer(&glue->timer[pdev->id], + jiffies + wrp->poll_seconds * HZ); + else + musb->xceiv->state = OTG_STATE_A_IDLE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +static void dsps_musb_try_idle(struct musb *musb, unsigned long timeout) +{ + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev); + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + + 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->state == OTG_STATE_A_WAIT_BCON)) { + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->state)); + del_timer(&glue->timer[pdev->id]); + glue->last_timer[pdev->id] = jiffies; + return; + } + + if (time_after(glue->last_timer[pdev->id], timeout) && + timer_pending(&glue->timer[pdev->id])) { + dev_dbg(musb->controller, + "Longer idle timer already pending, ignoring...\n"); + return; + } + glue->last_timer[pdev->id] = timeout; + + dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n", + usb_otg_state_string(musb->xceiv->state), + jiffies_to_msecs(timeout - jiffies)); + mod_timer(&glue->timer[pdev->id], timeout); +} + +static irqreturn_t dsps_interrupt(int irq, void *hci) +{ + struct musb *musb = hci; + void __iomem *reg_base = musb->ctrl_base; + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev); + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + const struct dsps_musb_wrapper *wrp = glue->wrp; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + u32 epintr, usbintr; + + spin_lock_irqsave(&musb->lock, flags); + + /* Get endpoint interrupts */ + epintr = dsps_readl(reg_base, wrp->epintr_status); + musb->int_rx = (epintr & wrp->rxep_bitmap) >> wrp->rxep_shift; + musb->int_tx = (epintr & wrp->txep_bitmap) >> wrp->txep_shift; + + if (epintr) + dsps_writel(reg_base, wrp->epintr_status, epintr); + + /* Get usb core interrupts */ + usbintr = dsps_readl(reg_base, wrp->coreintr_status); + if (!usbintr && !epintr) + goto eoi; + + musb->int_usb = (usbintr & wrp->usb_bitmap) >> wrp->usb_shift; + if (usbintr) + dsps_writel(reg_base, wrp->coreintr_status, usbintr); + + dev_dbg(musb->controller, "usbintr (%x) epintr(%x)\n", + usbintr, epintr); + /* + * DRVVBUS IRQs are the only proxy we have (a very poor one!) for + * DSPS IP's missing ID change IRQ. We need an ID change IRQ to + * switch appropriately between halves of the OTG state machine. + * Managing DEVCTL.SESSION per Mentor docs requires that we know its + * value but DEVCTL.BDEVICE is invalid without DEVCTL.SESSION set. + * Also, DRVVBUS pulses for SRP (but not at 5V) ... + */ + if (is_host_active(musb) && usbintr & MUSB_INTR_BABBLE) + pr_info("CAUTION: musb: Babble Interrupt Occurred\n"); + + if (usbintr & ((1 << wrp->drvvbus) << wrp->usb_shift)) { + int drvvbus = dsps_readl(reg_base, wrp->status); + void __iomem *mregs = musb->mregs; + u8 devctl = dsps_readb(mregs, MUSB_DEVCTL); + int err; + + err = musb->int_usb & MUSB_INTR_VBUSERROR; + if (err) { + /* + * The Mentor core doesn't debounce VBUS as needed + * to cope with device connect current spikes. This + * means it's not uncommon for bus-powered devices + * to get VBUS errors during enumeration. + * + * This is a workaround, but newer RTL from Mentor + * seems to allow a better one: "re"-starting sessions + * without waiting for VBUS to stop registering in + * devctl. + */ + musb->int_usb &= ~MUSB_INTR_VBUSERROR; + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; + mod_timer(&glue->timer[pdev->id], + jiffies + wrp->poll_seconds * HZ); + WARNING("VBUS error workaround (delay coming)\n"); + } else if (drvvbus) { + musb->is_active = 1; + MUSB_HST_MODE(musb); + musb->xceiv->otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + del_timer(&glue->timer[pdev->id]); + } else { + musb->is_active = 0; + MUSB_DEV_MODE(musb); + musb->xceiv->otg->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; + } + + /* NOTE: this must complete power-on within 100 ms. */ + dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n", + drvvbus ? "on" : "off", + usb_otg_state_string(musb->xceiv->state), + err ? " ERROR" : "", + devctl); + ret = IRQ_HANDLED; + } + + if (musb->int_tx || musb->int_rx || musb->int_usb) + ret |= musb_interrupt(musb); + + eoi: + /* EOI needs to be written for the IRQ to be re-asserted. */ + if (ret == IRQ_HANDLED || epintr || usbintr) + dsps_writel(reg_base, wrp->eoi, 1); + + /* Poll for ID change */ + if (musb->xceiv->state == OTG_STATE_B_IDLE) + mod_timer(&glue->timer[pdev->id], + jiffies + wrp->poll_seconds * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static int dsps_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev); + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + const struct dsps_musb_wrapper *wrp = glue->wrp; + void __iomem *reg_base = musb->ctrl_base; + u32 rev, val; + int status; + + /* mentor core register starts at offset of 0x400 from musb base */ + musb->mregs += wrp->musb_core_offset; + + /* NOP driver needs change if supporting dual instance */ + usb_nop_xceiv_register(); + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) + return -EPROBE_DEFER; + + /* Returns zero if e.g. not clocked */ + rev = dsps_readl(reg_base, wrp->revision); + if (!rev) { + status = -ENODEV; + goto err0; + } + + setup_timer(&glue->timer[pdev->id], otg_timer, (unsigned long) musb); + + /* Reset the musb */ + dsps_writel(reg_base, wrp->control, (1 << wrp->reset)); + + /* Start the on-chip PHY and its PLL. */ + musb_dsps_phy_control(glue, pdev->id, 1); + + musb->isr = dsps_interrupt; + + /* reset the otgdisable bit, needed for host mode to work */ + val = dsps_readl(reg_base, wrp->phy_utmi); + val &= ~(1 << wrp->otg_disable); + dsps_writel(musb->ctrl_base, wrp->phy_utmi, val); + + /* clear level interrupt */ + dsps_writel(reg_base, wrp->eoi, 0); + + return 0; +err0: + usb_put_phy(musb->xceiv); + usb_nop_xceiv_unregister(); + return status; +} + +static int dsps_musb_exit(struct musb *musb) +{ + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev); + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + + del_timer_sync(&glue->timer[pdev->id]); + + /* Shutdown the on-chip PHY and its PLL. */ + musb_dsps_phy_control(glue, pdev->id, 0); + + /* NOP driver needs change if supporting dual instance */ + usb_put_phy(musb->xceiv); + usb_nop_xceiv_unregister(); + + return 0; +} + +static struct musb_platform_ops dsps_ops = { + .init = dsps_musb_init, + .exit = dsps_musb_exit, + + .enable = dsps_musb_enable, + .disable = dsps_musb_disable, + + .try_idle = dsps_musb_try_idle, +}; + +static u64 musb_dmamask = DMA_BIT_MASK(32); + +static int dsps_create_musb_pdev(struct dsps_glue *glue, u8 id) +{ + struct device *dev = glue->dev; + struct platform_device *pdev = to_platform_device(dev); + struct musb_hdrc_platform_data *pdata = dev->platform_data; + struct device_node *np = pdev->dev.of_node; + struct musb_hdrc_config *config; + struct platform_device *musb; + struct resource *res; + struct resource resources[2]; + char res_name[11]; + int ret; + + resources[0].start = dsps_control_module_phys[id]; + resources[0].end = resources[0].start + SZ_4 - 1; + resources[0].flags = IORESOURCE_MEM; + + glue->usb_ctrl[id] = devm_ioremap_resource(&pdev->dev, resources); + if (IS_ERR(glue->usb_ctrl[id])) { + ret = PTR_ERR(glue->usb_ctrl[id]); + goto err0; + } + + /* first resource is for usbss, so start index from 1 */ + res = platform_get_resource(pdev, IORESOURCE_MEM, id + 1); + if (!res) { + dev_err(dev, "failed to get memory for instance %d\n", id); + ret = -ENODEV; + goto err0; + } + res->parent = NULL; + resources[0] = *res; + + /* first resource is for usbss, so start index from 1 */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, id + 1); + if (!res) { + dev_err(dev, "failed to get irq for instance %d\n", id); + ret = -ENODEV; + goto err0; + } + res->parent = NULL; + resources[1] = *res; + resources[1].name = "mc"; + + /* allocate the child platform device */ + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(dev, "failed to allocate musb device\n"); + ret = -ENOMEM; + goto err0; + } + + musb->dev.parent = dev; + musb->dev.dma_mask = &musb_dmamask; + musb->dev.coherent_dma_mask = musb_dmamask; + + glue->musb[id] = musb; + + ret = platform_device_add_resources(musb, resources, 2); + if (ret) { + dev_err(dev, "failed to add resources\n"); + goto err2; + } + + if (np) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, + "failed to allocate musb platfrom data\n"); + ret = -ENOMEM; + goto err2; + } + + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!config) { + dev_err(&pdev->dev, + "failed to allocate musb hdrc config\n"); + ret = -ENOMEM; + goto err2; + } + + of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps); + of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits); + snprintf(res_name, sizeof(res_name), "port%d-mode", id); + of_property_read_u32(np, res_name, (u32 *)&pdata->mode); + of_property_read_u32(np, "power", (u32 *)&pdata->power); + config->multipoint = of_property_read_bool(np, "multipoint"); + + pdata->config = config; + } + + pdata->platform_ops = &dsps_ops; + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(dev, "failed to add platform_data\n"); + goto err2; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(dev, "failed to register musb device\n"); + goto err2; + } + + return 0; + +err2: + platform_device_put(musb); +err0: + return ret; +} + +static int dsps_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct dsps_musb_wrapper *wrp; + struct dsps_glue *glue; + struct resource *iomem; + int ret, i; + + match = of_match_node(musb_dsps_of_match, pdev->dev.of_node); + if (!match) { + dev_err(&pdev->dev, "fail to get matching of_match struct\n"); + ret = -EINVAL; + goto err0; + } + wrp = match->data; + + /* allocate glue */ + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "unable to allocate glue memory\n"); + ret = -ENOMEM; + goto err0; + } + + /* get memory resource */ + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iomem) { + dev_err(&pdev->dev, "failed to get usbss mem resourse\n"); + ret = -ENODEV; + goto err1; + } + + glue->dev = &pdev->dev; + + glue->wrp = kmemdup(wrp, sizeof(*wrp), GFP_KERNEL); + if (!glue->wrp) { + dev_err(&pdev->dev, "failed to duplicate wrapper struct memory\n"); + ret = -ENOMEM; + goto err1; + } + platform_set_drvdata(pdev, glue); + + /* enable the usbss clocks */ + pm_runtime_enable(&pdev->dev); + + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "pm_runtime_get_sync FAILED"); + goto err2; + } + + /* create the child platform device for all instances of musb */ + for (i = 0; i < wrp->instances ; i++) { + ret = dsps_create_musb_pdev(glue, i); + if (ret != 0) { + dev_err(&pdev->dev, "failed to create child pdev\n"); + /* release resources of previously created instances */ + for (i--; i >= 0 ; i--) + platform_device_unregister(glue->musb[i]); + goto err3; + } + } + + return 0; + +err3: + pm_runtime_put(&pdev->dev); +err2: + pm_runtime_disable(&pdev->dev); + kfree(glue->wrp); +err1: + kfree(glue); +err0: + return ret; +} +static int dsps_remove(struct platform_device *pdev) +{ + struct dsps_glue *glue = platform_get_drvdata(pdev); + const struct dsps_musb_wrapper *wrp = glue->wrp; + int i; + + /* delete the child platform device */ + for (i = 0; i < wrp->instances ; i++) + platform_device_unregister(glue->musb[i]); + + /* disable usbss clocks */ + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(glue->wrp); + kfree(glue); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dsps_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev->parent); + struct dsps_glue *glue = platform_get_drvdata(pdev); + const struct dsps_musb_wrapper *wrp = glue->wrp; + int i; + + for (i = 0; i < wrp->instances; i++) + musb_dsps_phy_control(glue, i, 0); + + return 0; +} + +static int dsps_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev->parent); + struct dsps_glue *glue = platform_get_drvdata(pdev); + const struct dsps_musb_wrapper *wrp = glue->wrp; + int i; + + for (i = 0; i < wrp->instances; i++) + musb_dsps_phy_control(glue, i, 1); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(dsps_pm_ops, dsps_suspend, dsps_resume); + +static const struct dsps_musb_wrapper ti81xx_driver_data = { + .revision = 0x00, + .control = 0x14, + .status = 0x18, + .eoi = 0x24, + .epintr_set = 0x38, + .epintr_clear = 0x40, + .epintr_status = 0x30, + .coreintr_set = 0x3c, + .coreintr_clear = 0x44, + .coreintr_status = 0x34, + .phy_utmi = 0xe0, + .mode = 0xe8, + .reset = 0, + .otg_disable = 21, + .iddig = 8, + .usb_shift = 0, + .usb_mask = 0x1ff, + .usb_bitmap = (0x1ff << 0), + .drvvbus = 8, + .txep_shift = 0, + .txep_mask = 0xffff, + .txep_bitmap = (0xffff << 0), + .rxep_shift = 16, + .rxep_mask = 0xfffe, + .rxep_bitmap = (0xfffe << 16), + .musb_core_offset = 0x400, + .poll_seconds = 2, + .instances = 1, +}; + +static const struct platform_device_id musb_dsps_id_table[] = { + { + .name = "musb-ti81xx", + .driver_data = (kernel_ulong_t) &ti81xx_driver_data, + }, + { }, /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(platform, musb_dsps_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id musb_dsps_of_match[] = { + { .compatible = "ti,musb-am33xx", + .data = (void *) &ti81xx_driver_data, }, + { }, +}; +MODULE_DEVICE_TABLE(of, musb_dsps_of_match); +#endif + +static struct platform_driver dsps_usbss_driver = { + .probe = dsps_probe, + .remove = dsps_remove, + .driver = { + .name = "musb-dsps", + .pm = &dsps_pm_ops, + .of_match_table = of_match_ptr(musb_dsps_of_match), + }, + .id_table = musb_dsps_id_table, +}; + +MODULE_DESCRIPTION("TI DSPS MUSB Glue Layer"); +MODULE_AUTHOR("Ravi B "); +MODULE_AUTHOR("Ajay Kumar Gupta "); +MODULE_LICENSE("GPL v2"); + +static int __init dsps_init(void) +{ + return platform_driver_register(&dsps_usbss_driver); +} +subsys_initcall(dsps_init); + +static void __exit dsps_exit(void) +{ + platform_driver_unregister(&dsps_usbss_driver); +} +module_exit(dsps_exit); diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c new file mode 100644 index 00000000..ba709234 --- /dev/null +++ b/drivers/usb/musb/musb_gadget.c @@ -0,0 +1,2118 @@ +/* + * MUSB OTG driver peripheral support + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2009 MontaVista Software, Inc. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" + + +/* ----------------------------------------------------------------------- */ + +#define is_buffer_mapped(req) (is_dma_capable() && \ + (req->map_state != UN_MAPPED)) + +/* Maps the buffer to dma */ + +static inline void map_dma_buffer(struct musb_request *request, + struct musb *musb, struct musb_ep *musb_ep) +{ + int compatible = true; + struct dma_controller *dma = musb->dma_controller; + + request->map_state = UN_MAPPED; + + if (!is_dma_capable() || !musb_ep->dma) + return; + + /* Check if DMA engine can handle this request. + * DMA code must reject the USB request explicitly. + * Default behaviour is to map the request. + */ + if (dma->is_compatible) + compatible = dma->is_compatible(musb_ep->dma, + musb_ep->packet_sz, request->request.buf, + request->request.length); + if (!compatible) + return; + + if (request->request.dma == DMA_ADDR_INVALID) { + request->request.dma = dma_map_single( + musb->controller, + request->request.buf, + request->request.length, + request->tx + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + request->map_state = MUSB_MAPPED; + } else { + dma_sync_single_for_device(musb->controller, + request->request.dma, + request->request.length, + request->tx + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + request->map_state = PRE_MAPPED; + } +} + +/* Unmap the buffer from dma and maps it back to cpu */ +static inline void unmap_dma_buffer(struct musb_request *request, + struct musb *musb) +{ + struct musb_ep *musb_ep = request->ep; + + if (!is_buffer_mapped(request) || !musb_ep->dma) + return; + + if (request->request.dma == DMA_ADDR_INVALID) { + dev_vdbg(musb->controller, + "not unmapping a never mapped buffer\n"); + return; + } + if (request->map_state == MUSB_MAPPED) { + dma_unmap_single(musb->controller, + request->request.dma, + request->request.length, + request->tx + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + request->request.dma = DMA_ADDR_INVALID; + } else { /* PRE_MAPPED */ + dma_sync_single_for_cpu(musb->controller, + request->request.dma, + request->request.length, + request->tx + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + } + request->map_state = UN_MAPPED; +} + +/* + * Immediately complete a request. + * + * @param request the request to complete + * @param status the status to complete the request with + * Context: controller locked, IRQs blocked. + */ +void musb_g_giveback( + struct musb_ep *ep, + struct usb_request *request, + int status) +__releases(ep->musb->lock) +__acquires(ep->musb->lock) +{ + struct musb_request *req; + struct musb *musb; + int busy = ep->busy; + + req = to_musb_request(request); + + list_del(&req->list); + if (req->request.status == -EINPROGRESS) + req->request.status = status; + musb = req->musb; + + ep->busy = 1; + spin_unlock(&musb->lock); + + if (!dma_mapping_error(&musb->g.dev, request->dma)) + unmap_dma_buffer(req, musb); + + if (request->status == 0) + dev_dbg(musb->controller, "%s done request %p, %d/%d\n", + ep->end_point.name, request, + req->request.actual, req->request.length); + else + dev_dbg(musb->controller, "%s request %p, %d/%d fault %d\n", + ep->end_point.name, request, + req->request.actual, req->request.length, + request->status); + req->request.complete(&req->ep->end_point, &req->request); + spin_lock(&musb->lock); + ep->busy = busy; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Abort requests queued to an endpoint using the status. Synchronous. + * caller locked controller and blocked irqs, and selected this ep. + */ +static void nuke(struct musb_ep *ep, const int status) +{ + struct musb *musb = ep->musb; + struct musb_request *req = NULL; + void __iomem *epio = ep->musb->endpoints[ep->current_epnum].regs; + + ep->busy = 1; + + if (is_dma_capable() && ep->dma) { + struct dma_controller *c = ep->musb->dma_controller; + int value; + + if (ep->is_in) { + /* + * The programming guide says that we must not clear + * the DMAMODE bit before DMAENAB, so we only + * clear it in the second write... + */ + musb_writew(epio, MUSB_TXCSR, + MUSB_TXCSR_DMAMODE | MUSB_TXCSR_FLUSHFIFO); + musb_writew(epio, MUSB_TXCSR, + 0 | MUSB_TXCSR_FLUSHFIFO); + } else { + musb_writew(epio, MUSB_RXCSR, + 0 | MUSB_RXCSR_FLUSHFIFO); + musb_writew(epio, MUSB_RXCSR, + 0 | MUSB_RXCSR_FLUSHFIFO); + } + + value = c->channel_abort(ep->dma); + dev_dbg(musb->controller, "%s: abort DMA --> %d\n", + ep->name, value); + c->channel_release(ep->dma); + ep->dma = NULL; + } + + while (!list_empty(&ep->req_list)) { + req = list_first_entry(&ep->req_list, struct musb_request, list); + musb_g_giveback(ep, &req->request, status); + } +} + +/* ----------------------------------------------------------------------- */ + +/* Data transfers - pure PIO, pure DMA, or mixed mode */ + +/* + * This assumes the separate CPPI engine is responding to DMA requests + * from the usb core ... sequenced a bit differently from mentor dma. + */ + +static inline int max_ep_writesize(struct musb *musb, struct musb_ep *ep) +{ + if (can_bulk_split(musb, ep->type)) + return ep->hw_ep->max_packet_sz_tx; + else + return ep->packet_sz; +} + +/* + * An endpoint is transmitting data. This can be called either from + * the IRQ routine or from ep.queue() to kickstart a request on an + * endpoint. + * + * Context: controller locked, IRQs blocked, endpoint selected + */ +static void txstate(struct musb *musb, struct musb_request *req) +{ + u8 epnum = req->epnum; + struct musb_ep *musb_ep; + void __iomem *epio = musb->endpoints[epnum].regs; + struct usb_request *request; + u16 fifo_count = 0, csr; + int use_dma = 0; + + musb_ep = req->ep; + + /* Check if EP is disabled */ + if (!musb_ep->desc) { + dev_dbg(musb->controller, "ep:%s disabled - ignore request\n", + musb_ep->end_point.name); + return; + } + + /* we shouldn't get here while DMA is active ... but we do ... */ + if (dma_channel_status(musb_ep->dma) == MUSB_DMA_STATUS_BUSY) { + dev_dbg(musb->controller, "dma pending...\n"); + return; + } + + /* read TXCSR before */ + csr = musb_readw(epio, MUSB_TXCSR); + + request = &req->request; + fifo_count = min(max_ep_writesize(musb, musb_ep), + (int)(request->length - request->actual)); + + if (csr & MUSB_TXCSR_TXPKTRDY) { + dev_dbg(musb->controller, "%s old packet still ready , txcsr %03x\n", + musb_ep->end_point.name, csr); + return; + } + + if (csr & MUSB_TXCSR_P_SENDSTALL) { + dev_dbg(musb->controller, "%s stalling, txcsr %03x\n", + musb_ep->end_point.name, csr); + return; + } + + dev_dbg(musb->controller, "hw_ep%d, maxpacket %d, fifo count %d, txcsr %03x\n", + epnum, musb_ep->packet_sz, fifo_count, + csr); + +#ifndef CONFIG_MUSB_PIO_ONLY + if (is_buffer_mapped(req)) { + struct dma_controller *c = musb->dma_controller; + size_t request_size; + + /* setup DMA, then program endpoint CSR */ + request_size = min_t(size_t, request->length - request->actual, + musb_ep->dma->max_len); + + use_dma = (request->dma != DMA_ADDR_INVALID && request_size); + + /* MUSB_TXCSR_P_ISO is still set correctly */ + +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) + { + if (request_size < musb_ep->packet_sz) + musb_ep->dma->desired_mode = 0; + else + musb_ep->dma->desired_mode = 1; + + use_dma = use_dma && c->channel_program( + musb_ep->dma, musb_ep->packet_sz, + musb_ep->dma->desired_mode, + request->dma + request->actual, request_size); + if (use_dma) { + if (musb_ep->dma->desired_mode == 0) { + /* + * We must not clear the DMAMODE bit + * before the DMAENAB bit -- and the + * latter doesn't always get cleared + * before we get here... + */ + csr &= ~(MUSB_TXCSR_AUTOSET + | MUSB_TXCSR_DMAENAB); + musb_writew(epio, MUSB_TXCSR, csr + | MUSB_TXCSR_P_WZC_BITS); + csr &= ~MUSB_TXCSR_DMAMODE; + csr |= (MUSB_TXCSR_DMAENAB | + MUSB_TXCSR_MODE); + /* against programming guide */ + } else { + csr |= (MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_DMAMODE + | MUSB_TXCSR_MODE); + /* + * Enable Autoset according to table + * below + * bulk_split hb_mult Autoset_Enable + * 0 0 Yes(Normal) + * 0 >0 No(High BW ISO) + * 1 0 Yes(HS bulk) + * 1 >0 Yes(FS bulk) + */ + if (!musb_ep->hb_mult || + (musb_ep->hb_mult && + can_bulk_split(musb, + musb_ep->type))) + csr |= MUSB_TXCSR_AUTOSET; + } + csr &= ~MUSB_TXCSR_P_UNDERRUN; + + musb_writew(epio, MUSB_TXCSR, csr); + } + } + +#elif defined(CONFIG_USB_TI_CPPI_DMA) + /* program endpoint CSR first, then setup DMA */ + csr &= ~(MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_TXPKTRDY); + csr |= MUSB_TXCSR_DMAENAB | MUSB_TXCSR_DMAMODE | + MUSB_TXCSR_MODE; + musb_writew(epio, MUSB_TXCSR, + (MUSB_TXCSR_P_WZC_BITS & ~MUSB_TXCSR_P_UNDERRUN) + | csr); + + /* ensure writebuffer is empty */ + csr = musb_readw(epio, MUSB_TXCSR); + + /* NOTE host side sets DMAENAB later than this; both are + * OK since the transfer dma glue (between CPPI and Mentor + * fifos) just tells CPPI it could start. Data only moves + * to the USB TX fifo when both fifos are ready. + */ + + /* "mode" is irrelevant here; handle terminating ZLPs like + * PIO does, since the hardware RNDIS mode seems unreliable + * except for the last-packet-is-already-short case. + */ + use_dma = use_dma && c->channel_program( + musb_ep->dma, musb_ep->packet_sz, + 0, + request->dma + request->actual, + request_size); + if (!use_dma) { + c->channel_release(musb_ep->dma); + musb_ep->dma = NULL; + csr &= ~MUSB_TXCSR_DMAENAB; + musb_writew(epio, MUSB_TXCSR, csr); + /* invariant: prequest->buf is non-null */ + } +#elif defined(CONFIG_USB_TUSB_OMAP_DMA) + use_dma = use_dma && c->channel_program( + musb_ep->dma, musb_ep->packet_sz, + request->zero, + request->dma + request->actual, + request_size); +#endif + } +#endif + + if (!use_dma) { + /* + * Unmap the dma buffer back to cpu if dma channel + * programming fails + */ + unmap_dma_buffer(req, musb); + + musb_write_fifo(musb_ep->hw_ep, fifo_count, + (u8 *) (request->buf + request->actual)); + request->actual += fifo_count; + csr |= MUSB_TXCSR_TXPKTRDY; + csr &= ~MUSB_TXCSR_P_UNDERRUN; + musb_writew(epio, MUSB_TXCSR, csr); + } + + /* host may already have the data when this message shows... */ + dev_dbg(musb->controller, "%s TX/IN %s len %d/%d, txcsr %04x, fifo %d/%d\n", + musb_ep->end_point.name, use_dma ? "dma" : "pio", + request->actual, request->length, + musb_readw(epio, MUSB_TXCSR), + fifo_count, + musb_readw(epio, MUSB_TXMAXP)); +} + +/* + * FIFO state update (e.g. data ready). + * Called from IRQ, with controller locked. + */ +void musb_g_tx(struct musb *musb, u8 epnum) +{ + u16 csr; + struct musb_request *req; + struct usb_request *request; + u8 __iomem *mbase = musb->mregs; + struct musb_ep *musb_ep = &musb->endpoints[epnum].ep_in; + void __iomem *epio = musb->endpoints[epnum].regs; + struct dma_channel *dma; + + musb_ep_select(mbase, epnum); + req = next_request(musb_ep); + request = &req->request; + + csr = musb_readw(epio, MUSB_TXCSR); + dev_dbg(musb->controller, "<== %s, txcsr %04x\n", musb_ep->end_point.name, csr); + + dma = is_dma_capable() ? musb_ep->dma : NULL; + + /* + * REVISIT: for high bandwidth, MUSB_TXCSR_P_INCOMPTX + * probably rates reporting as a host error. + */ + if (csr & MUSB_TXCSR_P_SENTSTALL) { + csr |= MUSB_TXCSR_P_WZC_BITS; + csr &= ~MUSB_TXCSR_P_SENTSTALL; + musb_writew(epio, MUSB_TXCSR, csr); + return; + } + + if (csr & MUSB_TXCSR_P_UNDERRUN) { + /* We NAKed, no big deal... little reason to care. */ + csr |= MUSB_TXCSR_P_WZC_BITS; + csr &= ~(MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_TXPKTRDY); + musb_writew(epio, MUSB_TXCSR, csr); + dev_vdbg(musb->controller, "underrun on ep%d, req %p\n", + epnum, request); + } + + if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) { + /* + * SHOULD NOT HAPPEN... has with CPPI though, after + * changing SENDSTALL (and other cases); harmless? + */ + dev_dbg(musb->controller, "%s dma still busy?\n", musb_ep->end_point.name); + return; + } + + if (request) { + u8 is_dma = 0; + + if (dma && (csr & MUSB_TXCSR_DMAENAB)) { + is_dma = 1; + csr |= MUSB_TXCSR_P_WZC_BITS; + csr &= ~(MUSB_TXCSR_DMAENAB | MUSB_TXCSR_P_UNDERRUN | + MUSB_TXCSR_TXPKTRDY | MUSB_TXCSR_AUTOSET); + musb_writew(epio, MUSB_TXCSR, csr); + /* Ensure writebuffer is empty. */ + csr = musb_readw(epio, MUSB_TXCSR); + request->actual += musb_ep->dma->actual_len; + dev_dbg(musb->controller, "TXCSR%d %04x, DMA off, len %zu, req %p\n", + epnum, csr, musb_ep->dma->actual_len, request); + } + + /* + * First, maybe a terminating short packet. Some DMA + * engines might handle this by themselves. + */ + if ((request->zero && request->length + && (request->length % musb_ep->packet_sz == 0) + && (request->actual == request->length)) +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) + || (is_dma && (!dma->desired_mode || + (request->actual & + (musb_ep->packet_sz - 1)))) +#endif + ) { + /* + * On DMA completion, FIFO may not be + * available yet... + */ + if (csr & MUSB_TXCSR_TXPKTRDY) + return; + + dev_dbg(musb->controller, "sending zero pkt\n"); + musb_writew(epio, MUSB_TXCSR, MUSB_TXCSR_MODE + | MUSB_TXCSR_TXPKTRDY); + request->zero = 0; + } + + if (request->actual == request->length) { + musb_g_giveback(musb_ep, request, 0); + /* + * In the giveback function the MUSB lock is + * released and acquired after sometime. During + * this time period the INDEX register could get + * changed by the gadget_queue function especially + * on SMP systems. Reselect the INDEX to be sure + * we are reading/modifying the right registers + */ + musb_ep_select(mbase, epnum); + req = musb_ep->desc ? next_request(musb_ep) : NULL; + if (!req) { + dev_dbg(musb->controller, "%s idle now\n", + musb_ep->end_point.name); + return; + } + } + + txstate(musb, req); + } +} + +/* ------------------------------------------------------------ */ + +/* + * Context: controller locked, IRQs blocked, endpoint selected + */ +static void rxstate(struct musb *musb, struct musb_request *req) +{ + const u8 epnum = req->epnum; + struct usb_request *request = &req->request; + struct musb_ep *musb_ep; + void __iomem *epio = musb->endpoints[epnum].regs; + unsigned len = 0; + u16 fifo_count; + u16 csr = musb_readw(epio, MUSB_RXCSR); + struct musb_hw_ep *hw_ep = &musb->endpoints[epnum]; + u8 use_mode_1; + + if (hw_ep->is_shared_fifo) + musb_ep = &hw_ep->ep_in; + else + musb_ep = &hw_ep->ep_out; + + fifo_count = musb_ep->packet_sz; + + /* Check if EP is disabled */ + if (!musb_ep->desc) { + dev_dbg(musb->controller, "ep:%s disabled - ignore request\n", + musb_ep->end_point.name); + return; + } + + /* We shouldn't get here while DMA is active, but we do... */ + if (dma_channel_status(musb_ep->dma) == MUSB_DMA_STATUS_BUSY) { + dev_dbg(musb->controller, "DMA pending...\n"); + return; + } + + if (csr & MUSB_RXCSR_P_SENDSTALL) { + dev_dbg(musb->controller, "%s stalling, RXCSR %04x\n", + musb_ep->end_point.name, csr); + return; + } + + if (is_cppi_enabled() && is_buffer_mapped(req)) { + struct dma_controller *c = musb->dma_controller; + struct dma_channel *channel = musb_ep->dma; + + /* NOTE: CPPI won't actually stop advancing the DMA + * queue after short packet transfers, so this is almost + * always going to run as IRQ-per-packet DMA so that + * faults will be handled correctly. + */ + if (c->channel_program(channel, + musb_ep->packet_sz, + !request->short_not_ok, + request->dma + request->actual, + request->length - request->actual)) { + + /* make sure that if an rxpkt arrived after the irq, + * the cppi engine will be ready to take it as soon + * as DMA is enabled + */ + csr &= ~(MUSB_RXCSR_AUTOCLEAR + | MUSB_RXCSR_DMAMODE); + csr |= MUSB_RXCSR_DMAENAB | MUSB_RXCSR_P_WZC_BITS; + musb_writew(epio, MUSB_RXCSR, csr); + return; + } + } + + if (csr & MUSB_RXCSR_RXPKTRDY) { + fifo_count = musb_readw(epio, MUSB_RXCOUNT); + + /* + * Enable Mode 1 on RX transfers only when short_not_ok flag + * is set. Currently short_not_ok flag is set only from + * file_storage and f_mass_storage drivers + */ + + if (request->short_not_ok && fifo_count == musb_ep->packet_sz) + use_mode_1 = 1; + else + use_mode_1 = 0; + + if (request->actual < request->length) { +#ifdef CONFIG_USB_INVENTRA_DMA + if (is_buffer_mapped(req)) { + struct dma_controller *c; + struct dma_channel *channel; + int use_dma = 0; + unsigned int transfer_size; + + c = musb->dma_controller; + channel = musb_ep->dma; + + /* We use DMA Req mode 0 in rx_csr, and DMA controller operates in + * mode 0 only. So we do not get endpoint interrupts due to DMA + * completion. We only get interrupts from DMA controller. + * + * We could operate in DMA mode 1 if we knew the size of the tranfer + * in advance. For mass storage class, request->length = what the host + * sends, so that'd work. But for pretty much everything else, + * request->length is routinely more than what the host sends. For + * most these gadgets, end of is signified either by a short packet, + * or filling the last byte of the buffer. (Sending extra data in + * that last pckate should trigger an overflow fault.) But in mode 1, + * we don't get DMA completion interrupt for short packets. + * + * Theoretically, we could enable DMAReq irq (MUSB_RXCSR_DMAMODE = 1), + * to get endpoint interrupt on every DMA req, but that didn't seem + * to work reliably. + * + * REVISIT an updated g_file_storage can set req->short_not_ok, which + * then becomes usable as a runtime "use mode 1" hint... + */ + + /* Experimental: Mode1 works with mass storage use cases */ + if (use_mode_1) { + csr |= MUSB_RXCSR_AUTOCLEAR; + musb_writew(epio, MUSB_RXCSR, csr); + csr |= MUSB_RXCSR_DMAENAB; + musb_writew(epio, MUSB_RXCSR, csr); + + /* + * this special sequence (enabling and then + * disabling MUSB_RXCSR_DMAMODE) is required + * to get DMAReq to activate + */ + musb_writew(epio, MUSB_RXCSR, + csr | MUSB_RXCSR_DMAMODE); + musb_writew(epio, MUSB_RXCSR, csr); + + transfer_size = min_t(unsigned int, + request->length - + request->actual, + channel->max_len); + musb_ep->dma->desired_mode = 1; + } else { + if (!musb_ep->hb_mult && + musb_ep->hw_ep->rx_double_buffered) + csr |= MUSB_RXCSR_AUTOCLEAR; + csr |= MUSB_RXCSR_DMAENAB; + musb_writew(epio, MUSB_RXCSR, csr); + + transfer_size = min(request->length - request->actual, + (unsigned)fifo_count); + musb_ep->dma->desired_mode = 0; + } + + use_dma = c->channel_program( + channel, + musb_ep->packet_sz, + channel->desired_mode, + request->dma + + request->actual, + transfer_size); + + if (use_dma) + return; + } +#elif defined(CONFIG_USB_UX500_DMA) + if ((is_buffer_mapped(req)) && + (request->actual < request->length)) { + + struct dma_controller *c; + struct dma_channel *channel; + unsigned int transfer_size = 0; + + c = musb->dma_controller; + channel = musb_ep->dma; + + /* In case first packet is short */ + if (fifo_count < musb_ep->packet_sz) + transfer_size = fifo_count; + else if (request->short_not_ok) + transfer_size = min_t(unsigned int, + request->length - + request->actual, + channel->max_len); + else + transfer_size = min_t(unsigned int, + request->length - + request->actual, + (unsigned)fifo_count); + + csr &= ~MUSB_RXCSR_DMAMODE; + csr |= (MUSB_RXCSR_DMAENAB | + MUSB_RXCSR_AUTOCLEAR); + + musb_writew(epio, MUSB_RXCSR, csr); + + if (transfer_size <= musb_ep->packet_sz) { + musb_ep->dma->desired_mode = 0; + } else { + musb_ep->dma->desired_mode = 1; + /* Mode must be set after DMAENAB */ + csr |= MUSB_RXCSR_DMAMODE; + musb_writew(epio, MUSB_RXCSR, csr); + } + + if (c->channel_program(channel, + musb_ep->packet_sz, + channel->desired_mode, + request->dma + + request->actual, + transfer_size)) + + return; + } +#endif /* Mentor's DMA */ + + len = request->length - request->actual; + dev_dbg(musb->controller, "%s OUT/RX pio fifo %d/%d, maxpacket %d\n", + musb_ep->end_point.name, + fifo_count, len, + musb_ep->packet_sz); + + fifo_count = min_t(unsigned, len, fifo_count); + +#ifdef CONFIG_USB_TUSB_OMAP_DMA + if (tusb_dma_omap() && is_buffer_mapped(req)) { + struct dma_controller *c = musb->dma_controller; + struct dma_channel *channel = musb_ep->dma; + u32 dma_addr = request->dma + request->actual; + int ret; + + ret = c->channel_program(channel, + musb_ep->packet_sz, + channel->desired_mode, + dma_addr, + fifo_count); + if (ret) + return; + } +#endif + /* + * Unmap the dma buffer back to cpu if dma channel + * programming fails. This buffer is mapped if the + * channel allocation is successful + */ + if (is_buffer_mapped(req)) { + unmap_dma_buffer(req, musb); + + /* + * Clear DMAENAB and AUTOCLEAR for the + * PIO mode transfer + */ + csr &= ~(MUSB_RXCSR_DMAENAB | MUSB_RXCSR_AUTOCLEAR); + musb_writew(epio, MUSB_RXCSR, csr); + } + + musb_read_fifo(musb_ep->hw_ep, fifo_count, (u8 *) + (request->buf + request->actual)); + request->actual += fifo_count; + + /* REVISIT if we left anything in the fifo, flush + * it and report -EOVERFLOW + */ + + /* ack the read! */ + csr |= MUSB_RXCSR_P_WZC_BITS; + csr &= ~MUSB_RXCSR_RXPKTRDY; + musb_writew(epio, MUSB_RXCSR, csr); + } + } + + /* reach the end or short packet detected */ + if (request->actual == request->length || + fifo_count < musb_ep->packet_sz) + musb_g_giveback(musb_ep, request, 0); +} + +/* + * Data ready for a request; called from IRQ + */ +void musb_g_rx(struct musb *musb, u8 epnum) +{ + u16 csr; + struct musb_request *req; + struct usb_request *request; + void __iomem *mbase = musb->mregs; + struct musb_ep *musb_ep; + void __iomem *epio = musb->endpoints[epnum].regs; + struct dma_channel *dma; + struct musb_hw_ep *hw_ep = &musb->endpoints[epnum]; + + if (hw_ep->is_shared_fifo) + musb_ep = &hw_ep->ep_in; + else + musb_ep = &hw_ep->ep_out; + + musb_ep_select(mbase, epnum); + + req = next_request(musb_ep); + if (!req) + return; + + request = &req->request; + + csr = musb_readw(epio, MUSB_RXCSR); + dma = is_dma_capable() ? musb_ep->dma : NULL; + + dev_dbg(musb->controller, "<== %s, rxcsr %04x%s %p\n", musb_ep->end_point.name, + csr, dma ? " (dma)" : "", request); + + if (csr & MUSB_RXCSR_P_SENTSTALL) { + csr |= MUSB_RXCSR_P_WZC_BITS; + csr &= ~MUSB_RXCSR_P_SENTSTALL; + musb_writew(epio, MUSB_RXCSR, csr); + return; + } + + if (csr & MUSB_RXCSR_P_OVERRUN) { + /* csr |= MUSB_RXCSR_P_WZC_BITS; */ + csr &= ~MUSB_RXCSR_P_OVERRUN; + musb_writew(epio, MUSB_RXCSR, csr); + + dev_dbg(musb->controller, "%s iso overrun on %p\n", musb_ep->name, request); + if (request->status == -EINPROGRESS) + request->status = -EOVERFLOW; + } + if (csr & MUSB_RXCSR_INCOMPRX) { + /* REVISIT not necessarily an error */ + dev_dbg(musb->controller, "%s, incomprx\n", musb_ep->end_point.name); + } + + if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) { + /* "should not happen"; likely RXPKTRDY pending for DMA */ + dev_dbg(musb->controller, "%s busy, csr %04x\n", + musb_ep->end_point.name, csr); + return; + } + + if (dma && (csr & MUSB_RXCSR_DMAENAB)) { + csr &= ~(MUSB_RXCSR_AUTOCLEAR + | MUSB_RXCSR_DMAENAB + | MUSB_RXCSR_DMAMODE); + musb_writew(epio, MUSB_RXCSR, + MUSB_RXCSR_P_WZC_BITS | csr); + + request->actual += musb_ep->dma->actual_len; + + dev_dbg(musb->controller, "RXCSR%d %04x, dma off, %04x, len %zu, req %p\n", + epnum, csr, + musb_readw(epio, MUSB_RXCSR), + musb_ep->dma->actual_len, request); + +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_TUSB_OMAP_DMA) || \ + defined(CONFIG_USB_UX500_DMA) + /* Autoclear doesn't clear RxPktRdy for short packets */ + if ((dma->desired_mode == 0 && !hw_ep->rx_double_buffered) + || (dma->actual_len + & (musb_ep->packet_sz - 1))) { + /* ack the read! */ + csr &= ~MUSB_RXCSR_RXPKTRDY; + musb_writew(epio, MUSB_RXCSR, csr); + } + + /* incomplete, and not short? wait for next IN packet */ + if ((request->actual < request->length) + && (musb_ep->dma->actual_len + == musb_ep->packet_sz)) { + /* In double buffer case, continue to unload fifo if + * there is Rx packet in FIFO. + **/ + csr = musb_readw(epio, MUSB_RXCSR); + if ((csr & MUSB_RXCSR_RXPKTRDY) && + hw_ep->rx_double_buffered) + goto exit; + return; + } +#endif + musb_g_giveback(musb_ep, request, 0); + /* + * In the giveback function the MUSB lock is + * released and acquired after sometime. During + * this time period the INDEX register could get + * changed by the gadget_queue function especially + * on SMP systems. Reselect the INDEX to be sure + * we are reading/modifying the right registers + */ + musb_ep_select(mbase, epnum); + + req = next_request(musb_ep); + if (!req) + return; + } +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_TUSB_OMAP_DMA) || \ + defined(CONFIG_USB_UX500_DMA) +exit: +#endif + /* Analyze request */ + rxstate(musb, req); +} + +/* ------------------------------------------------------------ */ + +static int musb_gadget_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + unsigned long flags; + struct musb_ep *musb_ep; + struct musb_hw_ep *hw_ep; + void __iomem *regs; + struct musb *musb; + void __iomem *mbase; + u8 epnum; + u16 csr; + unsigned tmp; + int status = -EINVAL; + + if (!ep || !desc) + return -EINVAL; + + musb_ep = to_musb_ep(ep); + hw_ep = musb_ep->hw_ep; + regs = hw_ep->regs; + musb = musb_ep->musb; + mbase = musb->mregs; + epnum = musb_ep->current_epnum; + + spin_lock_irqsave(&musb->lock, flags); + + if (musb_ep->desc) { + status = -EBUSY; + goto fail; + } + musb_ep->type = usb_endpoint_type(desc); + + /* check direction and (later) maxpacket size against endpoint */ + if (usb_endpoint_num(desc) != epnum) + goto fail; + + /* REVISIT this rules out high bandwidth periodic transfers */ + tmp = usb_endpoint_maxp(desc); + if (tmp & ~0x07ff) { + int ok; + + if (usb_endpoint_dir_in(desc)) + ok = musb->hb_iso_tx; + else + ok = musb->hb_iso_rx; + + if (!ok) { + dev_dbg(musb->controller, "no support for high bandwidth ISO\n"); + goto fail; + } + musb_ep->hb_mult = (tmp >> 11) & 3; + } else { + musb_ep->hb_mult = 0; + } + + musb_ep->packet_sz = tmp & 0x7ff; + tmp = musb_ep->packet_sz * (musb_ep->hb_mult + 1); + + /* enable the interrupts for the endpoint, set the endpoint + * packet size (or fail), set the mode, clear the fifo + */ + musb_ep_select(mbase, epnum); + if (usb_endpoint_dir_in(desc)) { + + if (hw_ep->is_shared_fifo) + musb_ep->is_in = 1; + if (!musb_ep->is_in) + goto fail; + + if (tmp > hw_ep->max_packet_sz_tx) { + dev_dbg(musb->controller, "packet size beyond hardware FIFO size\n"); + goto fail; + } + + musb->intrtxe |= (1 << epnum); + musb_writew(mbase, MUSB_INTRTXE, musb->intrtxe); + + /* REVISIT if can_bulk_split(), use by updating "tmp"; + * likewise high bandwidth periodic tx + */ + /* Set TXMAXP with the FIFO size of the endpoint + * to disable double buffering mode. + */ + if (musb->double_buffer_not_ok) { + musb_writew(regs, MUSB_TXMAXP, hw_ep->max_packet_sz_tx); + } else { + if (can_bulk_split(musb, musb_ep->type)) + musb_ep->hb_mult = (hw_ep->max_packet_sz_tx / + musb_ep->packet_sz) - 1; + musb_writew(regs, MUSB_TXMAXP, musb_ep->packet_sz + | (musb_ep->hb_mult << 11)); + } + + csr = MUSB_TXCSR_MODE | MUSB_TXCSR_CLRDATATOG; + if (musb_readw(regs, MUSB_TXCSR) + & MUSB_TXCSR_FIFONOTEMPTY) + csr |= MUSB_TXCSR_FLUSHFIFO; + if (musb_ep->type == USB_ENDPOINT_XFER_ISOC) + csr |= MUSB_TXCSR_P_ISO; + + /* set twice in case of double buffering */ + musb_writew(regs, MUSB_TXCSR, csr); + /* REVISIT may be inappropriate w/o FIFONOTEMPTY ... */ + musb_writew(regs, MUSB_TXCSR, csr); + + } else { + + if (hw_ep->is_shared_fifo) + musb_ep->is_in = 0; + if (musb_ep->is_in) + goto fail; + + if (tmp > hw_ep->max_packet_sz_rx) { + dev_dbg(musb->controller, "packet size beyond hardware FIFO size\n"); + goto fail; + } + + musb->intrrxe |= (1 << epnum); + musb_writew(mbase, MUSB_INTRRXE, musb->intrrxe); + + /* REVISIT if can_bulk_combine() use by updating "tmp" + * likewise high bandwidth periodic rx + */ + /* Set RXMAXP with the FIFO size of the endpoint + * to disable double buffering mode. + */ + if (musb->double_buffer_not_ok) + musb_writew(regs, MUSB_RXMAXP, hw_ep->max_packet_sz_tx); + else + musb_writew(regs, MUSB_RXMAXP, musb_ep->packet_sz + | (musb_ep->hb_mult << 11)); + + /* force shared fifo to OUT-only mode */ + if (hw_ep->is_shared_fifo) { + csr = musb_readw(regs, MUSB_TXCSR); + csr &= ~(MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY); + musb_writew(regs, MUSB_TXCSR, csr); + } + + csr = MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_CLRDATATOG; + if (musb_ep->type == USB_ENDPOINT_XFER_ISOC) + csr |= MUSB_RXCSR_P_ISO; + else if (musb_ep->type == USB_ENDPOINT_XFER_INT) + csr |= MUSB_RXCSR_DISNYET; + + /* set twice in case of double buffering */ + musb_writew(regs, MUSB_RXCSR, csr); + musb_writew(regs, MUSB_RXCSR, csr); + } + + /* NOTE: all the I/O code _should_ work fine without DMA, in case + * for some reason you run out of channels here. + */ + if (is_dma_capable() && musb->dma_controller) { + struct dma_controller *c = musb->dma_controller; + + musb_ep->dma = c->channel_alloc(c, hw_ep, + (desc->bEndpointAddress & USB_DIR_IN)); + } else + musb_ep->dma = NULL; + + musb_ep->desc = desc; + musb_ep->busy = 0; + musb_ep->wedged = 0; + status = 0; + + pr_debug("%s periph: enabled %s for %s %s, %smaxpacket %d\n", + musb_driver_name, musb_ep->end_point.name, + ({ char *s; switch (musb_ep->type) { + case USB_ENDPOINT_XFER_BULK: s = "bulk"; break; + case USB_ENDPOINT_XFER_INT: s = "int"; break; + default: s = "iso"; break; + }; s; }), + musb_ep->is_in ? "IN" : "OUT", + musb_ep->dma ? "dma, " : "", + musb_ep->packet_sz); + + schedule_work(&musb->irq_work); + +fail: + spin_unlock_irqrestore(&musb->lock, flags); + return status; +} + +/* + * Disable an endpoint flushing all requests queued. + */ +static int musb_gadget_disable(struct usb_ep *ep) +{ + unsigned long flags; + struct musb *musb; + u8 epnum; + struct musb_ep *musb_ep; + void __iomem *epio; + int status = 0; + + musb_ep = to_musb_ep(ep); + musb = musb_ep->musb; + epnum = musb_ep->current_epnum; + epio = musb->endpoints[epnum].regs; + + spin_lock_irqsave(&musb->lock, flags); + musb_ep_select(musb->mregs, epnum); + + /* zero the endpoint sizes */ + if (musb_ep->is_in) { + musb->intrtxe &= ~(1 << epnum); + musb_writew(musb->mregs, MUSB_INTRTXE, musb->intrtxe); + musb_writew(epio, MUSB_TXMAXP, 0); + } else { + musb->intrrxe &= ~(1 << epnum); + musb_writew(musb->mregs, MUSB_INTRRXE, musb->intrrxe); + musb_writew(epio, MUSB_RXMAXP, 0); + } + + musb_ep->desc = NULL; + musb_ep->end_point.desc = NULL; + + /* abort all pending DMA and requests */ + nuke(musb_ep, -ESHUTDOWN); + + schedule_work(&musb->irq_work); + + spin_unlock_irqrestore(&(musb->lock), flags); + + dev_dbg(musb->controller, "%s\n", musb_ep->end_point.name); + + return status; +} + +/* + * Allocate a request for an endpoint. + * Reused by ep0 code. + */ +struct usb_request *musb_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) +{ + struct musb_ep *musb_ep = to_musb_ep(ep); + struct musb *musb = musb_ep->musb; + struct musb_request *request = NULL; + + request = kzalloc(sizeof *request, gfp_flags); + if (!request) { + dev_dbg(musb->controller, "not enough memory\n"); + return NULL; + } + + request->request.dma = DMA_ADDR_INVALID; + request->epnum = musb_ep->current_epnum; + request->ep = musb_ep; + + return &request->request; +} + +/* + * Free a request + * Reused by ep0 code. + */ +void musb_free_request(struct usb_ep *ep, struct usb_request *req) +{ + kfree(to_musb_request(req)); +} + +static LIST_HEAD(buffers); + +struct free_record { + struct list_head list; + struct device *dev; + unsigned bytes; + dma_addr_t dma; +}; + +/* + * Context: controller locked, IRQs blocked. + */ +void musb_ep_restart(struct musb *musb, struct musb_request *req) +{ + dev_dbg(musb->controller, "<== %s request %p len %u on hw_ep%d\n", + req->tx ? "TX/IN" : "RX/OUT", + &req->request, req->request.length, req->epnum); + + musb_ep_select(musb->mregs, req->epnum); + if (req->tx) + txstate(musb, req); + else + rxstate(musb, req); +} + +static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags) +{ + struct musb_ep *musb_ep; + struct musb_request *request; + struct musb *musb; + int status = 0; + unsigned long lockflags; + + if (!ep || !req) + return -EINVAL; + if (!req->buf) + return -ENODATA; + + musb_ep = to_musb_ep(ep); + musb = musb_ep->musb; + + request = to_musb_request(req); + request->musb = musb; + + if (request->ep != musb_ep) + return -EINVAL; + + dev_dbg(musb->controller, "<== to %s request=%p\n", ep->name, req); + + /* request is mine now... */ + request->request.actual = 0; + request->request.status = -EINPROGRESS; + request->epnum = musb_ep->current_epnum; + request->tx = musb_ep->is_in; + + map_dma_buffer(request, musb, musb_ep); + + spin_lock_irqsave(&musb->lock, lockflags); + + /* don't queue if the ep is down */ + if (!musb_ep->desc) { + dev_dbg(musb->controller, "req %p queued to %s while ep %s\n", + req, ep->name, "disabled"); + status = -ESHUTDOWN; + goto cleanup; + } + + /* add request to the list */ + list_add_tail(&request->list, &musb_ep->req_list); + + /* it this is the head of the queue, start i/o ... */ + if (!musb_ep->busy && &request->list == musb_ep->req_list.next) + musb_ep_restart(musb, request); + +cleanup: + spin_unlock_irqrestore(&musb->lock, lockflags); + return status; +} + +static int musb_gadget_dequeue(struct usb_ep *ep, struct usb_request *request) +{ + struct musb_ep *musb_ep = to_musb_ep(ep); + struct musb_request *req = to_musb_request(request); + struct musb_request *r; + unsigned long flags; + int status = 0; + struct musb *musb = musb_ep->musb; + + if (!ep || !request || to_musb_request(request)->ep != musb_ep) + return -EINVAL; + + spin_lock_irqsave(&musb->lock, flags); + + list_for_each_entry(r, &musb_ep->req_list, list) { + if (r == req) + break; + } + if (r != req) { + dev_dbg(musb->controller, "request %p not queued to %s\n", request, ep->name); + status = -EINVAL; + goto done; + } + + /* if the hardware doesn't have the request, easy ... */ + if (musb_ep->req_list.next != &req->list || musb_ep->busy) + musb_g_giveback(musb_ep, request, -ECONNRESET); + + /* ... else abort the dma transfer ... */ + else if (is_dma_capable() && musb_ep->dma) { + struct dma_controller *c = musb->dma_controller; + + musb_ep_select(musb->mregs, musb_ep->current_epnum); + if (c->channel_abort) + status = c->channel_abort(musb_ep->dma); + else + status = -EBUSY; + if (status == 0) + musb_g_giveback(musb_ep, request, -ECONNRESET); + } else { + /* NOTE: by sticking to easily tested hardware/driver states, + * we leave counting of in-flight packets imprecise. + */ + musb_g_giveback(musb_ep, request, -ECONNRESET); + } + +done: + spin_unlock_irqrestore(&musb->lock, flags); + return status; +} + +/* + * Set or clear the halt bit of an endpoint. A halted enpoint won't tx/rx any + * data but will queue requests. + * + * exported to ep0 code + */ +static int musb_gadget_set_halt(struct usb_ep *ep, int value) +{ + struct musb_ep *musb_ep = to_musb_ep(ep); + u8 epnum = musb_ep->current_epnum; + struct musb *musb = musb_ep->musb; + void __iomem *epio = musb->endpoints[epnum].regs; + void __iomem *mbase; + unsigned long flags; + u16 csr; + struct musb_request *request; + int status = 0; + + if (!ep) + return -EINVAL; + mbase = musb->mregs; + + spin_lock_irqsave(&musb->lock, flags); + + if ((USB_ENDPOINT_XFER_ISOC == musb_ep->type)) { + status = -EINVAL; + goto done; + } + + musb_ep_select(mbase, epnum); + + request = next_request(musb_ep); + if (value) { + if (request) { + dev_dbg(musb->controller, "request in progress, cannot halt %s\n", + ep->name); + status = -EAGAIN; + goto done; + } + /* Cannot portably stall with non-empty FIFO */ + if (musb_ep->is_in) { + csr = musb_readw(epio, MUSB_TXCSR); + if (csr & MUSB_TXCSR_FIFONOTEMPTY) { + dev_dbg(musb->controller, "FIFO busy, cannot halt %s\n", ep->name); + status = -EAGAIN; + goto done; + } + } + } else + musb_ep->wedged = 0; + + /* set/clear the stall and toggle bits */ + dev_dbg(musb->controller, "%s: %s stall\n", ep->name, value ? "set" : "clear"); + if (musb_ep->is_in) { + csr = musb_readw(epio, MUSB_TXCSR); + csr |= MUSB_TXCSR_P_WZC_BITS + | MUSB_TXCSR_CLRDATATOG; + if (value) + csr |= MUSB_TXCSR_P_SENDSTALL; + else + csr &= ~(MUSB_TXCSR_P_SENDSTALL + | MUSB_TXCSR_P_SENTSTALL); + csr &= ~MUSB_TXCSR_TXPKTRDY; + musb_writew(epio, MUSB_TXCSR, csr); + } else { + csr = musb_readw(epio, MUSB_RXCSR); + csr |= MUSB_RXCSR_P_WZC_BITS + | MUSB_RXCSR_FLUSHFIFO + | MUSB_RXCSR_CLRDATATOG; + if (value) + csr |= MUSB_RXCSR_P_SENDSTALL; + else + csr &= ~(MUSB_RXCSR_P_SENDSTALL + | MUSB_RXCSR_P_SENTSTALL); + musb_writew(epio, MUSB_RXCSR, csr); + } + + /* maybe start the first request in the queue */ + if (!musb_ep->busy && !value && request) { + dev_dbg(musb->controller, "restarting the request\n"); + musb_ep_restart(musb, request); + } + +done: + spin_unlock_irqrestore(&musb->lock, flags); + return status; +} + +/* + * Sets the halt feature with the clear requests ignored + */ +static int musb_gadget_set_wedge(struct usb_ep *ep) +{ + struct musb_ep *musb_ep = to_musb_ep(ep); + + if (!ep) + return -EINVAL; + + musb_ep->wedged = 1; + + return usb_ep_set_halt(ep); +} + +static int musb_gadget_fifo_status(struct usb_ep *ep) +{ + struct musb_ep *musb_ep = to_musb_ep(ep); + void __iomem *epio = musb_ep->hw_ep->regs; + int retval = -EINVAL; + + if (musb_ep->desc && !musb_ep->is_in) { + struct musb *musb = musb_ep->musb; + int epnum = musb_ep->current_epnum; + void __iomem *mbase = musb->mregs; + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + + musb_ep_select(mbase, epnum); + /* FIXME return zero unless RXPKTRDY is set */ + retval = musb_readw(epio, MUSB_RXCOUNT); + + spin_unlock_irqrestore(&musb->lock, flags); + } + return retval; +} + +static void musb_gadget_fifo_flush(struct usb_ep *ep) +{ + struct musb_ep *musb_ep = to_musb_ep(ep); + struct musb *musb = musb_ep->musb; + u8 epnum = musb_ep->current_epnum; + void __iomem *epio = musb->endpoints[epnum].regs; + void __iomem *mbase; + unsigned long flags; + u16 csr; + + mbase = musb->mregs; + + spin_lock_irqsave(&musb->lock, flags); + musb_ep_select(mbase, (u8) epnum); + + /* disable interrupts */ + musb_writew(mbase, MUSB_INTRTXE, musb->intrtxe & ~(1 << epnum)); + + if (musb_ep->is_in) { + csr = musb_readw(epio, MUSB_TXCSR); + if (csr & MUSB_TXCSR_FIFONOTEMPTY) { + csr |= MUSB_TXCSR_FLUSHFIFO | MUSB_TXCSR_P_WZC_BITS; + /* + * Setting both TXPKTRDY and FLUSHFIFO makes controller + * to interrupt current FIFO loading, but not flushing + * the already loaded ones. + */ + csr &= ~MUSB_TXCSR_TXPKTRDY; + musb_writew(epio, MUSB_TXCSR, csr); + /* REVISIT may be inappropriate w/o FIFONOTEMPTY ... */ + musb_writew(epio, MUSB_TXCSR, csr); + } + } else { + csr = musb_readw(epio, MUSB_RXCSR); + csr |= MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_P_WZC_BITS; + musb_writew(epio, MUSB_RXCSR, csr); + musb_writew(epio, MUSB_RXCSR, csr); + } + + /* re-enable interrupt */ + musb_writew(mbase, MUSB_INTRTXE, musb->intrtxe); + spin_unlock_irqrestore(&musb->lock, flags); +} + +static const struct usb_ep_ops musb_ep_ops = { + .enable = musb_gadget_enable, + .disable = musb_gadget_disable, + .alloc_request = musb_alloc_request, + .free_request = musb_free_request, + .queue = musb_gadget_queue, + .dequeue = musb_gadget_dequeue, + .set_halt = musb_gadget_set_halt, + .set_wedge = musb_gadget_set_wedge, + .fifo_status = musb_gadget_fifo_status, + .fifo_flush = musb_gadget_fifo_flush +}; + +/* ----------------------------------------------------------------------- */ + +static int musb_gadget_get_frame(struct usb_gadget *gadget) +{ + struct musb *musb = gadget_to_musb(gadget); + + return (int)musb_readw(musb->mregs, MUSB_FRAME); +} + +static int musb_gadget_wakeup(struct usb_gadget *gadget) +{ + struct musb *musb = gadget_to_musb(gadget); + void __iomem *mregs = musb->mregs; + unsigned long flags; + int status = -EINVAL; + u8 power, devctl; + int retries; + + spin_lock_irqsave(&musb->lock, flags); + + switch (musb->xceiv->state) { + case OTG_STATE_B_PERIPHERAL: + /* NOTE: OTG state machine doesn't include B_SUSPENDED; + * that's part of the standard usb 1.1 state machine, and + * doesn't affect OTG transitions. + */ + if (musb->may_wakeup && musb->is_suspended) + break; + goto done; + case OTG_STATE_B_IDLE: + /* Start SRP ... OTG not required. */ + devctl = musb_readb(mregs, MUSB_DEVCTL); + dev_dbg(musb->controller, "Sending SRP: devctl: %02x\n", devctl); + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(mregs, MUSB_DEVCTL, devctl); + devctl = musb_readb(mregs, MUSB_DEVCTL); + retries = 100; + while (!(devctl & MUSB_DEVCTL_SESSION)) { + devctl = musb_readb(mregs, MUSB_DEVCTL); + if (retries-- < 1) + break; + } + retries = 10000; + while (devctl & MUSB_DEVCTL_SESSION) { + devctl = musb_readb(mregs, MUSB_DEVCTL); + if (retries-- < 1) + break; + } + + spin_unlock_irqrestore(&musb->lock, flags); + otg_start_srp(musb->xceiv->otg); + spin_lock_irqsave(&musb->lock, flags); + + /* Block idling for at least 1s */ + musb_platform_try_idle(musb, + jiffies + msecs_to_jiffies(1 * HZ)); + + status = 0; + goto done; + default: + dev_dbg(musb->controller, "Unhandled wake: %s\n", + usb_otg_state_string(musb->xceiv->state)); + goto done; + } + + status = 0; + + power = musb_readb(mregs, MUSB_POWER); + power |= MUSB_POWER_RESUME; + musb_writeb(mregs, MUSB_POWER, power); + dev_dbg(musb->controller, "issue wakeup\n"); + + /* FIXME do this next chunk in a timer callback, no udelay */ + mdelay(2); + + power = musb_readb(mregs, MUSB_POWER); + power &= ~MUSB_POWER_RESUME; + musb_writeb(mregs, MUSB_POWER, power); +done: + spin_unlock_irqrestore(&musb->lock, flags); + return status; +} + +static int +musb_gadget_set_self_powered(struct usb_gadget *gadget, int is_selfpowered) +{ + struct musb *musb = gadget_to_musb(gadget); + + musb->is_self_powered = !!is_selfpowered; + return 0; +} + +static void musb_pullup(struct musb *musb, int is_on) +{ + u8 power; + + power = musb_readb(musb->mregs, MUSB_POWER); + if (is_on) + power |= MUSB_POWER_SOFTCONN; + else + power &= ~MUSB_POWER_SOFTCONN; + + /* FIXME if on, HdrcStart; if off, HdrcStop */ + + dev_dbg(musb->controller, "gadget D+ pullup %s\n", + is_on ? "on" : "off"); + musb_writeb(musb->mregs, MUSB_POWER, power); +} + +#if 0 +static int musb_gadget_vbus_session(struct usb_gadget *gadget, int is_active) +{ + dev_dbg(musb->controller, "<= %s =>\n", __func__); + + /* + * FIXME iff driver's softconnect flag is set (as it is during probe, + * though that can clear it), just musb_pullup(). + */ + + return -EINVAL; +} +#endif + +static int musb_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct musb *musb = gadget_to_musb(gadget); + + if (!musb->xceiv->set_power) + return -EOPNOTSUPP; + return usb_phy_set_power(musb->xceiv, mA); +} + +static int musb_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct musb *musb = gadget_to_musb(gadget); + unsigned long flags; + + is_on = !!is_on; + + pm_runtime_get_sync(musb->controller); + + /* NOTE: this assumes we are sensing vbus; we'd rather + * not pullup unless the B-session is active. + */ + spin_lock_irqsave(&musb->lock, flags); + if (is_on != musb->softconnect) { + musb->softconnect = is_on; + musb_pullup(musb, is_on); + } + spin_unlock_irqrestore(&musb->lock, flags); + + pm_runtime_put(musb->controller); + + return 0; +} + +static int musb_gadget_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int musb_gadget_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops musb_gadget_operations = { + .get_frame = musb_gadget_get_frame, + .wakeup = musb_gadget_wakeup, + .set_selfpowered = musb_gadget_set_self_powered, + /* .vbus_session = musb_gadget_vbus_session, */ + .vbus_draw = musb_gadget_vbus_draw, + .pullup = musb_gadget_pullup, + .udc_start = musb_gadget_start, + .udc_stop = musb_gadget_stop, +}; + +/* ----------------------------------------------------------------------- */ + +/* Registration */ + +/* Only this registration code "knows" the rule (from USB standards) + * about there being only one external upstream port. It assumes + * all peripheral ports are external... + */ + +static void +init_peripheral_ep(struct musb *musb, struct musb_ep *ep, u8 epnum, int is_in) +{ + struct musb_hw_ep *hw_ep = musb->endpoints + epnum; + + memset(ep, 0, sizeof *ep); + + ep->current_epnum = epnum; + ep->musb = musb; + ep->hw_ep = hw_ep; + ep->is_in = is_in; + + INIT_LIST_HEAD(&ep->req_list); + + sprintf(ep->name, "ep%d%s", epnum, + (!epnum || hw_ep->is_shared_fifo) ? "" : ( + is_in ? "in" : "out")); + ep->end_point.name = ep->name; + INIT_LIST_HEAD(&ep->end_point.ep_list); + if (!epnum) { + ep->end_point.maxpacket = 64; + ep->end_point.ops = &musb_g_ep0_ops; + musb->g.ep0 = &ep->end_point; + } else { + if (is_in) + ep->end_point.maxpacket = hw_ep->max_packet_sz_tx; + else + ep->end_point.maxpacket = hw_ep->max_packet_sz_rx; + ep->end_point.ops = &musb_ep_ops; + list_add_tail(&ep->end_point.ep_list, &musb->g.ep_list); + } +} + +/* + * Initialize the endpoints exposed to peripheral drivers, with backlinks + * to the rest of the driver state. + */ +static inline void musb_g_init_endpoints(struct musb *musb) +{ + u8 epnum; + struct musb_hw_ep *hw_ep; + unsigned count = 0; + + /* initialize endpoint list just once */ + INIT_LIST_HEAD(&(musb->g.ep_list)); + + for (epnum = 0, hw_ep = musb->endpoints; + epnum < musb->nr_endpoints; + epnum++, hw_ep++) { + if (hw_ep->is_shared_fifo /* || !epnum */) { + init_peripheral_ep(musb, &hw_ep->ep_in, epnum, 0); + count++; + } else { + if (hw_ep->max_packet_sz_tx) { + init_peripheral_ep(musb, &hw_ep->ep_in, + epnum, 1); + count++; + } + if (hw_ep->max_packet_sz_rx) { + init_peripheral_ep(musb, &hw_ep->ep_out, + epnum, 0); + count++; + } + } + } +} + +/* called once during driver setup to initialize and link into + * the driver model; memory is zeroed. + */ +int musb_gadget_setup(struct musb *musb) +{ + int status; + + /* REVISIT minor race: if (erroneously) setting up two + * musb peripherals at the same time, only the bus lock + * is probably held. + */ + + musb->g.ops = &musb_gadget_operations; + musb->g.max_speed = USB_SPEED_HIGH; + musb->g.speed = USB_SPEED_UNKNOWN; + + /* this "gadget" abstracts/virtualizes the controller */ + musb->g.name = musb_driver_name; + musb->g.is_otg = 1; + + musb_g_init_endpoints(musb); + + musb->is_active = 0; + musb_platform_try_idle(musb, 0); + + status = usb_add_gadget_udc(musb->controller, &musb->g); + if (status) + goto err; + + return 0; +err: + musb->g.dev.parent = NULL; + device_unregister(&musb->g.dev); + return status; +} + +void musb_gadget_cleanup(struct musb *musb) +{ + usb_del_gadget_udc(&musb->g); +} + +/* + * Register the gadget driver. Used by gadget drivers when + * registering themselves with the controller. + * + * -EINVAL something went wrong (not driver) + * -EBUSY another gadget is already using the controller + * -ENOMEM no memory to perform the operation + * + * @param driver the gadget driver + * @return <0 if error, 0 if everything is fine + */ +static int musb_gadget_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct musb *musb = gadget_to_musb(g); + struct usb_otg *otg = musb->xceiv->otg; + struct usb_hcd *hcd = musb_to_hcd(musb); + unsigned long flags; + int retval = 0; + + if (driver->max_speed < USB_SPEED_HIGH) { + retval = -EINVAL; + goto err; + } + + pm_runtime_get_sync(musb->controller); + + dev_dbg(musb->controller, "registering driver %s\n", driver->function); + + musb->softconnect = 0; + musb->gadget_driver = driver; + + spin_lock_irqsave(&musb->lock, flags); + musb->is_active = 1; + + otg_set_peripheral(otg, &musb->g); + musb->xceiv->state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&musb->lock, flags); + + /* REVISIT: funcall to other code, which also + * handles power budgeting ... this way also + * ensures HdrcStart is indirectly called. + */ + retval = usb_add_hcd(hcd, 0, 0); + if (retval < 0) { + dev_dbg(musb->controller, "add_hcd failed, %d\n", retval); + goto err; + } + + if (musb->xceiv->last_event == USB_EVENT_ID) + musb_platform_set_vbus(musb, 1); + + hcd->self.uses_pio_for_control = 1; + + if (musb->xceiv->last_event == USB_EVENT_NONE) + pm_runtime_put(musb->controller); + + return 0; + +err: + return retval; +} + +static void stop_activity(struct musb *musb, struct usb_gadget_driver *driver) +{ + int i; + struct musb_hw_ep *hw_ep; + + /* don't disconnect if it's not connected */ + if (musb->g.speed == USB_SPEED_UNKNOWN) + driver = NULL; + else + musb->g.speed = USB_SPEED_UNKNOWN; + + /* deactivate the hardware */ + if (musb->softconnect) { + musb->softconnect = 0; + musb_pullup(musb, 0); + } + musb_stop(musb); + + /* killing any outstanding requests will quiesce the driver; + * then report disconnect + */ + if (driver) { + for (i = 0, hw_ep = musb->endpoints; + i < musb->nr_endpoints; + i++, hw_ep++) { + musb_ep_select(musb->mregs, i); + if (hw_ep->is_shared_fifo /* || !epnum */) { + nuke(&hw_ep->ep_in, -ESHUTDOWN); + } else { + if (hw_ep->max_packet_sz_tx) + nuke(&hw_ep->ep_in, -ESHUTDOWN); + if (hw_ep->max_packet_sz_rx) + nuke(&hw_ep->ep_out, -ESHUTDOWN); + } + } + } +} + +/* + * Unregister the gadget driver. Used by gadget drivers when + * unregistering themselves from the controller. + * + * @param driver the gadget driver to unregister + */ +static int musb_gadget_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct musb *musb = gadget_to_musb(g); + unsigned long flags; + + if (musb->xceiv->last_event == USB_EVENT_NONE) + pm_runtime_get_sync(musb->controller); + + /* + * REVISIT always use otg_set_peripheral() here too; + * this needs to shut down the OTG engine. + */ + + spin_lock_irqsave(&musb->lock, flags); + + musb_hnp_stop(musb); + + (void) musb_gadget_vbus_draw(&musb->g, 0); + + musb->xceiv->state = OTG_STATE_UNDEFINED; + stop_activity(musb, driver); + otg_set_peripheral(musb->xceiv->otg, NULL); + + dev_dbg(musb->controller, "unregistering driver %s\n", driver->function); + + musb->is_active = 0; + musb->gadget_driver = NULL; + musb_platform_try_idle(musb, 0); + spin_unlock_irqrestore(&musb->lock, flags); + + usb_remove_hcd(musb_to_hcd(musb)); + /* + * FIXME we need to be able to register another + * gadget driver here and have everything work; + * that currently misbehaves. + */ + + pm_runtime_put(musb->controller); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* lifecycle operations called through plat_uds.c */ + +void musb_g_resume(struct musb *musb) +{ + musb->is_suspended = 0; + switch (musb->xceiv->state) { + case OTG_STATE_B_IDLE: + break; + case OTG_STATE_B_WAIT_ACON: + case OTG_STATE_B_PERIPHERAL: + musb->is_active = 1; + if (musb->gadget_driver && musb->gadget_driver->resume) { + spin_unlock(&musb->lock); + musb->gadget_driver->resume(&musb->g); + spin_lock(&musb->lock); + } + break; + default: + WARNING("unhandled RESUME transition (%s)\n", + usb_otg_state_string(musb->xceiv->state)); + } +} + +/* called when SOF packets stop for 3+ msec */ +void musb_g_suspend(struct musb *musb) +{ + u8 devctl; + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + dev_dbg(musb->controller, "devctl %02x\n", devctl); + + switch (musb->xceiv->state) { + case OTG_STATE_B_IDLE: + if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + break; + case OTG_STATE_B_PERIPHERAL: + musb->is_suspended = 1; + if (musb->gadget_driver && musb->gadget_driver->suspend) { + spin_unlock(&musb->lock); + musb->gadget_driver->suspend(&musb->g); + spin_lock(&musb->lock); + } + break; + default: + /* REVISIT if B_HOST, clear DEVCTL.HOSTREQ; + * A_PERIPHERAL may need care too + */ + WARNING("unhandled SUSPEND transition (%s)\n", + usb_otg_state_string(musb->xceiv->state)); + } +} + +/* Called during SRP */ +void musb_g_wakeup(struct musb *musb) +{ + musb_gadget_wakeup(&musb->g); +} + +/* called when VBUS drops below session threshold, and in other cases */ +void musb_g_disconnect(struct musb *musb) +{ + void __iomem *mregs = musb->mregs; + u8 devctl = musb_readb(mregs, MUSB_DEVCTL); + + dev_dbg(musb->controller, "devctl %02x\n", devctl); + + /* clear HR */ + musb_writeb(mregs, MUSB_DEVCTL, devctl & MUSB_DEVCTL_SESSION); + + /* don't draw vbus until new b-default session */ + (void) musb_gadget_vbus_draw(&musb->g, 0); + + musb->g.speed = USB_SPEED_UNKNOWN; + if (musb->gadget_driver && musb->gadget_driver->disconnect) { + spin_unlock(&musb->lock); + musb->gadget_driver->disconnect(&musb->g); + spin_lock(&musb->lock); + } + + switch (musb->xceiv->state) { + default: + dev_dbg(musb->controller, "Unhandled disconnect %s, setting a_idle\n", + usb_otg_state_string(musb->xceiv->state)); + musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + break; + case OTG_STATE_A_PERIPHERAL: + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + MUSB_HST_MODE(musb); + break; + case OTG_STATE_B_WAIT_ACON: + case OTG_STATE_B_HOST: + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_IDLE: + musb->xceiv->state = OTG_STATE_B_IDLE; + break; + case OTG_STATE_B_SRP_INIT: + break; + } + + musb->is_active = 0; +} + +void musb_g_reset(struct musb *musb) +__releases(musb->lock) +__acquires(musb->lock) +{ + void __iomem *mbase = musb->mregs; + u8 devctl = musb_readb(mbase, MUSB_DEVCTL); + u8 power; + + dev_dbg(musb->controller, "<== %s driver '%s'\n", + (devctl & MUSB_DEVCTL_BDEVICE) + ? "B-Device" : "A-Device", + musb->gadget_driver + ? musb->gadget_driver->driver.name + : NULL + ); + + /* report disconnect, if we didn't already (flushing EP state) */ + if (musb->g.speed != USB_SPEED_UNKNOWN) + musb_g_disconnect(musb); + + /* clear HR */ + else if (devctl & MUSB_DEVCTL_HR) + musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION); + + + /* what speed did we negotiate? */ + power = musb_readb(mbase, MUSB_POWER); + musb->g.speed = (power & MUSB_POWER_HSMODE) + ? USB_SPEED_HIGH : USB_SPEED_FULL; + + /* start in USB_STATE_DEFAULT */ + musb->is_active = 1; + musb->is_suspended = 0; + MUSB_DEV_MODE(musb); + musb->address = 0; + musb->ep0_state = MUSB_EP0_STAGE_SETUP; + + musb->may_wakeup = 0; + musb->g.b_hnp_enable = 0; + musb->g.a_alt_hnp_support = 0; + musb->g.a_hnp_support = 0; + + /* Normal reset, as B-Device; + * or else after HNP, as A-Device + */ + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + musb->g.is_a_peripheral = 0; + } else { + musb->xceiv->state = OTG_STATE_A_PERIPHERAL; + musb->g.is_a_peripheral = 1; + } + + /* start with default limits on VBUS power draw */ + (void) musb_gadget_vbus_draw(&musb->g, 8); +} diff --git a/drivers/usb/musb/musb_gadget.h b/drivers/usb/musb/musb_gadget.h new file mode 100644 index 00000000..66b7c5e0 --- /dev/null +++ b/drivers/usb/musb/musb_gadget.h @@ -0,0 +1,121 @@ +/* + * MUSB OTG driver peripheral defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __MUSB_GADGET_H +#define __MUSB_GADGET_H + +#include + +enum buffer_map_state { + UN_MAPPED = 0, + PRE_MAPPED, + MUSB_MAPPED +}; + +struct musb_request { + struct usb_request request; + struct list_head list; + struct musb_ep *ep; + struct musb *musb; + u8 tx; /* endpoint direction */ + u8 epnum; + enum buffer_map_state map_state; +}; + +static inline struct musb_request *to_musb_request(struct usb_request *req) +{ + return req ? container_of(req, struct musb_request, request) : NULL; +} + +extern struct usb_request * +musb_alloc_request(struct usb_ep *ep, gfp_t gfp_flags); +extern void musb_free_request(struct usb_ep *ep, struct usb_request *req); + + +/* + * struct musb_ep - peripheral side view of endpoint rx or tx side + */ +struct musb_ep { + /* stuff towards the head is basically write-once. */ + struct usb_ep end_point; + char name[12]; + struct musb_hw_ep *hw_ep; + struct musb *musb; + u8 current_epnum; + + /* ... when enabled/disabled ... */ + u8 type; + u8 is_in; + u16 packet_sz; + const struct usb_endpoint_descriptor *desc; + struct dma_channel *dma; + + /* later things are modified based on usage */ + struct list_head req_list; + + u8 wedged; + + /* true if lock must be dropped but req_list may not be advanced */ + u8 busy; + + u8 hb_mult; +}; + +static inline struct musb_ep *to_musb_ep(struct usb_ep *ep) +{ + return ep ? container_of(ep, struct musb_ep, end_point) : NULL; +} + +static inline struct musb_request *next_request(struct musb_ep *ep) +{ + struct list_head *queue = &ep->req_list; + + if (list_empty(queue)) + return NULL; + return container_of(queue->next, struct musb_request, list); +} + +extern void musb_g_tx(struct musb *musb, u8 epnum); +extern void musb_g_rx(struct musb *musb, u8 epnum); + +extern const struct usb_ep_ops musb_g_ep0_ops; + +extern int musb_gadget_setup(struct musb *); +extern void musb_gadget_cleanup(struct musb *); + +extern void musb_g_giveback(struct musb_ep *, struct usb_request *, int); + +extern void musb_ep_restart(struct musb *, struct musb_request *); + +#endif /* __MUSB_GADGET_H */ diff --git a/drivers/usb/musb/musb_gadget_ep0.c b/drivers/usb/musb/musb_gadget_ep0.c new file mode 100644 index 00000000..2af45a0c --- /dev/null +++ b/drivers/usb/musb/musb_gadget_ep0.c @@ -0,0 +1,1083 @@ +/* + * MUSB OTG peripheral driver ep0 handling + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2008-2009 MontaVista Software, Inc. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" + +/* ep0 is always musb->endpoints[0].ep_in */ +#define next_ep0_request(musb) next_in_request(&(musb)->endpoints[0]) + +/* + * locking note: we use only the controller lock, for simpler correctness. + * It's always held with IRQs blocked. + * + * It protects the ep0 request queue as well as ep0_state, not just the + * controller and indexed registers. And that lock stays held unless it + * needs to be dropped to allow reentering this driver ... like upcalls to + * the gadget driver, or adjusting endpoint halt status. + */ + +static char *decode_ep0stage(u8 stage) +{ + switch (stage) { + case MUSB_EP0_STAGE_IDLE: return "idle"; + case MUSB_EP0_STAGE_SETUP: return "setup"; + case MUSB_EP0_STAGE_TX: return "in"; + case MUSB_EP0_STAGE_RX: return "out"; + case MUSB_EP0_STAGE_ACKWAIT: return "wait"; + case MUSB_EP0_STAGE_STATUSIN: return "in/status"; + case MUSB_EP0_STAGE_STATUSOUT: return "out/status"; + default: return "?"; + } +} + +/* handle a standard GET_STATUS request + * Context: caller holds controller lock + */ +static int service_tx_status_request( + struct musb *musb, + const struct usb_ctrlrequest *ctrlrequest) +{ + void __iomem *mbase = musb->mregs; + int handled = 1; + u8 result[2], epnum = 0; + const u8 recip = ctrlrequest->bRequestType & USB_RECIP_MASK; + + result[1] = 0; + + switch (recip) { + case USB_RECIP_DEVICE: + result[0] = musb->is_self_powered << USB_DEVICE_SELF_POWERED; + result[0] |= musb->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; + if (musb->g.is_otg) { + result[0] |= musb->g.b_hnp_enable + << USB_DEVICE_B_HNP_ENABLE; + result[0] |= musb->g.a_alt_hnp_support + << USB_DEVICE_A_ALT_HNP_SUPPORT; + result[0] |= musb->g.a_hnp_support + << USB_DEVICE_A_HNP_SUPPORT; + } + break; + + case USB_RECIP_INTERFACE: + result[0] = 0; + break; + + case USB_RECIP_ENDPOINT: { + int is_in; + struct musb_ep *ep; + u16 tmp; + void __iomem *regs; + + epnum = (u8) ctrlrequest->wIndex; + if (!epnum) { + result[0] = 0; + break; + } + + is_in = epnum & USB_DIR_IN; + if (is_in) { + epnum &= 0x0f; + ep = &musb->endpoints[epnum].ep_in; + } else { + ep = &musb->endpoints[epnum].ep_out; + } + regs = musb->endpoints[epnum].regs; + + if (epnum >= MUSB_C_NUM_EPS || !ep->desc) { + handled = -EINVAL; + break; + } + + musb_ep_select(mbase, epnum); + if (is_in) + tmp = musb_readw(regs, MUSB_TXCSR) + & MUSB_TXCSR_P_SENDSTALL; + else + tmp = musb_readw(regs, MUSB_RXCSR) + & MUSB_RXCSR_P_SENDSTALL; + musb_ep_select(mbase, 0); + + result[0] = tmp ? 1 : 0; + } break; + + default: + /* class, vendor, etc ... delegate */ + handled = 0; + break; + } + + /* fill up the fifo; caller updates csr0 */ + if (handled > 0) { + u16 len = le16_to_cpu(ctrlrequest->wLength); + + if (len > 2) + len = 2; + musb_write_fifo(&musb->endpoints[0], len, result); + } + + return handled; +} + +/* + * handle a control-IN request, the end0 buffer contains the current request + * that is supposed to be a standard control request. Assumes the fifo to + * be at least 2 bytes long. + * + * @return 0 if the request was NOT HANDLED, + * < 0 when error + * > 0 when the request is processed + * + * Context: caller holds controller lock + */ +static int +service_in_request(struct musb *musb, const struct usb_ctrlrequest *ctrlrequest) +{ + int handled = 0; /* not handled */ + + if ((ctrlrequest->bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD) { + switch (ctrlrequest->bRequest) { + case USB_REQ_GET_STATUS: + handled = service_tx_status_request(musb, + ctrlrequest); + break; + + /* case USB_REQ_SYNC_FRAME: */ + + default: + break; + } + } + return handled; +} + +/* + * Context: caller holds controller lock + */ +static void musb_g_ep0_giveback(struct musb *musb, struct usb_request *req) +{ + musb_g_giveback(&musb->endpoints[0].ep_in, req, 0); +} + +/* + * Tries to start B-device HNP negotiation if enabled via sysfs + */ +static inline void musb_try_b_hnp_enable(struct musb *musb) +{ + void __iomem *mbase = musb->mregs; + u8 devctl; + + dev_dbg(musb->controller, "HNP: Setting HR\n"); + devctl = musb_readb(mbase, MUSB_DEVCTL); + musb_writeb(mbase, MUSB_DEVCTL, devctl | MUSB_DEVCTL_HR); +} + +/* + * Handle all control requests with no DATA stage, including standard + * requests such as: + * USB_REQ_SET_CONFIGURATION, USB_REQ_SET_INTERFACE, unrecognized + * always delegated to the gadget driver + * USB_REQ_SET_ADDRESS, USB_REQ_CLEAR_FEATURE, USB_REQ_SET_FEATURE + * always handled here, except for class/vendor/... features + * + * Context: caller holds controller lock + */ +static int +service_zero_data_request(struct musb *musb, + struct usb_ctrlrequest *ctrlrequest) +__releases(musb->lock) +__acquires(musb->lock) +{ + int handled = -EINVAL; + void __iomem *mbase = musb->mregs; + const u8 recip = ctrlrequest->bRequestType & USB_RECIP_MASK; + + /* the gadget driver handles everything except what we MUST handle */ + if ((ctrlrequest->bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD) { + switch (ctrlrequest->bRequest) { + case USB_REQ_SET_ADDRESS: + /* change it after the status stage */ + musb->set_address = true; + musb->address = (u8) (ctrlrequest->wValue & 0x7f); + handled = 1; + break; + + case USB_REQ_CLEAR_FEATURE: + switch (recip) { + case USB_RECIP_DEVICE: + if (ctrlrequest->wValue + != USB_DEVICE_REMOTE_WAKEUP) + break; + musb->may_wakeup = 0; + handled = 1; + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT:{ + const u8 epnum = + ctrlrequest->wIndex & 0x0f; + struct musb_ep *musb_ep; + struct musb_hw_ep *ep; + struct musb_request *request; + void __iomem *regs; + int is_in; + u16 csr; + + if (epnum == 0 || epnum >= MUSB_C_NUM_EPS || + ctrlrequest->wValue != USB_ENDPOINT_HALT) + break; + + ep = musb->endpoints + epnum; + regs = ep->regs; + is_in = ctrlrequest->wIndex & USB_DIR_IN; + if (is_in) + musb_ep = &ep->ep_in; + else + musb_ep = &ep->ep_out; + if (!musb_ep->desc) + break; + + handled = 1; + /* Ignore request if endpoint is wedged */ + if (musb_ep->wedged) + break; + + musb_ep_select(mbase, epnum); + if (is_in) { + csr = musb_readw(regs, MUSB_TXCSR); + csr |= MUSB_TXCSR_CLRDATATOG | + MUSB_TXCSR_P_WZC_BITS; + csr &= ~(MUSB_TXCSR_P_SENDSTALL | + MUSB_TXCSR_P_SENTSTALL | + MUSB_TXCSR_TXPKTRDY); + musb_writew(regs, MUSB_TXCSR, csr); + } else { + csr = musb_readw(regs, MUSB_RXCSR); + csr |= MUSB_RXCSR_CLRDATATOG | + MUSB_RXCSR_P_WZC_BITS; + csr &= ~(MUSB_RXCSR_P_SENDSTALL | + MUSB_RXCSR_P_SENTSTALL); + musb_writew(regs, MUSB_RXCSR, csr); + } + + /* Maybe start the first request in the queue */ + request = next_request(musb_ep); + if (!musb_ep->busy && request) { + dev_dbg(musb->controller, "restarting the request\n"); + musb_ep_restart(musb, request); + } + + /* select ep0 again */ + musb_ep_select(mbase, 0); + } break; + default: + /* class, vendor, etc ... delegate */ + handled = 0; + break; + } + break; + + case USB_REQ_SET_FEATURE: + switch (recip) { + case USB_RECIP_DEVICE: + handled = 1; + switch (ctrlrequest->wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + musb->may_wakeup = 1; + break; + case USB_DEVICE_TEST_MODE: + if (musb->g.speed != USB_SPEED_HIGH) + goto stall; + if (ctrlrequest->wIndex & 0xff) + goto stall; + + switch (ctrlrequest->wIndex >> 8) { + case 1: + pr_debug("TEST_J\n"); + /* TEST_J */ + musb->test_mode_nr = + MUSB_TEST_J; + break; + case 2: + /* TEST_K */ + pr_debug("TEST_K\n"); + musb->test_mode_nr = + MUSB_TEST_K; + break; + case 3: + /* TEST_SE0_NAK */ + pr_debug("TEST_SE0_NAK\n"); + musb->test_mode_nr = + MUSB_TEST_SE0_NAK; + break; + case 4: + /* TEST_PACKET */ + pr_debug("TEST_PACKET\n"); + musb->test_mode_nr = + MUSB_TEST_PACKET; + break; + + case 0xc0: + /* TEST_FORCE_HS */ + pr_debug("TEST_FORCE_HS\n"); + musb->test_mode_nr = + MUSB_TEST_FORCE_HS; + break; + case 0xc1: + /* TEST_FORCE_FS */ + pr_debug("TEST_FORCE_FS\n"); + musb->test_mode_nr = + MUSB_TEST_FORCE_FS; + break; + case 0xc2: + /* TEST_FIFO_ACCESS */ + pr_debug("TEST_FIFO_ACCESS\n"); + musb->test_mode_nr = + MUSB_TEST_FIFO_ACCESS; + break; + case 0xc3: + /* TEST_FORCE_HOST */ + pr_debug("TEST_FORCE_HOST\n"); + musb->test_mode_nr = + MUSB_TEST_FORCE_HOST; + break; + default: + goto stall; + } + + /* enter test mode after irq */ + if (handled > 0) + musb->test_mode = true; + break; + case USB_DEVICE_B_HNP_ENABLE: + if (!musb->g.is_otg) + goto stall; + musb->g.b_hnp_enable = 1; + musb_try_b_hnp_enable(musb); + break; + case USB_DEVICE_A_HNP_SUPPORT: + if (!musb->g.is_otg) + goto stall; + musb->g.a_hnp_support = 1; + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + if (!musb->g.is_otg) + goto stall; + musb->g.a_alt_hnp_support = 1; + break; + case USB_DEVICE_DEBUG_MODE: + handled = 0; + break; +stall: + default: + handled = -EINVAL; + break; + } + break; + + case USB_RECIP_INTERFACE: + break; + + case USB_RECIP_ENDPOINT:{ + const u8 epnum = + ctrlrequest->wIndex & 0x0f; + struct musb_ep *musb_ep; + struct musb_hw_ep *ep; + void __iomem *regs; + int is_in; + u16 csr; + + if (epnum == 0 || epnum >= MUSB_C_NUM_EPS || + ctrlrequest->wValue != USB_ENDPOINT_HALT) + break; + + ep = musb->endpoints + epnum; + regs = ep->regs; + is_in = ctrlrequest->wIndex & USB_DIR_IN; + if (is_in) + musb_ep = &ep->ep_in; + else + musb_ep = &ep->ep_out; + if (!musb_ep->desc) + break; + + musb_ep_select(mbase, epnum); + if (is_in) { + csr = musb_readw(regs, MUSB_TXCSR); + if (csr & MUSB_TXCSR_FIFONOTEMPTY) + csr |= MUSB_TXCSR_FLUSHFIFO; + csr |= MUSB_TXCSR_P_SENDSTALL + | MUSB_TXCSR_CLRDATATOG + | MUSB_TXCSR_P_WZC_BITS; + musb_writew(regs, MUSB_TXCSR, csr); + } else { + csr = musb_readw(regs, MUSB_RXCSR); + csr |= MUSB_RXCSR_P_SENDSTALL + | MUSB_RXCSR_FLUSHFIFO + | MUSB_RXCSR_CLRDATATOG + | MUSB_RXCSR_P_WZC_BITS; + musb_writew(regs, MUSB_RXCSR, csr); + } + + /* select ep0 again */ + musb_ep_select(mbase, 0); + handled = 1; + } break; + + default: + /* class, vendor, etc ... delegate */ + handled = 0; + break; + } + break; + default: + /* delegate SET_CONFIGURATION, etc */ + handled = 0; + } + } else + handled = 0; + return handled; +} + +/* we have an ep0out data packet + * Context: caller holds controller lock + */ +static void ep0_rxstate(struct musb *musb) +{ + void __iomem *regs = musb->control_ep->regs; + struct musb_request *request; + struct usb_request *req; + u16 count, csr; + + request = next_ep0_request(musb); + req = &request->request; + + /* read packet and ack; or stall because of gadget driver bug: + * should have provided the rx buffer before setup() returned. + */ + if (req) { + void *buf = req->buf + req->actual; + unsigned len = req->length - req->actual; + + /* read the buffer */ + count = musb_readb(regs, MUSB_COUNT0); + if (count > len) { + req->status = -EOVERFLOW; + count = len; + } + if (count > 0) { + musb_read_fifo(&musb->endpoints[0], count, buf); + req->actual += count; + } + csr = MUSB_CSR0_P_SVDRXPKTRDY; + if (count < 64 || req->actual == req->length) { + musb->ep0_state = MUSB_EP0_STAGE_STATUSIN; + csr |= MUSB_CSR0_P_DATAEND; + } else + req = NULL; + } else + csr = MUSB_CSR0_P_SVDRXPKTRDY | MUSB_CSR0_P_SENDSTALL; + + + /* Completion handler may choose to stall, e.g. because the + * message just received holds invalid data. + */ + if (req) { + musb->ackpend = csr; + musb_g_ep0_giveback(musb, req); + if (!musb->ackpend) + return; + musb->ackpend = 0; + } + musb_ep_select(musb->mregs, 0); + musb_writew(regs, MUSB_CSR0, csr); +} + +/* + * transmitting to the host (IN), this code might be called from IRQ + * and from kernel thread. + * + * Context: caller holds controller lock + */ +static void ep0_txstate(struct musb *musb) +{ + void __iomem *regs = musb->control_ep->regs; + struct musb_request *req = next_ep0_request(musb); + struct usb_request *request; + u16 csr = MUSB_CSR0_TXPKTRDY; + u8 *fifo_src; + u8 fifo_count; + + if (!req) { + /* WARN_ON(1); */ + dev_dbg(musb->controller, "odd; csr0 %04x\n", musb_readw(regs, MUSB_CSR0)); + return; + } + + request = &req->request; + + /* load the data */ + fifo_src = (u8 *) request->buf + request->actual; + fifo_count = min((unsigned) MUSB_EP0_FIFOSIZE, + request->length - request->actual); + musb_write_fifo(&musb->endpoints[0], fifo_count, fifo_src); + request->actual += fifo_count; + + /* update the flags */ + if (fifo_count < MUSB_MAX_END0_PACKET + || (request->actual == request->length + && !request->zero)) { + musb->ep0_state = MUSB_EP0_STAGE_STATUSOUT; + csr |= MUSB_CSR0_P_DATAEND; + } else + request = NULL; + + /* report completions as soon as the fifo's loaded; there's no + * win in waiting till this last packet gets acked. (other than + * very precise fault reporting, needed by USB TMC; possible with + * this hardware, but not usable from portable gadget drivers.) + */ + if (request) { + musb->ackpend = csr; + musb_g_ep0_giveback(musb, request); + if (!musb->ackpend) + return; + musb->ackpend = 0; + } + + /* send it out, triggering a "txpktrdy cleared" irq */ + musb_ep_select(musb->mregs, 0); + musb_writew(regs, MUSB_CSR0, csr); +} + +/* + * Read a SETUP packet (struct usb_ctrlrequest) from the hardware. + * Fields are left in USB byte-order. + * + * Context: caller holds controller lock. + */ +static void +musb_read_setup(struct musb *musb, struct usb_ctrlrequest *req) +{ + struct musb_request *r; + void __iomem *regs = musb->control_ep->regs; + + musb_read_fifo(&musb->endpoints[0], sizeof *req, (u8 *)req); + + /* NOTE: earlier 2.6 versions changed setup packets to host + * order, but now USB packets always stay in USB byte order. + */ + dev_dbg(musb->controller, "SETUP req%02x.%02x v%04x i%04x l%d\n", + req->bRequestType, + req->bRequest, + le16_to_cpu(req->wValue), + le16_to_cpu(req->wIndex), + le16_to_cpu(req->wLength)); + + /* clean up any leftover transfers */ + r = next_ep0_request(musb); + if (r) + musb_g_ep0_giveback(musb, &r->request); + + /* For zero-data requests we want to delay the STATUS stage to + * avoid SETUPEND errors. If we read data (OUT), delay accepting + * packets until there's a buffer to store them in. + * + * If we write data, the controller acts happier if we enable + * the TX FIFO right away, and give the controller a moment + * to switch modes... + */ + musb->set_address = false; + musb->ackpend = MUSB_CSR0_P_SVDRXPKTRDY; + if (req->wLength == 0) { + if (req->bRequestType & USB_DIR_IN) + musb->ackpend |= MUSB_CSR0_TXPKTRDY; + musb->ep0_state = MUSB_EP0_STAGE_ACKWAIT; + } else if (req->bRequestType & USB_DIR_IN) { + musb->ep0_state = MUSB_EP0_STAGE_TX; + musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SVDRXPKTRDY); + while ((musb_readw(regs, MUSB_CSR0) + & MUSB_CSR0_RXPKTRDY) != 0) + cpu_relax(); + musb->ackpend = 0; + } else + musb->ep0_state = MUSB_EP0_STAGE_RX; +} + +static int +forward_to_driver(struct musb *musb, const struct usb_ctrlrequest *ctrlrequest) +__releases(musb->lock) +__acquires(musb->lock) +{ + int retval; + if (!musb->gadget_driver) + return -EOPNOTSUPP; + spin_unlock(&musb->lock); + retval = musb->gadget_driver->setup(&musb->g, ctrlrequest); + spin_lock(&musb->lock); + return retval; +} + +/* + * Handle peripheral ep0 interrupt + * + * Context: irq handler; we won't re-enter the driver that way. + */ +irqreturn_t musb_g_ep0_irq(struct musb *musb) +{ + u16 csr; + u16 len; + void __iomem *mbase = musb->mregs; + void __iomem *regs = musb->endpoints[0].regs; + irqreturn_t retval = IRQ_NONE; + + musb_ep_select(mbase, 0); /* select ep0 */ + csr = musb_readw(regs, MUSB_CSR0); + len = musb_readb(regs, MUSB_COUNT0); + + dev_dbg(musb->controller, "csr %04x, count %d, ep0stage %s\n", + csr, len, decode_ep0stage(musb->ep0_state)); + + if (csr & MUSB_CSR0_P_DATAEND) { + /* + * If DATAEND is set we should not call the callback, + * hence the status stage is not complete. + */ + return IRQ_HANDLED; + } + + /* I sent a stall.. need to acknowledge it now.. */ + if (csr & MUSB_CSR0_P_SENTSTALL) { + musb_writew(regs, MUSB_CSR0, + csr & ~MUSB_CSR0_P_SENTSTALL); + retval = IRQ_HANDLED; + musb->ep0_state = MUSB_EP0_STAGE_IDLE; + csr = musb_readw(regs, MUSB_CSR0); + } + + /* request ended "early" */ + if (csr & MUSB_CSR0_P_SETUPEND) { + musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SVDSETUPEND); + retval = IRQ_HANDLED; + /* Transition into the early status phase */ + switch (musb->ep0_state) { + case MUSB_EP0_STAGE_TX: + musb->ep0_state = MUSB_EP0_STAGE_STATUSOUT; + break; + case MUSB_EP0_STAGE_RX: + musb->ep0_state = MUSB_EP0_STAGE_STATUSIN; + break; + default: + ERR("SetupEnd came in a wrong ep0stage %s\n", + decode_ep0stage(musb->ep0_state)); + } + csr = musb_readw(regs, MUSB_CSR0); + /* NOTE: request may need completion */ + } + + /* docs from Mentor only describe tx, rx, and idle/setup states. + * we need to handle nuances around status stages, and also the + * case where status and setup stages come back-to-back ... + */ + switch (musb->ep0_state) { + + case MUSB_EP0_STAGE_TX: + /* irq on clearing txpktrdy */ + if ((csr & MUSB_CSR0_TXPKTRDY) == 0) { + ep0_txstate(musb); + retval = IRQ_HANDLED; + } + break; + + case MUSB_EP0_STAGE_RX: + /* irq on set rxpktrdy */ + if (csr & MUSB_CSR0_RXPKTRDY) { + ep0_rxstate(musb); + retval = IRQ_HANDLED; + } + break; + + case MUSB_EP0_STAGE_STATUSIN: + /* end of sequence #2 (OUT/RX state) or #3 (no data) */ + + /* update address (if needed) only @ the end of the + * status phase per usb spec, which also guarantees + * we get 10 msec to receive this irq... until this + * is done we won't see the next packet. + */ + if (musb->set_address) { + musb->set_address = false; + musb_writeb(mbase, MUSB_FADDR, musb->address); + } + + /* enter test mode if needed (exit by reset) */ + else if (musb->test_mode) { + dev_dbg(musb->controller, "entering TESTMODE\n"); + + if (MUSB_TEST_PACKET == musb->test_mode_nr) + musb_load_testpacket(musb); + + musb_writeb(mbase, MUSB_TESTMODE, + musb->test_mode_nr); + } + /* FALLTHROUGH */ + + case MUSB_EP0_STAGE_STATUSOUT: + /* end of sequence #1: write to host (TX state) */ + { + struct musb_request *req; + + req = next_ep0_request(musb); + if (req) + musb_g_ep0_giveback(musb, &req->request); + } + + /* + * In case when several interrupts can get coalesced, + * check to see if we've already received a SETUP packet... + */ + if (csr & MUSB_CSR0_RXPKTRDY) + goto setup; + + retval = IRQ_HANDLED; + musb->ep0_state = MUSB_EP0_STAGE_IDLE; + break; + + case MUSB_EP0_STAGE_IDLE: + /* + * This state is typically (but not always) indiscernible + * from the status states since the corresponding interrupts + * tend to happen within too little period of time (with only + * a zero-length packet in between) and so get coalesced... + */ + retval = IRQ_HANDLED; + musb->ep0_state = MUSB_EP0_STAGE_SETUP; + /* FALLTHROUGH */ + + case MUSB_EP0_STAGE_SETUP: +setup: + if (csr & MUSB_CSR0_RXPKTRDY) { + struct usb_ctrlrequest setup; + int handled = 0; + + if (len != 8) { + ERR("SETUP packet len %d != 8 ?\n", len); + break; + } + musb_read_setup(musb, &setup); + retval = IRQ_HANDLED; + + /* sometimes the RESET won't be reported */ + if (unlikely(musb->g.speed == USB_SPEED_UNKNOWN)) { + u8 power; + + printk(KERN_NOTICE "%s: peripheral reset " + "irq lost!\n", + musb_driver_name); + power = musb_readb(mbase, MUSB_POWER); + musb->g.speed = (power & MUSB_POWER_HSMODE) + ? USB_SPEED_HIGH : USB_SPEED_FULL; + + } + + switch (musb->ep0_state) { + + /* sequence #3 (no data stage), includes requests + * we can't forward (notably SET_ADDRESS and the + * device/endpoint feature set/clear operations) + * plus SET_CONFIGURATION and others we must + */ + case MUSB_EP0_STAGE_ACKWAIT: + handled = service_zero_data_request( + musb, &setup); + + /* + * We're expecting no data in any case, so + * always set the DATAEND bit -- doing this + * here helps avoid SetupEnd interrupt coming + * in the idle stage when we're stalling... + */ + musb->ackpend |= MUSB_CSR0_P_DATAEND; + + /* status stage might be immediate */ + if (handled > 0) + musb->ep0_state = + MUSB_EP0_STAGE_STATUSIN; + break; + + /* sequence #1 (IN to host), includes GET_STATUS + * requests that we can't forward, GET_DESCRIPTOR + * and others that we must + */ + case MUSB_EP0_STAGE_TX: + handled = service_in_request(musb, &setup); + if (handled > 0) { + musb->ackpend = MUSB_CSR0_TXPKTRDY + | MUSB_CSR0_P_DATAEND; + musb->ep0_state = + MUSB_EP0_STAGE_STATUSOUT; + } + break; + + /* sequence #2 (OUT from host), always forward */ + default: /* MUSB_EP0_STAGE_RX */ + break; + } + + dev_dbg(musb->controller, "handled %d, csr %04x, ep0stage %s\n", + handled, csr, + decode_ep0stage(musb->ep0_state)); + + /* unless we need to delegate this to the gadget + * driver, we know how to wrap this up: csr0 has + * not yet been written. + */ + if (handled < 0) + goto stall; + else if (handled > 0) + goto finish; + + handled = forward_to_driver(musb, &setup); + if (handled < 0) { + musb_ep_select(mbase, 0); +stall: + dev_dbg(musb->controller, "stall (%d)\n", handled); + musb->ackpend |= MUSB_CSR0_P_SENDSTALL; + musb->ep0_state = MUSB_EP0_STAGE_IDLE; +finish: + musb_writew(regs, MUSB_CSR0, + musb->ackpend); + musb->ackpend = 0; + } + } + break; + + case MUSB_EP0_STAGE_ACKWAIT: + /* This should not happen. But happens with tusb6010 with + * g_file_storage and high speed. Do nothing. + */ + retval = IRQ_HANDLED; + break; + + default: + /* "can't happen" */ + WARN_ON(1); + musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SENDSTALL); + musb->ep0_state = MUSB_EP0_STAGE_IDLE; + break; + } + + return retval; +} + + +static int +musb_g_ep0_enable(struct usb_ep *ep, const struct usb_endpoint_descriptor *desc) +{ + /* always enabled */ + return -EINVAL; +} + +static int musb_g_ep0_disable(struct usb_ep *e) +{ + /* always enabled */ + return -EINVAL; +} + +static int +musb_g_ep0_queue(struct usb_ep *e, struct usb_request *r, gfp_t gfp_flags) +{ + struct musb_ep *ep; + struct musb_request *req; + struct musb *musb; + int status; + unsigned long lockflags; + void __iomem *regs; + + if (!e || !r) + return -EINVAL; + + ep = to_musb_ep(e); + musb = ep->musb; + regs = musb->control_ep->regs; + + req = to_musb_request(r); + req->musb = musb; + req->request.actual = 0; + req->request.status = -EINPROGRESS; + req->tx = ep->is_in; + + spin_lock_irqsave(&musb->lock, lockflags); + + if (!list_empty(&ep->req_list)) { + status = -EBUSY; + goto cleanup; + } + + switch (musb->ep0_state) { + case MUSB_EP0_STAGE_RX: /* control-OUT data */ + case MUSB_EP0_STAGE_TX: /* control-IN data */ + case MUSB_EP0_STAGE_ACKWAIT: /* zero-length data */ + status = 0; + break; + default: + dev_dbg(musb->controller, "ep0 request queued in state %d\n", + musb->ep0_state); + status = -EINVAL; + goto cleanup; + } + + /* add request to the list */ + list_add_tail(&req->list, &ep->req_list); + + dev_dbg(musb->controller, "queue to %s (%s), length=%d\n", + ep->name, ep->is_in ? "IN/TX" : "OUT/RX", + req->request.length); + + musb_ep_select(musb->mregs, 0); + + /* sequence #1, IN ... start writing the data */ + if (musb->ep0_state == MUSB_EP0_STAGE_TX) + ep0_txstate(musb); + + /* sequence #3, no-data ... issue IN status */ + else if (musb->ep0_state == MUSB_EP0_STAGE_ACKWAIT) { + if (req->request.length) + status = -EINVAL; + else { + musb->ep0_state = MUSB_EP0_STAGE_STATUSIN; + musb_writew(regs, MUSB_CSR0, + musb->ackpend | MUSB_CSR0_P_DATAEND); + musb->ackpend = 0; + musb_g_ep0_giveback(ep->musb, r); + } + + /* else for sequence #2 (OUT), caller provides a buffer + * before the next packet arrives. deferred responses + * (after SETUP is acked) are racey. + */ + } else if (musb->ackpend) { + musb_writew(regs, MUSB_CSR0, musb->ackpend); + musb->ackpend = 0; + } + +cleanup: + spin_unlock_irqrestore(&musb->lock, lockflags); + return status; +} + +static int musb_g_ep0_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + /* we just won't support this */ + return -EINVAL; +} + +static int musb_g_ep0_halt(struct usb_ep *e, int value) +{ + struct musb_ep *ep; + struct musb *musb; + void __iomem *base, *regs; + unsigned long flags; + int status; + u16 csr; + + if (!e || !value) + return -EINVAL; + + ep = to_musb_ep(e); + musb = ep->musb; + base = musb->mregs; + regs = musb->control_ep->regs; + status = 0; + + spin_lock_irqsave(&musb->lock, flags); + + if (!list_empty(&ep->req_list)) { + status = -EBUSY; + goto cleanup; + } + + musb_ep_select(base, 0); + csr = musb->ackpend; + + switch (musb->ep0_state) { + + /* Stalls are usually issued after parsing SETUP packet, either + * directly in irq context from setup() or else later. + */ + case MUSB_EP0_STAGE_TX: /* control-IN data */ + case MUSB_EP0_STAGE_ACKWAIT: /* STALL for zero-length data */ + case MUSB_EP0_STAGE_RX: /* control-OUT data */ + csr = musb_readw(regs, MUSB_CSR0); + /* FALLTHROUGH */ + + /* It's also OK to issue stalls during callbacks when a non-empty + * DATA stage buffer has been read (or even written). + */ + case MUSB_EP0_STAGE_STATUSIN: /* control-OUT status */ + case MUSB_EP0_STAGE_STATUSOUT: /* control-IN status */ + + csr |= MUSB_CSR0_P_SENDSTALL; + musb_writew(regs, MUSB_CSR0, csr); + musb->ep0_state = MUSB_EP0_STAGE_IDLE; + musb->ackpend = 0; + break; + default: + dev_dbg(musb->controller, "ep0 can't halt in state %d\n", musb->ep0_state); + status = -EINVAL; + } + +cleanup: + spin_unlock_irqrestore(&musb->lock, flags); + return status; +} + +const struct usb_ep_ops musb_g_ep0_ops = { + .enable = musb_g_ep0_enable, + .disable = musb_g_ep0_disable, + .alloc_request = musb_alloc_request, + .free_request = musb_free_request, + .queue = musb_g_ep0_queue, + .dequeue = musb_g_ep0_dequeue, + .set_halt = musb_g_ep0_halt, +}; diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c new file mode 100644 index 00000000..9d3044bd --- /dev/null +++ b/drivers/usb/musb/musb_host.c @@ -0,0 +1,2608 @@ +/* + * MUSB OTG driver host support + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2008-2009 MontaVista Software, Inc. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" +#include "musb_host.h" + + +/* MUSB HOST status 22-mar-2006 + * + * - There's still lots of partial code duplication for fault paths, so + * they aren't handled as consistently as they need to be. + * + * - PIO mostly behaved when last tested. + * + including ep0, with all usbtest cases 9, 10 + * + usbtest 14 (ep0out) doesn't seem to run at all + * + double buffered OUT/TX endpoints saw stalls(!) with certain usbtest + * configurations, but otherwise double buffering passes basic tests. + * + for 2.6.N, for N > ~10, needs API changes for hcd framework. + * + * - DMA (CPPI) ... partially behaves, not currently recommended + * + about 1/15 the speed of typical EHCI implementations (PCI) + * + RX, all too often reqpkt seems to misbehave after tx + * + TX, no known issues (other than evident silicon issue) + * + * - DMA (Mentor/OMAP) ...has at least toggle update problems + * + * - [23-feb-2009] minimal traffic scheduling to avoid bulk RX packet + * starvation ... nothing yet for TX, interrupt, or bulk. + * + * - Not tested with HNP, but some SRP paths seem to behave. + * + * NOTE 24-August-2006: + * + * - Bulk traffic finally uses both sides of hardware ep1, freeing up an + * extra endpoint for periodic use enabling hub + keybd + mouse. That + * mostly works, except that with "usbnet" it's easy to trigger cases + * with "ping" where RX loses. (a) ping to davinci, even "ping -f", + * fine; but (b) ping _from_ davinci, even "ping -c 1", ICMP RX loses + * although ARP RX wins. (That test was done with a full speed link.) + */ + + +/* + * NOTE on endpoint usage: + * + * CONTROL transfers all go through ep0. BULK ones go through dedicated IN + * and OUT endpoints ... hardware is dedicated for those "async" queue(s). + * (Yes, bulk _could_ use more of the endpoints than that, and would even + * benefit from it.) + * + * INTERUPPT and ISOCHRONOUS transfers are scheduled to the other endpoints. + * So far that scheduling is both dumb and optimistic: the endpoint will be + * "claimed" until its software queue is no longer refilled. No multiplexing + * of transfers between endpoints, or anything clever. + */ + + +static void musb_ep_program(struct musb *musb, u8 epnum, + struct urb *urb, int is_out, + u8 *buf, u32 offset, u32 len); + +/* + * Clear TX fifo. Needed to avoid BABBLE errors. + */ +static void musb_h_tx_flush_fifo(struct musb_hw_ep *ep) +{ + struct musb *musb = ep->musb; + void __iomem *epio = ep->regs; + u16 csr; + u16 lastcsr = 0; + int retries = 1000; + + csr = musb_readw(epio, MUSB_TXCSR); + while (csr & MUSB_TXCSR_FIFONOTEMPTY) { + if (csr != lastcsr) + dev_dbg(musb->controller, "Host TX FIFONOTEMPTY csr: %02x\n", csr); + lastcsr = csr; + csr |= MUSB_TXCSR_FLUSHFIFO; + musb_writew(epio, MUSB_TXCSR, csr); + csr = musb_readw(epio, MUSB_TXCSR); + if (WARN(retries-- < 1, + "Could not flush host TX%d fifo: csr: %04x\n", + ep->epnum, csr)) + return; + mdelay(1); + } +} + +static void musb_h_ep0_flush_fifo(struct musb_hw_ep *ep) +{ + void __iomem *epio = ep->regs; + u16 csr; + int retries = 5; + + /* scrub any data left in the fifo */ + do { + csr = musb_readw(epio, MUSB_TXCSR); + if (!(csr & (MUSB_CSR0_TXPKTRDY | MUSB_CSR0_RXPKTRDY))) + break; + musb_writew(epio, MUSB_TXCSR, MUSB_CSR0_FLUSHFIFO); + csr = musb_readw(epio, MUSB_TXCSR); + udelay(10); + } while (--retries); + + WARN(!retries, "Could not flush host TX%d fifo: csr: %04x\n", + ep->epnum, csr); + + /* and reset for the next transfer */ + musb_writew(epio, MUSB_TXCSR, 0); +} + +/* + * Start transmit. Caller is responsible for locking shared resources. + * musb must be locked. + */ +static inline void musb_h_tx_start(struct musb_hw_ep *ep) +{ + u16 txcsr; + + /* NOTE: no locks here; caller should lock and select EP */ + if (ep->epnum) { + txcsr = musb_readw(ep->regs, MUSB_TXCSR); + txcsr |= MUSB_TXCSR_TXPKTRDY | MUSB_TXCSR_H_WZC_BITS; + musb_writew(ep->regs, MUSB_TXCSR, txcsr); + } else { + txcsr = MUSB_CSR0_H_SETUPPKT | MUSB_CSR0_TXPKTRDY; + musb_writew(ep->regs, MUSB_CSR0, txcsr); + } + +} + +static inline void musb_h_tx_dma_start(struct musb_hw_ep *ep) +{ + u16 txcsr; + + /* NOTE: no locks here; caller should lock and select EP */ + txcsr = musb_readw(ep->regs, MUSB_TXCSR); + txcsr |= MUSB_TXCSR_DMAENAB | MUSB_TXCSR_H_WZC_BITS; + if (is_cppi_enabled()) + txcsr |= MUSB_TXCSR_DMAMODE; + musb_writew(ep->regs, MUSB_TXCSR, txcsr); +} + +static void musb_ep_set_qh(struct musb_hw_ep *ep, int is_in, struct musb_qh *qh) +{ + if (is_in != 0 || ep->is_shared_fifo) + ep->in_qh = qh; + if (is_in == 0 || ep->is_shared_fifo) + ep->out_qh = qh; +} + +static struct musb_qh *musb_ep_get_qh(struct musb_hw_ep *ep, int is_in) +{ + return is_in ? ep->in_qh : ep->out_qh; +} + +/* + * Start the URB at the front of an endpoint's queue + * end must be claimed from the caller. + * + * Context: controller locked, irqs blocked + */ +static void +musb_start_urb(struct musb *musb, int is_in, struct musb_qh *qh) +{ + u16 frame; + u32 len; + void __iomem *mbase = musb->mregs; + struct urb *urb = next_urb(qh); + void *buf = urb->transfer_buffer; + u32 offset = 0; + struct musb_hw_ep *hw_ep = qh->hw_ep; + unsigned pipe = urb->pipe; + u8 address = usb_pipedevice(pipe); + int epnum = hw_ep->epnum; + + /* initialize software qh state */ + qh->offset = 0; + qh->segsize = 0; + + /* gather right source of data */ + switch (qh->type) { + case USB_ENDPOINT_XFER_CONTROL: + /* control transfers always start with SETUP */ + is_in = 0; + musb->ep0_stage = MUSB_EP0_START; + buf = urb->setup_packet; + len = 8; + break; + case USB_ENDPOINT_XFER_ISOC: + qh->iso_idx = 0; + qh->frame = 0; + offset = urb->iso_frame_desc[0].offset; + len = urb->iso_frame_desc[0].length; + break; + default: /* bulk, interrupt */ + /* actual_length may be nonzero on retry paths */ + buf = urb->transfer_buffer + urb->actual_length; + len = urb->transfer_buffer_length - urb->actual_length; + } + + dev_dbg(musb->controller, "qh %p urb %p dev%d ep%d%s%s, hw_ep %d, %p/%d\n", + qh, urb, address, qh->epnum, + is_in ? "in" : "out", + ({char *s; switch (qh->type) { + case USB_ENDPOINT_XFER_CONTROL: s = ""; break; + case USB_ENDPOINT_XFER_BULK: s = "-bulk"; break; + case USB_ENDPOINT_XFER_ISOC: s = "-iso"; break; + default: s = "-intr"; break; + }; s; }), + epnum, buf + offset, len); + + /* Configure endpoint */ + musb_ep_set_qh(hw_ep, is_in, qh); + musb_ep_program(musb, epnum, urb, !is_in, buf, offset, len); + + /* transmit may have more work: start it when it is time */ + if (is_in) + return; + + /* determine if the time is right for a periodic transfer */ + switch (qh->type) { + case USB_ENDPOINT_XFER_ISOC: + case USB_ENDPOINT_XFER_INT: + dev_dbg(musb->controller, "check whether there's still time for periodic Tx\n"); + frame = musb_readw(mbase, MUSB_FRAME); + /* FIXME this doesn't implement that scheduling policy ... + * or handle framecounter wrapping + */ + if ((urb->transfer_flags & URB_ISO_ASAP) + || (frame >= urb->start_frame)) { + /* REVISIT the SOF irq handler shouldn't duplicate + * this code; and we don't init urb->start_frame... + */ + qh->frame = 0; + goto start; + } else { + qh->frame = urb->start_frame; + /* enable SOF interrupt so we can count down */ + dev_dbg(musb->controller, "SOF for %d\n", epnum); +#if 1 /* ifndef CONFIG_ARCH_DAVINCI */ + musb_writeb(mbase, MUSB_INTRUSBE, 0xff); +#endif + } + break; + default: +start: + dev_dbg(musb->controller, "Start TX%d %s\n", epnum, + hw_ep->tx_channel ? "dma" : "pio"); + + if (!hw_ep->tx_channel) + musb_h_tx_start(hw_ep); + else if (is_cppi_enabled() || tusb_dma_omap()) + musb_h_tx_dma_start(hw_ep); + } +} + +/* Context: caller owns controller lock, IRQs are blocked */ +static void musb_giveback(struct musb *musb, struct urb *urb, int status) +__releases(musb->lock) +__acquires(musb->lock) +{ + dev_dbg(musb->controller, + "complete %p %pF (%d), dev%d ep%d%s, %d/%d\n", + urb, urb->complete, status, + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + urb->actual_length, urb->transfer_buffer_length + ); + + usb_hcd_unlink_urb_from_ep(musb_to_hcd(musb), urb); + spin_unlock(&musb->lock); + usb_hcd_giveback_urb(musb_to_hcd(musb), urb, status); + spin_lock(&musb->lock); +} + +/* For bulk/interrupt endpoints only */ +static inline void musb_save_toggle(struct musb_qh *qh, int is_in, + struct urb *urb) +{ + void __iomem *epio = qh->hw_ep->regs; + u16 csr; + + /* + * FIXME: the current Mentor DMA code seems to have + * problems getting toggle correct. + */ + + if (is_in) + csr = musb_readw(epio, MUSB_RXCSR) & MUSB_RXCSR_H_DATATOGGLE; + else + csr = musb_readw(epio, MUSB_TXCSR) & MUSB_TXCSR_H_DATATOGGLE; + + usb_settoggle(urb->dev, qh->epnum, !is_in, csr ? 1 : 0); +} + +/* + * Advance this hardware endpoint's queue, completing the specified URB and + * advancing to either the next URB queued to that qh, or else invalidating + * that qh and advancing to the next qh scheduled after the current one. + * + * Context: caller owns controller lock, IRQs are blocked + */ +static void musb_advance_schedule(struct musb *musb, struct urb *urb, + struct musb_hw_ep *hw_ep, int is_in) +{ + struct musb_qh *qh = musb_ep_get_qh(hw_ep, is_in); + struct musb_hw_ep *ep = qh->hw_ep; + int ready = qh->is_ready; + int status; + + status = (urb->status == -EINPROGRESS) ? 0 : urb->status; + + /* save toggle eagerly, for paranoia */ + switch (qh->type) { + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + musb_save_toggle(qh, is_in, urb); + break; + case USB_ENDPOINT_XFER_ISOC: + if (status == 0 && urb->error_count) + status = -EXDEV; + break; + } + + qh->is_ready = 0; + musb_giveback(musb, urb, status); + qh->is_ready = ready; + + /* reclaim resources (and bandwidth) ASAP; deschedule it, and + * invalidate qh as soon as list_empty(&hep->urb_list) + */ + if (list_empty(&qh->hep->urb_list)) { + struct list_head *head; + struct dma_controller *dma = musb->dma_controller; + + if (is_in) { + ep->rx_reinit = 1; + if (ep->rx_channel) { + dma->channel_release(ep->rx_channel); + ep->rx_channel = NULL; + } + } else { + ep->tx_reinit = 1; + if (ep->tx_channel) { + dma->channel_release(ep->tx_channel); + ep->tx_channel = NULL; + } + } + + /* Clobber old pointers to this qh */ + musb_ep_set_qh(ep, is_in, NULL); + qh->hep->hcpriv = NULL; + + switch (qh->type) { + + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + /* fifo policy for these lists, except that NAKing + * should rotate a qh to the end (for fairness). + */ + if (qh->mux == 1) { + head = qh->ring.prev; + list_del(&qh->ring); + kfree(qh); + qh = first_qh(head); + break; + } + + case USB_ENDPOINT_XFER_ISOC: + case USB_ENDPOINT_XFER_INT: + /* this is where periodic bandwidth should be + * de-allocated if it's tracked and allocated; + * and where we'd update the schedule tree... + */ + kfree(qh); + qh = NULL; + break; + } + } + + if (qh != NULL && qh->is_ready) { + dev_dbg(musb->controller, "... next ep%d %cX urb %p\n", + hw_ep->epnum, is_in ? 'R' : 'T', next_urb(qh)); + musb_start_urb(musb, is_in, qh); + } +} + +static u16 musb_h_flush_rxfifo(struct musb_hw_ep *hw_ep, u16 csr) +{ + /* we don't want fifo to fill itself again; + * ignore dma (various models), + * leave toggle alone (may not have been saved yet) + */ + csr |= MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_RXPKTRDY; + csr &= ~(MUSB_RXCSR_H_REQPKT + | MUSB_RXCSR_H_AUTOREQ + | MUSB_RXCSR_AUTOCLEAR); + + /* write 2x to allow double buffering */ + musb_writew(hw_ep->regs, MUSB_RXCSR, csr); + musb_writew(hw_ep->regs, MUSB_RXCSR, csr); + + /* flush writebuffer */ + return musb_readw(hw_ep->regs, MUSB_RXCSR); +} + +/* + * PIO RX for a packet (or part of it). + */ +static bool +musb_host_packet_rx(struct musb *musb, struct urb *urb, u8 epnum, u8 iso_err) +{ + u16 rx_count; + u8 *buf; + u16 csr; + bool done = false; + u32 length; + int do_flush = 0; + struct musb_hw_ep *hw_ep = musb->endpoints + epnum; + void __iomem *epio = hw_ep->regs; + struct musb_qh *qh = hw_ep->in_qh; + int pipe = urb->pipe; + void *buffer = urb->transfer_buffer; + + /* musb_ep_select(mbase, epnum); */ + rx_count = musb_readw(epio, MUSB_RXCOUNT); + dev_dbg(musb->controller, "RX%d count %d, buffer %p len %d/%d\n", epnum, rx_count, + urb->transfer_buffer, qh->offset, + urb->transfer_buffer_length); + + /* unload FIFO */ + if (usb_pipeisoc(pipe)) { + int status = 0; + struct usb_iso_packet_descriptor *d; + + if (iso_err) { + status = -EILSEQ; + urb->error_count++; + } + + d = urb->iso_frame_desc + qh->iso_idx; + buf = buffer + d->offset; + length = d->length; + if (rx_count > length) { + if (status == 0) { + status = -EOVERFLOW; + urb->error_count++; + } + dev_dbg(musb->controller, "** OVERFLOW %d into %d\n", rx_count, length); + do_flush = 1; + } else + length = rx_count; + urb->actual_length += length; + d->actual_length = length; + + d->status = status; + + /* see if we are done */ + done = (++qh->iso_idx >= urb->number_of_packets); + } else { + /* non-isoch */ + buf = buffer + qh->offset; + length = urb->transfer_buffer_length - qh->offset; + if (rx_count > length) { + if (urb->status == -EINPROGRESS) + urb->status = -EOVERFLOW; + dev_dbg(musb->controller, "** OVERFLOW %d into %d\n", rx_count, length); + do_flush = 1; + } else + length = rx_count; + urb->actual_length += length; + qh->offset += length; + + /* see if we are done */ + done = (urb->actual_length == urb->transfer_buffer_length) + || (rx_count < qh->maxpacket) + || (urb->status != -EINPROGRESS); + if (done + && (urb->status == -EINPROGRESS) + && (urb->transfer_flags & URB_SHORT_NOT_OK) + && (urb->actual_length + < urb->transfer_buffer_length)) + urb->status = -EREMOTEIO; + } + + musb_read_fifo(hw_ep, length, buf); + + csr = musb_readw(epio, MUSB_RXCSR); + csr |= MUSB_RXCSR_H_WZC_BITS; + if (unlikely(do_flush)) + musb_h_flush_rxfifo(hw_ep, csr); + else { + /* REVISIT this assumes AUTOCLEAR is never set */ + csr &= ~(MUSB_RXCSR_RXPKTRDY | MUSB_RXCSR_H_REQPKT); + if (!done) + csr |= MUSB_RXCSR_H_REQPKT; + musb_writew(epio, MUSB_RXCSR, csr); + } + + return done; +} + +/* we don't always need to reinit a given side of an endpoint... + * when we do, use tx/rx reinit routine and then construct a new CSR + * to address data toggle, NYET, and DMA or PIO. + * + * it's possible that driver bugs (especially for DMA) or aborting a + * transfer might have left the endpoint busier than it should be. + * the busy/not-empty tests are basically paranoia. + */ +static void +musb_rx_reinit(struct musb *musb, struct musb_qh *qh, struct musb_hw_ep *ep) +{ + u16 csr; + + /* NOTE: we know the "rx" fifo reinit never triggers for ep0. + * That always uses tx_reinit since ep0 repurposes TX register + * offsets; the initial SETUP packet is also a kind of OUT. + */ + + /* if programmed for Tx, put it in RX mode */ + if (ep->is_shared_fifo) { + csr = musb_readw(ep->regs, MUSB_TXCSR); + if (csr & MUSB_TXCSR_MODE) { + musb_h_tx_flush_fifo(ep); + csr = musb_readw(ep->regs, MUSB_TXCSR); + musb_writew(ep->regs, MUSB_TXCSR, + csr | MUSB_TXCSR_FRCDATATOG); + } + + /* + * Clear the MODE bit (and everything else) to enable Rx. + * NOTE: we mustn't clear the DMAMODE bit before DMAENAB. + */ + if (csr & MUSB_TXCSR_DMAMODE) + musb_writew(ep->regs, MUSB_TXCSR, MUSB_TXCSR_DMAMODE); + musb_writew(ep->regs, MUSB_TXCSR, 0); + + /* scrub all previous state, clearing toggle */ + } else { + csr = musb_readw(ep->regs, MUSB_RXCSR); + if (csr & MUSB_RXCSR_RXPKTRDY) + WARNING("rx%d, packet/%d ready?\n", ep->epnum, + musb_readw(ep->regs, MUSB_RXCOUNT)); + + musb_h_flush_rxfifo(ep, MUSB_RXCSR_CLRDATATOG); + } + + /* target addr and (for multipoint) hub addr/port */ + if (musb->is_multipoint) { + musb_write_rxfunaddr(ep->target_regs, qh->addr_reg); + musb_write_rxhubaddr(ep->target_regs, qh->h_addr_reg); + musb_write_rxhubport(ep->target_regs, qh->h_port_reg); + + } else + musb_writeb(musb->mregs, MUSB_FADDR, qh->addr_reg); + + /* protocol/endpoint, interval/NAKlimit, i/o size */ + musb_writeb(ep->regs, MUSB_RXTYPE, qh->type_reg); + musb_writeb(ep->regs, MUSB_RXINTERVAL, qh->intv_reg); + /* NOTE: bulk combining rewrites high bits of maxpacket */ + /* Set RXMAXP with the FIFO size of the endpoint + * to disable double buffer mode. + */ + if (musb->double_buffer_not_ok) + musb_writew(ep->regs, MUSB_RXMAXP, ep->max_packet_sz_rx); + else + musb_writew(ep->regs, MUSB_RXMAXP, + qh->maxpacket | ((qh->hb_mult - 1) << 11)); + + ep->rx_reinit = 0; +} + +static bool musb_tx_dma_program(struct dma_controller *dma, + struct musb_hw_ep *hw_ep, struct musb_qh *qh, + struct urb *urb, u32 offset, u32 length) +{ + struct dma_channel *channel = hw_ep->tx_channel; + void __iomem *epio = hw_ep->regs; + u16 pkt_size = qh->maxpacket; + u16 csr; + u8 mode; + +#ifdef CONFIG_USB_INVENTRA_DMA + if (length > channel->max_len) + length = channel->max_len; + + csr = musb_readw(epio, MUSB_TXCSR); + if (length > pkt_size) { + mode = 1; + csr |= MUSB_TXCSR_DMAMODE | MUSB_TXCSR_DMAENAB; + /* autoset shouldn't be set in high bandwidth */ + /* + * Enable Autoset according to table + * below + * bulk_split hb_mult Autoset_Enable + * 0 1 Yes(Normal) + * 0 >1 No(High BW ISO) + * 1 1 Yes(HS bulk) + * 1 >1 Yes(FS bulk) + */ + if (qh->hb_mult == 1 || (qh->hb_mult > 1 && + can_bulk_split(hw_ep->musb, qh->type))) + csr |= MUSB_TXCSR_AUTOSET; + } else { + mode = 0; + csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAMODE); + csr |= MUSB_TXCSR_DMAENAB; /* against programmer's guide */ + } + channel->desired_mode = mode; + musb_writew(epio, MUSB_TXCSR, csr); +#else + if (!is_cppi_enabled() && !tusb_dma_omap()) + return false; + + channel->actual_len = 0; + + /* + * TX uses "RNDIS" mode automatically but needs help + * to identify the zero-length-final-packet case. + */ + mode = (urb->transfer_flags & URB_ZERO_PACKET) ? 1 : 0; +#endif + + qh->segsize = length; + + /* + * Ensure the data reaches to main memory before starting + * DMA transfer + */ + wmb(); + + if (!dma->channel_program(channel, pkt_size, mode, + urb->transfer_dma + offset, length)) { + dma->channel_release(channel); + hw_ep->tx_channel = NULL; + + csr = musb_readw(epio, MUSB_TXCSR); + csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB); + musb_writew(epio, MUSB_TXCSR, csr | MUSB_TXCSR_H_WZC_BITS); + return false; + } + return true; +} + +/* + * Program an HDRC endpoint as per the given URB + * Context: irqs blocked, controller lock held + */ +static void musb_ep_program(struct musb *musb, u8 epnum, + struct urb *urb, int is_out, + u8 *buf, u32 offset, u32 len) +{ + struct dma_controller *dma_controller; + struct dma_channel *dma_channel; + u8 dma_ok; + void __iomem *mbase = musb->mregs; + struct musb_hw_ep *hw_ep = musb->endpoints + epnum; + void __iomem *epio = hw_ep->regs; + struct musb_qh *qh = musb_ep_get_qh(hw_ep, !is_out); + u16 packet_sz = qh->maxpacket; + u8 use_dma = 1; + u16 csr; + + dev_dbg(musb->controller, "%s hw%d urb %p spd%d dev%d ep%d%s " + "h_addr%02x h_port%02x bytes %d\n", + is_out ? "-->" : "<--", + epnum, urb, urb->dev->speed, + qh->addr_reg, qh->epnum, is_out ? "out" : "in", + qh->h_addr_reg, qh->h_port_reg, + len); + + musb_ep_select(mbase, epnum); + + if (is_out && !len) { + use_dma = 0; + csr = musb_readw(epio, MUSB_TXCSR); + csr &= ~MUSB_TXCSR_DMAENAB; + musb_writew(epio, MUSB_TXCSR, csr); + hw_ep->tx_channel = NULL; + } + + /* candidate for DMA? */ + dma_controller = musb->dma_controller; + if (use_dma && is_dma_capable() && epnum && dma_controller) { + dma_channel = is_out ? hw_ep->tx_channel : hw_ep->rx_channel; + if (!dma_channel) { + dma_channel = dma_controller->channel_alloc( + dma_controller, hw_ep, is_out); + if (is_out) + hw_ep->tx_channel = dma_channel; + else + hw_ep->rx_channel = dma_channel; + } + } else + dma_channel = NULL; + + /* make sure we clear DMAEnab, autoSet bits from previous run */ + + /* OUT/transmit/EP0 or IN/receive? */ + if (is_out) { + u16 csr; + u16 int_txe; + u16 load_count; + + csr = musb_readw(epio, MUSB_TXCSR); + + /* disable interrupt in case we flush */ + int_txe = musb->intrtxe; + musb_writew(mbase, MUSB_INTRTXE, int_txe & ~(1 << epnum)); + + /* general endpoint setup */ + if (epnum) { + /* flush all old state, set default */ + /* + * We could be flushing valid + * packets in double buffering + * case + */ + if (!hw_ep->tx_double_buffered) + musb_h_tx_flush_fifo(hw_ep); + + /* + * We must not clear the DMAMODE bit before or in + * the same cycle with the DMAENAB bit, so we clear + * the latter first... + */ + csr &= ~(MUSB_TXCSR_H_NAKTIMEOUT + | MUSB_TXCSR_AUTOSET + | MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_FRCDATATOG + | MUSB_TXCSR_H_RXSTALL + | MUSB_TXCSR_H_ERROR + | MUSB_TXCSR_TXPKTRDY + ); + csr |= MUSB_TXCSR_MODE; + + if (!hw_ep->tx_double_buffered) { + if (usb_gettoggle(urb->dev, qh->epnum, 1)) + csr |= MUSB_TXCSR_H_WR_DATATOGGLE + | MUSB_TXCSR_H_DATATOGGLE; + else + csr |= MUSB_TXCSR_CLRDATATOG; + } + + musb_writew(epio, MUSB_TXCSR, csr); + /* REVISIT may need to clear FLUSHFIFO ... */ + csr &= ~MUSB_TXCSR_DMAMODE; + musb_writew(epio, MUSB_TXCSR, csr); + csr = musb_readw(epio, MUSB_TXCSR); + } else { + /* endpoint 0: just flush */ + musb_h_ep0_flush_fifo(hw_ep); + } + + /* target addr and (for multipoint) hub addr/port */ + if (musb->is_multipoint) { + musb_write_txfunaddr(mbase, epnum, qh->addr_reg); + musb_write_txhubaddr(mbase, epnum, qh->h_addr_reg); + musb_write_txhubport(mbase, epnum, qh->h_port_reg); +/* FIXME if !epnum, do the same for RX ... */ + } else + musb_writeb(mbase, MUSB_FADDR, qh->addr_reg); + + /* protocol/endpoint/interval/NAKlimit */ + if (epnum) { + musb_writeb(epio, MUSB_TXTYPE, qh->type_reg); + if (musb->double_buffer_not_ok) { + musb_writew(epio, MUSB_TXMAXP, + hw_ep->max_packet_sz_tx); + } else if (can_bulk_split(musb, qh->type)) { + qh->hb_mult = hw_ep->max_packet_sz_tx + / packet_sz; + musb_writew(epio, MUSB_TXMAXP, packet_sz + | ((qh->hb_mult) - 1) << 11); + } else { + musb_writew(epio, MUSB_TXMAXP, + qh->maxpacket | + ((qh->hb_mult - 1) << 11)); + } + musb_writeb(epio, MUSB_TXINTERVAL, qh->intv_reg); + } else { + musb_writeb(epio, MUSB_NAKLIMIT0, qh->intv_reg); + if (musb->is_multipoint) + musb_writeb(epio, MUSB_TYPE0, + qh->type_reg); + } + + if (can_bulk_split(musb, qh->type)) + load_count = min((u32) hw_ep->max_packet_sz_tx, + len); + else + load_count = min((u32) packet_sz, len); + + if (dma_channel && musb_tx_dma_program(dma_controller, + hw_ep, qh, urb, offset, len)) + load_count = 0; + + if (load_count) { + /* PIO to load FIFO */ + qh->segsize = load_count; + if (!buf) { + sg_miter_start(&qh->sg_miter, urb->sg, 1, + SG_MITER_ATOMIC + | SG_MITER_FROM_SG); + if (!sg_miter_next(&qh->sg_miter)) { + dev_err(musb->controller, + "error: sg" + "list empty\n"); + sg_miter_stop(&qh->sg_miter); + goto finish; + } + buf = qh->sg_miter.addr + urb->sg->offset + + urb->actual_length; + load_count = min_t(u32, load_count, + qh->sg_miter.length); + musb_write_fifo(hw_ep, load_count, buf); + qh->sg_miter.consumed = load_count; + sg_miter_stop(&qh->sg_miter); + } else + musb_write_fifo(hw_ep, load_count, buf); + } +finish: + /* re-enable interrupt */ + musb_writew(mbase, MUSB_INTRTXE, int_txe); + + /* IN/receive */ + } else { + u16 csr; + + if (hw_ep->rx_reinit) { + musb_rx_reinit(musb, qh, hw_ep); + + /* init new state: toggle and NYET, maybe DMA later */ + if (usb_gettoggle(urb->dev, qh->epnum, 0)) + csr = MUSB_RXCSR_H_WR_DATATOGGLE + | MUSB_RXCSR_H_DATATOGGLE; + else + csr = 0; + if (qh->type == USB_ENDPOINT_XFER_INT) + csr |= MUSB_RXCSR_DISNYET; + + } else { + csr = musb_readw(hw_ep->regs, MUSB_RXCSR); + + if (csr & (MUSB_RXCSR_RXPKTRDY + | MUSB_RXCSR_DMAENAB + | MUSB_RXCSR_H_REQPKT)) + ERR("broken !rx_reinit, ep%d csr %04x\n", + hw_ep->epnum, csr); + + /* scrub any stale state, leaving toggle alone */ + csr &= MUSB_RXCSR_DISNYET; + } + + /* kick things off */ + + if ((is_cppi_enabled() || tusb_dma_omap()) && dma_channel) { + /* Candidate for DMA */ + dma_channel->actual_len = 0L; + qh->segsize = len; + + /* AUTOREQ is in a DMA register */ + musb_writew(hw_ep->regs, MUSB_RXCSR, csr); + csr = musb_readw(hw_ep->regs, MUSB_RXCSR); + + /* + * Unless caller treats short RX transfers as + * errors, we dare not queue multiple transfers. + */ + dma_ok = dma_controller->channel_program(dma_channel, + packet_sz, !(urb->transfer_flags & + URB_SHORT_NOT_OK), + urb->transfer_dma + offset, + qh->segsize); + if (!dma_ok) { + dma_controller->channel_release(dma_channel); + hw_ep->rx_channel = dma_channel = NULL; + } else + csr |= MUSB_RXCSR_DMAENAB; + } + + csr |= MUSB_RXCSR_H_REQPKT; + dev_dbg(musb->controller, "RXCSR%d := %04x\n", epnum, csr); + musb_writew(hw_ep->regs, MUSB_RXCSR, csr); + csr = musb_readw(hw_ep->regs, MUSB_RXCSR); + } +} + +/* Schedule next QH from musb->in_bulk/out_bulk and move the current qh to + * the end; avoids starvation for other endpoints. + */ +static void musb_bulk_nak_timeout(struct musb *musb, struct musb_hw_ep *ep, + int is_in) +{ + struct dma_channel *dma; + struct urb *urb; + void __iomem *mbase = musb->mregs; + void __iomem *epio = ep->regs; + struct musb_qh *cur_qh, *next_qh; + u16 rx_csr, tx_csr; + + musb_ep_select(mbase, ep->epnum); + if (is_in) { + dma = is_dma_capable() ? ep->rx_channel : NULL; + + /* clear nak timeout bit */ + rx_csr = musb_readw(epio, MUSB_RXCSR); + rx_csr |= MUSB_RXCSR_H_WZC_BITS; + rx_csr &= ~MUSB_RXCSR_DATAERROR; + musb_writew(epio, MUSB_RXCSR, rx_csr); + + cur_qh = first_qh(&musb->in_bulk); + } else { + dma = is_dma_capable() ? ep->tx_channel : NULL; + + /* clear nak timeout bit */ + tx_csr = musb_readw(epio, MUSB_TXCSR); + tx_csr |= MUSB_TXCSR_H_WZC_BITS; + tx_csr &= ~MUSB_TXCSR_H_NAKTIMEOUT; + musb_writew(epio, MUSB_TXCSR, tx_csr); + + cur_qh = first_qh(&musb->out_bulk); + } + if (cur_qh) { + urb = next_urb(cur_qh); + if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) { + dma->status = MUSB_DMA_STATUS_CORE_ABORT; + musb->dma_controller->channel_abort(dma); + urb->actual_length += dma->actual_len; + dma->actual_len = 0L; + } + musb_save_toggle(cur_qh, is_in, urb); + + if (is_in) { + /* move cur_qh to end of queue */ + list_move_tail(&cur_qh->ring, &musb->in_bulk); + + /* get the next qh from musb->in_bulk */ + next_qh = first_qh(&musb->in_bulk); + + /* set rx_reinit and schedule the next qh */ + ep->rx_reinit = 1; + } else { + /* move cur_qh to end of queue */ + list_move_tail(&cur_qh->ring, &musb->out_bulk); + + /* get the next qh from musb->out_bulk */ + next_qh = first_qh(&musb->out_bulk); + + /* set tx_reinit and schedule the next qh */ + ep->tx_reinit = 1; + } + musb_start_urb(musb, is_in, next_qh); + } +} + +/* + * Service the default endpoint (ep0) as host. + * Return true until it's time to start the status stage. + */ +static bool musb_h_ep0_continue(struct musb *musb, u16 len, struct urb *urb) +{ + bool more = false; + u8 *fifo_dest = NULL; + u16 fifo_count = 0; + struct musb_hw_ep *hw_ep = musb->control_ep; + struct musb_qh *qh = hw_ep->in_qh; + struct usb_ctrlrequest *request; + + switch (musb->ep0_stage) { + case MUSB_EP0_IN: + fifo_dest = urb->transfer_buffer + urb->actual_length; + fifo_count = min_t(size_t, len, urb->transfer_buffer_length - + urb->actual_length); + if (fifo_count < len) + urb->status = -EOVERFLOW; + + musb_read_fifo(hw_ep, fifo_count, fifo_dest); + + urb->actual_length += fifo_count; + if (len < qh->maxpacket) { + /* always terminate on short read; it's + * rarely reported as an error. + */ + } else if (urb->actual_length < + urb->transfer_buffer_length) + more = true; + break; + case MUSB_EP0_START: + request = (struct usb_ctrlrequest *) urb->setup_packet; + + if (!request->wLength) { + dev_dbg(musb->controller, "start no-DATA\n"); + break; + } else if (request->bRequestType & USB_DIR_IN) { + dev_dbg(musb->controller, "start IN-DATA\n"); + musb->ep0_stage = MUSB_EP0_IN; + more = true; + break; + } else { + dev_dbg(musb->controller, "start OUT-DATA\n"); + musb->ep0_stage = MUSB_EP0_OUT; + more = true; + } + /* FALLTHROUGH */ + case MUSB_EP0_OUT: + fifo_count = min_t(size_t, qh->maxpacket, + urb->transfer_buffer_length - + urb->actual_length); + if (fifo_count) { + fifo_dest = (u8 *) (urb->transfer_buffer + + urb->actual_length); + dev_dbg(musb->controller, "Sending %d byte%s to ep0 fifo %p\n", + fifo_count, + (fifo_count == 1) ? "" : "s", + fifo_dest); + musb_write_fifo(hw_ep, fifo_count, fifo_dest); + + urb->actual_length += fifo_count; + more = true; + } + break; + default: + ERR("bogus ep0 stage %d\n", musb->ep0_stage); + break; + } + + return more; +} + +/* + * Handle default endpoint interrupt as host. Only called in IRQ time + * from musb_interrupt(). + * + * called with controller irqlocked + */ +irqreturn_t musb_h_ep0_irq(struct musb *musb) +{ + struct urb *urb; + u16 csr, len; + int status = 0; + void __iomem *mbase = musb->mregs; + struct musb_hw_ep *hw_ep = musb->control_ep; + void __iomem *epio = hw_ep->regs; + struct musb_qh *qh = hw_ep->in_qh; + bool complete = false; + irqreturn_t retval = IRQ_NONE; + + /* ep0 only has one queue, "in" */ + urb = next_urb(qh); + + musb_ep_select(mbase, 0); + csr = musb_readw(epio, MUSB_CSR0); + len = (csr & MUSB_CSR0_RXPKTRDY) + ? musb_readb(epio, MUSB_COUNT0) + : 0; + + dev_dbg(musb->controller, "<== csr0 %04x, qh %p, count %d, urb %p, stage %d\n", + csr, qh, len, urb, musb->ep0_stage); + + /* if we just did status stage, we are done */ + if (MUSB_EP0_STATUS == musb->ep0_stage) { + retval = IRQ_HANDLED; + complete = true; + } + + /* prepare status */ + if (csr & MUSB_CSR0_H_RXSTALL) { + dev_dbg(musb->controller, "STALLING ENDPOINT\n"); + status = -EPIPE; + + } else if (csr & MUSB_CSR0_H_ERROR) { + dev_dbg(musb->controller, "no response, csr0 %04x\n", csr); + status = -EPROTO; + + } else if (csr & MUSB_CSR0_H_NAKTIMEOUT) { + dev_dbg(musb->controller, "control NAK timeout\n"); + + /* NOTE: this code path would be a good place to PAUSE a + * control transfer, if another one is queued, so that + * ep0 is more likely to stay busy. That's already done + * for bulk RX transfers. + * + * if (qh->ring.next != &musb->control), then + * we have a candidate... NAKing is *NOT* an error + */ + musb_writew(epio, MUSB_CSR0, 0); + retval = IRQ_HANDLED; + } + + if (status) { + dev_dbg(musb->controller, "aborting\n"); + retval = IRQ_HANDLED; + if (urb) + urb->status = status; + complete = true; + + /* use the proper sequence to abort the transfer */ + if (csr & MUSB_CSR0_H_REQPKT) { + csr &= ~MUSB_CSR0_H_REQPKT; + musb_writew(epio, MUSB_CSR0, csr); + csr &= ~MUSB_CSR0_H_NAKTIMEOUT; + musb_writew(epio, MUSB_CSR0, csr); + } else { + musb_h_ep0_flush_fifo(hw_ep); + } + + musb_writeb(epio, MUSB_NAKLIMIT0, 0); + + /* clear it */ + musb_writew(epio, MUSB_CSR0, 0); + } + + if (unlikely(!urb)) { + /* stop endpoint since we have no place for its data, this + * SHOULD NEVER HAPPEN! */ + ERR("no URB for end 0\n"); + + musb_h_ep0_flush_fifo(hw_ep); + goto done; + } + + if (!complete) { + /* call common logic and prepare response */ + if (musb_h_ep0_continue(musb, len, urb)) { + /* more packets required */ + csr = (MUSB_EP0_IN == musb->ep0_stage) + ? MUSB_CSR0_H_REQPKT : MUSB_CSR0_TXPKTRDY; + } else { + /* data transfer complete; perform status phase */ + if (usb_pipeout(urb->pipe) + || !urb->transfer_buffer_length) + csr = MUSB_CSR0_H_STATUSPKT + | MUSB_CSR0_H_REQPKT; + else + csr = MUSB_CSR0_H_STATUSPKT + | MUSB_CSR0_TXPKTRDY; + + /* flag status stage */ + musb->ep0_stage = MUSB_EP0_STATUS; + + dev_dbg(musb->controller, "ep0 STATUS, csr %04x\n", csr); + + } + musb_writew(epio, MUSB_CSR0, csr); + retval = IRQ_HANDLED; + } else + musb->ep0_stage = MUSB_EP0_IDLE; + + /* call completion handler if done */ + if (complete) + musb_advance_schedule(musb, urb, hw_ep, 1); +done: + return retval; +} + + +#ifdef CONFIG_USB_INVENTRA_DMA + +/* Host side TX (OUT) using Mentor DMA works as follows: + submit_urb -> + - if queue was empty, Program Endpoint + - ... which starts DMA to fifo in mode 1 or 0 + + DMA Isr (transfer complete) -> TxAvail() + - Stop DMA (~DmaEnab) (<--- Alert ... currently happens + only in musb_cleanup_urb) + - TxPktRdy has to be set in mode 0 or for + short packets in mode 1. +*/ + +#endif + +/* Service a Tx-Available or dma completion irq for the endpoint */ +void musb_host_tx(struct musb *musb, u8 epnum) +{ + int pipe; + bool done = false; + u16 tx_csr; + size_t length = 0; + size_t offset = 0; + struct musb_hw_ep *hw_ep = musb->endpoints + epnum; + void __iomem *epio = hw_ep->regs; + struct musb_qh *qh = hw_ep->out_qh; + struct urb *urb = next_urb(qh); + u32 status = 0; + void __iomem *mbase = musb->mregs; + struct dma_channel *dma; + bool transfer_pending = false; + + musb_ep_select(mbase, epnum); + tx_csr = musb_readw(epio, MUSB_TXCSR); + + /* with CPPI, DMA sometimes triggers "extra" irqs */ + if (!urb) { + dev_dbg(musb->controller, "extra TX%d ready, csr %04x\n", epnum, tx_csr); + return; + } + + pipe = urb->pipe; + dma = is_dma_capable() ? hw_ep->tx_channel : NULL; + dev_dbg(musb->controller, "OUT/TX%d end, csr %04x%s\n", epnum, tx_csr, + dma ? ", dma" : ""); + + /* check for errors */ + if (tx_csr & MUSB_TXCSR_H_RXSTALL) { + /* dma was disabled, fifo flushed */ + dev_dbg(musb->controller, "TX end %d stall\n", epnum); + + /* stall; record URB status */ + status = -EPIPE; + + } else if (tx_csr & MUSB_TXCSR_H_ERROR) { + /* (NON-ISO) dma was disabled, fifo flushed */ + dev_dbg(musb->controller, "TX 3strikes on ep=%d\n", epnum); + + status = -ETIMEDOUT; + + } else if (tx_csr & MUSB_TXCSR_H_NAKTIMEOUT) { + if (USB_ENDPOINT_XFER_BULK == qh->type && qh->mux == 1 + && !list_is_singular(&musb->out_bulk)) { + dev_dbg(musb->controller, + "NAK timeout on TX%d ep\n", epnum); + musb_bulk_nak_timeout(musb, hw_ep, 0); + } else { + dev_dbg(musb->controller, + "TX end=%d device not responding\n", epnum); + /* NOTE: this code path would be a good place to PAUSE a + * transfer, if there's some other (nonperiodic) tx urb + * that could use this fifo. (dma complicates it...) + * That's already done for bulk RX transfers. + * + * if (bulk && qh->ring.next != &musb->out_bulk), then + * we have a candidate... NAKing is *NOT* an error + */ + musb_ep_select(mbase, epnum); + musb_writew(epio, MUSB_TXCSR, + MUSB_TXCSR_H_WZC_BITS + | MUSB_TXCSR_TXPKTRDY); + } + return; + } + +done: + if (status) { + if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) { + dma->status = MUSB_DMA_STATUS_CORE_ABORT; + (void) musb->dma_controller->channel_abort(dma); + } + + /* do the proper sequence to abort the transfer in the + * usb core; the dma engine should already be stopped. + */ + musb_h_tx_flush_fifo(hw_ep); + tx_csr &= ~(MUSB_TXCSR_AUTOSET + | MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_H_ERROR + | MUSB_TXCSR_H_RXSTALL + | MUSB_TXCSR_H_NAKTIMEOUT + ); + + musb_ep_select(mbase, epnum); + musb_writew(epio, MUSB_TXCSR, tx_csr); + /* REVISIT may need to clear FLUSHFIFO ... */ + musb_writew(epio, MUSB_TXCSR, tx_csr); + musb_writeb(epio, MUSB_TXINTERVAL, 0); + + done = true; + } + + /* second cppi case */ + if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) { + dev_dbg(musb->controller, "extra TX%d ready, csr %04x\n", epnum, tx_csr); + return; + } + + if (is_dma_capable() && dma && !status) { + /* + * DMA has completed. But if we're using DMA mode 1 (multi + * packet DMA), we need a terminal TXPKTRDY interrupt before + * we can consider this transfer completed, lest we trash + * its last packet when writing the next URB's data. So we + * switch back to mode 0 to get that interrupt; we'll come + * back here once it happens. + */ + if (tx_csr & MUSB_TXCSR_DMAMODE) { + /* + * We shouldn't clear DMAMODE with DMAENAB set; so + * clear them in a safe order. That should be OK + * once TXPKTRDY has been set (and I've never seen + * it being 0 at this moment -- DMA interrupt latency + * is significant) but if it hasn't been then we have + * no choice but to stop being polite and ignore the + * programmer's guide... :-) + * + * Note that we must write TXCSR with TXPKTRDY cleared + * in order not to re-trigger the packet send (this bit + * can't be cleared by CPU), and there's another caveat: + * TXPKTRDY may be set shortly and then cleared in the + * double-buffered FIFO mode, so we do an extra TXCSR + * read for debouncing... + */ + tx_csr &= musb_readw(epio, MUSB_TXCSR); + if (tx_csr & MUSB_TXCSR_TXPKTRDY) { + tx_csr &= ~(MUSB_TXCSR_DMAENAB | + MUSB_TXCSR_TXPKTRDY); + musb_writew(epio, MUSB_TXCSR, + tx_csr | MUSB_TXCSR_H_WZC_BITS); + } + tx_csr &= ~(MUSB_TXCSR_DMAMODE | + MUSB_TXCSR_TXPKTRDY); + musb_writew(epio, MUSB_TXCSR, + tx_csr | MUSB_TXCSR_H_WZC_BITS); + + /* + * There is no guarantee that we'll get an interrupt + * after clearing DMAMODE as we might have done this + * too late (after TXPKTRDY was cleared by controller). + * Re-read TXCSR as we have spoiled its previous value. + */ + tx_csr = musb_readw(epio, MUSB_TXCSR); + } + + /* + * We may get here from a DMA completion or TXPKTRDY interrupt. + * In any case, we must check the FIFO status here and bail out + * only if the FIFO still has data -- that should prevent the + * "missed" TXPKTRDY interrupts and deal with double-buffered + * FIFO mode too... + */ + if (tx_csr & (MUSB_TXCSR_FIFONOTEMPTY | MUSB_TXCSR_TXPKTRDY)) { + dev_dbg(musb->controller, "DMA complete but packet still in FIFO, " + "CSR %04x\n", tx_csr); + return; + } + } + + if (!status || dma || usb_pipeisoc(pipe)) { + if (dma) + length = dma->actual_len; + else + length = qh->segsize; + qh->offset += length; + + if (usb_pipeisoc(pipe)) { + struct usb_iso_packet_descriptor *d; + + d = urb->iso_frame_desc + qh->iso_idx; + d->actual_length = length; + d->status = status; + if (++qh->iso_idx >= urb->number_of_packets) { + done = true; + } else { + d++; + offset = d->offset; + length = d->length; + } + } else if (dma && urb->transfer_buffer_length == qh->offset) { + done = true; + } else { + /* see if we need to send more data, or ZLP */ + if (qh->segsize < qh->maxpacket) + done = true; + else if (qh->offset == urb->transfer_buffer_length + && !(urb->transfer_flags + & URB_ZERO_PACKET)) + done = true; + if (!done) { + offset = qh->offset; + length = urb->transfer_buffer_length - offset; + transfer_pending = true; + } + } + } + + /* urb->status != -EINPROGRESS means request has been faulted, + * so we must abort this transfer after cleanup + */ + if (urb->status != -EINPROGRESS) { + done = true; + if (status == 0) + status = urb->status; + } + + if (done) { + /* set status */ + urb->status = status; + urb->actual_length = qh->offset; + musb_advance_schedule(musb, urb, hw_ep, USB_DIR_OUT); + return; + } else if ((usb_pipeisoc(pipe) || transfer_pending) && dma) { + if (musb_tx_dma_program(musb->dma_controller, hw_ep, qh, urb, + offset, length)) { + if (is_cppi_enabled() || tusb_dma_omap()) + musb_h_tx_dma_start(hw_ep); + return; + } + } else if (tx_csr & MUSB_TXCSR_DMAENAB) { + dev_dbg(musb->controller, "not complete, but DMA enabled?\n"); + return; + } + + /* + * PIO: start next packet in this URB. + * + * REVISIT: some docs say that when hw_ep->tx_double_buffered, + * (and presumably, FIFO is not half-full) we should write *two* + * packets before updating TXCSR; other docs disagree... + */ + if (length > qh->maxpacket) + length = qh->maxpacket; + /* Unmap the buffer so that CPU can use it */ + usb_hcd_unmap_urb_for_dma(musb_to_hcd(musb), urb); + + /* + * We need to map sg if the transfer_buffer is + * NULL. + */ + if (!urb->transfer_buffer) + qh->use_sg = true; + + if (qh->use_sg) { + /* sg_miter_start is already done in musb_ep_program */ + if (!sg_miter_next(&qh->sg_miter)) { + dev_err(musb->controller, "error: sg list empty\n"); + sg_miter_stop(&qh->sg_miter); + status = -EINVAL; + goto done; + } + urb->transfer_buffer = qh->sg_miter.addr; + length = min_t(u32, length, qh->sg_miter.length); + musb_write_fifo(hw_ep, length, urb->transfer_buffer); + qh->sg_miter.consumed = length; + sg_miter_stop(&qh->sg_miter); + } else { + musb_write_fifo(hw_ep, length, urb->transfer_buffer + offset); + } + + qh->segsize = length; + + if (qh->use_sg) { + if (offset + length >= urb->transfer_buffer_length) + qh->use_sg = false; + } + + musb_ep_select(mbase, epnum); + musb_writew(epio, MUSB_TXCSR, + MUSB_TXCSR_H_WZC_BITS | MUSB_TXCSR_TXPKTRDY); +} + + +#ifdef CONFIG_USB_INVENTRA_DMA + +/* Host side RX (IN) using Mentor DMA works as follows: + submit_urb -> + - if queue was empty, ProgramEndpoint + - first IN token is sent out (by setting ReqPkt) + LinuxIsr -> RxReady() + /\ => first packet is received + | - Set in mode 0 (DmaEnab, ~ReqPkt) + | -> DMA Isr (transfer complete) -> RxReady() + | - Ack receive (~RxPktRdy), turn off DMA (~DmaEnab) + | - if urb not complete, send next IN token (ReqPkt) + | | else complete urb. + | | + --------------------------- + * + * Nuances of mode 1: + * For short packets, no ack (+RxPktRdy) is sent automatically + * (even if AutoClear is ON) + * For full packets, ack (~RxPktRdy) and next IN token (+ReqPkt) is sent + * automatically => major problem, as collecting the next packet becomes + * difficult. Hence mode 1 is not used. + * + * REVISIT + * All we care about at this driver level is that + * (a) all URBs terminate with REQPKT cleared and fifo(s) empty; + * (b) termination conditions are: short RX, or buffer full; + * (c) fault modes include + * - iff URB_SHORT_NOT_OK, short RX status is -EREMOTEIO. + * (and that endpoint's dma queue stops immediately) + * - overflow (full, PLUS more bytes in the terminal packet) + * + * So for example, usb-storage sets URB_SHORT_NOT_OK, and would + * thus be a great candidate for using mode 1 ... for all but the + * last packet of one URB's transfer. + */ + +#endif + +/* + * Service an RX interrupt for the given IN endpoint; docs cover bulk, iso, + * and high-bandwidth IN transfer cases. + */ +void musb_host_rx(struct musb *musb, u8 epnum) +{ + struct urb *urb; + struct musb_hw_ep *hw_ep = musb->endpoints + epnum; + void __iomem *epio = hw_ep->regs; + struct musb_qh *qh = hw_ep->in_qh; + size_t xfer_len; + void __iomem *mbase = musb->mregs; + int pipe; + u16 rx_csr, val; + bool iso_err = false; + bool done = false; + u32 status; + struct dma_channel *dma; + unsigned int sg_flags = SG_MITER_ATOMIC | SG_MITER_TO_SG; + + musb_ep_select(mbase, epnum); + + urb = next_urb(qh); + dma = is_dma_capable() ? hw_ep->rx_channel : NULL; + status = 0; + xfer_len = 0; + + rx_csr = musb_readw(epio, MUSB_RXCSR); + val = rx_csr; + + if (unlikely(!urb)) { + /* REVISIT -- THIS SHOULD NEVER HAPPEN ... but, at least + * usbtest #11 (unlinks) triggers it regularly, sometimes + * with fifo full. (Only with DMA??) + */ + dev_dbg(musb->controller, "BOGUS RX%d ready, csr %04x, count %d\n", epnum, val, + musb_readw(epio, MUSB_RXCOUNT)); + musb_h_flush_rxfifo(hw_ep, MUSB_RXCSR_CLRDATATOG); + return; + } + + pipe = urb->pipe; + + dev_dbg(musb->controller, "<== hw %d rxcsr %04x, urb actual %d (+dma %zu)\n", + epnum, rx_csr, urb->actual_length, + dma ? dma->actual_len : 0); + + /* check for errors, concurrent stall & unlink is not really + * handled yet! */ + if (rx_csr & MUSB_RXCSR_H_RXSTALL) { + dev_dbg(musb->controller, "RX end %d STALL\n", epnum); + + /* stall; record URB status */ + status = -EPIPE; + + } else if (rx_csr & MUSB_RXCSR_H_ERROR) { + dev_dbg(musb->controller, "end %d RX proto error\n", epnum); + + status = -EPROTO; + musb_writeb(epio, MUSB_RXINTERVAL, 0); + + } else if (rx_csr & MUSB_RXCSR_DATAERROR) { + + if (USB_ENDPOINT_XFER_ISOC != qh->type) { + dev_dbg(musb->controller, "RX end %d NAK timeout\n", epnum); + + /* NOTE: NAKing is *NOT* an error, so we want to + * continue. Except ... if there's a request for + * another QH, use that instead of starving it. + * + * Devices like Ethernet and serial adapters keep + * reads posted at all times, which will starve + * other devices without this logic. + */ + if (usb_pipebulk(urb->pipe) + && qh->mux == 1 + && !list_is_singular(&musb->in_bulk)) { + musb_bulk_nak_timeout(musb, hw_ep, 1); + return; + } + musb_ep_select(mbase, epnum); + rx_csr |= MUSB_RXCSR_H_WZC_BITS; + rx_csr &= ~MUSB_RXCSR_DATAERROR; + musb_writew(epio, MUSB_RXCSR, rx_csr); + + goto finish; + } else { + dev_dbg(musb->controller, "RX end %d ISO data error\n", epnum); + /* packet error reported later */ + iso_err = true; + } + } else if (rx_csr & MUSB_RXCSR_INCOMPRX) { + dev_dbg(musb->controller, "end %d high bandwidth incomplete ISO packet RX\n", + epnum); + status = -EPROTO; + } + + /* faults abort the transfer */ + if (status) { + /* clean up dma and collect transfer count */ + if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) { + dma->status = MUSB_DMA_STATUS_CORE_ABORT; + (void) musb->dma_controller->channel_abort(dma); + xfer_len = dma->actual_len; + } + musb_h_flush_rxfifo(hw_ep, MUSB_RXCSR_CLRDATATOG); + musb_writeb(epio, MUSB_RXINTERVAL, 0); + done = true; + goto finish; + } + + if (unlikely(dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY)) { + /* SHOULD NEVER HAPPEN ... but at least DaVinci has done it */ + ERR("RX%d dma busy, csr %04x\n", epnum, rx_csr); + goto finish; + } + + /* thorough shutdown for now ... given more precise fault handling + * and better queueing support, we might keep a DMA pipeline going + * while processing this irq for earlier completions. + */ + + /* FIXME this is _way_ too much in-line logic for Mentor DMA */ + +#ifndef CONFIG_USB_INVENTRA_DMA + if (rx_csr & MUSB_RXCSR_H_REQPKT) { + /* REVISIT this happened for a while on some short reads... + * the cleanup still needs investigation... looks bad... + * and also duplicates dma cleanup code above ... plus, + * shouldn't this be the "half full" double buffer case? + */ + if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) { + dma->status = MUSB_DMA_STATUS_CORE_ABORT; + (void) musb->dma_controller->channel_abort(dma); + xfer_len = dma->actual_len; + done = true; + } + + dev_dbg(musb->controller, "RXCSR%d %04x, reqpkt, len %zu%s\n", epnum, rx_csr, + xfer_len, dma ? ", dma" : ""); + rx_csr &= ~MUSB_RXCSR_H_REQPKT; + + musb_ep_select(mbase, epnum); + musb_writew(epio, MUSB_RXCSR, + MUSB_RXCSR_H_WZC_BITS | rx_csr); + } +#endif + if (dma && (rx_csr & MUSB_RXCSR_DMAENAB)) { + xfer_len = dma->actual_len; + + val &= ~(MUSB_RXCSR_DMAENAB + | MUSB_RXCSR_H_AUTOREQ + | MUSB_RXCSR_AUTOCLEAR + | MUSB_RXCSR_RXPKTRDY); + musb_writew(hw_ep->regs, MUSB_RXCSR, val); + +#ifdef CONFIG_USB_INVENTRA_DMA + if (usb_pipeisoc(pipe)) { + struct usb_iso_packet_descriptor *d; + + d = urb->iso_frame_desc + qh->iso_idx; + d->actual_length = xfer_len; + + /* even if there was an error, we did the dma + * for iso_frame_desc->length + */ + if (d->status != -EILSEQ && d->status != -EOVERFLOW) + d->status = 0; + + if (++qh->iso_idx >= urb->number_of_packets) + done = true; + else + done = false; + + } else { + /* done if urb buffer is full or short packet is recd */ + done = (urb->actual_length + xfer_len >= + urb->transfer_buffer_length + || dma->actual_len < qh->maxpacket); + } + + /* send IN token for next packet, without AUTOREQ */ + if (!done) { + val |= MUSB_RXCSR_H_REQPKT; + musb_writew(epio, MUSB_RXCSR, + MUSB_RXCSR_H_WZC_BITS | val); + } + + dev_dbg(musb->controller, "ep %d dma %s, rxcsr %04x, rxcount %d\n", epnum, + done ? "off" : "reset", + musb_readw(epio, MUSB_RXCSR), + musb_readw(epio, MUSB_RXCOUNT)); +#else + done = true; +#endif + } else if (urb->status == -EINPROGRESS) { + /* if no errors, be sure a packet is ready for unloading */ + if (unlikely(!(rx_csr & MUSB_RXCSR_RXPKTRDY))) { + status = -EPROTO; + ERR("Rx interrupt with no errors or packet!\n"); + + /* FIXME this is another "SHOULD NEVER HAPPEN" */ + +/* SCRUB (RX) */ + /* do the proper sequence to abort the transfer */ + musb_ep_select(mbase, epnum); + val &= ~MUSB_RXCSR_H_REQPKT; + musb_writew(epio, MUSB_RXCSR, val); + goto finish; + } + + /* we are expecting IN packets */ +#ifdef CONFIG_USB_INVENTRA_DMA + if (dma) { + struct dma_controller *c; + u16 rx_count; + int ret, length; + dma_addr_t buf; + + rx_count = musb_readw(epio, MUSB_RXCOUNT); + + dev_dbg(musb->controller, "RX%d count %d, buffer 0x%x len %d/%d\n", + epnum, rx_count, + urb->transfer_dma + + urb->actual_length, + qh->offset, + urb->transfer_buffer_length); + + c = musb->dma_controller; + + if (usb_pipeisoc(pipe)) { + int d_status = 0; + struct usb_iso_packet_descriptor *d; + + d = urb->iso_frame_desc + qh->iso_idx; + + if (iso_err) { + d_status = -EILSEQ; + urb->error_count++; + } + if (rx_count > d->length) { + if (d_status == 0) { + d_status = -EOVERFLOW; + urb->error_count++; + } + dev_dbg(musb->controller, "** OVERFLOW %d into %d\n",\ + rx_count, d->length); + + length = d->length; + } else + length = rx_count; + d->status = d_status; + buf = urb->transfer_dma + d->offset; + } else { + length = rx_count; + buf = urb->transfer_dma + + urb->actual_length; + } + + dma->desired_mode = 0; +#ifdef USE_MODE1 + /* because of the issue below, mode 1 will + * only rarely behave with correct semantics. + */ + if ((urb->transfer_flags & + URB_SHORT_NOT_OK) + && (urb->transfer_buffer_length - + urb->actual_length) + > qh->maxpacket) + dma->desired_mode = 1; + if (rx_count < hw_ep->max_packet_sz_rx) { + length = rx_count; + dma->desired_mode = 0; + } else { + length = urb->transfer_buffer_length; + } +#endif + +/* Disadvantage of using mode 1: + * It's basically usable only for mass storage class; essentially all + * other protocols also terminate transfers on short packets. + * + * Details: + * An extra IN token is sent at the end of the transfer (due to AUTOREQ) + * If you try to use mode 1 for (transfer_buffer_length - 512), and try + * to use the extra IN token to grab the last packet using mode 0, then + * the problem is that you cannot be sure when the device will send the + * last packet and RxPktRdy set. Sometimes the packet is recd too soon + * such that it gets lost when RxCSR is re-set at the end of the mode 1 + * transfer, while sometimes it is recd just a little late so that if you + * try to configure for mode 0 soon after the mode 1 transfer is + * completed, you will find rxcount 0. Okay, so you might think why not + * wait for an interrupt when the pkt is recd. Well, you won't get any! + */ + + val = musb_readw(epio, MUSB_RXCSR); + val &= ~MUSB_RXCSR_H_REQPKT; + + if (dma->desired_mode == 0) + val &= ~MUSB_RXCSR_H_AUTOREQ; + else + val |= MUSB_RXCSR_H_AUTOREQ; + val |= MUSB_RXCSR_DMAENAB; + + /* autoclear shouldn't be set in high bandwidth */ + if (qh->hb_mult == 1) + val |= MUSB_RXCSR_AUTOCLEAR; + + musb_writew(epio, MUSB_RXCSR, + MUSB_RXCSR_H_WZC_BITS | val); + + /* REVISIT if when actual_length != 0, + * transfer_buffer_length needs to be + * adjusted first... + */ + ret = c->channel_program( + dma, qh->maxpacket, + dma->desired_mode, buf, length); + + if (!ret) { + c->channel_release(dma); + hw_ep->rx_channel = NULL; + dma = NULL; + val = musb_readw(epio, MUSB_RXCSR); + val &= ~(MUSB_RXCSR_DMAENAB + | MUSB_RXCSR_H_AUTOREQ + | MUSB_RXCSR_AUTOCLEAR); + musb_writew(epio, MUSB_RXCSR, val); + } + } +#endif /* Mentor DMA */ + + if (!dma) { + unsigned int received_len; + + /* Unmap the buffer so that CPU can use it */ + usb_hcd_unmap_urb_for_dma(musb_to_hcd(musb), urb); + + /* + * We need to map sg if the transfer_buffer is + * NULL. + */ + if (!urb->transfer_buffer) { + qh->use_sg = true; + sg_miter_start(&qh->sg_miter, urb->sg, 1, + sg_flags); + } + + if (qh->use_sg) { + if (!sg_miter_next(&qh->sg_miter)) { + dev_err(musb->controller, "error: sg list empty\n"); + sg_miter_stop(&qh->sg_miter); + status = -EINVAL; + done = true; + goto finish; + } + urb->transfer_buffer = qh->sg_miter.addr; + received_len = urb->actual_length; + qh->offset = 0x0; + done = musb_host_packet_rx(musb, urb, epnum, + iso_err); + /* Calculate the number of bytes received */ + received_len = urb->actual_length - + received_len; + qh->sg_miter.consumed = received_len; + sg_miter_stop(&qh->sg_miter); + } else { + done = musb_host_packet_rx(musb, urb, + epnum, iso_err); + } + dev_dbg(musb->controller, "read %spacket\n", done ? "last " : ""); + } + } + +finish: + urb->actual_length += xfer_len; + qh->offset += xfer_len; + if (done) { + if (qh->use_sg) + qh->use_sg = false; + + if (urb->status == -EINPROGRESS) + urb->status = status; + musb_advance_schedule(musb, urb, hw_ep, USB_DIR_IN); + } +} + +/* schedule nodes correspond to peripheral endpoints, like an OHCI QH. + * the software schedule associates multiple such nodes with a given + * host side hardware endpoint + direction; scheduling may activate + * that hardware endpoint. + */ +static int musb_schedule( + struct musb *musb, + struct musb_qh *qh, + int is_in) +{ + int idle; + int best_diff; + int best_end, epnum; + struct musb_hw_ep *hw_ep = NULL; + struct list_head *head = NULL; + u8 toggle; + u8 txtype; + struct urb *urb = next_urb(qh); + + /* use fixed hardware for control and bulk */ + if (qh->type == USB_ENDPOINT_XFER_CONTROL) { + head = &musb->control; + hw_ep = musb->control_ep; + goto success; + } + + /* else, periodic transfers get muxed to other endpoints */ + + /* + * We know this qh hasn't been scheduled, so all we need to do + * is choose which hardware endpoint to put it on ... + * + * REVISIT what we really want here is a regular schedule tree + * like e.g. OHCI uses. + */ + best_diff = 4096; + best_end = -1; + + for (epnum = 1, hw_ep = musb->endpoints + 1; + epnum < musb->nr_endpoints; + epnum++, hw_ep++) { + int diff; + + if (musb_ep_get_qh(hw_ep, is_in) != NULL) + continue; + + if (hw_ep == musb->bulk_ep) + continue; + + if (is_in) + diff = hw_ep->max_packet_sz_rx; + else + diff = hw_ep->max_packet_sz_tx; + diff -= (qh->maxpacket * qh->hb_mult); + + if (diff >= 0 && best_diff > diff) { + + /* + * Mentor controller has a bug in that if we schedule + * a BULK Tx transfer on an endpoint that had earlier + * handled ISOC then the BULK transfer has to start on + * a zero toggle. If the BULK transfer starts on a 1 + * toggle then this transfer will fail as the mentor + * controller starts the Bulk transfer on a 0 toggle + * irrespective of the programming of the toggle bits + * in the TXCSR register. Check for this condition + * while allocating the EP for a Tx Bulk transfer. If + * so skip this EP. + */ + hw_ep = musb->endpoints + epnum; + toggle = usb_gettoggle(urb->dev, qh->epnum, !is_in); + txtype = (musb_readb(hw_ep->regs, MUSB_TXTYPE) + >> 4) & 0x3; + if (!is_in && (qh->type == USB_ENDPOINT_XFER_BULK) && + toggle && (txtype == USB_ENDPOINT_XFER_ISOC)) + continue; + + best_diff = diff; + best_end = epnum; + } + } + /* use bulk reserved ep1 if no other ep is free */ + if (best_end < 0 && qh->type == USB_ENDPOINT_XFER_BULK) { + hw_ep = musb->bulk_ep; + if (is_in) + head = &musb->in_bulk; + else + head = &musb->out_bulk; + + /* Enable bulk RX/TX NAK timeout scheme when bulk requests are + * multiplexed. This scheme doen't work in high speed to full + * speed scenario as NAK interrupts are not coming from a + * full speed device connected to a high speed device. + * NAK timeout interval is 8 (128 uframe or 16ms) for HS and + * 4 (8 frame or 8ms) for FS device. + */ + if (qh->dev) + qh->intv_reg = + (USB_SPEED_HIGH == qh->dev->speed) ? 8 : 4; + goto success; + } else if (best_end < 0) { + return -ENOSPC; + } + + idle = 1; + qh->mux = 0; + hw_ep = musb->endpoints + best_end; + dev_dbg(musb->controller, "qh %p periodic slot %d\n", qh, best_end); +success: + if (head) { + idle = list_empty(head); + list_add_tail(&qh->ring, head); + qh->mux = 1; + } + qh->hw_ep = hw_ep; + qh->hep->hcpriv = qh; + if (idle) + musb_start_urb(musb, is_in, qh); + return 0; +} + +static int musb_urb_enqueue( + struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + unsigned long flags; + struct musb *musb = hcd_to_musb(hcd); + struct usb_host_endpoint *hep = urb->ep; + struct musb_qh *qh; + struct usb_endpoint_descriptor *epd = &hep->desc; + int ret; + unsigned type_reg; + unsigned interval; + + /* host role must be active */ + if (!is_host_active(musb) || !musb->is_active) + return -ENODEV; + + spin_lock_irqsave(&musb->lock, flags); + ret = usb_hcd_link_urb_to_ep(hcd, urb); + qh = ret ? NULL : hep->hcpriv; + if (qh) + urb->hcpriv = qh; + spin_unlock_irqrestore(&musb->lock, flags); + + /* DMA mapping was already done, if needed, and this urb is on + * hep->urb_list now ... so we're done, unless hep wasn't yet + * scheduled onto a live qh. + * + * REVISIT best to keep hep->hcpriv valid until the endpoint gets + * disabled, testing for empty qh->ring and avoiding qh setup costs + * except for the first urb queued after a config change. + */ + if (qh || ret) + return ret; + + /* Allocate and initialize qh, minimizing the work done each time + * hw_ep gets reprogrammed, or with irqs blocked. Then schedule it. + * + * REVISIT consider a dedicated qh kmem_cache, so it's harder + * for bugs in other kernel code to break this driver... + */ + qh = kzalloc(sizeof *qh, mem_flags); + if (!qh) { + spin_lock_irqsave(&musb->lock, flags); + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&musb->lock, flags); + return -ENOMEM; + } + + qh->hep = hep; + qh->dev = urb->dev; + INIT_LIST_HEAD(&qh->ring); + qh->is_ready = 1; + + qh->maxpacket = usb_endpoint_maxp(epd); + qh->type = usb_endpoint_type(epd); + + /* Bits 11 & 12 of wMaxPacketSize encode high bandwidth multiplier. + * Some musb cores don't support high bandwidth ISO transfers; and + * we don't (yet!) support high bandwidth interrupt transfers. + */ + qh->hb_mult = 1 + ((qh->maxpacket >> 11) & 0x03); + if (qh->hb_mult > 1) { + int ok = (qh->type == USB_ENDPOINT_XFER_ISOC); + + if (ok) + ok = (usb_pipein(urb->pipe) && musb->hb_iso_rx) + || (usb_pipeout(urb->pipe) && musb->hb_iso_tx); + if (!ok) { + ret = -EMSGSIZE; + goto done; + } + qh->maxpacket &= 0x7ff; + } + + qh->epnum = usb_endpoint_num(epd); + + /* NOTE: urb->dev->devnum is wrong during SET_ADDRESS */ + qh->addr_reg = (u8) usb_pipedevice(urb->pipe); + + /* precompute rxtype/txtype/type0 register */ + type_reg = (qh->type << 4) | qh->epnum; + switch (urb->dev->speed) { + case USB_SPEED_LOW: + type_reg |= 0xc0; + break; + case USB_SPEED_FULL: + type_reg |= 0x80; + break; + default: + type_reg |= 0x40; + } + qh->type_reg = type_reg; + + /* Precompute RXINTERVAL/TXINTERVAL register */ + switch (qh->type) { + case USB_ENDPOINT_XFER_INT: + /* + * Full/low speeds use the linear encoding, + * high speed uses the logarithmic encoding. + */ + if (urb->dev->speed <= USB_SPEED_FULL) { + interval = max_t(u8, epd->bInterval, 1); + break; + } + /* FALLTHROUGH */ + case USB_ENDPOINT_XFER_ISOC: + /* ISO always uses logarithmic encoding */ + interval = min_t(u8, epd->bInterval, 16); + break; + default: + /* REVISIT we actually want to use NAK limits, hinting to the + * transfer scheduling logic to try some other qh, e.g. try + * for 2 msec first: + * + * interval = (USB_SPEED_HIGH == urb->dev->speed) ? 16 : 2; + * + * The downside of disabling this is that transfer scheduling + * gets VERY unfair for nonperiodic transfers; a misbehaving + * peripheral could make that hurt. That's perfectly normal + * for reads from network or serial adapters ... so we have + * partial NAKlimit support for bulk RX. + * + * The upside of disabling it is simpler transfer scheduling. + */ + interval = 0; + } + qh->intv_reg = interval; + + /* precompute addressing for external hub/tt ports */ + if (musb->is_multipoint) { + struct usb_device *parent = urb->dev->parent; + + if (parent != hcd->self.root_hub) { + qh->h_addr_reg = (u8) parent->devnum; + + /* set up tt info if needed */ + if (urb->dev->tt) { + qh->h_port_reg = (u8) urb->dev->ttport; + if (urb->dev->tt->hub) + qh->h_addr_reg = + (u8) urb->dev->tt->hub->devnum; + if (urb->dev->tt->multi) + qh->h_addr_reg |= 0x80; + } + } + } + + /* invariant: hep->hcpriv is null OR the qh that's already scheduled. + * until we get real dma queues (with an entry for each urb/buffer), + * we only have work to do in the former case. + */ + spin_lock_irqsave(&musb->lock, flags); + if (hep->hcpriv || !next_urb(qh)) { + /* some concurrent activity submitted another urb to hep... + * odd, rare, error prone, but legal. + */ + kfree(qh); + qh = NULL; + ret = 0; + } else + ret = musb_schedule(musb, qh, + epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK); + + if (ret == 0) { + urb->hcpriv = qh; + /* FIXME set urb->start_frame for iso/intr, it's tested in + * musb_start_urb(), but otherwise only konicawc cares ... + */ + } + spin_unlock_irqrestore(&musb->lock, flags); + +done: + if (ret != 0) { + spin_lock_irqsave(&musb->lock, flags); + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&musb->lock, flags); + kfree(qh); + } + return ret; +} + + +/* + * abort a transfer that's at the head of a hardware queue. + * called with controller locked, irqs blocked + * that hardware queue advances to the next transfer, unless prevented + */ +static int musb_cleanup_urb(struct urb *urb, struct musb_qh *qh) +{ + struct musb_hw_ep *ep = qh->hw_ep; + struct musb *musb = ep->musb; + void __iomem *epio = ep->regs; + unsigned hw_end = ep->epnum; + void __iomem *regs = ep->musb->mregs; + int is_in = usb_pipein(urb->pipe); + int status = 0; + u16 csr; + + musb_ep_select(regs, hw_end); + + if (is_dma_capable()) { + struct dma_channel *dma; + + dma = is_in ? ep->rx_channel : ep->tx_channel; + if (dma) { + status = ep->musb->dma_controller->channel_abort(dma); + dev_dbg(musb->controller, + "abort %cX%d DMA for urb %p --> %d\n", + is_in ? 'R' : 'T', ep->epnum, + urb, status); + urb->actual_length += dma->actual_len; + } + } + + /* turn off DMA requests, discard state, stop polling ... */ + if (ep->epnum && is_in) { + /* giveback saves bulk toggle */ + csr = musb_h_flush_rxfifo(ep, 0); + + /* REVISIT we still get an irq; should likely clear the + * endpoint's irq status here to avoid bogus irqs. + * clearing that status is platform-specific... + */ + } else if (ep->epnum) { + musb_h_tx_flush_fifo(ep); + csr = musb_readw(epio, MUSB_TXCSR); + csr &= ~(MUSB_TXCSR_AUTOSET + | MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_H_RXSTALL + | MUSB_TXCSR_H_NAKTIMEOUT + | MUSB_TXCSR_H_ERROR + | MUSB_TXCSR_TXPKTRDY); + musb_writew(epio, MUSB_TXCSR, csr); + /* REVISIT may need to clear FLUSHFIFO ... */ + musb_writew(epio, MUSB_TXCSR, csr); + /* flush cpu writebuffer */ + csr = musb_readw(epio, MUSB_TXCSR); + } else { + musb_h_ep0_flush_fifo(ep); + } + if (status == 0) + musb_advance_schedule(ep->musb, urb, ep, is_in); + return status; +} + +static int musb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct musb *musb = hcd_to_musb(hcd); + struct musb_qh *qh; + unsigned long flags; + int is_in = usb_pipein(urb->pipe); + int ret; + + dev_dbg(musb->controller, "urb=%p, dev%d ep%d%s\n", urb, + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + is_in ? "in" : "out"); + + spin_lock_irqsave(&musb->lock, flags); + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) + goto done; + + qh = urb->hcpriv; + if (!qh) + goto done; + + /* + * Any URB not actively programmed into endpoint hardware can be + * immediately given back; that's any URB not at the head of an + * endpoint queue, unless someday we get real DMA queues. And even + * if it's at the head, it might not be known to the hardware... + * + * Otherwise abort current transfer, pending DMA, etc.; urb->status + * has already been updated. This is a synchronous abort; it'd be + * OK to hold off until after some IRQ, though. + * + * NOTE: qh is invalid unless !list_empty(&hep->urb_list) + */ + if (!qh->is_ready + || urb->urb_list.prev != &qh->hep->urb_list + || musb_ep_get_qh(qh->hw_ep, is_in) != qh) { + int ready = qh->is_ready; + + qh->is_ready = 0; + musb_giveback(musb, urb, 0); + qh->is_ready = ready; + + /* If nothing else (usually musb_giveback) is using it + * and its URB list has emptied, recycle this qh. + */ + if (ready && list_empty(&qh->hep->urb_list)) { + qh->hep->hcpriv = NULL; + list_del(&qh->ring); + kfree(qh); + } + } else + ret = musb_cleanup_urb(urb, qh); +done: + spin_unlock_irqrestore(&musb->lock, flags); + return ret; +} + +/* disable an endpoint */ +static void +musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) +{ + u8 is_in = hep->desc.bEndpointAddress & USB_DIR_IN; + unsigned long flags; + struct musb *musb = hcd_to_musb(hcd); + struct musb_qh *qh; + struct urb *urb; + + spin_lock_irqsave(&musb->lock, flags); + + qh = hep->hcpriv; + if (qh == NULL) + goto exit; + + /* NOTE: qh is invalid unless !list_empty(&hep->urb_list) */ + + /* Kick the first URB off the hardware, if needed */ + qh->is_ready = 0; + if (musb_ep_get_qh(qh->hw_ep, is_in) == qh) { + urb = next_urb(qh); + + /* make software (then hardware) stop ASAP */ + if (!urb->unlinked) + urb->status = -ESHUTDOWN; + + /* cleanup */ + musb_cleanup_urb(urb, qh); + + /* Then nuke all the others ... and advance the + * queue on hw_ep (e.g. bulk ring) when we're done. + */ + while (!list_empty(&hep->urb_list)) { + urb = next_urb(qh); + urb->status = -ESHUTDOWN; + musb_advance_schedule(musb, urb, qh->hw_ep, is_in); + } + } else { + /* Just empty the queue; the hardware is busy with + * other transfers, and since !qh->is_ready nothing + * will activate any of these as it advances. + */ + while (!list_empty(&hep->urb_list)) + musb_giveback(musb, next_urb(qh), -ESHUTDOWN); + + hep->hcpriv = NULL; + list_del(&qh->ring); + kfree(qh); + } +exit: + spin_unlock_irqrestore(&musb->lock, flags); +} + +static int musb_h_get_frame_number(struct usb_hcd *hcd) +{ + struct musb *musb = hcd_to_musb(hcd); + + return musb_readw(musb->mregs, MUSB_FRAME); +} + +static int musb_h_start(struct usb_hcd *hcd) +{ + struct musb *musb = hcd_to_musb(hcd); + + /* NOTE: musb_start() is called when the hub driver turns + * on port power, or when (OTG) peripheral starts. + */ + hcd->state = HC_STATE_RUNNING; + musb->port1_status = 0; + return 0; +} + +static void musb_h_stop(struct usb_hcd *hcd) +{ + musb_stop(hcd_to_musb(hcd)); + hcd->state = HC_STATE_HALT; +} + +static int musb_bus_suspend(struct usb_hcd *hcd) +{ + struct musb *musb = hcd_to_musb(hcd); + u8 devctl; + + if (!is_host_active(musb)) + return 0; + + switch (musb->xceiv->state) { + case OTG_STATE_A_SUSPEND: + return 0; + case OTG_STATE_A_WAIT_VRISE: + /* ID could be grounded even if there's no device + * on the other end of the cable. NOTE that the + * A_WAIT_VRISE timers are messy with MUSB... + */ + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + break; + default: + break; + } + + if (musb->is_active) { + WARNING("trying to suspend as %s while active\n", + usb_otg_state_string(musb->xceiv->state)); + return -EBUSY; + } else + return 0; +} + +static int musb_bus_resume(struct usb_hcd *hcd) +{ + /* resuming child port does the work */ + return 0; +} + + +#ifndef CONFIG_MUSB_PIO_ONLY + +#define MUSB_USB_DMA_ALIGN 4 + +struct musb_temp_buffer { + void *kmalloc_ptr; + void *old_xfer_buffer; + u8 data[0]; +}; + +static void musb_free_temp_buffer(struct urb *urb) +{ + enum dma_data_direction dir; + struct musb_temp_buffer *temp; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + temp = container_of(urb->transfer_buffer, struct musb_temp_buffer, + data); + + if (dir == DMA_FROM_DEVICE) { + memcpy(temp->old_xfer_buffer, temp->data, + urb->transfer_buffer_length); + } + urb->transfer_buffer = temp->old_xfer_buffer; + kfree(temp->kmalloc_ptr); + + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; +} + +static int musb_alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) +{ + enum dma_data_direction dir; + struct musb_temp_buffer *temp; + void *kmalloc_ptr; + size_t kmalloc_size; + + if (urb->num_sgs || urb->sg || + urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (MUSB_USB_DMA_ALIGN - 1))) + return 0; + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + /* Allocate a buffer with enough padding for alignment */ + kmalloc_size = urb->transfer_buffer_length + + sizeof(struct musb_temp_buffer) + MUSB_USB_DMA_ALIGN - 1; + + kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); + if (!kmalloc_ptr) + return -ENOMEM; + + /* Position our struct temp_buffer such that data is aligned */ + temp = PTR_ALIGN(kmalloc_ptr, MUSB_USB_DMA_ALIGN); + + + temp->kmalloc_ptr = kmalloc_ptr; + temp->old_xfer_buffer = urb->transfer_buffer; + if (dir == DMA_TO_DEVICE) + memcpy(temp->data, urb->transfer_buffer, + urb->transfer_buffer_length); + urb->transfer_buffer = temp->data; + + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; +} + +static int musb_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct musb *musb = hcd_to_musb(hcd); + int ret; + + /* + * The DMA engine in RTL1.8 and above cannot handle + * DMA addresses that are not aligned to a 4 byte boundary. + * For such engine implemented (un)map_urb_for_dma hooks. + * Do not use these hooks for RTL<1.8 + */ + if (musb->hwvers < MUSB_HWVERS_1800) + return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + + ret = musb_alloc_temp_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + musb_free_temp_buffer(urb); + + return ret; +} + +static void musb_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + struct musb *musb = hcd_to_musb(hcd); + + usb_hcd_unmap_urb_for_dma(hcd, urb); + + /* Do not use this hook for RTL<1.8 (see description above) */ + if (musb->hwvers < MUSB_HWVERS_1800) + return; + + musb_free_temp_buffer(urb); +} +#endif /* !CONFIG_MUSB_PIO_ONLY */ + +const struct hc_driver musb_hc_driver = { + .description = "musb-hcd", + .product_desc = "MUSB HDRC host driver", + .hcd_priv_size = sizeof(struct musb), + .flags = HCD_USB2 | HCD_MEMORY, + + /* not using irq handler or reset hooks from usbcore, since + * those must be shared with peripheral code for OTG configs + */ + + .start = musb_h_start, + .stop = musb_h_stop, + + .get_frame_number = musb_h_get_frame_number, + + .urb_enqueue = musb_urb_enqueue, + .urb_dequeue = musb_urb_dequeue, + .endpoint_disable = musb_h_disable, + +#ifndef CONFIG_MUSB_PIO_ONLY + .map_urb_for_dma = musb_map_urb_for_dma, + .unmap_urb_for_dma = musb_unmap_urb_for_dma, +#endif + + .hub_status_data = musb_hub_status_data, + .hub_control = musb_hub_control, + .bus_suspend = musb_bus_suspend, + .bus_resume = musb_bus_resume, + /* .start_port_reset = NULL, */ + /* .hub_irq_enable = NULL, */ +}; diff --git a/drivers/usb/musb/musb_host.h b/drivers/usb/musb/musb_host.h new file mode 100644 index 00000000..738f7eb6 --- /dev/null +++ b/drivers/usb/musb/musb_host.h @@ -0,0 +1,112 @@ +/* + * MUSB OTG driver host defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _MUSB_HOST_H +#define _MUSB_HOST_H + +#include + +static inline struct usb_hcd *musb_to_hcd(struct musb *musb) +{ + return container_of((void *) musb, struct usb_hcd, hcd_priv); +} + +static inline struct musb *hcd_to_musb(struct usb_hcd *hcd) +{ + return (struct musb *) (hcd->hcd_priv); +} + +/* stored in "usb_host_endpoint.hcpriv" for scheduled endpoints */ +struct musb_qh { + struct usb_host_endpoint *hep; /* usbcore info */ + struct usb_device *dev; + struct musb_hw_ep *hw_ep; /* current binding */ + + struct list_head ring; /* of musb_qh */ + /* struct musb_qh *next; */ /* for periodic tree */ + u8 mux; /* qh multiplexed to hw_ep */ + + unsigned offset; /* in urb->transfer_buffer */ + unsigned segsize; /* current xfer fragment */ + + u8 type_reg; /* {rx,tx} type register */ + u8 intv_reg; /* {rx,tx} interval register */ + u8 addr_reg; /* device address register */ + u8 h_addr_reg; /* hub address register */ + u8 h_port_reg; /* hub port register */ + + u8 is_ready; /* safe to modify hw_ep */ + u8 type; /* XFERTYPE_* */ + u8 epnum; + u8 hb_mult; /* high bandwidth pkts per uf */ + u16 maxpacket; + u16 frame; /* for periodic schedule */ + unsigned iso_idx; /* in urb->iso_frame_desc[] */ + struct sg_mapping_iter sg_miter; /* for highmem in PIO mode */ + bool use_sg; /* to track urb using sglist */ +}; + +/* map from control or bulk queue head to the first qh on that ring */ +static inline struct musb_qh *first_qh(struct list_head *q) +{ + if (list_empty(q)) + return NULL; + return list_entry(q->next, struct musb_qh, ring); +} + + +extern void musb_root_disconnect(struct musb *musb); + +struct usb_hcd; + +extern int musb_hub_status_data(struct usb_hcd *hcd, char *buf); +extern int musb_hub_control(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); + +extern const struct hc_driver musb_hc_driver; + +static inline struct urb *next_urb(struct musb_qh *qh) +{ + struct list_head *queue; + + if (!qh) + return NULL; + queue = &qh->hep->urb_list; + if (list_empty(queue)) + return NULL; + return list_entry(queue->next, struct urb, urb_list); +} + +#endif /* _MUSB_HOST_H */ diff --git a/drivers/usb/musb/musb_io.h b/drivers/usb/musb/musb_io.h new file mode 100644 index 00000000..eebeed78 --- /dev/null +++ b/drivers/usb/musb/musb_io.h @@ -0,0 +1,122 @@ +/* + * MUSB OTG driver register I/O + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __MUSB_LINUX_PLATFORM_ARCH_H__ +#define __MUSB_LINUX_PLATFORM_ARCH_H__ + +#include + +#ifndef CONFIG_BLACKFIN + +/* NOTE: these offsets are all in bytes */ + +static inline u16 musb_readw(const void __iomem *addr, unsigned offset) + { return __raw_readw(addr + offset); } + +static inline u32 musb_readl(const void __iomem *addr, unsigned offset) + { return __raw_readl(addr + offset); } + + +static inline void musb_writew(void __iomem *addr, unsigned offset, u16 data) + { __raw_writew(data, addr + offset); } + +static inline void musb_writel(void __iomem *addr, unsigned offset, u32 data) + { __raw_writel(data, addr + offset); } + + +#if defined(CONFIG_USB_MUSB_TUSB6010) || defined (CONFIG_USB_MUSB_TUSB6010_MODULE) + +/* + * TUSB6010 doesn't allow 8-bit access; 16-bit access is the minimum. + */ +static inline u8 musb_readb(const void __iomem *addr, unsigned offset) +{ + u16 tmp; + u8 val; + + tmp = __raw_readw(addr + (offset & ~1)); + if (offset & 1) + val = (tmp >> 8); + else + val = tmp & 0xff; + + return val; +} + +static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data) +{ + u16 tmp; + + tmp = __raw_readw(addr + (offset & ~1)); + if (offset & 1) + tmp = (data << 8) | (tmp & 0xff); + else + tmp = (tmp & 0xff00) | data; + + __raw_writew(tmp, addr + (offset & ~1)); +} + +#else + +static inline u8 musb_readb(const void __iomem *addr, unsigned offset) + { return __raw_readb(addr + offset); } + +static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data) + { __raw_writeb(data, addr + offset); } + +#endif /* CONFIG_USB_MUSB_TUSB6010 */ + +#else + +static inline u8 musb_readb(const void __iomem *addr, unsigned offset) + { return (u8) (bfin_read16(addr + offset)); } + +static inline u16 musb_readw(const void __iomem *addr, unsigned offset) + { return bfin_read16(addr + offset); } + +static inline u32 musb_readl(const void __iomem *addr, unsigned offset) + { return (u32) (bfin_read16(addr + offset)); } + +static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data) + { bfin_write16(addr + offset, (u16) data); } + +static inline void musb_writew(void __iomem *addr, unsigned offset, u16 data) + { bfin_write16(addr + offset, data); } + +static inline void musb_writel(void __iomem *addr, unsigned offset, u32 data) + { bfin_write16(addr + offset, (u16) data); } + +#endif /* CONFIG_BLACKFIN */ + +#endif diff --git a/drivers/usb/musb/musb_regs.h b/drivers/usb/musb/musb_regs.h new file mode 100644 index 00000000..03f2655a --- /dev/null +++ b/drivers/usb/musb/musb_regs.h @@ -0,0 +1,645 @@ +/* + * MUSB OTG driver register defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __MUSB_REGS_H__ +#define __MUSB_REGS_H__ + +#define MUSB_EP0_FIFOSIZE 64 /* This is non-configurable */ + +/* + * MUSB Register bits + */ + +/* POWER */ +#define MUSB_POWER_ISOUPDATE 0x80 +#define MUSB_POWER_SOFTCONN 0x40 +#define MUSB_POWER_HSENAB 0x20 +#define MUSB_POWER_HSMODE 0x10 +#define MUSB_POWER_RESET 0x08 +#define MUSB_POWER_RESUME 0x04 +#define MUSB_POWER_SUSPENDM 0x02 +#define MUSB_POWER_ENSUSPEND 0x01 + +/* INTRUSB */ +#define MUSB_INTR_SUSPEND 0x01 +#define MUSB_INTR_RESUME 0x02 +#define MUSB_INTR_RESET 0x04 +#define MUSB_INTR_BABBLE 0x04 +#define MUSB_INTR_SOF 0x08 +#define MUSB_INTR_CONNECT 0x10 +#define MUSB_INTR_DISCONNECT 0x20 +#define MUSB_INTR_SESSREQ 0x40 +#define MUSB_INTR_VBUSERROR 0x80 /* For SESSION end */ + +/* DEVCTL */ +#define MUSB_DEVCTL_BDEVICE 0x80 +#define MUSB_DEVCTL_FSDEV 0x40 +#define MUSB_DEVCTL_LSDEV 0x20 +#define MUSB_DEVCTL_VBUS 0x18 +#define MUSB_DEVCTL_VBUS_SHIFT 3 +#define MUSB_DEVCTL_HM 0x04 +#define MUSB_DEVCTL_HR 0x02 +#define MUSB_DEVCTL_SESSION 0x01 + +/* MUSB ULPI VBUSCONTROL */ +#define MUSB_ULPI_USE_EXTVBUS 0x01 +#define MUSB_ULPI_USE_EXTVBUSIND 0x02 +/* ULPI_REG_CONTROL */ +#define MUSB_ULPI_REG_REQ (1 << 0) +#define MUSB_ULPI_REG_CMPLT (1 << 1) +#define MUSB_ULPI_RDN_WR (1 << 2) + +/* TESTMODE */ +#define MUSB_TEST_FORCE_HOST 0x80 +#define MUSB_TEST_FIFO_ACCESS 0x40 +#define MUSB_TEST_FORCE_FS 0x20 +#define MUSB_TEST_FORCE_HS 0x10 +#define MUSB_TEST_PACKET 0x08 +#define MUSB_TEST_K 0x04 +#define MUSB_TEST_J 0x02 +#define MUSB_TEST_SE0_NAK 0x01 + +/* Allocate for double-packet buffering (effectively doubles assigned _SIZE) */ +#define MUSB_FIFOSZ_DPB 0x10 +/* Allocation size (8, 16, 32, ... 4096) */ +#define MUSB_FIFOSZ_SIZE 0x0f + +/* CSR0 */ +#define MUSB_CSR0_FLUSHFIFO 0x0100 +#define MUSB_CSR0_TXPKTRDY 0x0002 +#define MUSB_CSR0_RXPKTRDY 0x0001 + +/* CSR0 in Peripheral mode */ +#define MUSB_CSR0_P_SVDSETUPEND 0x0080 +#define MUSB_CSR0_P_SVDRXPKTRDY 0x0040 +#define MUSB_CSR0_P_SENDSTALL 0x0020 +#define MUSB_CSR0_P_SETUPEND 0x0010 +#define MUSB_CSR0_P_DATAEND 0x0008 +#define MUSB_CSR0_P_SENTSTALL 0x0004 + +/* CSR0 in Host mode */ +#define MUSB_CSR0_H_DIS_PING 0x0800 +#define MUSB_CSR0_H_WR_DATATOGGLE 0x0400 /* Set to allow setting: */ +#define MUSB_CSR0_H_DATATOGGLE 0x0200 /* Data toggle control */ +#define MUSB_CSR0_H_NAKTIMEOUT 0x0080 +#define MUSB_CSR0_H_STATUSPKT 0x0040 +#define MUSB_CSR0_H_REQPKT 0x0020 +#define MUSB_CSR0_H_ERROR 0x0010 +#define MUSB_CSR0_H_SETUPPKT 0x0008 +#define MUSB_CSR0_H_RXSTALL 0x0004 + +/* CSR0 bits to avoid zeroing (write zero clears, write 1 ignored) */ +#define MUSB_CSR0_P_WZC_BITS \ + (MUSB_CSR0_P_SENTSTALL) +#define MUSB_CSR0_H_WZC_BITS \ + (MUSB_CSR0_H_NAKTIMEOUT | MUSB_CSR0_H_RXSTALL \ + | MUSB_CSR0_RXPKTRDY) + +/* TxType/RxType */ +#define MUSB_TYPE_SPEED 0xc0 +#define MUSB_TYPE_SPEED_SHIFT 6 +#define MUSB_TYPE_PROTO 0x30 /* Implicitly zero for ep0 */ +#define MUSB_TYPE_PROTO_SHIFT 4 +#define MUSB_TYPE_REMOTE_END 0xf /* Implicitly zero for ep0 */ + +/* CONFIGDATA */ +#define MUSB_CONFIGDATA_MPRXE 0x80 /* Auto bulk pkt combining */ +#define MUSB_CONFIGDATA_MPTXE 0x40 /* Auto bulk pkt splitting */ +#define MUSB_CONFIGDATA_BIGENDIAN 0x20 +#define MUSB_CONFIGDATA_HBRXE 0x10 /* HB-ISO for RX */ +#define MUSB_CONFIGDATA_HBTXE 0x08 /* HB-ISO for TX */ +#define MUSB_CONFIGDATA_DYNFIFO 0x04 /* Dynamic FIFO sizing */ +#define MUSB_CONFIGDATA_SOFTCONE 0x02 /* SoftConnect */ +#define MUSB_CONFIGDATA_UTMIDW 0x01 /* Data width 0/1 => 8/16bits */ + +/* TXCSR in Peripheral and Host mode */ +#define MUSB_TXCSR_AUTOSET 0x8000 +#define MUSB_TXCSR_DMAENAB 0x1000 +#define MUSB_TXCSR_FRCDATATOG 0x0800 +#define MUSB_TXCSR_DMAMODE 0x0400 +#define MUSB_TXCSR_CLRDATATOG 0x0040 +#define MUSB_TXCSR_FLUSHFIFO 0x0008 +#define MUSB_TXCSR_FIFONOTEMPTY 0x0002 +#define MUSB_TXCSR_TXPKTRDY 0x0001 + +/* TXCSR in Peripheral mode */ +#define MUSB_TXCSR_P_ISO 0x4000 +#define MUSB_TXCSR_P_INCOMPTX 0x0080 +#define MUSB_TXCSR_P_SENTSTALL 0x0020 +#define MUSB_TXCSR_P_SENDSTALL 0x0010 +#define MUSB_TXCSR_P_UNDERRUN 0x0004 + +/* TXCSR in Host mode */ +#define MUSB_TXCSR_H_WR_DATATOGGLE 0x0200 +#define MUSB_TXCSR_H_DATATOGGLE 0x0100 +#define MUSB_TXCSR_H_NAKTIMEOUT 0x0080 +#define MUSB_TXCSR_H_RXSTALL 0x0020 +#define MUSB_TXCSR_H_ERROR 0x0004 + +/* TXCSR bits to avoid zeroing (write zero clears, write 1 ignored) */ +#define MUSB_TXCSR_P_WZC_BITS \ + (MUSB_TXCSR_P_INCOMPTX | MUSB_TXCSR_P_SENTSTALL \ + | MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_FIFONOTEMPTY) +#define MUSB_TXCSR_H_WZC_BITS \ + (MUSB_TXCSR_H_NAKTIMEOUT | MUSB_TXCSR_H_RXSTALL \ + | MUSB_TXCSR_H_ERROR | MUSB_TXCSR_FIFONOTEMPTY) + +/* RXCSR in Peripheral and Host mode */ +#define MUSB_RXCSR_AUTOCLEAR 0x8000 +#define MUSB_RXCSR_DMAENAB 0x2000 +#define MUSB_RXCSR_DISNYET 0x1000 +#define MUSB_RXCSR_PID_ERR 0x1000 +#define MUSB_RXCSR_DMAMODE 0x0800 +#define MUSB_RXCSR_INCOMPRX 0x0100 +#define MUSB_RXCSR_CLRDATATOG 0x0080 +#define MUSB_RXCSR_FLUSHFIFO 0x0010 +#define MUSB_RXCSR_DATAERROR 0x0008 +#define MUSB_RXCSR_FIFOFULL 0x0002 +#define MUSB_RXCSR_RXPKTRDY 0x0001 + +/* RXCSR in Peripheral mode */ +#define MUSB_RXCSR_P_ISO 0x4000 +#define MUSB_RXCSR_P_SENTSTALL 0x0040 +#define MUSB_RXCSR_P_SENDSTALL 0x0020 +#define MUSB_RXCSR_P_OVERRUN 0x0004 + +/* RXCSR in Host mode */ +#define MUSB_RXCSR_H_AUTOREQ 0x4000 +#define MUSB_RXCSR_H_WR_DATATOGGLE 0x0400 +#define MUSB_RXCSR_H_DATATOGGLE 0x0200 +#define MUSB_RXCSR_H_RXSTALL 0x0040 +#define MUSB_RXCSR_H_REQPKT 0x0020 +#define MUSB_RXCSR_H_ERROR 0x0004 + +/* RXCSR bits to avoid zeroing (write zero clears, write 1 ignored) */ +#define MUSB_RXCSR_P_WZC_BITS \ + (MUSB_RXCSR_P_SENTSTALL | MUSB_RXCSR_P_OVERRUN \ + | MUSB_RXCSR_RXPKTRDY) +#define MUSB_RXCSR_H_WZC_BITS \ + (MUSB_RXCSR_H_RXSTALL | MUSB_RXCSR_H_ERROR \ + | MUSB_RXCSR_DATAERROR | MUSB_RXCSR_RXPKTRDY) + +/* HUBADDR */ +#define MUSB_HUBADDR_MULTI_TT 0x80 + + +#ifndef CONFIG_BLACKFIN + +/* + * Common USB registers + */ + +#define MUSB_FADDR 0x00 /* 8-bit */ +#define MUSB_POWER 0x01 /* 8-bit */ + +#define MUSB_INTRTX 0x02 /* 16-bit */ +#define MUSB_INTRRX 0x04 +#define MUSB_INTRTXE 0x06 +#define MUSB_INTRRXE 0x08 +#define MUSB_INTRUSB 0x0A /* 8 bit */ +#define MUSB_INTRUSBE 0x0B /* 8 bit */ +#define MUSB_FRAME 0x0C +#define MUSB_INDEX 0x0E /* 8 bit */ +#define MUSB_TESTMODE 0x0F /* 8 bit */ + +/* Get offset for a given FIFO from musb->mregs */ +#if defined(CONFIG_USB_MUSB_TUSB6010) || \ + defined(CONFIG_USB_MUSB_TUSB6010_MODULE) +#define MUSB_FIFO_OFFSET(epnum) (0x200 + ((epnum) * 0x20)) +#else +#define MUSB_FIFO_OFFSET(epnum) (0x20 + ((epnum) * 4)) +#endif + +/* + * Additional Control Registers + */ + +#define MUSB_DEVCTL 0x60 /* 8 bit */ + +/* These are always controlled through the INDEX register */ +#define MUSB_TXFIFOSZ 0x62 /* 8-bit (see masks) */ +#define MUSB_RXFIFOSZ 0x63 /* 8-bit (see masks) */ +#define MUSB_TXFIFOADD 0x64 /* 16-bit offset shifted right 3 */ +#define MUSB_RXFIFOADD 0x66 /* 16-bit offset shifted right 3 */ + +/* REVISIT: vctrl/vstatus: optional vendor utmi+phy register at 0x68 */ +#define MUSB_HWVERS 0x6C /* 8 bit */ +#define MUSB_ULPI_BUSCONTROL 0x70 /* 8 bit */ +#define MUSB_ULPI_INT_MASK 0x72 /* 8 bit */ +#define MUSB_ULPI_INT_SRC 0x73 /* 8 bit */ +#define MUSB_ULPI_REG_DATA 0x74 /* 8 bit */ +#define MUSB_ULPI_REG_ADDR 0x75 /* 8 bit */ +#define MUSB_ULPI_REG_CONTROL 0x76 /* 8 bit */ +#define MUSB_ULPI_RAW_DATA 0x77 /* 8 bit */ + +#define MUSB_EPINFO 0x78 /* 8 bit */ +#define MUSB_RAMINFO 0x79 /* 8 bit */ +#define MUSB_LINKINFO 0x7a /* 8 bit */ +#define MUSB_VPLEN 0x7b /* 8 bit */ +#define MUSB_HS_EOF1 0x7c /* 8 bit */ +#define MUSB_FS_EOF1 0x7d /* 8 bit */ +#define MUSB_LS_EOF1 0x7e /* 8 bit */ + +/* Offsets to endpoint registers */ +#define MUSB_TXMAXP 0x00 +#define MUSB_TXCSR 0x02 +#define MUSB_CSR0 MUSB_TXCSR /* Re-used for EP0 */ +#define MUSB_RXMAXP 0x04 +#define MUSB_RXCSR 0x06 +#define MUSB_RXCOUNT 0x08 +#define MUSB_COUNT0 MUSB_RXCOUNT /* Re-used for EP0 */ +#define MUSB_TXTYPE 0x0A +#define MUSB_TYPE0 MUSB_TXTYPE /* Re-used for EP0 */ +#define MUSB_TXINTERVAL 0x0B +#define MUSB_NAKLIMIT0 MUSB_TXINTERVAL /* Re-used for EP0 */ +#define MUSB_RXTYPE 0x0C +#define MUSB_RXINTERVAL 0x0D +#define MUSB_FIFOSIZE 0x0F +#define MUSB_CONFIGDATA MUSB_FIFOSIZE /* Re-used for EP0 */ + +/* Offsets to endpoint registers in indexed model (using INDEX register) */ +#define MUSB_INDEXED_OFFSET(_epnum, _offset) \ + (0x10 + (_offset)) + +/* Offsets to endpoint registers in flat models */ +#define MUSB_FLAT_OFFSET(_epnum, _offset) \ + (0x100 + (0x10*(_epnum)) + (_offset)) + +#if defined(CONFIG_USB_MUSB_TUSB6010) || \ + defined(CONFIG_USB_MUSB_TUSB6010_MODULE) +/* TUSB6010 EP0 configuration register is special */ +#define MUSB_TUSB_OFFSET(_epnum, _offset) \ + (0x10 + _offset) +#include "tusb6010.h" /* Needed "only" for TUSB_EP0_CONF */ +#endif + +#define MUSB_TXCSR_MODE 0x2000 + +/* "bus control"/target registers, for host side multipoint (external hubs) */ +#define MUSB_TXFUNCADDR 0x00 +#define MUSB_TXHUBADDR 0x02 +#define MUSB_TXHUBPORT 0x03 + +#define MUSB_RXFUNCADDR 0x04 +#define MUSB_RXHUBADDR 0x06 +#define MUSB_RXHUBPORT 0x07 + +#define MUSB_BUSCTL_OFFSET(_epnum, _offset) \ + (0x80 + (8*(_epnum)) + (_offset)) + +static inline void musb_write_txfifosz(void __iomem *mbase, u8 c_size) +{ + musb_writeb(mbase, MUSB_TXFIFOSZ, c_size); +} + +static inline void musb_write_txfifoadd(void __iomem *mbase, u16 c_off) +{ + musb_writew(mbase, MUSB_TXFIFOADD, c_off); +} + +static inline void musb_write_rxfifosz(void __iomem *mbase, u8 c_size) +{ + musb_writeb(mbase, MUSB_RXFIFOSZ, c_size); +} + +static inline void musb_write_rxfifoadd(void __iomem *mbase, u16 c_off) +{ + musb_writew(mbase, MUSB_RXFIFOADD, c_off); +} + +static inline void musb_write_ulpi_buscontrol(void __iomem *mbase, u8 val) +{ + musb_writeb(mbase, MUSB_ULPI_BUSCONTROL, val); +} + +static inline u8 musb_read_txfifosz(void __iomem *mbase) +{ + return musb_readb(mbase, MUSB_TXFIFOSZ); +} + +static inline u16 musb_read_txfifoadd(void __iomem *mbase) +{ + return musb_readw(mbase, MUSB_TXFIFOADD); +} + +static inline u8 musb_read_rxfifosz(void __iomem *mbase) +{ + return musb_readb(mbase, MUSB_RXFIFOSZ); +} + +static inline u16 musb_read_rxfifoadd(void __iomem *mbase) +{ + return musb_readw(mbase, MUSB_RXFIFOADD); +} + +static inline u8 musb_read_ulpi_buscontrol(void __iomem *mbase) +{ + return musb_readb(mbase, MUSB_ULPI_BUSCONTROL); +} + +static inline u8 musb_read_configdata(void __iomem *mbase) +{ + musb_writeb(mbase, MUSB_INDEX, 0); + return musb_readb(mbase, 0x10 + MUSB_CONFIGDATA); +} + +static inline u16 musb_read_hwvers(void __iomem *mbase) +{ + return musb_readw(mbase, MUSB_HWVERS); +} + +static inline void __iomem *musb_read_target_reg_base(u8 i, void __iomem *mbase) +{ + return (MUSB_BUSCTL_OFFSET(i, 0) + mbase); +} + +static inline void musb_write_rxfunaddr(void __iomem *ep_target_regs, + u8 qh_addr_reg) +{ + musb_writeb(ep_target_regs, MUSB_RXFUNCADDR, qh_addr_reg); +} + +static inline void musb_write_rxhubaddr(void __iomem *ep_target_regs, + u8 qh_h_addr_reg) +{ + musb_writeb(ep_target_regs, MUSB_RXHUBADDR, qh_h_addr_reg); +} + +static inline void musb_write_rxhubport(void __iomem *ep_target_regs, + u8 qh_h_port_reg) +{ + musb_writeb(ep_target_regs, MUSB_RXHUBPORT, qh_h_port_reg); +} + +static inline void musb_write_txfunaddr(void __iomem *mbase, u8 epnum, + u8 qh_addr_reg) +{ + musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXFUNCADDR), + qh_addr_reg); +} + +static inline void musb_write_txhubaddr(void __iomem *mbase, u8 epnum, + u8 qh_addr_reg) +{ + musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBADDR), + qh_addr_reg); +} + +static inline void musb_write_txhubport(void __iomem *mbase, u8 epnum, + u8 qh_h_port_reg) +{ + musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBPORT), + qh_h_port_reg); +} + +static inline u8 musb_read_rxfunaddr(void __iomem *mbase, u8 epnum) +{ + return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_RXFUNCADDR)); +} + +static inline u8 musb_read_rxhubaddr(void __iomem *mbase, u8 epnum) +{ + return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_RXHUBADDR)); +} + +static inline u8 musb_read_rxhubport(void __iomem *mbase, u8 epnum) +{ + return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_RXHUBPORT)); +} + +static inline u8 musb_read_txfunaddr(void __iomem *mbase, u8 epnum) +{ + return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXFUNCADDR)); +} + +static inline u8 musb_read_txhubaddr(void __iomem *mbase, u8 epnum) +{ + return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBADDR)); +} + +static inline u8 musb_read_txhubport(void __iomem *mbase, u8 epnum) +{ + return musb_readb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBPORT)); +} + +#else /* CONFIG_BLACKFIN */ + +#define USB_BASE USB_FADDR +#define USB_OFFSET(reg) (reg - USB_BASE) + +/* + * Common USB registers + */ +#define MUSB_FADDR USB_OFFSET(USB_FADDR) /* 8-bit */ +#define MUSB_POWER USB_OFFSET(USB_POWER) /* 8-bit */ +#define MUSB_INTRTX USB_OFFSET(USB_INTRTX) /* 16-bit */ +#define MUSB_INTRRX USB_OFFSET(USB_INTRRX) +#define MUSB_INTRTXE USB_OFFSET(USB_INTRTXE) +#define MUSB_INTRRXE USB_OFFSET(USB_INTRRXE) +#define MUSB_INTRUSB USB_OFFSET(USB_INTRUSB) /* 8 bit */ +#define MUSB_INTRUSBE USB_OFFSET(USB_INTRUSBE)/* 8 bit */ +#define MUSB_FRAME USB_OFFSET(USB_FRAME) +#define MUSB_INDEX USB_OFFSET(USB_INDEX) /* 8 bit */ +#define MUSB_TESTMODE USB_OFFSET(USB_TESTMODE)/* 8 bit */ + +/* Get offset for a given FIFO from musb->mregs */ +#define MUSB_FIFO_OFFSET(epnum) \ + (USB_OFFSET(USB_EP0_FIFO) + ((epnum) * 8)) + +/* + * Additional Control Registers + */ + +#define MUSB_DEVCTL USB_OFFSET(USB_OTG_DEV_CTL) /* 8 bit */ + +#define MUSB_LINKINFO USB_OFFSET(USB_LINKINFO)/* 8 bit */ +#define MUSB_VPLEN USB_OFFSET(USB_VPLEN) /* 8 bit */ +#define MUSB_HS_EOF1 USB_OFFSET(USB_HS_EOF1) /* 8 bit */ +#define MUSB_FS_EOF1 USB_OFFSET(USB_FS_EOF1) /* 8 bit */ +#define MUSB_LS_EOF1 USB_OFFSET(USB_LS_EOF1) /* 8 bit */ + +/* Offsets to endpoint registers */ +#define MUSB_TXMAXP 0x00 +#define MUSB_TXCSR 0x04 +#define MUSB_CSR0 MUSB_TXCSR /* Re-used for EP0 */ +#define MUSB_RXMAXP 0x08 +#define MUSB_RXCSR 0x0C +#define MUSB_RXCOUNT 0x10 +#define MUSB_COUNT0 MUSB_RXCOUNT /* Re-used for EP0 */ +#define MUSB_TXTYPE 0x14 +#define MUSB_TYPE0 MUSB_TXTYPE /* Re-used for EP0 */ +#define MUSB_TXINTERVAL 0x18 +#define MUSB_NAKLIMIT0 MUSB_TXINTERVAL /* Re-used for EP0 */ +#define MUSB_RXTYPE 0x1C +#define MUSB_RXINTERVAL 0x20 +#define MUSB_TXCOUNT 0x28 + +/* Offsets to endpoint registers in indexed model (using INDEX register) */ +#define MUSB_INDEXED_OFFSET(_epnum, _offset) \ + (0x40 + (_offset)) + +/* Offsets to endpoint registers in flat models */ +#define MUSB_FLAT_OFFSET(_epnum, _offset) \ + (USB_OFFSET(USB_EP_NI0_TXMAXP) + (0x40 * (_epnum)) + (_offset)) + +/* Not implemented - HW has separate Tx/Rx FIFO */ +#define MUSB_TXCSR_MODE 0x0000 + +static inline void musb_write_txfifosz(void __iomem *mbase, u8 c_size) +{ +} + +static inline void musb_write_txfifoadd(void __iomem *mbase, u16 c_off) +{ +} + +static inline void musb_write_rxfifosz(void __iomem *mbase, u8 c_size) +{ +} + +static inline void musb_write_rxfifoadd(void __iomem *mbase, u16 c_off) +{ +} + +static inline void musb_write_ulpi_buscontrol(void __iomem *mbase, u8 val) +{ +} + +static inline u8 musb_read_txfifosz(void __iomem *mbase) +{ + return 0; +} + +static inline u16 musb_read_txfifoadd(void __iomem *mbase) +{ + return 0; +} + +static inline u8 musb_read_rxfifosz(void __iomem *mbase) +{ + return 0; +} + +static inline u16 musb_read_rxfifoadd(void __iomem *mbase) +{ + return 0; +} + +static inline u8 musb_read_ulpi_buscontrol(void __iomem *mbase) +{ + return 0; +} + +static inline u8 musb_read_configdata(void __iomem *mbase) +{ + return 0; +} + +static inline u16 musb_read_hwvers(void __iomem *mbase) +{ + /* + * This register is invisible on Blackfin, actually the MUSB + * RTL version of Blackfin is 1.9, so just harcode its value. + */ + return MUSB_HWVERS_1900; +} + +static inline void __iomem *musb_read_target_reg_base(u8 i, void __iomem *mbase) +{ + return NULL; +} + +static inline void musb_write_rxfunaddr(void __iomem *ep_target_regs, + u8 qh_addr_req) +{ +} + +static inline void musb_write_rxhubaddr(void __iomem *ep_target_regs, + u8 qh_h_addr_reg) +{ +} + +static inline void musb_write_rxhubport(void __iomem *ep_target_regs, + u8 qh_h_port_reg) +{ +} + +static inline void musb_write_txfunaddr(void __iomem *mbase, u8 epnum, + u8 qh_addr_reg) +{ +} + +static inline void musb_write_txhubaddr(void __iomem *mbase, u8 epnum, + u8 qh_addr_reg) +{ +} + +static inline void musb_write_txhubport(void __iomem *mbase, u8 epnum, + u8 qh_h_port_reg) +{ +} + +static inline u8 musb_read_rxfunaddr(void __iomem *mbase, u8 epnum) +{ + return 0; +} + +static inline u8 musb_read_rxhubaddr(void __iomem *mbase, u8 epnum) +{ + return 0; +} + +static inline u8 musb_read_rxhubport(void __iomem *mbase, u8 epnum) +{ + return 0; +} + +static inline u8 musb_read_txfunaddr(void __iomem *mbase, u8 epnum) +{ + return 0; +} + +static inline u8 musb_read_txhubaddr(void __iomem *mbase, u8 epnum) +{ + return 0; +} + +static inline u8 musb_read_txhubport(void __iomem *mbase, u8 epnum) +{ + return 0; +} + +#endif /* CONFIG_BLACKFIN */ + +#endif /* __MUSB_REGS_H__ */ diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c new file mode 100644 index 00000000..ef7d1104 --- /dev/null +++ b/drivers/usb/musb/musb_virthub.c @@ -0,0 +1,432 @@ +/* + * MUSB OTG driver virtual root hub support + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "musb_core.h" + + +static void musb_port_suspend(struct musb *musb, bool do_suspend) +{ + struct usb_otg *otg = musb->xceiv->otg; + u8 power; + void __iomem *mbase = musb->mregs; + + if (!is_host_active(musb)) + return; + + /* NOTE: this doesn't necessarily put PHY into low power mode, + * turning off its clock; that's a function of PHY integration and + * MUSB_POWER_ENSUSPEND. PHY may need a clock (sigh) to detect + * SE0 changing to connect (J) or wakeup (K) states. + */ + power = musb_readb(mbase, MUSB_POWER); + if (do_suspend) { + int retries = 10000; + + power &= ~MUSB_POWER_RESUME; + power |= MUSB_POWER_SUSPENDM; + musb_writeb(mbase, MUSB_POWER, power); + + /* Needed for OPT A tests */ + power = musb_readb(mbase, MUSB_POWER); + while (power & MUSB_POWER_SUSPENDM) { + power = musb_readb(mbase, MUSB_POWER); + if (retries-- < 1) + break; + } + + dev_dbg(musb->controller, "Root port suspended, power %02x\n", power); + + musb->port1_status |= USB_PORT_STAT_SUSPEND; + switch (musb->xceiv->state) { + case OTG_STATE_A_HOST: + musb->xceiv->state = OTG_STATE_A_SUSPEND; + musb->is_active = otg->host->b_hnp_enable; + if (musb->is_active) + mod_timer(&musb->otg_timer, jiffies + + msecs_to_jiffies( + OTG_TIME_A_AIDL_BDIS)); + musb_platform_try_idle(musb, 0); + break; + case OTG_STATE_B_HOST: + musb->xceiv->state = OTG_STATE_B_WAIT_ACON; + musb->is_active = otg->host->b_hnp_enable; + musb_platform_try_idle(musb, 0); + break; + default: + dev_dbg(musb->controller, "bogus rh suspend? %s\n", + usb_otg_state_string(musb->xceiv->state)); + } + } else if (power & MUSB_POWER_SUSPENDM) { + power &= ~MUSB_POWER_SUSPENDM; + power |= MUSB_POWER_RESUME; + musb_writeb(mbase, MUSB_POWER, power); + + dev_dbg(musb->controller, "Root port resuming, power %02x\n", power); + + /* later, GetPortStatus will stop RESUME signaling */ + musb->port1_status |= MUSB_PORT_STAT_RESUME; + musb->rh_timer = jiffies + msecs_to_jiffies(20); + } +} + +static void musb_port_reset(struct musb *musb, bool do_reset) +{ + u8 power; + void __iomem *mbase = musb->mregs; + + if (musb->xceiv->state == OTG_STATE_B_IDLE) { + dev_dbg(musb->controller, "HNP: Returning from HNP; no hub reset from b_idle\n"); + musb->port1_status &= ~USB_PORT_STAT_RESET; + return; + } + + if (!is_host_active(musb)) + return; + + /* NOTE: caller guarantees it will turn off the reset when + * the appropriate amount of time has passed + */ + power = musb_readb(mbase, MUSB_POWER); + if (do_reset) { + + /* + * If RESUME is set, we must make sure it stays minimum 20 ms. + * Then we must clear RESUME and wait a bit to let musb start + * generating SOFs. If we don't do this, OPT HS A 6.8 tests + * fail with "Error! Did not receive an SOF before suspend + * detected". + */ + if (power & MUSB_POWER_RESUME) { + while (time_before(jiffies, musb->rh_timer)) + msleep(1); + musb_writeb(mbase, MUSB_POWER, + power & ~MUSB_POWER_RESUME); + msleep(1); + } + + musb->ignore_disconnect = true; + power &= 0xf0; + musb_writeb(mbase, MUSB_POWER, + power | MUSB_POWER_RESET); + + musb->port1_status |= USB_PORT_STAT_RESET; + musb->port1_status &= ~USB_PORT_STAT_ENABLE; + musb->rh_timer = jiffies + msecs_to_jiffies(50); + } else { + dev_dbg(musb->controller, "root port reset stopped\n"); + musb_writeb(mbase, MUSB_POWER, + power & ~MUSB_POWER_RESET); + + musb->ignore_disconnect = false; + + power = musb_readb(mbase, MUSB_POWER); + if (power & MUSB_POWER_HSMODE) { + dev_dbg(musb->controller, "high-speed device connected\n"); + musb->port1_status |= USB_PORT_STAT_HIGH_SPEED; + } + + musb->port1_status &= ~USB_PORT_STAT_RESET; + musb->port1_status |= USB_PORT_STAT_ENABLE + | (USB_PORT_STAT_C_RESET << 16) + | (USB_PORT_STAT_C_ENABLE << 16); + usb_hcd_poll_rh_status(musb_to_hcd(musb)); + + musb->vbuserr_retry = VBUSERR_RETRY_COUNT; + } +} + +void musb_root_disconnect(struct musb *musb) +{ + struct usb_otg *otg = musb->xceiv->otg; + + musb->port1_status = USB_PORT_STAT_POWER + | (USB_PORT_STAT_C_CONNECTION << 16); + + usb_hcd_poll_rh_status(musb_to_hcd(musb)); + musb->is_active = 0; + + switch (musb->xceiv->state) { + case OTG_STATE_A_SUSPEND: + if (otg->host->b_hnp_enable) { + musb->xceiv->state = OTG_STATE_A_PERIPHERAL; + musb->g.is_a_peripheral = 1; + break; + } + /* FALLTHROUGH */ + case OTG_STATE_A_HOST: + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + musb->is_active = 0; + break; + case OTG_STATE_A_WAIT_VFALL: + musb->xceiv->state = OTG_STATE_B_IDLE; + break; + default: + dev_dbg(musb->controller, "host disconnect (%s)\n", + usb_otg_state_string(musb->xceiv->state)); + } +} + + +/*---------------------------------------------------------------------*/ + +/* Caller may or may not hold musb->lock */ +int musb_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct musb *musb = hcd_to_musb(hcd); + int retval = 0; + + /* called in_irq() via usb_hcd_poll_rh_status() */ + if (musb->port1_status & 0xffff0000) { + *buf = 0x02; + retval = 1; + } + return retval; +} + +int musb_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength) +{ + struct musb *musb = hcd_to_musb(hcd); + u32 temp; + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + + if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) { + spin_unlock_irqrestore(&musb->lock, flags); + return -ESHUTDOWN; + } + + /* hub features: always zero, setting is a NOP + * port features: reported, sometimes updated when host is active + * no indicators + */ + switch (typeReq) { + case ClearHubFeature: + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case ClearPortFeature: + if ((wIndex & 0xff) != 1) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + break; + case USB_PORT_FEAT_SUSPEND: + musb_port_suspend(musb, false); + break; + case USB_PORT_FEAT_POWER: + if (!hcd->self.is_b_host) + musb_platform_set_vbus(musb, 0); + break; + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_RESET: + case USB_PORT_FEAT_C_SUSPEND: + break; + default: + goto error; + } + dev_dbg(musb->controller, "clear feature %d\n", wValue); + musb->port1_status &= ~(1 << wValue); + break; + case GetHubDescriptor: + { + struct usb_hub_descriptor *desc = (void *)buf; + + desc->bDescLength = 9; + desc->bDescriptorType = 0x29; + desc->bNbrPorts = 1; + desc->wHubCharacteristics = cpu_to_le16( + 0x0001 /* per-port power switching */ + | 0x0010 /* no overcurrent reporting */ + ); + desc->bPwrOn2PwrGood = 5; /* msec/2 */ + desc->bHubContrCurrent = 0; + + /* workaround bogus struct definition */ + desc->u.hs.DeviceRemovable[0] = 0x02; /* port 1 */ + desc->u.hs.DeviceRemovable[1] = 0xff; + } + break; + case GetHubStatus: + temp = 0; + *(__le32 *) buf = cpu_to_le32(temp); + break; + case GetPortStatus: + if (wIndex != 1) + goto error; + + /* finish RESET signaling? */ + if ((musb->port1_status & USB_PORT_STAT_RESET) + && time_after_eq(jiffies, musb->rh_timer)) + musb_port_reset(musb, false); + + /* finish RESUME signaling? */ + if ((musb->port1_status & MUSB_PORT_STAT_RESUME) + && time_after_eq(jiffies, musb->rh_timer)) { + u8 power; + + power = musb_readb(musb->mregs, MUSB_POWER); + power &= ~MUSB_POWER_RESUME; + dev_dbg(musb->controller, "root port resume stopped, power %02x\n", + power); + musb_writeb(musb->mregs, MUSB_POWER, power); + + /* ISSUE: DaVinci (RTL 1.300) disconnects after + * resume of high speed peripherals (but not full + * speed ones). + */ + + musb->is_active = 1; + musb->port1_status &= ~(USB_PORT_STAT_SUSPEND + | MUSB_PORT_STAT_RESUME); + musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16; + usb_hcd_poll_rh_status(musb_to_hcd(musb)); + /* NOTE: it might really be A_WAIT_BCON ... */ + musb->xceiv->state = OTG_STATE_A_HOST; + } + + put_unaligned(cpu_to_le32(musb->port1_status + & ~MUSB_PORT_STAT_RESUME), + (__le32 *) buf); + + /* port change status is more interesting */ + dev_dbg(musb->controller, "port status %08x\n", + musb->port1_status); + break; + case SetPortFeature: + if ((wIndex & 0xff) != 1) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_POWER: + /* NOTE: this controller has a strange state machine + * that involves "requesting sessions" according to + * magic side effects from incompletely-described + * rules about startup... + * + * This call is what really starts the host mode; be + * very careful about side effects if you reorder any + * initialization logic, e.g. for OTG, or change any + * logic relating to VBUS power-up. + */ + if (!hcd->self.is_b_host) + musb_start(musb); + break; + case USB_PORT_FEAT_RESET: + musb_port_reset(musb, true); + break; + case USB_PORT_FEAT_SUSPEND: + musb_port_suspend(musb, true); + break; + case USB_PORT_FEAT_TEST: + if (unlikely(is_host_active(musb))) + goto error; + + wIndex >>= 8; + switch (wIndex) { + case 1: + pr_debug("TEST_J\n"); + temp = MUSB_TEST_J; + break; + case 2: + pr_debug("TEST_K\n"); + temp = MUSB_TEST_K; + break; + case 3: + pr_debug("TEST_SE0_NAK\n"); + temp = MUSB_TEST_SE0_NAK; + break; + case 4: + pr_debug("TEST_PACKET\n"); + temp = MUSB_TEST_PACKET; + musb_load_testpacket(musb); + break; + case 5: + pr_debug("TEST_FORCE_ENABLE\n"); + temp = MUSB_TEST_FORCE_HOST + | MUSB_TEST_FORCE_HS; + + musb_writeb(musb->mregs, MUSB_DEVCTL, + MUSB_DEVCTL_SESSION); + break; + case 6: + pr_debug("TEST_FIFO_ACCESS\n"); + temp = MUSB_TEST_FIFO_ACCESS; + break; + default: + goto error; + } + musb_writeb(musb->mregs, MUSB_TESTMODE, temp); + break; + default: + goto error; + } + dev_dbg(musb->controller, "set feature %d\n", wValue); + musb->port1_status |= 1 << wValue; + break; + + default: +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&musb->lock, flags); + return retval; +} diff --git a/drivers/usb/musb/musbhsdma.c b/drivers/usb/musb/musbhsdma.c new file mode 100644 index 00000000..3d1fd52a --- /dev/null +++ b/drivers/usb/musb/musbhsdma.c @@ -0,0 +1,421 @@ +/* + * MUSB OTG driver - support for Mentor's DMA controller + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2007 by Texas Instruments + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#include +#include +#include +#include +#include "musb_core.h" +#include "musbhsdma.h" + +static int dma_controller_start(struct dma_controller *c) +{ + /* nothing to do */ + return 0; +} + +static void dma_channel_release(struct dma_channel *channel); + +static int dma_controller_stop(struct dma_controller *c) +{ + struct musb_dma_controller *controller = container_of(c, + struct musb_dma_controller, controller); + struct musb *musb = controller->private_data; + struct dma_channel *channel; + u8 bit; + + if (controller->used_channels != 0) { + dev_err(musb->controller, + "Stopping DMA controller while channel active\n"); + + for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) { + if (controller->used_channels & (1 << bit)) { + channel = &controller->channel[bit].channel; + dma_channel_release(channel); + + if (!controller->used_channels) + break; + } + } + } + + return 0; +} + +static struct dma_channel *dma_channel_allocate(struct dma_controller *c, + struct musb_hw_ep *hw_ep, u8 transmit) +{ + struct musb_dma_controller *controller = container_of(c, + struct musb_dma_controller, controller); + struct musb_dma_channel *musb_channel = NULL; + struct dma_channel *channel = NULL; + u8 bit; + + for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) { + if (!(controller->used_channels & (1 << bit))) { + controller->used_channels |= (1 << bit); + musb_channel = &(controller->channel[bit]); + musb_channel->controller = controller; + musb_channel->idx = bit; + musb_channel->epnum = hw_ep->epnum; + musb_channel->transmit = transmit; + channel = &(musb_channel->channel); + channel->private_data = musb_channel; + channel->status = MUSB_DMA_STATUS_FREE; + channel->max_len = 0x100000; + /* Tx => mode 1; Rx => mode 0 */ + channel->desired_mode = transmit; + channel->actual_len = 0; + break; + } + } + + return channel; +} + +static void dma_channel_release(struct dma_channel *channel) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + + channel->actual_len = 0; + musb_channel->start_addr = 0; + musb_channel->len = 0; + + musb_channel->controller->used_channels &= + ~(1 << musb_channel->idx); + + channel->status = MUSB_DMA_STATUS_UNKNOWN; +} + +static void configure_channel(struct dma_channel *channel, + u16 packet_sz, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + struct musb_dma_controller *controller = musb_channel->controller; + struct musb *musb = controller->private_data; + void __iomem *mbase = controller->base; + u8 bchannel = musb_channel->idx; + u16 csr = 0; + + dev_dbg(musb->controller, "%p, pkt_sz %d, addr 0x%x, len %d, mode %d\n", + channel, packet_sz, dma_addr, len, mode); + + if (mode) { + csr |= 1 << MUSB_HSDMA_MODE1_SHIFT; + BUG_ON(len < packet_sz); + } + csr |= MUSB_HSDMA_BURSTMODE_INCR16 + << MUSB_HSDMA_BURSTMODE_SHIFT; + + csr |= (musb_channel->epnum << MUSB_HSDMA_ENDPOINT_SHIFT) + | (1 << MUSB_HSDMA_ENABLE_SHIFT) + | (1 << MUSB_HSDMA_IRQENABLE_SHIFT) + | (musb_channel->transmit + ? (1 << MUSB_HSDMA_TRANSMIT_SHIFT) + : 0); + + /* address/count */ + musb_write_hsdma_addr(mbase, bchannel, dma_addr); + musb_write_hsdma_count(mbase, bchannel, len); + + /* control (this should start things) */ + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL), + csr); +} + +static int dma_channel_program(struct dma_channel *channel, + u16 packet_sz, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + struct musb_dma_controller *controller = musb_channel->controller; + struct musb *musb = controller->private_data; + + dev_dbg(musb->controller, "ep%d-%s pkt_sz %d, dma_addr 0x%x length %d, mode %d\n", + musb_channel->epnum, + musb_channel->transmit ? "Tx" : "Rx", + packet_sz, dma_addr, len, mode); + + BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN || + channel->status == MUSB_DMA_STATUS_BUSY); + + /* Let targets check/tweak the arguments */ + if (musb->ops->adjust_channel_params) { + int ret = musb->ops->adjust_channel_params(channel, + packet_sz, &mode, &dma_addr, &len); + if (ret) + return ret; + } + + /* + * The DMA engine in RTL1.8 and above cannot handle + * DMA addresses that are not aligned to a 4 byte boundary. + * It ends up masking the last two bits of the address + * programmed in DMA_ADDR. + * + * Fail such DMA transfers, so that the backup PIO mode + * can carry out the transfer + */ + if ((musb->hwvers >= MUSB_HWVERS_1800) && (dma_addr % 4)) + return false; + + channel->actual_len = 0; + musb_channel->start_addr = dma_addr; + musb_channel->len = len; + musb_channel->max_packet_sz = packet_sz; + channel->status = MUSB_DMA_STATUS_BUSY; + + configure_channel(channel, packet_sz, mode, dma_addr, len); + + return true; +} + +static int dma_channel_abort(struct dma_channel *channel) +{ + struct musb_dma_channel *musb_channel = channel->private_data; + void __iomem *mbase = musb_channel->controller->base; + + u8 bchannel = musb_channel->idx; + int offset; + u16 csr; + + if (channel->status == MUSB_DMA_STATUS_BUSY) { + if (musb_channel->transmit) { + offset = MUSB_EP_OFFSET(musb_channel->epnum, + MUSB_TXCSR); + + /* + * The programming guide says that we must clear + * the DMAENAB bit before the DMAMODE bit... + */ + csr = musb_readw(mbase, offset); + csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB); + musb_writew(mbase, offset, csr); + csr &= ~MUSB_TXCSR_DMAMODE; + musb_writew(mbase, offset, csr); + } else { + offset = MUSB_EP_OFFSET(musb_channel->epnum, + MUSB_RXCSR); + + csr = musb_readw(mbase, offset); + csr &= ~(MUSB_RXCSR_AUTOCLEAR | + MUSB_RXCSR_DMAENAB | + MUSB_RXCSR_DMAMODE); + musb_writew(mbase, offset, csr); + } + + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL), + 0); + musb_write_hsdma_addr(mbase, bchannel, 0); + musb_write_hsdma_count(mbase, bchannel, 0); + channel->status = MUSB_DMA_STATUS_FREE; + } + + return 0; +} + +static irqreturn_t dma_controller_irq(int irq, void *private_data) +{ + struct musb_dma_controller *controller = private_data; + struct musb *musb = controller->private_data; + struct musb_dma_channel *musb_channel; + struct dma_channel *channel; + + void __iomem *mbase = controller->base; + + irqreturn_t retval = IRQ_NONE; + + unsigned long flags; + + u8 bchannel; + u8 int_hsdma; + + u32 addr, count; + u16 csr; + + spin_lock_irqsave(&musb->lock, flags); + + int_hsdma = musb_readb(mbase, MUSB_HSDMA_INTR); + +#ifdef CONFIG_BLACKFIN + /* Clear DMA interrupt flags */ + musb_writeb(mbase, MUSB_HSDMA_INTR, int_hsdma); +#endif + + if (!int_hsdma) { + dev_dbg(musb->controller, "spurious DMA irq\n"); + + for (bchannel = 0; bchannel < MUSB_HSDMA_CHANNELS; bchannel++) { + musb_channel = (struct musb_dma_channel *) + &(controller->channel[bchannel]); + channel = &musb_channel->channel; + if (channel->status == MUSB_DMA_STATUS_BUSY) { + count = musb_read_hsdma_count(mbase, bchannel); + + if (count == 0) + int_hsdma |= (1 << bchannel); + } + } + + dev_dbg(musb->controller, "int_hsdma = 0x%x\n", int_hsdma); + + if (!int_hsdma) + goto done; + } + + for (bchannel = 0; bchannel < MUSB_HSDMA_CHANNELS; bchannel++) { + if (int_hsdma & (1 << bchannel)) { + musb_channel = (struct musb_dma_channel *) + &(controller->channel[bchannel]); + channel = &musb_channel->channel; + + csr = musb_readw(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, + MUSB_HSDMA_CONTROL)); + + if (csr & (1 << MUSB_HSDMA_BUSERROR_SHIFT)) { + musb_channel->channel.status = + MUSB_DMA_STATUS_BUS_ABORT; + } else { + u8 devctl; + + addr = musb_read_hsdma_addr(mbase, + bchannel); + channel->actual_len = addr + - musb_channel->start_addr; + + dev_dbg(musb->controller, "ch %p, 0x%x -> 0x%x (%zu / %d) %s\n", + channel, musb_channel->start_addr, + addr, channel->actual_len, + musb_channel->len, + (channel->actual_len + < musb_channel->len) ? + "=> reconfig 0" : "=> complete"); + + devctl = musb_readb(mbase, MUSB_DEVCTL); + + channel->status = MUSB_DMA_STATUS_FREE; + + /* completed */ + if ((devctl & MUSB_DEVCTL_HM) + && (musb_channel->transmit) + && ((channel->desired_mode == 0) + || (channel->actual_len & + (musb_channel->max_packet_sz - 1))) + ) { + u8 epnum = musb_channel->epnum; + int offset = MUSB_EP_OFFSET(epnum, + MUSB_TXCSR); + u16 txcsr; + + /* + * The programming guide says that we + * must clear DMAENAB before DMAMODE. + */ + musb_ep_select(mbase, epnum); + txcsr = musb_readw(mbase, offset); + txcsr &= ~(MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_AUTOSET); + musb_writew(mbase, offset, txcsr); + /* Send out the packet */ + txcsr &= ~MUSB_TXCSR_DMAMODE; + txcsr |= MUSB_TXCSR_TXPKTRDY; + musb_writew(mbase, offset, txcsr); + } + musb_dma_completion(musb, musb_channel->epnum, + musb_channel->transmit); + } + } + } + + retval = IRQ_HANDLED; +done: + spin_unlock_irqrestore(&musb->lock, flags); + return retval; +} + +void dma_controller_destroy(struct dma_controller *c) +{ + struct musb_dma_controller *controller = container_of(c, + struct musb_dma_controller, controller); + + if (!controller) + return; + + if (controller->irq) + free_irq(controller->irq, c); + + kfree(controller); +} + +struct dma_controller *dma_controller_create(struct musb *musb, void __iomem *base) +{ + struct musb_dma_controller *controller; + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev); + int irq = platform_get_irq_byname(pdev, "dma"); + + if (irq <= 0) { + dev_err(dev, "No DMA interrupt line!\n"); + return NULL; + } + + controller = kzalloc(sizeof(*controller), GFP_KERNEL); + if (!controller) + return NULL; + + controller->channel_count = MUSB_HSDMA_CHANNELS; + controller->private_data = musb; + controller->base = base; + + controller->controller.start = dma_controller_start; + controller->controller.stop = dma_controller_stop; + controller->controller.channel_alloc = dma_channel_allocate; + controller->controller.channel_release = dma_channel_release; + controller->controller.channel_program = dma_channel_program; + controller->controller.channel_abort = dma_channel_abort; + + if (request_irq(irq, dma_controller_irq, 0, + dev_name(musb->controller), &controller->controller)) { + dev_err(dev, "request_irq %d failed!\n", irq); + dma_controller_destroy(&controller->controller); + + return NULL; + } + + controller->irq = irq; + + return &controller->controller; +} diff --git a/drivers/usb/musb/musbhsdma.h b/drivers/usb/musb/musbhsdma.h new file mode 100644 index 00000000..f7b13fd2 --- /dev/null +++ b/drivers/usb/musb/musbhsdma.h @@ -0,0 +1,161 @@ +/* + * MUSB OTG driver - support for Mentor's DMA controller + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2007 by Texas Instruments + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef CONFIG_BLACKFIN + +#define MUSB_HSDMA_BASE 0x200 +#define MUSB_HSDMA_INTR (MUSB_HSDMA_BASE + 0) +#define MUSB_HSDMA_CONTROL 0x4 +#define MUSB_HSDMA_ADDRESS 0x8 +#define MUSB_HSDMA_COUNT 0xc + +#define MUSB_HSDMA_CHANNEL_OFFSET(_bchannel, _offset) \ + (MUSB_HSDMA_BASE + (_bchannel << 4) + _offset) + +#define musb_read_hsdma_addr(mbase, bchannel) \ + musb_readl(mbase, \ + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS)) + +#define musb_write_hsdma_addr(mbase, bchannel, addr) \ + musb_writel(mbase, \ + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS), \ + addr) + +#define musb_read_hsdma_count(mbase, bchannel) \ + musb_readl(mbase, \ + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT)) + +#define musb_write_hsdma_count(mbase, bchannel, len) \ + musb_writel(mbase, \ + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT), \ + len) +#else + +#define MUSB_HSDMA_BASE 0x400 +#define MUSB_HSDMA_INTR (MUSB_HSDMA_BASE + 0) +#define MUSB_HSDMA_CONTROL 0x04 +#define MUSB_HSDMA_ADDR_LOW 0x08 +#define MUSB_HSDMA_ADDR_HIGH 0x0C +#define MUSB_HSDMA_COUNT_LOW 0x10 +#define MUSB_HSDMA_COUNT_HIGH 0x14 + +#define MUSB_HSDMA_CHANNEL_OFFSET(_bchannel, _offset) \ + (MUSB_HSDMA_BASE + (_bchannel * 0x20) + _offset) + +static inline u32 musb_read_hsdma_addr(void __iomem *mbase, u8 bchannel) +{ + u32 addr = musb_readw(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_HIGH)); + + addr = addr << 16; + + addr |= musb_readw(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_LOW)); + + return addr; +} + +static inline void musb_write_hsdma_addr(void __iomem *mbase, + u8 bchannel, dma_addr_t dma_addr) +{ + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_LOW), + dma_addr); + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_HIGH), + (dma_addr >> 16)); +} + +static inline u32 musb_read_hsdma_count(void __iomem *mbase, u8 bchannel) +{ + u32 count = musb_readw(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_HIGH)); + + count = count << 16; + + count |= musb_readw(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_LOW)); + + return count; +} + +static inline void musb_write_hsdma_count(void __iomem *mbase, + u8 bchannel, u32 len) +{ + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_LOW),len); + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_HIGH), + (len >> 16)); +} + +#endif /* CONFIG_BLACKFIN */ + +/* control register (16-bit): */ +#define MUSB_HSDMA_ENABLE_SHIFT 0 +#define MUSB_HSDMA_TRANSMIT_SHIFT 1 +#define MUSB_HSDMA_MODE1_SHIFT 2 +#define MUSB_HSDMA_IRQENABLE_SHIFT 3 +#define MUSB_HSDMA_ENDPOINT_SHIFT 4 +#define MUSB_HSDMA_BUSERROR_SHIFT 8 +#define MUSB_HSDMA_BURSTMODE_SHIFT 9 +#define MUSB_HSDMA_BURSTMODE (3 << MUSB_HSDMA_BURSTMODE_SHIFT) +#define MUSB_HSDMA_BURSTMODE_UNSPEC 0 +#define MUSB_HSDMA_BURSTMODE_INCR4 1 +#define MUSB_HSDMA_BURSTMODE_INCR8 2 +#define MUSB_HSDMA_BURSTMODE_INCR16 3 + +#define MUSB_HSDMA_CHANNELS 8 + +struct musb_dma_controller; + +struct musb_dma_channel { + struct dma_channel channel; + struct musb_dma_controller *controller; + u32 start_addr; + u32 len; + u16 max_packet_sz; + u8 idx; + u8 epnum; + u8 transmit; +}; + +struct musb_dma_controller { + struct dma_controller controller; + struct musb_dma_channel channel[MUSB_HSDMA_CHANNELS]; + void *private_data; + void __iomem *base; + u8 channel_count; + u8 used_channels; + u8 irq; +}; diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c new file mode 100644 index 00000000..628b93fe --- /dev/null +++ b/drivers/usb/musb/omap2430.c @@ -0,0 +1,691 @@ +/* + * Copyright (C) 2005-2007 by Texas Instruments + * Some code has been taken from tusb6010.c + * Copyrights for that are attributable to: + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + * + * The Inventra Controller Driver for Linux is distributed in + * the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" +#include "omap2430.h" + +struct omap2430_glue { + struct device *dev; + struct platform_device *musb; + enum omap_musb_vbus_id_status status; + struct work_struct omap_musb_mailbox_work; + struct device *control_otghs; +}; +#define glue_to_musb(g) platform_get_drvdata(g->musb) + +static struct omap2430_glue *_glue; + +static struct timer_list musb_idle_timer; + +static void musb_do_idle(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + unsigned long flags; + u8 power; + u8 devctl; + + spin_lock_irqsave(&musb->lock, flags); + + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_BCON: + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + break; + case OTG_STATE_A_SUSPEND: + /* finish RESUME signaling? */ + if (musb->port1_status & MUSB_PORT_STAT_RESUME) { + power = musb_readb(musb->mregs, MUSB_POWER); + power &= ~MUSB_POWER_RESUME; + dev_dbg(musb->controller, "root port resume stopped, power %02x\n", power); + musb_writeb(musb->mregs, MUSB_POWER, power); + musb->is_active = 1; + musb->port1_status &= ~(USB_PORT_STAT_SUSPEND + | MUSB_PORT_STAT_RESUME); + musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16; + usb_hcd_poll_rh_status(musb_to_hcd(musb)); + /* NOTE: it might really be A_WAIT_BCON ... */ + musb->xceiv->state = OTG_STATE_A_HOST; + } + break; + case OTG_STATE_A_HOST: + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (devctl & MUSB_DEVCTL_BDEVICE) + musb->xceiv->state = OTG_STATE_B_IDLE; + else + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + + +static void omap2430_musb_try_idle(struct musb *musb, unsigned long timeout) +{ + unsigned long default_timeout = jiffies + msecs_to_jiffies(3); + static unsigned long last_timer; + + if (timeout == 0) + timeout = default_timeout; + + /* Never idle if active, or when VBUS timeout is not set as host */ + if (musb->is_active || ((musb->a_wait_bcon == 0) + && (musb->xceiv->state == OTG_STATE_A_WAIT_BCON))) { + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->state)); + del_timer(&musb_idle_timer); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout)) { + if (!timer_pending(&musb_idle_timer)) + last_timer = timeout; + else { + dev_dbg(musb->controller, "Longer idle timer already pending, ignoring\n"); + return; + } + } + last_timer = timeout; + + dev_dbg(musb->controller, "%s inactive, for idle timer for %lu ms\n", + usb_otg_state_string(musb->xceiv->state), + (unsigned long)jiffies_to_msecs(timeout - jiffies)); + mod_timer(&musb_idle_timer, timeout); +} + +static void omap2430_musb_set_vbus(struct musb *musb, int is_on) +{ + struct usb_otg *otg = musb->xceiv->otg; + u8 devctl; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + /* HDRC controls CPEN, but beware current surges during device + * connect. They can trigger transient overcurrent conditions + * that must be ignored. + */ + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + if (musb->xceiv->state == OTG_STATE_A_IDLE) { + int loops = 100; + /* start the session */ + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + /* + * Wait for the musb to set as A device to enable the + * VBUS + */ + while (musb_readb(musb->mregs, MUSB_DEVCTL) & 0x80) { + + mdelay(5); + cpu_relax(); + + if (time_after(jiffies, timeout) + || loops-- <= 0) { + dev_err(musb->controller, + "configured as A device timeout"); + break; + } + } + + otg_set_vbus(otg, 1); + } else { + musb->is_active = 1; + otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + MUSB_HST_MODE(musb); + } + } else { + musb->is_active = 0; + + /* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and + * jumping right to B_IDLE... + */ + + otg->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; + devctl &= ~MUSB_DEVCTL_SESSION; + + MUSB_DEV_MODE(musb); + } + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + dev_dbg(musb->controller, "VBUS %s, devctl %02x " + /* otg %3x conf %08x prcm %08x */ "\n", + usb_otg_state_string(musb->xceiv->state), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} + +static int omap2430_musb_set_mode(struct musb *musb, u8 musb_mode) +{ + u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + return 0; +} + +static inline void omap2430_low_level_exit(struct musb *musb) +{ + u32 l; + + /* in any role */ + l = musb_readl(musb->mregs, OTG_FORCESTDBY); + l |= ENABLEFORCE; /* enable MSTANDBY */ + musb_writel(musb->mregs, OTG_FORCESTDBY, l); +} + +static inline void omap2430_low_level_init(struct musb *musb) +{ + u32 l; + + l = musb_readl(musb->mregs, OTG_FORCESTDBY); + l &= ~ENABLEFORCE; /* disable MSTANDBY */ + musb_writel(musb->mregs, OTG_FORCESTDBY, l); +} + +void omap_musb_mailbox(enum omap_musb_vbus_id_status status) +{ + struct omap2430_glue *glue = _glue; + + if (!glue) { + pr_err("%s: musb core is not yet initialized\n", __func__); + return; + } + glue->status = status; + + if (!glue_to_musb(glue)) { + pr_err("%s: musb core is not yet ready\n", __func__); + return; + } + + schedule_work(&glue->omap_musb_mailbox_work); +} +EXPORT_SYMBOL_GPL(omap_musb_mailbox); + +static void omap_musb_set_mailbox(struct omap2430_glue *glue) +{ + struct musb *musb = glue_to_musb(glue); + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *pdata = dev->platform_data; + struct omap_musb_board_data *data = pdata->board_data; + struct usb_otg *otg = musb->xceiv->otg; + + switch (glue->status) { + case OMAP_MUSB_ID_GROUND: + dev_dbg(dev, "ID GND\n"); + + otg->default_a = true; + musb->xceiv->state = OTG_STATE_A_IDLE; + musb->xceiv->last_event = USB_EVENT_ID; + if (musb->gadget_driver) { + pm_runtime_get_sync(dev); + omap_control_usb_set_mode(glue->control_otghs, + USB_MODE_HOST); + omap2430_musb_set_vbus(musb, 1); + } + break; + + case OMAP_MUSB_VBUS_VALID: + dev_dbg(dev, "VBUS Connect\n"); + + otg->default_a = false; + musb->xceiv->state = OTG_STATE_B_IDLE; + musb->xceiv->last_event = USB_EVENT_VBUS; + if (musb->gadget_driver) + pm_runtime_get_sync(dev); + omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE); + break; + + case OMAP_MUSB_ID_FLOAT: + case OMAP_MUSB_VBUS_OFF: + dev_dbg(dev, "VBUS Disconnect\n"); + + musb->xceiv->last_event = USB_EVENT_NONE; + if (musb->gadget_driver) { + omap2430_musb_set_vbus(musb, 0); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + } + + if (data->interface_type == MUSB_INTERFACE_UTMI) + otg_set_vbus(musb->xceiv->otg, 0); + + omap_control_usb_set_mode(glue->control_otghs, + USB_MODE_DISCONNECT); + break; + default: + dev_dbg(dev, "ID float\n"); + } +} + + +static void omap_musb_mailbox_work(struct work_struct *mailbox_work) +{ + struct omap2430_glue *glue = container_of(mailbox_work, + struct omap2430_glue, omap_musb_mailbox_work); + omap_musb_set_mailbox(glue); +} + +static irqreturn_t omap2430_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + struct musb *musb = __hci; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb || musb->int_tx || musb->int_rx) + retval = musb_interrupt(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + + return retval; +} + +static int omap2430_musb_init(struct musb *musb) +{ + u32 l; + int status = 0; + struct device *dev = musb->controller; + struct omap2430_glue *glue = dev_get_drvdata(dev->parent); + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct omap_musb_board_data *data = plat->board_data; + + /* We require some kind of external transceiver, hooked + * up through ULPI. TWL4030-family PMICs include one, + * which needs a driver, drivers aren't always needed. + */ + if (dev->parent->of_node) + musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent, + "usb-phy", 0); + else + musb->xceiv = devm_usb_get_phy_dev(dev, 0); + + if (IS_ERR(musb->xceiv)) { + status = PTR_ERR(musb->xceiv); + + if (status == -ENXIO) + return status; + + pr_err("HS USB OTG: no transceiver configured\n"); + return -EPROBE_DEFER; + } + + musb->isr = omap2430_musb_interrupt; + + status = pm_runtime_get_sync(dev); + if (status < 0) { + dev_err(dev, "pm_runtime_get_sync FAILED %d\n", status); + goto err1; + } + + l = musb_readl(musb->mregs, OTG_INTERFSEL); + + if (data->interface_type == MUSB_INTERFACE_UTMI) { + /* OMAP4 uses Internal PHY GS70 which uses UTMI interface */ + l &= ~ULPI_12PIN; /* Disable ULPI */ + l |= UTMI_8BIT; /* Enable UTMI */ + } else { + l |= ULPI_12PIN; + } + + musb_writel(musb->mregs, OTG_INTERFSEL, l); + + pr_debug("HS USB OTG: revision 0x%x, sysconfig 0x%02x, " + "sysstatus 0x%x, intrfsel 0x%x, simenable 0x%x\n", + musb_readl(musb->mregs, OTG_REVISION), + musb_readl(musb->mregs, OTG_SYSCONFIG), + musb_readl(musb->mregs, OTG_SYSSTATUS), + musb_readl(musb->mregs, OTG_INTERFSEL), + musb_readl(musb->mregs, OTG_SIMENABLE)); + + setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb); + + if (glue->status != OMAP_MUSB_UNKNOWN) + omap_musb_set_mailbox(glue); + + usb_phy_init(musb->xceiv); + + pm_runtime_put_noidle(musb->controller); + return 0; + +err1: + return status; +} + +static void omap2430_musb_enable(struct musb *musb) +{ + u8 devctl; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + struct device *dev = musb->controller; + struct omap2430_glue *glue = dev_get_drvdata(dev->parent); + struct musb_hdrc_platform_data *pdata = dev->platform_data; + struct omap_musb_board_data *data = pdata->board_data; + + switch (glue->status) { + + case OMAP_MUSB_ID_GROUND: + omap_control_usb_set_mode(glue->control_otghs, USB_MODE_HOST); + if (data->interface_type != MUSB_INTERFACE_UTMI) + break; + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + /* start the session */ + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + while (musb_readb(musb->mregs, MUSB_DEVCTL) & + MUSB_DEVCTL_BDEVICE) { + cpu_relax(); + + if (time_after(jiffies, timeout)) { + dev_err(dev, "configured as A device timeout"); + break; + } + } + break; + + case OMAP_MUSB_VBUS_VALID: + omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE); + break; + + default: + break; + } +} + +static void omap2430_musb_disable(struct musb *musb) +{ + struct device *dev = musb->controller; + struct omap2430_glue *glue = dev_get_drvdata(dev->parent); + + if (glue->status != OMAP_MUSB_UNKNOWN) + omap_control_usb_set_mode(glue->control_otghs, + USB_MODE_DISCONNECT); +} + +static int omap2430_musb_exit(struct musb *musb) +{ + del_timer_sync(&musb_idle_timer); + + omap2430_low_level_exit(musb); + + return 0; +} + +static const struct musb_platform_ops omap2430_ops = { + .init = omap2430_musb_init, + .exit = omap2430_musb_exit, + + .set_mode = omap2430_musb_set_mode, + .try_idle = omap2430_musb_try_idle, + + .set_vbus = omap2430_musb_set_vbus, + + .enable = omap2430_musb_enable, + .disable = omap2430_musb_disable, +}; + +static u64 omap2430_dmamask = DMA_BIT_MASK(32); + +static int omap2430_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct omap_musb_board_data *data; + struct platform_device *musb; + struct omap2430_glue *glue; + struct device_node *np = pdev->dev.of_node; + struct musb_hdrc_config *config; + int ret = -ENOMEM; + + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(&pdev->dev, "failed to allocate musb device\n"); + goto err0; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &omap2430_dmamask; + musb->dev.coherent_dma_mask = omap2430_dmamask; + + glue->dev = &pdev->dev; + glue->musb = musb; + glue->status = OMAP_MUSB_UNKNOWN; + + if (np) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, + "failed to allocate musb platfrom data\n"); + goto err2; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, + "failed to allocate musb board data\n"); + goto err2; + } + + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!config) { + dev_err(&pdev->dev, + "failed to allocate musb hdrc config\n"); + goto err2; + } + + of_property_read_u32(np, "mode", (u32 *)&pdata->mode); + of_property_read_u32(np, "interface-type", + (u32 *)&data->interface_type); + of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps); + of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits); + of_property_read_u32(np, "power", (u32 *)&pdata->power); + config->multipoint = of_property_read_bool(np, "multipoint"); + pdata->has_mailbox = of_property_read_bool(np, + "ti,has-mailbox"); + + pdata->board_data = data; + pdata->config = config; + } + + if (pdata->has_mailbox) { + glue->control_otghs = omap_get_control_dev(); + if (IS_ERR(glue->control_otghs)) { + dev_vdbg(&pdev->dev, "Failed to get control device\n"); + ret = PTR_ERR(glue->control_otghs); + goto err2; + } + } else { + glue->control_otghs = ERR_PTR(-ENODEV); + } + pdata->platform_ops = &omap2430_ops; + + platform_set_drvdata(pdev, glue); + + /* + * REVISIT if we ever have two instances of the wrapper, we will be + * in big trouble + */ + _glue = glue; + + INIT_WORK(&glue->omap_musb_mailbox_work, omap_musb_mailbox_work); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(&pdev->dev, "failed to add resources\n"); + goto err2; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err2; + } + + pm_runtime_enable(&pdev->dev); + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err2; + } + + return 0; + +err2: + platform_device_put(musb); + +err0: + return ret; +} + +static int omap2430_remove(struct platform_device *pdev) +{ + struct omap2430_glue *glue = platform_get_drvdata(pdev); + + cancel_work_sync(&glue->omap_musb_mailbox_work); + platform_device_unregister(glue->musb); + + return 0; +} + +#ifdef CONFIG_PM + +static int omap2430_runtime_suspend(struct device *dev) +{ + struct omap2430_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + if (musb) { + musb->context.otg_interfsel = musb_readl(musb->mregs, + OTG_INTERFSEL); + + omap2430_low_level_exit(musb); + usb_phy_set_suspend(musb->xceiv, 1); + } + + return 0; +} + +static int omap2430_runtime_resume(struct device *dev) +{ + struct omap2430_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + if (musb) { + omap2430_low_level_init(musb); + musb_writel(musb->mregs, OTG_INTERFSEL, + musb->context.otg_interfsel); + + usb_phy_set_suspend(musb->xceiv, 0); + } + + return 0; +} + +static struct dev_pm_ops omap2430_pm_ops = { + .runtime_suspend = omap2430_runtime_suspend, + .runtime_resume = omap2430_runtime_resume, +}; + +#define DEV_PM_OPS (&omap2430_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id omap2430_id_table[] = { + { + .compatible = "ti,omap4-musb" + }, + { + .compatible = "ti,omap3-musb" + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap2430_id_table); +#endif + +static struct platform_driver omap2430_driver = { + .probe = omap2430_probe, + .remove = omap2430_remove, + .driver = { + .name = "musb-omap2430", + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(omap2430_id_table), + }, +}; + +MODULE_DESCRIPTION("OMAP2PLUS MUSB Glue Layer"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("GPL v2"); + +static int __init omap2430_init(void) +{ + return platform_driver_register(&omap2430_driver); +} +subsys_initcall(omap2430_init); + +static void __exit omap2430_exit(void) +{ + platform_driver_unregister(&omap2430_driver); +} +module_exit(omap2430_exit); diff --git a/drivers/usb/musb/omap2430.h b/drivers/usb/musb/omap2430.h new file mode 100644 index 00000000..1b5e83a9 --- /dev/null +++ b/drivers/usb/musb/omap2430.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2006 by Texas Instruments + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + */ + +#ifndef __MUSB_OMAP243X_H__ +#define __MUSB_OMAP243X_H__ + +#include + +/* + * OMAP2430-specific definitions + */ + +#define OTG_REVISION 0x400 + +#define OTG_SYSCONFIG 0x404 +# define MIDLEMODE 12 /* bit position */ +# define FORCESTDBY (0 << MIDLEMODE) +# define NOSTDBY (1 << MIDLEMODE) +# define SMARTSTDBY (2 << MIDLEMODE) + +# define SIDLEMODE 3 /* bit position */ +# define FORCEIDLE (0 << SIDLEMODE) +# define NOIDLE (1 << SIDLEMODE) +# define SMARTIDLE (2 << SIDLEMODE) + +# define ENABLEWAKEUP (1 << 2) +# define SOFTRST (1 << 1) +# define AUTOIDLE (1 << 0) + +#define OTG_SYSSTATUS 0x408 +# define RESETDONE (1 << 0) + +#define OTG_INTERFSEL 0x40c +# define EXTCP (1 << 2) +# define PHYSEL 0 /* bit position */ +# define UTMI_8BIT (0 << PHYSEL) +# define ULPI_12PIN (1 << PHYSEL) +# define ULPI_8PIN (2 << PHYSEL) + +#define OTG_SIMENABLE 0x410 +# define TM1 (1 << 0) + +#define OTG_FORCESTDBY 0x414 +# define ENABLEFORCE (1 << 0) + +#endif /* __MUSB_OMAP243X_H__ */ diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c new file mode 100644 index 00000000..7369ba33 --- /dev/null +++ b/drivers/usb/musb/tusb6010.c @@ -0,0 +1,1240 @@ +/* + * TUSB6010 USB 2.0 OTG Dual Role controller + * + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Notes: + * - Driver assumes that interface to external host (main CPU) is + * configured for NOR FLASH interface instead of VLYNQ serial + * interface. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" + +struct tusb6010_glue { + struct device *dev; + struct platform_device *musb; +}; + +static void tusb_musb_set_vbus(struct musb *musb, int is_on); + +#define TUSB_REV_MAJOR(reg_val) ((reg_val >> 4) & 0xf) +#define TUSB_REV_MINOR(reg_val) (reg_val & 0xf) + +/* + * Checks the revision. We need to use the DMA register as 3.0 does not + * have correct versions for TUSB_PRCM_REV or TUSB_INT_CTRL_REV. + */ +u8 tusb_get_revision(struct musb *musb) +{ + void __iomem *tbase = musb->ctrl_base; + u32 die_id; + u8 rev; + + rev = musb_readl(tbase, TUSB_DMA_CTRL_REV) & 0xff; + if (TUSB_REV_MAJOR(rev) == 3) { + die_id = TUSB_DIDR1_HI_CHIP_REV(musb_readl(tbase, + TUSB_DIDR1_HI)); + if (die_id >= TUSB_DIDR1_HI_REV_31) + rev |= 1; + } + + return rev; +} +EXPORT_SYMBOL_GPL(tusb_get_revision); + +static int tusb_print_revision(struct musb *musb) +{ + void __iomem *tbase = musb->ctrl_base; + u8 rev; + + rev = tusb_get_revision(musb); + + pr_info("tusb: %s%i.%i %s%i.%i %s%i.%i %s%i.%i %s%i %s%i.%i\n", + "prcm", + TUSB_REV_MAJOR(musb_readl(tbase, TUSB_PRCM_REV)), + TUSB_REV_MINOR(musb_readl(tbase, TUSB_PRCM_REV)), + "int", + TUSB_REV_MAJOR(musb_readl(tbase, TUSB_INT_CTRL_REV)), + TUSB_REV_MINOR(musb_readl(tbase, TUSB_INT_CTRL_REV)), + "gpio", + TUSB_REV_MAJOR(musb_readl(tbase, TUSB_GPIO_REV)), + TUSB_REV_MINOR(musb_readl(tbase, TUSB_GPIO_REV)), + "dma", + TUSB_REV_MAJOR(musb_readl(tbase, TUSB_DMA_CTRL_REV)), + TUSB_REV_MINOR(musb_readl(tbase, TUSB_DMA_CTRL_REV)), + "dieid", + TUSB_DIDR1_HI_CHIP_REV(musb_readl(tbase, TUSB_DIDR1_HI)), + "rev", + TUSB_REV_MAJOR(rev), TUSB_REV_MINOR(rev)); + + return tusb_get_revision(musb); +} + +#define WBUS_QUIRK_MASK (TUSB_PHY_OTG_CTRL_TESTM2 | TUSB_PHY_OTG_CTRL_TESTM1 \ + | TUSB_PHY_OTG_CTRL_TESTM0) + +/* + * Workaround for spontaneous WBUS wake-up issue #2 for tusb3.0. + * Disables power detection in PHY for the duration of idle. + */ +static void tusb_wbus_quirk(struct musb *musb, int enabled) +{ + void __iomem *tbase = musb->ctrl_base; + static u32 phy_otg_ctrl, phy_otg_ena; + u32 tmp; + + if (enabled) { + phy_otg_ctrl = musb_readl(tbase, TUSB_PHY_OTG_CTRL); + phy_otg_ena = musb_readl(tbase, TUSB_PHY_OTG_CTRL_ENABLE); + tmp = TUSB_PHY_OTG_CTRL_WRPROTECT + | phy_otg_ena | WBUS_QUIRK_MASK; + musb_writel(tbase, TUSB_PHY_OTG_CTRL, tmp); + tmp = phy_otg_ena & ~WBUS_QUIRK_MASK; + tmp |= TUSB_PHY_OTG_CTRL_WRPROTECT | TUSB_PHY_OTG_CTRL_TESTM2; + musb_writel(tbase, TUSB_PHY_OTG_CTRL_ENABLE, tmp); + dev_dbg(musb->controller, "Enabled tusb wbus quirk ctrl %08x ena %08x\n", + musb_readl(tbase, TUSB_PHY_OTG_CTRL), + musb_readl(tbase, TUSB_PHY_OTG_CTRL_ENABLE)); + } else if (musb_readl(tbase, TUSB_PHY_OTG_CTRL_ENABLE) + & TUSB_PHY_OTG_CTRL_TESTM2) { + tmp = TUSB_PHY_OTG_CTRL_WRPROTECT | phy_otg_ctrl; + musb_writel(tbase, TUSB_PHY_OTG_CTRL, tmp); + tmp = TUSB_PHY_OTG_CTRL_WRPROTECT | phy_otg_ena; + musb_writel(tbase, TUSB_PHY_OTG_CTRL_ENABLE, tmp); + dev_dbg(musb->controller, "Disabled tusb wbus quirk ctrl %08x ena %08x\n", + musb_readl(tbase, TUSB_PHY_OTG_CTRL), + musb_readl(tbase, TUSB_PHY_OTG_CTRL_ENABLE)); + phy_otg_ctrl = 0; + phy_otg_ena = 0; + } +} + +/* + * TUSB 6010 may use a parallel bus that doesn't support byte ops; + * so both loading and unloading FIFOs need explicit byte counts. + */ + +static inline void +tusb_fifo_write_unaligned(void __iomem *fifo, const u8 *buf, u16 len) +{ + u32 val; + int i; + + if (len > 4) { + for (i = 0; i < (len >> 2); i++) { + memcpy(&val, buf, 4); + musb_writel(fifo, 0, val); + buf += 4; + } + len %= 4; + } + if (len > 0) { + /* Write the rest 1 - 3 bytes to FIFO */ + memcpy(&val, buf, len); + musb_writel(fifo, 0, val); + } +} + +static inline void tusb_fifo_read_unaligned(void __iomem *fifo, + void *buf, u16 len) +{ + u32 val; + int i; + + if (len > 4) { + for (i = 0; i < (len >> 2); i++) { + val = musb_readl(fifo, 0); + memcpy(buf, &val, 4); + buf += 4; + } + len %= 4; + } + if (len > 0) { + /* Read the rest 1 - 3 bytes from FIFO */ + val = musb_readl(fifo, 0); + memcpy(buf, &val, len); + } +} + +void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *buf) +{ + struct musb *musb = hw_ep->musb; + void __iomem *ep_conf = hw_ep->conf; + void __iomem *fifo = hw_ep->fifo; + u8 epnum = hw_ep->epnum; + + prefetch(buf); + + dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n", + 'T', epnum, fifo, len, buf); + + if (epnum) + musb_writel(ep_conf, TUSB_EP_TX_OFFSET, + TUSB_EP_CONFIG_XFR_SIZE(len)); + else + musb_writel(ep_conf, 0, TUSB_EP0_CONFIG_DIR_TX | + TUSB_EP0_CONFIG_XFR_SIZE(len)); + + if (likely((0x01 & (unsigned long) buf) == 0)) { + + /* Best case is 32bit-aligned destination address */ + if ((0x02 & (unsigned long) buf) == 0) { + if (len >= 4) { + iowrite32_rep(fifo, buf, len >> 2); + buf += (len & ~0x03); + len &= 0x03; + } + } else { + if (len >= 2) { + u32 val; + int i; + + /* Cannot use writesw, fifo is 32-bit */ + for (i = 0; i < (len >> 2); i++) { + val = (u32)(*(u16 *)buf); + buf += 2; + val |= (*(u16 *)buf) << 16; + buf += 2; + musb_writel(fifo, 0, val); + } + len &= 0x03; + } + } + } + + if (len > 0) + tusb_fifo_write_unaligned(fifo, buf, len); +} + +void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *buf) +{ + struct musb *musb = hw_ep->musb; + void __iomem *ep_conf = hw_ep->conf; + void __iomem *fifo = hw_ep->fifo; + u8 epnum = hw_ep->epnum; + + dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n", + 'R', epnum, fifo, len, buf); + + if (epnum) + musb_writel(ep_conf, TUSB_EP_RX_OFFSET, + TUSB_EP_CONFIG_XFR_SIZE(len)); + else + musb_writel(ep_conf, 0, TUSB_EP0_CONFIG_XFR_SIZE(len)); + + if (likely((0x01 & (unsigned long) buf) == 0)) { + + /* Best case is 32bit-aligned destination address */ + if ((0x02 & (unsigned long) buf) == 0) { + if (len >= 4) { + ioread32_rep(fifo, buf, len >> 2); + buf += (len & ~0x03); + len &= 0x03; + } + } else { + if (len >= 2) { + u32 val; + int i; + + /* Cannot use readsw, fifo is 32-bit */ + for (i = 0; i < (len >> 2); i++) { + val = musb_readl(fifo, 0); + *(u16 *)buf = (u16)(val & 0xffff); + buf += 2; + *(u16 *)buf = (u16)(val >> 16); + buf += 2; + } + len &= 0x03; + } + } + } + + if (len > 0) + tusb_fifo_read_unaligned(fifo, buf, len); +} + +static struct musb *the_musb; + +/* This is used by gadget drivers, and OTG transceiver logic, allowing + * at most mA current to be drawn from VBUS during a Default-B session + * (that is, while VBUS exceeds 4.4V). In Default-A (including pure host + * mode), or low power Default-B sessions, something else supplies power. + * Caller must take care of locking. + */ +static int tusb_draw_power(struct usb_phy *x, unsigned mA) +{ + struct musb *musb = the_musb; + void __iomem *tbase = musb->ctrl_base; + u32 reg; + + /* tps65030 seems to consume max 100mA, with maybe 60mA available + * (measured on one board) for things other than tps and tusb. + * + * Boards sharing the CPU clock with CLKIN will need to prevent + * certain idle sleep states while the USB link is active. + * + * REVISIT we could use VBUS to supply only _one_ of { 1.5V, 3.3V }. + * The actual current usage would be very board-specific. For now, + * it's simpler to just use an aggregate (also board-specific). + */ + if (x->otg->default_a || mA < (musb->min_power << 1)) + mA = 0; + + reg = musb_readl(tbase, TUSB_PRCM_MNGMT); + if (mA) { + musb->is_bus_powered = 1; + reg |= TUSB_PRCM_MNGMT_15_SW_EN | TUSB_PRCM_MNGMT_33_SW_EN; + } else { + musb->is_bus_powered = 0; + reg &= ~(TUSB_PRCM_MNGMT_15_SW_EN | TUSB_PRCM_MNGMT_33_SW_EN); + } + musb_writel(tbase, TUSB_PRCM_MNGMT, reg); + + dev_dbg(musb->controller, "draw max %d mA VBUS\n", mA); + return 0; +} + +/* workaround for issue 13: change clock during chip idle + * (to be fixed in rev3 silicon) ... symptoms include disconnect + * or looping suspend/resume cycles + */ +static void tusb_set_clock_source(struct musb *musb, unsigned mode) +{ + void __iomem *tbase = musb->ctrl_base; + u32 reg; + + reg = musb_readl(tbase, TUSB_PRCM_CONF); + reg &= ~TUSB_PRCM_CONF_SYS_CLKSEL(0x3); + + /* 0 = refclk (clkin, XI) + * 1 = PHY 60 MHz (internal PLL) + * 2 = not supported + * 3 = what? + */ + if (mode > 0) + reg |= TUSB_PRCM_CONF_SYS_CLKSEL(mode & 0x3); + + musb_writel(tbase, TUSB_PRCM_CONF, reg); + + /* FIXME tusb6010_platform_retime(mode == 0); */ +} + +/* + * Idle TUSB6010 until next wake-up event; NOR access always wakes. + * Other code ensures that we idle unless we're connected _and_ the + * USB link is not suspended ... and tells us the relevant wakeup + * events. SW_EN for voltage is handled separately. + */ +static void tusb_allow_idle(struct musb *musb, u32 wakeup_enables) +{ + void __iomem *tbase = musb->ctrl_base; + u32 reg; + + if ((wakeup_enables & TUSB_PRCM_WBUS) + && (tusb_get_revision(musb) == TUSB_REV_30)) + tusb_wbus_quirk(musb, 1); + + tusb_set_clock_source(musb, 0); + + wakeup_enables |= TUSB_PRCM_WNORCS; + musb_writel(tbase, TUSB_PRCM_WAKEUP_MASK, ~wakeup_enables); + + /* REVISIT writeup of WID implies that if WID set and ID is grounded, + * TUSB_PHY_OTG_CTRL.TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP must be cleared. + * Presumably that's mostly to save power, hence WID is immaterial ... + */ + + reg = musb_readl(tbase, TUSB_PRCM_MNGMT); + /* issue 4: when driving vbus, use hipower (vbus_det) comparator */ + if (is_host_active(musb)) { + reg |= TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN; + reg &= ~TUSB_PRCM_MNGMT_OTG_SESS_END_EN; + } else { + reg |= TUSB_PRCM_MNGMT_OTG_SESS_END_EN; + reg &= ~TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN; + } + reg |= TUSB_PRCM_MNGMT_PM_IDLE | TUSB_PRCM_MNGMT_DEV_IDLE; + musb_writel(tbase, TUSB_PRCM_MNGMT, reg); + + dev_dbg(musb->controller, "idle, wake on %02x\n", wakeup_enables); +} + +/* + * Updates cable VBUS status. Caller must take care of locking. + */ +static int tusb_musb_vbus_status(struct musb *musb) +{ + void __iomem *tbase = musb->ctrl_base; + u32 otg_stat, prcm_mngmt; + int ret = 0; + + otg_stat = musb_readl(tbase, TUSB_DEV_OTG_STAT); + prcm_mngmt = musb_readl(tbase, TUSB_PRCM_MNGMT); + + /* Temporarily enable VBUS detection if it was disabled for + * suspend mode. Unless it's enabled otg_stat and devctl will + * not show correct VBUS state. + */ + if (!(prcm_mngmt & TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN)) { + u32 tmp = prcm_mngmt; + tmp |= TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN; + musb_writel(tbase, TUSB_PRCM_MNGMT, tmp); + otg_stat = musb_readl(tbase, TUSB_DEV_OTG_STAT); + musb_writel(tbase, TUSB_PRCM_MNGMT, prcm_mngmt); + } + + if (otg_stat & TUSB_DEV_OTG_STAT_VBUS_VALID) + ret = 1; + + return ret; +} + +static struct timer_list musb_idle_timer; + +static void musb_do_idle(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_BCON: + if ((musb->a_wait_bcon != 0) + && (musb->idle_timeout == 0 + || time_after(jiffies, musb->idle_timeout))) { + dev_dbg(musb->controller, "Nothing connected %s, turning off VBUS\n", + usb_otg_state_string(musb->xceiv->state)); + } + /* FALLTHROUGH */ + case OTG_STATE_A_IDLE: + tusb_musb_set_vbus(musb, 0); + default: + break; + } + + if (!musb->is_active) { + u32 wakeups; + + /* wait until khubd handles port change status */ + if (is_host_active(musb) && (musb->port1_status >> 16)) + goto done; + + if (!musb->gadget_driver) { + wakeups = 0; + } else { + wakeups = TUSB_PRCM_WHOSTDISCON + | TUSB_PRCM_WBUS + | TUSB_PRCM_WVBUS; + wakeups |= TUSB_PRCM_WID; + } + tusb_allow_idle(musb, wakeups); + } +done: + spin_unlock_irqrestore(&musb->lock, flags); +} + +/* + * Maybe put TUSB6010 into idle mode mode depending on USB link status, + * like "disconnected" or "suspended". We'll be woken out of it by + * connect, resume, or disconnect. + * + * Needs to be called as the last function everywhere where there is + * register access to TUSB6010 because of NOR flash wake-up. + * Caller should own controller spinlock. + * + * Delay because peripheral enables D+ pullup 3msec after SE0, and + * we don't want to treat that full speed J as a wakeup event. + * ... peripherals must draw only suspend current after 10 msec. + */ +static void tusb_musb_try_idle(struct musb *musb, unsigned long timeout) +{ + unsigned long default_timeout = jiffies + msecs_to_jiffies(3); + static unsigned long last_timer; + + if (timeout == 0) + timeout = default_timeout; + + /* Never idle if active, or when VBUS timeout is not set as host */ + if (musb->is_active || ((musb->a_wait_bcon == 0) + && (musb->xceiv->state == OTG_STATE_A_WAIT_BCON))) { + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->state)); + del_timer(&musb_idle_timer); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout)) { + if (!timer_pending(&musb_idle_timer)) + last_timer = timeout; + else { + dev_dbg(musb->controller, "Longer idle timer already pending, ignoring\n"); + return; + } + } + last_timer = timeout; + + dev_dbg(musb->controller, "%s inactive, for idle timer for %lu ms\n", + usb_otg_state_string(musb->xceiv->state), + (unsigned long)jiffies_to_msecs(timeout - jiffies)); + mod_timer(&musb_idle_timer, timeout); +} + +/* ticks of 60 MHz clock */ +#define DEVCLOCK 60000000 +#define OTG_TIMER_MS(msecs) ((msecs) \ + ? (TUSB_DEV_OTG_TIMER_VAL((DEVCLOCK/1000)*(msecs)) \ + | TUSB_DEV_OTG_TIMER_ENABLE) \ + : 0) + +static void tusb_musb_set_vbus(struct musb *musb, int is_on) +{ + void __iomem *tbase = musb->ctrl_base; + u32 conf, prcm, timer; + u8 devctl; + struct usb_otg *otg = musb->xceiv->otg; + + /* HDRC controls CPEN, but beware current surges during device + * connect. They can trigger transient overcurrent conditions + * that must be ignored. + */ + + prcm = musb_readl(tbase, TUSB_PRCM_MNGMT); + conf = musb_readl(tbase, TUSB_DEV_CONF); + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + timer = OTG_TIMER_MS(OTG_TIME_A_WAIT_VRISE); + otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + + conf |= TUSB_DEV_CONF_USB_HOST_MODE; + MUSB_HST_MODE(musb); + } else { + u32 otg_stat; + + timer = 0; + + /* If ID pin is grounded, we want to be a_idle */ + otg_stat = musb_readl(tbase, TUSB_DEV_OTG_STAT); + if (!(otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS)) { + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_VRISE: + case OTG_STATE_A_WAIT_BCON: + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; + break; + case OTG_STATE_A_WAIT_VFALL: + musb->xceiv->state = OTG_STATE_A_IDLE; + break; + default: + musb->xceiv->state = OTG_STATE_A_IDLE; + } + musb->is_active = 0; + otg->default_a = 1; + MUSB_HST_MODE(musb); + } else { + musb->is_active = 0; + otg->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } + + devctl &= ~MUSB_DEVCTL_SESSION; + conf &= ~TUSB_DEV_CONF_USB_HOST_MODE; + } + prcm &= ~(TUSB_PRCM_MNGMT_15_SW_EN | TUSB_PRCM_MNGMT_33_SW_EN); + + musb_writel(tbase, TUSB_PRCM_MNGMT, prcm); + musb_writel(tbase, TUSB_DEV_OTG_TIMER, timer); + musb_writel(tbase, TUSB_DEV_CONF, conf); + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + dev_dbg(musb->controller, "VBUS %s, devctl %02x otg %3x conf %08x prcm %08x\n", + usb_otg_state_string(musb->xceiv->state), + musb_readb(musb->mregs, MUSB_DEVCTL), + musb_readl(tbase, TUSB_DEV_OTG_STAT), + conf, prcm); +} + +/* + * Sets the mode to OTG, peripheral or host by changing the ID detection. + * Caller must take care of locking. + * + * Note that if a mini-A cable is plugged in the ID line will stay down as + * the weak ID pull-up is not able to pull the ID up. + */ +static int tusb_musb_set_mode(struct musb *musb, u8 musb_mode) +{ + void __iomem *tbase = musb->ctrl_base; + u32 otg_stat, phy_otg_ctrl, phy_otg_ena, dev_conf; + + otg_stat = musb_readl(tbase, TUSB_DEV_OTG_STAT); + phy_otg_ctrl = musb_readl(tbase, TUSB_PHY_OTG_CTRL); + phy_otg_ena = musb_readl(tbase, TUSB_PHY_OTG_CTRL_ENABLE); + dev_conf = musb_readl(tbase, TUSB_DEV_CONF); + + switch (musb_mode) { + + case MUSB_HOST: /* Disable PHY ID detect, ground ID */ + phy_otg_ctrl &= ~TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP; + phy_otg_ena |= TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP; + dev_conf |= TUSB_DEV_CONF_ID_SEL; + dev_conf &= ~TUSB_DEV_CONF_SOFT_ID; + break; + case MUSB_PERIPHERAL: /* Disable PHY ID detect, keep ID pull-up on */ + phy_otg_ctrl |= TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP; + phy_otg_ena |= TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP; + dev_conf |= (TUSB_DEV_CONF_ID_SEL | TUSB_DEV_CONF_SOFT_ID); + break; + case MUSB_OTG: /* Use PHY ID detection */ + phy_otg_ctrl |= TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP; + phy_otg_ena |= TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP; + dev_conf &= ~(TUSB_DEV_CONF_ID_SEL | TUSB_DEV_CONF_SOFT_ID); + break; + + default: + dev_dbg(musb->controller, "Trying to set mode %i\n", musb_mode); + return -EINVAL; + } + + musb_writel(tbase, TUSB_PHY_OTG_CTRL, + TUSB_PHY_OTG_CTRL_WRPROTECT | phy_otg_ctrl); + musb_writel(tbase, TUSB_PHY_OTG_CTRL_ENABLE, + TUSB_PHY_OTG_CTRL_WRPROTECT | phy_otg_ena); + musb_writel(tbase, TUSB_DEV_CONF, dev_conf); + + otg_stat = musb_readl(tbase, TUSB_DEV_OTG_STAT); + if ((musb_mode == MUSB_PERIPHERAL) && + !(otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS)) + INFO("Cannot be peripheral with mini-A cable " + "otg_stat: %08x\n", otg_stat); + + return 0; +} + +static inline unsigned long +tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase) +{ + u32 otg_stat = musb_readl(tbase, TUSB_DEV_OTG_STAT); + unsigned long idle_timeout = 0; + struct usb_otg *otg = musb->xceiv->otg; + + /* ID pin */ + if ((int_src & TUSB_INT_SRC_ID_STATUS_CHNG)) { + int default_a; + + default_a = !(otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS); + dev_dbg(musb->controller, "Default-%c\n", default_a ? 'A' : 'B'); + otg->default_a = default_a; + tusb_musb_set_vbus(musb, default_a); + + /* Don't allow idling immediately */ + if (default_a) + idle_timeout = jiffies + (HZ * 3); + } + + /* VBUS state change */ + if (int_src & TUSB_INT_SRC_VBUS_SENSE_CHNG) { + + /* B-dev state machine: no vbus ~= disconnect */ + if (!otg->default_a) { + /* ? musb_root_disconnect(musb); */ + musb->port1_status &= + ~(USB_PORT_STAT_CONNECTION + | USB_PORT_STAT_ENABLE + | USB_PORT_STAT_LOW_SPEED + | USB_PORT_STAT_HIGH_SPEED + | USB_PORT_STAT_TEST + ); + + if (otg_stat & TUSB_DEV_OTG_STAT_SESS_END) { + dev_dbg(musb->controller, "Forcing disconnect (no interrupt)\n"); + if (musb->xceiv->state != OTG_STATE_B_IDLE) { + /* INTR_DISCONNECT can hide... */ + musb->xceiv->state = OTG_STATE_B_IDLE; + musb->int_usb |= MUSB_INTR_DISCONNECT; + } + musb->is_active = 0; + } + dev_dbg(musb->controller, "vbus change, %s, otg %03x\n", + usb_otg_state_string(musb->xceiv->state), otg_stat); + idle_timeout = jiffies + (1 * HZ); + schedule_work(&musb->irq_work); + + } else /* A-dev state machine */ { + dev_dbg(musb->controller, "vbus change, %s, otg %03x\n", + usb_otg_state_string(musb->xceiv->state), otg_stat); + + switch (musb->xceiv->state) { + case OTG_STATE_A_IDLE: + dev_dbg(musb->controller, "Got SRP, turning on VBUS\n"); + musb_platform_set_vbus(musb, 1); + + /* CONNECT can wake if a_wait_bcon is set */ + if (musb->a_wait_bcon != 0) + musb->is_active = 0; + else + musb->is_active = 1; + + /* + * OPT FS A TD.4.6 needs few seconds for + * A_WAIT_VRISE + */ + idle_timeout = jiffies + (2 * HZ); + + break; + case OTG_STATE_A_WAIT_VRISE: + /* ignore; A-session-valid < VBUS_VALID/2, + * we monitor this with the timer + */ + break; + case OTG_STATE_A_WAIT_VFALL: + /* REVISIT this irq triggers during short + * spikes caused by enumeration ... + */ + if (musb->vbuserr_retry) { + musb->vbuserr_retry--; + tusb_musb_set_vbus(musb, 1); + } else { + musb->vbuserr_retry + = VBUSERR_RETRY_COUNT; + tusb_musb_set_vbus(musb, 0); + } + break; + default: + break; + } + } + } + + /* OTG timer expiration */ + if (int_src & TUSB_INT_SRC_OTG_TIMEOUT) { + u8 devctl; + + dev_dbg(musb->controller, "%s timer, %03x\n", + usb_otg_state_string(musb->xceiv->state), otg_stat); + + switch (musb->xceiv->state) { + case OTG_STATE_A_WAIT_VRISE: + /* VBUS has probably been valid for a while now, + * but may well have bounced out of range a bit + */ + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if (otg_stat & TUSB_DEV_OTG_STAT_VBUS_VALID) { + if ((devctl & MUSB_DEVCTL_VBUS) + != MUSB_DEVCTL_VBUS) { + dev_dbg(musb->controller, "devctl %02x\n", devctl); + break; + } + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + musb->is_active = 0; + idle_timeout = jiffies + + msecs_to_jiffies(musb->a_wait_bcon); + } else { + /* REVISIT report overcurrent to hub? */ + ERR("vbus too slow, devctl %02x\n", devctl); + tusb_musb_set_vbus(musb, 0); + } + break; + case OTG_STATE_A_WAIT_BCON: + if (musb->a_wait_bcon != 0) + idle_timeout = jiffies + + msecs_to_jiffies(musb->a_wait_bcon); + break; + case OTG_STATE_A_SUSPEND: + break; + case OTG_STATE_B_WAIT_ACON: + break; + default: + break; + } + } + schedule_work(&musb->irq_work); + + return idle_timeout; +} + +static irqreturn_t tusb_musb_interrupt(int irq, void *__hci) +{ + struct musb *musb = __hci; + void __iomem *tbase = musb->ctrl_base; + unsigned long flags, idle_timeout = 0; + u32 int_mask, int_src; + + spin_lock_irqsave(&musb->lock, flags); + + /* Mask all interrupts to allow using both edge and level GPIO irq */ + int_mask = musb_readl(tbase, TUSB_INT_MASK); + musb_writel(tbase, TUSB_INT_MASK, ~TUSB_INT_MASK_RESERVED_BITS); + + int_src = musb_readl(tbase, TUSB_INT_SRC) & ~TUSB_INT_SRC_RESERVED_BITS; + dev_dbg(musb->controller, "TUSB IRQ %08x\n", int_src); + + musb->int_usb = (u8) int_src; + + /* Acknowledge wake-up source interrupts */ + if (int_src & TUSB_INT_SRC_DEV_WAKEUP) { + u32 reg; + u32 i; + + if (tusb_get_revision(musb) == TUSB_REV_30) + tusb_wbus_quirk(musb, 0); + + /* there are issues re-locking the PLL on wakeup ... */ + + /* work around issue 8 */ + for (i = 0xf7f7f7; i > 0xf7f7f7 - 1000; i--) { + musb_writel(tbase, TUSB_SCRATCH_PAD, 0); + musb_writel(tbase, TUSB_SCRATCH_PAD, i); + reg = musb_readl(tbase, TUSB_SCRATCH_PAD); + if (reg == i) + break; + dev_dbg(musb->controller, "TUSB NOR not ready\n"); + } + + /* work around issue 13 (2nd half) */ + tusb_set_clock_source(musb, 1); + + reg = musb_readl(tbase, TUSB_PRCM_WAKEUP_SOURCE); + musb_writel(tbase, TUSB_PRCM_WAKEUP_CLEAR, reg); + if (reg & ~TUSB_PRCM_WNORCS) { + musb->is_active = 1; + schedule_work(&musb->irq_work); + } + dev_dbg(musb->controller, "wake %sactive %02x\n", + musb->is_active ? "" : "in", reg); + + /* REVISIT host side TUSB_PRCM_WHOSTDISCON, TUSB_PRCM_WBUS */ + } + + if (int_src & TUSB_INT_SRC_USB_IP_CONN) + del_timer(&musb_idle_timer); + + /* OTG state change reports (annoyingly) not issued by Mentor core */ + if (int_src & (TUSB_INT_SRC_VBUS_SENSE_CHNG + | TUSB_INT_SRC_OTG_TIMEOUT + | TUSB_INT_SRC_ID_STATUS_CHNG)) + idle_timeout = tusb_otg_ints(musb, int_src, tbase); + + /* TX dma callback must be handled here, RX dma callback is + * handled in tusb_omap_dma_cb. + */ + if ((int_src & TUSB_INT_SRC_TXRX_DMA_DONE)) { + u32 dma_src = musb_readl(tbase, TUSB_DMA_INT_SRC); + u32 real_dma_src = musb_readl(tbase, TUSB_DMA_INT_MASK); + + dev_dbg(musb->controller, "DMA IRQ %08x\n", dma_src); + real_dma_src = ~real_dma_src & dma_src; + if (tusb_dma_omap() && real_dma_src) { + int tx_source = (real_dma_src & 0xffff); + int i; + + for (i = 1; i <= 15; i++) { + if (tx_source & (1 << i)) { + dev_dbg(musb->controller, "completing ep%i %s\n", i, "tx"); + musb_dma_completion(musb, i, 1); + } + } + } + musb_writel(tbase, TUSB_DMA_INT_CLEAR, dma_src); + } + + /* EP interrupts. In OCP mode tusb6010 mirrors the MUSB interrupts */ + if (int_src & (TUSB_INT_SRC_USB_IP_TX | TUSB_INT_SRC_USB_IP_RX)) { + u32 musb_src = musb_readl(tbase, TUSB_USBIP_INT_SRC); + + musb_writel(tbase, TUSB_USBIP_INT_CLEAR, musb_src); + musb->int_rx = (((musb_src >> 16) & 0xffff) << 1); + musb->int_tx = (musb_src & 0xffff); + } else { + musb->int_rx = 0; + musb->int_tx = 0; + } + + if (int_src & (TUSB_INT_SRC_USB_IP_TX | TUSB_INT_SRC_USB_IP_RX | 0xff)) + musb_interrupt(musb); + + /* Acknowledge TUSB interrupts. Clear only non-reserved bits */ + musb_writel(tbase, TUSB_INT_SRC_CLEAR, + int_src & ~TUSB_INT_MASK_RESERVED_BITS); + + tusb_musb_try_idle(musb, idle_timeout); + + musb_writel(tbase, TUSB_INT_MASK, int_mask); + spin_unlock_irqrestore(&musb->lock, flags); + + return IRQ_HANDLED; +} + +static int dma_off; + +/* + * Enables TUSB6010. Caller must take care of locking. + * REVISIT: + * - Check what is unnecessary in MGC_HdrcStart() + */ +static void tusb_musb_enable(struct musb *musb) +{ + void __iomem *tbase = musb->ctrl_base; + + /* Setup TUSB6010 main interrupt mask. Enable all interrupts except SOF. + * REVISIT: Enable and deal with TUSB_INT_SRC_USB_IP_SOF */ + musb_writel(tbase, TUSB_INT_MASK, TUSB_INT_SRC_USB_IP_SOF); + + /* Setup TUSB interrupt, disable DMA and GPIO interrupts */ + musb_writel(tbase, TUSB_USBIP_INT_MASK, 0); + musb_writel(tbase, TUSB_DMA_INT_MASK, 0x7fffffff); + musb_writel(tbase, TUSB_GPIO_INT_MASK, 0x1ff); + + /* Clear all subsystem interrups */ + musb_writel(tbase, TUSB_USBIP_INT_CLEAR, 0x7fffffff); + musb_writel(tbase, TUSB_DMA_INT_CLEAR, 0x7fffffff); + musb_writel(tbase, TUSB_GPIO_INT_CLEAR, 0x1ff); + + /* Acknowledge pending interrupt(s) */ + musb_writel(tbase, TUSB_INT_SRC_CLEAR, ~TUSB_INT_MASK_RESERVED_BITS); + + /* Only 0 clock cycles for minimum interrupt de-assertion time and + * interrupt polarity active low seems to work reliably here */ + musb_writel(tbase, TUSB_INT_CTRL_CONF, + TUSB_INT_CTRL_CONF_INT_RELCYC(0)); + + irq_set_irq_type(musb->nIrq, IRQ_TYPE_LEVEL_LOW); + + /* maybe force into the Default-A OTG state machine */ + if (!(musb_readl(tbase, TUSB_DEV_OTG_STAT) + & TUSB_DEV_OTG_STAT_ID_STATUS)) + musb_writel(tbase, TUSB_INT_SRC_SET, + TUSB_INT_SRC_ID_STATUS_CHNG); + + if (is_dma_capable() && dma_off) + printk(KERN_WARNING "%s %s: dma not reactivated\n", + __FILE__, __func__); + else + dma_off = 1; +} + +/* + * Disables TUSB6010. Caller must take care of locking. + */ +static void tusb_musb_disable(struct musb *musb) +{ + void __iomem *tbase = musb->ctrl_base; + + /* FIXME stop DMA, IRQs, timers, ... */ + + /* disable all IRQs */ + musb_writel(tbase, TUSB_INT_MASK, ~TUSB_INT_MASK_RESERVED_BITS); + musb_writel(tbase, TUSB_USBIP_INT_MASK, 0x7fffffff); + musb_writel(tbase, TUSB_DMA_INT_MASK, 0x7fffffff); + musb_writel(tbase, TUSB_GPIO_INT_MASK, 0x1ff); + + del_timer(&musb_idle_timer); + + if (is_dma_capable() && !dma_off) { + printk(KERN_WARNING "%s %s: dma still active\n", + __FILE__, __func__); + dma_off = 1; + } +} + +/* + * Sets up TUSB6010 CPU interface specific signals and registers + * Note: Settings optimized for OMAP24xx + */ +static void tusb_setup_cpu_interface(struct musb *musb) +{ + void __iomem *tbase = musb->ctrl_base; + + /* + * Disable GPIO[5:0] pullups (used as output DMA requests) + * Don't disable GPIO[7:6] as they are needed for wake-up. + */ + musb_writel(tbase, TUSB_PULLUP_1_CTRL, 0x0000003F); + + /* Disable all pullups on NOR IF, DMAREQ0 and DMAREQ1 */ + musb_writel(tbase, TUSB_PULLUP_2_CTRL, 0x01FFFFFF); + + /* Turn GPIO[5:0] to DMAREQ[5:0] signals */ + musb_writel(tbase, TUSB_GPIO_CONF, TUSB_GPIO_CONF_DMAREQ(0x3f)); + + /* Burst size 16x16 bits, all six DMA requests enabled, DMA request + * de-assertion time 2 system clocks p 62 */ + musb_writel(tbase, TUSB_DMA_REQ_CONF, + TUSB_DMA_REQ_CONF_BURST_SIZE(2) | + TUSB_DMA_REQ_CONF_DMA_REQ_EN(0x3f) | + TUSB_DMA_REQ_CONF_DMA_REQ_ASSER(2)); + + /* Set 0 wait count for synchronous burst access */ + musb_writel(tbase, TUSB_WAIT_COUNT, 1); +} + +static int tusb_musb_start(struct musb *musb) +{ + void __iomem *tbase = musb->ctrl_base; + int ret = 0; + unsigned long flags; + u32 reg; + + if (musb->board_set_power) + ret = musb->board_set_power(1); + if (ret != 0) { + printk(KERN_ERR "tusb: Cannot enable TUSB6010\n"); + return ret; + } + + spin_lock_irqsave(&musb->lock, flags); + + if (musb_readl(tbase, TUSB_PROD_TEST_RESET) != + TUSB_PROD_TEST_RESET_VAL) { + printk(KERN_ERR "tusb: Unable to detect TUSB6010\n"); + goto err; + } + + ret = tusb_print_revision(musb); + if (ret < 2) { + printk(KERN_ERR "tusb: Unsupported TUSB6010 revision %i\n", + ret); + goto err; + } + + /* The uint bit for "USB non-PDR interrupt enable" has to be 1 when + * NOR FLASH interface is used */ + musb_writel(tbase, TUSB_VLYNQ_CTRL, 8); + + /* Select PHY free running 60MHz as a system clock */ + tusb_set_clock_source(musb, 1); + + /* VBus valid timer 1us, disable DFT/Debug and VLYNQ clocks for + * power saving, enable VBus detect and session end comparators, + * enable IDpullup, enable VBus charging */ + musb_writel(tbase, TUSB_PRCM_MNGMT, + TUSB_PRCM_MNGMT_VBUS_VALID_TIMER(0xa) | + TUSB_PRCM_MNGMT_VBUS_VALID_FLT_EN | + TUSB_PRCM_MNGMT_OTG_SESS_END_EN | + TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN | + TUSB_PRCM_MNGMT_OTG_ID_PULLUP); + tusb_setup_cpu_interface(musb); + + /* simplify: always sense/pullup ID pins, as if in OTG mode */ + reg = musb_readl(tbase, TUSB_PHY_OTG_CTRL_ENABLE); + reg |= TUSB_PHY_OTG_CTRL_WRPROTECT | TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP; + musb_writel(tbase, TUSB_PHY_OTG_CTRL_ENABLE, reg); + + reg = musb_readl(tbase, TUSB_PHY_OTG_CTRL); + reg |= TUSB_PHY_OTG_CTRL_WRPROTECT | TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP; + musb_writel(tbase, TUSB_PHY_OTG_CTRL, reg); + + spin_unlock_irqrestore(&musb->lock, flags); + + return 0; + +err: + spin_unlock_irqrestore(&musb->lock, flags); + + if (musb->board_set_power) + musb->board_set_power(0); + + return -ENODEV; +} + +static int tusb_musb_init(struct musb *musb) +{ + struct platform_device *pdev; + struct resource *mem; + void __iomem *sync = NULL; + int ret; + + usb_nop_xceiv_register(); + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) + return -EPROBE_DEFER; + + pdev = to_platform_device(musb->controller); + + /* dma address for async dma */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + musb->async = mem->start; + + /* dma address for sync dma */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!mem) { + pr_debug("no sync dma resource?\n"); + ret = -ENODEV; + goto done; + } + musb->sync = mem->start; + + sync = ioremap(mem->start, resource_size(mem)); + if (!sync) { + pr_debug("ioremap for sync failed\n"); + ret = -ENOMEM; + goto done; + } + musb->sync_va = sync; + + /* Offsets from base: VLYNQ at 0x000, MUSB regs at 0x400, + * FIFOs at 0x600, TUSB at 0x800 + */ + musb->mregs += TUSB_BASE_OFFSET; + + ret = tusb_musb_start(musb); + if (ret) { + printk(KERN_ERR "Could not start tusb6010 (%d)\n", + ret); + goto done; + } + musb->isr = tusb_musb_interrupt; + + musb->xceiv->set_power = tusb_draw_power; + the_musb = musb; + + setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb); + +done: + if (ret < 0) { + if (sync) + iounmap(sync); + + usb_put_phy(musb->xceiv); + usb_nop_xceiv_unregister(); + } + return ret; +} + +static int tusb_musb_exit(struct musb *musb) +{ + del_timer_sync(&musb_idle_timer); + the_musb = NULL; + + if (musb->board_set_power) + musb->board_set_power(0); + + iounmap(musb->sync_va); + + usb_put_phy(musb->xceiv); + usb_nop_xceiv_unregister(); + return 0; +} + +static const struct musb_platform_ops tusb_ops = { + .init = tusb_musb_init, + .exit = tusb_musb_exit, + + .enable = tusb_musb_enable, + .disable = tusb_musb_disable, + + .set_mode = tusb_musb_set_mode, + .try_idle = tusb_musb_try_idle, + + .vbus_status = tusb_musb_vbus_status, + .set_vbus = tusb_musb_set_vbus, +}; + +static u64 tusb_dmamask = DMA_BIT_MASK(32); + +static int tusb_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct platform_device *musb; + struct tusb6010_glue *glue; + + int ret = -ENOMEM; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(&pdev->dev, "failed to allocate musb device\n"); + goto err1; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &tusb_dmamask; + musb->dev.coherent_dma_mask = tusb_dmamask; + + glue->dev = &pdev->dev; + glue->musb = musb; + + pdata->platform_ops = &tusb_ops; + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(&pdev->dev, "failed to add resources\n"); + goto err3; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err3; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err3; + } + + return 0; + +err3: + platform_device_put(musb); + +err1: + kfree(glue); + +err0: + return ret; +} + +static int tusb_remove(struct platform_device *pdev) +{ + struct tusb6010_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + kfree(glue); + + return 0; +} + +static struct platform_driver tusb_driver = { + .probe = tusb_probe, + .remove = tusb_remove, + .driver = { + .name = "musb-tusb", + }, +}; + +MODULE_DESCRIPTION("TUSB6010 MUSB Glue Layer"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("GPL v2"); +module_platform_driver(tusb_driver); diff --git a/drivers/usb/musb/tusb6010.h b/drivers/usb/musb/tusb6010.h new file mode 100644 index 00000000..35c933a5 --- /dev/null +++ b/drivers/usb/musb/tusb6010.h @@ -0,0 +1,232 @@ +/* + * Definitions for TUSB6010 USB 2.0 OTG Dual Role controller + * + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __TUSB6010_H__ +#define __TUSB6010_H__ + +extern u8 tusb_get_revision(struct musb *musb); + +#ifdef CONFIG_USB_TUSB6010 +#define musb_in_tusb() 1 +#else +#define musb_in_tusb() 0 +#endif + +#ifdef CONFIG_USB_TUSB_OMAP_DMA +#define tusb_dma_omap() 1 +#else +#define tusb_dma_omap() 0 +#endif + +/* VLYNQ control register. 32-bit at offset 0x000 */ +#define TUSB_VLYNQ_CTRL 0x004 + +/* Mentor Graphics OTG core registers. 8,- 16- and 32-bit at offset 0x400 */ +#define TUSB_BASE_OFFSET 0x400 + +/* FIFO registers 32-bit at offset 0x600 */ +#define TUSB_FIFO_BASE 0x600 + +/* Device System & Control registers. 32-bit at offset 0x800 */ +#define TUSB_SYS_REG_BASE 0x800 + +#define TUSB_DEV_CONF (TUSB_SYS_REG_BASE + 0x000) +#define TUSB_DEV_CONF_USB_HOST_MODE (1 << 16) +#define TUSB_DEV_CONF_PROD_TEST_MODE (1 << 15) +#define TUSB_DEV_CONF_SOFT_ID (1 << 1) +#define TUSB_DEV_CONF_ID_SEL (1 << 0) + +#define TUSB_PHY_OTG_CTRL_ENABLE (TUSB_SYS_REG_BASE + 0x004) +#define TUSB_PHY_OTG_CTRL (TUSB_SYS_REG_BASE + 0x008) +#define TUSB_PHY_OTG_CTRL_WRPROTECT (0xa5 << 24) +#define TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP (1 << 23) +#define TUSB_PHY_OTG_CTRL_OTG_VBUS_DET_EN (1 << 19) +#define TUSB_PHY_OTG_CTRL_OTG_SESS_END_EN (1 << 18) +#define TUSB_PHY_OTG_CTRL_TESTM2 (1 << 17) +#define TUSB_PHY_OTG_CTRL_TESTM1 (1 << 16) +#define TUSB_PHY_OTG_CTRL_TESTM0 (1 << 15) +#define TUSB_PHY_OTG_CTRL_TX_DATA2 (1 << 14) +#define TUSB_PHY_OTG_CTRL_TX_GZ2 (1 << 13) +#define TUSB_PHY_OTG_CTRL_TX_ENABLE2 (1 << 12) +#define TUSB_PHY_OTG_CTRL_DM_PULLDOWN (1 << 11) +#define TUSB_PHY_OTG_CTRL_DP_PULLDOWN (1 << 10) +#define TUSB_PHY_OTG_CTRL_OSC_EN (1 << 9) +#define TUSB_PHY_OTG_CTRL_PHYREF_CLKSEL(v) (((v) & 3) << 7) +#define TUSB_PHY_OTG_CTRL_PD (1 << 6) +#define TUSB_PHY_OTG_CTRL_PLL_ON (1 << 5) +#define TUSB_PHY_OTG_CTRL_EXT_RPU (1 << 4) +#define TUSB_PHY_OTG_CTRL_PWR_GOOD (1 << 3) +#define TUSB_PHY_OTG_CTRL_RESET (1 << 2) +#define TUSB_PHY_OTG_CTRL_SUSPENDM (1 << 1) +#define TUSB_PHY_OTG_CTRL_CLK_MODE (1 << 0) + +/*OTG status register */ +#define TUSB_DEV_OTG_STAT (TUSB_SYS_REG_BASE + 0x00c) +#define TUSB_DEV_OTG_STAT_PWR_CLK_GOOD (1 << 8) +#define TUSB_DEV_OTG_STAT_SESS_END (1 << 7) +#define TUSB_DEV_OTG_STAT_SESS_VALID (1 << 6) +#define TUSB_DEV_OTG_STAT_VBUS_VALID (1 << 5) +#define TUSB_DEV_OTG_STAT_VBUS_SENSE (1 << 4) +#define TUSB_DEV_OTG_STAT_ID_STATUS (1 << 3) +#define TUSB_DEV_OTG_STAT_HOST_DISCON (1 << 2) +#define TUSB_DEV_OTG_STAT_LINE_STATE (3 << 0) +#define TUSB_DEV_OTG_STAT_DP_ENABLE (1 << 1) +#define TUSB_DEV_OTG_STAT_DM_ENABLE (1 << 0) + +#define TUSB_DEV_OTG_TIMER (TUSB_SYS_REG_BASE + 0x010) +# define TUSB_DEV_OTG_TIMER_ENABLE (1 << 31) +# define TUSB_DEV_OTG_TIMER_VAL(v) ((v) & 0x07ffffff) +#define TUSB_PRCM_REV (TUSB_SYS_REG_BASE + 0x014) + +/* PRCM configuration register */ +#define TUSB_PRCM_CONF (TUSB_SYS_REG_BASE + 0x018) +#define TUSB_PRCM_CONF_SFW_CPEN (1 << 24) +#define TUSB_PRCM_CONF_SYS_CLKSEL(v) (((v) & 3) << 16) + +/* PRCM management register */ +#define TUSB_PRCM_MNGMT (TUSB_SYS_REG_BASE + 0x01c) +#define TUSB_PRCM_MNGMT_SRP_FIX_TIMER(v) (((v) & 0xf) << 25) +#define TUSB_PRCM_MNGMT_SRP_FIX_EN (1 << 24) +#define TUSB_PRCM_MNGMT_VBUS_VALID_TIMER(v) (((v) & 0xf) << 20) +#define TUSB_PRCM_MNGMT_VBUS_VALID_FLT_EN (1 << 19) +#define TUSB_PRCM_MNGMT_DFT_CLK_DIS (1 << 18) +#define TUSB_PRCM_MNGMT_VLYNQ_CLK_DIS (1 << 17) +#define TUSB_PRCM_MNGMT_OTG_SESS_END_EN (1 << 10) +#define TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN (1 << 9) +#define TUSB_PRCM_MNGMT_OTG_ID_PULLUP (1 << 8) +#define TUSB_PRCM_MNGMT_15_SW_EN (1 << 4) +#define TUSB_PRCM_MNGMT_33_SW_EN (1 << 3) +#define TUSB_PRCM_MNGMT_5V_CPEN (1 << 2) +#define TUSB_PRCM_MNGMT_PM_IDLE (1 << 1) +#define TUSB_PRCM_MNGMT_DEV_IDLE (1 << 0) + +/* Wake-up source clear and mask registers */ +#define TUSB_PRCM_WAKEUP_SOURCE (TUSB_SYS_REG_BASE + 0x020) +#define TUSB_PRCM_WAKEUP_CLEAR (TUSB_SYS_REG_BASE + 0x028) +#define TUSB_PRCM_WAKEUP_MASK (TUSB_SYS_REG_BASE + 0x02c) +#define TUSB_PRCM_WAKEUP_RESERVED_BITS (0xffffe << 13) +#define TUSB_PRCM_WGPIO_7 (1 << 12) +#define TUSB_PRCM_WGPIO_6 (1 << 11) +#define TUSB_PRCM_WGPIO_5 (1 << 10) +#define TUSB_PRCM_WGPIO_4 (1 << 9) +#define TUSB_PRCM_WGPIO_3 (1 << 8) +#define TUSB_PRCM_WGPIO_2 (1 << 7) +#define TUSB_PRCM_WGPIO_1 (1 << 6) +#define TUSB_PRCM_WGPIO_0 (1 << 5) +#define TUSB_PRCM_WHOSTDISCON (1 << 4) /* Host disconnect */ +#define TUSB_PRCM_WBUS (1 << 3) /* USB bus resume */ +#define TUSB_PRCM_WNORCS (1 << 2) /* NOR chip select */ +#define TUSB_PRCM_WVBUS (1 << 1) /* OTG PHY VBUS */ +#define TUSB_PRCM_WID (1 << 0) /* OTG PHY ID detect */ + +#define TUSB_PULLUP_1_CTRL (TUSB_SYS_REG_BASE + 0x030) +#define TUSB_PULLUP_2_CTRL (TUSB_SYS_REG_BASE + 0x034) +#define TUSB_INT_CTRL_REV (TUSB_SYS_REG_BASE + 0x038) +#define TUSB_INT_CTRL_CONF (TUSB_SYS_REG_BASE + 0x03c) +#define TUSB_USBIP_INT_SRC (TUSB_SYS_REG_BASE + 0x040) +#define TUSB_USBIP_INT_SET (TUSB_SYS_REG_BASE + 0x044) +#define TUSB_USBIP_INT_CLEAR (TUSB_SYS_REG_BASE + 0x048) +#define TUSB_USBIP_INT_MASK (TUSB_SYS_REG_BASE + 0x04c) +#define TUSB_DMA_INT_SRC (TUSB_SYS_REG_BASE + 0x050) +#define TUSB_DMA_INT_SET (TUSB_SYS_REG_BASE + 0x054) +#define TUSB_DMA_INT_CLEAR (TUSB_SYS_REG_BASE + 0x058) +#define TUSB_DMA_INT_MASK (TUSB_SYS_REG_BASE + 0x05c) +#define TUSB_GPIO_INT_SRC (TUSB_SYS_REG_BASE + 0x060) +#define TUSB_GPIO_INT_SET (TUSB_SYS_REG_BASE + 0x064) +#define TUSB_GPIO_INT_CLEAR (TUSB_SYS_REG_BASE + 0x068) +#define TUSB_GPIO_INT_MASK (TUSB_SYS_REG_BASE + 0x06c) + +/* NOR flash interrupt source registers */ +#define TUSB_INT_SRC (TUSB_SYS_REG_BASE + 0x070) +#define TUSB_INT_SRC_SET (TUSB_SYS_REG_BASE + 0x074) +#define TUSB_INT_SRC_CLEAR (TUSB_SYS_REG_BASE + 0x078) +#define TUSB_INT_MASK (TUSB_SYS_REG_BASE + 0x07c) +#define TUSB_INT_SRC_TXRX_DMA_DONE (1 << 24) +#define TUSB_INT_SRC_USB_IP_CORE (1 << 17) +#define TUSB_INT_SRC_OTG_TIMEOUT (1 << 16) +#define TUSB_INT_SRC_VBUS_SENSE_CHNG (1 << 15) +#define TUSB_INT_SRC_ID_STATUS_CHNG (1 << 14) +#define TUSB_INT_SRC_DEV_WAKEUP (1 << 13) +#define TUSB_INT_SRC_DEV_READY (1 << 12) +#define TUSB_INT_SRC_USB_IP_TX (1 << 9) +#define TUSB_INT_SRC_USB_IP_RX (1 << 8) +#define TUSB_INT_SRC_USB_IP_VBUS_ERR (1 << 7) +#define TUSB_INT_SRC_USB_IP_VBUS_REQ (1 << 6) +#define TUSB_INT_SRC_USB_IP_DISCON (1 << 5) +#define TUSB_INT_SRC_USB_IP_CONN (1 << 4) +#define TUSB_INT_SRC_USB_IP_SOF (1 << 3) +#define TUSB_INT_SRC_USB_IP_RST_BABBLE (1 << 2) +#define TUSB_INT_SRC_USB_IP_RESUME (1 << 1) +#define TUSB_INT_SRC_USB_IP_SUSPEND (1 << 0) + +/* NOR flash interrupt registers reserved bits. Must be written as 0 */ +#define TUSB_INT_MASK_RESERVED_17 (0x3fff << 17) +#define TUSB_INT_MASK_RESERVED_13 (1 << 13) +#define TUSB_INT_MASK_RESERVED_8 (0xf << 8) +#define TUSB_INT_SRC_RESERVED_26 (0x1f << 26) +#define TUSB_INT_SRC_RESERVED_18 (0x3f << 18) +#define TUSB_INT_SRC_RESERVED_10 (0x03 << 10) + +/* Reserved bits for NOR flash interrupt mask and clear register */ +#define TUSB_INT_MASK_RESERVED_BITS (TUSB_INT_MASK_RESERVED_17 | \ + TUSB_INT_MASK_RESERVED_13 | \ + TUSB_INT_MASK_RESERVED_8) + +/* Reserved bits for NOR flash interrupt status register */ +#define TUSB_INT_SRC_RESERVED_BITS (TUSB_INT_SRC_RESERVED_26 | \ + TUSB_INT_SRC_RESERVED_18 | \ + TUSB_INT_SRC_RESERVED_10) + +#define TUSB_GPIO_REV (TUSB_SYS_REG_BASE + 0x080) +#define TUSB_GPIO_CONF (TUSB_SYS_REG_BASE + 0x084) +#define TUSB_DMA_CTRL_REV (TUSB_SYS_REG_BASE + 0x100) +#define TUSB_DMA_REQ_CONF (TUSB_SYS_REG_BASE + 0x104) +#define TUSB_EP0_CONF (TUSB_SYS_REG_BASE + 0x108) +#define TUSB_DMA_EP_MAP (TUSB_SYS_REG_BASE + 0x148) + +/* Offsets from each ep base register */ +#define TUSB_EP_TX_OFFSET 0x10c /* EP_IN in docs */ +#define TUSB_EP_RX_OFFSET 0x14c /* EP_OUT in docs */ +#define TUSB_EP_MAX_PACKET_SIZE_OFFSET 0x188 + +#define TUSB_WAIT_COUNT (TUSB_SYS_REG_BASE + 0x1c8) +#define TUSB_SCRATCH_PAD (TUSB_SYS_REG_BASE + 0x1c4) +#define TUSB_PROD_TEST_RESET (TUSB_SYS_REG_BASE + 0x1d8) + +/* Device System & Control register bitfields */ +#define TUSB_INT_CTRL_CONF_INT_RELCYC(v) (((v) & 0x7) << 18) +#define TUSB_INT_CTRL_CONF_INT_POLARITY (1 << 17) +#define TUSB_INT_CTRL_CONF_INT_MODE (1 << 16) +#define TUSB_GPIO_CONF_DMAREQ(v) (((v) & 0x3f) << 24) +#define TUSB_DMA_REQ_CONF_BURST_SIZE(v) (((v) & 3) << 26) +#define TUSB_DMA_REQ_CONF_DMA_REQ_EN(v) (((v) & 0x3f) << 20) +#define TUSB_DMA_REQ_CONF_DMA_REQ_ASSER(v) (((v) & 0xf) << 16) +#define TUSB_EP0_CONFIG_SW_EN (1 << 8) +#define TUSB_EP0_CONFIG_DIR_TX (1 << 7) +#define TUSB_EP0_CONFIG_XFR_SIZE(v) ((v) & 0x7f) +#define TUSB_EP_CONFIG_SW_EN (1 << 31) +#define TUSB_EP_CONFIG_XFR_SIZE(v) ((v) & 0x7fffffff) +#define TUSB_PROD_TEST_RESET_VAL 0xa596 +#define TUSB_EP_FIFO(ep) (TUSB_FIFO_BASE + (ep) * 0x20) + +#define TUSB_DIDR1_LO (TUSB_SYS_REG_BASE + 0x1f8) +#define TUSB_DIDR1_HI (TUSB_SYS_REG_BASE + 0x1fc) +#define TUSB_DIDR1_HI_CHIP_REV(v) (((v) >> 17) & 0xf) +#define TUSB_DIDR1_HI_REV_20 0 +#define TUSB_DIDR1_HI_REV_30 1 +#define TUSB_DIDR1_HI_REV_31 2 + +#define TUSB_REV_10 0x10 +#define TUSB_REV_20 0x20 +#define TUSB_REV_30 0x30 +#define TUSB_REV_31 0x31 + +#endif /* __TUSB6010_H__ */ diff --git a/drivers/usb/musb/tusb6010_omap.c b/drivers/usb/musb/tusb6010_omap.c new file mode 100644 index 00000000..98df17c9 --- /dev/null +++ b/drivers/usb/musb/tusb6010_omap.c @@ -0,0 +1,732 @@ +/* + * TUSB6010 USB 2.0 OTG Dual Role controller OMAP DMA interface + * + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" +#include "tusb6010.h" + +#define to_chdat(c) ((struct tusb_omap_dma_ch *)(c)->private_data) + +#define MAX_DMAREQ 5 /* REVISIT: Really 6, but req5 not OK */ + +#define OMAP24XX_DMA_EXT_DMAREQ0 2 +#define OMAP24XX_DMA_EXT_DMAREQ1 3 +#define OMAP242X_DMA_EXT_DMAREQ2 14 +#define OMAP242X_DMA_EXT_DMAREQ3 15 +#define OMAP242X_DMA_EXT_DMAREQ4 16 +#define OMAP242X_DMA_EXT_DMAREQ5 64 + +struct tusb_omap_dma_ch { + struct musb *musb; + void __iomem *tbase; + unsigned long phys_offset; + int epnum; + u8 tx; + struct musb_hw_ep *hw_ep; + + int ch; + s8 dmareq; + s8 sync_dev; + + struct tusb_omap_dma *tusb_dma; + + dma_addr_t dma_addr; + + u32 len; + u16 packet_sz; + u16 transfer_packet_sz; + u32 transfer_len; + u32 completed_len; +}; + +struct tusb_omap_dma { + struct dma_controller controller; + struct musb *musb; + void __iomem *tbase; + + int ch; + s8 dmareq; + s8 sync_dev; + unsigned multichannel:1; +}; + +static int tusb_omap_dma_start(struct dma_controller *c) +{ + struct tusb_omap_dma *tusb_dma; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + + /* dev_dbg(musb->controller, "ep%i ch: %i\n", chdat->epnum, chdat->ch); */ + + return 0; +} + +static int tusb_omap_dma_stop(struct dma_controller *c) +{ + struct tusb_omap_dma *tusb_dma; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + + /* dev_dbg(musb->controller, "ep%i ch: %i\n", chdat->epnum, chdat->ch); */ + + return 0; +} + +/* + * Allocate dmareq0 to the current channel unless it's already taken + */ +static inline int tusb_omap_use_shared_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + + if (reg != 0) { + dev_dbg(chdat->musb->controller, "ep%i dmareq0 is busy for ep%i\n", + chdat->epnum, reg & 0xf); + return -EAGAIN; + } + + if (chdat->tx) + reg = (1 << 4) | chdat->epnum; + else + reg = chdat->epnum; + + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); + + return 0; +} + +static inline void tusb_omap_free_shared_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + + if ((reg & 0xf) != chdat->epnum) { + printk(KERN_ERR "ep%i trying to release dmareq0 for ep%i\n", + chdat->epnum, reg & 0xf); + return; + } + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, 0); +} + +/* + * See also musb_dma_completion in plat_uds.c and musb_g_[tx|rx]() in + * musb_gadget.c. + */ +static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) +{ + struct dma_channel *channel = (struct dma_channel *)data; + struct tusb_omap_dma_ch *chdat = to_chdat(channel); + struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; + struct musb *musb = chdat->musb; + struct device *dev = musb->controller; + struct musb_hw_ep *hw_ep = chdat->hw_ep; + void __iomem *ep_conf = hw_ep->conf; + void __iomem *mbase = musb->mregs; + unsigned long remaining, flags, pio; + int ch; + + spin_lock_irqsave(&musb->lock, flags); + + if (tusb_dma->multichannel) + ch = chdat->ch; + else + ch = tusb_dma->ch; + + if (ch_status != OMAP_DMA_BLOCK_IRQ) + printk(KERN_ERR "TUSB DMA error status: %i\n", ch_status); + + dev_dbg(musb->controller, "ep%i %s dma callback ch: %i status: %x\n", + chdat->epnum, chdat->tx ? "tx" : "rx", + ch, ch_status); + + if (chdat->tx) + remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET); + else + remaining = musb_readl(ep_conf, TUSB_EP_RX_OFFSET); + + remaining = TUSB_EP_CONFIG_XFR_SIZE(remaining); + + /* HW issue #10: XFR_SIZE may get corrupt on DMA (both async & sync) */ + if (unlikely(remaining > chdat->transfer_len)) { + dev_dbg(musb->controller, "Corrupt %s dma ch%i XFR_SIZE: 0x%08lx\n", + chdat->tx ? "tx" : "rx", chdat->ch, + remaining); + remaining = 0; + } + + channel->actual_len = chdat->transfer_len - remaining; + pio = chdat->len - channel->actual_len; + + dev_dbg(musb->controller, "DMA remaining %lu/%u\n", remaining, chdat->transfer_len); + + /* Transfer remaining 1 - 31 bytes */ + if (pio > 0 && pio < 32) { + u8 *buf; + + dev_dbg(musb->controller, "Using PIO for remaining %lu bytes\n", pio); + buf = phys_to_virt((u32)chdat->dma_addr) + chdat->transfer_len; + if (chdat->tx) { + dma_unmap_single(dev, chdat->dma_addr, + chdat->transfer_len, + DMA_TO_DEVICE); + musb_write_fifo(hw_ep, pio, buf); + } else { + dma_unmap_single(dev, chdat->dma_addr, + chdat->transfer_len, + DMA_FROM_DEVICE); + musb_read_fifo(hw_ep, pio, buf); + } + channel->actual_len += pio; + } + + if (!tusb_dma->multichannel) + tusb_omap_free_shared_dmareq(chdat); + + channel->status = MUSB_DMA_STATUS_FREE; + + /* Handle only RX callbacks here. TX callbacks must be handled based + * on the TUSB DMA status interrupt. + * REVISIT: Use both TUSB DMA status interrupt and OMAP DMA callback + * interrupt for RX and TX. + */ + if (!chdat->tx) + musb_dma_completion(musb, chdat->epnum, chdat->tx); + + /* We must terminate short tx transfers manually by setting TXPKTRDY. + * REVISIT: This same problem may occur with other MUSB dma as well. + * Easy to test with g_ether by pinging the MUSB board with ping -s54. + */ + if ((chdat->transfer_len < chdat->packet_sz) + || (chdat->transfer_len % chdat->packet_sz != 0)) { + u16 csr; + + if (chdat->tx) { + dev_dbg(musb->controller, "terminating short tx packet\n"); + musb_ep_select(mbase, chdat->epnum); + csr = musb_readw(hw_ep->regs, MUSB_TXCSR); + csr |= MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY + | MUSB_TXCSR_P_WZC_BITS; + musb_writew(hw_ep->regs, MUSB_TXCSR, csr); + } + } + + spin_unlock_irqrestore(&musb->lock, flags); +} + +static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, + u8 rndis_mode, dma_addr_t dma_addr, u32 len) +{ + struct tusb_omap_dma_ch *chdat = to_chdat(channel); + struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; + struct musb *musb = chdat->musb; + struct device *dev = musb->controller; + struct musb_hw_ep *hw_ep = chdat->hw_ep; + void __iomem *mbase = musb->mregs; + void __iomem *ep_conf = hw_ep->conf; + dma_addr_t fifo = hw_ep->fifo_sync; + struct omap_dma_channel_params dma_params; + u32 dma_remaining; + int src_burst, dst_burst; + u16 csr; + int ch; + s8 dmareq; + s8 sync_dev; + + if (unlikely(dma_addr & 0x1) || (len < 32) || (len > packet_sz)) + return false; + + /* + * HW issue #10: Async dma will eventually corrupt the XFR_SIZE + * register which will cause missed DMA interrupt. We could try to + * use a timer for the callback, but it is unsafe as the XFR_SIZE + * register is corrupt, and we won't know if the DMA worked. + */ + if (dma_addr & 0x2) + return false; + + /* + * Because of HW issue #10, it seems like mixing sync DMA and async + * PIO access can confuse the DMA. Make sure XFR_SIZE is reset before + * using the channel for DMA. + */ + if (chdat->tx) + dma_remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET); + else + dma_remaining = musb_readl(ep_conf, TUSB_EP_RX_OFFSET); + + dma_remaining = TUSB_EP_CONFIG_XFR_SIZE(dma_remaining); + if (dma_remaining) { + dev_dbg(musb->controller, "Busy %s dma ch%i, not using: %08x\n", + chdat->tx ? "tx" : "rx", chdat->ch, + dma_remaining); + return false; + } + + chdat->transfer_len = len & ~0x1f; + + if (len < packet_sz) + chdat->transfer_packet_sz = chdat->transfer_len; + else + chdat->transfer_packet_sz = packet_sz; + + if (tusb_dma->multichannel) { + ch = chdat->ch; + dmareq = chdat->dmareq; + sync_dev = chdat->sync_dev; + } else { + if (tusb_omap_use_shared_dmareq(chdat) != 0) { + dev_dbg(musb->controller, "could not get dma for ep%i\n", chdat->epnum); + return false; + } + if (tusb_dma->ch < 0) { + /* REVISIT: This should get blocked earlier, happens + * with MSC ErrorRecoveryTest + */ + WARN_ON(1); + return false; + } + + ch = tusb_dma->ch; + dmareq = tusb_dma->dmareq; + sync_dev = tusb_dma->sync_dev; + omap_set_dma_callback(ch, tusb_omap_dma_cb, channel); + } + + chdat->packet_sz = packet_sz; + chdat->len = len; + channel->actual_len = 0; + chdat->dma_addr = dma_addr; + channel->status = MUSB_DMA_STATUS_BUSY; + + /* Since we're recycling dma areas, we need to clean or invalidate */ + if (chdat->tx) + dma_map_single(dev, phys_to_virt(dma_addr), len, + DMA_TO_DEVICE); + else + dma_map_single(dev, phys_to_virt(dma_addr), len, + DMA_FROM_DEVICE); + + /* Use 16-bit transfer if dma_addr is not 32-bit aligned */ + if ((dma_addr & 0x3) == 0) { + dma_params.data_type = OMAP_DMA_DATA_TYPE_S32; + dma_params.elem_count = 8; /* Elements in frame */ + } else { + dma_params.data_type = OMAP_DMA_DATA_TYPE_S16; + dma_params.elem_count = 16; /* Elements in frame */ + fifo = hw_ep->fifo_async; + } + + dma_params.frame_count = chdat->transfer_len / 32; /* Burst sz frame */ + + dev_dbg(musb->controller, "ep%i %s dma ch%i dma: %08x len: %u(%u) packet_sz: %i(%i)\n", + chdat->epnum, chdat->tx ? "tx" : "rx", + ch, dma_addr, chdat->transfer_len, len, + chdat->transfer_packet_sz, packet_sz); + + /* + * Prepare omap DMA for transfer + */ + if (chdat->tx) { + dma_params.src_amode = OMAP_DMA_AMODE_POST_INC; + dma_params.src_start = (unsigned long)dma_addr; + dma_params.src_ei = 0; + dma_params.src_fi = 0; + + dma_params.dst_amode = OMAP_DMA_AMODE_DOUBLE_IDX; + dma_params.dst_start = (unsigned long)fifo; + dma_params.dst_ei = 1; + dma_params.dst_fi = -31; /* Loop 32 byte window */ + + dma_params.trigger = sync_dev; + dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; + dma_params.src_or_dst_synch = 0; /* Dest sync */ + + src_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 read */ + dst_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 write */ + } else { + dma_params.src_amode = OMAP_DMA_AMODE_DOUBLE_IDX; + dma_params.src_start = (unsigned long)fifo; + dma_params.src_ei = 1; + dma_params.src_fi = -31; /* Loop 32 byte window */ + + dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC; + dma_params.dst_start = (unsigned long)dma_addr; + dma_params.dst_ei = 0; + dma_params.dst_fi = 0; + + dma_params.trigger = sync_dev; + dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; + dma_params.src_or_dst_synch = 1; /* Source sync */ + + src_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 read */ + dst_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 write */ + } + + dev_dbg(musb->controller, "ep%i %s using %i-bit %s dma from 0x%08lx to 0x%08lx\n", + chdat->epnum, chdat->tx ? "tx" : "rx", + (dma_params.data_type == OMAP_DMA_DATA_TYPE_S32) ? 32 : 16, + ((dma_addr & 0x3) == 0) ? "sync" : "async", + dma_params.src_start, dma_params.dst_start); + + omap_set_dma_params(ch, &dma_params); + omap_set_dma_src_burst_mode(ch, src_burst); + omap_set_dma_dest_burst_mode(ch, dst_burst); + omap_set_dma_write_mode(ch, OMAP_DMA_WRITE_LAST_NON_POSTED); + + /* + * Prepare MUSB for DMA transfer + */ + if (chdat->tx) { + musb_ep_select(mbase, chdat->epnum); + csr = musb_readw(hw_ep->regs, MUSB_TXCSR); + csr |= (MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_DMAMODE | MUSB_TXCSR_MODE); + csr &= ~MUSB_TXCSR_P_UNDERRUN; + musb_writew(hw_ep->regs, MUSB_TXCSR, csr); + } else { + musb_ep_select(mbase, chdat->epnum); + csr = musb_readw(hw_ep->regs, MUSB_RXCSR); + csr |= MUSB_RXCSR_DMAENAB; + csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAMODE); + musb_writew(hw_ep->regs, MUSB_RXCSR, + csr | MUSB_RXCSR_P_WZC_BITS); + } + + /* + * Start DMA transfer + */ + omap_start_dma(ch); + + if (chdat->tx) { + /* Send transfer_packet_sz packets at a time */ + musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET, + chdat->transfer_packet_sz); + + musb_writel(ep_conf, TUSB_EP_TX_OFFSET, + TUSB_EP_CONFIG_XFR_SIZE(chdat->transfer_len)); + } else { + /* Receive transfer_packet_sz packets at a time */ + musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET, + chdat->transfer_packet_sz << 16); + + musb_writel(ep_conf, TUSB_EP_RX_OFFSET, + TUSB_EP_CONFIG_XFR_SIZE(chdat->transfer_len)); + } + + return true; +} + +static int tusb_omap_dma_abort(struct dma_channel *channel) +{ + struct tusb_omap_dma_ch *chdat = to_chdat(channel); + struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; + + if (!tusb_dma->multichannel) { + if (tusb_dma->ch >= 0) { + omap_stop_dma(tusb_dma->ch); + omap_free_dma(tusb_dma->ch); + tusb_dma->ch = -1; + } + + tusb_dma->dmareq = -1; + tusb_dma->sync_dev = -1; + } + + channel->status = MUSB_DMA_STATUS_FREE; + + return 0; +} + +static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + int i, dmareq_nr = -1; + + const int sync_dev[6] = { + OMAP24XX_DMA_EXT_DMAREQ0, + OMAP24XX_DMA_EXT_DMAREQ1, + OMAP242X_DMA_EXT_DMAREQ2, + OMAP242X_DMA_EXT_DMAREQ3, + OMAP242X_DMA_EXT_DMAREQ4, + OMAP242X_DMA_EXT_DMAREQ5, + }; + + for (i = 0; i < MAX_DMAREQ; i++) { + int cur = (reg & (0xf << (i * 5))) >> (i * 5); + if (cur == 0) { + dmareq_nr = i; + break; + } + } + + if (dmareq_nr == -1) + return -EAGAIN; + + reg |= (chdat->epnum << (dmareq_nr * 5)); + if (chdat->tx) + reg |= ((1 << 4) << (dmareq_nr * 5)); + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); + + chdat->dmareq = dmareq_nr; + chdat->sync_dev = sync_dev[chdat->dmareq]; + + return 0; +} + +static inline void tusb_omap_dma_free_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg; + + if (!chdat || chdat->dmareq < 0) + return; + + reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + reg &= ~(0x1f << (chdat->dmareq * 5)); + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); + + chdat->dmareq = -1; + chdat->sync_dev = -1; +} + +static struct dma_channel *dma_channel_pool[MAX_DMAREQ]; + +static struct dma_channel * +tusb_omap_dma_allocate(struct dma_controller *c, + struct musb_hw_ep *hw_ep, + u8 tx) +{ + int ret, i; + const char *dev_name; + struct tusb_omap_dma *tusb_dma; + struct musb *musb; + void __iomem *tbase; + struct dma_channel *channel = NULL; + struct tusb_omap_dma_ch *chdat = NULL; + u32 reg; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + musb = tusb_dma->musb; + tbase = musb->ctrl_base; + + reg = musb_readl(tbase, TUSB_DMA_INT_MASK); + if (tx) + reg &= ~(1 << hw_ep->epnum); + else + reg &= ~(1 << (hw_ep->epnum + 15)); + musb_writel(tbase, TUSB_DMA_INT_MASK, reg); + + /* REVISIT: Why does dmareq5 not work? */ + if (hw_ep->epnum == 0) { + dev_dbg(musb->controller, "Not allowing DMA for ep0 %s\n", tx ? "tx" : "rx"); + return NULL; + } + + for (i = 0; i < MAX_DMAREQ; i++) { + struct dma_channel *ch = dma_channel_pool[i]; + if (ch->status == MUSB_DMA_STATUS_UNKNOWN) { + ch->status = MUSB_DMA_STATUS_FREE; + channel = ch; + chdat = ch->private_data; + break; + } + } + + if (!channel) + return NULL; + + if (tx) { + chdat->tx = 1; + dev_name = "TUSB transmit"; + } else { + chdat->tx = 0; + dev_name = "TUSB receive"; + } + + chdat->musb = tusb_dma->musb; + chdat->tbase = tusb_dma->tbase; + chdat->hw_ep = hw_ep; + chdat->epnum = hw_ep->epnum; + chdat->dmareq = -1; + chdat->completed_len = 0; + chdat->tusb_dma = tusb_dma; + + channel->max_len = 0x7fffffff; + channel->desired_mode = 0; + channel->actual_len = 0; + + if (tusb_dma->multichannel) { + ret = tusb_omap_dma_allocate_dmareq(chdat); + if (ret != 0) + goto free_dmareq; + + ret = omap_request_dma(chdat->sync_dev, dev_name, + tusb_omap_dma_cb, channel, &chdat->ch); + if (ret != 0) + goto free_dmareq; + } else if (tusb_dma->ch == -1) { + tusb_dma->dmareq = 0; + tusb_dma->sync_dev = OMAP24XX_DMA_EXT_DMAREQ0; + + /* Callback data gets set later in the shared dmareq case */ + ret = omap_request_dma(tusb_dma->sync_dev, "TUSB shared", + tusb_omap_dma_cb, NULL, &tusb_dma->ch); + if (ret != 0) + goto free_dmareq; + + chdat->dmareq = -1; + chdat->ch = -1; + } + + dev_dbg(musb->controller, "ep%i %s dma: %s dma%i dmareq%i sync%i\n", + chdat->epnum, + chdat->tx ? "tx" : "rx", + chdat->ch >= 0 ? "dedicated" : "shared", + chdat->ch >= 0 ? chdat->ch : tusb_dma->ch, + chdat->dmareq >= 0 ? chdat->dmareq : tusb_dma->dmareq, + chdat->sync_dev >= 0 ? chdat->sync_dev : tusb_dma->sync_dev); + + return channel; + +free_dmareq: + tusb_omap_dma_free_dmareq(chdat); + + dev_dbg(musb->controller, "ep%i: Could not get a DMA channel\n", chdat->epnum); + channel->status = MUSB_DMA_STATUS_UNKNOWN; + + return NULL; +} + +static void tusb_omap_dma_release(struct dma_channel *channel) +{ + struct tusb_omap_dma_ch *chdat = to_chdat(channel); + struct musb *musb = chdat->musb; + void __iomem *tbase = musb->ctrl_base; + u32 reg; + + dev_dbg(musb->controller, "ep%i ch%i\n", chdat->epnum, chdat->ch); + + reg = musb_readl(tbase, TUSB_DMA_INT_MASK); + if (chdat->tx) + reg |= (1 << chdat->epnum); + else + reg |= (1 << (chdat->epnum + 15)); + musb_writel(tbase, TUSB_DMA_INT_MASK, reg); + + reg = musb_readl(tbase, TUSB_DMA_INT_CLEAR); + if (chdat->tx) + reg |= (1 << chdat->epnum); + else + reg |= (1 << (chdat->epnum + 15)); + musb_writel(tbase, TUSB_DMA_INT_CLEAR, reg); + + channel->status = MUSB_DMA_STATUS_UNKNOWN; + + if (chdat->ch >= 0) { + omap_stop_dma(chdat->ch); + omap_free_dma(chdat->ch); + chdat->ch = -1; + } + + if (chdat->dmareq >= 0) + tusb_omap_dma_free_dmareq(chdat); + + channel = NULL; +} + +void dma_controller_destroy(struct dma_controller *c) +{ + struct tusb_omap_dma *tusb_dma; + int i; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + for (i = 0; i < MAX_DMAREQ; i++) { + struct dma_channel *ch = dma_channel_pool[i]; + if (ch) { + kfree(ch->private_data); + kfree(ch); + } + } + + if (tusb_dma && !tusb_dma->multichannel && tusb_dma->ch >= 0) + omap_free_dma(tusb_dma->ch); + + kfree(tusb_dma); +} + +struct dma_controller *dma_controller_create(struct musb *musb, void __iomem *base) +{ + void __iomem *tbase = musb->ctrl_base; + struct tusb_omap_dma *tusb_dma; + int i; + + /* REVISIT: Get dmareq lines used from board-*.c */ + + musb_writel(musb->ctrl_base, TUSB_DMA_INT_MASK, 0x7fffffff); + musb_writel(musb->ctrl_base, TUSB_DMA_EP_MAP, 0); + + musb_writel(tbase, TUSB_DMA_REQ_CONF, + TUSB_DMA_REQ_CONF_BURST_SIZE(2) + | TUSB_DMA_REQ_CONF_DMA_REQ_EN(0x3f) + | TUSB_DMA_REQ_CONF_DMA_REQ_ASSER(2)); + + tusb_dma = kzalloc(sizeof(struct tusb_omap_dma), GFP_KERNEL); + if (!tusb_dma) + goto out; + + tusb_dma->musb = musb; + tusb_dma->tbase = musb->ctrl_base; + + tusb_dma->ch = -1; + tusb_dma->dmareq = -1; + tusb_dma->sync_dev = -1; + + tusb_dma->controller.start = tusb_omap_dma_start; + tusb_dma->controller.stop = tusb_omap_dma_stop; + tusb_dma->controller.channel_alloc = tusb_omap_dma_allocate; + tusb_dma->controller.channel_release = tusb_omap_dma_release; + tusb_dma->controller.channel_program = tusb_omap_dma_program; + tusb_dma->controller.channel_abort = tusb_omap_dma_abort; + + if (tusb_get_revision(musb) >= TUSB_REV_30) + tusb_dma->multichannel = 1; + + for (i = 0; i < MAX_DMAREQ; i++) { + struct dma_channel *ch; + struct tusb_omap_dma_ch *chdat; + + ch = kzalloc(sizeof(struct dma_channel), GFP_KERNEL); + if (!ch) + goto cleanup; + + dma_channel_pool[i] = ch; + + chdat = kzalloc(sizeof(struct tusb_omap_dma_ch), GFP_KERNEL); + if (!chdat) + goto cleanup; + + ch->status = MUSB_DMA_STATUS_UNKNOWN; + ch->private_data = chdat; + } + + return &tusb_dma->controller; + +cleanup: + dma_controller_destroy(&tusb_dma->controller); +out: + return NULL; +} diff --git a/drivers/usb/musb/ux500.c b/drivers/usb/musb/ux500.c new file mode 100644 index 00000000..2c80004e --- /dev/null +++ b/drivers/usb/musb/ux500.c @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2010 ST-Ericsson AB + * Mian Yousaf Kaukab + * + * Based on omap2430.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" + +struct ux500_glue { + struct device *dev; + struct platform_device *musb; + struct clk *clk; +}; +#define glue_to_musb(g) platform_get_drvdata(g->musb) + +static void ux500_musb_set_vbus(struct musb *musb, int is_on) +{ + u8 devctl; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + /* HDRC controls CPEN, but beware current surges during device + * connect. They can trigger transient overcurrent conditions + * that must be ignored. + */ + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + if (musb->xceiv->state == OTG_STATE_A_IDLE) { + /* start the session */ + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + /* + * Wait for the musb to set as A device to enable the + * VBUS + */ + while (musb_readb(musb->mregs, MUSB_DEVCTL) & 0x80) { + + if (time_after(jiffies, timeout)) { + dev_err(musb->controller, + "configured as A device timeout"); + break; + } + } + + } else { + musb->is_active = 1; + musb->xceiv->otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + MUSB_HST_MODE(musb); + } + } else { + musb->is_active = 0; + + /* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and jumping + * right to B_IDLE... + */ + musb->xceiv->otg->default_a = 0; + devctl &= ~MUSB_DEVCTL_SESSION; + MUSB_DEV_MODE(musb); + } + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + /* + * Devctl values will be updated after vbus goes below + * session_valid. The time taken depends on the capacitance + * on VBUS line. The max discharge time can be upto 1 sec + * as per the spec. Typically on our platform, it is 200ms + */ + if (!is_on) + mdelay(200); + + dev_dbg(musb->controller, "VBUS %s, devctl %02x\n", + usb_otg_state_string(musb->xceiv->state), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} + +static int musb_otg_notifications(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct musb *musb = container_of(nb, struct musb, nb); + + dev_dbg(musb->controller, "musb_otg_notifications %ld %s\n", + event, usb_otg_state_string(musb->xceiv->state)); + + switch (event) { + case UX500_MUSB_ID: + dev_dbg(musb->controller, "ID GND\n"); + ux500_musb_set_vbus(musb, 1); + break; + case UX500_MUSB_VBUS: + dev_dbg(musb->controller, "VBUS Connect\n"); + break; + case UX500_MUSB_NONE: + dev_dbg(musb->controller, "VBUS Disconnect\n"); + if (is_host_active(musb)) + ux500_musb_set_vbus(musb, 0); + else + musb->xceiv->state = OTG_STATE_B_IDLE; + break; + default: + dev_dbg(musb->controller, "ID float\n"); + return NOTIFY_DONE; + } + return NOTIFY_OK; +} + +static irqreturn_t ux500_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + struct musb *musb = __hci; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb || musb->int_tx || musb->int_rx) + retval = musb_interrupt(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + + return retval; +} + +static int ux500_musb_init(struct musb *musb) +{ + int status; + + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) { + pr_err("HS USB OTG: no transceiver configured\n"); + return -EPROBE_DEFER; + } + + musb->nb.notifier_call = musb_otg_notifications; + status = usb_register_notifier(musb->xceiv, &musb->nb); + if (status < 0) { + dev_dbg(musb->controller, "notification register failed\n"); + return status; + } + + musb->isr = ux500_musb_interrupt; + + return 0; +} + +static int ux500_musb_exit(struct musb *musb) +{ + usb_unregister_notifier(musb->xceiv, &musb->nb); + + usb_put_phy(musb->xceiv); + + return 0; +} + +static const struct musb_platform_ops ux500_ops = { + .init = ux500_musb_init, + .exit = ux500_musb_exit, + + .set_vbus = ux500_musb_set_vbus, +}; + +static int ux500_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct platform_device *musb; + struct ux500_glue *glue; + struct clk *clk; + int ret = -ENOMEM; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(&pdev->dev, "failed to allocate musb device\n"); + goto err1; + } + + clk = clk_get(&pdev->dev, "usb"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err3; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err4; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = pdev->dev.dma_mask; + musb->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; + + glue->dev = &pdev->dev; + glue->musb = musb; + glue->clk = clk; + + pdata->platform_ops = &ux500_ops; + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(&pdev->dev, "failed to add resources\n"); + goto err5; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err5; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err5; + } + + return 0; + +err5: + clk_disable_unprepare(clk); + +err4: + clk_put(clk); + +err3: + platform_device_put(musb); + +err1: + kfree(glue); + +err0: + return ret; +} + +static int ux500_remove(struct platform_device *pdev) +{ + struct ux500_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + clk_disable_unprepare(glue->clk); + clk_put(glue->clk); + kfree(glue); + + return 0; +} + +#ifdef CONFIG_PM +static int ux500_suspend(struct device *dev) +{ + struct ux500_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + usb_phy_set_suspend(musb->xceiv, 1); + clk_disable_unprepare(glue->clk); + + return 0; +} + +static int ux500_resume(struct device *dev) +{ + struct ux500_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + int ret; + + ret = clk_prepare_enable(glue->clk); + if (ret) { + dev_err(dev, "failed to enable clock\n"); + return ret; + } + + usb_phy_set_suspend(musb->xceiv, 0); + + return 0; +} + +static const struct dev_pm_ops ux500_pm_ops = { + .suspend = ux500_suspend, + .resume = ux500_resume, +}; + +#define DEV_PM_OPS (&ux500_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +static struct platform_driver ux500_driver = { + .probe = ux500_probe, + .remove = ux500_remove, + .driver = { + .name = "musb-ux500", + .pm = DEV_PM_OPS, + }, +}; + +MODULE_DESCRIPTION("UX500 MUSB Glue Layer"); +MODULE_AUTHOR("Mian Yousaf Kaukab "); +MODULE_LICENSE("GPL v2"); +module_platform_driver(ux500_driver); diff --git a/drivers/usb/musb/ux500_dma.c b/drivers/usb/musb/ux500_dma.c new file mode 100644 index 00000000..33812064 --- /dev/null +++ b/drivers/usb/musb/ux500_dma.c @@ -0,0 +1,404 @@ +/* + * drivers/usb/musb/ux500_dma.c + * + * U8500 DMA support code + * + * Copyright (C) 2009 STMicroelectronics + * Copyright (C) 2011 ST-Ericsson SA + * Authors: + * Mian Yousaf Kaukab + * Praveena Nadahally + * Rajaram Regupathy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "musb_core.h" + +struct ux500_dma_channel { + struct dma_channel channel; + struct ux500_dma_controller *controller; + struct musb_hw_ep *hw_ep; + struct dma_chan *dma_chan; + unsigned int cur_len; + dma_cookie_t cookie; + u8 ch_num; + u8 is_tx; + u8 is_allocated; +}; + +struct ux500_dma_controller { + struct dma_controller controller; + struct ux500_dma_channel rx_channel[UX500_MUSB_DMA_NUM_RX_CHANNELS]; + struct ux500_dma_channel tx_channel[UX500_MUSB_DMA_NUM_TX_CHANNELS]; + u32 num_rx_channels; + u32 num_tx_channels; + void *private_data; + dma_addr_t phy_base; +}; + +/* Work function invoked from DMA callback to handle rx transfers. */ +static void ux500_dma_callback(void *private_data) +{ + struct dma_channel *channel = private_data; + struct ux500_dma_channel *ux500_channel = channel->private_data; + struct musb_hw_ep *hw_ep = ux500_channel->hw_ep; + struct musb *musb = hw_ep->musb; + unsigned long flags; + + dev_dbg(musb->controller, "DMA rx transfer done on hw_ep=%d\n", + hw_ep->epnum); + + spin_lock_irqsave(&musb->lock, flags); + ux500_channel->channel.actual_len = ux500_channel->cur_len; + ux500_channel->channel.status = MUSB_DMA_STATUS_FREE; + musb_dma_completion(musb, hw_ep->epnum, + ux500_channel->is_tx); + spin_unlock_irqrestore(&musb->lock, flags); + +} + +static bool ux500_configure_channel(struct dma_channel *channel, + u16 packet_sz, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + struct ux500_dma_channel *ux500_channel = channel->private_data; + struct musb_hw_ep *hw_ep = ux500_channel->hw_ep; + struct dma_chan *dma_chan = ux500_channel->dma_chan; + struct dma_async_tx_descriptor *dma_desc; + enum dma_transfer_direction direction; + struct scatterlist sg; + struct dma_slave_config slave_conf; + enum dma_slave_buswidth addr_width; + dma_addr_t usb_fifo_addr = (MUSB_FIFO_OFFSET(hw_ep->epnum) + + ux500_channel->controller->phy_base); + struct musb *musb = ux500_channel->controller->private_data; + + dev_dbg(musb->controller, + "packet_sz=%d, mode=%d, dma_addr=0x%llu, len=%d is_tx=%d\n", + packet_sz, mode, (unsigned long long) dma_addr, + len, ux500_channel->is_tx); + + ux500_channel->cur_len = len; + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(dma_addr)), len, + offset_in_page(dma_addr)); + sg_dma_address(&sg) = dma_addr; + sg_dma_len(&sg) = len; + + direction = ux500_channel->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + addr_width = (len & 0x3) ? DMA_SLAVE_BUSWIDTH_1_BYTE : + DMA_SLAVE_BUSWIDTH_4_BYTES; + + slave_conf.direction = direction; + slave_conf.src_addr = usb_fifo_addr; + slave_conf.src_addr_width = addr_width; + slave_conf.src_maxburst = 16; + slave_conf.dst_addr = usb_fifo_addr; + slave_conf.dst_addr_width = addr_width; + slave_conf.dst_maxburst = 16; + slave_conf.device_fc = false; + + dma_chan->device->device_control(dma_chan, DMA_SLAVE_CONFIG, + (unsigned long) &slave_conf); + + dma_desc = dmaengine_prep_slave_sg(dma_chan, &sg, 1, direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!dma_desc) + return false; + + dma_desc->callback = ux500_dma_callback; + dma_desc->callback_param = channel; + ux500_channel->cookie = dma_desc->tx_submit(dma_desc); + + dma_async_issue_pending(dma_chan); + + return true; +} + +static struct dma_channel *ux500_dma_channel_allocate(struct dma_controller *c, + struct musb_hw_ep *hw_ep, u8 is_tx) +{ + struct ux500_dma_controller *controller = container_of(c, + struct ux500_dma_controller, controller); + struct ux500_dma_channel *ux500_channel = NULL; + struct musb *musb = controller->private_data; + u8 ch_num = hw_ep->epnum - 1; + u32 max_ch; + + /* Max 8 DMA channels (0 - 7). Each DMA channel can only be allocated + * to specified hw_ep. For example DMA channel 0 can only be allocated + * to hw_ep 1 and 9. + */ + if (ch_num > 7) + ch_num -= 8; + + max_ch = is_tx ? controller->num_tx_channels : + controller->num_rx_channels; + + if (ch_num >= max_ch) + return NULL; + + ux500_channel = is_tx ? &(controller->tx_channel[ch_num]) : + &(controller->rx_channel[ch_num]) ; + + /* Check if channel is already used. */ + if (ux500_channel->is_allocated) + return NULL; + + ux500_channel->hw_ep = hw_ep; + ux500_channel->is_allocated = 1; + + dev_dbg(musb->controller, "hw_ep=%d, is_tx=0x%x, channel=%d\n", + hw_ep->epnum, is_tx, ch_num); + + return &(ux500_channel->channel); +} + +static void ux500_dma_channel_release(struct dma_channel *channel) +{ + struct ux500_dma_channel *ux500_channel = channel->private_data; + struct musb *musb = ux500_channel->controller->private_data; + + dev_dbg(musb->controller, "channel=%d\n", ux500_channel->ch_num); + + if (ux500_channel->is_allocated) { + ux500_channel->is_allocated = 0; + channel->status = MUSB_DMA_STATUS_FREE; + channel->actual_len = 0; + } +} + +static int ux500_dma_is_compatible(struct dma_channel *channel, + u16 maxpacket, void *buf, u32 length) +{ + if ((maxpacket & 0x3) || + ((unsigned long int) buf & 0x3) || + (length < 512) || + (length & 0x3)) + return false; + else + return true; +} + +static int ux500_dma_channel_program(struct dma_channel *channel, + u16 packet_sz, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + int ret; + + BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN || + channel->status == MUSB_DMA_STATUS_BUSY); + + if (!ux500_dma_is_compatible(channel, packet_sz, (void *)dma_addr, len)) + return false; + + channel->status = MUSB_DMA_STATUS_BUSY; + channel->actual_len = 0; + ret = ux500_configure_channel(channel, packet_sz, mode, dma_addr, len); + if (!ret) + channel->status = MUSB_DMA_STATUS_FREE; + + return ret; +} + +static int ux500_dma_channel_abort(struct dma_channel *channel) +{ + struct ux500_dma_channel *ux500_channel = channel->private_data; + struct ux500_dma_controller *controller = ux500_channel->controller; + struct musb *musb = controller->private_data; + void __iomem *epio = musb->endpoints[ux500_channel->hw_ep->epnum].regs; + u16 csr; + + dev_dbg(musb->controller, "channel=%d, is_tx=%d\n", + ux500_channel->ch_num, ux500_channel->is_tx); + + if (channel->status == MUSB_DMA_STATUS_BUSY) { + if (ux500_channel->is_tx) { + csr = musb_readw(epio, MUSB_TXCSR); + csr &= ~(MUSB_TXCSR_AUTOSET | + MUSB_TXCSR_DMAENAB | + MUSB_TXCSR_DMAMODE); + musb_writew(epio, MUSB_TXCSR, csr); + } else { + csr = musb_readw(epio, MUSB_RXCSR); + csr &= ~(MUSB_RXCSR_AUTOCLEAR | + MUSB_RXCSR_DMAENAB | + MUSB_RXCSR_DMAMODE); + musb_writew(epio, MUSB_RXCSR, csr); + } + + ux500_channel->dma_chan->device-> + device_control(ux500_channel->dma_chan, + DMA_TERMINATE_ALL, 0); + channel->status = MUSB_DMA_STATUS_FREE; + } + return 0; +} + +static int ux500_dma_controller_stop(struct dma_controller *c) +{ + struct ux500_dma_controller *controller = container_of(c, + struct ux500_dma_controller, controller); + struct ux500_dma_channel *ux500_channel; + struct dma_channel *channel; + u8 ch_num; + + for (ch_num = 0; ch_num < controller->num_rx_channels; ch_num++) { + channel = &controller->rx_channel[ch_num].channel; + ux500_channel = channel->private_data; + + ux500_dma_channel_release(channel); + + if (ux500_channel->dma_chan) + dma_release_channel(ux500_channel->dma_chan); + } + + for (ch_num = 0; ch_num < controller->num_tx_channels; ch_num++) { + channel = &controller->tx_channel[ch_num].channel; + ux500_channel = channel->private_data; + + ux500_dma_channel_release(channel); + + if (ux500_channel->dma_chan) + dma_release_channel(ux500_channel->dma_chan); + } + + return 0; +} + +static int ux500_dma_controller_start(struct dma_controller *c) +{ + struct ux500_dma_controller *controller = container_of(c, + struct ux500_dma_controller, controller); + struct ux500_dma_channel *ux500_channel = NULL; + struct musb *musb = controller->private_data; + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev->platform_data; + struct ux500_musb_board_data *data = plat->board_data; + struct dma_channel *dma_channel = NULL; + u32 ch_num; + u8 dir; + u8 is_tx = 0; + + void **param_array; + struct ux500_dma_channel *channel_array; + u32 ch_count; + dma_cap_mask_t mask; + + if ((data->num_rx_channels > UX500_MUSB_DMA_NUM_RX_CHANNELS) || + (data->num_tx_channels > UX500_MUSB_DMA_NUM_TX_CHANNELS)) + return -EINVAL; + + controller->num_rx_channels = data->num_rx_channels; + controller->num_tx_channels = data->num_tx_channels; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* Prepare the loop for RX channels */ + channel_array = controller->rx_channel; + ch_count = data->num_rx_channels; + param_array = data->dma_rx_param_array; + + for (dir = 0; dir < 2; dir++) { + for (ch_num = 0; ch_num < ch_count; ch_num++) { + ux500_channel = &channel_array[ch_num]; + ux500_channel->controller = controller; + ux500_channel->ch_num = ch_num; + ux500_channel->is_tx = is_tx; + + dma_channel = &(ux500_channel->channel); + dma_channel->private_data = ux500_channel; + dma_channel->status = MUSB_DMA_STATUS_FREE; + dma_channel->max_len = SZ_16M; + + ux500_channel->dma_chan = dma_request_channel(mask, + data->dma_filter, + param_array[ch_num]); + if (!ux500_channel->dma_chan) { + ERR("Dma pipe allocation error dir=%d ch=%d\n", + dir, ch_num); + + /* Release already allocated channels */ + ux500_dma_controller_stop(c); + + return -EBUSY; + } + + } + + /* Prepare the loop for TX channels */ + channel_array = controller->tx_channel; + ch_count = data->num_tx_channels; + param_array = data->dma_tx_param_array; + is_tx = 1; + } + + return 0; +} + +void dma_controller_destroy(struct dma_controller *c) +{ + struct ux500_dma_controller *controller = container_of(c, + struct ux500_dma_controller, controller); + + kfree(controller); +} + +struct dma_controller *dma_controller_create(struct musb *musb, void __iomem *base) +{ + struct ux500_dma_controller *controller; + struct platform_device *pdev = to_platform_device(musb->controller); + struct resource *iomem; + + controller = kzalloc(sizeof(*controller), GFP_KERNEL); + if (!controller) + goto kzalloc_fail; + + controller->private_data = musb; + + /* Save physical address for DMA controller. */ + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iomem) { + dev_err(musb->controller, "no memory resource defined\n"); + goto plat_get_fail; + } + + controller->phy_base = (dma_addr_t) iomem->start; + + controller->controller.start = ux500_dma_controller_start; + controller->controller.stop = ux500_dma_controller_stop; + controller->controller.channel_alloc = ux500_dma_channel_allocate; + controller->controller.channel_release = ux500_dma_channel_release; + controller->controller.channel_program = ux500_dma_channel_program; + controller->controller.channel_abort = ux500_dma_channel_abort; + controller->controller.is_compatible = ux500_dma_is_compatible; + + return &controller->controller; + +plat_get_fail: + kfree(controller); +kzalloc_fail: + return NULL; +} diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig new file mode 100644 index 00000000..0dd0da0d --- /dev/null +++ b/drivers/usb/phy/Kconfig @@ -0,0 +1,221 @@ +# +# Physical Layer USB driver configuration +# +menuconfig USB_PHY + bool "USB Physical Layer drivers" + help + Most USB controllers have the physical layer signalling part + (commonly called a PHY) built in. However, dual-role devices + (a.k.a. USB on-the-go) which support being USB master or slave + with the same connector often use an external PHY. + + The drivers in this submenu add support for such PHY devices. + They are not needed for standard master-only (or the vast + majority of slave-only) USB interfaces. + + If you're not sure if this applies to you, it probably doesn't; + say N here. + +config USB_OTG_WAKELOCK + bool "Hold a wakelock when USB connected" + depends on WAKELOCK + select USB_OTG_UTILS + help + Select this to automatically hold a wakelock when USB is + connected, preventing suspend. + +if USB_PHY + +# +# USB Transceiver Drivers +# +config AB8500_USB + tristate "AB8500 USB Transceiver Driver" + depends on AB8500_CORE + help + Enable this to support the USB OTG transceiver in AB8500 chip. + This transceiver supports high and full speed devices plus, + in host mode, low speed. + +config FSL_USB2_OTG + bool "Freescale USB OTG Transceiver Driver" + depends on USB_EHCI_FSL && USB_FSL_USB2 && PM_RUNTIME + select USB_OTG + help + Enable this to support Freescale USB OTG transceiver. + +config ISP1301_OMAP + tristate "Philips ISP1301 with OMAP OTG" + depends on I2C && ARCH_OMAP_OTG + help + If you say yes here you get support for the Philips ISP1301 + USB-On-The-Go transceiver working with the OMAP OTG controller. + The ISP1301 is a full speed USB transceiver which is used in + products including H2, H3, and H4 development boards for Texas + Instruments OMAP processors. + + This driver can also be built as a module. If so, the module + will be called isp1301_omap. + +config MV_U3D_PHY + bool "Marvell USB 3.0 PHY controller Driver" + depends on CPU_MMP3 + help + Enable this to support Marvell USB 3.0 phy controller for Marvell + SoC. + +config NOP_USB_XCEIV + tristate "NOP USB Transceiver Driver" + help + This driver is to be used by all the usb transceiver which are either + built-in with usb ip or which are autonomous and doesn't require any + phy programming such as ISP1x04 etc. + +config OMAP_CONTROL_USB + tristate "OMAP CONTROL USB Driver" + help + Enable this to add support for the USB part present in the control + module. This driver has API to power on the USB2 PHY and to write to + the mailbox. The mailbox is present only in omap4 and the register to + power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an + additional register to power on USB3 PHY. + +config OMAP_USB2 + tristate "OMAP USB2 PHY Driver" + depends on ARCH_OMAP2PLUS + select OMAP_CONTROL_USB + help + Enable this to support the transceiver that is part of SOC. This + driver takes care of all the PHY functionality apart from comparator. + The USB OTG controller communicates with the comparator using this + driver. + +config OMAP_USB3 + tristate "OMAP USB3 PHY Driver" + select OMAP_CONTROL_USB + help + Enable this to support the USB3 PHY that is part of SOC. This + driver takes care of all the PHY functionality apart from comparator. + This driver interacts with the "OMAP Control USB Driver" to power + on/off the PHY. + +config SAMSUNG_USBPHY + tristate "Samsung USB PHY Driver" + help + Enable this to support Samsung USB phy helper driver for Samsung SoCs. + This driver provides common interface to interact, for Samsung USB 2.0 PHY + driver and later for Samsung USB 3.0 PHY driver. + +config SAMSUNG_USB2PHY + tristate "Samsung USB 2.0 PHY controller Driver" + select SAMSUNG_USBPHY + help + Enable this to support Samsung USB 2.0 (High Speed) PHY controller + driver for Samsung SoCs. + +config SAMSUNG_USB3PHY + tristate "Samsung USB 3.0 PHY controller Driver" + select SAMSUNG_USBPHY + help + Enable this to support Samsung USB 3.0 (Super Speed) phy controller + for samsung SoCs. + +config TWL4030_USB + tristate "TWL4030 USB Transceiver Driver" + depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS + help + Enable this to support the USB OTG transceiver on TWL4030 + family chips (including the TWL5030 and TPS659x0 devices). + This transceiver supports high and full speed devices plus, + in host mode, low speed. + +config TWL6030_USB + tristate "TWL6030 USB Transceiver Driver" + depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS + help + Enable this to support the USB OTG transceiver on TWL6030 + family chips. This TWL6030 transceiver has the VBUS and ID GND + and OTG SRP events capabilities. For all other transceiver functionality + UTMI PHY is embedded in OMAP4430. The internal PHY configurations APIs + are hooked to this driver through platform_data structure. + The definition of internal PHY APIs are in the mach-omap2 layer. + +config USB_GPIO_VBUS + tristate "GPIO based peripheral-only VBUS sensing 'transceiver'" + depends on GPIOLIB + help + Provides simple GPIO VBUS sensing for controllers with an + internal transceiver via the usb_phy interface, and + optionally control of a D+ pullup GPIO as well as a VBUS + current limit regulator. + +config USB_ISP1301 + tristate "NXP ISP1301 USB transceiver support" + depends on USB || USB_GADGET + depends on I2C + help + Say Y here to add support for the NXP ISP1301 USB transceiver driver. + This chip is typically used as USB transceiver for USB host, gadget + and OTG drivers (to be selected separately). + + To compile this driver as a module, choose M here: the + module will be called isp1301. + +config USB_MSM_OTG + tristate "OTG support for Qualcomm on-chip USB controller" + depends on (USB || USB_GADGET) && ARCH_MSM + help + Enable this to support the USB OTG transceiver on MSM chips. It + handles PHY initialization, clock management, and workarounds + required after resetting the hardware and power management. + This driver is required even for peripheral only or host only + mode configurations. + This driver is not supported on boards like trout which + has an external PHY. + +config USB_MV_OTG + tristate "Marvell USB OTG support" + depends on USB_EHCI_MV && USB_MV_UDC && PM_RUNTIME + select USB_OTG + help + Say Y here if you want to build Marvell USB OTG transciever + driver in kernel (including PXA and MMP series). This driver + implements role switch between EHCI host driver and gadget driver. + + To compile this driver as a module, choose M here. + +config USB_MXS_PHY + tristate "Freescale MXS USB PHY support" + depends on ARCH_MXC || ARCH_MXS + select STMP_DEVICE + help + Enable this to support the Freescale MXS USB PHY. + + MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x. + +config USB_RCAR_PHY + tristate "Renesas R-Car USB phy support" + depends on USB || USB_GADGET + help + Say Y here to add support for the Renesas R-Car USB phy driver. + This chip is typically used as USB phy for USB host, gadget. + This driver supports: R8A7779 + + To compile this driver as a module, choose M here: the + module will be called rcar-phy. + +config USB_ULPI + bool "Generic ULPI Transceiver Driver" + depends on ARM || ARM64 + help + Enable this to support ULPI connected USB OTG transceivers which + are likely found on embedded boards. + +config USB_ULPI_VIEWPORT + bool + depends on USB_ULPI + help + Provides read/write operations to the ULPI phy register set for + controllers with a viewport register (e.g. Chipidea/ARC controllers). + +endif # USB_PHY diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile new file mode 100644 index 00000000..a0a6cbad --- /dev/null +++ b/drivers/usb/phy/Makefile @@ -0,0 +1,34 @@ +# +# Makefile for physical layer USB drivers +# + +ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG + +obj-$(CONFIG_USB_PHY) += phy.o +obj-$(CONFIG_USB_OTG_WAKELOCK) += otg-wakelock.o + +# transceiver drivers, keep the list sorted + +obj-$(CONFIG_AB8500_USB) += phy-ab8500-usb.o +phy-fsl-usb2-objs := phy-fsl-usb.o phy-fsm-usb.o +obj-$(CONFIG_FSL_USB2_OTG) += phy-fsl-usb2.o +obj-$(CONFIG_ISP1301_OMAP) += phy-isp1301-omap.o +obj-$(CONFIG_MV_U3D_PHY) += phy-mv-u3d-usb.o +obj-$(CONFIG_NOP_USB_XCEIV) += phy-nop.o +obj-$(CONFIG_OMAP_CONTROL_USB) += phy-omap-control.o +obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o +obj-$(CONFIG_OMAP_USB3) += phy-omap-usb3.o +obj-$(CONFIG_SAMSUNG_USBPHY) += phy-samsung-usb.o +obj-$(CONFIG_SAMSUNG_USB2PHY) += phy-samsung-usb2.o +obj-$(CONFIG_SAMSUNG_USB3PHY) += phy-samsung-usb3.o +obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o +obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o +obj-$(CONFIG_USB_EHCI_TEGRA) += phy-tegra-usb.o +obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o +obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o +obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o +obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o +obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o +obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o +obj-$(CONFIG_USB_ULPI) += phy-ulpi.o +obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o diff --git a/drivers/usb/phy/otg-wakelock.c b/drivers/usb/phy/otg-wakelock.c new file mode 100644 index 00000000..479376bf --- /dev/null +++ b/drivers/usb/phy/otg-wakelock.c @@ -0,0 +1,173 @@ +/* + * otg-wakelock.c + * + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include + +#define TEMPORARY_HOLD_TIME 2000 + +static bool enabled = true; +static struct usb_phy *otgwl_xceiv; +static struct notifier_block otgwl_nb; + +/* + * otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the + * held field is updated to match. + */ + +static DEFINE_SPINLOCK(otgwl_spinlock); + +/* + * Only one lock, but since these 3 fields are associated with each other... + */ + +struct otgwl_lock { + char name[40]; + struct wake_lock wakelock; + bool held; +}; + +/* + * VBUS present lock. Also used as a timed lock on charger + * connect/disconnect and USB host disconnect, to allow the system + * to react to the change in power. + */ + +static struct otgwl_lock vbus_lock; + +static void otgwl_hold(struct otgwl_lock *lock) +{ + if (!lock->held) { + wake_lock(&lock->wakelock); + lock->held = true; + } +} + +static void otgwl_temporary_hold(struct otgwl_lock *lock) +{ + wake_lock_timeout(&lock->wakelock, + msecs_to_jiffies(TEMPORARY_HOLD_TIME)); + lock->held = false; +} + +static void otgwl_drop(struct otgwl_lock *lock) +{ + if (lock->held) { + wake_unlock(&lock->wakelock); + lock->held = false; + } +} + +static void otgwl_handle_event(unsigned long event) +{ + unsigned long irqflags; + + spin_lock_irqsave(&otgwl_spinlock, irqflags); + + if (!enabled) { + otgwl_drop(&vbus_lock); + spin_unlock_irqrestore(&otgwl_spinlock, irqflags); + return; + } + + switch (event) { + case USB_EVENT_VBUS: + case USB_EVENT_ENUMERATED: + otgwl_hold(&vbus_lock); + break; + + case USB_EVENT_NONE: + case USB_EVENT_ID: + case USB_EVENT_CHARGER: + otgwl_temporary_hold(&vbus_lock); + break; + + default: + break; + } + + spin_unlock_irqrestore(&otgwl_spinlock, irqflags); +} + +static int otgwl_otg_notifications(struct notifier_block *nb, + unsigned long event, void *unused) +{ + otgwl_handle_event(event); + return NOTIFY_OK; +} + +static int set_enabled(const char *val, const struct kernel_param *kp) +{ + int rv = param_set_bool(val, kp); + + if (rv) + return rv; + + if (otgwl_xceiv) + otgwl_handle_event(otgwl_xceiv->last_event); + + return 0; +} + +static struct kernel_param_ops enabled_param_ops = { + .set = set_enabled, + .get = param_get_bool, +}; + +module_param_cb(enabled, &enabled_param_ops, &enabled, 0644); +MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present"); + +static int __init otg_wakelock_init(void) +{ + int ret; + struct usb_phy *phy; + + phy = usb_get_phy(USB_PHY_TYPE_USB2); + + if (IS_ERR(phy)) { + pr_err("%s: No USB transceiver found\n", __func__); + return PTR_ERR(phy); + } + otgwl_xceiv = phy; + + snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s", + dev_name(otgwl_xceiv->dev)); + wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND, + vbus_lock.name); + + otgwl_nb.notifier_call = otgwl_otg_notifications; + ret = usb_register_notifier(otgwl_xceiv, &otgwl_nb); + + if (ret) { + pr_err("%s: usb_register_notifier on transceiver %s" + " failed\n", __func__, + dev_name(otgwl_xceiv->dev)); + otgwl_xceiv = NULL; + wake_lock_destroy(&vbus_lock.wakelock); + return ret; + } + + otgwl_handle_event(otgwl_xceiv->last_event); + return ret; +} + +late_initcall(otg_wakelock_init); diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c new file mode 100644 index 00000000..e5eb1b5a --- /dev/null +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -0,0 +1,922 @@ +/* + * drivers/usb/otg/ab8500_usb.c + * + * USB transceiver driver for AB8500 chip + * + * Copyright (C) 2010 ST-Ericsson AB + * Mian Yousaf Kaukab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Bank AB8500_SYS_CTRL2_BLOCK */ +#define AB8500_MAIN_WD_CTRL_REG 0x01 + +/* Bank AB8500_USB */ +#define AB8500_USB_LINE_STAT_REG 0x80 +#define AB8505_USB_LINE_STAT_REG 0x94 +#define AB8500_USB_PHY_CTRL_REG 0x8A + +/* Bank AB8500_DEVELOPMENT */ +#define AB8500_BANK12_ACCESS 0x00 + +/* Bank AB8500_DEBUG */ +#define AB8500_USB_PHY_TUNE1 0x05 +#define AB8500_USB_PHY_TUNE2 0x06 +#define AB8500_USB_PHY_TUNE3 0x07 + +#define AB8500_BIT_OTG_STAT_ID (1 << 0) +#define AB8500_BIT_PHY_CTRL_HOST_EN (1 << 0) +#define AB8500_BIT_PHY_CTRL_DEVICE_EN (1 << 1) +#define AB8500_BIT_WD_CTRL_ENABLE (1 << 0) +#define AB8500_BIT_WD_CTRL_KICK (1 << 1) + +#define AB8500_WD_KICK_DELAY_US 100 /* usec */ +#define AB8500_WD_V11_DISABLE_DELAY_US 100 /* usec */ +#define AB8500_V20_31952_DISABLE_DELAY_US 100 /* usec */ + +/* Usb line status register */ +enum ab8500_usb_link_status { + USB_LINK_NOT_CONFIGURED_8500 = 0, + USB_LINK_STD_HOST_NC_8500, + USB_LINK_STD_HOST_C_NS_8500, + USB_LINK_STD_HOST_C_S_8500, + USB_LINK_HOST_CHG_NM_8500, + USB_LINK_HOST_CHG_HS_8500, + USB_LINK_HOST_CHG_HS_CHIRP_8500, + USB_LINK_DEDICATED_CHG_8500, + USB_LINK_ACA_RID_A_8500, + USB_LINK_ACA_RID_B_8500, + USB_LINK_ACA_RID_C_NM_8500, + USB_LINK_ACA_RID_C_HS_8500, + USB_LINK_ACA_RID_C_HS_CHIRP_8500, + USB_LINK_HM_IDGND_8500, + USB_LINK_RESERVED_8500, + USB_LINK_NOT_VALID_LINK_8500, +}; + +enum ab8505_usb_link_status { + USB_LINK_NOT_CONFIGURED_8505 = 0, + USB_LINK_STD_HOST_NC_8505, + USB_LINK_STD_HOST_C_NS_8505, + USB_LINK_STD_HOST_C_S_8505, + USB_LINK_CDP_8505, + USB_LINK_RESERVED0_8505, + USB_LINK_RESERVED1_8505, + USB_LINK_DEDICATED_CHG_8505, + USB_LINK_ACA_RID_A_8505, + USB_LINK_ACA_RID_B_8505, + USB_LINK_ACA_RID_C_NM_8505, + USB_LINK_RESERVED2_8505, + USB_LINK_RESERVED3_8505, + USB_LINK_HM_IDGND_8505, + USB_LINK_CHARGERPORT_NOT_OK_8505, + USB_LINK_CHARGER_DM_HIGH_8505, + USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8505, + USB_LINK_STD_UPSTREAM_NO_IDGNG_NO_VBUS_8505, + USB_LINK_STD_UPSTREAM_8505, + USB_LINK_CHARGER_SE1_8505, + USB_LINK_CARKIT_CHGR_1_8505, + USB_LINK_CARKIT_CHGR_2_8505, + USB_LINK_ACA_DOCK_CHGR_8505, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_8505, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_8505, + USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8505, + USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8505, + USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8505, +}; + +enum ab8500_usb_mode { + USB_IDLE = 0, + USB_PERIPHERAL, + USB_HOST, + USB_DEDICATED_CHG +}; + +struct ab8500_usb { + struct usb_phy phy; + struct device *dev; + struct ab8500 *ab8500; + unsigned vbus_draw; + struct work_struct phy_dis_work; + enum ab8500_usb_mode mode; + struct regulator *v_ape; + struct regulator *v_musb; + struct regulator *v_ulpi; + int saved_v_ulpi; + int previous_link_status_state; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_sleep; +}; + +static inline struct ab8500_usb *phy_to_ab(struct usb_phy *x) +{ + return container_of(x, struct ab8500_usb, phy); +} + +static void ab8500_usb_wd_workaround(struct ab8500_usb *ab) +{ + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WD_CTRL_REG, + AB8500_BIT_WD_CTRL_ENABLE); + + udelay(AB8500_WD_KICK_DELAY_US); + + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WD_CTRL_REG, + (AB8500_BIT_WD_CTRL_ENABLE + | AB8500_BIT_WD_CTRL_KICK)); + + udelay(AB8500_WD_V11_DISABLE_DELAY_US); + + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WD_CTRL_REG, + 0); +} + +static void ab8500_usb_regulator_enable(struct ab8500_usb *ab) +{ + int ret, volt; + + ret = regulator_enable(ab->v_ape); + if (ret) + dev_err(ab->dev, "Failed to enable v-ape\n"); + + if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + ab->saved_v_ulpi = regulator_get_voltage(ab->v_ulpi); + if (ab->saved_v_ulpi < 0) + dev_err(ab->dev, "Failed to get v_ulpi voltage\n"); + + ret = regulator_set_voltage(ab->v_ulpi, 1300000, 1350000); + if (ret < 0) + dev_err(ab->dev, "Failed to set the Vintcore to 1.3V, ret=%d\n", + ret); + + ret = regulator_set_optimum_mode(ab->v_ulpi, 28000); + if (ret < 0) + dev_err(ab->dev, "Failed to set optimum mode (ret=%d)\n", + ret); + } + + ret = regulator_enable(ab->v_ulpi); + if (ret) + dev_err(ab->dev, "Failed to enable vddulpivio18\n"); + + if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + volt = regulator_get_voltage(ab->v_ulpi); + if ((volt != 1300000) && (volt != 1350000)) + dev_err(ab->dev, "Vintcore is not set to 1.3V volt=%d\n", + volt); + } + + ret = regulator_enable(ab->v_musb); + if (ret) + dev_err(ab->dev, "Failed to enable musb_1v8\n"); +} + +static void ab8500_usb_regulator_disable(struct ab8500_usb *ab) +{ + int ret; + + regulator_disable(ab->v_musb); + + regulator_disable(ab->v_ulpi); + + /* USB is not the only consumer of Vintcore, restore old settings */ + if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + if (ab->saved_v_ulpi > 0) { + ret = regulator_set_voltage(ab->v_ulpi, + ab->saved_v_ulpi, ab->saved_v_ulpi); + if (ret < 0) + dev_err(ab->dev, "Failed to set the Vintcore to %duV, ret=%d\n", + ab->saved_v_ulpi, ret); + } + + ret = regulator_set_optimum_mode(ab->v_ulpi, 0); + if (ret < 0) + dev_err(ab->dev, "Failed to set optimum mode (ret=%d)\n", + ret); + } + + regulator_disable(ab->v_ape); +} + +static void ab8500_usb_wd_linkstatus(struct ab8500_usb *ab, u8 bit) +{ + /* Workaround for v2.0 bug # 31952 */ + if (is_ab8500_2p0(ab->ab8500)) { + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + bit, bit); + udelay(AB8500_V20_31952_DISABLE_DELAY_US); + } +} + +static void ab8500_usb_phy_enable(struct ab8500_usb *ab, bool sel_host) +{ + u8 bit; + bit = sel_host ? AB8500_BIT_PHY_CTRL_HOST_EN : + AB8500_BIT_PHY_CTRL_DEVICE_EN; + + /* mux and configure USB pins to DEFAULT state */ + ab->pinctrl = pinctrl_get_select(ab->dev, PINCTRL_STATE_DEFAULT); + if (IS_ERR(ab->pinctrl)) + dev_err(ab->dev, "could not get/set default pinstate\n"); + + ab8500_usb_regulator_enable(ab); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + bit, bit); +} + +static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) +{ + u8 bit; + bit = sel_host ? AB8500_BIT_PHY_CTRL_HOST_EN : + AB8500_BIT_PHY_CTRL_DEVICE_EN; + + ab8500_usb_wd_linkstatus(ab, bit); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + bit, 0); + + /* Needed to disable the phy.*/ + ab8500_usb_wd_workaround(ab); + + ab8500_usb_regulator_disable(ab); + + if (!IS_ERR(ab->pinctrl)) { + /* configure USB pins to SLEEP state */ + ab->pins_sleep = pinctrl_lookup_state(ab->pinctrl, + PINCTRL_STATE_SLEEP); + + if (IS_ERR(ab->pins_sleep)) + dev_dbg(ab->dev, "could not get sleep pinstate\n"); + else if (pinctrl_select_state(ab->pinctrl, ab->pins_sleep)) + dev_err(ab->dev, "could not set pins to sleep state\n"); + + /* as USB pins are shared with idddet, release them to allow + * iddet to request them + */ + pinctrl_put(ab->pinctrl); + } +} + +#define ab8500_usb_host_phy_en(ab) ab8500_usb_phy_enable(ab, true) +#define ab8500_usb_host_phy_dis(ab) ab8500_usb_phy_disable(ab, true) +#define ab8500_usb_peri_phy_en(ab) ab8500_usb_phy_enable(ab, false) +#define ab8500_usb_peri_phy_dis(ab) ab8500_usb_phy_disable(ab, false) + +static int ab8505_usb_link_status_update(struct ab8500_usb *ab, + enum ab8505_usb_link_status lsts) +{ + enum ux500_musb_vbus_id_status event = 0; + + dev_dbg(ab->dev, "ab8505_usb_link_status_update %d\n", lsts); + + /* + * Spurious link_status interrupts are seen at the time of + * disconnection of a device in RIDA state + */ + if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8505 && + (lsts == USB_LINK_STD_HOST_NC_8505)) + return 0; + + ab->previous_link_status_state = lsts; + + switch (lsts) { + case USB_LINK_ACA_RID_B_8505: + event = UX500_MUSB_RIDB; + case USB_LINK_NOT_CONFIGURED_8505: + case USB_LINK_RESERVED0_8505: + case USB_LINK_RESERVED1_8505: + case USB_LINK_RESERVED2_8505: + case USB_LINK_RESERVED3_8505: + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + if (event != UX500_MUSB_RIDB) + event = UX500_MUSB_NONE; + /* + * Fallback to default B_IDLE as nothing + * is connected + */ + ab->phy.state = OTG_STATE_B_IDLE; + break; + + case USB_LINK_ACA_RID_C_NM_8505: + event = UX500_MUSB_RIDC; + case USB_LINK_STD_HOST_NC_8505: + case USB_LINK_STD_HOST_C_NS_8505: + case USB_LINK_STD_HOST_C_S_8505: + case USB_LINK_CDP_8505: + if (ab->mode == USB_IDLE) { + ab->mode = USB_PERIPHERAL; + ab8500_usb_peri_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (event != UX500_MUSB_RIDC) + event = UX500_MUSB_VBUS; + break; + + case USB_LINK_ACA_RID_A_8505: + case USB_LINK_ACA_DOCK_CHGR_8505: + event = UX500_MUSB_RIDA; + case USB_LINK_HM_IDGND_8505: + if (ab->mode == USB_IDLE) { + ab->mode = USB_HOST; + ab8500_usb_host_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + ab->phy.otg->default_a = true; + if (event != UX500_MUSB_RIDA) + event = UX500_MUSB_ID; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_DEDICATED_CHG_8505: + ab->mode = USB_DEDICATED_CHG; + event = UX500_MUSB_CHARGER; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + default: + break; + } + + return 0; +} + +static int ab8500_usb_link_status_update(struct ab8500_usb *ab, + enum ab8500_usb_link_status lsts) +{ + enum ux500_musb_vbus_id_status event = 0; + + dev_dbg(ab->dev, "ab8500_usb_link_status_update %d\n", lsts); + + /* + * Spurious link_status interrupts are seen in case of a + * disconnection of a device in IDGND and RIDA stage + */ + if (ab->previous_link_status_state == USB_LINK_HM_IDGND_8500 && + (lsts == USB_LINK_STD_HOST_C_NS_8500 || + lsts == USB_LINK_STD_HOST_NC_8500)) + return 0; + + if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8500 && + lsts == USB_LINK_STD_HOST_NC_8500) + return 0; + + ab->previous_link_status_state = lsts; + + switch (lsts) { + case USB_LINK_ACA_RID_B_8500: + event = UX500_MUSB_RIDB; + case USB_LINK_NOT_CONFIGURED_8500: + case USB_LINK_NOT_VALID_LINK_8500: + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + if (event != UX500_MUSB_RIDB) + event = UX500_MUSB_NONE; + /* Fallback to default B_IDLE as nothing is connected */ + ab->phy.state = OTG_STATE_B_IDLE; + break; + + case USB_LINK_ACA_RID_C_NM_8500: + case USB_LINK_ACA_RID_C_HS_8500: + case USB_LINK_ACA_RID_C_HS_CHIRP_8500: + event = UX500_MUSB_RIDC; + case USB_LINK_STD_HOST_NC_8500: + case USB_LINK_STD_HOST_C_NS_8500: + case USB_LINK_STD_HOST_C_S_8500: + case USB_LINK_HOST_CHG_NM_8500: + case USB_LINK_HOST_CHG_HS_8500: + case USB_LINK_HOST_CHG_HS_CHIRP_8500: + if (ab->mode == USB_IDLE) { + ab->mode = USB_PERIPHERAL; + ab8500_usb_peri_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (event != UX500_MUSB_RIDC) + event = UX500_MUSB_VBUS; + break; + + case USB_LINK_ACA_RID_A_8500: + event = UX500_MUSB_RIDA; + case USB_LINK_HM_IDGND_8500: + if (ab->mode == USB_IDLE) { + ab->mode = USB_HOST; + ab8500_usb_host_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + ab->phy.otg->default_a = true; + if (event != UX500_MUSB_RIDA) + event = UX500_MUSB_ID; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_DEDICATED_CHG_8500: + ab->mode = USB_DEDICATED_CHG; + event = UX500_MUSB_CHARGER; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_RESERVED_8500: + break; + } + + return 0; +} + +/* + * Connection Sequence: + * 1. Link Status Interrupt + * 2. Enable AB clock + * 3. Enable AB regulators + * 4. Enable USB phy + * 5. Reset the musb controller + * 6. Switch the ULPI GPIO pins to fucntion mode + * 7. Enable the musb Peripheral5 clock + * 8. Restore MUSB context + */ +static int abx500_usb_link_status_update(struct ab8500_usb *ab) +{ + u8 reg; + int ret = 0; + + if (is_ab8500(ab->ab8500)) { + enum ab8500_usb_link_status lsts; + + abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_LINE_STAT_REG, ®); + lsts = (reg >> 3) & 0x0F; + ret = ab8500_usb_link_status_update(ab, lsts); + } else if (is_ab8505(ab->ab8500)) { + enum ab8505_usb_link_status lsts; + + abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB8505_USB_LINE_STAT_REG, ®); + lsts = (reg >> 3) & 0x1F; + ret = ab8505_usb_link_status_update(ab, lsts); + } + + return ret; +} + +/* + * Disconnection Sequence: + * 1. Disconect Interrupt + * 2. Disable regulators + * 3. Disable AB clock + * 4. Disable the Phy + * 5. Link Status Interrupt + * 6. Disable Musb Clock + */ +static irqreturn_t ab8500_usb_disconnect_irq(int irq, void *data) +{ + struct ab8500_usb *ab = (struct ab8500_usb *) data; + enum usb_phy_events event = UX500_MUSB_NONE; + + /* Link status will not be updated till phy is disabled. */ + if (ab->mode == USB_HOST) { + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_host_phy_dis(ab); + ab->mode = USB_IDLE; + } + + if (ab->mode == USB_PERIPHERAL) { + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_peri_phy_dis(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_CLEAN, &ab->vbus_draw); + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + } + + if (is_ab8500_2p0(ab->ab8500)) { + if (ab->mode == USB_DEDICATED_CHG) { + ab8500_usb_wd_linkstatus(ab, + AB8500_BIT_PHY_CTRL_DEVICE_EN); + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_DEVICE_EN, 0); + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t ab8500_usb_link_status_irq(int irq, void *data) +{ + struct ab8500_usb *ab = (struct ab8500_usb *) data; + + abx500_usb_link_status_update(ab); + + return IRQ_HANDLED; +} + +static void ab8500_usb_phy_disable_work(struct work_struct *work) +{ + struct ab8500_usb *ab = container_of(work, struct ab8500_usb, + phy_dis_work); + + if (!ab->phy.otg->host) + ab8500_usb_host_phy_dis(ab); + + if (!ab->phy.otg->gadget) + ab8500_usb_peri_phy_dis(ab); +} + +static unsigned ab8500_eyediagram_workaroud(struct ab8500_usb *ab, unsigned mA) +{ + /* + * AB8500 V2 has eye diagram issues when drawing more than 100mA from + * VBUS. Set charging current to 100mA in case of standard host + */ + if (is_ab8500_2p0_or_earlier(ab->ab8500)) + if (mA > 100) + mA = 100; + + return mA; +} + +static int ab8500_usb_set_power(struct usb_phy *phy, unsigned mA) +{ + struct ab8500_usb *ab; + + if (!phy) + return -ENODEV; + + ab = phy_to_ab(phy); + + mA = ab8500_eyediagram_workaroud(ab, mA); + + ab->vbus_draw = mA; + + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_VBUS, &ab->vbus_draw); + + return 0; +} + +static int ab8500_usb_set_suspend(struct usb_phy *x, int suspend) +{ + /* TODO */ + return 0; +} + +static int ab8500_usb_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct ab8500_usb *ab; + + if (!otg) + return -ENODEV; + + ab = phy_to_ab(otg->phy); + + ab->phy.otg->gadget = gadget; + + /* Some drivers call this function in atomic context. + * Do not update ab8500 registers directly till this + * is fixed. + */ + + if ((ab->mode != USB_IDLE) && (!gadget)) { + ab->mode = USB_IDLE; + schedule_work(&ab->phy_dis_work); + } + + return 0; +} + +static int ab8500_usb_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct ab8500_usb *ab; + + if (!otg) + return -ENODEV; + + ab = phy_to_ab(otg->phy); + + ab->phy.otg->host = host; + + /* Some drivers call this function in atomic context. + * Do not update ab8500 registers directly till this + * is fixed. + */ + + if ((ab->mode != USB_IDLE) && (!host)) { + ab->mode = USB_IDLE; + schedule_work(&ab->phy_dis_work); + } + + return 0; +} + +static int ab8500_usb_regulator_get(struct ab8500_usb *ab) +{ + int err; + + ab->v_ape = devm_regulator_get(ab->dev, "v-ape"); + if (IS_ERR(ab->v_ape)) { + dev_err(ab->dev, "Could not get v-ape supply\n"); + err = PTR_ERR(ab->v_ape); + return err; + } + + ab->v_ulpi = devm_regulator_get(ab->dev, "vddulpivio18"); + if (IS_ERR(ab->v_ulpi)) { + dev_err(ab->dev, "Could not get vddulpivio18 supply\n"); + err = PTR_ERR(ab->v_ulpi); + return err; + } + + ab->v_musb = devm_regulator_get(ab->dev, "musb_1v8"); + if (IS_ERR(ab->v_musb)) { + dev_err(ab->dev, "Could not get musb_1v8 supply\n"); + err = PTR_ERR(ab->v_musb); + return err; + } + + return 0; +} + +static int ab8500_usb_irq_setup(struct platform_device *pdev, + struct ab8500_usb *ab) +{ + int err; + int irq; + + irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS"); + if (irq < 0) { + dev_err(&pdev->dev, "Link status irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_link_status_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, "usb-link-status", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for link status irq\n"); + return err; + } + + irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); + if (irq < 0) { + dev_err(&pdev->dev, "ID fall irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, "usb-id-fall", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for ID fall irq\n"); + return err; + } + + irq = platform_get_irq_byname(pdev, "VBUS_DET_F"); + if (irq < 0) { + dev_err(&pdev->dev, "VBUS fall irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, "usb-vbus-fall", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for Vbus fall irq\n"); + return err; + } + + return 0; +} + +static int ab8500_usb_probe(struct platform_device *pdev) +{ + struct ab8500_usb *ab; + struct ab8500 *ab8500; + struct usb_otg *otg; + int err; + int rev; + + ab8500 = dev_get_drvdata(pdev->dev.parent); + rev = abx500_get_chip_id(&pdev->dev); + + if (is_ab8500_1p1_or_earlier(ab8500)) { + dev_err(&pdev->dev, "Unsupported AB8500 chip rev=%d\n", rev); + return -ENODEV; + } + + ab = devm_kzalloc(&pdev->dev, sizeof(*ab), GFP_KERNEL); + if (!ab) + return -ENOMEM; + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + ab->dev = &pdev->dev; + ab->ab8500 = ab8500; + ab->phy.dev = ab->dev; + ab->phy.otg = otg; + ab->phy.label = "ab8500"; + ab->phy.set_suspend = ab8500_usb_set_suspend; + ab->phy.set_power = ab8500_usb_set_power; + ab->phy.state = OTG_STATE_UNDEFINED; + + otg->phy = &ab->phy; + otg->set_host = ab8500_usb_set_host; + otg->set_peripheral = ab8500_usb_set_peripheral; + + platform_set_drvdata(pdev, ab); + + ATOMIC_INIT_NOTIFIER_HEAD(&ab->phy.notifier); + + /* all: Disable phy when called from set_host and set_peripheral */ + INIT_WORK(&ab->phy_dis_work, ab8500_usb_phy_disable_work); + + err = ab8500_usb_regulator_get(ab); + if (err) + return err; + + err = ab8500_usb_irq_setup(pdev, ab); + if (err < 0) + return err; + + err = usb_add_phy(&ab->phy, USB_PHY_TYPE_USB2); + if (err) { + dev_err(&pdev->dev, "Can't register transceiver\n"); + return err; + } + + /* Phy tuning values for AB8500 */ + if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + /* Enable the PBT/Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); + if (err < 0) + dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x78); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", + err); + + /* Switch to normal mode/disable Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", + err); + } + + /* Phy tuning values for AB8505 */ + if (is_ab8505(ab->ab8500)) { + /* Enable the PBT/Bank 0x12 access */ + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, + 0x01, 0x01); + if (err < 0) + dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE1, + 0xC8, 0xC8); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE2, + 0x60, 0x60); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE3, + 0xFC, 0x80); + + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", + err); + + /* Switch to normal mode/disable Bank 0x12 access */ + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, + 0x00, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", + err); + } + + /* Needed to enable ID detection. */ + ab8500_usb_wd_workaround(ab); + + abx500_usb_link_status_update(ab); + + dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", rev); + + return 0; +} + +static int ab8500_usb_remove(struct platform_device *pdev) +{ + struct ab8500_usb *ab = platform_get_drvdata(pdev); + + cancel_work_sync(&ab->phy_dis_work); + + usb_remove_phy(&ab->phy); + + if (ab->mode == USB_HOST) + ab8500_usb_host_phy_dis(ab); + else if (ab->mode == USB_PERIPHERAL) + ab8500_usb_peri_phy_dis(ab); + + return 0; +} + +static struct platform_driver ab8500_usb_driver = { + .probe = ab8500_usb_probe, + .remove = ab8500_usb_remove, + .driver = { + .name = "ab8500-usb", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_usb_init(void) +{ + return platform_driver_register(&ab8500_usb_driver); +} +subsys_initcall(ab8500_usb_init); + +static void __exit ab8500_usb_exit(void) +{ + platform_driver_unregister(&ab8500_usb_driver); +} +module_exit(ab8500_usb_exit); + +MODULE_ALIAS("platform:ab8500_usb"); +MODULE_AUTHOR("ST-Ericsson AB"); +MODULE_DESCRIPTION("AB8500 usb transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c new file mode 100644 index 00000000..e771bafb --- /dev/null +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -0,0 +1,1174 @@ +/* + * Copyright (C) 2007,2008 Freescale semiconductor, Inc. + * + * Author: Li Yang + * Jerry Huang + * + * Initialization based on code from Shlomi Gridish. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "phy-fsl-usb.h" + +#define DRIVER_VERSION "Rev. 1.55" +#define DRIVER_AUTHOR "Jerry Huang/Li Yang" +#define DRIVER_DESC "Freescale USB OTG Transceiver Driver" +#define DRIVER_INFO DRIVER_DESC " " DRIVER_VERSION + +static const char driver_name[] = "fsl-usb2-otg"; + +const pm_message_t otg_suspend_state = { + .event = 1, +}; + +#define HA_DATA_PULSE + +static struct usb_dr_mmap *usb_dr_regs; +static struct fsl_otg *fsl_otg_dev; +static int srp_wait_done; + +/* FSM timers */ +struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr; + +/* Driver specific timers */ +struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, + *b_srp_wait_tmr, *a_wait_enum_tmr; + +static struct list_head active_timers; + +static struct fsl_otg_config fsl_otg_initdata = { + .otg_port = 1, +}; + +#ifdef CONFIG_PPC32 +static u32 _fsl_readl_be(const unsigned __iomem *p) +{ + return in_be32(p); +} + +static u32 _fsl_readl_le(const unsigned __iomem *p) +{ + return in_le32(p); +} + +static void _fsl_writel_be(u32 v, unsigned __iomem *p) +{ + out_be32(p, v); +} + +static void _fsl_writel_le(u32 v, unsigned __iomem *p) +{ + out_le32(p, v); +} + +static u32 (*_fsl_readl)(const unsigned __iomem *p); +static void (*_fsl_writel)(u32 v, unsigned __iomem *p); + +#define fsl_readl(p) (*_fsl_readl)((p)) +#define fsl_writel(v, p) (*_fsl_writel)((v), (p)) + +#else +#define fsl_readl(addr) readl(addr) +#define fsl_writel(val, addr) writel(val, addr) +#endif /* CONFIG_PPC32 */ + +/* Routines to access transceiver ULPI registers */ +u8 view_ulpi(u8 addr) +{ + u32 temp; + + temp = 0x40000000 | (addr << 16); + fsl_writel(temp, &usb_dr_regs->ulpiview); + udelay(1000); + while (temp & 0x40) + temp = fsl_readl(&usb_dr_regs->ulpiview); + return (le32_to_cpu(temp) & 0x0000ff00) >> 8; +} + +int write_ulpi(u8 addr, u8 data) +{ + u32 temp; + + temp = 0x60000000 | (addr << 16) | data; + fsl_writel(temp, &usb_dr_regs->ulpiview); + return 0; +} + +/* -------------------------------------------------------------*/ +/* Operations that will be called from OTG Finite State Machine */ + +/* Charge vbus for vbus pulsing in SRP */ +void fsl_otg_chrg_vbus(int on) +{ + u32 tmp; + + tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + + if (on) + /* stop discharging, start charging */ + tmp = (tmp & ~OTGSC_CTRL_VBUS_DISCHARGE) | + OTGSC_CTRL_VBUS_CHARGE; + else + /* stop charging */ + tmp &= ~OTGSC_CTRL_VBUS_CHARGE; + + fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* Discharge vbus through a resistor to ground */ +void fsl_otg_dischrg_vbus(int on) +{ + u32 tmp; + + tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + + if (on) + /* stop charging, start discharging */ + tmp = (tmp & ~OTGSC_CTRL_VBUS_CHARGE) | + OTGSC_CTRL_VBUS_DISCHARGE; + else + /* stop discharging */ + tmp &= ~OTGSC_CTRL_VBUS_DISCHARGE; + + fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* A-device driver vbus, controlled through PP bit in PORTSC */ +void fsl_otg_drv_vbus(int on) +{ + u32 tmp; + + if (on) { + tmp = fsl_readl(&usb_dr_regs->portsc) & ~PORTSC_W1C_BITS; + fsl_writel(tmp | PORTSC_PORT_POWER, &usb_dr_regs->portsc); + } else { + tmp = fsl_readl(&usb_dr_regs->portsc) & + ~PORTSC_W1C_BITS & ~PORTSC_PORT_POWER; + fsl_writel(tmp, &usb_dr_regs->portsc); + } +} + +/* + * Pull-up D+, signalling connect by periperal. Also used in + * data-line pulsing in SRP + */ +void fsl_otg_loc_conn(int on) +{ + u32 tmp; + + tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + + if (on) + tmp |= OTGSC_CTRL_DATA_PULSING; + else + tmp &= ~OTGSC_CTRL_DATA_PULSING; + + fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* + * Generate SOF by host. This is controlled through suspend/resume the + * port. In host mode, controller will automatically send SOF. + * Suspend will block the data on the port. + */ +void fsl_otg_loc_sof(int on) +{ + u32 tmp; + + tmp = fsl_readl(&fsl_otg_dev->dr_mem_map->portsc) & ~PORTSC_W1C_BITS; + if (on) + tmp |= PORTSC_PORT_FORCE_RESUME; + else + tmp |= PORTSC_PORT_SUSPEND; + + fsl_writel(tmp, &fsl_otg_dev->dr_mem_map->portsc); + +} + +/* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ +void fsl_otg_start_pulse(void) +{ + u32 tmp; + + srp_wait_done = 0; +#ifdef HA_DATA_PULSE + tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + tmp |= OTGSC_HA_DATA_PULSE; + fsl_writel(tmp, &usb_dr_regs->otgsc); +#else + fsl_otg_loc_conn(1); +#endif + + fsl_otg_add_timer(b_data_pulse_tmr); +} + +void b_data_pulse_end(unsigned long foo) +{ +#ifdef HA_DATA_PULSE +#else + fsl_otg_loc_conn(0); +#endif + + /* Do VBUS pulse after data pulse */ + fsl_otg_pulse_vbus(); +} + +void fsl_otg_pulse_vbus(void) +{ + srp_wait_done = 0; + fsl_otg_chrg_vbus(1); + /* start the timer to end vbus charge */ + fsl_otg_add_timer(b_vbus_pulse_tmr); +} + +void b_vbus_pulse_end(unsigned long foo) +{ + fsl_otg_chrg_vbus(0); + + /* + * As USB3300 using the same a_sess_vld and b_sess_vld voltage + * we need to discharge the bus for a while to distinguish + * residual voltage of vbus pulsing and A device pull up + */ + fsl_otg_dischrg_vbus(1); + fsl_otg_add_timer(b_srp_wait_tmr); +} + +void b_srp_end(unsigned long foo) +{ + fsl_otg_dischrg_vbus(0); + srp_wait_done = 1; + + if ((fsl_otg_dev->phy.state == OTG_STATE_B_SRP_INIT) && + fsl_otg_dev->fsm.b_sess_vld) + fsl_otg_dev->fsm.b_srp_done = 1; +} + +/* + * Workaround for a_host suspending too fast. When a_bus_req=0, + * a_host will start by SRP. It needs to set b_hnp_enable before + * actually suspending to start HNP + */ +void a_wait_enum(unsigned long foo) +{ + VDBG("a_wait_enum timeout\n"); + if (!fsl_otg_dev->phy.otg->host->b_hnp_enable) + fsl_otg_add_timer(a_wait_enum_tmr); + else + otg_statemachine(&fsl_otg_dev->fsm); +} + +/* The timeout callback function to set time out bit */ +void set_tmout(unsigned long indicator) +{ + *(int *)indicator = 1; +} + +/* Initialize timers */ +int fsl_otg_init_timers(struct otg_fsm *fsm) +{ + /* FSM used timers */ + a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, + (unsigned long)&fsm->a_wait_vrise_tmout); + if (!a_wait_vrise_tmr) + return -ENOMEM; + + a_wait_bcon_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_BCON, + (unsigned long)&fsm->a_wait_bcon_tmout); + if (!a_wait_bcon_tmr) + return -ENOMEM; + + a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout, TA_AIDL_BDIS, + (unsigned long)&fsm->a_aidl_bdis_tmout); + if (!a_aidl_bdis_tmr) + return -ENOMEM; + + b_ase0_brst_tmr = otg_timer_initializer(&set_tmout, TB_ASE0_BRST, + (unsigned long)&fsm->b_ase0_brst_tmout); + if (!b_ase0_brst_tmr) + return -ENOMEM; + + b_se0_srp_tmr = otg_timer_initializer(&set_tmout, TB_SE0_SRP, + (unsigned long)&fsm->b_se0_srp); + if (!b_se0_srp_tmr) + return -ENOMEM; + + b_srp_fail_tmr = otg_timer_initializer(&set_tmout, TB_SRP_FAIL, + (unsigned long)&fsm->b_srp_done); + if (!b_srp_fail_tmr) + return -ENOMEM; + + a_wait_enum_tmr = otg_timer_initializer(&a_wait_enum, 10, + (unsigned long)&fsm); + if (!a_wait_enum_tmr) + return -ENOMEM; + + /* device driver used timers */ + b_srp_wait_tmr = otg_timer_initializer(&b_srp_end, TB_SRP_WAIT, 0); + if (!b_srp_wait_tmr) + return -ENOMEM; + + b_data_pulse_tmr = otg_timer_initializer(&b_data_pulse_end, + TB_DATA_PLS, 0); + if (!b_data_pulse_tmr) + return -ENOMEM; + + b_vbus_pulse_tmr = otg_timer_initializer(&b_vbus_pulse_end, + TB_VBUS_PLS, 0); + if (!b_vbus_pulse_tmr) + return -ENOMEM; + + return 0; +} + +/* Uninitialize timers */ +void fsl_otg_uninit_timers(void) +{ + /* FSM used timers */ + kfree(a_wait_vrise_tmr); + kfree(a_wait_bcon_tmr); + kfree(a_aidl_bdis_tmr); + kfree(b_ase0_brst_tmr); + kfree(b_se0_srp_tmr); + kfree(b_srp_fail_tmr); + kfree(a_wait_enum_tmr); + + /* device driver used timers */ + kfree(b_srp_wait_tmr); + kfree(b_data_pulse_tmr); + kfree(b_vbus_pulse_tmr); +} + +/* Add timer to timer list */ +void fsl_otg_add_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = gtimer; + struct fsl_otg_timer *tmp_timer; + + /* + * Check if the timer is already in the active list, + * if so update timer count + */ + list_for_each_entry(tmp_timer, &active_timers, list) + if (tmp_timer == timer) { + timer->count = timer->expires; + return; + } + timer->count = timer->expires; + list_add_tail(&timer->list, &active_timers); +} + +/* Remove timer from the timer list; clear timeout status */ +void fsl_otg_del_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = gtimer; + struct fsl_otg_timer *tmp_timer, *del_tmp; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) + if (tmp_timer == timer) + list_del(&timer->list); +} + +/* + * Reduce timer count by 1, and find timeout conditions. + * Called by fsl_otg 1ms timer interrupt + */ +int fsl_otg_tick_timer(void) +{ + struct fsl_otg_timer *tmp_timer, *del_tmp; + int expired = 0; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { + tmp_timer->count--; + /* check if timer expires */ + if (!tmp_timer->count) { + list_del(&tmp_timer->list); + tmp_timer->function(tmp_timer->data); + expired = 1; + } + } + + return expired; +} + +/* Reset controller, not reset the bus */ +void otg_reset_controller(void) +{ + u32 command; + + command = fsl_readl(&usb_dr_regs->usbcmd); + command |= (1 << 1); + fsl_writel(command, &usb_dr_regs->usbcmd); + while (fsl_readl(&usb_dr_regs->usbcmd) & (1 << 1)) + ; +} + +/* Call suspend/resume routines in host driver */ +int fsl_otg_start_host(struct otg_fsm *fsm, int on) +{ + struct usb_otg *otg = fsm->otg; + struct device *dev; + struct fsl_otg *otg_dev = container_of(otg->phy, struct fsl_otg, phy); + u32 retval = 0; + + if (!otg->host) + return -ENODEV; + dev = otg->host->controller; + + /* + * Update a_vbus_vld state as a_vbus_vld int is disabled + * in device mode + */ + fsm->a_vbus_vld = + !!(fsl_readl(&usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID); + if (on) { + /* start fsl usb host controller */ + if (otg_dev->host_working) + goto end; + else { + otg_reset_controller(); + VDBG("host on......\n"); + if (dev->driver->pm && dev->driver->pm->resume) { + retval = dev->driver->pm->resume(dev); + if (fsm->id) { + /* default-b */ + fsl_otg_drv_vbus(1); + /* + * Workaround: b_host can't driver + * vbus, but PP in PORTSC needs to + * be 1 for host to work. + * So we set drv_vbus bit in + * transceiver to 0 thru ULPI. + */ + write_ulpi(0x0c, 0x20); + } + } + + otg_dev->host_working = 1; + } + } else { + /* stop fsl usb host controller */ + if (!otg_dev->host_working) + goto end; + else { + VDBG("host off......\n"); + if (dev && dev->driver) { + if (dev->driver->pm && dev->driver->pm->suspend) + retval = dev->driver->pm->suspend(dev); + if (fsm->id) + /* default-b */ + fsl_otg_drv_vbus(0); + } + otg_dev->host_working = 0; + } + } +end: + return retval; +} + +/* + * Call suspend and resume function in udc driver + * to stop and start udc driver. + */ +int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) +{ + struct usb_otg *otg = fsm->otg; + struct device *dev; + + if (!otg->gadget || !otg->gadget->dev.parent) + return -ENODEV; + + VDBG("gadget %s\n", on ? "on" : "off"); + dev = otg->gadget->dev.parent; + + if (on) { + if (dev->driver->resume) + dev->driver->resume(dev); + } else { + if (dev->driver->suspend) + dev->driver->suspend(dev, otg_suspend_state); + } + + return 0; +} + +/* + * Called by initialization code of host driver. Register host controller + * to the OTG. Suspend host for OTG role detection. + */ +static int fsl_otg_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct fsl_otg *otg_dev; + + if (!otg) + return -ENODEV; + + otg_dev = container_of(otg->phy, struct fsl_otg, phy); + if (otg_dev != fsl_otg_dev) + return -ENODEV; + + otg->host = host; + + otg_dev->fsm.a_bus_drop = 0; + otg_dev->fsm.a_bus_req = 1; + + if (host) { + VDBG("host off......\n"); + + otg->host->otg_port = fsl_otg_initdata.otg_port; + otg->host->is_b_host = otg_dev->fsm.id; + /* + * must leave time for khubd to finish its thing + * before yanking the host driver out from under it, + * so suspend the host after a short delay. + */ + otg_dev->host_working = 1; + schedule_delayed_work(&otg_dev->otg_event, 100); + return 0; + } else { + /* host driver going away */ + if (!(fsl_readl(&otg_dev->dr_mem_map->otgsc) & + OTGSC_STS_USB_ID)) { + /* Mini-A cable connected */ + struct otg_fsm *fsm = &otg_dev->fsm; + + otg->phy->state = OTG_STATE_UNDEFINED; + fsm->protocol = PROTO_UNDEF; + } + } + + otg_dev->host_working = 0; + + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* Called by initialization code of udc. Register udc to OTG. */ +static int fsl_otg_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct fsl_otg *otg_dev; + + if (!otg) + return -ENODEV; + + otg_dev = container_of(otg->phy, struct fsl_otg, phy); + VDBG("otg_dev 0x%x\n", (int)otg_dev); + VDBG("fsl_otg_dev 0x%x\n", (int)fsl_otg_dev); + if (otg_dev != fsl_otg_dev) + return -ENODEV; + + if (!gadget) { + if (!otg->default_a) + otg->gadget->ops->vbus_draw(otg->gadget, 0); + usb_gadget_vbus_disconnect(otg->gadget); + otg->gadget = 0; + otg_dev->fsm.b_bus_req = 0; + otg_statemachine(&otg_dev->fsm); + return 0; + } + + otg->gadget = gadget; + otg->gadget->is_a_peripheral = !otg_dev->fsm.id; + + otg_dev->fsm.b_bus_req = 1; + + /* start the gadget right away if the ID pin says Mini-B */ + DBG("ID pin=%d\n", otg_dev->fsm.id); + if (otg_dev->fsm.id == 1) { + fsl_otg_start_host(&otg_dev->fsm, 0); + otg_drv_vbus(&otg_dev->fsm, 0); + fsl_otg_start_gadget(&otg_dev->fsm, 1); + } + + return 0; +} + +/* Set OTG port power, only for B-device */ +static int fsl_otg_set_power(struct usb_phy *phy, unsigned mA) +{ + if (!fsl_otg_dev) + return -ENODEV; + if (phy->state == OTG_STATE_B_PERIPHERAL) + pr_info("FSL OTG: Draw %d mA\n", mA); + + return 0; +} + +/* + * Delayed pin detect interrupt processing. + * + * When the Mini-A cable is disconnected from the board, + * the pin-detect interrupt happens before the disconnect + * interrupts for the connected device(s). In order to + * process the disconnect interrupt(s) prior to switching + * roles, the pin-detect interrupts are delayed, and handled + * by this routine. + */ +static void fsl_otg_event(struct work_struct *work) +{ + struct fsl_otg *og = container_of(work, struct fsl_otg, otg_event.work); + struct otg_fsm *fsm = &og->fsm; + + if (fsm->id) { /* switch to gadget */ + fsl_otg_start_host(fsm, 0); + otg_drv_vbus(fsm, 0); + fsl_otg_start_gadget(fsm, 1); + } +} + +/* B-device start SRP */ +static int fsl_otg_start_srp(struct usb_otg *otg) +{ + struct fsl_otg *otg_dev; + + if (!otg || otg->phy->state != OTG_STATE_B_IDLE) + return -ENODEV; + + otg_dev = container_of(otg->phy, struct fsl_otg, phy); + if (otg_dev != fsl_otg_dev) + return -ENODEV; + + otg_dev->fsm.b_bus_req = 1; + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* A_host suspend will call this function to start hnp */ +static int fsl_otg_start_hnp(struct usb_otg *otg) +{ + struct fsl_otg *otg_dev; + + if (!otg) + return -ENODEV; + + otg_dev = container_of(otg->phy, struct fsl_otg, phy); + if (otg_dev != fsl_otg_dev) + return -ENODEV; + + DBG("start_hnp...n"); + + /* clear a_bus_req to enter a_suspend state */ + otg_dev->fsm.a_bus_req = 0; + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* + * Interrupt handler. OTG/host/peripheral share the same int line. + * OTG driver clears OTGSC interrupts and leaves USB interrupts + * intact. It needs to have knowledge of some USB interrupts + * such as port change. + */ +irqreturn_t fsl_otg_isr(int irq, void *dev_id) +{ + struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; + struct usb_otg *otg = ((struct fsl_otg *)dev_id)->phy.otg; + u32 otg_int_src, otg_sc; + + otg_sc = fsl_readl(&usb_dr_regs->otgsc); + otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8); + + /* Only clear otg interrupts */ + fsl_writel(otg_sc, &usb_dr_regs->otgsc); + + /*FIXME: ID change not generate when init to 0 */ + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + + /* process OTG interrupts */ + if (otg_int_src) { + if (otg_int_src & OTGSC_INTSTS_USB_ID) { + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + /* clear conn information */ + if (fsm->id) + fsm->b_conn = 0; + else + fsm->a_conn = 0; + + if (otg->host) + otg->host->is_b_host = fsm->id; + if (otg->gadget) + otg->gadget->is_a_peripheral = !fsm->id; + VDBG("ID int (ID is %d)\n", fsm->id); + + if (fsm->id) { /* switch to gadget */ + schedule_delayed_work( + &((struct fsl_otg *)dev_id)->otg_event, + 100); + } else { /* switch to host */ + cancel_delayed_work(& + ((struct fsl_otg *)dev_id)-> + otg_event); + fsl_otg_start_gadget(fsm, 0); + otg_drv_vbus(fsm, 1); + fsl_otg_start_host(fsm, 1); + } + return IRQ_HANDLED; + } + } + return IRQ_NONE; +} + +static struct otg_fsm_ops fsl_otg_ops = { + .chrg_vbus = fsl_otg_chrg_vbus, + .drv_vbus = fsl_otg_drv_vbus, + .loc_conn = fsl_otg_loc_conn, + .loc_sof = fsl_otg_loc_sof, + .start_pulse = fsl_otg_start_pulse, + + .add_timer = fsl_otg_add_timer, + .del_timer = fsl_otg_del_timer, + + .start_host = fsl_otg_start_host, + .start_gadget = fsl_otg_start_gadget, +}; + +/* Initialize the global variable fsl_otg_dev and request IRQ for OTG */ +static int fsl_otg_conf(struct platform_device *pdev) +{ + struct fsl_otg *fsl_otg_tc; + int status; + + if (fsl_otg_dev) + return 0; + + /* allocate space to fsl otg device */ + fsl_otg_tc = kzalloc(sizeof(struct fsl_otg), GFP_KERNEL); + if (!fsl_otg_tc) + return -ENOMEM; + + fsl_otg_tc->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); + if (!fsl_otg_tc->phy.otg) { + kfree(fsl_otg_tc); + return -ENOMEM; + } + + INIT_DELAYED_WORK(&fsl_otg_tc->otg_event, fsl_otg_event); + + INIT_LIST_HEAD(&active_timers); + status = fsl_otg_init_timers(&fsl_otg_tc->fsm); + if (status) { + pr_info("Couldn't init OTG timers\n"); + goto err; + } + spin_lock_init(&fsl_otg_tc->fsm.lock); + + /* Set OTG state machine operations */ + fsl_otg_tc->fsm.ops = &fsl_otg_ops; + + /* initialize the otg structure */ + fsl_otg_tc->phy.label = DRIVER_DESC; + fsl_otg_tc->phy.dev = &pdev->dev; + fsl_otg_tc->phy.set_power = fsl_otg_set_power; + + fsl_otg_tc->phy.otg->phy = &fsl_otg_tc->phy; + fsl_otg_tc->phy.otg->set_host = fsl_otg_set_host; + fsl_otg_tc->phy.otg->set_peripheral = fsl_otg_set_peripheral; + fsl_otg_tc->phy.otg->start_hnp = fsl_otg_start_hnp; + fsl_otg_tc->phy.otg->start_srp = fsl_otg_start_srp; + + fsl_otg_dev = fsl_otg_tc; + + /* Store the otg transceiver */ + status = usb_add_phy(&fsl_otg_tc->phy, USB_PHY_TYPE_USB2); + if (status) { + pr_warn(FSL_OTG_NAME ": unable to register OTG transceiver.\n"); + goto err; + } + + return 0; +err: + fsl_otg_uninit_timers(); + kfree(fsl_otg_tc->phy.otg); + kfree(fsl_otg_tc); + return status; +} + +/* OTG Initialization */ +int usb_otg_start(struct platform_device *pdev) +{ + struct fsl_otg *p_otg; + struct usb_phy *otg_trans = usb_get_phy(USB_PHY_TYPE_USB2); + struct otg_fsm *fsm; + int status; + struct resource *res; + u32 temp; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + p_otg = container_of(otg_trans, struct fsl_otg, phy); + fsm = &p_otg->fsm; + + /* Initialize the state machine structure with default values */ + SET_OTG_STATE(otg_trans, OTG_STATE_UNDEFINED); + fsm->otg = p_otg->phy.otg; + + /* We don't require predefined MEM/IRQ resource index */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + /* We don't request_mem_region here to enable resource sharing + * with host/device */ + + usb_dr_regs = ioremap(res->start, sizeof(struct usb_dr_mmap)); + p_otg->dr_mem_map = (struct usb_dr_mmap *)usb_dr_regs; + pdata->regs = (void *)usb_dr_regs; + + if (pdata->init && pdata->init(pdev) != 0) + return -EINVAL; + + if (pdata->big_endian_mmio) { + _fsl_readl = _fsl_readl_be; + _fsl_writel = _fsl_writel_be; + } else { + _fsl_readl = _fsl_readl_le; + _fsl_writel = _fsl_writel_le; + } + + /* request irq */ + p_otg->irq = platform_get_irq(pdev, 0); + status = request_irq(p_otg->irq, fsl_otg_isr, + IRQF_SHARED, driver_name, p_otg); + if (status) { + dev_dbg(p_otg->phy.dev, "can't get IRQ %d, error %d\n", + p_otg->irq, status); + iounmap(p_otg->dr_mem_map); + kfree(p_otg->phy.otg); + kfree(p_otg); + return status; + } + + /* stop the controller */ + temp = fsl_readl(&p_otg->dr_mem_map->usbcmd); + temp &= ~USB_CMD_RUN_STOP; + fsl_writel(temp, &p_otg->dr_mem_map->usbcmd); + + /* reset the controller */ + temp = fsl_readl(&p_otg->dr_mem_map->usbcmd); + temp |= USB_CMD_CTRL_RESET; + fsl_writel(temp, &p_otg->dr_mem_map->usbcmd); + + /* wait reset completed */ + while (fsl_readl(&p_otg->dr_mem_map->usbcmd) & USB_CMD_CTRL_RESET) + ; + + /* configure the VBUSHS as IDLE(both host and device) */ + temp = USB_MODE_STREAM_DISABLE | (pdata->es ? USB_MODE_ES : 0); + fsl_writel(temp, &p_otg->dr_mem_map->usbmode); + + /* configure PHY interface */ + temp = fsl_readl(&p_otg->dr_mem_map->portsc); + temp &= ~(PORTSC_PHY_TYPE_SEL | PORTSC_PTW); + switch (pdata->phy_mode) { + case FSL_USB2_PHY_ULPI: + temp |= PORTSC_PTS_ULPI; + break; + case FSL_USB2_PHY_UTMI_WIDE: + temp |= PORTSC_PTW_16BIT; + /* fall through */ + case FSL_USB2_PHY_UTMI: + temp |= PORTSC_PTS_UTMI; + /* fall through */ + default: + break; + } + fsl_writel(temp, &p_otg->dr_mem_map->portsc); + + if (pdata->have_sysif_regs) { + /* configure control enable IO output, big endian register */ + temp = __raw_readl(&p_otg->dr_mem_map->control); + temp |= USB_CTRL_IOENB; + __raw_writel(temp, &p_otg->dr_mem_map->control); + } + + /* disable all interrupt and clear all OTGSC status */ + temp = fsl_readl(&p_otg->dr_mem_map->otgsc); + temp &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; + temp |= OTGSC_INTERRUPT_STATUS_BITS_MASK | OTGSC_CTRL_VBUS_DISCHARGE; + fsl_writel(temp, &p_otg->dr_mem_map->otgsc); + + /* + * The identification (id) input is FALSE when a Mini-A plug is inserted + * in the devices Mini-AB receptacle. Otherwise, this input is TRUE. + * Also: record initial state of ID pin + */ + if (fsl_readl(&p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) { + p_otg->phy.state = OTG_STATE_UNDEFINED; + p_otg->fsm.id = 1; + } else { + p_otg->phy.state = OTG_STATE_A_IDLE; + p_otg->fsm.id = 0; + } + + DBG("initial ID pin=%d\n", p_otg->fsm.id); + + /* enable OTG ID pin interrupt */ + temp = fsl_readl(&p_otg->dr_mem_map->otgsc); + temp |= OTGSC_INTR_USB_ID_EN; + temp &= ~(OTGSC_CTRL_VBUS_DISCHARGE | OTGSC_INTR_1MS_TIMER_EN); + fsl_writel(temp, &p_otg->dr_mem_map->otgsc); + + return 0; +} + +/* + * state file in sysfs + */ +static int show_fsl_usb2_otg_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct otg_fsm *fsm = &fsl_otg_dev->fsm; + char *next = buf; + unsigned size = PAGE_SIZE; + unsigned long flags; + int t; + + spin_lock_irqsave(&fsm->lock, flags); + + /* basic driver infomation */ + t = scnprintf(next, size, + DRIVER_DESC "\n" "fsl_usb2_otg version: %s\n\n", + DRIVER_VERSION); + size -= t; + next += t; + + /* Registers */ + t = scnprintf(next, size, + "OTGSC: 0x%08x\n" + "PORTSC: 0x%08x\n" + "USBMODE: 0x%08x\n" + "USBCMD: 0x%08x\n" + "USBSTS: 0x%08x\n" + "USBINTR: 0x%08x\n", + fsl_readl(&usb_dr_regs->otgsc), + fsl_readl(&usb_dr_regs->portsc), + fsl_readl(&usb_dr_regs->usbmode), + fsl_readl(&usb_dr_regs->usbcmd), + fsl_readl(&usb_dr_regs->usbsts), + fsl_readl(&usb_dr_regs->usbintr)); + size -= t; + next += t; + + /* State */ + t = scnprintf(next, size, + "OTG state: %s\n\n", + usb_otg_state_string(fsl_otg_dev->phy.state)); + size -= t; + next += t; + + /* State Machine Variables */ + t = scnprintf(next, size, + "a_bus_req: %d\n" + "b_bus_req: %d\n" + "a_bus_resume: %d\n" + "a_bus_suspend: %d\n" + "a_conn: %d\n" + "a_sess_vld: %d\n" + "a_srp_det: %d\n" + "a_vbus_vld: %d\n" + "b_bus_resume: %d\n" + "b_bus_suspend: %d\n" + "b_conn: %d\n" + "b_se0_srp: %d\n" + "b_sess_end: %d\n" + "b_sess_vld: %d\n" + "id: %d\n", + fsm->a_bus_req, + fsm->b_bus_req, + fsm->a_bus_resume, + fsm->a_bus_suspend, + fsm->a_conn, + fsm->a_sess_vld, + fsm->a_srp_det, + fsm->a_vbus_vld, + fsm->b_bus_resume, + fsm->b_bus_suspend, + fsm->b_conn, + fsm->b_se0_srp, + fsm->b_sess_end, + fsm->b_sess_vld, + fsm->id); + size -= t; + next += t; + + spin_unlock_irqrestore(&fsm->lock, flags); + + return PAGE_SIZE - size; +} + +static DEVICE_ATTR(fsl_usb2_otg_state, S_IRUGO, show_fsl_usb2_otg_state, NULL); + + +/* Char driver interface to control some OTG input */ + +/* + * Handle some ioctl command, such as get otg + * status and set host suspend + */ +static long fsl_otg_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + u32 retval = 0; + + switch (cmd) { + case GET_OTG_STATUS: + retval = fsl_otg_dev->host_working; + break; + + case SET_A_SUSPEND_REQ: + fsl_otg_dev->fsm.a_suspend_req = arg; + break; + + case SET_A_BUS_DROP: + fsl_otg_dev->fsm.a_bus_drop = arg; + break; + + case SET_A_BUS_REQ: + fsl_otg_dev->fsm.a_bus_req = arg; + break; + + case SET_B_BUS_REQ: + fsl_otg_dev->fsm.b_bus_req = arg; + break; + + default: + break; + } + + otg_statemachine(&fsl_otg_dev->fsm); + + return retval; +} + +static int fsl_otg_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int fsl_otg_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations otg_fops = { + .owner = THIS_MODULE, + .llseek = NULL, + .read = NULL, + .write = NULL, + .unlocked_ioctl = fsl_otg_ioctl, + .open = fsl_otg_open, + .release = fsl_otg_release, +}; + +static int fsl_otg_probe(struct platform_device *pdev) +{ + int ret; + + if (!pdev->dev.platform_data) + return -ENODEV; + + /* configure the OTG */ + ret = fsl_otg_conf(pdev); + if (ret) { + dev_err(&pdev->dev, "Couldn't configure OTG module\n"); + return ret; + } + + /* start OTG */ + ret = usb_otg_start(pdev); + if (ret) { + dev_err(&pdev->dev, "Can't init FSL OTG device\n"); + return ret; + } + + ret = register_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME, &otg_fops); + if (ret) { + dev_err(&pdev->dev, "unable to register FSL OTG device\n"); + return ret; + } + + ret = device_create_file(&pdev->dev, &dev_attr_fsl_usb2_otg_state); + if (ret) + dev_warn(&pdev->dev, "Can't register sysfs attribute\n"); + + return ret; +} + +static int fsl_otg_remove(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + usb_remove_phy(&fsl_otg_dev->phy); + free_irq(fsl_otg_dev->irq, fsl_otg_dev); + + iounmap((void *)usb_dr_regs); + + fsl_otg_uninit_timers(); + kfree(fsl_otg_dev->phy.otg); + kfree(fsl_otg_dev); + + device_remove_file(&pdev->dev, &dev_attr_fsl_usb2_otg_state); + + unregister_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME); + + if (pdata->exit) + pdata->exit(pdev); + + return 0; +} + +struct platform_driver fsl_otg_driver = { + .probe = fsl_otg_probe, + .remove = fsl_otg_remove, + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(fsl_otg_driver); + +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-fsl-usb.h b/drivers/usb/phy/phy-fsl-usb.h new file mode 100644 index 00000000..e1859b8e --- /dev/null +++ b/drivers/usb/phy/phy-fsl-usb.h @@ -0,0 +1,406 @@ +/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "phy-fsm-usb.h" +#include +#include + +/* USB Command Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x1<<0) +#define USB_CMD_CTRL_RESET (0x1<<1) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x1<<4) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x1<<5) +#define USB_CMD_INT_AA_DOORBELL (0x1<<6) +#define USB_CMD_ASP (0x3<<8) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x1<<11) +#define USB_CMD_SUTW (0x1<<13) +#define USB_CMD_ATDTW (0x1<<14) +#define USB_CMD_ITC (0xFF<<16) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x0<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_512 (0x0<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_256 (0x0<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_128 (0x0<<15 | 0x3<<2) +#define USB_CMD_FRAME_SIZE_64 (0x1<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_32 (0x1<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_16 (0x1<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_8 (0x1<<15 | 0x3<<2) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x0<<8) +#define USB_CMD_ASP_01 (0x1<<8) +#define USB_CMD_ASP_10 (0x2<<8) +#define USB_CMD_ASP_11 (0x3<<8) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00<<16) +#define USB_CMD_ITC_1_MICRO_FRM (0x01<<16) +#define USB_CMD_ITC_2_MICRO_FRM (0x02<<16) +#define USB_CMD_ITC_4_MICRO_FRM (0x04<<16) +#define USB_CMD_ITC_8_MICRO_FRM (0x08<<16) +#define USB_CMD_ITC_16_MICRO_FRM (0x10<<16) +#define USB_CMD_ITC_32_MICRO_FRM (0x20<<16) +#define USB_CMD_ITC_64_MICRO_FRM (0x40<<16) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB Status Register Bit Masks */ +#define USB_STS_INT (0x1<<0) +#define USB_STS_ERR (0x1<<1) +#define USB_STS_PORT_CHANGE (0x1<<2) +#define USB_STS_FRM_LST_ROLL (0x1<<3) +#define USB_STS_SYS_ERR (0x1<<4) +#define USB_STS_IAA (0x1<<5) +#define USB_STS_RESET_RECEIVED (0x1<<6) +#define USB_STS_SOF (0x1<<7) +#define USB_STS_DCSUSPEND (0x1<<8) +#define USB_STS_HC_HALTED (0x1<<12) +#define USB_STS_RCL (0x1<<13) +#define USB_STS_PERIODIC_SCHEDULE (0x1<<14) +#define USB_STS_ASYNC_SCHEDULE (0x1<<15) + +/* USB Interrupt Enable Register Bit Masks */ +#define USB_INTR_INT_EN (0x1<<0) +#define USB_INTR_ERR_INT_EN (0x1<<1) +#define USB_INTR_PC_DETECT_EN (0x1<<2) +#define USB_INTR_FRM_LST_ROLL_EN (0x1<<3) +#define USB_INTR_SYS_ERR_EN (0x1<<4) +#define USB_INTR_ASYN_ADV_EN (0x1<<5) +#define USB_INTR_RESET_EN (0x1<<6) +#define USB_INTR_SOF_EN (0x1<<7) +#define USB_INTR_DEVICE_SUSPEND (0x1<<8) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0x7F<<25) +#define USB_DEVICE_ADDRESS_BIT_POS (25) +/* PORTSC Register Bit Masks,Only one PORT in OTG mode*/ +#define PORTSC_CURRENT_CONNECT_STATUS (0x1<<0) +#define PORTSC_CONNECT_STATUS_CHANGE (0x1<<1) +#define PORTSC_PORT_ENABLE (0x1<<2) +#define PORTSC_PORT_EN_DIS_CHANGE (0x1<<3) +#define PORTSC_OVER_CURRENT_ACT (0x1<<4) +#define PORTSC_OVER_CUURENT_CHG (0x1<<5) +#define PORTSC_PORT_FORCE_RESUME (0x1<<6) +#define PORTSC_PORT_SUSPEND (0x1<<7) +#define PORTSC_PORT_RESET (0x1<<8) +#define PORTSC_LINE_STATUS_BITS (0x3<<10) +#define PORTSC_PORT_POWER (0x1<<12) +#define PORTSC_PORT_INDICTOR_CTRL (0x3<<14) +#define PORTSC_PORT_TEST_CTRL (0xF<<16) +#define PORTSC_WAKE_ON_CONNECT_EN (0x1<<20) +#define PORTSC_WAKE_ON_CONNECT_DIS (0x1<<21) +#define PORTSC_WAKE_ON_OVER_CURRENT (0x1<<22) +#define PORTSC_PHY_LOW_POWER_SPD (0x1<<23) +#define PORTSC_PORT_FORCE_FULL_SPEED (0x1<<24) +#define PORTSC_PORT_SPEED_MASK (0x3<<26) +#define PORTSC_TRANSCEIVER_WIDTH (0x1<<28) +#define PORTSC_PHY_TYPE_SEL (0x3<<30) +/* bit 11-10 are line status */ +#define PORTSC_LINE_STATUS_SE0 (0x0<<10) +#define PORTSC_LINE_STATUS_JSTATE (0x1<<10) +#define PORTSC_LINE_STATUS_KSTATE (0x2<<10) +#define PORTSC_LINE_STATUS_UNDEF (0x3<<10) +#define PORTSC_LINE_STATUS_BIT_POS (10) + +/* bit 15-14 are port indicator control */ +#define PORTSC_PIC_OFF (0x0<<14) +#define PORTSC_PIC_AMBER (0x1<<14) +#define PORTSC_PIC_GREEN (0x2<<14) +#define PORTSC_PIC_UNDEF (0x3<<14) +#define PORTSC_PIC_BIT_POS (14) + +/* bit 19-16 are port test control */ +#define PORTSC_PTC_DISABLE (0x0<<16) +#define PORTSC_PTC_JSTATE (0x1<<16) +#define PORTSC_PTC_KSTATE (0x2<<16) +#define PORTSC_PTC_SEQNAK (0x3<<16) +#define PORTSC_PTC_PACKET (0x4<<16) +#define PORTSC_PTC_FORCE_EN (0x5<<16) +#define PORTSC_PTC_BIT_POS (16) + +/* bit 27-26 are port speed */ +#define PORTSC_PORT_SPEED_FULL (0x0<<26) +#define PORTSC_PORT_SPEED_LOW (0x1<<26) +#define PORTSC_PORT_SPEED_HIGH (0x2<<26) +#define PORTSC_PORT_SPEED_UNDEF (0x3<<26) +#define PORTSC_SPEED_BIT_POS (26) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSC_PTW (0x1<<28) +#define PORTSC_PTW_8BIT (0x0<<28) +#define PORTSC_PTW_16BIT (0x1<<28) + +/* bit 31-30 are port transceiver select */ +#define PORTSC_PTS_UTMI (0x0<<30) +#define PORTSC_PTS_ULPI (0x2<<30) +#define PORTSC_PTS_FSLS_SERIAL (0x3<<30) +#define PORTSC_PTS_BIT_POS (30) + +#define PORTSC_W1C_BITS \ + (PORTSC_CONNECT_STATUS_CHANGE | \ + PORTSC_PORT_EN_DIS_CHANGE | \ + PORTSC_OVER_CUURENT_CHG) + +/* OTG Status Control Register Bit Masks */ +#define OTGSC_CTRL_VBUS_DISCHARGE (0x1<<0) +#define OTGSC_CTRL_VBUS_CHARGE (0x1<<1) +#define OTGSC_CTRL_OTG_TERMINATION (0x1<<3) +#define OTGSC_CTRL_DATA_PULSING (0x1<<4) +#define OTGSC_CTRL_ID_PULL_EN (0x1<<5) +#define OTGSC_HA_DATA_PULSE (0x1<<6) +#define OTGSC_HA_BA (0x1<<7) +#define OTGSC_STS_USB_ID (0x1<<8) +#define OTGSC_STS_A_VBUS_VALID (0x1<<9) +#define OTGSC_STS_A_SESSION_VALID (0x1<<10) +#define OTGSC_STS_B_SESSION_VALID (0x1<<11) +#define OTGSC_STS_B_SESSION_END (0x1<<12) +#define OTGSC_STS_1MS_TOGGLE (0x1<<13) +#define OTGSC_STS_DATA_PULSING (0x1<<14) +#define OTGSC_INTSTS_USB_ID (0x1<<16) +#define OTGSC_INTSTS_A_VBUS_VALID (0x1<<17) +#define OTGSC_INTSTS_A_SESSION_VALID (0x1<<18) +#define OTGSC_INTSTS_B_SESSION_VALID (0x1<<19) +#define OTGSC_INTSTS_B_SESSION_END (0x1<<20) +#define OTGSC_INTSTS_1MS (0x1<<21) +#define OTGSC_INTSTS_DATA_PULSING (0x1<<22) +#define OTGSC_INTR_USB_ID_EN (0x1<<24) +#define OTGSC_INTR_A_VBUS_VALID_EN (0x1<<25) +#define OTGSC_INTR_A_SESSION_VALID_EN (0x1<<26) +#define OTGSC_INTR_B_SESSION_VALID_EN (0x1<<27) +#define OTGSC_INTR_B_SESSION_END_EN (0x1<<28) +#define OTGSC_INTR_1MS_TIMER_EN (0x1<<29) +#define OTGSC_INTR_DATA_PULSING_EN (0x1<<30) +#define OTGSC_INTSTS_MASK (0x00ff0000) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x0<<0) +#define USB_MODE_CTRL_MODE_DEVICE (0x2<<0) +#define USB_MODE_CTRL_MODE_HOST (0x3<<0) +#define USB_MODE_CTRL_MODE_RSV (0x1<<0) +#define USB_MODE_SETUP_LOCK_OFF (0x1<<3) +#define USB_MODE_STREAM_DISABLE (0x1<<4) +#define USB_MODE_ES (0x1<<2) /* Endian Select */ + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB (0x1<<2) +#define USB_CTRL_ULPI_INT0EN (0x1<<0) + +/* BCSR5 */ +#define BCSR5_INT_USB (0x02) + +/* USB module clk cfg */ +#define SCCR_OFFS (0xA08) +#define SCCR_USB_CLK_DISABLE (0x00000000) /* USB clk disable */ +#define SCCR_USB_MPHCM_11 (0x00c00000) +#define SCCR_USB_MPHCM_01 (0x00400000) +#define SCCR_USB_MPHCM_10 (0x00800000) +#define SCCR_USB_DRCM_11 (0x00300000) +#define SCCR_USB_DRCM_01 (0x00100000) +#define SCCR_USB_DRCM_10 (0x00200000) + +#define SICRL_OFFS (0x114) +#define SICRL_USB0 (0x40000000) +#define SICRL_USB1 (0x20000000) + +#define SICRH_OFFS (0x118) +#define SICRH_USB_UTMI (0x00020000) + +/* OTG interrupt enable bit masks */ +#define OTGSC_INTERRUPT_ENABLE_BITS_MASK \ + (OTGSC_INTR_USB_ID_EN | \ + OTGSC_INTR_1MS_TIMER_EN | \ + OTGSC_INTR_A_VBUS_VALID_EN | \ + OTGSC_INTR_A_SESSION_VALID_EN | \ + OTGSC_INTR_B_SESSION_VALID_EN | \ + OTGSC_INTR_B_SESSION_END_EN | \ + OTGSC_INTR_DATA_PULSING_EN) + +/* OTG interrupt status bit masks */ +#define OTGSC_INTERRUPT_STATUS_BITS_MASK \ + (OTGSC_INTSTS_USB_ID | \ + OTGSC_INTR_1MS_TIMER_EN | \ + OTGSC_INTSTS_A_VBUS_VALID | \ + OTGSC_INTSTS_A_SESSION_VALID | \ + OTGSC_INTSTS_B_SESSION_VALID | \ + OTGSC_INTSTS_B_SESSION_END | \ + OTGSC_INTSTS_DATA_PULSING) + +/* + * A-DEVICE timing constants + */ + +/* Wait for VBUS Rise */ +#define TA_WAIT_VRISE (100) /* a_wait_vrise 100 ms, section: 6.6.5.1 */ + +/* Wait for B-Connect */ +#define TA_WAIT_BCON (10000) /* a_wait_bcon > 1 sec, section: 6.6.5.2 + * This is only used to get out of + * OTG_STATE_A_WAIT_BCON state if there was + * no connection for these many milliseconds + */ + +/* A-Idle to B-Disconnect */ +/* It is necessary for this timer to be more than 750 ms because of a bug in OPT + * test 5.4 in which B OPT disconnects after 750 ms instead of 75ms as stated + * in the test description + */ +#define TA_AIDL_BDIS (5000) /* a_suspend minimum 200 ms, section: 6.6.5.3 */ + +/* B-Idle to A-Disconnect */ +#define TA_BIDL_ADIS (12) /* 3 to 200 ms */ + +/* B-device timing constants */ + + +/* Data-Line Pulse Time*/ +#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms, section:5.3.3 */ +#define TB_DATA_PLS_MIN (5) /* minimum 5 ms */ +#define TB_DATA_PLS_MAX (10) /* maximum 10 ms */ + +/* SRP Initiate Time */ +#define TB_SRP_INIT (100) /* b_srp_init,maximum 100 ms, section:5.3.8 */ + +/* SRP Fail Time */ +#define TB_SRP_FAIL (7000) /* b_srp_init,Fail time 5~30s, section:6.8.2.2*/ + +/* SRP result wait time */ +#define TB_SRP_WAIT (60) + +/* VBus time */ +#define TB_VBUS_PLS (30) /* time to keep vbus pulsing asserted */ + +/* Discharge time */ +/* This time should be less than 10ms. It varies from system to system. */ +#define TB_VBUS_DSCHRG (8) + +/* A-SE0 to B-Reset */ +#define TB_ASE0_BRST (20) /* b_wait_acon, mini 3.125 ms,section:6.8.2.4 */ + +/* A bus suspend timer before we can switch to b_wait_aconn */ +#define TB_A_SUSPEND (7) +#define TB_BUS_RESUME (12) + +/* SE0 Time Before SRP */ +#define TB_SE0_SRP (2) /* b_idle,minimum 2 ms, section:5.3.2 */ + +#define SET_OTG_STATE(otg_ptr, newstate) ((otg_ptr)->state = newstate) + +struct usb_dr_mmap { + /* Capability register */ + u8 res1[256]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u8 res2[20]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u8 res3[24]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u8 res4[4]; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u8 res5[4]; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u8 res6[8]; + u32 ulpiview; /* ULPI register access */ + u8 res7[12]; + u32 configflag; /* Configure Flag Register */ + u32 portsc; /* Port 1 Status and Control Register */ + u8 res8[28]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[6]; /* Endpoint Control Registers */ + u8 res9[552]; + u32 snoop1; + u32 snoop2; + u32 age_cnt_thresh; /* Age Count Threshold Register */ + u32 pri_ctrl; /* Priority Control Register */ + u32 si_ctrl; /* System Interface Control Register */ + u8 res10[236]; + u32 control; /* General Purpose Control Register */ +}; + +struct fsl_otg_timer { + unsigned long expires; /* Number of count increase to timeout */ + unsigned long count; /* Tick counter */ + void (*function)(unsigned long); /* Timeout function */ + unsigned long data; /* Data passed to function */ + struct list_head list; +}; + +inline struct fsl_otg_timer *otg_timer_initializer +(void (*function)(unsigned long), unsigned long expires, unsigned long data) +{ + struct fsl_otg_timer *timer; + + timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL); + if (!timer) + return NULL; + timer->function = function; + timer->expires = expires; + timer->data = data; + return timer; +} + +struct fsl_otg { + struct usb_phy phy; + struct otg_fsm fsm; + struct usb_dr_mmap *dr_mem_map; + struct delayed_work otg_event; + + /* used for usb host */ + struct work_struct work_wq; + u8 host_working; + + int irq; +}; + +struct fsl_otg_config { + u8 otg_port; +}; + +/* For SRP and HNP handle */ +#define FSL_OTG_MAJOR 240 +#define FSL_OTG_NAME "fsl-usb2-otg" +/* Command to OTG driver ioctl */ +#define OTG_IOCTL_MAGIC FSL_OTG_MAJOR +/* if otg work as host, it should return 1, otherwise return 0 */ +#define GET_OTG_STATUS _IOR(OTG_IOCTL_MAGIC, 1, int) +#define SET_A_SUSPEND_REQ _IOW(OTG_IOCTL_MAGIC, 2, int) +#define SET_A_BUS_DROP _IOW(OTG_IOCTL_MAGIC, 3, int) +#define SET_A_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 4, int) +#define SET_B_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 5, int) +#define GET_A_SUSPEND_REQ _IOR(OTG_IOCTL_MAGIC, 6, int) +#define GET_A_BUS_DROP _IOR(OTG_IOCTL_MAGIC, 7, int) +#define GET_A_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 8, int) +#define GET_B_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 9, int) + +void fsl_otg_add_timer(void *timer); +void fsl_otg_del_timer(void *timer); +void fsl_otg_pulse_vbus(void); diff --git a/drivers/usb/phy/phy-fsm-usb.c b/drivers/usb/phy/phy-fsm-usb.c new file mode 100644 index 00000000..7f459660 --- /dev/null +++ b/drivers/usb/phy/phy-fsm-usb.c @@ -0,0 +1,348 @@ +/* + * OTG Finite State Machine from OTG spec + * + * Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * + * Author: Li Yang + * Jerry Huang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "phy-fsm-usb.h" + +/* Change USB protocol when there is a protocol change */ +static int otg_set_protocol(struct otg_fsm *fsm, int protocol) +{ + int ret = 0; + + if (fsm->protocol != protocol) { + VDBG("Changing role fsm->protocol= %d; new protocol= %d\n", + fsm->protocol, protocol); + /* stop old protocol */ + if (fsm->protocol == PROTO_HOST) + ret = fsm->ops->start_host(fsm, 0); + else if (fsm->protocol == PROTO_GADGET) + ret = fsm->ops->start_gadget(fsm, 0); + if (ret) + return ret; + + /* start new protocol */ + if (protocol == PROTO_HOST) + ret = fsm->ops->start_host(fsm, 1); + else if (protocol == PROTO_GADGET) + ret = fsm->ops->start_gadget(fsm, 1); + if (ret) + return ret; + + fsm->protocol = protocol; + return 0; + } + + return 0; +} + +static int state_changed; + +/* Called when leaving a state. Do state clean up jobs here */ +void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state) +{ + switch (old_state) { + case OTG_STATE_B_IDLE: + otg_del_timer(fsm, b_se0_srp_tmr); + fsm->b_se0_srp = 0; + break; + case OTG_STATE_B_SRP_INIT: + fsm->b_srp_done = 0; + break; + case OTG_STATE_B_PERIPHERAL: + break; + case OTG_STATE_B_WAIT_ACON: + otg_del_timer(fsm, b_ase0_brst_tmr); + fsm->b_ase0_brst_tmout = 0; + break; + case OTG_STATE_B_HOST: + break; + case OTG_STATE_A_IDLE: + break; + case OTG_STATE_A_WAIT_VRISE: + otg_del_timer(fsm, a_wait_vrise_tmr); + fsm->a_wait_vrise_tmout = 0; + break; + case OTG_STATE_A_WAIT_BCON: + otg_del_timer(fsm, a_wait_bcon_tmr); + fsm->a_wait_bcon_tmout = 0; + break; + case OTG_STATE_A_HOST: + otg_del_timer(fsm, a_wait_enum_tmr); + break; + case OTG_STATE_A_SUSPEND: + otg_del_timer(fsm, a_aidl_bdis_tmr); + fsm->a_aidl_bdis_tmout = 0; + fsm->a_suspend_req = 0; + break; + case OTG_STATE_A_PERIPHERAL: + break; + case OTG_STATE_A_WAIT_VFALL: + otg_del_timer(fsm, a_wait_vrise_tmr); + break; + case OTG_STATE_A_VBUS_ERR: + break; + default: + break; + } +} + +/* Called when entering a state */ +int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) +{ + state_changed = 1; + if (fsm->otg->phy->state == new_state) + return 0; + VDBG("Set state: %s\n", usb_otg_state_string(new_state)); + otg_leave_state(fsm, fsm->otg->phy->state); + switch (new_state) { + case OTG_STATE_B_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, b_se0_srp_tmr); + break; + case OTG_STATE_B_SRP_INIT: + otg_start_pulse(fsm); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, b_srp_fail_tmr); + break; + case OTG_STATE_B_PERIPHERAL: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 1); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + break; + case OTG_STATE_B_WAIT_ACON: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, b_ase0_brst_tmr); + fsm->a_bus_suspend = 0; + break; + case OTG_STATE_B_HOST: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + usb_bus_start_enum(fsm->otg->host, + fsm->otg->host->otg_port); + break; + case OTG_STATE_A_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_WAIT_VRISE: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_wait_vrise_tmr); + break; + case OTG_STATE_A_WAIT_BCON: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_wait_bcon_tmr); + break; + case OTG_STATE_A_HOST: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + /* + * When HNP is triggered while a_bus_req = 0, a_host will + * suspend too fast to complete a_set_b_hnp_en + */ + if (!fsm->a_bus_req || fsm->a_suspend_req) + otg_add_timer(fsm, a_wait_enum_tmr); + break; + case OTG_STATE_A_SUSPEND: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_aidl_bdis_tmr); + + break; + case OTG_STATE_A_PERIPHERAL: + otg_loc_conn(fsm, 1); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + otg_drv_vbus(fsm, 1); + break; + case OTG_STATE_A_WAIT_VFALL: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_VBUS_ERR: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + break; + default: + break; + } + + fsm->otg->phy->state = new_state; + return 0; +} + +/* State change judgement */ +int otg_statemachine(struct otg_fsm *fsm) +{ + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&fsm->lock, flags); + + state = fsm->otg->phy->state; + state_changed = 0; + /* State machine state change judgement */ + + switch (state) { + case OTG_STATE_UNDEFINED: + VDBG("fsm->id = %d\n", fsm->id); + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_B_IDLE: + if (!fsm->id) + otg_set_state(fsm, OTG_STATE_A_IDLE); + else if (fsm->b_sess_vld && fsm->otg->gadget) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + else if (fsm->b_bus_req && fsm->b_sess_end && fsm->b_se0_srp) + otg_set_state(fsm, OTG_STATE_B_SRP_INIT); + break; + case OTG_STATE_B_SRP_INIT: + if (!fsm->id || fsm->b_srp_done) + otg_set_state(fsm, OTG_STATE_B_IDLE); + break; + case OTG_STATE_B_PERIPHERAL: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->b_bus_req && fsm->otg-> + gadget->b_hnp_enable && fsm->a_bus_suspend) + otg_set_state(fsm, OTG_STATE_B_WAIT_ACON); + break; + case OTG_STATE_B_WAIT_ACON: + if (fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_HOST); + else if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) { + fsm->b_ase0_brst_tmout = 0; + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + } + break; + case OTG_STATE_B_HOST: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->b_bus_req || !fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + break; + case OTG_STATE_A_IDLE: + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->a_bus_drop && (fsm->a_bus_req || fsm->a_srp_det)) + otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE); + break; + case OTG_STATE_A_WAIT_VRISE: + if (fsm->id || fsm->a_bus_drop || fsm->a_vbus_vld || + fsm->a_wait_vrise_tmout) { + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + } + break; + case OTG_STATE_A_WAIT_BCON: + if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + else if (fsm->b_conn) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id | fsm->a_bus_drop | fsm->a_wait_bcon_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + case OTG_STATE_A_HOST: + if ((!fsm->a_bus_req || fsm->a_suspend_req) && + fsm->otg->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_SUSPEND); + else if (fsm->id || !fsm->b_conn || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_SUSPEND: + if (!fsm->b_conn && fsm->otg->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_PERIPHERAL); + else if (!fsm->b_conn && !fsm->otg->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (fsm->a_bus_req || fsm->b_bus_resume) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_PERIPHERAL: + if (fsm->id || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (fsm->b_bus_suspend) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_WAIT_VFALL: + if (fsm->id || fsm->a_bus_req || (!fsm->a_sess_vld && + !fsm->b_conn)) + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_A_VBUS_ERR: + if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + default: + break; + } + spin_unlock_irqrestore(&fsm->lock, flags); + + VDBG("quit statemachine, changed = %d\n", state_changed); + return state_changed; +} diff --git a/drivers/usb/phy/phy-fsm-usb.h b/drivers/usb/phy/phy-fsm-usb.h new file mode 100644 index 00000000..c30a2e1d --- /dev/null +++ b/drivers/usb/phy/phy-fsm-usb.h @@ -0,0 +1,154 @@ +/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#undef DEBUG +#undef VERBOSE + +#ifdef DEBUG +#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt , \ + __func__, ## args) +#else +#define DBG(fmt, args...) do {} while (0) +#endif + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(stuff...) do {} while (0) +#endif + +#ifdef VERBOSE +#define MPC_LOC printk("Current Location [%s]:[%d]\n", __FILE__, __LINE__) +#else +#define MPC_LOC do {} while (0) +#endif + +#define PROTO_UNDEF (0) +#define PROTO_HOST (1) +#define PROTO_GADGET (2) + +/* OTG state machine according to the OTG spec */ +struct otg_fsm { + /* Input */ + int a_bus_resume; + int a_bus_suspend; + int a_conn; + int a_sess_vld; + int a_srp_det; + int a_vbus_vld; + int b_bus_resume; + int b_bus_suspend; + int b_conn; + int b_se0_srp; + int b_sess_end; + int b_sess_vld; + int id; + + /* Internal variables */ + int a_set_b_hnp_en; + int b_srp_done; + int b_hnp_enable; + + /* Timeout indicator for timers */ + int a_wait_vrise_tmout; + int a_wait_bcon_tmout; + int a_aidl_bdis_tmout; + int b_ase0_brst_tmout; + + /* Informative variables */ + int a_bus_drop; + int a_bus_req; + int a_clr_err; + int a_suspend_req; + int b_bus_req; + + /* Output */ + int drv_vbus; + int loc_conn; + int loc_sof; + + struct otg_fsm_ops *ops; + struct usb_otg *otg; + + /* Current usb protocol used: 0:undefine; 1:host; 2:client */ + int protocol; + spinlock_t lock; +}; + +struct otg_fsm_ops { + void (*chrg_vbus)(int on); + void (*drv_vbus)(int on); + void (*loc_conn)(int on); + void (*loc_sof)(int on); + void (*start_pulse)(void); + void (*add_timer)(void *timer); + void (*del_timer)(void *timer); + int (*start_host)(struct otg_fsm *fsm, int on); + int (*start_gadget)(struct otg_fsm *fsm, int on); +}; + + +static inline void otg_chrg_vbus(struct otg_fsm *fsm, int on) +{ + fsm->ops->chrg_vbus(on); +} + +static inline void otg_drv_vbus(struct otg_fsm *fsm, int on) +{ + if (fsm->drv_vbus != on) { + fsm->drv_vbus = on; + fsm->ops->drv_vbus(on); + } +} + +static inline void otg_loc_conn(struct otg_fsm *fsm, int on) +{ + if (fsm->loc_conn != on) { + fsm->loc_conn = on; + fsm->ops->loc_conn(on); + } +} + +static inline void otg_loc_sof(struct otg_fsm *fsm, int on) +{ + if (fsm->loc_sof != on) { + fsm->loc_sof = on; + fsm->ops->loc_sof(on); + } +} + +static inline void otg_start_pulse(struct otg_fsm *fsm) +{ + fsm->ops->start_pulse(); +} + +static inline void otg_add_timer(struct otg_fsm *fsm, void *timer) +{ + fsm->ops->add_timer(timer); +} + +static inline void otg_del_timer(struct otg_fsm *fsm, void *timer) +{ + fsm->ops->del_timer(timer); +} + +int otg_statemachine(struct otg_fsm *fsm); + +/* Defined by device specific driver, for different timer implementation */ +extern struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, + *a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr, + *a_wait_enum_tmr; diff --git a/drivers/usb/phy/phy-gpio-vbus-usb.c b/drivers/usb/phy/phy-gpio-vbus-usb.c new file mode 100644 index 00000000..8443335c --- /dev/null +++ b/drivers/usb/phy/phy-gpio-vbus-usb.c @@ -0,0 +1,420 @@ +/* + * gpio-vbus.c - simple GPIO VBUS sensing driver for B peripheral devices + * + * Copyright (c) 2008 Philipp Zabel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + + +/* + * A simple GPIO VBUS sensing driver for B peripheral only devices + * with internal transceivers. It can control a D+ pullup GPIO and + * a regulator to limit the current drawn from VBUS. + * + * Needs to be loaded before the UDC driver that will use it. + */ +struct gpio_vbus_data { + struct usb_phy phy; + struct device *dev; + struct regulator *vbus_draw; + int vbus_draw_enabled; + unsigned mA; + struct delayed_work work; + int vbus; + int irq; +}; + + +/* + * This driver relies on "both edges" triggering. VBUS has 100 msec to + * stabilize, so the peripheral controller driver may need to cope with + * some bouncing due to current surges (e.g. charging local capacitance) + * and contact chatter. + * + * REVISIT in desperate straits, toggling between rising and falling + * edges might be workable. + */ +#define VBUS_IRQ_FLAGS \ + (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING) + + +/* interface to regulator framework */ +static void set_vbus_draw(struct gpio_vbus_data *gpio_vbus, unsigned mA) +{ + struct regulator *vbus_draw = gpio_vbus->vbus_draw; + int enabled; + int ret; + + if (!vbus_draw) + return; + + enabled = gpio_vbus->vbus_draw_enabled; + if (mA) { + regulator_set_current_limit(vbus_draw, 0, 1000 * mA); + if (!enabled) { + ret = regulator_enable(vbus_draw); + if (ret < 0) + return; + gpio_vbus->vbus_draw_enabled = 1; + } + } else { + if (enabled) { + ret = regulator_disable(vbus_draw); + if (ret < 0) + return; + gpio_vbus->vbus_draw_enabled = 0; + } + } + gpio_vbus->mA = mA; +} + +static int is_vbus_powered(struct gpio_vbus_mach_info *pdata) +{ + int vbus; + + vbus = gpio_get_value(pdata->gpio_vbus); + if (pdata->gpio_vbus_inverted) + vbus = !vbus; + + return vbus; +} + +static void gpio_vbus_work(struct work_struct *work) +{ + struct gpio_vbus_data *gpio_vbus = + container_of(work, struct gpio_vbus_data, work.work); + struct gpio_vbus_mach_info *pdata = gpio_vbus->dev->platform_data; + int gpio, status, vbus; + + if (!gpio_vbus->phy.otg->gadget) + return; + + vbus = is_vbus_powered(pdata); + if ((vbus ^ gpio_vbus->vbus) == 0) + return; + gpio_vbus->vbus = vbus; + + /* Peripheral controllers which manage the pullup themselves won't have + * gpio_pullup configured here. If it's configured here, we'll do what + * isp1301_omap::b_peripheral() does and enable the pullup here... although + * that may complicate usb_gadget_{,dis}connect() support. + */ + gpio = pdata->gpio_pullup; + + if (vbus) { + status = USB_EVENT_VBUS; + gpio_vbus->phy.state = OTG_STATE_B_PERIPHERAL; + gpio_vbus->phy.last_event = status; + usb_gadget_vbus_connect(gpio_vbus->phy.otg->gadget); + + /* drawing a "unit load" is *always* OK, except for OTG */ + set_vbus_draw(gpio_vbus, 100); + + /* optionally enable D+ pullup */ + if (gpio_is_valid(gpio)) + gpio_set_value(gpio, !pdata->gpio_pullup_inverted); + + atomic_notifier_call_chain(&gpio_vbus->phy.notifier, + status, gpio_vbus->phy.otg->gadget); + } else { + /* optionally disable D+ pullup */ + if (gpio_is_valid(gpio)) + gpio_set_value(gpio, pdata->gpio_pullup_inverted); + + set_vbus_draw(gpio_vbus, 0); + + usb_gadget_vbus_disconnect(gpio_vbus->phy.otg->gadget); + status = USB_EVENT_NONE; + gpio_vbus->phy.state = OTG_STATE_B_IDLE; + gpio_vbus->phy.last_event = status; + + atomic_notifier_call_chain(&gpio_vbus->phy.notifier, + status, gpio_vbus->phy.otg->gadget); + } +} + +/* VBUS change IRQ handler */ +static irqreturn_t gpio_vbus_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; + struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); + struct usb_otg *otg = gpio_vbus->phy.otg; + + dev_dbg(&pdev->dev, "VBUS %s (gadget: %s)\n", + is_vbus_powered(pdata) ? "supplied" : "inactive", + otg->gadget ? otg->gadget->name : "none"); + + if (otg->gadget) + schedule_delayed_work(&gpio_vbus->work, msecs_to_jiffies(100)); + + return IRQ_HANDLED; +} + +/* OTG transceiver interface */ + +/* bind/unbind the peripheral controller */ +static int gpio_vbus_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct gpio_vbus_data *gpio_vbus; + struct gpio_vbus_mach_info *pdata; + struct platform_device *pdev; + int gpio; + + gpio_vbus = container_of(otg->phy, struct gpio_vbus_data, phy); + pdev = to_platform_device(gpio_vbus->dev); + pdata = gpio_vbus->dev->platform_data; + gpio = pdata->gpio_pullup; + + if (!gadget) { + dev_dbg(&pdev->dev, "unregistering gadget '%s'\n", + otg->gadget->name); + + /* optionally disable D+ pullup */ + if (gpio_is_valid(gpio)) + gpio_set_value(gpio, pdata->gpio_pullup_inverted); + + set_vbus_draw(gpio_vbus, 0); + + usb_gadget_vbus_disconnect(otg->gadget); + otg->phy->state = OTG_STATE_UNDEFINED; + + otg->gadget = NULL; + return 0; + } + + otg->gadget = gadget; + dev_dbg(&pdev->dev, "registered gadget '%s'\n", gadget->name); + + /* initialize connection state */ + gpio_vbus->vbus = 0; /* start with disconnected */ + gpio_vbus_irq(gpio_vbus->irq, pdev); + return 0; +} + +/* effective for B devices, ignored for A-peripheral */ +static int gpio_vbus_set_power(struct usb_phy *phy, unsigned mA) +{ + struct gpio_vbus_data *gpio_vbus; + + gpio_vbus = container_of(phy, struct gpio_vbus_data, phy); + + if (phy->state == OTG_STATE_B_PERIPHERAL) + set_vbus_draw(gpio_vbus, mA); + return 0; +} + +/* for non-OTG B devices: set/clear transceiver suspend mode */ +static int gpio_vbus_set_suspend(struct usb_phy *phy, int suspend) +{ + struct gpio_vbus_data *gpio_vbus; + + gpio_vbus = container_of(phy, struct gpio_vbus_data, phy); + + /* draw max 0 mA from vbus in suspend mode; or the previously + * recorded amount of current if not suspended + * + * NOTE: high powered configs (mA > 100) may draw up to 2.5 mA + * if they're wake-enabled ... we don't handle that yet. + */ + return gpio_vbus_set_power(phy, suspend ? 0 : gpio_vbus->mA); +} + +/* platform driver interface */ + +static int __init gpio_vbus_probe(struct platform_device *pdev) +{ + struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; + struct gpio_vbus_data *gpio_vbus; + struct resource *res; + int err, gpio, irq; + unsigned long irqflags; + + if (!pdata || !gpio_is_valid(pdata->gpio_vbus)) + return -EINVAL; + gpio = pdata->gpio_vbus; + + gpio_vbus = kzalloc(sizeof(struct gpio_vbus_data), GFP_KERNEL); + if (!gpio_vbus) + return -ENOMEM; + + gpio_vbus->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); + if (!gpio_vbus->phy.otg) { + kfree(gpio_vbus); + return -ENOMEM; + } + + platform_set_drvdata(pdev, gpio_vbus); + gpio_vbus->dev = &pdev->dev; + gpio_vbus->phy.label = "gpio-vbus"; + gpio_vbus->phy.dev = gpio_vbus->dev; + gpio_vbus->phy.set_power = gpio_vbus_set_power; + gpio_vbus->phy.set_suspend = gpio_vbus_set_suspend; + gpio_vbus->phy.state = OTG_STATE_UNDEFINED; + + gpio_vbus->phy.otg->phy = &gpio_vbus->phy; + gpio_vbus->phy.otg->set_peripheral = gpio_vbus_set_peripheral; + + err = gpio_request(gpio, "vbus_detect"); + if (err) { + dev_err(&pdev->dev, "can't request vbus gpio %d, err: %d\n", + gpio, err); + goto err_gpio; + } + gpio_direction_input(gpio); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) { + irq = res->start; + irqflags = (res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED; + } else { + irq = gpio_to_irq(gpio); + irqflags = VBUS_IRQ_FLAGS; + } + + gpio_vbus->irq = irq; + + /* if data line pullup is in use, initialize it to "not pulling up" */ + gpio = pdata->gpio_pullup; + if (gpio_is_valid(gpio)) { + err = gpio_request(gpio, "udc_pullup"); + if (err) { + dev_err(&pdev->dev, + "can't request pullup gpio %d, err: %d\n", + gpio, err); + gpio_free(pdata->gpio_vbus); + goto err_gpio; + } + gpio_direction_output(gpio, pdata->gpio_pullup_inverted); + } + + err = request_irq(irq, gpio_vbus_irq, irqflags, "vbus_detect", pdev); + if (err) { + dev_err(&pdev->dev, "can't request irq %i, err: %d\n", + irq, err); + goto err_irq; + } + + ATOMIC_INIT_NOTIFIER_HEAD(&gpio_vbus->phy.notifier); + + INIT_DELAYED_WORK(&gpio_vbus->work, gpio_vbus_work); + + gpio_vbus->vbus_draw = regulator_get(&pdev->dev, "vbus_draw"); + if (IS_ERR(gpio_vbus->vbus_draw)) { + dev_dbg(&pdev->dev, "can't get vbus_draw regulator, err: %ld\n", + PTR_ERR(gpio_vbus->vbus_draw)); + gpio_vbus->vbus_draw = NULL; + } + + /* only active when a gadget is registered */ + err = usb_add_phy(&gpio_vbus->phy, USB_PHY_TYPE_USB2); + if (err) { + dev_err(&pdev->dev, "can't register transceiver, err: %d\n", + err); + goto err_otg; + } + + device_init_wakeup(&pdev->dev, pdata->wakeup); + + return 0; +err_otg: + regulator_put(gpio_vbus->vbus_draw); + free_irq(irq, pdev); +err_irq: + if (gpio_is_valid(pdata->gpio_pullup)) + gpio_free(pdata->gpio_pullup); + gpio_free(pdata->gpio_vbus); +err_gpio: + kfree(gpio_vbus->phy.otg); + kfree(gpio_vbus); + return err; +} + +static int __exit gpio_vbus_remove(struct platform_device *pdev) +{ + struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); + struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; + int gpio = pdata->gpio_vbus; + + device_init_wakeup(&pdev->dev, 0); + cancel_delayed_work_sync(&gpio_vbus->work); + regulator_put(gpio_vbus->vbus_draw); + + usb_remove_phy(&gpio_vbus->phy); + + free_irq(gpio_vbus->irq, pdev); + if (gpio_is_valid(pdata->gpio_pullup)) + gpio_free(pdata->gpio_pullup); + gpio_free(gpio); + kfree(gpio_vbus->phy.otg); + kfree(gpio_vbus); + + return 0; +} + +#ifdef CONFIG_PM +static int gpio_vbus_pm_suspend(struct device *dev) +{ + struct gpio_vbus_data *gpio_vbus = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(gpio_vbus->irq); + + return 0; +} + +static int gpio_vbus_pm_resume(struct device *dev) +{ + struct gpio_vbus_data *gpio_vbus = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(gpio_vbus->irq); + + return 0; +} + +static const struct dev_pm_ops gpio_vbus_dev_pm_ops = { + .suspend = gpio_vbus_pm_suspend, + .resume = gpio_vbus_pm_resume, +}; +#endif + +/* NOTE: the gpio-vbus device may *NOT* be hotplugged */ + +MODULE_ALIAS("platform:gpio-vbus"); + +static struct platform_driver gpio_vbus_driver = { + .driver = { + .name = "gpio-vbus", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &gpio_vbus_dev_pm_ops, +#endif + }, + .remove = __exit_p(gpio_vbus_remove), +}; + +module_platform_driver_probe(gpio_vbus_driver, gpio_vbus_probe); + +MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver"); +MODULE_AUTHOR("Philipp Zabel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-isp1301-omap.c b/drivers/usb/phy/phy-isp1301-omap.c new file mode 100644 index 00000000..9201feb9 --- /dev/null +++ b/drivers/usb/phy/phy-isp1301-omap.c @@ -0,0 +1,1656 @@ +/* + * isp1301_omap - ISP 1301 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#ifndef DEBUG +#undef VERBOSE +#endif + + +#define DRIVER_VERSION "24 August 2004" +#define DRIVER_NAME (isp1301_driver.driver.name) + +MODULE_DESCRIPTION("ISP1301 USB OTG Transceiver Driver"); +MODULE_LICENSE("GPL"); + +struct isp1301 { + struct usb_phy phy; + struct i2c_client *client; + void (*i2c_release)(struct device *dev); + + int irq_type; + + u32 last_otg_ctrl; + unsigned working:1; + + struct timer_list timer; + + /* use keventd context to change the state for us */ + struct work_struct work; + + unsigned long todo; +# define WORK_UPDATE_ISP 0 /* update ISP from OTG */ +# define WORK_UPDATE_OTG 1 /* update OTG from ISP */ +# define WORK_HOST_RESUME 4 /* resume host */ +# define WORK_TIMER 6 /* timer fired */ +# define WORK_STOP 7 /* don't resubmit */ +}; + + +/* bits in OTG_CTRL */ + +#define OTG_XCEIV_OUTPUTS \ + (OTG_ASESSVLD|OTG_BSESSEND|OTG_BSESSVLD|OTG_VBUSVLD|OTG_ID) +#define OTG_XCEIV_INPUTS \ + (OTG_PULLDOWN|OTG_PULLUP|OTG_DRV_VBUS|OTG_PD_VBUS|OTG_PU_VBUS|OTG_PU_ID) +#define OTG_CTRL_BITS \ + (OTG_A_BUSREQ|OTG_A_SETB_HNPEN|OTG_B_BUSREQ|OTG_B_HNPEN|OTG_BUSDROP) + /* and OTG_PULLUP is sometimes written */ + +#define OTG_CTRL_MASK (OTG_DRIVER_SEL| \ + OTG_XCEIV_OUTPUTS|OTG_XCEIV_INPUTS| \ + OTG_CTRL_BITS) + + +/*-------------------------------------------------------------------------*/ + +/* board-specific PM hooks */ + +#if defined(CONFIG_MACH_OMAP_H2) || defined(CONFIG_MACH_OMAP_H3) + +#if defined(CONFIG_TPS65010) || defined(CONFIG_TPS65010_MODULE) + +#include + +#else + +static inline int tps65010_set_vbus_draw(unsigned mA) +{ + pr_debug("tps65010: draw %d mA (STUB)\n", mA); + return 0; +} + +#endif + +static void enable_vbus_draw(struct isp1301 *isp, unsigned mA) +{ + int status = tps65010_set_vbus_draw(mA); + if (status < 0) + pr_debug(" VBUS %d mA error %d\n", mA, status); +} + +#else + +static void enable_vbus_draw(struct isp1301 *isp, unsigned mA) +{ + /* H4 controls this by DIP switch S2.4; no soft control. + * ON means the charger is always enabled. Leave it OFF + * unless the OTG port is used only in B-peripheral mode. + */ +} + +#endif + +static void enable_vbus_source(struct isp1301 *isp) +{ + /* this board won't supply more than 8mA vbus power. + * some boards can switch a 100ma "unit load" (or more). + */ +} + + +/* products will deliver OTG messages with LEDs, GUI, etc */ +static inline void notresponding(struct isp1301 *isp) +{ + printk(KERN_NOTICE "OTG device not responding.\n"); +} + + +/*-------------------------------------------------------------------------*/ + +static struct i2c_driver isp1301_driver; + +/* smbus apis are used for portability */ + +static inline u8 +isp1301_get_u8(struct isp1301 *isp, u8 reg) +{ + return i2c_smbus_read_byte_data(isp->client, reg + 0); +} + +static inline int +isp1301_get_u16(struct isp1301 *isp, u8 reg) +{ + return i2c_smbus_read_word_data(isp->client, reg); +} + +static inline int +isp1301_set_bits(struct isp1301 *isp, u8 reg, u8 bits) +{ + return i2c_smbus_write_byte_data(isp->client, reg + 0, bits); +} + +static inline int +isp1301_clear_bits(struct isp1301 *isp, u8 reg, u8 bits) +{ + return i2c_smbus_write_byte_data(isp->client, reg + 1, bits); +} + +/*-------------------------------------------------------------------------*/ + +/* identification */ +#define ISP1301_VENDOR_ID 0x00 /* u16 read */ +#define ISP1301_PRODUCT_ID 0x02 /* u16 read */ +#define ISP1301_BCD_DEVICE 0x14 /* u16 read */ + +#define I2C_VENDOR_ID_PHILIPS 0x04cc +#define I2C_PRODUCT_ID_PHILIPS_1301 0x1301 + +/* operational registers */ +#define ISP1301_MODE_CONTROL_1 0x04 /* u8 read, set, +1 clear */ +# define MC1_SPEED (1 << 0) +# define MC1_SUSPEND (1 << 1) +# define MC1_DAT_SE0 (1 << 2) +# define MC1_TRANSPARENT (1 << 3) +# define MC1_BDIS_ACON_EN (1 << 4) +# define MC1_OE_INT_EN (1 << 5) +# define MC1_UART_EN (1 << 6) +# define MC1_MASK 0x7f +#define ISP1301_MODE_CONTROL_2 0x12 /* u8 read, set, +1 clear */ +# define MC2_GLOBAL_PWR_DN (1 << 0) +# define MC2_SPD_SUSP_CTRL (1 << 1) +# define MC2_BI_DI (1 << 2) +# define MC2_TRANSP_BDIR0 (1 << 3) +# define MC2_TRANSP_BDIR1 (1 << 4) +# define MC2_AUDIO_EN (1 << 5) +# define MC2_PSW_EN (1 << 6) +# define MC2_EN2V7 (1 << 7) +#define ISP1301_OTG_CONTROL_1 0x06 /* u8 read, set, +1 clear */ +# define OTG1_DP_PULLUP (1 << 0) +# define OTG1_DM_PULLUP (1 << 1) +# define OTG1_DP_PULLDOWN (1 << 2) +# define OTG1_DM_PULLDOWN (1 << 3) +# define OTG1_ID_PULLDOWN (1 << 4) +# define OTG1_VBUS_DRV (1 << 5) +# define OTG1_VBUS_DISCHRG (1 << 6) +# define OTG1_VBUS_CHRG (1 << 7) +#define ISP1301_OTG_STATUS 0x10 /* u8 readonly */ +# define OTG_B_SESS_END (1 << 6) +# define OTG_B_SESS_VLD (1 << 7) + +#define ISP1301_INTERRUPT_SOURCE 0x08 /* u8 read */ +#define ISP1301_INTERRUPT_LATCH 0x0A /* u8 read, set, +1 clear */ + +#define ISP1301_INTERRUPT_FALLING 0x0C /* u8 read, set, +1 clear */ +#define ISP1301_INTERRUPT_RISING 0x0E /* u8 read, set, +1 clear */ + +/* same bitfields in all interrupt registers */ +# define INTR_VBUS_VLD (1 << 0) +# define INTR_SESS_VLD (1 << 1) +# define INTR_DP_HI (1 << 2) +# define INTR_ID_GND (1 << 3) +# define INTR_DM_HI (1 << 4) +# define INTR_ID_FLOAT (1 << 5) +# define INTR_BDIS_ACON (1 << 6) +# define INTR_CR_INT (1 << 7) + +/*-------------------------------------------------------------------------*/ + +static inline const char *state_name(struct isp1301 *isp) +{ + return usb_otg_state_string(isp->phy.state); +} + +/*-------------------------------------------------------------------------*/ + +/* NOTE: some of this ISP1301 setup is specific to H2 boards; + * not everything is guarded by board-specific checks, or even using + * omap_usb_config data to deduce MC1_DAT_SE0 and MC2_BI_DI. + * + * ALSO: this currently doesn't use ISP1301 low-power modes + * while OTG is running. + */ + +static void power_down(struct isp1301 *isp) +{ + isp->phy.state = OTG_STATE_UNDEFINED; + + // isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN); + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND); + + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_ID_PULLDOWN); + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); +} + +static void power_up(struct isp1301 *isp) +{ + // isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN); + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND); + + /* do this only when cpu is driving transceiver, + * so host won't see a low speed device... + */ + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); +} + +#define NO_HOST_SUSPEND + +static int host_suspend(struct isp1301 *isp) +{ +#ifdef NO_HOST_SUSPEND + return 0; +#else + struct device *dev; + + if (!isp->phy.otg->host) + return -ENODEV; + + /* Currently ASSUMES only the OTG port matters; + * other ports could be active... + */ + dev = isp->phy.otg->host->controller; + return dev->driver->suspend(dev, 3, 0); +#endif +} + +static int host_resume(struct isp1301 *isp) +{ +#ifdef NO_HOST_SUSPEND + return 0; +#else + struct device *dev; + + if (!isp->phy.otg->host) + return -ENODEV; + + dev = isp->phy.otg->host->controller; + return dev->driver->resume(dev, 0); +#endif +} + +static int gadget_suspend(struct isp1301 *isp) +{ + isp->phy.otg->gadget->b_hnp_enable = 0; + isp->phy.otg->gadget->a_hnp_support = 0; + isp->phy.otg->gadget->a_alt_hnp_support = 0; + return usb_gadget_vbus_disconnect(isp->phy.otg->gadget); +} + +/*-------------------------------------------------------------------------*/ + +#define TIMER_MINUTES 10 +#define TIMER_JIFFIES (TIMER_MINUTES * 60 * HZ) + +/* Almost all our I2C messaging comes from a work queue's task context. + * NOTE: guaranteeing certain response times might mean we shouldn't + * share keventd's work queue; a realtime task might be safest. + */ +static void isp1301_defer_work(struct isp1301 *isp, int work) +{ + int status; + + if (isp && !test_and_set_bit(work, &isp->todo)) { + (void) get_device(&isp->client->dev); + status = schedule_work(&isp->work); + if (!status && !isp->working) + dev_vdbg(&isp->client->dev, + "work item %d may be lost\n", work); + } +} + +/* called from irq handlers */ +static void a_idle(struct isp1301 *isp, const char *tag) +{ + u32 l; + + if (isp->phy.state == OTG_STATE_A_IDLE) + return; + + isp->phy.otg->default_a = 1; + if (isp->phy.otg->host) { + isp->phy.otg->host->is_b_host = 0; + host_suspend(isp); + } + if (isp->phy.otg->gadget) { + isp->phy.otg->gadget->is_a_peripheral = 1; + gadget_suspend(isp); + } + isp->phy.state = OTG_STATE_A_IDLE; + l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; + omap_writel(l, OTG_CTRL); + isp->last_otg_ctrl = l; + pr_debug(" --> %s/%s\n", state_name(isp), tag); +} + +/* called from irq handlers */ +static void b_idle(struct isp1301 *isp, const char *tag) +{ + u32 l; + + if (isp->phy.state == OTG_STATE_B_IDLE) + return; + + isp->phy.otg->default_a = 0; + if (isp->phy.otg->host) { + isp->phy.otg->host->is_b_host = 1; + host_suspend(isp); + } + if (isp->phy.otg->gadget) { + isp->phy.otg->gadget->is_a_peripheral = 0; + gadget_suspend(isp); + } + isp->phy.state = OTG_STATE_B_IDLE; + l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; + omap_writel(l, OTG_CTRL); + isp->last_otg_ctrl = l; + pr_debug(" --> %s/%s\n", state_name(isp), tag); +} + +static void +dump_regs(struct isp1301 *isp, const char *label) +{ +#ifdef DEBUG + u8 ctrl = isp1301_get_u8(isp, ISP1301_OTG_CONTROL_1); + u8 status = isp1301_get_u8(isp, ISP1301_OTG_STATUS); + u8 src = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE); + + pr_debug("otg: %06x, %s %s, otg/%02x stat/%02x.%02x\n", + omap_readl(OTG_CTRL), label, state_name(isp), + ctrl, status, src); + /* mode control and irq enables don't change much */ +#endif +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_OTG + +/* + * The OMAP OTG controller handles most of the OTG state transitions. + * + * We translate isp1301 outputs (mostly voltage comparator status) into + * OTG inputs; OTG outputs (mostly pullup/pulldown controls) and HNP state + * flags into isp1301 inputs ... and infer state transitions. + */ + +#ifdef VERBOSE + +static void check_state(struct isp1301 *isp, const char *tag) +{ + enum usb_otg_state state = OTG_STATE_UNDEFINED; + u8 fsm = omap_readw(OTG_TEST) & 0x0ff; + unsigned extra = 0; + + switch (fsm) { + + /* default-b */ + case 0x0: + state = OTG_STATE_B_IDLE; + break; + case 0x3: + case 0x7: + extra = 1; + case 0x1: + state = OTG_STATE_B_PERIPHERAL; + break; + case 0x11: + state = OTG_STATE_B_SRP_INIT; + break; + + /* extra dual-role default-b states */ + case 0x12: + case 0x13: + case 0x16: + extra = 1; + case 0x17: + state = OTG_STATE_B_WAIT_ACON; + break; + case 0x34: + state = OTG_STATE_B_HOST; + break; + + /* default-a */ + case 0x36: + state = OTG_STATE_A_IDLE; + break; + case 0x3c: + state = OTG_STATE_A_WAIT_VFALL; + break; + case 0x7d: + state = OTG_STATE_A_VBUS_ERR; + break; + case 0x9e: + case 0x9f: + extra = 1; + case 0x89: + state = OTG_STATE_A_PERIPHERAL; + break; + case 0xb7: + state = OTG_STATE_A_WAIT_VRISE; + break; + case 0xb8: + state = OTG_STATE_A_WAIT_BCON; + break; + case 0xb9: + state = OTG_STATE_A_HOST; + break; + case 0xba: + state = OTG_STATE_A_SUSPEND; + break; + default: + break; + } + if (isp->phy.state == state && !extra) + return; + pr_debug("otg: %s FSM %s/%02x, %s, %06x\n", tag, + usb_otg_state_string(state), fsm, state_name(isp), + omap_readl(OTG_CTRL)); +} + +#else + +static inline void check_state(struct isp1301 *isp, const char *tag) { } + +#endif + +/* outputs from ISP1301_INTERRUPT_SOURCE */ +static void update_otg1(struct isp1301 *isp, u8 int_src) +{ + u32 otg_ctrl; + + otg_ctrl = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + otg_ctrl &= ~OTG_XCEIV_INPUTS; + otg_ctrl &= ~(OTG_ID|OTG_ASESSVLD|OTG_VBUSVLD); + + if (int_src & INTR_SESS_VLD) + otg_ctrl |= OTG_ASESSVLD; + else if (isp->phy.state == OTG_STATE_A_WAIT_VFALL) { + a_idle(isp, "vfall"); + otg_ctrl &= ~OTG_CTRL_BITS; + } + if (int_src & INTR_VBUS_VLD) + otg_ctrl |= OTG_VBUSVLD; + if (int_src & INTR_ID_GND) { /* default-A */ + if (isp->phy.state == OTG_STATE_B_IDLE + || isp->phy.state + == OTG_STATE_UNDEFINED) { + a_idle(isp, "init"); + return; + } + } else { /* default-B */ + otg_ctrl |= OTG_ID; + if (isp->phy.state == OTG_STATE_A_IDLE + || isp->phy.state == OTG_STATE_UNDEFINED) { + b_idle(isp, "init"); + return; + } + } + omap_writel(otg_ctrl, OTG_CTRL); +} + +/* outputs from ISP1301_OTG_STATUS */ +static void update_otg2(struct isp1301 *isp, u8 otg_status) +{ + u32 otg_ctrl; + + otg_ctrl = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + otg_ctrl &= ~OTG_XCEIV_INPUTS; + otg_ctrl &= ~(OTG_BSESSVLD | OTG_BSESSEND); + if (otg_status & OTG_B_SESS_VLD) + otg_ctrl |= OTG_BSESSVLD; + else if (otg_status & OTG_B_SESS_END) + otg_ctrl |= OTG_BSESSEND; + omap_writel(otg_ctrl, OTG_CTRL); +} + +/* inputs going to ISP1301 */ +static void otg_update_isp(struct isp1301 *isp) +{ + u32 otg_ctrl, otg_change; + u8 set = OTG1_DM_PULLDOWN, clr = OTG1_DM_PULLUP; + + otg_ctrl = omap_readl(OTG_CTRL); + otg_change = otg_ctrl ^ isp->last_otg_ctrl; + isp->last_otg_ctrl = otg_ctrl; + otg_ctrl = otg_ctrl & OTG_XCEIV_INPUTS; + + switch (isp->phy.state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_SRP_INIT: + if (!(otg_ctrl & OTG_PULLUP)) { + // if (otg_ctrl & OTG_B_HNPEN) { + if (isp->phy.otg->gadget->b_hnp_enable) { + isp->phy.state = OTG_STATE_B_WAIT_ACON; + pr_debug(" --> b_wait_acon\n"); + } + goto pulldown; + } +pullup: + set |= OTG1_DP_PULLUP; + clr |= OTG1_DP_PULLDOWN; + break; + case OTG_STATE_A_SUSPEND: + case OTG_STATE_A_PERIPHERAL: + if (otg_ctrl & OTG_PULLUP) + goto pullup; + /* FALLTHROUGH */ + // case OTG_STATE_B_WAIT_ACON: + default: +pulldown: + set |= OTG1_DP_PULLDOWN; + clr |= OTG1_DP_PULLUP; + break; + } + +# define toggle(OTG,ISP) do { \ + if (otg_ctrl & OTG) set |= ISP; \ + else clr |= ISP; \ + } while (0) + + if (!(isp->phy.otg->host)) + otg_ctrl &= ~OTG_DRV_VBUS; + + switch (isp->phy.state) { + case OTG_STATE_A_SUSPEND: + if (otg_ctrl & OTG_DRV_VBUS) { + set |= OTG1_VBUS_DRV; + break; + } + /* HNP failed for some reason (A_AIDL_BDIS timeout) */ + notresponding(isp); + + /* FALLTHROUGH */ + case OTG_STATE_A_VBUS_ERR: + isp->phy.state = OTG_STATE_A_WAIT_VFALL; + pr_debug(" --> a_wait_vfall\n"); + /* FALLTHROUGH */ + case OTG_STATE_A_WAIT_VFALL: + /* FIXME usbcore thinks port power is still on ... */ + clr |= OTG1_VBUS_DRV; + break; + case OTG_STATE_A_IDLE: + if (otg_ctrl & OTG_DRV_VBUS) { + isp->phy.state = OTG_STATE_A_WAIT_VRISE; + pr_debug(" --> a_wait_vrise\n"); + } + /* FALLTHROUGH */ + default: + toggle(OTG_DRV_VBUS, OTG1_VBUS_DRV); + } + + toggle(OTG_PU_VBUS, OTG1_VBUS_CHRG); + toggle(OTG_PD_VBUS, OTG1_VBUS_DISCHRG); + +# undef toggle + + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, set); + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, clr); + + /* HNP switch to host or peripheral; and SRP */ + if (otg_change & OTG_PULLUP) { + u32 l; + + switch (isp->phy.state) { + case OTG_STATE_B_IDLE: + if (clr & OTG1_DP_PULLUP) + break; + isp->phy.state = OTG_STATE_B_PERIPHERAL; + pr_debug(" --> b_peripheral\n"); + break; + case OTG_STATE_A_SUSPEND: + if (clr & OTG1_DP_PULLUP) + break; + isp->phy.state = OTG_STATE_A_PERIPHERAL; + pr_debug(" --> a_peripheral\n"); + break; + default: + break; + } + l = omap_readl(OTG_CTRL); + l |= OTG_PULLUP; + omap_writel(l, OTG_CTRL); + } + + check_state(isp, __func__); + dump_regs(isp, "otg->isp1301"); +} + +static irqreturn_t omap_otg_irq(int irq, void *_isp) +{ + u16 otg_irq = omap_readw(OTG_IRQ_SRC); + u32 otg_ctrl; + int ret = IRQ_NONE; + struct isp1301 *isp = _isp; + struct usb_otg *otg = isp->phy.otg; + + /* update ISP1301 transceiver from OTG controller */ + if (otg_irq & OPRT_CHG) { + omap_writew(OPRT_CHG, OTG_IRQ_SRC); + isp1301_defer_work(isp, WORK_UPDATE_ISP); + ret = IRQ_HANDLED; + + /* SRP to become b_peripheral failed */ + } else if (otg_irq & B_SRP_TMROUT) { + pr_debug("otg: B_SRP_TIMEOUT, %06x\n", omap_readl(OTG_CTRL)); + notresponding(isp); + + /* gadget drivers that care should monitor all kinds of + * remote wakeup (SRP, normal) using their own timer + * to give "check cable and A-device" messages. + */ + if (isp->phy.state == OTG_STATE_B_SRP_INIT) + b_idle(isp, "srp_timeout"); + + omap_writew(B_SRP_TMROUT, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* HNP to become b_host failed */ + } else if (otg_irq & B_HNP_FAIL) { + pr_debug("otg: %s B_HNP_FAIL, %06x\n", + state_name(isp), omap_readl(OTG_CTRL)); + notresponding(isp); + + otg_ctrl = omap_readl(OTG_CTRL); + otg_ctrl |= OTG_BUSDROP; + otg_ctrl &= OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; + omap_writel(otg_ctrl, OTG_CTRL); + + /* subset of b_peripheral()... */ + isp->phy.state = OTG_STATE_B_PERIPHERAL; + pr_debug(" --> b_peripheral\n"); + + omap_writew(B_HNP_FAIL, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* detect SRP from B-device ... */ + } else if (otg_irq & A_SRP_DETECT) { + pr_debug("otg: %s SRP_DETECT, %06x\n", + state_name(isp), omap_readl(OTG_CTRL)); + + isp1301_defer_work(isp, WORK_UPDATE_OTG); + switch (isp->phy.state) { + case OTG_STATE_A_IDLE: + if (!otg->host) + break; + isp1301_defer_work(isp, WORK_HOST_RESUME); + otg_ctrl = omap_readl(OTG_CTRL); + otg_ctrl |= OTG_A_BUSREQ; + otg_ctrl &= ~(OTG_BUSDROP|OTG_B_BUSREQ) + & ~OTG_XCEIV_INPUTS + & OTG_CTRL_MASK; + omap_writel(otg_ctrl, OTG_CTRL); + break; + default: + break; + } + + omap_writew(A_SRP_DETECT, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* timer expired: T(a_wait_bcon) and maybe T(a_wait_vrise) + * we don't track them separately + */ + } else if (otg_irq & A_REQ_TMROUT) { + otg_ctrl = omap_readl(OTG_CTRL); + pr_info("otg: BCON_TMOUT from %s, %06x\n", + state_name(isp), otg_ctrl); + notresponding(isp); + + otg_ctrl |= OTG_BUSDROP; + otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; + omap_writel(otg_ctrl, OTG_CTRL); + isp->phy.state = OTG_STATE_A_WAIT_VFALL; + + omap_writew(A_REQ_TMROUT, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* A-supplied voltage fell too low; overcurrent */ + } else if (otg_irq & A_VBUS_ERR) { + otg_ctrl = omap_readl(OTG_CTRL); + printk(KERN_ERR "otg: %s, VBUS_ERR %04x ctrl %06x\n", + state_name(isp), otg_irq, otg_ctrl); + + otg_ctrl |= OTG_BUSDROP; + otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; + omap_writel(otg_ctrl, OTG_CTRL); + isp->phy.state = OTG_STATE_A_VBUS_ERR; + + omap_writew(A_VBUS_ERR, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + /* switch driver; the transceiver code activates it, + * ungating the udc clock or resuming OHCI. + */ + } else if (otg_irq & DRIVER_SWITCH) { + int kick = 0; + + otg_ctrl = omap_readl(OTG_CTRL); + printk(KERN_NOTICE "otg: %s, SWITCH to %s, ctrl %06x\n", + state_name(isp), + (otg_ctrl & OTG_DRIVER_SEL) + ? "gadget" : "host", + otg_ctrl); + isp1301_defer_work(isp, WORK_UPDATE_ISP); + + /* role is peripheral */ + if (otg_ctrl & OTG_DRIVER_SEL) { + switch (isp->phy.state) { + case OTG_STATE_A_IDLE: + b_idle(isp, __func__); + break; + default: + break; + } + isp1301_defer_work(isp, WORK_UPDATE_ISP); + + /* role is host */ + } else { + if (!(otg_ctrl & OTG_ID)) { + otg_ctrl &= OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; + omap_writel(otg_ctrl | OTG_A_BUSREQ, OTG_CTRL); + } + + if (otg->host) { + switch (isp->phy.state) { + case OTG_STATE_B_WAIT_ACON: + isp->phy.state = OTG_STATE_B_HOST; + pr_debug(" --> b_host\n"); + kick = 1; + break; + case OTG_STATE_A_WAIT_BCON: + isp->phy.state = OTG_STATE_A_HOST; + pr_debug(" --> a_host\n"); + break; + case OTG_STATE_A_PERIPHERAL: + isp->phy.state = OTG_STATE_A_WAIT_BCON; + pr_debug(" --> a_wait_bcon\n"); + break; + default: + break; + } + isp1301_defer_work(isp, WORK_HOST_RESUME); + } + } + + omap_writew(DRIVER_SWITCH, OTG_IRQ_SRC); + ret = IRQ_HANDLED; + + if (kick) + usb_bus_start_enum(otg->host, otg->host->otg_port); + } + + check_state(isp, __func__); + return ret; +} + +static struct platform_device *otg_dev; + +static int isp1301_otg_init(struct isp1301 *isp) +{ + u32 l; + + if (!otg_dev) + return -ENODEV; + + dump_regs(isp, __func__); + /* some of these values are board-specific... */ + l = omap_readl(OTG_SYSCON_2); + l |= OTG_EN + /* for B-device: */ + | SRP_GPDATA /* 9msec Bdev D+ pulse */ + | SRP_GPDVBUS /* discharge after VBUS pulse */ + // | (3 << 24) /* 2msec VBUS pulse */ + /* for A-device: */ + | (0 << 20) /* 200ms nominal A_WAIT_VRISE timer */ + | SRP_DPW /* detect 167+ns SRP pulses */ + | SRP_DATA | SRP_VBUS /* accept both kinds of SRP pulse */ + ; + omap_writel(l, OTG_SYSCON_2); + + update_otg1(isp, isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE)); + update_otg2(isp, isp1301_get_u8(isp, ISP1301_OTG_STATUS)); + + check_state(isp, __func__); + pr_debug("otg: %s, %s %06x\n", + state_name(isp), __func__, omap_readl(OTG_CTRL)); + + omap_writew(DRIVER_SWITCH | OPRT_CHG + | B_SRP_TMROUT | B_HNP_FAIL + | A_VBUS_ERR | A_SRP_DETECT | A_REQ_TMROUT, OTG_IRQ_EN); + + l = omap_readl(OTG_SYSCON_2); + l |= OTG_EN; + omap_writel(l, OTG_SYSCON_2); + + return 0; +} + +static int otg_probe(struct platform_device *dev) +{ + // struct omap_usb_config *config = dev->platform_data; + + otg_dev = dev; + return 0; +} + +static int otg_remove(struct platform_device *dev) +{ + otg_dev = NULL; + return 0; +} + +static struct platform_driver omap_otg_driver = { + .probe = otg_probe, + .remove = otg_remove, + .driver = { + .owner = THIS_MODULE, + .name = "omap_otg", + }, +}; + +static int otg_bind(struct isp1301 *isp) +{ + int status; + + if (otg_dev) + return -EBUSY; + + status = platform_driver_register(&omap_otg_driver); + if (status < 0) + return status; + + if (otg_dev) + status = request_irq(otg_dev->resource[1].start, omap_otg_irq, + 0, DRIVER_NAME, isp); + else + status = -ENODEV; + + if (status < 0) + platform_driver_unregister(&omap_otg_driver); + return status; +} + +static void otg_unbind(struct isp1301 *isp) +{ + if (!otg_dev) + return; + free_irq(otg_dev->resource[1].start, isp); +} + +#else + +/* OTG controller isn't clocked */ + +#endif /* CONFIG_USB_OTG */ + +/*-------------------------------------------------------------------------*/ + +static void b_peripheral(struct isp1301 *isp) +{ + u32 l; + + l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; + omap_writel(l, OTG_CTRL); + + usb_gadget_vbus_connect(isp->phy.otg->gadget); + +#ifdef CONFIG_USB_OTG + enable_vbus_draw(isp, 8); + otg_update_isp(isp); +#else + enable_vbus_draw(isp, 100); + /* UDC driver just set OTG_BSESSVLD */ + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_DP_PULLUP); + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_DP_PULLDOWN); + isp->phy.state = OTG_STATE_B_PERIPHERAL; + pr_debug(" --> b_peripheral\n"); + dump_regs(isp, "2periph"); +#endif +} + +static void isp_update_otg(struct isp1301 *isp, u8 stat) +{ + struct usb_otg *otg = isp->phy.otg; + u8 isp_stat, isp_bstat; + enum usb_otg_state state = isp->phy.state; + + if (stat & INTR_BDIS_ACON) + pr_debug("OTG: BDIS_ACON, %s\n", state_name(isp)); + + /* start certain state transitions right away */ + isp_stat = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE); + if (isp_stat & INTR_ID_GND) { + if (otg->default_a) { + switch (state) { + case OTG_STATE_B_IDLE: + a_idle(isp, "idle"); + /* FALLTHROUGH */ + case OTG_STATE_A_IDLE: + enable_vbus_source(isp); + /* FALLTHROUGH */ + case OTG_STATE_A_WAIT_VRISE: + /* we skip over OTG_STATE_A_WAIT_BCON, since + * the HC will transition to A_HOST (or + * A_SUSPEND!) without our noticing except + * when HNP is used. + */ + if (isp_stat & INTR_VBUS_VLD) + isp->phy.state = OTG_STATE_A_HOST; + break; + case OTG_STATE_A_WAIT_VFALL: + if (!(isp_stat & INTR_SESS_VLD)) + a_idle(isp, "vfell"); + break; + default: + if (!(isp_stat & INTR_VBUS_VLD)) + isp->phy.state = OTG_STATE_A_VBUS_ERR; + break; + } + isp_bstat = isp1301_get_u8(isp, ISP1301_OTG_STATUS); + } else { + switch (state) { + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_HOST: + case OTG_STATE_B_WAIT_ACON: + usb_gadget_vbus_disconnect(otg->gadget); + break; + default: + break; + } + if (state != OTG_STATE_A_IDLE) + a_idle(isp, "id"); + if (otg->host && state == OTG_STATE_A_IDLE) + isp1301_defer_work(isp, WORK_HOST_RESUME); + isp_bstat = 0; + } + } else { + u32 l; + + /* if user unplugged mini-A end of cable, + * don't bypass A_WAIT_VFALL. + */ + if (otg->default_a) { + switch (state) { + default: + isp->phy.state = OTG_STATE_A_WAIT_VFALL; + break; + case OTG_STATE_A_WAIT_VFALL: + state = OTG_STATE_A_IDLE; + /* khubd may take a while to notice and + * handle this disconnect, so don't go + * to B_IDLE quite yet. + */ + break; + case OTG_STATE_A_IDLE: + host_suspend(isp); + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, + MC1_BDIS_ACON_EN); + isp->phy.state = OTG_STATE_B_IDLE; + l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + l &= ~OTG_CTRL_BITS; + omap_writel(l, OTG_CTRL); + break; + case OTG_STATE_B_IDLE: + break; + } + } + isp_bstat = isp1301_get_u8(isp, ISP1301_OTG_STATUS); + + switch (isp->phy.state) { + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_WAIT_ACON: + case OTG_STATE_B_HOST: + if (likely(isp_bstat & OTG_B_SESS_VLD)) + break; + enable_vbus_draw(isp, 0); +#ifndef CONFIG_USB_OTG + /* UDC driver will clear OTG_BSESSVLD */ + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, + OTG1_DP_PULLDOWN); + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, + OTG1_DP_PULLUP); + dump_regs(isp, __func__); +#endif + /* FALLTHROUGH */ + case OTG_STATE_B_SRP_INIT: + b_idle(isp, __func__); + l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; + omap_writel(l, OTG_CTRL); + /* FALLTHROUGH */ + case OTG_STATE_B_IDLE: + if (otg->gadget && (isp_bstat & OTG_B_SESS_VLD)) { +#ifdef CONFIG_USB_OTG + update_otg1(isp, isp_stat); + update_otg2(isp, isp_bstat); +#endif + b_peripheral(isp); + } else if (!(isp_stat & (INTR_VBUS_VLD|INTR_SESS_VLD))) + isp_bstat |= OTG_B_SESS_END; + break; + case OTG_STATE_A_WAIT_VFALL: + break; + default: + pr_debug("otg: unsupported b-device %s\n", + state_name(isp)); + break; + } + } + + if (state != isp->phy.state) + pr_debug(" isp, %s -> %s\n", + usb_otg_state_string(state), state_name(isp)); + +#ifdef CONFIG_USB_OTG + /* update the OTG controller state to match the isp1301; may + * trigger OPRT_CHG irqs for changes going to the isp1301. + */ + update_otg1(isp, isp_stat); + update_otg2(isp, isp_bstat); + check_state(isp, __func__); +#endif + + dump_regs(isp, "isp1301->otg"); +} + +/*-------------------------------------------------------------------------*/ + +static u8 isp1301_clear_latch(struct isp1301 *isp) +{ + u8 latch = isp1301_get_u8(isp, ISP1301_INTERRUPT_LATCH); + isp1301_clear_bits(isp, ISP1301_INTERRUPT_LATCH, latch); + return latch; +} + +static void +isp1301_work(struct work_struct *work) +{ + struct isp1301 *isp = container_of(work, struct isp1301, work); + int stop; + + /* implicit lock: we're the only task using this device */ + isp->working = 1; + do { + stop = test_bit(WORK_STOP, &isp->todo); + +#ifdef CONFIG_USB_OTG + /* transfer state from otg engine to isp1301 */ + if (test_and_clear_bit(WORK_UPDATE_ISP, &isp->todo)) { + otg_update_isp(isp); + put_device(&isp->client->dev); + } +#endif + /* transfer state from isp1301 to otg engine */ + if (test_and_clear_bit(WORK_UPDATE_OTG, &isp->todo)) { + u8 stat = isp1301_clear_latch(isp); + + isp_update_otg(isp, stat); + put_device(&isp->client->dev); + } + + if (test_and_clear_bit(WORK_HOST_RESUME, &isp->todo)) { + u32 otg_ctrl; + + /* + * skip A_WAIT_VRISE; hc transitions invisibly + * skip A_WAIT_BCON; same. + */ + switch (isp->phy.state) { + case OTG_STATE_A_WAIT_BCON: + case OTG_STATE_A_WAIT_VRISE: + isp->phy.state = OTG_STATE_A_HOST; + pr_debug(" --> a_host\n"); + otg_ctrl = omap_readl(OTG_CTRL); + otg_ctrl |= OTG_A_BUSREQ; + otg_ctrl &= ~(OTG_BUSDROP|OTG_B_BUSREQ) + & OTG_CTRL_MASK; + omap_writel(otg_ctrl, OTG_CTRL); + break; + case OTG_STATE_B_WAIT_ACON: + isp->phy.state = OTG_STATE_B_HOST; + pr_debug(" --> b_host (acon)\n"); + break; + case OTG_STATE_B_HOST: + case OTG_STATE_B_IDLE: + case OTG_STATE_A_IDLE: + break; + default: + pr_debug(" host resume in %s\n", + state_name(isp)); + } + host_resume(isp); + // mdelay(10); + put_device(&isp->client->dev); + } + + if (test_and_clear_bit(WORK_TIMER, &isp->todo)) { +#ifdef VERBOSE + dump_regs(isp, "timer"); + if (!stop) + mod_timer(&isp->timer, jiffies + TIMER_JIFFIES); +#endif + put_device(&isp->client->dev); + } + + if (isp->todo) + dev_vdbg(&isp->client->dev, + "work done, todo = 0x%lx\n", + isp->todo); + if (stop) { + dev_dbg(&isp->client->dev, "stop\n"); + break; + } + } while (isp->todo); + isp->working = 0; +} + +static irqreturn_t isp1301_irq(int irq, void *isp) +{ + isp1301_defer_work(isp, WORK_UPDATE_OTG); + return IRQ_HANDLED; +} + +static void isp1301_timer(unsigned long _isp) +{ + isp1301_defer_work((void *)_isp, WORK_TIMER); +} + +/*-------------------------------------------------------------------------*/ + +static void isp1301_release(struct device *dev) +{ + struct isp1301 *isp; + + isp = dev_get_drvdata(dev); + + /* FIXME -- not with a "new style" driver, it doesn't!! */ + + /* ugly -- i2c hijacks our memory hook to wait_for_completion() */ + if (isp->i2c_release) + isp->i2c_release(dev); + kfree(isp->phy.otg); + kfree (isp); +} + +static struct isp1301 *the_transceiver; + +static int isp1301_remove(struct i2c_client *i2c) +{ + struct isp1301 *isp; + + isp = i2c_get_clientdata(i2c); + + isp1301_clear_bits(isp, ISP1301_INTERRUPT_FALLING, ~0); + isp1301_clear_bits(isp, ISP1301_INTERRUPT_RISING, ~0); + free_irq(i2c->irq, isp); +#ifdef CONFIG_USB_OTG + otg_unbind(isp); +#endif + if (machine_is_omap_h2()) + gpio_free(2); + + isp->timer.data = 0; + set_bit(WORK_STOP, &isp->todo); + del_timer_sync(&isp->timer); + flush_work(&isp->work); + + put_device(&i2c->dev); + the_transceiver = NULL; + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* NOTE: three modes are possible here, only one of which + * will be standards-conformant on any given system: + * + * - OTG mode (dual-role), required if there's a Mini-AB connector + * - HOST mode, for when there's one or more A (host) connectors + * - DEVICE mode, for when there's a B/Mini-B (device) connector + * + * As a rule, you won't have an isp1301 chip unless it's there to + * support the OTG mode. Other modes help testing USB controllers + * in isolation from (full) OTG support, or maybe so later board + * revisions can help to support those feature. + */ + +#ifdef CONFIG_USB_OTG + +static int isp1301_otg_enable(struct isp1301 *isp) +{ + power_up(isp); + isp1301_otg_init(isp); + + /* NOTE: since we don't change this, this provides + * a few more interrupts than are strictly needed. + */ + isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, + INTR_VBUS_VLD | INTR_SESS_VLD | INTR_ID_GND); + isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, + INTR_VBUS_VLD | INTR_SESS_VLD | INTR_ID_GND); + + dev_info(&isp->client->dev, "ready for dual-role USB ...\n"); + + return 0; +} + +#endif + +/* add or disable the host device+driver */ +static int +isp1301_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct isp1301 *isp = container_of(otg->phy, struct isp1301, phy); + + if (!otg || isp != the_transceiver) + return -ENODEV; + + if (!host) { + omap_writew(0, OTG_IRQ_EN); + power_down(isp); + otg->host = NULL; + return 0; + } + +#ifdef CONFIG_USB_OTG + otg->host = host; + dev_dbg(&isp->client->dev, "registered host\n"); + host_suspend(isp); + if (otg->gadget) + return isp1301_otg_enable(isp); + return 0; + +#elif !IS_ENABLED(CONFIG_USB_OMAP) + // FIXME update its refcount + otg->host = host; + + power_up(isp); + + if (machine_is_omap_h2()) + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); + + dev_info(&isp->client->dev, "A-Host sessions ok\n"); + isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, + INTR_ID_GND); + isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, + INTR_ID_GND); + + /* If this has a Mini-AB connector, this mode is highly + * nonstandard ... but can be handy for testing, especially with + * the Mini-A end of an OTG cable. (Or something nonstandard + * like MiniB-to-StandardB, maybe built with a gender mender.) + */ + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_VBUS_DRV); + + dump_regs(isp, __func__); + + return 0; + +#else + dev_dbg(&isp->client->dev, "host sessions not allowed\n"); + return -EINVAL; +#endif + +} + +static int +isp1301_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) +{ + struct isp1301 *isp = container_of(otg->phy, struct isp1301, phy); + + if (!otg || isp != the_transceiver) + return -ENODEV; + + if (!gadget) { + omap_writew(0, OTG_IRQ_EN); + if (!otg->default_a) + enable_vbus_draw(isp, 0); + usb_gadget_vbus_disconnect(otg->gadget); + otg->gadget = NULL; + power_down(isp); + return 0; + } + +#ifdef CONFIG_USB_OTG + otg->gadget = gadget; + dev_dbg(&isp->client->dev, "registered gadget\n"); + /* gadget driver may be suspended until vbus_connect () */ + if (otg->host) + return isp1301_otg_enable(isp); + return 0; + +#elif !defined(CONFIG_USB_OHCI_HCD) && !defined(CONFIG_USB_OHCI_HCD_MODULE) + otg->gadget = gadget; + // FIXME update its refcount + + { + u32 l; + + l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; + l &= ~(OTG_XCEIV_OUTPUTS|OTG_CTRL_BITS); + l |= OTG_ID; + omap_writel(l, OTG_CTRL); + } + + power_up(isp); + isp->phy.state = OTG_STATE_B_IDLE; + + if (machine_is_omap_h2() || machine_is_omap_h3()) + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); + + isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, + INTR_SESS_VLD); + isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, + INTR_VBUS_VLD); + dev_info(&isp->client->dev, "B-Peripheral sessions ok\n"); + dump_regs(isp, __func__); + + /* If this has a Mini-AB connector, this mode is highly + * nonstandard ... but can be handy for testing, so long + * as you don't plug a Mini-A cable into the jack. + */ + if (isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE) & INTR_VBUS_VLD) + b_peripheral(isp); + + return 0; + +#else + dev_dbg(&isp->client->dev, "peripheral sessions not allowed\n"); + return -EINVAL; +#endif +} + + +/*-------------------------------------------------------------------------*/ + +static int +isp1301_set_power(struct usb_phy *dev, unsigned mA) +{ + if (!the_transceiver) + return -ENODEV; + if (dev->state == OTG_STATE_B_PERIPHERAL) + enable_vbus_draw(the_transceiver, mA); + return 0; +} + +static int +isp1301_start_srp(struct usb_otg *otg) +{ + struct isp1301 *isp = container_of(otg->phy, struct isp1301, phy); + u32 otg_ctrl; + + if (!otg || isp != the_transceiver + || isp->phy.state != OTG_STATE_B_IDLE) + return -ENODEV; + + otg_ctrl = omap_readl(OTG_CTRL); + if (!(otg_ctrl & OTG_BSESSEND)) + return -EINVAL; + + otg_ctrl |= OTG_B_BUSREQ; + otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK; + omap_writel(otg_ctrl, OTG_CTRL); + isp->phy.state = OTG_STATE_B_SRP_INIT; + + pr_debug("otg: SRP, %s ... %06x\n", state_name(isp), + omap_readl(OTG_CTRL)); +#ifdef CONFIG_USB_OTG + check_state(isp, __func__); +#endif + return 0; +} + +static int +isp1301_start_hnp(struct usb_otg *otg) +{ +#ifdef CONFIG_USB_OTG + struct isp1301 *isp = container_of(otg->phy, struct isp1301, phy); + u32 l; + + if (!otg || isp != the_transceiver) + return -ENODEV; + if (otg->default_a && (otg->host == NULL || !otg->host->b_hnp_enable)) + return -ENOTCONN; + if (!otg->default_a && (otg->gadget == NULL + || !otg->gadget->b_hnp_enable)) + return -ENOTCONN; + + /* We want hardware to manage most HNP protocol timings. + * So do this part as early as possible... + */ + switch (isp->phy.state) { + case OTG_STATE_B_HOST: + isp->phy.state = OTG_STATE_B_PERIPHERAL; + /* caller will suspend next */ + break; + case OTG_STATE_A_HOST: +#if 0 + /* autoconnect mode avoids irq latency bugs */ + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, + MC1_BDIS_ACON_EN); +#endif + /* caller must suspend then clear A_BUSREQ */ + usb_gadget_vbus_connect(otg->gadget); + l = omap_readl(OTG_CTRL); + l |= OTG_A_SETB_HNPEN; + omap_writel(l, OTG_CTRL); + + break; + case OTG_STATE_A_PERIPHERAL: + /* initiated by B-Host suspend */ + break; + default: + return -EILSEQ; + } + pr_debug("otg: HNP %s, %06x ...\n", + state_name(isp), omap_readl(OTG_CTRL)); + check_state(isp, __func__); + return 0; +#else + /* srp-only */ + return -EINVAL; +#endif +} + +/*-------------------------------------------------------------------------*/ + +static int +isp1301_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + int status; + struct isp1301 *isp; + + if (the_transceiver) + return 0; + + isp = kzalloc(sizeof *isp, GFP_KERNEL); + if (!isp) + return 0; + + isp->phy.otg = kzalloc(sizeof *isp->phy.otg, GFP_KERNEL); + if (!isp->phy.otg) { + kfree(isp); + return 0; + } + + INIT_WORK(&isp->work, isp1301_work); + init_timer(&isp->timer); + isp->timer.function = isp1301_timer; + isp->timer.data = (unsigned long) isp; + + i2c_set_clientdata(i2c, isp); + isp->client = i2c; + + /* verify the chip (shouldn't be necessary) */ + status = isp1301_get_u16(isp, ISP1301_VENDOR_ID); + if (status != I2C_VENDOR_ID_PHILIPS) { + dev_dbg(&i2c->dev, "not philips id: %d\n", status); + goto fail; + } + status = isp1301_get_u16(isp, ISP1301_PRODUCT_ID); + if (status != I2C_PRODUCT_ID_PHILIPS_1301) { + dev_dbg(&i2c->dev, "not isp1301, %d\n", status); + goto fail; + } + isp->i2c_release = i2c->dev.release; + i2c->dev.release = isp1301_release; + + /* initial development used chiprev 2.00 */ + status = i2c_smbus_read_word_data(i2c, ISP1301_BCD_DEVICE); + dev_info(&i2c->dev, "chiprev %x.%02x, driver " DRIVER_VERSION "\n", + status >> 8, status & 0xff); + + /* make like power-on reset */ + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_MASK); + + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_BI_DI); + isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_2, ~MC2_BI_DI); + + isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, + OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN); + isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, + ~(OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN)); + + isp1301_clear_bits(isp, ISP1301_INTERRUPT_LATCH, ~0); + isp1301_clear_bits(isp, ISP1301_INTERRUPT_FALLING, ~0); + isp1301_clear_bits(isp, ISP1301_INTERRUPT_RISING, ~0); + +#ifdef CONFIG_USB_OTG + status = otg_bind(isp); + if (status < 0) { + dev_dbg(&i2c->dev, "can't bind OTG\n"); + goto fail; + } +#endif + + if (machine_is_omap_h2()) { + /* full speed signaling by default */ + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, + MC1_SPEED); + isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, + MC2_SPD_SUSP_CTRL); + + /* IRQ wired at M14 */ + omap_cfg_reg(M14_1510_GPIO2); + if (gpio_request(2, "isp1301") == 0) + gpio_direction_input(2); + isp->irq_type = IRQF_TRIGGER_FALLING; + } + + status = request_irq(i2c->irq, isp1301_irq, + isp->irq_type, DRIVER_NAME, isp); + if (status < 0) { + dev_dbg(&i2c->dev, "can't get IRQ %d, err %d\n", + i2c->irq, status); + goto fail; + } + + isp->phy.dev = &i2c->dev; + isp->phy.label = DRIVER_NAME; + isp->phy.set_power = isp1301_set_power, + + isp->phy.otg->phy = &isp->phy; + isp->phy.otg->set_host = isp1301_set_host, + isp->phy.otg->set_peripheral = isp1301_set_peripheral, + isp->phy.otg->start_srp = isp1301_start_srp, + isp->phy.otg->start_hnp = isp1301_start_hnp, + + enable_vbus_draw(isp, 0); + power_down(isp); + the_transceiver = isp; + +#ifdef CONFIG_USB_OTG + update_otg1(isp, isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE)); + update_otg2(isp, isp1301_get_u8(isp, ISP1301_OTG_STATUS)); +#endif + + dump_regs(isp, __func__); + +#ifdef VERBOSE + mod_timer(&isp->timer, jiffies + TIMER_JIFFIES); + dev_dbg(&i2c->dev, "scheduled timer, %d min\n", TIMER_MINUTES); +#endif + + status = usb_add_phy(&isp->phy, USB_PHY_TYPE_USB2); + if (status < 0) + dev_err(&i2c->dev, "can't register transceiver, %d\n", + status); + + return 0; + +fail: + kfree(isp->phy.otg); + kfree(isp); + return -ENODEV; +} + +static const struct i2c_device_id isp1301_id[] = { + { "isp1301_omap", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isp1301_id); + +static struct i2c_driver isp1301_driver = { + .driver = { + .name = "isp1301_omap", + }, + .probe = isp1301_probe, + .remove = isp1301_remove, + .id_table = isp1301_id, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init isp_init(void) +{ + return i2c_add_driver(&isp1301_driver); +} +subsys_initcall(isp_init); + +static void __exit isp_exit(void) +{ + if (the_transceiver) + usb_remove_phy(&the_transceiver->phy); + i2c_del_driver(&isp1301_driver); +} +module_exit(isp_exit); + diff --git a/drivers/usb/phy/phy-isp1301.c b/drivers/usb/phy/phy-isp1301.c new file mode 100644 index 00000000..8a55b37d --- /dev/null +++ b/drivers/usb/phy/phy-isp1301.c @@ -0,0 +1,163 @@ +/* + * NXP ISP1301 USB transceiver driver + * + * Copyright (C) 2012 Roland Stigge + * + * Author: Roland Stigge + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define DRV_NAME "isp1301" + +struct isp1301 { + struct usb_phy phy; + struct mutex mutex; + + struct i2c_client *client; +}; + +#define phy_to_isp(p) (container_of((p), struct isp1301, phy)) + +static const struct i2c_device_id isp1301_id[] = { + { "isp1301", 0 }, + { } +}; + +static struct i2c_client *isp1301_i2c_client; + +static int __isp1301_write(struct isp1301 *isp, u8 reg, u8 value, u8 clear) +{ + return i2c_smbus_write_byte_data(isp->client, reg | clear, value); +} + +static int isp1301_write(struct isp1301 *isp, u8 reg, u8 value) +{ + return __isp1301_write(isp, reg, value, 0); +} + +static int isp1301_clear(struct isp1301 *isp, u8 reg, u8 value) +{ + return __isp1301_write(isp, reg, value, ISP1301_I2C_REG_CLEAR_ADDR); +} + +static int isp1301_phy_init(struct usb_phy *phy) +{ + struct isp1301 *isp = phy_to_isp(phy); + + /* Disable transparent UART mode first */ + isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_UART_EN); + isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_1, ~MC1_SPEED_REG); + isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_SPEED_REG); + isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_2, ~0); + isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_2, (MC2_BI_DI | MC2_PSW_EN + | MC2_SPD_SUSP_CTRL)); + + isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, ~0); + isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_DAT_SE0); + isp1301_write(isp, ISP1301_I2C_OTG_CONTROL_1, (OTG1_DM_PULLDOWN + | OTG1_DP_PULLDOWN)); + isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, (OTG1_DM_PULLUP + | OTG1_DP_PULLUP)); + + /* mask all interrupts */ + isp1301_clear(isp, ISP1301_I2C_INTERRUPT_LATCH, ~0); + isp1301_clear(isp, ISP1301_I2C_INTERRUPT_FALLING, ~0); + isp1301_clear(isp, ISP1301_I2C_INTERRUPT_RISING, ~0); + + return 0; +} + +static int isp1301_phy_set_vbus(struct usb_phy *phy, int on) +{ + struct isp1301 *isp = phy_to_isp(phy); + + if (on) + isp1301_write(isp, ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV); + else + isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV); + + return 0; +} + +static int isp1301_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct isp1301 *isp; + struct usb_phy *phy; + + isp = devm_kzalloc(&client->dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->client = client; + mutex_init(&isp->mutex); + + phy = &isp->phy; + phy->dev = &client->dev; + phy->label = DRV_NAME; + phy->init = isp1301_phy_init; + phy->set_vbus = isp1301_phy_set_vbus; + phy->type = USB_PHY_TYPE_USB2; + + i2c_set_clientdata(client, isp); + usb_add_phy_dev(phy); + + isp1301_i2c_client = client; + + return 0; +} + +static int isp1301_remove(struct i2c_client *client) +{ + struct isp1301 *isp = i2c_get_clientdata(client); + + usb_remove_phy(&isp->phy); + isp1301_i2c_client = NULL; + + return 0; +} + +static struct i2c_driver isp1301_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = isp1301_probe, + .remove = isp1301_remove, + .id_table = isp1301_id, +}; + +module_i2c_driver(isp1301_driver); + +static int match(struct device *dev, void *data) +{ + struct device_node *node = (struct device_node *)data; + return (dev->of_node == node) && + (dev->driver == &isp1301_driver.driver); +} + +struct i2c_client *isp1301_get_client(struct device_node *node) +{ + if (node) { /* reference of ISP1301 I2C node via DT */ + struct device *dev = bus_find_device(&i2c_bus_type, NULL, + node, match); + if (!dev) + return NULL; + return to_i2c_client(dev); + } else { /* non-DT: only one ISP1301 chip supported */ + return isp1301_i2c_client; + } +} +EXPORT_SYMBOL_GPL(isp1301_get_client); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("NXP ISP1301 USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c new file mode 100644 index 00000000..749fbf41 --- /dev/null +++ b/drivers/usb/phy/phy-msm-usb.c @@ -0,0 +1,1762 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MSM_USB_BASE (motg->regs) +#define DRIVER_NAME "msm_otg" + +#define ULPI_IO_TIMEOUT_USEC (10 * 1000) + +#define USB_PHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_PHY_3P3_VOL_MAX 3300000 /* uV */ +#define USB_PHY_3P3_HPM_LOAD 50000 /* uA */ +#define USB_PHY_3P3_LPM_LOAD 4000 /* uA */ + +#define USB_PHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_PHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_PHY_1P8_HPM_LOAD 50000 /* uA */ +#define USB_PHY_1P8_LPM_LOAD 4000 /* uA */ + +#define USB_PHY_VDD_DIG_VOL_MIN 1000000 /* uV */ +#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */ + +static struct regulator *hsusb_3p3; +static struct regulator *hsusb_1p8; +static struct regulator *hsusb_vddcx; + +static int msm_hsusb_init_vddcx(struct msm_otg *motg, int init) +{ + int ret = 0; + + if (init) { + hsusb_vddcx = regulator_get(motg->phy.dev, "HSUSB_VDDCX"); + if (IS_ERR(hsusb_vddcx)) { + dev_err(motg->phy.dev, "unable to get hsusb vddcx\n"); + return PTR_ERR(hsusb_vddcx); + } + + ret = regulator_set_voltage(hsusb_vddcx, + USB_PHY_VDD_DIG_VOL_MIN, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret) { + dev_err(motg->phy.dev, "unable to set the voltage " + "for hsusb vddcx\n"); + regulator_put(hsusb_vddcx); + return ret; + } + + ret = regulator_enable(hsusb_vddcx); + if (ret) { + dev_err(motg->phy.dev, "unable to enable hsusb vddcx\n"); + regulator_put(hsusb_vddcx); + } + } else { + ret = regulator_set_voltage(hsusb_vddcx, 0, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret) + dev_err(motg->phy.dev, "unable to set the voltage " + "for hsusb vddcx\n"); + ret = regulator_disable(hsusb_vddcx); + if (ret) + dev_err(motg->phy.dev, "unable to disable hsusb vddcx\n"); + + regulator_put(hsusb_vddcx); + } + + return ret; +} + +static int msm_hsusb_ldo_init(struct msm_otg *motg, int init) +{ + int rc = 0; + + if (init) { + hsusb_3p3 = regulator_get(motg->phy.dev, "HSUSB_3p3"); + if (IS_ERR(hsusb_3p3)) { + dev_err(motg->phy.dev, "unable to get hsusb 3p3\n"); + return PTR_ERR(hsusb_3p3); + } + + rc = regulator_set_voltage(hsusb_3p3, USB_PHY_3P3_VOL_MIN, + USB_PHY_3P3_VOL_MAX); + if (rc) { + dev_err(motg->phy.dev, "unable to set voltage level " + "for hsusb 3p3\n"); + goto put_3p3; + } + rc = regulator_enable(hsusb_3p3); + if (rc) { + dev_err(motg->phy.dev, "unable to enable the hsusb 3p3\n"); + goto put_3p3; + } + hsusb_1p8 = regulator_get(motg->phy.dev, "HSUSB_1p8"); + if (IS_ERR(hsusb_1p8)) { + dev_err(motg->phy.dev, "unable to get hsusb 1p8\n"); + rc = PTR_ERR(hsusb_1p8); + goto disable_3p3; + } + rc = regulator_set_voltage(hsusb_1p8, USB_PHY_1P8_VOL_MIN, + USB_PHY_1P8_VOL_MAX); + if (rc) { + dev_err(motg->phy.dev, "unable to set voltage level " + "for hsusb 1p8\n"); + goto put_1p8; + } + rc = regulator_enable(hsusb_1p8); + if (rc) { + dev_err(motg->phy.dev, "unable to enable the hsusb 1p8\n"); + goto put_1p8; + } + + return 0; + } + + regulator_disable(hsusb_1p8); +put_1p8: + regulator_put(hsusb_1p8); +disable_3p3: + regulator_disable(hsusb_3p3); +put_3p3: + regulator_put(hsusb_3p3); + return rc; +} + +#ifdef CONFIG_PM_SLEEP +#define USB_PHY_SUSP_DIG_VOL 500000 +static int msm_hsusb_config_vddcx(int high) +{ + int max_vol = USB_PHY_VDD_DIG_VOL_MAX; + int min_vol; + int ret; + + if (high) + min_vol = USB_PHY_VDD_DIG_VOL_MIN; + else + min_vol = USB_PHY_SUSP_DIG_VOL; + + ret = regulator_set_voltage(hsusb_vddcx, min_vol, max_vol); + if (ret) { + pr_err("%s: unable to set the voltage for regulator " + "HSUSB_VDDCX\n", __func__); + return ret; + } + + pr_debug("%s: min_vol:%d max_vol:%d\n", __func__, min_vol, max_vol); + + return ret; +} +#endif + +static int msm_hsusb_ldo_set_mode(int on) +{ + int ret = 0; + + if (!hsusb_1p8 || IS_ERR(hsusb_1p8)) { + pr_err("%s: HSUSB_1p8 is not initialized\n", __func__); + return -ENODEV; + } + + if (!hsusb_3p3 || IS_ERR(hsusb_3p3)) { + pr_err("%s: HSUSB_3p3 is not initialized\n", __func__); + return -ENODEV; + } + + if (on) { + ret = regulator_set_optimum_mode(hsusb_1p8, + USB_PHY_1P8_HPM_LOAD); + if (ret < 0) { + pr_err("%s: Unable to set HPM of the regulator " + "HSUSB_1p8\n", __func__); + return ret; + } + ret = regulator_set_optimum_mode(hsusb_3p3, + USB_PHY_3P3_HPM_LOAD); + if (ret < 0) { + pr_err("%s: Unable to set HPM of the regulator " + "HSUSB_3p3\n", __func__); + regulator_set_optimum_mode(hsusb_1p8, + USB_PHY_1P8_LPM_LOAD); + return ret; + } + } else { + ret = regulator_set_optimum_mode(hsusb_1p8, + USB_PHY_1P8_LPM_LOAD); + if (ret < 0) + pr_err("%s: Unable to set LPM of the regulator " + "HSUSB_1p8\n", __func__); + ret = regulator_set_optimum_mode(hsusb_3p3, + USB_PHY_3P3_LPM_LOAD); + if (ret < 0) + pr_err("%s: Unable to set LPM of the regulator " + "HSUSB_3p3\n", __func__); + } + + pr_debug("reg (%s)\n", on ? "HPM" : "LPM"); + return ret < 0 ? ret : 0; +} + +static int ulpi_read(struct usb_phy *phy, u32 reg) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + int cnt = 0; + + /* initiate read operation */ + writel(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while (cnt < ULPI_IO_TIMEOUT_USEC) { + if (!(readl(USB_ULPI_VIEWPORT) & ULPI_RUN)) + break; + udelay(1); + cnt++; + } + + if (cnt >= ULPI_IO_TIMEOUT_USEC) { + dev_err(phy->dev, "ulpi_read: timeout %08x\n", + readl(USB_ULPI_VIEWPORT)); + return -ETIMEDOUT; + } + return ULPI_DATA_READ(readl(USB_ULPI_VIEWPORT)); +} + +static int ulpi_write(struct usb_phy *phy, u32 val, u32 reg) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + int cnt = 0; + + /* initiate write operation */ + writel(ULPI_RUN | ULPI_WRITE | + ULPI_ADDR(reg) | ULPI_DATA(val), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while (cnt < ULPI_IO_TIMEOUT_USEC) { + if (!(readl(USB_ULPI_VIEWPORT) & ULPI_RUN)) + break; + udelay(1); + cnt++; + } + + if (cnt >= ULPI_IO_TIMEOUT_USEC) { + dev_err(phy->dev, "ulpi_write: timeout\n"); + return -ETIMEDOUT; + } + return 0; +} + +static struct usb_phy_io_ops msm_otg_io_ops = { + .read = ulpi_read, + .write = ulpi_write, +}; + +static void ulpi_init(struct msm_otg *motg) +{ + struct msm_otg_platform_data *pdata = motg->pdata; + int *seq = pdata->phy_init_seq; + + if (!seq) + return; + + while (seq[0] >= 0) { + dev_vdbg(motg->phy.dev, "ulpi: write 0x%02x to 0x%02x\n", + seq[0], seq[1]); + ulpi_write(&motg->phy, seq[0], seq[1]); + seq += 2; + } +} + +static int msm_otg_link_clk_reset(struct msm_otg *motg, bool assert) +{ + int ret; + + if (assert) { + ret = clk_reset(motg->clk, CLK_RESET_ASSERT); + if (ret) + dev_err(motg->phy.dev, "usb hs_clk assert failed\n"); + } else { + ret = clk_reset(motg->clk, CLK_RESET_DEASSERT); + if (ret) + dev_err(motg->phy.dev, "usb hs_clk deassert failed\n"); + } + return ret; +} + +static int msm_otg_phy_clk_reset(struct msm_otg *motg) +{ + int ret; + + ret = clk_reset(motg->phy_reset_clk, CLK_RESET_ASSERT); + if (ret) { + dev_err(motg->phy.dev, "usb phy clk assert failed\n"); + return ret; + } + usleep_range(10000, 12000); + ret = clk_reset(motg->phy_reset_clk, CLK_RESET_DEASSERT); + if (ret) + dev_err(motg->phy.dev, "usb phy clk deassert failed\n"); + return ret; +} + +static int msm_otg_phy_reset(struct msm_otg *motg) +{ + u32 val; + int ret; + int retries; + + ret = msm_otg_link_clk_reset(motg, 1); + if (ret) + return ret; + ret = msm_otg_phy_clk_reset(motg); + if (ret) + return ret; + ret = msm_otg_link_clk_reset(motg, 0); + if (ret) + return ret; + + val = readl(USB_PORTSC) & ~PORTSC_PTS_MASK; + writel(val | PORTSC_PTS_ULPI, USB_PORTSC); + + for (retries = 3; retries > 0; retries--) { + ret = ulpi_write(&motg->phy, ULPI_FUNC_CTRL_SUSPENDM, + ULPI_CLR(ULPI_FUNC_CTRL)); + if (!ret) + break; + ret = msm_otg_phy_clk_reset(motg); + if (ret) + return ret; + } + if (!retries) + return -ETIMEDOUT; + + /* This reset calibrates the phy, if the above write succeeded */ + ret = msm_otg_phy_clk_reset(motg); + if (ret) + return ret; + + for (retries = 3; retries > 0; retries--) { + ret = ulpi_read(&motg->phy, ULPI_DEBUG); + if (ret != -ETIMEDOUT) + break; + ret = msm_otg_phy_clk_reset(motg); + if (ret) + return ret; + } + if (!retries) + return -ETIMEDOUT; + + dev_info(motg->phy.dev, "phy_reset: success\n"); + return 0; +} + +#define LINK_RESET_TIMEOUT_USEC (250 * 1000) +static int msm_otg_reset(struct usb_phy *phy) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + struct msm_otg_platform_data *pdata = motg->pdata; + int cnt = 0; + int ret; + u32 val = 0; + u32 ulpi_val = 0; + + ret = msm_otg_phy_reset(motg); + if (ret) { + dev_err(phy->dev, "phy_reset failed\n"); + return ret; + } + + ulpi_init(motg); + + writel(USBCMD_RESET, USB_USBCMD); + while (cnt < LINK_RESET_TIMEOUT_USEC) { + if (!(readl(USB_USBCMD) & USBCMD_RESET)) + break; + udelay(1); + cnt++; + } + if (cnt >= LINK_RESET_TIMEOUT_USEC) + return -ETIMEDOUT; + + /* select ULPI phy */ + writel(0x80000000, USB_PORTSC); + + msleep(100); + + writel(0x0, USB_AHBBURST); + writel(0x00, USB_AHBMODE); + + if (pdata->otg_control == OTG_PHY_CONTROL) { + val = readl(USB_OTGSC); + if (pdata->mode == USB_OTG) { + ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; + val |= OTGSC_IDIE | OTGSC_BSVIE; + } else if (pdata->mode == USB_PERIPHERAL) { + ulpi_val = ULPI_INT_SESS_VALID; + val |= OTGSC_BSVIE; + } + writel(val, USB_OTGSC); + ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_RISE); + ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_FALL); + } + + return 0; +} + +#define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000) +#define PHY_RESUME_TIMEOUT_USEC (100 * 1000) + +#ifdef CONFIG_PM_SLEEP +static int msm_otg_suspend(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + struct usb_bus *bus = phy->otg->host; + struct msm_otg_platform_data *pdata = motg->pdata; + int cnt = 0; + + if (atomic_read(&motg->in_lpm)) + return 0; + + disable_irq(motg->irq); + /* + * Chipidea 45-nm PHY suspend sequence: + * + * Interrupt Latch Register auto-clear feature is not present + * in all PHY versions. Latch register is clear on read type. + * Clear latch register to avoid spurious wakeup from + * low power mode (LPM). + * + * PHY comparators are disabled when PHY enters into low power + * mode (LPM). Keep PHY comparators ON in LPM only when we expect + * VBUS/Id notifications from USB PHY. Otherwise turn off USB + * PHY comparators. This save significant amount of power. + * + * PLL is not turned off when PHY enters into low power mode (LPM). + * Disable PLL for maximum power savings. + */ + + if (motg->pdata->phy_type == CI_45NM_INTEGRATED_PHY) { + ulpi_read(phy, 0x14); + if (pdata->otg_control == OTG_PHY_CONTROL) + ulpi_write(phy, 0x01, 0x30); + ulpi_write(phy, 0x08, 0x09); + } + + /* + * PHY may take some time or even fail to enter into low power + * mode (LPM). Hence poll for 500 msec and reset the PHY and link + * in failure case. + */ + writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); + while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { + if (readl(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) { + dev_err(phy->dev, "Unable to suspend PHY\n"); + msm_otg_reset(phy); + enable_irq(motg->irq); + return -ETIMEDOUT; + } + + /* + * PHY has capability to generate interrupt asynchronously in low + * power mode (LPM). This interrupt is level triggered. So USB IRQ + * line must be disabled till async interrupt enable bit is cleared + * in USBCMD register. Assert STP (ULPI interface STOP signal) to + * block data communication from PHY. + */ + writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); + + if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && + motg->pdata->otg_control == OTG_PMIC_CONTROL) + writel(readl(USB_PHY_CTRL) | PHY_RETEN, USB_PHY_CTRL); + + clk_disable(motg->pclk); + clk_disable(motg->clk); + if (motg->core_clk) + clk_disable(motg->core_clk); + + if (!IS_ERR(motg->pclk_src)) + clk_disable(motg->pclk_src); + + if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && + motg->pdata->otg_control == OTG_PMIC_CONTROL) { + msm_hsusb_ldo_set_mode(0); + msm_hsusb_config_vddcx(0); + } + + if (device_may_wakeup(phy->dev)) + enable_irq_wake(motg->irq); + if (bus) + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + + atomic_set(&motg->in_lpm, 1); + enable_irq(motg->irq); + + dev_info(phy->dev, "USB in low power mode\n"); + + return 0; +} + +static int msm_otg_resume(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + struct usb_bus *bus = phy->otg->host; + int cnt = 0; + unsigned temp; + + if (!atomic_read(&motg->in_lpm)) + return 0; + + if (!IS_ERR(motg->pclk_src)) + clk_enable(motg->pclk_src); + + clk_enable(motg->pclk); + clk_enable(motg->clk); + if (motg->core_clk) + clk_enable(motg->core_clk); + + if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && + motg->pdata->otg_control == OTG_PMIC_CONTROL) { + msm_hsusb_ldo_set_mode(1); + msm_hsusb_config_vddcx(1); + writel(readl(USB_PHY_CTRL) & ~PHY_RETEN, USB_PHY_CTRL); + } + + temp = readl(USB_USBCMD); + temp &= ~ASYNC_INTR_CTRL; + temp &= ~ULPI_STP_CTRL; + writel(temp, USB_USBCMD); + + /* + * PHY comes out of low power mode (LPM) in case of wakeup + * from asynchronous interrupt. + */ + if (!(readl(USB_PORTSC) & PORTSC_PHCD)) + goto skip_phy_resume; + + writel(readl(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC); + while (cnt < PHY_RESUME_TIMEOUT_USEC) { + if (!(readl(USB_PORTSC) & PORTSC_PHCD)) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_RESUME_TIMEOUT_USEC) { + /* + * This is a fatal error. Reset the link and + * PHY. USB state can not be restored. Re-insertion + * of USB cable is the only way to get USB working. + */ + dev_err(phy->dev, "Unable to resume USB." + "Re-plugin the cable\n"); + msm_otg_reset(phy); + } + +skip_phy_resume: + if (device_may_wakeup(phy->dev)) + disable_irq_wake(motg->irq); + if (bus) + set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + + atomic_set(&motg->in_lpm, 0); + + if (motg->async_int) { + motg->async_int = 0; + pm_runtime_put(phy->dev); + enable_irq(motg->irq); + } + + dev_info(phy->dev, "USB exited from low power mode\n"); + + return 0; +} +#endif + +static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA) +{ + if (motg->cur_power == mA) + return; + + /* TODO: Notify PMIC about available current */ + dev_info(motg->phy.dev, "Avail curr from USB = %u\n", mA); + motg->cur_power = mA; +} + +static int msm_otg_set_power(struct usb_phy *phy, unsigned mA) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + + /* + * Gadget driver uses set_power method to notify about the + * available current based on suspend/configured states. + * + * IDEV_CHG can be drawn irrespective of suspend/un-configured + * states when CDP/ACA is connected. + */ + if (motg->chg_type == USB_SDP_CHARGER) + msm_otg_notify_charger(motg, mA); + + return 0; +} + +static void msm_otg_start_host(struct usb_phy *phy, int on) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + struct msm_otg_platform_data *pdata = motg->pdata; + struct usb_hcd *hcd; + + if (!phy->otg->host) + return; + + hcd = bus_to_hcd(phy->otg->host); + + if (on) { + dev_dbg(phy->dev, "host on\n"); + + if (pdata->vbus_power) + pdata->vbus_power(1); + /* + * Some boards have a switch cotrolled by gpio + * to enable/disable internal HUB. Enable internal + * HUB before kicking the host. + */ + if (pdata->setup_gpio) + pdata->setup_gpio(OTG_STATE_A_HOST); +#ifdef CONFIG_USB + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); +#endif + } else { + dev_dbg(phy->dev, "host off\n"); + +#ifdef CONFIG_USB + usb_remove_hcd(hcd); +#endif + if (pdata->setup_gpio) + pdata->setup_gpio(OTG_STATE_UNDEFINED); + if (pdata->vbus_power) + pdata->vbus_power(0); + } +} + +static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); + struct usb_hcd *hcd; + + /* + * Fail host registration if this board can support + * only peripheral configuration. + */ + if (motg->pdata->mode == USB_PERIPHERAL) { + dev_info(otg->phy->dev, "Host mode is not supported\n"); + return -ENODEV; + } + + if (!host) { + if (otg->phy->state == OTG_STATE_A_HOST) { + pm_runtime_get_sync(otg->phy->dev); + msm_otg_start_host(otg->phy, 0); + otg->host = NULL; + otg->phy->state = OTG_STATE_UNDEFINED; + schedule_work(&motg->sm_work); + } else { + otg->host = NULL; + } + + return 0; + } + + hcd = bus_to_hcd(host); + hcd->power_budget = motg->pdata->power_budget; + + otg->host = host; + dev_dbg(otg->phy->dev, "host driver registered w/ tranceiver\n"); + + /* + * Kick the state machine work, if peripheral is not supported + * or peripheral is already registered with us. + */ + if (motg->pdata->mode == USB_HOST || otg->gadget) { + pm_runtime_get_sync(otg->phy->dev); + schedule_work(&motg->sm_work); + } + + return 0; +} + +static void msm_otg_start_peripheral(struct usb_phy *phy, int on) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + struct msm_otg_platform_data *pdata = motg->pdata; + + if (!phy->otg->gadget) + return; + + if (on) { + dev_dbg(phy->dev, "gadget on\n"); + /* + * Some boards have a switch cotrolled by gpio + * to enable/disable internal HUB. Disable internal + * HUB before kicking the gadget. + */ + if (pdata->setup_gpio) + pdata->setup_gpio(OTG_STATE_B_PERIPHERAL); + usb_gadget_vbus_connect(phy->otg->gadget); + } else { + dev_dbg(phy->dev, "gadget off\n"); + usb_gadget_vbus_disconnect(phy->otg->gadget); + if (pdata->setup_gpio) + pdata->setup_gpio(OTG_STATE_UNDEFINED); + } + +} + +static int msm_otg_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); + + /* + * Fail peripheral registration if this board can support + * only host configuration. + */ + if (motg->pdata->mode == USB_HOST) { + dev_info(otg->phy->dev, "Peripheral mode is not supported\n"); + return -ENODEV; + } + + if (!gadget) { + if (otg->phy->state == OTG_STATE_B_PERIPHERAL) { + pm_runtime_get_sync(otg->phy->dev); + msm_otg_start_peripheral(otg->phy, 0); + otg->gadget = NULL; + otg->phy->state = OTG_STATE_UNDEFINED; + schedule_work(&motg->sm_work); + } else { + otg->gadget = NULL; + } + + return 0; + } + otg->gadget = gadget; + dev_dbg(otg->phy->dev, "peripheral driver registered w/ tranceiver\n"); + + /* + * Kick the state machine work, if host is not supported + * or host is already registered with us. + */ + if (motg->pdata->mode == USB_PERIPHERAL || otg->host) { + pm_runtime_get_sync(otg->phy->dev); + schedule_work(&motg->sm_work); + } + + return 0; +} + +static bool msm_chg_check_secondary_det(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 chg_det; + bool ret = false; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x34); + ret = chg_det & (1 << 4); + break; + case SNPS_28NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x87); + ret = chg_det & 1; + break; + default: + break; + } + return ret; +} + +static void msm_chg_enable_secondary_det(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x34); + /* Turn off charger block */ + chg_det |= ~(1 << 1); + ulpi_write(phy, chg_det, 0x34); + udelay(20); + /* control chg block via ULPI */ + chg_det &= ~(1 << 3); + ulpi_write(phy, chg_det, 0x34); + /* put it in host mode for enabling D- source */ + chg_det &= ~(1 << 2); + ulpi_write(phy, chg_det, 0x34); + /* Turn on chg detect block */ + chg_det &= ~(1 << 1); + ulpi_write(phy, chg_det, 0x34); + udelay(20); + /* enable chg detection */ + chg_det &= ~(1 << 0); + ulpi_write(phy, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* + * Configure DM as current source, DP as current sink + * and enable battery charging comparators. + */ + ulpi_write(phy, 0x8, 0x85); + ulpi_write(phy, 0x2, 0x85); + ulpi_write(phy, 0x1, 0x85); + break; + default: + break; + } +} + +static bool msm_chg_check_primary_det(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 chg_det; + bool ret = false; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x34); + ret = chg_det & (1 << 4); + break; + case SNPS_28NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x87); + ret = chg_det & 1; + break; + default: + break; + } + return ret; +} + +static void msm_chg_enable_primary_det(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x34); + /* enable chg detection */ + chg_det &= ~(1 << 0); + ulpi_write(phy, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* + * Configure DP as current source, DM as current sink + * and enable battery charging comparators. + */ + ulpi_write(phy, 0x2, 0x85); + ulpi_write(phy, 0x1, 0x85); + break; + default: + break; + } +} + +static bool msm_chg_check_dcd(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 line_state; + bool ret = false; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + line_state = ulpi_read(phy, 0x15); + ret = !(line_state & 1); + break; + case SNPS_28NM_INTEGRATED_PHY: + line_state = ulpi_read(phy, 0x87); + ret = line_state & 2; + break; + default: + break; + } + return ret; +} + +static void msm_chg_disable_dcd(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x34); + chg_det &= ~(1 << 5); + ulpi_write(phy, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + ulpi_write(phy, 0x10, 0x86); + break; + default: + break; + } +} + +static void msm_chg_enable_dcd(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x34); + /* Turn on D+ current source */ + chg_det |= (1 << 5); + ulpi_write(phy, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* Data contact detection enable */ + ulpi_write(phy, 0x10, 0x85); + break; + default: + break; + } +} + +static void msm_chg_block_on(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 func_ctrl, chg_det; + + /* put the controller in non-driving mode */ + func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); + func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; + ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x34); + /* control chg block via ULPI */ + chg_det &= ~(1 << 3); + ulpi_write(phy, chg_det, 0x34); + /* Turn on chg detect block */ + chg_det &= ~(1 << 1); + ulpi_write(phy, chg_det, 0x34); + udelay(20); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* Clear charger detecting control bits */ + ulpi_write(phy, 0x3F, 0x86); + /* Clear alt interrupt latch and enable bits */ + ulpi_write(phy, 0x1F, 0x92); + ulpi_write(phy, 0x1F, 0x95); + udelay(100); + break; + default: + break; + } +} + +static void msm_chg_block_off(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 func_ctrl, chg_det; + + switch (motg->pdata->phy_type) { + case CI_45NM_INTEGRATED_PHY: + chg_det = ulpi_read(phy, 0x34); + /* Turn off charger block */ + chg_det |= ~(1 << 1); + ulpi_write(phy, chg_det, 0x34); + break; + case SNPS_28NM_INTEGRATED_PHY: + /* Clear charger detecting control bits */ + ulpi_write(phy, 0x3F, 0x86); + /* Clear alt interrupt latch and enable bits */ + ulpi_write(phy, 0x1F, 0x92); + ulpi_write(phy, 0x1F, 0x95); + break; + default: + break; + } + + /* put the controller in normal mode */ + func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); + func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL; + ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); +} + +#define MSM_CHG_DCD_POLL_TIME (100 * HZ/1000) /* 100 msec */ +#define MSM_CHG_DCD_MAX_RETRIES 6 /* Tdcd_tmout = 6 * 100 msec */ +#define MSM_CHG_PRIMARY_DET_TIME (40 * HZ/1000) /* TVDPSRC_ON */ +#define MSM_CHG_SECONDARY_DET_TIME (40 * HZ/1000) /* TVDMSRC_ON */ +static void msm_chg_detect_work(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work); + struct usb_phy *phy = &motg->phy; + bool is_dcd, tmout, vout; + unsigned long delay; + + dev_dbg(phy->dev, "chg detection work\n"); + switch (motg->chg_state) { + case USB_CHG_STATE_UNDEFINED: + pm_runtime_get_sync(phy->dev); + msm_chg_block_on(motg); + msm_chg_enable_dcd(motg); + motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; + motg->dcd_retries = 0; + delay = MSM_CHG_DCD_POLL_TIME; + break; + case USB_CHG_STATE_WAIT_FOR_DCD: + is_dcd = msm_chg_check_dcd(motg); + tmout = ++motg->dcd_retries == MSM_CHG_DCD_MAX_RETRIES; + if (is_dcd || tmout) { + msm_chg_disable_dcd(motg); + msm_chg_enable_primary_det(motg); + delay = MSM_CHG_PRIMARY_DET_TIME; + motg->chg_state = USB_CHG_STATE_DCD_DONE; + } else { + delay = MSM_CHG_DCD_POLL_TIME; + } + break; + case USB_CHG_STATE_DCD_DONE: + vout = msm_chg_check_primary_det(motg); + if (vout) { + msm_chg_enable_secondary_det(motg); + delay = MSM_CHG_SECONDARY_DET_TIME; + motg->chg_state = USB_CHG_STATE_PRIMARY_DONE; + } else { + motg->chg_type = USB_SDP_CHARGER; + motg->chg_state = USB_CHG_STATE_DETECTED; + delay = 0; + } + break; + case USB_CHG_STATE_PRIMARY_DONE: + vout = msm_chg_check_secondary_det(motg); + if (vout) + motg->chg_type = USB_DCP_CHARGER; + else + motg->chg_type = USB_CDP_CHARGER; + motg->chg_state = USB_CHG_STATE_SECONDARY_DONE; + /* fall through */ + case USB_CHG_STATE_SECONDARY_DONE: + motg->chg_state = USB_CHG_STATE_DETECTED; + case USB_CHG_STATE_DETECTED: + msm_chg_block_off(motg); + dev_dbg(phy->dev, "charger = %d\n", motg->chg_type); + schedule_work(&motg->sm_work); + return; + default: + return; + } + + schedule_delayed_work(&motg->chg_work, delay); +} + +/* + * We support OTG, Peripheral only and Host only configurations. In case + * of OTG, mode switch (host-->peripheral/peripheral-->host) can happen + * via Id pin status or user request (debugfs). Id/BSV interrupts are not + * enabled when switch is controlled by user and default mode is supplied + * by board file, which can be changed by userspace later. + */ +static void msm_otg_init_sm(struct msm_otg *motg) +{ + struct msm_otg_platform_data *pdata = motg->pdata; + u32 otgsc = readl(USB_OTGSC); + + switch (pdata->mode) { + case USB_OTG: + if (pdata->otg_control == OTG_PHY_CONTROL) { + if (otgsc & OTGSC_ID) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + } else if (pdata->otg_control == OTG_USER_CONTROL) { + if (pdata->default_mode == USB_HOST) { + clear_bit(ID, &motg->inputs); + } else if (pdata->default_mode == USB_PERIPHERAL) { + set_bit(ID, &motg->inputs); + set_bit(B_SESS_VLD, &motg->inputs); + } else { + set_bit(ID, &motg->inputs); + clear_bit(B_SESS_VLD, &motg->inputs); + } + } + break; + case USB_HOST: + clear_bit(ID, &motg->inputs); + break; + case USB_PERIPHERAL: + set_bit(ID, &motg->inputs); + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + break; + default: + break; + } +} + +static void msm_otg_sm_work(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, sm_work); + struct usb_otg *otg = motg->phy.otg; + + switch (otg->phy->state) { + case OTG_STATE_UNDEFINED: + dev_dbg(otg->phy->dev, "OTG_STATE_UNDEFINED state\n"); + msm_otg_reset(otg->phy); + msm_otg_init_sm(motg); + otg->phy->state = OTG_STATE_B_IDLE; + /* FALL THROUGH */ + case OTG_STATE_B_IDLE: + dev_dbg(otg->phy->dev, "OTG_STATE_B_IDLE state\n"); + if (!test_bit(ID, &motg->inputs) && otg->host) { + /* disable BSV bit */ + writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); + msm_otg_start_host(otg->phy, 1); + otg->phy->state = OTG_STATE_A_HOST; + } else if (test_bit(B_SESS_VLD, &motg->inputs)) { + switch (motg->chg_state) { + case USB_CHG_STATE_UNDEFINED: + msm_chg_detect_work(&motg->chg_work.work); + break; + case USB_CHG_STATE_DETECTED: + switch (motg->chg_type) { + case USB_DCP_CHARGER: + msm_otg_notify_charger(motg, + IDEV_CHG_MAX); + break; + case USB_CDP_CHARGER: + msm_otg_notify_charger(motg, + IDEV_CHG_MAX); + msm_otg_start_peripheral(otg->phy, 1); + otg->phy->state + = OTG_STATE_B_PERIPHERAL; + break; + case USB_SDP_CHARGER: + msm_otg_notify_charger(motg, IUNIT); + msm_otg_start_peripheral(otg->phy, 1); + otg->phy->state + = OTG_STATE_B_PERIPHERAL; + break; + default: + break; + } + break; + default: + break; + } + } else { + /* + * If charger detection work is pending, decrement + * the pm usage counter to balance with the one that + * is incremented in charger detection work. + */ + if (cancel_delayed_work_sync(&motg->chg_work)) { + pm_runtime_put_sync(otg->phy->dev); + msm_otg_reset(otg->phy); + } + msm_otg_notify_charger(motg, 0); + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; + } + pm_runtime_put_sync(otg->phy->dev); + break; + case OTG_STATE_B_PERIPHERAL: + dev_dbg(otg->phy->dev, "OTG_STATE_B_PERIPHERAL state\n"); + if (!test_bit(B_SESS_VLD, &motg->inputs) || + !test_bit(ID, &motg->inputs)) { + msm_otg_notify_charger(motg, 0); + msm_otg_start_peripheral(otg->phy, 0); + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; + otg->phy->state = OTG_STATE_B_IDLE; + msm_otg_reset(otg->phy); + schedule_work(w); + } + break; + case OTG_STATE_A_HOST: + dev_dbg(otg->phy->dev, "OTG_STATE_A_HOST state\n"); + if (test_bit(ID, &motg->inputs)) { + msm_otg_start_host(otg->phy, 0); + otg->phy->state = OTG_STATE_B_IDLE; + msm_otg_reset(otg->phy); + schedule_work(w); + } + break; + default: + break; + } +} + +static irqreturn_t msm_otg_irq(int irq, void *data) +{ + struct msm_otg *motg = data; + struct usb_phy *phy = &motg->phy; + u32 otgsc = 0; + + if (atomic_read(&motg->in_lpm)) { + disable_irq_nosync(irq); + motg->async_int = 1; + pm_runtime_get(phy->dev); + return IRQ_HANDLED; + } + + otgsc = readl(USB_OTGSC); + if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) + return IRQ_NONE; + + if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) { + if (otgsc & OTGSC_ID) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + dev_dbg(phy->dev, "ID set/clear\n"); + pm_runtime_get_noresume(phy->dev); + } else if ((otgsc & OTGSC_BSVIS) && (otgsc & OTGSC_BSVIE)) { + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + dev_dbg(phy->dev, "BSV set/clear\n"); + pm_runtime_get_noresume(phy->dev); + } + + writel(otgsc, USB_OTGSC); + schedule_work(&motg->sm_work); + return IRQ_HANDLED; +} + +static int msm_otg_mode_show(struct seq_file *s, void *unused) +{ + struct msm_otg *motg = s->private; + struct usb_otg *otg = motg->phy.otg; + + switch (otg->phy->state) { + case OTG_STATE_A_HOST: + seq_printf(s, "host\n"); + break; + case OTG_STATE_B_PERIPHERAL: + seq_printf(s, "peripheral\n"); + break; + default: + seq_printf(s, "none\n"); + break; + } + + return 0; +} + +static int msm_otg_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_mode_show, inode->i_private); +} + +static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct msm_otg *motg = s->private; + char buf[16]; + struct usb_otg *otg = motg->phy.otg; + int status = count; + enum usb_mode_type req_mode; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) { + status = -EFAULT; + goto out; + } + + if (!strncmp(buf, "host", 4)) { + req_mode = USB_HOST; + } else if (!strncmp(buf, "peripheral", 10)) { + req_mode = USB_PERIPHERAL; + } else if (!strncmp(buf, "none", 4)) { + req_mode = USB_NONE; + } else { + status = -EINVAL; + goto out; + } + + switch (req_mode) { + case USB_NONE: + switch (otg->phy->state) { + case OTG_STATE_A_HOST: + case OTG_STATE_B_PERIPHERAL: + set_bit(ID, &motg->inputs); + clear_bit(B_SESS_VLD, &motg->inputs); + break; + default: + goto out; + } + break; + case USB_PERIPHERAL: + switch (otg->phy->state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_A_HOST: + set_bit(ID, &motg->inputs); + set_bit(B_SESS_VLD, &motg->inputs); + break; + default: + goto out; + } + break; + case USB_HOST: + switch (otg->phy->state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_B_PERIPHERAL: + clear_bit(ID, &motg->inputs); + break; + default: + goto out; + } + break; + default: + goto out; + } + + pm_runtime_get_sync(otg->phy->dev); + schedule_work(&motg->sm_work); +out: + return status; +} + +const struct file_operations msm_otg_mode_fops = { + .open = msm_otg_mode_open, + .read = seq_read, + .write = msm_otg_mode_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *msm_otg_dbg_root; +static struct dentry *msm_otg_dbg_mode; + +static int msm_otg_debugfs_init(struct msm_otg *motg) +{ + msm_otg_dbg_root = debugfs_create_dir("msm_otg", NULL); + + if (!msm_otg_dbg_root || IS_ERR(msm_otg_dbg_root)) + return -ENODEV; + + msm_otg_dbg_mode = debugfs_create_file("mode", S_IRUGO | S_IWUSR, + msm_otg_dbg_root, motg, &msm_otg_mode_fops); + if (!msm_otg_dbg_mode) { + debugfs_remove(msm_otg_dbg_root); + msm_otg_dbg_root = NULL; + return -ENODEV; + } + + return 0; +} + +static void msm_otg_debugfs_cleanup(void) +{ + debugfs_remove(msm_otg_dbg_mode); + debugfs_remove(msm_otg_dbg_root); +} + +static int __init msm_otg_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res; + struct msm_otg *motg; + struct usb_phy *phy; + + dev_info(&pdev->dev, "msm_otg probe\n"); + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "No platform data given. Bailing out\n"); + return -ENODEV; + } + + motg = kzalloc(sizeof(struct msm_otg), GFP_KERNEL); + if (!motg) { + dev_err(&pdev->dev, "unable to allocate msm_otg\n"); + return -ENOMEM; + } + + motg->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); + if (!motg->phy.otg) { + dev_err(&pdev->dev, "unable to allocate msm_otg\n"); + return -ENOMEM; + } + + motg->pdata = pdev->dev.platform_data; + phy = &motg->phy; + phy->dev = &pdev->dev; + + motg->phy_reset_clk = clk_get(&pdev->dev, "usb_phy_clk"); + if (IS_ERR(motg->phy_reset_clk)) { + dev_err(&pdev->dev, "failed to get usb_phy_clk\n"); + ret = PTR_ERR(motg->phy_reset_clk); + goto free_motg; + } + + motg->clk = clk_get(&pdev->dev, "usb_hs_clk"); + if (IS_ERR(motg->clk)) { + dev_err(&pdev->dev, "failed to get usb_hs_clk\n"); + ret = PTR_ERR(motg->clk); + goto put_phy_reset_clk; + } + clk_set_rate(motg->clk, 60000000); + + /* + * If USB Core is running its protocol engine based on CORE CLK, + * CORE CLK must be running at >55Mhz for correct HSUSB + * operation and USB core cannot tolerate frequency changes on + * CORE CLK. For such USB cores, vote for maximum clk frequency + * on pclk source + */ + if (motg->pdata->pclk_src_name) { + motg->pclk_src = clk_get(&pdev->dev, + motg->pdata->pclk_src_name); + if (IS_ERR(motg->pclk_src)) + goto put_clk; + clk_set_rate(motg->pclk_src, INT_MAX); + clk_enable(motg->pclk_src); + } else + motg->pclk_src = ERR_PTR(-ENOENT); + + + motg->pclk = clk_get(&pdev->dev, "usb_hs_pclk"); + if (IS_ERR(motg->pclk)) { + dev_err(&pdev->dev, "failed to get usb_hs_pclk\n"); + ret = PTR_ERR(motg->pclk); + goto put_pclk_src; + } + + /* + * USB core clock is not present on all MSM chips. This + * clock is introduced to remove the dependency on AXI + * bus frequency. + */ + motg->core_clk = clk_get(&pdev->dev, "usb_hs_core_clk"); + if (IS_ERR(motg->core_clk)) + motg->core_clk = NULL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform resource mem\n"); + ret = -ENODEV; + goto put_core_clk; + } + + motg->regs = ioremap(res->start, resource_size(res)); + if (!motg->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto put_core_clk; + } + dev_info(&pdev->dev, "OTG regs = %p\n", motg->regs); + + motg->irq = platform_get_irq(pdev, 0); + if (!motg->irq) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + ret = -ENODEV; + goto free_regs; + } + + clk_enable(motg->clk); + clk_enable(motg->pclk); + + ret = msm_hsusb_init_vddcx(motg, 1); + if (ret) { + dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); + goto free_regs; + } + + ret = msm_hsusb_ldo_init(motg, 1); + if (ret) { + dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); + goto vddcx_exit; + } + ret = msm_hsusb_ldo_set_mode(1); + if (ret) { + dev_err(&pdev->dev, "hsusb vreg enable failed\n"); + goto ldo_exit; + } + + if (motg->core_clk) + clk_enable(motg->core_clk); + + writel(0, USB_USBINTR); + writel(0, USB_OTGSC); + + INIT_WORK(&motg->sm_work, msm_otg_sm_work); + INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); + ret = request_irq(motg->irq, msm_otg_irq, IRQF_SHARED, + "msm_otg", motg); + if (ret) { + dev_err(&pdev->dev, "request irq failed\n"); + goto disable_clks; + } + + phy->init = msm_otg_reset; + phy->set_power = msm_otg_set_power; + + phy->io_ops = &msm_otg_io_ops; + + phy->otg->phy = &motg->phy; + phy->otg->set_host = msm_otg_set_host; + phy->otg->set_peripheral = msm_otg_set_peripheral; + + ret = usb_add_phy(&motg->phy, USB_PHY_TYPE_USB2); + if (ret) { + dev_err(&pdev->dev, "usb_add_phy failed\n"); + goto free_irq; + } + + platform_set_drvdata(pdev, motg); + device_init_wakeup(&pdev->dev, 1); + + if (motg->pdata->mode == USB_OTG && + motg->pdata->otg_control == OTG_USER_CONTROL) { + ret = msm_otg_debugfs_init(motg); + if (ret) + dev_dbg(&pdev->dev, "mode debugfs file is" + "not available\n"); + } + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; +free_irq: + free_irq(motg->irq, motg); +disable_clks: + clk_disable(motg->pclk); + clk_disable(motg->clk); +ldo_exit: + msm_hsusb_ldo_init(motg, 0); +vddcx_exit: + msm_hsusb_init_vddcx(motg, 0); +free_regs: + iounmap(motg->regs); +put_core_clk: + if (motg->core_clk) + clk_put(motg->core_clk); + clk_put(motg->pclk); +put_pclk_src: + if (!IS_ERR(motg->pclk_src)) { + clk_disable(motg->pclk_src); + clk_put(motg->pclk_src); + } +put_clk: + clk_put(motg->clk); +put_phy_reset_clk: + clk_put(motg->phy_reset_clk); +free_motg: + kfree(motg->phy.otg); + kfree(motg); + return ret; +} + +static int msm_otg_remove(struct platform_device *pdev) +{ + struct msm_otg *motg = platform_get_drvdata(pdev); + struct usb_phy *phy = &motg->phy; + int cnt = 0; + + if (phy->otg->host || phy->otg->gadget) + return -EBUSY; + + msm_otg_debugfs_cleanup(); + cancel_delayed_work_sync(&motg->chg_work); + cancel_work_sync(&motg->sm_work); + + pm_runtime_resume(&pdev->dev); + + device_init_wakeup(&pdev->dev, 0); + pm_runtime_disable(&pdev->dev); + + usb_remove_phy(phy); + free_irq(motg->irq, motg); + + /* + * Put PHY in low power mode. + */ + ulpi_read(phy, 0x14); + ulpi_write(phy, 0x08, 0x09); + + writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); + while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { + if (readl(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } + if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) + dev_err(phy->dev, "Unable to suspend PHY\n"); + + clk_disable(motg->pclk); + clk_disable(motg->clk); + if (motg->core_clk) + clk_disable(motg->core_clk); + if (!IS_ERR(motg->pclk_src)) { + clk_disable(motg->pclk_src); + clk_put(motg->pclk_src); + } + msm_hsusb_ldo_init(motg, 0); + + iounmap(motg->regs); + pm_runtime_set_suspended(&pdev->dev); + + clk_put(motg->phy_reset_clk); + clk_put(motg->pclk); + clk_put(motg->clk); + if (motg->core_clk) + clk_put(motg->core_clk); + + kfree(motg->phy.otg); + kfree(motg); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int msm_otg_runtime_idle(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + struct usb_otg *otg = motg->phy.otg; + + dev_dbg(dev, "OTG runtime idle\n"); + + /* + * It is observed some times that a spurious interrupt + * comes when PHY is put into LPM immediately after PHY reset. + * This 1 sec delay also prevents entering into LPM immediately + * after asynchronous interrupt. + */ + if (otg->phy->state != OTG_STATE_UNDEFINED) + pm_schedule_suspend(dev, 1000); + + return -EAGAIN; +} + +static int msm_otg_runtime_suspend(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG runtime suspend\n"); + return msm_otg_suspend(motg); +} + +static int msm_otg_runtime_resume(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG runtime resume\n"); + return msm_otg_resume(motg); +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int msm_otg_pm_suspend(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG PM suspend\n"); + return msm_otg_suspend(motg); +} + +static int msm_otg_pm_resume(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "OTG PM resume\n"); + + ret = msm_otg_resume(motg); + if (ret) + return ret; + + /* + * Runtime PM Documentation recommends bringing the + * device to full powered state upon resume. + */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops msm_otg_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(msm_otg_pm_suspend, msm_otg_pm_resume) + SET_RUNTIME_PM_OPS(msm_otg_runtime_suspend, msm_otg_runtime_resume, + msm_otg_runtime_idle) +}; +#endif + +static struct platform_driver msm_otg_driver = { + .remove = msm_otg_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &msm_otg_dev_pm_ops, +#endif + }, +}; + +module_platform_driver_probe(msm_otg_driver, msm_otg_probe); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM USB transceiver driver"); diff --git a/drivers/usb/phy/phy-mv-u3d-usb.c b/drivers/usb/phy/phy-mv-u3d-usb.c new file mode 100644 index 00000000..1568ea63 --- /dev/null +++ b/drivers/usb/phy/phy-mv-u3d-usb.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-mv-u3d-usb.h" + +/* + * struct mv_u3d_phy - transceiver driver state + * @phy: transceiver structure + * @dev: The parent device supplied to the probe function + * @clk: usb phy clock + * @base: usb phy register memory base + */ +struct mv_u3d_phy { + struct usb_phy phy; + struct mv_usb_platform_data *plat; + struct device *dev; + struct clk *clk; + void __iomem *base; +}; + +static u32 mv_u3d_phy_read(void __iomem *base, u32 reg) +{ + void __iomem *addr, *data; + + addr = base; + data = base + 0x4; + + writel_relaxed(reg, addr); + return readl_relaxed(data); +} + +static void mv_u3d_phy_set(void __iomem *base, u32 reg, u32 value) +{ + void __iomem *addr, *data; + u32 tmp; + + addr = base; + data = base + 0x4; + + writel_relaxed(reg, addr); + tmp = readl_relaxed(data); + tmp |= value; + writel_relaxed(tmp, data); +} + +static void mv_u3d_phy_clear(void __iomem *base, u32 reg, u32 value) +{ + void __iomem *addr, *data; + u32 tmp; + + addr = base; + data = base + 0x4; + + writel_relaxed(reg, addr); + tmp = readl_relaxed(data); + tmp &= ~value; + writel_relaxed(tmp, data); +} + +static void mv_u3d_phy_write(void __iomem *base, u32 reg, u32 value) +{ + void __iomem *addr, *data; + + addr = base; + data = base + 0x4; + + writel_relaxed(reg, addr); + writel_relaxed(value, data); +} + +void mv_u3d_phy_shutdown(struct usb_phy *phy) +{ + struct mv_u3d_phy *mv_u3d_phy; + void __iomem *base; + u32 val; + + mv_u3d_phy = container_of(phy, struct mv_u3d_phy, phy); + base = mv_u3d_phy->base; + + /* Power down Reference Analog current, bit 15 + * Power down PLL, bit 14 + * Power down Receiver, bit 13 + * Power down Transmitter, bit 12 + * of USB3_POWER_PLL_CONTROL register + */ + val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); + val &= ~(USB3_POWER_PLL_CONTROL_PU); + mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); + + if (mv_u3d_phy->clk) + clk_disable(mv_u3d_phy->clk); +} + +static int mv_u3d_phy_init(struct usb_phy *phy) +{ + struct mv_u3d_phy *mv_u3d_phy; + void __iomem *base; + u32 val, count; + + /* enable usb3 phy */ + mv_u3d_phy = container_of(phy, struct mv_u3d_phy, phy); + + if (mv_u3d_phy->clk) + clk_enable(mv_u3d_phy->clk); + + base = mv_u3d_phy->base; + + val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); + val &= ~(USB3_POWER_PLL_CONTROL_PU_MASK); + val |= 0xF << USB3_POWER_PLL_CONTROL_PU_SHIFT; + mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); + udelay(100); + + mv_u3d_phy_write(base, USB3_RESET_CONTROL, + USB3_RESET_CONTROL_RESET_PIPE); + udelay(100); + + mv_u3d_phy_write(base, USB3_RESET_CONTROL, + USB3_RESET_CONTROL_RESET_PIPE + | USB3_RESET_CONTROL_RESET_PHY); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); + val &= ~(USB3_POWER_PLL_CONTROL_REF_FREF_SEL_MASK + | USB3_POWER_PLL_CONTROL_PHY_MODE_MASK); + val |= (USB3_PLL_25MHZ << USB3_POWER_PLL_CONTROL_REF_FREF_SEL_SHIFT) + | (0x5 << USB3_POWER_PLL_CONTROL_PHY_MODE_SHIFT); + mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); + udelay(100); + + mv_u3d_phy_clear(base, USB3_KVCO_CALI_CONTROL, + USB3_KVCO_CALI_CONTROL_USE_MAX_PLL_RATE_MASK); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_SQUELCH_FFE); + val &= ~(USB3_SQUELCH_FFE_FFE_CAP_SEL_MASK + | USB3_SQUELCH_FFE_FFE_RES_SEL_MASK + | USB3_SQUELCH_FFE_SQ_THRESH_IN_MASK); + val |= ((0xD << USB3_SQUELCH_FFE_FFE_CAP_SEL_SHIFT) + | (0x7 << USB3_SQUELCH_FFE_FFE_RES_SEL_SHIFT) + | (0x8 << USB3_SQUELCH_FFE_SQ_THRESH_IN_SHIFT)); + mv_u3d_phy_write(base, USB3_SQUELCH_FFE, val); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_GEN1_SET0); + val &= ~USB3_GEN1_SET0_G1_TX_SLEW_CTRL_EN_MASK; + val |= 1 << USB3_GEN1_SET0_G1_TX_EMPH_EN_SHIFT; + mv_u3d_phy_write(base, USB3_GEN1_SET0, val); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_GEN2_SET0); + val &= ~(USB3_GEN2_SET0_G2_TX_AMP_MASK + | USB3_GEN2_SET0_G2_TX_EMPH_AMP_MASK + | USB3_GEN2_SET0_G2_TX_SLEW_CTRL_EN_MASK); + val |= ((0x14 << USB3_GEN2_SET0_G2_TX_AMP_SHIFT) + | (1 << USB3_GEN2_SET0_G2_TX_AMP_ADJ_SHIFT) + | (0xA << USB3_GEN2_SET0_G2_TX_EMPH_AMP_SHIFT) + | (1 << USB3_GEN2_SET0_G2_TX_EMPH_EN_SHIFT)); + mv_u3d_phy_write(base, USB3_GEN2_SET0, val); + udelay(100); + + mv_u3d_phy_read(base, USB3_TX_EMPPH); + val &= ~(USB3_TX_EMPPH_AMP_MASK + | USB3_TX_EMPPH_EN_MASK + | USB3_TX_EMPPH_AMP_FORCE_MASK + | USB3_TX_EMPPH_PAR1_MASK + | USB3_TX_EMPPH_PAR2_MASK); + val |= ((0xB << USB3_TX_EMPPH_AMP_SHIFT) + | (1 << USB3_TX_EMPPH_EN_SHIFT) + | (1 << USB3_TX_EMPPH_AMP_FORCE_SHIFT) + | (0x1C << USB3_TX_EMPPH_PAR1_SHIFT) + | (1 << USB3_TX_EMPPH_PAR2_SHIFT)); + + mv_u3d_phy_write(base, USB3_TX_EMPPH, val); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_GEN2_SET1); + val &= ~(USB3_GEN2_SET1_G2_RX_SELMUPI_MASK + | USB3_GEN2_SET1_G2_RX_SELMUPF_MASK + | USB3_GEN2_SET1_G2_RX_SELMUFI_MASK + | USB3_GEN2_SET1_G2_RX_SELMUFF_MASK); + val |= ((1 << USB3_GEN2_SET1_G2_RX_SELMUPI_SHIFT) + | (1 << USB3_GEN2_SET1_G2_RX_SELMUPF_SHIFT) + | (1 << USB3_GEN2_SET1_G2_RX_SELMUFI_SHIFT) + | (1 << USB3_GEN2_SET1_G2_RX_SELMUFF_SHIFT)); + mv_u3d_phy_write(base, USB3_GEN2_SET1, val); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_DIGITAL_LOOPBACK_EN); + val &= ~USB3_DIGITAL_LOOPBACK_EN_SEL_BITS_MASK; + val |= 1 << USB3_DIGITAL_LOOPBACK_EN_SEL_BITS_SHIFT; + mv_u3d_phy_write(base, USB3_DIGITAL_LOOPBACK_EN, val); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_IMPEDANCE_TX_SSC); + val &= ~USB3_IMPEDANCE_TX_SSC_SSC_AMP_MASK; + val |= 0xC << USB3_IMPEDANCE_TX_SSC_SSC_AMP_SHIFT; + mv_u3d_phy_write(base, USB3_IMPEDANCE_TX_SSC, val); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_IMPEDANCE_CALI_CTRL); + val &= ~USB3_IMPEDANCE_CALI_CTRL_IMP_CAL_THR_MASK; + val |= 0x4 << USB3_IMPEDANCE_CALI_CTRL_IMP_CAL_THR_SHIFT; + mv_u3d_phy_write(base, USB3_IMPEDANCE_CALI_CTRL, val); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_PHY_ISOLATION_MODE); + val &= ~(USB3_PHY_ISOLATION_MODE_PHY_GEN_RX_MASK + | USB3_PHY_ISOLATION_MODE_PHY_GEN_TX_MASK + | USB3_PHY_ISOLATION_MODE_TX_DRV_IDLE_MASK); + val |= ((1 << USB3_PHY_ISOLATION_MODE_PHY_GEN_RX_SHIFT) + | (1 << USB3_PHY_ISOLATION_MODE_PHY_GEN_TX_SHIFT)); + mv_u3d_phy_write(base, USB3_PHY_ISOLATION_MODE, val); + udelay(100); + + val = mv_u3d_phy_read(base, USB3_TXDETRX); + val &= ~(USB3_TXDETRX_VTHSEL_MASK); + val |= 0x1 << USB3_TXDETRX_VTHSEL_SHIFT; + mv_u3d_phy_write(base, USB3_TXDETRX, val); + udelay(100); + + dev_dbg(mv_u3d_phy->dev, "start calibration\n"); + +calstart: + /* Perform Manual Calibration */ + mv_u3d_phy_set(base, USB3_KVCO_CALI_CONTROL, + 1 << USB3_KVCO_CALI_CONTROL_CAL_START_SHIFT); + + mdelay(1); + + count = 0; + while (1) { + val = mv_u3d_phy_read(base, USB3_KVCO_CALI_CONTROL); + if (val & (1 << USB3_KVCO_CALI_CONTROL_CAL_DONE_SHIFT)) + break; + else if (count > 50) { + dev_dbg(mv_u3d_phy->dev, "calibration failure, retry...\n"); + goto calstart; + } + count++; + mdelay(1); + } + + /* active PIPE interface */ + mv_u3d_phy_write(base, USB3_PIPE_SM_CTRL, + 1 << USB3_PIPE_SM_CTRL_PHY_INIT_DONE); + + return 0; +} + +static int mv_u3d_phy_probe(struct platform_device *pdev) +{ + struct mv_u3d_phy *mv_u3d_phy; + struct mv_usb_platform_data *pdata; + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *phy_base; + int ret; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "%s: no platform data defined\n", __func__); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy_base)) + return PTR_ERR(phy_base); + + mv_u3d_phy = devm_kzalloc(dev, sizeof(*mv_u3d_phy), GFP_KERNEL); + if (!mv_u3d_phy) + return -ENOMEM; + + mv_u3d_phy->dev = &pdev->dev; + mv_u3d_phy->plat = pdata; + mv_u3d_phy->base = phy_base; + mv_u3d_phy->phy.dev = mv_u3d_phy->dev; + mv_u3d_phy->phy.label = "mv-u3d-phy"; + mv_u3d_phy->phy.init = mv_u3d_phy_init; + mv_u3d_phy->phy.shutdown = mv_u3d_phy_shutdown; + + ret = usb_add_phy(&mv_u3d_phy->phy, USB_PHY_TYPE_USB3); + if (ret) + goto err; + + if (!mv_u3d_phy->clk) + mv_u3d_phy->clk = clk_get(mv_u3d_phy->dev, "u3dphy"); + + platform_set_drvdata(pdev, mv_u3d_phy); + + dev_info(&pdev->dev, "Initialized Marvell USB 3.0 PHY\n"); +err: + return ret; +} + +static int mv_u3d_phy_remove(struct platform_device *pdev) +{ + struct mv_u3d_phy *mv_u3d_phy = platform_get_drvdata(pdev); + + usb_remove_phy(&mv_u3d_phy->phy); + + if (mv_u3d_phy->clk) { + clk_put(mv_u3d_phy->clk); + mv_u3d_phy->clk = NULL; + } + + return 0; +} + +static struct platform_driver mv_u3d_phy_driver = { + .probe = mv_u3d_phy_probe, + .remove = mv_u3d_phy_remove, + .driver = { + .name = "mv-u3d-phy", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(mv_u3d_phy_driver); +MODULE_DESCRIPTION("Marvell USB 3.0 PHY controller"); +MODULE_AUTHOR("Yu Xu "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mv-u3d-phy"); diff --git a/drivers/usb/phy/phy-mv-u3d-usb.h b/drivers/usb/phy/phy-mv-u3d-usb.h new file mode 100644 index 00000000..2a658cb9 --- /dev/null +++ b/drivers/usb/phy/phy-mv-u3d-usb.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#ifndef __MV_U3D_PHY_H +#define __MV_U3D_PHY_H + +#define USB3_POWER_PLL_CONTROL 0x1 +#define USB3_KVCO_CALI_CONTROL 0x2 +#define USB3_IMPEDANCE_CALI_CTRL 0x3 +#define USB3_IMPEDANCE_TX_SSC 0x4 +#define USB3_SQUELCH_FFE 0x6 +#define USB3_GEN1_SET0 0xD +#define USB3_GEN2_SET0 0xF +#define USB3_GEN2_SET1 0x10 +#define USB3_DIGITAL_LOOPBACK_EN 0x23 +#define USB3_PHY_ISOLATION_MODE 0x26 +#define USB3_TXDETRX 0x48 +#define USB3_TX_EMPPH 0x5E +#define USB3_RESET_CONTROL 0x90 +#define USB3_PIPE_SM_CTRL 0x91 + +#define USB3_RESET_CONTROL_RESET_PIPE 0x1 +#define USB3_RESET_CONTROL_RESET_PHY 0x2 + +#define USB3_POWER_PLL_CONTROL_REF_FREF_SEL_MASK (0x1F << 0) +#define USB3_POWER_PLL_CONTROL_REF_FREF_SEL_SHIFT 0 +#define USB3_PLL_25MHZ 0x2 +#define USB3_PLL_26MHZ 0x5 +#define USB3_POWER_PLL_CONTROL_PHY_MODE_MASK (0x7 << 5) +#define USB3_POWER_PLL_CONTROL_PHY_MODE_SHIFT 5 +#define USB3_POWER_PLL_CONTROL_PU_MASK (0xF << 12) +#define USB3_POWER_PLL_CONTROL_PU_SHIFT 12 +#define USB3_POWER_PLL_CONTROL_PU (0xF << 12) + +#define USB3_KVCO_CALI_CONTROL_USE_MAX_PLL_RATE_MASK (0x1 << 12) +#define USB3_KVCO_CALI_CONTROL_USE_MAX_PLL_RATE_SHIFT 12 +#define USB3_KVCO_CALI_CONTROL_CAL_DONE_SHIFT 14 +#define USB3_KVCO_CALI_CONTROL_CAL_START_SHIFT 15 + +#define USB3_SQUELCH_FFE_FFE_CAP_SEL_MASK 0xF +#define USB3_SQUELCH_FFE_FFE_CAP_SEL_SHIFT 0 +#define USB3_SQUELCH_FFE_FFE_RES_SEL_MASK (0x7 << 4) +#define USB3_SQUELCH_FFE_FFE_RES_SEL_SHIFT 4 +#define USB3_SQUELCH_FFE_SQ_THRESH_IN_MASK (0x1F << 8) +#define USB3_SQUELCH_FFE_SQ_THRESH_IN_SHIFT 8 + +#define USB3_GEN1_SET0_G1_TX_SLEW_CTRL_EN_MASK (0x1 << 15) +#define USB3_GEN1_SET0_G1_TX_EMPH_EN_SHIFT 11 + +#define USB3_GEN2_SET0_G2_TX_AMP_MASK (0x1F << 1) +#define USB3_GEN2_SET0_G2_TX_AMP_SHIFT 1 +#define USB3_GEN2_SET0_G2_TX_AMP_ADJ_SHIFT 6 +#define USB3_GEN2_SET0_G2_TX_EMPH_AMP_MASK (0xF << 7) +#define USB3_GEN2_SET0_G2_TX_EMPH_AMP_SHIFT 7 +#define USB3_GEN2_SET0_G2_TX_EMPH_EN_MASK (0x1 << 11) +#define USB3_GEN2_SET0_G2_TX_EMPH_EN_SHIFT 11 +#define USB3_GEN2_SET0_G2_TX_SLEW_CTRL_EN_MASK (0x1 << 15) +#define USB3_GEN2_SET0_G2_TX_SLEW_CTRL_EN_SHIFT 15 + +#define USB3_GEN2_SET1_G2_RX_SELMUPI_MASK (0x7 << 0) +#define USB3_GEN2_SET1_G2_RX_SELMUPI_SHIFT 0 +#define USB3_GEN2_SET1_G2_RX_SELMUPF_MASK (0x7 << 3) +#define USB3_GEN2_SET1_G2_RX_SELMUPF_SHIFT 3 +#define USB3_GEN2_SET1_G2_RX_SELMUFI_MASK (0x3 << 6) +#define USB3_GEN2_SET1_G2_RX_SELMUFI_SHIFT 6 +#define USB3_GEN2_SET1_G2_RX_SELMUFF_MASK (0x3 << 8) +#define USB3_GEN2_SET1_G2_RX_SELMUFF_SHIFT 8 + +#define USB3_DIGITAL_LOOPBACK_EN_SEL_BITS_MASK (0x3 << 10) +#define USB3_DIGITAL_LOOPBACK_EN_SEL_BITS_SHIFT 10 + +#define USB3_IMPEDANCE_CALI_CTRL_IMP_CAL_THR_MASK (0x7 << 12) +#define USB3_IMPEDANCE_CALI_CTRL_IMP_CAL_THR_SHIFT 12 + +#define USB3_IMPEDANCE_TX_SSC_SSC_AMP_MASK (0x3F << 0) +#define USB3_IMPEDANCE_TX_SSC_SSC_AMP_SHIFT 0 + +#define USB3_PHY_ISOLATION_MODE_PHY_GEN_RX_MASK 0xF +#define USB3_PHY_ISOLATION_MODE_PHY_GEN_RX_SHIFT 0 +#define USB3_PHY_ISOLATION_MODE_PHY_GEN_TX_MASK (0xF << 4) +#define USB3_PHY_ISOLATION_MODE_PHY_GEN_TX_SHIFT 4 +#define USB3_PHY_ISOLATION_MODE_TX_DRV_IDLE_MASK (0x1 << 8) + +#define USB3_TXDETRX_VTHSEL_MASK (0x3 << 4) +#define USB3_TXDETRX_VTHSEL_SHIFT 4 + +#define USB3_TX_EMPPH_AMP_MASK (0xF << 0) +#define USB3_TX_EMPPH_AMP_SHIFT 0 +#define USB3_TX_EMPPH_EN_MASK (0x1 << 6) +#define USB3_TX_EMPPH_EN_SHIFT 6 +#define USB3_TX_EMPPH_AMP_FORCE_MASK (0x1 << 7) +#define USB3_TX_EMPPH_AMP_FORCE_SHIFT 7 +#define USB3_TX_EMPPH_PAR1_MASK (0x1F << 8) +#define USB3_TX_EMPPH_PAR1_SHIFT 8 +#define USB3_TX_EMPPH_PAR2_MASK (0x1 << 13) +#define USB3_TX_EMPPH_PAR2_SHIFT 13 + +#define USB3_PIPE_SM_CTRL_PHY_INIT_DONE 15 + +#endif /* __MV_U3D_PHY_H */ diff --git a/drivers/usb/phy/phy-mv-usb.c b/drivers/usb/phy/phy-mv-usb.c new file mode 100644 index 00000000..4a6b03c7 --- /dev/null +++ b/drivers/usb/phy/phy-mv-usb.c @@ -0,0 +1,906 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * Author: Chao Xie + * Neil Zhang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "phy-mv-usb.h" + +#define DRIVER_DESC "Marvell USB OTG transceiver driver" +#define DRIVER_VERSION "Jan 20, 2010" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + +static const char driver_name[] = "mv-otg"; + +static char *state_string[] = { + "undefined", + "b_idle", + "b_srp_init", + "b_peripheral", + "b_wait_acon", + "b_host", + "a_idle", + "a_wait_vrise", + "a_wait_bcon", + "a_host", + "a_suspend", + "a_peripheral", + "a_wait_vfall", + "a_vbus_err" +}; + +static int mv_otg_set_vbus(struct usb_otg *otg, bool on) +{ + struct mv_otg *mvotg = container_of(otg->phy, struct mv_otg, phy); + if (mvotg->pdata->set_vbus == NULL) + return -ENODEV; + + return mvotg->pdata->set_vbus(on); +} + +static int mv_otg_set_host(struct usb_otg *otg, + struct usb_bus *host) +{ + otg->host = host; + + return 0; +} + +static int mv_otg_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + + return 0; +} + +static void mv_otg_run_state_machine(struct mv_otg *mvotg, + unsigned long delay) +{ + dev_dbg(&mvotg->pdev->dev, "transceiver is updated\n"); + if (!mvotg->qwork) + return; + + queue_delayed_work(mvotg->qwork, &mvotg->work, delay); +} + +static void mv_otg_timer_await_bcon(unsigned long data) +{ + struct mv_otg *mvotg = (struct mv_otg *) data; + + mvotg->otg_ctrl.a_wait_bcon_timeout = 1; + + dev_info(&mvotg->pdev->dev, "B Device No Response!\n"); + + if (spin_trylock(&mvotg->wq_lock)) { + mv_otg_run_state_machine(mvotg, 0); + spin_unlock(&mvotg->wq_lock); + } +} + +static int mv_otg_cancel_timer(struct mv_otg *mvotg, unsigned int id) +{ + struct timer_list *timer; + + if (id >= OTG_TIMER_NUM) + return -EINVAL; + + timer = &mvotg->otg_ctrl.timer[id]; + + if (timer_pending(timer)) + del_timer(timer); + + return 0; +} + +static int mv_otg_set_timer(struct mv_otg *mvotg, unsigned int id, + unsigned long interval, + void (*callback) (unsigned long)) +{ + struct timer_list *timer; + + if (id >= OTG_TIMER_NUM) + return -EINVAL; + + timer = &mvotg->otg_ctrl.timer[id]; + if (timer_pending(timer)) { + dev_err(&mvotg->pdev->dev, "Timer%d is already running\n", id); + return -EBUSY; + } + + init_timer(timer); + timer->data = (unsigned long) mvotg; + timer->function = callback; + timer->expires = jiffies + interval; + add_timer(timer); + + return 0; +} + +static int mv_otg_reset(struct mv_otg *mvotg) +{ + unsigned int loops; + u32 tmp; + + /* Stop the controller */ + tmp = readl(&mvotg->op_regs->usbcmd); + tmp &= ~USBCMD_RUN_STOP; + writel(tmp, &mvotg->op_regs->usbcmd); + + /* Reset the controller to get default values */ + writel(USBCMD_CTRL_RESET, &mvotg->op_regs->usbcmd); + + loops = 500; + while (readl(&mvotg->op_regs->usbcmd) & USBCMD_CTRL_RESET) { + if (loops == 0) { + dev_err(&mvotg->pdev->dev, + "Wait for RESET completed TIMEOUT\n"); + return -ETIMEDOUT; + } + loops--; + udelay(20); + } + + writel(0x0, &mvotg->op_regs->usbintr); + tmp = readl(&mvotg->op_regs->usbsts); + writel(tmp, &mvotg->op_regs->usbsts); + + return 0; +} + +static void mv_otg_init_irq(struct mv_otg *mvotg) +{ + u32 otgsc; + + mvotg->irq_en = OTGSC_INTR_A_SESSION_VALID + | OTGSC_INTR_A_VBUS_VALID; + mvotg->irq_status = OTGSC_INTSTS_A_SESSION_VALID + | OTGSC_INTSTS_A_VBUS_VALID; + + if (mvotg->pdata->vbus == NULL) { + mvotg->irq_en |= OTGSC_INTR_B_SESSION_VALID + | OTGSC_INTR_B_SESSION_END; + mvotg->irq_status |= OTGSC_INTSTS_B_SESSION_VALID + | OTGSC_INTSTS_B_SESSION_END; + } + + if (mvotg->pdata->id == NULL) { + mvotg->irq_en |= OTGSC_INTR_USB_ID; + mvotg->irq_status |= OTGSC_INTSTS_USB_ID; + } + + otgsc = readl(&mvotg->op_regs->otgsc); + otgsc |= mvotg->irq_en; + writel(otgsc, &mvotg->op_regs->otgsc); +} + +static void mv_otg_start_host(struct mv_otg *mvotg, int on) +{ +#ifdef CONFIG_USB + struct usb_otg *otg = mvotg->phy.otg; + struct usb_hcd *hcd; + + if (!otg->host) + return; + + dev_info(&mvotg->pdev->dev, "%s host\n", on ? "start" : "stop"); + + hcd = bus_to_hcd(otg->host); + + if (on) + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + else + usb_remove_hcd(hcd); +#endif /* CONFIG_USB */ +} + +static void mv_otg_start_periphrals(struct mv_otg *mvotg, int on) +{ + struct usb_otg *otg = mvotg->phy.otg; + + if (!otg->gadget) + return; + + dev_info(mvotg->phy.dev, "gadget %s\n", on ? "on" : "off"); + + if (on) + usb_gadget_vbus_connect(otg->gadget); + else + usb_gadget_vbus_disconnect(otg->gadget); +} + +static void otg_clock_enable(struct mv_otg *mvotg) +{ + clk_prepare_enable(mvotg->clk); +} + +static void otg_clock_disable(struct mv_otg *mvotg) +{ + clk_disable_unprepare(mvotg->clk); +} + +static int mv_otg_enable_internal(struct mv_otg *mvotg) +{ + int retval = 0; + + if (mvotg->active) + return 0; + + dev_dbg(&mvotg->pdev->dev, "otg enabled\n"); + + otg_clock_enable(mvotg); + if (mvotg->pdata->phy_init) { + retval = mvotg->pdata->phy_init(mvotg->phy_regs); + if (retval) { + dev_err(&mvotg->pdev->dev, + "init phy error %d\n", retval); + otg_clock_disable(mvotg); + return retval; + } + } + mvotg->active = 1; + + return 0; + +} + +static int mv_otg_enable(struct mv_otg *mvotg) +{ + if (mvotg->clock_gating) + return mv_otg_enable_internal(mvotg); + + return 0; +} + +static void mv_otg_disable_internal(struct mv_otg *mvotg) +{ + if (mvotg->active) { + dev_dbg(&mvotg->pdev->dev, "otg disabled\n"); + if (mvotg->pdata->phy_deinit) + mvotg->pdata->phy_deinit(mvotg->phy_regs); + otg_clock_disable(mvotg); + mvotg->active = 0; + } +} + +static void mv_otg_disable(struct mv_otg *mvotg) +{ + if (mvotg->clock_gating) + mv_otg_disable_internal(mvotg); +} + +static void mv_otg_update_inputs(struct mv_otg *mvotg) +{ + struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl; + u32 otgsc; + + otgsc = readl(&mvotg->op_regs->otgsc); + + if (mvotg->pdata->vbus) { + if (mvotg->pdata->vbus->poll() == VBUS_HIGH) { + otg_ctrl->b_sess_vld = 1; + otg_ctrl->b_sess_end = 0; + } else { + otg_ctrl->b_sess_vld = 0; + otg_ctrl->b_sess_end = 1; + } + } else { + otg_ctrl->b_sess_vld = !!(otgsc & OTGSC_STS_B_SESSION_VALID); + otg_ctrl->b_sess_end = !!(otgsc & OTGSC_STS_B_SESSION_END); + } + + if (mvotg->pdata->id) + otg_ctrl->id = !!mvotg->pdata->id->poll(); + else + otg_ctrl->id = !!(otgsc & OTGSC_STS_USB_ID); + + if (mvotg->pdata->otg_force_a_bus_req && !otg_ctrl->id) + otg_ctrl->a_bus_req = 1; + + otg_ctrl->a_sess_vld = !!(otgsc & OTGSC_STS_A_SESSION_VALID); + otg_ctrl->a_vbus_vld = !!(otgsc & OTGSC_STS_A_VBUS_VALID); + + dev_dbg(&mvotg->pdev->dev, "%s: ", __func__); + dev_dbg(&mvotg->pdev->dev, "id %d\n", otg_ctrl->id); + dev_dbg(&mvotg->pdev->dev, "b_sess_vld %d\n", otg_ctrl->b_sess_vld); + dev_dbg(&mvotg->pdev->dev, "b_sess_end %d\n", otg_ctrl->b_sess_end); + dev_dbg(&mvotg->pdev->dev, "a_vbus_vld %d\n", otg_ctrl->a_vbus_vld); + dev_dbg(&mvotg->pdev->dev, "a_sess_vld %d\n", otg_ctrl->a_sess_vld); +} + +static void mv_otg_update_state(struct mv_otg *mvotg) +{ + struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl; + struct usb_phy *phy = &mvotg->phy; + int old_state = phy->state; + + switch (old_state) { + case OTG_STATE_UNDEFINED: + phy->state = OTG_STATE_B_IDLE; + /* FALL THROUGH */ + case OTG_STATE_B_IDLE: + if (otg_ctrl->id == 0) + phy->state = OTG_STATE_A_IDLE; + else if (otg_ctrl->b_sess_vld) + phy->state = OTG_STATE_B_PERIPHERAL; + break; + case OTG_STATE_B_PERIPHERAL: + if (!otg_ctrl->b_sess_vld || otg_ctrl->id == 0) + phy->state = OTG_STATE_B_IDLE; + break; + case OTG_STATE_A_IDLE: + if (otg_ctrl->id) + phy->state = OTG_STATE_B_IDLE; + else if (!(otg_ctrl->a_bus_drop) && + (otg_ctrl->a_bus_req || otg_ctrl->a_srp_det)) + phy->state = OTG_STATE_A_WAIT_VRISE; + break; + case OTG_STATE_A_WAIT_VRISE: + if (otg_ctrl->a_vbus_vld) + phy->state = OTG_STATE_A_WAIT_BCON; + break; + case OTG_STATE_A_WAIT_BCON: + if (otg_ctrl->id || otg_ctrl->a_bus_drop + || otg_ctrl->a_wait_bcon_timeout) { + mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); + mvotg->otg_ctrl.a_wait_bcon_timeout = 0; + phy->state = OTG_STATE_A_WAIT_VFALL; + otg_ctrl->a_bus_req = 0; + } else if (!otg_ctrl->a_vbus_vld) { + mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); + mvotg->otg_ctrl.a_wait_bcon_timeout = 0; + phy->state = OTG_STATE_A_VBUS_ERR; + } else if (otg_ctrl->b_conn) { + mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); + mvotg->otg_ctrl.a_wait_bcon_timeout = 0; + phy->state = OTG_STATE_A_HOST; + } + break; + case OTG_STATE_A_HOST: + if (otg_ctrl->id || !otg_ctrl->b_conn + || otg_ctrl->a_bus_drop) + phy->state = OTG_STATE_A_WAIT_BCON; + else if (!otg_ctrl->a_vbus_vld) + phy->state = OTG_STATE_A_VBUS_ERR; + break; + case OTG_STATE_A_WAIT_VFALL: + if (otg_ctrl->id + || (!otg_ctrl->b_conn && otg_ctrl->a_sess_vld) + || otg_ctrl->a_bus_req) + phy->state = OTG_STATE_A_IDLE; + break; + case OTG_STATE_A_VBUS_ERR: + if (otg_ctrl->id || otg_ctrl->a_clr_err + || otg_ctrl->a_bus_drop) { + otg_ctrl->a_clr_err = 0; + phy->state = OTG_STATE_A_WAIT_VFALL; + } + break; + default: + break; + } +} + +static void mv_otg_work(struct work_struct *work) +{ + struct mv_otg *mvotg; + struct usb_phy *phy; + struct usb_otg *otg; + int old_state; + + mvotg = container_of(to_delayed_work(work), struct mv_otg, work); + +run: + /* work queue is single thread, or we need spin_lock to protect */ + phy = &mvotg->phy; + otg = phy->otg; + old_state = phy->state; + + if (!mvotg->active) + return; + + mv_otg_update_inputs(mvotg); + mv_otg_update_state(mvotg); + + if (old_state != phy->state) { + dev_info(&mvotg->pdev->dev, "change from state %s to %s\n", + state_string[old_state], + state_string[phy->state]); + + switch (phy->state) { + case OTG_STATE_B_IDLE: + otg->default_a = 0; + if (old_state == OTG_STATE_B_PERIPHERAL) + mv_otg_start_periphrals(mvotg, 0); + mv_otg_reset(mvotg); + mv_otg_disable(mvotg); + break; + case OTG_STATE_B_PERIPHERAL: + mv_otg_enable(mvotg); + mv_otg_start_periphrals(mvotg, 1); + break; + case OTG_STATE_A_IDLE: + otg->default_a = 1; + mv_otg_enable(mvotg); + if (old_state == OTG_STATE_A_WAIT_VFALL) + mv_otg_start_host(mvotg, 0); + mv_otg_reset(mvotg); + break; + case OTG_STATE_A_WAIT_VRISE: + mv_otg_set_vbus(otg, 1); + break; + case OTG_STATE_A_WAIT_BCON: + if (old_state != OTG_STATE_A_HOST) + mv_otg_start_host(mvotg, 1); + mv_otg_set_timer(mvotg, A_WAIT_BCON_TIMER, + T_A_WAIT_BCON, + mv_otg_timer_await_bcon); + /* + * Now, we directly enter A_HOST. So set b_conn = 1 + * here. In fact, it need host driver to notify us. + */ + mvotg->otg_ctrl.b_conn = 1; + break; + case OTG_STATE_A_HOST: + break; + case OTG_STATE_A_WAIT_VFALL: + /* + * Now, we has exited A_HOST. So set b_conn = 0 + * here. In fact, it need host driver to notify us. + */ + mvotg->otg_ctrl.b_conn = 0; + mv_otg_set_vbus(otg, 0); + break; + case OTG_STATE_A_VBUS_ERR: + break; + default: + break; + } + goto run; + } +} + +static irqreturn_t mv_otg_irq(int irq, void *dev) +{ + struct mv_otg *mvotg = dev; + u32 otgsc; + + otgsc = readl(&mvotg->op_regs->otgsc); + writel(otgsc, &mvotg->op_regs->otgsc); + + /* + * if we have vbus, then the vbus detection for B-device + * will be done by mv_otg_inputs_irq(). + */ + if (mvotg->pdata->vbus) + if ((otgsc & OTGSC_STS_USB_ID) && + !(otgsc & OTGSC_INTSTS_USB_ID)) + return IRQ_NONE; + + if ((otgsc & mvotg->irq_status) == 0) + return IRQ_NONE; + + mv_otg_run_state_machine(mvotg, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t mv_otg_inputs_irq(int irq, void *dev) +{ + struct mv_otg *mvotg = dev; + + /* The clock may disabled at this time */ + if (!mvotg->active) { + mv_otg_enable(mvotg); + mv_otg_init_irq(mvotg); + } + + mv_otg_run_state_machine(mvotg, 0); + + return IRQ_HANDLED; +} + +static ssize_t +get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct mv_otg *mvotg = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%d\n", + mvotg->otg_ctrl.a_bus_req); +} + +static ssize_t +set_a_bus_req(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mv_otg *mvotg = dev_get_drvdata(dev); + + if (count > 2) + return -1; + + /* We will use this interface to change to A device */ + if (mvotg->phy.state != OTG_STATE_B_IDLE + && mvotg->phy.state != OTG_STATE_A_IDLE) + return -1; + + /* The clock may disabled and we need to set irq for ID detected */ + mv_otg_enable(mvotg); + mv_otg_init_irq(mvotg); + + if (buf[0] == '1') { + mvotg->otg_ctrl.a_bus_req = 1; + mvotg->otg_ctrl.a_bus_drop = 0; + dev_dbg(&mvotg->pdev->dev, + "User request: a_bus_req = 1\n"); + + if (spin_trylock(&mvotg->wq_lock)) { + mv_otg_run_state_machine(mvotg, 0); + spin_unlock(&mvotg->wq_lock); + } + } + + return count; +} + +static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req, + set_a_bus_req); + +static ssize_t +set_a_clr_err(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mv_otg *mvotg = dev_get_drvdata(dev); + if (!mvotg->phy.otg->default_a) + return -1; + + if (count > 2) + return -1; + + if (buf[0] == '1') { + mvotg->otg_ctrl.a_clr_err = 1; + dev_dbg(&mvotg->pdev->dev, + "User request: a_clr_err = 1\n"); + } + + if (spin_trylock(&mvotg->wq_lock)) { + mv_otg_run_state_machine(mvotg, 0); + spin_unlock(&mvotg->wq_lock); + } + + return count; +} + +static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err); + +static ssize_t +get_a_bus_drop(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mv_otg *mvotg = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%d\n", + mvotg->otg_ctrl.a_bus_drop); +} + +static ssize_t +set_a_bus_drop(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mv_otg *mvotg = dev_get_drvdata(dev); + if (!mvotg->phy.otg->default_a) + return -1; + + if (count > 2) + return -1; + + if (buf[0] == '0') { + mvotg->otg_ctrl.a_bus_drop = 0; + dev_dbg(&mvotg->pdev->dev, + "User request: a_bus_drop = 0\n"); + } else if (buf[0] == '1') { + mvotg->otg_ctrl.a_bus_drop = 1; + mvotg->otg_ctrl.a_bus_req = 0; + dev_dbg(&mvotg->pdev->dev, + "User request: a_bus_drop = 1\n"); + dev_dbg(&mvotg->pdev->dev, + "User request: and a_bus_req = 0\n"); + } + + if (spin_trylock(&mvotg->wq_lock)) { + mv_otg_run_state_machine(mvotg, 0); + spin_unlock(&mvotg->wq_lock); + } + + return count; +} + +static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR, + get_a_bus_drop, set_a_bus_drop); + +static struct attribute *inputs_attrs[] = { + &dev_attr_a_bus_req.attr, + &dev_attr_a_clr_err.attr, + &dev_attr_a_bus_drop.attr, + NULL, +}; + +static struct attribute_group inputs_attr_group = { + .name = "inputs", + .attrs = inputs_attrs, +}; + +int mv_otg_remove(struct platform_device *pdev) +{ + struct mv_otg *mvotg = platform_get_drvdata(pdev); + + sysfs_remove_group(&mvotg->pdev->dev.kobj, &inputs_attr_group); + + if (mvotg->qwork) { + flush_workqueue(mvotg->qwork); + destroy_workqueue(mvotg->qwork); + } + + mv_otg_disable(mvotg); + + usb_remove_phy(&mvotg->phy); + + return 0; +} + +static int mv_otg_probe(struct platform_device *pdev) +{ + struct mv_usb_platform_data *pdata = pdev->dev.platform_data; + struct mv_otg *mvotg; + struct usb_otg *otg; + struct resource *r; + int retval = 0, i; + + if (pdata == NULL) { + dev_err(&pdev->dev, "failed to get platform data\n"); + return -ENODEV; + } + + mvotg = devm_kzalloc(&pdev->dev, sizeof(*mvotg), GFP_KERNEL); + if (!mvotg) { + dev_err(&pdev->dev, "failed to allocate memory!\n"); + return -ENOMEM; + } + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + platform_set_drvdata(pdev, mvotg); + + mvotg->pdev = pdev; + mvotg->pdata = pdata; + + mvotg->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mvotg->clk)) + return PTR_ERR(mvotg->clk); + + mvotg->qwork = create_singlethread_workqueue("mv_otg_queue"); + if (!mvotg->qwork) { + dev_dbg(&pdev->dev, "cannot create workqueue for OTG\n"); + return -ENOMEM; + } + + INIT_DELAYED_WORK(&mvotg->work, mv_otg_work); + + /* OTG common part */ + mvotg->pdev = pdev; + mvotg->phy.dev = &pdev->dev; + mvotg->phy.otg = otg; + mvotg->phy.label = driver_name; + mvotg->phy.state = OTG_STATE_UNDEFINED; + + otg->phy = &mvotg->phy; + otg->set_host = mv_otg_set_host; + otg->set_peripheral = mv_otg_set_peripheral; + otg->set_vbus = mv_otg_set_vbus; + + for (i = 0; i < OTG_TIMER_NUM; i++) + init_timer(&mvotg->otg_ctrl.timer[i]); + + r = platform_get_resource_byname(mvotg->pdev, + IORESOURCE_MEM, "phyregs"); + if (r == NULL) { + dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); + retval = -ENODEV; + goto err_destroy_workqueue; + } + + mvotg->phy_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (mvotg->phy_regs == NULL) { + dev_err(&pdev->dev, "failed to map phy I/O memory\n"); + retval = -EFAULT; + goto err_destroy_workqueue; + } + + r = platform_get_resource_byname(mvotg->pdev, + IORESOURCE_MEM, "capregs"); + if (r == NULL) { + dev_err(&pdev->dev, "no I/O memory resource defined\n"); + retval = -ENODEV; + goto err_destroy_workqueue; + } + + mvotg->cap_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (mvotg->cap_regs == NULL) { + dev_err(&pdev->dev, "failed to map I/O memory\n"); + retval = -EFAULT; + goto err_destroy_workqueue; + } + + /* we will acces controller register, so enable the udc controller */ + retval = mv_otg_enable_internal(mvotg); + if (retval) { + dev_err(&pdev->dev, "mv otg enable error %d\n", retval); + goto err_destroy_workqueue; + } + + mvotg->op_regs = + (struct mv_otg_regs __iomem *) ((unsigned long) mvotg->cap_regs + + (readl(mvotg->cap_regs) & CAPLENGTH_MASK)); + + if (pdata->id) { + retval = devm_request_threaded_irq(&pdev->dev, pdata->id->irq, + NULL, mv_otg_inputs_irq, + IRQF_ONESHOT, "id", mvotg); + if (retval) { + dev_info(&pdev->dev, + "Failed to request irq for ID\n"); + pdata->id = NULL; + } + } + + if (pdata->vbus) { + mvotg->clock_gating = 1; + retval = devm_request_threaded_irq(&pdev->dev, pdata->vbus->irq, + NULL, mv_otg_inputs_irq, + IRQF_ONESHOT, "vbus", mvotg); + if (retval) { + dev_info(&pdev->dev, + "Failed to request irq for VBUS, " + "disable clock gating\n"); + mvotg->clock_gating = 0; + pdata->vbus = NULL; + } + } + + if (pdata->disable_otg_clock_gating) + mvotg->clock_gating = 0; + + mv_otg_reset(mvotg); + mv_otg_init_irq(mvotg); + + r = platform_get_resource(mvotg->pdev, IORESOURCE_IRQ, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no IRQ resource defined\n"); + retval = -ENODEV; + goto err_disable_clk; + } + + mvotg->irq = r->start; + if (devm_request_irq(&pdev->dev, mvotg->irq, mv_otg_irq, IRQF_SHARED, + driver_name, mvotg)) { + dev_err(&pdev->dev, "Request irq %d for OTG failed\n", + mvotg->irq); + mvotg->irq = 0; + retval = -ENODEV; + goto err_disable_clk; + } + + retval = usb_add_phy(&mvotg->phy, USB_PHY_TYPE_USB2); + if (retval < 0) { + dev_err(&pdev->dev, "can't register transceiver, %d\n", + retval); + goto err_disable_clk; + } + + retval = sysfs_create_group(&pdev->dev.kobj, &inputs_attr_group); + if (retval < 0) { + dev_dbg(&pdev->dev, + "Can't register sysfs attr group: %d\n", retval); + goto err_remove_phy; + } + + spin_lock_init(&mvotg->wq_lock); + if (spin_trylock(&mvotg->wq_lock)) { + mv_otg_run_state_machine(mvotg, 2 * HZ); + spin_unlock(&mvotg->wq_lock); + } + + dev_info(&pdev->dev, + "successful probe OTG device %s clock gating.\n", + mvotg->clock_gating ? "with" : "without"); + + return 0; + +err_remove_phy: + usb_remove_phy(&mvotg->phy); +err_disable_clk: + mv_otg_disable_internal(mvotg); +err_destroy_workqueue: + flush_workqueue(mvotg->qwork); + destroy_workqueue(mvotg->qwork); + + return retval; +} + +#ifdef CONFIG_PM +static int mv_otg_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mv_otg *mvotg = platform_get_drvdata(pdev); + + if (mvotg->phy.state != OTG_STATE_B_IDLE) { + dev_info(&pdev->dev, + "OTG state is not B_IDLE, it is %d!\n", + mvotg->phy.state); + return -EAGAIN; + } + + if (!mvotg->clock_gating) + mv_otg_disable_internal(mvotg); + + return 0; +} + +static int mv_otg_resume(struct platform_device *pdev) +{ + struct mv_otg *mvotg = platform_get_drvdata(pdev); + u32 otgsc; + + if (!mvotg->clock_gating) { + mv_otg_enable_internal(mvotg); + + otgsc = readl(&mvotg->op_regs->otgsc); + otgsc |= mvotg->irq_en; + writel(otgsc, &mvotg->op_regs->otgsc); + + if (spin_trylock(&mvotg->wq_lock)) { + mv_otg_run_state_machine(mvotg, 0); + spin_unlock(&mvotg->wq_lock); + } + } + return 0; +} +#endif + +static struct platform_driver mv_otg_driver = { + .probe = mv_otg_probe, + .remove = __exit_p(mv_otg_remove), + .driver = { + .owner = THIS_MODULE, + .name = driver_name, + }, +#ifdef CONFIG_PM + .suspend = mv_otg_suspend, + .resume = mv_otg_resume, +#endif +}; +module_platform_driver(mv_otg_driver); diff --git a/drivers/usb/phy/phy-mv-usb.h b/drivers/usb/phy/phy-mv-usb.h new file mode 100644 index 00000000..551da6eb --- /dev/null +++ b/drivers/usb/phy/phy-mv-usb.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __MV_USB_OTG_CONTROLLER__ +#define __MV_USB_OTG_CONTROLLER__ + +#include + +/* Command Register Bit Masks */ +#define USBCMD_RUN_STOP (0x00000001) +#define USBCMD_CTRL_RESET (0x00000002) + +/* otgsc Register Bit Masks */ +#define OTGSC_CTRL_VUSB_DISCHARGE 0x00000001 +#define OTGSC_CTRL_VUSB_CHARGE 0x00000002 +#define OTGSC_CTRL_OTG_TERM 0x00000008 +#define OTGSC_CTRL_DATA_PULSING 0x00000010 +#define OTGSC_STS_USB_ID 0x00000100 +#define OTGSC_STS_A_VBUS_VALID 0x00000200 +#define OTGSC_STS_A_SESSION_VALID 0x00000400 +#define OTGSC_STS_B_SESSION_VALID 0x00000800 +#define OTGSC_STS_B_SESSION_END 0x00001000 +#define OTGSC_STS_1MS_TOGGLE 0x00002000 +#define OTGSC_STS_DATA_PULSING 0x00004000 +#define OTGSC_INTSTS_USB_ID 0x00010000 +#define OTGSC_INTSTS_A_VBUS_VALID 0x00020000 +#define OTGSC_INTSTS_A_SESSION_VALID 0x00040000 +#define OTGSC_INTSTS_B_SESSION_VALID 0x00080000 +#define OTGSC_INTSTS_B_SESSION_END 0x00100000 +#define OTGSC_INTSTS_1MS 0x00200000 +#define OTGSC_INTSTS_DATA_PULSING 0x00400000 +#define OTGSC_INTR_USB_ID 0x01000000 +#define OTGSC_INTR_A_VBUS_VALID 0x02000000 +#define OTGSC_INTR_A_SESSION_VALID 0x04000000 +#define OTGSC_INTR_B_SESSION_VALID 0x08000000 +#define OTGSC_INTR_B_SESSION_END 0x10000000 +#define OTGSC_INTR_1MS_TIMER 0x20000000 +#define OTGSC_INTR_DATA_PULSING 0x40000000 + +#define CAPLENGTH_MASK (0xff) + +/* Timer's interval, unit 10ms */ +#define T_A_WAIT_VRISE 100 +#define T_A_WAIT_BCON 2000 +#define T_A_AIDL_BDIS 100 +#define T_A_BIDL_ADIS 20 +#define T_B_ASE0_BRST 400 +#define T_B_SE0_SRP 300 +#define T_B_SRP_FAIL 2000 +#define T_B_DATA_PLS 10 +#define T_B_SRP_INIT 100 +#define T_A_SRP_RSPNS 10 +#define T_A_DRV_RSM 5 + +enum otg_function { + OTG_B_DEVICE = 0, + OTG_A_DEVICE +}; + +enum mv_otg_timer { + A_WAIT_BCON_TIMER = 0, + OTG_TIMER_NUM +}; + +/* PXA OTG state machine */ +struct mv_otg_ctrl { + /* internal variables */ + u8 a_set_b_hnp_en; /* A-Device set b_hnp_en */ + u8 b_srp_done; + u8 b_hnp_en; + + /* OTG inputs */ + u8 a_bus_drop; + u8 a_bus_req; + u8 a_clr_err; + u8 a_bus_resume; + u8 a_bus_suspend; + u8 a_conn; + u8 a_sess_vld; + u8 a_srp_det; + u8 a_vbus_vld; + u8 b_bus_req; /* B-Device Require Bus */ + u8 b_bus_resume; + u8 b_bus_suspend; + u8 b_conn; + u8 b_se0_srp; + u8 b_sess_end; + u8 b_sess_vld; + u8 id; + u8 a_suspend_req; + + /*Timer event */ + u8 a_aidl_bdis_timeout; + u8 b_ase0_brst_timeout; + u8 a_bidl_adis_timeout; + u8 a_wait_bcon_timeout; + + struct timer_list timer[OTG_TIMER_NUM]; +}; + +#define VUSBHS_MAX_PORTS 8 + +struct mv_otg_regs { + u32 usbcmd; /* Command register */ + u32 usbsts; /* Status register */ + u32 usbintr; /* Interrupt enable */ + u32 frindex; /* Frame index */ + u32 reserved1[1]; + u32 deviceaddr; /* Device Address */ + u32 eplistaddr; /* Endpoint List Address */ + u32 ttctrl; /* HOST TT status and control */ + u32 burstsize; /* Programmable Burst Size */ + u32 txfilltuning; /* Host Transmit Pre-Buffer Packet Tuning */ + u32 reserved[4]; + u32 epnak; /* Endpoint NAK */ + u32 epnaken; /* Endpoint NAK Enable */ + u32 configflag; /* Configured Flag register */ + u32 portsc[VUSBHS_MAX_PORTS]; /* Port Status/Control x, x = 1..8 */ + u32 otgsc; + u32 usbmode; /* USB Host/Device mode */ + u32 epsetupstat; /* Endpoint Setup Status */ + u32 epprime; /* Endpoint Initialize */ + u32 epflush; /* Endpoint De-initialize */ + u32 epstatus; /* Endpoint Status */ + u32 epcomplete; /* Endpoint Interrupt On Complete */ + u32 epctrlx[16]; /* Endpoint Control, where x = 0.. 15 */ + u32 mcr; /* Mux Control */ + u32 isr; /* Interrupt Status */ + u32 ier; /* Interrupt Enable */ +}; + +struct mv_otg { + struct usb_phy phy; + struct mv_otg_ctrl otg_ctrl; + + /* base address */ + void __iomem *phy_regs; + void __iomem *cap_regs; + struct mv_otg_regs __iomem *op_regs; + + struct platform_device *pdev; + int irq; + u32 irq_status; + u32 irq_en; + + struct delayed_work work; + struct workqueue_struct *qwork; + + spinlock_t wq_lock; + + struct mv_usb_platform_data *pdata; + + unsigned int active; + unsigned int clock_gating; + struct clk *clk; +}; + +#endif diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c new file mode 100644 index 00000000..bd601c53 --- /dev/null +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -0,0 +1,214 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Marek Vasut + * on behalf of DENX Software Engineering GmbH + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "mxs_phy" + +#define HW_USBPHY_PWD 0x00 +#define HW_USBPHY_CTRL 0x30 +#define HW_USBPHY_CTRL_SET 0x34 +#define HW_USBPHY_CTRL_CLR 0x38 + +#define BM_USBPHY_CTRL_SFTRST BIT(31) +#define BM_USBPHY_CTRL_CLKGATE BIT(30) +#define BM_USBPHY_CTRL_ENUTMILEVEL3 BIT(15) +#define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14) +#define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1) + +struct mxs_phy { + struct usb_phy phy; + struct clk *clk; +}; + +#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) + +static void mxs_phy_hw_init(struct mxs_phy *mxs_phy) +{ + void __iomem *base = mxs_phy->phy.io_priv; + + stmp_reset_block(base + HW_USBPHY_CTRL); + + /* Power up the PHY */ + writel(0, base + HW_USBPHY_PWD); + + /* enable FS/LS device */ + writel(BM_USBPHY_CTRL_ENUTMILEVEL2 | + BM_USBPHY_CTRL_ENUTMILEVEL3, + base + HW_USBPHY_CTRL_SET); +} + +static int mxs_phy_init(struct usb_phy *phy) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + + clk_prepare_enable(mxs_phy->clk); + mxs_phy_hw_init(mxs_phy); + + return 0; +} + +static void mxs_phy_shutdown(struct usb_phy *phy) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + + writel(BM_USBPHY_CTRL_CLKGATE, + phy->io_priv + HW_USBPHY_CTRL_SET); + + clk_disable_unprepare(mxs_phy->clk); +} + +static int mxs_phy_suspend(struct usb_phy *x, int suspend) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(x); + + if (suspend) { + writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); + writel(BM_USBPHY_CTRL_CLKGATE, + x->io_priv + HW_USBPHY_CTRL_SET); + clk_disable_unprepare(mxs_phy->clk); + } else { + clk_prepare_enable(mxs_phy->clk); + writel(BM_USBPHY_CTRL_CLKGATE, + x->io_priv + HW_USBPHY_CTRL_CLR); + writel(0, x->io_priv + HW_USBPHY_PWD); + } + + return 0; +} + +static int mxs_phy_on_connect(struct usb_phy *phy, + enum usb_device_speed speed) +{ + dev_dbg(phy->dev, "%s speed device has connected\n", + (speed == USB_SPEED_HIGH) ? "high" : "non-high"); + + if (speed == USB_SPEED_HIGH) + writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_SET); + + return 0; +} + +static int mxs_phy_on_disconnect(struct usb_phy *phy, + enum usb_device_speed speed) +{ + dev_dbg(phy->dev, "%s speed device has disconnected\n", + (speed == USB_SPEED_HIGH) ? "high" : "non-high"); + + if (speed == USB_SPEED_HIGH) + writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_CLR); + + return 0; +} + +static int mxs_phy_probe(struct platform_device *pdev) +{ + struct resource *res; + void __iomem *base; + struct clk *clk; + struct mxs_phy *mxs_phy; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, + "can't get the clock, err=%ld", PTR_ERR(clk)); + return PTR_ERR(clk); + } + + mxs_phy = devm_kzalloc(&pdev->dev, sizeof(*mxs_phy), GFP_KERNEL); + if (!mxs_phy) { + dev_err(&pdev->dev, "Failed to allocate USB PHY structure!\n"); + return -ENOMEM; + } + + mxs_phy->phy.io_priv = base; + mxs_phy->phy.dev = &pdev->dev; + mxs_phy->phy.label = DRIVER_NAME; + mxs_phy->phy.init = mxs_phy_init; + mxs_phy->phy.shutdown = mxs_phy_shutdown; + mxs_phy->phy.set_suspend = mxs_phy_suspend; + mxs_phy->phy.notify_connect = mxs_phy_on_connect; + mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect; + mxs_phy->phy.type = USB_PHY_TYPE_USB2; + + ATOMIC_INIT_NOTIFIER_HEAD(&mxs_phy->phy.notifier); + + mxs_phy->clk = clk; + + platform_set_drvdata(pdev, &mxs_phy->phy); + + ret = usb_add_phy_dev(&mxs_phy->phy); + if (ret) + return ret; + + return 0; +} + +static int mxs_phy_remove(struct platform_device *pdev) +{ + struct mxs_phy *mxs_phy = platform_get_drvdata(pdev); + + usb_remove_phy(&mxs_phy->phy); + + return 0; +} + +static const struct of_device_id mxs_phy_dt_ids[] = { + { .compatible = "fsl,imx23-usbphy", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids); + +static struct platform_driver mxs_phy_driver = { + .probe = mxs_phy_probe, + .remove = mxs_phy_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = mxs_phy_dt_ids, + }, +}; + +static int __init mxs_phy_module_init(void) +{ + return platform_driver_register(&mxs_phy_driver); +} +postcore_initcall(mxs_phy_module_init); + +static void __exit mxs_phy_module_exit(void) +{ + platform_driver_unregister(&mxs_phy_driver); +} +module_exit(mxs_phy_module_exit); + +MODULE_ALIAS("platform:mxs-usb-phy"); +MODULE_AUTHOR("Marek Vasut "); +MODULE_AUTHOR("Richard Zhao "); +MODULE_DESCRIPTION("Freescale MXS USB PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-nop.c b/drivers/usb/phy/phy-nop.c new file mode 100644 index 00000000..638cc5da --- /dev/null +++ b/drivers/usb/phy/phy-nop.c @@ -0,0 +1,292 @@ +/* + * drivers/usb/otg/nop-usb-xceiv.c + * + * NOP USB transceiver for all USB transceiver which are either built-in + * into USB IP or which are mostly autonomous. + * + * Copyright (C) 2009 Texas Instruments Inc + * Author: Ajay Kumar Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Current status: + * This provides a "nop" transceiver for PHYs which are + * autonomous such as isp1504, isp1707, etc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct nop_usb_xceiv { + struct usb_phy phy; + struct device *dev; + struct clk *clk; + struct regulator *vcc; + struct regulator *reset; +}; + +static struct platform_device *pd; + +void usb_nop_xceiv_register(void) +{ + if (pd) + return; + pd = platform_device_register_simple("nop_usb_xceiv", -1, NULL, 0); + if (!pd) { + printk(KERN_ERR "Unable to register usb nop transceiver\n"); + return; + } +} +EXPORT_SYMBOL(usb_nop_xceiv_register); + +void usb_nop_xceiv_unregister(void) +{ + platform_device_unregister(pd); + pd = NULL; +} +EXPORT_SYMBOL(usb_nop_xceiv_unregister); + +static int nop_set_suspend(struct usb_phy *x, int suspend) +{ + return 0; +} + +static int nop_init(struct usb_phy *phy) +{ + struct nop_usb_xceiv *nop = dev_get_drvdata(phy->dev); + + if (!IS_ERR(nop->vcc)) { + if (regulator_enable(nop->vcc)) + dev_err(phy->dev, "Failed to enable power\n"); + } + + if (!IS_ERR(nop->clk)) + clk_enable(nop->clk); + + if (!IS_ERR(nop->reset)) { + /* De-assert RESET */ + if (regulator_enable(nop->reset)) + dev_err(phy->dev, "Failed to de-assert reset\n"); + } + + return 0; +} + +static void nop_shutdown(struct usb_phy *phy) +{ + struct nop_usb_xceiv *nop = dev_get_drvdata(phy->dev); + + if (!IS_ERR(nop->reset)) { + /* Assert RESET */ + if (regulator_disable(nop->reset)) + dev_err(phy->dev, "Failed to assert reset\n"); + } + + if (!IS_ERR(nop->clk)) + clk_disable(nop->clk); + + if (!IS_ERR(nop->vcc)) { + if (regulator_disable(nop->vcc)) + dev_err(phy->dev, "Failed to disable power\n"); + } +} + +static int nop_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) +{ + if (!otg) + return -ENODEV; + + if (!gadget) { + otg->gadget = NULL; + return -ENODEV; + } + + otg->gadget = gadget; + otg->phy->state = OTG_STATE_B_IDLE; + return 0; +} + +static int nop_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + if (!otg) + return -ENODEV; + + if (!host) { + otg->host = NULL; + return -ENODEV; + } + + otg->host = host; + return 0; +} + +static int nop_usb_xceiv_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nop_usb_xceiv_platform_data *pdata = pdev->dev.platform_data; + struct nop_usb_xceiv *nop; + enum usb_phy_type type = USB_PHY_TYPE_USB2; + int err; + u32 clk_rate = 0; + bool needs_vcc = false; + bool needs_reset = false; + + nop = devm_kzalloc(&pdev->dev, sizeof(*nop), GFP_KERNEL); + if (!nop) + return -ENOMEM; + + nop->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*nop->phy.otg), + GFP_KERNEL); + if (!nop->phy.otg) + return -ENOMEM; + + if (dev->of_node) { + struct device_node *node = dev->of_node; + + if (of_property_read_u32(node, "clock-frequency", &clk_rate)) + clk_rate = 0; + + needs_vcc = of_property_read_bool(node, "vcc-supply"); + needs_reset = of_property_read_bool(node, "reset-supply"); + + } else if (pdata) { + type = pdata->type; + clk_rate = pdata->clk_rate; + needs_vcc = pdata->needs_vcc; + needs_reset = pdata->needs_reset; + } + + nop->clk = devm_clk_get(&pdev->dev, "main_clk"); + if (IS_ERR(nop->clk)) { + dev_dbg(&pdev->dev, "Can't get phy clock: %ld\n", + PTR_ERR(nop->clk)); + } + + if (!IS_ERR(nop->clk) && clk_rate) { + err = clk_set_rate(nop->clk, clk_rate); + if (err) { + dev_err(&pdev->dev, "Error setting clock rate\n"); + return err; + } + } + + if (!IS_ERR(nop->clk)) { + err = clk_prepare(nop->clk); + if (err) { + dev_err(&pdev->dev, "Error preparing clock\n"); + return err; + } + } + + nop->vcc = devm_regulator_get(&pdev->dev, "vcc"); + if (IS_ERR(nop->vcc)) { + dev_dbg(&pdev->dev, "Error getting vcc regulator: %ld\n", + PTR_ERR(nop->vcc)); + if (needs_vcc) + return -EPROBE_DEFER; + } + + nop->reset = devm_regulator_get(&pdev->dev, "reset"); + if (IS_ERR(nop->reset)) { + dev_dbg(&pdev->dev, "Error getting reset regulator: %ld\n", + PTR_ERR(nop->reset)); + if (needs_reset) + return -EPROBE_DEFER; + } + + nop->dev = &pdev->dev; + nop->phy.dev = nop->dev; + nop->phy.label = "nop-xceiv"; + nop->phy.set_suspend = nop_set_suspend; + nop->phy.init = nop_init; + nop->phy.shutdown = nop_shutdown; + nop->phy.state = OTG_STATE_UNDEFINED; + nop->phy.type = type; + + nop->phy.otg->phy = &nop->phy; + nop->phy.otg->set_host = nop_set_host; + nop->phy.otg->set_peripheral = nop_set_peripheral; + + err = usb_add_phy_dev(&nop->phy); + if (err) { + dev_err(&pdev->dev, "can't register transceiver, err: %d\n", + err); + goto err_add; + } + + platform_set_drvdata(pdev, nop); + + ATOMIC_INIT_NOTIFIER_HEAD(&nop->phy.notifier); + + return 0; + +err_add: + if (!IS_ERR(nop->clk)) + clk_unprepare(nop->clk); + return err; +} + +static int nop_usb_xceiv_remove(struct platform_device *pdev) +{ + struct nop_usb_xceiv *nop = platform_get_drvdata(pdev); + + if (!IS_ERR(nop->clk)) + clk_unprepare(nop->clk); + + usb_remove_phy(&nop->phy); + + return 0; +} + +static const struct of_device_id nop_xceiv_dt_ids[] = { + { .compatible = "usb-nop-xceiv" }, + { } +}; + +MODULE_DEVICE_TABLE(of, nop_xceiv_dt_ids); + +static struct platform_driver nop_usb_xceiv_driver = { + .probe = nop_usb_xceiv_probe, + .remove = nop_usb_xceiv_remove, + .driver = { + .name = "nop_usb_xceiv", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(nop_xceiv_dt_ids), + }, +}; + +static int __init nop_usb_xceiv_init(void) +{ + return platform_driver_register(&nop_usb_xceiv_driver); +} +subsys_initcall(nop_usb_xceiv_init); + +static void __exit nop_usb_xceiv_exit(void) +{ + platform_driver_unregister(&nop_usb_xceiv_driver); +} +module_exit(nop_usb_xceiv_exit); + +MODULE_ALIAS("platform:nop_usb_xceiv"); +MODULE_AUTHOR("Texas Instruments Inc"); +MODULE_DESCRIPTION("NOP USB Transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-omap-control.c b/drivers/usb/phy/phy-omap-control.c new file mode 100644 index 00000000..1419ceda --- /dev/null +++ b/drivers/usb/phy/phy-omap-control.c @@ -0,0 +1,289 @@ +/* + * omap-control-usb.c - The USB part of control module. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include + +static struct omap_control_usb *control_usb; + +/** + * omap_get_control_dev - returns the device pointer for this control device + * + * This API should be called to get the device pointer for this control + * module device. This device pointer should be used for called other + * exported API's in this driver. + * + * To be used by PHY driver and glue driver. + */ +struct device *omap_get_control_dev(void) +{ + if (!control_usb) + return ERR_PTR(-ENODEV); + + return control_usb->dev; +} +EXPORT_SYMBOL_GPL(omap_get_control_dev); + +/** + * omap_control_usb3_phy_power - power on/off the serializer using control + * module + * @dev: the control module device + * @on: 0 to off and 1 to on based on powering on or off the PHY + * + * usb3 PHY driver should call this API to power on or off the PHY. + */ +void omap_control_usb3_phy_power(struct device *dev, bool on) +{ + u32 val; + unsigned long rate; + struct omap_control_usb *control_usb = dev_get_drvdata(dev); + + if (control_usb->type != OMAP_CTRL_DEV_TYPE2) + return; + + rate = clk_get_rate(control_usb->sys_clk); + rate = rate/1000000; + + val = readl(control_usb->phy_power); + + if (on) { + val &= ~(OMAP_CTRL_USB_PWRCTL_CLK_CMD_MASK | + OMAP_CTRL_USB_PWRCTL_CLK_FREQ_MASK); + val |= OMAP_CTRL_USB3_PHY_TX_RX_POWERON << + OMAP_CTRL_USB_PWRCTL_CLK_CMD_SHIFT; + val |= rate << OMAP_CTRL_USB_PWRCTL_CLK_FREQ_SHIFT; + } else { + val &= ~OMAP_CTRL_USB_PWRCTL_CLK_CMD_MASK; + val |= OMAP_CTRL_USB3_PHY_TX_RX_POWEROFF << + OMAP_CTRL_USB_PWRCTL_CLK_CMD_SHIFT; + } + + writel(val, control_usb->phy_power); +} +EXPORT_SYMBOL_GPL(omap_control_usb3_phy_power); + +/** + * omap_control_usb_phy_power - power on/off the phy using control module reg + * @dev: the control module device + * @on: 0 or 1, based on powering on or off the PHY + */ +void omap_control_usb_phy_power(struct device *dev, int on) +{ + u32 val; + struct omap_control_usb *control_usb = dev_get_drvdata(dev); + + val = readl(control_usb->dev_conf); + + if (on) + val &= ~OMAP_CTRL_DEV_PHY_PD; + else + val |= OMAP_CTRL_DEV_PHY_PD; + + writel(val, control_usb->dev_conf); +} +EXPORT_SYMBOL_GPL(omap_control_usb_phy_power); + +/** + * omap_control_usb_host_mode - set AVALID, VBUSVALID and ID pin in grounded + * @ctrl_usb: struct omap_control_usb * + * + * Writes to the mailbox register to notify the usb core that a usb + * device has been connected. + */ +static void omap_control_usb_host_mode(struct omap_control_usb *ctrl_usb) +{ + u32 val; + + val = readl(ctrl_usb->otghs_control); + val &= ~(OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND); + val |= OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID; + writel(val, ctrl_usb->otghs_control); +} + +/** + * omap_control_usb_device_mode - set AVALID, VBUSVALID and ID pin in high + * impedance + * @ctrl_usb: struct omap_control_usb * + * + * Writes to the mailbox register to notify the usb core that it has been + * connected to a usb host. + */ +static void omap_control_usb_device_mode(struct omap_control_usb *ctrl_usb) +{ + u32 val; + + val = readl(ctrl_usb->otghs_control); + val &= ~OMAP_CTRL_DEV_SESSEND; + val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_AVALID | + OMAP_CTRL_DEV_VBUSVALID; + writel(val, ctrl_usb->otghs_control); +} + +/** + * omap_control_usb_set_sessionend - Enable SESSIONEND and IDIG to high + * impedance + * @ctrl_usb: struct omap_control_usb * + * + * Writes to the mailbox register to notify the usb core it's now in + * disconnected state. + */ +static void omap_control_usb_set_sessionend(struct omap_control_usb *ctrl_usb) +{ + u32 val; + + val = readl(ctrl_usb->otghs_control); + val &= ~(OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID); + val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND; + writel(val, ctrl_usb->otghs_control); +} + +/** + * omap_control_usb_set_mode - Calls to functions to set USB in one of host mode + * or device mode or to denote disconnected state + * @dev: the control module device + * @mode: The mode to which usb should be configured + * + * This is an API to write to the mailbox register to notify the usb core that + * a usb device has been connected. + */ +void omap_control_usb_set_mode(struct device *dev, + enum omap_control_usb_mode mode) +{ + struct omap_control_usb *ctrl_usb; + + if (IS_ERR(dev) || control_usb->type != OMAP_CTRL_DEV_TYPE1) + return; + + ctrl_usb = dev_get_drvdata(dev); + + switch (mode) { + case USB_MODE_HOST: + omap_control_usb_host_mode(ctrl_usb); + break; + case USB_MODE_DEVICE: + omap_control_usb_device_mode(ctrl_usb); + break; + case USB_MODE_DISCONNECT: + omap_control_usb_set_sessionend(ctrl_usb); + break; + default: + dev_vdbg(dev, "invalid omap control usb mode\n"); + } +} +EXPORT_SYMBOL_GPL(omap_control_usb_set_mode); + +static int omap_control_usb_probe(struct platform_device *pdev) +{ + struct resource *res; + struct device_node *np = pdev->dev.of_node; + struct omap_control_usb_platform_data *pdata = pdev->dev.platform_data; + + control_usb = devm_kzalloc(&pdev->dev, sizeof(*control_usb), + GFP_KERNEL); + if (!control_usb) { + dev_err(&pdev->dev, "unable to alloc memory for control usb\n"); + return -ENOMEM; + } + + if (np) { + of_property_read_u32(np, "ti,type", &control_usb->type); + } else if (pdata) { + control_usb->type = pdata->type; + } else { + dev_err(&pdev->dev, "no pdata present\n"); + return -EINVAL; + } + + control_usb->dev = &pdev->dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "control_dev_conf"); + control_usb->dev_conf = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(control_usb->dev_conf)) + return PTR_ERR(control_usb->dev_conf); + + if (control_usb->type == OMAP_CTRL_DEV_TYPE1) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "otghs_control"); + control_usb->otghs_control = devm_ioremap_resource( + &pdev->dev, res); + if (IS_ERR(control_usb->otghs_control)) + return PTR_ERR(control_usb->otghs_control); + } + + if (control_usb->type == OMAP_CTRL_DEV_TYPE2) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phy_power_usb"); + control_usb->phy_power = devm_ioremap_resource( + &pdev->dev, res); + if (IS_ERR(control_usb->phy_power)) + return PTR_ERR(control_usb->phy_power); + + control_usb->sys_clk = devm_clk_get(control_usb->dev, + "sys_clkin"); + if (IS_ERR(control_usb->sys_clk)) { + pr_err("%s: unable to get sys_clkin\n", __func__); + return -EINVAL; + } + } + + + dev_set_drvdata(control_usb->dev, control_usb); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id omap_control_usb_id_table[] = { + { .compatible = "ti,omap-control-usb" }, + {} +}; +MODULE_DEVICE_TABLE(of, omap_control_usb_id_table); +#endif + +static struct platform_driver omap_control_usb_driver = { + .probe = omap_control_usb_probe, + .driver = { + .name = "omap-control-usb", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(omap_control_usb_id_table), + }, +}; + +static int __init omap_control_usb_init(void) +{ + return platform_driver_register(&omap_control_usb_driver); +} +subsys_initcall(omap_control_usb_init); + +static void __exit omap_control_usb_exit(void) +{ + platform_driver_unregister(&omap_control_usb_driver); +} +module_exit(omap_control_usb_exit); + +MODULE_ALIAS("platform: omap_control_usb"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP Control Module USB Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-omap-usb2.c b/drivers/usb/phy/phy-omap-usb2.c new file mode 100644 index 00000000..844ab68f --- /dev/null +++ b/drivers/usb/phy/phy-omap-usb2.c @@ -0,0 +1,273 @@ +/* + * omap-usb2.c - USB PHY, talking to musb controller in OMAP. + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * omap_usb2_set_comparator - links the comparator present in the sytem with + * this phy + * @comparator - the companion phy(comparator) for this phy + * + * The phy companion driver should call this API passing the phy_companion + * filled with set_vbus and start_srp to be used by usb phy. + * + * For use by phy companion driver + */ +int omap_usb2_set_comparator(struct phy_companion *comparator) +{ + struct omap_usb *phy; + struct usb_phy *x = usb_get_phy(USB_PHY_TYPE_USB2); + + if (IS_ERR(x)) + return -ENODEV; + + phy = phy_to_omapusb(x); + phy->comparator = comparator; + return 0; +} +EXPORT_SYMBOL_GPL(omap_usb2_set_comparator); + +static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled) +{ + struct omap_usb *phy = phy_to_omapusb(otg->phy); + + if (!phy->comparator) + return -ENODEV; + + return phy->comparator->set_vbus(phy->comparator, enabled); +} + +static int omap_usb_start_srp(struct usb_otg *otg) +{ + struct omap_usb *phy = phy_to_omapusb(otg->phy); + + if (!phy->comparator) + return -ENODEV; + + return phy->comparator->start_srp(phy->comparator); +} + +static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct usb_phy *phy = otg->phy; + + otg->host = host; + if (!host) + phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct usb_phy *phy = otg->phy; + + otg->gadget = gadget; + if (!gadget) + phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb2_suspend(struct usb_phy *x, int suspend) +{ + u32 ret; + struct omap_usb *phy = phy_to_omapusb(x); + + if (suspend && !phy->is_suspended) { + omap_control_usb_phy_power(phy->control_dev, 0); + pm_runtime_put_sync(phy->dev); + phy->is_suspended = 1; + } else if (!suspend && phy->is_suspended) { + ret = pm_runtime_get_sync(phy->dev); + if (ret < 0) { + dev_err(phy->dev, "get_sync failed with err %d\n", + ret); + return ret; + } + omap_control_usb_phy_power(phy->control_dev, 1); + phy->is_suspended = 0; + } + + return 0; +} + +static int omap_usb2_probe(struct platform_device *pdev) +{ + struct omap_usb *phy; + struct usb_otg *otg; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + dev_err(&pdev->dev, "unable to allocate memory for USB2 PHY\n"); + return -ENOMEM; + } + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) { + dev_err(&pdev->dev, "unable to allocate memory for USB OTG\n"); + return -ENOMEM; + } + + phy->dev = &pdev->dev; + + phy->phy.dev = phy->dev; + phy->phy.label = "omap-usb2"; + phy->phy.set_suspend = omap_usb2_suspend; + phy->phy.otg = otg; + phy->phy.type = USB_PHY_TYPE_USB2; + + phy->control_dev = omap_get_control_dev(); + if (IS_ERR(phy->control_dev)) { + dev_dbg(&pdev->dev, "Failed to get control device\n"); + return -ENODEV; + } + + phy->is_suspended = 1; + omap_control_usb_phy_power(phy->control_dev, 0); + + otg->set_host = omap_usb_set_host; + otg->set_peripheral = omap_usb_set_peripheral; + otg->set_vbus = omap_usb_set_vbus; + otg->start_srp = omap_usb_start_srp; + otg->phy = &phy->phy; + + phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); + if (IS_ERR(phy->wkupclk)) { + dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); + return PTR_ERR(phy->wkupclk); + } + clk_prepare(phy->wkupclk); + + phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m"); + if (IS_ERR(phy->optclk)) + dev_vdbg(&pdev->dev, "unable to get refclk960m\n"); + else + clk_prepare(phy->optclk); + + usb_add_phy_dev(&phy->phy); + + platform_set_drvdata(pdev, phy); + + pm_runtime_enable(phy->dev); + + return 0; +} + +static int omap_usb2_remove(struct platform_device *pdev) +{ + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_unprepare(phy->wkupclk); + if (!IS_ERR(phy->optclk)) + clk_unprepare(phy->optclk); + usb_remove_phy(&phy->phy); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME + +static int omap_usb2_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_disable(phy->wkupclk); + if (!IS_ERR(phy->optclk)) + clk_disable(phy->optclk); + + return 0; +} + +static int omap_usb2_runtime_resume(struct device *dev) +{ + u32 ret = 0; + struct platform_device *pdev = to_platform_device(dev); + struct omap_usb *phy = platform_get_drvdata(pdev); + + ret = clk_enable(phy->wkupclk); + if (ret < 0) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto err0; + } + + if (!IS_ERR(phy->optclk)) { + ret = clk_enable(phy->optclk); + if (ret < 0) { + dev_err(phy->dev, "Failed to enable optclk %d\n", ret); + goto err1; + } + } + + return 0; + +err1: + clk_disable(phy->wkupclk); + +err0: + return ret; +} + +static const struct dev_pm_ops omap_usb2_pm_ops = { + SET_RUNTIME_PM_OPS(omap_usb2_runtime_suspend, omap_usb2_runtime_resume, + NULL) +}; + +#define DEV_PM_OPS (&omap_usb2_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id omap_usb2_id_table[] = { + { .compatible = "ti,omap-usb2" }, + {} +}; +MODULE_DEVICE_TABLE(of, omap_usb2_id_table); +#endif + +static struct platform_driver omap_usb2_driver = { + .probe = omap_usb2_probe, + .remove = omap_usb2_remove, + .driver = { + .name = "omap-usb2", + .owner = THIS_MODULE, + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(omap_usb2_id_table), + }, +}; + +module_platform_driver(omap_usb2_driver); + +MODULE_ALIAS("platform: omap_usb2"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP USB2 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-omap-usb3.c b/drivers/usb/phy/phy-omap-usb3.c new file mode 100644 index 00000000..a6e60b1e --- /dev/null +++ b/drivers/usb/phy/phy-omap-usb3.c @@ -0,0 +1,353 @@ +/* + * omap-usb3 - USB PHY, talking to dwc3 controller in OMAP. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NUM_SYS_CLKS 5 +#define PLL_STATUS 0x00000004 +#define PLL_GO 0x00000008 +#define PLL_CONFIGURATION1 0x0000000C +#define PLL_CONFIGURATION2 0x00000010 +#define PLL_CONFIGURATION3 0x00000014 +#define PLL_CONFIGURATION4 0x00000020 + +#define PLL_REGM_MASK 0x001FFE00 +#define PLL_REGM_SHIFT 0x9 +#define PLL_REGM_F_MASK 0x0003FFFF +#define PLL_REGM_F_SHIFT 0x0 +#define PLL_REGN_MASK 0x000001FE +#define PLL_REGN_SHIFT 0x1 +#define PLL_SELFREQDCO_MASK 0x0000000E +#define PLL_SELFREQDCO_SHIFT 0x1 +#define PLL_SD_MASK 0x0003FC00 +#define PLL_SD_SHIFT 0x9 +#define SET_PLL_GO 0x1 +#define PLL_TICOPWDN 0x10000 +#define PLL_LOCK 0x2 +#define PLL_IDLE 0x1 + +/* + * This is an Empirical value that works, need to confirm the actual + * value required for the USB3PHY_PLL_CONFIGURATION2.PLL_IDLE status + * to be correctly reflected in the USB3PHY_PLL_STATUS register. + */ +# define PLL_IDLE_TIME 100; + +enum sys_clk_rate { + CLK_RATE_UNDEFINED = -1, + CLK_RATE_12MHZ, + CLK_RATE_16MHZ, + CLK_RATE_19MHZ, + CLK_RATE_26MHZ, + CLK_RATE_38MHZ +}; + +static struct usb_dpll_params omap_usb3_dpll_params[NUM_SYS_CLKS] = { + {1250, 5, 4, 20, 0}, /* 12 MHz */ + {3125, 20, 4, 20, 0}, /* 16.8 MHz */ + {1172, 8, 4, 20, 65537}, /* 19.2 MHz */ + {1250, 12, 4, 20, 0}, /* 26 MHz */ + {3125, 47, 4, 20, 92843}, /* 38.4 MHz */ +}; + +static int omap_usb3_suspend(struct usb_phy *x, int suspend) +{ + struct omap_usb *phy = phy_to_omapusb(x); + int val; + int timeout = PLL_IDLE_TIME; + + if (suspend && !phy->is_suspended) { + val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val |= PLL_IDLE; + omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + do { + val = omap_usb_readl(phy->pll_ctrl_base, PLL_STATUS); + if (val & PLL_TICOPWDN) + break; + udelay(1); + } while (--timeout); + + omap_control_usb3_phy_power(phy->control_dev, 0); + + phy->is_suspended = 1; + } else if (!suspend && phy->is_suspended) { + phy->is_suspended = 0; + + val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val &= ~PLL_IDLE; + omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + do { + val = omap_usb_readl(phy->pll_ctrl_base, PLL_STATUS); + if (!(val & PLL_TICOPWDN)) + break; + udelay(1); + } while (--timeout); + } + + return 0; +} + +static inline enum sys_clk_rate __get_sys_clk_index(unsigned long rate) +{ + switch (rate) { + case 12000000: + return CLK_RATE_12MHZ; + case 16800000: + return CLK_RATE_16MHZ; + case 19200000: + return CLK_RATE_19MHZ; + case 26000000: + return CLK_RATE_26MHZ; + case 38400000: + return CLK_RATE_38MHZ; + default: + return CLK_RATE_UNDEFINED; + } +} + +static void omap_usb_dpll_relock(struct omap_usb *phy) +{ + u32 val; + unsigned long timeout; + + omap_usb_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO); + + timeout = jiffies + msecs_to_jiffies(20); + do { + val = omap_usb_readl(phy->pll_ctrl_base, PLL_STATUS); + if (val & PLL_LOCK) + break; + } while (!WARN_ON(time_after(jiffies, timeout))); +} + +static int omap_usb_dpll_lock(struct omap_usb *phy) +{ + u32 val; + unsigned long rate; + enum sys_clk_rate clk_index; + + rate = clk_get_rate(phy->sys_clk); + clk_index = __get_sys_clk_index(rate); + + if (clk_index == CLK_RATE_UNDEFINED) { + pr_err("dpll cannot be locked for sys clk freq:%luHz\n", rate); + return -EINVAL; + } + + val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); + val &= ~PLL_REGN_MASK; + val |= omap_usb3_dpll_params[clk_index].n << PLL_REGN_SHIFT; + omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); + + val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val &= ~PLL_SELFREQDCO_MASK; + val |= omap_usb3_dpll_params[clk_index].freq << PLL_SELFREQDCO_SHIFT; + omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); + val &= ~PLL_REGM_MASK; + val |= omap_usb3_dpll_params[clk_index].m << PLL_REGM_SHIFT; + omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); + + val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION4); + val &= ~PLL_REGM_F_MASK; + val |= omap_usb3_dpll_params[clk_index].mf << PLL_REGM_F_SHIFT; + omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION4, val); + + val = omap_usb_readl(phy->pll_ctrl_base, PLL_CONFIGURATION3); + val &= ~PLL_SD_MASK; + val |= omap_usb3_dpll_params[clk_index].sd << PLL_SD_SHIFT; + omap_usb_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val); + + omap_usb_dpll_relock(phy); + + return 0; +} + +static int omap_usb3_init(struct usb_phy *x) +{ + struct omap_usb *phy = phy_to_omapusb(x); + + omap_usb_dpll_lock(phy); + omap_control_usb3_phy_power(phy->control_dev, 1); + + return 0; +} + +static int omap_usb3_probe(struct platform_device *pdev) +{ + struct omap_usb *phy; + struct resource *res; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + dev_err(&pdev->dev, "unable to alloc mem for OMAP USB3 PHY\n"); + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll_ctrl"); + phy->pll_ctrl_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(phy->pll_ctrl_base)) + return PTR_ERR(phy->pll_ctrl_base); + + phy->dev = &pdev->dev; + + phy->phy.dev = phy->dev; + phy->phy.label = "omap-usb3"; + phy->phy.init = omap_usb3_init; + phy->phy.set_suspend = omap_usb3_suspend; + phy->phy.type = USB_PHY_TYPE_USB3; + + phy->is_suspended = 1; + phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); + if (IS_ERR(phy->wkupclk)) { + dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); + return PTR_ERR(phy->wkupclk); + } + clk_prepare(phy->wkupclk); + + phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m"); + if (IS_ERR(phy->optclk)) { + dev_err(&pdev->dev, "unable to get usb_otg_ss_refclk960m\n"); + return PTR_ERR(phy->optclk); + } + clk_prepare(phy->optclk); + + phy->sys_clk = devm_clk_get(phy->dev, "sys_clkin"); + if (IS_ERR(phy->sys_clk)) { + pr_err("%s: unable to get sys_clkin\n", __func__); + return -EINVAL; + } + + phy->control_dev = omap_get_control_dev(); + if (IS_ERR(phy->control_dev)) { + dev_dbg(&pdev->dev, "Failed to get control device\n"); + return -ENODEV; + } + + omap_control_usb3_phy_power(phy->control_dev, 0); + usb_add_phy_dev(&phy->phy); + + platform_set_drvdata(pdev, phy); + + pm_runtime_enable(phy->dev); + pm_runtime_get(&pdev->dev); + + return 0; +} + +static int omap_usb3_remove(struct platform_device *pdev) +{ + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_unprepare(phy->wkupclk); + clk_unprepare(phy->optclk); + usb_remove_phy(&phy->phy); + if (!pm_runtime_suspended(&pdev->dev)) + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME + +static int omap_usb3_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_disable(phy->wkupclk); + clk_disable(phy->optclk); + + return 0; +} + +static int omap_usb3_runtime_resume(struct device *dev) +{ + u32 ret = 0; + struct platform_device *pdev = to_platform_device(dev); + struct omap_usb *phy = platform_get_drvdata(pdev); + + ret = clk_enable(phy->optclk); + if (ret) { + dev_err(phy->dev, "Failed to enable optclk %d\n", ret); + goto err1; + } + + ret = clk_enable(phy->wkupclk); + if (ret) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto err2; + } + + return 0; + +err2: + clk_disable(phy->optclk); + +err1: + return ret; +} + +static const struct dev_pm_ops omap_usb3_pm_ops = { + SET_RUNTIME_PM_OPS(omap_usb3_runtime_suspend, omap_usb3_runtime_resume, + NULL) +}; + +#define DEV_PM_OPS (&omap_usb3_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id omap_usb3_id_table[] = { + { .compatible = "ti,omap-usb3" }, + {} +}; +MODULE_DEVICE_TABLE(of, omap_usb3_id_table); +#endif + +static struct platform_driver omap_usb3_driver = { + .probe = omap_usb3_probe, + .remove = omap_usb3_remove, + .driver = { + .name = "omap-usb3", + .owner = THIS_MODULE, + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(omap_usb3_id_table), + }, +}; + +module_platform_driver(omap_usb3_driver); + +MODULE_ALIAS("platform: omap_usb3"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP USB3 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-rcar-usb.c b/drivers/usb/phy/phy-rcar-usb.c new file mode 100644 index 00000000..a35681b0 --- /dev/null +++ b/drivers/usb/phy/phy-rcar-usb.c @@ -0,0 +1,220 @@ +/* + * Renesas R-Car USB phy driver + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +/* USBH common register */ +#define USBPCTRL0 0x0800 +#define USBPCTRL1 0x0804 +#define USBST 0x0808 +#define USBEH0 0x080C +#define USBOH0 0x081C +#define USBCTL0 0x0858 +#define EIIBC1 0x0094 +#define EIIBC2 0x009C + +/* USBPCTRL1 */ +#define PHY_RST (1 << 2) +#define PLL_ENB (1 << 1) +#define PHY_ENB (1 << 0) + +/* USBST */ +#define ST_ACT (1 << 31) +#define ST_PLL (1 << 30) + +struct rcar_usb_phy_priv { + struct usb_phy phy; + spinlock_t lock; + + void __iomem *reg0; + void __iomem *reg1; + int counter; +}; + +#define usb_phy_to_priv(p) container_of(p, struct rcar_usb_phy_priv, phy) + + +/* + * USB initial/install operation. + * + * This function setup USB phy. + * The used value and setting order came from + * [USB :: Initial setting] on datasheet. + */ +static int rcar_usb_phy_init(struct usb_phy *phy) +{ + struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy); + struct device *dev = phy->dev; + void __iomem *reg0 = priv->reg0; + void __iomem *reg1 = priv->reg1; + int i; + u32 val; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + if (priv->counter++ == 0) { + + /* + * USB phy start-up + */ + + /* (1) USB-PHY standby release */ + iowrite32(PHY_ENB, (reg0 + USBPCTRL1)); + + /* (2) start USB-PHY internal PLL */ + iowrite32(PHY_ENB | PLL_ENB, (reg0 + USBPCTRL1)); + + /* (3) USB module status check */ + for (i = 0; i < 1024; i++) { + udelay(10); + val = ioread32(reg0 + USBST); + if (val == (ST_ACT | ST_PLL)) + break; + } + + if (val != (ST_ACT | ST_PLL)) { + dev_err(dev, "USB phy not ready\n"); + goto phy_init_end; + } + + /* (4) USB-PHY reset clear */ + iowrite32(PHY_ENB | PLL_ENB | PHY_RST, (reg0 + USBPCTRL1)); + + /* set platform specific port settings */ + iowrite32(0x00000000, (reg0 + USBPCTRL0)); + + /* + * EHCI IP internal buffer setting + * EHCI IP internal buffer enable + * + * These are recommended value of a datasheet + * see [USB :: EHCI internal buffer setting] + */ + iowrite32(0x00ff0040, (reg0 + EIIBC1)); + iowrite32(0x00ff0040, (reg1 + EIIBC1)); + + iowrite32(0x00000001, (reg0 + EIIBC2)); + iowrite32(0x00000001, (reg1 + EIIBC2)); + + /* + * Bus alignment settings + */ + + /* (1) EHCI bus alignment (little endian) */ + iowrite32(0x00000000, (reg0 + USBEH0)); + + /* (1) OHCI bus alignment (little endian) */ + iowrite32(0x00000000, (reg0 + USBOH0)); + } + +phy_init_end: + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static void rcar_usb_phy_shutdown(struct usb_phy *phy) +{ + struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy); + void __iomem *reg0 = priv->reg0; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + if (priv->counter-- == 1) { /* last user */ + iowrite32(0x00000000, (reg0 + USBPCTRL0)); + iowrite32(0x00000000, (reg0 + USBPCTRL1)); + } + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static int rcar_usb_phy_probe(struct platform_device *pdev) +{ + struct rcar_usb_phy_priv *priv; + struct resource *res0, *res1; + struct device *dev = &pdev->dev; + void __iomem *reg0, *reg1; + int ret; + + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res0 || !res1) { + dev_err(dev, "Not enough platform resources\n"); + return -EINVAL; + } + + /* + * CAUTION + * + * Because this phy address is also mapped under OHCI/EHCI address area, + * this driver can't use devm_request_and_ioremap(dev, res) here + */ + reg0 = devm_ioremap_nocache(dev, res0->start, resource_size(res0)); + reg1 = devm_ioremap_nocache(dev, res1->start, resource_size(res1)); + if (!reg0 || !reg1) { + dev_err(dev, "ioremap error\n"); + return -ENOMEM; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev, "priv data allocation error\n"); + return -ENOMEM; + } + + priv->reg0 = reg0; + priv->reg1 = reg1; + priv->counter = 0; + priv->phy.dev = dev; + priv->phy.label = dev_name(dev); + priv->phy.init = rcar_usb_phy_init; + priv->phy.shutdown = rcar_usb_phy_shutdown; + spin_lock_init(&priv->lock); + + ret = usb_add_phy(&priv->phy, USB_PHY_TYPE_USB2); + if (ret < 0) { + dev_err(dev, "usb phy addition error\n"); + return ret; + } + + platform_set_drvdata(pdev, priv); + + return ret; +} + +static int rcar_usb_phy_remove(struct platform_device *pdev) +{ + struct rcar_usb_phy_priv *priv = platform_get_drvdata(pdev); + + usb_remove_phy(&priv->phy); + + return 0; +} + +static struct platform_driver rcar_usb_phy_driver = { + .driver = { + .name = "rcar_usb_phy", + }, + .probe = rcar_usb_phy_probe, + .remove = rcar_usb_phy_remove, +}; + +module_platform_driver(rcar_usb_phy_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car USB phy"); +MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/drivers/usb/phy/phy-samsung-usb.c b/drivers/usb/phy/phy-samsung-usb.c new file mode 100644 index 00000000..7b118ee5 --- /dev/null +++ b/drivers/usb/phy/phy-samsung-usb.c @@ -0,0 +1,236 @@ +/* linux/drivers/usb/phy/phy-samsung-usb.c + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Praveen Paneri + * + * Samsung USB-PHY helper driver with common function calls; + * interacts with Samsung USB 2.0 PHY controller driver and later + * with Samsung USB 3.0 PHY driver. + * + * This program is free software; you can redistribute 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 that 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-samsung-usb.h" + +int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy) +{ + struct device_node *usbphy_sys; + + /* Getting node for system controller interface for usb-phy */ + usbphy_sys = of_get_child_by_name(sphy->dev->of_node, "usbphy-sys"); + if (!usbphy_sys) { + dev_err(sphy->dev, "No sys-controller interface for usb-phy\n"); + return -ENODEV; + } + + sphy->pmuregs = of_iomap(usbphy_sys, 0); + + if (sphy->pmuregs == NULL) { + dev_err(sphy->dev, "Can't get usb-phy pmu control register\n"); + goto err0; + } + + sphy->sysreg = of_iomap(usbphy_sys, 1); + + /* + * Not returning error code here, since this situation is not fatal. + * Few SoCs may not have this switch available + */ + if (sphy->sysreg == NULL) + dev_warn(sphy->dev, "Can't get usb-phy sysreg cfg register\n"); + + of_node_put(usbphy_sys); + + return 0; + +err0: + of_node_put(usbphy_sys); + return -ENXIO; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_parse_dt); + +/* + * Set isolation here for phy. + * Here 'on = true' would mean USB PHY block is isolated, hence + * de-activated and vice-versa. + */ +void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) +{ + void __iomem *reg = NULL; + u32 reg_val; + u32 en_mask = 0; + + if (!sphy->pmuregs) { + dev_warn(sphy->dev, "Can't set pmu isolation\n"); + return; + } + + switch (sphy->drv_data->cpu_type) { + case TYPE_S3C64XX: + /* + * Do nothing: We will add here once S3C64xx goes for DT support + */ + break; + case TYPE_EXYNOS4210: + /* + * Fall through since exynos4210 and exynos5250 have similar + * register architecture: two separate registers for host and + * device phy control with enable bit at position 0. + */ + case TYPE_EXYNOS5250: + if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { + reg = sphy->pmuregs + + sphy->drv_data->devphy_reg_offset; + en_mask = sphy->drv_data->devphy_en_mask; + } else if (sphy->phy_type == USB_PHY_TYPE_HOST) { + reg = sphy->pmuregs + + sphy->drv_data->hostphy_reg_offset; + en_mask = sphy->drv_data->hostphy_en_mask; + } + break; + default: + dev_err(sphy->dev, "Invalid SoC type\n"); + return; + } + + reg_val = readl(reg); + + if (on) + reg_val &= ~en_mask; + else + reg_val |= en_mask; + + writel(reg_val, reg); +} +EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation); + +/* + * Configure the mode of working of usb-phy here: HOST/DEVICE. + */ +void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy) +{ + u32 reg; + + if (!sphy->sysreg) { + dev_warn(sphy->dev, "Can't configure specified phy mode\n"); + return; + } + + reg = readl(sphy->sysreg); + + if (sphy->phy_type == USB_PHY_TYPE_DEVICE) + reg &= ~EXYNOS_USB20PHY_CFG_HOST_LINK; + else if (sphy->phy_type == USB_PHY_TYPE_HOST) + reg |= EXYNOS_USB20PHY_CFG_HOST_LINK; + + writel(reg, sphy->sysreg); +} +EXPORT_SYMBOL_GPL(samsung_usbphy_cfg_sel); + +/* + * PHYs are different for USB Device and USB Host. + * This make sure that correct PHY type is selected before + * any operation on PHY. + */ +int samsung_usbphy_set_type(struct usb_phy *phy, + enum samsung_usb_phy_type phy_type) +{ + struct samsung_usbphy *sphy = phy_to_sphy(phy); + + sphy->phy_type = phy_type; + + return 0; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_set_type); + +/* + * Returns reference clock frequency selection value + */ +int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) +{ + struct clk *ref_clk; + int refclk_freq = 0; + + /* + * In exynos5250 USB host and device PHY use + * external crystal clock XXTI + */ + if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) + ref_clk = devm_clk_get(sphy->dev, "ext_xtal"); + else + ref_clk = devm_clk_get(sphy->dev, "xusbxti"); + if (IS_ERR(ref_clk)) { + dev_err(sphy->dev, "Failed to get reference clock\n"); + return PTR_ERR(ref_clk); + } + + if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) { + /* set clock frequency for PLL */ + switch (clk_get_rate(ref_clk)) { + case 9600 * KHZ: + refclk_freq = FSEL_CLKSEL_9600K; + break; + case 10 * MHZ: + refclk_freq = FSEL_CLKSEL_10M; + break; + case 12 * MHZ: + refclk_freq = FSEL_CLKSEL_12M; + break; + case 19200 * KHZ: + refclk_freq = FSEL_CLKSEL_19200K; + break; + case 20 * MHZ: + refclk_freq = FSEL_CLKSEL_20M; + break; + case 50 * MHZ: + refclk_freq = FSEL_CLKSEL_50M; + break; + case 24 * MHZ: + default: + /* default reference clock */ + refclk_freq = FSEL_CLKSEL_24M; + break; + } + } else { + switch (clk_get_rate(ref_clk)) { + case 12 * MHZ: + refclk_freq = PHYCLK_CLKSEL_12M; + break; + case 24 * MHZ: + refclk_freq = PHYCLK_CLKSEL_24M; + break; + case 48 * MHZ: + refclk_freq = PHYCLK_CLKSEL_48M; + break; + default: + if (sphy->drv_data->cpu_type == TYPE_S3C64XX) + refclk_freq = PHYCLK_CLKSEL_48M; + else + refclk_freq = PHYCLK_CLKSEL_24M; + break; + } + } + clk_put(ref_clk); + + return refclk_freq; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_get_refclk_freq); diff --git a/drivers/usb/phy/phy-samsung-usb.h b/drivers/usb/phy/phy-samsung-usb.h new file mode 100644 index 00000000..70a9cae5 --- /dev/null +++ b/drivers/usb/phy/phy-samsung-usb.h @@ -0,0 +1,327 @@ +/* linux/drivers/usb/phy/phy-samsung-usb.h + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung USB-PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and + * OHCI-EXYNOS controllers. + * + * This program is free software; you can redistribute 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 that 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 + +/* Register definitions */ + +#define SAMSUNG_PHYPWR (0x00) + +#define PHYPWR_NORMAL_MASK (0x19 << 0) +#define PHYPWR_OTG_DISABLE (0x1 << 4) +#define PHYPWR_ANALOG_POWERDOWN (0x1 << 3) +#define PHYPWR_FORCE_SUSPEND (0x1 << 1) +/* For Exynos4 */ +#define PHYPWR_NORMAL_MASK_PHY0 (0x39 << 0) +#define PHYPWR_SLEEP_PHY0 (0x1 << 5) + +#define SAMSUNG_PHYCLK (0x04) + +#define PHYCLK_MODE_USB11 (0x1 << 6) +#define PHYCLK_EXT_OSC (0x1 << 5) +#define PHYCLK_COMMON_ON_N (0x1 << 4) +#define PHYCLK_ID_PULL (0x1 << 2) +#define PHYCLK_CLKSEL_MASK (0x3 << 0) +#define PHYCLK_CLKSEL_48M (0x0 << 0) +#define PHYCLK_CLKSEL_12M (0x2 << 0) +#define PHYCLK_CLKSEL_24M (0x3 << 0) + +#define SAMSUNG_RSTCON (0x08) + +#define RSTCON_PHYLINK_SWRST (0x1 << 2) +#define RSTCON_HLINK_SWRST (0x1 << 1) +#define RSTCON_SWRST (0x1 << 0) + +/* EXYNOS5 */ +#define EXYNOS5_PHY_HOST_CTRL0 (0x00) + +#define HOST_CTRL0_PHYSWRSTALL (0x1 << 31) + +#define HOST_CTRL0_REFCLKSEL_MASK (0x3 << 19) +#define HOST_CTRL0_REFCLKSEL_XTAL (0x0 << 19) +#define HOST_CTRL0_REFCLKSEL_EXTL (0x1 << 19) +#define HOST_CTRL0_REFCLKSEL_CLKCORE (0x2 << 19) + +#define HOST_CTRL0_FSEL_MASK (0x7 << 16) +#define HOST_CTRL0_FSEL(_x) ((_x) << 16) + +#define FSEL_CLKSEL_50M (0x7) +#define FSEL_CLKSEL_24M (0x5) +#define FSEL_CLKSEL_20M (0x4) +#define FSEL_CLKSEL_19200K (0x3) +#define FSEL_CLKSEL_12M (0x2) +#define FSEL_CLKSEL_10M (0x1) +#define FSEL_CLKSEL_9600K (0x0) + +#define HOST_CTRL0_TESTBURNIN (0x1 << 11) +#define HOST_CTRL0_RETENABLE (0x1 << 10) +#define HOST_CTRL0_COMMONON_N (0x1 << 9) +#define HOST_CTRL0_SIDDQ (0x1 << 6) +#define HOST_CTRL0_FORCESLEEP (0x1 << 5) +#define HOST_CTRL0_FORCESUSPEND (0x1 << 4) +#define HOST_CTRL0_WORDINTERFACE (0x1 << 3) +#define HOST_CTRL0_UTMISWRST (0x1 << 2) +#define HOST_CTRL0_LINKSWRST (0x1 << 1) +#define HOST_CTRL0_PHYSWRST (0x1 << 0) + +#define EXYNOS5_PHY_HOST_TUNE0 (0x04) + +#define EXYNOS5_PHY_HSIC_CTRL1 (0x10) + +#define EXYNOS5_PHY_HSIC_TUNE1 (0x14) + +#define EXYNOS5_PHY_HSIC_CTRL2 (0x20) + +#define EXYNOS5_PHY_HSIC_TUNE2 (0x24) + +#define HSIC_CTRL_REFCLKSEL_MASK (0x3 << 23) +#define HSIC_CTRL_REFCLKSEL (0x2 << 23) + +#define HSIC_CTRL_REFCLKDIV_MASK (0x7f << 16) +#define HSIC_CTRL_REFCLKDIV(_x) ((_x) << 16) +#define HSIC_CTRL_REFCLKDIV_12 (0x24 << 16) +#define HSIC_CTRL_REFCLKDIV_15 (0x1c << 16) +#define HSIC_CTRL_REFCLKDIV_16 (0x1a << 16) +#define HSIC_CTRL_REFCLKDIV_19_2 (0x15 << 16) +#define HSIC_CTRL_REFCLKDIV_20 (0x14 << 16) + +#define HSIC_CTRL_SIDDQ (0x1 << 6) +#define HSIC_CTRL_FORCESLEEP (0x1 << 5) +#define HSIC_CTRL_FORCESUSPEND (0x1 << 4) +#define HSIC_CTRL_WORDINTERFACE (0x1 << 3) +#define HSIC_CTRL_UTMISWRST (0x1 << 2) +#define HSIC_CTRL_PHYSWRST (0x1 << 0) + +#define EXYNOS5_PHY_HOST_EHCICTRL (0x30) + +#define HOST_EHCICTRL_ENAINCRXALIGN (0x1 << 29) +#define HOST_EHCICTRL_ENAINCR4 (0x1 << 28) +#define HOST_EHCICTRL_ENAINCR8 (0x1 << 27) +#define HOST_EHCICTRL_ENAINCR16 (0x1 << 26) + +#define EXYNOS5_PHY_HOST_OHCICTRL (0x34) + +#define HOST_OHCICTRL_SUSPLGCY (0x1 << 3) +#define HOST_OHCICTRL_APPSTARTCLK (0x1 << 2) +#define HOST_OHCICTRL_CNTSEL (0x1 << 1) +#define HOST_OHCICTRL_CLKCKTRST (0x1 << 0) + +#define EXYNOS5_PHY_OTG_SYS (0x38) + +#define OTG_SYS_PHYLINK_SWRESET (0x1 << 14) +#define OTG_SYS_LINKSWRST_UOTG (0x1 << 13) +#define OTG_SYS_PHY0_SWRST (0x1 << 12) + +#define OTG_SYS_REFCLKSEL_MASK (0x3 << 9) +#define OTG_SYS_REFCLKSEL_XTAL (0x0 << 9) +#define OTG_SYS_REFCLKSEL_EXTL (0x1 << 9) +#define OTG_SYS_REFCLKSEL_CLKCORE (0x2 << 9) + +#define OTG_SYS_IDPULLUP_UOTG (0x1 << 8) +#define OTG_SYS_COMMON_ON (0x1 << 7) + +#define OTG_SYS_FSEL_MASK (0x7 << 4) +#define OTG_SYS_FSEL(_x) ((_x) << 4) + +#define OTG_SYS_FORCESLEEP (0x1 << 3) +#define OTG_SYS_OTGDISABLE (0x1 << 2) +#define OTG_SYS_SIDDQ_UOTG (0x1 << 1) +#define OTG_SYS_FORCESUSPEND (0x1 << 0) + +#define EXYNOS5_PHY_OTG_TUNE (0x40) + +/* EXYNOS5: USB 3.0 DRD */ +#define EXYNOS5_DRD_LINKSYSTEM (0x04) + +#define LINKSYSTEM_FLADJ_MASK (0x3f << 1) +#define LINKSYSTEM_FLADJ(_x) ((_x) << 1) +#define LINKSYSTEM_XHCI_VERSION_CONTROL (0x1 << 27) + +#define EXYNOS5_DRD_PHYUTMI (0x08) + +#define PHYUTMI_OTGDISABLE (0x1 << 6) +#define PHYUTMI_FORCESUSPEND (0x1 << 1) +#define PHYUTMI_FORCESLEEP (0x1 << 0) + +#define EXYNOS5_DRD_PHYPIPE (0x0c) + +#define EXYNOS5_DRD_PHYCLKRST (0x10) + +#define PHYCLKRST_SSC_REFCLKSEL_MASK (0xff << 23) +#define PHYCLKRST_SSC_REFCLKSEL(_x) ((_x) << 23) + +#define PHYCLKRST_SSC_RANGE_MASK (0x03 << 21) +#define PHYCLKRST_SSC_RANGE(_x) ((_x) << 21) + +#define PHYCLKRST_SSC_EN (0x1 << 20) +#define PHYCLKRST_REF_SSP_EN (0x1 << 19) +#define PHYCLKRST_REF_CLKDIV2 (0x1 << 18) + +#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF (0x19 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF (0x02 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF (0x68 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF (0x7d << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF (0x02 << 11) + +#define PHYCLKRST_FSEL_MASK (0x3f << 5) +#define PHYCLKRST_FSEL(_x) ((_x) << 5) +#define PHYCLKRST_FSEL_PAD_100MHZ (0x27 << 5) +#define PHYCLKRST_FSEL_PAD_24MHZ (0x2a << 5) +#define PHYCLKRST_FSEL_PAD_20MHZ (0x31 << 5) +#define PHYCLKRST_FSEL_PAD_19_2MHZ (0x38 << 5) + +#define PHYCLKRST_RETENABLEN (0x1 << 4) + +#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2) +#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2) +#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2) + +#define PHYCLKRST_PORTRESET (0x1 << 1) +#define PHYCLKRST_COMMONONN (0x1 << 0) + +#define EXYNOS5_DRD_PHYREG0 (0x14) +#define EXYNOS5_DRD_PHYREG1 (0x18) + +#define EXYNOS5_DRD_PHYPARAM0 (0x1c) + +#define PHYPARAM0_REF_USE_PAD (0x1 << 31) +#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26) +#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26) + +#define EXYNOS5_DRD_PHYPARAM1 (0x20) + +#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x1f << 0) +#define PHYPARAM1_PCS_TXDEEMPH (0x1c) + +#define EXYNOS5_DRD_PHYTERM (0x24) + +#define EXYNOS5_DRD_PHYTEST (0x28) + +#define PHYTEST_POWERDOWN_SSP (0x1 << 3) +#define PHYTEST_POWERDOWN_HSP (0x1 << 2) + +#define EXYNOS5_DRD_PHYADP (0x2c) + +#define EXYNOS5_DRD_PHYBATCHG (0x30) + +#define PHYBATCHG_UTMI_CLKSEL (0x1 << 2) + +#define EXYNOS5_DRD_PHYRESUME (0x34) +#define EXYNOS5_DRD_LINKPORT (0x44) + +#ifndef MHZ +#define MHZ (1000*1000) +#endif + +#ifndef KHZ +#define KHZ (1000) +#endif + +#define EXYNOS_USBHOST_PHY_CTRL_OFFSET (0x4) +#define S3C64XX_USBPHY_ENABLE (0x1 << 16) +#define EXYNOS_USBPHY_ENABLE (0x1 << 0) +#define EXYNOS_USB20PHY_CFG_HOST_LINK (0x1 << 0) + +enum samsung_cpu_type { + TYPE_S3C64XX, + TYPE_EXYNOS4210, + TYPE_EXYNOS5250, +}; + +/* + * struct samsung_usbphy_drvdata - driver data for various SoC variants + * @cpu_type: machine identifier + * @devphy_en_mask: device phy enable mask for PHY CONTROL register + * @hostphy_en_mask: host phy enable mask for PHY CONTROL register + * @devphy_reg_offset: offset to DEVICE PHY CONTROL register from + * mapped address of system controller. + * @hostphy_reg_offset: offset to HOST PHY CONTROL register from + * mapped address of system controller. + * + * Here we have a separate mask for device type phy. + * Having different masks for host and device type phy helps + * in setting independent masks in case of SoCs like S5PV210, + * in which PHY0 and PHY1 enable bits belong to same register + * placed at position 0 and 1 respectively. + * Although for newer SoCs like exynos these bits belong to + * different registers altogether placed at position 0. + */ +struct samsung_usbphy_drvdata { + int cpu_type; + int devphy_en_mask; + int hostphy_en_mask; + u32 devphy_reg_offset; + u32 hostphy_reg_offset; +}; + +/* + * struct samsung_usbphy - transceiver driver state + * @phy: transceiver structure + * @plat: platform data + * @dev: The parent device supplied to the probe function + * @clk: usb phy clock + * @regs: usb phy controller registers memory base + * @pmuregs: USB device PHY_CONTROL register memory base + * @sysreg: USB2.0 PHY_CFG register memory base + * @ref_clk_freq: reference clock frequency selection + * @drv_data: driver data available for different SoCs + * @phy_type: Samsung SoCs specific phy types: #HOST + * #DEVICE + * @phy_usage: usage count for phy + * @lock: lock for phy operations + */ +struct samsung_usbphy { + struct usb_phy phy; + struct samsung_usbphy_data *plat; + struct device *dev; + struct clk *clk; + void __iomem *regs; + void __iomem *pmuregs; + void __iomem *sysreg; + int ref_clk_freq; + const struct samsung_usbphy_drvdata *drv_data; + enum samsung_usb_phy_type phy_type; + atomic_t phy_usage; + spinlock_t lock; +}; + +#define phy_to_sphy(x) container_of((x), struct samsung_usbphy, phy) + +static const struct of_device_id samsung_usbphy_dt_match[]; + +static inline const struct samsung_usbphy_drvdata +*samsung_usbphy_get_driver_data(struct platform_device *pdev) +{ + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(samsung_usbphy_dt_match, + pdev->dev.of_node); + return match->data; + } + + return (struct samsung_usbphy_drvdata *) + platform_get_device_id(pdev)->driver_data; +} + +extern int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy); +extern void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on); +extern void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy); +extern int samsung_usbphy_set_type(struct usb_phy *phy, + enum samsung_usb_phy_type phy_type); +extern int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy); diff --git a/drivers/usb/phy/phy-samsung-usb2.c b/drivers/usb/phy/phy-samsung-usb2.c new file mode 100644 index 00000000..9d5e273a --- /dev/null +++ b/drivers/usb/phy/phy-samsung-usb2.c @@ -0,0 +1,504 @@ +/* linux/drivers/usb/phy/phy-samsung-usb2.c + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Praveen Paneri + * + * Samsung USB2.0 PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and + * OHCI-EXYNOS controllers. + * + * This program is free software; you can redistribute 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 that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-samsung-usb.h" + +static int samsung_usbphy_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + if (!otg) + return -ENODEV; + + if (!otg->host) + otg->host = host; + + return 0; +} + +static bool exynos5_phyhost_is_on(void __iomem *regs) +{ + u32 reg; + + reg = readl(regs + EXYNOS5_PHY_HOST_CTRL0); + + return !(reg & HOST_CTRL0_SIDDQ); +} + +static void samsung_exynos5_usb2phy_enable(struct samsung_usbphy *sphy) +{ + void __iomem *regs = sphy->regs; + u32 phyclk = sphy->ref_clk_freq; + u32 phyhost; + u32 phyotg; + u32 phyhsic; + u32 ehcictrl; + u32 ohcictrl; + + /* + * phy_usage helps in keeping usage count for phy + * so that the first consumer enabling the phy is also + * the last consumer to disable it. + */ + + atomic_inc(&sphy->phy_usage); + + if (exynos5_phyhost_is_on(regs)) { + dev_info(sphy->dev, "Already power on PHY\n"); + return; + } + + /* Host configuration */ + phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0); + + /* phy reference clock configuration */ + phyhost &= ~HOST_CTRL0_FSEL_MASK; + phyhost |= HOST_CTRL0_FSEL(phyclk); + + /* host phy reset */ + phyhost &= ~(HOST_CTRL0_PHYSWRST | + HOST_CTRL0_PHYSWRSTALL | + HOST_CTRL0_SIDDQ | + /* Enable normal mode of operation */ + HOST_CTRL0_FORCESUSPEND | + HOST_CTRL0_FORCESLEEP); + + /* Link reset */ + phyhost |= (HOST_CTRL0_LINKSWRST | + HOST_CTRL0_UTMISWRST | + /* COMMON Block configuration during suspend */ + HOST_CTRL0_COMMONON_N); + writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); + udelay(10); + phyhost &= ~(HOST_CTRL0_LINKSWRST | + HOST_CTRL0_UTMISWRST); + writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); + + /* OTG configuration */ + phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS); + + /* phy reference clock configuration */ + phyotg &= ~OTG_SYS_FSEL_MASK; + phyotg |= OTG_SYS_FSEL(phyclk); + + /* Enable normal mode of operation */ + phyotg &= ~(OTG_SYS_FORCESUSPEND | + OTG_SYS_SIDDQ_UOTG | + OTG_SYS_FORCESLEEP | + OTG_SYS_REFCLKSEL_MASK | + /* COMMON Block configuration during suspend */ + OTG_SYS_COMMON_ON); + + /* OTG phy & link reset */ + phyotg |= (OTG_SYS_PHY0_SWRST | + OTG_SYS_LINKSWRST_UOTG | + OTG_SYS_PHYLINK_SWRESET | + OTG_SYS_OTGDISABLE | + /* Set phy refclk */ + OTG_SYS_REFCLKSEL_CLKCORE); + + writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); + udelay(10); + phyotg &= ~(OTG_SYS_PHY0_SWRST | + OTG_SYS_LINKSWRST_UOTG | + OTG_SYS_PHYLINK_SWRESET); + writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); + + /* HSIC phy configuration */ + phyhsic = (HSIC_CTRL_REFCLKDIV_12 | + HSIC_CTRL_REFCLKSEL | + HSIC_CTRL_PHYSWRST); + writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); + writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); + udelay(10); + phyhsic &= ~HSIC_CTRL_PHYSWRST; + writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); + writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); + + udelay(80); + + /* enable EHCI DMA burst */ + ehcictrl = readl(regs + EXYNOS5_PHY_HOST_EHCICTRL); + ehcictrl |= (HOST_EHCICTRL_ENAINCRXALIGN | + HOST_EHCICTRL_ENAINCR4 | + HOST_EHCICTRL_ENAINCR8 | + HOST_EHCICTRL_ENAINCR16); + writel(ehcictrl, regs + EXYNOS5_PHY_HOST_EHCICTRL); + + /* set ohci_suspend_on_n */ + ohcictrl = readl(regs + EXYNOS5_PHY_HOST_OHCICTRL); + ohcictrl |= HOST_OHCICTRL_SUSPLGCY; + writel(ohcictrl, regs + EXYNOS5_PHY_HOST_OHCICTRL); +} + +static void samsung_usb2phy_enable(struct samsung_usbphy *sphy) +{ + void __iomem *regs = sphy->regs; + u32 phypwr; + u32 phyclk; + u32 rstcon; + + /* set clock frequency for PLL */ + phyclk = sphy->ref_clk_freq; + phypwr = readl(regs + SAMSUNG_PHYPWR); + rstcon = readl(regs + SAMSUNG_RSTCON); + + switch (sphy->drv_data->cpu_type) { + case TYPE_S3C64XX: + phyclk &= ~PHYCLK_COMMON_ON_N; + phypwr &= ~PHYPWR_NORMAL_MASK; + rstcon |= RSTCON_SWRST; + break; + case TYPE_EXYNOS4210: + phypwr &= ~PHYPWR_NORMAL_MASK_PHY0; + rstcon |= RSTCON_SWRST; + default: + break; + } + + writel(phyclk, regs + SAMSUNG_PHYCLK); + /* Configure PHY0 for normal operation*/ + writel(phypwr, regs + SAMSUNG_PHYPWR); + /* reset all ports of PHY and Link */ + writel(rstcon, regs + SAMSUNG_RSTCON); + udelay(10); + rstcon &= ~RSTCON_SWRST; + writel(rstcon, regs + SAMSUNG_RSTCON); +} + +static void samsung_exynos5_usb2phy_disable(struct samsung_usbphy *sphy) +{ + void __iomem *regs = sphy->regs; + u32 phyhost; + u32 phyotg; + u32 phyhsic; + + if (atomic_dec_return(&sphy->phy_usage) > 0) { + dev_info(sphy->dev, "still being used\n"); + return; + } + + phyhsic = (HSIC_CTRL_REFCLKDIV_12 | + HSIC_CTRL_REFCLKSEL | + HSIC_CTRL_SIDDQ | + HSIC_CTRL_FORCESLEEP | + HSIC_CTRL_FORCESUSPEND); + writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); + writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); + + phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0); + phyhost |= (HOST_CTRL0_SIDDQ | + HOST_CTRL0_FORCESUSPEND | + HOST_CTRL0_FORCESLEEP | + HOST_CTRL0_PHYSWRST | + HOST_CTRL0_PHYSWRSTALL); + writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); + + phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS); + phyotg |= (OTG_SYS_FORCESUSPEND | + OTG_SYS_SIDDQ_UOTG | + OTG_SYS_FORCESLEEP); + writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); +} + +static void samsung_usb2phy_disable(struct samsung_usbphy *sphy) +{ + void __iomem *regs = sphy->regs; + u32 phypwr; + + phypwr = readl(regs + SAMSUNG_PHYPWR); + + switch (sphy->drv_data->cpu_type) { + case TYPE_S3C64XX: + phypwr |= PHYPWR_NORMAL_MASK; + break; + case TYPE_EXYNOS4210: + phypwr |= PHYPWR_NORMAL_MASK_PHY0; + default: + break; + } + + /* Disable analog and otg block power */ + writel(phypwr, regs + SAMSUNG_PHYPWR); +} + +/* + * The function passed to the usb driver for phy initialization + */ +static int samsung_usb2phy_init(struct usb_phy *phy) +{ + struct samsung_usbphy *sphy; + struct usb_bus *host = NULL; + unsigned long flags; + int ret = 0; + + sphy = phy_to_sphy(phy); + + host = phy->otg->host; + + /* Enable the phy clock */ + ret = clk_prepare_enable(sphy->clk); + if (ret) { + dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); + return ret; + } + + spin_lock_irqsave(&sphy->lock, flags); + + if (host) { + /* setting default phy-type for USB 2.0 */ + if (!strstr(dev_name(host->controller), "ehci") || + !strstr(dev_name(host->controller), "ohci")) + samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST); + } else { + samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); + } + + /* Disable phy isolation */ + if (sphy->plat && sphy->plat->pmu_isolation) + sphy->plat->pmu_isolation(false); + else + samsung_usbphy_set_isolation(sphy, false); + + /* Selecting Host/OTG mode; After reset USB2.0PHY_CFG: HOST */ + samsung_usbphy_cfg_sel(sphy); + + /* Initialize usb phy registers */ + if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) + samsung_exynos5_usb2phy_enable(sphy); + else + samsung_usb2phy_enable(sphy); + + spin_unlock_irqrestore(&sphy->lock, flags); + + /* Disable the phy clock */ + clk_disable_unprepare(sphy->clk); + + return ret; +} + +/* + * The function passed to the usb driver for phy shutdown + */ +static void samsung_usb2phy_shutdown(struct usb_phy *phy) +{ + struct samsung_usbphy *sphy; + struct usb_bus *host = NULL; + unsigned long flags; + + sphy = phy_to_sphy(phy); + + host = phy->otg->host; + + if (clk_prepare_enable(sphy->clk)) { + dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); + return; + } + + spin_lock_irqsave(&sphy->lock, flags); + + if (host) { + /* setting default phy-type for USB 2.0 */ + if (!strstr(dev_name(host->controller), "ehci") || + !strstr(dev_name(host->controller), "ohci")) + samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST); + } else { + samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); + } + + /* De-initialize usb phy registers */ + if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) + samsung_exynos5_usb2phy_disable(sphy); + else + samsung_usb2phy_disable(sphy); + + /* Enable phy isolation */ + if (sphy->plat && sphy->plat->pmu_isolation) + sphy->plat->pmu_isolation(true); + else + samsung_usbphy_set_isolation(sphy, true); + + spin_unlock_irqrestore(&sphy->lock, flags); + + clk_disable_unprepare(sphy->clk); +} + +static int samsung_usb2phy_probe(struct platform_device *pdev) +{ + struct samsung_usbphy *sphy; + struct usb_otg *otg; + struct samsung_usbphy_data *pdata = pdev->dev.platform_data; + const struct samsung_usbphy_drvdata *drv_data; + struct device *dev = &pdev->dev; + struct resource *phy_mem; + void __iomem *phy_base; + struct clk *clk; + int ret; + + phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy_base = devm_ioremap_resource(dev, phy_mem); + if (IS_ERR(phy_base)) + return PTR_ERR(phy_base); + + sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); + if (!sphy) + return -ENOMEM; + + otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + drv_data = samsung_usbphy_get_driver_data(pdev); + + if (drv_data->cpu_type == TYPE_EXYNOS5250) + clk = devm_clk_get(dev, "usbhost"); + else + clk = devm_clk_get(dev, "otg"); + + if (IS_ERR(clk)) { + dev_err(dev, "Failed to get otg clock\n"); + return PTR_ERR(clk); + } + + sphy->dev = dev; + + if (dev->of_node) { + ret = samsung_usbphy_parse_dt(sphy); + if (ret < 0) + return ret; + } else { + if (!pdata) { + dev_err(dev, "no platform data specified\n"); + return -EINVAL; + } + } + + sphy->plat = pdata; + sphy->regs = phy_base; + sphy->clk = clk; + sphy->drv_data = drv_data; + sphy->phy.dev = sphy->dev; + sphy->phy.label = "samsung-usb2phy"; + sphy->phy.init = samsung_usb2phy_init; + sphy->phy.shutdown = samsung_usb2phy_shutdown; + sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + + sphy->phy.otg = otg; + sphy->phy.otg->phy = &sphy->phy; + sphy->phy.otg->set_host = samsung_usbphy_set_host; + + spin_lock_init(&sphy->lock); + + platform_set_drvdata(pdev, sphy); + + return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB2); +} + +static int samsung_usb2phy_remove(struct platform_device *pdev) +{ + struct samsung_usbphy *sphy = platform_get_drvdata(pdev); + + usb_remove_phy(&sphy->phy); + + if (sphy->pmuregs) + iounmap(sphy->pmuregs); + if (sphy->sysreg) + iounmap(sphy->sysreg); + + return 0; +} + +static const struct samsung_usbphy_drvdata usb2phy_s3c64xx = { + .cpu_type = TYPE_S3C64XX, + .devphy_en_mask = S3C64XX_USBPHY_ENABLE, +}; + +static const struct samsung_usbphy_drvdata usb2phy_exynos4 = { + .cpu_type = TYPE_EXYNOS4210, + .devphy_en_mask = EXYNOS_USBPHY_ENABLE, + .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, +}; + +static struct samsung_usbphy_drvdata usb2phy_exynos5 = { + .cpu_type = TYPE_EXYNOS5250, + .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, + .hostphy_reg_offset = EXYNOS_USBHOST_PHY_CTRL_OFFSET, +}; + +#ifdef CONFIG_OF +static const struct of_device_id samsung_usbphy_dt_match[] = { + { + .compatible = "samsung,s3c64xx-usb2phy", + .data = &usb2phy_s3c64xx, + }, { + .compatible = "samsung,exynos4210-usb2phy", + .data = &usb2phy_exynos4, + }, { + .compatible = "samsung,exynos5250-usb2phy", + .data = &usb2phy_exynos5 + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match); +#endif + +static struct platform_device_id samsung_usbphy_driver_ids[] = { + { + .name = "s3c64xx-usb2phy", + .driver_data = (unsigned long)&usb2phy_s3c64xx, + }, { + .name = "exynos4210-usb2phy", + .driver_data = (unsigned long)&usb2phy_exynos4, + }, { + .name = "exynos5250-usb2phy", + .driver_data = (unsigned long)&usb2phy_exynos5, + }, + {}, +}; + +MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids); + +static struct platform_driver samsung_usb2phy_driver = { + .probe = samsung_usb2phy_probe, + .remove = samsung_usb2phy_remove, + .id_table = samsung_usbphy_driver_ids, + .driver = { + .name = "samsung-usb2phy", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_usbphy_dt_match), + }, +}; + +module_platform_driver(samsung_usb2phy_driver); + +MODULE_DESCRIPTION("Samsung USB 2.0 phy controller"); +MODULE_AUTHOR("Praveen Paneri "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-usb2phy"); diff --git a/drivers/usb/phy/phy-samsung-usb3.c b/drivers/usb/phy/phy-samsung-usb3.c new file mode 100644 index 00000000..5a9efcbc --- /dev/null +++ b/drivers/usb/phy/phy-samsung-usb3.c @@ -0,0 +1,342 @@ +/* linux/drivers/usb/phy/phy-samsung-usb3.c + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Vivek Gautam + * + * Samsung USB 3.0 PHY transceiver; talks to DWC3 controller. + * + * This program is free software; you can redistribute 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 that 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-samsung-usb.h" + +/* + * Sets the phy clk as EXTREFCLK (XXTI) which is internal clock from clock core. + */ +static u32 samsung_usb3phy_set_refclk(struct samsung_usbphy *sphy) +{ + u32 reg; + u32 refclk; + + refclk = sphy->ref_clk_freq; + + reg = PHYCLKRST_REFCLKSEL_EXT_REFCLK | + PHYCLKRST_FSEL(refclk); + + switch (refclk) { + case FSEL_CLKSEL_50M: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF | + PHYCLKRST_SSC_REFCLKSEL(0x00)); + break; + case FSEL_CLKSEL_20M: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x00)); + break; + case FSEL_CLKSEL_19200K: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x88)); + break; + case FSEL_CLKSEL_24M: + default: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x88)); + break; + } + + return reg; +} + +static int samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) +{ + void __iomem *regs = sphy->regs; + u32 phyparam0; + u32 phyparam1; + u32 linksystem; + u32 phybatchg; + u32 phytest; + u32 phyclkrst; + + /* Reset USB 3.0 PHY */ + writel(0x0, regs + EXYNOS5_DRD_PHYREG0); + + phyparam0 = readl(regs + EXYNOS5_DRD_PHYPARAM0); + /* Select PHY CLK source */ + phyparam0 &= ~PHYPARAM0_REF_USE_PAD; + /* Set Loss-of-Signal Detector sensitivity */ + phyparam0 &= ~PHYPARAM0_REF_LOSLEVEL_MASK; + phyparam0 |= PHYPARAM0_REF_LOSLEVEL; + writel(phyparam0, regs + EXYNOS5_DRD_PHYPARAM0); + + writel(0x0, regs + EXYNOS5_DRD_PHYRESUME); + + /* + * Setting the Frame length Adj value[6:1] to default 0x20 + * See xHCI 1.0 spec, 5.2.4 + */ + linksystem = LINKSYSTEM_XHCI_VERSION_CONTROL | + LINKSYSTEM_FLADJ(0x20); + writel(linksystem, regs + EXYNOS5_DRD_LINKSYSTEM); + + phyparam1 = readl(regs + EXYNOS5_DRD_PHYPARAM1); + /* Set Tx De-Emphasis level */ + phyparam1 &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; + phyparam1 |= PHYPARAM1_PCS_TXDEEMPH; + writel(phyparam1, regs + EXYNOS5_DRD_PHYPARAM1); + + phybatchg = readl(regs + EXYNOS5_DRD_PHYBATCHG); + phybatchg |= PHYBATCHG_UTMI_CLKSEL; + writel(phybatchg, regs + EXYNOS5_DRD_PHYBATCHG); + + /* PHYTEST POWERDOWN Control */ + phytest = readl(regs + EXYNOS5_DRD_PHYTEST); + phytest &= ~(PHYTEST_POWERDOWN_SSP | + PHYTEST_POWERDOWN_HSP); + writel(phytest, regs + EXYNOS5_DRD_PHYTEST); + + /* UTMI Power Control */ + writel(PHYUTMI_OTGDISABLE, regs + EXYNOS5_DRD_PHYUTMI); + + phyclkrst = samsung_usb3phy_set_refclk(sphy); + + phyclkrst |= PHYCLKRST_PORTRESET | + /* Digital power supply in normal operating mode */ + PHYCLKRST_RETENABLEN | + /* Enable ref clock for SS function */ + PHYCLKRST_REF_SSP_EN | + /* Enable spread spectrum */ + PHYCLKRST_SSC_EN | + /* Power down HS Bias and PLL blocks in suspend mode */ + PHYCLKRST_COMMONONN; + + writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); + + udelay(10); + + phyclkrst &= ~(PHYCLKRST_PORTRESET); + writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); + + return 0; +} + +static void samsung_exynos5_usb3phy_disable(struct samsung_usbphy *sphy) +{ + u32 phyutmi; + u32 phyclkrst; + u32 phytest; + void __iomem *regs = sphy->regs; + + phyutmi = PHYUTMI_OTGDISABLE | + PHYUTMI_FORCESUSPEND | + PHYUTMI_FORCESLEEP; + writel(phyutmi, regs + EXYNOS5_DRD_PHYUTMI); + + /* Resetting the PHYCLKRST enable bits to reduce leakage current */ + phyclkrst = readl(regs + EXYNOS5_DRD_PHYCLKRST); + phyclkrst &= ~(PHYCLKRST_REF_SSP_EN | + PHYCLKRST_SSC_EN | + PHYCLKRST_COMMONONN); + writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); + + /* Control PHYTEST to remove leakage current */ + phytest = readl(regs + EXYNOS5_DRD_PHYTEST); + phytest |= (PHYTEST_POWERDOWN_SSP | + PHYTEST_POWERDOWN_HSP); + writel(phytest, regs + EXYNOS5_DRD_PHYTEST); +} + +static int samsung_usb3phy_init(struct usb_phy *phy) +{ + struct samsung_usbphy *sphy; + unsigned long flags; + int ret = 0; + + sphy = phy_to_sphy(phy); + + /* Enable the phy clock */ + ret = clk_prepare_enable(sphy->clk); + if (ret) { + dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); + return ret; + } + + spin_lock_irqsave(&sphy->lock, flags); + + /* setting default phy-type for USB 3.0 */ + samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); + + /* Disable phy isolation */ + samsung_usbphy_set_isolation(sphy, false); + + /* Initialize usb phy registers */ + samsung_exynos5_usb3phy_enable(sphy); + + spin_unlock_irqrestore(&sphy->lock, flags); + + /* Disable the phy clock */ + clk_disable_unprepare(sphy->clk); + + return ret; +} + +/* + * The function passed to the usb driver for phy shutdown + */ +static void samsung_usb3phy_shutdown(struct usb_phy *phy) +{ + struct samsung_usbphy *sphy; + unsigned long flags; + + sphy = phy_to_sphy(phy); + + if (clk_prepare_enable(sphy->clk)) { + dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); + return; + } + + spin_lock_irqsave(&sphy->lock, flags); + + /* setting default phy-type for USB 3.0 */ + samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); + + /* De-initialize usb phy registers */ + samsung_exynos5_usb3phy_disable(sphy); + + /* Enable phy isolation */ + samsung_usbphy_set_isolation(sphy, true); + + spin_unlock_irqrestore(&sphy->lock, flags); + + clk_disable_unprepare(sphy->clk); +} + +static int samsung_usb3phy_probe(struct platform_device *pdev) +{ + struct samsung_usbphy *sphy; + struct samsung_usbphy_data *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct resource *phy_mem; + void __iomem *phy_base; + struct clk *clk; + int ret; + + phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy_base = devm_ioremap_resource(dev, phy_mem); + if (IS_ERR(phy_base)) + return PTR_ERR(phy_base); + + sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); + if (!sphy) + return -ENOMEM; + + clk = devm_clk_get(dev, "usbdrd30"); + if (IS_ERR(clk)) { + dev_err(dev, "Failed to get device clock\n"); + return PTR_ERR(clk); + } + + sphy->dev = dev; + + if (dev->of_node) { + ret = samsung_usbphy_parse_dt(sphy); + if (ret < 0) + return ret; + } else { + if (!pdata) { + dev_err(dev, "no platform data specified\n"); + return -EINVAL; + } + } + + sphy->plat = pdata; + sphy->regs = phy_base; + sphy->clk = clk; + sphy->phy.dev = sphy->dev; + sphy->phy.label = "samsung-usb3phy"; + sphy->phy.init = samsung_usb3phy_init; + sphy->phy.shutdown = samsung_usb3phy_shutdown; + sphy->drv_data = samsung_usbphy_get_driver_data(pdev); + sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + + spin_lock_init(&sphy->lock); + + platform_set_drvdata(pdev, sphy); + + return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB3); +} + +static int samsung_usb3phy_remove(struct platform_device *pdev) +{ + struct samsung_usbphy *sphy = platform_get_drvdata(pdev); + + usb_remove_phy(&sphy->phy); + + if (sphy->pmuregs) + iounmap(sphy->pmuregs); + if (sphy->sysreg) + iounmap(sphy->sysreg); + + return 0; +} + +static struct samsung_usbphy_drvdata usb3phy_exynos5 = { + .cpu_type = TYPE_EXYNOS5250, + .devphy_en_mask = EXYNOS_USBPHY_ENABLE, +}; + +#ifdef CONFIG_OF +static const struct of_device_id samsung_usbphy_dt_match[] = { + { + .compatible = "samsung,exynos5250-usb3phy", + .data = &usb3phy_exynos5 + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match); +#endif + +static struct platform_device_id samsung_usbphy_driver_ids[] = { + { + .name = "exynos5250-usb3phy", + .driver_data = (unsigned long)&usb3phy_exynos5, + }, + {}, +}; + +MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids); + +static struct platform_driver samsung_usb3phy_driver = { + .probe = samsung_usb3phy_probe, + .remove = samsung_usb3phy_remove, + .id_table = samsung_usbphy_driver_ids, + .driver = { + .name = "samsung-usb3phy", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_usbphy_dt_match), + }, +}; + +module_platform_driver(samsung_usb3phy_driver); + +MODULE_DESCRIPTION("Samsung USB 3.0 phy controller"); +MODULE_AUTHOR("Vivek Gautam "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-usb3phy"); diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c new file mode 100644 index 00000000..17d81129 --- /dev/null +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -0,0 +1,803 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Erik Gilling + * Benoit Goby + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEGRA_USB_BASE 0xC5000000 +#define TEGRA_USB_SIZE SZ_16K + +#define ULPI_VIEWPORT 0x170 + +#define USB_SUSP_CTRL 0x400 +#define USB_WAKE_ON_CNNT_EN_DEV (1 << 3) +#define USB_WAKE_ON_DISCON_EN_DEV (1 << 4) +#define USB_SUSP_CLR (1 << 5) +#define USB_PHY_CLK_VALID (1 << 7) +#define UTMIP_RESET (1 << 11) +#define UHSIC_RESET (1 << 11) +#define UTMIP_PHY_ENABLE (1 << 12) +#define ULPI_PHY_ENABLE (1 << 13) +#define USB_SUSP_SET (1 << 14) +#define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16) + +#define USB1_LEGACY_CTRL 0x410 +#define USB1_NO_LEGACY_MODE (1 << 0) +#define USB1_VBUS_SENSE_CTL_MASK (3 << 1) +#define USB1_VBUS_SENSE_CTL_VBUS_WAKEUP (0 << 1) +#define USB1_VBUS_SENSE_CTL_AB_SESS_VLD_OR_VBUS_WAKEUP \ + (1 << 1) +#define USB1_VBUS_SENSE_CTL_AB_SESS_VLD (2 << 1) +#define USB1_VBUS_SENSE_CTL_A_SESS_VLD (3 << 1) + +#define ULPI_TIMING_CTRL_0 0x424 +#define ULPI_OUTPUT_PINMUX_BYP (1 << 10) +#define ULPI_CLKOUT_PINMUX_BYP (1 << 11) + +#define ULPI_TIMING_CTRL_1 0x428 +#define ULPI_DATA_TRIMMER_LOAD (1 << 0) +#define ULPI_DATA_TRIMMER_SEL(x) (((x) & 0x7) << 1) +#define ULPI_STPDIRNXT_TRIMMER_LOAD (1 << 16) +#define ULPI_STPDIRNXT_TRIMMER_SEL(x) (((x) & 0x7) << 17) +#define ULPI_DIR_TRIMMER_LOAD (1 << 24) +#define ULPI_DIR_TRIMMER_SEL(x) (((x) & 0x7) << 25) + +#define UTMIP_PLL_CFG1 0x804 +#define UTMIP_XTAL_FREQ_COUNT(x) (((x) & 0xfff) << 0) +#define UTMIP_PLLU_ENABLE_DLY_COUNT(x) (((x) & 0x1f) << 27) + +#define UTMIP_XCVR_CFG0 0x808 +#define UTMIP_XCVR_SETUP(x) (((x) & 0xf) << 0) +#define UTMIP_XCVR_LSRSLEW(x) (((x) & 0x3) << 8) +#define UTMIP_XCVR_LSFSLEW(x) (((x) & 0x3) << 10) +#define UTMIP_FORCE_PD_POWERDOWN (1 << 14) +#define UTMIP_FORCE_PD2_POWERDOWN (1 << 16) +#define UTMIP_FORCE_PDZI_POWERDOWN (1 << 18) +#define UTMIP_XCVR_HSSLEW_MSB(x) (((x) & 0x7f) << 25) + +#define UTMIP_BIAS_CFG0 0x80c +#define UTMIP_OTGPD (1 << 11) +#define UTMIP_BIASPD (1 << 10) + +#define UTMIP_HSRX_CFG0 0x810 +#define UTMIP_ELASTIC_LIMIT(x) (((x) & 0x1f) << 10) +#define UTMIP_IDLE_WAIT(x) (((x) & 0x1f) << 15) + +#define UTMIP_HSRX_CFG1 0x814 +#define UTMIP_HS_SYNC_START_DLY(x) (((x) & 0x1f) << 1) + +#define UTMIP_TX_CFG0 0x820 +#define UTMIP_FS_PREABMLE_J (1 << 19) +#define UTMIP_HS_DISCON_DISABLE (1 << 8) + +#define UTMIP_MISC_CFG0 0x824 +#define UTMIP_DPDM_OBSERVE (1 << 26) +#define UTMIP_DPDM_OBSERVE_SEL(x) (((x) & 0xf) << 27) +#define UTMIP_DPDM_OBSERVE_SEL_FS_J UTMIP_DPDM_OBSERVE_SEL(0xf) +#define UTMIP_DPDM_OBSERVE_SEL_FS_K UTMIP_DPDM_OBSERVE_SEL(0xe) +#define UTMIP_DPDM_OBSERVE_SEL_FS_SE1 UTMIP_DPDM_OBSERVE_SEL(0xd) +#define UTMIP_DPDM_OBSERVE_SEL_FS_SE0 UTMIP_DPDM_OBSERVE_SEL(0xc) +#define UTMIP_SUSPEND_EXIT_ON_EDGE (1 << 22) + +#define UTMIP_MISC_CFG1 0x828 +#define UTMIP_PLL_ACTIVE_DLY_COUNT(x) (((x) & 0x1f) << 18) +#define UTMIP_PLLU_STABLE_COUNT(x) (((x) & 0xfff) << 6) + +#define UTMIP_DEBOUNCE_CFG0 0x82c +#define UTMIP_BIAS_DEBOUNCE_A(x) (((x) & 0xffff) << 0) + +#define UTMIP_BAT_CHRG_CFG0 0x830 +#define UTMIP_PD_CHRG (1 << 0) + +#define UTMIP_SPARE_CFG0 0x834 +#define FUSE_SETUP_SEL (1 << 3) + +#define UTMIP_XCVR_CFG1 0x838 +#define UTMIP_FORCE_PDDISC_POWERDOWN (1 << 0) +#define UTMIP_FORCE_PDCHRP_POWERDOWN (1 << 2) +#define UTMIP_FORCE_PDDR_POWERDOWN (1 << 4) +#define UTMIP_XCVR_TERM_RANGE_ADJ(x) (((x) & 0xf) << 18) + +#define UTMIP_BIAS_CFG1 0x83c +#define UTMIP_BIAS_PDTRK_COUNT(x) (((x) & 0x1f) << 3) + +static DEFINE_SPINLOCK(utmip_pad_lock); +static int utmip_pad_count; + +struct tegra_xtal_freq { + int freq; + u8 enable_delay; + u8 stable_count; + u8 active_delay; + u8 xtal_freq_count; + u16 debounce; +}; + +static const struct tegra_xtal_freq tegra_freq_table[] = { + { + .freq = 12000000, + .enable_delay = 0x02, + .stable_count = 0x2F, + .active_delay = 0x04, + .xtal_freq_count = 0x76, + .debounce = 0x7530, + }, + { + .freq = 13000000, + .enable_delay = 0x02, + .stable_count = 0x33, + .active_delay = 0x05, + .xtal_freq_count = 0x7F, + .debounce = 0x7EF4, + }, + { + .freq = 19200000, + .enable_delay = 0x03, + .stable_count = 0x4B, + .active_delay = 0x06, + .xtal_freq_count = 0xBB, + .debounce = 0xBB80, + }, + { + .freq = 26000000, + .enable_delay = 0x04, + .stable_count = 0x66, + .active_delay = 0x09, + .xtal_freq_count = 0xFE, + .debounce = 0xFDE8, + }, +}; + +static struct tegra_utmip_config utmip_default[] = { + [0] = { + .hssync_start_delay = 9, + .idle_wait_delay = 17, + .elastic_limit = 16, + .term_range_adj = 6, + .xcvr_setup = 9, + .xcvr_lsfslew = 1, + .xcvr_lsrslew = 1, + }, + [2] = { + .hssync_start_delay = 9, + .idle_wait_delay = 17, + .elastic_limit = 16, + .term_range_adj = 6, + .xcvr_setup = 9, + .xcvr_lsfslew = 2, + .xcvr_lsrslew = 2, + }, +}; + +static int utmip_pad_open(struct tegra_usb_phy *phy) +{ + phy->pad_clk = clk_get_sys("utmip-pad", NULL); + if (IS_ERR(phy->pad_clk)) { + pr_err("%s: can't get utmip pad clock\n", __func__); + return PTR_ERR(phy->pad_clk); + } + + if (phy->is_legacy_phy) { + phy->pad_regs = phy->regs; + } else { + phy->pad_regs = ioremap(TEGRA_USB_BASE, TEGRA_USB_SIZE); + if (!phy->pad_regs) { + pr_err("%s: can't remap usb registers\n", __func__); + clk_put(phy->pad_clk); + return -ENOMEM; + } + } + return 0; +} + +static void utmip_pad_close(struct tegra_usb_phy *phy) +{ + if (!phy->is_legacy_phy) + iounmap(phy->pad_regs); + clk_put(phy->pad_clk); +} + +static void utmip_pad_power_on(struct tegra_usb_phy *phy) +{ + unsigned long val, flags; + void __iomem *base = phy->pad_regs; + + clk_prepare_enable(phy->pad_clk); + + spin_lock_irqsave(&utmip_pad_lock, flags); + + if (utmip_pad_count++ == 0) { + val = readl(base + UTMIP_BIAS_CFG0); + val &= ~(UTMIP_OTGPD | UTMIP_BIASPD); + writel(val, base + UTMIP_BIAS_CFG0); + } + + spin_unlock_irqrestore(&utmip_pad_lock, flags); + + clk_disable_unprepare(phy->pad_clk); +} + +static int utmip_pad_power_off(struct tegra_usb_phy *phy) +{ + unsigned long val, flags; + void __iomem *base = phy->pad_regs; + + if (!utmip_pad_count) { + pr_err("%s: utmip pad already powered off\n", __func__); + return -EINVAL; + } + + clk_prepare_enable(phy->pad_clk); + + spin_lock_irqsave(&utmip_pad_lock, flags); + + if (--utmip_pad_count == 0) { + val = readl(base + UTMIP_BIAS_CFG0); + val |= UTMIP_OTGPD | UTMIP_BIASPD; + writel(val, base + UTMIP_BIAS_CFG0); + } + + spin_unlock_irqrestore(&utmip_pad_lock, flags); + + clk_disable_unprepare(phy->pad_clk); + + return 0; +} + +static int utmi_wait_register(void __iomem *reg, u32 mask, u32 result) +{ + unsigned long timeout = 2000; + do { + if ((readl(reg) & mask) == result) + return 0; + udelay(1); + timeout--; + } while (timeout); + return -1; +} + +static void utmi_phy_clk_disable(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + + if (phy->is_legacy_phy) { + val = readl(base + USB_SUSP_CTRL); + val |= USB_SUSP_SET; + writel(val, base + USB_SUSP_CTRL); + + udelay(10); + + val = readl(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_SET; + writel(val, base + USB_SUSP_CTRL); + } else + phy->set_phcd(&phy->u_phy, true); + + if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) < 0) + pr_err("%s: timeout waiting for phy to stabilize\n", __func__); +} + +static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + + if (phy->is_legacy_phy) { + val = readl(base + USB_SUSP_CTRL); + val |= USB_SUSP_CLR; + writel(val, base + USB_SUSP_CTRL); + + udelay(10); + + val = readl(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_CLR; + writel(val, base + USB_SUSP_CTRL); + } else + phy->set_phcd(&phy->u_phy, false); + + if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, + USB_PHY_CLK_VALID)) + pr_err("%s: timeout waiting for phy to stabilize\n", __func__); +} + +static int utmi_phy_power_on(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + struct tegra_utmip_config *config = phy->config; + + val = readl(base + USB_SUSP_CTRL); + val |= UTMIP_RESET; + writel(val, base + USB_SUSP_CTRL); + + if (phy->is_legacy_phy) { + val = readl(base + USB1_LEGACY_CTRL); + val |= USB1_NO_LEGACY_MODE; + writel(val, base + USB1_LEGACY_CTRL); + } + + val = readl(base + UTMIP_TX_CFG0); + val &= ~UTMIP_FS_PREABMLE_J; + writel(val, base + UTMIP_TX_CFG0); + + val = readl(base + UTMIP_HSRX_CFG0); + val &= ~(UTMIP_IDLE_WAIT(~0) | UTMIP_ELASTIC_LIMIT(~0)); + val |= UTMIP_IDLE_WAIT(config->idle_wait_delay); + val |= UTMIP_ELASTIC_LIMIT(config->elastic_limit); + writel(val, base + UTMIP_HSRX_CFG0); + + val = readl(base + UTMIP_HSRX_CFG1); + val &= ~UTMIP_HS_SYNC_START_DLY(~0); + val |= UTMIP_HS_SYNC_START_DLY(config->hssync_start_delay); + writel(val, base + UTMIP_HSRX_CFG1); + + val = readl(base + UTMIP_DEBOUNCE_CFG0); + val &= ~UTMIP_BIAS_DEBOUNCE_A(~0); + val |= UTMIP_BIAS_DEBOUNCE_A(phy->freq->debounce); + writel(val, base + UTMIP_DEBOUNCE_CFG0); + + val = readl(base + UTMIP_MISC_CFG0); + val &= ~UTMIP_SUSPEND_EXIT_ON_EDGE; + writel(val, base + UTMIP_MISC_CFG0); + + val = readl(base + UTMIP_MISC_CFG1); + val &= ~(UTMIP_PLL_ACTIVE_DLY_COUNT(~0) | UTMIP_PLLU_STABLE_COUNT(~0)); + val |= UTMIP_PLL_ACTIVE_DLY_COUNT(phy->freq->active_delay) | + UTMIP_PLLU_STABLE_COUNT(phy->freq->stable_count); + writel(val, base + UTMIP_MISC_CFG1); + + val = readl(base + UTMIP_PLL_CFG1); + val &= ~(UTMIP_XTAL_FREQ_COUNT(~0) | UTMIP_PLLU_ENABLE_DLY_COUNT(~0)); + val |= UTMIP_XTAL_FREQ_COUNT(phy->freq->xtal_freq_count) | + UTMIP_PLLU_ENABLE_DLY_COUNT(phy->freq->enable_delay); + writel(val, base + UTMIP_PLL_CFG1); + + if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE) { + val = readl(base + USB_SUSP_CTRL); + val &= ~(USB_WAKE_ON_CNNT_EN_DEV | USB_WAKE_ON_DISCON_EN_DEV); + writel(val, base + USB_SUSP_CTRL); + } + + utmip_pad_power_on(phy); + + val = readl(base + UTMIP_XCVR_CFG0); + val &= ~(UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN | + UTMIP_FORCE_PDZI_POWERDOWN | UTMIP_XCVR_SETUP(~0) | + UTMIP_XCVR_LSFSLEW(~0) | UTMIP_XCVR_LSRSLEW(~0) | + UTMIP_XCVR_HSSLEW_MSB(~0)); + val |= UTMIP_XCVR_SETUP(config->xcvr_setup); + val |= UTMIP_XCVR_LSFSLEW(config->xcvr_lsfslew); + val |= UTMIP_XCVR_LSRSLEW(config->xcvr_lsrslew); + writel(val, base + UTMIP_XCVR_CFG0); + + val = readl(base + UTMIP_XCVR_CFG1); + val &= ~(UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN | + UTMIP_FORCE_PDDR_POWERDOWN | UTMIP_XCVR_TERM_RANGE_ADJ(~0)); + val |= UTMIP_XCVR_TERM_RANGE_ADJ(config->term_range_adj); + writel(val, base + UTMIP_XCVR_CFG1); + + val = readl(base + UTMIP_BAT_CHRG_CFG0); + val &= ~UTMIP_PD_CHRG; + writel(val, base + UTMIP_BAT_CHRG_CFG0); + + val = readl(base + UTMIP_BIAS_CFG1); + val &= ~UTMIP_BIAS_PDTRK_COUNT(~0); + val |= UTMIP_BIAS_PDTRK_COUNT(0x5); + writel(val, base + UTMIP_BIAS_CFG1); + + if (phy->is_legacy_phy) { + val = readl(base + UTMIP_SPARE_CFG0); + if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE) + val &= ~FUSE_SETUP_SEL; + else + val |= FUSE_SETUP_SEL; + writel(val, base + UTMIP_SPARE_CFG0); + } else { + val = readl(base + USB_SUSP_CTRL); + val |= UTMIP_PHY_ENABLE; + writel(val, base + USB_SUSP_CTRL); + } + + val = readl(base + USB_SUSP_CTRL); + val &= ~UTMIP_RESET; + writel(val, base + USB_SUSP_CTRL); + + if (phy->is_legacy_phy) { + val = readl(base + USB1_LEGACY_CTRL); + val &= ~USB1_VBUS_SENSE_CTL_MASK; + val |= USB1_VBUS_SENSE_CTL_A_SESS_VLD; + writel(val, base + USB1_LEGACY_CTRL); + + val = readl(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_SET; + writel(val, base + USB_SUSP_CTRL); + } + + utmi_phy_clk_enable(phy); + + if (!phy->is_legacy_phy) + phy->set_pts(&phy->u_phy, 0); + + return 0; +} + +static int utmi_phy_power_off(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + + utmi_phy_clk_disable(phy); + + if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE) { + val = readl(base + USB_SUSP_CTRL); + val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0); + val |= USB_WAKE_ON_CNNT_EN_DEV | USB_WAKEUP_DEBOUNCE_COUNT(5); + writel(val, base + USB_SUSP_CTRL); + } + + val = readl(base + USB_SUSP_CTRL); + val |= UTMIP_RESET; + writel(val, base + USB_SUSP_CTRL); + + val = readl(base + UTMIP_BAT_CHRG_CFG0); + val |= UTMIP_PD_CHRG; + writel(val, base + UTMIP_BAT_CHRG_CFG0); + + val = readl(base + UTMIP_XCVR_CFG0); + val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN | + UTMIP_FORCE_PDZI_POWERDOWN; + writel(val, base + UTMIP_XCVR_CFG0); + + val = readl(base + UTMIP_XCVR_CFG1); + val |= UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN | + UTMIP_FORCE_PDDR_POWERDOWN; + writel(val, base + UTMIP_XCVR_CFG1); + + return utmip_pad_power_off(phy); +} + +static void utmi_phy_preresume(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + + val = readl(base + UTMIP_TX_CFG0); + val |= UTMIP_HS_DISCON_DISABLE; + writel(val, base + UTMIP_TX_CFG0); +} + +static void utmi_phy_postresume(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + + val = readl(base + UTMIP_TX_CFG0); + val &= ~UTMIP_HS_DISCON_DISABLE; + writel(val, base + UTMIP_TX_CFG0); +} + +static void utmi_phy_restore_start(struct tegra_usb_phy *phy, + enum tegra_usb_phy_port_speed port_speed) +{ + unsigned long val; + void __iomem *base = phy->regs; + + val = readl(base + UTMIP_MISC_CFG0); + val &= ~UTMIP_DPDM_OBSERVE_SEL(~0); + if (port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW) + val |= UTMIP_DPDM_OBSERVE_SEL_FS_K; + else + val |= UTMIP_DPDM_OBSERVE_SEL_FS_J; + writel(val, base + UTMIP_MISC_CFG0); + udelay(1); + + val = readl(base + UTMIP_MISC_CFG0); + val |= UTMIP_DPDM_OBSERVE; + writel(val, base + UTMIP_MISC_CFG0); + udelay(10); +} + +static void utmi_phy_restore_end(struct tegra_usb_phy *phy) +{ + unsigned long val; + void __iomem *base = phy->regs; + + val = readl(base + UTMIP_MISC_CFG0); + val &= ~UTMIP_DPDM_OBSERVE; + writel(val, base + UTMIP_MISC_CFG0); + udelay(10); +} + +static int ulpi_phy_power_on(struct tegra_usb_phy *phy) +{ + int ret; + unsigned long val; + void __iomem *base = phy->regs; + struct tegra_ulpi_config *config = phy->config; + + gpio_direction_output(config->reset_gpio, 0); + msleep(5); + gpio_direction_output(config->reset_gpio, 1); + + clk_prepare_enable(phy->clk); + msleep(1); + + val = readl(base + USB_SUSP_CTRL); + val |= UHSIC_RESET; + writel(val, base + USB_SUSP_CTRL); + + val = readl(base + ULPI_TIMING_CTRL_0); + val |= ULPI_OUTPUT_PINMUX_BYP | ULPI_CLKOUT_PINMUX_BYP; + writel(val, base + ULPI_TIMING_CTRL_0); + + val = readl(base + USB_SUSP_CTRL); + val |= ULPI_PHY_ENABLE; + writel(val, base + USB_SUSP_CTRL); + + val = 0; + writel(val, base + ULPI_TIMING_CTRL_1); + + val |= ULPI_DATA_TRIMMER_SEL(4); + val |= ULPI_STPDIRNXT_TRIMMER_SEL(4); + val |= ULPI_DIR_TRIMMER_SEL(4); + writel(val, base + ULPI_TIMING_CTRL_1); + udelay(10); + + val |= ULPI_DATA_TRIMMER_LOAD; + val |= ULPI_STPDIRNXT_TRIMMER_LOAD; + val |= ULPI_DIR_TRIMMER_LOAD; + writel(val, base + ULPI_TIMING_CTRL_1); + + /* Fix VbusInvalid due to floating VBUS */ + ret = usb_phy_io_write(phy->ulpi, 0x40, 0x08); + if (ret) { + pr_err("%s: ulpi write failed\n", __func__); + return ret; + } + + ret = usb_phy_io_write(phy->ulpi, 0x80, 0x0B); + if (ret) { + pr_err("%s: ulpi write failed\n", __func__); + return ret; + } + + val = readl(base + USB_SUSP_CTRL); + val |= USB_SUSP_CLR; + writel(val, base + USB_SUSP_CTRL); + udelay(100); + + val = readl(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_CLR; + writel(val, base + USB_SUSP_CTRL); + + return 0; +} + +static int ulpi_phy_power_off(struct tegra_usb_phy *phy) +{ + struct tegra_ulpi_config *config = phy->config; + + clk_disable(phy->clk); + return gpio_direction_output(config->reset_gpio, 0); +} + +static int tegra_phy_init(struct usb_phy *x) +{ + struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + struct tegra_ulpi_config *ulpi_config; + int err; + + if (phy->is_ulpi_phy) { + ulpi_config = phy->config; + phy->clk = clk_get_sys(NULL, ulpi_config->clk); + if (IS_ERR(phy->clk)) { + pr_err("%s: can't get ulpi clock\n", __func__); + err = -ENXIO; + goto err1; + } + if (!gpio_is_valid(ulpi_config->reset_gpio)) + ulpi_config->reset_gpio = + of_get_named_gpio(phy->dev->of_node, + "nvidia,phy-reset-gpio", 0); + if (!gpio_is_valid(ulpi_config->reset_gpio)) { + pr_err("%s: invalid reset gpio: %d\n", __func__, + ulpi_config->reset_gpio); + err = -EINVAL; + goto err1; + } + gpio_request(ulpi_config->reset_gpio, "ulpi_phy_reset_b"); + gpio_direction_output(ulpi_config->reset_gpio, 0); + phy->ulpi = otg_ulpi_create(&ulpi_viewport_access_ops, 0); + phy->ulpi->io_priv = phy->regs + ULPI_VIEWPORT; + } else { + err = utmip_pad_open(phy); + if (err < 0) + goto err1; + } + return 0; +err1: + clk_disable_unprepare(phy->pll_u); + clk_put(phy->pll_u); + return err; +} + +static void tegra_usb_phy_close(struct usb_phy *x) +{ + struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + + if (phy->is_ulpi_phy) + clk_put(phy->clk); + else + utmip_pad_close(phy); + clk_disable_unprepare(phy->pll_u); + clk_put(phy->pll_u); + kfree(phy); +} + +static int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) +{ + if (phy->is_ulpi_phy) + return ulpi_phy_power_on(phy); + else + return utmi_phy_power_on(phy); +} + +static int tegra_usb_phy_power_off(struct tegra_usb_phy *phy) +{ + if (phy->is_ulpi_phy) + return ulpi_phy_power_off(phy); + else + return utmi_phy_power_off(phy); +} + +static int tegra_usb_phy_suspend(struct usb_phy *x, int suspend) +{ + struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + if (suspend) + return tegra_usb_phy_power_off(phy); + else + return tegra_usb_phy_power_on(phy); +} + +struct tegra_usb_phy *tegra_usb_phy_open(struct device *dev, int instance, + void __iomem *regs, void *config, enum tegra_usb_phy_mode phy_mode, + void (*set_pts)(struct usb_phy *x, u8 pts_val), + void (*set_phcd)(struct usb_phy *x, bool enable)) + +{ + struct tegra_usb_phy *phy; + unsigned long parent_rate; + int i; + int err; + struct device_node *np = dev->of_node; + + phy = kzalloc(sizeof(struct tegra_usb_phy), GFP_KERNEL); + if (!phy) + return ERR_PTR(-ENOMEM); + + phy->instance = instance; + phy->regs = regs; + phy->config = config; + phy->mode = phy_mode; + phy->dev = dev; + phy->is_legacy_phy = + of_property_read_bool(np, "nvidia,has-legacy-mode"); + phy->set_pts = set_pts; + phy->set_phcd = set_phcd; + err = of_property_match_string(np, "phy_type", "ulpi"); + if (err < 0) + phy->is_ulpi_phy = false; + else + phy->is_ulpi_phy = true; + + if (!phy->config) { + if (phy->is_ulpi_phy) { + pr_err("%s: ulpi phy configuration missing", __func__); + err = -EINVAL; + goto err0; + } else { + phy->config = &utmip_default[instance]; + } + } + + phy->pll_u = clk_get_sys(NULL, "pll_u"); + if (IS_ERR(phy->pll_u)) { + pr_err("Can't get pll_u clock\n"); + err = PTR_ERR(phy->pll_u); + goto err0; + } + clk_prepare_enable(phy->pll_u); + + parent_rate = clk_get_rate(clk_get_parent(phy->pll_u)); + for (i = 0; i < ARRAY_SIZE(tegra_freq_table); i++) { + if (tegra_freq_table[i].freq == parent_rate) { + phy->freq = &tegra_freq_table[i]; + break; + } + } + if (!phy->freq) { + pr_err("invalid pll_u parent rate %ld\n", parent_rate); + err = -EINVAL; + goto err1; + } + + phy->u_phy.init = tegra_phy_init; + phy->u_phy.shutdown = tegra_usb_phy_close; + phy->u_phy.set_suspend = tegra_usb_phy_suspend; + + return phy; + +err1: + clk_disable_unprepare(phy->pll_u); + clk_put(phy->pll_u); +err0: + kfree(phy); + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(tegra_usb_phy_open); + +void tegra_usb_phy_preresume(struct usb_phy *x) +{ + struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + + if (!phy->is_ulpi_phy) + utmi_phy_preresume(phy); +} +EXPORT_SYMBOL_GPL(tegra_usb_phy_preresume); + +void tegra_usb_phy_postresume(struct usb_phy *x) +{ + struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + + if (!phy->is_ulpi_phy) + utmi_phy_postresume(phy); +} +EXPORT_SYMBOL_GPL(tegra_usb_phy_postresume); + +void tegra_ehci_phy_restore_start(struct usb_phy *x, + enum tegra_usb_phy_port_speed port_speed) +{ + struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + + if (!phy->is_ulpi_phy) + utmi_phy_restore_start(phy, port_speed); +} +EXPORT_SYMBOL_GPL(tegra_ehci_phy_restore_start); + +void tegra_ehci_phy_restore_end(struct usb_phy *x) +{ + struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + + if (!phy->is_ulpi_phy) + utmi_phy_restore_end(phy); +} +EXPORT_SYMBOL_GPL(tegra_ehci_phy_restore_end); + diff --git a/drivers/usb/phy/phy-twl4030-usb.c b/drivers/usb/phy/phy-twl4030-usb.c new file mode 100644 index 00000000..8f78d2d4 --- /dev/null +++ b/drivers/usb/phy/phy-twl4030-usb.c @@ -0,0 +1,794 @@ +/* + * twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004-2007 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Contact: Felipe Balbi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Current status: + * - HS USB ULPI mode works. + * - 3-pin mode support may be added in future. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register defines */ + +#define MCPC_CTRL 0x30 +#define MCPC_CTRL_RTSOL (1 << 7) +#define MCPC_CTRL_EXTSWR (1 << 6) +#define MCPC_CTRL_EXTSWC (1 << 5) +#define MCPC_CTRL_VOICESW (1 << 4) +#define MCPC_CTRL_OUT64K (1 << 3) +#define MCPC_CTRL_RTSCTSSW (1 << 2) +#define MCPC_CTRL_HS_UART (1 << 0) + +#define MCPC_IO_CTRL 0x33 +#define MCPC_IO_CTRL_MICBIASEN (1 << 5) +#define MCPC_IO_CTRL_CTS_NPU (1 << 4) +#define MCPC_IO_CTRL_RXD_PU (1 << 3) +#define MCPC_IO_CTRL_TXDTYP (1 << 2) +#define MCPC_IO_CTRL_CTSTYP (1 << 1) +#define MCPC_IO_CTRL_RTSTYP (1 << 0) + +#define MCPC_CTRL2 0x36 +#define MCPC_CTRL2_MCPC_CK_EN (1 << 0) + +#define OTHER_FUNC_CTRL 0x80 +#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4) +#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2) + +#define OTHER_IFC_CTRL 0x83 +#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6) +#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5) +#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4) +#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3) +#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2) +#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0) + +#define OTHER_INT_EN_RISE 0x86 +#define OTHER_INT_EN_FALL 0x89 +#define OTHER_INT_STS 0x8C +#define OTHER_INT_LATCH 0x8D +#define OTHER_INT_VB_SESS_VLD (1 << 7) +#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */ +#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */ +#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */ +#define OTHER_INT_MANU (1 << 1) +#define OTHER_INT_ABNORMAL_STRESS (1 << 0) + +#define ID_STATUS 0x96 +#define ID_RES_FLOAT (1 << 4) +#define ID_RES_440K (1 << 3) +#define ID_RES_200K (1 << 2) +#define ID_RES_102K (1 << 1) +#define ID_RES_GND (1 << 0) + +#define POWER_CTRL 0xAC +#define POWER_CTRL_OTG_ENAB (1 << 5) + +#define OTHER_IFC_CTRL2 0xAF +#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4) +#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3) +#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */ +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0) + +#define REG_CTRL_EN 0xB2 +#define REG_CTRL_ERROR 0xB5 +#define ULPI_I2C_CONFLICT_INTEN (1 << 0) + +#define OTHER_FUNC_CTRL2 0xB8 +#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0) + +/* following registers do not have separate _clr and _set registers */ +#define VBUS_DEBOUNCE 0xC0 +#define ID_DEBOUNCE 0xC1 +#define VBAT_TIMER 0xD3 +#define PHY_PWR_CTRL 0xFD +#define PHY_PWR_PHYPWD (1 << 0) +#define PHY_CLK_CTRL 0xFE +#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2) +#define PHY_CLK_CTRL_CLK32K_EN (1 << 1) +#define REQ_PHY_DPLL_CLK (1 << 0) +#define PHY_CLK_CTRL_STS 0xFF +#define PHY_DPLL_CLK (1 << 0) + +/* In module TWL_MODULE_PM_MASTER */ +#define STS_HW_CONDITIONS 0x0F + +/* In module TWL_MODULE_PM_RECEIVER */ +#define VUSB_DEDICATED1 0x7D +#define VUSB_DEDICATED2 0x7E +#define VUSB1V5_DEV_GRP 0x71 +#define VUSB1V5_TYPE 0x72 +#define VUSB1V5_REMAP 0x73 +#define VUSB1V8_DEV_GRP 0x74 +#define VUSB1V8_TYPE 0x75 +#define VUSB1V8_REMAP 0x76 +#define VUSB3V1_DEV_GRP 0x77 +#define VUSB3V1_TYPE 0x78 +#define VUSB3V1_REMAP 0x79 + +/* In module TWL4030_MODULE_INTBR */ +#define PMBR1 0x0D +#define GPIO_USB_4PIN_ULPI_2430C (3 << 0) + +struct twl4030_usb { + struct usb_phy phy; + struct device *dev; + + /* TWL4030 internal USB regulator supplies */ + struct regulator *usb1v5; + struct regulator *usb1v8; + struct regulator *usb3v1; + + /* for vbus reporting with irqs disabled */ + spinlock_t lock; + + /* pin configuration */ + enum twl4030_usb_mode usb_mode; + + int irq; + enum omap_musb_vbus_id_status linkstat; + bool vbus_supplied; + u8 asleep; + bool irq_enabled; + + struct delayed_work id_workaround_work; +}; + +/* internal define on top of container_of */ +#define phy_to_twl(x) container_of((x), struct twl4030_usb, phy) + +/*-------------------------------------------------------------------------*/ + +static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl, + u8 module, u8 data, u8 address) +{ + u8 check; + + if ((twl_i2c_write_u8(module, data, address) >= 0) && + (twl_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", + 1, module, address, check, data); + + /* Failed once: Try again */ + if ((twl_i2c_write_u8(module, data, address) >= 0) && + (twl_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", + 2, module, address, check, data); + + /* Failed again: Return error */ + return -EBUSY; +} + +#define twl4030_usb_write_verify(twl, address, data) \ + twl4030_i2c_write_u8_verify(twl, TWL_MODULE_USB, (data), (address)) + +static inline int twl4030_usb_write(struct twl4030_usb *twl, + u8 address, u8 data) +{ + int ret = 0; + + ret = twl_i2c_write_u8(TWL_MODULE_USB, data, address); + if (ret < 0) + dev_dbg(twl->dev, + "TWL4030:USB:Write[0x%x] Error %d\n", address, ret); + return ret; +} + +static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address) +{ + u8 data; + int ret = 0; + + ret = twl_i2c_read_u8(module, &data, address); + if (ret >= 0) + ret = data; + else + dev_dbg(twl->dev, + "TWL4030:readb[0x%x,0x%x] Error %d\n", + module, address, ret); + + return ret; +} + +static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address) +{ + return twl4030_readb(twl, TWL_MODULE_USB, address); +} + +/*-------------------------------------------------------------------------*/ + +static inline int +twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(twl, ULPI_SET(reg), bits); +} + +static inline int +twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(twl, ULPI_CLR(reg), bits); +} + +/*-------------------------------------------------------------------------*/ + +static bool twl4030_is_driving_vbus(struct twl4030_usb *twl) +{ + int ret; + + ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS); + if (ret < 0 || !(ret & PHY_DPLL_CLK)) + /* + * if clocks are off, registers are not updated, + * but we can assume we don't drive VBUS in this case + */ + return false; + + ret = twl4030_usb_read(twl, ULPI_OTG_CTRL); + if (ret < 0) + return false; + + return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false; +} + +static enum omap_musb_vbus_id_status + twl4030_usb_linkstat(struct twl4030_usb *twl) +{ + int status; + enum omap_musb_vbus_id_status linkstat = OMAP_MUSB_UNKNOWN; + + twl->vbus_supplied = false; + + /* + * For ID/VBUS sensing, see manual section 15.4.8 ... + * except when using only battery backup power, two + * comparators produce VBUS_PRES and ID_PRES signals, + * which don't match docs elsewhere. But ... BIT(7) + * and BIT(2) of STS_HW_CONDITIONS, respectively, do + * seem to match up. If either is true the USB_PRES + * signal is active, the OTG module is activated, and + * its interrupt may be raised (may wake the system). + */ + status = twl4030_readb(twl, TWL_MODULE_PM_MASTER, STS_HW_CONDITIONS); + if (status < 0) + dev_err(twl->dev, "USB link status err %d\n", status); + else if (status & (BIT(7) | BIT(2))) { + if (status & BIT(7)) { + if (twl4030_is_driving_vbus(twl)) + status &= ~BIT(7); + else + twl->vbus_supplied = true; + } + + if (status & BIT(2)) + linkstat = OMAP_MUSB_ID_GROUND; + else if (status & BIT(7)) + linkstat = OMAP_MUSB_VBUS_VALID; + else + linkstat = OMAP_MUSB_VBUS_OFF; + } else { + if (twl->linkstat != OMAP_MUSB_UNKNOWN) + linkstat = OMAP_MUSB_VBUS_OFF; + } + + dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", + status, status, linkstat); + + /* REVISIT this assumes host and peripheral controllers + * are registered, and that both are active... + */ + + return linkstat; +} + +static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) +{ + twl->usb_mode = mode; + + switch (mode) { + case T2_USB_MODE_ULPI: + twl4030_usb_clear_bits(twl, ULPI_IFC_CTRL, + ULPI_IFC_CTRL_CARKITMODE); + twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + twl4030_usb_clear_bits(twl, ULPI_FUNC_CTRL, + ULPI_FUNC_CTRL_XCVRSEL_MASK | + ULPI_FUNC_CTRL_OPMODE_MASK); + break; + case -1: + /* FIXME: power on defaults */ + break; + default: + dev_err(twl->dev, "unsupported T2 transceiver mode %d\n", + mode); + break; + }; +} + +static void twl4030_i2c_access(struct twl4030_usb *twl, int on) +{ + unsigned long timeout; + int val = twl4030_usb_read(twl, PHY_CLK_CTRL); + + if (val >= 0) { + if (on) { + /* enable DPLL to access PHY registers over I2C */ + val |= REQ_PHY_DPLL_CLK; + WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, + (u8)val) < 0); + + timeout = jiffies + HZ; + while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK) + && time_before(jiffies, timeout)) + udelay(10); + if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK)) + dev_err(twl->dev, "Timeout setting T2 HSUSB " + "PHY DPLL clock\n"); + } else { + /* let ULPI control the DPLL clock */ + val &= ~REQ_PHY_DPLL_CLK; + WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, + (u8)val) < 0); + } + } +} + +static void __twl4030_phy_power(struct twl4030_usb *twl, int on) +{ + u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); + + if (on) + pwr &= ~PHY_PWR_PHYPWD; + else + pwr |= PHY_PWR_PHYPWD; + + WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); +} + +static void twl4030_phy_power(struct twl4030_usb *twl, int on) +{ + int ret; + + if (on) { + ret = regulator_enable(twl->usb3v1); + if (ret) + dev_err(twl->dev, "Failed to enable usb3v1\n"); + + ret = regulator_enable(twl->usb1v8); + if (ret) + dev_err(twl->dev, "Failed to enable usb1v8\n"); + + /* + * Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP + * in twl4030) resets the VUSB_DEDICATED2 register. This reset + * enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to + * SLEEP. We work around this by clearing the bit after usv3v1 + * is re-activated. This ensures that VUSB3V1 is really active. + */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2); + + ret = regulator_enable(twl->usb1v5); + if (ret) + dev_err(twl->dev, "Failed to enable usb1v5\n"); + + __twl4030_phy_power(twl, 1); + twl4030_usb_write(twl, PHY_CLK_CTRL, + twl4030_usb_read(twl, PHY_CLK_CTRL) | + (PHY_CLK_CTRL_CLOCKGATING_EN | + PHY_CLK_CTRL_CLK32K_EN)); + } else { + __twl4030_phy_power(twl, 0); + regulator_disable(twl->usb1v5); + regulator_disable(twl->usb1v8); + regulator_disable(twl->usb3v1); + } +} + +static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off) +{ + if (twl->asleep) + return; + + twl4030_phy_power(twl, 0); + twl->asleep = 1; + dev_dbg(twl->dev, "%s\n", __func__); +} + +static void __twl4030_phy_resume(struct twl4030_usb *twl) +{ + twl4030_phy_power(twl, 1); + twl4030_i2c_access(twl, 1); + twl4030_usb_set_mode(twl, twl->usb_mode); + if (twl->usb_mode == T2_USB_MODE_ULPI) + twl4030_i2c_access(twl, 0); +} + +static void twl4030_phy_resume(struct twl4030_usb *twl) +{ + if (!twl->asleep) + return; + __twl4030_phy_resume(twl); + twl->asleep = 0; + dev_dbg(twl->dev, "%s\n", __func__); + + /* + * XXX When VBUS gets driven after musb goes to A mode, + * ID_PRES related interrupts no longer arrive, why? + * Register itself is updated fine though, so we must poll. + */ + if (twl->linkstat == OMAP_MUSB_ID_GROUND) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); + } +} + +static int twl4030_usb_ldo_init(struct twl4030_usb *twl) +{ + /* Enable writing to power configuration registers */ + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, + TWL4030_PM_MASTER_PROTECT_KEY); + + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2, + TWL4030_PM_MASTER_PROTECT_KEY); + + /* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/ + /*twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/ + + /* input to VUSB3V1 LDO is from VBAT, not VBUS */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); + + /* Initialize 3.1V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP); + + twl->usb3v1 = devm_regulator_get(twl->dev, "usb3v1"); + if (IS_ERR(twl->usb3v1)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE); + + /* Initialize 1.5V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP); + + twl->usb1v5 = devm_regulator_get(twl->dev, "usb1v5"); + if (IS_ERR(twl->usb1v5)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE); + + /* Initialize 1.8V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP); + + twl->usb1v8 = devm_regulator_get(twl->dev, "usb1v8"); + if (IS_ERR(twl->usb1v8)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE); + + /* disable access to power configuration registers */ + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, + TWL4030_PM_MASTER_PROTECT_KEY); + + return 0; +} + +static ssize_t twl4030_usb_vbus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&twl->lock, flags); + ret = sprintf(buf, "%s\n", + twl->vbus_supplied ? "on" : "off"); + spin_unlock_irqrestore(&twl->lock, flags); + + return ret; +} +static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); + +static irqreturn_t twl4030_usb_irq(int irq, void *_twl) +{ + struct twl4030_usb *twl = _twl; + enum omap_musb_vbus_id_status status; + bool status_changed = false; + + status = twl4030_usb_linkstat(twl); + + spin_lock_irq(&twl->lock); + if (status >= 0 && status != twl->linkstat) { + twl->linkstat = status; + status_changed = true; + } + spin_unlock_irq(&twl->lock); + + if (status_changed) { + /* FIXME add a set_power() method so that B-devices can + * configure the charger appropriately. It's not always + * correct to consume VBUS power, and how much current to + * consume is a function of the USB configuration chosen + * by the host. + * + * REVISIT usb_gadget_vbus_connect(...) as needed, ditto + * its disconnect() sibling, when changing to/from the + * USB_LINK_VBUS state. musb_hdrc won't care until it + * starts to handle softconnect right. + */ + omap_musb_mailbox(status); + } + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + + return IRQ_HANDLED; +} + +static void twl4030_id_workaround_work(struct work_struct *work) +{ + struct twl4030_usb *twl = container_of(work, struct twl4030_usb, + id_workaround_work.work); + enum omap_musb_vbus_id_status status; + bool status_changed = false; + + status = twl4030_usb_linkstat(twl); + + spin_lock_irq(&twl->lock); + if (status >= 0 && status != twl->linkstat) { + twl->linkstat = status; + status_changed = true; + } + spin_unlock_irq(&twl->lock); + + if (status_changed) { + dev_dbg(twl->dev, "handle missing status change to %d\n", + status); + omap_musb_mailbox(status); + } + + /* don't schedule during sleep - irq works right then */ + if (status == OMAP_MUSB_ID_GROUND && !twl->asleep) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); + } +} + +static int twl4030_usb_phy_init(struct usb_phy *phy) +{ + struct twl4030_usb *twl = phy_to_twl(phy); + enum omap_musb_vbus_id_status status; + + /* + * Start in sleep state, we'll get called through set_suspend() + * callback when musb is runtime resumed and it's time to start. + */ + __twl4030_phy_power(twl, 0); + twl->asleep = 1; + + status = twl4030_usb_linkstat(twl); + twl->linkstat = status; + + if (status == OMAP_MUSB_ID_GROUND || status == OMAP_MUSB_VBUS_VALID) + omap_musb_mailbox(twl->linkstat); + + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + return 0; +} + +static int twl4030_set_suspend(struct usb_phy *x, int suspend) +{ + struct twl4030_usb *twl = phy_to_twl(x); + + if (suspend) + twl4030_phy_suspend(twl, 1); + else + twl4030_phy_resume(twl); + + return 0; +} + +static int twl4030_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + if (!otg) + return -ENODEV; + + otg->gadget = gadget; + if (!gadget) + otg->phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + if (!otg) + return -ENODEV; + + otg->host = host; + if (!host) + otg->phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int twl4030_usb_probe(struct platform_device *pdev) +{ + struct twl4030_usb_data *pdata = pdev->dev.platform_data; + struct twl4030_usb *twl; + int status, err; + struct usb_otg *otg; + struct device_node *np = pdev->dev.of_node; + + twl = devm_kzalloc(&pdev->dev, sizeof *twl, GFP_KERNEL); + if (!twl) + return -ENOMEM; + + if (np) + of_property_read_u32(np, "usb_mode", + (enum twl4030_usb_mode *)&twl->usb_mode); + else if (pdata) + twl->usb_mode = pdata->usb_mode; + else { + dev_err(&pdev->dev, "twl4030 initialized without pdata\n"); + return -EINVAL; + } + + otg = devm_kzalloc(&pdev->dev, sizeof *otg, GFP_KERNEL); + if (!otg) + return -ENOMEM; + + twl->dev = &pdev->dev; + twl->irq = platform_get_irq(pdev, 0); + twl->vbus_supplied = false; + twl->asleep = 1; + twl->linkstat = OMAP_MUSB_UNKNOWN; + + twl->phy.dev = twl->dev; + twl->phy.label = "twl4030"; + twl->phy.otg = otg; + twl->phy.type = USB_PHY_TYPE_USB2; + twl->phy.set_suspend = twl4030_set_suspend; + twl->phy.init = twl4030_usb_phy_init; + + otg->phy = &twl->phy; + otg->set_host = twl4030_set_host; + otg->set_peripheral = twl4030_set_peripheral; + + /* init spinlock for workqueue */ + spin_lock_init(&twl->lock); + + INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work); + + err = twl4030_usb_ldo_init(twl); + if (err) { + dev_err(&pdev->dev, "ldo init failed\n"); + return err; + } + usb_add_phy_dev(&twl->phy); + + platform_set_drvdata(pdev, twl); + if (device_create_file(&pdev->dev, &dev_attr_vbus)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + + /* Our job is to use irqs and status from the power module + * to keep the transceiver disabled when nothing's connected. + * + * FIXME we actually shouldn't start enabling it until the + * USB controller drivers have said they're ready, by calling + * set_host() and/or set_peripheral() ... OTG_capable boards + * need both handles, otherwise just one suffices. + */ + twl->irq_enabled = true; + status = devm_request_threaded_irq(twl->dev, twl->irq, NULL, + twl4030_usb_irq, IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "twl4030_usb", twl); + if (status < 0) { + dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n", + twl->irq, status); + return status; + } + + dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); + return 0; +} + +static int twl4030_usb_remove(struct platform_device *pdev) +{ + struct twl4030_usb *twl = platform_get_drvdata(pdev); + int val; + + cancel_delayed_work(&twl->id_workaround_work); + device_remove_file(twl->dev, &dev_attr_vbus); + + /* set transceiver mode to power on defaults */ + twl4030_usb_set_mode(twl, -1); + + /* autogate 60MHz ULPI clock, + * clear dpll clock request for i2c access, + * disable 32KHz + */ + val = twl4030_usb_read(twl, PHY_CLK_CTRL); + if (val >= 0) { + val |= PHY_CLK_CTRL_CLOCKGATING_EN; + val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK); + twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val); + } + + /* disable complete OTG block */ + twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + + if (!twl->asleep) + twl4030_phy_power(twl, 0); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id twl4030_usb_id_table[] = { + { .compatible = "ti,twl4030-usb" }, + {} +}; +MODULE_DEVICE_TABLE(of, twl4030_usb_id_table); +#endif + +static struct platform_driver twl4030_usb_driver = { + .probe = twl4030_usb_probe, + .remove = twl4030_usb_remove, + .driver = { + .name = "twl4030_usb", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(twl4030_usb_id_table), + }, +}; + +static int __init twl4030_usb_init(void) +{ + return platform_driver_register(&twl4030_usb_driver); +} +subsys_initcall(twl4030_usb_init); + +static void __exit twl4030_usb_exit(void) +{ + platform_driver_unregister(&twl4030_usb_driver); +} +module_exit(twl4030_usb_exit); + +MODULE_ALIAS("platform:twl4030_usb"); +MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation"); +MODULE_DESCRIPTION("TWL4030 USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c new file mode 100644 index 00000000..9de7ada9 --- /dev/null +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -0,0 +1,453 @@ +/* + * twl6030_usb - TWL6030 USB transceiver, talking to OMAP OTG driver. + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Hema HK + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* usb register definitions */ +#define USB_VENDOR_ID_LSB 0x00 +#define USB_VENDOR_ID_MSB 0x01 +#define USB_PRODUCT_ID_LSB 0x02 +#define USB_PRODUCT_ID_MSB 0x03 +#define USB_VBUS_CTRL_SET 0x04 +#define USB_VBUS_CTRL_CLR 0x05 +#define USB_ID_CTRL_SET 0x06 +#define USB_ID_CTRL_CLR 0x07 +#define USB_VBUS_INT_SRC 0x08 +#define USB_VBUS_INT_LATCH_SET 0x09 +#define USB_VBUS_INT_LATCH_CLR 0x0A +#define USB_VBUS_INT_EN_LO_SET 0x0B +#define USB_VBUS_INT_EN_LO_CLR 0x0C +#define USB_VBUS_INT_EN_HI_SET 0x0D +#define USB_VBUS_INT_EN_HI_CLR 0x0E +#define USB_ID_INT_SRC 0x0F +#define USB_ID_INT_LATCH_SET 0x10 +#define USB_ID_INT_LATCH_CLR 0x11 + +#define USB_ID_INT_EN_LO_SET 0x12 +#define USB_ID_INT_EN_LO_CLR 0x13 +#define USB_ID_INT_EN_HI_SET 0x14 +#define USB_ID_INT_EN_HI_CLR 0x15 +#define USB_OTG_ADP_CTRL 0x16 +#define USB_OTG_ADP_HIGH 0x17 +#define USB_OTG_ADP_LOW 0x18 +#define USB_OTG_ADP_RISE 0x19 +#define USB_OTG_REVISION 0x1A + +/* to be moved to LDO */ +#define TWL6030_MISC2 0xE5 +#define TWL6030_CFG_LDO_PD2 0xF5 +#define TWL6030_BACKUP_REG 0xFA + +#define STS_HW_CONDITIONS 0x21 + +/* In module TWL6030_MODULE_PM_MASTER */ +#define STS_HW_CONDITIONS 0x21 +#define STS_USB_ID BIT(2) + +/* In module TWL6030_MODULE_PM_RECEIVER */ +#define VUSB_CFG_TRANS 0x71 +#define VUSB_CFG_STATE 0x72 +#define VUSB_CFG_VOLTAGE 0x73 + +/* in module TWL6030_MODULE_MAIN_CHARGE */ + +#define CHARGERUSB_CTRL1 0x8 + +#define CONTROLLER_STAT1 0x03 +#define VBUS_DET BIT(2) + +struct twl6030_usb { + struct phy_companion comparator; + struct device *dev; + + /* for vbus reporting with irqs disabled */ + spinlock_t lock; + + struct regulator *usb3v3; + + /* used to set vbus, in atomic path */ + struct work_struct set_vbus_work; + + int irq1; + int irq2; + enum omap_musb_vbus_id_status linkstat; + u8 asleep; + bool irq_enabled; + bool vbus_enable; + const char *regulator; +}; + +#define comparator_to_twl(x) container_of((x), struct twl6030_usb, comparator) + +/*-------------------------------------------------------------------------*/ + +static inline int twl6030_writeb(struct twl6030_usb *twl, u8 module, + u8 data, u8 address) +{ + int ret = 0; + + ret = twl_i2c_write_u8(module, data, address); + if (ret < 0) + dev_err(twl->dev, + "Write[0x%x] Error %d\n", address, ret); + return ret; +} + +static inline u8 twl6030_readb(struct twl6030_usb *twl, u8 module, u8 address) +{ + u8 data, ret = 0; + + ret = twl_i2c_read_u8(module, &data, address); + if (ret >= 0) + ret = data; + else + dev_err(twl->dev, + "readb[0x%x,0x%x] Error %d\n", + module, address, ret); + return ret; +} + +static int twl6030_start_srp(struct phy_companion *comparator) +{ + struct twl6030_usb *twl = comparator_to_twl(comparator); + + twl6030_writeb(twl, TWL_MODULE_USB, 0x24, USB_VBUS_CTRL_SET); + twl6030_writeb(twl, TWL_MODULE_USB, 0x84, USB_VBUS_CTRL_SET); + + mdelay(100); + twl6030_writeb(twl, TWL_MODULE_USB, 0xa0, USB_VBUS_CTRL_CLR); + + return 0; +} + +static int twl6030_usb_ldo_init(struct twl6030_usb *twl) +{ + /* Set to OTG_REV 1.3 and turn on the ID_WAKEUP_COMP */ + twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_BACKUP_REG); + + /* Program CFG_LDO_PD2 register and set VUSB bit */ + twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_CFG_LDO_PD2); + + /* Program MISC2 register and set bit VUSB_IN_VBAT */ + twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x10, TWL6030_MISC2); + + twl->usb3v3 = regulator_get(twl->dev, twl->regulator); + if (IS_ERR(twl->usb3v3)) + return -ENODEV; + + /* Program the USB_VBUS_CTRL_SET and set VBUS_ACT_COMP bit */ + twl6030_writeb(twl, TWL_MODULE_USB, 0x4, USB_VBUS_CTRL_SET); + + /* + * Program the USB_ID_CTRL_SET register to enable GND drive + * and the ID comparators + */ + twl6030_writeb(twl, TWL_MODULE_USB, 0x14, USB_ID_CTRL_SET); + + return 0; +} + +static ssize_t twl6030_usb_vbus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct twl6030_usb *twl = dev_get_drvdata(dev); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&twl->lock, flags); + + switch (twl->linkstat) { + case OMAP_MUSB_VBUS_VALID: + ret = snprintf(buf, PAGE_SIZE, "vbus\n"); + break; + case OMAP_MUSB_ID_GROUND: + ret = snprintf(buf, PAGE_SIZE, "id\n"); + break; + case OMAP_MUSB_VBUS_OFF: + ret = snprintf(buf, PAGE_SIZE, "none\n"); + break; + default: + ret = snprintf(buf, PAGE_SIZE, "UNKNOWN\n"); + } + spin_unlock_irqrestore(&twl->lock, flags); + + return ret; +} +static DEVICE_ATTR(vbus, 0444, twl6030_usb_vbus_show, NULL); + +static irqreturn_t twl6030_usb_irq(int irq, void *_twl) +{ + struct twl6030_usb *twl = _twl; + enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; + u8 vbus_state, hw_state; + int ret; + + hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS); + + vbus_state = twl6030_readb(twl, TWL_MODULE_MAIN_CHARGE, + CONTROLLER_STAT1); + if (!(hw_state & STS_USB_ID)) { + if (vbus_state & VBUS_DET) { + ret = regulator_enable(twl->usb3v3); + if (ret) + dev_err(twl->dev, "Failed to enable usb3v3\n"); + + twl->asleep = 1; + status = OMAP_MUSB_VBUS_VALID; + twl->linkstat = status; + omap_musb_mailbox(status); + } else { + if (twl->linkstat != OMAP_MUSB_UNKNOWN) { + status = OMAP_MUSB_VBUS_OFF; + twl->linkstat = status; + omap_musb_mailbox(status); + if (twl->asleep) { + regulator_disable(twl->usb3v3); + twl->asleep = 0; + } + } + } + } + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + + return IRQ_HANDLED; +} + +static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl) +{ + struct twl6030_usb *twl = _twl; + enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; + u8 hw_state; + int ret; + + hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS); + + if (hw_state & STS_USB_ID) { + ret = regulator_enable(twl->usb3v3); + if (ret) + dev_err(twl->dev, "Failed to enable usb3v3\n"); + + twl->asleep = 1; + twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_CLR); + twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_SET); + status = OMAP_MUSB_ID_GROUND; + twl->linkstat = status; + omap_musb_mailbox(status); + } else { + twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_CLR); + twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET); + } + twl6030_writeb(twl, TWL_MODULE_USB, status, USB_ID_INT_LATCH_CLR); + + return IRQ_HANDLED; +} + +static int twl6030_enable_irq(struct twl6030_usb *twl) +{ + twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET); + twl6030_interrupt_unmask(0x05, REG_INT_MSK_LINE_C); + twl6030_interrupt_unmask(0x05, REG_INT_MSK_STS_C); + + twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_LINE_C); + twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, + REG_INT_MSK_STS_C); + twl6030_usb_irq(twl->irq2, twl); + twl6030_usbotg_irq(twl->irq1, twl); + + return 0; +} + +static void otg_set_vbus_work(struct work_struct *data) +{ + struct twl6030_usb *twl = container_of(data, struct twl6030_usb, + set_vbus_work); + + /* + * Start driving VBUS. Set OPA_MODE bit in CHARGERUSB_CTRL1 + * register. This enables boost mode. + */ + + if (twl->vbus_enable) + twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x40, + CHARGERUSB_CTRL1); + else + twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x00, + CHARGERUSB_CTRL1); +} + +static int twl6030_set_vbus(struct phy_companion *comparator, bool enabled) +{ + struct twl6030_usb *twl = comparator_to_twl(comparator); + + twl->vbus_enable = enabled; + schedule_work(&twl->set_vbus_work); + + return 0; +} + +static int twl6030_usb_probe(struct platform_device *pdev) +{ + u32 ret; + struct twl6030_usb *twl; + int status, err; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct twl4030_usb_data *pdata = dev->platform_data; + + twl = devm_kzalloc(dev, sizeof *twl, GFP_KERNEL); + if (!twl) + return -ENOMEM; + + twl->dev = &pdev->dev; + twl->irq1 = platform_get_irq(pdev, 0); + twl->irq2 = platform_get_irq(pdev, 1); + twl->linkstat = OMAP_MUSB_UNKNOWN; + + twl->comparator.set_vbus = twl6030_set_vbus; + twl->comparator.start_srp = twl6030_start_srp; + + ret = omap_usb2_set_comparator(&twl->comparator); + if (ret == -ENODEV) { + dev_info(&pdev->dev, "phy not ready, deferring probe"); + return -EPROBE_DEFER; + } + + if (np) { + twl->regulator = "usb"; + } else if (pdata) { + if (pdata->features & TWL6025_SUBCLASS) + twl->regulator = "ldousb"; + else + twl->regulator = "vusb"; + } else { + dev_err(&pdev->dev, "twl6030 initialized without pdata\n"); + return -EINVAL; + } + + /* init spinlock for workqueue */ + spin_lock_init(&twl->lock); + + err = twl6030_usb_ldo_init(twl); + if (err) { + dev_err(&pdev->dev, "ldo init failed\n"); + return err; + } + + platform_set_drvdata(pdev, twl); + if (device_create_file(&pdev->dev, &dev_attr_vbus)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + + INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work); + + twl->irq_enabled = true; + status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "twl6030_usb", twl); + if (status < 0) { + dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", + twl->irq1, status); + device_remove_file(twl->dev, &dev_attr_vbus); + return status; + } + + status = request_threaded_irq(twl->irq2, NULL, twl6030_usb_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "twl6030_usb", twl); + if (status < 0) { + dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", + twl->irq2, status); + free_irq(twl->irq1, twl); + device_remove_file(twl->dev, &dev_attr_vbus); + return status; + } + + twl->asleep = 0; + twl6030_enable_irq(twl); + dev_info(&pdev->dev, "Initialized TWL6030 USB module\n"); + + return 0; +} + +static int twl6030_usb_remove(struct platform_device *pdev) +{ + struct twl6030_usb *twl = platform_get_drvdata(pdev); + + twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK, + REG_INT_MSK_LINE_C); + twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK, + REG_INT_MSK_STS_C); + free_irq(twl->irq1, twl); + free_irq(twl->irq2, twl); + regulator_put(twl->usb3v3); + device_remove_file(twl->dev, &dev_attr_vbus); + cancel_work_sync(&twl->set_vbus_work); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id twl6030_usb_id_table[] = { + { .compatible = "ti,twl6030-usb" }, + {} +}; +MODULE_DEVICE_TABLE(of, twl6030_usb_id_table); +#endif + +static struct platform_driver twl6030_usb_driver = { + .probe = twl6030_usb_probe, + .remove = twl6030_usb_remove, + .driver = { + .name = "twl6030_usb", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(twl6030_usb_id_table), + }, +}; + +static int __init twl6030_usb_init(void) +{ + return platform_driver_register(&twl6030_usb_driver); +} +subsys_initcall(twl6030_usb_init); + +static void __exit twl6030_usb_exit(void) +{ + platform_driver_unregister(&twl6030_usb_driver); +} +module_exit(twl6030_usb_exit); + +MODULE_ALIAS("platform:twl6030_usb"); +MODULE_AUTHOR("Hema HK "); +MODULE_DESCRIPTION("TWL6030 USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-ulpi-viewport.c b/drivers/usb/phy/phy-ulpi-viewport.c new file mode 100644 index 00000000..c5ba7e54 --- /dev/null +++ b/drivers/usb/phy/phy-ulpi-viewport.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include + +#define ULPI_VIEW_WAKEUP (1 << 31) +#define ULPI_VIEW_RUN (1 << 30) +#define ULPI_VIEW_WRITE (1 << 29) +#define ULPI_VIEW_READ (0 << 29) +#define ULPI_VIEW_ADDR(x) (((x) & 0xff) << 16) +#define ULPI_VIEW_DATA_READ(x) (((x) >> 8) & 0xff) +#define ULPI_VIEW_DATA_WRITE(x) ((x) & 0xff) + +static int ulpi_viewport_wait(void __iomem *view, u32 mask) +{ + unsigned long usec = 2000; + + while (usec--) { + if (!(readl(view) & mask)) + return 0; + + udelay(1); + }; + + return -ETIMEDOUT; +} + +static int ulpi_viewport_read(struct usb_phy *otg, u32 reg) +{ + int ret; + void __iomem *view = otg->io_priv; + + writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view); + ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP); + if (ret) + return ret; + + writel(ULPI_VIEW_RUN | ULPI_VIEW_READ | ULPI_VIEW_ADDR(reg), view); + ret = ulpi_viewport_wait(view, ULPI_VIEW_RUN); + if (ret) + return ret; + + return ULPI_VIEW_DATA_READ(readl(view)); +} + +static int ulpi_viewport_write(struct usb_phy *otg, u32 val, u32 reg) +{ + int ret; + void __iomem *view = otg->io_priv; + + writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view); + ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP); + if (ret) + return ret; + + writel(ULPI_VIEW_RUN | ULPI_VIEW_WRITE | ULPI_VIEW_DATA_WRITE(val) | + ULPI_VIEW_ADDR(reg), view); + + return ulpi_viewport_wait(view, ULPI_VIEW_RUN); +} + +struct usb_phy_io_ops ulpi_viewport_access_ops = { + .read = ulpi_viewport_read, + .write = ulpi_viewport_write, +}; diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c new file mode 100644 index 00000000..4e3877c3 --- /dev/null +++ b/drivers/usb/phy/phy-ulpi.c @@ -0,0 +1,286 @@ +/* + * Generic ULPI USB transceiver support + * + * Copyright (C) 2009 Daniel Mack + * + * Based on sources from + * + * Sascha Hauer + * Freescale Semiconductors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + + +struct ulpi_info { + unsigned int id; + char *name; +}; + +#define ULPI_ID(vendor, product) (((vendor) << 16) | (product)) +#define ULPI_INFO(_id, _name) \ + { \ + .id = (_id), \ + .name = (_name), \ + } + +/* ULPI hardcoded IDs, used for probing */ +static struct ulpi_info ulpi_ids[] = { + ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"), + ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"), + ULPI_INFO(ULPI_ID(0x0424, 0x0007), "SMSC USB3320"), + ULPI_INFO(ULPI_ID(0x0424, 0x0009), "SMSC USB334x"), + ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"), +}; + +static int ulpi_set_otg_flags(struct usb_phy *phy) +{ + unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN | + ULPI_OTG_CTRL_DM_PULLDOWN; + + if (phy->flags & ULPI_OTG_ID_PULLUP) + flags |= ULPI_OTG_CTRL_ID_PULLUP; + + /* + * ULPI Specification rev.1.1 default + * for Dp/DmPulldown is enabled. + */ + if (phy->flags & ULPI_OTG_DP_PULLDOWN_DIS) + flags &= ~ULPI_OTG_CTRL_DP_PULLDOWN; + + if (phy->flags & ULPI_OTG_DM_PULLDOWN_DIS) + flags &= ~ULPI_OTG_CTRL_DM_PULLDOWN; + + if (phy->flags & ULPI_OTG_EXTVBUSIND) + flags |= ULPI_OTG_CTRL_EXTVBUSIND; + + return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL); +} + +static int ulpi_set_fc_flags(struct usb_phy *phy) +{ + unsigned int flags = 0; + + /* + * ULPI Specification rev.1.1 default + * for XcvrSelect is Full Speed. + */ + if (phy->flags & ULPI_FC_HS) + flags |= ULPI_FUNC_CTRL_HIGH_SPEED; + else if (phy->flags & ULPI_FC_LS) + flags |= ULPI_FUNC_CTRL_LOW_SPEED; + else if (phy->flags & ULPI_FC_FS4LS) + flags |= ULPI_FUNC_CTRL_FS4LS; + else + flags |= ULPI_FUNC_CTRL_FULL_SPEED; + + if (phy->flags & ULPI_FC_TERMSEL) + flags |= ULPI_FUNC_CTRL_TERMSELECT; + + /* + * ULPI Specification rev.1.1 default + * for OpMode is Normal Operation. + */ + if (phy->flags & ULPI_FC_OP_NODRV) + flags |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; + else if (phy->flags & ULPI_FC_OP_DIS_NRZI) + flags |= ULPI_FUNC_CTRL_OPMODE_DISABLE_NRZI; + else if (phy->flags & ULPI_FC_OP_NSYNC_NEOP) + flags |= ULPI_FUNC_CTRL_OPMODE_NOSYNC_NOEOP; + else + flags |= ULPI_FUNC_CTRL_OPMODE_NORMAL; + + /* + * ULPI Specification rev.1.1 default + * for SuspendM is Powered. + */ + flags |= ULPI_FUNC_CTRL_SUSPENDM; + + return usb_phy_io_write(phy, flags, ULPI_FUNC_CTRL); +} + +static int ulpi_set_ic_flags(struct usb_phy *phy) +{ + unsigned int flags = 0; + + if (phy->flags & ULPI_IC_AUTORESUME) + flags |= ULPI_IFC_CTRL_AUTORESUME; + + if (phy->flags & ULPI_IC_EXTVBUS_INDINV) + flags |= ULPI_IFC_CTRL_EXTERNAL_VBUS; + + if (phy->flags & ULPI_IC_IND_PASSTHRU) + flags |= ULPI_IFC_CTRL_PASSTHRU; + + if (phy->flags & ULPI_IC_PROTECT_DIS) + flags |= ULPI_IFC_CTRL_PROTECT_IFC_DISABLE; + + return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL); +} + +static int ulpi_set_flags(struct usb_phy *phy) +{ + int ret; + + ret = ulpi_set_otg_flags(phy); + if (ret) + return ret; + + ret = ulpi_set_ic_flags(phy); + if (ret) + return ret; + + return ulpi_set_fc_flags(phy); +} + +static int ulpi_check_integrity(struct usb_phy *phy) +{ + int ret, i; + unsigned int val = 0x55; + + for (i = 0; i < 2; i++) { + ret = usb_phy_io_write(phy, val, ULPI_SCRATCH); + if (ret < 0) + return ret; + + ret = usb_phy_io_read(phy, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != val) { + pr_err("ULPI integrity check: failed!"); + return -ENODEV; + } + val = val << 1; + } + + pr_info("ULPI integrity check: passed.\n"); + + return 0; +} + +static int ulpi_init(struct usb_phy *phy) +{ + int i, vid, pid, ret; + u32 ulpi_id = 0; + + for (i = 0; i < 4; i++) { + ret = usb_phy_io_read(phy, ULPI_PRODUCT_ID_HIGH - i); + if (ret < 0) + return ret; + ulpi_id = (ulpi_id << 8) | ret; + } + vid = ulpi_id & 0xffff; + pid = ulpi_id >> 16; + + pr_info("ULPI transceiver vendor/product ID 0x%04x/0x%04x\n", vid, pid); + + for (i = 0; i < ARRAY_SIZE(ulpi_ids); i++) { + if (ulpi_ids[i].id == ULPI_ID(vid, pid)) { + pr_info("Found %s ULPI transceiver.\n", + ulpi_ids[i].name); + break; + } + } + + ret = ulpi_check_integrity(phy); + if (ret) + return ret; + + return ulpi_set_flags(phy); +} + +static int ulpi_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct usb_phy *phy = otg->phy; + unsigned int flags = usb_phy_io_read(phy, ULPI_IFC_CTRL); + + if (!host) { + otg->host = NULL; + return 0; + } + + otg->host = host; + + flags &= ~(ULPI_IFC_CTRL_6_PIN_SERIAL_MODE | + ULPI_IFC_CTRL_3_PIN_SERIAL_MODE | + ULPI_IFC_CTRL_CARKITMODE); + + if (phy->flags & ULPI_IC_6PIN_SERIAL) + flags |= ULPI_IFC_CTRL_6_PIN_SERIAL_MODE; + else if (phy->flags & ULPI_IC_3PIN_SERIAL) + flags |= ULPI_IFC_CTRL_3_PIN_SERIAL_MODE; + else if (phy->flags & ULPI_IC_CARKIT) + flags |= ULPI_IFC_CTRL_CARKITMODE; + + return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL); +} + +static int ulpi_set_vbus(struct usb_otg *otg, bool on) +{ + struct usb_phy *phy = otg->phy; + unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL); + + flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT); + + if (on) { + if (phy->flags & ULPI_OTG_DRVVBUS) + flags |= ULPI_OTG_CTRL_DRVVBUS; + + if (phy->flags & ULPI_OTG_DRVVBUS_EXT) + flags |= ULPI_OTG_CTRL_DRVVBUS_EXT; + } + + return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL); +} + +struct usb_phy * +otg_ulpi_create(struct usb_phy_io_ops *ops, + unsigned int flags) +{ + struct usb_phy *phy; + struct usb_otg *otg; + + phy = kzalloc(sizeof(*phy), GFP_KERNEL); + if (!phy) + return NULL; + + otg = kzalloc(sizeof(*otg), GFP_KERNEL); + if (!otg) { + kfree(phy); + return NULL; + } + + phy->label = "ULPI"; + phy->flags = flags; + phy->io_ops = ops; + phy->otg = otg; + phy->init = ulpi_init; + + otg->phy = phy; + otg->set_host = ulpi_set_host; + otg->set_vbus = ulpi_set_vbus; + + return phy; +} +EXPORT_SYMBOL_GPL(otg_ulpi_create); + diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c new file mode 100644 index 00000000..a9984c70 --- /dev/null +++ b/drivers/usb/phy/phy.c @@ -0,0 +1,438 @@ +/* + * phy.c -- USB phy handling + * + * Copyright (C) 2004-2013 Texas Instruments + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +static LIST_HEAD(phy_list); +static LIST_HEAD(phy_bind_list); +static DEFINE_SPINLOCK(phy_lock); + +static struct usb_phy *__usb_find_phy(struct list_head *list, + enum usb_phy_type type) +{ + struct usb_phy *phy = NULL; + + list_for_each_entry(phy, list, head) { + if (phy->type != type) + continue; + + return phy; + } + + return ERR_PTR(-ENODEV); +} + +static struct usb_phy *__usb_find_phy_dev(struct device *dev, + struct list_head *list, u8 index) +{ + struct usb_phy_bind *phy_bind = NULL; + + list_for_each_entry(phy_bind, list, list) { + if (!(strcmp(phy_bind->dev_name, dev_name(dev))) && + phy_bind->index == index) { + if (phy_bind->phy) + return phy_bind->phy; + else + return ERR_PTR(-EPROBE_DEFER); + } + } + + return ERR_PTR(-ENODEV); +} + +static struct usb_phy *__of_usb_find_phy(struct device_node *node) +{ + struct usb_phy *phy; + + list_for_each_entry(phy, &phy_list, head) { + if (node != phy->dev->of_node) + continue; + + return phy; + } + + return ERR_PTR(-ENODEV); +} + +static void devm_usb_phy_release(struct device *dev, void *res) +{ + struct usb_phy *phy = *(struct usb_phy **)res; + + usb_put_phy(phy); +} + +static int devm_usb_phy_match(struct device *dev, void *res, void *match_data) +{ + return res == match_data; +} + +/** + * devm_usb_get_phy - find the USB PHY + * @dev - device that requests this phy + * @type - the type of the phy the controller requires + * + * Gets the phy using usb_get_phy(), and associates a device with it using + * devres. On driver detach, release function is invoked on the devres data, + * then, devres data is freed. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *devm_usb_get_phy(struct device *dev, enum usb_phy_type type) +{ + struct usb_phy **ptr, *phy; + + ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + phy = usb_get_phy(type); + if (!IS_ERR(phy)) { + *ptr = phy; + devres_add(dev, ptr); + } else + devres_free(ptr); + + return phy; +} +EXPORT_SYMBOL_GPL(devm_usb_get_phy); + +/** + * usb_get_phy - find the USB PHY + * @type - the type of the phy the controller requires + * + * Returns the phy driver, after getting a refcount to it; or + * -ENODEV if there is no such phy. The caller is responsible for + * calling usb_put_phy() to release that count. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *usb_get_phy(enum usb_phy_type type) +{ + struct usb_phy *phy = NULL; + unsigned long flags; + + spin_lock_irqsave(&phy_lock, flags); + + phy = __usb_find_phy(&phy_list, type); + if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) { + pr_err("unable to find transceiver of type %s\n", + usb_phy_type_string(type)); + goto err0; + } + + get_device(phy->dev); + +err0: + spin_unlock_irqrestore(&phy_lock, flags); + + return phy; +} +EXPORT_SYMBOL_GPL(usb_get_phy); + + /** + * devm_usb_get_phy_by_phandle - find the USB PHY by phandle + * @dev - device that requests this phy + * @phandle - name of the property holding the phy phandle value + * @index - the index of the phy + * + * Returns the phy driver associated with the given phandle value, + * after getting a refcount to it, -ENODEV if there is no such phy or + * -EPROBE_DEFER if there is a phandle to the phy, but the device is + * not yet loaded. While at that, it also associates the device with + * the phy using devres. On driver detach, release function is invoked + * on the devres data, then, devres data is freed. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev, + const char *phandle, u8 index) +{ + struct usb_phy *phy = ERR_PTR(-ENOMEM), **ptr; + unsigned long flags; + struct device_node *node; + + if (!dev->of_node) { + dev_dbg(dev, "device does not have a device node entry\n"); + return ERR_PTR(-EINVAL); + } + + node = of_parse_phandle(dev->of_node, phandle, index); + if (!node) { + dev_dbg(dev, "failed to get %s phandle in %s node\n", phandle, + dev->of_node->full_name); + return ERR_PTR(-ENODEV); + } + + ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) { + dev_dbg(dev, "failed to allocate memory for devres\n"); + goto err0; + } + + spin_lock_irqsave(&phy_lock, flags); + + phy = __of_usb_find_phy(node); + if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) { + phy = ERR_PTR(-EPROBE_DEFER); + devres_free(ptr); + goto err1; + } + + *ptr = phy; + devres_add(dev, ptr); + + get_device(phy->dev); + +err1: + spin_unlock_irqrestore(&phy_lock, flags); + +err0: + of_node_put(node); + + return phy; +} +EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_phandle); + +/** + * usb_get_phy_dev - find the USB PHY + * @dev - device that requests this phy + * @index - the index of the phy + * + * Returns the phy driver, after getting a refcount to it; or + * -ENODEV if there is no such phy. The caller is responsible for + * calling usb_put_phy() to release that count. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index) +{ + struct usb_phy *phy = NULL; + unsigned long flags; + + spin_lock_irqsave(&phy_lock, flags); + + phy = __usb_find_phy_dev(dev, &phy_bind_list, index); + if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) { + pr_err("unable to find transceiver\n"); + goto err0; + } + + get_device(phy->dev); + +err0: + spin_unlock_irqrestore(&phy_lock, flags); + + return phy; +} +EXPORT_SYMBOL_GPL(usb_get_phy_dev); + +/** + * devm_usb_get_phy_dev - find the USB PHY using device ptr and index + * @dev - device that requests this phy + * @index - the index of the phy + * + * Gets the phy using usb_get_phy_dev(), and associates a device with it using + * devres. On driver detach, release function is invoked on the devres data, + * then, devres data is freed. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index) +{ + struct usb_phy **ptr, *phy; + + ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + phy = usb_get_phy_dev(dev, index); + if (!IS_ERR(phy)) { + *ptr = phy; + devres_add(dev, ptr); + } else + devres_free(ptr); + + return phy; +} +EXPORT_SYMBOL_GPL(devm_usb_get_phy_dev); + +/** + * devm_usb_put_phy - release the USB PHY + * @dev - device that wants to release this phy + * @phy - the phy returned by devm_usb_get_phy() + * + * destroys the devres associated with this phy and invokes usb_put_phy + * to release the phy. + * + * For use by USB host and peripheral drivers. + */ +void devm_usb_put_phy(struct device *dev, struct usb_phy *phy) +{ + int r; + + r = devres_destroy(dev, devm_usb_phy_release, devm_usb_phy_match, phy); + dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n"); +} +EXPORT_SYMBOL_GPL(devm_usb_put_phy); + +/** + * usb_put_phy - release the USB PHY + * @x: the phy returned by usb_get_phy() + * + * Releases a refcount the caller received from usb_get_phy(). + * + * For use by USB host and peripheral drivers. + */ +void usb_put_phy(struct usb_phy *x) +{ + if (x) { + struct module *owner = x->dev->driver->owner; + + put_device(x->dev); + module_put(owner); + } +} +EXPORT_SYMBOL_GPL(usb_put_phy); + +/** + * usb_add_phy - declare the USB PHY + * @x: the USB phy to be used; or NULL + * @type - the type of this PHY + * + * This call is exclusively for use by phy drivers, which + * coordinate the activities of drivers for host and peripheral + * controllers, and in some cases for VBUS current regulation. + */ +int usb_add_phy(struct usb_phy *x, enum usb_phy_type type) +{ + int ret = 0; + unsigned long flags; + struct usb_phy *phy; + + if (x->type != USB_PHY_TYPE_UNDEFINED) { + dev_err(x->dev, "not accepting initialized PHY %s\n", x->label); + return -EINVAL; + } + + spin_lock_irqsave(&phy_lock, flags); + + list_for_each_entry(phy, &phy_list, head) { + if (phy->type == type) { + ret = -EBUSY; + dev_err(x->dev, "transceiver type %s already exists\n", + usb_phy_type_string(type)); + goto out; + } + } + + x->type = type; + list_add_tail(&x->head, &phy_list); + +out: + spin_unlock_irqrestore(&phy_lock, flags); + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_phy); + +/** + * usb_add_phy_dev - declare the USB PHY + * @x: the USB phy to be used; or NULL + * + * This call is exclusively for use by phy drivers, which + * coordinate the activities of drivers for host and peripheral + * controllers, and in some cases for VBUS current regulation. + */ +int usb_add_phy_dev(struct usb_phy *x) +{ + struct usb_phy_bind *phy_bind; + unsigned long flags; + + if (!x->dev) { + dev_err(x->dev, "no device provided for PHY\n"); + return -EINVAL; + } + + spin_lock_irqsave(&phy_lock, flags); + list_for_each_entry(phy_bind, &phy_bind_list, list) + if (!(strcmp(phy_bind->phy_dev_name, dev_name(x->dev)))) + phy_bind->phy = x; + + list_add_tail(&x->head, &phy_list); + + spin_unlock_irqrestore(&phy_lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(usb_add_phy_dev); + +/** + * usb_remove_phy - remove the OTG PHY + * @x: the USB OTG PHY to be removed; + * + * This reverts the effects of usb_add_phy + */ +void usb_remove_phy(struct usb_phy *x) +{ + unsigned long flags; + struct usb_phy_bind *phy_bind; + + spin_lock_irqsave(&phy_lock, flags); + if (x) { + list_for_each_entry(phy_bind, &phy_bind_list, list) + if (phy_bind->phy == x) + phy_bind->phy = NULL; + list_del(&x->head); + } + spin_unlock_irqrestore(&phy_lock, flags); +} +EXPORT_SYMBOL_GPL(usb_remove_phy); + +/** + * usb_bind_phy - bind the phy and the controller that uses the phy + * @dev_name: the device name of the device that will bind to the phy + * @index: index to specify the port number + * @phy_dev_name: the device name of the phy + * + * Fills the phy_bind structure with the dev_name and phy_dev_name. This will + * be used when the phy driver registers the phy and when the controller + * requests this phy. + * + * To be used by platform specific initialization code. + */ +int usb_bind_phy(const char *dev_name, u8 index, + const char *phy_dev_name) +{ + struct usb_phy_bind *phy_bind; + unsigned long flags; + + phy_bind = kzalloc(sizeof(*phy_bind), GFP_KERNEL); + if (!phy_bind) { + pr_err("phy_bind(): No memory for phy_bind"); + return -ENOMEM; + } + + phy_bind->dev_name = dev_name; + phy_bind->phy_dev_name = phy_dev_name; + phy_bind->index = index; + + spin_lock_irqsave(&phy_lock, flags); + list_add_tail(&phy_bind->list, &phy_bind_list); + spin_unlock_irqrestore(&phy_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_bind_phy); diff --git a/drivers/usb/renesas_usbhs/Kconfig b/drivers/usb/renesas_usbhs/Kconfig new file mode 100644 index 00000000..019bf7e4 --- /dev/null +++ b/drivers/usb/renesas_usbhs/Kconfig @@ -0,0 +1,15 @@ +# +# Renesas USBHS Controller Drivers +# + +config USB_RENESAS_USBHS + tristate 'Renesas USBHS controller' + depends on USB_GADGET && GENERIC_HARDIRQS + default n + help + Renesas USBHS is a discrete USB host and peripheral controller chip + that supports both full and high speed USB 2.0 data transfers. + It has nine or more configurable endpoints, and endpoint zero. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "renesas_usbhs" diff --git a/drivers/usb/renesas_usbhs/Makefile b/drivers/usb/renesas_usbhs/Makefile new file mode 100644 index 00000000..bc8aef43 --- /dev/null +++ b/drivers/usb/renesas_usbhs/Makefile @@ -0,0 +1,15 @@ +# +# for Renesas USB +# + +obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs.o + +renesas_usbhs-y := common.o mod.o pipe.o fifo.o + +ifneq ($(CONFIG_USB_RENESAS_USBHS_HCD),) + renesas_usbhs-y += mod_host.o +endif + +ifneq ($(CONFIG_USB_RENESAS_USBHS_UDC),) + renesas_usbhs-y += mod_gadget.o +endif diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c new file mode 100644 index 00000000..cfd20503 --- /dev/null +++ b/drivers/usb/renesas_usbhs/common.c @@ -0,0 +1,645 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include +#include +#include "common.h" + +/* + * image of renesas_usbhs + * + * ex) gadget case + + * mod.c + * mod_gadget.c + * mod_host.c pipe.c fifo.c + * + * +-------+ +-----------+ + * | pipe0 |------>| fifo pio | + * +------------+ +-------+ +-----------+ + * | mod_gadget |=====> | pipe1 |--+ + * +------------+ +-------+ | +-----------+ + * | pipe2 | | +-| fifo dma0 | + * +------------+ +-------+ | | +-----------+ + * | mod_host | | pipe3 |<-|--+ + * +------------+ +-------+ | +-----------+ + * | .... | +--->| fifo dma1 | + * | .... | +-----------+ + */ + + +#define USBHSF_RUNTIME_PWCTRL (1 << 0) + +/* status */ +#define usbhsc_flags_init(p) do {(p)->flags = 0; } while (0) +#define usbhsc_flags_set(p, b) ((p)->flags |= (b)) +#define usbhsc_flags_clr(p, b) ((p)->flags &= ~(b)) +#define usbhsc_flags_has(p, b) ((p)->flags & (b)) + +/* + * platform call back + * + * renesas usb support platform callback function. + * Below macro call it. + * if platform doesn't have callback, it return 0 (no error) + */ +#define usbhs_platform_call(priv, func, args...)\ + (!(priv) ? -ENODEV : \ + !((priv)->pfunc.func) ? 0 : \ + (priv)->pfunc.func(args)) + +/* + * common functions + */ +u16 usbhs_read(struct usbhs_priv *priv, u32 reg) +{ + return ioread16(priv->base + reg); +} + +void usbhs_write(struct usbhs_priv *priv, u32 reg, u16 data) +{ + iowrite16(data, priv->base + reg); +} + +void usbhs_bset(struct usbhs_priv *priv, u32 reg, u16 mask, u16 data) +{ + u16 val = usbhs_read(priv, reg); + + val &= ~mask; + val |= data & mask; + + usbhs_write(priv, reg, val); +} + +struct usbhs_priv *usbhs_pdev_to_priv(struct platform_device *pdev) +{ + return dev_get_drvdata(&pdev->dev); +} + +/* + * syscfg functions + */ +static void usbhs_sys_clock_ctrl(struct usbhs_priv *priv, int enable) +{ + usbhs_bset(priv, SYSCFG, SCKE, enable ? SCKE : 0); +} + +void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable) +{ + u16 mask = DCFM | DRPD | DPRPU | HSE | USBE; + u16 val = DCFM | DRPD | HSE | USBE; + int has_otg = usbhs_get_dparam(priv, has_otg); + + if (has_otg) + usbhs_bset(priv, DVSTCTR, (EXTLP | PWEN), (EXTLP | PWEN)); + + /* + * if enable + * + * - select Host mode + * - D+ Line/D- Line Pull-down + */ + usbhs_bset(priv, SYSCFG, mask, enable ? val : 0); +} + +void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable) +{ + u16 mask = DCFM | DRPD | DPRPU | HSE | USBE; + u16 val = DPRPU | HSE | USBE; + + /* + * if enable + * + * - select Function mode + * - D+ Line Pull-up + */ + usbhs_bset(priv, SYSCFG, mask, enable ? val : 0); +} + +void usbhs_sys_function_pullup(struct usbhs_priv *priv, int enable) +{ + usbhs_bset(priv, SYSCFG, DPRPU, enable ? DPRPU : 0); +} + +void usbhs_sys_set_test_mode(struct usbhs_priv *priv, u16 mode) +{ + usbhs_write(priv, TESTMODE, mode); +} + +/* + * frame functions + */ +int usbhs_frame_get_num(struct usbhs_priv *priv) +{ + return usbhs_read(priv, FRMNUM) & FRNM_MASK; +} + +/* + * usb request functions + */ +void usbhs_usbreq_get_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req) +{ + u16 val; + + val = usbhs_read(priv, USBREQ); + req->bRequest = (val >> 8) & 0xFF; + req->bRequestType = (val >> 0) & 0xFF; + + req->wValue = usbhs_read(priv, USBVAL); + req->wIndex = usbhs_read(priv, USBINDX); + req->wLength = usbhs_read(priv, USBLENG); +} + +void usbhs_usbreq_set_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req) +{ + usbhs_write(priv, USBREQ, (req->bRequest << 8) | req->bRequestType); + usbhs_write(priv, USBVAL, req->wValue); + usbhs_write(priv, USBINDX, req->wIndex); + usbhs_write(priv, USBLENG, req->wLength); + + usbhs_bset(priv, DCPCTR, SUREQ, SUREQ); +} + +/* + * bus/vbus functions + */ +void usbhs_bus_send_sof_enable(struct usbhs_priv *priv) +{ + u16 status = usbhs_read(priv, DVSTCTR) & (USBRST | UACT); + + if (status != USBRST) { + struct device *dev = usbhs_priv_to_dev(priv); + dev_err(dev, "usbhs should be reset\n"); + } + + usbhs_bset(priv, DVSTCTR, (USBRST | UACT), UACT); +} + +void usbhs_bus_send_reset(struct usbhs_priv *priv) +{ + usbhs_bset(priv, DVSTCTR, (USBRST | UACT), USBRST); +} + +int usbhs_bus_get_speed(struct usbhs_priv *priv) +{ + u16 dvstctr = usbhs_read(priv, DVSTCTR); + + switch (RHST & dvstctr) { + case RHST_LOW_SPEED: + return USB_SPEED_LOW; + case RHST_FULL_SPEED: + return USB_SPEED_FULL; + case RHST_HIGH_SPEED: + return USB_SPEED_HIGH; + } + + return USB_SPEED_UNKNOWN; +} + +int usbhs_vbus_ctrl(struct usbhs_priv *priv, int enable) +{ + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + + return usbhs_platform_call(priv, set_vbus, pdev, enable); +} + +static void usbhsc_bus_init(struct usbhs_priv *priv) +{ + usbhs_write(priv, DVSTCTR, 0); + + usbhs_vbus_ctrl(priv, 0); +} + +/* + * device configuration + */ +int usbhs_set_device_config(struct usbhs_priv *priv, int devnum, + u16 upphub, u16 hubport, u16 speed) +{ + struct device *dev = usbhs_priv_to_dev(priv); + u16 usbspd = 0; + u32 reg = DEVADD0 + (2 * devnum); + + if (devnum > 10) { + dev_err(dev, "cannot set speed to unknown device %d\n", devnum); + return -EIO; + } + + if (upphub > 0xA) { + dev_err(dev, "unsupported hub number %d\n", upphub); + return -EIO; + } + + switch (speed) { + case USB_SPEED_LOW: + usbspd = USBSPD_SPEED_LOW; + break; + case USB_SPEED_FULL: + usbspd = USBSPD_SPEED_FULL; + break; + case USB_SPEED_HIGH: + usbspd = USBSPD_SPEED_HIGH; + break; + default: + dev_err(dev, "unsupported speed %d\n", speed); + return -EIO; + } + + usbhs_write(priv, reg, UPPHUB(upphub) | + HUBPORT(hubport)| + USBSPD(usbspd)); + + return 0; +} + +/* + * local functions + */ +static void usbhsc_set_buswait(struct usbhs_priv *priv) +{ + int wait = usbhs_get_dparam(priv, buswait_bwait); + + /* set bus wait if platform have */ + if (wait) + usbhs_bset(priv, BUSWAIT, 0x000F, wait); +} + +/* + * platform default param + */ +static u32 usbhsc_default_pipe_type[] = { + USB_ENDPOINT_XFER_CONTROL, + USB_ENDPOINT_XFER_ISOC, + USB_ENDPOINT_XFER_ISOC, + USB_ENDPOINT_XFER_BULK, + USB_ENDPOINT_XFER_BULK, + USB_ENDPOINT_XFER_BULK, + USB_ENDPOINT_XFER_INT, + USB_ENDPOINT_XFER_INT, + USB_ENDPOINT_XFER_INT, + USB_ENDPOINT_XFER_INT, +}; + +/* + * power control + */ +static void usbhsc_power_ctrl(struct usbhs_priv *priv, int enable) +{ + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + struct device *dev = usbhs_priv_to_dev(priv); + + if (enable) { + /* enable PM */ + pm_runtime_get_sync(dev); + + /* enable platform power */ + usbhs_platform_call(priv, power_ctrl, pdev, priv->base, enable); + + /* USB on */ + usbhs_sys_clock_ctrl(priv, enable); + } else { + /* USB off */ + usbhs_sys_clock_ctrl(priv, enable); + + /* disable platform power */ + usbhs_platform_call(priv, power_ctrl, pdev, priv->base, enable); + + /* disable PM */ + pm_runtime_put_sync(dev); + } +} + +/* + * hotplug + */ +static void usbhsc_hotplug(struct usbhs_priv *priv) +{ + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + int id; + int enable; + int ret; + + /* + * get vbus status from platform + */ + enable = usbhs_platform_call(priv, get_vbus, pdev); + + /* + * get id from platform + */ + id = usbhs_platform_call(priv, get_id, pdev); + + if (enable && !mod) { + ret = usbhs_mod_change(priv, id); + if (ret < 0) + return; + + dev_dbg(&pdev->dev, "%s enable\n", __func__); + + /* power on */ + if (usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, enable); + + /* bus init */ + usbhsc_set_buswait(priv); + usbhsc_bus_init(priv); + + /* module start */ + usbhs_mod_call(priv, start, priv); + + } else if (!enable && mod) { + dev_dbg(&pdev->dev, "%s disable\n", __func__); + + /* module stop */ + usbhs_mod_call(priv, stop, priv); + + /* bus init */ + usbhsc_bus_init(priv); + + /* power off */ + if (usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, enable); + + usbhs_mod_change(priv, -1); + + /* reset phy for next connection */ + usbhs_platform_call(priv, phy_reset, pdev); + } +} + +/* + * notify hotplug + */ +static void usbhsc_notify_hotplug(struct work_struct *work) +{ + struct usbhs_priv *priv = container_of(work, + struct usbhs_priv, + notify_hotplug_work.work); + usbhsc_hotplug(priv); +} + +static int usbhsc_drvcllbck_notify_hotplug(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + int delay = usbhs_get_dparam(priv, detection_delay); + + /* + * This functions will be called in interrupt. + * To make sure safety context, + * use workqueue for usbhs_notify_hotplug + */ + schedule_delayed_work(&priv->notify_hotplug_work, + msecs_to_jiffies(delay)); + return 0; +} + +/* + * platform functions + */ +static int usbhs_probe(struct platform_device *pdev) +{ + struct renesas_usbhs_platform_info *info = pdev->dev.platform_data; + struct renesas_usbhs_driver_callback *dfunc; + struct usbhs_priv *priv; + struct resource *res, *irq_res; + int ret; + + /* check platform information */ + if (!info || + !info->platform_callback.get_id) { + dev_err(&pdev->dev, "no platform information\n"); + return -EINVAL; + } + + /* platform data */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res || !irq_res) { + dev_err(&pdev->dev, "Not enough Renesas USB platform resources.\n"); + return -ENODEV; + } + + /* usb private data */ + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "Could not allocate priv\n"); + return -ENOMEM; + } + + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + /* + * care platform info + */ + memcpy(&priv->pfunc, + &info->platform_callback, + sizeof(struct renesas_usbhs_platform_callback)); + memcpy(&priv->dparam, + &info->driver_param, + sizeof(struct renesas_usbhs_driver_param)); + + /* set driver callback functions for platform */ + dfunc = &info->driver_callback; + dfunc->notify_hotplug = usbhsc_drvcllbck_notify_hotplug; + + /* set default param if platform doesn't have */ + if (!priv->dparam.pipe_type) { + priv->dparam.pipe_type = usbhsc_default_pipe_type; + priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_default_pipe_type); + } + if (!priv->dparam.pio_dma_border) + priv->dparam.pio_dma_border = 64; /* 64byte */ + + /* FIXME */ + /* runtime power control ? */ + if (priv->pfunc.get_vbus) + usbhsc_flags_set(priv, USBHSF_RUNTIME_PWCTRL); + + /* + * priv settings + */ + priv->irq = irq_res->start; + if (irq_res->flags & IORESOURCE_IRQ_SHAREABLE) + priv->irqflags = IRQF_SHARED; + priv->pdev = pdev; + INIT_DELAYED_WORK(&priv->notify_hotplug_work, usbhsc_notify_hotplug); + spin_lock_init(usbhs_priv_to_lock(priv)); + + /* call pipe and module init */ + ret = usbhs_pipe_probe(priv); + if (ret < 0) + return ret; + + ret = usbhs_fifo_probe(priv); + if (ret < 0) + goto probe_end_pipe_exit; + + ret = usbhs_mod_probe(priv); + if (ret < 0) + goto probe_end_fifo_exit; + + /* dev_set_drvdata should be called after usbhs_mod_init */ + dev_set_drvdata(&pdev->dev, priv); + + /* + * deviece reset here because + * USB device might be used in boot loader. + */ + usbhs_sys_clock_ctrl(priv, 0); + + /* + * platform call + * + * USB phy setup might depend on CPU/Board. + * If platform has its callback functions, + * call it here. + */ + ret = usbhs_platform_call(priv, hardware_init, pdev); + if (ret < 0) { + dev_err(&pdev->dev, "platform prove failed.\n"); + goto probe_end_mod_exit; + } + + /* reset phy for connection */ + usbhs_platform_call(priv, phy_reset, pdev); + + /* power control */ + pm_runtime_enable(&pdev->dev); + if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) { + usbhsc_power_ctrl(priv, 1); + usbhs_mod_autonomy_mode(priv); + } + + /* + * manual call notify_hotplug for cold plug + */ + ret = usbhsc_drvcllbck_notify_hotplug(pdev); + if (ret < 0) + goto probe_end_call_remove; + + dev_info(&pdev->dev, "probed\n"); + + return ret; + +probe_end_call_remove: + usbhs_platform_call(priv, hardware_exit, pdev); +probe_end_mod_exit: + usbhs_mod_remove(priv); +probe_end_fifo_exit: + usbhs_fifo_remove(priv); +probe_end_pipe_exit: + usbhs_pipe_remove(priv); + + dev_info(&pdev->dev, "probe failed\n"); + + return ret; +} + +static int usbhs_remove(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + struct renesas_usbhs_platform_info *info = pdev->dev.platform_data; + struct renesas_usbhs_driver_callback *dfunc = &info->driver_callback; + + dev_dbg(&pdev->dev, "usb remove\n"); + + dfunc->notify_hotplug = NULL; + + /* power off */ + if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, 0); + + pm_runtime_disable(&pdev->dev); + + usbhs_platform_call(priv, hardware_exit, pdev); + usbhs_mod_remove(priv); + usbhs_fifo_remove(priv); + usbhs_pipe_remove(priv); + + return 0; +} + +static int usbhsc_suspend(struct device *dev) +{ + struct usbhs_priv *priv = dev_get_drvdata(dev); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + + if (mod) { + usbhs_mod_call(priv, stop, priv); + usbhs_mod_change(priv, -1); + } + + if (mod || !usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, 0); + + return 0; +} + +static int usbhsc_resume(struct device *dev) +{ + struct usbhs_priv *priv = dev_get_drvdata(dev); + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + + if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, 1); + + usbhs_platform_call(priv, phy_reset, pdev); + + usbhsc_drvcllbck_notify_hotplug(pdev); + + return 0; +} + +static int usbhsc_runtime_nop(struct device *dev) +{ + /* Runtime PM callback shared between ->runtime_suspend() + * and ->runtime_resume(). Simply returns success. + * + * This driver re-initializes all registers after + * pm_runtime_get_sync() anyway so there is no need + * to save and restore registers here. + */ + return 0; +} + +static const struct dev_pm_ops usbhsc_pm_ops = { + .suspend = usbhsc_suspend, + .resume = usbhsc_resume, + .runtime_suspend = usbhsc_runtime_nop, + .runtime_resume = usbhsc_runtime_nop, +}; + +static struct platform_driver renesas_usbhs_driver = { + .driver = { + .name = "renesas_usbhs", + .pm = &usbhsc_pm_ops, + }, + .probe = usbhs_probe, + .remove = usbhs_remove, +}; + +module_platform_driver(renesas_usbhs_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Renesas USB driver"); +MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h new file mode 100644 index 00000000..c69dd2fb --- /dev/null +++ b/drivers/usb/renesas_usbhs/common.h @@ -0,0 +1,325 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef RENESAS_USB_DRIVER_H +#define RENESAS_USB_DRIVER_H + +#include +#include + +struct usbhs_priv; + +#include "mod.h" +#include "pipe.h" + +/* + * + * register define + * + */ +#define SYSCFG 0x0000 +#define BUSWAIT 0x0002 +#define DVSTCTR 0x0008 +#define TESTMODE 0x000C +#define CFIFO 0x0014 +#define CFIFOSEL 0x0020 +#define CFIFOCTR 0x0022 +#define D0FIFO 0x0100 +#define D0FIFOSEL 0x0028 +#define D0FIFOCTR 0x002A +#define D1FIFO 0x0120 +#define D1FIFOSEL 0x002C +#define D1FIFOCTR 0x002E +#define INTENB0 0x0030 +#define INTENB1 0x0032 +#define BRDYENB 0x0036 +#define NRDYENB 0x0038 +#define BEMPENB 0x003A +#define INTSTS0 0x0040 +#define INTSTS1 0x0042 +#define BRDYSTS 0x0046 +#define NRDYSTS 0x0048 +#define BEMPSTS 0x004A +#define FRMNUM 0x004C +#define USBREQ 0x0054 /* USB request type register */ +#define USBVAL 0x0056 /* USB request value register */ +#define USBINDX 0x0058 /* USB request index register */ +#define USBLENG 0x005A /* USB request length register */ +#define DCPCFG 0x005C +#define DCPMAXP 0x005E +#define DCPCTR 0x0060 +#define PIPESEL 0x0064 +#define PIPECFG 0x0068 +#define PIPEBUF 0x006A +#define PIPEMAXP 0x006C +#define PIPEPERI 0x006E +#define PIPEnCTR 0x0070 +#define PIPE1TRE 0x0090 +#define PIPE1TRN 0x0092 +#define PIPE2TRE 0x0094 +#define PIPE2TRN 0x0096 +#define PIPE3TRE 0x0098 +#define PIPE3TRN 0x009A +#define PIPE4TRE 0x009C +#define PIPE4TRN 0x009E +#define PIPE5TRE 0x00A0 +#define PIPE5TRN 0x00A2 +#define PIPEBTRE 0x00A4 +#define PIPEBTRN 0x00A6 +#define PIPECTRE 0x00A8 +#define PIPECTRN 0x00AA +#define PIPEDTRE 0x00AC +#define PIPEDTRN 0x00AE +#define PIPEETRE 0x00B0 +#define PIPEETRN 0x00B2 +#define PIPEFTRE 0x00B4 +#define PIPEFTRN 0x00B6 +#define PIPE9TRE 0x00B8 +#define PIPE9TRN 0x00BA +#define PIPEATRE 0x00BC +#define PIPEATRN 0x00BE +#define DEVADD0 0x00D0 /* Device address n configuration */ +#define DEVADD1 0x00D2 +#define DEVADD2 0x00D4 +#define DEVADD3 0x00D6 +#define DEVADD4 0x00D8 +#define DEVADD5 0x00DA +#define DEVADD6 0x00DC +#define DEVADD7 0x00DE +#define DEVADD8 0x00E0 +#define DEVADD9 0x00E2 +#define DEVADDA 0x00E4 + +/* SYSCFG */ +#define SCKE (1 << 10) /* USB Module Clock Enable */ +#define HSE (1 << 7) /* High-Speed Operation Enable */ +#define DCFM (1 << 6) /* Controller Function Select */ +#define DRPD (1 << 5) /* D+ Line/D- Line Resistance Control */ +#define DPRPU (1 << 4) /* D+ Line Resistance Control */ +#define USBE (1 << 0) /* USB Module Operation Enable */ + +/* DVSTCTR */ +#define EXTLP (1 << 10) /* Controls the EXTLP pin output state */ +#define PWEN (1 << 9) /* Controls the PWEN pin output state */ +#define USBRST (1 << 6) /* Bus Reset Output */ +#define UACT (1 << 4) /* USB Bus Enable */ +#define RHST (0x7) /* Reset Handshake */ +#define RHST_LOW_SPEED 1 /* Low-speed connection */ +#define RHST_FULL_SPEED 2 /* Full-speed connection */ +#define RHST_HIGH_SPEED 3 /* High-speed connection */ + +/* CFIFOSEL */ +#define DREQE (1 << 12) /* DMA Transfer Request Enable */ +#define MBW_32 (0x2 << 10) /* CFIFO Port Access Bit Width */ + +/* CFIFOCTR */ +#define BVAL (1 << 15) /* Buffer Memory Enable Flag */ +#define BCLR (1 << 14) /* CPU buffer clear */ +#define FRDY (1 << 13) /* FIFO Port Ready */ +#define DTLN_MASK (0x0FFF) /* Receive Data Length */ + +/* INTENB0 */ +#define VBSE (1 << 15) /* Enable IRQ VBUS_0 and VBUSIN_0 */ +#define RSME (1 << 14) /* Enable IRQ Resume */ +#define SOFE (1 << 13) /* Enable IRQ Frame Number Update */ +#define DVSE (1 << 12) /* Enable IRQ Device State Transition */ +#define CTRE (1 << 11) /* Enable IRQ Control Stage Transition */ +#define BEMPE (1 << 10) /* Enable IRQ Buffer Empty */ +#define NRDYE (1 << 9) /* Enable IRQ Buffer Not Ready Response */ +#define BRDYE (1 << 8) /* Enable IRQ Buffer Ready */ + +/* INTENB1 */ +#define BCHGE (1 << 14) /* USB Bus Change Interrupt Enable */ +#define DTCHE (1 << 12) /* Disconnection Detect Interrupt Enable */ +#define ATTCHE (1 << 11) /* Connection Detect Interrupt Enable */ +#define EOFERRE (1 << 6) /* EOF Error Detect Interrupt Enable */ +#define SIGNE (1 << 5) /* Setup Transaction Error Interrupt Enable */ +#define SACKE (1 << 4) /* Setup Transaction ACK Interrupt Enable */ + +/* INTSTS0 */ +#define VBINT (1 << 15) /* VBUS0_0 and VBUS1_0 Interrupt Status */ +#define DVST (1 << 12) /* Device State Transition Interrupt Status */ +#define CTRT (1 << 11) /* Control Stage Interrupt Status */ +#define BEMP (1 << 10) /* Buffer Empty Interrupt Status */ +#define BRDY (1 << 8) /* Buffer Ready Interrupt Status */ +#define VBSTS (1 << 7) /* VBUS_0 and VBUSIN_0 Input Status */ +#define VALID (1 << 3) /* USB Request Receive */ + +#define DVSQ_MASK (0x3 << 4) /* Device State */ +#define POWER_STATE (0 << 4) +#define DEFAULT_STATE (1 << 4) +#define ADDRESS_STATE (2 << 4) +#define CONFIGURATION_STATE (3 << 4) + +#define CTSQ_MASK (0x7) /* Control Transfer Stage */ +#define IDLE_SETUP_STAGE 0 /* Idle stage or setup stage */ +#define READ_DATA_STAGE 1 /* Control read data stage */ +#define READ_STATUS_STAGE 2 /* Control read status stage */ +#define WRITE_DATA_STAGE 3 /* Control write data stage */ +#define WRITE_STATUS_STAGE 4 /* Control write status stage */ +#define NODATA_STATUS_STAGE 5 /* Control write NoData status stage */ +#define SEQUENCE_ERROR 6 /* Control transfer sequence error */ + +/* INTSTS1 */ +#define OVRCR (1 << 15) /* OVRCR Interrupt Status */ +#define BCHG (1 << 14) /* USB Bus Change Interrupt Status */ +#define DTCH (1 << 12) /* USB Disconnection Detect Interrupt Status */ +#define ATTCH (1 << 11) /* ATTCH Interrupt Status */ +#define EOFERR (1 << 6) /* EOF Error Detect Interrupt Status */ +#define SIGN (1 << 5) /* Setup Transaction Error Interrupt Status */ +#define SACK (1 << 4) /* Setup Transaction ACK Response Interrupt Status */ + +/* PIPECFG */ +/* DCPCFG */ +#define TYPE_NONE (0 << 14) /* Transfer Type */ +#define TYPE_BULK (1 << 14) +#define TYPE_INT (2 << 14) +#define TYPE_ISO (3 << 14) +#define DBLB (1 << 9) /* Double Buffer Mode */ +#define SHTNAK (1 << 7) /* Pipe Disable in Transfer End */ +#define DIR_OUT (1 << 4) /* Transfer Direction */ + +/* PIPEMAXP */ +/* DCPMAXP */ +#define DEVSEL_MASK (0xF << 12) /* Device Select */ +#define DCP_MAXP_MASK (0x7F) +#define PIPE_MAXP_MASK (0x7FF) + +/* PIPEBUF */ +#define BUFSIZE_SHIFT 10 +#define BUFSIZE_MASK (0x1F << BUFSIZE_SHIFT) +#define BUFNMB_MASK (0xFF) + +/* PIPEnCTR */ +/* DCPCTR */ +#define BSTS (1 << 15) /* Buffer Status */ +#define SUREQ (1 << 14) /* Sending SETUP Token */ +#define CSSTS (1 << 12) /* CSSTS Status */ +#define ACLRM (1 << 9) /* Buffer Auto-Clear Mode */ +#define SQCLR (1 << 8) /* Toggle Bit Clear */ +#define SQSET (1 << 7) /* Toggle Bit Set */ +#define PBUSY (1 << 5) /* Pipe Busy */ +#define PID_MASK (0x3) /* Response PID */ +#define PID_NAK 0 +#define PID_BUF 1 +#define PID_STALL10 2 +#define PID_STALL11 3 + +#define CCPL (1 << 2) /* Control Transfer End Enable */ + +/* PIPEnTRE */ +#define TRENB (1 << 9) /* Transaction Counter Enable */ +#define TRCLR (1 << 8) /* Transaction Counter Clear */ + +/* FRMNUM */ +#define FRNM_MASK (0x7FF) + +/* DEVADDn */ +#define UPPHUB(x) (((x) & 0xF) << 11) /* HUB Register */ +#define HUBPORT(x) (((x) & 0x7) << 8) /* HUB Port for Target Device */ +#define USBSPD(x) (((x) & 0x3) << 6) /* Device Transfer Rate */ +#define USBSPD_SPEED_LOW 0x1 +#define USBSPD_SPEED_FULL 0x2 +#define USBSPD_SPEED_HIGH 0x3 + +/* + * struct + */ +struct usbhs_priv { + + void __iomem *base; + unsigned int irq; + unsigned long irqflags; + + struct renesas_usbhs_platform_callback pfunc; + struct renesas_usbhs_driver_param dparam; + + struct delayed_work notify_hotplug_work; + struct platform_device *pdev; + + spinlock_t lock; + + u32 flags; + + /* + * module control + */ + struct usbhs_mod_info mod_info; + + /* + * pipe control + */ + struct usbhs_pipe_info pipe_info; + + /* + * fifo control + */ + struct usbhs_fifo_info fifo_info; +}; + +/* + * common + */ +u16 usbhs_read(struct usbhs_priv *priv, u32 reg); +void usbhs_write(struct usbhs_priv *priv, u32 reg, u16 data); +void usbhs_bset(struct usbhs_priv *priv, u32 reg, u16 mask, u16 data); + +#define usbhs_lock(p, f) spin_lock_irqsave(usbhs_priv_to_lock(p), f) +#define usbhs_unlock(p, f) spin_unlock_irqrestore(usbhs_priv_to_lock(p), f) + +/* + * sysconfig + */ +void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable); +void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable); +void usbhs_sys_function_pullup(struct usbhs_priv *priv, int enable); +void usbhs_sys_set_test_mode(struct usbhs_priv *priv, u16 mode); + +/* + * usb request + */ +void usbhs_usbreq_get_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req); +void usbhs_usbreq_set_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req); + +/* + * bus + */ +void usbhs_bus_send_sof_enable(struct usbhs_priv *priv); +void usbhs_bus_send_reset(struct usbhs_priv *priv); +int usbhs_bus_get_speed(struct usbhs_priv *priv); +int usbhs_vbus_ctrl(struct usbhs_priv *priv, int enable); + +/* + * frame + */ +int usbhs_frame_get_num(struct usbhs_priv *priv); + +/* + * device config + */ +int usbhs_set_device_config(struct usbhs_priv *priv, int devnum, u16 upphub, + u16 hubport, u16 speed); + +/* + * data + */ +struct usbhs_priv *usbhs_pdev_to_priv(struct platform_device *pdev); +#define usbhs_get_dparam(priv, param) (priv->dparam.param) +#define usbhs_priv_to_pdev(priv) (priv->pdev) +#define usbhs_priv_to_dev(priv) (&priv->pdev->dev) +#define usbhs_priv_to_lock(priv) (&priv->lock) + +#endif /* RENESAS_USB_DRIVER_H */ diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c new file mode 100644 index 00000000..45b94019 --- /dev/null +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -0,0 +1,1189 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include "common.h" +#include "pipe.h" + +#define usbhsf_get_cfifo(p) (&((p)->fifo_info.cfifo)) +#define usbhsf_get_d0fifo(p) (&((p)->fifo_info.d0fifo)) +#define usbhsf_get_d1fifo(p) (&((p)->fifo_info.d1fifo)) +#define usbhsf_is_cfifo(p, f) (usbhsf_get_cfifo(p) == f) + +#define usbhsf_fifo_is_busy(f) ((f)->pipe) /* see usbhs_pipe_select_fifo */ + +/* + * packet initialize + */ +void usbhs_pkt_init(struct usbhs_pkt *pkt) +{ + INIT_LIST_HEAD(&pkt->node); +} + +/* + * packet control function + */ +static int usbhsf_null_handle(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pkt->pipe); + struct device *dev = usbhs_priv_to_dev(priv); + + dev_err(dev, "null handler\n"); + + return -EINVAL; +} + +static struct usbhs_pkt_handle usbhsf_null_handler = { + .prepare = usbhsf_null_handle, + .try_run = usbhsf_null_handle, +}; + +void usbhs_pkt_push(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt, + void (*done)(struct usbhs_priv *priv, + struct usbhs_pkt *pkt), + void *buf, int len, int zero, int sequence) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + unsigned long flags; + + if (!done) { + dev_err(dev, "no done function\n"); + return; + } + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + if (!pipe->handler) { + dev_err(dev, "no handler function\n"); + pipe->handler = &usbhsf_null_handler; + } + + list_move_tail(&pkt->node, &pipe->list); + + /* + * each pkt must hold own handler. + * because handler might be changed by its situation. + * dma handler -> pio handler. + */ + pkt->pipe = pipe; + pkt->buf = buf; + pkt->handler = pipe->handler; + pkt->length = len; + pkt->zero = zero; + pkt->actual = 0; + pkt->done = done; + pkt->sequence = sequence; + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ +} + +static void __usbhsf_pkt_del(struct usbhs_pkt *pkt) +{ + list_del_init(&pkt->node); +} + +static struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe) +{ + if (list_empty(&pipe->list)) + return NULL; + + return list_first_entry(&pipe->list, struct usbhs_pkt, node); +} + +struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + unsigned long flags; + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + if (!pkt) + pkt = __usbhsf_pkt_get(pipe); + + if (pkt) + __usbhsf_pkt_del(pkt); + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + return pkt; +} + +enum { + USBHSF_PKT_PREPARE, + USBHSF_PKT_TRY_RUN, + USBHSF_PKT_DMA_DONE, +}; + +static int usbhsf_pkt_handler(struct usbhs_pipe *pipe, int type) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_pkt *pkt; + struct device *dev = usbhs_priv_to_dev(priv); + int (*func)(struct usbhs_pkt *pkt, int *is_done); + unsigned long flags; + int ret = 0; + int is_done = 0; + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + pkt = __usbhsf_pkt_get(pipe); + if (!pkt) + goto __usbhs_pkt_handler_end; + + switch (type) { + case USBHSF_PKT_PREPARE: + func = pkt->handler->prepare; + break; + case USBHSF_PKT_TRY_RUN: + func = pkt->handler->try_run; + break; + case USBHSF_PKT_DMA_DONE: + func = pkt->handler->dma_done; + break; + default: + dev_err(dev, "unknown pkt handler\n"); + goto __usbhs_pkt_handler_end; + } + + ret = func(pkt, &is_done); + + if (is_done) + __usbhsf_pkt_del(pkt); + +__usbhs_pkt_handler_end: + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + if (is_done) { + pkt->done(priv, pkt); + usbhs_pkt_start(pipe); + } + + return ret; +} + +void usbhs_pkt_start(struct usbhs_pipe *pipe) +{ + usbhsf_pkt_handler(pipe, USBHSF_PKT_PREPARE); +} + +/* + * irq enable/disable function + */ +#define usbhsf_irq_empty_ctrl(p, e) usbhsf_irq_callback_ctrl(p, irq_bempsts, e) +#define usbhsf_irq_ready_ctrl(p, e) usbhsf_irq_callback_ctrl(p, irq_brdysts, e) +#define usbhsf_irq_callback_ctrl(pipe, status, enable) \ + ({ \ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); \ + struct usbhs_mod *mod = usbhs_mod_get_current(priv); \ + u16 status = (1 << usbhs_pipe_number(pipe)); \ + if (!mod) \ + return; \ + if (enable) \ + mod->status |= status; \ + else \ + mod->status &= ~status; \ + usbhs_irq_callback_update(priv, mod); \ + }) + +static void usbhsf_tx_irq_ctrl(struct usbhs_pipe *pipe, int enable) +{ + /* + * And DCP pipe can NOT use "ready interrupt" for "send" + * it should use "empty" interrupt. + * see + * "Operation" - "Interrupt Function" - "BRDY Interrupt" + * + * on the other hand, normal pipe can use "ready interrupt" for "send" + * even though it is single/double buffer + */ + if (usbhs_pipe_is_dcp(pipe)) + usbhsf_irq_empty_ctrl(pipe, enable); + else + usbhsf_irq_ready_ctrl(pipe, enable); +} + +static void usbhsf_rx_irq_ctrl(struct usbhs_pipe *pipe, int enable) +{ + usbhsf_irq_ready_ctrl(pipe, enable); +} + +/* + * FIFO ctrl + */ +static void usbhsf_send_terminator(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + + usbhs_bset(priv, fifo->ctr, BVAL, BVAL); +} + +static int usbhsf_fifo_barrier(struct usbhs_priv *priv, + struct usbhs_fifo *fifo) +{ + int timeout = 1024; + + do { + /* The FIFO port is accessible */ + if (usbhs_read(priv, fifo->ctr) & FRDY) + return 0; + + udelay(10); + } while (timeout--); + + return -EBUSY; +} + +static void usbhsf_fifo_clear(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + + if (!usbhs_pipe_is_dcp(pipe)) + usbhsf_fifo_barrier(priv, fifo); + + usbhs_write(priv, fifo->ctr, BCLR); +} + +static int usbhsf_fifo_rcv_len(struct usbhs_priv *priv, + struct usbhs_fifo *fifo) +{ + return usbhs_read(priv, fifo->ctr) & DTLN_MASK; +} + +static void usbhsf_fifo_unselect(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + + usbhs_pipe_select_fifo(pipe, NULL); + usbhs_write(priv, fifo->sel, 0); +} + +static int usbhsf_fifo_select(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo, + int write) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + int timeout = 1024; + u16 mask = ((1 << 5) | 0xF); /* mask of ISEL | CURPIPE */ + u16 base = usbhs_pipe_number(pipe); /* CURPIPE */ + + if (usbhs_pipe_is_busy(pipe) || + usbhsf_fifo_is_busy(fifo)) + return -EBUSY; + + if (usbhs_pipe_is_dcp(pipe)) { + base |= (1 == write) << 5; /* ISEL */ + + if (usbhs_mod_is_host(priv)) + usbhs_dcp_dir_for_host(pipe, write); + } + + /* "base" will be used below */ + if (usbhs_get_dparam(priv, has_sudmac) && !usbhsf_is_cfifo(priv, fifo)) + usbhs_write(priv, fifo->sel, base); + else + usbhs_write(priv, fifo->sel, base | MBW_32); + + /* check ISEL and CURPIPE value */ + while (timeout--) { + if (base == (mask & usbhs_read(priv, fifo->sel))) { + usbhs_pipe_select_fifo(pipe, fifo); + return 0; + } + udelay(10); + } + + dev_err(dev, "fifo select error\n"); + + return -EIO; +} + +/* + * DCP status stage + */ +static int usbhs_dcp_dir_switch_to_write(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ + struct device *dev = usbhs_priv_to_dev(priv); + int ret; + + usbhs_pipe_disable(pipe); + + ret = usbhsf_fifo_select(pipe, fifo, 1); + if (ret < 0) { + dev_err(dev, "%s() faile\n", __func__); + return ret; + } + + usbhs_pipe_sequence_data1(pipe); /* DATA1 */ + + usbhsf_fifo_clear(pipe, fifo); + usbhsf_send_terminator(pipe, fifo); + + usbhsf_fifo_unselect(pipe, fifo); + + usbhsf_tx_irq_ctrl(pipe, 1); + usbhs_pipe_enable(pipe); + + return ret; +} + +static int usbhs_dcp_dir_switch_to_read(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ + struct device *dev = usbhs_priv_to_dev(priv); + int ret; + + usbhs_pipe_disable(pipe); + + ret = usbhsf_fifo_select(pipe, fifo, 0); + if (ret < 0) { + dev_err(dev, "%s() fail\n", __func__); + return ret; + } + + usbhs_pipe_sequence_data1(pipe); /* DATA1 */ + usbhsf_fifo_clear(pipe, fifo); + + usbhsf_fifo_unselect(pipe, fifo); + + usbhsf_rx_irq_ctrl(pipe, 1); + usbhs_pipe_enable(pipe); + + return ret; + +} + +static int usbhs_dcp_dir_switch_done(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + + if (pkt->handler == &usbhs_dcp_status_stage_in_handler) + usbhsf_tx_irq_ctrl(pipe, 0); + else + usbhsf_rx_irq_ctrl(pipe, 0); + + pkt->actual = pkt->length; + *is_done = 1; + + return 0; +} + +struct usbhs_pkt_handle usbhs_dcp_status_stage_in_handler = { + .prepare = usbhs_dcp_dir_switch_to_write, + .try_run = usbhs_dcp_dir_switch_done, +}; + +struct usbhs_pkt_handle usbhs_dcp_status_stage_out_handler = { + .prepare = usbhs_dcp_dir_switch_to_read, + .try_run = usbhs_dcp_dir_switch_done, +}; + +/* + * DCP data stage (push) + */ +static int usbhsf_dcp_data_stage_try_push(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + + usbhs_pipe_sequence_data1(pipe); /* DATA1 */ + + /* + * change handler to PIO push + */ + pkt->handler = &usbhs_fifo_pio_push_handler; + + return pkt->handler->prepare(pkt, is_done); +} + +struct usbhs_pkt_handle usbhs_dcp_data_stage_out_handler = { + .prepare = usbhsf_dcp_data_stage_try_push, +}; + +/* + * DCP data stage (pop) + */ +static int usbhsf_dcp_data_stage_prepare_pop(struct usbhs_pkt *pkt, + int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); + + if (usbhs_pipe_is_busy(pipe)) + return 0; + + /* + * prepare pop for DCP should + * - change DCP direction, + * - clear fifo + * - DATA1 + */ + usbhs_pipe_disable(pipe); + + usbhs_pipe_sequence_data1(pipe); /* DATA1 */ + + usbhsf_fifo_select(pipe, fifo, 0); + usbhsf_fifo_clear(pipe, fifo); + usbhsf_fifo_unselect(pipe, fifo); + + /* + * change handler to PIO pop + */ + pkt->handler = &usbhs_fifo_pio_pop_handler; + + return pkt->handler->prepare(pkt, is_done); +} + +struct usbhs_pkt_handle usbhs_dcp_data_stage_in_handler = { + .prepare = usbhsf_dcp_data_stage_prepare_pop, +}; + +/* + * PIO push handler + */ +static int usbhsf_pio_try_push(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ + void __iomem *addr = priv->base + fifo->port; + u8 *buf; + int maxp = usbhs_pipe_get_maxpacket(pipe); + int total_len; + int i, ret, len; + int is_short; + + usbhs_pipe_data_sequence(pipe, pkt->sequence); + pkt->sequence = -1; /* -1 sequence will be ignored */ + + usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->length); + + ret = usbhsf_fifo_select(pipe, fifo, 1); + if (ret < 0) + return 0; + + ret = usbhs_pipe_is_accessible(pipe); + if (ret < 0) { + /* inaccessible pipe is not an error */ + ret = 0; + goto usbhs_fifo_write_busy; + } + + ret = usbhsf_fifo_barrier(priv, fifo); + if (ret < 0) + goto usbhs_fifo_write_busy; + + buf = pkt->buf + pkt->actual; + len = pkt->length - pkt->actual; + len = min(len, maxp); + total_len = len; + is_short = total_len < maxp; + + /* + * FIXME + * + * 32-bit access only + */ + if (len >= 4 && !((unsigned long)buf & 0x03)) { + iowrite32_rep(addr, buf, len / 4); + len %= 4; + buf += total_len - len; + } + + /* the rest operation */ + for (i = 0; i < len; i++) + iowrite8(buf[i], addr + (0x03 - (i & 0x03))); + + /* + * variable update + */ + pkt->actual += total_len; + + if (pkt->actual < pkt->length) + *is_done = 0; /* there are remainder data */ + else if (is_short) + *is_done = 1; /* short packet */ + else + *is_done = !pkt->zero; /* send zero packet ? */ + + /* + * pipe/irq handling + */ + if (is_short) + usbhsf_send_terminator(pipe, fifo); + + usbhsf_tx_irq_ctrl(pipe, !*is_done); + usbhs_pipe_enable(pipe); + + dev_dbg(dev, " send %d (%d/ %d/ %d/ %d)\n", + usbhs_pipe_number(pipe), + pkt->length, pkt->actual, *is_done, pkt->zero); + + /* + * Transmission end + */ + if (*is_done) { + if (usbhs_pipe_is_dcp(pipe)) + usbhs_dcp_control_transfer_done(pipe); + } + + usbhsf_fifo_unselect(pipe, fifo); + + return 0; + +usbhs_fifo_write_busy: + usbhsf_fifo_unselect(pipe, fifo); + + /* + * pipe is busy. + * retry in interrupt + */ + usbhsf_tx_irq_ctrl(pipe, 1); + + return ret; +} + +struct usbhs_pkt_handle usbhs_fifo_pio_push_handler = { + .prepare = usbhsf_pio_try_push, + .try_run = usbhsf_pio_try_push, +}; + +/* + * PIO pop handler + */ +static int usbhsf_prepare_pop(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + + if (usbhs_pipe_is_busy(pipe)) + return 0; + + /* + * pipe enable to prepare packet receive + */ + usbhs_pipe_data_sequence(pipe, pkt->sequence); + pkt->sequence = -1; /* -1 sequence will be ignored */ + + usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->length); + usbhs_pipe_enable(pipe); + usbhsf_rx_irq_ctrl(pipe, 1); + + return 0; +} + +static int usbhsf_pio_try_pop(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ + void __iomem *addr = priv->base + fifo->port; + u8 *buf; + u32 data = 0; + int maxp = usbhs_pipe_get_maxpacket(pipe); + int rcv_len, len; + int i, ret; + int total_len = 0; + + ret = usbhsf_fifo_select(pipe, fifo, 0); + if (ret < 0) + return 0; + + ret = usbhsf_fifo_barrier(priv, fifo); + if (ret < 0) + goto usbhs_fifo_read_busy; + + rcv_len = usbhsf_fifo_rcv_len(priv, fifo); + + buf = pkt->buf + pkt->actual; + len = pkt->length - pkt->actual; + len = min(len, rcv_len); + total_len = len; + + /* + * update actual length first here to decide disable pipe. + * if this pipe keeps BUF status and all data were popped, + * then, next interrupt/token will be issued again + */ + pkt->actual += total_len; + + if ((pkt->actual == pkt->length) || /* receive all data */ + (total_len < maxp)) { /* short packet */ + *is_done = 1; + usbhsf_rx_irq_ctrl(pipe, 0); + usbhs_pipe_disable(pipe); /* disable pipe first */ + } + + /* + * Buffer clear if Zero-Length packet + * + * see + * "Operation" - "FIFO Buffer Memory" - "FIFO Port Function" + */ + if (0 == rcv_len) { + pkt->zero = 1; + usbhsf_fifo_clear(pipe, fifo); + goto usbhs_fifo_read_end; + } + + /* + * FIXME + * + * 32-bit access only + */ + if (len >= 4 && !((unsigned long)buf & 0x03)) { + ioread32_rep(addr, buf, len / 4); + len %= 4; + buf += total_len - len; + } + + /* the rest operation */ + for (i = 0; i < len; i++) { + if (!(i & 0x03)) + data = ioread32(addr); + + buf[i] = (data >> ((i & 0x03) * 8)) & 0xff; + } + +usbhs_fifo_read_end: + dev_dbg(dev, " recv %d (%d/ %d/ %d/ %d)\n", + usbhs_pipe_number(pipe), + pkt->length, pkt->actual, *is_done, pkt->zero); + +usbhs_fifo_read_busy: + usbhsf_fifo_unselect(pipe, fifo); + + return ret; +} + +struct usbhs_pkt_handle usbhs_fifo_pio_pop_handler = { + .prepare = usbhsf_prepare_pop, + .try_run = usbhsf_pio_try_pop, +}; + +/* + * DCP ctrol statge handler + */ +static int usbhsf_ctrl_stage_end(struct usbhs_pkt *pkt, int *is_done) +{ + usbhs_dcp_control_transfer_done(pkt->pipe); + + *is_done = 1; + + return 0; +} + +struct usbhs_pkt_handle usbhs_ctrl_stage_end_handler = { + .prepare = usbhsf_ctrl_stage_end, + .try_run = usbhsf_ctrl_stage_end, +}; + +/* + * DMA fifo functions + */ +static struct dma_chan *usbhsf_dma_chan_get(struct usbhs_fifo *fifo, + struct usbhs_pkt *pkt) +{ + if (&usbhs_fifo_dma_push_handler == pkt->handler) + return fifo->tx_chan; + + if (&usbhs_fifo_dma_pop_handler == pkt->handler) + return fifo->rx_chan; + + return NULL; +} + +static struct usbhs_fifo *usbhsf_get_dma_fifo(struct usbhs_priv *priv, + struct usbhs_pkt *pkt) +{ + struct usbhs_fifo *fifo; + + /* DMA :: D0FIFO */ + fifo = usbhsf_get_d0fifo(priv); + if (usbhsf_dma_chan_get(fifo, pkt) && + !usbhsf_fifo_is_busy(fifo)) + return fifo; + + /* DMA :: D1FIFO */ + fifo = usbhsf_get_d1fifo(priv); + if (usbhsf_dma_chan_get(fifo, pkt) && + !usbhsf_fifo_is_busy(fifo)) + return fifo; + + return NULL; +} + +#define usbhsf_dma_start(p, f) __usbhsf_dma_ctrl(p, f, DREQE) +#define usbhsf_dma_stop(p, f) __usbhsf_dma_ctrl(p, f, 0) +static void __usbhsf_dma_ctrl(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo, + u16 dreqe) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + + usbhs_bset(priv, fifo->sel, DREQE, dreqe); +} + +#define usbhsf_dma_map(p) __usbhsf_dma_map_ctrl(p, 1) +#define usbhsf_dma_unmap(p) __usbhsf_dma_map_ctrl(p, 0) +static int __usbhsf_dma_map_ctrl(struct usbhs_pkt *pkt, int map) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); + + return info->dma_map_ctrl(pkt, map); +} + +static void usbhsf_dma_complete(void *arg); +static void xfer_work(struct work_struct *work) +{ + struct usbhs_pkt *pkt = container_of(work, struct usbhs_pkt, work); + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_fifo *fifo = usbhs_pipe_to_fifo(pipe); + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct dma_async_tx_descriptor *desc; + struct dma_chan *chan = usbhsf_dma_chan_get(fifo, pkt); + struct device *dev = usbhs_priv_to_dev(priv); + enum dma_transfer_direction dir; + + dir = usbhs_pipe_is_dir_in(pipe) ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; + + desc = dmaengine_prep_slave_single(chan, pkt->dma + pkt->actual, + pkt->trans, dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + return; + + desc->callback = usbhsf_dma_complete; + desc->callback_param = pipe; + + if (dmaengine_submit(desc) < 0) { + dev_err(dev, "Failed to submit dma descriptor\n"); + return; + } + + dev_dbg(dev, " %s %d (%d/ %d)\n", + fifo->name, usbhs_pipe_number(pipe), pkt->length, pkt->zero); + + usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->trans); + usbhs_pipe_enable(pipe); + usbhsf_dma_start(pipe, fifo); + dma_async_issue_pending(chan); +} + +/* + * DMA push handler + */ +static int usbhsf_dma_prepare_push(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_fifo *fifo; + int len = pkt->length - pkt->actual; + int ret; + + if (usbhs_pipe_is_busy(pipe)) + return 0; + + /* use PIO if packet is less than pio_dma_border or pipe is DCP */ + if ((len < usbhs_get_dparam(priv, pio_dma_border)) || + usbhs_pipe_is_dcp(pipe)) + goto usbhsf_pio_prepare_push; + + if (len & 0x7) /* 8byte alignment */ + goto usbhsf_pio_prepare_push; + + if ((uintptr_t)(pkt->buf + pkt->actual) & 0x7) /* 8byte alignment */ + goto usbhsf_pio_prepare_push; + + /* get enable DMA fifo */ + fifo = usbhsf_get_dma_fifo(priv, pkt); + if (!fifo) + goto usbhsf_pio_prepare_push; + + if (usbhsf_dma_map(pkt) < 0) + goto usbhsf_pio_prepare_push; + + ret = usbhsf_fifo_select(pipe, fifo, 0); + if (ret < 0) + goto usbhsf_pio_prepare_push_unmap; + + pkt->trans = len; + + INIT_WORK(&pkt->work, xfer_work); + schedule_work(&pkt->work); + + return 0; + +usbhsf_pio_prepare_push_unmap: + usbhsf_dma_unmap(pkt); +usbhsf_pio_prepare_push: + /* + * change handler to PIO + */ + pkt->handler = &usbhs_fifo_pio_push_handler; + + return pkt->handler->prepare(pkt, is_done); +} + +static int usbhsf_dma_push_done(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + + pkt->actual = pkt->trans; + + *is_done = !pkt->zero; /* send zero packet ? */ + + usbhsf_dma_stop(pipe, pipe->fifo); + usbhsf_dma_unmap(pkt); + usbhsf_fifo_unselect(pipe, pipe->fifo); + + return 0; +} + +struct usbhs_pkt_handle usbhs_fifo_dma_push_handler = { + .prepare = usbhsf_dma_prepare_push, + .dma_done = usbhsf_dma_push_done, +}; + +/* + * DMA pop handler + */ +static int usbhsf_dma_try_pop(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_fifo *fifo; + int len, ret; + + if (usbhs_pipe_is_busy(pipe)) + return 0; + + if (usbhs_pipe_is_dcp(pipe)) + goto usbhsf_pio_prepare_pop; + + /* get enable DMA fifo */ + fifo = usbhsf_get_dma_fifo(priv, pkt); + if (!fifo) + goto usbhsf_pio_prepare_pop; + + if ((uintptr_t)(pkt->buf + pkt->actual) & 0x7) /* 8byte alignment */ + goto usbhsf_pio_prepare_pop; + + ret = usbhsf_fifo_select(pipe, fifo, 0); + if (ret < 0) + goto usbhsf_pio_prepare_pop; + + /* use PIO if packet is less than pio_dma_border */ + len = usbhsf_fifo_rcv_len(priv, fifo); + len = min(pkt->length - pkt->actual, len); + if (len & 0x7) /* 8byte alignment */ + goto usbhsf_pio_prepare_pop_unselect; + + if (len < usbhs_get_dparam(priv, pio_dma_border)) + goto usbhsf_pio_prepare_pop_unselect; + + ret = usbhsf_fifo_barrier(priv, fifo); + if (ret < 0) + goto usbhsf_pio_prepare_pop_unselect; + + if (usbhsf_dma_map(pkt) < 0) + goto usbhsf_pio_prepare_pop_unselect; + + /* DMA */ + + /* + * usbhs_fifo_dma_pop_handler :: prepare + * enabled irq to come here. + * but it is no longer needed for DMA. disable it. + */ + usbhsf_rx_irq_ctrl(pipe, 0); + + pkt->trans = len; + + INIT_WORK(&pkt->work, xfer_work); + schedule_work(&pkt->work); + + return 0; + +usbhsf_pio_prepare_pop_unselect: + usbhsf_fifo_unselect(pipe, fifo); +usbhsf_pio_prepare_pop: + + /* + * change handler to PIO + */ + pkt->handler = &usbhs_fifo_pio_pop_handler; + + return pkt->handler->try_run(pkt, is_done); +} + +static int usbhsf_dma_pop_done(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_pipe *pipe = pkt->pipe; + int maxp = usbhs_pipe_get_maxpacket(pipe); + + usbhsf_dma_stop(pipe, pipe->fifo); + usbhsf_dma_unmap(pkt); + usbhsf_fifo_unselect(pipe, pipe->fifo); + + pkt->actual += pkt->trans; + + if ((pkt->actual == pkt->length) || /* receive all data */ + (pkt->trans < maxp)) { /* short packet */ + *is_done = 1; + } else { + /* re-enable */ + usbhsf_prepare_pop(pkt, is_done); + } + + return 0; +} + +struct usbhs_pkt_handle usbhs_fifo_dma_pop_handler = { + .prepare = usbhsf_prepare_pop, + .try_run = usbhsf_dma_try_pop, + .dma_done = usbhsf_dma_pop_done +}; + +/* + * DMA setting + */ +static bool usbhsf_dma_filter(struct dma_chan *chan, void *param) +{ + struct sh_dmae_slave *slave = param; + + /* + * FIXME + * + * usbhs doesn't recognize id = 0 as valid DMA + */ + if (0 == slave->shdma_slave.slave_id) + return false; + + chan->private = slave; + + return true; +} + +static void usbhsf_dma_quit(struct usbhs_priv *priv, struct usbhs_fifo *fifo) +{ + if (fifo->tx_chan) + dma_release_channel(fifo->tx_chan); + if (fifo->rx_chan) + dma_release_channel(fifo->rx_chan); + + fifo->tx_chan = NULL; + fifo->rx_chan = NULL; +} + +static void usbhsf_dma_init(struct usbhs_priv *priv, + struct usbhs_fifo *fifo) +{ + struct device *dev = usbhs_priv_to_dev(priv); + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + fifo->tx_chan = dma_request_channel(mask, usbhsf_dma_filter, + &fifo->tx_slave); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + fifo->rx_chan = dma_request_channel(mask, usbhsf_dma_filter, + &fifo->rx_slave); + + if (fifo->tx_chan || fifo->rx_chan) + dev_dbg(dev, "enable DMAEngine (%s%s%s)\n", + fifo->name, + fifo->tx_chan ? "[TX]" : " ", + fifo->rx_chan ? "[RX]" : " "); +} + +/* + * irq functions + */ +static int usbhsf_irq_empty(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhs_pipe *pipe; + struct device *dev = usbhs_priv_to_dev(priv); + int i, ret; + + if (!irq_state->bempsts) { + dev_err(dev, "debug %s !!\n", __func__); + return -EIO; + } + + dev_dbg(dev, "irq empty [0x%04x]\n", irq_state->bempsts); + + /* + * search interrupted "pipe" + * not "uep". + */ + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + if (!(irq_state->bempsts & (1 << i))) + continue; + + ret = usbhsf_pkt_handler(pipe, USBHSF_PKT_TRY_RUN); + if (ret < 0) + dev_err(dev, "irq_empty run_error %d : %d\n", i, ret); + } + + return 0; +} + +static int usbhsf_irq_ready(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhs_pipe *pipe; + struct device *dev = usbhs_priv_to_dev(priv); + int i, ret; + + if (!irq_state->brdysts) { + dev_err(dev, "debug %s !!\n", __func__); + return -EIO; + } + + dev_dbg(dev, "irq ready [0x%04x]\n", irq_state->brdysts); + + /* + * search interrupted "pipe" + * not "uep". + */ + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + if (!(irq_state->brdysts & (1 << i))) + continue; + + ret = usbhsf_pkt_handler(pipe, USBHSF_PKT_TRY_RUN); + if (ret < 0) + dev_err(dev, "irq_ready run_error %d : %d\n", i, ret); + } + + return 0; +} + +static void usbhsf_dma_complete(void *arg) +{ + struct usbhs_pipe *pipe = arg; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + int ret; + + ret = usbhsf_pkt_handler(pipe, USBHSF_PKT_DMA_DONE); + if (ret < 0) + dev_err(dev, "dma_complete run_error %d : %d\n", + usbhs_pipe_number(pipe), ret); +} + +/* + * fifo init + */ +void usbhs_fifo_init(struct usbhs_priv *priv) +{ + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct usbhs_fifo *cfifo = usbhsf_get_cfifo(priv); + struct usbhs_fifo *d0fifo = usbhsf_get_d0fifo(priv); + struct usbhs_fifo *d1fifo = usbhsf_get_d1fifo(priv); + + mod->irq_empty = usbhsf_irq_empty; + mod->irq_ready = usbhsf_irq_ready; + mod->irq_bempsts = 0; + mod->irq_brdysts = 0; + + cfifo->pipe = NULL; + cfifo->tx_chan = NULL; + cfifo->rx_chan = NULL; + + d0fifo->pipe = NULL; + d0fifo->tx_chan = NULL; + d0fifo->rx_chan = NULL; + + d1fifo->pipe = NULL; + d1fifo->tx_chan = NULL; + d1fifo->rx_chan = NULL; + + usbhsf_dma_init(priv, usbhsf_get_d0fifo(priv)); + usbhsf_dma_init(priv, usbhsf_get_d1fifo(priv)); +} + +void usbhs_fifo_quit(struct usbhs_priv *priv) +{ + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + + mod->irq_empty = NULL; + mod->irq_ready = NULL; + mod->irq_bempsts = 0; + mod->irq_brdysts = 0; + + usbhsf_dma_quit(priv, usbhsf_get_d0fifo(priv)); + usbhsf_dma_quit(priv, usbhsf_get_d1fifo(priv)); +} + +int usbhs_fifo_probe(struct usbhs_priv *priv) +{ + struct usbhs_fifo *fifo; + + /* CFIFO */ + fifo = usbhsf_get_cfifo(priv); + fifo->name = "CFIFO"; + fifo->port = CFIFO; + fifo->sel = CFIFOSEL; + fifo->ctr = CFIFOCTR; + + /* D0FIFO */ + fifo = usbhsf_get_d0fifo(priv); + fifo->name = "D0FIFO"; + fifo->port = D0FIFO; + fifo->sel = D0FIFOSEL; + fifo->ctr = D0FIFOCTR; + fifo->tx_slave.shdma_slave.slave_id = usbhs_get_dparam(priv, d0_tx_id); + fifo->rx_slave.shdma_slave.slave_id = usbhs_get_dparam(priv, d0_rx_id); + + /* D1FIFO */ + fifo = usbhsf_get_d1fifo(priv); + fifo->name = "D1FIFO"; + fifo->port = D1FIFO; + fifo->sel = D1FIFOSEL; + fifo->ctr = D1FIFOCTR; + fifo->tx_slave.shdma_slave.slave_id = usbhs_get_dparam(priv, d1_tx_id); + fifo->rx_slave.shdma_slave.slave_id = usbhs_get_dparam(priv, d1_rx_id); + + return 0; +} + +void usbhs_fifo_remove(struct usbhs_priv *priv) +{ +} diff --git a/drivers/usb/renesas_usbhs/fifo.h b/drivers/usb/renesas_usbhs/fifo.h new file mode 100644 index 00000000..a168a176 --- /dev/null +++ b/drivers/usb/renesas_usbhs/fifo.h @@ -0,0 +1,102 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef RENESAS_USB_FIFO_H +#define RENESAS_USB_FIFO_H + +#include +#include +#include +#include +#include "pipe.h" + +struct usbhs_fifo { + char *name; + u32 port; /* xFIFO */ + u32 sel; /* xFIFOSEL */ + u32 ctr; /* xFIFOCTR */ + + struct usbhs_pipe *pipe; + + struct dma_chan *tx_chan; + struct dma_chan *rx_chan; + + struct sh_dmae_slave tx_slave; + struct sh_dmae_slave rx_slave; +}; + +struct usbhs_fifo_info { + struct usbhs_fifo cfifo; + struct usbhs_fifo d0fifo; + struct usbhs_fifo d1fifo; +}; + +struct usbhs_pkt_handle; +struct usbhs_pkt { + struct list_head node; + struct usbhs_pipe *pipe; + struct usbhs_pkt_handle *handler; + void (*done)(struct usbhs_priv *priv, + struct usbhs_pkt *pkt); + struct work_struct work; + dma_addr_t dma; + void *buf; + int length; + int trans; + int actual; + int zero; + int sequence; +}; + +struct usbhs_pkt_handle { + int (*prepare)(struct usbhs_pkt *pkt, int *is_done); + int (*try_run)(struct usbhs_pkt *pkt, int *is_done); + int (*dma_done)(struct usbhs_pkt *pkt, int *is_done); +}; + +/* + * fifo + */ +int usbhs_fifo_probe(struct usbhs_priv *priv); +void usbhs_fifo_remove(struct usbhs_priv *priv); +void usbhs_fifo_init(struct usbhs_priv *priv); +void usbhs_fifo_quit(struct usbhs_priv *priv); + +/* + * packet info + */ +extern struct usbhs_pkt_handle usbhs_fifo_pio_push_handler; +extern struct usbhs_pkt_handle usbhs_fifo_pio_pop_handler; +extern struct usbhs_pkt_handle usbhs_ctrl_stage_end_handler; + +extern struct usbhs_pkt_handle usbhs_fifo_dma_push_handler; +extern struct usbhs_pkt_handle usbhs_fifo_dma_pop_handler; + +extern struct usbhs_pkt_handle usbhs_dcp_status_stage_in_handler; +extern struct usbhs_pkt_handle usbhs_dcp_status_stage_out_handler; + +extern struct usbhs_pkt_handle usbhs_dcp_data_stage_in_handler; +extern struct usbhs_pkt_handle usbhs_dcp_data_stage_out_handler; + +void usbhs_pkt_init(struct usbhs_pkt *pkt); +void usbhs_pkt_push(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt, + void (*done)(struct usbhs_priv *priv, + struct usbhs_pkt *pkt), + void *buf, int len, int zero, int sequence); +struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt); +void usbhs_pkt_start(struct usbhs_pipe *pipe); + +#endif /* RENESAS_USB_FIFO_H */ diff --git a/drivers/usb/renesas_usbhs/mod.c b/drivers/usb/renesas_usbhs/mod.c new file mode 100644 index 00000000..6a030b93 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod.c @@ -0,0 +1,387 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include + +#include "common.h" +#include "mod.h" + +#define usbhs_priv_to_modinfo(priv) (&priv->mod_info) +#define usbhs_mod_info_call(priv, func, param...) \ +({ \ + struct usbhs_mod_info *info; \ + info = usbhs_priv_to_modinfo(priv); \ + !info->func ? 0 : \ + info->func(param); \ +}) + +/* + * autonomy + * + * these functions are used if platform doesn't have external phy. + * -> there is no "notify_hotplug" callback from platform + * -> call "notify_hotplug" by itself + * -> use own interrupt to connect/disconnect + * -> it mean module clock is always ON + * ~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +static int usbhsm_autonomy_get_vbus(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + + return VBSTS & usbhs_read(priv, INTSTS0); +} + +static int usbhsm_autonomy_irq_vbus(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + + renesas_usbhs_call_notify_hotplug(pdev); + + return 0; +} + +void usbhs_mod_autonomy_mode(struct usbhs_priv *priv) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + info->irq_vbus = usbhsm_autonomy_irq_vbus; + priv->pfunc.get_vbus = usbhsm_autonomy_get_vbus; + + usbhs_irq_callback_update(priv, NULL); +} + +/* + * host / gadget functions + * + * renesas_usbhs host/gadget can register itself by below functions. + * these functions are called when probe + * + */ +void usbhs_mod_register(struct usbhs_priv *priv, struct usbhs_mod *mod, int id) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + info->mod[id] = mod; + mod->priv = priv; +} + +struct usbhs_mod *usbhs_mod_get(struct usbhs_priv *priv, int id) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + struct usbhs_mod *ret = NULL; + + switch (id) { + case USBHS_HOST: + case USBHS_GADGET: + ret = info->mod[id]; + break; + } + + return ret; +} + +int usbhs_mod_is_host(struct usbhs_priv *priv) +{ + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + if (!mod) + return -EINVAL; + + return info->mod[USBHS_HOST] == mod; +} + +struct usbhs_mod *usbhs_mod_get_current(struct usbhs_priv *priv) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + return info->curt; +} + +int usbhs_mod_change(struct usbhs_priv *priv, int id) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + struct usbhs_mod *mod = NULL; + int ret = 0; + + /* id < 0 mean no current */ + switch (id) { + case USBHS_HOST: + case USBHS_GADGET: + mod = info->mod[id]; + break; + default: + ret = -EINVAL; + } + info->curt = mod; + + return ret; +} + +static irqreturn_t usbhs_interrupt(int irq, void *data); +int usbhs_mod_probe(struct usbhs_priv *priv) +{ + struct device *dev = usbhs_priv_to_dev(priv); + int ret; + + /* + * install host/gadget driver + */ + ret = usbhs_mod_host_probe(priv); + if (ret < 0) + return ret; + + ret = usbhs_mod_gadget_probe(priv); + if (ret < 0) + goto mod_init_host_err; + + /* irq settings */ + ret = devm_request_irq(dev, priv->irq, usbhs_interrupt, + priv->irqflags, dev_name(dev), priv); + if (ret) { + dev_err(dev, "irq request err\n"); + goto mod_init_gadget_err; + } + + return ret; + +mod_init_gadget_err: + usbhs_mod_gadget_remove(priv); +mod_init_host_err: + usbhs_mod_host_remove(priv); + + return ret; +} + +void usbhs_mod_remove(struct usbhs_priv *priv) +{ + usbhs_mod_host_remove(priv); + usbhs_mod_gadget_remove(priv); +} + +/* + * status functions + */ +int usbhs_status_get_device_state(struct usbhs_irq_state *irq_state) +{ + int state = irq_state->intsts0 & DVSQ_MASK; + + switch (state) { + case POWER_STATE: + case DEFAULT_STATE: + case ADDRESS_STATE: + case CONFIGURATION_STATE: + return state; + } + + return -EIO; +} + +int usbhs_status_get_ctrl_stage(struct usbhs_irq_state *irq_state) +{ + /* + * return value + * + * IDLE_SETUP_STAGE + * READ_DATA_STAGE + * READ_STATUS_STAGE + * WRITE_DATA_STAGE + * WRITE_STATUS_STAGE + * NODATA_STATUS_STAGE + * SEQUENCE_ERROR + */ + return (int)irq_state->intsts0 & CTSQ_MASK; +} + +static int usbhs_status_get_each_irq(struct usbhs_priv *priv, + struct usbhs_irq_state *state) +{ + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + u16 intenb0, intenb1; + + state->intsts0 = usbhs_read(priv, INTSTS0); + state->intsts1 = usbhs_read(priv, INTSTS1); + + intenb0 = usbhs_read(priv, INTENB0); + intenb1 = usbhs_read(priv, INTENB1); + + /* mask */ + if (mod) { + state->brdysts = usbhs_read(priv, BRDYSTS); + state->nrdysts = usbhs_read(priv, NRDYSTS); + state->bempsts = usbhs_read(priv, BEMPSTS); + + state->bempsts &= mod->irq_bempsts; + state->brdysts &= mod->irq_brdysts; + } + + /* + * Check whether the irq enable registers and the irq status are set + * when IRQF_SHARED is set. + */ + if (priv->irqflags & IRQF_SHARED) { + if (!(intenb0 & state->intsts0) && + !(intenb1 & state->intsts1) && + !(state->bempsts) && + !(state->brdysts)) + return -EIO; + } + + return 0; +} + +/* + * interrupt + */ +#define INTSTS0_MAGIC 0xF800 /* acknowledge magical interrupt sources */ +#define INTSTS1_MAGIC 0xA870 /* acknowledge magical interrupt sources */ +static irqreturn_t usbhs_interrupt(int irq, void *data) +{ + struct usbhs_priv *priv = data; + struct usbhs_irq_state irq_state; + + if (usbhs_status_get_each_irq(priv, &irq_state) < 0) + return IRQ_NONE; + + /* + * clear interrupt + * + * The hardware is _very_ picky to clear interrupt bit. + * Especially INTSTS0_MAGIC, INTSTS1_MAGIC value. + * + * see + * "Operation" + * - "Control Transfer (DCP)" + * - Function :: VALID bit should 0 + */ + usbhs_write(priv, INTSTS0, ~irq_state.intsts0 & INTSTS0_MAGIC); + usbhs_write(priv, INTSTS1, ~irq_state.intsts1 & INTSTS1_MAGIC); + + usbhs_write(priv, BRDYSTS, ~irq_state.brdysts); + usbhs_write(priv, NRDYSTS, ~irq_state.nrdysts); + usbhs_write(priv, BEMPSTS, ~irq_state.bempsts); + + /* + * call irq callback functions + * see also + * usbhs_irq_setting_update + */ + + /* INTSTS0 */ + if (irq_state.intsts0 & VBINT) + usbhs_mod_info_call(priv, irq_vbus, priv, &irq_state); + + if (irq_state.intsts0 & DVST) + usbhs_mod_call(priv, irq_dev_state, priv, &irq_state); + + if (irq_state.intsts0 & CTRT) + usbhs_mod_call(priv, irq_ctrl_stage, priv, &irq_state); + + if (irq_state.intsts0 & BEMP) + usbhs_mod_call(priv, irq_empty, priv, &irq_state); + + if (irq_state.intsts0 & BRDY) + usbhs_mod_call(priv, irq_ready, priv, &irq_state); + + /* INTSTS1 */ + if (irq_state.intsts1 & ATTCH) + usbhs_mod_call(priv, irq_attch, priv, &irq_state); + + if (irq_state.intsts1 & DTCH) + usbhs_mod_call(priv, irq_dtch, priv, &irq_state); + + if (irq_state.intsts1 & SIGN) + usbhs_mod_call(priv, irq_sign, priv, &irq_state); + + if (irq_state.intsts1 & SACK) + usbhs_mod_call(priv, irq_sack, priv, &irq_state); + + return IRQ_HANDLED; +} + +void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod) +{ + u16 intenb0 = 0; + u16 intenb1 = 0; + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + /* + * BEMPENB/BRDYENB are picky. + * below method is required + * + * - clear INTSTS0 + * - update BEMPENB/BRDYENB + * - update INTSTS0 + */ + usbhs_write(priv, INTENB0, 0); + usbhs_write(priv, INTENB1, 0); + + usbhs_write(priv, BEMPENB, 0); + usbhs_write(priv, BRDYENB, 0); + + /* + * see also + * usbhs_interrupt + */ + + /* + * it don't enable DVSE (intenb0) here + * but "mod->irq_dev_state" will be called. + */ + if (info->irq_vbus) + intenb0 |= VBSE; + + if (mod) { + /* + * INTSTS0 + */ + if (mod->irq_ctrl_stage) + intenb0 |= CTRE; + + if (mod->irq_empty && mod->irq_bempsts) { + usbhs_write(priv, BEMPENB, mod->irq_bempsts); + intenb0 |= BEMPE; + } + + if (mod->irq_ready && mod->irq_brdysts) { + usbhs_write(priv, BRDYENB, mod->irq_brdysts); + intenb0 |= BRDYE; + } + + /* + * INTSTS1 + */ + if (mod->irq_attch) + intenb1 |= ATTCHE; + + if (mod->irq_dtch) + intenb1 |= DTCHE; + + if (mod->irq_sign) + intenb1 |= SIGNE; + + if (mod->irq_sack) + intenb1 |= SACKE; + } + + if (intenb0) + usbhs_write(priv, INTENB0, intenb0); + + if (intenb1) + usbhs_write(priv, INTENB1, intenb1); +} diff --git a/drivers/usb/renesas_usbhs/mod.h b/drivers/usb/renesas_usbhs/mod.h new file mode 100644 index 00000000..1ef5bf60 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod.h @@ -0,0 +1,172 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef RENESAS_USB_MOD_H +#define RENESAS_USB_MOD_H + +#include +#include +#include "common.h" + +/* + * struct + */ +struct usbhs_irq_state { + u16 intsts0; + u16 intsts1; + u16 brdysts; + u16 nrdysts; + u16 bempsts; +}; + +struct usbhs_mod { + char *name; + + /* + * entry point from common.c + */ + int (*start)(struct usbhs_priv *priv); + int (*stop)(struct usbhs_priv *priv); + + /* + * INTSTS0 + */ + + /* DVST (DVSQ) */ + int (*irq_dev_state)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + + /* CTRT (CTSQ) */ + int (*irq_ctrl_stage)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + + /* BEMP / BEMPSTS */ + int (*irq_empty)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + u16 irq_bempsts; + + /* BRDY / BRDYSTS */ + int (*irq_ready)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + u16 irq_brdysts; + + /* + * INTSTS1 + */ + + /* ATTCHE */ + int (*irq_attch)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + + /* DTCHE */ + int (*irq_dtch)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + + /* SIGN */ + int (*irq_sign)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + + /* SACK */ + int (*irq_sack)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + + struct usbhs_priv *priv; +}; + +struct usbhs_mod_info { + struct usbhs_mod *mod[USBHS_MAX]; + struct usbhs_mod *curt; /* current mod */ + + /* + * INTSTS0 :: VBINT + * + * This function will be used as autonomy mode + * when platform cannot call notify_hotplug. + * + * This callback cannot be member of "struct usbhs_mod" + * because it will be used even though + * host/gadget has not been selected. + */ + int (*irq_vbus)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); +}; + +/* + * for host/gadget module + */ +struct usbhs_mod *usbhs_mod_get(struct usbhs_priv *priv, int id); +struct usbhs_mod *usbhs_mod_get_current(struct usbhs_priv *priv); +void usbhs_mod_register(struct usbhs_priv *priv, struct usbhs_mod *usb, int id); +int usbhs_mod_is_host(struct usbhs_priv *priv); +int usbhs_mod_change(struct usbhs_priv *priv, int id); +int usbhs_mod_probe(struct usbhs_priv *priv); +void usbhs_mod_remove(struct usbhs_priv *priv); + +void usbhs_mod_autonomy_mode(struct usbhs_priv *priv); + +/* + * status functions + */ +int usbhs_status_get_device_state(struct usbhs_irq_state *irq_state); +int usbhs_status_get_ctrl_stage(struct usbhs_irq_state *irq_state); + +/* + * callback functions + */ +void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod); + + +#define usbhs_mod_call(priv, func, param...) \ + ({ \ + struct usbhs_mod *mod; \ + mod = usbhs_mod_get_current(priv); \ + !mod ? -ENODEV : \ + !mod->func ? 0 : \ + mod->func(param); \ + }) + +/* + * host / gadget control + */ +#if defined(CONFIG_USB_RENESAS_USBHS_HCD) || \ + defined(CONFIG_USB_RENESAS_USBHS_HCD_MODULE) +extern int usbhs_mod_host_probe(struct usbhs_priv *priv); +extern int usbhs_mod_host_remove(struct usbhs_priv *priv); +#else +static inline int usbhs_mod_host_probe(struct usbhs_priv *priv) +{ + return 0; +} +static inline void usbhs_mod_host_remove(struct usbhs_priv *priv) +{ +} +#endif + +#if defined(CONFIG_USB_RENESAS_USBHS_UDC) || \ + defined(CONFIG_USB_RENESAS_USBHS_UDC_MODULE) +extern int usbhs_mod_gadget_probe(struct usbhs_priv *priv); +extern void usbhs_mod_gadget_remove(struct usbhs_priv *priv); +#else +static inline int usbhs_mod_gadget_probe(struct usbhs_priv *priv) +{ + return 0; +} +static inline void usbhs_mod_gadget_remove(struct usbhs_priv *priv) +{ +} +#endif + +#endif /* RENESAS_USB_MOD_H */ diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c new file mode 100644 index 00000000..ed4949fa --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -0,0 +1,1029 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +/* + * struct + */ +struct usbhsg_request { + struct usb_request req; + struct usbhs_pkt pkt; +}; + +#define EP_NAME_SIZE 8 +struct usbhsg_gpriv; +struct usbhsg_uep { + struct usb_ep ep; + struct usbhs_pipe *pipe; + + char ep_name[EP_NAME_SIZE]; + + struct usbhsg_gpriv *gpriv; +}; + +struct usbhsg_gpriv { + struct usb_gadget gadget; + struct usbhs_mod mod; + + struct usbhsg_uep *uep; + int uep_size; + + struct usb_gadget_driver *driver; + + u32 status; +#define USBHSG_STATUS_STARTED (1 << 0) +#define USBHSG_STATUS_REGISTERD (1 << 1) +#define USBHSG_STATUS_WEDGE (1 << 2) +#define USBHSG_STATUS_SELF_POWERED (1 << 3) +}; + +struct usbhsg_recip_handle { + char *name; + int (*device)(struct usbhs_priv *priv, struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl); + int (*interface)(struct usbhs_priv *priv, struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl); + int (*endpoint)(struct usbhs_priv *priv, struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl); +}; + +/* + * macro + */ +#define usbhsg_priv_to_gpriv(priv) \ + container_of( \ + usbhs_mod_get(priv, USBHS_GADGET), \ + struct usbhsg_gpriv, mod) + +#define __usbhsg_for_each_uep(start, pos, g, i) \ + for (i = start, pos = (g)->uep + i; \ + i < (g)->uep_size; \ + i++, pos = (g)->uep + i) + +#define usbhsg_for_each_uep(pos, gpriv, i) \ + __usbhsg_for_each_uep(1, pos, gpriv, i) + +#define usbhsg_for_each_uep_with_dcp(pos, gpriv, i) \ + __usbhsg_for_each_uep(0, pos, gpriv, i) + +#define usbhsg_gadget_to_gpriv(g)\ + container_of(g, struct usbhsg_gpriv, gadget) + +#define usbhsg_req_to_ureq(r)\ + container_of(r, struct usbhsg_request, req) + +#define usbhsg_ep_to_uep(e) container_of(e, struct usbhsg_uep, ep) +#define usbhsg_gpriv_to_dev(gp) usbhs_priv_to_dev((gp)->mod.priv) +#define usbhsg_gpriv_to_priv(gp) ((gp)->mod.priv) +#define usbhsg_gpriv_to_dcp(gp) ((gp)->uep) +#define usbhsg_gpriv_to_nth_uep(gp, i) ((gp)->uep + i) +#define usbhsg_uep_to_gpriv(u) ((u)->gpriv) +#define usbhsg_uep_to_pipe(u) ((u)->pipe) +#define usbhsg_pipe_to_uep(p) ((p)->mod_private) +#define usbhsg_is_dcp(u) ((u) == usbhsg_gpriv_to_dcp((u)->gpriv)) + +#define usbhsg_ureq_to_pkt(u) (&(u)->pkt) +#define usbhsg_pkt_to_ureq(i) \ + container_of(i, struct usbhsg_request, pkt) + +#define usbhsg_is_not_connected(gp) ((gp)->gadget.speed == USB_SPEED_UNKNOWN) + +/* status */ +#define usbhsg_status_init(gp) do {(gp)->status = 0; } while (0) +#define usbhsg_status_set(gp, b) (gp->status |= b) +#define usbhsg_status_clr(gp, b) (gp->status &= ~b) +#define usbhsg_status_has(gp, b) (gp->status & b) + +/* + * queue push/pop + */ +static void usbhsg_queue_pop(struct usbhsg_uep *uep, + struct usbhsg_request *ureq, + int status) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + + dev_dbg(dev, "pipe %d : queue pop\n", usbhs_pipe_number(pipe)); + + ureq->req.status = status; + ureq->req.complete(&uep->ep, &ureq->req); +} + +static void usbhsg_queue_done(struct usbhs_priv *priv, struct usbhs_pkt *pkt) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhsg_uep *uep = usbhsg_pipe_to_uep(pipe); + struct usbhsg_request *ureq = usbhsg_pkt_to_ureq(pkt); + + ureq->req.actual = pkt->actual; + + usbhsg_queue_pop(uep, ureq, 0); +} + +static void usbhsg_queue_push(struct usbhsg_uep *uep, + struct usbhsg_request *ureq) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct usbhs_pkt *pkt = usbhsg_ureq_to_pkt(ureq); + struct usb_request *req = &ureq->req; + + req->actual = 0; + req->status = -EINPROGRESS; + usbhs_pkt_push(pipe, pkt, usbhsg_queue_done, + req->buf, req->length, req->zero, -1); + usbhs_pkt_start(pipe); + + dev_dbg(dev, "pipe %d : queue push (%d)\n", + usbhs_pipe_number(pipe), + req->length); +} + +/* + * dma map/unmap + */ +static int usbhsg_dma_map_ctrl(struct usbhs_pkt *pkt, int map) +{ + struct usbhsg_request *ureq = usbhsg_pkt_to_ureq(pkt); + struct usb_request *req = &ureq->req; + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhsg_uep *uep = usbhsg_pipe_to_uep(pipe); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + enum dma_data_direction dir; + int ret = 0; + + dir = usbhs_pipe_is_dir_host(pipe); + + if (map) { + /* it can not use scatter/gather */ + WARN_ON(req->num_sgs); + + ret = usb_gadget_map_request(&gpriv->gadget, req, dir); + if (ret < 0) + return ret; + + pkt->dma = req->dma; + } else { + usb_gadget_unmap_request(&gpriv->gadget, req, dir); + } + + return ret; +} + +/* + * USB_TYPE_STANDARD / clear feature functions + */ +static int usbhsg_recip_handler_std_control_done(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(dcp); + + usbhs_dcp_control_transfer_done(pipe); + + return 0; +} + +static int usbhsg_recip_handler_std_clear_endpoint(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + + if (!usbhsg_status_has(gpriv, USBHSG_STATUS_WEDGE)) { + usbhs_pipe_disable(pipe); + usbhs_pipe_sequence_data0(pipe); + usbhs_pipe_enable(pipe); + } + + usbhsg_recip_handler_std_control_done(priv, uep, ctrl); + + usbhs_pkt_start(pipe); + + return 0; +} + +static struct usbhsg_recip_handle req_clear_feature = { + .name = "clear feature", + .device = usbhsg_recip_handler_std_control_done, + .interface = usbhsg_recip_handler_std_control_done, + .endpoint = usbhsg_recip_handler_std_clear_endpoint, +}; + +/* + * USB_TYPE_STANDARD / set feature functions + */ +static int usbhsg_recip_handler_std_set_device(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + switch (le16_to_cpu(ctrl->wValue)) { + case USB_DEVICE_TEST_MODE: + usbhsg_recip_handler_std_control_done(priv, uep, ctrl); + udelay(100); + usbhs_sys_set_test_mode(priv, le16_to_cpu(ctrl->wIndex >> 8)); + break; + default: + usbhsg_recip_handler_std_control_done(priv, uep, ctrl); + break; + } + + return 0; +} + +static int usbhsg_recip_handler_std_set_endpoint(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + + usbhs_pipe_stall(pipe); + + usbhsg_recip_handler_std_control_done(priv, uep, ctrl); + + return 0; +} + +static struct usbhsg_recip_handle req_set_feature = { + .name = "set feature", + .device = usbhsg_recip_handler_std_set_device, + .interface = usbhsg_recip_handler_std_control_done, + .endpoint = usbhsg_recip_handler_std_set_endpoint, +}; + +/* + * USB_TYPE_STANDARD / get status functions + */ +static void __usbhsg_recip_send_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct usbhsg_request *ureq = usbhsg_req_to_ureq(req); + + /* free allocated recip-buffer/usb_request */ + kfree(ureq->pkt.buf); + usb_ep_free_request(ep, req); +} + +static void __usbhsg_recip_send_status(struct usbhsg_gpriv *gpriv, + unsigned short status) +{ + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(dcp); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + struct usb_request *req; + unsigned short *buf; + + /* alloc new usb_request for recip */ + req = usb_ep_alloc_request(&dcp->ep, GFP_ATOMIC); + if (!req) { + dev_err(dev, "recip request allocation fail\n"); + return; + } + + /* alloc recip data buffer */ + buf = kmalloc(sizeof(*buf), GFP_ATOMIC); + if (!buf) { + usb_ep_free_request(&dcp->ep, req); + dev_err(dev, "recip data allocation fail\n"); + return; + } + + /* recip data is status */ + *buf = cpu_to_le16(status); + + /* allocated usb_request/buffer will be freed */ + req->complete = __usbhsg_recip_send_complete; + req->buf = buf; + req->length = sizeof(*buf); + req->zero = 0; + + /* push packet */ + pipe->handler = &usbhs_fifo_pio_push_handler; + usbhsg_queue_push(dcp, usbhsg_req_to_ureq(req)); +} + +static int usbhsg_recip_handler_std_get_device(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + unsigned short status = 0; + + if (usbhsg_status_has(gpriv, USBHSG_STATUS_SELF_POWERED)) + status = 1 << USB_DEVICE_SELF_POWERED; + + __usbhsg_recip_send_status(gpriv, status); + + return 0; +} + +static int usbhsg_recip_handler_std_get_interface(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + unsigned short status = 0; + + __usbhsg_recip_send_status(gpriv, status); + + return 0; +} + +static int usbhsg_recip_handler_std_get_endpoint(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + unsigned short status = 0; + + if (usbhs_pipe_is_stall(pipe)) + status = 1 << USB_ENDPOINT_HALT; + + __usbhsg_recip_send_status(gpriv, status); + + return 0; +} + +static struct usbhsg_recip_handle req_get_status = { + .name = "get status", + .device = usbhsg_recip_handler_std_get_device, + .interface = usbhsg_recip_handler_std_get_interface, + .endpoint = usbhsg_recip_handler_std_get_endpoint, +}; + +/* + * USB_TYPE handler + */ +static int usbhsg_recip_run_handle(struct usbhs_priv *priv, + struct usbhsg_recip_handle *handler, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + struct usbhsg_uep *uep; + struct usbhs_pipe *pipe; + int recip = ctrl->bRequestType & USB_RECIP_MASK; + int nth = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + int ret = 0; + int (*func)(struct usbhs_priv *priv, struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl); + char *msg; + + uep = usbhsg_gpriv_to_nth_uep(gpriv, nth); + pipe = usbhsg_uep_to_pipe(uep); + if (!pipe) { + dev_err(dev, "wrong recip request\n"); + return -EINVAL; + } + + switch (recip) { + case USB_RECIP_DEVICE: + msg = "DEVICE"; + func = handler->device; + break; + case USB_RECIP_INTERFACE: + msg = "INTERFACE"; + func = handler->interface; + break; + case USB_RECIP_ENDPOINT: + msg = "ENDPOINT"; + func = handler->endpoint; + break; + default: + dev_warn(dev, "unsupported RECIP(%d)\n", recip); + func = NULL; + ret = -EINVAL; + } + + if (func) { + dev_dbg(dev, "%s (pipe %d :%s)\n", handler->name, nth, msg); + ret = func(priv, uep, ctrl); + } + + return ret; +} + +/* + * irq functions + * + * it will be called from usbhs_interrupt + */ +static int usbhsg_irq_dev_state(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + + gpriv->gadget.speed = usbhs_bus_get_speed(priv); + + dev_dbg(dev, "state = %x : speed : %d\n", + usbhs_status_get_device_state(irq_state), + gpriv->gadget.speed); + + return 0; +} + +static int usbhsg_irq_ctrl_stage(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(dcp); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + struct usb_ctrlrequest ctrl; + struct usbhsg_recip_handle *recip_handler = NULL; + int stage = usbhs_status_get_ctrl_stage(irq_state); + int ret = 0; + + dev_dbg(dev, "stage = %d\n", stage); + + /* + * see Manual + * + * "Operation" + * - "Interrupt Function" + * - "Control Transfer Stage Transition Interrupt" + * - Fig. "Control Transfer Stage Transitions" + */ + + switch (stage) { + case READ_DATA_STAGE: + pipe->handler = &usbhs_fifo_pio_push_handler; + break; + case WRITE_DATA_STAGE: + pipe->handler = &usbhs_fifo_pio_pop_handler; + break; + case NODATA_STATUS_STAGE: + pipe->handler = &usbhs_ctrl_stage_end_handler; + break; + default: + return ret; + } + + /* + * get usb request + */ + usbhs_usbreq_get_val(priv, &ctrl); + + switch (ctrl.bRequestType & USB_TYPE_MASK) { + case USB_TYPE_STANDARD: + switch (ctrl.bRequest) { + case USB_REQ_CLEAR_FEATURE: + recip_handler = &req_clear_feature; + break; + case USB_REQ_SET_FEATURE: + recip_handler = &req_set_feature; + break; + case USB_REQ_GET_STATUS: + recip_handler = &req_get_status; + break; + } + } + + /* + * setup stage / run recip + */ + if (recip_handler) + ret = usbhsg_recip_run_handle(priv, recip_handler, &ctrl); + else + ret = gpriv->driver->setup(&gpriv->gadget, &ctrl); + + if (ret < 0) + usbhs_pipe_stall(pipe); + + return ret; +} + +/* + * + * usb_dcp_ops + * + */ +static int usbhsg_pipe_disable(struct usbhsg_uep *uep) +{ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct usbhs_pkt *pkt; + + while (1) { + pkt = usbhs_pkt_pop(pipe, NULL); + if (!pkt) + break; + + usbhsg_queue_pop(uep, usbhsg_pkt_to_ureq(pkt), -ECONNRESET); + } + + usbhs_pipe_disable(pipe); + + return 0; +} + +/* + * + * usb_ep_ops + * + */ +static int usbhsg_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + struct usbhs_pipe *pipe; + int ret = -EIO; + + /* + * if it already have pipe, + * nothing to do + */ + if (uep->pipe) { + usbhs_pipe_clear(uep->pipe); + usbhs_pipe_sequence_data0(uep->pipe); + return 0; + } + + pipe = usbhs_pipe_malloc(priv, + usb_endpoint_type(desc), + usb_endpoint_dir_in(desc)); + if (pipe) { + uep->pipe = pipe; + pipe->mod_private = uep; + + /* set epnum / maxp */ + usbhs_pipe_config_update(pipe, 0, + usb_endpoint_num(desc), + usb_endpoint_maxp(desc)); + + /* + * usbhs_fifo_dma_push/pop_handler try to + * use dmaengine if possible. + * It will use pio handler if impossible. + */ + if (usb_endpoint_dir_in(desc)) + pipe->handler = &usbhs_fifo_dma_push_handler; + else + pipe->handler = &usbhs_fifo_dma_pop_handler; + + ret = 0; + } + + return ret; +} + +static int usbhsg_ep_disable(struct usb_ep *ep) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + + usbhsg_pipe_disable(uep); + + uep->pipe->mod_private = NULL; + uep->pipe = NULL; + + return 0; +} + +static struct usb_request *usbhsg_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct usbhsg_request *ureq; + + ureq = kzalloc(sizeof *ureq, gfp_flags); + if (!ureq) + return NULL; + + usbhs_pkt_init(usbhsg_ureq_to_pkt(ureq)); + + return &ureq->req; +} + +static void usbhsg_ep_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + struct usbhsg_request *ureq = usbhsg_req_to_ureq(req); + + WARN_ON(!list_empty(&ureq->pkt.node)); + kfree(ureq); +} + +static int usbhsg_ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhsg_request *ureq = usbhsg_req_to_ureq(req); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + + /* param check */ + if (usbhsg_is_not_connected(gpriv) || + unlikely(!gpriv->driver) || + unlikely(!pipe)) + return -ESHUTDOWN; + + usbhsg_queue_push(uep, ureq); + + return 0; +} + +static int usbhsg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhsg_request *ureq = usbhsg_req_to_ureq(req); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + + usbhs_pkt_pop(pipe, usbhsg_ureq_to_pkt(ureq)); + usbhsg_queue_pop(uep, ureq, -ECONNRESET); + + return 0; +} + +static int __usbhsg_ep_set_halt_wedge(struct usb_ep *ep, int halt, int wedge) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + unsigned long flags; + + usbhsg_pipe_disable(uep); + + dev_dbg(dev, "set halt %d (pipe %d)\n", + halt, usbhs_pipe_number(pipe)); + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + if (halt) + usbhs_pipe_stall(pipe); + else + usbhs_pipe_disable(pipe); + + if (halt && wedge) + usbhsg_status_set(gpriv, USBHSG_STATUS_WEDGE); + else + usbhsg_status_clr(gpriv, USBHSG_STATUS_WEDGE); + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + return 0; +} + +static int usbhsg_ep_set_halt(struct usb_ep *ep, int value) +{ + return __usbhsg_ep_set_halt_wedge(ep, value, 0); +} + +static int usbhsg_ep_set_wedge(struct usb_ep *ep) +{ + return __usbhsg_ep_set_halt_wedge(ep, 1, 1); +} + +static struct usb_ep_ops usbhsg_ep_ops = { + .enable = usbhsg_ep_enable, + .disable = usbhsg_ep_disable, + + .alloc_request = usbhsg_ep_alloc_request, + .free_request = usbhsg_ep_free_request, + + .queue = usbhsg_ep_queue, + .dequeue = usbhsg_ep_dequeue, + + .set_halt = usbhsg_ep_set_halt, + .set_wedge = usbhsg_ep_set_wedge, +}; + +/* + * usb module start/end + */ +static int usbhsg_try_start(struct usbhs_priv *priv, u32 status) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct device *dev = usbhs_priv_to_dev(priv); + unsigned long flags; + int ret = 0; + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + usbhsg_status_set(gpriv, status); + if (!(usbhsg_status_has(gpriv, USBHSG_STATUS_STARTED) && + usbhsg_status_has(gpriv, USBHSG_STATUS_REGISTERD))) + ret = -1; /* not ready */ + + usbhs_unlock(priv, flags); + /******************** spin unlock ********************/ + + if (ret < 0) + return 0; /* not ready is not error */ + + /* + * enable interrupt and systems if ready + */ + dev_dbg(dev, "start gadget\n"); + + /* + * pipe initialize and enable DCP + */ + usbhs_pipe_init(priv, + usbhsg_dma_map_ctrl); + usbhs_fifo_init(priv); + + /* dcp init instead of usbhsg_ep_enable() */ + dcp->pipe = usbhs_dcp_malloc(priv); + dcp->pipe->mod_private = dcp; + usbhs_pipe_config_update(dcp->pipe, 0, 0, 64); + + /* + * system config enble + * - HI speed + * - function + * - usb module + */ + usbhs_sys_function_ctrl(priv, 1); + + /* + * enable irq callback + */ + mod->irq_dev_state = usbhsg_irq_dev_state; + mod->irq_ctrl_stage = usbhsg_irq_ctrl_stage; + usbhs_irq_callback_update(priv, mod); + + return 0; +} + +static int usbhsg_try_stop(struct usbhs_priv *priv, u32 status) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct device *dev = usbhs_priv_to_dev(priv); + unsigned long flags; + int ret = 0; + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + usbhsg_status_clr(gpriv, status); + if (!usbhsg_status_has(gpriv, USBHSG_STATUS_STARTED) && + !usbhsg_status_has(gpriv, USBHSG_STATUS_REGISTERD)) + ret = -1; /* already done */ + + usbhs_unlock(priv, flags); + /******************** spin unlock ********************/ + + if (ret < 0) + return 0; /* already done is not error */ + + /* + * disable interrupt and systems if 1st try + */ + usbhs_fifo_quit(priv); + + /* disable all irq */ + mod->irq_dev_state = NULL; + mod->irq_ctrl_stage = NULL; + usbhs_irq_callback_update(priv, mod); + + gpriv->gadget.speed = USB_SPEED_UNKNOWN; + + /* disable sys */ + usbhs_sys_set_test_mode(priv, 0); + usbhs_sys_function_ctrl(priv, 0); + + usbhsg_ep_disable(&dcp->ep); + + dev_dbg(dev, "stop gadget\n"); + + return 0; +} + +/* + * + * linux usb function + * + */ +static int usbhsg_gadget_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + + if (!driver || + !driver->setup || + driver->max_speed < USB_SPEED_FULL) + return -EINVAL; + + /* first hook up the driver ... */ + gpriv->driver = driver; + + return usbhsg_try_start(priv, USBHSG_STATUS_REGISTERD); +} + +static int usbhsg_gadget_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + + if (!driver || + !driver->unbind) + return -EINVAL; + + usbhsg_try_stop(priv, USBHSG_STATUS_REGISTERD); + gpriv->driver = NULL; + + return 0; +} + +/* + * usb gadget ops + */ +static int usbhsg_get_frame(struct usb_gadget *gadget) +{ + struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + + return usbhs_frame_get_num(priv); +} + +static int usbhsg_pullup(struct usb_gadget *gadget, int is_on) +{ + struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + + usbhs_sys_function_pullup(priv, is_on); + + return 0; +} + +static int usbhsg_set_selfpowered(struct usb_gadget *gadget, int is_self) +{ + struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + + if (is_self) + usbhsg_status_set(gpriv, USBHSG_STATUS_SELF_POWERED); + else + usbhsg_status_clr(gpriv, USBHSG_STATUS_SELF_POWERED); + + return 0; +} + +static const struct usb_gadget_ops usbhsg_gadget_ops = { + .get_frame = usbhsg_get_frame, + .set_selfpowered = usbhsg_set_selfpowered, + .udc_start = usbhsg_gadget_start, + .udc_stop = usbhsg_gadget_stop, + .pullup = usbhsg_pullup, +}; + +static int usbhsg_start(struct usbhs_priv *priv) +{ + return usbhsg_try_start(priv, USBHSG_STATUS_STARTED); +} + +static int usbhsg_stop(struct usbhs_priv *priv) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + + /* cable disconnect */ + if (gpriv->driver && + gpriv->driver->disconnect) + gpriv->driver->disconnect(&gpriv->gadget); + + return usbhsg_try_stop(priv, USBHSG_STATUS_STARTED); +} + +int usbhs_mod_gadget_probe(struct usbhs_priv *priv) +{ + struct usbhsg_gpriv *gpriv; + struct usbhsg_uep *uep; + struct device *dev = usbhs_priv_to_dev(priv); + int pipe_size = usbhs_get_dparam(priv, pipe_size); + int i; + int ret; + + gpriv = kzalloc(sizeof(struct usbhsg_gpriv), GFP_KERNEL); + if (!gpriv) { + dev_err(dev, "Could not allocate gadget priv\n"); + return -ENOMEM; + } + + uep = kzalloc(sizeof(struct usbhsg_uep) * pipe_size, GFP_KERNEL); + if (!uep) { + dev_err(dev, "Could not allocate ep\n"); + ret = -ENOMEM; + goto usbhs_mod_gadget_probe_err_gpriv; + } + + /* + * CAUTION + * + * There is no guarantee that it is possible to access usb module here. + * Don't accesses to it. + * The accesse will be enable after "usbhsg_start" + */ + + /* + * register itself + */ + usbhs_mod_register(priv, &gpriv->mod, USBHS_GADGET); + + /* init gpriv */ + gpriv->mod.name = "gadget"; + gpriv->mod.start = usbhsg_start; + gpriv->mod.stop = usbhsg_stop; + gpriv->uep = uep; + gpriv->uep_size = pipe_size; + usbhsg_status_init(gpriv); + + /* + * init gadget + */ + gpriv->gadget.dev.parent = dev; + gpriv->gadget.name = "renesas_usbhs_udc"; + gpriv->gadget.ops = &usbhsg_gadget_ops; + gpriv->gadget.max_speed = USB_SPEED_HIGH; + + INIT_LIST_HEAD(&gpriv->gadget.ep_list); + + /* + * init usb_ep + */ + usbhsg_for_each_uep_with_dcp(uep, gpriv, i) { + uep->gpriv = gpriv; + uep->pipe = NULL; + snprintf(uep->ep_name, EP_NAME_SIZE, "ep%d", i); + + uep->ep.name = uep->ep_name; + uep->ep.ops = &usbhsg_ep_ops; + INIT_LIST_HEAD(&uep->ep.ep_list); + + /* init DCP */ + if (usbhsg_is_dcp(uep)) { + gpriv->gadget.ep0 = &uep->ep; + uep->ep.maxpacket = 64; + } + /* init normal pipe */ + else { + uep->ep.maxpacket = 512; + list_add_tail(&uep->ep.ep_list, &gpriv->gadget.ep_list); + } + } + + ret = usb_add_gadget_udc(dev, &gpriv->gadget); + if (ret) + goto err_add_udc; + + + dev_info(dev, "gadget probed\n"); + + return 0; + +err_add_udc: + kfree(gpriv->uep); + +usbhs_mod_gadget_probe_err_gpriv: + kfree(gpriv); + + return ret; +} + +void usbhs_mod_gadget_remove(struct usbhs_priv *priv) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + + usb_del_gadget_udc(&gpriv->gadget); + + kfree(gpriv->uep); + kfree(gpriv); +} diff --git a/drivers/usb/renesas_usbhs/mod_host.c b/drivers/usb/renesas_usbhs/mod_host.c new file mode 100644 index 00000000..b8681542 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod_host.c @@ -0,0 +1,1585 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +/* + *** HARDWARE LIMITATION *** + * + * 1) renesas_usbhs has a limited number of controllable devices. + * it can control only 9 devices in generally. + * see DEVADDn / DCPMAXP / PIPEMAXP. + * + * 2) renesas_usbhs pipe number is limited. + * the pipe will be re-used for each devices. + * so, software should control DATA0/1 sequence of each devices. + */ + + +/* + * image of mod_host + * + * +--------+ + * | udev 0 | --> it is used when set address + * +--------+ + * + * +--------+ pipes are reused for each uep. + * | udev 1 |-+- [uep 0 (dcp) ] --+ pipe will be switched when + * +--------+ | | other device requested + * +- [uep 1 (bulk)] --|---+ +--------------+ + * | +--------------> | pipe0 (dcp) | + * +- [uep 2 (bulk)] -@ | +--------------+ + * | | pipe1 (isoc) | + * +--------+ | +--------------+ + * | udev 2 |-+- [uep 0 (dcp) ] -@ +----------> | pipe2 (bulk) | + * +--------+ | +--------------+ + * +- [uep 1 (int) ] ----+ +------> | pipe3 (bulk) | + * | | +--------------+ + * +--------+ +-----|------> | pipe4 (int) | + * | udev 3 |-+- [uep 0 (dcp) ] -@ | +--------------+ + * +--------+ | | | .... | + * +- [uep 1 (bulk)] -@ | | .... | + * | | + * +- [uep 2 (bulk)]-----------+ + * + * @ : uep requested free pipe, but all have been used. + * now it is waiting for free pipe + */ + + +/* + * struct + */ +struct usbhsh_request { + struct urb *urb; + struct usbhs_pkt pkt; +}; + +struct usbhsh_device { + struct usb_device *usbv; + struct list_head ep_list_head; /* list of usbhsh_ep */ +}; + +struct usbhsh_ep { + struct usbhs_pipe *pipe; /* attached pipe */ + struct usbhsh_device *udev; /* attached udev */ + struct usb_host_endpoint *ep; + struct list_head ep_list; /* list to usbhsh_device */ + unsigned int counter; /* pipe attach counter */ +}; + +#define USBHSH_DEVICE_MAX 10 /* see DEVADDn / DCPMAXP / PIPEMAXP */ +#define USBHSH_PORT_MAX 7 /* see DEVADDn :: HUBPORT */ +struct usbhsh_hpriv { + struct usbhs_mod mod; + struct usbhs_pipe *dcp; + + struct usbhsh_device udev[USBHSH_DEVICE_MAX]; + + u32 port_stat; /* USB_PORT_STAT_xxx */ + + struct completion setup_ack_done; +}; + + +static const char usbhsh_hcd_name[] = "renesas_usbhs host"; + +/* + * macro + */ +#define usbhsh_priv_to_hpriv(priv) \ + container_of(usbhs_mod_get(priv, USBHS_HOST), struct usbhsh_hpriv, mod) + +#define __usbhsh_for_each_udev(start, pos, h, i) \ + for (i = start, pos = (h)->udev + i; \ + i < USBHSH_DEVICE_MAX; \ + i++, pos = (h)->udev + i) + +#define usbhsh_for_each_udev(pos, hpriv, i) \ + __usbhsh_for_each_udev(1, pos, hpriv, i) + +#define usbhsh_for_each_udev_with_dev0(pos, hpriv, i) \ + __usbhsh_for_each_udev(0, pos, hpriv, i) + +#define usbhsh_hcd_to_hpriv(h) (struct usbhsh_hpriv *)((h)->hcd_priv) +#define usbhsh_hcd_to_dev(h) ((h)->self.controller) + +#define usbhsh_hpriv_to_priv(h) ((h)->mod.priv) +#define usbhsh_hpriv_to_dcp(h) ((h)->dcp) +#define usbhsh_hpriv_to_hcd(h) \ + container_of((void *)h, struct usb_hcd, hcd_priv) + +#define usbhsh_ep_to_uep(u) ((u)->hcpriv) +#define usbhsh_uep_to_pipe(u) ((u)->pipe) +#define usbhsh_uep_to_udev(u) ((u)->udev) +#define usbhsh_uep_to_ep(u) ((u)->ep) + +#define usbhsh_urb_to_ureq(u) ((u)->hcpriv) +#define usbhsh_urb_to_usbv(u) ((u)->dev) + +#define usbhsh_usbv_to_udev(d) dev_get_drvdata(&(d)->dev) + +#define usbhsh_udev_to_usbv(h) ((h)->usbv) +#define usbhsh_udev_is_used(h) usbhsh_udev_to_usbv(h) + +#define usbhsh_pipe_to_uep(p) ((p)->mod_private) + +#define usbhsh_device_parent(d) (usbhsh_usbv_to_udev((d)->usbv->parent)) +#define usbhsh_device_hubport(d) ((d)->usbv->portnum) +#define usbhsh_device_number(h, d) ((int)((d) - (h)->udev)) +#define usbhsh_device_nth(h, d) ((h)->udev + d) +#define usbhsh_device0(h) usbhsh_device_nth(h, 0) + +#define usbhsh_port_stat_init(h) ((h)->port_stat = 0) +#define usbhsh_port_stat_set(h, s) ((h)->port_stat |= (s)) +#define usbhsh_port_stat_clear(h, s) ((h)->port_stat &= ~(s)) +#define usbhsh_port_stat_get(h) ((h)->port_stat) + +#define usbhsh_pkt_to_ureq(p) \ + container_of((void *)p, struct usbhsh_request, pkt) + +/* + * req alloc/free + */ +static struct usbhsh_request *usbhsh_ureq_alloc(struct usbhsh_hpriv *hpriv, + struct urb *urb, + gfp_t mem_flags) +{ + struct usbhsh_request *ureq; + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct device *dev = usbhs_priv_to_dev(priv); + + ureq = kzalloc(sizeof(struct usbhsh_request), mem_flags); + if (!ureq) { + dev_err(dev, "ureq alloc fail\n"); + return NULL; + } + + usbhs_pkt_init(&ureq->pkt); + ureq->urb = urb; + usbhsh_urb_to_ureq(urb) = ureq; + + return ureq; +} + +static void usbhsh_ureq_free(struct usbhsh_hpriv *hpriv, + struct usbhsh_request *ureq) +{ + usbhsh_urb_to_ureq(ureq->urb) = NULL; + ureq->urb = NULL; + + kfree(ureq); +} + +/* + * status + */ +static int usbhsh_is_running(struct usbhsh_hpriv *hpriv) +{ + /* + * we can decide some device is attached or not + * by checking mod.irq_attch + * see + * usbhsh_irq_attch() + * usbhsh_irq_dtch() + */ + return (hpriv->mod.irq_attch == NULL); +} + +/* + * pipe control + */ +static void usbhsh_endpoint_sequence_save(struct usbhsh_hpriv *hpriv, + struct urb *urb, + struct usbhs_pkt *pkt) +{ + int len = urb->actual_length; + int maxp = usb_endpoint_maxp(&urb->ep->desc); + int t = 0; + + /* DCP is out of sequence control */ + if (usb_pipecontrol(urb->pipe)) + return; + + /* + * renesas_usbhs pipe has a limitation in a number. + * So, driver should re-use the limited pipe for each device/endpoint. + * DATA0/1 sequence should be saved for it. + * see [image of mod_host] + * [HARDWARE LIMITATION] + */ + + /* + * next sequence depends on actual_length + * + * ex) actual_length = 1147, maxp = 512 + * data0 : 512 + * data1 : 512 + * data0 : 123 + * data1 is the next sequence + */ + t = len / maxp; + if (len % maxp) + t++; + if (pkt->zero) + t++; + t %= 2; + + if (t) + usb_dotoggle(urb->dev, + usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe)); +} + +static struct usbhsh_device *usbhsh_device_get(struct usbhsh_hpriv *hpriv, + struct urb *urb); + +static int usbhsh_pipe_attach(struct usbhsh_hpriv *hpriv, + struct urb *urb) +{ + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct usbhsh_ep *uep = usbhsh_ep_to_uep(urb->ep); + struct usbhsh_device *udev = usbhsh_device_get(hpriv, urb); + struct usbhs_pipe *pipe; + struct usb_endpoint_descriptor *desc = &urb->ep->desc; + struct device *dev = usbhs_priv_to_dev(priv); + unsigned long flags; + int dir_in_req = !!usb_pipein(urb->pipe); + int is_dcp = usb_endpoint_xfer_control(desc); + int i, dir_in; + int ret = -EBUSY; + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + /* + * if uep has been attached to pipe, + * reuse it + */ + if (usbhsh_uep_to_pipe(uep)) { + ret = 0; + goto usbhsh_pipe_attach_done; + } + + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + + /* check pipe type */ + if (!usbhs_pipe_type_is(pipe, usb_endpoint_type(desc))) + continue; + + /* check pipe direction if normal pipe */ + if (!is_dcp) { + dir_in = !!usbhs_pipe_is_dir_in(pipe); + if (0 != (dir_in - dir_in_req)) + continue; + } + + /* check pipe is free */ + if (usbhsh_pipe_to_uep(pipe)) + continue; + + /* + * attach pipe to uep + * + * usbhs_pipe_config_update() should be called after + * usbhs_set_device_config() + * see + * DCPMAXP/PIPEMAXP + */ + usbhsh_uep_to_pipe(uep) = pipe; + usbhsh_pipe_to_uep(pipe) = uep; + + usbhs_pipe_config_update(pipe, + usbhsh_device_number(hpriv, udev), + usb_endpoint_num(desc), + usb_endpoint_maxp(desc)); + + dev_dbg(dev, "%s [%d-%d(%s:%s)]\n", __func__, + usbhsh_device_number(hpriv, udev), + usb_endpoint_num(desc), + usbhs_pipe_name(pipe), + dir_in_req ? "in" : "out"); + + ret = 0; + break; + } + +usbhsh_pipe_attach_done: + if (0 == ret) + uep->counter++; + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + return ret; +} + +static void usbhsh_pipe_detach(struct usbhsh_hpriv *hpriv, + struct usbhsh_ep *uep) +{ + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct usbhs_pipe *pipe; + struct device *dev = usbhs_priv_to_dev(priv); + unsigned long flags; + + if (unlikely(!uep)) { + dev_err(dev, "no uep\n"); + return; + } + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + pipe = usbhsh_uep_to_pipe(uep); + + if (unlikely(!pipe)) { + dev_err(dev, "uep doens't have pipe\n"); + } else if (1 == uep->counter--) { /* last user */ + struct usb_host_endpoint *ep = usbhsh_uep_to_ep(uep); + struct usbhsh_device *udev = usbhsh_uep_to_udev(uep); + + /* detach pipe from uep */ + usbhsh_uep_to_pipe(uep) = NULL; + usbhsh_pipe_to_uep(pipe) = NULL; + + dev_dbg(dev, "%s [%d-%d(%s)]\n", __func__, + usbhsh_device_number(hpriv, udev), + usb_endpoint_num(&ep->desc), + usbhs_pipe_name(pipe)); + } + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ +} + +/* + * endpoint control + */ +static int usbhsh_endpoint_attach(struct usbhsh_hpriv *hpriv, + struct urb *urb, + gfp_t mem_flags) +{ + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct usbhsh_device *udev = usbhsh_device_get(hpriv, urb); + struct usb_host_endpoint *ep = urb->ep; + struct usbhsh_ep *uep; + struct device *dev = usbhs_priv_to_dev(priv); + struct usb_endpoint_descriptor *desc = &ep->desc; + unsigned long flags; + + uep = kzalloc(sizeof(struct usbhsh_ep), mem_flags); + if (!uep) { + dev_err(dev, "usbhsh_ep alloc fail\n"); + return -ENOMEM; + } + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + /* + * init endpoint + */ + uep->counter = 0; + INIT_LIST_HEAD(&uep->ep_list); + list_add_tail(&uep->ep_list, &udev->ep_list_head); + + usbhsh_uep_to_udev(uep) = udev; + usbhsh_uep_to_ep(uep) = ep; + usbhsh_ep_to_uep(ep) = uep; + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + dev_dbg(dev, "%s [%d-%d]\n", __func__, + usbhsh_device_number(hpriv, udev), + usb_endpoint_num(desc)); + + return 0; +} + +static void usbhsh_endpoint_detach(struct usbhsh_hpriv *hpriv, + struct usb_host_endpoint *ep) +{ + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct device *dev = usbhs_priv_to_dev(priv); + struct usbhsh_ep *uep = usbhsh_ep_to_uep(ep); + unsigned long flags; + + if (!uep) + return; + + dev_dbg(dev, "%s [%d-%d]\n", __func__, + usbhsh_device_number(hpriv, usbhsh_uep_to_udev(uep)), + usb_endpoint_num(&ep->desc)); + + if (usbhsh_uep_to_pipe(uep)) + usbhsh_pipe_detach(hpriv, uep); + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + /* remove this endpoint from udev */ + list_del_init(&uep->ep_list); + + usbhsh_uep_to_udev(uep) = NULL; + usbhsh_uep_to_ep(uep) = NULL; + usbhsh_ep_to_uep(ep) = NULL; + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + kfree(uep); +} + +static void usbhsh_endpoint_detach_all(struct usbhsh_hpriv *hpriv, + struct usbhsh_device *udev) +{ + struct usbhsh_ep *uep, *next; + + list_for_each_entry_safe(uep, next, &udev->ep_list_head, ep_list) + usbhsh_endpoint_detach(hpriv, usbhsh_uep_to_ep(uep)); +} + +/* + * device control + */ +static int usbhsh_connected_to_rhdev(struct usb_hcd *hcd, + struct usbhsh_device *udev) +{ + struct usb_device *usbv = usbhsh_udev_to_usbv(udev); + + return hcd->self.root_hub == usbv->parent; +} + +static int usbhsh_device_has_endpoint(struct usbhsh_device *udev) +{ + return !list_empty(&udev->ep_list_head); +} + +static struct usbhsh_device *usbhsh_device_get(struct usbhsh_hpriv *hpriv, + struct urb *urb) +{ + struct usb_device *usbv = usbhsh_urb_to_usbv(urb); + struct usbhsh_device *udev = usbhsh_usbv_to_udev(usbv); + + /* usbhsh_device_attach() is still not called */ + if (!udev) + return NULL; + + /* if it is device0, return it */ + if (0 == usb_pipedevice(urb->pipe)) + return usbhsh_device0(hpriv); + + /* return attached device */ + return udev; +} + +static struct usbhsh_device *usbhsh_device_attach(struct usbhsh_hpriv *hpriv, + struct urb *urb) +{ + struct usbhsh_device *udev = NULL; + struct usbhsh_device *udev0 = usbhsh_device0(hpriv); + struct usbhsh_device *pos; + struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); + struct device *dev = usbhsh_hcd_to_dev(hcd); + struct usb_device *usbv = usbhsh_urb_to_usbv(urb); + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + unsigned long flags; + u16 upphub, hubport; + int i; + + /* + * This function should be called only while urb is pointing to device0. + * It will attach unused usbhsh_device to urb (usbv), + * and initialize device0. + * You can use usbhsh_device_get() to get "current" udev, + * and usbhsh_usbv_to_udev() is for "attached" udev. + */ + if (0 != usb_pipedevice(urb->pipe)) { + dev_err(dev, "%s fail: urb isn't pointing device0\n", __func__); + return NULL; + } + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + /* + * find unused device + */ + usbhsh_for_each_udev(pos, hpriv, i) { + if (usbhsh_udev_is_used(pos)) + continue; + udev = pos; + break; + } + + if (udev) { + /* + * usbhsh_usbv_to_udev() + * usbhsh_udev_to_usbv() + * will be enable + */ + dev_set_drvdata(&usbv->dev, udev); + udev->usbv = usbv; + } + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + if (!udev) { + dev_err(dev, "no free usbhsh_device\n"); + return NULL; + } + + if (usbhsh_device_has_endpoint(udev)) { + dev_warn(dev, "udev have old endpoint\n"); + usbhsh_endpoint_detach_all(hpriv, udev); + } + + if (usbhsh_device_has_endpoint(udev0)) { + dev_warn(dev, "udev0 have old endpoint\n"); + usbhsh_endpoint_detach_all(hpriv, udev0); + } + + /* uep will be attached */ + INIT_LIST_HEAD(&udev0->ep_list_head); + INIT_LIST_HEAD(&udev->ep_list_head); + + /* + * set device0 config + */ + usbhs_set_device_config(priv, + 0, 0, 0, usbv->speed); + + /* + * set new device config + */ + upphub = 0; + hubport = 0; + if (!usbhsh_connected_to_rhdev(hcd, udev)) { + /* if udev is not connected to rhdev, it means parent is Hub */ + struct usbhsh_device *parent = usbhsh_device_parent(udev); + + upphub = usbhsh_device_number(hpriv, parent); + hubport = usbhsh_device_hubport(udev); + + dev_dbg(dev, "%s connecte to Hub [%d:%d](%p)\n", __func__, + upphub, hubport, parent); + } + + usbhs_set_device_config(priv, + usbhsh_device_number(hpriv, udev), + upphub, hubport, usbv->speed); + + dev_dbg(dev, "%s [%d](%p)\n", __func__, + usbhsh_device_number(hpriv, udev), udev); + + return udev; +} + +static void usbhsh_device_detach(struct usbhsh_hpriv *hpriv, + struct usbhsh_device *udev) +{ + struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct device *dev = usbhsh_hcd_to_dev(hcd); + struct usb_device *usbv = usbhsh_udev_to_usbv(udev); + unsigned long flags; + + dev_dbg(dev, "%s [%d](%p)\n", __func__, + usbhsh_device_number(hpriv, udev), udev); + + if (usbhsh_device_has_endpoint(udev)) { + dev_warn(dev, "udev still have endpoint\n"); + usbhsh_endpoint_detach_all(hpriv, udev); + } + + /* + * There is nothing to do if it is device0. + * see + * usbhsh_device_attach() + * usbhsh_device_get() + */ + if (0 == usbhsh_device_number(hpriv, udev)) + return; + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + /* + * usbhsh_usbv_to_udev() + * usbhsh_udev_to_usbv() + * will be disable + */ + dev_set_drvdata(&usbv->dev, NULL); + udev->usbv = NULL; + + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ +} + +/* + * queue push/pop + */ +static void usbhsh_queue_done(struct usbhs_priv *priv, struct usbhs_pkt *pkt) +{ + struct usbhsh_request *ureq = usbhsh_pkt_to_ureq(pkt); + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); + struct urb *urb = ureq->urb; + struct device *dev = usbhs_priv_to_dev(priv); + int status = 0; + + dev_dbg(dev, "%s\n", __func__); + + if (!urb) { + dev_warn(dev, "pkt doesn't have urb\n"); + return; + } + + if (!usbhsh_is_running(hpriv)) + status = -ESHUTDOWN; + + urb->actual_length = pkt->actual; + + usbhsh_endpoint_sequence_save(hpriv, urb, pkt); + usbhsh_ureq_free(hpriv, ureq); + + usbhsh_pipe_detach(hpriv, usbhsh_ep_to_uep(urb->ep)); + + usb_hcd_unlink_urb_from_ep(hcd, urb); + usb_hcd_giveback_urb(hcd, urb, status); +} + +static int usbhsh_queue_push(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); + struct usbhsh_ep *uep = usbhsh_ep_to_uep(urb->ep); + struct usbhs_pipe *pipe = usbhsh_uep_to_pipe(uep); + struct device *dev = usbhsh_hcd_to_dev(hcd); + struct usbhsh_request *ureq; + void *buf; + int len, sequence; + + if (usb_pipeisoc(urb->pipe)) { + dev_err(dev, "pipe iso is not supported now\n"); + return -EIO; + } + + /* this ureq will be freed on usbhsh_queue_done() */ + ureq = usbhsh_ureq_alloc(hpriv, urb, mem_flags); + if (unlikely(!ureq)) { + dev_err(dev, "ureq alloc fail\n"); + return -ENOMEM; + } + + if (usb_pipein(urb->pipe)) + pipe->handler = &usbhs_fifo_dma_pop_handler; + else + pipe->handler = &usbhs_fifo_dma_push_handler; + + buf = (void *)(urb->transfer_buffer + urb->actual_length); + len = urb->transfer_buffer_length - urb->actual_length; + + sequence = usb_gettoggle(urb->dev, + usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe)); + + dev_dbg(dev, "%s\n", __func__); + usbhs_pkt_push(pipe, &ureq->pkt, usbhsh_queue_done, + buf, len, (urb->transfer_flags & URB_ZERO_PACKET), + sequence); + + usbhs_pkt_start(pipe); + + return 0; +} + +static void usbhsh_queue_force_pop(struct usbhs_priv *priv, + struct usbhs_pipe *pipe) +{ + struct usbhs_pkt *pkt; + + while (1) { + pkt = usbhs_pkt_pop(pipe, NULL); + if (!pkt) + break; + + /* + * if all packet are gone, usbhsh_endpoint_disable() + * will be called. + * then, attached device/endpoint/pipe will be detached + */ + usbhsh_queue_done(priv, pkt); + } +} + +static void usbhsh_queue_force_pop_all(struct usbhs_priv *priv) +{ + struct usbhs_pipe *pos; + int i; + + usbhs_for_each_pipe_with_dcp(pos, priv, i) + usbhsh_queue_force_pop(priv, pos); +} + +/* + * DCP setup stage + */ +static int usbhsh_is_request_address(struct urb *urb) +{ + struct usb_ctrlrequest *req; + + req = (struct usb_ctrlrequest *)urb->setup_packet; + + if ((DeviceOutRequest == req->bRequestType << 8) && + (USB_REQ_SET_ADDRESS == req->bRequest)) + return 1; + else + return 0; +} + +static void usbhsh_setup_stage_packet_push(struct usbhsh_hpriv *hpriv, + struct urb *urb, + struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct usb_ctrlrequest req; + struct device *dev = usbhs_priv_to_dev(priv); + + /* + * wait setup packet ACK + * see + * usbhsh_irq_setup_ack() + * usbhsh_irq_setup_err() + */ + init_completion(&hpriv->setup_ack_done); + + /* copy original request */ + memcpy(&req, urb->setup_packet, sizeof(struct usb_ctrlrequest)); + + /* + * renesas_usbhs can not use original usb address. + * see HARDWARE LIMITATION. + * modify usb address here to use attached device. + * see usbhsh_device_attach() + */ + if (usbhsh_is_request_address(urb)) { + struct usb_device *usbv = usbhsh_urb_to_usbv(urb); + struct usbhsh_device *udev = usbhsh_usbv_to_udev(usbv); + + /* udev is a attached device */ + req.wValue = usbhsh_device_number(hpriv, udev); + dev_dbg(dev, "create new address - %d\n", req.wValue); + } + + /* set request */ + usbhs_usbreq_set_val(priv, &req); + + /* + * wait setup packet ACK + */ + wait_for_completion(&hpriv->setup_ack_done); + + dev_dbg(dev, "%s done\n", __func__); +} + +/* + * DCP data stage + */ +static void usbhsh_data_stage_packet_done(struct usbhs_priv *priv, + struct usbhs_pkt *pkt) +{ + struct usbhsh_request *ureq = usbhsh_pkt_to_ureq(pkt); + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + + /* this ureq was connected to urb when usbhsh_urb_enqueue() */ + + usbhsh_ureq_free(hpriv, ureq); +} + +static int usbhsh_data_stage_packet_push(struct usbhsh_hpriv *hpriv, + struct urb *urb, + struct usbhs_pipe *pipe, + gfp_t mem_flags) + +{ + struct usbhsh_request *ureq; + + /* this ureq will be freed on usbhsh_data_stage_packet_done() */ + ureq = usbhsh_ureq_alloc(hpriv, urb, mem_flags); + if (unlikely(!ureq)) + return -ENOMEM; + + if (usb_pipein(urb->pipe)) + pipe->handler = &usbhs_dcp_data_stage_in_handler; + else + pipe->handler = &usbhs_dcp_data_stage_out_handler; + + usbhs_pkt_push(pipe, &ureq->pkt, + usbhsh_data_stage_packet_done, + urb->transfer_buffer, + urb->transfer_buffer_length, + (urb->transfer_flags & URB_ZERO_PACKET), + -1); + + return 0; +} + +/* + * DCP status stage + */ +static int usbhsh_status_stage_packet_push(struct usbhsh_hpriv *hpriv, + struct urb *urb, + struct usbhs_pipe *pipe, + gfp_t mem_flags) +{ + struct usbhsh_request *ureq; + + /* This ureq will be freed on usbhsh_queue_done() */ + ureq = usbhsh_ureq_alloc(hpriv, urb, mem_flags); + if (unlikely(!ureq)) + return -ENOMEM; + + if (usb_pipein(urb->pipe)) + pipe->handler = &usbhs_dcp_status_stage_in_handler; + else + pipe->handler = &usbhs_dcp_status_stage_out_handler; + + usbhs_pkt_push(pipe, &ureq->pkt, + usbhsh_queue_done, + NULL, + urb->transfer_buffer_length, + 0, -1); + + return 0; +} + +static int usbhsh_dcp_queue_push(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mflags) +{ + struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); + struct usbhsh_ep *uep = usbhsh_ep_to_uep(urb->ep); + struct usbhs_pipe *pipe = usbhsh_uep_to_pipe(uep); + struct device *dev = usbhsh_hcd_to_dev(hcd); + int ret; + + dev_dbg(dev, "%s\n", __func__); + + /* + * setup stage + * + * usbhsh_send_setup_stage_packet() wait SACK/SIGN + */ + usbhsh_setup_stage_packet_push(hpriv, urb, pipe); + + /* + * data stage + * + * It is pushed only when urb has buffer. + */ + if (urb->transfer_buffer_length) { + ret = usbhsh_data_stage_packet_push(hpriv, urb, pipe, mflags); + if (ret < 0) { + dev_err(dev, "data stage failed\n"); + return ret; + } + } + + /* + * status stage + */ + ret = usbhsh_status_stage_packet_push(hpriv, urb, pipe, mflags); + if (ret < 0) { + dev_err(dev, "status stage failed\n"); + return ret; + } + + /* + * start pushed packets + */ + usbhs_pkt_start(pipe); + + return 0; +} + +/* + * dma map functions + */ +static int usbhsh_dma_map_ctrl(struct usbhs_pkt *pkt, int map) +{ + if (map) { + struct usbhsh_request *ureq = usbhsh_pkt_to_ureq(pkt); + struct urb *urb = ureq->urb; + + /* it can not use scatter/gather */ + if (urb->num_sgs) + return -EINVAL; + + pkt->dma = urb->transfer_dma; + if (!pkt->dma) + return -EINVAL; + } + + return 0; +} + +/* + * for hc_driver + */ +static int usbhsh_host_start(struct usb_hcd *hcd) +{ + return 0; +} + +static void usbhsh_host_stop(struct usb_hcd *hcd) +{ +} + +static int usbhsh_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct device *dev = usbhs_priv_to_dev(priv); + struct usb_host_endpoint *ep = urb->ep; + struct usbhsh_device *new_udev = NULL; + int is_dir_in = usb_pipein(urb->pipe); + int ret; + + dev_dbg(dev, "%s (%s)\n", __func__, is_dir_in ? "in" : "out"); + + if (!usbhsh_is_running(hpriv)) { + ret = -EIO; + dev_err(dev, "host is not running\n"); + goto usbhsh_urb_enqueue_error_not_linked; + } + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) { + dev_err(dev, "urb link failed\n"); + goto usbhsh_urb_enqueue_error_not_linked; + } + + /* + * attach udev if needed + * see [image of mod_host] + */ + if (!usbhsh_device_get(hpriv, urb)) { + new_udev = usbhsh_device_attach(hpriv, urb); + if (!new_udev) { + ret = -EIO; + dev_err(dev, "device attach failed\n"); + goto usbhsh_urb_enqueue_error_not_linked; + } + } + + /* + * attach endpoint if needed + * see [image of mod_host] + */ + if (!usbhsh_ep_to_uep(ep)) { + ret = usbhsh_endpoint_attach(hpriv, urb, mem_flags); + if (ret < 0) { + dev_err(dev, "endpoint attach failed\n"); + goto usbhsh_urb_enqueue_error_free_device; + } + } + + /* + * attach pipe to endpoint + * see [image of mod_host] + */ + ret = usbhsh_pipe_attach(hpriv, urb); + if (ret < 0) { + dev_err(dev, "pipe attach failed\n"); + goto usbhsh_urb_enqueue_error_free_endpoint; + } + + /* + * push packet + */ + if (usb_pipecontrol(urb->pipe)) + ret = usbhsh_dcp_queue_push(hcd, urb, mem_flags); + else + ret = usbhsh_queue_push(hcd, urb, mem_flags); + + return ret; + +usbhsh_urb_enqueue_error_free_endpoint: + usbhsh_endpoint_detach(hpriv, ep); +usbhsh_urb_enqueue_error_free_device: + if (new_udev) + usbhsh_device_detach(hpriv, new_udev); +usbhsh_urb_enqueue_error_not_linked: + + dev_dbg(dev, "%s error\n", __func__); + + return ret; +} + +static int usbhsh_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); + struct usbhsh_request *ureq = usbhsh_urb_to_ureq(urb); + + if (ureq) { + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct usbhs_pkt *pkt = &ureq->pkt; + + usbhs_pkt_pop(pkt->pipe, pkt); + usbhsh_queue_done(priv, pkt); + } + + return 0; +} + +static void usbhsh_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct usbhsh_ep *uep = usbhsh_ep_to_uep(ep); + struct usbhsh_device *udev; + struct usbhsh_hpriv *hpriv; + + /* + * this function might be called manytimes by same hcd/ep + * in-endpoint == out-endpoint if ep == dcp. + */ + if (!uep) + return; + + udev = usbhsh_uep_to_udev(uep); + hpriv = usbhsh_hcd_to_hpriv(hcd); + + usbhsh_endpoint_detach(hpriv, ep); + + /* + * if there is no endpoint, + * free device + */ + if (!usbhsh_device_has_endpoint(udev)) + usbhsh_device_detach(hpriv, udev); +} + +static int usbhsh_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); + int roothub_id = 1; /* only 1 root hub */ + + /* + * does port stat was changed ? + * check USB_PORT_STAT_C_xxx << 16 + */ + if (usbhsh_port_stat_get(hpriv) & 0xFFFF0000) + *buf = (1 << roothub_id); + else + *buf = 0; + + return !!(*buf); +} + +static int __usbhsh_hub_hub_feature(struct usbhsh_hpriv *hpriv, + u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct device *dev = usbhs_priv_to_dev(priv); + + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + dev_dbg(dev, "%s :: C_HUB_xx\n", __func__); + return 0; + } + + return -EPIPE; +} + +static int __usbhsh_hub_port_feature(struct usbhsh_hpriv *hpriv, + u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct device *dev = usbhs_priv_to_dev(priv); + int enable = (typeReq == SetPortFeature); + int speed, i, timeout = 128; + int roothub_id = 1; /* only 1 root hub */ + + /* common error */ + if (wIndex > roothub_id || wLength != 0) + return -EPIPE; + + /* check wValue */ + switch (wValue) { + case USB_PORT_FEAT_POWER: + usbhs_vbus_ctrl(priv, enable); + dev_dbg(dev, "%s :: USB_PORT_FEAT_POWER\n", __func__); + break; + + case USB_PORT_FEAT_ENABLE: + case USB_PORT_FEAT_SUSPEND: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_RESET: + dev_dbg(dev, "%s :: USB_PORT_FEAT_xxx\n", __func__); + break; + + case USB_PORT_FEAT_RESET: + if (!enable) + break; + + usbhsh_port_stat_clear(hpriv, + USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_LOW_SPEED); + + usbhsh_queue_force_pop_all(priv); + + usbhs_bus_send_reset(priv); + msleep(20); + usbhs_bus_send_sof_enable(priv); + + for (i = 0; i < timeout ; i++) { + switch (usbhs_bus_get_speed(priv)) { + case USB_SPEED_LOW: + speed = USB_PORT_STAT_LOW_SPEED; + goto got_usb_bus_speed; + case USB_SPEED_HIGH: + speed = USB_PORT_STAT_HIGH_SPEED; + goto got_usb_bus_speed; + case USB_SPEED_FULL: + speed = 0; + goto got_usb_bus_speed; + } + + msleep(20); + } + return -EPIPE; + +got_usb_bus_speed: + usbhsh_port_stat_set(hpriv, speed); + usbhsh_port_stat_set(hpriv, USB_PORT_STAT_ENABLE); + + dev_dbg(dev, "%s :: USB_PORT_FEAT_RESET (speed = %d)\n", + __func__, speed); + + /* status change is not needed */ + return 0; + + default: + return -EPIPE; + } + + /* set/clear status */ + if (enable) + usbhsh_port_stat_set(hpriv, (1 << wValue)); + else + usbhsh_port_stat_clear(hpriv, (1 << wValue)); + + return 0; +} + +static int __usbhsh_hub_get_status(struct usbhsh_hpriv *hpriv, + u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct usb_hub_descriptor *desc = (struct usb_hub_descriptor *)buf; + struct device *dev = usbhs_priv_to_dev(priv); + int roothub_id = 1; /* only 1 root hub */ + + switch (typeReq) { + case GetHubStatus: + dev_dbg(dev, "%s :: GetHubStatus\n", __func__); + + *buf = 0x00; + break; + + case GetPortStatus: + if (wIndex != roothub_id) + return -EPIPE; + + dev_dbg(dev, "%s :: GetPortStatus\n", __func__); + *(__le32 *)buf = cpu_to_le32(usbhsh_port_stat_get(hpriv)); + break; + + case GetHubDescriptor: + desc->bDescriptorType = 0x29; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = roothub_id; + desc->bDescLength = 9; + desc->bPwrOn2PwrGood = 0; + desc->wHubCharacteristics = cpu_to_le16(0x0011); + desc->u.hs.DeviceRemovable[0] = (roothub_id << 1); + desc->u.hs.DeviceRemovable[1] = ~0; + dev_dbg(dev, "%s :: GetHubDescriptor\n", __func__); + break; + } + + return 0; +} + +static int usbhsh_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); + struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); + struct device *dev = usbhs_priv_to_dev(priv); + int ret = -EPIPE; + + switch (typeReq) { + + /* Hub Feature */ + case ClearHubFeature: + case SetHubFeature: + ret = __usbhsh_hub_hub_feature(hpriv, typeReq, + wValue, wIndex, buf, wLength); + break; + + /* Port Feature */ + case SetPortFeature: + case ClearPortFeature: + ret = __usbhsh_hub_port_feature(hpriv, typeReq, + wValue, wIndex, buf, wLength); + break; + + /* Get status */ + case GetHubStatus: + case GetPortStatus: + case GetHubDescriptor: + ret = __usbhsh_hub_get_status(hpriv, typeReq, + wValue, wIndex, buf, wLength); + break; + } + + dev_dbg(dev, "typeReq = %x, ret = %d, port_stat = %x\n", + typeReq, ret, usbhsh_port_stat_get(hpriv)); + + return ret; +} + +static int usbhsh_bus_nop(struct usb_hcd *hcd) +{ + /* nothing to do */ + return 0; +} + +static struct hc_driver usbhsh_driver = { + .description = usbhsh_hcd_name, + .hcd_priv_size = sizeof(struct usbhsh_hpriv), + + /* + * generic hardware linkage + */ + .flags = HCD_USB2, + + .start = usbhsh_host_start, + .stop = usbhsh_host_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = usbhsh_urb_enqueue, + .urb_dequeue = usbhsh_urb_dequeue, + .endpoint_disable = usbhsh_endpoint_disable, + + /* + * root hub + */ + .hub_status_data = usbhsh_hub_status_data, + .hub_control = usbhsh_hub_control, + .bus_suspend = usbhsh_bus_nop, + .bus_resume = usbhsh_bus_nop, +}; + +/* + * interrupt functions + */ +static int usbhsh_irq_attch(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct device *dev = usbhs_priv_to_dev(priv); + + dev_dbg(dev, "device attached\n"); + + usbhsh_port_stat_set(hpriv, USB_PORT_STAT_CONNECTION); + usbhsh_port_stat_set(hpriv, USB_PORT_STAT_C_CONNECTION << 16); + + /* + * attch interrupt might happen infinitely on some device + * (on self power USB hub ?) + * disable it here. + * + * usbhsh_is_running() becomes effective + * according to this process. + * see + * usbhsh_is_running() + * usbhsh_urb_enqueue() + */ + hpriv->mod.irq_attch = NULL; + usbhs_irq_callback_update(priv, &hpriv->mod); + + return 0; +} + +static int usbhsh_irq_dtch(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct device *dev = usbhs_priv_to_dev(priv); + + dev_dbg(dev, "device detached\n"); + + usbhsh_port_stat_clear(hpriv, USB_PORT_STAT_CONNECTION); + usbhsh_port_stat_set(hpriv, USB_PORT_STAT_C_CONNECTION << 16); + + /* + * enable attch interrupt again + * + * usbhsh_is_running() becomes invalid + * according to this process. + * see + * usbhsh_is_running() + * usbhsh_urb_enqueue() + */ + hpriv->mod.irq_attch = usbhsh_irq_attch; + usbhs_irq_callback_update(priv, &hpriv->mod); + + /* + * usbhsh_queue_force_pop_all() should be called + * after usbhsh_is_running() becomes invalid. + */ + usbhsh_queue_force_pop_all(priv); + + return 0; +} + +static int usbhsh_irq_setup_ack(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct device *dev = usbhs_priv_to_dev(priv); + + dev_dbg(dev, "setup packet OK\n"); + + complete(&hpriv->setup_ack_done); /* see usbhsh_urb_enqueue() */ + + return 0; +} + +static int usbhsh_irq_setup_err(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct device *dev = usbhs_priv_to_dev(priv); + + dev_dbg(dev, "setup packet Err\n"); + + complete(&hpriv->setup_ack_done); /* see usbhsh_urb_enqueue() */ + + return 0; +} + +/* + * module start/stop + */ +static void usbhsh_pipe_init_for_host(struct usbhs_priv *priv) +{ + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct usbhs_pipe *pipe; + u32 *pipe_type = usbhs_get_dparam(priv, pipe_type); + int pipe_size = usbhs_get_dparam(priv, pipe_size); + int old_type, dir_in, i; + + /* init all pipe */ + old_type = USB_ENDPOINT_XFER_CONTROL; + for (i = 0; i < pipe_size; i++) { + + /* + * data "output" will be finished as soon as possible, + * but there is no guaranty at data "input" case. + * + * "input" needs "standby" pipe. + * So, "input" direction pipe > "output" direction pipe + * is good idea. + * + * 1st USB_ENDPOINT_XFER_xxx will be output direction, + * and the other will be input direction here. + * + * ex) + * ... + * USB_ENDPOINT_XFER_ISOC -> dir out + * USB_ENDPOINT_XFER_ISOC -> dir in + * USB_ENDPOINT_XFER_BULK -> dir out + * USB_ENDPOINT_XFER_BULK -> dir in + * USB_ENDPOINT_XFER_BULK -> dir in + * ... + */ + dir_in = (pipe_type[i] == old_type); + old_type = pipe_type[i]; + + if (USB_ENDPOINT_XFER_CONTROL == pipe_type[i]) { + pipe = usbhs_dcp_malloc(priv); + usbhsh_hpriv_to_dcp(hpriv) = pipe; + } else { + pipe = usbhs_pipe_malloc(priv, + pipe_type[i], + dir_in); + } + + pipe->mod_private = NULL; + } +} + +static int usbhsh_start(struct usbhs_priv *priv) +{ + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct device *dev = usbhs_priv_to_dev(priv); + int ret; + + /* add hcd */ + ret = usb_add_hcd(hcd, 0, 0); + if (ret < 0) + return 0; + + /* + * pipe initialize and enable DCP + */ + usbhs_pipe_init(priv, + usbhsh_dma_map_ctrl); + usbhs_fifo_init(priv); + usbhsh_pipe_init_for_host(priv); + + /* + * system config enble + * - HI speed + * - host + * - usb module + */ + usbhs_sys_host_ctrl(priv, 1); + + /* + * enable irq callback + */ + mod->irq_attch = usbhsh_irq_attch; + mod->irq_dtch = usbhsh_irq_dtch; + mod->irq_sack = usbhsh_irq_setup_ack; + mod->irq_sign = usbhsh_irq_setup_err; + usbhs_irq_callback_update(priv, mod); + + dev_dbg(dev, "start host\n"); + + return ret; +} + +static int usbhsh_stop(struct usbhs_priv *priv) +{ + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct device *dev = usbhs_priv_to_dev(priv); + + /* + * disable irq callback + */ + mod->irq_attch = NULL; + mod->irq_dtch = NULL; + mod->irq_sack = NULL; + mod->irq_sign = NULL; + usbhs_irq_callback_update(priv, mod); + + usb_remove_hcd(hcd); + + /* disable sys */ + usbhs_sys_host_ctrl(priv, 0); + + dev_dbg(dev, "quit host\n"); + + return 0; +} + +int usbhs_mod_host_probe(struct usbhs_priv *priv) +{ + struct usbhsh_hpriv *hpriv; + struct usb_hcd *hcd; + struct usbhsh_device *udev; + struct device *dev = usbhs_priv_to_dev(priv); + int i; + + /* initialize hcd */ + hcd = usb_create_hcd(&usbhsh_driver, dev, usbhsh_hcd_name); + if (!hcd) { + dev_err(dev, "Failed to create hcd\n"); + return -ENOMEM; + } + hcd->has_tt = 1; /* for low/full speed */ + + /* + * CAUTION + * + * There is no guarantee that it is possible to access usb module here. + * Don't accesses to it. + * The accesse will be enable after "usbhsh_start" + */ + + hpriv = usbhsh_hcd_to_hpriv(hcd); + + /* + * register itself + */ + usbhs_mod_register(priv, &hpriv->mod, USBHS_HOST); + + /* init hpriv */ + hpriv->mod.name = "host"; + hpriv->mod.start = usbhsh_start; + hpriv->mod.stop = usbhsh_stop; + usbhsh_port_stat_init(hpriv); + + /* init all device */ + usbhsh_for_each_udev_with_dev0(udev, hpriv, i) { + udev->usbv = NULL; + INIT_LIST_HEAD(&udev->ep_list_head); + } + + dev_info(dev, "host probed\n"); + + return 0; +} + +int usbhs_mod_host_remove(struct usbhs_priv *priv) +{ + struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); + + usb_put_hcd(hcd); + + return 0; +} diff --git a/drivers/usb/renesas_usbhs/pipe.c b/drivers/usb/renesas_usbhs/pipe.c new file mode 100644 index 00000000..7926e1c7 --- /dev/null +++ b/drivers/usb/renesas_usbhs/pipe.c @@ -0,0 +1,826 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include "common.h" +#include "pipe.h" + +/* + * macros + */ +#define usbhsp_addr_offset(p) ((usbhs_pipe_number(p) - 1) * 2) + +#define usbhsp_flags_set(p, f) ((p)->flags |= USBHS_PIPE_FLAGS_##f) +#define usbhsp_flags_clr(p, f) ((p)->flags &= ~USBHS_PIPE_FLAGS_##f) +#define usbhsp_flags_has(p, f) ((p)->flags & USBHS_PIPE_FLAGS_##f) +#define usbhsp_flags_init(p) do {(p)->flags = 0; } while (0) + +/* + * for debug + */ +static char *usbhsp_pipe_name[] = { + [USB_ENDPOINT_XFER_CONTROL] = "DCP", + [USB_ENDPOINT_XFER_BULK] = "BULK", + [USB_ENDPOINT_XFER_INT] = "INT", + [USB_ENDPOINT_XFER_ISOC] = "ISO", +}; + +char *usbhs_pipe_name(struct usbhs_pipe *pipe) +{ + return usbhsp_pipe_name[usbhs_pipe_type(pipe)]; +} + +/* + * DCPCTR/PIPEnCTR functions + */ +static void usbhsp_pipectrl_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + int offset = usbhsp_addr_offset(pipe); + + if (usbhs_pipe_is_dcp(pipe)) + usbhs_bset(priv, DCPCTR, mask, val); + else + usbhs_bset(priv, PIPEnCTR + offset, mask, val); +} + +static u16 usbhsp_pipectrl_get(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + int offset = usbhsp_addr_offset(pipe); + + if (usbhs_pipe_is_dcp(pipe)) + return usbhs_read(priv, DCPCTR); + else + return usbhs_read(priv, PIPEnCTR + offset); +} + +/* + * DCP/PIPE functions + */ +static void __usbhsp_pipe_xxx_set(struct usbhs_pipe *pipe, + u16 dcp_reg, u16 pipe_reg, + u16 mask, u16 val) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + + if (usbhs_pipe_is_dcp(pipe)) + usbhs_bset(priv, dcp_reg, mask, val); + else + usbhs_bset(priv, pipe_reg, mask, val); +} + +/* + * DCPCFG/PIPECFG functions + */ +static void usbhsp_pipe_cfg_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + __usbhsp_pipe_xxx_set(pipe, DCPCFG, PIPECFG, mask, val); +} + +/* + * PIPEnTRN/PIPEnTRE functions + */ +static void usbhsp_pipe_trn_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + int num = usbhs_pipe_number(pipe); + u16 reg; + + /* + * It is impossible to calculate address, + * since PIPEnTRN addresses were mapped randomly. + */ +#define CASE_PIPExTRN(a) \ + case 0x ## a: \ + reg = PIPE ## a ## TRN; \ + break; + + switch (num) { + CASE_PIPExTRN(1); + CASE_PIPExTRN(2); + CASE_PIPExTRN(3); + CASE_PIPExTRN(4); + CASE_PIPExTRN(5); + CASE_PIPExTRN(B); + CASE_PIPExTRN(C); + CASE_PIPExTRN(D); + CASE_PIPExTRN(E); + CASE_PIPExTRN(F); + CASE_PIPExTRN(9); + CASE_PIPExTRN(A); + default: + dev_err(dev, "unknown pipe (%d)\n", num); + return; + } + __usbhsp_pipe_xxx_set(pipe, 0, reg, mask, val); +} + +static void usbhsp_pipe_tre_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + int num = usbhs_pipe_number(pipe); + u16 reg; + + /* + * It is impossible to calculate address, + * since PIPEnTRE addresses were mapped randomly. + */ +#define CASE_PIPExTRE(a) \ + case 0x ## a: \ + reg = PIPE ## a ## TRE; \ + break; + + switch (num) { + CASE_PIPExTRE(1); + CASE_PIPExTRE(2); + CASE_PIPExTRE(3); + CASE_PIPExTRE(4); + CASE_PIPExTRE(5); + CASE_PIPExTRE(B); + CASE_PIPExTRE(C); + CASE_PIPExTRE(D); + CASE_PIPExTRE(E); + CASE_PIPExTRE(F); + CASE_PIPExTRE(9); + CASE_PIPExTRE(A); + default: + dev_err(dev, "unknown pipe (%d)\n", num); + return; + } + + __usbhsp_pipe_xxx_set(pipe, 0, reg, mask, val); +} + +/* + * PIPEBUF + */ +static void usbhsp_pipe_buf_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + if (usbhs_pipe_is_dcp(pipe)) + return; + + __usbhsp_pipe_xxx_set(pipe, 0, PIPEBUF, mask, val); +} + +/* + * DCPMAXP/PIPEMAXP + */ +static void usbhsp_pipe_maxp_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + __usbhsp_pipe_xxx_set(pipe, DCPMAXP, PIPEMAXP, mask, val); +} + +/* + * pipe control functions + */ +static void usbhsp_pipe_select(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + + /* + * On pipe, this is necessary before + * accesses to below registers. + * + * PIPESEL : usbhsp_pipe_select + * PIPECFG : usbhsp_pipe_cfg_xxx + * PIPEBUF : usbhsp_pipe_buf_xxx + * PIPEMAXP : usbhsp_pipe_maxp_xxx + * PIPEPERI + */ + + /* + * if pipe is dcp, no pipe is selected. + * it is no problem, because dcp have its register + */ + usbhs_write(priv, PIPESEL, 0xF & usbhs_pipe_number(pipe)); +} + +static int usbhsp_pipe_barrier(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + int timeout = 1024; + u16 val; + + /* + * make sure.... + * + * Modify these bits when CSSTS = 0, PID = NAK, and no pipe number is + * specified by the CURPIPE bits. + * When changing the setting of this bit after changing + * the PID bits for the selected pipe from BUF to NAK, + * check that CSSTS = 0 and PBUSY = 0. + */ + + /* + * CURPIPE bit = 0 + * + * see also + * "Operation" + * - "Pipe Control" + * - "Pipe Control Registers Switching Procedure" + */ + usbhs_write(priv, CFIFOSEL, 0); + usbhs_pipe_disable(pipe); + + do { + val = usbhsp_pipectrl_get(pipe); + val &= CSSTS | PID_MASK; + if (!val) + return 0; + + udelay(10); + + } while (timeout--); + + return -EBUSY; +} + +int usbhs_pipe_is_accessible(struct usbhs_pipe *pipe) +{ + u16 val; + + val = usbhsp_pipectrl_get(pipe); + if (val & BSTS) + return 0; + + return -EBUSY; +} + +/* + * PID ctrl + */ +static void __usbhsp_pid_try_nak_if_stall(struct usbhs_pipe *pipe) +{ + u16 pid = usbhsp_pipectrl_get(pipe); + + pid &= PID_MASK; + + /* + * see + * "Pipe n Control Register" - "PID" + */ + switch (pid) { + case PID_STALL11: + usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL10); + /* fall-through */ + case PID_STALL10: + usbhsp_pipectrl_set(pipe, PID_MASK, PID_NAK); + } +} + +void usbhs_pipe_disable(struct usbhs_pipe *pipe) +{ + int timeout = 1024; + u16 val; + + /* see "Pipe n Control Register" - "PID" */ + __usbhsp_pid_try_nak_if_stall(pipe); + + usbhsp_pipectrl_set(pipe, PID_MASK, PID_NAK); + + do { + val = usbhsp_pipectrl_get(pipe); + val &= PBUSY; + if (!val) + break; + + udelay(10); + } while (timeout--); +} + +void usbhs_pipe_enable(struct usbhs_pipe *pipe) +{ + /* see "Pipe n Control Register" - "PID" */ + __usbhsp_pid_try_nak_if_stall(pipe); + + usbhsp_pipectrl_set(pipe, PID_MASK, PID_BUF); +} + +void usbhs_pipe_stall(struct usbhs_pipe *pipe) +{ + u16 pid = usbhsp_pipectrl_get(pipe); + + pid &= PID_MASK; + + /* + * see + * "Pipe n Control Register" - "PID" + */ + switch (pid) { + case PID_NAK: + usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL10); + break; + case PID_BUF: + usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL11); + break; + } +} + +int usbhs_pipe_is_stall(struct usbhs_pipe *pipe) +{ + u16 pid = usbhsp_pipectrl_get(pipe) & PID_MASK; + + return (int)(pid == PID_STALL10 || pid == PID_STALL11); +} + +void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len) +{ + if (!usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) + return; + + /* + * clear and disable transfer counter for IN/OUT pipe + */ + usbhsp_pipe_tre_set(pipe, TRCLR | TRENB, TRCLR); + + /* + * Only IN direction bulk pipe can use transfer count. + * Without using this function, + * received data will break if it was large data size. + * see PIPEnTRN/PIPEnTRE for detail + */ + if (usbhs_pipe_is_dir_in(pipe)) { + int maxp = usbhs_pipe_get_maxpacket(pipe); + + usbhsp_pipe_trn_set(pipe, 0xffff, DIV_ROUND_UP(len, maxp)); + usbhsp_pipe_tre_set(pipe, TRENB, TRENB); /* enable */ + } +} + + +/* + * pipe setup + */ +static int usbhsp_possible_double_buffer(struct usbhs_pipe *pipe) +{ + /* + * only ISO / BULK pipe can use double buffer + */ + if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK) || + usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC)) + return 1; + + return 0; +} + +static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, + int is_host, + int dir_in) +{ + u16 type = 0; + u16 bfre = 0; + u16 dblb = 0; + u16 cntmd = 0; + u16 dir = 0; + u16 epnum = 0; + u16 shtnak = 0; + u16 type_array[] = { + [USB_ENDPOINT_XFER_BULK] = TYPE_BULK, + [USB_ENDPOINT_XFER_INT] = TYPE_INT, + [USB_ENDPOINT_XFER_ISOC] = TYPE_ISO, + }; + int is_double = usbhsp_possible_double_buffer(pipe); + + if (usbhs_pipe_is_dcp(pipe)) + return -EINVAL; + + /* + * PIPECFG + * + * see + * - "Register Descriptions" - "PIPECFG" register + * - "Features" - "Pipe configuration" + * - "Operation" - "Pipe Control" + */ + + /* TYPE */ + type = type_array[usbhs_pipe_type(pipe)]; + + /* BFRE */ + if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC) || + usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) + bfre = 0; /* FIXME */ + + /* DBLB */ + if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC) || + usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) + dblb = (is_double) ? DBLB : 0; + + /* CNTMD */ + if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) + cntmd = 0; /* FIXME */ + + /* DIR */ + if (dir_in) + usbhsp_flags_set(pipe, IS_DIR_HOST); + + if (!!is_host ^ !!dir_in) + dir |= DIR_OUT; + + if (!dir) + usbhsp_flags_set(pipe, IS_DIR_IN); + + /* SHTNAK */ + if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK) && + !dir) + shtnak = SHTNAK; + + /* EPNUM */ + epnum = 0; /* see usbhs_pipe_config_update() */ + + return type | + bfre | + dblb | + cntmd | + dir | + shtnak | + epnum; +} + +static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); + struct device *dev = usbhs_priv_to_dev(priv); + int pipe_num = usbhs_pipe_number(pipe); + int is_double = usbhsp_possible_double_buffer(pipe); + u16 buff_size; + u16 bufnmb; + u16 bufnmb_cnt; + + /* + * PIPEBUF + * + * see + * - "Register Descriptions" - "PIPEBUF" register + * - "Features" - "Pipe configuration" + * - "Operation" - "FIFO Buffer Memory" + * - "Operation" - "Pipe Control" + * + * ex) if pipe6 - pipe9 are USB_ENDPOINT_XFER_INT (SH7724) + * + * BUFNMB: PIPE + * 0: pipe0 (DCP 256byte) + * 1: - + * 2: - + * 3: - + * 4: pipe6 (INT 64byte) + * 5: pipe7 (INT 64byte) + * 6: pipe8 (INT 64byte) + * 7: pipe9 (INT 64byte) + * 8 - xx: free (for BULK, ISOC) + */ + + /* + * FIXME + * + * it doesn't have good buffer allocator + * + * DCP : 256 byte + * BULK: 512 byte + * INT : 64 byte + * ISOC: 512 byte + */ + if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_CONTROL)) + buff_size = 256; + else if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) + buff_size = 64; + else + buff_size = 512; + + /* change buff_size to register value */ + bufnmb_cnt = (buff_size / 64) - 1; + + /* BUFNMB has been reserved for INT pipe + * see above */ + if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) { + bufnmb = pipe_num - 2; + } else { + bufnmb = info->bufnmb_last; + info->bufnmb_last += bufnmb_cnt + 1; + + /* + * double buffer + */ + if (is_double) + info->bufnmb_last += bufnmb_cnt + 1; + } + + dev_dbg(dev, "pipe : %d : buff_size 0x%x: bufnmb 0x%x\n", + pipe_num, buff_size, bufnmb); + + return (0x1f & bufnmb_cnt) << 10 | + (0xff & bufnmb) << 0; +} + +void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, + u16 epnum, u16 maxp) +{ + if (devsel > 0xA) { + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + + dev_err(dev, "devsel error %d\n", devsel); + + devsel = 0; + } + + usbhsp_pipe_barrier(pipe); + + pipe->maxp = maxp; + + usbhsp_pipe_select(pipe); + usbhsp_pipe_maxp_set(pipe, 0xFFFF, + (devsel << 12) | + maxp); + + if (!usbhs_pipe_is_dcp(pipe)) + usbhsp_pipe_cfg_set(pipe, 0x000F, epnum); +} + +/* + * pipe control + */ +int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe) +{ + /* + * see + * usbhs_pipe_config_update() + * usbhs_dcp_malloc() + */ + return pipe->maxp; +} + +int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe) +{ + return usbhsp_flags_has(pipe, IS_DIR_IN); +} + +int usbhs_pipe_is_dir_host(struct usbhs_pipe *pipe) +{ + return usbhsp_flags_has(pipe, IS_DIR_HOST); +} + +void usbhs_pipe_data_sequence(struct usbhs_pipe *pipe, int sequence) +{ + u16 mask = (SQCLR | SQSET); + u16 val; + + /* + * sequence + * 0 : data0 + * 1 : data1 + * -1 : no change + */ + switch (sequence) { + case 0: + val = SQCLR; + break; + case 1: + val = SQSET; + break; + default: + return; + } + + usbhsp_pipectrl_set(pipe, mask, val); +} + +void usbhs_pipe_clear(struct usbhs_pipe *pipe) +{ + usbhsp_pipectrl_set(pipe, ACLRM, ACLRM); + usbhsp_pipectrl_set(pipe, ACLRM, 0); +} + +static struct usbhs_pipe *usbhsp_get_pipe(struct usbhs_priv *priv, u32 type) +{ + struct usbhs_pipe *pos, *pipe; + int i; + + /* + * find target pipe + */ + pipe = NULL; + usbhs_for_each_pipe_with_dcp(pos, priv, i) { + if (!usbhs_pipe_type_is(pos, type)) + continue; + if (usbhsp_flags_has(pos, IS_USED)) + continue; + + pipe = pos; + break; + } + + if (!pipe) + return NULL; + + /* + * initialize pipe flags + */ + usbhsp_flags_init(pipe); + usbhsp_flags_set(pipe, IS_USED); + + return pipe; +} + +void usbhs_pipe_init(struct usbhs_priv *priv, + int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map)) +{ + struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); + struct usbhs_pipe *pipe; + int i; + + /* + * FIXME + * + * driver needs good allocator. + * + * find first free buffer area (BULK, ISOC) + * (DCP, INT area is fixed) + * + * buffer number 0 - 3 have been reserved for DCP + * see + * usbhsp_to_bufnmb + */ + info->bufnmb_last = 4; + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) + info->bufnmb_last++; + + usbhsp_flags_init(pipe); + pipe->fifo = NULL; + pipe->mod_private = NULL; + INIT_LIST_HEAD(&pipe->list); + + /* pipe force init */ + usbhs_pipe_clear(pipe); + } + + info->dma_map_ctrl = dma_map_ctrl; +} + +struct usbhs_pipe *usbhs_pipe_malloc(struct usbhs_priv *priv, + int endpoint_type, + int dir_in) +{ + struct device *dev = usbhs_priv_to_dev(priv); + struct usbhs_pipe *pipe; + int is_host = usbhs_mod_is_host(priv); + int ret; + u16 pipecfg, pipebuf; + + pipe = usbhsp_get_pipe(priv, endpoint_type); + if (!pipe) { + dev_err(dev, "can't get pipe (%s)\n", + usbhsp_pipe_name[endpoint_type]); + return NULL; + } + + INIT_LIST_HEAD(&pipe->list); + + usbhs_pipe_disable(pipe); + + /* make sure pipe is not busy */ + ret = usbhsp_pipe_barrier(pipe); + if (ret < 0) { + dev_err(dev, "pipe setup failed %d\n", usbhs_pipe_number(pipe)); + return NULL; + } + + pipecfg = usbhsp_setup_pipecfg(pipe, is_host, dir_in); + pipebuf = usbhsp_setup_pipebuff(pipe); + + usbhsp_pipe_select(pipe); + usbhsp_pipe_cfg_set(pipe, 0xFFFF, pipecfg); + usbhsp_pipe_buf_set(pipe, 0xFFFF, pipebuf); + + usbhs_pipe_sequence_data0(pipe); + + dev_dbg(dev, "enable pipe %d : %s (%s)\n", + usbhs_pipe_number(pipe), + usbhs_pipe_name(pipe), + usbhs_pipe_is_dir_in(pipe) ? "in" : "out"); + + /* + * epnum / maxp are still not set to this pipe. + * call usbhs_pipe_config_update() after this function !! + */ + + return pipe; +} + +void usbhs_pipe_select_fifo(struct usbhs_pipe *pipe, struct usbhs_fifo *fifo) +{ + if (pipe->fifo) + pipe->fifo->pipe = NULL; + + pipe->fifo = fifo; + + if (fifo) + fifo->pipe = pipe; +} + + +/* + * dcp control + */ +struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv) +{ + struct usbhs_pipe *pipe; + + pipe = usbhsp_get_pipe(priv, USB_ENDPOINT_XFER_CONTROL); + if (!pipe) + return NULL; + + INIT_LIST_HEAD(&pipe->list); + + /* + * call usbhs_pipe_config_update() after this function !! + */ + + return pipe; +} + +void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + + WARN_ON(!usbhs_pipe_is_dcp(pipe)); + + usbhs_pipe_enable(pipe); + + if (!usbhs_mod_is_host(priv)) /* funconly */ + usbhsp_pipectrl_set(pipe, CCPL, CCPL); +} + +void usbhs_dcp_dir_for_host(struct usbhs_pipe *pipe, int dir_out) +{ + usbhsp_pipe_cfg_set(pipe, DIR_OUT, + dir_out ? DIR_OUT : 0); +} + +/* + * pipe module function + */ +int usbhs_pipe_probe(struct usbhs_priv *priv) +{ + struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); + struct usbhs_pipe *pipe; + struct device *dev = usbhs_priv_to_dev(priv); + u32 *pipe_type = usbhs_get_dparam(priv, pipe_type); + int pipe_size = usbhs_get_dparam(priv, pipe_size); + int i; + + /* This driver expects 1st pipe is DCP */ + if (pipe_type[0] != USB_ENDPOINT_XFER_CONTROL) { + dev_err(dev, "1st PIPE is not DCP\n"); + return -EINVAL; + } + + info->pipe = kzalloc(sizeof(struct usbhs_pipe) * pipe_size, GFP_KERNEL); + if (!info->pipe) { + dev_err(dev, "Could not allocate pipe\n"); + return -ENOMEM; + } + + info->size = pipe_size; + + /* + * init pipe + */ + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + pipe->priv = priv; + + usbhs_pipe_type(pipe) = + pipe_type[i] & USB_ENDPOINT_XFERTYPE_MASK; + + dev_dbg(dev, "pipe %x\t: %s\n", + i, usbhsp_pipe_name[pipe_type[i]]); + } + + return 0; +} + +void usbhs_pipe_remove(struct usbhs_priv *priv) +{ + struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); + + kfree(info->pipe); +} diff --git a/drivers/usb/renesas_usbhs/pipe.h b/drivers/usb/renesas_usbhs/pipe.h new file mode 100644 index 00000000..b476fde9 --- /dev/null +++ b/drivers/usb/renesas_usbhs/pipe.h @@ -0,0 +1,116 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef RENESAS_USB_PIPE_H +#define RENESAS_USB_PIPE_H + +#include "common.h" +#include "fifo.h" + +/* + * struct + */ +struct usbhs_pipe { + u32 pipe_type; /* USB_ENDPOINT_XFER_xxx */ + + struct usbhs_priv *priv; + struct usbhs_fifo *fifo; + struct list_head list; + + int maxp; + + u32 flags; +#define USBHS_PIPE_FLAGS_IS_USED (1 << 0) +#define USBHS_PIPE_FLAGS_IS_DIR_IN (1 << 1) +#define USBHS_PIPE_FLAGS_IS_DIR_HOST (1 << 2) + + struct usbhs_pkt_handle *handler; + + void *mod_private; +}; + +struct usbhs_pipe_info { + struct usbhs_pipe *pipe; + int size; /* array size of "pipe" */ + int bufnmb_last; /* FIXME : driver needs good allocator */ + + int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map); +}; + +/* + * pipe list + */ +#define __usbhs_for_each_pipe(start, pos, info, i) \ + for (i = start, pos = (info)->pipe + i; \ + i < (info)->size; \ + i++, pos = (info)->pipe + i) + +#define usbhs_for_each_pipe(pos, priv, i) \ + __usbhs_for_each_pipe(1, pos, &((priv)->pipe_info), i) + +#define usbhs_for_each_pipe_with_dcp(pos, priv, i) \ + __usbhs_for_each_pipe(0, pos, &((priv)->pipe_info), i) + +/* + * data + */ +#define usbhs_priv_to_pipeinfo(pr) (&(pr)->pipe_info) + +/* + * pipe control + */ +char *usbhs_pipe_name(struct usbhs_pipe *pipe); +struct usbhs_pipe +*usbhs_pipe_malloc(struct usbhs_priv *priv, int endpoint_type, int dir_in); +int usbhs_pipe_probe(struct usbhs_priv *priv); +void usbhs_pipe_remove(struct usbhs_priv *priv); +int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe); +int usbhs_pipe_is_dir_host(struct usbhs_pipe *pipe); +void usbhs_pipe_init(struct usbhs_priv *priv, + int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map)); +int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe); +void usbhs_pipe_clear(struct usbhs_pipe *pipe); +int usbhs_pipe_is_accessible(struct usbhs_pipe *pipe); +void usbhs_pipe_enable(struct usbhs_pipe *pipe); +void usbhs_pipe_disable(struct usbhs_pipe *pipe); +void usbhs_pipe_stall(struct usbhs_pipe *pipe); +int usbhs_pipe_is_stall(struct usbhs_pipe *pipe); +void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len); +void usbhs_pipe_select_fifo(struct usbhs_pipe *pipe, struct usbhs_fifo *fifo); +void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, + u16 epnum, u16 maxp); + +#define usbhs_pipe_sequence_data0(pipe) usbhs_pipe_data_sequence(pipe, 0) +#define usbhs_pipe_sequence_data1(pipe) usbhs_pipe_data_sequence(pipe, 1) +void usbhs_pipe_data_sequence(struct usbhs_pipe *pipe, int data); + +#define usbhs_pipe_to_priv(p) ((p)->priv) +#define usbhs_pipe_number(p) (int)((p) - (p)->priv->pipe_info.pipe) +#define usbhs_pipe_is_dcp(p) ((p)->priv->pipe_info.pipe == (p)) +#define usbhs_pipe_to_fifo(p) ((p)->fifo) +#define usbhs_pipe_is_busy(p) usbhs_pipe_to_fifo(p) + +#define usbhs_pipe_type(p) ((p)->pipe_type) +#define usbhs_pipe_type_is(p, t) ((p)->pipe_type == t) + +/* + * dcp control + */ +struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv); +void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe); +void usbhs_dcp_dir_for_host(struct usbhs_pipe *pipe, int dir_out); + +#endif /* RENESAS_USB_PIPE_H */ diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig new file mode 100644 index 00000000..1d55762a --- /dev/null +++ b/drivers/usb/serial/Kconfig @@ -0,0 +1,723 @@ +# +# USB Serial device configuration +# + +menuconfig USB_SERIAL + tristate "USB Serial Converter support" + depends on TTY + ---help--- + Say Y here if you have a USB device that provides normal serial + ports, or acts like a serial device, and you want to connect it to + your USB bus. + + Please read for more + information on the specifics of the different devices that are + supported, and on how to use them. + + To compile this driver as a module, choose M here: the + module will be called usbserial. + +if USB_SERIAL + +config USB_SERIAL_CONSOLE + bool "USB Serial Console device support" + depends on USB_SERIAL=y + ---help--- + If you say Y here, it will be possible to use a USB to serial + converter port as the system console (the system console is the + device which receives all kernel messages and warnings and which + allows logins in single user mode). This could be useful if some + terminal or printer is connected to that serial port. + + Even if you say Y here, the currently visible virtual console + (/dev/tty0) will still be used as the system console by default, but + you can alter that using a kernel command line option such as + "console=ttyUSB0". (Try "man bootparam" or see the documentation of + your boot loader (lilo or loadlin) about how to pass options to the + kernel at boot time.) + + If you don't have a VGA card installed and you say Y here, the + kernel will automatically use the first USB to serial converter + port, /dev/ttyUSB0, as system console. + + If unsure, say N. + +config USB_SERIAL_GENERIC + bool "USB Generic Serial Driver" + help + Say Y here if you want to use the generic USB serial driver. Please + read for more information on + using this driver. It is recommended that the "USB Serial converter + support" be compiled as a module for this driver to be used + properly. + +config USB_SERIAL_AIRCABLE + tristate "USB AIRcable Bluetooth Dongle Driver" + help + Say Y here if you want to use USB AIRcable Bluetooth Dongle. + + To compile this driver as a module, choose M here: the module + will be called aircable. + +config USB_SERIAL_ARK3116 + tristate "USB ARK Micro 3116 USB Serial Driver" + help + Say Y here if you want to use a ARK Micro 3116 USB to Serial + device. + + To compile this driver as a module, choose M here: the + module will be called ark3116 + +config USB_SERIAL_BELKIN + tristate "USB Belkin and Peracom Single Port Serial Driver" + help + Say Y here if you want to use a Belkin USB Serial single port + adaptor (F5U103 is one of the model numbers) or the Peracom single + port USB to serial adapter. + + To compile this driver as a module, choose M here: the + module will be called belkin_sa. + +config USB_SERIAL_CH341 + tristate "USB Winchiphead CH341 Single Port Serial Driver" + help + Say Y here if you want to use a Winchiphead CH341 single port + USB to serial adapter. + + To compile this driver as a module, choose M here: the + module will be called ch341. + +config USB_SERIAL_WHITEHEAT + tristate "USB ConnectTech WhiteHEAT Serial Driver" + select USB_EZUSB_FX2 + help + Say Y here if you want to use a ConnectTech WhiteHEAT 4 port + USB to serial converter device. + + To compile this driver as a module, choose M here: the + module will be called whiteheat. + +config USB_SERIAL_DIGI_ACCELEPORT + tristate "USB Digi International AccelePort USB Serial Driver" + ---help--- + Say Y here if you want to use Digi AccelePort USB 2 or 4 devices, + 2 port (plus parallel port) and 4 port USB serial converters. The + parallel port on the USB 2 appears as a third serial port on Linux. + The Digi Acceleport USB 8 is not yet supported by this driver. + + This driver works under SMP with the usb-uhci driver. It does not + work under SMP with the uhci driver. + + To compile this driver as a module, choose M here: the + module will be called digi_acceleport. + +config USB_SERIAL_CP210X + tristate "USB CP210x family of UART Bridge Controllers" + help + Say Y here if you want to use a CP2101/CP2102/CP2103 based USB + to RS232 converters. + + To compile this driver as a module, choose M here: the + module will be called cp210x. + +config USB_SERIAL_CYPRESS_M8 + tristate "USB Cypress M8 USB Serial Driver" + help + Say Y here if you want to use a device that contains the Cypress + USB to Serial microcontroller, such as the DeLorme Earthmate GPS. + + Attempted SMP support... send bug reports! + + Supported microcontrollers in the CY4601 family are: + CY7C63741 CY7C63742 CY7C63743 CY7C64013 + + To compile this driver as a module, choose M here: the + module will be called cypress_m8. + +config USB_SERIAL_EMPEG + tristate "USB Empeg empeg-car Mark I/II Driver" + help + Say Y here if you want to connect to your Empeg empeg-car Mark I/II + mp3 player via USB. The driver uses a single ttyUSB{0,1,2,...} + device node. See for more + tidbits of information. + + To compile this driver as a module, choose M here: the + module will be called empeg. + +config USB_SERIAL_FTDI_SIO + tristate "USB FTDI Single Port Serial Driver" + ---help--- + Say Y here if you want to use a FTDI SIO single port USB to serial + converter device. The implementation I have is called the USC-1000. + This driver has also be tested with the 245 and 232 devices. + + See for more + information on this driver and the device. + + To compile this driver as a module, choose M here: the + module will be called ftdi_sio. + +config USB_SERIAL_FUNSOFT + tristate "USB Fundamental Software Dongle Driver" + ---help--- + Say Y here if you want to use the Fundamental Software dongle. + + To compile this driver as a module, choose M here: the + module will be called funsoft. + +config USB_SERIAL_VISOR + tristate "USB Handspring Visor / Palm m50x / Sony Clie Driver" + help + Say Y here if you want to connect to your HandSpring Visor, Palm + m500 or m505 through its USB docking station. See + for more information on using this + driver. + + To compile this driver as a module, choose M here: the + module will be called visor. + +config USB_SERIAL_IPAQ + tristate "USB PocketPC PDA Driver" + help + Say Y here if you want to connect to your Compaq iPAQ, HP Jornada + or any other PDA running Windows CE 3.0 or PocketPC 2002 + using a USB cradle/cable. For information on using the driver, + read . + + To compile this driver as a module, choose M here: the + module will be called ipaq. + +config USB_SERIAL_IR + tristate "USB IR Dongle Serial Driver" + help + Say Y here if you want to enable simple serial support for USB IrDA + devices. This is useful if you do not want to use the full IrDA + stack. + + To compile this driver as a module, choose M here: the + module will be called ir-usb. + +config USB_SERIAL_EDGEPORT + tristate "USB Inside Out Edgeport Serial Driver" + ---help--- + Say Y here if you want to use any of the following devices from + Inside Out Networks (Digi): + Edgeport/4 + Rapidport/4 + Edgeport/4t + Edgeport/2 + Edgeport/4i + Edgeport/2i + Edgeport/421 + Edgeport/21 + Edgeport/8 + Edgeport/8 Dual + Edgeport/2D8 + Edgeport/4D8 + Edgeport/8i + Edgeport/2 DIN + Edgeport/4 DIN + Edgeport/16 Dual + + To compile this driver as a module, choose M here: the + module will be called io_edgeport. + +config USB_SERIAL_EDGEPORT_TI + tristate "USB Inside Out Edgeport Serial Driver (TI devices)" + help + Say Y here if you want to use any of the devices from Inside Out + Networks (Digi) that are not supported by the io_edgeport driver. + This includes the Edgeport/1 device. + + To compile this driver as a module, choose M here: the + module will be called io_ti. + +config USB_SERIAL_F81232 + tristate "USB Fintek F81232 Single Port Serial Driver" + help + Say Y here if you want to use the Fintek F81232 single + port usb to serial adapter. + + To compile this driver as a module, choose M here: the + module will be called f81232. + +config USB_SERIAL_GARMIN + tristate "USB Garmin GPS driver" + help + Say Y here if you want to connect to your Garmin GPS. + Should work with most Garmin GPS devices which have a native USB port. + + See for the latest + version of the driver. + + To compile this driver as a module, choose M here: the + module will be called garmin_gps. + +config USB_SERIAL_IPW + tristate "USB IPWireless (3G UMTS TDD) Driver" + select USB_SERIAL_WWAN + help + Say Y here if you want to use a IPWireless USB modem such as + the ones supplied by Axity3G/Sentech South Africa. + + To compile this driver as a module, choose M here: the + module will be called ipw. + +config USB_SERIAL_IUU + tristate "USB Infinity USB Unlimited Phoenix Driver" + help + Say Y here if you want to use a IUU in phoenix mode and get + an extra ttyUSBx device. More information available on + http://eczema.ecze.com/iuu_phoenix.html + + To compile this driver as a module, choose M here: the + module will be called iuu_phoenix.o + +config USB_SERIAL_KEYSPAN_PDA + tristate "USB Keyspan PDA Single Port Serial Driver" + select USB_EZUSB_FX2 + help + Say Y here if you want to use a Keyspan PDA single port USB to + serial converter device. This driver makes use of firmware + developed from scratch by Brian Warner. + + To compile this driver as a module, choose M here: the + module will be called keyspan_pda. + +config USB_SERIAL_KEYSPAN + tristate "USB Keyspan USA-xxx Serial Driver" + select USB_EZUSB_FX2 + ---help--- + Say Y here if you want to use Keyspan USB to serial converter + devices. This driver makes use of Keyspan's official firmware + and was developed with their support. You must also include + firmware to support your particular device(s). + + See for more information. + + To compile this driver as a module, choose M here: the + module will be called keyspan. + +config USB_SERIAL_KEYSPAN_MPR + bool "USB Keyspan MPR Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the Keyspan MPR converter. + +config USB_SERIAL_KEYSPAN_USA28 + bool "USB Keyspan USA-28 Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-28 converter. + +config USB_SERIAL_KEYSPAN_USA28X + bool "USB Keyspan USA-28X Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-28X converter. + Be sure you have a USA-28X, there are also 28XA and 28XB + models, the label underneath has the actual part number. + +config USB_SERIAL_KEYSPAN_USA28XA + bool "USB Keyspan USA-28XA Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-28XA converter. + Be sure you have a USA-28XA, there are also 28X and 28XB + models, the label underneath has the actual part number. + +config USB_SERIAL_KEYSPAN_USA28XB + bool "USB Keyspan USA-28XB Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-28XB converter. + Be sure you have a USA-28XB, there are also 28X and 28XA + models, the label underneath has the actual part number. + +config USB_SERIAL_KEYSPAN_USA19 + bool "USB Keyspan USA-19 Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-19 converter. + +config USB_SERIAL_KEYSPAN_USA18X + bool "USB Keyspan USA-18X Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-18X converter. + +config USB_SERIAL_KEYSPAN_USA19W + bool "USB Keyspan USA-19W Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-19W converter. + +config USB_SERIAL_KEYSPAN_USA19QW + bool "USB Keyspan USA-19QW Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-19QW converter. + +config USB_SERIAL_KEYSPAN_USA19QI + bool "USB Keyspan USA-19QI Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-19QI converter. + +config USB_SERIAL_KEYSPAN_USA49W + bool "USB Keyspan USA-49W Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-49W converter. + +config USB_SERIAL_KEYSPAN_USA49WLC + bool "USB Keyspan USA-49WLC Firmware" + depends on USB_SERIAL_KEYSPAN && FIRMWARE_IN_KERNEL + help + Say Y here to include firmware for the USA-49WLC converter. + +config USB_SERIAL_KLSI + tristate "USB KL5KUSB105 (Palmconnect) Driver" + ---help--- + Say Y here if you want to use a KL5KUSB105 - based single port + serial adapter. The most widely known -- and currently the only + tested -- device in this category is the PalmConnect USB Serial + adapter sold by Palm Inc. for use with their Palm III and Palm V + series PDAs. + + Please read for more + information. + + To compile this driver as a module, choose M here: the + module will be called kl5kusb105. + +config USB_SERIAL_KOBIL_SCT + tristate "USB KOBIL chipcard reader" + ---help--- + Say Y here if you want to use one of the following KOBIL USB chipcard + readers: + + - USB TWIN + - KAAN Standard Plus + - KAAN SIM + - SecOVID Reader Plus + - B1 Professional + - KAAN Professional + + Note that you need a current CT-API. + To compile this driver as a module, choose M here: the + module will be called kobil_sct. + +config USB_SERIAL_MCT_U232 + tristate "USB MCT Single Port Serial Driver" + ---help--- + Say Y here if you want to use a USB Serial single port adapter from + Magic Control Technology Corp. (U232 is one of the model numbers). + + This driver also works with Sitecom U232-P25 and D-Link DU-H3SP USB + BAY, Belkin F5U109, and Belkin F5U409 devices. + + To compile this driver as a module, choose M here: the + module will be called mct_u232. + +config USB_SERIAL_METRO + tristate "USB Metrologic Instruments USB-POS Barcode Scanner Driver" + ---help--- + Say Y here if you want to use a USB POS Metrologic barcode scanner. + + To compile this driver as a module, choose M here: the + module will be called metro-usb. + +config USB_SERIAL_MOS7720 + tristate "USB Moschip 7720 Serial Driver" + ---help--- + Say Y here if you want to use USB Serial single and double + port adapters from Moschip Semiconductor Tech. + + To compile this driver as a module, choose M here: the + module will be called mos7720. + +config USB_SERIAL_MOS7715_PARPORT + bool "Support for parallel port on the Moschip 7715" + depends on USB_SERIAL_MOS7720 + depends on PARPORT=y || PARPORT=USB_SERIAL_MOS7720 + select PARPORT_NOT_PC + ---help--- + Say Y if you have a Moschip 7715 device and would like to use + the parallel port it provides. The port will register with + the parport subsystem as a low-level driver. + +config USB_SERIAL_MOS7840 + tristate "USB Moschip 7840/7820 USB Serial Driver" + ---help--- + Say Y here if you want to use a MCS7840 Quad-Serial or MCS7820 + Dual-Serial port device from MosChip Semiconductor. + + The MCS7840 and MCS7820 have been developed to connect a wide range + of standard serial devices to a USB host. The MCS7840 has a USB + device controller connected to four (4) individual UARTs while the + MCS7820 controller connects to two (2) individual UARTs. + + To compile this driver as a module, choose M here: the + module will be called mos7840. If unsure, choose N. + +config USB_SERIAL_MOTOROLA + tristate "USB Motorola Phone modem driver" + ---help--- + Say Y here if you want to use a Motorola phone with a USB + connector as a modem link. + + To compile this driver as a module, choose M here: the + module will be called moto_modem. If unsure, choose N. + +config USB_SERIAL_NAVMAN + tristate "USB Navman GPS device" + help + To compile this driver as a module, choose M here: the + module will be called navman. + +config USB_SERIAL_PL2303 + tristate "USB Prolific 2303 Single Port Serial Driver" + help + Say Y here if you want to use the PL2303 USB Serial single port + adapter from Prolific. + + To compile this driver as a module, choose M here: the + module will be called pl2303. + +config USB_SERIAL_OTI6858 + tristate "USB Ours Technology Inc. OTi-6858 USB To RS232 Bridge Controller" + help + Say Y here if you want to use the OTi-6858 single port USB to serial + converter device. + + To compile this driver as a module, choose M here: the + module will be called oti6858. + +config USB_SERIAL_QCAUX + tristate "USB Qualcomm Auxiliary Serial Port Driver" + help + Say Y here if you want to use the auxiliary serial ports provided + by many modems based on Qualcomm chipsets. These ports often use + a proprietary protocol called DM and cannot be used for AT- or + PPP-based communication. + + To compile this driver as a module, choose M here: the + module will be called qcaux. If unsure, choose N. + +config USB_SERIAL_QUALCOMM + tristate "USB Qualcomm Serial modem" + select USB_SERIAL_WWAN + help + Say Y here if you have a Qualcomm USB modem device. These are + usually wireless cellular modems. + + To compile this driver as a module, choose M here: the + module will be called qcserial. + +config USB_SERIAL_SPCP8X5 + tristate "USB SPCP8x5 USB To Serial Driver" + help + Say Y here if you want to use the spcp8x5 converter chip. This is + commonly found in some Z-Wave USB devices. + + To compile this driver as a module, choose M here: the + module will be called spcp8x5. + +config USB_SERIAL_HP4X + tristate "USB HP4x Calculators support" + help + Say Y here if you want to use an Hewlett-Packard 4x Calculator. + + To compile this driver as a module, choose M here: the + module will be called hp4x. + +config USB_SERIAL_SAFE + tristate "USB Safe Serial (Encapsulated) Driver" + +config USB_SERIAL_SAFE_PADDED + bool "USB Secure Encapsulated Driver - Padded" + depends on USB_SERIAL_SAFE + +config USB_SERIAL_SIEMENS_MPI + tristate "USB Siemens MPI driver" + help + Say M here if you want to use a Siemens USB/MPI adapter. + + To compile this driver as a module, choose M here: the + module will be called siemens_mpi. + +config USB_SERIAL_SIERRAWIRELESS + tristate "USB Sierra Wireless Driver" + help + Say M here if you want to use Sierra Wireless devices. + + Many devices have a feature known as TRU-Install. For those devices + to work properly, the USB Storage Sierra feature must be enabled. + + To compile this driver as a module, choose M here: the + module will be called sierra. + +config USB_SERIAL_SYMBOL + tristate "USB Symbol Barcode driver (serial mode)" + help + Say Y here if you want to use a Symbol USB Barcode device + in serial emulation mode. + + To compile this driver as a module, choose M here: the + module will be called symbolserial. + +config USB_SERIAL_TI + tristate "USB TI 3410/5052 Serial Driver" + help + Say Y here if you want to use the TI USB 3410 or 5052 + serial devices. + + To compile this driver as a module, choose M here: the + module will be called ti_usb_3410_5052. + +config USB_SERIAL_CYBERJACK + tristate "USB REINER SCT cyberJack pinpad/e-com chipcard reader" + ---help--- + Say Y here if you want to use a cyberJack pinpad/e-com USB chipcard + reader. This is an interface to ISO 7816 compatible contact-based + chipcards, e.g. GSM SIMs. + + To compile this driver as a module, choose M here: the + module will be called cyberjack. + + If unsure, say N. + +config USB_SERIAL_XIRCOM + tristate "USB Xircom / Entregra Single Port Serial Driver" + select USB_EZUSB_FX2 + help + Say Y here if you want to use a Xircom or Entregra single port USB to + serial converter device. This driver makes use of firmware + developed from scratch by Brian Warner. + + To compile this driver as a module, choose M here: the + module will be called keyspan_pda. + +config USB_SERIAL_WWAN + tristate + +config USB_SERIAL_OPTION + tristate "USB driver for GSM and CDMA modems" + select USB_SERIAL_WWAN + help + Say Y here if you have a GSM or CDMA modem that's connected to USB. + + This driver also supports several PCMCIA cards which have a + built-in OHCI-USB adapter and an internally-connected GSM modem. + The USB bus on these cards is not accessible externally. + + Supported devices include (some of?) those made by: + Option, Huawei, Audiovox, Novatel Wireless, or Anydata. + + To compile this driver as a module, choose M here: the + module will be called option. + + If this driver doesn't recognize your device, + it might be accessible via the FTDI_SIO driver. + +config USB_SERIAL_OMNINET + tristate "USB ZyXEL omni.net LCD Plus Driver" + help + Say Y here if you want to use a ZyXEL omni.net LCD ISDN TA. + + To compile this driver as a module, choose M here: the + module will be called omninet. + +config USB_SERIAL_OPTICON + tristate "USB Opticon Barcode driver (serial mode)" + help + Say Y here if you want to use a Opticon USB Barcode device + in serial emulation mode. + + To compile this driver as a module, choose M here: the + module will be called opticon. + +config USB_SERIAL_VIVOPAY_SERIAL + tristate "USB ViVOpay serial interface driver" + help + Say Y here if you want to use a ViVOtech ViVOpay USB device. + + To compile this driver as a module, choose M here: the + module will be called vivopay-serial. + +config USB_SERIAL_XSENS_MT + tristate "Xsens motion tracker serial interface driver" + help + Say Y here if you want to use Xsens motion trackers. + + This driver supports the new generation of motion trackers + by Xsens. Older devices can be accessed using the FTDI_SIO + driver. + + To compile this driver as a module, choose M here: the + module will be called xsens_mt. + +config USB_SERIAL_ZIO + tristate "ZIO Motherboard USB serial interface driver" + help + Say Y here if you want to use ZIO Motherboard. + + To compile this driver as a module, choose M here: the + module will be called zio. + +config USB_SERIAL_WISHBONE + tristate "USB-Wishbone adapter interface driver" + help + Say Y here if you want to use a USB attached Wishbone bus. + + Wishbone is an open hardware SoC bus commonly used in FPGA + designs. Bus access can be serialized using the Etherbone + protocol . + + This driver is intended to be used with devices which attach + their internal Wishbone bus to a USB serial interface using + the Etherbone protocol. A userspace library is required to + speak the protocol made available by this driver as ttyUSBx. + + To compile this driver as a module, choose M here: the + module will be called wishbone-serial. + +config USB_SERIAL_ZTE + tristate "ZTE USB serial driver" + help + Say Y here if you want to use a ZTE USB to serial device. + + To compile this driver as a module, choose M here: the + module will be called zte. + +config USB_SERIAL_SSU100 + tristate "USB Quatech SSU-100 Single Port Serial Driver" + help + Say Y here if you want to use the Quatech SSU-100 single + port usb to serial adapter. + + To compile this driver as a module, choose M here: the + module will be called ssu100. + +config USB_SERIAL_QT2 + tristate "USB Quatech Serial Driver for USB 2 devices" + help + Say Y here if you want to use the Quatech USB 2 + serial adapters. + + To compile this driver as a module, choose M here: the + module will be called quatech-serial. + +config USB_SERIAL_DEBUG + tristate "USB Debugging Device" + help + Say Y here if you have a USB debugging device used to receive + debugging data from another machine. The most common of these + devices is the NetChip TurboCONNECT device. + + To compile this driver as a module, choose M here: the + module will be called usb-debug. + +endif # USB_SERIAL diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile new file mode 100644 index 00000000..cec63fa1 --- /dev/null +++ b/drivers/usb/serial/Makefile @@ -0,0 +1,67 @@ +# +# Makefile for the USB serial device drivers. +# + +# Object file lists. + +obj-$(CONFIG_USB_SERIAL) += usbserial.o + +usbserial-y := usb-serial.o generic.o bus.o + +usbserial-$(CONFIG_USB_SERIAL_CONSOLE) += console.o + +obj-$(CONFIG_USB_SERIAL_AIRCABLE) += aircable.o +obj-$(CONFIG_USB_SERIAL_ARK3116) += ark3116.o +obj-$(CONFIG_USB_SERIAL_BELKIN) += belkin_sa.o +obj-$(CONFIG_USB_SERIAL_CH341) += ch341.o +obj-$(CONFIG_USB_SERIAL_CP210X) += cp210x.o +obj-$(CONFIG_USB_SERIAL_CYBERJACK) += cyberjack.o +obj-$(CONFIG_USB_SERIAL_CYPRESS_M8) += cypress_m8.o +obj-$(CONFIG_USB_SERIAL_DEBUG) += usb_debug.o +obj-$(CONFIG_USB_SERIAL_DIGI_ACCELEPORT) += digi_acceleport.o +obj-$(CONFIG_USB_SERIAL_EDGEPORT) += io_edgeport.o +obj-$(CONFIG_USB_SERIAL_EDGEPORT_TI) += io_ti.o +obj-$(CONFIG_USB_SERIAL_EMPEG) += empeg.o +obj-$(CONFIG_USB_SERIAL_F81232) += f81232.o +obj-$(CONFIG_USB_SERIAL_FTDI_SIO) += ftdi_sio.o +obj-$(CONFIG_USB_SERIAL_FUNSOFT) += funsoft.o +obj-$(CONFIG_USB_SERIAL_GARMIN) += garmin_gps.o +obj-$(CONFIG_USB_SERIAL_HP4X) += hp4x.o +obj-$(CONFIG_USB_SERIAL_IPAQ) += ipaq.o +obj-$(CONFIG_USB_SERIAL_IPW) += ipw.o +obj-$(CONFIG_USB_SERIAL_IR) += ir-usb.o +obj-$(CONFIG_USB_SERIAL_IUU) += iuu_phoenix.o +obj-$(CONFIG_USB_SERIAL_KEYSPAN) += keyspan.o +obj-$(CONFIG_USB_SERIAL_KEYSPAN_PDA) += keyspan_pda.o +obj-$(CONFIG_USB_SERIAL_KLSI) += kl5kusb105.o +obj-$(CONFIG_USB_SERIAL_KOBIL_SCT) += kobil_sct.o +obj-$(CONFIG_USB_SERIAL_MCT_U232) += mct_u232.o +obj-$(CONFIG_USB_SERIAL_METRO) += metro-usb.o +obj-$(CONFIG_USB_SERIAL_MOS7720) += mos7720.o +obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o +obj-$(CONFIG_USB_SERIAL_MOTOROLA) += moto_modem.o +obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o +obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o +obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o +obj-$(CONFIG_USB_SERIAL_OPTION) += option.o +obj-$(CONFIG_USB_SERIAL_OTI6858) += oti6858.o +obj-$(CONFIG_USB_SERIAL_PL2303) += pl2303.o +obj-$(CONFIG_USB_SERIAL_QCAUX) += qcaux.o +obj-$(CONFIG_USB_SERIAL_QUALCOMM) += qcserial.o +obj-$(CONFIG_USB_SERIAL_QT2) += quatech2.o +obj-$(CONFIG_USB_SERIAL_SAFE) += safe_serial.o +obj-$(CONFIG_USB_SERIAL_SIEMENS_MPI) += siemens_mpi.o +obj-$(CONFIG_USB_SERIAL_SIERRAWIRELESS) += sierra.o +obj-$(CONFIG_USB_SERIAL_SPCP8X5) += spcp8x5.o +obj-$(CONFIG_USB_SERIAL_SSU100) += ssu100.o +obj-$(CONFIG_USB_SERIAL_SYMBOL) += symbolserial.o +obj-$(CONFIG_USB_SERIAL_WWAN) += usb_wwan.o +obj-$(CONFIG_USB_SERIAL_TI) += ti_usb_3410_5052.o +obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o +obj-$(CONFIG_USB_SERIAL_WISHBONE) += wishbone-serial.o +obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o +obj-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda.o +obj-$(CONFIG_USB_SERIAL_VIVOPAY_SERIAL) += vivopay-serial.o +obj-$(CONFIG_USB_SERIAL_XSENS_MT) += xsens_mt.o +obj-$(CONFIG_USB_SERIAL_ZIO) += zio.o +obj-$(CONFIG_USB_SERIAL_ZTE) += zte_ev.o diff --git a/drivers/usb/serial/Makefile-keyspan_pda_fw b/drivers/usb/serial/Makefile-keyspan_pda_fw new file mode 100644 index 00000000..c20baf72 --- /dev/null +++ b/drivers/usb/serial/Makefile-keyspan_pda_fw @@ -0,0 +1,16 @@ + +# some rules to handle the quirks of the 'as31' assembler, like +# insisting upon fixed suffixes for the input and output files, +# and its lack of preprocessor support + +all: keyspan_pda_fw.h + +%.asm: %.S + gcc -x assembler-with-cpp -P -E -o $@ $< + +%.hex: %.asm + as31 -l $< + mv $*.obj $@ + +%_fw.h: %.hex ezusb_convert.pl + perl ezusb_convert.pl $* < $< > $@ diff --git a/drivers/usb/serial/aircable.c b/drivers/usb/serial/aircable.c new file mode 100644 index 00000000..6e320cec --- /dev/null +++ b/drivers/usb/serial/aircable.c @@ -0,0 +1,184 @@ +/* + * AIRcable USB Bluetooth Dongle Driver. + * + * Copyright (C) 2010 Johan Hovold + * Copyright (C) 2006 Manuel Francisco Naranjo (naranjo.manuel@gmail.com) + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + * + * The device works as an standard CDC device, it has 2 interfaces, the first + * one is for firmware access and the second is the serial one. + * The protocol is very simply, there are two posibilities reading or writing. + * When writing the first urb must have a Header that starts with 0x20 0x29 the + * next two bytes must say how much data will be sended. + * When reading the process is almost equal except that the header starts with + * 0x00 0x20. + * + * The device simply need some stuff to understand data coming from the usb + * buffer: The First and Second byte is used for a Header, the Third and Fourth + * tells the device the amount of information the package holds. + * Packages are 60 bytes long Header Stuff. + * When writing to the device the first two bytes of the header are 0x20 0x29 + * When reading the bytes are 0x00 0x20, or 0x00 0x10, there is an strange + * situation, when too much data arrives to the device because it sends the data + * but with out the header. I will use a simply hack to override this situation, + * if there is data coming that does not contain any header, then that is data + * that must go directly to the tty, as there is no documentation about if there + * is any other control code, I will simply check for the first + * one. + * + * The driver registers himself with the USB-serial core and the USB Core. I had + * to implement a probe function against USB-serial, because other way, the + * driver was attaching himself to both interfaces. I have tryed with different + * configurations of usb_serial_driver with out exit, only the probe function + * could handle this correctly. + * + * I have taken some info from a Greg Kroah-Hartman article: + * http://www.linuxjournal.com/article/6573 + * And from Linux Device Driver Kit CD, which is a great work, the authors taken + * the work to recompile lots of information an knowladge in drivers development + * and made it all avaible inside a cd. + * URL: http://kernel.org/pub/linux/kernel/people/gregkh/ddk/ + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Vendor and Product ID */ +#define AIRCABLE_VID 0x16CA +#define AIRCABLE_USB_PID 0x1502 + +/* Protocol Stuff */ +#define HCI_HEADER_LENGTH 0x4 +#define TX_HEADER_0 0x20 +#define TX_HEADER_1 0x29 +#define RX_HEADER_0 0x00 +#define RX_HEADER_1 0x20 +#define HCI_COMPLETE_FRAME 64 + +/* rx_flags */ +#define THROTTLED 0x01 +#define ACTUALLY_THROTTLED 0x02 + +#define DRIVER_AUTHOR "Naranjo, Manuel Francisco , Johan Hovold " +#define DRIVER_DESC "AIRcable USB Driver" + +/* ID table that will be registered with USB core */ +static const struct usb_device_id id_table[] = { + { USB_DEVICE(AIRCABLE_VID, AIRCABLE_USB_PID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static int aircable_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size) +{ + int count; + unsigned char *buf = dest; + + count = kfifo_out_locked(&port->write_fifo, buf + HCI_HEADER_LENGTH, + size - HCI_HEADER_LENGTH, &port->lock); + buf[0] = TX_HEADER_0; + buf[1] = TX_HEADER_1; + put_unaligned_le16(count, &buf[2]); + + return count + HCI_HEADER_LENGTH; +} + +static int aircable_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct usb_host_interface *iface_desc = serial->interface-> + cur_altsetting; + struct usb_endpoint_descriptor *endpoint; + int num_bulk_out = 0; + int i; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + if (usb_endpoint_is_bulk_out(endpoint)) { + dev_dbg(&serial->dev->dev, + "found bulk out on endpoint %d\n", i); + ++num_bulk_out; + } + } + + if (num_bulk_out == 0) { + dev_dbg(&serial->dev->dev, "Invalid interface, discarding\n"); + return -ENODEV; + } + + return 0; +} + +static int aircable_process_packet(struct usb_serial_port *port, + int has_headers, char *packet, int len) +{ + if (has_headers) { + len -= HCI_HEADER_LENGTH; + packet += HCI_HEADER_LENGTH; + } + if (len <= 0) { + dev_dbg(&port->dev, "%s - malformed packet\n", __func__); + return 0; + } + + tty_insert_flip_string(&port->port, packet, len); + + return len; +} + +static void aircable_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + char *data = (char *)urb->transfer_buffer; + int has_headers; + int count; + int len; + int i; + + has_headers = (urb->actual_length > 2 && data[0] == RX_HEADER_0); + + count = 0; + for (i = 0; i < urb->actual_length; i += HCI_COMPLETE_FRAME) { + len = min_t(int, urb->actual_length - i, HCI_COMPLETE_FRAME); + count += aircable_process_packet(port, has_headers, + &data[i], len); + } + + if (count) + tty_flip_buffer_push(&port->port); +} + +static struct usb_serial_driver aircable_device = { + .driver = { + .owner = THIS_MODULE, + .name = "aircable", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_out_size = HCI_COMPLETE_FRAME, + .probe = aircable_probe, + .process_read_urb = aircable_process_read_urb, + .prepare_write_buffer = aircable_prepare_write_buffer, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &aircable_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c new file mode 100644 index 00000000..40e7fd94 --- /dev/null +++ b/drivers/usb/serial/ark3116.c @@ -0,0 +1,792 @@ +/* + * Copyright (C) 2009 by Bart Hartgers (bart.hartgers+ark3116@gmail.com) + * Original version: + * Copyright (C) 2006 + * Simon Schulz (ark3116_driver auctionant.de) + * + * ark3116 + * - implements a driver for the arkmicro ark3116 chipset (vendor=0x6547, + * productid=0x0232) (used in a datacable called KQ-U8A) + * + * Supports full modem status lines, break, hardware flow control. Does not + * support software flow control, since I do not know how to enable it in hw. + * + * This driver is a essentially new implementation. I initially dug + * into the old ark3116.c driver and suddenly realized the ark3116 is + * a 16450 with a USB interface glued to it. See comments at the + * bottom of this file. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Bart Hartgers " +#define DRIVER_DESC "USB ARK3116 serial/IrDA driver" +#define DRIVER_DEV_DESC "ARK3116 RS232/IrDA" +#define DRIVER_NAME "ark3116" + +/* usb timeout of 1 second */ +#define ARK_TIMEOUT 1000 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x6547, 0x0232) }, + { USB_DEVICE(0x18ec, 0x3118) }, /* USB to IrDA adapter */ + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static int is_irda(struct usb_serial *serial) +{ + struct usb_device *dev = serial->dev; + if (le16_to_cpu(dev->descriptor.idVendor) == 0x18ec && + le16_to_cpu(dev->descriptor.idProduct) == 0x3118) + return 1; + return 0; +} + +struct ark3116_private { + int irda; /* 1 for irda device */ + + /* protects hw register updates */ + struct mutex hw_lock; + + int quot; /* baudrate divisor */ + __u32 lcr; /* line control register value */ + __u32 hcr; /* handshake control register (0x8) + * value */ + __u32 mcr; /* modem contol register value */ + + /* protects the status values below */ + spinlock_t status_lock; + __u32 msr; /* modem status register value */ + __u32 lsr; /* line status register value */ +}; + +static int ark3116_write_reg(struct usb_serial *serial, + unsigned reg, __u8 val) +{ + int result; + /* 0xfe 0x40 are magic values taken from original driver */ + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + 0xfe, 0x40, val, reg, + NULL, 0, ARK_TIMEOUT); + return result; +} + +static int ark3116_read_reg(struct usb_serial *serial, + unsigned reg, unsigned char *buf) +{ + int result; + /* 0xfe 0xc0 are magic values taken from original driver */ + result = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + 0xfe, 0xc0, 0, reg, + buf, 1, ARK_TIMEOUT); + if (result < 0) + return result; + else + return buf[0]; +} + +static inline int calc_divisor(int bps) +{ + /* Original ark3116 made some exceptions in rounding here + * because windows did the same. Assume that is not really + * necessary. + * Crystal is 12MHz, probably because of USB, but we divide by 4? + */ + return (12000000 + 2*bps) / (4*bps); +} + +static int ark3116_attach(struct usb_serial *serial) +{ + /* make sure we have our end-points */ + if ((serial->num_bulk_in == 0) || + (serial->num_bulk_out == 0) || + (serial->num_interrupt_in == 0)) { + dev_err(&serial->dev->dev, + "%s - missing endpoint - " + "bulk in: %d, bulk out: %d, int in %d\n", + KBUILD_MODNAME, + serial->num_bulk_in, + serial->num_bulk_out, + serial->num_interrupt_in); + return -EINVAL; + } + + return 0; +} + +static int ark3116_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct ark3116_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->hw_lock); + spin_lock_init(&priv->status_lock); + + priv->irda = is_irda(serial); + + usb_set_serial_port_data(port, priv); + + /* setup the hardware */ + ark3116_write_reg(serial, UART_IER, 0); + /* disable DMA */ + ark3116_write_reg(serial, UART_FCR, 0); + /* handshake control */ + priv->hcr = 0; + ark3116_write_reg(serial, 0x8 , 0); + /* modem control */ + priv->mcr = 0; + ark3116_write_reg(serial, UART_MCR, 0); + + if (!(priv->irda)) { + ark3116_write_reg(serial, 0xb , 0); + } else { + ark3116_write_reg(serial, 0xb , 1); + ark3116_write_reg(serial, 0xc , 0); + ark3116_write_reg(serial, 0xd , 0x41); + ark3116_write_reg(serial, 0xa , 1); + } + + /* setup baudrate */ + ark3116_write_reg(serial, UART_LCR, UART_LCR_DLAB); + + /* setup for 9600 8N1 */ + priv->quot = calc_divisor(9600); + ark3116_write_reg(serial, UART_DLL, priv->quot & 0xff); + ark3116_write_reg(serial, UART_DLM, (priv->quot>>8) & 0xff); + + priv->lcr = UART_LCR_WLEN8; + ark3116_write_reg(serial, UART_LCR, UART_LCR_WLEN8); + + ark3116_write_reg(serial, 0xe, 0); + + if (priv->irda) + ark3116_write_reg(serial, 0x9, 0); + + dev_info(&serial->dev->dev, + "%s using %s mode\n", + KBUILD_MODNAME, + priv->irda ? "IrDA" : "RS232"); + return 0; +} + +static int ark3116_port_remove(struct usb_serial_port *port) +{ + struct ark3116_private *priv = usb_get_serial_port_data(port); + + /* device is closed, so URBs and DMA should be down */ + mutex_destroy(&priv->hw_lock); + kfree(priv); + + return 0; +} + +static void ark3116_init_termios(struct tty_struct *tty) +{ + struct ktermios *termios = &tty->termios; + *termios = tty_std_termios; + termios->c_cflag = B9600 | CS8 + | CREAD | HUPCL | CLOCAL; + termios->c_ispeed = 9600; + termios->c_ospeed = 9600; +} + +static void ark3116_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct usb_serial *serial = port->serial; + struct ark3116_private *priv = usb_get_serial_port_data(port); + struct ktermios *termios = &tty->termios; + unsigned int cflag = termios->c_cflag; + int bps = tty_get_baud_rate(tty); + int quot; + __u8 lcr, hcr, eval; + + /* set data bit count */ + switch (cflag & CSIZE) { + case CS5: + lcr = UART_LCR_WLEN5; + break; + case CS6: + lcr = UART_LCR_WLEN6; + break; + case CS7: + lcr = UART_LCR_WLEN7; + break; + default: + case CS8: + lcr = UART_LCR_WLEN8; + break; + } + if (cflag & CSTOPB) + lcr |= UART_LCR_STOP; + if (cflag & PARENB) + lcr |= UART_LCR_PARITY; + if (!(cflag & PARODD)) + lcr |= UART_LCR_EPAR; +#ifdef CMSPAR + if (cflag & CMSPAR) + lcr |= UART_LCR_SPAR; +#endif + /* handshake control */ + hcr = (cflag & CRTSCTS) ? 0x03 : 0x00; + + /* calc baudrate */ + dev_dbg(&port->dev, "%s - setting bps to %d\n", __func__, bps); + eval = 0; + switch (bps) { + case 0: + quot = calc_divisor(9600); + break; + default: + if ((bps < 75) || (bps > 3000000)) + bps = 9600; + quot = calc_divisor(bps); + break; + case 460800: + eval = 1; + quot = calc_divisor(bps); + break; + case 921600: + eval = 2; + quot = calc_divisor(bps); + break; + } + + /* Update state: synchronize */ + mutex_lock(&priv->hw_lock); + + /* keep old LCR_SBC bit */ + lcr |= (priv->lcr & UART_LCR_SBC); + + dev_dbg(&port->dev, "%s - setting hcr:0x%02x,lcr:0x%02x,quot:%d\n", + __func__, hcr, lcr, quot); + + /* handshake control */ + if (priv->hcr != hcr) { + priv->hcr = hcr; + ark3116_write_reg(serial, 0x8, hcr); + } + + /* baudrate */ + if (priv->quot != quot) { + priv->quot = quot; + priv->lcr = lcr; /* need to write lcr anyway */ + + /* disable DMA since transmit/receive is + * shadowed by UART_DLL + */ + ark3116_write_reg(serial, UART_FCR, 0); + + ark3116_write_reg(serial, UART_LCR, + lcr|UART_LCR_DLAB); + ark3116_write_reg(serial, UART_DLL, quot & 0xff); + ark3116_write_reg(serial, UART_DLM, (quot>>8) & 0xff); + + /* restore lcr */ + ark3116_write_reg(serial, UART_LCR, lcr); + /* magic baudrate thingy: not sure what it does, + * but windows does this as well. + */ + ark3116_write_reg(serial, 0xe, eval); + + /* enable DMA */ + ark3116_write_reg(serial, UART_FCR, UART_FCR_DMA_SELECT); + } else if (priv->lcr != lcr) { + priv->lcr = lcr; + ark3116_write_reg(serial, UART_LCR, lcr); + } + + mutex_unlock(&priv->hw_lock); + + /* check for software flow control */ + if (I_IXOFF(tty) || I_IXON(tty)) { + dev_warn(&serial->dev->dev, + "%s: don't know how to do software flow control\n", + KBUILD_MODNAME); + } + + /* Don't rewrite B0 */ + if (tty_termios_baud_rate(termios)) + tty_termios_encode_baud_rate(termios, bps, bps); +} + +static void ark3116_close(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + + /* disable DMA */ + ark3116_write_reg(serial, UART_FCR, 0); + + /* deactivate interrupts */ + ark3116_write_reg(serial, UART_IER, 0); + + usb_serial_generic_close(port); + if (serial->num_interrupt_in) + usb_kill_urb(port->interrupt_in_urb); +} + +static int ark3116_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct ark3116_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + unsigned char *buf; + int result; + + buf = kmalloc(1, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + result = usb_serial_generic_open(tty, port); + if (result) { + dev_dbg(&port->dev, + "%s - usb_serial_generic_open failed: %d\n", + __func__, result); + goto err_out; + } + + /* remove any data still left: also clears error state */ + ark3116_read_reg(serial, UART_RX, buf); + + /* read modem status */ + priv->msr = ark3116_read_reg(serial, UART_MSR, buf); + /* read line status */ + priv->lsr = ark3116_read_reg(serial, UART_LSR, buf); + + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "submit irq_in urb failed %d\n", + result); + ark3116_close(port); + goto err_out; + } + + /* activate interrupts */ + ark3116_write_reg(port->serial, UART_IER, UART_IER_MSI|UART_IER_RLSI); + + /* enable DMA */ + ark3116_write_reg(port->serial, UART_FCR, UART_FCR_DMA_SELECT); + + /* setup termios */ + if (tty) + ark3116_set_termios(tty, port, NULL); + +err_out: + kfree(buf); + return result; +} + +static int ark3116_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct serial_struct serstruct; + void __user *user_arg = (void __user *)arg; + + switch (cmd) { + case TIOCGSERIAL: + /* XXX: Some of these values are probably wrong. */ + memset(&serstruct, 0, sizeof(serstruct)); + serstruct.type = PORT_16654; + serstruct.line = port->serial->minor; + serstruct.port = port->number; + serstruct.custom_divisor = 0; + serstruct.baud_base = 460800; + + if (copy_to_user(user_arg, &serstruct, sizeof(serstruct))) + return -EFAULT; + + return 0; + case TIOCSSERIAL: + if (copy_from_user(&serstruct, user_arg, sizeof(serstruct))) + return -EFAULT; + return 0; + } + + return -ENOIOCTLCMD; +} + +static int ark3116_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ark3116_private *priv = usb_get_serial_port_data(port); + __u32 status; + __u32 ctrl; + unsigned long flags; + + mutex_lock(&priv->hw_lock); + ctrl = priv->mcr; + mutex_unlock(&priv->hw_lock); + + spin_lock_irqsave(&priv->status_lock, flags); + status = priv->msr; + spin_unlock_irqrestore(&priv->status_lock, flags); + + return (status & UART_MSR_DSR ? TIOCM_DSR : 0) | + (status & UART_MSR_CTS ? TIOCM_CTS : 0) | + (status & UART_MSR_RI ? TIOCM_RI : 0) | + (status & UART_MSR_DCD ? TIOCM_CD : 0) | + (ctrl & UART_MCR_DTR ? TIOCM_DTR : 0) | + (ctrl & UART_MCR_RTS ? TIOCM_RTS : 0) | + (ctrl & UART_MCR_OUT1 ? TIOCM_OUT1 : 0) | + (ctrl & UART_MCR_OUT2 ? TIOCM_OUT2 : 0); +} + +static int ark3116_tiocmset(struct tty_struct *tty, + unsigned set, unsigned clr) +{ + struct usb_serial_port *port = tty->driver_data; + struct ark3116_private *priv = usb_get_serial_port_data(port); + + /* we need to take the mutex here, to make sure that the value + * in priv->mcr is actually the one that is in the hardware + */ + + mutex_lock(&priv->hw_lock); + + if (set & TIOCM_RTS) + priv->mcr |= UART_MCR_RTS; + if (set & TIOCM_DTR) + priv->mcr |= UART_MCR_DTR; + if (set & TIOCM_OUT1) + priv->mcr |= UART_MCR_OUT1; + if (set & TIOCM_OUT2) + priv->mcr |= UART_MCR_OUT2; + if (clr & TIOCM_RTS) + priv->mcr &= ~UART_MCR_RTS; + if (clr & TIOCM_DTR) + priv->mcr &= ~UART_MCR_DTR; + if (clr & TIOCM_OUT1) + priv->mcr &= ~UART_MCR_OUT1; + if (clr & TIOCM_OUT2) + priv->mcr &= ~UART_MCR_OUT2; + + ark3116_write_reg(port->serial, UART_MCR, priv->mcr); + + mutex_unlock(&priv->hw_lock); + + return 0; +} + +static void ark3116_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct ark3116_private *priv = usb_get_serial_port_data(port); + + /* LCR is also used for other things: protect access */ + mutex_lock(&priv->hw_lock); + + if (break_state) + priv->lcr |= UART_LCR_SBC; + else + priv->lcr &= ~UART_LCR_SBC; + + ark3116_write_reg(port->serial, UART_LCR, priv->lcr); + + mutex_unlock(&priv->hw_lock); +} + +static void ark3116_update_msr(struct usb_serial_port *port, __u8 msr) +{ + struct ark3116_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&priv->status_lock, flags); + priv->msr = msr; + spin_unlock_irqrestore(&priv->status_lock, flags); + + if (msr & UART_MSR_ANY_DELTA) { + /* update input line counters */ + if (msr & UART_MSR_DCTS) + port->icount.cts++; + if (msr & UART_MSR_DDSR) + port->icount.dsr++; + if (msr & UART_MSR_DDCD) + port->icount.dcd++; + if (msr & UART_MSR_TERI) + port->icount.rng++; + wake_up_interruptible(&port->port.delta_msr_wait); + } +} + +static void ark3116_update_lsr(struct usb_serial_port *port, __u8 lsr) +{ + struct ark3116_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&priv->status_lock, flags); + /* combine bits */ + priv->lsr |= lsr; + spin_unlock_irqrestore(&priv->status_lock, flags); + + if (lsr&UART_LSR_BRK_ERROR_BITS) { + if (lsr & UART_LSR_BI) + port->icount.brk++; + if (lsr & UART_LSR_FE) + port->icount.frame++; + if (lsr & UART_LSR_PE) + port->icount.parity++; + if (lsr & UART_LSR_OE) + port->icount.overrun++; + } +} + +static void ark3116_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + int status = urb->status; + const __u8 *data = urb->transfer_buffer; + int result; + + switch (status) { + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + break; + case 0: /* success */ + /* discovered this by trail and error... */ + if ((urb->actual_length == 4) && (data[0] == 0xe8)) { + const __u8 id = data[1]&UART_IIR_ID; + dev_dbg(&port->dev, "%s: iir=%02x\n", __func__, data[1]); + if (id == UART_IIR_MSI) { + dev_dbg(&port->dev, "%s: msr=%02x\n", + __func__, data[3]); + ark3116_update_msr(port, data[3]); + break; + } else if (id == UART_IIR_RLSI) { + dev_dbg(&port->dev, "%s: lsr=%02x\n", + __func__, data[2]); + ark3116_update_lsr(port, data[2]); + break; + } + } + /* + * Not sure what this data meant... + */ + usb_serial_debug_data(&port->dev, __func__, + urb->actual_length, + urb->transfer_buffer); + break; + } + + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) + dev_err(&urb->dev->dev, + "%s - Error %d submitting interrupt urb\n", + __func__, result); +} + + +/* Data comes in via the bulk (data) URB, erors/interrupts via the int URB. + * This means that we cannot be sure which data byte has an associated error + * condition, so we report an error for all data in the next bulk read. + * + * Actually, there might even be a window between the bulk data leaving the + * ark and reading/resetting the lsr in the read_bulk_callback where an + * interrupt for the next data block could come in. + * Without somekind of ordering on the ark, we would have to report the + * error for the next block of data as well... + * For now, let's pretend this can't happen. + */ +static void ark3116_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct ark3116_private *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + char tty_flag = TTY_NORMAL; + unsigned long flags; + __u32 lsr; + + /* update line status */ + spin_lock_irqsave(&priv->status_lock, flags); + lsr = priv->lsr; + priv->lsr &= ~UART_LSR_BRK_ERROR_BITS; + spin_unlock_irqrestore(&priv->status_lock, flags); + + if (!urb->actual_length) + return; + + if (lsr & UART_LSR_BRK_ERROR_BITS) { + if (lsr & UART_LSR_BI) + tty_flag = TTY_BREAK; + else if (lsr & UART_LSR_PE) + tty_flag = TTY_PARITY; + else if (lsr & UART_LSR_FE) + tty_flag = TTY_FRAME; + + /* overrun is special, not associated with a char */ + if (lsr & UART_LSR_OE) + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } + tty_insert_flip_string_fixed_flag(&port->port, data, tty_flag, + urb->actual_length); + tty_flip_buffer_push(&port->port); +} + +static struct usb_serial_driver ark3116_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ark3116", + }, + .id_table = id_table, + .num_ports = 1, + .attach = ark3116_attach, + .port_probe = ark3116_port_probe, + .port_remove = ark3116_port_remove, + .set_termios = ark3116_set_termios, + .init_termios = ark3116_init_termios, + .ioctl = ark3116_ioctl, + .tiocmget = ark3116_tiocmget, + .tiocmset = ark3116_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .open = ark3116_open, + .close = ark3116_close, + .break_ctl = ark3116_break_ctl, + .read_int_callback = ark3116_read_int_callback, + .process_read_urb = ark3116_process_read_urb, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ark3116_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_LICENSE("GPL"); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); + +/* + * The following describes what I learned from studying the old + * ark3116.c driver, disassembling the windows driver, and some lucky + * guesses. Since I do not have any datasheet or other + * documentation, inaccuracies are almost guaranteed. + * + * Some specs for the ARK3116 can be found here: + * http://web.archive.org/web/20060318000438/ + * www.arkmicro.com/en/products/view.php?id=10 + * On that page, 2 GPIO pins are mentioned: I assume these are the + * OUT1 and OUT2 pins of the UART, so I added support for those + * through the MCR. Since the pins are not available on my hardware, + * I could not verify this. + * Also, it states there is "on-chip hardware flow control". I have + * discovered how to enable that. Unfortunately, I do not know how to + * enable XON/XOFF (software) flow control, which would need support + * from the chip as well to work. Because of the wording on the web + * page there is a real possibility the chip simply does not support + * software flow control. + * + * I got my ark3116 as part of a mobile phone adapter cable. On the + * PCB, the following numbered contacts are present: + * + * 1:- +5V + * 2:o DTR + * 3:i RX + * 4:i DCD + * 5:o RTS + * 6:o TX + * 7:i RI + * 8:i DSR + * 10:- 0V + * 11:i CTS + * + * On my chip, all signals seem to be 3.3V, but 5V tolerant. But that + * may be different for the one you have ;-). + * + * The windows driver limits the registers to 0-F, so I assume there + * are actually 16 present on the device. + * + * On an UART interrupt, 4 bytes of data come in on the interrupt + * endpoint. The bytes are 0xe8 IIR LSR MSR. + * + * The baudrate seems to be generated from the 12MHz crystal, using + * 4-times subsampling. So quot=12e6/(4*baud). Also see description + * of register E. + * + * Registers 0-7: + * These seem to be the same as for a regular 16450. The FCR is set + * to UART_FCR_DMA_SELECT (0x8), I guess to enable transfers between + * the UART and the USB bridge/DMA engine. + * + * Register 8: + * By trial and error, I found out that bit 0 enables hardware CTS, + * stopping TX when CTS is +5V. Bit 1 does the same for RTS, making + * RTS +5V when the 3116 cannot transfer the data to the USB bus + * (verified by disabling the reading URB). Note that as far as I can + * tell, the windows driver does NOT use this, so there might be some + * hardware bug or something. + * + * According to a patch provided here + * (http://lkml.org/lkml/2009/7/26/56), the ARK3116 can also be used + * as an IrDA dongle. Since I do not have such a thing, I could not + * investigate that aspect. However, I can speculate ;-). + * + * - IrDA encodes data differently than RS232. Most likely, one of + * the bits in registers 9..E enables the IR ENDEC (encoder/decoder). + * - Depending on the IR transceiver, the input and output need to be + * inverted, so there are probably bits for that as well. + * - IrDA is half-duplex, so there should be a bit for selecting that. + * + * This still leaves at least two registers unaccounted for. Perhaps + * The chip can do XON/XOFF or CRC in HW? + * + * Register 9: + * Set to 0x00 for IrDA, when the baudrate is initialised. + * + * Register A: + * Set to 0x01 for IrDA, at init. + * + * Register B: + * Set to 0x01 for IrDA, 0x00 for RS232, at init. + * + * Register C: + * Set to 00 for IrDA, at init. + * + * Register D: + * Set to 0x41 for IrDA, at init. + * + * Register E: + * Somekind of baudrate override. The windows driver seems to set + * this to 0x00 for normal baudrates, 0x01 for 460800, 0x02 for 921600. + * Since 460800 and 921600 cannot be obtained by dividing 3MHz by an integer, + * it could be somekind of subdivisor thingy. + * However,it does not seem to do anything: selecting 921600 (divisor 3, + * reg E=2), still gets 1 MHz. I also checked if registers 9, C or F would + * work, but they don't. + * + * Register F: unknown + */ diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c new file mode 100644 index 00000000..84217e78 --- /dev/null +++ b/drivers/usb/serial/belkin_sa.c @@ -0,0 +1,502 @@ +/* + * Belkin USB Serial Adapter Driver + * + * Copyright (C) 2000 William Greathouse (wgreathouse@smva.com) + * Copyright (C) 2000-2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2010 Johan Hovold (jhovold@gmail.com) + * + * This program is largely derived from work by the linux-usb group + * and associated source files. Please see the usb/serial files for + * individual credits and copyrights. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + * + * TODO: + * -- Add true modem contol line query capability. Currently we track the + * states reported by the interrupt and the states we request. + * -- Add support for flush commands + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "belkin_sa.h" + +#define DRIVER_AUTHOR "William Greathouse " +#define DRIVER_DESC "USB Belkin Serial converter driver" + +/* function prototypes for a Belkin USB Serial Adapter F5U103 */ +static int belkin_sa_port_probe(struct usb_serial_port *port); +static int belkin_sa_port_remove(struct usb_serial_port *port); +static int belkin_sa_open(struct tty_struct *tty, + struct usb_serial_port *port); +static void belkin_sa_close(struct usb_serial_port *port); +static void belkin_sa_read_int_callback(struct urb *urb); +static void belkin_sa_process_read_urb(struct urb *urb); +static void belkin_sa_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios * old); +static void belkin_sa_break_ctl(struct tty_struct *tty, int break_state); +static int belkin_sa_tiocmget(struct tty_struct *tty); +static int belkin_sa_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); + + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(BELKIN_SA_VID, BELKIN_SA_PID) }, + { USB_DEVICE(BELKIN_OLD_VID, BELKIN_OLD_PID) }, + { USB_DEVICE(PERACOM_VID, PERACOM_PID) }, + { USB_DEVICE(GOHUBS_VID, GOHUBS_PID) }, + { USB_DEVICE(GOHUBS_VID, HANDYLINK_PID) }, + { USB_DEVICE(BELKIN_DOCKSTATION_VID, BELKIN_DOCKSTATION_PID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* All of the device info needed for the serial converters */ +static struct usb_serial_driver belkin_device = { + .driver = { + .owner = THIS_MODULE, + .name = "belkin", + }, + .description = "Belkin / Peracom / GoHubs USB Serial Adapter", + .id_table = id_table, + .num_ports = 1, + .open = belkin_sa_open, + .close = belkin_sa_close, + .read_int_callback = belkin_sa_read_int_callback, + .process_read_urb = belkin_sa_process_read_urb, + .set_termios = belkin_sa_set_termios, + .break_ctl = belkin_sa_break_ctl, + .tiocmget = belkin_sa_tiocmget, + .tiocmset = belkin_sa_tiocmset, + .port_probe = belkin_sa_port_probe, + .port_remove = belkin_sa_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &belkin_device, NULL +}; + +struct belkin_sa_private { + spinlock_t lock; + unsigned long control_state; + unsigned char last_lsr; + unsigned char last_msr; + int bad_flow_control; +}; + + +/* + * *************************************************************************** + * Belkin USB Serial Adapter F5U103 specific driver functions + * *************************************************************************** + */ + +#define WDR_TIMEOUT 5000 /* default urb timeout */ + +/* assumes that struct usb_serial *serial is available */ +#define BSA_USB_CMD(c, v) usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), \ + (c), BELKIN_SA_SET_REQUEST_TYPE, \ + (v), 0, NULL, 0, WDR_TIMEOUT) + +static int belkin_sa_port_probe(struct usb_serial_port *port) +{ + struct usb_device *dev = port->serial->dev; + struct belkin_sa_private *priv; + + priv = kmalloc(sizeof(struct belkin_sa_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + priv->control_state = 0; + priv->last_lsr = 0; + priv->last_msr = 0; + /* see comments at top of file */ + priv->bad_flow_control = + (le16_to_cpu(dev->descriptor.bcdDevice) <= 0x0206) ? 1 : 0; + dev_info(&dev->dev, "bcdDevice: %04x, bfc: %d\n", + le16_to_cpu(dev->descriptor.bcdDevice), + priv->bad_flow_control); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int belkin_sa_port_remove(struct usb_serial_port *port) +{ + struct belkin_sa_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int belkin_sa_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + int retval; + + retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&port->dev, "usb_submit_urb(read int) failed\n"); + return retval; + } + + retval = usb_serial_generic_open(tty, port); + if (retval) + usb_kill_urb(port->interrupt_in_urb); + + return retval; +} + +static void belkin_sa_close(struct usb_serial_port *port) +{ + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); +} + +static void belkin_sa_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct belkin_sa_private *priv; + unsigned char *data = urb->transfer_buffer; + int retval; + int status = urb->status; + unsigned long flags; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + + /* Handle known interrupt data */ + /* ignore data[0] and data[1] */ + + priv = usb_get_serial_port_data(port); + spin_lock_irqsave(&priv->lock, flags); + priv->last_msr = data[BELKIN_SA_MSR_INDEX]; + + /* Record Control Line states */ + if (priv->last_msr & BELKIN_SA_MSR_DSR) + priv->control_state |= TIOCM_DSR; + else + priv->control_state &= ~TIOCM_DSR; + + if (priv->last_msr & BELKIN_SA_MSR_CTS) + priv->control_state |= TIOCM_CTS; + else + priv->control_state &= ~TIOCM_CTS; + + if (priv->last_msr & BELKIN_SA_MSR_RI) + priv->control_state |= TIOCM_RI; + else + priv->control_state &= ~TIOCM_RI; + + if (priv->last_msr & BELKIN_SA_MSR_CD) + priv->control_state |= TIOCM_CD; + else + priv->control_state &= ~TIOCM_CD; + + priv->last_lsr = data[BELKIN_SA_LSR_INDEX]; + spin_unlock_irqrestore(&priv->lock, flags); +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, "%s - usb_submit_urb failed with " + "result %d\n", __func__, retval); +} + +static void belkin_sa_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct belkin_sa_private *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + unsigned char status; + char tty_flag; + + /* Update line status */ + tty_flag = TTY_NORMAL; + + spin_lock_irqsave(&priv->lock, flags); + status = priv->last_lsr; + priv->last_lsr &= ~BELKIN_SA_LSR_ERR; + spin_unlock_irqrestore(&priv->lock, flags); + + if (!urb->actual_length) + return; + + if (status & BELKIN_SA_LSR_ERR) { + /* Break takes precedence over parity, which takes precedence + * over framing errors. */ + if (status & BELKIN_SA_LSR_BI) + tty_flag = TTY_BREAK; + else if (status & BELKIN_SA_LSR_PE) + tty_flag = TTY_PARITY; + else if (status & BELKIN_SA_LSR_FE) + tty_flag = TTY_FRAME; + dev_dbg(&port->dev, "tty_flag = %d\n", tty_flag); + + /* Overrun is special, not associated with a char. */ + if (status & BELKIN_SA_LSR_OE) + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } + + tty_insert_flip_string_fixed_flag(&port->port, data, tty_flag, + urb->actual_length); + tty_flip_buffer_push(&port->port); +} + +static void belkin_sa_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct usb_serial *serial = port->serial; + struct belkin_sa_private *priv = usb_get_serial_port_data(port); + unsigned int iflag; + unsigned int cflag; + unsigned int old_iflag = 0; + unsigned int old_cflag = 0; + __u16 urb_value = 0; /* Will hold the new flags */ + unsigned long flags; + unsigned long control_state; + int bad_flow_control; + speed_t baud; + struct ktermios *termios = &tty->termios; + + iflag = termios->c_iflag; + cflag = termios->c_cflag; + + termios->c_cflag &= ~CMSPAR; + + /* get a local copy of the current port settings */ + spin_lock_irqsave(&priv->lock, flags); + control_state = priv->control_state; + bad_flow_control = priv->bad_flow_control; + spin_unlock_irqrestore(&priv->lock, flags); + + old_iflag = old_termios->c_iflag; + old_cflag = old_termios->c_cflag; + + /* Set the baud rate */ + if ((cflag & CBAUD) != (old_cflag & CBAUD)) { + /* reassert DTR and (maybe) RTS on transition from B0 */ + if ((old_cflag & CBAUD) == B0) { + control_state |= (TIOCM_DTR|TIOCM_RTS); + if (BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST, 1) < 0) + dev_err(&port->dev, "Set DTR error\n"); + /* don't set RTS if using hardware flow control */ + if (!(old_cflag & CRTSCTS)) + if (BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST + , 1) < 0) + dev_err(&port->dev, "Set RTS error\n"); + } + } + + baud = tty_get_baud_rate(tty); + if (baud) { + urb_value = BELKIN_SA_BAUD(baud); + /* Clip to maximum speed */ + if (urb_value == 0) + urb_value = 1; + /* Turn it back into a resulting real baud rate */ + baud = BELKIN_SA_BAUD(urb_value); + + /* Report the actual baud rate back to the caller */ + tty_encode_baud_rate(tty, baud, baud); + if (BSA_USB_CMD(BELKIN_SA_SET_BAUDRATE_REQUEST, urb_value) < 0) + dev_err(&port->dev, "Set baudrate error\n"); + } else { + /* Disable flow control */ + if (BSA_USB_CMD(BELKIN_SA_SET_FLOW_CTRL_REQUEST, + BELKIN_SA_FLOW_NONE) < 0) + dev_err(&port->dev, "Disable flowcontrol error\n"); + /* Drop RTS and DTR */ + control_state &= ~(TIOCM_DTR | TIOCM_RTS); + if (BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST, 0) < 0) + dev_err(&port->dev, "DTR LOW error\n"); + if (BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST, 0) < 0) + dev_err(&port->dev, "RTS LOW error\n"); + } + + /* set the parity */ + if ((cflag ^ old_cflag) & (PARENB | PARODD)) { + if (cflag & PARENB) + urb_value = (cflag & PARODD) ? BELKIN_SA_PARITY_ODD + : BELKIN_SA_PARITY_EVEN; + else + urb_value = BELKIN_SA_PARITY_NONE; + if (BSA_USB_CMD(BELKIN_SA_SET_PARITY_REQUEST, urb_value) < 0) + dev_err(&port->dev, "Set parity error\n"); + } + + /* set the number of data bits */ + if ((cflag & CSIZE) != (old_cflag & CSIZE)) { + switch (cflag & CSIZE) { + case CS5: + urb_value = BELKIN_SA_DATA_BITS(5); + break; + case CS6: + urb_value = BELKIN_SA_DATA_BITS(6); + break; + case CS7: + urb_value = BELKIN_SA_DATA_BITS(7); + break; + case CS8: + urb_value = BELKIN_SA_DATA_BITS(8); + break; + default: + dev_dbg(&port->dev, + "CSIZE was not CS5-CS8, using default of 8\n"); + urb_value = BELKIN_SA_DATA_BITS(8); + break; + } + if (BSA_USB_CMD(BELKIN_SA_SET_DATA_BITS_REQUEST, urb_value) < 0) + dev_err(&port->dev, "Set data bits error\n"); + } + + /* set the number of stop bits */ + if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { + urb_value = (cflag & CSTOPB) ? BELKIN_SA_STOP_BITS(2) + : BELKIN_SA_STOP_BITS(1); + if (BSA_USB_CMD(BELKIN_SA_SET_STOP_BITS_REQUEST, + urb_value) < 0) + dev_err(&port->dev, "Set stop bits error\n"); + } + + /* Set flow control */ + if (((iflag ^ old_iflag) & (IXOFF | IXON)) || + ((cflag ^ old_cflag) & CRTSCTS)) { + urb_value = 0; + if ((iflag & IXOFF) || (iflag & IXON)) + urb_value |= (BELKIN_SA_FLOW_OXON | BELKIN_SA_FLOW_IXON); + else + urb_value &= ~(BELKIN_SA_FLOW_OXON | BELKIN_SA_FLOW_IXON); + + if (cflag & CRTSCTS) + urb_value |= (BELKIN_SA_FLOW_OCTS | BELKIN_SA_FLOW_IRTS); + else + urb_value &= ~(BELKIN_SA_FLOW_OCTS | BELKIN_SA_FLOW_IRTS); + + if (bad_flow_control) + urb_value &= ~(BELKIN_SA_FLOW_IRTS); + + if (BSA_USB_CMD(BELKIN_SA_SET_FLOW_CTRL_REQUEST, urb_value) < 0) + dev_err(&port->dev, "Set flow control error\n"); + } + + /* save off the modified port settings */ + spin_lock_irqsave(&priv->lock, flags); + priv->control_state = control_state; + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void belkin_sa_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + + if (BSA_USB_CMD(BELKIN_SA_SET_BREAK_REQUEST, break_state ? 1 : 0) < 0) + dev_err(&port->dev, "Set break_ctl %d\n", break_state); +} + +static int belkin_sa_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct belkin_sa_private *priv = usb_get_serial_port_data(port); + unsigned long control_state; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + control_state = priv->control_state; + spin_unlock_irqrestore(&priv->lock, flags); + + return control_state; +} + +static int belkin_sa_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + struct belkin_sa_private *priv = usb_get_serial_port_data(port); + unsigned long control_state; + unsigned long flags; + int retval; + int rts = 0; + int dtr = 0; + + spin_lock_irqsave(&priv->lock, flags); + control_state = priv->control_state; + + if (set & TIOCM_RTS) { + control_state |= TIOCM_RTS; + rts = 1; + } + if (set & TIOCM_DTR) { + control_state |= TIOCM_DTR; + dtr = 1; + } + if (clear & TIOCM_RTS) { + control_state &= ~TIOCM_RTS; + rts = 0; + } + if (clear & TIOCM_DTR) { + control_state &= ~TIOCM_DTR; + dtr = 0; + } + + priv->control_state = control_state; + spin_unlock_irqrestore(&priv->lock, flags); + + retval = BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST, rts); + if (retval < 0) { + dev_err(&port->dev, "Set RTS error %d\n", retval); + goto exit; + } + + retval = BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST, dtr); + if (retval < 0) { + dev_err(&port->dev, "Set DTR error %d\n", retval); + goto exit; + } +exit: + return retval; +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/belkin_sa.h b/drivers/usb/serial/belkin_sa.h new file mode 100644 index 00000000..c74b58ab --- /dev/null +++ b/drivers/usb/serial/belkin_sa.h @@ -0,0 +1,124 @@ +/* + * Definitions for Belkin USB Serial Adapter Driver + * + * Copyright (C) 2000 + * William Greathouse (wgreathouse@smva.com) + * + * This program is largely derived from work by the linux-usb group + * and associated source files. Please see the usb/serial files for + * individual credits and copyrights. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + * + * 12-Mar-2001 gkh + * Added GoHubs GO-COM232 device id. + * + * 06-Nov-2000 gkh + * Added old Belkin and Peracom device ids, which this driver supports + * + * 12-Oct-2000 William Greathouse + * First cut at supporting Belkin USB Serial Adapter F5U103 + * I did not have a copy of the original work to support this + * adapter, so pardon any stupid mistakes. All of the information + * I am using to write this driver was acquired by using a modified + * UsbSnoop on Windows2000. + * + */ + +#ifndef __LINUX_USB_SERIAL_BSA_H +#define __LINUX_USB_SERIAL_BSA_H + +#define BELKIN_DOCKSTATION_VID 0x050d /* Vendor Id */ +#define BELKIN_DOCKSTATION_PID 0x1203 /* Product Id */ + +#define BELKIN_SA_VID 0x050d /* Vendor Id */ +#define BELKIN_SA_PID 0x0103 /* Product Id */ + +#define BELKIN_OLD_VID 0x056c /* Belkin's "old" vendor id */ +#define BELKIN_OLD_PID 0x8007 /* Belkin's "old" single port serial converter's id */ + +#define PERACOM_VID 0x0565 /* Peracom's vendor id */ +#define PERACOM_PID 0x0001 /* Peracom's single port serial converter's id */ + +#define GOHUBS_VID 0x0921 /* GoHubs vendor id */ +#define GOHUBS_PID 0x1000 /* GoHubs single port serial converter's id (identical to the Peracom device) */ +#define HANDYLINK_PID 0x1200 /* HandyLink USB's id (identical to the Peracom device) */ + +/* Vendor Request Interface */ +#define BELKIN_SA_SET_BAUDRATE_REQUEST 0 /* Set baud rate */ +#define BELKIN_SA_SET_STOP_BITS_REQUEST 1 /* Set stop bits (1,2) */ +#define BELKIN_SA_SET_DATA_BITS_REQUEST 2 /* Set data bits (5,6,7,8) */ +#define BELKIN_SA_SET_PARITY_REQUEST 3 /* Set parity (None, Even, Odd) */ + +#define BELKIN_SA_SET_DTR_REQUEST 10 /* Set DTR state */ +#define BELKIN_SA_SET_RTS_REQUEST 11 /* Set RTS state */ +#define BELKIN_SA_SET_BREAK_REQUEST 12 /* Set BREAK state */ + +#define BELKIN_SA_SET_FLOW_CTRL_REQUEST 16 /* Set flow control mode */ + + +#ifdef WHEN_I_LEARN_THIS +#define BELKIN_SA_SET_MAGIC_REQUEST 17 /* I don't know, possibly flush */ + /* (always in Wininit sequence before flow control) */ +#define BELKIN_SA_RESET xx /* Reset the port */ +#define BELKIN_SA_GET_MODEM_STATUS xx /* Force return of modem status register */ +#endif + +#define BELKIN_SA_SET_REQUEST_TYPE 0x40 + +#define BELKIN_SA_BAUD(b) (230400/b) + +#define BELKIN_SA_STOP_BITS(b) (b-1) + +#define BELKIN_SA_DATA_BITS(b) (b-5) + +#define BELKIN_SA_PARITY_NONE 0 +#define BELKIN_SA_PARITY_EVEN 1 +#define BELKIN_SA_PARITY_ODD 2 +#define BELKIN_SA_PARITY_MARK 3 +#define BELKIN_SA_PARITY_SPACE 4 + +#define BELKIN_SA_FLOW_NONE 0x0000 /* No flow control */ +#define BELKIN_SA_FLOW_OCTS 0x0001 /* use CTS input to throttle output */ +#define BELKIN_SA_FLOW_ODSR 0x0002 /* use DSR input to throttle output */ +#define BELKIN_SA_FLOW_IDSR 0x0004 /* use DSR input to enable receive */ +#define BELKIN_SA_FLOW_IDTR 0x0008 /* use DTR output for input flow control */ +#define BELKIN_SA_FLOW_IRTS 0x0010 /* use RTS output for input flow control */ +#define BELKIN_SA_FLOW_ORTS 0x0020 /* use RTS to indicate data available to send */ +#define BELKIN_SA_FLOW_ERRSUB 0x0040 /* ???? guess ???? substitute inline errors */ +#define BELKIN_SA_FLOW_OXON 0x0080 /* use XON/XOFF for output flow control */ +#define BELKIN_SA_FLOW_IXON 0x0100 /* use XON/XOFF for input flow control */ + +/* + * It seems that the interrupt pipe is closely modelled after the + * 16550 register layout. This is probably because the adapter can + * be used in a "DOS" environment to simulate a standard hardware port. + */ +#define BELKIN_SA_LSR_INDEX 2 /* Line Status Register */ +#define BELKIN_SA_LSR_RDR 0x01 /* receive data ready */ +#define BELKIN_SA_LSR_OE 0x02 /* overrun error */ +#define BELKIN_SA_LSR_PE 0x04 /* parity error */ +#define BELKIN_SA_LSR_FE 0x08 /* framing error */ +#define BELKIN_SA_LSR_BI 0x10 /* break indicator */ +#define BELKIN_SA_LSR_THE 0x20 /* tx holding register empty */ +#define BELKIN_SA_LSR_TE 0x40 /* transmit register empty */ +#define BELKIN_SA_LSR_ERR 0x80 /* OE | PE | FE | BI */ + +#define BELKIN_SA_MSR_INDEX 3 /* Modem Status Register */ +#define BELKIN_SA_MSR_DCTS 0x01 /* Delta CTS */ +#define BELKIN_SA_MSR_DDSR 0x02 /* Delta DSR */ +#define BELKIN_SA_MSR_DRI 0x04 /* Delta RI */ +#define BELKIN_SA_MSR_DCD 0x08 /* Delta CD */ +#define BELKIN_SA_MSR_CTS 0x10 /* Current CTS */ +#define BELKIN_SA_MSR_DSR 0x20 /* Current DSR */ +#define BELKIN_SA_MSR_RI 0x40 /* Current RI */ +#define BELKIN_SA_MSR_CD 0x80 /* Current CD */ + +#endif /* __LINUX_USB_SERIAL_BSA_H */ + diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c new file mode 100644 index 00000000..7229b265 --- /dev/null +++ b/drivers/usb/serial/bus.c @@ -0,0 +1,196 @@ +/* + * USB Serial Converter Bus specific functions + * + * Copyright (C) 2002 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static int usb_serial_device_match(struct device *dev, + struct device_driver *drv) +{ + struct usb_serial_driver *driver; + const struct usb_serial_port *port; + + /* + * drivers are already assigned to ports in serial_probe so it's + * a simple check here. + */ + port = to_usb_serial_port(dev); + if (!port) + return 0; + + driver = to_usb_serial_driver(drv); + + if (driver == port->serial->type) + return 1; + + return 0; +} + +static ssize_t show_port_number(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + + return sprintf(buf, "%d\n", port->number - port->serial->minor); +} + +static DEVICE_ATTR(port_number, S_IRUGO, show_port_number, NULL); + +static int usb_serial_device_probe(struct device *dev) +{ + struct usb_serial_driver *driver; + struct usb_serial_port *port; + int retval = 0; + int minor; + + port = to_usb_serial_port(dev); + if (!port) { + retval = -ENODEV; + goto exit; + } + + /* make sure suspend/resume doesn't race against port_probe */ + retval = usb_autopm_get_interface(port->serial->interface); + if (retval) + goto exit; + + driver = port->serial->type; + if (driver->port_probe) { + retval = driver->port_probe(port); + if (retval) + goto exit_with_autopm; + } + + retval = device_create_file(dev, &dev_attr_port_number); + if (retval) { + if (driver->port_remove) + retval = driver->port_remove(port); + goto exit_with_autopm; + } + + minor = port->number; + tty_register_device(usb_serial_tty_driver, minor, dev); + dev_info(&port->serial->dev->dev, + "%s converter now attached to ttyUSB%d\n", + driver->description, minor); + +exit_with_autopm: + usb_autopm_put_interface(port->serial->interface); +exit: + return retval; +} + +static int usb_serial_device_remove(struct device *dev) +{ + struct usb_serial_driver *driver; + struct usb_serial_port *port; + int retval = 0; + int minor; + int autopm_err; + + port = to_usb_serial_port(dev); + if (!port) + return -ENODEV; + + /* + * Make sure suspend/resume doesn't race against port_remove. + * + * Note that no further runtime PM callbacks will be made if + * autopm_get fails. + */ + autopm_err = usb_autopm_get_interface(port->serial->interface); + + minor = port->number; + tty_unregister_device(usb_serial_tty_driver, minor); + + device_remove_file(&port->dev, &dev_attr_port_number); + + driver = port->serial->type; + if (driver->port_remove) + retval = driver->port_remove(port); + + dev_info(dev, "%s converter now disconnected from ttyUSB%d\n", + driver->description, minor); + + if (!autopm_err) + usb_autopm_put_interface(port->serial->interface); + + return retval; +} + +static ssize_t store_new_id(struct device_driver *driver, + const char *buf, size_t count) +{ + struct usb_serial_driver *usb_drv = to_usb_serial_driver(driver); + ssize_t retval = usb_store_new_id(&usb_drv->dynids, driver, buf, count); + + if (retval >= 0 && usb_drv->usb_driver != NULL) + retval = usb_store_new_id(&usb_drv->usb_driver->dynids, + &usb_drv->usb_driver->drvwrap.driver, + buf, count); + return retval; +} + +static ssize_t show_dynids(struct device_driver *driver, char *buf) +{ + struct usb_serial_driver *usb_drv = to_usb_serial_driver(driver); + + return usb_show_dynids(&usb_drv->dynids, buf); +} + +static struct driver_attribute drv_attrs[] = { + __ATTR(new_id, S_IRUGO | S_IWUSR, show_dynids, store_new_id), + __ATTR_NULL, +}; + +static void free_dynids(struct usb_serial_driver *drv) +{ + struct usb_dynid *dynid, *n; + + spin_lock(&drv->dynids.lock); + list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { + list_del(&dynid->node); + kfree(dynid); + } + spin_unlock(&drv->dynids.lock); +} + +struct bus_type usb_serial_bus_type = { + .name = "usb-serial", + .match = usb_serial_device_match, + .probe = usb_serial_device_probe, + .remove = usb_serial_device_remove, + .drv_attrs = drv_attrs, +}; + +int usb_serial_bus_register(struct usb_serial_driver *driver) +{ + int retval; + + driver->driver.bus = &usb_serial_bus_type; + spin_lock_init(&driver->dynids.lock); + INIT_LIST_HEAD(&driver->dynids.list); + + retval = driver_register(&driver->driver); + + return retval; +} + +void usb_serial_bus_deregister(struct usb_serial_driver *driver) +{ + free_dynids(driver); + driver_unregister(&driver->driver); +} + diff --git a/drivers/usb/serial/ch341.c b/drivers/usb/serial/ch341.c new file mode 100644 index 00000000..c2a4171a --- /dev/null +++ b/drivers/usb/serial/ch341.c @@ -0,0 +1,611 @@ +/* + * Copyright 2007, Frank A Kingswood + * Copyright 2007, Werner Cornelius + * Copyright 2009, Boris Hajduk + * + * ch341.c implements a serial port driver for the Winchiphead CH341. + * + * The CH341 device can be used to implement an RS232 asynchronous + * serial port, an IEEE-1284 parallel printer port or a memory-like + * interface. In all cases the CH341 supports an I2C interface as well. + * This driver only supports the asynchronous serial interface. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_BAUD_RATE 9600 +#define DEFAULT_TIMEOUT 1000 + +/* flags for IO-Bits */ +#define CH341_BIT_RTS (1 << 6) +#define CH341_BIT_DTR (1 << 5) + +/******************************/ +/* interrupt pipe definitions */ +/******************************/ +/* always 4 interrupt bytes */ +/* first irq byte normally 0x08 */ +/* second irq byte base 0x7d + below */ +/* third irq byte base 0x94 + below */ +/* fourth irq byte normally 0xee */ + +/* second interrupt byte */ +#define CH341_MULT_STAT 0x04 /* multiple status since last interrupt event */ + +/* status returned in third interrupt answer byte, inverted in data + from irq */ +#define CH341_BIT_CTS 0x01 +#define CH341_BIT_DSR 0x02 +#define CH341_BIT_RI 0x04 +#define CH341_BIT_DCD 0x08 +#define CH341_BITS_MODEM_STAT 0x0f /* all bits */ + +/*******************************/ +/* baudrate calculation factor */ +/*******************************/ +#define CH341_BAUDBASE_FACTOR 1532620800 +#define CH341_BAUDBASE_DIVMAX 3 + +/* Break support - the information used to implement this was gleaned from + * the Net/FreeBSD uchcom.c driver by Takanori Watanabe. Domo arigato. + */ + +#define CH341_REQ_WRITE_REG 0x9A +#define CH341_REQ_READ_REG 0x95 +#define CH341_REG_BREAK1 0x05 +#define CH341_REG_BREAK2 0x18 +#define CH341_NBREAK_BITS_REG1 0x01 +#define CH341_NBREAK_BITS_REG2 0x40 + + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x4348, 0x5523) }, + { USB_DEVICE(0x1a86, 0x7523) }, + { USB_DEVICE(0x1a86, 0x5523) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct ch341_private { + spinlock_t lock; /* access lock */ + unsigned baud_rate; /* set baud rate */ + u8 line_control; /* set line control value RTS/DTR */ + u8 line_status; /* active status of modem control inputs */ + u8 multi_status_change; /* status changed multiple since last call */ +}; + +static int ch341_control_out(struct usb_device *dev, u8 request, + u16 value, u16 index) +{ + int r; + + dev_dbg(&dev->dev, "ch341_control_out(%02x,%02x,%04x,%04x)\n", + USB_DIR_OUT|0x40, (int)request, (int)value, (int)index); + + r = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + value, index, NULL, 0, DEFAULT_TIMEOUT); + + return r; +} + +static int ch341_control_in(struct usb_device *dev, + u8 request, u16 value, u16 index, + char *buf, unsigned bufsize) +{ + int r; + + dev_dbg(&dev->dev, "ch341_control_in(%02x,%02x,%04x,%04x,%p,%u)\n", + USB_DIR_IN|0x40, (int)request, (int)value, (int)index, buf, + (int)bufsize); + + r = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, buf, bufsize, DEFAULT_TIMEOUT); + return r; +} + +static int ch341_set_baudrate(struct usb_device *dev, + struct ch341_private *priv) +{ + short a, b; + int r; + unsigned long factor; + short divisor; + + if (!priv->baud_rate) + return -EINVAL; + factor = (CH341_BAUDBASE_FACTOR / priv->baud_rate); + divisor = CH341_BAUDBASE_DIVMAX; + + while ((factor > 0xfff0) && divisor) { + factor >>= 3; + divisor--; + } + + if (factor > 0xfff0) + return -EINVAL; + + factor = 0x10000 - factor; + a = (factor & 0xff00) | divisor; + b = factor & 0xff; + + r = ch341_control_out(dev, 0x9a, 0x1312, a); + if (!r) + r = ch341_control_out(dev, 0x9a, 0x0f2c, b); + + return r; +} + +static int ch341_set_handshake(struct usb_device *dev, u8 control) +{ + return ch341_control_out(dev, 0xa4, ~control, 0); +} + +static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv) +{ + char *buffer; + int r; + const unsigned size = 8; + unsigned long flags; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + r = ch341_control_in(dev, 0x95, 0x0706, 0, buffer, size); + if (r < 0) + goto out; + + /* setup the private status if available */ + if (r == 2) { + r = 0; + spin_lock_irqsave(&priv->lock, flags); + priv->line_status = (~(*buffer)) & CH341_BITS_MODEM_STAT; + priv->multi_status_change = 0; + spin_unlock_irqrestore(&priv->lock, flags); + } else + r = -EPROTO; + +out: kfree(buffer); + return r; +} + +/* -------------------------------------------------------------------------- */ + +static int ch341_configure(struct usb_device *dev, struct ch341_private *priv) +{ + char *buffer; + int r; + const unsigned size = 8; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + /* expect two bytes 0x27 0x00 */ + r = ch341_control_in(dev, 0x5f, 0, 0, buffer, size); + if (r < 0) + goto out; + + r = ch341_control_out(dev, 0xa1, 0, 0); + if (r < 0) + goto out; + + r = ch341_set_baudrate(dev, priv); + if (r < 0) + goto out; + + /* expect two bytes 0x56 0x00 */ + r = ch341_control_in(dev, 0x95, 0x2518, 0, buffer, size); + if (r < 0) + goto out; + + r = ch341_control_out(dev, 0x9a, 0x2518, 0x0050); + if (r < 0) + goto out; + + /* expect 0xff 0xee */ + r = ch341_get_status(dev, priv); + if (r < 0) + goto out; + + r = ch341_control_out(dev, 0xa1, 0x501f, 0xd90a); + if (r < 0) + goto out; + + r = ch341_set_baudrate(dev, priv); + if (r < 0) + goto out; + + r = ch341_set_handshake(dev, priv->line_control); + if (r < 0) + goto out; + + /* expect 0x9f 0xee */ + r = ch341_get_status(dev, priv); + +out: kfree(buffer); + return r; +} + +static int ch341_port_probe(struct usb_serial_port *port) +{ + struct ch341_private *priv; + int r; + + priv = kzalloc(sizeof(struct ch341_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + priv->baud_rate = DEFAULT_BAUD_RATE; + priv->line_control = CH341_BIT_RTS | CH341_BIT_DTR; + + r = ch341_configure(port->serial->dev, priv); + if (r < 0) + goto error; + + usb_set_serial_port_data(port, priv); + return 0; + +error: kfree(priv); + return r; +} + +static int ch341_port_remove(struct usb_serial_port *port) +{ + struct ch341_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int ch341_carrier_raised(struct usb_serial_port *port) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + if (priv->line_status & CH341_BIT_DCD) + return 1; + return 0; +} + +static void ch341_dtr_rts(struct usb_serial_port *port, int on) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + /* drop DTR and RTS */ + spin_lock_irqsave(&priv->lock, flags); + if (on) + priv->line_control |= CH341_BIT_RTS | CH341_BIT_DTR; + else + priv->line_control &= ~(CH341_BIT_RTS | CH341_BIT_DTR); + spin_unlock_irqrestore(&priv->lock, flags); + ch341_set_handshake(port->serial->dev, priv->line_control); +} + +static void ch341_close(struct usb_serial_port *port) +{ + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); +} + + +/* open this device, set default parameters */ +static int ch341_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct ch341_private *priv = usb_get_serial_port_data(port); + int r; + + priv->baud_rate = DEFAULT_BAUD_RATE; + + r = ch341_configure(serial->dev, priv); + if (r) + goto out; + + r = ch341_set_handshake(serial->dev, priv->line_control); + if (r) + goto out; + + r = ch341_set_baudrate(serial->dev, priv); + if (r) + goto out; + + dev_dbg(&port->dev, "%s - submitting interrupt urb", __func__); + r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (r) { + dev_err(&port->dev, "%s - failed submitting interrupt urb," + " error %d\n", __func__, r); + ch341_close(port); + goto out; + } + + r = usb_serial_generic_open(tty, port); + +out: return r; +} + +/* Old_termios contains the original termios settings and + * tty->termios contains the new setting to be used. + */ +static void ch341_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned baud_rate; + unsigned long flags; + + baud_rate = tty_get_baud_rate(tty); + + priv->baud_rate = baud_rate; + + if (baud_rate) { + spin_lock_irqsave(&priv->lock, flags); + priv->line_control |= (CH341_BIT_DTR | CH341_BIT_RTS); + spin_unlock_irqrestore(&priv->lock, flags); + ch341_set_baudrate(port->serial->dev, priv); + } else { + spin_lock_irqsave(&priv->lock, flags); + priv->line_control &= ~(CH341_BIT_DTR | CH341_BIT_RTS); + spin_unlock_irqrestore(&priv->lock, flags); + } + + ch341_set_handshake(port->serial->dev, priv->line_control); + + /* Unimplemented: + * (cflag & CSIZE) : data bits [5, 8] + * (cflag & PARENB) : parity {NONE, EVEN, ODD} + * (cflag & CSTOPB) : stop bits [1, 2] + */ +} + +static void ch341_break_ctl(struct tty_struct *tty, int break_state) +{ + const uint16_t ch341_break_reg = + CH341_REG_BREAK1 | ((uint16_t) CH341_REG_BREAK2 << 8); + struct usb_serial_port *port = tty->driver_data; + int r; + uint16_t reg_contents; + uint8_t *break_reg; + + break_reg = kmalloc(2, GFP_KERNEL); + if (!break_reg) { + dev_err(&port->dev, "%s - kmalloc failed\n", __func__); + return; + } + + r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG, + ch341_break_reg, 0, break_reg, 2); + if (r < 0) { + dev_err(&port->dev, "%s - USB control read error (%d)\n", + __func__, r); + goto out; + } + dev_dbg(&port->dev, "%s - initial ch341 break register contents - reg1: %x, reg2: %x\n", + __func__, break_reg[0], break_reg[1]); + if (break_state != 0) { + dev_dbg(&port->dev, "%s - Enter break state requested\n", __func__); + break_reg[0] &= ~CH341_NBREAK_BITS_REG1; + break_reg[1] &= ~CH341_NBREAK_BITS_REG2; + } else { + dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__); + break_reg[0] |= CH341_NBREAK_BITS_REG1; + break_reg[1] |= CH341_NBREAK_BITS_REG2; + } + dev_dbg(&port->dev, "%s - New ch341 break register contents - reg1: %x, reg2: %x\n", + __func__, break_reg[0], break_reg[1]); + reg_contents = get_unaligned_le16(break_reg); + r = ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG, + ch341_break_reg, reg_contents); + if (r < 0) + dev_err(&port->dev, "%s - USB control write error (%d)\n", + __func__, r); +out: + kfree(break_reg); +} + +static int ch341_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 control; + + spin_lock_irqsave(&priv->lock, flags); + if (set & TIOCM_RTS) + priv->line_control |= CH341_BIT_RTS; + if (set & TIOCM_DTR) + priv->line_control |= CH341_BIT_DTR; + if (clear & TIOCM_RTS) + priv->line_control &= ~CH341_BIT_RTS; + if (clear & TIOCM_DTR) + priv->line_control &= ~CH341_BIT_DTR; + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + + return ch341_set_handshake(port->serial->dev, control); +} + +static void ch341_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = (struct usb_serial_port *) urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int actual_length = urb->actual_length; + int status; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", + __func__, urb->status); + goto exit; + } + + usb_serial_debug_data(&port->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + if (actual_length >= 4) { + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 prev_line_status = priv->line_status; + + spin_lock_irqsave(&priv->lock, flags); + priv->line_status = (~(data[2])) & CH341_BITS_MODEM_STAT; + if ((data[1] & CH341_MULT_STAT)) + priv->multi_status_change = 1; + spin_unlock_irqrestore(&priv->lock, flags); + + if ((priv->line_status ^ prev_line_status) & CH341_BIT_DCD) { + struct tty_struct *tty = tty_port_tty_get(&port->port); + if (tty) + usb_serial_handle_dcd_change(port, tty, + priv->line_status & CH341_BIT_DCD); + tty_kref_put(tty); + } + + wake_up_interruptible(&port->port.delta_msr_wait); + } + +exit: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + dev_err(&urb->dev->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, status); +} + +static int ch341_tiocmiwait(struct tty_struct *tty, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 prevstatus; + u8 status; + u8 changed; + u8 multi_change = 0; + + spin_lock_irqsave(&priv->lock, flags); + prevstatus = priv->line_status; + priv->multi_status_change = 0; + spin_unlock_irqrestore(&priv->lock, flags); + + while (!multi_change) { + interruptible_sleep_on(&port->port.delta_msr_wait); + /* see if a signal did it */ + if (signal_pending(current)) + return -ERESTARTSYS; + + if (port->serial->disconnected) + return -EIO; + + spin_lock_irqsave(&priv->lock, flags); + status = priv->line_status; + multi_change = priv->multi_status_change; + spin_unlock_irqrestore(&priv->lock, flags); + + changed = prevstatus ^ status; + + if (((arg & TIOCM_RNG) && (changed & CH341_BIT_RI)) || + ((arg & TIOCM_DSR) && (changed & CH341_BIT_DSR)) || + ((arg & TIOCM_CD) && (changed & CH341_BIT_DCD)) || + ((arg & TIOCM_CTS) && (changed & CH341_BIT_CTS))) { + return 0; + } + prevstatus = status; + } + + return 0; +} + +static int ch341_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 mcr; + u8 status; + unsigned int result; + + spin_lock_irqsave(&priv->lock, flags); + mcr = priv->line_control; + status = priv->line_status; + spin_unlock_irqrestore(&priv->lock, flags); + + result = ((mcr & CH341_BIT_DTR) ? TIOCM_DTR : 0) + | ((mcr & CH341_BIT_RTS) ? TIOCM_RTS : 0) + | ((status & CH341_BIT_CTS) ? TIOCM_CTS : 0) + | ((status & CH341_BIT_DSR) ? TIOCM_DSR : 0) + | ((status & CH341_BIT_RI) ? TIOCM_RI : 0) + | ((status & CH341_BIT_DCD) ? TIOCM_CD : 0); + + dev_dbg(&port->dev, "%s - result = %x\n", __func__, result); + + return result; +} + +static int ch341_reset_resume(struct usb_serial *serial) +{ + struct ch341_private *priv; + + priv = usb_get_serial_port_data(serial->port[0]); + + /* reconfigure ch341 serial port after bus-reset */ + ch341_configure(serial->dev, priv); + + return 0; +} + +static struct usb_serial_driver ch341_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ch341-uart", + }, + .id_table = id_table, + .num_ports = 1, + .open = ch341_open, + .dtr_rts = ch341_dtr_rts, + .carrier_raised = ch341_carrier_raised, + .close = ch341_close, + .set_termios = ch341_set_termios, + .break_ctl = ch341_break_ctl, + .tiocmget = ch341_tiocmget, + .tiocmset = ch341_tiocmset, + .tiocmiwait = ch341_tiocmiwait, + .read_int_callback = ch341_read_int_callback, + .port_probe = ch341_port_probe, + .port_remove = ch341_port_remove, + .reset_resume = ch341_reset_resume, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ch341_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/console.c b/drivers/usb/serial/console.c new file mode 100644 index 00000000..5f3bcd31 --- /dev/null +++ b/drivers/usb/serial/console.c @@ -0,0 +1,313 @@ +/* + * USB Serial Console driver + * + * Copyright (C) 2001 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * Thanks to Randy Dunlap for the original version of this code. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +struct usbcons_info { + int magic; + int break_flag; + struct usb_serial_port *port; +}; + +static struct usbcons_info usbcons_info; +static struct console usbcons; + +/* + * ------------------------------------------------------------ + * USB Serial console driver + * + * Much of the code here is copied from drivers/char/serial.c + * and implements a phony serial console in the same way that + * serial.c does so that in case some software queries it, + * it will get the same results. + * + * Things that are different from the way the serial port code + * does things, is that we call the lower level usb-serial + * driver code to initialize the device, and we set the initial + * console speeds based on the command line arguments. + * ------------------------------------------------------------ + */ + + +/* + * The parsing of the command line works exactly like the + * serial.c code, except that the specifier is "ttyUSB" instead + * of "ttyS". + */ +static int usb_console_setup(struct console *co, char *options) +{ + struct usbcons_info *info = &usbcons_info; + int baud = 9600; + int bits = 8; + int parity = 'n'; + int doflow = 0; + int cflag = CREAD | HUPCL | CLOCAL; + char *s; + struct usb_serial *serial; + struct usb_serial_port *port; + int retval; + struct tty_struct *tty = NULL; + struct ktermios dummy; + + if (options) { + baud = simple_strtoul(options, NULL, 10); + s = options; + while (*s >= '0' && *s <= '9') + s++; + if (*s) + parity = *s++; + if (*s) + bits = *s++ - '0'; + if (*s) + doflow = (*s++ == 'r'); + } + + /* Sane default */ + if (baud == 0) + baud = 9600; + + switch (bits) { + case 7: + cflag |= CS7; + break; + default: + case 8: + cflag |= CS8; + break; + } + switch (parity) { + case 'o': case 'O': + cflag |= PARODD; + break; + case 'e': case 'E': + cflag |= PARENB; + break; + } + co->cflag = cflag; + + /* + * no need to check the index here: if the index is wrong, console + * code won't call us + */ + serial = usb_serial_get_by_index(co->index); + if (serial == NULL) { + /* no device is connected yet, sorry :( */ + pr_err("No USB device connected to ttyUSB%i\n", co->index); + return -ENODEV; + } + + retval = usb_autopm_get_interface(serial->interface); + if (retval) + goto error_get_interface; + + port = serial->port[co->index - serial->minor]; + tty_port_tty_set(&port->port, NULL); + + info->port = port; + + ++port->port.count; + if (!test_bit(ASYNCB_INITIALIZED, &port->port.flags)) { + if (serial->type->set_termios) { + /* + * allocate a fake tty so the driver can initialize + * the termios structure, then later call set_termios to + * configure according to command line arguments + */ + tty = kzalloc(sizeof(*tty), GFP_KERNEL); + if (!tty) { + retval = -ENOMEM; + dev_err(&port->dev, "no more memory\n"); + goto reset_open_count; + } + kref_init(&tty->kref); + tty_port_tty_set(&port->port, tty); + tty->driver = usb_serial_tty_driver; + tty->index = co->index; + if (tty_init_termios(tty)) { + retval = -ENOMEM; + dev_err(&port->dev, "no more memory\n"); + goto free_tty; + } + } + + /* only call the device specific open if this + * is the first time the port is opened */ + if (serial->type->open) + retval = serial->type->open(NULL, port); + else + retval = usb_serial_generic_open(NULL, port); + + if (retval) { + dev_err(&port->dev, "could not open USB console port\n"); + goto fail; + } + + if (serial->type->set_termios) { + tty->termios.c_cflag = cflag; + tty_termios_encode_baud_rate(&tty->termios, baud, baud); + memset(&dummy, 0, sizeof(struct ktermios)); + serial->type->set_termios(tty, port, &dummy); + + tty_port_tty_set(&port->port, NULL); + kfree(tty); + } + set_bit(ASYNCB_INITIALIZED, &port->port.flags); + } + /* Now that any required fake tty operations are completed restore + * the tty port count */ + --port->port.count; + /* The console is special in terms of closing the device so + * indicate this port is now acting as a system console. */ + port->port.console = 1; + + mutex_unlock(&serial->disc_mutex); + return retval; + + fail: + tty_port_tty_set(&port->port, NULL); + free_tty: + kfree(tty); + reset_open_count: + port->port.count = 0; + usb_autopm_put_interface(serial->interface); + error_get_interface: + usb_serial_put(serial); + mutex_unlock(&serial->disc_mutex); + return retval; +} + +static void usb_console_write(struct console *co, + const char *buf, unsigned count) +{ + static struct usbcons_info *info = &usbcons_info; + struct usb_serial_port *port = info->port; + struct usb_serial *serial; + int retval = -ENODEV; + + if (!port || port->serial->dev->state == USB_STATE_NOTATTACHED) + return; + serial = port->serial; + + if (count == 0) + return; + + pr_debug("%s - port %d, %d byte(s)\n", __func__, port->number, count); + + if (!port->port.console) { + pr_debug("%s - port not opened\n", __func__); + return; + } + + while (count) { + unsigned int i; + unsigned int lf; + /* search for LF so we can insert CR if necessary */ + for (i = 0, lf = 0 ; i < count ; i++) { + if (*(buf + i) == 10) { + lf = 1; + i++; + break; + } + } + /* pass on to the driver specific version of this function if + it is available */ + if (serial->type->write) + retval = serial->type->write(NULL, port, buf, i); + else + retval = usb_serial_generic_write(NULL, port, buf, i); + pr_debug("%s - return value : %d\n", __func__, retval); + if (lf) { + /* append CR after LF */ + unsigned char cr = 13; + if (serial->type->write) + retval = serial->type->write(NULL, + port, &cr, 1); + else + retval = usb_serial_generic_write(NULL, + port, &cr, 1); + pr_debug("%s - return value : %d\n", __func__, retval); + } + buf += i; + count -= i; + } +} + +static struct tty_driver *usb_console_device(struct console *co, int *index) +{ + struct tty_driver **p = (struct tty_driver **)co->data; + + if (!*p) + return NULL; + + *index = co->index; + return *p; +} + +static struct console usbcons = { + .name = "ttyUSB", + .write = usb_console_write, + .device = usb_console_device, + .setup = usb_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &usb_serial_tty_driver, +}; + +void usb_serial_console_disconnect(struct usb_serial *serial) +{ + if (serial && serial->port && serial->port[0] + && serial->port[0] == usbcons_info.port) { + usb_serial_console_exit(); + usb_serial_put(serial); + } +} + +void usb_serial_console_init(int minor) +{ + if (minor == 0) { + /* + * Call register_console() if this is the first device plugged + * in. If we call it earlier, then the callback to + * console_setup() will fail, as there is not a device seen by + * the USB subsystem yet. + */ + /* + * Register console. + * NOTES: + * console_setup() is called (back) immediately (from + * register_console). console_write() is called immediately + * from register_console iff CON_PRINTBUFFER is set in flags. + */ + pr_debug("registering the USB serial console.\n"); + register_console(&usbcons); + } +} + +void usb_serial_console_exit(void) +{ + if (usbcons_info.port) { + unregister_console(&usbcons); + usbcons_info.port->port.console = 0; + usbcons_info.port = NULL; + } +} + diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c new file mode 100644 index 00000000..a24714f6 --- /dev/null +++ b/drivers/usb/serial/cp210x.c @@ -0,0 +1,898 @@ +/* + * Silicon Laboratories CP210x USB to RS232 serial adaptor driver + * + * Copyright (C) 2005 Craig Shelley (craig@microtron.org.uk) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * Support to set flow control line levels using TIOCMGET and TIOCMSET + * thanks to Karl Hiramoto karl@hiramoto.org. RTSCTS hardware flow + * control thanks to Munir Nassar nassarmu@real-time.com + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver" + +/* + * Function Prototypes + */ +static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *); +static void cp210x_close(struct usb_serial_port *); +static void cp210x_get_termios(struct tty_struct *, struct usb_serial_port *); +static void cp210x_get_termios_port(struct usb_serial_port *port, + unsigned int *cflagp, unsigned int *baudp); +static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *, + struct ktermios *); +static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *, + struct ktermios*); +static int cp210x_tiocmget(struct tty_struct *); +static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int); +static int cp210x_tiocmset_port(struct usb_serial_port *port, + unsigned int, unsigned int); +static void cp210x_break_ctl(struct tty_struct *, int); +static int cp210x_startup(struct usb_serial *); +static void cp210x_release(struct usb_serial *); +static void cp210x_dtr_rts(struct usb_serial_port *p, int on); + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x045B, 0x0053) }, /* Renesas RX610 RX-Stick */ + { USB_DEVICE(0x0471, 0x066A) }, /* AKTAKOM ACE-1001 cable */ + { USB_DEVICE(0x0489, 0xE000) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */ + { USB_DEVICE(0x0489, 0xE003) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */ + { USB_DEVICE(0x0745, 0x1000) }, /* CipherLab USB CCD Barcode Scanner 1000 */ + { USB_DEVICE(0x0846, 0x1100) }, /* NetGear Managed Switch M4100 series, M5300 series, M7100 series */ + { USB_DEVICE(0x08e6, 0x5501) }, /* Gemalto Prox-PU/CU contactless smartcard reader */ + { USB_DEVICE(0x08FD, 0x000A) }, /* Digianswer A/S , ZigBee/802.15.4 MAC Device */ + { USB_DEVICE(0x0BED, 0x1100) }, /* MEI (TM) Cashflow-SC Bill/Voucher Acceptor */ + { USB_DEVICE(0x0BED, 0x1101) }, /* MEI series 2000 Combo Acceptor */ + { USB_DEVICE(0x0FCF, 0x1003) }, /* Dynastream ANT development board */ + { USB_DEVICE(0x0FCF, 0x1004) }, /* Dynastream ANT2USB */ + { USB_DEVICE(0x0FCF, 0x1006) }, /* Dynastream ANT development board */ + { USB_DEVICE(0x0FDE, 0xCA05) }, /* OWL Wireless Electricity Monitor CM-160 */ + { USB_DEVICE(0x10A6, 0xAA26) }, /* Knock-off DCU-11 cable */ + { USB_DEVICE(0x10AB, 0x10C5) }, /* Siemens MC60 Cable */ + { USB_DEVICE(0x10B5, 0xAC70) }, /* Nokia CA-42 USB */ + { USB_DEVICE(0x10C4, 0x0F91) }, /* Vstabi */ + { USB_DEVICE(0x10C4, 0x1101) }, /* Arkham Technology DS101 Bus Monitor */ + { USB_DEVICE(0x10C4, 0x1601) }, /* Arkham Technology DS101 Adapter */ + { USB_DEVICE(0x10C4, 0x800A) }, /* SPORTident BSM7-D-USB main station */ + { USB_DEVICE(0x10C4, 0x803B) }, /* Pololu USB-serial converter */ + { USB_DEVICE(0x10C4, 0x8044) }, /* Cygnal Debug Adapter */ + { USB_DEVICE(0x10C4, 0x804E) }, /* Software Bisque Paramount ME build-in converter */ + { USB_DEVICE(0x10C4, 0x8053) }, /* Enfora EDG1228 */ + { USB_DEVICE(0x10C4, 0x8054) }, /* Enfora GSM2228 */ + { USB_DEVICE(0x10C4, 0x8066) }, /* Argussoft In-System Programmer */ + { USB_DEVICE(0x10C4, 0x806F) }, /* IMS USB to RS422 Converter Cable */ + { USB_DEVICE(0x10C4, 0x807A) }, /* Crumb128 board */ + { USB_DEVICE(0x10C4, 0x80C4) }, /* Cygnal Integrated Products, Inc., Optris infrared thermometer */ + { USB_DEVICE(0x10C4, 0x80CA) }, /* Degree Controls Inc */ + { USB_DEVICE(0x10C4, 0x80DD) }, /* Tracient RFID */ + { USB_DEVICE(0x10C4, 0x80F6) }, /* Suunto sports instrument */ + { USB_DEVICE(0x10C4, 0x8115) }, /* Arygon NFC/Mifare Reader */ + { USB_DEVICE(0x10C4, 0x813D) }, /* Burnside Telecom Deskmobile */ + { USB_DEVICE(0x10C4, 0x813F) }, /* Tams Master Easy Control */ + { USB_DEVICE(0x10C4, 0x814A) }, /* West Mountain Radio RIGblaster P&P */ + { USB_DEVICE(0x10C4, 0x814B) }, /* West Mountain Radio RIGtalk */ + { USB_DEVICE(0x2405, 0x0003) }, /* West Mountain Radio RIGblaster Advantage */ + { USB_DEVICE(0x10C4, 0x8156) }, /* B&G H3000 link cable */ + { USB_DEVICE(0x10C4, 0x815E) }, /* Helicomm IP-Link 1220-DVM */ + { USB_DEVICE(0x10C4, 0x815F) }, /* Timewave HamLinkUSB */ + { USB_DEVICE(0x10C4, 0x818B) }, /* AVIT Research USB to TTL */ + { USB_DEVICE(0x10C4, 0x819F) }, /* MJS USB Toslink Switcher */ + { USB_DEVICE(0x10C4, 0x81A6) }, /* ThinkOptics WavIt */ + { USB_DEVICE(0x10C4, 0x81A9) }, /* Multiplex RC Interface */ + { USB_DEVICE(0x10C4, 0x81AC) }, /* MSD Dash Hawk */ + { USB_DEVICE(0x10C4, 0x81AD) }, /* INSYS USB Modem */ + { USB_DEVICE(0x10C4, 0x81C8) }, /* Lipowsky Industrie Elektronik GmbH, Baby-JTAG */ + { USB_DEVICE(0x10C4, 0x81E2) }, /* Lipowsky Industrie Elektronik GmbH, Baby-LIN */ + { USB_DEVICE(0x10C4, 0x81E7) }, /* Aerocomm Radio */ + { USB_DEVICE(0x10C4, 0x81E8) }, /* Zephyr Bioharness */ + { USB_DEVICE(0x10C4, 0x81F2) }, /* C1007 HF band RFID controller */ + { USB_DEVICE(0x10C4, 0x8218) }, /* Lipowsky Industrie Elektronik GmbH, HARP-1 */ + { USB_DEVICE(0x10C4, 0x822B) }, /* Modem EDGE(GSM) Comander 2 */ + { USB_DEVICE(0x10C4, 0x826B) }, /* Cygnal Integrated Products, Inc., Fasttrax GPS demonstration module */ + { USB_DEVICE(0x10C4, 0x8281) }, /* Nanotec Plug & Drive */ + { USB_DEVICE(0x10C4, 0x8293) }, /* Telegesis ETRX2USB */ + { USB_DEVICE(0x10C4, 0x82F9) }, /* Procyon AVS */ + { USB_DEVICE(0x10C4, 0x8341) }, /* Siemens MC35PU GPRS Modem */ + { USB_DEVICE(0x10C4, 0x8382) }, /* Cygnal Integrated Products, Inc. */ + { USB_DEVICE(0x10C4, 0x83A8) }, /* Amber Wireless AMB2560 */ + { USB_DEVICE(0x10C4, 0x83D8) }, /* DekTec DTA Plus VHF/UHF Booster/Attenuator */ + { USB_DEVICE(0x10C4, 0x8411) }, /* Kyocera GPS Module */ + { USB_DEVICE(0x10C4, 0x8418) }, /* IRZ Automation Teleport SG-10 GSM/GPRS Modem */ + { USB_DEVICE(0x10C4, 0x846E) }, /* BEI USB Sensor Interface (VCP) */ + { USB_DEVICE(0x10C4, 0x8477) }, /* Balluff RFID */ + { USB_DEVICE(0x10C4, 0x85EA) }, /* AC-Services IBUS-IF */ + { USB_DEVICE(0x10C4, 0x85EB) }, /* AC-Services CIS-IBUS */ + { USB_DEVICE(0x10C4, 0x85F8) }, /* Virtenio Preon32 */ + { USB_DEVICE(0x10C4, 0x8664) }, /* AC-Services CAN-IF */ + { USB_DEVICE(0x10C4, 0x8665) }, /* AC-Services OBD-IF */ + { USB_DEVICE(0x10C4, 0x8875) }, /* CEL MeshConnect USB Stick */ + { USB_DEVICE(0x10C4, 0x88A4) }, /* MMB Networks ZigBee USB Device */ + { USB_DEVICE(0x10C4, 0x88A5) }, /* Planet Innovation Ingeni ZigBee USB Device */ + { USB_DEVICE(0x10C4, 0x8946) }, /* Ketra N1 Wireless Interface */ + { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA80) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA71) }, /* Infinity GPS-MIC-1 Radio Monophone */ + { USB_DEVICE(0x10C4, 0xF001) }, /* Elan Digital Systems USBscope50 */ + { USB_DEVICE(0x10C4, 0xF002) }, /* Elan Digital Systems USBwave12 */ + { USB_DEVICE(0x10C4, 0xF003) }, /* Elan Digital Systems USBpulse100 */ + { USB_DEVICE(0x10C4, 0xF004) }, /* Elan Digital Systems USBcount50 */ + { USB_DEVICE(0x10C5, 0xEA61) }, /* Silicon Labs MobiData GPRS USB Modem */ + { USB_DEVICE(0x10CE, 0xEA6A) }, /* Silicon Labs MobiData GPRS USB Modem 100EU */ + { USB_DEVICE(0x13AD, 0x9999) }, /* Baltech card reader */ + { USB_DEVICE(0x1555, 0x0004) }, /* Owen AC4 USB-RS485 Converter */ + { USB_DEVICE(0x166A, 0x0201) }, /* Clipsal 5500PACA C-Bus Pascal Automation Controller */ + { USB_DEVICE(0x166A, 0x0301) }, /* Clipsal 5800PC C-Bus Wireless PC Interface */ + { USB_DEVICE(0x166A, 0x0303) }, /* Clipsal 5500PCU C-Bus USB interface */ + { USB_DEVICE(0x166A, 0x0304) }, /* Clipsal 5000CT2 C-Bus Black and White Touchscreen */ + { USB_DEVICE(0x166A, 0x0305) }, /* Clipsal C-5000CT2 C-Bus Spectrum Colour Touchscreen */ + { USB_DEVICE(0x166A, 0x0401) }, /* Clipsal L51xx C-Bus Architectural Dimmer */ + { USB_DEVICE(0x166A, 0x0101) }, /* Clipsal 5560884 C-Bus Multi-room Audio Matrix Switcher */ + { USB_DEVICE(0x16D6, 0x0001) }, /* Jablotron serial interface */ + { USB_DEVICE(0x16DC, 0x0010) }, /* W-IE-NE-R Plein & Baus GmbH PL512 Power Supply */ + { USB_DEVICE(0x16DC, 0x0011) }, /* W-IE-NE-R Plein & Baus GmbH RCM Remote Control for MARATON Power Supply */ + { USB_DEVICE(0x16DC, 0x0012) }, /* W-IE-NE-R Plein & Baus GmbH MPOD Multi Channel Power Supply */ + { USB_DEVICE(0x16DC, 0x0015) }, /* W-IE-NE-R Plein & Baus GmbH CML Control, Monitoring and Data Logger */ + { USB_DEVICE(0x17A8, 0x0001) }, /* Kamstrup Optical Eye/3-wire */ + { USB_DEVICE(0x17A8, 0x0005) }, /* Kamstrup M-Bus Master MultiPort 250D */ + { USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */ + { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */ + { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */ + { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ + { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ + { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ + { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ + { USB_DEVICE(0x1D6F, 0x0010) }, /* Seluxit ApS RF Dongle */ + { USB_DEVICE(0x1E29, 0x0102) }, /* Festo CPX-USB */ + { USB_DEVICE(0x1E29, 0x0501) }, /* Festo CMSP */ + { USB_DEVICE(0x1FB9, 0x0100) }, /* Lake Shore Model 121 Current Source */ + { USB_DEVICE(0x1FB9, 0x0200) }, /* Lake Shore Model 218A Temperature Monitor */ + { USB_DEVICE(0x1FB9, 0x0201) }, /* Lake Shore Model 219 Temperature Monitor */ + { USB_DEVICE(0x1FB9, 0x0202) }, /* Lake Shore Model 233 Temperature Transmitter */ + { USB_DEVICE(0x1FB9, 0x0203) }, /* Lake Shore Model 235 Temperature Transmitter */ + { USB_DEVICE(0x1FB9, 0x0300) }, /* Lake Shore Model 335 Temperature Controller */ + { USB_DEVICE(0x1FB9, 0x0301) }, /* Lake Shore Model 336 Temperature Controller */ + { USB_DEVICE(0x1FB9, 0x0302) }, /* Lake Shore Model 350 Temperature Controller */ + { USB_DEVICE(0x1FB9, 0x0303) }, /* Lake Shore Model 371 AC Bridge */ + { USB_DEVICE(0x1FB9, 0x0400) }, /* Lake Shore Model 411 Handheld Gaussmeter */ + { USB_DEVICE(0x1FB9, 0x0401) }, /* Lake Shore Model 425 Gaussmeter */ + { USB_DEVICE(0x1FB9, 0x0402) }, /* Lake Shore Model 455A Gaussmeter */ + { USB_DEVICE(0x1FB9, 0x0403) }, /* Lake Shore Model 475A Gaussmeter */ + { USB_DEVICE(0x1FB9, 0x0404) }, /* Lake Shore Model 465 Three Axis Gaussmeter */ + { USB_DEVICE(0x1FB9, 0x0600) }, /* Lake Shore Model 625A Superconducting MPS */ + { USB_DEVICE(0x1FB9, 0x0601) }, /* Lake Shore Model 642A Magnet Power Supply */ + { USB_DEVICE(0x1FB9, 0x0602) }, /* Lake Shore Model 648 Magnet Power Supply */ + { USB_DEVICE(0x1FB9, 0x0700) }, /* Lake Shore Model 737 VSM Controller */ + { USB_DEVICE(0x1FB9, 0x0701) }, /* Lake Shore Model 776 Hall Matrix */ + { USB_DEVICE(0x3195, 0xF190) }, /* Link Instruments MSO-19 */ + { USB_DEVICE(0x3195, 0xF280) }, /* Link Instruments MSO-28 */ + { USB_DEVICE(0x3195, 0xF281) }, /* Link Instruments MSO-28 */ + { USB_DEVICE(0x413C, 0x9500) }, /* DW700 GPS USB interface */ + { } /* Terminating Entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +struct cp210x_serial_private { + __u8 bInterfaceNumber; +}; + +static struct usb_serial_driver cp210x_device = { + .driver = { + .owner = THIS_MODULE, + .name = "cp210x", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_in_size = 256, + .bulk_out_size = 256, + .open = cp210x_open, + .close = cp210x_close, + .break_ctl = cp210x_break_ctl, + .set_termios = cp210x_set_termios, + .tiocmget = cp210x_tiocmget, + .tiocmset = cp210x_tiocmset, + .attach = cp210x_startup, + .release = cp210x_release, + .dtr_rts = cp210x_dtr_rts +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &cp210x_device, NULL +}; + +/* Config request types */ +#define REQTYPE_HOST_TO_INTERFACE 0x41 +#define REQTYPE_INTERFACE_TO_HOST 0xc1 +#define REQTYPE_HOST_TO_DEVICE 0x40 +#define REQTYPE_DEVICE_TO_HOST 0xc0 + +/* Config request codes */ +#define CP210X_IFC_ENABLE 0x00 +#define CP210X_SET_BAUDDIV 0x01 +#define CP210X_GET_BAUDDIV 0x02 +#define CP210X_SET_LINE_CTL 0x03 +#define CP210X_GET_LINE_CTL 0x04 +#define CP210X_SET_BREAK 0x05 +#define CP210X_IMM_CHAR 0x06 +#define CP210X_SET_MHS 0x07 +#define CP210X_GET_MDMSTS 0x08 +#define CP210X_SET_XON 0x09 +#define CP210X_SET_XOFF 0x0A +#define CP210X_SET_EVENTMASK 0x0B +#define CP210X_GET_EVENTMASK 0x0C +#define CP210X_SET_CHAR 0x0D +#define CP210X_GET_CHARS 0x0E +#define CP210X_GET_PROPS 0x0F +#define CP210X_GET_COMM_STATUS 0x10 +#define CP210X_RESET 0x11 +#define CP210X_PURGE 0x12 +#define CP210X_SET_FLOW 0x13 +#define CP210X_GET_FLOW 0x14 +#define CP210X_EMBED_EVENTS 0x15 +#define CP210X_GET_EVENTSTATE 0x16 +#define CP210X_SET_CHARS 0x19 +#define CP210X_GET_BAUDRATE 0x1D +#define CP210X_SET_BAUDRATE 0x1E + +/* CP210X_IFC_ENABLE */ +#define UART_ENABLE 0x0001 +#define UART_DISABLE 0x0000 + +/* CP210X_(SET|GET)_BAUDDIV */ +#define BAUD_RATE_GEN_FREQ 0x384000 + +/* CP210X_(SET|GET)_LINE_CTL */ +#define BITS_DATA_MASK 0X0f00 +#define BITS_DATA_5 0X0500 +#define BITS_DATA_6 0X0600 +#define BITS_DATA_7 0X0700 +#define BITS_DATA_8 0X0800 +#define BITS_DATA_9 0X0900 + +#define BITS_PARITY_MASK 0x00f0 +#define BITS_PARITY_NONE 0x0000 +#define BITS_PARITY_ODD 0x0010 +#define BITS_PARITY_EVEN 0x0020 +#define BITS_PARITY_MARK 0x0030 +#define BITS_PARITY_SPACE 0x0040 + +#define BITS_STOP_MASK 0x000f +#define BITS_STOP_1 0x0000 +#define BITS_STOP_1_5 0x0001 +#define BITS_STOP_2 0x0002 + +/* CP210X_SET_BREAK */ +#define BREAK_ON 0x0001 +#define BREAK_OFF 0x0000 + +/* CP210X_(SET_MHS|GET_MDMSTS) */ +#define CONTROL_DTR 0x0001 +#define CONTROL_RTS 0x0002 +#define CONTROL_CTS 0x0010 +#define CONTROL_DSR 0x0020 +#define CONTROL_RING 0x0040 +#define CONTROL_DCD 0x0080 +#define CONTROL_WRITE_DTR 0x0100 +#define CONTROL_WRITE_RTS 0x0200 + +/* + * cp210x_get_config + * Reads from the CP210x configuration registers + * 'size' is specified in bytes. + * 'data' is a pointer to a pre-allocated array of integers large + * enough to hold 'size' bytes (with 4 bytes to each integer) + */ +static int cp210x_get_config(struct usb_serial_port *port, u8 request, + unsigned int *data, int size) +{ + struct usb_serial *serial = port->serial; + struct cp210x_serial_private *spriv = usb_get_serial_data(serial); + __le32 *buf; + int result, i, length; + + /* Number of integers required to contain the array */ + length = (((size - 1) | 3) + 1) / 4; + + buf = kcalloc(length, sizeof(__le32), GFP_KERNEL); + if (!buf) { + dev_err(&port->dev, "%s - out of memory.\n", __func__); + return -ENOMEM; + } + + /* Issue the request, attempting to read 'size' bytes */ + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + request, REQTYPE_INTERFACE_TO_HOST, 0x0000, + spriv->bInterfaceNumber, buf, size, + USB_CTRL_GET_TIMEOUT); + + /* Convert data into an array of integers */ + for (i = 0; i < length; i++) + data[i] = le32_to_cpu(buf[i]); + + kfree(buf); + + if (result != size) { + dev_dbg(&port->dev, "%s - Unable to send config request, request=0x%x size=%d result=%d\n", + __func__, request, size, result); + if (result > 0) + result = -EPROTO; + + return result; + } + + return 0; +} + +/* + * cp210x_set_config + * Writes to the CP210x configuration registers + * Values less than 16 bits wide are sent directly + * 'size' is specified in bytes. + */ +static int cp210x_set_config(struct usb_serial_port *port, u8 request, + unsigned int *data, int size) +{ + struct usb_serial *serial = port->serial; + struct cp210x_serial_private *spriv = usb_get_serial_data(serial); + __le32 *buf; + int result, i, length; + + /* Number of integers required to contain the array */ + length = (((size - 1) | 3) + 1) / 4; + + buf = kmalloc(length * sizeof(__le32), GFP_KERNEL); + if (!buf) { + dev_err(&port->dev, "%s - out of memory.\n", __func__); + return -ENOMEM; + } + + /* Array of integers into bytes */ + for (i = 0; i < length; i++) + buf[i] = cpu_to_le32(data[i]); + + if (size > 2) { + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, REQTYPE_HOST_TO_INTERFACE, 0x0000, + spriv->bInterfaceNumber, buf, size, + USB_CTRL_SET_TIMEOUT); + } else { + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, REQTYPE_HOST_TO_INTERFACE, data[0], + spriv->bInterfaceNumber, NULL, 0, + USB_CTRL_SET_TIMEOUT); + } + + kfree(buf); + + if ((size > 2 && result != size) || result < 0) { + dev_dbg(&port->dev, "%s - Unable to send request, request=0x%x size=%d result=%d\n", + __func__, request, size, result); + if (result > 0) + result = -EPROTO; + + return result; + } + + return 0; +} + +/* + * cp210x_set_config_single + * Convenience function for calling cp210x_set_config on single data values + * without requiring an integer pointer + */ +static inline int cp210x_set_config_single(struct usb_serial_port *port, + u8 request, unsigned int data) +{ + return cp210x_set_config(port, request, &data, 2); +} + +/* + * cp210x_quantise_baudrate + * Quantises the baud rate as per AN205 Table 1 + */ +static unsigned int cp210x_quantise_baudrate(unsigned int baud) +{ + if (baud <= 300) + baud = 300; + else if (baud <= 600) baud = 600; + else if (baud <= 1200) baud = 1200; + else if (baud <= 1800) baud = 1800; + else if (baud <= 2400) baud = 2400; + else if (baud <= 4000) baud = 4000; + else if (baud <= 4803) baud = 4800; + else if (baud <= 7207) baud = 7200; + else if (baud <= 9612) baud = 9600; + else if (baud <= 14428) baud = 14400; + else if (baud <= 16062) baud = 16000; + else if (baud <= 19250) baud = 19200; + else if (baud <= 28912) baud = 28800; + else if (baud <= 38601) baud = 38400; + else if (baud <= 51558) baud = 51200; + else if (baud <= 56280) baud = 56000; + else if (baud <= 58053) baud = 57600; + else if (baud <= 64111) baud = 64000; + else if (baud <= 77608) baud = 76800; + else if (baud <= 117028) baud = 115200; + else if (baud <= 129347) baud = 128000; + else if (baud <= 156868) baud = 153600; + else if (baud <= 237832) baud = 230400; + else if (baud <= 254234) baud = 250000; + else if (baud <= 273066) baud = 256000; + else if (baud <= 491520) baud = 460800; + else if (baud <= 567138) baud = 500000; + else if (baud <= 670254) baud = 576000; + else if (baud < 1000000) + baud = 921600; + else if (baud > 2000000) + baud = 2000000; + return baud; +} + +static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int result; + + result = cp210x_set_config_single(port, CP210X_IFC_ENABLE, + UART_ENABLE); + if (result) { + dev_err(&port->dev, "%s - Unable to enable UART\n", __func__); + return result; + } + + /* Configure the termios structure */ + cp210x_get_termios(tty, port); + + /* The baud rate must be initialised on cp2104 */ + if (tty) + cp210x_change_speed(tty, port, NULL); + + return usb_serial_generic_open(tty, port); +} + +static void cp210x_close(struct usb_serial_port *port) +{ + usb_serial_generic_close(port); + cp210x_set_config_single(port, CP210X_IFC_ENABLE, UART_DISABLE); +} + +/* + * cp210x_get_termios + * Reads the baud rate, data bits, parity, stop bits and flow control mode + * from the device, corrects any unsupported values, and configures the + * termios structure to reflect the state of the device + */ +static void cp210x_get_termios(struct tty_struct *tty, + struct usb_serial_port *port) +{ + unsigned int baud; + + if (tty) { + cp210x_get_termios_port(tty->driver_data, + &tty->termios.c_cflag, &baud); + tty_encode_baud_rate(tty, baud, baud); + } else { + unsigned int cflag; + cflag = 0; + cp210x_get_termios_port(port, &cflag, &baud); + } +} + +/* + * cp210x_get_termios_port + * This is the heart of cp210x_get_termios which always uses a &usb_serial_port. + */ +static void cp210x_get_termios_port(struct usb_serial_port *port, + unsigned int *cflagp, unsigned int *baudp) +{ + struct device *dev = &port->dev; + unsigned int cflag, modem_ctl[4]; + unsigned int baud; + unsigned int bits; + + cp210x_get_config(port, CP210X_GET_BAUDRATE, &baud, 4); + + dev_dbg(dev, "%s - baud rate = %d\n", __func__, baud); + *baudp = baud; + + cflag = *cflagp; + + cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cflag &= ~CSIZE; + switch (bits & BITS_DATA_MASK) { + case BITS_DATA_5: + dev_dbg(dev, "%s - data bits = 5\n", __func__); + cflag |= CS5; + break; + case BITS_DATA_6: + dev_dbg(dev, "%s - data bits = 6\n", __func__); + cflag |= CS6; + break; + case BITS_DATA_7: + dev_dbg(dev, "%s - data bits = 7\n", __func__); + cflag |= CS7; + break; + case BITS_DATA_8: + dev_dbg(dev, "%s - data bits = 8\n", __func__); + cflag |= CS8; + break; + case BITS_DATA_9: + dev_dbg(dev, "%s - data bits = 9 (not supported, using 8 data bits)\n", __func__); + cflag |= CS8; + bits &= ~BITS_DATA_MASK; + bits |= BITS_DATA_8; + cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2); + break; + default: + dev_dbg(dev, "%s - Unknown number of data bits, using 8\n", __func__); + cflag |= CS8; + bits &= ~BITS_DATA_MASK; + bits |= BITS_DATA_8; + cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2); + break; + } + + switch (bits & BITS_PARITY_MASK) { + case BITS_PARITY_NONE: + dev_dbg(dev, "%s - parity = NONE\n", __func__); + cflag &= ~PARENB; + break; + case BITS_PARITY_ODD: + dev_dbg(dev, "%s - parity = ODD\n", __func__); + cflag |= (PARENB|PARODD); + break; + case BITS_PARITY_EVEN: + dev_dbg(dev, "%s - parity = EVEN\n", __func__); + cflag &= ~PARODD; + cflag |= PARENB; + break; + case BITS_PARITY_MARK: + dev_dbg(dev, "%s - parity = MARK\n", __func__); + cflag |= (PARENB|PARODD|CMSPAR); + break; + case BITS_PARITY_SPACE: + dev_dbg(dev, "%s - parity = SPACE\n", __func__); + cflag &= ~PARODD; + cflag |= (PARENB|CMSPAR); + break; + default: + dev_dbg(dev, "%s - Unknown parity mode, disabling parity\n", __func__); + cflag &= ~PARENB; + bits &= ~BITS_PARITY_MASK; + cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2); + break; + } + + cflag &= ~CSTOPB; + switch (bits & BITS_STOP_MASK) { + case BITS_STOP_1: + dev_dbg(dev, "%s - stop bits = 1\n", __func__); + break; + case BITS_STOP_1_5: + dev_dbg(dev, "%s - stop bits = 1.5 (not supported, using 1 stop bit)\n", __func__); + bits &= ~BITS_STOP_MASK; + cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2); + break; + case BITS_STOP_2: + dev_dbg(dev, "%s - stop bits = 2\n", __func__); + cflag |= CSTOPB; + break; + default: + dev_dbg(dev, "%s - Unknown number of stop bits, using 1 stop bit\n", __func__); + bits &= ~BITS_STOP_MASK; + cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2); + break; + } + + cp210x_get_config(port, CP210X_GET_FLOW, modem_ctl, 16); + if (modem_ctl[0] & 0x0008) { + dev_dbg(dev, "%s - flow control = CRTSCTS\n", __func__); + cflag |= CRTSCTS; + } else { + dev_dbg(dev, "%s - flow control = NONE\n", __func__); + cflag &= ~CRTSCTS; + } + + *cflagp = cflag; +} + +/* + * CP2101 supports the following baud rates: + * + * 300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 28800, + * 38400, 56000, 57600, 115200, 128000, 230400, 460800, 921600 + * + * CP2102 and CP2103 support the following additional rates: + * + * 4000, 16000, 51200, 64000, 76800, 153600, 250000, 256000, 500000, + * 576000 + * + * The device will map a requested rate to a supported one, but the result + * of requests for rates greater than 1053257 is undefined (see AN205). + * + * CP2104, CP2105 and CP2110 support most rates up to 2M, 921k and 1M baud, + * respectively, with an error less than 1%. The actual rates are determined + * by + * + * div = round(freq / (2 x prescale x request)) + * actual = freq / (2 x prescale x div) + * + * For CP2104 and CP2105 freq is 48Mhz and prescale is 4 for request <= 365bps + * or 1 otherwise. + * For CP2110 freq is 24Mhz and prescale is 4 for request <= 300bps or 1 + * otherwise. + */ +static void cp210x_change_speed(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + u32 baud; + + baud = tty->termios.c_ospeed; + + /* This maps the requested rate to a rate valid on cp2102 or cp2103, + * or to an arbitrary rate in [1M,2M]. + * + * NOTE: B0 is not implemented. + */ + baud = cp210x_quantise_baudrate(baud); + + dev_dbg(&port->dev, "%s - setting baud rate to %u\n", __func__, baud); + if (cp210x_set_config(port, CP210X_SET_BAUDRATE, &baud, + sizeof(baud))) { + dev_warn(&port->dev, "failed to set baud rate to %u\n", baud); + if (old_termios) + baud = old_termios->c_ospeed; + else + baud = 9600; + } + + tty_encode_baud_rate(tty, baud, baud); +} + +static void cp210x_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct device *dev = &port->dev; + unsigned int cflag, old_cflag; + unsigned int bits; + unsigned int modem_ctl[4]; + + dev_dbg(dev, "%s - port %d\n", __func__, port->number); + + if (!tty) + return; + + cflag = tty->termios.c_cflag; + old_cflag = old_termios->c_cflag; + + if (tty->termios.c_ospeed != old_termios->c_ospeed) + cp210x_change_speed(tty, port, old_termios); + + /* If the number of data bits is to be updated */ + if ((cflag & CSIZE) != (old_cflag & CSIZE)) { + cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + bits &= ~BITS_DATA_MASK; + switch (cflag & CSIZE) { + case CS5: + bits |= BITS_DATA_5; + dev_dbg(dev, "%s - data bits = 5\n", __func__); + break; + case CS6: + bits |= BITS_DATA_6; + dev_dbg(dev, "%s - data bits = 6\n", __func__); + break; + case CS7: + bits |= BITS_DATA_7; + dev_dbg(dev, "%s - data bits = 7\n", __func__); + break; + case CS8: + bits |= BITS_DATA_8; + dev_dbg(dev, "%s - data bits = 8\n", __func__); + break; + /*case CS9: + bits |= BITS_DATA_9; + dev_dbg(dev, "%s - data bits = 9\n", __func__); + break;*/ + default: + dev_dbg(dev, "cp210x driver does not support the number of bits requested, using 8 bit mode\n"); + bits |= BITS_DATA_8; + break; + } + if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2)) + dev_dbg(dev, "Number of data bits requested not supported by device\n"); + } + + if ((cflag & (PARENB|PARODD|CMSPAR)) != + (old_cflag & (PARENB|PARODD|CMSPAR))) { + cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + bits &= ~BITS_PARITY_MASK; + if (cflag & PARENB) { + if (cflag & CMSPAR) { + if (cflag & PARODD) { + bits |= BITS_PARITY_MARK; + dev_dbg(dev, "%s - parity = MARK\n", __func__); + } else { + bits |= BITS_PARITY_SPACE; + dev_dbg(dev, "%s - parity = SPACE\n", __func__); + } + } else { + if (cflag & PARODD) { + bits |= BITS_PARITY_ODD; + dev_dbg(dev, "%s - parity = ODD\n", __func__); + } else { + bits |= BITS_PARITY_EVEN; + dev_dbg(dev, "%s - parity = EVEN\n", __func__); + } + } + } + if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2)) + dev_dbg(dev, "Parity mode not supported by device\n"); + } + + if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { + cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + bits &= ~BITS_STOP_MASK; + if (cflag & CSTOPB) { + bits |= BITS_STOP_2; + dev_dbg(dev, "%s - stop bits = 2\n", __func__); + } else { + bits |= BITS_STOP_1; + dev_dbg(dev, "%s - stop bits = 1\n", __func__); + } + if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2)) + dev_dbg(dev, "Number of stop bits requested not supported by device\n"); + } + + if ((cflag & CRTSCTS) != (old_cflag & CRTSCTS)) { + cp210x_get_config(port, CP210X_GET_FLOW, modem_ctl, 16); + dev_dbg(dev, "%s - read modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x\n", + __func__, modem_ctl[0], modem_ctl[1], + modem_ctl[2], modem_ctl[3]); + + if (cflag & CRTSCTS) { + modem_ctl[0] &= ~0x7B; + modem_ctl[0] |= 0x09; + modem_ctl[1] = 0x80; + dev_dbg(dev, "%s - flow control = CRTSCTS\n", __func__); + } else { + modem_ctl[0] &= ~0x7B; + modem_ctl[0] |= 0x01; + modem_ctl[1] |= 0x40; + dev_dbg(dev, "%s - flow control = NONE\n", __func__); + } + + dev_dbg(dev, "%s - write modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x\n", + __func__, modem_ctl[0], modem_ctl[1], + modem_ctl[2], modem_ctl[3]); + cp210x_set_config(port, CP210X_SET_FLOW, modem_ctl, 16); + } + +} + +static int cp210x_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + return cp210x_tiocmset_port(port, set, clear); +} + +static int cp210x_tiocmset_port(struct usb_serial_port *port, + unsigned int set, unsigned int clear) +{ + unsigned int control = 0; + + if (set & TIOCM_RTS) { + control |= CONTROL_RTS; + control |= CONTROL_WRITE_RTS; + } + if (set & TIOCM_DTR) { + control |= CONTROL_DTR; + control |= CONTROL_WRITE_DTR; + } + if (clear & TIOCM_RTS) { + control &= ~CONTROL_RTS; + control |= CONTROL_WRITE_RTS; + } + if (clear & TIOCM_DTR) { + control &= ~CONTROL_DTR; + control |= CONTROL_WRITE_DTR; + } + + dev_dbg(&port->dev, "%s - control = 0x%.4x\n", __func__, control); + + return cp210x_set_config(port, CP210X_SET_MHS, &control, 2); +} + +static void cp210x_dtr_rts(struct usb_serial_port *p, int on) +{ + if (on) + cp210x_tiocmset_port(p, TIOCM_DTR|TIOCM_RTS, 0); + else + cp210x_tiocmset_port(p, 0, TIOCM_DTR|TIOCM_RTS); +} + +static int cp210x_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int control; + int result; + + cp210x_get_config(port, CP210X_GET_MDMSTS, &control, 1); + + result = ((control & CONTROL_DTR) ? TIOCM_DTR : 0) + |((control & CONTROL_RTS) ? TIOCM_RTS : 0) + |((control & CONTROL_CTS) ? TIOCM_CTS : 0) + |((control & CONTROL_DSR) ? TIOCM_DSR : 0) + |((control & CONTROL_RING)? TIOCM_RI : 0) + |((control & CONTROL_DCD) ? TIOCM_CD : 0); + + dev_dbg(&port->dev, "%s - control = 0x%.2x\n", __func__, control); + + return result; +} + +static void cp210x_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int state; + + if (break_state == 0) + state = BREAK_OFF; + else + state = BREAK_ON; + dev_dbg(&port->dev, "%s - turning break %s\n", __func__, + state == BREAK_OFF ? "off" : "on"); + cp210x_set_config(port, CP210X_SET_BREAK, &state, 2); +} + +static int cp210x_startup(struct usb_serial *serial) +{ + struct usb_host_interface *cur_altsetting; + struct cp210x_serial_private *spriv; + + /* cp210x buffers behave strangely unless device is reset */ + usb_reset_device(serial->dev); + + spriv = kzalloc(sizeof(*spriv), GFP_KERNEL); + if (!spriv) + return -ENOMEM; + + cur_altsetting = serial->interface->cur_altsetting; + spriv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber; + + usb_set_serial_data(serial, spriv); + + return 0; +} + +static void cp210x_release(struct usb_serial *serial) +{ + struct cp210x_serial_private *spriv; + + spriv = usb_get_serial_data(serial); + kfree(spriv); +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c new file mode 100644 index 00000000..78142623 --- /dev/null +++ b/drivers/usb/serial/cyberjack.c @@ -0,0 +1,422 @@ +/* + * REINER SCT cyberJack pinpad/e-com USB Chipcard Reader Driver + * + * Copyright (C) 2001 REINER SCT + * Author: Matthias Bruestle + * + * Contact: support@reiner-sct.com (see MAINTAINERS) + * + * This program is largely derived from work by the linux-usb group + * and associated source files. Please see the usb/serial files for + * individual credits and copyrights. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Thanks to Greg Kroah-Hartman (greg@kroah.com) for his help and + * patience. + * + * In case of problems, please write to the contact e-mail address + * mentioned above. + * + * Please note that later models of the cyberjack reader family are + * supported by a libusb-based userspace device driver. + * + * Homepage: http://www.reiner-sct.de/support/treiber_cyberjack.php#linux + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CYBERJACK_LOCAL_BUF_SIZE 32 + +#define DRIVER_AUTHOR "Matthias Bruestle" +#define DRIVER_DESC "REINER SCT cyberJack pinpad/e-com USB Chipcard Reader Driver" + + +#define CYBERJACK_VENDOR_ID 0x0C4B +#define CYBERJACK_PRODUCT_ID 0x0100 + +/* Function prototypes */ +static int cyberjack_port_probe(struct usb_serial_port *port); +static int cyberjack_port_remove(struct usb_serial_port *port); +static int cyberjack_open(struct tty_struct *tty, + struct usb_serial_port *port); +static void cyberjack_close(struct usb_serial_port *port); +static int cyberjack_write(struct tty_struct *tty, + struct usb_serial_port *port, const unsigned char *buf, int count); +static int cyberjack_write_room(struct tty_struct *tty); +static void cyberjack_read_int_callback(struct urb *urb); +static void cyberjack_read_bulk_callback(struct urb *urb); +static void cyberjack_write_bulk_callback(struct urb *urb); + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(CYBERJACK_VENDOR_ID, CYBERJACK_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver cyberjack_device = { + .driver = { + .owner = THIS_MODULE, + .name = "cyberjack", + }, + .description = "Reiner SCT Cyberjack USB card reader", + .id_table = id_table, + .num_ports = 1, + .port_probe = cyberjack_port_probe, + .port_remove = cyberjack_port_remove, + .open = cyberjack_open, + .close = cyberjack_close, + .write = cyberjack_write, + .write_room = cyberjack_write_room, + .read_int_callback = cyberjack_read_int_callback, + .read_bulk_callback = cyberjack_read_bulk_callback, + .write_bulk_callback = cyberjack_write_bulk_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &cyberjack_device, NULL +}; + +struct cyberjack_private { + spinlock_t lock; /* Lock for SMP */ + short rdtodo; /* Bytes still to read */ + unsigned char wrbuf[5*64]; /* Buffer for collecting data to write */ + short wrfilled; /* Overall data size we already got */ + short wrsent; /* Data already sent */ +}; + +static int cyberjack_port_probe(struct usb_serial_port *port) +{ + struct cyberjack_private *priv; + int result; + + priv = kmalloc(sizeof(struct cyberjack_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + priv->rdtodo = 0; + priv->wrfilled = 0; + priv->wrsent = 0; + + usb_set_serial_port_data(port, priv); + + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) + dev_err(&port->dev, "usb_submit_urb(read int) failed\n"); + + return 0; +} + +static int cyberjack_port_remove(struct usb_serial_port *port) +{ + struct cyberjack_private *priv; + + usb_kill_urb(port->interrupt_in_urb); + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int cyberjack_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + struct cyberjack_private *priv; + unsigned long flags; + int result = 0; + + dev_dbg(&port->dev, "%s - usb_clear_halt\n", __func__); + usb_clear_halt(port->serial->dev, port->write_urb->pipe); + + priv = usb_get_serial_port_data(port); + spin_lock_irqsave(&priv->lock, flags); + priv->rdtodo = 0; + priv->wrfilled = 0; + priv->wrsent = 0; + spin_unlock_irqrestore(&priv->lock, flags); + + return result; +} + +static void cyberjack_close(struct usb_serial_port *port) +{ + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); +} + +static int cyberjack_write(struct tty_struct *tty, + struct usb_serial_port *port, const unsigned char *buf, int count) +{ + struct device *dev = &port->dev; + struct cyberjack_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int result; + int wrexpected; + + if (count == 0) { + dev_dbg(dev, "%s - write request of 0 bytes\n", __func__); + return 0; + } + + if (!test_and_clear_bit(0, &port->write_urbs_free)) { + dev_dbg(dev, "%s - already writing\n", __func__); + return 0; + } + + spin_lock_irqsave(&priv->lock, flags); + + if (count+priv->wrfilled > sizeof(priv->wrbuf)) { + /* To much data for buffer. Reset buffer. */ + priv->wrfilled = 0; + spin_unlock_irqrestore(&priv->lock, flags); + set_bit(0, &port->write_urbs_free); + return 0; + } + + /* Copy data */ + memcpy(priv->wrbuf + priv->wrfilled, buf, count); + + usb_serial_debug_data(dev, __func__, count, priv->wrbuf + priv->wrfilled); + priv->wrfilled += count; + + if (priv->wrfilled >= 3) { + wrexpected = ((int)priv->wrbuf[2]<<8)+priv->wrbuf[1]+3; + dev_dbg(dev, "%s - expected data: %d\n", __func__, wrexpected); + } else + wrexpected = sizeof(priv->wrbuf); + + if (priv->wrfilled >= wrexpected) { + /* We have enough data to begin transmission */ + int length; + + dev_dbg(dev, "%s - transmitting data (frame 1)\n", __func__); + length = (wrexpected > port->bulk_out_size) ? + port->bulk_out_size : wrexpected; + + memcpy(port->write_urb->transfer_buffer, priv->wrbuf, length); + priv->wrsent = length; + + /* set up our urb */ + port->write_urb->transfer_buffer_length = length; + + /* send the data out the bulk port */ + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (result) { + dev_err(&port->dev, + "%s - failed submitting write urb, error %d", + __func__, result); + /* Throw away data. No better idea what to do with it. */ + priv->wrfilled = 0; + priv->wrsent = 0; + spin_unlock_irqrestore(&priv->lock, flags); + set_bit(0, &port->write_urbs_free); + return 0; + } + + dev_dbg(dev, "%s - priv->wrsent=%d\n", __func__, priv->wrsent); + dev_dbg(dev, "%s - priv->wrfilled=%d\n", __func__, priv->wrfilled); + + if (priv->wrsent >= priv->wrfilled) { + dev_dbg(dev, "%s - buffer cleaned\n", __func__); + memset(priv->wrbuf, 0, sizeof(priv->wrbuf)); + priv->wrfilled = 0; + priv->wrsent = 0; + } + } + + spin_unlock_irqrestore(&priv->lock, flags); + + return count; +} + +static int cyberjack_write_room(struct tty_struct *tty) +{ + /* FIXME: .... */ + return CYBERJACK_LOCAL_BUF_SIZE; +} + +static void cyberjack_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct cyberjack_private *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + int result; + + /* the urb might have been killed. */ + if (status) + return; + + usb_serial_debug_data(dev, __func__, urb->actual_length, data); + + /* React only to interrupts signaling a bulk_in transfer */ + if (urb->actual_length == 4 && data[0] == 0x01) { + short old_rdtodo; + + /* This is a announcement of coming bulk_ins. */ + unsigned short size = ((unsigned short)data[3]<<8)+data[2]+3; + + spin_lock(&priv->lock); + + old_rdtodo = priv->rdtodo; + + if (old_rdtodo + size < old_rdtodo) { + dev_dbg(dev, "To many bulk_in urbs to do.\n"); + spin_unlock(&priv->lock); + goto resubmit; + } + + /* "+=" is probably more fault tollerant than "=" */ + priv->rdtodo += size; + + dev_dbg(dev, "%s - rdtodo: %d\n", __func__, priv->rdtodo); + + spin_unlock(&priv->lock); + + if (!old_rdtodo) { + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (result) + dev_err(dev, "%s - failed resubmitting read urb, error %d\n", + __func__, result); + dev_dbg(dev, "%s - usb_submit_urb(read urb)\n", __func__); + } + } + +resubmit: + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + if (result) + dev_err(&port->dev, "usb_submit_urb(read int) failed\n"); + dev_dbg(dev, "%s - usb_submit_urb(int urb)\n", __func__); +} + +static void cyberjack_read_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct cyberjack_private *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + unsigned char *data = urb->transfer_buffer; + short todo; + int result; + int status = urb->status; + + usb_serial_debug_data(dev, __func__, urb->actual_length, data); + if (status) { + dev_dbg(dev, "%s - nonzero read bulk status received: %d\n", + __func__, status); + return; + } + + if (urb->actual_length) { + tty_insert_flip_string(&port->port, data, urb->actual_length); + tty_flip_buffer_push(&port->port); + } + + spin_lock(&priv->lock); + + /* Reduce urbs to do by one. */ + priv->rdtodo -= urb->actual_length; + /* Just to be sure */ + if (priv->rdtodo < 0) + priv->rdtodo = 0; + todo = priv->rdtodo; + + spin_unlock(&priv->lock); + + dev_dbg(dev, "%s - rdtodo: %d\n", __func__, todo); + + /* Continue to read if we have still urbs to do. */ + if (todo /* || (urb->actual_length==port->bulk_in_endpointAddress)*/) { + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (result) + dev_err(dev, "%s - failed resubmitting read urb, error %d\n", + __func__, result); + dev_dbg(dev, "%s - usb_submit_urb(read urb)\n", __func__); + } +} + +static void cyberjack_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct cyberjack_private *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + int status = urb->status; + + set_bit(0, &port->write_urbs_free); + if (status) { + dev_dbg(dev, "%s - nonzero write bulk status received: %d\n", + __func__, status); + return; + } + + spin_lock(&priv->lock); + + /* only do something if we have more data to send */ + if (priv->wrfilled) { + int length, blksize, result; + + dev_dbg(dev, "%s - transmitting data (frame n)\n", __func__); + + length = ((priv->wrfilled - priv->wrsent) > port->bulk_out_size) ? + port->bulk_out_size : (priv->wrfilled - priv->wrsent); + + memcpy(port->write_urb->transfer_buffer, + priv->wrbuf + priv->wrsent, length); + priv->wrsent += length; + + /* set up our urb */ + port->write_urb->transfer_buffer_length = length; + + /* send the data out the bulk port */ + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (result) { + dev_err(dev, "%s - failed submitting write urb, error %d\n", + __func__, result); + /* Throw away data. No better idea what to do with it. */ + priv->wrfilled = 0; + priv->wrsent = 0; + goto exit; + } + + dev_dbg(dev, "%s - priv->wrsent=%d\n", __func__, priv->wrsent); + dev_dbg(dev, "%s - priv->wrfilled=%d\n", __func__, priv->wrfilled); + + blksize = ((int)priv->wrbuf[2]<<8)+priv->wrbuf[1]+3; + + if (priv->wrsent >= priv->wrfilled || + priv->wrsent >= blksize) { + dev_dbg(dev, "%s - buffer cleaned\n", __func__); + memset(priv->wrbuf, 0, sizeof(priv->wrbuf)); + priv->wrfilled = 0; + priv->wrsent = 0; + } + } + +exit: + spin_unlock(&priv->lock); + usb_serial_port_softint(port); +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c new file mode 100644 index 00000000..08212019 --- /dev/null +++ b/drivers/usb/serial/cypress_m8.c @@ -0,0 +1,1307 @@ +/* + * USB Cypress M8 driver + * + * Copyright (C) 2004 + * Lonnie Mendez (dignome@gmail.com) + * Copyright (C) 2003,2004 + * Neil Whelchel (koyama@firstlight.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + * + * See http://geocities.com/i0xox0i for information on this driver and the + * earthmate usb device. + */ + +/* Thanks to Neil Whelchel for writing the first cypress m8 implementation + for linux. */ +/* Thanks to cypress for providing references for the hid reports. */ +/* Thanks to Jiang Zhang for providing links and for general help. */ +/* Code originates and was built up from ftdi_sio, belkin, pl2303 and others.*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cypress_m8.h" + + +static bool stats; +static int interval; +static bool unstable_bauds; + +#define DRIVER_AUTHOR "Lonnie Mendez , Neil Whelchel " +#define DRIVER_DESC "Cypress USB to Serial Driver" + +/* write buffer size defines */ +#define CYPRESS_BUF_SIZE 1024 + +static const struct usb_device_id id_table_earthmate[] = { + { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB) }, + { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB_LT20) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id id_table_cyphidcomrs232[] = { + { USB_DEVICE(VENDOR_ID_CYPRESS, PRODUCT_ID_CYPHIDCOM) }, + { USB_DEVICE(VENDOR_ID_POWERCOM, PRODUCT_ID_UPS) }, + { USB_DEVICE(VENDOR_ID_FRWD, PRODUCT_ID_CYPHIDCOM_FRWD) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id id_table_nokiaca42v2[] = { + { USB_DEVICE(VENDOR_ID_DAZZLE, PRODUCT_ID_CA42) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id id_table_combined[] = { + { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB) }, + { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB_LT20) }, + { USB_DEVICE(VENDOR_ID_CYPRESS, PRODUCT_ID_CYPHIDCOM) }, + { USB_DEVICE(VENDOR_ID_POWERCOM, PRODUCT_ID_UPS) }, + { USB_DEVICE(VENDOR_ID_FRWD, PRODUCT_ID_CYPHIDCOM_FRWD) }, + { USB_DEVICE(VENDOR_ID_DAZZLE, PRODUCT_ID_CA42) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +enum packet_format { + packet_format_1, /* b0:status, b1:payload count */ + packet_format_2 /* b0[7:3]:status, b0[2:0]:payload count */ +}; + +struct cypress_private { + spinlock_t lock; /* private lock */ + int chiptype; /* identifier of device, for quirks/etc */ + int bytes_in; /* used for statistics */ + int bytes_out; /* used for statistics */ + int cmd_count; /* used for statistics */ + int cmd_ctrl; /* always set this to 1 before issuing a command */ + struct kfifo write_fifo; /* write fifo */ + int write_urb_in_use; /* write urb in use indicator */ + int write_urb_interval; /* interval to use for write urb */ + int read_urb_interval; /* interval to use for read urb */ + int comm_is_ok; /* true if communication is (still) ok */ + int termios_initialized; + __u8 line_control; /* holds dtr / rts value */ + __u8 current_status; /* received from last read - info on dsr,cts,cd,ri,etc */ + __u8 current_config; /* stores the current configuration byte */ + __u8 rx_flags; /* throttling - used from whiteheat/ftdi_sio */ + enum packet_format pkt_fmt; /* format to use for packet send / receive */ + int get_cfg_unsafe; /* If true, the CYPRESS_GET_CONFIG is unsafe */ + int baud_rate; /* stores current baud rate in + integer form */ + int isthrottled; /* if throttled, discard reads */ + char prev_status, diff_status; /* used for TIOCMIWAIT */ + /* we pass a pointer to this as the argument sent to + cypress_set_termios old_termios */ + struct ktermios tmp_termios; /* stores the old termios settings */ +}; + +/* function prototypes for the Cypress USB to serial device */ +static int cypress_earthmate_port_probe(struct usb_serial_port *port); +static int cypress_hidcom_port_probe(struct usb_serial_port *port); +static int cypress_ca42v2_port_probe(struct usb_serial_port *port); +static int cypress_port_remove(struct usb_serial_port *port); +static int cypress_open(struct tty_struct *tty, struct usb_serial_port *port); +static void cypress_close(struct usb_serial_port *port); +static void cypress_dtr_rts(struct usb_serial_port *port, int on); +static int cypress_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +static void cypress_send(struct usb_serial_port *port); +static int cypress_write_room(struct tty_struct *tty); +static void cypress_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old); +static int cypress_tiocmget(struct tty_struct *tty); +static int cypress_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static int cypress_tiocmiwait(struct tty_struct *tty, unsigned long arg); +static int cypress_chars_in_buffer(struct tty_struct *tty); +static void cypress_throttle(struct tty_struct *tty); +static void cypress_unthrottle(struct tty_struct *tty); +static void cypress_set_dead(struct usb_serial_port *port); +static void cypress_read_int_callback(struct urb *urb); +static void cypress_write_int_callback(struct urb *urb); + +static struct usb_serial_driver cypress_earthmate_device = { + .driver = { + .owner = THIS_MODULE, + .name = "earthmate", + }, + .description = "DeLorme Earthmate USB", + .id_table = id_table_earthmate, + .num_ports = 1, + .port_probe = cypress_earthmate_port_probe, + .port_remove = cypress_port_remove, + .open = cypress_open, + .close = cypress_close, + .dtr_rts = cypress_dtr_rts, + .write = cypress_write, + .write_room = cypress_write_room, + .set_termios = cypress_set_termios, + .tiocmget = cypress_tiocmget, + .tiocmset = cypress_tiocmset, + .tiocmiwait = cypress_tiocmiwait, + .chars_in_buffer = cypress_chars_in_buffer, + .throttle = cypress_throttle, + .unthrottle = cypress_unthrottle, + .read_int_callback = cypress_read_int_callback, + .write_int_callback = cypress_write_int_callback, +}; + +static struct usb_serial_driver cypress_hidcom_device = { + .driver = { + .owner = THIS_MODULE, + .name = "cyphidcom", + }, + .description = "HID->COM RS232 Adapter", + .id_table = id_table_cyphidcomrs232, + .num_ports = 1, + .port_probe = cypress_hidcom_port_probe, + .port_remove = cypress_port_remove, + .open = cypress_open, + .close = cypress_close, + .dtr_rts = cypress_dtr_rts, + .write = cypress_write, + .write_room = cypress_write_room, + .set_termios = cypress_set_termios, + .tiocmget = cypress_tiocmget, + .tiocmset = cypress_tiocmset, + .tiocmiwait = cypress_tiocmiwait, + .chars_in_buffer = cypress_chars_in_buffer, + .throttle = cypress_throttle, + .unthrottle = cypress_unthrottle, + .read_int_callback = cypress_read_int_callback, + .write_int_callback = cypress_write_int_callback, +}; + +static struct usb_serial_driver cypress_ca42v2_device = { + .driver = { + .owner = THIS_MODULE, + .name = "nokiaca42v2", + }, + .description = "Nokia CA-42 V2 Adapter", + .id_table = id_table_nokiaca42v2, + .num_ports = 1, + .port_probe = cypress_ca42v2_port_probe, + .port_remove = cypress_port_remove, + .open = cypress_open, + .close = cypress_close, + .dtr_rts = cypress_dtr_rts, + .write = cypress_write, + .write_room = cypress_write_room, + .set_termios = cypress_set_termios, + .tiocmget = cypress_tiocmget, + .tiocmset = cypress_tiocmset, + .tiocmiwait = cypress_tiocmiwait, + .chars_in_buffer = cypress_chars_in_buffer, + .throttle = cypress_throttle, + .unthrottle = cypress_unthrottle, + .read_int_callback = cypress_read_int_callback, + .write_int_callback = cypress_write_int_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &cypress_earthmate_device, &cypress_hidcom_device, + &cypress_ca42v2_device, NULL +}; + +/***************************************************************************** + * Cypress serial helper functions + *****************************************************************************/ + +/* FRWD Dongle hidcom needs to skip reset and speed checks */ +static inline bool is_frwd(struct usb_device *dev) +{ + return ((le16_to_cpu(dev->descriptor.idVendor) == VENDOR_ID_FRWD) && + (le16_to_cpu(dev->descriptor.idProduct) == PRODUCT_ID_CYPHIDCOM_FRWD)); +} + +static int analyze_baud_rate(struct usb_serial_port *port, speed_t new_rate) +{ + struct cypress_private *priv; + priv = usb_get_serial_port_data(port); + + if (unstable_bauds) + return new_rate; + + /* FRWD Dongle uses 115200 bps */ + if (is_frwd(port->serial->dev)) + return new_rate; + + /* + * The general purpose firmware for the Cypress M8 allows for + * a maximum speed of 57600bps (I have no idea whether DeLorme + * chose to use the general purpose firmware or not), if you + * need to modify this speed setting for your own project + * please add your own chiptype and modify the code likewise. + * The Cypress HID->COM device will work successfully up to + * 115200bps (but the actual throughput is around 3kBps). + */ + if (port->serial->dev->speed == USB_SPEED_LOW) { + /* + * Mike Isely 2-Feb-2008: The + * Cypress app note that describes this mechanism + * states the the low-speed part can't handle more + * than 800 bytes/sec, in which case 4800 baud is the + * safest speed for a part like that. + */ + if (new_rate > 4800) { + dev_dbg(&port->dev, + "%s - failed setting baud rate, device incapable speed %d\n", + __func__, new_rate); + return -1; + } + } + switch (priv->chiptype) { + case CT_EARTHMATE: + if (new_rate <= 600) { + /* 300 and 600 baud rates are supported under + * the generic firmware, but are not used with + * NMEA and SiRF protocols */ + dev_dbg(&port->dev, + "%s - failed setting baud rate, unsupported speed of %d on Earthmate GPS", + __func__, new_rate); + return -1; + } + break; + default: + break; + } + return new_rate; +} + + +/* This function can either set or retrieve the current serial line settings */ +static int cypress_serial_control(struct tty_struct *tty, + struct usb_serial_port *port, speed_t baud_rate, int data_bits, + int stop_bits, int parity_enable, int parity_type, int reset, + int cypress_request_type) +{ + int new_baudrate = 0, retval = 0, tries = 0; + struct cypress_private *priv; + struct device *dev = &port->dev; + u8 *feature_buffer; + const unsigned int feature_len = 5; + unsigned long flags; + + priv = usb_get_serial_port_data(port); + + if (!priv->comm_is_ok) + return -ENODEV; + + feature_buffer = kcalloc(feature_len, sizeof(u8), GFP_KERNEL); + if (!feature_buffer) + return -ENOMEM; + + switch (cypress_request_type) { + case CYPRESS_SET_CONFIG: + /* 0 means 'Hang up' so doesn't change the true bit rate */ + new_baudrate = priv->baud_rate; + if (baud_rate && baud_rate != priv->baud_rate) { + dev_dbg(dev, "%s - baud rate is changing\n", __func__); + retval = analyze_baud_rate(port, baud_rate); + if (retval >= 0) { + new_baudrate = retval; + dev_dbg(dev, "%s - New baud rate set to %d\n", + __func__, new_baudrate); + } + } + dev_dbg(dev, "%s - baud rate is being sent as %d\n", __func__, + new_baudrate); + + /* fill the feature_buffer with new configuration */ + put_unaligned_le32(new_baudrate, feature_buffer); + feature_buffer[4] |= data_bits; /* assign data bits in 2 bit space ( max 3 ) */ + /* 1 bit gap */ + feature_buffer[4] |= (stop_bits << 3); /* assign stop bits in 1 bit space */ + feature_buffer[4] |= (parity_enable << 4); /* assign parity flag in 1 bit space */ + feature_buffer[4] |= (parity_type << 5); /* assign parity type in 1 bit space */ + /* 1 bit gap */ + feature_buffer[4] |= (reset << 7); /* assign reset at end of byte, 1 bit space */ + + dev_dbg(dev, "%s - device is being sent this feature report:\n", __func__); + dev_dbg(dev, "%s - %02X - %02X - %02X - %02X - %02X\n", __func__, + feature_buffer[0], feature_buffer[1], + feature_buffer[2], feature_buffer[3], + feature_buffer[4]); + + do { + retval = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS, + 0x0300, 0, feature_buffer, + feature_len, 500); + + if (tries++ >= 3) + break; + + } while (retval != feature_len && + retval != -ENODEV); + + if (retval != feature_len) { + dev_err(dev, "%s - failed sending serial line settings - %d\n", + __func__, retval); + cypress_set_dead(port); + } else { + spin_lock_irqsave(&priv->lock, flags); + priv->baud_rate = new_baudrate; + priv->current_config = feature_buffer[4]; + spin_unlock_irqrestore(&priv->lock, flags); + /* If we asked for a speed change encode it */ + if (baud_rate) + tty_encode_baud_rate(tty, + new_baudrate, new_baudrate); + } + break; + case CYPRESS_GET_CONFIG: + if (priv->get_cfg_unsafe) { + /* Not implemented for this device, + and if we try to do it we're likely + to crash the hardware. */ + retval = -ENOTTY; + goto out; + } + dev_dbg(dev, "%s - retreiving serial line settings\n", __func__); + do { + retval = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + HID_REQ_GET_REPORT, + USB_DIR_IN | USB_RECIP_INTERFACE | USB_TYPE_CLASS, + 0x0300, 0, feature_buffer, + feature_len, 500); + + if (tries++ >= 3) + break; + } while (retval != feature_len + && retval != -ENODEV); + + if (retval != feature_len) { + dev_err(dev, "%s - failed to retrieve serial line settings - %d\n", + __func__, retval); + cypress_set_dead(port); + goto out; + } else { + spin_lock_irqsave(&priv->lock, flags); + /* store the config in one byte, and later + use bit masks to check values */ + priv->current_config = feature_buffer[4]; + priv->baud_rate = get_unaligned_le32(feature_buffer); + spin_unlock_irqrestore(&priv->lock, flags); + } + } + spin_lock_irqsave(&priv->lock, flags); + ++priv->cmd_count; + spin_unlock_irqrestore(&priv->lock, flags); +out: + kfree(feature_buffer); + return retval; +} /* cypress_serial_control */ + + +static void cypress_set_dead(struct usb_serial_port *port) +{ + struct cypress_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + if (!priv->comm_is_ok) { + spin_unlock_irqrestore(&priv->lock, flags); + return; + } + priv->comm_is_ok = 0; + spin_unlock_irqrestore(&priv->lock, flags); + + dev_err(&port->dev, "cypress_m8 suspending failing port %d - " + "interval might be too short\n", port->number); +} + + +/***************************************************************************** + * Cypress serial driver functions + *****************************************************************************/ + + +static int cypress_generic_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct cypress_private *priv; + + priv = kzalloc(sizeof(struct cypress_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->comm_is_ok = !0; + spin_lock_init(&priv->lock); + if (kfifo_alloc(&priv->write_fifo, CYPRESS_BUF_SIZE, GFP_KERNEL)) { + kfree(priv); + return -ENOMEM; + } + + /* Skip reset for FRWD device. It is a workaound: + device hangs if it receives SET_CONFIGURE in Configured + state. */ + if (!is_frwd(serial->dev)) + usb_reset_configuration(serial->dev); + + priv->cmd_ctrl = 0; + priv->line_control = 0; + priv->termios_initialized = 0; + priv->rx_flags = 0; + /* Default packet format setting is determined by packet size. + Anything with a size larger then 9 must have a separate + count field since the 3 bit count field is otherwise too + small. Otherwise we can use the slightly more compact + format. This is in accordance with the cypress_m8 serial + converter app note. */ + if (port->interrupt_out_size > 9) + priv->pkt_fmt = packet_format_1; + else + priv->pkt_fmt = packet_format_2; + + if (interval > 0) { + priv->write_urb_interval = interval; + priv->read_urb_interval = interval; + dev_dbg(&port->dev, "%s - read & write intervals forced to %d\n", + __func__, interval); + } else { + priv->write_urb_interval = port->interrupt_out_urb->interval; + priv->read_urb_interval = port->interrupt_in_urb->interval; + dev_dbg(&port->dev, "%s - intervals: read=%d write=%d\n", + __func__, priv->read_urb_interval, + priv->write_urb_interval); + } + usb_set_serial_port_data(port, priv); + + return 0; +} + + +static int cypress_earthmate_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct cypress_private *priv; + int ret; + + ret = cypress_generic_port_probe(port); + if (ret) { + dev_dbg(&port->dev, "%s - Failed setting up port\n", __func__); + return ret; + } + + priv = usb_get_serial_port_data(port); + priv->chiptype = CT_EARTHMATE; + /* All Earthmate devices use the separated-count packet + format! Idiotic. */ + priv->pkt_fmt = packet_format_1; + if (serial->dev->descriptor.idProduct != + cpu_to_le16(PRODUCT_ID_EARTHMATEUSB)) { + /* The old original USB Earthmate seemed able to + handle GET_CONFIG requests; everything they've + produced since that time crashes if this command is + attempted :-( */ + dev_dbg(&port->dev, + "%s - Marking this device as unsafe for GET_CONFIG commands\n", + __func__); + priv->get_cfg_unsafe = !0; + } + + return 0; +} + +static int cypress_hidcom_port_probe(struct usb_serial_port *port) +{ + struct cypress_private *priv; + int ret; + + ret = cypress_generic_port_probe(port); + if (ret) { + dev_dbg(&port->dev, "%s - Failed setting up port\n", __func__); + return ret; + } + + priv = usb_get_serial_port_data(port); + priv->chiptype = CT_CYPHIDCOM; + + return 0; +} + +static int cypress_ca42v2_port_probe(struct usb_serial_port *port) +{ + struct cypress_private *priv; + int ret; + + ret = cypress_generic_port_probe(port); + if (ret) { + dev_dbg(&port->dev, "%s - Failed setting up port\n", __func__); + return ret; + } + + priv = usb_get_serial_port_data(port); + priv->chiptype = CT_CA42V2; + + return 0; +} + +static int cypress_port_remove(struct usb_serial_port *port) +{ + struct cypress_private *priv; + + priv = usb_get_serial_port_data(port); + + kfifo_free(&priv->write_fifo); + kfree(priv); + + return 0; +} + +static int cypress_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct cypress_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + unsigned long flags; + int result = 0; + + if (!priv->comm_is_ok) + return -EIO; + + /* clear halts before open */ + usb_clear_halt(serial->dev, 0x81); + usb_clear_halt(serial->dev, 0x02); + + spin_lock_irqsave(&priv->lock, flags); + /* reset read/write statistics */ + priv->bytes_in = 0; + priv->bytes_out = 0; + priv->cmd_count = 0; + priv->rx_flags = 0; + spin_unlock_irqrestore(&priv->lock, flags); + + /* Set termios */ + cypress_send(port); + + if (tty) + cypress_set_termios(tty, port, &priv->tmp_termios); + + /* setup the port and start reading from the device */ + if (!port->interrupt_in_urb) { + dev_err(&port->dev, "%s - interrupt_in_urb is empty!\n", + __func__); + return -1; + } + + usb_fill_int_urb(port->interrupt_in_urb, serial->dev, + usb_rcvintpipe(serial->dev, port->interrupt_in_endpointAddress), + port->interrupt_in_urb->transfer_buffer, + port->interrupt_in_urb->transfer_buffer_length, + cypress_read_int_callback, port, priv->read_urb_interval); + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + + if (result) { + dev_err(&port->dev, + "%s - failed submitting read urb, error %d\n", + __func__, result); + cypress_set_dead(port); + } + port->port.drain_delay = 256; + return result; +} /* cypress_open */ + +static void cypress_dtr_rts(struct usb_serial_port *port, int on) +{ + struct cypress_private *priv = usb_get_serial_port_data(port); + /* drop dtr and rts */ + spin_lock_irq(&priv->lock); + if (on == 0) + priv->line_control = 0; + else + priv->line_control = CONTROL_DTR | CONTROL_RTS; + priv->cmd_ctrl = 1; + spin_unlock_irq(&priv->lock); + cypress_write(NULL, port, NULL, 0); +} + +static void cypress_close(struct usb_serial_port *port) +{ + struct cypress_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + kfifo_reset_out(&priv->write_fifo); + spin_unlock_irqrestore(&priv->lock, flags); + + dev_dbg(&port->dev, "%s - stopping urbs\n", __func__); + usb_kill_urb(port->interrupt_in_urb); + usb_kill_urb(port->interrupt_out_urb); + + if (stats) + dev_info(&port->dev, "Statistics: %d Bytes In | %d Bytes Out | %d Commands Issued\n", + priv->bytes_in, priv->bytes_out, priv->cmd_count); +} /* cypress_close */ + + +static int cypress_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct cypress_private *priv = usb_get_serial_port_data(port); + + dev_dbg(&port->dev, "%s - port %d, %d bytes\n", __func__, port->number, count); + + /* line control commands, which need to be executed immediately, + are not put into the buffer for obvious reasons. + */ + if (priv->cmd_ctrl) { + count = 0; + goto finish; + } + + if (!count) + return count; + + count = kfifo_in_locked(&priv->write_fifo, buf, count, &priv->lock); + +finish: + cypress_send(port); + + return count; +} /* cypress_write */ + + +static void cypress_send(struct usb_serial_port *port) +{ + int count = 0, result, offset, actual_size; + struct cypress_private *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + unsigned long flags; + + if (!priv->comm_is_ok) + return; + + dev_dbg(dev, "%s - interrupt out size is %d\n", __func__, + port->interrupt_out_size); + + spin_lock_irqsave(&priv->lock, flags); + if (priv->write_urb_in_use) { + dev_dbg(dev, "%s - can't write, urb in use\n", __func__); + spin_unlock_irqrestore(&priv->lock, flags); + return; + } + spin_unlock_irqrestore(&priv->lock, flags); + + /* clear buffer */ + memset(port->interrupt_out_urb->transfer_buffer, 0, + port->interrupt_out_size); + + spin_lock_irqsave(&priv->lock, flags); + switch (priv->pkt_fmt) { + default: + case packet_format_1: + /* this is for the CY7C64013... */ + offset = 2; + port->interrupt_out_buffer[0] = priv->line_control; + break; + case packet_format_2: + /* this is for the CY7C63743... */ + offset = 1; + port->interrupt_out_buffer[0] = priv->line_control; + break; + } + + if (priv->line_control & CONTROL_RESET) + priv->line_control &= ~CONTROL_RESET; + + if (priv->cmd_ctrl) { + priv->cmd_count++; + dev_dbg(dev, "%s - line control command being issued\n", __func__); + spin_unlock_irqrestore(&priv->lock, flags); + goto send; + } else + spin_unlock_irqrestore(&priv->lock, flags); + + count = kfifo_out_locked(&priv->write_fifo, + &port->interrupt_out_buffer[offset], + port->interrupt_out_size - offset, + &priv->lock); + if (count == 0) + return; + + switch (priv->pkt_fmt) { + default: + case packet_format_1: + port->interrupt_out_buffer[1] = count; + break; + case packet_format_2: + port->interrupt_out_buffer[0] |= count; + } + + dev_dbg(dev, "%s - count is %d\n", __func__, count); + +send: + spin_lock_irqsave(&priv->lock, flags); + priv->write_urb_in_use = 1; + spin_unlock_irqrestore(&priv->lock, flags); + + if (priv->cmd_ctrl) + actual_size = 1; + else + actual_size = count + + (priv->pkt_fmt == packet_format_1 ? 2 : 1); + + usb_serial_debug_data(dev, __func__, port->interrupt_out_size, + port->interrupt_out_urb->transfer_buffer); + + usb_fill_int_urb(port->interrupt_out_urb, port->serial->dev, + usb_sndintpipe(port->serial->dev, port->interrupt_out_endpointAddress), + port->interrupt_out_buffer, port->interrupt_out_size, + cypress_write_int_callback, port, priv->write_urb_interval); + result = usb_submit_urb(port->interrupt_out_urb, GFP_ATOMIC); + if (result) { + dev_err_console(port, + "%s - failed submitting write urb, error %d\n", + __func__, result); + priv->write_urb_in_use = 0; + cypress_set_dead(port); + } + + spin_lock_irqsave(&priv->lock, flags); + if (priv->cmd_ctrl) + priv->cmd_ctrl = 0; + + /* do not count the line control and size bytes */ + priv->bytes_out += count; + spin_unlock_irqrestore(&priv->lock, flags); + + usb_serial_port_softint(port); +} /* cypress_send */ + + +/* returns how much space is available in the soft buffer */ +static int cypress_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct cypress_private *priv = usb_get_serial_port_data(port); + int room = 0; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + room = kfifo_avail(&priv->write_fifo); + spin_unlock_irqrestore(&priv->lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, room); + return room; +} + + +static int cypress_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct cypress_private *priv = usb_get_serial_port_data(port); + __u8 status, control; + unsigned int result = 0; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + control = priv->line_control; + status = priv->current_status; + spin_unlock_irqrestore(&priv->lock, flags); + + result = ((control & CONTROL_DTR) ? TIOCM_DTR : 0) + | ((control & CONTROL_RTS) ? TIOCM_RTS : 0) + | ((status & UART_CTS) ? TIOCM_CTS : 0) + | ((status & UART_DSR) ? TIOCM_DSR : 0) + | ((status & UART_RI) ? TIOCM_RI : 0) + | ((status & UART_CD) ? TIOCM_CD : 0); + + dev_dbg(&port->dev, "%s - result = %x\n", __func__, result); + + return result; +} + + +static int cypress_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct cypress_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + if (set & TIOCM_RTS) + priv->line_control |= CONTROL_RTS; + if (set & TIOCM_DTR) + priv->line_control |= CONTROL_DTR; + if (clear & TIOCM_RTS) + priv->line_control &= ~CONTROL_RTS; + if (clear & TIOCM_DTR) + priv->line_control &= ~CONTROL_DTR; + priv->cmd_ctrl = 1; + spin_unlock_irqrestore(&priv->lock, flags); + + return cypress_write(tty, port, NULL, 0); +} + + +static int cypress_tiocmiwait(struct tty_struct *tty, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct cypress_private *priv = usb_get_serial_port_data(port); + char diff; + + for (;;) { + interruptible_sleep_on(&port->port.delta_msr_wait); + /* see if a signal did it */ + if (signal_pending(current)) + return -ERESTARTSYS; + + if (port->serial->disconnected) + return -EIO; + + diff = priv->diff_status; + if (diff == 0) + return -EIO; /* no change => error */ + + /* consume all events */ + priv->diff_status = 0; + + /* return 0 if caller wanted to know about + these bits */ + if (((arg & TIOCM_RNG) && (diff & UART_RI)) || + ((arg & TIOCM_DSR) && (diff & UART_DSR)) || + ((arg & TIOCM_CD) && (diff & UART_CD)) || + ((arg & TIOCM_CTS) && (diff & UART_CTS))) + return 0; + /* otherwise caller can't care less about what + * happened, and so we continue to wait for + * more events. + */ + } + + return 0; +} + +static void cypress_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct cypress_private *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + int data_bits, stop_bits, parity_type, parity_enable; + unsigned cflag, iflag; + unsigned long flags; + __u8 oldlines; + int linechange = 0; + + spin_lock_irqsave(&priv->lock, flags); + /* We can't clean this one up as we don't know the device type + early enough */ + if (!priv->termios_initialized) { + if (priv->chiptype == CT_EARTHMATE) { + tty->termios = tty_std_termios; + tty->termios.c_cflag = B4800 | CS8 | CREAD | HUPCL | + CLOCAL; + tty->termios.c_ispeed = 4800; + tty->termios.c_ospeed = 4800; + } else if (priv->chiptype == CT_CYPHIDCOM) { + tty->termios = tty_std_termios; + tty->termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | + CLOCAL; + tty->termios.c_ispeed = 9600; + tty->termios.c_ospeed = 9600; + } else if (priv->chiptype == CT_CA42V2) { + tty->termios = tty_std_termios; + tty->termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | + CLOCAL; + tty->termios.c_ispeed = 9600; + tty->termios.c_ospeed = 9600; + } + priv->termios_initialized = 1; + } + spin_unlock_irqrestore(&priv->lock, flags); + + /* Unsupported features need clearing */ + tty->termios.c_cflag &= ~(CMSPAR|CRTSCTS); + + cflag = tty->termios.c_cflag; + iflag = tty->termios.c_iflag; + + /* check if there are new settings */ + if (old_termios) { + spin_lock_irqsave(&priv->lock, flags); + priv->tmp_termios = tty->termios; + spin_unlock_irqrestore(&priv->lock, flags); + } + + /* set number of data bits, parity, stop bits */ + /* when parity is disabled the parity type bit is ignored */ + + /* 1 means 2 stop bits, 0 means 1 stop bit */ + stop_bits = cflag & CSTOPB ? 1 : 0; + + if (cflag & PARENB) { + parity_enable = 1; + /* 1 means odd parity, 0 means even parity */ + parity_type = cflag & PARODD ? 1 : 0; + } else + parity_enable = parity_type = 0; + + switch (cflag & CSIZE) { + case CS5: + data_bits = 0; + break; + case CS6: + data_bits = 1; + break; + case CS7: + data_bits = 2; + break; + case CS8: + data_bits = 3; + break; + default: + dev_err(dev, "%s - CSIZE was set, but not CS5-CS8\n", __func__); + data_bits = 3; + } + spin_lock_irqsave(&priv->lock, flags); + oldlines = priv->line_control; + if ((cflag & CBAUD) == B0) { + /* drop dtr and rts */ + dev_dbg(dev, "%s - dropping the lines, baud rate 0bps\n", __func__); + priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); + } else + priv->line_control = (CONTROL_DTR | CONTROL_RTS); + spin_unlock_irqrestore(&priv->lock, flags); + + dev_dbg(dev, "%s - sending %d stop_bits, %d parity_enable, %d parity_type, %d data_bits (+5)\n", + __func__, stop_bits, parity_enable, parity_type, data_bits); + + cypress_serial_control(tty, port, tty_get_baud_rate(tty), + data_bits, stop_bits, + parity_enable, parity_type, + 0, CYPRESS_SET_CONFIG); + + /* we perform a CYPRESS_GET_CONFIG so that the current settings are + * filled into the private structure this should confirm that all is + * working if it returns what we just set */ + cypress_serial_control(tty, port, 0, 0, 0, 0, 0, 0, CYPRESS_GET_CONFIG); + + /* Here we can define custom tty settings for devices; the main tty + * termios flag base comes from empeg.c */ + + spin_lock_irqsave(&priv->lock, flags); + if (priv->chiptype == CT_EARTHMATE && priv->baud_rate == 4800) { + dev_dbg(dev, "Using custom termios settings for a baud rate of 4800bps.\n"); + /* define custom termios settings for NMEA protocol */ + + tty->termios.c_iflag /* input modes - */ + &= ~(IGNBRK /* disable ignore break */ + | BRKINT /* disable break causes interrupt */ + | PARMRK /* disable mark parity errors */ + | ISTRIP /* disable clear high bit of input char */ + | INLCR /* disable translate NL to CR */ + | IGNCR /* disable ignore CR */ + | ICRNL /* disable translate CR to NL */ + | IXON); /* disable enable XON/XOFF flow control */ + + tty->termios.c_oflag /* output modes */ + &= ~OPOST; /* disable postprocess output char */ + + tty->termios.c_lflag /* line discipline modes */ + &= ~(ECHO /* disable echo input characters */ + | ECHONL /* disable echo new line */ + | ICANON /* disable erase, kill, werase, and rprnt + special characters */ + | ISIG /* disable interrupt, quit, and suspend + special characters */ + | IEXTEN); /* disable non-POSIX special characters */ + } /* CT_CYPHIDCOM: Application should handle this for device */ + + linechange = (priv->line_control != oldlines); + spin_unlock_irqrestore(&priv->lock, flags); + + /* if necessary, set lines */ + if (linechange) { + priv->cmd_ctrl = 1; + cypress_write(tty, port, NULL, 0); + } +} /* cypress_set_termios */ + + +/* returns amount of data still left in soft buffer */ +static int cypress_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct cypress_private *priv = usb_get_serial_port_data(port); + int chars = 0; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + chars = kfifo_len(&priv->write_fifo); + spin_unlock_irqrestore(&priv->lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, chars); + return chars; +} + + +static void cypress_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct cypress_private *priv = usb_get_serial_port_data(port); + + spin_lock_irq(&priv->lock); + priv->rx_flags = THROTTLED; + spin_unlock_irq(&priv->lock); +} + + +static void cypress_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct cypress_private *priv = usb_get_serial_port_data(port); + int actually_throttled, result; + + spin_lock_irq(&priv->lock); + actually_throttled = priv->rx_flags & ACTUALLY_THROTTLED; + priv->rx_flags = 0; + spin_unlock_irq(&priv->lock); + + if (!priv->comm_is_ok) + return; + + if (actually_throttled) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "%s - failed submitting read urb, " + "error %d\n", __func__, result); + cypress_set_dead(port); + } + } +} + + +static void cypress_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct cypress_private *priv = usb_get_serial_port_data(port); + struct device *dev = &urb->dev->dev; + struct tty_struct *tty; + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + char tty_flag = TTY_NORMAL; + int havedata = 0; + int bytes = 0; + int result; + int i = 0; + int status = urb->status; + + switch (status) { + case 0: /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* precursor to disconnect so just go away */ + return; + case -EPIPE: + /* Can't call usb_clear_halt while in_interrupt */ + /* FALLS THROUGH */ + default: + /* something ugly is going on... */ + dev_err(dev, "%s - unexpected nonzero read status received: %d\n", + __func__, status); + cypress_set_dead(port); + return; + } + + spin_lock_irqsave(&priv->lock, flags); + if (priv->rx_flags & THROTTLED) { + dev_dbg(dev, "%s - now throttling\n", __func__); + priv->rx_flags |= ACTUALLY_THROTTLED; + spin_unlock_irqrestore(&priv->lock, flags); + return; + } + spin_unlock_irqrestore(&priv->lock, flags); + + tty = tty_port_tty_get(&port->port); + if (!tty) { + dev_dbg(dev, "%s - bad tty pointer - exiting\n", __func__); + return; + } + + spin_lock_irqsave(&priv->lock, flags); + result = urb->actual_length; + switch (priv->pkt_fmt) { + default: + case packet_format_1: + /* This is for the CY7C64013... */ + priv->current_status = data[0] & 0xF8; + bytes = data[1] + 2; + i = 2; + if (bytes > 2) + havedata = 1; + break; + case packet_format_2: + /* This is for the CY7C63743... */ + priv->current_status = data[0] & 0xF8; + bytes = (data[0] & 0x07) + 1; + i = 1; + if (bytes > 1) + havedata = 1; + break; + } + spin_unlock_irqrestore(&priv->lock, flags); + if (result < bytes) { + dev_dbg(dev, + "%s - wrong packet size - received %d bytes but packet said %d bytes\n", + __func__, result, bytes); + goto continue_read; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + + spin_lock_irqsave(&priv->lock, flags); + /* check to see if status has changed */ + if (priv->current_status != priv->prev_status) { + priv->diff_status |= priv->current_status ^ + priv->prev_status; + wake_up_interruptible(&port->port.delta_msr_wait); + priv->prev_status = priv->current_status; + } + spin_unlock_irqrestore(&priv->lock, flags); + + /* hangup, as defined in acm.c... this might be a bad place for it + * though */ + if (tty && !(tty->termios.c_cflag & CLOCAL) && + !(priv->current_status & UART_CD)) { + dev_dbg(dev, "%s - calling hangup\n", __func__); + tty_hangup(tty); + goto continue_read; + } + + /* There is one error bit... I'm assuming it is a parity error + * indicator as the generic firmware will set this bit to 1 if a + * parity error occurs. + * I can not find reference to any other error events. */ + spin_lock_irqsave(&priv->lock, flags); + if (priv->current_status & CYP_ERROR) { + spin_unlock_irqrestore(&priv->lock, flags); + tty_flag = TTY_PARITY; + dev_dbg(dev, "%s - Parity Error detected\n", __func__); + } else + spin_unlock_irqrestore(&priv->lock, flags); + + /* process read if there is data other than line status */ + if (bytes > i) { + tty_insert_flip_string_fixed_flag(&port->port, data + i, + tty_flag, bytes - i); + tty_flip_buffer_push(&port->port); + } + + spin_lock_irqsave(&priv->lock, flags); + /* control and status byte(s) are also counted */ + priv->bytes_in += bytes; + spin_unlock_irqrestore(&priv->lock, flags); + +continue_read: + tty_kref_put(tty); + + /* Continue trying to always read */ + + if (priv->comm_is_ok) { + usb_fill_int_urb(port->interrupt_in_urb, port->serial->dev, + usb_rcvintpipe(port->serial->dev, + port->interrupt_in_endpointAddress), + port->interrupt_in_urb->transfer_buffer, + port->interrupt_in_urb->transfer_buffer_length, + cypress_read_int_callback, port, + priv->read_urb_interval); + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + if (result && result != -EPERM) { + dev_err(dev, "%s - failed resubmitting read urb, error %d\n", + __func__, result); + cypress_set_dead(port); + } + } +} /* cypress_read_int_callback */ + + +static void cypress_write_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct cypress_private *priv = usb_get_serial_port_data(port); + struct device *dev = &urb->dev->dev; + int result; + int status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shutting down with status: %d\n", + __func__, status); + priv->write_urb_in_use = 0; + return; + case -EPIPE: /* no break needed; clear halt and resubmit */ + if (!priv->comm_is_ok) + break; + usb_clear_halt(port->serial->dev, 0x02); + /* error in the urb, so we have to resubmit it */ + dev_dbg(dev, "%s - nonzero write bulk status received: %d\n", + __func__, status); + port->interrupt_out_urb->transfer_buffer_length = 1; + result = usb_submit_urb(port->interrupt_out_urb, GFP_ATOMIC); + if (!result) + return; + dev_err(dev, "%s - failed resubmitting write urb, error %d\n", + __func__, result); + cypress_set_dead(port); + break; + default: + dev_err(dev, "%s - unexpected nonzero write status received: %d\n", + __func__, status); + cypress_set_dead(port); + break; + } + priv->write_urb_in_use = 0; + + /* send any buffered data */ + cypress_send(port); +} + +module_usb_serial_driver(serial_drivers, id_table_combined); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(stats, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(stats, "Enable statistics or not"); +module_param(interval, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(interval, "Overrides interrupt interval"); +module_param(unstable_bauds, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(unstable_bauds, "Allow unstable baud rates"); diff --git a/drivers/usb/serial/cypress_m8.h b/drivers/usb/serial/cypress_m8.h new file mode 100644 index 00000000..ce13e61b --- /dev/null +++ b/drivers/usb/serial/cypress_m8.h @@ -0,0 +1,74 @@ +#ifndef CYPRESS_M8_H +#define CYPRESS_M8_H + +/* + * definitions and function prototypes used for the cypress USB to Serial + * controller + */ + +/* + * For sending our feature buffer - controlling serial communication states. + * Linux HID has no support for serial devices so we do this through the driver + */ +#define HID_REQ_GET_REPORT 0x01 +#define HID_REQ_SET_REPORT 0x09 + +/* List other cypress USB to Serial devices here, and add them to the id_table */ + +/* DeLorme Earthmate USB - a GPS device */ +#define VENDOR_ID_DELORME 0x1163 +#define PRODUCT_ID_EARTHMATEUSB 0x0100 +#define PRODUCT_ID_EARTHMATEUSB_LT20 0x0200 + +/* Cypress HID->COM RS232 Adapter */ +#define VENDOR_ID_CYPRESS 0x04b4 +#define PRODUCT_ID_CYPHIDCOM 0x5500 + +/* FRWD Dongle - a GPS sports watch */ +#define VENDOR_ID_FRWD 0x6737 +#define PRODUCT_ID_CYPHIDCOM_FRWD 0x0001 + +/* Powercom UPS, chip CY7C63723 */ +#define VENDOR_ID_POWERCOM 0x0d9f +#define PRODUCT_ID_UPS 0x0002 + +/* Nokia CA-42 USB to serial cable */ +#define VENDOR_ID_DAZZLE 0x07d0 +#define PRODUCT_ID_CA42 0x4101 +/* End of device listing */ + +/* Used for setting / requesting serial line settings */ +#define CYPRESS_SET_CONFIG 0x01 +#define CYPRESS_GET_CONFIG 0x02 + +/* Used for throttle control */ +#define THROTTLED 0x1 +#define ACTUALLY_THROTTLED 0x2 + +/* + * chiptypes - used in case firmware differs from the generic form ... offering + * different baud speeds/etc. + */ +#define CT_EARTHMATE 0x01 +#define CT_CYPHIDCOM 0x02 +#define CT_CA42V2 0x03 +#define CT_GENERIC 0x0F +/* End of chiptype definitions */ + +/* RS-232 serial data communication protocol definitions */ +/* these are sent / read at byte 0 of the input/output hid reports */ +/* You can find these values defined in the CY4601 USB to Serial design notes */ + +#define CONTROL_DTR 0x20 /* data terminal ready - flow control - host to device */ +#define UART_DSR 0x20 /* data set ready - flow control - device to host */ +#define CONTROL_RTS 0x10 /* request to send - flow control - host to device */ +#define UART_CTS 0x10 /* clear to send - flow control - device to host */ +#define UART_RI 0x80 /* ring indicator - modem - device to host */ +#define UART_CD 0x40 /* carrier detect - modem - device to host */ +#define CYP_ERROR 0x08 /* received from input report - device to host */ +/* Note - the below has nothing to do with the "feature report" reset */ +#define CONTROL_RESET 0x08 /* sent with output report - host to device */ + +/* End of RS-232 protocol definitions */ + +#endif /* CYPRESS_M8_H */ diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c new file mode 100644 index 00000000..7b807d38 --- /dev/null +++ b/drivers/usb/serial/digi_acceleport.c @@ -0,0 +1,1550 @@ +/* +* Digi AccelePort USB-4 and USB-2 Serial Converters +* +* Copyright 2000 by Digi International +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* Shamelessly based on Brian Warner's keyspan_pda.c and Greg Kroah-Hartman's +* usb-serial driver. +* +* Peter Berger (pberger@brimson.com) +* Al Borchers (borchers@steinerpoint.com) +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Defines */ + +#define DRIVER_AUTHOR "Peter Berger , Al Borchers " +#define DRIVER_DESC "Digi AccelePort USB-2/USB-4 Serial Converter driver" + +/* port output buffer length -- must be <= transfer buffer length - 2 */ +/* so we can be sure to send the full buffer in one urb */ +#define DIGI_OUT_BUF_SIZE 8 + +/* port input buffer length -- must be >= transfer buffer length - 3 */ +/* so we can be sure to hold at least one full buffer from one urb */ +#define DIGI_IN_BUF_SIZE 64 + +/* retry timeout while sleeping */ +#define DIGI_RETRY_TIMEOUT (HZ/10) + +/* timeout while waiting for tty output to drain in close */ +/* this delay is used twice in close, so the total delay could */ +/* be twice this value */ +#define DIGI_CLOSE_TIMEOUT (5*HZ) + + +/* AccelePort USB Defines */ + +/* ids */ +#define DIGI_VENDOR_ID 0x05c5 +#define DIGI_2_ID 0x0002 /* USB-2 */ +#define DIGI_4_ID 0x0004 /* USB-4 */ + +/* commands + * "INB": can be used on the in-band endpoint + * "OOB": can be used on the out-of-band endpoint + */ +#define DIGI_CMD_SET_BAUD_RATE 0 /* INB, OOB */ +#define DIGI_CMD_SET_WORD_SIZE 1 /* INB, OOB */ +#define DIGI_CMD_SET_PARITY 2 /* INB, OOB */ +#define DIGI_CMD_SET_STOP_BITS 3 /* INB, OOB */ +#define DIGI_CMD_SET_INPUT_FLOW_CONTROL 4 /* INB, OOB */ +#define DIGI_CMD_SET_OUTPUT_FLOW_CONTROL 5 /* INB, OOB */ +#define DIGI_CMD_SET_DTR_SIGNAL 6 /* INB, OOB */ +#define DIGI_CMD_SET_RTS_SIGNAL 7 /* INB, OOB */ +#define DIGI_CMD_READ_INPUT_SIGNALS 8 /* OOB */ +#define DIGI_CMD_IFLUSH_FIFO 9 /* OOB */ +#define DIGI_CMD_RECEIVE_ENABLE 10 /* INB, OOB */ +#define DIGI_CMD_BREAK_CONTROL 11 /* INB, OOB */ +#define DIGI_CMD_LOCAL_LOOPBACK 12 /* INB, OOB */ +#define DIGI_CMD_TRANSMIT_IDLE 13 /* INB, OOB */ +#define DIGI_CMD_READ_UART_REGISTER 14 /* OOB */ +#define DIGI_CMD_WRITE_UART_REGISTER 15 /* INB, OOB */ +#define DIGI_CMD_AND_UART_REGISTER 16 /* INB, OOB */ +#define DIGI_CMD_OR_UART_REGISTER 17 /* INB, OOB */ +#define DIGI_CMD_SEND_DATA 18 /* INB */ +#define DIGI_CMD_RECEIVE_DATA 19 /* INB */ +#define DIGI_CMD_RECEIVE_DISABLE 20 /* INB */ +#define DIGI_CMD_GET_PORT_TYPE 21 /* OOB */ + +/* baud rates */ +#define DIGI_BAUD_50 0 +#define DIGI_BAUD_75 1 +#define DIGI_BAUD_110 2 +#define DIGI_BAUD_150 3 +#define DIGI_BAUD_200 4 +#define DIGI_BAUD_300 5 +#define DIGI_BAUD_600 6 +#define DIGI_BAUD_1200 7 +#define DIGI_BAUD_1800 8 +#define DIGI_BAUD_2400 9 +#define DIGI_BAUD_4800 10 +#define DIGI_BAUD_7200 11 +#define DIGI_BAUD_9600 12 +#define DIGI_BAUD_14400 13 +#define DIGI_BAUD_19200 14 +#define DIGI_BAUD_28800 15 +#define DIGI_BAUD_38400 16 +#define DIGI_BAUD_57600 17 +#define DIGI_BAUD_76800 18 +#define DIGI_BAUD_115200 19 +#define DIGI_BAUD_153600 20 +#define DIGI_BAUD_230400 21 +#define DIGI_BAUD_460800 22 + +/* arguments */ +#define DIGI_WORD_SIZE_5 0 +#define DIGI_WORD_SIZE_6 1 +#define DIGI_WORD_SIZE_7 2 +#define DIGI_WORD_SIZE_8 3 + +#define DIGI_PARITY_NONE 0 +#define DIGI_PARITY_ODD 1 +#define DIGI_PARITY_EVEN 2 +#define DIGI_PARITY_MARK 3 +#define DIGI_PARITY_SPACE 4 + +#define DIGI_STOP_BITS_1 0 +#define DIGI_STOP_BITS_2 1 + +#define DIGI_INPUT_FLOW_CONTROL_XON_XOFF 1 +#define DIGI_INPUT_FLOW_CONTROL_RTS 2 +#define DIGI_INPUT_FLOW_CONTROL_DTR 4 + +#define DIGI_OUTPUT_FLOW_CONTROL_XON_XOFF 1 +#define DIGI_OUTPUT_FLOW_CONTROL_CTS 2 +#define DIGI_OUTPUT_FLOW_CONTROL_DSR 4 + +#define DIGI_DTR_INACTIVE 0 +#define DIGI_DTR_ACTIVE 1 +#define DIGI_DTR_INPUT_FLOW_CONTROL 2 + +#define DIGI_RTS_INACTIVE 0 +#define DIGI_RTS_ACTIVE 1 +#define DIGI_RTS_INPUT_FLOW_CONTROL 2 +#define DIGI_RTS_TOGGLE 3 + +#define DIGI_FLUSH_TX 1 +#define DIGI_FLUSH_RX 2 +#define DIGI_RESUME_TX 4 /* clears xoff condition */ + +#define DIGI_TRANSMIT_NOT_IDLE 0 +#define DIGI_TRANSMIT_IDLE 1 + +#define DIGI_DISABLE 0 +#define DIGI_ENABLE 1 + +#define DIGI_DEASSERT 0 +#define DIGI_ASSERT 1 + +/* in band status codes */ +#define DIGI_OVERRUN_ERROR 4 +#define DIGI_PARITY_ERROR 8 +#define DIGI_FRAMING_ERROR 16 +#define DIGI_BREAK_ERROR 32 + +/* out of band status */ +#define DIGI_NO_ERROR 0 +#define DIGI_BAD_FIRST_PARAMETER 1 +#define DIGI_BAD_SECOND_PARAMETER 2 +#define DIGI_INVALID_LINE 3 +#define DIGI_INVALID_OPCODE 4 + +/* input signals */ +#define DIGI_READ_INPUT_SIGNALS_SLOT 1 +#define DIGI_READ_INPUT_SIGNALS_ERR 2 +#define DIGI_READ_INPUT_SIGNALS_BUSY 4 +#define DIGI_READ_INPUT_SIGNALS_PE 8 +#define DIGI_READ_INPUT_SIGNALS_CTS 16 +#define DIGI_READ_INPUT_SIGNALS_DSR 32 +#define DIGI_READ_INPUT_SIGNALS_RI 64 +#define DIGI_READ_INPUT_SIGNALS_DCD 128 + + +/* Structures */ + +struct digi_serial { + spinlock_t ds_serial_lock; + struct usb_serial_port *ds_oob_port; /* out-of-band port */ + int ds_oob_port_num; /* index of out-of-band port */ + int ds_device_started; +}; + +struct digi_port { + spinlock_t dp_port_lock; + int dp_port_num; + int dp_out_buf_len; + unsigned char dp_out_buf[DIGI_OUT_BUF_SIZE]; + int dp_write_urb_in_use; + unsigned int dp_modem_signals; + int dp_transmit_idle; + wait_queue_head_t dp_transmit_idle_wait; + int dp_throttled; + int dp_throttle_restart; + wait_queue_head_t dp_flush_wait; + wait_queue_head_t dp_close_wait; /* wait queue for close */ + struct work_struct dp_wakeup_work; + struct usb_serial_port *dp_port; +}; + + +/* Local Function Declarations */ + +static void digi_wakeup_write_lock(struct work_struct *work); +static int digi_write_oob_command(struct usb_serial_port *port, + unsigned char *buf, int count, int interruptible); +static int digi_write_inb_command(struct usb_serial_port *port, + unsigned char *buf, int count, unsigned long timeout); +static int digi_set_modem_signals(struct usb_serial_port *port, + unsigned int modem_signals, int interruptible); +static int digi_transmit_idle(struct usb_serial_port *port, + unsigned long timeout); +static void digi_rx_throttle(struct tty_struct *tty); +static void digi_rx_unthrottle(struct tty_struct *tty); +static void digi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios); +static void digi_break_ctl(struct tty_struct *tty, int break_state); +static int digi_tiocmget(struct tty_struct *tty); +static int digi_tiocmset(struct tty_struct *tty, unsigned int set, + unsigned int clear); +static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +static void digi_write_bulk_callback(struct urb *urb); +static int digi_write_room(struct tty_struct *tty); +static int digi_chars_in_buffer(struct tty_struct *tty); +static int digi_open(struct tty_struct *tty, struct usb_serial_port *port); +static void digi_close(struct usb_serial_port *port); +static void digi_dtr_rts(struct usb_serial_port *port, int on); +static int digi_startup_device(struct usb_serial *serial); +static int digi_startup(struct usb_serial *serial); +static void digi_disconnect(struct usb_serial *serial); +static void digi_release(struct usb_serial *serial); +static int digi_port_probe(struct usb_serial_port *port); +static int digi_port_remove(struct usb_serial_port *port); +static void digi_read_bulk_callback(struct urb *urb); +static int digi_read_inb_callback(struct urb *urb); +static int digi_read_oob_callback(struct urb *urb); + + +static const struct usb_device_id id_table_combined[] = { + { USB_DEVICE(DIGI_VENDOR_ID, DIGI_2_ID) }, + { USB_DEVICE(DIGI_VENDOR_ID, DIGI_4_ID) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id id_table_2[] = { + { USB_DEVICE(DIGI_VENDOR_ID, DIGI_2_ID) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id id_table_4[] = { + { USB_DEVICE(DIGI_VENDOR_ID, DIGI_4_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +/* device info needed for the Digi serial converter */ + +static struct usb_serial_driver digi_acceleport_2_device = { + .driver = { + .owner = THIS_MODULE, + .name = "digi_2", + }, + .description = "Digi 2 port USB adapter", + .id_table = id_table_2, + .num_ports = 3, + .open = digi_open, + .close = digi_close, + .dtr_rts = digi_dtr_rts, + .write = digi_write, + .write_room = digi_write_room, + .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, + .unthrottle = digi_rx_unthrottle, + .set_termios = digi_set_termios, + .break_ctl = digi_break_ctl, + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, + .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +}; + +static struct usb_serial_driver digi_acceleport_4_device = { + .driver = { + .owner = THIS_MODULE, + .name = "digi_4", + }, + .description = "Digi 4 port USB adapter", + .id_table = id_table_4, + .num_ports = 4, + .open = digi_open, + .close = digi_close, + .write = digi_write, + .write_room = digi_write_room, + .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, + .unthrottle = digi_rx_unthrottle, + .set_termios = digi_set_termios, + .break_ctl = digi_break_ctl, + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, + .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &digi_acceleport_2_device, &digi_acceleport_4_device, NULL +}; + +/* Functions */ + +/* + * Cond Wait Interruptible Timeout Irqrestore + * + * Do spin_unlock_irqrestore and interruptible_sleep_on_timeout + * so that wake ups are not lost if they occur between the unlock + * and the sleep. In other words, spin_unlock_irqrestore and + * interruptible_sleep_on_timeout are "atomic" with respect to + * wake ups. This is used to implement condition variables. + * + * interruptible_sleep_on_timeout is deprecated and has been replaced + * with the equivalent code. + */ + +static long cond_wait_interruptible_timeout_irqrestore( + wait_queue_head_t *q, long timeout, + spinlock_t *lock, unsigned long flags) +__releases(lock) +{ + DEFINE_WAIT(wait); + + prepare_to_wait(q, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(lock, flags); + timeout = schedule_timeout(timeout); + finish_wait(q, &wait); + + return timeout; +} + + +/* + * Digi Wakeup Write + * + * Wake up port, line discipline, and tty processes sleeping + * on writes. + */ + +static void digi_wakeup_write_lock(struct work_struct *work) +{ + struct digi_port *priv = + container_of(work, struct digi_port, dp_wakeup_work); + struct usb_serial_port *port = priv->dp_port; + unsigned long flags; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + tty_port_tty_wakeup(&port->port); + spin_unlock_irqrestore(&priv->dp_port_lock, flags); +} + +/* + * Digi Write OOB Command + * + * Write commands on the out of band port. Commands are 4 + * bytes each, multiple commands can be sent at once, and + * no command will be split across USB packets. Returns 0 + * if successful, -EINTR if interrupted while sleeping and + * the interruptible flag is true, or a negative error + * returned by usb_submit_urb. + */ + +static int digi_write_oob_command(struct usb_serial_port *port, + unsigned char *buf, int count, int interruptible) +{ + int ret = 0; + int len; + struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned long flags = 0; + + dev_dbg(&port->dev, + "digi_write_oob_command: TOP: port=%d, count=%d\n", + oob_priv->dp_port_num, count); + + spin_lock_irqsave(&oob_priv->dp_port_lock, flags); + while (count > 0) { + while (oob_priv->dp_write_urb_in_use) { + cond_wait_interruptible_timeout_irqrestore( + &oob_port->write_wait, DIGI_RETRY_TIMEOUT, + &oob_priv->dp_port_lock, flags); + if (interruptible && signal_pending(current)) + return -EINTR; + spin_lock_irqsave(&oob_priv->dp_port_lock, flags); + } + + /* len must be a multiple of 4, so commands are not split */ + len = min(count, oob_port->bulk_out_size); + if (len > 4) + len &= ~3; + memcpy(oob_port->write_urb->transfer_buffer, buf, len); + oob_port->write_urb->transfer_buffer_length = len; + ret = usb_submit_urb(oob_port->write_urb, GFP_ATOMIC); + if (ret == 0) { + oob_priv->dp_write_urb_in_use = 1; + count -= len; + buf += len; + } + } + spin_unlock_irqrestore(&oob_priv->dp_port_lock, flags); + if (ret) + dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", + __func__, ret); + return ret; + +} + + +/* + * Digi Write In Band Command + * + * Write commands on the given port. Commands are 4 + * bytes each, multiple commands can be sent at once, and + * no command will be split across USB packets. If timeout + * is non-zero, write in band command will return after + * waiting unsuccessfully for the URB status to clear for + * timeout ticks. Returns 0 if successful, or a negative + * error returned by digi_write. + */ + +static int digi_write_inb_command(struct usb_serial_port *port, + unsigned char *buf, int count, unsigned long timeout) +{ + int ret = 0; + int len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; + unsigned long flags = 0; + + dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); + + if (timeout) + timeout += jiffies; + else + timeout = ULONG_MAX; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + while (count > 0 && ret == 0) { + while (priv->dp_write_urb_in_use && + time_before(jiffies, timeout)) { + cond_wait_interruptible_timeout_irqrestore( + &port->write_wait, DIGI_RETRY_TIMEOUT, + &priv->dp_port_lock, flags); + if (signal_pending(current)) + return -EINTR; + spin_lock_irqsave(&priv->dp_port_lock, flags); + } + + /* len must be a multiple of 4 and small enough to */ + /* guarantee the write will send buffered data first, */ + /* so commands are in order with data and not split */ + len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); + if (len > 4) + len &= ~3; + + /* write any buffered data first */ + if (priv->dp_out_buf_len > 0) { + data[0] = DIGI_CMD_SEND_DATA; + data[1] = priv->dp_out_buf_len; + memcpy(data + 2, priv->dp_out_buf, + priv->dp_out_buf_len); + memcpy(data + 2 + priv->dp_out_buf_len, buf, len); + port->write_urb->transfer_buffer_length + = priv->dp_out_buf_len + 2 + len; + } else { + memcpy(data, buf, len); + port->write_urb->transfer_buffer_length = len; + } + + ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (ret == 0) { + priv->dp_write_urb_in_use = 1; + priv->dp_out_buf_len = 0; + count -= len; + buf += len; + } + + } + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + + if (ret) + dev_err(&port->dev, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + return ret; +} + + +/* + * Digi Set Modem Signals + * + * Sets or clears DTR and RTS on the port, according to the + * modem_signals argument. Use TIOCM_DTR and TIOCM_RTS flags + * for the modem_signals argument. Returns 0 if successful, + * -EINTR if interrupted while sleeping, or a non-zero error + * returned by usb_submit_urb. + */ + +static int digi_set_modem_signals(struct usb_serial_port *port, + unsigned int modem_signals, int interruptible) +{ + + int ret; + struct digi_port *port_priv = usb_get_serial_port_data(port); + struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned char *data = oob_port->write_urb->transfer_buffer; + unsigned long flags = 0; + + + dev_dbg(&port->dev, + "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n", + port_priv->dp_port_num, modem_signals); + + spin_lock_irqsave(&oob_priv->dp_port_lock, flags); + spin_lock(&port_priv->dp_port_lock); + + while (oob_priv->dp_write_urb_in_use) { + spin_unlock(&port_priv->dp_port_lock); + cond_wait_interruptible_timeout_irqrestore( + &oob_port->write_wait, DIGI_RETRY_TIMEOUT, + &oob_priv->dp_port_lock, flags); + if (interruptible && signal_pending(current)) + return -EINTR; + spin_lock_irqsave(&oob_priv->dp_port_lock, flags); + spin_lock(&port_priv->dp_port_lock); + } + data[0] = DIGI_CMD_SET_DTR_SIGNAL; + data[1] = port_priv->dp_port_num; + data[2] = (modem_signals & TIOCM_DTR) ? + DIGI_DTR_ACTIVE : DIGI_DTR_INACTIVE; + data[3] = 0; + data[4] = DIGI_CMD_SET_RTS_SIGNAL; + data[5] = port_priv->dp_port_num; + data[6] = (modem_signals & TIOCM_RTS) ? + DIGI_RTS_ACTIVE : DIGI_RTS_INACTIVE; + data[7] = 0; + + oob_port->write_urb->transfer_buffer_length = 8; + + ret = usb_submit_urb(oob_port->write_urb, GFP_ATOMIC); + if (ret == 0) { + oob_priv->dp_write_urb_in_use = 1; + port_priv->dp_modem_signals = + (port_priv->dp_modem_signals&~(TIOCM_DTR|TIOCM_RTS)) + | (modem_signals&(TIOCM_DTR|TIOCM_RTS)); + } + spin_unlock(&port_priv->dp_port_lock); + spin_unlock_irqrestore(&oob_priv->dp_port_lock, flags); + if (ret) + dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", + __func__, ret); + return ret; +} + +/* + * Digi Transmit Idle + * + * Digi transmit idle waits, up to timeout ticks, for the transmitter + * to go idle. It returns 0 if successful or a negative error. + * + * There are race conditions here if more than one process is calling + * digi_transmit_idle on the same port at the same time. However, this + * is only called from close, and only one process can be in close on a + * port at a time, so its ok. + */ + +static int digi_transmit_idle(struct usb_serial_port *port, + unsigned long timeout) +{ + int ret; + unsigned char buf[2]; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags = 0; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_transmit_idle = 0; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + + buf[0] = DIGI_CMD_TRANSMIT_IDLE; + buf[1] = 0; + + timeout += jiffies; + + ret = digi_write_inb_command(port, buf, 2, timeout - jiffies); + if (ret != 0) + return ret; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + + while (time_before(jiffies, timeout) && !priv->dp_transmit_idle) { + cond_wait_interruptible_timeout_irqrestore( + &priv->dp_transmit_idle_wait, DIGI_RETRY_TIMEOUT, + &priv->dp_port_lock, flags); + if (signal_pending(current)) + return -EINTR; + spin_lock_irqsave(&priv->dp_port_lock, flags); + } + priv->dp_transmit_idle = 0; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return 0; + +} + + +static void digi_rx_throttle(struct tty_struct *tty) +{ + unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); + + /* stop receiving characters by not resubmitting the read urb */ + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_throttled = 1; + priv->dp_throttle_restart = 0; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); +} + + +static void digi_rx_unthrottle(struct tty_struct *tty) +{ + int ret = 0; + unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); + + spin_lock_irqsave(&priv->dp_port_lock, flags); + + /* restart read chain */ + if (priv->dp_throttle_restart) + ret = usb_submit_urb(port->read_urb, GFP_ATOMIC); + + /* turn throttle off */ + priv->dp_throttled = 0; + priv->dp_throttle_restart = 0; + + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + + if (ret) + dev_err(&port->dev, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); +} + + +static void digi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct digi_port *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + unsigned int iflag = tty->termios.c_iflag; + unsigned int cflag = tty->termios.c_cflag; + unsigned int old_iflag = old_termios->c_iflag; + unsigned int old_cflag = old_termios->c_cflag; + unsigned char buf[32]; + unsigned int modem_signals; + int arg, ret; + int i = 0; + speed_t baud; + + dev_dbg(dev, + "digi_set_termios: TOP: port=%d, iflag=0x%x, old_iflag=0x%x, cflag=0x%x, old_cflag=0x%x\n", + priv->dp_port_num, iflag, old_iflag, cflag, old_cflag); + + /* set baud rate */ + baud = tty_get_baud_rate(tty); + if (baud != tty_termios_baud_rate(old_termios)) { + arg = -1; + + /* reassert DTR and (maybe) RTS on transition from B0 */ + if ((old_cflag&CBAUD) == B0) { + /* don't set RTS if using hardware flow control */ + /* and throttling input */ + modem_signals = TIOCM_DTR; + if (!(tty->termios.c_cflag & CRTSCTS) || + !test_bit(TTY_THROTTLED, &tty->flags)) + modem_signals |= TIOCM_RTS; + digi_set_modem_signals(port, modem_signals, 1); + } + switch (baud) { + /* drop DTR and RTS on transition to B0 */ + case 0: digi_set_modem_signals(port, 0, 1); break; + case 50: arg = DIGI_BAUD_50; break; + case 75: arg = DIGI_BAUD_75; break; + case 110: arg = DIGI_BAUD_110; break; + case 150: arg = DIGI_BAUD_150; break; + case 200: arg = DIGI_BAUD_200; break; + case 300: arg = DIGI_BAUD_300; break; + case 600: arg = DIGI_BAUD_600; break; + case 1200: arg = DIGI_BAUD_1200; break; + case 1800: arg = DIGI_BAUD_1800; break; + case 2400: arg = DIGI_BAUD_2400; break; + case 4800: arg = DIGI_BAUD_4800; break; + case 9600: arg = DIGI_BAUD_9600; break; + case 19200: arg = DIGI_BAUD_19200; break; + case 38400: arg = DIGI_BAUD_38400; break; + case 57600: arg = DIGI_BAUD_57600; break; + case 115200: arg = DIGI_BAUD_115200; break; + case 230400: arg = DIGI_BAUD_230400; break; + case 460800: arg = DIGI_BAUD_460800; break; + default: + arg = DIGI_BAUD_9600; + baud = 9600; + break; + } + if (arg != -1) { + buf[i++] = DIGI_CMD_SET_BAUD_RATE; + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; + } + } + /* set parity */ + tty->termios.c_cflag &= ~CMSPAR; + + if ((cflag&(PARENB|PARODD)) != (old_cflag&(PARENB|PARODD))) { + if (cflag&PARENB) { + if (cflag&PARODD) + arg = DIGI_PARITY_ODD; + else + arg = DIGI_PARITY_EVEN; + } else { + arg = DIGI_PARITY_NONE; + } + buf[i++] = DIGI_CMD_SET_PARITY; + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; + } + /* set word size */ + if ((cflag&CSIZE) != (old_cflag&CSIZE)) { + arg = -1; + switch (cflag&CSIZE) { + case CS5: arg = DIGI_WORD_SIZE_5; break; + case CS6: arg = DIGI_WORD_SIZE_6; break; + case CS7: arg = DIGI_WORD_SIZE_7; break; + case CS8: arg = DIGI_WORD_SIZE_8; break; + default: + dev_dbg(dev, + "digi_set_termios: can't handle word size %d\n", + (cflag&CSIZE)); + break; + } + + if (arg != -1) { + buf[i++] = DIGI_CMD_SET_WORD_SIZE; + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; + } + + } + + /* set stop bits */ + if ((cflag&CSTOPB) != (old_cflag&CSTOPB)) { + + if ((cflag&CSTOPB)) + arg = DIGI_STOP_BITS_2; + else + arg = DIGI_STOP_BITS_1; + + buf[i++] = DIGI_CMD_SET_STOP_BITS; + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; + + } + + /* set input flow control */ + if ((iflag&IXOFF) != (old_iflag&IXOFF) + || (cflag&CRTSCTS) != (old_cflag&CRTSCTS)) { + arg = 0; + if (iflag&IXOFF) + arg |= DIGI_INPUT_FLOW_CONTROL_XON_XOFF; + else + arg &= ~DIGI_INPUT_FLOW_CONTROL_XON_XOFF; + + if (cflag&CRTSCTS) { + arg |= DIGI_INPUT_FLOW_CONTROL_RTS; + + /* On USB-4 it is necessary to assert RTS prior */ + /* to selecting RTS input flow control. */ + buf[i++] = DIGI_CMD_SET_RTS_SIGNAL; + buf[i++] = priv->dp_port_num; + buf[i++] = DIGI_RTS_ACTIVE; + buf[i++] = 0; + + } else { + arg &= ~DIGI_INPUT_FLOW_CONTROL_RTS; + } + buf[i++] = DIGI_CMD_SET_INPUT_FLOW_CONTROL; + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; + } + + /* set output flow control */ + if ((iflag & IXON) != (old_iflag & IXON) + || (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) { + arg = 0; + if (iflag & IXON) + arg |= DIGI_OUTPUT_FLOW_CONTROL_XON_XOFF; + else + arg &= ~DIGI_OUTPUT_FLOW_CONTROL_XON_XOFF; + + if (cflag & CRTSCTS) { + arg |= DIGI_OUTPUT_FLOW_CONTROL_CTS; + } else { + arg &= ~DIGI_OUTPUT_FLOW_CONTROL_CTS; + tty->hw_stopped = 0; + } + + buf[i++] = DIGI_CMD_SET_OUTPUT_FLOW_CONTROL; + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; + } + + /* set receive enable/disable */ + if ((cflag & CREAD) != (old_cflag & CREAD)) { + if (cflag & CREAD) + arg = DIGI_ENABLE; + else + arg = DIGI_DISABLE; + + buf[i++] = DIGI_CMD_RECEIVE_ENABLE; + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; + } + ret = digi_write_oob_command(port, buf, i, 1); + if (ret != 0) + dev_dbg(dev, "digi_set_termios: write oob failed, ret=%d\n", ret); + tty_encode_baud_rate(tty, baud, baud); +} + + +static void digi_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned char buf[4]; + + buf[0] = DIGI_CMD_BREAK_CONTROL; + buf[1] = 2; /* length */ + buf[2] = break_state ? 1 : 0; + buf[3] = 0; /* pad */ + digi_write_inb_command(port, buf, 4, 0); +} + + +static int digi_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned int val; + unsigned long flags; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = priv->dp_modem_signals; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return val; +} + + +static int digi_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned int val; + unsigned long flags; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = (priv->dp_modem_signals & ~clear) | set; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return digi_set_modem_signals(port, val, 1); +} + + +static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + + int ret, data_len, new_len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; + unsigned long flags = 0; + + dev_dbg(&port->dev, + "digi_write: TOP: port=%d, count=%d, in_interrupt=%ld\n", + priv->dp_port_num, count, in_interrupt()); + + /* copy user data (which can sleep) before getting spin lock */ + count = min(count, port->bulk_out_size-2); + count = min(64, count); + + /* be sure only one write proceeds at a time */ + /* there are races on the port private buffer */ + spin_lock_irqsave(&priv->dp_port_lock, flags); + + /* wait for urb status clear to submit another urb */ + if (priv->dp_write_urb_in_use) { + /* buffer data if count is 1 (probably put_char) if possible */ + if (count == 1 && priv->dp_out_buf_len < DIGI_OUT_BUF_SIZE) { + priv->dp_out_buf[priv->dp_out_buf_len++] = *buf; + new_len = 1; + } else { + new_len = 0; + } + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return new_len; + } + + /* allow space for any buffered data and for new data, up to */ + /* transfer buffer size - 2 (for command and length bytes) */ + new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); + data_len = new_len + priv->dp_out_buf_len; + + if (data_len == 0) { + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return 0; + } + + port->write_urb->transfer_buffer_length = data_len+2; + + *data++ = DIGI_CMD_SEND_DATA; + *data++ = data_len; + + /* copy in buffered data first */ + memcpy(data, priv->dp_out_buf, priv->dp_out_buf_len); + data += priv->dp_out_buf_len; + + /* copy in new data */ + memcpy(data, buf, new_len); + + ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (ret == 0) { + priv->dp_write_urb_in_use = 1; + ret = new_len; + priv->dp_out_buf_len = 0; + } + + /* return length of new data written, or error */ + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + if (ret < 0) + dev_err_console(port, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + dev_dbg(&port->dev, "digi_write: returning %d\n", ret); + return ret; + +} + +static void digi_write_bulk_callback(struct urb *urb) +{ + + struct usb_serial_port *port = urb->context; + struct usb_serial *serial; + struct digi_port *priv; + struct digi_serial *serial_priv; + int ret = 0; + int status = urb->status; + + /* port and serial sanity check */ + if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { + pr_err("%s: port or port->private is NULL, status=%d\n", + __func__, status); + return; + } + serial = port->serial; + if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) { + dev_err(&port->dev, + "%s: serial or serial->private is NULL, status=%d\n", + __func__, status); + return; + } + + /* handle oob callback */ + if (priv->dp_port_num == serial_priv->ds_oob_port_num) { + dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); + spin_lock(&priv->dp_port_lock); + priv->dp_write_urb_in_use = 0; + wake_up_interruptible(&port->write_wait); + spin_unlock(&priv->dp_port_lock); + return; + } + + /* try to send any buffered data on this port */ + spin_lock(&priv->dp_port_lock); + priv->dp_write_urb_in_use = 0; + if (priv->dp_out_buf_len > 0) { + *((unsigned char *)(port->write_urb->transfer_buffer)) + = (unsigned char)DIGI_CMD_SEND_DATA; + *((unsigned char *)(port->write_urb->transfer_buffer) + 1) + = (unsigned char)priv->dp_out_buf_len; + port->write_urb->transfer_buffer_length = + priv->dp_out_buf_len + 2; + memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf, + priv->dp_out_buf_len); + ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (ret == 0) { + priv->dp_write_urb_in_use = 1; + priv->dp_out_buf_len = 0; + } + } + /* wake up processes sleeping on writes immediately */ + tty_port_tty_wakeup(&port->port); + /* also queue up a wakeup at scheduler time, in case we */ + /* lost the race in write_chan(). */ + schedule_work(&priv->dp_wakeup_work); + + spin_unlock(&priv->dp_port_lock); + if (ret && ret != -EPERM) + dev_err_console(port, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); +} + +static int digi_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); + int room; + unsigned long flags = 0; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + + if (priv->dp_write_urb_in_use) + room = 0; + else + room = port->bulk_out_size - 2 - priv->dp_out_buf_len; + + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + dev_dbg(&port->dev, "digi_write_room: port=%d, room=%d\n", priv->dp_port_num, room); + return room; + +} + +static int digi_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); + + if (priv->dp_write_urb_in_use) { + dev_dbg(&port->dev, "digi_chars_in_buffer: port=%d, chars=%d\n", + priv->dp_port_num, port->bulk_out_size - 2); + /* return(port->bulk_out_size - 2); */ + return 256; + } else { + dev_dbg(&port->dev, "digi_chars_in_buffer: port=%d, chars=%d\n", + priv->dp_port_num, priv->dp_out_buf_len); + return priv->dp_out_buf_len; + } + +} + +static void digi_dtr_rts(struct usb_serial_port *port, int on) +{ + /* Adjust DTR and RTS */ + digi_set_modem_signals(port, on * (TIOCM_DTR|TIOCM_RTS), 1); +} + +static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int ret; + unsigned char buf[32]; + struct digi_port *priv = usb_get_serial_port_data(port); + struct ktermios not_termios; + + /* be sure the device is started up */ + if (digi_startup_device(port->serial) != 0) + return -ENXIO; + + /* read modem signals automatically whenever they change */ + buf[0] = DIGI_CMD_READ_INPUT_SIGNALS; + buf[1] = priv->dp_port_num; + buf[2] = DIGI_ENABLE; + buf[3] = 0; + + /* flush fifos */ + buf[4] = DIGI_CMD_IFLUSH_FIFO; + buf[5] = priv->dp_port_num; + buf[6] = DIGI_FLUSH_TX | DIGI_FLUSH_RX; + buf[7] = 0; + + ret = digi_write_oob_command(port, buf, 8, 1); + if (ret != 0) + dev_dbg(&port->dev, "digi_open: write oob failed, ret=%d\n", ret); + + /* set termios settings */ + if (tty) { + not_termios.c_cflag = ~tty->termios.c_cflag; + not_termios.c_iflag = ~tty->termios.c_iflag; + digi_set_termios(tty, port, ¬_termios); + } + return 0; +} + + +static void digi_close(struct usb_serial_port *port) +{ + DEFINE_WAIT(wait); + int ret; + unsigned char buf[32]; + struct digi_port *priv = usb_get_serial_port_data(port); + + mutex_lock(&port->serial->disc_mutex); + /* if disconnected, just clear flags */ + if (port->serial->disconnected) + goto exit; + + /* FIXME: Transmit idle belongs in the wait_unti_sent path */ + digi_transmit_idle(port, DIGI_CLOSE_TIMEOUT); + + /* disable input flow control */ + buf[0] = DIGI_CMD_SET_INPUT_FLOW_CONTROL; + buf[1] = priv->dp_port_num; + buf[2] = DIGI_DISABLE; + buf[3] = 0; + + /* disable output flow control */ + buf[4] = DIGI_CMD_SET_OUTPUT_FLOW_CONTROL; + buf[5] = priv->dp_port_num; + buf[6] = DIGI_DISABLE; + buf[7] = 0; + + /* disable reading modem signals automatically */ + buf[8] = DIGI_CMD_READ_INPUT_SIGNALS; + buf[9] = priv->dp_port_num; + buf[10] = DIGI_DISABLE; + buf[11] = 0; + + /* disable receive */ + buf[12] = DIGI_CMD_RECEIVE_ENABLE; + buf[13] = priv->dp_port_num; + buf[14] = DIGI_DISABLE; + buf[15] = 0; + + /* flush fifos */ + buf[16] = DIGI_CMD_IFLUSH_FIFO; + buf[17] = priv->dp_port_num; + buf[18] = DIGI_FLUSH_TX | DIGI_FLUSH_RX; + buf[19] = 0; + + ret = digi_write_oob_command(port, buf, 20, 0); + if (ret != 0) + dev_dbg(&port->dev, "digi_close: write oob failed, ret=%d\n", + ret); + /* wait for final commands on oob port to complete */ + prepare_to_wait(&priv->dp_flush_wait, &wait, + TASK_INTERRUPTIBLE); + schedule_timeout(DIGI_CLOSE_TIMEOUT); + finish_wait(&priv->dp_flush_wait, &wait); + + /* shutdown any outstanding bulk writes */ + usb_kill_urb(port->write_urb); +exit: + spin_lock_irq(&priv->dp_port_lock); + priv->dp_write_urb_in_use = 0; + wake_up_interruptible(&priv->dp_close_wait); + spin_unlock_irq(&priv->dp_port_lock); + mutex_unlock(&port->serial->disc_mutex); +} + + +/* + * Digi Startup Device + * + * Starts reads on all ports. Must be called AFTER startup, with + * urbs initialized. Returns 0 if successful, non-zero error otherwise. + */ + +static int digi_startup_device(struct usb_serial *serial) +{ + int i, ret = 0; + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *port; + + /* be sure this happens exactly once */ + spin_lock(&serial_priv->ds_serial_lock); + if (serial_priv->ds_device_started) { + spin_unlock(&serial_priv->ds_serial_lock); + return 0; + } + serial_priv->ds_device_started = 1; + spin_unlock(&serial_priv->ds_serial_lock); + + /* start reading from each bulk in endpoint for the device */ + /* set USB_DISABLE_SPD flag for write bulk urbs */ + for (i = 0; i < serial->type->num_ports + 1; i++) { + port = serial->port[i]; + ret = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (ret != 0) { + dev_err(&port->dev, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, i); + break; + } + } + return ret; +} + +static int digi_port_init(struct usb_serial_port *port, unsigned port_num) +{ + struct digi_port *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->dp_port_lock); + priv->dp_port_num = port_num; + init_waitqueue_head(&priv->dp_transmit_idle_wait); + init_waitqueue_head(&priv->dp_flush_wait); + init_waitqueue_head(&priv->dp_close_wait); + INIT_WORK(&priv->dp_wakeup_work, digi_wakeup_write_lock); + priv->dp_port = port; + + init_waitqueue_head(&port->write_wait); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int digi_startup(struct usb_serial *serial) +{ + struct digi_serial *serial_priv; + int ret; + + serial_priv = kzalloc(sizeof(*serial_priv), GFP_KERNEL); + if (!serial_priv) + return -ENOMEM; + + spin_lock_init(&serial_priv->ds_serial_lock); + serial_priv->ds_oob_port_num = serial->type->num_ports; + serial_priv->ds_oob_port = serial->port[serial_priv->ds_oob_port_num]; + + ret = digi_port_init(serial_priv->ds_oob_port, + serial_priv->ds_oob_port_num); + if (ret) { + kfree(serial_priv); + return ret; + } + + usb_set_serial_data(serial, serial_priv); + + return 0; +} + + +static void digi_disconnect(struct usb_serial *serial) +{ + int i; + + /* stop reads and writes on all ports */ + for (i = 0; i < serial->type->num_ports + 1; i++) { + usb_kill_urb(serial->port[i]->read_urb); + usb_kill_urb(serial->port[i]->write_urb); + } +} + + +static void digi_release(struct usb_serial *serial) +{ + struct digi_serial *serial_priv; + struct digi_port *priv; + + serial_priv = usb_get_serial_data(serial); + + priv = usb_get_serial_port_data(serial_priv->ds_oob_port); + kfree(priv); + + kfree(serial_priv); +} + +static int digi_port_probe(struct usb_serial_port *port) +{ + unsigned port_num; + + port_num = port->number - port->serial->minor; + + return digi_port_init(port, port_num); +} + +static int digi_port_remove(struct usb_serial_port *port) +{ + struct digi_port *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static void digi_read_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct digi_port *priv; + struct digi_serial *serial_priv; + int ret; + int status = urb->status; + + /* port sanity check, do not resubmit if port is not valid */ + if (port == NULL) + return; + priv = usb_get_serial_port_data(port); + if (priv == NULL) { + dev_err(&port->dev, "%s: port->private is NULL, status=%d\n", + __func__, status); + return; + } + if (port->serial == NULL || + (serial_priv = usb_get_serial_data(port->serial)) == NULL) { + dev_err(&port->dev, "%s: serial is bad or serial->private " + "is NULL, status=%d\n", __func__, status); + return; + } + + /* do not resubmit urb if it has any status error */ + if (status) { + dev_err(&port->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; + } + + /* handle oob or inb callback, do not resubmit if error */ + if (priv->dp_port_num == serial_priv->ds_oob_port_num) { + if (digi_read_oob_callback(urb) != 0) + return; + } else { + if (digi_read_inb_callback(urb) != 0) + return; + } + + /* continue read */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret != 0 && ret != -EPERM) { + dev_err(&port->dev, + "%s: failed resubmitting urb, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + } + +} + +/* + * Digi Read INB Callback + * + * Digi Read INB Callback handles reads on the in band ports, sending + * the data on to the tty subsystem. When called we know port and + * port->private are not NULL and port->serial has been validated. + * It returns 0 if successful, 1 if successful but the port is + * throttled, and -1 if the sanity checks failed. + */ + +static int digi_read_inb_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct digi_port *priv = usb_get_serial_port_data(port); + int opcode = ((unsigned char *)urb->transfer_buffer)[0]; + int len = ((unsigned char *)urb->transfer_buffer)[1]; + int port_status = ((unsigned char *)urb->transfer_buffer)[2]; + unsigned char *data = ((unsigned char *)urb->transfer_buffer) + 3; + int flag, throttled; + int status = urb->status; + + /* do not process callbacks on closed ports */ + /* but do continue the read chain */ + if (urb->status == -ENOENT) + return 0; + + /* short/multiple packet check */ + if (urb->actual_length != len + 2) { + dev_err(&port->dev, "%s: INCOMPLETE OR MULTIPLE PACKET, " + "status=%d, port=%d, opcode=%d, len=%d, " + "actual_length=%d, status=%d\n", __func__, status, + priv->dp_port_num, opcode, len, urb->actual_length, + port_status); + return -1; + } + + spin_lock(&priv->dp_port_lock); + + /* check for throttle; if set, do not resubmit read urb */ + /* indicate the read chain needs to be restarted on unthrottle */ + throttled = priv->dp_throttled; + if (throttled) + priv->dp_throttle_restart = 1; + + /* receive data */ + if (opcode == DIGI_CMD_RECEIVE_DATA) { + /* get flag from port_status */ + flag = 0; + + /* overrun is special, not associated with a char */ + if (port_status & DIGI_OVERRUN_ERROR) + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + + /* break takes precedence over parity, */ + /* which takes precedence over framing errors */ + if (port_status & DIGI_BREAK_ERROR) + flag = TTY_BREAK; + else if (port_status & DIGI_PARITY_ERROR) + flag = TTY_PARITY; + else if (port_status & DIGI_FRAMING_ERROR) + flag = TTY_FRAME; + + /* data length is len-1 (one byte of len is port_status) */ + --len; + if (len > 0) { + tty_insert_flip_string_fixed_flag(&port->port, data, + flag, len); + tty_flip_buffer_push(&port->port); + } + } + spin_unlock(&priv->dp_port_lock); + + if (opcode == DIGI_CMD_RECEIVE_DISABLE) + dev_dbg(&port->dev, "%s: got RECEIVE_DISABLE\n", __func__); + else if (opcode != DIGI_CMD_RECEIVE_DATA) + dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode); + + return throttled ? 1 : 0; + +} + + +/* + * Digi Read OOB Callback + * + * Digi Read OOB Callback handles reads on the out of band port. + * When called we know port and port->private are not NULL and + * the port->serial is valid. It returns 0 if successful, and + * -1 if the sanity checks failed. + */ + +static int digi_read_oob_callback(struct urb *urb) +{ + + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + struct tty_struct *tty; + struct digi_port *priv = usb_get_serial_port_data(port); + int opcode, line, status, val; + int i; + unsigned int rts; + + /* handle each oob command */ + for (i = 0; i < urb->actual_length - 3;) { + opcode = ((unsigned char *)urb->transfer_buffer)[i++]; + line = ((unsigned char *)urb->transfer_buffer)[i++]; + status = ((unsigned char *)urb->transfer_buffer)[i++]; + val = ((unsigned char *)urb->transfer_buffer)[i++]; + + dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", + opcode, line, status, val); + + if (status != 0 || line >= serial->type->num_ports) + continue; + + port = serial->port[line]; + + priv = usb_get_serial_port_data(port); + if (priv == NULL) + return -1; + + tty = tty_port_tty_get(&port->port); + + rts = 0; + if (tty) + rts = tty->termios.c_cflag & CRTSCTS; + + if (tty && opcode == DIGI_CMD_READ_INPUT_SIGNALS) { + spin_lock(&priv->dp_port_lock); + /* convert from digi flags to termiox flags */ + if (val & DIGI_READ_INPUT_SIGNALS_CTS) { + priv->dp_modem_signals |= TIOCM_CTS; + /* port must be open to use tty struct */ + if (rts) { + tty->hw_stopped = 0; + tty_port_tty_wakeup(&port->port); + } + } else { + priv->dp_modem_signals &= ~TIOCM_CTS; + /* port must be open to use tty struct */ + if (rts) + tty->hw_stopped = 1; + } + if (val & DIGI_READ_INPUT_SIGNALS_DSR) + priv->dp_modem_signals |= TIOCM_DSR; + else + priv->dp_modem_signals &= ~TIOCM_DSR; + if (val & DIGI_READ_INPUT_SIGNALS_RI) + priv->dp_modem_signals |= TIOCM_RI; + else + priv->dp_modem_signals &= ~TIOCM_RI; + if (val & DIGI_READ_INPUT_SIGNALS_DCD) + priv->dp_modem_signals |= TIOCM_CD; + else + priv->dp_modem_signals &= ~TIOCM_CD; + + spin_unlock(&priv->dp_port_lock); + } else if (opcode == DIGI_CMD_TRANSMIT_IDLE) { + spin_lock(&priv->dp_port_lock); + priv->dp_transmit_idle = 1; + wake_up_interruptible(&priv->dp_transmit_idle_wait); + spin_unlock(&priv->dp_port_lock); + } else if (opcode == DIGI_CMD_IFLUSH_FIFO) { + wake_up_interruptible(&priv->dp_flush_wait); + } + tty_kref_put(tty); + } + return 0; + +} + +module_usb_serial_driver(serial_drivers, id_table_combined); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/empeg.c b/drivers/usb/serial/empeg.c new file mode 100644 index 00000000..0f658618 --- /dev/null +++ b/drivers/usb/serial/empeg.c @@ -0,0 +1,130 @@ +/* + * USB Empeg empeg-car player driver + * + * Copyright (C) 2000, 2001 + * Gary Brubaker (xavyer@ix.netcom.com) + * + * Copyright (C) 1999 - 2001 + * Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, as published by + * the Free Software Foundation, version 2. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Greg Kroah-Hartman , Gary Brubaker " +#define DRIVER_DESC "USB Empeg Mark I/II Driver" + +#define EMPEG_VENDOR_ID 0x084f +#define EMPEG_PRODUCT_ID 0x0001 + +/* function prototypes for an empeg-car player */ +static int empeg_startup(struct usb_serial *serial); +static void empeg_init_termios(struct tty_struct *tty); + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(EMPEG_VENDOR_ID, EMPEG_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver empeg_device = { + .driver = { + .owner = THIS_MODULE, + .name = "empeg", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_out_size = 256, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .attach = empeg_startup, + .init_termios = empeg_init_termios, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &empeg_device, NULL +}; + +static int empeg_startup(struct usb_serial *serial) +{ + int r; + + if (serial->dev->actconfig->desc.bConfigurationValue != 1) { + dev_err(&serial->dev->dev, "active config #%d != 1 ??\n", + serial->dev->actconfig->desc.bConfigurationValue); + return -ENODEV; + } + + r = usb_reset_configuration(serial->dev); + + /* continue on with initialization */ + return r; +} + +static void empeg_init_termios(struct tty_struct *tty) +{ + struct ktermios *termios = &tty->termios; + + /* + * The empeg-car player wants these particular tty settings. + * You could, for example, change the baud rate, however the + * player only supports 115200 (currently), so there is really + * no point in support for changes to the tty settings. + * (at least for now) + * + * The default requirements for this device are: + */ + termios->c_iflag + &= ~(IGNBRK /* disable ignore break */ + | BRKINT /* disable break causes interrupt */ + | PARMRK /* disable mark parity errors */ + | ISTRIP /* disable clear high bit of input characters */ + | INLCR /* disable translate NL to CR */ + | IGNCR /* disable ignore CR */ + | ICRNL /* disable translate CR to NL */ + | IXON); /* disable enable XON/XOFF flow control */ + + termios->c_oflag + &= ~OPOST; /* disable postprocess output characters */ + + termios->c_lflag + &= ~(ECHO /* disable echo input characters */ + | ECHONL /* disable echo new line */ + | ICANON /* disable erase, kill, werase, and rprnt special characters */ + | ISIG /* disable interrupt, quit, and suspend special characters */ + | IEXTEN); /* disable non-POSIX special characters */ + + termios->c_cflag + &= ~(CSIZE /* no size */ + | PARENB /* disable parity bit */ + | CBAUD); /* clear current baud rate */ + + termios->c_cflag + |= CS8; /* character size 8 bits */ + + tty_encode_baud_rate(tty, 115200, 115200); +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/ezusb_convert.pl b/drivers/usb/serial/ezusb_convert.pl new file mode 100644 index 00000000..13f11469 --- /dev/null +++ b/drivers/usb/serial/ezusb_convert.pl @@ -0,0 +1,50 @@ +#! /usr/bin/perl -w + + +# convert an Intel HEX file into a set of C records usable by the firmware +# loading code in usb-serial.c (or others) + +# accepts the .hex file(s) on stdin, a basename (to name the initialized +# array) as an argument, and prints the .h file to stdout. Typical usage: +# perl ezusb_convert.pl foo fw_foo.h + + +my $basename = $ARGV[0]; +die "no base name specified" unless $basename; + +while () { + # ':' '\r' + # len, type, crc are 2-char hex, addr is 4-char hex. type is 00 for + # normal records, 01 for EOF + my($lenstring, $addrstring, $typestring, $reststring, $doscrap) = + /^:(\w\w)(\w\w\w\w)(\w\w)(\w+)(\r?)$/; + die "malformed line: $_" unless $reststring; + last if $typestring eq '01'; + my($len) = hex($lenstring); + my($addr) = hex($addrstring); + my(@bytes) = unpack("C*", pack("H".(2*$len), $reststring)); + #pop(@bytes); # last byte is a CRC + push(@records, [$addr, \@bytes]); +} + +@sorted_records = sort { $a->[0] <=> $b->[0] } @records; + +print <<"EOF"; +/* + * ${basename}_fw.h + * + * Generated from ${basename}.s by ezusb_convert.pl + * This file is presumed to be under the same copyright as the source file + * from which it was derived. + */ + +EOF + +print "static const struct ezusb_hex_record ${basename}_firmware[] = {\n"; +foreach $r (@sorted_records) { + printf("{ 0x%04x,\t%d,\t{", $r->[0], scalar(@{$r->[1]})); + print join(", ", map {sprintf('0x%02x', $_);} @{$r->[1]}); + print "} },\n"; +} +print "{ 0xffff,\t0,\t{0x00} }\n"; +print "};\n"; diff --git a/drivers/usb/serial/f81232.c b/drivers/usb/serial/f81232.c new file mode 100644 index 00000000..7d8dd5aa --- /dev/null +++ b/drivers/usb/serial/f81232.c @@ -0,0 +1,373 @@ +/* + * Fintek F81232 USB to serial adaptor driver + * + * Copyright (C) 2012 Greg Kroah-Hartman (gregkh@linuxfoundation.org) + * Copyright (C) 2012 Linux Foundation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x1934, 0x0706) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +#define CONTROL_DTR 0x01 +#define CONTROL_RTS 0x02 + +#define UART_STATE 0x08 +#define UART_STATE_TRANSIENT_MASK 0x74 +#define UART_DCD 0x01 +#define UART_DSR 0x02 +#define UART_BREAK_ERROR 0x04 +#define UART_RING 0x08 +#define UART_FRAME_ERROR 0x10 +#define UART_PARITY_ERROR 0x20 +#define UART_OVERRUN_ERROR 0x40 +#define UART_CTS 0x80 + +struct f81232_private { + spinlock_t lock; + u8 line_control; + u8 line_status; +}; + +static void f81232_update_line_status(struct usb_serial_port *port, + unsigned char *data, + unsigned int actual_length) +{ +} + +static void f81232_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int actual_length = urb->actual_length; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_serial_debug_data(&port->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + f81232_update_line_status(port, data, actual_length); + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&urb->dev->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, retval); +} + +static void f81232_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct f81232_private *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + char tty_flag = TTY_NORMAL; + unsigned long flags; + u8 line_status; + int i; + + /* update line status */ + spin_lock_irqsave(&priv->lock, flags); + line_status = priv->line_status; + priv->line_status &= ~UART_STATE_TRANSIENT_MASK; + spin_unlock_irqrestore(&priv->lock, flags); + wake_up_interruptible(&port->port.delta_msr_wait); + + if (!urb->actual_length) + return; + + /* break takes precedence over parity, */ + /* which takes precedence over framing errors */ + if (line_status & UART_BREAK_ERROR) + tty_flag = TTY_BREAK; + else if (line_status & UART_PARITY_ERROR) + tty_flag = TTY_PARITY; + else if (line_status & UART_FRAME_ERROR) + tty_flag = TTY_FRAME; + dev_dbg(&port->dev, "%s - tty_flag = %d\n", __func__, tty_flag); + + /* overrun is special, not associated with a char */ + if (line_status & UART_OVERRUN_ERROR) + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + + if (port->port.console && port->sysrq) { + for (i = 0; i < urb->actual_length; ++i) + if (!usb_serial_handle_sysrq_char(port, data[i])) + tty_insert_flip_char(&port->port, data[i], + tty_flag); + } else { + tty_insert_flip_string_fixed_flag(&port->port, data, tty_flag, + urb->actual_length); + } + + tty_flip_buffer_push(&port->port); +} + +static int set_control_lines(struct usb_device *dev, u8 value) +{ + /* FIXME - Stubbed out for now */ + return 0; +} + +static void f81232_break_ctl(struct tty_struct *tty, int break_state) +{ + /* FIXME - Stubbed out for now */ + + /* + * break_state = -1 to turn on break, and 0 to turn off break + * see drivers/char/tty_io.c to see it used. + * last_set_data_urb_value NEVER has the break bit set in it. + */ +} + +static void f81232_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + /* FIXME - Stubbed out for now */ + + /* Don't change anything if nothing has changed */ + if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) + return; + + /* Do the real work here... */ + if (old_termios) + tty_termios_copy_hw(&tty->termios, old_termios); +} + +static int f81232_tiocmget(struct tty_struct *tty) +{ + /* FIXME - Stubbed out for now */ + return 0; +} + +static int f81232_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + /* FIXME - Stubbed out for now */ + return 0; +} + +static int f81232_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int result; + + /* Setup termios */ + if (tty) + f81232_set_termios(tty, port, NULL); + + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "%s - failed submitting interrupt urb," + " error %d\n", __func__, result); + return result; + } + + result = usb_serial_generic_open(tty, port); + if (result) { + usb_kill_urb(port->interrupt_in_urb); + return result; + } + + port->port.drain_delay = 256; + return 0; +} + +static void f81232_close(struct usb_serial_port *port) +{ + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); +} + +static void f81232_dtr_rts(struct usb_serial_port *port, int on) +{ + struct f81232_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 control; + + spin_lock_irqsave(&priv->lock, flags); + /* Change DTR and RTS */ + if (on) + priv->line_control |= (CONTROL_DTR | CONTROL_RTS); + else + priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + set_control_lines(port->serial->dev, control); +} + +static int f81232_carrier_raised(struct usb_serial_port *port) +{ + struct f81232_private *priv = usb_get_serial_port_data(port); + if (priv->line_status & UART_DCD) + return 1; + return 0; +} + +static int f81232_tiocmiwait(struct tty_struct *tty, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct f81232_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int prevstatus; + unsigned int status; + unsigned int changed; + + spin_lock_irqsave(&priv->lock, flags); + prevstatus = priv->line_status; + spin_unlock_irqrestore(&priv->lock, flags); + + while (1) { + interruptible_sleep_on(&port->port.delta_msr_wait); + /* see if a signal did it */ + if (signal_pending(current)) + return -ERESTARTSYS; + + if (port->serial->disconnected) + return -EIO; + + spin_lock_irqsave(&priv->lock, flags); + status = priv->line_status; + spin_unlock_irqrestore(&priv->lock, flags); + + changed = prevstatus ^ status; + + if (((arg & TIOCM_RNG) && (changed & UART_RING)) || + ((arg & TIOCM_DSR) && (changed & UART_DSR)) || + ((arg & TIOCM_CD) && (changed & UART_DCD)) || + ((arg & TIOCM_CTS) && (changed & UART_CTS))) { + return 0; + } + prevstatus = status; + } + /* NOTREACHED */ + return 0; +} + +static int f81232_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct serial_struct ser; + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s (%d) cmd = 0x%04x\n", __func__, + port->number, cmd); + + switch (cmd) { + case TIOCGSERIAL: + memset(&ser, 0, sizeof ser); + ser.type = PORT_16654; + ser.line = port->serial->minor; + ser.port = port->number; + ser.baud_base = 460800; + + if (copy_to_user((void __user *)arg, &ser, sizeof ser)) + return -EFAULT; + + return 0; + default: + dev_dbg(&port->dev, "%s not supported = 0x%04x\n", + __func__, cmd); + break; + } + return -ENOIOCTLCMD; +} + +static int f81232_port_probe(struct usb_serial_port *port) +{ + struct f81232_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int f81232_port_remove(struct usb_serial_port *port) +{ + struct f81232_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static struct usb_serial_driver f81232_device = { + .driver = { + .owner = THIS_MODULE, + .name = "f81232", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_in_size = 256, + .bulk_out_size = 256, + .open = f81232_open, + .close = f81232_close, + .dtr_rts = f81232_dtr_rts, + .carrier_raised = f81232_carrier_raised, + .ioctl = f81232_ioctl, + .break_ctl = f81232_break_ctl, + .set_termios = f81232_set_termios, + .tiocmget = f81232_tiocmget, + .tiocmset = f81232_tiocmset, + .tiocmiwait = f81232_tiocmiwait, + .process_read_urb = f81232_process_read_urb, + .read_int_callback = f81232_read_int_callback, + .port_probe = f81232_port_probe, + .port_remove = f81232_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &f81232_device, + NULL, +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION("Fintek F81232 USB to serial adaptor driver"); +MODULE_AUTHOR("Greg Kroah-Hartman +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ftdi_sio.h" +#include "ftdi_sio_ids.h" + +#define DRIVER_AUTHOR "Greg Kroah-Hartman , Bill Ryder , Kuba Ober , Andreas Mohr, Johan Hovold " +#define DRIVER_DESC "USB FTDI Serial Converters Driver" + +static __u16 vendor = FTDI_VID; +static __u16 product; + +struct ftdi_private { + enum ftdi_chip_type chip_type; + /* type of device, either SIO or FT8U232AM */ + int baud_base; /* baud base clock for divisor setting */ + int custom_divisor; /* custom_divisor kludge, this is for + baud_base (different from what goes to the + chip!) */ + __u16 last_set_data_urb_value ; + /* the last data state set - needed for doing + * a break + */ + int flags; /* some ASYNC_xxxx flags are supported */ + unsigned long last_dtr_rts; /* saved modem control outputs */ + char prev_status; /* Used for TIOCMIWAIT */ + char transmit_empty; /* If transmitter is empty or not */ + __u16 interface; /* FT2232C, FT2232H or FT4232H port interface + (0 for FT232/245) */ + + speed_t force_baud; /* if non-zero, force the baud rate to + this value */ + int force_rtscts; /* if non-zero, force RTS-CTS to always + be enabled */ + + unsigned int latency; /* latency setting in use */ + unsigned short max_packet_size; + struct mutex cfg_lock; /* Avoid mess by parallel calls of config ioctl() and change_speed() */ +}; + +/* struct ftdi_sio_quirk is used by devices requiring special attention. */ +struct ftdi_sio_quirk { + int (*probe)(struct usb_serial *); + /* Special settings for probed ports. */ + void (*port_probe)(struct ftdi_private *); +}; + +static int ftdi_jtag_probe(struct usb_serial *serial); +static int ftdi_mtxorb_hack_setup(struct usb_serial *serial); +static int ftdi_NDI_device_setup(struct usb_serial *serial); +static int ftdi_stmclite_probe(struct usb_serial *serial); +static int ftdi_8u2232c_probe(struct usb_serial *serial); +static void ftdi_USB_UIRT_setup(struct ftdi_private *priv); +static void ftdi_HE_TIRA1_setup(struct ftdi_private *priv); + +static struct ftdi_sio_quirk ftdi_jtag_quirk = { + .probe = ftdi_jtag_probe, +}; + +static struct ftdi_sio_quirk ftdi_mtxorb_hack_quirk = { + .probe = ftdi_mtxorb_hack_setup, +}; + +static struct ftdi_sio_quirk ftdi_NDI_device_quirk = { + .probe = ftdi_NDI_device_setup, +}; + +static struct ftdi_sio_quirk ftdi_USB_UIRT_quirk = { + .port_probe = ftdi_USB_UIRT_setup, +}; + +static struct ftdi_sio_quirk ftdi_HE_TIRA1_quirk = { + .port_probe = ftdi_HE_TIRA1_setup, +}; + +static struct ftdi_sio_quirk ftdi_stmclite_quirk = { + .probe = ftdi_stmclite_probe, +}; + +static struct ftdi_sio_quirk ftdi_8u2232c_quirk = { + .probe = ftdi_8u2232c_probe, +}; + +/* + * The 8U232AM has the same API as the sio except for: + * - it can support MUCH higher baudrates; up to: + * o 921600 for RS232 and 2000000 for RS422/485 at 48MHz + * o 230400 at 12MHz + * so .. 8U232AM's baudrate setting codes are different + * - it has a two byte status code. + * - it returns characters every 16ms (the FTDI does it every 40ms) + * + * the bcdDevice value is used to differentiate FT232BM and FT245BM from + * the earlier FT8U232AM and FT8U232BM. For now, include all known VID/PID + * combinations in both tables. + * FIXME: perhaps bcdDevice can also identify 12MHz FT8U232AM devices, + * but I don't know if those ever went into mass production. [Ian Abbott] + */ + + + +/* + * Device ID not listed? Test via module params product/vendor or + * /sys/bus/usb/ftdi_sio/new_id, then send patch/report! + */ +static struct usb_device_id id_table_combined [] = { + { USB_DEVICE(FTDI_VID, FTDI_BRICK_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CTI_MINI_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CTI_NANO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_AMC232_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CANUSB_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CANDAPTER_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_BM_ATOM_NANO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NXTCAM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_EV3CON_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_3_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_4_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_5_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_6_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_7_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USINT_CAT_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USINT_WKEY_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USINT_RS232_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ACTZWAVE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IRTRANS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IPLUS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IPLUS2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DMX4ALL) }, + { USB_DEVICE(FTDI_VID, FTDI_SIO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_8U232AM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_8U232AM_ALT_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_232RL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) , + .driver_info = (kernel_ulong_t)&ftdi_8u2232c_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_4232H_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_232H_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FTX_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MICRO_CHAMELEON_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_SNIFFER_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_THROTTLE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GATEWAY_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GBM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GBM_BOOST_PID) }, + { USB_DEVICE(NEWPORT_VID, NEWPORT_AGILIS_PID) }, + { USB_DEVICE(NEWPORT_VID, NEWPORT_CONEX_CC_PID) }, + { USB_DEVICE(NEWPORT_VID, NEWPORT_CONEX_AGP_PID) }, + { USB_DEVICE(INTERBIOMETRICS_VID, INTERBIOMETRICS_IOBOARD_PID) }, + { USB_DEVICE(INTERBIOMETRICS_VID, INTERBIOMETRICS_MINI_IOBOARD_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SPROG_II) }, + { USB_DEVICE(FTDI_VID, FTDI_TAGSYS_LP101_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TAGSYS_P200X_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_LENZ_LIUSB_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_632_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_634_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_547_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_633_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_631_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_635_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_640_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_642_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DSS20_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_URBAN_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_URBAN_1_PID) }, + { USB_DEVICE(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_VNHCPCUSB_D_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_3_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_4_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_5_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_6_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_R2000KU_TRUE_RNG) }, + { USB_DEVICE(FTDI_VID, FTDI_VARDAAN_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0100_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0101_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0102_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0103_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0104_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0105_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0106_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0107_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0108_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0109_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0110_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0111_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0112_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0113_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0114_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0115_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0116_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0117_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0118_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0119_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0120_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0121_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0122_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0123_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0124_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0125_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0126_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0127_PID), + .driver_info = (kernel_ulong_t)&ftdi_mtxorb_hack_quirk }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0128_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0129_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012C_PID), + .driver_info = (kernel_ulong_t)&ftdi_mtxorb_hack_quirk }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0130_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0131_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0132_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0133_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0134_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0135_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0136_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0137_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0138_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0139_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0140_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0141_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0142_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0143_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0144_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0145_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0146_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0147_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0148_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0149_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0150_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0151_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0152_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0153_PID), + .driver_info = (kernel_ulong_t)&ftdi_mtxorb_hack_quirk }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0154_PID), + .driver_info = (kernel_ulong_t)&ftdi_mtxorb_hack_quirk }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0155_PID), + .driver_info = (kernel_ulong_t)&ftdi_mtxorb_hack_quirk }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0156_PID), + .driver_info = (kernel_ulong_t)&ftdi_mtxorb_hack_quirk }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0157_PID), + .driver_info = (kernel_ulong_t)&ftdi_mtxorb_hack_quirk }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0158_PID), + .driver_info = (kernel_ulong_t)&ftdi_mtxorb_hack_quirk }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0159_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0160_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0161_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0162_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0163_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0164_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0165_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0166_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0167_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0168_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0169_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0170_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0171_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0172_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0173_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0174_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0175_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0176_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0177_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0178_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0179_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0180_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0181_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0182_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0183_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0184_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0185_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0186_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0187_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0188_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0189_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0190_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0191_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0192_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0193_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0194_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0195_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0196_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0197_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0198_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0199_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01ED_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_4701_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9300_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9301_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9302_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9303_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9304_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9305_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9306_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9307_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9308_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9309_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_930F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9310_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9311_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9312_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9313_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9314_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9315_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9316_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9317_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9318_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_9319_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_931F_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PERLE_ULTRAPORT_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PIEGROUP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TNC_X_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USBX_707_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2101_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2102_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2103_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2104_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2106_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_8_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_8_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_8_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_4_PID) }, + { USB_DEVICE(IDTECH_VID, IDTECH_IDT1221U_PID) }, + { USB_DEVICE(OCT_VID, OCT_US101_PID) }, + { USB_DEVICE(OCT_VID, OCT_DK201_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_HE_TIRA1_PID), + .driver_info = (kernel_ulong_t)&ftdi_HE_TIRA1_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID), + .driver_info = (kernel_ulong_t)&ftdi_USB_UIRT_quirk }, + { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_1) }, + { USB_DEVICE(FTDI_VID, PROTEGO_R2X0) }, + { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_3) }, + { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_4) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E808_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E809_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80A_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80B_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80D_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80E_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80F_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E888_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E889_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88A_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88B_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88D_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88E_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88F_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UO100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UM100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UR100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_ALC8500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PYRAMID_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1000PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_US485_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PICPRO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PCMCIA_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PK1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_RS232MON_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_APP70_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PEDO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PROD_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TAVIR_STK500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TIAO_UMPA_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONLXM_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + /* + * ELV devices: + */ + { USB_DEVICE(FTDI_ELV_VID, FTDI_ELV_WS300_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_USR_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_MSM1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_KL100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS550_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_EC3000_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS888_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_TWS550_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FEM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_CLI7000_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_PPS7330_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_TFM100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UDF77_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UIO88_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UAD8_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UDA7_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_USI2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_T1100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_PCD200_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_ULA200_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_CSI8_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_EM1000DL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_PCK100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_RFP500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FS20SIG_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UTP8_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS300PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS444PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1300PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_EM1010PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_HS485_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UMS100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_TFD128_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FM3RX_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS777_PID) }, + { USB_DEVICE(FTDI_VID, LINX_SDMUSBQSS_PID) }, + { USB_DEVICE(FTDI_VID, LINX_MASTERDEVEL2_PID) }, + { USB_DEVICE(FTDI_VID, LINX_FUTURE_0_PID) }, + { USB_DEVICE(FTDI_VID, LINX_FUTURE_1_PID) }, + { USB_DEVICE(FTDI_VID, LINX_FUTURE_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU20_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU40_1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSMACHX_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSLOAD_N_GO_3_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU64_4_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSPRIME8_5_PID) }, + { USB_DEVICE(FTDI_VID, INSIDE_ACCESSO) }, + { USB_DEVICE(INTREPID_VID, INTREPID_VALUECAN_PID) }, + { USB_DEVICE(INTREPID_VID, INTREPID_NEOVI_PID) }, + { USB_DEVICE(FALCOM_VID, FALCOM_TWIST_PID) }, + { USB_DEVICE(FALCOM_VID, FALCOM_SAMBA_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SUUNTO_SPORTS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OCEANIC_PID) }, + { USB_DEVICE(TTI_VID, TTI_QL355P_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_RM_CANVIEW_PID) }, + { USB_DEVICE(ACTON_VID, ACTON_SPECTRAPRO_PID) }, + { USB_DEVICE(CONTEC_VID, CONTEC_COM1USBH_PID) }, + { USB_DEVICE(MITSUBISHI_VID, MITSUBISHI_FXUSB_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USO9ML2_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOPTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USPTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USO9ML2DR_2_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USO9ML2DR_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOPTL4DR2_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOPTL4DR_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_485USB9F_2W_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_485USB9F_4W_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_232USB9M_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_485USBTB_2W_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_485USBTB_4W_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_TTL5USB9M_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_TTL3USB9M_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_ZZ_PROG1_USB_PID) }, + { USB_DEVICE(FTDI_VID, EVER_ECO_PRO_CDS) }, + { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_3_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_0_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_1_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_2_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_3_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_4_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_5_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_6_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_7_PID) }, + { USB_DEVICE(XSENS_VID, XSENS_AWINDA_DONGLE_PID) }, + { USB_DEVICE(XSENS_VID, XSENS_AWINDA_STATION_PID) }, + { USB_DEVICE(XSENS_VID, XSENS_CONVERTER_PID) }, + { USB_DEVICE(XSENS_VID, XSENS_MTW_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OMNI1509) }, + { USB_DEVICE(MOBILITY_VID, MOBILITY_USB_SERIAL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ACTIVE_ROBOTS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_KW_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_YS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y6_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y8_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_IC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_DB9_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_RS232_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y9_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TERATRONIK_VCP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TERATRONIK_D2XX_PID) }, + { USB_DEVICE(EVOLUTION_VID, EVOLUTION_ER1_PID) }, + { USB_DEVICE(EVOLUTION_VID, EVO_HYBRID_PID) }, + { USB_DEVICE(EVOLUTION_VID, EVO_RCM4_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ARTEMIS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16HR_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16HRC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16IC_PID) }, + { USB_DEVICE(KOBIL_VID, KOBIL_CONV_B1_PID) }, + { USB_DEVICE(KOBIL_VID, KOBIL_CONV_KAAN_PID) }, + { USB_DEVICE(POSIFLEX_VID, POSIFLEX_PP7000_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TTUSB_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ECLO_COM_1WIRE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_WESTREX_MODEL_777_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_WESTREX_MODEL_8900F_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PCDJ_DAC2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_RRCIRKITS_LOCOBUFFER_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ASK_RDR400_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NZR_SEM_USB_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_1_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_OPC_U_UC_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C1_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C2_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2D_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VT_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VR_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVT_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVR_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVT_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVR_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ACG_HFDUAL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_YEI_SERVOCENTER31_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_THORLABS_PID) }, + { USB_DEVICE(TESTO_VID, TESTO_1_PID) }, + { USB_DEVICE(TESTO_VID, TESTO_3_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GAMMA_SCOUT_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13M_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13S_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13U_PID) }, + { USB_DEVICE(ELEKTOR_VID, ELEKTOR_FT323R_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_HUC_PID), + .driver_info = (kernel_ulong_t)&ftdi_NDI_device_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_SPECTRA_SCU_PID), + .driver_info = (kernel_ulong_t)&ftdi_NDI_device_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_2_PID), + .driver_info = (kernel_ulong_t)&ftdi_NDI_device_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_3_PID), + .driver_info = (kernel_ulong_t)&ftdi_NDI_device_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_AURORA_SCU_PID), + .driver_info = (kernel_ulong_t)&ftdi_NDI_device_quirk }, + { USB_DEVICE(TELLDUS_VID, TELLDUS_TELLSTICK_PID) }, + { USB_DEVICE(NOVITUS_VID, NOVITUS_BONO_E_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_S03_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_59_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_57A_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_57B_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_29A_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_29B_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_29F_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_62B_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_S01_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_63_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_29C_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_81B_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_82B_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_K5D_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_K4Y_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_K5G_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_S05_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_60_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_61_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_62_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_63B_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_64_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_65_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_92_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_92D_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_W5R_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_A5R_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_USB_PW1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MAXSTREAM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PHI_FISCO_PID) }, + { USB_DEVICE(TML_VID, TML_USB_SERIAL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELSTER_UNICOM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PROPOX_JTAGCABLEII_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PROPOX_ISPCABLEIII_PID) }, + { USB_DEVICE(OLIMEX_VID, OLIMEX_ARM_USB_OCD_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(OLIMEX_VID, OLIMEX_ARM_USB_OCD_H_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FIC_VID, FIC_NEO1973_DEBUG_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_OOCDLINK_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, LMI_LM3S_DEVEL_BOARD_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_TURTELIZER_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_USB60F) }, + { USB_DEVICE(FTDI_VID, FTDI_REU_TINY_PID) }, + + /* Papouch devices based on FTDI chip */ + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AP485_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB422_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485_2_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AP485_2_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB422_2_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485S_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485C_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_LEC_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB232_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_TMU_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_IRAMP_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_DRAK5_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO8x8_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO4x4_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO2x2_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO10x1_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO30x3_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO60x3_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO2x16_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO3x32_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_DRAK6_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_UPSUSB_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_MU_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SIMUKEY_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AD4USB_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_GMUX_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_GMSR_PID) }, + + { USB_DEVICE(FTDI_VID, FTDI_DOMINTELL_DGQG_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DOMINTELL_DUSB_PID) }, + { USB_DEVICE(ALTI2_VID, ALTI2_N3_PID) }, + { USB_DEVICE(FTDI_VID, DIEBOLD_BCS_SE923_PID) }, + { USB_DEVICE(ATMEL_VID, STK541_PID) }, + { USB_DEVICE(DE_VID, STB_PID) }, + { USB_DEVICE(DE_VID, WHT_PID) }, + { USB_DEVICE(ADI_VID, ADI_GNICE_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(ADI_VID, ADI_GNICEPLUS_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_AND_INTERFACE_INFO(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID, + USB_CLASS_VENDOR_SPEC, + USB_SUBCLASS_VENDOR_SPEC, 0x00) }, + { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) }, + { USB_DEVICE(MARVELL_VID, MARVELL_SHEEVAPLUG_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(LARSENBRUSGAARD_VID, LB_ALTITRACK_PID) }, + { USB_DEVICE(GN_OTOMETRICS_VID, AURICAL_USB_PID) }, + { USB_DEVICE(FTDI_VID, PI_C865_PID) }, + { USB_DEVICE(FTDI_VID, PI_C857_PID) }, + { USB_DEVICE(PI_VID, PI_C866_PID) }, + { USB_DEVICE(PI_VID, PI_C663_PID) }, + { USB_DEVICE(PI_VID, PI_C725_PID) }, + { USB_DEVICE(PI_VID, PI_E517_PID) }, + { USB_DEVICE(PI_VID, PI_C863_PID) }, + { USB_DEVICE(PI_VID, PI_E861_PID) }, + { USB_DEVICE(PI_VID, PI_C867_PID) }, + { USB_DEVICE(PI_VID, PI_E609_PID) }, + { USB_DEVICE(PI_VID, PI_E709_PID) }, + { USB_DEVICE(PI_VID, PI_100F_PID) }, + { USB_DEVICE(PI_VID, PI_1011_PID) }, + { USB_DEVICE(PI_VID, PI_1012_PID) }, + { USB_DEVICE(PI_VID, PI_1013_PID) }, + { USB_DEVICE(PI_VID, PI_1014_PID) }, + { USB_DEVICE(PI_VID, PI_1015_PID) }, + { USB_DEVICE(PI_VID, PI_1016_PID) }, + { USB_DEVICE(KONDO_VID, KONDO_USB_SERIAL_PID) }, + { USB_DEVICE(BAYER_VID, BAYER_CONTOUR_CABLE_PID) }, + { USB_DEVICE(FTDI_VID, MARVELL_OPENRD_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, TI_XDS100V2_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, HAMEG_HO820_PID) }, + { USB_DEVICE(FTDI_VID, HAMEG_HO720_PID) }, + { USB_DEVICE(FTDI_VID, HAMEG_HO730_PID) }, + { USB_DEVICE(FTDI_VID, HAMEG_HO870_PID) }, + { USB_DEVICE(FTDI_VID, MJSG_GENERIC_PID) }, + { USB_DEVICE(FTDI_VID, MJSG_SR_RADIO_PID) }, + { USB_DEVICE(FTDI_VID, MJSG_HD_RADIO_PID) }, + { USB_DEVICE(FTDI_VID, MJSG_XM_RADIO_PID) }, + { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_ST_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SLITE_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH2_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH4_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, SEGWAY_RMP200_PID) }, + { USB_DEVICE(FTDI_VID, ACCESIO_COM4SM_PID) }, + { USB_DEVICE(IONICS_VID, IONICS_PLUGCOMPUTER_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_24_MASTER_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_PC_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_USB_DMX_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MIDI_TIMECODE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MINI_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MAXI_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MEDIA_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LOGBOOKML_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LS_LOGBOOK_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_HS_LOGBOOK_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CINTERION_MC55I_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DOTEC_PID) }, + { USB_DEVICE(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(ST_VID, ST_STMCLT_2232_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(ST_VID, ST_STMCLT_4232_PID), + .driver_info = (kernel_ulong_t)&ftdi_stmclite_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_RF_R106) }, + { USB_DEVICE(FTDI_VID, FTDI_DISTORTEC_JTAG_LOCK_PICK_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_LUMEL_PD12_PID) }, + /* Crucible Devices */ + { USB_DEVICE(FTDI_VID, FTDI_CT_COMET_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_Z3X_PID) }, + /* Cressi Devices */ + { USB_DEVICE(FTDI_VID, FTDI_CRESSI_PID) }, + /* Brainboxes Devices */ + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_VX_001_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_VX_012_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_VX_023_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_VX_034_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_101_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_1_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_2_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_3_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_4_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_5_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_6_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_7_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_160_8_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_257_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_279_1_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_279_2_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_279_3_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_279_4_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_313_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_324_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_346_1_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_346_2_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_357_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_606_1_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_606_2_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_606_3_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_701_1_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_701_2_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_842_1_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_842_2_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_842_3_PID) }, + { USB_DEVICE(BRAINBOXES_VID, BRAINBOXES_US_842_4_PID) }, + /* ekey Devices */ + { USB_DEVICE(FTDI_VID, FTDI_EKEY_CONV_USB_PID) }, + /* Infineon Devices */ + { USB_DEVICE_INTERFACE_NUMBER(INFINEON_VID, INFINEON_TRIBOARD_PID, 1) }, + { }, /* Optional parameter entry */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +static const char *ftdi_chip_name[] = { + [SIO] = "SIO", /* the serial part of FT8U100AX */ + [FT8U232AM] = "FT8U232AM", + [FT232BM] = "FT232BM", + [FT2232C] = "FT2232C", + [FT232RL] = "FT232RL", + [FT2232H] = "FT2232H", + [FT4232H] = "FT4232H", + [FT232H] = "FT232H", + [FTX] = "FT-X" +}; + + +/* Used for TIOCMIWAIT */ +#define FTDI_STATUS_B0_MASK (FTDI_RS0_CTS | FTDI_RS0_DSR | FTDI_RS0_RI | FTDI_RS0_RLSD) +#define FTDI_STATUS_B1_MASK (FTDI_RS_BI) +/* End TIOCMIWAIT */ + +#define FTDI_IMPL_ASYNC_FLAGS = (ASYNC_SPD_HI | ASYNC_SPD_VHI \ + | ASYNC_SPD_CUST | ASYNC_SPD_SHI | ASYNC_SPD_WARP) + +/* function prototypes for a FTDI serial converter */ +static int ftdi_sio_probe(struct usb_serial *serial, + const struct usb_device_id *id); +static int ftdi_sio_port_probe(struct usb_serial_port *port); +static int ftdi_sio_port_remove(struct usb_serial_port *port); +static int ftdi_open(struct tty_struct *tty, struct usb_serial_port *port); +static void ftdi_dtr_rts(struct usb_serial_port *port, int on); +static void ftdi_process_read_urb(struct urb *urb); +static int ftdi_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size); +static void ftdi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old); +static int ftdi_tiocmget(struct tty_struct *tty); +static int ftdi_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static int ftdi_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg); +static void ftdi_break_ctl(struct tty_struct *tty, int break_state); +static bool ftdi_tx_empty(struct usb_serial_port *port); +static int ftdi_get_modem_status(struct usb_serial_port *port, + unsigned char status[2]); + +static unsigned short int ftdi_232am_baud_base_to_divisor(int baud, int base); +static unsigned short int ftdi_232am_baud_to_divisor(int baud); +static __u32 ftdi_232bm_baud_base_to_divisor(int baud, int base); +static __u32 ftdi_232bm_baud_to_divisor(int baud); +static __u32 ftdi_2232h_baud_base_to_divisor(int baud, int base); +static __u32 ftdi_2232h_baud_to_divisor(int baud); + +static struct usb_serial_driver ftdi_sio_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ftdi_sio", + }, + .description = "FTDI USB Serial Device", + .id_table = id_table_combined, + .num_ports = 1, + .bulk_in_size = 512, + .bulk_out_size = 256, + .probe = ftdi_sio_probe, + .port_probe = ftdi_sio_port_probe, + .port_remove = ftdi_sio_port_remove, + .open = ftdi_open, + .dtr_rts = ftdi_dtr_rts, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .process_read_urb = ftdi_process_read_urb, + .prepare_write_buffer = ftdi_prepare_write_buffer, + .tiocmget = ftdi_tiocmget, + .tiocmset = ftdi_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .ioctl = ftdi_ioctl, + .set_termios = ftdi_set_termios, + .break_ctl = ftdi_break_ctl, + .tx_empty = ftdi_tx_empty, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ftdi_sio_device, NULL +}; + + +#define WDR_TIMEOUT 5000 /* default urb timeout */ +#define WDR_SHORT_TIMEOUT 1000 /* shorter urb timeout */ + +/* High and low are for DTR, RTS etc etc */ +#define HIGH 1 +#define LOW 0 + +/* + * *************************************************************************** + * Utility functions + * *************************************************************************** + */ + +static unsigned short int ftdi_232am_baud_base_to_divisor(int baud, int base) +{ + unsigned short int divisor; + /* divisor shifted 3 bits to the left */ + int divisor3 = base / 2 / baud; + if ((divisor3 & 0x7) == 7) + divisor3++; /* round x.7/8 up to x+1 */ + divisor = divisor3 >> 3; + divisor3 &= 0x7; + if (divisor3 == 1) + divisor |= 0xc000; + else if (divisor3 >= 4) + divisor |= 0x4000; + else if (divisor3 != 0) + divisor |= 0x8000; + else if (divisor == 1) + divisor = 0; /* special case for maximum baud rate */ + return divisor; +} + +static unsigned short int ftdi_232am_baud_to_divisor(int baud) +{ + return ftdi_232am_baud_base_to_divisor(baud, 48000000); +} + +static __u32 ftdi_232bm_baud_base_to_divisor(int baud, int base) +{ + static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; + __u32 divisor; + /* divisor shifted 3 bits to the left */ + int divisor3 = base / 2 / baud; + divisor = divisor3 >> 3; + divisor |= (__u32)divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) + divisor = 0; + else if (divisor == 0x4001) + divisor = 1; + return divisor; +} + +static __u32 ftdi_232bm_baud_to_divisor(int baud) +{ + return ftdi_232bm_baud_base_to_divisor(baud, 48000000); +} + +static __u32 ftdi_2232h_baud_base_to_divisor(int baud, int base) +{ + static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; + __u32 divisor; + int divisor3; + + /* hi-speed baud rate is 10-bit sampling instead of 16-bit */ + divisor3 = base * 8 / (baud * 10); + + divisor = divisor3 >> 3; + divisor |= (__u32)divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) + divisor = 0; + else if (divisor == 0x4001) + divisor = 1; + /* + * Set this bit to turn off a divide by 2.5 on baud rate generator + * This enables baud rates up to 12Mbaud but cannot reach below 1200 + * baud with this bit set + */ + divisor |= 0x00020000; + return divisor; +} + +static __u32 ftdi_2232h_baud_to_divisor(int baud) +{ + return ftdi_2232h_baud_base_to_divisor(baud, 120000000); +} + +#define set_mctrl(port, set) update_mctrl((port), (set), 0) +#define clear_mctrl(port, clear) update_mctrl((port), 0, (clear)) + +static int update_mctrl(struct usb_serial_port *port, unsigned int set, + unsigned int clear) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + unsigned urb_value; + int rv; + + if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0) { + dev_dbg(dev, "%s - DTR|RTS not being set|cleared\n", __func__); + return 0; /* no change */ + } + + clear &= ~set; /* 'set' takes precedence over 'clear' */ + urb_value = 0; + if (clear & TIOCM_DTR) + urb_value |= FTDI_SIO_SET_DTR_LOW; + if (clear & TIOCM_RTS) + urb_value |= FTDI_SIO_SET_RTS_LOW; + if (set & TIOCM_DTR) + urb_value |= FTDI_SIO_SET_DTR_HIGH; + if (set & TIOCM_RTS) + urb_value |= FTDI_SIO_SET_RTS_HIGH; + rv = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + FTDI_SIO_SET_MODEM_CTRL_REQUEST, + FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE, + urb_value, priv->interface, + NULL, 0, WDR_TIMEOUT); + if (rv < 0) { + dev_dbg(dev, "%s Error from MODEM_CTRL urb: DTR %s, RTS %s\n", + __func__, + (set & TIOCM_DTR) ? "HIGH" : (clear & TIOCM_DTR) ? "LOW" : "unchanged", + (set & TIOCM_RTS) ? "HIGH" : (clear & TIOCM_RTS) ? "LOW" : "unchanged"); + rv = usb_translate_errors(rv); + } else { + dev_dbg(dev, "%s - DTR %s, RTS %s\n", __func__, + (set & TIOCM_DTR) ? "HIGH" : (clear & TIOCM_DTR) ? "LOW" : "unchanged", + (set & TIOCM_RTS) ? "HIGH" : (clear & TIOCM_RTS) ? "LOW" : "unchanged"); + /* FIXME: locking on last_dtr_rts */ + priv->last_dtr_rts = (priv->last_dtr_rts & ~clear) | set; + } + return rv; +} + + +static __u32 get_ftdi_divisor(struct tty_struct *tty, + struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + __u32 div_value = 0; + int div_okay = 1; + int baud; + + /* + * The logic involved in setting the baudrate can be cleanly split into + * 3 steps. + * 1. Standard baud rates are set in tty->termios->c_cflag + * 2. If these are not enough, you can set any speed using alt_speed as + * follows: + * - set tty->termios->c_cflag speed to B38400 + * - set your real speed in tty->alt_speed; it gets ignored when + * alt_speed==0, (or) + * - call TIOCSSERIAL ioctl with (struct serial_struct) set as + * follows: + * flags & ASYNC_SPD_MASK == ASYNC_SPD_[HI, VHI, SHI, WARP], + * this just sets alt_speed to (HI: 57600, VHI: 115200, + * SHI: 230400, WARP: 460800) + * ** Steps 1, 2 are done courtesy of tty_get_baud_rate + * 3. You can also set baud rate by setting custom divisor as follows + * - set tty->termios->c_cflag speed to B38400 + * - call TIOCSSERIAL ioctl with (struct serial_struct) set as + * follows: + * o flags & ASYNC_SPD_MASK == ASYNC_SPD_CUST + * o custom_divisor set to baud_base / your_new_baudrate + * ** Step 3 is done courtesy of code borrowed from serial.c + * I should really spend some time and separate + move this common + * code to serial.c, it is replicated in nearly every serial driver + * you see. + */ + + /* 1. Get the baud rate from the tty settings, this observes + alt_speed hack */ + + baud = tty_get_baud_rate(tty); + dev_dbg(dev, "%s - tty_get_baud_rate reports speed %d\n", __func__, baud); + + /* 2. Observe async-compatible custom_divisor hack, update baudrate + if needed */ + + if (baud == 38400 && + ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) && + (priv->custom_divisor)) { + baud = priv->baud_base / priv->custom_divisor; + dev_dbg(dev, "%s - custom divisor %d sets baud rate to %d\n", + __func__, priv->custom_divisor, baud); + } + + /* 3. Convert baudrate to device-specific divisor */ + + if (!baud) + baud = 9600; + switch (priv->chip_type) { + case SIO: /* SIO chip */ + switch (baud) { + case 300: div_value = ftdi_sio_b300; break; + case 600: div_value = ftdi_sio_b600; break; + case 1200: div_value = ftdi_sio_b1200; break; + case 2400: div_value = ftdi_sio_b2400; break; + case 4800: div_value = ftdi_sio_b4800; break; + case 9600: div_value = ftdi_sio_b9600; break; + case 19200: div_value = ftdi_sio_b19200; break; + case 38400: div_value = ftdi_sio_b38400; break; + case 57600: div_value = ftdi_sio_b57600; break; + case 115200: div_value = ftdi_sio_b115200; break; + } /* baud */ + if (div_value == 0) { + dev_dbg(dev, "%s - Baudrate (%d) requested is not supported\n", + __func__, baud); + div_value = ftdi_sio_b9600; + baud = 9600; + div_okay = 0; + } + break; + case FT8U232AM: /* 8U232AM chip */ + if (baud <= 3000000) { + div_value = ftdi_232am_baud_to_divisor(baud); + } else { + dev_dbg(dev, "%s - Baud rate too high!\n", __func__); + baud = 9600; + div_value = ftdi_232am_baud_to_divisor(9600); + div_okay = 0; + } + break; + case FT232BM: /* FT232BM chip */ + case FT2232C: /* FT2232C chip */ + case FT232RL: /* FT232RL chip */ + case FTX: /* FT-X series */ + if (baud <= 3000000) { + __u16 product_id = le16_to_cpu( + port->serial->dev->descriptor.idProduct); + if (((FTDI_NDI_HUC_PID == product_id) || + (FTDI_NDI_SPECTRA_SCU_PID == product_id) || + (FTDI_NDI_FUTURE_2_PID == product_id) || + (FTDI_NDI_FUTURE_3_PID == product_id) || + (FTDI_NDI_AURORA_SCU_PID == product_id)) && + (baud == 19200)) { + baud = 1200000; + } + div_value = ftdi_232bm_baud_to_divisor(baud); + } else { + dev_dbg(dev, "%s - Baud rate too high!\n", __func__); + div_value = ftdi_232bm_baud_to_divisor(9600); + div_okay = 0; + baud = 9600; + } + break; + case FT2232H: /* FT2232H chip */ + case FT4232H: /* FT4232H chip */ + case FT232H: /* FT232H chip */ + if ((baud <= 12000000) && (baud >= 1200)) { + div_value = ftdi_2232h_baud_to_divisor(baud); + } else if (baud < 1200) { + div_value = ftdi_232bm_baud_to_divisor(baud); + } else { + dev_dbg(dev, "%s - Baud rate too high!\n", __func__); + div_value = ftdi_232bm_baud_to_divisor(9600); + div_okay = 0; + baud = 9600; + } + break; + } /* priv->chip_type */ + + if (div_okay) { + dev_dbg(dev, "%s - Baud rate set to %d (divisor 0x%lX) on chip %s\n", + __func__, baud, (unsigned long)div_value, + ftdi_chip_name[priv->chip_type]); + } + + tty_encode_baud_rate(tty, baud, baud); + return div_value; +} + +static int change_speed(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + __u16 urb_value; + __u16 urb_index; + __u32 urb_index_value; + int rv; + + urb_index_value = get_ftdi_divisor(tty, port); + urb_value = (__u16)urb_index_value; + urb_index = (__u16)(urb_index_value >> 16); + if ((priv->chip_type == FT2232C) || (priv->chip_type == FT2232H) || + (priv->chip_type == FT4232H) || (priv->chip_type == FT232H)) { + /* Probably the BM type needs the MSB of the encoded fractional + * divider also moved like for the chips above. Any infos? */ + urb_index = (__u16)((urb_index << 8) | priv->interface); + } + + rv = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + FTDI_SIO_SET_BAUDRATE_REQUEST, + FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE, + urb_value, urb_index, + NULL, 0, WDR_SHORT_TIMEOUT); + return rv; +} + +static int write_latency_timer(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_device *udev = port->serial->dev; + int rv; + int l = priv->latency; + + if (priv->flags & ASYNC_LOW_LATENCY) + l = 1; + + dev_dbg(&port->dev, "%s: setting latency timer = %i\n", __func__, l); + + rv = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + FTDI_SIO_SET_LATENCY_TIMER_REQUEST, + FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE, + l, priv->interface, + NULL, 0, WDR_TIMEOUT); + if (rv < 0) + dev_err(&port->dev, "Unable to write latency timer: %i\n", rv); + return rv; +} + +static int read_latency_timer(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_device *udev = port->serial->dev; + unsigned char *buf; + int rv; + + buf = kmalloc(1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + rv = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + FTDI_SIO_GET_LATENCY_TIMER_REQUEST, + FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE, + 0, priv->interface, + buf, 1, WDR_TIMEOUT); + if (rv < 0) + dev_err(&port->dev, "Unable to read latency timer: %i\n", rv); + else + priv->latency = buf[0]; + + kfree(buf); + + return rv; +} + +static int get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *retinfo) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = priv->flags; + tmp.baud_base = priv->baud_base; + tmp.custom_divisor = priv->custom_divisor; + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int set_serial_info(struct tty_struct *tty, + struct usb_serial_port *port, struct serial_struct __user *newinfo) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct serial_struct new_serial; + struct ftdi_private old_priv; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + mutex_lock(&priv->cfg_lock); + old_priv = *priv; + + /* Do error checking and permission checking */ + + if (!capable(CAP_SYS_ADMIN)) { + if (((new_serial.flags & ~ASYNC_USR_MASK) != + (priv->flags & ~ASYNC_USR_MASK))) { + mutex_unlock(&priv->cfg_lock); + return -EPERM; + } + priv->flags = ((priv->flags & ~ASYNC_USR_MASK) | + (new_serial.flags & ASYNC_USR_MASK)); + priv->custom_divisor = new_serial.custom_divisor; + goto check_and_exit; + } + + if (new_serial.baud_base != priv->baud_base) { + mutex_unlock(&priv->cfg_lock); + return -EINVAL; + } + + /* Make the changes - these are privileged changes! */ + + priv->flags = ((priv->flags & ~ASYNC_FLAGS) | + (new_serial.flags & ASYNC_FLAGS)); + priv->custom_divisor = new_serial.custom_divisor; + + write_latency_timer(port); + +check_and_exit: + if ((old_priv.flags & ASYNC_SPD_MASK) != + (priv->flags & ASYNC_SPD_MASK)) { + if ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) + tty->alt_speed = 57600; + else if ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) + tty->alt_speed = 115200; + else if ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) + tty->alt_speed = 230400; + else if ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) + tty->alt_speed = 460800; + else + tty->alt_speed = 0; + } + if (((old_priv.flags & ASYNC_SPD_MASK) != + (priv->flags & ASYNC_SPD_MASK)) || + (((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) && + (old_priv.custom_divisor != priv->custom_divisor))) { + change_speed(tty, port); + mutex_unlock(&priv->cfg_lock); + } + else + mutex_unlock(&priv->cfg_lock); + return 0; +} + +static int get_lsr_info(struct usb_serial_port *port, + struct serial_struct __user *retinfo) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + unsigned int result = 0; + + if (!retinfo) + return -EFAULT; + + if (priv->transmit_empty) + result = TIOCSER_TEMT; + + if (copy_to_user(retinfo, &result, sizeof(unsigned int))) + return -EFAULT; + return 0; +} + + +/* Determine type of FTDI chip based on USB config and descriptor. */ +static void ftdi_determine_type(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + struct usb_device *udev = serial->dev; + unsigned version; + unsigned interfaces; + + /* Assume it is not the original SIO device for now. */ + priv->baud_base = 48000000 / 2; + + version = le16_to_cpu(udev->descriptor.bcdDevice); + interfaces = udev->actconfig->desc.bNumInterfaces; + dev_dbg(&port->dev, "%s: bcdDevice = 0x%x, bNumInterfaces = %u\n", __func__, + version, interfaces); + if (interfaces > 1) { + int inter; + + /* Multiple interfaces.*/ + if (version == 0x0800) { + priv->chip_type = FT4232H; + /* Hi-speed - baud clock runs at 120MHz */ + priv->baud_base = 120000000 / 2; + } else if (version == 0x0700) { + priv->chip_type = FT2232H; + /* Hi-speed - baud clock runs at 120MHz */ + priv->baud_base = 120000000 / 2; + } else + priv->chip_type = FT2232C; + + /* Determine interface code. */ + inter = serial->interface->altsetting->desc.bInterfaceNumber; + if (inter == 0) { + priv->interface = INTERFACE_A; + } else if (inter == 1) { + priv->interface = INTERFACE_B; + } else if (inter == 2) { + priv->interface = INTERFACE_C; + } else if (inter == 3) { + priv->interface = INTERFACE_D; + } + /* BM-type devices have a bug where bcdDevice gets set + * to 0x200 when iSerialNumber is 0. */ + if (version < 0x500) { + dev_dbg(&port->dev, + "%s: something fishy - bcdDevice too low for multi-interface device\n", + __func__); + } + } else if (version < 0x200) { + /* Old device. Assume it's the original SIO. */ + priv->chip_type = SIO; + priv->baud_base = 12000000 / 16; + } else if (version < 0x400) { + /* Assume it's an FT8U232AM (or FT8U245AM) */ + /* (It might be a BM because of the iSerialNumber bug, + * but it will still work as an AM device.) */ + priv->chip_type = FT8U232AM; + } else if (version < 0x600) { + /* Assume it's an FT232BM (or FT245BM) */ + priv->chip_type = FT232BM; + } else if (version < 0x900) { + /* Assume it's an FT232RL */ + priv->chip_type = FT232RL; + } else if (version < 0x1000) { + /* Assume it's an FT232H */ + priv->chip_type = FT232H; + } else { + /* Assume it's an FT-X series device */ + priv->chip_type = FTX; + } + + dev_info(&udev->dev, "Detected %s\n", ftdi_chip_name[priv->chip_type]); +} + + +/* Determine the maximum packet size for the device. This depends on the chip + * type and the USB host capabilities. The value should be obtained from the + * device descriptor as the chip will use the appropriate values for the host.*/ +static void ftdi_set_max_packet_size(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + struct usb_device *udev = serial->dev; + + struct usb_interface *interface = serial->interface; + struct usb_endpoint_descriptor *ep_desc; + + unsigned num_endpoints; + unsigned i; + + num_endpoints = interface->cur_altsetting->desc.bNumEndpoints; + dev_info(&udev->dev, "Number of endpoints %d\n", num_endpoints); + + if (!num_endpoints) + return; + + /* NOTE: some customers have programmed FT232R/FT245R devices + * with an endpoint size of 0 - not good. In this case, we + * want to override the endpoint descriptor setting and use a + * value of 64 for wMaxPacketSize */ + for (i = 0; i < num_endpoints; i++) { + dev_info(&udev->dev, "Endpoint %d MaxPacketSize %d\n", i+1, + interface->cur_altsetting->endpoint[i].desc.wMaxPacketSize); + ep_desc = &interface->cur_altsetting->endpoint[i].desc; + if (ep_desc->wMaxPacketSize == 0) { + ep_desc->wMaxPacketSize = cpu_to_le16(0x40); + dev_info(&udev->dev, "Overriding wMaxPacketSize on endpoint %d\n", i); + } + } + + /* set max packet size based on descriptor */ + priv->max_packet_size = usb_endpoint_maxp(ep_desc); + + dev_info(&udev->dev, "Setting MaxPacketSize %d\n", priv->max_packet_size); +} + + +/* + * *************************************************************************** + * Sysfs Attribute + * *************************************************************************** + */ + +static ssize_t show_latency_timer(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct ftdi_private *priv = usb_get_serial_port_data(port); + if (priv->flags & ASYNC_LOW_LATENCY) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "%i\n", priv->latency); +} + + +/* Write a new value of the latency timer, in units of milliseconds. */ +static ssize_t store_latency_timer(struct device *dev, + struct device_attribute *attr, const char *valbuf, + size_t count) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int v = simple_strtoul(valbuf, NULL, 10); + int rv; + + priv->latency = v; + rv = write_latency_timer(port); + if (rv < 0) + return -EIO; + return count; +} + +/* Write an event character directly to the FTDI register. The ASCII + value is in the low 8 bits, with the enable bit in the 9th bit. */ +static ssize_t store_event_char(struct device *dev, + struct device_attribute *attr, const char *valbuf, size_t count) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_device *udev = port->serial->dev; + int v = simple_strtoul(valbuf, NULL, 10); + int rv; + + dev_dbg(&port->dev, "%s: setting event char = %i\n", __func__, v); + + rv = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + FTDI_SIO_SET_EVENT_CHAR_REQUEST, + FTDI_SIO_SET_EVENT_CHAR_REQUEST_TYPE, + v, priv->interface, + NULL, 0, WDR_TIMEOUT); + if (rv < 0) { + dev_dbg(&port->dev, "Unable to write event character: %i\n", rv); + return -EIO; + } + + return count; +} + +static DEVICE_ATTR(latency_timer, S_IWUSR | S_IRUGO, show_latency_timer, + store_latency_timer); +static DEVICE_ATTR(event_char, S_IWUSR, NULL, store_event_char); + +static int create_sysfs_attrs(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + int retval = 0; + + /* XXX I've no idea if the original SIO supports the event_char + * sysfs parameter, so I'm playing it safe. */ + if (priv->chip_type != SIO) { + dev_dbg(&port->dev, "sysfs attributes for %s\n", ftdi_chip_name[priv->chip_type]); + retval = device_create_file(&port->dev, &dev_attr_event_char); + if ((!retval) && + (priv->chip_type == FT232BM || + priv->chip_type == FT2232C || + priv->chip_type == FT232RL || + priv->chip_type == FT2232H || + priv->chip_type == FT4232H || + priv->chip_type == FT232H || + priv->chip_type == FTX)) { + retval = device_create_file(&port->dev, + &dev_attr_latency_timer); + } + } + return retval; +} + +static void remove_sysfs_attrs(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + + /* XXX see create_sysfs_attrs */ + if (priv->chip_type != SIO) { + device_remove_file(&port->dev, &dev_attr_event_char); + if (priv->chip_type == FT232BM || + priv->chip_type == FT2232C || + priv->chip_type == FT232RL || + priv->chip_type == FT2232H || + priv->chip_type == FT4232H || + priv->chip_type == FT232H || + priv->chip_type == FTX) { + device_remove_file(&port->dev, &dev_attr_latency_timer); + } + } + +} + +/* + * *************************************************************************** + * FTDI driver specific functions + * *************************************************************************** + */ + +/* Probe function to check for special devices */ +static int ftdi_sio_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct ftdi_sio_quirk *quirk = + (struct ftdi_sio_quirk *)id->driver_info; + + if (quirk && quirk->probe) { + int ret = quirk->probe(serial); + if (ret != 0) + return ret; + } + + usb_set_serial_data(serial, (void *)id->driver_info); + + return 0; +} + +static int ftdi_sio_port_probe(struct usb_serial_port *port) +{ + struct ftdi_private *priv; + struct ftdi_sio_quirk *quirk = usb_get_serial_data(port->serial); + + + priv = kzalloc(sizeof(struct ftdi_private), GFP_KERNEL); + if (!priv) { + dev_err(&port->dev, "%s- kmalloc(%Zd) failed.\n", __func__, + sizeof(struct ftdi_private)); + return -ENOMEM; + } + + mutex_init(&priv->cfg_lock); + + priv->flags = ASYNC_LOW_LATENCY; + + if (quirk && quirk->port_probe) + quirk->port_probe(priv); + + usb_set_serial_port_data(port, priv); + + ftdi_determine_type(port); + ftdi_set_max_packet_size(port); + if (read_latency_timer(port) < 0) + priv->latency = 16; + write_latency_timer(port); + create_sysfs_attrs(port); + return 0; +} + +/* Setup for the USB-UIRT device, which requires hardwired + * baudrate (38400 gets mapped to 312500) */ +/* Called from usbserial:serial_probe */ +static void ftdi_USB_UIRT_setup(struct ftdi_private *priv) +{ + priv->flags |= ASYNC_SPD_CUST; + priv->custom_divisor = 77; + priv->force_baud = 38400; +} + +/* Setup for the HE-TIRA1 device, which requires hardwired + * baudrate (38400 gets mapped to 100000) and RTS-CTS enabled. */ + +static void ftdi_HE_TIRA1_setup(struct ftdi_private *priv) +{ + priv->flags |= ASYNC_SPD_CUST; + priv->custom_divisor = 240; + priv->force_baud = 38400; + priv->force_rtscts = 1; +} + +/* + * Module parameter to control latency timer for NDI FTDI-based USB devices. + * If this value is not set in /etc/modprobe.d/ its value will be set + * to 1ms. + */ +static int ndi_latency_timer = 1; + +/* Setup for the NDI FTDI-based USB devices, which requires hardwired + * baudrate (19200 gets mapped to 1200000). + * + * Called from usbserial:serial_probe. + */ +static int ftdi_NDI_device_setup(struct usb_serial *serial) +{ + struct usb_device *udev = serial->dev; + int latency = ndi_latency_timer; + + if (latency == 0) + latency = 1; + if (latency > 99) + latency = 99; + + dev_dbg(&udev->dev, "%s setting NDI device latency to %d\n", __func__, latency); + dev_info(&udev->dev, "NDI device with a latency value of %d\n", latency); + + /* FIXME: errors are not returned */ + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + FTDI_SIO_SET_LATENCY_TIMER_REQUEST, + FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE, + latency, 0, NULL, 0, WDR_TIMEOUT); + return 0; +} + +/* + * First port on JTAG adaptors such as Olimex arm-usb-ocd or the FIC/OpenMoko + * Neo1973 Debug Board is reserved for JTAG interface and can be accessed from + * userspace using openocd. + */ +static int ftdi_jtag_probe(struct usb_serial *serial) +{ + struct usb_device *udev = serial->dev; + struct usb_interface *interface = serial->interface; + + if (interface == udev->actconfig->interface[0]) { + dev_info(&udev->dev, + "Ignoring serial port reserved for JTAG\n"); + return -ENODEV; + } + + return 0; +} + +static int ftdi_8u2232c_probe(struct usb_serial *serial) +{ + struct usb_device *udev = serial->dev; + + if ((udev->manufacturer && !strcmp(udev->manufacturer, "CALAO Systems")) || + (udev->product && !strcmp(udev->product, "BeagleBone/XDS100V2"))) + return ftdi_jtag_probe(serial); + + return 0; +} + +/* + * First two ports on JTAG adaptors using an FT4232 such as STMicroelectronics's + * ST Micro Connect Lite are reserved for JTAG or other non-UART interfaces and + * can be accessed from userspace. + * The next two ports are enabled as UARTs by default, where port 2 is + * a conventional RS-232 UART. + */ +static int ftdi_stmclite_probe(struct usb_serial *serial) +{ + struct usb_device *udev = serial->dev; + struct usb_interface *interface = serial->interface; + + if (interface == udev->actconfig->interface[0] || + interface == udev->actconfig->interface[1]) { + dev_info(&udev->dev, "Ignoring serial port reserved for JTAG\n"); + return -ENODEV; + } + + return 0; +} + +/* + * The Matrix Orbital VK204-25-USB has an invalid IN endpoint. + * We have to correct it if we want to read from it. + */ +static int ftdi_mtxorb_hack_setup(struct usb_serial *serial) +{ + struct usb_host_endpoint *ep = serial->dev->ep_in[1]; + struct usb_endpoint_descriptor *ep_desc = &ep->desc; + + if (ep->enabled && ep_desc->wMaxPacketSize == 0) { + ep_desc->wMaxPacketSize = cpu_to_le16(0x40); + dev_info(&serial->dev->dev, + "Fixing invalid wMaxPacketSize on read pipe\n"); + } + + return 0; +} + +static int ftdi_sio_port_remove(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + + remove_sysfs_attrs(port); + + kfree(priv); + + return 0; +} + +static int ftdi_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct ktermios dummy; + struct usb_device *dev = port->serial->dev; + struct ftdi_private *priv = usb_get_serial_port_data(port); + + /* No error checking for this (will get errors later anyway) */ + /* See ftdi_sio.h for description of what is reset */ + usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + FTDI_SIO_RESET_REQUEST, FTDI_SIO_RESET_REQUEST_TYPE, + FTDI_SIO_RESET_SIO, + priv->interface, NULL, 0, WDR_TIMEOUT); + + /* Termios defaults are set by usb_serial_init. We don't change + port->tty->termios - this would lose speed settings, etc. + This is same behaviour as serial.c/rs_open() - Kuba */ + + /* ftdi_set_termios will send usb control messages */ + if (tty) { + memset(&dummy, 0, sizeof(dummy)); + ftdi_set_termios(tty, port, &dummy); + } + + return usb_serial_generic_open(tty, port); +} + +static void ftdi_dtr_rts(struct usb_serial_port *port, int on) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + + /* Disable flow control */ + if (!on) { + if (usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + FTDI_SIO_SET_FLOW_CTRL_REQUEST, + FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, + 0, priv->interface, NULL, 0, + WDR_TIMEOUT) < 0) { + dev_err(&port->dev, "error from flowcontrol urb\n"); + } + } + /* drop RTS and DTR */ + if (on) + set_mctrl(port, TIOCM_DTR | TIOCM_RTS); + else + clear_mctrl(port, TIOCM_DTR | TIOCM_RTS); +} + +/* The SIO requires the first byte to have: + * B0 1 + * B1 0 + * B2..7 length of message excluding byte 0 + * + * The new devices do not require this byte + */ +static int ftdi_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size) +{ + struct ftdi_private *priv; + int count; + unsigned long flags; + + priv = usb_get_serial_port_data(port); + + if (priv->chip_type == SIO) { + unsigned char *buffer = dest; + int i, len, c; + + count = 0; + spin_lock_irqsave(&port->lock, flags); + for (i = 0; i < size - 1; i += priv->max_packet_size) { + len = min_t(int, size - i, priv->max_packet_size) - 1; + c = kfifo_out(&port->write_fifo, &buffer[i + 1], len); + if (!c) + break; + port->icount.tx += c; + buffer[i] = (c << 2) + 1; + count += c + 1; + } + spin_unlock_irqrestore(&port->lock, flags); + } else { + count = kfifo_out_locked(&port->write_fifo, dest, size, + &port->lock); + port->icount.tx += count; + } + + return count; +} + +#define FTDI_RS_ERR_MASK (FTDI_RS_BI | FTDI_RS_PE | FTDI_RS_FE | FTDI_RS_OE) + +static int ftdi_process_packet(struct usb_serial_port *port, + struct ftdi_private *priv, char *packet, int len) +{ + int i; + char status; + char flag; + char *ch; + + if (len < 2) { + dev_dbg(&port->dev, "malformed packet\n"); + return 0; + } + + /* Compare new line status to the old one, signal if different/ + N.B. packet may be processed more than once, but differences + are only processed once. */ + status = packet[0] & FTDI_STATUS_B0_MASK; + if (status != priv->prev_status) { + char diff_status = status ^ priv->prev_status; + + if (diff_status & FTDI_RS0_CTS) + port->icount.cts++; + if (diff_status & FTDI_RS0_DSR) + port->icount.dsr++; + if (diff_status & FTDI_RS0_RI) + port->icount.rng++; + if (diff_status & FTDI_RS0_RLSD) + port->icount.dcd++; + + wake_up_interruptible(&port->port.delta_msr_wait); + priv->prev_status = status; + } + + flag = TTY_NORMAL; + if (packet[1] & FTDI_RS_ERR_MASK) { + /* Break takes precedence over parity, which takes precedence + * over framing errors */ + if (packet[1] & FTDI_RS_BI) { + flag = TTY_BREAK; + port->icount.brk++; + usb_serial_handle_break(port); + } else if (packet[1] & FTDI_RS_PE) { + flag = TTY_PARITY; + port->icount.parity++; + } else if (packet[1] & FTDI_RS_FE) { + flag = TTY_FRAME; + port->icount.frame++; + } + /* Overrun is special, not associated with a char */ + if (packet[1] & FTDI_RS_OE) { + port->icount.overrun++; + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } + } + + /* save if the transmitter is empty or not */ + if (packet[1] & FTDI_RS_TEMT) + priv->transmit_empty = 1; + else + priv->transmit_empty = 0; + + len -= 2; + if (!len) + return 0; /* status only */ + port->icount.rx += len; + ch = packet + 2; + + if (port->port.console && port->sysrq) { + for (i = 0; i < len; i++, ch++) { + if (!usb_serial_handle_sysrq_char(port, *ch)) + tty_insert_flip_char(&port->port, *ch, flag); + } + } else { + tty_insert_flip_string_fixed_flag(&port->port, ch, flag, len); + } + + return len; +} + +static void ftdi_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct ftdi_private *priv = usb_get_serial_port_data(port); + char *data = (char *)urb->transfer_buffer; + int i; + int len; + int count = 0; + + for (i = 0; i < urb->actual_length; i += priv->max_packet_size) { + len = min_t(int, urb->actual_length - i, priv->max_packet_size); + count += ftdi_process_packet(port, priv, &data[i], len); + } + + if (count) + tty_flip_buffer_push(&port->port); +} + +static void ftdi_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct ftdi_private *priv = usb_get_serial_port_data(port); + __u16 urb_value; + + /* break_state = -1 to turn on break, and 0 to turn off break */ + /* see drivers/char/tty_io.c to see it used */ + /* last_set_data_urb_value NEVER has the break bit set in it */ + + if (break_state) + urb_value = priv->last_set_data_urb_value | FTDI_SIO_SET_BREAK; + else + urb_value = priv->last_set_data_urb_value; + + if (usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + FTDI_SIO_SET_DATA_REQUEST, + FTDI_SIO_SET_DATA_REQUEST_TYPE, + urb_value , priv->interface, + NULL, 0, WDR_TIMEOUT) < 0) { + dev_err(&port->dev, "%s FAILED to enable/disable break state (state was %d)\n", + __func__, break_state); + } + + dev_dbg(&port->dev, "%s break state is %d - urb is %d\n", __func__, + break_state, urb_value); + +} + +static bool ftdi_tx_empty(struct usb_serial_port *port) +{ + unsigned char buf[2]; + int ret; + + ret = ftdi_get_modem_status(port, buf); + if (ret == 2) { + if (!(buf[1] & FTDI_RS_TEMT)) + return false; + } + + return true; +} + +/* old_termios contains the original termios settings and tty->termios contains + * the new setting to be used + * WARNING: set_termios calls this with old_termios in kernel space + */ +static void ftdi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct usb_device *dev = port->serial->dev; + struct device *ddev = &port->dev; + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct ktermios *termios = &tty->termios; + unsigned int cflag = termios->c_cflag; + __u16 urb_value; /* will hold the new flags */ + + /* Added for xon/xoff support */ + unsigned int iflag = termios->c_iflag; + unsigned char vstop; + unsigned char vstart; + + /* Force baud rate if this device requires it, unless it is set to + B0. */ + if (priv->force_baud && ((termios->c_cflag & CBAUD) != B0)) { + dev_dbg(ddev, "%s: forcing baud rate for this device\n", __func__); + tty_encode_baud_rate(tty, priv->force_baud, + priv->force_baud); + } + + /* Force RTS-CTS if this device requires it. */ + if (priv->force_rtscts) { + dev_dbg(ddev, "%s: forcing rtscts for this device\n", __func__); + termios->c_cflag |= CRTSCTS; + } + + /* + * All FTDI UART chips are limited to CS7/8. We shouldn't pretend to + * support CS5/6 and revert the CSIZE setting instead. + * + * CS5 however is used to control some smartcard readers which abuse + * this limitation to switch modes. Original FTDI chips fall back to + * eight data bits. + * + * TODO: Implement a quirk to only allow this with mentioned + * readers. One I know of (Argolis Smartreader V1) + * returns "USB smartcard server" as iInterface string. + * The vendor didn't bother with a custom VID/PID of + * course. + */ + if (C_CSIZE(tty) == CS6) { + dev_warn(ddev, "requested CSIZE setting not supported\n"); + + termios->c_cflag &= ~CSIZE; + if (old_termios) + termios->c_cflag |= old_termios->c_cflag & CSIZE; + else + termios->c_cflag |= CS8; + } + + cflag = termios->c_cflag; + + if (!old_termios) + goto no_skip; + + if (old_termios->c_cflag == termios->c_cflag + && old_termios->c_ispeed == termios->c_ispeed + && old_termios->c_ospeed == termios->c_ospeed) + goto no_c_cflag_changes; + + /* NOTE These routines can get interrupted by + ftdi_sio_read_bulk_callback - need to examine what this means - + don't see any problems yet */ + + if ((old_termios->c_cflag & (CSIZE|PARODD|PARENB|CMSPAR|CSTOPB)) == + (termios->c_cflag & (CSIZE|PARODD|PARENB|CMSPAR|CSTOPB))) + goto no_data_parity_stop_changes; + +no_skip: + /* Set number of data bits, parity, stop bits */ + + urb_value = 0; + urb_value |= (cflag & CSTOPB ? FTDI_SIO_SET_DATA_STOP_BITS_2 : + FTDI_SIO_SET_DATA_STOP_BITS_1); + if (cflag & PARENB) { + if (cflag & CMSPAR) + urb_value |= cflag & PARODD ? + FTDI_SIO_SET_DATA_PARITY_MARK : + FTDI_SIO_SET_DATA_PARITY_SPACE; + else + urb_value |= cflag & PARODD ? + FTDI_SIO_SET_DATA_PARITY_ODD : + FTDI_SIO_SET_DATA_PARITY_EVEN; + } else { + urb_value |= FTDI_SIO_SET_DATA_PARITY_NONE; + } + switch (cflag & CSIZE) { + case CS5: + dev_dbg(ddev, "Setting CS5 quirk\n"); + break; + case CS7: + urb_value |= 7; + dev_dbg(ddev, "Setting CS7\n"); + break; + default: + case CS8: + urb_value |= 8; + dev_dbg(ddev, "Setting CS8\n"); + break; + } + + /* This is needed by the break command since it uses the same command + - but is or'ed with this value */ + priv->last_set_data_urb_value = urb_value; + + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + FTDI_SIO_SET_DATA_REQUEST, + FTDI_SIO_SET_DATA_REQUEST_TYPE, + urb_value , priv->interface, + NULL, 0, WDR_SHORT_TIMEOUT) < 0) { + dev_err(ddev, "%s FAILED to set databits/stopbits/parity\n", + __func__); + } + + /* Now do the baudrate */ +no_data_parity_stop_changes: + if ((cflag & CBAUD) == B0) { + /* Disable flow control */ + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + FTDI_SIO_SET_FLOW_CTRL_REQUEST, + FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, + 0, priv->interface, + NULL, 0, WDR_TIMEOUT) < 0) { + dev_err(ddev, "%s error from disable flowcontrol urb\n", + __func__); + } + /* Drop RTS and DTR */ + clear_mctrl(port, TIOCM_DTR | TIOCM_RTS); + } else { + /* set the baudrate determined before */ + mutex_lock(&priv->cfg_lock); + if (change_speed(tty, port)) + dev_err(ddev, "%s urb failed to set baudrate\n", __func__); + mutex_unlock(&priv->cfg_lock); + /* Ensure RTS and DTR are raised when baudrate changed from 0 */ + if (!old_termios || (old_termios->c_cflag & CBAUD) == B0) + set_mctrl(port, TIOCM_DTR | TIOCM_RTS); + } + + /* Set flow control */ + /* Note device also supports DTR/CD (ugh) and Xon/Xoff in hardware */ +no_c_cflag_changes: + if (cflag & CRTSCTS) { + dev_dbg(ddev, "%s Setting to CRTSCTS flow control\n", __func__); + if (usb_control_msg(dev, + usb_sndctrlpipe(dev, 0), + FTDI_SIO_SET_FLOW_CTRL_REQUEST, + FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, + 0 , (FTDI_SIO_RTS_CTS_HS | priv->interface), + NULL, 0, WDR_TIMEOUT) < 0) { + dev_err(ddev, "urb failed to set to rts/cts flow control\n"); + } + } else { + /* + * Xon/Xoff code + * + * Check the IXOFF status in the iflag component of the + * termios structure. If IXOFF is not set, the pre-xon/xoff + * code is executed. + */ + if (iflag & IXOFF) { + dev_dbg(ddev, "%s request to enable xonxoff iflag=%04x\n", + __func__, iflag); + /* Try to enable the XON/XOFF on the ftdi_sio + * Set the vstart and vstop -- could have been done up + * above where a lot of other dereferencing is done but + * that would be very inefficient as vstart and vstop + * are not always needed. + */ + vstart = termios->c_cc[VSTART]; + vstop = termios->c_cc[VSTOP]; + urb_value = (vstop << 8) | (vstart); + + if (usb_control_msg(dev, + usb_sndctrlpipe(dev, 0), + FTDI_SIO_SET_FLOW_CTRL_REQUEST, + FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, + urb_value , (FTDI_SIO_XON_XOFF_HS + | priv->interface), + NULL, 0, WDR_TIMEOUT) < 0) { + dev_err(&port->dev, "urb failed to set to " + "xon/xoff flow control\n"); + } + } else { + /* else clause to only run if cflag ! CRTSCTS and iflag + * ! XOFF. CHECKME Assuming XON/XOFF handled by tty + * stack - not by device */ + dev_dbg(ddev, "%s Turning off hardware flow control\n", __func__); + if (usb_control_msg(dev, + usb_sndctrlpipe(dev, 0), + FTDI_SIO_SET_FLOW_CTRL_REQUEST, + FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, + 0, priv->interface, + NULL, 0, WDR_TIMEOUT) < 0) { + dev_err(ddev, "urb failed to clear flow control\n"); + } + } + } +} + +/* + * Get modem-control status. + * + * Returns the number of status bytes retrieved (device dependant), or + * negative error code. + */ +static int ftdi_get_modem_status(struct usb_serial_port *port, + unsigned char status[2]) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + unsigned char *buf; + int len; + int ret; + + buf = kmalloc(2, GFP_KERNEL); + if (!buf) + return -ENOMEM; + /* + * The 8U232AM returns a two byte value (the SIO a 1 byte value) in + * the same format as the data returned from the in point. + */ + switch (priv->chip_type) { + case SIO: + len = 1; + break; + case FT8U232AM: + case FT232BM: + case FT2232C: + case FT232RL: + case FT2232H: + case FT4232H: + case FT232H: + case FTX: + len = 2; + break; + default: + ret = -EFAULT; + goto out; + } + + ret = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + FTDI_SIO_GET_MODEM_STATUS_REQUEST, + FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE, + 0, priv->interface, + buf, len, WDR_TIMEOUT); + if (ret < 0) { + dev_err(&port->dev, "failed to get modem status: %d\n", ret); + ret = usb_translate_errors(ret); + goto out; + } + + status[0] = buf[0]; + if (ret > 1) + status[1] = buf[1]; + else + status[1] = 0; + + dev_dbg(&port->dev, "%s - 0x%02x%02x\n", __func__, status[0], + status[1]); +out: + kfree(buf); + + return ret; +} + +static int ftdi_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ftdi_private *priv = usb_get_serial_port_data(port); + unsigned char buf[2]; + int ret; + + ret = ftdi_get_modem_status(port, buf); + if (ret < 0) + return ret; + + ret = (buf[0] & FTDI_SIO_DSR_MASK ? TIOCM_DSR : 0) | + (buf[0] & FTDI_SIO_CTS_MASK ? TIOCM_CTS : 0) | + (buf[0] & FTDI_SIO_RI_MASK ? TIOCM_RI : 0) | + (buf[0] & FTDI_SIO_RLSD_MASK ? TIOCM_CD : 0) | + priv->last_dtr_rts; + + return ret; +} + +static int ftdi_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + + return update_mctrl(port, set, clear); +} + +static int ftdi_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s cmd 0x%04x\n", __func__, cmd); + + /* Based on code from acm.c and others */ + switch (cmd) { + + case TIOCGSERIAL: /* gets serial port data */ + return get_serial_info(port, + (struct serial_struct __user *) arg); + + case TIOCSSERIAL: /* sets serial port data */ + return set_serial_info(tty, port, + (struct serial_struct __user *) arg); + case TIOCSERGETLSR: + return get_lsr_info(port, (struct serial_struct __user *)arg); + break; + default: + break; + } + /* This is not necessarily an error - turns out the higher layers + * will do some ioctls themselves (see comment above) + */ + dev_dbg(&port->dev, "%s arg not supported - it was 0x%04x - check /usr/include/asm/ioctls.h\n", + __func__, cmd); + return -ENOIOCTLCMD; +} + +static int __init ftdi_init(void) +{ + if (vendor > 0 && product > 0) { + /* Add user specified VID/PID to reserved element of table. */ + int i; + for (i = 0; id_table_combined[i].idVendor; i++) + ; + id_table_combined[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + id_table_combined[i].idVendor = vendor; + id_table_combined[i].idProduct = product; + } + return usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME, id_table_combined); +} + +static void __exit ftdi_exit(void) +{ + usb_serial_deregister_drivers(serial_drivers); +} + + +module_init(ftdi_init); +module_exit(ftdi_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(vendor, ushort, 0); +MODULE_PARM_DESC(vendor, "User specified vendor ID (default=" + __MODULE_STRING(FTDI_VID)")"); +module_param(product, ushort, 0); +MODULE_PARM_DESC(product, "User specified product ID"); + +module_param(ndi_latency_timer, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ndi_latency_timer, "NDI device latency timer override"); diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h new file mode 100644 index 00000000..ed58c6fa --- /dev/null +++ b/drivers/usb/serial/ftdi_sio.h @@ -0,0 +1,564 @@ +/* + * Driver definitions for the FTDI USB Single Port Serial Converter - + * known as FTDI_SIO (Serial Input/Output application of the chipset) + * + * For USB vendor/product IDs (VID/PID), please see ftdi_sio_ids.h + * + * + * The example I have is known as the USC-1000 which is available from + * http://www.dse.co.nz - cat no XH4214 It looks similar to this: + * http://www.dansdata.com/usbser.htm but I can't be sure There are other + * USC-1000s which don't look like my device though so beware! + * + * The device is based on the FTDI FT8U100AX chip. It has a DB25 on one side, + * USB on the other. + * + * Thanx to FTDI (http://www.ftdichip.com) for so kindly providing details + * of the protocol required to talk to the device and ongoing assistence + * during development. + * + * Bill Ryder - bryder@sgi.com formerly of Silicon Graphics, Inc.- wrote the + * FTDI_SIO implementation. + * + */ + +/* Commands */ +#define FTDI_SIO_RESET 0 /* Reset the port */ +#define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ +#define FTDI_SIO_SET_FLOW_CTRL 2 /* Set flow control register */ +#define FTDI_SIO_SET_BAUD_RATE 3 /* Set baud rate */ +#define FTDI_SIO_SET_DATA 4 /* Set the data characteristics of + the port */ +#define FTDI_SIO_GET_MODEM_STATUS 5 /* Retrieve current value of modem + status register */ +#define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */ +#define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ +#define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */ +#define FTDI_SIO_GET_LATENCY_TIMER 10 /* Get the latency timer */ + +/* Interface indices for FT2232, FT2232H and FT4232H devices */ +#define INTERFACE_A 1 +#define INTERFACE_B 2 +#define INTERFACE_C 3 +#define INTERFACE_D 4 + + +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_E2_READ + * wValue: 0 + * wIndex: Address of word to read + * wLength: 2 + * Data: Will return a word of data from E2Address + * + */ + +/* Port Identifier Table */ +#define PIT_DEFAULT 0 /* SIOA */ +#define PIT_SIOA 1 /* SIOA */ +/* The device this driver is tested with one has only one port */ +#define PIT_SIOB 2 /* SIOB */ +#define PIT_PARALLEL 3 /* Parallel */ + +/* FTDI_SIO_RESET */ +#define FTDI_SIO_RESET_REQUEST FTDI_SIO_RESET +#define FTDI_SIO_RESET_REQUEST_TYPE 0x40 +#define FTDI_SIO_RESET_SIO 0 +#define FTDI_SIO_RESET_PURGE_RX 1 +#define FTDI_SIO_RESET_PURGE_TX 2 + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_RESET + * wValue: Control Value + * 0 = Reset SIO + * 1 = Purge RX buffer + * 2 = Purge TX buffer + * wIndex: Port + * wLength: 0 + * Data: None + * + * The Reset SIO command has this effect: + * + * Sets flow control set to 'none' + * Event char = $0D + * Event trigger = disabled + * Purge RX buffer + * Purge TX buffer + * Clear DTR + * Clear RTS + * baud and data format not reset + * + * The Purge RX and TX buffer commands affect nothing except the buffers + * + */ + +/* FTDI_SIO_SET_BAUDRATE */ +#define FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BAUDRATE_REQUEST 3 + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_BAUDRATE + * wValue: BaudDivisor value - see below + * wIndex: Port + * wLength: 0 + * Data: None + * The BaudDivisor values are calculated as follows: + * - BaseClock is either 12000000 or 48000000 depending on the device. + * FIXME: I wish I knew how to detect old chips to select proper base clock! + * - BaudDivisor is a fixed point number encoded in a funny way. + * (--WRONG WAY OF THINKING--) + * BaudDivisor is a fixed point number encoded with following bit weighs: + * (-2)(-1)(13..0). It is a radical with a denominator of 4, so values + * end with 0.0 (00...), 0.25 (10...), 0.5 (01...), and 0.75 (11...). + * (--THE REALITY--) + * The both-bits-set has quite different meaning from 0.75 - the chip + * designers have decided it to mean 0.125 instead of 0.75. + * This info looked up in FTDI application note "FT8U232 DEVICES \ Data Rates + * and Flow Control Consideration for USB to RS232". + * - BaudDivisor = (BaseClock / 16) / BaudRate, where the (=) operation should + * automagically re-encode the resulting value to take fractions into + * consideration. + * As all values are integers, some bit twiddling is in order: + * BaudDivisor = (BaseClock / 16 / BaudRate) | + * (((BaseClock / 2 / BaudRate) & 4) ? 0x4000 // 0.5 + * : ((BaseClock / 2 / BaudRate) & 2) ? 0x8000 // 0.25 + * : ((BaseClock / 2 / BaudRate) & 1) ? 0xc000 // 0.125 + * : 0) + * + * For the FT232BM, a 17th divisor bit was introduced to encode the multiples + * of 0.125 missing from the FT8U232AM. Bits 16 to 14 are coded as follows + * (the first four codes are the same as for the FT8U232AM, where bit 16 is + * always 0): + * 000 - add .000 to divisor + * 001 - add .500 to divisor + * 010 - add .250 to divisor + * 011 - add .125 to divisor + * 100 - add .375 to divisor + * 101 - add .625 to divisor + * 110 - add .750 to divisor + * 111 - add .875 to divisor + * Bits 15 to 0 of the 17-bit divisor are placed in the urb value. Bit 16 is + * placed in bit 0 of the urb index. + * + * Note that there are a couple of special cases to support the highest baud + * rates. If the calculated divisor value is 1, this needs to be replaced with + * 0. Additionally for the FT232BM, if the calculated divisor value is 0x4001 + * (1.5), this needs to be replaced with 0x0001 (1) (but this divisor value is + * not supported by the FT8U232AM). + */ + +enum ftdi_chip_type { + SIO = 1, + FT8U232AM = 2, + FT232BM = 3, + FT2232C = 4, + FT232RL = 5, + FT2232H = 6, + FT4232H = 7, + FT232H = 8, + FTX = 9, +}; + +enum ftdi_sio_baudrate { + ftdi_sio_b300 = 0, + ftdi_sio_b600 = 1, + ftdi_sio_b1200 = 2, + ftdi_sio_b2400 = 3, + ftdi_sio_b4800 = 4, + ftdi_sio_b9600 = 5, + ftdi_sio_b19200 = 6, + ftdi_sio_b38400 = 7, + ftdi_sio_b57600 = 8, + ftdi_sio_b115200 = 9 +}; + +/* + * The ftdi_8U232AM_xxMHz_byyy constants have been removed. The encoded divisor + * values are calculated internally. + */ +#define FTDI_SIO_SET_DATA_REQUEST FTDI_SIO_SET_DATA +#define FTDI_SIO_SET_DATA_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) +#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) +#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) +#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) +#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) +#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) +#define FTDI_SIO_SET_BREAK (0x1 << 14) +/* FTDI_SIO_SET_DATA */ + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_DATA + * wValue: Data characteristics (see below) + * wIndex: Port + * wLength: 0 + * Data: No + * + * Data characteristics + * + * B0..7 Number of data bits + * B8..10 Parity + * 0 = None + * 1 = Odd + * 2 = Even + * 3 = Mark + * 4 = Space + * B11..13 Stop Bits + * 0 = 1 + * 1 = 1.5 + * 2 = 2 + * B14 + * 1 = TX ON (break) + * 0 = TX OFF (normal state) + * B15 Reserved + * + */ + + + +/* FTDI_SIO_MODEM_CTRL */ +#define FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_MODEM_CTRL_REQUEST FTDI_SIO_MODEM_CTRL + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_MODEM_CTRL + * wValue: ControlValue (see below) + * wIndex: Port + * wLength: 0 + * Data: None + * + * NOTE: If the device is in RTS/CTS flow control, the RTS set by this + * command will be IGNORED without an error being returned + * Also - you can not set DTR and RTS with one control message + */ + +#define FTDI_SIO_SET_DTR_MASK 0x1 +#define FTDI_SIO_SET_DTR_HIGH (1 | (FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_DTR_LOW (0 | (FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_RTS_MASK 0x2 +#define FTDI_SIO_SET_RTS_HIGH (2 | (FTDI_SIO_SET_RTS_MASK << 8)) +#define FTDI_SIO_SET_RTS_LOW (0 | (FTDI_SIO_SET_RTS_MASK << 8)) + +/* + * ControlValue + * B0 DTR state + * 0 = reset + * 1 = set + * B1 RTS state + * 0 = reset + * 1 = set + * B2..7 Reserved + * B8 DTR state enable + * 0 = ignore + * 1 = use DTR state + * B9 RTS state enable + * 0 = ignore + * 1 = use RTS state + * B10..15 Reserved + */ + +/* FTDI_SIO_SET_FLOW_CTRL */ +#define FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_FLOW_CTRL_REQUEST FTDI_SIO_SET_FLOW_CTRL +#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 +#define FTDI_SIO_RTS_CTS_HS (0x1 << 8) +#define FTDI_SIO_DTR_DSR_HS (0x2 << 8) +#define FTDI_SIO_XON_XOFF_HS (0x4 << 8) +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_FLOW_CTRL + * wValue: Xoff/Xon + * wIndex: Protocol/Port - hIndex is protocol / lIndex is port + * wLength: 0 + * Data: None + * + * hIndex protocol is: + * B0 Output handshaking using RTS/CTS + * 0 = disabled + * 1 = enabled + * B1 Output handshaking using DTR/DSR + * 0 = disabled + * 1 = enabled + * B2 Xon/Xoff handshaking + * 0 = disabled + * 1 = enabled + * + * A value of zero in the hIndex field disables handshaking + * + * If Xon/Xoff handshaking is specified, the hValue field should contain the + * XOFF character and the lValue field contains the XON character. + */ + +/* + * FTDI_SIO_GET_LATENCY_TIMER + * + * Set the timeout interval. The FTDI collects data from the slave + * device, transmitting it to the host when either A) 62 bytes are + * received, or B) the timeout interval has elapsed and the buffer + * contains at least 1 byte. Setting this value to a small number + * can dramatically improve performance for applications which send + * small packets, since the default value is 16ms. + */ +#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST FTDI_SIO_GET_LATENCY_TIMER +#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE 0xC0 + +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_SIO_GET_LATENCY_TIMER + * wValue: 0 + * wIndex: Port + * wLength: 0 + * Data: latency (on return) + */ + +/* + * FTDI_SIO_SET_LATENCY_TIMER + * + * Set the timeout interval. The FTDI collects data from the slave + * device, transmitting it to the host when either A) 62 bytes are + * received, or B) the timeout interval has elapsed and the buffer + * contains at least 1 byte. Setting this value to a small number + * can dramatically improve performance for applications which send + * small packets, since the default value is 16ms. + */ +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40 + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_LATENCY_TIMER + * wValue: Latency (milliseconds) + * wIndex: Port + * wLength: 0 + * Data: None + * + * wValue: + * B0..7 Latency timer + * B8..15 0 + * + */ + +/* + * FTDI_SIO_SET_EVENT_CHAR + * + * Set the special event character for the specified communications port. + * If the device sees this character it will immediately return the + * data read so far - rather than wait 40ms or until 62 bytes are read + * which is what normally happens. + */ + + +#define FTDI_SIO_SET_EVENT_CHAR_REQUEST FTDI_SIO_SET_EVENT_CHAR +#define FTDI_SIO_SET_EVENT_CHAR_REQUEST_TYPE 0x40 + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_EVENT_CHAR + * wValue: EventChar + * wIndex: Port + * wLength: 0 + * Data: None + * + * wValue: + * B0..7 Event Character + * B8 Event Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + */ + +/* FTDI_SIO_SET_ERROR_CHAR */ + +/* + * Set the parity error replacement character for the specified communications + * port + */ + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_EVENT_CHAR + * wValue: Error Char + * wIndex: Port + * wLength: 0 + * Data: None + * + *Error Char + * B0..7 Error Character + * B8 Error Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + */ + +/* FTDI_SIO_GET_MODEM_STATUS */ +/* Retrieve the current value of the modem status register */ + +#define FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_GET_MODEM_STATUS_REQUEST FTDI_SIO_GET_MODEM_STATUS +#define FTDI_SIO_CTS_MASK 0x10 +#define FTDI_SIO_DSR_MASK 0x20 +#define FTDI_SIO_RI_MASK 0x40 +#define FTDI_SIO_RLSD_MASK 0x80 +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_SIO_GET_MODEM_STATUS + * wValue: zero + * wIndex: Port + * wLength: 1 + * Data: Status + * + * One byte of data is returned + * B0..3 0 + * B4 CTS + * 0 = inactive + * 1 = active + * B5 DSR + * 0 = inactive + * 1 = active + * B6 Ring Indicator (RI) + * 0 = inactive + * 1 = active + * B7 Receive Line Signal Detect (RLSD) + * 0 = inactive + * 1 = active + */ + + + +/* Descriptors returned by the device + * + * Device Descriptor + * + * Offset Field Size Value Description + * 0 bLength 1 0x12 Size of descriptor in bytes + * 1 bDescriptorType 1 0x01 DEVICE Descriptor Type + * 2 bcdUSB 2 0x0110 USB Spec Release Number + * 4 bDeviceClass 1 0x00 Class Code + * 5 bDeviceSubClass 1 0x00 SubClass Code + * 6 bDeviceProtocol 1 0x00 Protocol Code + * 7 bMaxPacketSize0 1 0x08 Maximum packet size for endpoint 0 + * 8 idVendor 2 0x0403 Vendor ID + * 10 idProduct 2 0x8372 Product ID (FTDI_SIO_PID) + * 12 bcdDevice 2 0x0001 Device release number + * 14 iManufacturer 1 0x01 Index of man. string desc + * 15 iProduct 1 0x02 Index of prod string desc + * 16 iSerialNumber 1 0x02 Index of serial nmr string desc + * 17 bNumConfigurations 1 0x01 Number of possible configurations + * + * Configuration Descriptor + * + * Offset Field Size Value + * 0 bLength 1 0x09 Size of descriptor in bytes + * 1 bDescriptorType 1 0x02 CONFIGURATION Descriptor Type + * 2 wTotalLength 2 0x0020 Total length of data + * 4 bNumInterfaces 1 0x01 Number of interfaces supported + * 5 bConfigurationValue 1 0x01 Argument for SetCOnfiguration() req + * 6 iConfiguration 1 0x02 Index of config string descriptor + * 7 bmAttributes 1 0x20 Config characteristics Remote Wakeup + * 8 MaxPower 1 0x1E Max power consumption + * + * Interface Descriptor + * + * Offset Field Size Value + * 0 bLength 1 0x09 Size of descriptor in bytes + * 1 bDescriptorType 1 0x04 INTERFACE Descriptor Type + * 2 bInterfaceNumber 1 0x00 Number of interface + * 3 bAlternateSetting 1 0x00 Value used to select alternate + * 4 bNumEndpoints 1 0x02 Number of endpoints + * 5 bInterfaceClass 1 0xFF Class Code + * 6 bInterfaceSubClass 1 0xFF Subclass Code + * 7 bInterfaceProtocol 1 0xFF Protocol Code + * 8 iInterface 1 0x02 Index of interface string description + * + * IN Endpoint Descriptor + * + * Offset Field Size Value + * 0 bLength 1 0x07 Size of descriptor in bytes + * 1 bDescriptorType 1 0x05 ENDPOINT descriptor type + * 2 bEndpointAddress 1 0x82 Address of endpoint + * 3 bmAttributes 1 0x02 Endpoint attributes - Bulk + * 4 bNumEndpoints 2 0x0040 maximum packet size + * 5 bInterval 1 0x00 Interval for polling endpoint + * + * OUT Endpoint Descriptor + * + * Offset Field Size Value + * 0 bLength 1 0x07 Size of descriptor in bytes + * 1 bDescriptorType 1 0x05 ENDPOINT descriptor type + * 2 bEndpointAddress 1 0x02 Address of endpoint + * 3 bmAttributes 1 0x02 Endpoint attributes - Bulk + * 4 bNumEndpoints 2 0x0040 maximum packet size + * 5 bInterval 1 0x00 Interval for polling endpoint + * + * DATA FORMAT + * + * IN Endpoint + * + * The device reserves the first two bytes of data on this endpoint to contain + * the current values of the modem and line status registers. In the absence of + * data, the device generates a message consisting of these two status bytes + * every 40 ms + * + * Byte 0: Modem Status + * + * Offset Description + * B0 Reserved - must be 1 + * B1 Reserved - must be 0 + * B2 Reserved - must be 0 + * B3 Reserved - must be 0 + * B4 Clear to Send (CTS) + * B5 Data Set Ready (DSR) + * B6 Ring Indicator (RI) + * B7 Receive Line Signal Detect (RLSD) + * + * Byte 1: Line Status + * + * Offset Description + * B0 Data Ready (DR) + * B1 Overrun Error (OE) + * B2 Parity Error (PE) + * B3 Framing Error (FE) + * B4 Break Interrupt (BI) + * B5 Transmitter Holding Register (THRE) + * B6 Transmitter Empty (TEMT) + * B7 Error in RCVR FIFO + * + */ +#define FTDI_RS0_CTS (1 << 4) +#define FTDI_RS0_DSR (1 << 5) +#define FTDI_RS0_RI (1 << 6) +#define FTDI_RS0_RLSD (1 << 7) + +#define FTDI_RS_DR 1 +#define FTDI_RS_OE (1<<1) +#define FTDI_RS_PE (1<<2) +#define FTDI_RS_FE (1<<3) +#define FTDI_RS_BI (1<<4) +#define FTDI_RS_THRE (1<<5) +#define FTDI_RS_TEMT (1<<6) +#define FTDI_RS_FIFO (1<<7) + +/* + * OUT Endpoint + * + * This device reserves the first bytes of data on this endpoint contain the + * length and port identifier of the message. For the FTDI USB Serial converter + * the port identifier is always 1. + * + * Byte 0: Line Status + * + * Offset Description + * B0 Reserved - must be 1 + * B1 Reserved - must be 0 + * B2..7 Length of message - (not including Byte 0) + * + */ diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h new file mode 100644 index 00000000..ac703a6e --- /dev/null +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -0,0 +1,1434 @@ +/* + * vendor/product IDs (VID/PID) of devices using FTDI USB serial converters. + * Please keep numerically sorted within individual areas, thanks! + * + * Philipp Gühring - pg@futureware.at - added the Device ID of the USB relais + * from Rudolf Gugler + * + */ + + +/**********************************/ +/***** devices using FTDI VID *****/ +/**********************************/ + + +#define FTDI_VID 0x0403 /* Vendor Id */ + + +/*** "original" FTDI device PIDs ***/ + +#define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */ +#define FTDI_8U232AM_ALT_PID 0x6006 /* FTDI's alternate PID for above */ +#define FTDI_8U2232C_PID 0x6010 /* Dual channel device */ +#define FTDI_4232H_PID 0x6011 /* Quad channel hi-speed device */ +#define FTDI_232H_PID 0x6014 /* Single channel hi-speed device */ +#define FTDI_FTX_PID 0x6015 /* FT-X series (FT201X, FT230X, FT231X, etc) */ +#define FTDI_SIO_PID 0x8372 /* Product Id SIO application of 8U100AX */ +#define FTDI_232RL_PID 0xFBFA /* Product ID for FT232RL */ + + +/*** third-party PIDs (using FTDI_VID) ***/ + +/* + * Certain versions of the official Windows FTDI driver reprogrammed + * counterfeit FTDI devices to PID 0. Support these devices anyway. + */ +#define FTDI_BRICK_PID 0x0000 + +#define FTDI_LUMEL_PD12_PID 0x6002 + +/* + * Marvell OpenRD Base, Client + * http://www.open-rd.org + * OpenRD Base, Client use VID 0x0403 + */ +#define MARVELL_OPENRD_PID 0x9e90 + +/* www.candapter.com Ewert Energy Systems CANdapter device */ +#define FTDI_CANDAPTER_PID 0x9F80 /* Product Id */ + +#define FTDI_BM_ATOM_NANO_PID 0xa559 /* Basic Micro ATOM Nano USB2Serial */ + +/* + * Texas Instruments XDS100v2 JTAG / BeagleBone A3 + * http://processors.wiki.ti.com/index.php/XDS100 + * http://beagleboard.org/bone + */ +#define TI_XDS100V2_PID 0xa6d0 + +#define FTDI_NXTCAM_PID 0xABB8 /* NXTCam for Mindstorms NXT */ +#define FTDI_EV3CON_PID 0xABB9 /* Mindstorms EV3 Console Adapter */ + +/* US Interface Navigator (http://www.usinterface.com/) */ +#define FTDI_USINT_CAT_PID 0xb810 /* Navigator CAT and 2nd PTT lines */ +#define FTDI_USINT_WKEY_PID 0xb811 /* Navigator WKEY and FSK lines */ +#define FTDI_USINT_RS232_PID 0xb812 /* Navigator RS232 and CONFIG lines */ + +/* OOCDlink by Joern Kaipf + * (http://www.joernonline.de/) */ +#define FTDI_OOCDLINK_PID 0xbaf8 /* Amontec JTAGkey */ + +/* Luminary Micro Stellaris Boards, VID = FTDI_VID */ +/* FTDI 2332C Dual channel device, side A=245 FIFO (JTAG), Side B=RS232 UART */ +#define LMI_LM3S_DEVEL_BOARD_PID 0xbcd8 +#define LMI_LM3S_EVAL_BOARD_PID 0xbcd9 +#define LMI_LM3S_ICDI_BOARD_PID 0xbcda + +#define FTDI_TURTELIZER_PID 0xBDC8 /* JTAG/RS-232 adapter by egnite GmbH */ + +/* OpenDCC (www.opendcc.de) product id */ +#define FTDI_OPENDCC_PID 0xBFD8 +#define FTDI_OPENDCC_SNIFFER_PID 0xBFD9 +#define FTDI_OPENDCC_THROTTLE_PID 0xBFDA +#define FTDI_OPENDCC_GATEWAY_PID 0xBFDB +#define FTDI_OPENDCC_GBM_PID 0xBFDC +#define FTDI_OPENDCC_GBM_BOOST_PID 0xBFDD + +/* NZR SEM 16+ USB (http://www.nzr.de) */ +#define FTDI_NZR_SEM_USB_PID 0xC1E0 /* NZR SEM-LOG16+ */ + +/* + * RR-CirKits LocoBuffer USB (http://www.rr-cirkits.com) + */ +#define FTDI_RRCIRKITS_LOCOBUFFER_PID 0xc7d0 /* LocoBuffer USB */ + +/* DMX4ALL DMX Interfaces */ +#define FTDI_DMX4ALL 0xC850 + +/* + * ASK.fr devices + */ +#define FTDI_ASK_RDR400_PID 0xC991 /* ASK RDR 400 series card reader */ + +/* www.starting-point-systems.com µChameleon device */ +#define FTDI_MICRO_CHAMELEON_PID 0xCAA0 /* Product Id */ + +/* + * Tactrix OpenPort (ECU) devices. + * OpenPort 1.3M submitted by Donour Sizemore. + * OpenPort 1.3S and 1.3U submitted by Ian Abbott. + */ +#define FTDI_TACTRIX_OPENPORT_13M_PID 0xCC48 /* OpenPort 1.3 Mitsubishi */ +#define FTDI_TACTRIX_OPENPORT_13S_PID 0xCC49 /* OpenPort 1.3 Subaru */ +#define FTDI_TACTRIX_OPENPORT_13U_PID 0xCC4A /* OpenPort 1.3 Universal */ + +#define FTDI_DISTORTEC_JTAG_LOCK_PICK_PID 0xCFF8 + +/* SCS HF Radio Modems PID's (http://www.scs-ptc.com) */ +/* the VID is the standard ftdi vid (FTDI_VID) */ +#define FTDI_SCS_DEVICE_0_PID 0xD010 /* SCS PTC-IIusb */ +#define FTDI_SCS_DEVICE_1_PID 0xD011 /* SCS Tracker / DSP TNC */ +#define FTDI_SCS_DEVICE_2_PID 0xD012 +#define FTDI_SCS_DEVICE_3_PID 0xD013 +#define FTDI_SCS_DEVICE_4_PID 0xD014 +#define FTDI_SCS_DEVICE_5_PID 0xD015 +#define FTDI_SCS_DEVICE_6_PID 0xD016 +#define FTDI_SCS_DEVICE_7_PID 0xD017 + +/* iPlus device */ +#define FTDI_IPLUS_PID 0xD070 /* Product Id */ +#define FTDI_IPLUS2_PID 0xD071 /* Product Id */ + +/* + * Gamma Scout (http://gamma-scout.com/). Submitted by rsc@runtux.com. + */ +#define FTDI_GAMMA_SCOUT_PID 0xD678 /* Gamma Scout online */ + +/* Propox devices */ +#define FTDI_PROPOX_JTAGCABLEII_PID 0xD738 +#define FTDI_PROPOX_ISPCABLEIII_PID 0xD739 + +/* Lenz LI-USB Computer Interface. */ +#define FTDI_LENZ_LIUSB_PID 0xD780 + +/* Vardaan Enterprises Serial Interface VEUSB422R3 */ +#define FTDI_VARDAAN_PID 0xF070 + +/* + * Xsens Technologies BV products (http://www.xsens.com). + */ +#define XSENS_VID 0x2639 +#define XSENS_AWINDA_STATION_PID 0x0101 +#define XSENS_AWINDA_DONGLE_PID 0x0102 +#define XSENS_MTW_PID 0x0200 /* Xsens MTw */ +#define XSENS_CONVERTER_PID 0xD00D /* Xsens USB-serial converter */ + +/* Xsens devices using FTDI VID */ +#define XSENS_CONVERTER_0_PID 0xD388 /* Xsens USB converter */ +#define XSENS_CONVERTER_1_PID 0xD389 /* Xsens Wireless Receiver */ +#define XSENS_CONVERTER_2_PID 0xD38A +#define XSENS_CONVERTER_3_PID 0xD38B /* Xsens USB-serial converter */ +#define XSENS_CONVERTER_4_PID 0xD38C /* Xsens Wireless Receiver */ +#define XSENS_CONVERTER_5_PID 0xD38D /* Xsens Awinda Station */ +#define XSENS_CONVERTER_6_PID 0xD38E +#define XSENS_CONVERTER_7_PID 0xD38F + +/** + * Zolix (www.zolix.com.cb) product ids + */ +#define FTDI_OMNI1509 0xD491 /* Omni1509 embedded USB-serial */ + +/* + * NDI (www.ndigital.com) product ids + */ +#define FTDI_NDI_HUC_PID 0xDA70 /* NDI Host USB Converter */ +#define FTDI_NDI_SPECTRA_SCU_PID 0xDA71 /* NDI Spectra SCU */ +#define FTDI_NDI_FUTURE_2_PID 0xDA72 /* NDI future device #2 */ +#define FTDI_NDI_FUTURE_3_PID 0xDA73 /* NDI future device #3 */ +#define FTDI_NDI_AURORA_SCU_PID 0xDA74 /* NDI Aurora SCU */ + +/* + * ChamSys Limited (www.chamsys.co.uk) USB wing/interface product IDs + */ +#define FTDI_CHAMSYS_24_MASTER_WING_PID 0xDAF8 +#define FTDI_CHAMSYS_PC_WING_PID 0xDAF9 +#define FTDI_CHAMSYS_USB_DMX_PID 0xDAFA +#define FTDI_CHAMSYS_MIDI_TIMECODE_PID 0xDAFB +#define FTDI_CHAMSYS_MINI_WING_PID 0xDAFC +#define FTDI_CHAMSYS_MAXI_WING_PID 0xDAFD +#define FTDI_CHAMSYS_MEDIA_WING_PID 0xDAFE +#define FTDI_CHAMSYS_WING_PID 0xDAFF + +/* + * Westrex International devices submitted by Cory Lee + */ +#define FTDI_WESTREX_MODEL_777_PID 0xDC00 /* Model 777 */ +#define FTDI_WESTREX_MODEL_8900F_PID 0xDC01 /* Model 8900F */ + +/* + * ACG Identification Technologies GmbH products (http://www.acg.de/). + * Submitted by anton -at- goto10 -dot- org. + */ +#define FTDI_ACG_HFDUAL_PID 0xDD20 /* HF Dual ISO Reader (RFID) */ + +/* + * Definitions for Artemis astronomical USB based cameras + * Check it at http://www.artemisccd.co.uk/ + */ +#define FTDI_ARTEMIS_PID 0xDF28 /* All Artemis Cameras */ + +/* + * Definitions for ATIK Instruments astronomical USB based cameras + * Check it at http://www.atik-instruments.com/ + */ +#define FTDI_ATIK_ATK16_PID 0xDF30 /* ATIK ATK-16 Grayscale Camera */ +#define FTDI_ATIK_ATK16C_PID 0xDF32 /* ATIK ATK-16C Colour Camera */ +#define FTDI_ATIK_ATK16HR_PID 0xDF31 /* ATIK ATK-16HR Grayscale Camera */ +#define FTDI_ATIK_ATK16HRC_PID 0xDF33 /* ATIK ATK-16HRC Colour Camera */ +#define FTDI_ATIK_ATK16IC_PID 0xDF35 /* ATIK ATK-16IC Grayscale Camera */ + +/* + * Yost Engineering, Inc. products (www.yostengineering.com). + * PID 0xE050 submitted by Aaron Prose. + */ +#define FTDI_YEI_SERVOCENTER31_PID 0xE050 /* YEI ServoCenter3.1 USB */ + +/* + * ELV USB devices submitted by Christian Abt of ELV (www.elv.de). + * Almost all of these devices use FTDI's vendor ID (0x0403). + * Further IDs taken from ELV Windows .inf file. + * + * The previously included PID for the UO 100 module was incorrect. + * In fact, that PID was for ELV's UR 100 USB-RS232 converter (0xFB58). + * + * Armin Laeuger originally sent the PID for the UM 100 module. + */ +#define FTDI_ELV_VID 0x1B1F /* ELV AG */ +#define FTDI_ELV_WS300_PID 0xC006 /* eQ3 WS 300 PC II */ +#define FTDI_ELV_USR_PID 0xE000 /* ELV Universal-Sound-Recorder */ +#define FTDI_ELV_MSM1_PID 0xE001 /* ELV Mini-Sound-Modul */ +#define FTDI_ELV_KL100_PID 0xE002 /* ELV Kfz-Leistungsmesser KL 100 */ +#define FTDI_ELV_WS550_PID 0xE004 /* WS 550 */ +#define FTDI_ELV_EC3000_PID 0xE006 /* ENERGY CONTROL 3000 USB */ +#define FTDI_ELV_WS888_PID 0xE008 /* WS 888 */ +#define FTDI_ELV_TWS550_PID 0xE009 /* Technoline WS 550 */ +#define FTDI_ELV_FEM_PID 0xE00A /* Funk Energie Monitor */ +#define FTDI_ELV_FHZ1300PC_PID 0xE0E8 /* FHZ 1300 PC */ +#define FTDI_ELV_WS500_PID 0xE0E9 /* PC-Wetterstation (WS 500) */ +#define FTDI_ELV_HS485_PID 0xE0EA /* USB to RS-485 adapter */ +#define FTDI_ELV_UMS100_PID 0xE0EB /* ELV USB Master-Slave Schaltsteckdose UMS 100 */ +#define FTDI_ELV_TFD128_PID 0xE0EC /* ELV Temperatur-Feuchte-Datenlogger TFD 128 */ +#define FTDI_ELV_FM3RX_PID 0xE0ED /* ELV Messwertuebertragung FM3 RX */ +#define FTDI_ELV_WS777_PID 0xE0EE /* Conrad WS 777 */ +#define FTDI_ELV_EM1010PC_PID 0xE0EF /* Energy monitor EM 1010 PC */ +#define FTDI_ELV_CSI8_PID 0xE0F0 /* Computer-Schalt-Interface (CSI 8) */ +#define FTDI_ELV_EM1000DL_PID 0xE0F1 /* PC-Datenlogger fuer Energiemonitor (EM 1000 DL) */ +#define FTDI_ELV_PCK100_PID 0xE0F2 /* PC-Kabeltester (PCK 100) */ +#define FTDI_ELV_RFP500_PID 0xE0F3 /* HF-Leistungsmesser (RFP 500) */ +#define FTDI_ELV_FS20SIG_PID 0xE0F4 /* Signalgeber (FS 20 SIG) */ +#define FTDI_ELV_UTP8_PID 0xE0F5 /* ELV UTP 8 */ +#define FTDI_ELV_WS300PC_PID 0xE0F6 /* PC-Wetterstation (WS 300 PC) */ +#define FTDI_ELV_WS444PC_PID 0xE0F7 /* Conrad WS 444 PC */ +#define FTDI_PHI_FISCO_PID 0xE40B /* PHI Fisco USB to Serial cable */ +#define FTDI_ELV_UAD8_PID 0xF068 /* USB-AD-Wandler (UAD 8) */ +#define FTDI_ELV_UDA7_PID 0xF069 /* USB-DA-Wandler (UDA 7) */ +#define FTDI_ELV_USI2_PID 0xF06A /* USB-Schrittmotoren-Interface (USI 2) */ +#define FTDI_ELV_T1100_PID 0xF06B /* Thermometer (T 1100) */ +#define FTDI_ELV_PCD200_PID 0xF06C /* PC-Datenlogger (PCD 200) */ +#define FTDI_ELV_ULA200_PID 0xF06D /* USB-LCD-Ansteuerung (ULA 200) */ +#define FTDI_ELV_ALC8500_PID 0xF06E /* ALC 8500 Expert */ +#define FTDI_ELV_FHZ1000PC_PID 0xF06F /* FHZ 1000 PC */ +#define FTDI_ELV_UR100_PID 0xFB58 /* USB-RS232-Umsetzer (UR 100) */ +#define FTDI_ELV_UM100_PID 0xFB5A /* USB-Modul UM 100 */ +#define FTDI_ELV_UO100_PID 0xFB5B /* USB-Modul UO 100 */ +/* Additional ELV PIDs that default to using the FTDI D2XX drivers on + * MS Windows, rather than the FTDI Virtual Com Port drivers. + * Maybe these will be easier to use with the libftdi/libusb user-space + * drivers, or possibly the Comedi drivers in some cases. */ +#define FTDI_ELV_CLI7000_PID 0xFB59 /* Computer-Light-Interface (CLI 7000) */ +#define FTDI_ELV_PPS7330_PID 0xFB5C /* Processor-Power-Supply (PPS 7330) */ +#define FTDI_ELV_TFM100_PID 0xFB5D /* Temperatur-Feuchte-Messgeraet (TFM 100) */ +#define FTDI_ELV_UDF77_PID 0xFB5E /* USB DCF Funkuhr (UDF 77) */ +#define FTDI_ELV_UIO88_PID 0xFB5F /* USB-I/O Interface (UIO 88) */ + +/* + * EVER Eco Pro UPS (http://www.ever.com.pl/) + */ + +#define EVER_ECO_PRO_CDS 0xe520 /* RS-232 converter */ + +/* + * Active Robots product ids. + */ +#define FTDI_ACTIVE_ROBOTS_PID 0xE548 /* USB comms board */ + +/* Pyramid Computer GmbH */ +#define FTDI_PYRAMID_PID 0xE6C8 /* Pyramid Appliance Display */ + +/* www.elsterelectricity.com Elster Unicom III Optical Probe */ +#define FTDI_ELSTER_UNICOM_PID 0xE700 /* Product Id */ + +/* + * Gude Analog- und Digitalsysteme GmbH + */ +#define FTDI_GUDEADS_E808_PID 0xE808 +#define FTDI_GUDEADS_E809_PID 0xE809 +#define FTDI_GUDEADS_E80A_PID 0xE80A +#define FTDI_GUDEADS_E80B_PID 0xE80B +#define FTDI_GUDEADS_E80C_PID 0xE80C +#define FTDI_GUDEADS_E80D_PID 0xE80D +#define FTDI_GUDEADS_E80E_PID 0xE80E +#define FTDI_GUDEADS_E80F_PID 0xE80F +#define FTDI_GUDEADS_E888_PID 0xE888 /* Expert ISDN Control USB */ +#define FTDI_GUDEADS_E889_PID 0xE889 /* USB RS-232 OptoBridge */ +#define FTDI_GUDEADS_E88A_PID 0xE88A +#define FTDI_GUDEADS_E88B_PID 0xE88B +#define FTDI_GUDEADS_E88C_PID 0xE88C +#define FTDI_GUDEADS_E88D_PID 0xE88D +#define FTDI_GUDEADS_E88E_PID 0xE88E +#define FTDI_GUDEADS_E88F_PID 0xE88F + +/* + * Eclo (http://www.eclo.pt/) product IDs. + * PID 0xEA90 submitted by Martin Grill. + */ +#define FTDI_ECLO_COM_1WIRE_PID 0xEA90 /* COM to 1-Wire USB adaptor */ + +/* TNC-X USB-to-packet-radio adapter, versions prior to 3.0 (DLP module) */ +#define FTDI_TNC_X_PID 0xEBE0 + +/* + * Teratronik product ids. + * Submitted by O. Wölfelschneider. + */ +#define FTDI_TERATRONIK_VCP_PID 0xEC88 /* Teratronik device (preferring VCP driver on windows) */ +#define FTDI_TERATRONIK_D2XX_PID 0xEC89 /* Teratronik device (preferring D2XX driver on windows) */ + +/* Rig Expert Ukraine devices */ +#define FTDI_REU_TINY_PID 0xED22 /* RigExpert Tiny */ + +/* + * Hameg HO820 and HO870 interface (using VID 0x0403) + */ +#define HAMEG_HO820_PID 0xed74 +#define HAMEG_HO730_PID 0xed73 +#define HAMEG_HO720_PID 0xed72 +#define HAMEG_HO870_PID 0xed71 + +/* + * MaxStream devices www.maxstream.net + */ +#define FTDI_MAXSTREAM_PID 0xEE18 /* Xbee PKG-U Module */ + +/* + * microHAM product IDs (http://www.microham.com). + * Submitted by Justin Burket (KL1RL) + * and Mike Studer (K6EEP) . + * Ian Abbott added a few more from the driver INF file. + */ +#define FTDI_MHAM_KW_PID 0xEEE8 /* USB-KW interface */ +#define FTDI_MHAM_YS_PID 0xEEE9 /* USB-YS interface */ +#define FTDI_MHAM_Y6_PID 0xEEEA /* USB-Y6 interface */ +#define FTDI_MHAM_Y8_PID 0xEEEB /* USB-Y8 interface */ +#define FTDI_MHAM_IC_PID 0xEEEC /* USB-IC interface */ +#define FTDI_MHAM_DB9_PID 0xEEED /* USB-DB9 interface */ +#define FTDI_MHAM_RS232_PID 0xEEEE /* USB-RS232 interface */ +#define FTDI_MHAM_Y9_PID 0xEEEF /* USB-Y9 interface */ + +/* Domintell products http://www.domintell.com */ +#define FTDI_DOMINTELL_DGQG_PID 0xEF50 /* Master */ +#define FTDI_DOMINTELL_DUSB_PID 0xEF51 /* DUSB01 module */ + +/* + * The following are the values for the Perle Systems + * UltraPort USB serial converters + */ +#define FTDI_PERLE_ULTRAPORT_PID 0xF0C0 /* Perle UltraPort Product Id */ + +/* Sprog II (Andrew Crosland's SprogII DCC interface) */ +#define FTDI_SPROG_II 0xF0C8 + +/* + * Two of the Tagsys RFID Readers + */ +#define FTDI_TAGSYS_LP101_PID 0xF0E9 /* Tagsys L-P101 RFID*/ +#define FTDI_TAGSYS_P200X_PID 0xF0EE /* Tagsys Medio P200x RFID*/ + +/* an infrared receiver for user access control with IR tags */ +#define FTDI_PIEGROUP_PID 0xF208 /* Product Id */ + +/* ACT Solutions HomePro ZWave interface + (http://www.act-solutions.com/HomePro-Product-Matrix.html) */ +#define FTDI_ACTZWAVE_PID 0xF2D0 + +/* + * 4N-GALAXY.DE PIDs for CAN-USB, USB-RS232, USB-RS422, USB-RS485, + * USB-TTY aktiv, USB-TTY passiv. Some PIDs are used by several devices + * and I'm not entirely sure which are used by which. + */ +#define FTDI_4N_GALAXY_DE_1_PID 0xF3C0 +#define FTDI_4N_GALAXY_DE_2_PID 0xF3C1 +#define FTDI_4N_GALAXY_DE_3_PID 0xF3C2 + +/* + * Linx Technologies product ids + */ +#define LINX_SDMUSBQSS_PID 0xF448 /* Linx SDM-USB-QS-S */ +#define LINX_MASTERDEVEL2_PID 0xF449 /* Linx Master Development 2.0 */ +#define LINX_FUTURE_0_PID 0xF44A /* Linx future device */ +#define LINX_FUTURE_1_PID 0xF44B /* Linx future device */ +#define LINX_FUTURE_2_PID 0xF44C /* Linx future device */ + +/* + * Oceanic product ids + */ +#define FTDI_OCEANIC_PID 0xF460 /* Oceanic dive instrument */ + +/* + * SUUNTO product ids + */ +#define FTDI_SUUNTO_SPORTS_PID 0xF680 /* Suunto Sports instrument */ + +/* USB-UIRT - An infrared receiver and transmitter using the 8U232AM chip */ +/* http://www.usbuirt.com/ */ +#define FTDI_USB_UIRT_PID 0xF850 /* Product Id */ + +/* CCS Inc. ICDU/ICDU40 product ID - + * the FT232BM is used in an in-circuit-debugger unit for PIC16's/PIC18's */ +#define FTDI_CCSICDU20_0_PID 0xF9D0 +#define FTDI_CCSICDU40_1_PID 0xF9D1 +#define FTDI_CCSMACHX_2_PID 0xF9D2 +#define FTDI_CCSLOAD_N_GO_3_PID 0xF9D3 +#define FTDI_CCSICDU64_4_PID 0xF9D4 +#define FTDI_CCSPRIME8_5_PID 0xF9D5 + +/* + * The following are the values for the Matrix Orbital LCD displays, + * which are the FT232BM ( similar to the 8U232AM ) + */ +#define FTDI_MTXORB_0_PID 0xFA00 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_1_PID 0xFA01 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_2_PID 0xFA02 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_3_PID 0xFA03 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_4_PID 0xFA04 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_5_PID 0xFA05 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_6_PID 0xFA06 /* Matrix Orbital Product Id */ + +/* + * Home Electronics (www.home-electro.com) USB gadgets + */ +#define FTDI_HE_TIRA1_PID 0xFA78 /* Tira-1 IR transceiver */ + +/* Inside Accesso contactless reader (http://www.insidecontactless.com/) */ +#define INSIDE_ACCESSO 0xFAD0 + +/* + * ThorLabs USB motor drivers + */ +#define FTDI_THORLABS_PID 0xfaf0 /* ThorLabs USB motor drivers */ + +/* + * Protego product ids + */ +#define PROTEGO_SPECIAL_1 0xFC70 /* special/unknown device */ +#define PROTEGO_R2X0 0xFC71 /* R200-USB TRNG unit (R210, R220, and R230) */ +#define PROTEGO_SPECIAL_3 0xFC72 /* special/unknown device */ +#define PROTEGO_SPECIAL_4 0xFC73 /* special/unknown device */ + +/* + * Sony Ericsson product ids + */ +#define FTDI_DSS20_PID 0xFC82 /* DSS-20 Sync Station for Sony Ericsson P800 */ +#define FTDI_URBAN_0_PID 0xFC8A /* Sony Ericsson Urban, uart #0 */ +#define FTDI_URBAN_1_PID 0xFC8B /* Sony Ericsson Urban, uart #1 */ + +/* www.irtrans.de device */ +#define FTDI_IRTRANS_PID 0xFC60 /* Product Id */ + +/* + * RM Michaelides CANview USB (http://www.rmcan.com) (FTDI_VID) + * CAN fieldbus interface adapter, added by port GmbH www.port.de) + * Ian Abbott changed the macro names for consistency. + */ +#define FTDI_RM_CANVIEW_PID 0xfd60 /* Product Id */ +/* www.thoughttechnology.com/ TT-USB provide with procomp use ftdi_sio */ +#define FTDI_TTUSB_PID 0xFF20 /* Product Id */ + +#define FTDI_USBX_707_PID 0xF857 /* ADSTech IR Blaster USBX-707 (FTDI_VID) */ + +#define FTDI_RELAIS_PID 0xFA10 /* Relais device from Rudolf Gugler */ + +/* + * PCDJ use ftdi based dj-controllers. The following PID is + * for their DAC-2 device http://www.pcdjhardware.com/DAC2.asp + * (the VID is the standard ftdi vid (FTDI_VID), PID sent by Wouter Paesen) + */ +#define FTDI_PCDJ_DAC2_PID 0xFA88 + +#define FTDI_R2000KU_TRUE_RNG 0xFB80 /* R2000KU TRUE RNG (FTDI_VID) */ + +/* + * DIEBOLD BCS SE923 (FTDI_VID) + */ +#define DIEBOLD_BCS_SE923_PID 0xfb99 + +/* www.crystalfontz.com devices + * - thanx for providing free devices for evaluation ! + * they use the ftdi chipset for the USB interface + * and the vendor id is the same + */ +#define FTDI_XF_632_PID 0xFC08 /* 632: 16x2 Character Display */ +#define FTDI_XF_634_PID 0xFC09 /* 634: 20x4 Character Display */ +#define FTDI_XF_547_PID 0xFC0A /* 547: Two line Display */ +#define FTDI_XF_633_PID 0xFC0B /* 633: 16x2 Character Display with Keys */ +#define FTDI_XF_631_PID 0xFC0C /* 631: 20x2 Character Display */ +#define FTDI_XF_635_PID 0xFC0D /* 635: 20x4 Character Display */ +#define FTDI_XF_640_PID 0xFC0E /* 640: Two line Display */ +#define FTDI_XF_642_PID 0xFC0F /* 642: Two line Display */ + +/* + * Video Networks Limited / Homechoice in the UK use an ftdi-based device + * for their 1Mb broadband internet service. The following PID is exhibited + * by the usb device supplied (the VID is the standard ftdi vid (FTDI_VID) + */ +#define FTDI_VNHCPCUSB_D_PID 0xfe38 /* Product Id */ + +/* AlphaMicro Components AMC-232USB01 device (FTDI_VID) */ +#define FTDI_AMC232_PID 0xFF00 /* Product Id */ + +/* + * IBS elektronik product ids (FTDI_VID) + * Submitted by Thomas Schleusener + */ +#define FTDI_IBS_US485_PID 0xff38 /* IBS US485 (USB<-->RS422/485 interface) */ +#define FTDI_IBS_PICPRO_PID 0xff39 /* IBS PIC-Programmer */ +#define FTDI_IBS_PCMCIA_PID 0xff3a /* IBS Card reader for PCMCIA SRAM-cards */ +#define FTDI_IBS_PK1_PID 0xff3b /* IBS PK1 - Particel counter */ +#define FTDI_IBS_RS232MON_PID 0xff3c /* IBS RS232 - Monitor */ +#define FTDI_IBS_APP70_PID 0xff3d /* APP 70 (dust monitoring system) */ +#define FTDI_IBS_PEDO_PID 0xff3e /* IBS PEDO-Modem (RF modem 868.35 MHz) */ +#define FTDI_IBS_PROD_PID 0xff3f /* future device */ +/* www.canusb.com Lawicel CANUSB device (FTDI_VID) */ +#define FTDI_CANUSB_PID 0xFFA8 /* Product Id */ + +/* + * TavIR AVR product ids (FTDI_VID) + */ +#define FTDI_TAVIR_STK500_PID 0xFA33 /* STK500 AVR programmer */ + +/* + * TIAO product ids (FTDI_VID) + * http://www.tiaowiki.com/w/Main_Page + */ +#define FTDI_TIAO_UMPA_PID 0x8a98 /* TIAO/DIYGADGET USB Multi-Protocol Adapter */ + +/* + * NovaTech product ids (FTDI_VID) + */ +#define FTDI_NT_ORIONLXM_PID 0x7c90 /* OrionLXm Substation Automation Platform */ + + +/********************************/ +/** third-party VID/PID combos **/ +/********************************/ + + + +/* + * Atmel STK541 + */ +#define ATMEL_VID 0x03eb /* Vendor ID */ +#define STK541_PID 0x2109 /* Zigbee Controller */ + +/* + * Blackfin gnICE JTAG + * http://docs.blackfin.uclinux.org/doku.php?id=hw:jtag:gnice + */ +#define ADI_VID 0x0456 +#define ADI_GNICE_PID 0xF000 +#define ADI_GNICEPLUS_PID 0xF001 + +/* + * Microchip Technology, Inc. + * + * MICROCHIP_VID (0x04D8) and MICROCHIP_USB_BOARD_PID (0x000A) are + * used by single function CDC ACM class based firmware demo + * applications. The VID/PID has also been used in firmware + * emulating FTDI serial chips by: + * Hornby Elite - Digital Command Control Console + * http://www.hornby.com/hornby-dcc/controllers/ + */ +#define MICROCHIP_VID 0x04D8 +#define MICROCHIP_USB_BOARD_PID 0x000A /* CDC RS-232 Emulation Demo */ + +/* + * RATOC REX-USB60F + */ +#define RATOC_VENDOR_ID 0x0584 +#define RATOC_PRODUCT_ID_USB60F 0xb020 + +/* + * Infineon Technologies + */ +#define INFINEON_VID 0x058b +#define INFINEON_TRIBOARD_PID 0x0028 /* DAS JTAG TriBoard TC1798 V1.0 */ + +/* + * Acton Research Corp. + */ +#define ACTON_VID 0x0647 /* Vendor ID */ +#define ACTON_SPECTRAPRO_PID 0x0100 + +/* + * Contec products (http://www.contec.com) + * Submitted by Daniel Sangorrin + */ +#define CONTEC_VID 0x06CE /* Vendor ID */ +#define CONTEC_COM1USBH_PID 0x8311 /* COM-1(USB)H */ + +/* + * Mitsubishi Electric Corp. (http://www.meau.com) + * Submitted by Konstantin Holoborodko + */ +#define MITSUBISHI_VID 0x06D3 +#define MITSUBISHI_FXUSB_PID 0x0284 /* USB/RS422 converters: FX-USB-AW/-BD */ + +/* + * Definitions for B&B Electronics products. + */ +#define BANDB_VID 0x0856 /* B&B Electronics Vendor ID */ +#define BANDB_USOTL4_PID 0xAC01 /* USOTL4 Isolated RS-485 Converter */ +#define BANDB_USTL4_PID 0xAC02 /* USTL4 RS-485 Converter */ +#define BANDB_USO9ML2_PID 0xAC03 /* USO9ML2 Isolated RS-232 Converter */ +#define BANDB_USOPTL4_PID 0xAC11 +#define BANDB_USPTL4_PID 0xAC12 +#define BANDB_USO9ML2DR_2_PID 0xAC16 +#define BANDB_USO9ML2DR_PID 0xAC17 +#define BANDB_USOPTL4DR2_PID 0xAC18 /* USOPTL4R-2 2-port Isolated RS-232 Converter */ +#define BANDB_USOPTL4DR_PID 0xAC19 +#define BANDB_485USB9F_2W_PID 0xAC25 +#define BANDB_485USB9F_4W_PID 0xAC26 +#define BANDB_232USB9M_PID 0xAC27 +#define BANDB_485USBTB_2W_PID 0xAC33 +#define BANDB_485USBTB_4W_PID 0xAC34 +#define BANDB_TTL5USB9M_PID 0xAC49 +#define BANDB_TTL3USB9M_PID 0xAC50 +#define BANDB_ZZ_PROG1_USB_PID 0xBA02 + +/* + * Intrepid Control Systems (http://www.intrepidcs.com/) ValueCAN and NeoVI + */ +#define INTREPID_VID 0x093C +#define INTREPID_VALUECAN_PID 0x0601 +#define INTREPID_NEOVI_PID 0x0701 + +/* + * Definitions for ID TECH (www.idt-net.com) devices + */ +#define IDTECH_VID 0x0ACD /* ID TECH Vendor ID */ +#define IDTECH_IDT1221U_PID 0x0300 /* IDT1221U USB to RS-232 adapter */ + +/* + * Definitions for Omnidirectional Control Technology, Inc. devices + */ +#define OCT_VID 0x0B39 /* OCT vendor ID */ +/* Note: OCT US101 is also rebadged as Dick Smith Electronics (NZ) XH6381 */ +/* Also rebadged as Dick Smith Electronics (Aus) XH6451 */ +/* Also rebadged as SIIG Inc. model US2308 hardware version 1 */ +#define OCT_DK201_PID 0x0103 /* OCT DK201 USB docking station */ +#define OCT_US101_PID 0x0421 /* OCT US101 USB to RS-232 */ + +/* + * Definitions for Icom Inc. devices + */ +#define ICOM_VID 0x0C26 /* Icom vendor ID */ +/* Note: ID-1 is a communications tranceiver for HAM-radio operators */ +#define ICOM_ID_1_PID 0x0004 /* ID-1 USB to RS-232 */ +/* Note: OPC is an Optional cable to connect an Icom Tranceiver */ +#define ICOM_OPC_U_UC_PID 0x0018 /* OPC-478UC, OPC-1122U cloning cable */ +/* Note: ID-RP* devices are Icom Repeater Devices for HAM-radio */ +#define ICOM_ID_RP2C1_PID 0x0009 /* ID-RP2C Asset 1 to RS-232 */ +#define ICOM_ID_RP2C2_PID 0x000A /* ID-RP2C Asset 2 to RS-232 */ +#define ICOM_ID_RP2D_PID 0x000B /* ID-RP2D configuration port*/ +#define ICOM_ID_RP2VT_PID 0x000C /* ID-RP2V Transmit config port */ +#define ICOM_ID_RP2VR_PID 0x000D /* ID-RP2V Receive config port */ +#define ICOM_ID_RP4KVT_PID 0x0010 /* ID-RP4000V Transmit config port */ +#define ICOM_ID_RP4KVR_PID 0x0011 /* ID-RP4000V Receive config port */ +#define ICOM_ID_RP2KVT_PID 0x0012 /* ID-RP2000V Transmit config port */ +#define ICOM_ID_RP2KVR_PID 0x0013 /* ID-RP2000V Receive config port */ + +/* + * GN Otometrics (http://www.otometrics.com) + * Submitted by Ville Sundberg. + */ +#define GN_OTOMETRICS_VID 0x0c33 /* Vendor ID */ +#define AURICAL_USB_PID 0x0010 /* Aurical USB Audiometer */ + +/* + * The following are the values for the Sealevel SeaLINK+ adapters. + * (Original list sent by Tuan Hoang. Ian Abbott renamed the macros and + * removed some PIDs that don't seem to match any existing products.) + */ +#define SEALEVEL_VID 0x0c52 /* Sealevel Vendor ID */ +#define SEALEVEL_2101_PID 0x2101 /* SeaLINK+232 (2101/2105) */ +#define SEALEVEL_2102_PID 0x2102 /* SeaLINK+485 (2102) */ +#define SEALEVEL_2103_PID 0x2103 /* SeaLINK+232I (2103) */ +#define SEALEVEL_2104_PID 0x2104 /* SeaLINK+485I (2104) */ +#define SEALEVEL_2106_PID 0x9020 /* SeaLINK+422 (2106) */ +#define SEALEVEL_2201_1_PID 0x2211 /* SeaPORT+2/232 (2201) Port 1 */ +#define SEALEVEL_2201_2_PID 0x2221 /* SeaPORT+2/232 (2201) Port 2 */ +#define SEALEVEL_2202_1_PID 0x2212 /* SeaPORT+2/485 (2202) Port 1 */ +#define SEALEVEL_2202_2_PID 0x2222 /* SeaPORT+2/485 (2202) Port 2 */ +#define SEALEVEL_2203_1_PID 0x2213 /* SeaPORT+2 (2203) Port 1 */ +#define SEALEVEL_2203_2_PID 0x2223 /* SeaPORT+2 (2203) Port 2 */ +#define SEALEVEL_2401_1_PID 0x2411 /* SeaPORT+4/232 (2401) Port 1 */ +#define SEALEVEL_2401_2_PID 0x2421 /* SeaPORT+4/232 (2401) Port 2 */ +#define SEALEVEL_2401_3_PID 0x2431 /* SeaPORT+4/232 (2401) Port 3 */ +#define SEALEVEL_2401_4_PID 0x2441 /* SeaPORT+4/232 (2401) Port 4 */ +#define SEALEVEL_2402_1_PID 0x2412 /* SeaPORT+4/485 (2402) Port 1 */ +#define SEALEVEL_2402_2_PID 0x2422 /* SeaPORT+4/485 (2402) Port 2 */ +#define SEALEVEL_2402_3_PID 0x2432 /* SeaPORT+4/485 (2402) Port 3 */ +#define SEALEVEL_2402_4_PID 0x2442 /* SeaPORT+4/485 (2402) Port 4 */ +#define SEALEVEL_2403_1_PID 0x2413 /* SeaPORT+4 (2403) Port 1 */ +#define SEALEVEL_2403_2_PID 0x2423 /* SeaPORT+4 (2403) Port 2 */ +#define SEALEVEL_2403_3_PID 0x2433 /* SeaPORT+4 (2403) Port 3 */ +#define SEALEVEL_2403_4_PID 0x2443 /* SeaPORT+4 (2403) Port 4 */ +#define SEALEVEL_2801_1_PID 0X2811 /* SeaLINK+8/232 (2801) Port 1 */ +#define SEALEVEL_2801_2_PID 0X2821 /* SeaLINK+8/232 (2801) Port 2 */ +#define SEALEVEL_2801_3_PID 0X2831 /* SeaLINK+8/232 (2801) Port 3 */ +#define SEALEVEL_2801_4_PID 0X2841 /* SeaLINK+8/232 (2801) Port 4 */ +#define SEALEVEL_2801_5_PID 0X2851 /* SeaLINK+8/232 (2801) Port 5 */ +#define SEALEVEL_2801_6_PID 0X2861 /* SeaLINK+8/232 (2801) Port 6 */ +#define SEALEVEL_2801_7_PID 0X2871 /* SeaLINK+8/232 (2801) Port 7 */ +#define SEALEVEL_2801_8_PID 0X2881 /* SeaLINK+8/232 (2801) Port 8 */ +#define SEALEVEL_2802_1_PID 0X2812 /* SeaLINK+8/485 (2802) Port 1 */ +#define SEALEVEL_2802_2_PID 0X2822 /* SeaLINK+8/485 (2802) Port 2 */ +#define SEALEVEL_2802_3_PID 0X2832 /* SeaLINK+8/485 (2802) Port 3 */ +#define SEALEVEL_2802_4_PID 0X2842 /* SeaLINK+8/485 (2802) Port 4 */ +#define SEALEVEL_2802_5_PID 0X2852 /* SeaLINK+8/485 (2802) Port 5 */ +#define SEALEVEL_2802_6_PID 0X2862 /* SeaLINK+8/485 (2802) Port 6 */ +#define SEALEVEL_2802_7_PID 0X2872 /* SeaLINK+8/485 (2802) Port 7 */ +#define SEALEVEL_2802_8_PID 0X2882 /* SeaLINK+8/485 (2802) Port 8 */ +#define SEALEVEL_2803_1_PID 0X2813 /* SeaLINK+8 (2803) Port 1 */ +#define SEALEVEL_2803_2_PID 0X2823 /* SeaLINK+8 (2803) Port 2 */ +#define SEALEVEL_2803_3_PID 0X2833 /* SeaLINK+8 (2803) Port 3 */ +#define SEALEVEL_2803_4_PID 0X2843 /* SeaLINK+8 (2803) Port 4 */ +#define SEALEVEL_2803_5_PID 0X2853 /* SeaLINK+8 (2803) Port 5 */ +#define SEALEVEL_2803_6_PID 0X2863 /* SeaLINK+8 (2803) Port 6 */ +#define SEALEVEL_2803_7_PID 0X2873 /* SeaLINK+8 (2803) Port 7 */ +#define SEALEVEL_2803_8_PID 0X2883 /* SeaLINK+8 (2803) Port 8 */ +#define SEALEVEL_2803R_1_PID 0Xa02a /* SeaLINK+8 (2803-ROHS) Port 1+2 */ +#define SEALEVEL_2803R_2_PID 0Xa02b /* SeaLINK+8 (2803-ROHS) Port 3+4 */ +#define SEALEVEL_2803R_3_PID 0Xa02c /* SeaLINK+8 (2803-ROHS) Port 5+6 */ +#define SEALEVEL_2803R_4_PID 0Xa02d /* SeaLINK+8 (2803-ROHS) Port 7+8 */ + +/* + * JETI SPECTROMETER SPECBOS 1201 + * http://www.jeti.com/cms/index.php/instruments/other-instruments/specbos-2101 + */ +#define JETI_VID 0x0c6c +#define JETI_SPC1201_PID 0x04b2 + +/* + * FTDI USB UART chips used in construction projects from the + * Elektor Electronics magazine (http://www.elektor.com/) + */ +#define ELEKTOR_VID 0x0C7D +#define ELEKTOR_FT323R_PID 0x0005 /* RFID-Reader, issue 09-2006 */ + +/* + * Posiflex inc retail equipment (http://www.posiflex.com.tw) + */ +#define POSIFLEX_VID 0x0d3a /* Vendor ID */ +#define POSIFLEX_PP7000_PID 0x0300 /* PP-7000II thermal printer */ + +/* + * The following are the values for two KOBIL chipcard terminals. + */ +#define KOBIL_VID 0x0d46 /* KOBIL Vendor ID */ +#define KOBIL_CONV_B1_PID 0x2020 /* KOBIL Konverter for B1 */ +#define KOBIL_CONV_KAAN_PID 0x2021 /* KOBIL_Konverter for KAAN */ + +#define FTDI_NF_RIC_VID 0x0DCD /* Vendor Id */ +#define FTDI_NF_RIC_PID 0x0001 /* Product Id */ + +/* + * Falcom Wireless Communications GmbH + */ +#define FALCOM_VID 0x0F94 /* Vendor Id */ +#define FALCOM_TWIST_PID 0x0001 /* Falcom Twist USB GPRS modem */ +#define FALCOM_SAMBA_PID 0x0005 /* Falcom Samba USB GPRS modem */ + +/* Larsen and Brusgaard AltiTrack/USBtrack */ +#define LARSENBRUSGAARD_VID 0x0FD8 +#define LB_ALTITRACK_PID 0x0001 + +/* + * TTi (Thurlby Thandar Instruments) + */ +#define TTI_VID 0x103E /* Vendor Id */ +#define TTI_QL355P_PID 0x03E8 /* TTi QL355P power supply */ + +/* + * Newport Cooperation (www.newport.com) + */ +#define NEWPORT_VID 0x104D +#define NEWPORT_AGILIS_PID 0x3000 +#define NEWPORT_CONEX_CC_PID 0x3002 +#define NEWPORT_CONEX_AGP_PID 0x3006 + +/* Interbiometrics USB I/O Board */ +/* Developed for Interbiometrics by Rudolf Gugler */ +#define INTERBIOMETRICS_VID 0x1209 +#define INTERBIOMETRICS_IOBOARD_PID 0x1002 +#define INTERBIOMETRICS_MINI_IOBOARD_PID 0x1006 + +/* + * Testo products (http://www.testo.com/) + * Submitted by Colin Leroy + */ +#define TESTO_VID 0x128D +#define TESTO_1_PID 0x0001 +#define TESTO_3_PID 0x0003 + +/* + * Mobility Electronics products. + */ +#define MOBILITY_VID 0x1342 +#define MOBILITY_USB_SERIAL_PID 0x0202 /* EasiDock USB 200 serial */ + +/* + * FIC / OpenMoko, Inc. http://wiki.openmoko.org/wiki/Neo1973_Debug_Board_v3 + * Submitted by Harald Welte + */ +#define FIC_VID 0x1457 +#define FIC_NEO1973_DEBUG_PID 0x5118 + +/* Olimex */ +#define OLIMEX_VID 0x15BA +#define OLIMEX_ARM_USB_OCD_PID 0x0003 +#define OLIMEX_ARM_USB_OCD_H_PID 0x002b + +/* + * Telldus Technologies + */ +#define TELLDUS_VID 0x1781 /* Vendor ID */ +#define TELLDUS_TELLSTICK_PID 0x0C30 /* RF control dongle 433 MHz using FT232RL */ + +/* + * NOVITUS printers + */ +#define NOVITUS_VID 0x1a28 +#define NOVITUS_BONO_E_PID 0x6010 + +/* + * RT Systems programming cables for various ham radios + */ +#define RTSYSTEMS_VID 0x2100 /* Vendor ID */ +#define RTSYSTEMS_USB_S03_PID 0x9001 /* RTS-03 USB to Serial Adapter */ +#define RTSYSTEMS_USB_59_PID 0x9e50 /* USB-59 USB to 8 pin plug */ +#define RTSYSTEMS_USB_57A_PID 0x9e51 /* USB-57A USB to 4pin 3.5mm plug */ +#define RTSYSTEMS_USB_57B_PID 0x9e52 /* USB-57B USB to extended 4pin 3.5mm plug */ +#define RTSYSTEMS_USB_29A_PID 0x9e53 /* USB-29A USB to 3.5mm stereo plug */ +#define RTSYSTEMS_USB_29B_PID 0x9e54 /* USB-29B USB to 6 pin mini din */ +#define RTSYSTEMS_USB_29F_PID 0x9e55 /* USB-29F USB to 6 pin modular plug */ +#define RTSYSTEMS_USB_62B_PID 0x9e56 /* USB-62B USB to 8 pin mini din plug*/ +#define RTSYSTEMS_USB_S01_PID 0x9e57 /* USB-RTS01 USB to 3.5 mm stereo plug*/ +#define RTSYSTEMS_USB_63_PID 0x9e58 /* USB-63 USB to 9 pin female*/ +#define RTSYSTEMS_USB_29C_PID 0x9e59 /* USB-29C USB to 4 pin modular plug*/ +#define RTSYSTEMS_USB_81B_PID 0x9e5A /* USB-81 USB to 8 pin mini din plug*/ +#define RTSYSTEMS_USB_82B_PID 0x9e5B /* USB-82 USB to 2.5 mm stereo plug*/ +#define RTSYSTEMS_USB_K5D_PID 0x9e5C /* USB-K5D USB to 8 pin modular plug*/ +#define RTSYSTEMS_USB_K4Y_PID 0x9e5D /* USB-K4Y USB to 2.5/3.5 mm plugs*/ +#define RTSYSTEMS_USB_K5G_PID 0x9e5E /* USB-K5G USB to 8 pin modular plug*/ +#define RTSYSTEMS_USB_S05_PID 0x9e5F /* USB-RTS05 USB to 2.5 mm stereo plug*/ +#define RTSYSTEMS_USB_60_PID 0x9e60 /* USB-60 USB to 6 pin din*/ +#define RTSYSTEMS_USB_61_PID 0x9e61 /* USB-61 USB to 6 pin mini din*/ +#define RTSYSTEMS_USB_62_PID 0x9e62 /* USB-62 USB to 8 pin mini din*/ +#define RTSYSTEMS_USB_63B_PID 0x9e63 /* USB-63 USB to 9 pin female*/ +#define RTSYSTEMS_USB_64_PID 0x9e64 /* USB-64 USB to 9 pin male*/ +#define RTSYSTEMS_USB_65_PID 0x9e65 /* USB-65 USB to 9 pin female null modem*/ +#define RTSYSTEMS_USB_92_PID 0x9e66 /* USB-92 USB to 12 pin plug*/ +#define RTSYSTEMS_USB_92D_PID 0x9e67 /* USB-92D USB to 12 pin plug data*/ +#define RTSYSTEMS_USB_W5R_PID 0x9e68 /* USB-W5R USB to 8 pin modular plug*/ +#define RTSYSTEMS_USB_A5R_PID 0x9e69 /* USB-A5R USB to 8 pin modular plug*/ +#define RTSYSTEMS_USB_PW1_PID 0x9e6A /* USB-PW1 USB to 8 pin modular plug*/ + +/* + * Physik Instrumente + * http://www.physikinstrumente.com/en/products/ + */ +/* These two devices use the VID of FTDI */ +#define PI_C865_PID 0xe0a0 /* PI C-865 Piezomotor Controller */ +#define PI_C857_PID 0xe0a1 /* PI Encoder Trigger Box */ + +#define PI_VID 0x1a72 /* Vendor ID */ +#define PI_C866_PID 0x1000 /* PI C-866 Piezomotor Controller */ +#define PI_C663_PID 0x1001 /* PI C-663 Mercury-Step */ +#define PI_C725_PID 0x1002 /* PI C-725 Piezomotor Controller */ +#define PI_E517_PID 0x1005 /* PI E-517 Digital Piezo Controller Operation Module */ +#define PI_C863_PID 0x1007 /* PI C-863 */ +#define PI_E861_PID 0x1008 /* PI E-861 Piezomotor Controller */ +#define PI_C867_PID 0x1009 /* PI C-867 Piezomotor Controller */ +#define PI_E609_PID 0x100D /* PI E-609 Digital Piezo Controller */ +#define PI_E709_PID 0x100E /* PI E-709 Digital Piezo Controller */ +#define PI_100F_PID 0x100F /* PI Digital Piezo Controller */ +#define PI_1011_PID 0x1011 /* PI Digital Piezo Controller */ +#define PI_1012_PID 0x1012 /* PI Motion Controller */ +#define PI_1013_PID 0x1013 /* PI Motion Controller */ +#define PI_1014_PID 0x1014 /* PI Device */ +#define PI_1015_PID 0x1015 /* PI Device */ +#define PI_1016_PID 0x1016 /* PI Digital Servo Module */ + +/* + * Kondo Kagaku Co.Ltd. + * http://www.kondo-robot.com/EN + */ +#define KONDO_VID 0x165c +#define KONDO_USB_SERIAL_PID 0x0002 + +/* + * Bayer Ascensia Contour blood glucose meter USB-converter cable. + * http://winglucofacts.com/cables/ + */ +#define BAYER_VID 0x1A79 +#define BAYER_CONTOUR_CABLE_PID 0x6001 + +/* + * Matrix Orbital Intelligent USB displays. + * http://www.matrixorbital.com + */ +#define MTXORB_VID 0x1B3D +#define MTXORB_FTDI_RANGE_0100_PID 0x0100 +#define MTXORB_FTDI_RANGE_0101_PID 0x0101 +#define MTXORB_FTDI_RANGE_0102_PID 0x0102 +#define MTXORB_FTDI_RANGE_0103_PID 0x0103 +#define MTXORB_FTDI_RANGE_0104_PID 0x0104 +#define MTXORB_FTDI_RANGE_0105_PID 0x0105 +#define MTXORB_FTDI_RANGE_0106_PID 0x0106 +#define MTXORB_FTDI_RANGE_0107_PID 0x0107 +#define MTXORB_FTDI_RANGE_0108_PID 0x0108 +#define MTXORB_FTDI_RANGE_0109_PID 0x0109 +#define MTXORB_FTDI_RANGE_010A_PID 0x010A +#define MTXORB_FTDI_RANGE_010B_PID 0x010B +#define MTXORB_FTDI_RANGE_010C_PID 0x010C +#define MTXORB_FTDI_RANGE_010D_PID 0x010D +#define MTXORB_FTDI_RANGE_010E_PID 0x010E +#define MTXORB_FTDI_RANGE_010F_PID 0x010F +#define MTXORB_FTDI_RANGE_0110_PID 0x0110 +#define MTXORB_FTDI_RANGE_0111_PID 0x0111 +#define MTXORB_FTDI_RANGE_0112_PID 0x0112 +#define MTXORB_FTDI_RANGE_0113_PID 0x0113 +#define MTXORB_FTDI_RANGE_0114_PID 0x0114 +#define MTXORB_FTDI_RANGE_0115_PID 0x0115 +#define MTXORB_FTDI_RANGE_0116_PID 0x0116 +#define MTXORB_FTDI_RANGE_0117_PID 0x0117 +#define MTXORB_FTDI_RANGE_0118_PID 0x0118 +#define MTXORB_FTDI_RANGE_0119_PID 0x0119 +#define MTXORB_FTDI_RANGE_011A_PID 0x011A +#define MTXORB_FTDI_RANGE_011B_PID 0x011B +#define MTXORB_FTDI_RANGE_011C_PID 0x011C +#define MTXORB_FTDI_RANGE_011D_PID 0x011D +#define MTXORB_FTDI_RANGE_011E_PID 0x011E +#define MTXORB_FTDI_RANGE_011F_PID 0x011F +#define MTXORB_FTDI_RANGE_0120_PID 0x0120 +#define MTXORB_FTDI_RANGE_0121_PID 0x0121 +#define MTXORB_FTDI_RANGE_0122_PID 0x0122 +#define MTXORB_FTDI_RANGE_0123_PID 0x0123 +#define MTXORB_FTDI_RANGE_0124_PID 0x0124 +#define MTXORB_FTDI_RANGE_0125_PID 0x0125 +#define MTXORB_FTDI_RANGE_0126_PID 0x0126 +#define MTXORB_FTDI_RANGE_0127_PID 0x0127 +#define MTXORB_FTDI_RANGE_0128_PID 0x0128 +#define MTXORB_FTDI_RANGE_0129_PID 0x0129 +#define MTXORB_FTDI_RANGE_012A_PID 0x012A +#define MTXORB_FTDI_RANGE_012B_PID 0x012B +#define MTXORB_FTDI_RANGE_012C_PID 0x012C +#define MTXORB_FTDI_RANGE_012D_PID 0x012D +#define MTXORB_FTDI_RANGE_012E_PID 0x012E +#define MTXORB_FTDI_RANGE_012F_PID 0x012F +#define MTXORB_FTDI_RANGE_0130_PID 0x0130 +#define MTXORB_FTDI_RANGE_0131_PID 0x0131 +#define MTXORB_FTDI_RANGE_0132_PID 0x0132 +#define MTXORB_FTDI_RANGE_0133_PID 0x0133 +#define MTXORB_FTDI_RANGE_0134_PID 0x0134 +#define MTXORB_FTDI_RANGE_0135_PID 0x0135 +#define MTXORB_FTDI_RANGE_0136_PID 0x0136 +#define MTXORB_FTDI_RANGE_0137_PID 0x0137 +#define MTXORB_FTDI_RANGE_0138_PID 0x0138 +#define MTXORB_FTDI_RANGE_0139_PID 0x0139 +#define MTXORB_FTDI_RANGE_013A_PID 0x013A +#define MTXORB_FTDI_RANGE_013B_PID 0x013B +#define MTXORB_FTDI_RANGE_013C_PID 0x013C +#define MTXORB_FTDI_RANGE_013D_PID 0x013D +#define MTXORB_FTDI_RANGE_013E_PID 0x013E +#define MTXORB_FTDI_RANGE_013F_PID 0x013F +#define MTXORB_FTDI_RANGE_0140_PID 0x0140 +#define MTXORB_FTDI_RANGE_0141_PID 0x0141 +#define MTXORB_FTDI_RANGE_0142_PID 0x0142 +#define MTXORB_FTDI_RANGE_0143_PID 0x0143 +#define MTXORB_FTDI_RANGE_0144_PID 0x0144 +#define MTXORB_FTDI_RANGE_0145_PID 0x0145 +#define MTXORB_FTDI_RANGE_0146_PID 0x0146 +#define MTXORB_FTDI_RANGE_0147_PID 0x0147 +#define MTXORB_FTDI_RANGE_0148_PID 0x0148 +#define MTXORB_FTDI_RANGE_0149_PID 0x0149 +#define MTXORB_FTDI_RANGE_014A_PID 0x014A +#define MTXORB_FTDI_RANGE_014B_PID 0x014B +#define MTXORB_FTDI_RANGE_014C_PID 0x014C +#define MTXORB_FTDI_RANGE_014D_PID 0x014D +#define MTXORB_FTDI_RANGE_014E_PID 0x014E +#define MTXORB_FTDI_RANGE_014F_PID 0x014F +#define MTXORB_FTDI_RANGE_0150_PID 0x0150 +#define MTXORB_FTDI_RANGE_0151_PID 0x0151 +#define MTXORB_FTDI_RANGE_0152_PID 0x0152 +#define MTXORB_FTDI_RANGE_0153_PID 0x0153 +#define MTXORB_FTDI_RANGE_0154_PID 0x0154 +#define MTXORB_FTDI_RANGE_0155_PID 0x0155 +#define MTXORB_FTDI_RANGE_0156_PID 0x0156 +#define MTXORB_FTDI_RANGE_0157_PID 0x0157 +#define MTXORB_FTDI_RANGE_0158_PID 0x0158 +#define MTXORB_FTDI_RANGE_0159_PID 0x0159 +#define MTXORB_FTDI_RANGE_015A_PID 0x015A +#define MTXORB_FTDI_RANGE_015B_PID 0x015B +#define MTXORB_FTDI_RANGE_015C_PID 0x015C +#define MTXORB_FTDI_RANGE_015D_PID 0x015D +#define MTXORB_FTDI_RANGE_015E_PID 0x015E +#define MTXORB_FTDI_RANGE_015F_PID 0x015F +#define MTXORB_FTDI_RANGE_0160_PID 0x0160 +#define MTXORB_FTDI_RANGE_0161_PID 0x0161 +#define MTXORB_FTDI_RANGE_0162_PID 0x0162 +#define MTXORB_FTDI_RANGE_0163_PID 0x0163 +#define MTXORB_FTDI_RANGE_0164_PID 0x0164 +#define MTXORB_FTDI_RANGE_0165_PID 0x0165 +#define MTXORB_FTDI_RANGE_0166_PID 0x0166 +#define MTXORB_FTDI_RANGE_0167_PID 0x0167 +#define MTXORB_FTDI_RANGE_0168_PID 0x0168 +#define MTXORB_FTDI_RANGE_0169_PID 0x0169 +#define MTXORB_FTDI_RANGE_016A_PID 0x016A +#define MTXORB_FTDI_RANGE_016B_PID 0x016B +#define MTXORB_FTDI_RANGE_016C_PID 0x016C +#define MTXORB_FTDI_RANGE_016D_PID 0x016D +#define MTXORB_FTDI_RANGE_016E_PID 0x016E +#define MTXORB_FTDI_RANGE_016F_PID 0x016F +#define MTXORB_FTDI_RANGE_0170_PID 0x0170 +#define MTXORB_FTDI_RANGE_0171_PID 0x0171 +#define MTXORB_FTDI_RANGE_0172_PID 0x0172 +#define MTXORB_FTDI_RANGE_0173_PID 0x0173 +#define MTXORB_FTDI_RANGE_0174_PID 0x0174 +#define MTXORB_FTDI_RANGE_0175_PID 0x0175 +#define MTXORB_FTDI_RANGE_0176_PID 0x0176 +#define MTXORB_FTDI_RANGE_0177_PID 0x0177 +#define MTXORB_FTDI_RANGE_0178_PID 0x0178 +#define MTXORB_FTDI_RANGE_0179_PID 0x0179 +#define MTXORB_FTDI_RANGE_017A_PID 0x017A +#define MTXORB_FTDI_RANGE_017B_PID 0x017B +#define MTXORB_FTDI_RANGE_017C_PID 0x017C +#define MTXORB_FTDI_RANGE_017D_PID 0x017D +#define MTXORB_FTDI_RANGE_017E_PID 0x017E +#define MTXORB_FTDI_RANGE_017F_PID 0x017F +#define MTXORB_FTDI_RANGE_0180_PID 0x0180 +#define MTXORB_FTDI_RANGE_0181_PID 0x0181 +#define MTXORB_FTDI_RANGE_0182_PID 0x0182 +#define MTXORB_FTDI_RANGE_0183_PID 0x0183 +#define MTXORB_FTDI_RANGE_0184_PID 0x0184 +#define MTXORB_FTDI_RANGE_0185_PID 0x0185 +#define MTXORB_FTDI_RANGE_0186_PID 0x0186 +#define MTXORB_FTDI_RANGE_0187_PID 0x0187 +#define MTXORB_FTDI_RANGE_0188_PID 0x0188 +#define MTXORB_FTDI_RANGE_0189_PID 0x0189 +#define MTXORB_FTDI_RANGE_018A_PID 0x018A +#define MTXORB_FTDI_RANGE_018B_PID 0x018B +#define MTXORB_FTDI_RANGE_018C_PID 0x018C +#define MTXORB_FTDI_RANGE_018D_PID 0x018D +#define MTXORB_FTDI_RANGE_018E_PID 0x018E +#define MTXORB_FTDI_RANGE_018F_PID 0x018F +#define MTXORB_FTDI_RANGE_0190_PID 0x0190 +#define MTXORB_FTDI_RANGE_0191_PID 0x0191 +#define MTXORB_FTDI_RANGE_0192_PID 0x0192 +#define MTXORB_FTDI_RANGE_0193_PID 0x0193 +#define MTXORB_FTDI_RANGE_0194_PID 0x0194 +#define MTXORB_FTDI_RANGE_0195_PID 0x0195 +#define MTXORB_FTDI_RANGE_0196_PID 0x0196 +#define MTXORB_FTDI_RANGE_0197_PID 0x0197 +#define MTXORB_FTDI_RANGE_0198_PID 0x0198 +#define MTXORB_FTDI_RANGE_0199_PID 0x0199 +#define MTXORB_FTDI_RANGE_019A_PID 0x019A +#define MTXORB_FTDI_RANGE_019B_PID 0x019B +#define MTXORB_FTDI_RANGE_019C_PID 0x019C +#define MTXORB_FTDI_RANGE_019D_PID 0x019D +#define MTXORB_FTDI_RANGE_019E_PID 0x019E +#define MTXORB_FTDI_RANGE_019F_PID 0x019F +#define MTXORB_FTDI_RANGE_01A0_PID 0x01A0 +#define MTXORB_FTDI_RANGE_01A1_PID 0x01A1 +#define MTXORB_FTDI_RANGE_01A2_PID 0x01A2 +#define MTXORB_FTDI_RANGE_01A3_PID 0x01A3 +#define MTXORB_FTDI_RANGE_01A4_PID 0x01A4 +#define MTXORB_FTDI_RANGE_01A5_PID 0x01A5 +#define MTXORB_FTDI_RANGE_01A6_PID 0x01A6 +#define MTXORB_FTDI_RANGE_01A7_PID 0x01A7 +#define MTXORB_FTDI_RANGE_01A8_PID 0x01A8 +#define MTXORB_FTDI_RANGE_01A9_PID 0x01A9 +#define MTXORB_FTDI_RANGE_01AA_PID 0x01AA +#define MTXORB_FTDI_RANGE_01AB_PID 0x01AB +#define MTXORB_FTDI_RANGE_01AC_PID 0x01AC +#define MTXORB_FTDI_RANGE_01AD_PID 0x01AD +#define MTXORB_FTDI_RANGE_01AE_PID 0x01AE +#define MTXORB_FTDI_RANGE_01AF_PID 0x01AF +#define MTXORB_FTDI_RANGE_01B0_PID 0x01B0 +#define MTXORB_FTDI_RANGE_01B1_PID 0x01B1 +#define MTXORB_FTDI_RANGE_01B2_PID 0x01B2 +#define MTXORB_FTDI_RANGE_01B3_PID 0x01B3 +#define MTXORB_FTDI_RANGE_01B4_PID 0x01B4 +#define MTXORB_FTDI_RANGE_01B5_PID 0x01B5 +#define MTXORB_FTDI_RANGE_01B6_PID 0x01B6 +#define MTXORB_FTDI_RANGE_01B7_PID 0x01B7 +#define MTXORB_FTDI_RANGE_01B8_PID 0x01B8 +#define MTXORB_FTDI_RANGE_01B9_PID 0x01B9 +#define MTXORB_FTDI_RANGE_01BA_PID 0x01BA +#define MTXORB_FTDI_RANGE_01BB_PID 0x01BB +#define MTXORB_FTDI_RANGE_01BC_PID 0x01BC +#define MTXORB_FTDI_RANGE_01BD_PID 0x01BD +#define MTXORB_FTDI_RANGE_01BE_PID 0x01BE +#define MTXORB_FTDI_RANGE_01BF_PID 0x01BF +#define MTXORB_FTDI_RANGE_01C0_PID 0x01C0 +#define MTXORB_FTDI_RANGE_01C1_PID 0x01C1 +#define MTXORB_FTDI_RANGE_01C2_PID 0x01C2 +#define MTXORB_FTDI_RANGE_01C3_PID 0x01C3 +#define MTXORB_FTDI_RANGE_01C4_PID 0x01C4 +#define MTXORB_FTDI_RANGE_01C5_PID 0x01C5 +#define MTXORB_FTDI_RANGE_01C6_PID 0x01C6 +#define MTXORB_FTDI_RANGE_01C7_PID 0x01C7 +#define MTXORB_FTDI_RANGE_01C8_PID 0x01C8 +#define MTXORB_FTDI_RANGE_01C9_PID 0x01C9 +#define MTXORB_FTDI_RANGE_01CA_PID 0x01CA +#define MTXORB_FTDI_RANGE_01CB_PID 0x01CB +#define MTXORB_FTDI_RANGE_01CC_PID 0x01CC +#define MTXORB_FTDI_RANGE_01CD_PID 0x01CD +#define MTXORB_FTDI_RANGE_01CE_PID 0x01CE +#define MTXORB_FTDI_RANGE_01CF_PID 0x01CF +#define MTXORB_FTDI_RANGE_01D0_PID 0x01D0 +#define MTXORB_FTDI_RANGE_01D1_PID 0x01D1 +#define MTXORB_FTDI_RANGE_01D2_PID 0x01D2 +#define MTXORB_FTDI_RANGE_01D3_PID 0x01D3 +#define MTXORB_FTDI_RANGE_01D4_PID 0x01D4 +#define MTXORB_FTDI_RANGE_01D5_PID 0x01D5 +#define MTXORB_FTDI_RANGE_01D6_PID 0x01D6 +#define MTXORB_FTDI_RANGE_01D7_PID 0x01D7 +#define MTXORB_FTDI_RANGE_01D8_PID 0x01D8 +#define MTXORB_FTDI_RANGE_01D9_PID 0x01D9 +#define MTXORB_FTDI_RANGE_01DA_PID 0x01DA +#define MTXORB_FTDI_RANGE_01DB_PID 0x01DB +#define MTXORB_FTDI_RANGE_01DC_PID 0x01DC +#define MTXORB_FTDI_RANGE_01DD_PID 0x01DD +#define MTXORB_FTDI_RANGE_01DE_PID 0x01DE +#define MTXORB_FTDI_RANGE_01DF_PID 0x01DF +#define MTXORB_FTDI_RANGE_01E0_PID 0x01E0 +#define MTXORB_FTDI_RANGE_01E1_PID 0x01E1 +#define MTXORB_FTDI_RANGE_01E2_PID 0x01E2 +#define MTXORB_FTDI_RANGE_01E3_PID 0x01E3 +#define MTXORB_FTDI_RANGE_01E4_PID 0x01E4 +#define MTXORB_FTDI_RANGE_01E5_PID 0x01E5 +#define MTXORB_FTDI_RANGE_01E6_PID 0x01E6 +#define MTXORB_FTDI_RANGE_01E7_PID 0x01E7 +#define MTXORB_FTDI_RANGE_01E8_PID 0x01E8 +#define MTXORB_FTDI_RANGE_01E9_PID 0x01E9 +#define MTXORB_FTDI_RANGE_01EA_PID 0x01EA +#define MTXORB_FTDI_RANGE_01EB_PID 0x01EB +#define MTXORB_FTDI_RANGE_01EC_PID 0x01EC +#define MTXORB_FTDI_RANGE_01ED_PID 0x01ED +#define MTXORB_FTDI_RANGE_01EE_PID 0x01EE +#define MTXORB_FTDI_RANGE_01EF_PID 0x01EF +#define MTXORB_FTDI_RANGE_01F0_PID 0x01F0 +#define MTXORB_FTDI_RANGE_01F1_PID 0x01F1 +#define MTXORB_FTDI_RANGE_01F2_PID 0x01F2 +#define MTXORB_FTDI_RANGE_01F3_PID 0x01F3 +#define MTXORB_FTDI_RANGE_01F4_PID 0x01F4 +#define MTXORB_FTDI_RANGE_01F5_PID 0x01F5 +#define MTXORB_FTDI_RANGE_01F6_PID 0x01F6 +#define MTXORB_FTDI_RANGE_01F7_PID 0x01F7 +#define MTXORB_FTDI_RANGE_01F8_PID 0x01F8 +#define MTXORB_FTDI_RANGE_01F9_PID 0x01F9 +#define MTXORB_FTDI_RANGE_01FA_PID 0x01FA +#define MTXORB_FTDI_RANGE_01FB_PID 0x01FB +#define MTXORB_FTDI_RANGE_01FC_PID 0x01FC +#define MTXORB_FTDI_RANGE_01FD_PID 0x01FD +#define MTXORB_FTDI_RANGE_01FE_PID 0x01FE +#define MTXORB_FTDI_RANGE_01FF_PID 0x01FF +#define MTXORB_FTDI_RANGE_4701_PID 0x4701 +#define MTXORB_FTDI_RANGE_9300_PID 0x9300 +#define MTXORB_FTDI_RANGE_9301_PID 0x9301 +#define MTXORB_FTDI_RANGE_9302_PID 0x9302 +#define MTXORB_FTDI_RANGE_9303_PID 0x9303 +#define MTXORB_FTDI_RANGE_9304_PID 0x9304 +#define MTXORB_FTDI_RANGE_9305_PID 0x9305 +#define MTXORB_FTDI_RANGE_9306_PID 0x9306 +#define MTXORB_FTDI_RANGE_9307_PID 0x9307 +#define MTXORB_FTDI_RANGE_9308_PID 0x9308 +#define MTXORB_FTDI_RANGE_9309_PID 0x9309 +#define MTXORB_FTDI_RANGE_930A_PID 0x930A +#define MTXORB_FTDI_RANGE_930B_PID 0x930B +#define MTXORB_FTDI_RANGE_930C_PID 0x930C +#define MTXORB_FTDI_RANGE_930D_PID 0x930D +#define MTXORB_FTDI_RANGE_930E_PID 0x930E +#define MTXORB_FTDI_RANGE_930F_PID 0x930F +#define MTXORB_FTDI_RANGE_9310_PID 0x9310 +#define MTXORB_FTDI_RANGE_9311_PID 0x9311 +#define MTXORB_FTDI_RANGE_9312_PID 0x9312 +#define MTXORB_FTDI_RANGE_9313_PID 0x9313 +#define MTXORB_FTDI_RANGE_9314_PID 0x9314 +#define MTXORB_FTDI_RANGE_9315_PID 0x9315 +#define MTXORB_FTDI_RANGE_9316_PID 0x9316 +#define MTXORB_FTDI_RANGE_9317_PID 0x9317 +#define MTXORB_FTDI_RANGE_9318_PID 0x9318 +#define MTXORB_FTDI_RANGE_9319_PID 0x9319 +#define MTXORB_FTDI_RANGE_931A_PID 0x931A +#define MTXORB_FTDI_RANGE_931B_PID 0x931B +#define MTXORB_FTDI_RANGE_931C_PID 0x931C +#define MTXORB_FTDI_RANGE_931D_PID 0x931D +#define MTXORB_FTDI_RANGE_931E_PID 0x931E +#define MTXORB_FTDI_RANGE_931F_PID 0x931F + +/* + * The Mobility Lab (TML) + * Submitted by Pierre Castella + */ +#define TML_VID 0x1B91 /* Vendor ID */ +#define TML_USB_SERIAL_PID 0x0064 /* USB - Serial Converter */ + +/* Alti-2 products http://www.alti-2.com */ +#define ALTI2_VID 0x1BC9 +#define ALTI2_N3_PID 0x6001 /* Neptune 3 */ + +/* + * Ionics PlugComputer + */ +#define IONICS_VID 0x1c0c +#define IONICS_PLUGCOMPUTER_PID 0x0102 + +/* + * Dresden Elektronik Sensor Terminal Board + */ +#define DE_VID 0x1cf1 /* Vendor ID */ +#define STB_PID 0x0001 /* Sensor Terminal Board */ +#define WHT_PID 0x0004 /* Wireless Handheld Terminal */ + +/* + * STMicroelectonics + */ +#define ST_VID 0x0483 +#define ST_STMCLT_2232_PID 0x3746 +#define ST_STMCLT_4232_PID 0x3747 + +/* + * Papouch products (http://www.papouch.com/) + * Submitted by Folkert van Heusden + */ + +#define PAPOUCH_VID 0x5050 /* Vendor ID */ +#define PAPOUCH_SB485_PID 0x0100 /* Papouch SB485 USB-485/422 Converter */ +#define PAPOUCH_AP485_PID 0x0101 /* AP485 USB-RS485 Converter */ +#define PAPOUCH_SB422_PID 0x0102 /* Papouch SB422 USB-RS422 Converter */ +#define PAPOUCH_SB485_2_PID 0x0103 /* Papouch SB485 USB-485/422 Converter */ +#define PAPOUCH_AP485_2_PID 0x0104 /* AP485 USB-RS485 Converter */ +#define PAPOUCH_SB422_2_PID 0x0105 /* Papouch SB422 USB-RS422 Converter */ +#define PAPOUCH_SB485S_PID 0x0106 /* Papouch SB485S USB-485/422 Converter */ +#define PAPOUCH_SB485C_PID 0x0107 /* Papouch SB485C USB-485/422 Converter */ +#define PAPOUCH_LEC_PID 0x0300 /* LEC USB Converter */ +#define PAPOUCH_SB232_PID 0x0301 /* Papouch SB232 USB-RS232 Converter */ +#define PAPOUCH_TMU_PID 0x0400 /* TMU USB Thermometer */ +#define PAPOUCH_IRAMP_PID 0x0500 /* Papouch IRAmp Duplex */ +#define PAPOUCH_DRAK5_PID 0x0700 /* Papouch DRAK5 */ +#define PAPOUCH_QUIDO8x8_PID 0x0800 /* Papouch Quido 8/8 Module */ +#define PAPOUCH_QUIDO4x4_PID 0x0900 /* Papouch Quido 4/4 Module */ +#define PAPOUCH_QUIDO2x2_PID 0x0a00 /* Papouch Quido 2/2 Module */ +#define PAPOUCH_QUIDO10x1_PID 0x0b00 /* Papouch Quido 10/1 Module */ +#define PAPOUCH_QUIDO30x3_PID 0x0c00 /* Papouch Quido 30/3 Module */ +#define PAPOUCH_QUIDO60x3_PID 0x0d00 /* Papouch Quido 60(100)/3 Module */ +#define PAPOUCH_QUIDO2x16_PID 0x0e00 /* Papouch Quido 2/16 Module */ +#define PAPOUCH_QUIDO3x32_PID 0x0f00 /* Papouch Quido 3/32 Module */ +#define PAPOUCH_DRAK6_PID 0x1000 /* Papouch DRAK6 */ +#define PAPOUCH_UPSUSB_PID 0x8000 /* Papouch UPS-USB adapter */ +#define PAPOUCH_MU_PID 0x8001 /* MU controller */ +#define PAPOUCH_SIMUKEY_PID 0x8002 /* Papouch SimuKey */ +#define PAPOUCH_AD4USB_PID 0x8003 /* AD4USB Measurement Module */ +#define PAPOUCH_GMUX_PID 0x8004 /* Papouch GOLIATH MUX */ +#define PAPOUCH_GMSR_PID 0x8005 /* Papouch GOLIATH MSR */ + +/* + * Marvell SheevaPlug + */ +#define MARVELL_VID 0x9e88 +#define MARVELL_SHEEVAPLUG_PID 0x9e8f + +/* + * Evolution Robotics products (http://www.evolution.com/). + * Submitted by Shawn M. Lavelle. + */ +#define EVOLUTION_VID 0xDEEE /* Vendor ID */ +#define EVOLUTION_ER1_PID 0x0300 /* ER1 Control Module */ +#define EVO_8U232AM_PID 0x02FF /* Evolution robotics RCM2 (FT232AM)*/ +#define EVO_HYBRID_PID 0x0302 /* Evolution robotics RCM4 PID (FT232BM)*/ +#define EVO_RCM4_PID 0x0303 /* Evolution robotics RCM4 PID */ + +/* + * MJS Gadgets HD Radio / XM Radio / Sirius Radio interfaces (using VID 0x0403) + */ +#define MJSG_GENERIC_PID 0x9378 +#define MJSG_SR_RADIO_PID 0x9379 +#define MJSG_XM_RADIO_PID 0x937A +#define MJSG_HD_RADIO_PID 0x937C + +/* + * D.O.Tec products (http://www.directout.eu) + */ +#define FTDI_DOTEC_PID 0x9868 + +/* + * Xverve Signalyzer tools (http://www.signalyzer.com/) + */ +#define XVERVE_SIGNALYZER_ST_PID 0xBCA0 +#define XVERVE_SIGNALYZER_SLITE_PID 0xBCA1 +#define XVERVE_SIGNALYZER_SH2_PID 0xBCA2 +#define XVERVE_SIGNALYZER_SH4_PID 0xBCA4 + +/* + * Segway Robotic Mobility Platform USB interface (using VID 0x0403) + * Submitted by John G. Rogers + */ +#define SEGWAY_RMP200_PID 0xe729 + + +/* + * Accesio USB Data Acquisition products (http://www.accesio.com/) + */ +#define ACCESIO_COM4SM_PID 0xD578 + +/* www.sciencescope.co.uk educational dataloggers */ +#define FTDI_SCIENCESCOPE_LOGBOOKML_PID 0xFF18 +#define FTDI_SCIENCESCOPE_LS_LOGBOOK_PID 0xFF1C +#define FTDI_SCIENCESCOPE_HS_LOGBOOK_PID 0xFF1D + +/* + * Milkymist One JTAG/Serial + */ +#define QIHARDWARE_VID 0x20B7 +#define MILKYMISTONE_JTAGSERIAL_PID 0x0713 + +/* + * CTI GmbH RS485 Converter http://www.cti-lean.com/ + */ +/* USB-485-Mini*/ +#define FTDI_CTI_MINI_PID 0xF608 +/* USB-Nano-485*/ +#define FTDI_CTI_NANO_PID 0xF60B + +/* + * ZeitControl cardsystems GmbH rfid-readers http://zeitconrol.de + */ +/* TagTracer MIFARE*/ +#define FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID 0xF7C0 + +/* + * Rainforest Automation + */ +/* ZigBee controller */ +#define FTDI_RF_R106 0x8A28 + +/* + * Product: HCP HIT GPRS modem + * Manufacturer: HCP d.o.o. + * ATI command output: Cinterion MC55i + */ +#define FTDI_CINTERION_MC55I_PID 0xA951 + +/* + * Product: Comet Caller ID decoder + * Manufacturer: Crucible Technologies + */ +#define FTDI_CT_COMET_PID 0x8e08 + +/* + * Product: Z3X Box + * Manufacturer: Smart GSM Team + */ +#define FTDI_Z3X_PID 0x0011 + +/* + * Product: Cressi PC Interface + * Manufacturer: Cressi + */ +#define FTDI_CRESSI_PID 0x87d0 + +/* + * Brainboxes devices + */ +#define BRAINBOXES_VID 0x05d1 +#define BRAINBOXES_VX_001_PID 0x1001 /* VX-001 ExpressCard 1 Port RS232 */ +#define BRAINBOXES_VX_012_PID 0x1002 /* VX-012 ExpressCard 2 Port RS232 */ +#define BRAINBOXES_VX_023_PID 0x1003 /* VX-023 ExpressCard 1 Port RS422/485 */ +#define BRAINBOXES_VX_034_PID 0x1004 /* VX-034 ExpressCard 2 Port RS422/485 */ +#define BRAINBOXES_US_101_PID 0x1011 /* US-101 1xRS232 */ +#define BRAINBOXES_US_324_PID 0x1013 /* US-324 1xRS422/485 1Mbaud */ +#define BRAINBOXES_US_606_1_PID 0x2001 /* US-606 6 Port RS232 Serial Port 1 and 2 */ +#define BRAINBOXES_US_606_2_PID 0x2002 /* US-606 6 Port RS232 Serial Port 3 and 4 */ +#define BRAINBOXES_US_606_3_PID 0x2003 /* US-606 6 Port RS232 Serial Port 4 and 6 */ +#define BRAINBOXES_US_701_1_PID 0x2011 /* US-701 4xRS232 1Mbaud Port 1 and 2 */ +#define BRAINBOXES_US_701_2_PID 0x2012 /* US-701 4xRS422 1Mbaud Port 3 and 4 */ +#define BRAINBOXES_US_279_1_PID 0x2021 /* US-279 8xRS422 1Mbaud Port 1 and 2 */ +#define BRAINBOXES_US_279_2_PID 0x2022 /* US-279 8xRS422 1Mbaud Port 3 and 4 */ +#define BRAINBOXES_US_279_3_PID 0x2023 /* US-279 8xRS422 1Mbaud Port 5 and 6 */ +#define BRAINBOXES_US_279_4_PID 0x2024 /* US-279 8xRS422 1Mbaud Port 7 and 8 */ +#define BRAINBOXES_US_346_1_PID 0x3011 /* US-346 4xRS422/485 1Mbaud Port 1 and 2 */ +#define BRAINBOXES_US_346_2_PID 0x3012 /* US-346 4xRS422/485 1Mbaud Port 3 and 4 */ +#define BRAINBOXES_US_257_PID 0x5001 /* US-257 2xRS232 1Mbaud */ +#define BRAINBOXES_US_313_PID 0x6001 /* US-313 2xRS422/485 1Mbaud */ +#define BRAINBOXES_US_357_PID 0x7001 /* US_357 1xRS232/422/485 */ +#define BRAINBOXES_US_842_1_PID 0x8001 /* US-842 8xRS422/485 1Mbaud Port 1 and 2 */ +#define BRAINBOXES_US_842_2_PID 0x8002 /* US-842 8xRS422/485 1Mbaud Port 3 and 4 */ +#define BRAINBOXES_US_842_3_PID 0x8003 /* US-842 8xRS422/485 1Mbaud Port 5 and 6 */ +#define BRAINBOXES_US_842_4_PID 0x8004 /* US-842 8xRS422/485 1Mbaud Port 7 and 8 */ +#define BRAINBOXES_US_160_1_PID 0x9001 /* US-160 16xRS232 1Mbaud Port 1 and 2 */ +#define BRAINBOXES_US_160_2_PID 0x9002 /* US-160 16xRS232 1Mbaud Port 3 and 4 */ +#define BRAINBOXES_US_160_3_PID 0x9003 /* US-160 16xRS232 1Mbaud Port 5 and 6 */ +#define BRAINBOXES_US_160_4_PID 0x9004 /* US-160 16xRS232 1Mbaud Port 7 and 8 */ +#define BRAINBOXES_US_160_5_PID 0x9005 /* US-160 16xRS232 1Mbaud Port 9 and 10 */ +#define BRAINBOXES_US_160_6_PID 0x9006 /* US-160 16xRS232 1Mbaud Port 11 and 12 */ +#define BRAINBOXES_US_160_7_PID 0x9007 /* US-160 16xRS232 1Mbaud Port 13 and 14 */ +#define BRAINBOXES_US_160_8_PID 0x9008 /* US-160 16xRS232 1Mbaud Port 15 and 16 */ + +/* + * ekey biometric systems GmbH (http://ekey.net/) + */ +#define FTDI_EKEY_CONV_USB_PID 0xCB08 /* Converter USB */ diff --git a/drivers/usb/serial/funsoft.c b/drivers/usb/serial/funsoft.c new file mode 100644 index 00000000..9362f8fd --- /dev/null +++ b/drivers/usb/serial/funsoft.c @@ -0,0 +1,40 @@ +/* + * Funsoft Serial USB driver + * + * Copyright (C) 2006 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x1404, 0xcddc) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver funsoft_device = { + .driver = { + .owner = THIS_MODULE, + .name = "funsoft", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &funsoft_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c new file mode 100644 index 00000000..b110c573 --- /dev/null +++ b/drivers/usb/serial/garmin_gps.c @@ -0,0 +1,1462 @@ +/* + * Garmin GPS driver + * + * Copyright (C) 2006-2011 Hermann Kneissel herkne@gmx.de + * + * The latest version of the driver can be found at + * http://sourceforge.net/projects/garmin-gps/ + * + * This driver has been derived from v2.1 of the visor driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* the mode to be set when the port ist opened */ +static int initial_mode = 1; + +#define GARMIN_VENDOR_ID 0x091E + +/* + * Version Information + */ + +#define VERSION_MAJOR 0 +#define VERSION_MINOR 36 + +#define _STR(s) #s +#define _DRIVER_VERSION(a, b) "v" _STR(a) "." _STR(b) +#define DRIVER_VERSION _DRIVER_VERSION(VERSION_MAJOR, VERSION_MINOR) +#define DRIVER_AUTHOR "hermann kneissel" +#define DRIVER_DESC "garmin gps driver" + +/* error codes returned by the driver */ +#define EINVPKT 1000 /* invalid packet structure */ + + +/* size of the header of a packet using the usb protocol */ +#define GARMIN_PKTHDR_LENGTH 12 + +/* max. possible size of a packet using the serial protocol */ +#define MAX_SERIAL_PKT_SIZ (3 + 255 + 3) + +/* max. possible size of a packet with worst case stuffing */ +#define MAX_SERIAL_PKT_SIZ_STUFFED (MAX_SERIAL_PKT_SIZ + 256) + +/* size of a buffer able to hold a complete (no stuffing) packet + * (the document protocol does not contain packets with a larger + * size, but in theory a packet may be 64k+12 bytes - if in + * later protocol versions larger packet sizes occur, this value + * should be increased accordingly, so the input buffer is always + * large enough the store a complete packet inclusive header) */ +#define GPS_IN_BUFSIZ (GARMIN_PKTHDR_LENGTH+MAX_SERIAL_PKT_SIZ) + +/* size of a buffer able to hold a complete (incl. stuffing) packet */ +#define GPS_OUT_BUFSIZ (GARMIN_PKTHDR_LENGTH+MAX_SERIAL_PKT_SIZ_STUFFED) + +/* where to place the packet id of a serial packet, so we can + * prepend the usb-packet header without the need to move the + * packets data */ +#define GSP_INITIAL_OFFSET (GARMIN_PKTHDR_LENGTH-2) + +/* max. size of incoming private packets (header+1 param) */ +#define PRIVPKTSIZ (GARMIN_PKTHDR_LENGTH+4) + +#define GARMIN_LAYERID_TRANSPORT 0 +#define GARMIN_LAYERID_APPL 20 +/* our own layer-id to use for some control mechanisms */ +#define GARMIN_LAYERID_PRIVATE 0x01106E4B + +#define GARMIN_PKTID_PVT_DATA 51 +#define GARMIN_PKTID_L001_COMMAND_DATA 10 + +#define CMND_ABORT_TRANSFER 0 + +/* packet ids used in private layer */ +#define PRIV_PKTID_SET_DEBUG 1 +#define PRIV_PKTID_SET_MODE 2 +#define PRIV_PKTID_INFO_REQ 3 +#define PRIV_PKTID_INFO_RESP 4 +#define PRIV_PKTID_RESET_REQ 5 +#define PRIV_PKTID_SET_DEF_MODE 6 + + +#define ETX 0x03 +#define DLE 0x10 +#define ACK 0x06 +#define NAK 0x15 + +/* structure used to queue incoming packets */ +struct garmin_packet { + struct list_head list; + int seq; + /* the real size of the data array, always > 0 */ + int size; + __u8 data[1]; +}; + +/* structure used to keep the current state of the driver */ +struct garmin_data { + __u8 state; + __u16 flags; + __u8 mode; + __u8 count; + __u8 pkt_id; + __u32 serial_num; + struct timer_list timer; + struct usb_serial_port *port; + int seq_counter; + int insize; + int outsize; + __u8 inbuffer [GPS_IN_BUFSIZ]; /* tty -> usb */ + __u8 outbuffer[GPS_OUT_BUFSIZ]; /* usb -> tty */ + __u8 privpkt[4*6]; + spinlock_t lock; + struct list_head pktlist; +}; + + +#define STATE_NEW 0 +#define STATE_INITIAL_DELAY 1 +#define STATE_TIMEOUT 2 +#define STATE_SESSION_REQ1 3 +#define STATE_SESSION_REQ2 4 +#define STATE_ACTIVE 5 + +#define STATE_RESET 8 +#define STATE_DISCONNECTED 9 +#define STATE_WAIT_TTY_ACK 10 +#define STATE_GSP_WAIT_DATA 11 + +#define MODE_NATIVE 0 +#define MODE_GARMIN_SERIAL 1 + +/* Flags used in garmin_data.flags: */ +#define FLAGS_SESSION_REPLY_MASK 0x00C0 +#define FLAGS_SESSION_REPLY1_SEEN 0x0080 +#define FLAGS_SESSION_REPLY2_SEEN 0x0040 +#define FLAGS_BULK_IN_ACTIVE 0x0020 +#define FLAGS_BULK_IN_RESTART 0x0010 +#define FLAGS_THROTTLED 0x0008 +#define APP_REQ_SEEN 0x0004 +#define APP_RESP_SEEN 0x0002 +#define CLEAR_HALT_REQUIRED 0x0001 + +#define FLAGS_QUEUING 0x0100 +#define FLAGS_DROP_DATA 0x0800 + +#define FLAGS_GSP_SKIP 0x1000 +#define FLAGS_GSP_DLESEEN 0x2000 + + + + + + +/* function prototypes */ +static int gsp_next_packet(struct garmin_data *garmin_data_p); +static int garmin_write_bulk(struct usb_serial_port *port, + const unsigned char *buf, int count, + int dismiss_ack); + +/* some special packets to be send or received */ +static unsigned char const GARMIN_START_SESSION_REQ[] + = { 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0 }; +static unsigned char const GARMIN_START_SESSION_REPLY[] + = { 0, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0 }; +static unsigned char const GARMIN_BULK_IN_AVAIL_REPLY[] + = { 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 }; +static unsigned char const GARMIN_APP_LAYER_REPLY[] + = { 0x14, 0, 0, 0 }; +static unsigned char const GARMIN_START_PVT_REQ[] + = { 20, 0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 49, 0 }; +static unsigned char const GARMIN_STOP_PVT_REQ[] + = { 20, 0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 50, 0 }; +static unsigned char const GARMIN_STOP_TRANSFER_REQ[] + = { 20, 0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 0, 0 }; +static unsigned char const GARMIN_STOP_TRANSFER_REQ_V2[] + = { 20, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0 }; +static unsigned char const PRIVATE_REQ[] + = { 0x4B, 0x6E, 0x10, 0x01, 0xFF, 0, 0, 0, 0xFF, 0, 0, 0 }; + + + +static const struct usb_device_id id_table[] = { + /* the same device id seems to be used by all + usb enabled GPS devices */ + { USB_DEVICE(GARMIN_VENDOR_ID, 3) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + + +static inline int getLayerId(const __u8 *usbPacket) +{ + return __le32_to_cpup((__le32 *)(usbPacket)); +} + +static inline int getPacketId(const __u8 *usbPacket) +{ + return __le32_to_cpup((__le32 *)(usbPacket+4)); +} + +static inline int getDataLength(const __u8 *usbPacket) +{ + return __le32_to_cpup((__le32 *)(usbPacket+8)); +} + + +/* + * check if the usb-packet in buf contains an abort-transfer command. + * (if yes, all queued data will be dropped) + */ +static inline int isAbortTrfCmnd(const unsigned char *buf) +{ + if (0 == memcmp(buf, GARMIN_STOP_TRANSFER_REQ, + sizeof(GARMIN_STOP_TRANSFER_REQ)) || + 0 == memcmp(buf, GARMIN_STOP_TRANSFER_REQ_V2, + sizeof(GARMIN_STOP_TRANSFER_REQ_V2))) + return 1; + else + return 0; +} + + + +static void send_to_tty(struct usb_serial_port *port, + char *data, unsigned int actual_length) +{ + if (actual_length) { + usb_serial_debug_data(&port->dev, __func__, actual_length, data); + tty_insert_flip_string(&port->port, data, actual_length); + tty_flip_buffer_push(&port->port); + } +} + + +/****************************************************************************** + * packet queue handling + ******************************************************************************/ + +/* + * queue a received (usb-)packet for later processing + */ +static int pkt_add(struct garmin_data *garmin_data_p, + unsigned char *data, unsigned int data_length) +{ + int state = 0; + int result = 0; + unsigned long flags; + struct garmin_packet *pkt; + + /* process only packets containg data ... */ + if (data_length) { + pkt = kmalloc(sizeof(struct garmin_packet)+data_length, + GFP_ATOMIC); + if (pkt == NULL) { + dev_err(&garmin_data_p->port->dev, "out of memory\n"); + return 0; + } + pkt->size = data_length; + memcpy(pkt->data, data, data_length); + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= FLAGS_QUEUING; + result = list_empty(&garmin_data_p->pktlist); + pkt->seq = garmin_data_p->seq_counter++; + list_add_tail(&pkt->list, &garmin_data_p->pktlist); + state = garmin_data_p->state; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + dev_dbg(&garmin_data_p->port->dev, + "%s - added: pkt: %d - %d bytes\n", __func__, + pkt->seq, data_length); + + /* in serial mode, if someone is waiting for data from + the device, convert and send the next packet to tty. */ + if (result && (state == STATE_GSP_WAIT_DATA)) + gsp_next_packet(garmin_data_p); + } + return result; +} + + +/* get the next pending packet */ +static struct garmin_packet *pkt_pop(struct garmin_data *garmin_data_p) +{ + unsigned long flags; + struct garmin_packet *result = NULL; + + spin_lock_irqsave(&garmin_data_p->lock, flags); + if (!list_empty(&garmin_data_p->pktlist)) { + result = (struct garmin_packet *)garmin_data_p->pktlist.next; + list_del(&result->list); + } + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + return result; +} + + +/* free up all queued data */ +static void pkt_clear(struct garmin_data *garmin_data_p) +{ + unsigned long flags; + struct garmin_packet *result = NULL; + + spin_lock_irqsave(&garmin_data_p->lock, flags); + while (!list_empty(&garmin_data_p->pktlist)) { + result = (struct garmin_packet *)garmin_data_p->pktlist.next; + list_del(&result->list); + kfree(result); + } + spin_unlock_irqrestore(&garmin_data_p->lock, flags); +} + + +/****************************************************************************** + * garmin serial protocol handling handling + ******************************************************************************/ + +/* send an ack packet back to the tty */ +static int gsp_send_ack(struct garmin_data *garmin_data_p, __u8 pkt_id) +{ + __u8 pkt[10]; + __u8 cksum = 0; + __u8 *ptr = pkt; + unsigned l = 0; + + dev_dbg(&garmin_data_p->port->dev, "%s - pkt-id: 0x%X.\n", __func__, + 0xFF & pkt_id); + + *ptr++ = DLE; + *ptr++ = ACK; + cksum += ACK; + + *ptr++ = 2; + cksum += 2; + + *ptr++ = pkt_id; + cksum += pkt_id; + + if (pkt_id == DLE) + *ptr++ = DLE; + + *ptr++ = 0; + *ptr++ = 0xFF & (-cksum); + *ptr++ = DLE; + *ptr++ = ETX; + + l = ptr-pkt; + + send_to_tty(garmin_data_p->port, pkt, l); + return 0; +} + + + +/* + * called for a complete packet received from tty layer + * + * the complete packet (pktid ... cksum) is in garmin_data_p->inbuf starting + * at GSP_INITIAL_OFFSET. + * + * count - number of bytes in the input buffer including space reserved for + * the usb header: GSP_INITIAL_OFFSET + number of bytes in packet + * (including pkt-id, data-length a. cksum) + */ +static int gsp_rec_packet(struct garmin_data *garmin_data_p, int count) +{ + struct device *dev = &garmin_data_p->port->dev; + unsigned long flags; + const __u8 *recpkt = garmin_data_p->inbuffer+GSP_INITIAL_OFFSET; + __le32 *usbdata = (__le32 *) garmin_data_p->inbuffer; + int cksum = 0; + int n = 0; + int pktid = recpkt[0]; + int size = recpkt[1]; + + usb_serial_debug_data(&garmin_data_p->port->dev, __func__, + count-GSP_INITIAL_OFFSET, recpkt); + + if (size != (count-GSP_INITIAL_OFFSET-3)) { + dev_dbg(dev, "%s - invalid size, expected %d bytes, got %d\n", + __func__, size, (count-GSP_INITIAL_OFFSET-3)); + return -EINVPKT; + } + + cksum += *recpkt++; + cksum += *recpkt++; + + /* sanity check, remove after test ... */ + if ((__u8 *)&(usbdata[3]) != recpkt) { + dev_dbg(dev, "%s - ptr mismatch %p - %p\n", __func__, + &(usbdata[4]), recpkt); + return -EINVPKT; + } + + while (n < size) { + cksum += *recpkt++; + n++; + } + + if ((0xff & (cksum + *recpkt)) != 0) { + dev_dbg(dev, "%s - invalid checksum, expected %02x, got %02x\n", + __func__, 0xff & -cksum, 0xff & *recpkt); + return -EINVPKT; + } + + usbdata[0] = __cpu_to_le32(GARMIN_LAYERID_APPL); + usbdata[1] = __cpu_to_le32(pktid); + usbdata[2] = __cpu_to_le32(size); + + garmin_write_bulk(garmin_data_p->port, garmin_data_p->inbuffer, + GARMIN_PKTHDR_LENGTH+size, 0); + + /* if this was an abort-transfer command, flush all + queued data. */ + if (isAbortTrfCmnd(garmin_data_p->inbuffer)) { + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= FLAGS_DROP_DATA; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + pkt_clear(garmin_data_p); + } + + return count; +} + + + +/* + * Called for data received from tty + * + * buf contains the data read, it may span more than one packet or even + * incomplete packets + * + * input record should be a serial-record, but it may not be complete. + * Copy it into our local buffer, until an etx is seen (or an error + * occurs). + * Once the record is complete, convert into a usb packet and send it + * to the bulk pipe, send an ack back to the tty. + * + * If the input is an ack, just send the last queued packet to the + * tty layer. + * + * if the input is an abort command, drop all queued data. + */ + +static int gsp_receive(struct garmin_data *garmin_data_p, + const unsigned char *buf, int count) +{ + struct device *dev = &garmin_data_p->port->dev; + unsigned long flags; + int offs = 0; + int ack_or_nak_seen = 0; + __u8 *dest; + int size; + /* dleSeen: set if last byte read was a DLE */ + int dleSeen; + /* skip: if set, skip incoming data until possible start of + * new packet + */ + int skip; + __u8 data; + + spin_lock_irqsave(&garmin_data_p->lock, flags); + dest = garmin_data_p->inbuffer; + size = garmin_data_p->insize; + dleSeen = garmin_data_p->flags & FLAGS_GSP_DLESEEN; + skip = garmin_data_p->flags & FLAGS_GSP_SKIP; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + /* dev_dbg(dev, "%s - dle=%d skip=%d size=%d count=%d\n", + __func__, dleSeen, skip, size, count); */ + + if (size == 0) + size = GSP_INITIAL_OFFSET; + + while (offs < count) { + + data = *(buf+offs); + offs++; + + if (data == DLE) { + if (skip) { /* start of a new pkt */ + skip = 0; + size = GSP_INITIAL_OFFSET; + dleSeen = 1; + } else if (dleSeen) { + dest[size++] = data; + dleSeen = 0; + } else { + dleSeen = 1; + } + } else if (data == ETX) { + if (dleSeen) { + /* packet complete */ + + data = dest[GSP_INITIAL_OFFSET]; + + if (data == ACK) { + ack_or_nak_seen = ACK; + dev_dbg(dev, "ACK packet complete.\n"); + } else if (data == NAK) { + ack_or_nak_seen = NAK; + dev_dbg(dev, "NAK packet complete.\n"); + } else { + dev_dbg(dev, "packet complete - id=0x%X.\n", + 0xFF & data); + gsp_rec_packet(garmin_data_p, size); + } + + skip = 1; + size = GSP_INITIAL_OFFSET; + dleSeen = 0; + } else { + dest[size++] = data; + } + } else if (!skip) { + + if (dleSeen) { + size = GSP_INITIAL_OFFSET; + dleSeen = 0; + } + + dest[size++] = data; + } + + if (size >= GPS_IN_BUFSIZ) { + dev_dbg(dev, "%s - packet too large.\n", __func__); + skip = 1; + size = GSP_INITIAL_OFFSET; + dleSeen = 0; + } + } + + spin_lock_irqsave(&garmin_data_p->lock, flags); + + garmin_data_p->insize = size; + + /* copy flags back to structure */ + if (skip) + garmin_data_p->flags |= FLAGS_GSP_SKIP; + else + garmin_data_p->flags &= ~FLAGS_GSP_SKIP; + + if (dleSeen) + garmin_data_p->flags |= FLAGS_GSP_DLESEEN; + else + garmin_data_p->flags &= ~FLAGS_GSP_DLESEEN; + + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + if (ack_or_nak_seen) { + if (gsp_next_packet(garmin_data_p) > 0) + garmin_data_p->state = STATE_ACTIVE; + else + garmin_data_p->state = STATE_GSP_WAIT_DATA; + } + return count; +} + + + +/* + * Sends a usb packet to the tty + * + * Assumes, that all packages and at an usb-packet boundary. + * + * return <0 on error, 0 if packet is incomplete or > 0 if packet was sent + */ +static int gsp_send(struct garmin_data *garmin_data_p, + const unsigned char *buf, int count) +{ + struct device *dev = &garmin_data_p->port->dev; + const unsigned char *src; + unsigned char *dst; + int pktid = 0; + int datalen = 0; + int cksum = 0; + int i = 0; + int k; + + dev_dbg(dev, "%s - state %d - %d bytes.\n", __func__, + garmin_data_p->state, count); + + k = garmin_data_p->outsize; + if ((k+count) > GPS_OUT_BUFSIZ) { + dev_dbg(dev, "packet too large\n"); + garmin_data_p->outsize = 0; + return -4; + } + + memcpy(garmin_data_p->outbuffer+k, buf, count); + k += count; + garmin_data_p->outsize = k; + + if (k >= GARMIN_PKTHDR_LENGTH) { + pktid = getPacketId(garmin_data_p->outbuffer); + datalen = getDataLength(garmin_data_p->outbuffer); + i = GARMIN_PKTHDR_LENGTH + datalen; + if (k < i) + return 0; + } else { + return 0; + } + + dev_dbg(dev, "%s - %d bytes in buffer, %d bytes in pkt.\n", __func__, k, i); + + /* garmin_data_p->outbuffer now contains a complete packet */ + + usb_serial_debug_data(&garmin_data_p->port->dev, __func__, k, + garmin_data_p->outbuffer); + + garmin_data_p->outsize = 0; + + if (GARMIN_LAYERID_APPL != getLayerId(garmin_data_p->outbuffer)) { + dev_dbg(dev, "not an application packet (%d)\n", + getLayerId(garmin_data_p->outbuffer)); + return -1; + } + + if (pktid > 255) { + dev_dbg(dev, "packet-id %d too large\n", pktid); + return -2; + } + + if (datalen > 255) { + dev_dbg(dev, "packet-size %d too large\n", datalen); + return -3; + } + + /* the serial protocol should be able to handle this packet */ + + k = 0; + src = garmin_data_p->outbuffer+GARMIN_PKTHDR_LENGTH; + for (i = 0; i < datalen; i++) { + if (*src++ == DLE) + k++; + } + + src = garmin_data_p->outbuffer+GARMIN_PKTHDR_LENGTH; + if (k > (GARMIN_PKTHDR_LENGTH-2)) { + /* can't add stuffing DLEs in place, move data to end + of buffer ... */ + dst = garmin_data_p->outbuffer+GPS_OUT_BUFSIZ-datalen; + memcpy(dst, src, datalen); + src = dst; + } + + dst = garmin_data_p->outbuffer; + + *dst++ = DLE; + *dst++ = pktid; + cksum += pktid; + *dst++ = datalen; + cksum += datalen; + if (datalen == DLE) + *dst++ = DLE; + + for (i = 0; i < datalen; i++) { + __u8 c = *src++; + *dst++ = c; + cksum += c; + if (c == DLE) + *dst++ = DLE; + } + + cksum = 0xFF & -cksum; + *dst++ = cksum; + if (cksum == DLE) + *dst++ = DLE; + *dst++ = DLE; + *dst++ = ETX; + + i = dst-garmin_data_p->outbuffer; + + send_to_tty(garmin_data_p->port, garmin_data_p->outbuffer, i); + + garmin_data_p->pkt_id = pktid; + garmin_data_p->state = STATE_WAIT_TTY_ACK; + + return i; +} + + +/* + * Process the next pending data packet - if there is one + */ +static int gsp_next_packet(struct garmin_data *garmin_data_p) +{ + int result = 0; + struct garmin_packet *pkt = NULL; + + while ((pkt = pkt_pop(garmin_data_p)) != NULL) { + dev_dbg(&garmin_data_p->port->dev, "%s - next pkt: %d\n", __func__, pkt->seq); + result = gsp_send(garmin_data_p, pkt->data, pkt->size); + if (result > 0) { + kfree(pkt); + return result; + } + kfree(pkt); + } + return result; +} + + + +/****************************************************************************** + * garmin native mode + ******************************************************************************/ + + +/* + * Called for data received from tty + * + * The input data is expected to be in garmin usb-packet format. + * + * buf contains the data read, it may span more than one packet + * or even incomplete packets + */ +static int nat_receive(struct garmin_data *garmin_data_p, + const unsigned char *buf, int count) +{ + unsigned long flags; + __u8 *dest; + int offs = 0; + int result = count; + int len; + + while (offs < count) { + /* if buffer contains header, copy rest of data */ + if (garmin_data_p->insize >= GARMIN_PKTHDR_LENGTH) + len = GARMIN_PKTHDR_LENGTH + +getDataLength(garmin_data_p->inbuffer); + else + len = GARMIN_PKTHDR_LENGTH; + + if (len >= GPS_IN_BUFSIZ) { + /* seems to be an invalid packet, ignore rest + of input */ + dev_dbg(&garmin_data_p->port->dev, + "%s - packet size too large: %d\n", + __func__, len); + garmin_data_p->insize = 0; + count = 0; + result = -EINVPKT; + } else { + len -= garmin_data_p->insize; + if (len > (count-offs)) + len = (count-offs); + if (len > 0) { + dest = garmin_data_p->inbuffer + + garmin_data_p->insize; + memcpy(dest, buf+offs, len); + garmin_data_p->insize += len; + offs += len; + } + } + + /* do we have a complete packet ? */ + if (garmin_data_p->insize >= GARMIN_PKTHDR_LENGTH) { + len = GARMIN_PKTHDR_LENGTH+ + getDataLength(garmin_data_p->inbuffer); + if (garmin_data_p->insize >= len) { + garmin_write_bulk(garmin_data_p->port, + garmin_data_p->inbuffer, + len, 0); + garmin_data_p->insize = 0; + + /* if this was an abort-transfer command, + flush all queued data. */ + if (isAbortTrfCmnd(garmin_data_p->inbuffer)) { + spin_lock_irqsave(&garmin_data_p->lock, + flags); + garmin_data_p->flags |= FLAGS_DROP_DATA; + spin_unlock_irqrestore( + &garmin_data_p->lock, flags); + pkt_clear(garmin_data_p); + } + } + } + } + return result; +} + + +/****************************************************************************** + * private packets + ******************************************************************************/ + +static void priv_status_resp(struct usb_serial_port *port) +{ + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + __le32 *pkt = (__le32 *)garmin_data_p->privpkt; + + pkt[0] = __cpu_to_le32(GARMIN_LAYERID_PRIVATE); + pkt[1] = __cpu_to_le32(PRIV_PKTID_INFO_RESP); + pkt[2] = __cpu_to_le32(12); + pkt[3] = __cpu_to_le32(VERSION_MAJOR << 16 | VERSION_MINOR); + pkt[4] = __cpu_to_le32(garmin_data_p->mode); + pkt[5] = __cpu_to_le32(garmin_data_p->serial_num); + + send_to_tty(port, (__u8 *)pkt, 6 * 4); +} + + +/****************************************************************************** + * Garmin specific driver functions + ******************************************************************************/ + +static int process_resetdev_request(struct usb_serial_port *port) +{ + unsigned long flags; + int status; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags &= ~(CLEAR_HALT_REQUIRED); + garmin_data_p->state = STATE_RESET; + garmin_data_p->serial_num = 0; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + usb_kill_urb(port->interrupt_in_urb); + dev_dbg(&port->dev, "%s - usb_reset_device\n", __func__); + status = usb_reset_device(port->serial->dev); + if (status) + dev_dbg(&port->dev, "%s - usb_reset_device failed: %d\n", + __func__, status); + return status; +} + + + +/* + * clear all cached data + */ +static int garmin_clear(struct garmin_data *garmin_data_p) +{ + unsigned long flags; + int status = 0; + + /* flush all queued data */ + pkt_clear(garmin_data_p); + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->insize = 0; + garmin_data_p->outsize = 0; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + return status; +} + + +static int garmin_init_session(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + int status = 0; + int i = 0; + + if (status == 0) { + usb_kill_urb(port->interrupt_in_urb); + + dev_dbg(&serial->dev->dev, "%s - adding interrupt input\n", __func__); + status = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (status) + dev_err(&serial->dev->dev, + "%s - failed submitting interrupt urb, error %d\n", + __func__, status); + } + + /* + * using the initialization method from gpsbabel. See comments in + * gpsbabel/jeeps/gpslibusb.c gusb_reset_toggles() + */ + if (status == 0) { + dev_dbg(&serial->dev->dev, "%s - starting session ...\n", __func__); + garmin_data_p->state = STATE_ACTIVE; + + for (i = 0; i < 3; i++) { + status = garmin_write_bulk(port, + GARMIN_START_SESSION_REQ, + sizeof(GARMIN_START_SESSION_REQ), 0); + + if (status < 0) + break; + } + + if (status > 0) + status = 0; + } + + return status; +} + + + +static int garmin_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + unsigned long flags; + int status = 0; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->mode = initial_mode; + garmin_data_p->count = 0; + garmin_data_p->flags &= FLAGS_SESSION_REPLY1_SEEN; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + /* shutdown any bulk reads that might be going on */ + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); + + if (garmin_data_p->state == STATE_RESET) + status = garmin_init_session(port); + + garmin_data_p->state = STATE_ACTIVE; + return status; +} + + +static void garmin_close(struct usb_serial_port *port) +{ + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + + dev_dbg(&port->dev, "%s - port %d - mode=%d state=%d flags=0x%X\n", + __func__, port->number, garmin_data_p->mode, + garmin_data_p->state, garmin_data_p->flags); + + garmin_clear(garmin_data_p); + + /* shutdown our urbs */ + usb_kill_urb(port->read_urb); + usb_kill_urb(port->write_urb); + + /* keep reset state so we know that we must start a new session */ + if (garmin_data_p->state != STATE_RESET) + garmin_data_p->state = STATE_DISCONNECTED; +} + + +static void garmin_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + + if (port) { + struct garmin_data *garmin_data_p = + usb_get_serial_port_data(port); + + if (GARMIN_LAYERID_APPL == getLayerId(urb->transfer_buffer)) { + + if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { + gsp_send_ack(garmin_data_p, + ((__u8 *)urb->transfer_buffer)[4]); + } + } + usb_serial_port_softint(port); + } + + /* Ignore errors that resulted from garmin_write_bulk with + dismiss_ack = 1 */ + + /* free up the transfer buffer, as usb_free_urb() does not do this */ + kfree(urb->transfer_buffer); +} + + +static int garmin_write_bulk(struct usb_serial_port *port, + const unsigned char *buf, int count, + int dismiss_ack) +{ + unsigned long flags; + struct usb_serial *serial = port->serial; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + struct urb *urb; + unsigned char *buffer; + int status; + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags &= ~FLAGS_DROP_DATA; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + buffer = kmalloc(count, GFP_ATOMIC); + if (!buffer) { + dev_err(&port->dev, "out of memory\n"); + return -ENOMEM; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + dev_err(&port->dev, "no more free urbs\n"); + kfree(buffer); + return -ENOMEM; + } + + memcpy(buffer, buf, count); + + usb_serial_debug_data(&port->dev, __func__, count, buffer); + + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), + buffer, count, + garmin_write_bulk_callback, + dismiss_ack ? NULL : port); + urb->transfer_flags |= URB_ZERO_PACKET; + + if (GARMIN_LAYERID_APPL == getLayerId(buffer)) { + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= APP_REQ_SEEN; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { + pkt_clear(garmin_data_p); + garmin_data_p->state = STATE_GSP_WAIT_DATA; + } + } + + /* send it down the pipe */ + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&port->dev, + "%s - usb_submit_urb(write bulk) failed with status = %d\n", + __func__, status); + count = status; + } + + /* we are done with this urb, so let the host driver + * really free it when it is finished with it */ + usb_free_urb(urb); + + return count; +} + +static int garmin_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct device *dev = &port->dev; + int pktid, pktsiz, len; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + __le32 *privpkt = (__le32 *)garmin_data_p->privpkt; + + usb_serial_debug_data(dev, __func__, count, buf); + + if (garmin_data_p->state == STATE_RESET) + return -EIO; + + /* check for our private packets */ + if (count >= GARMIN_PKTHDR_LENGTH) { + len = PRIVPKTSIZ; + if (count < len) + len = count; + + memcpy(garmin_data_p->privpkt, buf, len); + + pktsiz = getDataLength(garmin_data_p->privpkt); + pktid = getPacketId(garmin_data_p->privpkt); + + if (count == (GARMIN_PKTHDR_LENGTH+pktsiz) + && GARMIN_LAYERID_PRIVATE == + getLayerId(garmin_data_p->privpkt)) { + + dev_dbg(dev, "%s - processing private request %d\n", + __func__, pktid); + + /* drop all unfinished transfers */ + garmin_clear(garmin_data_p); + + switch (pktid) { + case PRIV_PKTID_SET_MODE: + if (pktsiz != 4) + return -EINVPKT; + garmin_data_p->mode = __le32_to_cpu(privpkt[3]); + dev_dbg(dev, "%s - mode set to %d\n", + __func__, garmin_data_p->mode); + break; + + case PRIV_PKTID_INFO_REQ: + priv_status_resp(port); + break; + + case PRIV_PKTID_RESET_REQ: + process_resetdev_request(port); + break; + + case PRIV_PKTID_SET_DEF_MODE: + if (pktsiz != 4) + return -EINVPKT; + initial_mode = __le32_to_cpu(privpkt[3]); + dev_dbg(dev, "%s - initial_mode set to %d\n", + __func__, + garmin_data_p->mode); + break; + } + return count; + } + } + + if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { + return gsp_receive(garmin_data_p, buf, count); + } else { /* MODE_NATIVE */ + return nat_receive(garmin_data_p, buf, count); + } +} + + +static int garmin_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + /* + * Report back the bytes currently available in the output buffer. + */ + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + return GPS_OUT_BUFSIZ-garmin_data_p->outsize; +} + + +static void garmin_read_process(struct garmin_data *garmin_data_p, + unsigned char *data, unsigned data_length, + int bulk_data) +{ + unsigned long flags; + + if (garmin_data_p->flags & FLAGS_DROP_DATA) { + /* abort-transfer cmd is actice */ + dev_dbg(&garmin_data_p->port->dev, "%s - pkt dropped\n", __func__); + } else if (garmin_data_p->state != STATE_DISCONNECTED && + garmin_data_p->state != STATE_RESET) { + + /* if throttling is active or postprecessing is required + put the received data in the input queue, otherwise + send it directly to the tty port */ + if (garmin_data_p->flags & FLAGS_QUEUING) { + pkt_add(garmin_data_p, data, data_length); + } else if (bulk_data || + getLayerId(data) == GARMIN_LAYERID_APPL) { + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= APP_RESP_SEEN; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { + pkt_add(garmin_data_p, data, data_length); + } else { + send_to_tty(garmin_data_p->port, data, + data_length); + } + } + /* ignore system layer packets ... */ + } +} + + +static void garmin_read_bulk_callback(struct urb *urb) +{ + unsigned long flags; + struct usb_serial_port *port = urb->context; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + int retval; + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero read bulk status received: %d\n", + __func__, status); + return; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + + garmin_read_process(garmin_data_p, data, urb->actual_length, 1); + + if (urb->actual_length == 0 && + 0 != (garmin_data_p->flags & FLAGS_BULK_IN_RESTART)) { + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags &= ~FLAGS_BULK_IN_RESTART; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + retval = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, + "%s - failed resubmitting read urb, error %d\n", + __func__, retval); + } else if (urb->actual_length > 0) { + /* Continue trying to read until nothing more is received */ + if (0 == (garmin_data_p->flags & FLAGS_THROTTLED)) { + retval = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, + "%s - failed resubmitting read urb, error %d\n", + __func__, retval); + } + } else { + dev_dbg(&port->dev, "%s - end of bulk data\n", __func__); + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags &= ~FLAGS_BULK_IN_ACTIVE; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + } +} + + +static void garmin_read_int_callback(struct urb *urb) +{ + unsigned long flags; + int retval; + struct usb_serial_port *port = urb->context; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + return; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, + urb->transfer_buffer); + + if (urb->actual_length == sizeof(GARMIN_BULK_IN_AVAIL_REPLY) && + 0 == memcmp(data, GARMIN_BULK_IN_AVAIL_REPLY, + sizeof(GARMIN_BULK_IN_AVAIL_REPLY))) { + + dev_dbg(&port->dev, "%s - bulk data available.\n", __func__); + + if (0 == (garmin_data_p->flags & FLAGS_BULK_IN_ACTIVE)) { + + /* bulk data available */ + retval = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (retval) { + dev_err(&port->dev, + "%s - failed submitting read urb, error %d\n", + __func__, retval); + } else { + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= FLAGS_BULK_IN_ACTIVE; + spin_unlock_irqrestore(&garmin_data_p->lock, + flags); + } + } else { + /* bulk-in transfer still active */ + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= FLAGS_BULK_IN_RESTART; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + } + + } else if (urb->actual_length == (4+sizeof(GARMIN_START_SESSION_REPLY)) + && 0 == memcmp(data, GARMIN_START_SESSION_REPLY, + sizeof(GARMIN_START_SESSION_REPLY))) { + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= FLAGS_SESSION_REPLY1_SEEN; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + /* save the serial number */ + garmin_data_p->serial_num = __le32_to_cpup( + (__le32 *)(data+GARMIN_PKTHDR_LENGTH)); + + dev_dbg(&port->dev, "%s - start-of-session reply seen - serial %u.\n", + __func__, garmin_data_p->serial_num); + } + + garmin_read_process(garmin_data_p, data, urb->actual_length, 0); + + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&urb->dev->dev, + "%s - Error %d submitting interrupt urb\n", + __func__, retval); +} + + +/* + * Sends the next queued packt to the tty port (garmin native mode only) + * and then sets a timer to call itself again until all queued data + * is sent. + */ +static int garmin_flush_queue(struct garmin_data *garmin_data_p) +{ + unsigned long flags; + struct garmin_packet *pkt; + + if ((garmin_data_p->flags & FLAGS_THROTTLED) == 0) { + pkt = pkt_pop(garmin_data_p); + if (pkt != NULL) { + send_to_tty(garmin_data_p->port, pkt->data, pkt->size); + kfree(pkt); + mod_timer(&garmin_data_p->timer, (1)+jiffies); + + } else { + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags &= ~FLAGS_QUEUING; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + } + } + return 0; +} + + +static void garmin_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + + /* set flag, data received will be put into a queue + for later processing */ + spin_lock_irq(&garmin_data_p->lock); + garmin_data_p->flags |= FLAGS_QUEUING|FLAGS_THROTTLED; + spin_unlock_irq(&garmin_data_p->lock); +} + + +static void garmin_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + int status; + + spin_lock_irq(&garmin_data_p->lock); + garmin_data_p->flags &= ~FLAGS_THROTTLED; + spin_unlock_irq(&garmin_data_p->lock); + + /* in native mode send queued data to tty, in + serial mode nothing needs to be done here */ + if (garmin_data_p->mode == MODE_NATIVE) + garmin_flush_queue(garmin_data_p); + + if (0 != (garmin_data_p->flags & FLAGS_BULK_IN_ACTIVE)) { + status = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (status) + dev_err(&port->dev, + "%s - failed resubmitting read urb, error %d\n", + __func__, status); + } +} + +/* + * The timer is currently only used to send queued packets to + * the tty in cases where the protocol provides no own handshaking + * to initiate the transfer. + */ +static void timeout_handler(unsigned long data) +{ + struct garmin_data *garmin_data_p = (struct garmin_data *) data; + + /* send the next queued packet to the tty port */ + if (garmin_data_p->mode == MODE_NATIVE) + if (garmin_data_p->flags & FLAGS_QUEUING) + garmin_flush_queue(garmin_data_p); +} + + + +static int garmin_port_probe(struct usb_serial_port *port) +{ + int status; + struct garmin_data *garmin_data_p; + + garmin_data_p = kzalloc(sizeof(struct garmin_data), GFP_KERNEL); + if (garmin_data_p == NULL) { + dev_err(&port->dev, "%s - Out of memory\n", __func__); + return -ENOMEM; + } + init_timer(&garmin_data_p->timer); + spin_lock_init(&garmin_data_p->lock); + INIT_LIST_HEAD(&garmin_data_p->pktlist); + /* garmin_data_p->timer.expires = jiffies + session_timeout; */ + garmin_data_p->timer.data = (unsigned long)garmin_data_p; + garmin_data_p->timer.function = timeout_handler; + garmin_data_p->port = port; + garmin_data_p->state = 0; + garmin_data_p->flags = 0; + garmin_data_p->count = 0; + usb_set_serial_port_data(port, garmin_data_p); + + status = garmin_init_session(port); + + return status; +} + + +static int garmin_port_remove(struct usb_serial_port *port) +{ + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + + usb_kill_urb(port->interrupt_in_urb); + del_timer_sync(&garmin_data_p->timer); + kfree(garmin_data_p); + return 0; +} + + +/* All of the device info needed */ +static struct usb_serial_driver garmin_device = { + .driver = { + .owner = THIS_MODULE, + .name = "garmin_gps", + }, + .description = "Garmin GPS usb/tty", + .id_table = id_table, + .num_ports = 1, + .open = garmin_open, + .close = garmin_close, + .throttle = garmin_throttle, + .unthrottle = garmin_unthrottle, + .port_probe = garmin_port_probe, + .port_remove = garmin_port_remove, + .write = garmin_write, + .write_room = garmin_write_room, + .write_bulk_callback = garmin_write_bulk_callback, + .read_bulk_callback = garmin_read_bulk_callback, + .read_int_callback = garmin_read_int_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &garmin_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(initial_mode, int, S_IRUGO); +MODULE_PARM_DESC(initial_mode, "Initial mode"); diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c new file mode 100644 index 00000000..8335b484 --- /dev/null +++ b/drivers/usb/serial/generic.c @@ -0,0 +1,608 @@ +/* + * USB Serial Converter Generic functions + * + * Copyright (C) 2010 - 2013 Johan Hovold (jhovold@gmail.com) + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_USB_SERIAL_GENERIC + +static __u16 vendor = 0x05f9; +static __u16 product = 0xffff; + +module_param(vendor, ushort, 0); +MODULE_PARM_DESC(vendor, "User specified USB idVendor"); + +module_param(product, ushort, 0); +MODULE_PARM_DESC(product, "User specified USB idProduct"); + +static struct usb_device_id generic_device_ids[2]; /* Initially all zeroes. */ + +/* All of the device info needed for the Generic Serial Converter */ +struct usb_serial_driver usb_serial_generic_device = { + .driver = { + .owner = THIS_MODULE, + .name = "generic", + }, + .id_table = generic_device_ids, + .num_ports = 1, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .resume = usb_serial_generic_resume, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &usb_serial_generic_device, NULL +}; + +#endif + +int usb_serial_generic_register(void) +{ + int retval = 0; + +#ifdef CONFIG_USB_SERIAL_GENERIC + generic_device_ids[0].idVendor = vendor; + generic_device_ids[0].idProduct = product; + generic_device_ids[0].match_flags = + USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT; + + /* register our generic driver with ourselves */ + retval = usb_serial_register_drivers(serial_drivers, + "usbserial_generic", generic_device_ids); +#endif + return retval; +} + +void usb_serial_generic_deregister(void) +{ +#ifdef CONFIG_USB_SERIAL_GENERIC + /* remove our generic driver */ + usb_serial_deregister_drivers(serial_drivers); +#endif +} + +int usb_serial_generic_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int result = 0; + unsigned long flags; + + /* clear the throttle flags */ + spin_lock_irqsave(&port->lock, flags); + port->throttled = 0; + port->throttle_req = 0; + spin_unlock_irqrestore(&port->lock, flags); + + /* if we have a bulk endpoint, start reading from it */ + if (port->bulk_in_size) + result = usb_serial_generic_submit_read_urbs(port, GFP_KERNEL); + + return result; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_open); + +void usb_serial_generic_close(struct usb_serial_port *port) +{ + unsigned long flags; + int i; + + if (port->bulk_out_size) { + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) + usb_kill_urb(port->write_urbs[i]); + + spin_lock_irqsave(&port->lock, flags); + kfifo_reset_out(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + } + if (port->bulk_in_size) { + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) + usb_kill_urb(port->read_urbs[i]); + } +} +EXPORT_SYMBOL_GPL(usb_serial_generic_close); + +int usb_serial_generic_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size) +{ + return kfifo_out_locked(&port->write_fifo, dest, size, &port->lock); +} + +/** + * usb_serial_generic_write_start - kick off an URB write + * @port: Pointer to the &struct usb_serial_port data + * + * Returns zero on success, or a negative errno value + */ +static int usb_serial_generic_write_start(struct usb_serial_port *port) +{ + struct urb *urb; + int count, result; + unsigned long flags; + int i; + + if (test_and_set_bit_lock(USB_SERIAL_WRITE_BUSY, &port->flags)) + return 0; +retry: + spin_lock_irqsave(&port->lock, flags); + if (!port->write_urbs_free || !kfifo_len(&port->write_fifo)) { + clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags); + spin_unlock_irqrestore(&port->lock, flags); + return 0; + } + i = (int)find_first_bit(&port->write_urbs_free, + ARRAY_SIZE(port->write_urbs)); + spin_unlock_irqrestore(&port->lock, flags); + + urb = port->write_urbs[i]; + count = port->serial->type->prepare_write_buffer(port, + urb->transfer_buffer, + port->bulk_out_size); + urb->transfer_buffer_length = count; + usb_serial_debug_data(&port->dev, __func__, count, urb->transfer_buffer); + spin_lock_irqsave(&port->lock, flags); + port->tx_bytes += count; + spin_unlock_irqrestore(&port->lock, flags); + + clear_bit(i, &port->write_urbs_free); + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) { + dev_err_console(port, "%s - error submitting urb: %d\n", + __func__, result); + set_bit(i, &port->write_urbs_free); + spin_lock_irqsave(&port->lock, flags); + port->tx_bytes -= count; + spin_unlock_irqrestore(&port->lock, flags); + + clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags); + return result; + } + + goto retry; /* try sending off another urb */ +} + +/** + * usb_serial_generic_write - generic write function for serial USB devices + * @tty: Pointer to &struct tty_struct for the device + * @port: Pointer to the &usb_serial_port structure for the device + * @buf: Pointer to the data to write + * @count: Number of bytes to write + * + * Returns the number of characters actually written, which may be anything + * from zero to @count. If an error occurs, it returns the negative errno + * value. + */ +int usb_serial_generic_write(struct tty_struct *tty, + struct usb_serial_port *port, const unsigned char *buf, int count) +{ + int result; + + /* only do something if we have a bulk out endpoint */ + if (!port->bulk_out_size) + return -ENODEV; + + if (!count) + return 0; + + count = kfifo_in_locked(&port->write_fifo, buf, count, &port->lock); + result = usb_serial_generic_write_start(port); + if (result) + return result; + + return count; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_write); + +int usb_serial_generic_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned long flags; + int room; + + if (!port->bulk_out_size) + return 0; + + spin_lock_irqsave(&port->lock, flags); + room = kfifo_avail(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, room); + return room; +} + +int usb_serial_generic_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned long flags; + int chars; + + if (!port->bulk_out_size) + return 0; + + spin_lock_irqsave(&port->lock, flags); + chars = kfifo_len(&port->write_fifo) + port->tx_bytes; + spin_unlock_irqrestore(&port->lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, chars); + return chars; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer); + +void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int bps; + unsigned long period; + unsigned long expire; + + bps = tty_get_baud_rate(tty); + if (!bps) + bps = 9600; /* B0 */ + /* + * Use a poll-period of roughly the time it takes to send one + * character or at least one jiffy. + */ + period = max_t(unsigned long, (10 * HZ / bps), 1); + period = min_t(unsigned long, period, timeout); + + dev_dbg(&port->dev, "%s - timeout = %u ms, period = %u ms\n", + __func__, jiffies_to_msecs(timeout), + jiffies_to_msecs(period)); + expire = jiffies + timeout; + while (!port->serial->type->tx_empty(port)) { + schedule_timeout_interruptible(period); + if (signal_pending(current)) + break; + if (time_after(jiffies, expire)) + break; + } +} +EXPORT_SYMBOL_GPL(usb_serial_generic_wait_until_sent); + +static int usb_serial_generic_submit_read_urb(struct usb_serial_port *port, + int index, gfp_t mem_flags) +{ + int res; + + if (!test_and_clear_bit(index, &port->read_urbs_free)) + return 0; + + dev_dbg(&port->dev, "%s - urb %d\n", __func__, index); + + res = usb_submit_urb(port->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&port->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &port->read_urbs_free); + return res; + } + + return 0; +} + +int usb_serial_generic_submit_read_urbs(struct usb_serial_port *port, + gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) { + res = usb_serial_generic_submit_read_urb(port, i, mem_flags); + if (res) + goto err; + } + + return 0; +err: + for (; i >= 0; --i) + usb_kill_urb(port->read_urbs[i]); + + return res; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_submit_read_urbs); + +void usb_serial_generic_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + char *ch = (char *)urb->transfer_buffer; + int i; + + if (!urb->actual_length) + return; + + /* The per character mucking around with sysrq path it too slow for + stuff like 3G modems, so shortcircuit it in the 99.9999999% of cases + where the USB serial is not a console anyway */ + if (!port->port.console || !port->sysrq) + tty_insert_flip_string(&port->port, ch, urb->actual_length); + else { + for (i = 0; i < urb->actual_length; i++, ch++) { + if (!usb_serial_handle_sysrq_char(port, *ch)) + tty_insert_flip_char(&port->port, *ch, TTY_NORMAL); + } + } + tty_flip_buffer_push(&port->port); +} +EXPORT_SYMBOL_GPL(usb_serial_generic_process_read_urb); + +void usb_serial_generic_read_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + int i; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) { + if (urb == port->read_urbs[i]) + break; + } + set_bit(i, &port->read_urbs_free); + + dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i, + urb->actual_length); + + if (urb->status) { + dev_dbg(&port->dev, "%s - non-zero urb status: %d\n", + __func__, urb->status); + return; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + port->serial->type->process_read_urb(urb); + + /* Throttle the device if requested by tty */ + spin_lock_irqsave(&port->lock, flags); + port->throttled = port->throttle_req; + if (!port->throttled) { + spin_unlock_irqrestore(&port->lock, flags); + usb_serial_generic_submit_read_urb(port, i, GFP_ATOMIC); + } else + spin_unlock_irqrestore(&port->lock, flags); +} +EXPORT_SYMBOL_GPL(usb_serial_generic_read_bulk_callback); + +void usb_serial_generic_write_bulk_callback(struct urb *urb) +{ + unsigned long flags; + struct usb_serial_port *port = urb->context; + int status = urb->status; + int i; + + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) + if (port->write_urbs[i] == urb) + break; + + spin_lock_irqsave(&port->lock, flags); + port->tx_bytes -= urb->transfer_buffer_length; + set_bit(i, &port->write_urbs_free); + spin_unlock_irqrestore(&port->lock, flags); + + if (status) { + dev_dbg(&port->dev, "%s - non-zero urb status: %d\n", + __func__, status); + + spin_lock_irqsave(&port->lock, flags); + kfifo_reset_out(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + } else { + usb_serial_generic_write_start(port); + } + + usb_serial_port_softint(port); +} +EXPORT_SYMBOL_GPL(usb_serial_generic_write_bulk_callback); + +void usb_serial_generic_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned long flags; + + /* Set the throttle request flag. It will be picked up + * by usb_serial_generic_read_bulk_callback(). */ + spin_lock_irqsave(&port->lock, flags); + port->throttle_req = 1; + spin_unlock_irqrestore(&port->lock, flags); +} +EXPORT_SYMBOL_GPL(usb_serial_generic_throttle); + +void usb_serial_generic_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + int was_throttled; + + /* Clear the throttle flags */ + spin_lock_irq(&port->lock); + was_throttled = port->throttled; + port->throttled = port->throttle_req = 0; + spin_unlock_irq(&port->lock); + + if (was_throttled) + usb_serial_generic_submit_read_urbs(port, GFP_KERNEL); +} +EXPORT_SYMBOL_GPL(usb_serial_generic_unthrottle); + +static bool usb_serial_generic_msr_changed(struct tty_struct *tty, + unsigned long arg, struct async_icount *cprev) +{ + struct usb_serial_port *port = tty->driver_data; + struct async_icount cnow; + unsigned long flags; + bool ret; + + /* + * Use tty-port initialised flag to detect all hangups including the + * one generated at USB-device disconnect. + * + * FIXME: Remove hupping check once tty_port_hangup calls shutdown + * (which clears the initialised flag) before wake up. + */ + if (test_bit(TTY_HUPPING, &tty->flags)) + return true; + if (!test_bit(ASYNCB_INITIALIZED, &port->port.flags)) + return true; + + spin_lock_irqsave(&port->lock, flags); + cnow = port->icount; /* atomic copy*/ + spin_unlock_irqrestore(&port->lock, flags); + + ret = ((arg & TIOCM_RNG) && (cnow.rng != cprev->rng)) || + ((arg & TIOCM_DSR) && (cnow.dsr != cprev->dsr)) || + ((arg & TIOCM_CD) && (cnow.dcd != cprev->dcd)) || + ((arg & TIOCM_CTS) && (cnow.cts != cprev->cts)); + + *cprev = cnow; + + return ret; +} + +int usb_serial_generic_tiocmiwait(struct tty_struct *tty, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct async_icount cnow; + unsigned long flags; + int ret; + + spin_lock_irqsave(&port->lock, flags); + cnow = port->icount; /* atomic copy */ + spin_unlock_irqrestore(&port->lock, flags); + + ret = wait_event_interruptible(port->port.delta_msr_wait, + usb_serial_generic_msr_changed(tty, arg, &cnow)); + if (!ret) { + if (test_bit(TTY_HUPPING, &tty->flags)) + ret = -EIO; + if (!test_bit(ASYNCB_INITIALIZED, &port->port.flags)) + ret = -EIO; + } + + return ret; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_tiocmiwait); + +int usb_serial_generic_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct usb_serial_port *port = tty->driver_data; + struct async_icount cnow; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + cnow = port->icount; /* atomic copy */ + spin_unlock_irqrestore(&port->lock, flags); + + icount->cts = cnow.cts; + icount->dsr = cnow.dsr; + icount->rng = cnow.rng; + icount->dcd = cnow.dcd; + icount->tx = cnow.tx; + icount->rx = cnow.rx; + icount->frame = cnow.frame; + icount->parity = cnow.parity; + icount->overrun = cnow.overrun; + icount->brk = cnow.brk; + icount->buf_overrun = cnow.buf_overrun; + + return 0; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_get_icount); + +#ifdef CONFIG_MAGIC_SYSRQ +int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch) +{ + if (port->sysrq && port->port.console) { + if (ch && time_before(jiffies, port->sysrq)) { + handle_sysrq(ch); + port->sysrq = 0; + return 1; + } + port->sysrq = 0; + } + return 0; +} +#else +int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch) +{ + return 0; +} +#endif +EXPORT_SYMBOL_GPL(usb_serial_handle_sysrq_char); + +int usb_serial_handle_break(struct usb_serial_port *port) +{ + if (!port->sysrq) { + port->sysrq = jiffies + HZ*5; + return 1; + } + port->sysrq = 0; + return 0; +} +EXPORT_SYMBOL_GPL(usb_serial_handle_break); + +/** + * usb_serial_handle_dcd_change - handle a change of carrier detect state + * @port: usb_serial_port structure for the open port + * @tty: tty_struct structure for the port + * @status: new carrier detect status, nonzero if active + */ +void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, + struct tty_struct *tty, unsigned int status) +{ + struct tty_port *port = &usb_port->port; + + dev_dbg(&usb_port->dev, "%s - status %d\n", __func__, status); + + if (status) + wake_up_interruptible(&port->open_wait); + else if (tty && !C_CLOCAL(tty)) + tty_hangup(tty); +} +EXPORT_SYMBOL_GPL(usb_serial_handle_dcd_change); + +int usb_serial_generic_resume(struct usb_serial *serial) +{ + struct usb_serial_port *port; + int i, c = 0, r; + + for (i = 0; i < serial->num_ports; i++) { + port = serial->port[i]; + if (!test_bit(ASYNCB_INITIALIZED, &port->port.flags)) + continue; + + if (port->bulk_in_size) { + r = usb_serial_generic_submit_read_urbs(port, + GFP_NOIO); + if (r < 0) + c++; + } + + if (port->bulk_out_size) { + r = usb_serial_generic_write_start(port); + if (r < 0) + c++; + } + } + + return c ? -EIO : 0; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_resume); diff --git a/drivers/usb/serial/hp4x.c b/drivers/usb/serial/hp4x.c new file mode 100644 index 00000000..2cba60d9 --- /dev/null +++ b/drivers/usb/serial/hp4x.c @@ -0,0 +1,51 @@ +/* + * HP4x Calculators Serial USB driver + * + * Copyright (C) 2005 Arthur Huillet (ahuillet@users.sf.net) + * Copyright (C) 2001-2005 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + */ + +#include +#include +#include +#include +#include +#include + +#define DRIVER_DESC "HP4x (48/49) Generic Serial driver" + +#define HP_VENDOR_ID 0x03f0 +#define HP49GP_PRODUCT_ID 0x0121 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(HP_VENDOR_ID, HP49GP_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver hp49gp_device = { + .driver = { + .owner = THIS_MODULE, + .name = "hp4X", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &hp49gp_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/io_16654.h b/drivers/usb/serial/io_16654.h new file mode 100644 index 00000000..a53abc95 --- /dev/null +++ b/drivers/usb/serial/io_16654.h @@ -0,0 +1,195 @@ +/************************************************************************ + * + * 16654.H Definitions for 16C654 UART used on EdgePorts + * + * Copyright (C) 1998 Inside Out Networks, Inc. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ************************************************************************/ + +#if !defined(_16654_H) +#define _16654_H + +/************************************************************************ + * + * D e f i n e s / T y p e d e f s + * + ************************************************************************/ + + // + // UART register numbers + // Numbers 0-7 are passed to the Edgeport directly. Numbers 8 and + // above are used internally to indicate that we must enable access + // to them via LCR bit 0x80 or LCR = 0xBF. + // The register number sent to the Edgeport is then (x & 0x7). + // + // Driver must not access registers that affect operation of the + // the EdgePort firmware -- that includes THR, RHR, IER, FCR. + + +#define THR 0 // ! Transmit Holding Register (Write) +#define RDR 0 // ! Receive Holding Register (Read) +#define IER 1 // ! Interrupt Enable Register +#define FCR 2 // ! Fifo Control Register (Write) +#define ISR 2 // Interrupt Status Register (Read) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register +#define DLL 8 // Bank2[ 0 ] Divisor Latch LSB +#define DLM 9 // Bank2[ 1 ] Divisor Latch MSB +#define EFR 10 // Bank2[ 2 ] Extended Function Register +//efine unused 11 // Bank2[ 3 ] +#define XON1 12 // Bank2[ 4 ] Xon-1 +#define XON2 13 // Bank2[ 5 ] Xon-2 +#define XOFF1 14 // Bank2[ 6 ] Xoff-1 +#define XOFF2 15 // Bank2[ 7 ] Xoff-2 + +#define NUM_16654_REGS 16 + +#define IS_REG_2ND_BANK(x) ((x) >= 8) + + // + // Bit definitions for each register + // + +#define IER_RX 0x01 // Enable receive interrupt +#define IER_TX 0x02 // Enable transmit interrupt +#define IER_RXS 0x04 // Enable receive status interrupt +#define IER_MDM 0x08 // Enable modem status interrupt +#define IER_SLEEP 0x10 // Enable sleep mode +#define IER_XOFF 0x20 // Enable s/w flow control (XOFF) interrupt +#define IER_RTS 0x40 // Enable RTS interrupt +#define IER_CTS 0x80 // Enable CTS interrupt +#define IER_ENABLE_ALL 0xFF // Enable all ints + + +#define FCR_FIFO_EN 0x01 // Enable FIFOs +#define FCR_RXCLR 0x02 // Reset Rx FIFO +#define FCR_TXCLR 0x04 // Reset Tx FIFO +#define FCR_DMA_BLK 0x08 // Enable DMA block mode +#define FCR_TX_LEVEL_MASK 0x30 // Mask for Tx FIFO Level +#define FCR_TX_LEVEL_8 0x00 // Tx FIFO Level = 8 bytes +#define FCR_TX_LEVEL_16 0x10 // Tx FIFO Level = 16 bytes +#define FCR_TX_LEVEL_32 0x20 // Tx FIFO Level = 32 bytes +#define FCR_TX_LEVEL_56 0x30 // Tx FIFO Level = 56 bytes +#define FCR_RX_LEVEL_MASK 0xC0 // Mask for Rx FIFO Level +#define FCR_RX_LEVEL_8 0x00 // Rx FIFO Level = 8 bytes +#define FCR_RX_LEVEL_16 0x40 // Rx FIFO Level = 16 bytes +#define FCR_RX_LEVEL_56 0x80 // Rx FIFO Level = 56 bytes +#define FCR_RX_LEVEL_60 0xC0 // Rx FIFO Level = 60 bytes + + +#define ISR_INT_MDM_STATUS 0x00 // Modem status int pending +#define ISR_INT_NONE 0x01 // No interrupt pending +#define ISR_INT_TXRDY 0x02 // Tx ready int pending +#define ISR_INT_RXRDY 0x04 // Rx ready int pending +#define ISR_INT_LINE_STATUS 0x06 // Line status int pending +#define ISR_INT_RX_TIMEOUT 0x0C // Rx timeout int pending +#define ISR_INT_RX_XOFF 0x10 // Rx Xoff int pending +#define ISR_INT_RTS_CTS 0x20 // RTS/CTS change int pending +#define ISR_FIFO_ENABLED 0xC0 // Bits set if FIFOs enabled +#define ISR_INT_BITS_MASK 0x3E // Mask to isolate valid int causes + + +#define LCR_BITS_5 0x00 // 5 bits/char +#define LCR_BITS_6 0x01 // 6 bits/char +#define LCR_BITS_7 0x02 // 7 bits/char +#define LCR_BITS_8 0x03 // 8 bits/char +#define LCR_BITS_MASK 0x03 // Mask for bits/char field + +#define LCR_STOP_1 0x00 // 1 stop bit +#define LCR_STOP_1_5 0x04 // 1.5 stop bits (if 5 bits/char) +#define LCR_STOP_2 0x04 // 2 stop bits (if 6-8 bits/char) +#define LCR_STOP_MASK 0x04 // Mask for stop bits field + +#define LCR_PAR_NONE 0x00 // No parity +#define LCR_PAR_ODD 0x08 // Odd parity +#define LCR_PAR_EVEN 0x18 // Even parity +#define LCR_PAR_MARK 0x28 // Force parity bit to 1 +#define LCR_PAR_SPACE 0x38 // Force parity bit to 0 +#define LCR_PAR_MASK 0x38 // Mask for parity field + +#define LCR_SET_BREAK 0x40 // Set Break condition +#define LCR_DL_ENABLE 0x80 // Enable access to divisor latch + +#define LCR_ACCESS_EFR 0xBF // Load this value to access DLL,DLM, + // and also the '654-only registers + // EFR, XON1, XON2, XOFF1, XOFF2 + + +#define MCR_DTR 0x01 // Assert DTR +#define MCR_RTS 0x02 // Assert RTS +#define MCR_OUT1 0x04 // Loopback only: Sets state of RI +#define MCR_MASTER_IE 0x08 // Enable interrupt outputs +#define MCR_LOOPBACK 0x10 // Set internal (digital) loopback mode +#define MCR_XON_ANY 0x20 // Enable any char to exit XOFF mode +#define MCR_IR_ENABLE 0x40 // Enable IrDA functions +#define MCR_BRG_DIV_4 0x80 // Divide baud rate clk by /4 instead of /1 + + +#define LSR_RX_AVAIL 0x01 // Rx data available +#define LSR_OVER_ERR 0x02 // Rx overrun +#define LSR_PAR_ERR 0x04 // Rx parity error +#define LSR_FRM_ERR 0x08 // Rx framing error +#define LSR_BREAK 0x10 // Rx break condition detected +#define LSR_TX_EMPTY 0x20 // Tx Fifo empty +#define LSR_TX_ALL_EMPTY 0x40 // Tx Fifo and shift register empty +#define LSR_FIFO_ERR 0x80 // Rx Fifo contains at least 1 erred char + + +#define EDGEPORT_MSR_DELTA_CTS 0x01 // CTS changed from last read +#define EDGEPORT_MSR_DELTA_DSR 0x02 // DSR changed from last read +#define EDGEPORT_MSR_DELTA_RI 0x04 // RI changed from 0 -> 1 +#define EDGEPORT_MSR_DELTA_CD 0x08 // CD changed from last read +#define EDGEPORT_MSR_CTS 0x10 // Current state of CTS +#define EDGEPORT_MSR_DSR 0x20 // Current state of DSR +#define EDGEPORT_MSR_RI 0x40 // Current state of RI +#define EDGEPORT_MSR_CD 0x80 // Current state of CD + + + + // Tx Rx + //------------------------------- +#define EFR_SWFC_NONE 0x00 // None None +#define EFR_SWFC_RX1 0x02 // None XOFF1 +#define EFR_SWFC_RX2 0x01 // None XOFF2 +#define EFR_SWFC_RX12 0x03 // None XOFF1 & XOFF2 +#define EFR_SWFC_TX1 0x08 // XOFF1 None +#define EFR_SWFC_TX1_RX1 0x0a // XOFF1 XOFF1 +#define EFR_SWFC_TX1_RX2 0x09 // XOFF1 XOFF2 +#define EFR_SWFC_TX1_RX12 0x0b // XOFF1 XOFF1 & XOFF2 +#define EFR_SWFC_TX2 0x04 // XOFF2 None +#define EFR_SWFC_TX2_RX1 0x06 // XOFF2 XOFF1 +#define EFR_SWFC_TX2_RX2 0x05 // XOFF2 XOFF2 +#define EFR_SWFC_TX2_RX12 0x07 // XOFF2 XOFF1 & XOFF2 +#define EFR_SWFC_TX12 0x0c // XOFF1 & XOFF2 None +#define EFR_SWFC_TX12_RX1 0x0e // XOFF1 & XOFF2 XOFF1 +#define EFR_SWFC_TX12_RX2 0x0d // XOFF1 & XOFF2 XOFF2 +#define EFR_SWFC_TX12_RX12 0x0f // XOFF1 & XOFF2 XOFF1 & XOFF2 + +#define EFR_TX_FC_MASK 0x0c // Mask to isolate Rx flow control +#define EFR_TX_FC_NONE 0x00 // No Tx Xon/Xoff flow control +#define EFR_TX_FC_X1 0x08 // Transmit Xon1/Xoff1 +#define EFR_TX_FC_X2 0x04 // Transmit Xon2/Xoff2 +#define EFR_TX_FC_X1_2 0x0c // Transmit Xon1&2/Xoff1&2 + +#define EFR_RX_FC_MASK 0x03 // Mask to isolate Rx flow control +#define EFR_RX_FC_NONE 0x00 // No Rx Xon/Xoff flow control +#define EFR_RX_FC_X1 0x02 // Receiver compares Xon1/Xoff1 +#define EFR_RX_FC_X2 0x01 // Receiver compares Xon2/Xoff2 +#define EFR_RX_FC_X1_2 0x03 // Receiver compares Xon1&2/Xoff1&2 + + +#define EFR_SWFC_MASK 0x0F // Mask for software flow control field +#define EFR_ENABLE_16654 0x10 // Enable 16C654 features +#define EFR_SPEC_DETECT 0x20 // Enable special character detect interrupt +#define EFR_AUTO_RTS 0x40 // Use RTS for Rx flow control +#define EFR_AUTO_CTS 0x80 // Use CTS for Tx flow control + +#endif // if !defined(_16654_H) + diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c new file mode 100644 index 00000000..1477e859 --- /dev/null +++ b/drivers/usb/serial/io_edgeport.c @@ -0,0 +1,3050 @@ +/* + * Edgeport USB Serial Converter driver + * + * Copyright (C) 2000 Inside Out Networks, All rights reserved. + * Copyright (C) 2001-2002 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Supports the following devices: + * Edgeport/4 + * Edgeport/4t + * Edgeport/2 + * Edgeport/4i + * Edgeport/2i + * Edgeport/421 + * Edgeport/21 + * Rapidport/4 + * Edgeport/8 + * Edgeport/2D8 + * Edgeport/4D8 + * Edgeport/8i + * + * For questions or problems with this driver, contact Inside Out + * Networks technical support, or Peter Berger , + * or Al Borchers . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "io_edgeport.h" +#include "io_ionsp.h" /* info for the iosp messages */ +#include "io_16654.h" /* 16654 UART defines */ + +#define DRIVER_AUTHOR "Greg Kroah-Hartman and David Iacovelli" +#define DRIVER_DESC "Edgeport USB Serial Driver" + +#define MAX_NAME_LEN 64 + +#define CHASE_TIMEOUT (5*HZ) /* 5 seconds */ +#define OPEN_TIMEOUT (5*HZ) /* 5 seconds */ +#define COMMAND_TIMEOUT (5*HZ) /* 5 seconds */ + +/* receive port state */ +enum RXSTATE { + EXPECT_HDR1 = 0, /* Expect header byte 1 */ + EXPECT_HDR2 = 1, /* Expect header byte 2 */ + EXPECT_DATA = 2, /* Expect 'RxBytesRemaining' data */ + EXPECT_HDR3 = 3, /* Expect header byte 3 (for status hdrs only) */ +}; + + +/* Transmit Fifo + * This Transmit queue is an extension of the edgeport Rx buffer. + * The maximum amount of data buffered in both the edgeport + * Rx buffer (maxTxCredits) and this buffer will never exceed maxTxCredits. + */ +struct TxFifo { + unsigned int head; /* index to head pointer (write) */ + unsigned int tail; /* index to tail pointer (read) */ + unsigned int count; /* Bytes in queue */ + unsigned int size; /* Max size of queue (equal to Max number of TxCredits) */ + unsigned char *fifo; /* allocated Buffer */ +}; + +/* This structure holds all of the local port information */ +struct edgeport_port { + __u16 txCredits; /* our current credits for this port */ + __u16 maxTxCredits; /* the max size of the port */ + + struct TxFifo txfifo; /* transmit fifo -- size will be maxTxCredits */ + struct urb *write_urb; /* write URB for this port */ + bool write_in_progress; /* 'true' while a write URB is outstanding */ + spinlock_t ep_lock; + + __u8 shadowLCR; /* last LCR value received */ + __u8 shadowMCR; /* last MCR value received */ + __u8 shadowMSR; /* last MSR value received */ + __u8 shadowLSR; /* last LSR value received */ + __u8 shadowXonChar; /* last value set as XON char in Edgeport */ + __u8 shadowXoffChar; /* last value set as XOFF char in Edgeport */ + __u8 validDataMask; + __u32 baudRate; + + bool open; + bool openPending; + bool commandPending; + bool closePending; + bool chaseResponsePending; + + wait_queue_head_t wait_chase; /* for handling sleeping while waiting for chase to finish */ + wait_queue_head_t wait_open; /* for handling sleeping while waiting for open to finish */ + wait_queue_head_t wait_command; /* for handling sleeping while waiting for command to finish */ + + struct usb_serial_port *port; /* loop back to the owner of this object */ +}; + + +/* This structure holds all of the individual device information */ +struct edgeport_serial { + char name[MAX_NAME_LEN+2]; /* string name of this device */ + + struct edge_manuf_descriptor manuf_descriptor; /* the manufacturer descriptor */ + struct edge_boot_descriptor boot_descriptor; /* the boot firmware descriptor */ + struct edgeport_product_info product_info; /* Product Info */ + struct edge_compatibility_descriptor epic_descriptor; /* Edgeport compatible descriptor */ + int is_epic; /* flag if EPiC device or not */ + + __u8 interrupt_in_endpoint; /* the interrupt endpoint handle */ + unsigned char *interrupt_in_buffer; /* the buffer we use for the interrupt endpoint */ + struct urb *interrupt_read_urb; /* our interrupt urb */ + + __u8 bulk_in_endpoint; /* the bulk in endpoint handle */ + unsigned char *bulk_in_buffer; /* the buffer we use for the bulk in endpoint */ + struct urb *read_urb; /* our bulk read urb */ + bool read_in_progress; + spinlock_t es_lock; + + __u8 bulk_out_endpoint; /* the bulk out endpoint handle */ + + __s16 rxBytesAvail; /* the number of bytes that we need to read from this device */ + + enum RXSTATE rxState; /* the current state of the bulk receive processor */ + __u8 rxHeader1; /* receive header byte 1 */ + __u8 rxHeader2; /* receive header byte 2 */ + __u8 rxHeader3; /* receive header byte 3 */ + __u8 rxPort; /* the port that we are currently receiving data for */ + __u8 rxStatusCode; /* the receive status code */ + __u8 rxStatusParam; /* the receive status paramater */ + __s16 rxBytesRemaining; /* the number of port bytes left to read */ + struct usb_serial *serial; /* loop back to the owner of this object */ +}; + +/* baud rate information */ +struct divisor_table_entry { + __u32 BaudRate; + __u16 Divisor; +}; + +/* + * Define table of divisors for Rev A EdgePort/4 hardware + * These assume a 3.6864MHz crystal, the standard /16, and + * MCR.7 = 0. + */ + +static const struct divisor_table_entry divisor_table[] = { + { 50, 4608}, + { 75, 3072}, + { 110, 2095}, /* 2094.545455 => 230450 => .0217 % over */ + { 134, 1713}, /* 1713.011152 => 230398.5 => .00065% under */ + { 150, 1536}, + { 300, 768}, + { 600, 384}, + { 1200, 192}, + { 1800, 128}, + { 2400, 96}, + { 4800, 48}, + { 7200, 32}, + { 9600, 24}, + { 14400, 16}, + { 19200, 12}, + { 38400, 6}, + { 57600, 4}, + { 115200, 2}, + { 230400, 1}, +}; + +/* Number of outstanding Command Write Urbs */ +static atomic_t CmdUrbs = ATOMIC_INIT(0); + + +/* local function prototypes */ + +/* function prototypes for all URB callbacks */ +static void edge_interrupt_callback(struct urb *urb); +static void edge_bulk_in_callback(struct urb *urb); +static void edge_bulk_out_data_callback(struct urb *urb); +static void edge_bulk_out_cmd_callback(struct urb *urb); + +/* function prototypes for the usbserial callbacks */ +static int edge_open(struct tty_struct *tty, struct usb_serial_port *port); +static void edge_close(struct usb_serial_port *port); +static int edge_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +static int edge_write_room(struct tty_struct *tty); +static int edge_chars_in_buffer(struct tty_struct *tty); +static void edge_throttle(struct tty_struct *tty); +static void edge_unthrottle(struct tty_struct *tty); +static void edge_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios); +static int edge_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg); +static void edge_break(struct tty_struct *tty, int break_state); +static int edge_tiocmget(struct tty_struct *tty); +static int edge_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static int edge_startup(struct usb_serial *serial); +static void edge_disconnect(struct usb_serial *serial); +static void edge_release(struct usb_serial *serial); +static int edge_port_probe(struct usb_serial_port *port); +static int edge_port_remove(struct usb_serial_port *port); + +#include "io_tables.h" /* all of the devices that this driver supports */ + +/* function prototypes for all of our local functions */ + +static void process_rcvd_data(struct edgeport_serial *edge_serial, + unsigned char *buffer, __u16 bufferLength); +static void process_rcvd_status(struct edgeport_serial *edge_serial, + __u8 byte2, __u8 byte3); +static void edge_tty_recv(struct usb_serial_port *port, unsigned char *data, + int length); +static void handle_new_msr(struct edgeport_port *edge_port, __u8 newMsr); +static void handle_new_lsr(struct edgeport_port *edge_port, __u8 lsrData, + __u8 lsr, __u8 data); +static int send_iosp_ext_cmd(struct edgeport_port *edge_port, __u8 command, + __u8 param); +static int calc_baud_rate_divisor(struct device *dev, int baud_rate, int *divisor); +static int send_cmd_write_baud_rate(struct edgeport_port *edge_port, + int baudRate); +static void change_port_settings(struct tty_struct *tty, + struct edgeport_port *edge_port, + struct ktermios *old_termios); +static int send_cmd_write_uart_register(struct edgeport_port *edge_port, + __u8 regNum, __u8 regValue); +static int write_cmd_usb(struct edgeport_port *edge_port, + unsigned char *buffer, int writeLength); +static void send_more_port_data(struct edgeport_serial *edge_serial, + struct edgeport_port *edge_port); + +static int sram_write(struct usb_serial *serial, __u16 extAddr, __u16 addr, + __u16 length, const __u8 *data); +static int rom_read(struct usb_serial *serial, __u16 extAddr, __u16 addr, + __u16 length, __u8 *data); +static int rom_write(struct usb_serial *serial, __u16 extAddr, __u16 addr, + __u16 length, const __u8 *data); +static void get_manufacturing_desc(struct edgeport_serial *edge_serial); +static void get_boot_desc(struct edgeport_serial *edge_serial); +static void load_application_firmware(struct edgeport_serial *edge_serial); + +static void unicode_to_ascii(char *string, int buflen, + __le16 *unicode, int unicode_size); + + +/* ************************************************************************ */ +/* ************************************************************************ */ +/* ************************************************************************ */ +/* ************************************************************************ */ + +/************************************************************************ + * * + * update_edgeport_E2PROM() Compare current versions of * + * Boot ROM and Manufacture * + * Descriptors with versions * + * embedded in this driver * + * * + ************************************************************************/ +static void update_edgeport_E2PROM(struct edgeport_serial *edge_serial) +{ + struct device *dev = &edge_serial->serial->dev->dev; + __u32 BootCurVer; + __u32 BootNewVer; + __u8 BootMajorVersion; + __u8 BootMinorVersion; + __u16 BootBuildNumber; + __u32 Bootaddr; + const struct ihex_binrec *rec; + const struct firmware *fw; + const char *fw_name; + int response; + + switch (edge_serial->product_info.iDownloadFile) { + case EDGE_DOWNLOAD_FILE_I930: + fw_name = "edgeport/boot.fw"; + break; + case EDGE_DOWNLOAD_FILE_80251: + fw_name = "edgeport/boot2.fw"; + break; + default: + return; + } + + response = request_ihex_firmware(&fw, fw_name, + &edge_serial->serial->dev->dev); + if (response) { + dev_err(dev, "Failed to load image \"%s\" err %d\n", + fw_name, response); + return; + } + + rec = (const struct ihex_binrec *)fw->data; + BootMajorVersion = rec->data[0]; + BootMinorVersion = rec->data[1]; + BootBuildNumber = (rec->data[2] << 8) | rec->data[3]; + + /* Check Boot Image Version */ + BootCurVer = (edge_serial->boot_descriptor.MajorVersion << 24) + + (edge_serial->boot_descriptor.MinorVersion << 16) + + le16_to_cpu(edge_serial->boot_descriptor.BuildNumber); + + BootNewVer = (BootMajorVersion << 24) + + (BootMinorVersion << 16) + + BootBuildNumber; + + dev_dbg(dev, "Current Boot Image version %d.%d.%d\n", + edge_serial->boot_descriptor.MajorVersion, + edge_serial->boot_descriptor.MinorVersion, + le16_to_cpu(edge_serial->boot_descriptor.BuildNumber)); + + + if (BootNewVer > BootCurVer) { + dev_dbg(dev, "**Update Boot Image from %d.%d.%d to %d.%d.%d\n", + edge_serial->boot_descriptor.MajorVersion, + edge_serial->boot_descriptor.MinorVersion, + le16_to_cpu(edge_serial->boot_descriptor.BuildNumber), + BootMajorVersion, BootMinorVersion, BootBuildNumber); + + dev_dbg(dev, "Downloading new Boot Image\n"); + + for (rec = ihex_next_binrec(rec); rec; + rec = ihex_next_binrec(rec)) { + Bootaddr = be32_to_cpu(rec->addr); + response = rom_write(edge_serial->serial, + Bootaddr >> 16, + Bootaddr & 0xFFFF, + be16_to_cpu(rec->len), + &rec->data[0]); + if (response < 0) { + dev_err(&edge_serial->serial->dev->dev, + "rom_write failed (%x, %x, %d)\n", + Bootaddr >> 16, Bootaddr & 0xFFFF, + be16_to_cpu(rec->len)); + break; + } + } + } else { + dev_dbg(dev, "Boot Image -- already up to date\n"); + } + release_firmware(fw); +} + +#if 0 +/************************************************************************ + * + * Get string descriptor from device + * + ************************************************************************/ +static int get_string_desc(struct usb_device *dev, int Id, + struct usb_string_descriptor **pRetDesc) +{ + struct usb_string_descriptor StringDesc; + struct usb_string_descriptor *pStringDesc; + + dev_dbg(&dev->dev, "%s - USB String ID = %d\n", __func__, Id); + + if (!usb_get_descriptor(dev, USB_DT_STRING, Id, &StringDesc, + sizeof(StringDesc))) + return 0; + + pStringDesc = kmalloc(StringDesc.bLength, GFP_KERNEL); + if (!pStringDesc) + return -1; + + if (!usb_get_descriptor(dev, USB_DT_STRING, Id, pStringDesc, + StringDesc.bLength)) { + kfree(pStringDesc); + return -1; + } + + *pRetDesc = pStringDesc; + return 0; +} +#endif + +static void dump_product_info(struct edgeport_serial *edge_serial, + struct edgeport_product_info *product_info) +{ + struct device *dev = &edge_serial->serial->dev->dev; + + /* Dump Product Info structure */ + dev_dbg(dev, "**Product Information:\n"); + dev_dbg(dev, " ProductId %x\n", product_info->ProductId); + dev_dbg(dev, " NumPorts %d\n", product_info->NumPorts); + dev_dbg(dev, " ProdInfoVer %d\n", product_info->ProdInfoVer); + dev_dbg(dev, " IsServer %d\n", product_info->IsServer); + dev_dbg(dev, " IsRS232 %d\n", product_info->IsRS232); + dev_dbg(dev, " IsRS422 %d\n", product_info->IsRS422); + dev_dbg(dev, " IsRS485 %d\n", product_info->IsRS485); + dev_dbg(dev, " RomSize %d\n", product_info->RomSize); + dev_dbg(dev, " RamSize %d\n", product_info->RamSize); + dev_dbg(dev, " CpuRev %x\n", product_info->CpuRev); + dev_dbg(dev, " BoardRev %x\n", product_info->BoardRev); + dev_dbg(dev, " BootMajorVersion %d.%d.%d\n", + product_info->BootMajorVersion, + product_info->BootMinorVersion, + le16_to_cpu(product_info->BootBuildNumber)); + dev_dbg(dev, " FirmwareMajorVersion %d.%d.%d\n", + product_info->FirmwareMajorVersion, + product_info->FirmwareMinorVersion, + le16_to_cpu(product_info->FirmwareBuildNumber)); + dev_dbg(dev, " ManufactureDescDate %d/%d/%d\n", + product_info->ManufactureDescDate[0], + product_info->ManufactureDescDate[1], + product_info->ManufactureDescDate[2]+1900); + dev_dbg(dev, " iDownloadFile 0x%x\n", + product_info->iDownloadFile); + dev_dbg(dev, " EpicVer %d\n", product_info->EpicVer); +} + +static void get_product_info(struct edgeport_serial *edge_serial) +{ + struct edgeport_product_info *product_info = &edge_serial->product_info; + + memset(product_info, 0, sizeof(struct edgeport_product_info)); + + product_info->ProductId = (__u16)(le16_to_cpu(edge_serial->serial->dev->descriptor.idProduct) & ~ION_DEVICE_ID_80251_NETCHIP); + product_info->NumPorts = edge_serial->manuf_descriptor.NumPorts; + product_info->ProdInfoVer = 0; + + product_info->RomSize = edge_serial->manuf_descriptor.RomSize; + product_info->RamSize = edge_serial->manuf_descriptor.RamSize; + product_info->CpuRev = edge_serial->manuf_descriptor.CpuRev; + product_info->BoardRev = edge_serial->manuf_descriptor.BoardRev; + + product_info->BootMajorVersion = + edge_serial->boot_descriptor.MajorVersion; + product_info->BootMinorVersion = + edge_serial->boot_descriptor.MinorVersion; + product_info->BootBuildNumber = + edge_serial->boot_descriptor.BuildNumber; + + memcpy(product_info->ManufactureDescDate, + edge_serial->manuf_descriptor.DescDate, + sizeof(edge_serial->manuf_descriptor.DescDate)); + + /* check if this is 2nd generation hardware */ + if (le16_to_cpu(edge_serial->serial->dev->descriptor.idProduct) + & ION_DEVICE_ID_80251_NETCHIP) + product_info->iDownloadFile = EDGE_DOWNLOAD_FILE_80251; + else + product_info->iDownloadFile = EDGE_DOWNLOAD_FILE_I930; + + /* Determine Product type and set appropriate flags */ + switch (DEVICE_ID_FROM_USB_PRODUCT_ID(product_info->ProductId)) { + case ION_DEVICE_ID_EDGEPORT_COMPATIBLE: + case ION_DEVICE_ID_EDGEPORT_4T: + case ION_DEVICE_ID_EDGEPORT_4: + case ION_DEVICE_ID_EDGEPORT_2: + case ION_DEVICE_ID_EDGEPORT_8_DUAL_CPU: + case ION_DEVICE_ID_EDGEPORT_8: + case ION_DEVICE_ID_EDGEPORT_421: + case ION_DEVICE_ID_EDGEPORT_21: + case ION_DEVICE_ID_EDGEPORT_2_DIN: + case ION_DEVICE_ID_EDGEPORT_4_DIN: + case ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU: + product_info->IsRS232 = 1; + break; + + case ION_DEVICE_ID_EDGEPORT_2I: /* Edgeport/2 RS422/RS485 */ + product_info->IsRS422 = 1; + product_info->IsRS485 = 1; + break; + + case ION_DEVICE_ID_EDGEPORT_8I: /* Edgeport/4 RS422 */ + case ION_DEVICE_ID_EDGEPORT_4I: /* Edgeport/4 RS422 */ + product_info->IsRS422 = 1; + break; + } + + dump_product_info(edge_serial, product_info); +} + +static int get_epic_descriptor(struct edgeport_serial *ep) +{ + int result; + struct usb_serial *serial = ep->serial; + struct edgeport_product_info *product_info = &ep->product_info; + struct edge_compatibility_descriptor *epic = &ep->epic_descriptor; + struct edge_compatibility_bits *bits; + struct device *dev = &serial->dev->dev; + + ep->is_epic = 0; + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + USB_REQUEST_ION_GET_EPIC_DESC, + 0xC0, 0x00, 0x00, + &ep->epic_descriptor, + sizeof(struct edge_compatibility_descriptor), + 300); + + if (result > 0) { + ep->is_epic = 1; + memset(product_info, 0, sizeof(struct edgeport_product_info)); + + product_info->NumPorts = epic->NumPorts; + product_info->ProdInfoVer = 0; + product_info->FirmwareMajorVersion = epic->MajorVersion; + product_info->FirmwareMinorVersion = epic->MinorVersion; + product_info->FirmwareBuildNumber = epic->BuildNumber; + product_info->iDownloadFile = epic->iDownloadFile; + product_info->EpicVer = epic->EpicVer; + product_info->Epic = epic->Supports; + product_info->ProductId = ION_DEVICE_ID_EDGEPORT_COMPATIBLE; + dump_product_info(ep, product_info); + + bits = &ep->epic_descriptor.Supports; + dev_dbg(dev, "**EPIC descriptor:\n"); + dev_dbg(dev, " VendEnableSuspend: %s\n", bits->VendEnableSuspend ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPOpen : %s\n", bits->IOSPOpen ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPClose : %s\n", bits->IOSPClose ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPChase : %s\n", bits->IOSPChase ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPSetRxFlow : %s\n", bits->IOSPSetRxFlow ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPSetTxFlow : %s\n", bits->IOSPSetTxFlow ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPSetXChar : %s\n", bits->IOSPSetXChar ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPRxCheck : %s\n", bits->IOSPRxCheck ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPSetClrBreak : %s\n", bits->IOSPSetClrBreak ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPWriteMCR : %s\n", bits->IOSPWriteMCR ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPWriteLCR : %s\n", bits->IOSPWriteLCR ? "TRUE": "FALSE"); + dev_dbg(dev, " IOSPSetBaudRate : %s\n", bits->IOSPSetBaudRate ? "TRUE": "FALSE"); + dev_dbg(dev, " TrueEdgeport : %s\n", bits->TrueEdgeport ? "TRUE": "FALSE"); + } + + return result; +} + + +/************************************************************************/ +/************************************************************************/ +/* U S B C A L L B A C K F U N C T I O N S */ +/* U S B C A L L B A C K F U N C T I O N S */ +/************************************************************************/ +/************************************************************************/ + +/***************************************************************************** + * edge_interrupt_callback + * this is the callback function for when we have received data on the + * interrupt endpoint. + *****************************************************************************/ +static void edge_interrupt_callback(struct urb *urb) +{ + struct edgeport_serial *edge_serial = urb->context; + struct device *dev; + struct edgeport_port *edge_port; + struct usb_serial_port *port; + unsigned char *data = urb->transfer_buffer; + int length = urb->actual_length; + int bytes_avail; + int position; + int txCredits; + int portNumber; + int result; + int status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", __func__, status); + return; + default: + dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", __func__, status); + goto exit; + } + + dev = &edge_serial->serial->dev->dev; + + /* process this interrupt-read even if there are no ports open */ + if (length) { + usb_serial_debug_data(dev, __func__, length, data); + + if (length > 1) { + bytes_avail = data[0] | (data[1] << 8); + if (bytes_avail) { + spin_lock(&edge_serial->es_lock); + edge_serial->rxBytesAvail += bytes_avail; + dev_dbg(dev, + "%s - bytes_avail=%d, rxBytesAvail=%d, read_in_progress=%d\n", + __func__, bytes_avail, + edge_serial->rxBytesAvail, + edge_serial->read_in_progress); + + if (edge_serial->rxBytesAvail > 0 && + !edge_serial->read_in_progress) { + dev_dbg(dev, "%s - posting a read\n", __func__); + edge_serial->read_in_progress = true; + + /* we have pending bytes on the + bulk in pipe, send a request */ + result = usb_submit_urb(edge_serial->read_urb, GFP_ATOMIC); + if (result) { + dev_err(dev, + "%s - usb_submit_urb(read bulk) failed with result = %d\n", + __func__, result); + edge_serial->read_in_progress = false; + } + } + spin_unlock(&edge_serial->es_lock); + } + } + /* grab the txcredits for the ports if available */ + position = 2; + portNumber = 0; + while ((position < length) && + (portNumber < edge_serial->serial->num_ports)) { + txCredits = data[position] | (data[position+1] << 8); + if (txCredits) { + port = edge_serial->serial->port[portNumber]; + edge_port = usb_get_serial_port_data(port); + if (edge_port->open) { + spin_lock(&edge_port->ep_lock); + edge_port->txCredits += txCredits; + spin_unlock(&edge_port->ep_lock); + dev_dbg(dev, "%s - txcredits for port%d = %d\n", + __func__, portNumber, + edge_port->txCredits); + + /* tell the tty driver that something + has changed */ + tty_port_tty_wakeup(&edge_port->port->port); + /* Since we have more credit, check + if more data can be sent */ + send_more_port_data(edge_serial, + edge_port); + } + } + position += 2; + ++portNumber; + } + } + +exit: + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) + dev_err(&urb->dev->dev, + "%s - Error %d submitting control urb\n", + __func__, result); +} + + +/***************************************************************************** + * edge_bulk_in_callback + * this is the callback function for when we have received data on the + * bulk in endpoint. + *****************************************************************************/ +static void edge_bulk_in_callback(struct urb *urb) +{ + struct edgeport_serial *edge_serial = urb->context; + struct device *dev; + unsigned char *data = urb->transfer_buffer; + int retval; + __u16 raw_data_length; + int status = urb->status; + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero read bulk status received: %d\n", + __func__, status); + edge_serial->read_in_progress = false; + return; + } + + if (urb->actual_length == 0) { + dev_dbg(&urb->dev->dev, "%s - read bulk callback with no data\n", __func__); + edge_serial->read_in_progress = false; + return; + } + + dev = &edge_serial->serial->dev->dev; + raw_data_length = urb->actual_length; + + usb_serial_debug_data(dev, __func__, raw_data_length, data); + + spin_lock(&edge_serial->es_lock); + + /* decrement our rxBytes available by the number that we just got */ + edge_serial->rxBytesAvail -= raw_data_length; + + dev_dbg(dev, "%s - Received = %d, rxBytesAvail %d\n", __func__, + raw_data_length, edge_serial->rxBytesAvail); + + process_rcvd_data(edge_serial, data, urb->actual_length); + + /* check to see if there's any more data for us to read */ + if (edge_serial->rxBytesAvail > 0) { + dev_dbg(dev, "%s - posting a read\n", __func__); + retval = usb_submit_urb(edge_serial->read_urb, GFP_ATOMIC); + if (retval) { + dev_err(dev, + "%s - usb_submit_urb(read bulk) failed, retval = %d\n", + __func__, retval); + edge_serial->read_in_progress = false; + } + } else { + edge_serial->read_in_progress = false; + } + + spin_unlock(&edge_serial->es_lock); +} + + +/***************************************************************************** + * edge_bulk_out_data_callback + * this is the callback function for when we have finished sending + * serial data on the bulk out endpoint. + *****************************************************************************/ +static void edge_bulk_out_data_callback(struct urb *urb) +{ + struct edgeport_port *edge_port = urb->context; + int status = urb->status; + + if (status) { + dev_dbg(&urb->dev->dev, + "%s - nonzero write bulk status received: %d\n", + __func__, status); + } + + if (edge_port->open) + tty_port_tty_wakeup(&edge_port->port->port); + + /* Release the Write URB */ + edge_port->write_in_progress = false; + + /* Check if more data needs to be sent */ + send_more_port_data((struct edgeport_serial *) + (usb_get_serial_data(edge_port->port->serial)), edge_port); +} + + +/***************************************************************************** + * BulkOutCmdCallback + * this is the callback function for when we have finished sending a + * command on the bulk out endpoint. + *****************************************************************************/ +static void edge_bulk_out_cmd_callback(struct urb *urb) +{ + struct edgeport_port *edge_port = urb->context; + int status = urb->status; + + atomic_dec(&CmdUrbs); + dev_dbg(&urb->dev->dev, "%s - FREE URB %p (outstanding %d)\n", + __func__, urb, atomic_read(&CmdUrbs)); + + + /* clean up the transfer buffer */ + kfree(urb->transfer_buffer); + + /* Free the command urb */ + usb_free_urb(urb); + + if (status) { + dev_dbg(&urb->dev->dev, + "%s - nonzero write bulk status received: %d\n", + __func__, status); + return; + } + + /* tell the tty driver that something has changed */ + if (edge_port->open) + tty_port_tty_wakeup(&edge_port->port->port); + + /* we have completed the command */ + edge_port->commandPending = false; + wake_up(&edge_port->wait_command); +} + + +/***************************************************************************** + * Driver tty interface functions + *****************************************************************************/ + +/***************************************************************************** + * SerialOpen + * this function is called by the tty driver when a port is opened + * If successful, we return 0 + * Otherwise we return a negative error number. + *****************************************************************************/ +static int edge_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + struct usb_serial *serial; + struct edgeport_serial *edge_serial; + int response; + + if (edge_port == NULL) + return -ENODEV; + + /* see if we've set up our endpoint info yet (can't set it up + in edge_startup as the structures were not set up at that time.) */ + serial = port->serial; + edge_serial = usb_get_serial_data(serial); + if (edge_serial == NULL) + return -ENODEV; + if (edge_serial->interrupt_in_buffer == NULL) { + struct usb_serial_port *port0 = serial->port[0]; + + /* not set up yet, so do it now */ + edge_serial->interrupt_in_buffer = + port0->interrupt_in_buffer; + edge_serial->interrupt_in_endpoint = + port0->interrupt_in_endpointAddress; + edge_serial->interrupt_read_urb = port0->interrupt_in_urb; + edge_serial->bulk_in_buffer = port0->bulk_in_buffer; + edge_serial->bulk_in_endpoint = + port0->bulk_in_endpointAddress; + edge_serial->read_urb = port0->read_urb; + edge_serial->bulk_out_endpoint = + port0->bulk_out_endpointAddress; + + /* set up our interrupt urb */ + usb_fill_int_urb(edge_serial->interrupt_read_urb, + serial->dev, + usb_rcvintpipe(serial->dev, + port0->interrupt_in_endpointAddress), + port0->interrupt_in_buffer, + edge_serial->interrupt_read_urb->transfer_buffer_length, + edge_interrupt_callback, edge_serial, + edge_serial->interrupt_read_urb->interval); + + /* set up our bulk in urb */ + usb_fill_bulk_urb(edge_serial->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, + port0->bulk_in_endpointAddress), + port0->bulk_in_buffer, + edge_serial->read_urb->transfer_buffer_length, + edge_bulk_in_callback, edge_serial); + edge_serial->read_in_progress = false; + + /* start interrupt read for this edgeport + * this interrupt will continue as long + * as the edgeport is connected */ + response = usb_submit_urb(edge_serial->interrupt_read_urb, + GFP_KERNEL); + if (response) { + dev_err(dev, "%s - Error %d submitting control urb\n", + __func__, response); + } + } + + /* initialize our wait queues */ + init_waitqueue_head(&edge_port->wait_open); + init_waitqueue_head(&edge_port->wait_chase); + init_waitqueue_head(&edge_port->wait_command); + + /* initialize our port settings */ + edge_port->txCredits = 0; /* Can't send any data yet */ + /* Must always set this bit to enable ints! */ + edge_port->shadowMCR = MCR_MASTER_IE; + edge_port->chaseResponsePending = false; + + /* send a open port command */ + edge_port->openPending = true; + edge_port->open = false; + response = send_iosp_ext_cmd(edge_port, IOSP_CMD_OPEN_PORT, 0); + + if (response < 0) { + dev_err(dev, "%s - error sending open port command\n", __func__); + edge_port->openPending = false; + return -ENODEV; + } + + /* now wait for the port to be completely opened */ + wait_event_timeout(edge_port->wait_open, !edge_port->openPending, + OPEN_TIMEOUT); + + if (!edge_port->open) { + /* open timed out */ + dev_dbg(dev, "%s - open timedout\n", __func__); + edge_port->openPending = false; + return -ENODEV; + } + + /* create the txfifo */ + edge_port->txfifo.head = 0; + edge_port->txfifo.tail = 0; + edge_port->txfifo.count = 0; + edge_port->txfifo.size = edge_port->maxTxCredits; + edge_port->txfifo.fifo = kmalloc(edge_port->maxTxCredits, GFP_KERNEL); + + if (!edge_port->txfifo.fifo) { + dev_dbg(dev, "%s - no memory\n", __func__); + edge_close(port); + return -ENOMEM; + } + + /* Allocate a URB for the write */ + edge_port->write_urb = usb_alloc_urb(0, GFP_KERNEL); + edge_port->write_in_progress = false; + + if (!edge_port->write_urb) { + dev_dbg(dev, "%s - no memory\n", __func__); + edge_close(port); + return -ENOMEM; + } + + dev_dbg(dev, "%s(%d) - Initialize TX fifo to %d bytes\n", + __func__, port->number, edge_port->maxTxCredits); + + return 0; +} + + +/************************************************************************ + * + * block_until_chase_response + * + * This function will block the close until one of the following: + * 1. Response to our Chase comes from Edgeport + * 2. A timeout of 10 seconds without activity has expired + * (1K of Edgeport data @ 2400 baud ==> 4 sec to empty) + * + ************************************************************************/ +static void block_until_chase_response(struct edgeport_port *edge_port) +{ + struct device *dev = &edge_port->port->dev; + DEFINE_WAIT(wait); + __u16 lastCredits; + int timeout = 1*HZ; + int loop = 10; + + while (1) { + /* Save Last credits */ + lastCredits = edge_port->txCredits; + + /* Did we get our Chase response */ + if (!edge_port->chaseResponsePending) { + dev_dbg(dev, "%s - Got Chase Response\n", __func__); + + /* did we get all of our credit back? */ + if (edge_port->txCredits == edge_port->maxTxCredits) { + dev_dbg(dev, "%s - Got all credits\n", __func__); + return; + } + } + + /* Block the thread for a while */ + prepare_to_wait(&edge_port->wait_chase, &wait, + TASK_UNINTERRUPTIBLE); + schedule_timeout(timeout); + finish_wait(&edge_port->wait_chase, &wait); + + if (lastCredits == edge_port->txCredits) { + /* No activity.. count down. */ + loop--; + if (loop == 0) { + edge_port->chaseResponsePending = false; + dev_dbg(dev, "%s - Chase TIMEOUT\n", __func__); + return; + } + } else { + /* Reset timeout value back to 10 seconds */ + dev_dbg(dev, "%s - Last %d, Current %d\n", __func__, + lastCredits, edge_port->txCredits); + loop = 10; + } + } +} + + +/************************************************************************ + * + * block_until_tx_empty + * + * This function will block the close until one of the following: + * 1. TX count are 0 + * 2. The edgeport has stopped + * 3. A timeout of 3 seconds without activity has expired + * + ************************************************************************/ +static void block_until_tx_empty(struct edgeport_port *edge_port) +{ + struct device *dev = &edge_port->port->dev; + DEFINE_WAIT(wait); + struct TxFifo *fifo = &edge_port->txfifo; + __u32 lastCount; + int timeout = HZ/10; + int loop = 30; + + while (1) { + /* Save Last count */ + lastCount = fifo->count; + + /* Is the Edgeport Buffer empty? */ + if (lastCount == 0) { + dev_dbg(dev, "%s - TX Buffer Empty\n", __func__); + return; + } + + /* Block the thread for a while */ + prepare_to_wait(&edge_port->wait_chase, &wait, + TASK_UNINTERRUPTIBLE); + schedule_timeout(timeout); + finish_wait(&edge_port->wait_chase, &wait); + + dev_dbg(dev, "%s wait\n", __func__); + + if (lastCount == fifo->count) { + /* No activity.. count down. */ + loop--; + if (loop == 0) { + dev_dbg(dev, "%s - TIMEOUT\n", __func__); + return; + } + } else { + /* Reset timeout value back to seconds */ + loop = 30; + } + } +} + + +/***************************************************************************** + * edge_close + * this function is called by the tty driver when a port is closed + *****************************************************************************/ +static void edge_close(struct usb_serial_port *port) +{ + struct edgeport_serial *edge_serial; + struct edgeport_port *edge_port; + int status; + + edge_serial = usb_get_serial_data(port->serial); + edge_port = usb_get_serial_port_data(port); + if (edge_serial == NULL || edge_port == NULL) + return; + + /* block until tx is empty */ + block_until_tx_empty(edge_port); + + edge_port->closePending = true; + + if ((!edge_serial->is_epic) || + ((edge_serial->is_epic) && + (edge_serial->epic_descriptor.Supports.IOSPChase))) { + /* flush and chase */ + edge_port->chaseResponsePending = true; + + dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CHASE_PORT\n", __func__); + status = send_iosp_ext_cmd(edge_port, IOSP_CMD_CHASE_PORT, 0); + if (status == 0) + /* block until chase finished */ + block_until_chase_response(edge_port); + else + edge_port->chaseResponsePending = false; + } + + if ((!edge_serial->is_epic) || + ((edge_serial->is_epic) && + (edge_serial->epic_descriptor.Supports.IOSPClose))) { + /* close the port */ + dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CLOSE_PORT\n", __func__); + send_iosp_ext_cmd(edge_port, IOSP_CMD_CLOSE_PORT, 0); + } + + /* port->close = true; */ + edge_port->closePending = false; + edge_port->open = false; + edge_port->openPending = false; + + usb_kill_urb(edge_port->write_urb); + + if (edge_port->write_urb) { + /* if this urb had a transfer buffer already + (old transfer) free it */ + kfree(edge_port->write_urb->transfer_buffer); + usb_free_urb(edge_port->write_urb); + edge_port->write_urb = NULL; + } + kfree(edge_port->txfifo.fifo); + edge_port->txfifo.fifo = NULL; +} + +/***************************************************************************** + * SerialWrite + * this function is called by the tty driver when data should be written + * to the port. + * If successful, we return the number of bytes written, otherwise we + * return a negative error number. + *****************************************************************************/ +static int edge_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *data, int count) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + struct TxFifo *fifo; + int copySize; + int bytesleft; + int firsthalf; + int secondhalf; + unsigned long flags; + + if (edge_port == NULL) + return -ENODEV; + + /* get a pointer to the Tx fifo */ + fifo = &edge_port->txfifo; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + + /* calculate number of bytes to put in fifo */ + copySize = min((unsigned int)count, + (edge_port->txCredits - fifo->count)); + + dev_dbg(&port->dev, "%s(%d) of %d byte(s) Fifo room %d -- will copy %d bytes\n", + __func__, port->number, count, + edge_port->txCredits - fifo->count, copySize); + + /* catch writes of 0 bytes which the tty driver likes to give us, + and when txCredits is empty */ + if (copySize == 0) { + dev_dbg(&port->dev, "%s - copySize = Zero\n", __func__); + goto finish_write; + } + + /* queue the data + * since we can never overflow the buffer we do not have to check for a + * full condition + * + * the copy is done is two parts -- first fill to the end of the buffer + * then copy the reset from the start of the buffer + */ + bytesleft = fifo->size - fifo->head; + firsthalf = min(bytesleft, copySize); + dev_dbg(&port->dev, "%s - copy %d bytes of %d into fifo \n", __func__, + firsthalf, bytesleft); + + /* now copy our data */ + memcpy(&fifo->fifo[fifo->head], data, firsthalf); + usb_serial_debug_data(&port->dev, __func__, firsthalf, &fifo->fifo[fifo->head]); + + /* update the index and size */ + fifo->head += firsthalf; + fifo->count += firsthalf; + + /* wrap the index */ + if (fifo->head == fifo->size) + fifo->head = 0; + + secondhalf = copySize-firsthalf; + + if (secondhalf) { + dev_dbg(&port->dev, "%s - copy rest of data %d\n", __func__, secondhalf); + memcpy(&fifo->fifo[fifo->head], &data[firsthalf], secondhalf); + usb_serial_debug_data(&port->dev, __func__, secondhalf, &fifo->fifo[fifo->head]); + /* update the index and size */ + fifo->count += secondhalf; + fifo->head += secondhalf; + /* No need to check for wrap since we can not get to end of + * the fifo in this part + */ + } + +finish_write: + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + send_more_port_data((struct edgeport_serial *) + usb_get_serial_data(port->serial), edge_port); + + dev_dbg(&port->dev, "%s wrote %d byte(s) TxCredits %d, Fifo %d\n", + __func__, copySize, edge_port->txCredits, fifo->count); + + return copySize; +} + + +/************************************************************************ + * + * send_more_port_data() + * + * This routine attempts to write additional UART transmit data + * to a port over the USB bulk pipe. It is called (1) when new + * data has been written to a port's TxBuffer from higher layers + * (2) when the peripheral sends us additional TxCredits indicating + * that it can accept more Tx data for a given port; and (3) when + * a bulk write completes successfully and we want to see if we + * can transmit more. + * + ************************************************************************/ +static void send_more_port_data(struct edgeport_serial *edge_serial, + struct edgeport_port *edge_port) +{ + struct TxFifo *fifo = &edge_port->txfifo; + struct device *dev = &edge_port->port->dev; + struct urb *urb; + unsigned char *buffer; + int status; + int count; + int bytesleft; + int firsthalf; + int secondhalf; + unsigned long flags; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + + if (edge_port->write_in_progress || + !edge_port->open || + (fifo->count == 0)) { + dev_dbg(dev, "%s(%d) EXIT - fifo %d, PendingWrite = %d\n", + __func__, edge_port->port->number, + fifo->count, edge_port->write_in_progress); + goto exit_send; + } + + /* since the amount of data in the fifo will always fit into the + * edgeport buffer we do not need to check the write length + * + * Do we have enough credits for this port to make it worthwhile + * to bother queueing a write. If it's too small, say a few bytes, + * it's better to wait for more credits so we can do a larger write. + */ + if (edge_port->txCredits < EDGE_FW_GET_TX_CREDITS_SEND_THRESHOLD(edge_port->maxTxCredits, EDGE_FW_BULK_MAX_PACKET_SIZE)) { + dev_dbg(dev, "%s(%d) Not enough credit - fifo %d TxCredit %d\n", + __func__, edge_port->port->number, fifo->count, + edge_port->txCredits); + goto exit_send; + } + + /* lock this write */ + edge_port->write_in_progress = true; + + /* get a pointer to the write_urb */ + urb = edge_port->write_urb; + + /* make sure transfer buffer is freed */ + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + + /* build the data header for the buffer and port that we are about + to send out */ + count = fifo->count; + buffer = kmalloc(count+2, GFP_ATOMIC); + if (buffer == NULL) { + dev_err_console(edge_port->port, + "%s - no more kernel memory...\n", __func__); + edge_port->write_in_progress = false; + goto exit_send; + } + buffer[0] = IOSP_BUILD_DATA_HDR1(edge_port->port->number + - edge_port->port->serial->minor, count); + buffer[1] = IOSP_BUILD_DATA_HDR2(edge_port->port->number + - edge_port->port->serial->minor, count); + + /* now copy our data */ + bytesleft = fifo->size - fifo->tail; + firsthalf = min(bytesleft, count); + memcpy(&buffer[2], &fifo->fifo[fifo->tail], firsthalf); + fifo->tail += firsthalf; + fifo->count -= firsthalf; + if (fifo->tail == fifo->size) + fifo->tail = 0; + + secondhalf = count-firsthalf; + if (secondhalf) { + memcpy(&buffer[2+firsthalf], &fifo->fifo[fifo->tail], + secondhalf); + fifo->tail += secondhalf; + fifo->count -= secondhalf; + } + + if (count) + usb_serial_debug_data(&edge_port->port->dev, __func__, count, &buffer[2]); + + /* fill up the urb with all of our data and submit it */ + usb_fill_bulk_urb(urb, edge_serial->serial->dev, + usb_sndbulkpipe(edge_serial->serial->dev, + edge_serial->bulk_out_endpoint), + buffer, count+2, + edge_bulk_out_data_callback, edge_port); + + /* decrement the number of credits we have by the number we just sent */ + edge_port->txCredits -= count; + edge_port->port->icount.tx += count; + + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + /* something went wrong */ + dev_err_console(edge_port->port, + "%s - usb_submit_urb(write bulk) failed, status = %d, data lost\n", + __func__, status); + edge_port->write_in_progress = false; + + /* revert the credits as something bad happened. */ + edge_port->txCredits += count; + edge_port->port->icount.tx -= count; + } + dev_dbg(dev, "%s wrote %d byte(s) TxCredit %d, Fifo %d\n", + __func__, count, edge_port->txCredits, fifo->count); + +exit_send: + spin_unlock_irqrestore(&edge_port->ep_lock, flags); +} + + +/***************************************************************************** + * edge_write_room + * this function is called by the tty driver when it wants to know how + * many bytes of data we can accept for a specific port. If successful, + * we return the amount of room that we have for this port (the txCredits) + * otherwise we return a negative error number. + *****************************************************************************/ +static int edge_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int room; + unsigned long flags; + + if (edge_port == NULL) + return 0; + if (edge_port->closePending) + return 0; + + if (!edge_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return 0; + } + + /* total of both buffers is still txCredit */ + spin_lock_irqsave(&edge_port->ep_lock, flags); + room = edge_port->txCredits - edge_port->txfifo.count; + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, room); + return room; +} + + +/***************************************************************************** + * edge_chars_in_buffer + * this function is called by the tty driver when it wants to know how + * many bytes of data we currently have outstanding in the port (data that + * has been written, but hasn't made it out the port yet) + * If successful, we return the number of bytes left to be written in the + * system, + * Otherwise we return a negative error number. + *****************************************************************************/ +static int edge_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int num_chars; + unsigned long flags; + + if (edge_port == NULL) + return 0; + if (edge_port->closePending) + return 0; + + if (!edge_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return 0; + } + + spin_lock_irqsave(&edge_port->ep_lock, flags); + num_chars = edge_port->maxTxCredits - edge_port->txCredits + + edge_port->txfifo.count; + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + if (num_chars) { + dev_dbg(&port->dev, "%s(port %d) - returns %d\n", __func__, + port->number, num_chars); + } + + return num_chars; +} + + +/***************************************************************************** + * SerialThrottle + * this function is called by the tty driver when it wants to stop the data + * being read from the port. + *****************************************************************************/ +static void edge_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int status; + + if (edge_port == NULL) + return; + + if (!edge_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + /* if we are implementing XON/XOFF, send the stop character */ + if (I_IXOFF(tty)) { + unsigned char stop_char = STOP_CHAR(tty); + status = edge_write(tty, port, &stop_char, 1); + if (status <= 0) + return; + } + + /* if we are implementing RTS/CTS, toggle that line */ + if (tty->termios.c_cflag & CRTSCTS) { + edge_port->shadowMCR &= ~MCR_RTS; + status = send_cmd_write_uart_register(edge_port, MCR, + edge_port->shadowMCR); + if (status != 0) + return; + } +} + + +/***************************************************************************** + * edge_unthrottle + * this function is called by the tty driver when it wants to resume the + * data being read from the port (called after SerialThrottle is called) + *****************************************************************************/ +static void edge_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int status; + + if (edge_port == NULL) + return; + + if (!edge_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + /* if we are implementing XON/XOFF, send the start character */ + if (I_IXOFF(tty)) { + unsigned char start_char = START_CHAR(tty); + status = edge_write(tty, port, &start_char, 1); + if (status <= 0) + return; + } + /* if we are implementing RTS/CTS, toggle that line */ + if (tty->termios.c_cflag & CRTSCTS) { + edge_port->shadowMCR |= MCR_RTS; + send_cmd_write_uart_register(edge_port, MCR, + edge_port->shadowMCR); + } +} + + +/***************************************************************************** + * SerialSetTermios + * this function is called by the tty driver when it wants to change + * the termios structure + *****************************************************************************/ +static void edge_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + unsigned int cflag; + + cflag = tty->termios.c_cflag; + dev_dbg(&port->dev, "%s - clfag %08x iflag %08x\n", __func__, tty->termios.c_cflag, tty->termios.c_iflag); + dev_dbg(&port->dev, "%s - old clfag %08x old iflag %08x\n", __func__, old_termios->c_cflag, old_termios->c_iflag); + + if (edge_port == NULL) + return; + + if (!edge_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + /* change the port settings to the new ones specified */ + change_port_settings(tty, edge_port, old_termios); +} + + +/***************************************************************************** + * get_lsr_info - get line status register info + * + * Purpose: Let user call ioctl() to get info when the UART physically + * is emptied. On bus types like RS485, the transmitter must + * release the bus after transmitting. This must be done when + * the transmit shift register is empty, not be done when the + * transmit holding register is empty. This functionality + * allows an RS485 driver to be written in user space. + *****************************************************************************/ +static int get_lsr_info(struct edgeport_port *edge_port, + unsigned int __user *value) +{ + unsigned int result = 0; + unsigned long flags; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + if (edge_port->maxTxCredits == edge_port->txCredits && + edge_port->txfifo.count == 0) { + dev_dbg(&edge_port->port->dev, "%s -- Empty\n", __func__); + result = TIOCSER_TEMT; + } + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + if (copy_to_user(value, &result, sizeof(int))) + return -EFAULT; + return 0; +} + +static int edge_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + unsigned int mcr; + + mcr = edge_port->shadowMCR; + if (set & TIOCM_RTS) + mcr |= MCR_RTS; + if (set & TIOCM_DTR) + mcr |= MCR_DTR; + if (set & TIOCM_LOOP) + mcr |= MCR_LOOPBACK; + + if (clear & TIOCM_RTS) + mcr &= ~MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~MCR_DTR; + if (clear & TIOCM_LOOP) + mcr &= ~MCR_LOOPBACK; + + edge_port->shadowMCR = mcr; + + send_cmd_write_uart_register(edge_port, MCR, edge_port->shadowMCR); + + return 0; +} + +static int edge_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + unsigned int result = 0; + unsigned int msr; + unsigned int mcr; + + msr = edge_port->shadowMSR; + mcr = edge_port->shadowMCR; + result = ((mcr & MCR_DTR) ? TIOCM_DTR: 0) /* 0x002 */ + | ((mcr & MCR_RTS) ? TIOCM_RTS: 0) /* 0x004 */ + | ((msr & EDGEPORT_MSR_CTS) ? TIOCM_CTS: 0) /* 0x020 */ + | ((msr & EDGEPORT_MSR_CD) ? TIOCM_CAR: 0) /* 0x040 */ + | ((msr & EDGEPORT_MSR_RI) ? TIOCM_RI: 0) /* 0x080 */ + | ((msr & EDGEPORT_MSR_DSR) ? TIOCM_DSR: 0); /* 0x100 */ + + return result; +} + +static int get_serial_info(struct edgeport_port *edge_port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + + tmp.type = PORT_16550A; + tmp.line = edge_port->port->serial->minor; + tmp.port = edge_port->port->number; + tmp.irq = 0; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = edge_port->maxTxCredits; + tmp.baud_base = 9600; + tmp.close_delay = 5*HZ; + tmp.closing_wait = 30*HZ; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + + +/***************************************************************************** + * SerialIoctl + * this function handles any ioctl calls to the driver + *****************************************************************************/ +static int edge_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + DEFINE_WAIT(wait); + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + + dev_dbg(&port->dev, "%s - port %d, cmd = 0x%x\n", __func__, port->number, cmd); + + switch (cmd) { + case TIOCSERGETLSR: + dev_dbg(&port->dev, "%s (%d) TIOCSERGETLSR\n", __func__, port->number); + return get_lsr_info(edge_port, (unsigned int __user *) arg); + + case TIOCGSERIAL: + dev_dbg(&port->dev, "%s (%d) TIOCGSERIAL\n", __func__, port->number); + return get_serial_info(edge_port, (struct serial_struct __user *) arg); + } + return -ENOIOCTLCMD; +} + + +/***************************************************************************** + * SerialBreak + * this function sends a break to the port + *****************************************************************************/ +static void edge_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + struct edgeport_serial *edge_serial = usb_get_serial_data(port->serial); + int status; + + if ((!edge_serial->is_epic) || + ((edge_serial->is_epic) && + (edge_serial->epic_descriptor.Supports.IOSPChase))) { + /* flush and chase */ + edge_port->chaseResponsePending = true; + + dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CHASE_PORT\n", __func__); + status = send_iosp_ext_cmd(edge_port, IOSP_CMD_CHASE_PORT, 0); + if (status == 0) { + /* block until chase finished */ + block_until_chase_response(edge_port); + } else { + edge_port->chaseResponsePending = false; + } + } + + if ((!edge_serial->is_epic) || + ((edge_serial->is_epic) && + (edge_serial->epic_descriptor.Supports.IOSPSetClrBreak))) { + if (break_state == -1) { + dev_dbg(&port->dev, "%s - Sending IOSP_CMD_SET_BREAK\n", __func__); + status = send_iosp_ext_cmd(edge_port, + IOSP_CMD_SET_BREAK, 0); + } else { + dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CLEAR_BREAK\n", __func__); + status = send_iosp_ext_cmd(edge_port, + IOSP_CMD_CLEAR_BREAK, 0); + } + if (status) + dev_dbg(&port->dev, "%s - error sending break set/clear command.\n", + __func__); + } +} + + +/***************************************************************************** + * process_rcvd_data + * this function handles the data received on the bulk in pipe. + *****************************************************************************/ +static void process_rcvd_data(struct edgeport_serial *edge_serial, + unsigned char *buffer, __u16 bufferLength) +{ + struct device *dev = &edge_serial->serial->dev->dev; + struct usb_serial_port *port; + struct edgeport_port *edge_port; + __u16 lastBufferLength; + __u16 rxLen; + + lastBufferLength = bufferLength + 1; + + while (bufferLength > 0) { + /* failsafe incase we get a message that we don't understand */ + if (lastBufferLength == bufferLength) { + dev_dbg(dev, "%s - stuck in loop, exiting it.\n", __func__); + break; + } + lastBufferLength = bufferLength; + + switch (edge_serial->rxState) { + case EXPECT_HDR1: + edge_serial->rxHeader1 = *buffer; + ++buffer; + --bufferLength; + + if (bufferLength == 0) { + edge_serial->rxState = EXPECT_HDR2; + break; + } + /* otherwise, drop on through */ + case EXPECT_HDR2: + edge_serial->rxHeader2 = *buffer; + ++buffer; + --bufferLength; + + dev_dbg(dev, "%s - Hdr1=%02X Hdr2=%02X\n", __func__, + edge_serial->rxHeader1, edge_serial->rxHeader2); + /* Process depending on whether this header is + * data or status */ + + if (IS_CMD_STAT_HDR(edge_serial->rxHeader1)) { + /* Decode this status header and go to + * EXPECT_HDR1 (if we can process the status + * with only 2 bytes), or go to EXPECT_HDR3 to + * get the third byte. */ + edge_serial->rxPort = + IOSP_GET_HDR_PORT(edge_serial->rxHeader1); + edge_serial->rxStatusCode = + IOSP_GET_STATUS_CODE( + edge_serial->rxHeader1); + + if (!IOSP_STATUS_IS_2BYTE( + edge_serial->rxStatusCode)) { + /* This status needs additional bytes. + * Save what we have and then wait for + * more data. + */ + edge_serial->rxStatusParam + = edge_serial->rxHeader2; + edge_serial->rxState = EXPECT_HDR3; + break; + } + /* We have all the header bytes, process the + status now */ + process_rcvd_status(edge_serial, + edge_serial->rxHeader2, 0); + edge_serial->rxState = EXPECT_HDR1; + break; + } else { + edge_serial->rxPort = + IOSP_GET_HDR_PORT(edge_serial->rxHeader1); + edge_serial->rxBytesRemaining = + IOSP_GET_HDR_DATA_LEN( + edge_serial->rxHeader1, + edge_serial->rxHeader2); + dev_dbg(dev, "%s - Data for Port %u Len %u\n", + __func__, + edge_serial->rxPort, + edge_serial->rxBytesRemaining); + + /* ASSERT(DevExt->RxPort < DevExt->NumPorts); + * ASSERT(DevExt->RxBytesRemaining < + * IOSP_MAX_DATA_LENGTH); + */ + + if (bufferLength == 0) { + edge_serial->rxState = EXPECT_DATA; + break; + } + /* Else, drop through */ + } + case EXPECT_DATA: /* Expect data */ + if (bufferLength < edge_serial->rxBytesRemaining) { + rxLen = bufferLength; + /* Expect data to start next buffer */ + edge_serial->rxState = EXPECT_DATA; + } else { + /* BufLen >= RxBytesRemaining */ + rxLen = edge_serial->rxBytesRemaining; + /* Start another header next time */ + edge_serial->rxState = EXPECT_HDR1; + } + + bufferLength -= rxLen; + edge_serial->rxBytesRemaining -= rxLen; + + /* spit this data back into the tty driver if this + port is open */ + if (rxLen) { + port = edge_serial->serial->port[ + edge_serial->rxPort]; + edge_port = usb_get_serial_port_data(port); + if (edge_port->open) { + dev_dbg(dev, "%s - Sending %d bytes to TTY for port %d\n", + __func__, rxLen, + edge_serial->rxPort); + edge_tty_recv(edge_port->port, buffer, + rxLen); + edge_port->port->icount.rx += rxLen; + } + buffer += rxLen; + } + break; + + case EXPECT_HDR3: /* Expect 3rd byte of status header */ + edge_serial->rxHeader3 = *buffer; + ++buffer; + --bufferLength; + + /* We have all the header bytes, process the + status now */ + process_rcvd_status(edge_serial, + edge_serial->rxStatusParam, + edge_serial->rxHeader3); + edge_serial->rxState = EXPECT_HDR1; + break; + } + } +} + + +/***************************************************************************** + * process_rcvd_status + * this function handles the any status messages received on the + * bulk in pipe. + *****************************************************************************/ +static void process_rcvd_status(struct edgeport_serial *edge_serial, + __u8 byte2, __u8 byte3) +{ + struct usb_serial_port *port; + struct edgeport_port *edge_port; + struct tty_struct *tty; + struct device *dev; + __u8 code = edge_serial->rxStatusCode; + + /* switch the port pointer to the one being currently talked about */ + port = edge_serial->serial->port[edge_serial->rxPort]; + edge_port = usb_get_serial_port_data(port); + if (edge_port == NULL) { + dev_err(&edge_serial->serial->dev->dev, + "%s - edge_port == NULL for port %d\n", + __func__, edge_serial->rxPort); + return; + } + dev = &port->dev; + + if (code == IOSP_EXT_STATUS) { + switch (byte2) { + case IOSP_EXT_STATUS_CHASE_RSP: + /* we want to do EXT status regardless of port + * open/closed */ + dev_dbg(dev, "%s - Port %u EXT CHASE_RSP Data = %02x\n", + __func__, edge_serial->rxPort, byte3); + /* Currently, the only EXT_STATUS is Chase, so process + * here instead of one more call to one more subroutine + * If/when more EXT_STATUS, there'll be more work to do + * Also, we currently clear flag and close the port + * regardless of content of above's Byte3. + * We could choose to do something else when Byte3 says + * Timeout on Chase from Edgeport, like wait longer in + * block_until_chase_response, but for now we don't. + */ + edge_port->chaseResponsePending = false; + wake_up(&edge_port->wait_chase); + return; + + case IOSP_EXT_STATUS_RX_CHECK_RSP: + dev_dbg(dev, "%s ========== Port %u CHECK_RSP Sequence = %02x =============\n", + __func__, edge_serial->rxPort, byte3); + /* Port->RxCheckRsp = true; */ + return; + } + } + + if (code == IOSP_STATUS_OPEN_RSP) { + edge_port->txCredits = GET_TX_BUFFER_SIZE(byte3); + edge_port->maxTxCredits = edge_port->txCredits; + dev_dbg(dev, "%s - Port %u Open Response Initial MSR = %02x TxBufferSize = %d\n", + __func__, edge_serial->rxPort, byte2, edge_port->txCredits); + handle_new_msr(edge_port, byte2); + + /* send the current line settings to the port so we are + in sync with any further termios calls */ + tty = tty_port_tty_get(&edge_port->port->port); + if (tty) { + change_port_settings(tty, + edge_port, &tty->termios); + tty_kref_put(tty); + } + + /* we have completed the open */ + edge_port->openPending = false; + edge_port->open = true; + wake_up(&edge_port->wait_open); + return; + } + + /* If port is closed, silently discard all rcvd status. We can + * have cases where buffered status is received AFTER the close + * port command is sent to the Edgeport. + */ + if (!edge_port->open || edge_port->closePending) + return; + + switch (code) { + /* Not currently sent by Edgeport */ + case IOSP_STATUS_LSR: + dev_dbg(dev, "%s - Port %u LSR Status = %02x\n", + __func__, edge_serial->rxPort, byte2); + handle_new_lsr(edge_port, false, byte2, 0); + break; + + case IOSP_STATUS_LSR_DATA: + dev_dbg(dev, "%s - Port %u LSR Status = %02x, Data = %02x\n", + __func__, edge_serial->rxPort, byte2, byte3); + /* byte2 is LSR Register */ + /* byte3 is broken data byte */ + handle_new_lsr(edge_port, true, byte2, byte3); + break; + /* + * case IOSP_EXT_4_STATUS: + * dev_dbg(dev, "%s - Port %u LSR Status = %02x Data = %02x\n", + * __func__, edge_serial->rxPort, byte2, byte3); + * break; + */ + case IOSP_STATUS_MSR: + dev_dbg(dev, "%s - Port %u MSR Status = %02x\n", + __func__, edge_serial->rxPort, byte2); + /* + * Process this new modem status and generate appropriate + * events, etc, based on the new status. This routine + * also saves the MSR in Port->ShadowMsr. + */ + handle_new_msr(edge_port, byte2); + break; + + default: + dev_dbg(dev, "%s - Unrecognized IOSP status code %u\n", __func__, code); + break; + } +} + + +/***************************************************************************** + * edge_tty_recv + * this function passes data on to the tty flip buffer + *****************************************************************************/ +static void edge_tty_recv(struct usb_serial_port *port, unsigned char *data, + int length) +{ + int cnt; + + cnt = tty_insert_flip_string(&port->port, data, length); + if (cnt < length) { + dev_err(&port->dev, "%s - dropping data, %d bytes lost\n", + __func__, length - cnt); + } + data += cnt; + length -= cnt; + + tty_flip_buffer_push(&port->port); +} + + +/***************************************************************************** + * handle_new_msr + * this function handles any change to the msr register for a port. + *****************************************************************************/ +static void handle_new_msr(struct edgeport_port *edge_port, __u8 newMsr) +{ + struct async_icount *icount; + + if (newMsr & (EDGEPORT_MSR_DELTA_CTS | EDGEPORT_MSR_DELTA_DSR | + EDGEPORT_MSR_DELTA_RI | EDGEPORT_MSR_DELTA_CD)) { + icount = &edge_port->port->icount; + + /* update input line counters */ + if (newMsr & EDGEPORT_MSR_DELTA_CTS) + icount->cts++; + if (newMsr & EDGEPORT_MSR_DELTA_DSR) + icount->dsr++; + if (newMsr & EDGEPORT_MSR_DELTA_CD) + icount->dcd++; + if (newMsr & EDGEPORT_MSR_DELTA_RI) + icount->rng++; + wake_up_interruptible(&edge_port->port->port.delta_msr_wait); + } + + /* Save the new modem status */ + edge_port->shadowMSR = newMsr & 0xf0; +} + + +/***************************************************************************** + * handle_new_lsr + * this function handles any change to the lsr register for a port. + *****************************************************************************/ +static void handle_new_lsr(struct edgeport_port *edge_port, __u8 lsrData, + __u8 lsr, __u8 data) +{ + __u8 newLsr = (__u8) (lsr & (__u8) + (LSR_OVER_ERR | LSR_PAR_ERR | LSR_FRM_ERR | LSR_BREAK)); + struct async_icount *icount; + + edge_port->shadowLSR = lsr; + + if (newLsr & LSR_BREAK) { + /* + * Parity and Framing errors only count if they + * occur exclusive of a break being + * received. + */ + newLsr &= (__u8)(LSR_OVER_ERR | LSR_BREAK); + } + + /* Place LSR data byte into Rx buffer */ + if (lsrData) + edge_tty_recv(edge_port->port, &data, 1); + + /* update input line counters */ + icount = &edge_port->port->icount; + if (newLsr & LSR_BREAK) + icount->brk++; + if (newLsr & LSR_OVER_ERR) + icount->overrun++; + if (newLsr & LSR_PAR_ERR) + icount->parity++; + if (newLsr & LSR_FRM_ERR) + icount->frame++; +} + + +/**************************************************************************** + * sram_write + * writes a number of bytes to the Edgeport device's sram starting at the + * given address. + * If successful returns the number of bytes written, otherwise it returns + * a negative error number of the problem. + ****************************************************************************/ +static int sram_write(struct usb_serial *serial, __u16 extAddr, __u16 addr, + __u16 length, const __u8 *data) +{ + int result; + __u16 current_length; + unsigned char *transfer_buffer; + + dev_dbg(&serial->dev->dev, "%s - %x, %x, %d\n", __func__, extAddr, addr, length); + + transfer_buffer = kmalloc(64, GFP_KERNEL); + if (!transfer_buffer) { + dev_err(&serial->dev->dev, "%s - kmalloc(%d) failed.\n", + __func__, 64); + return -ENOMEM; + } + + /* need to split these writes up into 64 byte chunks */ + result = 0; + while (length > 0) { + if (length > 64) + current_length = 64; + else + current_length = length; + +/* dev_dbg(&serial->dev->dev, "%s - writing %x, %x, %d\n", __func__, extAddr, addr, current_length); */ + memcpy(transfer_buffer, data, current_length); + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + USB_REQUEST_ION_WRITE_RAM, + 0x40, addr, extAddr, transfer_buffer, + current_length, 300); + if (result < 0) + break; + length -= current_length; + addr += current_length; + data += current_length; + } + + kfree(transfer_buffer); + return result; +} + + +/**************************************************************************** + * rom_write + * writes a number of bytes to the Edgeport device's ROM starting at the + * given address. + * If successful returns the number of bytes written, otherwise it returns + * a negative error number of the problem. + ****************************************************************************/ +static int rom_write(struct usb_serial *serial, __u16 extAddr, __u16 addr, + __u16 length, const __u8 *data) +{ + int result; + __u16 current_length; + unsigned char *transfer_buffer; + + transfer_buffer = kmalloc(64, GFP_KERNEL); + if (!transfer_buffer) { + dev_err(&serial->dev->dev, "%s - kmalloc(%d) failed.\n", + __func__, 64); + return -ENOMEM; + } + + /* need to split these writes up into 64 byte chunks */ + result = 0; + while (length > 0) { + if (length > 64) + current_length = 64; + else + current_length = length; + memcpy(transfer_buffer, data, current_length); + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + USB_REQUEST_ION_WRITE_ROM, 0x40, + addr, extAddr, + transfer_buffer, current_length, 300); + if (result < 0) + break; + length -= current_length; + addr += current_length; + data += current_length; + } + + kfree(transfer_buffer); + return result; +} + + +/**************************************************************************** + * rom_read + * reads a number of bytes from the Edgeport device starting at the given + * address. + * If successful returns the number of bytes read, otherwise it returns + * a negative error number of the problem. + ****************************************************************************/ +static int rom_read(struct usb_serial *serial, __u16 extAddr, + __u16 addr, __u16 length, __u8 *data) +{ + int result; + __u16 current_length; + unsigned char *transfer_buffer; + + transfer_buffer = kmalloc(64, GFP_KERNEL); + if (!transfer_buffer) { + dev_err(&serial->dev->dev, + "%s - kmalloc(%d) failed.\n", __func__, 64); + return -ENOMEM; + } + + /* need to split these reads up into 64 byte chunks */ + result = 0; + while (length > 0) { + if (length > 64) + current_length = 64; + else + current_length = length; + result = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + USB_REQUEST_ION_READ_ROM, + 0xC0, addr, extAddr, transfer_buffer, + current_length, 300); + if (result < 0) + break; + memcpy(data, transfer_buffer, current_length); + length -= current_length; + addr += current_length; + data += current_length; + } + + kfree(transfer_buffer); + return result; +} + + +/**************************************************************************** + * send_iosp_ext_cmd + * Is used to send a IOSP message to the Edgeport device + ****************************************************************************/ +static int send_iosp_ext_cmd(struct edgeport_port *edge_port, + __u8 command, __u8 param) +{ + unsigned char *buffer; + unsigned char *currentCommand; + int length = 0; + int status = 0; + + buffer = kmalloc(10, GFP_ATOMIC); + if (!buffer) { + dev_err(&edge_port->port->dev, + "%s - kmalloc(%d) failed.\n", __func__, 10); + return -ENOMEM; + } + + currentCommand = buffer; + + MAKE_CMD_EXT_CMD(¤tCommand, &length, + edge_port->port->number - edge_port->port->serial->minor, + command, param); + + status = write_cmd_usb(edge_port, buffer, length); + if (status) { + /* something bad happened, let's free up the memory */ + kfree(buffer); + } + + return status; +} + + +/***************************************************************************** + * write_cmd_usb + * this function writes the given buffer out to the bulk write endpoint. + *****************************************************************************/ +static int write_cmd_usb(struct edgeport_port *edge_port, + unsigned char *buffer, int length) +{ + struct edgeport_serial *edge_serial = + usb_get_serial_data(edge_port->port->serial); + struct device *dev = &edge_port->port->dev; + int status = 0; + struct urb *urb; + + usb_serial_debug_data(dev, __func__, length, buffer); + + /* Allocate our next urb */ + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + atomic_inc(&CmdUrbs); + dev_dbg(dev, "%s - ALLOCATE URB %p (outstanding %d)\n", + __func__, urb, atomic_read(&CmdUrbs)); + + usb_fill_bulk_urb(urb, edge_serial->serial->dev, + usb_sndbulkpipe(edge_serial->serial->dev, + edge_serial->bulk_out_endpoint), + buffer, length, edge_bulk_out_cmd_callback, edge_port); + + edge_port->commandPending = true; + status = usb_submit_urb(urb, GFP_ATOMIC); + + if (status) { + /* something went wrong */ + dev_err(dev, "%s - usb_submit_urb(write command) failed, status = %d\n", + __func__, status); + usb_kill_urb(urb); + usb_free_urb(urb); + atomic_dec(&CmdUrbs); + return status; + } + +#if 0 + wait_event(&edge_port->wait_command, !edge_port->commandPending); + + if (edge_port->commandPending) { + /* command timed out */ + dev_dbg(dev, "%s - command timed out\n", __func__); + status = -EINVAL; + } +#endif + return status; +} + + +/***************************************************************************** + * send_cmd_write_baud_rate + * this function sends the proper command to change the baud rate of the + * specified port. + *****************************************************************************/ +static int send_cmd_write_baud_rate(struct edgeport_port *edge_port, + int baudRate) +{ + struct edgeport_serial *edge_serial = + usb_get_serial_data(edge_port->port->serial); + struct device *dev = &edge_port->port->dev; + unsigned char *cmdBuffer; + unsigned char *currCmd; + int cmdLen = 0; + int divisor; + int status; + unsigned char number = + edge_port->port->number - edge_port->port->serial->minor; + + if (edge_serial->is_epic && + !edge_serial->epic_descriptor.Supports.IOSPSetBaudRate) { + dev_dbg(dev, "SendCmdWriteBaudRate - NOT Setting baud rate for port = %d, baud = %d\n", + edge_port->port->number, baudRate); + return 0; + } + + dev_dbg(dev, "%s - port = %d, baud = %d\n", __func__, + edge_port->port->number, baudRate); + + status = calc_baud_rate_divisor(dev, baudRate, &divisor); + if (status) { + dev_err(dev, "%s - bad baud rate\n", __func__); + return status; + } + + /* Alloc memory for the string of commands. */ + cmdBuffer = kmalloc(0x100, GFP_ATOMIC); + if (!cmdBuffer) { + dev_err(dev, "%s - kmalloc(%d) failed.\n", __func__, 0x100); + return -ENOMEM; + } + currCmd = cmdBuffer; + + /* Enable access to divisor latch */ + MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, number, LCR, LCR_DL_ENABLE); + + /* Write the divisor itself */ + MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, number, DLL, LOW8(divisor)); + MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, number, DLM, HIGH8(divisor)); + + /* Restore original value to disable access to divisor latch */ + MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, number, LCR, + edge_port->shadowLCR); + + status = write_cmd_usb(edge_port, cmdBuffer, cmdLen); + if (status) { + /* something bad happened, let's free up the memory */ + kfree(cmdBuffer); + } + + return status; +} + + +/***************************************************************************** + * calc_baud_rate_divisor + * this function calculates the proper baud rate divisor for the specified + * baud rate. + *****************************************************************************/ +static int calc_baud_rate_divisor(struct device *dev, int baudrate, int *divisor) +{ + int i; + __u16 custom; + + for (i = 0; i < ARRAY_SIZE(divisor_table); i++) { + if (divisor_table[i].BaudRate == baudrate) { + *divisor = divisor_table[i].Divisor; + return 0; + } + } + + /* We have tried all of the standard baud rates + * lets try to calculate the divisor for this baud rate + * Make sure the baud rate is reasonable */ + if (baudrate > 50 && baudrate < 230400) { + /* get divisor */ + custom = (__u16)((230400L + baudrate/2) / baudrate); + + *divisor = custom; + + dev_dbg(dev, "%s - Baud %d = %d\n", __func__, baudrate, custom); + return 0; + } + + return -1; +} + + +/***************************************************************************** + * send_cmd_write_uart_register + * this function builds up a uart register message and sends to the device. + *****************************************************************************/ +static int send_cmd_write_uart_register(struct edgeport_port *edge_port, + __u8 regNum, __u8 regValue) +{ + struct edgeport_serial *edge_serial = + usb_get_serial_data(edge_port->port->serial); + struct device *dev = &edge_port->port->dev; + unsigned char *cmdBuffer; + unsigned char *currCmd; + unsigned long cmdLen = 0; + int status; + + dev_dbg(dev, "%s - write to %s register 0x%02x\n", + (regNum == MCR) ? "MCR" : "LCR", __func__, regValue); + + if (edge_serial->is_epic && + !edge_serial->epic_descriptor.Supports.IOSPWriteMCR && + regNum == MCR) { + dev_dbg(dev, "SendCmdWriteUartReg - Not writing to MCR Register\n"); + return 0; + } + + if (edge_serial->is_epic && + !edge_serial->epic_descriptor.Supports.IOSPWriteLCR && + regNum == LCR) { + dev_dbg(dev, "SendCmdWriteUartReg - Not writing to LCR Register\n"); + return 0; + } + + /* Alloc memory for the string of commands. */ + cmdBuffer = kmalloc(0x10, GFP_ATOMIC); + if (cmdBuffer == NULL) + return -ENOMEM; + + currCmd = cmdBuffer; + + /* Build a cmd in the buffer to write the given register */ + MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, + edge_port->port->number - edge_port->port->serial->minor, + regNum, regValue); + + status = write_cmd_usb(edge_port, cmdBuffer, cmdLen); + if (status) { + /* something bad happened, let's free up the memory */ + kfree(cmdBuffer); + } + + return status; +} + + +/***************************************************************************** + * change_port_settings + * This routine is called to set the UART on the device to match the + * specified new settings. + *****************************************************************************/ + +static void change_port_settings(struct tty_struct *tty, + struct edgeport_port *edge_port, struct ktermios *old_termios) +{ + struct device *dev = &edge_port->port->dev; + struct edgeport_serial *edge_serial = + usb_get_serial_data(edge_port->port->serial); + int baud; + unsigned cflag; + __u8 mask = 0xff; + __u8 lData; + __u8 lParity; + __u8 lStop; + __u8 rxFlow; + __u8 txFlow; + int status; + + dev_dbg(dev, "%s - port %d\n", __func__, edge_port->port->number); + + if (!edge_port->open && + !edge_port->openPending) { + dev_dbg(dev, "%s - port not opened\n", __func__); + return; + } + + cflag = tty->termios.c_cflag; + + switch (cflag & CSIZE) { + case CS5: + lData = LCR_BITS_5; mask = 0x1f; + dev_dbg(dev, "%s - data bits = 5\n", __func__); + break; + case CS6: + lData = LCR_BITS_6; mask = 0x3f; + dev_dbg(dev, "%s - data bits = 6\n", __func__); + break; + case CS7: + lData = LCR_BITS_7; mask = 0x7f; + dev_dbg(dev, "%s - data bits = 7\n", __func__); + break; + default: + case CS8: + lData = LCR_BITS_8; + dev_dbg(dev, "%s - data bits = 8\n", __func__); + break; + } + + lParity = LCR_PAR_NONE; + if (cflag & PARENB) { + if (cflag & CMSPAR) { + if (cflag & PARODD) { + lParity = LCR_PAR_MARK; + dev_dbg(dev, "%s - parity = mark\n", __func__); + } else { + lParity = LCR_PAR_SPACE; + dev_dbg(dev, "%s - parity = space\n", __func__); + } + } else if (cflag & PARODD) { + lParity = LCR_PAR_ODD; + dev_dbg(dev, "%s - parity = odd\n", __func__); + } else { + lParity = LCR_PAR_EVEN; + dev_dbg(dev, "%s - parity = even\n", __func__); + } + } else { + dev_dbg(dev, "%s - parity = none\n", __func__); + } + + if (cflag & CSTOPB) { + lStop = LCR_STOP_2; + dev_dbg(dev, "%s - stop bits = 2\n", __func__); + } else { + lStop = LCR_STOP_1; + dev_dbg(dev, "%s - stop bits = 1\n", __func__); + } + + /* figure out the flow control settings */ + rxFlow = txFlow = 0x00; + if (cflag & CRTSCTS) { + rxFlow |= IOSP_RX_FLOW_RTS; + txFlow |= IOSP_TX_FLOW_CTS; + dev_dbg(dev, "%s - RTS/CTS is enabled\n", __func__); + } else { + dev_dbg(dev, "%s - RTS/CTS is disabled\n", __func__); + } + + /* if we are implementing XON/XOFF, set the start and stop character + in the device */ + if (I_IXOFF(tty) || I_IXON(tty)) { + unsigned char stop_char = STOP_CHAR(tty); + unsigned char start_char = START_CHAR(tty); + + if ((!edge_serial->is_epic) || + ((edge_serial->is_epic) && + (edge_serial->epic_descriptor.Supports.IOSPSetXChar))) { + send_iosp_ext_cmd(edge_port, + IOSP_CMD_SET_XON_CHAR, start_char); + send_iosp_ext_cmd(edge_port, + IOSP_CMD_SET_XOFF_CHAR, stop_char); + } + + /* if we are implementing INBOUND XON/XOFF */ + if (I_IXOFF(tty)) { + rxFlow |= IOSP_RX_FLOW_XON_XOFF; + dev_dbg(dev, "%s - INBOUND XON/XOFF is enabled, XON = %2x, XOFF = %2x\n", + __func__, start_char, stop_char); + } else { + dev_dbg(dev, "%s - INBOUND XON/XOFF is disabled\n", __func__); + } + + /* if we are implementing OUTBOUND XON/XOFF */ + if (I_IXON(tty)) { + txFlow |= IOSP_TX_FLOW_XON_XOFF; + dev_dbg(dev, "%s - OUTBOUND XON/XOFF is enabled, XON = %2x, XOFF = %2x\n", + __func__, start_char, stop_char); + } else { + dev_dbg(dev, "%s - OUTBOUND XON/XOFF is disabled\n", __func__); + } + } + + /* Set flow control to the configured value */ + if ((!edge_serial->is_epic) || + ((edge_serial->is_epic) && + (edge_serial->epic_descriptor.Supports.IOSPSetRxFlow))) + send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_RX_FLOW, rxFlow); + if ((!edge_serial->is_epic) || + ((edge_serial->is_epic) && + (edge_serial->epic_descriptor.Supports.IOSPSetTxFlow))) + send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_TX_FLOW, txFlow); + + + edge_port->shadowLCR &= ~(LCR_BITS_MASK | LCR_STOP_MASK | LCR_PAR_MASK); + edge_port->shadowLCR |= (lData | lParity | lStop); + + edge_port->validDataMask = mask; + + /* Send the updated LCR value to the EdgePort */ + status = send_cmd_write_uart_register(edge_port, LCR, + edge_port->shadowLCR); + if (status != 0) + return; + + /* set up the MCR register and send it to the EdgePort */ + edge_port->shadowMCR = MCR_MASTER_IE; + if (cflag & CBAUD) + edge_port->shadowMCR |= (MCR_DTR | MCR_RTS); + + status = send_cmd_write_uart_register(edge_port, MCR, + edge_port->shadowMCR); + if (status != 0) + return; + + /* Determine divisor based on baud rate */ + baud = tty_get_baud_rate(tty); + if (!baud) { + /* pick a default, any default... */ + baud = 9600; + } + + dev_dbg(dev, "%s - baud rate = %d\n", __func__, baud); + status = send_cmd_write_baud_rate(edge_port, baud); + if (status == -1) { + /* Speed change was not possible - put back the old speed */ + baud = tty_termios_baud_rate(old_termios); + tty_encode_baud_rate(tty, baud, baud); + } +} + + +/**************************************************************************** + * unicode_to_ascii + * Turns a string from Unicode into ASCII. + * Doesn't do a good job with any characters that are outside the normal + * ASCII range, but it's only for debugging... + * NOTE: expects the unicode in LE format + ****************************************************************************/ +static void unicode_to_ascii(char *string, int buflen, + __le16 *unicode, int unicode_size) +{ + int i; + + if (buflen <= 0) /* never happens, but... */ + return; + --buflen; /* space for nul */ + + for (i = 0; i < unicode_size; i++) { + if (i >= buflen) + break; + string[i] = (char)(le16_to_cpu(unicode[i])); + } + string[i] = 0x00; +} + + +/**************************************************************************** + * get_manufacturing_desc + * reads in the manufacturing descriptor and stores it into the serial + * structure. + ****************************************************************************/ +static void get_manufacturing_desc(struct edgeport_serial *edge_serial) +{ + struct device *dev = &edge_serial->serial->dev->dev; + int response; + + dev_dbg(dev, "getting manufacturer descriptor\n"); + + response = rom_read(edge_serial->serial, + (EDGE_MANUF_DESC_ADDR & 0xffff0000) >> 16, + (__u16)(EDGE_MANUF_DESC_ADDR & 0x0000ffff), + EDGE_MANUF_DESC_LEN, + (__u8 *)(&edge_serial->manuf_descriptor)); + + if (response < 1) + dev_err(dev, "error in getting manufacturer descriptor\n"); + else { + char string[30]; + dev_dbg(dev, "**Manufacturer Descriptor\n"); + dev_dbg(dev, " RomSize: %dK\n", + edge_serial->manuf_descriptor.RomSize); + dev_dbg(dev, " RamSize: %dK\n", + edge_serial->manuf_descriptor.RamSize); + dev_dbg(dev, " CpuRev: %d\n", + edge_serial->manuf_descriptor.CpuRev); + dev_dbg(dev, " BoardRev: %d\n", + edge_serial->manuf_descriptor.BoardRev); + dev_dbg(dev, " NumPorts: %d\n", + edge_serial->manuf_descriptor.NumPorts); + dev_dbg(dev, " DescDate: %d/%d/%d\n", + edge_serial->manuf_descriptor.DescDate[0], + edge_serial->manuf_descriptor.DescDate[1], + edge_serial->manuf_descriptor.DescDate[2]+1900); + unicode_to_ascii(string, sizeof(string), + edge_serial->manuf_descriptor.SerialNumber, + edge_serial->manuf_descriptor.SerNumLength/2); + dev_dbg(dev, " SerialNumber: %s\n", string); + unicode_to_ascii(string, sizeof(string), + edge_serial->manuf_descriptor.AssemblyNumber, + edge_serial->manuf_descriptor.AssemblyNumLength/2); + dev_dbg(dev, " AssemblyNumber: %s\n", string); + unicode_to_ascii(string, sizeof(string), + edge_serial->manuf_descriptor.OemAssyNumber, + edge_serial->manuf_descriptor.OemAssyNumLength/2); + dev_dbg(dev, " OemAssyNumber: %s\n", string); + dev_dbg(dev, " UartType: %d\n", + edge_serial->manuf_descriptor.UartType); + dev_dbg(dev, " IonPid: %d\n", + edge_serial->manuf_descriptor.IonPid); + dev_dbg(dev, " IonConfig: %d\n", + edge_serial->manuf_descriptor.IonConfig); + } +} + + +/**************************************************************************** + * get_boot_desc + * reads in the bootloader descriptor and stores it into the serial + * structure. + ****************************************************************************/ +static void get_boot_desc(struct edgeport_serial *edge_serial) +{ + struct device *dev = &edge_serial->serial->dev->dev; + int response; + + dev_dbg(dev, "getting boot descriptor\n"); + + response = rom_read(edge_serial->serial, + (EDGE_BOOT_DESC_ADDR & 0xffff0000) >> 16, + (__u16)(EDGE_BOOT_DESC_ADDR & 0x0000ffff), + EDGE_BOOT_DESC_LEN, + (__u8 *)(&edge_serial->boot_descriptor)); + + if (response < 1) + dev_err(dev, "error in getting boot descriptor\n"); + else { + dev_dbg(dev, "**Boot Descriptor:\n"); + dev_dbg(dev, " BootCodeLength: %d\n", + le16_to_cpu(edge_serial->boot_descriptor.BootCodeLength)); + dev_dbg(dev, " MajorVersion: %d\n", + edge_serial->boot_descriptor.MajorVersion); + dev_dbg(dev, " MinorVersion: %d\n", + edge_serial->boot_descriptor.MinorVersion); + dev_dbg(dev, " BuildNumber: %d\n", + le16_to_cpu(edge_serial->boot_descriptor.BuildNumber)); + dev_dbg(dev, " Capabilities: 0x%x\n", + le16_to_cpu(edge_serial->boot_descriptor.Capabilities)); + dev_dbg(dev, " UConfig0: %d\n", + edge_serial->boot_descriptor.UConfig0); + dev_dbg(dev, " UConfig1: %d\n", + edge_serial->boot_descriptor.UConfig1); + } +} + + +/**************************************************************************** + * load_application_firmware + * This is called to load the application firmware to the device + ****************************************************************************/ +static void load_application_firmware(struct edgeport_serial *edge_serial) +{ + struct device *dev = &edge_serial->serial->dev->dev; + const struct ihex_binrec *rec; + const struct firmware *fw; + const char *fw_name; + const char *fw_info; + int response; + __u32 Operaddr; + __u16 build; + + switch (edge_serial->product_info.iDownloadFile) { + case EDGE_DOWNLOAD_FILE_I930: + fw_info = "downloading firmware version (930)"; + fw_name = "edgeport/down.fw"; + break; + + case EDGE_DOWNLOAD_FILE_80251: + fw_info = "downloading firmware version (80251)"; + fw_name = "edgeport/down2.fw"; + break; + + case EDGE_DOWNLOAD_FILE_NONE: + dev_dbg(dev, "No download file specified, skipping download\n"); + return; + + default: + return; + } + + response = request_ihex_firmware(&fw, fw_name, + &edge_serial->serial->dev->dev); + if (response) { + dev_err(dev, "Failed to load image \"%s\" err %d\n", + fw_name, response); + return; + } + + rec = (const struct ihex_binrec *)fw->data; + build = (rec->data[2] << 8) | rec->data[3]; + + dev_dbg(dev, "%s %d.%d.%d\n", fw_info, rec->data[0], rec->data[1], build); + + edge_serial->product_info.FirmwareMajorVersion = rec->data[0]; + edge_serial->product_info.FirmwareMinorVersion = rec->data[1]; + edge_serial->product_info.FirmwareBuildNumber = cpu_to_le16(build); + + for (rec = ihex_next_binrec(rec); rec; + rec = ihex_next_binrec(rec)) { + Operaddr = be32_to_cpu(rec->addr); + response = sram_write(edge_serial->serial, + Operaddr >> 16, + Operaddr & 0xFFFF, + be16_to_cpu(rec->len), + &rec->data[0]); + if (response < 0) { + dev_err(&edge_serial->serial->dev->dev, + "sram_write failed (%x, %x, %d)\n", + Operaddr >> 16, Operaddr & 0xFFFF, + be16_to_cpu(rec->len)); + break; + } + } + + dev_dbg(dev, "sending exec_dl_code\n"); + response = usb_control_msg (edge_serial->serial->dev, + usb_sndctrlpipe(edge_serial->serial->dev, 0), + USB_REQUEST_ION_EXEC_DL_CODE, + 0x40, 0x4000, 0x0001, NULL, 0, 3000); + + release_firmware(fw); +} + + +/**************************************************************************** + * edge_startup + ****************************************************************************/ +static int edge_startup(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial; + struct usb_device *dev; + struct device *ddev = &serial->dev->dev; + int i; + int response; + bool interrupt_in_found; + bool bulk_in_found; + bool bulk_out_found; + static __u32 descriptor[3] = { EDGE_COMPATIBILITY_MASK0, + EDGE_COMPATIBILITY_MASK1, + EDGE_COMPATIBILITY_MASK2 }; + + dev = serial->dev; + + /* create our private serial structure */ + edge_serial = kzalloc(sizeof(struct edgeport_serial), GFP_KERNEL); + if (edge_serial == NULL) { + dev_err(&serial->dev->dev, "%s - Out of memory\n", __func__); + return -ENOMEM; + } + spin_lock_init(&edge_serial->es_lock); + edge_serial->serial = serial; + usb_set_serial_data(serial, edge_serial); + + /* get the name for the device from the device */ + i = usb_string(dev, dev->descriptor.iManufacturer, + &edge_serial->name[0], MAX_NAME_LEN+1); + if (i < 0) + i = 0; + edge_serial->name[i++] = ' '; + usb_string(dev, dev->descriptor.iProduct, + &edge_serial->name[i], MAX_NAME_LEN+2 - i); + + dev_info(&serial->dev->dev, "%s detected\n", edge_serial->name); + + /* Read the epic descriptor */ + if (get_epic_descriptor(edge_serial) <= 0) { + /* memcpy descriptor to Supports structures */ + memcpy(&edge_serial->epic_descriptor.Supports, descriptor, + sizeof(struct edge_compatibility_bits)); + + /* get the manufacturing descriptor for this device */ + get_manufacturing_desc(edge_serial); + + /* get the boot descriptor */ + get_boot_desc(edge_serial); + + get_product_info(edge_serial); + } + + /* set the number of ports from the manufacturing description */ + /* serial->num_ports = serial->product_info.NumPorts; */ + if ((!edge_serial->is_epic) && + (edge_serial->product_info.NumPorts != serial->num_ports)) { + dev_warn(ddev, + "Device Reported %d serial ports vs. core thinking we have %d ports, email greg@kroah.com this information.\n", + edge_serial->product_info.NumPorts, + serial->num_ports); + } + + dev_dbg(ddev, "%s - time 1 %ld\n", __func__, jiffies); + + /* If not an EPiC device */ + if (!edge_serial->is_epic) { + /* now load the application firmware into this device */ + load_application_firmware(edge_serial); + + dev_dbg(ddev, "%s - time 2 %ld\n", __func__, jiffies); + + /* Check current Edgeport EEPROM and update if necessary */ + update_edgeport_E2PROM(edge_serial); + + dev_dbg(ddev, "%s - time 3 %ld\n", __func__, jiffies); + + /* set the configuration to use #1 */ +/* dev_dbg(ddev, "set_configuration 1\n"); */ +/* usb_set_configuration (dev, 1); */ + } + dev_dbg(ddev, " FirmwareMajorVersion %d.%d.%d\n", + edge_serial->product_info.FirmwareMajorVersion, + edge_serial->product_info.FirmwareMinorVersion, + le16_to_cpu(edge_serial->product_info.FirmwareBuildNumber)); + + /* we set up the pointers to the endpoints in the edge_open function, + * as the structures aren't created yet. */ + + response = 0; + + if (edge_serial->is_epic) { + /* EPIC thing, set up our interrupt polling now and our read + * urb, so that the device knows it really is connected. */ + interrupt_in_found = bulk_in_found = bulk_out_found = false; + for (i = 0; i < serial->interface->altsetting[0] + .desc.bNumEndpoints; ++i) { + struct usb_endpoint_descriptor *endpoint; + int buffer_size; + + endpoint = &serial->interface->altsetting[0]. + endpoint[i].desc; + buffer_size = usb_endpoint_maxp(endpoint); + if (!interrupt_in_found && + (usb_endpoint_is_int_in(endpoint))) { + /* we found a interrupt in endpoint */ + dev_dbg(ddev, "found interrupt in\n"); + + /* not set up yet, so do it now */ + edge_serial->interrupt_read_urb = + usb_alloc_urb(0, GFP_KERNEL); + if (!edge_serial->interrupt_read_urb) { + dev_err(ddev, "out of memory\n"); + return -ENOMEM; + } + edge_serial->interrupt_in_buffer = + kmalloc(buffer_size, GFP_KERNEL); + if (!edge_serial->interrupt_in_buffer) { + dev_err(ddev, "out of memory\n"); + usb_free_urb(edge_serial->interrupt_read_urb); + return -ENOMEM; + } + edge_serial->interrupt_in_endpoint = + endpoint->bEndpointAddress; + + /* set up our interrupt urb */ + usb_fill_int_urb( + edge_serial->interrupt_read_urb, + dev, + usb_rcvintpipe(dev, + endpoint->bEndpointAddress), + edge_serial->interrupt_in_buffer, + buffer_size, + edge_interrupt_callback, + edge_serial, + endpoint->bInterval); + + interrupt_in_found = true; + } + + if (!bulk_in_found && + (usb_endpoint_is_bulk_in(endpoint))) { + /* we found a bulk in endpoint */ + dev_dbg(ddev, "found bulk in\n"); + + /* not set up yet, so do it now */ + edge_serial->read_urb = + usb_alloc_urb(0, GFP_KERNEL); + if (!edge_serial->read_urb) { + dev_err(ddev, "out of memory\n"); + return -ENOMEM; + } + edge_serial->bulk_in_buffer = + kmalloc(buffer_size, GFP_KERNEL); + if (!edge_serial->bulk_in_buffer) { + dev_err(&dev->dev, "out of memory\n"); + usb_free_urb(edge_serial->read_urb); + return -ENOMEM; + } + edge_serial->bulk_in_endpoint = + endpoint->bEndpointAddress; + + /* set up our bulk in urb */ + usb_fill_bulk_urb(edge_serial->read_urb, dev, + usb_rcvbulkpipe(dev, + endpoint->bEndpointAddress), + edge_serial->bulk_in_buffer, + usb_endpoint_maxp(endpoint), + edge_bulk_in_callback, + edge_serial); + bulk_in_found = true; + } + + if (!bulk_out_found && + (usb_endpoint_is_bulk_out(endpoint))) { + /* we found a bulk out endpoint */ + dev_dbg(ddev, "found bulk out\n"); + edge_serial->bulk_out_endpoint = + endpoint->bEndpointAddress; + bulk_out_found = true; + } + } + + if (!interrupt_in_found || !bulk_in_found || !bulk_out_found) { + dev_err(ddev, "Error - the proper endpoints were not found!\n"); + return -ENODEV; + } + + /* start interrupt read for this edgeport this interrupt will + * continue as long as the edgeport is connected */ + response = usb_submit_urb(edge_serial->interrupt_read_urb, + GFP_KERNEL); + if (response) + dev_err(ddev, "%s - Error %d submitting control urb\n", + __func__, response); + } + return response; +} + + +/**************************************************************************** + * edge_disconnect + * This function is called whenever the device is removed from the usb bus. + ****************************************************************************/ +static void edge_disconnect(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + + /* stop reads and writes on all ports */ + /* free up our endpoint stuff */ + if (edge_serial->is_epic) { + usb_kill_urb(edge_serial->interrupt_read_urb); + usb_free_urb(edge_serial->interrupt_read_urb); + kfree(edge_serial->interrupt_in_buffer); + + usb_kill_urb(edge_serial->read_urb); + usb_free_urb(edge_serial->read_urb); + kfree(edge_serial->bulk_in_buffer); + } +} + + +/**************************************************************************** + * edge_release + * This function is called when the device structure is deallocated. + ****************************************************************************/ +static void edge_release(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + + kfree(edge_serial); +} + +static int edge_port_probe(struct usb_serial_port *port) +{ + struct edgeport_port *edge_port; + + edge_port = kzalloc(sizeof(*edge_port), GFP_KERNEL); + if (!edge_port) + return -ENOMEM; + + spin_lock_init(&edge_port->ep_lock); + edge_port->port = port; + + usb_set_serial_port_data(port, edge_port); + + return 0; +} + +static int edge_port_remove(struct usb_serial_port *port) +{ + struct edgeport_port *edge_port; + + edge_port = usb_get_serial_port_data(port); + kfree(edge_port); + + return 0; +} + +module_usb_serial_driver(serial_drivers, id_table_combined); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("edgeport/boot.fw"); +MODULE_FIRMWARE("edgeport/boot2.fw"); +MODULE_FIRMWARE("edgeport/down.fw"); +MODULE_FIRMWARE("edgeport/down2.fw"); diff --git a/drivers/usb/serial/io_edgeport.h b/drivers/usb/serial/io_edgeport.h new file mode 100644 index 00000000..ad9c1d47 --- /dev/null +++ b/drivers/usb/serial/io_edgeport.h @@ -0,0 +1,134 @@ +/************************************************************************ + * + * io_edgeport.h Edgeport Linux Interface definitions + * + * Copyright (C) 2000 Inside Out Networks, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * + ************************************************************************/ + +#if !defined(_IO_EDGEPORT_H_) +#define _IO_EDGEPORT_H_ + + +#define MAX_RS232_PORTS 8 /* Max # of RS-232 ports per device */ + +/* typedefs that the insideout headers need */ +#ifndef LOW8 + #define LOW8(a) ((unsigned char)(a & 0xff)) +#endif +#ifndef HIGH8 + #define HIGH8(a) ((unsigned char)((a & 0xff00) >> 8)) +#endif + +#ifndef __KERNEL__ +#define __KERNEL__ +#endif + +#include "io_usbvend.h" + + + +/* The following table is used to map the USBx port number to + * the device serial number (or physical USB path), */ +#define MAX_EDGEPORTS 64 + +struct comMapper { + char SerialNumber[MAX_SERIALNUMBER_LEN+1]; /* Serial number/usb path */ + int numPorts; /* Number of ports */ + int Original[MAX_RS232_PORTS]; /* Port numbers set by IOCTL */ + int Port[MAX_RS232_PORTS]; /* Actual used port numbers */ +}; + + +#define EDGEPORT_CONFIG_DEVICE "/proc/edgeport" + +/* /proc/edgeport Interface + * This interface uses read/write/lseek interface to talk to the edgeport driver + * the following read functions are supported: */ +#define PROC_GET_MAPPING_TO_PATH 1 +#define PROC_GET_COM_ENTRY 2 +#define PROC_GET_EDGE_MANUF_DESCRIPTOR 3 +#define PROC_GET_BOOT_DESCRIPTOR 4 +#define PROC_GET_PRODUCT_INFO 5 +#define PROC_GET_STRINGS 6 +#define PROC_GET_CURRENT_COM_MAPPING 7 + +/* The parameters to the lseek() for the read is: */ +#define PROC_READ_SETUP(Command, Argument) ((Command) + ((Argument)<<8)) + + +/* the following write functions are supported: */ +#define PROC_SET_COM_MAPPING 1 +#define PROC_SET_COM_ENTRY 2 + + +/* The following structure is passed to the write */ +struct procWrite { + int Command; + union { + struct comMapper Entry; + int ComMappingBasedOnUSBPort; /* Boolean value */ + } u; +}; + +/* + * Product information read from the Edgeport + */ +struct edgeport_product_info { + __u16 ProductId; /* Product Identifier */ + __u8 NumPorts; /* Number of ports on edgeport */ + __u8 ProdInfoVer; /* What version of structure is this? */ + + __u32 IsServer :1; /* Set if Server */ + __u32 IsRS232 :1; /* Set if RS-232 ports exist */ + __u32 IsRS422 :1; /* Set if RS-422 ports exist */ + __u32 IsRS485 :1; /* Set if RS-485 ports exist */ + __u32 IsReserved :28; /* Reserved for later expansion */ + + __u8 RomSize; /* Size of ROM/E2PROM in K */ + __u8 RamSize; /* Size of external RAM in K */ + __u8 CpuRev; /* CPU revision level (chg only if s/w visible) */ + __u8 BoardRev; /* PCB revision level (chg only if s/w visible) */ + + __u8 BootMajorVersion; /* Boot Firmware version: xx. */ + __u8 BootMinorVersion; /* yy. */ + __le16 BootBuildNumber; /* zzzz (LE format) */ + + __u8 FirmwareMajorVersion; /* Operational Firmware version:xx. */ + __u8 FirmwareMinorVersion; /* yy. */ + __le16 FirmwareBuildNumber; /* zzzz (LE format) */ + + __u8 ManufactureDescDate[3]; /* MM/DD/YY when descriptor template was compiled */ + __u8 HardwareType; + + __u8 iDownloadFile; /* What to download to EPiC device */ + __u8 EpicVer; /* What version of EPiC spec this device supports */ + + struct edge_compatibility_bits Epic; +}; + +/* + * Edgeport Stringblock String locations + */ +#define EDGESTRING_MANUFNAME 1 /* Manufacture Name */ +#define EDGESTRING_PRODNAME 2 /* Product Name */ +#define EDGESTRING_SERIALNUM 3 /* Serial Number */ +#define EDGESTRING_ASSEMNUM 4 /* Assembly Number */ +#define EDGESTRING_OEMASSEMNUM 5 /* OEM Assembly Number */ +#define EDGESTRING_MANUFDATE 6 /* Manufacture Date */ +#define EDGESTRING_ORIGSERIALNUM 7 /* Serial Number */ + +struct string_block { + __u16 NumStrings; /* Number of strings in block */ + __u16 Strings[1]; /* Start of string block */ +}; + + + +#endif diff --git a/drivers/usb/serial/io_ionsp.h b/drivers/usb/serial/io_ionsp.h new file mode 100644 index 00000000..5cc591ba --- /dev/null +++ b/drivers/usb/serial/io_ionsp.h @@ -0,0 +1,455 @@ +/************************************************************************ + * + * IONSP.H Definitions for I/O Networks Serial Protocol + * + * Copyright (C) 1997-1998 Inside Out Networks, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * These definitions are used by both kernel-mode driver and the + * peripheral firmware and MUST be kept in sync. + * + ************************************************************************/ + +/************************************************************************ + +The data to and from all ports on the peripheral is multiplexed +through a single endpoint pair (EP1 since it supports 64-byte +MaxPacketSize). Therefore, the data, commands, and status for +each port must be preceded by a short header identifying the +destination port. The header also identifies the bytes that follow +as data or as command/status info. + +Header format, first byte: + + CLLLLPPP + -------- + | | |------ Port Number: 0-7 + | |--------- Length: MSB bits of length + |----------- Data/Command: 0 = Data header + 1 = Cmd / Status (Cmd if OUT, Status if IN) + +This gives 2 possible formats: + + + Data header: 0LLLLPPP LLLLLLLL + ============ + + Where (LLLL,LLLLLLL) is 12-bit length of data that follows for + port number (PPP). The length is 0-based (0-FFF means 0-4095 + bytes). The ~4K limit allows the host driver (which deals in + transfer requests instead of individual packets) to write a + large chunk of data in a single request. Note, however, that + the length must always be <= the current TxCredits for a given + port due to buffering limitations on the peripheral. + + + Cmd/Status header: 1ccccPPP [ CCCCCCCC, Params ]... + ================== + + Where (cccc) or (cccc,CCCCCCCC) is the cmd or status identifier. + Frequently-used values are encoded as (cccc), longer ones using + (cccc,CCCCCCCC). Subsequent bytes are optional parameters and are + specific to the cmd or status code. This may include a length + for command and status codes that need variable-length parameters. + + +In addition, we use another interrupt pipe (endpoint) which the host polls +periodically for flow control information. The peripheral, when there has +been a change, sends the following 10-byte packet: + + RRRRRRRRRRRRRRRR + T0T0T0T0T0T0T0T0 + T1T1T1T1T1T1T1T1 + T2T2T2T2T2T2T2T2 + T3T3T3T3T3T3T3T3 + +The first field is the 16-bit RxBytesAvail field, which indicates the +number of bytes which may be read by the host from EP1. This is necessary: +(a) because OSR2.1 has a bug which causes data loss if the peripheral returns +fewer bytes than the host expects to read, and (b) because, on Microsoft +platforms at least, an outstanding read posted on EP1 consumes about 35% of +the CPU just polling the device for data. + +The next 4 fields are the 16-bit TxCredits for each port, which indicate how +many bytes the host is allowed to send on EP1 for transmit to a given port. +After an OPEN_PORT command, the Edgeport sends the initial TxCredits for that +port. + +All 16-bit fields are sent in little-endian (Intel) format. + +************************************************************************/ + +// +// Define format of InterruptStatus packet returned from the +// Interrupt pipe +// + +struct int_status_pkt { + __u16 RxBytesAvail; // Additional bytes available to + // be read from Bulk IN pipe + __u16 TxCredits[MAX_RS232_PORTS]; // Additional space available in + // given port's TxBuffer +}; + + +#define GET_INT_STATUS_SIZE(NumPorts) (sizeof(__u16) + (sizeof(__u16) * (NumPorts))) + + + +// +// Define cmd/status header values and macros to extract them. +// +// Data: 0LLLLPPP LLLLLLLL +// Cmd/Stat: 1ccccPPP CCCCCCCC + +#define IOSP_DATA_HDR_SIZE 2 +#define IOSP_CMD_HDR_SIZE 2 + +#define IOSP_MAX_DATA_LENGTH 0x0FFF // 12 bits -> 4K + +#define IOSP_PORT_MASK 0x07 // Mask to isolate port number +#define IOSP_CMD_STAT_BIT 0x80 // If set, this is command/status header + +#define IS_CMD_STAT_HDR(Byte1) ((Byte1) & IOSP_CMD_STAT_BIT) +#define IS_DATA_HDR(Byte1) (!IS_CMD_STAT_HDR(Byte1)) + +#define IOSP_GET_HDR_PORT(Byte1) ((__u8) ((Byte1) & IOSP_PORT_MASK)) +#define IOSP_GET_HDR_DATA_LEN(Byte1, Byte2) ((__u16) (((__u16)((Byte1) & 0x78)) << 5) | (Byte2)) +#define IOSP_GET_STATUS_CODE(Byte1) ((__u8) (((Byte1) & 0x78) >> 3)) + + +// +// These macros build the 1st and 2nd bytes for a data header +// +#define IOSP_BUILD_DATA_HDR1(Port, Len) ((__u8) (((Port) | ((__u8) (((__u16) (Len)) >> 5) & 0x78)))) +#define IOSP_BUILD_DATA_HDR2(Port, Len) ((__u8) (Len)) + + +// +// These macros build the 1st and 2nd bytes for a command header +// +#define IOSP_BUILD_CMD_HDR1(Port, Cmd) ((__u8) (IOSP_CMD_STAT_BIT | (Port) | ((__u8) ((Cmd) << 3)))) + + +//-------------------------------------------------------------- +// +// Define values for commands and command parameters +// (sent from Host to Edgeport) +// +// 1ccccPPP P1P1P1P1 [ P2P2P2P2P2 ]... +// +// cccc: 00-07 2-byte commands. Write UART register 0-7 with +// value in P1. See 16650.H for definitions of +// UART register numbers and contents. +// +// 08-0B 3-byte commands: ==== P1 ==== ==== P2 ==== +// 08 available for expansion +// 09 1-param commands Command Code Param +// 0A available for expansion +// 0B available for expansion +// +// 0C-0D 4-byte commands. P1 = extended cmd and P2,P3 = params +// Currently unimplemented. +// +// 0E-0F N-byte commands: P1 = num bytes after P1 (ie, TotalLen - 2) +// P2 = extended cmd, P3..Pn = parameters. +// Currently unimplemented. +// + +#define IOSP_WRITE_UART_REG(n) ((n) & 0x07) // UartReg[ n ] := P1 + +// Register numbers and contents +// defined in 16554.H. + +// 0x08 // Available for expansion. +#define IOSP_EXT_CMD 0x09 // P1 = Command code (defined below) + +// P2 = Parameter + +// +// Extended Command values, used with IOSP_EXT_CMD, may +// or may not use parameter P2. +// + +#define IOSP_CMD_OPEN_PORT 0x00 // Enable ints, init UART. (NO PARAM) +#define IOSP_CMD_CLOSE_PORT 0x01 // Disable ints, flush buffers. (NO PARAM) +#define IOSP_CMD_CHASE_PORT 0x02 // Wait for Edgeport TX buffers to empty. (NO PARAM) +#define IOSP_CMD_SET_RX_FLOW 0x03 // Set Rx Flow Control in Edgeport +#define IOSP_CMD_SET_TX_FLOW 0x04 // Set Tx Flow Control in Edgeport +#define IOSP_CMD_SET_XON_CHAR 0x05 // Set XON Character in Edgeport +#define IOSP_CMD_SET_XOFF_CHAR 0x06 // Set XOFF Character in Edgeport +#define IOSP_CMD_RX_CHECK_REQ 0x07 // Request Edgeport to insert a Checkpoint into + +// the receive data stream (Parameter = 1 byte sequence number) + +#define IOSP_CMD_SET_BREAK 0x08 // Turn on the BREAK (LCR bit 6) +#define IOSP_CMD_CLEAR_BREAK 0x09 // Turn off the BREAK (LCR bit 6) + + +// +// Define macros to simplify building of IOSP cmds +// + +#define MAKE_CMD_WRITE_REG(ppBuf, pLen, Port, Reg, Val) \ +do { \ + (*(ppBuf))[0] = IOSP_BUILD_CMD_HDR1((Port), \ + IOSP_WRITE_UART_REG(Reg)); \ + (*(ppBuf))[1] = (Val); \ + \ + *ppBuf += 2; \ + *pLen += 2; \ +} while (0) + +#define MAKE_CMD_EXT_CMD(ppBuf, pLen, Port, ExtCmd, Param) \ +do { \ + (*(ppBuf))[0] = IOSP_BUILD_CMD_HDR1((Port), IOSP_EXT_CMD); \ + (*(ppBuf))[1] = (ExtCmd); \ + (*(ppBuf))[2] = (Param); \ + \ + *ppBuf += 3; \ + *pLen += 3; \ +} while (0) + + + +//-------------------------------------------------------------- +// +// Define format of flow control commands +// (sent from Host to Edgeport) +// +// 11001PPP FlowCmd FlowTypes +// +// Note that the 'FlowTypes' parameter is a bit mask; that is, +// more than one flow control type can be active at the same time. +// FlowTypes = 0 means 'no flow control'. +// + +// +// IOSP_CMD_SET_RX_FLOW +// +// Tells Edgeport how it can stop incoming UART data +// +// Example for Port 0 +// P0 = 11001000 +// P1 = IOSP_CMD_SET_RX_FLOW +// P2 = Bit mask as follows: + +#define IOSP_RX_FLOW_RTS 0x01 // Edgeport drops RTS to stop incoming data +#define IOSP_RX_FLOW_DTR 0x02 // Edgeport drops DTR to stop incoming data +#define IOSP_RX_FLOW_DSR_SENSITIVITY 0x04 // Ignores Rx data unless DSR high + +// Not currently implemented by firmware. +#define IOSP_RX_FLOW_XON_XOFF 0x08 // Edgeport sends XOFF char to stop incoming data. + +// Host must have previously programmed the +// XON/XOFF values with SET_XON/SET_XOFF +// before enabling this bit. + +// +// IOSP_CMD_SET_TX_FLOW +// +// Tells Edgeport what signal(s) will stop it from transmitting UART data +// +// Example for Port 0 +// P0 = 11001000 +// P1 = IOSP_CMD_SET_TX_FLOW +// P2 = Bit mask as follows: + +#define IOSP_TX_FLOW_CTS 0x01 // Edgeport stops Tx if CTS low +#define IOSP_TX_FLOW_DSR 0x02 // Edgeport stops Tx if DSR low +#define IOSP_TX_FLOW_DCD 0x04 // Edgeport stops Tx if DCD low +#define IOSP_TX_FLOW_XON_XOFF 0x08 // Edgeport stops Tx upon receiving XOFF char. + +// Host must have previously programmed the +// XON/XOFF values with SET_XON/SET_XOFF +// before enabling this bit. +#define IOSP_TX_FLOW_XOFF_CONTINUE 0x10 // If not set, Edgeport stops Tx when + +// sending XOFF in order to fix broken +// systems that interpret the next +// received char as XON. +// If set, Edgeport continues Tx +// normally after transmitting XOFF. +// Not currently implemented by firmware. +#define IOSP_TX_TOGGLE_RTS 0x20 // Edgeport drives RTS as a true half-duplex + +// Request-to-Send signal: it is raised before +// beginning transmission and lowered after +// the last Tx char leaves the UART. +// Not currently implemented by firmware. + +// +// IOSP_CMD_SET_XON_CHAR +// +// Sets the character which Edgeport transmits/interprets as XON. +// Note: This command MUST be sent before sending a SET_RX_FLOW or +// SET_TX_FLOW with the XON_XOFF bit set. +// +// Example for Port 0 +// P0 = 11001000 +// P1 = IOSP_CMD_SET_XON_CHAR +// P2 = 0x11 + + +// +// IOSP_CMD_SET_XOFF_CHAR +// +// Sets the character which Edgeport transmits/interprets as XOFF. +// Note: This command must be sent before sending a SET_RX_FLOW or +// SET_TX_FLOW with the XON_XOFF bit set. +// +// Example for Port 0 +// P0 = 11001000 +// P1 = IOSP_CMD_SET_XOFF_CHAR +// P2 = 0x13 + + +// +// IOSP_CMD_RX_CHECK_REQ +// +// This command is used to assist in the implementation of the +// IOCTL_SERIAL_PURGE Windows IOCTL. +// This IOSP command tries to place a marker at the end of the RX +// queue in the Edgeport. If the Edgeport RX queue is full then +// the Check will be discarded. +// It is up to the device driver to timeout waiting for the +// RX_CHECK_RSP. If a RX_CHECK_RSP is received, the driver is +// sure that all data has been received from the edgeport and +// may now purge any internal RX buffers. +// Note tat the sequence numbers may be used to detect lost +// CHECK_REQs. + +// Example for Port 0 +// P0 = 11001000 +// P1 = IOSP_CMD_RX_CHECK_REQ +// P2 = Sequence number + + +// Response will be: +// P1 = IOSP_EXT_RX_CHECK_RSP +// P2 = Request Sequence number + + + +//-------------------------------------------------------------- +// +// Define values for status and status parameters +// (received by Host from Edgeport) +// +// 1ssssPPP P1P1P1P1 [ P2P2P2P2P2 ]... +// +// ssss: 00-07 2-byte status. ssss identifies which UART register +// has changed value, and the new value is in P1. +// Note that the ssss values do not correspond to the +// 16554 register numbers given in 16554.H. Instead, +// see below for definitions of the ssss numbers +// used in this status message. +// +// 08-0B 3-byte status: ==== P1 ==== ==== P2 ==== +// 08 LSR_DATA: New LSR Errored byte +// 09 1-param responses Response Code Param +// 0A OPEN_RSP: InitialMsr TxBufferSize +// 0B available for expansion +// +// 0C-0D 4-byte status. P1 = extended status code and P2,P3 = params +// Not currently implemented. +// +// 0E-0F N-byte status: P1 = num bytes after P1 (ie, TotalLen - 2) +// P2 = extended status, P3..Pn = parameters. +// Not currently implemented. +// + +/**************************************************** + * SSSS values for 2-byte status messages (0-8) + ****************************************************/ + +#define IOSP_STATUS_LSR 0x00 // P1 is new value of LSR register. + +// Bits defined in 16554.H. Edgeport +// returns this in order to report +// line status errors (overrun, +// parity, framing, break). This form +// is used when a errored receive data +// character was NOT present in the +// UART when the LSR error occurred +// (ie, when LSR bit 0 = 0). + +#define IOSP_STATUS_MSR 0x01 // P1 is new value of MSR register. + +// Bits defined in 16554.H. Edgeport +// returns this in order to report +// changes in modem status lines +// (CTS, DSR, RI, CD) +// + +// 0x02 // Available for future expansion +// 0x03 // +// 0x04 // +// 0x05 // +// 0x06 // +// 0x07 // + + +/**************************************************** + * SSSS values for 3-byte status messages (8-A) + ****************************************************/ + +#define IOSP_STATUS_LSR_DATA 0x08 // P1 is new value of LSR register (same as STATUS_LSR) + +// P2 is errored character read from +// RxFIFO after LSR reported an error. + +#define IOSP_EXT_STATUS 0x09 // P1 is status/response code, param in P2. + + +// Response Codes (P1 values) for 3-byte status messages + +#define IOSP_EXT_STATUS_CHASE_RSP 0 // Reply to CHASE_PORT cmd. P2 is outcome: +#define IOSP_EXT_STATUS_CHASE_PASS 0 // P2 = 0: All Tx data drained successfully +#define IOSP_EXT_STATUS_CHASE_FAIL 1 // P2 = 1: Timed out (stuck due to flow + +// control from remote device). + +#define IOSP_EXT_STATUS_RX_CHECK_RSP 1 // Reply to RX_CHECK cmd. P2 is sequence number + + +#define IOSP_STATUS_OPEN_RSP 0x0A // Reply to OPEN_PORT cmd. + +// P1 is Initial MSR value +// P2 is encoded TxBuffer Size: +// TxBufferSize = (P2 + 1) * 64 + +// 0x0B // Available for future expansion + +#define GET_TX_BUFFER_SIZE(P2) (((P2) + 1) * 64) + + + + +/**************************************************** + * SSSS values for 4-byte status messages + ****************************************************/ + +#define IOSP_EXT4_STATUS 0x0C // Extended status code in P1, + +// Params in P2, P3 +// Currently unimplemented. + +// 0x0D // Currently unused, available. + + + +// +// Macros to parse status messages +// + +#define IOSP_GET_STATUS_LEN(code) ((code) < 8 ? 2 : ((code) < 0x0A ? 3 : 4)) + +#define IOSP_STATUS_IS_2BYTE(code) ((code) < 0x08) +#define IOSP_STATUS_IS_3BYTE(code) (((code) >= 0x08) && ((code) <= 0x0B)) +#define IOSP_STATUS_IS_4BYTE(code) (((code) >= 0x0C) && ((code) <= 0x0D)) + diff --git a/drivers/usb/serial/io_tables.h b/drivers/usb/serial/io_tables.h new file mode 100644 index 00000000..ae5fac56 --- /dev/null +++ b/drivers/usb/serial/io_tables.h @@ -0,0 +1,232 @@ +/* + * IO Edgeport Driver tables + * + * Copyright (C) 2001 + * Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#ifndef IO_TABLES_H +#define IO_TABLES_H + +static const struct usb_device_id edgeport_2port_id_table[] = { + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_421) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_21) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2_DIN) }, + { } +}; + +static const struct usb_device_id edgeport_4port_id_table[] = { + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_RAPIDPORT_4) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4T) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_MT4X56USB) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8_DUAL_CPU) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4_DIN) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_22I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_4) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_COMPATIBLE) }, + { } +}; + +static const struct usb_device_id edgeport_8port_id_table[] = { + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8R) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8RR) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_8) }, + { } +}; + +static const struct usb_device_id Epic_port_id_table[] = { + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0202) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0203) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0310) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0311) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0312) }, + { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A758) }, + { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A794) }, + { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A225) }, + { } +}; + +/* Devices that this driver supports */ +static const struct usb_device_id id_table_combined[] = { + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_RAPIDPORT_4) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4T) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_MT4X56USB) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_421) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_21) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8_DUAL_CPU) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_2_DIN) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_4_DIN) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_22I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_4) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_COMPATIBLE) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8R) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8RR) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_8) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0202) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0203) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0310) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0311) }, + { USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0312) }, + { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A758) }, + { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A794) }, + { USB_DEVICE(USB_VENDOR_ID_AXIOHM, AXIOHM_DEVICE_ID_EPIC_A225) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +static struct usb_serial_driver edgeport_2port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "edgeport_2", + }, + .description = "Edgeport 2 port adapter", + .id_table = edgeport_2port_id_table, + .num_ports = 2, + .open = edge_open, + .close = edge_close, + .throttle = edge_throttle, + .unthrottle = edge_unthrottle, + .attach = edge_startup, + .disconnect = edge_disconnect, + .release = edge_release, + .port_probe = edge_port_probe, + .port_remove = edge_port_remove, + .ioctl = edge_ioctl, + .set_termios = edge_set_termios, + .tiocmget = edge_tiocmget, + .tiocmset = edge_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .write = edge_write, + .write_room = edge_write_room, + .chars_in_buffer = edge_chars_in_buffer, + .break_ctl = edge_break, + .read_int_callback = edge_interrupt_callback, + .read_bulk_callback = edge_bulk_in_callback, + .write_bulk_callback = edge_bulk_out_data_callback, +}; + +static struct usb_serial_driver edgeport_4port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "edgeport_4", + }, + .description = "Edgeport 4 port adapter", + .id_table = edgeport_4port_id_table, + .num_ports = 4, + .open = edge_open, + .close = edge_close, + .throttle = edge_throttle, + .unthrottle = edge_unthrottle, + .attach = edge_startup, + .disconnect = edge_disconnect, + .release = edge_release, + .port_probe = edge_port_probe, + .port_remove = edge_port_remove, + .ioctl = edge_ioctl, + .set_termios = edge_set_termios, + .tiocmget = edge_tiocmget, + .tiocmset = edge_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .write = edge_write, + .write_room = edge_write_room, + .chars_in_buffer = edge_chars_in_buffer, + .break_ctl = edge_break, + .read_int_callback = edge_interrupt_callback, + .read_bulk_callback = edge_bulk_in_callback, + .write_bulk_callback = edge_bulk_out_data_callback, +}; + +static struct usb_serial_driver edgeport_8port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "edgeport_8", + }, + .description = "Edgeport 8 port adapter", + .id_table = edgeport_8port_id_table, + .num_ports = 8, + .open = edge_open, + .close = edge_close, + .throttle = edge_throttle, + .unthrottle = edge_unthrottle, + .attach = edge_startup, + .disconnect = edge_disconnect, + .release = edge_release, + .port_probe = edge_port_probe, + .port_remove = edge_port_remove, + .ioctl = edge_ioctl, + .set_termios = edge_set_termios, + .tiocmget = edge_tiocmget, + .tiocmset = edge_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .write = edge_write, + .write_room = edge_write_room, + .chars_in_buffer = edge_chars_in_buffer, + .break_ctl = edge_break, + .read_int_callback = edge_interrupt_callback, + .read_bulk_callback = edge_bulk_in_callback, + .write_bulk_callback = edge_bulk_out_data_callback, +}; + +static struct usb_serial_driver epic_device = { + .driver = { + .owner = THIS_MODULE, + .name = "epic", + }, + .description = "EPiC device", + .id_table = Epic_port_id_table, + .num_ports = 1, + .open = edge_open, + .close = edge_close, + .throttle = edge_throttle, + .unthrottle = edge_unthrottle, + .attach = edge_startup, + .disconnect = edge_disconnect, + .release = edge_release, + .port_probe = edge_port_probe, + .port_remove = edge_port_remove, + .ioctl = edge_ioctl, + .set_termios = edge_set_termios, + .tiocmget = edge_tiocmget, + .tiocmset = edge_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .write = edge_write, + .write_room = edge_write_room, + .chars_in_buffer = edge_chars_in_buffer, + .break_ctl = edge_break, + .read_int_callback = edge_interrupt_callback, + .read_bulk_callback = edge_bulk_in_callback, + .write_bulk_callback = edge_bulk_out_data_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &edgeport_2port_device, &edgeport_4port_device, + &edgeport_8port_device, &epic_device, NULL +}; + +#endif + diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c new file mode 100644 index 00000000..8cd6479a --- /dev/null +++ b/drivers/usb/serial/io_ti.c @@ -0,0 +1,2639 @@ +/* + * Edgeport USB Serial Converter driver + * + * Copyright (C) 2000-2002 Inside Out Networks, All rights reserved. + * Copyright (C) 2001-2002 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Supports the following devices: + * EP/1 EP/2 EP/4 EP/21 EP/22 EP/221 EP/42 EP/421 WATCHPORT + * + * For questions or problems with this driver, contact Inside Out + * Networks technical support, or Peter Berger , + * or Al Borchers . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "io_16654.h" +#include "io_usbvend.h" +#include "io_ti.h" + +#define DRIVER_AUTHOR "Greg Kroah-Hartman and David Iacovelli" +#define DRIVER_DESC "Edgeport USB Serial Driver" + +#define EPROM_PAGE_SIZE 64 + + +/* different hardware types */ +#define HARDWARE_TYPE_930 0 +#define HARDWARE_TYPE_TIUMP 1 + +/* IOCTL_PRIVATE_TI_GET_MODE Definitions */ +#define TI_MODE_CONFIGURING 0 /* Device has not entered start device */ +#define TI_MODE_BOOT 1 /* Staying in boot mode */ +#define TI_MODE_DOWNLOAD 2 /* Made it to download mode */ +#define TI_MODE_TRANSITIONING 3 /* Currently in boot mode but + transitioning to download mode */ + +/* read urb state */ +#define EDGE_READ_URB_RUNNING 0 +#define EDGE_READ_URB_STOPPING 1 +#define EDGE_READ_URB_STOPPED 2 + +#define EDGE_CLOSING_WAIT 4000 /* in .01 sec */ + +#define EDGE_OUT_BUF_SIZE 1024 + + +/* Product information read from the Edgeport */ +struct product_info { + int TiMode; /* Current TI Mode */ + __u8 hardware_type; /* Type of hardware */ +} __attribute__((packed)); + +struct edgeport_port { + __u16 uart_base; + __u16 dma_address; + __u8 shadow_msr; + __u8 shadow_mcr; + __u8 shadow_lsr; + __u8 lsr_mask; + __u32 ump_read_timeout; /* Number of milliseconds the UMP will + wait without data before completing + a read short */ + int baud_rate; + int close_pending; + int lsr_event; + + struct edgeport_serial *edge_serial; + struct usb_serial_port *port; + __u8 bUartMode; /* Port type, 0: RS232, etc. */ + spinlock_t ep_lock; + int ep_read_urb_state; + int ep_write_urb_in_use; + struct kfifo write_fifo; +}; + +struct edgeport_serial { + struct product_info product_info; + u8 TI_I2C_Type; /* Type of I2C in UMP */ + u8 TiReadI2C; /* Set to TRUE if we have read the + I2c in Boot Mode */ + struct mutex es_lock; + int num_ports_open; + struct usb_serial *serial; +}; + + +/* Devices that this driver supports */ +static const struct usb_device_id edgeport_1port_id_table[] = { + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_1) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_TI3410_EDGEPORT_1) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_TI3410_EDGEPORT_1I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_PROXIMITY) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_MOTION) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_MOISTURE) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_TEMPERATURE) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_HUMIDITY) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_POWER) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_LIGHT) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_RADIATION) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_DISTANCE) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_ACCELERATION) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_PROX_DIST) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_PLUS_PWR_HP4CD) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_PLUS_PWR_PCI) }, + { } +}; + +static const struct usb_device_id edgeport_2port_id_table[] = { + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2C) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_421) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_21) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_42) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_22I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_221C) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_22C) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_21C) }, + /* The 4, 8 and 16 port devices show up as multiple 2 port devices */ + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4S) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_8) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_8S) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_416) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_416B) }, + { } +}; + +/* Devices that this driver supports */ +static const struct usb_device_id id_table_combined[] = { + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_1) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_TI3410_EDGEPORT_1) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_TI3410_EDGEPORT_1I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_PROXIMITY) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_MOTION) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_MOISTURE) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_TEMPERATURE) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_HUMIDITY) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_POWER) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_LIGHT) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_RADIATION) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_DISTANCE) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_ACCELERATION) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_WP_PROX_DIST) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_PLUS_PWR_HP4CD) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_PLUS_PWR_PCI) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2C) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_2I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_421) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_21) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_42) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_22I) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_221C) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_22C) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_21C) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_4S) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_8) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_8S) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_416) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_TI_EDGEPORT_416B) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +static unsigned char OperationalMajorVersion; +static unsigned char OperationalMinorVersion; +static unsigned short OperationalBuildNumber; + +static int closing_wait = EDGE_CLOSING_WAIT; +static bool ignore_cpu_rev; +static int default_uart_mode; /* RS232 */ + +static void edge_tty_recv(struct usb_serial_port *port, unsigned char *data, + int length); + +static void stop_read(struct edgeport_port *edge_port); +static int restart_read(struct edgeport_port *edge_port); + +static void edge_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios); +static void edge_send(struct usb_serial_port *port, struct tty_struct *tty); + +/* sysfs attributes */ +static int edge_create_sysfs_attrs(struct usb_serial_port *port); +static int edge_remove_sysfs_attrs(struct usb_serial_port *port); + + +static int ti_vread_sync(struct usb_device *dev, __u8 request, + __u16 value, __u16 index, u8 *data, int size) +{ + int status; + + status = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request, + (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN), + value, index, data, size, 1000); + if (status < 0) + return status; + if (status != size) { + dev_dbg(&dev->dev, "%s - wanted to write %d, but only wrote %d\n", + __func__, size, status); + return -ECOMM; + } + return 0; +} + +static int ti_vsend_sync(struct usb_device *dev, __u8 request, + __u16 value, __u16 index, u8 *data, int size) +{ + int status; + + status = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request, + (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT), + value, index, data, size, 1000); + if (status < 0) + return status; + if (status != size) { + dev_dbg(&dev->dev, "%s - wanted to write %d, but only wrote %d\n", + __func__, size, status); + return -ECOMM; + } + return 0; +} + +static int send_cmd(struct usb_device *dev, __u8 command, + __u8 moduleid, __u16 value, u8 *data, + int size) +{ + return ti_vsend_sync(dev, command, value, moduleid, data, size); +} + +/* clear tx/rx buffers and fifo in TI UMP */ +static int purge_port(struct usb_serial_port *port, __u16 mask) +{ + int port_number = port->number - port->serial->minor; + + dev_dbg(&port->dev, "%s - port %d, mask %x\n", __func__, port_number, mask); + + return send_cmd(port->serial->dev, + UMPC_PURGE_PORT, + (__u8)(UMPM_UART1_PORT + port_number), + mask, + NULL, + 0); +} + +/** + * read_download_mem - Read edgeport memory from TI chip + * @dev: usb device pointer + * @start_address: Device CPU address at which to read + * @length: Length of above data + * @address_type: Can read both XDATA and I2C + * @buffer: pointer to input data buffer + */ +static int read_download_mem(struct usb_device *dev, int start_address, + int length, __u8 address_type, __u8 *buffer) +{ + int status = 0; + __u8 read_length; + u16 be_start_address; + + dev_dbg(&dev->dev, "%s - @ %x for %d\n", __func__, start_address, length); + + /* Read in blocks of 64 bytes + * (TI firmware can't handle more than 64 byte reads) + */ + while (length) { + if (length > 64) + read_length = 64; + else + read_length = (__u8)length; + + if (read_length > 1) { + dev_dbg(&dev->dev, "%s - @ %x for %d\n", __func__, start_address, read_length); + } + /* + * NOTE: Must use swab as wIndex is sent in little-endian + * byte order regardless of host byte order. + */ + be_start_address = swab16((u16)start_address); + status = ti_vread_sync(dev, UMPC_MEMORY_READ, + (__u16)address_type, + be_start_address, + buffer, read_length); + + if (status) { + dev_dbg(&dev->dev, "%s - ERROR %x\n", __func__, status); + return status; + } + + if (read_length > 1) + usb_serial_debug_data(&dev->dev, __func__, read_length, buffer); + + /* Update pointers/length */ + start_address += read_length; + buffer += read_length; + length -= read_length; + } + + return status; +} + +static int read_ram(struct usb_device *dev, int start_address, + int length, __u8 *buffer) +{ + return read_download_mem(dev, start_address, length, + DTK_ADDR_SPACE_XDATA, buffer); +} + +/* Read edgeport memory to a given block */ +static int read_boot_mem(struct edgeport_serial *serial, + int start_address, int length, __u8 *buffer) +{ + int status = 0; + int i; + + for (i = 0; i < length; i++) { + status = ti_vread_sync(serial->serial->dev, + UMPC_MEMORY_READ, serial->TI_I2C_Type, + (__u16)(start_address+i), &buffer[i], 0x01); + if (status) { + dev_dbg(&serial->serial->dev->dev, "%s - ERROR %x\n", __func__, status); + return status; + } + } + + dev_dbg(&serial->serial->dev->dev, "%s - start_address = %x, length = %d\n", + __func__, start_address, length); + usb_serial_debug_data(&serial->serial->dev->dev, __func__, length, buffer); + + serial->TiReadI2C = 1; + + return status; +} + +/* Write given block to TI EPROM memory */ +static int write_boot_mem(struct edgeport_serial *serial, + int start_address, int length, __u8 *buffer) +{ + int status = 0; + int i; + u8 *temp; + + /* Must do a read before write */ + if (!serial->TiReadI2C) { + temp = kmalloc(1, GFP_KERNEL); + if (!temp) { + dev_err(&serial->serial->dev->dev, + "%s - out of memory\n", __func__); + return -ENOMEM; + } + status = read_boot_mem(serial, 0, 1, temp); + kfree(temp); + if (status) + return status; + } + + for (i = 0; i < length; ++i) { + status = ti_vsend_sync(serial->serial->dev, + UMPC_MEMORY_WRITE, buffer[i], + (__u16)(i + start_address), NULL, 0); + if (status) + return status; + } + + dev_dbg(&serial->serial->dev->dev, "%s - start_sddr = %x, length = %d\n", __func__, start_address, length); + usb_serial_debug_data(&serial->serial->dev->dev, __func__, length, buffer); + + return status; +} + + +/* Write edgeport I2C memory to TI chip */ +static int write_i2c_mem(struct edgeport_serial *serial, + int start_address, int length, __u8 address_type, __u8 *buffer) +{ + struct device *dev = &serial->serial->dev->dev; + int status = 0; + int write_length; + u16 be_start_address; + + /* We can only send a maximum of 1 aligned byte page at a time */ + + /* calculate the number of bytes left in the first page */ + write_length = EPROM_PAGE_SIZE - + (start_address & (EPROM_PAGE_SIZE - 1)); + + if (write_length > length) + write_length = length; + + dev_dbg(dev, "%s - BytesInFirstPage Addr = %x, length = %d\n", + __func__, start_address, write_length); + usb_serial_debug_data(dev, __func__, write_length, buffer); + + /* + * Write first page. + * + * NOTE: Must use swab as wIndex is sent in little-endian byte order + * regardless of host byte order. + */ + be_start_address = swab16((u16)start_address); + status = ti_vsend_sync(serial->serial->dev, + UMPC_MEMORY_WRITE, (__u16)address_type, + be_start_address, + buffer, write_length); + if (status) { + dev_dbg(dev, "%s - ERROR %d\n", __func__, status); + return status; + } + + length -= write_length; + start_address += write_length; + buffer += write_length; + + /* We should be aligned now -- can write + max page size bytes at a time */ + while (length) { + if (length > EPROM_PAGE_SIZE) + write_length = EPROM_PAGE_SIZE; + else + write_length = length; + + dev_dbg(dev, "%s - Page Write Addr = %x, length = %d\n", + __func__, start_address, write_length); + usb_serial_debug_data(dev, __func__, write_length, buffer); + + /* + * Write next page. + * + * NOTE: Must use swab as wIndex is sent in little-endian byte + * order regardless of host byte order. + */ + be_start_address = swab16((u16)start_address); + status = ti_vsend_sync(serial->serial->dev, UMPC_MEMORY_WRITE, + (__u16)address_type, + be_start_address, + buffer, write_length); + if (status) { + dev_err(dev, "%s - ERROR %d\n", __func__, status); + return status; + } + + length -= write_length; + start_address += write_length; + buffer += write_length; + } + return status; +} + +/* Examine the UMP DMA registers and LSR + * + * Check the MSBit of the X and Y DMA byte count registers. + * A zero in this bit indicates that the TX DMA buffers are empty + * then check the TX Empty bit in the UART. + */ +static int tx_active(struct edgeport_port *port) +{ + int status; + struct out_endpoint_desc_block *oedb; + __u8 *lsr; + int bytes_left = 0; + + oedb = kmalloc(sizeof(*oedb), GFP_KERNEL); + if (!oedb) { + dev_err(&port->port->dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + + lsr = kmalloc(1, GFP_KERNEL); /* Sigh, that's right, just one byte, + as not all platforms can do DMA + from stack */ + if (!lsr) { + kfree(oedb); + return -ENOMEM; + } + /* Read the DMA Count Registers */ + status = read_ram(port->port->serial->dev, port->dma_address, + sizeof(*oedb), (void *)oedb); + if (status) + goto exit_is_tx_active; + + dev_dbg(&port->port->dev, "%s - XByteCount 0x%X\n", __func__, oedb->XByteCount); + + /* and the LSR */ + status = read_ram(port->port->serial->dev, + port->uart_base + UMPMEM_OFFS_UART_LSR, 1, lsr); + + if (status) + goto exit_is_tx_active; + dev_dbg(&port->port->dev, "%s - LSR = 0x%X\n", __func__, *lsr); + + /* If either buffer has data or we are transmitting then return TRUE */ + if ((oedb->XByteCount & 0x80) != 0) + bytes_left += 64; + + if ((*lsr & UMP_UART_LSR_TX_MASK) == 0) + bytes_left += 1; + + /* We return Not Active if we get any kind of error */ +exit_is_tx_active: + dev_dbg(&port->port->dev, "%s - return %d\n", __func__, bytes_left); + + kfree(lsr); + kfree(oedb); + return bytes_left; +} + +static int choose_config(struct usb_device *dev) +{ + /* + * There may be multiple configurations on this device, in which case + * we would need to read and parse all of them to find out which one + * we want. However, we just support one config at this point, + * configuration # 1, which is Config Descriptor 0. + */ + + dev_dbg(&dev->dev, "%s - Number of Interfaces = %d\n", + __func__, dev->config->desc.bNumInterfaces); + dev_dbg(&dev->dev, "%s - MAX Power = %d\n", + __func__, dev->config->desc.bMaxPower * 2); + + if (dev->config->desc.bNumInterfaces != 1) { + dev_err(&dev->dev, "%s - bNumInterfaces is not 1, ERROR!\n", __func__); + return -ENODEV; + } + + return 0; +} + +static int read_rom(struct edgeport_serial *serial, + int start_address, int length, __u8 *buffer) +{ + int status; + + if (serial->product_info.TiMode == TI_MODE_DOWNLOAD) { + status = read_download_mem(serial->serial->dev, + start_address, + length, + serial->TI_I2C_Type, + buffer); + } else { + status = read_boot_mem(serial, start_address, length, + buffer); + } + return status; +} + +static int write_rom(struct edgeport_serial *serial, int start_address, + int length, __u8 *buffer) +{ + if (serial->product_info.TiMode == TI_MODE_BOOT) + return write_boot_mem(serial, start_address, length, + buffer); + + if (serial->product_info.TiMode == TI_MODE_DOWNLOAD) + return write_i2c_mem(serial, start_address, length, + serial->TI_I2C_Type, buffer); + return -EINVAL; +} + + + +/* Read a descriptor header from I2C based on type */ +static int get_descriptor_addr(struct edgeport_serial *serial, + int desc_type, struct ti_i2c_desc *rom_desc) +{ + int start_address; + int status; + + /* Search for requested descriptor in I2C */ + start_address = 2; + do { + status = read_rom(serial, + start_address, + sizeof(struct ti_i2c_desc), + (__u8 *)rom_desc); + if (status) + return 0; + + if (rom_desc->Type == desc_type) + return start_address; + + start_address = start_address + sizeof(struct ti_i2c_desc) + + le16_to_cpu(rom_desc->Size); + + } while ((start_address < TI_MAX_I2C_SIZE) && rom_desc->Type); + + return 0; +} + +/* Validate descriptor checksum */ +static int valid_csum(struct ti_i2c_desc *rom_desc, __u8 *buffer) +{ + __u16 i; + __u8 cs = 0; + + for (i = 0; i < le16_to_cpu(rom_desc->Size); i++) + cs = (__u8)(cs + buffer[i]); + + if (cs != rom_desc->CheckSum) { + pr_debug("%s - Mismatch %x - %x", __func__, rom_desc->CheckSum, cs); + return -EINVAL; + } + return 0; +} + +/* Make sure that the I2C image is good */ +static int check_i2c_image(struct edgeport_serial *serial) +{ + struct device *dev = &serial->serial->dev->dev; + int status = 0; + struct ti_i2c_desc *rom_desc; + int start_address = 2; + __u8 *buffer; + __u16 ttype; + + rom_desc = kmalloc(sizeof(*rom_desc), GFP_KERNEL); + if (!rom_desc) { + dev_err(dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + buffer = kmalloc(TI_MAX_I2C_SIZE, GFP_KERNEL); + if (!buffer) { + dev_err(dev, "%s - out of memory when allocating buffer\n", + __func__); + kfree(rom_desc); + return -ENOMEM; + } + + /* Read the first byte (Signature0) must be 0x52 or 0x10 */ + status = read_rom(serial, 0, 1, buffer); + if (status) + goto out; + + if (*buffer != UMP5152 && *buffer != UMP3410) { + dev_err(dev, "%s - invalid buffer signature\n", __func__); + status = -ENODEV; + goto out; + } + + do { + /* Validate the I2C */ + status = read_rom(serial, + start_address, + sizeof(struct ti_i2c_desc), + (__u8 *)rom_desc); + if (status) + break; + + if ((start_address + sizeof(struct ti_i2c_desc) + + le16_to_cpu(rom_desc->Size)) > TI_MAX_I2C_SIZE) { + status = -ENODEV; + dev_dbg(dev, "%s - structure too big, erroring out.\n", __func__); + break; + } + + dev_dbg(dev, "%s Type = 0x%x\n", __func__, rom_desc->Type); + + /* Skip type 2 record */ + ttype = rom_desc->Type & 0x0f; + if (ttype != I2C_DESC_TYPE_FIRMWARE_BASIC + && ttype != I2C_DESC_TYPE_FIRMWARE_AUTO) { + /* Read the descriptor data */ + status = read_rom(serial, start_address + + sizeof(struct ti_i2c_desc), + le16_to_cpu(rom_desc->Size), + buffer); + if (status) + break; + + status = valid_csum(rom_desc, buffer); + if (status) + break; + } + start_address = start_address + sizeof(struct ti_i2c_desc) + + le16_to_cpu(rom_desc->Size); + + } while ((rom_desc->Type != I2C_DESC_TYPE_ION) && + (start_address < TI_MAX_I2C_SIZE)); + + if ((rom_desc->Type != I2C_DESC_TYPE_ION) || + (start_address > TI_MAX_I2C_SIZE)) + status = -ENODEV; + +out: + kfree(buffer); + kfree(rom_desc); + return status; +} + +static int get_manuf_info(struct edgeport_serial *serial, __u8 *buffer) +{ + int status; + int start_address; + struct ti_i2c_desc *rom_desc; + struct edge_ti_manuf_descriptor *desc; + struct device *dev = &serial->serial->dev->dev; + + rom_desc = kmalloc(sizeof(*rom_desc), GFP_KERNEL); + if (!rom_desc) { + dev_err(dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + start_address = get_descriptor_addr(serial, I2C_DESC_TYPE_ION, + rom_desc); + + if (!start_address) { + dev_dbg(dev, "%s - Edge Descriptor not found in I2C\n", __func__); + status = -ENODEV; + goto exit; + } + + /* Read the descriptor data */ + status = read_rom(serial, start_address+sizeof(struct ti_i2c_desc), + le16_to_cpu(rom_desc->Size), buffer); + if (status) + goto exit; + + status = valid_csum(rom_desc, buffer); + + desc = (struct edge_ti_manuf_descriptor *)buffer; + dev_dbg(dev, "%s - IonConfig 0x%x\n", __func__, desc->IonConfig); + dev_dbg(dev, "%s - Version %d\n", __func__, desc->Version); + dev_dbg(dev, "%s - Cpu/Board 0x%x\n", __func__, desc->CpuRev_BoardRev); + dev_dbg(dev, "%s - NumPorts %d\n", __func__, desc->NumPorts); + dev_dbg(dev, "%s - NumVirtualPorts %d\n", __func__, desc->NumVirtualPorts); + dev_dbg(dev, "%s - TotalPorts %d\n", __func__, desc->TotalPorts); + +exit: + kfree(rom_desc); + return status; +} + +/* Build firmware header used for firmware update */ +static int build_i2c_fw_hdr(__u8 *header, struct device *dev) +{ + __u8 *buffer; + int buffer_size; + int i; + int err; + __u8 cs = 0; + struct ti_i2c_desc *i2c_header; + struct ti_i2c_image_header *img_header; + struct ti_i2c_firmware_rec *firmware_rec; + const struct firmware *fw; + const char *fw_name = "edgeport/down3.bin"; + + /* In order to update the I2C firmware we must change the type 2 record + * to type 0xF2. This will force the UMP to come up in Boot Mode. + * Then while in boot mode, the driver will download the latest + * firmware (padded to 15.5k) into the UMP ram. And finally when the + * device comes back up in download mode the driver will cause the new + * firmware to be copied from the UMP Ram to I2C and the firmware will + * update the record type from 0xf2 to 0x02. + */ + + /* Allocate a 15.5k buffer + 2 bytes for version number + * (Firmware Record) */ + buffer_size = (((1024 * 16) - 512 ) + + sizeof(struct ti_i2c_firmware_rec)); + + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + dev_err(dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + + // Set entire image of 0xffs + memset(buffer, 0xff, buffer_size); + + err = request_firmware(&fw, fw_name, dev); + if (err) { + dev_err(dev, "Failed to load image \"%s\" err %d\n", + fw_name, err); + kfree(buffer); + return err; + } + + /* Save Download Version Number */ + OperationalMajorVersion = fw->data[0]; + OperationalMinorVersion = fw->data[1]; + OperationalBuildNumber = fw->data[2] | (fw->data[3] << 8); + + /* Copy version number into firmware record */ + firmware_rec = (struct ti_i2c_firmware_rec *)buffer; + + firmware_rec->Ver_Major = OperationalMajorVersion; + firmware_rec->Ver_Minor = OperationalMinorVersion; + + /* Pointer to fw_down memory image */ + img_header = (struct ti_i2c_image_header *)&fw->data[4]; + + memcpy(buffer + sizeof(struct ti_i2c_firmware_rec), + &fw->data[4 + sizeof(struct ti_i2c_image_header)], + le16_to_cpu(img_header->Length)); + + release_firmware(fw); + + for (i=0; i < buffer_size; i++) { + cs = (__u8)(cs + buffer[i]); + } + + kfree(buffer); + + /* Build new header */ + i2c_header = (struct ti_i2c_desc *)header; + firmware_rec = (struct ti_i2c_firmware_rec*)i2c_header->Data; + + i2c_header->Type = I2C_DESC_TYPE_FIRMWARE_BLANK; + i2c_header->Size = cpu_to_le16(buffer_size); + i2c_header->CheckSum = cs; + firmware_rec->Ver_Major = OperationalMajorVersion; + firmware_rec->Ver_Minor = OperationalMinorVersion; + + return 0; +} + +/* Try to figure out what type of I2c we have */ +static int i2c_type_bootmode(struct edgeport_serial *serial) +{ + struct device *dev = &serial->serial->dev->dev; + int status; + u8 *data; + + data = kmalloc(1, GFP_KERNEL); + if (!data) { + dev_err(dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + + /* Try to read type 2 */ + status = ti_vread_sync(serial->serial->dev, UMPC_MEMORY_READ, + DTK_ADDR_SPACE_I2C_TYPE_II, 0, data, 0x01); + if (status) + dev_dbg(dev, "%s - read 2 status error = %d\n", __func__, status); + else + dev_dbg(dev, "%s - read 2 data = 0x%x\n", __func__, *data); + if ((!status) && (*data == UMP5152 || *data == UMP3410)) { + dev_dbg(dev, "%s - ROM_TYPE_II\n", __func__); + serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_II; + goto out; + } + + /* Try to read type 3 */ + status = ti_vread_sync(serial->serial->dev, UMPC_MEMORY_READ, + DTK_ADDR_SPACE_I2C_TYPE_III, 0, data, 0x01); + if (status) + dev_dbg(dev, "%s - read 3 status error = %d\n", __func__, status); + else + dev_dbg(dev, "%s - read 2 data = 0x%x\n", __func__, *data); + if ((!status) && (*data == UMP5152 || *data == UMP3410)) { + dev_dbg(dev, "%s - ROM_TYPE_III\n", __func__); + serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_III; + goto out; + } + + dev_dbg(dev, "%s - Unknown\n", __func__); + serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_II; + status = -ENODEV; +out: + kfree(data); + return status; +} + +static int bulk_xfer(struct usb_serial *serial, void *buffer, + int length, int *num_sent) +{ + int status; + + status = usb_bulk_msg(serial->dev, + usb_sndbulkpipe(serial->dev, + serial->port[0]->bulk_out_endpointAddress), + buffer, length, num_sent, 1000); + return status; +} + +/* Download given firmware image to the device (IN BOOT MODE) */ +static int download_code(struct edgeport_serial *serial, __u8 *image, + int image_length) +{ + int status = 0; + int pos; + int transfer; + int done; + + /* Transfer firmware image */ + for (pos = 0; pos < image_length; ) { + /* Read the next buffer from file */ + transfer = image_length - pos; + if (transfer > EDGE_FW_BULK_MAX_PACKET_SIZE) + transfer = EDGE_FW_BULK_MAX_PACKET_SIZE; + + /* Transfer data */ + status = bulk_xfer(serial->serial, &image[pos], + transfer, &done); + if (status) + break; + /* Advance buffer pointer */ + pos += done; + } + + return status; +} + +/* FIXME!!! */ +static int config_boot_dev(struct usb_device *dev) +{ + return 0; +} + +static int ti_cpu_rev(struct edge_ti_manuf_descriptor *desc) +{ + return TI_GET_CPU_REVISION(desc->CpuRev_BoardRev); +} + +/** + * DownloadTIFirmware - Download run-time operating firmware to the TI5052 + * + * This routine downloads the main operating code into the TI5052, using the + * boot code already burned into E2PROM or ROM. + */ +static int download_fw(struct edgeport_serial *serial) +{ + struct device *dev = &serial->serial->dev->dev; + int status = 0; + int start_address; + struct edge_ti_manuf_descriptor *ti_manuf_desc; + struct usb_interface_descriptor *interface; + int download_cur_ver; + int download_new_ver; + + /* This routine is entered by both the BOOT mode and the Download mode + * We can determine which code is running by the reading the config + * descriptor and if we have only one bulk pipe it is in boot mode + */ + serial->product_info.hardware_type = HARDWARE_TYPE_TIUMP; + + /* Default to type 2 i2c */ + serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_II; + + status = choose_config(serial->serial->dev); + if (status) + return status; + + interface = &serial->serial->interface->cur_altsetting->desc; + if (!interface) { + dev_err(dev, "%s - no interface set, error!\n", __func__); + return -ENODEV; + } + + /* + * Setup initial mode -- the default mode 0 is TI_MODE_CONFIGURING + * if we have more than one endpoint we are definitely in download + * mode + */ + if (interface->bNumEndpoints > 1) + serial->product_info.TiMode = TI_MODE_DOWNLOAD; + else + /* Otherwise we will remain in configuring mode */ + serial->product_info.TiMode = TI_MODE_CONFIGURING; + + /********************************************************************/ + /* Download Mode */ + /********************************************************************/ + if (serial->product_info.TiMode == TI_MODE_DOWNLOAD) { + struct ti_i2c_desc *rom_desc; + + dev_dbg(dev, "%s - RUNNING IN DOWNLOAD MODE\n", __func__); + + status = check_i2c_image(serial); + if (status) { + dev_dbg(dev, "%s - DOWNLOAD MODE -- BAD I2C\n", __func__); + return status; + } + + /* Validate Hardware version number + * Read Manufacturing Descriptor from TI Based Edgeport + */ + ti_manuf_desc = kmalloc(sizeof(*ti_manuf_desc), GFP_KERNEL); + if (!ti_manuf_desc) { + dev_err(dev, "%s - out of memory.\n", __func__); + return -ENOMEM; + } + status = get_manuf_info(serial, (__u8 *)ti_manuf_desc); + if (status) { + kfree(ti_manuf_desc); + return status; + } + + /* Check version number of ION descriptor */ + if (!ignore_cpu_rev && ti_cpu_rev(ti_manuf_desc) < 2) { + dev_dbg(dev, "%s - Wrong CPU Rev %d (Must be 2)\n", + __func__, ti_cpu_rev(ti_manuf_desc)); + kfree(ti_manuf_desc); + return -EINVAL; + } + + rom_desc = kmalloc(sizeof(*rom_desc), GFP_KERNEL); + if (!rom_desc) { + dev_err(dev, "%s - out of memory.\n", __func__); + kfree(ti_manuf_desc); + return -ENOMEM; + } + + /* Search for type 2 record (firmware record) */ + start_address = get_descriptor_addr(serial, + I2C_DESC_TYPE_FIRMWARE_BASIC, rom_desc); + if (start_address != 0) { + struct ti_i2c_firmware_rec *firmware_version; + u8 *record; + + dev_dbg(dev, "%s - Found Type FIRMWARE (Type 2) record\n", __func__); + + firmware_version = kmalloc(sizeof(*firmware_version), + GFP_KERNEL); + if (!firmware_version) { + dev_err(dev, "%s - out of memory.\n", __func__); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -ENOMEM; + } + + /* Validate version number + * Read the descriptor data + */ + status = read_rom(serial, start_address + + sizeof(struct ti_i2c_desc), + sizeof(struct ti_i2c_firmware_rec), + (__u8 *)firmware_version); + if (status) { + kfree(firmware_version); + kfree(rom_desc); + kfree(ti_manuf_desc); + return status; + } + + /* Check version number of download with current + version in I2c */ + download_cur_ver = (firmware_version->Ver_Major << 8) + + (firmware_version->Ver_Minor); + download_new_ver = (OperationalMajorVersion << 8) + + (OperationalMinorVersion); + + dev_dbg(dev, "%s - >> FW Versions Device %d.%d Driver %d.%d\n", + __func__, firmware_version->Ver_Major, + firmware_version->Ver_Minor, + OperationalMajorVersion, + OperationalMinorVersion); + + /* Check if we have an old version in the I2C and + update if necessary */ + if (download_cur_ver < download_new_ver) { + dev_dbg(dev, "%s - Update I2C dld from %d.%d to %d.%d\n", + __func__, + firmware_version->Ver_Major, + firmware_version->Ver_Minor, + OperationalMajorVersion, + OperationalMinorVersion); + + record = kmalloc(1, GFP_KERNEL); + if (!record) { + dev_err(dev, "%s - out of memory.\n", + __func__); + kfree(firmware_version); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -ENOMEM; + } + /* In order to update the I2C firmware we must + * change the type 2 record to type 0xF2. This + * will force the UMP to come up in Boot Mode. + * Then while in boot mode, the driver will + * download the latest firmware (padded to + * 15.5k) into the UMP ram. Finally when the + * device comes back up in download mode the + * driver will cause the new firmware to be + * copied from the UMP Ram to I2C and the + * firmware will update the record type from + * 0xf2 to 0x02. + */ + *record = I2C_DESC_TYPE_FIRMWARE_BLANK; + + /* Change the I2C Firmware record type to + 0xf2 to trigger an update */ + status = write_rom(serial, start_address, + sizeof(*record), record); + if (status) { + kfree(record); + kfree(firmware_version); + kfree(rom_desc); + kfree(ti_manuf_desc); + return status; + } + + /* verify the write -- must do this in order + * for write to complete before we do the + * hardware reset + */ + status = read_rom(serial, + start_address, + sizeof(*record), + record); + if (status) { + kfree(record); + kfree(firmware_version); + kfree(rom_desc); + kfree(ti_manuf_desc); + return status; + } + + if (*record != I2C_DESC_TYPE_FIRMWARE_BLANK) { + dev_err(dev, "%s - error resetting device\n", __func__); + kfree(record); + kfree(firmware_version); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -ENODEV; + } + + dev_dbg(dev, "%s - HARDWARE RESET\n", __func__); + + /* Reset UMP -- Back to BOOT MODE */ + status = ti_vsend_sync(serial->serial->dev, + UMPC_HARDWARE_RESET, + 0, 0, NULL, 0); + + dev_dbg(dev, "%s - HARDWARE RESET return %d\n", __func__, status); + + /* return an error on purpose. */ + kfree(record); + kfree(firmware_version); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -ENODEV; + } + kfree(firmware_version); + } + /* Search for type 0xF2 record (firmware blank record) */ + else if ((start_address = get_descriptor_addr(serial, I2C_DESC_TYPE_FIRMWARE_BLANK, rom_desc)) != 0) { +#define HEADER_SIZE (sizeof(struct ti_i2c_desc) + \ + sizeof(struct ti_i2c_firmware_rec)) + __u8 *header; + __u8 *vheader; + + header = kmalloc(HEADER_SIZE, GFP_KERNEL); + if (!header) { + dev_err(dev, "%s - out of memory.\n", __func__); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -ENOMEM; + } + + vheader = kmalloc(HEADER_SIZE, GFP_KERNEL); + if (!vheader) { + dev_err(dev, "%s - out of memory.\n", __func__); + kfree(header); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -ENOMEM; + } + + dev_dbg(dev, "%s - Found Type BLANK FIRMWARE (Type F2) record\n", __func__); + + /* + * In order to update the I2C firmware we must change + * the type 2 record to type 0xF2. This will force the + * UMP to come up in Boot Mode. Then while in boot + * mode, the driver will download the latest firmware + * (padded to 15.5k) into the UMP ram. Finally when the + * device comes back up in download mode the driver + * will cause the new firmware to be copied from the + * UMP Ram to I2C and the firmware will update the + * record type from 0xf2 to 0x02. + */ + status = build_i2c_fw_hdr(header, dev); + if (status) { + kfree(vheader); + kfree(header); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -EINVAL; + } + + /* Update I2C with type 0xf2 record with correct + size and checksum */ + status = write_rom(serial, + start_address, + HEADER_SIZE, + header); + if (status) { + kfree(vheader); + kfree(header); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -EINVAL; + } + + /* verify the write -- must do this in order for + write to complete before we do the hardware reset */ + status = read_rom(serial, start_address, + HEADER_SIZE, vheader); + + if (status) { + dev_dbg(dev, "%s - can't read header back\n", __func__); + kfree(vheader); + kfree(header); + kfree(rom_desc); + kfree(ti_manuf_desc); + return status; + } + if (memcmp(vheader, header, HEADER_SIZE)) { + dev_dbg(dev, "%s - write download record failed\n", __func__); + kfree(vheader); + kfree(header); + kfree(rom_desc); + kfree(ti_manuf_desc); + return -EINVAL; + } + + kfree(vheader); + kfree(header); + + dev_dbg(dev, "%s - Start firmware update\n", __func__); + + /* Tell firmware to copy download image into I2C */ + status = ti_vsend_sync(serial->serial->dev, + UMPC_COPY_DNLD_TO_I2C, 0, 0, NULL, 0); + + dev_dbg(dev, "%s - Update complete 0x%x\n", __func__, status); + if (status) { + dev_err(dev, + "%s - UMPC_COPY_DNLD_TO_I2C failed\n", + __func__); + kfree(rom_desc); + kfree(ti_manuf_desc); + return status; + } + } + + // The device is running the download code + kfree(rom_desc); + kfree(ti_manuf_desc); + return 0; + } + + /********************************************************************/ + /* Boot Mode */ + /********************************************************************/ + dev_dbg(dev, "%s - RUNNING IN BOOT MODE\n", __func__); + + /* Configure the TI device so we can use the BULK pipes for download */ + status = config_boot_dev(serial->serial->dev); + if (status) + return status; + + if (le16_to_cpu(serial->serial->dev->descriptor.idVendor) + != USB_VENDOR_ID_ION) { + dev_dbg(dev, "%s - VID = 0x%x\n", __func__, + le16_to_cpu(serial->serial->dev->descriptor.idVendor)); + serial->TI_I2C_Type = DTK_ADDR_SPACE_I2C_TYPE_II; + goto stayinbootmode; + } + + /* We have an ION device (I2c Must be programmed) + Determine I2C image type */ + if (i2c_type_bootmode(serial)) + goto stayinbootmode; + + /* Check for ION Vendor ID and that the I2C is valid */ + if (!check_i2c_image(serial)) { + struct ti_i2c_image_header *header; + int i; + __u8 cs = 0; + __u8 *buffer; + int buffer_size; + int err; + const struct firmware *fw; + const char *fw_name = "edgeport/down3.bin"; + + /* Validate Hardware version number + * Read Manufacturing Descriptor from TI Based Edgeport + */ + ti_manuf_desc = kmalloc(sizeof(*ti_manuf_desc), GFP_KERNEL); + if (!ti_manuf_desc) { + dev_err(dev, "%s - out of memory.\n", __func__); + return -ENOMEM; + } + status = get_manuf_info(serial, (__u8 *)ti_manuf_desc); + if (status) { + kfree(ti_manuf_desc); + goto stayinbootmode; + } + + /* Check for version 2 */ + if (!ignore_cpu_rev && ti_cpu_rev(ti_manuf_desc) < 2) { + dev_dbg(dev, "%s - Wrong CPU Rev %d (Must be 2)\n", + __func__, ti_cpu_rev(ti_manuf_desc)); + kfree(ti_manuf_desc); + goto stayinbootmode; + } + + kfree(ti_manuf_desc); + + /* + * In order to update the I2C firmware we must change the type + * 2 record to type 0xF2. This will force the UMP to come up + * in Boot Mode. Then while in boot mode, the driver will + * download the latest firmware (padded to 15.5k) into the + * UMP ram. Finally when the device comes back up in download + * mode the driver will cause the new firmware to be copied + * from the UMP Ram to I2C and the firmware will update the + * record type from 0xf2 to 0x02. + * + * Do we really have to copy the whole firmware image, + * or could we do this in place! + */ + + /* Allocate a 15.5k buffer + 3 byte header */ + buffer_size = (((1024 * 16) - 512) + + sizeof(struct ti_i2c_image_header)); + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + dev_err(dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + + /* Initialize the buffer to 0xff (pad the buffer) */ + memset(buffer, 0xff, buffer_size); + + err = request_firmware(&fw, fw_name, dev); + if (err) { + dev_err(dev, "Failed to load image \"%s\" err %d\n", + fw_name, err); + kfree(buffer); + return err; + } + memcpy(buffer, &fw->data[4], fw->size - 4); + release_firmware(fw); + + for (i = sizeof(struct ti_i2c_image_header); + i < buffer_size; i++) { + cs = (__u8)(cs + buffer[i]); + } + + header = (struct ti_i2c_image_header *)buffer; + + /* update length and checksum after padding */ + header->Length = cpu_to_le16((__u16)(buffer_size - + sizeof(struct ti_i2c_image_header))); + header->CheckSum = cs; + + /* Download the operational code */ + dev_dbg(dev, "%s - Downloading operational code image (TI UMP)\n", __func__); + status = download_code(serial, buffer, buffer_size); + + kfree(buffer); + + if (status) { + dev_dbg(dev, "%s - Error downloading operational code image\n", __func__); + return status; + } + + /* Device will reboot */ + serial->product_info.TiMode = TI_MODE_TRANSITIONING; + + dev_dbg(dev, "%s - Download successful -- Device rebooting...\n", __func__); + + /* return an error on purpose */ + return -ENODEV; + } + +stayinbootmode: + /* Eprom is invalid or blank stay in boot mode */ + dev_dbg(dev, "%s - STAYING IN BOOT MODE\n", __func__); + serial->product_info.TiMode = TI_MODE_BOOT; + + return 0; +} + + +static int ti_do_config(struct edgeport_port *port, int feature, int on) +{ + int port_number = port->port->number - port->port->serial->minor; + on = !!on; /* 1 or 0 not bitmask */ + return send_cmd(port->port->serial->dev, + feature, (__u8)(UMPM_UART1_PORT + port_number), + on, NULL, 0); +} + + +static int restore_mcr(struct edgeport_port *port, __u8 mcr) +{ + int status = 0; + + dev_dbg(&port->port->dev, "%s - %x\n", __func__, mcr); + + status = ti_do_config(port, UMPC_SET_CLR_DTR, mcr & MCR_DTR); + if (status) + return status; + status = ti_do_config(port, UMPC_SET_CLR_RTS, mcr & MCR_RTS); + if (status) + return status; + return ti_do_config(port, UMPC_SET_CLR_LOOPBACK, mcr & MCR_LOOPBACK); +} + +/* Convert TI LSR to standard UART flags */ +static __u8 map_line_status(__u8 ti_lsr) +{ + __u8 lsr = 0; + +#define MAP_FLAG(flagUmp, flagUart) \ + if (ti_lsr & flagUmp) \ + lsr |= flagUart; + + MAP_FLAG(UMP_UART_LSR_OV_MASK, LSR_OVER_ERR) /* overrun */ + MAP_FLAG(UMP_UART_LSR_PE_MASK, LSR_PAR_ERR) /* parity error */ + MAP_FLAG(UMP_UART_LSR_FE_MASK, LSR_FRM_ERR) /* framing error */ + MAP_FLAG(UMP_UART_LSR_BR_MASK, LSR_BREAK) /* break detected */ + MAP_FLAG(UMP_UART_LSR_RX_MASK, LSR_RX_AVAIL) /* rx data available */ + MAP_FLAG(UMP_UART_LSR_TX_MASK, LSR_TX_EMPTY) /* tx hold reg empty */ + +#undef MAP_FLAG + + return lsr; +} + +static void handle_new_msr(struct edgeport_port *edge_port, __u8 msr) +{ + struct async_icount *icount; + struct tty_struct *tty; + + dev_dbg(&edge_port->port->dev, "%s - %02x\n", __func__, msr); + + if (msr & (EDGEPORT_MSR_DELTA_CTS | EDGEPORT_MSR_DELTA_DSR | + EDGEPORT_MSR_DELTA_RI | EDGEPORT_MSR_DELTA_CD)) { + icount = &edge_port->port->icount; + + /* update input line counters */ + if (msr & EDGEPORT_MSR_DELTA_CTS) + icount->cts++; + if (msr & EDGEPORT_MSR_DELTA_DSR) + icount->dsr++; + if (msr & EDGEPORT_MSR_DELTA_CD) + icount->dcd++; + if (msr & EDGEPORT_MSR_DELTA_RI) + icount->rng++; + wake_up_interruptible(&edge_port->port->port.delta_msr_wait); + } + + /* Save the new modem status */ + edge_port->shadow_msr = msr & 0xf0; + + tty = tty_port_tty_get(&edge_port->port->port); + /* handle CTS flow control */ + if (tty && C_CRTSCTS(tty)) { + if (msr & EDGEPORT_MSR_CTS) { + tty->hw_stopped = 0; + tty_wakeup(tty); + } else { + tty->hw_stopped = 1; + } + } + tty_kref_put(tty); +} + +static void handle_new_lsr(struct edgeport_port *edge_port, int lsr_data, + __u8 lsr, __u8 data) +{ + struct async_icount *icount; + __u8 new_lsr = (__u8)(lsr & (__u8)(LSR_OVER_ERR | LSR_PAR_ERR | + LSR_FRM_ERR | LSR_BREAK)); + + dev_dbg(&edge_port->port->dev, "%s - %02x\n", __func__, new_lsr); + + edge_port->shadow_lsr = lsr; + + if (new_lsr & LSR_BREAK) + /* + * Parity and Framing errors only count if they + * occur exclusive of a break being received. + */ + new_lsr &= (__u8)(LSR_OVER_ERR | LSR_BREAK); + + /* Place LSR data byte into Rx buffer */ + if (lsr_data) + edge_tty_recv(edge_port->port, &data, 1); + + /* update input line counters */ + icount = &edge_port->port->icount; + if (new_lsr & LSR_BREAK) + icount->brk++; + if (new_lsr & LSR_OVER_ERR) + icount->overrun++; + if (new_lsr & LSR_PAR_ERR) + icount->parity++; + if (new_lsr & LSR_FRM_ERR) + icount->frame++; +} + + +static void edge_interrupt_callback(struct urb *urb) +{ + struct edgeport_serial *edge_serial = urb->context; + struct usb_serial_port *port; + struct edgeport_port *edge_port; + struct device *dev; + unsigned char *data = urb->transfer_buffer; + int length = urb->actual_length; + int port_number; + int function; + int retval; + __u8 lsr; + __u8 msr; + int status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_err(&urb->dev->dev, "%s - nonzero urb status received: " + "%d\n", __func__, status); + goto exit; + } + + if (!length) { + dev_dbg(&urb->dev->dev, "%s - no data in urb\n", __func__); + goto exit; + } + + dev = &edge_serial->serial->dev->dev; + usb_serial_debug_data(dev, __func__, length, data); + + if (length != 2) { + dev_dbg(dev, "%s - expecting packet of size 2, got %d\n", __func__, length); + goto exit; + } + + port_number = TIUMP_GET_PORT_FROM_CODE(data[0]); + function = TIUMP_GET_FUNC_FROM_CODE(data[0]); + dev_dbg(dev, "%s - port_number %d, function %d, info 0x%x\n", __func__, + port_number, function, data[1]); + port = edge_serial->serial->port[port_number]; + edge_port = usb_get_serial_port_data(port); + if (!edge_port) { + dev_dbg(dev, "%s - edge_port not found\n", __func__); + return; + } + switch (function) { + case TIUMP_INTERRUPT_CODE_LSR: + lsr = map_line_status(data[1]); + if (lsr & UMP_UART_LSR_DATA_MASK) { + /* Save the LSR event for bulk read + completion routine */ + dev_dbg(dev, "%s - LSR Event Port %u LSR Status = %02x\n", + __func__, port_number, lsr); + edge_port->lsr_event = 1; + edge_port->lsr_mask = lsr; + } else { + dev_dbg(dev, "%s - ===== Port %d LSR Status = %02x ======\n", + __func__, port_number, lsr); + handle_new_lsr(edge_port, 0, lsr, 0); + } + break; + + case TIUMP_INTERRUPT_CODE_MSR: /* MSR */ + /* Copy MSR from UMP */ + msr = data[1]; + dev_dbg(dev, "%s - ===== Port %u MSR Status = %02x ======\n", + __func__, port_number, msr); + handle_new_msr(edge_port, msr); + break; + + default: + dev_err(&urb->dev->dev, + "%s - Unknown Interrupt code from UMP %x\n", + __func__, data[1]); + break; + + } + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&urb->dev->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, retval); +} + +static void edge_bulk_in_callback(struct urb *urb) +{ + struct edgeport_port *edge_port = urb->context; + struct device *dev = &edge_port->port->dev; + unsigned char *data = urb->transfer_buffer; + int retval = 0; + int port_number; + int status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", __func__, status); + return; + default: + dev_err(&urb->dev->dev, "%s - nonzero read bulk status received: %d\n", __func__, status); + } + + if (status == -EPIPE) + goto exit; + + if (status) { + dev_err(&urb->dev->dev, "%s - stopping read!\n", __func__); + return; + } + + port_number = edge_port->port->number - edge_port->port->serial->minor; + + if (edge_port->lsr_event) { + edge_port->lsr_event = 0; + dev_dbg(dev, "%s ===== Port %u LSR Status = %02x, Data = %02x ======\n", + __func__, port_number, edge_port->lsr_mask, *data); + handle_new_lsr(edge_port, 1, edge_port->lsr_mask, *data); + /* Adjust buffer length/pointer */ + --urb->actual_length; + ++data; + } + + if (urb->actual_length) { + usb_serial_debug_data(dev, __func__, urb->actual_length, data); + if (edge_port->close_pending) + dev_dbg(dev, "%s - close pending, dropping data on the floor\n", + __func__); + else + edge_tty_recv(edge_port->port, data, + urb->actual_length); + edge_port->port->icount.rx += urb->actual_length; + } + +exit: + /* continue read unless stopped */ + spin_lock(&edge_port->ep_lock); + if (edge_port->ep_read_urb_state == EDGE_READ_URB_RUNNING) + retval = usb_submit_urb(urb, GFP_ATOMIC); + else if (edge_port->ep_read_urb_state == EDGE_READ_URB_STOPPING) + edge_port->ep_read_urb_state = EDGE_READ_URB_STOPPED; + + spin_unlock(&edge_port->ep_lock); + if (retval) + dev_err(dev, "%s - usb_submit_urb failed with result %d\n", __func__, retval); +} + +static void edge_tty_recv(struct usb_serial_port *port, unsigned char *data, + int length) +{ + int queued; + + queued = tty_insert_flip_string(&port->port, data, length); + if (queued < length) + dev_err(&port->dev, "%s - dropping data, %d bytes lost\n", + __func__, length - queued); + tty_flip_buffer_push(&port->port); +} + +static void edge_bulk_out_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int status = urb->status; + struct tty_struct *tty; + + edge_port->ep_write_urb_in_use = 0; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_err_console(port, "%s - nonzero write bulk status " + "received: %d\n", __func__, status); + } + + /* send any buffered data */ + tty = tty_port_tty_get(&port->port); + edge_send(port, tty); + tty_kref_put(tty); +} + +static int edge_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + struct edgeport_serial *edge_serial; + struct usb_device *dev; + struct urb *urb; + int port_number; + int status; + u16 open_settings; + u8 transaction_timeout; + + if (edge_port == NULL) + return -ENODEV; + + port_number = port->number - port->serial->minor; + switch (port_number) { + case 0: + edge_port->uart_base = UMPMEM_BASE_UART1; + edge_port->dma_address = UMPD_OEDB1_ADDRESS; + break; + case 1: + edge_port->uart_base = UMPMEM_BASE_UART2; + edge_port->dma_address = UMPD_OEDB2_ADDRESS; + break; + default: + dev_err(&port->dev, "Unknown port number!!!\n"); + return -ENODEV; + } + + dev_dbg(&port->dev, "%s - port_number = %d, uart_base = %04x, dma_address = %04x\n", + __func__, port_number, edge_port->uart_base, edge_port->dma_address); + + dev = port->serial->dev; + + /* turn off loopback */ + status = ti_do_config(edge_port, UMPC_SET_CLR_LOOPBACK, 0); + if (status) { + dev_err(&port->dev, + "%s - cannot send clear loopback command, %d\n", + __func__, status); + return status; + } + + /* set up the port settings */ + if (tty) + edge_set_termios(tty, port, &tty->termios); + + /* open up the port */ + + /* milliseconds to timeout for DMA transfer */ + transaction_timeout = 2; + + edge_port->ump_read_timeout = + max(20, ((transaction_timeout * 3) / 2)); + + /* milliseconds to timeout for DMA transfer */ + open_settings = (u8)(UMP_DMA_MODE_CONTINOUS | + UMP_PIPE_TRANS_TIMEOUT_ENA | + (transaction_timeout << 2)); + + dev_dbg(&port->dev, "%s - Sending UMPC_OPEN_PORT\n", __func__); + + /* Tell TI to open and start the port */ + status = send_cmd(dev, UMPC_OPEN_PORT, + (u8)(UMPM_UART1_PORT + port_number), open_settings, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send open command, %d\n", + __func__, status); + return status; + } + + /* Start the DMA? */ + status = send_cmd(dev, UMPC_START_PORT, + (u8)(UMPM_UART1_PORT + port_number), 0, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send start DMA command, %d\n", + __func__, status); + return status; + } + + /* Clear TX and RX buffers in UMP */ + status = purge_port(port, UMP_PORT_DIR_OUT | UMP_PORT_DIR_IN); + if (status) { + dev_err(&port->dev, + "%s - cannot send clear buffers command, %d\n", + __func__, status); + return status; + } + + /* Read Initial MSR */ + status = ti_vread_sync(dev, UMPC_READ_MSR, 0, + (__u16)(UMPM_UART1_PORT + port_number), + &edge_port->shadow_msr, 1); + if (status) { + dev_err(&port->dev, "%s - cannot send read MSR command, %d\n", + __func__, status); + return status; + } + + dev_dbg(&port->dev, "ShadowMSR 0x%X\n", edge_port->shadow_msr); + + /* Set Initial MCR */ + edge_port->shadow_mcr = MCR_RTS | MCR_DTR; + dev_dbg(&port->dev, "ShadowMCR 0x%X\n", edge_port->shadow_mcr); + + edge_serial = edge_port->edge_serial; + if (mutex_lock_interruptible(&edge_serial->es_lock)) + return -ERESTARTSYS; + if (edge_serial->num_ports_open == 0) { + /* we are the first port to open, post the interrupt urb */ + urb = edge_serial->serial->port[0]->interrupt_in_urb; + if (!urb) { + dev_err(&port->dev, + "%s - no interrupt urb present, exiting\n", + __func__); + status = -EINVAL; + goto release_es_lock; + } + urb->context = edge_serial; + status = usb_submit_urb(urb, GFP_KERNEL); + if (status) { + dev_err(&port->dev, + "%s - usb_submit_urb failed with value %d\n", + __func__, status); + goto release_es_lock; + } + } + + /* + * reset the data toggle on the bulk endpoints to work around bug in + * host controllers where things get out of sync some times + */ + usb_clear_halt(dev, port->write_urb->pipe); + usb_clear_halt(dev, port->read_urb->pipe); + + /* start up our bulk read urb */ + urb = port->read_urb; + if (!urb) { + dev_err(&port->dev, "%s - no read urb present, exiting\n", + __func__); + status = -EINVAL; + goto unlink_int_urb; + } + edge_port->ep_read_urb_state = EDGE_READ_URB_RUNNING; + urb->context = edge_port; + status = usb_submit_urb(urb, GFP_KERNEL); + if (status) { + dev_err(&port->dev, + "%s - read bulk usb_submit_urb failed with value %d\n", + __func__, status); + goto unlink_int_urb; + } + + ++edge_serial->num_ports_open; + + port->port.drain_delay = 1; + + goto release_es_lock; + +unlink_int_urb: + if (edge_port->edge_serial->num_ports_open == 0) + usb_kill_urb(port->serial->port[0]->interrupt_in_urb); +release_es_lock: + mutex_unlock(&edge_serial->es_lock); + return status; +} + +static void edge_close(struct usb_serial_port *port) +{ + struct edgeport_serial *edge_serial; + struct edgeport_port *edge_port; + struct usb_serial *serial = port->serial; + unsigned long flags; + int port_number; + + edge_serial = usb_get_serial_data(port->serial); + edge_port = usb_get_serial_port_data(port); + if (edge_serial == NULL || edge_port == NULL) + return; + + /* The bulkreadcompletion routine will check + * this flag and dump add read data */ + edge_port->close_pending = 1; + + usb_kill_urb(port->read_urb); + usb_kill_urb(port->write_urb); + edge_port->ep_write_urb_in_use = 0; + spin_lock_irqsave(&edge_port->ep_lock, flags); + kfifo_reset_out(&edge_port->write_fifo); + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + dev_dbg(&port->dev, "%s - send umpc_close_port\n", __func__); + port_number = port->number - port->serial->minor; + send_cmd(serial->dev, UMPC_CLOSE_PORT, + (__u8)(UMPM_UART1_PORT + port_number), 0, NULL, 0); + + mutex_lock(&edge_serial->es_lock); + --edge_port->edge_serial->num_ports_open; + if (edge_port->edge_serial->num_ports_open <= 0) { + /* last port is now closed, let's shut down our interrupt urb */ + usb_kill_urb(port->serial->port[0]->interrupt_in_urb); + edge_port->edge_serial->num_ports_open = 0; + } + mutex_unlock(&edge_serial->es_lock); + edge_port->close_pending = 0; +} + +static int edge_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *data, int count) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + + if (count == 0) { + dev_dbg(&port->dev, "%s - write request of 0 bytes\n", __func__); + return 0; + } + + if (edge_port == NULL) + return -ENODEV; + if (edge_port->close_pending == 1) + return -ENODEV; + + count = kfifo_in_locked(&edge_port->write_fifo, data, count, + &edge_port->ep_lock); + edge_send(port, tty); + + return count; +} + +static void edge_send(struct usb_serial_port *port, struct tty_struct *tty) +{ + int count, result; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + + if (edge_port->ep_write_urb_in_use) { + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + return; + } + + count = kfifo_out(&edge_port->write_fifo, + port->write_urb->transfer_buffer, + port->bulk_out_size); + + if (count == 0) { + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + return; + } + + edge_port->ep_write_urb_in_use = 1; + + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + usb_serial_debug_data(&port->dev, __func__, count, port->write_urb->transfer_buffer); + + /* set up our urb */ + port->write_urb->transfer_buffer_length = count; + + /* send the data out the bulk port */ + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (result) { + dev_err_console(port, + "%s - failed submitting write urb, error %d\n", + __func__, result); + edge_port->ep_write_urb_in_use = 0; + /* TODO: reschedule edge_send */ + } else + edge_port->port->icount.tx += count; + + /* wakeup any process waiting for writes to complete */ + /* there is now more room in the buffer for new writes */ + if (tty) + tty_wakeup(tty); +} + +static int edge_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int room = 0; + unsigned long flags; + + if (edge_port == NULL) + return 0; + if (edge_port->close_pending == 1) + return 0; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + room = kfifo_avail(&edge_port->write_fifo); + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, room); + return room; +} + +static int edge_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int chars = 0; + unsigned long flags; + if (edge_port == NULL) + return 0; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + chars = kfifo_len(&edge_port->write_fifo); + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, chars); + return chars; +} + +static bool edge_tx_empty(struct usb_serial_port *port) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int ret; + + ret = tx_active(edge_port); + if (ret > 0) + return false; + + return true; +} + +static void edge_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int status; + + if (edge_port == NULL) + return; + + /* if we are implementing XON/XOFF, send the stop character */ + if (I_IXOFF(tty)) { + unsigned char stop_char = STOP_CHAR(tty); + status = edge_write(tty, port, &stop_char, 1); + if (status <= 0) { + dev_err(&port->dev, "%s - failed to write stop character, %d\n", __func__, status); + } + } + + /* if we are implementing RTS/CTS, stop reads */ + /* and the Edgeport will clear the RTS line */ + if (C_CRTSCTS(tty)) + stop_read(edge_port); + +} + +static void edge_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int status; + + if (edge_port == NULL) + return; + + /* if we are implementing XON/XOFF, send the start character */ + if (I_IXOFF(tty)) { + unsigned char start_char = START_CHAR(tty); + status = edge_write(tty, port, &start_char, 1); + if (status <= 0) { + dev_err(&port->dev, "%s - failed to write start character, %d\n", __func__, status); + } + } + /* if we are implementing RTS/CTS, restart reads */ + /* are the Edgeport will assert the RTS line */ + if (C_CRTSCTS(tty)) { + status = restart_read(edge_port); + if (status) + dev_err(&port->dev, + "%s - read bulk usb_submit_urb failed: %d\n", + __func__, status); + } + +} + +static void stop_read(struct edgeport_port *edge_port) +{ + unsigned long flags; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + + if (edge_port->ep_read_urb_state == EDGE_READ_URB_RUNNING) + edge_port->ep_read_urb_state = EDGE_READ_URB_STOPPING; + edge_port->shadow_mcr &= ~MCR_RTS; + + spin_unlock_irqrestore(&edge_port->ep_lock, flags); +} + +static int restart_read(struct edgeport_port *edge_port) +{ + struct urb *urb; + int status = 0; + unsigned long flags; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + + if (edge_port->ep_read_urb_state == EDGE_READ_URB_STOPPED) { + urb = edge_port->port->read_urb; + status = usb_submit_urb(urb, GFP_ATOMIC); + } + edge_port->ep_read_urb_state = EDGE_READ_URB_RUNNING; + edge_port->shadow_mcr |= MCR_RTS; + + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + return status; +} + +static void change_port_settings(struct tty_struct *tty, + struct edgeport_port *edge_port, struct ktermios *old_termios) +{ + struct device *dev = &edge_port->port->dev; + struct ump_uart_config *config; + int baud; + unsigned cflag; + int status; + int port_number = edge_port->port->number - + edge_port->port->serial->minor; + + dev_dbg(dev, "%s - port %d\n", __func__, edge_port->port->number); + + config = kmalloc (sizeof (*config), GFP_KERNEL); + if (!config) { + tty->termios = *old_termios; + dev_err(dev, "%s - out of memory\n", __func__); + return; + } + + cflag = tty->termios.c_cflag; + + config->wFlags = 0; + + /* These flags must be set */ + config->wFlags |= UMP_MASK_UART_FLAGS_RECEIVE_MS_INT; + config->wFlags |= UMP_MASK_UART_FLAGS_AUTO_START_ON_ERR; + config->bUartMode = (__u8)(edge_port->bUartMode); + + switch (cflag & CSIZE) { + case CS5: + config->bDataBits = UMP_UART_CHAR5BITS; + dev_dbg(dev, "%s - data bits = 5\n", __func__); + break; + case CS6: + config->bDataBits = UMP_UART_CHAR6BITS; + dev_dbg(dev, "%s - data bits = 6\n", __func__); + break; + case CS7: + config->bDataBits = UMP_UART_CHAR7BITS; + dev_dbg(dev, "%s - data bits = 7\n", __func__); + break; + default: + case CS8: + config->bDataBits = UMP_UART_CHAR8BITS; + dev_dbg(dev, "%s - data bits = 8\n", __func__); + break; + } + + if (cflag & PARENB) { + if (cflag & PARODD) { + config->wFlags |= UMP_MASK_UART_FLAGS_PARITY; + config->bParity = UMP_UART_ODDPARITY; + dev_dbg(dev, "%s - parity = odd\n", __func__); + } else { + config->wFlags |= UMP_MASK_UART_FLAGS_PARITY; + config->bParity = UMP_UART_EVENPARITY; + dev_dbg(dev, "%s - parity = even\n", __func__); + } + } else { + config->bParity = UMP_UART_NOPARITY; + dev_dbg(dev, "%s - parity = none\n", __func__); + } + + if (cflag & CSTOPB) { + config->bStopBits = UMP_UART_STOPBIT2; + dev_dbg(dev, "%s - stop bits = 2\n", __func__); + } else { + config->bStopBits = UMP_UART_STOPBIT1; + dev_dbg(dev, "%s - stop bits = 1\n", __func__); + } + + /* figure out the flow control settings */ + if (cflag & CRTSCTS) { + config->wFlags |= UMP_MASK_UART_FLAGS_OUT_X_CTS_FLOW; + config->wFlags |= UMP_MASK_UART_FLAGS_RTS_FLOW; + dev_dbg(dev, "%s - RTS/CTS is enabled\n", __func__); + } else { + dev_dbg(dev, "%s - RTS/CTS is disabled\n", __func__); + tty->hw_stopped = 0; + restart_read(edge_port); + } + + /* if we are implementing XON/XOFF, set the start and stop + character in the device */ + config->cXon = START_CHAR(tty); + config->cXoff = STOP_CHAR(tty); + + /* if we are implementing INBOUND XON/XOFF */ + if (I_IXOFF(tty)) { + config->wFlags |= UMP_MASK_UART_FLAGS_IN_X; + dev_dbg(dev, "%s - INBOUND XON/XOFF is enabled, XON = %2x, XOFF = %2x\n", + __func__, config->cXon, config->cXoff); + } else + dev_dbg(dev, "%s - INBOUND XON/XOFF is disabled\n", __func__); + + /* if we are implementing OUTBOUND XON/XOFF */ + if (I_IXON(tty)) { + config->wFlags |= UMP_MASK_UART_FLAGS_OUT_X; + dev_dbg(dev, "%s - OUTBOUND XON/XOFF is enabled, XON = %2x, XOFF = %2x\n", + __func__, config->cXon, config->cXoff); + } else + dev_dbg(dev, "%s - OUTBOUND XON/XOFF is disabled\n", __func__); + + tty->termios.c_cflag &= ~CMSPAR; + + /* Round the baud rate */ + baud = tty_get_baud_rate(tty); + if (!baud) { + /* pick a default, any default... */ + baud = 9600; + } else + tty_encode_baud_rate(tty, baud, baud); + + edge_port->baud_rate = baud; + config->wBaudRate = (__u16)((461550L + baud/2) / baud); + + /* FIXME: Recompute actual baud from divisor here */ + + dev_dbg(dev, "%s - baud rate = %d, wBaudRate = %d\n", __func__, baud, config->wBaudRate); + + dev_dbg(dev, "wBaudRate: %d\n", (int)(461550L / config->wBaudRate)); + dev_dbg(dev, "wFlags: 0x%x\n", config->wFlags); + dev_dbg(dev, "bDataBits: %d\n", config->bDataBits); + dev_dbg(dev, "bParity: %d\n", config->bParity); + dev_dbg(dev, "bStopBits: %d\n", config->bStopBits); + dev_dbg(dev, "cXon: %d\n", config->cXon); + dev_dbg(dev, "cXoff: %d\n", config->cXoff); + dev_dbg(dev, "bUartMode: %d\n", config->bUartMode); + + /* move the word values into big endian mode */ + cpu_to_be16s(&config->wFlags); + cpu_to_be16s(&config->wBaudRate); + + status = send_cmd(edge_port->port->serial->dev, UMPC_SET_CONFIG, + (__u8)(UMPM_UART1_PORT + port_number), + 0, (__u8 *)config, sizeof(*config)); + if (status) + dev_dbg(dev, "%s - error %d when trying to write config to device\n", + __func__, status); + kfree(config); +} + +static void edge_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + unsigned int cflag; + + cflag = tty->termios.c_cflag; + + dev_dbg(&port->dev, "%s - clfag %08x iflag %08x\n", __func__, + tty->termios.c_cflag, tty->termios.c_iflag); + dev_dbg(&port->dev, "%s - old clfag %08x old iflag %08x\n", __func__, + old_termios->c_cflag, old_termios->c_iflag); + dev_dbg(&port->dev, "%s - port %d\n", __func__, port->number); + + if (edge_port == NULL) + return; + /* change the port settings to the new ones specified */ + change_port_settings(tty, edge_port, old_termios); +} + +static int edge_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + unsigned int mcr; + unsigned long flags; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + mcr = edge_port->shadow_mcr; + if (set & TIOCM_RTS) + mcr |= MCR_RTS; + if (set & TIOCM_DTR) + mcr |= MCR_DTR; + if (set & TIOCM_LOOP) + mcr |= MCR_LOOPBACK; + + if (clear & TIOCM_RTS) + mcr &= ~MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~MCR_DTR; + if (clear & TIOCM_LOOP) + mcr &= ~MCR_LOOPBACK; + + edge_port->shadow_mcr = mcr; + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + restore_mcr(edge_port, mcr); + return 0; +} + +static int edge_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + unsigned int result = 0; + unsigned int msr; + unsigned int mcr; + unsigned long flags; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + + msr = edge_port->shadow_msr; + mcr = edge_port->shadow_mcr; + result = ((mcr & MCR_DTR) ? TIOCM_DTR: 0) /* 0x002 */ + | ((mcr & MCR_RTS) ? TIOCM_RTS: 0) /* 0x004 */ + | ((msr & EDGEPORT_MSR_CTS) ? TIOCM_CTS: 0) /* 0x020 */ + | ((msr & EDGEPORT_MSR_CD) ? TIOCM_CAR: 0) /* 0x040 */ + | ((msr & EDGEPORT_MSR_RI) ? TIOCM_RI: 0) /* 0x080 */ + | ((msr & EDGEPORT_MSR_DSR) ? TIOCM_DSR: 0); /* 0x100 */ + + + dev_dbg(&port->dev, "%s -- %x\n", __func__, result); + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + return result; +} + +static int get_serial_info(struct edgeport_port *edge_port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + unsigned cwait; + + if (!retinfo) + return -EFAULT; + + cwait = edge_port->port->port.closing_wait; + if (cwait != ASYNC_CLOSING_WAIT_NONE) + cwait = jiffies_to_msecs(cwait) / 10; + + memset(&tmp, 0, sizeof(tmp)); + + tmp.type = PORT_16550A; + tmp.line = edge_port->port->serial->minor; + tmp.port = edge_port->port->number; + tmp.irq = 0; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = edge_port->port->bulk_out_size; + tmp.baud_base = 9600; + tmp.close_delay = 5*HZ; + tmp.closing_wait = cwait; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int edge_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + + dev_dbg(&port->dev, "%s - port %d, cmd = 0x%x\n", __func__, port->number, cmd); + + switch (cmd) { + case TIOCGSERIAL: + dev_dbg(&port->dev, "%s - TIOCGSERIAL\n", __func__); + return get_serial_info(edge_port, + (struct serial_struct __user *) arg); + } + return -ENOIOCTLCMD; +} + +static void edge_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + int status; + int bv = 0; /* Off */ + + if (break_state == -1) + bv = 1; /* On */ + status = ti_do_config(edge_port, UMPC_SET_CLR_BREAK, bv); + if (status) + dev_dbg(&port->dev, "%s - error %d sending break set/clear command.\n", + __func__, status); +} + +static int edge_startup(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial; + int status; + + /* create our private serial structure */ + edge_serial = kzalloc(sizeof(struct edgeport_serial), GFP_KERNEL); + if (edge_serial == NULL) { + dev_err(&serial->dev->dev, "%s - Out of memory\n", __func__); + return -ENOMEM; + } + mutex_init(&edge_serial->es_lock); + edge_serial->serial = serial; + usb_set_serial_data(serial, edge_serial); + + status = download_fw(edge_serial); + if (status) { + kfree(edge_serial); + return status; + } + + return 0; +} + +static void edge_disconnect(struct usb_serial *serial) +{ +} + +static void edge_release(struct usb_serial *serial) +{ + kfree(usb_get_serial_data(serial)); +} + +static int edge_port_probe(struct usb_serial_port *port) +{ + struct edgeport_port *edge_port; + int ret; + + edge_port = kzalloc(sizeof(*edge_port), GFP_KERNEL); + if (!edge_port) + return -ENOMEM; + + ret = kfifo_alloc(&edge_port->write_fifo, EDGE_OUT_BUF_SIZE, + GFP_KERNEL); + if (ret) { + kfree(edge_port); + return -ENOMEM; + } + + spin_lock_init(&edge_port->ep_lock); + edge_port->port = port; + edge_port->edge_serial = usb_get_serial_data(port->serial); + edge_port->bUartMode = default_uart_mode; + + usb_set_serial_port_data(port, edge_port); + + ret = edge_create_sysfs_attrs(port); + if (ret) { + kfifo_free(&edge_port->write_fifo); + kfree(edge_port); + return ret; + } + + port->port.closing_wait = msecs_to_jiffies(closing_wait * 10); + + return 0; +} + +static int edge_port_remove(struct usb_serial_port *port) +{ + struct edgeport_port *edge_port; + + edge_port = usb_get_serial_port_data(port); + edge_remove_sysfs_attrs(port); + kfifo_free(&edge_port->write_fifo); + kfree(edge_port); + + return 0; +} + +/* Sysfs Attributes */ + +static ssize_t show_uart_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + + return sprintf(buf, "%d\n", edge_port->bUartMode); +} + +static ssize_t store_uart_mode(struct device *dev, + struct device_attribute *attr, const char *valbuf, size_t count) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + unsigned int v = simple_strtoul(valbuf, NULL, 0); + + dev_dbg(dev, "%s: setting uart_mode = %d\n", __func__, v); + + if (v < 256) + edge_port->bUartMode = v; + else + dev_err(dev, "%s - uart_mode %d is invalid\n", __func__, v); + + return count; +} + +static DEVICE_ATTR(uart_mode, S_IWUSR | S_IRUGO, show_uart_mode, + store_uart_mode); + +static int edge_create_sysfs_attrs(struct usb_serial_port *port) +{ + return device_create_file(&port->dev, &dev_attr_uart_mode); +} + +static int edge_remove_sysfs_attrs(struct usb_serial_port *port) +{ + device_remove_file(&port->dev, &dev_attr_uart_mode); + return 0; +} + + +static struct usb_serial_driver edgeport_1port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "edgeport_ti_1", + }, + .description = "Edgeport TI 1 port adapter", + .id_table = edgeport_1port_id_table, + .num_ports = 1, + .open = edge_open, + .close = edge_close, + .throttle = edge_throttle, + .unthrottle = edge_unthrottle, + .attach = edge_startup, + .disconnect = edge_disconnect, + .release = edge_release, + .port_probe = edge_port_probe, + .port_remove = edge_port_remove, + .ioctl = edge_ioctl, + .set_termios = edge_set_termios, + .tiocmget = edge_tiocmget, + .tiocmset = edge_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .write = edge_write, + .write_room = edge_write_room, + .chars_in_buffer = edge_chars_in_buffer, + .tx_empty = edge_tx_empty, + .break_ctl = edge_break, + .read_int_callback = edge_interrupt_callback, + .read_bulk_callback = edge_bulk_in_callback, + .write_bulk_callback = edge_bulk_out_callback, +}; + +static struct usb_serial_driver edgeport_2port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "edgeport_ti_2", + }, + .description = "Edgeport TI 2 port adapter", + .id_table = edgeport_2port_id_table, + .num_ports = 2, + .open = edge_open, + .close = edge_close, + .throttle = edge_throttle, + .unthrottle = edge_unthrottle, + .attach = edge_startup, + .disconnect = edge_disconnect, + .release = edge_release, + .port_probe = edge_port_probe, + .port_remove = edge_port_remove, + .ioctl = edge_ioctl, + .set_termios = edge_set_termios, + .tiocmget = edge_tiocmget, + .tiocmset = edge_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .write = edge_write, + .write_room = edge_write_room, + .chars_in_buffer = edge_chars_in_buffer, + .tx_empty = edge_tx_empty, + .break_ctl = edge_break, + .read_int_callback = edge_interrupt_callback, + .read_bulk_callback = edge_bulk_in_callback, + .write_bulk_callback = edge_bulk_out_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &edgeport_1port_device, &edgeport_2port_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table_combined); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("edgeport/down3.bin"); + +module_param(closing_wait, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(closing_wait, "Maximum wait for data to drain, in .01 secs"); + +module_param(ignore_cpu_rev, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ignore_cpu_rev, + "Ignore the cpu revision when connecting to a device"); + +module_param(default_uart_mode, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(default_uart_mode, "Default uart_mode, 0=RS232, ..."); diff --git a/drivers/usb/serial/io_ti.h b/drivers/usb/serial/io_ti.h new file mode 100644 index 00000000..1bd67b24 --- /dev/null +++ b/drivers/usb/serial/io_ti.h @@ -0,0 +1,186 @@ +/***************************************************************************** + * + * Copyright (C) 1997-2002 Inside Out Networks, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * + * Feb-16-2001 DMI Added I2C structure definitions + * May-29-2002 gkh Ported to Linux + * + * + ******************************************************************************/ + +#ifndef _IO_TI_H_ +#define _IO_TI_H_ + +/* Address Space */ +#define DTK_ADDR_SPACE_XDATA 0x03 /* Addr is placed in XDATA space */ +#define DTK_ADDR_SPACE_I2C_TYPE_II 0x82 /* Addr is placed in I2C area */ +#define DTK_ADDR_SPACE_I2C_TYPE_III 0x83 /* Addr is placed in I2C area */ + +/* UART Defines */ +#define UMPMEM_BASE_UART1 0xFFA0 /* UMP UART1 base address */ +#define UMPMEM_BASE_UART2 0xFFB0 /* UMP UART2 base address */ +#define UMPMEM_OFFS_UART_LSR 0x05 /* UMP UART LSR register offset */ + +/* Bits per character */ +#define UMP_UART_CHAR5BITS 0x00 +#define UMP_UART_CHAR6BITS 0x01 +#define UMP_UART_CHAR7BITS 0x02 +#define UMP_UART_CHAR8BITS 0x03 + +/* Parity */ +#define UMP_UART_NOPARITY 0x00 +#define UMP_UART_ODDPARITY 0x01 +#define UMP_UART_EVENPARITY 0x02 +#define UMP_UART_MARKPARITY 0x03 +#define UMP_UART_SPACEPARITY 0x04 + +/* Stop bits */ +#define UMP_UART_STOPBIT1 0x00 +#define UMP_UART_STOPBIT15 0x01 +#define UMP_UART_STOPBIT2 0x02 + +/* Line status register masks */ +#define UMP_UART_LSR_OV_MASK 0x01 +#define UMP_UART_LSR_PE_MASK 0x02 +#define UMP_UART_LSR_FE_MASK 0x04 +#define UMP_UART_LSR_BR_MASK 0x08 +#define UMP_UART_LSR_ER_MASK 0x0F +#define UMP_UART_LSR_RX_MASK 0x10 +#define UMP_UART_LSR_TX_MASK 0x20 + +#define UMP_UART_LSR_DATA_MASK (LSR_PAR_ERR | LSR_FRM_ERR | LSR_BREAK) + +/* Port Settings Constants) */ +#define UMP_MASK_UART_FLAGS_RTS_FLOW 0x0001 +#define UMP_MASK_UART_FLAGS_RTS_DISABLE 0x0002 +#define UMP_MASK_UART_FLAGS_PARITY 0x0008 +#define UMP_MASK_UART_FLAGS_OUT_X_DSR_FLOW 0x0010 +#define UMP_MASK_UART_FLAGS_OUT_X_CTS_FLOW 0x0020 +#define UMP_MASK_UART_FLAGS_OUT_X 0x0040 +#define UMP_MASK_UART_FLAGS_OUT_XA 0x0080 +#define UMP_MASK_UART_FLAGS_IN_X 0x0100 +#define UMP_MASK_UART_FLAGS_DTR_FLOW 0x0800 +#define UMP_MASK_UART_FLAGS_DTR_DISABLE 0x1000 +#define UMP_MASK_UART_FLAGS_RECEIVE_MS_INT 0x2000 +#define UMP_MASK_UART_FLAGS_AUTO_START_ON_ERR 0x4000 + +#define UMP_DMA_MODE_CONTINOUS 0x01 +#define UMP_PIPE_TRANS_TIMEOUT_ENA 0x80 +#define UMP_PIPE_TRANSFER_MODE_MASK 0x03 +#define UMP_PIPE_TRANS_TIMEOUT_MASK 0x7C + +/* Purge port Direction Mask Bits */ +#define UMP_PORT_DIR_OUT 0x01 +#define UMP_PORT_DIR_IN 0x02 + +/* Address of Port 0 */ +#define UMPM_UART1_PORT 0x03 + +/* Commands */ +#define UMPC_SET_CONFIG 0x05 +#define UMPC_OPEN_PORT 0x06 +#define UMPC_CLOSE_PORT 0x07 +#define UMPC_START_PORT 0x08 +#define UMPC_STOP_PORT 0x09 +#define UMPC_TEST_PORT 0x0A +#define UMPC_PURGE_PORT 0x0B + +/* Force the Firmware to complete the current Read */ +#define UMPC_COMPLETE_READ 0x80 +/* Force UMP back into BOOT Mode */ +#define UMPC_HARDWARE_RESET 0x81 +/* + * Copy current download image to type 0xf2 record in 16k I2C + * firmware will change 0xff record to type 2 record when complete + */ +#define UMPC_COPY_DNLD_TO_I2C 0x82 + +/* + * Special function register commands + * wIndex is register address + * wValue is MSB/LSB mask/data + */ +#define UMPC_WRITE_SFR 0x83 /* Write SFR Register */ + +/* wIndex is register address */ +#define UMPC_READ_SFR 0x84 /* Read SRF Register */ + +/* Set or Clear DTR (wValue bit 0 Set/Clear) wIndex ModuleID (port) */ +#define UMPC_SET_CLR_DTR 0x85 + +/* Set or Clear RTS (wValue bit 0 Set/Clear) wIndex ModuleID (port) */ +#define UMPC_SET_CLR_RTS 0x86 + +/* Set or Clear LOOPBACK (wValue bit 0 Set/Clear) wIndex ModuleID (port) */ +#define UMPC_SET_CLR_LOOPBACK 0x87 + +/* Set or Clear BREAK (wValue bit 0 Set/Clear) wIndex ModuleID (port) */ +#define UMPC_SET_CLR_BREAK 0x88 + +/* Read MSR wIndex ModuleID (port) */ +#define UMPC_READ_MSR 0x89 + +/* Toolkit commands */ +/* Read-write group */ +#define UMPC_MEMORY_READ 0x92 +#define UMPC_MEMORY_WRITE 0x93 + +/* + * UMP DMA Definitions + */ +#define UMPD_OEDB1_ADDRESS 0xFF08 +#define UMPD_OEDB2_ADDRESS 0xFF10 + +struct out_endpoint_desc_block { + __u8 Configuration; + __u8 XBufAddr; + __u8 XByteCount; + __u8 Unused1; + __u8 Unused2; + __u8 YBufAddr; + __u8 YByteCount; + __u8 BufferSize; +} __attribute__((packed)); + + +/* + * TYPE DEFINITIONS + * Structures for Firmware commands + */ +/* UART settings */ +struct ump_uart_config { + __u16 wBaudRate; /* Baud rate */ + __u16 wFlags; /* Bitmap mask of flags */ + __u8 bDataBits; /* 5..8 - data bits per character */ + __u8 bParity; /* Parity settings */ + __u8 bStopBits; /* Stop bits settings */ + char cXon; /* XON character */ + char cXoff; /* XOFF character */ + __u8 bUartMode; /* Will be updated when a user */ + /* interface is defined */ +} __attribute__((packed)); + + +/* + * TYPE DEFINITIONS + * Structures for USB interrupts + */ +/* Interrupt packet structure */ +struct ump_interrupt { + __u8 bICode; /* Interrupt code (interrupt num) */ + __u8 bIInfo; /* Interrupt information */ +} __attribute__((packed)); + + +#define TIUMP_GET_PORT_FROM_CODE(c) (((c) >> 4) - 3) +#define TIUMP_GET_FUNC_FROM_CODE(c) ((c) & 0x0f) +#define TIUMP_INTERRUPT_CODE_LSR 0x03 +#define TIUMP_INTERRUPT_CODE_MSR 0x04 + +#endif diff --git a/drivers/usb/serial/io_usbvend.h b/drivers/usb/serial/io_usbvend.h new file mode 100644 index 00000000..6f6a856b --- /dev/null +++ b/drivers/usb/serial/io_usbvend.h @@ -0,0 +1,683 @@ +/************************************************************************ + * + * USBVEND.H Vendor-specific USB definitions + * + * NOTE: This must be kept in sync with the Edgeport firmware and + * must be kept backward-compatible with older firmware. + * + ************************************************************************ + * + * Copyright (C) 1998 Inside Out Networks, Inc. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ************************************************************************/ + +#if !defined(_USBVEND_H) +#define _USBVEND_H + +/************************************************************************ + * + * D e f i n e s / T y p e d e f s + * + ************************************************************************/ + +// +// Definitions of USB product IDs +// + +#define USB_VENDOR_ID_ION 0x1608 // Our VID +#define USB_VENDOR_ID_TI 0x0451 // TI VID +#define USB_VENDOR_ID_AXIOHM 0x05D9 /* Axiohm VID */ + +// +// Definitions of USB product IDs (PID) +// We break the USB-defined PID into an OEM Id field (upper 6 bits) +// and a Device Id (bottom 10 bits). The Device Id defines what +// device this actually is regardless of what the OEM wants to +// call it. +// + +// ION-device OEM IDs +#define ION_OEM_ID_ION 0 // 00h Inside Out Networks +#define ION_OEM_ID_NLYNX 1 // 01h NLynx Systems +#define ION_OEM_ID_GENERIC 2 // 02h Generic OEM +#define ION_OEM_ID_MAC 3 // 03h Mac Version +#define ION_OEM_ID_MEGAWOLF 4 // 04h Lupusb OEM Mac version (MegaWolf) +#define ION_OEM_ID_MULTITECH 5 // 05h Multitech Rapidports +#define ION_OEM_ID_AGILENT 6 // 06h AGILENT board + + +// ION-device Device IDs +// Product IDs - assigned to match middle digit of serial number (No longer true) + +#define ION_DEVICE_ID_80251_NETCHIP 0x020 // This bit is set in the PID if this edgeport hardware$ + // is based on the 80251+Netchip. + +#define ION_DEVICE_ID_GENERATION_1 0x00 // Value for 930 based edgeports +#define ION_DEVICE_ID_GENERATION_2 0x01 // Value for 80251+Netchip. +#define ION_DEVICE_ID_GENERATION_3 0x02 // Value for Texas Instruments TUSB5052 chip +#define ION_DEVICE_ID_GENERATION_4 0x03 // Watchport Family of products +#define ION_GENERATION_MASK 0x03 + +#define ION_DEVICE_ID_HUB_MASK 0x0080 // This bit in the PID designates a HUB device + // for example 8C would be a 421 4 port hub + // and 8D would be a 2 port embedded hub + +#define EDGEPORT_DEVICE_ID_MASK 0x0ff // Not including OEM or GENERATION fields + +#define ION_DEVICE_ID_UNCONFIGURED_EDGE_DEVICE 0x000 // In manufacturing only +#define ION_DEVICE_ID_EDGEPORT_4 0x001 // Edgeport/4 RS232 +#define ION_DEVICE_ID_EDGEPORT_8R 0x002 // Edgeport with RJ45 no Ring +#define ION_DEVICE_ID_RAPIDPORT_4 0x003 // Rapidport/4 +#define ION_DEVICE_ID_EDGEPORT_4T 0x004 // Edgeport/4 RS232 for Telxon (aka "Fleetport") +#define ION_DEVICE_ID_EDGEPORT_2 0x005 // Edgeport/2 RS232 +#define ION_DEVICE_ID_EDGEPORT_4I 0x006 // Edgeport/4 RS422 +#define ION_DEVICE_ID_EDGEPORT_2I 0x007 // Edgeport/2 RS422/RS485 +#define ION_DEVICE_ID_EDGEPORT_8RR 0x008 // Edgeport with RJ45 with Data and RTS/CTS only +// ION_DEVICE_ID_EDGEPORT_8_HANDBUILT 0x009 // Hand-built Edgeport/8 (Placeholder, used in middle digit of serial number only!) +// ION_DEVICE_ID_MULTIMODEM_4X56 0x00A // MultiTech version of RP/4 (Placeholder, used in middle digit of serial number only!) +#define ION_DEVICE_ID_EDGEPORT_PARALLEL_PORT 0x00B // Edgeport/(4)21 Parallel port (USS720) +#define ION_DEVICE_ID_EDGEPORT_421 0x00C // Edgeport/421 Hub+RS232+Parallel +#define ION_DEVICE_ID_EDGEPORT_21 0x00D // Edgeport/21 RS232+Parallel +#define ION_DEVICE_ID_EDGEPORT_8_DUAL_CPU 0x00E // Half of an Edgeport/8 (the kind with 2 EP/4s on 1 PCB) +#define ION_DEVICE_ID_EDGEPORT_8 0x00F // Edgeport/8 (single-CPU) +#define ION_DEVICE_ID_EDGEPORT_2_DIN 0x010 // Edgeport/2 RS232 with Apple DIN connector +#define ION_DEVICE_ID_EDGEPORT_4_DIN 0x011 // Edgeport/4 RS232 with Apple DIN connector +#define ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU 0x012 // Half of an Edgeport/16 (the kind with 2 EP/8s) +#define ION_DEVICE_ID_EDGEPORT_COMPATIBLE 0x013 // Edgeport Compatible, for NCR, Axiohm etc. testing +#define ION_DEVICE_ID_EDGEPORT_8I 0x014 // Edgeport/8 RS422 (single-CPU) +#define ION_DEVICE_ID_EDGEPORT_1 0x015 // Edgeport/1 RS232 +#define ION_DEVICE_ID_EPOS44 0x016 // Half of an EPOS/44 (TIUMP BASED) +#define ION_DEVICE_ID_EDGEPORT_42 0x017 // Edgeport/42 +#define ION_DEVICE_ID_EDGEPORT_412_8 0x018 // Edgeport/412 8 port part +#define ION_DEVICE_ID_EDGEPORT_412_4 0x019 // Edgeport/412 4 port part +#define ION_DEVICE_ID_EDGEPORT_22I 0x01A // Edgeport/22I is an Edgeport/4 with ports 1&2 RS422 and ports 3&4 RS232 + +// Compact Form factor TI based devices 2c, 21c, 22c, 221c +#define ION_DEVICE_ID_EDGEPORT_2C 0x01B // Edgeport/2c is a TI based Edgeport/2 - Small I2c +#define ION_DEVICE_ID_EDGEPORT_221C 0x01C // Edgeport/221c is a TI based Edgeport/2 with lucent chip and + // 2 external hub ports - Large I2C +#define ION_DEVICE_ID_EDGEPORT_22C 0x01D // Edgeport/22c is a TI based Edgeport/2 with + // 2 external hub ports - Large I2C +#define ION_DEVICE_ID_EDGEPORT_21C 0x01E // Edgeport/21c is a TI based Edgeport/2 with lucent chip + // Small I2C + + +/* + * DANGER DANGER The 0x20 bit was used to indicate a 8251/netchip GEN 2 device. + * Since the MAC, Linux, and Optimal drivers still used the old code + * I suggest that you skip the 0x20 bit when creating new PIDs + */ + + +// Generation 3 devices -- 3410 based edgport/1 (256 byte I2C) +#define ION_DEVICE_ID_TI3410_EDGEPORT_1 0x040 // Edgeport/1 RS232 +#define ION_DEVICE_ID_TI3410_EDGEPORT_1I 0x041 // Edgeport/1i- RS422 model + +// Ti based software switchable RS232/RS422/RS485 devices +#define ION_DEVICE_ID_EDGEPORT_4S 0x042 // Edgeport/4s - software switchable model +#define ION_DEVICE_ID_EDGEPORT_8S 0x043 // Edgeport/8s - software switchable model + +// Usb to Ethernet dongle +#define ION_DEVICE_ID_EDGEPORT_E 0x0E0 // Edgeport/E Usb to Ethernet + +// Edgeport TI based devices +#define ION_DEVICE_ID_TI_EDGEPORT_4 0x0201 // Edgeport/4 RS232 +#define ION_DEVICE_ID_TI_EDGEPORT_2 0x0205 // Edgeport/2 RS232 +#define ION_DEVICE_ID_TI_EDGEPORT_4I 0x0206 // Edgeport/4i RS422 +#define ION_DEVICE_ID_TI_EDGEPORT_2I 0x0207 // Edgeport/2i RS422/RS485 +#define ION_DEVICE_ID_TI_EDGEPORT_421 0x020C // Edgeport/421 4 hub 2 RS232 + Parallel (lucent on a different hub port) +#define ION_DEVICE_ID_TI_EDGEPORT_21 0x020D // Edgeport/21 2 RS232 + Parallel (lucent on a different hub port) +#define ION_DEVICE_ID_TI_EDGEPORT_416 0x0212 // Edgeport/416 +#define ION_DEVICE_ID_TI_EDGEPORT_1 0x0215 // Edgeport/1 RS232 +#define ION_DEVICE_ID_TI_EDGEPORT_42 0x0217 // Edgeport/42 4 hub 2 RS232 +#define ION_DEVICE_ID_TI_EDGEPORT_22I 0x021A // Edgeport/22I is an Edgeport/4 with ports 1&2 RS422 and ports 3&4 RS232 +#define ION_DEVICE_ID_TI_EDGEPORT_2C 0x021B // Edgeport/2c RS232 +#define ION_DEVICE_ID_TI_EDGEPORT_221C 0x021C // Edgeport/221c is a TI based Edgeport/2 with lucent chip and + // 2 external hub ports - Large I2C +#define ION_DEVICE_ID_TI_EDGEPORT_22C 0x021D // Edgeport/22c is a TI based Edgeport/2 with + // 2 external hub ports - Large I2C +#define ION_DEVICE_ID_TI_EDGEPORT_21C 0x021E // Edgeport/21c is a TI based Edgeport/2 with lucent chip + +// Generation 3 devices -- 3410 based edgport/1 (256 byte I2C) +#define ION_DEVICE_ID_TI_TI3410_EDGEPORT_1 0x0240 // Edgeport/1 RS232 +#define ION_DEVICE_ID_TI_TI3410_EDGEPORT_1I 0x0241 // Edgeport/1i- RS422 model + +// Ti based software switchable RS232/RS422/RS485 devices +#define ION_DEVICE_ID_TI_EDGEPORT_4S 0x0242 // Edgeport/4s - software switchable model +#define ION_DEVICE_ID_TI_EDGEPORT_8S 0x0243 // Edgeport/8s - software switchable model +#define ION_DEVICE_ID_TI_EDGEPORT_8 0x0244 // Edgeport/8 (single-CPU) +#define ION_DEVICE_ID_TI_EDGEPORT_416B 0x0247 // Edgeport/416 + + +/************************************************************************ + * + * Generation 4 devices + * + ************************************************************************/ + +// Watchport based on 3410 both 1-wire and binary products (16K I2C) +#define ION_DEVICE_ID_WP_UNSERIALIZED 0x300 // Watchport based on 3410 both 1-wire and binary products +#define ION_DEVICE_ID_WP_PROXIMITY 0x301 // Watchport/P Discontinued +#define ION_DEVICE_ID_WP_MOTION 0x302 // Watchport/M +#define ION_DEVICE_ID_WP_MOISTURE 0x303 // Watchport/W +#define ION_DEVICE_ID_WP_TEMPERATURE 0x304 // Watchport/T +#define ION_DEVICE_ID_WP_HUMIDITY 0x305 // Watchport/H + +#define ION_DEVICE_ID_WP_POWER 0x306 // Watchport +#define ION_DEVICE_ID_WP_LIGHT 0x307 // Watchport +#define ION_DEVICE_ID_WP_RADIATION 0x308 // Watchport +#define ION_DEVICE_ID_WP_ACCELERATION 0x309 // Watchport/A +#define ION_DEVICE_ID_WP_DISTANCE 0x30A // Watchport/D Discontinued +#define ION_DEVICE_ID_WP_PROX_DIST 0x30B // Watchport/D uses distance sensor + // Default to /P function + +#define ION_DEVICE_ID_PLUS_PWR_HP4CD 0x30C // 5052 Plus Power HubPort/4CD+ (for Dell) +#define ION_DEVICE_ID_PLUS_PWR_HP4C 0x30D // 5052 Plus Power HubPort/4C+ +#define ION_DEVICE_ID_PLUS_PWR_PCI 0x30E // 3410 Plus Power PCI Host Controller 4 port + + +// +// Definitions for AXIOHM USB product IDs +// +#define USB_VENDOR_ID_AXIOHM 0x05D9 // Axiohm VID + +#define AXIOHM_DEVICE_ID_MASK 0xffff +#define AXIOHM_DEVICE_ID_EPIC_A758 0xA758 +#define AXIOHM_DEVICE_ID_EPIC_A794 0xA794 +#define AXIOHM_DEVICE_ID_EPIC_A225 0xA225 + + +// +// Definitions for NCR USB product IDs +// +#define USB_VENDOR_ID_NCR 0x0404 // NCR VID + +#define NCR_DEVICE_ID_MASK 0xffff +#define NCR_DEVICE_ID_EPIC_0202 0x0202 +#define NCR_DEVICE_ID_EPIC_0203 0x0203 +#define NCR_DEVICE_ID_EPIC_0310 0x0310 +#define NCR_DEVICE_ID_EPIC_0311 0x0311 +#define NCR_DEVICE_ID_EPIC_0312 0x0312 + + +// +// Definitions for SYMBOL USB product IDs +// +#define USB_VENDOR_ID_SYMBOL 0x05E0 // Symbol VID +#define SYMBOL_DEVICE_ID_MASK 0xffff +#define SYMBOL_DEVICE_ID_KEYFOB 0x0700 + + +// +// Definitions for other product IDs +#define ION_DEVICE_ID_MT4X56USB 0x1403 // OEM device + + +#define GENERATION_ID_FROM_USB_PRODUCT_ID(ProductId) \ + ((__u16) ((ProductId >> 8) & (ION_GENERATION_MASK))) + +#define MAKE_USB_PRODUCT_ID(OemId, DeviceId) \ + ((__u16) (((OemId) << 10) || (DeviceId))) + +#define DEVICE_ID_FROM_USB_PRODUCT_ID(ProductId) \ + ((__u16) ((ProductId) & (EDGEPORT_DEVICE_ID_MASK))) + +#define OEM_ID_FROM_USB_PRODUCT_ID(ProductId) \ + ((__u16) (((ProductId) >> 10) & 0x3F)) + +// +// Definitions of parameters for download code. Note that these are +// specific to a given version of download code and must change if the +// corresponding download code changes. +// + +// TxCredits value below which driver won't bother sending (to prevent too many small writes). +// Send only if above 25% +#define EDGE_FW_GET_TX_CREDITS_SEND_THRESHOLD(InitialCredit, MaxPacketSize) (max(((InitialCredit) / 4), (MaxPacketSize))) + +#define EDGE_FW_BULK_MAX_PACKET_SIZE 64 // Max Packet Size for Bulk In Endpoint (EP1) +#define EDGE_FW_BULK_READ_BUFFER_SIZE 1024 // Size to use for Bulk reads + +#define EDGE_FW_INT_MAX_PACKET_SIZE 32 // Max Packet Size for Interrupt In Endpoint + // Note that many units were shipped with MPS=16, we + // force an upgrade to this value). +#define EDGE_FW_INT_INTERVAL 2 // 2ms polling on IntPipe + + +// +// Definitions of I/O Networks vendor-specific requests +// for default endpoint +// +// bmRequestType = 01000000 Set vendor-specific, to device +// bmRequestType = 11000000 Get vendor-specific, to device +// +// These are the definitions for the bRequest field for the +// above bmRequestTypes. +// +// For the read/write Edgeport memory commands, the parameters +// are as follows: +// wValue = 16-bit address +// wIndex = unused (though we could put segment 00: or FF: here) +// wLength = # bytes to read/write (max 64) +// + +#define USB_REQUEST_ION_RESET_DEVICE 0 // Warm reboot Edgeport, retaining USB address +#define USB_REQUEST_ION_GET_EPIC_DESC 1 // Get Edgeport Compatibility Descriptor +// unused 2 // Unused, available +#define USB_REQUEST_ION_READ_RAM 3 // Read EdgePort RAM at specified addr +#define USB_REQUEST_ION_WRITE_RAM 4 // Write EdgePort RAM at specified addr +#define USB_REQUEST_ION_READ_ROM 5 // Read EdgePort ROM at specified addr +#define USB_REQUEST_ION_WRITE_ROM 6 // Write EdgePort ROM at specified addr +#define USB_REQUEST_ION_EXEC_DL_CODE 7 // Begin execution of RAM-based download + // code by jumping to address in wIndex:wValue +// 8 // Unused, available +#define USB_REQUEST_ION_ENABLE_SUSPEND 9 // Enable/Disable suspend feature + // (wValue != 0: Enable; wValue = 0: Disable) + +#define USB_REQUEST_ION_SEND_IOSP 10 // Send an IOSP command to the edgeport over the control pipe +#define USB_REQUEST_ION_RECV_IOSP 11 // Receive an IOSP command from the edgeport over the control pipe + + +#define USB_REQUEST_ION_DIS_INT_TIMER 0x80 // Sent to Axiohm to enable/ disable + // interrupt token timer + // wValue = 1, enable (default) + // wValue = 0, disable + +// +// Define parameter values for our vendor-specific commands +// + +// +// Edgeport Compatibility Descriptor +// +// This descriptor is only returned by Edgeport-compatible devices +// supporting the EPiC spec. True ION devices do not return this +// descriptor, but instead return STALL on receipt of the +// GET_EPIC_DESC command. The driver interprets a STALL to mean that +// this is a "real" Edgeport. +// + +struct edge_compatibility_bits { + // This __u32 defines which Vendor-specific commands/functionality + // the device supports on the default EP0 pipe. + + __u32 VendEnableSuspend : 1; // 0001 Set if device supports ION_ENABLE_SUSPEND + __u32 VendUnused : 31; // Available for future expansion, must be 0 + + // This __u32 defines which IOSP commands are supported over the + // bulk pipe EP1. + + // xxxx Set if device supports: + __u32 IOSPOpen : 1; // 0001 OPEN / OPEN_RSP (Currently must be 1) + __u32 IOSPClose : 1; // 0002 CLOSE + __u32 IOSPChase : 1; // 0004 CHASE / CHASE_RSP + __u32 IOSPSetRxFlow : 1; // 0008 SET_RX_FLOW + __u32 IOSPSetTxFlow : 1; // 0010 SET_TX_FLOW + __u32 IOSPSetXChar : 1; // 0020 SET_XON_CHAR/SET_XOFF_CHAR + __u32 IOSPRxCheck : 1; // 0040 RX_CHECK_REQ/RX_CHECK_RSP + __u32 IOSPSetClrBreak : 1; // 0080 SET_BREAK/CLEAR_BREAK + __u32 IOSPWriteMCR : 1; // 0100 MCR register writes (set/clr DTR/RTS) + __u32 IOSPWriteLCR : 1; // 0200 LCR register writes (wordlen/stop/parity) + __u32 IOSPSetBaudRate : 1; // 0400 setting Baud rate (writes to LCR.80h and DLL/DLM register) + __u32 IOSPDisableIntPipe : 1; // 0800 Do not use the interrupt pipe for TxCredits or RxButesAvailable + __u32 IOSPRxDataAvail : 1; // 1000 Return status of RX Fifo (Data available in Fifo) + __u32 IOSPTxPurge : 1; // 2000 Purge TXBuffer and/or Fifo in Edgeport hardware + __u32 IOSPUnused : 18; // Available for future expansion, must be 0 + + // This __u32 defines which 'general' features are supported + + __u32 TrueEdgeport : 1; // 0001 Set if device is a 'real' Edgeport + // (Used only by driver, NEVER set by an EPiC device) + __u32 GenUnused : 31; // Available for future expansion, must be 0 +}; + +#define EDGE_COMPATIBILITY_MASK0 0x0001 +#define EDGE_COMPATIBILITY_MASK1 0x3FFF +#define EDGE_COMPATIBILITY_MASK2 0x0001 + +struct edge_compatibility_descriptor { + __u8 Length; // Descriptor Length (per USB spec) + __u8 DescType; // Descriptor Type (per USB spec, =DEVICE type) + __u8 EpicVer; // Version of EPiC spec supported + // (Currently must be 1) + __u8 NumPorts; // Number of serial ports supported + __u8 iDownloadFile; // Index of string containing download code filename + // 0=no download, FF=download compiled into driver. + __u8 Unused[3]; // Available for future expansion, must be 0 + // (Currently must be 0). + __u8 MajorVersion; // Firmware version: xx. + __u8 MinorVersion; // yy. + __le16 BuildNumber; // zzzz (LE format) + + // The following structure contains __u32s, with each bit + // specifying whether the EPiC device supports the given + // command or functionality. + struct edge_compatibility_bits Supports; +}; + +// Values for iDownloadFile +#define EDGE_DOWNLOAD_FILE_NONE 0 // No download requested +#define EDGE_DOWNLOAD_FILE_INTERNAL 0xFF // Download the file compiled into driver (930 version) +#define EDGE_DOWNLOAD_FILE_I930 0xFF // Download the file compiled into driver (930 version) +#define EDGE_DOWNLOAD_FILE_80251 0xFE // Download the file compiled into driver (80251 version) + + + +/* + * Special addresses for READ/WRITE_RAM/ROM + */ + +// Version 1 (original) format of DeviceParams +#define EDGE_MANUF_DESC_ADDR_V1 0x00FF7F00 +#define EDGE_MANUF_DESC_LEN_V1 sizeof(EDGE_MANUF_DESCRIPTOR_V1) + +// Version 2 format of DeviceParams. This format is longer (3C0h) +// and starts lower in memory, at the uppermost 1K in ROM. +#define EDGE_MANUF_DESC_ADDR 0x00FF7C00 +#define EDGE_MANUF_DESC_LEN sizeof(struct edge_manuf_descriptor) + +// Boot params descriptor +#define EDGE_BOOT_DESC_ADDR 0x00FF7FC0 +#define EDGE_BOOT_DESC_LEN sizeof(struct edge_boot_descriptor) + +// Define the max block size that may be read or written +// in a read/write RAM/ROM command. +#define MAX_SIZE_REQ_ION_READ_MEM ((__u16)64) +#define MAX_SIZE_REQ_ION_WRITE_MEM ((__u16)64) + + +// +// Notes for the following two ION vendor-specific param descriptors: +// +// 1. These have a standard USB descriptor header so they look like a +// normal descriptor. +// 2. Any strings in the structures are in USB-defined string +// descriptor format, so that they may be separately retrieved, +// if necessary, with a minimum of work on the 930. This also +// requires them to be in UNICODE format, which, for English at +// least, simply means extending each __u8 into a __u16. +// 3. For all fields, 00 means 'uninitialized'. +// 4. All unused areas should be set to 00 for future expansion. +// + +// This structure is ver 2 format. It contains ALL USB descriptors as +// well as the configuration parameters that were in the original V1 +// structure. It is NOT modified when new boot code is downloaded; rather, +// these values are set or modified by manufacturing. It is located at +// xC00-xFBF (length 3C0h) in the ROM. +// This structure is a superset of the v1 structure and is arranged so +// that all of the v1 fields remain at the same address. We are just +// adding more room to the front of the structure to hold the descriptors. +// +// The actual contents of this structure are defined in a 930 assembly +// file, converted to a binary image, and then written by the serialization +// program. The C definition of this structure just defines a dummy +// area for general USB descriptors and the descriptor tables (the root +// descriptor starts at xC00). At the bottom of the structure are the +// fields inherited from the v1 structure. + +#define MAX_SERIALNUMBER_LEN 12 +#define MAX_ASSEMBLYNUMBER_LEN 14 + +struct edge_manuf_descriptor { + + __u16 RootDescTable[0x10]; // C00 Root of descriptor tables (just a placeholder) + __u8 DescriptorArea[0x2E0]; // C20 Descriptors go here, up to 2E0h (just a placeholder) + + // Start of v1-compatible section + __u8 Length; // F00 Desc length for what follows, per USB (= C0h ) + __u8 DescType; // F01 Desc type, per USB (=DEVICE type) + __u8 DescVer; // F02 Desc version/format (currently 2) + __u8 NumRootDescEntries; // F03 # entries in RootDescTable + + __u8 RomSize; // F04 Size of ROM/E2PROM in K + __u8 RamSize; // F05 Size of external RAM in K + __u8 CpuRev; // F06 CPU revision level (chg only if s/w visible) + __u8 BoardRev; // F07 PCB revision level (chg only if s/w visible) + + __u8 NumPorts; // F08 Number of ports + __u8 DescDate[3]; // F09 MM/DD/YY when descriptor template was compiler, + // so host can track changes to USB-only descriptors. + + __u8 SerNumLength; // F0C USB string descriptor len + __u8 SerNumDescType; // F0D USB descriptor type (=STRING type) + __le16 SerialNumber[MAX_SERIALNUMBER_LEN]; // F0E "01-01-000100" Unicode Serial Number + + __u8 AssemblyNumLength; // F26 USB string descriptor len + __u8 AssemblyNumDescType; // F27 USB descriptor type (=STRING type) + __le16 AssemblyNumber[MAX_ASSEMBLYNUMBER_LEN]; // F28 "350-1000-01-A " assembly number + + __u8 OemAssyNumLength; // F44 USB string descriptor len + __u8 OemAssyNumDescType; // F45 USB descriptor type (=STRING type) + __le16 OemAssyNumber[MAX_ASSEMBLYNUMBER_LEN]; // F46 "xxxxxxxxxxxxxx" OEM assembly number + + __u8 ManufDateLength; // F62 USB string descriptor len + __u8 ManufDateDescType; // F63 USB descriptor type (=STRING type) + __le16 ManufDate[6]; // F64 "MMDDYY" manufacturing date + + __u8 Reserved3[0x4D]; // F70 -- unused, set to 0 -- + + __u8 UartType; // FBD Uart Type + __u8 IonPid; // FBE Product ID, == LSB of USB DevDesc.PID + // (Note: Edgeport/4s before 11/98 will have + // 00 here instead of 01) + __u8 IonConfig; // FBF Config byte for ION manufacturing use + // FBF end of structure, total len = 3C0h + +}; + + +#define MANUF_DESC_VER_1 1 // Original definition of MANUF_DESC +#define MANUF_DESC_VER_2 2 // Ver 2, starts at xC00h len 3C0h + + +// Uart Types +// Note: Since this field was added only recently, all Edgeport/4 units +// shipped before 11/98 will have 00 in this field. Therefore, +// both 00 and 01 values mean '654. +#define MANUF_UART_EXAR_654_EARLY 0 // Exar 16C654 in Edgeport/4s before 11/98 +#define MANUF_UART_EXAR_654 1 // Exar 16C654 +#define MANUF_UART_EXAR_2852 2 // Exar 16C2852 + +// +// Note: The CpuRev and BoardRev values do not conform to manufacturing +// revisions; they are to be incremented only when the CPU or hardware +// changes in a software-visible way, such that the 930 software or +// the host driver needs to handle the hardware differently. +// + +// Values of bottom 5 bits of CpuRev & BoardRev for +// Implementation 0 (ie, 930-based) +#define MANUF_CPU_REV_AD4 1 // 930 AD4, with EP1 Rx bug (needs RXSPM) +#define MANUF_CPU_REV_AD5 2 // 930 AD5, with above bug (supposedly) fixed +#define MANUF_CPU_80251 0x20 // Intel 80251 + + +#define MANUF_BOARD_REV_A 1 // Original version, == Manuf Rev A +#define MANUF_BOARD_REV_B 2 // Manuf Rev B, wakeup interrupt works +#define MANUF_BOARD_REV_C 3 // Manuf Rev C, 2/4 ports, rs232/rs422 +#define MANUF_BOARD_REV_GENERATION_2 0x20 // Second generaiton edgeport + + +// Values of bottom 5 bits of CpuRev & BoardRev for +// Implementation 1 (ie, 251+Netchip-based) +#define MANUF_CPU_REV_1 1 // C251TB Rev 1 (Need actual Intel rev here) + +#define MANUF_BOARD_REV_A 1 // First rev of 251+Netchip design + +#define MANUF_SERNUM_LENGTH sizeof(((struct edge_manuf_descriptor *)0)->SerialNumber) +#define MANUF_ASSYNUM_LENGTH sizeof(((struct edge_manuf_descriptor *)0)->AssemblyNumber) +#define MANUF_OEMASSYNUM_LENGTH sizeof(((struct edge_manuf_descriptor *)0)->OemAssyNumber) +#define MANUF_MANUFDATE_LENGTH sizeof(((struct edge_manuf_descriptor *)0)->ManufDate) + +#define MANUF_ION_CONFIG_DIAG_NO_LOOP 0x20 // As below but no ext loopback test +#define MANUF_ION_CONFIG_DIAG 0x40 // 930 based device: 1=Run h/w diags, 0=norm + // TIUMP Device : 1=IONSERIAL needs to run Final Test +#define MANUF_ION_CONFIG_MASTER 0x80 // 930 based device: 1=Master mode, 0=Normal + // TIUMP Device : 1=First device on a multi TIUMP Device + +// +// This structure describes parameters for the boot code, and +// is programmed along with new boot code. These are values +// which are specific to a given build of the boot code. It +// is exactly 64 bytes long and is fixed at address FF:xFC0 +// - FF:xFFF. Note that the 930-mandated UCONFIG bytes are +// included in this structure. +// +struct edge_boot_descriptor { + __u8 Length; // C0 Desc length, per USB (= 40h) + __u8 DescType; // C1 Desc type, per USB (= DEVICE type) + __u8 DescVer; // C2 Desc version/format + __u8 Reserved1; // C3 -- unused, set to 0 -- + + __le16 BootCodeLength; // C4 Boot code goes from FF:0000 to FF:(len-1) + // (LE format) + + __u8 MajorVersion; // C6 Firmware version: xx. + __u8 MinorVersion; // C7 yy. + __le16 BuildNumber; // C8 zzzz (LE format) + + __u16 EnumRootDescTable; // CA Root of ROM-based descriptor table + __u8 NumDescTypes; // CC Number of supported descriptor types + + __u8 Reserved4; // CD Fix Compiler Packing + + __le16 Capabilities; // CE-CF Capabilities flags (LE format) + __u8 Reserved2[0x28]; // D0 -- unused, set to 0 -- + __u8 UConfig0; // F8 930-defined CPU configuration byte 0 + __u8 UConfig1; // F9 930-defined CPU configuration byte 1 + __u8 Reserved3[6]; // FA -- unused, set to 0 -- + // FF end of structure, total len = 80 +}; + + +#define BOOT_DESC_VER_1 1 // Original definition of BOOT_PARAMS +#define BOOT_DESC_VER_2 2 // 2nd definition, descriptors not included in boot + + + // Capabilities flags + +#define BOOT_CAP_RESET_CMD 0x0001 // If set, boot correctly supports ION_RESET_DEVICE + + +/************************************************************************ + T I U M P D E F I N I T I O N S + ***********************************************************************/ + +// Chip definitions in I2C +#define UMP5152 0x52 +#define UMP3410 0x10 + + +//************************************************************************ +// TI I2C Format Definitions +//************************************************************************ +#define I2C_DESC_TYPE_INFO_BASIC 0x01 +#define I2C_DESC_TYPE_FIRMWARE_BASIC 0x02 +#define I2C_DESC_TYPE_DEVICE 0x03 +#define I2C_DESC_TYPE_CONFIG 0x04 +#define I2C_DESC_TYPE_STRING 0x05 +#define I2C_DESC_TYPE_FIRMWARE_AUTO 0x07 // for 3410 download +#define I2C_DESC_TYPE_CONFIG_KLUDGE 0x14 // for 3410 +#define I2C_DESC_TYPE_WATCHPORT_VERSION 0x15 // firmware version number for watchport +#define I2C_DESC_TYPE_WATCHPORT_CALIBRATION_DATA 0x16 // Watchport Calibration Data + +#define I2C_DESC_TYPE_FIRMWARE_BLANK 0xf2 + +// Special section defined by ION +#define I2C_DESC_TYPE_ION 0 // Not defined by TI + + +struct ti_i2c_desc { + __u8 Type; // Type of descriptor + __le16 Size; // Size of data only not including header + __u8 CheckSum; // Checksum (8 bit sum of data only) + __u8 Data[0]; // Data starts here +} __attribute__((packed)); + +// for 5152 devices only (type 2 record) +// for 3410 the version is stored in the WATCHPORT_FIRMWARE_VERSION descriptor +struct ti_i2c_firmware_rec { + __u8 Ver_Major; // Firmware Major version number + __u8 Ver_Minor; // Firmware Minor version number + __u8 Data[0]; // Download starts here +} __attribute__((packed)); + + +struct watchport_firmware_version { +// Added 2 bytes for version number + __u8 Version_Major; // Download Version (for Watchport) + __u8 Version_Minor; +} __attribute__((packed)); + + +// Structure of header of download image in fw_down.h +struct ti_i2c_image_header { + __le16 Length; + __u8 CheckSum; +} __attribute__((packed)); + +struct ti_basic_descriptor { + __u8 Power; // Self powered + // bit 7: 1 - power switching supported + // 0 - power switching not supported + // + // bit 0: 1 - self powered + // 0 - bus powered + // + // + __u16 HubVid; // VID HUB + __u16 HubPid; // PID HUB + __u16 DevPid; // PID Edgeport + __u8 HubTime; // Time for power on to power good + __u8 HubCurrent; // HUB Current = 100ma +} __attribute__((packed)); + + +// CPU / Board Rev Definitions +#define TI_CPU_REV_5052 2 // 5052 based edgeports +#define TI_CPU_REV_3410 3 // 3410 based edgeports + +#define TI_BOARD_REV_TI_EP 0 // Basic ti based edgeport +#define TI_BOARD_REV_COMPACT 1 // Compact board +#define TI_BOARD_REV_WATCHPORT 2 // Watchport + + +#define TI_GET_CPU_REVISION(x) (__u8)((((x)>>4)&0x0f)) +#define TI_GET_BOARD_REVISION(x) (__u8)(((x)&0x0f)) + +#define TI_I2C_SIZE_MASK 0x1f // 5 bits +#define TI_GET_I2C_SIZE(x) ((((x) & TI_I2C_SIZE_MASK)+1)*256) + +#define TI_MAX_I2C_SIZE (16 * 1024) + +#define TI_MANUF_VERSION_0 0 + +// IonConig2 flags +#define TI_CONFIG2_RS232 0x01 +#define TI_CONFIG2_RS422 0x02 +#define TI_CONFIG2_RS485 0x04 +#define TI_CONFIG2_SWITCHABLE 0x08 + +#define TI_CONFIG2_WATCHPORT 0x10 + + +struct edge_ti_manuf_descriptor { + __u8 IonConfig; // Config byte for ION manufacturing use + __u8 IonConfig2; // Expansion + __u8 Version; // Version + __u8 CpuRev_BoardRev; // CPU revision level (0xF0) and Board Rev Level (0x0F) + __u8 NumPorts; // Number of ports for this UMP + __u8 NumVirtualPorts; // Number of Virtual ports + __u8 HubConfig1; // Used to configure the Hub + __u8 HubConfig2; // Used to configure the Hub + __u8 TotalPorts; // Total Number of Com Ports for the entire device (All UMPs) + __u8 Reserved; // Reserved +} __attribute__((packed)); + + +#endif // if !defined(_USBVEND_H) diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c new file mode 100644 index 00000000..76c9a847 --- /dev/null +++ b/drivers/usb/serial/ipaq.c @@ -0,0 +1,619 @@ +/* + * USB Compaq iPAQ driver + * + * Copyright (C) 2001 - 2002 + * Ganesh Varadarajan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KP_RETRIES 100 + +#define DRIVER_AUTHOR "Ganesh Varadarajan " +#define DRIVER_DESC "USB PocketPC PDA driver" + +static int connect_retries = KP_RETRIES; +static int initial_wait; + +/* Function prototypes for an ipaq */ +static int ipaq_open(struct tty_struct *tty, + struct usb_serial_port *port); +static int ipaq_calc_num_ports(struct usb_serial *serial); +static int ipaq_startup(struct usb_serial *serial); + +static struct usb_device_id ipaq_id_table [] = { + { USB_DEVICE(0x0104, 0x00BE) }, /* Socket USB Sync */ + { USB_DEVICE(0x03F0, 0x1016) }, /* HP USB Sync */ + { USB_DEVICE(0x03F0, 0x1116) }, /* HP USB Sync 1611 */ + { USB_DEVICE(0x03F0, 0x1216) }, /* HP USB Sync 1612 */ + { USB_DEVICE(0x03F0, 0x2016) }, /* HP USB Sync 1620 */ + { USB_DEVICE(0x03F0, 0x2116) }, /* HP USB Sync 1621 */ + { USB_DEVICE(0x03F0, 0x2216) }, /* HP USB Sync 1622 */ + { USB_DEVICE(0x03F0, 0x3016) }, /* HP USB Sync 1630 */ + { USB_DEVICE(0x03F0, 0x3116) }, /* HP USB Sync 1631 */ + { USB_DEVICE(0x03F0, 0x3216) }, /* HP USB Sync 1632 */ + { USB_DEVICE(0x03F0, 0x4016) }, /* HP USB Sync 1640 */ + { USB_DEVICE(0x03F0, 0x4116) }, /* HP USB Sync 1641 */ + { USB_DEVICE(0x03F0, 0x4216) }, /* HP USB Sync 1642 */ + { USB_DEVICE(0x03F0, 0x5016) }, /* HP USB Sync 1650 */ + { USB_DEVICE(0x03F0, 0x5116) }, /* HP USB Sync 1651 */ + { USB_DEVICE(0x03F0, 0x5216) }, /* HP USB Sync 1652 */ + { USB_DEVICE(0x0409, 0x00D5) }, /* NEC USB Sync */ + { USB_DEVICE(0x0409, 0x00D6) }, /* NEC USB Sync */ + { USB_DEVICE(0x0409, 0x00D7) }, /* NEC USB Sync */ + { USB_DEVICE(0x0409, 0x8024) }, /* NEC USB Sync */ + { USB_DEVICE(0x0409, 0x8025) }, /* NEC USB Sync */ + { USB_DEVICE(0x043E, 0x9C01) }, /* LGE USB Sync */ + { USB_DEVICE(0x045E, 0x00CE) }, /* Microsoft USB Sync */ + { USB_DEVICE(0x045E, 0x0400) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0401) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0402) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0403) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0404) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0405) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0406) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0407) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0408) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0409) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x040A) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x040B) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x040C) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x040D) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x040E) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x040F) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0410) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0411) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0412) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0413) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0414) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0415) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0416) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0417) }, /* Windows Powered Pocket PC 2002 */ + { USB_DEVICE(0x045E, 0x0432) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0433) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0434) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0435) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0436) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0437) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0438) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0439) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x043A) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x043B) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x043C) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x043D) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x043E) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x043F) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0440) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0441) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0442) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0443) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0444) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0445) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0446) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0447) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0448) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0449) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x044A) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x044B) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x044C) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x044D) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x044E) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x044F) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0450) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0451) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0452) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0453) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0454) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0455) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0456) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0457) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0458) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0459) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x045A) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x045B) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x045C) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x045D) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x045E) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x045F) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0460) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0461) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0462) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0463) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0464) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0465) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0466) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0467) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0468) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0469) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x046A) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x046B) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x046C) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x046D) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x046E) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x046F) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0470) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0471) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0472) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0473) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0474) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0475) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0476) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0477) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0478) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x0479) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x047A) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x047B) }, /* Windows Powered Pocket PC 2003 */ + { USB_DEVICE(0x045E, 0x04C8) }, /* Windows Powered Smartphone 2002 */ + { USB_DEVICE(0x045E, 0x04C9) }, /* Windows Powered Smartphone 2002 */ + { USB_DEVICE(0x045E, 0x04CA) }, /* Windows Powered Smartphone 2002 */ + { USB_DEVICE(0x045E, 0x04CB) }, /* Windows Powered Smartphone 2002 */ + { USB_DEVICE(0x045E, 0x04CC) }, /* Windows Powered Smartphone 2002 */ + { USB_DEVICE(0x045E, 0x04CD) }, /* Windows Powered Smartphone 2002 */ + { USB_DEVICE(0x045E, 0x04CE) }, /* Windows Powered Smartphone 2002 */ + { USB_DEVICE(0x045E, 0x04D7) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04D8) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04D9) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04DA) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04DB) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04DC) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04DD) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04DE) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04DF) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E0) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E1) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E2) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E3) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E4) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E5) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E6) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E7) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E8) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04E9) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x045E, 0x04EA) }, /* Windows Powered Smartphone 2003 */ + { USB_DEVICE(0x049F, 0x0003) }, /* Compaq iPAQ USB Sync */ + { USB_DEVICE(0x049F, 0x0032) }, /* Compaq iPAQ USB Sync */ + { USB_DEVICE(0x04A4, 0x0014) }, /* Hitachi USB Sync */ + { USB_DEVICE(0x04AD, 0x0301) }, /* USB Sync 0301 */ + { USB_DEVICE(0x04AD, 0x0302) }, /* USB Sync 0302 */ + { USB_DEVICE(0x04AD, 0x0303) }, /* USB Sync 0303 */ + { USB_DEVICE(0x04AD, 0x0306) }, /* GPS Pocket PC USB Sync */ + { USB_DEVICE(0x04B7, 0x0531) }, /* MyGuide 7000 XL USB Sync */ + { USB_DEVICE(0x04C5, 0x1058) }, /* FUJITSU USB Sync */ + { USB_DEVICE(0x04C5, 0x1079) }, /* FUJITSU USB Sync */ + { USB_DEVICE(0x04DA, 0x2500) }, /* Panasonic USB Sync */ + { USB_DEVICE(0x04DD, 0x9102) }, /* SHARP WS003SH USB Modem */ + { USB_DEVICE(0x04DD, 0x9121) }, /* SHARP WS004SH USB Modem */ + { USB_DEVICE(0x04DD, 0x9123) }, /* SHARP WS007SH USB Modem */ + { USB_DEVICE(0x04DD, 0x9151) }, /* SHARP S01SH USB Modem */ + { USB_DEVICE(0x04DD, 0x91AC) }, /* SHARP WS011SH USB Modem */ + { USB_DEVICE(0x04E8, 0x5F00) }, /* Samsung NEXiO USB Sync */ + { USB_DEVICE(0x04E8, 0x5F01) }, /* Samsung NEXiO USB Sync */ + { USB_DEVICE(0x04E8, 0x5F02) }, /* Samsung NEXiO USB Sync */ + { USB_DEVICE(0x04E8, 0x5F03) }, /* Samsung NEXiO USB Sync */ + { USB_DEVICE(0x04E8, 0x5F04) }, /* Samsung NEXiO USB Sync */ + { USB_DEVICE(0x04E8, 0x6611) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04E8, 0x6613) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04E8, 0x6615) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04E8, 0x6617) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04E8, 0x6619) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04E8, 0x661B) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04E8, 0x662E) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04E8, 0x6630) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04E8, 0x6632) }, /* Samsung MITs USB Sync */ + { USB_DEVICE(0x04f1, 0x3011) }, /* JVC USB Sync */ + { USB_DEVICE(0x04F1, 0x3012) }, /* JVC USB Sync */ + { USB_DEVICE(0x0502, 0x1631) }, /* c10 Series */ + { USB_DEVICE(0x0502, 0x1632) }, /* c20 Series */ + { USB_DEVICE(0x0502, 0x16E1) }, /* Acer n10 Handheld USB Sync */ + { USB_DEVICE(0x0502, 0x16E2) }, /* Acer n20 Handheld USB Sync */ + { USB_DEVICE(0x0502, 0x16E3) }, /* Acer n30 Handheld USB Sync */ + { USB_DEVICE(0x0536, 0x01A0) }, /* HHP PDT */ + { USB_DEVICE(0x0543, 0x0ED9) }, /* ViewSonic Color Pocket PC V35 */ + { USB_DEVICE(0x0543, 0x1527) }, /* ViewSonic Color Pocket PC V36 */ + { USB_DEVICE(0x0543, 0x1529) }, /* ViewSonic Color Pocket PC V37 */ + { USB_DEVICE(0x0543, 0x152B) }, /* ViewSonic Color Pocket PC V38 */ + { USB_DEVICE(0x0543, 0x152E) }, /* ViewSonic Pocket PC */ + { USB_DEVICE(0x0543, 0x1921) }, /* ViewSonic Communicator Pocket PC */ + { USB_DEVICE(0x0543, 0x1922) }, /* ViewSonic Smartphone */ + { USB_DEVICE(0x0543, 0x1923) }, /* ViewSonic Pocket PC V30 */ + { USB_DEVICE(0x05E0, 0x2000) }, /* Symbol USB Sync */ + { USB_DEVICE(0x05E0, 0x2001) }, /* Symbol USB Sync 0x2001 */ + { USB_DEVICE(0x05E0, 0x2002) }, /* Symbol USB Sync 0x2002 */ + { USB_DEVICE(0x05E0, 0x2003) }, /* Symbol USB Sync 0x2003 */ + { USB_DEVICE(0x05E0, 0x2004) }, /* Symbol USB Sync 0x2004 */ + { USB_DEVICE(0x05E0, 0x2005) }, /* Symbol USB Sync 0x2005 */ + { USB_DEVICE(0x05E0, 0x2006) }, /* Symbol USB Sync 0x2006 */ + { USB_DEVICE(0x05E0, 0x2007) }, /* Symbol USB Sync 0x2007 */ + { USB_DEVICE(0x05E0, 0x2008) }, /* Symbol USB Sync 0x2008 */ + { USB_DEVICE(0x05E0, 0x2009) }, /* Symbol USB Sync 0x2009 */ + { USB_DEVICE(0x05E0, 0x200A) }, /* Symbol USB Sync 0x200A */ + { USB_DEVICE(0x067E, 0x1001) }, /* Intermec Mobile Computer */ + { USB_DEVICE(0x07CF, 0x2001) }, /* CASIO USB Sync 2001 */ + { USB_DEVICE(0x07CF, 0x2002) }, /* CASIO USB Sync 2002 */ + { USB_DEVICE(0x07CF, 0x2003) }, /* CASIO USB Sync 2003 */ + { USB_DEVICE(0x0930, 0x0700) }, /* TOSHIBA USB Sync 0700 */ + { USB_DEVICE(0x0930, 0x0705) }, /* TOSHIBA Pocket PC e310 */ + { USB_DEVICE(0x0930, 0x0706) }, /* TOSHIBA Pocket PC e740 */ + { USB_DEVICE(0x0930, 0x0707) }, /* TOSHIBA Pocket PC e330 Series */ + { USB_DEVICE(0x0930, 0x0708) }, /* TOSHIBA Pocket PC e350 Series */ + { USB_DEVICE(0x0930, 0x0709) }, /* TOSHIBA Pocket PC e750 Series */ + { USB_DEVICE(0x0930, 0x070A) }, /* TOSHIBA Pocket PC e400 Series */ + { USB_DEVICE(0x0930, 0x070B) }, /* TOSHIBA Pocket PC e800 Series */ + { USB_DEVICE(0x094B, 0x0001) }, /* Linkup Systems USB Sync */ + { USB_DEVICE(0x0960, 0x0065) }, /* BCOM USB Sync 0065 */ + { USB_DEVICE(0x0960, 0x0066) }, /* BCOM USB Sync 0066 */ + { USB_DEVICE(0x0960, 0x0067) }, /* BCOM USB Sync 0067 */ + { USB_DEVICE(0x0961, 0x0010) }, /* Portatec USB Sync */ + { USB_DEVICE(0x099E, 0x0052) }, /* Trimble GeoExplorer */ + { USB_DEVICE(0x099E, 0x4000) }, /* TDS Data Collector */ + { USB_DEVICE(0x0B05, 0x4200) }, /* ASUS USB Sync */ + { USB_DEVICE(0x0B05, 0x4201) }, /* ASUS USB Sync */ + { USB_DEVICE(0x0B05, 0x4202) }, /* ASUS USB Sync */ + { USB_DEVICE(0x0B05, 0x420F) }, /* ASUS USB Sync */ + { USB_DEVICE(0x0B05, 0x9200) }, /* ASUS USB Sync */ + { USB_DEVICE(0x0B05, 0x9202) }, /* ASUS USB Sync */ + { USB_DEVICE(0x0BB4, 0x00CE) }, /* HTC USB Sync */ + { USB_DEVICE(0x0BB4, 0x00CF) }, /* HTC USB Modem */ + { USB_DEVICE(0x0BB4, 0x0A01) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A02) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A03) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A04) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A05) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A06) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A07) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A08) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A09) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A0A) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A0B) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A0C) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A0D) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A0E) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A0F) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A10) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A11) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A12) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A13) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A14) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A15) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A16) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A17) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A18) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A19) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A1A) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A1B) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A1C) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A1D) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A1E) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A1F) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A20) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A21) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A22) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A23) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A24) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A25) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A26) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A27) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A28) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A29) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A2A) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A2B) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A2C) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A2D) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A2E) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A2F) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A30) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A31) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A32) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A33) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A34) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A35) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A36) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A37) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A38) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A39) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A3A) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A3B) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A3C) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A3D) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A3E) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A3F) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A40) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A41) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A42) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A43) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A44) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A45) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A46) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A47) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A48) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A49) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A4A) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A4B) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A4C) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A4D) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A4E) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A4F) }, /* PocketPC USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A50) }, /* HTC SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A51) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A52) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A53) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A54) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A55) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A56) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A57) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A58) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A59) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A5A) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A5B) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A5C) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A5D) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A5E) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A5F) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A60) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A61) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A62) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A63) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A64) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A65) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A66) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A67) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A68) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A69) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A6A) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A6B) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A6C) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A6D) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A6E) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A6F) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A70) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A71) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A72) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A73) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A74) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A75) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A76) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A77) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A78) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A79) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A7A) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A7B) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A7C) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A7D) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A7E) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A7F) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A80) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A81) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A82) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A83) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A84) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A85) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A86) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A87) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A88) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A89) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A8A) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A8B) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A8C) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A8D) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A8E) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A8F) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A90) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A91) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A92) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A93) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A94) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A95) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A96) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A97) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A98) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A99) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A9A) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A9B) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A9C) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A9D) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A9E) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0A9F) }, /* SmartPhone USB Sync */ + { USB_DEVICE(0x0BB4, 0x0BCE) }, /* "High Tech Computer Corp" */ + { USB_DEVICE(0x0BF8, 0x1001) }, /* Fujitsu Siemens Computers USB Sync */ + { USB_DEVICE(0x0C44, 0x03A2) }, /* Motorola iDEN Smartphone */ + { USB_DEVICE(0x0C8E, 0x6000) }, /* Cesscom Luxian Series */ + { USB_DEVICE(0x0CAD, 0x9001) }, /* Motorola PowerPad Pocket PC Device */ + { USB_DEVICE(0x0F4E, 0x0200) }, /* Freedom Scientific USB Sync */ + { USB_DEVICE(0x0F98, 0x0201) }, /* Cyberbank USB Sync */ + { USB_DEVICE(0x0FB8, 0x3001) }, /* Wistron USB Sync */ + { USB_DEVICE(0x0FB8, 0x3002) }, /* Wistron USB Sync */ + { USB_DEVICE(0x0FB8, 0x3003) }, /* Wistron USB Sync */ + { USB_DEVICE(0x0FB8, 0x4001) }, /* Wistron USB Sync */ + { USB_DEVICE(0x1066, 0x00CE) }, /* E-TEN USB Sync */ + { USB_DEVICE(0x1066, 0x0300) }, /* E-TEN P3XX Pocket PC */ + { USB_DEVICE(0x1066, 0x0500) }, /* E-TEN P5XX Pocket PC */ + { USB_DEVICE(0x1066, 0x0600) }, /* E-TEN P6XX Pocket PC */ + { USB_DEVICE(0x1066, 0x0700) }, /* E-TEN P7XX Pocket PC */ + { USB_DEVICE(0x1114, 0x0001) }, /* Psion Teklogix Sync 753x */ + { USB_DEVICE(0x1114, 0x0004) }, /* Psion Teklogix Sync netBookPro */ + { USB_DEVICE(0x1114, 0x0006) }, /* Psion Teklogix Sync 7525 */ + { USB_DEVICE(0x1182, 0x1388) }, /* VES USB Sync */ + { USB_DEVICE(0x11D9, 0x1002) }, /* Rugged Pocket PC 2003 */ + { USB_DEVICE(0x11D9, 0x1003) }, /* Rugged Pocket PC 2003 */ + { USB_DEVICE(0x1231, 0xCE01) }, /* USB Sync 03 */ + { USB_DEVICE(0x1231, 0xCE02) }, /* USB Sync 03 */ + { USB_DEVICE(0x1690, 0x0601) }, /* Askey USB Sync */ + { USB_DEVICE(0x22B8, 0x4204) }, /* Motorola MPx200 Smartphone */ + { USB_DEVICE(0x22B8, 0x4214) }, /* Motorola MPc GSM */ + { USB_DEVICE(0x22B8, 0x4224) }, /* Motorola MPx220 Smartphone */ + { USB_DEVICE(0x22B8, 0x4234) }, /* Motorola MPc CDMA */ + { USB_DEVICE(0x22B8, 0x4244) }, /* Motorola MPx100 Smartphone */ + { USB_DEVICE(0x3340, 0x011C) }, /* Mio DigiWalker PPC StrongARM */ + { USB_DEVICE(0x3340, 0x0326) }, /* Mio DigiWalker 338 */ + { USB_DEVICE(0x3340, 0x0426) }, /* Mio DigiWalker 338 */ + { USB_DEVICE(0x3340, 0x043A) }, /* Mio DigiWalker USB Sync */ + { USB_DEVICE(0x3340, 0x051C) }, /* MiTAC USB Sync 528 */ + { USB_DEVICE(0x3340, 0x053A) }, /* Mio DigiWalker SmartPhone USB Sync */ + { USB_DEVICE(0x3340, 0x071C) }, /* MiTAC USB Sync */ + { USB_DEVICE(0x3340, 0x0B1C) }, /* Generic PPC StrongARM */ + { USB_DEVICE(0x3340, 0x0E3A) }, /* Generic PPC USB Sync */ + { USB_DEVICE(0x3340, 0x0F1C) }, /* Itautec USB Sync */ + { USB_DEVICE(0x3340, 0x0F3A) }, /* Generic SmartPhone USB Sync */ + { USB_DEVICE(0x3340, 0x1326) }, /* Itautec USB Sync */ + { USB_DEVICE(0x3340, 0x191C) }, /* YAKUMO USB Sync */ + { USB_DEVICE(0x3340, 0x2326) }, /* Vobis USB Sync */ + { USB_DEVICE(0x3340, 0x3326) }, /* MEDION Winodws Moble USB Sync */ + { USB_DEVICE(0x3708, 0x20CE) }, /* Legend USB Sync */ + { USB_DEVICE(0x3708, 0x21CE) }, /* Lenovo USB Sync */ + { USB_DEVICE(0x4113, 0x0210) }, /* Mobile Media Technology USB Sync */ + { USB_DEVICE(0x4113, 0x0211) }, /* Mobile Media Technology USB Sync */ + { USB_DEVICE(0x4113, 0x0400) }, /* Mobile Media Technology USB Sync */ + { USB_DEVICE(0x4113, 0x0410) }, /* Mobile Media Technology USB Sync */ + { USB_DEVICE(0x413C, 0x4001) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x413C, 0x4002) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x413C, 0x4003) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x413C, 0x4004) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x413C, 0x4005) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x413C, 0x4006) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x413C, 0x4007) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x413C, 0x4008) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x413C, 0x4009) }, /* Dell Axim USB Sync */ + { USB_DEVICE(0x4505, 0x0010) }, /* Smartphone */ + { USB_DEVICE(0x5E04, 0xCE00) }, /* SAGEM Wireless Assistant */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, ipaq_id_table); + + +/* All of the device info needed for the Compaq iPAQ */ +static struct usb_serial_driver ipaq_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ipaq", + }, + .description = "PocketPC PDA", + .id_table = ipaq_id_table, + .bulk_in_size = 256, + .bulk_out_size = 256, + .open = ipaq_open, + .attach = ipaq_startup, + .calc_num_ports = ipaq_calc_num_ports, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ipaq_device, NULL +}; + +static int ipaq_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + int result = 0; + int retries = connect_retries; + + msleep(1000*initial_wait); + + /* + * Send out control message observed in win98 sniffs. Not sure what + * it does, but from empirical observations, it seems that the device + * will start the chat sequence once one of these messages gets + * through. Since this has a reasonably high failure rate, we retry + * several times. + */ + while (retries--) { + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), 0x22, 0x21, + 0x1, 0, NULL, 0, 100); + if (!result) + break; + + msleep(1000); + } + if (!retries && result) { + dev_err(&port->dev, "%s - failed doing control urb, error %d\n", + __func__, result); + return result; + } + + return usb_serial_generic_open(tty, port); +} + +static int ipaq_calc_num_ports(struct usb_serial *serial) +{ + /* + * some devices have 3 endpoints, the 3rd of which + * must be ignored as it would make the core + * create a second port which oopses when used + */ + int ipaq_num_ports = 1; + + dev_dbg(&serial->dev->dev, "%s - numberofendpoints: %d\n", __func__, + (int)serial->interface->cur_altsetting->desc.bNumEndpoints); + + /* + * a few devices have 4 endpoints, seemingly Yakuma devices, + * and we need the second pair, so let them have 2 ports + * + * TODO: can we drop port 1 ? + */ + if (serial->interface->cur_altsetting->desc.bNumEndpoints > 3) { + ipaq_num_ports = 2; + } + + return ipaq_num_ports; +} + + +static int ipaq_startup(struct usb_serial *serial) +{ + /* Some of the devices in ipaq_id_table[] are composite, and we + * shouldn't bind to all the interfaces. This test will rule out + * some obviously invalid possibilities. + */ + if (serial->num_bulk_in < serial->num_ports || + serial->num_bulk_out < serial->num_ports) + return -ENODEV; + + if (serial->dev->actconfig->desc.bConfigurationValue != 1) { + /* + * FIXME: HP iPaq rx3715, possibly others, have 1 config that + * is labeled as 2 + */ + + dev_err(&serial->dev->dev, "active config #%d != 1 ??\n", + serial->dev->actconfig->desc.bConfigurationValue); + return -ENODEV; + } + + dev_dbg(&serial->dev->dev, + "%s - iPAQ module configured for %d ports\n", __func__, + serial->num_ports); + + return usb_reset_configuration(serial->dev); +} + +module_usb_serial_driver(serial_drivers, ipaq_id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(connect_retries, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(connect_retries, + "Maximum number of connect retries (one second each)"); + +module_param(initial_wait, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(initial_wait, + "Time to wait before attempting a connection (in seconds)"); diff --git a/drivers/usb/serial/ipw.c b/drivers/usb/serial/ipw.c new file mode 100644 index 00000000..155eab14 --- /dev/null +++ b/drivers/usb/serial/ipw.c @@ -0,0 +1,318 @@ +/* + * IPWireless 3G UMTS TDD Modem driver (USB connected) + * + * Copyright (C) 2004 Roelf Diedericks + * Copyright (C) 2004 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * All information about the device was acquired using SnoopyPro + * on MSFT's O/S, and examing the MSFT drivers' debug output + * (insanely left _on_ in the enduser version) + * + * It was written out of frustration with the IPWireless USB modem + * supplied by Axity3G/Sentech South Africa not supporting + * Linux whatsoever. + * + * Nobody provided any proprietary information that was not already + * available for this device. + * + * The modem adheres to the "3GPP TS 27.007 AT command set for 3G + * User Equipment (UE)" standard, available from + * http://www.3gpp.org/ftp/Specs/html-info/27007.htm + * + * The code was only tested the IPWireless handheld modem distributed + * in South Africa by Sentech. + * + * It may work for Woosh Inc in .nz too, as it appears they use the + * same kit. + * + * There is still some work to be done in terms of handling + * DCD, DTR, RTS, CTS which are currently faked. + * It's good enough for PPP at this point. It's based off all kinds of + * code found in usb/serial and usb/class + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb-wwan.h" + +#define DRIVER_AUTHOR "Roelf Diedericks" +#define DRIVER_DESC "IPWireless tty driver" + +#define IPW_TTY_MAJOR 240 /* real device node major id, experimental range */ +#define IPW_TTY_MINORS 256 /* we support 256 devices, dunno why, it'd be insane :) */ + +#define USB_IPW_MAGIC 0x6d02 /* magic number for ipw struct */ + + +/* Message sizes */ +#define EVENT_BUFFER_SIZE 0xFF +#define CHAR2INT16(c1, c0) (((u32)((c1) & 0xff) << 8) + (u32)((c0) & 0xff)) + +/* vendor/product pairs that are known work with this driver*/ +#define IPW_VID 0x0bc3 +#define IPW_PID 0x0001 + + +/* Vendor commands: */ + +/* baud rates */ +enum { + ipw_sio_b256000 = 0x000e, + ipw_sio_b128000 = 0x001d, + ipw_sio_b115200 = 0x0020, + ipw_sio_b57600 = 0x0040, + ipw_sio_b56000 = 0x0042, + ipw_sio_b38400 = 0x0060, + ipw_sio_b19200 = 0x00c0, + ipw_sio_b14400 = 0x0100, + ipw_sio_b9600 = 0x0180, + ipw_sio_b4800 = 0x0300, + ipw_sio_b2400 = 0x0600, + ipw_sio_b1200 = 0x0c00, + ipw_sio_b600 = 0x1800 +}; + +/* data bits */ +#define ipw_dtb_7 0x700 +#define ipw_dtb_8 0x810 /* ok so the define is misleading, I know, but forces 8,n,1 */ + /* I mean, is there a point to any other setting these days? :) */ + +/* usb control request types : */ +#define IPW_SIO_RXCTL 0x00 /* control bulk rx channel transmissions, value=1/0 (on/off) */ +#define IPW_SIO_SET_BAUD 0x01 /* set baud, value=requested ipw_sio_bxxxx */ +#define IPW_SIO_SET_LINE 0x03 /* set databits, parity. value=ipw_dtb_x */ +#define IPW_SIO_SET_PIN 0x03 /* set/clear dtr/rts value=ipw_pin_xxx */ +#define IPW_SIO_POLL 0x08 /* get serial port status byte, call with value=0 */ +#define IPW_SIO_INIT 0x11 /* initializes ? value=0 (appears as first thing todo on open) */ +#define IPW_SIO_PURGE 0x12 /* purge all transmissions?, call with value=numchar_to_purge */ +#define IPW_SIO_HANDFLOW 0x13 /* set xon/xoff limits value=0, and a buffer of 0x10 bytes */ +#define IPW_SIO_SETCHARS 0x13 /* set the flowcontrol special chars, value=0, buf=6 bytes, */ + /* last 2 bytes contain flowcontrol chars e.g. 00 00 00 00 11 13 */ + +/* values used for request IPW_SIO_SET_PIN */ +#define IPW_PIN_SETDTR 0x101 +#define IPW_PIN_SETRTS 0x202 +#define IPW_PIN_CLRDTR 0x100 +#define IPW_PIN_CLRRTS 0x200 /* unconfirmed */ + +/* values used for request IPW_SIO_RXCTL */ +#define IPW_RXBULK_ON 1 +#define IPW_RXBULK_OFF 0 + +/* various 16 byte hardcoded transferbuffers used by flow control */ +#define IPW_BYTES_FLOWINIT { 0x01, 0, 0, 0, 0x40, 0, 0, 0, \ + 0, 0, 0, 0, 0, 0, 0, 0 } + +/* Interpretation of modem status lines */ +/* These need sorting out by individually connecting pins and checking + * results. FIXME! + * When data is being sent we see 0x30 in the lower byte; this must + * contain DSR and CTS ... + */ +#define IPW_DSR ((1<<4) | (1<<5)) +#define IPW_CTS ((1<<5) | (1<<4)) + +#define IPW_WANTS_TO_SEND 0x30 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(IPW_VID, IPW_PID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static int ipw_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_device *udev = port->serial->dev; + struct device *dev = &port->dev; + u8 buf_flow_static[16] = IPW_BYTES_FLOWINIT; + u8 *buf_flow_init; + int result; + + buf_flow_init = kmemdup(buf_flow_static, 16, GFP_KERNEL); + if (!buf_flow_init) + return -ENOMEM; + + /* --1: Tell the modem to initialize (we think) From sniffs this is + * always the first thing that gets sent to the modem during + * opening of the device */ + dev_dbg(dev, "%s: Sending SIO_INIT (we guess)\n", __func__); + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + IPW_SIO_INIT, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0, + 0, /* index */ + NULL, + 0, + 100000); + if (result < 0) + dev_err(dev, "Init of modem failed (error = %d)\n", result); + + /* reset the bulk pipes */ + usb_clear_halt(udev, usb_rcvbulkpipe(udev, port->bulk_in_endpointAddress)); + usb_clear_halt(udev, usb_sndbulkpipe(udev, port->bulk_out_endpointAddress)); + + /*--2: Start reading from the device */ + dev_dbg(dev, "%s: setting up bulk read callback\n", __func__); + usb_wwan_open(tty, port); + + /*--3: Tell the modem to open the floodgates on the rx bulk channel */ + dev_dbg(dev, "%s:asking modem for RxRead (RXBULK_ON)\n", __func__); + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + IPW_SIO_RXCTL, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + IPW_RXBULK_ON, + 0, /* index */ + NULL, + 0, + 100000); + if (result < 0) + dev_err(dev, "Enabling bulk RxRead failed (error = %d)\n", result); + + /*--4: setup the initial flowcontrol */ + dev_dbg(dev, "%s:setting init flowcontrol (%s)\n", __func__, buf_flow_init); + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + IPW_SIO_HANDFLOW, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0, + 0, + buf_flow_init, + 0x10, + 200000); + if (result < 0) + dev_err(dev, "initial flowcontrol failed (error = %d)\n", result); + + kfree(buf_flow_init); + return 0; +} + +static int ipw_attach(struct usb_serial *serial) +{ + struct usb_wwan_intf_private *data; + + data = kzalloc(sizeof(struct usb_wwan_intf_private), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->susp_lock); + usb_set_serial_data(serial, data); + return 0; +} + +static void ipw_release(struct usb_serial *serial) +{ + struct usb_wwan_intf_private *data = usb_get_serial_data(serial); + + usb_set_serial_data(serial, NULL); + kfree(data); +} + +static void ipw_dtr_rts(struct usb_serial_port *port, int on) +{ + struct usb_device *udev = port->serial->dev; + struct device *dev = &port->dev; + int result; + + dev_dbg(dev, "%s: on = %d\n", __func__, on); + + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + IPW_SIO_SET_PIN, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + on ? IPW_PIN_SETDTR : IPW_PIN_CLRDTR, + 0, + NULL, + 0, + 200000); + if (result < 0) + dev_err(dev, "setting dtr failed (error = %d)\n", result); + + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + IPW_SIO_SET_PIN, USB_TYPE_VENDOR | + USB_RECIP_INTERFACE | USB_DIR_OUT, + on ? IPW_PIN_SETRTS : IPW_PIN_CLRRTS, + 0, + NULL, + 0, + 200000); + if (result < 0) + dev_err(dev, "setting rts failed (error = %d)\n", result); +} + +static void ipw_close(struct usb_serial_port *port) +{ + struct usb_device *udev = port->serial->dev; + struct device *dev = &port->dev; + int result; + + /*--3: purge */ + dev_dbg(dev, "%s:sending purge\n", __func__); + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + IPW_SIO_PURGE, USB_TYPE_VENDOR | + USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x03, + 0, + NULL, + 0, + 200000); + if (result < 0) + dev_err(dev, "purge failed (error = %d)\n", result); + + + /* send RXBULK_off (tell modem to stop transmitting bulk data on + rx chan) */ + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + IPW_SIO_RXCTL, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + IPW_RXBULK_OFF, + 0, /* index */ + NULL, + 0, + 100000); + + if (result < 0) + dev_err(dev, "Disabling bulk RxRead failed (error = %d)\n", result); + + usb_wwan_close(port); +} + +static struct usb_serial_driver ipw_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ipw", + }, + .description = "IPWireless converter", + .id_table = id_table, + .num_ports = 1, + .open = ipw_open, + .close = ipw_close, + .attach = ipw_attach, + .release = ipw_release, + .port_probe = usb_wwan_port_probe, + .port_remove = usb_wwan_port_remove, + .dtr_rts = ipw_dtr_rts, + .write = usb_wwan_write, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ipw_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +/* Module information */ +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/ir-usb.c b/drivers/usb/serial/ir-usb.c new file mode 100644 index 00000000..716930ab --- /dev/null +++ b/drivers/usb/serial/ir-usb.c @@ -0,0 +1,447 @@ +/* + * USB IR Dongle driver + * + * Copyright (C) 2001-2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2002 Gary Brubaker (xavyer@ix.netcom.com) + * Copyright (C) 2010 Johan Hovold (jhovold@gmail.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver allows a USB IrDA device to be used as a "dumb" serial device. + * This can be useful if you do not have access to a full IrDA stack on the + * other side of the connection. If you do have an IrDA stack on both devices, + * please use the usb-irda driver, as it contains the proper error checking and + * other goodness of a full IrDA stack. + * + * Portions of this driver were taken from drivers/net/irda/irda-usb.c, which + * was written by Roman Weissgaerber , Dag Brattli + * , and Jean Tourrilhes + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Greg Kroah-Hartman , Johan Hovold " +#define DRIVER_DESC "USB IR Dongle driver" + +/* if overridden by the user, then use their value for the size of the read and + * write urbs */ +static int buffer_size; + +/* if overridden by the user, then use the specified number of XBOFs */ +static int xbof = -1; + +static int ir_startup (struct usb_serial *serial); +static int ir_open(struct tty_struct *tty, struct usb_serial_port *port); +static int ir_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size); +static void ir_process_read_urb(struct urb *urb); +static void ir_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios); + +/* Not that this lot means you can only have one per system */ +static u8 ir_baud; +static u8 ir_xbof; +static u8 ir_add_bof; + +static const struct usb_device_id ir_id_table[] = { + { USB_DEVICE(0x050f, 0x0180) }, /* KC Technology, KC-180 */ + { USB_DEVICE(0x08e9, 0x0100) }, /* XTNDAccess */ + { USB_DEVICE(0x09c4, 0x0011) }, /* ACTiSys ACT-IR2000U */ + { USB_INTERFACE_INFO(USB_CLASS_APP_SPEC, USB_SUBCLASS_IRDA, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, ir_id_table); + +static struct usb_serial_driver ir_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ir-usb", + }, + .description = "IR Dongle", + .id_table = ir_id_table, + .num_ports = 1, + .set_termios = ir_set_termios, + .attach = ir_startup, + .open = ir_open, + .prepare_write_buffer = ir_prepare_write_buffer, + .process_read_urb = ir_process_read_urb, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ir_device, NULL +}; + +static inline void irda_usb_dump_class_desc(struct usb_serial *serial, + struct usb_irda_cs_descriptor *desc) +{ + struct device *dev = &serial->dev->dev; + + dev_dbg(dev, "bLength=%x\n", desc->bLength); + dev_dbg(dev, "bDescriptorType=%x\n", desc->bDescriptorType); + dev_dbg(dev, "bcdSpecRevision=%x\n", __le16_to_cpu(desc->bcdSpecRevision)); + dev_dbg(dev, "bmDataSize=%x\n", desc->bmDataSize); + dev_dbg(dev, "bmWindowSize=%x\n", desc->bmWindowSize); + dev_dbg(dev, "bmMinTurnaroundTime=%d\n", desc->bmMinTurnaroundTime); + dev_dbg(dev, "wBaudRate=%x\n", __le16_to_cpu(desc->wBaudRate)); + dev_dbg(dev, "bmAdditionalBOFs=%x\n", desc->bmAdditionalBOFs); + dev_dbg(dev, "bIrdaRateSniff=%x\n", desc->bIrdaRateSniff); + dev_dbg(dev, "bMaxUnicastList=%x\n", desc->bMaxUnicastList); +} + +/*------------------------------------------------------------------*/ +/* + * Function irda_usb_find_class_desc(dev, ifnum) + * + * Returns instance of IrDA class descriptor, or NULL if not found + * + * The class descriptor is some extra info that IrDA USB devices will + * offer to us, describing their IrDA characteristics. We will use that in + * irda_usb_init_qos() + * + * Based on the same function in drivers/net/irda/irda-usb.c + */ +static struct usb_irda_cs_descriptor * +irda_usb_find_class_desc(struct usb_serial *serial, unsigned int ifnum) +{ + struct usb_device *dev = serial->dev; + struct usb_irda_cs_descriptor *desc; + int ret; + + desc = kzalloc(sizeof(*desc), GFP_KERNEL); + if (!desc) + return NULL; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_CS_IRDA_GET_CLASS_DESC, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, ifnum, desc, sizeof(*desc), 1000); + + dev_dbg(&serial->dev->dev, "%s - ret=%d\n", __func__, ret); + if (ret < sizeof(*desc)) { + dev_dbg(&serial->dev->dev, + "%s - class descriptor read %s (%d)\n", __func__, + (ret < 0) ? "failed" : "too short", ret); + goto error; + } + if (desc->bDescriptorType != USB_DT_CS_IRDA) { + dev_dbg(&serial->dev->dev, "%s - bad class descriptor type\n", + __func__); + goto error; + } + + irda_usb_dump_class_desc(serial, desc); + return desc; + +error: + kfree(desc); + return NULL; +} + +static u8 ir_xbof_change(u8 xbof) +{ + u8 result; + + /* reference irda-usb.c */ + switch (xbof) { + case 48: + result = 0x10; + break; + case 28: + case 24: + result = 0x20; + break; + default: + case 12: + result = 0x30; + break; + case 5: + case 6: + result = 0x40; + break; + case 3: + result = 0x50; + break; + case 2: + result = 0x60; + break; + case 1: + result = 0x70; + break; + case 0: + result = 0x80; + break; + } + + return(result); +} + +static int ir_startup(struct usb_serial *serial) +{ + struct usb_irda_cs_descriptor *irda_desc; + + irda_desc = irda_usb_find_class_desc(serial, 0); + if (!irda_desc) { + dev_err(&serial->dev->dev, + "IRDA class descriptor not found, device not bound\n"); + return -ENODEV; + } + + dev_dbg(&serial->dev->dev, + "%s - Baud rates supported:%s%s%s%s%s%s%s%s%s\n", + __func__, + (irda_desc->wBaudRate & USB_IRDA_BR_2400) ? " 2400" : "", + (irda_desc->wBaudRate & USB_IRDA_BR_9600) ? " 9600" : "", + (irda_desc->wBaudRate & USB_IRDA_BR_19200) ? " 19200" : "", + (irda_desc->wBaudRate & USB_IRDA_BR_38400) ? " 38400" : "", + (irda_desc->wBaudRate & USB_IRDA_BR_57600) ? " 57600" : "", + (irda_desc->wBaudRate & USB_IRDA_BR_115200) ? " 115200" : "", + (irda_desc->wBaudRate & USB_IRDA_BR_576000) ? " 576000" : "", + (irda_desc->wBaudRate & USB_IRDA_BR_1152000) ? " 1152000" : "", + (irda_desc->wBaudRate & USB_IRDA_BR_4000000) ? " 4000000" : ""); + + switch (irda_desc->bmAdditionalBOFs) { + case USB_IRDA_AB_48: + ir_add_bof = 48; + break; + case USB_IRDA_AB_24: + ir_add_bof = 24; + break; + case USB_IRDA_AB_12: + ir_add_bof = 12; + break; + case USB_IRDA_AB_6: + ir_add_bof = 6; + break; + case USB_IRDA_AB_3: + ir_add_bof = 3; + break; + case USB_IRDA_AB_2: + ir_add_bof = 2; + break; + case USB_IRDA_AB_1: + ir_add_bof = 1; + break; + case USB_IRDA_AB_0: + ir_add_bof = 0; + break; + default: + break; + } + + kfree(irda_desc); + + return 0; +} + +static int ir_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) + port->write_urbs[i]->transfer_flags = URB_ZERO_PACKET; + + /* Start reading from the device */ + return usb_serial_generic_open(tty, port); +} + +static int ir_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size) +{ + unsigned char *buf = dest; + int count; + + /* + * The first byte of the packet we send to the device contains an + * inbound header which indicates an additional number of BOFs and + * a baud rate change. + * + * See section 5.4.2.2 of the USB IrDA spec. + */ + *buf = ir_xbof | ir_baud; + + count = kfifo_out_locked(&port->write_fifo, buf + 1, size - 1, + &port->lock); + return count + 1; +} + +static void ir_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + + if (!urb->actual_length) + return; + /* + * The first byte of the packet we get from the device + * contains a busy indicator and baud rate change. + * See section 5.4.1.2 of the USB IrDA spec. + */ + if (*data & 0x0f) + ir_baud = *data & 0x0f; + + if (urb->actual_length == 1) + return; + + tty_insert_flip_string(&port->port, data + 1, urb->actual_length - 1); + tty_flip_buffer_push(&port->port); +} + +static void ir_set_termios_callback(struct urb *urb) +{ + kfree(urb->transfer_buffer); + + if (urb->status) + dev_dbg(&urb->dev->dev, "%s - non-zero urb status: %d\n", + __func__, urb->status); +} + +static void ir_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct urb *urb; + unsigned char *transfer_buffer; + int result; + speed_t baud; + int ir_baud; + + baud = tty_get_baud_rate(tty); + + /* + * FIXME, we should compare the baud request against the + * capability stated in the IR header that we got in the + * startup function. + */ + + switch (baud) { + case 2400: + ir_baud = USB_IRDA_BR_2400; + break; + case 9600: + ir_baud = USB_IRDA_BR_9600; + break; + case 19200: + ir_baud = USB_IRDA_BR_19200; + break; + case 38400: + ir_baud = USB_IRDA_BR_38400; + break; + case 57600: + ir_baud = USB_IRDA_BR_57600; + break; + case 115200: + ir_baud = USB_IRDA_BR_115200; + break; + case 576000: + ir_baud = USB_IRDA_BR_576000; + break; + case 1152000: + ir_baud = USB_IRDA_BR_1152000; + break; + case 4000000: + ir_baud = USB_IRDA_BR_4000000; + break; + default: + ir_baud = USB_IRDA_BR_9600; + baud = 9600; + } + + if (xbof == -1) + ir_xbof = ir_xbof_change(ir_add_bof); + else + ir_xbof = ir_xbof_change(xbof) ; + + /* Only speed changes are supported */ + tty_termios_copy_hw(&tty->termios, old_termios); + tty_encode_baud_rate(tty, baud, baud); + + /* + * send the baud change out on an "empty" data packet + */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&port->dev, "%s - no more urbs\n", __func__); + return; + } + transfer_buffer = kmalloc(1, GFP_KERNEL); + if (!transfer_buffer) { + dev_err(&port->dev, "%s - out of memory\n", __func__); + goto err_buf; + } + + *transfer_buffer = ir_xbof | ir_baud; + + usb_fill_bulk_urb( + urb, + port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + transfer_buffer, + 1, + ir_set_termios_callback, + port); + + urb->transfer_flags = URB_ZERO_PACKET; + + result = usb_submit_urb(urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "%s - failed to submit urb: %d\n", + __func__, result); + goto err_subm; + } + + usb_free_urb(urb); + + return; +err_subm: + kfree(transfer_buffer); +err_buf: + usb_free_urb(urb); +} + +static int __init ir_init(void) +{ + if (buffer_size) { + ir_device.bulk_in_size = buffer_size; + ir_device.bulk_out_size = buffer_size; + } + + return usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME, ir_id_table); +} + +static void __exit ir_exit(void) +{ + usb_serial_deregister_drivers(serial_drivers); +} + + +module_init(ir_init); +module_exit(ir_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(xbof, int, 0); +MODULE_PARM_DESC(xbof, "Force specific number of XBOFs"); +module_param(buffer_size, int, 0); +MODULE_PARM_DESC(buffer_size, "Size of the transfer buffers"); + diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c new file mode 100644 index 00000000..790673e5 --- /dev/null +++ b/drivers/usb/serial/iuu_phoenix.c @@ -0,0 +1,1233 @@ +/* + * Infinity Unlimited USB Phoenix driver + * + * Copyright (C) 2010 James Courtier-Dutton (James@superbug.co.uk) + + * Copyright (C) 2007 Alain Degreffe (eczema@ecze.com) + * + * Original code taken from iuutool (Copyright (C) 2006 Juan Carlos Borrás) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * And tested with help of WB Electronics + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iuu_phoenix.h" +#include + +#define DRIVER_DESC "Infinity USB Unlimited Phoenix driver" + +static const struct usb_device_id id_table[] = { + {USB_DEVICE(IUU_USB_VENDOR_ID, IUU_USB_PRODUCT_ID)}, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* turbo parameter */ +static int boost = 100; +static int clockmode = 1; +static int cdmode = 1; +static int iuu_cardin; +static int iuu_cardout; +static bool xmas; +static int vcc_default = 5; + +static int iuu_create_sysfs_attrs(struct usb_serial_port *port); +static int iuu_remove_sysfs_attrs(struct usb_serial_port *port); +static void read_rxcmd_callback(struct urb *urb); + +struct iuu_private { + spinlock_t lock; /* store irq state */ + u8 line_status; + int tiostatus; /* store IUART SIGNAL for tiocmget call */ + u8 reset; /* if 1 reset is needed */ + int poll; /* number of poll */ + u8 *writebuf; /* buffer for writing to device */ + int writelen; /* num of byte to write to device */ + u8 *buf; /* used for initialize speed */ + u8 len; + int vcc; /* vcc (either 3 or 5 V) */ + u32 baud; + u32 boost; + u32 clk; +}; + +static int iuu_port_probe(struct usb_serial_port *port) +{ + struct iuu_private *priv; + int ret; + + priv = kzalloc(sizeof(struct iuu_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->buf = kzalloc(256, GFP_KERNEL); + if (!priv->buf) { + kfree(priv); + return -ENOMEM; + } + + priv->writebuf = kzalloc(256, GFP_KERNEL); + if (!priv->writebuf) { + kfree(priv->buf); + kfree(priv); + return -ENOMEM; + } + + priv->vcc = vcc_default; + spin_lock_init(&priv->lock); + + usb_set_serial_port_data(port, priv); + + ret = iuu_create_sysfs_attrs(port); + if (ret) { + kfree(priv->writebuf); + kfree(priv->buf); + kfree(priv); + return ret; + } + + return 0; +} + +static int iuu_port_remove(struct usb_serial_port *port) +{ + struct iuu_private *priv = usb_get_serial_port_data(port); + + iuu_remove_sysfs_attrs(port); + kfree(priv->writebuf); + kfree(priv->buf); + kfree(priv); + + return 0; +} + +static int iuu_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct iuu_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + /* FIXME: locking on tiomstatus */ + dev_dbg(&port->dev, "%s msg : SET = 0x%04x, CLEAR = 0x%04x\n", + __func__, set, clear); + + spin_lock_irqsave(&priv->lock, flags); + + if ((set & TIOCM_RTS) && !(priv->tiostatus == TIOCM_RTS)) { + dev_dbg(&port->dev, "%s TIOCMSET RESET called !!!\n", __func__); + priv->reset = 1; + } + if (set & TIOCM_RTS) + priv->tiostatus = TIOCM_RTS; + + spin_unlock_irqrestore(&priv->lock, flags); + return 0; +} + +/* This is used to provide a carrier detect mechanism + * When a card is present, the response is 0x00 + * When no card , the reader respond with TIOCM_CD + * This is known as CD autodetect mechanism + */ +static int iuu_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct iuu_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int rc; + + spin_lock_irqsave(&priv->lock, flags); + rc = priv->tiostatus; + spin_unlock_irqrestore(&priv->lock, flags); + + return rc; +} + +static void iuu_rxcmd(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + int result; + int status = urb->status; + + if (status) { + dev_dbg(&port->dev, "%s - status = %d\n", __func__, status); + /* error stop all */ + return; + } + + + memset(port->write_urb->transfer_buffer, IUU_UART_RX, 1); + usb_fill_bulk_urb(port->write_urb, port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, 1, + read_rxcmd_callback, port); + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); +} + +static int iuu_reset(struct usb_serial_port *port, u8 wt) +{ + struct iuu_private *priv = usb_get_serial_port_data(port); + int result; + char *buf_ptr = port->write_urb->transfer_buffer; + + /* Prepare the reset sequence */ + + *buf_ptr++ = IUU_RST_SET; + *buf_ptr++ = IUU_DELAY_MS; + *buf_ptr++ = wt; + *buf_ptr = IUU_RST_CLEAR; + + /* send the sequence */ + + usb_fill_bulk_urb(port->write_urb, + port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, 4, iuu_rxcmd, port); + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + priv->reset = 0; + return result; +} + +/* Status Function + * Return value is + * 0x00 = no card + * 0x01 = smartcard + * 0x02 = sim card + */ +static void iuu_update_status_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct iuu_private *priv = usb_get_serial_port_data(port); + u8 *st; + int status = urb->status; + + if (status) { + dev_dbg(&port->dev, "%s - status = %d\n", __func__, status); + /* error stop all */ + return; + } + + st = urb->transfer_buffer; + dev_dbg(&port->dev, "%s - enter\n", __func__); + if (urb->actual_length == 1) { + switch (st[0]) { + case 0x1: + priv->tiostatus = iuu_cardout; + break; + case 0x0: + priv->tiostatus = iuu_cardin; + break; + default: + priv->tiostatus = iuu_cardin; + } + } + iuu_rxcmd(urb); +} + +static void iuu_status_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + int result; + int status = urb->status; + + dev_dbg(&port->dev, "%s - status = %d\n", __func__, status); + usb_fill_bulk_urb(port->read_urb, port->serial->dev, + usb_rcvbulkpipe(port->serial->dev, + port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, 256, + iuu_update_status_callback, port); + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); +} + +static int iuu_status(struct usb_serial_port *port) +{ + int result; + + memset(port->write_urb->transfer_buffer, IUU_GET_STATE_REGISTER, 1); + usb_fill_bulk_urb(port->write_urb, port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, 1, + iuu_status_callback, port); + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + return result; + +} + +static int bulk_immediate(struct usb_serial_port *port, u8 *buf, u8 count) +{ + int status; + struct usb_serial *serial = port->serial; + int actual = 0; + + /* send the data out the bulk port */ + + status = + usb_bulk_msg(serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), buf, + count, &actual, 1000); + + if (status != IUU_OPERATION_OK) + dev_dbg(&port->dev, "%s - error = %2x\n", __func__, status); + else + dev_dbg(&port->dev, "%s - write OK !\n", __func__); + return status; +} + +static int read_immediate(struct usb_serial_port *port, u8 *buf, u8 count) +{ + int status; + struct usb_serial *serial = port->serial; + int actual = 0; + + /* send the data out the bulk port */ + status = + usb_bulk_msg(serial->dev, + usb_rcvbulkpipe(serial->dev, + port->bulk_in_endpointAddress), buf, + count, &actual, 1000); + + if (status != IUU_OPERATION_OK) + dev_dbg(&port->dev, "%s - error = %2x\n", __func__, status); + else + dev_dbg(&port->dev, "%s - read OK !\n", __func__); + return status; +} + +static int iuu_led(struct usb_serial_port *port, unsigned int R, + unsigned int G, unsigned int B, u8 f) +{ + int status; + u8 *buf; + buf = kmalloc(8, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = IUU_SET_LED; + buf[1] = R & 0xFF; + buf[2] = (R >> 8) & 0xFF; + buf[3] = G & 0xFF; + buf[4] = (G >> 8) & 0xFF; + buf[5] = B & 0xFF; + buf[6] = (B >> 8) & 0xFF; + buf[7] = f; + status = bulk_immediate(port, buf, 8); + kfree(buf); + if (status != IUU_OPERATION_OK) + dev_dbg(&port->dev, "%s - led error status = %2x\n", __func__, status); + else + dev_dbg(&port->dev, "%s - led OK !\n", __func__); + return IUU_OPERATION_OK; +} + +static void iuu_rgbf_fill_buffer(u8 *buf, u8 r1, u8 r2, u8 g1, u8 g2, u8 b1, + u8 b2, u8 freq) +{ + *buf++ = IUU_SET_LED; + *buf++ = r1; + *buf++ = r2; + *buf++ = g1; + *buf++ = g2; + *buf++ = b1; + *buf++ = b2; + *buf = freq; +} + +static void iuu_led_activity_on(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + int result; + char *buf_ptr = port->write_urb->transfer_buffer; + *buf_ptr++ = IUU_SET_LED; + if (xmas == 1) { + get_random_bytes(buf_ptr, 6); + *(buf_ptr+7) = 1; + } else { + iuu_rgbf_fill_buffer(buf_ptr, 255, 255, 0, 0, 0, 0, 255); + } + + usb_fill_bulk_urb(port->write_urb, port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, 8 , + iuu_rxcmd, port); + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); +} + +static void iuu_led_activity_off(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + int result; + char *buf_ptr = port->write_urb->transfer_buffer; + if (xmas == 1) { + iuu_rxcmd(urb); + return; + } else { + *buf_ptr++ = IUU_SET_LED; + iuu_rgbf_fill_buffer(buf_ptr, 0, 0, 255, 255, 0, 0, 255); + } + usb_fill_bulk_urb(port->write_urb, port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, 8 , + iuu_rxcmd, port); + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); +} + + + +static int iuu_clk(struct usb_serial_port *port, int dwFrq) +{ + int status; + struct iuu_private *priv = usb_get_serial_port_data(port); + int Count = 0; + u8 FrqGenAdr = 0x69; + u8 DIV = 0; /* 8bit */ + u8 XDRV = 0; /* 8bit */ + u8 PUMP = 0; /* 3bit */ + u8 PBmsb = 0; /* 2bit */ + u8 PBlsb = 0; /* 8bit */ + u8 PO = 0; /* 1bit */ + u8 Q = 0; /* 7bit */ + /* 24bit = 3bytes */ + unsigned int P = 0; + unsigned int P2 = 0; + int frq = (int)dwFrq; + + if (frq == 0) { + priv->buf[Count++] = IUU_UART_WRITE_I2C; + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x09; + priv->buf[Count++] = 0x00; + + status = bulk_immediate(port, (u8 *) priv->buf, Count); + if (status != 0) { + dev_dbg(&port->dev, "%s - write error\n", __func__); + return status; + } + } else if (frq == 3579000) { + DIV = 100; + P = 1193; + Q = 40; + XDRV = 0; + } else if (frq == 3680000) { + DIV = 105; + P = 161; + Q = 5; + XDRV = 0; + } else if (frq == 6000000) { + DIV = 66; + P = 66; + Q = 2; + XDRV = 0x28; + } else { + unsigned int result = 0; + unsigned int tmp = 0; + unsigned int check; + unsigned int check2; + char found = 0x00; + unsigned int lQ = 2; + unsigned int lP = 2055; + unsigned int lDiv = 4; + + for (lQ = 2; lQ <= 47 && !found; lQ++) + for (lP = 2055; lP >= 8 && !found; lP--) + for (lDiv = 4; lDiv <= 127 && !found; lDiv++) { + tmp = (12000000 / lDiv) * (lP / lQ); + if (abs((int)(tmp - frq)) < + abs((int)(frq - result))) { + check2 = (12000000 / lQ); + if (check2 < 250000) + continue; + check = (12000000 / lQ) * lP; + if (check > 400000000) + continue; + if (check < 100000000) + continue; + if (lDiv < 4 || lDiv > 127) + continue; + result = tmp; + P = lP; + DIV = lDiv; + Q = lQ; + if (result == frq) + found = 0x01; + } + } + } + P2 = ((P - PO) / 2) - 4; + DIV = DIV; + PUMP = 0x04; + PBmsb = (P2 >> 8 & 0x03); + PBlsb = P2 & 0xFF; + PO = (P >> 10) & 0x01; + Q = Q - 2; + + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x09; + priv->buf[Count++] = 0x20; /* Adr = 0x09 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x0C; + priv->buf[Count++] = DIV; /* Adr = 0x0C */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x12; + priv->buf[Count++] = XDRV; /* Adr = 0x12 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x13; + priv->buf[Count++] = 0x6B; /* Adr = 0x13 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x40; + priv->buf[Count++] = (0xC0 | ((PUMP & 0x07) << 2)) | + (PBmsb & 0x03); /* Adr = 0x40 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x41; + priv->buf[Count++] = PBlsb; /* Adr = 0x41 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x42; + priv->buf[Count++] = Q | (((PO & 0x01) << 7)); /* Adr = 0x42 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x44; + priv->buf[Count++] = (char)0xFF; /* Adr = 0x44 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x45; + priv->buf[Count++] = (char)0xFE; /* Adr = 0x45 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x46; + priv->buf[Count++] = 0x7F; /* Adr = 0x46 */ + priv->buf[Count++] = IUU_UART_WRITE_I2C; /* 0x4C */ + priv->buf[Count++] = FrqGenAdr << 1; + priv->buf[Count++] = 0x47; + priv->buf[Count++] = (char)0x84; /* Adr = 0x47 */ + + status = bulk_immediate(port, (u8 *) priv->buf, Count); + if (status != IUU_OPERATION_OK) + dev_dbg(&port->dev, "%s - write error\n", __func__); + return status; +} + +static int iuu_uart_flush(struct usb_serial_port *port) +{ + struct device *dev = &port->dev; + int i; + int status; + u8 rxcmd = IUU_UART_RX; + struct iuu_private *priv = usb_get_serial_port_data(port); + + if (iuu_led(port, 0xF000, 0, 0, 0xFF) < 0) + return -EIO; + + for (i = 0; i < 2; i++) { + status = bulk_immediate(port, &rxcmd, 1); + if (status != IUU_OPERATION_OK) { + dev_dbg(dev, "%s - uart_flush_write error\n", __func__); + return status; + } + + status = read_immediate(port, &priv->len, 1); + if (status != IUU_OPERATION_OK) { + dev_dbg(dev, "%s - uart_flush_read error\n", __func__); + return status; + } + + if (priv->len > 0) { + dev_dbg(dev, "%s - uart_flush datalen is : %i\n", __func__, priv->len); + status = read_immediate(port, priv->buf, priv->len); + if (status != IUU_OPERATION_OK) { + dev_dbg(dev, "%s - uart_flush_read error\n", __func__); + return status; + } + } + } + dev_dbg(dev, "%s - uart_flush_read OK!\n", __func__); + iuu_led(port, 0, 0xF000, 0, 0xFF); + return status; +} + +static void read_buf_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + if (status) { + if (status == -EPROTO) { + /* reschedule needed */ + } + return; + } + + dev_dbg(&port->dev, "%s - %i chars to write\n", __func__, urb->actual_length); + if (data == NULL) + dev_dbg(&port->dev, "%s - data is NULL !!!\n", __func__); + if (urb->actual_length && data) { + tty_insert_flip_string(&port->port, data, urb->actual_length); + tty_flip_buffer_push(&port->port); + } + iuu_led_activity_on(urb); +} + +static int iuu_bulk_write(struct usb_serial_port *port) +{ + struct iuu_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int result; + int buf_len; + char *buf_ptr = port->write_urb->transfer_buffer; + + spin_lock_irqsave(&priv->lock, flags); + *buf_ptr++ = IUU_UART_ESC; + *buf_ptr++ = IUU_UART_TX; + *buf_ptr++ = priv->writelen; + + memcpy(buf_ptr, priv->writebuf, priv->writelen); + buf_len = priv->writelen; + priv->writelen = 0; + spin_unlock_irqrestore(&priv->lock, flags); + dev_dbg(&port->dev, "%s - writing %i chars : %*ph\n", __func__, + buf_len, buf_len, buf_ptr); + usb_fill_bulk_urb(port->write_urb, port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, buf_len + 3, + iuu_rxcmd, port); + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + usb_serial_port_softint(port); + return result; +} + +static int iuu_read_buf(struct usb_serial_port *port, int len) +{ + int result; + + usb_fill_bulk_urb(port->read_urb, port->serial->dev, + usb_rcvbulkpipe(port->serial->dev, + port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, len, + read_buf_callback, port); + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + return result; +} + +static void iuu_uart_read_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct iuu_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int status = urb->status; + int error = 0; + int len = 0; + unsigned char *data = urb->transfer_buffer; + priv->poll++; + + if (status) { + dev_dbg(&port->dev, "%s - status = %d\n", __func__, status); + /* error stop all */ + return; + } + if (data == NULL) + dev_dbg(&port->dev, "%s - data is NULL !!!\n", __func__); + + if (urb->actual_length == 1 && data != NULL) + len = (int) data[0]; + + if (urb->actual_length > 1) { + dev_dbg(&port->dev, "%s - urb->actual_length = %i\n", __func__, + urb->actual_length); + error = 1; + return; + } + /* if len > 0 call readbuf */ + + if (len > 0 && error == 0) { + dev_dbg(&port->dev, "%s - call read buf - len to read is %i\n", + __func__, len); + status = iuu_read_buf(port, len); + return; + } + /* need to update status ? */ + if (priv->poll > 99) { + status = iuu_status(port); + priv->poll = 0; + return; + } + + /* reset waiting ? */ + + if (priv->reset == 1) { + status = iuu_reset(port, 0xC); + return; + } + /* Writebuf is waiting */ + spin_lock_irqsave(&priv->lock, flags); + if (priv->writelen > 0) { + spin_unlock_irqrestore(&priv->lock, flags); + status = iuu_bulk_write(port); + return; + } + spin_unlock_irqrestore(&priv->lock, flags); + /* if nothing to write call again rxcmd */ + dev_dbg(&port->dev, "%s - rxcmd recall\n", __func__); + iuu_led_activity_off(urb); +} + +static int iuu_uart_write(struct tty_struct *tty, struct usb_serial_port *port, + const u8 *buf, int count) +{ + struct iuu_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + if (count > 256) + return -ENOMEM; + + spin_lock_irqsave(&priv->lock, flags); + + /* fill the buffer */ + memcpy(priv->writebuf + priv->writelen, buf, count); + priv->writelen += count; + spin_unlock_irqrestore(&priv->lock, flags); + + return count; +} + +static void read_rxcmd_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + int result; + int status = urb->status; + + if (status) { + /* error stop all */ + return; + } + + usb_fill_bulk_urb(port->read_urb, port->serial->dev, + usb_rcvbulkpipe(port->serial->dev, + port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, 256, + iuu_uart_read_callback, port); + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + dev_dbg(&port->dev, "%s - submit result = %d\n", __func__, result); +} + +static int iuu_uart_on(struct usb_serial_port *port) +{ + int status; + u8 *buf; + + buf = kmalloc(sizeof(u8) * 4, GFP_KERNEL); + + if (!buf) + return -ENOMEM; + + buf[0] = IUU_UART_ENABLE; + buf[1] = (u8) ((IUU_BAUD_9600 >> 8) & 0x00FF); + buf[2] = (u8) (0x00FF & IUU_BAUD_9600); + buf[3] = (u8) (0x0F0 & IUU_ONE_STOP_BIT) | (0x07 & IUU_PARITY_EVEN); + + status = bulk_immediate(port, buf, 4); + if (status != IUU_OPERATION_OK) { + dev_dbg(&port->dev, "%s - uart_on error\n", __func__); + goto uart_enable_failed; + } + /* iuu_reset() the card after iuu_uart_on() */ + status = iuu_uart_flush(port); + if (status != IUU_OPERATION_OK) + dev_dbg(&port->dev, "%s - uart_flush error\n", __func__); +uart_enable_failed: + kfree(buf); + return status; +} + +/* Diables the IUU UART (a.k.a. the Phoenix voiderface) */ +static int iuu_uart_off(struct usb_serial_port *port) +{ + int status; + u8 *buf; + buf = kmalloc(1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + buf[0] = IUU_UART_DISABLE; + + status = bulk_immediate(port, buf, 1); + if (status != IUU_OPERATION_OK) + dev_dbg(&port->dev, "%s - uart_off error\n", __func__); + + kfree(buf); + return status; +} + +static int iuu_uart_baud(struct usb_serial_port *port, u32 baud_base, + u32 *actual, u8 parity) +{ + int status; + u32 baud; + u8 *dataout; + u8 DataCount = 0; + u8 T1Frekvens = 0; + u8 T1reload = 0; + unsigned int T1FrekvensHZ = 0; + + dev_dbg(&port->dev, "%s - enter baud_base=%d\n", __func__, baud_base); + dataout = kmalloc(sizeof(u8) * 5, GFP_KERNEL); + + if (!dataout) + return -ENOMEM; + /*baud = (((priv->clk / 35) * baud_base) / 100000); */ + baud = baud_base; + + if (baud < 1200 || baud > 230400) { + kfree(dataout); + return IUU_INVALID_PARAMETER; + } + if (baud > 977) { + T1Frekvens = 3; + T1FrekvensHZ = 500000; + } + + if (baud > 3906) { + T1Frekvens = 2; + T1FrekvensHZ = 2000000; + } + + if (baud > 11718) { + T1Frekvens = 1; + T1FrekvensHZ = 6000000; + } + + if (baud > 46875) { + T1Frekvens = 0; + T1FrekvensHZ = 24000000; + } + + T1reload = 256 - (u8) (T1FrekvensHZ / (baud * 2)); + + /* magic number here: ENTER_FIRMWARE_UPDATE; */ + dataout[DataCount++] = IUU_UART_ESC; + /* magic number here: CHANGE_BAUD; */ + dataout[DataCount++] = IUU_UART_CHANGE; + dataout[DataCount++] = T1Frekvens; + dataout[DataCount++] = T1reload; + + *actual = (T1FrekvensHZ / (256 - T1reload)) / 2; + + switch (parity & 0x0F) { + case IUU_PARITY_NONE: + dataout[DataCount++] = 0x00; + break; + case IUU_PARITY_EVEN: + dataout[DataCount++] = 0x01; + break; + case IUU_PARITY_ODD: + dataout[DataCount++] = 0x02; + break; + case IUU_PARITY_MARK: + dataout[DataCount++] = 0x03; + break; + case IUU_PARITY_SPACE: + dataout[DataCount++] = 0x04; + break; + default: + kfree(dataout); + return IUU_INVALID_PARAMETER; + break; + } + + switch (parity & 0xF0) { + case IUU_ONE_STOP_BIT: + dataout[DataCount - 1] |= IUU_ONE_STOP_BIT; + break; + + case IUU_TWO_STOP_BITS: + dataout[DataCount - 1] |= IUU_TWO_STOP_BITS; + break; + default: + kfree(dataout); + return IUU_INVALID_PARAMETER; + break; + } + + status = bulk_immediate(port, dataout, DataCount); + if (status != IUU_OPERATION_OK) + dev_dbg(&port->dev, "%s - uart_off error\n", __func__); + kfree(dataout); + return status; +} + +static void iuu_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + const u32 supported_mask = CMSPAR|PARENB|PARODD; + struct iuu_private *priv = usb_get_serial_port_data(port); + unsigned int cflag = tty->termios.c_cflag; + int status; + u32 actual; + u32 parity; + int csize = CS7; + int baud; + u32 newval = cflag & supported_mask; + + /* Just use the ospeed. ispeed should be the same. */ + baud = tty->termios.c_ospeed; + + dev_dbg(&port->dev, "%s - enter c_ospeed or baud=%d\n", __func__, baud); + + /* compute the parity parameter */ + parity = 0; + if (cflag & CMSPAR) { /* Using mark space */ + if (cflag & PARODD) + parity |= IUU_PARITY_SPACE; + else + parity |= IUU_PARITY_MARK; + } else if (!(cflag & PARENB)) { + parity |= IUU_PARITY_NONE; + csize = CS8; + } else if (cflag & PARODD) + parity |= IUU_PARITY_ODD; + else + parity |= IUU_PARITY_EVEN; + + parity |= (cflag & CSTOPB ? IUU_TWO_STOP_BITS : IUU_ONE_STOP_BIT); + + /* set it */ + status = iuu_uart_baud(port, + baud * priv->boost / 100, + &actual, parity); + + /* set the termios value to the real one, so the user now what has + * changed. We support few fields so its easies to copy the old hw + * settings back over and then adjust them + */ + if (old_termios) + tty_termios_copy_hw(&tty->termios, old_termios); + if (status != 0) /* Set failed - return old bits */ + return; + /* Re-encode speed, parity and csize */ + tty_encode_baud_rate(tty, baud, baud); + tty->termios.c_cflag &= ~(supported_mask|CSIZE); + tty->termios.c_cflag |= newval | csize; +} + +static void iuu_close(struct usb_serial_port *port) +{ + /* iuu_led (port,255,0,0,0); */ + + iuu_uart_off(port); + + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); + + iuu_led(port, 0, 0, 0xF000, 0xFF); +} + +static void iuu_init_termios(struct tty_struct *tty) +{ + tty->termios = tty_std_termios; + tty->termios.c_cflag = CLOCAL | CREAD | CS8 | B9600 + | TIOCM_CTS | CSTOPB | PARENB; + tty->termios.c_ispeed = 9600; + tty->termios.c_ospeed = 9600; + tty->termios.c_lflag = 0; + tty->termios.c_oflag = 0; + tty->termios.c_iflag = 0; +} + +static int iuu_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct device *dev = &port->dev; + u8 *buf; + int result; + int baud; + u32 actual; + struct iuu_private *priv = usb_get_serial_port_data(port); + + baud = tty->termios.c_ospeed; + tty->termios.c_ispeed = baud; + /* Re-encode speed */ + tty_encode_baud_rate(tty, baud, baud); + + dev_dbg(dev, "%s - baud %d\n", __func__, baud); + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + + buf = kmalloc(10, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + priv->poll = 0; + + /* initialize writebuf */ +#define FISH(a, b, c, d) do { \ + result = usb_control_msg(port->serial->dev, \ + usb_rcvctrlpipe(port->serial->dev, 0), \ + b, a, c, d, buf, 1, 1000); \ + dev_dbg(dev, "0x%x:0x%x:0x%x:0x%x %d - %x\n", a, b, c, d, result, \ + buf[0]); } while (0); + +#define SOUP(a, b, c, d) do { \ + result = usb_control_msg(port->serial->dev, \ + usb_sndctrlpipe(port->serial->dev, 0), \ + b, a, c, d, NULL, 0, 1000); \ + dev_dbg(dev, "0x%x:0x%x:0x%x:0x%x %d\n", a, b, c, d, result); } while (0) + + /* This is not UART related but IUU USB driver related or something */ + /* like that. Basically no IUU will accept any commands from the USB */ + /* host unless it has received the following message */ + /* sprintf(buf ,"%c%c%c%c",0x03,0x02,0x02,0x0); */ + + SOUP(0x03, 0x02, 0x02, 0x0); + kfree(buf); + iuu_led(port, 0xF000, 0xF000, 0, 0xFF); + iuu_uart_on(port); + if (boost < 100) + boost = 100; + priv->boost = boost; + priv->baud = baud; + switch (clockmode) { + case 2: /* 3.680 Mhz */ + priv->clk = IUU_CLK_3680000; + iuu_clk(port, IUU_CLK_3680000 * boost / 100); + result = + iuu_uart_baud(port, baud * boost / 100, &actual, + IUU_PARITY_EVEN); + break; + case 3: /* 6.00 Mhz */ + iuu_clk(port, IUU_CLK_6000000 * boost / 100); + priv->clk = IUU_CLK_6000000; + /* Ratio of 6000000 to 3500000 for baud 9600 */ + result = + iuu_uart_baud(port, 16457 * boost / 100, &actual, + IUU_PARITY_EVEN); + break; + default: /* 3.579 Mhz */ + iuu_clk(port, IUU_CLK_3579000 * boost / 100); + priv->clk = IUU_CLK_3579000; + result = + iuu_uart_baud(port, baud * boost / 100, &actual, + IUU_PARITY_EVEN); + } + + /* set the cardin cardout signals */ + switch (cdmode) { + case 0: + iuu_cardin = 0; + iuu_cardout = 0; + break; + case 1: + iuu_cardin = TIOCM_CD; + iuu_cardout = 0; + break; + case 2: + iuu_cardin = 0; + iuu_cardout = TIOCM_CD; + break; + case 3: + iuu_cardin = TIOCM_DSR; + iuu_cardout = 0; + break; + case 4: + iuu_cardin = 0; + iuu_cardout = TIOCM_DSR; + break; + case 5: + iuu_cardin = TIOCM_CTS; + iuu_cardout = 0; + break; + case 6: + iuu_cardin = 0; + iuu_cardout = TIOCM_CTS; + break; + case 7: + iuu_cardin = TIOCM_RNG; + iuu_cardout = 0; + break; + case 8: + iuu_cardin = 0; + iuu_cardout = TIOCM_RNG; + } + + iuu_uart_flush(port); + + dev_dbg(dev, "%s - initialization done\n", __func__); + + memset(port->write_urb->transfer_buffer, IUU_UART_RX, 1); + usb_fill_bulk_urb(port->write_urb, port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, 1, + read_rxcmd_callback, port); + result = usb_submit_urb(port->write_urb, GFP_KERNEL); + if (result) { + dev_err(dev, "%s - failed submitting read urb, error %d\n", __func__, result); + iuu_close(port); + } else { + dev_dbg(dev, "%s - rxcmd OK\n", __func__); + } + + return result; +} + +/* how to change VCC */ +static int iuu_vcc_set(struct usb_serial_port *port, unsigned int vcc) +{ + int status; + u8 *buf; + + buf = kmalloc(5, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = IUU_SET_VCC; + buf[1] = vcc & 0xFF; + buf[2] = (vcc >> 8) & 0xFF; + buf[3] = (vcc >> 16) & 0xFF; + buf[4] = (vcc >> 24) & 0xFF; + + status = bulk_immediate(port, buf, 5); + kfree(buf); + + if (status != IUU_OPERATION_OK) + dev_dbg(&port->dev, "%s - vcc error status = %2x\n", __func__, status); + else + dev_dbg(&port->dev, "%s - vcc OK !\n", __func__); + + return status; +} + +/* + * Sysfs Attributes + */ + +static ssize_t show_vcc_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct iuu_private *priv = usb_get_serial_port_data(port); + + return sprintf(buf, "%d\n", priv->vcc); +} + +static ssize_t store_vcc_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct iuu_private *priv = usb_get_serial_port_data(port); + unsigned long v; + + if (kstrtoul(buf, 10, &v)) { + dev_err(dev, "%s - vcc_mode: %s is not a unsigned long\n", + __func__, buf); + goto fail_store_vcc_mode; + } + + dev_dbg(dev, "%s: setting vcc_mode = %ld", __func__, v); + + if ((v != 3) && (v != 5)) { + dev_err(dev, "%s - vcc_mode %ld is invalid\n", __func__, v); + } else { + iuu_vcc_set(port, v); + priv->vcc = v; + } +fail_store_vcc_mode: + return count; +} + +static DEVICE_ATTR(vcc_mode, S_IRUSR | S_IWUSR, show_vcc_mode, + store_vcc_mode); + +static int iuu_create_sysfs_attrs(struct usb_serial_port *port) +{ + return device_create_file(&port->dev, &dev_attr_vcc_mode); +} + +static int iuu_remove_sysfs_attrs(struct usb_serial_port *port) +{ + device_remove_file(&port->dev, &dev_attr_vcc_mode); + return 0; +} + +/* + * End Sysfs Attributes + */ + +static struct usb_serial_driver iuu_device = { + .driver = { + .owner = THIS_MODULE, + .name = "iuu_phoenix", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_in_size = 512, + .bulk_out_size = 512, + .open = iuu_open, + .close = iuu_close, + .write = iuu_uart_write, + .read_bulk_callback = iuu_uart_read_callback, + .tiocmget = iuu_tiocmget, + .tiocmset = iuu_tiocmset, + .set_termios = iuu_set_termios, + .init_termios = iuu_init_termios, + .port_probe = iuu_port_probe, + .port_remove = iuu_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &iuu_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR("Alain Degreffe eczema@ecze.com"); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(xmas, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(xmas, "Xmas colors enabled or not"); + +module_param(boost, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(boost, "Card overclock boost (in percent 100-500)"); + +module_param(clockmode, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(clockmode, "Card clock mode (1=3.579 MHz, 2=3.680 MHz, " + "3=6 Mhz)"); + +module_param(cdmode, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(cdmode, "Card detect mode (0=none, 1=CD, 2=!CD, 3=DSR, " + "4=!DSR, 5=CTS, 6=!CTS, 7=RING, 8=!RING)"); + +module_param(vcc_default, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(vcc_default, "Set default VCC (either 3 for 3.3V or 5 " + "for 5V). Default to 5."); diff --git a/drivers/usb/serial/iuu_phoenix.h b/drivers/usb/serial/iuu_phoenix.h new file mode 100644 index 00000000..b82630a3 --- /dev/null +++ b/drivers/usb/serial/iuu_phoenix.h @@ -0,0 +1,122 @@ +/* + * Infinity Unlimited USB Phoenix driver + * + * Copyright (C) 2007 Alain Degreffe (eczema@ecze.com) + * + * + * Original code taken from iuutool ( Copyright (C) 2006 Juan Carlos Borrás ) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * And tested with help of WB Electronics + * + */ + +#define IUU_USB_VENDOR_ID 0x104f +#define IUU_USB_PRODUCT_ID 0x0004 +#define IUU_USB_OP_TIMEOUT 0x0200 + +/* Programmer commands */ + +#define IUU_NO_OPERATION 0x00 +#define IUU_GET_FIRMWARE_VERSION 0x01 +#define IUU_GET_PRODUCT_NAME 0x02 +#define IUU_GET_STATE_REGISTER 0x03 +#define IUU_SET_LED 0x04 +#define IUU_WAIT_MUS 0x05 +#define IUU_WAIT_MS 0x06 +#define IUU_GET_LOADER_VERSION 0x50 +#define IUU_RST_SET 0x52 +#define IUU_RST_CLEAR 0x53 +#define IUU_SET_VCC 0x59 +#define IUU_UART_ENABLE 0x49 +#define IUU_UART_DISABLE 0x4A +#define IUU_UART_WRITE_I2C 0x4C +#define IUU_UART_ESC 0x5E +#define IUU_UART_TRAP 0x54 +#define IUU_UART_TRAP_BREAK 0x5B +#define IUU_UART_RX 0x56 +#define IUU_AVR_ON 0x21 +#define IUU_AVR_OFF 0x22 +#define IUU_AVR_1CLK 0x23 +#define IUU_AVR_RESET 0x24 +#define IUU_AVR_RESET_PC 0x25 +#define IUU_AVR_INC_PC 0x26 +#define IUU_AVR_INCN_PC 0x27 +#define IUU_AVR_PREAD 0x29 +#define IUU_AVR_PREADN 0x2A +#define IUU_AVR_PWRITE 0x28 +#define IUU_AVR_DREAD 0x2C +#define IUU_AVR_DREADN 0x2D +#define IUU_AVR_DWRITE 0x2B +#define IUU_AVR_PWRITEN 0x2E +#define IUU_EEPROM_ON 0x37 +#define IUU_EEPROM_OFF 0x38 +#define IUU_EEPROM_WRITE 0x39 +#define IUU_EEPROM_WRITEX 0x3A +#define IUU_EEPROM_WRITE8 0x3B +#define IUU_EEPROM_WRITE16 0x3C +#define IUU_EEPROM_WRITEX32 0x3D +#define IUU_EEPROM_WRITEX64 0x3E +#define IUU_EEPROM_READ 0x3F +#define IUU_EEPROM_READX 0x40 +#define IUU_EEPROM_BREAD 0x41 +#define IUU_EEPROM_BREADX 0x42 +#define IUU_PIC_CMD 0x0A +#define IUU_PIC_CMD_LOAD 0x0B +#define IUU_PIC_CMD_READ 0x0C +#define IUU_PIC_ON 0x0D +#define IUU_PIC_OFF 0x0E +#define IUU_PIC_RESET 0x16 +#define IUU_PIC_INC_PC 0x0F +#define IUU_PIC_INCN_PC 0x10 +#define IUU_PIC_PWRITE 0x11 +#define IUU_PIC_PREAD 0x12 +#define IUU_PIC_PREADN 0x13 +#define IUU_PIC_DWRITE 0x14 +#define IUU_PIC_DREAD 0x15 +#define IUU_UART_NOP 0x00 +#define IUU_UART_CHANGE 0x02 +#define IUU_UART_TX 0x04 +#define IUU_DELAY_MS 0x06 + +#define IUU_OPERATION_OK 0x00 +#define IUU_DEVICE_NOT_FOUND 0x01 +#define IUU_INVALID_HANDLE 0x02 +#define IUU_INVALID_PARAMETER 0x03 +#define IUU_INVALID_voidERFACE 0x04 +#define IUU_INVALID_REQUEST_LENGTH 0x05 +#define IUU_UART_NOT_ENABLED 0x06 +#define IUU_WRITE_ERROR 0x07 +#define IUU_READ_ERROR 0x08 +#define IUU_TX_ERROR 0x09 +#define IUU_RX_ERROR 0x0A + +#define IUU_PARITY_NONE 0x00 +#define IUU_PARITY_EVEN 0x01 +#define IUU_PARITY_ODD 0x02 +#define IUU_PARITY_MARK 0x03 +#define IUU_PARITY_SPACE 0x04 +#define IUU_SC_INSERTED 0x01 +#define IUU_VERIFY_ERROR 0x02 +#define IUU_SIM_INSERTED 0x04 +#define IUU_TWO_STOP_BITS 0x00 +#define IUU_ONE_STOP_BIT 0x20 +#define IUU_BAUD_2400 0x0398 +#define IUU_BAUD_9600 0x0298 +#define IUU_BAUD_19200 0x0164 +#define IUU_BAUD_28800 0x0198 +#define IUU_BAUD_38400 0x01B2 +#define IUU_BAUD_57600 0x0030 +#define IUU_BAUD_115200 0x0098 +#define IUU_CLK_3579000 3579000 +#define IUU_CLK_3680000 3680000 +#define IUU_CLK_6000000 6000000 +#define IUU_FULLCARD_IN 0x01 +#define IUU_DEV_ERROR 0x02 +#define IUU_MINICARD_IN 0x04 +#define IUU_VCC_5V 0x00 +#define IUU_VCC_3V 0x01 diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c new file mode 100644 index 00000000..f0e65c97 --- /dev/null +++ b/drivers/usb/serial/keyspan.c @@ -0,0 +1,2492 @@ +/* + Keyspan USB to Serial Converter driver + + (C) Copyright (C) 2000-2001 Hugh Blemings + (C) Copyright (C) 2002 Greg Kroah-Hartman + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + See http://blemings.org/hugh/keyspan.html for more information. + + Code in this driver inspired by and in a number of places taken + from Brian Warner's original Keyspan-PDA driver. + + This driver has been put together with the support of Innosys, Inc. + and Keyspan, Inc the manufacturers of the Keyspan USB-serial products. + Thanks Guys :) + + Thanks to Paulus for miscellaneous tidy ups, some largish chunks + of much nicer and/or completely new code and (perhaps most uniquely) + having the patience to sit down and explain why and where he'd changed + stuff. + + Tip 'o the hat to IBM (and previously Linuxcare :) for supporting + staff in their work on open source projects. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "keyspan.h" + +#define DRIVER_AUTHOR "Hugh Blemings driver_data; + struct keyspan_port_private *p_priv; + + p_priv = usb_get_serial_port_data(port); + + if (break_state == -1) + p_priv->break_on = 1; + else + p_priv->break_on = 0; + + keyspan_send_setup(port, 0); +} + + +static void keyspan_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + int baud_rate, device_port; + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + unsigned int cflag; + + p_priv = usb_get_serial_port_data(port); + d_details = p_priv->device_details; + cflag = tty->termios.c_cflag; + device_port = port->number - port->serial->minor; + + /* Baud rate calculation takes baud rate as an integer + so other rates can be generated if desired. */ + baud_rate = tty_get_baud_rate(tty); + /* If no match or invalid, don't change */ + if (d_details->calculate_baud_rate(port, baud_rate, d_details->baudclk, + NULL, NULL, NULL, device_port) == KEYSPAN_BAUD_RATE_OK) { + /* FIXME - more to do here to ensure rate changes cleanly */ + /* FIXME - calcuate exact rate from divisor ? */ + p_priv->baud = baud_rate; + } else + baud_rate = tty_termios_baud_rate(old_termios); + + tty_encode_baud_rate(tty, baud_rate, baud_rate); + /* set CTS/RTS handshake etc. */ + p_priv->cflag = cflag; + p_priv->flow_control = (cflag & CRTSCTS) ? flow_cts : flow_none; + + /* Mark/Space not supported */ + tty->termios.c_cflag &= ~CMSPAR; + + keyspan_send_setup(port, 0); +} + +static int keyspan_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct keyspan_port_private *p_priv = usb_get_serial_port_data(port); + unsigned int value; + + value = ((p_priv->rts_state) ? TIOCM_RTS : 0) | + ((p_priv->dtr_state) ? TIOCM_DTR : 0) | + ((p_priv->cts_state) ? TIOCM_CTS : 0) | + ((p_priv->dsr_state) ? TIOCM_DSR : 0) | + ((p_priv->dcd_state) ? TIOCM_CAR : 0) | + ((p_priv->ri_state) ? TIOCM_RNG : 0); + + return value; +} + +static int keyspan_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct keyspan_port_private *p_priv = usb_get_serial_port_data(port); + + if (set & TIOCM_RTS) + p_priv->rts_state = 1; + if (set & TIOCM_DTR) + p_priv->dtr_state = 1; + if (clear & TIOCM_RTS) + p_priv->rts_state = 0; + if (clear & TIOCM_DTR) + p_priv->dtr_state = 0; + keyspan_send_setup(port, 0); + return 0; +} + +/* Write function is similar for the four protocols used + with only a minor change for usa90 (usa19hs) required */ +static int keyspan_write(struct tty_struct *tty, + struct usb_serial_port *port, const unsigned char *buf, int count) +{ + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + int flip; + int left, todo; + struct urb *this_urb; + int err, maxDataLen, dataOffset; + + p_priv = usb_get_serial_port_data(port); + d_details = p_priv->device_details; + + if (d_details->msg_format == msg_usa90) { + maxDataLen = 64; + dataOffset = 0; + } else { + maxDataLen = 63; + dataOffset = 1; + } + + dev_dbg(&port->dev, "%s - for port %d (%d chars), flip=%d\n", + __func__, port->number, count, p_priv->out_flip); + + for (left = count; left > 0; left -= todo) { + todo = left; + if (todo > maxDataLen) + todo = maxDataLen; + + flip = p_priv->out_flip; + + /* Check we have a valid urb/endpoint before we use it... */ + this_urb = p_priv->out_urbs[flip]; + if (this_urb == NULL) { + /* no bulk out, so return 0 bytes written */ + dev_dbg(&port->dev, "%s - no output urb :(\n", __func__); + return count; + } + + dev_dbg(&port->dev, "%s - endpoint %d flip %d\n", + __func__, usb_pipeendpoint(this_urb->pipe), flip); + + if (this_urb->status == -EINPROGRESS) { + if (time_before(jiffies, + p_priv->tx_start_time[flip] + 10 * HZ)) + break; + usb_unlink_urb(this_urb); + break; + } + + /* First byte in buffer is "last flag" (except for usa19hx) + - unused so for now so set to zero */ + ((char *)this_urb->transfer_buffer)[0] = 0; + + memcpy(this_urb->transfer_buffer + dataOffset, buf, todo); + buf += todo; + + /* send the data out the bulk port */ + this_urb->transfer_buffer_length = todo + dataOffset; + + err = usb_submit_urb(this_urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "usb_submit_urb(write bulk) failed (%d)\n", err); + p_priv->tx_start_time[flip] = jiffies; + + /* Flip for next time if usa26 or usa28 interface + (not used on usa49) */ + p_priv->out_flip = (flip + 1) & d_details->outdat_endp_flip; + } + + return count - left; +} + +static void usa26_indat_callback(struct urb *urb) +{ + int i, err; + int endpoint; + struct usb_serial_port *port; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + endpoint = usb_pipeendpoint(urb->pipe); + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x on endpoint %d.\n", + __func__, status, endpoint); + return; + } + + port = urb->context; + if (urb->actual_length) { + /* 0x80 bit is error flag */ + if ((data[0] & 0x80) == 0) { + /* no errors on individual bytes, only + possible overrun err */ + if (data[0] & RXERROR_OVERRUN) { + tty_insert_flip_char(&port->port, 0, + TTY_OVERRUN); + } + for (i = 1; i < urb->actual_length ; ++i) + tty_insert_flip_char(&port->port, data[i], + TTY_NORMAL); + } else { + /* some bytes had errors, every byte has status */ + dev_dbg(&port->dev, "%s - RX error!!!!\n", __func__); + for (i = 0; i + 1 < urb->actual_length; i += 2) { + int stat = data[i]; + int flag = TTY_NORMAL; + + if (stat & RXERROR_OVERRUN) { + tty_insert_flip_char(&port->port, 0, + TTY_OVERRUN); + } + /* XXX should handle break (0x10) */ + if (stat & RXERROR_PARITY) + flag = TTY_PARITY; + else if (stat & RXERROR_FRAMING) + flag = TTY_FRAME; + + tty_insert_flip_char(&port->port, data[i+1], + flag); + } + } + tty_flip_buffer_push(&port->port); + } + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +} + +/* Outdat handling is common for all devices */ +static void usa2x_outdat_callback(struct urb *urb) +{ + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + + port = urb->context; + p_priv = usb_get_serial_port_data(port); + dev_dbg(&port->dev, "%s - urb %d\n", __func__, urb == p_priv->out_urbs[1]); + + usb_serial_port_softint(port); +} + +static void usa26_inack_callback(struct urb *urb) +{ +} + +static void usa26_outcont_callback(struct urb *urb) +{ + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + + port = urb->context; + p_priv = usb_get_serial_port_data(port); + + if (p_priv->resend_cont) { + dev_dbg(&port->dev, "%s - sending setup\n", __func__); + keyspan_usa26_send_setup(port->serial, port, + p_priv->resend_cont - 1); + } +} + +static void usa26_instat_callback(struct urb *urb) +{ + unsigned char *data = urb->transfer_buffer; + struct keyspan_usa26_portStatusMessage *msg; + struct usb_serial *serial; + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + int old_dcd_state, err; + int status = urb->status; + + serial = urb->context; + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x\n", __func__, status); + return; + } + if (urb->actual_length != 9) { + dev_dbg(&urb->dev->dev, "%s - %d byte report??\n", __func__, urb->actual_length); + goto exit; + } + + msg = (struct keyspan_usa26_portStatusMessage *)data; + +#if 0 + dev_dbg(&urb->dev->dev, + "%s - port status: port %d cts %d dcd %d dsr %d ri %d toff %d txoff %d rxen %d cr %d", + __func__, msg->port, msg->hskia_cts, msg->gpia_dcd, msg->dsr, + msg->ri, msg->_txOff, msg->_txXoff, msg->rxEnabled, + msg->controlResponse); +#endif + + /* Now do something useful with the data */ + + + /* Check port number from message and retrieve private data */ + if (msg->port >= serial->num_ports) { + dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", __func__, msg->port); + goto exit; + } + port = serial->port[msg->port]; + p_priv = usb_get_serial_port_data(port); + + /* Update handshaking pin state information */ + old_dcd_state = p_priv->dcd_state; + p_priv->cts_state = ((msg->hskia_cts) ? 1 : 0); + p_priv->dsr_state = ((msg->dsr) ? 1 : 0); + p_priv->dcd_state = ((msg->gpia_dcd) ? 1 : 0); + p_priv->ri_state = ((msg->ri) ? 1 : 0); + + if (old_dcd_state != p_priv->dcd_state) + tty_port_tty_hangup(&port->port, true); + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +exit: ; +} + +static void usa26_glocont_callback(struct urb *urb) +{ +} + + +static void usa28_indat_callback(struct urb *urb) +{ + int err; + struct usb_serial_port *port; + unsigned char *data; + struct keyspan_port_private *p_priv; + int status = urb->status; + + port = urb->context; + p_priv = usb_get_serial_port_data(port); + data = urb->transfer_buffer; + + if (urb != p_priv->in_urbs[p_priv->in_flip]) + return; + + do { + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x on endpoint %d.\n", + __func__, status, usb_pipeendpoint(urb->pipe)); + return; + } + + port = urb->context; + p_priv = usb_get_serial_port_data(port); + data = urb->transfer_buffer; + + if (urb->actual_length) { + tty_insert_flip_string(&port->port, data, + urb->actual_length); + tty_flip_buffer_push(&port->port); + } + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", + __func__, err); + p_priv->in_flip ^= 1; + + urb = p_priv->in_urbs[p_priv->in_flip]; + } while (urb->status != -EINPROGRESS); +} + +static void usa28_inack_callback(struct urb *urb) +{ +} + +static void usa28_outcont_callback(struct urb *urb) +{ + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + + port = urb->context; + p_priv = usb_get_serial_port_data(port); + + if (p_priv->resend_cont) { + dev_dbg(&port->dev, "%s - sending setup\n", __func__); + keyspan_usa28_send_setup(port->serial, port, + p_priv->resend_cont - 1); + } +} + +static void usa28_instat_callback(struct urb *urb) +{ + int err; + unsigned char *data = urb->transfer_buffer; + struct keyspan_usa28_portStatusMessage *msg; + struct usb_serial *serial; + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + int old_dcd_state; + int status = urb->status; + + serial = urb->context; + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x\n", __func__, status); + return; + } + + if (urb->actual_length != sizeof(struct keyspan_usa28_portStatusMessage)) { + dev_dbg(&urb->dev->dev, "%s - bad length %d\n", __func__, urb->actual_length); + goto exit; + } + + /* + dev_dbg(&urb->dev->dev, + "%s %x %x %x %x %x %x %x %x %x %x %x %x", __func__, + data[0], data[1], data[2], data[3], data[4], data[5], + data[6], data[7], data[8], data[9], data[10], data[11]); + */ + + /* Now do something useful with the data */ + msg = (struct keyspan_usa28_portStatusMessage *)data; + + /* Check port number from message and retrieve private data */ + if (msg->port >= serial->num_ports) { + dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", __func__, msg->port); + goto exit; + } + port = serial->port[msg->port]; + p_priv = usb_get_serial_port_data(port); + + /* Update handshaking pin state information */ + old_dcd_state = p_priv->dcd_state; + p_priv->cts_state = ((msg->cts) ? 1 : 0); + p_priv->dsr_state = ((msg->dsr) ? 1 : 0); + p_priv->dcd_state = ((msg->dcd) ? 1 : 0); + p_priv->ri_state = ((msg->ri) ? 1 : 0); + + if (old_dcd_state != p_priv->dcd_state && old_dcd_state) + tty_port_tty_hangup(&port->port, true); + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +exit: ; +} + +static void usa28_glocont_callback(struct urb *urb) +{ +} + + +static void usa49_glocont_callback(struct urb *urb) +{ + struct usb_serial *serial; + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + int i; + + serial = urb->context; + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + p_priv = usb_get_serial_port_data(port); + + if (p_priv->resend_cont) { + dev_dbg(&port->dev, "%s - sending setup\n", __func__); + keyspan_usa49_send_setup(serial, port, + p_priv->resend_cont - 1); + break; + } + } +} + + /* This is actually called glostat in the Keyspan + doco */ +static void usa49_instat_callback(struct urb *urb) +{ + int err; + unsigned char *data = urb->transfer_buffer; + struct keyspan_usa49_portStatusMessage *msg; + struct usb_serial *serial; + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + int old_dcd_state; + int status = urb->status; + + serial = urb->context; + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x\n", __func__, status); + return; + } + + if (urb->actual_length != + sizeof(struct keyspan_usa49_portStatusMessage)) { + dev_dbg(&urb->dev->dev, "%s - bad length %d\n", __func__, urb->actual_length); + goto exit; + } + + /* + dev_dbg(&urb->dev->dev, "%s: %x %x %x %x %x %x %x %x %x %x %x", + __func__, data[0], data[1], data[2], data[3], data[4], + data[5], data[6], data[7], data[8], data[9], data[10]); + */ + + /* Now do something useful with the data */ + msg = (struct keyspan_usa49_portStatusMessage *)data; + + /* Check port number from message and retrieve private data */ + if (msg->portNumber >= serial->num_ports) { + dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", + __func__, msg->portNumber); + goto exit; + } + port = serial->port[msg->portNumber]; + p_priv = usb_get_serial_port_data(port); + + /* Update handshaking pin state information */ + old_dcd_state = p_priv->dcd_state; + p_priv->cts_state = ((msg->cts) ? 1 : 0); + p_priv->dsr_state = ((msg->dsr) ? 1 : 0); + p_priv->dcd_state = ((msg->dcd) ? 1 : 0); + p_priv->ri_state = ((msg->ri) ? 1 : 0); + + if (old_dcd_state != p_priv->dcd_state && old_dcd_state) + tty_port_tty_hangup(&port->port, true); + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +exit: ; +} + +static void usa49_inack_callback(struct urb *urb) +{ +} + +static void usa49_indat_callback(struct urb *urb) +{ + int i, err; + int endpoint; + struct usb_serial_port *port; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + endpoint = usb_pipeendpoint(urb->pipe); + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x on endpoint %d.\n", + __func__, status, endpoint); + return; + } + + port = urb->context; + if (urb->actual_length) { + /* 0x80 bit is error flag */ + if ((data[0] & 0x80) == 0) { + /* no error on any byte */ + tty_insert_flip_string(&port->port, data + 1, + urb->actual_length - 1); + } else { + /* some bytes had errors, every byte has status */ + for (i = 0; i + 1 < urb->actual_length; i += 2) { + int stat = data[i]; + int flag = TTY_NORMAL; + + if (stat & RXERROR_OVERRUN) { + tty_insert_flip_char(&port->port, 0, + TTY_OVERRUN); + } + /* XXX should handle break (0x10) */ + if (stat & RXERROR_PARITY) + flag = TTY_PARITY; + else if (stat & RXERROR_FRAMING) + flag = TTY_FRAME; + + tty_insert_flip_char(&port->port, data[i+1], + flag); + } + } + tty_flip_buffer_push(&port->port); + } + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +} + +static void usa49wg_indat_callback(struct urb *urb) +{ + int i, len, x, err; + struct usb_serial *serial; + struct usb_serial_port *port; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + serial = urb->context; + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x\n", __func__, status); + return; + } + + /* inbound data is in the form P#, len, status, data */ + i = 0; + len = 0; + + while (i < urb->actual_length) { + + /* Check port number from message */ + if (data[i] >= serial->num_ports) { + dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", + __func__, data[i]); + return; + } + port = serial->port[data[i++]]; + len = data[i++]; + + /* 0x80 bit is error flag */ + if ((data[i] & 0x80) == 0) { + /* no error on any byte */ + i++; + for (x = 1; x < len && i < urb->actual_length; ++x) + tty_insert_flip_char(&port->port, + data[i++], 0); + } else { + /* + * some bytes had errors, every byte has status + */ + for (x = 0; x + 1 < len && + i + 1 < urb->actual_length; x += 2) { + int stat = data[i]; + int flag = TTY_NORMAL; + + if (stat & RXERROR_OVERRUN) { + tty_insert_flip_char(&port->port, 0, + TTY_OVERRUN); + } + /* XXX should handle break (0x10) */ + if (stat & RXERROR_PARITY) + flag = TTY_PARITY; + else if (stat & RXERROR_FRAMING) + flag = TTY_FRAME; + + tty_insert_flip_char(&port->port, data[i+1], + flag); + i += 2; + } + } + tty_flip_buffer_push(&port->port); + } + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&urb->dev->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +} + +/* not used, usa-49 doesn't have per-port control endpoints */ +static void usa49_outcont_callback(struct urb *urb) +{ +} + +static void usa90_indat_callback(struct urb *urb) +{ + int i, err; + int endpoint; + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + endpoint = usb_pipeendpoint(urb->pipe); + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x on endpoint %d.\n", + __func__, status, endpoint); + return; + } + + port = urb->context; + p_priv = usb_get_serial_port_data(port); + + if (urb->actual_length) { + /* if current mode is DMA, looks like usa28 format + otherwise looks like usa26 data format */ + + if (p_priv->baud > 57600) + tty_insert_flip_string(&port->port, data, + urb->actual_length); + else { + /* 0x80 bit is error flag */ + if ((data[0] & 0x80) == 0) { + /* no errors on individual bytes, only + possible overrun err*/ + if (data[0] & RXERROR_OVERRUN) { + tty_insert_flip_char(&port->port, 0, + TTY_OVERRUN); + } + for (i = 1; i < urb->actual_length ; ++i) + tty_insert_flip_char(&port->port, + data[i], TTY_NORMAL); + } else { + /* some bytes had errors, every byte has status */ + dev_dbg(&port->dev, "%s - RX error!!!!\n", __func__); + for (i = 0; i + 1 < urb->actual_length; i += 2) { + int stat = data[i]; + int flag = TTY_NORMAL; + + if (stat & RXERROR_OVERRUN) { + tty_insert_flip_char( + &port->port, 0, + TTY_OVERRUN); + } + /* XXX should handle break (0x10) */ + if (stat & RXERROR_PARITY) + flag = TTY_PARITY; + else if (stat & RXERROR_FRAMING) + flag = TTY_FRAME; + + tty_insert_flip_char(&port->port, + data[i+1], flag); + } + } + } + tty_flip_buffer_push(&port->port); + } + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +} + + +static void usa90_instat_callback(struct urb *urb) +{ + unsigned char *data = urb->transfer_buffer; + struct keyspan_usa90_portStatusMessage *msg; + struct usb_serial *serial; + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + int old_dcd_state, err; + int status = urb->status; + + serial = urb->context; + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x\n", __func__, status); + return; + } + if (urb->actual_length < 14) { + dev_dbg(&urb->dev->dev, "%s - %d byte report??\n", __func__, urb->actual_length); + goto exit; + } + + msg = (struct keyspan_usa90_portStatusMessage *)data; + + /* Now do something useful with the data */ + + port = serial->port[0]; + p_priv = usb_get_serial_port_data(port); + + /* Update handshaking pin state information */ + old_dcd_state = p_priv->dcd_state; + p_priv->cts_state = ((msg->cts) ? 1 : 0); + p_priv->dsr_state = ((msg->dsr) ? 1 : 0); + p_priv->dcd_state = ((msg->dcd) ? 1 : 0); + p_priv->ri_state = ((msg->ri) ? 1 : 0); + + if (old_dcd_state != p_priv->dcd_state && old_dcd_state) + tty_port_tty_hangup(&port->port, true); + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +exit: + ; +} + +static void usa90_outcont_callback(struct urb *urb) +{ + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + + port = urb->context; + p_priv = usb_get_serial_port_data(port); + + if (p_priv->resend_cont) { + dev_dbg(&urb->dev->dev, "%s - sending setup\n", __func__); + keyspan_usa90_send_setup(port->serial, port, + p_priv->resend_cont - 1); + } +} + +/* Status messages from the 28xg */ +static void usa67_instat_callback(struct urb *urb) +{ + int err; + unsigned char *data = urb->transfer_buffer; + struct keyspan_usa67_portStatusMessage *msg; + struct usb_serial *serial; + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + int old_dcd_state; + int status = urb->status; + + serial = urb->context; + + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status: %x\n", __func__, status); + return; + } + + if (urb->actual_length != + sizeof(struct keyspan_usa67_portStatusMessage)) { + dev_dbg(&urb->dev->dev, "%s - bad length %d\n", __func__, urb->actual_length); + return; + } + + + /* Now do something useful with the data */ + msg = (struct keyspan_usa67_portStatusMessage *)data; + + /* Check port number from message and retrieve private data */ + if (msg->port >= serial->num_ports) { + dev_dbg(&urb->dev->dev, "%s - Unexpected port number %d\n", __func__, msg->port); + return; + } + + port = serial->port[msg->port]; + p_priv = usb_get_serial_port_data(port); + + /* Update handshaking pin state information */ + old_dcd_state = p_priv->dcd_state; + p_priv->cts_state = ((msg->hskia_cts) ? 1 : 0); + p_priv->dcd_state = ((msg->gpia_dcd) ? 1 : 0); + + if (old_dcd_state != p_priv->dcd_state && old_dcd_state) + tty_port_tty_hangup(&port->port, true); + + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - resubmit read urb failed. (%d)\n", __func__, err); +} + +static void usa67_glocont_callback(struct urb *urb) +{ + struct usb_serial *serial; + struct usb_serial_port *port; + struct keyspan_port_private *p_priv; + int i; + + serial = urb->context; + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + p_priv = usb_get_serial_port_data(port); + + if (p_priv->resend_cont) { + dev_dbg(&port->dev, "%s - sending setup\n", __func__); + keyspan_usa67_send_setup(serial, port, + p_priv->resend_cont - 1); + break; + } + } +} + +static int keyspan_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + int flip; + int data_len; + struct urb *this_urb; + + p_priv = usb_get_serial_port_data(port); + d_details = p_priv->device_details; + + /* FIXME: locking */ + if (d_details->msg_format == msg_usa90) + data_len = 64; + else + data_len = 63; + + flip = p_priv->out_flip; + + /* Check both endpoints to see if any are available. */ + this_urb = p_priv->out_urbs[flip]; + if (this_urb != NULL) { + if (this_urb->status != -EINPROGRESS) + return data_len; + flip = (flip + 1) & d_details->outdat_endp_flip; + this_urb = p_priv->out_urbs[flip]; + if (this_urb != NULL) { + if (this_urb->status != -EINPROGRESS) + return data_len; + } + } + return 0; +} + + +static int keyspan_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + int i, err; + int baud_rate, device_port; + struct urb *urb; + unsigned int cflag = 0; + + p_priv = usb_get_serial_port_data(port); + d_details = p_priv->device_details; + + /* Set some sane defaults */ + p_priv->rts_state = 1; + p_priv->dtr_state = 1; + p_priv->baud = 9600; + + /* force baud and lcr to be set on open */ + p_priv->old_baud = 0; + p_priv->old_cflag = 0; + + p_priv->out_flip = 0; + p_priv->in_flip = 0; + + /* Reset low level data toggle and start reading from endpoints */ + for (i = 0; i < 2; i++) { + urb = p_priv->in_urbs[i]; + if (urb == NULL) + continue; + + /* make sure endpoint data toggle is synchronized + with the device */ + usb_clear_halt(urb->dev, urb->pipe); + err = usb_submit_urb(urb, GFP_KERNEL); + if (err != 0) + dev_dbg(&port->dev, "%s - submit urb %d failed (%d)\n", __func__, i, err); + } + + /* Reset low level data toggle on out endpoints */ + for (i = 0; i < 2; i++) { + urb = p_priv->out_urbs[i]; + if (urb == NULL) + continue; + /* usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), 0); */ + } + + /* get the terminal config for the setup message now so we don't + * need to send 2 of them */ + + device_port = port->number - port->serial->minor; + if (tty) { + cflag = tty->termios.c_cflag; + /* Baud rate calculation takes baud rate as an integer + so other rates can be generated if desired. */ + baud_rate = tty_get_baud_rate(tty); + /* If no match or invalid, leave as default */ + if (baud_rate >= 0 + && d_details->calculate_baud_rate(port, baud_rate, d_details->baudclk, + NULL, NULL, NULL, device_port) == KEYSPAN_BAUD_RATE_OK) { + p_priv->baud = baud_rate; + } + } + /* set CTS/RTS handshake etc. */ + p_priv->cflag = cflag; + p_priv->flow_control = (cflag & CRTSCTS) ? flow_cts : flow_none; + + keyspan_send_setup(port, 1); + /* mdelay(100); */ + /* keyspan_set_termios(port, NULL); */ + + return 0; +} + +static inline void stop_urb(struct urb *urb) +{ + if (urb && urb->status == -EINPROGRESS) + usb_kill_urb(urb); +} + +static void keyspan_dtr_rts(struct usb_serial_port *port, int on) +{ + struct keyspan_port_private *p_priv = usb_get_serial_port_data(port); + + p_priv->rts_state = on; + p_priv->dtr_state = on; + keyspan_send_setup(port, 0); +} + +static void keyspan_close(struct usb_serial_port *port) +{ + int i; + struct keyspan_port_private *p_priv; + + p_priv = usb_get_serial_port_data(port); + + p_priv->rts_state = 0; + p_priv->dtr_state = 0; + + keyspan_send_setup(port, 2); + /* pilot-xfer seems to work best with this delay */ + mdelay(100); + + p_priv->out_flip = 0; + p_priv->in_flip = 0; + + stop_urb(p_priv->inack_urb); + for (i = 0; i < 2; i++) { + stop_urb(p_priv->in_urbs[i]); + stop_urb(p_priv->out_urbs[i]); + } +} + +/* download the firmware to a pre-renumeration device */ +static int keyspan_fake_startup(struct usb_serial *serial) +{ + char *fw_name; + + dev_dbg(&serial->dev->dev, "Keyspan startup version %04x product %04x\n", + le16_to_cpu(serial->dev->descriptor.bcdDevice), + le16_to_cpu(serial->dev->descriptor.idProduct)); + + if ((le16_to_cpu(serial->dev->descriptor.bcdDevice) & 0x8000) + != 0x8000) { + dev_dbg(&serial->dev->dev, "Firmware already loaded. Quitting.\n"); + return 1; + } + + /* Select firmware image on the basis of idProduct */ + switch (le16_to_cpu(serial->dev->descriptor.idProduct)) { + case keyspan_usa28_pre_product_id: + fw_name = "keyspan/usa28.fw"; + break; + + case keyspan_usa28x_pre_product_id: + fw_name = "keyspan/usa28x.fw"; + break; + + case keyspan_usa28xa_pre_product_id: + fw_name = "keyspan/usa28xa.fw"; + break; + + case keyspan_usa28xb_pre_product_id: + fw_name = "keyspan/usa28xb.fw"; + break; + + case keyspan_usa19_pre_product_id: + fw_name = "keyspan/usa19.fw"; + break; + + case keyspan_usa19qi_pre_product_id: + fw_name = "keyspan/usa19qi.fw"; + break; + + case keyspan_mpr_pre_product_id: + fw_name = "keyspan/mpr.fw"; + break; + + case keyspan_usa19qw_pre_product_id: + fw_name = "keyspan/usa19qw.fw"; + break; + + case keyspan_usa18x_pre_product_id: + fw_name = "keyspan/usa18x.fw"; + break; + + case keyspan_usa19w_pre_product_id: + fw_name = "keyspan/usa19w.fw"; + break; + + case keyspan_usa49w_pre_product_id: + fw_name = "keyspan/usa49w.fw"; + break; + + case keyspan_usa49wlc_pre_product_id: + fw_name = "keyspan/usa49wlc.fw"; + break; + + default: + dev_err(&serial->dev->dev, "Unknown product ID (%04x)\n", + le16_to_cpu(serial->dev->descriptor.idProduct)); + return 1; + } + + dev_dbg(&serial->dev->dev, "Uploading Keyspan %s firmware.\n", fw_name); + + if (ezusb_fx1_ihex_firmware_download(serial->dev, fw_name) < 0) { + dev_err(&serial->dev->dev, "failed to load firmware \"%s\"\n", + fw_name); + return -ENOENT; + } + + /* after downloading firmware Renumeration will occur in a + moment and the new device will bind to the real driver */ + + /* we don't want this device to have a driver assigned to it. */ + return 1; +} + +/* Helper functions used by keyspan_setup_urbs */ +static struct usb_endpoint_descriptor const *find_ep(struct usb_serial const *serial, + int endpoint) +{ + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *ep; + int i; + + iface_desc = serial->interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + ep = &iface_desc->endpoint[i].desc; + if (ep->bEndpointAddress == endpoint) + return ep; + } + dev_warn(&serial->interface->dev, "found no endpoint descriptor for " + "endpoint %x\n", endpoint); + return NULL; +} + +static struct urb *keyspan_setup_urb(struct usb_serial *serial, int endpoint, + int dir, void *ctx, char *buf, int len, + void (*callback)(struct urb *)) +{ + struct urb *urb; + struct usb_endpoint_descriptor const *ep_desc; + char const *ep_type_name; + + if (endpoint == -1) + return NULL; /* endpoint not needed */ + + dev_dbg(&serial->interface->dev, "%s - alloc for endpoint %d.\n", __func__, endpoint); + urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */ + if (urb == NULL) { + dev_dbg(&serial->interface->dev, "%s - alloc for endpoint %d failed.\n", __func__, endpoint); + return NULL; + } + + if (endpoint == 0) { + /* control EP filled in when used */ + return urb; + } + + ep_desc = find_ep(serial, endpoint); + if (!ep_desc) { + /* leak the urb, something's wrong and the callers don't care */ + return urb; + } + if (usb_endpoint_xfer_int(ep_desc)) { + ep_type_name = "INT"; + usb_fill_int_urb(urb, serial->dev, + usb_sndintpipe(serial->dev, endpoint) | dir, + buf, len, callback, ctx, + ep_desc->bInterval); + } else if (usb_endpoint_xfer_bulk(ep_desc)) { + ep_type_name = "BULK"; + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, endpoint) | dir, + buf, len, callback, ctx); + } else { + dev_warn(&serial->interface->dev, + "unsupported endpoint type %x\n", + usb_endpoint_type(ep_desc)); + usb_free_urb(urb); + return NULL; + } + + dev_dbg(&serial->interface->dev, "%s - using urb %p for %s endpoint %x\n", + __func__, urb, ep_type_name, endpoint); + return urb; +} + +static struct callbacks { + void (*instat_callback)(struct urb *); + void (*glocont_callback)(struct urb *); + void (*indat_callback)(struct urb *); + void (*outdat_callback)(struct urb *); + void (*inack_callback)(struct urb *); + void (*outcont_callback)(struct urb *); +} keyspan_callbacks[] = { + { + /* msg_usa26 callbacks */ + .instat_callback = usa26_instat_callback, + .glocont_callback = usa26_glocont_callback, + .indat_callback = usa26_indat_callback, + .outdat_callback = usa2x_outdat_callback, + .inack_callback = usa26_inack_callback, + .outcont_callback = usa26_outcont_callback, + }, { + /* msg_usa28 callbacks */ + .instat_callback = usa28_instat_callback, + .glocont_callback = usa28_glocont_callback, + .indat_callback = usa28_indat_callback, + .outdat_callback = usa2x_outdat_callback, + .inack_callback = usa28_inack_callback, + .outcont_callback = usa28_outcont_callback, + }, { + /* msg_usa49 callbacks */ + .instat_callback = usa49_instat_callback, + .glocont_callback = usa49_glocont_callback, + .indat_callback = usa49_indat_callback, + .outdat_callback = usa2x_outdat_callback, + .inack_callback = usa49_inack_callback, + .outcont_callback = usa49_outcont_callback, + }, { + /* msg_usa90 callbacks */ + .instat_callback = usa90_instat_callback, + .glocont_callback = usa28_glocont_callback, + .indat_callback = usa90_indat_callback, + .outdat_callback = usa2x_outdat_callback, + .inack_callback = usa28_inack_callback, + .outcont_callback = usa90_outcont_callback, + }, { + /* msg_usa67 callbacks */ + .instat_callback = usa67_instat_callback, + .glocont_callback = usa67_glocont_callback, + .indat_callback = usa26_indat_callback, + .outdat_callback = usa2x_outdat_callback, + .inack_callback = usa26_inack_callback, + .outcont_callback = usa26_outcont_callback, + } +}; + + /* Generic setup urbs function that uses + data in device_details */ +static void keyspan_setup_urbs(struct usb_serial *serial) +{ + struct keyspan_serial_private *s_priv; + const struct keyspan_device_details *d_details; + struct callbacks *cback; + + s_priv = usb_get_serial_data(serial); + d_details = s_priv->device_details; + + /* Setup values for the various callback routines */ + cback = &keyspan_callbacks[d_details->msg_format]; + + /* Allocate and set up urbs for each one that is in use, + starting with instat endpoints */ + s_priv->instat_urb = keyspan_setup_urb + (serial, d_details->instat_endpoint, USB_DIR_IN, + serial, s_priv->instat_buf, INSTAT_BUFLEN, + cback->instat_callback); + + s_priv->indat_urb = keyspan_setup_urb + (serial, d_details->indat_endpoint, USB_DIR_IN, + serial, s_priv->indat_buf, INDAT49W_BUFLEN, + usa49wg_indat_callback); + + s_priv->glocont_urb = keyspan_setup_urb + (serial, d_details->glocont_endpoint, USB_DIR_OUT, + serial, s_priv->glocont_buf, GLOCONT_BUFLEN, + cback->glocont_callback); +} + +/* usa19 function doesn't require prescaler */ +static int keyspan_usa19_calc_baud(struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, u8 *rate_hi, + u8 *rate_low, u8 *prescaler, int portnum) +{ + u32 b16, /* baud rate times 16 (actual rate used internally) */ + div, /* divisor */ + cnt; /* inverse of divisor (programmed into 8051) */ + + dev_dbg(&port->dev, "%s - %d.\n", __func__, baud_rate); + + /* prevent divide by zero... */ + b16 = baud_rate * 16L; + if (b16 == 0) + return KEYSPAN_INVALID_BAUD_RATE; + /* Any "standard" rate over 57k6 is marginal on the USA-19 + as we run out of divisor resolution. */ + if (baud_rate > 57600) + return KEYSPAN_INVALID_BAUD_RATE; + + /* calculate the divisor and the counter (its inverse) */ + div = baudclk / b16; + if (div == 0) + return KEYSPAN_INVALID_BAUD_RATE; + else + cnt = 0 - div; + + if (div > 0xffff) + return KEYSPAN_INVALID_BAUD_RATE; + + /* return the counter values if non-null */ + if (rate_low) + *rate_low = (u8) (cnt & 0xff); + if (rate_hi) + *rate_hi = (u8) ((cnt >> 8) & 0xff); + if (rate_low && rate_hi) + dev_dbg(&port->dev, "%s - %d %02x %02x.\n", + __func__, baud_rate, *rate_hi, *rate_low); + return KEYSPAN_BAUD_RATE_OK; +} + +/* usa19hs function doesn't require prescaler */ +static int keyspan_usa19hs_calc_baud(struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, u8 *rate_hi, + u8 *rate_low, u8 *prescaler, int portnum) +{ + u32 b16, /* baud rate times 16 (actual rate used internally) */ + div; /* divisor */ + + dev_dbg(&port->dev, "%s - %d.\n", __func__, baud_rate); + + /* prevent divide by zero... */ + b16 = baud_rate * 16L; + if (b16 == 0) + return KEYSPAN_INVALID_BAUD_RATE; + + /* calculate the divisor */ + div = baudclk / b16; + if (div == 0) + return KEYSPAN_INVALID_BAUD_RATE; + + if (div > 0xffff) + return KEYSPAN_INVALID_BAUD_RATE; + + /* return the counter values if non-null */ + if (rate_low) + *rate_low = (u8) (div & 0xff); + + if (rate_hi) + *rate_hi = (u8) ((div >> 8) & 0xff); + + if (rate_low && rate_hi) + dev_dbg(&port->dev, "%s - %d %02x %02x.\n", + __func__, baud_rate, *rate_hi, *rate_low); + + return KEYSPAN_BAUD_RATE_OK; +} + +static int keyspan_usa19w_calc_baud(struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, u8 *rate_hi, + u8 *rate_low, u8 *prescaler, int portnum) +{ + u32 b16, /* baud rate times 16 (actual rate used internally) */ + clk, /* clock with 13/8 prescaler */ + div, /* divisor using 13/8 prescaler */ + res, /* resulting baud rate using 13/8 prescaler */ + diff, /* error using 13/8 prescaler */ + smallest_diff; + u8 best_prescaler; + int i; + + dev_dbg(&port->dev, "%s - %d.\n", __func__, baud_rate); + + /* prevent divide by zero */ + b16 = baud_rate * 16L; + if (b16 == 0) + return KEYSPAN_INVALID_BAUD_RATE; + + /* Calculate prescaler by trying them all and looking + for best fit */ + + /* start with largest possible difference */ + smallest_diff = 0xffffffff; + + /* 0 is an invalid prescaler, used as a flag */ + best_prescaler = 0; + + for (i = 8; i <= 0xff; ++i) { + clk = (baudclk * 8) / (u32) i; + + div = clk / b16; + if (div == 0) + continue; + + res = clk / div; + diff = (res > b16) ? (res-b16) : (b16-res); + + if (diff < smallest_diff) { + best_prescaler = i; + smallest_diff = diff; + } + } + + if (best_prescaler == 0) + return KEYSPAN_INVALID_BAUD_RATE; + + clk = (baudclk * 8) / (u32) best_prescaler; + div = clk / b16; + + /* return the divisor and prescaler if non-null */ + if (rate_low) + *rate_low = (u8) (div & 0xff); + if (rate_hi) + *rate_hi = (u8) ((div >> 8) & 0xff); + if (prescaler) { + *prescaler = best_prescaler; + /* dev_dbg(&port->dev, "%s - %d %d\n", __func__, *prescaler, div); */ + } + return KEYSPAN_BAUD_RATE_OK; +} + + /* USA-28 supports different maximum baud rates on each port */ +static int keyspan_usa28_calc_baud(struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, u8 *rate_hi, + u8 *rate_low, u8 *prescaler, int portnum) +{ + u32 b16, /* baud rate times 16 (actual rate used internally) */ + div, /* divisor */ + cnt; /* inverse of divisor (programmed into 8051) */ + + dev_dbg(&port->dev, "%s - %d.\n", __func__, baud_rate); + + /* prevent divide by zero */ + b16 = baud_rate * 16L; + if (b16 == 0) + return KEYSPAN_INVALID_BAUD_RATE; + + /* calculate the divisor and the counter (its inverse) */ + div = KEYSPAN_USA28_BAUDCLK / b16; + if (div == 0) + return KEYSPAN_INVALID_BAUD_RATE; + else + cnt = 0 - div; + + /* check for out of range, based on portnum, + and return result */ + if (portnum == 0) { + if (div > 0xffff) + return KEYSPAN_INVALID_BAUD_RATE; + } else { + if (portnum == 1) { + if (div > 0xff) + return KEYSPAN_INVALID_BAUD_RATE; + } else + return KEYSPAN_INVALID_BAUD_RATE; + } + + /* return the counter values if not NULL + (port 1 will ignore retHi) */ + if (rate_low) + *rate_low = (u8) (cnt & 0xff); + if (rate_hi) + *rate_hi = (u8) ((cnt >> 8) & 0xff); + dev_dbg(&port->dev, "%s - %d OK.\n", __func__, baud_rate); + return KEYSPAN_BAUD_RATE_OK; +} + +static int keyspan_usa26_send_setup(struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port) +{ + struct keyspan_usa26_portControlMessage msg; + struct keyspan_serial_private *s_priv; + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + struct urb *this_urb; + int device_port, err; + + dev_dbg(&port->dev, "%s reset=%d\n", __func__, reset_port); + + s_priv = usb_get_serial_data(serial); + p_priv = usb_get_serial_port_data(port); + d_details = s_priv->device_details; + device_port = port->number - port->serial->minor; + + this_urb = p_priv->outcont_urb; + + dev_dbg(&port->dev, "%s - endpoint %d\n", __func__, usb_pipeendpoint(this_urb->pipe)); + + /* Make sure we have an urb then send the message */ + if (this_urb == NULL) { + dev_dbg(&port->dev, "%s - oops no urb.\n", __func__); + return -1; + } + + /* Save reset port val for resend. + Don't overwrite resend for open/close condition. */ + if ((reset_port + 1) > p_priv->resend_cont) + p_priv->resend_cont = reset_port + 1; + if (this_urb->status == -EINPROGRESS) { + /* dev_dbg(&port->dev, "%s - already writing\n", __func__); */ + mdelay(5); + return -1; + } + + memset(&msg, 0, sizeof(struct keyspan_usa26_portControlMessage)); + + /* Only set baud rate if it's changed */ + if (p_priv->old_baud != p_priv->baud) { + p_priv->old_baud = p_priv->baud; + msg.setClocking = 0xff; + if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk, + &msg.baudHi, &msg.baudLo, &msg.prescaler, + device_port) == KEYSPAN_INVALID_BAUD_RATE) { + dev_dbg(&port->dev, "%s - Invalid baud rate %d requested, using 9600.\n", + __func__, p_priv->baud); + msg.baudLo = 0; + msg.baudHi = 125; /* Values for 9600 baud */ + msg.prescaler = 10; + } + msg.setPrescaler = 0xff; + } + + msg.lcr = (p_priv->cflag & CSTOPB) ? STOPBITS_678_2 : STOPBITS_5678_1; + switch (p_priv->cflag & CSIZE) { + case CS5: + msg.lcr |= USA_DATABITS_5; + break; + case CS6: + msg.lcr |= USA_DATABITS_6; + break; + case CS7: + msg.lcr |= USA_DATABITS_7; + break; + case CS8: + msg.lcr |= USA_DATABITS_8; + break; + } + if (p_priv->cflag & PARENB) { + /* note USA_PARITY_NONE == 0 */ + msg.lcr |= (p_priv->cflag & PARODD) ? + USA_PARITY_ODD : USA_PARITY_EVEN; + } + msg.setLcr = 0xff; + + msg.ctsFlowControl = (p_priv->flow_control == flow_cts); + msg.xonFlowControl = 0; + msg.setFlowControl = 0xff; + msg.forwardingLength = 16; + msg.xonChar = 17; + msg.xoffChar = 19; + + /* Opening port */ + if (reset_port == 1) { + msg._txOn = 1; + msg._txOff = 0; + msg.txFlush = 0; + msg.txBreak = 0; + msg.rxOn = 1; + msg.rxOff = 0; + msg.rxFlush = 1; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0xff; + } + + /* Closing port */ + else if (reset_port == 2) { + msg._txOn = 0; + msg._txOff = 1; + msg.txFlush = 0; + msg.txBreak = 0; + msg.rxOn = 0; + msg.rxOff = 1; + msg.rxFlush = 1; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0; + } + + /* Sending intermediate configs */ + else { + msg._txOn = (!p_priv->break_on); + msg._txOff = 0; + msg.txFlush = 0; + msg.txBreak = (p_priv->break_on); + msg.rxOn = 0; + msg.rxOff = 0; + msg.rxFlush = 0; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0x0; + } + + /* Do handshaking outputs */ + msg.setTxTriState_setRts = 0xff; + msg.txTriState_rts = p_priv->rts_state; + + msg.setHskoa_setDtr = 0xff; + msg.hskoa_dtr = p_priv->dtr_state; + + p_priv->resend_cont = 0; + memcpy(this_urb->transfer_buffer, &msg, sizeof(msg)); + + /* send the data out the device on control endpoint */ + this_urb->transfer_buffer_length = sizeof(msg); + + err = usb_submit_urb(this_urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err); + return 0; +} + +static int keyspan_usa28_send_setup(struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port) +{ + struct keyspan_usa28_portControlMessage msg; + struct keyspan_serial_private *s_priv; + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + struct urb *this_urb; + int device_port, err; + + s_priv = usb_get_serial_data(serial); + p_priv = usb_get_serial_port_data(port); + d_details = s_priv->device_details; + device_port = port->number - port->serial->minor; + + /* only do something if we have a bulk out endpoint */ + this_urb = p_priv->outcont_urb; + if (this_urb == NULL) { + dev_dbg(&port->dev, "%s - oops no urb.\n", __func__); + return -1; + } + + /* Save reset port val for resend. + Don't overwrite resend for open/close condition. */ + if ((reset_port + 1) > p_priv->resend_cont) + p_priv->resend_cont = reset_port + 1; + if (this_urb->status == -EINPROGRESS) { + dev_dbg(&port->dev, "%s already writing\n", __func__); + mdelay(5); + return -1; + } + + memset(&msg, 0, sizeof(struct keyspan_usa28_portControlMessage)); + + msg.setBaudRate = 1; + if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk, + &msg.baudHi, &msg.baudLo, NULL, + device_port) == KEYSPAN_INVALID_BAUD_RATE) { + dev_dbg(&port->dev, "%s - Invalid baud rate requested %d.\n", + __func__, p_priv->baud); + msg.baudLo = 0xff; + msg.baudHi = 0xb2; /* Values for 9600 baud */ + } + + /* If parity is enabled, we must calculate it ourselves. */ + msg.parity = 0; /* XXX for now */ + + msg.ctsFlowControl = (p_priv->flow_control == flow_cts); + msg.xonFlowControl = 0; + + /* Do handshaking outputs, DTR is inverted relative to RTS */ + msg.rts = p_priv->rts_state; + msg.dtr = p_priv->dtr_state; + + msg.forwardingLength = 16; + msg.forwardMs = 10; + msg.breakThreshold = 45; + msg.xonChar = 17; + msg.xoffChar = 19; + + /*msg.returnStatus = 1; + msg.resetDataToggle = 0xff;*/ + /* Opening port */ + if (reset_port == 1) { + msg._txOn = 1; + msg._txOff = 0; + msg.txFlush = 0; + msg.txForceXoff = 0; + msg.txBreak = 0; + msg.rxOn = 1; + msg.rxOff = 0; + msg.rxFlush = 1; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0xff; + } + /* Closing port */ + else if (reset_port == 2) { + msg._txOn = 0; + msg._txOff = 1; + msg.txFlush = 0; + msg.txForceXoff = 0; + msg.txBreak = 0; + msg.rxOn = 0; + msg.rxOff = 1; + msg.rxFlush = 1; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0; + } + /* Sending intermediate configs */ + else { + msg._txOn = (!p_priv->break_on); + msg._txOff = 0; + msg.txFlush = 0; + msg.txForceXoff = 0; + msg.txBreak = (p_priv->break_on); + msg.rxOn = 0; + msg.rxOff = 0; + msg.rxFlush = 0; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0x0; + } + + p_priv->resend_cont = 0; + memcpy(this_urb->transfer_buffer, &msg, sizeof(msg)); + + /* send the data out the device on control endpoint */ + this_urb->transfer_buffer_length = sizeof(msg); + + err = usb_submit_urb(this_urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed\n", __func__); +#if 0 + else { + dev_dbg(&port->dev, "%s - usb_submit_urb(setup) OK %d bytes\n", __func__, + this_urb->transfer_buffer_length); + } +#endif + + return 0; +} + +static int keyspan_usa49_send_setup(struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port) +{ + struct keyspan_usa49_portControlMessage msg; + struct usb_ctrlrequest *dr = NULL; + struct keyspan_serial_private *s_priv; + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + struct urb *this_urb; + int err, device_port; + + s_priv = usb_get_serial_data(serial); + p_priv = usb_get_serial_port_data(port); + d_details = s_priv->device_details; + + this_urb = s_priv->glocont_urb; + + /* Work out which port within the device is being setup */ + device_port = port->number - port->serial->minor; + + /* Make sure we have an urb then send the message */ + if (this_urb == NULL) { + dev_dbg(&port->dev, "%s - oops no urb for port %d.\n", __func__, port->number); + return -1; + } + + dev_dbg(&port->dev, "%s - endpoint %d port %d (%d)\n", + __func__, usb_pipeendpoint(this_urb->pipe), + port->number, device_port); + + /* Save reset port val for resend. + Don't overwrite resend for open/close condition. */ + if ((reset_port + 1) > p_priv->resend_cont) + p_priv->resend_cont = reset_port + 1; + + if (this_urb->status == -EINPROGRESS) { + /* dev_dbg(&port->dev, "%s - already writing\n", __func__); */ + mdelay(5); + return -1; + } + + memset(&msg, 0, sizeof(struct keyspan_usa49_portControlMessage)); + + /*msg.portNumber = port->number;*/ + msg.portNumber = device_port; + + /* Only set baud rate if it's changed */ + if (p_priv->old_baud != p_priv->baud) { + p_priv->old_baud = p_priv->baud; + msg.setClocking = 0xff; + if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk, + &msg.baudHi, &msg.baudLo, &msg.prescaler, + device_port) == KEYSPAN_INVALID_BAUD_RATE) { + dev_dbg(&port->dev, "%s - Invalid baud rate %d requested, using 9600.\n", + __func__, p_priv->baud); + msg.baudLo = 0; + msg.baudHi = 125; /* Values for 9600 baud */ + msg.prescaler = 10; + } + /* msg.setPrescaler = 0xff; */ + } + + msg.lcr = (p_priv->cflag & CSTOPB) ? STOPBITS_678_2 : STOPBITS_5678_1; + switch (p_priv->cflag & CSIZE) { + case CS5: + msg.lcr |= USA_DATABITS_5; + break; + case CS6: + msg.lcr |= USA_DATABITS_6; + break; + case CS7: + msg.lcr |= USA_DATABITS_7; + break; + case CS8: + msg.lcr |= USA_DATABITS_8; + break; + } + if (p_priv->cflag & PARENB) { + /* note USA_PARITY_NONE == 0 */ + msg.lcr |= (p_priv->cflag & PARODD) ? + USA_PARITY_ODD : USA_PARITY_EVEN; + } + msg.setLcr = 0xff; + + msg.ctsFlowControl = (p_priv->flow_control == flow_cts); + msg.xonFlowControl = 0; + msg.setFlowControl = 0xff; + + msg.forwardingLength = 16; + msg.xonChar = 17; + msg.xoffChar = 19; + + /* Opening port */ + if (reset_port == 1) { + msg._txOn = 1; + msg._txOff = 0; + msg.txFlush = 0; + msg.txBreak = 0; + msg.rxOn = 1; + msg.rxOff = 0; + msg.rxFlush = 1; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0xff; + msg.enablePort = 1; + msg.disablePort = 0; + } + /* Closing port */ + else if (reset_port == 2) { + msg._txOn = 0; + msg._txOff = 1; + msg.txFlush = 0; + msg.txBreak = 0; + msg.rxOn = 0; + msg.rxOff = 1; + msg.rxFlush = 1; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0; + msg.enablePort = 0; + msg.disablePort = 1; + } + /* Sending intermediate configs */ + else { + msg._txOn = (!p_priv->break_on); + msg._txOff = 0; + msg.txFlush = 0; + msg.txBreak = (p_priv->break_on); + msg.rxOn = 0; + msg.rxOff = 0; + msg.rxFlush = 0; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0x0; + msg.enablePort = 0; + msg.disablePort = 0; + } + + /* Do handshaking outputs */ + msg.setRts = 0xff; + msg.rts = p_priv->rts_state; + + msg.setDtr = 0xff; + msg.dtr = p_priv->dtr_state; + + p_priv->resend_cont = 0; + + /* if the device is a 49wg, we send control message on usb + control EP 0 */ + + if (d_details->product_id == keyspan_usa49wg_product_id) { + dr = (void *)(s_priv->ctrl_buf); + dr->bRequestType = USB_TYPE_VENDOR | USB_DIR_OUT; + dr->bRequest = 0xB0; /* 49wg control message */; + dr->wValue = 0; + dr->wIndex = 0; + dr->wLength = cpu_to_le16(sizeof(msg)); + + memcpy(s_priv->glocont_buf, &msg, sizeof(msg)); + + usb_fill_control_urb(this_urb, serial->dev, + usb_sndctrlpipe(serial->dev, 0), + (unsigned char *)dr, s_priv->glocont_buf, + sizeof(msg), usa49_glocont_callback, serial); + + } else { + memcpy(this_urb->transfer_buffer, &msg, sizeof(msg)); + + /* send the data out the device on control endpoint */ + this_urb->transfer_buffer_length = sizeof(msg); + } + err = usb_submit_urb(this_urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err); +#if 0 + else { + dev_dbg(&port->dev, "%s - usb_submit_urb(%d) OK %d bytes (end %d)\n", __func__, + outcont_urb, this_urb->transfer_buffer_length, + usb_pipeendpoint(this_urb->pipe)); + } +#endif + + return 0; +} + +static int keyspan_usa90_send_setup(struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port) +{ + struct keyspan_usa90_portControlMessage msg; + struct keyspan_serial_private *s_priv; + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + struct urb *this_urb; + int err; + u8 prescaler; + + s_priv = usb_get_serial_data(serial); + p_priv = usb_get_serial_port_data(port); + d_details = s_priv->device_details; + + /* only do something if we have a bulk out endpoint */ + this_urb = p_priv->outcont_urb; + if (this_urb == NULL) { + dev_dbg(&port->dev, "%s - oops no urb.\n", __func__); + return -1; + } + + /* Save reset port val for resend. + Don't overwrite resend for open/close condition. */ + if ((reset_port + 1) > p_priv->resend_cont) + p_priv->resend_cont = reset_port + 1; + if (this_urb->status == -EINPROGRESS) { + dev_dbg(&port->dev, "%s already writing\n", __func__); + mdelay(5); + return -1; + } + + memset(&msg, 0, sizeof(struct keyspan_usa90_portControlMessage)); + + /* Only set baud rate if it's changed */ + if (p_priv->old_baud != p_priv->baud) { + p_priv->old_baud = p_priv->baud; + msg.setClocking = 0x01; + if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk, + &msg.baudHi, &msg.baudLo, &prescaler, 0) == KEYSPAN_INVALID_BAUD_RATE) { + dev_dbg(&port->dev, "%s - Invalid baud rate %d requested, using 9600.\n", + __func__, p_priv->baud); + p_priv->baud = 9600; + d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk, + &msg.baudHi, &msg.baudLo, &prescaler, 0); + } + msg.setRxMode = 1; + msg.setTxMode = 1; + } + + /* modes must always be correctly specified */ + if (p_priv->baud > 57600) { + msg.rxMode = RXMODE_DMA; + msg.txMode = TXMODE_DMA; + } else { + msg.rxMode = RXMODE_BYHAND; + msg.txMode = TXMODE_BYHAND; + } + + msg.lcr = (p_priv->cflag & CSTOPB) ? STOPBITS_678_2 : STOPBITS_5678_1; + switch (p_priv->cflag & CSIZE) { + case CS5: + msg.lcr |= USA_DATABITS_5; + break; + case CS6: + msg.lcr |= USA_DATABITS_6; + break; + case CS7: + msg.lcr |= USA_DATABITS_7; + break; + case CS8: + msg.lcr |= USA_DATABITS_8; + break; + } + if (p_priv->cflag & PARENB) { + /* note USA_PARITY_NONE == 0 */ + msg.lcr |= (p_priv->cflag & PARODD) ? + USA_PARITY_ODD : USA_PARITY_EVEN; + } + if (p_priv->old_cflag != p_priv->cflag) { + p_priv->old_cflag = p_priv->cflag; + msg.setLcr = 0x01; + } + + if (p_priv->flow_control == flow_cts) + msg.txFlowControl = TXFLOW_CTS; + msg.setTxFlowControl = 0x01; + msg.setRxFlowControl = 0x01; + + msg.rxForwardingLength = 16; + msg.rxForwardingTimeout = 16; + msg.txAckSetting = 0; + msg.xonChar = 17; + msg.xoffChar = 19; + + /* Opening port */ + if (reset_port == 1) { + msg.portEnabled = 1; + msg.rxFlush = 1; + msg.txBreak = (p_priv->break_on); + } + /* Closing port */ + else if (reset_port == 2) + msg.portEnabled = 0; + /* Sending intermediate configs */ + else { + msg.portEnabled = 1; + msg.txBreak = (p_priv->break_on); + } + + /* Do handshaking outputs */ + msg.setRts = 0x01; + msg.rts = p_priv->rts_state; + + msg.setDtr = 0x01; + msg.dtr = p_priv->dtr_state; + + p_priv->resend_cont = 0; + memcpy(this_urb->transfer_buffer, &msg, sizeof(msg)); + + /* send the data out the device on control endpoint */ + this_urb->transfer_buffer_length = sizeof(msg); + + err = usb_submit_urb(this_urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err); + return 0; +} + +static int keyspan_usa67_send_setup(struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port) +{ + struct keyspan_usa67_portControlMessage msg; + struct keyspan_serial_private *s_priv; + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + struct urb *this_urb; + int err, device_port; + + s_priv = usb_get_serial_data(serial); + p_priv = usb_get_serial_port_data(port); + d_details = s_priv->device_details; + + this_urb = s_priv->glocont_urb; + + /* Work out which port within the device is being setup */ + device_port = port->number - port->serial->minor; + + /* Make sure we have an urb then send the message */ + if (this_urb == NULL) { + dev_dbg(&port->dev, "%s - oops no urb for port %d.\n", __func__, + port->number); + return -1; + } + + /* Save reset port val for resend. + Don't overwrite resend for open/close condition. */ + if ((reset_port + 1) > p_priv->resend_cont) + p_priv->resend_cont = reset_port + 1; + if (this_urb->status == -EINPROGRESS) { + /* dev_dbg(&port->dev, "%s - already writing\n", __func__); */ + mdelay(5); + return -1; + } + + memset(&msg, 0, sizeof(struct keyspan_usa67_portControlMessage)); + + msg.port = device_port; + + /* Only set baud rate if it's changed */ + if (p_priv->old_baud != p_priv->baud) { + p_priv->old_baud = p_priv->baud; + msg.setClocking = 0xff; + if (d_details->calculate_baud_rate(port, p_priv->baud, d_details->baudclk, + &msg.baudHi, &msg.baudLo, &msg.prescaler, + device_port) == KEYSPAN_INVALID_BAUD_RATE) { + dev_dbg(&port->dev, "%s - Invalid baud rate %d requested, using 9600.\n", + __func__, p_priv->baud); + msg.baudLo = 0; + msg.baudHi = 125; /* Values for 9600 baud */ + msg.prescaler = 10; + } + msg.setPrescaler = 0xff; + } + + msg.lcr = (p_priv->cflag & CSTOPB) ? STOPBITS_678_2 : STOPBITS_5678_1; + switch (p_priv->cflag & CSIZE) { + case CS5: + msg.lcr |= USA_DATABITS_5; + break; + case CS6: + msg.lcr |= USA_DATABITS_6; + break; + case CS7: + msg.lcr |= USA_DATABITS_7; + break; + case CS8: + msg.lcr |= USA_DATABITS_8; + break; + } + if (p_priv->cflag & PARENB) { + /* note USA_PARITY_NONE == 0 */ + msg.lcr |= (p_priv->cflag & PARODD) ? + USA_PARITY_ODD : USA_PARITY_EVEN; + } + msg.setLcr = 0xff; + + msg.ctsFlowControl = (p_priv->flow_control == flow_cts); + msg.xonFlowControl = 0; + msg.setFlowControl = 0xff; + msg.forwardingLength = 16; + msg.xonChar = 17; + msg.xoffChar = 19; + + if (reset_port == 1) { + /* Opening port */ + msg._txOn = 1; + msg._txOff = 0; + msg.txFlush = 0; + msg.txBreak = 0; + msg.rxOn = 1; + msg.rxOff = 0; + msg.rxFlush = 1; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0xff; + } else if (reset_port == 2) { + /* Closing port */ + msg._txOn = 0; + msg._txOff = 1; + msg.txFlush = 0; + msg.txBreak = 0; + msg.rxOn = 0; + msg.rxOff = 1; + msg.rxFlush = 1; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0; + } else { + /* Sending intermediate configs */ + msg._txOn = (!p_priv->break_on); + msg._txOff = 0; + msg.txFlush = 0; + msg.txBreak = (p_priv->break_on); + msg.rxOn = 0; + msg.rxOff = 0; + msg.rxFlush = 0; + msg.rxForward = 0; + msg.returnStatus = 0; + msg.resetDataToggle = 0x0; + } + + /* Do handshaking outputs */ + msg.setTxTriState_setRts = 0xff; + msg.txTriState_rts = p_priv->rts_state; + + msg.setHskoa_setDtr = 0xff; + msg.hskoa_dtr = p_priv->dtr_state; + + p_priv->resend_cont = 0; + + memcpy(this_urb->transfer_buffer, &msg, sizeof(msg)); + + /* send the data out the device on control endpoint */ + this_urb->transfer_buffer_length = sizeof(msg); + + err = usb_submit_urb(this_urb, GFP_ATOMIC); + if (err != 0) + dev_dbg(&port->dev, "%s - usb_submit_urb(setup) failed (%d)\n", __func__, err); + return 0; +} + +static void keyspan_send_setup(struct usb_serial_port *port, int reset_port) +{ + struct usb_serial *serial = port->serial; + struct keyspan_serial_private *s_priv; + const struct keyspan_device_details *d_details; + + s_priv = usb_get_serial_data(serial); + d_details = s_priv->device_details; + + switch (d_details->msg_format) { + case msg_usa26: + keyspan_usa26_send_setup(serial, port, reset_port); + break; + case msg_usa28: + keyspan_usa28_send_setup(serial, port, reset_port); + break; + case msg_usa49: + keyspan_usa49_send_setup(serial, port, reset_port); + break; + case msg_usa90: + keyspan_usa90_send_setup(serial, port, reset_port); + break; + case msg_usa67: + keyspan_usa67_send_setup(serial, port, reset_port); + break; + } +} + + +/* Gets called by the "real" driver (ie once firmware is loaded + and renumeration has taken place. */ +static int keyspan_startup(struct usb_serial *serial) +{ + int i, err; + struct keyspan_serial_private *s_priv; + const struct keyspan_device_details *d_details; + + for (i = 0; (d_details = keyspan_devices[i]) != NULL; ++i) + if (d_details->product_id == + le16_to_cpu(serial->dev->descriptor.idProduct)) + break; + if (d_details == NULL) { + dev_err(&serial->dev->dev, "%s - unknown product id %x\n", + __func__, le16_to_cpu(serial->dev->descriptor.idProduct)); + return -ENODEV; + } + + /* Setup private data for serial driver */ + s_priv = kzalloc(sizeof(struct keyspan_serial_private), GFP_KERNEL); + if (!s_priv) { + dev_dbg(&serial->dev->dev, "%s - kmalloc for keyspan_serial_private failed.\n", __func__); + return -ENOMEM; + } + + s_priv->device_details = d_details; + usb_set_serial_data(serial, s_priv); + + keyspan_setup_urbs(serial); + + if (s_priv->instat_urb != NULL) { + err = usb_submit_urb(s_priv->instat_urb, GFP_KERNEL); + if (err != 0) + dev_dbg(&serial->dev->dev, "%s - submit instat urb failed %d\n", __func__, err); + } + if (s_priv->indat_urb != NULL) { + err = usb_submit_urb(s_priv->indat_urb, GFP_KERNEL); + if (err != 0) + dev_dbg(&serial->dev->dev, "%s - submit indat urb failed %d\n", __func__, err); + } + + return 0; +} + +static void keyspan_disconnect(struct usb_serial *serial) +{ + struct keyspan_serial_private *s_priv; + + s_priv = usb_get_serial_data(serial); + + stop_urb(s_priv->instat_urb); + stop_urb(s_priv->glocont_urb); + stop_urb(s_priv->indat_urb); +} + +static void keyspan_release(struct usb_serial *serial) +{ + struct keyspan_serial_private *s_priv; + + s_priv = usb_get_serial_data(serial); + + usb_free_urb(s_priv->instat_urb); + usb_free_urb(s_priv->indat_urb); + usb_free_urb(s_priv->glocont_urb); + + kfree(s_priv); +} + +static int keyspan_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct keyspan_serial_private *s_priv; + struct keyspan_port_private *p_priv; + const struct keyspan_device_details *d_details; + struct callbacks *cback; + int endp; + int port_num; + int i; + + s_priv = usb_get_serial_data(serial); + d_details = s_priv->device_details; + + p_priv = kzalloc(sizeof(*p_priv), GFP_KERNEL); + if (!p_priv) + return -ENOMEM; + + p_priv->device_details = d_details; + + /* Setup values for the various callback routines */ + cback = &keyspan_callbacks[d_details->msg_format]; + + port_num = port->number - port->serial->minor; + + /* Do indat endpoints first, once for each flip */ + endp = d_details->indat_endpoints[port_num]; + for (i = 0; i <= d_details->indat_endp_flip; ++i, ++endp) { + p_priv->in_urbs[i] = keyspan_setup_urb(serial, endp, + USB_DIR_IN, port, + p_priv->in_buffer[i], 64, + cback->indat_callback); + } + /* outdat endpoints also have flip */ + endp = d_details->outdat_endpoints[port_num]; + for (i = 0; i <= d_details->outdat_endp_flip; ++i, ++endp) { + p_priv->out_urbs[i] = keyspan_setup_urb(serial, endp, + USB_DIR_OUT, port, + p_priv->out_buffer[i], 64, + cback->outdat_callback); + } + /* inack endpoint */ + p_priv->inack_urb = keyspan_setup_urb(serial, + d_details->inack_endpoints[port_num], + USB_DIR_IN, port, + p_priv->inack_buffer, 1, + cback->inack_callback); + /* outcont endpoint */ + p_priv->outcont_urb = keyspan_setup_urb(serial, + d_details->outcont_endpoints[port_num], + USB_DIR_OUT, port, + p_priv->outcont_buffer, 64, + cback->outcont_callback); + + usb_set_serial_port_data(port, p_priv); + + return 0; +} + +static int keyspan_port_remove(struct usb_serial_port *port) +{ + struct keyspan_port_private *p_priv; + int i; + + p_priv = usb_get_serial_port_data(port); + + stop_urb(p_priv->inack_urb); + stop_urb(p_priv->outcont_urb); + for (i = 0; i < 2; i++) { + stop_urb(p_priv->in_urbs[i]); + stop_urb(p_priv->out_urbs[i]); + } + + usb_free_urb(p_priv->inack_urb); + usb_free_urb(p_priv->outcont_urb); + for (i = 0; i < 2; i++) { + usb_free_urb(p_priv->in_urbs[i]); + usb_free_urb(p_priv->out_urbs[i]); + } + + kfree(p_priv); + + return 0; +} + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("keyspan/usa28.fw"); +MODULE_FIRMWARE("keyspan/usa28x.fw"); +MODULE_FIRMWARE("keyspan/usa28xa.fw"); +MODULE_FIRMWARE("keyspan/usa28xb.fw"); +MODULE_FIRMWARE("keyspan/usa19.fw"); +MODULE_FIRMWARE("keyspan/usa19qi.fw"); +MODULE_FIRMWARE("keyspan/mpr.fw"); +MODULE_FIRMWARE("keyspan/usa19qw.fw"); +MODULE_FIRMWARE("keyspan/usa18x.fw"); +MODULE_FIRMWARE("keyspan/usa19w.fw"); +MODULE_FIRMWARE("keyspan/usa49w.fw"); +MODULE_FIRMWARE("keyspan/usa49wlc.fw"); diff --git a/drivers/usb/serial/keyspan.h b/drivers/usb/serial/keyspan.h new file mode 100644 index 00000000..0273dda3 --- /dev/null +++ b/drivers/usb/serial/keyspan.h @@ -0,0 +1,629 @@ +/* + Keyspan USB to Serial Converter driver + + (C) Copyright (C) 2000-2001 + Hugh Blemings + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + See http://blemings.org/hugh/keyspan.html for more information. + + Code in this driver inspired by and in a number of places taken + from Brian Warner's original Keyspan-PDA driver. + + This driver has been put together with the support of Innosys, Inc. + and Keyspan, Inc the manufacturers of the Keyspan USB-serial products. + Thanks Guys :) + + Thanks to Paulus for miscellaneous tidy ups, some largish chunks + of much nicer and/or completely new code and (perhaps most uniquely) + having the patience to sit down and explain why and where he'd changed + stuff. + + Tip 'o the hat to IBM (and previously Linuxcare :) for supporting + staff in their work on open source projects. + + See keyspan.c for update history. + +*/ + +#ifndef __LINUX_USB_SERIAL_KEYSPAN_H +#define __LINUX_USB_SERIAL_KEYSPAN_H + + +/* Function prototypes for Keyspan serial converter */ +static int keyspan_open (struct tty_struct *tty, + struct usb_serial_port *port); +static void keyspan_close (struct usb_serial_port *port); +static void keyspan_dtr_rts (struct usb_serial_port *port, int on); +static int keyspan_startup (struct usb_serial *serial); +static void keyspan_disconnect (struct usb_serial *serial); +static void keyspan_release (struct usb_serial *serial); +static int keyspan_port_probe(struct usb_serial_port *port); +static int keyspan_port_remove(struct usb_serial_port *port); +static int keyspan_write_room (struct tty_struct *tty); + +static int keyspan_write (struct tty_struct *tty, + struct usb_serial_port *port, + const unsigned char *buf, + int count); + +static void keyspan_send_setup (struct usb_serial_port *port, + int reset_port); + + +static void keyspan_set_termios (struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old); +static void keyspan_break_ctl (struct tty_struct *tty, + int break_state); +static int keyspan_tiocmget (struct tty_struct *tty); +static int keyspan_tiocmset (struct tty_struct *tty, + unsigned int set, + unsigned int clear); +static int keyspan_fake_startup (struct usb_serial *serial); + +static int keyspan_usa19_calc_baud (struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, + u8 *rate_hi, u8 *rate_low, + u8 *prescaler, int portnum); + +static int keyspan_usa19w_calc_baud (struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, + u8 *rate_hi, u8 *rate_low, + u8 *prescaler, int portnum); + +static int keyspan_usa28_calc_baud (struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, + u8 *rate_hi, u8 *rate_low, + u8 *prescaler, int portnum); + +static int keyspan_usa19hs_calc_baud (struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, + u8 *rate_hi, u8 *rate_low, + u8 *prescaler, int portnum); + +static int keyspan_usa28_send_setup (struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port); +static int keyspan_usa26_send_setup (struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port); +static int keyspan_usa49_send_setup (struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port); + +static int keyspan_usa90_send_setup (struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port); + +static int keyspan_usa67_send_setup (struct usb_serial *serial, + struct usb_serial_port *port, + int reset_port); + +/* Values used for baud rate calculation - device specific */ +#define KEYSPAN_INVALID_BAUD_RATE (-1) +#define KEYSPAN_BAUD_RATE_OK (0) +#define KEYSPAN_USA18X_BAUDCLK (12000000L) /* a guess */ +#define KEYSPAN_USA19_BAUDCLK (12000000L) +#define KEYSPAN_USA19W_BAUDCLK (24000000L) +#define KEYSPAN_USA19HS_BAUDCLK (14769231L) +#define KEYSPAN_USA28_BAUDCLK (1843200L) +#define KEYSPAN_USA28X_BAUDCLK (12000000L) +#define KEYSPAN_USA49W_BAUDCLK (48000000L) + +/* Some constants used to characterise each device. */ +#define KEYSPAN_MAX_NUM_PORTS (4) +#define KEYSPAN_MAX_FLIPS (2) + +/* Device info for the Keyspan serial converter, used + by the overall usb-serial probe function */ +#define KEYSPAN_VENDOR_ID (0x06cd) + +/* Product IDs for the products supported, pre-renumeration */ +#define keyspan_usa18x_pre_product_id 0x0105 +#define keyspan_usa19_pre_product_id 0x0103 +#define keyspan_usa19qi_pre_product_id 0x010b +#define keyspan_mpr_pre_product_id 0x011b +#define keyspan_usa19qw_pre_product_id 0x0118 +#define keyspan_usa19w_pre_product_id 0x0106 +#define keyspan_usa28_pre_product_id 0x0101 +#define keyspan_usa28x_pre_product_id 0x0102 +#define keyspan_usa28xa_pre_product_id 0x0114 +#define keyspan_usa28xb_pre_product_id 0x0113 +#define keyspan_usa49w_pre_product_id 0x0109 +#define keyspan_usa49wlc_pre_product_id 0x011a + +/* Product IDs post-renumeration. Note that the 28x and 28xb + have the same id's post-renumeration but behave identically + so it's not an issue. As such, the 28xb is not listed in any + of the device tables. */ +#define keyspan_usa18x_product_id 0x0112 +#define keyspan_usa19_product_id 0x0107 +#define keyspan_usa19qi_product_id 0x010c +#define keyspan_usa19hs_product_id 0x0121 +#define keyspan_mpr_product_id 0x011c +#define keyspan_usa19qw_product_id 0x0119 +#define keyspan_usa19w_product_id 0x0108 +#define keyspan_usa28_product_id 0x010f +#define keyspan_usa28x_product_id 0x0110 +#define keyspan_usa28xa_product_id 0x0115 +#define keyspan_usa28xb_product_id 0x0110 +#define keyspan_usa28xg_product_id 0x0135 +#define keyspan_usa49w_product_id 0x010a +#define keyspan_usa49wlc_product_id 0x012a +#define keyspan_usa49wg_product_id 0x0131 + +struct keyspan_device_details { + /* product ID value */ + int product_id; + + enum {msg_usa26, msg_usa28, msg_usa49, msg_usa90, msg_usa67} msg_format; + + /* Number of physical ports */ + int num_ports; + + /* 1 if endpoint flipping used on input, 0 if not */ + int indat_endp_flip; + + /* 1 if endpoint flipping used on output, 0 if not */ + int outdat_endp_flip; + + /* Table mapping input data endpoint IDs to physical + port number and flip if used */ + int indat_endpoints[KEYSPAN_MAX_NUM_PORTS]; + + /* Same for output endpoints */ + int outdat_endpoints[KEYSPAN_MAX_NUM_PORTS]; + + /* Input acknowledge endpoints */ + int inack_endpoints[KEYSPAN_MAX_NUM_PORTS]; + + /* Output control endpoints */ + int outcont_endpoints[KEYSPAN_MAX_NUM_PORTS]; + + /* Endpoint used for input status */ + int instat_endpoint; + + /* Endpoint used for input data 49WG only */ + int indat_endpoint; + + /* Endpoint used for global control functions */ + int glocont_endpoint; + + int (*calculate_baud_rate) (struct usb_serial_port *port, + u32 baud_rate, u32 baudclk, + u8 *rate_hi, u8 *rate_low, u8 *prescaler, int portnum); + u32 baudclk; +}; + +/* Now for each device type we setup the device detail + structure with the appropriate information (provided + in Keyspan's documentation) */ + +static const struct keyspan_device_details usa18x_device_details = { + .product_id = keyspan_usa18x_product_id, + .msg_format = msg_usa26, + .num_ports = 1, + .indat_endp_flip = 0, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81}, + .outdat_endpoints = {0x01}, + .inack_endpoints = {0x85}, + .outcont_endpoints = {0x05}, + .instat_endpoint = 0x87, + .indat_endpoint = -1, + .glocont_endpoint = 0x07, + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA18X_BAUDCLK, +}; + +static const struct keyspan_device_details usa19_device_details = { + .product_id = keyspan_usa19_product_id, + .msg_format = msg_usa28, + .num_ports = 1, + .indat_endp_flip = 1, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81}, + .outdat_endpoints = {0x01}, + .inack_endpoints = {0x83}, + .outcont_endpoints = {0x03}, + .instat_endpoint = 0x84, + .indat_endpoint = -1, + .glocont_endpoint = -1, + .calculate_baud_rate = keyspan_usa19_calc_baud, + .baudclk = KEYSPAN_USA19_BAUDCLK, +}; + +static const struct keyspan_device_details usa19qi_device_details = { + .product_id = keyspan_usa19qi_product_id, + .msg_format = msg_usa28, + .num_ports = 1, + .indat_endp_flip = 1, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81}, + .outdat_endpoints = {0x01}, + .inack_endpoints = {0x83}, + .outcont_endpoints = {0x03}, + .instat_endpoint = 0x84, + .indat_endpoint = -1, + .glocont_endpoint = -1, + .calculate_baud_rate = keyspan_usa28_calc_baud, + .baudclk = KEYSPAN_USA19_BAUDCLK, +}; + +static const struct keyspan_device_details mpr_device_details = { + .product_id = keyspan_mpr_product_id, + .msg_format = msg_usa28, + .num_ports = 1, + .indat_endp_flip = 1, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81}, + .outdat_endpoints = {0x01}, + .inack_endpoints = {0x83}, + .outcont_endpoints = {0x03}, + .instat_endpoint = 0x84, + .indat_endpoint = -1, + .glocont_endpoint = -1, + .calculate_baud_rate = keyspan_usa28_calc_baud, + .baudclk = KEYSPAN_USA19_BAUDCLK, +}; + +static const struct keyspan_device_details usa19qw_device_details = { + .product_id = keyspan_usa19qw_product_id, + .msg_format = msg_usa26, + .num_ports = 1, + .indat_endp_flip = 0, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81}, + .outdat_endpoints = {0x01}, + .inack_endpoints = {0x85}, + .outcont_endpoints = {0x05}, + .instat_endpoint = 0x87, + .indat_endpoint = -1, + .glocont_endpoint = 0x07, + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA19W_BAUDCLK, +}; + +static const struct keyspan_device_details usa19w_device_details = { + .product_id = keyspan_usa19w_product_id, + .msg_format = msg_usa26, + .num_ports = 1, + .indat_endp_flip = 0, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81}, + .outdat_endpoints = {0x01}, + .inack_endpoints = {0x85}, + .outcont_endpoints = {0x05}, + .instat_endpoint = 0x87, + .indat_endpoint = -1, + .glocont_endpoint = 0x07, + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA19W_BAUDCLK, +}; + +static const struct keyspan_device_details usa19hs_device_details = { + .product_id = keyspan_usa19hs_product_id, + .msg_format = msg_usa90, + .num_ports = 1, + .indat_endp_flip = 0, + .outdat_endp_flip = 0, + .indat_endpoints = {0x81}, + .outdat_endpoints = {0x01}, + .inack_endpoints = {-1}, + .outcont_endpoints = {0x02}, + .instat_endpoint = 0x82, + .indat_endpoint = -1, + .glocont_endpoint = -1, + .calculate_baud_rate = keyspan_usa19hs_calc_baud, + .baudclk = KEYSPAN_USA19HS_BAUDCLK, +}; + +static const struct keyspan_device_details usa28_device_details = { + .product_id = keyspan_usa28_product_id, + .msg_format = msg_usa28, + .num_ports = 2, + .indat_endp_flip = 1, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81, 0x83}, + .outdat_endpoints = {0x01, 0x03}, + .inack_endpoints = {0x85, 0x86}, + .outcont_endpoints = {0x05, 0x06}, + .instat_endpoint = 0x87, + .indat_endpoint = -1, + .glocont_endpoint = 0x07, + .calculate_baud_rate = keyspan_usa28_calc_baud, + .baudclk = KEYSPAN_USA28_BAUDCLK, +}; + +static const struct keyspan_device_details usa28x_device_details = { + .product_id = keyspan_usa28x_product_id, + .msg_format = msg_usa26, + .num_ports = 2, + .indat_endp_flip = 0, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81, 0x83}, + .outdat_endpoints = {0x01, 0x03}, + .inack_endpoints = {0x85, 0x86}, + .outcont_endpoints = {0x05, 0x06}, + .instat_endpoint = 0x87, + .indat_endpoint = -1, + .glocont_endpoint = 0x07, + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA28X_BAUDCLK, +}; + +static const struct keyspan_device_details usa28xa_device_details = { + .product_id = keyspan_usa28xa_product_id, + .msg_format = msg_usa26, + .num_ports = 2, + .indat_endp_flip = 0, + .outdat_endp_flip = 1, + .indat_endpoints = {0x81, 0x83}, + .outdat_endpoints = {0x01, 0x03}, + .inack_endpoints = {0x85, 0x86}, + .outcont_endpoints = {0x05, 0x06}, + .instat_endpoint = 0x87, + .indat_endpoint = -1, + .glocont_endpoint = 0x07, + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA28X_BAUDCLK, +}; + +static const struct keyspan_device_details usa28xg_device_details = { + .product_id = keyspan_usa28xg_product_id, + .msg_format = msg_usa67, + .num_ports = 2, + .indat_endp_flip = 0, + .outdat_endp_flip = 0, + .indat_endpoints = {0x84, 0x88}, + .outdat_endpoints = {0x02, 0x06}, + .inack_endpoints = {-1, -1}, + .outcont_endpoints = {-1, -1}, + .instat_endpoint = 0x81, + .indat_endpoint = -1, + .glocont_endpoint = 0x01, + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA28X_BAUDCLK, +}; +/* We don't need a separate entry for the usa28xb as it appears as a 28x anyway */ + +static const struct keyspan_device_details usa49w_device_details = { + .product_id = keyspan_usa49w_product_id, + .msg_format = msg_usa49, + .num_ports = 4, + .indat_endp_flip = 0, + .outdat_endp_flip = 0, + .indat_endpoints = {0x81, 0x82, 0x83, 0x84}, + .outdat_endpoints = {0x01, 0x02, 0x03, 0x04}, + .inack_endpoints = {-1, -1, -1, -1}, + .outcont_endpoints = {-1, -1, -1, -1}, + .instat_endpoint = 0x87, + .indat_endpoint = -1, + .glocont_endpoint = 0x07, + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA49W_BAUDCLK, +}; + +static const struct keyspan_device_details usa49wlc_device_details = { + .product_id = keyspan_usa49wlc_product_id, + .msg_format = msg_usa49, + .num_ports = 4, + .indat_endp_flip = 0, + .outdat_endp_flip = 0, + .indat_endpoints = {0x81, 0x82, 0x83, 0x84}, + .outdat_endpoints = {0x01, 0x02, 0x03, 0x04}, + .inack_endpoints = {-1, -1, -1, -1}, + .outcont_endpoints = {-1, -1, -1, -1}, + .instat_endpoint = 0x87, + .indat_endpoint = -1, + .glocont_endpoint = 0x07, + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA19W_BAUDCLK, +}; + +static const struct keyspan_device_details usa49wg_device_details = { + .product_id = keyspan_usa49wg_product_id, + .msg_format = msg_usa49, + .num_ports = 4, + .indat_endp_flip = 0, + .outdat_endp_flip = 0, + .indat_endpoints = {-1, -1, -1, -1}, /* single 'global' data in EP */ + .outdat_endpoints = {0x01, 0x02, 0x04, 0x06}, + .inack_endpoints = {-1, -1, -1, -1}, + .outcont_endpoints = {-1, -1, -1, -1}, + .instat_endpoint = 0x81, + .indat_endpoint = 0x88, + .glocont_endpoint = 0x00, /* uses control EP */ + .calculate_baud_rate = keyspan_usa19w_calc_baud, + .baudclk = KEYSPAN_USA19W_BAUDCLK, +}; + +static const struct keyspan_device_details *keyspan_devices[] = { + &usa18x_device_details, + &usa19_device_details, + &usa19qi_device_details, + &mpr_device_details, + &usa19qw_device_details, + &usa19w_device_details, + &usa19hs_device_details, + &usa28_device_details, + &usa28x_device_details, + &usa28xa_device_details, + &usa28xg_device_details, + /* 28xb not required as it renumerates as a 28x */ + &usa49w_device_details, + &usa49wlc_device_details, + &usa49wg_device_details, + NULL, +}; + +static const struct usb_device_id keyspan_ids_combined[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa18x_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19w_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qi_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qw_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_mpr_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28x_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xa_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xb_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49w_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wlc_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa18x_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19w_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qi_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qw_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19hs_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_mpr_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28x_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xa_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xg_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49w_product_id)}, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wlc_product_id)}, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wg_product_id)}, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, keyspan_ids_combined); + +/* usb_device_id table for the pre-firmware download keyspan devices */ +static const struct usb_device_id keyspan_pre_ids[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa18x_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qi_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qw_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19w_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_mpr_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28x_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xa_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xb_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49w_pre_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wlc_pre_product_id) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id keyspan_1port_ids[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa18x_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qi_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19qw_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19w_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa19hs_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_mpr_product_id) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id keyspan_2port_ids[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28x_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xa_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa28xg_product_id) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id keyspan_4port_ids[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49w_product_id) }, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wlc_product_id)}, + { USB_DEVICE(KEYSPAN_VENDOR_ID, keyspan_usa49wg_product_id)}, + { } /* Terminating entry */ +}; + +/* Structs for the devices, pre and post renumeration. */ +static struct usb_serial_driver keyspan_pre_device = { + .driver = { + .owner = THIS_MODULE, + .name = "keyspan_no_firm", + }, + .description = "Keyspan - (without firmware)", + .id_table = keyspan_pre_ids, + .num_ports = 1, + .attach = keyspan_fake_startup, +}; + +static struct usb_serial_driver keyspan_1port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "keyspan_1", + }, + .description = "Keyspan 1 port adapter", + .id_table = keyspan_1port_ids, + .num_ports = 1, + .open = keyspan_open, + .close = keyspan_close, + .dtr_rts = keyspan_dtr_rts, + .write = keyspan_write, + .write_room = keyspan_write_room, + .set_termios = keyspan_set_termios, + .break_ctl = keyspan_break_ctl, + .tiocmget = keyspan_tiocmget, + .tiocmset = keyspan_tiocmset, + .attach = keyspan_startup, + .disconnect = keyspan_disconnect, + .release = keyspan_release, + .port_probe = keyspan_port_probe, + .port_remove = keyspan_port_remove, +}; + +static struct usb_serial_driver keyspan_2port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "keyspan_2", + }, + .description = "Keyspan 2 port adapter", + .id_table = keyspan_2port_ids, + .num_ports = 2, + .open = keyspan_open, + .close = keyspan_close, + .dtr_rts = keyspan_dtr_rts, + .write = keyspan_write, + .write_room = keyspan_write_room, + .set_termios = keyspan_set_termios, + .break_ctl = keyspan_break_ctl, + .tiocmget = keyspan_tiocmget, + .tiocmset = keyspan_tiocmset, + .attach = keyspan_startup, + .disconnect = keyspan_disconnect, + .release = keyspan_release, + .port_probe = keyspan_port_probe, + .port_remove = keyspan_port_remove, +}; + +static struct usb_serial_driver keyspan_4port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "keyspan_4", + }, + .description = "Keyspan 4 port adapter", + .id_table = keyspan_4port_ids, + .num_ports = 4, + .open = keyspan_open, + .close = keyspan_close, + .dtr_rts = keyspan_dtr_rts, + .write = keyspan_write, + .write_room = keyspan_write_room, + .set_termios = keyspan_set_termios, + .break_ctl = keyspan_break_ctl, + .tiocmget = keyspan_tiocmget, + .tiocmset = keyspan_tiocmset, + .attach = keyspan_startup, + .disconnect = keyspan_disconnect, + .release = keyspan_release, + .port_probe = keyspan_port_probe, + .port_remove = keyspan_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &keyspan_pre_device, &keyspan_1port_device, + &keyspan_2port_device, &keyspan_4port_device, NULL +}; + +#endif diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c new file mode 100644 index 00000000..5f1d382e --- /dev/null +++ b/drivers/usb/serial/keyspan_pda.c @@ -0,0 +1,796 @@ +/* + * USB Keyspan PDA / Xircom / Entregra Converter driver + * + * Copyright (C) 1999 - 2001 Greg Kroah-Hartman + * Copyright (C) 1999, 2000 Brian Warner + * Copyright (C) 2000 Al Borchers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* make a simple define to handle if we are compiling keyspan_pda or xircom support */ +#if defined(CONFIG_USB_SERIAL_KEYSPAN_PDA) || defined(CONFIG_USB_SERIAL_KEYSPAN_PDA_MODULE) + #define KEYSPAN +#else + #undef KEYSPAN +#endif +#if defined(CONFIG_USB_SERIAL_XIRCOM) || defined(CONFIG_USB_SERIAL_XIRCOM_MODULE) + #define XIRCOM +#else + #undef XIRCOM +#endif + +#define DRIVER_AUTHOR "Brian Warner " +#define DRIVER_DESC "USB Keyspan PDA Converter driver" + +struct keyspan_pda_private { + int tx_room; + int tx_throttled; + struct work_struct wakeup_work; + struct work_struct unthrottle_work; + struct usb_serial *serial; + struct usb_serial_port *port; +}; + + +#define KEYSPAN_VENDOR_ID 0x06cd +#define KEYSPAN_PDA_FAKE_ID 0x0103 +#define KEYSPAN_PDA_ID 0x0104 /* no clue */ + +/* For Xircom PGSDB9 and older Entregra version of the same device */ +#define XIRCOM_VENDOR_ID 0x085a +#define XIRCOM_FAKE_ID 0x8027 +#define ENTREGRA_VENDOR_ID 0x1645 +#define ENTREGRA_FAKE_ID 0x8093 + +static const struct usb_device_id id_table_combined[] = { +#ifdef KEYSPAN + { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_FAKE_ID) }, +#endif +#ifdef XIRCOM + { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) }, + { USB_DEVICE(ENTREGRA_VENDOR_ID, ENTREGRA_FAKE_ID) }, +#endif + { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +static const struct usb_device_id id_table_std[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_ID) }, + { } /* Terminating entry */ +}; + +#ifdef KEYSPAN +static const struct usb_device_id id_table_fake[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_FAKE_ID) }, + { } /* Terminating entry */ +}; +#endif + +#ifdef XIRCOM +static const struct usb_device_id id_table_fake_xircom[] = { + { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) }, + { USB_DEVICE(ENTREGRA_VENDOR_ID, ENTREGRA_FAKE_ID) }, + { } +}; +#endif + +static void keyspan_pda_wakeup_write(struct work_struct *work) +{ + struct keyspan_pda_private *priv = + container_of(work, struct keyspan_pda_private, wakeup_work); + struct usb_serial_port *port = priv->port; + + tty_port_tty_wakeup(&port->port); +} + +static void keyspan_pda_request_unthrottle(struct work_struct *work) +{ + struct keyspan_pda_private *priv = + container_of(work, struct keyspan_pda_private, unthrottle_work); + struct usb_serial *serial = priv->serial; + int result; + + /* ask the device to tell us when the tx buffer becomes + sufficiently empty */ + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + 7, /* request_unthrottle */ + USB_TYPE_VENDOR | USB_RECIP_INTERFACE + | USB_DIR_OUT, + 16, /* value: threshold */ + 0, /* index */ + NULL, + 0, + 2000); + if (result < 0) + dev_dbg(&serial->dev->dev, "%s - error %d from usb_control_msg\n", + __func__, result); +} + + +static void keyspan_pda_rx_interrupt(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + int retval; + int status = urb->status; + struct keyspan_pda_private *priv; + priv = usb_get_serial_port_data(port); + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", __func__, status); + return; + default: + dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", __func__, status); + goto exit; + } + + /* see if the message is data or a status interrupt */ + switch (data[0]) { + case 0: + /* rest of message is rx data */ + if (urb->actual_length) { + tty_insert_flip_string(&port->port, data + 1, + urb->actual_length - 1); + tty_flip_buffer_push(&port->port); + } + break; + case 1: + /* status interrupt */ + dev_dbg(&port->dev, "rx int, d1=%d, d2=%d\n", data[1], data[2]); + switch (data[1]) { + case 1: /* modemline change */ + break; + case 2: /* tx unthrottle interrupt */ + priv->tx_throttled = 0; + /* queue up a wakeup at scheduler time */ + schedule_work(&priv->wakeup_work); + break; + default: + break; + } + break; + default: + break; + } + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, + "%s - usb_submit_urb failed with result %d", + __func__, retval); +} + + +static void keyspan_pda_rx_throttle(struct tty_struct *tty) +{ + /* stop receiving characters. We just turn off the URB request, and + let chars pile up in the device. If we're doing hardware + flowcontrol, the device will signal the other end when its buffer + fills up. If we're doing XON/XOFF, this would be a good time to + send an XOFF, although it might make sense to foist that off + upon the device too. */ + struct usb_serial_port *port = tty->driver_data; + + usb_kill_urb(port->interrupt_in_urb); +} + + +static void keyspan_pda_rx_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + /* just restart the receive interrupt URB */ + + if (usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL)) + dev_dbg(&port->dev, "usb_submit_urb(read urb) failed\n"); +} + + +static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud) +{ + int rc; + int bindex; + + switch (baud) { + case 110: + bindex = 0; + break; + case 300: + bindex = 1; + break; + case 1200: + bindex = 2; + break; + case 2400: + bindex = 3; + break; + case 4800: + bindex = 4; + break; + case 9600: + bindex = 5; + break; + case 19200: + bindex = 6; + break; + case 38400: + bindex = 7; + break; + case 57600: + bindex = 8; + break; + case 115200: + bindex = 9; + break; + default: + bindex = 5; /* Default to 9600 */ + baud = 9600; + } + + /* rather than figure out how to sleep while waiting for this + to complete, I just use the "legacy" API. */ + rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + 0, /* set baud */ + USB_TYPE_VENDOR + | USB_RECIP_INTERFACE + | USB_DIR_OUT, /* type */ + bindex, /* value */ + 0, /* index */ + NULL, /* &data */ + 0, /* size */ + 2000); /* timeout */ + if (rc < 0) + return 0; + return baud; +} + + +static void keyspan_pda_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + int value; + int result; + + if (break_state == -1) + value = 1; /* start break */ + else + value = 0; /* clear break */ + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + 4, /* set break */ + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + value, 0, NULL, 0, 2000); + if (result < 0) + dev_dbg(&port->dev, "%s - error %d from usb_control_msg\n", + __func__, result); + /* there is something funky about this.. the TCSBRK that 'cu' performs + ought to translate into a break_ctl(-1),break_ctl(0) pair HZ/4 + seconds apart, but it feels like the break sent isn't as long as it + is on /dev/ttyS0 */ +} + + +static void keyspan_pda_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct usb_serial *serial = port->serial; + speed_t speed; + + /* cflag specifies lots of stuff: number of stop bits, parity, number + of data bits, baud. What can the device actually handle?: + CSTOPB (1 stop bit or 2) + PARENB (parity) + CSIZE (5bit .. 8bit) + There is minimal hw support for parity (a PSW bit seems to hold the + parity of whatever is in the accumulator). The UART either deals + with 10 bits (start, 8 data, stop) or 11 bits (start, 8 data, + 1 special, stop). So, with firmware changes, we could do: + 8N1: 10 bit + 8N2: 11 bit, extra bit always (mark?) + 8[EOMS]1: 11 bit, extra bit is parity + 7[EOMS]1: 10 bit, b0/b7 is parity + 7[EOMS]2: 11 bit, b0/b7 is parity, extra bit always (mark?) + + HW flow control is dictated by the tty->termios.c_cflags & CRTSCTS + bit. + + For now, just do baud. */ + + speed = tty_get_baud_rate(tty); + speed = keyspan_pda_setbaud(serial, speed); + + if (speed == 0) { + dev_dbg(&port->dev, "can't handle requested baud rate\n"); + /* It hasn't changed so.. */ + speed = tty_termios_baud_rate(old_termios); + } + /* Only speed can change so copy the old h/w parameters + then encode the new speed */ + tty_termios_copy_hw(&tty->termios, old_termios); + tty_encode_baud_rate(tty, speed, speed); +} + + +/* modem control pins: DTR and RTS are outputs and can be controlled. + DCD, RI, DSR, CTS are inputs and can be read. All outputs can also be + read. The byte passed is: DTR(b7) DCD RI DSR CTS RTS(b2) unused unused */ + +static int keyspan_pda_get_modem_info(struct usb_serial *serial, + unsigned char *value) +{ + int rc; + u8 *data; + + data = kmalloc(1, GFP_KERNEL); + if (!data) + return -ENOMEM; + + rc = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + 3, /* get pins */ + USB_TYPE_VENDOR|USB_RECIP_INTERFACE|USB_DIR_IN, + 0, 0, data, 1, 2000); + if (rc >= 0) + *value = *data; + + kfree(data); + return rc; +} + + +static int keyspan_pda_set_modem_info(struct usb_serial *serial, + unsigned char value) +{ + int rc; + rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + 3, /* set pins */ + USB_TYPE_VENDOR|USB_RECIP_INTERFACE|USB_DIR_OUT, + value, 0, NULL, 0, 2000); + return rc; +} + +static int keyspan_pda_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + int rc; + unsigned char status; + int value; + + rc = keyspan_pda_get_modem_info(serial, &status); + if (rc < 0) + return rc; + value = + ((status & (1<<7)) ? TIOCM_DTR : 0) | + ((status & (1<<6)) ? TIOCM_CAR : 0) | + ((status & (1<<5)) ? TIOCM_RNG : 0) | + ((status & (1<<4)) ? TIOCM_DSR : 0) | + ((status & (1<<3)) ? TIOCM_CTS : 0) | + ((status & (1<<2)) ? TIOCM_RTS : 0); + return value; +} + +static int keyspan_pda_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + int rc; + unsigned char status; + + rc = keyspan_pda_get_modem_info(serial, &status); + if (rc < 0) + return rc; + + if (set & TIOCM_RTS) + status |= (1<<2); + if (set & TIOCM_DTR) + status |= (1<<7); + + if (clear & TIOCM_RTS) + status &= ~(1<<2); + if (clear & TIOCM_DTR) + status &= ~(1<<7); + rc = keyspan_pda_set_modem_info(serial, status); + return rc; +} + +static int keyspan_pda_write(struct tty_struct *tty, + struct usb_serial_port *port, const unsigned char *buf, int count) +{ + struct usb_serial *serial = port->serial; + int request_unthrottle = 0; + int rc = 0; + struct keyspan_pda_private *priv; + + priv = usb_get_serial_port_data(port); + /* guess how much room is left in the device's ring buffer, and if we + want to send more than that, check first, updating our notion of + what is left. If our write will result in no room left, ask the + device to give us an interrupt when the room available rises above + a threshold, and hold off all writers (eventually, those using + select() or poll() too) until we receive that unthrottle interrupt. + Block if we can't write anything at all, otherwise write as much as + we can. */ + if (count == 0) { + dev_dbg(&port->dev, "write request of 0 bytes\n"); + return 0; + } + + /* we might block because of: + the TX urb is in-flight (wait until it completes) + the device is full (wait until it says there is room) + */ + spin_lock_bh(&port->lock); + if (!test_bit(0, &port->write_urbs_free) || priv->tx_throttled) { + spin_unlock_bh(&port->lock); + return 0; + } + clear_bit(0, &port->write_urbs_free); + spin_unlock_bh(&port->lock); + + /* At this point the URB is in our control, nobody else can submit it + again (the only sudden transition was the one from EINPROGRESS to + finished). Also, the tx process is not throttled. So we are + ready to write. */ + + count = (count > port->bulk_out_size) ? port->bulk_out_size : count; + + /* Check if we might overrun the Tx buffer. If so, ask the + device how much room it really has. This is done only on + scheduler time, since usb_control_msg() sleeps. */ + if (count > priv->tx_room && !in_interrupt()) { + u8 *room; + + room = kmalloc(1, GFP_KERNEL); + if (!room) { + rc = -ENOMEM; + goto exit; + } + + rc = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + 6, /* write_room */ + USB_TYPE_VENDOR | USB_RECIP_INTERFACE + | USB_DIR_IN, + 0, /* value: 0 means "remaining room" */ + 0, /* index */ + room, + 1, + 2000); + if (rc > 0) { + dev_dbg(&port->dev, "roomquery says %d\n", *room); + priv->tx_room = *room; + } + kfree(room); + if (rc < 0) { + dev_dbg(&port->dev, "roomquery failed\n"); + goto exit; + } + if (rc == 0) { + dev_dbg(&port->dev, "roomquery returned 0 bytes\n"); + rc = -EIO; /* device didn't return any data */ + goto exit; + } + } + if (count > priv->tx_room) { + /* we're about to completely fill the Tx buffer, so + we'll be throttled afterwards. */ + count = priv->tx_room; + request_unthrottle = 1; + } + + if (count) { + /* now transfer data */ + memcpy(port->write_urb->transfer_buffer, buf, count); + /* send the data out the bulk port */ + port->write_urb->transfer_buffer_length = count; + + priv->tx_room -= count; + + rc = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (rc) { + dev_dbg(&port->dev, "usb_submit_urb(write bulk) failed\n"); + goto exit; + } + } else { + /* There wasn't any room left, so we are throttled until + the buffer empties a bit */ + request_unthrottle = 1; + } + + if (request_unthrottle) { + priv->tx_throttled = 1; /* block writers */ + schedule_work(&priv->unthrottle_work); + } + + rc = count; +exit: + if (rc < 0) + set_bit(0, &port->write_urbs_free); + return rc; +} + + +static void keyspan_pda_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct keyspan_pda_private *priv; + + set_bit(0, &port->write_urbs_free); + priv = usb_get_serial_port_data(port); + + /* queue up a wakeup at scheduler time */ + schedule_work(&priv->wakeup_work); +} + + +static int keyspan_pda_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct keyspan_pda_private *priv; + priv = usb_get_serial_port_data(port); + /* used by n_tty.c for processing of tabs and such. Giving it our + conservative guess is probably good enough, but needs testing by + running a console through the device. */ + return priv->tx_room; +} + + +static int keyspan_pda_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct keyspan_pda_private *priv; + unsigned long flags; + int ret = 0; + + priv = usb_get_serial_port_data(port); + + /* when throttled, return at least WAKEUP_CHARS to tell select() (via + n_tty.c:normal_poll() ) that we're not writeable. */ + + spin_lock_irqsave(&port->lock, flags); + if (!test_bit(0, &port->write_urbs_free) || priv->tx_throttled) + ret = 256; + spin_unlock_irqrestore(&port->lock, flags); + return ret; +} + + +static void keyspan_pda_dtr_rts(struct usb_serial_port *port, int on) +{ + struct usb_serial *serial = port->serial; + + if (on) + keyspan_pda_set_modem_info(serial, (1 << 7) | (1 << 2)); + else + keyspan_pda_set_modem_info(serial, 0); +} + + +static int keyspan_pda_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + u8 *room; + int rc = 0; + struct keyspan_pda_private *priv; + + /* find out how much room is in the Tx ring */ + room = kmalloc(1, GFP_KERNEL); + if (!room) + return -ENOMEM; + + rc = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + 6, /* write_room */ + USB_TYPE_VENDOR | USB_RECIP_INTERFACE + | USB_DIR_IN, + 0, /* value */ + 0, /* index */ + room, + 1, + 2000); + if (rc < 0) { + dev_dbg(&port->dev, "%s - roomquery failed\n", __func__); + goto error; + } + if (rc == 0) { + dev_dbg(&port->dev, "%s - roomquery returned 0 bytes\n", __func__); + rc = -EIO; + goto error; + } + priv = usb_get_serial_port_data(port); + priv->tx_room = *room; + priv->tx_throttled = *room ? 0 : 1; + + /*Start reading from the device*/ + rc = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (rc) { + dev_dbg(&port->dev, "%s - usb_submit_urb(read int) failed\n", __func__); + goto error; + } +error: + kfree(room); + return rc; +} +static void keyspan_pda_close(struct usb_serial_port *port) +{ + usb_kill_urb(port->write_urb); + usb_kill_urb(port->interrupt_in_urb); +} + + +/* download the firmware to a "fake" device (pre-renumeration) */ +static int keyspan_pda_fake_startup(struct usb_serial *serial) +{ + int response; + const char *fw_name; + + /* download the firmware here ... */ + response = ezusb_fx1_set_reset(serial->dev, 1); + + if (0) { ; } +#ifdef KEYSPAN + else if (le16_to_cpu(serial->dev->descriptor.idVendor) == KEYSPAN_VENDOR_ID) + fw_name = "keyspan_pda/keyspan_pda.fw"; +#endif +#ifdef XIRCOM + else if ((le16_to_cpu(serial->dev->descriptor.idVendor) == XIRCOM_VENDOR_ID) || + (le16_to_cpu(serial->dev->descriptor.idVendor) == ENTREGRA_VENDOR_ID)) + fw_name = "keyspan_pda/xircom_pgs.fw"; +#endif + else { + dev_err(&serial->dev->dev, "%s: unknown vendor, aborting.\n", + __func__); + return -ENODEV; + } + + if (ezusb_fx1_ihex_firmware_download(serial->dev, fw_name) < 0) { + dev_err(&serial->dev->dev, "failed to load firmware \"%s\"\n", + fw_name); + return -ENOENT; + } + + /* after downloading firmware Renumeration will occur in a + moment and the new device will bind to the real driver */ + + /* we want this device to fail to have a driver assigned to it. */ + return 1; +} + +#ifdef KEYSPAN +MODULE_FIRMWARE("keyspan_pda/keyspan_pda.fw"); +#endif +#ifdef XIRCOM +MODULE_FIRMWARE("keyspan_pda/xircom_pgs.fw"); +#endif + +static int keyspan_pda_port_probe(struct usb_serial_port *port) +{ + + struct keyspan_pda_private *priv; + + priv = kmalloc(sizeof(struct keyspan_pda_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + INIT_WORK(&priv->wakeup_work, keyspan_pda_wakeup_write); + INIT_WORK(&priv->unthrottle_work, keyspan_pda_request_unthrottle); + priv->serial = port->serial; + priv->port = port; + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int keyspan_pda_port_remove(struct usb_serial_port *port) +{ + struct keyspan_pda_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +#ifdef KEYSPAN +static struct usb_serial_driver keyspan_pda_fake_device = { + .driver = { + .owner = THIS_MODULE, + .name = "keyspan_pda_pre", + }, + .description = "Keyspan PDA - (prerenumeration)", + .id_table = id_table_fake, + .num_ports = 1, + .attach = keyspan_pda_fake_startup, +}; +#endif + +#ifdef XIRCOM +static struct usb_serial_driver xircom_pgs_fake_device = { + .driver = { + .owner = THIS_MODULE, + .name = "xircom_no_firm", + }, + .description = "Xircom / Entregra PGS - (prerenumeration)", + .id_table = id_table_fake_xircom, + .num_ports = 1, + .attach = keyspan_pda_fake_startup, +}; +#endif + +static struct usb_serial_driver keyspan_pda_device = { + .driver = { + .owner = THIS_MODULE, + .name = "keyspan_pda", + }, + .description = "Keyspan PDA", + .id_table = id_table_std, + .num_ports = 1, + .dtr_rts = keyspan_pda_dtr_rts, + .open = keyspan_pda_open, + .close = keyspan_pda_close, + .write = keyspan_pda_write, + .write_room = keyspan_pda_write_room, + .write_bulk_callback = keyspan_pda_write_bulk_callback, + .read_int_callback = keyspan_pda_rx_interrupt, + .chars_in_buffer = keyspan_pda_chars_in_buffer, + .throttle = keyspan_pda_rx_throttle, + .unthrottle = keyspan_pda_rx_unthrottle, + .set_termios = keyspan_pda_set_termios, + .break_ctl = keyspan_pda_break_ctl, + .tiocmget = keyspan_pda_tiocmget, + .tiocmset = keyspan_pda_tiocmset, + .port_probe = keyspan_pda_port_probe, + .port_remove = keyspan_pda_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &keyspan_pda_device, +#ifdef KEYSPAN + &keyspan_pda_fake_device, +#endif +#ifdef XIRCOM + &xircom_pgs_fake_device, +#endif + NULL +}; + +module_usb_serial_driver(serial_drivers, id_table_combined); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/keyspan_usa26msg.h b/drivers/usb/serial/keyspan_usa26msg.h new file mode 100644 index 00000000..3808727d --- /dev/null +++ b/drivers/usb/serial/keyspan_usa26msg.h @@ -0,0 +1,260 @@ +/* + usa26msg.h + + Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved + This file is available under a BSD-style copyright + + Keyspan USB Async Message Formats for the USA28X + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain this licence text + without modification, this list of conditions, and the following + disclaimer. The following copyright notice must appear immediately at + the beginning of all source files: + + Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved + + This file is available under a BSD-style copyright + + 2. The name of InnoSys Incorporated may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Third revision: USA28X version (aka USA26) + + Buffer formats for RX/TX data messages are not defined by + a structure, but are described here: + + USB OUT (host -> USAxx, transmit) messages contain a + REQUEST_ACK indicator (set to 0xff to request an ACK at the + completion of transmit; 0x00 otherwise), followed by data: + + RQSTACK DAT DAT DAT ... + + with a total data length of 63. + + USB IN (USAxx -> host, receive) messages begin with a status + byte in which the 0x80 bit is either: + + (a) 0x80 bit clear + indicates that the bytes following it are all data + bytes: + + STAT DATA DATA DATA DATA DATA ... + + for a total of up to 63 DATA bytes, + + or: + + (b) 0x80 bit set + indiates that the bytes following alternate data and + status bytes: + + STAT DATA STAT DATA STAT DATA STAT DATA ... + + for a total of up to 32 DATA bytes. + + The valid bits in the STAT bytes are: + + OVERRUN 0x02 + PARITY 0x04 + FRAMING 0x08 + BREAK 0x10 + + Notes: + + (1) The OVERRUN bit can appear in either (a) or (b) format + messages, but the but the PARITY/FRAMING/BREAK bits + only appear in (b) format messages. + (2) For the host to determine the exact point at which the + overrun occurred (to identify the point in the data + stream at which the data was lost), it needs to count + 128 characters, starting at the first character of the + message in which OVERRUN was reported; the lost character(s) + would have been received between the 128th and 129th + characters. + (3) An RX data message in which the first byte has 0x80 clear + serves as a "break off" indicator. + + revision history: + + 1999feb10 add reportHskiaChanges to allow us to ignore them + 1999feb10 add txAckThreshold for fast+loose throughput enhancement + 1999mar30 beef up support for RX error reporting + 1999apr14 add resetDataToggle to control message + 2000jan04 merge with usa17msg.h + 2000jun01 add extended BSD-style copyright text + 2001jul05 change message format to improve OVERRUN case + + Note on shared names: + + In the case of fields which have been merged between the USA17 + and USA26 definitions, the USA26 definition is the first part + of the name and the USA17 definition is the second part of the + name; both meanings are described below. +*/ + +#ifndef __USA26MSG__ +#define __USA26MSG__ + + +struct keyspan_usa26_portControlMessage +{ + /* + there are three types of "commands" sent in the control message: + + 1. configuration changes which must be requested by setting + the corresponding "set" flag (and should only be requested + when necessary, to reduce overhead on the USA26): + */ + u8 setClocking, // BOTH: host requests baud rate be set + baudLo, // BOTH: host does baud divisor calculation + baudHi, // BOTH: baudHi is only used for first port (gives lower rates) + externalClock_txClocking, + // USA26: 0=internal, other=external + // USA17: 0=internal, other=external/RI + rxClocking, // USA17: 0=internal, 1=external/RI, other=external/DSR + + + setLcr, // BOTH: host requests lcr be set + lcr, // BOTH: use PARITY, STOPBITS, DATABITS below + + setFlowControl, // BOTH: host requests flow control be set + ctsFlowControl, // BOTH: 1=use CTS flow control, 0=don't + xonFlowControl, // BOTH: 1=use XON/XOFF flow control, 0=don't + xonChar, // BOTH: specified in current character format + xoffChar, // BOTH: specified in current character format + + setTxTriState_setRts, + // USA26: host requests TX tri-state be set + // USA17: host requests RTS output be set + txTriState_rts, // BOTH: 1=active (normal), 0=tristate (off) + + setHskoa_setDtr, + // USA26: host requests HSKOA output be set + // USA17: host requests DTR output be set + hskoa_dtr, // BOTH: 1=on, 0=off + + setPrescaler, // USA26: host requests prescalar be set (default: 13) + prescaler; // BOTH: specified as N/8; values 8-ff are valid + // must be set any time internal baud rate is set; + // must not be set when external clocking is used + // note: in USA17, prescaler is applied whenever + // setClocking is requested + + /* + 3. configuration data which is simply used as is (no overhead, + but must be specified correctly in every host message). + */ + u8 forwardingLength, // BOTH: forward when this number of chars available + reportHskiaChanges_dsrFlowControl, + // USA26: 1=normal; 0=ignore external clock + // USA17: 1=use DSR flow control, 0=don't + txAckThreshold, // BOTH: 0=not allowed, 1=normal, 2-255 deliver ACK faster + loopbackMode; // BOTH: 0=no loopback, 1=loopback enabled + + /* + 4. commands which are flags only; these are processed in order + (so that, e.g., if both _txOn and _txOff flags are set, the + port ends in a TX_OFF state); any non-zero value is respected + */ + u8 _txOn, // BOTH: enable transmitting (and continue if there's data) + _txOff, // BOTH: stop transmitting + txFlush, // BOTH: toss outbound data + txBreak, // BOTH: turn on break (cleared by _txOn) + rxOn, // BOTH: turn on receiver + rxOff, // BOTH: turn off receiver + rxFlush, // BOTH: toss inbound data + rxForward, // BOTH: forward all inbound data, NOW (as if fwdLen==1) + returnStatus, // BOTH: return current status (even if it hasn't changed) + resetDataToggle;// BOTH: reset data toggle state to DATA0 + +}; + +// defines for bits in lcr +#define USA_DATABITS_5 0x00 +#define USA_DATABITS_6 0x01 +#define USA_DATABITS_7 0x02 +#define USA_DATABITS_8 0x03 +#define STOPBITS_5678_1 0x00 // 1 stop bit for all byte sizes +#define STOPBITS_5_1p5 0x04 // 1.5 stop bits for 5-bit byte +#define STOPBITS_678_2 0x04 // 2 stop bits for 6/7/8-bit byte +#define USA_PARITY_NONE 0x00 +#define USA_PARITY_ODD 0x08 +#define USA_PARITY_EVEN 0x18 +#define PARITY_1 0x28 +#define PARITY_0 0x38 + +// all things called "StatusMessage" are sent on the status endpoint + +struct keyspan_usa26_portStatusMessage // one for each port +{ + u8 port, // BOTH: 0=first, 1=second, other=see below + hskia_cts, // USA26: reports HSKIA pin + // USA17: reports CTS pin + gpia_dcd, // USA26: reports GPIA pin + // USA17: reports DCD pin + dsr, // USA17: reports DSR pin + ri, // USA17: reports RI pin + _txOff, // port has been disabled (by host) + _txXoff, // port is in XOFF state (either host or RX XOFF) + rxEnabled, // as configured by rxOn/rxOff 1=on, 0=off + controlResponse;// 1=a control message has been processed +}; + +// bits in RX data message when STAT byte is included +#define RXERROR_OVERRUN 0x02 +#define RXERROR_PARITY 0x04 +#define RXERROR_FRAMING 0x08 +#define RXERROR_BREAK 0x10 + +struct keyspan_usa26_globalControlMessage +{ + u8 sendGlobalStatus, // 2=request for two status responses + resetStatusToggle, // 1=reset global status toggle + resetStatusCount; // a cycling value +}; + +struct keyspan_usa26_globalStatusMessage +{ + u8 port, // 3 + sendGlobalStatus, // from request, decremented + resetStatusCount; // as in request +}; + +struct keyspan_usa26_globalDebugMessage +{ + u8 port, // 2 + a, + b, + c, + d; +}; + +// ie: the maximum length of an EZUSB endpoint buffer +#define MAX_DATA_LEN 64 + +// update status approx. 60 times a second (16.6666 ms) +#define STATUS_UPDATE_INTERVAL 16 + +// status rationing tuning value (each port gets checked each n ms) +#define STATUS_RATION 10 + +#endif + + diff --git a/drivers/usb/serial/keyspan_usa28msg.h b/drivers/usb/serial/keyspan_usa28msg.h new file mode 100644 index 00000000..dee454c4 --- /dev/null +++ b/drivers/usb/serial/keyspan_usa28msg.h @@ -0,0 +1,201 @@ +/* + usa28msg.h + + Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved + This file is available under a BSD-style copyright + + Keyspan USB Async Message Formats for the USA26X + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain this licence text + without modification, this list of conditions, and the following + disclaimer. The following copyright notice must appear immediately at + the beginning of all source files: + + Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved + + This file is available under a BSD-style copyright + + 2. The name of InnoSys Incorporated may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Note: these message formats are common to USA18, USA19, and USA28; + (for USA28X, see usa26msg.h) + + Buffer formats for RX/TX data messages are not defined by + a structure, but are described here: + + USB OUT (host -> USA28, transmit) messages contain a + REQUEST_ACK indicator (set to 0xff to request an ACK at the + completion of transmit; 0x00 otherwise), followed by data. + If the port is configured for parity, the data will be an + alternating string of parity and data bytes, so the message + format will be: + + RQSTACK PAR DAT PAR DAT ... + + so the maximum length is 63 bytes (1 + 62, or 31 data bytes); + always an odd number for the total message length. + + If there is no parity, the format is simply: + + RQSTACK DAT DAT DAT ... + + with a total data length of 63. + + USB IN (USA28 -> host, receive) messages contain data and parity + if parity is configred, thusly: + + DAT PAR DAT PAR DAT PAR ... + + for a total of 32 data bytes; + + If parity is not configured, the format is: + + DAT DAT DAT ... + + for a total of 64 data bytes. + + In the TX messages (USB OUT), the 0x01 bit of the PARity byte is + the parity bit. In the RX messages (USB IN), the PARity byte is + the content of the 8051's status register; the parity bit + (RX_PARITY_BIT) is the 0x04 bit. + + revision history: + + 1999may06 add resetDataToggle to control message + 2000mar21 add rs232invalid to status response message + 2000apr04 add 230.4Kb definition to setBaudRate + 2000apr13 add/remove loopbackMode switch + 2000apr13 change definition of setBaudRate to cover 115.2Kb, too + 2000jun01 add extended BSD-style copyright text +*/ + +#ifndef __USA28MSG__ +#define __USA28MSG__ + + +struct keyspan_usa28_portControlMessage +{ + /* + there are four types of "commands" sent in the control message: + + 1. configuration changes which must be requested by setting + the corresponding "set" flag (and should only be requested + when necessary, to reduce overhead on the USA28): + */ + u8 setBaudRate, // 0=don't set, 1=baudLo/Hi, 2=115.2K, 3=230.4K + baudLo, // host does baud divisor calculation + baudHi; // baudHi is only used for first port (gives lower rates) + + /* + 2. configuration changes which are done every time (because it's + hardly more trouble to do them than to check whether to do them): + */ + u8 parity, // 1=use parity, 0=don't + ctsFlowControl, // all except 19Q: 1=use CTS flow control, 0=don't + // 19Q: 0x08:CTSflowControl 0x10:DSRflowControl + xonFlowControl, // 1=use XON/XOFF flow control, 0=don't + rts, // 1=on, 0=off + dtr; // 1=on, 0=off + + /* + 3. configuration data which is simply used as is (no overhead, + but must be correct in every host message). + */ + u8 forwardingLength, // forward when this number of chars available + forwardMs, // forward this many ms after last rx data + breakThreshold, // specified in ms, 1-255 (see note below) + xonChar, // specified in current character format + xoffChar; // specified in current character format + + /* + 4. commands which are flags only; these are processed in order + (so that, e.g., if both _txOn and _txOff flags are set, the + port ends in a TX_OFF state); any non-zero value is respected + */ + u8 _txOn, // enable transmitting (and continue if there's data) + _txOff, // stop transmitting + txFlush, // toss outbound data + txForceXoff, // pretend we've received XOFF + txBreak, // turn on break (leave on until txOn clears it) + rxOn, // turn on receiver + rxOff, // turn off receiver + rxFlush, // toss inbound data + rxForward, // forward all inbound data, NOW + returnStatus, // return current status n times (1 or 2) + resetDataToggle;// reset data toggle state to DATA0 + +}; + +struct keyspan_usa28_portStatusMessage +{ + u8 port, // 0=first, 1=second, 2=global (see below) + cts, + dsr, // (not used in all products) + dcd, + + ri, // (not used in all products) + _txOff, // port has been disabled (by host) + _txXoff, // port is in XOFF state (either host or RX XOFF) + dataLost, // count of lost chars; wraps; not guaranteed exact + + rxEnabled, // as configured by rxOn/rxOff 1=on, 0=off + rxBreak, // 1=we're in break state + rs232invalid, // 1=no valid signals on rs-232 inputs + controlResponse;// 1=a control messages has been processed +}; + +// bit defines in txState +#define TX_OFF 0x01 // requested by host txOff command +#define TX_XOFF 0x02 // either real, or simulated by host + +struct keyspan_usa28_globalControlMessage +{ + u8 sendGlobalStatus, // 2=request for two status responses + resetStatusToggle, // 1=reset global status toggle + resetStatusCount; // a cycling value +}; + +struct keyspan_usa28_globalStatusMessage +{ + u8 port, // 3 + sendGlobalStatus, // from request, decremented + resetStatusCount; // as in request +}; + +struct keyspan_usa28_globalDebugMessage +{ + u8 port, // 2 + n, // typically a count/status byte + b; // typically a data byte +}; + +// ie: the maximum length of an EZUSB endpoint buffer +#define MAX_DATA_LEN 64 + +// the parity bytes have only one significant bit +#define RX_PARITY_BIT 0x04 +#define TX_PARITY_BIT 0x01 + +// update status approx. 60 times a second (16.6666 ms) +#define STATUS_UPDATE_INTERVAL 16 + +#endif + diff --git a/drivers/usb/serial/keyspan_usa49msg.h b/drivers/usb/serial/keyspan_usa49msg.h new file mode 100644 index 00000000..163b2dea --- /dev/null +++ b/drivers/usb/serial/keyspan_usa49msg.h @@ -0,0 +1,282 @@ +/* + usa49msg.h + + Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved + This file is available under a BSD-style copyright + + Keyspan USB Async Message Formats for the USA49W + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain this licence text + without modification, this list of conditions, and the following + disclaimer. The following copyright notice must appear immediately at + the beginning of all source files: + + Copyright (C) 1998-2000 InnoSys Incorporated. All Rights Reserved + + This file is available under a BSD-style copyright + + 2. The name of InnoSys Incorporated may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + 4th revision: USA49W version + + Buffer formats for RX/TX data messages are not defined by + a structure, but are described here: + + USB OUT (host -> USAxx, transmit) messages contain a + REQUEST_ACK indicator (set to 0xff to request an ACK at the + completion of transmit; 0x00 otherwise), followed by data: + + RQSTACK DAT DAT DAT ... + + with a total data length of 63. + + USB IN (USAxx -> host, receive) messages begin with a status + byte in which the 0x80 bit is either: + + (a) 0x80 bit clear + indicates that the bytes following it are all data + bytes: + + STAT DATA DATA DATA DATA DATA ... + + for a total of up to 63 DATA bytes, + + or: + + (b) 0x80 bit set + indiates that the bytes following alternate data and + status bytes: + + STAT DATA STAT DATA STAT DATA STAT DATA ... + + for a total of up to 32 DATA bytes. + + The valid bits in the STAT bytes are: + + OVERRUN 0x02 + PARITY 0x04 + FRAMING 0x08 + BREAK 0x10 + + Notes: + + (1) The OVERRUN bit can appear in either (a) or (b) format + messages, but the but the PARITY/FRAMING/BREAK bits + only appear in (b) format messages. + (2) For the host to determine the exact point at which the + overrun occurred (to identify the point in the data + stream at which the data was lost), it needs to count + 128 characters, starting at the first character of the + message in which OVERRUN was reported; the lost character(s) + would have been received between the 128th and 129th + characters. + (3) An RX data message in which the first byte has 0x80 clear + serves as a "break off" indicator. + (4) a control message specifying disablePort will be answered + with a status message, but no further status will be sent + until a control messages with enablePort is sent + + revision history: + + 1999feb10 add reportHskiaChanges to allow us to ignore them + 1999feb10 add txAckThreshold for fast+loose throughput enhancement + 1999mar30 beef up support for RX error reporting + 1999apr14 add resetDataToggle to control message + 2000jan04 merge with usa17msg.h + 2000mar08 clone from usa26msg.h -> usa49msg.h + 2000mar09 change to support 4 ports + 2000may03 change external clocking to match USA-49W hardware + 2000jun01 add extended BSD-style copyright text + 2001jul05 change message format to improve OVERRUN case +*/ + +#ifndef __USA49MSG__ +#define __USA49MSG__ + + +/* + Host->device messages sent on the global control endpoint: + + portNumber message + ---------- -------------------- + 0,1,2,3 portControlMessage + 0x80 globalControlMessage +*/ + +struct keyspan_usa49_portControlMessage +{ + /* + 0. 0/1/2/3 port control message follows + 0x80 set non-port control message follows + */ + u8 portNumber, + + /* + there are three types of "commands" sent in the control message: + + 1. configuration changes which must be requested by setting + the corresponding "set" flag (and should only be requested + when necessary, to reduce overhead on the USA26): + */ + setClocking, // host requests baud rate be set + baudLo, // host does baud divisor calculation + baudHi, // baudHi is only used for first port (gives lower rates) + prescaler, // specified as N/8; values 8-ff are valid + // must be set any time internal baud rate is set; + txClocking, // 0=internal, 1=external/DSR + rxClocking, // 0=internal, 1=external/DSR + + setLcr, // host requests lcr be set + lcr, // use PARITY, STOPBITS, DATABITS below + + setFlowControl, // host requests flow control be set + ctsFlowControl, // 1=use CTS flow control, 0=don't + xonFlowControl, // 1=use XON/XOFF flow control, 0=don't + xonChar, // specified in current character format + xoffChar, // specified in current character format + + setRts, // host requests RTS output be set + rts, // 1=active, 0=inactive + + setDtr, // host requests DTR output be set + dtr; // 1=on, 0=off + + + /* + 3. configuration data which is simply used as is (no overhead, + but must be specified correctly in every host message). + */ + u8 forwardingLength, // forward when this number of chars available + dsrFlowControl, // 1=use DSR flow control, 0=don't + txAckThreshold, // 0=not allowed, 1=normal, 2-255 deliver ACK faster + loopbackMode; // 0=no loopback, 1=loopback enabled + + /* + 4. commands which are flags only; these are processed in order + (so that, e.g., if both _txOn and _txOff flags are set, the + port ends in a TX_OFF state); any non-zero value is respected + */ + u8 _txOn, // enable transmitting (and continue if there's data) + _txOff, // stop transmitting + txFlush, // toss outbound data + txBreak, // turn on break (cleared by _txOn) + rxOn, // turn on receiver + rxOff, // turn off receiver + rxFlush, // toss inbound data + rxForward, // forward all inbound data, NOW (as if fwdLen==1) + returnStatus, // return current status (even if it hasn't changed) + resetDataToggle,// reset data toggle state to DATA0 + enablePort, // start servicing port (move data, check status) + disablePort; // stop servicing port (does implicit tx/rx flush/off) + +}; + +// defines for bits in lcr +#define USA_DATABITS_5 0x00 +#define USA_DATABITS_6 0x01 +#define USA_DATABITS_7 0x02 +#define USA_DATABITS_8 0x03 +#define STOPBITS_5678_1 0x00 // 1 stop bit for all byte sizes +#define STOPBITS_5_1p5 0x04 // 1.5 stop bits for 5-bit byte +#define STOPBITS_678_2 0x04 // 2 stop bits for 6/7/8-bit byte +#define USA_PARITY_NONE 0x00 +#define USA_PARITY_ODD 0x08 +#define USA_PARITY_EVEN 0x18 +#define PARITY_1 0x28 +#define PARITY_0 0x38 + +/* + during normal operation, status messages are returned + to the host whenever the board detects changes. In some + circumstances (e.g. Windows), status messages from the + device cause problems; to shut them off, the host issues + a control message with the disableStatusMessages flags + set (to any non-zero value). The device will respond to + this message, and then suppress further status messages; + it will resume sending status messages any time the host + sends any control message (either global or port-specific). +*/ + +struct keyspan_usa49_globalControlMessage +{ + u8 portNumber, // 0x80 + sendGlobalStatus, // 1/2=number of status responses requested + resetStatusToggle, // 1=reset global status toggle + resetStatusCount, // a cycling value + remoteWakeupEnable, // 0x10=P1, 0x20=P2, 0x40=P3, 0x80=P4 + disableStatusMessages; // 1=send no status until host talks +}; + +/* + Device->host messages send on the global status endpoint + + portNumber message + ---------- -------------------- + 0x00,0x01,0x02,0x03 portStatusMessage + 0x80 globalStatusMessage + 0x81 globalDebugMessage +*/ + +struct keyspan_usa49_portStatusMessage // one for each port +{ + u8 portNumber, // 0,1,2,3 + cts, // reports CTS pin + dcd, // reports DCD pin + dsr, // reports DSR pin + ri, // reports RI pin + _txOff, // transmit has been disabled (by host) + _txXoff, // transmit is in XOFF state (either host or RX XOFF) + rxEnabled, // as configured by rxOn/rxOff 1=on, 0=off + controlResponse,// 1=a control message has been processed + txAck, // ACK (data TX complete) + rs232valid; // RS-232 signal valid +}; + +// bits in RX data message when STAT byte is included +#define RXERROR_OVERRUN 0x02 +#define RXERROR_PARITY 0x04 +#define RXERROR_FRAMING 0x08 +#define RXERROR_BREAK 0x10 + +struct keyspan_usa49_globalStatusMessage +{ + u8 portNumber, // 0x80=globalStatusMessage + sendGlobalStatus, // from request, decremented + resetStatusCount; // as in request +}; + +struct keyspan_usa49_globalDebugMessage +{ + u8 portNumber, // 0x81=globalDebugMessage + n, // typically a count/status byte + b; // typically a data byte +}; + +// ie: the maximum length of an EZUSB endpoint buffer +#define MAX_DATA_LEN 64 + +// update status approx. 60 times a second (16.6666 ms) +#define STATUS_UPDATE_INTERVAL 16 + +// status rationing tuning value (each port gets checked each n ms) +#define STATUS_RATION 10 + +#endif diff --git a/drivers/usb/serial/keyspan_usa67msg.h b/drivers/usb/serial/keyspan_usa67msg.h new file mode 100644 index 00000000..20fa3e2f --- /dev/null +++ b/drivers/usb/serial/keyspan_usa67msg.h @@ -0,0 +1,254 @@ +/* + usa67msg.h + + Copyright (c) 1998-2007 InnoSys Incorporated. All Rights Reserved + This file is available under a BSD-style copyright + + Keyspan USB Async Firmware to run on Anchor FX1 + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain this licence text + without modification, this list of conditions, and the following + disclaimer. The following copyright notice must appear immediately at + the beginning of all source files: + + Copyright (c) 1998-2007 InnoSys Incorporated. All Rights Reserved + + This file is available under a BSD-style copyright + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of InnoSys Incorprated may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Fourth revision: This message format supports the USA28XG + + Buffer formats for RX/TX data messages are not defined by + a structure, but are described here: + + USB OUT (host -> USAxx, transmit) messages contain a + REQUEST_ACK indicator (set to 0xff to request an ACK at the + completion of transmit; 0x00 otherwise), followed by data: + + RQSTACK DAT DAT DAT ... + + with a total data length of up to 63. + + USB IN (USAxx -> host, receive) messages begin with a status + byte in which the 0x80 bit is either: + + (a) 0x80 bit clear + indicates that the bytes following it are all data + bytes: + + STAT DATA DATA DATA DATA DATA ... + + for a total of up to 63 DATA bytes, + + or: + + (b) 0x80 bit set + indiates that the bytes following alternate data and + status bytes: + + STAT DATA STAT DATA STAT DATA STAT DATA ... + + for a total of up to 32 DATA bytes. + + The valid bits in the STAT bytes are: + + OVERRUN 0x02 + PARITY 0x04 + FRAMING 0x08 + BREAK 0x10 + + Notes: + + (1) The OVERRUN bit can appear in either (a) or (b) format + messages, but the but the PARITY/FRAMING/BREAK bits + only appear in (b) format messages. + (2) For the host to determine the exact point at which the + overrun occurred (to identify the point in the data + stream at which the data was lost), it needs to count + 128 characters, starting at the first character of the + message in which OVERRUN was reported; the lost character(s) + would have been received between the 128th and 129th + characters. + (3) An RX data message in which the first byte has 0x80 clear + serves as a "break off" indicator. + + revision history: + + 1999feb10 add reportHskiaChanges to allow us to ignore them + 1999feb10 add txAckThreshold for fast+loose throughput enhancement + 1999mar30 beef up support for RX error reporting + 1999apr14 add resetDataToggle to control message + 2000jan04 merge with usa17msg.h + 2000jun01 add extended BSD-style copyright text + 2001jul05 change message format to improve OVERRUN case + 2002jun05 update copyright date, improve comments + 2006feb06 modify for FX1 chip + +*/ + +#ifndef __USA67MSG__ +#define __USA67MSG__ + + +// all things called "ControlMessage" are sent on the 'control' endpoint + +typedef struct keyspan_usa67_portControlMessage +{ + u8 port; // 0 or 1 (selects port) + /* + there are three types of "commands" sent in the control message: + + 1. configuration changes which must be requested by setting + the corresponding "set" flag (and should only be requested + when necessary, to reduce overhead on the device): + */ + u8 setClocking, // host requests baud rate be set + baudLo, // host does baud divisor calculation + baudHi, // baudHi is only used for first port (gives lower rates) + externalClock_txClocking, + // 0=internal, other=external + + setLcr, // host requests lcr be set + lcr, // use PARITY, STOPBITS, DATABITS below + + setFlowControl, // host requests flow control be set + ctsFlowControl, // 1=use CTS flow control, 0=don't + xonFlowControl, // 1=use XON/XOFF flow control, 0=don't + xonChar, // specified in current character format + xoffChar, // specified in current character format + + setTxTriState_setRts, + // host requests TX tri-state be set + txTriState_rts, // 1=active (normal), 0=tristate (off) + + setHskoa_setDtr, + // host requests HSKOA output be set + hskoa_dtr, // 1=on, 0=off + + setPrescaler, // host requests prescalar be set (default: 13) + prescaler; // specified as N/8; values 8-ff are valid + // must be set any time internal baud rate is set; + // must not be set when external clocking is used + + /* + 3. configuration data which is simply used as is (no overhead, + but must be specified correctly in every host message). + */ + u8 forwardingLength, // forward when this number of chars available + reportHskiaChanges_dsrFlowControl, + // 1=normal; 0=ignore external clock + // 1=use DSR flow control, 0=don't + txAckThreshold, // 0=not allowed, 1=normal, 2-255 deliver ACK faster + loopbackMode; // 0=no loopback, 1=loopback enabled + + /* + 4. commands which are flags only; these are processed in order + (so that, e.g., if both _txOn and _txOff flags are set, the + port ends in a TX_OFF state); any non-zero value is respected + */ + u8 _txOn, // enable transmitting (and continue if there's data) + _txOff, // stop transmitting + txFlush, // toss outbound data + txBreak, // turn on break (cleared by _txOn) + rxOn, // turn on receiver + rxOff, // turn off receiver + rxFlush, // toss inbound data + rxForward, // forward all inbound data, NOW (as if fwdLen==1) + returnStatus, // return current status (even if it hasn't changed) + resetDataToggle;// reset data toggle state to DATA0 + +} keyspan_usa67_portControlMessage; + +// defines for bits in lcr +#define USA_DATABITS_5 0x00 +#define USA_DATABITS_6 0x01 +#define USA_DATABITS_7 0x02 +#define USA_DATABITS_8 0x03 +#define STOPBITS_5678_1 0x00 // 1 stop bit for all byte sizes +#define STOPBITS_5_1p5 0x04 // 1.5 stop bits for 5-bit byte +#define STOPBITS_678_2 0x04 // 2 stop bits for 6/7/8-bit byte +#define USA_PARITY_NONE 0x00 +#define USA_PARITY_ODD 0x08 +#define USA_PARITY_EVEN 0x18 +#define PARITY_1 0x28 +#define PARITY_0 0x38 + +// all things called "StatusMessage" are sent on the status endpoint + +typedef struct keyspan_usa67_portStatusMessage // one for each port +{ + u8 port, // 0=first, 1=second, other=see below + hskia_cts, // reports HSKIA pin + gpia_dcd, // reports GPIA pin + _txOff, // port has been disabled (by host) + _txXoff, // port is in XOFF state (either host or RX XOFF) + txAck, // indicates a TX message acknowledgement + rxEnabled, // as configured by rxOn/rxOff 1=on, 0=off + controlResponse;// 1=a control message has been processed +} keyspan_usa67_portStatusMessage; + +// bits in RX data message when STAT byte is included +#define RXERROR_OVERRUN 0x02 +#define RXERROR_PARITY 0x04 +#define RXERROR_FRAMING 0x08 +#define RXERROR_BREAK 0x10 + +typedef struct keyspan_usa67_globalControlMessage +{ + u8 port, // 3 + sendGlobalStatus, // 2=request for two status responses + resetStatusToggle, // 1=reset global status toggle + resetStatusCount; // a cycling value +} keyspan_usa67_globalControlMessage; + +typedef struct keyspan_usa67_globalStatusMessage +{ + u8 port, // 3 + sendGlobalStatus, // from request, decremented + resetStatusCount; // as in request +} keyspan_usa67_globalStatusMessage; + +typedef struct keyspan_usa67_globalDebugMessage +{ + u8 port, // 2 + a, + b, + c, + d; +} keyspan_usa67_globalDebugMessage; + +// ie: the maximum length of an FX1 endpoint buffer +#define MAX_DATA_LEN 64 + +// update status approx. 60 times a second (16.6666 ms) +#define STATUS_UPDATE_INTERVAL 16 + +// status rationing tuning value (each port gets checked each n ms) +#define STATUS_RATION 10 + +#endif + + diff --git a/drivers/usb/serial/keyspan_usa90msg.h b/drivers/usb/serial/keyspan_usa90msg.h new file mode 100644 index 00000000..86708ecd --- /dev/null +++ b/drivers/usb/serial/keyspan_usa90msg.h @@ -0,0 +1,198 @@ +/* + usa90msg.h + + Copyright (c) 1998-2003 InnoSys Incorporated. All Rights Reserved + This file is available under a BSD-style copyright + + Keyspan USB Async Message Formats for the USA19HS + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain this licence text + without modification, this list of conditions, and the following + disclaimer. The following copyright notice must appear immediately at + the beginning of all source files: + + Copyright (c) 1998-2003 InnoSys Incorporated. All Rights Reserved + + This file is available under a BSD-style copyright + + 2. The name of InnoSys Incorporated may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY INNOSYS CORP. ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Revisions: + + 2003feb14 add setTxMode/txMode and cancelRxXoff to portControl + 2003mar21 change name of PARITY_0/1 to add MARK/SPACE +*/ + +#ifndef __USA90MSG__ +#define __USA90MSG__ + +struct keyspan_usa90_portControlMessage +{ + /* + there are three types of "commands" sent in the control message: + + 1. configuration changes which must be requested by setting + the corresponding "set" flag (and should only be requested + when necessary, to reduce overhead on the device): + */ + + u8 setClocking, // host requests baud rate be set + baudLo, // host does baud divisor calculation + baudHi, // host does baud divisor calculation + + setLcr, // host requests lcr be set + lcr, // use PARITY, STOPBITS, DATABITS below + + setRxMode, // set receive mode + rxMode, // RXMODE_DMA or RXMODE_BYHAND + + setTxMode, // set transmit mode + txMode, // TXMODE_DMA or TXMODE_BYHAND + + setTxFlowControl, // host requests tx flow control be set + txFlowControl , // use TX_FLOW... bits below + setRxFlowControl, // host requests rx flow control be set + rxFlowControl, // use RX_FLOW... bits below + sendXoff, // host requests XOFF transmitted immediately + sendXon, // host requests XON char transmitted + xonChar, // specified in current character format + xoffChar, // specified in current character format + + sendChar, // host requests char transmitted immediately + txChar, // character to send + + setRts, // host requests RTS output be set + rts, // 1=on, 0=off + setDtr, // host requests DTR output be set + dtr; // 1=on, 0=off + + + /* + 2. configuration data which is simply used as is + and must be specified correctly in every host message. + */ + + u8 rxForwardingLength, // forward when this number of chars available + rxForwardingTimeout, // (1-31 in ms) + txAckSetting; // 0=don't ack, 1=normal, 2-255 TBD... + /* + 3. Firmware states which cause actions if they change + and must be specified correctly in every host message. + */ + + u8 portEnabled, // 0=disabled, 1=enabled + txFlush, // 0=normal, 1=toss outbound data + txBreak, // 0=break off, 1=break on + loopbackMode; // 0=no loopback, 1=loopback enabled + + /* + 4. commands which are flags only; these are processed in order + (so that, e.g., if rxFlush and rxForward flags are set, the + port will have no data to forward); any non-zero value + is respected + */ + + u8 rxFlush, // toss inbound data + rxForward, // forward all inbound data, NOW (as if fwdLen==1) + cancelRxXoff, // cancel any receive XOFF state (_txXoff) + returnStatus; // return current status NOW +}; + +// defines for bits in lcr +#define USA_DATABITS_5 0x00 +#define USA_DATABITS_6 0x01 +#define USA_DATABITS_7 0x02 +#define USA_DATABITS_8 0x03 +#define STOPBITS_5678_1 0x00 // 1 stop bit for all byte sizes +#define STOPBITS_5_1p5 0x04 // 1.5 stop bits for 5-bit byte +#define STOPBITS_678_2 0x04 // 2 stop bits for 6-8 bit byte +#define USA_PARITY_NONE 0x00 +#define USA_PARITY_ODD 0x08 +#define USA_PARITY_EVEN 0x18 +#define PARITY_MARK_1 0x28 // force parity MARK +#define PARITY_SPACE_0 0x38 // force parity SPACE + +#define TXFLOW_CTS 0x04 +#define TXFLOW_DSR 0x08 +#define TXFLOW_XOFF 0x01 +#define TXFLOW_XOFF_ANY 0x02 +#define TXFLOW_XOFF_BITS (TXFLOW_XOFF | TXFLOW_XOFF_ANY) + +#define RXFLOW_XOFF 0x10 +#define RXFLOW_RTS 0x20 +#define RXFLOW_DTR 0x40 +#define RXFLOW_DSR_SENSITIVITY 0x80 + +#define RXMODE_BYHAND 0x00 +#define RXMODE_DMA 0x02 + +#define TXMODE_BYHAND 0x00 +#define TXMODE_DMA 0x02 + + +// all things called "StatusMessage" are sent on the status endpoint + +struct keyspan_usa90_portStatusMessage +{ + u8 msr, // reports the actual MSR register + cts, // reports CTS pin + dcd, // reports DCD pin + dsr, // reports DSR pin + ri, // reports RI pin + _txXoff, // port is in XOFF state (we received XOFF) + rxBreak, // reports break state + rxOverrun, // count of overrun errors (since last reported) + rxParity, // count of parity errors (since last reported) + rxFrame, // count of frame errors (since last reported) + portState, // PORTSTATE_xxx bits (useful for debugging) + messageAck, // message acknowledgement + charAck, // character acknowledgement + controlResponse; // (value = returnStatus) a control message has been processed +}; + +// bits in RX data message when STAT byte is included + +#define RXERROR_OVERRUN 0x02 +#define RXERROR_PARITY 0x04 +#define RXERROR_FRAMING 0x08 +#define RXERROR_BREAK 0x10 + +#define PORTSTATE_ENABLED 0x80 +#define PORTSTATE_TXFLUSH 0x01 +#define PORTSTATE_TXBREAK 0x02 +#define PORTSTATE_LOOPBACK 0x04 + +// MSR bits + +#define USA_MSR_dCTS 0x01 // CTS has changed since last report +#define USA_MSR_dDSR 0x02 +#define USA_MSR_dRI 0x04 +#define USA_MSR_dDCD 0x08 + +#define USA_MSR_CTS 0x10 // current state of CTS +#define USA_MSR_DSR 0x20 +#define USA_USA_MSR_RI 0x40 +#define MSR_DCD 0x80 + +// ie: the maximum length of an endpoint buffer +#define MAX_DATA_LEN 64 + +#endif diff --git a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c new file mode 100644 index 00000000..1b4054fe --- /dev/null +++ b/drivers/usb/serial/kl5kusb105.c @@ -0,0 +1,643 @@ +/* + * KLSI KL5KUSB105 chip RS232 converter driver + * + * Copyright (C) 2010 Johan Hovold + * Copyright (C) 2001 Utz-Uwe Haus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * All information about the device was acquired using SniffUSB ans snoopUSB + * on Windows98. + * It was written out of frustration with the PalmConnect USB Serial adapter + * sold by Palm Inc. + * Neither Palm, nor their contractor (MCCI) or their supplier (KLSI) provided + * information that was not already available. + * + * It seems that KLSI bought some silicon-design information from ScanLogic, + * whose SL11R processor is at the core of the KL5KUSB chipset from KLSI. + * KLSI has firmware available for their devices; it is probable that the + * firmware differs from that used by KLSI in their products. If you have an + * original KLSI device and can provide some information on it, I would be + * most interested in adding support for it here. If you have any information + * on the protocol used (or find errors in my reverse-engineered stuff), please + * let me know. + * + * The code was only tested with a PalmConnect USB adapter; if you + * are adventurous, try it with any KLSI-based device and let me know how it + * breaks so that I can fix it! + */ + +/* TODO: + * check modem line signals + * implement handshaking or decide that we do not support it + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kl5kusb105.h" + +#define DRIVER_AUTHOR "Utz-Uwe Haus , Johan Hovold " +#define DRIVER_DESC "KLSI KL5KUSB105 chipset USB->Serial Converter driver" + + +/* + * Function prototypes + */ +static int klsi_105_port_probe(struct usb_serial_port *port); +static int klsi_105_port_remove(struct usb_serial_port *port); +static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port); +static void klsi_105_close(struct usb_serial_port *port); +static void klsi_105_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old); +static int klsi_105_tiocmget(struct tty_struct *tty); +static int klsi_105_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static void klsi_105_process_read_urb(struct urb *urb); +static int klsi_105_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size); + +/* + * All of the device info needed for the KLSI converters. + */ +static const struct usb_device_id id_table[] = { + { USB_DEVICE(PALMCONNECT_VID, PALMCONNECT_PID) }, + { USB_DEVICE(KLSI_VID, KLSI_KL5KUSB105D_PID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver kl5kusb105d_device = { + .driver = { + .owner = THIS_MODULE, + .name = "kl5kusb105d", + }, + .description = "KL5KUSB105D / PalmConnect", + .id_table = id_table, + .num_ports = 1, + .bulk_out_size = 64, + .open = klsi_105_open, + .close = klsi_105_close, + .set_termios = klsi_105_set_termios, + /*.break_ctl = klsi_105_break_ctl,*/ + .tiocmget = klsi_105_tiocmget, + .tiocmset = klsi_105_tiocmset, + .port_probe = klsi_105_port_probe, + .port_remove = klsi_105_port_remove, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .process_read_urb = klsi_105_process_read_urb, + .prepare_write_buffer = klsi_105_prepare_write_buffer, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &kl5kusb105d_device, NULL +}; + +struct klsi_105_port_settings { + __u8 pktlen; /* always 5, it seems */ + __u8 baudrate; + __u8 databits; + __u8 unknown1; + __u8 unknown2; +} __attribute__ ((packed)); + +struct klsi_105_private { + struct klsi_105_port_settings cfg; + struct ktermios termios; + unsigned long line_state; /* modem line settings */ + spinlock_t lock; +}; + + +/* + * Handle vendor specific USB requests + */ + + +#define KLSI_TIMEOUT 5000 /* default urb timeout */ + +static int klsi_105_chg_port_settings(struct usb_serial_port *port, + struct klsi_105_port_settings *settings) +{ + int rc; + + rc = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + KL5KUSB105A_SIO_SET_DATA, + USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_INTERFACE, + 0, /* value */ + 0, /* index */ + settings, + sizeof(struct klsi_105_port_settings), + KLSI_TIMEOUT); + if (rc < 0) + dev_err(&port->dev, + "Change port settings failed (error = %d)\n", rc); + dev_info(&port->serial->dev->dev, + "%d byte block, baudrate %x, databits %d, u1 %d, u2 %d\n", + settings->pktlen, settings->baudrate, settings->databits, + settings->unknown1, settings->unknown2); + return rc; +} + +/* translate a 16-bit status value from the device to linux's TIO bits */ +static unsigned long klsi_105_status2linestate(const __u16 status) +{ + unsigned long res = 0; + + res = ((status & KL5KUSB105A_DSR) ? TIOCM_DSR : 0) + | ((status & KL5KUSB105A_CTS) ? TIOCM_CTS : 0) + ; + + return res; +} + +/* + * Read line control via vendor command and return result through + * *line_state_p + */ +/* It seems that the status buffer has always only 2 bytes length */ +#define KLSI_STATUSBUF_LEN 2 +static int klsi_105_get_line_state(struct usb_serial_port *port, + unsigned long *line_state_p) +{ + int rc; + u8 *status_buf; + __u16 status; + + dev_info(&port->serial->dev->dev, "sending SIO Poll request\n"); + + status_buf = kmalloc(KLSI_STATUSBUF_LEN, GFP_KERNEL); + if (!status_buf) { + dev_err(&port->dev, "%s - out of memory for status buffer.\n", + __func__); + return -ENOMEM; + } + status_buf[0] = 0xff; + status_buf[1] = 0xff; + rc = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + KL5KUSB105A_SIO_POLL, + USB_TYPE_VENDOR | USB_DIR_IN, + 0, /* value */ + 0, /* index */ + status_buf, KLSI_STATUSBUF_LEN, + 10000 + ); + if (rc < 0) + dev_err(&port->dev, "Reading line status failed (error = %d)\n", + rc); + else { + status = get_unaligned_le16(status_buf); + + dev_info(&port->serial->dev->dev, "read status %x %x", + status_buf[0], status_buf[1]); + + *line_state_p = klsi_105_status2linestate(status); + } + + kfree(status_buf); + return rc; +} + + +/* + * Driver's tty interface functions + */ + +static int klsi_105_port_probe(struct usb_serial_port *port) +{ + struct klsi_105_private *priv; + + priv = kmalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* set initial values for control structures */ + priv->cfg.pktlen = 5; + priv->cfg.baudrate = kl5kusb105a_sio_b9600; + priv->cfg.databits = kl5kusb105a_dtb_8; + priv->cfg.unknown1 = 0; + priv->cfg.unknown2 = 1; + + priv->line_state = 0; + + spin_lock_init(&priv->lock); + + /* priv->termios is left uninitialized until port opening */ + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int klsi_105_port_remove(struct usb_serial_port *port) +{ + struct klsi_105_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct klsi_105_private *priv = usb_get_serial_port_data(port); + int retval = 0; + int rc; + int i; + unsigned long line_state; + struct klsi_105_port_settings *cfg; + unsigned long flags; + + /* Do a defined restart: + * Set up sane default baud rate and send the 'READ_ON' + * vendor command. + * FIXME: set modem line control (how?) + * Then read the modem line control and store values in + * priv->line_state. + */ + cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) { + dev_err(&port->dev, "%s - out of memory for config buffer.\n", + __func__); + return -ENOMEM; + } + cfg->pktlen = 5; + cfg->baudrate = kl5kusb105a_sio_b9600; + cfg->databits = kl5kusb105a_dtb_8; + cfg->unknown1 = 0; + cfg->unknown2 = 1; + klsi_105_chg_port_settings(port, cfg); + + /* set up termios structure */ + spin_lock_irqsave(&priv->lock, flags); + priv->termios.c_iflag = tty->termios.c_iflag; + priv->termios.c_oflag = tty->termios.c_oflag; + priv->termios.c_cflag = tty->termios.c_cflag; + priv->termios.c_lflag = tty->termios.c_lflag; + for (i = 0; i < NCCS; i++) + priv->termios.c_cc[i] = tty->termios.c_cc[i]; + priv->cfg.pktlen = cfg->pktlen; + priv->cfg.baudrate = cfg->baudrate; + priv->cfg.databits = cfg->databits; + priv->cfg.unknown1 = cfg->unknown1; + priv->cfg.unknown2 = cfg->unknown2; + spin_unlock_irqrestore(&priv->lock, flags); + + /* READ_ON and urb submission */ + rc = usb_serial_generic_open(tty, port); + if (rc) { + retval = rc; + goto exit; + } + + rc = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + KL5KUSB105A_SIO_CONFIGURE, + USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE, + KL5KUSB105A_SIO_CONFIGURE_READ_ON, + 0, /* index */ + NULL, + 0, + KLSI_TIMEOUT); + if (rc < 0) { + dev_err(&port->dev, "Enabling read failed (error = %d)\n", rc); + retval = rc; + } else + dev_dbg(&port->dev, "%s - enabled reading\n", __func__); + + rc = klsi_105_get_line_state(port, &line_state); + if (rc >= 0) { + spin_lock_irqsave(&priv->lock, flags); + priv->line_state = line_state; + spin_unlock_irqrestore(&priv->lock, flags); + dev_dbg(&port->dev, "%s - read line state 0x%lx\n", __func__, line_state); + retval = 0; + } else + retval = rc; + +exit: + kfree(cfg); + return retval; +} + +static void klsi_105_close(struct usb_serial_port *port) +{ + int rc; + + /* send READ_OFF */ + rc = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + KL5KUSB105A_SIO_CONFIGURE, + USB_TYPE_VENDOR | USB_DIR_OUT, + KL5KUSB105A_SIO_CONFIGURE_READ_OFF, + 0, /* index */ + NULL, 0, + KLSI_TIMEOUT); + if (rc < 0) + dev_err(&port->dev, "failed to disable read: %d\n", rc); + + /* shutdown our bulk reads and writes */ + usb_serial_generic_close(port); +} + +/* We need to write a complete 64-byte data block and encode the + * number actually sent in the first double-byte, LSB-order. That + * leaves at most 62 bytes of payload. + */ +#define KLSI_HDR_LEN 2 +static int klsi_105_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size) +{ + unsigned char *buf = dest; + int count; + + count = kfifo_out_locked(&port->write_fifo, buf + KLSI_HDR_LEN, size, + &port->lock); + put_unaligned_le16(count, buf); + + return count + KLSI_HDR_LEN; +} + +/* The data received is preceded by a length double-byte in LSB-first order. + */ +static void klsi_105_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned len; + + /* empty urbs seem to happen, we ignore them */ + if (!urb->actual_length) + return; + + if (urb->actual_length <= KLSI_HDR_LEN) { + dev_dbg(&port->dev, "%s - malformed packet\n", __func__); + return; + } + + len = get_unaligned_le16(data); + if (len > urb->actual_length - KLSI_HDR_LEN) { + dev_dbg(&port->dev, "%s - packet length mismatch\n", __func__); + len = urb->actual_length - KLSI_HDR_LEN; + } + + tty_insert_flip_string(&port->port, data + KLSI_HDR_LEN, len); + tty_flip_buffer_push(&port->port); +} + +static void klsi_105_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct klsi_105_private *priv = usb_get_serial_port_data(port); + struct device *dev = &port->dev; + unsigned int iflag = tty->termios.c_iflag; + unsigned int old_iflag = old_termios->c_iflag; + unsigned int cflag = tty->termios.c_cflag; + unsigned int old_cflag = old_termios->c_cflag; + struct klsi_105_port_settings *cfg; + unsigned long flags; + speed_t baud; + + cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) { + dev_err(dev, "%s - out of memory for config buffer.\n", __func__); + return; + } + + /* lock while we are modifying the settings */ + spin_lock_irqsave(&priv->lock, flags); + + /* + * Update baud rate + */ + baud = tty_get_baud_rate(tty); + + if ((cflag & CBAUD) != (old_cflag & CBAUD)) { + /* reassert DTR and (maybe) RTS on transition from B0 */ + if ((old_cflag & CBAUD) == B0) { + dev_dbg(dev, "%s: baud was B0\n", __func__); +#if 0 + priv->control_state |= TIOCM_DTR; + /* don't set RTS if using hardware flow control */ + if (!(old_cflag & CRTSCTS)) + priv->control_state |= TIOCM_RTS; + mct_u232_set_modem_ctrl(serial, priv->control_state); +#endif + } + } + switch (baud) { + case 0: /* handled below */ + break; + case 1200: + priv->cfg.baudrate = kl5kusb105a_sio_b1200; + break; + case 2400: + priv->cfg.baudrate = kl5kusb105a_sio_b2400; + break; + case 4800: + priv->cfg.baudrate = kl5kusb105a_sio_b4800; + break; + case 9600: + priv->cfg.baudrate = kl5kusb105a_sio_b9600; + break; + case 19200: + priv->cfg.baudrate = kl5kusb105a_sio_b19200; + break; + case 38400: + priv->cfg.baudrate = kl5kusb105a_sio_b38400; + break; + case 57600: + priv->cfg.baudrate = kl5kusb105a_sio_b57600; + break; + case 115200: + priv->cfg.baudrate = kl5kusb105a_sio_b115200; + break; + default: + dev_dbg(dev, "KLSI USB->Serial converter: unsupported baudrate request, using default of 9600"); + priv->cfg.baudrate = kl5kusb105a_sio_b9600; + baud = 9600; + break; + } + if ((cflag & CBAUD) == B0) { + dev_dbg(dev, "%s: baud is B0\n", __func__); + /* Drop RTS and DTR */ + /* maybe this should be simulated by sending read + * disable and read enable messages? + */ + ; +#if 0 + priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS); + mct_u232_set_modem_ctrl(serial, priv->control_state); +#endif + } + tty_encode_baud_rate(tty, baud, baud); + + if ((cflag & CSIZE) != (old_cflag & CSIZE)) { + /* set the number of data bits */ + switch (cflag & CSIZE) { + case CS5: + dev_dbg(dev, "%s - 5 bits/byte not supported\n", __func__); + spin_unlock_irqrestore(&priv->lock, flags); + goto err; + case CS6: + dev_dbg(dev, "%s - 6 bits/byte not supported\n", __func__); + spin_unlock_irqrestore(&priv->lock, flags); + goto err; + case CS7: + priv->cfg.databits = kl5kusb105a_dtb_7; + break; + case CS8: + priv->cfg.databits = kl5kusb105a_dtb_8; + break; + default: + dev_err(dev, "CSIZE was not CS5-CS8, using default of 8\n"); + priv->cfg.databits = kl5kusb105a_dtb_8; + break; + } + } + + /* + * Update line control register (LCR) + */ + if ((cflag & (PARENB|PARODD)) != (old_cflag & (PARENB|PARODD)) + || (cflag & CSTOPB) != (old_cflag & CSTOPB)) { + /* Not currently supported */ + tty->termios.c_cflag &= ~(PARENB|PARODD|CSTOPB); +#if 0 + priv->last_lcr = 0; + + /* set the parity */ + if (cflag & PARENB) + priv->last_lcr |= (cflag & PARODD) ? + MCT_U232_PARITY_ODD : MCT_U232_PARITY_EVEN; + else + priv->last_lcr |= MCT_U232_PARITY_NONE; + + /* set the number of stop bits */ + priv->last_lcr |= (cflag & CSTOPB) ? + MCT_U232_STOP_BITS_2 : MCT_U232_STOP_BITS_1; + + mct_u232_set_line_ctrl(serial, priv->last_lcr); +#endif + ; + } + /* + * Set flow control: well, I do not really now how to handle DTR/RTS. + * Just do what we have seen with SniffUSB on Win98. + */ + if ((iflag & IXOFF) != (old_iflag & IXOFF) + || (iflag & IXON) != (old_iflag & IXON) + || (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) { + /* Not currently supported */ + tty->termios.c_cflag &= ~CRTSCTS; + /* Drop DTR/RTS if no flow control otherwise assert */ +#if 0 + if ((iflag & IXOFF) || (iflag & IXON) || (cflag & CRTSCTS)) + priv->control_state |= TIOCM_DTR | TIOCM_RTS; + else + priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS); + mct_u232_set_modem_ctrl(serial, priv->control_state); +#endif + ; + } + memcpy(cfg, &priv->cfg, sizeof(*cfg)); + spin_unlock_irqrestore(&priv->lock, flags); + + /* now commit changes to device */ + klsi_105_chg_port_settings(port, cfg); +err: + kfree(cfg); +} + +#if 0 +static void mct_u232_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + struct mct_u232_private *priv = + (struct mct_u232_private *)port->private; + unsigned char lcr = priv->last_lcr; + + dev_dbg(&port->dev, "%s - state=%d\n", __func__, break_state); + + /* LOCKING */ + if (break_state) + lcr |= MCT_U232_SET_BREAK; + + mct_u232_set_line_ctrl(serial, lcr); +} +#endif + +static int klsi_105_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct klsi_105_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int rc; + unsigned long line_state; + + rc = klsi_105_get_line_state(port, &line_state); + if (rc < 0) { + dev_err(&port->dev, + "Reading line control failed (error = %d)\n", rc); + /* better return value? EAGAIN? */ + return rc; + } + + spin_lock_irqsave(&priv->lock, flags); + priv->line_state = line_state; + spin_unlock_irqrestore(&priv->lock, flags); + dev_dbg(&port->dev, "%s - read line state 0x%lx\n", __func__, line_state); + return (int)line_state; +} + +static int klsi_105_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + int retval = -EINVAL; + +/* if this ever gets implemented, it should be done something like this: + struct usb_serial *serial = port->serial; + struct klsi_105_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int control; + + spin_lock_irqsave (&priv->lock, flags); + if (set & TIOCM_RTS) + priv->control_state |= TIOCM_RTS; + if (set & TIOCM_DTR) + priv->control_state |= TIOCM_DTR; + if (clear & TIOCM_RTS) + priv->control_state &= ~TIOCM_RTS; + if (clear & TIOCM_DTR) + priv->control_state &= ~TIOCM_DTR; + control = priv->control_state; + spin_unlock_irqrestore (&priv->lock, flags); + retval = mct_u232_set_modem_ctrl(serial, control); +*/ + return retval; +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/kl5kusb105.h b/drivers/usb/serial/kl5kusb105.h new file mode 100644 index 00000000..22a90bad --- /dev/null +++ b/drivers/usb/serial/kl5kusb105.h @@ -0,0 +1,68 @@ +/* + * Definitions for the KLSI KL5KUSB105 serial port adapter + */ + +/* vendor/product pairs that are known to contain this chipset */ +#define PALMCONNECT_VID 0x0830 +#define PALMCONNECT_PID 0x0080 + +#define KLSI_VID 0x05e9 +#define KLSI_KL5KUSB105D_PID 0x00c0 + +/* Vendor commands: */ + + +/* port table -- the chip supports up to 4 channels */ + +/* baud rates */ + +enum { + kl5kusb105a_sio_b115200 = 0, + kl5kusb105a_sio_b57600 = 1, + kl5kusb105a_sio_b38400 = 2, + kl5kusb105a_sio_b19200 = 4, + kl5kusb105a_sio_b14400 = 5, + kl5kusb105a_sio_b9600 = 6, + kl5kusb105a_sio_b4800 = 8, /* unchecked */ + kl5kusb105a_sio_b2400 = 9, /* unchecked */ + kl5kusb105a_sio_b1200 = 0xa, /* unchecked */ + kl5kusb105a_sio_b600 = 0xb /* unchecked */ +}; + +/* data bits */ +#define kl5kusb105a_dtb_7 7 +#define kl5kusb105a_dtb_8 8 + + + +/* requests: */ +#define KL5KUSB105A_SIO_SET_DATA 1 +#define KL5KUSB105A_SIO_POLL 2 +#define KL5KUSB105A_SIO_CONFIGURE 3 +/* values used for request KL5KUSB105A_SIO_CONFIGURE */ +#define KL5KUSB105A_SIO_CONFIGURE_READ_ON 3 +#define KL5KUSB105A_SIO_CONFIGURE_READ_OFF 2 + +/* Interpretation of modem status lines */ +/* These need sorting out by individually connecting pins and checking + * results. FIXME! + * When data is being sent we see 0x30 in the lower byte; this must + * contain DSR and CTS ... + */ +#define KL5KUSB105A_DSR ((1<<4) | (1<<5)) +#define KL5KUSB105A_CTS ((1<<5) | (1<<4)) + +#define KL5KUSB105A_WANTS_TO_SEND 0x30 +#if 0 +#define KL5KUSB105A_DTR /* Data Terminal Ready */ +#define KL5KUSB105A_CTS /* Clear To Send */ +#define KL5KUSB105A_CD /* Carrier Detect */ +#define KL5KUSB105A_DSR /* Data Set Ready */ +#define KL5KUSB105A_RxD /* Receive pin */ + +#define KL5KUSB105A_LE +#define KL5KUSB105A_RTS +#define KL5KUSB105A_ST +#define KL5KUSB105A_SR +#define KL5KUSB105A_RI /* Ring Indicator */ +#endif diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c new file mode 100644 index 00000000..78b48c31 --- /dev/null +++ b/drivers/usb/serial/kobil_sct.c @@ -0,0 +1,573 @@ +/* + * KOBIL USB Smart Card Terminal Driver + * + * Copyright (C) 2002 KOBIL Systems GmbH + * Author: Thomas Wahrenbruch + * + * Contact: linuxusb@kobil.de + * + * This program is largely derived from work by the linux-usb group + * and associated source files. Please see the usb/serial files for + * individual credits and copyrights. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Thanks to Greg Kroah-Hartman (greg@kroah.com) for his help and + * patience. + * + * Supported readers: USB TWIN, KAAN Standard Plus and SecOVID Reader Plus + * (Adapter K), B1 Professional and KAAN Professional (Adapter B) + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kobil_sct.h" + +#define DRIVER_AUTHOR "KOBIL Systems GmbH - http://www.kobil.com" +#define DRIVER_DESC "KOBIL USB Smart Card Terminal Driver (experimental)" + +#define KOBIL_VENDOR_ID 0x0D46 +#define KOBIL_ADAPTER_B_PRODUCT_ID 0x2011 +#define KOBIL_ADAPTER_K_PRODUCT_ID 0x2012 +#define KOBIL_USBTWIN_PRODUCT_ID 0x0078 +#define KOBIL_KAAN_SIM_PRODUCT_ID 0x0081 + +#define KOBIL_TIMEOUT 500 +#define KOBIL_BUF_LENGTH 300 + + +/* Function prototypes */ +static int kobil_port_probe(struct usb_serial_port *probe); +static int kobil_port_remove(struct usb_serial_port *probe); +static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port); +static void kobil_close(struct usb_serial_port *port); +static int kobil_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +static int kobil_write_room(struct tty_struct *tty); +static int kobil_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg); +static int kobil_tiocmget(struct tty_struct *tty); +static int kobil_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static void kobil_read_int_callback(struct urb *urb); +static void kobil_write_int_callback(struct urb *urb); +static void kobil_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old); +static void kobil_init_termios(struct tty_struct *tty); + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_ADAPTER_B_PRODUCT_ID) }, + { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_ADAPTER_K_PRODUCT_ID) }, + { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_USBTWIN_PRODUCT_ID) }, + { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_KAAN_SIM_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver kobil_device = { + .driver = { + .owner = THIS_MODULE, + .name = "kobil", + }, + .description = "KOBIL USB smart card terminal", + .id_table = id_table, + .num_ports = 1, + .port_probe = kobil_port_probe, + .port_remove = kobil_port_remove, + .ioctl = kobil_ioctl, + .set_termios = kobil_set_termios, + .init_termios = kobil_init_termios, + .tiocmget = kobil_tiocmget, + .tiocmset = kobil_tiocmset, + .open = kobil_open, + .close = kobil_close, + .write = kobil_write, + .write_room = kobil_write_room, + .read_int_callback = kobil_read_int_callback, + .write_int_callback = kobil_write_int_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &kobil_device, NULL +}; + +struct kobil_private { + unsigned char buf[KOBIL_BUF_LENGTH]; /* buffer for the APDU to send */ + int filled; /* index of the last char in buf */ + int cur_pos; /* index of the next char to send in buf */ + __u16 device_type; +}; + + +static int kobil_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct kobil_private *priv; + + priv = kmalloc(sizeof(struct kobil_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->filled = 0; + priv->cur_pos = 0; + priv->device_type = le16_to_cpu(serial->dev->descriptor.idProduct); + + switch (priv->device_type) { + case KOBIL_ADAPTER_B_PRODUCT_ID: + dev_dbg(&serial->dev->dev, "KOBIL B1 PRO / KAAN PRO detected\n"); + break; + case KOBIL_ADAPTER_K_PRODUCT_ID: + dev_dbg(&serial->dev->dev, "KOBIL KAAN Standard Plus / SecOVID Reader Plus detected\n"); + break; + case KOBIL_USBTWIN_PRODUCT_ID: + dev_dbg(&serial->dev->dev, "KOBIL USBTWIN detected\n"); + break; + case KOBIL_KAAN_SIM_PRODUCT_ID: + dev_dbg(&serial->dev->dev, "KOBIL KAAN SIM detected\n"); + break; + } + usb_set_serial_port_data(port, priv); + + return 0; +} + + +static int kobil_port_remove(struct usb_serial_port *port) +{ + struct kobil_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static void kobil_init_termios(struct tty_struct *tty) +{ + /* Default to echo off and other sane device settings */ + tty->termios.c_lflag = 0; + tty->termios.c_iflag &= ~(ISIG | ICANON | ECHO | IEXTEN | XCASE); + tty->termios.c_iflag |= IGNBRK | IGNPAR | IXOFF; + /* do NOT translate CR to CR-NL (0x0A -> 0x0A 0x0D) */ + tty->termios.c_oflag &= ~ONLCR; +} + +static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct device *dev = &port->dev; + int result = 0; + struct kobil_private *priv; + unsigned char *transfer_buffer; + int transfer_buffer_length = 8; + + priv = usb_get_serial_port_data(port); + + /* allocate memory for transfer buffer */ + transfer_buffer = kzalloc(transfer_buffer_length, GFP_KERNEL); + if (!transfer_buffer) + return -ENOMEM; + + /* get hardware version */ + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_GetMisc, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_IN, + SUSBCR_MSC_GetHWVersion, + 0, + transfer_buffer, + transfer_buffer_length, + KOBIL_TIMEOUT + ); + dev_dbg(dev, "%s - Send get_HW_version URB returns: %i\n", __func__, result); + dev_dbg(dev, "Harware version: %i.%i.%i\n", transfer_buffer[0], + transfer_buffer[1], transfer_buffer[2]); + + /* get firmware version */ + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_GetMisc, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_IN, + SUSBCR_MSC_GetFWVersion, + 0, + transfer_buffer, + transfer_buffer_length, + KOBIL_TIMEOUT + ); + dev_dbg(dev, "%s - Send get_FW_version URB returns: %i\n", __func__, result); + dev_dbg(dev, "Firmware version: %i.%i.%i\n", transfer_buffer[0], + transfer_buffer[1], transfer_buffer[2]); + + if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID || + priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) { + /* Setting Baudrate, Parity and Stopbits */ + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_SetBaudRateParityAndStopBits, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, + SUSBCR_SBR_9600 | SUSBCR_SPASB_EvenParity | + SUSBCR_SPASB_1StopBit, + 0, + transfer_buffer, + 0, + KOBIL_TIMEOUT + ); + dev_dbg(dev, "%s - Send set_baudrate URB returns: %i\n", __func__, result); + + /* reset all queues */ + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_Misc, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, + SUSBCR_MSC_ResetAllQueues, + 0, + transfer_buffer, + 0, + KOBIL_TIMEOUT + ); + dev_dbg(dev, "%s - Send reset_all_queues URB returns: %i\n", __func__, result); + } + if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID || + priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID || + priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { + /* start reading (Adapter B 'cause PNP string) */ + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + dev_dbg(dev, "%s - Send read URB returns: %i\n", __func__, result); + } + + kfree(transfer_buffer); + return 0; +} + + +static void kobil_close(struct usb_serial_port *port) +{ + /* FIXME: Add rts/dtr methods */ + usb_kill_urb(port->interrupt_out_urb); + usb_kill_urb(port->interrupt_in_urb); +} + + +static void kobil_read_int_callback(struct urb *urb) +{ + int result; + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + if (status) { + dev_dbg(&port->dev, "%s - Read int status not zero: %d\n", __func__, status); + return; + } + + if (urb->actual_length) { + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, + data); + tty_insert_flip_string(&port->port, data, urb->actual_length); + tty_flip_buffer_push(&port->port); + } + + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + dev_dbg(&port->dev, "%s - Send read URB returns: %i\n", __func__, result); +} + + +static void kobil_write_int_callback(struct urb *urb) +{ +} + + +static int kobil_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + int length = 0; + int result = 0; + int todo = 0; + struct kobil_private *priv; + + if (count == 0) { + dev_dbg(&port->dev, "%s - write request of 0 bytes\n", __func__); + return 0; + } + + priv = usb_get_serial_port_data(port); + + if (count > (KOBIL_BUF_LENGTH - priv->filled)) { + dev_dbg(&port->dev, "%s - Error: write request bigger than buffer size\n", __func__); + return -ENOMEM; + } + + /* Copy data to buffer */ + memcpy(priv->buf + priv->filled, buf, count); + usb_serial_debug_data(&port->dev, __func__, count, priv->buf + priv->filled); + priv->filled = priv->filled + count; + + /* only send complete block. TWIN, KAAN SIM and adapter K + use the same protocol. */ + if (((priv->device_type != KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 2) && (priv->filled >= (priv->buf[1] + 3))) || + ((priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 3) && (priv->filled >= (priv->buf[2] + 4)))) { + /* stop reading (except TWIN and KAAN SIM) */ + if ((priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) + || (priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID)) + usb_kill_urb(port->interrupt_in_urb); + + todo = priv->filled - priv->cur_pos; + + while (todo > 0) { + /* max 8 byte in one urb (endpoint size) */ + length = min(todo, port->interrupt_out_size); + /* copy data to transfer buffer */ + memcpy(port->interrupt_out_buffer, + priv->buf + priv->cur_pos, length); + port->interrupt_out_urb->transfer_buffer_length = length; + + priv->cur_pos = priv->cur_pos + length; + result = usb_submit_urb(port->interrupt_out_urb, GFP_NOIO); + dev_dbg(&port->dev, "%s - Send write URB returns: %i\n", __func__, result); + todo = priv->filled - priv->cur_pos; + + if (todo > 0) + msleep(24); + } + + priv->filled = 0; + priv->cur_pos = 0; + + /* start reading (except TWIN and KAAN SIM) */ + if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID || + priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) { + result = usb_submit_urb(port->interrupt_in_urb, + GFP_NOIO); + dev_dbg(&port->dev, "%s - Send read URB returns: %i\n", __func__, result); + } + } + return count; +} + + +static int kobil_write_room(struct tty_struct *tty) +{ + /* FIXME */ + return 8; +} + + +static int kobil_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct kobil_private *priv; + int result; + unsigned char *transfer_buffer; + int transfer_buffer_length = 8; + + priv = usb_get_serial_port_data(port); + if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID + || priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { + /* This device doesn't support ioctl calls */ + return -EINVAL; + } + + /* allocate memory for transfer buffer */ + transfer_buffer = kzalloc(transfer_buffer_length, GFP_KERNEL); + if (!transfer_buffer) + return -ENOMEM; + + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_GetStatusLineState, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_IN, + 0, + 0, + transfer_buffer, + transfer_buffer_length, + KOBIL_TIMEOUT); + + dev_dbg(&port->dev, "%s - Send get_status_line_state URB returns: %i. Statusline: %02x\n", + __func__, result, transfer_buffer[0]); + + result = 0; + if ((transfer_buffer[0] & SUSBCR_GSL_DSR) != 0) + result = TIOCM_DSR; + kfree(transfer_buffer); + return result; +} + +static int kobil_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct device *dev = &port->dev; + struct kobil_private *priv; + int result; + int dtr = 0; + int rts = 0; + unsigned char *transfer_buffer; + int transfer_buffer_length = 8; + + /* FIXME: locking ? */ + priv = usb_get_serial_port_data(port); + if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID + || priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { + /* This device doesn't support ioctl calls */ + return -EINVAL; + } + + /* allocate memory for transfer buffer */ + transfer_buffer = kzalloc(transfer_buffer_length, GFP_KERNEL); + if (!transfer_buffer) + return -ENOMEM; + + if (set & TIOCM_RTS) + rts = 1; + if (set & TIOCM_DTR) + dtr = 1; + if (clear & TIOCM_RTS) + rts = 0; + if (clear & TIOCM_DTR) + dtr = 0; + + if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) { + if (dtr != 0) + dev_dbg(dev, "%s - Setting DTR\n", __func__); + else + dev_dbg(dev, "%s - Clearing DTR\n", __func__); + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_SetStatusLinesOrQueues, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, + ((dtr != 0) ? SUSBCR_SSL_SETDTR : SUSBCR_SSL_CLRDTR), + 0, + transfer_buffer, + 0, + KOBIL_TIMEOUT); + } else { + if (rts != 0) + dev_dbg(dev, "%s - Setting RTS\n", __func__); + else + dev_dbg(dev, "%s - Clearing RTS\n", __func__); + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_SetStatusLinesOrQueues, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, + ((rts != 0) ? SUSBCR_SSL_SETRTS : SUSBCR_SSL_CLRRTS), + 0, + transfer_buffer, + 0, + KOBIL_TIMEOUT); + } + dev_dbg(dev, "%s - Send set_status_line URB returns: %i\n", __func__, result); + kfree(transfer_buffer); + return (result < 0) ? result : 0; +} + +static void kobil_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old) +{ + struct kobil_private *priv; + int result; + unsigned short urb_val = 0; + int c_cflag = tty->termios.c_cflag; + speed_t speed; + + priv = usb_get_serial_port_data(port); + if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID || + priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { + /* This device doesn't support ioctl calls */ + tty_termios_copy_hw(&tty->termios, old); + return; + } + + speed = tty_get_baud_rate(tty); + switch (speed) { + case 1200: + urb_val = SUSBCR_SBR_1200; + break; + default: + speed = 9600; + case 9600: + urb_val = SUSBCR_SBR_9600; + break; + } + urb_val |= (c_cflag & CSTOPB) ? SUSBCR_SPASB_2StopBits : + SUSBCR_SPASB_1StopBit; + if (c_cflag & PARENB) { + if (c_cflag & PARODD) + urb_val |= SUSBCR_SPASB_OddParity; + else + urb_val |= SUSBCR_SPASB_EvenParity; + } else + urb_val |= SUSBCR_SPASB_NoParity; + tty->termios.c_cflag &= ~CMSPAR; + tty_encode_baud_rate(tty, speed, speed); + + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_SetBaudRateParityAndStopBits, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, + urb_val, + 0, + NULL, + 0, + KOBIL_TIMEOUT + ); +} + +static int kobil_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct kobil_private *priv = usb_get_serial_port_data(port); + unsigned char *transfer_buffer; + int transfer_buffer_length = 8; + int result; + + if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID || + priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) + /* This device doesn't support ioctl calls */ + return -ENOIOCTLCMD; + + switch (cmd) { + case TCFLSH: + transfer_buffer = kmalloc(transfer_buffer_length, GFP_KERNEL); + if (!transfer_buffer) + return -ENOBUFS; + + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + SUSBCRequest_Misc, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, + SUSBCR_MSC_ResetAllQueues, + 0, + NULL, /* transfer_buffer, */ + 0, + KOBIL_TIMEOUT + ); + + dev_dbg(&port->dev, + "%s - Send reset_all_queues (FLUSH) URB returns: %i", __func__, result); + kfree(transfer_buffer); + return (result < 0) ? -EIO: 0; + default: + return -ENOIOCTLCMD; + } +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/kobil_sct.h b/drivers/usb/serial/kobil_sct.h new file mode 100644 index 00000000..be207f71 --- /dev/null +++ b/drivers/usb/serial/kobil_sct.h @@ -0,0 +1,77 @@ +#define SUSBCRequest_SetBaudRateParityAndStopBits 1 +#define SUSBCR_SBR_MASK 0xFF00 +#define SUSBCR_SBR_1200 0x0100 +#define SUSBCR_SBR_9600 0x0200 +#define SUSBCR_SBR_19200 0x0400 +#define SUSBCR_SBR_28800 0x0800 +#define SUSBCR_SBR_38400 0x1000 +#define SUSBCR_SBR_57600 0x2000 +#define SUSBCR_SBR_115200 0x4000 + +#define SUSBCR_SPASB_MASK 0x0070 +#define SUSBCR_SPASB_NoParity 0x0010 +#define SUSBCR_SPASB_OddParity 0x0020 +#define SUSBCR_SPASB_EvenParity 0x0040 + +#define SUSBCR_SPASB_STPMASK 0x0003 +#define SUSBCR_SPASB_1StopBit 0x0001 +#define SUSBCR_SPASB_2StopBits 0x0002 + +#define SUSBCRequest_SetStatusLinesOrQueues 2 +#define SUSBCR_SSL_SETRTS 0x0001 +#define SUSBCR_SSL_CLRRTS 0x0002 +#define SUSBCR_SSL_SETDTR 0x0004 +#define SUSBCR_SSL_CLRDTR 0x0010 + +/* Kill the pending/current writes to the comm port. */ +#define SUSBCR_SSL_PURGE_TXABORT 0x0100 +/* Kill the pending/current reads to the comm port. */ +#define SUSBCR_SSL_PURGE_RXABORT 0x0200 +/* Kill the transmit queue if there. */ +#define SUSBCR_SSL_PURGE_TXCLEAR 0x0400 +/* Kill the typeahead buffer if there. */ +#define SUSBCR_SSL_PURGE_RXCLEAR 0x0800 + +#define SUSBCRequest_GetStatusLineState 4 +/* Any Character received */ +#define SUSBCR_GSL_RXCHAR 0x0001 +/* Transmitt Queue Empty */ +#define SUSBCR_GSL_TXEMPTY 0x0004 +/* CTS changed state */ +#define SUSBCR_GSL_CTS 0x0008 +/* DSR changed state */ +#define SUSBCR_GSL_DSR 0x0010 +/* RLSD changed state */ +#define SUSBCR_GSL_RLSD 0x0020 +/* BREAK received */ +#define SUSBCR_GSL_BREAK 0x0040 +/* Line status error occurred */ +#define SUSBCR_GSL_ERR 0x0080 +/* Ring signal detected */ +#define SUSBCR_GSL_RING 0x0100 + +#define SUSBCRequest_Misc 8 +/* use a predefined reset sequence */ +#define SUSBCR_MSC_ResetReader 0x0001 +/* use a predefined sequence to reset the internal queues */ +#define SUSBCR_MSC_ResetAllQueues 0x0002 + +#define SUSBCRequest_GetMisc 0x10 + +/* + * get the firmware version from device, coded like this 0xHHLLBBPP with + * HH = Firmware Version High Byte + * LL = Firmware Version Low Byte + * BB = Build Number + * PP = Further Attributes + */ +#define SUSBCR_MSC_GetFWVersion 0x0001 + +/* + * get the hardware version from device coded like this 0xHHLLPPRR with + * HH = Software Version High Byte + * LL = Software Version Low Byte + * PP = Further Attributes + * RR = Reserved for the hardware ID + */ +#define SUSBCR_MSC_GetHWVersion 0x0002 diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c new file mode 100644 index 00000000..6a15adf5 --- /dev/null +++ b/drivers/usb/serial/mct_u232.c @@ -0,0 +1,772 @@ +/* + * MCT (Magic Control Technology Corp.) USB RS232 Converter Driver + * + * Copyright (C) 2000 Wolfgang Grandegger (wolfgang@ces.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is largely derived from the Belkin USB Serial Adapter Driver + * (see belkin_sa.[ch]). All of the information about the device was acquired + * by using SniffUSB on Windows98. For technical details see mct_u232.h. + * + * William G. Greathouse and Greg Kroah-Hartman provided great help on how to + * do the reverse engineering and how to write a USB serial device driver. + * + * TO BE DONE, TO BE CHECKED: + * DTR/RTS signal handling may be incomplete or incorrect. I have mainly + * implemented what I have seen with SniffUSB or found in belkin_sa.c. + * For further TODOs check also belkin_sa.c. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mct_u232.h" + +#define DRIVER_AUTHOR "Wolfgang Grandegger " +#define DRIVER_DESC "Magic Control Technology USB-RS232 converter driver" + +/* + * Function prototypes + */ +static int mct_u232_port_probe(struct usb_serial_port *port); +static int mct_u232_port_remove(struct usb_serial_port *remove); +static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port); +static void mct_u232_close(struct usb_serial_port *port); +static void mct_u232_dtr_rts(struct usb_serial_port *port, int on); +static void mct_u232_read_int_callback(struct urb *urb); +static void mct_u232_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old); +static void mct_u232_break_ctl(struct tty_struct *tty, int break_state); +static int mct_u232_tiocmget(struct tty_struct *tty); +static int mct_u232_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static void mct_u232_throttle(struct tty_struct *tty); +static void mct_u232_unthrottle(struct tty_struct *tty); + + +/* + * All of the device info needed for the MCT USB-RS232 converter. + */ +static const struct usb_device_id id_table[] = { + { USB_DEVICE(MCT_U232_VID, MCT_U232_PID) }, + { USB_DEVICE(MCT_U232_VID, MCT_U232_SITECOM_PID) }, + { USB_DEVICE(MCT_U232_VID, MCT_U232_DU_H3SP_PID) }, + { USB_DEVICE(MCT_U232_BELKIN_F5U109_VID, MCT_U232_BELKIN_F5U109_PID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver mct_u232_device = { + .driver = { + .owner = THIS_MODULE, + .name = "mct_u232", + }, + .description = "MCT U232", + .id_table = id_table, + .num_ports = 1, + .open = mct_u232_open, + .close = mct_u232_close, + .dtr_rts = mct_u232_dtr_rts, + .throttle = mct_u232_throttle, + .unthrottle = mct_u232_unthrottle, + .read_int_callback = mct_u232_read_int_callback, + .set_termios = mct_u232_set_termios, + .break_ctl = mct_u232_break_ctl, + .tiocmget = mct_u232_tiocmget, + .tiocmset = mct_u232_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .port_probe = mct_u232_port_probe, + .port_remove = mct_u232_port_remove, + .get_icount = usb_serial_generic_get_icount, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &mct_u232_device, NULL +}; + +struct mct_u232_private { + struct urb *read_urb; + spinlock_t lock; + unsigned int control_state; /* Modem Line Setting (TIOCM) */ + unsigned char last_lcr; /* Line Control Register */ + unsigned char last_lsr; /* Line Status Register */ + unsigned char last_msr; /* Modem Status Register */ + unsigned int rx_flags; /* Throttling flags */ +}; + +#define THROTTLED 0x01 + +/* + * Handle vendor specific USB requests + */ + +#define WDR_TIMEOUT 5000 /* default urb timeout */ + +/* + * Later day 2.6.0-test kernels have new baud rates like B230400 which + * we do not know how to support. We ignore them for the moment. + */ +static int mct_u232_calculate_baud_rate(struct usb_serial *serial, + speed_t value, speed_t *result) +{ + *result = value; + + if (le16_to_cpu(serial->dev->descriptor.idProduct) == MCT_U232_SITECOM_PID + || le16_to_cpu(serial->dev->descriptor.idProduct) == MCT_U232_BELKIN_F5U109_PID) { + switch (value) { + case 300: + return 0x01; + case 600: + return 0x02; /* this one not tested */ + case 1200: + return 0x03; + case 2400: + return 0x04; + case 4800: + return 0x06; + case 9600: + return 0x08; + case 19200: + return 0x09; + case 38400: + return 0x0a; + case 57600: + return 0x0b; + case 115200: + return 0x0c; + default: + *result = 9600; + return 0x08; + } + } else { + /* FIXME: Can we use any divider - should we do + divider = 115200/value; + real baud = 115200/divider */ + switch (value) { + case 300: break; + case 600: break; + case 1200: break; + case 2400: break; + case 4800: break; + case 9600: break; + case 19200: break; + case 38400: break; + case 57600: break; + case 115200: break; + default: + value = 9600; + *result = 9600; + } + return 115200/value; + } +} + +static int mct_u232_set_baud_rate(struct tty_struct *tty, + struct usb_serial *serial, struct usb_serial_port *port, speed_t value) +{ + unsigned int divisor; + int rc; + unsigned char *buf; + unsigned char cts_enable_byte = 0; + speed_t speed; + + buf = kmalloc(MCT_U232_MAX_SIZE, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + divisor = mct_u232_calculate_baud_rate(serial, value, &speed); + put_unaligned_le32(cpu_to_le32(divisor), buf); + rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + MCT_U232_SET_BAUD_RATE_REQUEST, + MCT_U232_SET_REQUEST_TYPE, + 0, 0, buf, MCT_U232_SET_BAUD_RATE_SIZE, + WDR_TIMEOUT); + if (rc < 0) /*FIXME: What value speed results */ + dev_err(&port->dev, "Set BAUD RATE %d failed (error = %d)\n", + value, rc); + else + tty_encode_baud_rate(tty, speed, speed); + dev_dbg(&port->dev, "set_baud_rate: value: 0x%x, divisor: 0x%x\n", value, divisor); + + /* Mimic the MCT-supplied Windows driver (version 1.21P.0104), which + always sends two extra USB 'device request' messages after the + 'baud rate change' message. The actual functionality of the + request codes in these messages is not fully understood but these + particular codes are never seen in any operation besides a baud + rate change. Both of these messages send a single byte of data. + In the first message, the value of this byte is always zero. + + The second message has been determined experimentally to control + whether data will be transmitted to a device which is not asserting + the 'CTS' signal. If the second message's data byte is zero, data + will be transmitted even if 'CTS' is not asserted (i.e. no hardware + flow control). if the second message's data byte is nonzero (a + value of 1 is used by this driver), data will not be transmitted to + a device which is not asserting 'CTS'. + */ + + buf[0] = 0; + rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + MCT_U232_SET_UNKNOWN1_REQUEST, + MCT_U232_SET_REQUEST_TYPE, + 0, 0, buf, MCT_U232_SET_UNKNOWN1_SIZE, + WDR_TIMEOUT); + if (rc < 0) + dev_err(&port->dev, "Sending USB device request code %d " + "failed (error = %d)\n", MCT_U232_SET_UNKNOWN1_REQUEST, + rc); + + if (port && C_CRTSCTS(tty)) + cts_enable_byte = 1; + + dev_dbg(&port->dev, "set_baud_rate: send second control message, data = %02X\n", + cts_enable_byte); + buf[0] = cts_enable_byte; + rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + MCT_U232_SET_CTS_REQUEST, + MCT_U232_SET_REQUEST_TYPE, + 0, 0, buf, MCT_U232_SET_CTS_SIZE, + WDR_TIMEOUT); + if (rc < 0) + dev_err(&port->dev, "Sending USB device request code %d " + "failed (error = %d)\n", MCT_U232_SET_CTS_REQUEST, rc); + + kfree(buf); + return rc; +} /* mct_u232_set_baud_rate */ + +static int mct_u232_set_line_ctrl(struct usb_serial_port *port, + unsigned char lcr) +{ + int rc; + unsigned char *buf; + + buf = kmalloc(MCT_U232_MAX_SIZE, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = lcr; + rc = usb_control_msg(port->serial->dev, usb_sndctrlpipe(port->serial->dev, 0), + MCT_U232_SET_LINE_CTRL_REQUEST, + MCT_U232_SET_REQUEST_TYPE, + 0, 0, buf, MCT_U232_SET_LINE_CTRL_SIZE, + WDR_TIMEOUT); + if (rc < 0) + dev_err(&port->dev, "Set LINE CTRL 0x%x failed (error = %d)\n", lcr, rc); + dev_dbg(&port->dev, "set_line_ctrl: 0x%x\n", lcr); + kfree(buf); + return rc; +} /* mct_u232_set_line_ctrl */ + +static int mct_u232_set_modem_ctrl(struct usb_serial_port *port, + unsigned int control_state) +{ + int rc; + unsigned char mcr; + unsigned char *buf; + + buf = kmalloc(MCT_U232_MAX_SIZE, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + mcr = MCT_U232_MCR_NONE; + if (control_state & TIOCM_DTR) + mcr |= MCT_U232_MCR_DTR; + if (control_state & TIOCM_RTS) + mcr |= MCT_U232_MCR_RTS; + + buf[0] = mcr; + rc = usb_control_msg(port->serial->dev, usb_sndctrlpipe(port->serial->dev, 0), + MCT_U232_SET_MODEM_CTRL_REQUEST, + MCT_U232_SET_REQUEST_TYPE, + 0, 0, buf, MCT_U232_SET_MODEM_CTRL_SIZE, + WDR_TIMEOUT); + kfree(buf); + + dev_dbg(&port->dev, "set_modem_ctrl: state=0x%x ==> mcr=0x%x\n", control_state, mcr); + + if (rc < 0) { + dev_err(&port->dev, "Set MODEM CTRL 0x%x failed (error = %d)\n", mcr, rc); + return rc; + } + return 0; +} /* mct_u232_set_modem_ctrl */ + +static int mct_u232_get_modem_stat(struct usb_serial_port *port, + unsigned char *msr) +{ + int rc; + unsigned char *buf; + + buf = kmalloc(MCT_U232_MAX_SIZE, GFP_KERNEL); + if (buf == NULL) { + *msr = 0; + return -ENOMEM; + } + rc = usb_control_msg(port->serial->dev, usb_rcvctrlpipe(port->serial->dev, 0), + MCT_U232_GET_MODEM_STAT_REQUEST, + MCT_U232_GET_REQUEST_TYPE, + 0, 0, buf, MCT_U232_GET_MODEM_STAT_SIZE, + WDR_TIMEOUT); + if (rc < 0) { + dev_err(&port->dev, "Get MODEM STATus failed (error = %d)\n", rc); + *msr = 0; + } else { + *msr = buf[0]; + } + dev_dbg(&port->dev, "get_modem_stat: 0x%x\n", *msr); + kfree(buf); + return rc; +} /* mct_u232_get_modem_stat */ + +static void mct_u232_msr_to_icount(struct async_icount *icount, + unsigned char msr) +{ + /* Translate Control Line states */ + if (msr & MCT_U232_MSR_DDSR) + icount->dsr++; + if (msr & MCT_U232_MSR_DCTS) + icount->cts++; + if (msr & MCT_U232_MSR_DRI) + icount->rng++; + if (msr & MCT_U232_MSR_DCD) + icount->dcd++; +} /* mct_u232_msr_to_icount */ + +static void mct_u232_msr_to_state(struct usb_serial_port *port, + unsigned int *control_state, unsigned char msr) +{ + /* Translate Control Line states */ + if (msr & MCT_U232_MSR_DSR) + *control_state |= TIOCM_DSR; + else + *control_state &= ~TIOCM_DSR; + if (msr & MCT_U232_MSR_CTS) + *control_state |= TIOCM_CTS; + else + *control_state &= ~TIOCM_CTS; + if (msr & MCT_U232_MSR_RI) + *control_state |= TIOCM_RI; + else + *control_state &= ~TIOCM_RI; + if (msr & MCT_U232_MSR_CD) + *control_state |= TIOCM_CD; + else + *control_state &= ~TIOCM_CD; + dev_dbg(&port->dev, "msr_to_state: msr=0x%x ==> state=0x%x\n", msr, *control_state); +} /* mct_u232_msr_to_state */ + +/* + * Driver's tty interface functions + */ + +static int mct_u232_port_probe(struct usb_serial_port *port) +{ + struct mct_u232_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Use second interrupt-in endpoint for reading. */ + priv->read_urb = port->serial->port[1]->interrupt_in_urb; + priv->read_urb->context = port; + + spin_lock_init(&priv->lock); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int mct_u232_port_remove(struct usb_serial_port *port) +{ + struct mct_u232_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + int retval = 0; + unsigned int control_state; + unsigned long flags; + unsigned char last_lcr; + unsigned char last_msr; + + /* Compensate for a hardware bug: although the Sitecom U232-P25 + * device reports a maximum output packet size of 32 bytes, + * it seems to be able to accept only 16 bytes (and that's what + * SniffUSB says too...) + */ + if (le16_to_cpu(serial->dev->descriptor.idProduct) + == MCT_U232_SITECOM_PID) + port->bulk_out_size = 16; + + /* Do a defined restart: the normal serial device seems to + * always turn on DTR and RTS here, so do the same. I'm not + * sure if this is really necessary. But it should not harm + * either. + */ + spin_lock_irqsave(&priv->lock, flags); + if (tty && (tty->termios.c_cflag & CBAUD)) + priv->control_state = TIOCM_DTR | TIOCM_RTS; + else + priv->control_state = 0; + + priv->last_lcr = (MCT_U232_DATA_BITS_8 | + MCT_U232_PARITY_NONE | + MCT_U232_STOP_BITS_1); + control_state = priv->control_state; + last_lcr = priv->last_lcr; + spin_unlock_irqrestore(&priv->lock, flags); + mct_u232_set_modem_ctrl(port, control_state); + mct_u232_set_line_ctrl(port, last_lcr); + + /* Read modem status and update control state */ + mct_u232_get_modem_stat(port, &last_msr); + spin_lock_irqsave(&priv->lock, flags); + priv->last_msr = last_msr; + mct_u232_msr_to_state(port, &priv->control_state, priv->last_msr); + spin_unlock_irqrestore(&priv->lock, flags); + + retval = usb_submit_urb(priv->read_urb, GFP_KERNEL); + if (retval) { + dev_err(&port->dev, + "usb_submit_urb(read) failed pipe 0x%x err %d\n", + port->read_urb->pipe, retval); + goto error; + } + + retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (retval) { + usb_kill_urb(priv->read_urb); + dev_err(&port->dev, + "usb_submit_urb(read int) failed pipe 0x%x err %d", + port->interrupt_in_urb->pipe, retval); + goto error; + } + return 0; + +error: + return retval; +} /* mct_u232_open */ + +static void mct_u232_dtr_rts(struct usb_serial_port *port, int on) +{ + unsigned int control_state; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + + spin_lock_irq(&priv->lock); + if (on) + priv->control_state |= TIOCM_DTR | TIOCM_RTS; + else + priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS); + control_state = priv->control_state; + spin_unlock_irq(&priv->lock); + + mct_u232_set_modem_ctrl(port, control_state); +} + +static void mct_u232_close(struct usb_serial_port *port) +{ + struct mct_u232_private *priv = usb_get_serial_port_data(port); + + usb_kill_urb(priv->read_urb); + usb_kill_urb(port->interrupt_in_urb); + + usb_serial_generic_close(port); +} /* mct_u232_close */ + + +static void mct_u232_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + int retval; + int status = urb->status; + unsigned long flags; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + + /* + * Work-a-round: handle the 'usual' bulk-in pipe here + */ + if (urb->transfer_buffer_length > 2) { + if (urb->actual_length) { + tty_insert_flip_string(&port->port, data, + urb->actual_length); + tty_flip_buffer_push(&port->port); + } + goto exit; + } + + /* + * The interrupt-in pipe signals exceptional conditions (modem line + * signal changes and errors). data[0] holds MSR, data[1] holds LSR. + */ + spin_lock_irqsave(&priv->lock, flags); + priv->last_msr = data[MCT_U232_MSR_INDEX]; + + /* Record Control Line states */ + mct_u232_msr_to_state(port, &priv->control_state, priv->last_msr); + + mct_u232_msr_to_icount(&port->icount, priv->last_msr); + +#if 0 + /* Not yet handled. See belkin_sa.c for further information */ + /* Now to report any errors */ + priv->last_lsr = data[MCT_U232_LSR_INDEX]; + /* + * fill in the flip buffer here, but I do not know the relation + * to the current/next receive buffer or characters. I need + * to look in to this before committing any code. + */ + if (priv->last_lsr & MCT_U232_LSR_ERR) { + tty = tty_port_tty_get(&port->port); + /* Overrun Error */ + if (priv->last_lsr & MCT_U232_LSR_OE) { + } + /* Parity Error */ + if (priv->last_lsr & MCT_U232_LSR_PE) { + } + /* Framing Error */ + if (priv->last_lsr & MCT_U232_LSR_FE) { + } + /* Break Indicator */ + if (priv->last_lsr & MCT_U232_LSR_BI) { + } + tty_kref_put(tty); + } +#endif + wake_up_interruptible(&port->port.delta_msr_wait); + spin_unlock_irqrestore(&priv->lock, flags); +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, retval); +} /* mct_u232_read_int_callback */ + +static void mct_u232_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct usb_serial *serial = port->serial; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + struct ktermios *termios = &tty->termios; + unsigned int cflag = termios->c_cflag; + unsigned int old_cflag = old_termios->c_cflag; + unsigned long flags; + unsigned int control_state; + unsigned char last_lcr; + + /* get a local copy of the current port settings */ + spin_lock_irqsave(&priv->lock, flags); + control_state = priv->control_state; + spin_unlock_irqrestore(&priv->lock, flags); + last_lcr = 0; + + /* + * Update baud rate. + * Do not attempt to cache old rates and skip settings, + * disconnects screw such tricks up completely. + * Premature optimization is the root of all evil. + */ + + /* reassert DTR and RTS on transition from B0 */ + if ((old_cflag & CBAUD) == B0) { + dev_dbg(&port->dev, "%s: baud was B0\n", __func__); + control_state |= TIOCM_DTR | TIOCM_RTS; + mct_u232_set_modem_ctrl(port, control_state); + } + + mct_u232_set_baud_rate(tty, serial, port, tty_get_baud_rate(tty)); + + if ((cflag & CBAUD) == B0) { + dev_dbg(&port->dev, "%s: baud is B0\n", __func__); + /* Drop RTS and DTR */ + control_state &= ~(TIOCM_DTR | TIOCM_RTS); + mct_u232_set_modem_ctrl(port, control_state); + } + + /* + * Update line control register (LCR) + */ + + /* set the parity */ + if (cflag & PARENB) + last_lcr |= (cflag & PARODD) ? + MCT_U232_PARITY_ODD : MCT_U232_PARITY_EVEN; + else + last_lcr |= MCT_U232_PARITY_NONE; + + /* set the number of data bits */ + switch (cflag & CSIZE) { + case CS5: + last_lcr |= MCT_U232_DATA_BITS_5; break; + case CS6: + last_lcr |= MCT_U232_DATA_BITS_6; break; + case CS7: + last_lcr |= MCT_U232_DATA_BITS_7; break; + case CS8: + last_lcr |= MCT_U232_DATA_BITS_8; break; + default: + dev_err(&port->dev, + "CSIZE was not CS5-CS8, using default of 8\n"); + last_lcr |= MCT_U232_DATA_BITS_8; + break; + } + + termios->c_cflag &= ~CMSPAR; + + /* set the number of stop bits */ + last_lcr |= (cflag & CSTOPB) ? + MCT_U232_STOP_BITS_2 : MCT_U232_STOP_BITS_1; + + mct_u232_set_line_ctrl(port, last_lcr); + + /* save off the modified port settings */ + spin_lock_irqsave(&priv->lock, flags); + priv->control_state = control_state; + priv->last_lcr = last_lcr; + spin_unlock_irqrestore(&priv->lock, flags); +} /* mct_u232_set_termios */ + +static void mct_u232_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + unsigned char lcr; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + lcr = priv->last_lcr; + + if (break_state) + lcr |= MCT_U232_SET_BREAK; + spin_unlock_irqrestore(&priv->lock, flags); + + mct_u232_set_line_ctrl(port, lcr); +} /* mct_u232_break_ctl */ + + +static int mct_u232_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + unsigned int control_state; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + control_state = priv->control_state; + spin_unlock_irqrestore(&priv->lock, flags); + + return control_state; +} + +static int mct_u232_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + unsigned int control_state; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + control_state = priv->control_state; + + if (set & TIOCM_RTS) + control_state |= TIOCM_RTS; + if (set & TIOCM_DTR) + control_state |= TIOCM_DTR; + if (clear & TIOCM_RTS) + control_state &= ~TIOCM_RTS; + if (clear & TIOCM_DTR) + control_state &= ~TIOCM_DTR; + + priv->control_state = control_state; + spin_unlock_irqrestore(&priv->lock, flags); + return mct_u232_set_modem_ctrl(port, control_state); +} + +static void mct_u232_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + unsigned int control_state; + + spin_lock_irq(&priv->lock); + priv->rx_flags |= THROTTLED; + if (C_CRTSCTS(tty)) { + priv->control_state &= ~TIOCM_RTS; + control_state = priv->control_state; + spin_unlock_irq(&priv->lock); + mct_u232_set_modem_ctrl(port, control_state); + } else { + spin_unlock_irq(&priv->lock); + } +} + +static void mct_u232_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct mct_u232_private *priv = usb_get_serial_port_data(port); + unsigned int control_state; + + spin_lock_irq(&priv->lock); + if ((priv->rx_flags & THROTTLED) && C_CRTSCTS(tty)) { + priv->rx_flags &= ~THROTTLED; + priv->control_state |= TIOCM_RTS; + control_state = priv->control_state; + spin_unlock_irq(&priv->lock); + mct_u232_set_modem_ctrl(port, control_state); + } else { + spin_unlock_irq(&priv->lock); + } +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/mct_u232.h b/drivers/usb/serial/mct_u232.h new file mode 100644 index 00000000..d325bb8c --- /dev/null +++ b/drivers/usb/serial/mct_u232.h @@ -0,0 +1,467 @@ +/* + * Definitions for MCT (Magic Control Technology) USB-RS232 Converter Driver + * + * Copyright (C) 2000 Wolfgang Grandegger (wolfgang@ces.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is for the device MCT USB-RS232 Converter (25 pin, Model No. + * U232-P25) from Magic Control Technology Corp. (there is also a 9 pin + * Model No. U232-P9). See http://www.mct.com.tw/products/product_us232.html + * for further information. The properties of this device are listed at the end + * of this file. This device was used in the Dlink DSB-S25. + * + * All of the information about the device was acquired by using SniffUSB + * on Windows98. The technical details of the reverse engineering are + * summarized at the end of this file. + */ + +#ifndef __LINUX_USB_SERIAL_MCT_U232_H +#define __LINUX_USB_SERIAL_MCT_U232_H + +#define MCT_U232_VID 0x0711 /* Vendor Id */ +#define MCT_U232_PID 0x0210 /* Original MCT Product Id */ + +/* U232-P25, Sitecom */ +#define MCT_U232_SITECOM_PID 0x0230 /* Sitecom Product Id */ + +/* DU-H3SP USB BAY hub */ +#define MCT_U232_DU_H3SP_PID 0x0200 /* D-Link DU-H3SP USB BAY */ + +/* Belkin badge the MCT U232-P9 as the F5U109 */ +#define MCT_U232_BELKIN_F5U109_VID 0x050d /* Vendor Id */ +#define MCT_U232_BELKIN_F5U109_PID 0x0109 /* Product Id */ + +/* + * Vendor Request Interface + */ +#define MCT_U232_SET_REQUEST_TYPE 0x40 +#define MCT_U232_GET_REQUEST_TYPE 0xc0 + +/* Get Modem Status Register (MSR) */ +#define MCT_U232_GET_MODEM_STAT_REQUEST 2 +#define MCT_U232_GET_MODEM_STAT_SIZE 1 + +/* Get Line Control Register (LCR) */ +/* ... not used by this driver */ +#define MCT_U232_GET_LINE_CTRL_REQUEST 6 +#define MCT_U232_GET_LINE_CTRL_SIZE 1 + +/* Set Baud Rate Divisor */ +#define MCT_U232_SET_BAUD_RATE_REQUEST 5 +#define MCT_U232_SET_BAUD_RATE_SIZE 4 + +/* Set Line Control Register (LCR) */ +#define MCT_U232_SET_LINE_CTRL_REQUEST 7 +#define MCT_U232_SET_LINE_CTRL_SIZE 1 + +/* Set Modem Control Register (MCR) */ +#define MCT_U232_SET_MODEM_CTRL_REQUEST 10 +#define MCT_U232_SET_MODEM_CTRL_SIZE 1 + +/* + * This USB device request code is not well understood. It is transmitted by + * the MCT-supplied Windows driver whenever the baud rate changes. + */ +#define MCT_U232_SET_UNKNOWN1_REQUEST 11 /* Unknown functionality */ +#define MCT_U232_SET_UNKNOWN1_SIZE 1 + +/* + * This USB device request code appears to control whether CTS is required + * during transmission. + * + * Sending a zero byte allows data transmission to a device which is not + * asserting CTS. Sending a '1' byte will cause transmission to be deferred + * until the device asserts CTS. + */ +#define MCT_U232_SET_CTS_REQUEST 12 +#define MCT_U232_SET_CTS_SIZE 1 + +#define MCT_U232_MAX_SIZE 4 /* of MCT_XXX_SIZE */ + +/* + * Baud rate (divisor) + * Actually, there are two of them, MCT website calls them "Philips solution" + * and "Intel solution". They are the regular MCT and "Sitecom" for us. + * This is pointless to document in the header, see the code for the bits. + */ +static int mct_u232_calculate_baud_rate(struct usb_serial *serial, + speed_t value, speed_t *result); + +/* + * Line Control Register (LCR) + */ +#define MCT_U232_SET_BREAK 0x40 + +#define MCT_U232_PARITY_SPACE 0x38 +#define MCT_U232_PARITY_MARK 0x28 +#define MCT_U232_PARITY_EVEN 0x18 +#define MCT_U232_PARITY_ODD 0x08 +#define MCT_U232_PARITY_NONE 0x00 + +#define MCT_U232_DATA_BITS_5 0x00 +#define MCT_U232_DATA_BITS_6 0x01 +#define MCT_U232_DATA_BITS_7 0x02 +#define MCT_U232_DATA_BITS_8 0x03 + +#define MCT_U232_STOP_BITS_2 0x04 +#define MCT_U232_STOP_BITS_1 0x00 + +/* + * Modem Control Register (MCR) + */ +#define MCT_U232_MCR_NONE 0x8 /* Deactivate DTR and RTS */ +#define MCT_U232_MCR_RTS 0xa /* Activate RTS */ +#define MCT_U232_MCR_DTR 0x9 /* Activate DTR */ + +/* + * Modem Status Register (MSR) + */ +#define MCT_U232_MSR_INDEX 0x0 /* data[index] */ +#define MCT_U232_MSR_CD 0x80 /* Current CD */ +#define MCT_U232_MSR_RI 0x40 /* Current RI */ +#define MCT_U232_MSR_DSR 0x20 /* Current DSR */ +#define MCT_U232_MSR_CTS 0x10 /* Current CTS */ +#define MCT_U232_MSR_DCD 0x08 /* Delta CD */ +#define MCT_U232_MSR_DRI 0x04 /* Delta RI */ +#define MCT_U232_MSR_DDSR 0x02 /* Delta DSR */ +#define MCT_U232_MSR_DCTS 0x01 /* Delta CTS */ + +/* + * Line Status Register (LSR) + */ +#define MCT_U232_LSR_INDEX 1 /* data[index] */ +#define MCT_U232_LSR_ERR 0x80 /* OE | PE | FE | BI */ +#define MCT_U232_LSR_TEMT 0x40 /* transmit register empty */ +#define MCT_U232_LSR_THRE 0x20 /* transmit holding register empty */ +#define MCT_U232_LSR_BI 0x10 /* break indicator */ +#define MCT_U232_LSR_FE 0x08 /* framing error */ +#define MCT_U232_LSR_OE 0x02 /* overrun error */ +#define MCT_U232_LSR_PE 0x04 /* parity error */ +#define MCT_U232_LSR_OE 0x02 /* overrun error */ +#define MCT_U232_LSR_DR 0x01 /* receive data ready */ + + +/* ----------------------------------------------------------------------------- + * Technical Specification reverse engineered with SniffUSB on Windows98 + * ===================================================================== + * + * The technical details of the device have been acquired be using "SniffUSB" + * and the vendor-supplied device driver (version 2.3A) under Windows98. To + * identify the USB vendor-specific requests and to assign them to terminal + * settings (flow control, baud rate, etc.) the program "SerialSettings" from + * William G. Greathouse has been proven to be very useful. I also used the + * Win98 "HyperTerminal" and "usb-robot" on Linux for testing. The results and + * observations are summarized below: + * + * The USB requests seem to be directly mapped to the registers of a 8250, + * 16450 or 16550 UART. The FreeBSD handbook (appendix F.4 "Input/Output + * devices") contains a comprehensive description of UARTs and its registers. + * The bit descriptions are actually taken from there. + * + * + * Baud rate (divisor) + * ------------------- + * + * BmRequestType: 0x40 (0100 0000B) + * bRequest: 0x05 + * wValue: 0x0000 + * wIndex: 0x0000 + * wLength: 0x0004 + * Data: divisor = 115200 / baud_rate + * + * SniffUSB observations (Nov 2003): Contrary to the 'wLength' value of 4 + * shown above, observations with a Belkin F5U109 adapter, using the + * MCT-supplied Windows98 driver (U2SPORT.VXD, "File version: 1.21P.0104 for + * Win98/Me"), show this request has a length of 1 byte, presumably because + * of the fact that the Belkin adapter and the 'Sitecom U232-P25' adapter + * use a baud-rate code instead of a conventional RS-232 baud rate divisor. + * The current source code for this driver does not reflect this fact, but + * the driver works fine with this adapter/driver combination nonetheless. + * + * + * Line Control Register (LCR) + * --------------------------- + * + * BmRequestType: 0x40 (0100 0000B) 0xc0 (1100 0000B) + * bRequest: 0x07 0x06 + * wValue: 0x0000 + * wIndex: 0x0000 + * wLength: 0x0001 + * Data: LCR (see below) + * + * Bit 7: Divisor Latch Access Bit (DLAB). When set, access to the data + * transmit/receive register (THR/RBR) and the Interrupt Enable Register + * (IER) is disabled. Any access to these ports is now redirected to the + * Divisor Latch Registers. Setting this bit, loading the Divisor + * Registers, and clearing DLAB should be done with interrupts disabled. + * Bit 6: Set Break. When set to "1", the transmitter begins to transmit + * continuous Spacing until this bit is set to "0". This overrides any + * bits of characters that are being transmitted. + * Bit 5: Stick Parity. When parity is enabled, setting this bit causes parity + * to always be "1" or "0", based on the value of Bit 4. + * Bit 4: Even Parity Select (EPS). When parity is enabled and Bit 5 is "0", + * setting this bit causes even parity to be transmitted and expected. + * Otherwise, odd parity is used. + * Bit 3: Parity Enable (PEN). When set to "1", a parity bit is inserted + * between the last bit of the data and the Stop Bit. The UART will also + * expect parity to be present in the received data. + * Bit 2: Number of Stop Bits (STB). If set to "1" and using 5-bit data words, + * 1.5 Stop Bits are transmitted and expected in each data word. For + * 6, 7 and 8-bit data words, 2 Stop Bits are transmitted and expected. + * When this bit is set to "0", one Stop Bit is used on each data word. + * Bit 1: Word Length Select Bit #1 (WLSB1) + * Bit 0: Word Length Select Bit #0 (WLSB0) + * Together these bits specify the number of bits in each data word. + * 1 0 Word Length + * 0 0 5 Data Bits + * 0 1 6 Data Bits + * 1 0 7 Data Bits + * 1 1 8 Data Bits + * + * SniffUSB observations: Bit 7 seems not to be used. There seem to be two bugs + * in the Win98 driver: the break does not work (bit 6 is not asserted) and the + * stick parity bit is not cleared when set once. The LCR can also be read + * back with USB request 6 but this has never been observed with SniffUSB. + * + * + * Modem Control Register (MCR) + * ---------------------------- + * + * BmRequestType: 0x40 (0100 0000B) + * bRequest: 0x0a + * wValue: 0x0000 + * wIndex: 0x0000 + * wLength: 0x0001 + * Data: MCR (Bit 4..7, see below) + * + * Bit 7: Reserved, always 0. + * Bit 6: Reserved, always 0. + * Bit 5: Reserved, always 0. + * Bit 4: Loop-Back Enable. When set to "1", the UART transmitter and receiver + * are internally connected together to allow diagnostic operations. In + * addition, the UART modem control outputs are connected to the UART + * modem control inputs. CTS is connected to RTS, DTR is connected to + * DSR, OUT1 is connected to RI, and OUT 2 is connected to DCD. + * Bit 3: OUT 2. An auxiliary output that the host processor may set high or + * low. In the IBM PC serial adapter (and most clones), OUT 2 is used + * to tri-state (disable) the interrupt signal from the + * 8250/16450/16550 UART. + * Bit 2: OUT 1. An auxiliary output that the host processor may set high or + * low. This output is not used on the IBM PC serial adapter. + * Bit 1: Request to Send (RTS). When set to "1", the output of the UART -RTS + * line is Low (Active). + * Bit 0: Data Terminal Ready (DTR). When set to "1", the output of the UART + * -DTR line is Low (Active). + * + * SniffUSB observations: Bit 2 and 4 seem not to be used but bit 3 has been + * seen _always_ set. + * + * + * Modem Status Register (MSR) + * --------------------------- + * + * BmRequestType: 0xc0 (1100 0000B) + * bRequest: 0x02 + * wValue: 0x0000 + * wIndex: 0x0000 + * wLength: 0x0001 + * Data: MSR (see below) + * + * Bit 7: Data Carrier Detect (CD). Reflects the state of the DCD line on the + * UART. + * Bit 6: Ring Indicator (RI). Reflects the state of the RI line on the UART. + * Bit 5: Data Set Ready (DSR). Reflects the state of the DSR line on the UART. + * Bit 4: Clear To Send (CTS). Reflects the state of the CTS line on the UART. + * Bit 3: Delta Data Carrier Detect (DDCD). Set to "1" if the -DCD line has + * changed state one more more times since the last time the MSR was + * read by the host. + * Bit 2: Trailing Edge Ring Indicator (TERI). Set to "1" if the -RI line has + * had a low to high transition since the last time the MSR was read by + * the host. + * Bit 1: Delta Data Set Ready (DDSR). Set to "1" if the -DSR line has changed + * state one more more times since the last time the MSR was read by the + * host. + * Bit 0: Delta Clear To Send (DCTS). Set to "1" if the -CTS line has changed + * state one more times since the last time the MSR was read by the + * host. + * + * SniffUSB observations: the MSR is also returned as first byte on the + * interrupt-in endpoint 0x83 to signal changes of modem status lines. The USB + * request to read MSR cannot be applied during normal device operation. + * + * + * Line Status Register (LSR) + * -------------------------- + * + * Bit 7 Error in Receiver FIFO. On the 8250/16450 UART, this bit is zero. + * This bit is set to "1" when any of the bytes in the FIFO have one + * or more of the following error conditions: PE, FE, or BI. + * Bit 6 Transmitter Empty (TEMT). When set to "1", there are no words + * remaining in the transmit FIFO or the transmit shift register. The + * transmitter is completely idle. + * Bit 5 Transmitter Holding Register Empty (THRE). When set to "1", the + * FIFO (or holding register) now has room for at least one additional + * word to transmit. The transmitter may still be transmitting when + * this bit is set to "1". + * Bit 4 Break Interrupt (BI). The receiver has detected a Break signal. + * Bit 3 Framing Error (FE). A Start Bit was detected but the Stop Bit did + * not appear at the expected time. The received word is probably + * garbled. + * Bit 2 Parity Error (PE). The parity bit was incorrect for the word + * received. + * Bit 1 Overrun Error (OE). A new word was received and there was no room + * in the receive buffer. The newly-arrived word in the shift register + * is discarded. On 8250/16450 UARTs, the word in the holding register + * is discarded and the newly- arrived word is put in the holding + * register. + * Bit 0 Data Ready (DR). One or more words are in the receive FIFO that the + * host may read. A word must be completely received and moved from + * the shift register into the FIFO (or holding register for + * 8250/16450 designs) before this bit is set. + * + * SniffUSB observations: the LSR is returned as second byte on the + * interrupt-in endpoint 0x83 to signal error conditions. Such errors have + * been seen with minicom/zmodem transfers (CRC errors). + * + * + * Unknown #1 + * ------------------- + * + * BmRequestType: 0x40 (0100 0000B) + * bRequest: 0x0b + * wValue: 0x0000 + * wIndex: 0x0000 + * wLength: 0x0001 + * Data: 0x00 + * + * SniffUSB observations (Nov 2003): With the MCT-supplied Windows98 driver + * (U2SPORT.VXD, "File version: 1.21P.0104 for Win98/Me"), this request + * occurs immediately after a "Baud rate (divisor)" message. It was not + * observed at any other time. It is unclear what purpose this message + * serves. + * + * + * Unknown #2 + * ------------------- + * + * BmRequestType: 0x40 (0100 0000B) + * bRequest: 0x0c + * wValue: 0x0000 + * wIndex: 0x0000 + * wLength: 0x0001 + * Data: 0x00 + * + * SniffUSB observations (Nov 2003): With the MCT-supplied Windows98 driver + * (U2SPORT.VXD, "File version: 1.21P.0104 for Win98/Me"), this request + * occurs immediately after the 'Unknown #1' message (see above). It was + * not observed at any other time. It is unclear what other purpose (if + * any) this message might serve, but without it, the USB/RS-232 adapter + * will not write to RS-232 devices which do not assert the 'CTS' signal. + * + * + * Flow control + * ------------ + * + * SniffUSB observations: no flow control specific requests have been realized + * apart from DTR/RTS settings. Both signals are dropped for no flow control + * but asserted for hardware or software flow control. + * + * + * Endpoint usage + * -------------- + * + * SniffUSB observations: the bulk-out endpoint 0x1 and interrupt-in endpoint + * 0x81 is used to transmit and receive characters. The second interrupt-in + * endpoint 0x83 signals exceptional conditions like modem line changes and + * errors. The first byte returned is the MSR and the second byte the LSR. + * + * + * Other observations + * ------------------ + * + * Queued bulk transfers like used in visor.c did not work. + * + * + * Properties of the USB device used (as found in /var/log/messages) + * ----------------------------------------------------------------- + * + * Manufacturer: MCT Corporation. + * Product: USB-232 Interfact Controller + * SerialNumber: U2S22050 + * + * Length = 18 + * DescriptorType = 01 + * USB version = 1.00 + * Vendor:Product = 0711:0210 + * MaxPacketSize0 = 8 + * NumConfigurations = 1 + * Device version = 1.02 + * Device Class:SubClass:Protocol = 00:00:00 + * Per-interface classes + * Configuration: + * bLength = 9 + * bDescriptorType = 02 + * wTotalLength = 0027 + * bNumInterfaces = 01 + * bConfigurationValue = 01 + * iConfiguration = 00 + * bmAttributes = c0 + * MaxPower = 100mA + * + * Interface: 0 + * Alternate Setting: 0 + * bLength = 9 + * bDescriptorType = 04 + * bInterfaceNumber = 00 + * bAlternateSetting = 00 + * bNumEndpoints = 03 + * bInterface Class:SubClass:Protocol = 00:00:00 + * iInterface = 00 + * Endpoint: + * bLength = 7 + * bDescriptorType = 05 + * bEndpointAddress = 81 (in) + * bmAttributes = 03 (Interrupt) + * wMaxPacketSize = 0040 + * bInterval = 02 + * Endpoint: + * bLength = 7 + * bDescriptorType = 05 + * bEndpointAddress = 01 (out) + * bmAttributes = 02 (Bulk) + * wMaxPacketSize = 0040 + * bInterval = 00 + * Endpoint: + * bLength = 7 + * bDescriptorType = 05 + * bEndpointAddress = 83 (in) + * bmAttributes = 03 (Interrupt) + * wMaxPacketSize = 0002 + * bInterval = 02 + * + * + * Hardware details (added by Martin Hamilton, 2001/12/06) + * ----------------------------------------------------------------- + * + * This info was gleaned from opening a Belkin F5U109 DB9 USB serial + * adaptor, which turns out to simply be a re-badged U232-P9. We + * know this because there is a sticky label on the circuit board + * which says "U232-P9" ;-) + * + * The circuit board inside the adaptor contains a Philips PDIUSBD12 + * USB endpoint chip and a Philips P87C52UBAA microcontroller with + * embedded UART. Exhaustive documentation for these is available at: + * + * http://www.semiconductors.philips.com/pip/p87c52ubaa + * http://www.nxp.com/acrobat_download/various/PDIUSBD12_PROGRAMMING_GUIDE.pdf + * + * Thanks to Julian Highfield for the pointer to the Philips database. + * + */ + +#endif /* __LINUX_USB_SERIAL_MCT_U232_H */ + diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c new file mode 100644 index 00000000..47e24775 --- /dev/null +++ b/drivers/usb/serial/metro-usb.c @@ -0,0 +1,400 @@ +/* + Some of this code is credited to Linux USB open source files that are + distributed with Linux. + + Copyright: 2007 Metrologic Instruments. All rights reserved. + Copyright: 2011 Azimut Ltd. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_DESC "Metrologic Instruments Inc. - USB-POS driver" + +/* Product information. */ +#define FOCUS_VENDOR_ID 0x0C2E +#define FOCUS_PRODUCT_ID_BI 0x0720 +#define FOCUS_PRODUCT_ID_UNI 0x0700 + +#define METROUSB_SET_REQUEST_TYPE 0x40 +#define METROUSB_SET_MODEM_CTRL_REQUEST 10 +#define METROUSB_SET_BREAK_REQUEST 0x40 +#define METROUSB_MCR_NONE 0x08 /* Deactivate DTR and RTS. */ +#define METROUSB_MCR_RTS 0x0a /* Activate RTS. */ +#define METROUSB_MCR_DTR 0x09 /* Activate DTR. */ +#define WDR_TIMEOUT 5000 /* default urb timeout. */ + +/* Private data structure. */ +struct metrousb_private { + spinlock_t lock; + int throttled; + unsigned long control_state; +}; + +/* Device table list. */ +static struct usb_device_id id_table[] = { + { USB_DEVICE(FOCUS_VENDOR_ID, FOCUS_PRODUCT_ID_BI) }, + { USB_DEVICE(FOCUS_VENDOR_ID, FOCUS_PRODUCT_ID_UNI) }, + { }, /* Terminating entry. */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* UNI-Directional mode commands for device configure */ +#define UNI_CMD_OPEN 0x80 +#define UNI_CMD_CLOSE 0xFF + +inline int metrousb_is_unidirectional_mode(struct usb_serial_port *port) +{ + __u16 product_id = le16_to_cpu( + port->serial->dev->descriptor.idProduct); + + return product_id == FOCUS_PRODUCT_ID_UNI; +} + +static int metrousb_send_unidirectional_cmd(u8 cmd, struct usb_serial_port *port) +{ + int ret; + int actual_len; + u8 *buffer_cmd = NULL; + + if (!metrousb_is_unidirectional_mode(port)) + return 0; + + buffer_cmd = kzalloc(sizeof(cmd), GFP_KERNEL); + if (!buffer_cmd) + return -ENOMEM; + + *buffer_cmd = cmd; + + ret = usb_interrupt_msg(port->serial->dev, + usb_sndintpipe(port->serial->dev, port->interrupt_out_endpointAddress), + buffer_cmd, sizeof(cmd), + &actual_len, USB_CTRL_SET_TIMEOUT); + + kfree(buffer_cmd); + + if (ret < 0) + return ret; + else if (actual_len != sizeof(cmd)) + return -EIO; + return 0; +} + +static void metrousb_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + int throttled = 0; + int result = 0; + unsigned long flags = 0; + + dev_dbg(&port->dev, "%s\n", __func__); + + switch (urb->status) { + case 0: + /* Success status, read from the port. */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* urb has been terminated. */ + dev_dbg(&port->dev, + "%s - urb shutting down, error code=%d\n", + __func__, urb->status); + return; + default: + dev_dbg(&port->dev, + "%s - non-zero urb received, error code=%d\n", + __func__, urb->status); + goto exit; + } + + + /* Set the data read from the usb port into the serial port buffer. */ + if (urb->actual_length) { + /* Loop through the data copying each byte to the tty layer. */ + tty_insert_flip_string(&port->port, data, urb->actual_length); + + /* Force the data to the tty layer. */ + tty_flip_buffer_push(&port->port); + } + + /* Set any port variables. */ + spin_lock_irqsave(&metro_priv->lock, flags); + throttled = metro_priv->throttled; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + /* Continue trying to read if set. */ + if (!throttled) { + usb_fill_int_urb(port->interrupt_in_urb, port->serial->dev, + usb_rcvintpipe(port->serial->dev, port->interrupt_in_endpointAddress), + port->interrupt_in_urb->transfer_buffer, + port->interrupt_in_urb->transfer_buffer_length, + metrousb_read_int_callback, port, 1); + + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + + if (result) + dev_err(&port->dev, + "%s - failed submitting interrupt in urb, error code=%d\n", + __func__, result); + } + return; + +exit: + /* Try to resubmit the urb. */ + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) + dev_err(&port->dev, + "%s - failed submitting interrupt in urb, error code=%d\n", + __func__, result); +} + +static void metrousb_write_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + + dev_warn(&port->dev, "%s not implemented yet.\n", + __func__); +} + +static void metrousb_cleanup(struct usb_serial_port *port) +{ + dev_dbg(&port->dev, "%s\n", __func__); + + usb_unlink_urb(port->interrupt_in_urb); + usb_kill_urb(port->interrupt_in_urb); + + metrousb_send_unidirectional_cmd(UNI_CMD_CLOSE, port); +} + +static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags = 0; + int result = 0; + + dev_dbg(&port->dev, "%s\n", __func__); + + /* Make sure the urb is initialized. */ + if (!port->interrupt_in_urb) { + dev_err(&port->dev, "%s - interrupt urb not initialized\n", + __func__); + return -ENODEV; + } + + /* Set the private data information for the port. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->control_state = 0; + metro_priv->throttled = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + /* Clear the urb pipe. */ + usb_clear_halt(serial->dev, port->interrupt_in_urb->pipe); + + /* Start reading from the device */ + usb_fill_int_urb(port->interrupt_in_urb, serial->dev, + usb_rcvintpipe(serial->dev, port->interrupt_in_endpointAddress), + port->interrupt_in_urb->transfer_buffer, + port->interrupt_in_urb->transfer_buffer_length, + metrousb_read_int_callback, port, 1); + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + + if (result) { + dev_err(&port->dev, + "%s - failed submitting interrupt in urb, error code=%d\n", + __func__, result); + goto exit; + } + + /* Send activate cmd to device */ + result = metrousb_send_unidirectional_cmd(UNI_CMD_OPEN, port); + if (result) { + dev_err(&port->dev, + "%s - failed to configure device for port number=%d, error code=%d\n", + __func__, port->number, result); + goto exit; + } + + dev_dbg(&port->dev, "%s - port open\n", __func__); +exit: + return result; +} + +static int metrousb_set_modem_ctrl(struct usb_serial *serial, unsigned int control_state) +{ + int retval = 0; + unsigned char mcr = METROUSB_MCR_NONE; + + dev_dbg(&serial->dev->dev, "%s - control state = %d\n", + __func__, control_state); + + /* Set the modem control value. */ + if (control_state & TIOCM_DTR) + mcr |= METROUSB_MCR_DTR; + if (control_state & TIOCM_RTS) + mcr |= METROUSB_MCR_RTS; + + /* Send the command to the usb port. */ + retval = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + METROUSB_SET_REQUEST_TYPE, METROUSB_SET_MODEM_CTRL_REQUEST, + control_state, 0, NULL, 0, WDR_TIMEOUT); + if (retval < 0) + dev_err(&serial->dev->dev, + "%s - set modem ctrl=0x%x failed, error code=%d\n", + __func__, mcr, retval); + + return retval; +} + +static int metrousb_port_probe(struct usb_serial_port *port) +{ + struct metrousb_private *metro_priv; + + metro_priv = kzalloc(sizeof(*metro_priv), GFP_KERNEL); + if (!metro_priv) + return -ENOMEM; + + spin_lock_init(&metro_priv->lock); + + usb_set_serial_port_data(port, metro_priv); + + return 0; +} + +static int metrousb_port_remove(struct usb_serial_port *port) +{ + struct metrousb_private *metro_priv; + + metro_priv = usb_get_serial_port_data(port); + kfree(metro_priv); + + return 0; +} + +static void metrousb_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags = 0; + + dev_dbg(tty->dev, "%s\n", __func__); + + /* Set the private information for the port to stop reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->throttled = 1; + spin_unlock_irqrestore(&metro_priv->lock, flags); +} + +static int metrousb_tiocmget(struct tty_struct *tty) +{ + unsigned long control_state = 0; + struct usb_serial_port *port = tty->driver_data; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags = 0; + + dev_dbg(tty->dev, "%s\n", __func__); + + spin_lock_irqsave(&metro_priv->lock, flags); + control_state = metro_priv->control_state; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + return control_state; +} + +static int metrousb_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags = 0; + unsigned long control_state = 0; + + dev_dbg(tty->dev, "%s - set=%d, clear=%d\n", __func__, set, clear); + + spin_lock_irqsave(&metro_priv->lock, flags); + control_state = metro_priv->control_state; + + /* Set the RTS and DTR values. */ + if (set & TIOCM_RTS) + control_state |= TIOCM_RTS; + if (set & TIOCM_DTR) + control_state |= TIOCM_DTR; + if (clear & TIOCM_RTS) + control_state &= ~TIOCM_RTS; + if (clear & TIOCM_DTR) + control_state &= ~TIOCM_DTR; + + metro_priv->control_state = control_state; + spin_unlock_irqrestore(&metro_priv->lock, flags); + return metrousb_set_modem_ctrl(serial, control_state); +} + +static void metrousb_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags = 0; + int result = 0; + + dev_dbg(tty->dev, "%s\n", __func__); + + /* Set the private information for the port to resume reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->throttled = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + /* Submit the urb to read from the port. */ + port->interrupt_in_urb->dev = port->serial->dev; + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + if (result) + dev_err(tty->dev, + "failed submitting interrupt in urb error code=%d\n", + result); +} + +static struct usb_serial_driver metrousb_device = { + .driver = { + .owner = THIS_MODULE, + .name = "metro-usb", + }, + .description = "Metrologic USB to Serial", + .id_table = id_table, + .num_ports = 1, + .open = metrousb_open, + .close = metrousb_cleanup, + .read_int_callback = metrousb_read_int_callback, + .write_int_callback = metrousb_write_int_callback, + .port_probe = metrousb_port_probe, + .port_remove = metrousb_port_remove, + .throttle = metrousb_throttle, + .unthrottle = metrousb_unthrottle, + .tiocmget = metrousb_tiocmget, + .tiocmset = metrousb_tiocmset, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &metrousb_device, + NULL, +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Philip Nicastro"); +MODULE_AUTHOR("Aleksey Babahin "); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c new file mode 100644 index 00000000..0f16bf6e --- /dev/null +++ b/drivers/usb/serial/mos7720.c @@ -0,0 +1,2080 @@ +/* + * mos7720.c + * Controls the Moschip 7720 usb to dual port serial convertor + * + * Copyright 2006 Moschip Semiconductor Tech. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * Developed by: + * Vijaya Kumar + * Ajay Kumar + * Gurudeva + * + * Cleaned up from the original by: + * Greg Kroah-Hartman + * + * Originally based on drivers/usb/serial/io_edgeport.c which is: + * Copyright (C) 2000 Inside Out Networks, All rights reserved. + * Copyright (C) 2001-2002 Greg Kroah-Hartman + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Aspire Communications pvt Ltd." +#define DRIVER_DESC "Moschip USB Serial Driver" + +/* default urb timeout */ +#define MOS_WDR_TIMEOUT 5000 + +#define MOS_MAX_PORT 0x02 +#define MOS_WRITE 0x0E +#define MOS_READ 0x0D + +/* Interrupt Rotinue Defines */ +#define SERIAL_IIR_RLS 0x06 +#define SERIAL_IIR_RDA 0x04 +#define SERIAL_IIR_CTI 0x0c +#define SERIAL_IIR_THR 0x02 +#define SERIAL_IIR_MS 0x00 + +#define NUM_URBS 16 /* URB Count */ +#define URB_TRANSFER_BUFFER_SIZE 32 /* URB Size */ + +/* This structure holds all of the local serial port information */ +struct moschip_port { + __u8 shadowLCR; /* last LCR value received */ + __u8 shadowMCR; /* last MCR value received */ + __u8 shadowMSR; /* last MSR value received */ + char open; + struct usb_serial_port *port; /* loop back to the owner */ + struct urb *write_urb_pool[NUM_URBS]; +}; + +static struct usb_serial_driver moschip7720_2port_driver; + +#define USB_VENDOR_ID_MOSCHIP 0x9710 +#define MOSCHIP_DEVICE_ID_7720 0x7720 +#define MOSCHIP_DEVICE_ID_7715 0x7715 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7720) }, + { USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7715) }, + { } /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT + +/* initial values for parport regs */ +#define DCR_INIT_VAL 0x0c /* SLCTIN, nINIT */ +#define ECR_INIT_VAL 0x00 /* SPP mode */ + +struct urbtracker { + struct mos7715_parport *mos_parport; + struct list_head urblist_entry; + struct kref ref_count; + struct urb *urb; + struct usb_ctrlrequest *setup; +}; + +enum mos7715_pp_modes { + SPP = 0<<5, + PS2 = 1<<5, /* moschip calls this 'NIBBLE' mode */ + PPF = 2<<5, /* moschip calls this 'CB-FIFO mode */ +}; + +struct mos7715_parport { + struct parport *pp; /* back to containing struct */ + struct kref ref_count; /* to instance of this struct */ + struct list_head deferred_urbs; /* list deferred async urbs */ + struct list_head active_urbs; /* list async urbs in flight */ + spinlock_t listlock; /* protects list access */ + bool msg_pending; /* usb sync call pending */ + struct completion syncmsg_compl; /* usb sync call completed */ + struct tasklet_struct urb_tasklet; /* for sending deferred urbs */ + struct usb_serial *serial; /* back to containing struct */ + __u8 shadowECR; /* parallel port regs... */ + __u8 shadowDCR; + atomic_t shadowDSR; /* updated in int-in callback */ +}; + +/* lock guards against dereferencing NULL ptr in parport ops callbacks */ +static DEFINE_SPINLOCK(release_lock); + +#endif /* CONFIG_USB_SERIAL_MOS7715_PARPORT */ + +static const unsigned int dummy; /* for clarity in register access fns */ + +enum mos_regs { + THR, /* serial port regs */ + RHR, + IER, + FCR, + ISR, + LCR, + MCR, + LSR, + MSR, + SPR, + DLL, + DLM, + DPR, /* parallel port regs */ + DSR, + DCR, + ECR, + SP1_REG, /* device control regs */ + SP2_REG, /* serial port 2 (7720 only) */ + PP_REG, + SP_CONTROL_REG, +}; + +/* + * Return the correct value for the Windex field of the setup packet + * for a control endpoint message. See the 7715 datasheet. + */ +static inline __u16 get_reg_index(enum mos_regs reg) +{ + static const __u16 mos7715_index_lookup_table[] = { + 0x00, /* THR */ + 0x00, /* RHR */ + 0x01, /* IER */ + 0x02, /* FCR */ + 0x02, /* ISR */ + 0x03, /* LCR */ + 0x04, /* MCR */ + 0x05, /* LSR */ + 0x06, /* MSR */ + 0x07, /* SPR */ + 0x00, /* DLL */ + 0x01, /* DLM */ + 0x00, /* DPR */ + 0x01, /* DSR */ + 0x02, /* DCR */ + 0x0a, /* ECR */ + 0x01, /* SP1_REG */ + 0x02, /* SP2_REG (7720 only) */ + 0x04, /* PP_REG (7715 only) */ + 0x08, /* SP_CONTROL_REG */ + }; + return mos7715_index_lookup_table[reg]; +} + +/* + * Return the correct value for the upper byte of the Wvalue field of + * the setup packet for a control endpoint message. + */ +static inline __u16 get_reg_value(enum mos_regs reg, + unsigned int serial_portnum) +{ + if (reg >= SP1_REG) /* control reg */ + return 0x0000; + + else if (reg >= DPR) /* parallel port reg (7715 only) */ + return 0x0100; + + else /* serial port reg */ + return (serial_portnum + 2) << 8; +} + +/* + * Write data byte to the specified device register. The data is embedded in + * the value field of the setup packet. serial_portnum is ignored for registers + * not specific to a particular serial port. + */ +static int write_mos_reg(struct usb_serial *serial, unsigned int serial_portnum, + enum mos_regs reg, __u8 data) +{ + struct usb_device *usbdev = serial->dev; + unsigned int pipe = usb_sndctrlpipe(usbdev, 0); + __u8 request = (__u8)0x0e; + __u8 requesttype = (__u8)0x40; + __u16 index = get_reg_index(reg); + __u16 value = get_reg_value(reg, serial_portnum) + data; + int status = usb_control_msg(usbdev, pipe, request, requesttype, value, + index, NULL, 0, MOS_WDR_TIMEOUT); + if (status < 0) + dev_err(&usbdev->dev, + "mos7720: usb_control_msg() failed: %d", status); + return status; +} + +/* + * Read data byte from the specified device register. The data returned by the + * device is embedded in the value field of the setup packet. serial_portnum is + * ignored for registers that are not specific to a particular serial port. + */ +static int read_mos_reg(struct usb_serial *serial, unsigned int serial_portnum, + enum mos_regs reg, __u8 *data) +{ + struct usb_device *usbdev = serial->dev; + unsigned int pipe = usb_rcvctrlpipe(usbdev, 0); + __u8 request = (__u8)0x0d; + __u8 requesttype = (__u8)0xc0; + __u16 index = get_reg_index(reg); + __u16 value = get_reg_value(reg, serial_portnum); + u8 *buf; + int status; + + buf = kmalloc(1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + status = usb_control_msg(usbdev, pipe, request, requesttype, value, + index, buf, 1, MOS_WDR_TIMEOUT); + if (status == 1) + *data = *buf; + else if (status < 0) + dev_err(&usbdev->dev, + "mos7720: usb_control_msg() failed: %d", status); + kfree(buf); + + return status; +} + +#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT + +static inline int mos7715_change_mode(struct mos7715_parport *mos_parport, + enum mos7715_pp_modes mode) +{ + mos_parport->shadowECR = mode; + write_mos_reg(mos_parport->serial, dummy, ECR, mos_parport->shadowECR); + return 0; +} + +static void destroy_mos_parport(struct kref *kref) +{ + struct mos7715_parport *mos_parport = + container_of(kref, struct mos7715_parport, ref_count); + + kfree(mos_parport); +} + +static void destroy_urbtracker(struct kref *kref) +{ + struct urbtracker *urbtrack = + container_of(kref, struct urbtracker, ref_count); + struct mos7715_parport *mos_parport = urbtrack->mos_parport; + + usb_free_urb(urbtrack->urb); + kfree(urbtrack->setup); + kfree(urbtrack); + kref_put(&mos_parport->ref_count, destroy_mos_parport); +} + +/* + * This runs as a tasklet when sending an urb in a non-blocking parallel + * port callback had to be deferred because the disconnect mutex could not be + * obtained at the time. + */ +static void send_deferred_urbs(unsigned long _mos_parport) +{ + int ret_val; + unsigned long flags; + struct mos7715_parport *mos_parport = (void *)_mos_parport; + struct urbtracker *urbtrack, *tmp; + struct list_head *cursor, *next; + struct device *dev; + + /* if release function ran, game over */ + if (unlikely(mos_parport->serial == NULL)) + return; + + dev = &mos_parport->serial->dev->dev; + + /* try again to get the mutex */ + if (!mutex_trylock(&mos_parport->serial->disc_mutex)) { + dev_dbg(dev, "%s: rescheduling tasklet\n", __func__); + tasklet_schedule(&mos_parport->urb_tasklet); + return; + } + + /* if device disconnected, game over */ + if (unlikely(mos_parport->serial->disconnected)) { + mutex_unlock(&mos_parport->serial->disc_mutex); + return; + } + + spin_lock_irqsave(&mos_parport->listlock, flags); + if (list_empty(&mos_parport->deferred_urbs)) { + spin_unlock_irqrestore(&mos_parport->listlock, flags); + mutex_unlock(&mos_parport->serial->disc_mutex); + dev_dbg(dev, "%s: deferred_urbs list empty\n", __func__); + return; + } + + /* move contents of deferred_urbs list to active_urbs list and submit */ + list_for_each_safe(cursor, next, &mos_parport->deferred_urbs) + list_move_tail(cursor, &mos_parport->active_urbs); + list_for_each_entry_safe(urbtrack, tmp, &mos_parport->active_urbs, + urblist_entry) { + ret_val = usb_submit_urb(urbtrack->urb, GFP_ATOMIC); + dev_dbg(dev, "%s: urb submitted\n", __func__); + if (ret_val) { + dev_err(dev, "usb_submit_urb() failed: %d\n", ret_val); + list_del(&urbtrack->urblist_entry); + kref_put(&urbtrack->ref_count, destroy_urbtracker); + } + } + spin_unlock_irqrestore(&mos_parport->listlock, flags); + mutex_unlock(&mos_parport->serial->disc_mutex); +} + +/* callback for parallel port control urbs submitted asynchronously */ +static void async_complete(struct urb *urb) +{ + struct urbtracker *urbtrack = urb->context; + int status = urb->status; + + if (unlikely(status)) + dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", __func__, status); + + /* remove the urbtracker from the active_urbs list */ + spin_lock(&urbtrack->mos_parport->listlock); + list_del(&urbtrack->urblist_entry); + spin_unlock(&urbtrack->mos_parport->listlock); + kref_put(&urbtrack->ref_count, destroy_urbtracker); +} + +static int write_parport_reg_nonblock(struct mos7715_parport *mos_parport, + enum mos_regs reg, __u8 data) +{ + struct urbtracker *urbtrack; + int ret_val; + unsigned long flags; + struct usb_serial *serial = mos_parport->serial; + struct usb_device *usbdev = serial->dev; + + /* create and initialize the control urb and containing urbtracker */ + urbtrack = kmalloc(sizeof(struct urbtracker), GFP_ATOMIC); + if (urbtrack == NULL) { + dev_err(&usbdev->dev, "out of memory"); + return -ENOMEM; + } + kref_get(&mos_parport->ref_count); + urbtrack->mos_parport = mos_parport; + urbtrack->urb = usb_alloc_urb(0, GFP_ATOMIC); + if (urbtrack->urb == NULL) { + dev_err(&usbdev->dev, "out of urbs"); + kfree(urbtrack); + return -ENOMEM; + } + urbtrack->setup = kmalloc(sizeof(*urbtrack->setup), GFP_ATOMIC); + if (!urbtrack->setup) { + usb_free_urb(urbtrack->urb); + kfree(urbtrack); + return -ENOMEM; + } + urbtrack->setup->bRequestType = (__u8)0x40; + urbtrack->setup->bRequest = (__u8)0x0e; + urbtrack->setup->wValue = cpu_to_le16(get_reg_value(reg, dummy)); + urbtrack->setup->wIndex = cpu_to_le16(get_reg_index(reg)); + urbtrack->setup->wLength = 0; + usb_fill_control_urb(urbtrack->urb, usbdev, + usb_sndctrlpipe(usbdev, 0), + (unsigned char *)urbtrack->setup, + NULL, 0, async_complete, urbtrack); + kref_init(&urbtrack->ref_count); + INIT_LIST_HEAD(&urbtrack->urblist_entry); + + /* + * get the disconnect mutex, or add tracker to the deferred_urbs list + * and schedule a tasklet to try again later + */ + if (!mutex_trylock(&serial->disc_mutex)) { + spin_lock_irqsave(&mos_parport->listlock, flags); + list_add_tail(&urbtrack->urblist_entry, + &mos_parport->deferred_urbs); + spin_unlock_irqrestore(&mos_parport->listlock, flags); + tasklet_schedule(&mos_parport->urb_tasklet); + dev_dbg(&usbdev->dev, "tasklet scheduled"); + return 0; + } + + /* bail if device disconnected */ + if (serial->disconnected) { + kref_put(&urbtrack->ref_count, destroy_urbtracker); + mutex_unlock(&serial->disc_mutex); + return -ENODEV; + } + + /* add the tracker to the active_urbs list and submit */ + spin_lock_irqsave(&mos_parport->listlock, flags); + list_add_tail(&urbtrack->urblist_entry, &mos_parport->active_urbs); + spin_unlock_irqrestore(&mos_parport->listlock, flags); + ret_val = usb_submit_urb(urbtrack->urb, GFP_ATOMIC); + mutex_unlock(&serial->disc_mutex); + if (ret_val) { + dev_err(&usbdev->dev, + "%s: submit_urb() failed: %d", __func__, ret_val); + spin_lock_irqsave(&mos_parport->listlock, flags); + list_del(&urbtrack->urblist_entry); + spin_unlock_irqrestore(&mos_parport->listlock, flags); + kref_put(&urbtrack->ref_count, destroy_urbtracker); + return ret_val; + } + return 0; +} + +/* + * This is the the common top part of all parallel port callback operations that + * send synchronous messages to the device. This implements convoluted locking + * that avoids two scenarios: (1) a port operation is called after usbserial + * has called our release function, at which point struct mos7715_parport has + * been destroyed, and (2) the device has been disconnected, but usbserial has + * not called the release function yet because someone has a serial port open. + * The shared release_lock prevents the first, and the mutex and disconnected + * flag maintained by usbserial covers the second. We also use the msg_pending + * flag to ensure that all synchronous usb messgage calls have completed before + * our release function can return. + */ +static int parport_prologue(struct parport *pp) +{ + struct mos7715_parport *mos_parport; + + spin_lock(&release_lock); + mos_parport = pp->private_data; + if (unlikely(mos_parport == NULL)) { + /* release fn called, port struct destroyed */ + spin_unlock(&release_lock); + return -1; + } + mos_parport->msg_pending = true; /* synch usb call pending */ + INIT_COMPLETION(mos_parport->syncmsg_compl); + spin_unlock(&release_lock); + + mutex_lock(&mos_parport->serial->disc_mutex); + if (mos_parport->serial->disconnected) { + /* device disconnected */ + mutex_unlock(&mos_parport->serial->disc_mutex); + mos_parport->msg_pending = false; + complete(&mos_parport->syncmsg_compl); + return -1; + } + + return 0; +} + +/* + * This is the the common bottom part of all parallel port functions that send + * synchronous messages to the device. + */ +static inline void parport_epilogue(struct parport *pp) +{ + struct mos7715_parport *mos_parport = pp->private_data; + mutex_unlock(&mos_parport->serial->disc_mutex); + mos_parport->msg_pending = false; + complete(&mos_parport->syncmsg_compl); +} + +static void parport_mos7715_write_data(struct parport *pp, unsigned char d) +{ + struct mos7715_parport *mos_parport = pp->private_data; + + if (parport_prologue(pp) < 0) + return; + mos7715_change_mode(mos_parport, SPP); + write_mos_reg(mos_parport->serial, dummy, DPR, (__u8)d); + parport_epilogue(pp); +} + +static unsigned char parport_mos7715_read_data(struct parport *pp) +{ + struct mos7715_parport *mos_parport = pp->private_data; + unsigned char d; + + if (parport_prologue(pp) < 0) + return 0; + read_mos_reg(mos_parport->serial, dummy, DPR, &d); + parport_epilogue(pp); + return d; +} + +static void parport_mos7715_write_control(struct parport *pp, unsigned char d) +{ + struct mos7715_parport *mos_parport = pp->private_data; + __u8 data; + + if (parport_prologue(pp) < 0) + return; + data = ((__u8)d & 0x0f) | (mos_parport->shadowDCR & 0xf0); + write_mos_reg(mos_parport->serial, dummy, DCR, data); + mos_parport->shadowDCR = data; + parport_epilogue(pp); +} + +static unsigned char parport_mos7715_read_control(struct parport *pp) +{ + struct mos7715_parport *mos_parport = pp->private_data; + __u8 dcr; + + spin_lock(&release_lock); + mos_parport = pp->private_data; + if (unlikely(mos_parport == NULL)) { + spin_unlock(&release_lock); + return 0; + } + dcr = mos_parport->shadowDCR & 0x0f; + spin_unlock(&release_lock); + return dcr; +} + +static unsigned char parport_mos7715_frob_control(struct parport *pp, + unsigned char mask, + unsigned char val) +{ + struct mos7715_parport *mos_parport = pp->private_data; + __u8 dcr; + + mask &= 0x0f; + val &= 0x0f; + if (parport_prologue(pp) < 0) + return 0; + mos_parport->shadowDCR = (mos_parport->shadowDCR & (~mask)) ^ val; + write_mos_reg(mos_parport->serial, dummy, DCR, mos_parport->shadowDCR); + dcr = mos_parport->shadowDCR & 0x0f; + parport_epilogue(pp); + return dcr; +} + +static unsigned char parport_mos7715_read_status(struct parport *pp) +{ + unsigned char status; + struct mos7715_parport *mos_parport = pp->private_data; + + spin_lock(&release_lock); + mos_parport = pp->private_data; + if (unlikely(mos_parport == NULL)) { /* release called */ + spin_unlock(&release_lock); + return 0; + } + status = atomic_read(&mos_parport->shadowDSR) & 0xf8; + spin_unlock(&release_lock); + return status; +} + +static void parport_mos7715_enable_irq(struct parport *pp) +{ +} + +static void parport_mos7715_disable_irq(struct parport *pp) +{ +} + +static void parport_mos7715_data_forward(struct parport *pp) +{ + struct mos7715_parport *mos_parport = pp->private_data; + + if (parport_prologue(pp) < 0) + return; + mos7715_change_mode(mos_parport, PS2); + mos_parport->shadowDCR &= ~0x20; + write_mos_reg(mos_parport->serial, dummy, DCR, mos_parport->shadowDCR); + parport_epilogue(pp); +} + +static void parport_mos7715_data_reverse(struct parport *pp) +{ + struct mos7715_parport *mos_parport = pp->private_data; + + if (parport_prologue(pp) < 0) + return; + mos7715_change_mode(mos_parport, PS2); + mos_parport->shadowDCR |= 0x20; + write_mos_reg(mos_parport->serial, dummy, DCR, mos_parport->shadowDCR); + parport_epilogue(pp); +} + +static void parport_mos7715_init_state(struct pardevice *dev, + struct parport_state *s) +{ + s->u.pc.ctr = DCR_INIT_VAL; + s->u.pc.ecr = ECR_INIT_VAL; +} + +/* N.B. Parport core code requires that this function not block */ +static void parport_mos7715_save_state(struct parport *pp, + struct parport_state *s) +{ + struct mos7715_parport *mos_parport; + + spin_lock(&release_lock); + mos_parport = pp->private_data; + if (unlikely(mos_parport == NULL)) { /* release called */ + spin_unlock(&release_lock); + return; + } + s->u.pc.ctr = mos_parport->shadowDCR; + s->u.pc.ecr = mos_parport->shadowECR; + spin_unlock(&release_lock); +} + +/* N.B. Parport core code requires that this function not block */ +static void parport_mos7715_restore_state(struct parport *pp, + struct parport_state *s) +{ + struct mos7715_parport *mos_parport; + + spin_lock(&release_lock); + mos_parport = pp->private_data; + if (unlikely(mos_parport == NULL)) { /* release called */ + spin_unlock(&release_lock); + return; + } + write_parport_reg_nonblock(mos_parport, DCR, mos_parport->shadowDCR); + write_parport_reg_nonblock(mos_parport, ECR, mos_parport->shadowECR); + spin_unlock(&release_lock); +} + +static size_t parport_mos7715_write_compat(struct parport *pp, + const void *buffer, + size_t len, int flags) +{ + int retval; + struct mos7715_parport *mos_parport = pp->private_data; + int actual_len; + + if (parport_prologue(pp) < 0) + return 0; + mos7715_change_mode(mos_parport, PPF); + retval = usb_bulk_msg(mos_parport->serial->dev, + usb_sndbulkpipe(mos_parport->serial->dev, 2), + (void *)buffer, len, &actual_len, + MOS_WDR_TIMEOUT); + parport_epilogue(pp); + if (retval) { + dev_err(&mos_parport->serial->dev->dev, + "mos7720: usb_bulk_msg() failed: %d", retval); + return 0; + } + return actual_len; +} + +static struct parport_operations parport_mos7715_ops = { + .owner = THIS_MODULE, + .write_data = parport_mos7715_write_data, + .read_data = parport_mos7715_read_data, + + .write_control = parport_mos7715_write_control, + .read_control = parport_mos7715_read_control, + .frob_control = parport_mos7715_frob_control, + + .read_status = parport_mos7715_read_status, + + .enable_irq = parport_mos7715_enable_irq, + .disable_irq = parport_mos7715_disable_irq, + + .data_forward = parport_mos7715_data_forward, + .data_reverse = parport_mos7715_data_reverse, + + .init_state = parport_mos7715_init_state, + .save_state = parport_mos7715_save_state, + .restore_state = parport_mos7715_restore_state, + + .compat_write_data = parport_mos7715_write_compat, + + .nibble_read_data = parport_ieee1284_read_nibble, + .byte_read_data = parport_ieee1284_read_byte, +}; + +/* + * Allocate and initialize parallel port control struct, initialize + * the parallel port hardware device, and register with the parport subsystem. + */ +static int mos7715_parport_init(struct usb_serial *serial) +{ + struct mos7715_parport *mos_parport; + + /* allocate and initialize parallel port control struct */ + mos_parport = kzalloc(sizeof(struct mos7715_parport), GFP_KERNEL); + if (mos_parport == NULL) { + dev_dbg(&serial->dev->dev, "%s: kzalloc failed\n", __func__); + return -ENOMEM; + } + mos_parport->msg_pending = false; + kref_init(&mos_parport->ref_count); + spin_lock_init(&mos_parport->listlock); + INIT_LIST_HEAD(&mos_parport->active_urbs); + INIT_LIST_HEAD(&mos_parport->deferred_urbs); + usb_set_serial_data(serial, mos_parport); /* hijack private pointer */ + mos_parport->serial = serial; + tasklet_init(&mos_parport->urb_tasklet, send_deferred_urbs, + (unsigned long) mos_parport); + init_completion(&mos_parport->syncmsg_compl); + + /* cycle parallel port reset bit */ + write_mos_reg(mos_parport->serial, dummy, PP_REG, (__u8)0x80); + write_mos_reg(mos_parport->serial, dummy, PP_REG, (__u8)0x00); + + /* initialize device registers */ + mos_parport->shadowDCR = DCR_INIT_VAL; + write_mos_reg(mos_parport->serial, dummy, DCR, mos_parport->shadowDCR); + mos_parport->shadowECR = ECR_INIT_VAL; + write_mos_reg(mos_parport->serial, dummy, ECR, mos_parport->shadowECR); + + /* register with parport core */ + mos_parport->pp = parport_register_port(0, PARPORT_IRQ_NONE, + PARPORT_DMA_NONE, + &parport_mos7715_ops); + if (mos_parport->pp == NULL) { + dev_err(&serial->interface->dev, + "Could not register parport\n"); + kref_put(&mos_parport->ref_count, destroy_mos_parport); + return -EIO; + } + mos_parport->pp->private_data = mos_parport; + mos_parport->pp->modes = PARPORT_MODE_COMPAT | PARPORT_MODE_PCSPP; + mos_parport->pp->dev = &serial->interface->dev; + parport_announce_port(mos_parport->pp); + + return 0; +} +#endif /* CONFIG_USB_SERIAL_MOS7715_PARPORT */ + +/* + * mos7720_interrupt_callback + * this is the callback function for when we have received data on the + * interrupt endpoint. + */ +static void mos7720_interrupt_callback(struct urb *urb) +{ + int result; + int length; + int status = urb->status; + struct device *dev = &urb->dev->dev; + __u8 *data; + __u8 sp1; + __u8 sp2; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shutting down with status: %d\n", __func__, status); + return; + default: + dev_dbg(dev, "%s - nonzero urb status received: %d\n", __func__, status); + goto exit; + } + + length = urb->actual_length; + data = urb->transfer_buffer; + + /* Moschip get 4 bytes + * Byte 1 IIR Port 1 (port.number is 0) + * Byte 2 IIR Port 2 (port.number is 1) + * Byte 3 -------------- + * Byte 4 FIFO status for both */ + + /* the above description is inverted + * oneukum 2007-03-14 */ + + if (unlikely(length != 4)) { + dev_dbg(dev, "Wrong data !!!\n"); + return; + } + + sp1 = data[3]; + sp2 = data[2]; + + if ((sp1 | sp2) & 0x01) { + /* No Interrupt Pending in both the ports */ + dev_dbg(dev, "No Interrupt !!!\n"); + } else { + switch (sp1 & 0x0f) { + case SERIAL_IIR_RLS: + dev_dbg(dev, "Serial Port 1: Receiver status error or address bit detected in 9-bit mode\n"); + break; + case SERIAL_IIR_CTI: + dev_dbg(dev, "Serial Port 1: Receiver time out\n"); + break; + case SERIAL_IIR_MS: + /* dev_dbg(dev, "Serial Port 1: Modem status change\n"); */ + break; + } + + switch (sp2 & 0x0f) { + case SERIAL_IIR_RLS: + dev_dbg(dev, "Serial Port 2: Receiver status error or address bit detected in 9-bit mode\n"); + break; + case SERIAL_IIR_CTI: + dev_dbg(dev, "Serial Port 2: Receiver time out\n"); + break; + case SERIAL_IIR_MS: + /* dev_dbg(dev, "Serial Port 2: Modem status change\n"); */ + break; + } + } + +exit: + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) + dev_err(dev, "%s - Error %d submitting control urb\n", __func__, result); +} + +/* + * mos7715_interrupt_callback + * this is the 7715's callback function for when we have received data on + * the interrupt endpoint. + */ +static void mos7715_interrupt_callback(struct urb *urb) +{ + int result; + int length; + int status = urb->status; + struct device *dev = &urb->dev->dev; + __u8 *data; + __u8 iir; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ENODEV: + /* this urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shutting down with status: %d\n", __func__, status); + return; + default: + dev_dbg(dev, "%s - nonzero urb status received: %d\n", __func__, status); + goto exit; + } + + length = urb->actual_length; + data = urb->transfer_buffer; + + /* Structure of data from 7715 device: + * Byte 1: IIR serial Port + * Byte 2: unused + * Byte 2: DSR parallel port + * Byte 4: FIFO status for both */ + + if (unlikely(length != 4)) { + dev_dbg(dev, "Wrong data !!!\n"); + return; + } + + iir = data[0]; + if (!(iir & 0x01)) { /* serial port interrupt pending */ + switch (iir & 0x0f) { + case SERIAL_IIR_RLS: + dev_dbg(dev, "Serial Port: Receiver status error or address bit detected in 9-bit mode\n\n"); + break; + case SERIAL_IIR_CTI: + dev_dbg(dev, "Serial Port: Receiver time out\n"); + break; + case SERIAL_IIR_MS: + /* dev_dbg(dev, "Serial Port: Modem status change\n"); */ + break; + } + } + +#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT + { /* update local copy of DSR reg */ + struct usb_serial_port *port = urb->context; + struct mos7715_parport *mos_parport = port->serial->private; + if (unlikely(mos_parport == NULL)) + return; + atomic_set(&mos_parport->shadowDSR, data[2]); + } +#endif + +exit: + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) + dev_err(dev, "%s - Error %d submitting control urb\n", __func__, result); +} + +/* + * mos7720_bulk_in_callback + * this is the callback function for when we have received data on the + * bulk in endpoint. + */ +static void mos7720_bulk_in_callback(struct urb *urb) +{ + int retval; + unsigned char *data ; + struct usb_serial_port *port; + int status = urb->status; + + if (status) { + dev_dbg(&urb->dev->dev, "nonzero read bulk status received: %d\n", status); + return; + } + + port = urb->context; + + dev_dbg(&port->dev, "Entering...%s\n", __func__); + + data = urb->transfer_buffer; + + if (urb->actual_length) { + tty_insert_flip_string(&port->port, data, urb->actual_length); + tty_flip_buffer_push(&port->port); + } + + if (port->read_urb->status != -EINPROGRESS) { + retval = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (retval) + dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, retval = %d\n", retval); + } +} + +/* + * mos7720_bulk_out_data_callback + * this is the callback function for when we have finished sending serial + * data on the bulk out endpoint. + */ +static void mos7720_bulk_out_data_callback(struct urb *urb) +{ + struct moschip_port *mos7720_port; + int status = urb->status; + + if (status) { + dev_dbg(&urb->dev->dev, "nonzero write bulk status received:%d\n", status); + return; + } + + mos7720_port = urb->context; + if (!mos7720_port) { + dev_dbg(&urb->dev->dev, "NULL mos7720_port pointer\n"); + return ; + } + + if (mos7720_port->open) + tty_port_tty_wakeup(&mos7720_port->port->port); +} + +/* + * mos77xx_probe + * this function installs the appropriate read interrupt endpoint callback + * depending on whether the device is a 7720 or 7715, thus avoiding costly + * run-time checks in the high-frequency callback routine itself. + */ +static int mos77xx_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + if (id->idProduct == MOSCHIP_DEVICE_ID_7715) + moschip7720_2port_driver.read_int_callback = + mos7715_interrupt_callback; + else + moschip7720_2port_driver.read_int_callback = + mos7720_interrupt_callback; + + return 0; +} + +static int mos77xx_calc_num_ports(struct usb_serial *serial) +{ + u16 product = le16_to_cpu(serial->dev->descriptor.idProduct); + if (product == MOSCHIP_DEVICE_ID_7715) + return 1; + + return 2; +} + +static int mos7720_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial; + struct urb *urb; + struct moschip_port *mos7720_port; + int response; + int port_number; + __u8 data; + int allocated_urbs = 0; + int j; + + serial = port->serial; + + mos7720_port = usb_get_serial_port_data(port); + if (mos7720_port == NULL) + return -ENODEV; + + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + + /* Initialising the write urb pool */ + for (j = 0; j < NUM_URBS; ++j) { + urb = usb_alloc_urb(0, GFP_KERNEL); + mos7720_port->write_urb_pool[j] = urb; + + if (urb == NULL) { + dev_err(&port->dev, "No more urbs???\n"); + continue; + } + + urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE, + GFP_KERNEL); + if (!urb->transfer_buffer) { + dev_err(&port->dev, + "%s-out of memory for urb buffers.\n", + __func__); + usb_free_urb(mos7720_port->write_urb_pool[j]); + mos7720_port->write_urb_pool[j] = NULL; + continue; + } + allocated_urbs++; + } + + if (!allocated_urbs) + return -ENOMEM; + + /* Initialize MCS7720 -- Write Init values to corresponding Registers + * + * Register Index + * 0 : THR/RHR + * 1 : IER + * 2 : FCR + * 3 : LCR + * 4 : MCR + * 5 : LSR + * 6 : MSR + * 7 : SPR + * + * 0x08 : SP1/2 Control Reg + */ + port_number = port->number - port->serial->minor; + read_mos_reg(serial, port_number, LSR, &data); + + dev_dbg(&port->dev, "SS::%p LSR:%x\n", mos7720_port, data); + + write_mos_reg(serial, dummy, SP1_REG, 0x02); + write_mos_reg(serial, dummy, SP2_REG, 0x02); + + write_mos_reg(serial, port_number, IER, 0x00); + write_mos_reg(serial, port_number, FCR, 0x00); + + write_mos_reg(serial, port_number, FCR, 0xcf); + mos7720_port->shadowLCR = 0x03; + write_mos_reg(serial, port_number, LCR, mos7720_port->shadowLCR); + mos7720_port->shadowMCR = 0x0b; + write_mos_reg(serial, port_number, MCR, mos7720_port->shadowMCR); + + write_mos_reg(serial, port_number, SP_CONTROL_REG, 0x00); + read_mos_reg(serial, dummy, SP_CONTROL_REG, &data); + data = data | (port->number - port->serial->minor + 1); + write_mos_reg(serial, dummy, SP_CONTROL_REG, data); + mos7720_port->shadowLCR = 0x83; + write_mos_reg(serial, port_number, LCR, mos7720_port->shadowLCR); + write_mos_reg(serial, port_number, THR, 0x0c); + write_mos_reg(serial, port_number, IER, 0x00); + mos7720_port->shadowLCR = 0x03; + write_mos_reg(serial, port_number, LCR, mos7720_port->shadowLCR); + write_mos_reg(serial, port_number, IER, 0x0c); + + response = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (response) + dev_err(&port->dev, "%s - Error %d submitting read urb\n", + __func__, response); + + /* initialize our port settings */ + mos7720_port->shadowMCR = UART_MCR_OUT2; /* Must set to enable ints! */ + + /* send a open port command */ + mos7720_port->open = 1; + + return 0; +} + +/* + * mos7720_chars_in_buffer + * this function is called by the tty driver when it wants to know how many + * bytes of data we currently have outstanding in the port (data that has + * been written, but hasn't made it out the port yet) + * If successful, we return the number of bytes left to be written in the + * system, + * Otherwise we return a negative error number. + */ +static int mos7720_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + int i; + int chars = 0; + struct moschip_port *mos7720_port; + + mos7720_port = usb_get_serial_port_data(port); + if (mos7720_port == NULL) + return 0; + + for (i = 0; i < NUM_URBS; ++i) { + if (mos7720_port->write_urb_pool[i] && + mos7720_port->write_urb_pool[i]->status == -EINPROGRESS) + chars += URB_TRANSFER_BUFFER_SIZE; + } + dev_dbg(&port->dev, "%s - returns %d\n", __func__, chars); + return chars; +} + +static void mos7720_close(struct usb_serial_port *port) +{ + struct usb_serial *serial; + struct moschip_port *mos7720_port; + int j; + + serial = port->serial; + + mos7720_port = usb_get_serial_port_data(port); + if (mos7720_port == NULL) + return; + + for (j = 0; j < NUM_URBS; ++j) + usb_kill_urb(mos7720_port->write_urb_pool[j]); + + /* Freeing Write URBs */ + for (j = 0; j < NUM_URBS; ++j) { + if (mos7720_port->write_urb_pool[j]) { + kfree(mos7720_port->write_urb_pool[j]->transfer_buffer); + usb_free_urb(mos7720_port->write_urb_pool[j]); + } + } + + /* While closing port, shutdown all bulk read, write * + * and interrupt read if they exists, otherwise nop */ + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); + + write_mos_reg(serial, port->number - port->serial->minor, MCR, 0x00); + write_mos_reg(serial, port->number - port->serial->minor, IER, 0x00); + + mos7720_port->open = 0; +} + +static void mos7720_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned char data; + struct usb_serial *serial; + struct moschip_port *mos7720_port; + + serial = port->serial; + + mos7720_port = usb_get_serial_port_data(port); + if (mos7720_port == NULL) + return; + + if (break_state == -1) + data = mos7720_port->shadowLCR | UART_LCR_SBC; + else + data = mos7720_port->shadowLCR & ~UART_LCR_SBC; + + mos7720_port->shadowLCR = data; + write_mos_reg(serial, port->number - port->serial->minor, + LCR, mos7720_port->shadowLCR); +} + +/* + * mos7720_write_room + * this function is called by the tty driver when it wants to know how many + * bytes of data we can accept for a specific port. + * If successful, we return the amount of room that we have for this port + * Otherwise we return a negative error number. + */ +static int mos7720_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7720_port; + int room = 0; + int i; + + mos7720_port = usb_get_serial_port_data(port); + if (mos7720_port == NULL) + return -ENODEV; + + /* FIXME: Locking */ + for (i = 0; i < NUM_URBS; ++i) { + if (mos7720_port->write_urb_pool[i] && + mos7720_port->write_urb_pool[i]->status != -EINPROGRESS) + room += URB_TRANSFER_BUFFER_SIZE; + } + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, room); + return room; +} + +static int mos7720_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *data, int count) +{ + int status; + int i; + int bytes_sent = 0; + int transfer_size; + + struct moschip_port *mos7720_port; + struct usb_serial *serial; + struct urb *urb; + const unsigned char *current_position = data; + + serial = port->serial; + + mos7720_port = usb_get_serial_port_data(port); + if (mos7720_port == NULL) + return -ENODEV; + + /* try to find a free urb in the list */ + urb = NULL; + + for (i = 0; i < NUM_URBS; ++i) { + if (mos7720_port->write_urb_pool[i] && + mos7720_port->write_urb_pool[i]->status != -EINPROGRESS) { + urb = mos7720_port->write_urb_pool[i]; + dev_dbg(&port->dev, "URB:%d\n", i); + break; + } + } + + if (urb == NULL) { + dev_dbg(&port->dev, "%s - no more free urbs\n", __func__); + goto exit; + } + + if (urb->transfer_buffer == NULL) { + urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE, + GFP_KERNEL); + if (urb->transfer_buffer == NULL) { + dev_err_console(port, "%s no more kernel memory...\n", + __func__); + goto exit; + } + } + transfer_size = min(count, URB_TRANSFER_BUFFER_SIZE); + + memcpy(urb->transfer_buffer, current_position, transfer_size); + usb_serial_debug_data(&port->dev, __func__, transfer_size, + urb->transfer_buffer); + + /* fill urb with data and submit */ + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), + urb->transfer_buffer, transfer_size, + mos7720_bulk_out_data_callback, mos7720_port); + + /* send it down the pipe */ + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err_console(port, "%s - usb_submit_urb(write bulk) failed " + "with status = %d\n", __func__, status); + bytes_sent = status; + goto exit; + } + bytes_sent = transfer_size; + +exit: + return bytes_sent; +} + +static void mos7720_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7720_port; + int status; + + mos7720_port = usb_get_serial_port_data(port); + + if (mos7720_port == NULL) + return; + + if (!mos7720_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + /* if we are implementing XON/XOFF, send the stop character */ + if (I_IXOFF(tty)) { + unsigned char stop_char = STOP_CHAR(tty); + status = mos7720_write(tty, port, &stop_char, 1); + if (status <= 0) + return; + } + + /* if we are implementing RTS/CTS, toggle that line */ + if (tty->termios.c_cflag & CRTSCTS) { + mos7720_port->shadowMCR &= ~UART_MCR_RTS; + write_mos_reg(port->serial, port->number - port->serial->minor, + MCR, mos7720_port->shadowMCR); + if (status != 0) + return; + } +} + +static void mos7720_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7720_port = usb_get_serial_port_data(port); + int status; + + if (mos7720_port == NULL) + return; + + if (!mos7720_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + /* if we are implementing XON/XOFF, send the start character */ + if (I_IXOFF(tty)) { + unsigned char start_char = START_CHAR(tty); + status = mos7720_write(tty, port, &start_char, 1); + if (status <= 0) + return; + } + + /* if we are implementing RTS/CTS, toggle that line */ + if (tty->termios.c_cflag & CRTSCTS) { + mos7720_port->shadowMCR |= UART_MCR_RTS; + write_mos_reg(port->serial, port->number - port->serial->minor, + MCR, mos7720_port->shadowMCR); + if (status != 0) + return; + } +} + +/* FIXME: this function does not work */ +static int set_higher_rates(struct moschip_port *mos7720_port, + unsigned int baud) +{ + struct usb_serial_port *port; + struct usb_serial *serial; + int port_number; + enum mos_regs sp_reg; + if (mos7720_port == NULL) + return -EINVAL; + + port = mos7720_port->port; + serial = port->serial; + + /*********************************************** + * Init Sequence for higher rates + ***********************************************/ + dev_dbg(&port->dev, "Sending Setting Commands ..........\n"); + port_number = port->number - port->serial->minor; + + write_mos_reg(serial, port_number, IER, 0x00); + write_mos_reg(serial, port_number, FCR, 0x00); + write_mos_reg(serial, port_number, FCR, 0xcf); + mos7720_port->shadowMCR = 0x0b; + write_mos_reg(serial, port_number, MCR, mos7720_port->shadowMCR); + write_mos_reg(serial, dummy, SP_CONTROL_REG, 0x00); + + /*********************************************** + * Set for higher rates * + ***********************************************/ + /* writing baud rate verbatum into uart clock field clearly not right */ + if (port_number == 0) + sp_reg = SP1_REG; + else + sp_reg = SP2_REG; + write_mos_reg(serial, dummy, sp_reg, baud * 0x10); + write_mos_reg(serial, dummy, SP_CONTROL_REG, 0x03); + mos7720_port->shadowMCR = 0x2b; + write_mos_reg(serial, port_number, MCR, mos7720_port->shadowMCR); + + /*********************************************** + * Set DLL/DLM + ***********************************************/ + mos7720_port->shadowLCR = mos7720_port->shadowLCR | UART_LCR_DLAB; + write_mos_reg(serial, port_number, LCR, mos7720_port->shadowLCR); + write_mos_reg(serial, port_number, DLL, 0x01); + write_mos_reg(serial, port_number, DLM, 0x00); + mos7720_port->shadowLCR = mos7720_port->shadowLCR & ~UART_LCR_DLAB; + write_mos_reg(serial, port_number, LCR, mos7720_port->shadowLCR); + + return 0; +} + +/* baud rate information */ +struct divisor_table_entry { + __u32 baudrate; + __u16 divisor; +}; + +/* Define table of divisors for moschip 7720 hardware * + * These assume a 3.6864MHz crystal, the standard /16, and * + * MCR.7 = 0. */ +static struct divisor_table_entry divisor_table[] = { + { 50, 2304}, + { 110, 1047}, /* 2094.545455 => 230450 => .0217 % over */ + { 134, 857}, /* 1713.011152 => 230398.5 => .00065% under */ + { 150, 768}, + { 300, 384}, + { 600, 192}, + { 1200, 96}, + { 1800, 64}, + { 2400, 48}, + { 4800, 24}, + { 7200, 16}, + { 9600, 12}, + { 19200, 6}, + { 38400, 3}, + { 57600, 2}, + { 115200, 1}, +}; + +/***************************************************************************** + * calc_baud_rate_divisor + * this function calculates the proper baud rate divisor for the specified + * baud rate. + *****************************************************************************/ +static int calc_baud_rate_divisor(struct usb_serial_port *port, int baudrate, int *divisor) +{ + int i; + __u16 custom; + __u16 round1; + __u16 round; + + + dev_dbg(&port->dev, "%s - %d\n", __func__, baudrate); + + for (i = 0; i < ARRAY_SIZE(divisor_table); i++) { + if (divisor_table[i].baudrate == baudrate) { + *divisor = divisor_table[i].divisor; + return 0; + } + } + + /* After trying for all the standard baud rates * + * Try calculating the divisor for this baud rate */ + if (baudrate > 75 && baudrate < 230400) { + /* get the divisor */ + custom = (__u16)(230400L / baudrate); + + /* Check for round off */ + round1 = (__u16)(2304000L / baudrate); + round = (__u16)(round1 - (custom * 10)); + if (round > 4) + custom++; + *divisor = custom; + + dev_dbg(&port->dev, "Baud %d = %d\n", baudrate, custom); + return 0; + } + + dev_dbg(&port->dev, "Baud calculation Failed...\n"); + return -EINVAL; +} + +/* + * send_cmd_write_baud_rate + * this function sends the proper command to change the baud rate of the + * specified port. + */ +static int send_cmd_write_baud_rate(struct moschip_port *mos7720_port, + int baudrate) +{ + struct usb_serial_port *port; + struct usb_serial *serial; + int divisor; + int status; + unsigned char number; + + if (mos7720_port == NULL) + return -1; + + port = mos7720_port->port; + serial = port->serial; + + number = port->number - port->serial->minor; + dev_dbg(&port->dev, "%s - baud = %d\n", __func__, baudrate); + + /* Calculate the Divisor */ + status = calc_baud_rate_divisor(port, baudrate, &divisor); + if (status) { + dev_err(&port->dev, "%s - bad baud rate\n", __func__); + return status; + } + + /* Enable access to divisor latch */ + mos7720_port->shadowLCR = mos7720_port->shadowLCR | UART_LCR_DLAB; + write_mos_reg(serial, number, LCR, mos7720_port->shadowLCR); + + /* Write the divisor */ + write_mos_reg(serial, number, DLL, (__u8)(divisor & 0xff)); + write_mos_reg(serial, number, DLM, (__u8)((divisor & 0xff00) >> 8)); + + /* Disable access to divisor latch */ + mos7720_port->shadowLCR = mos7720_port->shadowLCR & ~UART_LCR_DLAB; + write_mos_reg(serial, number, LCR, mos7720_port->shadowLCR); + + return status; +} + +/* + * change_port_settings + * This routine is called to set the UART on the device to match + * the specified new settings. + */ +static void change_port_settings(struct tty_struct *tty, + struct moschip_port *mos7720_port, + struct ktermios *old_termios) +{ + struct usb_serial_port *port; + struct usb_serial *serial; + int baud; + unsigned cflag; + unsigned iflag; + __u8 mask = 0xff; + __u8 lData; + __u8 lParity; + __u8 lStop; + int status; + int port_number; + + if (mos7720_port == NULL) + return ; + + port = mos7720_port->port; + serial = port->serial; + port_number = port->number - port->serial->minor; + + if (!mos7720_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + lData = UART_LCR_WLEN8; + lStop = 0x00; /* 1 stop bit */ + lParity = 0x00; /* No parity */ + + cflag = tty->termios.c_cflag; + iflag = tty->termios.c_iflag; + + /* Change the number of bits */ + switch (cflag & CSIZE) { + case CS5: + lData = UART_LCR_WLEN5; + mask = 0x1f; + break; + + case CS6: + lData = UART_LCR_WLEN6; + mask = 0x3f; + break; + + case CS7: + lData = UART_LCR_WLEN7; + mask = 0x7f; + break; + default: + case CS8: + lData = UART_LCR_WLEN8; + break; + } + + /* Change the Parity bit */ + if (cflag & PARENB) { + if (cflag & PARODD) { + lParity = UART_LCR_PARITY; + dev_dbg(&port->dev, "%s - parity = odd\n", __func__); + } else { + lParity = (UART_LCR_EPAR | UART_LCR_PARITY); + dev_dbg(&port->dev, "%s - parity = even\n", __func__); + } + + } else { + dev_dbg(&port->dev, "%s - parity = none\n", __func__); + } + + if (cflag & CMSPAR) + lParity = lParity | 0x20; + + /* Change the Stop bit */ + if (cflag & CSTOPB) { + lStop = UART_LCR_STOP; + dev_dbg(&port->dev, "%s - stop bits = 2\n", __func__); + } else { + lStop = 0x00; + dev_dbg(&port->dev, "%s - stop bits = 1\n", __func__); + } + +#define LCR_BITS_MASK 0x03 /* Mask for bits/char field */ +#define LCR_STOP_MASK 0x04 /* Mask for stop bits field */ +#define LCR_PAR_MASK 0x38 /* Mask for parity field */ + + /* Update the LCR with the correct value */ + mos7720_port->shadowLCR &= + ~(LCR_BITS_MASK | LCR_STOP_MASK | LCR_PAR_MASK); + mos7720_port->shadowLCR |= (lData | lParity | lStop); + + + /* Disable Interrupts */ + write_mos_reg(serial, port_number, IER, 0x00); + write_mos_reg(serial, port_number, FCR, 0x00); + write_mos_reg(serial, port_number, FCR, 0xcf); + + /* Send the updated LCR value to the mos7720 */ + write_mos_reg(serial, port_number, LCR, mos7720_port->shadowLCR); + mos7720_port->shadowMCR = 0x0b; + write_mos_reg(serial, port_number, MCR, mos7720_port->shadowMCR); + + /* set up the MCR register and send it to the mos7720 */ + mos7720_port->shadowMCR = UART_MCR_OUT2; + if (cflag & CBAUD) + mos7720_port->shadowMCR |= (UART_MCR_DTR | UART_MCR_RTS); + + if (cflag & CRTSCTS) { + mos7720_port->shadowMCR |= (UART_MCR_XONANY); + /* To set hardware flow control to the specified * + * serial port, in SP1/2_CONTROL_REG */ + if (port_number) + write_mos_reg(serial, dummy, SP_CONTROL_REG, 0x01); + else + write_mos_reg(serial, dummy, SP_CONTROL_REG, 0x02); + + } else + mos7720_port->shadowMCR &= ~(UART_MCR_XONANY); + + write_mos_reg(serial, port_number, MCR, mos7720_port->shadowMCR); + + /* Determine divisor based on baud rate */ + baud = tty_get_baud_rate(tty); + if (!baud) { + /* pick a default, any default... */ + dev_dbg(&port->dev, "Picked default baud...\n"); + baud = 9600; + } + + if (baud >= 230400) { + set_higher_rates(mos7720_port, baud); + /* Enable Interrupts */ + write_mos_reg(serial, port_number, IER, 0x0c); + return; + } + + dev_dbg(&port->dev, "%s - baud rate = %d\n", __func__, baud); + status = send_cmd_write_baud_rate(mos7720_port, baud); + /* FIXME: needs to write actual resulting baud back not just + blindly do so */ + if (cflag & CBAUD) + tty_encode_baud_rate(tty, baud, baud); + /* Enable Interrupts */ + write_mos_reg(serial, port_number, IER, 0x0c); + + if (port->read_urb->status != -EINPROGRESS) { + status = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (status) + dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n", status); + } +} + +/* + * mos7720_set_termios + * this function is called by the tty driver when it wants to change the + * termios structure. + */ +static void mos7720_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + int status; + unsigned int cflag; + struct usb_serial *serial; + struct moschip_port *mos7720_port; + + serial = port->serial; + + mos7720_port = usb_get_serial_port_data(port); + + if (mos7720_port == NULL) + return; + + if (!mos7720_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + dev_dbg(&port->dev, "setting termios - ASPIRE\n"); + + cflag = tty->termios.c_cflag; + + dev_dbg(&port->dev, "%s - cflag %08x iflag %08x\n", __func__, + tty->termios.c_cflag, RELEVANT_IFLAG(tty->termios.c_iflag)); + + dev_dbg(&port->dev, "%s - old cflag %08x old iflag %08x\n", __func__, + old_termios->c_cflag, RELEVANT_IFLAG(old_termios->c_iflag)); + + /* change the port settings to the new ones specified */ + change_port_settings(tty, mos7720_port, old_termios); + + if (port->read_urb->status != -EINPROGRESS) { + status = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (status) + dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n", status); + } +} + +/* + * get_lsr_info - get line status register info + * + * Purpose: Let user call ioctl() to get info when the UART physically + * is emptied. On bus types like RS485, the transmitter must + * release the bus after transmitting. This must be done when + * the transmit shift register is empty, not be done when the + * transmit holding register is empty. This functionality + * allows an RS485 driver to be written in user space. + */ +static int get_lsr_info(struct tty_struct *tty, + struct moschip_port *mos7720_port, unsigned int __user *value) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int result = 0; + unsigned char data = 0; + int port_number = port->number - port->serial->minor; + int count; + + count = mos7720_chars_in_buffer(tty); + if (count == 0) { + read_mos_reg(port->serial, port_number, LSR, &data); + if ((data & (UART_LSR_TEMT | UART_LSR_THRE)) + == (UART_LSR_TEMT | UART_LSR_THRE)) { + dev_dbg(&port->dev, "%s -- Empty\n", __func__); + result = TIOCSER_TEMT; + } + } + if (copy_to_user(value, &result, sizeof(int))) + return -EFAULT; + return 0; +} + +static int mos7720_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7720_port = usb_get_serial_port_data(port); + unsigned int result = 0; + unsigned int mcr ; + unsigned int msr ; + + mcr = mos7720_port->shadowMCR; + msr = mos7720_port->shadowMSR; + + result = ((mcr & UART_MCR_DTR) ? TIOCM_DTR : 0) /* 0x002 */ + | ((mcr & UART_MCR_RTS) ? TIOCM_RTS : 0) /* 0x004 */ + | ((msr & UART_MSR_CTS) ? TIOCM_CTS : 0) /* 0x020 */ + | ((msr & UART_MSR_DCD) ? TIOCM_CAR : 0) /* 0x040 */ + | ((msr & UART_MSR_RI) ? TIOCM_RI : 0) /* 0x080 */ + | ((msr & UART_MSR_DSR) ? TIOCM_DSR : 0); /* 0x100 */ + + return result; +} + +static int mos7720_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7720_port = usb_get_serial_port_data(port); + unsigned int mcr ; + + mcr = mos7720_port->shadowMCR; + + if (set & TIOCM_RTS) + mcr |= UART_MCR_RTS; + if (set & TIOCM_DTR) + mcr |= UART_MCR_DTR; + if (set & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + if (clear & TIOCM_RTS) + mcr &= ~UART_MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~UART_MCR_DTR; + if (clear & TIOCM_LOOP) + mcr &= ~UART_MCR_LOOP; + + mos7720_port->shadowMCR = mcr; + write_mos_reg(port->serial, port->number - port->serial->minor, + MCR, mos7720_port->shadowMCR); + + return 0; +} + +static int set_modem_info(struct moschip_port *mos7720_port, unsigned int cmd, + unsigned int __user *value) +{ + unsigned int mcr; + unsigned int arg; + + struct usb_serial_port *port; + + if (mos7720_port == NULL) + return -1; + + port = (struct usb_serial_port *)mos7720_port->port; + mcr = mos7720_port->shadowMCR; + + if (copy_from_user(&arg, value, sizeof(int))) + return -EFAULT; + + switch (cmd) { + case TIOCMBIS: + if (arg & TIOCM_RTS) + mcr |= UART_MCR_RTS; + if (arg & TIOCM_DTR) + mcr |= UART_MCR_RTS; + if (arg & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + break; + + case TIOCMBIC: + if (arg & TIOCM_RTS) + mcr &= ~UART_MCR_RTS; + if (arg & TIOCM_DTR) + mcr &= ~UART_MCR_RTS; + if (arg & TIOCM_LOOP) + mcr &= ~UART_MCR_LOOP; + break; + + } + + mos7720_port->shadowMCR = mcr; + write_mos_reg(port->serial, port->number - port->serial->minor, + MCR, mos7720_port->shadowMCR); + + return 0; +} + +static int get_serial_info(struct moschip_port *mos7720_port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + + tmp.type = PORT_16550A; + tmp.line = mos7720_port->port->serial->minor; + tmp.port = mos7720_port->port->number; + tmp.irq = 0; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = NUM_URBS * URB_TRANSFER_BUFFER_SIZE; + tmp.baud_base = 9600; + tmp.close_delay = 5*HZ; + tmp.closing_wait = 30*HZ; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int mos7720_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7720_port; + + mos7720_port = usb_get_serial_port_data(port); + if (mos7720_port == NULL) + return -ENODEV; + + dev_dbg(&port->dev, "%s - cmd = 0x%x", __func__, cmd); + + switch (cmd) { + case TIOCSERGETLSR: + dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__); + return get_lsr_info(tty, mos7720_port, + (unsigned int __user *)arg); + + /* FIXME: These should be using the mode methods */ + case TIOCMBIS: + case TIOCMBIC: + dev_dbg(&port->dev, "%s TIOCMSET/TIOCMBIC/TIOCMSET\n", __func__); + return set_modem_info(mos7720_port, cmd, + (unsigned int __user *)arg); + + case TIOCGSERIAL: + dev_dbg(&port->dev, "%s TIOCGSERIAL\n", __func__); + return get_serial_info(mos7720_port, + (struct serial_struct __user *)arg); + } + + return -ENOIOCTLCMD; +} + +static int mos7720_startup(struct usb_serial *serial) +{ + struct usb_device *dev; + char data; + u16 product; + int ret_val; + + product = le16_to_cpu(serial->dev->descriptor.idProduct); + dev = serial->dev; + + /* + * The 7715 uses the first bulk in/out endpoint pair for the parallel + * port, and the second for the serial port. Because the usbserial core + * assumes both pairs are serial ports, we must engage in a bit of + * subterfuge and swap the pointers for ports 0 and 1 in order to make + * port 0 point to the serial port. However, both moschip devices use a + * single interrupt-in endpoint for both ports (as mentioned a little + * further down), and this endpoint was assigned to port 0. So after + * the swap, we must copy the interrupt endpoint elements from port 1 + * (as newly assigned) to port 0, and null out port 1 pointers. + */ + if (product == MOSCHIP_DEVICE_ID_7715) { + struct usb_serial_port *tmp = serial->port[0]; + serial->port[0] = serial->port[1]; + serial->port[1] = tmp; + serial->port[0]->interrupt_in_urb = tmp->interrupt_in_urb; + serial->port[0]->interrupt_in_buffer = tmp->interrupt_in_buffer; + serial->port[0]->interrupt_in_endpointAddress = + tmp->interrupt_in_endpointAddress; + serial->port[1]->interrupt_in_urb = NULL; + serial->port[1]->interrupt_in_buffer = NULL; + } + + /* setting configuration feature to one */ + usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + (__u8)0x03, 0x00, 0x01, 0x00, NULL, 0x00, 5000); + + /* start the interrupt urb */ + ret_val = usb_submit_urb(serial->port[0]->interrupt_in_urb, GFP_KERNEL); + if (ret_val) + dev_err(&dev->dev, + "%s - Error %d submitting control urb\n", + __func__, ret_val); + +#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT + if (product == MOSCHIP_DEVICE_ID_7715) { + ret_val = mos7715_parport_init(serial); + if (ret_val < 0) + return ret_val; + } +#endif + /* LSR For Port 1 */ + read_mos_reg(serial, 0, LSR, &data); + dev_dbg(&dev->dev, "LSR:%x\n", data); + + return 0; +} + +static void mos7720_release(struct usb_serial *serial) +{ +#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT + /* close the parallel port */ + + if (le16_to_cpu(serial->dev->descriptor.idProduct) + == MOSCHIP_DEVICE_ID_7715) { + struct urbtracker *urbtrack; + unsigned long flags; + struct mos7715_parport *mos_parport = + usb_get_serial_data(serial); + + /* prevent NULL ptr dereference in port callbacks */ + spin_lock(&release_lock); + mos_parport->pp->private_data = NULL; + spin_unlock(&release_lock); + + /* wait for synchronous usb calls to return */ + if (mos_parport->msg_pending) + wait_for_completion_timeout(&mos_parport->syncmsg_compl, + msecs_to_jiffies(MOS_WDR_TIMEOUT)); + + parport_remove_port(mos_parport->pp); + usb_set_serial_data(serial, NULL); + mos_parport->serial = NULL; + + /* if tasklet currently scheduled, wait for it to complete */ + tasklet_kill(&mos_parport->urb_tasklet); + + /* unlink any urbs sent by the tasklet */ + spin_lock_irqsave(&mos_parport->listlock, flags); + list_for_each_entry(urbtrack, + &mos_parport->active_urbs, + urblist_entry) + usb_unlink_urb(urbtrack->urb); + spin_unlock_irqrestore(&mos_parport->listlock, flags); + + kref_put(&mos_parport->ref_count, destroy_mos_parport); + } +#endif +} + +static int mos7720_port_probe(struct usb_serial_port *port) +{ + struct moschip_port *mos7720_port; + + mos7720_port = kzalloc(sizeof(*mos7720_port), GFP_KERNEL); + if (!mos7720_port) + return -ENOMEM; + + /* Initialize all port interrupt end point to port 0 int endpoint. + * Our device has only one interrupt endpoint common to all ports. + */ + port->interrupt_in_endpointAddress = + port->serial->port[0]->interrupt_in_endpointAddress; + mos7720_port->port = port; + + usb_set_serial_port_data(port, mos7720_port); + + return 0; +} + +static int mos7720_port_remove(struct usb_serial_port *port) +{ + struct moschip_port *mos7720_port; + + mos7720_port = usb_get_serial_port_data(port); + kfree(mos7720_port); + + return 0; +} + +static struct usb_serial_driver moschip7720_2port_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "moschip7720", + }, + .description = "Moschip 2 port adapter", + .id_table = id_table, + .calc_num_ports = mos77xx_calc_num_ports, + .open = mos7720_open, + .close = mos7720_close, + .throttle = mos7720_throttle, + .unthrottle = mos7720_unthrottle, + .probe = mos77xx_probe, + .attach = mos7720_startup, + .release = mos7720_release, + .port_probe = mos7720_port_probe, + .port_remove = mos7720_port_remove, + .ioctl = mos7720_ioctl, + .tiocmget = mos7720_tiocmget, + .tiocmset = mos7720_tiocmset, + .set_termios = mos7720_set_termios, + .write = mos7720_write, + .write_room = mos7720_write_room, + .chars_in_buffer = mos7720_chars_in_buffer, + .break_ctl = mos7720_break, + .read_bulk_callback = mos7720_bulk_in_callback, + .read_int_callback = NULL /* dynamically assigned in probe() */ +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &moschip7720_2port_driver, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c new file mode 100644 index 00000000..d0601303 --- /dev/null +++ b/drivers/usb/serial/mos7840.c @@ -0,0 +1,2553 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Clean ups from Moschip version and a few ioctl implementations by: + * Paul B Schroeder + * + * Originally based on drivers/usb/serial/io_edgeport.c which is: + * Copyright (C) 2000 Inside Out Networks, All rights reserved. + * Copyright (C) 2001-2002 Greg Kroah-Hartman + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_DESC "Moschip 7840/7820 USB Serial Driver" + +/* + * 16C50 UART register defines + */ + +#define LCR_BITS_5 0x00 /* 5 bits/char */ +#define LCR_BITS_6 0x01 /* 6 bits/char */ +#define LCR_BITS_7 0x02 /* 7 bits/char */ +#define LCR_BITS_8 0x03 /* 8 bits/char */ +#define LCR_BITS_MASK 0x03 /* Mask for bits/char field */ + +#define LCR_STOP_1 0x00 /* 1 stop bit */ +#define LCR_STOP_1_5 0x04 /* 1.5 stop bits (if 5 bits/char) */ +#define LCR_STOP_2 0x04 /* 2 stop bits (if 6-8 bits/char) */ +#define LCR_STOP_MASK 0x04 /* Mask for stop bits field */ + +#define LCR_PAR_NONE 0x00 /* No parity */ +#define LCR_PAR_ODD 0x08 /* Odd parity */ +#define LCR_PAR_EVEN 0x18 /* Even parity */ +#define LCR_PAR_MARK 0x28 /* Force parity bit to 1 */ +#define LCR_PAR_SPACE 0x38 /* Force parity bit to 0 */ +#define LCR_PAR_MASK 0x38 /* Mask for parity field */ + +#define LCR_SET_BREAK 0x40 /* Set Break condition */ +#define LCR_DL_ENABLE 0x80 /* Enable access to divisor latch */ + +#define MCR_DTR 0x01 /* Assert DTR */ +#define MCR_RTS 0x02 /* Assert RTS */ +#define MCR_OUT1 0x04 /* Loopback only: Sets state of RI */ +#define MCR_MASTER_IE 0x08 /* Enable interrupt outputs */ +#define MCR_LOOPBACK 0x10 /* Set internal (digital) loopback mode */ +#define MCR_XON_ANY 0x20 /* Enable any char to exit XOFF mode */ + +#define MOS7840_MSR_CTS 0x10 /* Current state of CTS */ +#define MOS7840_MSR_DSR 0x20 /* Current state of DSR */ +#define MOS7840_MSR_RI 0x40 /* Current state of RI */ +#define MOS7840_MSR_CD 0x80 /* Current state of CD */ + +/* + * Defines used for sending commands to port + */ + +#define MOS_WDR_TIMEOUT 5000 /* default urb timeout */ + +#define MOS_PORT1 0x0200 +#define MOS_PORT2 0x0300 +#define MOS_VENREG 0x0000 +#define MOS_MAX_PORT 0x02 +#define MOS_WRITE 0x0E +#define MOS_READ 0x0D + +/* Requests */ +#define MCS_RD_RTYPE 0xC0 +#define MCS_WR_RTYPE 0x40 +#define MCS_RDREQ 0x0D +#define MCS_WRREQ 0x0E +#define MCS_CTRL_TIMEOUT 500 +#define VENDOR_READ_LENGTH (0x01) + +#define MAX_NAME_LEN 64 + +#define ZLP_REG1 0x3A /* Zero_Flag_Reg1 58 */ +#define ZLP_REG5 0x3E /* Zero_Flag_Reg5 62 */ + +/* For higher baud Rates use TIOCEXBAUD */ +#define TIOCEXBAUD 0x5462 + +/* vendor id and device id defines */ + +/* The native mos7840/7820 component */ +#define USB_VENDOR_ID_MOSCHIP 0x9710 +#define MOSCHIP_DEVICE_ID_7840 0x7840 +#define MOSCHIP_DEVICE_ID_7820 0x7820 +#define MOSCHIP_DEVICE_ID_7810 0x7810 +/* The native component can have its vendor/device id's overridden + * in vendor-specific implementations. Such devices can be handled + * by making a change here, in id_table. + */ +#define USB_VENDOR_ID_BANDB 0x0856 +#define BANDB_DEVICE_ID_USO9ML2_2 0xAC22 +#define BANDB_DEVICE_ID_USO9ML2_2P 0xBC00 +#define BANDB_DEVICE_ID_USO9ML2_4 0xAC24 +#define BANDB_DEVICE_ID_USO9ML2_4P 0xBC01 +#define BANDB_DEVICE_ID_US9ML2_2 0xAC29 +#define BANDB_DEVICE_ID_US9ML2_4 0xAC30 +#define BANDB_DEVICE_ID_USPTL4_2 0xAC31 +#define BANDB_DEVICE_ID_USPTL4_4 0xAC32 +#define BANDB_DEVICE_ID_USOPTL4_2 0xAC42 +#define BANDB_DEVICE_ID_USOPTL4_2P 0xBC02 +#define BANDB_DEVICE_ID_USOPTL4_4 0xAC44 +#define BANDB_DEVICE_ID_USOPTL4_4P 0xBC03 +#define BANDB_DEVICE_ID_USOPTL2_4 0xAC24 + +/* This driver also supports + * ATEN UC2324 device using Moschip MCS7840 + * ATEN UC2322 device using Moschip MCS7820 + */ +#define USB_VENDOR_ID_ATENINTL 0x0557 +#define ATENINTL_DEVICE_ID_UC2324 0x2011 +#define ATENINTL_DEVICE_ID_UC2322 0x7820 + +/* Interrupt Routine Defines */ + +#define SERIAL_IIR_RLS 0x06 +#define SERIAL_IIR_MS 0x00 + +/* + * Emulation of the bit mask on the LINE STATUS REGISTER. + */ +#define SERIAL_LSR_DR 0x0001 +#define SERIAL_LSR_OE 0x0002 +#define SERIAL_LSR_PE 0x0004 +#define SERIAL_LSR_FE 0x0008 +#define SERIAL_LSR_BI 0x0010 + +#define MOS_MSR_DELTA_CTS 0x10 +#define MOS_MSR_DELTA_DSR 0x20 +#define MOS_MSR_DELTA_RI 0x40 +#define MOS_MSR_DELTA_CD 0x80 + +/* Serial Port register Address */ +#define INTERRUPT_ENABLE_REGISTER ((__u16)(0x01)) +#define FIFO_CONTROL_REGISTER ((__u16)(0x02)) +#define LINE_CONTROL_REGISTER ((__u16)(0x03)) +#define MODEM_CONTROL_REGISTER ((__u16)(0x04)) +#define LINE_STATUS_REGISTER ((__u16)(0x05)) +#define MODEM_STATUS_REGISTER ((__u16)(0x06)) +#define SCRATCH_PAD_REGISTER ((__u16)(0x07)) +#define DIVISOR_LATCH_LSB ((__u16)(0x00)) +#define DIVISOR_LATCH_MSB ((__u16)(0x01)) + +#define CLK_MULTI_REGISTER ((__u16)(0x02)) +#define CLK_START_VALUE_REGISTER ((__u16)(0x03)) +#define GPIO_REGISTER ((__u16)(0x07)) + +#define SERIAL_LCR_DLAB ((__u16)(0x0080)) + +/* + * URB POOL related defines + */ +#define NUM_URBS 16 /* URB Count */ +#define URB_TRANSFER_BUFFER_SIZE 32 /* URB Size */ + +/* LED on/off milliseconds*/ +#define LED_ON_MS 500 +#define LED_OFF_MS 500 + +enum mos7840_flag { + MOS7840_FLAG_CTRL_BUSY, + MOS7840_FLAG_LED_BUSY, +}; + +static const struct usb_device_id id_table[] = { + {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7840)}, + {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7820)}, + {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7810)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2P)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_4)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_4P)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_US9ML2_2)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_US9ML2_4)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USPTL4_2)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USPTL4_4)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL4_2)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL4_2P)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL4_4)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL4_4P)}, + {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USOPTL2_4)}, + {USB_DEVICE(USB_VENDOR_ID_ATENINTL, ATENINTL_DEVICE_ID_UC2324)}, + {USB_DEVICE(USB_VENDOR_ID_ATENINTL, ATENINTL_DEVICE_ID_UC2322)}, + {} /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* This structure holds all of the local port information */ + +struct moschip_port { + int port_num; /*Actual port number in the device(1,2,etc) */ + struct urb *write_urb; /* write URB for this port */ + struct urb *read_urb; /* read URB for this port */ + __u8 shadowLCR; /* last LCR value received */ + __u8 shadowMCR; /* last MCR value received */ + char open; + char open_ports; + wait_queue_head_t wait_chase; /* for handling sleeping while waiting for chase to finish */ + struct usb_serial_port *port; /* loop back to the owner of this object */ + + /* Offsets */ + __u8 SpRegOffset; + __u8 ControlRegOffset; + __u8 DcrRegOffset; + /* for processing control URBS in interrupt context */ + struct urb *control_urb; + struct usb_ctrlrequest *dr; + char *ctrl_buf; + int MsrLsr; + + spinlock_t pool_lock; + struct urb *write_urb_pool[NUM_URBS]; + char busy[NUM_URBS]; + bool read_urb_busy; + + /* For device(s) with LED indicator */ + bool has_led; + struct timer_list led_timer1; /* Timer for LED on */ + struct timer_list led_timer2; /* Timer for LED off */ + struct urb *led_urb; + struct usb_ctrlrequest *led_dr; + + unsigned long flags; +}; + +/* + * mos7840_set_reg_sync + * To set the Control register by calling usb_fill_control_urb function + * by passing usb_sndctrlpipe function as parameter. + */ + +static int mos7840_set_reg_sync(struct usb_serial_port *port, __u16 reg, + __u16 val) +{ + struct usb_device *dev = port->serial->dev; + val = val & 0x00ff; + dev_dbg(&port->dev, "mos7840_set_reg_sync offset is %x, value %x\n", reg, val); + + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), MCS_WRREQ, + MCS_WR_RTYPE, val, reg, NULL, 0, + MOS_WDR_TIMEOUT); +} + +/* + * mos7840_get_reg_sync + * To set the Uart register by calling usb_fill_control_urb function by + * passing usb_rcvctrlpipe function as parameter. + */ + +static int mos7840_get_reg_sync(struct usb_serial_port *port, __u16 reg, + __u16 *val) +{ + struct usb_device *dev = port->serial->dev; + int ret = 0; + u8 *buf; + + buf = kmalloc(VENDOR_READ_LENGTH, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), MCS_RDREQ, + MCS_RD_RTYPE, 0, reg, buf, VENDOR_READ_LENGTH, + MOS_WDR_TIMEOUT); + *val = buf[0]; + dev_dbg(&port->dev, "%s offset is %x, return val %x\n", __func__, reg, *val); + + kfree(buf); + return ret; +} + +/* + * mos7840_set_uart_reg + * To set the Uart register by calling usb_fill_control_urb function by + * passing usb_sndctrlpipe function as parameter. + */ + +static int mos7840_set_uart_reg(struct usb_serial_port *port, __u16 reg, + __u16 val) +{ + + struct usb_device *dev = port->serial->dev; + val = val & 0x00ff; + /* For the UART control registers, the application number need + to be Or'ed */ + if (port->serial->num_ports == 4) { + val |= (((__u16) port->number - + (__u16) (port->serial->minor)) + 1) << 8; + } else { + if (((__u16) port->number - (__u16) (port->serial->minor)) == 0) { + val |= (((__u16) port->number - + (__u16) (port->serial->minor)) + 1) << 8; + } else { + val |= (((__u16) port->number - + (__u16) (port->serial->minor)) + 2) << 8; + } + } + dev_dbg(&port->dev, "%s application number is %x\n", __func__, val); + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), MCS_WRREQ, + MCS_WR_RTYPE, val, reg, NULL, 0, + MOS_WDR_TIMEOUT); + +} + +/* + * mos7840_get_uart_reg + * To set the Control register by calling usb_fill_control_urb function + * by passing usb_rcvctrlpipe function as parameter. + */ +static int mos7840_get_uart_reg(struct usb_serial_port *port, __u16 reg, + __u16 *val) +{ + struct usb_device *dev = port->serial->dev; + int ret = 0; + __u16 Wval; + u8 *buf; + + buf = kmalloc(VENDOR_READ_LENGTH, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* Wval is same as application number */ + if (port->serial->num_ports == 4) { + Wval = + (((__u16) port->number - (__u16) (port->serial->minor)) + + 1) << 8; + } else { + if (((__u16) port->number - (__u16) (port->serial->minor)) == 0) { + Wval = (((__u16) port->number - + (__u16) (port->serial->minor)) + 1) << 8; + } else { + Wval = (((__u16) port->number - + (__u16) (port->serial->minor)) + 2) << 8; + } + } + dev_dbg(&port->dev, "%s application number is %x\n", __func__, Wval); + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), MCS_RDREQ, + MCS_RD_RTYPE, Wval, reg, buf, VENDOR_READ_LENGTH, + MOS_WDR_TIMEOUT); + *val = buf[0]; + + kfree(buf); + return ret; +} + +static void mos7840_dump_serial_port(struct usb_serial_port *port, + struct moschip_port *mos7840_port) +{ + + dev_dbg(&port->dev, "SpRegOffset is %2x\n", mos7840_port->SpRegOffset); + dev_dbg(&port->dev, "ControlRegOffset is %2x\n", mos7840_port->ControlRegOffset); + dev_dbg(&port->dev, "DCRRegOffset is %2x\n", mos7840_port->DcrRegOffset); + +} + +/************************************************************************/ +/************************************************************************/ +/* I N T E R F A C E F U N C T I O N S */ +/* I N T E R F A C E F U N C T I O N S */ +/************************************************************************/ +/************************************************************************/ + +static inline void mos7840_set_port_private(struct usb_serial_port *port, + struct moschip_port *data) +{ + usb_set_serial_port_data(port, (void *)data); +} + +static inline struct moschip_port *mos7840_get_port_private(struct + usb_serial_port + *port) +{ + return (struct moschip_port *)usb_get_serial_port_data(port); +} + +static void mos7840_handle_new_msr(struct moschip_port *port, __u8 new_msr) +{ + struct moschip_port *mos7840_port; + struct async_icount *icount; + mos7840_port = port; + if (new_msr & + (MOS_MSR_DELTA_CTS | MOS_MSR_DELTA_DSR | MOS_MSR_DELTA_RI | + MOS_MSR_DELTA_CD)) { + icount = &mos7840_port->port->icount; + + /* update input line counters */ + if (new_msr & MOS_MSR_DELTA_CTS) + icount->cts++; + if (new_msr & MOS_MSR_DELTA_DSR) + icount->dsr++; + if (new_msr & MOS_MSR_DELTA_CD) + icount->dcd++; + if (new_msr & MOS_MSR_DELTA_RI) + icount->rng++; + + wake_up_interruptible(&port->port->port.delta_msr_wait); + } +} + +static void mos7840_handle_new_lsr(struct moschip_port *port, __u8 new_lsr) +{ + struct async_icount *icount; + + if (new_lsr & SERIAL_LSR_BI) { + /* + * Parity and Framing errors only count if they + * occur exclusive of a break being + * received. + */ + new_lsr &= (__u8) (SERIAL_LSR_OE | SERIAL_LSR_BI); + } + + /* update input line counters */ + icount = &port->port->icount; + if (new_lsr & SERIAL_LSR_BI) + icount->brk++; + if (new_lsr & SERIAL_LSR_OE) + icount->overrun++; + if (new_lsr & SERIAL_LSR_PE) + icount->parity++; + if (new_lsr & SERIAL_LSR_FE) + icount->frame++; +} + +/************************************************************************/ +/************************************************************************/ +/* U S B C A L L B A C K F U N C T I O N S */ +/* U S B C A L L B A C K F U N C T I O N S */ +/************************************************************************/ +/************************************************************************/ + +static void mos7840_control_callback(struct urb *urb) +{ + unsigned char *data; + struct moschip_port *mos7840_port; + struct device *dev = &urb->dev->dev; + __u8 regval = 0x0; + int status = urb->status; + + mos7840_port = urb->context; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shutting down with status: %d\n", __func__, status); + goto out; + default: + dev_dbg(dev, "%s - nonzero urb status received: %d\n", __func__, status); + goto out; + } + + dev_dbg(dev, "%s urb buffer size is %d\n", __func__, urb->actual_length); + dev_dbg(dev, "%s mos7840_port->MsrLsr is %d port %d\n", __func__, + mos7840_port->MsrLsr, mos7840_port->port_num); + data = urb->transfer_buffer; + regval = (__u8) data[0]; + dev_dbg(dev, "%s data is %x\n", __func__, regval); + if (mos7840_port->MsrLsr == 0) + mos7840_handle_new_msr(mos7840_port, regval); + else if (mos7840_port->MsrLsr == 1) + mos7840_handle_new_lsr(mos7840_port, regval); +out: + clear_bit_unlock(MOS7840_FLAG_CTRL_BUSY, &mos7840_port->flags); +} + +static int mos7840_get_reg(struct moschip_port *mcs, __u16 Wval, __u16 reg, + __u16 *val) +{ + struct usb_device *dev = mcs->port->serial->dev; + struct usb_ctrlrequest *dr = mcs->dr; + unsigned char *buffer = mcs->ctrl_buf; + int ret; + + if (test_and_set_bit_lock(MOS7840_FLAG_CTRL_BUSY, &mcs->flags)) + return -EBUSY; + + dr->bRequestType = MCS_RD_RTYPE; + dr->bRequest = MCS_RDREQ; + dr->wValue = cpu_to_le16(Wval); /* 0 */ + dr->wIndex = cpu_to_le16(reg); + dr->wLength = cpu_to_le16(2); + + usb_fill_control_urb(mcs->control_urb, dev, usb_rcvctrlpipe(dev, 0), + (unsigned char *)dr, buffer, 2, + mos7840_control_callback, mcs); + mcs->control_urb->transfer_buffer_length = 2; + ret = usb_submit_urb(mcs->control_urb, GFP_ATOMIC); + if (ret) + clear_bit_unlock(MOS7840_FLAG_CTRL_BUSY, &mcs->flags); + + return ret; +} + +static void mos7840_set_led_callback(struct urb *urb) +{ + switch (urb->status) { + case 0: + /* Success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* This urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d", + __func__, urb->status); + break; + default: + dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d", + __func__, urb->status); + } +} + +static void mos7840_set_led_async(struct moschip_port *mcs, __u16 wval, + __u16 reg) +{ + struct usb_device *dev = mcs->port->serial->dev; + struct usb_ctrlrequest *dr = mcs->led_dr; + + dr->bRequestType = MCS_WR_RTYPE; + dr->bRequest = MCS_WRREQ; + dr->wValue = cpu_to_le16(wval); + dr->wIndex = cpu_to_le16(reg); + dr->wLength = cpu_to_le16(0); + + usb_fill_control_urb(mcs->led_urb, dev, usb_sndctrlpipe(dev, 0), + (unsigned char *)dr, NULL, 0, mos7840_set_led_callback, NULL); + + usb_submit_urb(mcs->led_urb, GFP_ATOMIC); +} + +static void mos7840_set_led_sync(struct usb_serial_port *port, __u16 reg, + __u16 val) +{ + struct usb_device *dev = port->serial->dev; + + usb_control_msg(dev, usb_sndctrlpipe(dev, 0), MCS_WRREQ, MCS_WR_RTYPE, + val, reg, NULL, 0, MOS_WDR_TIMEOUT); +} + +static void mos7840_led_off(unsigned long arg) +{ + struct moschip_port *mcs = (struct moschip_port *) arg; + + /* Turn off LED */ + mos7840_set_led_async(mcs, 0x0300, MODEM_CONTROL_REGISTER); + mod_timer(&mcs->led_timer2, + jiffies + msecs_to_jiffies(LED_OFF_MS)); +} + +static void mos7840_led_flag_off(unsigned long arg) +{ + struct moschip_port *mcs = (struct moschip_port *) arg; + + clear_bit_unlock(MOS7840_FLAG_LED_BUSY, &mcs->flags); +} + +static void mos7840_led_activity(struct usb_serial_port *port) +{ + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + + if (test_and_set_bit_lock(MOS7840_FLAG_LED_BUSY, &mos7840_port->flags)) + return; + + mos7840_set_led_async(mos7840_port, 0x0301, MODEM_CONTROL_REGISTER); + mod_timer(&mos7840_port->led_timer1, + jiffies + msecs_to_jiffies(LED_ON_MS)); +} + +/***************************************************************************** + * mos7840_interrupt_callback + * this is the callback function for when we have received data on the + * interrupt endpoint. + *****************************************************************************/ + +static void mos7840_interrupt_callback(struct urb *urb) +{ + int result; + int length; + struct moschip_port *mos7840_port; + struct usb_serial *serial; + __u16 Data; + unsigned char *data; + __u8 sp[5], st; + int i, rv = 0; + __u16 wval, wreg = 0; + int status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + length = urb->actual_length; + data = urb->transfer_buffer; + + serial = urb->context; + + /* Moschip get 5 bytes + * Byte 1 IIR Port 1 (port.number is 0) + * Byte 2 IIR Port 2 (port.number is 1) + * Byte 3 IIR Port 3 (port.number is 2) + * Byte 4 IIR Port 4 (port.number is 3) + * Byte 5 FIFO status for both */ + + if (length && length > 5) { + dev_dbg(&urb->dev->dev, "%s", "Wrong data !!!\n"); + return; + } + + sp[0] = (__u8) data[0]; + sp[1] = (__u8) data[1]; + sp[2] = (__u8) data[2]; + sp[3] = (__u8) data[3]; + st = (__u8) data[4]; + + for (i = 0; i < serial->num_ports; i++) { + mos7840_port = mos7840_get_port_private(serial->port[i]); + wval = + (((__u16) serial->port[i]->number - + (__u16) (serial->minor)) + 1) << 8; + if (mos7840_port->open) { + if (sp[i] & 0x01) { + dev_dbg(&urb->dev->dev, "SP%d No Interrupt !!!\n", i); + } else { + switch (sp[i] & 0x0f) { + case SERIAL_IIR_RLS: + dev_dbg(&urb->dev->dev, "Serial Port %d: Receiver status error or \n", i); + dev_dbg(&urb->dev->dev, "address bit detected in 9-bit mode\n"); + mos7840_port->MsrLsr = 1; + wreg = LINE_STATUS_REGISTER; + break; + case SERIAL_IIR_MS: + dev_dbg(&urb->dev->dev, "Serial Port %d: Modem status change\n", i); + mos7840_port->MsrLsr = 0; + wreg = MODEM_STATUS_REGISTER; + break; + } + rv = mos7840_get_reg(mos7840_port, wval, wreg, &Data); + } + } + } + if (!(rv < 0)) + /* the completion handler for the control urb will resubmit */ + return; +exit: + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) { + dev_err(&urb->dev->dev, + "%s - Error %d submitting interrupt urb\n", + __func__, result); + } +} + +static int mos7840_port_paranoia_check(struct usb_serial_port *port, + const char *function) +{ + if (!port) { + pr_debug("%s - port == NULL\n", function); + return -1; + } + if (!port->serial) { + pr_debug("%s - port->serial == NULL\n", function); + return -1; + } + + return 0; +} + +/* Inline functions to check the sanity of a pointer that is passed to us */ +static int mos7840_serial_paranoia_check(struct usb_serial *serial, + const char *function) +{ + if (!serial) { + pr_debug("%s - serial == NULL\n", function); + return -1; + } + if (!serial->type) { + pr_debug("%s - serial->type == NULL!\n", function); + return -1; + } + + return 0; +} + +static struct usb_serial *mos7840_get_usb_serial(struct usb_serial_port *port, + const char *function) +{ + /* if no port was specified, or it fails a paranoia check */ + if (!port || + mos7840_port_paranoia_check(port, function) || + mos7840_serial_paranoia_check(port->serial, function)) { + /* then say that we don't have a valid usb_serial thing, + * which will end up genrating -ENODEV return values */ + return NULL; + } + + return port->serial; +} + +/***************************************************************************** + * mos7840_bulk_in_callback + * this is the callback function for when we have received data on the + * bulk in endpoint. + *****************************************************************************/ + +static void mos7840_bulk_in_callback(struct urb *urb) +{ + int retval; + unsigned char *data; + struct usb_serial *serial; + struct usb_serial_port *port; + struct moschip_port *mos7840_port; + int status = urb->status; + + mos7840_port = urb->context; + if (!mos7840_port) + return; + + if (status) { + dev_dbg(&urb->dev->dev, "nonzero read bulk status received: %d\n", status); + mos7840_port->read_urb_busy = false; + return; + } + + port = mos7840_port->port; + if (mos7840_port_paranoia_check(port, __func__)) { + mos7840_port->read_urb_busy = false; + return; + } + + serial = mos7840_get_usb_serial(port, __func__); + if (!serial) { + mos7840_port->read_urb_busy = false; + return; + } + + data = urb->transfer_buffer; + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + + if (urb->actual_length) { + struct tty_port *tport = &mos7840_port->port->port; + tty_insert_flip_string(tport, data, urb->actual_length); + tty_flip_buffer_push(tport); + port->icount.rx += urb->actual_length; + dev_dbg(&port->dev, "icount.rx is %d:\n", port->icount.rx); + } + + if (!mos7840_port->read_urb) { + dev_dbg(&port->dev, "%s", "URB KILLED !!!\n"); + mos7840_port->read_urb_busy = false; + return; + } + + if (mos7840_port->has_led) + mos7840_led_activity(port); + + mos7840_port->read_urb_busy = true; + retval = usb_submit_urb(mos7840_port->read_urb, GFP_ATOMIC); + + if (retval) { + dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, retval = %d\n", retval); + mos7840_port->read_urb_busy = false; + } +} + +/***************************************************************************** + * mos7840_bulk_out_data_callback + * this is the callback function for when we have finished sending + * serial data on the bulk out endpoint. + *****************************************************************************/ + +static void mos7840_bulk_out_data_callback(struct urb *urb) +{ + struct moschip_port *mos7840_port; + struct usb_serial_port *port; + int status = urb->status; + int i; + + mos7840_port = urb->context; + port = mos7840_port->port; + spin_lock(&mos7840_port->pool_lock); + for (i = 0; i < NUM_URBS; i++) { + if (urb == mos7840_port->write_urb_pool[i]) { + mos7840_port->busy[i] = 0; + break; + } + } + spin_unlock(&mos7840_port->pool_lock); + + if (status) { + dev_dbg(&port->dev, "nonzero write bulk status received:%d\n", status); + return; + } + + if (mos7840_port_paranoia_check(port, __func__)) + return; + + if (mos7840_port->open) + tty_port_tty_wakeup(&port->port); + +} + +/************************************************************************/ +/* D R I V E R T T Y I N T E R F A C E F U N C T I O N S */ +/************************************************************************/ + +/***************************************************************************** + * mos7840_open + * this function is called by the tty driver when a port is opened + * If successful, we return 0 + * Otherwise we return a negative error number. + *****************************************************************************/ + +static int mos7840_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int response; + int j; + struct usb_serial *serial; + struct urb *urb; + __u16 Data; + int status; + struct moschip_port *mos7840_port; + struct moschip_port *port0; + + if (mos7840_port_paranoia_check(port, __func__)) + return -ENODEV; + + serial = port->serial; + + if (mos7840_serial_paranoia_check(serial, __func__)) + return -ENODEV; + + mos7840_port = mos7840_get_port_private(port); + port0 = mos7840_get_port_private(serial->port[0]); + + if (mos7840_port == NULL || port0 == NULL) + return -ENODEV; + + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + port0->open_ports++; + + /* Initialising the write urb pool */ + for (j = 0; j < NUM_URBS; ++j) { + urb = usb_alloc_urb(0, GFP_KERNEL); + mos7840_port->write_urb_pool[j] = urb; + + if (urb == NULL) { + dev_err(&port->dev, "No more urbs???\n"); + continue; + } + + urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE, + GFP_KERNEL); + if (!urb->transfer_buffer) { + usb_free_urb(urb); + mos7840_port->write_urb_pool[j] = NULL; + dev_err(&port->dev, + "%s-out of memory for urb buffers.\n", + __func__); + continue; + } + } + +/***************************************************************************** + * Initialize MCS7840 -- Write Init values to corresponding Registers + * + * Register Index + * 1 : IER + * 2 : FCR + * 3 : LCR + * 4 : MCR + * + * 0x08 : SP1/2 Control Reg + *****************************************************************************/ + + /* NEED to check the following Block */ + + Data = 0x0; + status = mos7840_get_reg_sync(port, mos7840_port->SpRegOffset, &Data); + if (status < 0) { + dev_dbg(&port->dev, "Reading Spreg failed\n"); + goto err; + } + Data |= 0x80; + status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data); + if (status < 0) { + dev_dbg(&port->dev, "writing Spreg failed\n"); + goto err; + } + + Data &= ~0x80; + status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data); + if (status < 0) { + dev_dbg(&port->dev, "writing Spreg failed\n"); + goto err; + } + /* End of block to be checked */ + + Data = 0x0; + status = mos7840_get_reg_sync(port, mos7840_port->ControlRegOffset, + &Data); + if (status < 0) { + dev_dbg(&port->dev, "Reading Controlreg failed\n"); + goto err; + } + Data |= 0x08; /* Driver done bit */ + Data |= 0x20; /* rx_disable */ + status = mos7840_set_reg_sync(port, + mos7840_port->ControlRegOffset, Data); + if (status < 0) { + dev_dbg(&port->dev, "writing Controlreg failed\n"); + goto err; + } + /* do register settings here */ + /* Set all regs to the device default values. */ + /*********************************** + * First Disable all interrupts. + ***********************************/ + Data = 0x00; + status = mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data); + if (status < 0) { + dev_dbg(&port->dev, "disabling interrupts failed\n"); + goto err; + } + /* Set FIFO_CONTROL_REGISTER to the default value */ + Data = 0x00; + status = mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing FIFO_CONTROL_REGISTER failed\n"); + goto err; + } + + Data = 0xcf; + status = mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing FIFO_CONTROL_REGISTER failed\n"); + goto err; + } + + Data = 0x03; + status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); + mos7840_port->shadowLCR = Data; + + Data = 0x0b; + status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); + mos7840_port->shadowMCR = Data; + + Data = 0x00; + status = mos7840_get_uart_reg(port, LINE_CONTROL_REGISTER, &Data); + mos7840_port->shadowLCR = Data; + + Data |= SERIAL_LCR_DLAB; /* data latch enable in LCR 0x80 */ + status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); + + Data = 0x0c; + status = mos7840_set_uart_reg(port, DIVISOR_LATCH_LSB, Data); + + Data = 0x0; + status = mos7840_set_uart_reg(port, DIVISOR_LATCH_MSB, Data); + + Data = 0x00; + status = mos7840_get_uart_reg(port, LINE_CONTROL_REGISTER, &Data); + + Data = Data & ~SERIAL_LCR_DLAB; + status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); + mos7840_port->shadowLCR = Data; + + /* clearing Bulkin and Bulkout Fifo */ + Data = 0x0; + status = mos7840_get_reg_sync(port, mos7840_port->SpRegOffset, &Data); + + Data = Data | 0x0c; + status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data); + + Data = Data & ~0x0c; + status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data); + /* Finally enable all interrupts */ + Data = 0x0c; + status = mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data); + + /* clearing rx_disable */ + Data = 0x0; + status = mos7840_get_reg_sync(port, mos7840_port->ControlRegOffset, + &Data); + Data = Data & ~0x20; + status = mos7840_set_reg_sync(port, mos7840_port->ControlRegOffset, + Data); + + /* rx_negate */ + Data = 0x0; + status = mos7840_get_reg_sync(port, mos7840_port->ControlRegOffset, + &Data); + Data = Data | 0x10; + status = mos7840_set_reg_sync(port, mos7840_port->ControlRegOffset, + Data); + + /* Check to see if we've set up our endpoint info yet * + * (can't set it up in mos7840_startup as the structures * + * were not set up at that time.) */ + if (port0->open_ports == 1) { + if (serial->port[0]->interrupt_in_buffer == NULL) { + /* set up interrupt urb */ + usb_fill_int_urb(serial->port[0]->interrupt_in_urb, + serial->dev, + usb_rcvintpipe(serial->dev, + serial->port[0]->interrupt_in_endpointAddress), + serial->port[0]->interrupt_in_buffer, + serial->port[0]->interrupt_in_urb-> + transfer_buffer_length, + mos7840_interrupt_callback, + serial, + serial->port[0]->interrupt_in_urb->interval); + + /* start interrupt read for mos7840 * + * will continue as long as mos7840 is connected */ + + response = + usb_submit_urb(serial->port[0]->interrupt_in_urb, + GFP_KERNEL); + if (response) { + dev_err(&port->dev, "%s - Error %d submitting " + "interrupt urb\n", __func__, response); + } + + } + + } + + /* see if we've set up our endpoint info yet * + * (can't set it up in mos7840_startup as the * + * structures were not set up at that time.) */ + + dev_dbg(&port->dev, "port number is %d\n", port->number); + dev_dbg(&port->dev, "serial number is %d\n", port->serial->minor); + dev_dbg(&port->dev, "Bulkin endpoint is %d\n", port->bulk_in_endpointAddress); + dev_dbg(&port->dev, "BulkOut endpoint is %d\n", port->bulk_out_endpointAddress); + dev_dbg(&port->dev, "Interrupt endpoint is %d\n", port->interrupt_in_endpointAddress); + dev_dbg(&port->dev, "port's number in the device is %d\n", mos7840_port->port_num); + mos7840_port->read_urb = port->read_urb; + + /* set up our bulk in urb */ + if ((serial->num_ports == 2) + && ((((__u16)port->number - + (__u16)(port->serial->minor)) % 2) != 0)) { + usb_fill_bulk_urb(mos7840_port->read_urb, + serial->dev, + usb_rcvbulkpipe(serial->dev, + (port->bulk_in_endpointAddress) + 2), + port->bulk_in_buffer, + mos7840_port->read_urb->transfer_buffer_length, + mos7840_bulk_in_callback, mos7840_port); + } else { + usb_fill_bulk_urb(mos7840_port->read_urb, + serial->dev, + usb_rcvbulkpipe(serial->dev, + port->bulk_in_endpointAddress), + port->bulk_in_buffer, + mos7840_port->read_urb->transfer_buffer_length, + mos7840_bulk_in_callback, mos7840_port); + } + + dev_dbg(&port->dev, "%s: bulkin endpoint is %d\n", __func__, port->bulk_in_endpointAddress); + mos7840_port->read_urb_busy = true; + response = usb_submit_urb(mos7840_port->read_urb, GFP_KERNEL); + if (response) { + dev_err(&port->dev, "%s - Error %d submitting control urb\n", + __func__, response); + mos7840_port->read_urb_busy = false; + } + + /* initialize our wait queues */ + init_waitqueue_head(&mos7840_port->wait_chase); + + /* initialize our port settings */ + /* Must set to enable ints! */ + mos7840_port->shadowMCR = MCR_MASTER_IE; + /* send a open port command */ + mos7840_port->open = 1; + /* mos7840_change_port_settings(mos7840_port,old_termios); */ + + return 0; +err: + for (j = 0; j < NUM_URBS; ++j) { + urb = mos7840_port->write_urb_pool[j]; + if (!urb) + continue; + kfree(urb->transfer_buffer); + usb_free_urb(urb); + } + return status; +} + +/***************************************************************************** + * mos7840_chars_in_buffer + * this function is called by the tty driver when it wants to know how many + * bytes of data we currently have outstanding in the port (data that has + * been written, but hasn't made it out the port yet) + * If successful, we return the number of bytes left to be written in the + * system, + * Otherwise we return zero. + *****************************************************************************/ + +static int mos7840_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + int i; + int chars = 0; + unsigned long flags; + struct moschip_port *mos7840_port; + + if (mos7840_port_paranoia_check(port, __func__)) + return 0; + + mos7840_port = mos7840_get_port_private(port); + if (mos7840_port == NULL) + return 0; + + spin_lock_irqsave(&mos7840_port->pool_lock, flags); + for (i = 0; i < NUM_URBS; ++i) { + if (mos7840_port->busy[i]) { + struct urb *urb = mos7840_port->write_urb_pool[i]; + chars += urb->transfer_buffer_length; + } + } + spin_unlock_irqrestore(&mos7840_port->pool_lock, flags); + dev_dbg(&port->dev, "%s - returns %d\n", __func__, chars); + return chars; + +} + +/***************************************************************************** + * mos7840_close + * this function is called by the tty driver when a port is closed + *****************************************************************************/ + +static void mos7840_close(struct usb_serial_port *port) +{ + struct usb_serial *serial; + struct moschip_port *mos7840_port; + struct moschip_port *port0; + int j; + __u16 Data; + + if (mos7840_port_paranoia_check(port, __func__)) + return; + + serial = mos7840_get_usb_serial(port, __func__); + if (!serial) + return; + + mos7840_port = mos7840_get_port_private(port); + port0 = mos7840_get_port_private(serial->port[0]); + + if (mos7840_port == NULL || port0 == NULL) + return; + + for (j = 0; j < NUM_URBS; ++j) + usb_kill_urb(mos7840_port->write_urb_pool[j]); + + /* Freeing Write URBs */ + for (j = 0; j < NUM_URBS; ++j) { + if (mos7840_port->write_urb_pool[j]) { + if (mos7840_port->write_urb_pool[j]->transfer_buffer) + kfree(mos7840_port->write_urb_pool[j]-> + transfer_buffer); + + usb_free_urb(mos7840_port->write_urb_pool[j]); + } + } + + usb_kill_urb(mos7840_port->write_urb); + usb_kill_urb(mos7840_port->read_urb); + mos7840_port->read_urb_busy = false; + + port0->open_ports--; + dev_dbg(&port->dev, "%s in close%d:in port%d\n", __func__, port0->open_ports, port->number); + if (port0->open_ports == 0) { + if (serial->port[0]->interrupt_in_urb) { + dev_dbg(&port->dev, "Shutdown interrupt_in_urb\n"); + usb_kill_urb(serial->port[0]->interrupt_in_urb); + } + } + + if (mos7840_port->write_urb) { + /* if this urb had a transfer buffer already (old tx) free it */ + kfree(mos7840_port->write_urb->transfer_buffer); + usb_free_urb(mos7840_port->write_urb); + } + + Data = 0x0; + mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); + + Data = 0x00; + mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data); + + mos7840_port->open = 0; +} + +/************************************************************************ + * + * mos7840_block_until_chase_response + * + * This function will block the close until one of the following: + * 1. Response to our Chase comes from mos7840 + * 2. A timeout of 10 seconds without activity has expired + * (1K of mos7840 data @ 2400 baud ==> 4 sec to empty) + * + ************************************************************************/ + +static void mos7840_block_until_chase_response(struct tty_struct *tty, + struct moschip_port *mos7840_port) +{ + int timeout = msecs_to_jiffies(1000); + int wait = 10; + int count; + + while (1) { + count = mos7840_chars_in_buffer(tty); + + /* Check for Buffer status */ + if (count <= 0) + return; + + /* Block the thread for a while */ + interruptible_sleep_on_timeout(&mos7840_port->wait_chase, + timeout); + /* No activity.. count down section */ + wait--; + if (wait == 0) { + dev_dbg(&mos7840_port->port->dev, "%s - TIMEOUT\n", __func__); + return; + } else { + /* Reset timeout value back to seconds */ + wait = 10; + } + } + +} + +/***************************************************************************** + * mos7840_break + * this function sends a break to the port + *****************************************************************************/ +static void mos7840_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned char data; + struct usb_serial *serial; + struct moschip_port *mos7840_port; + + if (mos7840_port_paranoia_check(port, __func__)) + return; + + serial = mos7840_get_usb_serial(port, __func__); + if (!serial) + return; + + mos7840_port = mos7840_get_port_private(port); + + if (mos7840_port == NULL) + return; + + /* flush and block until tx is empty */ + mos7840_block_until_chase_response(tty, mos7840_port); + + if (break_state == -1) + data = mos7840_port->shadowLCR | LCR_SET_BREAK; + else + data = mos7840_port->shadowLCR & ~LCR_SET_BREAK; + + /* FIXME: no locking on shadowLCR anywhere in driver */ + mos7840_port->shadowLCR = data; + dev_dbg(&port->dev, "%s mos7840_port->shadowLCR is %x\n", __func__, mos7840_port->shadowLCR); + mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, + mos7840_port->shadowLCR); +} + +/***************************************************************************** + * mos7840_write_room + * this function is called by the tty driver when it wants to know how many + * bytes of data we can accept for a specific port. + * If successful, we return the amount of room that we have for this port + * Otherwise we return a negative error number. + *****************************************************************************/ + +static int mos7840_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + int i; + int room = 0; + unsigned long flags; + struct moschip_port *mos7840_port; + + if (mos7840_port_paranoia_check(port, __func__)) + return -1; + + mos7840_port = mos7840_get_port_private(port); + if (mos7840_port == NULL) + return -1; + + spin_lock_irqsave(&mos7840_port->pool_lock, flags); + for (i = 0; i < NUM_URBS; ++i) { + if (!mos7840_port->busy[i]) + room += URB_TRANSFER_BUFFER_SIZE; + } + spin_unlock_irqrestore(&mos7840_port->pool_lock, flags); + + room = (room == 0) ? 0 : room - URB_TRANSFER_BUFFER_SIZE + 1; + dev_dbg(&mos7840_port->port->dev, "%s - returns %d\n", __func__, room); + return room; + +} + +/***************************************************************************** + * mos7840_write + * this function is called by the tty driver when data should be written to + * the port. + * If successful, we return the number of bytes written, otherwise we + * return a negative error number. + *****************************************************************************/ + +static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *data, int count) +{ + int status; + int i; + int bytes_sent = 0; + int transfer_size; + unsigned long flags; + + struct moschip_port *mos7840_port; + struct usb_serial *serial; + struct urb *urb; + /* __u16 Data; */ + const unsigned char *current_position = data; + unsigned char *data1; + +#ifdef NOTMOS7840 + Data = 0x00; + status = mos7840_get_uart_reg(port, LINE_CONTROL_REGISTER, &Data); + mos7840_port->shadowLCR = Data; + dev_dbg(&port->dev, "%s: LINE_CONTROL_REGISTER is %x\n", __func__, Data); + dev_dbg(&port->dev, "%s: mos7840_port->shadowLCR is %x\n", __func__, mos7840_port->shadowLCR); + + /* Data = 0x03; */ + /* status = mos7840_set_uart_reg(port,LINE_CONTROL_REGISTER,Data); */ + /* mos7840_port->shadowLCR=Data;//Need to add later */ + + Data |= SERIAL_LCR_DLAB; /* data latch enable in LCR 0x80 */ + status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); + + /* Data = 0x0c; */ + /* status = mos7840_set_uart_reg(port,DIVISOR_LATCH_LSB,Data); */ + Data = 0x00; + status = mos7840_get_uart_reg(port, DIVISOR_LATCH_LSB, &Data); + dev_dbg(&port->dev, "%s: DLL value is %x\n", __func__, Data); + + Data = 0x0; + status = mos7840_get_uart_reg(port, DIVISOR_LATCH_MSB, &Data); + dev_dbg(&port->dev, "%s: DLM value is %x\n", __func__, Data); + + Data = Data & ~SERIAL_LCR_DLAB; + dev_dbg(&port->dev, "%s: mos7840_port->shadowLCR is %x\n", __func__, mos7840_port->shadowLCR); + status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); +#endif + + if (mos7840_port_paranoia_check(port, __func__)) + return -1; + + serial = port->serial; + if (mos7840_serial_paranoia_check(serial, __func__)) + return -1; + + mos7840_port = mos7840_get_port_private(port); + if (mos7840_port == NULL) + return -1; + + /* try to find a free urb in the list */ + urb = NULL; + + spin_lock_irqsave(&mos7840_port->pool_lock, flags); + for (i = 0; i < NUM_URBS; ++i) { + if (!mos7840_port->busy[i]) { + mos7840_port->busy[i] = 1; + urb = mos7840_port->write_urb_pool[i]; + dev_dbg(&port->dev, "URB:%d\n", i); + break; + } + } + spin_unlock_irqrestore(&mos7840_port->pool_lock, flags); + + if (urb == NULL) { + dev_dbg(&port->dev, "%s - no more free urbs\n", __func__); + goto exit; + } + + if (urb->transfer_buffer == NULL) { + urb->transfer_buffer = + kmalloc(URB_TRANSFER_BUFFER_SIZE, GFP_KERNEL); + + if (urb->transfer_buffer == NULL) { + dev_err_console(port, "%s no more kernel memory...\n", + __func__); + goto exit; + } + } + transfer_size = min(count, URB_TRANSFER_BUFFER_SIZE); + + memcpy(urb->transfer_buffer, current_position, transfer_size); + + /* fill urb with data and submit */ + if ((serial->num_ports == 2) + && ((((__u16)port->number - + (__u16)(port->serial->minor)) % 2) != 0)) { + usb_fill_bulk_urb(urb, + serial->dev, + usb_sndbulkpipe(serial->dev, + (port->bulk_out_endpointAddress) + 2), + urb->transfer_buffer, + transfer_size, + mos7840_bulk_out_data_callback, mos7840_port); + } else { + usb_fill_bulk_urb(urb, + serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), + urb->transfer_buffer, + transfer_size, + mos7840_bulk_out_data_callback, mos7840_port); + } + + data1 = urb->transfer_buffer; + dev_dbg(&port->dev, "bulkout endpoint is %d\n", port->bulk_out_endpointAddress); + + if (mos7840_port->has_led) + mos7840_led_activity(port); + + /* send it down the pipe */ + status = usb_submit_urb(urb, GFP_ATOMIC); + + if (status) { + mos7840_port->busy[i] = 0; + dev_err_console(port, "%s - usb_submit_urb(write bulk) failed " + "with status = %d\n", __func__, status); + bytes_sent = status; + goto exit; + } + bytes_sent = transfer_size; + port->icount.tx += transfer_size; + dev_dbg(&port->dev, "icount.tx is %d:\n", port->icount.tx); +exit: + return bytes_sent; + +} + +/***************************************************************************** + * mos7840_throttle + * this function is called by the tty driver when it wants to stop the data + * being read from the port. + *****************************************************************************/ + +static void mos7840_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7840_port; + int status; + + if (mos7840_port_paranoia_check(port, __func__)) + return; + + mos7840_port = mos7840_get_port_private(port); + + if (mos7840_port == NULL) + return; + + if (!mos7840_port->open) { + dev_dbg(&port->dev, "%s", "port not opened\n"); + return; + } + + /* if we are implementing XON/XOFF, send the stop character */ + if (I_IXOFF(tty)) { + unsigned char stop_char = STOP_CHAR(tty); + status = mos7840_write(tty, port, &stop_char, 1); + if (status <= 0) + return; + } + /* if we are implementing RTS/CTS, toggle that line */ + if (tty->termios.c_cflag & CRTSCTS) { + mos7840_port->shadowMCR &= ~MCR_RTS; + status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, + mos7840_port->shadowMCR); + if (status < 0) + return; + } +} + +/***************************************************************************** + * mos7840_unthrottle + * this function is called by the tty driver when it wants to resume + * the data being read from the port (called after mos7840_throttle is + * called) + *****************************************************************************/ +static void mos7840_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + int status; + struct moschip_port *mos7840_port = mos7840_get_port_private(port); + + if (mos7840_port_paranoia_check(port, __func__)) + return; + + if (mos7840_port == NULL) + return; + + if (!mos7840_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + /* if we are implementing XON/XOFF, send the start character */ + if (I_IXOFF(tty)) { + unsigned char start_char = START_CHAR(tty); + status = mos7840_write(tty, port, &start_char, 1); + if (status <= 0) + return; + } + + /* if we are implementing RTS/CTS, toggle that line */ + if (tty->termios.c_cflag & CRTSCTS) { + mos7840_port->shadowMCR |= MCR_RTS; + status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, + mos7840_port->shadowMCR); + if (status < 0) + return; + } +} + +static int mos7840_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7840_port; + unsigned int result; + __u16 msr; + __u16 mcr; + int status; + mos7840_port = mos7840_get_port_private(port); + + if (mos7840_port == NULL) + return -ENODEV; + + status = mos7840_get_uart_reg(port, MODEM_STATUS_REGISTER, &msr); + if (status != 1) + return -EIO; + status = mos7840_get_uart_reg(port, MODEM_CONTROL_REGISTER, &mcr); + if (status != 1) + return -EIO; + result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0) + | ((mcr & MCR_RTS) ? TIOCM_RTS : 0) + | ((mcr & MCR_LOOPBACK) ? TIOCM_LOOP : 0) + | ((msr & MOS7840_MSR_CTS) ? TIOCM_CTS : 0) + | ((msr & MOS7840_MSR_CD) ? TIOCM_CAR : 0) + | ((msr & MOS7840_MSR_RI) ? TIOCM_RI : 0) + | ((msr & MOS7840_MSR_DSR) ? TIOCM_DSR : 0); + + dev_dbg(&port->dev, "%s - 0x%04X\n", __func__, result); + + return result; +} + +static int mos7840_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7840_port; + unsigned int mcr; + int status; + + mos7840_port = mos7840_get_port_private(port); + + if (mos7840_port == NULL) + return -ENODEV; + + /* FIXME: What locks the port registers ? */ + mcr = mos7840_port->shadowMCR; + if (clear & TIOCM_RTS) + mcr &= ~MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~MCR_DTR; + if (clear & TIOCM_LOOP) + mcr &= ~MCR_LOOPBACK; + + if (set & TIOCM_RTS) + mcr |= MCR_RTS; + if (set & TIOCM_DTR) + mcr |= MCR_DTR; + if (set & TIOCM_LOOP) + mcr |= MCR_LOOPBACK; + + mos7840_port->shadowMCR = mcr; + + status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, mcr); + if (status < 0) { + dev_dbg(&port->dev, "setting MODEM_CONTROL_REGISTER Failed\n"); + return status; + } + + return 0; +} + +/***************************************************************************** + * mos7840_calc_baud_rate_divisor + * this function calculates the proper baud rate divisor for the specified + * baud rate. + *****************************************************************************/ +static int mos7840_calc_baud_rate_divisor(struct usb_serial_port *port, + int baudRate, int *divisor, + __u16 *clk_sel_val) +{ + dev_dbg(&port->dev, "%s - %d\n", __func__, baudRate); + + if (baudRate <= 115200) { + *divisor = 115200 / baudRate; + *clk_sel_val = 0x0; + } + if ((baudRate > 115200) && (baudRate <= 230400)) { + *divisor = 230400 / baudRate; + *clk_sel_val = 0x10; + } else if ((baudRate > 230400) && (baudRate <= 403200)) { + *divisor = 403200 / baudRate; + *clk_sel_val = 0x20; + } else if ((baudRate > 403200) && (baudRate <= 460800)) { + *divisor = 460800 / baudRate; + *clk_sel_val = 0x30; + } else if ((baudRate > 460800) && (baudRate <= 806400)) { + *divisor = 806400 / baudRate; + *clk_sel_val = 0x40; + } else if ((baudRate > 806400) && (baudRate <= 921600)) { + *divisor = 921600 / baudRate; + *clk_sel_val = 0x50; + } else if ((baudRate > 921600) && (baudRate <= 1572864)) { + *divisor = 1572864 / baudRate; + *clk_sel_val = 0x60; + } else if ((baudRate > 1572864) && (baudRate <= 3145728)) { + *divisor = 3145728 / baudRate; + *clk_sel_val = 0x70; + } + return 0; + +#ifdef NOTMCS7840 + + for (i = 0; i < ARRAY_SIZE(mos7840_divisor_table); i++) { + if (mos7840_divisor_table[i].BaudRate == baudrate) { + *divisor = mos7840_divisor_table[i].Divisor; + return 0; + } + } + + /* After trying for all the standard baud rates * + * Try calculating the divisor for this baud rate */ + + if (baudrate > 75 && baudrate < 230400) { + /* get the divisor */ + custom = (__u16) (230400L / baudrate); + + /* Check for round off */ + round1 = (__u16) (2304000L / baudrate); + round = (__u16) (round1 - (custom * 10)); + if (round > 4) + custom++; + *divisor = custom; + + dev_dbg(&port->dev, " Baud %d = %d\n", baudrate, custom); + return 0; + } + + dev_dbg(&port->dev, "%s", " Baud calculation Failed...\n"); + return -1; +#endif +} + +/***************************************************************************** + * mos7840_send_cmd_write_baud_rate + * this function sends the proper command to change the baud rate of the + * specified port. + *****************************************************************************/ + +static int mos7840_send_cmd_write_baud_rate(struct moschip_port *mos7840_port, + int baudRate) +{ + int divisor = 0; + int status; + __u16 Data; + unsigned char number; + __u16 clk_sel_val; + struct usb_serial_port *port; + + if (mos7840_port == NULL) + return -1; + + port = mos7840_port->port; + if (mos7840_port_paranoia_check(port, __func__)) + return -1; + + if (mos7840_serial_paranoia_check(port->serial, __func__)) + return -1; + + number = mos7840_port->port->number - mos7840_port->port->serial->minor; + + dev_dbg(&port->dev, "%s - port = %d, baud = %d\n", __func__, + mos7840_port->port->number, baudRate); + /* reset clk_uart_sel in spregOffset */ + if (baudRate > 115200) { +#ifdef HW_flow_control + /* NOTE: need to see the pther register to modify */ + /* setting h/w flow control bit to 1 */ + Data = 0x2b; + mos7840_port->shadowMCR = Data; + status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, + Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing spreg failed in set_serial_baud\n"); + return -1; + } +#endif + + } else { +#ifdef HW_flow_control + /* setting h/w flow control bit to 0 */ + Data = 0xb; + mos7840_port->shadowMCR = Data; + status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, + Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing spreg failed in set_serial_baud\n"); + return -1; + } +#endif + + } + + if (1) { /* baudRate <= 115200) */ + clk_sel_val = 0x0; + Data = 0x0; + status = mos7840_calc_baud_rate_divisor(port, baudRate, &divisor, + &clk_sel_val); + status = mos7840_get_reg_sync(port, mos7840_port->SpRegOffset, + &Data); + if (status < 0) { + dev_dbg(&port->dev, "reading spreg failed in set_serial_baud\n"); + return -1; + } + Data = (Data & 0x8f) | clk_sel_val; + status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, + Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing spreg failed in set_serial_baud\n"); + return -1; + } + /* Calculate the Divisor */ + + if (status) { + dev_err(&port->dev, "%s - bad baud rate\n", __func__); + return status; + } + /* Enable access to divisor latch */ + Data = mos7840_port->shadowLCR | SERIAL_LCR_DLAB; + mos7840_port->shadowLCR = Data; + mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); + + /* Write the divisor */ + Data = (unsigned char)(divisor & 0xff); + dev_dbg(&port->dev, "set_serial_baud Value to write DLL is %x\n", Data); + mos7840_set_uart_reg(port, DIVISOR_LATCH_LSB, Data); + + Data = (unsigned char)((divisor & 0xff00) >> 8); + dev_dbg(&port->dev, "set_serial_baud Value to write DLM is %x\n", Data); + mos7840_set_uart_reg(port, DIVISOR_LATCH_MSB, Data); + + /* Disable access to divisor latch */ + Data = mos7840_port->shadowLCR & ~SERIAL_LCR_DLAB; + mos7840_port->shadowLCR = Data; + mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); + + } + return status; +} + +/***************************************************************************** + * mos7840_change_port_settings + * This routine is called to set the UART on the device to match + * the specified new settings. + *****************************************************************************/ + +static void mos7840_change_port_settings(struct tty_struct *tty, + struct moschip_port *mos7840_port, struct ktermios *old_termios) +{ + int baud; + unsigned cflag; + unsigned iflag; + __u8 lData; + __u8 lParity; + __u8 lStop; + int status; + __u16 Data; + struct usb_serial_port *port; + struct usb_serial *serial; + + if (mos7840_port == NULL) + return; + + port = mos7840_port->port; + + if (mos7840_port_paranoia_check(port, __func__)) + return; + + if (mos7840_serial_paranoia_check(port->serial, __func__)) + return; + + serial = port->serial; + + if (!mos7840_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + lData = LCR_BITS_8; + lStop = LCR_STOP_1; + lParity = LCR_PAR_NONE; + + cflag = tty->termios.c_cflag; + iflag = tty->termios.c_iflag; + + /* Change the number of bits */ + switch (cflag & CSIZE) { + case CS5: + lData = LCR_BITS_5; + break; + + case CS6: + lData = LCR_BITS_6; + break; + + case CS7: + lData = LCR_BITS_7; + break; + + default: + case CS8: + lData = LCR_BITS_8; + break; + } + + /* Change the Parity bit */ + if (cflag & PARENB) { + if (cflag & PARODD) { + lParity = LCR_PAR_ODD; + dev_dbg(&port->dev, "%s - parity = odd\n", __func__); + } else { + lParity = LCR_PAR_EVEN; + dev_dbg(&port->dev, "%s - parity = even\n", __func__); + } + + } else { + dev_dbg(&port->dev, "%s - parity = none\n", __func__); + } + + if (cflag & CMSPAR) + lParity = lParity | 0x20; + + /* Change the Stop bit */ + if (cflag & CSTOPB) { + lStop = LCR_STOP_2; + dev_dbg(&port->dev, "%s - stop bits = 2\n", __func__); + } else { + lStop = LCR_STOP_1; + dev_dbg(&port->dev, "%s - stop bits = 1\n", __func__); + } + + /* Update the LCR with the correct value */ + mos7840_port->shadowLCR &= + ~(LCR_BITS_MASK | LCR_STOP_MASK | LCR_PAR_MASK); + mos7840_port->shadowLCR |= (lData | lParity | lStop); + + dev_dbg(&port->dev, "%s - mos7840_port->shadowLCR is %x\n", __func__, + mos7840_port->shadowLCR); + /* Disable Interrupts */ + Data = 0x00; + mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data); + + Data = 0x00; + mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data); + + Data = 0xcf; + mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data); + + /* Send the updated LCR value to the mos7840 */ + Data = mos7840_port->shadowLCR; + + mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); + + Data = 0x00b; + mos7840_port->shadowMCR = Data; + mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); + Data = 0x00b; + mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); + + /* set up the MCR register and send it to the mos7840 */ + + mos7840_port->shadowMCR = MCR_MASTER_IE; + if (cflag & CBAUD) + mos7840_port->shadowMCR |= (MCR_DTR | MCR_RTS); + + if (cflag & CRTSCTS) + mos7840_port->shadowMCR |= (MCR_XON_ANY); + else + mos7840_port->shadowMCR &= ~(MCR_XON_ANY); + + Data = mos7840_port->shadowMCR; + mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); + + /* Determine divisor based on baud rate */ + baud = tty_get_baud_rate(tty); + + if (!baud) { + /* pick a default, any default... */ + dev_dbg(&port->dev, "%s", "Picked default baud...\n"); + baud = 9600; + } + + dev_dbg(&port->dev, "%s - baud rate = %d\n", __func__, baud); + status = mos7840_send_cmd_write_baud_rate(mos7840_port, baud); + + /* Enable Interrupts */ + Data = 0x0c; + mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data); + + if (mos7840_port->read_urb_busy == false) { + mos7840_port->read_urb_busy = true; + status = usb_submit_urb(mos7840_port->read_urb, GFP_ATOMIC); + if (status) { + dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n", + status); + mos7840_port->read_urb_busy = false; + } + } + dev_dbg(&port->dev, "%s - mos7840_port->shadowLCR is End %x\n", __func__, + mos7840_port->shadowLCR); +} + +/***************************************************************************** + * mos7840_set_termios + * this function is called by the tty driver when it wants to change + * the termios structure + *****************************************************************************/ + +static void mos7840_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + int status; + unsigned int cflag; + struct usb_serial *serial; + struct moschip_port *mos7840_port; + + if (mos7840_port_paranoia_check(port, __func__)) + return; + + serial = port->serial; + + if (mos7840_serial_paranoia_check(serial, __func__)) + return; + + mos7840_port = mos7840_get_port_private(port); + + if (mos7840_port == NULL) + return; + + if (!mos7840_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + dev_dbg(&port->dev, "%s", "setting termios - \n"); + + cflag = tty->termios.c_cflag; + + dev_dbg(&port->dev, "%s - clfag %08x iflag %08x\n", __func__, + tty->termios.c_cflag, RELEVANT_IFLAG(tty->termios.c_iflag)); + dev_dbg(&port->dev, "%s - old clfag %08x old iflag %08x\n", __func__, + old_termios->c_cflag, RELEVANT_IFLAG(old_termios->c_iflag)); + dev_dbg(&port->dev, "%s - port %d\n", __func__, port->number); + + /* change the port settings to the new ones specified */ + + mos7840_change_port_settings(tty, mos7840_port, old_termios); + + if (!mos7840_port->read_urb) { + dev_dbg(&port->dev, "%s", "URB KILLED !!!!!\n"); + return; + } + + if (mos7840_port->read_urb_busy == false) { + mos7840_port->read_urb_busy = true; + status = usb_submit_urb(mos7840_port->read_urb, GFP_ATOMIC); + if (status) { + dev_dbg(&port->dev, "usb_submit_urb(read bulk) failed, status = %d\n", + status); + mos7840_port->read_urb_busy = false; + } + } +} + +/***************************************************************************** + * mos7840_get_lsr_info - get line status register info + * + * Purpose: Let user call ioctl() to get info when the UART physically + * is emptied. On bus types like RS485, the transmitter must + * release the bus after transmitting. This must be done when + * the transmit shift register is empty, not be done when the + * transmit holding register is empty. This functionality + * allows an RS485 driver to be written in user space. + *****************************************************************************/ + +static int mos7840_get_lsr_info(struct tty_struct *tty, + unsigned int __user *value) +{ + int count; + unsigned int result = 0; + + count = mos7840_chars_in_buffer(tty); + if (count == 0) + result = TIOCSER_TEMT; + + if (copy_to_user(value, &result, sizeof(int))) + return -EFAULT; + return 0; +} + +/***************************************************************************** + * mos7840_get_serial_info + * function to get information about serial port + *****************************************************************************/ + +static int mos7840_get_serial_info(struct moschip_port *mos7840_port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (mos7840_port == NULL) + return -1; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + + tmp.type = PORT_16550A; + tmp.line = mos7840_port->port->serial->minor; + tmp.port = mos7840_port->port->number; + tmp.irq = 0; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = NUM_URBS * URB_TRANSFER_BUFFER_SIZE; + tmp.baud_base = 9600; + tmp.close_delay = 5 * HZ; + tmp.closing_wait = 30 * HZ; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +/***************************************************************************** + * SerialIoctl + * this function handles any ioctl calls to the driver + *****************************************************************************/ + +static int mos7840_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + void __user *argp = (void __user *)arg; + struct moschip_port *mos7840_port; + + if (mos7840_port_paranoia_check(port, __func__)) + return -1; + + mos7840_port = mos7840_get_port_private(port); + + if (mos7840_port == NULL) + return -1; + + dev_dbg(&port->dev, "%s - cmd = 0x%x\n", __func__, cmd); + + switch (cmd) { + /* return number of bytes available */ + + case TIOCSERGETLSR: + dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__); + return mos7840_get_lsr_info(tty, argp); + + case TIOCGSERIAL: + dev_dbg(&port->dev, "%s TIOCGSERIAL\n", __func__); + return mos7840_get_serial_info(mos7840_port, argp); + + case TIOCSSERIAL: + dev_dbg(&port->dev, "%s TIOCSSERIAL\n", __func__); + break; + default: + break; + } + return -ENOIOCTLCMD; +} + +static int mos7810_check(struct usb_serial *serial) +{ + int i, pass_count = 0; + u8 *buf; + __u16 data = 0, mcr_data = 0; + __u16 test_pattern = 0x55AA; + int res; + + buf = kmalloc(VENDOR_READ_LENGTH, GFP_KERNEL); + if (!buf) + return 0; /* failed to identify 7810 */ + + /* Store MCR setting */ + res = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + MCS_RDREQ, MCS_RD_RTYPE, 0x0300, MODEM_CONTROL_REGISTER, + buf, VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT); + if (res == VENDOR_READ_LENGTH) + mcr_data = *buf; + + for (i = 0; i < 16; i++) { + /* Send the 1-bit test pattern out to MCS7810 test pin */ + usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + MCS_WRREQ, MCS_WR_RTYPE, + (0x0300 | (((test_pattern >> i) & 0x0001) << 1)), + MODEM_CONTROL_REGISTER, NULL, 0, MOS_WDR_TIMEOUT); + + /* Read the test pattern back */ + res = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), MCS_RDREQ, + MCS_RD_RTYPE, 0, GPIO_REGISTER, buf, + VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT); + if (res == VENDOR_READ_LENGTH) + data = *buf; + + /* If this is a MCS7810 device, both test patterns must match */ + if (((test_pattern >> i) ^ (~data >> 1)) & 0x0001) + break; + + pass_count++; + } + + /* Restore MCR setting */ + usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), MCS_WRREQ, + MCS_WR_RTYPE, 0x0300 | mcr_data, MODEM_CONTROL_REGISTER, NULL, + 0, MOS_WDR_TIMEOUT); + + kfree(buf); + + if (pass_count == 16) + return 1; + + return 0; +} + +static int mos7840_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + u16 product = le16_to_cpu(serial->dev->descriptor.idProduct); + u8 *buf; + int device_type; + + if (product == MOSCHIP_DEVICE_ID_7810 || + product == MOSCHIP_DEVICE_ID_7820) { + device_type = product; + goto out; + } + + buf = kzalloc(VENDOR_READ_LENGTH, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + MCS_RDREQ, MCS_RD_RTYPE, 0, GPIO_REGISTER, buf, + VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT); + + /* For a MCS7840 device GPIO0 must be set to 1 */ + if (buf[0] & 0x01) + device_type = MOSCHIP_DEVICE_ID_7840; + else if (mos7810_check(serial)) + device_type = MOSCHIP_DEVICE_ID_7810; + else + device_type = MOSCHIP_DEVICE_ID_7820; + + kfree(buf); +out: + usb_set_serial_data(serial, (void *)(unsigned long)device_type); + + return 0; +} + +static int mos7840_calc_num_ports(struct usb_serial *serial) +{ + int device_type = (unsigned long)usb_get_serial_data(serial); + int mos7840_num_ports; + + mos7840_num_ports = (device_type >> 4) & 0x000F; + + return mos7840_num_ports; +} + +static int mos7840_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + int device_type = (unsigned long)usb_get_serial_data(serial); + struct moschip_port *mos7840_port; + int status; + int pnum; + __u16 Data; + + /* we set up the pointers to the endpoints in the mos7840_open * + * function, as the structures aren't created yet. */ + + pnum = port->number - serial->minor; + + dev_dbg(&port->dev, "mos7840_startup: configuring port %d\n", pnum); + mos7840_port = kzalloc(sizeof(struct moschip_port), GFP_KERNEL); + if (mos7840_port == NULL) { + dev_err(&port->dev, "%s - Out of memory\n", __func__); + return -ENOMEM; + } + + /* Initialize all port interrupt end point to port 0 int + * endpoint. Our device has only one interrupt end point + * common to all port */ + + mos7840_port->port = port; + mos7840_set_port_private(port, mos7840_port); + spin_lock_init(&mos7840_port->pool_lock); + + /* minor is not initialised until later by + * usb-serial.c:get_free_serial() and cannot therefore be used + * to index device instances */ + mos7840_port->port_num = pnum + 1; + dev_dbg(&port->dev, "port->number = %d\n", port->number); + dev_dbg(&port->dev, "port->serial->minor = %d\n", port->serial->minor); + dev_dbg(&port->dev, "mos7840_port->port_num = %d\n", mos7840_port->port_num); + dev_dbg(&port->dev, "serial->minor = %d\n", serial->minor); + + if (mos7840_port->port_num == 1) { + mos7840_port->SpRegOffset = 0x0; + mos7840_port->ControlRegOffset = 0x1; + mos7840_port->DcrRegOffset = 0x4; + } else if ((mos7840_port->port_num == 2) && (serial->num_ports == 4)) { + mos7840_port->SpRegOffset = 0x8; + mos7840_port->ControlRegOffset = 0x9; + mos7840_port->DcrRegOffset = 0x16; + } else if ((mos7840_port->port_num == 2) && (serial->num_ports == 2)) { + mos7840_port->SpRegOffset = 0xa; + mos7840_port->ControlRegOffset = 0xb; + mos7840_port->DcrRegOffset = 0x19; + } else if ((mos7840_port->port_num == 3) && (serial->num_ports == 4)) { + mos7840_port->SpRegOffset = 0xa; + mos7840_port->ControlRegOffset = 0xb; + mos7840_port->DcrRegOffset = 0x19; + } else if ((mos7840_port->port_num == 4) && (serial->num_ports == 4)) { + mos7840_port->SpRegOffset = 0xc; + mos7840_port->ControlRegOffset = 0xd; + mos7840_port->DcrRegOffset = 0x1c; + } + mos7840_dump_serial_port(port, mos7840_port); + mos7840_set_port_private(port, mos7840_port); + + /* enable rx_disable bit in control register */ + status = mos7840_get_reg_sync(port, + mos7840_port->ControlRegOffset, &Data); + if (status < 0) { + dev_dbg(&port->dev, "Reading ControlReg failed status-0x%x\n", status); + goto out; + } else + dev_dbg(&port->dev, "ControlReg Reading success val is %x, status%d\n", Data, status); + Data |= 0x08; /* setting driver done bit */ + Data |= 0x04; /* sp1_bit to have cts change reflect in + modem status reg */ + + /* Data |= 0x20; //rx_disable bit */ + status = mos7840_set_reg_sync(port, + mos7840_port->ControlRegOffset, Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing ControlReg failed(rx_disable) status-0x%x\n", status); + goto out; + } else + dev_dbg(&port->dev, "ControlReg Writing success(rx_disable) status%d\n", status); + + /* Write default values in DCR (i.e 0x01 in DCR0, 0x05 in DCR2 + and 0x24 in DCR3 */ + Data = 0x01; + status = mos7840_set_reg_sync(port, + (__u16) (mos7840_port->DcrRegOffset + 0), Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing DCR0 failed status-0x%x\n", status); + goto out; + } else + dev_dbg(&port->dev, "DCR0 Writing success status%d\n", status); + + Data = 0x05; + status = mos7840_set_reg_sync(port, + (__u16) (mos7840_port->DcrRegOffset + 1), Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing DCR1 failed status-0x%x\n", status); + goto out; + } else + dev_dbg(&port->dev, "DCR1 Writing success status%d\n", status); + + Data = 0x24; + status = mos7840_set_reg_sync(port, + (__u16) (mos7840_port->DcrRegOffset + 2), Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing DCR2 failed status-0x%x\n", status); + goto out; + } else + dev_dbg(&port->dev, "DCR2 Writing success status%d\n", status); + + /* write values in clkstart0x0 and clkmulti 0x20 */ + Data = 0x0; + status = mos7840_set_reg_sync(port, CLK_START_VALUE_REGISTER, Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing CLK_START_VALUE_REGISTER failed status-0x%x\n", status); + goto out; + } else + dev_dbg(&port->dev, "CLK_START_VALUE_REGISTER Writing success status%d\n", status); + + Data = 0x20; + status = mos7840_set_reg_sync(port, CLK_MULTI_REGISTER, Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing CLK_MULTI_REGISTER failed status-0x%x\n", status); + goto error; + } else + dev_dbg(&port->dev, "CLK_MULTI_REGISTER Writing success status%d\n", status); + + /* write value 0x0 to scratchpad register */ + Data = 0x00; + status = mos7840_set_uart_reg(port, SCRATCH_PAD_REGISTER, Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing SCRATCH_PAD_REGISTER failed status-0x%x\n", status); + goto out; + } else + dev_dbg(&port->dev, "SCRATCH_PAD_REGISTER Writing success status%d\n", status); + + /* Zero Length flag register */ + if ((mos7840_port->port_num != 1) && (serial->num_ports == 2)) { + Data = 0xff; + status = mos7840_set_reg_sync(port, + (__u16) (ZLP_REG1 + + ((__u16)mos7840_port->port_num)), Data); + dev_dbg(&port->dev, "ZLIP offset %x\n", + (__u16)(ZLP_REG1 + ((__u16) mos7840_port->port_num))); + if (status < 0) { + dev_dbg(&port->dev, "Writing ZLP_REG%d failed status-0x%x\n", pnum + 2, status); + goto out; + } else + dev_dbg(&port->dev, "ZLP_REG%d Writing success status%d\n", pnum + 2, status); + } else { + Data = 0xff; + status = mos7840_set_reg_sync(port, + (__u16) (ZLP_REG1 + + ((__u16)mos7840_port->port_num) - 0x1), Data); + dev_dbg(&port->dev, "ZLIP offset %x\n", + (__u16)(ZLP_REG1 + ((__u16) mos7840_port->port_num) - 0x1)); + if (status < 0) { + dev_dbg(&port->dev, "Writing ZLP_REG%d failed status-0x%x\n", pnum + 1, status); + goto out; + } else + dev_dbg(&port->dev, "ZLP_REG%d Writing success status%d\n", pnum + 1, status); + + } + mos7840_port->control_urb = usb_alloc_urb(0, GFP_KERNEL); + mos7840_port->ctrl_buf = kmalloc(16, GFP_KERNEL); + mos7840_port->dr = kmalloc(sizeof(struct usb_ctrlrequest), + GFP_KERNEL); + if (!mos7840_port->control_urb || !mos7840_port->ctrl_buf || + !mos7840_port->dr) { + status = -ENOMEM; + goto error; + } + + mos7840_port->has_led = false; + + /* Initialize LED timers */ + if (device_type == MOSCHIP_DEVICE_ID_7810) { + mos7840_port->has_led = true; + + mos7840_port->led_urb = usb_alloc_urb(0, GFP_KERNEL); + mos7840_port->led_dr = kmalloc(sizeof(*mos7840_port->led_dr), + GFP_KERNEL); + if (!mos7840_port->led_urb || !mos7840_port->led_dr) { + status = -ENOMEM; + goto error; + } + + init_timer(&mos7840_port->led_timer1); + mos7840_port->led_timer1.function = mos7840_led_off; + mos7840_port->led_timer1.expires = + jiffies + msecs_to_jiffies(LED_ON_MS); + mos7840_port->led_timer1.data = (unsigned long)mos7840_port; + + init_timer(&mos7840_port->led_timer2); + mos7840_port->led_timer2.function = mos7840_led_flag_off; + mos7840_port->led_timer2.expires = + jiffies + msecs_to_jiffies(LED_OFF_MS); + mos7840_port->led_timer2.data = (unsigned long)mos7840_port; + + /* Turn off LED */ + mos7840_set_led_sync(port, MODEM_CONTROL_REGISTER, 0x0300); + } +out: + if (pnum == serial->num_ports - 1) { + /* Zero Length flag enable */ + Data = 0x0f; + status = mos7840_set_reg_sync(serial->port[0], ZLP_REG5, Data); + if (status < 0) { + dev_dbg(&port->dev, "Writing ZLP_REG5 failed status-0x%x\n", status); + goto error; + } else + dev_dbg(&port->dev, "ZLP_REG5 Writing success status%d\n", status); + + /* setting configuration feature to one */ + usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + 0x03, 0x00, 0x01, 0x00, NULL, 0x00, + MOS_WDR_TIMEOUT); + } + return 0; +error: + kfree(mos7840_port->led_dr); + usb_free_urb(mos7840_port->led_urb); + kfree(mos7840_port->dr); + kfree(mos7840_port->ctrl_buf); + usb_free_urb(mos7840_port->control_urb); + kfree(mos7840_port); + + return status; +} + +static int mos7840_port_remove(struct usb_serial_port *port) +{ + struct moschip_port *mos7840_port; + + mos7840_port = mos7840_get_port_private(port); + + if (mos7840_port->has_led) { + /* Turn off LED */ + mos7840_set_led_sync(port, MODEM_CONTROL_REGISTER, 0x0300); + + del_timer_sync(&mos7840_port->led_timer1); + del_timer_sync(&mos7840_port->led_timer2); + + usb_kill_urb(mos7840_port->led_urb); + usb_free_urb(mos7840_port->led_urb); + kfree(mos7840_port->led_dr); + } + usb_kill_urb(mos7840_port->control_urb); + usb_free_urb(mos7840_port->control_urb); + kfree(mos7840_port->ctrl_buf); + kfree(mos7840_port->dr); + kfree(mos7840_port); + + return 0; +} + +static struct usb_serial_driver moschip7840_4port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "mos7840", + }, + .description = DRIVER_DESC, + .id_table = id_table, + .num_ports = 4, + .open = mos7840_open, + .close = mos7840_close, + .write = mos7840_write, + .write_room = mos7840_write_room, + .chars_in_buffer = mos7840_chars_in_buffer, + .throttle = mos7840_throttle, + .unthrottle = mos7840_unthrottle, + .calc_num_ports = mos7840_calc_num_ports, + .probe = mos7840_probe, + .ioctl = mos7840_ioctl, + .set_termios = mos7840_set_termios, + .break_ctl = mos7840_break, + .tiocmget = mos7840_tiocmget, + .tiocmset = mos7840_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .port_probe = mos7840_port_probe, + .port_remove = mos7840_port_remove, + .read_bulk_callback = mos7840_bulk_in_callback, + .read_int_callback = mos7840_interrupt_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &moschip7840_4port_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/moto_modem.c b/drivers/usb/serial/moto_modem.c new file mode 100644 index 00000000..c5ff6c77 --- /dev/null +++ b/drivers/usb/serial/moto_modem.c @@ -0,0 +1,48 @@ +/* + * Motorola USB Phone driver + * + * Copyright (C) 2008 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * {sigh} + * Motorola should be using the CDC ACM USB spec, but instead + * they try to just "do their own thing"... This driver should handle a + * few phones in which a basic "dumb serial connection" is needed to be + * able to get a connection through to them. + */ + +#include +#include +#include +#include +#include +#include + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x05c6, 0x3197) }, /* unknown Motorola phone */ + { USB_DEVICE(0x0c44, 0x0022) }, /* unknown Mororola phone */ + { USB_DEVICE(0x22b8, 0x2a64) }, /* Motorola KRZR K1m */ + { USB_DEVICE(0x22b8, 0x2c84) }, /* Motorola VE240 phone */ + { USB_DEVICE(0x22b8, 0x2c64) }, /* Motorola V950 phone */ + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver moto_device = { + .driver = { + .owner = THIS_MODULE, + .name = "moto-modem", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &moto_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/navman.c b/drivers/usb/serial/navman.c new file mode 100644 index 00000000..38725fc8 --- /dev/null +++ b/drivers/usb/serial/navman.c @@ -0,0 +1,119 @@ +/* + * Navman Serial USB driver + * + * Copyright (C) 2006 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * TODO: + * Add termios method that uses copy_hw but also kills all echo + * flags as the navman is rx only so cannot echo. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x0a99, 0x0001) }, /* Talon Technology device */ + { USB_DEVICE(0x0df7, 0x0900) }, /* Mobile Action i-gotU */ + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static void navman_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + int result; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + + if (urb->actual_length) { + tty_insert_flip_string(&port->port, data, urb->actual_length); + tty_flip_buffer_push(&port->port); + } + +exit: + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) + dev_err(&urb->dev->dev, + "%s - Error %d submitting interrupt urb\n", + __func__, result); +} + +static int navman_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int result = 0; + + if (port->interrupt_in_urb) { + dev_dbg(&port->dev, "%s - adding interrupt input for treo\n", + __func__); + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) + dev_err(&port->dev, + "%s - failed submitting interrupt urb, error %d\n", + __func__, result); + } + return result; +} + +static void navman_close(struct usb_serial_port *port) +{ + usb_kill_urb(port->interrupt_in_urb); +} + +static int navman_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + /* + * This device can't write any data, only read from the device + */ + return -EOPNOTSUPP; +} + +static struct usb_serial_driver navman_device = { + .driver = { + .owner = THIS_MODULE, + .name = "navman", + }, + .id_table = id_table, + .num_ports = 1, + .open = navman_open, + .close = navman_close, + .write = navman_write, + .read_int_callback = navman_read_int_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &navman_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c new file mode 100644 index 00000000..5739bf6f --- /dev/null +++ b/drivers/usb/serial/omninet.c @@ -0,0 +1,259 @@ +/* + * USB ZyXEL omni.net LCD PLUS driver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + * + * Please report both successes and troubles to the author at omninet@kroah.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Alessandro Zummo" +#define DRIVER_DESC "USB ZyXEL omni.net LCD PLUS Driver" + +#define ZYXEL_VENDOR_ID 0x0586 +#define ZYXEL_OMNINET_ID 0x1000 +/* This one seems to be a re-branded ZyXEL device */ +#define BT_IGNITIONPRO_ID 0x2000 + +/* function prototypes */ +static int omninet_open(struct tty_struct *tty, struct usb_serial_port *port); +static void omninet_process_read_urb(struct urb *urb); +static void omninet_write_bulk_callback(struct urb *urb); +static int omninet_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +static int omninet_write_room(struct tty_struct *tty); +static void omninet_disconnect(struct usb_serial *serial); +static int omninet_port_probe(struct usb_serial_port *port); +static int omninet_port_remove(struct usb_serial_port *port); + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(ZYXEL_VENDOR_ID, ZYXEL_OMNINET_ID) }, + { USB_DEVICE(ZYXEL_VENDOR_ID, BT_IGNITIONPRO_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver zyxel_omninet_device = { + .driver = { + .owner = THIS_MODULE, + .name = "omninet", + }, + .description = "ZyXEL - omni.net lcd plus usb", + .id_table = id_table, + .num_ports = 1, + .port_probe = omninet_port_probe, + .port_remove = omninet_port_remove, + .open = omninet_open, + .write = omninet_write, + .write_room = omninet_write_room, + .write_bulk_callback = omninet_write_bulk_callback, + .process_read_urb = omninet_process_read_urb, + .disconnect = omninet_disconnect, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &zyxel_omninet_device, NULL +}; + + +/* + * The protocol. + * + * The omni.net always exchange 64 bytes of data with the host. The first + * four bytes are the control header. + * + * oh_seq is a sequence number. Don't know if/how it's used. + * oh_len is the length of the data bytes in the packet. + * oh_xxx Bit-mapped, related to handshaking and status info. + * I normally set it to 0x03 in transmitted frames. + * 7: Active when the TA is in a CONNECTed state. + * 6: unknown + * 5: handshaking, unknown + * 4: handshaking, unknown + * 3: unknown, usually 0 + * 2: unknown, usually 0 + * 1: handshaking, unknown, usually set to 1 in transmitted frames + * 0: handshaking, unknown, usually set to 1 in transmitted frames + * oh_pad Probably a pad byte. + * + * After the header you will find data bytes if oh_len was greater than zero. + */ +struct omninet_header { + __u8 oh_seq; + __u8 oh_len; + __u8 oh_xxx; + __u8 oh_pad; +}; + +struct omninet_data { + __u8 od_outseq; /* Sequence number for bulk_out URBs */ +}; + +static int omninet_port_probe(struct usb_serial_port *port) +{ + struct omninet_data *od; + + od = kzalloc(sizeof(*od), GFP_KERNEL); + if (!od) + return -ENOMEM; + + usb_set_serial_port_data(port, od); + + return 0; +} + +static int omninet_port_remove(struct usb_serial_port *port) +{ + struct omninet_data *od; + + od = usb_get_serial_port_data(port); + kfree(od); + + return 0; +} + +static int omninet_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct usb_serial_port *wport; + + wport = serial->port[1]; + tty_port_tty_set(&wport->port, tty); + + return usb_serial_generic_open(tty, port); +} + +#define OMNINET_HEADERLEN 4 +#define OMNINET_BULKOUTSIZE 64 +#define OMNINET_PAYLOADSIZE (OMNINET_BULKOUTSIZE - OMNINET_HEADERLEN) + +static void omninet_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + const struct omninet_header *hdr = urb->transfer_buffer; + const unsigned char *data; + size_t data_len; + + if (urb->actual_length <= OMNINET_HEADERLEN || !hdr->oh_len) + return; + + data = (char *)urb->transfer_buffer + OMNINET_HEADERLEN; + data_len = min_t(size_t, urb->actual_length - OMNINET_HEADERLEN, + hdr->oh_len); + tty_insert_flip_string(&port->port, data, data_len); + tty_flip_buffer_push(&port->port); +} + +static int omninet_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct usb_serial *serial = port->serial; + struct usb_serial_port *wport = serial->port[1]; + + struct omninet_data *od = usb_get_serial_port_data(port); + struct omninet_header *header = (struct omninet_header *) + wport->write_urb->transfer_buffer; + + int result; + + if (count == 0) { + dev_dbg(&port->dev, "%s - write request of 0 bytes\n", __func__); + return 0; + } + + if (!test_and_clear_bit(0, &port->write_urbs_free)) { + dev_dbg(&port->dev, "%s - already writing\n", __func__); + return 0; + } + + count = (count > OMNINET_PAYLOADSIZE) ? OMNINET_PAYLOADSIZE : count; + + memcpy(wport->write_urb->transfer_buffer + OMNINET_HEADERLEN, + buf, count); + + usb_serial_debug_data(&port->dev, __func__, count, + wport->write_urb->transfer_buffer); + + header->oh_seq = od->od_outseq++; + header->oh_len = count; + header->oh_xxx = 0x03; + header->oh_pad = 0x00; + + /* send the data out the bulk port, always 64 bytes */ + wport->write_urb->transfer_buffer_length = OMNINET_BULKOUTSIZE; + + result = usb_submit_urb(wport->write_urb, GFP_ATOMIC); + if (result) { + set_bit(0, &wport->write_urbs_free); + dev_err_console(port, + "%s - failed submitting write urb, error %d\n", + __func__, result); + } else + result = count; + + return result; +} + + +static int omninet_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + struct usb_serial_port *wport = serial->port[1]; + + int room = 0; /* Default: no room */ + + if (test_bit(0, &wport->write_urbs_free)) + room = wport->bulk_out_size - OMNINET_HEADERLEN; + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, room); + + return room; +} + +static void omninet_write_bulk_callback(struct urb *urb) +{ +/* struct omninet_header *header = (struct omninet_header *) + urb->transfer_buffer; */ + struct usb_serial_port *port = urb->context; + int status = urb->status; + + set_bit(0, &port->write_urbs_free); + if (status) { + dev_dbg(&port->dev, "%s - nonzero write bulk status received: %d\n", + __func__, status); + return; + } + + usb_serial_port_softint(port); +} + + +static void omninet_disconnect(struct usb_serial *serial) +{ + struct usb_serial_port *wport = serial->port[1]; + + usb_kill_urb(wport->write_urb); +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c new file mode 100644 index 00000000..b0eb1dfc --- /dev/null +++ b/drivers/usb/serial/opticon.c @@ -0,0 +1,444 @@ +/* + * Opticon USB barcode to serial driver + * + * Copyright (C) 2011 - 2012 Johan Hovold + * Copyright (C) 2011 Martin Jansen + * Copyright (C) 2008 - 2009 Greg Kroah-Hartman + * Copyright (C) 2008 - 2009 Novell Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONTROL_RTS 0x02 +#define RESEND_CTS_STATE 0x03 + +/* max number of write urbs in flight */ +#define URB_UPPER_LIMIT 8 + +/* This driver works for the Opticon 1D barcode reader + * an examples of 1D barcode types are EAN, UPC, Code39, IATA etc.. */ +#define DRIVER_DESC "Opticon USB barcode to serial driver (1D)" + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x065a, 0x0009) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* This structure holds all of the individual device information */ +struct opticon_private { + spinlock_t lock; /* protects the following flags */ + bool rts; + bool cts; + int outstanding_urbs; +}; + + +static void opticon_process_data_packet(struct usb_serial_port *port, + const unsigned char *buf, size_t len) +{ + tty_insert_flip_string(&port->port, buf, len); + tty_flip_buffer_push(&port->port); +} + +static void opticon_process_status_packet(struct usb_serial_port *port, + const unsigned char *buf, size_t len) +{ + struct opticon_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + if (buf[0] == 0x00) + priv->cts = false; + else + priv->cts = true; + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void opticon_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + const unsigned char *hdr = urb->transfer_buffer; + const unsigned char *data = hdr + 2; + size_t data_len = urb->actual_length - 2; + + if (urb->actual_length <= 2) { + dev_dbg(&port->dev, "malformed packet received: %d bytes\n", + urb->actual_length); + return; + } + /* + * Data from the device comes with a 2 byte header: + * + * <0x00><0x00>data... + * This is real data to be sent to the tty layer + * <0x00><0x01>level + * This is a CTS level change, the third byte is the CTS + * value (0 for low, 1 for high). + */ + if ((hdr[0] == 0x00) && (hdr[1] == 0x00)) { + opticon_process_data_packet(port, data, data_len); + } else if ((hdr[0] == 0x00) && (hdr[1] == 0x01)) { + opticon_process_status_packet(port, data, data_len); + } else { + dev_dbg(&port->dev, "unknown packet received: %02x %02x\n", + hdr[0], hdr[1]); + } +} + +static int send_control_msg(struct usb_serial_port *port, u8 requesttype, + u8 val) +{ + struct usb_serial *serial = port->serial; + int retval; + u8 *buffer; + + buffer = kzalloc(1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + buffer[0] = val; + /* Send the message to the vendor control endpoint + * of the connected device */ + retval = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + requesttype, + USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE, + 0, 0, buffer, 1, 0); + kfree(buffer); + + if (retval < 0) + return retval; + + return 0; +} + +static int opticon_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct opticon_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int res; + + spin_lock_irqsave(&priv->lock, flags); + priv->rts = false; + spin_unlock_irqrestore(&priv->lock, flags); + + /* Clear RTS line */ + send_control_msg(port, CONTROL_RTS, 0); + + /* clear the halt status of the enpoint */ + usb_clear_halt(port->serial->dev, port->read_urb->pipe); + + res = usb_serial_generic_open(tty, port); + if (!res) + return res; + + /* Request CTS line state, sometimes during opening the current + * CTS state can be missed. */ + send_control_msg(port, RESEND_CTS_STATE, 1); + + return res; +} + +static void opticon_write_control_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct opticon_private *priv = usb_get_serial_port_data(port); + int status = urb->status; + unsigned long flags; + + /* free up the transfer buffer, as usb_free_urb() does not do this */ + kfree(urb->transfer_buffer); + + /* setup packet may be set if we're using it for writing */ + kfree(urb->setup_packet); + + if (status) + dev_dbg(&port->dev, + "%s - non-zero urb status received: %d\n", + __func__, status); + + spin_lock_irqsave(&priv->lock, flags); + --priv->outstanding_urbs; + spin_unlock_irqrestore(&priv->lock, flags); + + usb_serial_port_softint(port); +} + +static int opticon_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct opticon_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + struct urb *urb; + unsigned char *buffer; + unsigned long flags; + int status; + struct usb_ctrlrequest *dr; + + spin_lock_irqsave(&priv->lock, flags); + if (priv->outstanding_urbs > URB_UPPER_LIMIT) { + spin_unlock_irqrestore(&priv->lock, flags); + dev_dbg(&port->dev, "%s - write limit hit\n", __func__); + return 0; + } + priv->outstanding_urbs++; + spin_unlock_irqrestore(&priv->lock, flags); + + buffer = kmalloc(count, GFP_ATOMIC); + if (!buffer) { + dev_err(&port->dev, "out of memory\n"); + count = -ENOMEM; + + goto error_no_buffer; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + dev_err(&port->dev, "no more free urbs\n"); + count = -ENOMEM; + goto error_no_urb; + } + + memcpy(buffer, buf, count); + + usb_serial_debug_data(&port->dev, __func__, count, buffer); + + /* The conncected devices do not have a bulk write endpoint, + * to transmit data to de barcode device the control endpoint is used */ + dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC); + if (!dr) { + dev_err(&port->dev, "out of memory\n"); + count = -ENOMEM; + goto error_no_dr; + } + + dr->bRequestType = USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT; + dr->bRequest = 0x01; + dr->wValue = 0; + dr->wIndex = 0; + dr->wLength = cpu_to_le16(count); + + usb_fill_control_urb(urb, serial->dev, + usb_sndctrlpipe(serial->dev, 0), + (unsigned char *)dr, buffer, count, + opticon_write_control_callback, port); + + /* send it down the pipe */ + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&port->dev, + "%s - usb_submit_urb(write endpoint) failed status = %d\n", + __func__, status); + count = status; + goto error; + } + + /* we are done with this urb, so let the host driver + * really free it when it is finished with it */ + usb_free_urb(urb); + + return count; +error: + kfree(dr); +error_no_dr: + usb_free_urb(urb); +error_no_urb: + kfree(buffer); +error_no_buffer: + spin_lock_irqsave(&priv->lock, flags); + --priv->outstanding_urbs; + spin_unlock_irqrestore(&priv->lock, flags); + return count; +} + +static int opticon_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct opticon_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + /* + * We really can take almost anything the user throws at us + * but let's pick a nice big number to tell the tty + * layer that we have lots of free space, unless we don't. + */ + spin_lock_irqsave(&priv->lock, flags); + if (priv->outstanding_urbs > URB_UPPER_LIMIT * 2 / 3) { + spin_unlock_irqrestore(&priv->lock, flags); + dev_dbg(&port->dev, "%s - write limit hit\n", __func__); + return 0; + } + spin_unlock_irqrestore(&priv->lock, flags); + + return 2048; +} + +static int opticon_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct opticon_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int result = 0; + + spin_lock_irqsave(&priv->lock, flags); + if (priv->rts) + result |= TIOCM_RTS; + if (priv->cts) + result |= TIOCM_CTS; + spin_unlock_irqrestore(&priv->lock, flags); + + dev_dbg(&port->dev, "%s - %x\n", __func__, result); + return result; +} + +static int opticon_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct opticon_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + bool rts; + bool changed = false; + int ret; + + /* We only support RTS so we only handle that */ + spin_lock_irqsave(&priv->lock, flags); + + rts = priv->rts; + if (set & TIOCM_RTS) + priv->rts = true; + if (clear & TIOCM_RTS) + priv->rts = false; + changed = rts ^ priv->rts; + spin_unlock_irqrestore(&priv->lock, flags); + + if (!changed) + return 0; + + ret = send_control_msg(port, CONTROL_RTS, !rts); + if (ret) + return usb_translate_errors(ret); + + return 0; +} + +static int get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *serial) +{ + struct serial_struct tmp; + + if (!serial) + return -EFAULT; + + memset(&tmp, 0x00, sizeof(tmp)); + + /* fake emulate a 16550 uart to make userspace code happy */ + tmp.type = PORT_16550A; + tmp.line = port->serial->minor; + tmp.port = 0; + tmp.irq = 0; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = 1024; + tmp.baud_base = 9600; + tmp.close_delay = 5*HZ; + tmp.closing_wait = 30*HZ; + + if (copy_to_user(serial, &tmp, sizeof(*serial))) + return -EFAULT; + return 0; +} + +static int opticon_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s - port %d, cmd = 0x%x\n", __func__, port->number, cmd); + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(port, + (struct serial_struct __user *)arg); + } + + return -ENOIOCTLCMD; +} + +static int opticon_startup(struct usb_serial *serial) +{ + if (!serial->num_bulk_in) { + dev_err(&serial->dev->dev, "no bulk in endpoint\n"); + return -ENODEV; + } + + return 0; +} + +static int opticon_port_probe(struct usb_serial_port *port) +{ + struct opticon_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int opticon_port_remove(struct usb_serial_port *port) +{ + struct opticon_private *priv = usb_get_serial_port_data(port); + + kfree(priv); + + return 0; +} + +static struct usb_serial_driver opticon_device = { + .driver = { + .owner = THIS_MODULE, + .name = "opticon", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_in_size = 256, + .attach = opticon_startup, + .port_probe = opticon_port_probe, + .port_remove = opticon_port_remove, + .open = opticon_open, + .write = opticon_write, + .write_room = opticon_write_room, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .ioctl = opticon_ioctl, + .tiocmget = opticon_tiocmget, + .tiocmset = opticon_tiocmset, + .process_read_urb = opticon_process_read_urb, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &opticon_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c new file mode 100644 index 00000000..8b348413 --- /dev/null +++ b/drivers/usb/serial/option.c @@ -0,0 +1,2003 @@ +/* + USB Driver for GSM modems + + Copyright (C) 2005 Matthias Urlichs + + This driver is free software; you can redistribute it and/or modify + it under the terms of Version 2 of the GNU General Public License as + published by the Free Software Foundation. + + Portions copied from the Keyspan driver by Hugh Blemings + + History: see the git log. + + Work sponsored by: Sigos GmbH, Germany + + This driver exists because the "normal" serial driver doesn't work too well + with GSM modems. Issues: + - data loss -- one single Receive URB is not nearly enough + - nonstandard flow (Option devices) control + - controlling the baud rate doesn't make sense + + This driver is named "option" because the most common device it's + used for is a PC-Card (with an internal OHCI-USB interface, behind + which the GSM interface sits), made by Option Inc. + + Some of the "one port" devices actually exhibit multiple USB instances + on the USB bus. This is not a bug, these ports are used for different + device features. +*/ + +#define DRIVER_AUTHOR "Matthias Urlichs " +#define DRIVER_DESC "USB Driver for GSM modems" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb-wwan.h" + +/* Function prototypes */ +static int option_probe(struct usb_serial *serial, + const struct usb_device_id *id); +static int option_attach(struct usb_serial *serial); +static void option_release(struct usb_serial *serial); +static int option_send_setup(struct usb_serial_port *port); +static void option_instat_callback(struct urb *urb); + +/* Vendor and product IDs */ +#define OPTION_VENDOR_ID 0x0AF0 +#define OPTION_PRODUCT_COLT 0x5000 +#define OPTION_PRODUCT_RICOLA 0x6000 +#define OPTION_PRODUCT_RICOLA_LIGHT 0x6100 +#define OPTION_PRODUCT_RICOLA_QUAD 0x6200 +#define OPTION_PRODUCT_RICOLA_QUAD_LIGHT 0x6300 +#define OPTION_PRODUCT_RICOLA_NDIS 0x6050 +#define OPTION_PRODUCT_RICOLA_NDIS_LIGHT 0x6150 +#define OPTION_PRODUCT_RICOLA_NDIS_QUAD 0x6250 +#define OPTION_PRODUCT_RICOLA_NDIS_QUAD_LIGHT 0x6350 +#define OPTION_PRODUCT_COBRA 0x6500 +#define OPTION_PRODUCT_COBRA_BUS 0x6501 +#define OPTION_PRODUCT_VIPER 0x6600 +#define OPTION_PRODUCT_VIPER_BUS 0x6601 +#define OPTION_PRODUCT_GT_MAX_READY 0x6701 +#define OPTION_PRODUCT_FUJI_MODEM_LIGHT 0x6721 +#define OPTION_PRODUCT_FUJI_MODEM_GT 0x6741 +#define OPTION_PRODUCT_FUJI_MODEM_EX 0x6761 +#define OPTION_PRODUCT_KOI_MODEM 0x6800 +#define OPTION_PRODUCT_SCORPION_MODEM 0x6901 +#define OPTION_PRODUCT_ETNA_MODEM 0x7001 +#define OPTION_PRODUCT_ETNA_MODEM_LITE 0x7021 +#define OPTION_PRODUCT_ETNA_MODEM_GT 0x7041 +#define OPTION_PRODUCT_ETNA_MODEM_EX 0x7061 +#define OPTION_PRODUCT_ETNA_KOI_MODEM 0x7100 +#define OPTION_PRODUCT_GTM380_MODEM 0x7201 + +#define HUAWEI_VENDOR_ID 0x12D1 +#define HUAWEI_PRODUCT_E173 0x140C +#define HUAWEI_PRODUCT_E1750 0x1406 +#define HUAWEI_PRODUCT_K4505 0x1464 +#define HUAWEI_PRODUCT_K3765 0x1465 +#define HUAWEI_PRODUCT_K4605 0x14C6 +#define HUAWEI_PRODUCT_E173S6 0x1C07 + +#define QUANTA_VENDOR_ID 0x0408 +#define QUANTA_PRODUCT_Q101 0xEA02 +#define QUANTA_PRODUCT_Q111 0xEA03 +#define QUANTA_PRODUCT_GLX 0xEA04 +#define QUANTA_PRODUCT_GKE 0xEA05 +#define QUANTA_PRODUCT_GLE 0xEA06 + +#define NOVATELWIRELESS_VENDOR_ID 0x1410 + +/* YISO PRODUCTS */ + +#define YISO_VENDOR_ID 0x0EAB +#define YISO_PRODUCT_U893 0xC893 + +/* + * NOVATEL WIRELESS PRODUCTS + * + * Note from Novatel Wireless: + * If your Novatel modem does not work on linux, don't + * change the option module, but check our website. If + * that does not help, contact ddeschepper@nvtl.com +*/ +/* MERLIN EVDO PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_V640 0x1100 +#define NOVATELWIRELESS_PRODUCT_V620 0x1110 +#define NOVATELWIRELESS_PRODUCT_V740 0x1120 +#define NOVATELWIRELESS_PRODUCT_V720 0x1130 + +/* MERLIN HSDPA/HSPA PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_U730 0x1400 +#define NOVATELWIRELESS_PRODUCT_U740 0x1410 +#define NOVATELWIRELESS_PRODUCT_U870 0x1420 +#define NOVATELWIRELESS_PRODUCT_XU870 0x1430 +#define NOVATELWIRELESS_PRODUCT_X950D 0x1450 + +/* EXPEDITE PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_EV620 0x2100 +#define NOVATELWIRELESS_PRODUCT_ES720 0x2110 +#define NOVATELWIRELESS_PRODUCT_E725 0x2120 +#define NOVATELWIRELESS_PRODUCT_ES620 0x2130 +#define NOVATELWIRELESS_PRODUCT_EU730 0x2400 +#define NOVATELWIRELESS_PRODUCT_EU740 0x2410 +#define NOVATELWIRELESS_PRODUCT_EU870D 0x2420 +/* OVATION PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_MC727 0x4100 +#define NOVATELWIRELESS_PRODUCT_MC950D 0x4400 +/* + * Note from Novatel Wireless: + * All PID in the 5xxx range are currently reserved for + * auto-install CDROMs, and should not be added to this + * module. + * + * #define NOVATELWIRELESS_PRODUCT_U727 0x5010 + * #define NOVATELWIRELESS_PRODUCT_MC727_NEW 0x5100 +*/ +#define NOVATELWIRELESS_PRODUCT_OVMC760 0x6002 +#define NOVATELWIRELESS_PRODUCT_MC780 0x6010 +#define NOVATELWIRELESS_PRODUCT_EVDO_FULLSPEED 0x6000 +#define NOVATELWIRELESS_PRODUCT_EVDO_HIGHSPEED 0x6001 +#define NOVATELWIRELESS_PRODUCT_HSPA_FULLSPEED 0x7000 +#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED 0x7001 +#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED3 0x7003 +#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED4 0x7004 +#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED5 0x7005 +#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED6 0x7006 +#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED7 0x7007 +#define NOVATELWIRELESS_PRODUCT_MC996D 0x7030 +#define NOVATELWIRELESS_PRODUCT_MF3470 0x7041 +#define NOVATELWIRELESS_PRODUCT_MC547 0x7042 +#define NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_FULLSPEED 0x8000 +#define NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_HIGHSPEED 0x8001 +#define NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_FULLSPEED 0x9000 +#define NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_HIGHSPEED 0x9001 +#define NOVATELWIRELESS_PRODUCT_E362 0x9010 +#define NOVATELWIRELESS_PRODUCT_E371 0x9011 +#define NOVATELWIRELESS_PRODUCT_G2 0xA010 +#define NOVATELWIRELESS_PRODUCT_MC551 0xB001 + +/* AMOI PRODUCTS */ +#define AMOI_VENDOR_ID 0x1614 +#define AMOI_PRODUCT_H01 0x0800 +#define AMOI_PRODUCT_H01A 0x7002 +#define AMOI_PRODUCT_H02 0x0802 +#define AMOI_PRODUCT_SKYPEPHONE_S2 0x0407 + +#define DELL_VENDOR_ID 0x413C + +/* Dell modems */ +#define DELL_PRODUCT_5700_MINICARD 0x8114 +#define DELL_PRODUCT_5500_MINICARD 0x8115 +#define DELL_PRODUCT_5505_MINICARD 0x8116 +#define DELL_PRODUCT_5700_EXPRESSCARD 0x8117 +#define DELL_PRODUCT_5510_EXPRESSCARD 0x8118 + +#define DELL_PRODUCT_5700_MINICARD_SPRINT 0x8128 +#define DELL_PRODUCT_5700_MINICARD_TELUS 0x8129 + +#define DELL_PRODUCT_5720_MINICARD_VZW 0x8133 +#define DELL_PRODUCT_5720_MINICARD_SPRINT 0x8134 +#define DELL_PRODUCT_5720_MINICARD_TELUS 0x8135 +#define DELL_PRODUCT_5520_MINICARD_CINGULAR 0x8136 +#define DELL_PRODUCT_5520_MINICARD_GENERIC_L 0x8137 +#define DELL_PRODUCT_5520_MINICARD_GENERIC_I 0x8138 + +#define DELL_PRODUCT_5730_MINICARD_SPRINT 0x8180 +#define DELL_PRODUCT_5730_MINICARD_TELUS 0x8181 +#define DELL_PRODUCT_5730_MINICARD_VZW 0x8182 + +#define DELL_PRODUCT_5800_MINICARD_VZW 0x8195 /* Novatel E362 */ +#define DELL_PRODUCT_5800_V2_MINICARD_VZW 0x8196 /* Novatel E362 */ +#define DELL_PRODUCT_5804_MINICARD_ATT 0x819b /* Novatel E371 */ + +#define KYOCERA_VENDOR_ID 0x0c88 +#define KYOCERA_PRODUCT_KPC650 0x17da +#define KYOCERA_PRODUCT_KPC680 0x180a + +#define ANYDATA_VENDOR_ID 0x16d5 +#define ANYDATA_PRODUCT_ADU_620UW 0x6202 +#define ANYDATA_PRODUCT_ADU_E100A 0x6501 +#define ANYDATA_PRODUCT_ADU_500A 0x6502 + +#define AXESSTEL_VENDOR_ID 0x1726 +#define AXESSTEL_PRODUCT_MV110H 0x1000 + +#define BANDRICH_VENDOR_ID 0x1A8D +#define BANDRICH_PRODUCT_C100_1 0x1002 +#define BANDRICH_PRODUCT_C100_2 0x1003 +#define BANDRICH_PRODUCT_1004 0x1004 +#define BANDRICH_PRODUCT_1005 0x1005 +#define BANDRICH_PRODUCT_1006 0x1006 +#define BANDRICH_PRODUCT_1007 0x1007 +#define BANDRICH_PRODUCT_1008 0x1008 +#define BANDRICH_PRODUCT_1009 0x1009 +#define BANDRICH_PRODUCT_100A 0x100a + +#define BANDRICH_PRODUCT_100B 0x100b +#define BANDRICH_PRODUCT_100C 0x100c +#define BANDRICH_PRODUCT_100D 0x100d +#define BANDRICH_PRODUCT_100E 0x100e + +#define BANDRICH_PRODUCT_100F 0x100f +#define BANDRICH_PRODUCT_1010 0x1010 +#define BANDRICH_PRODUCT_1011 0x1011 +#define BANDRICH_PRODUCT_1012 0x1012 + +#define QUALCOMM_VENDOR_ID 0x05C6 + +#define CMOTECH_VENDOR_ID 0x16d8 +#define CMOTECH_PRODUCT_6001 0x6001 +#define CMOTECH_PRODUCT_CMU_300 0x6002 +#define CMOTECH_PRODUCT_6003 0x6003 +#define CMOTECH_PRODUCT_6004 0x6004 +#define CMOTECH_PRODUCT_6005 0x6005 +#define CMOTECH_PRODUCT_CGU_628A 0x6006 +#define CMOTECH_PRODUCT_CHE_628S 0x6007 +#define CMOTECH_PRODUCT_CMU_301 0x6008 +#define CMOTECH_PRODUCT_CHU_628 0x6280 +#define CMOTECH_PRODUCT_CHU_628S 0x6281 +#define CMOTECH_PRODUCT_CDU_680 0x6803 +#define CMOTECH_PRODUCT_CDU_685A 0x6804 +#define CMOTECH_PRODUCT_CHU_720S 0x7001 +#define CMOTECH_PRODUCT_7002 0x7002 +#define CMOTECH_PRODUCT_CHU_629K 0x7003 +#define CMOTECH_PRODUCT_7004 0x7004 +#define CMOTECH_PRODUCT_7005 0x7005 +#define CMOTECH_PRODUCT_CGU_629 0x7006 +#define CMOTECH_PRODUCT_CHU_629S 0x700a +#define CMOTECH_PRODUCT_CHU_720I 0x7211 +#define CMOTECH_PRODUCT_7212 0x7212 +#define CMOTECH_PRODUCT_7213 0x7213 +#define CMOTECH_PRODUCT_7251 0x7251 +#define CMOTECH_PRODUCT_7252 0x7252 +#define CMOTECH_PRODUCT_7253 0x7253 + +#define TELIT_VENDOR_ID 0x1bc7 +#define TELIT_PRODUCT_UC864E 0x1003 +#define TELIT_PRODUCT_UC864G 0x1004 +#define TELIT_PRODUCT_CC864_DUAL 0x1005 +#define TELIT_PRODUCT_CC864_SINGLE 0x1006 +#define TELIT_PRODUCT_DE910_DUAL 0x1010 +#define TELIT_PRODUCT_UE910_V2 0x1012 +#define TELIT_PRODUCT_LE920 0x1200 +#define TELIT_PRODUCT_LE910 0x1201 + +/* ZTE PRODUCTS */ +#define ZTE_VENDOR_ID 0x19d2 +#define ZTE_PRODUCT_MF622 0x0001 +#define ZTE_PRODUCT_MF628 0x0015 +#define ZTE_PRODUCT_MF626 0x0031 +#define ZTE_PRODUCT_AC2726 0xfff1 +#define ZTE_PRODUCT_CDMA_TECH 0xfffe +#define ZTE_PRODUCT_AC8710T 0xffff +#define ZTE_PRODUCT_MC2718 0xffe8 +#define ZTE_PRODUCT_AD3812 0xffeb +#define ZTE_PRODUCT_MC2716 0xffed + +#define BENQ_VENDOR_ID 0x04a5 +#define BENQ_PRODUCT_H10 0x4068 + +#define DLINK_VENDOR_ID 0x1186 +#define DLINK_PRODUCT_DWM_652 0x3e04 +#define DLINK_PRODUCT_DWM_652_U5 0xce16 +#define DLINK_PRODUCT_DWM_652_U5A 0xce1e + +#define QISDA_VENDOR_ID 0x1da5 +#define QISDA_PRODUCT_H21_4512 0x4512 +#define QISDA_PRODUCT_H21_4523 0x4523 +#define QISDA_PRODUCT_H20_4515 0x4515 +#define QISDA_PRODUCT_H20_4518 0x4518 +#define QISDA_PRODUCT_H20_4519 0x4519 + +/* TLAYTECH PRODUCTS */ +#define TLAYTECH_VENDOR_ID 0x20B9 +#define TLAYTECH_PRODUCT_TEU800 0x1682 + +/* TOSHIBA PRODUCTS */ +#define TOSHIBA_VENDOR_ID 0x0930 +#define TOSHIBA_PRODUCT_HSDPA_MINICARD 0x1302 +#define TOSHIBA_PRODUCT_G450 0x0d45 + +#define ALINK_VENDOR_ID 0x1e0e +#define ALINK_PRODUCT_PH300 0x9100 +#define ALINK_PRODUCT_3GU 0x9200 + +/* ALCATEL PRODUCTS */ +#define ALCATEL_VENDOR_ID 0x1bbb +#define ALCATEL_PRODUCT_X060S_X200 0x0000 +#define ALCATEL_PRODUCT_X220_X500D 0x0017 +#define ALCATEL_PRODUCT_L100V 0x011e +#define ALCATEL_PRODUCT_L800MA 0x0203 + +#define PIRELLI_VENDOR_ID 0x1266 +#define PIRELLI_PRODUCT_C100_1 0x1002 +#define PIRELLI_PRODUCT_C100_2 0x1003 +#define PIRELLI_PRODUCT_1004 0x1004 +#define PIRELLI_PRODUCT_1005 0x1005 +#define PIRELLI_PRODUCT_1006 0x1006 +#define PIRELLI_PRODUCT_1007 0x1007 +#define PIRELLI_PRODUCT_1008 0x1008 +#define PIRELLI_PRODUCT_1009 0x1009 +#define PIRELLI_PRODUCT_100A 0x100a +#define PIRELLI_PRODUCT_100B 0x100b +#define PIRELLI_PRODUCT_100C 0x100c +#define PIRELLI_PRODUCT_100D 0x100d +#define PIRELLI_PRODUCT_100E 0x100e +#define PIRELLI_PRODUCT_100F 0x100f +#define PIRELLI_PRODUCT_1011 0x1011 +#define PIRELLI_PRODUCT_1012 0x1012 + +/* Airplus products */ +#define AIRPLUS_VENDOR_ID 0x1011 +#define AIRPLUS_PRODUCT_MCD650 0x3198 + +/* Longcheer/Longsung vendor ID; makes whitelabel devices that + * many other vendors like 4G Systems, Alcatel, ChinaBird, + * Mobidata, etc sell under their own brand names. + */ +#define LONGCHEER_VENDOR_ID 0x1c9e + +/* 4G Systems products */ +/* This is the 4G XS Stick W14 a.k.a. Mobilcom Debitel Surf-Stick * + * It seems to contain a Qualcomm QSC6240/6290 chipset */ +#define FOUR_G_SYSTEMS_PRODUCT_W14 0x9603 + +/* iBall 3.5G connect wireless modem */ +#define IBALL_3_5G_CONNECT 0x9605 + +/* Zoom */ +#define ZOOM_PRODUCT_4597 0x9607 + +/* SpeedUp SU9800 usb 3g modem */ +#define SPEEDUP_PRODUCT_SU9800 0x9800 + +/* Haier products */ +#define HAIER_VENDOR_ID 0x201e +#define HAIER_PRODUCT_CE81B 0x10f8 +#define HAIER_PRODUCT_CE100 0x2009 + +/* Cinterion (formerly Siemens) products */ +#define SIEMENS_VENDOR_ID 0x0681 +#define CINTERION_VENDOR_ID 0x1e2d +#define CINTERION_PRODUCT_HC25_MDM 0x0047 +#define CINTERION_PRODUCT_HC25_MDMNET 0x0040 +#define CINTERION_PRODUCT_HC28_MDM 0x004C +#define CINTERION_PRODUCT_HC28_MDMNET 0x004A /* same for HC28J */ +#define CINTERION_PRODUCT_EU3_E 0x0051 +#define CINTERION_PRODUCT_EU3_P 0x0052 +#define CINTERION_PRODUCT_PH8 0x0053 +#define CINTERION_PRODUCT_AHXX 0x0055 +#define CINTERION_PRODUCT_PLXX 0x0060 + +/* Olivetti products */ +#define OLIVETTI_VENDOR_ID 0x0b3c +#define OLIVETTI_PRODUCT_OLICARD100 0xc000 +#define OLIVETTI_PRODUCT_OLICARD120 0xc001 +#define OLIVETTI_PRODUCT_OLICARD140 0xc002 +#define OLIVETTI_PRODUCT_OLICARD145 0xc003 +#define OLIVETTI_PRODUCT_OLICARD155 0xc004 +#define OLIVETTI_PRODUCT_OLICARD200 0xc005 +#define OLIVETTI_PRODUCT_OLICARD160 0xc00a +#define OLIVETTI_PRODUCT_OLICARD500 0xc00b + +/* Celot products */ +#define CELOT_VENDOR_ID 0x211f +#define CELOT_PRODUCT_CT680M 0x6801 + +/* Samsung products */ +#define SAMSUNG_VENDOR_ID 0x04e8 +#define SAMSUNG_PRODUCT_GT_B3730 0x6889 + +/* YUGA products www.yuga-info.com gavin.kx@qq.com */ +#define YUGA_VENDOR_ID 0x257A +#define YUGA_PRODUCT_CEM600 0x1601 +#define YUGA_PRODUCT_CEM610 0x1602 +#define YUGA_PRODUCT_CEM500 0x1603 +#define YUGA_PRODUCT_CEM510 0x1604 +#define YUGA_PRODUCT_CEM800 0x1605 +#define YUGA_PRODUCT_CEM900 0x1606 + +#define YUGA_PRODUCT_CEU818 0x1607 +#define YUGA_PRODUCT_CEU816 0x1608 +#define YUGA_PRODUCT_CEU828 0x1609 +#define YUGA_PRODUCT_CEU826 0x160A +#define YUGA_PRODUCT_CEU518 0x160B +#define YUGA_PRODUCT_CEU516 0x160C +#define YUGA_PRODUCT_CEU528 0x160D +#define YUGA_PRODUCT_CEU526 0x160F +#define YUGA_PRODUCT_CEU881 0x161F +#define YUGA_PRODUCT_CEU882 0x162F + +#define YUGA_PRODUCT_CWM600 0x2601 +#define YUGA_PRODUCT_CWM610 0x2602 +#define YUGA_PRODUCT_CWM500 0x2603 +#define YUGA_PRODUCT_CWM510 0x2604 +#define YUGA_PRODUCT_CWM800 0x2605 +#define YUGA_PRODUCT_CWM900 0x2606 + +#define YUGA_PRODUCT_CWU718 0x2607 +#define YUGA_PRODUCT_CWU716 0x2608 +#define YUGA_PRODUCT_CWU728 0x2609 +#define YUGA_PRODUCT_CWU726 0x260A +#define YUGA_PRODUCT_CWU518 0x260B +#define YUGA_PRODUCT_CWU516 0x260C +#define YUGA_PRODUCT_CWU528 0x260D +#define YUGA_PRODUCT_CWU581 0x260E +#define YUGA_PRODUCT_CWU526 0x260F +#define YUGA_PRODUCT_CWU582 0x261F +#define YUGA_PRODUCT_CWU583 0x262F + +#define YUGA_PRODUCT_CLM600 0x3601 +#define YUGA_PRODUCT_CLM610 0x3602 +#define YUGA_PRODUCT_CLM500 0x3603 +#define YUGA_PRODUCT_CLM510 0x3604 +#define YUGA_PRODUCT_CLM800 0x3605 +#define YUGA_PRODUCT_CLM900 0x3606 + +#define YUGA_PRODUCT_CLU718 0x3607 +#define YUGA_PRODUCT_CLU716 0x3608 +#define YUGA_PRODUCT_CLU728 0x3609 +#define YUGA_PRODUCT_CLU726 0x360A +#define YUGA_PRODUCT_CLU518 0x360B +#define YUGA_PRODUCT_CLU516 0x360C +#define YUGA_PRODUCT_CLU528 0x360D +#define YUGA_PRODUCT_CLU526 0x360F + +/* Viettel products */ +#define VIETTEL_VENDOR_ID 0x2262 +#define VIETTEL_PRODUCT_VT1000 0x0002 + +/* ZD Incorporated */ +#define ZD_VENDOR_ID 0x0685 +#define ZD_PRODUCT_7000 0x7000 + +/* LG products */ +#define LG_VENDOR_ID 0x1004 +#define LG_PRODUCT_L02C 0x618f + +/* MediaTek products */ +#define MEDIATEK_VENDOR_ID 0x0e8d +#define MEDIATEK_PRODUCT_DC_1COM 0x00a0 +#define MEDIATEK_PRODUCT_DC_4COM 0x00a5 +#define MEDIATEK_PRODUCT_DC_4COM2 0x00a7 +#define MEDIATEK_PRODUCT_DC_5COM 0x00a4 +#define MEDIATEK_PRODUCT_7208_1COM 0x7101 +#define MEDIATEK_PRODUCT_7208_2COM 0x7102 +#define MEDIATEK_PRODUCT_7103_2COM 0x7103 +#define MEDIATEK_PRODUCT_7106_2COM 0x7106 +#define MEDIATEK_PRODUCT_FP_1COM 0x0003 +#define MEDIATEK_PRODUCT_FP_2COM 0x0023 +#define MEDIATEK_PRODUCT_FPDC_1COM 0x0043 +#define MEDIATEK_PRODUCT_FPDC_2COM 0x0033 + +/* Cellient products */ +#define CELLIENT_VENDOR_ID 0x2692 +#define CELLIENT_PRODUCT_MEN200 0x9005 + +/* Hyundai Petatel Inc. products */ +#define PETATEL_VENDOR_ID 0x1ff4 +#define PETATEL_PRODUCT_NP10T_600A 0x600a +#define PETATEL_PRODUCT_NP10T_600E 0x600e + +/* TP-LINK Incorporated products */ +#define TPLINK_VENDOR_ID 0x2357 +#define TPLINK_PRODUCT_MA180 0x0201 + +/* Changhong products */ +#define CHANGHONG_VENDOR_ID 0x2077 +#define CHANGHONG_PRODUCT_CH690 0x7001 + +/* Inovia */ +#define INOVIA_VENDOR_ID 0x20a6 +#define INOVIA_SEW858 0x1105 + +/* VIA Telecom */ +#define VIATELECOM_VENDOR_ID 0x15eb +#define VIATELECOM_PRODUCT_CDS7 0x0001 + +/* some devices interfaces need special handling due to a number of reasons */ +enum option_blacklist_reason { + OPTION_BLACKLIST_NONE = 0, + OPTION_BLACKLIST_SENDSETUP = 1, + OPTION_BLACKLIST_RESERVED_IF = 2 +}; + +#define MAX_BL_NUM 8 +struct option_blacklist_info { + /* bitfield of interface numbers for OPTION_BLACKLIST_SENDSETUP */ + const unsigned long sendsetup; + /* bitfield of interface numbers for OPTION_BLACKLIST_RESERVED_IF */ + const unsigned long reserved; +}; + +static const struct option_blacklist_info four_g_w14_blacklist = { + .sendsetup = BIT(0) | BIT(1), +}; + +static const struct option_blacklist_info alcatel_x200_blacklist = { + .sendsetup = BIT(0) | BIT(1), + .reserved = BIT(4), +}; + +static const struct option_blacklist_info zte_0037_blacklist = { + .sendsetup = BIT(0) | BIT(1), +}; + +static const struct option_blacklist_info zte_k3765_z_blacklist = { + .sendsetup = BIT(0) | BIT(1) | BIT(2), + .reserved = BIT(4), +}; + +static const struct option_blacklist_info zte_ad3812_z_blacklist = { + .sendsetup = BIT(0) | BIT(1) | BIT(2), +}; + +static const struct option_blacklist_info zte_mc2718_z_blacklist = { + .sendsetup = BIT(1) | BIT(2) | BIT(3) | BIT(4), +}; + +static const struct option_blacklist_info zte_mc2716_z_blacklist = { + .sendsetup = BIT(1) | BIT(2) | BIT(3), +}; + +static const struct option_blacklist_info huawei_cdc12_blacklist = { + .reserved = BIT(1) | BIT(2), +}; + +static const struct option_blacklist_info net_intf0_blacklist = { + .reserved = BIT(0), +}; + +static const struct option_blacklist_info net_intf1_blacklist = { + .reserved = BIT(1), +}; + +static const struct option_blacklist_info net_intf2_blacklist = { + .reserved = BIT(2), +}; + +static const struct option_blacklist_info net_intf3_blacklist = { + .reserved = BIT(3), +}; + +static const struct option_blacklist_info net_intf4_blacklist = { + .reserved = BIT(4), +}; + +static const struct option_blacklist_info net_intf5_blacklist = { + .reserved = BIT(5), +}; + +static const struct option_blacklist_info net_intf6_blacklist = { + .reserved = BIT(6), +}; + +static const struct option_blacklist_info zte_mf626_blacklist = { + .sendsetup = BIT(0) | BIT(1), + .reserved = BIT(4), +}; + +static const struct option_blacklist_info zte_1255_blacklist = { + .reserved = BIT(3) | BIT(4), +}; + +static const struct option_blacklist_info telit_le910_blacklist = { + .sendsetup = BIT(0), + .reserved = BIT(1) | BIT(2), +}; + +static const struct option_blacklist_info telit_le920_blacklist = { + .sendsetup = BIT(0), + .reserved = BIT(1) | BIT(5), +}; + +static const struct usb_device_id option_ids[] = { + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_LIGHT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_QUAD) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_QUAD_LIGHT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_LIGHT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_QUAD) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_QUAD_LIGHT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA_BUS) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_VIPER) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_VIPER_BUS) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_GT_MAX_READY) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_LIGHT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_GT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_EX) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_KOI_MODEM) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_SCORPION_MODEM) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_LITE) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_GT) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_EX) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_KOI_MODEM) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_GTM380_MODEM) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_Q101) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_Q111) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLX) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GKE) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLE) }, + { USB_DEVICE(QUANTA_VENDOR_ID, 0xea42), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c05, USB_CLASS_COMM, 0x02, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c1f, USB_CLASS_COMM, 0x02, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c23, USB_CLASS_COMM, 0x02, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E173, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t) &net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E173S6, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t) &net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1750, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t) &net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1441, USB_CLASS_COMM, 0x02, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1442, USB_CLASS_COMM, 0x02, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4505, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3765, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x14ac, 0xff, 0xff, 0xff), /* Huawei E1820 */ + .driver_info = (kernel_ulong_t) &net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4605, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0xff, 0xff) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x02) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x03) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x04) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x06) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x10) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x12) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x13) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x14) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x15) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x17) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x18) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x19) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x31) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x32) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x33) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x34) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x35) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x36) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x48) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x49) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x61) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x62) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x63) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x64) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x65) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x66) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x72) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x73) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x74) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x75) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x78) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x79) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x02) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x03) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x04) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x06) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x10) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x12) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x13) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x14) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x15) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x17) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x18) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x19) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x31) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x32) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x33) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x34) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x35) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x36) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x48) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x49) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x61) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x62) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x63) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x64) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x65) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x66) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x72) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x73) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x74) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x75) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x78) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x79) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x02) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x03) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x04) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x06) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x0F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x10) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x12) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x13) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x14) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x15) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x17) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x18) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x19) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x1A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x1B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x1C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x31) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x32) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x33) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x34) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x35) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x36) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x3F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x48) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x49) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x4A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x4B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x4C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x61) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x62) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x63) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x64) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x65) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x66) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x6F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x72) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x73) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x74) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x75) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x78) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x79) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x7A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x7B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x03, 0x7C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x02) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x03) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x04) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x06) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x0F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x10) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x12) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x13) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x14) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x15) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x17) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x18) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x19) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x1A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x1B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x1C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x31) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x32) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x33) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x34) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x35) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x36) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x3F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x48) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x49) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x4A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x4B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x4C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x61) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x62) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x63) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x64) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x65) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x66) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x6F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x72) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x73) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x74) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x75) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x78) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x79) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x7A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x7B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x04, 0x7C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x02) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x03) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x04) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x06) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x0F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x10) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x12) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x13) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x14) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x15) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x17) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x18) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x19) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x1A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x1B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x1C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x31) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x32) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x33) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x34) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x35) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x36) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x3F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x48) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x49) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x4A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x4B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x4C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x61) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x62) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x63) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x64) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x65) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x66) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x6F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x72) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x73) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x74) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x75) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x78) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x79) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x7A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x7B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x05, 0x7C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x02) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x03) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x04) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x06) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x0F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x10) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x12) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x13) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x14) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x15) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x17) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x18) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x19) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x1A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x1B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x1C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x31) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x32) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x33) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x34) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x35) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x36) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x3F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x48) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x49) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x4A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x4B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x4C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x61) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x62) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x63) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x64) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x65) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x66) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x6F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x72) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x73) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x74) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x75) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x78) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x79) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x7A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x7B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x06, 0x7C) }, + + + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V640) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V620) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V740) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V720) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U730) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U740) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U870) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_XU870) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_X950D) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EV620) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_ES720) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_E725) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_ES620) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU730) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU740) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU870D) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC950D) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC727) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_OVMC760) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC780) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_FULLSPEED) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_FULLSPEED) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_FULLSPEED) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_FULLSPEED) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_HIGHSPEED) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED3) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED4) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED5) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED6) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED7) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC996D) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MF3470) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC547) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_HIGHSPEED) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_HIGHSPEED) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_G2) }, + /* Novatel Ovation MC551 a.k.a. Verizon USB551L */ + { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC551, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_E362, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_E371, 0xff, 0xff, 0xff) }, + + { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01) }, + { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01A) }, + { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H02) }, + { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_SKYPEPHONE_S2) }, + + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite EV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5500_MINICARD) }, /* Dell Wireless 5500 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5505_MINICARD) }, /* Dell Wireless 5505 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_EXPRESSCARD) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO ExpressCard == Novatel Merlin XV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5510_EXPRESSCARD) }, /* Dell Wireless 5510 Mobile Broadband HSDPA ExpressCard == Novatel Merlin XU870 HSDPA/3G */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD_SPRINT) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite E720 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD_TELUS) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite ET620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_VZW) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_SPRINT) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_TELUS) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_CINGULAR) }, /* Dell Wireless HSDPA 5520 == Novatel Expedite EU860D */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_GENERIC_L) }, /* Dell Wireless HSDPA 5520 */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_GENERIC_I) }, /* Dell Wireless 5520 Voda I Mobile Broadband (3G HSDPA) Minicard */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_SPRINT) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_TELUS) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_VZW) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ + { USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, DELL_PRODUCT_5800_MINICARD_VZW, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, DELL_PRODUCT_5800_V2_MINICARD_VZW, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, DELL_PRODUCT_5804_MINICARD_ATT, 0xff, 0xff, 0xff) }, + { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_E100A) }, /* ADU-E100, ADU-310 */ + { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_500A) }, + { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_620UW) }, + { USB_DEVICE(AXESSTEL_VENDOR_ID, AXESSTEL_PRODUCT_MV110H) }, + { USB_DEVICE(YISO_VENDOR_ID, YISO_PRODUCT_U893) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_C100_1, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_C100_2, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1004, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1005, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1006, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1007, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1008, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1009, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100A, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100B, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100C, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100D, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100E, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100F, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1010, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1011, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1012, 0xff) }, + { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC650) }, + { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC680) }, + { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */ + { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */ + { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x0023)}, /* ONYX 3G device */ + { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x9000)}, /* SIMCom SIM5218 */ + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6001) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CMU_300) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6003), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6004) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6005) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CGU_628A) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHE_628S), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CMU_301), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_628), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_628S) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDU_680) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDU_685A) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_720S), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7002), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_629K), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7004), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7005) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CGU_629), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_629S), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_720I), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7212), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7213), + .driver_info = (kernel_ulong_t)&net_intf0_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7251), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7252), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7253), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_CC864_DUAL) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_CC864_SINGLE) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_DE910_DUAL) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UE910_V2) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE910), + .driver_info = (kernel_ulong_t)&telit_le910_blacklist }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920), + .driver_info = (kernel_ulong_t)&telit_le920_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */ + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0002, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0003, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0004, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0005, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0006, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0008, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0009, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000a, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000b, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000c, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000d, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000e, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x000f, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0010, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0011, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0012, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0013, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF628, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0016, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0017, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0018, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0019, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0020, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0021, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0022, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0023, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0024, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0025, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0028, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0029, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0030, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF626, 0xff, + 0xff, 0xff), .driver_info = (kernel_ulong_t)&zte_mf626_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0032, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0033, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0034, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0037, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&zte_0037_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0038, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0039, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0040, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0042, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0043, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0044, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0048, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0049, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0050, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0051, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0052, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0054, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0055, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0056, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0057, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0058, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0061, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0062, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0063, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0064, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0065, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0066, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0067, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0069, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0076, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0077, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0078, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0079, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0082, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0083, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0086, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0087, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0088, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0089, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0090, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0091, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0092, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0093, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0094, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0095, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0096, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0097, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0104, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0105, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0106, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0108, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0113, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0117, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0118, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0121, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0122, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0123, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0124, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0125, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0126, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0128, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0135, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0136, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0137, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0139, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0142, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0143, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0144, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0145, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0148, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0151, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0153, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0155, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0156, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0157, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0158, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0159, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0161, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0162, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0164, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0165, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0167, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0189, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0191, 0xff, 0xff, 0xff), /* ZTE EuFi890 */ + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0196, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0197, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0199, 0xff, 0xff, 0xff), /* ZTE MF820S */ + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0200, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0201, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0254, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0257, 0xff, 0xff, 0xff), /* ZTE MF821 */ + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0265, 0xff, 0xff, 0xff), /* ONDA MT8205 */ + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0284, 0xff, 0xff, 0xff), /* ZTE MF880 */ + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0317, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0326, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0330, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0395, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0412, 0xff, 0xff, 0xff), /* Telewell TW-LTE 4G */ + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0414, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0417, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1008, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1010, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1012, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1018, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1021, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1057, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1058, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1059, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1060, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1061, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1062, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1063, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1064, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1065, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1066, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1067, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1068, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1069, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1070, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1071, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1072, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1073, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1074, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1075, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1076, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1077, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1078, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1079, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1080, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1081, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1082, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1083, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1084, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1085, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1086, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1087, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1088, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1089, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1090, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1091, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1092, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1093, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1094, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1095, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1096, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1097, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1098, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1099, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1100, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1101, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1102, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1103, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1104, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1105, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1106, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1107, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1108, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1109, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1110, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1111, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1112, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1113, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1114, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1115, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1116, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1117, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1118, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1119, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1120, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1121, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1122, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1123, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1124, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1125, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1126, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1127, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1128, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1129, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1130, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1131, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1132, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1133, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1134, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1135, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1136, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1137, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1138, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1139, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1140, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1141, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1142, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1143, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1144, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1145, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1146, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1147, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1148, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1149, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1150, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1151, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1152, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1153, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1154, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1155, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1156, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1157, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1158, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1159, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1160, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1161, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1162, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1163, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1164, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1165, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1166, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1167, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1168, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1169, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1170, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1244, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1245, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1246, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1247, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1248, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1249, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1250, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1251, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1252, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1253, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1254, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1255, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&zte_1255_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1256, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1257, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1258, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1259, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1260, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1261, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1262, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1263, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1264, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1265, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1266, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1267, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1268, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1269, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1270, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1271, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1272, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1273, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1274, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1275, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1276, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1277, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1278, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1279, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1280, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1281, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1282, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1283, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1284, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1285, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1286, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1287, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1288, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1289, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1290, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1291, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1292, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1293, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1294, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1295, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1296, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1297, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1298, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1299, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1300, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1301, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1302, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1303, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1333, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1401, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1402, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1424, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1425, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1426, 0xff, 0xff, 0xff), /* ZTE MF91 */ + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1428, 0xff, 0xff, 0xff), /* Telewell TW-LTE 4G v2 */ + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1533, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1534, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1535, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1545, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1546, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1547, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1565, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1566, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1567, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1589, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1590, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1591, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1592, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1594, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1596, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1598, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1600, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2002, 0xff, + 0xff, 0xff), .driver_info = (kernel_ulong_t)&zte_k3765_z_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2003, 0xff, 0xff, 0xff) }, + + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0014, 0xff, 0xff, 0xff) }, /* ZTE CDMA products */ + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0027, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0059, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0060, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0070, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0073, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0094, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0130, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0133, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0141, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0147, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0152, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0168, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0170, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0176, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0178, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffe9, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8b, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8c, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8d, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8e, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8f, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff90, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff91, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff92, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff93, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff94, 0xff, 0xff, 0xff) }, + + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_CDMA_TECH, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AC2726, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AC8710T, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MC2718, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&zte_mc2718_z_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AD3812, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&zte_ad3812_z_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MC2716, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&zte_mc2716_z_blacklist }, + { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x02, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x02, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x86, 0x10) }, + + { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_H10) }, + { USB_DEVICE(DLINK_VENDOR_ID, DLINK_PRODUCT_DWM_652) }, + { USB_DEVICE(ALINK_VENDOR_ID, DLINK_PRODUCT_DWM_652_U5) }, /* Yes, ALINK_VENDOR_ID */ + { USB_DEVICE(ALINK_VENDOR_ID, DLINK_PRODUCT_DWM_652_U5A) }, + { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H21_4512) }, + { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H21_4523) }, + { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H20_4515) }, + { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H20_4518) }, + { USB_DEVICE(QISDA_VENDOR_ID, QISDA_PRODUCT_H20_4519) }, + { USB_DEVICE(TOSHIBA_VENDOR_ID, TOSHIBA_PRODUCT_G450) }, + { USB_DEVICE(TOSHIBA_VENDOR_ID, TOSHIBA_PRODUCT_HSDPA_MINICARD ) }, /* Toshiba 3G HSDPA == Novatel Expedite EU870D MiniCard */ + { USB_DEVICE(ALINK_VENDOR_ID, 0x9000) }, + { USB_DEVICE(ALINK_VENDOR_ID, ALINK_PRODUCT_PH300) }, + { USB_DEVICE_AND_INTERFACE_INFO(ALINK_VENDOR_ID, ALINK_PRODUCT_3GU, 0xff, 0xff, 0xff) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X060S_X200), + .driver_info = (kernel_ulong_t)&alcatel_x200_blacklist + }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X220_X500D), + .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + { USB_DEVICE(ALCATEL_VENDOR_ID, 0x0052), + .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + { USB_DEVICE(ALCATEL_VENDOR_ID, 0x00b6), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE(ALCATEL_VENDOR_ID, 0x00b7), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_L100V), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_L800MA), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE(AIRPLUS_VENDOR_ID, AIRPLUS_PRODUCT_MCD650) }, + { USB_DEVICE(TLAYTECH_VENDOR_ID, TLAYTECH_PRODUCT_TEU800) }, + { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W14), + .driver_info = (kernel_ulong_t)&four_g_w14_blacklist + }, + { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, SPEEDUP_PRODUCT_SU9800, 0xff) }, + { USB_DEVICE(LONGCHEER_VENDOR_ID, ZOOM_PRODUCT_4597) }, + { USB_DEVICE(LONGCHEER_VENDOR_ID, IBALL_3_5G_CONNECT) }, + { USB_DEVICE(HAIER_VENDOR_ID, HAIER_PRODUCT_CE100) }, + { USB_DEVICE_AND_INTERFACE_INFO(HAIER_VENDOR_ID, HAIER_PRODUCT_CE81B, 0xff, 0xff, 0xff) }, + /* Pirelli */ + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_C100_1, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_C100_2, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1004, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1005, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1006, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1007, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1008, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1009, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100A, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100B, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100C, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100D, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100E, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_100F, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1011, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(PIRELLI_VENDOR_ID, PIRELLI_PRODUCT_1012, 0xff) }, + /* Cinterion */ + { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_EU3_E) }, + { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_EU3_P) }, + { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX) }, + { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PLXX), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_HC28_MDM) }, + { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_HC28_MDMNET) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC25_MDM) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC25_MDMNET) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC28_MDM) }, /* HC28 enumerates with Siemens or Cinterion VID depending on FW revision */ + { USB_DEVICE(SIEMENS_VENDOR_ID, CINTERION_PRODUCT_HC28_MDMNET) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD100), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD120), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD140), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD145) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD155), + .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD200), + .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD160), + .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD500), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(CELOT_VENDOR_ID, CELOT_PRODUCT_CT680M) }, /* CT-650 CDMA 450 1xEVDO modem */ + { USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_GT_B3730, USB_CLASS_CDC_DATA, 0x00, 0x00) }, /* Samsung GT-B3730 LTE USB modem.*/ + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM600) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM610) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM500) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM510) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM800) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEM900) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU818) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU816) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU828) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU826) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU518) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU516) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU528) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU526) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM600) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM610) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM500) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM510) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM800) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWM900) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU718) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU716) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU728) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU726) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU518) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU516) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU528) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU526) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM600) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM610) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM500) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM510) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM800) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLM900) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU718) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU716) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU728) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU726) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU518) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU516) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU528) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU526) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU881) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU882) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU581) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU582) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU583) }, + { USB_DEVICE_AND_INTERFACE_INFO(VIETTEL_VENDOR_ID, VIETTEL_PRODUCT_VT1000, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZD_VENDOR_ID, ZD_PRODUCT_7000, 0xff, 0xff, 0xff) }, + { USB_DEVICE(LG_VENDOR_ID, LG_PRODUCT_L02C) }, /* docomo L-02C modem */ + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a1, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a1, 0xff, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a2, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a2, 0xff, 0x02, 0x01) }, /* MediaTek MT6276M modem & app port */ + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_1COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_5COM, 0xff, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_5COM, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM, 0xff, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7208_1COM, 0x02, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7208_2COM, 0x02, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FP_1COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FP_2COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FPDC_1COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FPDC_2COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7103_2COM, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7106_2COM, 0x02, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM2, 0xff, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM2, 0xff, 0x00, 0x00) }, + { USB_DEVICE(CELLIENT_VENDOR_ID, CELLIENT_PRODUCT_MEN200) }, + { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600A) }, + { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600E) }, + { USB_DEVICE(TPLINK_VENDOR_ID, TPLINK_PRODUCT_MA180), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(TPLINK_VENDOR_ID, 0x9000), /* TP-Link MA260 */ + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE(CHANGHONG_VENDOR_ID, CHANGHONG_PRODUCT_CH690) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x7d01, 0xff, 0x02, 0x01) }, /* D-Link DWM-156 (variant) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x7d01, 0xff, 0x00, 0x00) }, /* D-Link DWM-156 (variant) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x7d02, 0xff, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x7d02, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x7d03, 0xff, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x7d03, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3e01, 0xff, 0xff, 0xff) }, /* D-Link DWM-152/C1 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3e02, 0xff, 0xff, 0xff) }, /* D-Link DWM-156/C1 */ + { USB_DEVICE(INOVIA_VENDOR_ID, INOVIA_SEW858) }, + { USB_DEVICE(VIATELECOM_VENDOR_ID, VIATELECOM_PRODUCT_CDS7) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, option_ids); + +/* The card has three separate interfaces, which the serial driver + * recognizes separately, thus num_port=1. + */ + +static struct usb_serial_driver option_1port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "option1", + }, + .description = "GSM modem (1-port)", + .id_table = option_ids, + .num_ports = 1, + .probe = option_probe, + .open = usb_wwan_open, + .close = usb_wwan_close, + .dtr_rts = usb_wwan_dtr_rts, + .write = usb_wwan_write, + .write_room = usb_wwan_write_room, + .chars_in_buffer = usb_wwan_chars_in_buffer, + .set_termios = usb_wwan_set_termios, + .tiocmget = usb_wwan_tiocmget, + .tiocmset = usb_wwan_tiocmset, + .ioctl = usb_wwan_ioctl, + .attach = option_attach, + .release = option_release, + .port_probe = usb_wwan_port_probe, + .port_remove = usb_wwan_port_remove, + .read_int_callback = option_instat_callback, +#ifdef CONFIG_PM + .suspend = usb_wwan_suspend, + .resume = usb_wwan_resume, +#endif +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &option_1port_device, NULL +}; + +struct option_private { + u8 bInterfaceNumber; +}; + +module_usb_serial_driver(serial_drivers, option_ids); + +static bool is_blacklisted(const u8 ifnum, enum option_blacklist_reason reason, + const struct option_blacklist_info *blacklist) +{ + unsigned long num; + const unsigned long *intf_list; + + if (blacklist) { + if (reason == OPTION_BLACKLIST_SENDSETUP) + intf_list = &blacklist->sendsetup; + else if (reason == OPTION_BLACKLIST_RESERVED_IF) + intf_list = &blacklist->reserved; + else { + BUG_ON(reason); + return false; + } + + for_each_set_bit(num, intf_list, MAX_BL_NUM + 1) { + if (num == ifnum) + return true; + } + } + return false; +} + +static int option_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct usb_interface_descriptor *iface_desc = + &serial->interface->cur_altsetting->desc; + struct usb_device_descriptor *dev_desc = &serial->dev->descriptor; + + /* Never bind to the CD-Rom emulation interface */ + if (iface_desc->bInterfaceClass == 0x08) + return -ENODEV; + + /* + * Don't bind reserved interfaces (like network ones) which often have + * the same class/subclass/protocol as the serial interfaces. Look at + * the Windows driver .INF files for reserved interface numbers. + */ + if (is_blacklisted( + iface_desc->bInterfaceNumber, + OPTION_BLACKLIST_RESERVED_IF, + (const struct option_blacklist_info *) id->driver_info)) + return -ENODEV; + /* + * Don't bind network interface on Samsung GT-B3730, it is handled by + * a separate module. + */ + if (dev_desc->idVendor == cpu_to_le16(SAMSUNG_VENDOR_ID) && + dev_desc->idProduct == cpu_to_le16(SAMSUNG_PRODUCT_GT_B3730) && + iface_desc->bInterfaceClass != USB_CLASS_CDC_DATA) + return -ENODEV; + + /* Store device id so we can use it during attach. */ + usb_set_serial_data(serial, (void *)id); + + return 0; +} + +static int option_attach(struct usb_serial *serial) +{ + struct usb_interface_descriptor *iface_desc; + const struct usb_device_id *id; + struct usb_wwan_intf_private *data; + struct option_private *priv; + + data = kzalloc(sizeof(struct usb_wwan_intf_private), GFP_KERNEL); + if (!data) + return -ENOMEM; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + kfree(data); + return -ENOMEM; + } + + /* Retrieve device id stored at probe. */ + id = usb_get_serial_data(serial); + iface_desc = &serial->interface->cur_altsetting->desc; + + priv->bInterfaceNumber = iface_desc->bInterfaceNumber; + data->private = priv; + + if (!is_blacklisted(iface_desc->bInterfaceNumber, + OPTION_BLACKLIST_SENDSETUP, + (struct option_blacklist_info *)id->driver_info)) { + data->send_setup = option_send_setup; + } + spin_lock_init(&data->susp_lock); + + usb_set_serial_data(serial, data); + + return 0; +} + +static void option_release(struct usb_serial *serial) +{ + struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial); + struct option_private *priv = intfdata->private; + + kfree(priv); + kfree(intfdata); +} + +static void option_instat_callback(struct urb *urb) +{ + int err; + int status = urb->status; + struct usb_serial_port *port = urb->context; + struct device *dev = &port->dev; + struct usb_wwan_port_private *portdata = + usb_get_serial_port_data(port); + + dev_dbg(dev, "%s: urb %p port %p has data %p\n", __func__, urb, port, portdata); + + if (status == 0) { + struct usb_ctrlrequest *req_pkt = + (struct usb_ctrlrequest *)urb->transfer_buffer; + + if (!req_pkt) { + dev_dbg(dev, "%s: NULL req_pkt\n", __func__); + return; + } + if ((req_pkt->bRequestType == 0xA1) && + (req_pkt->bRequest == 0x20)) { + int old_dcd_state; + unsigned char signals = *((unsigned char *) + urb->transfer_buffer + + sizeof(struct usb_ctrlrequest)); + + dev_dbg(dev, "%s: signal x%x\n", __func__, signals); + + old_dcd_state = portdata->dcd_state; + portdata->cts_state = 1; + portdata->dcd_state = ((signals & 0x01) ? 1 : 0); + portdata->dsr_state = ((signals & 0x02) ? 1 : 0); + portdata->ri_state = ((signals & 0x08) ? 1 : 0); + + if (old_dcd_state && !portdata->dcd_state) + tty_port_tty_hangup(&port->port, true); + } else { + dev_dbg(dev, "%s: type %x req %x\n", __func__, + req_pkt->bRequestType, req_pkt->bRequest); + } + } else if (status == -ENOENT || status == -ESHUTDOWN) { + dev_dbg(dev, "%s: urb stopped: %d\n", __func__, status); + } else + dev_err(dev, "%s: error %d\n", __func__, status); + + /* Resubmit urb so we continue receiving IRQ data */ + if (status != -ESHUTDOWN && status != -ENOENT) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dev_dbg(dev, "%s: resubmit intr urb failed. (%d)\n", + __func__, err); + } +} + +/** send RTS/DTR state to the port. + * + * This is exactly the same as SET_CONTROL_LINE_STATE from the PSTN + * CDC. +*/ +static int option_send_setup(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial); + struct option_private *priv = intfdata->private; + struct usb_wwan_port_private *portdata; + int val = 0; + int res; + + portdata = usb_get_serial_port_data(port); + + if (portdata->dtr_state) + val |= 0x01; + if (portdata->rts_state) + val |= 0x02; + + res = usb_autopm_get_interface(serial->interface); + if (res) + return res; + + res = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + 0x22, 0x21, val, priv->bInterfaceNumber, NULL, + 0, USB_CTRL_SET_TIMEOUT); + + usb_autopm_put_interface(serial->interface); + + return res; +} + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c new file mode 100644 index 00000000..7e3e0782 --- /dev/null +++ b/drivers/usb/serial/oti6858.c @@ -0,0 +1,882 @@ +/* + * Ours Technology Inc. OTi-6858 USB to serial adapter driver. + * + * Copyleft (C) 2007 Kees Lemmens (adapted for kernel 2.6.20) + * Copyright (C) 2006 Tomasz Michal Lukaszewski (FIXME: add e-mail) + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2003 IBM Corp. + * + * Many thanks to the authors of pl2303 driver: all functions in this file + * are heavily based on pl2303 code, buffering code is a 1-to-1 copy. + * + * Warning! You use this driver on your own risk! The only official + * description of this device I have is datasheet from manufacturer, + * and it doesn't contain almost any information needed to write a driver. + * Almost all knowlegde used while writing this driver was gathered by: + * - analyzing traffic between device and the M$ Windows 2000 driver, + * - trying different bit combinations and checking pin states + * with a voltmeter, + * - receiving malformed frames and producing buffer overflows + * to learn how errors are reported, + * So, THIS CODE CAN DESTROY OTi-6858 AND ANY OTHER DEVICES, THAT ARE + * CONNECTED TO IT! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + * + * TODO: + * - implement correct flushing for ioctls and oti6858_close() + * - check how errors (rx overflow, parity error, framing error) are reported + * - implement oti6858_break_ctl() + * - implement more ioctls + * - test/implement flow control + * - allow setting custom baud rates + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "oti6858.h" + +#define OTI6858_DESCRIPTION \ + "Ours Technology Inc. OTi-6858 USB to serial adapter driver" +#define OTI6858_AUTHOR "Tomasz Michal Lukaszewski " + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(OTI6858_VENDOR_ID, OTI6858_PRODUCT_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +/* requests */ +#define OTI6858_REQ_GET_STATUS (USB_DIR_IN | USB_TYPE_VENDOR | 0x00) +#define OTI6858_REQ_T_GET_STATUS 0x01 + +#define OTI6858_REQ_SET_LINE (USB_DIR_OUT | USB_TYPE_VENDOR | 0x00) +#define OTI6858_REQ_T_SET_LINE 0x00 + +#define OTI6858_REQ_CHECK_TXBUFF (USB_DIR_IN | USB_TYPE_VENDOR | 0x01) +#define OTI6858_REQ_T_CHECK_TXBUFF 0x00 + +/* format of the control packet */ +struct oti6858_control_pkt { + __le16 divisor; /* baud rate = 96000000 / (16 * divisor), LE */ +#define OTI6858_MAX_BAUD_RATE 3000000 + u8 frame_fmt; +#define FMT_STOP_BITS_MASK 0xc0 +#define FMT_STOP_BITS_1 0x00 +#define FMT_STOP_BITS_2 0x40 /* 1.5 stop bits if FMT_DATA_BITS_5 */ +#define FMT_PARITY_MASK 0x38 +#define FMT_PARITY_NONE 0x00 +#define FMT_PARITY_ODD 0x08 +#define FMT_PARITY_EVEN 0x18 +#define FMT_PARITY_MARK 0x28 +#define FMT_PARITY_SPACE 0x38 +#define FMT_DATA_BITS_MASK 0x03 +#define FMT_DATA_BITS_5 0x00 +#define FMT_DATA_BITS_6 0x01 +#define FMT_DATA_BITS_7 0x02 +#define FMT_DATA_BITS_8 0x03 + u8 something; /* always equals 0x43 */ + u8 control; /* settings of flow control lines */ +#define CONTROL_MASK 0x0c +#define CONTROL_DTR_HIGH 0x08 +#define CONTROL_RTS_HIGH 0x04 + u8 tx_status; +#define TX_BUFFER_EMPTIED 0x09 + u8 pin_state; +#define PIN_MASK 0x3f +#define PIN_RTS 0x20 /* output pin */ +#define PIN_CTS 0x10 /* input pin, active low */ +#define PIN_DSR 0x08 /* input pin, active low */ +#define PIN_DTR 0x04 /* output pin */ +#define PIN_RI 0x02 /* input pin, active low */ +#define PIN_DCD 0x01 /* input pin, active low */ + u8 rx_bytes_avail; /* number of bytes in rx buffer */; +}; + +#define OTI6858_CTRL_PKT_SIZE sizeof(struct oti6858_control_pkt) +#define OTI6858_CTRL_EQUALS_PENDING(a, priv) \ + (((a)->divisor == (priv)->pending_setup.divisor) \ + && ((a)->control == (priv)->pending_setup.control) \ + && ((a)->frame_fmt == (priv)->pending_setup.frame_fmt)) + +/* function prototypes */ +static int oti6858_open(struct tty_struct *tty, struct usb_serial_port *port); +static void oti6858_close(struct usb_serial_port *port); +static void oti6858_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old); +static void oti6858_init_termios(struct tty_struct *tty); +static void oti6858_read_int_callback(struct urb *urb); +static void oti6858_read_bulk_callback(struct urb *urb); +static void oti6858_write_bulk_callback(struct urb *urb); +static int oti6858_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +static int oti6858_write_room(struct tty_struct *tty); +static int oti6858_chars_in_buffer(struct tty_struct *tty); +static int oti6858_tiocmget(struct tty_struct *tty); +static int oti6858_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static int oti6858_tiocmiwait(struct tty_struct *tty, unsigned long arg); +static int oti6858_port_probe(struct usb_serial_port *port); +static int oti6858_port_remove(struct usb_serial_port *port); + +/* device info */ +static struct usb_serial_driver oti6858_device = { + .driver = { + .owner = THIS_MODULE, + .name = "oti6858", + }, + .id_table = id_table, + .num_ports = 1, + .open = oti6858_open, + .close = oti6858_close, + .write = oti6858_write, + .set_termios = oti6858_set_termios, + .init_termios = oti6858_init_termios, + .tiocmget = oti6858_tiocmget, + .tiocmset = oti6858_tiocmset, + .tiocmiwait = oti6858_tiocmiwait, + .read_bulk_callback = oti6858_read_bulk_callback, + .read_int_callback = oti6858_read_int_callback, + .write_bulk_callback = oti6858_write_bulk_callback, + .write_room = oti6858_write_room, + .chars_in_buffer = oti6858_chars_in_buffer, + .port_probe = oti6858_port_probe, + .port_remove = oti6858_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &oti6858_device, NULL +}; + +struct oti6858_private { + spinlock_t lock; + + struct oti6858_control_pkt status; + + struct { + u8 read_urb_in_use; + u8 write_urb_in_use; + } flags; + struct delayed_work delayed_write_work; + + struct { + __le16 divisor; + u8 frame_fmt; + u8 control; + } pending_setup; + u8 transient; + u8 setup_done; + struct delayed_work delayed_setup_work; + + struct usb_serial_port *port; /* USB port with which associated */ +}; + +static void setup_line(struct work_struct *work) +{ + struct oti6858_private *priv = container_of(work, + struct oti6858_private, delayed_setup_work.work); + struct usb_serial_port *port = priv->port; + struct oti6858_control_pkt *new_setup; + unsigned long flags; + int result; + + new_setup = kmalloc(OTI6858_CTRL_PKT_SIZE, GFP_KERNEL); + if (new_setup == NULL) { + dev_err(&port->dev, "%s(): out of memory!\n", __func__); + /* we will try again */ + schedule_delayed_work(&priv->delayed_setup_work, + msecs_to_jiffies(2)); + return; + } + + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + OTI6858_REQ_T_GET_STATUS, + OTI6858_REQ_GET_STATUS, + 0, 0, + new_setup, OTI6858_CTRL_PKT_SIZE, + 100); + + if (result != OTI6858_CTRL_PKT_SIZE) { + dev_err(&port->dev, "%s(): error reading status\n", __func__); + kfree(new_setup); + /* we will try again */ + schedule_delayed_work(&priv->delayed_setup_work, + msecs_to_jiffies(2)); + return; + } + + spin_lock_irqsave(&priv->lock, flags); + if (!OTI6858_CTRL_EQUALS_PENDING(new_setup, priv)) { + new_setup->divisor = priv->pending_setup.divisor; + new_setup->control = priv->pending_setup.control; + new_setup->frame_fmt = priv->pending_setup.frame_fmt; + + spin_unlock_irqrestore(&priv->lock, flags); + result = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + OTI6858_REQ_T_SET_LINE, + OTI6858_REQ_SET_LINE, + 0, 0, + new_setup, OTI6858_CTRL_PKT_SIZE, + 100); + } else { + spin_unlock_irqrestore(&priv->lock, flags); + result = 0; + } + kfree(new_setup); + + spin_lock_irqsave(&priv->lock, flags); + if (result != OTI6858_CTRL_PKT_SIZE) + priv->transient = 0; + priv->setup_done = 1; + spin_unlock_irqrestore(&priv->lock, flags); + + dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__); + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result != 0) { + dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n", + __func__, result); + } +} + +static void send_data(struct work_struct *work) +{ + struct oti6858_private *priv = container_of(work, + struct oti6858_private, delayed_write_work.work); + struct usb_serial_port *port = priv->port; + int count = 0, result; + unsigned long flags; + u8 *allow; + + spin_lock_irqsave(&priv->lock, flags); + if (priv->flags.write_urb_in_use) { + spin_unlock_irqrestore(&priv->lock, flags); + schedule_delayed_work(&priv->delayed_write_work, + msecs_to_jiffies(2)); + return; + } + priv->flags.write_urb_in_use = 1; + spin_unlock_irqrestore(&priv->lock, flags); + + spin_lock_irqsave(&port->lock, flags); + count = kfifo_len(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + + if (count > port->bulk_out_size) + count = port->bulk_out_size; + + if (count != 0) { + allow = kmalloc(1, GFP_KERNEL); + if (!allow) { + dev_err_console(port, "%s(): kmalloc failed\n", + __func__); + return; + } + result = usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + OTI6858_REQ_T_CHECK_TXBUFF, + OTI6858_REQ_CHECK_TXBUFF, + count, 0, allow, 1, 100); + if (result != 1 || *allow != 0) + count = 0; + kfree(allow); + } + + if (count == 0) { + priv->flags.write_urb_in_use = 0; + + dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__); + result = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO); + if (result != 0) { + dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n", + __func__, result); + } + return; + } + + count = kfifo_out_locked(&port->write_fifo, + port->write_urb->transfer_buffer, + count, &port->lock); + port->write_urb->transfer_buffer_length = count; + result = usb_submit_urb(port->write_urb, GFP_NOIO); + if (result != 0) { + dev_err_console(port, "%s(): usb_submit_urb() failed with error %d\n", + __func__, result); + priv->flags.write_urb_in_use = 0; + } + + usb_serial_port_softint(port); +} + +static int oti6858_port_probe(struct usb_serial_port *port) +{ + struct oti6858_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + priv->port = port; + INIT_DELAYED_WORK(&priv->delayed_setup_work, setup_line); + INIT_DELAYED_WORK(&priv->delayed_write_work, send_data); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int oti6858_port_remove(struct usb_serial_port *port) +{ + struct oti6858_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int oti6858_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + if (!count) + return count; + + count = kfifo_in_locked(&port->write_fifo, buf, count, &port->lock); + + return count; +} + +static int oti6858_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + int room = 0; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + room = kfifo_avail(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + + return room; +} + +static int oti6858_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + int chars = 0; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + chars = kfifo_len(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + + return chars; +} + +static void oti6858_init_termios(struct tty_struct *tty) +{ + tty->termios = tty_std_termios; + tty->termios.c_cflag = B38400 | CS8 | CREAD | HUPCL | CLOCAL; + tty->termios.c_ispeed = 38400; + tty->termios.c_ospeed = 38400; +} + +static void oti6858_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct oti6858_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int cflag; + u8 frame_fmt, control; + __le16 divisor; + int br; + + if (!tty) + return; + + cflag = tty->termios.c_cflag; + + spin_lock_irqsave(&priv->lock, flags); + divisor = priv->pending_setup.divisor; + frame_fmt = priv->pending_setup.frame_fmt; + control = priv->pending_setup.control; + spin_unlock_irqrestore(&priv->lock, flags); + + frame_fmt &= ~FMT_DATA_BITS_MASK; + switch (cflag & CSIZE) { + case CS5: + frame_fmt |= FMT_DATA_BITS_5; + break; + case CS6: + frame_fmt |= FMT_DATA_BITS_6; + break; + case CS7: + frame_fmt |= FMT_DATA_BITS_7; + break; + default: + case CS8: + frame_fmt |= FMT_DATA_BITS_8; + break; + } + + /* manufacturer claims that this device can work with baud rates + * up to 3 Mbps; I've tested it only on 115200 bps, so I can't + * guarantee that any other baud rate will work (especially + * the higher ones) + */ + br = tty_get_baud_rate(tty); + if (br == 0) { + divisor = 0; + } else { + int real_br; + int new_divisor; + br = min(br, OTI6858_MAX_BAUD_RATE); + + new_divisor = (96000000 + 8 * br) / (16 * br); + real_br = 96000000 / (16 * new_divisor); + divisor = cpu_to_le16(new_divisor); + tty_encode_baud_rate(tty, real_br, real_br); + } + + frame_fmt &= ~FMT_STOP_BITS_MASK; + if ((cflag & CSTOPB) != 0) + frame_fmt |= FMT_STOP_BITS_2; + else + frame_fmt |= FMT_STOP_BITS_1; + + frame_fmt &= ~FMT_PARITY_MASK; + if ((cflag & PARENB) != 0) { + if ((cflag & PARODD) != 0) + frame_fmt |= FMT_PARITY_ODD; + else + frame_fmt |= FMT_PARITY_EVEN; + } else { + frame_fmt |= FMT_PARITY_NONE; + } + + control &= ~CONTROL_MASK; + if ((cflag & CRTSCTS) != 0) + control |= (CONTROL_DTR_HIGH | CONTROL_RTS_HIGH); + + /* change control lines if we are switching to or from B0 */ + /* FIXME: + spin_lock_irqsave(&priv->lock, flags); + control = priv->line_control; + if ((cflag & CBAUD) == B0) + priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); + else + priv->line_control |= (CONTROL_DTR | CONTROL_RTS); + if (control != priv->line_control) { + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + set_control_lines(serial->dev, control); + } else { + spin_unlock_irqrestore(&priv->lock, flags); + } + */ + + spin_lock_irqsave(&priv->lock, flags); + if (divisor != priv->pending_setup.divisor + || control != priv->pending_setup.control + || frame_fmt != priv->pending_setup.frame_fmt) { + priv->pending_setup.divisor = divisor; + priv->pending_setup.control = control; + priv->pending_setup.frame_fmt = frame_fmt; + } + spin_unlock_irqrestore(&priv->lock, flags); +} + +static int oti6858_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct oti6858_private *priv = usb_get_serial_port_data(port); + struct ktermios tmp_termios; + struct usb_serial *serial = port->serial; + struct oti6858_control_pkt *buf; + unsigned long flags; + int result; + + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + + buf = kmalloc(OTI6858_CTRL_PKT_SIZE, GFP_KERNEL); + if (buf == NULL) { + dev_err(&port->dev, "%s(): out of memory!\n", __func__); + return -ENOMEM; + } + + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + OTI6858_REQ_T_GET_STATUS, + OTI6858_REQ_GET_STATUS, + 0, 0, + buf, OTI6858_CTRL_PKT_SIZE, + 100); + if (result != OTI6858_CTRL_PKT_SIZE) { + /* assume default (after power-on reset) values */ + buf->divisor = cpu_to_le16(0x009c); /* 38400 bps */ + buf->frame_fmt = 0x03; /* 8N1 */ + buf->something = 0x43; + buf->control = 0x4c; /* DTR, RTS */ + buf->tx_status = 0x00; + buf->pin_state = 0x5b; /* RTS, CTS, DSR, DTR, RI, DCD */ + buf->rx_bytes_avail = 0x00; + } + + spin_lock_irqsave(&priv->lock, flags); + memcpy(&priv->status, buf, OTI6858_CTRL_PKT_SIZE); + priv->pending_setup.divisor = buf->divisor; + priv->pending_setup.frame_fmt = buf->frame_fmt; + priv->pending_setup.control = buf->control; + spin_unlock_irqrestore(&priv->lock, flags); + kfree(buf); + + dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__); + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result != 0) { + dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n", + __func__, result); + oti6858_close(port); + return result; + } + + /* setup termios */ + if (tty) + oti6858_set_termios(tty, port, &tmp_termios); + port->port.drain_delay = 256; /* FIXME: check the FIFO length */ + return 0; +} + +static void oti6858_close(struct usb_serial_port *port) +{ + struct oti6858_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + /* clear out any remaining data in the buffer */ + kfifo_reset_out(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + + dev_dbg(&port->dev, "%s(): after buf_clear()\n", __func__); + + /* cancel scheduled setup */ + cancel_delayed_work_sync(&priv->delayed_setup_work); + cancel_delayed_work_sync(&priv->delayed_write_work); + + /* shutdown our urbs */ + dev_dbg(&port->dev, "%s(): shutting down urbs\n", __func__); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); + usb_kill_urb(port->interrupt_in_urb); +} + +static int oti6858_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct oti6858_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 control; + + dev_dbg(&port->dev, "%s(set = 0x%08x, clear = 0x%08x)\n", + __func__, set, clear); + + /* FIXME: check if this is correct (active high/low) */ + spin_lock_irqsave(&priv->lock, flags); + control = priv->pending_setup.control; + if ((set & TIOCM_RTS) != 0) + control |= CONTROL_RTS_HIGH; + if ((set & TIOCM_DTR) != 0) + control |= CONTROL_DTR_HIGH; + if ((clear & TIOCM_RTS) != 0) + control &= ~CONTROL_RTS_HIGH; + if ((clear & TIOCM_DTR) != 0) + control &= ~CONTROL_DTR_HIGH; + + if (control != priv->pending_setup.control) + priv->pending_setup.control = control; + + spin_unlock_irqrestore(&priv->lock, flags); + return 0; +} + +static int oti6858_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct oti6858_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned pin_state; + unsigned result = 0; + + spin_lock_irqsave(&priv->lock, flags); + pin_state = priv->status.pin_state & PIN_MASK; + spin_unlock_irqrestore(&priv->lock, flags); + + /* FIXME: check if this is correct (active high/low) */ + if ((pin_state & PIN_RTS) != 0) + result |= TIOCM_RTS; + if ((pin_state & PIN_CTS) != 0) + result |= TIOCM_CTS; + if ((pin_state & PIN_DSR) != 0) + result |= TIOCM_DSR; + if ((pin_state & PIN_DTR) != 0) + result |= TIOCM_DTR; + if ((pin_state & PIN_RI) != 0) + result |= TIOCM_RI; + if ((pin_state & PIN_DCD) != 0) + result |= TIOCM_CD; + + dev_dbg(&port->dev, "%s() = 0x%08x\n", __func__, result); + + return result; +} + +static int oti6858_tiocmiwait(struct tty_struct *tty, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct oti6858_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int prev, status; + unsigned int changed; + + spin_lock_irqsave(&priv->lock, flags); + prev = priv->status.pin_state; + spin_unlock_irqrestore(&priv->lock, flags); + + while (1) { + wait_event_interruptible(port->port.delta_msr_wait, + port->serial->disconnected || + priv->status.pin_state != prev); + if (signal_pending(current)) + return -ERESTARTSYS; + + if (port->serial->disconnected) + return -EIO; + + spin_lock_irqsave(&priv->lock, flags); + status = priv->status.pin_state & PIN_MASK; + spin_unlock_irqrestore(&priv->lock, flags); + + changed = prev ^ status; + /* FIXME: check if this is correct (active high/low) */ + if (((arg & TIOCM_RNG) && (changed & PIN_RI)) || + ((arg & TIOCM_DSR) && (changed & PIN_DSR)) || + ((arg & TIOCM_CD) && (changed & PIN_DCD)) || + ((arg & TIOCM_CTS) && (changed & PIN_CTS))) + return 0; + prev = status; + } + + /* NOTREACHED */ + return 0; +} + +static void oti6858_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct oti6858_private *priv = usb_get_serial_port_data(port); + int transient = 0, can_recv = 0, resubmit = 1; + int status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s(): urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&urb->dev->dev, "%s(): nonzero urb status received: %d\n", + __func__, status); + break; + } + + if (status == 0 && urb->actual_length == OTI6858_CTRL_PKT_SIZE) { + struct oti6858_control_pkt *xs = urb->transfer_buffer; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + if (!priv->transient) { + if (!OTI6858_CTRL_EQUALS_PENDING(xs, priv)) { + if (xs->rx_bytes_avail == 0) { + priv->transient = 4; + priv->setup_done = 0; + resubmit = 0; + dev_dbg(&port->dev, "%s(): scheduling setup_line()\n", __func__); + schedule_delayed_work(&priv->delayed_setup_work, 0); + } + } + } else { + if (OTI6858_CTRL_EQUALS_PENDING(xs, priv)) { + priv->transient = 0; + } else if (!priv->setup_done) { + resubmit = 0; + } else if (--priv->transient == 0) { + if (xs->rx_bytes_avail == 0) { + priv->transient = 4; + priv->setup_done = 0; + resubmit = 0; + dev_dbg(&port->dev, "%s(): scheduling setup_line()\n", __func__); + schedule_delayed_work(&priv->delayed_setup_work, 0); + } + } + } + + if (!priv->transient) { + if (xs->pin_state != priv->status.pin_state) + wake_up_interruptible(&port->port.delta_msr_wait); + memcpy(&priv->status, xs, OTI6858_CTRL_PKT_SIZE); + } + + if (!priv->transient && xs->rx_bytes_avail != 0) { + can_recv = xs->rx_bytes_avail; + priv->flags.read_urb_in_use = 1; + } + + transient = priv->transient; + spin_unlock_irqrestore(&priv->lock, flags); + } + + if (can_recv) { + int result; + + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (result != 0) { + priv->flags.read_urb_in_use = 0; + dev_err(&port->dev, "%s(): usb_submit_urb() failed," + " error %d\n", __func__, result); + } else { + resubmit = 0; + } + } else if (!transient) { + unsigned long flags; + int count; + + spin_lock_irqsave(&port->lock, flags); + count = kfifo_len(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + + spin_lock_irqsave(&priv->lock, flags); + if (priv->flags.write_urb_in_use == 0 && count != 0) { + schedule_delayed_work(&priv->delayed_write_work, 0); + resubmit = 0; + } + spin_unlock_irqrestore(&priv->lock, flags); + } + + if (resubmit) { + int result; + +/* dev_dbg(&urb->dev->dev, "%s(): submitting interrupt urb\n", __func__); */ + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result != 0) { + dev_err(&urb->dev->dev, + "%s(): usb_submit_urb() failed with" + " error %d\n", __func__, result); + } + } +} + +static void oti6858_read_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct oti6858_private *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + int status = urb->status; + int result; + + spin_lock_irqsave(&priv->lock, flags); + priv->flags.read_urb_in_use = 0; + spin_unlock_irqrestore(&priv->lock, flags); + + if (status != 0) { + dev_dbg(&urb->dev->dev, "%s(): unable to handle the error, exiting\n", __func__); + return; + } + + if (urb->actual_length > 0) { + tty_insert_flip_string(&port->port, data, urb->actual_length); + tty_flip_buffer_push(&port->port); + } + + /* schedule the interrupt urb */ + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + if (result != 0 && result != -EPERM) { + dev_err(&port->dev, "%s(): usb_submit_urb() failed," + " error %d\n", __func__, result); + } +} + +static void oti6858_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct oti6858_private *priv = usb_get_serial_port_data(port); + int status = urb->status; + int result; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&urb->dev->dev, "%s(): urb shutting down with status: %d\n", __func__, status); + priv->flags.write_urb_in_use = 0; + return; + default: + /* error in the urb, so we have to resubmit it */ + dev_dbg(&urb->dev->dev, "%s(): nonzero write bulk status received: %d\n", __func__, status); + dev_dbg(&urb->dev->dev, "%s(): overflow in write\n", __func__); + + port->write_urb->transfer_buffer_length = 1; + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (result) { + dev_err_console(port, "%s(): usb_submit_urb() failed," + " error %d\n", __func__, result); + } else { + return; + } + } + + priv->flags.write_urb_in_use = 0; + + /* schedule the interrupt urb if we are still open */ + dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__); + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + if (result != 0) { + dev_err(&port->dev, "%s(): failed submitting int urb," + " error %d\n", __func__, result); + } +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(OTI6858_DESCRIPTION); +MODULE_AUTHOR(OTI6858_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/oti6858.h b/drivers/usb/serial/oti6858.h new file mode 100644 index 00000000..704ac3a5 --- /dev/null +++ b/drivers/usb/serial/oti6858.h @@ -0,0 +1,15 @@ +/* + * Ours Technology Inc. OTi-6858 USB to serial adapter driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __LINUX_USB_SERIAL_OTI6858_H +#define __LINUX_USB_SERIAL_OTI6858_H + +#define OTI6858_VENDOR_ID 0x0ea0 +#define OTI6858_PRODUCT_ID 0x6858 + +#endif diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c new file mode 100644 index 00000000..de3e15d8 --- /dev/null +++ b/drivers/usb/serial/pl2303.c @@ -0,0 +1,867 @@ +/* + * Prolific PL2303 USB to serial adaptor driver + * + * Copyright (C) 2001-2007 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2003 IBM Corp. + * + * Original driver for 2.2.x by anonymous + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pl2303.h" + +/* + * Version Information + */ +#define DRIVER_DESC "Prolific PL2303 USB to serial adaptor driver" + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_DCU11) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ3) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_PHAROS) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ALDIGA) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MMX) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GPRS) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_HCR331) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MOTOROLA) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ZTEK) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID) }, + { USB_DEVICE(ATEN_VENDOR_ID2, ATEN_PRODUCT_ID) }, + { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID) }, + { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID_UCSGT) }, + { USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID) }, + { USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID_2080) }, + { USB_DEVICE(MA620_VENDOR_ID, MA620_PRODUCT_ID) }, + { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID) }, + { USB_DEVICE(TRIPP_VENDOR_ID, TRIPP_PRODUCT_ID) }, + { USB_DEVICE(RADIOSHACK_VENDOR_ID, RADIOSHACK_PRODUCT_ID) }, + { USB_DEVICE(DCU10_VENDOR_ID, DCU10_PRODUCT_ID) }, + { USB_DEVICE(SITECOM_VENDOR_ID, SITECOM_PRODUCT_ID) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_ID) }, + { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_ID) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_SX1) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X65) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X75) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81) }, + { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_ID_S81) }, /* Benq/Siemens S81 */ + { USB_DEVICE(SYNTECH_VENDOR_ID, SYNTECH_PRODUCT_ID) }, + { USB_DEVICE(NOKIA_CA42_VENDOR_ID, NOKIA_CA42_PRODUCT_ID) }, + { USB_DEVICE(CA_42_CA42_VENDOR_ID, CA_42_CA42_PRODUCT_ID) }, + { USB_DEVICE(SAGEM_VENDOR_ID, SAGEM_PRODUCT_ID) }, + { USB_DEVICE(LEADTEK_VENDOR_ID, LEADTEK_9531_PRODUCT_ID) }, + { USB_DEVICE(SPEEDDRAGON_VENDOR_ID, SPEEDDRAGON_PRODUCT_ID) }, + { USB_DEVICE(DATAPILOT_U2_VENDOR_ID, DATAPILOT_U2_PRODUCT_ID) }, + { USB_DEVICE(BELKIN_VENDOR_ID, BELKIN_PRODUCT_ID) }, + { USB_DEVICE(ALCOR_VENDOR_ID, ALCOR_PRODUCT_ID) }, + { USB_DEVICE(WS002IN_VENDOR_ID, WS002IN_PRODUCT_ID) }, + { USB_DEVICE(COREGA_VENDOR_ID, COREGA_PRODUCT_ID) }, + { USB_DEVICE(YCCABLE_VENDOR_ID, YCCABLE_PRODUCT_ID) }, + { USB_DEVICE(SUPERIAL_VENDOR_ID, SUPERIAL_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LD220_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LD960_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LCM220_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LCM960_PRODUCT_ID) }, + { USB_DEVICE(CRESSI_VENDOR_ID, CRESSI_EDY_PRODUCT_ID) }, + { USB_DEVICE(ZEAGLE_VENDOR_ID, ZEAGLE_N2ITION3_PRODUCT_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_QN3USB_PRODUCT_ID) }, + { USB_DEVICE(SANWA_VENDOR_ID, SANWA_PRODUCT_ID) }, + { USB_DEVICE(ADLINK_VENDOR_ID, ADLINK_ND6530_PRODUCT_ID) }, + { USB_DEVICE(SMART_VENDOR_ID, SMART_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +#define SET_LINE_REQUEST_TYPE 0x21 +#define SET_LINE_REQUEST 0x20 + +#define SET_CONTROL_REQUEST_TYPE 0x21 +#define SET_CONTROL_REQUEST 0x22 +#define CONTROL_DTR 0x01 +#define CONTROL_RTS 0x02 + +#define BREAK_REQUEST_TYPE 0x21 +#define BREAK_REQUEST 0x23 +#define BREAK_ON 0xffff +#define BREAK_OFF 0x0000 + +#define GET_LINE_REQUEST_TYPE 0xa1 +#define GET_LINE_REQUEST 0x21 + +#define VENDOR_WRITE_REQUEST_TYPE 0x40 +#define VENDOR_WRITE_REQUEST 0x01 + +#define VENDOR_READ_REQUEST_TYPE 0xc0 +#define VENDOR_READ_REQUEST 0x01 + +#define UART_STATE 0x08 +#define UART_STATE_TRANSIENT_MASK 0x74 +#define UART_DCD 0x01 +#define UART_DSR 0x02 +#define UART_BREAK_ERROR 0x04 +#define UART_RING 0x08 +#define UART_FRAME_ERROR 0x10 +#define UART_PARITY_ERROR 0x20 +#define UART_OVERRUN_ERROR 0x40 +#define UART_CTS 0x80 + + +enum pl2303_type { + type_0, /* don't know the difference between type 0 and */ + type_1, /* type 1, until someone from prolific tells us... */ + HX, /* HX version of the pl2303 chip */ +}; + +struct pl2303_serial_private { + enum pl2303_type type; +}; + +struct pl2303_private { + spinlock_t lock; + u8 line_control; + u8 line_status; + + u8 line_settings[7]; +}; + +static int pl2303_vendor_read(__u16 value, __u16 index, + struct usb_serial *serial, unsigned char *buf) +{ + int res = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + VENDOR_READ_REQUEST, VENDOR_READ_REQUEST_TYPE, + value, index, buf, 1, 100); + dev_dbg(&serial->interface->dev, "0x%x:0x%x:0x%x:0x%x %d - %x\n", + VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, value, index, + res, buf[0]); + return res; +} + +static int pl2303_vendor_write(__u16 value, __u16 index, + struct usb_serial *serial) +{ + int res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + VENDOR_WRITE_REQUEST, VENDOR_WRITE_REQUEST_TYPE, + value, index, NULL, 0, 100); + dev_dbg(&serial->interface->dev, "0x%x:0x%x:0x%x:0x%x %d\n", + VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, value, index, + res); + return res; +} + +static int pl2303_startup(struct usb_serial *serial) +{ + struct pl2303_serial_private *spriv; + enum pl2303_type type = type_0; + unsigned char *buf; + + spriv = kzalloc(sizeof(*spriv), GFP_KERNEL); + if (!spriv) + return -ENOMEM; + + buf = kmalloc(10, GFP_KERNEL); + if (!buf) { + kfree(spriv); + return -ENOMEM; + } + + if (serial->dev->descriptor.bDeviceClass == 0x02) + type = type_0; + else if (serial->dev->descriptor.bMaxPacketSize0 == 0x40) + type = HX; + else if (serial->dev->descriptor.bDeviceClass == 0x00) + type = type_1; + else if (serial->dev->descriptor.bDeviceClass == 0xFF) + type = type_1; + dev_dbg(&serial->interface->dev, "device type: %d\n", type); + + spriv->type = type; + usb_set_serial_data(serial, spriv); + + pl2303_vendor_read(0x8484, 0, serial, buf); + pl2303_vendor_write(0x0404, 0, serial); + pl2303_vendor_read(0x8484, 0, serial, buf); + pl2303_vendor_read(0x8383, 0, serial, buf); + pl2303_vendor_read(0x8484, 0, serial, buf); + pl2303_vendor_write(0x0404, 1, serial); + pl2303_vendor_read(0x8484, 0, serial, buf); + pl2303_vendor_read(0x8383, 0, serial, buf); + pl2303_vendor_write(0, 1, serial); + pl2303_vendor_write(1, 0, serial); + if (type == HX) + pl2303_vendor_write(2, 0x44, serial); + else + pl2303_vendor_write(2, 0x24, serial); + + kfree(buf); + return 0; +} + +static void pl2303_release(struct usb_serial *serial) +{ + struct pl2303_serial_private *spriv; + + spriv = usb_get_serial_data(serial); + kfree(spriv); +} + +static int pl2303_port_probe(struct usb_serial_port *port) +{ + struct pl2303_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int pl2303_port_remove(struct usb_serial_port *port) +{ + struct pl2303_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) +{ + struct usb_device *dev = port->serial->dev; + int retval; + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, + value, 0, NULL, 0, 100); + dev_dbg(&port->dev, "%s - value = %d, retval = %d\n", __func__, + value, retval); + return retval; +} + +static void pl2303_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct pl2303_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int cflag; + unsigned char *buf; + int baud; + int i; + u8 control; + const int baud_sup[] = { 75, 150, 300, 600, 1200, 1800, 2400, 3600, + 4800, 7200, 9600, 14400, 19200, 28800, 38400, + 57600, 115200, 230400, 460800, 614400, + 921600, 1228800, 2457600, 3000000, 6000000 }; + int baud_floor, baud_ceil; + int k; + + if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) + return; + + cflag = tty->termios.c_cflag; + + buf = kzalloc(7, GFP_KERNEL); + if (!buf) { + dev_err(&port->dev, "%s - out of memory.\n", __func__); + /* Report back no change occurred */ + if (old_termios) + tty->termios = *old_termios; + return; + } + + i = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, + 0, 0, buf, 7, 100); + dev_dbg(&port->dev, "0xa1:0x21:0:0 %d - %x %x %x %x %x %x %x\n", i, + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + + switch (cflag & CSIZE) { + case CS5: + buf[6] = 5; + break; + case CS6: + buf[6] = 6; + break; + case CS7: + buf[6] = 7; + break; + default: + case CS8: + buf[6] = 8; + break; + } + dev_dbg(&port->dev, "data bits = %d\n", buf[6]); + + /* For reference buf[0]:buf[3] baud rate value */ + /* NOTE: Only the values defined in baud_sup are supported ! + * => if unsupported values are set, the PL2303 seems to use + * 9600 baud (at least my PL2303X always does) + */ + baud = tty_get_baud_rate(tty); + dev_dbg(&port->dev, "baud requested = %d\n", baud); + if (baud) { + /* Set baudrate to nearest supported value */ + for (k=0; k (baud % baud_floor)) + baud = baud_floor; + else + baud = baud_ceil; + } + break; + } + } + if (baud > 1228800) { + /* type_0, type_1 only support up to 1228800 baud */ + if (spriv->type != HX) + baud = 1228800; + else if (baud > 6000000) + baud = 6000000; + } + dev_dbg(&port->dev, "baud set = %d\n", baud); + if (baud <= 115200) { + buf[0] = baud & 0xff; + buf[1] = (baud >> 8) & 0xff; + buf[2] = (baud >> 16) & 0xff; + buf[3] = (baud >> 24) & 0xff; + } else { + /* apparently the formula for higher speeds is: + * baudrate = 12M * 32 / (2^buf[1]) / buf[0] + */ + unsigned tmp = 12*1000*1000*32 / baud; + buf[3] = 0x80; + buf[2] = 0; + buf[1] = (tmp >= 256); + while (tmp >= 256) { + tmp >>= 2; + buf[1] <<= 1; + } + buf[0] = tmp; + } + } + + /* For reference buf[4]=0 is 1 stop bits */ + /* For reference buf[4]=1 is 1.5 stop bits */ + /* For reference buf[4]=2 is 2 stop bits */ + if (cflag & CSTOPB) { + /* NOTE: Comply with "real" UARTs / RS232: + * use 1.5 instead of 2 stop bits with 5 data bits + */ + if ((cflag & CSIZE) == CS5) { + buf[4] = 1; + dev_dbg(&port->dev, "stop bits = 1.5\n"); + } else { + buf[4] = 2; + dev_dbg(&port->dev, "stop bits = 2\n"); + } + } else { + buf[4] = 0; + dev_dbg(&port->dev, "stop bits = 1\n"); + } + + if (cflag & PARENB) { + /* For reference buf[5]=0 is none parity */ + /* For reference buf[5]=1 is odd parity */ + /* For reference buf[5]=2 is even parity */ + /* For reference buf[5]=3 is mark parity */ + /* For reference buf[5]=4 is space parity */ + if (cflag & PARODD) { + if (cflag & CMSPAR) { + buf[5] = 3; + dev_dbg(&port->dev, "parity = mark\n"); + } else { + buf[5] = 1; + dev_dbg(&port->dev, "parity = odd\n"); + } + } else { + if (cflag & CMSPAR) { + buf[5] = 4; + dev_dbg(&port->dev, "parity = space\n"); + } else { + buf[5] = 2; + dev_dbg(&port->dev, "parity = even\n"); + } + } + } else { + buf[5] = 0; + dev_dbg(&port->dev, "parity = none\n"); + } + + /* + * Some PL2303 are known to lose bytes if you change serial settings + * even to the same values as before. Thus we actually need to filter + * in this specific case. + * + * Note that the tty_termios_hw_change check above is not sufficient + * as a previously requested baud rate may differ from the one + * actually used (and stored in old_termios). + * + * NOTE: No additional locking needed for line_settings as it is + * only used in set_termios, which is serialised against itself. + */ + if (!old_termios || memcmp(buf, priv->line_settings, 7)) { + i = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, + 0, 0, buf, 7, 100); + + dev_dbg(&port->dev, "0x21:0x20:0:0 %d\n", i); + + if (i == 7) + memcpy(priv->line_settings, buf, 7); + } + + /* change control lines if we are switching to or from B0 */ + spin_lock_irqsave(&priv->lock, flags); + control = priv->line_control; + if ((cflag & CBAUD) == B0) + priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + priv->line_control |= (CONTROL_DTR | CONTROL_RTS); + if (control != priv->line_control) { + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + pl2303_set_control_lines(port, control); + } else { + spin_unlock_irqrestore(&priv->lock, flags); + } + + buf[0] = buf[1] = buf[2] = buf[3] = buf[4] = buf[5] = buf[6] = 0; + + i = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, + 0, 0, buf, 7, 100); + dev_dbg(&port->dev, "0xa1:0x21:0:0 %d - %x %x %x %x %x %x %x\n", i, + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + + if (cflag & CRTSCTS) { + if (spriv->type == HX) + pl2303_vendor_write(0x0, 0x61, serial); + else + pl2303_vendor_write(0x0, 0x41, serial); + } else { + pl2303_vendor_write(0x0, 0x0, serial); + } + + /* Save resulting baud rate */ + if (baud) + tty_encode_baud_rate(tty, baud, baud); + + kfree(buf); +} + +static void pl2303_dtr_rts(struct usb_serial_port *port, int on) +{ + struct pl2303_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 control; + + spin_lock_irqsave(&priv->lock, flags); + /* Change DTR and RTS */ + if (on) + priv->line_control |= (CONTROL_DTR | CONTROL_RTS); + else + priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + pl2303_set_control_lines(port, control); +} + +static void pl2303_close(struct usb_serial_port *port) +{ + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); +} + +static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int result; + + if (spriv->type != HX) { + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + } else { + /* reset upstream data pipes */ + pl2303_vendor_write(8, 0, serial); + pl2303_vendor_write(9, 0, serial); + } + + /* Setup termios */ + if (tty) + pl2303_set_termios(tty, port, NULL); + + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "%s - failed submitting interrupt urb," + " error %d\n", __func__, result); + return result; + } + + result = usb_serial_generic_open(tty, port); + if (result) { + usb_kill_urb(port->interrupt_in_urb); + return result; + } + + port->port.drain_delay = 256; + return 0; +} + +static int pl2303_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct pl2303_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 control; + int ret; + + spin_lock_irqsave(&priv->lock, flags); + if (set & TIOCM_RTS) + priv->line_control |= CONTROL_RTS; + if (set & TIOCM_DTR) + priv->line_control |= CONTROL_DTR; + if (clear & TIOCM_RTS) + priv->line_control &= ~CONTROL_RTS; + if (clear & TIOCM_DTR) + priv->line_control &= ~CONTROL_DTR; + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + + ret = pl2303_set_control_lines(port, control); + if (ret) + return usb_translate_errors(ret); + + return 0; +} + +static int pl2303_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct pl2303_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int mcr; + unsigned int status; + unsigned int result; + + spin_lock_irqsave(&priv->lock, flags); + mcr = priv->line_control; + status = priv->line_status; + spin_unlock_irqrestore(&priv->lock, flags); + + result = ((mcr & CONTROL_DTR) ? TIOCM_DTR : 0) + | ((mcr & CONTROL_RTS) ? TIOCM_RTS : 0) + | ((status & UART_CTS) ? TIOCM_CTS : 0) + | ((status & UART_DSR) ? TIOCM_DSR : 0) + | ((status & UART_RING) ? TIOCM_RI : 0) + | ((status & UART_DCD) ? TIOCM_CD : 0); + + dev_dbg(&port->dev, "%s - result = %x\n", __func__, result); + + return result; +} + +static int pl2303_carrier_raised(struct usb_serial_port *port) +{ + struct pl2303_private *priv = usb_get_serial_port_data(port); + if (priv->line_status & UART_DCD) + return 1; + return 0; +} + +static int pl2303_tiocmiwait(struct tty_struct *tty, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct pl2303_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int prevstatus; + unsigned int status; + unsigned int changed; + + spin_lock_irqsave(&priv->lock, flags); + prevstatus = priv->line_status; + spin_unlock_irqrestore(&priv->lock, flags); + + while (1) { + interruptible_sleep_on(&port->port.delta_msr_wait); + /* see if a signal did it */ + if (signal_pending(current)) + return -ERESTARTSYS; + + if (port->serial->disconnected) + return -EIO; + + spin_lock_irqsave(&priv->lock, flags); + status = priv->line_status; + spin_unlock_irqrestore(&priv->lock, flags); + + changed = prevstatus ^ status; + + if (((arg & TIOCM_RNG) && (changed & UART_RING)) || + ((arg & TIOCM_DSR) && (changed & UART_DSR)) || + ((arg & TIOCM_CD) && (changed & UART_DCD)) || + ((arg & TIOCM_CTS) && (changed & UART_CTS))) { + return 0; + } + prevstatus = status; + } + /* NOTREACHED */ + return 0; +} + +static int pl2303_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct serial_struct ser; + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s cmd = 0x%04x\n", __func__, cmd); + + switch (cmd) { + case TIOCGSERIAL: + memset(&ser, 0, sizeof ser); + ser.type = PORT_16654; + ser.line = port->serial->minor; + ser.port = port->number; + ser.baud_base = 460800; + + if (copy_to_user((void __user *)arg, &ser, sizeof ser)) + return -EFAULT; + + return 0; + default: + dev_dbg(&port->dev, "%s not supported = 0x%04x\n", __func__, cmd); + break; + } + return -ENOIOCTLCMD; +} + +static void pl2303_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + u16 state; + int result; + + if (break_state == 0) + state = BREAK_OFF; + else + state = BREAK_ON; + dev_dbg(&port->dev, "%s - turning break %s\n", __func__, + state == BREAK_OFF ? "off" : "on"); + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + BREAK_REQUEST, BREAK_REQUEST_TYPE, state, + 0, NULL, 0, 100); + if (result) + dev_err(&port->dev, "error sending break = %d\n", result); +} + +static void pl2303_update_line_status(struct usb_serial_port *port, + unsigned char *data, + unsigned int actual_length) +{ + + struct pl2303_private *priv = usb_get_serial_port_data(port); + struct tty_struct *tty; + unsigned long flags; + u8 status_idx = UART_STATE; + u8 length = UART_STATE + 1; + u8 prev_line_status; + u16 idv, idp; + + idv = le16_to_cpu(port->serial->dev->descriptor.idVendor); + idp = le16_to_cpu(port->serial->dev->descriptor.idProduct); + + + if (idv == SIEMENS_VENDOR_ID) { + if (idp == SIEMENS_PRODUCT_ID_X65 || + idp == SIEMENS_PRODUCT_ID_SX1 || + idp == SIEMENS_PRODUCT_ID_X75) { + + length = 1; + status_idx = 0; + } + } + + if (actual_length < length) + return; + + /* Save off the uart status for others to look at */ + spin_lock_irqsave(&priv->lock, flags); + prev_line_status = priv->line_status; + priv->line_status = data[status_idx]; + spin_unlock_irqrestore(&priv->lock, flags); + if (priv->line_status & UART_BREAK_ERROR) + usb_serial_handle_break(port); + wake_up_interruptible(&port->port.delta_msr_wait); + + tty = tty_port_tty_get(&port->port); + if (!tty) + return; + if ((priv->line_status ^ prev_line_status) & UART_DCD) + usb_serial_handle_dcd_change(port, tty, + priv->line_status & UART_DCD); + tty_kref_put(tty); +} + +static void pl2303_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int actual_length = urb->actual_length; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_serial_debug_data(&port->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + pl2303_update_line_status(port, data, actual_length); + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, retval); +} + +static void pl2303_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct pl2303_private *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + char tty_flag = TTY_NORMAL; + unsigned long flags; + u8 line_status; + int i; + + /* update line status */ + spin_lock_irqsave(&priv->lock, flags); + line_status = priv->line_status; + priv->line_status &= ~UART_STATE_TRANSIENT_MASK; + spin_unlock_irqrestore(&priv->lock, flags); + wake_up_interruptible(&port->port.delta_msr_wait); + + if (!urb->actual_length) + return; + + /* break takes precedence over parity, */ + /* which takes precedence over framing errors */ + if (line_status & UART_BREAK_ERROR) + tty_flag = TTY_BREAK; + else if (line_status & UART_PARITY_ERROR) + tty_flag = TTY_PARITY; + else if (line_status & UART_FRAME_ERROR) + tty_flag = TTY_FRAME; + dev_dbg(&port->dev, "%s - tty_flag = %d\n", __func__, tty_flag); + + /* overrun is special, not associated with a char */ + if (line_status & UART_OVERRUN_ERROR) + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + + if (port->port.console && port->sysrq) { + for (i = 0; i < urb->actual_length; ++i) + if (!usb_serial_handle_sysrq_char(port, data[i])) + tty_insert_flip_char(&port->port, data[i], + tty_flag); + } else { + tty_insert_flip_string_fixed_flag(&port->port, data, tty_flag, + urb->actual_length); + } + + tty_flip_buffer_push(&port->port); +} + +/* All of the device info needed for the PL2303 SIO serial converter */ +static struct usb_serial_driver pl2303_device = { + .driver = { + .owner = THIS_MODULE, + .name = "pl2303", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_in_size = 256, + .bulk_out_size = 256, + .open = pl2303_open, + .close = pl2303_close, + .dtr_rts = pl2303_dtr_rts, + .carrier_raised = pl2303_carrier_raised, + .ioctl = pl2303_ioctl, + .break_ctl = pl2303_break_ctl, + .set_termios = pl2303_set_termios, + .tiocmget = pl2303_tiocmget, + .tiocmset = pl2303_tiocmset, + .tiocmiwait = pl2303_tiocmiwait, + .process_read_urb = pl2303_process_read_urb, + .read_int_callback = pl2303_read_int_callback, + .attach = pl2303_startup, + .release = pl2303_release, + .port_probe = pl2303_port_probe, + .port_remove = pl2303_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &pl2303_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h new file mode 100644 index 00000000..71fd9da1 --- /dev/null +++ b/drivers/usb/serial/pl2303.h @@ -0,0 +1,155 @@ +/* + * Prolific PL2303 USB to serial adaptor driver header file + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#define BENQ_VENDOR_ID 0x04a5 +#define BENQ_PRODUCT_ID_S81 0x4027 + +#define PL2303_VENDOR_ID 0x067b +#define PL2303_PRODUCT_ID 0x2303 +#define PL2303_PRODUCT_ID_RSAQ2 0x04bb +#define PL2303_PRODUCT_ID_DCU11 0x1234 +#define PL2303_PRODUCT_ID_PHAROS 0xaaa0 +#define PL2303_PRODUCT_ID_RSAQ3 0xaaa2 +#define PL2303_PRODUCT_ID_ALDIGA 0x0611 +#define PL2303_PRODUCT_ID_MMX 0x0612 +#define PL2303_PRODUCT_ID_GPRS 0x0609 +#define PL2303_PRODUCT_ID_HCR331 0x331a +#define PL2303_PRODUCT_ID_MOTOROLA 0x0307 +#define PL2303_PRODUCT_ID_ZTEK 0xe1f1 + +#define ATEN_VENDOR_ID 0x0557 +#define ATEN_VENDOR_ID2 0x0547 +#define ATEN_PRODUCT_ID 0x2008 + +#define IODATA_VENDOR_ID 0x04bb +#define IODATA_PRODUCT_ID 0x0a03 +#define IODATA_PRODUCT_ID_RSAQ5 0x0a0e + +#define ELCOM_VENDOR_ID 0x056e +#define ELCOM_PRODUCT_ID 0x5003 +#define ELCOM_PRODUCT_ID_UCSGT 0x5004 + +#define ITEGNO_VENDOR_ID 0x0eba +#define ITEGNO_PRODUCT_ID 0x1080 +#define ITEGNO_PRODUCT_ID_2080 0x2080 + +#define MA620_VENDOR_ID 0x0df7 +#define MA620_PRODUCT_ID 0x0620 + +#define RATOC_VENDOR_ID 0x0584 +#define RATOC_PRODUCT_ID 0xb000 + +#define TRIPP_VENDOR_ID 0x2478 +#define TRIPP_PRODUCT_ID 0x2008 + +#define RADIOSHACK_VENDOR_ID 0x1453 +#define RADIOSHACK_PRODUCT_ID 0x4026 + +#define DCU10_VENDOR_ID 0x0731 +#define DCU10_PRODUCT_ID 0x0528 + +#define SITECOM_VENDOR_ID 0x6189 +#define SITECOM_PRODUCT_ID 0x2068 + +/* Alcatel OT535/735 USB cable */ +#define ALCATEL_VENDOR_ID 0x11f7 +#define ALCATEL_PRODUCT_ID 0x02df + +/* Samsung I330 phone cradle */ +#define SAMSUNG_VENDOR_ID 0x04e8 +#define SAMSUNG_PRODUCT_ID 0x8001 + +#define SIEMENS_VENDOR_ID 0x11f5 +#define SIEMENS_PRODUCT_ID_SX1 0x0001 +#define SIEMENS_PRODUCT_ID_X65 0x0003 +#define SIEMENS_PRODUCT_ID_X75 0x0004 +#define SIEMENS_PRODUCT_ID_EF81 0x0005 + +#define SYNTECH_VENDOR_ID 0x0745 +#define SYNTECH_PRODUCT_ID 0x0001 + +/* Nokia CA-42 Cable */ +#define NOKIA_CA42_VENDOR_ID 0x078b +#define NOKIA_CA42_PRODUCT_ID 0x1234 + +/* CA-42 CLONE Cable www.ca-42.com chipset: Prolific Technology Inc */ +#define CA_42_CA42_VENDOR_ID 0x10b5 +#define CA_42_CA42_PRODUCT_ID 0xac70 + +#define SAGEM_VENDOR_ID 0x079b +#define SAGEM_PRODUCT_ID 0x0027 + +/* Leadtek GPS 9531 (ID 0413:2101) */ +#define LEADTEK_VENDOR_ID 0x0413 +#define LEADTEK_9531_PRODUCT_ID 0x2101 + +/* USB GSM cable from Speed Dragon Multimedia, Ltd */ +#define SPEEDDRAGON_VENDOR_ID 0x0e55 +#define SPEEDDRAGON_PRODUCT_ID 0x110b + +/* DATAPILOT Universal-2 Phone Cable */ +#define DATAPILOT_U2_VENDOR_ID 0x0731 +#define DATAPILOT_U2_PRODUCT_ID 0x2003 + +/* Belkin "F5U257" Serial Adapter */ +#define BELKIN_VENDOR_ID 0x050d +#define BELKIN_PRODUCT_ID 0x0257 + +/* Alcor Micro Corp. USB 2.0 TO RS-232 */ +#define ALCOR_VENDOR_ID 0x058F +#define ALCOR_PRODUCT_ID 0x9720 + +/* Willcom WS002IN Data Driver (by NetIndex Inc.) */ +#define WS002IN_VENDOR_ID 0x11f6 +#define WS002IN_PRODUCT_ID 0x2001 + +/* Corega CG-USBRS232R Serial Adapter */ +#define COREGA_VENDOR_ID 0x07aa +#define COREGA_PRODUCT_ID 0x002a + +/* Y.C. Cable U.S.A., Inc - USB to RS-232 */ +#define YCCABLE_VENDOR_ID 0x05ad +#define YCCABLE_PRODUCT_ID 0x0fba + +/* "Superial" USB - Serial */ +#define SUPERIAL_VENDOR_ID 0x5372 +#define SUPERIAL_PRODUCT_ID 0x2303 + +/* Hewlett-Packard POS Pole Displays */ +#define HP_VENDOR_ID 0x03f0 +#define HP_LD960_PRODUCT_ID 0x0b39 +#define HP_LCM220_PRODUCT_ID 0x3139 +#define HP_LCM960_PRODUCT_ID 0x3239 +#define HP_LD220_PRODUCT_ID 0x3524 + +/* Cressi Edy (diving computer) PC interface */ +#define CRESSI_VENDOR_ID 0x04b8 +#define CRESSI_EDY_PRODUCT_ID 0x0521 + +/* Zeagle dive computer interface */ +#define ZEAGLE_VENDOR_ID 0x04b8 +#define ZEAGLE_N2ITION3_PRODUCT_ID 0x0522 + +/* Sony, USB data cable for CMD-Jxx mobile phones */ +#define SONY_VENDOR_ID 0x054c +#define SONY_QN3USB_PRODUCT_ID 0x0437 + +/* Sanwa KB-USB2 multimeter cable (ID: 11ad:0001) */ +#define SANWA_VENDOR_ID 0x11ad +#define SANWA_PRODUCT_ID 0x0001 + +/* ADLINK ND-6530 RS232,RS485 and RS422 adapter */ +#define ADLINK_VENDOR_ID 0x0b63 +#define ADLINK_ND6530_PRODUCT_ID 0x6530 + +/* SMART USB Serial Adapter */ +#define SMART_VENDOR_ID 0x0b8c +#define SMART_PRODUCT_ID 0x2303 + diff --git a/drivers/usb/serial/qcaux.c b/drivers/usb/serial/qcaux.c new file mode 100644 index 00000000..31f81c3c --- /dev/null +++ b/drivers/usb/serial/qcaux.c @@ -0,0 +1,91 @@ +/* + * Qualcomm USB Auxiliary Serial Port driver + * + * Copyright (C) 2008 Greg Kroah-Hartman + * Copyright (C) 2010 Dan Williams + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Devices listed here usually provide a CDC ACM port on which normal modem + * AT commands and PPP can be used. But when that port is in-use by PPP it + * cannot be used simultaneously for status or signal strength. Instead, the + * ports here can be queried for that information using the Qualcomm DM + * protocol. + */ + +#include +#include +#include +#include +#include +#include + +/* NOTE: for now, only use this driver for devices that provide a CDC-ACM port + * for normal AT commands, but also provide secondary USB interfaces for the + * QCDM-capable ports. Devices that do not provide a CDC-ACM port should + * probably be driven by option.ko. + */ + +/* UTStarcom/Pantech/Curitel devices */ +#define UTSTARCOM_VENDOR_ID 0x106c +#define UTSTARCOM_PRODUCT_PC5740 0x3701 +#define UTSTARCOM_PRODUCT_PC5750 0x3702 /* aka Pantech PX-500 */ +#define UTSTARCOM_PRODUCT_UM150 0x3711 +#define UTSTARCOM_PRODUCT_UM175_V1 0x3712 +#define UTSTARCOM_PRODUCT_UM175_V2 0x3714 +#define UTSTARCOM_PRODUCT_UM175_ALLTEL 0x3715 + +/* CMOTECH devices */ +#define CMOTECH_VENDOR_ID 0x16d8 +#define CMOTECH_PRODUCT_CDU550 0x5553 +#define CMOTECH_PRODUCT_CDX650 0x6512 + +/* LG devices */ +#define LG_VENDOR_ID 0x1004 +#define LG_PRODUCT_VX4400_6000 0x6000 /* VX4400/VX6000/Rumor */ + +/* Sanyo devices */ +#define SANYO_VENDOR_ID 0x0474 +#define SANYO_PRODUCT_KATANA_LX 0x0754 /* SCP-3800 (Katana LX) */ + +/* Samsung devices */ +#define SAMSUNG_VENDOR_ID 0x04e8 +#define SAMSUNG_PRODUCT_U520 0x6640 /* SCH-U520 */ + +static struct usb_device_id id_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_PC5740, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_PC5750, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_UM150, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_UM175_V1, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_UM175_V2, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, UTSTARCOM_PRODUCT_UM175_ALLTEL, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDU550, 0xff, 0xff, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CDX650, 0xff, 0xff, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(LG_VENDOR_ID, LG_PRODUCT_VX4400_6000, 0xff, 0xff, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(SANYO_VENDOR_ID, SANYO_PRODUCT_KATANA_LX, 0xff, 0xff, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_U520, 0xff, 0x00, 0x00) }, + { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xfd, 0xff) }, /* NMEA */ + { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xfe, 0xff) }, /* WMC */ + { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xff, 0xff) }, /* DIAG */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1fac, 0x0151, 0xff, 0xff, 0xff) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver qcaux_device = { + .driver = { + .owner = THIS_MODULE, + .name = "qcaux", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &qcaux_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c new file mode 100644 index 00000000..43d93dbf --- /dev/null +++ b/drivers/usb/serial/qcserial.c @@ -0,0 +1,365 @@ +/* + * Qualcomm Serial USB driver + * + * Copyright (c) 2008 QUALCOMM Incorporated. + * Copyright (c) 2009 Greg Kroah-Hartman + * Copyright (c) 2009 Novell Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include "usb-wwan.h" + +#define DRIVER_AUTHOR "Qualcomm Inc" +#define DRIVER_DESC "Qualcomm USB Serial driver" + +#define DEVICE_G1K(v, p) \ + USB_DEVICE(v, p), .driver_info = 1 + +static const struct usb_device_id id_table[] = { + /* Gobi 1000 devices */ + {DEVICE_G1K(0x05c6, 0x9211)}, /* Acer Gobi QDL device */ + {DEVICE_G1K(0x05c6, 0x9212)}, /* Acer Gobi Modem Device */ + {DEVICE_G1K(0x03f0, 0x1f1d)}, /* HP un2400 Gobi Modem Device */ + {DEVICE_G1K(0x03f0, 0x201d)}, /* HP un2400 Gobi QDL Device */ + {DEVICE_G1K(0x04da, 0x250d)}, /* Panasonic Gobi Modem device */ + {DEVICE_G1K(0x04da, 0x250c)}, /* Panasonic Gobi QDL device */ + {DEVICE_G1K(0x413c, 0x8172)}, /* Dell Gobi Modem device */ + {DEVICE_G1K(0x413c, 0x8171)}, /* Dell Gobi QDL device */ + {DEVICE_G1K(0x1410, 0xa001)}, /* Novatel/Verizon USB-1000 */ + {DEVICE_G1K(0x1410, 0xa002)}, /* Novatel Gobi Modem device */ + {DEVICE_G1K(0x1410, 0xa003)}, /* Novatel Gobi Modem device */ + {DEVICE_G1K(0x1410, 0xa004)}, /* Novatel Gobi Modem device */ + {DEVICE_G1K(0x1410, 0xa005)}, /* Novatel Gobi Modem device */ + {DEVICE_G1K(0x1410, 0xa006)}, /* Novatel Gobi Modem device */ + {DEVICE_G1K(0x1410, 0xa007)}, /* Novatel Gobi Modem device */ + {DEVICE_G1K(0x1410, 0xa008)}, /* Novatel Gobi QDL device */ + {DEVICE_G1K(0x0b05, 0x1776)}, /* Asus Gobi Modem device */ + {DEVICE_G1K(0x0b05, 0x1774)}, /* Asus Gobi QDL device */ + {DEVICE_G1K(0x19d2, 0xfff3)}, /* ONDA Gobi Modem device */ + {DEVICE_G1K(0x19d2, 0xfff2)}, /* ONDA Gobi QDL device */ + {DEVICE_G1K(0x1557, 0x0a80)}, /* OQO Gobi QDL device */ + {DEVICE_G1K(0x05c6, 0x9001)}, /* Generic Gobi Modem device */ + {DEVICE_G1K(0x05c6, 0x9002)}, /* Generic Gobi Modem device */ + {DEVICE_G1K(0x05c6, 0x9202)}, /* Generic Gobi Modem device */ + {DEVICE_G1K(0x05c6, 0x9203)}, /* Generic Gobi Modem device */ + {DEVICE_G1K(0x05c6, 0x9222)}, /* Generic Gobi Modem device */ + {DEVICE_G1K(0x05c6, 0x9008)}, /* Generic Gobi QDL device */ + {DEVICE_G1K(0x05c6, 0x9009)}, /* Generic Gobi Modem device */ + {DEVICE_G1K(0x05c6, 0x9201)}, /* Generic Gobi QDL device */ + {DEVICE_G1K(0x05c6, 0x9221)}, /* Generic Gobi QDL device */ + {DEVICE_G1K(0x05c6, 0x9231)}, /* Generic Gobi QDL device */ + {DEVICE_G1K(0x1f45, 0x0001)}, /* Unknown Gobi QDL device */ + {DEVICE_G1K(0x1bc7, 0x900e)}, /* Telit Gobi QDL device */ + + /* Gobi 2000 devices */ + {USB_DEVICE(0x1410, 0xa010)}, /* Novatel Gobi 2000 QDL device */ + {USB_DEVICE(0x1410, 0xa011)}, /* Novatel Gobi 2000 QDL device */ + {USB_DEVICE(0x1410, 0xa012)}, /* Novatel Gobi 2000 QDL device */ + {USB_DEVICE(0x1410, 0xa013)}, /* Novatel Gobi 2000 QDL device */ + {USB_DEVICE(0x1410, 0xa014)}, /* Novatel Gobi 2000 QDL device */ + {USB_DEVICE(0x413c, 0x8185)}, /* Dell Gobi 2000 QDL device (N0218, VU936) */ + {USB_DEVICE(0x413c, 0x8186)}, /* Dell Gobi 2000 Modem device (N0218, VU936) */ + {USB_DEVICE(0x05c6, 0x9208)}, /* Generic Gobi 2000 QDL device */ + {USB_DEVICE(0x05c6, 0x920b)}, /* Generic Gobi 2000 Modem device */ + {USB_DEVICE(0x05c6, 0x9224)}, /* Sony Gobi 2000 QDL device (N0279, VU730) */ + {USB_DEVICE(0x05c6, 0x9225)}, /* Sony Gobi 2000 Modem device (N0279, VU730) */ + {USB_DEVICE(0x05c6, 0x9244)}, /* Samsung Gobi 2000 QDL device (VL176) */ + {USB_DEVICE(0x05c6, 0x9245)}, /* Samsung Gobi 2000 Modem device (VL176) */ + {USB_DEVICE(0x03f0, 0x241d)}, /* HP Gobi 2000 QDL device (VP412) */ + {USB_DEVICE(0x03f0, 0x251d)}, /* HP Gobi 2000 Modem device (VP412) */ + {USB_DEVICE(0x05c6, 0x9214)}, /* Acer Gobi 2000 QDL device (VP413) */ + {USB_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */ + {USB_DEVICE(0x05c6, 0x9264)}, /* Asus Gobi 2000 QDL device (VR305) */ + {USB_DEVICE(0x05c6, 0x9265)}, /* Asus Gobi 2000 Modem device (VR305) */ + {USB_DEVICE(0x05c6, 0x9234)}, /* Top Global Gobi 2000 QDL device (VR306) */ + {USB_DEVICE(0x05c6, 0x9235)}, /* Top Global Gobi 2000 Modem device (VR306) */ + {USB_DEVICE(0x05c6, 0x9274)}, /* iRex Technologies Gobi 2000 QDL device (VR307) */ + {USB_DEVICE(0x05c6, 0x9275)}, /* iRex Technologies Gobi 2000 Modem device (VR307) */ + {USB_DEVICE(0x1199, 0x9000)}, /* Sierra Wireless Gobi 2000 QDL device (VT773) */ + {USB_DEVICE(0x1199, 0x9001)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9002)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9003)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9004)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9005)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9006)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9007)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9008)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9009)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x900a)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */ + {USB_DEVICE(0x1199, 0x9011)}, /* Sierra Wireless Gobi 2000 Modem device (MC8305) */ + {USB_DEVICE(0x16d8, 0x8001)}, /* CMDTech Gobi 2000 QDL device (VU922) */ + {USB_DEVICE(0x16d8, 0x8002)}, /* CMDTech Gobi 2000 Modem device (VU922) */ + {USB_DEVICE(0x05c6, 0x9204)}, /* Gobi 2000 QDL device */ + {USB_DEVICE(0x05c6, 0x9205)}, /* Gobi 2000 Modem device */ + + /* Gobi 3000 devices */ + {USB_DEVICE(0x03f0, 0x371d)}, /* HP un2430 Gobi 3000 QDL */ + {USB_DEVICE(0x05c6, 0x920c)}, /* Gobi 3000 QDL */ + {USB_DEVICE(0x05c6, 0x920d)}, /* Gobi 3000 Composite */ + {USB_DEVICE(0x1410, 0xa020)}, /* Novatel Gobi 3000 QDL */ + {USB_DEVICE(0x1410, 0xa021)}, /* Novatel Gobi 3000 Composite */ + {USB_DEVICE(0x413c, 0x8193)}, /* Dell Gobi 3000 QDL */ + {USB_DEVICE(0x413c, 0x8194)}, /* Dell Gobi 3000 Composite */ + {USB_DEVICE(0x1199, 0x68a4)}, /* Sierra Wireless QDL */ + {USB_DEVICE(0x1199, 0x68a5)}, /* Sierra Wireless Modem */ + {USB_DEVICE(0x1199, 0x68a8)}, /* Sierra Wireless QDL */ + {USB_DEVICE(0x1199, 0x68a9)}, /* Sierra Wireless Modem */ + {USB_DEVICE(0x1199, 0x9010)}, /* Sierra Wireless Gobi 3000 QDL */ + {USB_DEVICE(0x1199, 0x9012)}, /* Sierra Wireless Gobi 3000 QDL */ + {USB_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */ + {USB_DEVICE(0x1199, 0x9014)}, /* Sierra Wireless Gobi 3000 QDL */ + {USB_DEVICE(0x1199, 0x9015)}, /* Sierra Wireless Gobi 3000 Modem device */ + {USB_DEVICE(0x1199, 0x9018)}, /* Sierra Wireless Gobi 3000 QDL */ + {USB_DEVICE(0x1199, 0x9019)}, /* Sierra Wireless Gobi 3000 Modem device */ + {USB_DEVICE(0x1199, 0x901b)}, /* Sierra Wireless MC7770 */ + {USB_DEVICE(0x12D1, 0x14F0)}, /* Sony Gobi 3000 QDL */ + {USB_DEVICE(0x12D1, 0x14F1)}, /* Sony Gobi 3000 Composite */ + {USB_DEVICE(0x0AF0, 0x8120)}, /* Option GTM681W */ + + /* non Gobi Qualcomm serial devices */ + {USB_DEVICE_INTERFACE_NUMBER(0x0f3d, 0x68a2, 0)}, /* Sierra Wireless MC7700 Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x0f3d, 0x68a2, 2)}, /* Sierra Wireless MC7700 NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x0f3d, 0x68a2, 3)}, /* Sierra Wireless MC7700 Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x114f, 0x68a2, 0)}, /* Sierra Wireless MC7750 Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x114f, 0x68a2, 2)}, /* Sierra Wireless MC7750 NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x114f, 0x68a2, 3)}, /* Sierra Wireless MC7750 Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x68a2, 0)}, /* Sierra Wireless MC7710 Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x68a2, 2)}, /* Sierra Wireless MC7710 NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x68a2, 3)}, /* Sierra Wireless MC7710 Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x68c0, 0)}, /* Sierra Wireless MC73xx Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x68c0, 2)}, /* Sierra Wireless MC73xx NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x68c0, 3)}, /* Sierra Wireless MC73xx Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x901c, 0)}, /* Sierra Wireless EM7700 Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x901c, 2)}, /* Sierra Wireless EM7700 NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x901c, 3)}, /* Sierra Wireless EM7700 Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x901f, 0)}, /* Sierra Wireless EM7355 Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x901f, 2)}, /* Sierra Wireless EM7355 NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x901f, 3)}, /* Sierra Wireless EM7355 Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9040, 0)}, /* Sierra Wireless Modem Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9040, 2)}, /* Sierra Wireless Modem NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9040, 3)}, /* Sierra Wireless Modem Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9041, 0)}, /* Sierra Wireless MC7305/MC7355 Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9041, 2)}, /* Sierra Wireless MC7305/MC7355 NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9041, 3)}, /* Sierra Wireless MC7305/MC7355 Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9051, 0)}, /* Netgear AirCard 340U Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9051, 2)}, /* Netgear AirCard 340U NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9051, 3)}, /* Netgear AirCard 340U Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9053, 0)}, /* Sierra Wireless Modem Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9053, 2)}, /* Sierra Wireless Modem NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9053, 3)}, /* Sierra Wireless Modem Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9054, 0)}, /* Sierra Wireless Modem Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9054, 2)}, /* Sierra Wireless Modem NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9054, 3)}, /* Sierra Wireless Modem Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9055, 0)}, /* Netgear AirCard 341U Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9055, 2)}, /* Netgear AirCard 341U NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9055, 3)}, /* Netgear AirCard 341U Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9056, 0)}, /* Sierra Wireless Modem Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9056, 2)}, /* Sierra Wireless Modem NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9056, 3)}, /* Sierra Wireless Modem Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9060, 0)}, /* Sierra Wireless Modem Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9060, 2)}, /* Sierra Wireless Modem NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9060, 3)}, /* Sierra Wireless Modem Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9061, 0)}, /* Sierra Wireless Modem Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9061, 2)}, /* Sierra Wireless Modem NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x1199, 0x9061, 3)}, /* Sierra Wireless Modem Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a2, 0)}, /* Dell Wireless 5806 Gobi(TM) 4G LTE Mobile Broadband Card Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a2, 2)}, /* Dell Wireless 5806 Gobi(TM) 4G LTE Mobile Broadband Card NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a2, 3)}, /* Dell Wireless 5806 Gobi(TM) 4G LTE Mobile Broadband Card Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a3, 0)}, /* Dell Wireless 5570 HSPA+ (42Mbps) Mobile Broadband Card Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a3, 2)}, /* Dell Wireless 5570 HSPA+ (42Mbps) Mobile Broadband Card NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a3, 3)}, /* Dell Wireless 5570 HSPA+ (42Mbps) Mobile Broadband Card Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a4, 0)}, /* Dell Wireless 5570e HSPA+ (42Mbps) Mobile Broadband Card Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a4, 2)}, /* Dell Wireless 5570e HSPA+ (42Mbps) Mobile Broadband Card NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a4, 3)}, /* Dell Wireless 5570e HSPA+ (42Mbps) Mobile Broadband Card Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a8, 0)}, /* Dell Wireless 5808 Gobi(TM) 4G LTE Mobile Broadband Card Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a8, 2)}, /* Dell Wireless 5808 Gobi(TM) 4G LTE Mobile Broadband Card NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a8, 3)}, /* Dell Wireless 5808 Gobi(TM) 4G LTE Mobile Broadband Card Modem */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a9, 0)}, /* Dell Wireless 5808e Gobi(TM) 4G LTE Mobile Broadband Card Device Management */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a9, 2)}, /* Dell Wireless 5808e Gobi(TM) 4G LTE Mobile Broadband Card NMEA */ + {USB_DEVICE_INTERFACE_NUMBER(0x413c, 0x81a9, 3)}, /* Dell Wireless 5808e Gobi(TM) 4G LTE Mobile Broadband Card Modem */ + + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) +{ + struct usb_host_interface *intf = serial->interface->cur_altsetting; + struct device *dev = &serial->dev->dev; + int retval = -ENODEV; + __u8 nintf; + __u8 ifnum; + bool is_gobi1k = id->driver_info ? true : false; + int altsetting = -1; + + dev_dbg(dev, "Is Gobi 1000 = %d\n", is_gobi1k); + + nintf = serial->dev->actconfig->desc.bNumInterfaces; + dev_dbg(dev, "Num Interfaces = %d\n", nintf); + ifnum = intf->desc.bInterfaceNumber; + dev_dbg(dev, "This Interface = %d\n", ifnum); + + if (nintf == 1) { + /* QDL mode */ + /* Gobi 2000 has a single altsetting, older ones have two */ + if (serial->interface->num_altsetting == 2) + intf = &serial->interface->altsetting[1]; + else if (serial->interface->num_altsetting > 2) + goto done; + + if (intf->desc.bNumEndpoints == 2 && + usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) && + usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) { + dev_dbg(dev, "QDL port found\n"); + + if (serial->interface->num_altsetting == 1) + retval = 0; /* Success */ + else + altsetting = 1; + } + goto done; + + } + + /* allow any number of interfaces when doing direct interface match */ + if (id->match_flags & USB_DEVICE_ID_MATCH_INT_NUMBER) { + dev_dbg(dev, "Generic Qualcomm serial interface found\n"); + altsetting = 0; + goto done; + } + + if (nintf < 3 || nintf > 4) { + dev_err(dev, "unknown number of interfaces: %d\n", nintf); + goto done; + } + + /* default to enabling interface */ + altsetting = 0; + + /* Composite mode; don't bind to the QMI/net interface as that + * gets handled by other drivers. + */ + + if (is_gobi1k) { + /* Gobi 1K USB layout: + * 0: DM/DIAG (use libqcdm from ModemManager for communication) + * 1: serial port (doesn't respond) + * 2: AT-capable modem port + * 3: QMI/net + */ + if (ifnum == 0) { + dev_dbg(dev, "Gobi 1K DM/DIAG interface found\n"); + altsetting = 1; + } else if (ifnum == 2) + dev_dbg(dev, "Modem port found\n"); + else + altsetting = -1; + } else { + /* Gobi 2K+ USB layout: + * 0: QMI/net + * 1: DM/DIAG (use libqcdm from ModemManager for communication) + * 2: AT-capable modem port + * 3: NMEA + */ + switch (ifnum) { + case 0: + /* Don't claim the QMI/net interface */ + altsetting = -1; + break; + case 1: + dev_dbg(dev, "Gobi 2K+ DM/DIAG interface found\n"); + break; + case 2: + dev_dbg(dev, "Modem port found\n"); + break; + case 3: + /* + * NMEA (serial line 9600 8N1) + * # echo "\$GPS_START" > /dev/ttyUSBx + * # echo "\$GPS_STOP" > /dev/ttyUSBx + */ + dev_dbg(dev, "Gobi 2K+ NMEA GPS interface found\n"); + break; + } + } + +done: + if (altsetting >= 0) { + retval = usb_set_interface(serial->dev, ifnum, altsetting); + if (retval < 0) { + dev_err(dev, + "Could not set interface, error %d\n", + retval); + retval = -ENODEV; + } + } + + return retval; +} + +static int qc_attach(struct usb_serial *serial) +{ + struct usb_wwan_intf_private *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->susp_lock); + + usb_set_serial_data(serial, data); + + return 0; +} + +static void qc_release(struct usb_serial *serial) +{ + struct usb_wwan_intf_private *priv = usb_get_serial_data(serial); + + usb_set_serial_data(serial, NULL); + kfree(priv); +} + +static struct usb_serial_driver qcdevice = { + .driver = { + .owner = THIS_MODULE, + .name = "qcserial", + }, + .description = "Qualcomm USB modem", + .id_table = id_table, + .num_ports = 1, + .probe = qcprobe, + .open = usb_wwan_open, + .close = usb_wwan_close, + .write = usb_wwan_write, + .write_room = usb_wwan_write_room, + .chars_in_buffer = usb_wwan_chars_in_buffer, + .attach = qc_attach, + .release = qc_release, + .port_probe = usb_wwan_port_probe, + .port_remove = usb_wwan_port_remove, +#ifdef CONFIG_PM + .suspend = usb_wwan_suspend, + .resume = usb_wwan_resume, +#endif +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &qcdevice, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c new file mode 100644 index 00000000..02b08034 --- /dev/null +++ b/drivers/usb/serial/quatech2.c @@ -0,0 +1,1023 @@ +/* + * usb-serial driver for Quatech USB 2 devices + * + * Copyright (C) 2012 Bill Pemberton (wfp5p@virginia.edu) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * + * These devices all have only 1 bulk in and 1 bulk out that is shared + * for all serial ports. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* default urb timeout for usb operations */ +#define QT2_USB_TIMEOUT USB_CTRL_SET_TIMEOUT + +#define QT_OPEN_CLOSE_CHANNEL 0xca +#define QT_SET_GET_DEVICE 0xc2 +#define QT_SET_GET_REGISTER 0xc0 +#define QT_GET_SET_PREBUF_TRIG_LVL 0xcc +#define QT_SET_ATF 0xcd +#define QT_TRANSFER_IN 0xc0 +#define QT_HW_FLOW_CONTROL_MASK 0xc5 +#define QT_SW_FLOW_CONTROL_MASK 0xc6 +#define QT2_BREAK_CONTROL 0xc8 +#define QT2_GET_SET_UART 0xc1 +#define QT2_FLUSH_DEVICE 0xc4 +#define QT2_GET_SET_QMCR 0xe1 +#define QT2_QMCR_RS232 0x40 +#define QT2_QMCR_RS422 0x10 + +#define SERIAL_CRTSCTS ((UART_MCR_RTS << 8) | UART_MSR_CTS) + +#define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) + +/* status bytes for the device */ +#define QT2_CONTROL_BYTE 0x1b +#define QT2_LINE_STATUS 0x00 /* following 1 byte is line status */ +#define QT2_MODEM_STATUS 0x01 /* following 1 byte is modem status */ +#define QT2_XMIT_HOLD 0x02 /* following 2 bytes are ?? */ +#define QT2_CHANGE_PORT 0x03 /* following 1 byte is port to change to */ +#define QT2_REC_FLUSH 0x04 /* no following info */ +#define QT2_XMIT_FLUSH 0x05 /* no following info */ +#define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */ + +#define MAX_BAUD_RATE 921600 +#define DEFAULT_BAUD_RATE 9600 + +#define QT2_WRITE_BUFFER_SIZE 512 /* size of write buffer */ +#define QT2_WRITE_CONTROL_SIZE 5 /* control bytes used for a write */ + +#define DRIVER_DESC "Quatech 2nd gen USB to Serial Driver" + +#define USB_VENDOR_ID_QUATECH 0x061d +#define QUATECH_SSU2_100 0xC120 /* RS232 single port */ +#define QUATECH_DSU2_100 0xC140 /* RS232 dual port */ +#define QUATECH_DSU2_400 0xC150 /* RS232/422/485 dual port */ +#define QUATECH_QSU2_100 0xC160 /* RS232 four port */ +#define QUATECH_QSU2_400 0xC170 /* RS232/422/485 four port */ +#define QUATECH_ESU2_100 0xC1A0 /* RS232 eight port */ +#define QUATECH_ESU2_400 0xC180 /* RS232/422/485 eight port */ + +struct qt2_device_detail { + int product_id; + int num_ports; +}; + +#define QT_DETAILS(prod, ports) \ + .product_id = (prod), \ + .num_ports = (ports) + +static const struct qt2_device_detail qt2_device_details[] = { + {QT_DETAILS(QUATECH_SSU2_100, 1)}, + {QT_DETAILS(QUATECH_DSU2_400, 2)}, + {QT_DETAILS(QUATECH_DSU2_100, 2)}, + {QT_DETAILS(QUATECH_QSU2_400, 4)}, + {QT_DETAILS(QUATECH_QSU2_100, 4)}, + {QT_DETAILS(QUATECH_ESU2_400, 8)}, + {QT_DETAILS(QUATECH_ESU2_100, 8)}, + {QT_DETAILS(0, 0)} /* Terminating entry */ +}; + +static const struct usb_device_id id_table[] = { + {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_SSU2_100)}, + {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_DSU2_100)}, + {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_DSU2_400)}, + {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_QSU2_100)}, + {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_QSU2_400)}, + {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_ESU2_100)}, + {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_ESU2_400)}, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct qt2_serial_private { + unsigned char current_port; /* current port for incoming data */ + + struct urb *read_urb; /* shared among all ports */ + char read_buffer[512]; +}; + +struct qt2_port_private { + u8 device_port; + + spinlock_t urb_lock; + bool urb_in_use; + struct urb *write_urb; + char write_buffer[QT2_WRITE_BUFFER_SIZE]; + + spinlock_t lock; + u8 shadowLSR; + u8 shadowMSR; + + struct usb_serial_port *port; +}; + +static void qt2_update_lsr(struct usb_serial_port *port, unsigned char *ch); +static void qt2_update_msr(struct usb_serial_port *port, unsigned char *ch); +static void qt2_write_bulk_callback(struct urb *urb); +static void qt2_read_bulk_callback(struct urb *urb); + +static void qt2_release(struct usb_serial *serial) +{ + struct qt2_serial_private *serial_priv; + + serial_priv = usb_get_serial_data(serial); + + usb_free_urb(serial_priv->read_urb); + kfree(serial_priv); +} + +static inline int calc_baud_divisor(int baudrate) +{ + int divisor, rem; + + divisor = MAX_BAUD_RATE / baudrate; + rem = MAX_BAUD_RATE % baudrate; + /* Round to nearest divisor */ + if (((rem * 2) >= baudrate) && (baudrate != 110)) + divisor++; + + return divisor; +} + +static inline int qt2_set_port_config(struct usb_device *dev, + unsigned char port_number, + u16 baudrate, u16 lcr) +{ + int divisor = calc_baud_divisor(baudrate); + u16 index = ((u16) (lcr << 8) | (u16) (port_number)); + + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + QT2_GET_SET_UART, 0x40, + divisor, index, NULL, 0, QT2_USB_TIMEOUT); +} + +static inline int qt2_control_msg(struct usb_device *dev, + u8 request, u16 data, u16 index) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + request, 0x40, data, index, + NULL, 0, QT2_USB_TIMEOUT); +} + +static inline int qt2_setdevice(struct usb_device *dev, u8 *data) +{ + u16 x = ((u16) (data[1] << 8) | (u16) (data[0])); + + return qt2_control_msg(dev, QT_SET_GET_DEVICE, x, 0); +} + + +static inline int qt2_getdevice(struct usb_device *dev, u8 *data) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + QT_SET_GET_DEVICE, 0xc0, 0, 0, + data, 3, QT2_USB_TIMEOUT); +} + +static inline int qt2_getregister(struct usb_device *dev, + u8 uart, + u8 reg, + u8 *data) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + QT_SET_GET_REGISTER, 0xc0, reg, + uart, data, sizeof(*data), QT2_USB_TIMEOUT); + +} + +static inline int qt2_setregister(struct usb_device *dev, + u8 uart, u8 reg, u16 data) +{ + u16 value = (data << 8) | reg; + + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + QT_SET_GET_REGISTER, 0x40, value, uart, + NULL, 0, QT2_USB_TIMEOUT); +} + +static inline int update_mctrl(struct qt2_port_private *port_priv, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = port_priv->port; + struct usb_device *dev = port->serial->dev; + unsigned urb_value; + int status; + + if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0) { + dev_dbg(&port->dev, + "update_mctrl - DTR|RTS not being set|cleared\n"); + return 0; /* no change */ + } + + clear &= ~set; /* 'set' takes precedence over 'clear' */ + urb_value = 0; + if (set & TIOCM_DTR) + urb_value |= UART_MCR_DTR; + if (set & TIOCM_RTS) + urb_value |= UART_MCR_RTS; + + status = qt2_setregister(dev, port_priv->device_port, UART_MCR, + urb_value); + if (status < 0) + dev_err(&port->dev, + "update_mctrl - Error from MODEM_CTRL urb: %i\n", + status); + return status; +} + +static int qt2_calc_num_ports(struct usb_serial *serial) +{ + struct qt2_device_detail d; + int i; + + for (i = 0; d = qt2_device_details[i], d.product_id != 0; i++) { + if (d.product_id == le16_to_cpu(serial->dev->descriptor.idProduct)) + return d.num_ports; + } + + /* we didn't recognize the device */ + dev_err(&serial->dev->dev, + "don't know the number of ports, assuming 1\n"); + + return 1; +} + +static void qt2_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct usb_device *dev = port->serial->dev; + struct qt2_port_private *port_priv; + struct ktermios *termios = &tty->termios; + u16 baud; + unsigned int cflag = termios->c_cflag; + u16 new_lcr = 0; + int status; + + port_priv = usb_get_serial_port_data(port); + + if (cflag & PARENB) { + if (cflag & PARODD) + new_lcr |= UART_LCR_PARITY; + else + new_lcr |= SERIAL_EVEN_PARITY; + } + + switch (cflag & CSIZE) { + case CS5: + new_lcr |= UART_LCR_WLEN5; + break; + case CS6: + new_lcr |= UART_LCR_WLEN6; + break; + case CS7: + new_lcr |= UART_LCR_WLEN7; + break; + default: + case CS8: + new_lcr |= UART_LCR_WLEN8; + break; + } + + baud = tty_get_baud_rate(tty); + if (!baud) + baud = 9600; + + status = qt2_set_port_config(dev, port_priv->device_port, baud, + new_lcr); + if (status < 0) + dev_err(&port->dev, "%s - qt2_set_port_config failed: %i\n", + __func__, status); + + if (cflag & CRTSCTS) + status = qt2_control_msg(dev, QT_HW_FLOW_CONTROL_MASK, + SERIAL_CRTSCTS, + port_priv->device_port); + else + status = qt2_control_msg(dev, QT_HW_FLOW_CONTROL_MASK, + 0, port_priv->device_port); + if (status < 0) + dev_err(&port->dev, "%s - set HW flow control failed: %i\n", + __func__, status); + + if (I_IXOFF(tty) || I_IXON(tty)) { + u16 x = ((u16) (START_CHAR(tty) << 8) | (u16) (STOP_CHAR(tty))); + + status = qt2_control_msg(dev, QT_SW_FLOW_CONTROL_MASK, + x, port_priv->device_port); + } else + status = qt2_control_msg(dev, QT_SW_FLOW_CONTROL_MASK, + 0, port_priv->device_port); + + if (status < 0) + dev_err(&port->dev, "%s - set SW flow control failed: %i\n", + __func__, status); + +} + +static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial; + struct qt2_port_private *port_priv; + u8 *data; + u16 device_port; + int status; + unsigned long flags; + + device_port = (u16) (port->number - port->serial->minor); + + serial = port->serial; + + port_priv = usb_get_serial_port_data(port); + + /* set the port to RS232 mode */ + status = qt2_control_msg(serial->dev, QT2_GET_SET_QMCR, + QT2_QMCR_RS232, device_port); + if (status < 0) { + dev_err(&port->dev, + "%s failed to set RS232 mode for port %i error %i\n", + __func__, device_port, status); + return status; + } + + data = kzalloc(2, GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* open the port */ + status = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + QT_OPEN_CLOSE_CHANNEL, + 0xc0, 0, + device_port, data, 2, QT2_USB_TIMEOUT); + + if (status < 0) { + dev_err(&port->dev, "%s - open port failed %i", __func__, + status); + kfree(data); + return status; + } + + spin_lock_irqsave(&port_priv->lock, flags); + port_priv->shadowLSR = data[0]; + port_priv->shadowMSR = data[1]; + spin_unlock_irqrestore(&port_priv->lock, flags); + + kfree(data); + + /* set to default speed and 8bit word size */ + status = qt2_set_port_config(serial->dev, device_port, + DEFAULT_BAUD_RATE, UART_LCR_WLEN8); + if (status < 0) { + dev_err(&port->dev, + "%s - initial setup failed for port %i (%i)\n", + __func__, port->number, device_port); + return status; + } + + port_priv->device_port = (u8) device_port; + + if (tty) + qt2_set_termios(tty, port, &tty->termios); + + return 0; + +} + +static void qt2_close(struct usb_serial_port *port) +{ + struct usb_serial *serial; + struct qt2_port_private *port_priv; + unsigned long flags; + int i; + + serial = port->serial; + port_priv = usb_get_serial_port_data(port); + + spin_lock_irqsave(&port_priv->urb_lock, flags); + usb_kill_urb(port_priv->write_urb); + port_priv->urb_in_use = false; + spin_unlock_irqrestore(&port_priv->urb_lock, flags); + + /* flush the port transmit buffer */ + i = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + QT2_FLUSH_DEVICE, 0x40, 1, + port_priv->device_port, NULL, 0, QT2_USB_TIMEOUT); + + if (i < 0) + dev_err(&port->dev, "%s - transmit buffer flush failed: %i\n", + __func__, i); + + /* flush the port receive buffer */ + i = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + QT2_FLUSH_DEVICE, 0x40, 0, + port_priv->device_port, NULL, 0, QT2_USB_TIMEOUT); + + if (i < 0) + dev_err(&port->dev, "%s - receive buffer flush failed: %i\n", + __func__, i); + + /* close the port */ + i = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + QT_OPEN_CLOSE_CHANNEL, + 0x40, 0, + port_priv->device_port, NULL, 0, QT2_USB_TIMEOUT); + + if (i < 0) + dev_err(&port->dev, "%s - close port failed %i\n", + __func__, i); +} + +static void qt2_disconnect(struct usb_serial *serial) +{ + struct qt2_serial_private *serial_priv = usb_get_serial_data(serial); + + usb_kill_urb(serial_priv->read_urb); +} + +static int get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + tmp.line = port->serial->minor; + tmp.port = 0; + tmp.irq = 0; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = port->bulk_out_size; + tmp.baud_base = 9600; + tmp.close_delay = 5*HZ; + tmp.closing_wait = 30*HZ; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int qt2_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(port, + (struct serial_struct __user *)arg); + default: + break; + } + + return -ENOIOCTLCMD; +} + +static void qt2_process_status(struct usb_serial_port *port, unsigned char *ch) +{ + switch (*ch) { + case QT2_LINE_STATUS: + qt2_update_lsr(port, ch + 1); + break; + case QT2_MODEM_STATUS: + qt2_update_msr(port, ch + 1); + break; + } +} + +/* not needed, kept to document functionality */ +static void qt2_process_xmit_empty(struct usb_serial_port *port, + unsigned char *ch) +{ + int bytes_written; + + bytes_written = (int)(*ch) + (int)(*(ch + 1) << 4); +} + +/* not needed, kept to document functionality */ +static void qt2_process_flush(struct usb_serial_port *port, unsigned char *ch) +{ + return; +} + +void qt2_process_read_urb(struct urb *urb) +{ + struct usb_serial *serial; + struct qt2_serial_private *serial_priv; + struct usb_serial_port *port; + struct qt2_port_private *port_priv; + bool escapeflag; + unsigned char *ch; + int i; + unsigned char newport; + int len = urb->actual_length; + + if (!len) + return; + + ch = urb->transfer_buffer; + serial = urb->context; + serial_priv = usb_get_serial_data(serial); + port = serial->port[serial_priv->current_port]; + port_priv = usb_get_serial_port_data(port); + + for (i = 0; i < urb->actual_length; i++) { + ch = (unsigned char *)urb->transfer_buffer + i; + if ((i <= (len - 3)) && + (*ch == QT2_CONTROL_BYTE) && + (*(ch + 1) == QT2_CONTROL_BYTE)) { + escapeflag = false; + switch (*(ch + 2)) { + case QT2_LINE_STATUS: + case QT2_MODEM_STATUS: + if (i > (len - 4)) { + dev_warn(&port->dev, + "%s - status message too short\n", + __func__); + break; + } + qt2_process_status(port, ch + 2); + i += 3; + escapeflag = true; + break; + case QT2_XMIT_HOLD: + if (i > (len - 5)) { + dev_warn(&port->dev, + "%s - xmit_empty message too short\n", + __func__); + break; + } + qt2_process_xmit_empty(port, ch + 3); + i += 4; + escapeflag = true; + break; + case QT2_CHANGE_PORT: + if (i > (len - 4)) { + dev_warn(&port->dev, + "%s - change_port message too short\n", + __func__); + break; + } + tty_flip_buffer_push(&port->port); + + newport = *(ch + 3); + + if (newport > serial->num_ports) { + dev_err(&port->dev, + "%s - port change to invalid port: %i\n", + __func__, newport); + break; + } + + serial_priv->current_port = newport; + port = serial->port[serial_priv->current_port]; + port_priv = usb_get_serial_port_data(port); + i += 3; + escapeflag = true; + break; + case QT2_REC_FLUSH: + case QT2_XMIT_FLUSH: + qt2_process_flush(port, ch + 2); + i += 2; + escapeflag = true; + break; + case QT2_CONTROL_ESCAPE: + tty_buffer_request_room(&port->port, 2); + tty_insert_flip_string(&port->port, ch, 2); + i += 2; + escapeflag = true; + break; + default: + dev_warn(&port->dev, + "%s - unsupported command %i\n", + __func__, *(ch + 2)); + break; + } + if (escapeflag) + continue; + } + + tty_buffer_request_room(&port->port, 1); + tty_insert_flip_string(&port->port, ch, 1); + } + + tty_flip_buffer_push(&port->port); +} + +static void qt2_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port; + struct qt2_port_private *port_priv; + + port = urb->context; + port_priv = usb_get_serial_port_data(port); + + spin_lock(&port_priv->urb_lock); + + port_priv->urb_in_use = false; + usb_serial_port_softint(port); + + spin_unlock(&port_priv->urb_lock); + +} + +static void qt2_read_bulk_callback(struct urb *urb) +{ + struct usb_serial *serial = urb->context; + int status; + + if (urb->status) { + dev_warn(&serial->dev->dev, + "%s - non-zero urb status: %i\n", __func__, + urb->status); + return; + } + + qt2_process_read_urb(urb); + + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status != 0) + dev_err(&serial->dev->dev, + "%s - resubmit read urb failed: %i\n", + __func__, status); +} + +static int qt2_setup_urbs(struct usb_serial *serial) +{ + struct usb_serial_port *port0; + struct qt2_serial_private *serial_priv; + int status; + + port0 = serial->port[0]; + + serial_priv = usb_get_serial_data(serial); + serial_priv->read_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!serial_priv->read_urb) { + dev_err(&serial->dev->dev, "No free urbs available\n"); + return -ENOMEM; + } + + usb_fill_bulk_urb(serial_priv->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, + port0->bulk_in_endpointAddress), + serial_priv->read_buffer, + sizeof(serial_priv->read_buffer), + qt2_read_bulk_callback, serial); + + status = usb_submit_urb(serial_priv->read_urb, GFP_KERNEL); + if (status != 0) { + dev_err(&serial->dev->dev, + "%s - submit read urb failed %i\n", __func__, status); + usb_free_urb(serial_priv->read_urb); + return status; + } + + return 0; +} + +static int qt2_attach(struct usb_serial *serial) +{ + struct qt2_serial_private *serial_priv; + int status; + + /* power on unit */ + status = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + 0xc2, 0x40, 0x8000, 0, NULL, 0, + QT2_USB_TIMEOUT); + if (status < 0) { + dev_err(&serial->dev->dev, + "%s - failed to power on unit: %i\n", __func__, status); + return status; + } + + serial_priv = kzalloc(sizeof(*serial_priv), GFP_KERNEL); + if (!serial_priv) { + dev_err(&serial->dev->dev, "%s - Out of memory\n", __func__); + return -ENOMEM; + } + + usb_set_serial_data(serial, serial_priv); + + status = qt2_setup_urbs(serial); + if (status != 0) + goto attach_failed; + + return 0; + +attach_failed: + kfree(serial_priv); + return status; +} + +static int qt2_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct qt2_port_private *port_priv; + u8 bEndpointAddress; + + port_priv = kzalloc(sizeof(*port_priv), GFP_KERNEL); + if (!port_priv) + return -ENOMEM; + + spin_lock_init(&port_priv->lock); + spin_lock_init(&port_priv->urb_lock); + port_priv->port = port; + + port_priv->write_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port_priv->write_urb) { + kfree(port_priv); + return -ENOMEM; + } + bEndpointAddress = serial->port[0]->bulk_out_endpointAddress; + usb_fill_bulk_urb(port_priv->write_urb, serial->dev, + usb_sndbulkpipe(serial->dev, bEndpointAddress), + port_priv->write_buffer, + sizeof(port_priv->write_buffer), + qt2_write_bulk_callback, port); + + usb_set_serial_port_data(port, port_priv); + + return 0; +} + +static int qt2_port_remove(struct usb_serial_port *port) +{ + struct qt2_port_private *port_priv; + + port_priv = usb_get_serial_port_data(port); + usb_free_urb(port_priv->write_urb); + kfree(port_priv); + + return 0; +} + +static int qt2_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_device *dev = port->serial->dev; + struct qt2_port_private *port_priv = usb_get_serial_port_data(port); + u8 *d; + int r; + + d = kzalloc(2, GFP_KERNEL); + if (!d) + return -ENOMEM; + + r = qt2_getregister(dev, port_priv->device_port, UART_MCR, d); + if (r < 0) + goto mget_out; + + r = qt2_getregister(dev, port_priv->device_port, UART_MSR, d + 1); + if (r < 0) + goto mget_out; + + r = (d[0] & UART_MCR_DTR ? TIOCM_DTR : 0) | + (d[0] & UART_MCR_RTS ? TIOCM_RTS : 0) | + (d[1] & UART_MSR_CTS ? TIOCM_CTS : 0) | + (d[1] & UART_MSR_DCD ? TIOCM_CAR : 0) | + (d[1] & UART_MSR_RI ? TIOCM_RI : 0) | + (d[1] & UART_MSR_DSR ? TIOCM_DSR : 0); + +mget_out: + kfree(d); + return r; +} + +static int qt2_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct qt2_port_private *port_priv; + + port_priv = usb_get_serial_port_data(tty->driver_data); + return update_mctrl(port_priv, set, clear); +} + +static void qt2_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct qt2_port_private *port_priv; + int status; + u16 val; + + port_priv = usb_get_serial_port_data(port); + + val = (break_state == -1) ? 1 : 0; + + status = qt2_control_msg(port->serial->dev, QT2_BREAK_CONTROL, + val, port_priv->device_port); + if (status < 0) + dev_warn(&port->dev, + "%s - failed to send control message: %i\n", __func__, + status); +} + + + +static void qt2_dtr_rts(struct usb_serial_port *port, int on) +{ + struct usb_device *dev = port->serial->dev; + struct qt2_port_private *port_priv = usb_get_serial_port_data(port); + + /* Disable flow control */ + if (!on) { + if (qt2_setregister(dev, port_priv->device_port, + UART_MCR, 0) < 0) + dev_warn(&port->dev, "error from flowcontrol urb\n"); + } + /* drop RTS and DTR */ + if (on) + update_mctrl(port_priv, TIOCM_DTR | TIOCM_RTS, 0); + else + update_mctrl(port_priv, 0, TIOCM_DTR | TIOCM_RTS); +} + +static void qt2_update_msr(struct usb_serial_port *port, unsigned char *ch) +{ + struct qt2_port_private *port_priv; + u8 newMSR = (u8) *ch; + unsigned long flags; + + port_priv = usb_get_serial_port_data(port); + + spin_lock_irqsave(&port_priv->lock, flags); + port_priv->shadowMSR = newMSR; + spin_unlock_irqrestore(&port_priv->lock, flags); + + if (newMSR & UART_MSR_ANY_DELTA) { + /* update input line counters */ + if (newMSR & UART_MSR_DCTS) + port->icount.cts++; + if (newMSR & UART_MSR_DDSR) + port->icount.dsr++; + if (newMSR & UART_MSR_DDCD) + port->icount.dcd++; + if (newMSR & UART_MSR_TERI) + port->icount.rng++; + + wake_up_interruptible(&port->port.delta_msr_wait); + } +} + +static void qt2_update_lsr(struct usb_serial_port *port, unsigned char *ch) +{ + struct qt2_port_private *port_priv; + struct async_icount *icount; + unsigned long flags; + u8 newLSR = (u8) *ch; + + port_priv = usb_get_serial_port_data(port); + + if (newLSR & UART_LSR_BI) + newLSR &= (u8) (UART_LSR_OE | UART_LSR_BI); + + spin_lock_irqsave(&port_priv->lock, flags); + port_priv->shadowLSR = newLSR; + spin_unlock_irqrestore(&port_priv->lock, flags); + + icount = &port->icount; + + if (newLSR & UART_LSR_BRK_ERROR_BITS) { + + if (newLSR & UART_LSR_BI) + icount->brk++; + + if (newLSR & UART_LSR_OE) + icount->overrun++; + + if (newLSR & UART_LSR_PE) + icount->parity++; + + if (newLSR & UART_LSR_FE) + icount->frame++; + } + +} + +static int qt2_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct qt2_port_private *port_priv; + unsigned long flags = 0; + int r; + + port_priv = usb_get_serial_port_data(port); + + spin_lock_irqsave(&port_priv->urb_lock, flags); + + if (port_priv->urb_in_use) + r = 0; + else + r = QT2_WRITE_BUFFER_SIZE - QT2_WRITE_CONTROL_SIZE; + + spin_unlock_irqrestore(&port_priv->urb_lock, flags); + + return r; +} + +static int qt2_write(struct tty_struct *tty, + struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct qt2_port_private *port_priv; + struct urb *write_urb; + unsigned char *data; + unsigned long flags; + int status; + int bytes_out = 0; + + port_priv = usb_get_serial_port_data(port); + + if (port_priv->write_urb == NULL) { + dev_err(&port->dev, "%s - no output urb\n", __func__); + return 0; + } + write_urb = port_priv->write_urb; + + count = min(count, QT2_WRITE_BUFFER_SIZE - QT2_WRITE_CONTROL_SIZE); + + data = write_urb->transfer_buffer; + spin_lock_irqsave(&port_priv->urb_lock, flags); + if (port_priv->urb_in_use == true) { + dev_err(&port->dev, "qt2_write - urb is in use\n"); + goto write_out; + } + + *data++ = QT2_CONTROL_BYTE; + *data++ = QT2_CONTROL_BYTE; + *data++ = port_priv->device_port; + put_unaligned_le16(count, data); + data += 2; + memcpy(data, buf, count); + + write_urb->transfer_buffer_length = count + QT2_WRITE_CONTROL_SIZE; + + status = usb_submit_urb(write_urb, GFP_ATOMIC); + if (status == 0) { + port_priv->urb_in_use = true; + bytes_out += count; + } + +write_out: + spin_unlock_irqrestore(&port_priv->urb_lock, flags); + return bytes_out; +} + + +static struct usb_serial_driver qt2_device = { + .driver = { + .owner = THIS_MODULE, + .name = "quatech-serial", + }, + .description = DRIVER_DESC, + .id_table = id_table, + .open = qt2_open, + .close = qt2_close, + .write = qt2_write, + .write_room = qt2_write_room, + .calc_num_ports = qt2_calc_num_ports, + .attach = qt2_attach, + .release = qt2_release, + .disconnect = qt2_disconnect, + .port_probe = qt2_port_probe, + .port_remove = qt2_port_remove, + .dtr_rts = qt2_dtr_rts, + .break_ctl = qt2_break_ctl, + .tiocmget = qt2_tiocmget, + .tiocmset = qt2_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .ioctl = qt2_ioctl, + .set_termios = qt2_set_termios, +}; + +static struct usb_serial_driver *const serial_drivers[] = { + &qt2_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/safe_serial.c b/drivers/usb/serial/safe_serial.c new file mode 100644 index 00000000..21cd7bf2 --- /dev/null +++ b/drivers/usb/serial/safe_serial.c @@ -0,0 +1,329 @@ +/* + * Safe Encapsulated USB Serial Driver + * + * Copyright (C) 2010 Johan Hovold + * Copyright (C) 2001 Lineo + * Copyright (C) 2001 Hewlett-Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * By: + * Stuart Lynne , Tom Rushworth + */ + +/* + * The encapsultaion is designed to overcome difficulties with some USB + * hardware. + * + * While the USB protocol has a CRC over the data while in transit, i.e. while + * being carried over the bus, there is no end to end protection. If the + * hardware has any problems getting the data into or out of the USB transmit + * and receive FIFO's then data can be lost. + * + * This protocol adds a two byte trailer to each USB packet to specify the + * number of bytes of valid data and a 10 bit CRC that will allow the receiver + * to verify that the entire USB packet was received without error. + * + * Because in this case the sender and receiver are the class and function + * drivers there is now end to end protection. + * + * There is an additional option that can be used to force all transmitted + * packets to be padded to the maximum packet size. This provides a work + * around for some devices which have problems with small USB packets. + * + * Assuming a packetsize of N: + * + * 0..N-2 data and optional padding + * + * N-2 bits 7-2 - number of bytes of valid data + * bits 1-0 top two bits of 10 bit CRC + * N-1 bottom 8 bits of 10 bit CRC + * + * + * | Data Length | 10 bit CRC | + * + 7 . 6 . 5 . 4 . 3 . 2 . 1 . 0 | 7 . 6 . 5 . 4 . 3 . 2 . 1 . 0 + + * + * The 10 bit CRC is computed across the sent data, followed by the trailer + * with the length set and the CRC set to zero. The CRC is then OR'd into + * the trailer. + * + * When received a 10 bit CRC is computed over the entire frame including + * the trailer and should be equal to zero. + * + * Two module parameters are used to control the encapsulation, if both are + * turned of the module works as a simple serial device with NO + * encapsulation. + * + * See linux/drivers/usbd/serial_fd for a device function driver + * implementation of this. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef CONFIG_USB_SERIAL_SAFE_PADDED +#define CONFIG_USB_SERIAL_SAFE_PADDED 0 +#endif + +static bool safe = 1; +static bool padded = CONFIG_USB_SERIAL_SAFE_PADDED; + +#define DRIVER_AUTHOR "sl@lineo.com, tbr@lineo.com, Johan Hovold " +#define DRIVER_DESC "USB Safe Encapsulated Serial" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static __u16 vendor; /* no default */ +static __u16 product; /* no default */ +module_param(vendor, ushort, 0); +MODULE_PARM_DESC(vendor, "User specified USB idVendor (required)"); +module_param(product, ushort, 0); +MODULE_PARM_DESC(product, "User specified USB idProduct (required)"); + +module_param(safe, bool, 0); +MODULE_PARM_DESC(safe, "Turn Safe Encapsulation On/Off"); + +module_param(padded, bool, 0); +MODULE_PARM_DESC(padded, "Pad to full wMaxPacketSize On/Off"); + +#define CDC_DEVICE_CLASS 0x02 + +#define CDC_INTERFACE_CLASS 0x02 +#define CDC_INTERFACE_SUBCLASS 0x06 + +#define LINEO_INTERFACE_CLASS 0xff + +#define LINEO_INTERFACE_SUBCLASS_SAFENET 0x01 +#define LINEO_SAFENET_CRC 0x01 +#define LINEO_SAFENET_CRC_PADDED 0x02 + +#define LINEO_INTERFACE_SUBCLASS_SAFESERIAL 0x02 +#define LINEO_SAFESERIAL_CRC 0x01 +#define LINEO_SAFESERIAL_CRC_PADDED 0x02 + + +#define MY_USB_DEVICE(vend, prod, dc, ic, isc) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_DEV_CLASS | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_SUBCLASS, \ + .idVendor = (vend), \ + .idProduct = (prod),\ + .bDeviceClass = (dc),\ + .bInterfaceClass = (ic), \ + .bInterfaceSubClass = (isc), + +static struct usb_device_id id_table[] = { + {MY_USB_DEVICE(0x49f, 0xffff, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Itsy */ + {MY_USB_DEVICE(0x3f0, 0x2101, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Calypso */ + {MY_USB_DEVICE(0x4dd, 0x8001, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Iris */ + {MY_USB_DEVICE(0x4dd, 0x8002, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Collie */ + {MY_USB_DEVICE(0x4dd, 0x8003, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Collie */ + {MY_USB_DEVICE(0x4dd, 0x8004, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Collie */ + {MY_USB_DEVICE(0x5f9, 0xffff, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, /* Sharp tmp */ + /* extra null entry for module vendor/produc parameters */ + {MY_USB_DEVICE(0, 0, CDC_DEVICE_CLASS, LINEO_INTERFACE_CLASS, LINEO_INTERFACE_SUBCLASS_SAFESERIAL)}, + {} /* terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static const __u16 crc10_table[256] = { + 0x000, 0x233, 0x255, 0x066, 0x299, 0x0aa, 0x0cc, 0x2ff, + 0x301, 0x132, 0x154, 0x367, 0x198, 0x3ab, 0x3cd, 0x1fe, + 0x031, 0x202, 0x264, 0x057, 0x2a8, 0x09b, 0x0fd, 0x2ce, + 0x330, 0x103, 0x165, 0x356, 0x1a9, 0x39a, 0x3fc, 0x1cf, + 0x062, 0x251, 0x237, 0x004, 0x2fb, 0x0c8, 0x0ae, 0x29d, + 0x363, 0x150, 0x136, 0x305, 0x1fa, 0x3c9, 0x3af, 0x19c, + 0x053, 0x260, 0x206, 0x035, 0x2ca, 0x0f9, 0x09f, 0x2ac, + 0x352, 0x161, 0x107, 0x334, 0x1cb, 0x3f8, 0x39e, 0x1ad, + 0x0c4, 0x2f7, 0x291, 0x0a2, 0x25d, 0x06e, 0x008, 0x23b, + 0x3c5, 0x1f6, 0x190, 0x3a3, 0x15c, 0x36f, 0x309, 0x13a, + 0x0f5, 0x2c6, 0x2a0, 0x093, 0x26c, 0x05f, 0x039, 0x20a, + 0x3f4, 0x1c7, 0x1a1, 0x392, 0x16d, 0x35e, 0x338, 0x10b, + 0x0a6, 0x295, 0x2f3, 0x0c0, 0x23f, 0x00c, 0x06a, 0x259, + 0x3a7, 0x194, 0x1f2, 0x3c1, 0x13e, 0x30d, 0x36b, 0x158, + 0x097, 0x2a4, 0x2c2, 0x0f1, 0x20e, 0x03d, 0x05b, 0x268, + 0x396, 0x1a5, 0x1c3, 0x3f0, 0x10f, 0x33c, 0x35a, 0x169, + 0x188, 0x3bb, 0x3dd, 0x1ee, 0x311, 0x122, 0x144, 0x377, + 0x289, 0x0ba, 0x0dc, 0x2ef, 0x010, 0x223, 0x245, 0x076, + 0x1b9, 0x38a, 0x3ec, 0x1df, 0x320, 0x113, 0x175, 0x346, + 0x2b8, 0x08b, 0x0ed, 0x2de, 0x021, 0x212, 0x274, 0x047, + 0x1ea, 0x3d9, 0x3bf, 0x18c, 0x373, 0x140, 0x126, 0x315, + 0x2eb, 0x0d8, 0x0be, 0x28d, 0x072, 0x241, 0x227, 0x014, + 0x1db, 0x3e8, 0x38e, 0x1bd, 0x342, 0x171, 0x117, 0x324, + 0x2da, 0x0e9, 0x08f, 0x2bc, 0x043, 0x270, 0x216, 0x025, + 0x14c, 0x37f, 0x319, 0x12a, 0x3d5, 0x1e6, 0x180, 0x3b3, + 0x24d, 0x07e, 0x018, 0x22b, 0x0d4, 0x2e7, 0x281, 0x0b2, + 0x17d, 0x34e, 0x328, 0x11b, 0x3e4, 0x1d7, 0x1b1, 0x382, + 0x27c, 0x04f, 0x029, 0x21a, 0x0e5, 0x2d6, 0x2b0, 0x083, + 0x12e, 0x31d, 0x37b, 0x148, 0x3b7, 0x184, 0x1e2, 0x3d1, + 0x22f, 0x01c, 0x07a, 0x249, 0x0b6, 0x285, 0x2e3, 0x0d0, + 0x11f, 0x32c, 0x34a, 0x179, 0x386, 0x1b5, 0x1d3, 0x3e0, + 0x21e, 0x02d, 0x04b, 0x278, 0x087, 0x2b4, 0x2d2, 0x0e1, +}; + +#define CRC10_INITFCS 0x000 /* Initial FCS value */ +#define CRC10_GOODFCS 0x000 /* Good final FCS value */ +#define CRC10_FCS(fcs, c) ((((fcs) << 8) & 0x3ff) ^ crc10_table[((fcs) >> 2) & 0xff] ^ (c)) + +/** + * fcs_compute10 - memcpy and calculate 10 bit CRC across buffer + * @sp: pointer to buffer + * @len: number of bytes + * @fcs: starting FCS + * + * Perform a memcpy and calculate fcs using ppp 10bit CRC algorithm. Return + * new 10 bit FCS. + */ +static __u16 __inline__ fcs_compute10(unsigned char *sp, int len, __u16 fcs) +{ + for (; len-- > 0; fcs = CRC10_FCS(fcs, *sp++)); + return fcs; +} + +static void safe_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned char length = urb->actual_length; + int actual_length; + __u16 fcs; + + if (!length) + return; + + if (!safe) + goto out; + + fcs = fcs_compute10(data, length, CRC10_INITFCS); + if (fcs) { + dev_err(&port->dev, "%s - bad CRC %x\n", __func__, fcs); + return; + } + + actual_length = data[length - 2] >> 2; + if (actual_length > (length - 2)) { + dev_err(&port->dev, "%s - inconsistent lengths %d:%d\n", + __func__, actual_length, length); + return; + } + dev_info(&urb->dev->dev, "%s - actual: %d\n", __func__, actual_length); + length = actual_length; +out: + tty_insert_flip_string(&port->port, data, length); + tty_flip_buffer_push(&port->port); +} + +static int safe_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size) +{ + unsigned char *buf = dest; + int count; + int trailer_len; + int pkt_len; + __u16 fcs; + + trailer_len = safe ? 2 : 0; + + count = kfifo_out_locked(&port->write_fifo, buf, size - trailer_len, + &port->lock); + if (!safe) + return count; + + /* pad if necessary */ + if (padded) { + pkt_len = size; + memset(buf + count, '0', pkt_len - count - trailer_len); + } else { + pkt_len = count + trailer_len; + } + + /* set count */ + buf[pkt_len - 2] = count << 2; + buf[pkt_len - 1] = 0; + + /* compute fcs and insert into trailer */ + fcs = fcs_compute10(buf, pkt_len, CRC10_INITFCS); + buf[pkt_len - 2] |= fcs >> 8; + buf[pkt_len - 1] |= fcs & 0xff; + + return pkt_len; +} + +static int safe_startup(struct usb_serial *serial) +{ + switch (serial->interface->cur_altsetting->desc.bInterfaceProtocol) { + case LINEO_SAFESERIAL_CRC: + break; + case LINEO_SAFESERIAL_CRC_PADDED: + padded = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static struct usb_serial_driver safe_device = { + .driver = { + .owner = THIS_MODULE, + .name = "safe_serial", + }, + .id_table = id_table, + .num_ports = 1, + .process_read_urb = safe_process_read_urb, + .prepare_write_buffer = safe_prepare_write_buffer, + .attach = safe_startup, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &safe_device, NULL +}; + +static int __init safe_init(void) +{ + int i; + + /* if we have vendor / product parameters patch them into id list */ + if (vendor || product) { + pr_info("vendor: %x product: %x\n", vendor, product); + + for (i = 0; i < ARRAY_SIZE(id_table); i++) { + if (!id_table[i].idVendor && !id_table[i].idProduct) { + id_table[i].idVendor = vendor; + id_table[i].idProduct = product; + break; + } + } + } + + return usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME, id_table); +} + +static void __exit safe_exit(void) +{ + usb_serial_deregister_drivers(serial_drivers); +} + +module_init(safe_init); +module_exit(safe_exit); diff --git a/drivers/usb/serial/siemens_mpi.c b/drivers/usb/serial/siemens_mpi.c new file mode 100644 index 00000000..a76b1ae5 --- /dev/null +++ b/drivers/usb/serial/siemens_mpi.c @@ -0,0 +1,47 @@ +/* + * Siemens USB-MPI Serial USB driver + * + * Copyright (C) 2005 Thomas Hergenhahn + * Copyright (C) 2005,2008 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Thomas Hergenhahn@web.de http://libnodave.sf.net" +#define DRIVER_DESC "Driver for Siemens USB/MPI adapter" + + +static const struct usb_device_id id_table[] = { + /* Vendor and product id for 6ES7-972-0CB20-0XA0 */ + { USB_DEVICE(0x908, 0x0004) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver siemens_usb_mpi_device = { + .driver = { + .owner = THIS_MODULE, + .name = "siemens_mpi", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &siemens_usb_mpi_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c new file mode 100644 index 00000000..5aaa2b67 --- /dev/null +++ b/drivers/usb/serial/sierra.c @@ -0,0 +1,1099 @@ +/* + USB Driver for Sierra Wireless + + Copyright (C) 2006, 2007, 2008 Kevin Lloyd , + + Copyright (C) 2008, 2009 Elina Pasheva, Matthew Safar, Rory Filer + + + IMPORTANT DISCLAIMER: This driver is not commercially supported by + Sierra Wireless. Use at your own risk. + + This driver is free software; you can redistribute it and/or modify + it under the terms of Version 2 of the GNU General Public License as + published by the Free Software Foundation. + + Portions based on the option driver by Matthias Urlichs + Whom based his on the Keyspan driver by Hugh Blemings +*/ +/* Uncomment to log function calls */ +/* #define DEBUG */ + +#define DRIVER_AUTHOR "Kevin Lloyd, Elina Pasheva, Matthew Safar, Rory Filer" +#define DRIVER_DESC "USB Driver for Sierra Wireless USB modems" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SWIMS_USB_REQUEST_SetPower 0x00 +#define SWIMS_USB_REQUEST_SetNmea 0x07 + +#define N_IN_URB_HM 8 +#define N_OUT_URB_HM 64 +#define N_IN_URB 4 +#define N_OUT_URB 4 +#define IN_BUFLEN 4096 + +#define MAX_TRANSFER (PAGE_SIZE - 512) +/* MAX_TRANSFER is chosen so that the VM is not stressed by + allocations > PAGE_SIZE and the number of packets in a page + is an integer 512 is the largest possible packet on EHCI */ + +static bool nmea; + +/* Used in interface blacklisting */ +struct sierra_iface_info { + const u32 infolen; /* number of interface numbers on blacklist */ + const u8 *ifaceinfo; /* pointer to the array holding the numbers */ +}; + +struct sierra_intf_private { + spinlock_t susp_lock; + unsigned int suspended:1; + int in_flight; + unsigned int open_ports; +}; + +static int sierra_set_power_state(struct usb_device *udev, __u16 swiState) +{ + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SWIMS_USB_REQUEST_SetPower, /* __u8 request */ + USB_TYPE_VENDOR, /* __u8 request type */ + swiState, /* __u16 value */ + 0, /* __u16 index */ + NULL, /* void *data */ + 0, /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ +} + +static int sierra_vsc_set_nmea(struct usb_device *udev, __u16 enable) +{ + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SWIMS_USB_REQUEST_SetNmea, /* __u8 request */ + USB_TYPE_VENDOR, /* __u8 request type */ + enable, /* __u16 value */ + 0x0000, /* __u16 index */ + NULL, /* void *data */ + 0, /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ +} + +static int sierra_calc_num_ports(struct usb_serial *serial) +{ + int num_ports = 0; + u8 ifnum, numendpoints; + + ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber; + numendpoints = serial->interface->cur_altsetting->desc.bNumEndpoints; + + /* Dummy interface present on some SKUs should be ignored */ + if (ifnum == 0x99) + num_ports = 0; + else if (numendpoints <= 3) + num_ports = 1; + else + num_ports = (numendpoints-1)/2; + return num_ports; +} + +static int is_blacklisted(const u8 ifnum, + const struct sierra_iface_info *blacklist) +{ + const u8 *info; + int i; + + if (blacklist) { + info = blacklist->ifaceinfo; + + for (i = 0; i < blacklist->infolen; i++) { + if (info[i] == ifnum) + return 1; + } + } + return 0; +} + +static int is_himemory(const u8 ifnum, + const struct sierra_iface_info *himemorylist) +{ + const u8 *info; + int i; + + if (himemorylist) { + info = himemorylist->ifaceinfo; + + for (i=0; i < himemorylist->infolen; i++) { + if (info[i] == ifnum) + return 1; + } + } + return 0; +} + +static int sierra_calc_interface(struct usb_serial *serial) +{ + int interface; + struct usb_interface *p_interface; + struct usb_host_interface *p_host_interface; + + /* Get the interface structure pointer from the serial struct */ + p_interface = serial->interface; + + /* Get a pointer to the host interface structure */ + p_host_interface = p_interface->cur_altsetting; + + /* read the interface descriptor for this active altsetting + * to find out the interface number we are on + */ + interface = p_host_interface->desc.bInterfaceNumber; + + return interface; +} + +static int sierra_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + int result = 0; + struct usb_device *udev; + u8 ifnum; + + udev = serial->dev; + ifnum = sierra_calc_interface(serial); + + /* + * If this interface supports more than 1 alternate + * select the 2nd one + */ + if (serial->interface->num_altsetting == 2) { + dev_dbg(&udev->dev, "Selecting alt setting for interface %d\n", + ifnum); + /* We know the alternate setting is 1 for the MC8785 */ + usb_set_interface(udev, ifnum, 1); + } + + /* ifnum could have changed - by calling usb_set_interface */ + ifnum = sierra_calc_interface(serial); + + if (is_blacklisted(ifnum, + (struct sierra_iface_info *)id->driver_info)) { + dev_dbg(&serial->dev->dev, + "Ignoring blacklisted interface #%d\n", ifnum); + return -ENODEV; + } + + return result; +} + +/* interfaces with higher memory requirements */ +static const u8 hi_memory_typeA_ifaces[] = { 0, 2 }; +static const struct sierra_iface_info typeA_interface_list = { + .infolen = ARRAY_SIZE(hi_memory_typeA_ifaces), + .ifaceinfo = hi_memory_typeA_ifaces, +}; + +static const u8 hi_memory_typeB_ifaces[] = { 3, 4, 5, 6 }; +static const struct sierra_iface_info typeB_interface_list = { + .infolen = ARRAY_SIZE(hi_memory_typeB_ifaces), + .ifaceinfo = hi_memory_typeB_ifaces, +}; + +/* 'blacklist' of interfaces not served by this driver */ +static const u8 direct_ip_non_serial_ifaces[] = { 7, 8, 9, 10, 11, 19, 20 }; +static const struct sierra_iface_info direct_ip_interface_blacklist = { + .infolen = ARRAY_SIZE(direct_ip_non_serial_ifaces), + .ifaceinfo = direct_ip_non_serial_ifaces, +}; + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x0F3D, 0x0112) }, /* Airprime/Sierra PC 5220 */ + { USB_DEVICE(0x03F0, 0x1B1D) }, /* HP ev2200 a.k.a MC5720 */ + { USB_DEVICE(0x03F0, 0x211D) }, /* HP ev2210 a.k.a MC5725 */ + { USB_DEVICE(0x03F0, 0x1E1D) }, /* HP hs2300 a.k.a MC8775 */ + + { USB_DEVICE(0x1199, 0x0017) }, /* Sierra Wireless EM5625 */ + { USB_DEVICE(0x1199, 0x0018) }, /* Sierra Wireless MC5720 */ + { USB_DEVICE(0x1199, 0x0218) }, /* Sierra Wireless MC5720 */ + { USB_DEVICE(0x1199, 0x0020) }, /* Sierra Wireless MC5725 */ + { USB_DEVICE(0x1199, 0x0220) }, /* Sierra Wireless MC5725 */ + { USB_DEVICE(0x1199, 0x0022) }, /* Sierra Wireless EM5725 */ + { USB_DEVICE(0x1199, 0x0024) }, /* Sierra Wireless MC5727 */ + { USB_DEVICE(0x1199, 0x0224) }, /* Sierra Wireless MC5727 */ + { USB_DEVICE(0x1199, 0x0019) }, /* Sierra Wireless AirCard 595 */ + { USB_DEVICE(0x1199, 0x0021) }, /* Sierra Wireless AirCard 597E */ + { USB_DEVICE(0x1199, 0x0112) }, /* Sierra Wireless AirCard 580 */ + { USB_DEVICE(0x1199, 0x0120) }, /* Sierra Wireless USB Dongle 595U */ + { USB_DEVICE(0x1199, 0x0301) }, /* Sierra Wireless USB Dongle 250U */ + /* Sierra Wireless C597 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x0023, 0xFF, 0xFF, 0xFF) }, + /* Sierra Wireless T598 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x0025, 0xFF, 0xFF, 0xFF) }, + { USB_DEVICE(0x1199, 0x0026) }, /* Sierra Wireless T11 */ + { USB_DEVICE(0x1199, 0x0027) }, /* Sierra Wireless AC402 */ + { USB_DEVICE(0x1199, 0x0028) }, /* Sierra Wireless MC5728 */ + { USB_DEVICE(0x1199, 0x0029) }, /* Sierra Wireless Device */ + + { USB_DEVICE(0x1199, 0x6802) }, /* Sierra Wireless MC8755 */ + { USB_DEVICE(0x1199, 0x6803) }, /* Sierra Wireless MC8765 */ + { USB_DEVICE(0x1199, 0x6804) }, /* Sierra Wireless MC8755 */ + { USB_DEVICE(0x1199, 0x6805) }, /* Sierra Wireless MC8765 */ + { USB_DEVICE(0x1199, 0x6808) }, /* Sierra Wireless MC8755 */ + { USB_DEVICE(0x1199, 0x6809) }, /* Sierra Wireless MC8765 */ + { USB_DEVICE(0x1199, 0x6812) }, /* Sierra Wireless MC8775 & AC 875U */ + { USB_DEVICE(0x1199, 0x6813) }, /* Sierra Wireless MC8775 */ + { USB_DEVICE(0x1199, 0x6815) }, /* Sierra Wireless MC8775 */ + { USB_DEVICE(0x1199, 0x6816) }, /* Sierra Wireless MC8775 */ + { USB_DEVICE(0x1199, 0x6820) }, /* Sierra Wireless AirCard 875 */ + { USB_DEVICE(0x1199, 0x6821) }, /* Sierra Wireless AirCard 875U */ + { USB_DEVICE(0x1199, 0x6822) }, /* Sierra Wireless AirCard 875E */ + { USB_DEVICE(0x1199, 0x6832) }, /* Sierra Wireless MC8780 */ + { USB_DEVICE(0x1199, 0x6833) }, /* Sierra Wireless MC8781 */ + { USB_DEVICE(0x1199, 0x6834) }, /* Sierra Wireless MC8780 */ + { USB_DEVICE(0x1199, 0x6835) }, /* Sierra Wireless MC8781 */ + { USB_DEVICE(0x1199, 0x6838) }, /* Sierra Wireless MC8780 */ + { USB_DEVICE(0x1199, 0x6839) }, /* Sierra Wireless MC8781 */ + { USB_DEVICE(0x1199, 0x683A) }, /* Sierra Wireless MC8785 */ + { USB_DEVICE(0x1199, 0x683B) }, /* Sierra Wireless MC8785 Composite */ + /* Sierra Wireless MC8790, MC8791, MC8792 Composite */ + { USB_DEVICE(0x1199, 0x683C) }, + { USB_DEVICE(0x1199, 0x683D) }, /* Sierra Wireless MC8791 Composite */ + /* Sierra Wireless MC8790, MC8791, MC8792 */ + { USB_DEVICE(0x1199, 0x683E) }, + { USB_DEVICE(0x1199, 0x6850) }, /* Sierra Wireless AirCard 880 */ + { USB_DEVICE(0x1199, 0x6851) }, /* Sierra Wireless AirCard 881 */ + { USB_DEVICE(0x1199, 0x6852) }, /* Sierra Wireless AirCard 880 E */ + { USB_DEVICE(0x1199, 0x6853) }, /* Sierra Wireless AirCard 881 E */ + { USB_DEVICE(0x1199, 0x6855) }, /* Sierra Wireless AirCard 880 U */ + { USB_DEVICE(0x1199, 0x6856) }, /* Sierra Wireless AirCard 881 U */ + { USB_DEVICE(0x1199, 0x6859) }, /* Sierra Wireless AirCard 885 E */ + { USB_DEVICE(0x1199, 0x685A) }, /* Sierra Wireless AirCard 885 E */ + /* Sierra Wireless C885 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x6880, 0xFF, 0xFF, 0xFF)}, + /* Sierra Wireless C888, Air Card 501, USB 303, USB 304 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x6890, 0xFF, 0xFF, 0xFF)}, + /* Sierra Wireless C22/C33 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x6891, 0xFF, 0xFF, 0xFF)}, + /* Sierra Wireless HSPA Non-Composite Device */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x6892, 0xFF, 0xFF, 0xFF)}, + { USB_DEVICE(0x1199, 0x6893) }, /* Sierra Wireless Device */ + /* Sierra Wireless Direct IP modems */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x68A3, 0xFF, 0xFF, 0xFF), + .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist + }, + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x68AA, 0xFF, 0xFF, 0xFF), + .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist + }, + /* AT&T Direct IP LTE modems */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0F3D, 0x68AA, 0xFF, 0xFF, 0xFF), + .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist + }, + /* Airprime/Sierra Wireless Direct IP modems */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0F3D, 0x68A3, 0xFF, 0xFF, 0xFF), + .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist + }, + + { } +}; +MODULE_DEVICE_TABLE(usb, id_table); + + +struct sierra_port_private { + spinlock_t lock; /* lock the structure */ + int outstanding_urbs; /* number of out urbs in flight */ + struct usb_anchor active; + struct usb_anchor delayed; + + int num_out_urbs; + int num_in_urbs; + /* Input endpoints and buffers for this port */ + struct urb *in_urbs[N_IN_URB_HM]; + + /* Settings for the port */ + int rts_state; /* Handshaking pins (outputs) */ + int dtr_state; + int cts_state; /* Handshaking pins (inputs) */ + int dsr_state; + int dcd_state; + int ri_state; + unsigned int opened:1; +}; + +static int sierra_send_setup(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct sierra_port_private *portdata; + __u16 interface = 0; + int val = 0; + int do_send = 0; + int retval; + + portdata = usb_get_serial_port_data(port); + + if (portdata->dtr_state) + val |= 0x01; + if (portdata->rts_state) + val |= 0x02; + + /* If composite device then properly report interface */ + if (serial->num_ports == 1) { + interface = sierra_calc_interface(serial); + /* Control message is sent only to interfaces with + * interrupt_in endpoints + */ + if (port->interrupt_in_urb) { + /* send control message */ + do_send = 1; + } + } + + /* Otherwise the need to do non-composite mapping */ + else { + if (port->bulk_out_endpointAddress == 2) + interface = 0; + else if (port->bulk_out_endpointAddress == 4) + interface = 1; + else if (port->bulk_out_endpointAddress == 5) + interface = 2; + + do_send = 1; + } + if (!do_send) + return 0; + + retval = usb_autopm_get_interface(serial->interface); + if (retval < 0) + return retval; + + retval = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + 0x22, 0x21, val, interface, NULL, 0, USB_CTRL_SET_TIMEOUT); + usb_autopm_put_interface(serial->interface); + + return retval; +} + +static void sierra_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + tty_termios_copy_hw(&tty->termios, old_termios); + sierra_send_setup(port); +} + +static int sierra_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int value; + struct sierra_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + + value = ((portdata->rts_state) ? TIOCM_RTS : 0) | + ((portdata->dtr_state) ? TIOCM_DTR : 0) | + ((portdata->cts_state) ? TIOCM_CTS : 0) | + ((portdata->dsr_state) ? TIOCM_DSR : 0) | + ((portdata->dcd_state) ? TIOCM_CAR : 0) | + ((portdata->ri_state) ? TIOCM_RNG : 0); + + return value; +} + +static int sierra_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct sierra_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + + if (set & TIOCM_RTS) + portdata->rts_state = 1; + if (set & TIOCM_DTR) + portdata->dtr_state = 1; + + if (clear & TIOCM_RTS) + portdata->rts_state = 0; + if (clear & TIOCM_DTR) + portdata->dtr_state = 0; + return sierra_send_setup(port); +} + +static void sierra_release_urb(struct urb *urb) +{ + struct usb_serial_port *port; + if (urb) { + port = urb->context; + kfree(urb->transfer_buffer); + usb_free_urb(urb); + } +} + +static void sierra_outdat_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + struct sierra_intf_private *intfdata; + int status = urb->status; + + intfdata = port->serial->private; + + /* free up the transfer buffer, as usb_free_urb() does not do this */ + kfree(urb->transfer_buffer); + usb_autopm_put_interface_async(port->serial->interface); + if (status) + dev_dbg(&port->dev, "%s - nonzero write bulk status " + "received: %d\n", __func__, status); + + spin_lock(&portdata->lock); + --portdata->outstanding_urbs; + spin_unlock(&portdata->lock); + spin_lock(&intfdata->susp_lock); + --intfdata->in_flight; + spin_unlock(&intfdata->susp_lock); + + usb_serial_port_softint(port); +} + +/* Write */ +static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct sierra_port_private *portdata; + struct sierra_intf_private *intfdata; + struct usb_serial *serial = port->serial; + unsigned long flags; + unsigned char *buffer; + struct urb *urb; + size_t writesize = min((size_t)count, (size_t)MAX_TRANSFER); + int retval = 0; + + /* verify that we actually have some data to write */ + if (count == 0) + return 0; + + portdata = usb_get_serial_port_data(port); + intfdata = serial->private; + + dev_dbg(&port->dev, "%s: write (%zd bytes)\n", __func__, writesize); + spin_lock_irqsave(&portdata->lock, flags); + dev_dbg(&port->dev, "%s - outstanding_urbs: %d\n", __func__, + portdata->outstanding_urbs); + if (portdata->outstanding_urbs > portdata->num_out_urbs) { + spin_unlock_irqrestore(&portdata->lock, flags); + dev_dbg(&port->dev, "%s - write limit hit\n", __func__); + return 0; + } + portdata->outstanding_urbs++; + dev_dbg(&port->dev, "%s - 1, outstanding_urbs: %d\n", __func__, + portdata->outstanding_urbs); + spin_unlock_irqrestore(&portdata->lock, flags); + + retval = usb_autopm_get_interface_async(serial->interface); + if (retval < 0) { + spin_lock_irqsave(&portdata->lock, flags); + portdata->outstanding_urbs--; + spin_unlock_irqrestore(&portdata->lock, flags); + goto error_simple; + } + + buffer = kmalloc(writesize, GFP_ATOMIC); + if (!buffer) { + dev_err(&port->dev, "out of memory\n"); + retval = -ENOMEM; + goto error_no_buffer; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + dev_err(&port->dev, "no more free urbs\n"); + retval = -ENOMEM; + goto error_no_urb; + } + + memcpy(buffer, buf, writesize); + + usb_serial_debug_data(&port->dev, __func__, writesize, buffer); + + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), + buffer, writesize, sierra_outdat_callback, port); + + /* Handle the need to send a zero length packet */ + urb->transfer_flags |= URB_ZERO_PACKET; + + spin_lock_irqsave(&intfdata->susp_lock, flags); + + if (intfdata->suspended) { + usb_anchor_urb(urb, &portdata->delayed); + spin_unlock_irqrestore(&intfdata->susp_lock, flags); + goto skip_power; + } else { + usb_anchor_urb(urb, &portdata->active); + } + /* send it down the pipe */ + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) { + usb_unanchor_urb(urb); + spin_unlock_irqrestore(&intfdata->susp_lock, flags); + dev_err(&port->dev, "%s - usb_submit_urb(write bulk) failed " + "with status = %d\n", __func__, retval); + goto error; + } else { + intfdata->in_flight++; + spin_unlock_irqrestore(&intfdata->susp_lock, flags); + } + +skip_power: + /* we are done with this urb, so let the host driver + * really free it when it is finished with it */ + usb_free_urb(urb); + + return writesize; +error: + usb_free_urb(urb); +error_no_urb: + kfree(buffer); +error_no_buffer: + spin_lock_irqsave(&portdata->lock, flags); + --portdata->outstanding_urbs; + dev_dbg(&port->dev, "%s - 2. outstanding_urbs: %d\n", __func__, + portdata->outstanding_urbs); + spin_unlock_irqrestore(&portdata->lock, flags); + usb_autopm_put_interface_async(serial->interface); +error_simple: + return retval; +} + +static void sierra_indat_callback(struct urb *urb) +{ + int err; + int endpoint; + struct usb_serial_port *port; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + endpoint = usb_pipeendpoint(urb->pipe); + port = urb->context; + + if (status) { + dev_dbg(&port->dev, "%s: nonzero status: %d on" + " endpoint %02x\n", __func__, status, endpoint); + } else { + if (urb->actual_length) { + tty_insert_flip_string(&port->port, data, + urb->actual_length); + tty_flip_buffer_push(&port->port); + + usb_serial_debug_data(&port->dev, __func__, + urb->actual_length, data); + } else { + dev_dbg(&port->dev, "%s: empty read urb" + " received\n", __func__); + } + } + + /* Resubmit urb so we continue receiving */ + if (status != -ESHUTDOWN && status != -EPERM) { + usb_mark_last_busy(port->serial->dev); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err && err != -EPERM) + dev_err(&port->dev, "resubmit read urb failed." + "(%d)\n", err); + } +} + +static void sierra_instat_callback(struct urb *urb) +{ + int err; + int status = urb->status; + struct usb_serial_port *port = urb->context; + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + + dev_dbg(&port->dev, "%s: urb %p port %p has data %p\n", __func__, + urb, port, portdata); + + if (status == 0) { + struct usb_ctrlrequest *req_pkt = + (struct usb_ctrlrequest *)urb->transfer_buffer; + + if (!req_pkt) { + dev_dbg(&port->dev, "%s: NULL req_pkt\n", + __func__); + return; + } + if ((req_pkt->bRequestType == 0xA1) && + (req_pkt->bRequest == 0x20)) { + int old_dcd_state; + unsigned char signals = *((unsigned char *) + urb->transfer_buffer + + sizeof(struct usb_ctrlrequest)); + + dev_dbg(&port->dev, "%s: signal x%x\n", __func__, + signals); + + old_dcd_state = portdata->dcd_state; + portdata->cts_state = 1; + portdata->dcd_state = ((signals & 0x01) ? 1 : 0); + portdata->dsr_state = ((signals & 0x02) ? 1 : 0); + portdata->ri_state = ((signals & 0x08) ? 1 : 0); + + if (old_dcd_state && !portdata->dcd_state) + tty_port_tty_hangup(&port->port, true); + } else { + dev_dbg(&port->dev, "%s: type %x req %x\n", + __func__, req_pkt->bRequestType, + req_pkt->bRequest); + } + } else + dev_dbg(&port->dev, "%s: error %d\n", __func__, status); + + /* Resubmit urb so we continue receiving IRQ data */ + if (status != -ESHUTDOWN && status != -ENOENT) { + usb_mark_last_busy(serial->dev); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err && err != -EPERM) + dev_err(&port->dev, "%s: resubmit intr urb " + "failed. (%d)\n", __func__, err); + } +} + +static int sierra_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + unsigned long flags; + + /* try to give a good number back based on if we have any free urbs at + * this point in time */ + spin_lock_irqsave(&portdata->lock, flags); + if (portdata->outstanding_urbs > (portdata->num_out_urbs * 2) / 3) { + spin_unlock_irqrestore(&portdata->lock, flags); + dev_dbg(&port->dev, "%s - write limit hit\n", __func__); + return 0; + } + spin_unlock_irqrestore(&portdata->lock, flags); + + return 2048; +} + +static void sierra_stop_rx_urbs(struct usb_serial_port *port) +{ + int i; + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + + for (i = 0; i < portdata->num_in_urbs; i++) + usb_kill_urb(portdata->in_urbs[i]); + + usb_kill_urb(port->interrupt_in_urb); +} + +static int sierra_submit_rx_urbs(struct usb_serial_port *port, gfp_t mem_flags) +{ + int ok_cnt; + int err = -EINVAL; + int i; + struct urb *urb; + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + + ok_cnt = 0; + for (i = 0; i < portdata->num_in_urbs; i++) { + urb = portdata->in_urbs[i]; + if (!urb) + continue; + err = usb_submit_urb(urb, mem_flags); + if (err) { + dev_err(&port->dev, "%s: submit urb failed: %d\n", + __func__, err); + } else { + ok_cnt++; + } + } + + if (ok_cnt && port->interrupt_in_urb) { + err = usb_submit_urb(port->interrupt_in_urb, mem_flags); + if (err) { + dev_err(&port->dev, "%s: submit intr urb failed: %d\n", + __func__, err); + } + } + + if (ok_cnt > 0) /* at least one rx urb submitted */ + return 0; + else + return err; +} + +static struct urb *sierra_setup_urb(struct usb_serial *serial, int endpoint, + int dir, void *ctx, int len, + gfp_t mem_flags, + usb_complete_t callback) +{ + struct urb *urb; + u8 *buf; + + if (endpoint == -1) + return NULL; + + urb = usb_alloc_urb(0, mem_flags); + if (urb == NULL) { + dev_dbg(&serial->dev->dev, "%s: alloc for endpoint %d failed\n", + __func__, endpoint); + return NULL; + } + + buf = kmalloc(len, mem_flags); + if (buf) { + /* Fill URB using supplied data */ + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, endpoint) | dir, + buf, len, callback, ctx); + + dev_dbg(&serial->dev->dev, "%s %c u : %p d:%p\n", __func__, + dir == USB_DIR_IN ? 'i' : 'o', urb, buf); + } else { + dev_dbg(&serial->dev->dev, "%s %c u:%p d:%p\n", __func__, + dir == USB_DIR_IN ? 'i' : 'o', urb, buf); + + sierra_release_urb(urb); + urb = NULL; + } + + return urb; +} + +static void sierra_close(struct usb_serial_port *port) +{ + int i; + struct usb_serial *serial = port->serial; + struct sierra_port_private *portdata; + struct sierra_intf_private *intfdata = port->serial->private; + struct urb *urb; + + portdata = usb_get_serial_port_data(port); + + portdata->rts_state = 0; + portdata->dtr_state = 0; + + mutex_lock(&serial->disc_mutex); + if (!serial->disconnected) { + /* odd error handling due to pm counters */ + if (!usb_autopm_get_interface(serial->interface)) + sierra_send_setup(port); + else + usb_autopm_get_interface_no_resume(serial->interface); + + } + mutex_unlock(&serial->disc_mutex); + spin_lock_irq(&intfdata->susp_lock); + portdata->opened = 0; + if (--intfdata->open_ports == 0) + serial->interface->needs_remote_wakeup = 0; + spin_unlock_irq(&intfdata->susp_lock); + + for (;;) { + urb = usb_get_from_anchor(&portdata->delayed); + if (!urb) + break; + kfree(urb->transfer_buffer); + usb_free_urb(urb); + usb_autopm_put_interface_async(serial->interface); + spin_lock(&portdata->lock); + portdata->outstanding_urbs--; + spin_unlock(&portdata->lock); + } + + sierra_stop_rx_urbs(port); + for (i = 0; i < portdata->num_in_urbs; i++) { + sierra_release_urb(portdata->in_urbs[i]); + portdata->in_urbs[i] = NULL; + } +} + +static int sierra_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct sierra_port_private *portdata; + struct usb_serial *serial = port->serial; + struct sierra_intf_private *intfdata = serial->private; + int i; + int err; + int endpoint; + struct urb *urb; + + portdata = usb_get_serial_port_data(port); + + /* Set some sane defaults */ + portdata->rts_state = 1; + portdata->dtr_state = 1; + + + endpoint = port->bulk_in_endpointAddress; + for (i = 0; i < portdata->num_in_urbs; i++) { + urb = sierra_setup_urb(serial, endpoint, USB_DIR_IN, port, + IN_BUFLEN, GFP_KERNEL, + sierra_indat_callback); + portdata->in_urbs[i] = urb; + } + /* clear halt condition */ + usb_clear_halt(serial->dev, + usb_sndbulkpipe(serial->dev, endpoint) | USB_DIR_IN); + + err = sierra_submit_rx_urbs(port, GFP_KERNEL); + if (err) + goto err_submit; + + sierra_send_setup(port); + + spin_lock_irq(&intfdata->susp_lock); + portdata->opened = 1; + if (++intfdata->open_ports == 1) + serial->interface->needs_remote_wakeup = 1; + spin_unlock_irq(&intfdata->susp_lock); + usb_autopm_put_interface(serial->interface); + + return 0; + +err_submit: + sierra_stop_rx_urbs(port); + + for (i = 0; i < portdata->num_in_urbs; i++) { + sierra_release_urb(portdata->in_urbs[i]); + portdata->in_urbs[i] = NULL; + } + + return err; +} + + +static void sierra_dtr_rts(struct usb_serial_port *port, int on) +{ + struct sierra_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + portdata->rts_state = on; + portdata->dtr_state = on; + + sierra_send_setup(port); +} + +static int sierra_startup(struct usb_serial *serial) +{ + struct sierra_intf_private *intfdata; + + intfdata = kzalloc(sizeof(*intfdata), GFP_KERNEL); + if (!intfdata) + return -ENOMEM; + + spin_lock_init(&intfdata->susp_lock); + + usb_set_serial_data(serial, intfdata); + + /* Set Device mode to D0 */ + sierra_set_power_state(serial->dev, 0x0000); + + /* Check NMEA and set */ + if (nmea) + sierra_vsc_set_nmea(serial->dev, 1); + + return 0; +} + +static void sierra_release(struct usb_serial *serial) +{ + struct sierra_intf_private *intfdata; + + intfdata = usb_get_serial_data(serial); + kfree(intfdata); +} + +static int sierra_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct sierra_port_private *portdata; + const struct sierra_iface_info *himemoryp; + u8 ifnum; + + portdata = kzalloc(sizeof(*portdata), GFP_KERNEL); + if (!portdata) + return -ENOMEM; + + spin_lock_init(&portdata->lock); + init_usb_anchor(&portdata->active); + init_usb_anchor(&portdata->delayed); + + /* Assume low memory requirements */ + portdata->num_out_urbs = N_OUT_URB; + portdata->num_in_urbs = N_IN_URB; + + /* Determine actual memory requirements */ + if (serial->num_ports == 1) { + /* Get interface number for composite device */ + ifnum = sierra_calc_interface(serial); + himemoryp = &typeB_interface_list; + } else { + /* This is really the usb-serial port number of the interface + * rather than the interface number. + */ + ifnum = port->number - serial->minor; + himemoryp = &typeA_interface_list; + } + + if (is_himemory(ifnum, himemoryp)) { + portdata->num_out_urbs = N_OUT_URB_HM; + portdata->num_in_urbs = N_IN_URB_HM; + } + + dev_dbg(&port->dev, + "Memory usage (urbs) interface #%d, in=%d, out=%d\n", + ifnum, portdata->num_in_urbs, portdata->num_out_urbs); + + usb_set_serial_port_data(port, portdata); + + return 0; +} + +static int sierra_port_remove(struct usb_serial_port *port) +{ + struct sierra_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + usb_set_serial_port_data(port, NULL); + kfree(portdata); + + return 0; +} + +#ifdef CONFIG_PM +static void stop_read_write_urbs(struct usb_serial *serial) +{ + int i; + struct usb_serial_port *port; + struct sierra_port_private *portdata; + + /* Stop reading/writing urbs */ + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + portdata = usb_get_serial_port_data(port); + if (!portdata) + continue; + sierra_stop_rx_urbs(port); + usb_kill_anchored_urbs(&portdata->active); + } +} + +static int sierra_suspend(struct usb_serial *serial, pm_message_t message) +{ + struct sierra_intf_private *intfdata; + int b; + + if (PMSG_IS_AUTO(message)) { + intfdata = serial->private; + spin_lock_irq(&intfdata->susp_lock); + b = intfdata->in_flight; + + if (b) { + spin_unlock_irq(&intfdata->susp_lock); + return -EBUSY; + } else { + intfdata->suspended = 1; + spin_unlock_irq(&intfdata->susp_lock); + } + } + stop_read_write_urbs(serial); + + return 0; +} + +static int sierra_resume(struct usb_serial *serial) +{ + struct usb_serial_port *port; + struct sierra_intf_private *intfdata = serial->private; + struct sierra_port_private *portdata; + struct urb *urb; + int ec = 0; + int i, err; + + spin_lock_irq(&intfdata->susp_lock); + for (i = 0; i < serial->num_ports; i++) { + port = serial->port[i]; + portdata = usb_get_serial_port_data(port); + + if (!portdata) + continue; + + while ((urb = usb_get_from_anchor(&portdata->delayed))) { + usb_anchor_urb(urb, &portdata->active); + intfdata->in_flight++; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + intfdata->in_flight--; + usb_unanchor_urb(urb); + kfree(urb->transfer_buffer); + usb_free_urb(urb); + spin_lock(&portdata->lock); + portdata->outstanding_urbs--; + spin_unlock(&portdata->lock); + continue; + } + } + + if (portdata->opened) { + err = sierra_submit_rx_urbs(port, GFP_ATOMIC); + if (err) + ec++; + } + } + intfdata->suspended = 0; + spin_unlock_irq(&intfdata->susp_lock); + + return ec ? -EIO : 0; +} + +#else +#define sierra_suspend NULL +#define sierra_resume NULL +#endif + +static struct usb_serial_driver sierra_device = { + .driver = { + .owner = THIS_MODULE, + .name = "sierra", + }, + .description = "Sierra USB modem", + .id_table = id_table, + .calc_num_ports = sierra_calc_num_ports, + .probe = sierra_probe, + .open = sierra_open, + .close = sierra_close, + .dtr_rts = sierra_dtr_rts, + .write = sierra_write, + .write_room = sierra_write_room, + .set_termios = sierra_set_termios, + .tiocmget = sierra_tiocmget, + .tiocmset = sierra_tiocmset, + .attach = sierra_startup, + .release = sierra_release, + .port_probe = sierra_port_probe, + .port_remove = sierra_port_remove, + .suspend = sierra_suspend, + .resume = sierra_resume, + .read_int_callback = sierra_instat_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &sierra_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(nmea, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(nmea, "NMEA streaming"); diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c new file mode 100644 index 00000000..1694d4ff --- /dev/null +++ b/drivers/usb/serial/spcp8x5.c @@ -0,0 +1,493 @@ +/* + * spcp8x5 USB to serial adaptor driver + * + * Copyright (C) 2010-2013 Johan Hovold (jhovold@gmail.com) + * Copyright (C) 2006 Linxb (xubin.lin@worldplus.com.cn) + * Copyright (C) 2006 S1 Corp. + * + * Original driver for 2.6.10 pl2303 driver by + * Greg Kroah-Hartman (greg@kroah.com) + * Changes for 2.6.20 by Harald Klein + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_DESC "SPCP8x5 USB to serial adaptor driver" + +#define SPCP825_QUIRK_NO_UART_STATUS 0x01 +#define SPCP825_QUIRK_NO_WORK_MODE 0x02 + +#define SPCP8x5_007_VID 0x04FC +#define SPCP8x5_007_PID 0x0201 +#define SPCP8x5_008_VID 0x04fc +#define SPCP8x5_008_PID 0x0235 +#define SPCP8x5_PHILIPS_VID 0x0471 +#define SPCP8x5_PHILIPS_PID 0x081e +#define SPCP8x5_INTERMATIC_VID 0x04FC +#define SPCP8x5_INTERMATIC_PID 0x0204 +#define SPCP8x5_835_VID 0x04fc +#define SPCP8x5_835_PID 0x0231 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(SPCP8x5_PHILIPS_VID , SPCP8x5_PHILIPS_PID)}, + { USB_DEVICE(SPCP8x5_INTERMATIC_VID, SPCP8x5_INTERMATIC_PID)}, + { USB_DEVICE(SPCP8x5_835_VID, SPCP8x5_835_PID)}, + { USB_DEVICE(SPCP8x5_008_VID, SPCP8x5_008_PID)}, + { USB_DEVICE(SPCP8x5_007_VID, SPCP8x5_007_PID), + .driver_info = SPCP825_QUIRK_NO_UART_STATUS | + SPCP825_QUIRK_NO_WORK_MODE }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct spcp8x5_usb_ctrl_arg { + u8 type; + u8 cmd; + u8 cmd_type; + u16 value; + u16 index; + u16 length; +}; + + +/* spcp8x5 spec register define */ +#define MCR_CONTROL_LINE_RTS 0x02 +#define MCR_CONTROL_LINE_DTR 0x01 +#define MCR_DTR 0x01 +#define MCR_RTS 0x02 + +#define MSR_STATUS_LINE_DCD 0x80 +#define MSR_STATUS_LINE_RI 0x40 +#define MSR_STATUS_LINE_DSR 0x20 +#define MSR_STATUS_LINE_CTS 0x10 + +/* verdor command here , we should define myself */ +#define SET_DEFAULT 0x40 +#define SET_DEFAULT_TYPE 0x20 + +#define SET_UART_FORMAT 0x40 +#define SET_UART_FORMAT_TYPE 0x21 +#define SET_UART_FORMAT_SIZE_5 0x00 +#define SET_UART_FORMAT_SIZE_6 0x01 +#define SET_UART_FORMAT_SIZE_7 0x02 +#define SET_UART_FORMAT_SIZE_8 0x03 +#define SET_UART_FORMAT_STOP_1 0x00 +#define SET_UART_FORMAT_STOP_2 0x04 +#define SET_UART_FORMAT_PAR_NONE 0x00 +#define SET_UART_FORMAT_PAR_ODD 0x10 +#define SET_UART_FORMAT_PAR_EVEN 0x30 +#define SET_UART_FORMAT_PAR_MASK 0xD0 +#define SET_UART_FORMAT_PAR_SPACE 0x90 + +#define GET_UART_STATUS_TYPE 0xc0 +#define GET_UART_STATUS 0x22 +#define GET_UART_STATUS_MSR 0x06 + +#define SET_UART_STATUS 0x40 +#define SET_UART_STATUS_TYPE 0x23 +#define SET_UART_STATUS_MCR 0x0004 +#define SET_UART_STATUS_MCR_DTR 0x01 +#define SET_UART_STATUS_MCR_RTS 0x02 +#define SET_UART_STATUS_MCR_LOOP 0x10 + +#define SET_WORKING_MODE 0x40 +#define SET_WORKING_MODE_TYPE 0x24 +#define SET_WORKING_MODE_U2C 0x00 +#define SET_WORKING_MODE_RS485 0x01 +#define SET_WORKING_MODE_PDMA 0x02 +#define SET_WORKING_MODE_SPP 0x03 + +#define SET_FLOWCTL_CHAR 0x40 +#define SET_FLOWCTL_CHAR_TYPE 0x25 + +#define GET_VERSION 0xc0 +#define GET_VERSION_TYPE 0x26 + +#define SET_REGISTER 0x40 +#define SET_REGISTER_TYPE 0x27 + +#define GET_REGISTER 0xc0 +#define GET_REGISTER_TYPE 0x28 + +#define SET_RAM 0x40 +#define SET_RAM_TYPE 0x31 + +#define GET_RAM 0xc0 +#define GET_RAM_TYPE 0x32 + +/* how come ??? */ +#define UART_STATE 0x08 +#define UART_STATE_TRANSIENT_MASK 0x75 +#define UART_DCD 0x01 +#define UART_DSR 0x02 +#define UART_BREAK_ERROR 0x04 +#define UART_RING 0x08 +#define UART_FRAME_ERROR 0x10 +#define UART_PARITY_ERROR 0x20 +#define UART_OVERRUN_ERROR 0x40 +#define UART_CTS 0x80 + +struct spcp8x5_private { + unsigned quirks; + spinlock_t lock; + u8 line_control; +}; + +static int spcp8x5_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + usb_set_serial_data(serial, (void *)id); + + return 0; +} + +static int spcp8x5_port_probe(struct usb_serial_port *port) +{ + const struct usb_device_id *id = usb_get_serial_data(port->serial); + struct spcp8x5_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + priv->quirks = id->driver_info; + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int spcp8x5_port_remove(struct usb_serial_port *port) +{ + struct spcp8x5_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int spcp8x5_set_ctrl_line(struct usb_serial_port *port, u8 mcr) +{ + struct spcp8x5_private *priv = usb_get_serial_port_data(port); + struct usb_device *dev = port->serial->dev; + int retval; + + if (priv->quirks & SPCP825_QUIRK_NO_UART_STATUS) + return -EPERM; + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + SET_UART_STATUS_TYPE, SET_UART_STATUS, + mcr, 0x04, NULL, 0, 100); + if (retval != 0) { + dev_err(&port->dev, "failed to set control lines: %d\n", + retval); + } + return retval; +} + +static int spcp8x5_get_msr(struct usb_serial_port *port, u8 *status) +{ + struct spcp8x5_private *priv = usb_get_serial_port_data(port); + struct usb_device *dev = port->serial->dev; + u8 *buf; + int ret; + + if (priv->quirks & SPCP825_QUIRK_NO_UART_STATUS) + return -EPERM; + + buf = kzalloc(1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + GET_UART_STATUS, GET_UART_STATUS_TYPE, + 0, GET_UART_STATUS_MSR, buf, 1, 100); + if (ret < 0) + dev_err(&port->dev, "failed to get modem status: %d", ret); + + dev_dbg(&port->dev, "0xc0:0x22:0:6 %d - 0x02%x", ret, *buf); + *status = *buf; + kfree(buf); + + return ret; +} + +static void spcp8x5_set_work_mode(struct usb_serial_port *port, u16 value, + u16 index) +{ + struct spcp8x5_private *priv = usb_get_serial_port_data(port); + struct usb_device *dev = port->serial->dev; + int ret; + + if (priv->quirks & SPCP825_QUIRK_NO_WORK_MODE) + return; + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + SET_WORKING_MODE_TYPE, SET_WORKING_MODE, + value, index, NULL, 0, 100); + dev_dbg(&port->dev, "value = %#x , index = %#x\n", value, index); + if (ret < 0) + dev_err(&port->dev, "failed to set work mode: %d\n", ret); +} + +static int spcp8x5_carrier_raised(struct usb_serial_port *port) +{ + u8 msr; + int ret; + + ret = spcp8x5_get_msr(port, &msr); + if (ret || msr & MSR_STATUS_LINE_DCD) + return 1; + + return 0; +} + +static void spcp8x5_dtr_rts(struct usb_serial_port *port, int on) +{ + struct spcp8x5_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 control; + + spin_lock_irqsave(&priv->lock, flags); + if (on) + priv->line_control = MCR_CONTROL_LINE_DTR + | MCR_CONTROL_LINE_RTS; + else + priv->line_control &= ~ (MCR_CONTROL_LINE_DTR + | MCR_CONTROL_LINE_RTS); + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + spcp8x5_set_ctrl_line(port, control); +} + +static void spcp8x5_init_termios(struct tty_struct *tty) +{ + tty->termios = tty_std_termios; + tty->termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; + tty->termios.c_ispeed = 115200; + tty->termios.c_ospeed = 115200; +} + +static void spcp8x5_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct usb_serial *serial = port->serial; + struct spcp8x5_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int cflag = tty->termios.c_cflag; + unsigned short uartdata; + unsigned char buf[2] = {0, 0}; + int baud; + int i; + u8 control; + + /* check that they really want us to change something */ + if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) + return; + + /* set DTR/RTS active */ + spin_lock_irqsave(&priv->lock, flags); + control = priv->line_control; + if (old_termios && (old_termios->c_cflag & CBAUD) == B0) { + priv->line_control |= MCR_DTR; + if (!(old_termios->c_cflag & CRTSCTS)) + priv->line_control |= MCR_RTS; + } + if (control != priv->line_control) { + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + spcp8x5_set_ctrl_line(port, control); + } else { + spin_unlock_irqrestore(&priv->lock, flags); + } + + /* Set Baud Rate */ + baud = tty_get_baud_rate(tty); + switch (baud) { + case 300: buf[0] = 0x00; break; + case 600: buf[0] = 0x01; break; + case 1200: buf[0] = 0x02; break; + case 2400: buf[0] = 0x03; break; + case 4800: buf[0] = 0x04; break; + case 9600: buf[0] = 0x05; break; + case 19200: buf[0] = 0x07; break; + case 38400: buf[0] = 0x09; break; + case 57600: buf[0] = 0x0a; break; + case 115200: buf[0] = 0x0b; break; + case 230400: buf[0] = 0x0c; break; + case 460800: buf[0] = 0x0d; break; + case 921600: buf[0] = 0x0e; break; +/* case 1200000: buf[0] = 0x0f; break; */ +/* case 2400000: buf[0] = 0x10; break; */ + case 3000000: buf[0] = 0x11; break; +/* case 6000000: buf[0] = 0x12; break; */ + case 0: + case 1000000: + buf[0] = 0x0b; break; + default: + dev_err(&port->dev, "spcp825 driver does not support the " + "baudrate requested, using default of 9600.\n"); + } + + /* Set Data Length : 00:5bit, 01:6bit, 10:7bit, 11:8bit */ + switch (cflag & CSIZE) { + case CS5: + buf[1] |= SET_UART_FORMAT_SIZE_5; + break; + case CS6: + buf[1] |= SET_UART_FORMAT_SIZE_6; + break; + case CS7: + buf[1] |= SET_UART_FORMAT_SIZE_7; + break; + default: + case CS8: + buf[1] |= SET_UART_FORMAT_SIZE_8; + break; + } + + /* Set Stop bit2 : 0:1bit 1:2bit */ + buf[1] |= (cflag & CSTOPB) ? SET_UART_FORMAT_STOP_2 : + SET_UART_FORMAT_STOP_1; + + /* Set Parity bit3-4 01:Odd 11:Even */ + if (cflag & PARENB) { + buf[1] |= (cflag & PARODD) ? + SET_UART_FORMAT_PAR_ODD : SET_UART_FORMAT_PAR_EVEN ; + } else { + buf[1] |= SET_UART_FORMAT_PAR_NONE; + } + uartdata = buf[0] | buf[1]<<8; + + i = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + SET_UART_FORMAT_TYPE, SET_UART_FORMAT, + uartdata, 0, NULL, 0, 100); + if (i < 0) + dev_err(&port->dev, "Set UART format %#x failed (error = %d)\n", + uartdata, i); + dev_dbg(&port->dev, "0x21:0x40:0:0 %d\n", i); + + if (cflag & CRTSCTS) { + /* enable hardware flow control */ + spcp8x5_set_work_mode(port, 0x000a, SET_WORKING_MODE_U2C); + } +} + +static int spcp8x5_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct spcp8x5_private *priv = usb_get_serial_port_data(port); + int ret; + + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + + ret = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + 0x09, 0x00, + 0x01, 0x00, NULL, 0x00, 100); + if (ret) + return ret; + + spcp8x5_set_ctrl_line(port, priv->line_control); + + if (tty) + spcp8x5_set_termios(tty, port, NULL); + + port->port.drain_delay = 256; + + return usb_serial_generic_open(tty, port); +} + +static int spcp8x5_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct spcp8x5_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + u8 control; + + spin_lock_irqsave(&priv->lock, flags); + if (set & TIOCM_RTS) + priv->line_control |= MCR_RTS; + if (set & TIOCM_DTR) + priv->line_control |= MCR_DTR; + if (clear & TIOCM_RTS) + priv->line_control &= ~MCR_RTS; + if (clear & TIOCM_DTR) + priv->line_control &= ~MCR_DTR; + control = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + + return spcp8x5_set_ctrl_line(port, control); +} + +static int spcp8x5_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct spcp8x5_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + unsigned int mcr; + u8 status; + unsigned int result; + + result = spcp8x5_get_msr(port, &status); + if (result) + return result; + + spin_lock_irqsave(&priv->lock, flags); + mcr = priv->line_control; + spin_unlock_irqrestore(&priv->lock, flags); + + result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0) + | ((mcr & MCR_RTS) ? TIOCM_RTS : 0) + | ((status & MSR_STATUS_LINE_CTS) ? TIOCM_CTS : 0) + | ((status & MSR_STATUS_LINE_DSR) ? TIOCM_DSR : 0) + | ((status & MSR_STATUS_LINE_RI) ? TIOCM_RI : 0) + | ((status & MSR_STATUS_LINE_DCD) ? TIOCM_CD : 0); + + return result; +} + +static struct usb_serial_driver spcp8x5_device = { + .driver = { + .owner = THIS_MODULE, + .name = "SPCP8x5", + }, + .id_table = id_table, + .num_ports = 1, + .open = spcp8x5_open, + .dtr_rts = spcp8x5_dtr_rts, + .carrier_raised = spcp8x5_carrier_raised, + .set_termios = spcp8x5_set_termios, + .init_termios = spcp8x5_init_termios, + .tiocmget = spcp8x5_tiocmget, + .tiocmset = spcp8x5_tiocmset, + .probe = spcp8x5_probe, + .port_probe = spcp8x5_port_probe, + .port_remove = spcp8x5_port_remove, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &spcp8x5_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c new file mode 100644 index 00000000..8f5f3613 --- /dev/null +++ b/drivers/usb/serial/ssu100.c @@ -0,0 +1,571 @@ +/* + * usb-serial driver for Quatech SSU-100 + * + * based on ftdi_sio.c and the original serqt_usb.c from Quatech + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QT_OPEN_CLOSE_CHANNEL 0xca +#define QT_SET_GET_DEVICE 0xc2 +#define QT_SET_GET_REGISTER 0xc0 +#define QT_GET_SET_PREBUF_TRIG_LVL 0xcc +#define QT_SET_ATF 0xcd +#define QT_GET_SET_UART 0xc1 +#define QT_TRANSFER_IN 0xc0 +#define QT_HW_FLOW_CONTROL_MASK 0xc5 +#define QT_SW_FLOW_CONTROL_MASK 0xc6 + +#define SERIAL_MSR_MASK 0xf0 + +#define SERIAL_CRTSCTS ((UART_MCR_RTS << 8) | UART_MSR_CTS) + +#define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) + +#define MAX_BAUD_RATE 460800 + +#define ATC_DISABLED 0x00 +#define DUPMODE_BITS 0xc0 +#define RR_BITS 0x03 +#define LOOPMODE_BITS 0x41 +#define RS232_MODE 0x00 +#define RTSCTS_TO_CONNECTOR 0x40 +#define CLKS_X4 0x02 +#define FULLPWRBIT 0x00000080 +#define NEXT_BOARD_POWER_BIT 0x00000004 + +#define DRIVER_DESC "Quatech SSU-100 USB to Serial Driver" + +#define USB_VENDOR_ID_QUATECH 0x061d /* Quatech VID */ +#define QUATECH_SSU100 0xC020 /* SSU100 */ + +static const struct usb_device_id id_table[] = { + {USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_SSU100)}, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct ssu100_port_private { + spinlock_t status_lock; + u8 shadowLSR; + u8 shadowMSR; +}; + +static inline int ssu100_control_msg(struct usb_device *dev, + u8 request, u16 data, u16 index) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + request, 0x40, data, index, + NULL, 0, 300); +} + +static inline int ssu100_setdevice(struct usb_device *dev, u8 *data) +{ + u16 x = ((u16)(data[1] << 8) | (u16)(data[0])); + + return ssu100_control_msg(dev, QT_SET_GET_DEVICE, x, 0); +} + + +static inline int ssu100_getdevice(struct usb_device *dev, u8 *data) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + QT_SET_GET_DEVICE, 0xc0, 0, 0, + data, 3, 300); +} + +static inline int ssu100_getregister(struct usb_device *dev, + unsigned short uart, + unsigned short reg, + u8 *data) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + QT_SET_GET_REGISTER, 0xc0, reg, + uart, data, sizeof(*data), 300); + +} + + +static inline int ssu100_setregister(struct usb_device *dev, + unsigned short uart, + unsigned short reg, + u16 data) +{ + u16 value = (data << 8) | reg; + + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + QT_SET_GET_REGISTER, 0x40, value, uart, + NULL, 0, 300); + +} + +#define set_mctrl(dev, set) update_mctrl((dev), (set), 0) +#define clear_mctrl(dev, clear) update_mctrl((dev), 0, (clear)) + +/* these do not deal with device that have more than 1 port */ +static inline int update_mctrl(struct usb_device *dev, unsigned int set, + unsigned int clear) +{ + unsigned urb_value; + int result; + + if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0) { + dev_dbg(&dev->dev, "%s - DTR|RTS not being set|cleared\n", __func__); + return 0; /* no change */ + } + + clear &= ~set; /* 'set' takes precedence over 'clear' */ + urb_value = 0; + if (set & TIOCM_DTR) + urb_value |= UART_MCR_DTR; + if (set & TIOCM_RTS) + urb_value |= UART_MCR_RTS; + + result = ssu100_setregister(dev, 0, UART_MCR, urb_value); + if (result < 0) + dev_dbg(&dev->dev, "%s Error from MODEM_CTRL urb\n", __func__); + + return result; +} + +static int ssu100_initdevice(struct usb_device *dev) +{ + u8 *data; + int result = 0; + + data = kzalloc(3, GFP_KERNEL); + if (!data) + return -ENOMEM; + + result = ssu100_getdevice(dev, data); + if (result < 0) { + dev_dbg(&dev->dev, "%s - get_device failed %i\n", __func__, result); + goto out; + } + + data[1] &= ~FULLPWRBIT; + + result = ssu100_setdevice(dev, data); + if (result < 0) { + dev_dbg(&dev->dev, "%s - setdevice failed %i\n", __func__, result); + goto out; + } + + result = ssu100_control_msg(dev, QT_GET_SET_PREBUF_TRIG_LVL, 128, 0); + if (result < 0) { + dev_dbg(&dev->dev, "%s - set prebuffer level failed %i\n", __func__, result); + goto out; + } + + result = ssu100_control_msg(dev, QT_SET_ATF, ATC_DISABLED, 0); + if (result < 0) { + dev_dbg(&dev->dev, "%s - set ATFprebuffer level failed %i\n", __func__, result); + goto out; + } + + result = ssu100_getdevice(dev, data); + if (result < 0) { + dev_dbg(&dev->dev, "%s - get_device failed %i\n", __func__, result); + goto out; + } + + data[0] &= ~(RR_BITS | DUPMODE_BITS); + data[0] |= CLKS_X4; + data[1] &= ~(LOOPMODE_BITS); + data[1] |= RS232_MODE; + + result = ssu100_setdevice(dev, data); + if (result < 0) { + dev_dbg(&dev->dev, "%s - setdevice failed %i\n", __func__, result); + goto out; + } + +out: kfree(data); + return result; + +} + + +static void ssu100_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct usb_device *dev = port->serial->dev; + struct ktermios *termios = &tty->termios; + u16 baud, divisor, remainder; + unsigned int cflag = termios->c_cflag; + u16 urb_value = 0; /* will hold the new flags */ + int result; + + if (cflag & PARENB) { + if (cflag & PARODD) + urb_value |= UART_LCR_PARITY; + else + urb_value |= SERIAL_EVEN_PARITY; + } + + switch (cflag & CSIZE) { + case CS5: + urb_value |= UART_LCR_WLEN5; + break; + case CS6: + urb_value |= UART_LCR_WLEN6; + break; + case CS7: + urb_value |= UART_LCR_WLEN7; + break; + default: + case CS8: + urb_value |= UART_LCR_WLEN8; + break; + } + + baud = tty_get_baud_rate(tty); + if (!baud) + baud = 9600; + + dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud); + + + divisor = MAX_BAUD_RATE / baud; + remainder = MAX_BAUD_RATE % baud; + if (((remainder * 2) >= baud) && (baud != 110)) + divisor++; + + urb_value = urb_value << 8; + + result = ssu100_control_msg(dev, QT_GET_SET_UART, divisor, urb_value); + if (result < 0) + dev_dbg(&port->dev, "%s - set uart failed\n", __func__); + + if (cflag & CRTSCTS) + result = ssu100_control_msg(dev, QT_HW_FLOW_CONTROL_MASK, + SERIAL_CRTSCTS, 0); + else + result = ssu100_control_msg(dev, QT_HW_FLOW_CONTROL_MASK, + 0, 0); + if (result < 0) + dev_dbg(&port->dev, "%s - set HW flow control failed\n", __func__); + + if (I_IXOFF(tty) || I_IXON(tty)) { + u16 x = ((u16)(START_CHAR(tty) << 8) | (u16)(STOP_CHAR(tty))); + + result = ssu100_control_msg(dev, QT_SW_FLOW_CONTROL_MASK, + x, 0); + } else + result = ssu100_control_msg(dev, QT_SW_FLOW_CONTROL_MASK, + 0, 0); + + if (result < 0) + dev_dbg(&port->dev, "%s - set SW flow control failed\n", __func__); + +} + + +static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_device *dev = port->serial->dev; + struct ssu100_port_private *priv = usb_get_serial_port_data(port); + u8 *data; + int result; + unsigned long flags; + + data = kzalloc(2, GFP_KERNEL); + if (!data) + return -ENOMEM; + + result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + QT_OPEN_CLOSE_CHANNEL, + QT_TRANSFER_IN, 0x01, + 0, data, 2, 300); + if (result < 0) { + dev_dbg(&port->dev, "%s - open failed %i\n", __func__, result); + kfree(data); + return result; + } + + spin_lock_irqsave(&priv->status_lock, flags); + priv->shadowLSR = data[0]; + priv->shadowMSR = data[1]; + spin_unlock_irqrestore(&priv->status_lock, flags); + + kfree(data); + +/* set to 9600 */ + result = ssu100_control_msg(dev, QT_GET_SET_UART, 0x30, 0x0300); + if (result < 0) + dev_dbg(&port->dev, "%s - set uart failed\n", __func__); + + if (tty) + ssu100_set_termios(tty, port, &tty->termios); + + return usb_serial_generic_open(tty, port); +} + +static int get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + tmp.line = port->serial->minor; + tmp.port = 0; + tmp.irq = 0; + tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + tmp.xmit_fifo_size = port->bulk_out_size; + tmp.baud_base = 9600; + tmp.close_delay = 5*HZ; + tmp.closing_wait = 30*HZ; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int ssu100_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s cmd 0x%04x\n", __func__, cmd); + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(port, + (struct serial_struct __user *) arg); + default: + break; + } + + dev_dbg(&port->dev, "%s arg not supported\n", __func__); + + return -ENOIOCTLCMD; +} + +static int ssu100_attach(struct usb_serial *serial) +{ + return ssu100_initdevice(serial->dev); +} + +static int ssu100_port_probe(struct usb_serial_port *port) +{ + struct ssu100_port_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->status_lock); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int ssu100_port_remove(struct usb_serial_port *port) +{ + struct ssu100_port_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); + + return 0; +} + +static int ssu100_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_device *dev = port->serial->dev; + u8 *d; + int r; + + d = kzalloc(2, GFP_KERNEL); + if (!d) + return -ENOMEM; + + r = ssu100_getregister(dev, 0, UART_MCR, d); + if (r < 0) + goto mget_out; + + r = ssu100_getregister(dev, 0, UART_MSR, d+1); + if (r < 0) + goto mget_out; + + r = (d[0] & UART_MCR_DTR ? TIOCM_DTR : 0) | + (d[0] & UART_MCR_RTS ? TIOCM_RTS : 0) | + (d[1] & UART_MSR_CTS ? TIOCM_CTS : 0) | + (d[1] & UART_MSR_DCD ? TIOCM_CAR : 0) | + (d[1] & UART_MSR_RI ? TIOCM_RI : 0) | + (d[1] & UART_MSR_DSR ? TIOCM_DSR : 0); + +mget_out: + kfree(d); + return r; +} + +static int ssu100_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_device *dev = port->serial->dev; + + return update_mctrl(dev, set, clear); +} + +static void ssu100_dtr_rts(struct usb_serial_port *port, int on) +{ + struct usb_device *dev = port->serial->dev; + + /* Disable flow control */ + if (!on) { + if (ssu100_setregister(dev, 0, UART_MCR, 0) < 0) + dev_err(&port->dev, "error from flowcontrol urb\n"); + } + /* drop RTS and DTR */ + if (on) + set_mctrl(dev, TIOCM_DTR | TIOCM_RTS); + else + clear_mctrl(dev, TIOCM_DTR | TIOCM_RTS); +} + +static void ssu100_update_msr(struct usb_serial_port *port, u8 msr) +{ + struct ssu100_port_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&priv->status_lock, flags); + priv->shadowMSR = msr; + spin_unlock_irqrestore(&priv->status_lock, flags); + + if (msr & UART_MSR_ANY_DELTA) { + /* update input line counters */ + if (msr & UART_MSR_DCTS) + port->icount.cts++; + if (msr & UART_MSR_DDSR) + port->icount.dsr++; + if (msr & UART_MSR_DDCD) + port->icount.dcd++; + if (msr & UART_MSR_TERI) + port->icount.rng++; + wake_up_interruptible(&port->port.delta_msr_wait); + } +} + +static void ssu100_update_lsr(struct usb_serial_port *port, u8 lsr, + char *tty_flag) +{ + struct ssu100_port_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + spin_lock_irqsave(&priv->status_lock, flags); + priv->shadowLSR = lsr; + spin_unlock_irqrestore(&priv->status_lock, flags); + + *tty_flag = TTY_NORMAL; + if (lsr & UART_LSR_BRK_ERROR_BITS) { + /* we always want to update icount, but we only want to + * update tty_flag for one case */ + if (lsr & UART_LSR_BI) { + port->icount.brk++; + *tty_flag = TTY_BREAK; + usb_serial_handle_break(port); + } + if (lsr & UART_LSR_PE) { + port->icount.parity++; + if (*tty_flag == TTY_NORMAL) + *tty_flag = TTY_PARITY; + } + if (lsr & UART_LSR_FE) { + port->icount.frame++; + if (*tty_flag == TTY_NORMAL) + *tty_flag = TTY_FRAME; + } + if (lsr & UART_LSR_OE) { + port->icount.overrun++; + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } + } + +} + +static void ssu100_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + char *packet = (char *)urb->transfer_buffer; + char flag = TTY_NORMAL; + u32 len = urb->actual_length; + int i; + char *ch; + + if ((len >= 4) && + (packet[0] == 0x1b) && (packet[1] == 0x1b) && + ((packet[2] == 0x00) || (packet[2] == 0x01))) { + if (packet[2] == 0x00) + ssu100_update_lsr(port, packet[3], &flag); + if (packet[2] == 0x01) + ssu100_update_msr(port, packet[3]); + + len -= 4; + ch = packet + 4; + } else + ch = packet; + + if (!len) + return; /* status only */ + + if (port->port.console && port->sysrq) { + for (i = 0; i < len; i++, ch++) { + if (!usb_serial_handle_sysrq_char(port, *ch)) + tty_insert_flip_char(&port->port, *ch, flag); + } + } else + tty_insert_flip_string_fixed_flag(&port->port, ch, flag, len); + + tty_flip_buffer_push(&port->port); +} + +static struct usb_serial_driver ssu100_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ssu100", + }, + .description = DRIVER_DESC, + .id_table = id_table, + .num_ports = 1, + .open = ssu100_open, + .attach = ssu100_attach, + .port_probe = ssu100_port_probe, + .port_remove = ssu100_port_remove, + .dtr_rts = ssu100_dtr_rts, + .process_read_urb = ssu100_process_read_urb, + .tiocmget = ssu100_tiocmget, + .tiocmset = ssu100_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .ioctl = ssu100_ioctl, + .set_termios = ssu100_set_termios, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ssu100_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c new file mode 100644 index 00000000..9b164894 --- /dev/null +++ b/drivers/usb/serial/symbolserial.c @@ -0,0 +1,212 @@ +/* + * Symbol USB barcode to serial driver + * + * Copyright (C) 2013 Johan Hovold + * Copyright (C) 2009 Greg Kroah-Hartman + * Copyright (C) 2009 Novell Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x05e0, 0x0600) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct symbol_private { + spinlock_t lock; /* protects the following flags */ + bool throttled; + bool actually_throttled; +}; + +static void symbol_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct symbol_private *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + int result; + int data_length; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); + + if (urb->actual_length > 1) { + data_length = urb->actual_length - 1; + + /* + * Data from the device comes with a 1 byte header: + * + * data... + * This is real data to be sent to the tty layer + * we pretty much just ignore the size and send everything + * else to the tty layer. + */ + tty_insert_flip_string(&port->port, &data[1], data_length); + tty_flip_buffer_push(&port->port); + } else { + dev_dbg(&port->dev, + "Improper amount of data received from the device, " + "%d bytes", urb->actual_length); + } + +exit: + spin_lock(&priv->lock); + + /* Continue trying to always read if we should */ + if (!priv->throttled) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + if (result) + dev_err(&port->dev, + "%s - failed resubmitting read urb, error %d\n", + __func__, result); + } else + priv->actually_throttled = true; + spin_unlock(&priv->lock); +} + +static int symbol_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct symbol_private *priv = usb_get_serial_data(port->serial); + unsigned long flags; + int result = 0; + + spin_lock_irqsave(&priv->lock, flags); + priv->throttled = false; + priv->actually_throttled = false; + spin_unlock_irqrestore(&priv->lock, flags); + + /* Start reading from the device */ + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) + dev_err(&port->dev, + "%s - failed resubmitting read urb, error %d\n", + __func__, result); + return result; +} + +static void symbol_close(struct usb_serial_port *port) +{ + usb_kill_urb(port->interrupt_in_urb); +} + +static void symbol_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct symbol_private *priv = usb_get_serial_data(port->serial); + + spin_lock_irq(&priv->lock); + priv->throttled = true; + spin_unlock_irq(&priv->lock); +} + +static void symbol_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct symbol_private *priv = usb_get_serial_data(port->serial); + int result; + bool was_throttled; + + spin_lock_irq(&priv->lock); + priv->throttled = false; + was_throttled = priv->actually_throttled; + priv->actually_throttled = false; + spin_unlock_irq(&priv->lock); + + if (was_throttled) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) + dev_err(&port->dev, + "%s - failed submitting read urb, error %d\n", + __func__, result); + } +} + +static int symbol_startup(struct usb_serial *serial) +{ + if (!serial->num_interrupt_in) { + dev_err(&serial->dev->dev, "no interrupt-in endpoint\n"); + return -ENODEV; + } + + return 0; +} + +static int symbol_port_probe(struct usb_serial_port *port) +{ + struct symbol_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int symbol_port_remove(struct usb_serial_port *port) +{ + struct symbol_private *priv = usb_get_serial_port_data(port); + + kfree(priv); + + return 0; +} + +static struct usb_serial_driver symbol_device = { + .driver = { + .owner = THIS_MODULE, + .name = "symbol", + }, + .id_table = id_table, + .num_ports = 1, + .attach = symbol_startup, + .port_probe = symbol_port_probe, + .port_remove = symbol_port_remove, + .open = symbol_open, + .close = symbol_close, + .throttle = symbol_throttle, + .unthrottle = symbol_unthrottle, + .read_int_callback = symbol_int_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &symbol_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c new file mode 100644 index 00000000..4cc84c0c --- /dev/null +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -0,0 +1,1607 @@ +/* vi: ts=8 sw=8 + * + * TI 3410/5052 USB Serial Driver + * + * Copyright (C) 2004 Texas Instruments + * + * This driver is based on the Linux io_ti driver, which is + * Copyright (C) 2000-2002 Inside Out Networks + * Copyright (C) 2001-2002 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * For questions or problems with this driver, contact Texas Instruments + * technical support, or Al Borchers , or + * Peter Berger . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ti_usb_3410_5052.h" + +/* Defines */ + +#define TI_DRIVER_AUTHOR "Al Borchers " +#define TI_DRIVER_DESC "TI USB 3410/5052 Serial Driver" + +#define TI_FIRMWARE_BUF_SIZE 16284 + +#define TI_WRITE_BUF_SIZE 1024 + +#define TI_TRANSFER_TIMEOUT 2 + +#define TI_DEFAULT_CLOSING_WAIT 4000 /* in .01 secs */ + +/* supported setserial flags */ +#define TI_SET_SERIAL_FLAGS 0 + +/* read urb states */ +#define TI_READ_URB_RUNNING 0 +#define TI_READ_URB_STOPPING 1 +#define TI_READ_URB_STOPPED 2 + +#define TI_EXTRA_VID_PID_COUNT 5 + + +/* Structures */ + +struct ti_port { + int tp_is_open; + __u8 tp_msr; + __u8 tp_shadow_mcr; + __u8 tp_uart_mode; /* 232 or 485 modes */ + unsigned int tp_uart_base_addr; + int tp_flags; + wait_queue_head_t tp_write_wait; + struct ti_device *tp_tdev; + struct usb_serial_port *tp_port; + spinlock_t tp_lock; + int tp_read_urb_state; + int tp_write_urb_in_use; + struct kfifo write_fifo; +}; + +struct ti_device { + struct mutex td_open_close_lock; + int td_open_port_count; + struct usb_serial *td_serial; + int td_is_3410; + int td_urb_error; +}; + + +/* Function Declarations */ + +static int ti_startup(struct usb_serial *serial); +static void ti_release(struct usb_serial *serial); +static int ti_port_probe(struct usb_serial_port *port); +static int ti_port_remove(struct usb_serial_port *port); +static int ti_open(struct tty_struct *tty, struct usb_serial_port *port); +static void ti_close(struct usb_serial_port *port); +static int ti_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *data, int count); +static int ti_write_room(struct tty_struct *tty); +static int ti_chars_in_buffer(struct tty_struct *tty); +static bool ti_tx_empty(struct usb_serial_port *port); +static void ti_throttle(struct tty_struct *tty); +static void ti_unthrottle(struct tty_struct *tty); +static int ti_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg); +static void ti_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios); +static int ti_tiocmget(struct tty_struct *tty); +static int ti_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static void ti_break(struct tty_struct *tty, int break_state); +static void ti_interrupt_callback(struct urb *urb); +static void ti_bulk_in_callback(struct urb *urb); +static void ti_bulk_out_callback(struct urb *urb); + +static void ti_recv(struct usb_serial_port *port, unsigned char *data, + int length); +static void ti_send(struct ti_port *tport); +static int ti_set_mcr(struct ti_port *tport, unsigned int mcr); +static int ti_get_lsr(struct ti_port *tport, u8 *lsr); +static int ti_get_serial_info(struct ti_port *tport, + struct serial_struct __user *ret_arg); +static int ti_set_serial_info(struct tty_struct *tty, struct ti_port *tport, + struct serial_struct __user *new_arg); +static void ti_handle_new_msr(struct ti_port *tport, __u8 msr); + +static void ti_stop_read(struct ti_port *tport, struct tty_struct *tty); +static int ti_restart_read(struct ti_port *tport, struct tty_struct *tty); + +static int ti_command_out_sync(struct ti_device *tdev, __u8 command, + __u16 moduleid, __u16 value, __u8 *data, int size); +static int ti_command_in_sync(struct ti_device *tdev, __u8 command, + __u16 moduleid, __u16 value, __u8 *data, int size); + +static int ti_write_byte(struct usb_serial_port *port, struct ti_device *tdev, + unsigned long addr, __u8 mask, __u8 byte); + +static int ti_download_firmware(struct ti_device *tdev); + + +/* Data */ + +/* module parameters */ +static int closing_wait = TI_DEFAULT_CLOSING_WAIT; +static ushort vendor_3410[TI_EXTRA_VID_PID_COUNT]; +static unsigned int vendor_3410_count; +static ushort product_3410[TI_EXTRA_VID_PID_COUNT]; +static unsigned int product_3410_count; +static ushort vendor_5052[TI_EXTRA_VID_PID_COUNT]; +static unsigned int vendor_5052_count; +static ushort product_5052[TI_EXTRA_VID_PID_COUNT]; +static unsigned int product_5052_count; + +/* supported devices */ +/* the array dimension is the number of default entries plus */ +/* TI_EXTRA_VID_PID_COUNT user defined entries plus 1 terminating */ +/* null entry */ +static struct usb_device_id ti_id_table_3410[15+TI_EXTRA_VID_PID_COUNT+1] = { + { USB_DEVICE(TI_VENDOR_ID, TI_3410_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_3410_EZ430_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_NO_FW_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_CDMA_NO_FW_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_CDMA_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_EDGE_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234MU_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234ZBA_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234ZBAOLD_PRODUCT_ID) }, + { USB_DEVICE(IBM_VENDOR_ID, IBM_4543_PRODUCT_ID) }, + { USB_DEVICE(IBM_VENDOR_ID, IBM_454B_PRODUCT_ID) }, + { USB_DEVICE(IBM_VENDOR_ID, IBM_454C_PRODUCT_ID) }, + { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_STEREO_PLUG_ID) }, + { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_STRIP_PORT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, FRI2_PRODUCT_ID) }, +}; + +static struct usb_device_id ti_id_table_5052[5+TI_EXTRA_VID_PID_COUNT+1] = { + { USB_DEVICE(TI_VENDOR_ID, TI_5052_BOOT_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_5152_BOOT_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_5052_EEPROM_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_5052_FIRMWARE_PRODUCT_ID) }, +}; + +static struct usb_device_id ti_id_table_combined[19+2*TI_EXTRA_VID_PID_COUNT+1] = { + { USB_DEVICE(TI_VENDOR_ID, TI_3410_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_3410_EZ430_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_NO_FW_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_CDMA_NO_FW_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_CDMA_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_EDGE_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234MU_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234ZBA_PRODUCT_ID) }, + { USB_DEVICE(MTS_VENDOR_ID, MTS_MT9234ZBAOLD_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_5052_BOOT_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_5152_BOOT_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_5052_EEPROM_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, TI_5052_FIRMWARE_PRODUCT_ID) }, + { USB_DEVICE(IBM_VENDOR_ID, IBM_4543_PRODUCT_ID) }, + { USB_DEVICE(IBM_VENDOR_ID, IBM_454B_PRODUCT_ID) }, + { USB_DEVICE(IBM_VENDOR_ID, IBM_454C_PRODUCT_ID) }, + { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_PRODUCT_ID) }, + { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_STRIP_PORT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, FRI2_PRODUCT_ID) }, + { } +}; + +static struct usb_serial_driver ti_1port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ti_usb_3410_5052_1", + }, + .description = "TI USB 3410 1 port adapter", + .id_table = ti_id_table_3410, + .num_ports = 1, + .attach = ti_startup, + .release = ti_release, + .port_probe = ti_port_probe, + .port_remove = ti_port_remove, + .open = ti_open, + .close = ti_close, + .write = ti_write, + .write_room = ti_write_room, + .chars_in_buffer = ti_chars_in_buffer, + .tx_empty = ti_tx_empty, + .throttle = ti_throttle, + .unthrottle = ti_unthrottle, + .ioctl = ti_ioctl, + .set_termios = ti_set_termios, + .tiocmget = ti_tiocmget, + .tiocmset = ti_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .break_ctl = ti_break, + .read_int_callback = ti_interrupt_callback, + .read_bulk_callback = ti_bulk_in_callback, + .write_bulk_callback = ti_bulk_out_callback, +}; + +static struct usb_serial_driver ti_2port_device = { + .driver = { + .owner = THIS_MODULE, + .name = "ti_usb_3410_5052_2", + }, + .description = "TI USB 5052 2 port adapter", + .id_table = ti_id_table_5052, + .num_ports = 2, + .attach = ti_startup, + .release = ti_release, + .port_probe = ti_port_probe, + .port_remove = ti_port_remove, + .open = ti_open, + .close = ti_close, + .write = ti_write, + .write_room = ti_write_room, + .chars_in_buffer = ti_chars_in_buffer, + .tx_empty = ti_tx_empty, + .throttle = ti_throttle, + .unthrottle = ti_unthrottle, + .ioctl = ti_ioctl, + .set_termios = ti_set_termios, + .tiocmget = ti_tiocmget, + .tiocmset = ti_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .break_ctl = ti_break, + .read_int_callback = ti_interrupt_callback, + .read_bulk_callback = ti_bulk_in_callback, + .write_bulk_callback = ti_bulk_out_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ti_1port_device, &ti_2port_device, NULL +}; + +/* Module */ + +MODULE_AUTHOR(TI_DRIVER_AUTHOR); +MODULE_DESCRIPTION(TI_DRIVER_DESC); +MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("ti_3410.fw"); +MODULE_FIRMWARE("ti_5052.fw"); +MODULE_FIRMWARE("mts_cdma.fw"); +MODULE_FIRMWARE("mts_gsm.fw"); +MODULE_FIRMWARE("mts_edge.fw"); +MODULE_FIRMWARE("mts_mt9234mu.fw"); +MODULE_FIRMWARE("mts_mt9234zba.fw"); + +module_param(closing_wait, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(closing_wait, + "Maximum wait for data to drain in close, in .01 secs, default is 4000"); + +module_param_array(vendor_3410, ushort, &vendor_3410_count, S_IRUGO); +MODULE_PARM_DESC(vendor_3410, + "Vendor ids for 3410 based devices, 1-5 short integers"); +module_param_array(product_3410, ushort, &product_3410_count, S_IRUGO); +MODULE_PARM_DESC(product_3410, + "Product ids for 3410 based devices, 1-5 short integers"); +module_param_array(vendor_5052, ushort, &vendor_5052_count, S_IRUGO); +MODULE_PARM_DESC(vendor_5052, + "Vendor ids for 5052 based devices, 1-5 short integers"); +module_param_array(product_5052, ushort, &product_5052_count, S_IRUGO); +MODULE_PARM_DESC(product_5052, + "Product ids for 5052 based devices, 1-5 short integers"); + +MODULE_DEVICE_TABLE(usb, ti_id_table_combined); + + +/* Functions */ + +static int __init ti_init(void) +{ + int i, j, c; + + /* insert extra vendor and product ids */ + c = ARRAY_SIZE(ti_id_table_combined) - 2 * TI_EXTRA_VID_PID_COUNT - 1; + j = ARRAY_SIZE(ti_id_table_3410) - TI_EXTRA_VID_PID_COUNT - 1; + for (i = 0; i < min(vendor_3410_count, product_3410_count); i++, j++, c++) { + ti_id_table_3410[j].idVendor = vendor_3410[i]; + ti_id_table_3410[j].idProduct = product_3410[i]; + ti_id_table_3410[j].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + ti_id_table_combined[c].idVendor = vendor_3410[i]; + ti_id_table_combined[c].idProduct = product_3410[i]; + ti_id_table_combined[c].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + } + j = ARRAY_SIZE(ti_id_table_5052) - TI_EXTRA_VID_PID_COUNT - 1; + for (i = 0; i < min(vendor_5052_count, product_5052_count); i++, j++, c++) { + ti_id_table_5052[j].idVendor = vendor_5052[i]; + ti_id_table_5052[j].idProduct = product_5052[i]; + ti_id_table_5052[j].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + ti_id_table_combined[c].idVendor = vendor_5052[i]; + ti_id_table_combined[c].idProduct = product_5052[i]; + ti_id_table_combined[c].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + } + + return usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME, ti_id_table_combined); +} + +static void __exit ti_exit(void) +{ + usb_serial_deregister_drivers(serial_drivers); +} + +module_init(ti_init); +module_exit(ti_exit); + + +static int ti_startup(struct usb_serial *serial) +{ + struct ti_device *tdev; + struct usb_device *dev = serial->dev; + int status; + + dev_dbg(&dev->dev, + "%s - product 0x%4X, num configurations %d, configuration value %d", + __func__, le16_to_cpu(dev->descriptor.idProduct), + dev->descriptor.bNumConfigurations, + dev->actconfig->desc.bConfigurationValue); + + /* create device structure */ + tdev = kzalloc(sizeof(struct ti_device), GFP_KERNEL); + if (tdev == NULL) { + dev_err(&dev->dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + mutex_init(&tdev->td_open_close_lock); + tdev->td_serial = serial; + usb_set_serial_data(serial, tdev); + + /* determine device type */ + if (serial->type == &ti_1port_device) + tdev->td_is_3410 = 1; + dev_dbg(&dev->dev, "%s - device type is %s\n", __func__, + tdev->td_is_3410 ? "3410" : "5052"); + + /* if we have only 1 configuration, download firmware */ + if (dev->descriptor.bNumConfigurations == 1) { + status = ti_download_firmware(tdev); + + if (status != 0) + goto free_tdev; + + /* 3410 must be reset, 5052 resets itself */ + if (tdev->td_is_3410) { + msleep_interruptible(100); + usb_reset_device(dev); + } + + status = -ENODEV; + goto free_tdev; + } + + /* the second configuration must be set */ + if (dev->actconfig->desc.bConfigurationValue == TI_BOOT_CONFIG) { + status = usb_driver_set_configuration(dev, TI_ACTIVE_CONFIG); + status = status ? status : -ENODEV; + goto free_tdev; + } + + return 0; + +free_tdev: + kfree(tdev); + usb_set_serial_data(serial, NULL); + return status; +} + + +static void ti_release(struct usb_serial *serial) +{ + struct ti_device *tdev = usb_get_serial_data(serial); + + kfree(tdev); +} + +static int ti_port_probe(struct usb_serial_port *port) +{ + struct ti_port *tport; + + tport = kzalloc(sizeof(*tport), GFP_KERNEL); + if (!tport) + return -ENOMEM; + + spin_lock_init(&tport->tp_lock); + if (port == port->serial->port[0]) + tport->tp_uart_base_addr = TI_UART1_BASE_ADDR; + else + tport->tp_uart_base_addr = TI_UART2_BASE_ADDR; + port->port.closing_wait = msecs_to_jiffies(10 * closing_wait); + init_waitqueue_head(&tport->tp_write_wait); + if (kfifo_alloc(&tport->write_fifo, TI_WRITE_BUF_SIZE, GFP_KERNEL)) { + kfree(tport); + return -ENOMEM; + } + tport->tp_port = port; + tport->tp_tdev = usb_get_serial_data(port->serial); + tport->tp_uart_mode = 0; /* default is RS232 */ + + usb_set_serial_port_data(port, tport); + + return 0; +} + +static int ti_port_remove(struct usb_serial_port *port) +{ + struct ti_port *tport; + + tport = usb_get_serial_port_data(port); + kfifo_free(&tport->write_fifo); + kfree(tport); + + return 0; +} + +static int ti_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct ti_port *tport = usb_get_serial_port_data(port); + struct ti_device *tdev; + struct usb_device *dev; + struct urb *urb; + int port_number; + int status; + __u16 open_settings = (__u8)(TI_PIPE_MODE_CONTINOUS | + TI_PIPE_TIMEOUT_ENABLE | + (TI_TRANSFER_TIMEOUT << 2)); + + if (tport == NULL) + return -ENODEV; + + dev = port->serial->dev; + tdev = tport->tp_tdev; + + /* only one open on any port on a device at a time */ + if (mutex_lock_interruptible(&tdev->td_open_close_lock)) + return -ERESTARTSYS; + + port_number = port->number - port->serial->minor; + + tport->tp_msr = 0; + tport->tp_shadow_mcr |= (TI_MCR_RTS | TI_MCR_DTR); + + /* start interrupt urb the first time a port is opened on this device */ + if (tdev->td_open_port_count == 0) { + dev_dbg(&port->dev, "%s - start interrupt in urb\n", __func__); + urb = tdev->td_serial->port[0]->interrupt_in_urb; + if (!urb) { + dev_err(&port->dev, "%s - no interrupt urb\n", __func__); + status = -EINVAL; + goto release_lock; + } + urb->context = tdev; + status = usb_submit_urb(urb, GFP_KERNEL); + if (status) { + dev_err(&port->dev, "%s - submit interrupt urb failed, %d\n", __func__, status); + goto release_lock; + } + } + + if (tty) + ti_set_termios(tty, port, &tty->termios); + + dev_dbg(&port->dev, "%s - sending TI_OPEN_PORT\n", __func__); + status = ti_command_out_sync(tdev, TI_OPEN_PORT, + (__u8)(TI_UART1_PORT + port_number), open_settings, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send open command, %d\n", + __func__, status); + goto unlink_int_urb; + } + + dev_dbg(&port->dev, "%s - sending TI_START_PORT\n", __func__); + status = ti_command_out_sync(tdev, TI_START_PORT, + (__u8)(TI_UART1_PORT + port_number), 0, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send start command, %d\n", + __func__, status); + goto unlink_int_urb; + } + + dev_dbg(&port->dev, "%s - sending TI_PURGE_PORT\n", __func__); + status = ti_command_out_sync(tdev, TI_PURGE_PORT, + (__u8)(TI_UART1_PORT + port_number), TI_PURGE_INPUT, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot clear input buffers, %d\n", + __func__, status); + goto unlink_int_urb; + } + status = ti_command_out_sync(tdev, TI_PURGE_PORT, + (__u8)(TI_UART1_PORT + port_number), TI_PURGE_OUTPUT, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot clear output buffers, %d\n", + __func__, status); + goto unlink_int_urb; + } + + /* reset the data toggle on the bulk endpoints to work around bug in + * host controllers where things get out of sync some times */ + usb_clear_halt(dev, port->write_urb->pipe); + usb_clear_halt(dev, port->read_urb->pipe); + + if (tty) + ti_set_termios(tty, port, &tty->termios); + + dev_dbg(&port->dev, "%s - sending TI_OPEN_PORT (2)\n", __func__); + status = ti_command_out_sync(tdev, TI_OPEN_PORT, + (__u8)(TI_UART1_PORT + port_number), open_settings, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send open command (2), %d\n", + __func__, status); + goto unlink_int_urb; + } + + dev_dbg(&port->dev, "%s - sending TI_START_PORT (2)\n", __func__); + status = ti_command_out_sync(tdev, TI_START_PORT, + (__u8)(TI_UART1_PORT + port_number), 0, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send start command (2), %d\n", + __func__, status); + goto unlink_int_urb; + } + + /* start read urb */ + dev_dbg(&port->dev, "%s - start read urb\n", __func__); + urb = port->read_urb; + if (!urb) { + dev_err(&port->dev, "%s - no read urb\n", __func__); + status = -EINVAL; + goto unlink_int_urb; + } + tport->tp_read_urb_state = TI_READ_URB_RUNNING; + urb->context = tport; + status = usb_submit_urb(urb, GFP_KERNEL); + if (status) { + dev_err(&port->dev, "%s - submit read urb failed, %d\n", + __func__, status); + goto unlink_int_urb; + } + + tport->tp_is_open = 1; + ++tdev->td_open_port_count; + + port->port.drain_delay = 3; + + goto release_lock; + +unlink_int_urb: + if (tdev->td_open_port_count == 0) + usb_kill_urb(port->serial->port[0]->interrupt_in_urb); +release_lock: + mutex_unlock(&tdev->td_open_close_lock); + dev_dbg(&port->dev, "%s - exit %d\n", __func__, status); + return status; +} + + +static void ti_close(struct usb_serial_port *port) +{ + struct ti_device *tdev; + struct ti_port *tport; + int port_number; + int status; + int do_unlock; + unsigned long flags; + + tdev = usb_get_serial_data(port->serial); + tport = usb_get_serial_port_data(port); + if (tdev == NULL || tport == NULL) + return; + + tport->tp_is_open = 0; + + usb_kill_urb(port->read_urb); + usb_kill_urb(port->write_urb); + tport->tp_write_urb_in_use = 0; + spin_lock_irqsave(&tport->tp_lock, flags); + kfifo_reset_out(&tport->write_fifo); + spin_unlock_irqrestore(&tport->tp_lock, flags); + + port_number = port->number - port->serial->minor; + + dev_dbg(&port->dev, "%s - sending TI_CLOSE_PORT\n", __func__); + status = ti_command_out_sync(tdev, TI_CLOSE_PORT, + (__u8)(TI_UART1_PORT + port_number), 0, NULL, 0); + if (status) + dev_err(&port->dev, + "%s - cannot send close port command, %d\n" + , __func__, status); + + /* if mutex_lock is interrupted, continue anyway */ + do_unlock = !mutex_lock_interruptible(&tdev->td_open_close_lock); + --tport->tp_tdev->td_open_port_count; + if (tport->tp_tdev->td_open_port_count <= 0) { + /* last port is closed, shut down interrupt urb */ + usb_kill_urb(port->serial->port[0]->interrupt_in_urb); + tport->tp_tdev->td_open_port_count = 0; + } + if (do_unlock) + mutex_unlock(&tdev->td_open_close_lock); +} + + +static int ti_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *data, int count) +{ + struct ti_port *tport = usb_get_serial_port_data(port); + + if (count == 0) { + dev_dbg(&port->dev, "%s - write request of 0 bytes\n", __func__); + return 0; + } + + if (tport == NULL || !tport->tp_is_open) + return -ENODEV; + + count = kfifo_in_locked(&tport->write_fifo, data, count, + &tport->tp_lock); + ti_send(tport); + + return count; +} + + +static int ti_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + int room = 0; + unsigned long flags; + + if (tport == NULL) + return 0; + + spin_lock_irqsave(&tport->tp_lock, flags); + room = kfifo_avail(&tport->write_fifo); + spin_unlock_irqrestore(&tport->tp_lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, room); + return room; +} + + +static int ti_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + int chars = 0; + unsigned long flags; + + if (tport == NULL) + return 0; + + spin_lock_irqsave(&tport->tp_lock, flags); + chars = kfifo_len(&tport->write_fifo); + spin_unlock_irqrestore(&tport->tp_lock, flags); + + dev_dbg(&port->dev, "%s - returns %d\n", __func__, chars); + return chars; +} + +static bool ti_tx_empty(struct usb_serial_port *port) +{ + struct ti_port *tport = usb_get_serial_port_data(port); + int ret; + u8 lsr; + + ret = ti_get_lsr(tport, &lsr); + if (!ret && !(lsr & TI_LSR_TX_EMPTY)) + return false; + + return true; +} + +static void ti_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + + if (tport == NULL) + return; + + if (I_IXOFF(tty) || C_CRTSCTS(tty)) + ti_stop_read(tport, tty); + +} + + +static void ti_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + int status; + + if (tport == NULL) + return; + + if (I_IXOFF(tty) || C_CRTSCTS(tty)) { + status = ti_restart_read(tport, tty); + if (status) + dev_err(&port->dev, "%s - cannot restart read, %d\n", + __func__, status); + } +} + +static int ti_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + + dev_dbg(&port->dev, "%s - cmd = 0x%04X\n", __func__, cmd); + + if (tport == NULL) + return -ENODEV; + + switch (cmd) { + case TIOCGSERIAL: + dev_dbg(&port->dev, "%s - TIOCGSERIAL\n", __func__); + return ti_get_serial_info(tport, + (struct serial_struct __user *)arg); + case TIOCSSERIAL: + dev_dbg(&port->dev, "%s - TIOCSSERIAL\n", __func__); + return ti_set_serial_info(tty, tport, + (struct serial_struct __user *)arg); + } + return -ENOIOCTLCMD; +} + + +static void ti_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct ti_port *tport = usb_get_serial_port_data(port); + struct ti_uart_config *config; + tcflag_t cflag, iflag; + int baud; + int status; + int port_number = port->number - port->serial->minor; + unsigned int mcr; + + cflag = tty->termios.c_cflag; + iflag = tty->termios.c_iflag; + + dev_dbg(&port->dev, "%s - cflag %08x, iflag %08x\n", __func__, cflag, iflag); + dev_dbg(&port->dev, "%s - old clfag %08x, old iflag %08x\n", __func__, + old_termios->c_cflag, old_termios->c_iflag); + + if (tport == NULL) + return; + + config = kmalloc(sizeof(*config), GFP_KERNEL); + if (!config) { + dev_err(&port->dev, "%s - out of memory\n", __func__); + return; + } + + config->wFlags = 0; + + /* these flags must be set */ + config->wFlags |= TI_UART_ENABLE_MS_INTS; + config->wFlags |= TI_UART_ENABLE_AUTO_START_DMA; + config->bUartMode = (__u8)(tport->tp_uart_mode); + + switch (cflag & CSIZE) { + case CS5: + config->bDataBits = TI_UART_5_DATA_BITS; + break; + case CS6: + config->bDataBits = TI_UART_6_DATA_BITS; + break; + case CS7: + config->bDataBits = TI_UART_7_DATA_BITS; + break; + default: + case CS8: + config->bDataBits = TI_UART_8_DATA_BITS; + break; + } + + /* CMSPAR isn't supported by this driver */ + tty->termios.c_cflag &= ~CMSPAR; + + if (cflag & PARENB) { + if (cflag & PARODD) { + config->wFlags |= TI_UART_ENABLE_PARITY_CHECKING; + config->bParity = TI_UART_ODD_PARITY; + } else { + config->wFlags |= TI_UART_ENABLE_PARITY_CHECKING; + config->bParity = TI_UART_EVEN_PARITY; + } + } else { + config->wFlags &= ~TI_UART_ENABLE_PARITY_CHECKING; + config->bParity = TI_UART_NO_PARITY; + } + + if (cflag & CSTOPB) + config->bStopBits = TI_UART_2_STOP_BITS; + else + config->bStopBits = TI_UART_1_STOP_BITS; + + if (cflag & CRTSCTS) { + /* RTS flow control must be off to drop RTS for baud rate B0 */ + if ((cflag & CBAUD) != B0) + config->wFlags |= TI_UART_ENABLE_RTS_IN; + config->wFlags |= TI_UART_ENABLE_CTS_OUT; + } else { + tty->hw_stopped = 0; + ti_restart_read(tport, tty); + } + + if (I_IXOFF(tty) || I_IXON(tty)) { + config->cXon = START_CHAR(tty); + config->cXoff = STOP_CHAR(tty); + + if (I_IXOFF(tty)) + config->wFlags |= TI_UART_ENABLE_X_IN; + else + ti_restart_read(tport, tty); + + if (I_IXON(tty)) + config->wFlags |= TI_UART_ENABLE_X_OUT; + } + + baud = tty_get_baud_rate(tty); + if (!baud) + baud = 9600; + if (tport->tp_tdev->td_is_3410) + config->wBaudRate = (__u16)((923077 + baud/2) / baud); + else + config->wBaudRate = (__u16)((461538 + baud/2) / baud); + + /* FIXME: Should calculate resulting baud here and report it back */ + if ((cflag & CBAUD) != B0) + tty_encode_baud_rate(tty, baud, baud); + + dev_dbg(&port->dev, + "%s - BaudRate=%d, wBaudRate=%d, wFlags=0x%04X, bDataBits=%d, bParity=%d, bStopBits=%d, cXon=%d, cXoff=%d, bUartMode=%d", + __func__, baud, config->wBaudRate, config->wFlags, + config->bDataBits, config->bParity, config->bStopBits, + config->cXon, config->cXoff, config->bUartMode); + + cpu_to_be16s(&config->wBaudRate); + cpu_to_be16s(&config->wFlags); + + status = ti_command_out_sync(tport->tp_tdev, TI_SET_CONFIG, + (__u8)(TI_UART1_PORT + port_number), 0, (__u8 *)config, + sizeof(*config)); + if (status) + dev_err(&port->dev, "%s - cannot set config on port %d, %d\n", + __func__, port_number, status); + + /* SET_CONFIG asserts RTS and DTR, reset them correctly */ + mcr = tport->tp_shadow_mcr; + /* if baud rate is B0, clear RTS and DTR */ + if ((cflag & CBAUD) == B0) + mcr &= ~(TI_MCR_DTR | TI_MCR_RTS); + status = ti_set_mcr(tport, mcr); + if (status) + dev_err(&port->dev, + "%s - cannot set modem control on port %d, %d\n", + __func__, port_number, status); + + kfree(config); +} + + +static int ti_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + unsigned int result; + unsigned int msr; + unsigned int mcr; + unsigned long flags; + + if (tport == NULL) + return -ENODEV; + + spin_lock_irqsave(&tport->tp_lock, flags); + msr = tport->tp_msr; + mcr = tport->tp_shadow_mcr; + spin_unlock_irqrestore(&tport->tp_lock, flags); + + result = ((mcr & TI_MCR_DTR) ? TIOCM_DTR : 0) + | ((mcr & TI_MCR_RTS) ? TIOCM_RTS : 0) + | ((mcr & TI_MCR_LOOP) ? TIOCM_LOOP : 0) + | ((msr & TI_MSR_CTS) ? TIOCM_CTS : 0) + | ((msr & TI_MSR_CD) ? TIOCM_CAR : 0) + | ((msr & TI_MSR_RI) ? TIOCM_RI : 0) + | ((msr & TI_MSR_DSR) ? TIOCM_DSR : 0); + + dev_dbg(&port->dev, "%s - 0x%04X\n", __func__, result); + + return result; +} + + +static int ti_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + unsigned int mcr; + unsigned long flags; + + if (tport == NULL) + return -ENODEV; + + spin_lock_irqsave(&tport->tp_lock, flags); + mcr = tport->tp_shadow_mcr; + + if (set & TIOCM_RTS) + mcr |= TI_MCR_RTS; + if (set & TIOCM_DTR) + mcr |= TI_MCR_DTR; + if (set & TIOCM_LOOP) + mcr |= TI_MCR_LOOP; + + if (clear & TIOCM_RTS) + mcr &= ~TI_MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~TI_MCR_DTR; + if (clear & TIOCM_LOOP) + mcr &= ~TI_MCR_LOOP; + spin_unlock_irqrestore(&tport->tp_lock, flags); + + return ti_set_mcr(tport, mcr); +} + + +static void ti_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + int status; + + dev_dbg(&port->dev, "%s - state = %d\n", __func__, break_state); + + if (tport == NULL) + return; + + status = ti_write_byte(port, tport->tp_tdev, + tport->tp_uart_base_addr + TI_UART_OFFSET_LCR, + TI_LCR_BREAK, break_state == -1 ? TI_LCR_BREAK : 0); + + if (status) + dev_dbg(&port->dev, "%s - error setting break, %d\n", __func__, status); +} + + +static void ti_interrupt_callback(struct urb *urb) +{ + struct ti_device *tdev = urb->context; + struct usb_serial_port *port; + struct usb_serial *serial = tdev->td_serial; + struct ti_port *tport; + struct device *dev = &urb->dev->dev; + unsigned char *data = urb->transfer_buffer; + int length = urb->actual_length; + int port_number; + int function; + int status = urb->status; + int retval; + __u8 msr; + + switch (status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(dev, "%s - urb shutting down, %d\n", __func__, status); + tdev->td_urb_error = 1; + return; + default: + dev_err(dev, "%s - nonzero urb status, %d\n", __func__, status); + tdev->td_urb_error = 1; + goto exit; + } + + if (length != 2) { + dev_dbg(dev, "%s - bad packet size, %d\n", __func__, length); + goto exit; + } + + if (data[0] == TI_CODE_HARDWARE_ERROR) { + dev_err(dev, "%s - hardware error, %d\n", __func__, data[1]); + goto exit; + } + + port_number = TI_GET_PORT_FROM_CODE(data[0]); + function = TI_GET_FUNC_FROM_CODE(data[0]); + + dev_dbg(dev, "%s - port_number %d, function %d, data 0x%02X\n", + __func__, port_number, function, data[1]); + + if (port_number >= serial->num_ports) { + dev_err(dev, "%s - bad port number, %d\n", + __func__, port_number); + goto exit; + } + + port = serial->port[port_number]; + + tport = usb_get_serial_port_data(port); + if (!tport) + goto exit; + + switch (function) { + case TI_CODE_DATA_ERROR: + dev_err(dev, "%s - DATA ERROR, port %d, data 0x%02X\n", + __func__, port_number, data[1]); + break; + + case TI_CODE_MODEM_STATUS: + msr = data[1]; + dev_dbg(dev, "%s - port %d, msr 0x%02X\n", __func__, port_number, msr); + ti_handle_new_msr(tport, msr); + break; + + default: + dev_err(dev, "%s - unknown interrupt code, 0x%02X\n", + __func__, data[1]); + break; + } + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(dev, "%s - resubmit interrupt urb failed, %d\n", + __func__, retval); +} + + +static void ti_bulk_in_callback(struct urb *urb) +{ + struct ti_port *tport = urb->context; + struct usb_serial_port *port = tport->tp_port; + struct device *dev = &urb->dev->dev; + int status = urb->status; + int retval = 0; + + switch (status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(dev, "%s - urb shutting down, %d\n", __func__, status); + tport->tp_tdev->td_urb_error = 1; + wake_up_interruptible(&tport->tp_write_wait); + return; + default: + dev_err(dev, "%s - nonzero urb status, %d\n", + __func__, status); + tport->tp_tdev->td_urb_error = 1; + wake_up_interruptible(&tport->tp_write_wait); + } + + if (status == -EPIPE) + goto exit; + + if (status) { + dev_err(dev, "%s - stopping read!\n", __func__); + return; + } + + if (urb->actual_length) { + usb_serial_debug_data(dev, __func__, urb->actual_length, + urb->transfer_buffer); + + if (!tport->tp_is_open) + dev_dbg(dev, "%s - port closed, dropping data\n", + __func__); + else + ti_recv(port, urb->transfer_buffer, urb->actual_length); + spin_lock(&tport->tp_lock); + port->icount.rx += urb->actual_length; + spin_unlock(&tport->tp_lock); + } + +exit: + /* continue to read unless stopping */ + spin_lock(&tport->tp_lock); + if (tport->tp_read_urb_state == TI_READ_URB_RUNNING) + retval = usb_submit_urb(urb, GFP_ATOMIC); + else if (tport->tp_read_urb_state == TI_READ_URB_STOPPING) + tport->tp_read_urb_state = TI_READ_URB_STOPPED; + + spin_unlock(&tport->tp_lock); + if (retval) + dev_err(dev, "%s - resubmit read urb failed, %d\n", + __func__, retval); +} + + +static void ti_bulk_out_callback(struct urb *urb) +{ + struct ti_port *tport = urb->context; + struct usb_serial_port *port = tport->tp_port; + int status = urb->status; + + tport->tp_write_urb_in_use = 0; + + switch (status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(&port->dev, "%s - urb shutting down, %d\n", __func__, status); + tport->tp_tdev->td_urb_error = 1; + wake_up_interruptible(&tport->tp_write_wait); + return; + default: + dev_err_console(port, "%s - nonzero urb status, %d\n", + __func__, status); + tport->tp_tdev->td_urb_error = 1; + wake_up_interruptible(&tport->tp_write_wait); + } + + /* send any buffered data */ + ti_send(tport); +} + + +static void ti_recv(struct usb_serial_port *port, unsigned char *data, + int length) +{ + int cnt; + + do { + cnt = tty_insert_flip_string(&port->port, data, length); + if (cnt < length) { + dev_err(&port->dev, "%s - dropping data, %d bytes lost\n", + __func__, length - cnt); + if (cnt == 0) + break; + } + tty_flip_buffer_push(&port->port); + data += cnt; + length -= cnt; + } while (length > 0); +} + + +static void ti_send(struct ti_port *tport) +{ + int count, result; + struct usb_serial_port *port = tport->tp_port; + unsigned long flags; + + spin_lock_irqsave(&tport->tp_lock, flags); + + if (tport->tp_write_urb_in_use) + goto unlock; + + count = kfifo_out(&tport->write_fifo, + port->write_urb->transfer_buffer, + port->bulk_out_size); + + if (count == 0) + goto unlock; + + tport->tp_write_urb_in_use = 1; + + spin_unlock_irqrestore(&tport->tp_lock, flags); + + usb_serial_debug_data(&port->dev, __func__, count, + port->write_urb->transfer_buffer); + + usb_fill_bulk_urb(port->write_urb, port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, count, + ti_bulk_out_callback, tport); + + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (result) { + dev_err_console(port, "%s - submit write urb failed, %d\n", + __func__, result); + tport->tp_write_urb_in_use = 0; + /* TODO: reschedule ti_send */ + } else { + spin_lock_irqsave(&tport->tp_lock, flags); + port->icount.tx += count; + spin_unlock_irqrestore(&tport->tp_lock, flags); + } + + /* more room in the buffer for new writes, wakeup */ + tty_port_tty_wakeup(&port->port); + + wake_up_interruptible(&tport->tp_write_wait); + return; +unlock: + spin_unlock_irqrestore(&tport->tp_lock, flags); + return; +} + + +static int ti_set_mcr(struct ti_port *tport, unsigned int mcr) +{ + unsigned long flags; + int status; + + status = ti_write_byte(tport->tp_port, tport->tp_tdev, + tport->tp_uart_base_addr + TI_UART_OFFSET_MCR, + TI_MCR_RTS | TI_MCR_DTR | TI_MCR_LOOP, mcr); + + spin_lock_irqsave(&tport->tp_lock, flags); + if (!status) + tport->tp_shadow_mcr = mcr; + spin_unlock_irqrestore(&tport->tp_lock, flags); + + return status; +} + + +static int ti_get_lsr(struct ti_port *tport, u8 *lsr) +{ + int size, status; + struct ti_device *tdev = tport->tp_tdev; + struct usb_serial_port *port = tport->tp_port; + int port_number = port->number - port->serial->minor; + struct ti_port_status *data; + + size = sizeof(struct ti_port_status); + data = kmalloc(size, GFP_KERNEL); + if (!data) { + dev_err(&port->dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + + status = ti_command_in_sync(tdev, TI_GET_PORT_STATUS, + (__u8)(TI_UART1_PORT+port_number), 0, (__u8 *)data, size); + if (status) { + dev_err(&port->dev, + "%s - get port status command failed, %d\n", + __func__, status); + goto free_data; + } + + dev_dbg(&port->dev, "%s - lsr 0x%02X\n", __func__, data->bLSR); + + *lsr = data->bLSR; + +free_data: + kfree(data); + return status; +} + + +static int ti_get_serial_info(struct ti_port *tport, + struct serial_struct __user *ret_arg) +{ + struct usb_serial_port *port = tport->tp_port; + struct serial_struct ret_serial; + unsigned cwait; + + if (!ret_arg) + return -EFAULT; + + cwait = port->port.closing_wait; + if (cwait != ASYNC_CLOSING_WAIT_NONE) + cwait = jiffies_to_msecs(cwait) / 10; + + memset(&ret_serial, 0, sizeof(ret_serial)); + + ret_serial.type = PORT_16550A; + ret_serial.line = port->serial->minor; + ret_serial.port = port->number - port->serial->minor; + ret_serial.flags = tport->tp_flags; + ret_serial.xmit_fifo_size = TI_WRITE_BUF_SIZE; + ret_serial.baud_base = tport->tp_tdev->td_is_3410 ? 921600 : 460800; + ret_serial.closing_wait = cwait; + + if (copy_to_user(ret_arg, &ret_serial, sizeof(*ret_arg))) + return -EFAULT; + + return 0; +} + + +static int ti_set_serial_info(struct tty_struct *tty, struct ti_port *tport, + struct serial_struct __user *new_arg) +{ + struct serial_struct new_serial; + unsigned cwait; + + if (copy_from_user(&new_serial, new_arg, sizeof(new_serial))) + return -EFAULT; + + cwait = new_serial.closing_wait; + if (cwait != ASYNC_CLOSING_WAIT_NONE) + cwait = msecs_to_jiffies(10 * new_serial.closing_wait); + + tport->tp_flags = new_serial.flags & TI_SET_SERIAL_FLAGS; + tport->tp_port->port.closing_wait = cwait; + + return 0; +} + + +static void ti_handle_new_msr(struct ti_port *tport, __u8 msr) +{ + struct async_icount *icount; + struct tty_struct *tty; + unsigned long flags; + + dev_dbg(&tport->tp_port->dev, "%s - msr 0x%02X\n", __func__, msr); + + if (msr & TI_MSR_DELTA_MASK) { + spin_lock_irqsave(&tport->tp_lock, flags); + icount = &tport->tp_port->icount; + if (msr & TI_MSR_DELTA_CTS) + icount->cts++; + if (msr & TI_MSR_DELTA_DSR) + icount->dsr++; + if (msr & TI_MSR_DELTA_CD) + icount->dcd++; + if (msr & TI_MSR_DELTA_RI) + icount->rng++; + wake_up_interruptible(&tport->tp_port->port.delta_msr_wait); + spin_unlock_irqrestore(&tport->tp_lock, flags); + } + + tport->tp_msr = msr & TI_MSR_MASK; + + /* handle CTS flow control */ + tty = tty_port_tty_get(&tport->tp_port->port); + if (tty && C_CRTSCTS(tty)) { + if (msr & TI_MSR_CTS) { + tty->hw_stopped = 0; + tty_wakeup(tty); + } else { + tty->hw_stopped = 1; + } + } + tty_kref_put(tty); +} + + +static void ti_stop_read(struct ti_port *tport, struct tty_struct *tty) +{ + unsigned long flags; + + spin_lock_irqsave(&tport->tp_lock, flags); + + if (tport->tp_read_urb_state == TI_READ_URB_RUNNING) + tport->tp_read_urb_state = TI_READ_URB_STOPPING; + + spin_unlock_irqrestore(&tport->tp_lock, flags); +} + + +static int ti_restart_read(struct ti_port *tport, struct tty_struct *tty) +{ + struct urb *urb; + int status = 0; + unsigned long flags; + + spin_lock_irqsave(&tport->tp_lock, flags); + + if (tport->tp_read_urb_state == TI_READ_URB_STOPPED) { + tport->tp_read_urb_state = TI_READ_URB_RUNNING; + urb = tport->tp_port->read_urb; + spin_unlock_irqrestore(&tport->tp_lock, flags); + urb->context = tport; + status = usb_submit_urb(urb, GFP_KERNEL); + } else { + tport->tp_read_urb_state = TI_READ_URB_RUNNING; + spin_unlock_irqrestore(&tport->tp_lock, flags); + } + + return status; +} + + +static int ti_command_out_sync(struct ti_device *tdev, __u8 command, + __u16 moduleid, __u16 value, __u8 *data, int size) +{ + int status; + + status = usb_control_msg(tdev->td_serial->dev, + usb_sndctrlpipe(tdev->td_serial->dev, 0), command, + (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT), + value, moduleid, data, size, 1000); + + if (status == size) + status = 0; + + if (status > 0) + status = -ECOMM; + + return status; +} + + +static int ti_command_in_sync(struct ti_device *tdev, __u8 command, + __u16 moduleid, __u16 value, __u8 *data, int size) +{ + int status; + + status = usb_control_msg(tdev->td_serial->dev, + usb_rcvctrlpipe(tdev->td_serial->dev, 0), command, + (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN), + value, moduleid, data, size, 1000); + + if (status == size) + status = 0; + + if (status > 0) + status = -ECOMM; + + return status; +} + + +static int ti_write_byte(struct usb_serial_port *port, + struct ti_device *tdev, unsigned long addr, + __u8 mask, __u8 byte) +{ + int status; + unsigned int size; + struct ti_write_data_bytes *data; + + dev_dbg(&port->dev, "%s - addr 0x%08lX, mask 0x%02X, byte 0x%02X\n", __func__, + addr, mask, byte); + + size = sizeof(struct ti_write_data_bytes) + 2; + data = kmalloc(size, GFP_KERNEL); + if (!data) { + dev_err(&port->dev, "%s - out of memory\n", __func__); + return -ENOMEM; + } + + data->bAddrType = TI_RW_DATA_ADDR_XDATA; + data->bDataType = TI_RW_DATA_BYTE; + data->bDataCounter = 1; + data->wBaseAddrHi = cpu_to_be16(addr>>16); + data->wBaseAddrLo = cpu_to_be16(addr); + data->bData[0] = mask; + data->bData[1] = byte; + + status = ti_command_out_sync(tdev, TI_WRITE_DATA, TI_RAM_PORT, 0, + (__u8 *)data, size); + + if (status < 0) + dev_err(&port->dev, "%s - failed, %d\n", __func__, status); + + kfree(data); + + return status; +} + +static int ti_do_download(struct usb_device *dev, int pipe, + u8 *buffer, int size) +{ + int pos; + u8 cs = 0; + int done; + struct ti_firmware_header *header; + int status = 0; + int len; + + for (pos = sizeof(struct ti_firmware_header); pos < size; pos++) + cs = (__u8)(cs + buffer[pos]); + + header = (struct ti_firmware_header *)buffer; + header->wLength = cpu_to_le16((__u16)(size + - sizeof(struct ti_firmware_header))); + header->bCheckSum = cs; + + dev_dbg(&dev->dev, "%s - downloading firmware\n", __func__); + for (pos = 0; pos < size; pos += done) { + len = min(size - pos, TI_DOWNLOAD_MAX_PACKET_SIZE); + status = usb_bulk_msg(dev, pipe, buffer + pos, len, + &done, 1000); + if (status) + break; + } + return status; +} + +static int ti_download_firmware(struct ti_device *tdev) +{ + int status; + int buffer_size; + __u8 *buffer; + struct usb_device *dev = tdev->td_serial->dev; + unsigned int pipe = usb_sndbulkpipe(dev, + tdev->td_serial->port[0]->bulk_out_endpointAddress); + const struct firmware *fw_p; + char buf[32]; + + /* try ID specific firmware first, then try generic firmware */ + sprintf(buf, "ti_usb-v%04x-p%04x.fw", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + status = request_firmware(&fw_p, buf, &dev->dev); + + if (status != 0) { + buf[0] = '\0'; + if (le16_to_cpu(dev->descriptor.idVendor) == MTS_VENDOR_ID) { + switch (le16_to_cpu(dev->descriptor.idProduct)) { + case MTS_CDMA_PRODUCT_ID: + strcpy(buf, "mts_cdma.fw"); + break; + case MTS_GSM_PRODUCT_ID: + strcpy(buf, "mts_gsm.fw"); + break; + case MTS_EDGE_PRODUCT_ID: + strcpy(buf, "mts_edge.fw"); + break; + case MTS_MT9234MU_PRODUCT_ID: + strcpy(buf, "mts_mt9234mu.fw"); + break; + case MTS_MT9234ZBA_PRODUCT_ID: + strcpy(buf, "mts_mt9234zba.fw"); + break; + case MTS_MT9234ZBAOLD_PRODUCT_ID: + strcpy(buf, "mts_mt9234zba.fw"); + break; } + } + if (buf[0] == '\0') { + if (tdev->td_is_3410) + strcpy(buf, "ti_3410.fw"); + else + strcpy(buf, "ti_5052.fw"); + } + status = request_firmware(&fw_p, buf, &dev->dev); + } + if (status) { + dev_err(&dev->dev, "%s - firmware not found\n", __func__); + return -ENOENT; + } + if (fw_p->size > TI_FIRMWARE_BUF_SIZE) { + dev_err(&dev->dev, "%s - firmware too large %zu\n", __func__, fw_p->size); + release_firmware(fw_p); + return -ENOENT; + } + + buffer_size = TI_FIRMWARE_BUF_SIZE + sizeof(struct ti_firmware_header); + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (buffer) { + memcpy(buffer, fw_p->data, fw_p->size); + memset(buffer + fw_p->size, 0xff, buffer_size - fw_p->size); + status = ti_do_download(dev, pipe, buffer, fw_p->size); + kfree(buffer); + } else { + dev_dbg(&dev->dev, "%s ENOMEM\n", __func__); + status = -ENOMEM; + } + release_firmware(fw_p); + if (status) { + dev_err(&dev->dev, "%s - error downloading firmware, %d\n", + __func__, status); + return status; + } + + dev_dbg(&dev->dev, "%s - download successful\n", __func__); + + return 0; +} diff --git a/drivers/usb/serial/ti_usb_3410_5052.h b/drivers/usb/serial/ti_usb_3410_5052.h new file mode 100644 index 00000000..4a2423e8 --- /dev/null +++ b/drivers/usb/serial/ti_usb_3410_5052.h @@ -0,0 +1,247 @@ +/* vi: ts=8 sw=8 + * + * TI 3410/5052 USB Serial Driver Header + * + * Copyright (C) 2004 Texas Instruments + * + * This driver is based on the Linux io_ti driver, which is + * Copyright (C) 2000-2002 Inside Out Networks + * Copyright (C) 2001-2002 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * For questions or problems with this driver, contact Texas Instruments + * technical support, or Al Borchers , or + * Peter Berger . + */ + +#ifndef _TI_3410_5052_H_ +#define _TI_3410_5052_H_ + +/* Configuration ids */ +#define TI_BOOT_CONFIG 1 +#define TI_ACTIVE_CONFIG 2 + +/* Vendor and product ids */ +#define TI_VENDOR_ID 0x0451 +#define IBM_VENDOR_ID 0x04b3 +#define TI_3410_PRODUCT_ID 0x3410 +#define IBM_4543_PRODUCT_ID 0x4543 +#define IBM_454B_PRODUCT_ID 0x454b +#define IBM_454C_PRODUCT_ID 0x454c +#define TI_3410_EZ430_ID 0xF430 /* TI ez430 development tool */ +#define TI_5052_BOOT_PRODUCT_ID 0x5052 /* no EEPROM, no firmware */ +#define TI_5152_BOOT_PRODUCT_ID 0x5152 /* no EEPROM, no firmware */ +#define TI_5052_EEPROM_PRODUCT_ID 0x505A /* EEPROM, no firmware */ +#define TI_5052_FIRMWARE_PRODUCT_ID 0x505F /* firmware is running */ +#define FRI2_PRODUCT_ID 0x5053 /* Fish River Island II */ + +/* Multi-Tech vendor and product ids */ +#define MTS_VENDOR_ID 0x06E0 +#define MTS_GSM_NO_FW_PRODUCT_ID 0xF108 +#define MTS_CDMA_NO_FW_PRODUCT_ID 0xF109 +#define MTS_CDMA_PRODUCT_ID 0xF110 +#define MTS_GSM_PRODUCT_ID 0xF111 +#define MTS_EDGE_PRODUCT_ID 0xF112 +#define MTS_MT9234MU_PRODUCT_ID 0xF114 +#define MTS_MT9234ZBA_PRODUCT_ID 0xF115 +#define MTS_MT9234ZBAOLD_PRODUCT_ID 0x0319 + +/* Abbott Diabetics vendor and product ids */ +#define ABBOTT_VENDOR_ID 0x1a61 +#define ABBOTT_STEREO_PLUG_ID 0x3410 +#define ABBOTT_PRODUCT_ID ABBOTT_STEREO_PLUG_ID +#define ABBOTT_STRIP_PORT_ID 0x3420 + +/* Commands */ +#define TI_GET_VERSION 0x01 +#define TI_GET_PORT_STATUS 0x02 +#define TI_GET_PORT_DEV_INFO 0x03 +#define TI_GET_CONFIG 0x04 +#define TI_SET_CONFIG 0x05 +#define TI_OPEN_PORT 0x06 +#define TI_CLOSE_PORT 0x07 +#define TI_START_PORT 0x08 +#define TI_STOP_PORT 0x09 +#define TI_TEST_PORT 0x0A +#define TI_PURGE_PORT 0x0B +#define TI_RESET_EXT_DEVICE 0x0C +#define TI_WRITE_DATA 0x80 +#define TI_READ_DATA 0x81 +#define TI_REQ_TYPE_CLASS 0x82 + +/* Module identifiers */ +#define TI_I2C_PORT 0x01 +#define TI_IEEE1284_PORT 0x02 +#define TI_UART1_PORT 0x03 +#define TI_UART2_PORT 0x04 +#define TI_RAM_PORT 0x05 + +/* Modem status */ +#define TI_MSR_DELTA_CTS 0x01 +#define TI_MSR_DELTA_DSR 0x02 +#define TI_MSR_DELTA_RI 0x04 +#define TI_MSR_DELTA_CD 0x08 +#define TI_MSR_CTS 0x10 +#define TI_MSR_DSR 0x20 +#define TI_MSR_RI 0x40 +#define TI_MSR_CD 0x80 +#define TI_MSR_DELTA_MASK 0x0F +#define TI_MSR_MASK 0xF0 + +/* Line status */ +#define TI_LSR_OVERRUN_ERROR 0x01 +#define TI_LSR_PARITY_ERROR 0x02 +#define TI_LSR_FRAMING_ERROR 0x04 +#define TI_LSR_BREAK 0x08 +#define TI_LSR_ERROR 0x0F +#define TI_LSR_RX_FULL 0x10 +#define TI_LSR_TX_EMPTY 0x20 + +/* Line control */ +#define TI_LCR_BREAK 0x40 + +/* Modem control */ +#define TI_MCR_LOOP 0x04 +#define TI_MCR_DTR 0x10 +#define TI_MCR_RTS 0x20 + +/* Mask settings */ +#define TI_UART_ENABLE_RTS_IN 0x0001 +#define TI_UART_DISABLE_RTS 0x0002 +#define TI_UART_ENABLE_PARITY_CHECKING 0x0008 +#define TI_UART_ENABLE_DSR_OUT 0x0010 +#define TI_UART_ENABLE_CTS_OUT 0x0020 +#define TI_UART_ENABLE_X_OUT 0x0040 +#define TI_UART_ENABLE_XA_OUT 0x0080 +#define TI_UART_ENABLE_X_IN 0x0100 +#define TI_UART_ENABLE_DTR_IN 0x0800 +#define TI_UART_DISABLE_DTR 0x1000 +#define TI_UART_ENABLE_MS_INTS 0x2000 +#define TI_UART_ENABLE_AUTO_START_DMA 0x4000 + +/* Parity */ +#define TI_UART_NO_PARITY 0x00 +#define TI_UART_ODD_PARITY 0x01 +#define TI_UART_EVEN_PARITY 0x02 +#define TI_UART_MARK_PARITY 0x03 +#define TI_UART_SPACE_PARITY 0x04 + +/* Stop bits */ +#define TI_UART_1_STOP_BITS 0x00 +#define TI_UART_1_5_STOP_BITS 0x01 +#define TI_UART_2_STOP_BITS 0x02 + +/* Bits per character */ +#define TI_UART_5_DATA_BITS 0x00 +#define TI_UART_6_DATA_BITS 0x01 +#define TI_UART_7_DATA_BITS 0x02 +#define TI_UART_8_DATA_BITS 0x03 + +/* 232/485 modes */ +#define TI_UART_232 0x00 +#define TI_UART_485_RECEIVER_DISABLED 0x01 +#define TI_UART_485_RECEIVER_ENABLED 0x02 + +/* Pipe transfer mode and timeout */ +#define TI_PIPE_MODE_CONTINOUS 0x01 +#define TI_PIPE_MODE_MASK 0x03 +#define TI_PIPE_TIMEOUT_MASK 0x7C +#define TI_PIPE_TIMEOUT_ENABLE 0x80 + +/* Config struct */ +struct ti_uart_config { + __u16 wBaudRate; + __u16 wFlags; + __u8 bDataBits; + __u8 bParity; + __u8 bStopBits; + char cXon; + char cXoff; + __u8 bUartMode; +} __attribute__((packed)); + +/* Get port status */ +struct ti_port_status { + __u8 bCmdCode; + __u8 bModuleId; + __u8 bErrorCode; + __u8 bMSR; + __u8 bLSR; +} __attribute__((packed)); + +/* Purge modes */ +#define TI_PURGE_OUTPUT 0x00 +#define TI_PURGE_INPUT 0x80 + +/* Read/Write data */ +#define TI_RW_DATA_ADDR_SFR 0x10 +#define TI_RW_DATA_ADDR_IDATA 0x20 +#define TI_RW_DATA_ADDR_XDATA 0x30 +#define TI_RW_DATA_ADDR_CODE 0x40 +#define TI_RW_DATA_ADDR_GPIO 0x50 +#define TI_RW_DATA_ADDR_I2C 0x60 +#define TI_RW_DATA_ADDR_FLASH 0x70 +#define TI_RW_DATA_ADDR_DSP 0x80 + +#define TI_RW_DATA_UNSPECIFIED 0x00 +#define TI_RW_DATA_BYTE 0x01 +#define TI_RW_DATA_WORD 0x02 +#define TI_RW_DATA_DOUBLE_WORD 0x04 + +struct ti_write_data_bytes { + __u8 bAddrType; + __u8 bDataType; + __u8 bDataCounter; + __be16 wBaseAddrHi; + __be16 wBaseAddrLo; + __u8 bData[0]; +} __attribute__((packed)); + +struct ti_read_data_request { + __u8 bAddrType; + __u8 bDataType; + __u8 bDataCounter; + __be16 wBaseAddrHi; + __be16 wBaseAddrLo; +} __attribute__((packed)); + +struct ti_read_data_bytes { + __u8 bCmdCode; + __u8 bModuleId; + __u8 bErrorCode; + __u8 bData[0]; +} __attribute__((packed)); + +/* Interrupt struct */ +struct ti_interrupt { + __u8 bICode; + __u8 bIInfo; +} __attribute__((packed)); + +/* Interrupt codes */ +#define TI_GET_PORT_FROM_CODE(c) (((c) >> 4) - 3) +#define TI_GET_FUNC_FROM_CODE(c) ((c) & 0x0f) +#define TI_CODE_HARDWARE_ERROR 0xFF +#define TI_CODE_DATA_ERROR 0x03 +#define TI_CODE_MODEM_STATUS 0x04 + +/* Download firmware max packet size */ +#define TI_DOWNLOAD_MAX_PACKET_SIZE 64 + +/* Firmware image header */ +struct ti_firmware_header { + __le16 wLength; + __u8 bCheckSum; +} __attribute__((packed)); + +/* UART addresses */ +#define TI_UART1_BASE_ADDR 0xFFA0 /* UART 1 base address */ +#define TI_UART2_BASE_ADDR 0xFFB0 /* UART 2 base address */ +#define TI_UART_OFFSET_LCR 0x0002 /* UART MCR register offset */ +#define TI_UART_OFFSET_MCR 0x0004 /* UART MCR register offset */ + +#endif /* _TI_3410_5052_H_ */ diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c new file mode 100644 index 00000000..80d689f0 --- /dev/null +++ b/drivers/usb/serial/usb-serial.c @@ -0,0 +1,1490 @@ +/* + * USB Serial Converter driver + * + * Copyright (C) 2009 - 2013 Johan Hovold (jhovold@gmail.com) + * Copyright (C) 1999 - 2012 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2000 Peter Berger (pberger@brimson.com) + * Copyright (C) 2000 Al Borchers (borchers@steinerpoint.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This driver was originally based on the ACM driver by Armin Fuerst (which was + * based on a driver by Brad Keryan) + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pl2303.h" + +#define DRIVER_AUTHOR "Greg Kroah-Hartman " +#define DRIVER_DESC "USB Serial Driver core" + +/* There is no MODULE_DEVICE_TABLE for usbserial.c. Instead + the MODULE_DEVICE_TABLE declarations in each serial driver + cause the "hotplug" program to pull in whatever module is necessary + via modprobe, and modprobe will load usbserial because the serial + drivers depend on it. +*/ + +static struct usb_serial *serial_table[SERIAL_TTY_MINORS]; +static DEFINE_MUTEX(table_lock); +static LIST_HEAD(usb_serial_driver_list); + +/* + * Look up the serial structure. If it is found and it hasn't been + * disconnected, return with its disc_mutex held and its refcount + * incremented. Otherwise return NULL. + */ +struct usb_serial *usb_serial_get_by_index(unsigned index) +{ + struct usb_serial *serial; + + mutex_lock(&table_lock); + serial = serial_table[index]; + + if (serial) { + mutex_lock(&serial->disc_mutex); + if (serial->disconnected) { + mutex_unlock(&serial->disc_mutex); + serial = NULL; + } else { + kref_get(&serial->kref); + } + } + mutex_unlock(&table_lock); + return serial; +} + +static struct usb_serial *get_free_serial(struct usb_serial *serial, + int num_ports, unsigned int *minor) +{ + unsigned int i, j; + int good_spot; + + dev_dbg(&serial->interface->dev, "%s %d\n", __func__, num_ports); + + *minor = 0; + mutex_lock(&table_lock); + for (i = 0; i < SERIAL_TTY_MINORS; ++i) { + if (serial_table[i]) + continue; + + good_spot = 1; + for (j = 1; j <= num_ports-1; ++j) + if ((i+j >= SERIAL_TTY_MINORS) || (serial_table[i+j])) { + good_spot = 0; + i += j; + break; + } + if (good_spot == 0) + continue; + + *minor = i; + j = 0; + dev_dbg(&serial->interface->dev, "%s - minor base = %d\n", __func__, *minor); + for (i = *minor; (i < (*minor + num_ports)) && (i < SERIAL_TTY_MINORS); ++i) { + serial_table[i] = serial; + serial->port[j++]->number = i; + } + mutex_unlock(&table_lock); + return serial; + } + mutex_unlock(&table_lock); + return NULL; +} + +static void return_serial(struct usb_serial *serial) +{ + int i; + + mutex_lock(&table_lock); + for (i = 0; i < serial->num_ports; ++i) + serial_table[serial->minor + i] = NULL; + mutex_unlock(&table_lock); +} + +static void destroy_serial(struct kref *kref) +{ + struct usb_serial *serial; + struct usb_serial_port *port; + int i; + + serial = to_usb_serial(kref); + + /* return the minor range that this device had */ + if (serial->minor != SERIAL_TTY_NO_MINOR) + return_serial(serial); + + if (serial->attached && serial->type->release) + serial->type->release(serial); + + /* Now that nothing is using the ports, they can be freed */ + for (i = 0; i < serial->num_port_pointers; ++i) { + port = serial->port[i]; + if (port) { + port->serial = NULL; + put_device(&port->dev); + } + } + + usb_put_intf(serial->interface); + usb_put_dev(serial->dev); + kfree(serial); +} + +void usb_serial_put(struct usb_serial *serial) +{ + kref_put(&serial->kref, destroy_serial); +} + +/***************************************************************************** + * Driver tty interface functions + *****************************************************************************/ + +/** + * serial_install - install tty + * @driver: the driver (USB in our case) + * @tty: the tty being created + * + * Create the termios objects for this tty. We use the default + * USB serial settings but permit them to be overridden by + * serial->type->init_termios. + * + * This is the first place a new tty gets used. Hence this is where we + * acquire references to the usb_serial structure and the driver module, + * where we store a pointer to the port, and where we do an autoresume. + * All these actions are reversed in serial_cleanup(). + */ +static int serial_install(struct tty_driver *driver, struct tty_struct *tty) +{ + int idx = tty->index; + struct usb_serial *serial; + struct usb_serial_port *port; + int retval = -ENODEV; + + serial = usb_serial_get_by_index(idx); + if (!serial) + return retval; + + port = serial->port[idx - serial->minor]; + if (!port) + goto error_no_port; + if (!try_module_get(serial->type->driver.owner)) + goto error_module_get; + + retval = usb_autopm_get_interface(serial->interface); + if (retval) + goto error_get_interface; + + retval = tty_port_install(&port->port, driver, tty); + if (retval) + goto error_init_termios; + + mutex_unlock(&serial->disc_mutex); + + /* allow the driver to update the settings */ + if (serial->type->init_termios) + serial->type->init_termios(tty); + + tty->driver_data = port; + + return retval; + + error_init_termios: + usb_autopm_put_interface(serial->interface); + error_get_interface: + module_put(serial->type->driver.owner); + error_module_get: + error_no_port: + usb_serial_put(serial); + mutex_unlock(&serial->disc_mutex); + return retval; +} + +static int serial_port_activate(struct tty_port *tport, struct tty_struct *tty) +{ + struct usb_serial_port *port = + container_of(tport, struct usb_serial_port, port); + struct usb_serial *serial = port->serial; + int retval; + + mutex_lock(&serial->disc_mutex); + if (serial->disconnected) + retval = -ENODEV; + else + retval = port->serial->type->open(tty, port); + mutex_unlock(&serial->disc_mutex); + + if (retval < 0) + retval = usb_translate_errors(retval); + + return retval; +} + +static int serial_open(struct tty_struct *tty, struct file *filp) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + return tty_port_open(&port->port, tty, filp); +} + +/** + * serial_port_shutdown - shut down hardware + * @tport: tty port to shut down + * + * Shut down a USB serial port. Serialized against activate by the + * tport mutex and kept to matching open/close pairs + * of calls by the ASYNCB_INITIALIZED flag. + * + * Not called if tty is console. + */ +static void serial_port_shutdown(struct tty_port *tport) +{ + struct usb_serial_port *port = + container_of(tport, struct usb_serial_port, port); + struct usb_serial_driver *drv = port->serial->type; + + if (drv->close) + drv->close(port); +} + +static void serial_hangup(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + tty_port_hangup(&port->port); +} + +static void serial_close(struct tty_struct *tty, struct file *filp) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + tty_port_close(&port->port, tty, filp); +} + +/** + * serial_cleanup - free resources post close/hangup + * @port: port to free up + * + * Do the resource freeing and refcount dropping for the port. + * Avoid freeing the console. + * + * Called asynchronously after the last tty kref is dropped. + */ +static void serial_cleanup(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial; + struct module *owner; + + dev_dbg(tty->dev, "%s\n", __func__); + + /* The console is magical. Do not hang up the console hardware + * or there will be tears. + */ + if (port->port.console) + return; + + tty->driver_data = NULL; + + serial = port->serial; + owner = serial->type->driver.owner; + + mutex_lock(&serial->disc_mutex); + if (!serial->disconnected) + usb_autopm_put_interface(serial->interface); + mutex_unlock(&serial->disc_mutex); + + usb_serial_put(serial); + module_put(owner); +} + +static int serial_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + struct usb_serial_port *port = tty->driver_data; + int retval = -ENODEV; + + if (port->serial->dev->state == USB_STATE_NOTATTACHED) + goto exit; + + dev_dbg(tty->dev, "%s - %d byte(s)\n", __func__, count); + + retval = port->serial->type->write(tty, port, buf, count); + if (retval < 0) + retval = usb_translate_errors(retval); +exit: + return retval; +} + +static int serial_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + return port->serial->type->write_room(tty); +} + +static int serial_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (serial->disconnected) + return 0; + + return serial->type->chars_in_buffer(tty); +} + +static void serial_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (!port->serial->type->wait_until_sent) + return; + + mutex_lock(&serial->disc_mutex); + if (!serial->disconnected) + port->serial->type->wait_until_sent(tty, timeout); + mutex_unlock(&serial->disc_mutex); +} + +static void serial_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (port->serial->type->throttle) + port->serial->type->throttle(tty); +} + +static void serial_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (port->serial->type->unthrottle) + port->serial->type->unthrottle(tty); +} + +static int serial_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + int retval = -ENOIOCTLCMD; + + dev_dbg(tty->dev, "%s - cmd 0x%.4x\n", __func__, cmd); + + switch (cmd) { + case TIOCMIWAIT: + if (port->serial->type->tiocmiwait) + retval = port->serial->type->tiocmiwait(tty, arg); + break; + default: + if (port->serial->type->ioctl) + retval = port->serial->type->ioctl(tty, cmd, arg); + } + + return retval; +} + +static void serial_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (port->serial->type->set_termios) + port->serial->type->set_termios(tty, port, old); + else + tty_termios_copy_hw(&tty->termios, old); +} + +static int serial_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (port->serial->type->break_ctl) + port->serial->type->break_ctl(tty, break_state); + + return 0; +} + +static int serial_proc_show(struct seq_file *m, void *v) +{ + struct usb_serial *serial; + int i; + char tmp[40]; + + seq_puts(m, "usbserinfo:1.0 driver:2.0\n"); + for (i = 0; i < SERIAL_TTY_MINORS; ++i) { + serial = usb_serial_get_by_index(i); + if (serial == NULL) + continue; + + seq_printf(m, "%d:", i); + if (serial->type->driver.owner) + seq_printf(m, " module:%s", + module_name(serial->type->driver.owner)); + seq_printf(m, " name:\"%s\"", + serial->type->description); + seq_printf(m, " vendor:%04x product:%04x", + le16_to_cpu(serial->dev->descriptor.idVendor), + le16_to_cpu(serial->dev->descriptor.idProduct)); + seq_printf(m, " num_ports:%d", serial->num_ports); + seq_printf(m, " port:%d", i - serial->minor + 1); + usb_make_path(serial->dev, tmp, sizeof(tmp)); + seq_printf(m, " path:%s", tmp); + + seq_putc(m, '\n'); + usb_serial_put(serial); + mutex_unlock(&serial->disc_mutex); + } + return 0; +} + +static int serial_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, serial_proc_show, NULL); +} + +static const struct file_operations serial_proc_fops = { + .owner = THIS_MODULE, + .open = serial_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int serial_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (port->serial->type->tiocmget) + return port->serial->type->tiocmget(tty); + return -EINVAL; +} + +static int serial_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (port->serial->type->tiocmset) + return port->serial->type->tiocmset(tty, set, clear); + return -EINVAL; +} + +static int serial_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (port->serial->type->get_icount) + return port->serial->type->get_icount(tty, icount); + return -EINVAL; +} + +/* + * We would be calling tty_wakeup here, but unfortunately some line + * disciplines have an annoying habit of calling tty->write from + * the write wakeup callback (e.g. n_hdlc.c). + */ +void usb_serial_port_softint(struct usb_serial_port *port) +{ + schedule_work(&port->work); +} +EXPORT_SYMBOL_GPL(usb_serial_port_softint); + +static void usb_serial_port_work(struct work_struct *work) +{ + struct usb_serial_port *port = + container_of(work, struct usb_serial_port, work); + + tty_port_tty_wakeup(&port->port); +} + +static void usb_serial_port_poison_urbs(struct usb_serial_port *port) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) + usb_poison_urb(port->read_urbs[i]); + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) + usb_poison_urb(port->write_urbs[i]); + + usb_poison_urb(port->interrupt_in_urb); + usb_poison_urb(port->interrupt_out_urb); +} + +static void usb_serial_port_unpoison_urbs(struct usb_serial_port *port) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) + usb_unpoison_urb(port->read_urbs[i]); + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) + usb_unpoison_urb(port->write_urbs[i]); + + usb_unpoison_urb(port->interrupt_in_urb); + usb_unpoison_urb(port->interrupt_out_urb); +} + +static void usb_serial_port_release(struct device *dev) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + int i; + + dev_dbg(dev, "%s\n", __func__); + + usb_free_urb(port->interrupt_in_urb); + usb_free_urb(port->interrupt_out_urb); + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) { + usb_free_urb(port->read_urbs[i]); + kfree(port->bulk_in_buffers[i]); + } + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) { + usb_free_urb(port->write_urbs[i]); + kfree(port->bulk_out_buffers[i]); + } + kfifo_free(&port->write_fifo); + kfree(port->interrupt_in_buffer); + kfree(port->interrupt_out_buffer); + tty_port_destroy(&port->port); + kfree(port); +} + +static struct usb_serial *create_serial(struct usb_device *dev, + struct usb_interface *interface, + struct usb_serial_driver *driver) +{ + struct usb_serial *serial; + + serial = kzalloc(sizeof(*serial), GFP_KERNEL); + if (!serial) + return NULL; + serial->dev = usb_get_dev(dev); + serial->type = driver; + serial->interface = usb_get_intf(interface); + kref_init(&serial->kref); + mutex_init(&serial->disc_mutex); + serial->minor = SERIAL_TTY_NO_MINOR; + + return serial; +} + +static const struct usb_device_id *match_dynamic_id(struct usb_interface *intf, + struct usb_serial_driver *drv) +{ + struct usb_dynid *dynid; + + spin_lock(&drv->dynids.lock); + list_for_each_entry(dynid, &drv->dynids.list, node) { + if (usb_match_one_id(intf, &dynid->id)) { + spin_unlock(&drv->dynids.lock); + return &dynid->id; + } + } + spin_unlock(&drv->dynids.lock); + return NULL; +} + +static const struct usb_device_id *get_iface_id(struct usb_serial_driver *drv, + struct usb_interface *intf) +{ + const struct usb_device_id *id; + + id = usb_match_id(intf, drv->id_table); + if (id) { + dev_dbg(&intf->dev, "static descriptor matches\n"); + goto exit; + } + id = match_dynamic_id(intf, drv); + if (id) + dev_dbg(&intf->dev, "dynamic descriptor matches\n"); +exit: + return id; +} + +/* Caller must hold table_lock */ +static struct usb_serial_driver *search_serial_device( + struct usb_interface *iface) +{ + const struct usb_device_id *id = NULL; + struct usb_serial_driver *drv; + struct usb_driver *driver = to_usb_driver(iface->dev.driver); + + /* Check if the usb id matches a known device */ + list_for_each_entry(drv, &usb_serial_driver_list, driver_list) { + if (drv->usb_driver == driver) + id = get_iface_id(drv, iface); + if (id) + return drv; + } + + return NULL; +} + +static int serial_port_carrier_raised(struct tty_port *port) +{ + struct usb_serial_port *p = container_of(port, struct usb_serial_port, port); + struct usb_serial_driver *drv = p->serial->type; + + if (drv->carrier_raised) + return drv->carrier_raised(p); + /* No carrier control - don't block */ + return 1; +} + +static void serial_port_dtr_rts(struct tty_port *port, int on) +{ + struct usb_serial_port *p = container_of(port, struct usb_serial_port, port); + struct usb_serial *serial = p->serial; + struct usb_serial_driver *drv = serial->type; + + if (!drv->dtr_rts) + return; + /* + * Work-around bug in the tty-layer which can result in dtr_rts + * being called after a disconnect (and tty_unregister_device + * has returned). Remove once bug has been squashed. + */ + mutex_lock(&serial->disc_mutex); + if (!serial->disconnected) + drv->dtr_rts(p, on); + mutex_unlock(&serial->disc_mutex); +} + +static const struct tty_port_operations serial_port_ops = { + .carrier_raised = serial_port_carrier_raised, + .dtr_rts = serial_port_dtr_rts, + .activate = serial_port_activate, + .shutdown = serial_port_shutdown, +}; + +static int usb_serial_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct device *ddev = &interface->dev; + struct usb_device *dev = interface_to_usbdev(interface); + struct usb_serial *serial = NULL; + struct usb_serial_port *port; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct usb_endpoint_descriptor *interrupt_in_endpoint[MAX_NUM_PORTS]; + struct usb_endpoint_descriptor *interrupt_out_endpoint[MAX_NUM_PORTS]; + struct usb_endpoint_descriptor *bulk_in_endpoint[MAX_NUM_PORTS]; + struct usb_endpoint_descriptor *bulk_out_endpoint[MAX_NUM_PORTS]; + struct usb_serial_driver *type = NULL; + int retval; + unsigned int minor; + int buffer_size; + int i; + int j; + int num_interrupt_in = 0; + int num_interrupt_out = 0; + int num_bulk_in = 0; + int num_bulk_out = 0; + int num_ports = 0; + int max_endpoints; + + mutex_lock(&table_lock); + type = search_serial_device(interface); + if (!type) { + mutex_unlock(&table_lock); + dev_dbg(ddev, "none matched\n"); + return -ENODEV; + } + + if (!try_module_get(type->driver.owner)) { + mutex_unlock(&table_lock); + dev_err(ddev, "module get failed, exiting\n"); + return -EIO; + } + mutex_unlock(&table_lock); + + serial = create_serial(dev, interface, type); + if (!serial) { + module_put(type->driver.owner); + return -ENOMEM; + } + + /* if this device type has a probe function, call it */ + if (type->probe) { + const struct usb_device_id *id; + + id = get_iface_id(type, interface); + retval = type->probe(serial, id); + + if (retval) { + dev_dbg(ddev, "sub driver rejected device\n"); + usb_serial_put(serial); + module_put(type->driver.owner); + return retval; + } + } + + /* descriptor matches, let's find the endpoints needed */ + /* check out the endpoints */ + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(endpoint)) { + /* we found a bulk in endpoint */ + dev_dbg(ddev, "found bulk in on endpoint %d\n", i); + if (num_bulk_in < MAX_NUM_PORTS) { + bulk_in_endpoint[num_bulk_in] = endpoint; + ++num_bulk_in; + } + } + + if (usb_endpoint_is_bulk_out(endpoint)) { + /* we found a bulk out endpoint */ + dev_dbg(ddev, "found bulk out on endpoint %d\n", i); + if (num_bulk_out < MAX_NUM_PORTS) { + bulk_out_endpoint[num_bulk_out] = endpoint; + ++num_bulk_out; + } + } + + if (usb_endpoint_is_int_in(endpoint)) { + /* we found a interrupt in endpoint */ + dev_dbg(ddev, "found interrupt in on endpoint %d\n", i); + if (num_interrupt_in < MAX_NUM_PORTS) { + interrupt_in_endpoint[num_interrupt_in] = + endpoint; + ++num_interrupt_in; + } + } + + if (usb_endpoint_is_int_out(endpoint)) { + /* we found an interrupt out endpoint */ + dev_dbg(ddev, "found interrupt out on endpoint %d\n", i); + if (num_interrupt_out < MAX_NUM_PORTS) { + interrupt_out_endpoint[num_interrupt_out] = + endpoint; + ++num_interrupt_out; + } + } + } + +#if defined(CONFIG_USB_SERIAL_PL2303) || defined(CONFIG_USB_SERIAL_PL2303_MODULE) + /* BEGIN HORRIBLE HACK FOR PL2303 */ + /* this is needed due to the looney way its endpoints are set up */ + if (((le16_to_cpu(dev->descriptor.idVendor) == PL2303_VENDOR_ID) && + (le16_to_cpu(dev->descriptor.idProduct) == PL2303_PRODUCT_ID)) || + ((le16_to_cpu(dev->descriptor.idVendor) == ATEN_VENDOR_ID) && + (le16_to_cpu(dev->descriptor.idProduct) == ATEN_PRODUCT_ID)) || + ((le16_to_cpu(dev->descriptor.idVendor) == ALCOR_VENDOR_ID) && + (le16_to_cpu(dev->descriptor.idProduct) == ALCOR_PRODUCT_ID)) || + ((le16_to_cpu(dev->descriptor.idVendor) == SIEMENS_VENDOR_ID) && + (le16_to_cpu(dev->descriptor.idProduct) == SIEMENS_PRODUCT_ID_EF81))) { + if (interface != dev->actconfig->interface[0]) { + /* check out the endpoints of the other interface*/ + iface_desc = dev->actconfig->interface[0]->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + if (usb_endpoint_is_int_in(endpoint)) { + /* we found a interrupt in endpoint */ + dev_dbg(ddev, "found interrupt in for Prolific device on separate interface\n"); + if (num_interrupt_in < MAX_NUM_PORTS) { + interrupt_in_endpoint[num_interrupt_in] = endpoint; + ++num_interrupt_in; + } + } + } + } + + /* Now make sure the PL-2303 is configured correctly. + * If not, give up now and hope this hack will work + * properly during a later invocation of usb_serial_probe + */ + if (num_bulk_in == 0 || num_bulk_out == 0) { + dev_info(ddev, "PL-2303 hack: descriptors matched but endpoints did not\n"); + usb_serial_put(serial); + module_put(type->driver.owner); + return -ENODEV; + } + } + /* END HORRIBLE HACK FOR PL2303 */ +#endif + +#ifdef CONFIG_USB_SERIAL_GENERIC + if (type == &usb_serial_generic_device) { + num_ports = num_bulk_out; + if (num_ports == 0) { + dev_err(ddev, "Generic device with no bulk out, not allowed.\n"); + usb_serial_put(serial); + module_put(type->driver.owner); + return -EIO; + } + dev_info(ddev, "The \"generic\" usb-serial driver is only for testing and one-off prototypes.\n"); + dev_info(ddev, "Tell linux-usb@vger.kernel.org to add your device to a proper driver.\n"); + } +#endif + if (!num_ports) { + /* if this device type has a calc_num_ports function, call it */ + if (type->calc_num_ports) + num_ports = type->calc_num_ports(serial); + if (!num_ports) + num_ports = type->num_ports; + } + + if (num_ports > MAX_NUM_PORTS) { + dev_warn(ddev, "too many ports requested: %d\n", num_ports); + num_ports = MAX_NUM_PORTS; + } + + serial->num_ports = num_ports; + serial->num_bulk_in = num_bulk_in; + serial->num_bulk_out = num_bulk_out; + serial->num_interrupt_in = num_interrupt_in; + serial->num_interrupt_out = num_interrupt_out; + + /* found all that we need */ + dev_info(ddev, "%s converter detected\n", type->description); + + /* create our ports, we need as many as the max endpoints */ + /* we don't use num_ports here because some devices have more + endpoint pairs than ports */ + max_endpoints = max(num_bulk_in, num_bulk_out); + max_endpoints = max(max_endpoints, num_interrupt_in); + max_endpoints = max(max_endpoints, num_interrupt_out); + max_endpoints = max(max_endpoints, (int)serial->num_ports); + serial->num_port_pointers = max_endpoints; + + dev_dbg(ddev, "setting up %d port structures for this device", max_endpoints); + for (i = 0; i < max_endpoints; ++i) { + port = kzalloc(sizeof(struct usb_serial_port), GFP_KERNEL); + if (!port) + goto probe_error; + tty_port_init(&port->port); + port->port.ops = &serial_port_ops; + port->serial = serial; + spin_lock_init(&port->lock); + /* Keep this for private driver use for the moment but + should probably go away */ + INIT_WORK(&port->work, usb_serial_port_work); + serial->port[i] = port; + port->dev.parent = &interface->dev; + port->dev.driver = NULL; + port->dev.bus = &usb_serial_bus_type; + port->dev.release = &usb_serial_port_release; + device_initialize(&port->dev); + } + + /* set up the endpoint information */ + for (i = 0; i < num_bulk_in; ++i) { + endpoint = bulk_in_endpoint[i]; + port = serial->port[i]; + buffer_size = max_t(int, serial->type->bulk_in_size, + usb_endpoint_maxp(endpoint)); + port->bulk_in_size = buffer_size; + port->bulk_in_endpointAddress = endpoint->bEndpointAddress; + + for (j = 0; j < ARRAY_SIZE(port->read_urbs); ++j) { + set_bit(j, &port->read_urbs_free); + port->read_urbs[j] = usb_alloc_urb(0, GFP_KERNEL); + if (!port->read_urbs[j]) + goto probe_error; + port->bulk_in_buffers[j] = kmalloc(buffer_size, + GFP_KERNEL); + if (!port->bulk_in_buffers[j]) + goto probe_error; + usb_fill_bulk_urb(port->read_urbs[j], dev, + usb_rcvbulkpipe(dev, + endpoint->bEndpointAddress), + port->bulk_in_buffers[j], buffer_size, + serial->type->read_bulk_callback, + port); + } + + port->read_urb = port->read_urbs[0]; + port->bulk_in_buffer = port->bulk_in_buffers[0]; + } + + for (i = 0; i < num_bulk_out; ++i) { + endpoint = bulk_out_endpoint[i]; + port = serial->port[i]; + if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) + goto probe_error; + buffer_size = serial->type->bulk_out_size; + if (!buffer_size) + buffer_size = usb_endpoint_maxp(endpoint); + port->bulk_out_size = buffer_size; + port->bulk_out_endpointAddress = endpoint->bEndpointAddress; + + for (j = 0; j < ARRAY_SIZE(port->write_urbs); ++j) { + set_bit(j, &port->write_urbs_free); + port->write_urbs[j] = usb_alloc_urb(0, GFP_KERNEL); + if (!port->write_urbs[j]) + goto probe_error; + port->bulk_out_buffers[j] = kmalloc(buffer_size, + GFP_KERNEL); + if (!port->bulk_out_buffers[j]) + goto probe_error; + usb_fill_bulk_urb(port->write_urbs[j], dev, + usb_sndbulkpipe(dev, + endpoint->bEndpointAddress), + port->bulk_out_buffers[j], buffer_size, + serial->type->write_bulk_callback, + port); + } + + port->write_urb = port->write_urbs[0]; + port->bulk_out_buffer = port->bulk_out_buffers[0]; + } + + if (serial->type->read_int_callback) { + for (i = 0; i < num_interrupt_in; ++i) { + endpoint = interrupt_in_endpoint[i]; + port = serial->port[i]; + port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->interrupt_in_urb) + goto probe_error; + buffer_size = usb_endpoint_maxp(endpoint); + port->interrupt_in_endpointAddress = + endpoint->bEndpointAddress; + port->interrupt_in_buffer = kmalloc(buffer_size, + GFP_KERNEL); + if (!port->interrupt_in_buffer) + goto probe_error; + usb_fill_int_urb(port->interrupt_in_urb, dev, + usb_rcvintpipe(dev, + endpoint->bEndpointAddress), + port->interrupt_in_buffer, buffer_size, + serial->type->read_int_callback, port, + endpoint->bInterval); + } + } else if (num_interrupt_in) { + dev_dbg(ddev, "The device claims to support interrupt in transfers, but read_int_callback is not defined\n"); + } + + if (serial->type->write_int_callback) { + for (i = 0; i < num_interrupt_out; ++i) { + endpoint = interrupt_out_endpoint[i]; + port = serial->port[i]; + port->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->interrupt_out_urb) + goto probe_error; + buffer_size = usb_endpoint_maxp(endpoint); + port->interrupt_out_size = buffer_size; + port->interrupt_out_endpointAddress = + endpoint->bEndpointAddress; + port->interrupt_out_buffer = kmalloc(buffer_size, + GFP_KERNEL); + if (!port->interrupt_out_buffer) + goto probe_error; + usb_fill_int_urb(port->interrupt_out_urb, dev, + usb_sndintpipe(dev, + endpoint->bEndpointAddress), + port->interrupt_out_buffer, buffer_size, + serial->type->write_int_callback, port, + endpoint->bInterval); + } + } else if (num_interrupt_out) { + dev_dbg(ddev, "The device claims to support interrupt out transfers, but write_int_callback is not defined\n"); + } + + usb_set_intfdata(interface, serial); + + /* if this device type has an attach function, call it */ + if (type->attach) { + retval = type->attach(serial); + if (retval < 0) + goto probe_error; + serial->attached = 1; + if (retval > 0) { + /* quietly accept this device, but don't bind to a + serial port as it's about to disappear */ + serial->num_ports = 0; + goto exit; + } + } else { + serial->attached = 1; + } + + /* Avoid race with tty_open and serial_install by setting the + * disconnected flag and not clearing it until all ports have been + * registered. + */ + serial->disconnected = 1; + + if (get_free_serial(serial, num_ports, &minor) == NULL) { + dev_err(ddev, "No more free serial devices\n"); + goto probe_error; + } + serial->minor = minor; + + /* register all of the individual ports with the driver core */ + for (i = 0; i < num_ports; ++i) { + port = serial->port[i]; + dev_set_name(&port->dev, "ttyUSB%d", port->number); + dev_dbg(ddev, "registering %s", dev_name(&port->dev)); + device_enable_async_suspend(&port->dev); + + retval = device_add(&port->dev); + if (retval) + dev_err(ddev, "Error registering port device, continuing\n"); + } + + serial->disconnected = 0; + + usb_serial_console_init(minor); +exit: + module_put(type->driver.owner); + return 0; + +probe_error: + usb_serial_put(serial); + module_put(type->driver.owner); + return -EIO; +} + +static void usb_serial_disconnect(struct usb_interface *interface) +{ + int i; + struct usb_serial *serial = usb_get_intfdata(interface); + struct device *dev = &interface->dev; + struct usb_serial_port *port; + + usb_serial_console_disconnect(serial); + + mutex_lock(&serial->disc_mutex); + /* must set a flag, to signal subdrivers */ + serial->disconnected = 1; + mutex_unlock(&serial->disc_mutex); + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (port) { + struct tty_struct *tty = tty_port_tty_get(&port->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + usb_serial_port_poison_urbs(port); + wake_up_interruptible(&port->port.delta_msr_wait); + cancel_work_sync(&port->work); + if (device_is_registered(&port->dev)) + device_del(&port->dev); + } + } + if (serial->type->disconnect) + serial->type->disconnect(serial); + + /* let the last holder of this object cause it to be cleaned up */ + usb_serial_put(serial); + dev_info(dev, "device disconnected\n"); +} + +int usb_serial_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_serial *serial = usb_get_intfdata(intf); + struct usb_serial_port *port; + int i, r = 0; + + serial->suspending = 1; + + /* + * serial->type->suspend() MUST return 0 in system sleep context, + * otherwise, the resume callback has to recover device from + * previous suspend failure. + */ + if (serial->type->suspend) { + r = serial->type->suspend(serial, message); + if (r < 0) { + serial->suspending = 0; + goto err_out; + } + } + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (port) + usb_serial_port_poison_urbs(port); + } + +err_out: + return r; +} +EXPORT_SYMBOL(usb_serial_suspend); + +static void usb_serial_unpoison_port_urbs(struct usb_serial *serial) +{ + struct usb_serial_port *port; + int i; + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (port) + usb_serial_port_unpoison_urbs(port); + } +} + +int usb_serial_resume(struct usb_interface *intf) +{ + struct usb_serial *serial = usb_get_intfdata(intf); + int rv; + + usb_serial_unpoison_port_urbs(serial); + + serial->suspending = 0; + if (serial->type->resume) + rv = serial->type->resume(serial); + else + rv = usb_serial_generic_resume(serial); + + return rv; +} +EXPORT_SYMBOL(usb_serial_resume); + +static int usb_serial_reset_resume(struct usb_interface *intf) +{ + struct usb_serial *serial = usb_get_intfdata(intf); + int rv; + + usb_serial_unpoison_port_urbs(serial); + + serial->suspending = 0; + if (serial->type->reset_resume) + rv = serial->type->reset_resume(serial); + else { + rv = -EOPNOTSUPP; + intf->needs_binding = 1; + } + + return rv; +} + +static const struct tty_operations serial_ops = { + .open = serial_open, + .close = serial_close, + .write = serial_write, + .hangup = serial_hangup, + .write_room = serial_write_room, + .ioctl = serial_ioctl, + .set_termios = serial_set_termios, + .throttle = serial_throttle, + .unthrottle = serial_unthrottle, + .break_ctl = serial_break, + .chars_in_buffer = serial_chars_in_buffer, + .wait_until_sent = serial_wait_until_sent, + .tiocmget = serial_tiocmget, + .tiocmset = serial_tiocmset, + .get_icount = serial_get_icount, + .cleanup = serial_cleanup, + .install = serial_install, + .proc_fops = &serial_proc_fops, +}; + + +struct tty_driver *usb_serial_tty_driver; + +/* Driver structure we register with the USB core */ +static struct usb_driver usb_serial_driver = { + .name = "usbserial", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .suspend = usb_serial_suspend, + .resume = usb_serial_resume, + .no_dynamic_id = 1, + .supports_autosuspend = 1, +}; + +static int __init usb_serial_init(void) +{ + int i; + int result; + + usb_serial_tty_driver = alloc_tty_driver(SERIAL_TTY_MINORS); + if (!usb_serial_tty_driver) + return -ENOMEM; + + /* Initialize our global data */ + for (i = 0; i < SERIAL_TTY_MINORS; ++i) + serial_table[i] = NULL; + + result = bus_register(&usb_serial_bus_type); + if (result) { + pr_err("%s - registering bus driver failed\n", __func__); + goto exit_bus; + } + + usb_serial_tty_driver->driver_name = "usbserial"; + usb_serial_tty_driver->name = "ttyUSB"; + usb_serial_tty_driver->major = SERIAL_TTY_MAJOR; + usb_serial_tty_driver->minor_start = 0; + usb_serial_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + usb_serial_tty_driver->subtype = SERIAL_TYPE_NORMAL; + usb_serial_tty_driver->flags = TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV; + usb_serial_tty_driver->init_termios = tty_std_termios; + usb_serial_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD + | HUPCL | CLOCAL; + usb_serial_tty_driver->init_termios.c_ispeed = 9600; + usb_serial_tty_driver->init_termios.c_ospeed = 9600; + tty_set_operations(usb_serial_tty_driver, &serial_ops); + result = tty_register_driver(usb_serial_tty_driver); + if (result) { + pr_err("%s - tty_register_driver failed\n", __func__); + goto exit_reg_driver; + } + + /* register the USB driver */ + result = usb_register(&usb_serial_driver); + if (result < 0) { + pr_err("%s - usb_register failed\n", __func__); + goto exit_tty; + } + + /* register the generic driver, if we should */ + result = usb_serial_generic_register(); + if (result < 0) { + pr_err("%s - registering generic driver failed\n", __func__); + goto exit_generic; + } + + return result; + +exit_generic: + usb_deregister(&usb_serial_driver); + +exit_tty: + tty_unregister_driver(usb_serial_tty_driver); + +exit_reg_driver: + bus_unregister(&usb_serial_bus_type); + +exit_bus: + pr_err("%s - returning with error %d\n", __func__, result); + put_tty_driver(usb_serial_tty_driver); + return result; +} + + +static void __exit usb_serial_exit(void) +{ + usb_serial_console_exit(); + + usb_serial_generic_deregister(); + + usb_deregister(&usb_serial_driver); + tty_unregister_driver(usb_serial_tty_driver); + put_tty_driver(usb_serial_tty_driver); + bus_unregister(&usb_serial_bus_type); +} + + +module_init(usb_serial_init); +module_exit(usb_serial_exit); + +#define set_to_generic_if_null(type, function) \ + do { \ + if (!type->function) { \ + type->function = usb_serial_generic_##function; \ + pr_debug("%s: using generic " #function "\n", \ + type->driver.name); \ + } \ + } while (0) + +static void usb_serial_operations_init(struct usb_serial_driver *device) +{ + set_to_generic_if_null(device, open); + set_to_generic_if_null(device, write); + set_to_generic_if_null(device, close); + set_to_generic_if_null(device, write_room); + set_to_generic_if_null(device, chars_in_buffer); + if (device->tx_empty) + set_to_generic_if_null(device, wait_until_sent); + set_to_generic_if_null(device, read_bulk_callback); + set_to_generic_if_null(device, write_bulk_callback); + set_to_generic_if_null(device, process_read_urb); + set_to_generic_if_null(device, prepare_write_buffer); +} + +static int usb_serial_register(struct usb_serial_driver *driver) +{ + int retval; + + if (usb_disabled()) + return -ENODEV; + + if (!driver->description) + driver->description = driver->driver.name; + if (!driver->usb_driver) { + WARN(1, "Serial driver %s has no usb_driver\n", + driver->description); + return -EINVAL; + } + + usb_serial_operations_init(driver); + + /* Add this device to our list of devices */ + mutex_lock(&table_lock); + list_add(&driver->driver_list, &usb_serial_driver_list); + + retval = usb_serial_bus_register(driver); + if (retval) { + pr_err("problem %d when registering driver %s\n", retval, driver->description); + list_del(&driver->driver_list); + } else + pr_info("USB Serial support registered for %s\n", driver->description); + + mutex_unlock(&table_lock); + return retval; +} + +static void usb_serial_deregister(struct usb_serial_driver *device) +{ + pr_info("USB Serial deregistering driver %s\n", device->description); + + mutex_lock(&table_lock); + list_del(&device->driver_list); + mutex_unlock(&table_lock); + + usb_serial_bus_deregister(device); +} + +/** + * usb_serial_register_drivers - register drivers for a usb-serial module + * @serial_drivers: NULL-terminated array of pointers to drivers to be registered + * @name: name of the usb_driver for this set of @serial_drivers + * @id_table: list of all devices this @serial_drivers set binds to + * + * Registers all the drivers in the @serial_drivers array, and dynamically + * creates a struct usb_driver with the name @name and id_table of @id_table. + */ +int usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers[], + const char *name, + const struct usb_device_id *id_table) +{ + int rc; + struct usb_driver *udriver; + struct usb_serial_driver * const *sd; + + /* + * udriver must be registered before any of the serial drivers, + * because the store_new_id() routine for the serial drivers (in + * bus.c) probes udriver. + * + * Performance hack: We don't want udriver to be probed until + * the serial drivers are registered, because the probe would + * simply fail for lack of a matching serial driver. + * So we leave udriver's id_table set to NULL until we are all set. + * + * Suspend/resume support is implemented in the usb-serial core, + * so fill in the PM-related fields in udriver. + */ + udriver = kzalloc(sizeof(*udriver), GFP_KERNEL); + if (!udriver) + return -ENOMEM; + + udriver->name = name; + udriver->no_dynamic_id = 1; + udriver->supports_autosuspend = 1; + udriver->suspend = usb_serial_suspend; + udriver->resume = usb_serial_resume; + udriver->probe = usb_serial_probe; + udriver->disconnect = usb_serial_disconnect; + + /* we only set the reset_resume field if the serial_driver has one */ + for (sd = serial_drivers; *sd; ++sd) { + if ((*sd)->reset_resume) { + udriver->reset_resume = usb_serial_reset_resume; + break; + } + } + + rc = usb_register(udriver); + if (rc) + return rc; + + for (sd = serial_drivers; *sd; ++sd) { + (*sd)->usb_driver = udriver; + rc = usb_serial_register(*sd); + if (rc) + goto failed; + } + + /* Now set udriver's id_table and look for matches */ + udriver->id_table = id_table; + rc = driver_attach(&udriver->drvwrap.driver); + return 0; + + failed: + while (sd-- > serial_drivers) + usb_serial_deregister(*sd); + usb_deregister(udriver); + return rc; +} +EXPORT_SYMBOL_GPL(usb_serial_register_drivers); + +/** + * usb_serial_deregister_drivers - deregister drivers for a usb-serial module + * @serial_drivers: NULL-terminated array of pointers to drivers to be deregistered + * + * Deregisters all the drivers in the @serial_drivers array and deregisters and + * frees the struct usb_driver that was created by the call to + * usb_serial_register_drivers(). + */ +void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[]) +{ + struct usb_driver *udriver = (*serial_drivers)->usb_driver; + + for (; *serial_drivers; ++serial_drivers) + usb_serial_deregister(*serial_drivers); + usb_deregister(udriver); + kfree(udriver); +} +EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h new file mode 100644 index 00000000..684739b8 --- /dev/null +++ b/drivers/usb/serial/usb-wwan.h @@ -0,0 +1,68 @@ +/* + * Definitions for USB serial mobile broadband cards + */ + +#ifndef __LINUX_USB_USB_WWAN +#define __LINUX_USB_USB_WWAN + +extern void usb_wwan_dtr_rts(struct usb_serial_port *port, int on); +extern int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port); +extern void usb_wwan_close(struct usb_serial_port *port); +extern int usb_wwan_port_probe(struct usb_serial_port *port); +extern int usb_wwan_port_remove(struct usb_serial_port *port); +extern int usb_wwan_write_room(struct tty_struct *tty); +extern void usb_wwan_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old); +extern int usb_wwan_tiocmget(struct tty_struct *tty); +extern int usb_wwan_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +extern int usb_wwan_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg); +extern int usb_wwan_send_setup(struct usb_serial_port *port); +extern int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +extern int usb_wwan_chars_in_buffer(struct tty_struct *tty); +#ifdef CONFIG_PM +extern int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message); +extern int usb_wwan_resume(struct usb_serial *serial); +#endif + +/* per port private data */ + +#define N_IN_URB 4 +#define N_OUT_URB 4 +#define IN_BUFLEN 4096 +#define OUT_BUFLEN 4096 + +struct usb_wwan_intf_private { + spinlock_t susp_lock; + unsigned int suspended:1; + int in_flight; + int (*send_setup) (struct usb_serial_port *port); + void *private; +}; + +struct usb_wwan_port_private { + /* Input endpoints and buffer for this port */ + struct urb *in_urbs[N_IN_URB]; + u8 *in_buffer[N_IN_URB]; + /* Output endpoints and buffer for this port */ + struct urb *out_urbs[N_OUT_URB]; + u8 *out_buffer[N_OUT_URB]; + unsigned long out_busy; /* Bit vector of URBs in use */ + int opened; + struct usb_anchor delayed; + + /* Settings for the port */ + int rts_state; /* Handshaking pins (outputs) */ + int dtr_state; + int cts_state; /* Handshaking pins (inputs) */ + int dsr_state; + int dcd_state; + int ri_state; + + unsigned long tx_start_time[N_OUT_URB]; +}; + +#endif /* __LINUX_USB_USB_WWAN */ diff --git a/drivers/usb/serial/usb_debug.c b/drivers/usb/serial/usb_debug.c new file mode 100644 index 00000000..5760f97e --- /dev/null +++ b/drivers/usb/serial/usb_debug.c @@ -0,0 +1,80 @@ +/* + * USB Debug cable driver + * + * Copyright (C) 2006 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_MAX_PACKET_SIZE 8 +#define USB_DEBUG_BRK_SIZE 8 +static char USB_DEBUG_BRK[USB_DEBUG_BRK_SIZE] = { + 0x00, + 0xff, + 0x01, + 0xfe, + 0x00, + 0xfe, + 0x01, + 0xff, +}; + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x0525, 0x127a) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* This HW really does not support a serial break, so one will be + * emulated when ever the break state is set to true. + */ +static void usb_debug_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + if (!break_state) + return; + usb_serial_generic_write(tty, port, USB_DEBUG_BRK, USB_DEBUG_BRK_SIZE); +} + +static void usb_debug_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + + if (urb->actual_length == USB_DEBUG_BRK_SIZE && + memcmp(urb->transfer_buffer, USB_DEBUG_BRK, + USB_DEBUG_BRK_SIZE) == 0) { + usb_serial_handle_break(port); + return; + } + + usb_serial_generic_process_read_urb(urb); +} + +static struct usb_serial_driver debug_device = { + .driver = { + .owner = THIS_MODULE, + .name = "debug", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_out_size = USB_DEBUG_MAX_PACKET_SIZE, + .break_ctl = usb_debug_break_ctl, + .process_read_urb = usb_debug_process_read_urb, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &debug_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c new file mode 100644 index 00000000..36f6b6a5 --- /dev/null +++ b/drivers/usb/serial/usb_wwan.c @@ -0,0 +1,710 @@ +/* + USB Driver layer for GSM modems + + Copyright (C) 2005 Matthias Urlichs + + This driver is free software; you can redistribute it and/or modify + it under the terms of Version 2 of the GNU General Public License as + published by the Free Software Foundation. + + Portions copied from the Keyspan driver by Hugh Blemings + + History: see the git log. + + Work sponsored by: Sigos GmbH, Germany + + This driver exists because the "normal" serial driver doesn't work too well + with GSM modems. Issues: + - data loss -- one single Receive URB is not nearly enough + - controlling the baud rate doesn't make sense +*/ + +#define DRIVER_AUTHOR "Matthias Urlichs " +#define DRIVER_DESC "USB Driver for GSM modems" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb-wwan.h" + +void usb_wwan_dtr_rts(struct usb_serial_port *port, int on) +{ + struct usb_wwan_port_private *portdata; + struct usb_wwan_intf_private *intfdata; + + intfdata = port->serial->private; + + if (!intfdata->send_setup) + return; + + portdata = usb_get_serial_port_data(port); + /* FIXME: locking */ + portdata->rts_state = on; + portdata->dtr_state = on; + + intfdata->send_setup(port); +} +EXPORT_SYMBOL(usb_wwan_dtr_rts); + +void usb_wwan_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct usb_wwan_intf_private *intfdata = port->serial->private; + + /* Doesn't support option setting */ + tty_termios_copy_hw(&tty->termios, old_termios); + + if (intfdata->send_setup) + intfdata->send_setup(port); +} +EXPORT_SYMBOL(usb_wwan_set_termios); + +int usb_wwan_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int value; + struct usb_wwan_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + + value = ((portdata->rts_state) ? TIOCM_RTS : 0) | + ((portdata->dtr_state) ? TIOCM_DTR : 0) | + ((portdata->cts_state) ? TIOCM_CTS : 0) | + ((portdata->dsr_state) ? TIOCM_DSR : 0) | + ((portdata->dcd_state) ? TIOCM_CAR : 0) | + ((portdata->ri_state) ? TIOCM_RNG : 0); + + return value; +} +EXPORT_SYMBOL(usb_wwan_tiocmget); + +int usb_wwan_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_wwan_port_private *portdata; + struct usb_wwan_intf_private *intfdata; + + portdata = usb_get_serial_port_data(port); + intfdata = port->serial->private; + + if (!intfdata->send_setup) + return -EINVAL; + + /* FIXME: what locks portdata fields ? */ + if (set & TIOCM_RTS) + portdata->rts_state = 1; + if (set & TIOCM_DTR) + portdata->dtr_state = 1; + + if (clear & TIOCM_RTS) + portdata->rts_state = 0; + if (clear & TIOCM_DTR) + portdata->dtr_state = 0; + return intfdata->send_setup(port); +} +EXPORT_SYMBOL(usb_wwan_tiocmset); + +static int get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + tmp.line = port->serial->minor; + tmp.port = port->number; + tmp.baud_base = tty_get_baud_rate(port->port.tty); + tmp.close_delay = port->port.close_delay / 10; + tmp.closing_wait = port->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + port->port.closing_wait / 10; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int set_serial_info(struct usb_serial_port *port, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&port->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != port->port.close_delay) || + (closing_wait != port->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + port->port.close_delay = close_delay; + port->port.closing_wait = closing_wait; + } + + mutex_unlock(&port->port.mutex); + return retval; +} + +int usb_wwan_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s cmd 0x%04x\n", __func__, cmd); + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(port, + (struct serial_struct __user *) arg); + case TIOCSSERIAL: + return set_serial_info(port, + (struct serial_struct __user *) arg); + default: + break; + } + + dev_dbg(&port->dev, "%s arg not supported\n", __func__); + + return -ENOIOCTLCMD; +} +EXPORT_SYMBOL(usb_wwan_ioctl); + +/* Write */ +int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct usb_wwan_port_private *portdata; + struct usb_wwan_intf_private *intfdata; + int i; + int left, todo; + struct urb *this_urb = NULL; /* spurious */ + int err; + unsigned long flags; + + portdata = usb_get_serial_port_data(port); + intfdata = port->serial->private; + + dev_dbg(&port->dev, "%s: write (%d chars)\n", __func__, count); + + i = 0; + left = count; + for (i = 0; left > 0 && i < N_OUT_URB; i++) { + todo = left; + if (todo > OUT_BUFLEN) + todo = OUT_BUFLEN; + + this_urb = portdata->out_urbs[i]; + if (test_and_set_bit(i, &portdata->out_busy)) { + if (time_before(jiffies, + portdata->tx_start_time[i] + 10 * HZ)) + continue; + usb_unlink_urb(this_urb); + continue; + } + dev_dbg(&port->dev, "%s: endpoint %d buf %d\n", __func__, + usb_pipeendpoint(this_urb->pipe), i); + + err = usb_autopm_get_interface_async(port->serial->interface); + if (err < 0) { + clear_bit(i, &portdata->out_busy); + break; + } + + /* send the data */ + memcpy(this_urb->transfer_buffer, buf, todo); + this_urb->transfer_buffer_length = todo; + + spin_lock_irqsave(&intfdata->susp_lock, flags); + if (intfdata->suspended) { + usb_anchor_urb(this_urb, &portdata->delayed); + spin_unlock_irqrestore(&intfdata->susp_lock, flags); + } else { + intfdata->in_flight++; + spin_unlock_irqrestore(&intfdata->susp_lock, flags); + err = usb_submit_urb(this_urb, GFP_ATOMIC); + if (err) { + dev_dbg(&port->dev, + "usb_submit_urb %p (write bulk) failed (%d)\n", + this_urb, err); + clear_bit(i, &portdata->out_busy); + spin_lock_irqsave(&intfdata->susp_lock, flags); + intfdata->in_flight--; + spin_unlock_irqrestore(&intfdata->susp_lock, + flags); + usb_autopm_put_interface_async(port->serial->interface); + break; + } + } + + portdata->tx_start_time[i] = jiffies; + buf += todo; + left -= todo; + } + + count -= left; + dev_dbg(&port->dev, "%s: wrote (did %d)\n", __func__, count); + return count; +} +EXPORT_SYMBOL(usb_wwan_write); + +static void usb_wwan_indat_callback(struct urb *urb) +{ + int err; + int endpoint; + struct usb_serial_port *port; + struct device *dev; + unsigned char *data = urb->transfer_buffer; + int status = urb->status; + + endpoint = usb_pipeendpoint(urb->pipe); + port = urb->context; + dev = &port->dev; + + if (status) { + dev_dbg(dev, "%s: nonzero status: %d on endpoint %02x.\n", + __func__, status, endpoint); + } else { + if (urb->actual_length) { + tty_insert_flip_string(&port->port, data, + urb->actual_length); + tty_flip_buffer_push(&port->port); + } else + dev_dbg(dev, "%s: empty read urb received\n", __func__); + } + /* Resubmit urb so we continue receiving */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + if (err != -EPERM) { + dev_err(dev, "%s: resubmit read urb failed. (%d)\n", + __func__, err); + /* busy also in error unless we are killed */ + usb_mark_last_busy(port->serial->dev); + } + } else { + usb_mark_last_busy(port->serial->dev); + } +} + +static void usb_wwan_outdat_callback(struct urb *urb) +{ + struct usb_serial_port *port; + struct usb_wwan_port_private *portdata; + struct usb_wwan_intf_private *intfdata; + int i; + + port = urb->context; + intfdata = port->serial->private; + + usb_serial_port_softint(port); + usb_autopm_put_interface_async(port->serial->interface); + portdata = usb_get_serial_port_data(port); + spin_lock(&intfdata->susp_lock); + intfdata->in_flight--; + spin_unlock(&intfdata->susp_lock); + + for (i = 0; i < N_OUT_URB; ++i) { + if (portdata->out_urbs[i] == urb) { + smp_mb__before_clear_bit(); + clear_bit(i, &portdata->out_busy); + break; + } + } +} + +int usb_wwan_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_wwan_port_private *portdata; + int i; + int data_len = 0; + struct urb *this_urb; + + portdata = usb_get_serial_port_data(port); + + for (i = 0; i < N_OUT_URB; i++) { + this_urb = portdata->out_urbs[i]; + if (this_urb && !test_bit(i, &portdata->out_busy)) + data_len += OUT_BUFLEN; + } + + dev_dbg(&port->dev, "%s: %d\n", __func__, data_len); + return data_len; +} +EXPORT_SYMBOL(usb_wwan_write_room); + +int usb_wwan_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_wwan_port_private *portdata; + int i; + int data_len = 0; + struct urb *this_urb; + + portdata = usb_get_serial_port_data(port); + + for (i = 0; i < N_OUT_URB; i++) { + this_urb = portdata->out_urbs[i]; + /* FIXME: This locking is insufficient as this_urb may + go unused during the test */ + if (this_urb && test_bit(i, &portdata->out_busy)) + data_len += this_urb->transfer_buffer_length; + } + dev_dbg(&port->dev, "%s: %d\n", __func__, data_len); + return data_len; +} +EXPORT_SYMBOL(usb_wwan_chars_in_buffer); + +int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct usb_wwan_port_private *portdata; + struct usb_wwan_intf_private *intfdata; + struct usb_serial *serial = port->serial; + int i, err; + struct urb *urb; + + portdata = usb_get_serial_port_data(port); + intfdata = serial->private; + + if (port->interrupt_in_urb) { + err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (err) { + dev_dbg(&port->dev, "%s: submit int urb failed: %d\n", + __func__, err); + } + } + + /* Start reading from the IN endpoint */ + for (i = 0; i < N_IN_URB; i++) { + urb = portdata->in_urbs[i]; + if (!urb) + continue; + err = usb_submit_urb(urb, GFP_KERNEL); + if (err) { + dev_dbg(&port->dev, "%s: submit urb %d failed (%d) %d\n", + __func__, i, err, urb->transfer_buffer_length); + } + } + + if (intfdata->send_setup) + intfdata->send_setup(port); + + serial->interface->needs_remote_wakeup = 1; + spin_lock_irq(&intfdata->susp_lock); + portdata->opened = 1; + spin_unlock_irq(&intfdata->susp_lock); + /* this balances a get in the generic USB serial code */ + usb_autopm_put_interface(serial->interface); + + return 0; +} +EXPORT_SYMBOL(usb_wwan_open); + +static void unbusy_queued_urb(struct urb *urb, + struct usb_wwan_port_private *portdata) +{ + int i; + + for (i = 0; i < N_OUT_URB; i++) { + if (urb == portdata->out_urbs[i]) { + clear_bit(i, &portdata->out_busy); + break; + } + } +} + +void usb_wwan_close(struct usb_serial_port *port) +{ + int i; + struct usb_serial *serial = port->serial; + struct usb_wwan_port_private *portdata; + struct usb_wwan_intf_private *intfdata = port->serial->private; + struct urb *urb; + + portdata = usb_get_serial_port_data(port); + + /* Stop reading/writing urbs */ + spin_lock_irq(&intfdata->susp_lock); + portdata->opened = 0; + spin_unlock_irq(&intfdata->susp_lock); + + for (;;) { + urb = usb_get_from_anchor(&portdata->delayed); + if (!urb) + break; + unbusy_queued_urb(urb, portdata); + usb_autopm_put_interface_async(serial->interface); + } + + for (i = 0; i < N_IN_URB; i++) + usb_kill_urb(portdata->in_urbs[i]); + for (i = 0; i < N_OUT_URB; i++) + usb_kill_urb(portdata->out_urbs[i]); + usb_kill_urb(port->interrupt_in_urb); + + /* balancing - important as an error cannot be handled*/ + usb_autopm_get_interface_no_resume(serial->interface); + serial->interface->needs_remote_wakeup = 0; +} +EXPORT_SYMBOL(usb_wwan_close); + +/* Helper functions used by usb_wwan_setup_urbs */ +static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port, + int endpoint, + int dir, void *ctx, char *buf, int len, + void (*callback) (struct urb *)) +{ + struct usb_serial *serial = port->serial; + struct urb *urb; + + urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */ + if (urb == NULL) { + dev_dbg(&serial->interface->dev, + "%s: alloc for endpoint %d failed.\n", __func__, + endpoint); + return NULL; + } + + /* Fill URB using supplied data. */ + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, endpoint) | dir, + buf, len, callback, ctx); + + return urb; +} + +int usb_wwan_port_probe(struct usb_serial_port *port) +{ + struct usb_wwan_port_private *portdata; + struct urb *urb; + u8 *buffer; + int i; + + if (!port->bulk_in_size || !port->bulk_out_size) + return -ENODEV; + + portdata = kzalloc(sizeof(*portdata), GFP_KERNEL); + if (!portdata) + return -ENOMEM; + + init_usb_anchor(&portdata->delayed); + + for (i = 0; i < N_IN_URB; i++) { + buffer = (u8 *)__get_free_page(GFP_KERNEL); + if (!buffer) + goto bail_out_error; + portdata->in_buffer[i] = buffer; + + urb = usb_wwan_setup_urb(port, port->bulk_in_endpointAddress, + USB_DIR_IN, port, + buffer, IN_BUFLEN, + usb_wwan_indat_callback); + portdata->in_urbs[i] = urb; + } + + for (i = 0; i < N_OUT_URB; i++) { + buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL); + if (!buffer) + goto bail_out_error2; + portdata->out_buffer[i] = buffer; + + urb = usb_wwan_setup_urb(port, port->bulk_out_endpointAddress, + USB_DIR_OUT, port, + buffer, OUT_BUFLEN, + usb_wwan_outdat_callback); + portdata->out_urbs[i] = urb; + } + + usb_set_serial_port_data(port, portdata); + + return 0; + +bail_out_error2: + for (i = 0; i < N_OUT_URB; i++) { + usb_free_urb(portdata->out_urbs[i]); + kfree(portdata->out_buffer[i]); + } +bail_out_error: + for (i = 0; i < N_IN_URB; i++) { + usb_free_urb(portdata->in_urbs[i]); + free_page((unsigned long)portdata->in_buffer[i]); + } + kfree(portdata); + + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(usb_wwan_port_probe); + +int usb_wwan_port_remove(struct usb_serial_port *port) +{ + int i; + struct usb_wwan_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + usb_set_serial_port_data(port, NULL); + + /* Stop reading/writing urbs and free them */ + for (i = 0; i < N_IN_URB; i++) { + usb_kill_urb(portdata->in_urbs[i]); + usb_free_urb(portdata->in_urbs[i]); + free_page((unsigned long)portdata->in_buffer[i]); + } + for (i = 0; i < N_OUT_URB; i++) { + usb_kill_urb(portdata->out_urbs[i]); + usb_free_urb(portdata->out_urbs[i]); + kfree(portdata->out_buffer[i]); + } + + /* Now free port private data */ + kfree(portdata); + return 0; +} +EXPORT_SYMBOL(usb_wwan_port_remove); + +#ifdef CONFIG_PM +static void stop_read_write_urbs(struct usb_serial *serial) +{ + int i, j; + struct usb_serial_port *port; + struct usb_wwan_port_private *portdata; + + /* Stop reading/writing urbs */ + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + portdata = usb_get_serial_port_data(port); + if (!portdata) + continue; + for (j = 0; j < N_IN_URB; j++) + usb_kill_urb(portdata->in_urbs[j]); + for (j = 0; j < N_OUT_URB; j++) + usb_kill_urb(portdata->out_urbs[j]); + } +} + +int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message) +{ + struct usb_wwan_intf_private *intfdata = serial->private; + + spin_lock_irq(&intfdata->susp_lock); + if (PMSG_IS_AUTO(message)) { + if (intfdata->in_flight) { + spin_unlock_irq(&intfdata->susp_lock); + return -EBUSY; + } + } + intfdata->suspended = 1; + spin_unlock_irq(&intfdata->susp_lock); + + stop_read_write_urbs(serial); + + return 0; +} +EXPORT_SYMBOL(usb_wwan_suspend); + +static int play_delayed(struct usb_serial_port *port) +{ + struct usb_wwan_intf_private *data; + struct usb_wwan_port_private *portdata; + struct urb *urb; + int err = 0; + + portdata = usb_get_serial_port_data(port); + data = port->serial->private; + while ((urb = usb_get_from_anchor(&portdata->delayed))) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (!err) { + data->in_flight++; + } else { + /* we have to throw away the rest */ + do { + unbusy_queued_urb(urb, portdata); + usb_autopm_put_interface_no_suspend(port->serial->interface); + } while ((urb = usb_get_from_anchor(&portdata->delayed))); + break; + } + } + + return err; +} + +int usb_wwan_resume(struct usb_serial *serial) +{ + int i, j; + struct usb_serial_port *port; + struct usb_wwan_intf_private *intfdata = serial->private; + struct usb_wwan_port_private *portdata; + struct urb *urb; + int err; + int err_count = 0; + + spin_lock_irq(&intfdata->susp_lock); + for (i = 0; i < serial->num_ports; i++) { + /* walk all ports */ + port = serial->port[i]; + portdata = usb_get_serial_port_data(port); + + /* skip closed ports */ + if (!portdata || !portdata->opened) + continue; + + if (port->interrupt_in_urb) { + err = usb_submit_urb(port->interrupt_in_urb, + GFP_ATOMIC); + if (err) { + dev_err(&port->dev, + "%s: submit int urb failed: %d\n", + __func__, err); + err_count++; + } + } + + err = play_delayed(port); + if (err) + err_count++; + + for (j = 0; j < N_IN_URB; j++) { + urb = portdata->in_urbs[j]; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + dev_err(&port->dev, "%s: Error %d for bulk URB %d\n", + __func__, err, i); + err_count++; + } + } + } + intfdata->suspended = 0; + spin_unlock_irq(&intfdata->susp_lock); + + if (err_count) + return -EIO; + + return 0; +} +EXPORT_SYMBOL(usb_wwan_resume); +#endif + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c new file mode 100644 index 00000000..9910aa2e --- /dev/null +++ b/drivers/usb/serial/visor.c @@ -0,0 +1,626 @@ +/* + * USB HandSpring Visor, Palm m50x, and Sony Clie driver + * (supports all of the Palm OS USB devices) + * + * Copyright (C) 1999 - 2004 + * Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "visor.h" + +/* + * Version Information + */ +#define DRIVER_AUTHOR "Greg Kroah-Hartman " +#define DRIVER_DESC "USB HandSpring Visor / Palm OS driver" + +/* function prototypes for a handspring visor */ +static int visor_open(struct tty_struct *tty, struct usb_serial_port *port); +static void visor_close(struct usb_serial_port *port); +static int visor_probe(struct usb_serial *serial, + const struct usb_device_id *id); +static int visor_calc_num_ports(struct usb_serial *serial); +static void visor_read_int_callback(struct urb *urb); +static int clie_3_5_startup(struct usb_serial *serial); +static int treo_attach(struct usb_serial *serial); +static int clie_5_attach(struct usb_serial *serial); +static int palm_os_3_probe(struct usb_serial *serial, + const struct usb_device_id *id); +static int palm_os_4_probe(struct usb_serial *serial, + const struct usb_device_id *id); + +static struct usb_device_id id_table [] = { + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID), + .driver_info = (kernel_ulong_t)&palm_os_3_probe }, + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO600_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(GSPDA_VENDOR_ID, GSPDA_XPLORE_M68_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M500_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M505_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M515_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_I705_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M100_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M125_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M130_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_TREO_650), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_Z_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_S360_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_1_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NX60_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NZ90V_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_TJ25_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(ACER_VENDOR_ID, ACER_S10_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SPH_I500_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(TAPWAVE_VENDOR_ID, TAPWAVE_ZODIAC_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(GARMIN_VENDOR_ID, GARMIN_IQUE_3600_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(ACEECA_VENDOR_ID, ACEECA_MEZ1000_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_7135_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { USB_DEVICE(FOSSIL_VENDOR_ID, FOSSIL_ABACUS_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { } /* Terminating entry */ +}; + +static struct usb_device_id clie_id_5_table [] = { + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_UX50_ID), + .driver_info = (kernel_ulong_t)&palm_os_4_probe }, + { } /* Terminating entry */ +}; + +static struct usb_device_id clie_id_3_5_table [] = { + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_3_5_ID) }, + { } /* Terminating entry */ +}; + +static struct usb_device_id id_table_combined [] = { + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_VISOR_ID) }, + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO_ID) }, + { USB_DEVICE(HANDSPRING_VENDOR_ID, HANDSPRING_TREO600_ID) }, + { USB_DEVICE(GSPDA_VENDOR_ID, GSPDA_XPLORE_M68_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M500_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M505_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M515_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_I705_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M100_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M125_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_M130_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_T_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_TREO_650) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_TUNGSTEN_Z_ID) }, + { USB_DEVICE(PALM_VENDOR_ID, PALM_ZIRE_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_3_5_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_0_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_S360_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_1_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NX60_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NZ90V_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_UX50_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_TJ25_ID) }, + { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID) }, + { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SPH_I500_ID) }, + { USB_DEVICE(TAPWAVE_VENDOR_ID, TAPWAVE_ZODIAC_ID) }, + { USB_DEVICE(GARMIN_VENDOR_ID, GARMIN_IQUE_3600_ID) }, + { USB_DEVICE(ACEECA_VENDOR_ID, ACEECA_MEZ1000_ID) }, + { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_7135_ID) }, + { USB_DEVICE(FOSSIL_VENDOR_ID, FOSSIL_ABACUS_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +/* All of the device info needed for the Handspring Visor, + and Palm 4.0 devices */ +static struct usb_serial_driver handspring_device = { + .driver = { + .owner = THIS_MODULE, + .name = "visor", + }, + .description = "Handspring Visor / Palm OS", + .id_table = id_table, + .num_ports = 2, + .bulk_out_size = 256, + .open = visor_open, + .close = visor_close, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .attach = treo_attach, + .probe = visor_probe, + .calc_num_ports = visor_calc_num_ports, + .read_int_callback = visor_read_int_callback, +}; + +/* All of the device info needed for the Clie UX50, TH55 Palm 5.0 devices */ +static struct usb_serial_driver clie_5_device = { + .driver = { + .owner = THIS_MODULE, + .name = "clie_5", + }, + .description = "Sony Clie 5.0", + .id_table = clie_id_5_table, + .num_ports = 2, + .bulk_out_size = 256, + .open = visor_open, + .close = visor_close, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .attach = clie_5_attach, + .probe = visor_probe, + .calc_num_ports = visor_calc_num_ports, + .read_int_callback = visor_read_int_callback, +}; + +/* device info for the Sony Clie OS version 3.5 */ +static struct usb_serial_driver clie_3_5_device = { + .driver = { + .owner = THIS_MODULE, + .name = "clie_3.5", + }, + .description = "Sony Clie 3.5", + .id_table = clie_id_3_5_table, + .num_ports = 1, + .bulk_out_size = 256, + .open = visor_open, + .close = visor_close, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .attach = clie_3_5_startup, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &handspring_device, &clie_5_device, &clie_3_5_device, NULL +}; + +/****************************************************************************** + * Handspring Visor specific driver functions + ******************************************************************************/ +static int visor_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int result = 0; + + if (!port->read_urb) { + /* this is needed for some brain dead Sony devices */ + dev_err(&port->dev, "Device lied about number of ports, please use a lower one.\n"); + return -ENODEV; + } + + /* Start reading from the device */ + result = usb_serial_generic_open(tty, port); + if (result) + goto exit; + + if (port->interrupt_in_urb) { + dev_dbg(&port->dev, "adding interrupt input for treo\n"); + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) + dev_err(&port->dev, + "%s - failed submitting interrupt urb, error %d\n", + __func__, result); + } +exit: + return result; +} + + +static void visor_close(struct usb_serial_port *port) +{ + unsigned char *transfer_buffer; + + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); + + transfer_buffer = kmalloc(0x12, GFP_KERNEL); + if (!transfer_buffer) + return; + usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + VISOR_CLOSE_NOTIFICATION, 0xc2, + 0x0000, 0x0000, + transfer_buffer, 0x12, 300); + kfree(transfer_buffer); +} + +static void visor_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + int status = urb->status; + int result; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + /* + * This information is still unknown what it can be used for. + * If anyone has an idea, please let the author know... + * + * Rumor has it this endpoint is used to notify when data + * is ready to be read from the bulk ones. + */ + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, + urb->transfer_buffer); + +exit: + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) + dev_err(&urb->dev->dev, + "%s - Error %d submitting interrupt urb\n", + __func__, result); +} + +static int palm_os_3_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct device *dev = &serial->dev->dev; + struct visor_connection_info *connection_info; + unsigned char *transfer_buffer; + char *string; + int retval = 0; + int i; + int num_ports = 0; + + transfer_buffer = kmalloc(sizeof(*connection_info), GFP_KERNEL); + if (!transfer_buffer) { + dev_err(dev, "%s - kmalloc(%Zd) failed.\n", __func__, + sizeof(*connection_info)); + return -ENOMEM; + } + + /* send a get connection info request */ + retval = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + VISOR_GET_CONNECTION_INFORMATION, + 0xc2, 0x0000, 0x0000, transfer_buffer, + sizeof(*connection_info), 300); + if (retval < 0) { + dev_err(dev, "%s - error %d getting connection information\n", + __func__, retval); + goto exit; + } + + if (retval == sizeof(*connection_info)) { + connection_info = (struct visor_connection_info *) + transfer_buffer; + + num_ports = le16_to_cpu(connection_info->num_ports); + for (i = 0; i < num_ports; ++i) { + switch ( + connection_info->connections[i].port_function_id) { + case VISOR_FUNCTION_GENERIC: + string = "Generic"; + break; + case VISOR_FUNCTION_DEBUGGER: + string = "Debugger"; + break; + case VISOR_FUNCTION_HOTSYNC: + string = "HotSync"; + break; + case VISOR_FUNCTION_CONSOLE: + string = "Console"; + break; + case VISOR_FUNCTION_REMOTE_FILE_SYS: + string = "Remote File System"; + break; + default: + string = "unknown"; + break; + } + dev_info(dev, "%s: port %d, is for %s use\n", + serial->type->description, + connection_info->connections[i].port, string); + } + } + /* + * Handle devices that report invalid stuff here. + */ + if (num_ports == 0 || num_ports > 2) { + dev_warn(dev, "%s: No valid connect info available\n", + serial->type->description); + num_ports = 2; + } + + dev_info(dev, "%s: Number of ports: %d\n", serial->type->description, + num_ports); + + /* + * save off our num_ports info so that we can use it in the + * calc_num_ports callback + */ + usb_set_serial_data(serial, (void *)(long)num_ports); + + /* ask for the number of bytes available, but ignore the + response as it is broken */ + retval = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + VISOR_REQUEST_BYTES_AVAILABLE, + 0xc2, 0x0000, 0x0005, transfer_buffer, + 0x02, 300); + if (retval < 0) + dev_err(dev, "%s - error %d getting bytes available request\n", + __func__, retval); + retval = 0; + +exit: + kfree(transfer_buffer); + + return retval; +} + +static int palm_os_4_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct device *dev = &serial->dev->dev; + struct palm_ext_connection_info *connection_info; + unsigned char *transfer_buffer; + int retval; + + transfer_buffer = kmalloc(sizeof(*connection_info), GFP_KERNEL); + if (!transfer_buffer) { + dev_err(dev, "%s - kmalloc(%Zd) failed.\n", __func__, + sizeof(*connection_info)); + return -ENOMEM; + } + + retval = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + PALM_GET_EXT_CONNECTION_INFORMATION, + 0xc2, 0x0000, 0x0000, transfer_buffer, + sizeof(*connection_info), 300); + if (retval < 0) + dev_err(dev, "%s - error %d getting connection info\n", + __func__, retval); + else + usb_serial_debug_data(dev, __func__, retval, transfer_buffer); + + kfree(transfer_buffer); + return 0; +} + + +static int visor_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + int retval = 0; + int (*startup)(struct usb_serial *serial, + const struct usb_device_id *id); + + /* + * some Samsung Android phones in modem mode have the same ID + * as SPH-I500, but they are ACM devices, so dont bind to them + */ + if (id->idVendor == SAMSUNG_VENDOR_ID && + id->idProduct == SAMSUNG_SPH_I500_ID && + serial->dev->descriptor.bDeviceClass == USB_CLASS_COMM && + serial->dev->descriptor.bDeviceSubClass == + USB_CDC_SUBCLASS_ACM) + return -ENODEV; + + if (serial->dev->actconfig->desc.bConfigurationValue != 1) { + dev_err(&serial->dev->dev, "active config #%d != 1 ??\n", + serial->dev->actconfig->desc.bConfigurationValue); + return -ENODEV; + } + + if (id->driver_info) { + startup = (void *)id->driver_info; + retval = startup(serial, id); + } + + return retval; +} + +static int visor_calc_num_ports(struct usb_serial *serial) +{ + int num_ports = (int)(long)(usb_get_serial_data(serial)); + + if (num_ports) + usb_set_serial_data(serial, NULL); + + return num_ports; +} + +static int clie_3_5_startup(struct usb_serial *serial) +{ + struct device *dev = &serial->dev->dev; + int result; + u8 *data; + + data = kmalloc(1, GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* + * Note that PEG-300 series devices expect the following two calls. + */ + + /* get the config number */ + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + USB_REQ_GET_CONFIGURATION, USB_DIR_IN, + 0, 0, data, 1, 3000); + if (result < 0) { + dev_err(dev, "%s: get config number failed: %d\n", + __func__, result); + goto out; + } + if (result != 1) { + dev_err(dev, "%s: get config number bad return length: %d\n", + __func__, result); + result = -EIO; + goto out; + } + + /* get the interface number */ + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + USB_REQ_GET_INTERFACE, + USB_DIR_IN | USB_RECIP_INTERFACE, + 0, 0, data, 1, 3000); + if (result < 0) { + dev_err(dev, "%s: get interface number failed: %d\n", + __func__, result); + goto out; + } + if (result != 1) { + dev_err(dev, + "%s: get interface number bad return length: %d\n", + __func__, result); + result = -EIO; + goto out; + } + + result = 0; +out: + kfree(data); + + return result; +} + +static int treo_attach(struct usb_serial *serial) +{ + struct usb_serial_port *swap_port; + + /* Only do this endpoint hack for the Handspring devices with + * interrupt in endpoints, which for now are the Treo devices. */ + if (!((le16_to_cpu(serial->dev->descriptor.idVendor) + == HANDSPRING_VENDOR_ID) || + (le16_to_cpu(serial->dev->descriptor.idVendor) + == KYOCERA_VENDOR_ID)) || + (serial->num_interrupt_in == 0)) + return 0; + + /* + * It appears that Treos and Kyoceras want to use the + * 1st bulk in endpoint to communicate with the 2nd bulk out endpoint, + * so let's swap the 1st and 2nd bulk in and interrupt endpoints. + * Note that swapping the bulk out endpoints would break lots of + * apps that want to communicate on the second port. + */ +#define COPY_PORT(dest, src) \ + do { \ + int i; \ + \ + for (i = 0; i < ARRAY_SIZE(src->read_urbs); ++i) { \ + dest->read_urbs[i] = src->read_urbs[i]; \ + dest->read_urbs[i]->context = dest; \ + dest->bulk_in_buffers[i] = src->bulk_in_buffers[i]; \ + } \ + dest->read_urb = src->read_urb; \ + dest->bulk_in_endpointAddress = src->bulk_in_endpointAddress;\ + dest->bulk_in_buffer = src->bulk_in_buffer; \ + dest->bulk_in_size = src->bulk_in_size; \ + dest->interrupt_in_urb = src->interrupt_in_urb; \ + dest->interrupt_in_urb->context = dest; \ + dest->interrupt_in_endpointAddress = \ + src->interrupt_in_endpointAddress;\ + dest->interrupt_in_buffer = src->interrupt_in_buffer; \ + } while (0); + + swap_port = kmalloc(sizeof(*swap_port), GFP_KERNEL); + if (!swap_port) + return -ENOMEM; + COPY_PORT(swap_port, serial->port[0]); + COPY_PORT(serial->port[0], serial->port[1]); + COPY_PORT(serial->port[1], swap_port); + kfree(swap_port); + + return 0; +} + +static int clie_5_attach(struct usb_serial *serial) +{ + struct usb_serial_port *port; + unsigned int pipe; + int j; + + /* TH55 registers 2 ports. + Communication in from the UX50/TH55 uses bulk_in_endpointAddress + from port 0. Communication out to the UX50/TH55 uses + bulk_out_endpointAddress from port 1 + + Lets do a quick and dirty mapping + */ + + /* some sanity check */ + if (serial->num_ports < 2) + return -1; + + /* port 0 now uses the modified endpoint Address */ + port = serial->port[0]; + port->bulk_out_endpointAddress = + serial->port[1]->bulk_out_endpointAddress; + + pipe = usb_sndbulkpipe(serial->dev, port->bulk_out_endpointAddress); + for (j = 0; j < ARRAY_SIZE(port->write_urbs); ++j) + port->write_urbs[j]->pipe = pipe; + + return 0; +} + +module_usb_serial_driver(serial_drivers, id_table_combined); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/visor.h b/drivers/usb/serial/visor.h new file mode 100644 index 00000000..88db4d06 --- /dev/null +++ b/drivers/usb/serial/visor.h @@ -0,0 +1,161 @@ +/* + * USB HandSpring Visor driver + * + * Copyright (C) 1999 - 2003 + * Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver. + * + */ + +#ifndef __LINUX_USB_SERIAL_VISOR_H +#define __LINUX_USB_SERIAL_VISOR_H + + +#define HANDSPRING_VENDOR_ID 0x082d +#define HANDSPRING_VISOR_ID 0x0100 +#define HANDSPRING_TREO_ID 0x0200 +#define HANDSPRING_TREO600_ID 0x0300 + +#define PALM_VENDOR_ID 0x0830 +#define PALM_M500_ID 0x0001 +#define PALM_M505_ID 0x0002 +#define PALM_M515_ID 0x0003 +#define PALM_I705_ID 0x0020 +#define PALM_M125_ID 0x0040 +#define PALM_M130_ID 0x0050 +#define PALM_TUNGSTEN_T_ID 0x0060 +#define PALM_TREO_650 0x0061 +#define PALM_TUNGSTEN_Z_ID 0x0031 +#define PALM_ZIRE_ID 0x0070 +#define PALM_M100_ID 0x0080 + +#define GSPDA_VENDOR_ID 0x115e +#define GSPDA_XPLORE_M68_ID 0xf100 + +#define SONY_VENDOR_ID 0x054C +#define SONY_CLIE_3_5_ID 0x0038 +#define SONY_CLIE_4_0_ID 0x0066 +#define SONY_CLIE_S360_ID 0x0095 +#define SONY_CLIE_4_1_ID 0x009A +#define SONY_CLIE_NX60_ID 0x00DA +#define SONY_CLIE_NZ90V_ID 0x00E9 +#define SONY_CLIE_UX50_ID 0x0144 +#define SONY_CLIE_TJ25_ID 0x0169 + +#define ACER_VENDOR_ID 0x0502 +#define ACER_S10_ID 0x0001 + +#define SAMSUNG_VENDOR_ID 0x04E8 +#define SAMSUNG_SCH_I330_ID 0x8001 +#define SAMSUNG_SPH_I500_ID 0x6601 + +#define TAPWAVE_VENDOR_ID 0x12EF +#define TAPWAVE_ZODIAC_ID 0x0100 + +#define GARMIN_VENDOR_ID 0x091E +#define GARMIN_IQUE_3600_ID 0x0004 + +#define ACEECA_VENDOR_ID 0x4766 +#define ACEECA_MEZ1000_ID 0x0001 + +#define KYOCERA_VENDOR_ID 0x0C88 +#define KYOCERA_7135_ID 0x0021 + +#define FOSSIL_VENDOR_ID 0x0E67 +#define FOSSIL_ABACUS_ID 0x0002 + +/**************************************************************************** + * Handspring Visor Vendor specific request codes (bRequest values) + * A big thank you to Handspring for providing the following information. + * If anyone wants the original file where these values and structures came + * from, send email to . + ****************************************************************************/ + +/**************************************************************************** + * VISOR_REQUEST_BYTES_AVAILABLE asks the visor for the number of bytes that + * are available to be transferred to the host for the specified endpoint. + * Currently this is not used, and always returns 0x0001 + ****************************************************************************/ +#define VISOR_REQUEST_BYTES_AVAILABLE 0x01 + +/**************************************************************************** + * VISOR_CLOSE_NOTIFICATION is set to the device to notify it that the host + * is now closing the pipe. An empty packet is sent in response. + ****************************************************************************/ +#define VISOR_CLOSE_NOTIFICATION 0x02 + +/**************************************************************************** + * VISOR_GET_CONNECTION_INFORMATION is sent by the host during enumeration to + * get the endpoints used by the connection. + ****************************************************************************/ +#define VISOR_GET_CONNECTION_INFORMATION 0x03 + + +/**************************************************************************** + * VISOR_GET_CONNECTION_INFORMATION returns data in the following format + ****************************************************************************/ +struct visor_connection_info { + __le16 num_ports; + struct { + __u8 port_function_id; + __u8 port; + } connections[2]; +}; + + +/* struct visor_connection_info.connection[x].port defines: */ +#define VISOR_ENDPOINT_1 0x01 +#define VISOR_ENDPOINT_2 0x02 + +/* struct visor_connection_info.connection[x].port_function_id defines: */ +#define VISOR_FUNCTION_GENERIC 0x00 +#define VISOR_FUNCTION_DEBUGGER 0x01 +#define VISOR_FUNCTION_HOTSYNC 0x02 +#define VISOR_FUNCTION_CONSOLE 0x03 +#define VISOR_FUNCTION_REMOTE_FILE_SYS 0x04 + + +/**************************************************************************** + * PALM_GET_SOME_UNKNOWN_INFORMATION is sent by the host during enumeration to + * get some information from the M series devices, that is currently unknown. + ****************************************************************************/ +#define PALM_GET_EXT_CONNECTION_INFORMATION 0x04 + +/** + * struct palm_ext_connection_info - return data from a PALM_GET_EXT_CONNECTION_INFORMATION request + * @num_ports: maximum number of functions/connections in use + * @endpoint_numbers_different: will be 1 if in and out endpoints numbers are + * different, otherwise it is 0. If value is 1, then + * connections.end_point_info is non-zero. If value is 0, then + * connections.port contains the endpoint number, which is the same for in + * and out. + * @port_function_id: contains the creator id of the applicaton that opened + * this connection. + * @port: contains the in/out endpoint number. Is 0 if in and out endpoint + * numbers are different. + * @end_point_info: high nubbe is in endpoint and low nibble will indicate out + * endpoint. Is 0 if in and out endpoints are the same. + * + * The maximum number of connections currently supported is 2 + */ +struct palm_ext_connection_info { + __u8 num_ports; + __u8 endpoint_numbers_different; + __le16 reserved1; + struct { + __u32 port_function_id; + __u8 port; + __u8 end_point_info; + __le16 reserved; + } connections[2]; +}; + +#endif + diff --git a/drivers/usb/serial/vivopay-serial.c b/drivers/usb/serial/vivopay-serial.c new file mode 100644 index 00000000..6299526f --- /dev/null +++ b/drivers/usb/serial/vivopay-serial.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2001-2005 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2009 Outpost Embedded, LLC + */ + +#include +#include +#include +#include +#include +#include + +#define DRIVER_DESC "ViVOpay USB Serial Driver" + +#define VIVOPAY_VENDOR_ID 0x1d5f + + +static struct usb_device_id id_table [] = { + /* ViVOpay 8800 */ + { USB_DEVICE(VIVOPAY_VENDOR_ID, 0x1004) }, + { }, +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver vivopay_serial_device = { + .driver = { + .owner = THIS_MODULE, + .name = "vivopay-serial", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &vivopay_serial_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR("Forest Bond "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c new file mode 100644 index 00000000..5e3dd9f8 --- /dev/null +++ b/drivers/usb/serial/whiteheat.c @@ -0,0 +1,847 @@ +/* + * USB ConnectTech WhiteHEAT driver + * + * Copyright (C) 2002 + * Connect Tech Inc. + * + * Copyright (C) 1999 - 2001 + * Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "whiteheat.h" /* WhiteHEAT specific commands */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Version Information + */ +#define DRIVER_AUTHOR "Greg Kroah-Hartman , Stuart MacDonald " +#define DRIVER_DESC "USB ConnectTech WhiteHEAT driver" + +#define CONNECT_TECH_VENDOR_ID 0x0710 +#define CONNECT_TECH_FAKE_WHITE_HEAT_ID 0x0001 +#define CONNECT_TECH_WHITE_HEAT_ID 0x8001 + +/* + ID tables for whiteheat are unusual, because we want to different + things for different versions of the device. Eventually, this + will be doable from a single table. But, for now, we define two + separate ID tables, and then a third table that combines them + just for the purpose of exporting the autoloading information. +*/ +static const struct usb_device_id id_table_std[] = { + { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id id_table_prerenumeration[] = { + { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) }, + { } /* Terminating entry */ +}; + +static const struct usb_device_id id_table_combined[] = { + { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_WHITE_HEAT_ID) }, + { USB_DEVICE(CONNECT_TECH_VENDOR_ID, CONNECT_TECH_FAKE_WHITE_HEAT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + + +/* function prototypes for the Connect Tech WhiteHEAT prerenumeration device */ +static int whiteheat_firmware_download(struct usb_serial *serial, + const struct usb_device_id *id); +static int whiteheat_firmware_attach(struct usb_serial *serial); + +/* function prototypes for the Connect Tech WhiteHEAT serial converter */ +static int whiteheat_attach(struct usb_serial *serial); +static void whiteheat_release(struct usb_serial *serial); +static int whiteheat_port_probe(struct usb_serial_port *port); +static int whiteheat_port_remove(struct usb_serial_port *port); +static int whiteheat_open(struct tty_struct *tty, + struct usb_serial_port *port); +static void whiteheat_close(struct usb_serial_port *port); +static int whiteheat_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg); +static void whiteheat_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old); +static int whiteheat_tiocmget(struct tty_struct *tty); +static int whiteheat_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static void whiteheat_break_ctl(struct tty_struct *tty, int break_state); + +static struct usb_serial_driver whiteheat_fake_device = { + .driver = { + .owner = THIS_MODULE, + .name = "whiteheatnofirm", + }, + .description = "Connect Tech - WhiteHEAT - (prerenumeration)", + .id_table = id_table_prerenumeration, + .num_ports = 1, + .probe = whiteheat_firmware_download, + .attach = whiteheat_firmware_attach, +}; + +static struct usb_serial_driver whiteheat_device = { + .driver = { + .owner = THIS_MODULE, + .name = "whiteheat", + }, + .description = "Connect Tech - WhiteHEAT", + .id_table = id_table_std, + .num_ports = 4, + .attach = whiteheat_attach, + .release = whiteheat_release, + .port_probe = whiteheat_port_probe, + .port_remove = whiteheat_port_remove, + .open = whiteheat_open, + .close = whiteheat_close, + .ioctl = whiteheat_ioctl, + .set_termios = whiteheat_set_termios, + .break_ctl = whiteheat_break_ctl, + .tiocmget = whiteheat_tiocmget, + .tiocmset = whiteheat_tiocmset, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &whiteheat_fake_device, &whiteheat_device, NULL +}; + +struct whiteheat_command_private { + struct mutex mutex; + __u8 port_running; + __u8 command_finished; + wait_queue_head_t wait_command; /* for handling sleeping whilst + waiting for a command to + finish */ + __u8 result_buffer[64]; +}; + +struct whiteheat_private { + __u8 mcr; /* FIXME: no locking on mcr */ +}; + + +/* local function prototypes */ +static int start_command_port(struct usb_serial *serial); +static void stop_command_port(struct usb_serial *serial); +static void command_port_write_callback(struct urb *urb); +static void command_port_read_callback(struct urb *urb); + +static int firm_send_command(struct usb_serial_port *port, __u8 command, + __u8 *data, __u8 datasize); +static int firm_open(struct usb_serial_port *port); +static int firm_close(struct usb_serial_port *port); +static void firm_setup_port(struct tty_struct *tty); +static int firm_set_rts(struct usb_serial_port *port, __u8 onoff); +static int firm_set_dtr(struct usb_serial_port *port, __u8 onoff); +static int firm_set_break(struct usb_serial_port *port, __u8 onoff); +static int firm_purge(struct usb_serial_port *port, __u8 rxtx); +static int firm_get_dtr_rts(struct usb_serial_port *port); +static int firm_report_tx_done(struct usb_serial_port *port); + + +#define COMMAND_PORT 4 +#define COMMAND_TIMEOUT (2*HZ) /* 2 second timeout for a command */ +#define COMMAND_TIMEOUT_MS 2000 +#define CLOSING_DELAY (30 * HZ) + + +/***************************************************************************** + * Connect Tech's White Heat prerenumeration driver functions + *****************************************************************************/ + +/* steps to download the firmware to the WhiteHEAT device: + - hold the reset (by writing to the reset bit of the CPUCS register) + - download the VEND_AX.HEX file to the chip using VENDOR_REQUEST-ANCHOR_LOAD + - release the reset (by writing to the CPUCS register) + - download the WH.HEX file for all addresses greater than 0x1b3f using + VENDOR_REQUEST-ANCHOR_EXTERNAL_RAM_LOAD + - hold the reset + - download the WH.HEX file for all addresses less than 0x1b40 using + VENDOR_REQUEST_ANCHOR_LOAD + - release the reset + - device renumerated itself and comes up as new device id with all + firmware download completed. +*/ +static int whiteheat_firmware_download(struct usb_serial *serial, + const struct usb_device_id *id) +{ + int response; + + response = ezusb_fx1_ihex_firmware_download(serial->dev, "whiteheat_loader.fw"); + if (response >= 0) { + response = ezusb_fx1_ihex_firmware_download(serial->dev, "whiteheat.fw"); + if (response >= 0) + return 0; + } + return -ENOENT; +} + + +static int whiteheat_firmware_attach(struct usb_serial *serial) +{ + /* We want this device to fail to have a driver assigned to it */ + return 1; +} + + +/***************************************************************************** + * Connect Tech's White Heat serial driver functions + *****************************************************************************/ +static int whiteheat_attach(struct usb_serial *serial) +{ + struct usb_serial_port *command_port; + struct whiteheat_command_private *command_info; + struct whiteheat_hw_info *hw_info; + int pipe; + int ret; + int alen; + __u8 *command; + __u8 *result; + + command_port = serial->port[COMMAND_PORT]; + + pipe = usb_sndbulkpipe(serial->dev, + command_port->bulk_out_endpointAddress); + command = kmalloc(2, GFP_KERNEL); + if (!command) + goto no_command_buffer; + command[0] = WHITEHEAT_GET_HW_INFO; + command[1] = 0; + + result = kmalloc(sizeof(*hw_info) + 1, GFP_KERNEL); + if (!result) + goto no_result_buffer; + /* + * When the module is reloaded the firmware is still there and + * the endpoints are still in the usb core unchanged. This is the + * unlinking bug in disguise. Same for the call below. + */ + usb_clear_halt(serial->dev, pipe); + ret = usb_bulk_msg(serial->dev, pipe, command, 2, + &alen, COMMAND_TIMEOUT_MS); + if (ret) { + dev_err(&serial->dev->dev, "%s: Couldn't send command [%d]\n", + serial->type->description, ret); + goto no_firmware; + } else if (alen != 2) { + dev_err(&serial->dev->dev, "%s: Send command incomplete [%d]\n", + serial->type->description, alen); + goto no_firmware; + } + + pipe = usb_rcvbulkpipe(serial->dev, + command_port->bulk_in_endpointAddress); + /* See the comment on the usb_clear_halt() above */ + usb_clear_halt(serial->dev, pipe); + ret = usb_bulk_msg(serial->dev, pipe, result, + sizeof(*hw_info) + 1, &alen, COMMAND_TIMEOUT_MS); + if (ret) { + dev_err(&serial->dev->dev, "%s: Couldn't get results [%d]\n", + serial->type->description, ret); + goto no_firmware; + } else if (alen != sizeof(*hw_info) + 1) { + dev_err(&serial->dev->dev, "%s: Get results incomplete [%d]\n", + serial->type->description, alen); + goto no_firmware; + } else if (result[0] != command[0]) { + dev_err(&serial->dev->dev, "%s: Command failed [%d]\n", + serial->type->description, result[0]); + goto no_firmware; + } + + hw_info = (struct whiteheat_hw_info *)&result[1]; + + dev_info(&serial->dev->dev, "%s: Firmware v%d.%02d\n", + serial->type->description, + hw_info->sw_major_rev, hw_info->sw_minor_rev); + + command_info = kmalloc(sizeof(struct whiteheat_command_private), + GFP_KERNEL); + if (command_info == NULL) { + dev_err(&serial->dev->dev, + "%s: Out of memory for port structures\n", + serial->type->description); + goto no_command_private; + } + + mutex_init(&command_info->mutex); + command_info->port_running = 0; + init_waitqueue_head(&command_info->wait_command); + usb_set_serial_port_data(command_port, command_info); + command_port->write_urb->complete = command_port_write_callback; + command_port->read_urb->complete = command_port_read_callback; + kfree(result); + kfree(command); + + return 0; + +no_firmware: + /* Firmware likely not running */ + dev_err(&serial->dev->dev, + "%s: Unable to retrieve firmware version, try replugging\n", + serial->type->description); + dev_err(&serial->dev->dev, + "%s: If the firmware is not running (status led not blinking)\n", + serial->type->description); + dev_err(&serial->dev->dev, + "%s: please contact support@connecttech.com\n", + serial->type->description); + kfree(result); + kfree(command); + return -ENODEV; + +no_command_private: + kfree(result); +no_result_buffer: + kfree(command); +no_command_buffer: + return -ENOMEM; +} + +static void whiteheat_release(struct usb_serial *serial) +{ + struct usb_serial_port *command_port; + + /* free up our private data for our command port */ + command_port = serial->port[COMMAND_PORT]; + kfree(usb_get_serial_port_data(command_port)); +} + +static int whiteheat_port_probe(struct usb_serial_port *port) +{ + struct whiteheat_private *info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + usb_set_serial_port_data(port, info); + + return 0; +} + +static int whiteheat_port_remove(struct usb_serial_port *port) +{ + struct whiteheat_private *info; + + info = usb_get_serial_port_data(port); + kfree(info); + + return 0; +} + +static int whiteheat_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int retval; + + retval = start_command_port(port->serial); + if (retval) + goto exit; + + /* send an open port command */ + retval = firm_open(port); + if (retval) { + stop_command_port(port->serial); + goto exit; + } + + retval = firm_purge(port, WHITEHEAT_PURGE_RX | WHITEHEAT_PURGE_TX); + if (retval) { + firm_close(port); + stop_command_port(port->serial); + goto exit; + } + + if (tty) + firm_setup_port(tty); + + /* Work around HCD bugs */ + usb_clear_halt(port->serial->dev, port->read_urb->pipe); + usb_clear_halt(port->serial->dev, port->write_urb->pipe); + + retval = usb_serial_generic_open(tty, port); + if (retval) { + firm_close(port); + stop_command_port(port->serial); + goto exit; + } +exit: + return retval; +} + + +static void whiteheat_close(struct usb_serial_port *port) +{ + firm_report_tx_done(port); + firm_close(port); + + usb_serial_generic_close(port); + + stop_command_port(port->serial); +} + +static int whiteheat_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct whiteheat_private *info = usb_get_serial_port_data(port); + unsigned int modem_signals = 0; + + firm_get_dtr_rts(port); + if (info->mcr & UART_MCR_DTR) + modem_signals |= TIOCM_DTR; + if (info->mcr & UART_MCR_RTS) + modem_signals |= TIOCM_RTS; + + return modem_signals; +} + +static int whiteheat_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct whiteheat_private *info = usb_get_serial_port_data(port); + + if (set & TIOCM_RTS) + info->mcr |= UART_MCR_RTS; + if (set & TIOCM_DTR) + info->mcr |= UART_MCR_DTR; + + if (clear & TIOCM_RTS) + info->mcr &= ~UART_MCR_RTS; + if (clear & TIOCM_DTR) + info->mcr &= ~UART_MCR_DTR; + + firm_set_dtr(port, info->mcr & UART_MCR_DTR); + firm_set_rts(port, info->mcr & UART_MCR_RTS); + return 0; +} + + +static int whiteheat_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct serial_struct serstruct; + void __user *user_arg = (void __user *)arg; + + dev_dbg(&port->dev, "%s - cmd 0x%.4x\n", __func__, cmd); + + switch (cmd) { + case TIOCGSERIAL: + memset(&serstruct, 0, sizeof(serstruct)); + serstruct.type = PORT_16654; + serstruct.line = port->serial->minor; + serstruct.port = port->number; + serstruct.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; + serstruct.xmit_fifo_size = kfifo_size(&port->write_fifo); + serstruct.custom_divisor = 0; + serstruct.baud_base = 460800; + serstruct.close_delay = CLOSING_DELAY; + serstruct.closing_wait = CLOSING_DELAY; + + if (copy_to_user(user_arg, &serstruct, sizeof(serstruct))) + return -EFAULT; + break; + default: + break; + } + + return -ENOIOCTLCMD; +} + + +static void whiteheat_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + firm_setup_port(tty); +} + +static void whiteheat_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + firm_set_break(port, break_state); +} + + +/***************************************************************************** + * Connect Tech's White Heat callback routines + *****************************************************************************/ +static void command_port_write_callback(struct urb *urb) +{ + int status = urb->status; + + if (status) { + dev_dbg(&urb->dev->dev, "nonzero urb status: %d\n", status); + return; + } +} + + +static void command_port_read_callback(struct urb *urb) +{ + struct usb_serial_port *command_port = urb->context; + struct whiteheat_command_private *command_info; + int status = urb->status; + unsigned char *data = urb->transfer_buffer; + int result; + + command_info = usb_get_serial_port_data(command_port); + if (!command_info) { + dev_dbg(&urb->dev->dev, "%s - command_info is NULL, exiting.\n", __func__); + return; + } + if (!urb->actual_length) { + dev_dbg(&urb->dev->dev, "%s - empty response, exiting.\n", __func__); + return; + } + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n", __func__, status); + if (status != -ENOENT) + command_info->command_finished = WHITEHEAT_CMD_FAILURE; + wake_up(&command_info->wait_command); + return; + } + + usb_serial_debug_data(&command_port->dev, __func__, urb->actual_length, data); + + if (data[0] == WHITEHEAT_CMD_COMPLETE) { + command_info->command_finished = WHITEHEAT_CMD_COMPLETE; + wake_up(&command_info->wait_command); + } else if (data[0] == WHITEHEAT_CMD_FAILURE) { + command_info->command_finished = WHITEHEAT_CMD_FAILURE; + wake_up(&command_info->wait_command); + } else if (data[0] == WHITEHEAT_EVENT) { + /* These are unsolicited reports from the firmware, hence no + waiting command to wakeup */ + dev_dbg(&urb->dev->dev, "%s - event received\n", __func__); + } else if ((data[0] == WHITEHEAT_GET_DTR_RTS) && + (urb->actual_length - 1 <= sizeof(command_info->result_buffer))) { + memcpy(command_info->result_buffer, &data[1], + urb->actual_length - 1); + command_info->command_finished = WHITEHEAT_CMD_COMPLETE; + wake_up(&command_info->wait_command); + } else + dev_dbg(&urb->dev->dev, "%s - bad reply from firmware\n", __func__); + + /* Continue trying to always read */ + result = usb_submit_urb(command_port->read_urb, GFP_ATOMIC); + if (result) + dev_dbg(&urb->dev->dev, "%s - failed resubmitting read urb, error %d\n", + __func__, result); +} + + +/***************************************************************************** + * Connect Tech's White Heat firmware interface + *****************************************************************************/ +static int firm_send_command(struct usb_serial_port *port, __u8 command, + __u8 *data, __u8 datasize) +{ + struct usb_serial_port *command_port; + struct whiteheat_command_private *command_info; + struct whiteheat_private *info; + struct device *dev = &port->dev; + __u8 *transfer_buffer; + int retval = 0; + int t; + + dev_dbg(dev, "%s - command %d\n", __func__, command); + + command_port = port->serial->port[COMMAND_PORT]; + command_info = usb_get_serial_port_data(command_port); + mutex_lock(&command_info->mutex); + command_info->command_finished = false; + + transfer_buffer = (__u8 *)command_port->write_urb->transfer_buffer; + transfer_buffer[0] = command; + memcpy(&transfer_buffer[1], data, datasize); + command_port->write_urb->transfer_buffer_length = datasize + 1; + retval = usb_submit_urb(command_port->write_urb, GFP_NOIO); + if (retval) { + dev_dbg(dev, "%s - submit urb failed\n", __func__); + goto exit; + } + + /* wait for the command to complete */ + t = wait_event_timeout(command_info->wait_command, + (bool)command_info->command_finished, COMMAND_TIMEOUT); + if (!t) + usb_kill_urb(command_port->write_urb); + + if (command_info->command_finished == false) { + dev_dbg(dev, "%s - command timed out.\n", __func__); + retval = -ETIMEDOUT; + goto exit; + } + + if (command_info->command_finished == WHITEHEAT_CMD_FAILURE) { + dev_dbg(dev, "%s - command failed.\n", __func__); + retval = -EIO; + goto exit; + } + + if (command_info->command_finished == WHITEHEAT_CMD_COMPLETE) { + dev_dbg(dev, "%s - command completed.\n", __func__); + switch (command) { + case WHITEHEAT_GET_DTR_RTS: + info = usb_get_serial_port_data(port); + memcpy(&info->mcr, command_info->result_buffer, + sizeof(struct whiteheat_dr_info)); + break; + } + } +exit: + mutex_unlock(&command_info->mutex); + return retval; +} + + +static int firm_open(struct usb_serial_port *port) +{ + struct whiteheat_simple open_command; + + open_command.port = port->number - port->serial->minor + 1; + return firm_send_command(port, WHITEHEAT_OPEN, + (__u8 *)&open_command, sizeof(open_command)); +} + + +static int firm_close(struct usb_serial_port *port) +{ + struct whiteheat_simple close_command; + + close_command.port = port->number - port->serial->minor + 1; + return firm_send_command(port, WHITEHEAT_CLOSE, + (__u8 *)&close_command, sizeof(close_command)); +} + + +static void firm_setup_port(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct device *dev = &port->dev; + struct whiteheat_port_settings port_settings; + unsigned int cflag = tty->termios.c_cflag; + + port_settings.port = port->number - port->serial->minor + 1; + + /* get the byte size */ + switch (cflag & CSIZE) { + case CS5: port_settings.bits = 5; break; + case CS6: port_settings.bits = 6; break; + case CS7: port_settings.bits = 7; break; + default: + case CS8: port_settings.bits = 8; break; + } + dev_dbg(dev, "%s - data bits = %d\n", __func__, port_settings.bits); + + /* determine the parity */ + if (cflag & PARENB) + if (cflag & CMSPAR) + if (cflag & PARODD) + port_settings.parity = WHITEHEAT_PAR_MARK; + else + port_settings.parity = WHITEHEAT_PAR_SPACE; + else + if (cflag & PARODD) + port_settings.parity = WHITEHEAT_PAR_ODD; + else + port_settings.parity = WHITEHEAT_PAR_EVEN; + else + port_settings.parity = WHITEHEAT_PAR_NONE; + dev_dbg(dev, "%s - parity = %c\n", __func__, port_settings.parity); + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) + port_settings.stop = 2; + else + port_settings.stop = 1; + dev_dbg(dev, "%s - stop bits = %d\n", __func__, port_settings.stop); + + /* figure out the flow control settings */ + if (cflag & CRTSCTS) + port_settings.hflow = (WHITEHEAT_HFLOW_CTS | + WHITEHEAT_HFLOW_RTS); + else + port_settings.hflow = WHITEHEAT_HFLOW_NONE; + dev_dbg(dev, "%s - hardware flow control = %s %s %s %s\n", __func__, + (port_settings.hflow & WHITEHEAT_HFLOW_CTS) ? "CTS" : "", + (port_settings.hflow & WHITEHEAT_HFLOW_RTS) ? "RTS" : "", + (port_settings.hflow & WHITEHEAT_HFLOW_DSR) ? "DSR" : "", + (port_settings.hflow & WHITEHEAT_HFLOW_DTR) ? "DTR" : ""); + + /* determine software flow control */ + if (I_IXOFF(tty)) + port_settings.sflow = WHITEHEAT_SFLOW_RXTX; + else + port_settings.sflow = WHITEHEAT_SFLOW_NONE; + dev_dbg(dev, "%s - software flow control = %c\n", __func__, port_settings.sflow); + + port_settings.xon = START_CHAR(tty); + port_settings.xoff = STOP_CHAR(tty); + dev_dbg(dev, "%s - XON = %2x, XOFF = %2x\n", __func__, port_settings.xon, port_settings.xoff); + + /* get the baud rate wanted */ + port_settings.baud = tty_get_baud_rate(tty); + dev_dbg(dev, "%s - baud rate = %d\n", __func__, port_settings.baud); + + /* fixme: should set validated settings */ + tty_encode_baud_rate(tty, port_settings.baud, port_settings.baud); + /* handle any settings that aren't specified in the tty structure */ + port_settings.lloop = 0; + + /* now send the message to the device */ + firm_send_command(port, WHITEHEAT_SETUP_PORT, + (__u8 *)&port_settings, sizeof(port_settings)); +} + + +static int firm_set_rts(struct usb_serial_port *port, __u8 onoff) +{ + struct whiteheat_set_rdb rts_command; + + rts_command.port = port->number - port->serial->minor + 1; + rts_command.state = onoff; + return firm_send_command(port, WHITEHEAT_SET_RTS, + (__u8 *)&rts_command, sizeof(rts_command)); +} + + +static int firm_set_dtr(struct usb_serial_port *port, __u8 onoff) +{ + struct whiteheat_set_rdb dtr_command; + + dtr_command.port = port->number - port->serial->minor + 1; + dtr_command.state = onoff; + return firm_send_command(port, WHITEHEAT_SET_DTR, + (__u8 *)&dtr_command, sizeof(dtr_command)); +} + + +static int firm_set_break(struct usb_serial_port *port, __u8 onoff) +{ + struct whiteheat_set_rdb break_command; + + break_command.port = port->number - port->serial->minor + 1; + break_command.state = onoff; + return firm_send_command(port, WHITEHEAT_SET_BREAK, + (__u8 *)&break_command, sizeof(break_command)); +} + + +static int firm_purge(struct usb_serial_port *port, __u8 rxtx) +{ + struct whiteheat_purge purge_command; + + purge_command.port = port->number - port->serial->minor + 1; + purge_command.what = rxtx; + return firm_send_command(port, WHITEHEAT_PURGE, + (__u8 *)&purge_command, sizeof(purge_command)); +} + + +static int firm_get_dtr_rts(struct usb_serial_port *port) +{ + struct whiteheat_simple get_dr_command; + + get_dr_command.port = port->number - port->serial->minor + 1; + return firm_send_command(port, WHITEHEAT_GET_DTR_RTS, + (__u8 *)&get_dr_command, sizeof(get_dr_command)); +} + + +static int firm_report_tx_done(struct usb_serial_port *port) +{ + struct whiteheat_simple close_command; + + close_command.port = port->number - port->serial->minor + 1; + return firm_send_command(port, WHITEHEAT_REPORT_TX_DONE, + (__u8 *)&close_command, sizeof(close_command)); +} + + +/***************************************************************************** + * Connect Tech's White Heat utility functions + *****************************************************************************/ +static int start_command_port(struct usb_serial *serial) +{ + struct usb_serial_port *command_port; + struct whiteheat_command_private *command_info; + int retval = 0; + + command_port = serial->port[COMMAND_PORT]; + command_info = usb_get_serial_port_data(command_port); + mutex_lock(&command_info->mutex); + if (!command_info->port_running) { + /* Work around HCD bugs */ + usb_clear_halt(serial->dev, command_port->read_urb->pipe); + + retval = usb_submit_urb(command_port->read_urb, GFP_KERNEL); + if (retval) { + dev_err(&serial->dev->dev, + "%s - failed submitting read urb, error %d\n", + __func__, retval); + goto exit; + } + } + command_info->port_running++; + +exit: + mutex_unlock(&command_info->mutex); + return retval; +} + + +static void stop_command_port(struct usb_serial *serial) +{ + struct usb_serial_port *command_port; + struct whiteheat_command_private *command_info; + + command_port = serial->port[COMMAND_PORT]; + command_info = usb_get_serial_port_data(command_port); + mutex_lock(&command_info->mutex); + command_info->port_running--; + if (!command_info->port_running) + usb_kill_urb(command_port->read_urb); + mutex_unlock(&command_info->mutex); +} + +module_usb_serial_driver(serial_drivers, id_table_combined); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("whiteheat.fw"); +MODULE_FIRMWARE("whiteheat_loader.fw"); diff --git a/drivers/usb/serial/whiteheat.h b/drivers/usb/serial/whiteheat.h new file mode 100644 index 00000000..38065df4 --- /dev/null +++ b/drivers/usb/serial/whiteheat.h @@ -0,0 +1,302 @@ +/* + * USB ConnectTech WhiteHEAT driver + * + * Copyright (C) 2002 + * Connect Tech Inc. + * + * Copyright (C) 1999, 2000 + * Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See Documentation/usb/usb-serial.txt for more information on using this + * driver + * + */ + +#ifndef __LINUX_USB_SERIAL_WHITEHEAT_H +#define __LINUX_USB_SERIAL_WHITEHEAT_H + + +/* WhiteHEAT commands */ +#define WHITEHEAT_OPEN 1 /* open the port */ +#define WHITEHEAT_CLOSE 2 /* close the port */ +#define WHITEHEAT_SETUP_PORT 3 /* change port settings */ +#define WHITEHEAT_SET_RTS 4 /* turn RTS on or off */ +#define WHITEHEAT_SET_DTR 5 /* turn DTR on or off */ +#define WHITEHEAT_SET_BREAK 6 /* turn BREAK on or off */ +#define WHITEHEAT_DUMP 7 /* dump memory */ +#define WHITEHEAT_STATUS 8 /* get status */ +#define WHITEHEAT_PURGE 9 /* clear the UART fifos */ +#define WHITEHEAT_GET_DTR_RTS 10 /* get the state of DTR and RTS + for a port */ +#define WHITEHEAT_GET_HW_INFO 11 /* get EEPROM info and + hardware ID */ +#define WHITEHEAT_REPORT_TX_DONE 12 /* get the next TX done */ +#define WHITEHEAT_EVENT 13 /* unsolicited status events */ +#define WHITEHEAT_ECHO 14 /* send data to the indicated + IN endpoint */ +#define WHITEHEAT_DO_TEST 15 /* perform specified test */ +#define WHITEHEAT_CMD_COMPLETE 16 /* reply for some commands */ +#define WHITEHEAT_CMD_FAILURE 17 /* reply for failed commands */ + + +/* + * Commands to the firmware + */ + + +/* + * WHITEHEAT_OPEN + * WHITEHEAT_CLOSE + * WHITEHEAT_STATUS + * WHITEHEAT_GET_DTR_RTS + * WHITEHEAT_REPORT_TX_DONE +*/ +struct whiteheat_simple { + __u8 port; /* port number (1 to N) */ +}; + + +/* + * WHITEHEAT_SETUP_PORT + */ +#define WHITEHEAT_PAR_NONE 'n' /* no parity */ +#define WHITEHEAT_PAR_EVEN 'e' /* even parity */ +#define WHITEHEAT_PAR_ODD 'o' /* odd parity */ +#define WHITEHEAT_PAR_SPACE '0' /* space (force 0) parity */ +#define WHITEHEAT_PAR_MARK '1' /* mark (force 1) parity */ + +#define WHITEHEAT_SFLOW_NONE 'n' /* no software flow control */ +#define WHITEHEAT_SFLOW_RX 'r' /* XOFF/ON is sent when RX + fills/empties */ +#define WHITEHEAT_SFLOW_TX 't' /* when received XOFF/ON will + stop/start TX */ +#define WHITEHEAT_SFLOW_RXTX 'b' /* both SFLOW_RX and SFLOW_TX */ + +#define WHITEHEAT_HFLOW_NONE 0x00 /* no hardware flow control */ +#define WHITEHEAT_HFLOW_RTS_TOGGLE 0x01 /* RTS is on during transmit, + off otherwise */ +#define WHITEHEAT_HFLOW_DTR 0x02 /* DTR is off/on when RX + fills/empties */ +#define WHITEHEAT_HFLOW_CTS 0x08 /* when received CTS off/on + will stop/start TX */ +#define WHITEHEAT_HFLOW_DSR 0x10 /* when received DSR off/on + will stop/start TX */ +#define WHITEHEAT_HFLOW_RTS 0x80 /* RTS is off/on when RX + fills/empties */ + +struct whiteheat_port_settings { + __u8 port; /* port number (1 to N) */ + __u32 baud; /* any value 7 - 460800, firmware calculates + best fit; arrives little endian */ + __u8 bits; /* 5, 6, 7, or 8 */ + __u8 stop; /* 1 or 2, default 1 (2 = 1.5 if bits = 5) */ + __u8 parity; /* see WHITEHEAT_PAR_* above */ + __u8 sflow; /* see WHITEHEAT_SFLOW_* above */ + __u8 xoff; /* XOFF byte value */ + __u8 xon; /* XON byte value */ + __u8 hflow; /* see WHITEHEAT_HFLOW_* above */ + __u8 lloop; /* 0/1 turns local loopback mode off/on */ +} __attribute__ ((packed)); + + +/* + * WHITEHEAT_SET_RTS + * WHITEHEAT_SET_DTR + * WHITEHEAT_SET_BREAK + */ +#define WHITEHEAT_RTS_OFF 0x00 +#define WHITEHEAT_RTS_ON 0x01 +#define WHITEHEAT_DTR_OFF 0x00 +#define WHITEHEAT_DTR_ON 0x01 +#define WHITEHEAT_BREAK_OFF 0x00 +#define WHITEHEAT_BREAK_ON 0x01 + +struct whiteheat_set_rdb { + __u8 port; /* port number (1 to N) */ + __u8 state; /* 0/1 turns signal off/on */ +}; + + +/* + * WHITEHEAT_DUMP + */ +#define WHITEHEAT_DUMP_MEM_DATA 'd' /* data */ +#define WHITEHEAT_DUMP_MEM_IDATA 'i' /* idata */ +#define WHITEHEAT_DUMP_MEM_BDATA 'b' /* bdata */ +#define WHITEHEAT_DUMP_MEM_XDATA 'x' /* xdata */ + +/* + * Allowable address ranges (firmware checks address): + * Type DATA: 0x00 - 0xff + * Type IDATA: 0x80 - 0xff + * Type BDATA: 0x20 - 0x2f + * Type XDATA: 0x0000 - 0xffff + * + * B/I/DATA all read the local memory space + * XDATA reads the external memory space + * BDATA returns bits as bytes + * + * NOTE: 0x80 - 0xff (local space) are the Special Function Registers + * of the 8051, and some have on-read side-effects. + */ + +struct whiteheat_dump { + __u8 mem_type; /* see WHITEHEAT_DUMP_* above */ + __u16 addr; /* address, see restrictions above */ + __u16 length; /* number of bytes to dump, max 63 bytes */ +}; + + +/* + * WHITEHEAT_PURGE + */ +#define WHITEHEAT_PURGE_RX 0x01 /* purge rx fifos */ +#define WHITEHEAT_PURGE_TX 0x02 /* purge tx fifos */ + +struct whiteheat_purge { + __u8 port; /* port number (1 to N) */ + __u8 what; /* bit pattern of what to purge */ +}; + + +/* + * WHITEHEAT_ECHO + */ +struct whiteheat_echo { + __u8 port; /* port number (1 to N) */ + __u8 length; /* length of message to echo, max 61 bytes */ + __u8 echo_data[61]; /* data to echo */ +}; + + +/* + * WHITEHEAT_DO_TEST + */ +#define WHITEHEAT_TEST_UART_RW 0x01 /* read/write uart registers */ +#define WHITEHEAT_TEST_UART_INTR 0x02 /* uart interrupt */ +#define WHITEHEAT_TEST_SETUP_CONT 0x03 /* setup for + PORT_CONT/PORT_DISCONT */ +#define WHITEHEAT_TEST_PORT_CONT 0x04 /* port connect */ +#define WHITEHEAT_TEST_PORT_DISCONT 0x05 /* port disconnect */ +#define WHITEHEAT_TEST_UART_CLK_START 0x06 /* uart clock test start */ +#define WHITEHEAT_TEST_UART_CLK_STOP 0x07 /* uart clock test stop */ +#define WHITEHEAT_TEST_MODEM_FT 0x08 /* modem signals, requires a + loopback cable/connector */ +#define WHITEHEAT_TEST_ERASE_EEPROM 0x09 /* erase eeprom */ +#define WHITEHEAT_TEST_READ_EEPROM 0x0a /* read eeprom */ +#define WHITEHEAT_TEST_PROGRAM_EEPROM 0x0b /* program eeprom */ + +struct whiteheat_test { + __u8 port; /* port number (1 to n) */ + __u8 test; /* see WHITEHEAT_TEST_* above*/ + __u8 info[32]; /* additional info */ +}; + + +/* + * Replies from the firmware + */ + + +/* + * WHITEHEAT_STATUS + */ +#define WHITEHEAT_EVENT_MODEM 0x01 /* modem field is valid */ +#define WHITEHEAT_EVENT_ERROR 0x02 /* error field is valid */ +#define WHITEHEAT_EVENT_FLOW 0x04 /* flow field is valid */ +#define WHITEHEAT_EVENT_CONNECT 0x08 /* connect field is valid */ + +#define WHITEHEAT_FLOW_NONE 0x00 /* no flow control active */ +#define WHITEHEAT_FLOW_HARD_OUT 0x01 /* TX is stopped by CTS + (waiting for CTS to go on) */ +#define WHITEHEAT_FLOW_HARD_IN 0x02 /* remote TX is stopped + by RTS */ +#define WHITEHEAT_FLOW_SOFT_OUT 0x04 /* TX is stopped by XOFF + received (waiting for XON) */ +#define WHITEHEAT_FLOW_SOFT_IN 0x08 /* remote TX is stopped by XOFF + transmitted */ +#define WHITEHEAT_FLOW_TX_DONE 0x80 /* TX has completed */ + +struct whiteheat_status_info { + __u8 port; /* port number (1 to N) */ + __u8 event; /* indicates what the current event is, + see WHITEHEAT_EVENT_* above */ + __u8 modem; /* modem signal status (copy of uart's + MSR register) */ + __u8 error; /* line status (copy of uart's LSR register) */ + __u8 flow; /* flow control state, see WHITEHEAT_FLOW_* + above */ + __u8 connect; /* 0 means not connected, non-zero means + connected */ +}; + + +/* + * WHITEHEAT_GET_DTR_RTS + */ +struct whiteheat_dr_info { + __u8 mcr; /* copy of uart's MCR register */ +}; + + +/* + * WHITEHEAT_GET_HW_INFO + */ +struct whiteheat_hw_info { + __u8 hw_id; /* hardware id number, WhiteHEAT = 0 */ + __u8 sw_major_rev; /* major version number */ + __u8 sw_minor_rev; /* minor version number */ + struct whiteheat_hw_eeprom_info { + __u8 b0; /* B0 */ + __u8 vendor_id_low; /* vendor id (low byte) */ + __u8 vendor_id_high; /* vendor id (high byte) */ + __u8 product_id_low; /* product id (low byte) */ + __u8 product_id_high; /* product id (high byte) */ + __u8 device_id_low; /* device id (low byte) */ + __u8 device_id_high; /* device id (high byte) */ + __u8 not_used_1; + __u8 serial_number_0; /* serial number (low byte) */ + __u8 serial_number_1; /* serial number */ + __u8 serial_number_2; /* serial number */ + __u8 serial_number_3; /* serial number (high byte) */ + __u8 not_used_2; + __u8 not_used_3; + __u8 checksum_low; /* checksum (low byte) */ + __u8 checksum_high; /* checksum (high byte */ + } hw_eeprom_info; /* EEPROM contents */ +}; + + +/* + * WHITEHEAT_EVENT + */ +struct whiteheat_event_info { + __u8 port; /* port number (1 to N) */ + __u8 event; /* see whiteheat_status_info.event */ + __u8 info; /* see whiteheat_status_info.modem, .error, + .flow, .connect */ +}; + + +/* + * WHITEHEAT_DO_TEST + */ +#define WHITEHEAT_TEST_FAIL 0x00 /* test failed */ +#define WHITEHEAT_TEST_UNKNOWN 0x01 /* unknown test requested */ +#define WHITEHEAT_TEST_PASS 0xff /* test passed */ + +struct whiteheat_test_info { + __u8 port; /* port number (1 to N) */ + __u8 test; /* indicates which test this is a response for, + see WHITEHEAT_DO_TEST above */ + __u8 status; /* see WHITEHEAT_TEST_* above */ + __u8 results[32]; /* test-dependent results */ +}; + + +#endif diff --git a/drivers/usb/serial/wishbone-serial.c b/drivers/usb/serial/wishbone-serial.c new file mode 100644 index 00000000..100573c6 --- /dev/null +++ b/drivers/usb/serial/wishbone-serial.c @@ -0,0 +1,95 @@ +/* + * USB Wishbone-Serial adapter driver + * + * Copyright (C) 2013 Wesley W. Terpstra + * Copyright (C) 2013 GSI Helmholtz Centre for Heavy Ion Research GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define GSI_VENDOR_OPENCLOSE 0xB0 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(0x1D50, 0x6062, 0xFF, 0xFF, 0xFF) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* + * Etherbone must be told that a new stream has begun before data arrives. + * This is necessary to restart the negotiation of Wishbone bus parameters. + * Similarly, when the stream ends, Etherbone must be told so that the cycle + * line can be driven low in the case that userspace failed to do so. + */ +static int usb_gsi_openclose(struct usb_serial_port *port, int value) +{ + struct usb_device *dev = port->serial->dev; + + return usb_control_msg( + dev, + usb_sndctrlpipe(dev, 0), /* Send to EP0OUT */ + GSI_VENDOR_OPENCLOSE, + USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE, + value, /* wValue = device is open(1) or closed(0) */ + port->serial->interface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, /* There is no data stage */ + 5000); /* Timeout till operation fails */ +} + +static int wishbone_serial_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + int retval; + + retval = usb_gsi_openclose(port, 1); + if (retval) { + dev_err(&port->serial->dev->dev, + "Could not mark device as open (%d)\n", + retval); + return retval; + } + + retval = usb_serial_generic_open(tty, port); + if (retval) + usb_gsi_openclose(port, 0); + + return retval; +} + +static void wishbone_serial_close(struct usb_serial_port *port) +{ + usb_serial_generic_close(port); + usb_gsi_openclose(port, 0); +} + +static struct usb_serial_driver wishbone_serial_device = { + .driver = { + .owner = THIS_MODULE, + .name = "wishbone_serial", + }, + .id_table = id_table, + .num_ports = 1, + .open = &wishbone_serial_open, + .close = &wishbone_serial_close, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &wishbone_serial_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR("Wesley W. Terpstra "); +MODULE_DESCRIPTION("USB Wishbone-Serial adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/xsens_mt.c b/drivers/usb/serial/xsens_mt.c new file mode 100644 index 00000000..1d5798d8 --- /dev/null +++ b/drivers/usb/serial/xsens_mt.c @@ -0,0 +1,86 @@ +/* + * Xsens MT USB driver + * + * Copyright (C) 2013 Xsens + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define XSENS_VID 0x2639 + +#define MTi_10_IMU_PID 0x0001 +#define MTi_20_VRU_PID 0x0002 +#define MTi_30_AHRS_PID 0x0003 + +#define MTi_100_IMU_PID 0x0011 +#define MTi_200_VRU_PID 0x0012 +#define MTi_300_AHRS_PID 0x0013 + +#define MTi_G_700_GPS_INS_PID 0x0017 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(XSENS_VID, MTi_10_IMU_PID) }, + { USB_DEVICE(XSENS_VID, MTi_20_VRU_PID) }, + { USB_DEVICE(XSENS_VID, MTi_30_AHRS_PID) }, + + { USB_DEVICE(XSENS_VID, MTi_100_IMU_PID) }, + { USB_DEVICE(XSENS_VID, MTi_200_VRU_PID) }, + { USB_DEVICE(XSENS_VID, MTi_300_AHRS_PID) }, + + { USB_DEVICE(XSENS_VID, MTi_G_700_GPS_INS_PID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static int has_required_endpoints(const struct usb_host_interface *interface) +{ + __u8 i; + int has_bulk_in = 0; + int has_bulk_out = 0; + + for (i = 0; i < interface->desc.bNumEndpoints; ++i) { + if (usb_endpoint_is_bulk_in(&interface->endpoint[i].desc)) + has_bulk_in = 1; + else if (usb_endpoint_is_bulk_out(&interface->endpoint[i].desc)) + has_bulk_out = 1; + } + + return has_bulk_in && has_bulk_out; +} + +static int xsens_mt_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + if (!has_required_endpoints(serial->interface->cur_altsetting)) + return -ENODEV; + return 0; +} + +static struct usb_serial_driver xsens_mt_device = { + .driver = { + .owner = THIS_MODULE, + .name = "xsens_mt", + }, + .id_table = id_table, + .num_ports = 1, + + .probe = xsens_mt_probe, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &xsens_mt_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/zio.c b/drivers/usb/serial/zio.c new file mode 100644 index 00000000..c043aa84 --- /dev/null +++ b/drivers/usb/serial/zio.c @@ -0,0 +1,39 @@ +/* + * ZIO Motherboard USB driver + * + * Copyright (C) 2010 Zilogic Systems + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x1CBE, 0x0103) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver zio_device = { + .driver = { + .owner = THIS_MODULE, + .name = "zio", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &zio_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/zte_ev.c b/drivers/usb/serial/zte_ev.c new file mode 100644 index 00000000..d6a3fbd0 --- /dev/null +++ b/drivers/usb/serial/zte_ev.c @@ -0,0 +1,306 @@ +/* + * ZTE_EV USB serial driver + * + * Copyright (C) 2012 Greg Kroah-Hartman + * Copyright (C) 2012 Linux Foundation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This driver is based on code found in a ZTE_ENV patch that modified + * the usb-serial generic driver. Comments were left in that I think + * show the commands used to talk to the device, but I am not sure. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_SETUP_DATA_SIZE 32 + +static void debug_data(struct device *dev, const char *function, int len, + const unsigned char *data, int result) +{ + dev_dbg(dev, "result = %d\n", result); + if (result == len) + dev_dbg(dev, "%s - length = %d, data = %*ph\n", function, + len, len, data); +} + +static int zte_ev_usb_serial_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + struct usb_device *udev = port->serial->dev; + struct device *dev = &port->dev; + int result = 0; + int len; + unsigned char *buf; + + buf = kmalloc(MAX_SETUP_DATA_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* send 1st ctl cmd(CTL 21 22 01 00 00 00 00 00) */ + len = 0; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x22, 0x21, + 0x0001, 0x0000, NULL, len, + USB_CTRL_GET_TIMEOUT); + dev_dbg(dev, "result = %d\n", result); + + /* send 2st cmd and recieve data */ + /* + * 16.0 CTL a1 21 00 00 00 00 07 00 CLASS 25.1.0(5) + * 16.0 DI 00 96 00 00 00 00 08 + */ + len = 0x0007; + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + 0x21, 0xa1, + 0x0000, 0x0000, buf, len, + USB_CTRL_GET_TIMEOUT); + debug_data(dev, __func__, len, buf, result); + + /* send 3 cmd */ + /* + * 16.0 CTL 21 20 00 00 00 00 07 00 CLASS 30.1.0 + * 16.0 DO 80 25 00 00 00 00 08 .%..... 30.2.0 + */ + len = 0x0007; + buf[0] = 0x80; + buf[1] = 0x25; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x00; + buf[6] = 0x08; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x20, 0x21, + 0x0000, 0x0000, buf, len, + USB_CTRL_GET_TIMEOUT); + debug_data(dev, __func__, len, buf, result); + + /* send 4 cmd */ + /* + * 16.0 CTL 21 22 03 00 00 00 00 00 + */ + len = 0; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x22, 0x21, + 0x0003, 0x0000, NULL, len, + USB_CTRL_GET_TIMEOUT); + dev_dbg(dev, "result = %d\n", result); + + /* send 5 cmd */ + /* + * 16.0 CTL a1 21 00 00 00 00 07 00 CLASS 33.1.0 + * 16.0 DI 80 25 00 00 00 00 08 + */ + len = 0x0007; + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + 0x21, 0xa1, + 0x0000, 0x0000, buf, len, + USB_CTRL_GET_TIMEOUT); + debug_data(dev, __func__, len, buf, result); + + /* send 6 cmd */ + /* + * 16.0 CTL 21 20 00 00 00 00 07 00 CLASS 34.1.0 + * 16.0 DO 80 25 00 00 00 00 08 + */ + len = 0x0007; + buf[0] = 0x80; + buf[1] = 0x25; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x00; + buf[6] = 0x08; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x20, 0x21, + 0x0000, 0x0000, buf, len, + USB_CTRL_GET_TIMEOUT); + debug_data(dev, __func__, len, buf, result); + kfree(buf); + + return usb_serial_generic_open(tty, port); +} + +/* + * CTL 21 22 02 00 00 00 00 00 CLASS 338.1.0 + * + * 16.1 DI a1 20 00 00 00 00 02 00 02 00 . ........ 340.1.0 + * 16.0 CTL 21 22 03 00 00 00 00 00 CLASS 341.1.0 + * + * 16.0 CTL a1 21 00 00 00 00 07 00 CLASS 346.1.0(3) + * 16.0 DI 00 08 07 00 00 00 08 ....... 346.2.0 + * + * 16.0 CTL 21 20 00 00 00 00 07 00 CLASS 349.1.0 + * 16.0 DO 00 c2 01 00 00 00 08 ....... 349.2.0 + * + * 16.0 CTL 21 22 03 00 00 00 00 00 CLASS 350.1.0(2) + * + * 16.0 CTL a1 21 00 00 00 00 07 00 CLASS 352.1.0 + * 16.0 DI 00 c2 01 00 00 00 08 ....... 352.2.0 + * + * 16.1 DI a1 20 00 00 00 00 02 00 02 00 . ........ 353.1.0 + * + * 16.0 CTL 21 20 00 00 00 00 07 00 CLASS 354.1.0 + * 16.0 DO 00 c2 01 00 00 00 08 ....... 354.2.0 + * + * 16.0 CTL 21 22 03 00 00 00 00 00 +*/ + +static void zte_ev_usb_serial_close(struct usb_serial_port *port) +{ + struct usb_device *udev = port->serial->dev; + struct device *dev = &port->dev; + int result = 0; + int len; + unsigned char *buf; + + buf = kmalloc(MAX_SETUP_DATA_SIZE, GFP_KERNEL); + if (!buf) + return; + + /* send 1st ctl cmd(CTL 21 22 02 00 00 00 00 00) */ + len = 0; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x22, 0x21, + 0x0002, 0x0000, NULL, len, + USB_CTRL_GET_TIMEOUT); + dev_dbg(dev, "result = %d\n", result); + + /* send 2st ctl cmd(CTL 21 22 03 00 00 00 00 00 ) */ + len = 0; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x22, 0x21, + 0x0003, 0x0000, NULL, len, + USB_CTRL_GET_TIMEOUT); + dev_dbg(dev, "result = %d\n", result); + + /* send 3st cmd and recieve data */ + /* + * 16.0 CTL a1 21 00 00 00 00 07 00 CLASS 25.1.0(5) + * 16.0 DI 00 08 07 00 00 00 08 + */ + len = 0x0007; + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + 0x21, 0xa1, + 0x0000, 0x0000, buf, len, + USB_CTRL_GET_TIMEOUT); + debug_data(dev, __func__, len, buf, result); + + /* send 4 cmd */ + /* + * 16.0 CTL 21 20 00 00 00 00 07 00 CLASS 30.1.0 + * 16.0 DO 00 c2 01 00 00 00 08 .%..... 30.2.0 + */ + len = 0x0007; + buf[0] = 0x00; + buf[1] = 0xc2; + buf[2] = 0x01; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x00; + buf[6] = 0x08; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x20, 0x21, + 0x0000, 0x0000, buf, len, + USB_CTRL_GET_TIMEOUT); + debug_data(dev, __func__, len, buf, result); + + /* send 5 cmd */ + /* + * 16.0 CTL 21 22 03 00 00 00 00 00 + */ + len = 0; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x22, 0x21, + 0x0003, 0x0000, NULL, len, + USB_CTRL_GET_TIMEOUT); + dev_dbg(dev, "result = %d\n", result); + + /* send 6 cmd */ + /* + * 16.0 CTL a1 21 00 00 00 00 07 00 CLASS 33.1.0 + * 16.0 DI 00 c2 01 00 00 00 08 + */ + len = 0x0007; + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + 0x21, 0xa1, + 0x0000, 0x0000, buf, len, + USB_CTRL_GET_TIMEOUT); + debug_data(dev, __func__, len, buf, result); + + /* send 7 cmd */ + /* + * 16.0 CTL 21 20 00 00 00 00 07 00 CLASS 354.1.0 + * 16.0 DO 00 c2 01 00 00 00 08 ....... 354.2.0 + */ + len = 0x0007; + buf[0] = 0x00; + buf[1] = 0xc2; + buf[2] = 0x01; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x00; + buf[6] = 0x08; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x20, 0x21, + 0x0000, 0x0000, buf, len, + USB_CTRL_GET_TIMEOUT); + debug_data(dev, __func__, len, buf, result); + + /* send 8 cmd */ + /* + * 16.0 CTL 21 22 03 00 00 00 00 00 + */ + len = 0; + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x22, 0x21, + 0x0003, 0x0000, NULL, len, + USB_CTRL_GET_TIMEOUT); + dev_dbg(dev, "result = %d\n", result); + + kfree(buf); + + usb_serial_generic_close(port); +} + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x19d2, 0xffec) }, + { USB_DEVICE(0x19d2, 0xffee) }, + { USB_DEVICE(0x19d2, 0xfff6) }, + { USB_DEVICE(0x19d2, 0xfff7) }, + { USB_DEVICE(0x19d2, 0xfff8) }, + { USB_DEVICE(0x19d2, 0xfff9) }, + { USB_DEVICE(0x19d2, 0xfffb) }, + { USB_DEVICE(0x19d2, 0xfffc) }, + /* MG880 */ + { USB_DEVICE(0x19d2, 0xfffd) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver zio_device = { + .driver = { + .owner = THIS_MODULE, + .name = "zte_ev", + }, + .id_table = id_table, + .num_ports = 1, + .open = zte_ev_usb_serial_open, + .close = zte_ev_usb_serial_close, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &zio_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig new file mode 100644 index 00000000..1dd0604d --- /dev/null +++ b/drivers/usb/storage/Kconfig @@ -0,0 +1,216 @@ +# +# USB Storage driver configuration +# + +comment "NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may" +comment "also be needed; see USB_STORAGE Help for more info" + +config USB_STORAGE + tristate "USB Mass Storage support" + depends on SCSI + ---help--- + Say Y here if you want to connect USB mass storage devices to your + computer's USB port. This is the driver you need for USB + floppy drives, USB hard disks, USB tape drives, USB CD-ROMs, + USB flash devices, and memory sticks, along with + similar devices. This driver may also be used for some cameras + and card readers. + + This option depends on 'SCSI' support being enabled, but you + probably also need 'SCSI device support: SCSI disk support' + (BLK_DEV_SD) for most USB storage devices. Some devices also + will require 'Probe all LUNs on each SCSI device' + (SCSI_MULTI_LUN). + + To compile this driver as a module, choose M here: the + module will be called usb-storage. + +config USB_STORAGE_DEBUG + bool "USB Mass Storage verbose debug" + depends on USB_STORAGE + help + Say Y here in order to have the USB Mass Storage code generate + verbose debugging messages. + +config USB_STORAGE_REALTEK + tristate "Realtek Card Reader support" + depends on USB_STORAGE + help + Say Y here to include additional code to support the power-saving function + for Realtek RTS51xx USB card readers. + + If this driver is compiled as a module, it will be named ums-realtek. + +config REALTEK_AUTOPM + bool "Realtek Card Reader autosuspend support" + depends on USB_STORAGE_REALTEK && PM_RUNTIME + default y + +config USB_STORAGE_DATAFAB + tristate "Datafab Compact Flash Reader support" + depends on USB_STORAGE + help + Support for certain Datafab CompactFlash readers. + Datafab has a web page at . + + If this driver is compiled as a module, it will be named ums-datafab. + +config USB_STORAGE_FREECOM + tristate "Freecom USB/ATAPI Bridge support" + depends on USB_STORAGE + help + Support for the Freecom USB to IDE/ATAPI adaptor. + Freecom has a web page at . + + If this driver is compiled as a module, it will be named ums-freecom. + +config USB_STORAGE_ISD200 + tristate "ISD-200 USB/ATA Bridge support" + depends on USB_STORAGE + ---help--- + Say Y here if you want to use USB Mass Store devices based + on the In-Systems Design ISD-200 USB/ATA bridge. + + Some of the products that use this chip are: + + - Archos Jukebox 6000 + - ISD SmartCable for Storage + - Taiwan Skymaster CD530U/DEL-0241 IDE bridge + - Sony CRX10U CD-R/RW drive + - CyQ've CQ8060A CDRW drive + - Planex eXtreme Drive RX-25HU USB-IDE cable (not model RX-25U) + + If this driver is compiled as a module, it will be named ums-isd200. + +config USB_STORAGE_USBAT + tristate "USBAT/USBAT02-based storage support" + depends on USB_STORAGE + help + Say Y here to include additional code to support storage devices + based on the SCM/Shuttle USBAT/USBAT02 processors. + + Devices reported to work with this driver include: + - CompactFlash reader included with Kodak DC3800 camera + - Dane-Elec Zmate CompactFlash reader + - Delkin Efilm reader2 + - HP 8200e/8210e/8230e CD-Writer Plus drives + - I-JAM JS-50U + - Jessops CompactFlash JESDCFRU BLACK + - Kingston Technology PCREAD-USB/CF + - Maxell UA4 CompactFlash reader + - Memorex UCF-100 + - Microtech ZiO! ICS-45 CF2 + - RCA LYRA MP3 portable + - Sandisk ImageMate SDDR-05b + + If this driver is compiled as a module, it will be named ums-usbat. + +config USB_STORAGE_SDDR09 + tristate "SanDisk SDDR-09 (and other SmartMedia, including DPCM) support" + depends on USB_STORAGE + help + Say Y here to include additional code to support the Sandisk SDDR-09 + SmartMedia reader in the USB Mass Storage driver. + Also works for the Microtech Zio! CompactFlash/SmartMedia reader. + + If this driver is compiled as a module, it will be named ums-sddr09. + +config USB_STORAGE_SDDR55 + tristate "SanDisk SDDR-55 SmartMedia support" + depends on USB_STORAGE + help + Say Y here to include additional code to support the Sandisk SDDR-55 + SmartMedia reader in the USB Mass Storage driver. + + If this driver is compiled as a module, it will be named ums-sddr55. + +config USB_STORAGE_JUMPSHOT + tristate "Lexar Jumpshot Compact Flash Reader" + depends on USB_STORAGE + help + Say Y here to include additional code to support the Lexar Jumpshot + USB CompactFlash reader. + + If this driver is compiled as a module, it will be named ums-jumpshot. + +config USB_STORAGE_ALAUDA + tristate "Olympus MAUSB-10/Fuji DPC-R1 support" + depends on USB_STORAGE + help + Say Y here to include additional code to support the Olympus MAUSB-10 + and Fujifilm DPC-R1 USB Card reader/writer devices. + + These devices are based on the Alauda chip and support both + XD and SmartMedia cards. + + If this driver is compiled as a module, it will be named ums-alauda. + +config USB_STORAGE_ONETOUCH + tristate "Support OneTouch Button on Maxtor Hard Drives" + depends on USB_STORAGE + depends on INPUT=y || INPUT=USB_STORAGE + help + Say Y here to include additional code to support the Maxtor OneTouch + USB hard drive's onetouch button. + + This code registers the button on the front of Maxtor OneTouch USB + hard drive's as an input device. An action can be associated with + this input in any keybinding software. (e.g. gnome's keyboard short- + cuts) + + If this driver is compiled as a module, it will be named ums-onetouch. + +config USB_STORAGE_KARMA + tristate "Support for Rio Karma music player" + depends on USB_STORAGE + help + Say Y here to include additional code to support the Rio Karma + USB interface. + + This code places the Rio Karma into mass storage mode, enabling + it to be mounted as an ordinary filesystem. Performing an eject + on the resulting scsi device node returns the Karma to normal + operation. + + If this driver is compiled as a module, it will be named ums-karma. + +config USB_STORAGE_CYPRESS_ATACB + tristate "SAT emulation on Cypress USB/ATA Bridge with ATACB" + depends on USB_STORAGE + ---help--- + Say Y here if you want to use SAT (ata pass through) on devices based + on the Cypress USB/ATA bridge supporting ATACB. This will allow you + to use tools to tune and monitor your drive (like hdparm or smartctl). + + If you say no here your device will still work with the standard usb + mass storage class. + + If this driver is compiled as a module, it will be named ums-cypress. + +config USB_STORAGE_ENE_UB6250 + tristate "USB ENE card reader support" + depends on SCSI + depends on USB_STORAGE + ---help--- + Say Y here if you wish to control a ENE SD/MS Card reader. + To use SM card, please build driver/staging/keucr/keucr.ko + + This option depends on 'SCSI' support being enabled, but you + probably also need 'SCSI device support: SCSI disk support' + (BLK_DEV_SD) for most USB storage devices. + + To compile this driver as a module, choose M here: the + module will be called ums-eneub6250. + +config USB_UAS + tristate "USB Attached SCSI" + depends on SCSI && BROKEN + help + The USB Attached SCSI protocol is supported by some USB + storage devices. It permits higher performance by supporting + multiple outstanding commands. + + If you don't know whether you have a UAS device, it is safe to + say 'Y' or 'M' here and the kernel will use the right driver. + + If you compile this driver as a module, it will be named uas. diff --git a/drivers/usb/storage/Makefile b/drivers/usb/storage/Makefile new file mode 100644 index 00000000..4cd55481 --- /dev/null +++ b/drivers/usb/storage/Makefile @@ -0,0 +1,44 @@ +# +# Makefile for the USB Mass Storage device drivers. +# +# 15 Aug 2000, Christoph Hellwig +# Rewritten to use lists instead of if-statements. +# + +ccflags-y := -Idrivers/scsi + +obj-$(CONFIG_USB_UAS) += uas.o +obj-$(CONFIG_USB_STORAGE) += usb-storage.o + +usb-storage-y := scsiglue.o protocol.o transport.o usb.o +usb-storage-y += initializers.o sierra_ms.o option_ms.o +usb-storage-y += usual-tables.o +usb-storage-$(CONFIG_USB_STORAGE_DEBUG) += debug.o + +obj-$(CONFIG_USB_STORAGE_ALAUDA) += ums-alauda.o +obj-$(CONFIG_USB_STORAGE_CYPRESS_ATACB) += ums-cypress.o +obj-$(CONFIG_USB_STORAGE_DATAFAB) += ums-datafab.o +obj-$(CONFIG_USB_STORAGE_ENE_UB6250) += ums-eneub6250.o +obj-$(CONFIG_USB_STORAGE_FREECOM) += ums-freecom.o +obj-$(CONFIG_USB_STORAGE_ISD200) += ums-isd200.o +obj-$(CONFIG_USB_STORAGE_JUMPSHOT) += ums-jumpshot.o +obj-$(CONFIG_USB_STORAGE_KARMA) += ums-karma.o +obj-$(CONFIG_USB_STORAGE_ONETOUCH) += ums-onetouch.o +obj-$(CONFIG_USB_STORAGE_REALTEK) += ums-realtek.o +obj-$(CONFIG_USB_STORAGE_SDDR09) += ums-sddr09.o +obj-$(CONFIG_USB_STORAGE_SDDR55) += ums-sddr55.o +obj-$(CONFIG_USB_STORAGE_USBAT) += ums-usbat.o + +ums-alauda-y := alauda.o +ums-cypress-y := cypress_atacb.o +ums-datafab-y := datafab.o +ums-eneub6250-y := ene_ub6250.o +ums-freecom-y := freecom.o +ums-isd200-y := isd200.o +ums-jumpshot-y := jumpshot.o +ums-karma-y := karma.o +ums-onetouch-y := onetouch.o +ums-realtek-y := realtek_cr.o +ums-sddr09-y := sddr09.o +ums-sddr55-y := sddr55.o +ums-usbat-y := shuttle_usbat.o diff --git a/drivers/usb/storage/alauda.c b/drivers/usb/storage/alauda.c new file mode 100644 index 00000000..77a2ddfe --- /dev/null +++ b/drivers/usb/storage/alauda.c @@ -0,0 +1,1271 @@ +/* + * Driver for Alauda-based card readers + * + * Current development and maintenance by: + * (c) 2005 Daniel Drake + * + * The 'Alauda' is a chip manufacturered by RATOC for OEM use. + * + * Alauda implements a vendor-specific command set to access two media reader + * ports (XD, SmartMedia). This driver converts SCSI commands to the commands + * which are accepted by these devices. + * + * The driver was developed through reverse-engineering, with the help of the + * sddr09 driver which has many similarities, and with some help from the + * (very old) vendor-supplied GPL sma03 driver. + * + * For protocol info, see http://alauda.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + +MODULE_DESCRIPTION("Driver for Alauda-based card readers"); +MODULE_AUTHOR("Daniel Drake "); +MODULE_LICENSE("GPL"); + +/* + * Status bytes + */ +#define ALAUDA_STATUS_ERROR 0x01 +#define ALAUDA_STATUS_READY 0x40 + +/* + * Control opcodes (for request field) + */ +#define ALAUDA_GET_XD_MEDIA_STATUS 0x08 +#define ALAUDA_GET_SM_MEDIA_STATUS 0x98 +#define ALAUDA_ACK_XD_MEDIA_CHANGE 0x0a +#define ALAUDA_ACK_SM_MEDIA_CHANGE 0x9a +#define ALAUDA_GET_XD_MEDIA_SIG 0x86 +#define ALAUDA_GET_SM_MEDIA_SIG 0x96 + +/* + * Bulk command identity (byte 0) + */ +#define ALAUDA_BULK_CMD 0x40 + +/* + * Bulk opcodes (byte 1) + */ +#define ALAUDA_BULK_GET_REDU_DATA 0x85 +#define ALAUDA_BULK_READ_BLOCK 0x94 +#define ALAUDA_BULK_ERASE_BLOCK 0xa3 +#define ALAUDA_BULK_WRITE_BLOCK 0xb4 +#define ALAUDA_BULK_GET_STATUS2 0xb7 +#define ALAUDA_BULK_RESET_MEDIA 0xe0 + +/* + * Port to operate on (byte 8) + */ +#define ALAUDA_PORT_XD 0x00 +#define ALAUDA_PORT_SM 0x01 + +/* + * LBA and PBA are unsigned ints. Special values. + */ +#define UNDEF 0xffff +#define SPARE 0xfffe +#define UNUSABLE 0xfffd + +struct alauda_media_info { + unsigned long capacity; /* total media size in bytes */ + unsigned int pagesize; /* page size in bytes */ + unsigned int blocksize; /* number of pages per block */ + unsigned int uzonesize; /* number of usable blocks per zone */ + unsigned int zonesize; /* number of blocks per zone */ + unsigned int blockmask; /* mask to get page from address */ + + unsigned char pageshift; + unsigned char blockshift; + unsigned char zoneshift; + + u16 **lba_to_pba; /* logical to physical block map */ + u16 **pba_to_lba; /* physical to logical block map */ +}; + +struct alauda_info { + struct alauda_media_info port[2]; + int wr_ep; /* endpoint to write data out of */ + + unsigned char sense_key; + unsigned long sense_asc; /* additional sense code */ + unsigned long sense_ascq; /* additional sense code qualifier */ +}; + +#define short_pack(lsb,msb) ( ((u16)(lsb)) | ( ((u16)(msb))<<8 ) ) +#define LSB_of(s) ((s)&0xFF) +#define MSB_of(s) ((s)>>8) + +#define MEDIA_PORT(us) us->srb->device->lun +#define MEDIA_INFO(us) ((struct alauda_info *)us->extra)->port[MEDIA_PORT(us)] + +#define PBA_LO(pba) ((pba & 0xF) << 5) +#define PBA_HI(pba) (pba >> 3) +#define PBA_ZONE(pba) (pba >> 11) + +static int init_alauda(struct us_data *us); + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id alauda_usb_ids[] = { +# include "unusual_alauda.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, alauda_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev alauda_unusual_dev_list[] = { +# include "unusual_alauda.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + +/* + * Media handling + */ + +struct alauda_card_info { + unsigned char id; /* id byte */ + unsigned char chipshift; /* 1< LBA mappings for a particular port + */ +static void alauda_free_maps (struct alauda_media_info *media_info) +{ + unsigned int shift = media_info->zoneshift + + media_info->blockshift + media_info->pageshift; + unsigned int num_zones = media_info->capacity >> shift; + unsigned int i; + + if (media_info->lba_to_pba != NULL) + for (i = 0; i < num_zones; i++) { + kfree(media_info->lba_to_pba[i]); + media_info->lba_to_pba[i] = NULL; + } + + if (media_info->pba_to_lba != NULL) + for (i = 0; i < num_zones; i++) { + kfree(media_info->pba_to_lba[i]); + media_info->pba_to_lba[i] = NULL; + } +} + +/* + * Returns 2 bytes of status data + * The first byte describes media status, and second byte describes door status + */ +static int alauda_get_media_status(struct us_data *us, unsigned char *data) +{ + int rc; + unsigned char command; + + if (MEDIA_PORT(us) == ALAUDA_PORT_XD) + command = ALAUDA_GET_XD_MEDIA_STATUS; + else + command = ALAUDA_GET_SM_MEDIA_STATUS; + + rc = usb_stor_ctrl_transfer(us, us->recv_ctrl_pipe, + command, 0xc0, 0, 1, data, 2); + + usb_stor_dbg(us, "Media status %02X %02X\n", data[0], data[1]); + + return rc; +} + +/* + * Clears the "media was changed" bit so that we know when it changes again + * in the future. + */ +static int alauda_ack_media(struct us_data *us) +{ + unsigned char command; + + if (MEDIA_PORT(us) == ALAUDA_PORT_XD) + command = ALAUDA_ACK_XD_MEDIA_CHANGE; + else + command = ALAUDA_ACK_SM_MEDIA_CHANGE; + + return usb_stor_ctrl_transfer(us, us->send_ctrl_pipe, + command, 0x40, 0, 1, NULL, 0); +} + +/* + * Retrieves a 4-byte media signature, which indicates manufacturer, capacity, + * and some other details. + */ +static int alauda_get_media_signature(struct us_data *us, unsigned char *data) +{ + unsigned char command; + + if (MEDIA_PORT(us) == ALAUDA_PORT_XD) + command = ALAUDA_GET_XD_MEDIA_SIG; + else + command = ALAUDA_GET_SM_MEDIA_SIG; + + return usb_stor_ctrl_transfer(us, us->recv_ctrl_pipe, + command, 0xc0, 0, 0, data, 4); +} + +/* + * Resets the media status (but not the whole device?) + */ +static int alauda_reset_media(struct us_data *us) +{ + unsigned char *command = us->iobuf; + + memset(command, 0, 9); + command[0] = ALAUDA_BULK_CMD; + command[1] = ALAUDA_BULK_RESET_MEDIA; + command[8] = MEDIA_PORT(us); + + return usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + command, 9, NULL); +} + +/* + * Examines the media and deduces capacity, etc. + */ +static int alauda_init_media(struct us_data *us) +{ + unsigned char *data = us->iobuf; + int ready = 0; + struct alauda_card_info *media_info; + unsigned int num_zones; + + while (ready == 0) { + msleep(20); + + if (alauda_get_media_status(us, data) != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (data[0] & 0x10) + ready = 1; + } + + usb_stor_dbg(us, "We are ready for action!\n"); + + if (alauda_ack_media(us) != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + msleep(10); + + if (alauda_get_media_status(us, data) != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (data[0] != 0x14) { + usb_stor_dbg(us, "Media not ready after ack\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + if (alauda_get_media_signature(us, data) != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "Media signature: %02X %02X %02X %02X\n", + data[0], data[1], data[2], data[3]); + media_info = alauda_card_find_id(data[1]); + if (media_info == NULL) { + printk(KERN_WARNING + "alauda_init_media: Unrecognised media signature: " + "%02X %02X %02X %02X\n", + data[0], data[1], data[2], data[3]); + return USB_STOR_TRANSPORT_ERROR; + } + + MEDIA_INFO(us).capacity = 1 << media_info->chipshift; + usb_stor_dbg(us, "Found media with capacity: %ldMB\n", + MEDIA_INFO(us).capacity >> 20); + + MEDIA_INFO(us).pageshift = media_info->pageshift; + MEDIA_INFO(us).blockshift = media_info->blockshift; + MEDIA_INFO(us).zoneshift = media_info->zoneshift; + + MEDIA_INFO(us).pagesize = 1 << media_info->pageshift; + MEDIA_INFO(us).blocksize = 1 << media_info->blockshift; + MEDIA_INFO(us).zonesize = 1 << media_info->zoneshift; + + MEDIA_INFO(us).uzonesize = ((1 << media_info->zoneshift) / 128) * 125; + MEDIA_INFO(us).blockmask = MEDIA_INFO(us).blocksize - 1; + + num_zones = MEDIA_INFO(us).capacity >> (MEDIA_INFO(us).zoneshift + + MEDIA_INFO(us).blockshift + MEDIA_INFO(us).pageshift); + MEDIA_INFO(us).pba_to_lba = kcalloc(num_zones, sizeof(u16*), GFP_NOIO); + MEDIA_INFO(us).lba_to_pba = kcalloc(num_zones, sizeof(u16*), GFP_NOIO); + + if (alauda_reset_media(us) != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Examines the media status and does the right thing when the media has gone, + * appeared, or changed. + */ +static int alauda_check_media(struct us_data *us) +{ + struct alauda_info *info = (struct alauda_info *) us->extra; + unsigned char status[2]; + int rc; + + rc = alauda_get_media_status(us, status); + + /* Check for no media or door open */ + if ((status[0] & 0x80) || ((status[0] & 0x1F) == 0x10) + || ((status[1] & 0x01) == 0)) { + usb_stor_dbg(us, "No media, or door open\n"); + alauda_free_maps(&MEDIA_INFO(us)); + info->sense_key = 0x02; + info->sense_asc = 0x3A; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; + } + + /* Check for media change */ + if (status[0] & 0x08) { + usb_stor_dbg(us, "Media change detected\n"); + alauda_free_maps(&MEDIA_INFO(us)); + alauda_init_media(us); + + info->sense_key = UNIT_ATTENTION; + info->sense_asc = 0x28; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Checks the status from the 2nd status register + * Returns 3 bytes of status data, only the first is known + */ +static int alauda_check_status2(struct us_data *us) +{ + int rc; + unsigned char command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_GET_STATUS2, + 0, 0, 0, 0, 3, 0, MEDIA_PORT(us) + }; + unsigned char data[3]; + + rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + command, 9, NULL); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + rc = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + data, 3, NULL); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + usb_stor_dbg(us, "%02X %02X %02X\n", data[0], data[1], data[2]); + if (data[0] & ALAUDA_STATUS_ERROR) + return USB_STOR_XFER_ERROR; + + return USB_STOR_XFER_GOOD; +} + +/* + * Gets the redundancy data for the first page of a PBA + * Returns 16 bytes. + */ +static int alauda_get_redu_data(struct us_data *us, u16 pba, unsigned char *data) +{ + int rc; + unsigned char command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_GET_REDU_DATA, + PBA_HI(pba), PBA_ZONE(pba), 0, PBA_LO(pba), 0, 0, MEDIA_PORT(us) + }; + + rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + command, 9, NULL); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + return usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + data, 16, NULL); +} + +/* + * Finds the first unused PBA in a zone + * Returns the absolute PBA of an unused PBA, or 0 if none found. + */ +static u16 alauda_find_unused_pba(struct alauda_media_info *info, + unsigned int zone) +{ + u16 *pba_to_lba = info->pba_to_lba[zone]; + unsigned int i; + + for (i = 0; i < info->zonesize; i++) + if (pba_to_lba[i] == UNDEF) + return (zone << info->zoneshift) + i; + + return 0; +} + +/* + * Reads the redundancy data for all PBA's in a zone + * Produces lba <--> pba mappings + */ +static int alauda_read_map(struct us_data *us, unsigned int zone) +{ + unsigned char *data = us->iobuf; + int result; + int i, j; + unsigned int zonesize = MEDIA_INFO(us).zonesize; + unsigned int uzonesize = MEDIA_INFO(us).uzonesize; + unsigned int lba_offset, lba_real, blocknum; + unsigned int zone_base_lba = zone * uzonesize; + unsigned int zone_base_pba = zone * zonesize; + u16 *lba_to_pba = kcalloc(zonesize, sizeof(u16), GFP_NOIO); + u16 *pba_to_lba = kcalloc(zonesize, sizeof(u16), GFP_NOIO); + if (lba_to_pba == NULL || pba_to_lba == NULL) { + result = USB_STOR_TRANSPORT_ERROR; + goto error; + } + + usb_stor_dbg(us, "Mapping blocks for zone %d\n", zone); + + /* 1024 PBA's per zone */ + for (i = 0; i < zonesize; i++) + lba_to_pba[i] = pba_to_lba[i] = UNDEF; + + for (i = 0; i < zonesize; i++) { + blocknum = zone_base_pba + i; + + result = alauda_get_redu_data(us, blocknum, data); + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_TRANSPORT_ERROR; + goto error; + } + + /* special PBAs have control field 0^16 */ + for (j = 0; j < 16; j++) + if (data[j] != 0) + goto nonz; + pba_to_lba[i] = UNUSABLE; + usb_stor_dbg(us, "PBA %d has no logical mapping\n", blocknum); + continue; + + nonz: + /* unwritten PBAs have control field FF^16 */ + for (j = 0; j < 16; j++) + if (data[j] != 0xff) + goto nonff; + continue; + + nonff: + /* normal PBAs start with six FFs */ + if (j < 6) { + usb_stor_dbg(us, "PBA %d has no logical mapping: reserved area = %02X%02X%02X%02X data status %02X block status %02X\n", + blocknum, + data[0], data[1], data[2], data[3], + data[4], data[5]); + pba_to_lba[i] = UNUSABLE; + continue; + } + + if ((data[6] >> 4) != 0x01) { + usb_stor_dbg(us, "PBA %d has invalid address field %02X%02X/%02X%02X\n", + blocknum, data[6], data[7], + data[11], data[12]); + pba_to_lba[i] = UNUSABLE; + continue; + } + + /* check even parity */ + if (parity[data[6] ^ data[7]]) { + printk(KERN_WARNING + "alauda_read_map: Bad parity in LBA for block %d" + " (%02X %02X)\n", i, data[6], data[7]); + pba_to_lba[i] = UNUSABLE; + continue; + } + + lba_offset = short_pack(data[7], data[6]); + lba_offset = (lba_offset & 0x07FF) >> 1; + lba_real = lba_offset + zone_base_lba; + + /* + * Every 1024 physical blocks ("zone"), the LBA numbers + * go back to zero, but are within a higher block of LBA's. + * Also, there is a maximum of 1000 LBA's per zone. + * In other words, in PBA 1024-2047 you will find LBA 0-999 + * which are really LBA 1000-1999. This allows for 24 bad + * or special physical blocks per zone. + */ + + if (lba_offset >= uzonesize) { + printk(KERN_WARNING + "alauda_read_map: Bad low LBA %d for block %d\n", + lba_real, blocknum); + continue; + } + + if (lba_to_pba[lba_offset] != UNDEF) { + printk(KERN_WARNING + "alauda_read_map: " + "LBA %d seen for PBA %d and %d\n", + lba_real, lba_to_pba[lba_offset], blocknum); + continue; + } + + pba_to_lba[i] = lba_real; + lba_to_pba[lba_offset] = blocknum; + continue; + } + + MEDIA_INFO(us).lba_to_pba[zone] = lba_to_pba; + MEDIA_INFO(us).pba_to_lba[zone] = pba_to_lba; + result = 0; + goto out; + +error: + kfree(lba_to_pba); + kfree(pba_to_lba); +out: + return result; +} + +/* + * Checks to see whether we have already mapped a certain zone + * If we haven't, the map is generated + */ +static void alauda_ensure_map_for_zone(struct us_data *us, unsigned int zone) +{ + if (MEDIA_INFO(us).lba_to_pba[zone] == NULL + || MEDIA_INFO(us).pba_to_lba[zone] == NULL) + alauda_read_map(us, zone); +} + +/* + * Erases an entire block + */ +static int alauda_erase_block(struct us_data *us, u16 pba) +{ + int rc; + unsigned char command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_ERASE_BLOCK, PBA_HI(pba), + PBA_ZONE(pba), 0, PBA_LO(pba), 0x02, 0, MEDIA_PORT(us) + }; + unsigned char buf[2]; + + usb_stor_dbg(us, "Erasing PBA %d\n", pba); + + rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + command, 9, NULL); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + rc = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + buf, 2, NULL); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + usb_stor_dbg(us, "Erase result: %02X %02X\n", buf[0], buf[1]); + return rc; +} + +/* + * Reads data from a certain offset page inside a PBA, including interleaved + * redundancy data. Returns (pagesize+64)*pages bytes in data. + */ +static int alauda_read_block_raw(struct us_data *us, u16 pba, + unsigned int page, unsigned int pages, unsigned char *data) +{ + int rc; + unsigned char command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_READ_BLOCK, PBA_HI(pba), + PBA_ZONE(pba), 0, PBA_LO(pba) + page, pages, 0, MEDIA_PORT(us) + }; + + usb_stor_dbg(us, "pba %d page %d count %d\n", pba, page, pages); + + rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + command, 9, NULL); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + return usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + data, (MEDIA_INFO(us).pagesize + 64) * pages, NULL); +} + +/* + * Reads data from a certain offset page inside a PBA, excluding redundancy + * data. Returns pagesize*pages bytes in data. Note that data must be big enough + * to hold (pagesize+64)*pages bytes of data, but you can ignore those 'extra' + * trailing bytes outside this function. + */ +static int alauda_read_block(struct us_data *us, u16 pba, + unsigned int page, unsigned int pages, unsigned char *data) +{ + int i, rc; + unsigned int pagesize = MEDIA_INFO(us).pagesize; + + rc = alauda_read_block_raw(us, pba, page, pages, data); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + /* Cut out the redundancy data */ + for (i = 0; i < pages; i++) { + int dest_offset = i * pagesize; + int src_offset = i * (pagesize + 64); + memmove(data + dest_offset, data + src_offset, pagesize); + } + + return rc; +} + +/* + * Writes an entire block of data and checks status after write. + * Redundancy data must be already included in data. Data should be + * (pagesize+64)*blocksize bytes in length. + */ +static int alauda_write_block(struct us_data *us, u16 pba, unsigned char *data) +{ + int rc; + struct alauda_info *info = (struct alauda_info *) us->extra; + unsigned char command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_WRITE_BLOCK, PBA_HI(pba), + PBA_ZONE(pba), 0, PBA_LO(pba), 32, 0, MEDIA_PORT(us) + }; + + usb_stor_dbg(us, "pba %d\n", pba); + + rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + command, 9, NULL); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + rc = usb_stor_bulk_transfer_buf(us, info->wr_ep, data, + (MEDIA_INFO(us).pagesize + 64) * MEDIA_INFO(us).blocksize, + NULL); + if (rc != USB_STOR_XFER_GOOD) + return rc; + + return alauda_check_status2(us); +} + +/* + * Write some data to a specific LBA. + */ +static int alauda_write_lba(struct us_data *us, u16 lba, + unsigned int page, unsigned int pages, + unsigned char *ptr, unsigned char *blockbuffer) +{ + u16 pba, lbap, new_pba; + unsigned char *bptr, *cptr, *xptr; + unsigned char ecc[3]; + int i, result; + unsigned int uzonesize = MEDIA_INFO(us).uzonesize; + unsigned int zonesize = MEDIA_INFO(us).zonesize; + unsigned int pagesize = MEDIA_INFO(us).pagesize; + unsigned int blocksize = MEDIA_INFO(us).blocksize; + unsigned int lba_offset = lba % uzonesize; + unsigned int new_pba_offset; + unsigned int zone = lba / uzonesize; + + alauda_ensure_map_for_zone(us, zone); + + pba = MEDIA_INFO(us).lba_to_pba[zone][lba_offset]; + if (pba == 1) { + /* Maybe it is impossible to write to PBA 1. + Fake success, but don't do anything. */ + printk(KERN_WARNING + "alauda_write_lba: avoid writing to pba 1\n"); + return USB_STOR_TRANSPORT_GOOD; + } + + new_pba = alauda_find_unused_pba(&MEDIA_INFO(us), zone); + if (!new_pba) { + printk(KERN_WARNING + "alauda_write_lba: Out of unused blocks\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* read old contents */ + if (pba != UNDEF) { + result = alauda_read_block_raw(us, pba, 0, + blocksize, blockbuffer); + if (result != USB_STOR_XFER_GOOD) + return result; + } else { + memset(blockbuffer, 0, blocksize * (pagesize + 64)); + } + + lbap = (lba_offset << 1) | 0x1000; + if (parity[MSB_of(lbap) ^ LSB_of(lbap)]) + lbap ^= 1; + + /* check old contents and fill lba */ + for (i = 0; i < blocksize; i++) { + bptr = blockbuffer + (i * (pagesize + 64)); + cptr = bptr + pagesize; + nand_compute_ecc(bptr, ecc); + if (!nand_compare_ecc(cptr+13, ecc)) { + usb_stor_dbg(us, "Warning: bad ecc in page %d- of pba %d\n", + i, pba); + nand_store_ecc(cptr+13, ecc); + } + nand_compute_ecc(bptr + (pagesize / 2), ecc); + if (!nand_compare_ecc(cptr+8, ecc)) { + usb_stor_dbg(us, "Warning: bad ecc in page %d+ of pba %d\n", + i, pba); + nand_store_ecc(cptr+8, ecc); + } + cptr[6] = cptr[11] = MSB_of(lbap); + cptr[7] = cptr[12] = LSB_of(lbap); + } + + /* copy in new stuff and compute ECC */ + xptr = ptr; + for (i = page; i < page+pages; i++) { + bptr = blockbuffer + (i * (pagesize + 64)); + cptr = bptr + pagesize; + memcpy(bptr, xptr, pagesize); + xptr += pagesize; + nand_compute_ecc(bptr, ecc); + nand_store_ecc(cptr+13, ecc); + nand_compute_ecc(bptr + (pagesize / 2), ecc); + nand_store_ecc(cptr+8, ecc); + } + + result = alauda_write_block(us, new_pba, blockbuffer); + if (result != USB_STOR_XFER_GOOD) + return result; + + new_pba_offset = new_pba - (zone * zonesize); + MEDIA_INFO(us).pba_to_lba[zone][new_pba_offset] = lba; + MEDIA_INFO(us).lba_to_pba[zone][lba_offset] = new_pba; + usb_stor_dbg(us, "Remapped LBA %d to PBA %d\n", lba, new_pba); + + if (pba != UNDEF) { + unsigned int pba_offset = pba - (zone * zonesize); + result = alauda_erase_block(us, pba); + if (result != USB_STOR_XFER_GOOD) + return result; + MEDIA_INFO(us).pba_to_lba[zone][pba_offset] = UNDEF; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Read data from a specific sector address + */ +static int alauda_read_data(struct us_data *us, unsigned long address, + unsigned int sectors) +{ + unsigned char *buffer; + u16 lba, max_lba; + unsigned int page, len, offset; + unsigned int blockshift = MEDIA_INFO(us).blockshift; + unsigned int pageshift = MEDIA_INFO(us).pageshift; + unsigned int blocksize = MEDIA_INFO(us).blocksize; + unsigned int pagesize = MEDIA_INFO(us).pagesize; + unsigned int uzonesize = MEDIA_INFO(us).uzonesize; + struct scatterlist *sg; + int result; + + /* + * Since we only read in one block at a time, we have to create + * a bounce buffer and move the data a piece at a time between the + * bounce buffer and the actual transfer buffer. + * We make this buffer big enough to hold temporary redundancy data, + * which we use when reading the data blocks. + */ + + len = min(sectors, blocksize) * (pagesize + 64); + buffer = kmalloc(len, GFP_NOIO); + if (buffer == NULL) { + printk(KERN_WARNING "alauda_read_data: Out of memory\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* Figure out the initial LBA and page */ + lba = address >> blockshift; + page = (address & MEDIA_INFO(us).blockmask); + max_lba = MEDIA_INFO(us).capacity >> (blockshift + pageshift); + + result = USB_STOR_TRANSPORT_GOOD; + offset = 0; + sg = NULL; + + while (sectors > 0) { + unsigned int zone = lba / uzonesize; /* integer division */ + unsigned int lba_offset = lba - (zone * uzonesize); + unsigned int pages; + u16 pba; + alauda_ensure_map_for_zone(us, zone); + + /* Not overflowing capacity? */ + if (lba >= max_lba) { + usb_stor_dbg(us, "Error: Requested lba %u exceeds maximum %u\n", + lba, max_lba); + result = USB_STOR_TRANSPORT_ERROR; + break; + } + + /* Find number of pages we can read in this block */ + pages = min(sectors, blocksize - page); + len = pages << pageshift; + + /* Find where this lba lives on disk */ + pba = MEDIA_INFO(us).lba_to_pba[zone][lba_offset]; + + if (pba == UNDEF) { /* this lba was never written */ + usb_stor_dbg(us, "Read %d zero pages (LBA %d) page %d\n", + pages, lba, page); + + /* This is not really an error. It just means + that the block has never been written. + Instead of returning USB_STOR_TRANSPORT_ERROR + it is better to return all zero data. */ + + memset(buffer, 0, len); + } else { + usb_stor_dbg(us, "Read %d pages, from PBA %d (LBA %d) page %d\n", + pages, pba, lba, page); + + result = alauda_read_block(us, pba, page, pages, buffer); + if (result != USB_STOR_TRANSPORT_GOOD) + break; + } + + /* Store the data in the transfer buffer */ + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &offset, TO_XFER_BUF); + + page = 0; + lba++; + sectors -= pages; + } + + kfree(buffer); + return result; +} + +/* + * Write data to a specific sector address + */ +static int alauda_write_data(struct us_data *us, unsigned long address, + unsigned int sectors) +{ + unsigned char *buffer, *blockbuffer; + unsigned int page, len, offset; + unsigned int blockshift = MEDIA_INFO(us).blockshift; + unsigned int pageshift = MEDIA_INFO(us).pageshift; + unsigned int blocksize = MEDIA_INFO(us).blocksize; + unsigned int pagesize = MEDIA_INFO(us).pagesize; + struct scatterlist *sg; + u16 lba, max_lba; + int result; + + /* + * Since we don't write the user data directly to the device, + * we have to create a bounce buffer and move the data a piece + * at a time between the bounce buffer and the actual transfer buffer. + */ + + len = min(sectors, blocksize) * pagesize; + buffer = kmalloc(len, GFP_NOIO); + if (buffer == NULL) { + printk(KERN_WARNING "alauda_write_data: Out of memory\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* + * We also need a temporary block buffer, where we read in the old data, + * overwrite parts with the new data, and manipulate the redundancy data + */ + blockbuffer = kmalloc((pagesize + 64) * blocksize, GFP_NOIO); + if (blockbuffer == NULL) { + printk(KERN_WARNING "alauda_write_data: Out of memory\n"); + kfree(buffer); + return USB_STOR_TRANSPORT_ERROR; + } + + /* Figure out the initial LBA and page */ + lba = address >> blockshift; + page = (address & MEDIA_INFO(us).blockmask); + max_lba = MEDIA_INFO(us).capacity >> (pageshift + blockshift); + + result = USB_STOR_TRANSPORT_GOOD; + offset = 0; + sg = NULL; + + while (sectors > 0) { + /* Write as many sectors as possible in this block */ + unsigned int pages = min(sectors, blocksize - page); + len = pages << pageshift; + + /* Not overflowing capacity? */ + if (lba >= max_lba) { + usb_stor_dbg(us, "Requested lba %u exceeds maximum %u\n", + lba, max_lba); + result = USB_STOR_TRANSPORT_ERROR; + break; + } + + /* Get the data from the transfer buffer */ + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &offset, FROM_XFER_BUF); + + result = alauda_write_lba(us, lba, page, pages, buffer, + blockbuffer); + if (result != USB_STOR_TRANSPORT_GOOD) + break; + + page = 0; + lba++; + sectors -= pages; + } + + kfree(buffer); + kfree(blockbuffer); + return result; +} + +/* + * Our interface with the rest of the world + */ + +static void alauda_info_destructor(void *extra) +{ + struct alauda_info *info = (struct alauda_info *) extra; + int port; + + if (!info) + return; + + for (port = 0; port < 2; port++) { + struct alauda_media_info *media_info = &info->port[port]; + + alauda_free_maps(media_info); + kfree(media_info->lba_to_pba); + kfree(media_info->pba_to_lba); + } +} + +/* + * Initialize alauda_info struct and find the data-write endpoint + */ +static int init_alauda(struct us_data *us) +{ + struct alauda_info *info; + struct usb_host_interface *altsetting = us->pusb_intf->cur_altsetting; + nand_init_ecc(); + + us->extra = kzalloc(sizeof(struct alauda_info), GFP_NOIO); + if (!us->extra) + return USB_STOR_TRANSPORT_ERROR; + + info = (struct alauda_info *) us->extra; + us->extra_destructor = alauda_info_destructor; + + info->wr_ep = usb_sndbulkpipe(us->pusb_dev, + altsetting->endpoint[0].desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + + return USB_STOR_TRANSPORT_GOOD; +} + +static int alauda_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int rc; + struct alauda_info *info = (struct alauda_info *) us->extra; + unsigned char *ptr = us->iobuf; + static unsigned char inquiry_response[36] = { + 0x00, 0x80, 0x00, 0x01, 0x1F, 0x00, 0x00, 0x00 + }; + + if (srb->cmnd[0] == INQUIRY) { + usb_stor_dbg(us, "INQUIRY - Returning bogus response\n"); + memcpy(ptr, inquiry_response, sizeof(inquiry_response)); + fill_inquiry_response(us, ptr, 36); + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == TEST_UNIT_READY) { + usb_stor_dbg(us, "TEST_UNIT_READY\n"); + return alauda_check_media(us); + } + + if (srb->cmnd[0] == READ_CAPACITY) { + unsigned int num_zones; + unsigned long capacity; + + rc = alauda_check_media(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + num_zones = MEDIA_INFO(us).capacity >> (MEDIA_INFO(us).zoneshift + + MEDIA_INFO(us).blockshift + MEDIA_INFO(us).pageshift); + + capacity = num_zones * MEDIA_INFO(us).uzonesize + * MEDIA_INFO(us).blocksize; + + /* Report capacity and page size */ + ((__be32 *) ptr)[0] = cpu_to_be32(capacity - 1); + ((__be32 *) ptr)[1] = cpu_to_be32(512); + + usb_stor_set_xfer_buf(ptr, 8, srb); + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == READ_10) { + unsigned int page, pages; + + rc = alauda_check_media(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + page = short_pack(srb->cmnd[3], srb->cmnd[2]); + page <<= 16; + page |= short_pack(srb->cmnd[5], srb->cmnd[4]); + pages = short_pack(srb->cmnd[8], srb->cmnd[7]); + + usb_stor_dbg(us, "READ_10: page %d pagect %d\n", page, pages); + + return alauda_read_data(us, page, pages); + } + + if (srb->cmnd[0] == WRITE_10) { + unsigned int page, pages; + + rc = alauda_check_media(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + page = short_pack(srb->cmnd[3], srb->cmnd[2]); + page <<= 16; + page |= short_pack(srb->cmnd[5], srb->cmnd[4]); + pages = short_pack(srb->cmnd[8], srb->cmnd[7]); + + usb_stor_dbg(us, "WRITE_10: page %d pagect %d\n", page, pages); + + return alauda_write_data(us, page, pages); + } + + if (srb->cmnd[0] == REQUEST_SENSE) { + usb_stor_dbg(us, "REQUEST_SENSE\n"); + + memset(ptr, 0, 18); + ptr[0] = 0xF0; + ptr[2] = info->sense_key; + ptr[7] = 11; + ptr[12] = info->sense_asc; + ptr[13] = info->sense_ascq; + usb_stor_set_xfer_buf(ptr, 18, srb); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) { + /* sure. whatever. not like we can stop the user from popping + the media out of the device (no locking doors, etc) */ + return USB_STOR_TRANSPORT_GOOD; + } + + usb_stor_dbg(us, "Gah! Unknown command: %d (0x%x)\n", + srb->cmnd[0], srb->cmnd[0]); + info->sense_key = 0x05; + info->sense_asc = 0x20; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; +} + +static int alauda_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - alauda_usb_ids) + alauda_unusual_dev_list); + if (result) + return result; + + us->transport_name = "Alauda Control/Bulk"; + us->transport = alauda_transport; + us->transport_reset = usb_stor_Bulk_reset; + us->max_lun = 1; + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver alauda_driver = { + .name = "ums-alauda", + .probe = alauda_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = alauda_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(alauda_driver); diff --git a/drivers/usb/storage/cypress_atacb.c b/drivers/usb/storage/cypress_atacb.c new file mode 100644 index 00000000..8514a2d8 --- /dev/null +++ b/drivers/usb/storage/cypress_atacb.c @@ -0,0 +1,290 @@ +/* + * Support for emulating SAT (ata pass through) on devices based + * on the Cypress USB/ATA bridge supporting ATACB. + * + * Copyright (c) 2008 Matthieu Castet (castet.matthieu@free.fr) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "usb.h" +#include "protocol.h" +#include "scsiglue.h" +#include "debug.h" + +MODULE_DESCRIPTION("SAT support for Cypress USB/ATA bridges with ATACB"); +MODULE_AUTHOR("Matthieu Castet "); +MODULE_LICENSE("GPL"); + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id cypress_usb_ids[] = { +# include "unusual_cypress.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, cypress_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev cypress_unusual_dev_list[] = { +# include "unusual_cypress.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + +/* + * ATACB is a protocol used on cypress usb<->ata bridge to + * send raw ATA command over mass storage + * There is a ATACB2 protocol that support LBA48 on newer chip. + * More info that be found on cy7c68310_8.pdf and cy7c68300c_8.pdf + * datasheet from cypress.com. + */ +static void cypress_atacb_passthrough(struct scsi_cmnd *srb, struct us_data *us) +{ + unsigned char save_cmnd[MAX_COMMAND_SIZE]; + + if (likely(srb->cmnd[0] != ATA_16 && srb->cmnd[0] != ATA_12)) { + usb_stor_transparent_scsi_command(srb, us); + return; + } + + memcpy(save_cmnd, srb->cmnd, sizeof(save_cmnd)); + memset(srb->cmnd, 0, MAX_COMMAND_SIZE); + + /* check if we support the command */ + if (save_cmnd[1] >> 5) /* MULTIPLE_COUNT */ + goto invalid_fld; + /* check protocol */ + switch((save_cmnd[1] >> 1) & 0xf) { + case 3: /*no DATA */ + case 4: /* PIO in */ + case 5: /* PIO out */ + break; + default: + goto invalid_fld; + } + + /* first build the ATACB command */ + srb->cmd_len = 16; + + srb->cmnd[0] = 0x24; /* bVSCBSignature : vendor-specific command + this value can change, but most(all ?) manufacturers + keep the cypress default : 0x24 */ + srb->cmnd[1] = 0x24; /* bVSCBSubCommand : 0x24 for ATACB */ + + srb->cmnd[3] = 0xff - 1; /* features, sector count, lba low, lba med + lba high, device, command are valid */ + srb->cmnd[4] = 1; /* TransferBlockCount : 512 */ + + if (save_cmnd[0] == ATA_16) { + srb->cmnd[ 6] = save_cmnd[ 4]; /* features */ + srb->cmnd[ 7] = save_cmnd[ 6]; /* sector count */ + srb->cmnd[ 8] = save_cmnd[ 8]; /* lba low */ + srb->cmnd[ 9] = save_cmnd[10]; /* lba med */ + srb->cmnd[10] = save_cmnd[12]; /* lba high */ + srb->cmnd[11] = save_cmnd[13]; /* device */ + srb->cmnd[12] = save_cmnd[14]; /* command */ + + if (save_cmnd[1] & 0x01) {/* extended bit set for LBA48 */ + /* this could be supported by atacb2 */ + if (save_cmnd[3] || save_cmnd[5] || save_cmnd[7] || save_cmnd[9] + || save_cmnd[11]) + goto invalid_fld; + } + } + else { /* ATA12 */ + srb->cmnd[ 6] = save_cmnd[3]; /* features */ + srb->cmnd[ 7] = save_cmnd[4]; /* sector count */ + srb->cmnd[ 8] = save_cmnd[5]; /* lba low */ + srb->cmnd[ 9] = save_cmnd[6]; /* lba med */ + srb->cmnd[10] = save_cmnd[7]; /* lba high */ + srb->cmnd[11] = save_cmnd[8]; /* device */ + srb->cmnd[12] = save_cmnd[9]; /* command */ + + } + /* Filter SET_FEATURES - XFER MODE command */ + if ((srb->cmnd[12] == ATA_CMD_SET_FEATURES) + && (srb->cmnd[6] == SETFEATURES_XFER)) + goto invalid_fld; + + if (srb->cmnd[12] == ATA_CMD_ID_ATA || srb->cmnd[12] == ATA_CMD_ID_ATAPI) + srb->cmnd[2] |= (1<<7); /* set IdentifyPacketDevice for these cmds */ + + + usb_stor_transparent_scsi_command(srb, us); + + /* if the device doesn't support ATACB + */ + if (srb->result == SAM_STAT_CHECK_CONDITION && + memcmp(srb->sense_buffer, usb_stor_sense_invalidCDB, + sizeof(usb_stor_sense_invalidCDB)) == 0) { + usb_stor_dbg(us, "cypress atacb not supported ???\n"); + goto end; + } + + /* if ck_cond flags is set, and there wasn't critical error, + * build the special sense + */ + if ((srb->result != (DID_ERROR << 16) && + srb->result != (DID_ABORT << 16)) && + save_cmnd[2] & 0x20) { + struct scsi_eh_save ses; + unsigned char regs[8]; + unsigned char *sb = srb->sense_buffer; + unsigned char *desc = sb + 8; + int tmp_result; + + /* build the command for + * reading the ATA registers */ + scsi_eh_prep_cmnd(srb, &ses, NULL, 0, sizeof(regs)); + + /* we use the same command as before, but we set + * the read taskfile bit, for not executing atacb command, + * but reading register selected in srb->cmnd[4] + */ + srb->cmd_len = 16; + srb->cmnd = ses.cmnd; + srb->cmnd[2] = 1; + + usb_stor_transparent_scsi_command(srb, us); + memcpy(regs, srb->sense_buffer, sizeof(regs)); + tmp_result = srb->result; + scsi_eh_restore_cmnd(srb, &ses); + /* we fail to get registers, report invalid command */ + if (tmp_result != SAM_STAT_GOOD) + goto invalid_fld; + + /* build the sense */ + memset(sb, 0, SCSI_SENSE_BUFFERSIZE); + + /* set sk, asc for a good command */ + sb[1] = RECOVERED_ERROR; + sb[2] = 0; /* ATA PASS THROUGH INFORMATION AVAILABLE */ + sb[3] = 0x1D; + + /* XXX we should generate sk, asc, ascq from status and error + * regs + * (see 11.1 Error translation ATA device error to SCSI error + * map, and ata_to_sense_error from libata.) + */ + + /* Sense data is current and format is descriptor. */ + sb[0] = 0x72; + desc[0] = 0x09; /* ATA_RETURN_DESCRIPTOR */ + + /* set length of additional sense data */ + sb[7] = 14; + desc[1] = 12; + + /* Copy registers into sense buffer. */ + desc[ 2] = 0x00; + desc[ 3] = regs[1]; /* features */ + desc[ 5] = regs[2]; /* sector count */ + desc[ 7] = regs[3]; /* lba low */ + desc[ 9] = regs[4]; /* lba med */ + desc[11] = regs[5]; /* lba high */ + desc[12] = regs[6]; /* device */ + desc[13] = regs[7]; /* command */ + + srb->result = (DRIVER_SENSE << 24) | SAM_STAT_CHECK_CONDITION; + } + goto end; +invalid_fld: + srb->result = (DRIVER_SENSE << 24) | SAM_STAT_CHECK_CONDITION; + + memcpy(srb->sense_buffer, + usb_stor_sense_invalidCDB, + sizeof(usb_stor_sense_invalidCDB)); +end: + memcpy(srb->cmnd, save_cmnd, sizeof(save_cmnd)); + if (srb->cmnd[0] == ATA_12) + srb->cmd_len = 12; +} + + +static int cypress_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + struct usb_device *device; + + result = usb_stor_probe1(&us, intf, id, + (id - cypress_usb_ids) + cypress_unusual_dev_list); + if (result) + return result; + + /* Among CY7C68300 chips, the A revision does not support Cypress ATACB + * Filter out this revision from EEPROM default descriptor values + */ + device = interface_to_usbdev(intf); + if (device->descriptor.iManufacturer != 0x38 || + device->descriptor.iProduct != 0x4e || + device->descriptor.iSerialNumber != 0x64) { + us->protocol_name = "Transparent SCSI with Cypress ATACB"; + us->proto_handler = cypress_atacb_passthrough; + } else { + us->protocol_name = "Transparent SCSI"; + us->proto_handler = usb_stor_transparent_scsi_command; + } + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver cypress_driver = { + .name = "ums-cypress", + .probe = cypress_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = cypress_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(cypress_driver); diff --git a/drivers/usb/storage/datafab.c b/drivers/usb/storage/datafab.c new file mode 100644 index 00000000..7b17c216 --- /dev/null +++ b/drivers/usb/storage/datafab.c @@ -0,0 +1,758 @@ +/* Driver for Datafab USB Compact Flash reader + * + * datafab driver v0.1: + * + * First release + * + * Current development and maintenance by: + * (c) 2000 Jimmie Mayfield (mayfield+datafab@sackheads.org) + * + * Many thanks to Robert Baruch for the SanDisk SmartMedia reader driver + * which I used as a template for this driver. + * + * Some bugfixes and scatter-gather code by Gregory P. Smith + * (greg-usb@electricrain.com) + * + * Fix for media change by Joerg Schneider (js@joergschneider.com) + * + * Other contributors: + * (c) 2002 Alan Stern + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This driver attempts to support USB CompactFlash reader/writer devices + * based on Datafab USB-to-ATA chips. It was specifically developed for the + * Datafab MDCFE-B USB CompactFlash reader but has since been found to work + * with a variety of Datafab-based devices from a number of manufacturers. + * I've received a report of this driver working with a Datafab-based + * SmartMedia device though please be aware that I'm personally unable to + * test SmartMedia support. + * + * This driver supports reading and writing. If you're truly paranoid, + * however, you can force the driver into a write-protected state by setting + * the WP enable bits in datafab_handle_mode_sense(). See the comments + * in that routine. + */ + +#include +#include +#include + +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + +MODULE_DESCRIPTION("Driver for Datafab USB Compact Flash reader"); +MODULE_AUTHOR("Jimmie Mayfield "); +MODULE_LICENSE("GPL"); + +struct datafab_info { + unsigned long sectors; /* total sector count */ + unsigned long ssize; /* sector size in bytes */ + signed char lun; /* used for dual-slot readers */ + + /* the following aren't used yet */ + unsigned char sense_key; + unsigned long sense_asc; /* additional sense code */ + unsigned long sense_ascq; /* additional sense code qualifier */ +}; + +static int datafab_determine_lun(struct us_data *us, + struct datafab_info *info); + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id datafab_usb_ids[] = { +# include "unusual_datafab.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, datafab_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev datafab_unusual_dev_list[] = { +# include "unusual_datafab.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + +static inline int +datafab_bulk_read(struct us_data *us, unsigned char *data, unsigned int len) { + if (len == 0) + return USB_STOR_XFER_GOOD; + + usb_stor_dbg(us, "len = %d\n", len); + return usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + data, len, NULL); +} + + +static inline int +datafab_bulk_write(struct us_data *us, unsigned char *data, unsigned int len) { + if (len == 0) + return USB_STOR_XFER_GOOD; + + usb_stor_dbg(us, "len = %d\n", len); + return usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + data, len, NULL); +} + + +static int datafab_read_data(struct us_data *us, + struct datafab_info *info, + u32 sector, + u32 sectors) +{ + unsigned char *command = us->iobuf; + unsigned char *buffer; + unsigned char thistime; + unsigned int totallen, alloclen; + int len, result; + unsigned int sg_offset = 0; + struct scatterlist *sg = NULL; + + // we're working in LBA mode. according to the ATA spec, + // we can support up to 28-bit addressing. I don't know if Datafab + // supports beyond 24-bit addressing. It's kind of hard to test + // since it requires > 8GB CF card. + // + if (sectors > 0x0FFFFFFF) + return USB_STOR_TRANSPORT_ERROR; + + if (info->lun == -1) { + result = datafab_determine_lun(us, info); + if (result != USB_STOR_TRANSPORT_GOOD) + return result; + } + + totallen = sectors * info->ssize; + + // Since we don't read more than 64 KB at a time, we have to create + // a bounce buffer and move the data a piece at a time between the + // bounce buffer and the actual transfer buffer. + + alloclen = min(totallen, 65536u); + buffer = kmalloc(alloclen, GFP_NOIO); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + do { + // loop, never allocate or transfer more than 64k at once + // (min(128k, 255*info->ssize) is the real limit) + + len = min(totallen, alloclen); + thistime = (len / info->ssize) & 0xff; + + command[0] = 0; + command[1] = thistime; + command[2] = sector & 0xFF; + command[3] = (sector >> 8) & 0xFF; + command[4] = (sector >> 16) & 0xFF; + + command[5] = 0xE0 + (info->lun << 4); + command[5] |= (sector >> 24) & 0x0F; + command[6] = 0x20; + command[7] = 0x01; + + // send the read command + result = datafab_bulk_write(us, command, 8); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + // read the result + result = datafab_bulk_read(us, buffer, len); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + // Store the data in the transfer buffer + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &sg_offset, TO_XFER_BUF); + + sector += thistime; + totallen -= len; + } while (totallen > 0); + + kfree(buffer); + return USB_STOR_TRANSPORT_GOOD; + + leave: + kfree(buffer); + return USB_STOR_TRANSPORT_ERROR; +} + + +static int datafab_write_data(struct us_data *us, + struct datafab_info *info, + u32 sector, + u32 sectors) +{ + unsigned char *command = us->iobuf; + unsigned char *reply = us->iobuf; + unsigned char *buffer; + unsigned char thistime; + unsigned int totallen, alloclen; + int len, result; + unsigned int sg_offset = 0; + struct scatterlist *sg = NULL; + + // we're working in LBA mode. according to the ATA spec, + // we can support up to 28-bit addressing. I don't know if Datafab + // supports beyond 24-bit addressing. It's kind of hard to test + // since it requires > 8GB CF card. + // + if (sectors > 0x0FFFFFFF) + return USB_STOR_TRANSPORT_ERROR; + + if (info->lun == -1) { + result = datafab_determine_lun(us, info); + if (result != USB_STOR_TRANSPORT_GOOD) + return result; + } + + totallen = sectors * info->ssize; + + // Since we don't write more than 64 KB at a time, we have to create + // a bounce buffer and move the data a piece at a time between the + // bounce buffer and the actual transfer buffer. + + alloclen = min(totallen, 65536u); + buffer = kmalloc(alloclen, GFP_NOIO); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + do { + // loop, never allocate or transfer more than 64k at once + // (min(128k, 255*info->ssize) is the real limit) + + len = min(totallen, alloclen); + thistime = (len / info->ssize) & 0xff; + + // Get the data from the transfer buffer + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &sg_offset, FROM_XFER_BUF); + + command[0] = 0; + command[1] = thistime; + command[2] = sector & 0xFF; + command[3] = (sector >> 8) & 0xFF; + command[4] = (sector >> 16) & 0xFF; + + command[5] = 0xE0 + (info->lun << 4); + command[5] |= (sector >> 24) & 0x0F; + command[6] = 0x30; + command[7] = 0x02; + + // send the command + result = datafab_bulk_write(us, command, 8); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + // send the data + result = datafab_bulk_write(us, buffer, len); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + // read the result + result = datafab_bulk_read(us, reply, 2); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + if (reply[0] != 0x50 && reply[1] != 0) { + usb_stor_dbg(us, "Gah! write return code: %02x %02x\n", + reply[0], reply[1]); + result = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + sector += thistime; + totallen -= len; + } while (totallen > 0); + + kfree(buffer); + return USB_STOR_TRANSPORT_GOOD; + + leave: + kfree(buffer); + return USB_STOR_TRANSPORT_ERROR; +} + + +static int datafab_determine_lun(struct us_data *us, + struct datafab_info *info) +{ + // Dual-slot readers can be thought of as dual-LUN devices. + // We need to determine which card slot is being used. + // We'll send an IDENTIFY DEVICE command and see which LUN responds... + // + // There might be a better way of doing this? + + static unsigned char scommand[8] = { 0, 1, 0, 0, 0, 0xa0, 0xec, 1 }; + unsigned char *command = us->iobuf; + unsigned char *buf; + int count = 0, rc; + + if (!info) + return USB_STOR_TRANSPORT_ERROR; + + memcpy(command, scommand, 8); + buf = kmalloc(512, GFP_NOIO); + if (!buf) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "locating...\n"); + + // we'll try 3 times before giving up... + // + while (count++ < 3) { + command[5] = 0xa0; + + rc = datafab_bulk_write(us, command, 8); + if (rc != USB_STOR_XFER_GOOD) { + rc = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + rc = datafab_bulk_read(us, buf, 512); + if (rc == USB_STOR_XFER_GOOD) { + info->lun = 0; + rc = USB_STOR_TRANSPORT_GOOD; + goto leave; + } + + command[5] = 0xb0; + + rc = datafab_bulk_write(us, command, 8); + if (rc != USB_STOR_XFER_GOOD) { + rc = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + rc = datafab_bulk_read(us, buf, 512); + if (rc == USB_STOR_XFER_GOOD) { + info->lun = 1; + rc = USB_STOR_TRANSPORT_GOOD; + goto leave; + } + + msleep(20); + } + + rc = USB_STOR_TRANSPORT_ERROR; + + leave: + kfree(buf); + return rc; +} + +static int datafab_id_device(struct us_data *us, + struct datafab_info *info) +{ + // this is a variation of the ATA "IDENTIFY DEVICE" command...according + // to the ATA spec, 'Sector Count' isn't used but the Windows driver + // sets this bit so we do too... + // + static unsigned char scommand[8] = { 0, 1, 0, 0, 0, 0xa0, 0xec, 1 }; + unsigned char *command = us->iobuf; + unsigned char *reply; + int rc; + + if (!info) + return USB_STOR_TRANSPORT_ERROR; + + if (info->lun == -1) { + rc = datafab_determine_lun(us, info); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + } + + memcpy(command, scommand, 8); + reply = kmalloc(512, GFP_NOIO); + if (!reply) + return USB_STOR_TRANSPORT_ERROR; + + command[5] += (info->lun << 4); + + rc = datafab_bulk_write(us, command, 8); + if (rc != USB_STOR_XFER_GOOD) { + rc = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + // we'll go ahead and extract the media capacity while we're here... + // + rc = datafab_bulk_read(us, reply, 512); + if (rc == USB_STOR_XFER_GOOD) { + // capacity is at word offset 57-58 + // + info->sectors = ((u32)(reply[117]) << 24) | + ((u32)(reply[116]) << 16) | + ((u32)(reply[115]) << 8) | + ((u32)(reply[114]) ); + rc = USB_STOR_TRANSPORT_GOOD; + goto leave; + } + + rc = USB_STOR_TRANSPORT_ERROR; + + leave: + kfree(reply); + return rc; +} + + +static int datafab_handle_mode_sense(struct us_data *us, + struct scsi_cmnd * srb, + int sense_6) +{ + static unsigned char rw_err_page[12] = { + 0x1, 0xA, 0x21, 1, 0, 0, 0, 0, 1, 0, 0, 0 + }; + static unsigned char cache_page[12] = { + 0x8, 0xA, 0x1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + static unsigned char rbac_page[12] = { + 0x1B, 0xA, 0, 0x81, 0, 0, 0, 0, 0, 0, 0, 0 + }; + static unsigned char timer_page[8] = { + 0x1C, 0x6, 0, 0, 0, 0 + }; + unsigned char pc, page_code; + unsigned int i = 0; + struct datafab_info *info = (struct datafab_info *) (us->extra); + unsigned char *ptr = us->iobuf; + + // most of this stuff is just a hack to get things working. the + // datafab reader doesn't present a SCSI interface so we + // fudge the SCSI commands... + // + + pc = srb->cmnd[2] >> 6; + page_code = srb->cmnd[2] & 0x3F; + + switch (pc) { + case 0x0: + usb_stor_dbg(us, "Current values\n"); + break; + case 0x1: + usb_stor_dbg(us, "Changeable values\n"); + break; + case 0x2: + usb_stor_dbg(us, "Default values\n"); + break; + case 0x3: + usb_stor_dbg(us, "Saves values\n"); + break; + } + + memset(ptr, 0, 8); + if (sense_6) { + ptr[2] = 0x00; // WP enable: 0x80 + i = 4; + } else { + ptr[3] = 0x00; // WP enable: 0x80 + i = 8; + } + + switch (page_code) { + default: + // vendor-specific mode + info->sense_key = 0x05; + info->sense_asc = 0x24; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; + + case 0x1: + memcpy(ptr + i, rw_err_page, sizeof(rw_err_page)); + i += sizeof(rw_err_page); + break; + + case 0x8: + memcpy(ptr + i, cache_page, sizeof(cache_page)); + i += sizeof(cache_page); + break; + + case 0x1B: + memcpy(ptr + i, rbac_page, sizeof(rbac_page)); + i += sizeof(rbac_page); + break; + + case 0x1C: + memcpy(ptr + i, timer_page, sizeof(timer_page)); + i += sizeof(timer_page); + break; + + case 0x3F: // retrieve all pages + memcpy(ptr + i, timer_page, sizeof(timer_page)); + i += sizeof(timer_page); + memcpy(ptr + i, rbac_page, sizeof(rbac_page)); + i += sizeof(rbac_page); + memcpy(ptr + i, cache_page, sizeof(cache_page)); + i += sizeof(cache_page); + memcpy(ptr + i, rw_err_page, sizeof(rw_err_page)); + i += sizeof(rw_err_page); + break; + } + + if (sense_6) + ptr[0] = i - 1; + else + ((__be16 *) ptr)[0] = cpu_to_be16(i - 2); + usb_stor_set_xfer_buf(ptr, i, srb); + + return USB_STOR_TRANSPORT_GOOD; +} + +static void datafab_info_destructor(void *extra) +{ + // this routine is a placeholder... + // currently, we don't allocate any extra memory so we're okay +} + + +// Transport for the Datafab MDCFE-B +// +static int datafab_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + struct datafab_info *info; + int rc; + unsigned long block, blocks; + unsigned char *ptr = us->iobuf; + static unsigned char inquiry_reply[8] = { + 0x00, 0x80, 0x00, 0x01, 0x1F, 0x00, 0x00, 0x00 + }; + + if (!us->extra) { + us->extra = kzalloc(sizeof(struct datafab_info), GFP_NOIO); + if (!us->extra) + return USB_STOR_TRANSPORT_ERROR; + + us->extra_destructor = datafab_info_destructor; + ((struct datafab_info *)us->extra)->lun = -1; + } + + info = (struct datafab_info *) (us->extra); + + if (srb->cmnd[0] == INQUIRY) { + usb_stor_dbg(us, "INQUIRY - Returning bogus response\n"); + memcpy(ptr, inquiry_reply, sizeof(inquiry_reply)); + fill_inquiry_response(us, ptr, 36); + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == READ_CAPACITY) { + info->ssize = 0x200; // hard coded 512 byte sectors as per ATA spec + rc = datafab_id_device(us, info); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + usb_stor_dbg(us, "READ_CAPACITY: %ld sectors, %ld bytes per sector\n", + info->sectors, info->ssize); + + // build the reply + // we need the last sector, not the number of sectors + ((__be32 *) ptr)[0] = cpu_to_be32(info->sectors - 1); + ((__be32 *) ptr)[1] = cpu_to_be32(info->ssize); + usb_stor_set_xfer_buf(ptr, 8, srb); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == MODE_SELECT_10) { + usb_stor_dbg(us, "Gah! MODE_SELECT_10\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + // don't bother implementing READ_6 or WRITE_6. + // + if (srb->cmnd[0] == READ_10) { + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[7]) << 8) | ((u32)(srb->cmnd[8])); + + usb_stor_dbg(us, "READ_10: read block 0x%04lx count %ld\n", + block, blocks); + return datafab_read_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == READ_12) { + // we'll probably never see a READ_12 but we'll do it anyway... + // + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[6]) << 24) | ((u32)(srb->cmnd[7]) << 16) | + ((u32)(srb->cmnd[8]) << 8) | ((u32)(srb->cmnd[9])); + + usb_stor_dbg(us, "READ_12: read block 0x%04lx count %ld\n", + block, blocks); + return datafab_read_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == WRITE_10) { + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[7]) << 8) | ((u32)(srb->cmnd[8])); + + usb_stor_dbg(us, "WRITE_10: write block 0x%04lx count %ld\n", + block, blocks); + return datafab_write_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == WRITE_12) { + // we'll probably never see a WRITE_12 but we'll do it anyway... + // + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[6]) << 24) | ((u32)(srb->cmnd[7]) << 16) | + ((u32)(srb->cmnd[8]) << 8) | ((u32)(srb->cmnd[9])); + + usb_stor_dbg(us, "WRITE_12: write block 0x%04lx count %ld\n", + block, blocks); + return datafab_write_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == TEST_UNIT_READY) { + usb_stor_dbg(us, "TEST_UNIT_READY\n"); + return datafab_id_device(us, info); + } + + if (srb->cmnd[0] == REQUEST_SENSE) { + usb_stor_dbg(us, "REQUEST_SENSE - Returning faked response\n"); + + // this response is pretty bogus right now. eventually if necessary + // we can set the correct sense data. so far though it hasn't been + // necessary + // + memset(ptr, 0, 18); + ptr[0] = 0xF0; + ptr[2] = info->sense_key; + ptr[7] = 11; + ptr[12] = info->sense_asc; + ptr[13] = info->sense_ascq; + usb_stor_set_xfer_buf(ptr, 18, srb); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == MODE_SENSE) { + usb_stor_dbg(us, "MODE_SENSE_6 detected\n"); + return datafab_handle_mode_sense(us, srb, 1); + } + + if (srb->cmnd[0] == MODE_SENSE_10) { + usb_stor_dbg(us, "MODE_SENSE_10 detected\n"); + return datafab_handle_mode_sense(us, srb, 0); + } + + if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) { + // sure. whatever. not like we can stop the user from + // popping the media out of the device (no locking doors, etc) + // + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == START_STOP) { + /* this is used by sd.c'check_scsidisk_media_change to detect + media change */ + usb_stor_dbg(us, "START_STOP\n"); + /* the first datafab_id_device after a media change returns + an error (determined experimentally) */ + rc = datafab_id_device(us, info); + if (rc == USB_STOR_TRANSPORT_GOOD) { + info->sense_key = NO_SENSE; + srb->result = SUCCESS; + } else { + info->sense_key = UNIT_ATTENTION; + srb->result = SAM_STAT_CHECK_CONDITION; + } + return rc; + } + + usb_stor_dbg(us, "Gah! Unknown command: %d (0x%x)\n", + srb->cmnd[0], srb->cmnd[0]); + info->sense_key = 0x05; + info->sense_asc = 0x20; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; +} + +static int datafab_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - datafab_usb_ids) + datafab_unusual_dev_list); + if (result) + return result; + + us->transport_name = "Datafab Bulk-Only"; + us->transport = datafab_transport; + us->transport_reset = usb_stor_Bulk_reset; + us->max_lun = 1; + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver datafab_driver = { + .name = "ums-datafab", + .probe = datafab_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = datafab_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(datafab_driver); diff --git a/drivers/usb/storage/debug.c b/drivers/usb/storage/debug.c new file mode 100644 index 00000000..e08f6478 --- /dev/null +++ b/drivers/usb/storage/debug.c @@ -0,0 +1,195 @@ +/* Driver for USB Mass Storage compliant devices + * Debugging Functions Source Code File + * + * Current development and maintenance by: + * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Developed with the assistance of: + * (c) 2002 Alan Stern + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "usb.h" +#include "debug.h" +#include "scsi.h" + + +void usb_stor_show_command(const struct us_data *us, struct scsi_cmnd *srb) +{ + char *what = NULL; + int i; + + switch (srb->cmnd[0]) { + case TEST_UNIT_READY: what = "TEST_UNIT_READY"; break; + case REZERO_UNIT: what = "REZERO_UNIT"; break; + case REQUEST_SENSE: what = "REQUEST_SENSE"; break; + case FORMAT_UNIT: what = "FORMAT_UNIT"; break; + case READ_BLOCK_LIMITS: what = "READ_BLOCK_LIMITS"; break; + case REASSIGN_BLOCKS: what = "REASSIGN_BLOCKS"; break; + case READ_6: what = "READ_6"; break; + case WRITE_6: what = "WRITE_6"; break; + case SEEK_6: what = "SEEK_6"; break; + case READ_REVERSE: what = "READ_REVERSE"; break; + case WRITE_FILEMARKS: what = "WRITE_FILEMARKS"; break; + case SPACE: what = "SPACE"; break; + case INQUIRY: what = "INQUIRY"; break; + case RECOVER_BUFFERED_DATA: what = "RECOVER_BUFFERED_DATA"; break; + case MODE_SELECT: what = "MODE_SELECT"; break; + case RESERVE: what = "RESERVE"; break; + case RELEASE: what = "RELEASE"; break; + case COPY: what = "COPY"; break; + case ERASE: what = "ERASE"; break; + case MODE_SENSE: what = "MODE_SENSE"; break; + case START_STOP: what = "START_STOP"; break; + case RECEIVE_DIAGNOSTIC: what = "RECEIVE_DIAGNOSTIC"; break; + case SEND_DIAGNOSTIC: what = "SEND_DIAGNOSTIC"; break; + case ALLOW_MEDIUM_REMOVAL: what = "ALLOW_MEDIUM_REMOVAL"; break; + case SET_WINDOW: what = "SET_WINDOW"; break; + case READ_CAPACITY: what = "READ_CAPACITY"; break; + case READ_10: what = "READ_10"; break; + case WRITE_10: what = "WRITE_10"; break; + case SEEK_10: what = "SEEK_10"; break; + case WRITE_VERIFY: what = "WRITE_VERIFY"; break; + case VERIFY: what = "VERIFY"; break; + case SEARCH_HIGH: what = "SEARCH_HIGH"; break; + case SEARCH_EQUAL: what = "SEARCH_EQUAL"; break; + case SEARCH_LOW: what = "SEARCH_LOW"; break; + case SET_LIMITS: what = "SET_LIMITS"; break; + case READ_POSITION: what = "READ_POSITION"; break; + case SYNCHRONIZE_CACHE: what = "SYNCHRONIZE_CACHE"; break; + case LOCK_UNLOCK_CACHE: what = "LOCK_UNLOCK_CACHE"; break; + case READ_DEFECT_DATA: what = "READ_DEFECT_DATA"; break; + case MEDIUM_SCAN: what = "MEDIUM_SCAN"; break; + case COMPARE: what = "COMPARE"; break; + case COPY_VERIFY: what = "COPY_VERIFY"; break; + case WRITE_BUFFER: what = "WRITE_BUFFER"; break; + case READ_BUFFER: what = "READ_BUFFER"; break; + case UPDATE_BLOCK: what = "UPDATE_BLOCK"; break; + case READ_LONG: what = "READ_LONG"; break; + case WRITE_LONG: what = "WRITE_LONG"; break; + case CHANGE_DEFINITION: what = "CHANGE_DEFINITION"; break; + case WRITE_SAME: what = "WRITE_SAME"; break; + case GPCMD_READ_SUBCHANNEL: what = "READ SUBCHANNEL"; break; + case READ_TOC: what = "READ_TOC"; break; + case GPCMD_READ_HEADER: what = "READ HEADER"; break; + case GPCMD_PLAY_AUDIO_10: what = "PLAY AUDIO (10)"; break; + case GPCMD_PLAY_AUDIO_MSF: what = "PLAY AUDIO MSF"; break; + case GPCMD_GET_EVENT_STATUS_NOTIFICATION: + what = "GET EVENT/STATUS NOTIFICATION"; break; + case GPCMD_PAUSE_RESUME: what = "PAUSE/RESUME"; break; + case LOG_SELECT: what = "LOG_SELECT"; break; + case LOG_SENSE: what = "LOG_SENSE"; break; + case GPCMD_STOP_PLAY_SCAN: what = "STOP PLAY/SCAN"; break; + case GPCMD_READ_DISC_INFO: what = "READ DISC INFORMATION"; break; + case GPCMD_READ_TRACK_RZONE_INFO: + what = "READ TRACK INFORMATION"; break; + case GPCMD_RESERVE_RZONE_TRACK: what = "RESERVE TRACK"; break; + case GPCMD_SEND_OPC: what = "SEND OPC"; break; + case MODE_SELECT_10: what = "MODE_SELECT_10"; break; + case GPCMD_REPAIR_RZONE_TRACK: what = "REPAIR TRACK"; break; + case 0x59: what = "READ MASTER CUE"; break; + case MODE_SENSE_10: what = "MODE_SENSE_10"; break; + case GPCMD_CLOSE_TRACK: what = "CLOSE TRACK/SESSION"; break; + case 0x5C: what = "READ BUFFER CAPACITY"; break; + case 0x5D: what = "SEND CUE SHEET"; break; + case GPCMD_BLANK: what = "BLANK"; break; + case REPORT_LUNS: what = "REPORT LUNS"; break; + case MOVE_MEDIUM: what = "MOVE_MEDIUM or PLAY AUDIO (12)"; break; + case READ_12: what = "READ_12"; break; + case WRITE_12: what = "WRITE_12"; break; + case WRITE_VERIFY_12: what = "WRITE_VERIFY_12"; break; + case SEARCH_HIGH_12: what = "SEARCH_HIGH_12"; break; + case SEARCH_EQUAL_12: what = "SEARCH_EQUAL_12"; break; + case SEARCH_LOW_12: what = "SEARCH_LOW_12"; break; + case SEND_VOLUME_TAG: what = "SEND_VOLUME_TAG"; break; + case READ_ELEMENT_STATUS: what = "READ_ELEMENT_STATUS"; break; + case GPCMD_READ_CD_MSF: what = "READ CD MSF"; break; + case GPCMD_SCAN: what = "SCAN"; break; + case GPCMD_SET_SPEED: what = "SET CD SPEED"; break; + case GPCMD_MECHANISM_STATUS: what = "MECHANISM STATUS"; break; + case GPCMD_READ_CD: what = "READ CD"; break; + case 0xE1: what = "WRITE CONTINUE"; break; + case WRITE_LONG_2: what = "WRITE_LONG_2"; break; + default: what = "(unknown command)"; break; + } + usb_stor_dbg(us, "Command %s (%d bytes)\n", what, srb->cmd_len); + usb_stor_dbg(us, "bytes: "); + for (i = 0; i < srb->cmd_len && i < 16; i++) + US_DEBUGPX(" %02x", srb->cmnd[i]); + US_DEBUGPX("\n"); +} + +void usb_stor_show_sense(const struct us_data *us, + unsigned char key, + unsigned char asc, + unsigned char ascq) +{ + const char *what, *keystr; + + keystr = scsi_sense_key_string(key); + what = scsi_extd_sense_format(asc, ascq); + + if (keystr == NULL) + keystr = "(Unknown Key)"; + if (what == NULL) + what = "(unknown ASC/ASCQ)"; + + usb_stor_dbg(us, "%s: ", keystr); + US_DEBUGPX(what, ascq); + US_DEBUGPX("\n"); +} + +int usb_stor_dbg(const struct us_data *us, const char *fmt, ...) +{ + va_list args; + int r; + + va_start(args, fmt); + + r = dev_vprintk_emit(7, &us->pusb_dev->dev, fmt, args); + + va_end(args); + + return r; +} +EXPORT_SYMBOL_GPL(usb_stor_dbg); diff --git a/drivers/usb/storage/debug.h b/drivers/usb/storage/debug.h new file mode 100644 index 00000000..b1273f03 --- /dev/null +++ b/drivers/usb/storage/debug.h @@ -0,0 +1,69 @@ +/* Driver for USB Mass Storage compliant devices + * Debugging Functions Header File + * + * Current development and maintenance by: + * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + +#include + +#define USB_STORAGE "usb-storage: " + +#ifdef CONFIG_USB_STORAGE_DEBUG +void usb_stor_show_command(const struct us_data *us, struct scsi_cmnd *srb); +void usb_stor_show_sense(const struct us_data *us, unsigned char key, + unsigned char asc, unsigned char ascq); +__printf(2, 3) int usb_stor_dbg(const struct us_data *us, + const char *fmt, ...); + +#define US_DEBUGPX(fmt, ...) printk(fmt, ##__VA_ARGS__) +#define US_DEBUG(x) x +#else +__printf(2, 3) +static inline int _usb_stor_dbg(const struct us_data *us, + const char *fmt, ...) {return 1;} +#define usb_stor_dbg(us, fmt, ...) \ + do { if (0) _usb_stor_dbg(us, fmt, ##__VA_ARGS__); } while (0) +#define US_DEBUGPX(fmt, ...) \ + do { if (0) printk(fmt, ##__VA_ARGS__); } while (0) +#define US_DEBUG(x) +#endif + +#endif diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c new file mode 100644 index 00000000..1bfc9a6c --- /dev/null +++ b/drivers/usb/storage/ene_ub6250.c @@ -0,0 +1,2421 @@ +/* + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include + +#include +#include + +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + +#define SD_INIT1_FIRMWARE "ene-ub6250/sd_init1.bin" +#define SD_INIT2_FIRMWARE "ene-ub6250/sd_init2.bin" +#define SD_RW_FIRMWARE "ene-ub6250/sd_rdwr.bin" +#define MS_INIT_FIRMWARE "ene-ub6250/ms_init.bin" +#define MSP_RW_FIRMWARE "ene-ub6250/msp_rdwr.bin" +#define MS_RW_FIRMWARE "ene-ub6250/ms_rdwr.bin" + +MODULE_DESCRIPTION("Driver for ENE UB6250 reader"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(SD_INIT1_FIRMWARE); +MODULE_FIRMWARE(SD_INIT2_FIRMWARE); +MODULE_FIRMWARE(SD_RW_FIRMWARE); +MODULE_FIRMWARE(MS_INIT_FIRMWARE); +MODULE_FIRMWARE(MSP_RW_FIRMWARE); +MODULE_FIRMWARE(MS_RW_FIRMWARE); + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags)} + +static struct usb_device_id ene_ub6250_usb_ids[] = { +# include "unusual_ene_ub6250.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, ene_ub6250_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev ene_ub6250_unusual_dev_list[] = { +# include "unusual_ene_ub6250.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + + +/* ENE bin code len */ +#define ENE_BIN_CODE_LEN 0x800 +/* EnE HW Register */ +#define REG_CARD_STATUS 0xFF83 +#define REG_HW_TRAP1 0xFF89 + +/* SRB Status */ +#define SS_SUCCESS 0x00 /* No Sense */ +#define SS_NOT_READY 0x02 +#define SS_MEDIUM_ERR 0x03 +#define SS_HW_ERR 0x04 +#define SS_ILLEGAL_REQUEST 0x05 +#define SS_UNIT_ATTENTION 0x06 + +/* ENE Load FW Pattern */ +#define SD_INIT1_PATTERN 1 +#define SD_INIT2_PATTERN 2 +#define SD_RW_PATTERN 3 +#define MS_INIT_PATTERN 4 +#define MSP_RW_PATTERN 5 +#define MS_RW_PATTERN 6 +#define SM_INIT_PATTERN 7 +#define SM_RW_PATTERN 8 + +#define FDIR_WRITE 0 +#define FDIR_READ 1 + +/* For MS Card */ + +/* Status Register 1 */ +#define MS_REG_ST1_MB 0x80 /* media busy */ +#define MS_REG_ST1_FB1 0x40 /* flush busy 1 */ +#define MS_REG_ST1_DTER 0x20 /* error on data(corrected) */ +#define MS_REG_ST1_UCDT 0x10 /* unable to correct data */ +#define MS_REG_ST1_EXER 0x08 /* error on extra(corrected) */ +#define MS_REG_ST1_UCEX 0x04 /* unable to correct extra */ +#define MS_REG_ST1_FGER 0x02 /* error on overwrite flag(corrected) */ +#define MS_REG_ST1_UCFG 0x01 /* unable to correct overwrite flag */ +#define MS_REG_ST1_DEFAULT (MS_REG_ST1_MB | MS_REG_ST1_FB1 | MS_REG_ST1_DTER | MS_REG_ST1_UCDT | MS_REG_ST1_EXER | MS_REG_ST1_UCEX | MS_REG_ST1_FGER | MS_REG_ST1_UCFG) + +/* Overwrite Area */ +#define MS_REG_OVR_BKST 0x80 /* block status */ +#define MS_REG_OVR_BKST_OK MS_REG_OVR_BKST /* OK */ +#define MS_REG_OVR_BKST_NG 0x00 /* NG */ +#define MS_REG_OVR_PGST0 0x40 /* page status */ +#define MS_REG_OVR_PGST1 0x20 +#define MS_REG_OVR_PGST_MASK (MS_REG_OVR_PGST0 | MS_REG_OVR_PGST1) +#define MS_REG_OVR_PGST_OK (MS_REG_OVR_PGST0 | MS_REG_OVR_PGST1) /* OK */ +#define MS_REG_OVR_PGST_NG MS_REG_OVR_PGST1 /* NG */ +#define MS_REG_OVR_PGST_DATA_ERROR 0x00 /* data error */ +#define MS_REG_OVR_UDST 0x10 /* update status */ +#define MS_REG_OVR_UDST_UPDATING 0x00 /* updating */ +#define MS_REG_OVR_UDST_NO_UPDATE MS_REG_OVR_UDST +#define MS_REG_OVR_RESERVED 0x08 +#define MS_REG_OVR_DEFAULT (MS_REG_OVR_BKST_OK | MS_REG_OVR_PGST_OK | MS_REG_OVR_UDST_NO_UPDATE | MS_REG_OVR_RESERVED) + +/* Management Flag */ +#define MS_REG_MNG_SCMS0 0x20 /* serial copy management system */ +#define MS_REG_MNG_SCMS1 0x10 +#define MS_REG_MNG_SCMS_MASK (MS_REG_MNG_SCMS0 | MS_REG_MNG_SCMS1) +#define MS_REG_MNG_SCMS_COPY_OK (MS_REG_MNG_SCMS0 | MS_REG_MNG_SCMS1) +#define MS_REG_MNG_SCMS_ONE_COPY MS_REG_MNG_SCMS1 +#define MS_REG_MNG_SCMS_NO_COPY 0x00 +#define MS_REG_MNG_ATFLG 0x08 /* address transfer table flag */ +#define MS_REG_MNG_ATFLG_OTHER MS_REG_MNG_ATFLG /* other */ +#define MS_REG_MNG_ATFLG_ATTBL 0x00 /* address transfer table */ +#define MS_REG_MNG_SYSFLG 0x04 /* system flag */ +#define MS_REG_MNG_SYSFLG_USER MS_REG_MNG_SYSFLG /* user block */ +#define MS_REG_MNG_SYSFLG_BOOT 0x00 /* system block */ +#define MS_REG_MNG_RESERVED 0xc3 +#define MS_REG_MNG_DEFAULT (MS_REG_MNG_SCMS_COPY_OK | MS_REG_MNG_ATFLG_OTHER | MS_REG_MNG_SYSFLG_USER | MS_REG_MNG_RESERVED) + + +#define MS_MAX_PAGES_PER_BLOCK 32 +#define MS_MAX_INITIAL_ERROR_BLOCKS 10 +#define MS_LIB_BITS_PER_BYTE 8 + +#define MS_SYSINF_FORMAT_FAT 1 +#define MS_SYSINF_USAGE_GENERAL 0 + +#define MS_SYSINF_MSCLASS_TYPE_1 1 +#define MS_SYSINF_PAGE_SIZE MS_BYTES_PER_PAGE /* fixed */ + +#define MS_SYSINF_CARDTYPE_RDONLY 1 +#define MS_SYSINF_CARDTYPE_RDWR 2 +#define MS_SYSINF_CARDTYPE_HYBRID 3 +#define MS_SYSINF_SECURITY 0x01 +#define MS_SYSINF_SECURITY_NO_SUPPORT MS_SYSINF_SECURITY +#define MS_SYSINF_SECURITY_SUPPORT 0 + +#define MS_SYSINF_RESERVED1 1 +#define MS_SYSINF_RESERVED2 1 + +#define MS_SYSENT_TYPE_INVALID_BLOCK 0x01 +#define MS_SYSENT_TYPE_CIS_IDI 0x0a /* CIS/IDI */ + +#define SIZE_OF_KIRO 1024 +#define BYTE_MASK 0xff + +/* ms error code */ +#define MS_STATUS_WRITE_PROTECT 0x0106 +#define MS_STATUS_SUCCESS 0x0000 +#define MS_ERROR_FLASH_READ 0x8003 +#define MS_ERROR_FLASH_ERASE 0x8005 +#define MS_LB_ERROR 0xfff0 +#define MS_LB_BOOT_BLOCK 0xfff1 +#define MS_LB_INITIAL_ERROR 0xfff2 +#define MS_STATUS_SUCCESS_WITH_ECC 0xfff3 +#define MS_LB_ACQUIRED_ERROR 0xfff4 +#define MS_LB_NOT_USED_ERASED 0xfff5 +#define MS_NOCARD_ERROR 0xfff8 +#define MS_NO_MEMORY_ERROR 0xfff9 +#define MS_STATUS_INT_ERROR 0xfffa +#define MS_STATUS_ERROR 0xfffe +#define MS_LB_NOT_USED 0xffff + +#define MS_REG_MNG_SYSFLG 0x04 /* system flag */ +#define MS_REG_MNG_SYSFLG_USER MS_REG_MNG_SYSFLG /* user block */ + +#define MS_BOOT_BLOCK_ID 0x0001 +#define MS_BOOT_BLOCK_FORMAT_VERSION 0x0100 +#define MS_BOOT_BLOCK_DATA_ENTRIES 2 + +#define MS_NUMBER_OF_SYSTEM_ENTRY 4 +#define MS_NUMBER_OF_BOOT_BLOCK 2 +#define MS_BYTES_PER_PAGE 512 +#define MS_LOGICAL_BLOCKS_PER_SEGMENT 496 +#define MS_LOGICAL_BLOCKS_IN_1ST_SEGMENT 494 + +#define MS_PHYSICAL_BLOCKS_PER_SEGMENT 0x200 /* 512 */ +#define MS_PHYSICAL_BLOCKS_PER_SEGMENT_MASK 0x1ff + +/* overwrite area */ +#define MS_REG_OVR_BKST 0x80 /* block status */ +#define MS_REG_OVR_BKST_OK MS_REG_OVR_BKST /* OK */ +#define MS_REG_OVR_BKST_NG 0x00 /* NG */ + +/* Status Register 1 */ +#define MS_REG_ST1_DTER 0x20 /* error on data(corrected) */ +#define MS_REG_ST1_EXER 0x08 /* error on extra(corrected) */ +#define MS_REG_ST1_FGER 0x02 /* error on overwrite flag(corrected) */ + +/* MemoryStick Register */ +/* Status Register 0 */ +#define MS_REG_ST0_WP 0x01 /* write protected */ +#define MS_REG_ST0_WP_ON MS_REG_ST0_WP + +#define MS_LIB_CTRL_RDONLY 0 +#define MS_LIB_CTRL_WRPROTECT 1 + +/*dphy->log table */ +#define ms_libconv_to_logical(pdx, PhyBlock) (((PhyBlock) >= (pdx)->MS_Lib.NumberOfPhyBlock) ? MS_STATUS_ERROR : (pdx)->MS_Lib.Phy2LogMap[PhyBlock]) +#define ms_libconv_to_physical(pdx, LogBlock) (((LogBlock) >= (pdx)->MS_Lib.NumberOfLogBlock) ? MS_STATUS_ERROR : (pdx)->MS_Lib.Log2PhyMap[LogBlock]) + +#define ms_lib_ctrl_set(pdx, Flag) ((pdx)->MS_Lib.flags |= (1 << (Flag))) +#define ms_lib_ctrl_reset(pdx, Flag) ((pdx)->MS_Lib.flags &= ~(1 << (Flag))) +#define ms_lib_ctrl_check(pdx, Flag) ((pdx)->MS_Lib.flags & (1 << (Flag))) + +#define ms_lib_iswritable(pdx) ((ms_lib_ctrl_check((pdx), MS_LIB_CTRL_RDONLY) == 0) && (ms_lib_ctrl_check(pdx, MS_LIB_CTRL_WRPROTECT) == 0)) +#define ms_lib_clear_pagemap(pdx) memset((pdx)->MS_Lib.pagemap, 0, sizeof((pdx)->MS_Lib.pagemap)) +#define memstick_logaddr(logadr1, logadr0) ((((u16)(logadr1)) << 8) | (logadr0)) + + +struct SD_STATUS { + u8 Insert:1; + u8 Ready:1; + u8 MediaChange:1; + u8 IsMMC:1; + u8 HiCapacity:1; + u8 HiSpeed:1; + u8 WtP:1; + u8 Reserved:1; +}; + +struct MS_STATUS { + u8 Insert:1; + u8 Ready:1; + u8 MediaChange:1; + u8 IsMSPro:1; + u8 IsMSPHG:1; + u8 Reserved1:1; + u8 WtP:1; + u8 Reserved2:1; +}; + +struct SM_STATUS { + u8 Insert:1; + u8 Ready:1; + u8 MediaChange:1; + u8 Reserved:3; + u8 WtP:1; + u8 IsMS:1; +}; + +struct ms_bootblock_cis { + u8 bCistplDEVICE[6]; /* 0 */ + u8 bCistplDEVICE0C[6]; /* 6 */ + u8 bCistplJEDECC[4]; /* 12 */ + u8 bCistplMANFID[6]; /* 16 */ + u8 bCistplVER1[32]; /* 22 */ + u8 bCistplFUNCID[4]; /* 54 */ + u8 bCistplFUNCE0[4]; /* 58 */ + u8 bCistplFUNCE1[5]; /* 62 */ + u8 bCistplCONF[7]; /* 67 */ + u8 bCistplCFTBLENT0[10];/* 74 */ + u8 bCistplCFTBLENT1[8]; /* 84 */ + u8 bCistplCFTBLENT2[12];/* 92 */ + u8 bCistplCFTBLENT3[8]; /* 104 */ + u8 bCistplCFTBLENT4[17];/* 112 */ + u8 bCistplCFTBLENT5[8]; /* 129 */ + u8 bCistplCFTBLENT6[17];/* 137 */ + u8 bCistplCFTBLENT7[8]; /* 154 */ + u8 bCistplNOLINK[3]; /* 162 */ +} ; + +struct ms_bootblock_idi { +#define MS_IDI_GENERAL_CONF 0x848A + u16 wIDIgeneralConfiguration; /* 0 */ + u16 wIDInumberOfCylinder; /* 1 */ + u16 wIDIreserved0; /* 2 */ + u16 wIDInumberOfHead; /* 3 */ + u16 wIDIbytesPerTrack; /* 4 */ + u16 wIDIbytesPerSector; /* 5 */ + u16 wIDIsectorsPerTrack; /* 6 */ + u16 wIDItotalSectors[2]; /* 7-8 high,low */ + u16 wIDIreserved1[11]; /* 9-19 */ + u16 wIDIbufferType; /* 20 */ + u16 wIDIbufferSize; /* 21 */ + u16 wIDIlongCmdECC; /* 22 */ + u16 wIDIfirmVersion[4]; /* 23-26 */ + u16 wIDImodelName[20]; /* 27-46 */ + u16 wIDIreserved2; /* 47 */ + u16 wIDIlongWordSupported; /* 48 */ + u16 wIDIdmaSupported; /* 49 */ + u16 wIDIreserved3; /* 50 */ + u16 wIDIpioTiming; /* 51 */ + u16 wIDIdmaTiming; /* 52 */ + u16 wIDItransferParameter; /* 53 */ + u16 wIDIformattedCylinder; /* 54 */ + u16 wIDIformattedHead; /* 55 */ + u16 wIDIformattedSectorsPerTrack;/* 56 */ + u16 wIDIformattedTotalSectors[2];/* 57-58 */ + u16 wIDImultiSector; /* 59 */ + u16 wIDIlbaSectors[2]; /* 60-61 */ + u16 wIDIsingleWordDMA; /* 62 */ + u16 wIDImultiWordDMA; /* 63 */ + u16 wIDIreserved4[192]; /* 64-255 */ +}; + +struct ms_bootblock_sysent_rec { + u32 dwStart; + u32 dwSize; + u8 bType; + u8 bReserved[3]; +}; + +struct ms_bootblock_sysent { + struct ms_bootblock_sysent_rec entry[MS_NUMBER_OF_SYSTEM_ENTRY]; +}; + +struct ms_bootblock_sysinf { + u8 bMsClass; /* must be 1 */ + u8 bCardType; /* see below */ + u16 wBlockSize; /* n KB */ + u16 wBlockNumber; /* number of physical block */ + u16 wTotalBlockNumber; /* number of logical block */ + u16 wPageSize; /* must be 0x200 */ + u8 bExtraSize; /* 0x10 */ + u8 bSecuritySupport; + u8 bAssemblyDate[8]; + u8 bFactoryArea[4]; + u8 bAssemblyMakerCode; + u8 bAssemblyMachineCode[3]; + u16 wMemoryMakerCode; + u16 wMemoryDeviceCode; + u16 wMemorySize; + u8 bReserved1; + u8 bReserved2; + u8 bVCC; + u8 bVPP; + u16 wControllerChipNumber; + u16 wControllerFunction; /* New MS */ + u8 bReserved3[9]; /* New MS */ + u8 bParallelSupport; /* New MS */ + u16 wFormatValue; /* New MS */ + u8 bFormatType; + u8 bUsage; + u8 bDeviceType; + u8 bReserved4[22]; + u8 bFUValue3; + u8 bFUValue4; + u8 bReserved5[15]; +}; + +struct ms_bootblock_header { + u16 wBlockID; + u16 wFormatVersion; + u8 bReserved1[184]; + u8 bNumberOfDataEntry; + u8 bReserved2[179]; +}; + +struct ms_bootblock_page0 { + struct ms_bootblock_header header; + struct ms_bootblock_sysent sysent; + struct ms_bootblock_sysinf sysinf; +}; + +struct ms_bootblock_cis_idi { + union { + struct ms_bootblock_cis cis; + u8 dmy[256]; + } cis; + + union { + struct ms_bootblock_idi idi; + u8 dmy[256]; + } idi; + +}; + +/* ENE MS Lib struct */ +struct ms_lib_type_extdat { + u8 reserved; + u8 intr; + u8 status0; + u8 status1; + u8 ovrflg; + u8 mngflg; + u16 logadr; +}; + +struct ms_lib_ctrl { + u32 flags; + u32 BytesPerSector; + u32 NumberOfCylinder; + u32 SectorsPerCylinder; + u16 cardType; /* R/W, RO, Hybrid */ + u16 blockSize; + u16 PagesPerBlock; + u16 NumberOfPhyBlock; + u16 NumberOfLogBlock; + u16 NumberOfSegment; + u16 *Phy2LogMap; /* phy2log table */ + u16 *Log2PhyMap; /* log2phy table */ + u16 wrtblk; + unsigned char *pagemap[(MS_MAX_PAGES_PER_BLOCK + (MS_LIB_BITS_PER_BYTE-1)) / MS_LIB_BITS_PER_BYTE]; + unsigned char *blkpag; + struct ms_lib_type_extdat *blkext; + unsigned char copybuf[512]; +}; + + +/* SD Block Length */ +/* 2^9 = 512 Bytes, The HW maximum read/write data length */ +#define SD_BLOCK_LEN 9 + +struct ene_ub6250_info { + /* for 6250 code */ + struct SD_STATUS SD_Status; + struct MS_STATUS MS_Status; + struct SM_STATUS SM_Status; + + /* ----- SD Control Data ---------------- */ + /*SD_REGISTER SD_Regs; */ + u16 SD_Block_Mult; + u8 SD_READ_BL_LEN; + u16 SD_C_SIZE; + u8 SD_C_SIZE_MULT; + + /* SD/MMC New spec. */ + u8 SD_SPEC_VER; + u8 SD_CSD_VER; + u8 SD20_HIGH_CAPACITY; + u32 HC_C_SIZE; + u8 MMC_SPEC_VER; + u8 MMC_BusWidth; + u8 MMC_HIGH_CAPACITY; + + /*----- MS Control Data ---------------- */ + bool MS_SWWP; + u32 MSP_TotalBlock; + struct ms_lib_ctrl MS_Lib; + bool MS_IsRWPage; + u16 MS_Model; + + /*----- SM Control Data ---------------- */ + u8 SM_DeviceID; + u8 SM_CardID; + + unsigned char *testbuf; + u8 BIN_FLAG; + u32 bl_num; + int SrbStatus; + + /*------Power Managerment ---------------*/ + bool Power_IsResum; +}; + +static int ene_sd_init(struct us_data *us); +static int ene_ms_init(struct us_data *us); +static int ene_load_bincode(struct us_data *us, unsigned char flag); + +static void ene_ub6250_info_destructor(void *extra) +{ + if (!extra) + return; +} + +static int ene_send_scsi_cmd(struct us_data *us, u8 fDir, void *buf, int use_sg) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf; + + int result; + unsigned int residue; + unsigned int cswlen = 0, partial = 0; + unsigned int transfer_length = bcb->DataTransferLength; + + /* usb_stor_dbg(us, "transport --- ene_send_scsi_cmd\n"); */ + /* send cmd to out endpoint */ + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + bcb, US_BULK_CB_WRAP_LEN, NULL); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "send cmd to out endpoint fail ---\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + if (buf) { + unsigned int pipe = fDir; + + if (fDir == FDIR_READ) + pipe = us->recv_bulk_pipe; + else + pipe = us->send_bulk_pipe; + + /* Bulk */ + if (use_sg) { + result = usb_stor_bulk_srb(us, pipe, us->srb); + } else { + result = usb_stor_bulk_transfer_sg(us, pipe, buf, + transfer_length, 0, &partial); + } + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "data transfer fail ---\n"); + return USB_STOR_TRANSPORT_ERROR; + } + } + + /* Get CSW for device status */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, &cswlen); + + if (result == USB_STOR_XFER_SHORT && cswlen == 0) { + usb_stor_dbg(us, "Received 0-length CSW; retrying...\n"); + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + bcs, US_BULK_CS_WRAP_LEN, &cswlen); + } + + if (result == USB_STOR_XFER_STALLED) { + /* get the status again */ + usb_stor_dbg(us, "Attempting to get CSW (2nd try)...\n"); + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + bcs, US_BULK_CS_WRAP_LEN, NULL); + } + + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* check bulk status */ + residue = le32_to_cpu(bcs->Residue); + + /* try to compute the actual residue, based on how much data + * was really transferred and what the device tells us */ + if (residue && !(us->fflags & US_FL_IGNORE_RESIDUE)) { + residue = min(residue, transfer_length); + if (us->srb != NULL) + scsi_set_resid(us->srb, max(scsi_get_resid(us->srb), + (int)residue)); + } + + if (bcs->Status != US_BULK_STAT_OK) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +static int sd_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if (info->SD_Status.Insert && info->SD_Status.Ready) + return USB_STOR_TRANSPORT_GOOD; + else { + ene_sd_init(us); + return USB_STOR_TRANSPORT_GOOD; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +static int sd_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) +{ + unsigned char data_ptr[36] = { + 0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, + 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30 }; + + usb_stor_set_xfer_buf(data_ptr, 36, srb); + return USB_STOR_TRANSPORT_GOOD; +} + +static int sd_scsi_mode_sense(struct us_data *us, struct scsi_cmnd *srb) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + unsigned char mediaNoWP[12] = { + 0x0b, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x71, 0xc0, 0x00, 0x00, 0x02, 0x00 }; + unsigned char mediaWP[12] = { + 0x0b, 0x00, 0x80, 0x08, 0x00, 0x00, + 0x71, 0xc0, 0x00, 0x00, 0x02, 0x00 }; + + if (info->SD_Status.WtP) + usb_stor_set_xfer_buf(mediaWP, 12, srb); + else + usb_stor_set_xfer_buf(mediaNoWP, 12, srb); + + + return USB_STOR_TRANSPORT_GOOD; +} + +static int sd_scsi_read_capacity(struct us_data *us, struct scsi_cmnd *srb) +{ + u32 bl_num; + u32 bl_len; + unsigned int offset = 0; + unsigned char buf[8]; + struct scatterlist *sg = NULL; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + usb_stor_dbg(us, "sd_scsi_read_capacity\n"); + if (info->SD_Status.HiCapacity) { + bl_len = 0x200; + if (info->SD_Status.IsMMC) + bl_num = info->HC_C_SIZE-1; + else + bl_num = (info->HC_C_SIZE + 1) * 1024 - 1; + } else { + bl_len = 1 << (info->SD_READ_BL_LEN); + bl_num = info->SD_Block_Mult * (info->SD_C_SIZE + 1) + * (1 << (info->SD_C_SIZE_MULT + 2)) - 1; + } + info->bl_num = bl_num; + usb_stor_dbg(us, "bl_len = %x\n", bl_len); + usb_stor_dbg(us, "bl_num = %x\n", bl_num); + + /*srb->request_bufflen = 8; */ + buf[0] = (bl_num >> 24) & 0xff; + buf[1] = (bl_num >> 16) & 0xff; + buf[2] = (bl_num >> 8) & 0xff; + buf[3] = (bl_num >> 0) & 0xff; + buf[4] = (bl_len >> 24) & 0xff; + buf[5] = (bl_len >> 16) & 0xff; + buf[6] = (bl_len >> 8) & 0xff; + buf[7] = (bl_len >> 0) & 0xff; + + usb_stor_access_xfer_buf(buf, 8, srb, &sg, &offset, TO_XFER_BUF); + + return USB_STOR_TRANSPORT_GOOD; +} + +static int sd_scsi_read(struct us_data *us, struct scsi_cmnd *srb) +{ + int result; + unsigned char *cdb = srb->cmnd; + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + u32 bn = ((cdb[2] << 24) & 0xff000000) | ((cdb[3] << 16) & 0x00ff0000) | + ((cdb[4] << 8) & 0x0000ff00) | ((cdb[5] << 0) & 0x000000ff); + u16 blen = ((cdb[7] << 8) & 0xff00) | ((cdb[8] << 0) & 0x00ff); + u32 bnByte = bn * 0x200; + u32 blenByte = blen * 0x200; + + if (bn > info->bl_num) + return USB_STOR_TRANSPORT_ERROR; + + result = ene_load_bincode(us, SD_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Load SD RW pattern Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + if (info->SD_Status.HiCapacity) + bnByte = bn; + + /* set up the command wrapper */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = blenByte; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + bcb->CDB[5] = (unsigned char)(bnByte); + bcb->CDB[4] = (unsigned char)(bnByte>>8); + bcb->CDB[3] = (unsigned char)(bnByte>>16); + bcb->CDB[2] = (unsigned char)(bnByte>>24); + + result = ene_send_scsi_cmd(us, FDIR_READ, scsi_sglist(srb), 1); + return result; +} + +static int sd_scsi_write(struct us_data *us, struct scsi_cmnd *srb) +{ + int result; + unsigned char *cdb = srb->cmnd; + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + u32 bn = ((cdb[2] << 24) & 0xff000000) | ((cdb[3] << 16) & 0x00ff0000) | + ((cdb[4] << 8) & 0x0000ff00) | ((cdb[5] << 0) & 0x000000ff); + u16 blen = ((cdb[7] << 8) & 0xff00) | ((cdb[8] << 0) & 0x00ff); + u32 bnByte = bn * 0x200; + u32 blenByte = blen * 0x200; + + if (bn > info->bl_num) + return USB_STOR_TRANSPORT_ERROR; + + result = ene_load_bincode(us, SD_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Load SD RW pattern Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + if (info->SD_Status.HiCapacity) + bnByte = bn; + + /* set up the command wrapper */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = blenByte; + bcb->Flags = 0x00; + bcb->CDB[0] = 0xF0; + bcb->CDB[5] = (unsigned char)(bnByte); + bcb->CDB[4] = (unsigned char)(bnByte>>8); + bcb->CDB[3] = (unsigned char)(bnByte>>16); + bcb->CDB[2] = (unsigned char)(bnByte>>24); + + result = ene_send_scsi_cmd(us, FDIR_WRITE, scsi_sglist(srb), 1); + return result; +} + +/* + * ENE MS Card + */ + +static int ms_lib_set_logicalpair(struct us_data *us, u16 logblk, u16 phyblk) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if ((logblk >= info->MS_Lib.NumberOfLogBlock) || (phyblk >= info->MS_Lib.NumberOfPhyBlock)) + return (u32)-1; + + info->MS_Lib.Phy2LogMap[phyblk] = logblk; + info->MS_Lib.Log2PhyMap[logblk] = phyblk; + + return 0; +} + +static int ms_lib_set_logicalblockmark(struct us_data *us, u16 phyblk, u16 mark) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if (phyblk >= info->MS_Lib.NumberOfPhyBlock) + return (u32)-1; + + info->MS_Lib.Phy2LogMap[phyblk] = mark; + + return 0; +} + +static int ms_lib_set_initialerrorblock(struct us_data *us, u16 phyblk) +{ + return ms_lib_set_logicalblockmark(us, phyblk, MS_LB_INITIAL_ERROR); +} + +static int ms_lib_set_bootblockmark(struct us_data *us, u16 phyblk) +{ + return ms_lib_set_logicalblockmark(us, phyblk, MS_LB_BOOT_BLOCK); +} + +static int ms_lib_free_logicalmap(struct us_data *us) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + kfree(info->MS_Lib.Phy2LogMap); + info->MS_Lib.Phy2LogMap = NULL; + + kfree(info->MS_Lib.Log2PhyMap); + info->MS_Lib.Log2PhyMap = NULL; + + return 0; +} + +static int ms_lib_alloc_logicalmap(struct us_data *us) +{ + u32 i; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + info->MS_Lib.Phy2LogMap = kmalloc(info->MS_Lib.NumberOfPhyBlock * sizeof(u16), GFP_KERNEL); + info->MS_Lib.Log2PhyMap = kmalloc(info->MS_Lib.NumberOfLogBlock * sizeof(u16), GFP_KERNEL); + + if ((info->MS_Lib.Phy2LogMap == NULL) || (info->MS_Lib.Log2PhyMap == NULL)) { + ms_lib_free_logicalmap(us); + return (u32)-1; + } + + for (i = 0; i < info->MS_Lib.NumberOfPhyBlock; i++) + info->MS_Lib.Phy2LogMap[i] = MS_LB_NOT_USED; + + for (i = 0; i < info->MS_Lib.NumberOfLogBlock; i++) + info->MS_Lib.Log2PhyMap[i] = MS_LB_NOT_USED; + + return 0; +} + +static void ms_lib_clear_writebuf(struct us_data *us) +{ + int i; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + info->MS_Lib.wrtblk = (u16)-1; + ms_lib_clear_pagemap(info); + + if (info->MS_Lib.blkpag) + memset(info->MS_Lib.blkpag, 0xff, info->MS_Lib.PagesPerBlock * info->MS_Lib.BytesPerSector); + + if (info->MS_Lib.blkext) { + for (i = 0; i < info->MS_Lib.PagesPerBlock; i++) { + info->MS_Lib.blkext[i].status1 = MS_REG_ST1_DEFAULT; + info->MS_Lib.blkext[i].ovrflg = MS_REG_OVR_DEFAULT; + info->MS_Lib.blkext[i].mngflg = MS_REG_MNG_DEFAULT; + info->MS_Lib.blkext[i].logadr = MS_LB_NOT_USED; + } + } +} + +static int ms_count_freeblock(struct us_data *us, u16 PhyBlock) +{ + u32 Ende, Count; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + Ende = PhyBlock + MS_PHYSICAL_BLOCKS_PER_SEGMENT; + for (Count = 0; PhyBlock < Ende; PhyBlock++) { + switch (info->MS_Lib.Phy2LogMap[PhyBlock]) { + case MS_LB_NOT_USED: + case MS_LB_NOT_USED_ERASED: + Count++; + default: + break; + } + } + + return Count; +} + +static int ms_read_readpage(struct us_data *us, u32 PhyBlockAddr, + u8 PageNum, u32 *PageBuf, struct ms_lib_type_extdat *ExtraDat) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + u8 ExtBuf[4]; + u32 bn = PhyBlockAddr * 0x20 + PageNum; + + /* printk(KERN_INFO "MS --- MS_ReaderReadPage, + PhyBlockAddr = %x, PageNum = %x\n", PhyBlockAddr, PageNum); */ + + result = ene_load_bincode(us, MS_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* Read Page Data */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + + bcb->CDB[1] = 0x02; /* in init.c ENE_MSInit() is 0x01 */ + + bcb->CDB[5] = (unsigned char)(bn); + bcb->CDB[4] = (unsigned char)(bn>>8); + bcb->CDB[3] = (unsigned char)(bn>>16); + bcb->CDB[2] = (unsigned char)(bn>>24); + + result = ene_send_scsi_cmd(us, FDIR_READ, PageBuf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + + /* Read Extra Data */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x4; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x03; + + bcb->CDB[5] = (unsigned char)(PageNum); + bcb->CDB[4] = (unsigned char)(PhyBlockAddr); + bcb->CDB[3] = (unsigned char)(PhyBlockAddr>>8); + bcb->CDB[2] = (unsigned char)(PhyBlockAddr>>16); + bcb->CDB[6] = 0x01; + + result = ene_send_scsi_cmd(us, FDIR_READ, &ExtBuf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + ExtraDat->reserved = 0; + ExtraDat->intr = 0x80; /* Not yet,fireware support */ + ExtraDat->status0 = 0x10; /* Not yet,fireware support */ + + ExtraDat->status1 = 0x00; /* Not yet,fireware support */ + ExtraDat->ovrflg = ExtBuf[0]; + ExtraDat->mngflg = ExtBuf[1]; + ExtraDat->logadr = memstick_logaddr(ExtBuf[2], ExtBuf[3]); + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_lib_process_bootblock(struct us_data *us, u16 PhyBlock, u8 *PageData) +{ + struct ms_bootblock_sysent *SysEntry; + struct ms_bootblock_sysinf *SysInfo; + u32 i, result; + u8 PageNumber; + u8 *PageBuffer; + struct ms_lib_type_extdat ExtraData; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + PageBuffer = kmalloc(MS_BYTES_PER_PAGE, GFP_KERNEL); + if (PageBuffer == NULL) + return (u32)-1; + + result = (u32)-1; + + SysInfo = &(((struct ms_bootblock_page0 *)PageData)->sysinf); + + if ((SysInfo->bMsClass != MS_SYSINF_MSCLASS_TYPE_1) || + (be16_to_cpu(SysInfo->wPageSize) != MS_SYSINF_PAGE_SIZE) || + ((SysInfo->bSecuritySupport & MS_SYSINF_SECURITY) == MS_SYSINF_SECURITY_SUPPORT) || + (SysInfo->bReserved1 != MS_SYSINF_RESERVED1) || + (SysInfo->bReserved2 != MS_SYSINF_RESERVED2) || + (SysInfo->bFormatType != MS_SYSINF_FORMAT_FAT) || + (SysInfo->bUsage != MS_SYSINF_USAGE_GENERAL)) + goto exit; + /* */ + switch (info->MS_Lib.cardType = SysInfo->bCardType) { + case MS_SYSINF_CARDTYPE_RDONLY: + ms_lib_ctrl_set(info, MS_LIB_CTRL_RDONLY); + break; + case MS_SYSINF_CARDTYPE_RDWR: + ms_lib_ctrl_reset(info, MS_LIB_CTRL_RDONLY); + break; + case MS_SYSINF_CARDTYPE_HYBRID: + default: + goto exit; + } + + info->MS_Lib.blockSize = be16_to_cpu(SysInfo->wBlockSize); + info->MS_Lib.NumberOfPhyBlock = be16_to_cpu(SysInfo->wBlockNumber); + info->MS_Lib.NumberOfLogBlock = be16_to_cpu(SysInfo->wTotalBlockNumber)-2; + info->MS_Lib.PagesPerBlock = info->MS_Lib.blockSize * SIZE_OF_KIRO / MS_BYTES_PER_PAGE; + info->MS_Lib.NumberOfSegment = info->MS_Lib.NumberOfPhyBlock / MS_PHYSICAL_BLOCKS_PER_SEGMENT; + info->MS_Model = be16_to_cpu(SysInfo->wMemorySize); + + /*Allocate to all number of logicalblock and physicalblock */ + if (ms_lib_alloc_logicalmap(us)) + goto exit; + + /* Mark the book block */ + ms_lib_set_bootblockmark(us, PhyBlock); + + SysEntry = &(((struct ms_bootblock_page0 *)PageData)->sysent); + + for (i = 0; i < MS_NUMBER_OF_SYSTEM_ENTRY; i++) { + u32 EntryOffset, EntrySize; + + EntryOffset = be32_to_cpu(SysEntry->entry[i].dwStart); + + if (EntryOffset == 0xffffff) + continue; + EntrySize = be32_to_cpu(SysEntry->entry[i].dwSize); + + if (EntrySize == 0) + continue; + + if (EntryOffset + MS_BYTES_PER_PAGE + EntrySize > info->MS_Lib.blockSize * (u32)SIZE_OF_KIRO) + continue; + + if (i == 0) { + u8 PrevPageNumber = 0; + u16 phyblk; + + if (SysEntry->entry[i].bType != MS_SYSENT_TYPE_INVALID_BLOCK) + goto exit; + + while (EntrySize > 0) { + + PageNumber = (u8)(EntryOffset / MS_BYTES_PER_PAGE + 1); + if (PageNumber != PrevPageNumber) { + switch (ms_read_readpage(us, PhyBlock, PageNumber, (u32 *)PageBuffer, &ExtraData)) { + case MS_STATUS_SUCCESS: + break; + case MS_STATUS_WRITE_PROTECT: + case MS_ERROR_FLASH_READ: + case MS_STATUS_ERROR: + default: + goto exit; + } + + PrevPageNumber = PageNumber; + } + + phyblk = be16_to_cpu(*(u16 *)(PageBuffer + (EntryOffset % MS_BYTES_PER_PAGE))); + if (phyblk < 0x0fff) + ms_lib_set_initialerrorblock(us, phyblk); + + EntryOffset += 2; + EntrySize -= 2; + } + } else if (i == 1) { /* CIS/IDI */ + struct ms_bootblock_idi *idi; + + if (SysEntry->entry[i].bType != MS_SYSENT_TYPE_CIS_IDI) + goto exit; + + switch (ms_read_readpage(us, PhyBlock, (u8)(EntryOffset / MS_BYTES_PER_PAGE + 1), (u32 *)PageBuffer, &ExtraData)) { + case MS_STATUS_SUCCESS: + break; + case MS_STATUS_WRITE_PROTECT: + case MS_ERROR_FLASH_READ: + case MS_STATUS_ERROR: + default: + goto exit; + } + + idi = &((struct ms_bootblock_cis_idi *)(PageBuffer + (EntryOffset % MS_BYTES_PER_PAGE)))->idi.idi; + if (le16_to_cpu(idi->wIDIgeneralConfiguration) != MS_IDI_GENERAL_CONF) + goto exit; + + info->MS_Lib.BytesPerSector = le16_to_cpu(idi->wIDIbytesPerSector); + if (info->MS_Lib.BytesPerSector != MS_BYTES_PER_PAGE) + goto exit; + } + } /* End for .. */ + + result = 0; + +exit: + if (result) + ms_lib_free_logicalmap(us); + + kfree(PageBuffer); + + result = 0; + return result; +} + +static void ms_lib_free_writebuf(struct us_data *us) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + info->MS_Lib.wrtblk = (u16)-1; /* set to -1 */ + + /* memset((fdoExt)->MS_Lib.pagemap, 0, sizeof((fdoExt)->MS_Lib.pagemap)) */ + + ms_lib_clear_pagemap(info); /* (pdx)->MS_Lib.pagemap memset 0 in ms.h */ + + if (info->MS_Lib.blkpag) { + kfree((u8 *)(info->MS_Lib.blkpag)); /* Arnold test ... */ + info->MS_Lib.blkpag = NULL; + } + + if (info->MS_Lib.blkext) { + kfree((u8 *)(info->MS_Lib.blkext)); /* Arnold test ... */ + info->MS_Lib.blkext = NULL; + } +} + + +static void ms_lib_free_allocatedarea(struct us_data *us) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + ms_lib_free_writebuf(us); /* Free MS_Lib.pagemap */ + ms_lib_free_logicalmap(us); /* kfree MS_Lib.Phy2LogMap and MS_Lib.Log2PhyMap */ + + /* set struct us point flag to 0 */ + info->MS_Lib.flags = 0; + info->MS_Lib.BytesPerSector = 0; + info->MS_Lib.SectorsPerCylinder = 0; + + info->MS_Lib.cardType = 0; + info->MS_Lib.blockSize = 0; + info->MS_Lib.PagesPerBlock = 0; + + info->MS_Lib.NumberOfPhyBlock = 0; + info->MS_Lib.NumberOfLogBlock = 0; +} + + +static int ms_lib_alloc_writebuf(struct us_data *us) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + info->MS_Lib.wrtblk = (u16)-1; + + info->MS_Lib.blkpag = kmalloc(info->MS_Lib.PagesPerBlock * info->MS_Lib.BytesPerSector, GFP_KERNEL); + info->MS_Lib.blkext = kmalloc(info->MS_Lib.PagesPerBlock * sizeof(struct ms_lib_type_extdat), GFP_KERNEL); + + if ((info->MS_Lib.blkpag == NULL) || (info->MS_Lib.blkext == NULL)) { + ms_lib_free_writebuf(us); + return (u32)-1; + } + + ms_lib_clear_writebuf(us); + +return 0; +} + +static int ms_lib_force_setlogical_pair(struct us_data *us, u16 logblk, u16 phyblk) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if (logblk == MS_LB_NOT_USED) + return 0; + + if ((logblk >= info->MS_Lib.NumberOfLogBlock) || + (phyblk >= info->MS_Lib.NumberOfPhyBlock)) + return (u32)-1; + + info->MS_Lib.Phy2LogMap[phyblk] = logblk; + info->MS_Lib.Log2PhyMap[logblk] = phyblk; + + return 0; +} + +static int ms_read_copyblock(struct us_data *us, u16 oldphy, u16 newphy, + u16 PhyBlockAddr, u8 PageNum, unsigned char *buf, u16 len) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + + /* printk(KERN_INFO "MS_ReaderCopyBlock --- PhyBlockAddr = %x, + PageNum = %x\n", PhyBlockAddr, PageNum); */ + result = ene_load_bincode(us, MS_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200*len; + bcb->Flags = 0x00; + bcb->CDB[0] = 0xF0; + bcb->CDB[1] = 0x08; + bcb->CDB[4] = (unsigned char)(oldphy); + bcb->CDB[3] = (unsigned char)(oldphy>>8); + bcb->CDB[2] = 0; /* (BYTE)(oldphy>>16) */ + bcb->CDB[7] = (unsigned char)(newphy); + bcb->CDB[6] = (unsigned char)(newphy>>8); + bcb->CDB[5] = 0; /* (BYTE)(newphy>>16) */ + bcb->CDB[9] = (unsigned char)(PhyBlockAddr); + bcb->CDB[8] = (unsigned char)(PhyBlockAddr>>8); + bcb->CDB[10] = PageNum; + + result = ene_send_scsi_cmd(us, FDIR_WRITE, buf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_read_eraseblock(struct us_data *us, u32 PhyBlockAddr) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + u32 bn = PhyBlockAddr; + + /* printk(KERN_INFO "MS --- ms_read_eraseblock, + PhyBlockAddr = %x\n", PhyBlockAddr); */ + result = ene_load_bincode(us, MS_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF2; + bcb->CDB[1] = 0x06; + bcb->CDB[4] = (unsigned char)(bn); + bcb->CDB[3] = (unsigned char)(bn>>8); + bcb->CDB[2] = (unsigned char)(bn>>16); + + result = ene_send_scsi_cmd(us, FDIR_READ, NULL, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_lib_check_disableblock(struct us_data *us, u16 PhyBlock) +{ + unsigned char *PageBuf = NULL; + u16 result = MS_STATUS_SUCCESS; + u16 blk, index = 0; + struct ms_lib_type_extdat extdat; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + PageBuf = kmalloc(MS_BYTES_PER_PAGE, GFP_KERNEL); + if (PageBuf == NULL) { + result = MS_NO_MEMORY_ERROR; + goto exit; + } + + ms_read_readpage(us, PhyBlock, 1, (u32 *)PageBuf, &extdat); + do { + blk = be16_to_cpu(PageBuf[index]); + if (blk == MS_LB_NOT_USED) + break; + if (blk == info->MS_Lib.Log2PhyMap[0]) { + result = MS_ERROR_FLASH_READ; + break; + } + index++; + } while (1); + +exit: + kfree(PageBuf); + return result; +} + +static int ms_lib_setacquired_errorblock(struct us_data *us, u16 phyblk) +{ + u16 log; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if (phyblk >= info->MS_Lib.NumberOfPhyBlock) + return (u32)-1; + + log = info->MS_Lib.Phy2LogMap[phyblk]; + + if (log < info->MS_Lib.NumberOfLogBlock) + info->MS_Lib.Log2PhyMap[log] = MS_LB_NOT_USED; + + if (info->MS_Lib.Phy2LogMap[phyblk] != MS_LB_INITIAL_ERROR) + info->MS_Lib.Phy2LogMap[phyblk] = MS_LB_ACQUIRED_ERROR; + + return 0; +} + +static int ms_lib_overwrite_extra(struct us_data *us, u32 PhyBlockAddr, + u8 PageNum, u8 OverwriteFlag) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + + /* printk("MS --- MS_LibOverwriteExtra, + PhyBlockAddr = %x, PageNum = %x\n", PhyBlockAddr, PageNum); */ + result = ene_load_bincode(us, MS_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x4; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF2; + bcb->CDB[1] = 0x05; + bcb->CDB[5] = (unsigned char)(PageNum); + bcb->CDB[4] = (unsigned char)(PhyBlockAddr); + bcb->CDB[3] = (unsigned char)(PhyBlockAddr>>8); + bcb->CDB[2] = (unsigned char)(PhyBlockAddr>>16); + bcb->CDB[6] = OverwriteFlag; + bcb->CDB[7] = 0xFF; + bcb->CDB[8] = 0xFF; + bcb->CDB[9] = 0xFF; + + result = ene_send_scsi_cmd(us, FDIR_READ, NULL, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_lib_error_phyblock(struct us_data *us, u16 phyblk) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if (phyblk >= info->MS_Lib.NumberOfPhyBlock) + return MS_STATUS_ERROR; + + ms_lib_setacquired_errorblock(us, phyblk); + + if (ms_lib_iswritable(info)) + return ms_lib_overwrite_extra(us, phyblk, 0, (u8)(~MS_REG_OVR_BKST & BYTE_MASK)); + + return MS_STATUS_SUCCESS; +} + +static int ms_lib_erase_phyblock(struct us_data *us, u16 phyblk) +{ + u16 log; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if (phyblk >= info->MS_Lib.NumberOfPhyBlock) + return MS_STATUS_ERROR; + + log = info->MS_Lib.Phy2LogMap[phyblk]; + + if (log < info->MS_Lib.NumberOfLogBlock) + info->MS_Lib.Log2PhyMap[log] = MS_LB_NOT_USED; + + info->MS_Lib.Phy2LogMap[phyblk] = MS_LB_NOT_USED; + + if (ms_lib_iswritable(info)) { + switch (ms_read_eraseblock(us, phyblk)) { + case MS_STATUS_SUCCESS: + info->MS_Lib.Phy2LogMap[phyblk] = MS_LB_NOT_USED_ERASED; + return MS_STATUS_SUCCESS; + case MS_ERROR_FLASH_ERASE: + case MS_STATUS_INT_ERROR: + ms_lib_error_phyblock(us, phyblk); + return MS_ERROR_FLASH_ERASE; + case MS_STATUS_ERROR: + default: + ms_lib_ctrl_set(info, MS_LIB_CTRL_RDONLY); /* MS_LibCtrlSet will used by ENE_MSInit ,need check, and why us to info*/ + ms_lib_setacquired_errorblock(us, phyblk); + return MS_STATUS_ERROR; + } + } + + ms_lib_setacquired_errorblock(us, phyblk); + + return MS_STATUS_SUCCESS; +} + +static int ms_lib_read_extra(struct us_data *us, u32 PhyBlock, + u8 PageNum, struct ms_lib_type_extdat *ExtraDat) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + u8 ExtBuf[4]; + + /* printk("MS_LibReadExtra --- PhyBlock = %x, PageNum = %x\n", PhyBlock, PageNum); */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x4; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x03; + bcb->CDB[5] = (unsigned char)(PageNum); + bcb->CDB[4] = (unsigned char)(PhyBlock); + bcb->CDB[3] = (unsigned char)(PhyBlock>>8); + bcb->CDB[2] = (unsigned char)(PhyBlock>>16); + bcb->CDB[6] = 0x01; + + result = ene_send_scsi_cmd(us, FDIR_READ, &ExtBuf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + ExtraDat->reserved = 0; + ExtraDat->intr = 0x80; /* Not yet, waiting for fireware support */ + ExtraDat->status0 = 0x10; /* Not yet, waiting for fireware support */ + ExtraDat->status1 = 0x00; /* Not yet, waiting for fireware support */ + ExtraDat->ovrflg = ExtBuf[0]; + ExtraDat->mngflg = ExtBuf[1]; + ExtraDat->logadr = memstick_logaddr(ExtBuf[2], ExtBuf[3]); + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_libsearch_block_from_physical(struct us_data *us, u16 phyblk) +{ + u16 Newblk; + u16 blk; + struct ms_lib_type_extdat extdat; /* need check */ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + + if (phyblk >= info->MS_Lib.NumberOfPhyBlock) + return MS_LB_ERROR; + + for (blk = phyblk + 1; blk != phyblk; blk++) { + if ((blk & MS_PHYSICAL_BLOCKS_PER_SEGMENT_MASK) == 0) + blk -= MS_PHYSICAL_BLOCKS_PER_SEGMENT; + + Newblk = info->MS_Lib.Phy2LogMap[blk]; + if (info->MS_Lib.Phy2LogMap[blk] == MS_LB_NOT_USED_ERASED) { + return blk; + } else if (info->MS_Lib.Phy2LogMap[blk] == MS_LB_NOT_USED) { + switch (ms_lib_read_extra(us, blk, 0, &extdat)) { + case MS_STATUS_SUCCESS: + case MS_STATUS_SUCCESS_WITH_ECC: + break; + case MS_NOCARD_ERROR: + return MS_NOCARD_ERROR; + case MS_STATUS_INT_ERROR: + return MS_LB_ERROR; + case MS_ERROR_FLASH_READ: + default: + ms_lib_setacquired_errorblock(us, blk); + continue; + } /* End switch */ + + if ((extdat.ovrflg & MS_REG_OVR_BKST) != MS_REG_OVR_BKST_OK) { + ms_lib_setacquired_errorblock(us, blk); + continue; + } + + switch (ms_lib_erase_phyblock(us, blk)) { + case MS_STATUS_SUCCESS: + return blk; + case MS_STATUS_ERROR: + return MS_LB_ERROR; + case MS_ERROR_FLASH_ERASE: + default: + ms_lib_error_phyblock(us, blk); + break; + } + } + } /* End for */ + + return MS_LB_ERROR; +} +static int ms_libsearch_block_from_logical(struct us_data *us, u16 logblk) +{ + u16 phyblk; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + phyblk = ms_libconv_to_physical(info, logblk); + if (phyblk >= MS_LB_ERROR) { + if (logblk >= info->MS_Lib.NumberOfLogBlock) + return MS_LB_ERROR; + + phyblk = (logblk + MS_NUMBER_OF_BOOT_BLOCK) / MS_LOGICAL_BLOCKS_PER_SEGMENT; + phyblk *= MS_PHYSICAL_BLOCKS_PER_SEGMENT; + phyblk += MS_PHYSICAL_BLOCKS_PER_SEGMENT - 1; + } + + return ms_libsearch_block_from_physical(us, phyblk); +} + +static int ms_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *)(us->extra); + + /* pr_info("MS_SCSI_Test_Unit_Ready\n"); */ + if (info->MS_Status.Insert && info->MS_Status.Ready) { + return USB_STOR_TRANSPORT_GOOD; + } else { + ene_ms_init(us); + return USB_STOR_TRANSPORT_GOOD; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) +{ + /* pr_info("MS_SCSI_Inquiry\n"); */ + unsigned char data_ptr[36] = { + 0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, + 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30}; + + usb_stor_set_xfer_buf(data_ptr, 36, srb); + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_scsi_mode_sense(struct us_data *us, struct scsi_cmnd *srb) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + unsigned char mediaNoWP[12] = { + 0x0b, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x71, 0xc0, 0x00, 0x00, 0x02, 0x00 }; + unsigned char mediaWP[12] = { + 0x0b, 0x00, 0x80, 0x08, 0x00, 0x00, + 0x71, 0xc0, 0x00, 0x00, 0x02, 0x00 }; + + if (info->MS_Status.WtP) + usb_stor_set_xfer_buf(mediaWP, 12, srb); + else + usb_stor_set_xfer_buf(mediaNoWP, 12, srb); + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_scsi_read_capacity(struct us_data *us, struct scsi_cmnd *srb) +{ + u32 bl_num; + u16 bl_len; + unsigned int offset = 0; + unsigned char buf[8]; + struct scatterlist *sg = NULL; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + usb_stor_dbg(us, "ms_scsi_read_capacity\n"); + bl_len = 0x200; + if (info->MS_Status.IsMSPro) + bl_num = info->MSP_TotalBlock - 1; + else + bl_num = info->MS_Lib.NumberOfLogBlock * info->MS_Lib.blockSize * 2 - 1; + + info->bl_num = bl_num; + usb_stor_dbg(us, "bl_len = %x\n", bl_len); + usb_stor_dbg(us, "bl_num = %x\n", bl_num); + + /*srb->request_bufflen = 8; */ + buf[0] = (bl_num >> 24) & 0xff; + buf[1] = (bl_num >> 16) & 0xff; + buf[2] = (bl_num >> 8) & 0xff; + buf[3] = (bl_num >> 0) & 0xff; + buf[4] = (bl_len >> 24) & 0xff; + buf[5] = (bl_len >> 16) & 0xff; + buf[6] = (bl_len >> 8) & 0xff; + buf[7] = (bl_len >> 0) & 0xff; + + usb_stor_access_xfer_buf(buf, 8, srb, &sg, &offset, TO_XFER_BUF); + + return USB_STOR_TRANSPORT_GOOD; +} + +static void ms_lib_phy_to_log_range(u16 PhyBlock, u16 *LogStart, u16 *LogEnde) +{ + PhyBlock /= MS_PHYSICAL_BLOCKS_PER_SEGMENT; + + if (PhyBlock) { + *LogStart = MS_LOGICAL_BLOCKS_IN_1ST_SEGMENT + (PhyBlock - 1) * MS_LOGICAL_BLOCKS_PER_SEGMENT;/*496*/ + *LogEnde = *LogStart + MS_LOGICAL_BLOCKS_PER_SEGMENT;/*496*/ + } else { + *LogStart = 0; + *LogEnde = MS_LOGICAL_BLOCKS_IN_1ST_SEGMENT;/*494*/ + } +} + +static int ms_lib_read_extrablock(struct us_data *us, u32 PhyBlock, + u8 PageNum, u8 blen, void *buf) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + + /* printk("MS_LibReadExtraBlock --- PhyBlock = %x, + PageNum = %x, blen = %x\n", PhyBlock, PageNum, blen); */ + + /* Read Extra Data */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x4 * blen; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x03; + bcb->CDB[5] = (unsigned char)(PageNum); + bcb->CDB[4] = (unsigned char)(PhyBlock); + bcb->CDB[3] = (unsigned char)(PhyBlock>>8); + bcb->CDB[2] = (unsigned char)(PhyBlock>>16); + bcb->CDB[6] = blen; + + result = ene_send_scsi_cmd(us, FDIR_READ, buf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_lib_scan_logicalblocknumber(struct us_data *us, u16 btBlk1st) +{ + u16 PhyBlock, newblk, i; + u16 LogStart, LogEnde; + struct ms_lib_type_extdat extdat; + u8 buf[0x200]; + u32 count = 0, index = 0; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + for (PhyBlock = 0; PhyBlock < info->MS_Lib.NumberOfPhyBlock;) { + ms_lib_phy_to_log_range(PhyBlock, &LogStart, &LogEnde); + + for (i = 0; i < MS_PHYSICAL_BLOCKS_PER_SEGMENT; i++, PhyBlock++) { + switch (ms_libconv_to_logical(info, PhyBlock)) { + case MS_STATUS_ERROR: + continue; + default: + break; + } + + if (count == PhyBlock) { + ms_lib_read_extrablock(us, PhyBlock, 0, 0x80, &buf); + count += 0x80; + } + index = (PhyBlock % 0x80) * 4; + + extdat.ovrflg = buf[index]; + extdat.mngflg = buf[index+1]; + extdat.logadr = memstick_logaddr(buf[index+2], buf[index+3]); + + if ((extdat.ovrflg & MS_REG_OVR_BKST) != MS_REG_OVR_BKST_OK) { + ms_lib_setacquired_errorblock(us, PhyBlock); + continue; + } + + if ((extdat.mngflg & MS_REG_MNG_ATFLG) == MS_REG_MNG_ATFLG_ATTBL) { + ms_lib_erase_phyblock(us, PhyBlock); + continue; + } + + if (extdat.logadr != MS_LB_NOT_USED) { + if ((extdat.logadr < LogStart) || (LogEnde <= extdat.logadr)) { + ms_lib_erase_phyblock(us, PhyBlock); + continue; + } + + newblk = ms_libconv_to_physical(info, extdat.logadr); + + if (newblk != MS_LB_NOT_USED) { + if (extdat.logadr == 0) { + ms_lib_set_logicalpair(us, extdat.logadr, PhyBlock); + if (ms_lib_check_disableblock(us, btBlk1st)) { + ms_lib_set_logicalpair(us, extdat.logadr, newblk); + continue; + } + } + + ms_lib_read_extra(us, newblk, 0, &extdat); + if ((extdat.ovrflg & MS_REG_OVR_UDST) == MS_REG_OVR_UDST_UPDATING) { + ms_lib_erase_phyblock(us, PhyBlock); + continue; + } else { + ms_lib_erase_phyblock(us, newblk); + } + } + + ms_lib_set_logicalpair(us, extdat.logadr, PhyBlock); + } + } + } /* End for ... */ + + return MS_STATUS_SUCCESS; +} + + +static int ms_scsi_read(struct us_data *us, struct scsi_cmnd *srb) +{ + int result; + unsigned char *cdb = srb->cmnd; + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + u32 bn = ((cdb[2] << 24) & 0xff000000) | ((cdb[3] << 16) & 0x00ff0000) | + ((cdb[4] << 8) & 0x0000ff00) | ((cdb[5] << 0) & 0x000000ff); + u16 blen = ((cdb[7] << 8) & 0xff00) | ((cdb[8] << 0) & 0x00ff); + u32 blenByte = blen * 0x200; + + if (bn > info->bl_num) + return USB_STOR_TRANSPORT_ERROR; + + if (info->MS_Status.IsMSPro) { + result = ene_load_bincode(us, MSP_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Load MPS RW pattern Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* set up the command wrapper */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = blenByte; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x02; + bcb->CDB[5] = (unsigned char)(bn); + bcb->CDB[4] = (unsigned char)(bn>>8); + bcb->CDB[3] = (unsigned char)(bn>>16); + bcb->CDB[2] = (unsigned char)(bn>>24); + + result = ene_send_scsi_cmd(us, FDIR_READ, scsi_sglist(srb), 1); + } else { + void *buf; + int offset = 0; + u16 phyblk, logblk; + u8 PageNum; + u16 len; + u32 blkno; + + buf = kmalloc(blenByte, GFP_KERNEL); + if (buf == NULL) + return USB_STOR_TRANSPORT_ERROR; + + result = ene_load_bincode(us, MS_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + pr_info("Load MS RW pattern Fail !!\n"); + result = USB_STOR_TRANSPORT_ERROR; + goto exit; + } + + logblk = (u16)(bn / info->MS_Lib.PagesPerBlock); + PageNum = (u8)(bn % info->MS_Lib.PagesPerBlock); + + while (1) { + if (blen > (info->MS_Lib.PagesPerBlock-PageNum)) + len = info->MS_Lib.PagesPerBlock-PageNum; + else + len = blen; + + phyblk = ms_libconv_to_physical(info, logblk); + blkno = phyblk * 0x20 + PageNum; + + /* set up the command wrapper */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200 * len; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x02; + bcb->CDB[5] = (unsigned char)(blkno); + bcb->CDB[4] = (unsigned char)(blkno>>8); + bcb->CDB[3] = (unsigned char)(blkno>>16); + bcb->CDB[2] = (unsigned char)(blkno>>24); + + result = ene_send_scsi_cmd(us, FDIR_READ, buf+offset, 0); + if (result != USB_STOR_XFER_GOOD) { + pr_info("MS_SCSI_Read --- result = %x\n", result); + result = USB_STOR_TRANSPORT_ERROR; + goto exit; + } + + blen -= len; + if (blen <= 0) + break; + logblk++; + PageNum = 0; + offset += MS_BYTES_PER_PAGE*len; + } + usb_stor_set_xfer_buf(buf, blenByte, srb); +exit: + kfree(buf); + } + return result; +} + +static int ms_scsi_write(struct us_data *us, struct scsi_cmnd *srb) +{ + int result; + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + unsigned char *cdb = srb->cmnd; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + u32 bn = ((cdb[2] << 24) & 0xff000000) | + ((cdb[3] << 16) & 0x00ff0000) | + ((cdb[4] << 8) & 0x0000ff00) | + ((cdb[5] << 0) & 0x000000ff); + u16 blen = ((cdb[7] << 8) & 0xff00) | ((cdb[8] << 0) & 0x00ff); + u32 blenByte = blen * 0x200; + + if (bn > info->bl_num) + return USB_STOR_TRANSPORT_ERROR; + + if (info->MS_Status.IsMSPro) { + result = ene_load_bincode(us, MSP_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + pr_info("Load MSP RW pattern Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* set up the command wrapper */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = blenByte; + bcb->Flags = 0x00; + bcb->CDB[0] = 0xF0; + bcb->CDB[1] = 0x04; + bcb->CDB[5] = (unsigned char)(bn); + bcb->CDB[4] = (unsigned char)(bn>>8); + bcb->CDB[3] = (unsigned char)(bn>>16); + bcb->CDB[2] = (unsigned char)(bn>>24); + + result = ene_send_scsi_cmd(us, FDIR_WRITE, scsi_sglist(srb), 1); + } else { + void *buf; + int offset = 0; + u16 PhyBlockAddr; + u8 PageNum; + u16 len, oldphy, newphy; + + buf = kmalloc(blenByte, GFP_KERNEL); + if (buf == NULL) + return USB_STOR_TRANSPORT_ERROR; + usb_stor_set_xfer_buf(buf, blenByte, srb); + + result = ene_load_bincode(us, MS_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + pr_info("Load MS RW pattern Fail !!\n"); + result = USB_STOR_TRANSPORT_ERROR; + goto exit; + } + + PhyBlockAddr = (u16)(bn / info->MS_Lib.PagesPerBlock); + PageNum = (u8)(bn % info->MS_Lib.PagesPerBlock); + + while (1) { + if (blen > (info->MS_Lib.PagesPerBlock-PageNum)) + len = info->MS_Lib.PagesPerBlock-PageNum; + else + len = blen; + + oldphy = ms_libconv_to_physical(info, PhyBlockAddr); /* need check us <-> info */ + newphy = ms_libsearch_block_from_logical(us, PhyBlockAddr); + + result = ms_read_copyblock(us, oldphy, newphy, PhyBlockAddr, PageNum, buf+offset, len); + + if (result != USB_STOR_XFER_GOOD) { + pr_info("MS_SCSI_Write --- result = %x\n", result); + result = USB_STOR_TRANSPORT_ERROR; + goto exit; + } + + info->MS_Lib.Phy2LogMap[oldphy] = MS_LB_NOT_USED_ERASED; + ms_lib_force_setlogical_pair(us, PhyBlockAddr, newphy); + + blen -= len; + if (blen <= 0) + break; + PhyBlockAddr++; + PageNum = 0; + offset += MS_BYTES_PER_PAGE*len; + } +exit: + kfree(buf); + } + return result; +} + +/* + * ENE MS Card + */ + +static int ene_get_card_type(struct us_data *us, u16 index, void *buf) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x01; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xED; + bcb->CDB[2] = (unsigned char)(index>>8); + bcb->CDB[3] = (unsigned char)index; + + result = ene_send_scsi_cmd(us, FDIR_READ, buf, 0); + return result; +} + +static int ene_get_card_status(struct us_data *us, u8 *buf) +{ + u16 tmpreg; + u32 reg4b; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + /*usb_stor_dbg(us, "transport --- ENE_ReadSDReg\n");*/ + reg4b = *(u32 *)&buf[0x18]; + info->SD_READ_BL_LEN = (u8)((reg4b >> 8) & 0x0f); + + tmpreg = (u16) reg4b; + reg4b = *(u32 *)(&buf[0x14]); + if (info->SD_Status.HiCapacity && !info->SD_Status.IsMMC) + info->HC_C_SIZE = (reg4b >> 8) & 0x3fffff; + + info->SD_C_SIZE = ((tmpreg & 0x03) << 10) | (u16)(reg4b >> 22); + info->SD_C_SIZE_MULT = (u8)(reg4b >> 7) & 0x07; + if (info->SD_Status.HiCapacity && info->SD_Status.IsMMC) + info->HC_C_SIZE = *(u32 *)(&buf[0x100]); + + if (info->SD_READ_BL_LEN > SD_BLOCK_LEN) { + info->SD_Block_Mult = 1 << (info->SD_READ_BL_LEN-SD_BLOCK_LEN); + info->SD_READ_BL_LEN = SD_BLOCK_LEN; + } else { + info->SD_Block_Mult = 1; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ene_load_bincode(struct us_data *us, unsigned char flag) +{ + int err; + char *fw_name = NULL; + unsigned char *buf = NULL; + const struct firmware *sd_fw = NULL; + int result = USB_STOR_TRANSPORT_ERROR; + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + if (info->BIN_FLAG == flag) + return USB_STOR_TRANSPORT_GOOD; + + switch (flag) { + /* For SD */ + case SD_INIT1_PATTERN: + usb_stor_dbg(us, "SD_INIT1_PATTERN\n"); + fw_name = SD_INIT1_FIRMWARE; + break; + case SD_INIT2_PATTERN: + usb_stor_dbg(us, "SD_INIT2_PATTERN\n"); + fw_name = SD_INIT2_FIRMWARE; + break; + case SD_RW_PATTERN: + usb_stor_dbg(us, "SD_RW_PATTERN\n"); + fw_name = SD_RW_FIRMWARE; + break; + /* For MS */ + case MS_INIT_PATTERN: + usb_stor_dbg(us, "MS_INIT_PATTERN\n"); + fw_name = MS_INIT_FIRMWARE; + break; + case MSP_RW_PATTERN: + usb_stor_dbg(us, "MSP_RW_PATTERN\n"); + fw_name = MSP_RW_FIRMWARE; + break; + case MS_RW_PATTERN: + usb_stor_dbg(us, "MS_RW_PATTERN\n"); + fw_name = MS_RW_FIRMWARE; + break; + default: + usb_stor_dbg(us, "----------- Unknown PATTERN ----------\n"); + goto nofw; + } + + err = request_firmware(&sd_fw, fw_name, &us->pusb_dev->dev); + if (err) { + usb_stor_dbg(us, "load firmware %s failed\n", fw_name); + goto nofw; + } + buf = kmalloc(sd_fw->size, GFP_KERNEL); + if (buf == NULL) + goto nofw; + + memcpy(buf, sd_fw->data, sd_fw->size); + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = sd_fw->size; + bcb->Flags = 0x00; + bcb->CDB[0] = 0xEF; + + result = ene_send_scsi_cmd(us, FDIR_WRITE, buf, 0); + info->BIN_FLAG = flag; + kfree(buf); + +nofw: + release_firmware(sd_fw); + return result; +} + +static int ms_card_init(struct us_data *us) +{ + u32 result; + u16 TmpBlock; + unsigned char *PageBuffer0 = NULL, *PageBuffer1 = NULL; + struct ms_lib_type_extdat extdat; + u16 btBlk1st, btBlk2nd; + u32 btBlk1stErred; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + printk(KERN_INFO "MS_CardInit start\n"); + + ms_lib_free_allocatedarea(us); /* Clean buffer and set struct us_data flag to 0 */ + + /* get two PageBuffer */ + PageBuffer0 = kmalloc(MS_BYTES_PER_PAGE, GFP_KERNEL); + PageBuffer1 = kmalloc(MS_BYTES_PER_PAGE, GFP_KERNEL); + if ((PageBuffer0 == NULL) || (PageBuffer1 == NULL)) { + result = MS_NO_MEMORY_ERROR; + goto exit; + } + + btBlk1st = btBlk2nd = MS_LB_NOT_USED; + btBlk1stErred = 0; + + for (TmpBlock = 0; TmpBlock < MS_MAX_INITIAL_ERROR_BLOCKS+2; TmpBlock++) { + + switch (ms_read_readpage(us, TmpBlock, 0, (u32 *)PageBuffer0, &extdat)) { + case MS_STATUS_SUCCESS: + break; + case MS_STATUS_INT_ERROR: + break; + case MS_STATUS_ERROR: + default: + continue; + } + + if ((extdat.ovrflg & MS_REG_OVR_BKST) == MS_REG_OVR_BKST_NG) + continue; + + if (((extdat.mngflg & MS_REG_MNG_SYSFLG) == MS_REG_MNG_SYSFLG_USER) || + (be16_to_cpu(((struct ms_bootblock_page0 *)PageBuffer0)->header.wBlockID) != MS_BOOT_BLOCK_ID) || + (be16_to_cpu(((struct ms_bootblock_page0 *)PageBuffer0)->header.wFormatVersion) != MS_BOOT_BLOCK_FORMAT_VERSION) || + (((struct ms_bootblock_page0 *)PageBuffer0)->header.bNumberOfDataEntry != MS_BOOT_BLOCK_DATA_ENTRIES)) + continue; + + if (btBlk1st != MS_LB_NOT_USED) { + btBlk2nd = TmpBlock; + break; + } + + btBlk1st = TmpBlock; + memcpy(PageBuffer1, PageBuffer0, MS_BYTES_PER_PAGE); + if (extdat.status1 & (MS_REG_ST1_DTER | MS_REG_ST1_EXER | MS_REG_ST1_FGER)) + btBlk1stErred = 1; + } + + if (btBlk1st == MS_LB_NOT_USED) { + result = MS_STATUS_ERROR; + goto exit; + } + + /* write protect */ + if ((extdat.status0 & MS_REG_ST0_WP) == MS_REG_ST0_WP_ON) + ms_lib_ctrl_set(info, MS_LIB_CTRL_WRPROTECT); + + result = MS_STATUS_ERROR; + /* 1st Boot Block */ + if (btBlk1stErred == 0) + result = ms_lib_process_bootblock(us, btBlk1st, PageBuffer1); + /* 1st */ + /* 2nd Boot Block */ + if (result && (btBlk2nd != MS_LB_NOT_USED)) + result = ms_lib_process_bootblock(us, btBlk2nd, PageBuffer0); + + if (result) { + result = MS_STATUS_ERROR; + goto exit; + } + + for (TmpBlock = 0; TmpBlock < btBlk1st; TmpBlock++) + info->MS_Lib.Phy2LogMap[TmpBlock] = MS_LB_INITIAL_ERROR; + + info->MS_Lib.Phy2LogMap[btBlk1st] = MS_LB_BOOT_BLOCK; + + if (btBlk2nd != MS_LB_NOT_USED) { + for (TmpBlock = btBlk1st + 1; TmpBlock < btBlk2nd; TmpBlock++) + info->MS_Lib.Phy2LogMap[TmpBlock] = MS_LB_INITIAL_ERROR; + + info->MS_Lib.Phy2LogMap[btBlk2nd] = MS_LB_BOOT_BLOCK; + } + + result = ms_lib_scan_logicalblocknumber(us, btBlk1st); + if (result) + goto exit; + + for (TmpBlock = MS_PHYSICAL_BLOCKS_PER_SEGMENT; + TmpBlock < info->MS_Lib.NumberOfPhyBlock; + TmpBlock += MS_PHYSICAL_BLOCKS_PER_SEGMENT) { + if (ms_count_freeblock(us, TmpBlock) == 0) { + ms_lib_ctrl_set(info, MS_LIB_CTRL_WRPROTECT); + break; + } + } + + /* write */ + if (ms_lib_alloc_writebuf(us)) { + result = MS_NO_MEMORY_ERROR; + goto exit; + } + + result = MS_STATUS_SUCCESS; + +exit: + kfree(PageBuffer1); + kfree(PageBuffer0); + + printk(KERN_INFO "MS_CardInit end\n"); + return result; +} + +static int ene_ms_init(struct us_data *us) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + u8 buf[0x200]; + u16 MSP_BlockSize, MSP_UserAreaBlocks; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + printk(KERN_INFO "transport --- ENE_MSInit\n"); + + /* the same part to test ENE */ + + result = ene_load_bincode(us, MS_INIT_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk(KERN_ERR "Load MS Init Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x01; + + result = ene_send_scsi_cmd(us, FDIR_READ, &buf, 0); + if (result != USB_STOR_XFER_GOOD) { + printk(KERN_ERR "Execution MS Init Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + /* the same part to test ENE */ + info->MS_Status = *(struct MS_STATUS *)&buf[0]; + + if (info->MS_Status.Insert && info->MS_Status.Ready) { + printk(KERN_INFO "Insert = %x\n", info->MS_Status.Insert); + printk(KERN_INFO "Ready = %x\n", info->MS_Status.Ready); + printk(KERN_INFO "IsMSPro = %x\n", info->MS_Status.IsMSPro); + printk(KERN_INFO "IsMSPHG = %x\n", info->MS_Status.IsMSPHG); + printk(KERN_INFO "WtP= %x\n", info->MS_Status.WtP); + if (info->MS_Status.IsMSPro) { + MSP_BlockSize = (buf[6] << 8) | buf[7]; + MSP_UserAreaBlocks = (buf[10] << 8) | buf[11]; + info->MSP_TotalBlock = MSP_BlockSize * MSP_UserAreaBlocks; + } else { + ms_card_init(us); /* Card is MS (to ms.c)*/ + } + usb_stor_dbg(us, "MS Init Code OK !!\n"); + } else { + usb_stor_dbg(us, "MS Card Not Ready --- %x\n", buf[0]); + return USB_STOR_TRANSPORT_ERROR; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ene_sd_init(struct us_data *us) +{ + int result; + u8 buf[0x200]; + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + + usb_stor_dbg(us, "transport --- ENE_SDInit\n"); + /* SD Init Part-1 */ + result = ene_load_bincode(us, SD_INIT1_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Load SD Init Code Part-1 Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF2; + + result = ene_send_scsi_cmd(us, FDIR_READ, NULL, 0); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Execution SD Init Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* SD Init Part-2 */ + result = ene_load_bincode(us, SD_INIT2_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Load SD Init Code Part-2 Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200; + bcb->Flags = US_BULK_FLAG_IN; + bcb->CDB[0] = 0xF1; + + result = ene_send_scsi_cmd(us, FDIR_READ, &buf, 0); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Execution SD Init Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + info->SD_Status = *(struct SD_STATUS *)&buf[0]; + if (info->SD_Status.Insert && info->SD_Status.Ready) { + struct SD_STATUS *s = &info->SD_Status; + + ene_get_card_status(us, (unsigned char *)&buf); + usb_stor_dbg(us, "Insert = %x\n", s->Insert); + usb_stor_dbg(us, "Ready = %x\n", s->Ready); + usb_stor_dbg(us, "IsMMC = %x\n", s->IsMMC); + usb_stor_dbg(us, "HiCapacity = %x\n", s->HiCapacity); + usb_stor_dbg(us, "HiSpeed = %x\n", s->HiSpeed); + usb_stor_dbg(us, "WtP = %x\n", s->WtP); + } else { + usb_stor_dbg(us, "SD Card Not Ready --- %x\n", buf[0]); + return USB_STOR_TRANSPORT_ERROR; + } + return USB_STOR_TRANSPORT_GOOD; +} + + +static int ene_init(struct us_data *us) +{ + int result; + u8 misc_reg03 = 0; + struct ene_ub6250_info *info = (struct ene_ub6250_info *)(us->extra); + + result = ene_get_card_type(us, REG_CARD_STATUS, &misc_reg03); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (misc_reg03 & 0x01) { + if (!info->SD_Status.Ready) { + result = ene_sd_init(us); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + } + } + if (misc_reg03 & 0x02) { + if (!info->MS_Status.Ready) { + result = ene_ms_init(us); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + } + } + return result; +} + +/*----- sd_scsi_irp() ---------*/ +static int sd_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) +{ + int result; + struct ene_ub6250_info *info = (struct ene_ub6250_info *)us->extra; + + info->SrbStatus = SS_SUCCESS; + switch (srb->cmnd[0]) { + case TEST_UNIT_READY: + result = sd_scsi_test_unit_ready(us, srb); + break; /* 0x00 */ + case INQUIRY: + result = sd_scsi_inquiry(us, srb); + break; /* 0x12 */ + case MODE_SENSE: + result = sd_scsi_mode_sense(us, srb); + break; /* 0x1A */ + /* + case START_STOP: + result = SD_SCSI_Start_Stop(us, srb); + break; //0x1B + */ + case READ_CAPACITY: + result = sd_scsi_read_capacity(us, srb); + break; /* 0x25 */ + case READ_10: + result = sd_scsi_read(us, srb); + break; /* 0x28 */ + case WRITE_10: + result = sd_scsi_write(us, srb); + break; /* 0x2A */ + default: + info->SrbStatus = SS_ILLEGAL_REQUEST; + result = USB_STOR_TRANSPORT_FAILED; + break; + } + return result; +} + +/* + * ms_scsi_irp() + */ +static int ms_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) +{ + int result; + struct ene_ub6250_info *info = (struct ene_ub6250_info *)us->extra; + info->SrbStatus = SS_SUCCESS; + switch (srb->cmnd[0]) { + case TEST_UNIT_READY: + result = ms_scsi_test_unit_ready(us, srb); + break; /* 0x00 */ + case INQUIRY: + result = ms_scsi_inquiry(us, srb); + break; /* 0x12 */ + case MODE_SENSE: + result = ms_scsi_mode_sense(us, srb); + break; /* 0x1A */ + case READ_CAPACITY: + result = ms_scsi_read_capacity(us, srb); + break; /* 0x25 */ + case READ_10: + result = ms_scsi_read(us, srb); + break; /* 0x28 */ + case WRITE_10: + result = ms_scsi_write(us, srb); + break; /* 0x2A */ + default: + info->SrbStatus = SS_ILLEGAL_REQUEST; + result = USB_STOR_TRANSPORT_FAILED; + break; + } + return result; +} + +static int ene_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int result = 0; + struct ene_ub6250_info *info = (struct ene_ub6250_info *)(us->extra); + + /*US_DEBUG(usb_stor_show_command(us, srb)); */ + scsi_set_resid(srb, 0); + if (unlikely(!(info->SD_Status.Ready || info->MS_Status.Ready))) { + result = ene_init(us); + } else { + if (info->SD_Status.Ready) + result = sd_scsi_irp(us, srb); + + if (info->MS_Status.Ready) + result = ms_scsi_irp(us, srb); + } + return 0; +} + + +static int ene_ub6250_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int result; + u8 misc_reg03 = 0; + struct us_data *us; + + result = usb_stor_probe1(&us, intf, id, + (id - ene_ub6250_usb_ids) + ene_ub6250_unusual_dev_list); + if (result) + return result; + + /* FIXME: where should the code alloc extra buf ? */ + if (!us->extra) { + us->extra = kzalloc(sizeof(struct ene_ub6250_info), GFP_KERNEL); + if (!us->extra) + return -ENOMEM; + us->extra_destructor = ene_ub6250_info_destructor; + } + + us->transport_name = "ene_ub6250"; + us->transport = ene_transport; + us->max_lun = 0; + + result = usb_stor_probe2(us); + if (result) + return result; + + /* probe card type */ + result = ene_get_card_type(us, REG_CARD_STATUS, &misc_reg03); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_disconnect(intf); + return USB_STOR_TRANSPORT_ERROR; + } + + if (!(misc_reg03 & 0x01)) { + pr_info("ums_eneub6250: The driver only supports SD/MS card. " + "To use SM card, please build driver/staging/keucr\n"); + } + + return result; +} + + +#ifdef CONFIG_PM + +static int ene_ub6250_resume(struct usb_interface *iface) +{ + u8 tmp = 0; + struct us_data *us = usb_get_intfdata(iface); + struct ene_ub6250_info *info = (struct ene_ub6250_info *)(us->extra); + + mutex_lock(&us->dev_mutex); + + if (us->suspend_resume_hook) + (us->suspend_resume_hook)(us, US_RESUME); + + mutex_unlock(&us->dev_mutex); + + info->Power_IsResum = true; + /*info->SD_Status.Ready = 0; */ + info->SD_Status = *(struct SD_STATUS *)&tmp; + info->MS_Status = *(struct MS_STATUS *)&tmp; + info->SM_Status = *(struct SM_STATUS *)&tmp; + + return 0; +} + +static int ene_ub6250_reset_resume(struct usb_interface *iface) +{ + u8 tmp = 0; + struct us_data *us = usb_get_intfdata(iface); + struct ene_ub6250_info *info = (struct ene_ub6250_info *)(us->extra); + + /* Report the reset to the SCSI core */ + usb_stor_reset_resume(iface); + + /* FIXME: Notify the subdrivers that they need to reinitialize + * the device */ + info->Power_IsResum = true; + /*info->SD_Status.Ready = 0; */ + info->SD_Status = *(struct SD_STATUS *)&tmp; + info->MS_Status = *(struct MS_STATUS *)&tmp; + info->SM_Status = *(struct SM_STATUS *)&tmp; + + return 0; +} + +#else + +#define ene_ub6250_resume NULL +#define ene_ub6250_reset_resume NULL + +#endif + +static struct usb_driver ene_ub6250_driver = { + .name = "ums_eneub6250", + .probe = ene_ub6250_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = ene_ub6250_resume, + .reset_resume = ene_ub6250_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = ene_ub6250_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(ene_ub6250_driver); diff --git a/drivers/usb/storage/freecom.c b/drivers/usb/storage/freecom.c new file mode 100644 index 00000000..ef16068b --- /dev/null +++ b/drivers/usb/storage/freecom.c @@ -0,0 +1,560 @@ +/* Driver for Freecom USB/IDE adaptor + * + * Freecom v0.1: + * + * First release + * + * Current development and maintenance by: + * (C) 2000 David Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This driver was developed with information provided in FREECOM's USB + * Programmers Reference Guide. For further information contact Freecom + * (http://www.freecom.de/) + */ + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + +MODULE_DESCRIPTION("Driver for Freecom USB/IDE adaptor"); +MODULE_AUTHOR("David Brown "); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_USB_STORAGE_DEBUG +static void pdump(struct us_data *us, void *ibuffer, int length); +#endif + +/* Bits of HD_STATUS */ +#define ERR_STAT 0x01 +#define DRQ_STAT 0x08 + +/* All of the outgoing packets are 64 bytes long. */ +struct freecom_cb_wrap { + u8 Type; /* Command type. */ + u8 Timeout; /* Timeout in seconds. */ + u8 Atapi[12]; /* An ATAPI packet. */ + u8 Filler[50]; /* Padding Data. */ +}; + +struct freecom_xfer_wrap { + u8 Type; /* Command type. */ + u8 Timeout; /* Timeout in seconds. */ + __le32 Count; /* Number of bytes to transfer. */ + u8 Pad[58]; +} __attribute__ ((packed)); + +struct freecom_ide_out { + u8 Type; /* Type + IDE register. */ + u8 Pad; + __le16 Value; /* Value to write. */ + u8 Pad2[60]; +}; + +struct freecom_ide_in { + u8 Type; /* Type | IDE register. */ + u8 Pad[63]; +}; + +struct freecom_status { + u8 Status; + u8 Reason; + __le16 Count; + u8 Pad[60]; +}; + +/* Freecom stuffs the interrupt status in the INDEX_STAT bit of the ide + * register. */ +#define FCM_INT_STATUS 0x02 /* INDEX_STAT */ +#define FCM_STATUS_BUSY 0x80 + +/* These are the packet types. The low bit indicates that this command + * should wait for an interrupt. */ +#define FCM_PACKET_ATAPI 0x21 +#define FCM_PACKET_STATUS 0x20 + +/* Receive data from the IDE interface. The ATAPI packet has already + * waited, so the data should be immediately available. */ +#define FCM_PACKET_INPUT 0x81 + +/* Send data to the IDE interface. */ +#define FCM_PACKET_OUTPUT 0x01 + +/* Write a value to an ide register. Or the ide register to write after + * munging the address a bit. */ +#define FCM_PACKET_IDE_WRITE 0x40 +#define FCM_PACKET_IDE_READ 0xC0 + +/* All packets (except for status) are 64 bytes long. */ +#define FCM_PACKET_LENGTH 64 +#define FCM_STATUS_PACKET_LENGTH 4 + +static int init_freecom(struct us_data *us); + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id freecom_usb_ids[] = { +# include "unusual_freecom.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, freecom_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev freecom_unusual_dev_list[] = { +# include "unusual_freecom.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + +static int +freecom_readdata (struct scsi_cmnd *srb, struct us_data *us, + unsigned int ipipe, unsigned int opipe, int count) +{ + struct freecom_xfer_wrap *fxfr = + (struct freecom_xfer_wrap *) us->iobuf; + int result; + + fxfr->Type = FCM_PACKET_INPUT | 0x00; + fxfr->Timeout = 0; /* Short timeout for debugging. */ + fxfr->Count = cpu_to_le32 (count); + memset (fxfr->Pad, 0, sizeof (fxfr->Pad)); + + usb_stor_dbg(us, "Read data Freecom! (c=%d)\n", count); + + /* Issue the transfer command. */ + result = usb_stor_bulk_transfer_buf (us, opipe, fxfr, + FCM_PACKET_LENGTH, NULL); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Freecom readdata transport error\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* Now transfer all of our blocks. */ + usb_stor_dbg(us, "Start of read\n"); + result = usb_stor_bulk_srb(us, ipipe, srb); + usb_stor_dbg(us, "freecom_readdata done!\n"); + + if (result > USB_STOR_XFER_SHORT) + return USB_STOR_TRANSPORT_ERROR; + return USB_STOR_TRANSPORT_GOOD; +} + +static int +freecom_writedata (struct scsi_cmnd *srb, struct us_data *us, + int unsigned ipipe, unsigned int opipe, int count) +{ + struct freecom_xfer_wrap *fxfr = + (struct freecom_xfer_wrap *) us->iobuf; + int result; + + fxfr->Type = FCM_PACKET_OUTPUT | 0x00; + fxfr->Timeout = 0; /* Short timeout for debugging. */ + fxfr->Count = cpu_to_le32 (count); + memset (fxfr->Pad, 0, sizeof (fxfr->Pad)); + + usb_stor_dbg(us, "Write data Freecom! (c=%d)\n", count); + + /* Issue the transfer command. */ + result = usb_stor_bulk_transfer_buf (us, opipe, fxfr, + FCM_PACKET_LENGTH, NULL); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Freecom writedata transport error\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* Now transfer all of our blocks. */ + usb_stor_dbg(us, "Start of write\n"); + result = usb_stor_bulk_srb(us, opipe, srb); + + usb_stor_dbg(us, "freecom_writedata done!\n"); + if (result > USB_STOR_XFER_SHORT) + return USB_STOR_TRANSPORT_ERROR; + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Transport for the Freecom USB/IDE adaptor. + * + */ +static int freecom_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + struct freecom_cb_wrap *fcb; + struct freecom_status *fst; + unsigned int ipipe, opipe; /* We need both pipes. */ + int result; + unsigned int partial; + int length; + + fcb = (struct freecom_cb_wrap *) us->iobuf; + fst = (struct freecom_status *) us->iobuf; + + usb_stor_dbg(us, "Freecom TRANSPORT STARTED\n"); + + /* Get handles for both transports. */ + opipe = us->send_bulk_pipe; + ipipe = us->recv_bulk_pipe; + + /* The ATAPI Command always goes out first. */ + fcb->Type = FCM_PACKET_ATAPI | 0x00; + fcb->Timeout = 0; + memcpy (fcb->Atapi, srb->cmnd, 12); + memset (fcb->Filler, 0, sizeof (fcb->Filler)); + + US_DEBUG(pdump(us, srb->cmnd, 12)); + + /* Send it out. */ + result = usb_stor_bulk_transfer_buf (us, opipe, fcb, + FCM_PACKET_LENGTH, NULL); + + /* The Freecom device will only fail if there is something wrong in + * USB land. It returns the status in its own registers, which + * come back in the bulk pipe. */ + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "freecom transport error\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* There are times we can optimize out this status read, but it + * doesn't hurt us to always do it now. */ + result = usb_stor_bulk_transfer_buf (us, ipipe, fst, + FCM_STATUS_PACKET_LENGTH, &partial); + usb_stor_dbg(us, "foo Status result %d %u\n", result, partial); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + US_DEBUG(pdump(us, (void *)fst, partial)); + + /* The firmware will time-out commands after 20 seconds. Some commands + * can legitimately take longer than this, so we use a different + * command that only waits for the interrupt and then sends status, + * without having to send a new ATAPI command to the device. + * + * NOTE: There is some indication that a data transfer after a timeout + * may not work, but that is a condition that should never happen. + */ + while (fst->Status & FCM_STATUS_BUSY) { + usb_stor_dbg(us, "20 second USB/ATAPI bridge TIMEOUT occurred!\n"); + usb_stor_dbg(us, "fst->Status is %x\n", fst->Status); + + /* Get the status again */ + fcb->Type = FCM_PACKET_STATUS; + fcb->Timeout = 0; + memset (fcb->Atapi, 0, sizeof(fcb->Atapi)); + memset (fcb->Filler, 0, sizeof (fcb->Filler)); + + /* Send it out. */ + result = usb_stor_bulk_transfer_buf (us, opipe, fcb, + FCM_PACKET_LENGTH, NULL); + + /* The Freecom device will only fail if there is something + * wrong in USB land. It returns the status in its own + * registers, which come back in the bulk pipe. + */ + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "freecom transport error\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* get the data */ + result = usb_stor_bulk_transfer_buf (us, ipipe, fst, + FCM_STATUS_PACKET_LENGTH, &partial); + + usb_stor_dbg(us, "bar Status result %d %u\n", result, partial); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + US_DEBUG(pdump(us, (void *)fst, partial)); + } + + if (partial != 4) + return USB_STOR_TRANSPORT_ERROR; + if ((fst->Status & 1) != 0) { + usb_stor_dbg(us, "operation failed\n"); + return USB_STOR_TRANSPORT_FAILED; + } + + /* The device might not have as much data available as we + * requested. If you ask for more than the device has, this reads + * and such will hang. */ + usb_stor_dbg(us, "Device indicates that it has %d bytes available\n", + le16_to_cpu(fst->Count)); + usb_stor_dbg(us, "SCSI requested %d\n", scsi_bufflen(srb)); + + /* Find the length we desire to read. */ + switch (srb->cmnd[0]) { + case INQUIRY: + case REQUEST_SENSE: /* 16 or 18 bytes? spec says 18, lots of devices only have 16 */ + case MODE_SENSE: + case MODE_SENSE_10: + length = le16_to_cpu(fst->Count); + break; + default: + length = scsi_bufflen(srb); + } + + /* verify that this amount is legal */ + if (length > scsi_bufflen(srb)) { + length = scsi_bufflen(srb); + usb_stor_dbg(us, "Truncating request to match buffer length: %d\n", + length); + } + + /* What we do now depends on what direction the data is supposed to + * move in. */ + + switch (us->srb->sc_data_direction) { + case DMA_FROM_DEVICE: + /* catch bogus "read 0 length" case */ + if (!length) + break; + /* Make sure that the status indicates that the device + * wants data as well. */ + if ((fst->Status & DRQ_STAT) == 0 || (fst->Reason & 3) != 2) { + usb_stor_dbg(us, "SCSI wants data, drive doesn't have any\n"); + return USB_STOR_TRANSPORT_FAILED; + } + result = freecom_readdata (srb, us, ipipe, opipe, length); + if (result != USB_STOR_TRANSPORT_GOOD) + return result; + + usb_stor_dbg(us, "Waiting for status\n"); + result = usb_stor_bulk_transfer_buf (us, ipipe, fst, + FCM_PACKET_LENGTH, &partial); + US_DEBUG(pdump(us, (void *)fst, partial)); + + if (partial != 4 || result > USB_STOR_XFER_SHORT) + return USB_STOR_TRANSPORT_ERROR; + if ((fst->Status & ERR_STAT) != 0) { + usb_stor_dbg(us, "operation failed\n"); + return USB_STOR_TRANSPORT_FAILED; + } + if ((fst->Reason & 3) != 3) { + usb_stor_dbg(us, "Drive seems still hungry\n"); + return USB_STOR_TRANSPORT_FAILED; + } + usb_stor_dbg(us, "Transfer happy\n"); + break; + + case DMA_TO_DEVICE: + /* catch bogus "write 0 length" case */ + if (!length) + break; + /* Make sure the status indicates that the device wants to + * send us data. */ + /* !!IMPLEMENT!! */ + result = freecom_writedata (srb, us, ipipe, opipe, length); + if (result != USB_STOR_TRANSPORT_GOOD) + return result; + + usb_stor_dbg(us, "Waiting for status\n"); + result = usb_stor_bulk_transfer_buf (us, ipipe, fst, + FCM_PACKET_LENGTH, &partial); + + if (partial != 4 || result > USB_STOR_XFER_SHORT) + return USB_STOR_TRANSPORT_ERROR; + if ((fst->Status & ERR_STAT) != 0) { + usb_stor_dbg(us, "operation failed\n"); + return USB_STOR_TRANSPORT_FAILED; + } + if ((fst->Reason & 3) != 3) { + usb_stor_dbg(us, "Drive seems still hungry\n"); + return USB_STOR_TRANSPORT_FAILED; + } + + usb_stor_dbg(us, "Transfer happy\n"); + break; + + + case DMA_NONE: + /* Easy, do nothing. */ + break; + + default: + /* should never hit here -- filtered in usb.c */ + usb_stor_dbg(us, "freecom unimplemented direction: %d\n", + us->srb->sc_data_direction); + /* Return fail, SCSI seems to handle this better. */ + return USB_STOR_TRANSPORT_FAILED; + break; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +static int init_freecom(struct us_data *us) +{ + int result; + char *buffer = us->iobuf; + + /* The DMA-mapped I/O buffer is 64 bytes long, just right for + * all our packets. No need to allocate any extra buffer space. + */ + + result = usb_stor_control_msg(us, us->recv_ctrl_pipe, + 0x4c, 0xc0, 0x4346, 0x0, buffer, 0x20, 3*HZ); + buffer[32] = '\0'; + usb_stor_dbg(us, "String returned from FC init is: %s\n", buffer); + + /* Special thanks to the people at Freecom for providing me with + * this "magic sequence", which they use in their Windows and MacOS + * drivers to make sure that all the attached perhiperals are + * properly reset. + */ + + /* send reset */ + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + 0x4d, 0x40, 0x24d8, 0x0, NULL, 0x0, 3*HZ); + usb_stor_dbg(us, "result from activate reset is %d\n", result); + + /* wait 250ms */ + mdelay(250); + + /* clear reset */ + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + 0x4d, 0x40, 0x24f8, 0x0, NULL, 0x0, 3*HZ); + usb_stor_dbg(us, "result from clear reset is %d\n", result); + + /* wait 3 seconds */ + mdelay(3 * 1000); + + return USB_STOR_TRANSPORT_GOOD; +} + +static int usb_stor_freecom_reset(struct us_data *us) +{ + printk (KERN_CRIT "freecom reset called\n"); + + /* We don't really have this feature. */ + return FAILED; +} + +#ifdef CONFIG_USB_STORAGE_DEBUG +static void pdump(struct us_data *us, void *ibuffer, int length) +{ + static char line[80]; + int offset = 0; + unsigned char *buffer = (unsigned char *) ibuffer; + int i, j; + int from, base; + + offset = 0; + for (i = 0; i < length; i++) { + if ((i & 15) == 0) { + if (i > 0) { + offset += sprintf (line+offset, " - "); + for (j = i - 16; j < i; j++) { + if (buffer[j] >= 32 && buffer[j] <= 126) + line[offset++] = buffer[j]; + else + line[offset++] = '.'; + } + line[offset] = 0; + usb_stor_dbg(us, "%s\n", line); + offset = 0; + } + offset += sprintf (line+offset, "%08x:", i); + } else if ((i & 7) == 0) { + offset += sprintf (line+offset, " -"); + } + offset += sprintf (line+offset, " %02x", buffer[i] & 0xff); + } + + /* Add the last "chunk" of data. */ + from = (length - 1) % 16; + base = ((length - 1) / 16) * 16; + + for (i = from + 1; i < 16; i++) + offset += sprintf (line+offset, " "); + if (from < 8) + offset += sprintf (line+offset, " "); + offset += sprintf (line+offset, " - "); + + for (i = 0; i <= from; i++) { + if (buffer[base+i] >= 32 && buffer[base+i] <= 126) + line[offset++] = buffer[base+i]; + else + line[offset++] = '.'; + } + line[offset] = 0; + usb_stor_dbg(us, "%s\n", line); + offset = 0; +} +#endif + +static int freecom_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - freecom_usb_ids) + freecom_unusual_dev_list); + if (result) + return result; + + us->transport_name = "Freecom"; + us->transport = freecom_transport; + us->transport_reset = usb_stor_freecom_reset; + us->max_lun = 0; + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver freecom_driver = { + .name = "ums-freecom", + .probe = freecom_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = freecom_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(freecom_driver); diff --git a/drivers/usb/storage/initializers.c b/drivers/usb/storage/initializers.c new file mode 100644 index 00000000..5a8b5ff1 --- /dev/null +++ b/drivers/usb/storage/initializers.c @@ -0,0 +1,106 @@ +/* Special Initializers for certain USB Mass Storage devices + * + * Current development and maintenance by: + * (c) 1999, 2000 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include "usb.h" +#include "initializers.h" +#include "debug.h" +#include "transport.h" + +/* This places the Shuttle/SCM USB<->SCSI bridge devices in multi-target + * mode */ +int usb_stor_euscsi_init(struct us_data *us) +{ + int result; + + usb_stor_dbg(us, "Attempting to init eUSCSI bridge...\n"); + us->iobuf[0] = 0x1; + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + 0x0C, USB_RECIP_INTERFACE | USB_TYPE_VENDOR, + 0x01, 0x0, us->iobuf, 0x1, 5000); + usb_stor_dbg(us, "-- result is %d\n", result); + + return 0; +} + +/* This function is required to activate all four slots on the UCR-61S2B + * flash reader */ +int usb_stor_ucr61s2b_init(struct us_data *us) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap*) us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap*) us->iobuf; + int res; + unsigned int partial; + static char init_string[] = "\xec\x0a\x06\x00$PCCHIPS"; + + usb_stor_dbg(us, "Sending UCR-61S2B initialization packet...\n"); + + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->Tag = 0; + bcb->DataTransferLength = cpu_to_le32(0); + bcb->Flags = bcb->Lun = 0; + bcb->Length = sizeof(init_string) - 1; + memset(bcb->CDB, 0, sizeof(bcb->CDB)); + memcpy(bcb->CDB, init_string, sizeof(init_string) - 1); + + res = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, bcb, + US_BULK_CB_WRAP_LEN, &partial); + if (res) + return -EIO; + + usb_stor_dbg(us, "Getting status packet...\n"); + res = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, &partial); + if (res) + return -EIO; + + return 0; +} + +/* This places the HUAWEI E220 devices in multi-port mode */ +int usb_stor_huawei_e220_init(struct us_data *us) +{ + int result; + + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + USB_REQ_SET_FEATURE, + USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 0x01, 0x0, NULL, 0x0, 1000); + usb_stor_dbg(us, "Huawei mode set result is %d\n", result); + return 0; +} diff --git a/drivers/usb/storage/initializers.h b/drivers/usb/storage/initializers.h new file mode 100644 index 00000000..529327fb --- /dev/null +++ b/drivers/usb/storage/initializers.h @@ -0,0 +1,50 @@ +/* Header file for Special Initializers for certain USB Mass Storage devices + * + * Current development and maintenance by: + * (c) 1999, 2000 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "usb.h" +#include "transport.h" + +/* This places the Shuttle/SCM USB<->SCSI bridge devices in multi-target + * mode */ +int usb_stor_euscsi_init(struct us_data *us); + +/* This function is required to activate all four slots on the UCR-61S2B + * flash reader */ +int usb_stor_ucr61s2b_init(struct us_data *us); + +/* This places the HUAWEI E220 devices in multi-port mode */ +int usb_stor_huawei_e220_init(struct us_data *us); diff --git a/drivers/usb/storage/isd200.c b/drivers/usb/storage/isd200.c new file mode 100644 index 00000000..599d8bff --- /dev/null +++ b/drivers/usb/storage/isd200.c @@ -0,0 +1,1572 @@ +/* Transport & Protocol Driver for In-System Design, Inc. ISD200 ASIC + * + * Current development and maintenance: + * (C) 2001-2002 Björn Stenberg (bjorn@haxx.se) + * + * Developed with the assistance of: + * (C) 2002 Alan Stern + * + * Initial work: + * (C) 2000 In-System Design, Inc. (support@in-system.com) + * + * The ISD200 ASIC does not natively support ATA devices. The chip + * does implement an interface, the ATA Command Block (ATACB) which provides + * a means of passing ATA commands and ATA register accesses to a device. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * History: + * + * 2002-10-19: Removed the specialized transfer routines. + * (Alan Stern ) + * 2001-02-24: Removed lots of duplicate code and simplified the structure. + * (bjorn@haxx.se) + * 2002-01-16: Fixed endianness bug so it works on the ppc arch. + * (Luc Saillard ) + * 2002-01-17: All bitfields removed. + * (bjorn@haxx.se) + */ + + +/* Include files */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" +#include "scsiglue.h" + +MODULE_DESCRIPTION("Driver for In-System Design, Inc. ISD200 ASIC"); +MODULE_AUTHOR("Björn Stenberg "); +MODULE_LICENSE("GPL"); + +static int isd200_Initialization(struct us_data *us); + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id isd200_usb_ids[] = { +# include "unusual_isd200.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, isd200_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev isd200_unusual_dev_list[] = { +# include "unusual_isd200.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + +/* Timeout defines (in Seconds) */ + +#define ISD200_ENUM_BSY_TIMEOUT 35 +#define ISD200_ENUM_DETECT_TIMEOUT 30 +#define ISD200_DEFAULT_TIMEOUT 30 + +/* device flags */ +#define DF_ATA_DEVICE 0x0001 +#define DF_MEDIA_STATUS_ENABLED 0x0002 +#define DF_REMOVABLE_MEDIA 0x0004 + +/* capability bit definitions */ +#define CAPABILITY_DMA 0x01 +#define CAPABILITY_LBA 0x02 + +/* command_setX bit definitions */ +#define COMMANDSET_REMOVABLE 0x02 +#define COMMANDSET_MEDIA_STATUS 0x10 + +/* ATA Vendor Specific defines */ +#define ATA_ADDRESS_DEVHEAD_STD 0xa0 +#define ATA_ADDRESS_DEVHEAD_LBA_MODE 0x40 +#define ATA_ADDRESS_DEVHEAD_SLAVE 0x10 + +/* Action Select bits */ +#define ACTION_SELECT_0 0x01 +#define ACTION_SELECT_1 0x02 +#define ACTION_SELECT_2 0x04 +#define ACTION_SELECT_3 0x08 +#define ACTION_SELECT_4 0x10 +#define ACTION_SELECT_5 0x20 +#define ACTION_SELECT_6 0x40 +#define ACTION_SELECT_7 0x80 + +/* Register Select bits */ +#define REG_ALTERNATE_STATUS 0x01 +#define REG_DEVICE_CONTROL 0x01 +#define REG_ERROR 0x02 +#define REG_FEATURES 0x02 +#define REG_SECTOR_COUNT 0x04 +#define REG_SECTOR_NUMBER 0x08 +#define REG_CYLINDER_LOW 0x10 +#define REG_CYLINDER_HIGH 0x20 +#define REG_DEVICE_HEAD 0x40 +#define REG_STATUS 0x80 +#define REG_COMMAND 0x80 + +/* ATA registers offset definitions */ +#define ATA_REG_ERROR_OFFSET 1 +#define ATA_REG_LCYL_OFFSET 4 +#define ATA_REG_HCYL_OFFSET 5 +#define ATA_REG_STATUS_OFFSET 7 + +/* ATA error definitions not in */ +#define ATA_ERROR_MEDIA_CHANGE 0x20 + +/* ATA command definitions not in */ +#define ATA_COMMAND_GET_MEDIA_STATUS 0xDA +#define ATA_COMMAND_MEDIA_EJECT 0xED + +/* ATA drive control definitions */ +#define ATA_DC_DISABLE_INTERRUPTS 0x02 +#define ATA_DC_RESET_CONTROLLER 0x04 +#define ATA_DC_REENABLE_CONTROLLER 0x00 + +/* + * General purpose return codes + */ + +#define ISD200_ERROR -1 +#define ISD200_GOOD 0 + +/* + * Transport return codes + */ + +#define ISD200_TRANSPORT_GOOD 0 /* Transport good, command good */ +#define ISD200_TRANSPORT_FAILED 1 /* Transport good, command failed */ +#define ISD200_TRANSPORT_ERROR 2 /* Transport bad (i.e. device dead) */ + +/* driver action codes */ +#define ACTION_READ_STATUS 0 +#define ACTION_RESET 1 +#define ACTION_REENABLE 2 +#define ACTION_SOFT_RESET 3 +#define ACTION_ENUM 4 +#define ACTION_IDENTIFY 5 + + +/* + * ata_cdb struct + */ + + +union ata_cdb { + struct { + unsigned char SignatureByte0; + unsigned char SignatureByte1; + unsigned char ActionSelect; + unsigned char RegisterSelect; + unsigned char TransferBlockSize; + unsigned char WriteData3F6; + unsigned char WriteData1F1; + unsigned char WriteData1F2; + unsigned char WriteData1F3; + unsigned char WriteData1F4; + unsigned char WriteData1F5; + unsigned char WriteData1F6; + unsigned char WriteData1F7; + unsigned char Reserved[3]; + } generic; + + struct { + unsigned char SignatureByte0; + unsigned char SignatureByte1; + unsigned char ActionSelect; + unsigned char RegisterSelect; + unsigned char TransferBlockSize; + unsigned char AlternateStatusByte; + unsigned char ErrorByte; + unsigned char SectorCountByte; + unsigned char SectorNumberByte; + unsigned char CylinderLowByte; + unsigned char CylinderHighByte; + unsigned char DeviceHeadByte; + unsigned char StatusByte; + unsigned char Reserved[3]; + } read; + + struct { + unsigned char SignatureByte0; + unsigned char SignatureByte1; + unsigned char ActionSelect; + unsigned char RegisterSelect; + unsigned char TransferBlockSize; + unsigned char DeviceControlByte; + unsigned char FeaturesByte; + unsigned char SectorCountByte; + unsigned char SectorNumberByte; + unsigned char CylinderLowByte; + unsigned char CylinderHighByte; + unsigned char DeviceHeadByte; + unsigned char CommandByte; + unsigned char Reserved[3]; + } write; +}; + + +/* + * Inquiry data structure. This is the data returned from the target + * after it receives an inquiry. + * + * This structure may be extended by the number of bytes specified + * in the field AdditionalLength. The defined size constant only + * includes fields through ProductRevisionLevel. + */ + +/* + * DeviceType field + */ +#define DIRECT_ACCESS_DEVICE 0x00 /* disks */ +#define DEVICE_REMOVABLE 0x80 + +struct inquiry_data { + unsigned char DeviceType; + unsigned char DeviceTypeModifier; + unsigned char Versions; + unsigned char Format; + unsigned char AdditionalLength; + unsigned char Reserved[2]; + unsigned char Capability; + unsigned char VendorId[8]; + unsigned char ProductId[16]; + unsigned char ProductRevisionLevel[4]; + unsigned char VendorSpecific[20]; + unsigned char Reserved3[40]; +} __attribute__ ((packed)); + +/* + * INQUIRY data buffer size + */ + +#define INQUIRYDATABUFFERSIZE 36 + + +/* + * ISD200 CONFIG data struct + */ + +#define ATACFG_TIMING 0x0f +#define ATACFG_ATAPI_RESET 0x10 +#define ATACFG_MASTER 0x20 +#define ATACFG_BLOCKSIZE 0xa0 + +#define ATACFGE_LAST_LUN 0x07 +#define ATACFGE_DESC_OVERRIDE 0x08 +#define ATACFGE_STATE_SUSPEND 0x10 +#define ATACFGE_SKIP_BOOT 0x20 +#define ATACFGE_CONF_DESC2 0x40 +#define ATACFGE_INIT_STATUS 0x80 + +#define CFG_CAPABILITY_SRST 0x01 + +struct isd200_config { + unsigned char EventNotification; + unsigned char ExternalClock; + unsigned char ATAInitTimeout; + unsigned char ATAConfig; + unsigned char ATAMajorCommand; + unsigned char ATAMinorCommand; + unsigned char ATAExtraConfig; + unsigned char Capability; +}__attribute__ ((packed)); + + +/* + * ISD200 driver information struct + */ + +struct isd200_info { + struct inquiry_data InquiryData; + u16 *id; + struct isd200_config ConfigData; + unsigned char *RegsBuf; + unsigned char ATARegs[8]; + unsigned char DeviceHead; + unsigned char DeviceFlags; + + /* maximum number of LUNs supported */ + unsigned char MaxLUNs; + unsigned char cmnd[BLK_MAX_CDB]; + struct scsi_cmnd srb; + struct scatterlist sg; +}; + + +/* + * Read Capacity Data - returned in Big Endian format + */ + +struct read_capacity_data { + __be32 LogicalBlockAddress; + __be32 BytesPerBlock; +}; + +/* + * Read Block Limits Data - returned in Big Endian format + * This structure returns the maximum and minimum block + * size for a TAPE device. + */ + +struct read_block_limits { + unsigned char Reserved; + unsigned char BlockMaximumSize[3]; + unsigned char BlockMinimumSize[2]; +}; + + +/* + * Sense Data Format + */ + +#define SENSE_ERRCODE 0x7f +#define SENSE_ERRCODE_VALID 0x80 +#define SENSE_FLAG_SENSE_KEY 0x0f +#define SENSE_FLAG_BAD_LENGTH 0x20 +#define SENSE_FLAG_END_OF_MEDIA 0x40 +#define SENSE_FLAG_FILE_MARK 0x80 +struct sense_data { + unsigned char ErrorCode; + unsigned char SegmentNumber; + unsigned char Flags; + unsigned char Information[4]; + unsigned char AdditionalSenseLength; + unsigned char CommandSpecificInformation[4]; + unsigned char AdditionalSenseCode; + unsigned char AdditionalSenseCodeQualifier; + unsigned char FieldReplaceableUnitCode; + unsigned char SenseKeySpecific[3]; +} __attribute__ ((packed)); + +/* + * Default request sense buffer size + */ + +#define SENSE_BUFFER_SIZE 18 + +/*********************************************************************** + * Helper routines + ***********************************************************************/ + +/************************************************************************** + * isd200_build_sense + * + * Builds an artificial sense buffer to report the results of a + * failed command. + * + * RETURNS: + * void + */ +static void isd200_build_sense(struct us_data *us, struct scsi_cmnd *srb) +{ + struct isd200_info *info = (struct isd200_info *)us->extra; + struct sense_data *buf = (struct sense_data *) &srb->sense_buffer[0]; + unsigned char error = info->ATARegs[ATA_REG_ERROR_OFFSET]; + + if(error & ATA_ERROR_MEDIA_CHANGE) { + buf->ErrorCode = 0x70 | SENSE_ERRCODE_VALID; + buf->AdditionalSenseLength = 0xb; + buf->Flags = UNIT_ATTENTION; + buf->AdditionalSenseCode = 0; + buf->AdditionalSenseCodeQualifier = 0; + } else if (error & ATA_MCR) { + buf->ErrorCode = 0x70 | SENSE_ERRCODE_VALID; + buf->AdditionalSenseLength = 0xb; + buf->Flags = UNIT_ATTENTION; + buf->AdditionalSenseCode = 0; + buf->AdditionalSenseCodeQualifier = 0; + } else if (error & ATA_TRK0NF) { + buf->ErrorCode = 0x70 | SENSE_ERRCODE_VALID; + buf->AdditionalSenseLength = 0xb; + buf->Flags = NOT_READY; + buf->AdditionalSenseCode = 0; + buf->AdditionalSenseCodeQualifier = 0; + } else if (error & ATA_UNC) { + buf->ErrorCode = 0x70 | SENSE_ERRCODE_VALID; + buf->AdditionalSenseLength = 0xb; + buf->Flags = DATA_PROTECT; + buf->AdditionalSenseCode = 0; + buf->AdditionalSenseCodeQualifier = 0; + } else { + buf->ErrorCode = 0; + buf->AdditionalSenseLength = 0; + buf->Flags = 0; + buf->AdditionalSenseCode = 0; + buf->AdditionalSenseCodeQualifier = 0; + } +} + + +/*********************************************************************** + * Transport routines + ***********************************************************************/ + +/************************************************************************** + * isd200_set_srb(), isd200_srb_set_bufflen() + * + * Two helpers to facilitate in initialization of scsi_cmnd structure + * Will need to change when struct scsi_cmnd changes + */ +static void isd200_set_srb(struct isd200_info *info, + enum dma_data_direction dir, void* buff, unsigned bufflen) +{ + struct scsi_cmnd *srb = &info->srb; + + if (buff) + sg_init_one(&info->sg, buff, bufflen); + + srb->sc_data_direction = dir; + srb->sdb.table.sgl = buff ? &info->sg : NULL; + srb->sdb.length = bufflen; + srb->sdb.table.nents = buff ? 1 : 0; +} + +static void isd200_srb_set_bufflen(struct scsi_cmnd *srb, unsigned bufflen) +{ + srb->sdb.length = bufflen; +} + + +/************************************************************************** + * isd200_action + * + * Routine for sending commands to the isd200 + * + * RETURNS: + * ISD status code + */ +static int isd200_action( struct us_data *us, int action, + void* pointer, int value ) +{ + union ata_cdb ata; + /* static to prevent this large struct being placed on the valuable stack */ + static struct scsi_device srb_dev; + struct isd200_info *info = (struct isd200_info *)us->extra; + struct scsi_cmnd *srb = &info->srb; + int status; + + memset(&ata, 0, sizeof(ata)); + srb->cmnd = info->cmnd; + srb->device = &srb_dev; + + ata.generic.SignatureByte0 = info->ConfigData.ATAMajorCommand; + ata.generic.SignatureByte1 = info->ConfigData.ATAMinorCommand; + ata.generic.TransferBlockSize = 1; + + switch ( action ) { + case ACTION_READ_STATUS: + usb_stor_dbg(us, " isd200_action(READ_STATUS)\n"); + ata.generic.ActionSelect = ACTION_SELECT_0|ACTION_SELECT_2; + ata.generic.RegisterSelect = + REG_CYLINDER_LOW | REG_CYLINDER_HIGH | + REG_STATUS | REG_ERROR; + isd200_set_srb(info, DMA_FROM_DEVICE, pointer, value); + break; + + case ACTION_ENUM: + usb_stor_dbg(us, " isd200_action(ENUM,0x%02x)\n", value); + ata.generic.ActionSelect = ACTION_SELECT_1|ACTION_SELECT_2| + ACTION_SELECT_3|ACTION_SELECT_4| + ACTION_SELECT_5; + ata.generic.RegisterSelect = REG_DEVICE_HEAD; + ata.write.DeviceHeadByte = value; + isd200_set_srb(info, DMA_NONE, NULL, 0); + break; + + case ACTION_RESET: + usb_stor_dbg(us, " isd200_action(RESET)\n"); + ata.generic.ActionSelect = ACTION_SELECT_1|ACTION_SELECT_2| + ACTION_SELECT_3|ACTION_SELECT_4; + ata.generic.RegisterSelect = REG_DEVICE_CONTROL; + ata.write.DeviceControlByte = ATA_DC_RESET_CONTROLLER; + isd200_set_srb(info, DMA_NONE, NULL, 0); + break; + + case ACTION_REENABLE: + usb_stor_dbg(us, " isd200_action(REENABLE)\n"); + ata.generic.ActionSelect = ACTION_SELECT_1|ACTION_SELECT_2| + ACTION_SELECT_3|ACTION_SELECT_4; + ata.generic.RegisterSelect = REG_DEVICE_CONTROL; + ata.write.DeviceControlByte = ATA_DC_REENABLE_CONTROLLER; + isd200_set_srb(info, DMA_NONE, NULL, 0); + break; + + case ACTION_SOFT_RESET: + usb_stor_dbg(us, " isd200_action(SOFT_RESET)\n"); + ata.generic.ActionSelect = ACTION_SELECT_1|ACTION_SELECT_5; + ata.generic.RegisterSelect = REG_DEVICE_HEAD | REG_COMMAND; + ata.write.DeviceHeadByte = info->DeviceHead; + ata.write.CommandByte = ATA_CMD_DEV_RESET; + isd200_set_srb(info, DMA_NONE, NULL, 0); + break; + + case ACTION_IDENTIFY: + usb_stor_dbg(us, " isd200_action(IDENTIFY)\n"); + ata.generic.RegisterSelect = REG_COMMAND; + ata.write.CommandByte = ATA_CMD_ID_ATA; + isd200_set_srb(info, DMA_FROM_DEVICE, info->id, + ATA_ID_WORDS * 2); + break; + + default: + usb_stor_dbg(us, "Error: Undefined action %d\n", action); + return ISD200_ERROR; + } + + memcpy(srb->cmnd, &ata, sizeof(ata.generic)); + srb->cmd_len = sizeof(ata.generic); + status = usb_stor_Bulk_transport(srb, us); + if (status == USB_STOR_TRANSPORT_GOOD) + status = ISD200_GOOD; + else { + usb_stor_dbg(us, " isd200_action(0x%02x) error: %d\n", + action, status); + status = ISD200_ERROR; + /* need to reset device here */ + } + + return status; +} + +/************************************************************************** + * isd200_read_regs + * + * Read ATA Registers + * + * RETURNS: + * ISD status code + */ +static int isd200_read_regs( struct us_data *us ) +{ + struct isd200_info *info = (struct isd200_info *)us->extra; + int retStatus = ISD200_GOOD; + int transferStatus; + + usb_stor_dbg(us, "Entering isd200_IssueATAReadRegs\n"); + + transferStatus = isd200_action( us, ACTION_READ_STATUS, + info->RegsBuf, sizeof(info->ATARegs) ); + if (transferStatus != ISD200_TRANSPORT_GOOD) { + usb_stor_dbg(us, " Error reading ATA registers\n"); + retStatus = ISD200_ERROR; + } else { + memcpy(info->ATARegs, info->RegsBuf, sizeof(info->ATARegs)); + usb_stor_dbg(us, " Got ATA Register[ATA_REG_ERROR_OFFSET] = 0x%x\n", + info->ATARegs[ATA_REG_ERROR_OFFSET]); + } + + return retStatus; +} + + +/************************************************************************** + * Invoke the transport and basic error-handling/recovery methods + * + * This is used by the protocol layers to actually send the message to + * the device and receive the response. + */ +static void isd200_invoke_transport( struct us_data *us, + struct scsi_cmnd *srb, + union ata_cdb *ataCdb ) +{ + int need_auto_sense = 0; + int transferStatus; + int result; + + /* send the command to the transport layer */ + memcpy(srb->cmnd, ataCdb, sizeof(ataCdb->generic)); + srb->cmd_len = sizeof(ataCdb->generic); + transferStatus = usb_stor_Bulk_transport(srb, us); + + /* if the command gets aborted by the higher layers, we need to + * short-circuit all other processing + */ + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + usb_stor_dbg(us, "-- command was aborted\n"); + goto Handle_Abort; + } + + switch (transferStatus) { + + case USB_STOR_TRANSPORT_GOOD: + /* Indicate a good result */ + srb->result = SAM_STAT_GOOD; + break; + + case USB_STOR_TRANSPORT_NO_SENSE: + usb_stor_dbg(us, "-- transport indicates protocol failure\n"); + srb->result = SAM_STAT_CHECK_CONDITION; + return; + + case USB_STOR_TRANSPORT_FAILED: + usb_stor_dbg(us, "-- transport indicates command failure\n"); + need_auto_sense = 1; + break; + + case USB_STOR_TRANSPORT_ERROR: + usb_stor_dbg(us, "-- transport indicates transport error\n"); + srb->result = DID_ERROR << 16; + /* Need reset here */ + return; + + default: + usb_stor_dbg(us, "-- transport indicates unknown error\n"); + srb->result = DID_ERROR << 16; + /* Need reset here */ + return; + } + + if ((scsi_get_resid(srb) > 0) && + !((srb->cmnd[0] == REQUEST_SENSE) || + (srb->cmnd[0] == INQUIRY) || + (srb->cmnd[0] == MODE_SENSE) || + (srb->cmnd[0] == LOG_SENSE) || + (srb->cmnd[0] == MODE_SENSE_10))) { + usb_stor_dbg(us, "-- unexpectedly short transfer\n"); + need_auto_sense = 1; + } + + if (need_auto_sense) { + result = isd200_read_regs(us); + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + usb_stor_dbg(us, "-- auto-sense aborted\n"); + goto Handle_Abort; + } + if (result == ISD200_GOOD) { + isd200_build_sense(us, srb); + srb->result = SAM_STAT_CHECK_CONDITION; + + /* If things are really okay, then let's show that */ + if ((srb->sense_buffer[2] & 0xf) == 0x0) + srb->result = SAM_STAT_GOOD; + } else { + srb->result = DID_ERROR << 16; + /* Need reset here */ + } + } + + /* Regardless of auto-sense, if we _know_ we have an error + * condition, show that in the result code + */ + if (transferStatus == USB_STOR_TRANSPORT_FAILED) + srb->result = SAM_STAT_CHECK_CONDITION; + return; + + /* abort processing: the bulk-only transport requires a reset + * following an abort */ + Handle_Abort: + srb->result = DID_ABORT << 16; + + /* permit the reset transfer to take place */ + clear_bit(US_FLIDX_ABORTING, &us->dflags); + /* Need reset here */ +} + +#ifdef CONFIG_USB_STORAGE_DEBUG +static void isd200_log_config(struct us_data *us, struct isd200_info *info) +{ + usb_stor_dbg(us, " Event Notification: 0x%x\n", + info->ConfigData.EventNotification); + usb_stor_dbg(us, " External Clock: 0x%x\n", + info->ConfigData.ExternalClock); + usb_stor_dbg(us, " ATA Init Timeout: 0x%x\n", + info->ConfigData.ATAInitTimeout); + usb_stor_dbg(us, " ATAPI Command Block Size: 0x%x\n", + (info->ConfigData.ATAConfig & ATACFG_BLOCKSIZE) >> 6); + usb_stor_dbg(us, " Master/Slave Selection: 0x%x\n", + info->ConfigData.ATAConfig & ATACFG_MASTER); + usb_stor_dbg(us, " ATAPI Reset: 0x%x\n", + info->ConfigData.ATAConfig & ATACFG_ATAPI_RESET); + usb_stor_dbg(us, " ATA Timing: 0x%x\n", + info->ConfigData.ATAConfig & ATACFG_TIMING); + usb_stor_dbg(us, " ATA Major Command: 0x%x\n", + info->ConfigData.ATAMajorCommand); + usb_stor_dbg(us, " ATA Minor Command: 0x%x\n", + info->ConfigData.ATAMinorCommand); + usb_stor_dbg(us, " Init Status: 0x%x\n", + info->ConfigData.ATAExtraConfig & ATACFGE_INIT_STATUS); + usb_stor_dbg(us, " Config Descriptor 2: 0x%x\n", + info->ConfigData.ATAExtraConfig & ATACFGE_CONF_DESC2); + usb_stor_dbg(us, " Skip Device Boot: 0x%x\n", + info->ConfigData.ATAExtraConfig & ATACFGE_SKIP_BOOT); + usb_stor_dbg(us, " ATA 3 State Supsend: 0x%x\n", + info->ConfigData.ATAExtraConfig & ATACFGE_STATE_SUSPEND); + usb_stor_dbg(us, " Descriptor Override: 0x%x\n", + info->ConfigData.ATAExtraConfig & ATACFGE_DESC_OVERRIDE); + usb_stor_dbg(us, " Last LUN Identifier: 0x%x\n", + info->ConfigData.ATAExtraConfig & ATACFGE_LAST_LUN); + usb_stor_dbg(us, " SRST Enable: 0x%x\n", + info->ConfigData.ATAExtraConfig & CFG_CAPABILITY_SRST); +} +#endif + +/************************************************************************** + * isd200_write_config + * + * Write the ISD200 Configuration data + * + * RETURNS: + * ISD status code + */ +static int isd200_write_config( struct us_data *us ) +{ + struct isd200_info *info = (struct isd200_info *)us->extra; + int retStatus = ISD200_GOOD; + int result; + +#ifdef CONFIG_USB_STORAGE_DEBUG + usb_stor_dbg(us, "Entering isd200_write_config\n"); + usb_stor_dbg(us, " Writing the following ISD200 Config Data:\n"); + isd200_log_config(us, info); +#endif + + /* let's send the command via the control pipe */ + result = usb_stor_ctrl_transfer( + us, + us->send_ctrl_pipe, + 0x01, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x0000, + 0x0002, + (void *) &info->ConfigData, + sizeof(info->ConfigData)); + + if (result >= 0) { + usb_stor_dbg(us, " ISD200 Config Data was written successfully\n"); + } else { + usb_stor_dbg(us, " Request to write ISD200 Config Data failed!\n"); + retStatus = ISD200_ERROR; + } + + usb_stor_dbg(us, "Leaving isd200_write_config %08X\n", retStatus); + return retStatus; +} + + +/************************************************************************** + * isd200_read_config + * + * Reads the ISD200 Configuration data + * + * RETURNS: + * ISD status code + */ +static int isd200_read_config( struct us_data *us ) +{ + struct isd200_info *info = (struct isd200_info *)us->extra; + int retStatus = ISD200_GOOD; + int result; + + usb_stor_dbg(us, "Entering isd200_read_config\n"); + + /* read the configuration information from ISD200. Use this to */ + /* determine what the special ATA CDB bytes are. */ + + result = usb_stor_ctrl_transfer( + us, + us->recv_ctrl_pipe, + 0x02, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0000, + 0x0002, + (void *) &info->ConfigData, + sizeof(info->ConfigData)); + + + if (result >= 0) { + usb_stor_dbg(us, " Retrieved the following ISD200 Config Data:\n"); +#ifdef CONFIG_USB_STORAGE_DEBUG + isd200_log_config(us, info); +#endif + } else { + usb_stor_dbg(us, " Request to get ISD200 Config Data failed!\n"); + retStatus = ISD200_ERROR; + } + + usb_stor_dbg(us, "Leaving isd200_read_config %08X\n", retStatus); + return retStatus; +} + + +/************************************************************************** + * isd200_atapi_soft_reset + * + * Perform an Atapi Soft Reset on the device + * + * RETURNS: + * NT status code + */ +static int isd200_atapi_soft_reset( struct us_data *us ) +{ + int retStatus = ISD200_GOOD; + int transferStatus; + + usb_stor_dbg(us, "Entering isd200_atapi_soft_reset\n"); + + transferStatus = isd200_action( us, ACTION_SOFT_RESET, NULL, 0 ); + if (transferStatus != ISD200_TRANSPORT_GOOD) { + usb_stor_dbg(us, " Error issuing Atapi Soft Reset\n"); + retStatus = ISD200_ERROR; + } + + usb_stor_dbg(us, "Leaving isd200_atapi_soft_reset %08X\n", retStatus); + return retStatus; +} + + +/************************************************************************** + * isd200_srst + * + * Perform an SRST on the device + * + * RETURNS: + * ISD status code + */ +static int isd200_srst( struct us_data *us ) +{ + int retStatus = ISD200_GOOD; + int transferStatus; + + usb_stor_dbg(us, "Entering isd200_SRST\n"); + + transferStatus = isd200_action( us, ACTION_RESET, NULL, 0 ); + + /* check to see if this request failed */ + if (transferStatus != ISD200_TRANSPORT_GOOD) { + usb_stor_dbg(us, " Error issuing SRST\n"); + retStatus = ISD200_ERROR; + } else { + /* delay 10ms to give the drive a chance to see it */ + msleep(10); + + transferStatus = isd200_action( us, ACTION_REENABLE, NULL, 0 ); + if (transferStatus != ISD200_TRANSPORT_GOOD) { + usb_stor_dbg(us, " Error taking drive out of reset\n"); + retStatus = ISD200_ERROR; + } else { + /* delay 50ms to give the drive a chance to recover after SRST */ + msleep(50); + } + } + + usb_stor_dbg(us, "Leaving isd200_srst %08X\n", retStatus); + return retStatus; +} + + +/************************************************************************** + * isd200_try_enum + * + * Helper function for isd200_manual_enum(). Does ENUM and READ_STATUS + * and tries to analyze the status registers + * + * RETURNS: + * ISD status code + */ +static int isd200_try_enum(struct us_data *us, unsigned char master_slave, + int detect ) +{ + int status = ISD200_GOOD; + unsigned long endTime; + struct isd200_info *info = (struct isd200_info *)us->extra; + unsigned char *regs = info->RegsBuf; + int recheckAsMaster = 0; + + if ( detect ) + endTime = jiffies + ISD200_ENUM_DETECT_TIMEOUT * HZ; + else + endTime = jiffies + ISD200_ENUM_BSY_TIMEOUT * HZ; + + /* loop until we detect !BSY or timeout */ + while(1) { + + status = isd200_action( us, ACTION_ENUM, NULL, master_slave ); + if ( status != ISD200_GOOD ) + break; + + status = isd200_action( us, ACTION_READ_STATUS, + regs, 8 ); + if ( status != ISD200_GOOD ) + break; + + if (!detect) { + if (regs[ATA_REG_STATUS_OFFSET] & ATA_BUSY) { + usb_stor_dbg(us, " %s status is still BSY, try again...\n", + master_slave == ATA_ADDRESS_DEVHEAD_STD ? + "Master" : "Slave"); + } else { + usb_stor_dbg(us, " %s status !BSY, continue with next operation\n", + master_slave == ATA_ADDRESS_DEVHEAD_STD ? + "Master" : "Slave"); + break; + } + } + /* check for ATA_BUSY and */ + /* ATA_DF (workaround ATA Zip drive) and */ + /* ATA_ERR (workaround for Archos CD-ROM) */ + else if (regs[ATA_REG_STATUS_OFFSET] & + (ATA_BUSY | ATA_DF | ATA_ERR)) { + usb_stor_dbg(us, " Status indicates it is not ready, try again...\n"); + } + /* check for DRDY, ATA devices set DRDY after SRST */ + else if (regs[ATA_REG_STATUS_OFFSET] & ATA_DRDY) { + usb_stor_dbg(us, " Identified ATA device\n"); + info->DeviceFlags |= DF_ATA_DEVICE; + info->DeviceHead = master_slave; + break; + } + /* check Cylinder High/Low to + determine if it is an ATAPI device + */ + else if (regs[ATA_REG_HCYL_OFFSET] == 0xEB && + regs[ATA_REG_LCYL_OFFSET] == 0x14) { + /* It seems that the RICOH + MP6200A CD/RW drive will + report itself okay as a + slave when it is really a + master. So this check again + as a master device just to + make sure it doesn't report + itself okay as a master also + */ + if ((master_slave & ATA_ADDRESS_DEVHEAD_SLAVE) && + !recheckAsMaster) { + usb_stor_dbg(us, " Identified ATAPI device as slave. Rechecking again as master\n"); + recheckAsMaster = 1; + master_slave = ATA_ADDRESS_DEVHEAD_STD; + } else { + usb_stor_dbg(us, " Identified ATAPI device\n"); + info->DeviceHead = master_slave; + + status = isd200_atapi_soft_reset(us); + break; + } + } else { + usb_stor_dbg(us, " Not ATA, not ATAPI - Weird\n"); + break; + } + + /* check for timeout on this request */ + if (time_after_eq(jiffies, endTime)) { + if (!detect) + usb_stor_dbg(us, " BSY check timeout, just continue with next operation...\n"); + else + usb_stor_dbg(us, " Device detect timeout!\n"); + break; + } + } + + return status; +} + +/************************************************************************** + * isd200_manual_enum + * + * Determines if the drive attached is an ATA or ATAPI and if it is a + * master or slave. + * + * RETURNS: + * ISD status code + */ +static int isd200_manual_enum(struct us_data *us) +{ + struct isd200_info *info = (struct isd200_info *)us->extra; + int retStatus = ISD200_GOOD; + + usb_stor_dbg(us, "Entering isd200_manual_enum\n"); + + retStatus = isd200_read_config(us); + if (retStatus == ISD200_GOOD) { + int isslave; + /* master or slave? */ + retStatus = isd200_try_enum( us, ATA_ADDRESS_DEVHEAD_STD, 0); + if (retStatus == ISD200_GOOD) + retStatus = isd200_try_enum( us, ATA_ADDRESS_DEVHEAD_SLAVE, 0); + + if (retStatus == ISD200_GOOD) { + retStatus = isd200_srst(us); + if (retStatus == ISD200_GOOD) + /* ata or atapi? */ + retStatus = isd200_try_enum( us, ATA_ADDRESS_DEVHEAD_STD, 1); + } + + isslave = (info->DeviceHead & ATA_ADDRESS_DEVHEAD_SLAVE) ? 1 : 0; + if (!(info->ConfigData.ATAConfig & ATACFG_MASTER)) { + usb_stor_dbg(us, " Setting Master/Slave selection to %d\n", + isslave); + info->ConfigData.ATAConfig &= 0x3f; + info->ConfigData.ATAConfig |= (isslave<<6); + retStatus = isd200_write_config(us); + } + } + + usb_stor_dbg(us, "Leaving isd200_manual_enum %08X\n", retStatus); + return(retStatus); +} + +static void isd200_fix_driveid(u16 *id) +{ +#ifndef __LITTLE_ENDIAN +# ifdef __BIG_ENDIAN + int i; + + for (i = 0; i < ATA_ID_WORDS; i++) + id[i] = __le16_to_cpu(id[i]); +# else +# error "Please fix " +# endif +#endif +} + +static void isd200_dump_driveid(struct us_data *us, u16 *id) +{ + usb_stor_dbg(us, " Identify Data Structure:\n"); + usb_stor_dbg(us, " config = 0x%x\n", id[ATA_ID_CONFIG]); + usb_stor_dbg(us, " cyls = 0x%x\n", id[ATA_ID_CYLS]); + usb_stor_dbg(us, " heads = 0x%x\n", id[ATA_ID_HEADS]); + usb_stor_dbg(us, " track_bytes = 0x%x\n", id[4]); + usb_stor_dbg(us, " sector_bytes = 0x%x\n", id[5]); + usb_stor_dbg(us, " sectors = 0x%x\n", id[ATA_ID_SECTORS]); + usb_stor_dbg(us, " serial_no[0] = 0x%x\n", *(char *)&id[ATA_ID_SERNO]); + usb_stor_dbg(us, " buf_type = 0x%x\n", id[20]); + usb_stor_dbg(us, " buf_size = 0x%x\n", id[ATA_ID_BUF_SIZE]); + usb_stor_dbg(us, " ecc_bytes = 0x%x\n", id[22]); + usb_stor_dbg(us, " fw_rev[0] = 0x%x\n", *(char *)&id[ATA_ID_FW_REV]); + usb_stor_dbg(us, " model[0] = 0x%x\n", *(char *)&id[ATA_ID_PROD]); + usb_stor_dbg(us, " max_multsect = 0x%x\n", id[ATA_ID_MAX_MULTSECT] & 0xff); + usb_stor_dbg(us, " dword_io = 0x%x\n", id[ATA_ID_DWORD_IO]); + usb_stor_dbg(us, " capability = 0x%x\n", id[ATA_ID_CAPABILITY] >> 8); + usb_stor_dbg(us, " tPIO = 0x%x\n", id[ATA_ID_OLD_PIO_MODES] >> 8); + usb_stor_dbg(us, " tDMA = 0x%x\n", id[ATA_ID_OLD_DMA_MODES] >> 8); + usb_stor_dbg(us, " field_valid = 0x%x\n", id[ATA_ID_FIELD_VALID]); + usb_stor_dbg(us, " cur_cyls = 0x%x\n", id[ATA_ID_CUR_CYLS]); + usb_stor_dbg(us, " cur_heads = 0x%x\n", id[ATA_ID_CUR_HEADS]); + usb_stor_dbg(us, " cur_sectors = 0x%x\n", id[ATA_ID_CUR_SECTORS]); + usb_stor_dbg(us, " cur_capacity = 0x%x\n", ata_id_u32(id, 57)); + usb_stor_dbg(us, " multsect = 0x%x\n", id[ATA_ID_MULTSECT] & 0xff); + usb_stor_dbg(us, " lba_capacity = 0x%x\n", ata_id_u32(id, ATA_ID_LBA_CAPACITY)); + usb_stor_dbg(us, " command_set_1 = 0x%x\n", id[ATA_ID_COMMAND_SET_1]); + usb_stor_dbg(us, " command_set_2 = 0x%x\n", id[ATA_ID_COMMAND_SET_2]); +} + +/************************************************************************** + * isd200_get_inquiry_data + * + * Get inquiry data + * + * RETURNS: + * ISD status code + */ +static int isd200_get_inquiry_data( struct us_data *us ) +{ + struct isd200_info *info = (struct isd200_info *)us->extra; + int retStatus = ISD200_GOOD; + u16 *id = info->id; + + usb_stor_dbg(us, "Entering isd200_get_inquiry_data\n"); + + /* set default to Master */ + info->DeviceHead = ATA_ADDRESS_DEVHEAD_STD; + + /* attempt to manually enumerate this device */ + retStatus = isd200_manual_enum(us); + if (retStatus == ISD200_GOOD) { + int transferStatus; + + /* check for an ATA device */ + if (info->DeviceFlags & DF_ATA_DEVICE) { + /* this must be an ATA device */ + /* perform an ATA Command Identify */ + transferStatus = isd200_action( us, ACTION_IDENTIFY, + id, ATA_ID_WORDS * 2); + if (transferStatus != ISD200_TRANSPORT_GOOD) { + /* Error issuing ATA Command Identify */ + usb_stor_dbg(us, " Error issuing ATA Command Identify\n"); + retStatus = ISD200_ERROR; + } else { + /* ATA Command Identify successful */ + int i; + __be16 *src; + __u16 *dest; + + isd200_fix_driveid(id); + isd200_dump_driveid(us, id); + + memset(&info->InquiryData, 0, sizeof(info->InquiryData)); + + /* Standard IDE interface only supports disks */ + info->InquiryData.DeviceType = DIRECT_ACCESS_DEVICE; + + /* The length must be at least 36 (5 + 31) */ + info->InquiryData.AdditionalLength = 0x1F; + + if (id[ATA_ID_COMMAND_SET_1] & COMMANDSET_MEDIA_STATUS) { + /* set the removable bit */ + info->InquiryData.DeviceTypeModifier = DEVICE_REMOVABLE; + info->DeviceFlags |= DF_REMOVABLE_MEDIA; + } + + /* Fill in vendor identification fields */ + src = (__be16 *)&id[ATA_ID_PROD]; + dest = (__u16*)info->InquiryData.VendorId; + for (i=0;i<4;i++) + dest[i] = be16_to_cpu(src[i]); + + src = (__be16 *)&id[ATA_ID_PROD + 8/2]; + dest = (__u16*)info->InquiryData.ProductId; + for (i=0;i<8;i++) + dest[i] = be16_to_cpu(src[i]); + + src = (__be16 *)&id[ATA_ID_FW_REV]; + dest = (__u16*)info->InquiryData.ProductRevisionLevel; + for (i=0;i<2;i++) + dest[i] = be16_to_cpu(src[i]); + + /* determine if it supports Media Status Notification */ + if (id[ATA_ID_COMMAND_SET_2] & COMMANDSET_MEDIA_STATUS) { + usb_stor_dbg(us, " Device supports Media Status Notification\n"); + + /* Indicate that it is enabled, even though it is not + * This allows the lock/unlock of the media to work + * correctly. + */ + info->DeviceFlags |= DF_MEDIA_STATUS_ENABLED; + } + else + info->DeviceFlags &= ~DF_MEDIA_STATUS_ENABLED; + + } + } else { + /* + * this must be an ATAPI device + * use an ATAPI protocol (Transparent SCSI) + */ + us->protocol_name = "Transparent SCSI"; + us->proto_handler = usb_stor_transparent_scsi_command; + + usb_stor_dbg(us, "Protocol changed to: %s\n", + us->protocol_name); + + /* Free driver structure */ + us->extra_destructor(info); + kfree(info); + us->extra = NULL; + us->extra_destructor = NULL; + } + } + + usb_stor_dbg(us, "Leaving isd200_get_inquiry_data %08X\n", retStatus); + + return(retStatus); +} + +/************************************************************************** + * isd200_scsi_to_ata + * + * Translate SCSI commands to ATA commands. + * + * RETURNS: + * 1 if the command needs to be sent to the transport layer + * 0 otherwise + */ +static int isd200_scsi_to_ata(struct scsi_cmnd *srb, struct us_data *us, + union ata_cdb * ataCdb) +{ + struct isd200_info *info = (struct isd200_info *)us->extra; + u16 *id = info->id; + int sendToTransport = 1; + unsigned char sectnum, head; + unsigned short cylinder; + unsigned long lba; + unsigned long blockCount; + unsigned char senseData[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + memset(ataCdb, 0, sizeof(union ata_cdb)); + + /* SCSI Command */ + switch (srb->cmnd[0]) { + case INQUIRY: + usb_stor_dbg(us, " ATA OUT - INQUIRY\n"); + + /* copy InquiryData */ + usb_stor_set_xfer_buf((unsigned char *) &info->InquiryData, + sizeof(info->InquiryData), srb); + srb->result = SAM_STAT_GOOD; + sendToTransport = 0; + break; + + case MODE_SENSE: + usb_stor_dbg(us, " ATA OUT - SCSIOP_MODE_SENSE\n"); + + /* Initialize the return buffer */ + usb_stor_set_xfer_buf(senseData, sizeof(senseData), srb); + + if (info->DeviceFlags & DF_MEDIA_STATUS_ENABLED) + { + ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand; + ataCdb->generic.SignatureByte1 = info->ConfigData.ATAMinorCommand; + ataCdb->generic.TransferBlockSize = 1; + ataCdb->generic.RegisterSelect = REG_COMMAND; + ataCdb->write.CommandByte = ATA_COMMAND_GET_MEDIA_STATUS; + isd200_srb_set_bufflen(srb, 0); + } else { + usb_stor_dbg(us, " Media Status not supported, just report okay\n"); + srb->result = SAM_STAT_GOOD; + sendToTransport = 0; + } + break; + + case TEST_UNIT_READY: + usb_stor_dbg(us, " ATA OUT - SCSIOP_TEST_UNIT_READY\n"); + + if (info->DeviceFlags & DF_MEDIA_STATUS_ENABLED) + { + ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand; + ataCdb->generic.SignatureByte1 = info->ConfigData.ATAMinorCommand; + ataCdb->generic.TransferBlockSize = 1; + ataCdb->generic.RegisterSelect = REG_COMMAND; + ataCdb->write.CommandByte = ATA_COMMAND_GET_MEDIA_STATUS; + isd200_srb_set_bufflen(srb, 0); + } else { + usb_stor_dbg(us, " Media Status not supported, just report okay\n"); + srb->result = SAM_STAT_GOOD; + sendToTransport = 0; + } + break; + + case READ_CAPACITY: + { + unsigned long capacity; + struct read_capacity_data readCapacityData; + + usb_stor_dbg(us, " ATA OUT - SCSIOP_READ_CAPACITY\n"); + + if (ata_id_has_lba(id)) + capacity = ata_id_u32(id, ATA_ID_LBA_CAPACITY) - 1; + else + capacity = (id[ATA_ID_HEADS] * id[ATA_ID_CYLS] * + id[ATA_ID_SECTORS]) - 1; + + readCapacityData.LogicalBlockAddress = cpu_to_be32(capacity); + readCapacityData.BytesPerBlock = cpu_to_be32(0x200); + + usb_stor_set_xfer_buf((unsigned char *) &readCapacityData, + sizeof(readCapacityData), srb); + srb->result = SAM_STAT_GOOD; + sendToTransport = 0; + } + break; + + case READ_10: + usb_stor_dbg(us, " ATA OUT - SCSIOP_READ\n"); + + lba = be32_to_cpu(*(__be32 *)&srb->cmnd[2]); + blockCount = (unsigned long)srb->cmnd[7]<<8 | (unsigned long)srb->cmnd[8]; + + if (ata_id_has_lba(id)) { + sectnum = (unsigned char)(lba); + cylinder = (unsigned short)(lba>>8); + head = ATA_ADDRESS_DEVHEAD_LBA_MODE | (unsigned char)(lba>>24 & 0x0F); + } else { + sectnum = (u8)((lba % id[ATA_ID_SECTORS]) + 1); + cylinder = (u16)(lba / (id[ATA_ID_SECTORS] * + id[ATA_ID_HEADS])); + head = (u8)((lba / id[ATA_ID_SECTORS]) % + id[ATA_ID_HEADS]); + } + ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand; + ataCdb->generic.SignatureByte1 = info->ConfigData.ATAMinorCommand; + ataCdb->generic.TransferBlockSize = 1; + ataCdb->generic.RegisterSelect = + REG_SECTOR_COUNT | REG_SECTOR_NUMBER | + REG_CYLINDER_LOW | REG_CYLINDER_HIGH | + REG_DEVICE_HEAD | REG_COMMAND; + ataCdb->write.SectorCountByte = (unsigned char)blockCount; + ataCdb->write.SectorNumberByte = sectnum; + ataCdb->write.CylinderHighByte = (unsigned char)(cylinder>>8); + ataCdb->write.CylinderLowByte = (unsigned char)cylinder; + ataCdb->write.DeviceHeadByte = (head | ATA_ADDRESS_DEVHEAD_STD); + ataCdb->write.CommandByte = ATA_CMD_PIO_READ; + break; + + case WRITE_10: + usb_stor_dbg(us, " ATA OUT - SCSIOP_WRITE\n"); + + lba = be32_to_cpu(*(__be32 *)&srb->cmnd[2]); + blockCount = (unsigned long)srb->cmnd[7]<<8 | (unsigned long)srb->cmnd[8]; + + if (ata_id_has_lba(id)) { + sectnum = (unsigned char)(lba); + cylinder = (unsigned short)(lba>>8); + head = ATA_ADDRESS_DEVHEAD_LBA_MODE | (unsigned char)(lba>>24 & 0x0F); + } else { + sectnum = (u8)((lba % id[ATA_ID_SECTORS]) + 1); + cylinder = (u16)(lba / (id[ATA_ID_SECTORS] * + id[ATA_ID_HEADS])); + head = (u8)((lba / id[ATA_ID_SECTORS]) % + id[ATA_ID_HEADS]); + } + ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand; + ataCdb->generic.SignatureByte1 = info->ConfigData.ATAMinorCommand; + ataCdb->generic.TransferBlockSize = 1; + ataCdb->generic.RegisterSelect = + REG_SECTOR_COUNT | REG_SECTOR_NUMBER | + REG_CYLINDER_LOW | REG_CYLINDER_HIGH | + REG_DEVICE_HEAD | REG_COMMAND; + ataCdb->write.SectorCountByte = (unsigned char)blockCount; + ataCdb->write.SectorNumberByte = sectnum; + ataCdb->write.CylinderHighByte = (unsigned char)(cylinder>>8); + ataCdb->write.CylinderLowByte = (unsigned char)cylinder; + ataCdb->write.DeviceHeadByte = (head | ATA_ADDRESS_DEVHEAD_STD); + ataCdb->write.CommandByte = ATA_CMD_PIO_WRITE; + break; + + case ALLOW_MEDIUM_REMOVAL: + usb_stor_dbg(us, " ATA OUT - SCSIOP_MEDIUM_REMOVAL\n"); + + if (info->DeviceFlags & DF_REMOVABLE_MEDIA) { + usb_stor_dbg(us, " srb->cmnd[4] = 0x%X\n", + srb->cmnd[4]); + + ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand; + ataCdb->generic.SignatureByte1 = info->ConfigData.ATAMinorCommand; + ataCdb->generic.TransferBlockSize = 1; + ataCdb->generic.RegisterSelect = REG_COMMAND; + ataCdb->write.CommandByte = (srb->cmnd[4] & 0x1) ? + ATA_CMD_MEDIA_LOCK : ATA_CMD_MEDIA_UNLOCK; + isd200_srb_set_bufflen(srb, 0); + } else { + usb_stor_dbg(us, " Not removeable media, just report okay\n"); + srb->result = SAM_STAT_GOOD; + sendToTransport = 0; + } + break; + + case START_STOP: + usb_stor_dbg(us, " ATA OUT - SCSIOP_START_STOP_UNIT\n"); + usb_stor_dbg(us, " srb->cmnd[4] = 0x%X\n", srb->cmnd[4]); + + if ((srb->cmnd[4] & 0x3) == 0x2) { + usb_stor_dbg(us, " Media Eject\n"); + ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand; + ataCdb->generic.SignatureByte1 = info->ConfigData.ATAMinorCommand; + ataCdb->generic.TransferBlockSize = 0; + ataCdb->generic.RegisterSelect = REG_COMMAND; + ataCdb->write.CommandByte = ATA_COMMAND_MEDIA_EJECT; + } else if ((srb->cmnd[4] & 0x3) == 0x1) { + usb_stor_dbg(us, " Get Media Status\n"); + ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand; + ataCdb->generic.SignatureByte1 = info->ConfigData.ATAMinorCommand; + ataCdb->generic.TransferBlockSize = 1; + ataCdb->generic.RegisterSelect = REG_COMMAND; + ataCdb->write.CommandByte = ATA_COMMAND_GET_MEDIA_STATUS; + isd200_srb_set_bufflen(srb, 0); + } else { + usb_stor_dbg(us, " Nothing to do, just report okay\n"); + srb->result = SAM_STAT_GOOD; + sendToTransport = 0; + } + break; + + default: + usb_stor_dbg(us, "Unsupported SCSI command - 0x%X\n", + srb->cmnd[0]); + srb->result = DID_ERROR << 16; + sendToTransport = 0; + break; + } + + return(sendToTransport); +} + + +/************************************************************************** + * isd200_free_info + * + * Frees the driver structure. + */ +static void isd200_free_info_ptrs(void *info_) +{ + struct isd200_info *info = (struct isd200_info *) info_; + + if (info) { + kfree(info->id); + kfree(info->RegsBuf); + kfree(info->srb.sense_buffer); + } +} + +/************************************************************************** + * isd200_init_info + * + * Allocates (if necessary) and initializes the driver structure. + * + * RETURNS: + * ISD status code + */ +static int isd200_init_info(struct us_data *us) +{ + int retStatus = ISD200_GOOD; + struct isd200_info *info; + + info = kzalloc(sizeof(struct isd200_info), GFP_KERNEL); + if (!info) + retStatus = ISD200_ERROR; + else { + info->id = kzalloc(ATA_ID_WORDS * 2, GFP_KERNEL); + info->RegsBuf = kmalloc(sizeof(info->ATARegs), GFP_KERNEL); + info->srb.sense_buffer = + kmalloc(SCSI_SENSE_BUFFERSIZE, GFP_KERNEL); + if (!info->id || !info->RegsBuf || !info->srb.sense_buffer) { + isd200_free_info_ptrs(info); + kfree(info); + retStatus = ISD200_ERROR; + } + } + + if (retStatus == ISD200_GOOD) { + us->extra = info; + us->extra_destructor = isd200_free_info_ptrs; + } + + return retStatus; +} + +/************************************************************************** + * Initialization for the ISD200 + */ + +static int isd200_Initialization(struct us_data *us) +{ + usb_stor_dbg(us, "ISD200 Initialization...\n"); + + /* Initialize ISD200 info struct */ + + if (isd200_init_info(us) == ISD200_ERROR) { + usb_stor_dbg(us, "ERROR Initializing ISD200 Info struct\n"); + } else { + /* Get device specific data */ + + if (isd200_get_inquiry_data(us) != ISD200_GOOD) + usb_stor_dbg(us, "ISD200 Initialization Failure\n"); + else + usb_stor_dbg(us, "ISD200 Initialization complete\n"); + } + + return 0; +} + + +/************************************************************************** + * Protocol and Transport for the ISD200 ASIC + * + * This protocol and transport are for ATA devices connected to an ISD200 + * ASIC. An ATAPI device that is connected as a slave device will be + * detected in the driver initialization function and the protocol will + * be changed to an ATAPI protocol (Transparent SCSI). + * + */ + +static void isd200_ata_command(struct scsi_cmnd *srb, struct us_data *us) +{ + int sendToTransport = 1, orig_bufflen; + union ata_cdb ataCdb; + + /* Make sure driver was initialized */ + + if (us->extra == NULL) + usb_stor_dbg(us, "ERROR Driver not initialized\n"); + + scsi_set_resid(srb, 0); + /* scsi_bufflen might change in protocol translation to ata */ + orig_bufflen = scsi_bufflen(srb); + sendToTransport = isd200_scsi_to_ata(srb, us, &ataCdb); + + /* send the command to the transport layer */ + if (sendToTransport) + isd200_invoke_transport(us, srb, &ataCdb); + + isd200_srb_set_bufflen(srb, orig_bufflen); +} + +static int isd200_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - isd200_usb_ids) + isd200_unusual_dev_list); + if (result) + return result; + + us->protocol_name = "ISD200 ATA/ATAPI"; + us->proto_handler = isd200_ata_command; + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver isd200_driver = { + .name = "ums-isd200", + .probe = isd200_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = isd200_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(isd200_driver); diff --git a/drivers/usb/storage/jumpshot.c b/drivers/usb/storage/jumpshot.c new file mode 100644 index 00000000..563078be --- /dev/null +++ b/drivers/usb/storage/jumpshot.c @@ -0,0 +1,684 @@ +/* Driver for Lexar "Jumpshot" Compact Flash reader + * + * jumpshot driver v0.1: + * + * First release + * + * Current development and maintenance by: + * (c) 2000 Jimmie Mayfield (mayfield+usb@sackheads.org) + * + * Many thanks to Robert Baruch for the SanDisk SmartMedia reader driver + * which I used as a template for this driver. + * + * Some bugfixes and scatter-gather code by Gregory P. Smith + * (greg-usb@electricrain.com) + * + * Fix for media change by Joerg Schneider (js@joergschneider.com) + * + * Developed with the assistance of: + * + * (C) 2002 Alan Stern + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + /* + * This driver attempts to support the Lexar Jumpshot USB CompactFlash + * reader. Like many other USB CompactFlash readers, the Jumpshot contains + * a USB-to-ATA chip. + * + * This driver supports reading and writing. If you're truly paranoid, + * however, you can force the driver into a write-protected state by setting + * the WP enable bits in jumpshot_handle_mode_sense. See the comments + * in that routine. + */ + +#include +#include +#include + +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + + +MODULE_DESCRIPTION("Driver for Lexar \"Jumpshot\" Compact Flash reader"); +MODULE_AUTHOR("Jimmie Mayfield "); +MODULE_LICENSE("GPL"); + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id jumpshot_usb_ids[] = { +# include "unusual_jumpshot.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, jumpshot_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev jumpshot_unusual_dev_list[] = { +# include "unusual_jumpshot.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + +struct jumpshot_info { + unsigned long sectors; /* total sector count */ + unsigned long ssize; /* sector size in bytes */ + + /* the following aren't used yet */ + unsigned char sense_key; + unsigned long sense_asc; /* additional sense code */ + unsigned long sense_ascq; /* additional sense code qualifier */ +}; + +static inline int jumpshot_bulk_read(struct us_data *us, + unsigned char *data, + unsigned int len) +{ + if (len == 0) + return USB_STOR_XFER_GOOD; + + usb_stor_dbg(us, "len = %d\n", len); + return usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + data, len, NULL); +} + + +static inline int jumpshot_bulk_write(struct us_data *us, + unsigned char *data, + unsigned int len) +{ + if (len == 0) + return USB_STOR_XFER_GOOD; + + usb_stor_dbg(us, "len = %d\n", len); + return usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + data, len, NULL); +} + + +static int jumpshot_get_status(struct us_data *us) +{ + int rc; + + if (!us) + return USB_STOR_TRANSPORT_ERROR; + + // send the setup + rc = usb_stor_ctrl_transfer(us, us->recv_ctrl_pipe, + 0, 0xA0, 0, 7, us->iobuf, 1); + + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (us->iobuf[0] != 0x50) { + usb_stor_dbg(us, "0x%2x\n", us->iobuf[0]); + return USB_STOR_TRANSPORT_ERROR; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +static int jumpshot_read_data(struct us_data *us, + struct jumpshot_info *info, + u32 sector, + u32 sectors) +{ + unsigned char *command = us->iobuf; + unsigned char *buffer; + unsigned char thistime; + unsigned int totallen, alloclen; + int len, result; + unsigned int sg_offset = 0; + struct scatterlist *sg = NULL; + + // we're working in LBA mode. according to the ATA spec, + // we can support up to 28-bit addressing. I don't know if Jumpshot + // supports beyond 24-bit addressing. It's kind of hard to test + // since it requires > 8GB CF card. + + if (sector > 0x0FFFFFFF) + return USB_STOR_TRANSPORT_ERROR; + + totallen = sectors * info->ssize; + + // Since we don't read more than 64 KB at a time, we have to create + // a bounce buffer and move the data a piece at a time between the + // bounce buffer and the actual transfer buffer. + + alloclen = min(totallen, 65536u); + buffer = kmalloc(alloclen, GFP_NOIO); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + do { + // loop, never allocate or transfer more than 64k at once + // (min(128k, 255*info->ssize) is the real limit) + len = min(totallen, alloclen); + thistime = (len / info->ssize) & 0xff; + + command[0] = 0; + command[1] = thistime; + command[2] = sector & 0xFF; + command[3] = (sector >> 8) & 0xFF; + command[4] = (sector >> 16) & 0xFF; + + command[5] = 0xE0 | ((sector >> 24) & 0x0F); + command[6] = 0x20; + + // send the setup + command + result = usb_stor_ctrl_transfer(us, us->send_ctrl_pipe, + 0, 0x20, 0, 1, command, 7); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + // read the result + result = jumpshot_bulk_read(us, buffer, len); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + usb_stor_dbg(us, "%d bytes\n", len); + + // Store the data in the transfer buffer + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &sg_offset, TO_XFER_BUF); + + sector += thistime; + totallen -= len; + } while (totallen > 0); + + kfree(buffer); + return USB_STOR_TRANSPORT_GOOD; + + leave: + kfree(buffer); + return USB_STOR_TRANSPORT_ERROR; +} + + +static int jumpshot_write_data(struct us_data *us, + struct jumpshot_info *info, + u32 sector, + u32 sectors) +{ + unsigned char *command = us->iobuf; + unsigned char *buffer; + unsigned char thistime; + unsigned int totallen, alloclen; + int len, result, waitcount; + unsigned int sg_offset = 0; + struct scatterlist *sg = NULL; + + // we're working in LBA mode. according to the ATA spec, + // we can support up to 28-bit addressing. I don't know if Jumpshot + // supports beyond 24-bit addressing. It's kind of hard to test + // since it requires > 8GB CF card. + // + if (sector > 0x0FFFFFFF) + return USB_STOR_TRANSPORT_ERROR; + + totallen = sectors * info->ssize; + + // Since we don't write more than 64 KB at a time, we have to create + // a bounce buffer and move the data a piece at a time between the + // bounce buffer and the actual transfer buffer. + + alloclen = min(totallen, 65536u); + buffer = kmalloc(alloclen, GFP_NOIO); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + do { + // loop, never allocate or transfer more than 64k at once + // (min(128k, 255*info->ssize) is the real limit) + + len = min(totallen, alloclen); + thistime = (len / info->ssize) & 0xff; + + // Get the data from the transfer buffer + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &sg_offset, FROM_XFER_BUF); + + command[0] = 0; + command[1] = thistime; + command[2] = sector & 0xFF; + command[3] = (sector >> 8) & 0xFF; + command[4] = (sector >> 16) & 0xFF; + + command[5] = 0xE0 | ((sector >> 24) & 0x0F); + command[6] = 0x30; + + // send the setup + command + result = usb_stor_ctrl_transfer(us, us->send_ctrl_pipe, + 0, 0x20, 0, 1, command, 7); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + // send the data + result = jumpshot_bulk_write(us, buffer, len); + if (result != USB_STOR_XFER_GOOD) + goto leave; + + // read the result. apparently the bulk write can complete + // before the jumpshot drive is finished writing. so we loop + // here until we get a good return code + waitcount = 0; + do { + result = jumpshot_get_status(us); + if (result != USB_STOR_TRANSPORT_GOOD) { + // I have not experimented to find the smallest value. + // + msleep(50); + } + } while ((result != USB_STOR_TRANSPORT_GOOD) && (waitcount < 10)); + + if (result != USB_STOR_TRANSPORT_GOOD) + usb_stor_dbg(us, "Gah! Waitcount = 10. Bad write!?\n"); + + sector += thistime; + totallen -= len; + } while (totallen > 0); + + kfree(buffer); + return result; + + leave: + kfree(buffer); + return USB_STOR_TRANSPORT_ERROR; +} + +static int jumpshot_id_device(struct us_data *us, + struct jumpshot_info *info) +{ + unsigned char *command = us->iobuf; + unsigned char *reply; + int rc; + + if (!info) + return USB_STOR_TRANSPORT_ERROR; + + command[0] = 0xE0; + command[1] = 0xEC; + reply = kmalloc(512, GFP_NOIO); + if (!reply) + return USB_STOR_TRANSPORT_ERROR; + + // send the setup + rc = usb_stor_ctrl_transfer(us, us->send_ctrl_pipe, + 0, 0x20, 0, 6, command, 2); + + if (rc != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Gah! send_control for read_capacity failed\n"); + rc = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + // read the reply + rc = jumpshot_bulk_read(us, reply, 512); + if (rc != USB_STOR_XFER_GOOD) { + rc = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + info->sectors = ((u32)(reply[117]) << 24) | + ((u32)(reply[116]) << 16) | + ((u32)(reply[115]) << 8) | + ((u32)(reply[114]) ); + + rc = USB_STOR_TRANSPORT_GOOD; + + leave: + kfree(reply); + return rc; +} + +static int jumpshot_handle_mode_sense(struct us_data *us, + struct scsi_cmnd * srb, + int sense_6) +{ + static unsigned char rw_err_page[12] = { + 0x1, 0xA, 0x21, 1, 0, 0, 0, 0, 1, 0, 0, 0 + }; + static unsigned char cache_page[12] = { + 0x8, 0xA, 0x1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + static unsigned char rbac_page[12] = { + 0x1B, 0xA, 0, 0x81, 0, 0, 0, 0, 0, 0, 0, 0 + }; + static unsigned char timer_page[8] = { + 0x1C, 0x6, 0, 0, 0, 0 + }; + unsigned char pc, page_code; + unsigned int i = 0; + struct jumpshot_info *info = (struct jumpshot_info *) (us->extra); + unsigned char *ptr = us->iobuf; + + pc = srb->cmnd[2] >> 6; + page_code = srb->cmnd[2] & 0x3F; + + switch (pc) { + case 0x0: + usb_stor_dbg(us, "Current values\n"); + break; + case 0x1: + usb_stor_dbg(us, "Changeable values\n"); + break; + case 0x2: + usb_stor_dbg(us, "Default values\n"); + break; + case 0x3: + usb_stor_dbg(us, "Saves values\n"); + break; + } + + memset(ptr, 0, 8); + if (sense_6) { + ptr[2] = 0x00; // WP enable: 0x80 + i = 4; + } else { + ptr[3] = 0x00; // WP enable: 0x80 + i = 8; + } + + switch (page_code) { + case 0x0: + // vendor-specific mode + info->sense_key = 0x05; + info->sense_asc = 0x24; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; + + case 0x1: + memcpy(ptr + i, rw_err_page, sizeof(rw_err_page)); + i += sizeof(rw_err_page); + break; + + case 0x8: + memcpy(ptr + i, cache_page, sizeof(cache_page)); + i += sizeof(cache_page); + break; + + case 0x1B: + memcpy(ptr + i, rbac_page, sizeof(rbac_page)); + i += sizeof(rbac_page); + break; + + case 0x1C: + memcpy(ptr + i, timer_page, sizeof(timer_page)); + i += sizeof(timer_page); + break; + + case 0x3F: + memcpy(ptr + i, timer_page, sizeof(timer_page)); + i += sizeof(timer_page); + memcpy(ptr + i, rbac_page, sizeof(rbac_page)); + i += sizeof(rbac_page); + memcpy(ptr + i, cache_page, sizeof(cache_page)); + i += sizeof(cache_page); + memcpy(ptr + i, rw_err_page, sizeof(rw_err_page)); + i += sizeof(rw_err_page); + break; + } + + if (sense_6) + ptr[0] = i - 1; + else + ((__be16 *) ptr)[0] = cpu_to_be16(i - 2); + usb_stor_set_xfer_buf(ptr, i, srb); + + return USB_STOR_TRANSPORT_GOOD; +} + + +static void jumpshot_info_destructor(void *extra) +{ + // this routine is a placeholder... + // currently, we don't allocate any extra blocks so we're okay +} + + + +// Transport for the Lexar 'Jumpshot' +// +static int jumpshot_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + struct jumpshot_info *info; + int rc; + unsigned long block, blocks; + unsigned char *ptr = us->iobuf; + static unsigned char inquiry_response[8] = { + 0x00, 0x80, 0x00, 0x01, 0x1F, 0x00, 0x00, 0x00 + }; + + if (!us->extra) { + us->extra = kzalloc(sizeof(struct jumpshot_info), GFP_NOIO); + if (!us->extra) + return USB_STOR_TRANSPORT_ERROR; + + us->extra_destructor = jumpshot_info_destructor; + } + + info = (struct jumpshot_info *) (us->extra); + + if (srb->cmnd[0] == INQUIRY) { + usb_stor_dbg(us, "INQUIRY - Returning bogus response\n"); + memcpy(ptr, inquiry_response, sizeof(inquiry_response)); + fill_inquiry_response(us, ptr, 36); + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == READ_CAPACITY) { + info->ssize = 0x200; // hard coded 512 byte sectors as per ATA spec + + rc = jumpshot_get_status(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + rc = jumpshot_id_device(us, info); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + usb_stor_dbg(us, "READ_CAPACITY: %ld sectors, %ld bytes per sector\n", + info->sectors, info->ssize); + + // build the reply + // + ((__be32 *) ptr)[0] = cpu_to_be32(info->sectors - 1); + ((__be32 *) ptr)[1] = cpu_to_be32(info->ssize); + usb_stor_set_xfer_buf(ptr, 8, srb); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == MODE_SELECT_10) { + usb_stor_dbg(us, "Gah! MODE_SELECT_10\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + if (srb->cmnd[0] == READ_10) { + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[7]) << 8) | ((u32)(srb->cmnd[8])); + + usb_stor_dbg(us, "READ_10: read block 0x%04lx count %ld\n", + block, blocks); + return jumpshot_read_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == READ_12) { + // I don't think we'll ever see a READ_12 but support it anyway... + // + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[6]) << 24) | ((u32)(srb->cmnd[7]) << 16) | + ((u32)(srb->cmnd[8]) << 8) | ((u32)(srb->cmnd[9])); + + usb_stor_dbg(us, "READ_12: read block 0x%04lx count %ld\n", + block, blocks); + return jumpshot_read_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == WRITE_10) { + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[7]) << 8) | ((u32)(srb->cmnd[8])); + + usb_stor_dbg(us, "WRITE_10: write block 0x%04lx count %ld\n", + block, blocks); + return jumpshot_write_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == WRITE_12) { + // I don't think we'll ever see a WRITE_12 but support it anyway... + // + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[6]) << 24) | ((u32)(srb->cmnd[7]) << 16) | + ((u32)(srb->cmnd[8]) << 8) | ((u32)(srb->cmnd[9])); + + usb_stor_dbg(us, "WRITE_12: write block 0x%04lx count %ld\n", + block, blocks); + return jumpshot_write_data(us, info, block, blocks); + } + + + if (srb->cmnd[0] == TEST_UNIT_READY) { + usb_stor_dbg(us, "TEST_UNIT_READY\n"); + return jumpshot_get_status(us); + } + + if (srb->cmnd[0] == REQUEST_SENSE) { + usb_stor_dbg(us, "REQUEST_SENSE\n"); + + memset(ptr, 0, 18); + ptr[0] = 0xF0; + ptr[2] = info->sense_key; + ptr[7] = 11; + ptr[12] = info->sense_asc; + ptr[13] = info->sense_ascq; + usb_stor_set_xfer_buf(ptr, 18, srb); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == MODE_SENSE) { + usb_stor_dbg(us, "MODE_SENSE_6 detected\n"); + return jumpshot_handle_mode_sense(us, srb, 1); + } + + if (srb->cmnd[0] == MODE_SENSE_10) { + usb_stor_dbg(us, "MODE_SENSE_10 detected\n"); + return jumpshot_handle_mode_sense(us, srb, 0); + } + + if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) { + // sure. whatever. not like we can stop the user from popping + // the media out of the device (no locking doors, etc) + // + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == START_STOP) { + /* this is used by sd.c'check_scsidisk_media_change to detect + media change */ + usb_stor_dbg(us, "START_STOP\n"); + /* the first jumpshot_id_device after a media change returns + an error (determined experimentally) */ + rc = jumpshot_id_device(us, info); + if (rc == USB_STOR_TRANSPORT_GOOD) { + info->sense_key = NO_SENSE; + srb->result = SUCCESS; + } else { + info->sense_key = UNIT_ATTENTION; + srb->result = SAM_STAT_CHECK_CONDITION; + } + return rc; + } + + usb_stor_dbg(us, "Gah! Unknown command: %d (0x%x)\n", + srb->cmnd[0], srb->cmnd[0]); + info->sense_key = 0x05; + info->sense_asc = 0x20; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; +} + +static int jumpshot_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - jumpshot_usb_ids) + jumpshot_unusual_dev_list); + if (result) + return result; + + us->transport_name = "Lexar Jumpshot Control/Bulk"; + us->transport = jumpshot_transport; + us->transport_reset = usb_stor_Bulk_reset; + us->max_lun = 1; + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver jumpshot_driver = { + .name = "ums-jumpshot", + .probe = jumpshot_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = jumpshot_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(jumpshot_driver); diff --git a/drivers/usb/storage/karma.c b/drivers/usb/storage/karma.c new file mode 100644 index 00000000..94d16ee5 --- /dev/null +++ b/drivers/usb/storage/karma.c @@ -0,0 +1,236 @@ +/* Driver for Rio Karma + * + * (c) 2006 Bob Copeland + * (c) 2006 Keith Bennett + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "debug.h" + +MODULE_DESCRIPTION("Driver for Rio Karma"); +MODULE_AUTHOR("Bob Copeland , Keith Bennett "); +MODULE_LICENSE("GPL"); + +#define RIO_PREFIX "RIOP\x00" +#define RIO_PREFIX_LEN 5 +#define RIO_SEND_LEN 40 +#define RIO_RECV_LEN 0x200 + +#define RIO_ENTER_STORAGE 0x1 +#define RIO_LEAVE_STORAGE 0x2 +#define RIO_RESET 0xC + +struct karma_data { + int in_storage; + char *recv; +}; + +static int rio_karma_init(struct us_data *us); + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id karma_usb_ids[] = { +# include "unusual_karma.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, karma_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev karma_unusual_dev_list[] = { +# include "unusual_karma.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + +/* + * Send commands to Rio Karma. + * + * For each command we send 40 bytes starting 'RIOP\0' followed by + * the command number and a sequence number, which the device will ack + * with a 512-byte packet with the high four bits set and everything + * else null. Then we send 'RIOP\x80' followed by a zero and the + * sequence number, until byte 5 in the response repeats the sequence + * number. + */ +static int rio_karma_send_command(char cmd, struct us_data *us) +{ + int result, partial; + unsigned long timeout; + static unsigned char seq = 1; + struct karma_data *data = (struct karma_data *) us->extra; + + usb_stor_dbg(us, "sending command %04x\n", cmd); + memset(us->iobuf, 0, RIO_SEND_LEN); + memcpy(us->iobuf, RIO_PREFIX, RIO_PREFIX_LEN); + us->iobuf[5] = cmd; + us->iobuf[6] = seq; + + timeout = jiffies + msecs_to_jiffies(6000); + for (;;) { + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + us->iobuf, RIO_SEND_LEN, &partial); + if (result != USB_STOR_XFER_GOOD) + goto err; + + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + data->recv, RIO_RECV_LEN, &partial); + if (result != USB_STOR_XFER_GOOD) + goto err; + + if (data->recv[5] == seq) + break; + + if (time_after(jiffies, timeout)) + goto err; + + us->iobuf[4] = 0x80; + us->iobuf[5] = 0; + msleep(50); + } + + seq++; + if (seq == 0) + seq = 1; + + usb_stor_dbg(us, "sent command %04x\n", cmd); + return 0; +err: + usb_stor_dbg(us, "command %04x failed\n", cmd); + return USB_STOR_TRANSPORT_FAILED; +} + +/* + * Trap START_STOP and READ_10 to leave/re-enter storage mode. + * Everything else is propagated to the normal bulk layer. + */ +static int rio_karma_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int ret; + struct karma_data *data = (struct karma_data *) us->extra; + + if (srb->cmnd[0] == READ_10 && !data->in_storage) { + ret = rio_karma_send_command(RIO_ENTER_STORAGE, us); + if (ret) + return ret; + + data->in_storage = 1; + return usb_stor_Bulk_transport(srb, us); + } else if (srb->cmnd[0] == START_STOP) { + ret = rio_karma_send_command(RIO_LEAVE_STORAGE, us); + if (ret) + return ret; + + data->in_storage = 0; + return rio_karma_send_command(RIO_RESET, us); + } + return usb_stor_Bulk_transport(srb, us); +} + +static void rio_karma_destructor(void *extra) +{ + struct karma_data *data = (struct karma_data *) extra; + kfree(data->recv); +} + +static int rio_karma_init(struct us_data *us) +{ + int ret = 0; + struct karma_data *data = kzalloc(sizeof(struct karma_data), GFP_NOIO); + if (!data) + goto out; + + data->recv = kmalloc(RIO_RECV_LEN, GFP_NOIO); + if (!data->recv) { + kfree(data); + goto out; + } + + us->extra = data; + us->extra_destructor = rio_karma_destructor; + ret = rio_karma_send_command(RIO_ENTER_STORAGE, us); + data->in_storage = (ret == 0); +out: + return ret; +} + +static int karma_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - karma_usb_ids) + karma_unusual_dev_list); + if (result) + return result; + + us->transport_name = "Rio Karma/Bulk"; + us->transport = rio_karma_transport; + us->transport_reset = usb_stor_Bulk_reset; + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver karma_driver = { + .name = "ums-karma", + .probe = karma_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = karma_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(karma_driver); diff --git a/drivers/usb/storage/onetouch.c b/drivers/usb/storage/onetouch.c new file mode 100644 index 00000000..26964895 --- /dev/null +++ b/drivers/usb/storage/onetouch.c @@ -0,0 +1,318 @@ +/* + * Support for the Maxtor OneTouch USB hard drive's button + * + * Current development and maintenance by: + * Copyright (c) 2005 Nick Sillik + * + * Initial work by: + * Copyright (c) 2003 Erik Thyren + * + * Based on usbmouse.c (Vojtech Pavlik) and xpad.c (Marko Friedemann) + * + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include "usb.h" +#include "debug.h" + +MODULE_DESCRIPTION("Maxtor USB OneTouch hard drive button driver"); +MODULE_AUTHOR("Nick Sillik "); +MODULE_LICENSE("GPL"); + +#define ONETOUCH_PKT_LEN 0x02 +#define ONETOUCH_BUTTON KEY_PROG1 + +static int onetouch_connect_input(struct us_data *ss); +static void onetouch_release_input(void *onetouch_); + +struct usb_onetouch { + char name[128]; + char phys[64]; + struct input_dev *dev; /* input device interface */ + struct usb_device *udev; /* usb device */ + + struct urb *irq; /* urb for interrupt in report */ + unsigned char *data; /* input data */ + dma_addr_t data_dma; + unsigned int is_open:1; +}; + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id onetouch_usb_ids[] = { +# include "unusual_onetouch.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, onetouch_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev onetouch_unusual_dev_list[] = { +# include "unusual_onetouch.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + +static void usb_onetouch_irq(struct urb *urb) +{ + struct usb_onetouch *onetouch = urb->context; + signed char *data = onetouch->data; + struct input_dev *dev = onetouch->dev; + int status = urb->status; + int retval; + + switch (status) { + case 0: /* success */ + break; + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -ESHUTDOWN: + return; + /* -EPIPE: should clear the halt */ + default: /* error */ + goto resubmit; + } + + input_report_key(dev, ONETOUCH_BUTTON, data[0] & 0x02); + input_sync(dev); + +resubmit: + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->dev, "can't resubmit intr, %s-%s/input0, " + "retval %d\n", onetouch->udev->bus->bus_name, + onetouch->udev->devpath, retval); +} + +static int usb_onetouch_open(struct input_dev *dev) +{ + struct usb_onetouch *onetouch = input_get_drvdata(dev); + + onetouch->is_open = 1; + onetouch->irq->dev = onetouch->udev; + if (usb_submit_urb(onetouch->irq, GFP_KERNEL)) { + dev_err(&dev->dev, "usb_submit_urb failed\n"); + return -EIO; + } + + return 0; +} + +static void usb_onetouch_close(struct input_dev *dev) +{ + struct usb_onetouch *onetouch = input_get_drvdata(dev); + + usb_kill_urb(onetouch->irq); + onetouch->is_open = 0; +} + +#ifdef CONFIG_PM +static void usb_onetouch_pm_hook(struct us_data *us, int action) +{ + struct usb_onetouch *onetouch = (struct usb_onetouch *) us->extra; + + if (onetouch->is_open) { + switch (action) { + case US_SUSPEND: + usb_kill_urb(onetouch->irq); + break; + case US_RESUME: + if (usb_submit_urb(onetouch->irq, GFP_NOIO) != 0) + dev_err(&onetouch->irq->dev->dev, + "usb_submit_urb failed\n"); + break; + default: + break; + } + } +} +#endif /* CONFIG_PM */ + +static int onetouch_connect_input(struct us_data *ss) +{ + struct usb_device *udev = ss->pusb_dev; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_onetouch *onetouch; + struct input_dev *input_dev; + int pipe, maxp; + int error = -ENOMEM; + + interface = ss->pusb_intf->cur_altsetting; + + if (interface->desc.bNumEndpoints != 3) + return -ENODEV; + + endpoint = &interface->endpoint[2].desc; + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = min(maxp, ONETOUCH_PKT_LEN); + + onetouch = kzalloc(sizeof(struct usb_onetouch), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!onetouch || !input_dev) + goto fail1; + + onetouch->data = usb_alloc_coherent(udev, ONETOUCH_PKT_LEN, + GFP_KERNEL, &onetouch->data_dma); + if (!onetouch->data) + goto fail1; + + onetouch->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!onetouch->irq) + goto fail2; + + onetouch->udev = udev; + onetouch->dev = input_dev; + + if (udev->manufacturer) + strlcpy(onetouch->name, udev->manufacturer, + sizeof(onetouch->name)); + if (udev->product) { + if (udev->manufacturer) + strlcat(onetouch->name, " ", sizeof(onetouch->name)); + strlcat(onetouch->name, udev->product, sizeof(onetouch->name)); + } + + if (!strlen(onetouch->name)) + snprintf(onetouch->name, sizeof(onetouch->name), + "Maxtor Onetouch %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + usb_make_path(udev, onetouch->phys, sizeof(onetouch->phys)); + strlcat(onetouch->phys, "/input0", sizeof(onetouch->phys)); + + input_dev->name = onetouch->name; + input_dev->phys = onetouch->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &udev->dev; + + set_bit(EV_KEY, input_dev->evbit); + set_bit(ONETOUCH_BUTTON, input_dev->keybit); + clear_bit(0, input_dev->keybit); + + input_set_drvdata(input_dev, onetouch); + + input_dev->open = usb_onetouch_open; + input_dev->close = usb_onetouch_close; + + usb_fill_int_urb(onetouch->irq, udev, pipe, onetouch->data, maxp, + usb_onetouch_irq, onetouch, endpoint->bInterval); + onetouch->irq->transfer_dma = onetouch->data_dma; + onetouch->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + ss->extra_destructor = onetouch_release_input; + ss->extra = onetouch; +#ifdef CONFIG_PM + ss->suspend_resume_hook = usb_onetouch_pm_hook; +#endif + + error = input_register_device(onetouch->dev); + if (error) + goto fail3; + + return 0; + + fail3: usb_free_urb(onetouch->irq); + fail2: usb_free_coherent(udev, ONETOUCH_PKT_LEN, + onetouch->data, onetouch->data_dma); + fail1: kfree(onetouch); + input_free_device(input_dev); + return error; +} + +static void onetouch_release_input(void *onetouch_) +{ + struct usb_onetouch *onetouch = (struct usb_onetouch *) onetouch_; + + if (onetouch) { + usb_kill_urb(onetouch->irq); + input_unregister_device(onetouch->dev); + usb_free_urb(onetouch->irq); + usb_free_coherent(onetouch->udev, ONETOUCH_PKT_LEN, + onetouch->data, onetouch->data_dma); + } +} + +static int onetouch_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - onetouch_usb_ids) + onetouch_unusual_dev_list); + if (result) + return result; + + /* Use default transport and protocol */ + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver onetouch_driver = { + .name = "ums-onetouch", + .probe = onetouch_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = onetouch_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(onetouch_driver); diff --git a/drivers/usb/storage/option_ms.c b/drivers/usb/storage/option_ms.c new file mode 100644 index 00000000..b2b35b1d --- /dev/null +++ b/drivers/usb/storage/option_ms.c @@ -0,0 +1,171 @@ +/* + * Driver for Option High Speed Mobile Devices. + * + * (c) 2008 Dan Williams + * + * Inspiration taken from sierra_ms.c by Kevin Lloyd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "option_ms.h" +#include "debug.h" + +#define ZCD_FORCE_MODEM 0x01 +#define ZCD_ALLOW_MS 0x02 + +static unsigned int option_zero_cd = ZCD_FORCE_MODEM; +module_param(option_zero_cd, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(option_zero_cd, "ZeroCD mode (1=Force Modem (default)," + " 2=Allow CD-Rom"); + +#define RESPONSE_LEN 1024 + +static int option_rezero(struct us_data *us) +{ + const unsigned char rezero_msg[] = { + 0x55, 0x53, 0x42, 0x43, 0x78, 0x56, 0x34, 0x12, + 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x06, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + char *buffer; + int result; + + usb_stor_dbg(us, "Option MS: %s\n", "DEVICE MODE SWITCH"); + + buffer = kzalloc(RESPONSE_LEN, GFP_KERNEL); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + memcpy(buffer, rezero_msg, sizeof(rezero_msg)); + result = usb_stor_bulk_transfer_buf(us, + us->send_bulk_pipe, + buffer, sizeof(rezero_msg), NULL); + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_XFER_ERROR; + goto out; + } + + /* Some of the devices need to be asked for a response, but we don't + * care what that response is. + */ + usb_stor_bulk_transfer_buf(us, + us->recv_bulk_pipe, + buffer, RESPONSE_LEN, NULL); + + /* Read the CSW */ + usb_stor_bulk_transfer_buf(us, + us->recv_bulk_pipe, + buffer, 13, NULL); + + result = USB_STOR_XFER_GOOD; + +out: + kfree(buffer); + return result; +} + +static int option_inquiry(struct us_data *us) +{ + const unsigned char inquiry_msg[] = { + 0x55, 0x53, 0x42, 0x43, 0x12, 0x34, 0x56, 0x78, + 0x24, 0x00, 0x00, 0x00, 0x80, 0x00, 0x06, 0x12, + 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + char *buffer; + int result; + + usb_stor_dbg(us, "Option MS: %s\n", "device inquiry for vendor name"); + + buffer = kzalloc(0x24, GFP_KERNEL); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + memcpy(buffer, inquiry_msg, sizeof(inquiry_msg)); + result = usb_stor_bulk_transfer_buf(us, + us->send_bulk_pipe, + buffer, sizeof(inquiry_msg), NULL); + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_XFER_ERROR; + goto out; + } + + result = usb_stor_bulk_transfer_buf(us, + us->recv_bulk_pipe, + buffer, 0x24, NULL); + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_XFER_ERROR; + goto out; + } + + result = memcmp(buffer+8, "Option", 6); + + if (result != 0) + result = memcmp(buffer+8, "ZCOPTION", 8); + + /* Read the CSW */ + usb_stor_bulk_transfer_buf(us, + us->recv_bulk_pipe, + buffer, 13, NULL); + +out: + kfree(buffer); + return result; +} + + +int option_ms_init(struct us_data *us) +{ + int result; + + usb_stor_dbg(us, "Option MS: %s\n", "option_ms_init called"); + + /* Additional test for vendor information via INQUIRY, + * because some vendor/product IDs are ambiguous + */ + result = option_inquiry(us); + if (result != 0) { + usb_stor_dbg(us, "Option MS: %s\n", + "vendor is not Option or not determinable, no action taken"); + return 0; + } else + usb_stor_dbg(us, "Option MS: %s\n", + "this is a genuine Option device, proceeding"); + + /* Force Modem mode */ + if (option_zero_cd == ZCD_FORCE_MODEM) { + usb_stor_dbg(us, "Option MS: %s\n", "Forcing Modem Mode"); + result = option_rezero(us); + if (result != USB_STOR_XFER_GOOD) + usb_stor_dbg(us, "Option MS: %s\n", + "Failed to switch to modem mode"); + return -EIO; + } else if (option_zero_cd == ZCD_ALLOW_MS) { + /* Allow Mass Storage mode (keep CD-Rom) */ + usb_stor_dbg(us, "Option MS: %s\n", + "Allowing Mass Storage Mode if device requests it"); + } + + return 0; +} + diff --git a/drivers/usb/storage/option_ms.h b/drivers/usb/storage/option_ms.h new file mode 100644 index 00000000..b6e448ca --- /dev/null +++ b/drivers/usb/storage/option_ms.h @@ -0,0 +1,4 @@ +#ifndef _OPTION_MS_H_ +#define _OPTION_MS_H_ +extern int option_ms_init(struct us_data *us); +#endif diff --git a/drivers/usb/storage/protocol.c b/drivers/usb/storage/protocol.c new file mode 100644 index 00000000..5dfb4c36 --- /dev/null +++ b/drivers/usb/storage/protocol.c @@ -0,0 +1,220 @@ +/* Driver for USB Mass Storage compliant devices + * + * Current development and maintenance by: + * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Developed with the assistance of: + * (c) 2000 David L. Brown, Jr. (usb-storage@davidb.org) + * (c) 2002 Alan Stern (stern@rowland.org) + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "usb.h" +#include "protocol.h" +#include "debug.h" +#include "scsiglue.h" +#include "transport.h" + +/*********************************************************************** + * Protocol routines + ***********************************************************************/ + +void usb_stor_pad12_command(struct scsi_cmnd *srb, struct us_data *us) +{ + /* + * Pad the SCSI command with zeros out to 12 bytes. If the + * command already is 12 bytes or longer, leave it alone. + * + * NOTE: This only works because a scsi_cmnd struct field contains + * a unsigned char cmnd[16], so we know we have storage available + */ + for (; srb->cmd_len < 12; srb->cmd_len++) + srb->cmnd[srb->cmd_len] = 0; + + /* send the command to the transport layer */ + usb_stor_invoke_transport(srb, us); +} + +void usb_stor_ufi_command(struct scsi_cmnd *srb, struct us_data *us) +{ + /* fix some commands -- this is a form of mode translation + * UFI devices only accept 12 byte long commands + * + * NOTE: This only works because a scsi_cmnd struct field contains + * a unsigned char cmnd[16], so we know we have storage available + */ + + /* Pad the ATAPI command with zeros */ + for (; srb->cmd_len < 12; srb->cmd_len++) + srb->cmnd[srb->cmd_len] = 0; + + /* set command length to 12 bytes (this affects the transport layer) */ + srb->cmd_len = 12; + + /* XXX We should be constantly re-evaluating the need for these */ + + /* determine the correct data length for these commands */ + switch (srb->cmnd[0]) { + + /* for INQUIRY, UFI devices only ever return 36 bytes */ + case INQUIRY: + srb->cmnd[4] = 36; + break; + + /* again, for MODE_SENSE_10, we get the minimum (8) */ + case MODE_SENSE_10: + srb->cmnd[7] = 0; + srb->cmnd[8] = 8; + break; + + /* for REQUEST_SENSE, UFI devices only ever return 18 bytes */ + case REQUEST_SENSE: + srb->cmnd[4] = 18; + break; + } /* end switch on cmnd[0] */ + + /* send the command to the transport layer */ + usb_stor_invoke_transport(srb, us); +} + +void usb_stor_transparent_scsi_command(struct scsi_cmnd *srb, + struct us_data *us) +{ + /* send the command to the transport layer */ + usb_stor_invoke_transport(srb, us); +} +EXPORT_SYMBOL_GPL(usb_stor_transparent_scsi_command); + +/*********************************************************************** + * Scatter-gather transfer buffer access routines + ***********************************************************************/ + +/* Copy a buffer of length buflen to/from the srb's transfer buffer. + * Update the **sgptr and *offset variables so that the next copy will + * pick up from where this one left off. + */ +unsigned int usb_stor_access_xfer_buf(unsigned char *buffer, + unsigned int buflen, struct scsi_cmnd *srb, struct scatterlist **sgptr, + unsigned int *offset, enum xfer_buf_dir dir) +{ + unsigned int cnt; + struct scatterlist *sg = *sgptr; + + /* We have to go through the list one entry + * at a time. Each s-g entry contains some number of pages, and + * each page has to be kmap()'ed separately. If the page is already + * in kernel-addressable memory then kmap() will return its address. + * If the page is not directly accessible -- such as a user buffer + * located in high memory -- then kmap() will map it to a temporary + * position in the kernel's virtual address space. + */ + + if (!sg) + sg = scsi_sglist(srb); + + /* This loop handles a single s-g list entry, which may + * include multiple pages. Find the initial page structure + * and the starting offset within the page, and update + * the *offset and **sgptr values for the next loop. + */ + cnt = 0; + while (cnt < buflen && sg) { + struct page *page = sg_page(sg) + + ((sg->offset + *offset) >> PAGE_SHIFT); + unsigned int poff = (sg->offset + *offset) & (PAGE_SIZE-1); + unsigned int sglen = sg->length - *offset; + + if (sglen > buflen - cnt) { + + /* Transfer ends within this s-g entry */ + sglen = buflen - cnt; + *offset += sglen; + } else { + + /* Transfer continues to next s-g entry */ + *offset = 0; + sg = sg_next(sg); + } + + /* Transfer the data for all the pages in this + * s-g entry. For each page: call kmap(), do the + * transfer, and call kunmap() immediately after. */ + while (sglen > 0) { + unsigned int plen = min(sglen, (unsigned int) + PAGE_SIZE - poff); + unsigned char *ptr = kmap(page); + + if (dir == TO_XFER_BUF) + memcpy(ptr + poff, buffer + cnt, plen); + else + memcpy(buffer + cnt, ptr + poff, plen); + kunmap(page); + + /* Start at the beginning of the next page */ + poff = 0; + ++page; + cnt += plen; + sglen -= plen; + } + } + *sgptr = sg; + + /* Return the amount actually transferred */ + return cnt; +} +EXPORT_SYMBOL_GPL(usb_stor_access_xfer_buf); + +/* Store the contents of buffer into srb's transfer buffer and set the + * SCSI residue. + */ +void usb_stor_set_xfer_buf(unsigned char *buffer, + unsigned int buflen, struct scsi_cmnd *srb) +{ + unsigned int offset = 0; + struct scatterlist *sg = NULL; + + buflen = min(buflen, scsi_bufflen(srb)); + buflen = usb_stor_access_xfer_buf(buffer, buflen, srb, &sg, &offset, + TO_XFER_BUF); + if (buflen < scsi_bufflen(srb)) + scsi_set_resid(srb, scsi_bufflen(srb) - buflen); +} +EXPORT_SYMBOL_GPL(usb_stor_set_xfer_buf); diff --git a/drivers/usb/storage/protocol.h b/drivers/usb/storage/protocol.h new file mode 100644 index 00000000..ffc3e2af --- /dev/null +++ b/drivers/usb/storage/protocol.h @@ -0,0 +1,57 @@ +/* Driver for USB Mass Storage compliant devices + * Protocol Functions Header File + * + * Current development and maintenance by: + * (c) 1999, 2000 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _PROTOCOL_H_ +#define _PROTOCOL_H_ + +/* Protocol handling routines */ +extern void usb_stor_pad12_command(struct scsi_cmnd*, struct us_data*); +extern void usb_stor_ufi_command(struct scsi_cmnd*, struct us_data*); +extern void usb_stor_transparent_scsi_command(struct scsi_cmnd*, + struct us_data*); + +/* struct scsi_cmnd transfer buffer access utilities */ +enum xfer_buf_dir {TO_XFER_BUF, FROM_XFER_BUF}; + +extern unsigned int usb_stor_access_xfer_buf(unsigned char *buffer, + unsigned int buflen, struct scsi_cmnd *srb, struct scatterlist **, + unsigned int *offset, enum xfer_buf_dir dir); + +extern void usb_stor_set_xfer_buf(unsigned char *buffer, + unsigned int buflen, struct scsi_cmnd *srb); +#endif diff --git a/drivers/usb/storage/realtek_cr.c b/drivers/usb/storage/realtek_cr.c new file mode 100644 index 00000000..281be56d --- /dev/null +++ b/drivers/usb/storage/realtek_cr.c @@ -0,0 +1,1071 @@ +/* Driver for Realtek RTS51xx USB card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + * + * Author: + * wwang (wei_wang@realsil.com.cn) + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + +MODULE_DESCRIPTION("Driver for Realtek USB Card Reader"); +MODULE_AUTHOR("wwang "); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.03"); + +static int auto_delink_en = 1; +module_param(auto_delink_en, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(auto_delink_en, "enable auto delink"); + +#ifdef CONFIG_REALTEK_AUTOPM +static int ss_en = 1; +module_param(ss_en, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ss_en, "enable selective suspend"); + +static int ss_delay = 50; +module_param(ss_delay, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ss_delay, + "seconds to delay before entering selective suspend"); + +enum RTS51X_STAT { + RTS51X_STAT_INIT, + RTS51X_STAT_IDLE, + RTS51X_STAT_RUN, + RTS51X_STAT_SS +}; + +#define POLLING_INTERVAL 50 + +#define rts51x_set_stat(chip, stat) \ + ((chip)->state = (enum RTS51X_STAT)(stat)) +#define rts51x_get_stat(chip) ((chip)->state) + +#define SET_LUN_READY(chip, lun) ((chip)->lun_ready |= ((u8)1 << (lun))) +#define CLR_LUN_READY(chip, lun) ((chip)->lun_ready &= ~((u8)1 << (lun))) +#define TST_LUN_READY(chip, lun) ((chip)->lun_ready & ((u8)1 << (lun))) + +#endif + +struct rts51x_status { + u16 vid; + u16 pid; + u8 cur_lun; + u8 card_type; + u8 total_lun; + u16 fw_ver; + u8 phy_exist; + u8 multi_flag; + u8 multi_card; + u8 log_exist; + union { + u8 detailed_type1; + u8 detailed_type2; + } detailed_type; + u8 function[2]; +}; + +struct rts51x_chip { + u16 vendor_id; + u16 product_id; + char max_lun; + + struct rts51x_status *status; + int status_len; + + u32 flag; + struct us_data *us; + +#ifdef CONFIG_REALTEK_AUTOPM + struct timer_list rts51x_suspend_timer; + unsigned long timer_expires; + int pwr_state; + u8 lun_ready; + enum RTS51X_STAT state; + int support_auto_delink; +#endif + /* used to back up the protocal choosen in probe1 phase */ + proto_cmnd proto_handler_backup; +}; + +/* flag definition */ +#define FLIDX_AUTO_DELINK 0x01 + +#define SCSI_LUN(srb) ((srb)->device->lun) + +/* Bit Operation */ +#define SET_BIT(data, idx) ((data) |= 1 << (idx)) +#define CLR_BIT(data, idx) ((data) &= ~(1 << (idx))) +#define CHK_BIT(data, idx) ((data) & (1 << (idx))) + +#define SET_AUTO_DELINK(chip) ((chip)->flag |= FLIDX_AUTO_DELINK) +#define CLR_AUTO_DELINK(chip) ((chip)->flag &= ~FLIDX_AUTO_DELINK) +#define CHK_AUTO_DELINK(chip) ((chip)->flag & FLIDX_AUTO_DELINK) + +#define RTS51X_GET_VID(chip) ((chip)->vendor_id) +#define RTS51X_GET_PID(chip) ((chip)->product_id) + +#define VENDOR_ID(chip) ((chip)->status[0].vid) +#define PRODUCT_ID(chip) ((chip)->status[0].pid) +#define FW_VERSION(chip) ((chip)->status[0].fw_ver) +#define STATUS_LEN(chip) ((chip)->status_len) + +#define STATUS_SUCCESS 0 +#define STATUS_FAIL 1 + +/* Check card reader function */ +#define SUPPORT_DETAILED_TYPE1(chip) \ + CHK_BIT((chip)->status[0].function[0], 1) +#define SUPPORT_OT(chip) \ + CHK_BIT((chip)->status[0].function[0], 2) +#define SUPPORT_OC(chip) \ + CHK_BIT((chip)->status[0].function[0], 3) +#define SUPPORT_AUTO_DELINK(chip) \ + CHK_BIT((chip)->status[0].function[0], 4) +#define SUPPORT_SDIO(chip) \ + CHK_BIT((chip)->status[0].function[1], 0) +#define SUPPORT_DETAILED_TYPE2(chip) \ + CHK_BIT((chip)->status[0].function[1], 1) + +#define CHECK_PID(chip, pid) (RTS51X_GET_PID(chip) == (pid)) +#define CHECK_FW_VER(chip, fw_ver) (FW_VERSION(chip) == (fw_ver)) +#define CHECK_ID(chip, pid, fw_ver) \ + (CHECK_PID((chip), (pid)) && CHECK_FW_VER((chip), (fw_ver))) + +static int init_realtek_cr(struct us_data *us); + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{\ + USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) \ +} + +static const struct usb_device_id realtek_cr_ids[] = { +# include "unusual_realtek.h" + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, realtek_cr_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev realtek_cr_unusual_dev_list[] = { +# include "unusual_realtek.h" + {} /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + +static int rts51x_bulk_transport(struct us_data *us, u8 lun, + u8 *cmd, int cmd_len, u8 *buf, int buf_len, + enum dma_data_direction dir, int *act_len) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *)us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *)us->iobuf; + int result; + unsigned int residue; + unsigned int cswlen; + unsigned int cbwlen = US_BULK_CB_WRAP_LEN; + + /* set up the command wrapper */ + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = cpu_to_le32(buf_len); + bcb->Flags = (dir == DMA_FROM_DEVICE) ? US_BULK_FLAG_IN : 0; + bcb->Tag = ++us->tag; + bcb->Lun = lun; + bcb->Length = cmd_len; + + /* copy the command payload */ + memset(bcb->CDB, 0, sizeof(bcb->CDB)); + memcpy(bcb->CDB, cmd, bcb->Length); + + /* send it to out endpoint */ + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + bcb, cbwlen, NULL); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* DATA STAGE */ + /* send/receive data payload, if there is any */ + + if (buf && buf_len) { + unsigned int pipe = (dir == DMA_FROM_DEVICE) ? + us->recv_bulk_pipe : us->send_bulk_pipe; + result = usb_stor_bulk_transfer_buf(us, pipe, + buf, buf_len, NULL); + if (result == USB_STOR_XFER_ERROR) + return USB_STOR_TRANSPORT_ERROR; + } + + /* get CSW for device status */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + bcs, US_BULK_CS_WRAP_LEN, &cswlen); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* check bulk status */ + if (bcs->Signature != cpu_to_le32(US_BULK_CS_SIGN)) { + usb_stor_dbg(us, "Signature mismatch: got %08X, expecting %08X\n", + le32_to_cpu(bcs->Signature), US_BULK_CS_SIGN); + return USB_STOR_TRANSPORT_ERROR; + } + + residue = bcs->Residue; + if (bcs->Tag != us->tag) + return USB_STOR_TRANSPORT_ERROR; + + /* try to compute the actual residue, based on how much data + * was really transferred and what the device tells us */ + if (residue) + residue = residue < buf_len ? residue : buf_len; + + if (act_len) + *act_len = buf_len - residue; + + /* based on the status code, we report good or bad */ + switch (bcs->Status) { + case US_BULK_STAT_OK: + /* command good -- note that data could be short */ + return USB_STOR_TRANSPORT_GOOD; + + case US_BULK_STAT_FAIL: + /* command failed */ + return USB_STOR_TRANSPORT_FAILED; + + case US_BULK_STAT_PHASE: + /* phase error -- note that a transport reset will be + * invoked by the invoke_transport() function + */ + return USB_STOR_TRANSPORT_ERROR; + } + + /* we should never get here, but if we do, we're in trouble */ + return USB_STOR_TRANSPORT_ERROR; +} + +static int rts51x_bulk_transport_special(struct us_data *us, u8 lun, + u8 *cmd, int cmd_len, u8 *buf, int buf_len, + enum dma_data_direction dir, int *act_len) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf; + int result; + unsigned int cswlen; + unsigned int cbwlen = US_BULK_CB_WRAP_LEN; + + /* set up the command wrapper */ + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = cpu_to_le32(buf_len); + bcb->Flags = (dir == DMA_FROM_DEVICE) ? US_BULK_FLAG_IN : 0; + bcb->Tag = ++us->tag; + bcb->Lun = lun; + bcb->Length = cmd_len; + + /* copy the command payload */ + memset(bcb->CDB, 0, sizeof(bcb->CDB)); + memcpy(bcb->CDB, cmd, bcb->Length); + + /* send it to out endpoint */ + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + bcb, cbwlen, NULL); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* DATA STAGE */ + /* send/receive data payload, if there is any */ + + if (buf && buf_len) { + unsigned int pipe = (dir == DMA_FROM_DEVICE) ? + us->recv_bulk_pipe : us->send_bulk_pipe; + result = usb_stor_bulk_transfer_buf(us, pipe, + buf, buf_len, NULL); + if (result == USB_STOR_XFER_ERROR) + return USB_STOR_TRANSPORT_ERROR; + } + + /* get CSW for device status */ + result = usb_bulk_msg(us->pusb_dev, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, &cswlen, 250); + return result; +} + +/* Determine what the maximum LUN supported is */ +static int rts51x_get_max_lun(struct us_data *us) +{ + int result; + + /* issue the command */ + us->iobuf[0] = 0; + result = usb_stor_control_msg(us, us->recv_ctrl_pipe, + US_BULK_GET_MAX_LUN, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + 0, us->ifnum, us->iobuf, 1, 10 * HZ); + + usb_stor_dbg(us, "GetMaxLUN command result is %d, data is %d\n", + result, us->iobuf[0]); + + /* if we have a successful request, return the result */ + if (result > 0) + return us->iobuf[0]; + + return 0; +} + +static int rts51x_read_mem(struct us_data *us, u16 addr, u8 *data, u16 len) +{ + int retval; + u8 cmnd[12] = { 0 }; + u8 *buf; + + buf = kmalloc(len, GFP_NOIO); + if (buf == NULL) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "addr = 0x%x, len = %d\n", addr, len); + + cmnd[0] = 0xF0; + cmnd[1] = 0x0D; + cmnd[2] = (u8) (addr >> 8); + cmnd[3] = (u8) addr; + cmnd[4] = (u8) (len >> 8); + cmnd[5] = (u8) len; + + retval = rts51x_bulk_transport(us, 0, cmnd, 12, + buf, len, DMA_FROM_DEVICE, NULL); + if (retval != USB_STOR_TRANSPORT_GOOD) { + kfree(buf); + return -EIO; + } + + memcpy(data, buf, len); + kfree(buf); + return 0; +} + +static int rts51x_write_mem(struct us_data *us, u16 addr, u8 *data, u16 len) +{ + int retval; + u8 cmnd[12] = { 0 }; + u8 *buf; + + buf = kmemdup(data, len, GFP_NOIO); + if (buf == NULL) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "addr = 0x%x, len = %d\n", addr, len); + + cmnd[0] = 0xF0; + cmnd[1] = 0x0E; + cmnd[2] = (u8) (addr >> 8); + cmnd[3] = (u8) addr; + cmnd[4] = (u8) (len >> 8); + cmnd[5] = (u8) len; + + retval = rts51x_bulk_transport(us, 0, cmnd, 12, + buf, len, DMA_TO_DEVICE, NULL); + kfree(buf); + if (retval != USB_STOR_TRANSPORT_GOOD) + return -EIO; + + return 0; +} + +static int rts51x_read_status(struct us_data *us, + u8 lun, u8 *status, int len, int *actlen) +{ + int retval; + u8 cmnd[12] = { 0 }; + u8 *buf; + + buf = kmalloc(len, GFP_NOIO); + if (buf == NULL) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "lun = %d\n", lun); + + cmnd[0] = 0xF0; + cmnd[1] = 0x09; + + retval = rts51x_bulk_transport(us, lun, cmnd, 12, + buf, len, DMA_FROM_DEVICE, actlen); + if (retval != USB_STOR_TRANSPORT_GOOD) { + kfree(buf); + return -EIO; + } + + memcpy(status, buf, len); + kfree(buf); + return 0; +} + +static int rts51x_check_status(struct us_data *us, u8 lun) +{ + struct rts51x_chip *chip = (struct rts51x_chip *)(us->extra); + int retval; + u8 buf[16]; + + retval = rts51x_read_status(us, lun, buf, 16, &(chip->status_len)); + if (retval != STATUS_SUCCESS) + return -EIO; + + usb_stor_dbg(us, "chip->status_len = %d\n", chip->status_len); + + chip->status[lun].vid = ((u16) buf[0] << 8) | buf[1]; + chip->status[lun].pid = ((u16) buf[2] << 8) | buf[3]; + chip->status[lun].cur_lun = buf[4]; + chip->status[lun].card_type = buf[5]; + chip->status[lun].total_lun = buf[6]; + chip->status[lun].fw_ver = ((u16) buf[7] << 8) | buf[8]; + chip->status[lun].phy_exist = buf[9]; + chip->status[lun].multi_flag = buf[10]; + chip->status[lun].multi_card = buf[11]; + chip->status[lun].log_exist = buf[12]; + if (chip->status_len == 16) { + chip->status[lun].detailed_type.detailed_type1 = buf[13]; + chip->status[lun].function[0] = buf[14]; + chip->status[lun].function[1] = buf[15]; + } + + return 0; +} + +static int enable_oscillator(struct us_data *us) +{ + int retval; + u8 value; + + retval = rts51x_read_mem(us, 0xFE77, &value, 1); + if (retval < 0) + return -EIO; + + value |= 0x04; + retval = rts51x_write_mem(us, 0xFE77, &value, 1); + if (retval < 0) + return -EIO; + + retval = rts51x_read_mem(us, 0xFE77, &value, 1); + if (retval < 0) + return -EIO; + + if (!(value & 0x04)) + return -EIO; + + return 0; +} + +static int __do_config_autodelink(struct us_data *us, u8 *data, u16 len) +{ + int retval; + u8 cmnd[12] = {0}; + u8 *buf; + + usb_stor_dbg(us, "addr = 0xfe47, len = %d\n", len); + + buf = kmemdup(data, len, GFP_NOIO); + if (!buf) + return USB_STOR_TRANSPORT_ERROR; + + cmnd[0] = 0xF0; + cmnd[1] = 0x0E; + cmnd[2] = 0xfe; + cmnd[3] = 0x47; + cmnd[4] = (u8)(len >> 8); + cmnd[5] = (u8)len; + + retval = rts51x_bulk_transport_special(us, 0, cmnd, 12, buf, len, DMA_TO_DEVICE, NULL); + kfree(buf); + if (retval != USB_STOR_TRANSPORT_GOOD) { + return -EIO; + } + + return 0; +} + +static int do_config_autodelink(struct us_data *us, int enable, int force) +{ + int retval; + u8 value; + + retval = rts51x_read_mem(us, 0xFE47, &value, 1); + if (retval < 0) + return -EIO; + + if (enable) { + if (force) + value |= 0x03; + else + value |= 0x01; + } else { + value &= ~0x03; + } + + usb_stor_dbg(us, "set 0xfe47 to 0x%x\n", value); + + /* retval = rts51x_write_mem(us, 0xFE47, &value, 1); */ + retval = __do_config_autodelink(us, &value, 1); + if (retval < 0) + return -EIO; + + return 0; +} + +static int config_autodelink_after_power_on(struct us_data *us) +{ + struct rts51x_chip *chip = (struct rts51x_chip *)(us->extra); + int retval; + u8 value; + + if (!CHK_AUTO_DELINK(chip)) + return 0; + + retval = rts51x_read_mem(us, 0xFE47, &value, 1); + if (retval < 0) + return -EIO; + + if (auto_delink_en) { + CLR_BIT(value, 0); + CLR_BIT(value, 1); + SET_BIT(value, 2); + + if (CHECK_ID(chip, 0x0138, 0x3882)) + CLR_BIT(value, 2); + + SET_BIT(value, 7); + + /* retval = rts51x_write_mem(us, 0xFE47, &value, 1); */ + retval = __do_config_autodelink(us, &value, 1); + if (retval < 0) + return -EIO; + + retval = enable_oscillator(us); + if (retval == 0) + (void)do_config_autodelink(us, 1, 0); + } else { + /* Autodelink controlled by firmware */ + + SET_BIT(value, 2); + + if (CHECK_ID(chip, 0x0138, 0x3882)) + CLR_BIT(value, 2); + + if (CHECK_ID(chip, 0x0159, 0x5889) || + CHECK_ID(chip, 0x0138, 0x3880)) { + CLR_BIT(value, 0); + CLR_BIT(value, 7); + } + + /* retval = rts51x_write_mem(us, 0xFE47, &value, 1); */ + retval = __do_config_autodelink(us, &value, 1); + if (retval < 0) + return -EIO; + + if (CHECK_ID(chip, 0x0159, 0x5888)) { + value = 0xFF; + retval = rts51x_write_mem(us, 0xFE79, &value, 1); + if (retval < 0) + return -EIO; + + value = 0x01; + retval = rts51x_write_mem(us, 0x48, &value, 1); + if (retval < 0) + return -EIO; + } + } + + return 0; +} + +static int config_autodelink_before_power_down(struct us_data *us) +{ + struct rts51x_chip *chip = (struct rts51x_chip *)(us->extra); + int retval; + u8 value; + + if (!CHK_AUTO_DELINK(chip)) + return 0; + + if (auto_delink_en) { + retval = rts51x_read_mem(us, 0xFE77, &value, 1); + if (retval < 0) + return -EIO; + + SET_BIT(value, 2); + retval = rts51x_write_mem(us, 0xFE77, &value, 1); + if (retval < 0) + return -EIO; + + if (CHECK_ID(chip, 0x0159, 0x5888)) { + value = 0x01; + retval = rts51x_write_mem(us, 0x48, &value, 1); + if (retval < 0) + return -EIO; + } + + retval = rts51x_read_mem(us, 0xFE47, &value, 1); + if (retval < 0) + return -EIO; + + SET_BIT(value, 0); + if (CHECK_ID(chip, 0x0138, 0x3882)) + SET_BIT(value, 2); + retval = rts51x_write_mem(us, 0xFE77, &value, 1); + if (retval < 0) + return -EIO; + } else { + if (CHECK_ID(chip, 0x0159, 0x5889) || + CHECK_ID(chip, 0x0138, 0x3880) || + CHECK_ID(chip, 0x0138, 0x3882)) { + retval = rts51x_read_mem(us, 0xFE47, &value, 1); + if (retval < 0) + return -EIO; + + if (CHECK_ID(chip, 0x0159, 0x5889) || + CHECK_ID(chip, 0x0138, 0x3880)) { + SET_BIT(value, 0); + SET_BIT(value, 7); + } + + if (CHECK_ID(chip, 0x0138, 0x3882)) + SET_BIT(value, 2); + + /* retval = rts51x_write_mem(us, 0xFE47, &value, 1); */ + retval = __do_config_autodelink(us, &value, 1); + if (retval < 0) + return -EIO; + } + + if (CHECK_ID(chip, 0x0159, 0x5888)) { + value = 0x01; + retval = rts51x_write_mem(us, 0x48, &value, 1); + if (retval < 0) + return -EIO; + } + } + + return 0; +} + +static void fw5895_init(struct us_data *us) +{ + struct rts51x_chip *chip = (struct rts51x_chip *)(us->extra); + int retval; + u8 val; + + if ((PRODUCT_ID(chip) != 0x0158) || (FW_VERSION(chip) != 0x5895)) { + usb_stor_dbg(us, "Not the specified device, return immediately!\n"); + } else { + retval = rts51x_read_mem(us, 0xFD6F, &val, 1); + if (retval == STATUS_SUCCESS && (val & 0x1F) == 0) { + val = 0x1F; + retval = rts51x_write_mem(us, 0xFD70, &val, 1); + if (retval != STATUS_SUCCESS) + usb_stor_dbg(us, "Write memory fail\n"); + } else { + usb_stor_dbg(us, "Read memory fail, OR (val & 0x1F) != 0\n"); + } + } +} + +#ifdef CONFIG_REALTEK_AUTOPM +static void fw5895_set_mmc_wp(struct us_data *us) +{ + struct rts51x_chip *chip = (struct rts51x_chip *)(us->extra); + int retval; + u8 buf[13]; + + if ((PRODUCT_ID(chip) != 0x0158) || (FW_VERSION(chip) != 0x5895)) { + usb_stor_dbg(us, "Not the specified device, return immediately!\n"); + } else { + retval = rts51x_read_mem(us, 0xFD6F, buf, 1); + if (retval == STATUS_SUCCESS && (buf[0] & 0x24) == 0x24) { + /* SD Exist and SD WP */ + retval = rts51x_read_mem(us, 0xD04E, buf, 1); + if (retval == STATUS_SUCCESS) { + buf[0] |= 0x04; + retval = rts51x_write_mem(us, 0xFD70, buf, 1); + if (retval != STATUS_SUCCESS) + usb_stor_dbg(us, "Write memory fail\n"); + } else { + usb_stor_dbg(us, "Read memory fail\n"); + } + } else { + usb_stor_dbg(us, "Read memory fail, OR (buf[0]&0x24)!=0x24\n"); + } + } +} + +static void rts51x_modi_suspend_timer(struct rts51x_chip *chip) +{ + struct us_data *us = chip->us; + + usb_stor_dbg(us, "state:%d\n", rts51x_get_stat(chip)); + + chip->timer_expires = jiffies + msecs_to_jiffies(1000*ss_delay); + mod_timer(&chip->rts51x_suspend_timer, chip->timer_expires); +} + +static void rts51x_suspend_timer_fn(unsigned long data) +{ + struct rts51x_chip *chip = (struct rts51x_chip *)data; + struct us_data *us = chip->us; + + switch (rts51x_get_stat(chip)) { + case RTS51X_STAT_INIT: + case RTS51X_STAT_RUN: + rts51x_modi_suspend_timer(chip); + break; + case RTS51X_STAT_IDLE: + case RTS51X_STAT_SS: + usb_stor_dbg(us, "RTS51X_STAT_SS, intf->pm_usage_cnt:%d, power.usage:%d\n", + atomic_read(&us->pusb_intf->pm_usage_cnt), + atomic_read(&us->pusb_intf->dev.power.usage_count)); + + if (atomic_read(&us->pusb_intf->pm_usage_cnt) > 0) { + usb_stor_dbg(us, "Ready to enter SS state\n"); + rts51x_set_stat(chip, RTS51X_STAT_SS); + /* ignore mass storage interface's children */ + pm_suspend_ignore_children(&us->pusb_intf->dev, true); + usb_autopm_put_interface_async(us->pusb_intf); + usb_stor_dbg(us, "RTS51X_STAT_SS 01, intf->pm_usage_cnt:%d, power.usage:%d\n", + atomic_read(&us->pusb_intf->pm_usage_cnt), + atomic_read(&us->pusb_intf->dev.power.usage_count)); + } + break; + default: + usb_stor_dbg(us, "Unknown state !!!\n"); + break; + } +} + +static inline int working_scsi(struct scsi_cmnd *srb) +{ + if ((srb->cmnd[0] == TEST_UNIT_READY) || + (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL)) { + return 0; + } + + return 1; +} + +static void rts51x_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + struct rts51x_chip *chip = (struct rts51x_chip *)(us->extra); + static int card_first_show = 1; + static u8 media_not_present[] = { 0x70, 0, 0x02, 0, 0, 0, 0, + 10, 0, 0, 0, 0, 0x3A, 0, 0, 0, 0, 0 + }; + static u8 invalid_cmd_field[] = { 0x70, 0, 0x05, 0, 0, 0, 0, + 10, 0, 0, 0, 0, 0x24, 0, 0, 0, 0, 0 + }; + int ret; + + if (working_scsi(srb)) { + usb_stor_dbg(us, "working scsi, intf->pm_usage_cnt:%d, power.usage:%d\n", + atomic_read(&us->pusb_intf->pm_usage_cnt), + atomic_read(&us->pusb_intf->dev.power.usage_count)); + + if (atomic_read(&us->pusb_intf->pm_usage_cnt) <= 0) { + ret = usb_autopm_get_interface(us->pusb_intf); + usb_stor_dbg(us, "working scsi, ret=%d\n", ret); + } + if (rts51x_get_stat(chip) != RTS51X_STAT_RUN) + rts51x_set_stat(chip, RTS51X_STAT_RUN); + chip->proto_handler_backup(srb, us); + } else { + if (rts51x_get_stat(chip) == RTS51X_STAT_SS) { + usb_stor_dbg(us, "NOT working scsi\n"); + if ((srb->cmnd[0] == TEST_UNIT_READY) && + (chip->pwr_state == US_SUSPEND)) { + if (TST_LUN_READY(chip, srb->device->lun)) { + srb->result = SAM_STAT_GOOD; + } else { + srb->result = SAM_STAT_CHECK_CONDITION; + memcpy(srb->sense_buffer, + media_not_present, + US_SENSE_SIZE); + } + usb_stor_dbg(us, "TEST_UNIT_READY\n"); + goto out; + } + if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) { + int prevent = srb->cmnd[4] & 0x1; + if (prevent) { + srb->result = SAM_STAT_CHECK_CONDITION; + memcpy(srb->sense_buffer, + invalid_cmd_field, + US_SENSE_SIZE); + } else { + srb->result = SAM_STAT_GOOD; + } + usb_stor_dbg(us, "ALLOW_MEDIUM_REMOVAL\n"); + goto out; + } + } else { + usb_stor_dbg(us, "NOT working scsi, not SS\n"); + chip->proto_handler_backup(srb, us); + /* Check whether card is plugged in */ + if (srb->cmnd[0] == TEST_UNIT_READY) { + if (srb->result == SAM_STAT_GOOD) { + SET_LUN_READY(chip, srb->device->lun); + if (card_first_show) { + card_first_show = 0; + fw5895_set_mmc_wp(us); + } + } else { + CLR_LUN_READY(chip, srb->device->lun); + card_first_show = 1; + } + } + if (rts51x_get_stat(chip) != RTS51X_STAT_IDLE) + rts51x_set_stat(chip, RTS51X_STAT_IDLE); + } + } +out: + usb_stor_dbg(us, "state:%d\n", rts51x_get_stat(chip)); + if (rts51x_get_stat(chip) == RTS51X_STAT_RUN) + rts51x_modi_suspend_timer(chip); +} + +static int realtek_cr_autosuspend_setup(struct us_data *us) +{ + struct rts51x_chip *chip; + struct rts51x_status *status = NULL; + u8 buf[16]; + int retval; + + chip = (struct rts51x_chip *)us->extra; + chip->support_auto_delink = 0; + chip->pwr_state = US_RESUME; + chip->lun_ready = 0; + rts51x_set_stat(chip, RTS51X_STAT_INIT); + + retval = rts51x_read_status(us, 0, buf, 16, &(chip->status_len)); + if (retval != STATUS_SUCCESS) { + usb_stor_dbg(us, "Read status fail\n"); + return -EIO; + } + status = chip->status; + status->vid = ((u16) buf[0] << 8) | buf[1]; + status->pid = ((u16) buf[2] << 8) | buf[3]; + status->cur_lun = buf[4]; + status->card_type = buf[5]; + status->total_lun = buf[6]; + status->fw_ver = ((u16) buf[7] << 8) | buf[8]; + status->phy_exist = buf[9]; + status->multi_flag = buf[10]; + status->multi_card = buf[11]; + status->log_exist = buf[12]; + if (chip->status_len == 16) { + status->detailed_type.detailed_type1 = buf[13]; + status->function[0] = buf[14]; + status->function[1] = buf[15]; + } + + /* back up the proto_handler in us->extra */ + chip = (struct rts51x_chip *)(us->extra); + chip->proto_handler_backup = us->proto_handler; + /* Set the autosuspend_delay to 0 */ + pm_runtime_set_autosuspend_delay(&us->pusb_dev->dev, 0); + /* override us->proto_handler setted in get_protocol() */ + us->proto_handler = rts51x_invoke_transport; + + chip->timer_expires = 0; + setup_timer(&chip->rts51x_suspend_timer, rts51x_suspend_timer_fn, + (unsigned long)chip); + fw5895_init(us); + + /* enable autosuspend funciton of the usb device */ + usb_enable_autosuspend(us->pusb_dev); + + return 0; +} +#endif + +static void realtek_cr_destructor(void *extra) +{ + struct rts51x_chip *chip = extra; + + if (!chip) + return; + +#ifdef CONFIG_REALTEK_AUTOPM + if (ss_en) { + del_timer(&chip->rts51x_suspend_timer); + chip->timer_expires = 0; + } +#endif + kfree(chip->status); +} + +#ifdef CONFIG_PM +static int realtek_cr_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct us_data *us = usb_get_intfdata(iface); + + /* wait until no command is running */ + mutex_lock(&us->dev_mutex); + + config_autodelink_before_power_down(us); + + mutex_unlock(&us->dev_mutex); + + return 0; +} + +static int realtek_cr_resume(struct usb_interface *iface) +{ + struct us_data *us = usb_get_intfdata(iface); + + fw5895_init(us); + config_autodelink_after_power_on(us); + + return 0; +} +#else +#define realtek_cr_suspend NULL +#define realtek_cr_resume NULL +#endif + +static int init_realtek_cr(struct us_data *us) +{ + struct rts51x_chip *chip; + int size, i, retval; + + chip = kzalloc(sizeof(struct rts51x_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + us->extra = chip; + us->extra_destructor = realtek_cr_destructor; + us->max_lun = chip->max_lun = rts51x_get_max_lun(us); + chip->us = us; + + usb_stor_dbg(us, "chip->max_lun = %d\n", chip->max_lun); + + size = (chip->max_lun + 1) * sizeof(struct rts51x_status); + chip->status = kzalloc(size, GFP_KERNEL); + if (!chip->status) + goto INIT_FAIL; + + for (i = 0; i <= (int)(chip->max_lun); i++) { + retval = rts51x_check_status(us, (u8) i); + if (retval < 0) + goto INIT_FAIL; + } + + if (CHECK_FW_VER(chip, 0x5888) || CHECK_FW_VER(chip, 0x5889) || + CHECK_FW_VER(chip, 0x5901)) + SET_AUTO_DELINK(chip); + if (STATUS_LEN(chip) == 16) { + if (SUPPORT_AUTO_DELINK(chip)) + SET_AUTO_DELINK(chip); + } +#ifdef CONFIG_REALTEK_AUTOPM + if (ss_en) + realtek_cr_autosuspend_setup(us); +#endif + + usb_stor_dbg(us, "chip->flag = 0x%x\n", chip->flag); + + (void)config_autodelink_after_power_on(us); + + return 0; + +INIT_FAIL: + if (us->extra) { + kfree(chip->status); + kfree(us->extra); + us->extra = NULL; + } + + return -EIO; +} + +static int realtek_cr_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + dev_dbg(&intf->dev, "Probe Realtek Card Reader!\n"); + + result = usb_stor_probe1(&us, intf, id, + (id - realtek_cr_ids) + + realtek_cr_unusual_dev_list); + if (result) + return result; + + result = usb_stor_probe2(us); + + return result; +} + +static struct usb_driver realtek_cr_driver = { + .name = "ums-realtek", + .probe = realtek_cr_probe, + .disconnect = usb_stor_disconnect, + /* .suspend = usb_stor_suspend, */ + /* .resume = usb_stor_resume, */ + .reset_resume = usb_stor_reset_resume, + .suspend = realtek_cr_suspend, + .resume = realtek_cr_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = realtek_cr_ids, + .soft_unbind = 1, + .supports_autosuspend = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(realtek_cr_driver); diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c new file mode 100644 index 00000000..bb7bf198 --- /dev/null +++ b/drivers/usb/storage/scsiglue.c @@ -0,0 +1,600 @@ +/* Driver for USB Mass Storage compliant devices + * SCSI layer glue code + * + * Current development and maintenance by: + * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Developed with the assistance of: + * (c) 2000 David L. Brown, Jr. (usb-storage@davidb.org) + * (c) 2000 Stephen J. Gowdy (SGowdy@lbl.gov) + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "usb.h" +#include "scsiglue.h" +#include "debug.h" +#include "transport.h" +#include "protocol.h" + +/* Vendor IDs for companies that seem to include the READ CAPACITY bug + * in all their devices + */ +#define VENDOR_ID_NOKIA 0x0421 +#define VENDOR_ID_NIKON 0x04b0 +#define VENDOR_ID_PENTAX 0x0a17 +#define VENDOR_ID_MOTOROLA 0x22b8 + +/*********************************************************************** + * Host functions + ***********************************************************************/ + +static const char* host_info(struct Scsi_Host *host) +{ + struct us_data *us = host_to_us(host); + return us->scsi_name; +} + +static int slave_alloc (struct scsi_device *sdev) +{ + struct us_data *us = host_to_us(sdev->host); + + /* + * Set the INQUIRY transfer length to 36. We don't use any of + * the extra data and many devices choke if asked for more or + * less than 36 bytes. + */ + sdev->inquiry_len = 36; + + /* USB has unusual DMA-alignment requirements: Although the + * starting address of each scatter-gather element doesn't matter, + * the length of each element except the last must be divisible + * by the Bulk maxpacket value. There's currently no way to + * express this by block-layer constraints, so we'll cop out + * and simply require addresses to be aligned at 512-byte + * boundaries. This is okay since most block I/O involves + * hardware sectors that are multiples of 512 bytes in length, + * and since host controllers up through USB 2.0 have maxpacket + * values no larger than 512. + * + * But it doesn't suffice for Wireless USB, where Bulk maxpacket + * values can be as large as 2048. To make that work properly + * will require changes to the block layer. + */ + blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1)); + + /* Tell the SCSI layer if we know there is more than one LUN */ + if (us->protocol == USB_PR_BULK && us->max_lun > 0) + sdev->sdev_bflags |= BLIST_FORCELUN; + + return 0; +} + +static int slave_configure(struct scsi_device *sdev) +{ + struct us_data *us = host_to_us(sdev->host); + + /* Many devices have trouble transferring more than 32KB at a time, + * while others have trouble with more than 64K. At this time we + * are limiting both to 32K (64 sectores). + */ + if (us->fflags & (US_FL_MAX_SECTORS_64 | US_FL_MAX_SECTORS_MIN)) { + unsigned int max_sectors = 64; + + if (us->fflags & US_FL_MAX_SECTORS_MIN) + max_sectors = PAGE_CACHE_SIZE >> 9; + if (queue_max_hw_sectors(sdev->request_queue) > max_sectors) + blk_queue_max_hw_sectors(sdev->request_queue, + max_sectors); + } else if (sdev->type == TYPE_TAPE) { + /* Tapes need much higher max_sector limits, so just + * raise it to the maximum possible (4 GB / 512) and + * let the queue segment size sort out the real limit. + */ + blk_queue_max_hw_sectors(sdev->request_queue, 0x7FFFFF); + } + + /* Some USB host controllers can't do DMA; they have to use PIO. + * They indicate this by setting their dma_mask to NULL. For + * such controllers we need to make sure the block layer sets + * up bounce buffers in addressable memory. + */ + if (!us->pusb_dev->bus->controller->dma_mask) + blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_HIGH); + + /* We can't put these settings in slave_alloc() because that gets + * called before the device type is known. Consequently these + * settings can't be overridden via the scsi devinfo mechanism. */ + if (sdev->type == TYPE_DISK) { + + /* Some vendors seem to put the READ CAPACITY bug into + * all their devices -- primarily makers of cell phones + * and digital cameras. Since these devices always use + * flash media and can be expected to have an even number + * of sectors, we will always enable the CAPACITY_HEURISTICS + * flag unless told otherwise. */ + switch (le16_to_cpu(us->pusb_dev->descriptor.idVendor)) { + case VENDOR_ID_NOKIA: + case VENDOR_ID_NIKON: + case VENDOR_ID_PENTAX: + case VENDOR_ID_MOTOROLA: + if (!(us->fflags & (US_FL_FIX_CAPACITY | + US_FL_CAPACITY_OK))) + us->fflags |= US_FL_CAPACITY_HEURISTICS; + break; + } + + /* Disk-type devices use MODE SENSE(6) if the protocol + * (SubClass) is Transparent SCSI, otherwise they use + * MODE SENSE(10). */ + if (us->subclass != USB_SC_SCSI && us->subclass != USB_SC_CYP_ATACB) + sdev->use_10_for_ms = 1; + + /* Many disks only accept MODE SENSE transfer lengths of + * 192 bytes (that's what Windows uses). */ + sdev->use_192_bytes_for_3f = 1; + + /* Some devices don't like MODE SENSE with page=0x3f, + * which is the command used for checking if a device + * is write-protected. Now that we tell the sd driver + * to do a 192-byte transfer with this command the + * majority of devices work fine, but a few still can't + * handle it. The sd driver will simply assume those + * devices are write-enabled. */ + if (us->fflags & US_FL_NO_WP_DETECT) + sdev->skip_ms_page_3f = 1; + + /* A number of devices have problems with MODE SENSE for + * page x08, so we will skip it. */ + sdev->skip_ms_page_8 = 1; + + /* Some devices don't handle VPD pages correctly */ + sdev->skip_vpd_pages = 1; + + /* Do not attempt to use REPORT SUPPORTED OPERATION CODES */ + sdev->no_report_opcodes = 1; + + /* Do not attempt to use WRITE SAME */ + sdev->no_write_same = 1; + + /* Some disks return the total number of blocks in response + * to READ CAPACITY rather than the highest block number. + * If this device makes that mistake, tell the sd driver. */ + if (us->fflags & US_FL_FIX_CAPACITY) + sdev->fix_capacity = 1; + + /* A few disks have two indistinguishable version, one of + * which reports the correct capacity and the other does not. + * The sd driver has to guess which is the case. */ + if (us->fflags & US_FL_CAPACITY_HEURISTICS) + sdev->guess_capacity = 1; + + /* Some devices cannot handle READ_CAPACITY_16 */ + if (us->fflags & US_FL_NO_READ_CAPACITY_16) + sdev->no_read_capacity_16 = 1; + + /* + * Many devices do not respond properly to READ_CAPACITY_16. + * Tell the SCSI layer to try READ_CAPACITY_10 first. + * However some USB 3.0 drive enclosures return capacity + * modulo 2TB. Those must use READ_CAPACITY_16 + */ + if (!(us->fflags & US_FL_NEEDS_CAP16)) + sdev->try_rc_10_first = 1; + + /* assume SPC3 or latter devices support sense size > 18 */ + if (sdev->scsi_level > SCSI_SPC_2) + us->fflags |= US_FL_SANE_SENSE; + + /* USB-IDE bridges tend to report SK = 0x04 (Non-recoverable + * Hardware Error) when any low-level error occurs, + * recoverable or not. Setting this flag tells the SCSI + * midlayer to retry such commands, which frequently will + * succeed and fix the error. The worst this can lead to + * is an occasional series of retries that will all fail. */ + sdev->retry_hwerror = 1; + + /* USB disks should allow restart. Some drives spin down + * automatically, requiring a START-STOP UNIT command. */ + sdev->allow_restart = 1; + + /* Some USB cardreaders have trouble reading an sdcard's last + * sector in a larger then 1 sector read, since the performance + * impact is negible we set this flag for all USB disks */ + sdev->last_sector_bug = 1; + + /* Enable last-sector hacks for single-target devices using + * the Bulk-only transport, unless we already know the + * capacity will be decremented or is correct. */ + if (!(us->fflags & (US_FL_FIX_CAPACITY | US_FL_CAPACITY_OK | + US_FL_SCM_MULT_TARG)) && + us->protocol == USB_PR_BULK) + us->use_last_sector_hacks = 1; + + /* Check if write cache default on flag is set or not */ + if (us->fflags & US_FL_WRITE_CACHE) + sdev->wce_default_on = 1; + + } else { + + /* Non-disk-type devices don't need to blacklist any pages + * or to force 192-byte transfer lengths for MODE SENSE. + * But they do need to use MODE SENSE(10). */ + sdev->use_10_for_ms = 1; + + /* Some (fake) usb cdrom devices don't like READ_DISC_INFO */ + if (us->fflags & US_FL_NO_READ_DISC_INFO) + sdev->no_read_disc_info = 1; + } + + /* The CB and CBI transports have no way to pass LUN values + * other than the bits in the second byte of a CDB. But those + * bits don't get set to the LUN value if the device reports + * scsi_level == 0 (UNKNOWN). Hence such devices must necessarily + * be single-LUN. + */ + if ((us->protocol == USB_PR_CB || us->protocol == USB_PR_CBI) && + sdev->scsi_level == SCSI_UNKNOWN) + us->max_lun = 0; + + /* Some devices choke when they receive a PREVENT-ALLOW MEDIUM + * REMOVAL command, so suppress those commands. */ + if (us->fflags & US_FL_NOT_LOCKABLE) + sdev->lockable = 0; + + /* this is to satisfy the compiler, tho I don't think the + * return code is ever checked anywhere. */ + return 0; +} + +static int target_alloc(struct scsi_target *starget) +{ + struct us_data *us = host_to_us(dev_to_shost(starget->dev.parent)); + + /* + * Some USB drives don't support REPORT LUNS, even though they + * report a SCSI revision level above 2. Tell the SCSI layer + * not to issue that command; it will perform a normal sequential + * scan instead. + */ + starget->no_report_luns = 1; + + /* + * The UFI spec treats the Peripheral Qualifier bits in an + * INQUIRY result as reserved and requires devices to set them + * to 0. However the SCSI spec requires these bits to be set + * to 3 to indicate when a LUN is not present. + * + * Let the scanning code know if this target merely sets + * Peripheral Device Type to 0x1f to indicate no LUN. + */ + if (us->subclass == USB_SC_UFI) + starget->pdt_1f_for_no_lun = 1; + + return 0; +} + +/* queue a command */ +/* This is always called with scsi_lock(host) held */ +static int queuecommand_lck(struct scsi_cmnd *srb, + void (*done)(struct scsi_cmnd *)) +{ + struct us_data *us = host_to_us(srb->device->host); + + /* check for state-transition errors */ + if (us->srb != NULL) { + printk(KERN_ERR USB_STORAGE "Error in %s: us->srb = %p\n", + __func__, us->srb); + return SCSI_MLQUEUE_HOST_BUSY; + } + + /* fail the command if we are disconnecting */ + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + usb_stor_dbg(us, "Fail command during disconnect\n"); + srb->result = DID_NO_CONNECT << 16; + done(srb); + return 0; + } + + /* enqueue the command and wake up the control thread */ + srb->scsi_done = done; + us->srb = srb; + complete(&us->cmnd_ready); + + return 0; +} + +static DEF_SCSI_QCMD(queuecommand) + +/*********************************************************************** + * Error handling functions + ***********************************************************************/ + +/* Command timeout and abort */ +static int command_abort(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + + usb_stor_dbg(us, "%s called\n", __func__); + + /* us->srb together with the TIMED_OUT, RESETTING, and ABORTING + * bits are protected by the host lock. */ + scsi_lock(us_to_host(us)); + + /* Is this command still active? */ + if (us->srb != srb) { + scsi_unlock(us_to_host(us)); + usb_stor_dbg(us, "-- nothing to abort\n"); + return FAILED; + } + + /* Set the TIMED_OUT bit. Also set the ABORTING bit, but only if + * a device reset isn't already in progress (to avoid interfering + * with the reset). Note that we must retain the host lock while + * calling usb_stor_stop_transport(); otherwise it might interfere + * with an auto-reset that begins as soon as we release the lock. */ + set_bit(US_FLIDX_TIMED_OUT, &us->dflags); + if (!test_bit(US_FLIDX_RESETTING, &us->dflags)) { + set_bit(US_FLIDX_ABORTING, &us->dflags); + usb_stor_stop_transport(us); + } + scsi_unlock(us_to_host(us)); + + /* Wait for the aborted command to finish */ + wait_for_completion(&us->notify); + return SUCCESS; +} + +/* This invokes the transport reset mechanism to reset the state of the + * device */ +static int device_reset(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + int result; + + usb_stor_dbg(us, "%s called\n", __func__); + + /* lock the device pointers and do the reset */ + mutex_lock(&(us->dev_mutex)); + result = us->transport_reset(us); + mutex_unlock(&us->dev_mutex); + + return result < 0 ? FAILED : SUCCESS; +} + +/* Simulate a SCSI bus reset by resetting the device's USB port. */ +static int bus_reset(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + int result; + + usb_stor_dbg(us, "%s called\n", __func__); + + result = usb_stor_port_reset(us); + return result < 0 ? FAILED : SUCCESS; +} + +/* Report a driver-initiated device reset to the SCSI layer. + * Calling this for a SCSI-initiated reset is unnecessary but harmless. + * The caller must own the SCSI host lock. */ +void usb_stor_report_device_reset(struct us_data *us) +{ + int i; + struct Scsi_Host *host = us_to_host(us); + + scsi_report_device_reset(host, 0, 0); + if (us->fflags & US_FL_SCM_MULT_TARG) { + for (i = 1; i < host->max_id; ++i) + scsi_report_device_reset(host, 0, i); + } +} + +/* Report a driver-initiated bus reset to the SCSI layer. + * Calling this for a SCSI-initiated reset is unnecessary but harmless. + * The caller must not own the SCSI host lock. */ +void usb_stor_report_bus_reset(struct us_data *us) +{ + struct Scsi_Host *host = us_to_host(us); + + scsi_lock(host); + scsi_report_bus_reset(host, 0); + scsi_unlock(host); +} + +/*********************************************************************** + * /proc/scsi/ functions + ***********************************************************************/ + +static int write_info(struct Scsi_Host *host, char *buffer, int length) +{ + /* if someone is sending us data, just throw it away */ + return length; +} + +/* we use this macro to help us write into the buffer */ +#undef SPRINTF +#define SPRINTF(args...) seq_printf(m, ## args) + +static int show_info (struct seq_file *m, struct Scsi_Host *host) +{ + struct us_data *us = host_to_us(host); + const char *string; + + /* print the controller name */ + SPRINTF(" Host scsi%d: usb-storage\n", host->host_no); + + /* print product, vendor, and serial number strings */ + if (us->pusb_dev->manufacturer) + string = us->pusb_dev->manufacturer; + else if (us->unusual_dev->vendorName) + string = us->unusual_dev->vendorName; + else + string = "Unknown"; + SPRINTF(" Vendor: %s\n", string); + if (us->pusb_dev->product) + string = us->pusb_dev->product; + else if (us->unusual_dev->productName) + string = us->unusual_dev->productName; + else + string = "Unknown"; + SPRINTF(" Product: %s\n", string); + if (us->pusb_dev->serial) + string = us->pusb_dev->serial; + else + string = "None"; + SPRINTF("Serial Number: %s\n", string); + + /* show the protocol and transport */ + SPRINTF(" Protocol: %s\n", us->protocol_name); + SPRINTF(" Transport: %s\n", us->transport_name); + + /* show the device flags */ + SPRINTF(" Quirks:"); + +#define US_FLAG(name, value) \ + if (us->fflags & value) seq_printf(m, " " #name); +US_DO_ALL_FLAGS +#undef US_FLAG + seq_putc(m, '\n'); + return 0; +} + +/*********************************************************************** + * Sysfs interface + ***********************************************************************/ + +/* Output routine for the sysfs max_sectors file */ +static ssize_t show_max_sectors(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct scsi_device *sdev = to_scsi_device(dev); + + return sprintf(buf, "%u\n", queue_max_hw_sectors(sdev->request_queue)); +} + +/* Input routine for the sysfs max_sectors file */ +static ssize_t store_max_sectors(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct scsi_device *sdev = to_scsi_device(dev); + unsigned short ms; + + if (sscanf(buf, "%hu", &ms) > 0) { + blk_queue_max_hw_sectors(sdev->request_queue, ms); + return count; + } + return -EINVAL; +} + +static DEVICE_ATTR(max_sectors, S_IRUGO | S_IWUSR, show_max_sectors, + store_max_sectors); + +static struct device_attribute *sysfs_device_attr_list[] = { + &dev_attr_max_sectors, + NULL, + }; + +/* + * this defines our host template, with which we'll allocate hosts + */ + +struct scsi_host_template usb_stor_host_template = { + /* basic userland interface stuff */ + .name = "usb-storage", + .proc_name = "usb-storage", + .show_info = show_info, + .write_info = write_info, + .info = host_info, + + /* command interface -- queued only */ + .queuecommand = queuecommand, + + /* error and abort handlers */ + .eh_abort_handler = command_abort, + .eh_device_reset_handler = device_reset, + .eh_bus_reset_handler = bus_reset, + + /* queue commands only, only one command per LUN */ + .can_queue = 1, + .cmd_per_lun = 1, + + /* unknown initiator id */ + .this_id = -1, + + .slave_alloc = slave_alloc, + .slave_configure = slave_configure, + .target_alloc = target_alloc, + + /* lots of sg segments can be handled */ + .sg_tablesize = SCSI_MAX_SG_CHAIN_SEGMENTS, + + /* limit the total size of a transfer to 120 KB */ + .max_sectors = 240, + + /* merge commands... this seems to help performance, but + * periodically someone should test to see which setting is more + * optimal. + */ + .use_clustering = 1, + + /* emulated HBA */ + .emulated = 1, + + /* we do our own delay after a device or bus reset */ + .skip_settle_delay = 1, + + /* sysfs device attributes */ + .sdev_attrs = sysfs_device_attr_list, + + /* module management */ + .module = THIS_MODULE +}; + +/* To Report "Illegal Request: Invalid Field in CDB */ +unsigned char usb_stor_sense_invalidCDB[18] = { + [0] = 0x70, /* current error */ + [2] = ILLEGAL_REQUEST, /* Illegal Request = 0x05 */ + [7] = 0x0a, /* additional length */ + [12] = 0x24 /* Invalid Field in CDB */ +}; +EXPORT_SYMBOL_GPL(usb_stor_sense_invalidCDB); diff --git a/drivers/usb/storage/scsiglue.h b/drivers/usb/storage/scsiglue.h new file mode 100644 index 00000000..ffa1cca9 --- /dev/null +++ b/drivers/usb/storage/scsiglue.h @@ -0,0 +1,48 @@ +/* Driver for USB Mass Storage compliant devices + * SCSI Connecting Glue Header File + * + * Current development and maintenance by: + * (c) 1999, 2000 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _SCSIGLUE_H_ +#define _SCSIGLUE_H_ + +extern void usb_stor_report_device_reset(struct us_data *us); +extern void usb_stor_report_bus_reset(struct us_data *us); + +extern unsigned char usb_stor_sense_invalidCDB[18]; +extern struct scsi_host_template usb_stor_host_template; + +#endif diff --git a/drivers/usb/storage/sddr09.c b/drivers/usb/storage/sddr09.c new file mode 100644 index 00000000..732027f3 --- /dev/null +++ b/drivers/usb/storage/sddr09.c @@ -0,0 +1,1787 @@ +/* Driver for SanDisk SDDR-09 SmartMedia reader + * + * (c) 2000, 2001 Robert Baruch (autophile@starband.net) + * (c) 2002 Andries Brouwer (aeb@cwi.nl) + * Developed with the assistance of: + * (c) 2002 Alan Stern + * + * The SanDisk SDDR-09 SmartMedia reader uses the Shuttle EUSB-01 chip. + * This chip is a programmable USB controller. In the SDDR-09, it has + * been programmed to obey a certain limited set of SCSI commands. + * This driver translates the "real" SCSI commands to the SDDR-09 SCSI + * commands. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Known vendor commands: 12 bytes, first byte is opcode + * + * E7: read scatter gather + * E8: read + * E9: write + * EA: erase + * EB: reset + * EC: read status + * ED: read ID + * EE: write CIS (?) + * EF: compute checksum (?) + */ + +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + +MODULE_DESCRIPTION("Driver for SanDisk SDDR-09 SmartMedia reader"); +MODULE_AUTHOR("Andries Brouwer , Robert Baruch "); +MODULE_LICENSE("GPL"); + +static int usb_stor_sddr09_dpcm_init(struct us_data *us); +static int sddr09_transport(struct scsi_cmnd *srb, struct us_data *us); +static int usb_stor_sddr09_init(struct us_data *us); + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id sddr09_usb_ids[] = { +# include "unusual_sddr09.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, sddr09_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev sddr09_unusual_dev_list[] = { +# include "unusual_sddr09.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + +#define short_pack(lsb,msb) ( ((u16)(lsb)) | ( ((u16)(msb))<<8 ) ) +#define LSB_of(s) ((s)&0xFF) +#define MSB_of(s) ((s)>>8) + +/* + * First some stuff that does not belong here: + * data on SmartMedia and other cards, completely + * unrelated to this driver. + * Similar stuff occurs in . + */ + +struct nand_flash_dev { + int model_id; + int chipshift; /* 1<recv_ctrl_pipe; + else + pipe = us->send_ctrl_pipe; + + rc = usb_stor_ctrl_transfer(us, pipe, request, requesttype, + 0, 0, xfer_data, xfer_len); + switch (rc) { + case USB_STOR_XFER_GOOD: return 0; + case USB_STOR_XFER_STALLED: return -EPIPE; + default: return -EIO; + } +} + +static int +sddr09_send_scsi_command(struct us_data *us, + unsigned char *command, + unsigned int command_len) { + return sddr09_send_command(us, 0, USB_DIR_OUT, command, command_len); +} + +#if 0 +/* + * Test Unit Ready Command: 12 bytes. + * byte 0: opcode: 00 + */ +static int +sddr09_test_unit_ready(struct us_data *us) { + unsigned char *command = us->iobuf; + int result; + + memset(command, 0, 6); + command[1] = LUNBITS; + + result = sddr09_send_scsi_command(us, command, 6); + + usb_stor_dbg(us, "sddr09_test_unit_ready returns %d\n", result); + + return result; +} +#endif + +/* + * Request Sense Command: 12 bytes. + * byte 0: opcode: 03 + * byte 4: data length + */ +static int +sddr09_request_sense(struct us_data *us, unsigned char *sensebuf, int buflen) { + unsigned char *command = us->iobuf; + int result; + + memset(command, 0, 12); + command[0] = 0x03; + command[1] = LUNBITS; + command[4] = buflen; + + result = sddr09_send_scsi_command(us, command, 12); + if (result) + return result; + + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + sensebuf, buflen, NULL); + return (result == USB_STOR_XFER_GOOD ? 0 : -EIO); +} + +/* + * Read Command: 12 bytes. + * byte 0: opcode: E8 + * byte 1: last two bits: 00: read data, 01: read blockwise control, + * 10: read both, 11: read pagewise control. + * It turns out we need values 20, 21, 22, 23 here (LUN 1). + * bytes 2-5: address (interpretation depends on byte 1, see below) + * bytes 10-11: count (idem) + * + * A page has 512 data bytes and 64 control bytes (16 control and 48 junk). + * A read data command gets data in 512-byte pages. + * A read control command gets control in 64-byte chunks. + * A read both command gets data+control in 576-byte chunks. + * + * Blocks are groups of 32 pages, and read blockwise control jumps to the + * next block, while read pagewise control jumps to the next page after + * reading a group of 64 control bytes. + * [Here 512 = 1<iobuf; + int result; + + command[0] = 0xE8; + command[1] = LUNBITS | x; + command[2] = MSB_of(fromaddress>>16); + command[3] = LSB_of(fromaddress>>16); + command[4] = MSB_of(fromaddress & 0xFFFF); + command[5] = LSB_of(fromaddress & 0xFFFF); + command[6] = 0; + command[7] = 0; + command[8] = 0; + command[9] = 0; + command[10] = MSB_of(nr_of_pages); + command[11] = LSB_of(nr_of_pages); + + result = sddr09_send_scsi_command(us, command, 12); + + if (result) { + usb_stor_dbg(us, "Result for send_control in sddr09_read2%d %d\n", + x, result); + return result; + } + + result = usb_stor_bulk_transfer_sg(us, us->recv_bulk_pipe, + buf, bulklen, use_sg, NULL); + + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Result for bulk_transfer in sddr09_read2%d %d\n", + x, result); + return -EIO; + } + return 0; +} + +/* + * Read Data + * + * fromaddress counts data shorts: + * increasing it by 256 shifts the bytestream by 512 bytes; + * the last 8 bits are ignored. + * + * nr_of_pages counts pages of size (1 << pageshift). + */ +static int +sddr09_read20(struct us_data *us, unsigned long fromaddress, + int nr_of_pages, int pageshift, unsigned char *buf, int use_sg) { + int bulklen = nr_of_pages << pageshift; + + /* The last 8 bits of fromaddress are ignored. */ + return sddr09_readX(us, 0, fromaddress, nr_of_pages, bulklen, + buf, use_sg); +} + +/* + * Read Blockwise Control + * + * fromaddress gives the starting position (as in read data; + * the last 8 bits are ignored); increasing it by 32*256 shifts + * the output stream by 64 bytes. + * + * count counts control groups of size (1 << controlshift). + * For me, controlshift = 6. Is this constant? + * + * After getting one control group, jump to the next block + * (fromaddress += 8192). + */ +static int +sddr09_read21(struct us_data *us, unsigned long fromaddress, + int count, int controlshift, unsigned char *buf, int use_sg) { + + int bulklen = (count << controlshift); + return sddr09_readX(us, 1, fromaddress, count, bulklen, + buf, use_sg); +} + +/* + * Read both Data and Control + * + * fromaddress counts data shorts, ignoring control: + * increasing it by 256 shifts the bytestream by 576 = 512+64 bytes; + * the last 8 bits are ignored. + * + * nr_of_pages counts pages of size (1 << pageshift) + (1 << controlshift). + */ +static int +sddr09_read22(struct us_data *us, unsigned long fromaddress, + int nr_of_pages, int pageshift, unsigned char *buf, int use_sg) { + + int bulklen = (nr_of_pages << pageshift) + (nr_of_pages << CONTROL_SHIFT); + usb_stor_dbg(us, "reading %d pages, %d bytes\n", nr_of_pages, bulklen); + return sddr09_readX(us, 2, fromaddress, nr_of_pages, bulklen, + buf, use_sg); +} + +#if 0 +/* + * Read Pagewise Control + * + * fromaddress gives the starting position (as in read data; + * the last 8 bits are ignored); increasing it by 256 shifts + * the output stream by 64 bytes. + * + * count counts control groups of size (1 << controlshift). + * For me, controlshift = 6. Is this constant? + * + * After getting one control group, jump to the next page + * (fromaddress += 256). + */ +static int +sddr09_read23(struct us_data *us, unsigned long fromaddress, + int count, int controlshift, unsigned char *buf, int use_sg) { + + int bulklen = (count << controlshift); + return sddr09_readX(us, 3, fromaddress, count, bulklen, + buf, use_sg); +} +#endif + +/* + * Erase Command: 12 bytes. + * byte 0: opcode: EA + * bytes 6-9: erase address (big-endian, counting shorts, sector aligned). + * + * Always precisely one block is erased; bytes 2-5 and 10-11 are ignored. + * The byte address being erased is 2*Eaddress. + * The CIS cannot be erased. + */ +static int +sddr09_erase(struct us_data *us, unsigned long Eaddress) { + unsigned char *command = us->iobuf; + int result; + + usb_stor_dbg(us, "erase address %lu\n", Eaddress); + + memset(command, 0, 12); + command[0] = 0xEA; + command[1] = LUNBITS; + command[6] = MSB_of(Eaddress>>16); + command[7] = LSB_of(Eaddress>>16); + command[8] = MSB_of(Eaddress & 0xFFFF); + command[9] = LSB_of(Eaddress & 0xFFFF); + + result = sddr09_send_scsi_command(us, command, 12); + + if (result) + usb_stor_dbg(us, "Result for send_control in sddr09_erase %d\n", + result); + + return result; +} + +/* + * Write CIS Command: 12 bytes. + * byte 0: opcode: EE + * bytes 2-5: write address in shorts + * bytes 10-11: sector count + * + * This writes at the indicated address. Don't know how it differs + * from E9. Maybe it does not erase? However, it will also write to + * the CIS. + * + * When two such commands on the same page follow each other directly, + * the second one is not done. + */ + +/* + * Write Command: 12 bytes. + * byte 0: opcode: E9 + * bytes 2-5: write address (big-endian, counting shorts, sector aligned). + * bytes 6-9: erase address (big-endian, counting shorts, sector aligned). + * bytes 10-11: sector count (big-endian, in 512-byte sectors). + * + * If write address equals erase address, the erase is done first, + * otherwise the write is done first. When erase address equals zero + * no erase is done? + */ +static int +sddr09_writeX(struct us_data *us, + unsigned long Waddress, unsigned long Eaddress, + int nr_of_pages, int bulklen, unsigned char *buf, int use_sg) { + + unsigned char *command = us->iobuf; + int result; + + command[0] = 0xE9; + command[1] = LUNBITS; + + command[2] = MSB_of(Waddress>>16); + command[3] = LSB_of(Waddress>>16); + command[4] = MSB_of(Waddress & 0xFFFF); + command[5] = LSB_of(Waddress & 0xFFFF); + + command[6] = MSB_of(Eaddress>>16); + command[7] = LSB_of(Eaddress>>16); + command[8] = MSB_of(Eaddress & 0xFFFF); + command[9] = LSB_of(Eaddress & 0xFFFF); + + command[10] = MSB_of(nr_of_pages); + command[11] = LSB_of(nr_of_pages); + + result = sddr09_send_scsi_command(us, command, 12); + + if (result) { + usb_stor_dbg(us, "Result for send_control in sddr09_writeX %d\n", + result); + return result; + } + + result = usb_stor_bulk_transfer_sg(us, us->send_bulk_pipe, + buf, bulklen, use_sg, NULL); + + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Result for bulk_transfer in sddr09_writeX %d\n", + result); + return -EIO; + } + return 0; +} + +/* erase address, write same address */ +static int +sddr09_write_inplace(struct us_data *us, unsigned long address, + int nr_of_pages, int pageshift, unsigned char *buf, + int use_sg) { + int bulklen = (nr_of_pages << pageshift) + (nr_of_pages << CONTROL_SHIFT); + return sddr09_writeX(us, address, address, nr_of_pages, bulklen, + buf, use_sg); +} + +#if 0 +/* + * Read Scatter Gather Command: 3+4n bytes. + * byte 0: opcode E7 + * byte 2: n + * bytes 4i-1,4i,4i+1: page address + * byte 4i+2: page count + * (i=1..n) + * + * This reads several pages from the card to a single memory buffer. + * The last two bits of byte 1 have the same meaning as for E8. + */ +static int +sddr09_read_sg_test_only(struct us_data *us) { + unsigned char *command = us->iobuf; + int result, bulklen, nsg, ct; + unsigned char *buf; + unsigned long address; + + nsg = bulklen = 0; + command[0] = 0xE7; + command[1] = LUNBITS; + command[2] = 0; + address = 040000; ct = 1; + nsg++; + bulklen += (ct << 9); + command[4*nsg+2] = ct; + command[4*nsg+1] = ((address >> 9) & 0xFF); + command[4*nsg+0] = ((address >> 17) & 0xFF); + command[4*nsg-1] = ((address >> 25) & 0xFF); + + address = 0340000; ct = 1; + nsg++; + bulklen += (ct << 9); + command[4*nsg+2] = ct; + command[4*nsg+1] = ((address >> 9) & 0xFF); + command[4*nsg+0] = ((address >> 17) & 0xFF); + command[4*nsg-1] = ((address >> 25) & 0xFF); + + address = 01000000; ct = 2; + nsg++; + bulklen += (ct << 9); + command[4*nsg+2] = ct; + command[4*nsg+1] = ((address >> 9) & 0xFF); + command[4*nsg+0] = ((address >> 17) & 0xFF); + command[4*nsg-1] = ((address >> 25) & 0xFF); + + command[2] = nsg; + + result = sddr09_send_scsi_command(us, command, 4*nsg+3); + + if (result) { + usb_stor_dbg(us, "Result for send_control in sddr09_read_sg %d\n", + result); + return result; + } + + buf = kmalloc(bulklen, GFP_NOIO); + if (!buf) + return -ENOMEM; + + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + buf, bulklen, NULL); + kfree(buf); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Result for bulk_transfer in sddr09_read_sg %d\n", + result); + return -EIO; + } + + return 0; +} +#endif + +/* + * Read Status Command: 12 bytes. + * byte 0: opcode: EC + * + * Returns 64 bytes, all zero except for the first. + * bit 0: 1: Error + * bit 5: 1: Suspended + * bit 6: 1: Ready + * bit 7: 1: Not write-protected + */ + +static int +sddr09_read_status(struct us_data *us, unsigned char *status) { + + unsigned char *command = us->iobuf; + unsigned char *data = us->iobuf; + int result; + + usb_stor_dbg(us, "Reading status...\n"); + + memset(command, 0, 12); + command[0] = 0xEC; + command[1] = LUNBITS; + + result = sddr09_send_scsi_command(us, command, 12); + if (result) + return result; + + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + data, 64, NULL); + *status = data[0]; + return (result == USB_STOR_XFER_GOOD ? 0 : -EIO); +} + +static int +sddr09_read_data(struct us_data *us, + unsigned long address, + unsigned int sectors) { + + struct sddr09_card_info *info = (struct sddr09_card_info *) us->extra; + unsigned char *buffer; + unsigned int lba, maxlba, pba; + unsigned int page, pages; + unsigned int len, offset; + struct scatterlist *sg; + int result; + + // Figure out the initial LBA and page + lba = address >> info->blockshift; + page = (address & info->blockmask); + maxlba = info->capacity >> (info->pageshift + info->blockshift); + if (lba >= maxlba) + return -EIO; + + // Since we only read in one block at a time, we have to create + // a bounce buffer and move the data a piece at a time between the + // bounce buffer and the actual transfer buffer. + + len = min(sectors, (unsigned int) info->blocksize) * info->pagesize; + buffer = kmalloc(len, GFP_NOIO); + if (buffer == NULL) { + printk(KERN_WARNING "sddr09_read_data: Out of memory\n"); + return -ENOMEM; + } + + // This could be made much more efficient by checking for + // contiguous LBA's. Another exercise left to the student. + + result = 0; + offset = 0; + sg = NULL; + + while (sectors > 0) { + + /* Find number of pages we can read in this block */ + pages = min(sectors, info->blocksize - page); + len = pages << info->pageshift; + + /* Not overflowing capacity? */ + if (lba >= maxlba) { + usb_stor_dbg(us, "Error: Requested lba %u exceeds maximum %u\n", + lba, maxlba); + result = -EIO; + break; + } + + /* Find where this lba lives on disk */ + pba = info->lba_to_pba[lba]; + + if (pba == UNDEF) { /* this lba was never written */ + + usb_stor_dbg(us, "Read %d zero pages (LBA %d) page %d\n", + pages, lba, page); + + /* This is not really an error. It just means + that the block has never been written. + Instead of returning an error + it is better to return all zero data. */ + + memset(buffer, 0, len); + + } else { + usb_stor_dbg(us, "Read %d pages, from PBA %d (LBA %d) page %d\n", + pages, pba, lba, page); + + address = ((pba << info->blockshift) + page) << + info->pageshift; + + result = sddr09_read20(us, address>>1, + pages, info->pageshift, buffer, 0); + if (result) + break; + } + + // Store the data in the transfer buffer + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &offset, TO_XFER_BUF); + + page = 0; + lba++; + sectors -= pages; + } + + kfree(buffer); + return result; +} + +static unsigned int +sddr09_find_unused_pba(struct sddr09_card_info *info, unsigned int lba) { + static unsigned int lastpba = 1; + int zonestart, end, i; + + zonestart = (lba/1000) << 10; + end = info->capacity >> (info->blockshift + info->pageshift); + end -= zonestart; + if (end > 1024) + end = 1024; + + for (i = lastpba+1; i < end; i++) { + if (info->pba_to_lba[zonestart+i] == UNDEF) { + lastpba = i; + return zonestart+i; + } + } + for (i = 0; i <= lastpba; i++) { + if (info->pba_to_lba[zonestart+i] == UNDEF) { + lastpba = i; + return zonestart+i; + } + } + return 0; +} + +static int +sddr09_write_lba(struct us_data *us, unsigned int lba, + unsigned int page, unsigned int pages, + unsigned char *ptr, unsigned char *blockbuffer) { + + struct sddr09_card_info *info = (struct sddr09_card_info *) us->extra; + unsigned long address; + unsigned int pba, lbap; + unsigned int pagelen; + unsigned char *bptr, *cptr, *xptr; + unsigned char ecc[3]; + int i, result, isnew; + + lbap = ((lba % 1000) << 1) | 0x1000; + if (parity[MSB_of(lbap) ^ LSB_of(lbap)]) + lbap ^= 1; + pba = info->lba_to_pba[lba]; + isnew = 0; + + if (pba == UNDEF) { + pba = sddr09_find_unused_pba(info, lba); + if (!pba) { + printk(KERN_WARNING + "sddr09_write_lba: Out of unused blocks\n"); + return -ENOSPC; + } + info->pba_to_lba[pba] = lba; + info->lba_to_pba[lba] = pba; + isnew = 1; + } + + if (pba == 1) { + /* Maybe it is impossible to write to PBA 1. + Fake success, but don't do anything. */ + printk(KERN_WARNING "sddr09: avoid writing to pba 1\n"); + return 0; + } + + pagelen = (1 << info->pageshift) + (1 << CONTROL_SHIFT); + + /* read old contents */ + address = (pba << (info->pageshift + info->blockshift)); + result = sddr09_read22(us, address>>1, info->blocksize, + info->pageshift, blockbuffer, 0); + if (result) + return result; + + /* check old contents and fill lba */ + for (i = 0; i < info->blocksize; i++) { + bptr = blockbuffer + i*pagelen; + cptr = bptr + info->pagesize; + nand_compute_ecc(bptr, ecc); + if (!nand_compare_ecc(cptr+13, ecc)) { + usb_stor_dbg(us, "Warning: bad ecc in page %d- of pba %d\n", + i, pba); + nand_store_ecc(cptr+13, ecc); + } + nand_compute_ecc(bptr+(info->pagesize / 2), ecc); + if (!nand_compare_ecc(cptr+8, ecc)) { + usb_stor_dbg(us, "Warning: bad ecc in page %d+ of pba %d\n", + i, pba); + nand_store_ecc(cptr+8, ecc); + } + cptr[6] = cptr[11] = MSB_of(lbap); + cptr[7] = cptr[12] = LSB_of(lbap); + } + + /* copy in new stuff and compute ECC */ + xptr = ptr; + for (i = page; i < page+pages; i++) { + bptr = blockbuffer + i*pagelen; + cptr = bptr + info->pagesize; + memcpy(bptr, xptr, info->pagesize); + xptr += info->pagesize; + nand_compute_ecc(bptr, ecc); + nand_store_ecc(cptr+13, ecc); + nand_compute_ecc(bptr+(info->pagesize / 2), ecc); + nand_store_ecc(cptr+8, ecc); + } + + usb_stor_dbg(us, "Rewrite PBA %d (LBA %d)\n", pba, lba); + + result = sddr09_write_inplace(us, address>>1, info->blocksize, + info->pageshift, blockbuffer, 0); + + usb_stor_dbg(us, "sddr09_write_inplace returns %d\n", result); + +#if 0 + { + unsigned char status = 0; + int result2 = sddr09_read_status(us, &status); + if (result2) + usb_stor_dbg(us, "cannot read status\n"); + else if (status != 0xc0) + usb_stor_dbg(us, "status after write: 0x%x\n", status); + } +#endif + +#if 0 + { + int result2 = sddr09_test_unit_ready(us); + } +#endif + + return result; +} + +static int +sddr09_write_data(struct us_data *us, + unsigned long address, + unsigned int sectors) { + + struct sddr09_card_info *info = (struct sddr09_card_info *) us->extra; + unsigned int lba, maxlba, page, pages; + unsigned int pagelen, blocklen; + unsigned char *blockbuffer; + unsigned char *buffer; + unsigned int len, offset; + struct scatterlist *sg; + int result; + + // Figure out the initial LBA and page + lba = address >> info->blockshift; + page = (address & info->blockmask); + maxlba = info->capacity >> (info->pageshift + info->blockshift); + if (lba >= maxlba) + return -EIO; + + // blockbuffer is used for reading in the old data, overwriting + // with the new data, and performing ECC calculations + + /* TODO: instead of doing kmalloc/kfree for each write, + add a bufferpointer to the info structure */ + + pagelen = (1 << info->pageshift) + (1 << CONTROL_SHIFT); + blocklen = (pagelen << info->blockshift); + blockbuffer = kmalloc(blocklen, GFP_NOIO); + if (!blockbuffer) { + printk(KERN_WARNING "sddr09_write_data: Out of memory\n"); + return -ENOMEM; + } + + // Since we don't write the user data directly to the device, + // we have to create a bounce buffer and move the data a piece + // at a time between the bounce buffer and the actual transfer buffer. + + len = min(sectors, (unsigned int) info->blocksize) * info->pagesize; + buffer = kmalloc(len, GFP_NOIO); + if (buffer == NULL) { + printk(KERN_WARNING "sddr09_write_data: Out of memory\n"); + kfree(blockbuffer); + return -ENOMEM; + } + + result = 0; + offset = 0; + sg = NULL; + + while (sectors > 0) { + + // Write as many sectors as possible in this block + + pages = min(sectors, info->blocksize - page); + len = (pages << info->pageshift); + + /* Not overflowing capacity? */ + if (lba >= maxlba) { + usb_stor_dbg(us, "Error: Requested lba %u exceeds maximum %u\n", + lba, maxlba); + result = -EIO; + break; + } + + // Get the data from the transfer buffer + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &offset, FROM_XFER_BUF); + + result = sddr09_write_lba(us, lba, page, pages, + buffer, blockbuffer); + if (result) + break; + + page = 0; + lba++; + sectors -= pages; + } + + kfree(buffer); + kfree(blockbuffer); + + return result; +} + +static int +sddr09_read_control(struct us_data *us, + unsigned long address, + unsigned int blocks, + unsigned char *content, + int use_sg) { + + usb_stor_dbg(us, "Read control address %lu, blocks %d\n", + address, blocks); + + return sddr09_read21(us, address, blocks, + CONTROL_SHIFT, content, use_sg); +} + +/* + * Read Device ID Command: 12 bytes. + * byte 0: opcode: ED + * + * Returns 2 bytes: Manufacturer ID and Device ID. + * On more recent cards 3 bytes: the third byte is an option code A5 + * signifying that the secret command to read an 128-bit ID is available. + * On still more recent cards 4 bytes: the fourth byte C0 means that + * a second read ID cmd is available. + */ +static int +sddr09_read_deviceID(struct us_data *us, unsigned char *deviceID) { + unsigned char *command = us->iobuf; + unsigned char *content = us->iobuf; + int result, i; + + memset(command, 0, 12); + command[0] = 0xED; + command[1] = LUNBITS; + + result = sddr09_send_scsi_command(us, command, 12); + if (result) + return result; + + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + content, 64, NULL); + + for (i = 0; i < 4; i++) + deviceID[i] = content[i]; + + return (result == USB_STOR_XFER_GOOD ? 0 : -EIO); +} + +static int +sddr09_get_wp(struct us_data *us, struct sddr09_card_info *info) { + int result; + unsigned char status; + + result = sddr09_read_status(us, &status); + if (result) { + usb_stor_dbg(us, "read_status fails\n"); + return result; + } + usb_stor_dbg(us, "status 0x%02X", status); + if ((status & 0x80) == 0) { + info->flags |= SDDR09_WP; /* write protected */ + US_DEBUGPX(" WP"); + } + if (status & 0x40) + US_DEBUGPX(" Ready"); + if (status & LUNBITS) + US_DEBUGPX(" Suspended"); + if (status & 0x1) + US_DEBUGPX(" Error"); + US_DEBUGPX("\n"); + return 0; +} + +#if 0 +/* + * Reset Command: 12 bytes. + * byte 0: opcode: EB + */ +static int +sddr09_reset(struct us_data *us) { + + unsigned char *command = us->iobuf; + + memset(command, 0, 12); + command[0] = 0xEB; + command[1] = LUNBITS; + + return sddr09_send_scsi_command(us, command, 12); +} +#endif + +static struct nand_flash_dev * +sddr09_get_cardinfo(struct us_data *us, unsigned char flags) { + struct nand_flash_dev *cardinfo; + unsigned char deviceID[4]; + char blurbtxt[256]; + int result; + + usb_stor_dbg(us, "Reading capacity...\n"); + + result = sddr09_read_deviceID(us, deviceID); + + if (result) { + usb_stor_dbg(us, "Result of read_deviceID is %d\n", result); + printk(KERN_WARNING "sddr09: could not read card info\n"); + return NULL; + } + + sprintf(blurbtxt, "sddr09: Found Flash card, ID = %02X %02X %02X %02X", + deviceID[0], deviceID[1], deviceID[2], deviceID[3]); + + /* Byte 0 is the manufacturer */ + sprintf(blurbtxt + strlen(blurbtxt), + ": Manuf. %s", + nand_flash_manufacturer(deviceID[0])); + + /* Byte 1 is the device type */ + cardinfo = nand_find_id(deviceID[1]); + if (cardinfo) { + /* MB or MiB? It is neither. A 16 MB card has + 17301504 raw bytes, of which 16384000 are + usable for user data. */ + sprintf(blurbtxt + strlen(blurbtxt), + ", %d MB", 1<<(cardinfo->chipshift - 20)); + } else { + sprintf(blurbtxt + strlen(blurbtxt), + ", type unrecognized"); + } + + /* Byte 2 is code to signal availability of 128-bit ID */ + if (deviceID[2] == 0xa5) { + sprintf(blurbtxt + strlen(blurbtxt), + ", 128-bit ID"); + } + + /* Byte 3 announces the availability of another read ID command */ + if (deviceID[3] == 0xc0) { + sprintf(blurbtxt + strlen(blurbtxt), + ", extra cmd"); + } + + if (flags & SDDR09_WP) + sprintf(blurbtxt + strlen(blurbtxt), + ", WP"); + + printk(KERN_WARNING "%s\n", blurbtxt); + + return cardinfo; +} + +static int +sddr09_read_map(struct us_data *us) { + + struct sddr09_card_info *info = (struct sddr09_card_info *) us->extra; + int numblocks, alloc_len, alloc_blocks; + int i, j, result; + unsigned char *buffer, *buffer_end, *ptr; + unsigned int lba, lbact; + + if (!info->capacity) + return -1; + + // size of a block is 1 << (blockshift + pageshift) bytes + // divide into the total capacity to get the number of blocks + + numblocks = info->capacity >> (info->blockshift + info->pageshift); + + // read 64 bytes for every block (actually 1 << CONTROL_SHIFT) + // but only use a 64 KB buffer + // buffer size used must be a multiple of (1 << CONTROL_SHIFT) +#define SDDR09_READ_MAP_BUFSZ 65536 + + alloc_blocks = min(numblocks, SDDR09_READ_MAP_BUFSZ >> CONTROL_SHIFT); + alloc_len = (alloc_blocks << CONTROL_SHIFT); + buffer = kmalloc(alloc_len, GFP_NOIO); + if (buffer == NULL) { + printk(KERN_WARNING "sddr09_read_map: out of memory\n"); + result = -1; + goto done; + } + buffer_end = buffer + alloc_len; + +#undef SDDR09_READ_MAP_BUFSZ + + kfree(info->lba_to_pba); + kfree(info->pba_to_lba); + info->lba_to_pba = kmalloc(numblocks*sizeof(int), GFP_NOIO); + info->pba_to_lba = kmalloc(numblocks*sizeof(int), GFP_NOIO); + + if (info->lba_to_pba == NULL || info->pba_to_lba == NULL) { + printk(KERN_WARNING "sddr09_read_map: out of memory\n"); + result = -1; + goto done; + } + + for (i = 0; i < numblocks; i++) + info->lba_to_pba[i] = info->pba_to_lba[i] = UNDEF; + + /* + * Define lba-pba translation table + */ + + ptr = buffer_end; + for (i = 0; i < numblocks; i++) { + ptr += (1 << CONTROL_SHIFT); + if (ptr >= buffer_end) { + unsigned long address; + + address = i << (info->pageshift + info->blockshift); + result = sddr09_read_control( + us, address>>1, + min(alloc_blocks, numblocks - i), + buffer, 0); + if (result) { + result = -1; + goto done; + } + ptr = buffer; + } + + if (i == 0 || i == 1) { + info->pba_to_lba[i] = UNUSABLE; + continue; + } + + /* special PBAs have control field 0^16 */ + for (j = 0; j < 16; j++) + if (ptr[j] != 0) + goto nonz; + info->pba_to_lba[i] = UNUSABLE; + printk(KERN_WARNING "sddr09: PBA %d has no logical mapping\n", + i); + continue; + + nonz: + /* unwritten PBAs have control field FF^16 */ + for (j = 0; j < 16; j++) + if (ptr[j] != 0xff) + goto nonff; + continue; + + nonff: + /* normal PBAs start with six FFs */ + if (j < 6) { + printk(KERN_WARNING + "sddr09: PBA %d has no logical mapping: " + "reserved area = %02X%02X%02X%02X " + "data status %02X block status %02X\n", + i, ptr[0], ptr[1], ptr[2], ptr[3], + ptr[4], ptr[5]); + info->pba_to_lba[i] = UNUSABLE; + continue; + } + + if ((ptr[6] >> 4) != 0x01) { + printk(KERN_WARNING + "sddr09: PBA %d has invalid address field " + "%02X%02X/%02X%02X\n", + i, ptr[6], ptr[7], ptr[11], ptr[12]); + info->pba_to_lba[i] = UNUSABLE; + continue; + } + + /* check even parity */ + if (parity[ptr[6] ^ ptr[7]]) { + printk(KERN_WARNING + "sddr09: Bad parity in LBA for block %d" + " (%02X %02X)\n", i, ptr[6], ptr[7]); + info->pba_to_lba[i] = UNUSABLE; + continue; + } + + lba = short_pack(ptr[7], ptr[6]); + lba = (lba & 0x07FF) >> 1; + + /* + * Every 1024 physical blocks ("zone"), the LBA numbers + * go back to zero, but are within a higher block of LBA's. + * Also, there is a maximum of 1000 LBA's per zone. + * In other words, in PBA 1024-2047 you will find LBA 0-999 + * which are really LBA 1000-1999. This allows for 24 bad + * or special physical blocks per zone. + */ + + if (lba >= 1000) { + printk(KERN_WARNING + "sddr09: Bad low LBA %d for block %d\n", + lba, i); + goto possibly_erase; + } + + lba += 1000*(i/0x400); + + if (info->lba_to_pba[lba] != UNDEF) { + printk(KERN_WARNING + "sddr09: LBA %d seen for PBA %d and %d\n", + lba, info->lba_to_pba[lba], i); + goto possibly_erase; + } + + info->pba_to_lba[i] = lba; + info->lba_to_pba[lba] = i; + continue; + + possibly_erase: + if (erase_bad_lba_entries) { + unsigned long address; + + address = (i << (info->pageshift + info->blockshift)); + sddr09_erase(us, address>>1); + info->pba_to_lba[i] = UNDEF; + } else + info->pba_to_lba[i] = UNUSABLE; + } + + /* + * Approximate capacity. This is not entirely correct yet, + * since a zone with less than 1000 usable pages leads to + * missing LBAs. Especially if it is the last zone, some + * LBAs can be past capacity. + */ + lbact = 0; + for (i = 0; i < numblocks; i += 1024) { + int ct = 0; + + for (j = 0; j < 1024 && i+j < numblocks; j++) { + if (info->pba_to_lba[i+j] != UNUSABLE) { + if (ct >= 1000) + info->pba_to_lba[i+j] = SPARE; + else + ct++; + } + } + lbact += ct; + } + info->lbact = lbact; + usb_stor_dbg(us, "Found %d LBA's\n", lbact); + result = 0; + + done: + if (result != 0) { + kfree(info->lba_to_pba); + kfree(info->pba_to_lba); + info->lba_to_pba = NULL; + info->pba_to_lba = NULL; + } + kfree(buffer); + return result; +} + +static void +sddr09_card_info_destructor(void *extra) { + struct sddr09_card_info *info = (struct sddr09_card_info *)extra; + + if (!info) + return; + + kfree(info->lba_to_pba); + kfree(info->pba_to_lba); +} + +static int +sddr09_common_init(struct us_data *us) { + int result; + + /* set the configuration -- STALL is an acceptable response here */ + if (us->pusb_dev->actconfig->desc.bConfigurationValue != 1) { + usb_stor_dbg(us, "active config #%d != 1 ??\n", + us->pusb_dev->actconfig->desc.bConfigurationValue); + return -EINVAL; + } + + result = usb_reset_configuration(us->pusb_dev); + usb_stor_dbg(us, "Result of usb_reset_configuration is %d\n", result); + if (result == -EPIPE) { + usb_stor_dbg(us, "-- stall on control interface\n"); + } else if (result != 0) { + /* it's not a stall, but another error -- time to bail */ + usb_stor_dbg(us, "-- Unknown error. Rejecting device\n"); + return -EINVAL; + } + + us->extra = kzalloc(sizeof(struct sddr09_card_info), GFP_NOIO); + if (!us->extra) + return -ENOMEM; + us->extra_destructor = sddr09_card_info_destructor; + + nand_init_ecc(); + return 0; +} + + +/* + * This is needed at a very early stage. If this is not listed in the + * unusual devices list but called from here then LUN 0 of the combo reader + * is not recognized. But I do not know what precisely these calls do. + */ +static int +usb_stor_sddr09_dpcm_init(struct us_data *us) { + int result; + unsigned char *data = us->iobuf; + + result = sddr09_common_init(us); + if (result) + return result; + + result = sddr09_send_command(us, 0x01, USB_DIR_IN, data, 2); + if (result) { + usb_stor_dbg(us, "send_command fails\n"); + return result; + } + + usb_stor_dbg(us, "%02X %02X\n", data[0], data[1]); + // get 07 02 + + result = sddr09_send_command(us, 0x08, USB_DIR_IN, data, 2); + if (result) { + usb_stor_dbg(us, "2nd send_command fails\n"); + return result; + } + + usb_stor_dbg(us, "%02X %02X\n", data[0], data[1]); + // get 07 00 + + result = sddr09_request_sense(us, data, 18); + if (result == 0 && data[2] != 0) { + int j; + for (j=0; j<18; j++) + printk(" %02X", data[j]); + printk("\n"); + // get 70 00 00 00 00 00 00 * 00 00 00 00 00 00 + // 70: current command + // sense key 0, sense code 0, extd sense code 0 + // additional transfer length * = sizeof(data) - 7 + // Or: 70 00 06 00 00 00 00 0b 00 00 00 00 28 00 00 00 00 00 + // sense key 06, sense code 28: unit attention, + // not ready to ready transition + } + + // test unit ready + + return 0; /* not result */ +} + +/* + * Transport for the Microtech DPCM-USB + */ +static int dpcm_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int ret; + + usb_stor_dbg(us, "LUN=%d\n", srb->device->lun); + + switch (srb->device->lun) { + case 0: + + /* + * LUN 0 corresponds to the CompactFlash card reader. + */ + ret = usb_stor_CB_transport(srb, us); + break; + + case 1: + + /* + * LUN 1 corresponds to the SmartMedia card reader. + */ + + /* + * Set the LUN to 0 (just in case). + */ + srb->device->lun = 0; + ret = sddr09_transport(srb, us); + srb->device->lun = 1; + break; + + default: + usb_stor_dbg(us, "Invalid LUN %d\n", srb->device->lun); + ret = USB_STOR_TRANSPORT_ERROR; + break; + } + return ret; +} + + +/* + * Transport for the Sandisk SDDR-09 + */ +static int sddr09_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + static unsigned char sensekey = 0, sensecode = 0; + static unsigned char havefakesense = 0; + int result, i; + unsigned char *ptr = us->iobuf; + unsigned long capacity; + unsigned int page, pages; + + struct sddr09_card_info *info; + + static unsigned char inquiry_response[8] = { + 0x00, 0x80, 0x00, 0x02, 0x1F, 0x00, 0x00, 0x00 + }; + + /* note: no block descriptor support */ + static unsigned char mode_page_01[19] = { + 0x00, 0x0F, 0x00, 0x0, 0x0, 0x0, 0x00, + 0x01, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + info = (struct sddr09_card_info *)us->extra; + + if (srb->cmnd[0] == REQUEST_SENSE && havefakesense) { + /* for a faked command, we have to follow with a faked sense */ + memset(ptr, 0, 18); + ptr[0] = 0x70; + ptr[2] = sensekey; + ptr[7] = 11; + ptr[12] = sensecode; + usb_stor_set_xfer_buf(ptr, 18, srb); + sensekey = sensecode = havefakesense = 0; + return USB_STOR_TRANSPORT_GOOD; + } + + havefakesense = 1; + + /* Dummy up a response for INQUIRY since SDDR09 doesn't + respond to INQUIRY commands */ + + if (srb->cmnd[0] == INQUIRY) { + memcpy(ptr, inquiry_response, 8); + fill_inquiry_response(us, ptr, 36); + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == READ_CAPACITY) { + struct nand_flash_dev *cardinfo; + + sddr09_get_wp(us, info); /* read WP bit */ + + cardinfo = sddr09_get_cardinfo(us, info->flags); + if (!cardinfo) { + /* probably no media */ + init_error: + sensekey = 0x02; /* not ready */ + sensecode = 0x3a; /* medium not present */ + return USB_STOR_TRANSPORT_FAILED; + } + + info->capacity = (1 << cardinfo->chipshift); + info->pageshift = cardinfo->pageshift; + info->pagesize = (1 << info->pageshift); + info->blockshift = cardinfo->blockshift; + info->blocksize = (1 << info->blockshift); + info->blockmask = info->blocksize - 1; + + // map initialization, must follow get_cardinfo() + if (sddr09_read_map(us)) { + /* probably out of memory */ + goto init_error; + } + + // Report capacity + + capacity = (info->lbact << info->blockshift) - 1; + + ((__be32 *) ptr)[0] = cpu_to_be32(capacity); + + // Report page size + + ((__be32 *) ptr)[1] = cpu_to_be32(info->pagesize); + usb_stor_set_xfer_buf(ptr, 8, srb); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == MODE_SENSE_10) { + int modepage = (srb->cmnd[2] & 0x3F); + + /* They ask for the Read/Write error recovery page, + or for all pages. */ + /* %% We should check DBD %% */ + if (modepage == 0x01 || modepage == 0x3F) { + usb_stor_dbg(us, "Dummy up request for mode page 0x%x\n", + modepage); + + memcpy(ptr, mode_page_01, sizeof(mode_page_01)); + ((__be16*)ptr)[0] = cpu_to_be16(sizeof(mode_page_01) - 2); + ptr[3] = (info->flags & SDDR09_WP) ? 0x80 : 0; + usb_stor_set_xfer_buf(ptr, sizeof(mode_page_01), srb); + return USB_STOR_TRANSPORT_GOOD; + } + + sensekey = 0x05; /* illegal request */ + sensecode = 0x24; /* invalid field in CDB */ + return USB_STOR_TRANSPORT_FAILED; + } + + if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) + return USB_STOR_TRANSPORT_GOOD; + + havefakesense = 0; + + if (srb->cmnd[0] == READ_10) { + + page = short_pack(srb->cmnd[3], srb->cmnd[2]); + page <<= 16; + page |= short_pack(srb->cmnd[5], srb->cmnd[4]); + pages = short_pack(srb->cmnd[8], srb->cmnd[7]); + + usb_stor_dbg(us, "READ_10: read page %d pagect %d\n", + page, pages); + + result = sddr09_read_data(us, page, pages); + return (result == 0 ? USB_STOR_TRANSPORT_GOOD : + USB_STOR_TRANSPORT_ERROR); + } + + if (srb->cmnd[0] == WRITE_10) { + + page = short_pack(srb->cmnd[3], srb->cmnd[2]); + page <<= 16; + page |= short_pack(srb->cmnd[5], srb->cmnd[4]); + pages = short_pack(srb->cmnd[8], srb->cmnd[7]); + + usb_stor_dbg(us, "WRITE_10: write page %d pagect %d\n", + page, pages); + + result = sddr09_write_data(us, page, pages); + return (result == 0 ? USB_STOR_TRANSPORT_GOOD : + USB_STOR_TRANSPORT_ERROR); + } + + /* catch-all for all other commands, except + * pass TEST_UNIT_READY and REQUEST_SENSE through + */ + if (srb->cmnd[0] != TEST_UNIT_READY && + srb->cmnd[0] != REQUEST_SENSE) { + sensekey = 0x05; /* illegal request */ + sensecode = 0x20; /* invalid command */ + havefakesense = 1; + return USB_STOR_TRANSPORT_FAILED; + } + + for (; srb->cmd_len<12; srb->cmd_len++) + srb->cmnd[srb->cmd_len] = 0; + + srb->cmnd[1] = LUNBITS; + + ptr[0] = 0; + for (i=0; i<12; i++) + sprintf(ptr+strlen(ptr), "%02X ", srb->cmnd[i]); + + usb_stor_dbg(us, "Send control for command %s\n", ptr); + + result = sddr09_send_scsi_command(us, srb->cmnd, 12); + if (result) { + usb_stor_dbg(us, "sddr09_send_scsi_command returns %d\n", + result); + return USB_STOR_TRANSPORT_ERROR; + } + + if (scsi_bufflen(srb) == 0) + return USB_STOR_TRANSPORT_GOOD; + + if (srb->sc_data_direction == DMA_TO_DEVICE || + srb->sc_data_direction == DMA_FROM_DEVICE) { + unsigned int pipe = (srb->sc_data_direction == DMA_TO_DEVICE) + ? us->send_bulk_pipe : us->recv_bulk_pipe; + + usb_stor_dbg(us, "%s %d bytes\n", + (srb->sc_data_direction == DMA_TO_DEVICE) ? + "sending" : "receiving", + scsi_bufflen(srb)); + + result = usb_stor_bulk_srb(us, pipe, srb); + + return (result == USB_STOR_XFER_GOOD ? + USB_STOR_TRANSPORT_GOOD : USB_STOR_TRANSPORT_ERROR); + } + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Initialization routine for the sddr09 subdriver + */ +static int +usb_stor_sddr09_init(struct us_data *us) { + return sddr09_common_init(us); +} + +static int sddr09_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - sddr09_usb_ids) + sddr09_unusual_dev_list); + if (result) + return result; + + if (us->protocol == USB_PR_DPCM_USB) { + us->transport_name = "Control/Bulk-EUSB/SDDR09"; + us->transport = dpcm_transport; + us->transport_reset = usb_stor_CB_reset; + us->max_lun = 1; + } else { + us->transport_name = "EUSB/SDDR09"; + us->transport = sddr09_transport; + us->transport_reset = usb_stor_CB_reset; + us->max_lun = 0; + } + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver sddr09_driver = { + .name = "ums-sddr09", + .probe = sddr09_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = sddr09_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(sddr09_driver); diff --git a/drivers/usb/storage/sddr55.c b/drivers/usb/storage/sddr55.c new file mode 100644 index 00000000..aacedef9 --- /dev/null +++ b/drivers/usb/storage/sddr55.c @@ -0,0 +1,1006 @@ +/* Driver for SanDisk SDDR-55 SmartMedia reader + * + * SDDR55 driver v0.1: + * + * First release + * + * Current development and maintenance by: + * (c) 2002 Simon Munton + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + +MODULE_DESCRIPTION("Driver for SanDisk SDDR-55 SmartMedia reader"); +MODULE_AUTHOR("Simon Munton"); +MODULE_LICENSE("GPL"); + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id sddr55_usb_ids[] = { +# include "unusual_sddr55.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, sddr55_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev sddr55_unusual_dev_list[] = { +# include "unusual_sddr55.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + + +#define short_pack(lsb,msb) ( ((u16)(lsb)) | ( ((u16)(msb))<<8 ) ) +#define LSB_of(s) ((s)&0xFF) +#define MSB_of(s) ((s)>>8) +#define PAGESIZE 512 + +#define set_sense_info(sk, asc, ascq) \ + do { \ + info->sense_data[2] = sk; \ + info->sense_data[12] = asc; \ + info->sense_data[13] = ascq; \ + } while (0) + + +struct sddr55_card_info { + unsigned long capacity; /* Size of card in bytes */ + int max_log_blks; /* maximum number of logical blocks */ + int pageshift; /* log2 of pagesize */ + int smallpageshift; /* 1 if pagesize == 256 */ + int blocksize; /* Size of block in pages */ + int blockshift; /* log2 of blocksize */ + int blockmask; /* 2^blockshift - 1 */ + int read_only; /* non zero if card is write protected */ + int force_read_only; /* non zero if we find a map error*/ + int *lba_to_pba; /* logical to physical map */ + int *pba_to_lba; /* physical to logical map */ + int fatal_error; /* set if we detect something nasty */ + unsigned long last_access; /* number of jiffies since we last talked to device */ + unsigned char sense_data[18]; +}; + + +#define NOT_ALLOCATED 0xffffffff +#define BAD_BLOCK 0xffff +#define CIS_BLOCK 0x400 +#define UNUSED_BLOCK 0x3ff + +static int +sddr55_bulk_transport(struct us_data *us, int direction, + unsigned char *data, unsigned int len) { + struct sddr55_card_info *info = (struct sddr55_card_info *)us->extra; + unsigned int pipe = (direction == DMA_FROM_DEVICE) ? + us->recv_bulk_pipe : us->send_bulk_pipe; + + if (!len) + return USB_STOR_XFER_GOOD; + info->last_access = jiffies; + return usb_stor_bulk_transfer_buf(us, pipe, data, len, NULL); +} + +/* check if card inserted, if there is, update read_only status + * return non zero if no card + */ + +static int sddr55_status(struct us_data *us) +{ + int result; + unsigned char *command = us->iobuf; + unsigned char *status = us->iobuf; + struct sddr55_card_info *info = (struct sddr55_card_info *)us->extra; + + /* send command */ + memset(command, 0, 8); + command[5] = 0xB0; + command[7] = 0x80; + result = sddr55_bulk_transport(us, + DMA_TO_DEVICE, command, 8); + + usb_stor_dbg(us, "Result for send_command in status %d\n", result); + + if (result != USB_STOR_XFER_GOOD) { + set_sense_info (4, 0, 0); /* hardware error */ + return USB_STOR_TRANSPORT_ERROR; + } + + result = sddr55_bulk_transport(us, + DMA_FROM_DEVICE, status, 4); + + /* expect to get short transfer if no card fitted */ + if (result == USB_STOR_XFER_SHORT || result == USB_STOR_XFER_STALLED) { + /* had a short transfer, no card inserted, free map memory */ + kfree(info->lba_to_pba); + kfree(info->pba_to_lba); + info->lba_to_pba = NULL; + info->pba_to_lba = NULL; + + info->fatal_error = 0; + info->force_read_only = 0; + + set_sense_info (2, 0x3a, 0); /* not ready, medium not present */ + return USB_STOR_TRANSPORT_FAILED; + } + + if (result != USB_STOR_XFER_GOOD) { + set_sense_info (4, 0, 0); /* hardware error */ + return USB_STOR_TRANSPORT_FAILED; + } + + /* check write protect status */ + info->read_only = (status[0] & 0x20); + + /* now read status */ + result = sddr55_bulk_transport(us, + DMA_FROM_DEVICE, status, 2); + + if (result != USB_STOR_XFER_GOOD) { + set_sense_info (4, 0, 0); /* hardware error */ + } + + return (result == USB_STOR_XFER_GOOD ? + USB_STOR_TRANSPORT_GOOD : USB_STOR_TRANSPORT_FAILED); +} + + +static int sddr55_read_data(struct us_data *us, + unsigned int lba, + unsigned int page, + unsigned short sectors) { + + int result = USB_STOR_TRANSPORT_GOOD; + unsigned char *command = us->iobuf; + unsigned char *status = us->iobuf; + struct sddr55_card_info *info = (struct sddr55_card_info *)us->extra; + unsigned char *buffer; + + unsigned int pba; + unsigned long address; + + unsigned short pages; + unsigned int len, offset; + struct scatterlist *sg; + + // Since we only read in one block at a time, we have to create + // a bounce buffer and move the data a piece at a time between the + // bounce buffer and the actual transfer buffer. + + len = min((unsigned int) sectors, (unsigned int) info->blocksize >> + info->smallpageshift) * PAGESIZE; + buffer = kmalloc(len, GFP_NOIO); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; /* out of memory */ + offset = 0; + sg = NULL; + + while (sectors>0) { + + /* have we got to end? */ + if (lba >= info->max_log_blks) + break; + + pba = info->lba_to_pba[lba]; + + // Read as many sectors as possible in this block + + pages = min((unsigned int) sectors << info->smallpageshift, + info->blocksize - page); + len = pages << info->pageshift; + + usb_stor_dbg(us, "Read %02X pages, from PBA %04X (LBA %04X) page %02X\n", + pages, pba, lba, page); + + if (pba == NOT_ALLOCATED) { + /* no pba for this lba, fill with zeroes */ + memset (buffer, 0, len); + } else { + + address = (pba << info->blockshift) + page; + + command[0] = 0; + command[1] = LSB_of(address>>16); + command[2] = LSB_of(address>>8); + command[3] = LSB_of(address); + + command[4] = 0; + command[5] = 0xB0; + command[6] = LSB_of(pages << (1 - info->smallpageshift)); + command[7] = 0x85; + + /* send command */ + result = sddr55_bulk_transport(us, + DMA_TO_DEVICE, command, 8); + + usb_stor_dbg(us, "Result for send_command in read_data %d\n", + result); + + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + /* read data */ + result = sddr55_bulk_transport(us, + DMA_FROM_DEVICE, buffer, len); + + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + /* now read status */ + result = sddr55_bulk_transport(us, + DMA_FROM_DEVICE, status, 2); + + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + /* check status for error */ + if (status[0] == 0xff && status[1] == 0x4) { + set_sense_info (3, 0x11, 0); + result = USB_STOR_TRANSPORT_FAILED; + goto leave; + } + } + + // Store the data in the transfer buffer + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &offset, TO_XFER_BUF); + + page = 0; + lba++; + sectors -= pages >> info->smallpageshift; + } + + result = USB_STOR_TRANSPORT_GOOD; + +leave: + kfree(buffer); + + return result; +} + +static int sddr55_write_data(struct us_data *us, + unsigned int lba, + unsigned int page, + unsigned short sectors) { + + int result = USB_STOR_TRANSPORT_GOOD; + unsigned char *command = us->iobuf; + unsigned char *status = us->iobuf; + struct sddr55_card_info *info = (struct sddr55_card_info *)us->extra; + unsigned char *buffer; + + unsigned int pba; + unsigned int new_pba; + unsigned long address; + + unsigned short pages; + int i; + unsigned int len, offset; + struct scatterlist *sg; + + /* check if we are allowed to write */ + if (info->read_only || info->force_read_only) { + set_sense_info (7, 0x27, 0); /* read only */ + return USB_STOR_TRANSPORT_FAILED; + } + + // Since we only write one block at a time, we have to create + // a bounce buffer and move the data a piece at a time between the + // bounce buffer and the actual transfer buffer. + + len = min((unsigned int) sectors, (unsigned int) info->blocksize >> + info->smallpageshift) * PAGESIZE; + buffer = kmalloc(len, GFP_NOIO); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + offset = 0; + sg = NULL; + + while (sectors > 0) { + + /* have we got to end? */ + if (lba >= info->max_log_blks) + break; + + pba = info->lba_to_pba[lba]; + + // Write as many sectors as possible in this block + + pages = min((unsigned int) sectors << info->smallpageshift, + info->blocksize - page); + len = pages << info->pageshift; + + // Get the data from the transfer buffer + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &offset, FROM_XFER_BUF); + + usb_stor_dbg(us, "Write %02X pages, to PBA %04X (LBA %04X) page %02X\n", + pages, pba, lba, page); + + command[4] = 0; + + if (pba == NOT_ALLOCATED) { + /* no pba allocated for this lba, find a free pba to use */ + + int max_pba = (info->max_log_blks / 250 ) * 256; + int found_count = 0; + int found_pba = -1; + + /* set pba to first block in zone lba is in */ + pba = (lba / 1000) * 1024; + + usb_stor_dbg(us, "No PBA for LBA %04X\n", lba); + + if (max_pba > 1024) + max_pba = 1024; + + /* + * Scan through the map looking for an unused block + * leave 16 unused blocks at start (or as many as + * possible) since the sddr55 seems to reuse a used + * block when it shouldn't if we don't leave space. + */ + for (i = 0; i < max_pba; i++, pba++) { + if (info->pba_to_lba[pba] == UNUSED_BLOCK) { + found_pba = pba; + if (found_count++ > 16) + break; + } + } + + pba = found_pba; + + if (pba == -1) { + /* oh dear */ + usb_stor_dbg(us, "Couldn't find unallocated block\n"); + + set_sense_info (3, 0x31, 0); /* medium error */ + result = USB_STOR_TRANSPORT_FAILED; + goto leave; + } + + usb_stor_dbg(us, "Allocating PBA %04X for LBA %04X\n", + pba, lba); + + /* set writing to unallocated block flag */ + command[4] = 0x40; + } + + address = (pba << info->blockshift) + page; + + command[1] = LSB_of(address>>16); + command[2] = LSB_of(address>>8); + command[3] = LSB_of(address); + + /* set the lba into the command, modulo 1000 */ + command[0] = LSB_of(lba % 1000); + command[6] = MSB_of(lba % 1000); + + command[4] |= LSB_of(pages >> info->smallpageshift); + command[5] = 0xB0; + command[7] = 0x86; + + /* send command */ + result = sddr55_bulk_transport(us, + DMA_TO_DEVICE, command, 8); + + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Result for send_command in write_data %d\n", + result); + + /* set_sense_info is superfluous here? */ + set_sense_info (3, 0x3, 0);/* peripheral write error */ + result = USB_STOR_TRANSPORT_FAILED; + goto leave; + } + + /* send the data */ + result = sddr55_bulk_transport(us, + DMA_TO_DEVICE, buffer, len); + + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Result for send_data in write_data %d\n", + result); + + /* set_sense_info is superfluous here? */ + set_sense_info (3, 0x3, 0);/* peripheral write error */ + result = USB_STOR_TRANSPORT_FAILED; + goto leave; + } + + /* now read status */ + result = sddr55_bulk_transport(us, DMA_FROM_DEVICE, status, 6); + + if (result != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Result for get_status in write_data %d\n", + result); + + /* set_sense_info is superfluous here? */ + set_sense_info (3, 0x3, 0);/* peripheral write error */ + result = USB_STOR_TRANSPORT_FAILED; + goto leave; + } + + new_pba = (status[3] + (status[4] << 8) + (status[5] << 16)) + >> info->blockshift; + + /* check status for error */ + if (status[0] == 0xff && status[1] == 0x4) { + info->pba_to_lba[new_pba] = BAD_BLOCK; + + set_sense_info (3, 0x0c, 0); + result = USB_STOR_TRANSPORT_FAILED; + goto leave; + } + + usb_stor_dbg(us, "Updating maps for LBA %04X: old PBA %04X, new PBA %04X\n", + lba, pba, new_pba); + + /* update the lba<->pba maps, note new_pba might be the same as pba */ + info->lba_to_pba[lba] = new_pba; + info->pba_to_lba[pba] = UNUSED_BLOCK; + + /* check that new_pba wasn't already being used */ + if (info->pba_to_lba[new_pba] != UNUSED_BLOCK) { + printk(KERN_ERR "sddr55 error: new PBA %04X already in use for LBA %04X\n", + new_pba, info->pba_to_lba[new_pba]); + info->fatal_error = 1; + set_sense_info (3, 0x31, 0); + result = USB_STOR_TRANSPORT_FAILED; + goto leave; + } + + /* update the pba<->lba maps for new_pba */ + info->pba_to_lba[new_pba] = lba % 1000; + + page = 0; + lba++; + sectors -= pages >> info->smallpageshift; + } + result = USB_STOR_TRANSPORT_GOOD; + + leave: + kfree(buffer); + return result; +} + +static int sddr55_read_deviceID(struct us_data *us, + unsigned char *manufacturerID, + unsigned char *deviceID) { + + int result; + unsigned char *command = us->iobuf; + unsigned char *content = us->iobuf; + + memset(command, 0, 8); + command[5] = 0xB0; + command[7] = 0x84; + result = sddr55_bulk_transport(us, DMA_TO_DEVICE, command, 8); + + usb_stor_dbg(us, "Result of send_control for device ID is %d\n", + result); + + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + result = sddr55_bulk_transport(us, + DMA_FROM_DEVICE, content, 4); + + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + *manufacturerID = content[0]; + *deviceID = content[1]; + + if (content[0] != 0xff) { + result = sddr55_bulk_transport(us, + DMA_FROM_DEVICE, content, 2); + } + + return USB_STOR_TRANSPORT_GOOD; +} + + +static int sddr55_reset(struct us_data *us) +{ + return 0; +} + + +static unsigned long sddr55_get_capacity(struct us_data *us) { + + unsigned char uninitialized_var(manufacturerID); + unsigned char uninitialized_var(deviceID); + int result; + struct sddr55_card_info *info = (struct sddr55_card_info *)us->extra; + + usb_stor_dbg(us, "Reading capacity...\n"); + + result = sddr55_read_deviceID(us, + &manufacturerID, + &deviceID); + + usb_stor_dbg(us, "Result of read_deviceID is %d\n", result); + + if (result != USB_STOR_XFER_GOOD) + return 0; + + usb_stor_dbg(us, "Device ID = %02X\n", deviceID); + usb_stor_dbg(us, "Manuf ID = %02X\n", manufacturerID); + + info->pageshift = 9; + info->smallpageshift = 0; + info->blocksize = 16; + info->blockshift = 4; + info->blockmask = 15; + + switch (deviceID) { + + case 0x6e: // 1MB + case 0xe8: + case 0xec: + info->pageshift = 8; + info->smallpageshift = 1; + return 0x00100000; + + case 0xea: // 2MB + case 0x64: + info->pageshift = 8; + info->smallpageshift = 1; + case 0x5d: // 5d is a ROM card with pagesize 512. + return 0x00200000; + + case 0xe3: // 4MB + case 0xe5: + case 0x6b: + case 0xd5: + return 0x00400000; + + case 0xe6: // 8MB + case 0xd6: + return 0x00800000; + + case 0x73: // 16MB + info->blocksize = 32; + info->blockshift = 5; + info->blockmask = 31; + return 0x01000000; + + case 0x75: // 32MB + info->blocksize = 32; + info->blockshift = 5; + info->blockmask = 31; + return 0x02000000; + + case 0x76: // 64MB + info->blocksize = 32; + info->blockshift = 5; + info->blockmask = 31; + return 0x04000000; + + case 0x79: // 128MB + info->blocksize = 32; + info->blockshift = 5; + info->blockmask = 31; + return 0x08000000; + + default: // unknown + return 0; + + } +} + +static int sddr55_read_map(struct us_data *us) { + + struct sddr55_card_info *info = (struct sddr55_card_info *)(us->extra); + int numblocks; + unsigned char *buffer; + unsigned char *command = us->iobuf; + int i; + unsigned short lba; + unsigned short max_lba; + int result; + + if (!info->capacity) + return -1; + + numblocks = info->capacity >> (info->blockshift + info->pageshift); + + buffer = kmalloc( numblocks * 2, GFP_NOIO ); + + if (!buffer) + return -1; + + memset(command, 0, 8); + command[5] = 0xB0; + command[6] = numblocks * 2 / 256; + command[7] = 0x8A; + + result = sddr55_bulk_transport(us, DMA_TO_DEVICE, command, 8); + + if ( result != USB_STOR_XFER_GOOD) { + kfree (buffer); + return -1; + } + + result = sddr55_bulk_transport(us, DMA_FROM_DEVICE, buffer, numblocks * 2); + + if ( result != USB_STOR_XFER_GOOD) { + kfree (buffer); + return -1; + } + + result = sddr55_bulk_transport(us, DMA_FROM_DEVICE, command, 2); + + if ( result != USB_STOR_XFER_GOOD) { + kfree (buffer); + return -1; + } + + kfree(info->lba_to_pba); + kfree(info->pba_to_lba); + info->lba_to_pba = kmalloc(numblocks*sizeof(int), GFP_NOIO); + info->pba_to_lba = kmalloc(numblocks*sizeof(int), GFP_NOIO); + + if (info->lba_to_pba == NULL || info->pba_to_lba == NULL) { + kfree(info->lba_to_pba); + kfree(info->pba_to_lba); + info->lba_to_pba = NULL; + info->pba_to_lba = NULL; + kfree(buffer); + return -1; + } + + memset(info->lba_to_pba, 0xff, numblocks*sizeof(int)); + memset(info->pba_to_lba, 0xff, numblocks*sizeof(int)); + + /* set maximum lba */ + max_lba = info->max_log_blks; + if (max_lba > 1000) + max_lba = 1000; + + // Each block is 64 bytes of control data, so block i is located in + // scatterlist block i*64/128k = i*(2^6)*(2^-17) = i*(2^-11) + + for (i=0; ipba_to_lba[i] = lba; + + if (lba >= max_lba) { + continue; + } + + if (info->lba_to_pba[lba + zone * 1000] != NOT_ALLOCATED && + !info->force_read_only) { + printk(KERN_WARNING + "sddr55: map inconsistency at LBA %04X\n", + lba + zone * 1000); + info->force_read_only = 1; + } + + if (lba<0x10 || (lba>=0x3E0 && lba<0x3EF)) + usb_stor_dbg(us, "LBA %04X <-> PBA %04X\n", lba, i); + + info->lba_to_pba[lba + zone * 1000] = i; + } + + kfree(buffer); + return 0; +} + + +static void sddr55_card_info_destructor(void *extra) { + struct sddr55_card_info *info = (struct sddr55_card_info *)extra; + + if (!extra) + return; + + kfree(info->lba_to_pba); + kfree(info->pba_to_lba); +} + + +/* + * Transport for the Sandisk SDDR-55 + */ +static int sddr55_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int result; + static unsigned char inquiry_response[8] = { + 0x00, 0x80, 0x00, 0x02, 0x1F, 0x00, 0x00, 0x00 + }; + // write-protected for now, no block descriptor support + static unsigned char mode_page_01[20] = { + 0x0, 0x12, 0x00, 0x80, 0x0, 0x0, 0x0, 0x0, + 0x01, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + unsigned char *ptr = us->iobuf; + unsigned long capacity; + unsigned int lba; + unsigned int pba; + unsigned int page; + unsigned short pages; + struct sddr55_card_info *info; + + if (!us->extra) { + us->extra = kzalloc( + sizeof(struct sddr55_card_info), GFP_NOIO); + if (!us->extra) + return USB_STOR_TRANSPORT_ERROR; + us->extra_destructor = sddr55_card_info_destructor; + } + + info = (struct sddr55_card_info *)(us->extra); + + if (srb->cmnd[0] == REQUEST_SENSE) { + usb_stor_dbg(us, "request sense %02x/%02x/%02x\n", + info->sense_data[2], + info->sense_data[12], + info->sense_data[13]); + + memcpy (ptr, info->sense_data, sizeof info->sense_data); + ptr[0] = 0x70; + ptr[7] = 11; + usb_stor_set_xfer_buf (ptr, sizeof info->sense_data, srb); + memset (info->sense_data, 0, sizeof info->sense_data); + + return USB_STOR_TRANSPORT_GOOD; + } + + memset (info->sense_data, 0, sizeof info->sense_data); + + /* Dummy up a response for INQUIRY since SDDR55 doesn't + respond to INQUIRY commands */ + + if (srb->cmnd[0] == INQUIRY) { + memcpy(ptr, inquiry_response, 8); + fill_inquiry_response(us, ptr, 36); + return USB_STOR_TRANSPORT_GOOD; + } + + /* only check card status if the map isn't allocated, ie no card seen yet + * or if it's been over half a second since we last accessed it + */ + if (info->lba_to_pba == NULL || time_after(jiffies, info->last_access + HZ/2)) { + + /* check to see if a card is fitted */ + result = sddr55_status (us); + if (result) { + result = sddr55_status (us); + if (!result) { + set_sense_info (6, 0x28, 0); /* new media, set unit attention, not ready to ready */ + } + return USB_STOR_TRANSPORT_FAILED; + } + } + + /* if we detected a problem with the map when writing, + don't allow any more access */ + if (info->fatal_error) { + + set_sense_info (3, 0x31, 0); + return USB_STOR_TRANSPORT_FAILED; + } + + if (srb->cmnd[0] == READ_CAPACITY) { + + capacity = sddr55_get_capacity(us); + + if (!capacity) { + set_sense_info (3, 0x30, 0); /* incompatible medium */ + return USB_STOR_TRANSPORT_FAILED; + } + + info->capacity = capacity; + + /* figure out the maximum logical block number, allowing for + * the fact that only 250 out of every 256 are used */ + info->max_log_blks = ((info->capacity >> (info->pageshift + info->blockshift)) / 256) * 250; + + /* Last page in the card, adjust as we only use 250 out of + * every 256 pages */ + capacity = (capacity / 256) * 250; + + capacity /= PAGESIZE; + capacity--; + + ((__be32 *) ptr)[0] = cpu_to_be32(capacity); + ((__be32 *) ptr)[1] = cpu_to_be32(PAGESIZE); + usb_stor_set_xfer_buf(ptr, 8, srb); + + sddr55_read_map(us); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == MODE_SENSE_10) { + + memcpy(ptr, mode_page_01, sizeof mode_page_01); + ptr[3] = (info->read_only || info->force_read_only) ? 0x80 : 0; + usb_stor_set_xfer_buf(ptr, sizeof(mode_page_01), srb); + + if ( (srb->cmnd[2] & 0x3F) == 0x01 ) { + usb_stor_dbg(us, "Dummy up request for mode page 1\n"); + return USB_STOR_TRANSPORT_GOOD; + + } else if ( (srb->cmnd[2] & 0x3F) == 0x3F ) { + usb_stor_dbg(us, "Dummy up request for all mode pages\n"); + return USB_STOR_TRANSPORT_GOOD; + } + + set_sense_info (5, 0x24, 0); /* invalid field in command */ + return USB_STOR_TRANSPORT_FAILED; + } + + if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) { + + usb_stor_dbg(us, "%s medium removal. Not that I can do anything about it...\n", + (srb->cmnd[4]&0x03) ? "Prevent" : "Allow"); + + return USB_STOR_TRANSPORT_GOOD; + + } + + if (srb->cmnd[0] == READ_10 || srb->cmnd[0] == WRITE_10) { + + page = short_pack(srb->cmnd[3], srb->cmnd[2]); + page <<= 16; + page |= short_pack(srb->cmnd[5], srb->cmnd[4]); + pages = short_pack(srb->cmnd[8], srb->cmnd[7]); + + page <<= info->smallpageshift; + + // convert page to block and page-within-block + + lba = page >> info->blockshift; + page = page & info->blockmask; + + // locate physical block corresponding to logical block + + if (lba >= info->max_log_blks) { + + usb_stor_dbg(us, "Error: Requested LBA %04X exceeds maximum block %04X\n", + lba, info->max_log_blks - 1); + + set_sense_info (5, 0x24, 0); /* invalid field in command */ + + return USB_STOR_TRANSPORT_FAILED; + } + + pba = info->lba_to_pba[lba]; + + if (srb->cmnd[0] == WRITE_10) { + usb_stor_dbg(us, "WRITE_10: write block %04X (LBA %04X) page %01X pages %d\n", + pba, lba, page, pages); + + return sddr55_write_data(us, lba, page, pages); + } else { + usb_stor_dbg(us, "READ_10: read block %04X (LBA %04X) page %01X pages %d\n", + pba, lba, page, pages); + + return sddr55_read_data(us, lba, page, pages); + } + } + + + if (srb->cmnd[0] == TEST_UNIT_READY) { + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == START_STOP) { + return USB_STOR_TRANSPORT_GOOD; + } + + set_sense_info (5, 0x20, 0); /* illegal command */ + + return USB_STOR_TRANSPORT_FAILED; // FIXME: sense buffer? +} + + +static int sddr55_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - sddr55_usb_ids) + sddr55_unusual_dev_list); + if (result) + return result; + + us->transport_name = "SDDR55"; + us->transport = sddr55_transport; + us->transport_reset = sddr55_reset; + us->max_lun = 0; + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver sddr55_driver = { + .name = "ums-sddr55", + .probe = sddr55_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = sddr55_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(sddr55_driver); diff --git a/drivers/usb/storage/shuttle_usbat.c b/drivers/usb/storage/shuttle_usbat.c new file mode 100644 index 00000000..008d805c --- /dev/null +++ b/drivers/usb/storage/shuttle_usbat.c @@ -0,0 +1,1874 @@ +/* Driver for SCM Microsystems (a.k.a. Shuttle) USB-ATAPI cable + * + * Current development and maintenance by: + * (c) 2000, 2001 Robert Baruch (autophile@starband.net) + * (c) 2004, 2005 Daniel Drake + * + * Developed with the assistance of: + * (c) 2002 Alan Stern + * + * Flash support based on earlier work by: + * (c) 2002 Thomas Kreiling + * + * Many originally ATAPI devices were slightly modified to meet the USB + * market by using some kind of translation from ATAPI to USB on the host, + * and the peripheral would translate from USB back to ATAPI. + * + * SCM Microsystems (www.scmmicro.com) makes a device, sold to OEM's only, + * which does the USB-to-ATAPI conversion. By obtaining the data sheet on + * their device under nondisclosure agreement, I have been able to write + * this driver for Linux. + * + * The chip used in the device can also be used for EPP and ISA translation + * as well. This driver is only guaranteed to work with the ATAPI + * translation. + * + * See the Kconfig help text for a list of devices known to be supported by + * this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" + +MODULE_DESCRIPTION("Driver for SCM Microsystems (a.k.a. Shuttle) USB-ATAPI cable"); +MODULE_AUTHOR("Daniel Drake , Robert Baruch "); +MODULE_LICENSE("GPL"); + +/* Supported device types */ +#define USBAT_DEV_HP8200 0x01 +#define USBAT_DEV_FLASH 0x02 + +#define USBAT_EPP_PORT 0x10 +#define USBAT_EPP_REGISTER 0x30 +#define USBAT_ATA 0x40 +#define USBAT_ISA 0x50 + +/* Commands (need to be logically OR'd with an access type */ +#define USBAT_CMD_READ_REG 0x00 +#define USBAT_CMD_WRITE_REG 0x01 +#define USBAT_CMD_READ_BLOCK 0x02 +#define USBAT_CMD_WRITE_BLOCK 0x03 +#define USBAT_CMD_COND_READ_BLOCK 0x04 +#define USBAT_CMD_COND_WRITE_BLOCK 0x05 +#define USBAT_CMD_WRITE_REGS 0x07 + +/* Commands (these don't need an access type) */ +#define USBAT_CMD_EXEC_CMD 0x80 +#define USBAT_CMD_SET_FEAT 0x81 +#define USBAT_CMD_UIO 0x82 + +/* Methods of accessing UIO register */ +#define USBAT_UIO_READ 1 +#define USBAT_UIO_WRITE 0 + +/* Qualifier bits */ +#define USBAT_QUAL_FCQ 0x20 /* full compare */ +#define USBAT_QUAL_ALQ 0x10 /* auto load subcount */ + +/* USBAT Flash Media status types */ +#define USBAT_FLASH_MEDIA_NONE 0 +#define USBAT_FLASH_MEDIA_CF 1 + +/* USBAT Flash Media change types */ +#define USBAT_FLASH_MEDIA_SAME 0 +#define USBAT_FLASH_MEDIA_CHANGED 1 + +/* USBAT ATA registers */ +#define USBAT_ATA_DATA 0x10 /* read/write data (R/W) */ +#define USBAT_ATA_FEATURES 0x11 /* set features (W) */ +#define USBAT_ATA_ERROR 0x11 /* error (R) */ +#define USBAT_ATA_SECCNT 0x12 /* sector count (R/W) */ +#define USBAT_ATA_SECNUM 0x13 /* sector number (R/W) */ +#define USBAT_ATA_LBA_ME 0x14 /* cylinder low (R/W) */ +#define USBAT_ATA_LBA_HI 0x15 /* cylinder high (R/W) */ +#define USBAT_ATA_DEVICE 0x16 /* head/device selection (R/W) */ +#define USBAT_ATA_STATUS 0x17 /* device status (R) */ +#define USBAT_ATA_CMD 0x17 /* device command (W) */ +#define USBAT_ATA_ALTSTATUS 0x0E /* status (no clear IRQ) (R) */ + +/* USBAT User I/O Data registers */ +#define USBAT_UIO_EPAD 0x80 /* Enable Peripheral Control Signals */ +#define USBAT_UIO_CDT 0x40 /* Card Detect (Read Only) */ + /* CDT = ACKD & !UI1 & !UI0 */ +#define USBAT_UIO_1 0x20 /* I/O 1 */ +#define USBAT_UIO_0 0x10 /* I/O 0 */ +#define USBAT_UIO_EPP_ATA 0x08 /* 1=EPP mode, 0=ATA mode */ +#define USBAT_UIO_UI1 0x04 /* Input 1 */ +#define USBAT_UIO_UI0 0x02 /* Input 0 */ +#define USBAT_UIO_INTR_ACK 0x01 /* Interrupt (ATA/ISA)/Acknowledge (EPP) */ + +/* USBAT User I/O Enable registers */ +#define USBAT_UIO_DRVRST 0x80 /* Reset Peripheral */ +#define USBAT_UIO_ACKD 0x40 /* Enable Card Detect */ +#define USBAT_UIO_OE1 0x20 /* I/O 1 set=output/clr=input */ + /* If ACKD=1, set OE1 to 1 also. */ +#define USBAT_UIO_OE0 0x10 /* I/O 0 set=output/clr=input */ +#define USBAT_UIO_ADPRST 0x01 /* Reset SCM chip */ + +/* USBAT Features */ +#define USBAT_FEAT_ETEN 0x80 /* External trigger enable */ +#define USBAT_FEAT_U1 0x08 +#define USBAT_FEAT_U0 0x04 +#define USBAT_FEAT_ET1 0x02 +#define USBAT_FEAT_ET2 0x01 + +struct usbat_info { + int devicetype; + + /* Used for Flash readers only */ + unsigned long sectors; /* total sector count */ + unsigned long ssize; /* sector size in bytes */ + + unsigned char sense_key; + unsigned long sense_asc; /* additional sense code */ + unsigned long sense_ascq; /* additional sense code qualifier */ +}; + +#define short_pack(LSB,MSB) ( ((u16)(LSB)) | ( ((u16)(MSB))<<8 ) ) +#define LSB_of(s) ((s)&0xFF) +#define MSB_of(s) ((s)>>8) + +static int transferred = 0; + +static int usbat_flash_transport(struct scsi_cmnd * srb, struct us_data *us); +static int usbat_hp8200e_transport(struct scsi_cmnd *srb, struct us_data *us); + +static int init_usbat_cd(struct us_data *us); +static int init_usbat_flash(struct us_data *us); + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +static struct usb_device_id usbat_usb_ids[] = { +# include "unusual_usbat.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usbat_usb_ids); + +#undef UNUSUAL_DEV + +/* + * The flags table + */ +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev usbat_unusual_dev_list[] = { +# include "unusual_usbat.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + +/* + * Convenience function to produce an ATA read/write sectors command + * Use cmd=0x20 for read, cmd=0x30 for write + */ +static void usbat_pack_ata_sector_cmd(unsigned char *buf, + unsigned char thistime, + u32 sector, unsigned char cmd) +{ + buf[0] = 0; + buf[1] = thistime; + buf[2] = sector & 0xFF; + buf[3] = (sector >> 8) & 0xFF; + buf[4] = (sector >> 16) & 0xFF; + buf[5] = 0xE0 | ((sector >> 24) & 0x0F); + buf[6] = cmd; +} + +/* + * Convenience function to get the device type (flash or hp8200) + */ +static int usbat_get_device_type(struct us_data *us) +{ + return ((struct usbat_info*)us->extra)->devicetype; +} + +/* + * Read a register from the device + */ +static int usbat_read(struct us_data *us, + unsigned char access, + unsigned char reg, + unsigned char *content) +{ + return usb_stor_ctrl_transfer(us, + us->recv_ctrl_pipe, + access | USBAT_CMD_READ_REG, + 0xC0, + (u16)reg, + 0, + content, + 1); +} + +/* + * Write to a register on the device + */ +static int usbat_write(struct us_data *us, + unsigned char access, + unsigned char reg, + unsigned char content) +{ + return usb_stor_ctrl_transfer(us, + us->send_ctrl_pipe, + access | USBAT_CMD_WRITE_REG, + 0x40, + short_pack(reg, content), + 0, + NULL, + 0); +} + +/* + * Convenience function to perform a bulk read + */ +static int usbat_bulk_read(struct us_data *us, + void* buf, + unsigned int len, + int use_sg) +{ + if (len == 0) + return USB_STOR_XFER_GOOD; + + usb_stor_dbg(us, "len = %d\n", len); + return usb_stor_bulk_transfer_sg(us, us->recv_bulk_pipe, buf, len, use_sg, NULL); +} + +/* + * Convenience function to perform a bulk write + */ +static int usbat_bulk_write(struct us_data *us, + void* buf, + unsigned int len, + int use_sg) +{ + if (len == 0) + return USB_STOR_XFER_GOOD; + + usb_stor_dbg(us, "len = %d\n", len); + return usb_stor_bulk_transfer_sg(us, us->send_bulk_pipe, buf, len, use_sg, NULL); +} + +/* + * Some USBAT-specific commands can only be executed over a command transport + * This transport allows one (len=8) or two (len=16) vendor-specific commands + * to be executed. + */ +static int usbat_execute_command(struct us_data *us, + unsigned char *commands, + unsigned int len) +{ + return usb_stor_ctrl_transfer(us, us->send_ctrl_pipe, + USBAT_CMD_EXEC_CMD, 0x40, 0, 0, + commands, len); +} + +/* + * Read the status register + */ +static int usbat_get_status(struct us_data *us, unsigned char *status) +{ + int rc; + rc = usbat_read(us, USBAT_ATA, USBAT_ATA_STATUS, status); + + usb_stor_dbg(us, "0x%02X\n", *status); + return rc; +} + +/* + * Check the device status + */ +static int usbat_check_status(struct us_data *us) +{ + unsigned char *reply = us->iobuf; + int rc; + + rc = usbat_get_status(us, reply); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_FAILED; + + /* error/check condition (0x51 is ok) */ + if (*reply & 0x01 && *reply != 0x51) + return USB_STOR_TRANSPORT_FAILED; + + /* device fault */ + if (*reply & 0x20) + return USB_STOR_TRANSPORT_FAILED; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Stores critical information in internal registers in preparation for the execution + * of a conditional usbat_read_blocks or usbat_write_blocks call. + */ +static int usbat_set_shuttle_features(struct us_data *us, + unsigned char external_trigger, + unsigned char epp_control, + unsigned char mask_byte, + unsigned char test_pattern, + unsigned char subcountH, + unsigned char subcountL) +{ + unsigned char *command = us->iobuf; + + command[0] = 0x40; + command[1] = USBAT_CMD_SET_FEAT; + + /* + * The only bit relevant to ATA access is bit 6 + * which defines 8 bit data access (set) or 16 bit (unset) + */ + command[2] = epp_control; + + /* + * If FCQ is set in the qualifier (defined in R/W cmd), then bits U0, U1, + * ET1 and ET2 define an external event to be checked for on event of a + * _read_blocks or _write_blocks operation. The read/write will not take + * place unless the defined trigger signal is active. + */ + command[3] = external_trigger; + + /* + * The resultant byte of the mask operation (see mask_byte) is compared for + * equivalence with this test pattern. If equal, the read/write will take + * place. + */ + command[4] = test_pattern; + + /* + * This value is logically ANDed with the status register field specified + * in the read/write command. + */ + command[5] = mask_byte; + + /* + * If ALQ is set in the qualifier, this field contains the address of the + * registers where the byte count should be read for transferring the data. + * If ALQ is not set, then this field contains the number of bytes to be + * transferred. + */ + command[6] = subcountL; + command[7] = subcountH; + + return usbat_execute_command(us, command, 8); +} + +/* + * Block, waiting for an ATA device to become not busy or to report + * an error condition. + */ +static int usbat_wait_not_busy(struct us_data *us, int minutes) +{ + int i; + int result; + unsigned char *status = us->iobuf; + + /* Synchronizing cache on a CDR could take a heck of a long time, + * but probably not more than 10 minutes or so. On the other hand, + * doing a full blank on a CDRW at speed 1 will take about 75 + * minutes! + */ + + for (i=0; i<1200+minutes*60; i++) { + + result = usbat_get_status(us, status); + + if (result!=USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + if (*status & 0x01) { /* check condition */ + result = usbat_read(us, USBAT_ATA, 0x10, status); + return USB_STOR_TRANSPORT_FAILED; + } + if (*status & 0x20) /* device fault */ + return USB_STOR_TRANSPORT_FAILED; + + if ((*status & 0x80)==0x00) { /* not busy */ + usb_stor_dbg(us, "Waited not busy for %d steps\n", i); + return USB_STOR_TRANSPORT_GOOD; + } + + if (i<500) + msleep(10); /* 5 seconds */ + else if (i<700) + msleep(50); /* 10 seconds */ + else if (i<1200) + msleep(100); /* 50 seconds */ + else + msleep(1000); /* X minutes */ + } + + usb_stor_dbg(us, "Waited not busy for %d minutes, timing out\n", + minutes); + return USB_STOR_TRANSPORT_FAILED; +} + +/* + * Read block data from the data register + */ +static int usbat_read_block(struct us_data *us, + void* buf, + unsigned short len, + int use_sg) +{ + int result; + unsigned char *command = us->iobuf; + + if (!len) + return USB_STOR_TRANSPORT_GOOD; + + command[0] = 0xC0; + command[1] = USBAT_ATA | USBAT_CMD_READ_BLOCK; + command[2] = USBAT_ATA_DATA; + command[3] = 0; + command[4] = 0; + command[5] = 0; + command[6] = LSB_of(len); + command[7] = MSB_of(len); + + result = usbat_execute_command(us, command, 8); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + result = usbat_bulk_read(us, buf, len, use_sg); + return (result == USB_STOR_XFER_GOOD ? + USB_STOR_TRANSPORT_GOOD : USB_STOR_TRANSPORT_ERROR); +} + +/* + * Write block data via the data register + */ +static int usbat_write_block(struct us_data *us, + unsigned char access, + void* buf, + unsigned short len, + int minutes, + int use_sg) +{ + int result; + unsigned char *command = us->iobuf; + + if (!len) + return USB_STOR_TRANSPORT_GOOD; + + command[0] = 0x40; + command[1] = access | USBAT_CMD_WRITE_BLOCK; + command[2] = USBAT_ATA_DATA; + command[3] = 0; + command[4] = 0; + command[5] = 0; + command[6] = LSB_of(len); + command[7] = MSB_of(len); + + result = usbat_execute_command(us, command, 8); + + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + result = usbat_bulk_write(us, buf, len, use_sg); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return usbat_wait_not_busy(us, minutes); +} + +/* + * Process read and write requests + */ +static int usbat_hp8200e_rw_block_test(struct us_data *us, + unsigned char access, + unsigned char *registers, + unsigned char *data_out, + unsigned short num_registers, + unsigned char data_reg, + unsigned char status_reg, + unsigned char timeout, + unsigned char qualifier, + int direction, + void *buf, + unsigned short len, + int use_sg, + int minutes) +{ + int result; + unsigned int pipe = (direction == DMA_FROM_DEVICE) ? + us->recv_bulk_pipe : us->send_bulk_pipe; + + unsigned char *command = us->iobuf; + int i, j; + int cmdlen; + unsigned char *data = us->iobuf; + unsigned char *status = us->iobuf; + + BUG_ON(num_registers > US_IOBUF_SIZE/2); + + for (i=0; i<20; i++) { + + /* + * The first time we send the full command, which consists + * of downloading the SCSI command followed by downloading + * the data via a write-and-test. Any other time we only + * send the command to download the data -- the SCSI command + * is still 'active' in some sense in the device. + * + * We're only going to try sending the data 10 times. After + * that, we just return a failure. + */ + + if (i==0) { + cmdlen = 16; + /* + * Write to multiple registers + * Not really sure the 0x07, 0x17, 0xfc, 0xe7 is + * necessary here, but that's what came out of the + * trace every single time. + */ + command[0] = 0x40; + command[1] = access | USBAT_CMD_WRITE_REGS; + command[2] = 0x07; + command[3] = 0x17; + command[4] = 0xFC; + command[5] = 0xE7; + command[6] = LSB_of(num_registers*2); + command[7] = MSB_of(num_registers*2); + } else + cmdlen = 8; + + /* Conditionally read or write blocks */ + command[cmdlen-8] = (direction==DMA_TO_DEVICE ? 0x40 : 0xC0); + command[cmdlen-7] = access | + (direction==DMA_TO_DEVICE ? + USBAT_CMD_COND_WRITE_BLOCK : USBAT_CMD_COND_READ_BLOCK); + command[cmdlen-6] = data_reg; + command[cmdlen-5] = status_reg; + command[cmdlen-4] = timeout; + command[cmdlen-3] = qualifier; + command[cmdlen-2] = LSB_of(len); + command[cmdlen-1] = MSB_of(len); + + result = usbat_execute_command(us, command, cmdlen); + + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (i==0) { + + for (j=0; jsend_bulk_pipe) < 0) + return USB_STOR_TRANSPORT_ERROR; + } + + /* + * Read status: is the device angry, or just busy? + */ + + result = usbat_read(us, USBAT_ATA, + direction==DMA_TO_DEVICE ? + USBAT_ATA_STATUS : USBAT_ATA_ALTSTATUS, + status); + + if (result!=USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + if (*status & 0x01) /* check condition */ + return USB_STOR_TRANSPORT_FAILED; + if (*status & 0x20) /* device fault */ + return USB_STOR_TRANSPORT_FAILED; + + usb_stor_dbg(us, "Redoing %s\n", + direction == DMA_TO_DEVICE + ? "write" : "read"); + + } else if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + else + return usbat_wait_not_busy(us, minutes); + + } + + usb_stor_dbg(us, "Bummer! %s bulk data 20 times failed\n", + direction == DMA_TO_DEVICE ? "Writing" : "Reading"); + + return USB_STOR_TRANSPORT_FAILED; +} + +/* + * Write to multiple registers: + * Allows us to write specific data to any registers. The data to be written + * gets packed in this sequence: reg0, data0, reg1, data1, ..., regN, dataN + * which gets sent through bulk out. + * Not designed for large transfers of data! + */ +static int usbat_multiple_write(struct us_data *us, + unsigned char *registers, + unsigned char *data_out, + unsigned short num_registers) +{ + int i, result; + unsigned char *data = us->iobuf; + unsigned char *command = us->iobuf; + + BUG_ON(num_registers > US_IOBUF_SIZE/2); + + /* Write to multiple registers, ATA access */ + command[0] = 0x40; + command[1] = USBAT_ATA | USBAT_CMD_WRITE_REGS; + + /* No relevance */ + command[2] = 0; + command[3] = 0; + command[4] = 0; + command[5] = 0; + + /* Number of bytes to be transferred (incl. addresses and data) */ + command[6] = LSB_of(num_registers*2); + command[7] = MSB_of(num_registers*2); + + /* The setup command */ + result = usbat_execute_command(us, command, 8); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* Create the reg/data, reg/data sequence */ + for (i=0; iiobuf; + + command[0] = 0xC0; + command[1] = USBAT_ATA | USBAT_CMD_COND_READ_BLOCK; + command[2] = USBAT_ATA_DATA; + command[3] = USBAT_ATA_STATUS; + command[4] = 0xFD; /* Timeout (ms); */ + command[5] = USBAT_QUAL_FCQ; + command[6] = LSB_of(len); + command[7] = MSB_of(len); + + /* Multiple block read setup command */ + result = usbat_execute_command(us, command, 8); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_FAILED; + + /* Read the blocks we just asked for */ + result = usbat_bulk_read(us, buffer, len, use_sg); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_FAILED; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Conditionally write blocks to device: + * Allows us to write blocks to a specific data register, based upon the + * condition that a status register can be successfully masked with a status + * qualifier. If this condition is not initially met, the write will wait + * up until a maximum amount of time has elapsed, as specified by timeout. + * The read will start when the condition is met, otherwise the command aborts. + * + * The qualifier defined here is not the value that is masked, it defines + * conditions for the write to take place. The actual masked qualifier (and + * other related details) are defined beforehand with _set_shuttle_features(). + */ +static int usbat_write_blocks(struct us_data *us, + void* buffer, + int len, + int use_sg) +{ + int result; + unsigned char *command = us->iobuf; + + command[0] = 0x40; + command[1] = USBAT_ATA | USBAT_CMD_COND_WRITE_BLOCK; + command[2] = USBAT_ATA_DATA; + command[3] = USBAT_ATA_STATUS; + command[4] = 0xFD; /* Timeout (ms) */ + command[5] = USBAT_QUAL_FCQ; + command[6] = LSB_of(len); + command[7] = MSB_of(len); + + /* Multiple block write setup command */ + result = usbat_execute_command(us, command, 8); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_FAILED; + + /* Write the data */ + result = usbat_bulk_write(us, buffer, len, use_sg); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_FAILED; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Read the User IO register + */ +static int usbat_read_user_io(struct us_data *us, unsigned char *data_flags) +{ + int result; + + result = usb_stor_ctrl_transfer(us, + us->recv_ctrl_pipe, + USBAT_CMD_UIO, + 0xC0, + 0, + 0, + data_flags, + USBAT_UIO_READ); + + usb_stor_dbg(us, "UIO register reads %02X\n", *data_flags); + + return result; +} + +/* + * Write to the User IO register + */ +static int usbat_write_user_io(struct us_data *us, + unsigned char enable_flags, + unsigned char data_flags) +{ + return usb_stor_ctrl_transfer(us, + us->send_ctrl_pipe, + USBAT_CMD_UIO, + 0x40, + short_pack(enable_flags, data_flags), + 0, + NULL, + USBAT_UIO_WRITE); +} + +/* + * Reset the device + * Often needed on media change. + */ +static int usbat_device_reset(struct us_data *us) +{ + int rc; + + /* + * Reset peripheral, enable peripheral control signals + * (bring reset signal up) + */ + rc = usbat_write_user_io(us, + USBAT_UIO_DRVRST | USBAT_UIO_OE1 | USBAT_UIO_OE0, + USBAT_UIO_EPAD | USBAT_UIO_1); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* + * Enable peripheral control signals + * (bring reset signal down) + */ + rc = usbat_write_user_io(us, + USBAT_UIO_OE1 | USBAT_UIO_OE0, + USBAT_UIO_EPAD | USBAT_UIO_1); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Enable card detect + */ +static int usbat_device_enable_cdt(struct us_data *us) +{ + int rc; + + /* Enable peripheral control signals and card detect */ + rc = usbat_write_user_io(us, + USBAT_UIO_ACKD | USBAT_UIO_OE1 | USBAT_UIO_OE0, + USBAT_UIO_EPAD | USBAT_UIO_1); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Determine if media is present. + */ +static int usbat_flash_check_media_present(struct us_data *us, + unsigned char *uio) +{ + if (*uio & USBAT_UIO_UI0) { + usb_stor_dbg(us, "no media detected\n"); + return USBAT_FLASH_MEDIA_NONE; + } + + return USBAT_FLASH_MEDIA_CF; +} + +/* + * Determine if media has changed since last operation + */ +static int usbat_flash_check_media_changed(struct us_data *us, + unsigned char *uio) +{ + if (*uio & USBAT_UIO_0) { + usb_stor_dbg(us, "media change detected\n"); + return USBAT_FLASH_MEDIA_CHANGED; + } + + return USBAT_FLASH_MEDIA_SAME; +} + +/* + * Check for media change / no media and handle the situation appropriately + */ +static int usbat_flash_check_media(struct us_data *us, + struct usbat_info *info) +{ + int rc; + unsigned char *uio = us->iobuf; + + rc = usbat_read_user_io(us, uio); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* Check for media existence */ + rc = usbat_flash_check_media_present(us, uio); + if (rc == USBAT_FLASH_MEDIA_NONE) { + info->sense_key = 0x02; + info->sense_asc = 0x3A; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; + } + + /* Check for media change */ + rc = usbat_flash_check_media_changed(us, uio); + if (rc == USBAT_FLASH_MEDIA_CHANGED) { + + /* Reset and re-enable card detect */ + rc = usbat_device_reset(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + rc = usbat_device_enable_cdt(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + msleep(50); + + rc = usbat_read_user_io(us, uio); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + info->sense_key = UNIT_ATTENTION; + info->sense_asc = 0x28; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Determine whether we are controlling a flash-based reader/writer, + * or a HP8200-based CD drive. + * Sets transport functions as appropriate. + */ +static int usbat_identify_device(struct us_data *us, + struct usbat_info *info) +{ + int rc; + unsigned char status; + + if (!us || !info) + return USB_STOR_TRANSPORT_ERROR; + + rc = usbat_device_reset(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + msleep(500); + + /* + * In attempt to distinguish between HP CDRW's and Flash readers, we now + * execute the IDENTIFY PACKET DEVICE command. On ATA devices (i.e. flash + * readers), this command should fail with error. On ATAPI devices (i.e. + * CDROM drives), it should succeed. + */ + rc = usbat_write(us, USBAT_ATA, USBAT_ATA_CMD, 0xA1); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + rc = usbat_get_status(us, &status); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* Check for error bit, or if the command 'fell through' */ + if (status == 0xA1 || !(status & 0x01)) { + /* Device is HP 8200 */ + usb_stor_dbg(us, "Detected HP8200 CDRW\n"); + info->devicetype = USBAT_DEV_HP8200; + } else { + /* Device is a CompactFlash reader/writer */ + usb_stor_dbg(us, "Detected Flash reader/writer\n"); + info->devicetype = USBAT_DEV_FLASH; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Set the transport function based on the device type + */ +static int usbat_set_transport(struct us_data *us, + struct usbat_info *info, + int devicetype) +{ + + if (!info->devicetype) + info->devicetype = devicetype; + + if (!info->devicetype) + usbat_identify_device(us, info); + + switch (info->devicetype) { + default: + return USB_STOR_TRANSPORT_ERROR; + + case USBAT_DEV_HP8200: + us->transport = usbat_hp8200e_transport; + break; + + case USBAT_DEV_FLASH: + us->transport = usbat_flash_transport; + break; + } + + return 0; +} + +/* + * Read the media capacity + */ +static int usbat_flash_get_sector_count(struct us_data *us, + struct usbat_info *info) +{ + unsigned char registers[3] = { + USBAT_ATA_SECCNT, + USBAT_ATA_DEVICE, + USBAT_ATA_CMD, + }; + unsigned char command[3] = { 0x01, 0xA0, 0xEC }; + unsigned char *reply; + unsigned char status; + int rc; + + if (!us || !info) + return USB_STOR_TRANSPORT_ERROR; + + reply = kmalloc(512, GFP_NOIO); + if (!reply) + return USB_STOR_TRANSPORT_ERROR; + + /* ATA command : IDENTIFY DEVICE */ + rc = usbat_multiple_write(us, registers, command, 3); + if (rc != USB_STOR_XFER_GOOD) { + usb_stor_dbg(us, "Gah! identify_device failed\n"); + rc = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + /* Read device status */ + if (usbat_get_status(us, &status) != USB_STOR_XFER_GOOD) { + rc = USB_STOR_TRANSPORT_ERROR; + goto leave; + } + + msleep(100); + + /* Read the device identification data */ + rc = usbat_read_block(us, reply, 512, 0); + if (rc != USB_STOR_TRANSPORT_GOOD) + goto leave; + + info->sectors = ((u32)(reply[117]) << 24) | + ((u32)(reply[116]) << 16) | + ((u32)(reply[115]) << 8) | + ((u32)(reply[114]) ); + + rc = USB_STOR_TRANSPORT_GOOD; + + leave: + kfree(reply); + return rc; +} + +/* + * Read data from device + */ +static int usbat_flash_read_data(struct us_data *us, + struct usbat_info *info, + u32 sector, + u32 sectors) +{ + unsigned char registers[7] = { + USBAT_ATA_FEATURES, + USBAT_ATA_SECCNT, + USBAT_ATA_SECNUM, + USBAT_ATA_LBA_ME, + USBAT_ATA_LBA_HI, + USBAT_ATA_DEVICE, + USBAT_ATA_STATUS, + }; + unsigned char command[7]; + unsigned char *buffer; + unsigned char thistime; + unsigned int totallen, alloclen; + int len, result; + unsigned int sg_offset = 0; + struct scatterlist *sg = NULL; + + result = usbat_flash_check_media(us, info); + if (result != USB_STOR_TRANSPORT_GOOD) + return result; + + /* + * we're working in LBA mode. according to the ATA spec, + * we can support up to 28-bit addressing. I don't know if Jumpshot + * supports beyond 24-bit addressing. It's kind of hard to test + * since it requires > 8GB CF card. + */ + + if (sector > 0x0FFFFFFF) + return USB_STOR_TRANSPORT_ERROR; + + totallen = sectors * info->ssize; + + /* + * Since we don't read more than 64 KB at a time, we have to create + * a bounce buffer and move the data a piece at a time between the + * bounce buffer and the actual transfer buffer. + */ + + alloclen = min(totallen, 65536u); + buffer = kmalloc(alloclen, GFP_NOIO); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + do { + /* + * loop, never allocate or transfer more than 64k at once + * (min(128k, 255*info->ssize) is the real limit) + */ + len = min(totallen, alloclen); + thistime = (len / info->ssize) & 0xff; + + /* ATA command 0x20 (READ SECTORS) */ + usbat_pack_ata_sector_cmd(command, thistime, sector, 0x20); + + /* Write/execute ATA read command */ + result = usbat_multiple_write(us, registers, command, 7); + if (result != USB_STOR_TRANSPORT_GOOD) + goto leave; + + /* Read the data we just requested */ + result = usbat_read_blocks(us, buffer, len, 0); + if (result != USB_STOR_TRANSPORT_GOOD) + goto leave; + + usb_stor_dbg(us, "%d bytes\n", len); + + /* Store the data in the transfer buffer */ + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &sg_offset, TO_XFER_BUF); + + sector += thistime; + totallen -= len; + } while (totallen > 0); + + kfree(buffer); + return USB_STOR_TRANSPORT_GOOD; + +leave: + kfree(buffer); + return USB_STOR_TRANSPORT_ERROR; +} + +/* + * Write data to device + */ +static int usbat_flash_write_data(struct us_data *us, + struct usbat_info *info, + u32 sector, + u32 sectors) +{ + unsigned char registers[7] = { + USBAT_ATA_FEATURES, + USBAT_ATA_SECCNT, + USBAT_ATA_SECNUM, + USBAT_ATA_LBA_ME, + USBAT_ATA_LBA_HI, + USBAT_ATA_DEVICE, + USBAT_ATA_STATUS, + }; + unsigned char command[7]; + unsigned char *buffer; + unsigned char thistime; + unsigned int totallen, alloclen; + int len, result; + unsigned int sg_offset = 0; + struct scatterlist *sg = NULL; + + result = usbat_flash_check_media(us, info); + if (result != USB_STOR_TRANSPORT_GOOD) + return result; + + /* + * we're working in LBA mode. according to the ATA spec, + * we can support up to 28-bit addressing. I don't know if the device + * supports beyond 24-bit addressing. It's kind of hard to test + * since it requires > 8GB media. + */ + + if (sector > 0x0FFFFFFF) + return USB_STOR_TRANSPORT_ERROR; + + totallen = sectors * info->ssize; + + /* + * Since we don't write more than 64 KB at a time, we have to create + * a bounce buffer and move the data a piece at a time between the + * bounce buffer and the actual transfer buffer. + */ + + alloclen = min(totallen, 65536u); + buffer = kmalloc(alloclen, GFP_NOIO); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + do { + /* + * loop, never allocate or transfer more than 64k at once + * (min(128k, 255*info->ssize) is the real limit) + */ + len = min(totallen, alloclen); + thistime = (len / info->ssize) & 0xff; + + /* Get the data from the transfer buffer */ + usb_stor_access_xfer_buf(buffer, len, us->srb, + &sg, &sg_offset, FROM_XFER_BUF); + + /* ATA command 0x30 (WRITE SECTORS) */ + usbat_pack_ata_sector_cmd(command, thistime, sector, 0x30); + + /* Write/execute ATA write command */ + result = usbat_multiple_write(us, registers, command, 7); + if (result != USB_STOR_TRANSPORT_GOOD) + goto leave; + + /* Write the data */ + result = usbat_write_blocks(us, buffer, len, 0); + if (result != USB_STOR_TRANSPORT_GOOD) + goto leave; + + sector += thistime; + totallen -= len; + } while (totallen > 0); + + kfree(buffer); + return result; + +leave: + kfree(buffer); + return USB_STOR_TRANSPORT_ERROR; +} + +/* + * Squeeze a potentially huge (> 65535 byte) read10 command into + * a little ( <= 65535 byte) ATAPI pipe + */ +static int usbat_hp8200e_handle_read10(struct us_data *us, + unsigned char *registers, + unsigned char *data, + struct scsi_cmnd *srb) +{ + int result = USB_STOR_TRANSPORT_GOOD; + unsigned char *buffer; + unsigned int len; + unsigned int sector; + unsigned int sg_offset = 0; + struct scatterlist *sg = NULL; + + usb_stor_dbg(us, "transfersize %d\n", srb->transfersize); + + if (scsi_bufflen(srb) < 0x10000) { + + result = usbat_hp8200e_rw_block_test(us, USBAT_ATA, + registers, data, 19, + USBAT_ATA_DATA, USBAT_ATA_STATUS, 0xFD, + (USBAT_QUAL_FCQ | USBAT_QUAL_ALQ), + DMA_FROM_DEVICE, + scsi_sglist(srb), + scsi_bufflen(srb), scsi_sg_count(srb), 1); + + return result; + } + + /* + * Since we're requesting more data than we can handle in + * a single read command (max is 64k-1), we will perform + * multiple reads, but each read must be in multiples of + * a sector. Luckily the sector size is in srb->transfersize + * (see linux/drivers/scsi/sr.c). + */ + + if (data[7+0] == GPCMD_READ_CD) { + len = short_pack(data[7+9], data[7+8]); + len <<= 16; + len |= data[7+7]; + usb_stor_dbg(us, "GPCMD_READ_CD: len %d\n", len); + srb->transfersize = scsi_bufflen(srb)/len; + } + + if (!srb->transfersize) { + srb->transfersize = 2048; /* A guess */ + usb_stor_dbg(us, "transfersize 0, forcing %d\n", + srb->transfersize); + } + + /* + * Since we only read in one block at a time, we have to create + * a bounce buffer and move the data a piece at a time between the + * bounce buffer and the actual transfer buffer. + */ + + len = (65535/srb->transfersize) * srb->transfersize; + usb_stor_dbg(us, "Max read is %d bytes\n", len); + len = min(len, scsi_bufflen(srb)); + buffer = kmalloc(len, GFP_NOIO); + if (buffer == NULL) /* bloody hell! */ + return USB_STOR_TRANSPORT_FAILED; + sector = short_pack(data[7+3], data[7+2]); + sector <<= 16; + sector |= short_pack(data[7+5], data[7+4]); + transferred = 0; + + while (transferred != scsi_bufflen(srb)) { + + if (len > scsi_bufflen(srb) - transferred) + len = scsi_bufflen(srb) - transferred; + + data[3] = len&0xFF; /* (cylL) = expected length (L) */ + data[4] = (len>>8)&0xFF; /* (cylH) = expected length (H) */ + + /* Fix up the SCSI command sector and num sectors */ + + data[7+2] = MSB_of(sector>>16); /* SCSI command sector */ + data[7+3] = LSB_of(sector>>16); + data[7+4] = MSB_of(sector&0xFFFF); + data[7+5] = LSB_of(sector&0xFFFF); + if (data[7+0] == GPCMD_READ_CD) + data[7+6] = 0; + data[7+7] = MSB_of(len / srb->transfersize); /* SCSI command */ + data[7+8] = LSB_of(len / srb->transfersize); /* num sectors */ + + result = usbat_hp8200e_rw_block_test(us, USBAT_ATA, + registers, data, 19, + USBAT_ATA_DATA, USBAT_ATA_STATUS, 0xFD, + (USBAT_QUAL_FCQ | USBAT_QUAL_ALQ), + DMA_FROM_DEVICE, + buffer, + len, 0, 1); + + if (result != USB_STOR_TRANSPORT_GOOD) + break; + + /* Store the data in the transfer buffer */ + usb_stor_access_xfer_buf(buffer, len, srb, + &sg, &sg_offset, TO_XFER_BUF); + + /* Update the amount transferred and the sector number */ + + transferred += len; + sector += len / srb->transfersize; + + } /* while transferred != scsi_bufflen(srb) */ + + kfree(buffer); + return result; +} + +static int usbat_select_and_test_registers(struct us_data *us) +{ + int selector; + unsigned char *status = us->iobuf; + + /* try device = master, then device = slave. */ + for (selector = 0xA0; selector <= 0xB0; selector += 0x10) { + if (usbat_write(us, USBAT_ATA, USBAT_ATA_DEVICE, selector) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (usbat_read(us, USBAT_ATA, USBAT_ATA_STATUS, status) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (usbat_read(us, USBAT_ATA, USBAT_ATA_DEVICE, status) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (usbat_read(us, USBAT_ATA, USBAT_ATA_LBA_ME, status) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (usbat_read(us, USBAT_ATA, USBAT_ATA_LBA_HI, status) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (usbat_write(us, USBAT_ATA, USBAT_ATA_LBA_ME, 0x55) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (usbat_write(us, USBAT_ATA, USBAT_ATA_LBA_HI, 0xAA) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (usbat_read(us, USBAT_ATA, USBAT_ATA_LBA_ME, status) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (usbat_read(us, USBAT_ATA, USBAT_ATA_LBA_ME, status) != + USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Initialize the USBAT processor and the storage device + */ +static int init_usbat(struct us_data *us, int devicetype) +{ + int rc; + struct usbat_info *info; + unsigned char subcountH = USBAT_ATA_LBA_HI; + unsigned char subcountL = USBAT_ATA_LBA_ME; + unsigned char *status = us->iobuf; + + us->extra = kzalloc(sizeof(struct usbat_info), GFP_NOIO); + if (!us->extra) + return 1; + + info = (struct usbat_info *) (us->extra); + + /* Enable peripheral control signals */ + rc = usbat_write_user_io(us, + USBAT_UIO_OE1 | USBAT_UIO_OE0, + USBAT_UIO_EPAD | USBAT_UIO_1); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "INIT 1\n"); + + msleep(2000); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + usb_stor_dbg(us, "INIT 2\n"); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "INIT 3\n"); + + rc = usbat_select_and_test_registers(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + usb_stor_dbg(us, "INIT 4\n"); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "INIT 5\n"); + + /* Enable peripheral control signals and card detect */ + rc = usbat_device_enable_cdt(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + usb_stor_dbg(us, "INIT 6\n"); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "INIT 7\n"); + + msleep(1400); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "INIT 8\n"); + + rc = usbat_select_and_test_registers(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + usb_stor_dbg(us, "INIT 9\n"); + + /* At this point, we need to detect which device we are using */ + if (usbat_set_transport(us, info, devicetype)) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "INIT 10\n"); + + if (usbat_get_device_type(us) == USBAT_DEV_FLASH) { + subcountH = 0x02; + subcountL = 0x00; + } + rc = usbat_set_shuttle_features(us, (USBAT_FEAT_ETEN | USBAT_FEAT_ET2 | USBAT_FEAT_ET1), + 0x00, 0x88, 0x08, subcountH, subcountL); + if (rc != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + usb_stor_dbg(us, "INIT 11\n"); + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * Transport for the HP 8200e + */ +static int usbat_hp8200e_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int result; + unsigned char *status = us->iobuf; + unsigned char registers[32]; + unsigned char data[32]; + unsigned int len; + int i; + + len = scsi_bufflen(srb); + + /* Send A0 (ATA PACKET COMMAND). + Note: I guess we're never going to get any of the ATA + commands... just ATA Packet Commands. + */ + + registers[0] = USBAT_ATA_FEATURES; + registers[1] = USBAT_ATA_SECCNT; + registers[2] = USBAT_ATA_SECNUM; + registers[3] = USBAT_ATA_LBA_ME; + registers[4] = USBAT_ATA_LBA_HI; + registers[5] = USBAT_ATA_DEVICE; + registers[6] = USBAT_ATA_CMD; + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = len&0xFF; /* (cylL) = expected length (L) */ + data[4] = (len>>8)&0xFF; /* (cylH) = expected length (H) */ + data[5] = 0xB0; /* (device sel) = slave */ + data[6] = 0xA0; /* (command) = ATA PACKET COMMAND */ + + for (i=7; i<19; i++) { + registers[i] = 0x10; + data[i] = (i-7 >= srb->cmd_len) ? 0 : srb->cmnd[i-7]; + } + + result = usbat_get_status(us, status); + usb_stor_dbg(us, "Status = %02X\n", *status); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + if (srb->cmnd[0] == TEST_UNIT_READY) + transferred = 0; + + if (srb->sc_data_direction == DMA_TO_DEVICE) { + + result = usbat_hp8200e_rw_block_test(us, USBAT_ATA, + registers, data, 19, + USBAT_ATA_DATA, USBAT_ATA_STATUS, 0xFD, + (USBAT_QUAL_FCQ | USBAT_QUAL_ALQ), + DMA_TO_DEVICE, + scsi_sglist(srb), + len, scsi_sg_count(srb), 10); + + if (result == USB_STOR_TRANSPORT_GOOD) { + transferred += len; + usb_stor_dbg(us, "Wrote %08X bytes\n", transferred); + } + + return result; + + } else if (srb->cmnd[0] == READ_10 || + srb->cmnd[0] == GPCMD_READ_CD) { + + return usbat_hp8200e_handle_read10(us, registers, data, srb); + + } + + if (len > 0xFFFF) { + usb_stor_dbg(us, "Error: len = %08X... what do I do now?\n", + len); + return USB_STOR_TRANSPORT_ERROR; + } + + result = usbat_multiple_write(us, registers, data, 7); + + if (result != USB_STOR_TRANSPORT_GOOD) + return result; + + /* + * Write the 12-byte command header. + * + * If the command is BLANK then set the timer for 75 minutes. + * Otherwise set it for 10 minutes. + * + * NOTE: THE 8200 DOCUMENTATION STATES THAT BLANKING A CDRW + * AT SPEED 4 IS UNRELIABLE!!! + */ + + result = usbat_write_block(us, USBAT_ATA, srb->cmnd, 12, + srb->cmnd[0] == GPCMD_BLANK ? 75 : 10, 0); + + if (result != USB_STOR_TRANSPORT_GOOD) + return result; + + /* If there is response data to be read in then do it here. */ + + if (len != 0 && (srb->sc_data_direction == DMA_FROM_DEVICE)) { + + /* How many bytes to read in? Check cylL register */ + + if (usbat_read(us, USBAT_ATA, USBAT_ATA_LBA_ME, status) != + USB_STOR_XFER_GOOD) { + return USB_STOR_TRANSPORT_ERROR; + } + + if (len > 0xFF) { /* need to read cylH also */ + len = *status; + if (usbat_read(us, USBAT_ATA, USBAT_ATA_LBA_HI, status) != + USB_STOR_XFER_GOOD) { + return USB_STOR_TRANSPORT_ERROR; + } + len += ((unsigned int) *status)<<8; + } + else + len = *status; + + + result = usbat_read_block(us, scsi_sglist(srb), len, + scsi_sg_count(srb)); + } + + return result; +} + +/* + * Transport for USBAT02-based CompactFlash and similar storage devices + */ +static int usbat_flash_transport(struct scsi_cmnd * srb, struct us_data *us) +{ + int rc; + struct usbat_info *info = (struct usbat_info *) (us->extra); + unsigned long block, blocks; + unsigned char *ptr = us->iobuf; + static unsigned char inquiry_response[36] = { + 0x00, 0x80, 0x00, 0x01, 0x1F, 0x00, 0x00, 0x00 + }; + + if (srb->cmnd[0] == INQUIRY) { + usb_stor_dbg(us, "INQUIRY - Returning bogus response\n"); + memcpy(ptr, inquiry_response, sizeof(inquiry_response)); + fill_inquiry_response(us, ptr, 36); + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == READ_CAPACITY) { + rc = usbat_flash_check_media(us, info); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + rc = usbat_flash_get_sector_count(us, info); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + /* hard coded 512 byte sectors as per ATA spec */ + info->ssize = 0x200; + usb_stor_dbg(us, "READ_CAPACITY: %ld sectors, %ld bytes per sector\n", + info->sectors, info->ssize); + + /* + * build the reply + * note: must return the sector number of the last sector, + * *not* the total number of sectors + */ + ((__be32 *) ptr)[0] = cpu_to_be32(info->sectors - 1); + ((__be32 *) ptr)[1] = cpu_to_be32(info->ssize); + usb_stor_set_xfer_buf(ptr, 8, srb); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == MODE_SELECT_10) { + usb_stor_dbg(us, "Gah! MODE_SELECT_10\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + if (srb->cmnd[0] == READ_10) { + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[7]) << 8) | ((u32)(srb->cmnd[8])); + + usb_stor_dbg(us, "READ_10: read block 0x%04lx count %ld\n", + block, blocks); + return usbat_flash_read_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == READ_12) { + /* + * I don't think we'll ever see a READ_12 but support it anyway + */ + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[6]) << 24) | ((u32)(srb->cmnd[7]) << 16) | + ((u32)(srb->cmnd[8]) << 8) | ((u32)(srb->cmnd[9])); + + usb_stor_dbg(us, "READ_12: read block 0x%04lx count %ld\n", + block, blocks); + return usbat_flash_read_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == WRITE_10) { + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[7]) << 8) | ((u32)(srb->cmnd[8])); + + usb_stor_dbg(us, "WRITE_10: write block 0x%04lx count %ld\n", + block, blocks); + return usbat_flash_write_data(us, info, block, blocks); + } + + if (srb->cmnd[0] == WRITE_12) { + /* + * I don't think we'll ever see a WRITE_12 but support it anyway + */ + block = ((u32)(srb->cmnd[2]) << 24) | ((u32)(srb->cmnd[3]) << 16) | + ((u32)(srb->cmnd[4]) << 8) | ((u32)(srb->cmnd[5])); + + blocks = ((u32)(srb->cmnd[6]) << 24) | ((u32)(srb->cmnd[7]) << 16) | + ((u32)(srb->cmnd[8]) << 8) | ((u32)(srb->cmnd[9])); + + usb_stor_dbg(us, "WRITE_12: write block 0x%04lx count %ld\n", + block, blocks); + return usbat_flash_write_data(us, info, block, blocks); + } + + + if (srb->cmnd[0] == TEST_UNIT_READY) { + usb_stor_dbg(us, "TEST_UNIT_READY\n"); + + rc = usbat_flash_check_media(us, info); + if (rc != USB_STOR_TRANSPORT_GOOD) + return rc; + + return usbat_check_status(us); + } + + if (srb->cmnd[0] == REQUEST_SENSE) { + usb_stor_dbg(us, "REQUEST_SENSE\n"); + + memset(ptr, 0, 18); + ptr[0] = 0xF0; + ptr[2] = info->sense_key; + ptr[7] = 11; + ptr[12] = info->sense_asc; + ptr[13] = info->sense_ascq; + usb_stor_set_xfer_buf(ptr, 18, srb); + + return USB_STOR_TRANSPORT_GOOD; + } + + if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) { + /* + * sure. whatever. not like we can stop the user from popping + * the media out of the device (no locking doors, etc) + */ + return USB_STOR_TRANSPORT_GOOD; + } + + usb_stor_dbg(us, "Gah! Unknown command: %d (0x%x)\n", + srb->cmnd[0], srb->cmnd[0]); + info->sense_key = 0x05; + info->sense_asc = 0x20; + info->sense_ascq = 0x00; + return USB_STOR_TRANSPORT_FAILED; +} + +static int init_usbat_cd(struct us_data *us) +{ + return init_usbat(us, USBAT_DEV_HP8200); +} + +static int init_usbat_flash(struct us_data *us) +{ + return init_usbat(us, USBAT_DEV_FLASH); +} + +static int usbat_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_data *us; + int result; + + result = usb_stor_probe1(&us, intf, id, + (id - usbat_usb_ids) + usbat_unusual_dev_list); + if (result) + return result; + + /* The actual transport will be determined later by the + * initialization routine; this is just a placeholder. + */ + us->transport_name = "Shuttle USBAT"; + us->transport = usbat_flash_transport; + us->transport_reset = usb_stor_CB_reset; + us->max_lun = 0; + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver usbat_driver = { + .name = "ums-usbat", + .probe = usbat_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = usbat_usb_ids, + .soft_unbind = 1, + .no_dynamic_id = 1, +}; + +module_usb_driver(usbat_driver); diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c new file mode 100644 index 00000000..2ea657be --- /dev/null +++ b/drivers/usb/storage/sierra_ms.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "scsiglue.h" +#include "sierra_ms.h" +#include "debug.h" + +#define SWIMS_USB_REQUEST_SetSwocMode 0x0B +#define SWIMS_USB_REQUEST_GetSwocInfo 0x0A +#define SWIMS_USB_INDEX_SetMode 0x0000 +#define SWIMS_SET_MODE_Modem 0x0001 + +#define TRU_NORMAL 0x01 +#define TRU_FORCE_MS 0x02 +#define TRU_FORCE_MODEM 0x03 + +static unsigned int swi_tru_install = 1; +module_param(swi_tru_install, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(swi_tru_install, "TRU-Install mode (1=Full Logic (def)," + " 2=Force CD-Rom, 3=Force Modem)"); + +struct swoc_info { + __u8 rev; + __u8 reserved[8]; + __u16 LinuxSKU; + __u16 LinuxVer; + __u8 reserved2[47]; +} __attribute__((__packed__)); + +static bool containsFullLinuxPackage(struct swoc_info *swocInfo) +{ + if ((swocInfo->LinuxSKU >= 0x2100 && swocInfo->LinuxSKU <= 0x2FFF) || + (swocInfo->LinuxSKU >= 0x7100 && swocInfo->LinuxSKU <= 0x7FFF)) + return true; + else + return false; +} + +static int sierra_set_ms_mode(struct usb_device *udev, __u16 eSWocMode) +{ + int result; + dev_dbg(&udev->dev, "SWIMS: %s", "DEVICE MODE SWITCH\n"); + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SWIMS_USB_REQUEST_SetSwocMode, /* __u8 request */ + USB_TYPE_VENDOR | USB_DIR_OUT, /* __u8 request type */ + eSWocMode, /* __u16 value */ + 0x0000, /* __u16 index */ + NULL, /* void *data */ + 0, /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + return result; +} + + +static int sierra_get_swoc_info(struct usb_device *udev, + struct swoc_info *swocInfo) +{ + int result; + + dev_dbg(&udev->dev, "SWIMS: Attempting to get TRU-Install info\n"); + + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + SWIMS_USB_REQUEST_GetSwocInfo, /* __u8 request */ + USB_TYPE_VENDOR | USB_DIR_IN, /* __u8 request type */ + 0, /* __u16 value */ + 0, /* __u16 index */ + (void *) swocInfo, /* void *data */ + sizeof(struct swoc_info), /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); + swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); + return result; +} + +static void debug_swoc(const struct device *dev, struct swoc_info *swocInfo) +{ + dev_dbg(dev, "SWIMS: SWoC Rev: %02d\n", swocInfo->rev); + dev_dbg(dev, "SWIMS: Linux SKU: %04X\n", swocInfo->LinuxSKU); + dev_dbg(dev, "SWIMS: Linux Version: %04X\n", swocInfo->LinuxVer); +} + + +static ssize_t show_truinst(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct swoc_info *swocInfo; + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + int result; + if (swi_tru_install == TRU_FORCE_MS) { + result = snprintf(buf, PAGE_SIZE, "Forced Mass Storage\n"); + } else { + swocInfo = kmalloc(sizeof(struct swoc_info), GFP_KERNEL); + if (!swocInfo) { + snprintf(buf, PAGE_SIZE, "Error\n"); + return -ENOMEM; + } + result = sierra_get_swoc_info(udev, swocInfo); + if (result < 0) { + dev_dbg(dev, "SWIMS: failed SWoC query\n"); + kfree(swocInfo); + snprintf(buf, PAGE_SIZE, "Error\n"); + return -EIO; + } + debug_swoc(dev, swocInfo); + result = snprintf(buf, PAGE_SIZE, + "REV=%02d SKU=%04X VER=%04X\n", + swocInfo->rev, + swocInfo->LinuxSKU, + swocInfo->LinuxVer); + kfree(swocInfo); + } + return result; +} +static DEVICE_ATTR(truinst, S_IRUGO, show_truinst, NULL); + +int sierra_ms_init(struct us_data *us) +{ + int result, retries; + struct swoc_info *swocInfo; + struct usb_device *udev; + struct Scsi_Host *sh; + + retries = 3; + result = 0; + udev = us->pusb_dev; + + sh = us_to_host(us); + scsi_get_host_dev(sh); + + /* Force Modem mode */ + if (swi_tru_install == TRU_FORCE_MODEM) { + usb_stor_dbg(us, "SWIMS: Forcing Modem Mode\n"); + result = sierra_set_ms_mode(udev, SWIMS_SET_MODE_Modem); + if (result < 0) + usb_stor_dbg(us, "SWIMS: Failed to switch to modem mode\n"); + return -EIO; + } + /* Force Mass Storage mode (keep CD-Rom) */ + else if (swi_tru_install == TRU_FORCE_MS) { + usb_stor_dbg(us, "SWIMS: Forcing Mass Storage Mode\n"); + goto complete; + } + /* Normal TRU-Install Logic */ + else { + usb_stor_dbg(us, "SWIMS: Normal SWoC Logic\n"); + + swocInfo = kmalloc(sizeof(struct swoc_info), + GFP_KERNEL); + if (!swocInfo) + return -ENOMEM; + + retries = 3; + do { + retries--; + result = sierra_get_swoc_info(udev, swocInfo); + if (result < 0) { + usb_stor_dbg(us, "SWIMS: Failed SWoC query\n"); + schedule_timeout_uninterruptible(2*HZ); + } + } while (retries && result < 0); + + if (result < 0) { + usb_stor_dbg(us, "SWIMS: Completely failed SWoC query\n"); + kfree(swocInfo); + return -EIO; + } + + debug_swoc(&us->pusb_dev->dev, swocInfo); + + /* If there is not Linux software on the TRU-Install device + * then switch to modem mode + */ + if (!containsFullLinuxPackage(swocInfo)) { + usb_stor_dbg(us, "SWIMS: Switching to Modem Mode\n"); + result = sierra_set_ms_mode(udev, + SWIMS_SET_MODE_Modem); + if (result < 0) + usb_stor_dbg(us, "SWIMS: Failed to switch modem\n"); + kfree(swocInfo); + return -EIO; + } + kfree(swocInfo); + } +complete: + result = device_create_file(&us->pusb_intf->dev, &dev_attr_truinst); + + return 0; +} + diff --git a/drivers/usb/storage/sierra_ms.h b/drivers/usb/storage/sierra_ms.h new file mode 100644 index 00000000..bb48634a --- /dev/null +++ b/drivers/usb/storage/sierra_ms.h @@ -0,0 +1,4 @@ +#ifndef _SIERRA_MS_H_ +#define _SIERRA_MS_H_ +extern int sierra_ms_init(struct us_data *us); +#endif diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c new file mode 100644 index 00000000..b1d815eb --- /dev/null +++ b/drivers/usb/storage/transport.c @@ -0,0 +1,1374 @@ +/* Driver for USB Mass Storage compliant devices + * + * Current development and maintenance by: + * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Developed with the assistance of: + * (c) 2000 David L. Brown, Jr. (usb-storage@davidb.org) + * (c) 2000 Stephen J. Gowdy (SGowdy@lbl.gov) + * (c) 2002 Alan Stern + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "scsiglue.h" +#include "debug.h" + +#include +#include "../../scsi/sd.h" + + +/*********************************************************************** + * Data transfer routines + ***********************************************************************/ + +/* + * This is subtle, so pay attention: + * --------------------------------- + * We're very concerned about races with a command abort. Hanging this code + * is a sure fire way to hang the kernel. (Note that this discussion applies + * only to transactions resulting from a scsi queued-command, since only + * these transactions are subject to a scsi abort. Other transactions, such + * as those occurring during device-specific initialization, must be handled + * by a separate code path.) + * + * The abort function (usb_storage_command_abort() in scsiglue.c) first + * sets the machine state and the ABORTING bit in us->dflags to prevent + * new URBs from being submitted. It then calls usb_stor_stop_transport() + * below, which atomically tests-and-clears the URB_ACTIVE bit in us->dflags + * to see if the current_urb needs to be stopped. Likewise, the SG_ACTIVE + * bit is tested to see if the current_sg scatter-gather request needs to be + * stopped. The timeout callback routine does much the same thing. + * + * When a disconnect occurs, the DISCONNECTING bit in us->dflags is set to + * prevent new URBs from being submitted, and usb_stor_stop_transport() is + * called to stop any ongoing requests. + * + * The submit function first verifies that the submitting is allowed + * (neither ABORTING nor DISCONNECTING bits are set) and that the submit + * completes without errors, and only then sets the URB_ACTIVE bit. This + * prevents the stop_transport() function from trying to cancel the URB + * while the submit call is underway. Next, the submit function must test + * the flags to see if an abort or disconnect occurred during the submission + * or before the URB_ACTIVE bit was set. If so, it's essential to cancel + * the URB if it hasn't been cancelled already (i.e., if the URB_ACTIVE bit + * is still set). Either way, the function must then wait for the URB to + * finish. Note that the URB can still be in progress even after a call to + * usb_unlink_urb() returns. + * + * The idea is that (1) once the ABORTING or DISCONNECTING bit is set, + * either the stop_transport() function or the submitting function + * is guaranteed to call usb_unlink_urb() for an active URB, + * and (2) test_and_clear_bit() prevents usb_unlink_urb() from being + * called more than once or from being called during usb_submit_urb(). + */ + +/* This is the completion handler which will wake us up when an URB + * completes. + */ +static void usb_stor_blocking_completion(struct urb *urb) +{ + struct completion *urb_done_ptr = urb->context; + + complete(urb_done_ptr); +} + +/* This is the common part of the URB message submission code + * + * All URBs from the usb-storage driver involved in handling a queued scsi + * command _must_ pass through this function (or something like it) for the + * abort mechanisms to work properly. + */ +static int usb_stor_msg_common(struct us_data *us, int timeout) +{ + struct completion urb_done; + long timeleft; + int status; + + /* don't submit URBs during abort processing */ + if (test_bit(US_FLIDX_ABORTING, &us->dflags)) + return -EIO; + + /* set up data structures for the wakeup system */ + init_completion(&urb_done); + + /* fill the common fields in the URB */ + us->current_urb->context = &urb_done; + us->current_urb->transfer_flags = 0; + + /* we assume that if transfer_buffer isn't us->iobuf then it + * hasn't been mapped for DMA. Yes, this is clunky, but it's + * easier than always having the caller tell us whether the + * transfer buffer has already been mapped. */ + if (us->current_urb->transfer_buffer == us->iobuf) + us->current_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + us->current_urb->transfer_dma = us->iobuf_dma; + + /* submit the URB */ + status = usb_submit_urb(us->current_urb, GFP_NOIO); + if (status) { + /* something went wrong */ + return status; + } + + /* since the URB has been submitted successfully, it's now okay + * to cancel it */ + set_bit(US_FLIDX_URB_ACTIVE, &us->dflags); + + /* did an abort occur during the submission? */ + if (test_bit(US_FLIDX_ABORTING, &us->dflags)) { + + /* cancel the URB, if it hasn't been cancelled already */ + if (test_and_clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags)) { + usb_stor_dbg(us, "-- cancelling URB\n"); + usb_unlink_urb(us->current_urb); + } + } + + /* wait for the completion of the URB */ + timeleft = wait_for_completion_interruptible_timeout( + &urb_done, timeout ? : MAX_SCHEDULE_TIMEOUT); + + clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags); + + if (timeleft <= 0) { + usb_stor_dbg(us, "%s -- cancelling URB\n", + timeleft == 0 ? "Timeout" : "Signal"); + usb_kill_urb(us->current_urb); + } + + /* return the URB status */ + return us->current_urb->status; +} + +/* + * Transfer one control message, with timeouts, and allowing early + * termination. Return codes are usual -Exxx, *not* USB_STOR_XFER_xxx. + */ +int usb_stor_control_msg(struct us_data *us, unsigned int pipe, + u8 request, u8 requesttype, u16 value, u16 index, + void *data, u16 size, int timeout) +{ + int status; + + usb_stor_dbg(us, "rq=%02x rqtype=%02x value=%04x index=%02x len=%u\n", + request, requesttype, value, index, size); + + /* fill in the devrequest structure */ + us->cr->bRequestType = requesttype; + us->cr->bRequest = request; + us->cr->wValue = cpu_to_le16(value); + us->cr->wIndex = cpu_to_le16(index); + us->cr->wLength = cpu_to_le16(size); + + /* fill and submit the URB */ + usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe, + (unsigned char*) us->cr, data, size, + usb_stor_blocking_completion, NULL); + status = usb_stor_msg_common(us, timeout); + + /* return the actual length of the data transferred if no error */ + if (status == 0) + status = us->current_urb->actual_length; + return status; +} +EXPORT_SYMBOL_GPL(usb_stor_control_msg); + +/* This is a version of usb_clear_halt() that allows early termination and + * doesn't read the status from the device -- this is because some devices + * crash their internal firmware when the status is requested after a halt. + * + * A definitive list of these 'bad' devices is too difficult to maintain or + * make complete enough to be useful. This problem was first observed on the + * Hagiwara FlashGate DUAL unit. However, bus traces reveal that neither + * MacOS nor Windows checks the status after clearing a halt. + * + * Since many vendors in this space limit their testing to interoperability + * with these two OSes, specification violations like this one are common. + */ +int usb_stor_clear_halt(struct us_data *us, unsigned int pipe) +{ + int result; + int endp = usb_pipeendpoint(pipe); + + if (usb_pipein (pipe)) + endp |= USB_DIR_IN; + + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, endp, + NULL, 0, 3*HZ); + + if (result >= 0) + usb_reset_endpoint(us->pusb_dev, endp); + + usb_stor_dbg(us, "result = %d\n", result); + return result; +} +EXPORT_SYMBOL_GPL(usb_stor_clear_halt); + + +/* + * Interpret the results of a URB transfer + * + * This function prints appropriate debugging messages, clears halts on + * non-control endpoints, and translates the status to the corresponding + * USB_STOR_XFER_xxx return code. + */ +static int interpret_urb_result(struct us_data *us, unsigned int pipe, + unsigned int length, int result, unsigned int partial) +{ + usb_stor_dbg(us, "Status code %d; transferred %u/%u\n", + result, partial, length); + switch (result) { + + /* no error code; did we send all the data? */ + case 0: + if (partial != length) { + usb_stor_dbg(us, "-- short transfer\n"); + return USB_STOR_XFER_SHORT; + } + + usb_stor_dbg(us, "-- transfer complete\n"); + return USB_STOR_XFER_GOOD; + + /* stalled */ + case -EPIPE: + /* for control endpoints, (used by CB[I]) a stall indicates + * a failed command */ + if (usb_pipecontrol(pipe)) { + usb_stor_dbg(us, "-- stall on control pipe\n"); + return USB_STOR_XFER_STALLED; + } + + /* for other sorts of endpoint, clear the stall */ + usb_stor_dbg(us, "clearing endpoint halt for pipe 0x%x\n", + pipe); + if (usb_stor_clear_halt(us, pipe) < 0) + return USB_STOR_XFER_ERROR; + return USB_STOR_XFER_STALLED; + + /* babble - the device tried to send more than we wanted to read */ + case -EOVERFLOW: + usb_stor_dbg(us, "-- babble\n"); + return USB_STOR_XFER_LONG; + + /* the transfer was cancelled by abort, disconnect, or timeout */ + case -ECONNRESET: + usb_stor_dbg(us, "-- transfer cancelled\n"); + return USB_STOR_XFER_ERROR; + + /* short scatter-gather read transfer */ + case -EREMOTEIO: + usb_stor_dbg(us, "-- short read transfer\n"); + return USB_STOR_XFER_SHORT; + + /* abort or disconnect in progress */ + case -EIO: + usb_stor_dbg(us, "-- abort or disconnect in progress\n"); + return USB_STOR_XFER_ERROR; + + /* the catch-all error case */ + default: + usb_stor_dbg(us, "-- unknown error\n"); + return USB_STOR_XFER_ERROR; + } +} + +/* + * Transfer one control message, without timeouts, but allowing early + * termination. Return codes are USB_STOR_XFER_xxx. + */ +int usb_stor_ctrl_transfer(struct us_data *us, unsigned int pipe, + u8 request, u8 requesttype, u16 value, u16 index, + void *data, u16 size) +{ + int result; + + usb_stor_dbg(us, "rq=%02x rqtype=%02x value=%04x index=%02x len=%u\n", + request, requesttype, value, index, size); + + /* fill in the devrequest structure */ + us->cr->bRequestType = requesttype; + us->cr->bRequest = request; + us->cr->wValue = cpu_to_le16(value); + us->cr->wIndex = cpu_to_le16(index); + us->cr->wLength = cpu_to_le16(size); + + /* fill and submit the URB */ + usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe, + (unsigned char*) us->cr, data, size, + usb_stor_blocking_completion, NULL); + result = usb_stor_msg_common(us, 0); + + return interpret_urb_result(us, pipe, size, result, + us->current_urb->actual_length); +} +EXPORT_SYMBOL_GPL(usb_stor_ctrl_transfer); + +/* + * Receive one interrupt buffer, without timeouts, but allowing early + * termination. Return codes are USB_STOR_XFER_xxx. + * + * This routine always uses us->recv_intr_pipe as the pipe and + * us->ep_bInterval as the interrupt interval. + */ +static int usb_stor_intr_transfer(struct us_data *us, void *buf, + unsigned int length) +{ + int result; + unsigned int pipe = us->recv_intr_pipe; + unsigned int maxp; + + usb_stor_dbg(us, "xfer %u bytes\n", length); + + /* calculate the max packet size */ + maxp = usb_maxpacket(us->pusb_dev, pipe, usb_pipeout(pipe)); + if (maxp > length) + maxp = length; + + /* fill and submit the URB */ + usb_fill_int_urb(us->current_urb, us->pusb_dev, pipe, buf, + maxp, usb_stor_blocking_completion, NULL, + us->ep_bInterval); + result = usb_stor_msg_common(us, 0); + + return interpret_urb_result(us, pipe, length, result, + us->current_urb->actual_length); +} + +/* + * Transfer one buffer via bulk pipe, without timeouts, but allowing early + * termination. Return codes are USB_STOR_XFER_xxx. If the bulk pipe + * stalls during the transfer, the halt is automatically cleared. + */ +int usb_stor_bulk_transfer_buf(struct us_data *us, unsigned int pipe, + void *buf, unsigned int length, unsigned int *act_len) +{ + int result; + + usb_stor_dbg(us, "xfer %u bytes\n", length); + + /* fill and submit the URB */ + usb_fill_bulk_urb(us->current_urb, us->pusb_dev, pipe, buf, length, + usb_stor_blocking_completion, NULL); + result = usb_stor_msg_common(us, 0); + + /* store the actual length of the data transferred */ + if (act_len) + *act_len = us->current_urb->actual_length; + return interpret_urb_result(us, pipe, length, result, + us->current_urb->actual_length); +} +EXPORT_SYMBOL_GPL(usb_stor_bulk_transfer_buf); + +/* + * Transfer a scatter-gather list via bulk transfer + * + * This function does basically the same thing as usb_stor_bulk_transfer_buf() + * above, but it uses the usbcore scatter-gather library. + */ +static int usb_stor_bulk_transfer_sglist(struct us_data *us, unsigned int pipe, + struct scatterlist *sg, int num_sg, unsigned int length, + unsigned int *act_len) +{ + int result; + + /* don't submit s-g requests during abort processing */ + if (test_bit(US_FLIDX_ABORTING, &us->dflags)) + return USB_STOR_XFER_ERROR; + + /* initialize the scatter-gather request block */ + usb_stor_dbg(us, "xfer %u bytes, %d entries\n", length, num_sg); + result = usb_sg_init(&us->current_sg, us->pusb_dev, pipe, 0, + sg, num_sg, length, GFP_NOIO); + if (result) { + usb_stor_dbg(us, "usb_sg_init returned %d\n", result); + return USB_STOR_XFER_ERROR; + } + + /* since the block has been initialized successfully, it's now + * okay to cancel it */ + set_bit(US_FLIDX_SG_ACTIVE, &us->dflags); + + /* did an abort occur during the submission? */ + if (test_bit(US_FLIDX_ABORTING, &us->dflags)) { + + /* cancel the request, if it hasn't been cancelled already */ + if (test_and_clear_bit(US_FLIDX_SG_ACTIVE, &us->dflags)) { + usb_stor_dbg(us, "-- cancelling sg request\n"); + usb_sg_cancel(&us->current_sg); + } + } + + /* wait for the completion of the transfer */ + usb_sg_wait(&us->current_sg); + clear_bit(US_FLIDX_SG_ACTIVE, &us->dflags); + + result = us->current_sg.status; + if (act_len) + *act_len = us->current_sg.bytes; + return interpret_urb_result(us, pipe, length, result, + us->current_sg.bytes); +} + +/* + * Common used function. Transfer a complete command + * via usb_stor_bulk_transfer_sglist() above. Set cmnd resid + */ +int usb_stor_bulk_srb(struct us_data* us, unsigned int pipe, + struct scsi_cmnd* srb) +{ + unsigned int partial; + int result = usb_stor_bulk_transfer_sglist(us, pipe, scsi_sglist(srb), + scsi_sg_count(srb), scsi_bufflen(srb), + &partial); + + scsi_set_resid(srb, scsi_bufflen(srb) - partial); + return result; +} +EXPORT_SYMBOL_GPL(usb_stor_bulk_srb); + +/* + * Transfer an entire SCSI command's worth of data payload over the bulk + * pipe. + * + * Note that this uses usb_stor_bulk_transfer_buf() and + * usb_stor_bulk_transfer_sglist() to achieve its goals -- + * this function simply determines whether we're going to use + * scatter-gather or not, and acts appropriately. + */ +int usb_stor_bulk_transfer_sg(struct us_data* us, unsigned int pipe, + void *buf, unsigned int length_left, int use_sg, int *residual) +{ + int result; + unsigned int partial; + + /* are we scatter-gathering? */ + if (use_sg) { + /* use the usb core scatter-gather primitives */ + result = usb_stor_bulk_transfer_sglist(us, pipe, + (struct scatterlist *) buf, use_sg, + length_left, &partial); + length_left -= partial; + } else { + /* no scatter-gather, just make the request */ + result = usb_stor_bulk_transfer_buf(us, pipe, buf, + length_left, &partial); + length_left -= partial; + } + + /* store the residual and return the error code */ + if (residual) + *residual = length_left; + return result; +} +EXPORT_SYMBOL_GPL(usb_stor_bulk_transfer_sg); + +/*********************************************************************** + * Transport routines + ***********************************************************************/ + +/* There are so many devices that report the capacity incorrectly, + * this routine was written to counteract some of the resulting + * problems. + */ +static void last_sector_hacks(struct us_data *us, struct scsi_cmnd *srb) +{ + struct gendisk *disk; + struct scsi_disk *sdkp; + u32 sector; + + /* To Report "Medium Error: Record Not Found */ + static unsigned char record_not_found[18] = { + [0] = 0x70, /* current error */ + [2] = MEDIUM_ERROR, /* = 0x03 */ + [7] = 0x0a, /* additional length */ + [12] = 0x14 /* Record Not Found */ + }; + + /* If last-sector problems can't occur, whether because the + * capacity was already decremented or because the device is + * known to report the correct capacity, then we don't need + * to do anything. + */ + if (!us->use_last_sector_hacks) + return; + + /* Was this command a READ(10) or a WRITE(10)? */ + if (srb->cmnd[0] != READ_10 && srb->cmnd[0] != WRITE_10) + goto done; + + /* Did this command access the last sector? */ + sector = (srb->cmnd[2] << 24) | (srb->cmnd[3] << 16) | + (srb->cmnd[4] << 8) | (srb->cmnd[5]); + disk = srb->request->rq_disk; + if (!disk) + goto done; + sdkp = scsi_disk(disk); + if (!sdkp) + goto done; + if (sector + 1 != sdkp->capacity) + goto done; + + if (srb->result == SAM_STAT_GOOD && scsi_get_resid(srb) == 0) { + + /* The command succeeded. We know this device doesn't + * have the last-sector bug, so stop checking it. + */ + us->use_last_sector_hacks = 0; + + } else { + /* The command failed. Allow up to 3 retries in case this + * is some normal sort of failure. After that, assume the + * capacity is wrong and we're trying to access the sector + * beyond the end. Replace the result code and sense data + * with values that will cause the SCSI core to fail the + * command immediately, instead of going into an infinite + * (or even just a very long) retry loop. + */ + if (++us->last_sector_retries < 3) + return; + srb->result = SAM_STAT_CHECK_CONDITION; + memcpy(srb->sense_buffer, record_not_found, + sizeof(record_not_found)); + } + + done: + /* Don't reset the retry counter for TEST UNIT READY commands, + * because they get issued after device resets which might be + * caused by a failed last-sector access. + */ + if (srb->cmnd[0] != TEST_UNIT_READY) + us->last_sector_retries = 0; +} + +/* Invoke the transport and basic error-handling/recovery methods + * + * This is used by the protocol layers to actually send the message to + * the device and receive the response. + */ +void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int need_auto_sense; + int result; + + /* send the command to the transport layer */ + scsi_set_resid(srb, 0); + result = us->transport(srb, us); + + /* if the command gets aborted by the higher layers, we need to + * short-circuit all other processing + */ + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + usb_stor_dbg(us, "-- command was aborted\n"); + srb->result = DID_ABORT << 16; + goto Handle_Errors; + } + + /* if there is a transport error, reset and don't auto-sense */ + if (result == USB_STOR_TRANSPORT_ERROR) { + usb_stor_dbg(us, "-- transport indicates error, resetting\n"); + srb->result = DID_ERROR << 16; + goto Handle_Errors; + } + + /* if the transport provided its own sense data, don't auto-sense */ + if (result == USB_STOR_TRANSPORT_NO_SENSE) { + srb->result = SAM_STAT_CHECK_CONDITION; + last_sector_hacks(us, srb); + return; + } + + srb->result = SAM_STAT_GOOD; + + /* Determine if we need to auto-sense + * + * I normally don't use a flag like this, but it's almost impossible + * to understand what's going on here if I don't. + */ + need_auto_sense = 0; + + /* + * If we're running the CB transport, which is incapable + * of determining status on its own, we will auto-sense + * unless the operation involved a data-in transfer. Devices + * can signal most data-in errors by stalling the bulk-in pipe. + */ + if ((us->protocol == USB_PR_CB || us->protocol == USB_PR_DPCM_USB) && + srb->sc_data_direction != DMA_FROM_DEVICE) { + usb_stor_dbg(us, "-- CB transport device requiring auto-sense\n"); + need_auto_sense = 1; + } + + /* + * If we have a failure, we're going to do a REQUEST_SENSE + * automatically. Note that we differentiate between a command + * "failure" and an "error" in the transport mechanism. + */ + if (result == USB_STOR_TRANSPORT_FAILED) { + usb_stor_dbg(us, "-- transport indicates command failure\n"); + need_auto_sense = 1; + } + + /* + * Determine if this device is SAT by seeing if the + * command executed successfully. Otherwise we'll have + * to wait for at least one CHECK_CONDITION to determine + * SANE_SENSE support + */ + if (unlikely((srb->cmnd[0] == ATA_16 || srb->cmnd[0] == ATA_12) && + result == USB_STOR_TRANSPORT_GOOD && + !(us->fflags & US_FL_SANE_SENSE) && + !(us->fflags & US_FL_BAD_SENSE) && + !(srb->cmnd[2] & 0x20))) { + usb_stor_dbg(us, "-- SAT supported, increasing auto-sense\n"); + us->fflags |= US_FL_SANE_SENSE; + } + + /* + * A short transfer on a command where we don't expect it + * is unusual, but it doesn't mean we need to auto-sense. + */ + if ((scsi_get_resid(srb) > 0) && + !((srb->cmnd[0] == REQUEST_SENSE) || + (srb->cmnd[0] == INQUIRY) || + (srb->cmnd[0] == MODE_SENSE) || + (srb->cmnd[0] == LOG_SENSE) || + (srb->cmnd[0] == MODE_SENSE_10))) { + usb_stor_dbg(us, "-- unexpectedly short transfer\n"); + } + + /* Now, if we need to do the auto-sense, let's do it */ + if (need_auto_sense) { + int temp_result; + struct scsi_eh_save ses; + int sense_size = US_SENSE_SIZE; + struct scsi_sense_hdr sshdr; + const u8 *scdd; + u8 fm_ili; + + /* device supports and needs bigger sense buffer */ + if (us->fflags & US_FL_SANE_SENSE) + sense_size = ~0; +Retry_Sense: + usb_stor_dbg(us, "Issuing auto-REQUEST_SENSE\n"); + + scsi_eh_prep_cmnd(srb, &ses, NULL, 0, sense_size); + + /* FIXME: we must do the protocol translation here */ + if (us->subclass == USB_SC_RBC || us->subclass == USB_SC_SCSI || + us->subclass == USB_SC_CYP_ATACB) + srb->cmd_len = 6; + else + srb->cmd_len = 12; + + /* issue the auto-sense command */ + scsi_set_resid(srb, 0); + temp_result = us->transport(us->srb, us); + + /* let's clean up right away */ + scsi_eh_restore_cmnd(srb, &ses); + + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + usb_stor_dbg(us, "-- auto-sense aborted\n"); + srb->result = DID_ABORT << 16; + + /* If SANE_SENSE caused this problem, disable it */ + if (sense_size != US_SENSE_SIZE) { + us->fflags &= ~US_FL_SANE_SENSE; + us->fflags |= US_FL_BAD_SENSE; + } + goto Handle_Errors; + } + + /* Some devices claim to support larger sense but fail when + * trying to request it. When a transport failure happens + * using US_FS_SANE_SENSE, we always retry with a standard + * (small) sense request. This fixes some USB GSM modems + */ + if (temp_result == USB_STOR_TRANSPORT_FAILED && + sense_size != US_SENSE_SIZE) { + usb_stor_dbg(us, "-- auto-sense failure, retry small sense\n"); + sense_size = US_SENSE_SIZE; + us->fflags &= ~US_FL_SANE_SENSE; + us->fflags |= US_FL_BAD_SENSE; + goto Retry_Sense; + } + + /* Other failures */ + if (temp_result != USB_STOR_TRANSPORT_GOOD) { + usb_stor_dbg(us, "-- auto-sense failure\n"); + + /* we skip the reset if this happens to be a + * multi-target device, since failure of an + * auto-sense is perfectly valid + */ + srb->result = DID_ERROR << 16; + if (!(us->fflags & US_FL_SCM_MULT_TARG)) + goto Handle_Errors; + return; + } + + /* If the sense data returned is larger than 18-bytes then we + * assume this device supports requesting more in the future. + * The response code must be 70h through 73h inclusive. + */ + if (srb->sense_buffer[7] > (US_SENSE_SIZE - 8) && + !(us->fflags & US_FL_SANE_SENSE) && + !(us->fflags & US_FL_BAD_SENSE) && + (srb->sense_buffer[0] & 0x7C) == 0x70) { + usb_stor_dbg(us, "-- SANE_SENSE support enabled\n"); + us->fflags |= US_FL_SANE_SENSE; + + /* Indicate to the user that we truncated their sense + * because we didn't know it supported larger sense. + */ + usb_stor_dbg(us, "-- Sense data truncated to %i from %i\n", + US_SENSE_SIZE, + srb->sense_buffer[7] + 8); + srb->sense_buffer[7] = (US_SENSE_SIZE - 8); + } + + scsi_normalize_sense(srb->sense_buffer, SCSI_SENSE_BUFFERSIZE, + &sshdr); + + usb_stor_dbg(us, "-- Result from auto-sense is %d\n", + temp_result); + usb_stor_dbg(us, "-- code: 0x%x, key: 0x%x, ASC: 0x%x, ASCQ: 0x%x\n", + sshdr.response_code, sshdr.sense_key, + sshdr.asc, sshdr.ascq); +#ifdef CONFIG_USB_STORAGE_DEBUG + usb_stor_show_sense(us, sshdr.sense_key, sshdr.asc, sshdr.ascq); +#endif + + /* set the result so the higher layers expect this data */ + srb->result = SAM_STAT_CHECK_CONDITION; + + scdd = scsi_sense_desc_find(srb->sense_buffer, + SCSI_SENSE_BUFFERSIZE, 4); + fm_ili = (scdd ? scdd[3] : srb->sense_buffer[2]) & 0xA0; + + /* We often get empty sense data. This could indicate that + * everything worked or that there was an unspecified + * problem. We have to decide which. + */ + if (sshdr.sense_key == 0 && sshdr.asc == 0 && sshdr.ascq == 0 && + fm_ili == 0) { + /* If things are really okay, then let's show that. + * Zero out the sense buffer so the higher layers + * won't realize we did an unsolicited auto-sense. + */ + if (result == USB_STOR_TRANSPORT_GOOD) { + srb->result = SAM_STAT_GOOD; + srb->sense_buffer[0] = 0x0; + + /* If there was a problem, report an unspecified + * hardware error to prevent the higher layers from + * entering an infinite retry loop. + */ + } else { + srb->result = DID_ERROR << 16; + if ((sshdr.response_code & 0x72) == 0x72) + srb->sense_buffer[1] = HARDWARE_ERROR; + else + srb->sense_buffer[2] = HARDWARE_ERROR; + } + } + } + + /* + * Some devices don't work or return incorrect data the first + * time they get a READ(10) command, or for the first READ(10) + * after a media change. If the INITIAL_READ10 flag is set, + * keep track of whether READ(10) commands succeed. If the + * previous one succeeded and this one failed, set the REDO_READ10 + * flag to force a retry. + */ + if (unlikely((us->fflags & US_FL_INITIAL_READ10) && + srb->cmnd[0] == READ_10)) { + if (srb->result == SAM_STAT_GOOD) { + set_bit(US_FLIDX_READ10_WORKED, &us->dflags); + } else if (test_bit(US_FLIDX_READ10_WORKED, &us->dflags)) { + clear_bit(US_FLIDX_READ10_WORKED, &us->dflags); + set_bit(US_FLIDX_REDO_READ10, &us->dflags); + } + + /* + * Next, if the REDO_READ10 flag is set, return a result + * code that will cause the SCSI core to retry the READ(10) + * command immediately. + */ + if (test_bit(US_FLIDX_REDO_READ10, &us->dflags)) { + clear_bit(US_FLIDX_REDO_READ10, &us->dflags); + srb->result = DID_IMM_RETRY << 16; + srb->sense_buffer[0] = 0; + } + } + + /* Did we transfer less than the minimum amount required? */ + if ((srb->result == SAM_STAT_GOOD || srb->sense_buffer[2] == 0) && + scsi_bufflen(srb) - scsi_get_resid(srb) < srb->underflow) + srb->result = DID_ERROR << 16; + + last_sector_hacks(us, srb); + return; + + /* Error and abort processing: try to resynchronize with the device + * by issuing a port reset. If that fails, try a class-specific + * device reset. */ + Handle_Errors: + + /* Set the RESETTING bit, and clear the ABORTING bit so that + * the reset may proceed. */ + scsi_lock(us_to_host(us)); + set_bit(US_FLIDX_RESETTING, &us->dflags); + clear_bit(US_FLIDX_ABORTING, &us->dflags); + scsi_unlock(us_to_host(us)); + + /* We must release the device lock because the pre_reset routine + * will want to acquire it. */ + mutex_unlock(&us->dev_mutex); + result = usb_stor_port_reset(us); + mutex_lock(&us->dev_mutex); + + if (result < 0) { + scsi_lock(us_to_host(us)); + usb_stor_report_device_reset(us); + scsi_unlock(us_to_host(us)); + us->transport_reset(us); + } + clear_bit(US_FLIDX_RESETTING, &us->dflags); + last_sector_hacks(us, srb); +} + +/* Stop the current URB transfer */ +void usb_stor_stop_transport(struct us_data *us) +{ + /* If the state machine is blocked waiting for an URB, + * let's wake it up. The test_and_clear_bit() call + * guarantees that if a URB has just been submitted, + * it won't be cancelled more than once. */ + if (test_and_clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags)) { + usb_stor_dbg(us, "-- cancelling URB\n"); + usb_unlink_urb(us->current_urb); + } + + /* If we are waiting for a scatter-gather operation, cancel it. */ + if (test_and_clear_bit(US_FLIDX_SG_ACTIVE, &us->dflags)) { + usb_stor_dbg(us, "-- cancelling sg request\n"); + usb_sg_cancel(&us->current_sg); + } +} + +/* + * Control/Bulk and Control/Bulk/Interrupt transport + */ + +int usb_stor_CB_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + unsigned int transfer_length = scsi_bufflen(srb); + unsigned int pipe = 0; + int result; + + /* COMMAND STAGE */ + /* let's send the command via the control pipe */ + result = usb_stor_ctrl_transfer(us, us->send_ctrl_pipe, + US_CBI_ADSC, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0, + us->ifnum, srb->cmnd, srb->cmd_len); + + /* check the return code for the command */ + usb_stor_dbg(us, "Call to usb_stor_ctrl_transfer() returned %d\n", + result); + + /* if we stalled the command, it means command failed */ + if (result == USB_STOR_XFER_STALLED) { + return USB_STOR_TRANSPORT_FAILED; + } + + /* Uh oh... serious problem here */ + if (result != USB_STOR_XFER_GOOD) { + return USB_STOR_TRANSPORT_ERROR; + } + + /* DATA STAGE */ + /* transfer the data payload for this command, if one exists*/ + if (transfer_length) { + pipe = srb->sc_data_direction == DMA_FROM_DEVICE ? + us->recv_bulk_pipe : us->send_bulk_pipe; + result = usb_stor_bulk_srb(us, pipe, srb); + usb_stor_dbg(us, "CBI data stage result is 0x%x\n", result); + + /* if we stalled the data transfer it means command failed */ + if (result == USB_STOR_XFER_STALLED) + return USB_STOR_TRANSPORT_FAILED; + if (result > USB_STOR_XFER_STALLED) + return USB_STOR_TRANSPORT_ERROR; + } + + /* STATUS STAGE */ + + /* NOTE: CB does not have a status stage. Silly, I know. So + * we have to catch this at a higher level. + */ + if (us->protocol != USB_PR_CBI) + return USB_STOR_TRANSPORT_GOOD; + + result = usb_stor_intr_transfer(us, us->iobuf, 2); + usb_stor_dbg(us, "Got interrupt data (0x%x, 0x%x)\n", + us->iobuf[0], us->iobuf[1]); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* UFI gives us ASC and ASCQ, like a request sense + * + * REQUEST_SENSE and INQUIRY don't affect the sense data on UFI + * devices, so we ignore the information for those commands. Note + * that this means we could be ignoring a real error on these + * commands, but that can't be helped. + */ + if (us->subclass == USB_SC_UFI) { + if (srb->cmnd[0] == REQUEST_SENSE || + srb->cmnd[0] == INQUIRY) + return USB_STOR_TRANSPORT_GOOD; + if (us->iobuf[0]) + goto Failed; + return USB_STOR_TRANSPORT_GOOD; + } + + /* If not UFI, we interpret the data as a result code + * The first byte should always be a 0x0. + * + * Some bogus devices don't follow that rule. They stuff the ASC + * into the first byte -- so if it's non-zero, call it a failure. + */ + if (us->iobuf[0]) { + usb_stor_dbg(us, "CBI IRQ data showed reserved bType 0x%x\n", + us->iobuf[0]); + goto Failed; + + } + + /* The second byte & 0x0F should be 0x0 for good, otherwise error */ + switch (us->iobuf[1] & 0x0F) { + case 0x00: + return USB_STOR_TRANSPORT_GOOD; + case 0x01: + goto Failed; + } + return USB_STOR_TRANSPORT_ERROR; + + /* the CBI spec requires that the bulk pipe must be cleared + * following any data-in/out command failure (section 2.4.3.1.3) + */ + Failed: + if (pipe) + usb_stor_clear_halt(us, pipe); + return USB_STOR_TRANSPORT_FAILED; +} +EXPORT_SYMBOL_GPL(usb_stor_CB_transport); + +/* + * Bulk only transport + */ + +/* Determine what the maximum LUN supported is */ +int usb_stor_Bulk_max_lun(struct us_data *us) +{ + int result; + + /* issue the command */ + us->iobuf[0] = 0; + result = usb_stor_control_msg(us, us->recv_ctrl_pipe, + US_BULK_GET_MAX_LUN, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + 0, us->ifnum, us->iobuf, 1, 10*HZ); + + usb_stor_dbg(us, "GetMaxLUN command result is %d, data is %d\n", + result, us->iobuf[0]); + + /* if we have a successful request, return the result */ + if (result > 0) + return us->iobuf[0]; + + /* + * Some devices don't like GetMaxLUN. They may STALL the control + * pipe, they may return a zero-length result, they may do nothing at + * all and timeout, or they may fail in even more bizarrely creative + * ways. In these cases the best approach is to use the default + * value: only one LUN. + */ + return 0; +} + +int usb_stor_Bulk_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf; + unsigned int transfer_length = scsi_bufflen(srb); + unsigned int residue; + int result; + int fake_sense = 0; + unsigned int cswlen; + unsigned int cbwlen = US_BULK_CB_WRAP_LEN; + + /* Take care of BULK32 devices; set extra byte to 0 */ + if (unlikely(us->fflags & US_FL_BULK32)) { + cbwlen = 32; + us->iobuf[31] = 0; + } + + /* set up the command wrapper */ + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = cpu_to_le32(transfer_length); + bcb->Flags = srb->sc_data_direction == DMA_FROM_DEVICE ? + US_BULK_FLAG_IN : 0; + bcb->Tag = ++us->tag; + bcb->Lun = srb->device->lun; + if (us->fflags & US_FL_SCM_MULT_TARG) + bcb->Lun |= srb->device->id << 4; + bcb->Length = srb->cmd_len; + + /* copy the command payload */ + memset(bcb->CDB, 0, sizeof(bcb->CDB)); + memcpy(bcb->CDB, srb->cmnd, bcb->Length); + + /* send it to out endpoint */ + usb_stor_dbg(us, "Bulk Command S 0x%x T 0x%x L %d F %d Trg %d LUN %d CL %d\n", + le32_to_cpu(bcb->Signature), bcb->Tag, + le32_to_cpu(bcb->DataTransferLength), bcb->Flags, + (bcb->Lun >> 4), (bcb->Lun & 0x0F), + bcb->Length); + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + bcb, cbwlen, NULL); + usb_stor_dbg(us, "Bulk command transfer result=%d\n", result); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* DATA STAGE */ + /* send/receive data payload, if there is any */ + + /* Some USB-IDE converter chips need a 100us delay between the + * command phase and the data phase. Some devices need a little + * more than that, probably because of clock rate inaccuracies. */ + if (unlikely(us->fflags & US_FL_GO_SLOW)) + udelay(125); + + if (transfer_length) { + unsigned int pipe = srb->sc_data_direction == DMA_FROM_DEVICE ? + us->recv_bulk_pipe : us->send_bulk_pipe; + result = usb_stor_bulk_srb(us, pipe, srb); + usb_stor_dbg(us, "Bulk data transfer result 0x%x\n", result); + if (result == USB_STOR_XFER_ERROR) + return USB_STOR_TRANSPORT_ERROR; + + /* If the device tried to send back more data than the + * amount requested, the spec requires us to transfer + * the CSW anyway. Since there's no point retrying the + * the command, we'll return fake sense data indicating + * Illegal Request, Invalid Field in CDB. + */ + if (result == USB_STOR_XFER_LONG) + fake_sense = 1; + + /* + * Sometimes a device will mistakenly skip the data phase + * and go directly to the status phase without sending a + * zero-length packet. If we get a 13-byte response here, + * check whether it really is a CSW. + */ + if (result == USB_STOR_XFER_SHORT && + srb->sc_data_direction == DMA_FROM_DEVICE && + transfer_length - scsi_get_resid(srb) == + US_BULK_CS_WRAP_LEN) { + struct scatterlist *sg = NULL; + unsigned int offset = 0; + + if (usb_stor_access_xfer_buf((unsigned char *) bcs, + US_BULK_CS_WRAP_LEN, srb, &sg, + &offset, FROM_XFER_BUF) == + US_BULK_CS_WRAP_LEN && + bcs->Signature == + cpu_to_le32(US_BULK_CS_SIGN)) { + usb_stor_dbg(us, "Device skipped data phase\n"); + scsi_set_resid(srb, transfer_length); + goto skipped_data_phase; + } + } + } + + /* See flow chart on pg 15 of the Bulk Only Transport spec for + * an explanation of how this code works. + */ + + /* get CSW for device status */ + usb_stor_dbg(us, "Attempting to get CSW...\n"); + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + bcs, US_BULK_CS_WRAP_LEN, &cswlen); + + /* Some broken devices add unnecessary zero-length packets to the + * end of their data transfers. Such packets show up as 0-length + * CSWs. If we encounter such a thing, try to read the CSW again. + */ + if (result == USB_STOR_XFER_SHORT && cswlen == 0) { + usb_stor_dbg(us, "Received 0-length CSW; retrying...\n"); + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + bcs, US_BULK_CS_WRAP_LEN, &cswlen); + } + + /* did the attempt to read the CSW fail? */ + if (result == USB_STOR_XFER_STALLED) { + + /* get the status again */ + usb_stor_dbg(us, "Attempting to get CSW (2nd try)...\n"); + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + bcs, US_BULK_CS_WRAP_LEN, NULL); + } + + /* if we still have a failure at this point, we're in trouble */ + usb_stor_dbg(us, "Bulk status result = %d\n", result); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + skipped_data_phase: + /* check bulk status */ + residue = le32_to_cpu(bcs->Residue); + usb_stor_dbg(us, "Bulk Status S 0x%x T 0x%x R %u Stat 0x%x\n", + le32_to_cpu(bcs->Signature), bcs->Tag, + residue, bcs->Status); + if (!(bcs->Tag == us->tag || (us->fflags & US_FL_BULK_IGNORE_TAG)) || + bcs->Status > US_BULK_STAT_PHASE) { + usb_stor_dbg(us, "Bulk logical error\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + /* Some broken devices report odd signatures, so we do not check them + * for validity against the spec. We store the first one we see, + * and check subsequent transfers for validity against this signature. + */ + if (!us->bcs_signature) { + us->bcs_signature = bcs->Signature; + if (us->bcs_signature != cpu_to_le32(US_BULK_CS_SIGN)) + usb_stor_dbg(us, "Learnt BCS signature 0x%08X\n", + le32_to_cpu(us->bcs_signature)); + } else if (bcs->Signature != us->bcs_signature) { + usb_stor_dbg(us, "Signature mismatch: got %08X, expecting %08X\n", + le32_to_cpu(bcs->Signature), + le32_to_cpu(us->bcs_signature)); + return USB_STOR_TRANSPORT_ERROR; + } + + /* try to compute the actual residue, based on how much data + * was really transferred and what the device tells us */ + if (residue && !(us->fflags & US_FL_IGNORE_RESIDUE)) { + + /* Heuristically detect devices that generate bogus residues + * by seeing what happens with INQUIRY and READ CAPACITY + * commands. + */ + if (bcs->Status == US_BULK_STAT_OK && + scsi_get_resid(srb) == 0 && + ((srb->cmnd[0] == INQUIRY && + transfer_length == 36) || + (srb->cmnd[0] == READ_CAPACITY && + transfer_length == 8))) { + us->fflags |= US_FL_IGNORE_RESIDUE; + + } else { + residue = min(residue, transfer_length); + scsi_set_resid(srb, max(scsi_get_resid(srb), + (int) residue)); + } + } + + /* based on the status code, we report good or bad */ + switch (bcs->Status) { + case US_BULK_STAT_OK: + /* device babbled -- return fake sense data */ + if (fake_sense) { + memcpy(srb->sense_buffer, + usb_stor_sense_invalidCDB, + sizeof(usb_stor_sense_invalidCDB)); + return USB_STOR_TRANSPORT_NO_SENSE; + } + + /* command good -- note that data could be short */ + return USB_STOR_TRANSPORT_GOOD; + + case US_BULK_STAT_FAIL: + /* command failed */ + return USB_STOR_TRANSPORT_FAILED; + + case US_BULK_STAT_PHASE: + /* phase error -- note that a transport reset will be + * invoked by the invoke_transport() function + */ + return USB_STOR_TRANSPORT_ERROR; + } + + /* we should never get here, but if we do, we're in trouble */ + return USB_STOR_TRANSPORT_ERROR; +} +EXPORT_SYMBOL_GPL(usb_stor_Bulk_transport); + +/*********************************************************************** + * Reset routines + ***********************************************************************/ + +/* This is the common part of the device reset code. + * + * It's handy that every transport mechanism uses the control endpoint for + * resets. + * + * Basically, we send a reset with a 5-second timeout, so we don't get + * jammed attempting to do the reset. + */ +static int usb_stor_reset_common(struct us_data *us, + u8 request, u8 requesttype, + u16 value, u16 index, void *data, u16 size) +{ + int result; + int result2; + + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + usb_stor_dbg(us, "No reset during disconnect\n"); + return -EIO; + } + + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + request, requesttype, value, index, data, size, + 5*HZ); + if (result < 0) { + usb_stor_dbg(us, "Soft reset failed: %d\n", result); + return result; + } + + /* Give the device some time to recover from the reset, + * but don't delay disconnect processing. */ + wait_event_interruptible_timeout(us->delay_wait, + test_bit(US_FLIDX_DISCONNECTING, &us->dflags), + HZ*6); + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + usb_stor_dbg(us, "Reset interrupted by disconnect\n"); + return -EIO; + } + + usb_stor_dbg(us, "Soft reset: clearing bulk-in endpoint halt\n"); + result = usb_stor_clear_halt(us, us->recv_bulk_pipe); + + usb_stor_dbg(us, "Soft reset: clearing bulk-out endpoint halt\n"); + result2 = usb_stor_clear_halt(us, us->send_bulk_pipe); + + /* return a result code based on the result of the clear-halts */ + if (result >= 0) + result = result2; + if (result < 0) + usb_stor_dbg(us, "Soft reset failed\n"); + else + usb_stor_dbg(us, "Soft reset done\n"); + return result; +} + +/* This issues a CB[I] Reset to the device in question + */ +#define CB_RESET_CMD_SIZE 12 + +int usb_stor_CB_reset(struct us_data *us) +{ + memset(us->iobuf, 0xFF, CB_RESET_CMD_SIZE); + us->iobuf[0] = SEND_DIAGNOSTIC; + us->iobuf[1] = 4; + return usb_stor_reset_common(us, US_CBI_ADSC, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, us->ifnum, us->iobuf, CB_RESET_CMD_SIZE); +} +EXPORT_SYMBOL_GPL(usb_stor_CB_reset); + +/* This issues a Bulk-only Reset to the device in question, including + * clearing the subsequent endpoint halts that may occur. + */ +int usb_stor_Bulk_reset(struct us_data *us) +{ + return usb_stor_reset_common(us, US_BULK_RESET_REQUEST, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, us->ifnum, NULL, 0); +} +EXPORT_SYMBOL_GPL(usb_stor_Bulk_reset); + +/* Issue a USB port reset to the device. The caller must not hold + * us->dev_mutex. + */ +int usb_stor_port_reset(struct us_data *us) +{ + int result; + + /*for these devices we must use the class specific method */ + if (us->pusb_dev->quirks & USB_QUIRK_RESET) + return -EPERM; + + result = usb_lock_device_for_reset(us->pusb_dev, us->pusb_intf); + if (result < 0) + usb_stor_dbg(us, "unable to lock device for reset: %d\n", + result); + else { + /* Were we disconnected while waiting for the lock? */ + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + result = -EIO; + usb_stor_dbg(us, "No reset during disconnect\n"); + } else { + result = usb_reset_device(us->pusb_dev); + usb_stor_dbg(us, "usb_reset_device returns %d\n", + result); + } + usb_unlock_device(us->pusb_dev); + } + return result; +} diff --git a/drivers/usb/storage/transport.h b/drivers/usb/storage/transport.h new file mode 100644 index 00000000..9369d752 --- /dev/null +++ b/drivers/usb/storage/transport.h @@ -0,0 +1,103 @@ +/* Driver for USB Mass Storage compliant devices + * Transport Functions Header File + * + * Current development and maintenance by: + * (c) 1999, 2000 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _TRANSPORT_H_ +#define _TRANSPORT_H_ + +#include + +/* + * usb_stor_bulk_transfer_xxx() return codes, in order of severity + */ + +#define USB_STOR_XFER_GOOD 0 /* good transfer */ +#define USB_STOR_XFER_SHORT 1 /* transferred less than expected */ +#define USB_STOR_XFER_STALLED 2 /* endpoint stalled */ +#define USB_STOR_XFER_LONG 3 /* device tried to send too much */ +#define USB_STOR_XFER_ERROR 4 /* transfer died in the middle */ + +/* + * Transport return codes + */ + +#define USB_STOR_TRANSPORT_GOOD 0 /* Transport good, command good */ +#define USB_STOR_TRANSPORT_FAILED 1 /* Transport good, command failed */ +#define USB_STOR_TRANSPORT_NO_SENSE 2 /* Command failed, no auto-sense */ +#define USB_STOR_TRANSPORT_ERROR 3 /* Transport bad (i.e. device dead) */ + +/* + * We used to have USB_STOR_XFER_ABORTED and USB_STOR_TRANSPORT_ABORTED + * return codes. But now the transport and low-level transfer routines + * treat an abort as just another error (-ENOENT for a cancelled URB). + * It is up to the invoke_transport() function to test for aborts and + * distinguish them from genuine communication errors. + */ + +/* + * CBI accept device specific command + */ + +#define US_CBI_ADSC 0 + +extern int usb_stor_CB_transport(struct scsi_cmnd *, struct us_data*); +extern int usb_stor_CB_reset(struct us_data*); + +extern int usb_stor_Bulk_transport(struct scsi_cmnd *, struct us_data*); +extern int usb_stor_Bulk_max_lun(struct us_data*); +extern int usb_stor_Bulk_reset(struct us_data*); + +extern void usb_stor_invoke_transport(struct scsi_cmnd *, struct us_data*); +extern void usb_stor_stop_transport(struct us_data*); + +extern int usb_stor_control_msg(struct us_data *us, unsigned int pipe, + u8 request, u8 requesttype, u16 value, u16 index, + void *data, u16 size, int timeout); +extern int usb_stor_clear_halt(struct us_data *us, unsigned int pipe); + +extern int usb_stor_ctrl_transfer(struct us_data *us, unsigned int pipe, + u8 request, u8 requesttype, u16 value, u16 index, + void *data, u16 size); +extern int usb_stor_bulk_transfer_buf(struct us_data *us, unsigned int pipe, + void *buf, unsigned int length, unsigned int *act_len); +extern int usb_stor_bulk_transfer_sg(struct us_data *us, unsigned int pipe, + void *buf, unsigned int length, int use_sg, int *residual); +extern int usb_stor_bulk_srb(struct us_data* us, unsigned int pipe, + struct scsi_cmnd* srb); + +extern int usb_stor_port_reset(struct us_data *us); +#endif diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c new file mode 100644 index 00000000..d966b59f --- /dev/null +++ b/drivers/usb/storage/uas.c @@ -0,0 +1,1073 @@ +/* + * USB Attached SCSI + * Note that this is not the same as the USB Mass Storage driver + * + * Copyright Matthew Wilcox for Intel Corp, 2010 + * Copyright Sarah Sharp for Intel Corp, 2010 + * + * Distributed under the terms of the GNU GPL, version two. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * The r00-r01c specs define this version of the SENSE IU data structure. + * It's still in use by several different firmware releases. + */ +struct sense_iu_old { + __u8 iu_id; + __u8 rsvd1; + __be16 tag; + __be16 len; + __u8 status; + __u8 service_response; + __u8 sense[SCSI_SENSE_BUFFERSIZE]; +}; + +struct uas_dev_info { + struct usb_interface *intf; + struct usb_device *udev; + struct usb_anchor cmd_urbs; + struct usb_anchor sense_urbs; + struct usb_anchor data_urbs; + int qdepth, resetting; + struct response_ui response; + unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe; + unsigned use_streams:1; + unsigned uas_sense_old:1; + struct scsi_cmnd *cmnd; + spinlock_t lock; +}; + +enum { + SUBMIT_STATUS_URB = (1 << 1), + ALLOC_DATA_IN_URB = (1 << 2), + SUBMIT_DATA_IN_URB = (1 << 3), + ALLOC_DATA_OUT_URB = (1 << 4), + SUBMIT_DATA_OUT_URB = (1 << 5), + ALLOC_CMD_URB = (1 << 6), + SUBMIT_CMD_URB = (1 << 7), + COMMAND_INFLIGHT = (1 << 8), + DATA_IN_URB_INFLIGHT = (1 << 9), + DATA_OUT_URB_INFLIGHT = (1 << 10), + COMMAND_COMPLETED = (1 << 11), + COMMAND_ABORTED = (1 << 12), + UNLINK_DATA_URBS = (1 << 13), + IS_IN_WORK_LIST = (1 << 14), +}; + +/* Overrides scsi_pointer */ +struct uas_cmd_info { + unsigned int state; + unsigned int stream; + struct urb *cmd_urb; + struct urb *data_in_urb; + struct urb *data_out_urb; + struct list_head list; +}; + +/* I hate forward declarations, but I actually have a loop */ +static int uas_submit_urbs(struct scsi_cmnd *cmnd, + struct uas_dev_info *devinfo, gfp_t gfp); +static void uas_do_work(struct work_struct *work); +static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); + +static DECLARE_WORK(uas_work, uas_do_work); +static DEFINE_SPINLOCK(uas_work_lock); +static LIST_HEAD(uas_work_list); + +static void uas_unlink_data_urbs(struct uas_dev_info *devinfo, + struct uas_cmd_info *cmdinfo) +{ + unsigned long flags; + + /* + * The UNLINK_DATA_URBS flag makes sure uas_try_complete + * (called by urb completion) doesn't release cmdinfo + * underneath us. + */ + spin_lock_irqsave(&devinfo->lock, flags); + cmdinfo->state |= UNLINK_DATA_URBS; + spin_unlock_irqrestore(&devinfo->lock, flags); + + if (cmdinfo->data_in_urb) + usb_unlink_urb(cmdinfo->data_in_urb); + if (cmdinfo->data_out_urb) + usb_unlink_urb(cmdinfo->data_out_urb); + + spin_lock_irqsave(&devinfo->lock, flags); + cmdinfo->state &= ~UNLINK_DATA_URBS; + spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static void uas_do_work(struct work_struct *work) +{ + struct uas_cmd_info *cmdinfo; + struct uas_cmd_info *temp; + struct list_head list; + unsigned long flags; + int err; + + spin_lock_irq(&uas_work_lock); + list_replace_init(&uas_work_list, &list); + spin_unlock_irq(&uas_work_lock); + + list_for_each_entry_safe(cmdinfo, temp, &list, list) { + struct scsi_pointer *scp = (void *)cmdinfo; + struct scsi_cmnd *cmnd = container_of(scp, + struct scsi_cmnd, SCp); + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + spin_lock_irqsave(&devinfo->lock, flags); + err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); + if (!err) + cmdinfo->state &= ~IS_IN_WORK_LIST; + spin_unlock_irqrestore(&devinfo->lock, flags); + if (err) { + list_del(&cmdinfo->list); + spin_lock_irq(&uas_work_lock); + list_add_tail(&cmdinfo->list, &uas_work_list); + spin_unlock_irq(&uas_work_lock); + schedule_work(&uas_work); + } + } +} + +static void uas_abort_work(struct uas_dev_info *devinfo) +{ + struct uas_cmd_info *cmdinfo; + struct uas_cmd_info *temp; + struct list_head list; + unsigned long flags; + + spin_lock_irq(&uas_work_lock); + list_replace_init(&uas_work_list, &list); + spin_unlock_irq(&uas_work_lock); + + spin_lock_irqsave(&devinfo->lock, flags); + list_for_each_entry_safe(cmdinfo, temp, &list, list) { + struct scsi_pointer *scp = (void *)cmdinfo; + struct scsi_cmnd *cmnd = container_of(scp, + struct scsi_cmnd, SCp); + struct uas_dev_info *di = (void *)cmnd->device->hostdata; + + if (di == devinfo) { + cmdinfo->state |= COMMAND_ABORTED; + cmdinfo->state &= ~IS_IN_WORK_LIST; + if (devinfo->resetting) { + /* uas_stat_cmplt() will not do that + * when a device reset is in + * progress */ + cmdinfo->state &= ~COMMAND_INFLIGHT; + } + uas_try_complete(cmnd, __func__); + } else { + /* not our uas device, relink into list */ + list_del(&cmdinfo->list); + spin_lock_irq(&uas_work_lock); + list_add_tail(&cmdinfo->list, &uas_work_list); + spin_unlock_irq(&uas_work_lock); + } + } + spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static void uas_sense(struct urb *urb, struct scsi_cmnd *cmnd) +{ + struct sense_iu *sense_iu = urb->transfer_buffer; + struct scsi_device *sdev = cmnd->device; + + if (urb->actual_length > 16) { + unsigned len = be16_to_cpup(&sense_iu->len); + if (len + 16 != urb->actual_length) { + int newlen = min(len + 16, urb->actual_length) - 16; + if (newlen < 0) + newlen = 0; + sdev_printk(KERN_INFO, sdev, "%s: urb length %d " + "disagrees with IU sense data length %d, " + "using %d bytes of sense data\n", __func__, + urb->actual_length, len, newlen); + len = newlen; + } + memcpy(cmnd->sense_buffer, sense_iu->sense, len); + } + + cmnd->result = sense_iu->status; +} + +static void uas_sense_old(struct urb *urb, struct scsi_cmnd *cmnd) +{ + struct sense_iu_old *sense_iu = urb->transfer_buffer; + struct scsi_device *sdev = cmnd->device; + + if (urb->actual_length > 8) { + unsigned len = be16_to_cpup(&sense_iu->len) - 2; + if (len + 8 != urb->actual_length) { + int newlen = min(len + 8, urb->actual_length) - 8; + if (newlen < 0) + newlen = 0; + sdev_printk(KERN_INFO, sdev, "%s: urb length %d " + "disagrees with IU sense data length %d, " + "using %d bytes of sense data\n", __func__, + urb->actual_length, len, newlen); + len = newlen; + } + memcpy(cmnd->sense_buffer, sense_iu->sense, len); + } + + cmnd->result = sense_iu->status; +} + +static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *caller) +{ + struct uas_cmd_info *ci = (void *)&cmnd->SCp; + + scmd_printk(KERN_INFO, cmnd, "%s %p tag %d, inflight:" + "%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + caller, cmnd, cmnd->request->tag, + (ci->state & SUBMIT_STATUS_URB) ? " s-st" : "", + (ci->state & ALLOC_DATA_IN_URB) ? " a-in" : "", + (ci->state & SUBMIT_DATA_IN_URB) ? " s-in" : "", + (ci->state & ALLOC_DATA_OUT_URB) ? " a-out" : "", + (ci->state & SUBMIT_DATA_OUT_URB) ? " s-out" : "", + (ci->state & ALLOC_CMD_URB) ? " a-cmd" : "", + (ci->state & SUBMIT_CMD_URB) ? " s-cmd" : "", + (ci->state & COMMAND_INFLIGHT) ? " CMD" : "", + (ci->state & DATA_IN_URB_INFLIGHT) ? " IN" : "", + (ci->state & DATA_OUT_URB_INFLIGHT) ? " OUT" : "", + (ci->state & COMMAND_COMPLETED) ? " done" : "", + (ci->state & COMMAND_ABORTED) ? " abort" : "", + (ci->state & UNLINK_DATA_URBS) ? " unlink": "", + (ci->state & IS_IN_WORK_LIST) ? " work" : ""); +} + +static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) +{ + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + + WARN_ON(!spin_is_locked(&devinfo->lock)); + if (cmdinfo->state & (COMMAND_INFLIGHT | + DATA_IN_URB_INFLIGHT | + DATA_OUT_URB_INFLIGHT | + UNLINK_DATA_URBS)) + return -EBUSY; + BUG_ON(cmdinfo->state & COMMAND_COMPLETED); + cmdinfo->state |= COMMAND_COMPLETED; + usb_free_urb(cmdinfo->data_in_urb); + usb_free_urb(cmdinfo->data_out_urb); + if (cmdinfo->state & COMMAND_ABORTED) { + scmd_printk(KERN_INFO, cmnd, "abort completed\n"); + cmnd->result = DID_ABORT << 16; + } + cmnd->scsi_done(cmnd); + return 0; +} + +static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, + unsigned direction) +{ + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + int err; + + cmdinfo->state |= direction | SUBMIT_STATUS_URB; + err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); + if (err) { + spin_lock(&uas_work_lock); + list_add_tail(&cmdinfo->list, &uas_work_list); + cmdinfo->state |= IS_IN_WORK_LIST; + spin_unlock(&uas_work_lock); + schedule_work(&uas_work); + } +} + +static void uas_stat_cmplt(struct urb *urb) +{ + struct iu *iu = urb->transfer_buffer; + struct Scsi_Host *shost = urb->context; + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct scsi_cmnd *cmnd; + struct uas_cmd_info *cmdinfo; + unsigned long flags; + u16 tag; + + if (urb->status) { + dev_err(&urb->dev->dev, "URB BAD STATUS %d\n", urb->status); + usb_free_urb(urb); + return; + } + + if (devinfo->resetting) { + usb_free_urb(urb); + return; + } + + spin_lock_irqsave(&devinfo->lock, flags); + tag = be16_to_cpup(&iu->tag) - 1; + if (tag == 0) + cmnd = devinfo->cmnd; + else + cmnd = scsi_host_find_tag(shost, tag - 1); + + if (!cmnd) { + if (iu->iu_id == IU_ID_RESPONSE) { + /* store results for uas_eh_task_mgmt() */ + memcpy(&devinfo->response, iu, sizeof(devinfo->response)); + } + usb_free_urb(urb); + spin_unlock_irqrestore(&devinfo->lock, flags); + return; + } + + cmdinfo = (void *)&cmnd->SCp; + switch (iu->iu_id) { + case IU_ID_STATUS: + if (devinfo->cmnd == cmnd) + devinfo->cmnd = NULL; + + if (urb->actual_length < 16) + devinfo->uas_sense_old = 1; + if (devinfo->uas_sense_old) + uas_sense_old(urb, cmnd); + else + uas_sense(urb, cmnd); + if (cmnd->result != 0) { + /* cancel data transfers on error */ + spin_unlock_irqrestore(&devinfo->lock, flags); + uas_unlink_data_urbs(devinfo, cmdinfo); + spin_lock_irqsave(&devinfo->lock, flags); + } + cmdinfo->state &= ~COMMAND_INFLIGHT; + uas_try_complete(cmnd, __func__); + break; + case IU_ID_READ_READY: + uas_xfer_data(urb, cmnd, SUBMIT_DATA_IN_URB); + break; + case IU_ID_WRITE_READY: + uas_xfer_data(urb, cmnd, SUBMIT_DATA_OUT_URB); + break; + default: + scmd_printk(KERN_ERR, cmnd, + "Bogus IU (%d) received on status pipe\n", iu->iu_id); + } + usb_free_urb(urb); + spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static void uas_data_cmplt(struct urb *urb) +{ + struct scsi_cmnd *cmnd = urb->context; + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + struct scsi_data_buffer *sdb = NULL; + unsigned long flags; + + spin_lock_irqsave(&devinfo->lock, flags); + if (cmdinfo->data_in_urb == urb) { + sdb = scsi_in(cmnd); + cmdinfo->state &= ~DATA_IN_URB_INFLIGHT; + } else if (cmdinfo->data_out_urb == urb) { + sdb = scsi_out(cmnd); + cmdinfo->state &= ~DATA_OUT_URB_INFLIGHT; + } + BUG_ON(sdb == NULL); + if (urb->status) { + /* error: no data transfered */ + sdb->resid = sdb->length; + } else { + sdb->resid = sdb->length - urb->actual_length; + } + uas_try_complete(cmnd, __func__); + spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp, + unsigned int pipe, u16 stream_id, + struct scsi_cmnd *cmnd, + enum dma_data_direction dir) +{ + struct usb_device *udev = devinfo->udev; + struct urb *urb = usb_alloc_urb(0, gfp); + struct scsi_data_buffer *sdb = (dir == DMA_FROM_DEVICE) + ? scsi_in(cmnd) : scsi_out(cmnd); + + if (!urb) + goto out; + usb_fill_bulk_urb(urb, udev, pipe, NULL, sdb->length, + uas_data_cmplt, cmnd); + if (devinfo->use_streams) + urb->stream_id = stream_id; + urb->num_sgs = udev->bus->sg_tablesize ? sdb->table.nents : 0; + urb->sg = sdb->table.sgl; + out: + return urb; +} + +static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp, + struct Scsi_Host *shost, u16 stream_id) +{ + struct usb_device *udev = devinfo->udev; + struct urb *urb = usb_alloc_urb(0, gfp); + struct sense_iu *iu; + + if (!urb) + goto out; + + iu = kzalloc(sizeof(*iu), gfp); + if (!iu) + goto free; + + usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu), + uas_stat_cmplt, shost); + urb->stream_id = stream_id; + urb->transfer_flags |= URB_FREE_BUFFER; + out: + return urb; + free: + usb_free_urb(urb); + return NULL; +} + +static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp, + struct scsi_cmnd *cmnd, u16 stream_id) +{ + struct usb_device *udev = devinfo->udev; + struct scsi_device *sdev = cmnd->device; + struct urb *urb = usb_alloc_urb(0, gfp); + struct command_iu *iu; + int len; + + if (!urb) + goto out; + + len = cmnd->cmd_len - 16; + if (len < 0) + len = 0; + len = ALIGN(len, 4); + iu = kzalloc(sizeof(*iu) + len, gfp); + if (!iu) + goto free; + + iu->iu_id = IU_ID_COMMAND; + if (blk_rq_tagged(cmnd->request)) + iu->tag = cpu_to_be16(cmnd->request->tag + 2); + else + iu->tag = cpu_to_be16(1); + iu->prio_attr = UAS_SIMPLE_TAG; + iu->len = len; + int_to_scsilun(sdev->lun, &iu->lun); + memcpy(iu->cdb, cmnd->cmnd, cmnd->cmd_len); + + usb_fill_bulk_urb(urb, udev, devinfo->cmd_pipe, iu, sizeof(*iu) + len, + usb_free_urb, NULL); + urb->transfer_flags |= URB_FREE_BUFFER; + out: + return urb; + free: + usb_free_urb(urb); + return NULL; +} + +static int uas_submit_task_urb(struct scsi_cmnd *cmnd, gfp_t gfp, + u8 function, u16 stream_id) +{ + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + struct usb_device *udev = devinfo->udev; + struct urb *urb = usb_alloc_urb(0, gfp); + struct task_mgmt_iu *iu; + int err = -ENOMEM; + + if (!urb) + goto err; + + iu = kzalloc(sizeof(*iu), gfp); + if (!iu) + goto err; + + iu->iu_id = IU_ID_TASK_MGMT; + iu->tag = cpu_to_be16(stream_id); + int_to_scsilun(cmnd->device->lun, &iu->lun); + + iu->function = function; + switch (function) { + case TMF_ABORT_TASK: + if (blk_rq_tagged(cmnd->request)) + iu->task_tag = cpu_to_be16(cmnd->request->tag + 2); + else + iu->task_tag = cpu_to_be16(1); + break; + } + + usb_fill_bulk_urb(urb, udev, devinfo->cmd_pipe, iu, sizeof(*iu), + usb_free_urb, NULL); + urb->transfer_flags |= URB_FREE_BUFFER; + + err = usb_submit_urb(urb, gfp); + if (err) + goto err; + usb_anchor_urb(urb, &devinfo->cmd_urbs); + + return 0; + +err: + usb_free_urb(urb); + return err; +} + +/* + * Why should I request the Status IU before sending the Command IU? Spec + * says to, but also says the device may receive them in any order. Seems + * daft to me. + */ + +static int uas_submit_sense_urb(struct Scsi_Host *shost, + gfp_t gfp, unsigned int stream) +{ + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct urb *urb; + + urb = uas_alloc_sense_urb(devinfo, gfp, shost, stream); + if (!urb) + return SCSI_MLQUEUE_DEVICE_BUSY; + if (usb_submit_urb(urb, gfp)) { + shost_printk(KERN_INFO, shost, + "sense urb submission failure\n"); + usb_free_urb(urb); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + usb_anchor_urb(urb, &devinfo->sense_urbs); + return 0; +} + +static int uas_submit_urbs(struct scsi_cmnd *cmnd, + struct uas_dev_info *devinfo, gfp_t gfp) +{ + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + int err; + + WARN_ON(!spin_is_locked(&devinfo->lock)); + if (cmdinfo->state & SUBMIT_STATUS_URB) { + err = uas_submit_sense_urb(cmnd->device->host, gfp, + cmdinfo->stream); + if (err) { + return err; + } + cmdinfo->state &= ~SUBMIT_STATUS_URB; + } + + if (cmdinfo->state & ALLOC_DATA_IN_URB) { + cmdinfo->data_in_urb = uas_alloc_data_urb(devinfo, gfp, + devinfo->data_in_pipe, cmdinfo->stream, + cmnd, DMA_FROM_DEVICE); + if (!cmdinfo->data_in_urb) + return SCSI_MLQUEUE_DEVICE_BUSY; + cmdinfo->state &= ~ALLOC_DATA_IN_URB; + } + + if (cmdinfo->state & SUBMIT_DATA_IN_URB) { + if (usb_submit_urb(cmdinfo->data_in_urb, gfp)) { + scmd_printk(KERN_INFO, cmnd, + "data in urb submission failure\n"); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + cmdinfo->state &= ~SUBMIT_DATA_IN_URB; + cmdinfo->state |= DATA_IN_URB_INFLIGHT; + usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); + } + + if (cmdinfo->state & ALLOC_DATA_OUT_URB) { + cmdinfo->data_out_urb = uas_alloc_data_urb(devinfo, gfp, + devinfo->data_out_pipe, cmdinfo->stream, + cmnd, DMA_TO_DEVICE); + if (!cmdinfo->data_out_urb) + return SCSI_MLQUEUE_DEVICE_BUSY; + cmdinfo->state &= ~ALLOC_DATA_OUT_URB; + } + + if (cmdinfo->state & SUBMIT_DATA_OUT_URB) { + if (usb_submit_urb(cmdinfo->data_out_urb, gfp)) { + scmd_printk(KERN_INFO, cmnd, + "data out urb submission failure\n"); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + cmdinfo->state &= ~SUBMIT_DATA_OUT_URB; + cmdinfo->state |= DATA_OUT_URB_INFLIGHT; + usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); + } + + if (cmdinfo->state & ALLOC_CMD_URB) { + cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd, + cmdinfo->stream); + if (!cmdinfo->cmd_urb) + return SCSI_MLQUEUE_DEVICE_BUSY; + cmdinfo->state &= ~ALLOC_CMD_URB; + } + + if (cmdinfo->state & SUBMIT_CMD_URB) { + usb_get_urb(cmdinfo->cmd_urb); + if (usb_submit_urb(cmdinfo->cmd_urb, gfp)) { + scmd_printk(KERN_INFO, cmnd, + "cmd urb submission failure\n"); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); + usb_put_urb(cmdinfo->cmd_urb); + cmdinfo->cmd_urb = NULL; + cmdinfo->state &= ~SUBMIT_CMD_URB; + cmdinfo->state |= COMMAND_INFLIGHT; + } + + return 0; +} + +static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, + void (*done)(struct scsi_cmnd *)) +{ + struct scsi_device *sdev = cmnd->device; + struct uas_dev_info *devinfo = sdev->hostdata; + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + unsigned long flags; + int err; + + BUILD_BUG_ON(sizeof(struct uas_cmd_info) > sizeof(struct scsi_pointer)); + + if (devinfo->resetting) { + cmnd->result = DID_ERROR << 16; + cmnd->scsi_done(cmnd); + return 0; + } + + spin_lock_irqsave(&devinfo->lock, flags); + if (devinfo->cmnd) { + spin_unlock_irqrestore(&devinfo->lock, flags); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + + if (blk_rq_tagged(cmnd->request)) { + cmdinfo->stream = cmnd->request->tag + 2; + } else { + devinfo->cmnd = cmnd; + cmdinfo->stream = 1; + } + + cmnd->scsi_done = done; + + cmdinfo->state = SUBMIT_STATUS_URB | + ALLOC_CMD_URB | SUBMIT_CMD_URB; + + switch (cmnd->sc_data_direction) { + case DMA_FROM_DEVICE: + cmdinfo->state |= ALLOC_DATA_IN_URB | SUBMIT_DATA_IN_URB; + break; + case DMA_BIDIRECTIONAL: + cmdinfo->state |= ALLOC_DATA_IN_URB | SUBMIT_DATA_IN_URB; + case DMA_TO_DEVICE: + cmdinfo->state |= ALLOC_DATA_OUT_URB | SUBMIT_DATA_OUT_URB; + case DMA_NONE: + break; + } + + if (!devinfo->use_streams) { + cmdinfo->state &= ~(SUBMIT_DATA_IN_URB | SUBMIT_DATA_OUT_URB); + cmdinfo->stream = 0; + } + + err = uas_submit_urbs(cmnd, devinfo, GFP_ATOMIC); + if (err) { + /* If we did nothing, give up now */ + if (cmdinfo->state & SUBMIT_STATUS_URB) { + spin_unlock_irqrestore(&devinfo->lock, flags); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + spin_lock(&uas_work_lock); + list_add_tail(&cmdinfo->list, &uas_work_list); + cmdinfo->state |= IS_IN_WORK_LIST; + spin_unlock(&uas_work_lock); + schedule_work(&uas_work); + } + + spin_unlock_irqrestore(&devinfo->lock, flags); + return 0; +} + +static DEF_SCSI_QCMD(uas_queuecommand) + +static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, + const char *fname, u8 function) +{ + struct Scsi_Host *shost = cmnd->device->host; + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + u16 tag = devinfo->qdepth - 1; + unsigned long flags; + + spin_lock_irqsave(&devinfo->lock, flags); + memset(&devinfo->response, 0, sizeof(devinfo->response)); + if (uas_submit_sense_urb(shost, GFP_ATOMIC, tag)) { + shost_printk(KERN_INFO, shost, + "%s: %s: submit sense urb failed\n", + __func__, fname); + spin_unlock_irqrestore(&devinfo->lock, flags); + return FAILED; + } + if (uas_submit_task_urb(cmnd, GFP_ATOMIC, function, tag)) { + shost_printk(KERN_INFO, shost, + "%s: %s: submit task mgmt urb failed\n", + __func__, fname); + spin_unlock_irqrestore(&devinfo->lock, flags); + return FAILED; + } + spin_unlock_irqrestore(&devinfo->lock, flags); + + if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 3000) == 0) { + shost_printk(KERN_INFO, shost, + "%s: %s timed out\n", __func__, fname); + return FAILED; + } + if (be16_to_cpu(devinfo->response.tag) != tag) { + shost_printk(KERN_INFO, shost, + "%s: %s failed (wrong tag %d/%d)\n", __func__, + fname, be16_to_cpu(devinfo->response.tag), tag); + return FAILED; + } + if (devinfo->response.response_code != RC_TMF_COMPLETE) { + shost_printk(KERN_INFO, shost, + "%s: %s failed (rc 0x%x)\n", __func__, + fname, devinfo->response.response_code); + return FAILED; + } + return SUCCESS; +} + +static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) +{ + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + unsigned long flags; + int ret; + + uas_log_cmd_state(cmnd, __func__); + spin_lock_irqsave(&devinfo->lock, flags); + cmdinfo->state |= COMMAND_ABORTED; + if (cmdinfo->state & IS_IN_WORK_LIST) { + spin_lock(&uas_work_lock); + list_del(&cmdinfo->list); + cmdinfo->state &= ~IS_IN_WORK_LIST; + spin_unlock(&uas_work_lock); + } + if (cmdinfo->state & COMMAND_INFLIGHT) { + spin_unlock_irqrestore(&devinfo->lock, flags); + ret = uas_eh_task_mgmt(cmnd, "ABORT TASK", TMF_ABORT_TASK); + } else { + spin_unlock_irqrestore(&devinfo->lock, flags); + uas_unlink_data_urbs(devinfo, cmdinfo); + spin_lock_irqsave(&devinfo->lock, flags); + uas_try_complete(cmnd, __func__); + spin_unlock_irqrestore(&devinfo->lock, flags); + ret = SUCCESS; + } + return ret; +} + +static int uas_eh_device_reset_handler(struct scsi_cmnd *cmnd) +{ + sdev_printk(KERN_INFO, cmnd->device, "%s\n", __func__); + return uas_eh_task_mgmt(cmnd, "LOGICAL UNIT RESET", + TMF_LOGICAL_UNIT_RESET); +} + +static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) +{ + struct scsi_device *sdev = cmnd->device; + struct uas_dev_info *devinfo = sdev->hostdata; + struct usb_device *udev = devinfo->udev; + int err; + + devinfo->resetting = 1; + uas_abort_work(devinfo); + usb_kill_anchored_urbs(&devinfo->cmd_urbs); + usb_kill_anchored_urbs(&devinfo->sense_urbs); + usb_kill_anchored_urbs(&devinfo->data_urbs); + err = usb_reset_device(udev); + devinfo->resetting = 0; + + if (err) { + shost_printk(KERN_INFO, sdev->host, "%s FAILED\n", __func__); + return FAILED; + } + + shost_printk(KERN_INFO, sdev->host, "%s success\n", __func__); + return SUCCESS; +} + +static int uas_slave_alloc(struct scsi_device *sdev) +{ + sdev->hostdata = (void *)sdev->host->hostdata[0]; + return 0; +} + +static int uas_slave_configure(struct scsi_device *sdev) +{ + struct uas_dev_info *devinfo = sdev->hostdata; + scsi_set_tag_type(sdev, MSG_ORDERED_TAG); + scsi_activate_tcq(sdev, devinfo->qdepth - 3); + return 0; +} + +static struct scsi_host_template uas_host_template = { + .module = THIS_MODULE, + .name = "uas", + .queuecommand = uas_queuecommand, + .slave_alloc = uas_slave_alloc, + .slave_configure = uas_slave_configure, + .eh_abort_handler = uas_eh_abort_handler, + .eh_device_reset_handler = uas_eh_device_reset_handler, + .eh_bus_reset_handler = uas_eh_bus_reset_handler, + .can_queue = 65536, /* Is there a limit on the _host_ ? */ + .this_id = -1, + .sg_tablesize = SG_NONE, + .cmd_per_lun = 1, /* until we override it */ + .skip_settle_delay = 1, + .ordered_tag = 1, +}; + +static struct usb_device_id uas_usb_ids[] = { + { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_BULK) }, + { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_UAS) }, + /* 0xaa is a prototype device I happen to have access to */ + { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, 0xaa) }, + { } +}; +MODULE_DEVICE_TABLE(usb, uas_usb_ids); + +static int uas_is_interface(struct usb_host_interface *intf) +{ + return (intf->desc.bInterfaceClass == USB_CLASS_MASS_STORAGE && + intf->desc.bInterfaceSubClass == USB_SC_SCSI && + intf->desc.bInterfaceProtocol == USB_PR_UAS); +} + +static int uas_isnt_supported(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + dev_warn(&udev->dev, "The driver for the USB controller %s does not " + "support scatter-gather which is\n", + hcd->driver->description); + dev_warn(&udev->dev, "required by the UAS driver. Please try an" + "alternative USB controller if you wish to use UAS.\n"); + return -ENODEV; +} + +static int uas_switch_interface(struct usb_device *udev, + struct usb_interface *intf) +{ + int i; + int sg_supported = udev->bus->sg_tablesize != 0; + + for (i = 0; i < intf->num_altsetting; i++) { + struct usb_host_interface *alt = &intf->altsetting[i]; + + if (uas_is_interface(alt)) { + if (!sg_supported) + return uas_isnt_supported(udev); + return usb_set_interface(udev, + alt->desc.bInterfaceNumber, + alt->desc.bAlternateSetting); + } + } + + return -ENODEV; +} + +static void uas_configure_endpoints(struct uas_dev_info *devinfo) +{ + struct usb_host_endpoint *eps[4] = { }; + struct usb_interface *intf = devinfo->intf; + struct usb_device *udev = devinfo->udev; + struct usb_host_endpoint *endpoint = intf->cur_altsetting->endpoint; + unsigned i, n_endpoints = intf->cur_altsetting->desc.bNumEndpoints; + + devinfo->uas_sense_old = 0; + devinfo->cmnd = NULL; + + for (i = 0; i < n_endpoints; i++) { + unsigned char *extra = endpoint[i].extra; + int len = endpoint[i].extralen; + while (len > 1) { + if (extra[1] == USB_DT_PIPE_USAGE) { + unsigned pipe_id = extra[2]; + if (pipe_id > 0 && pipe_id < 5) + eps[pipe_id - 1] = &endpoint[i]; + break; + } + len -= extra[0]; + extra += extra[0]; + } + } + + /* + * Assume that if we didn't find a control pipe descriptor, we're + * using a device with old firmware that happens to be set up like + * this. + */ + if (!eps[0]) { + devinfo->cmd_pipe = usb_sndbulkpipe(udev, 1); + devinfo->status_pipe = usb_rcvbulkpipe(udev, 1); + devinfo->data_in_pipe = usb_rcvbulkpipe(udev, 2); + devinfo->data_out_pipe = usb_sndbulkpipe(udev, 2); + + eps[1] = usb_pipe_endpoint(udev, devinfo->status_pipe); + eps[2] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); + eps[3] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); + } else { + devinfo->cmd_pipe = usb_sndbulkpipe(udev, + eps[0]->desc.bEndpointAddress); + devinfo->status_pipe = usb_rcvbulkpipe(udev, + eps[1]->desc.bEndpointAddress); + devinfo->data_in_pipe = usb_rcvbulkpipe(udev, + eps[2]->desc.bEndpointAddress); + devinfo->data_out_pipe = usb_sndbulkpipe(udev, + eps[3]->desc.bEndpointAddress); + } + + devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, 3, 256, + GFP_KERNEL); + if (devinfo->qdepth < 0) { + devinfo->qdepth = 256; + devinfo->use_streams = 0; + } else { + devinfo->use_streams = 1; + } +} + +static void uas_free_streams(struct uas_dev_info *devinfo) +{ + struct usb_device *udev = devinfo->udev; + struct usb_host_endpoint *eps[3]; + + eps[0] = usb_pipe_endpoint(udev, devinfo->status_pipe); + eps[1] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); + eps[2] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); + usb_free_streams(devinfo->intf, eps, 3, GFP_KERNEL); +} + +/* + * XXX: What I'd like to do here is register a SCSI host for each USB host in + * the system. Follow usb-storage's design of registering a SCSI host for + * each USB device for the moment. Can implement this by walking up the + * USB hierarchy until we find a USB host. + */ +static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + int result; + struct Scsi_Host *shost; + struct uas_dev_info *devinfo; + struct usb_device *udev = interface_to_usbdev(intf); + + if (uas_switch_interface(udev, intf)) + return -ENODEV; + + devinfo = kmalloc(sizeof(struct uas_dev_info), GFP_KERNEL); + if (!devinfo) + return -ENOMEM; + + result = -ENOMEM; + shost = scsi_host_alloc(&uas_host_template, sizeof(void *)); + if (!shost) + goto free; + + shost->max_cmd_len = 16 + 252; + shost->max_id = 1; + shost->max_lun = 256; + shost->max_channel = 0; + shost->sg_tablesize = udev->bus->sg_tablesize; + + devinfo->intf = intf; + devinfo->udev = udev; + devinfo->resetting = 0; + init_usb_anchor(&devinfo->cmd_urbs); + init_usb_anchor(&devinfo->sense_urbs); + init_usb_anchor(&devinfo->data_urbs); + spin_lock_init(&devinfo->lock); + uas_configure_endpoints(devinfo); + + result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 3); + if (result) + goto free; + + result = scsi_add_host(shost, &intf->dev); + if (result) + goto deconfig_eps; + + shost->hostdata[0] = (unsigned long)devinfo; + + scsi_scan_host(shost); + usb_set_intfdata(intf, shost); + return result; + +deconfig_eps: + uas_free_streams(devinfo); + free: + kfree(devinfo); + if (shost) + scsi_host_put(shost); + return result; +} + +static int uas_pre_reset(struct usb_interface *intf) +{ +/* XXX: Need to return 1 if it's not our device in error handling */ + return 0; +} + +static int uas_post_reset(struct usb_interface *intf) +{ +/* XXX: Need to return 1 if it's not our device in error handling */ + return 0; +} + +static void uas_disconnect(struct usb_interface *intf) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + + devinfo->resetting = 1; + uas_abort_work(devinfo); + usb_kill_anchored_urbs(&devinfo->cmd_urbs); + usb_kill_anchored_urbs(&devinfo->sense_urbs); + usb_kill_anchored_urbs(&devinfo->data_urbs); + scsi_remove_host(shost); + uas_free_streams(devinfo); + kfree(devinfo); +} + +/* + * XXX: Should this plug into libusual so we can auto-upgrade devices from + * Bulk-Only to UAS? + */ +static struct usb_driver uas_driver = { + .name = "uas", + .probe = uas_probe, + .disconnect = uas_disconnect, + .pre_reset = uas_pre_reset, + .post_reset = uas_post_reset, + .id_table = uas_usb_ids, +}; + +module_usb_driver(uas_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matthew Wilcox and Sarah Sharp"); diff --git a/drivers/usb/storage/unusual_alauda.h b/drivers/usb/storage/unusual_alauda.h new file mode 100644 index 00000000..fa3e9eda --- /dev/null +++ b/drivers/usb/storage/unusual_alauda.h @@ -0,0 +1,31 @@ +/* Unusual Devices File for the Alauda-based card readers + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_ALAUDA) || \ + defined(CONFIG_USB_STORAGE_ALAUDA_MODULE) + +UNUSUAL_DEV( 0x0584, 0x0008, 0x0102, 0x0102, + "Fujifilm", + "DPC-R1 (Alauda)", + USB_SC_SCSI, USB_PR_ALAUDA, init_alauda, 0), + +UNUSUAL_DEV( 0x07b4, 0x010a, 0x0102, 0x0102, + "Olympus", + "MAUSB-10 (Alauda)", + USB_SC_SCSI, USB_PR_ALAUDA, init_alauda, 0), + +#endif /* defined(CONFIG_USB_STORAGE_ALAUDA) || ... */ diff --git a/drivers/usb/storage/unusual_cypress.h b/drivers/usb/storage/unusual_cypress.h new file mode 100644 index 00000000..82e8ed03 --- /dev/null +++ b/drivers/usb/storage/unusual_cypress.h @@ -0,0 +1,39 @@ +/* Unusual Devices File for devices based on the Cypress USB/ATA bridge + * with support for ATACB + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_CYPRESS_ATACB) || \ + defined(CONFIG_USB_STORAGE_CYPRESS_ATACB_MODULE) + +/* CY7C68300 : support atacb */ +UNUSUAL_DEV( 0x04b4, 0x6830, 0x0000, 0x9999, + "Cypress", + "Cypress AT2LP", + USB_SC_CYP_ATACB, USB_PR_DEVICE, NULL, 0), + +/* CY7C68310 : support atacb and atacb2 */ +UNUSUAL_DEV( 0x04b4, 0x6831, 0x0000, 0x9999, + "Cypress", + "Cypress ISD-300LP", + USB_SC_CYP_ATACB, USB_PR_DEVICE, NULL, 0), + +UNUSUAL_DEV( 0x14cd, 0x6116, 0x0160, 0x0160, + "Super Top", + "USB 2.0 SATA BRIDGE", + USB_SC_CYP_ATACB, USB_PR_DEVICE, NULL, 0), + +#endif /* defined(CONFIG_USB_STORAGE_CYPRESS_ATACB) || ... */ diff --git a/drivers/usb/storage/unusual_datafab.h b/drivers/usb/storage/unusual_datafab.h new file mode 100644 index 00000000..582a603c --- /dev/null +++ b/drivers/usb/storage/unusual_datafab.h @@ -0,0 +1,98 @@ +/* Unusual Devices File for the Datafab USB Compact Flash reader + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_DATAFAB) || \ + defined(CONFIG_USB_STORAGE_DATAFAB_MODULE) + +UNUSUAL_DEV( 0x07c4, 0xa000, 0x0000, 0x0015, + "Datafab", + "MDCFE-B USB CF Reader", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + 0), + +/* + * The following Datafab-based devices may or may not work + * using the current driver...the 0xffff is arbitrary since I + * don't know what device versions exist for these guys. + * + * The 0xa003 and 0xa004 devices in particular I'm curious about. + * I'm told they exist but so far nobody has come forward to say that + * they work with this driver. Given the success we've had getting + * other Datafab-based cards operational with this driver, I've decided + * to leave these two devices in the list. + */ +UNUSUAL_DEV( 0x07c4, 0xa001, 0x0000, 0xffff, + "SIIG/Datafab", + "SIIG/Datafab Memory Stick+CF Reader/Writer", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + 0), + +/* Reported by Josef Reisinger */ +UNUSUAL_DEV( 0x07c4, 0xa002, 0x0000, 0xffff, + "Datafab/Unknown", + "MD2/MD3 Disk enclosure", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + US_FL_SINGLE_LUN), + +UNUSUAL_DEV( 0x07c4, 0xa003, 0x0000, 0xffff, + "Datafab/Unknown", + "Datafab-based Reader", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + 0), + +UNUSUAL_DEV( 0x07c4, 0xa004, 0x0000, 0xffff, + "Datafab/Unknown", + "Datafab-based Reader", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + 0), + +UNUSUAL_DEV( 0x07c4, 0xa005, 0x0000, 0xffff, + "PNY/Datafab", + "PNY/Datafab CF+SM Reader", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + 0), + +UNUSUAL_DEV( 0x07c4, 0xa006, 0x0000, 0xffff, + "Simple Tech/Datafab", + "Simple Tech/Datafab CF+SM Reader", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + 0), + +/* Submitted by Olaf Hering */ +UNUSUAL_DEV( 0x07c4, 0xa109, 0x0000, 0xffff, + "Datafab Systems, Inc.", + "USB to CF + SM Combo (LC1)", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + 0), + +/* Reported by Felix Moeller + * in Germany this is sold by Hama with the productnumber 46952 + * as "DualSlot CompactFlash(TM) & MStick Drive USB" + */ +UNUSUAL_DEV( 0x07c4, 0xa10b, 0x0000, 0xffff, + "DataFab Systems Inc.", + "USB CF+MS", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + 0), + +UNUSUAL_DEV( 0x0c0b, 0xa109, 0x0000, 0xffff, + "Acomdata", + "CF", + USB_SC_SCSI, USB_PR_DATAFAB, NULL, + US_FL_SINGLE_LUN), + +#endif /* defined(CONFIG_USB_STORAGE_DATAFAB) || ... */ diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h new file mode 100644 index 00000000..7f625306 --- /dev/null +++ b/drivers/usb/storage/unusual_devs.h @@ -0,0 +1,2163 @@ +/* Driver for USB Mass Storage compliant devices + * Unusual Devices File + * + * Current development and maintenance by: + * (c) 2000-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Initial work by: + * (c) 2000 Adam J. Richter (adam@yggdrasil.com), Yggdrasil Computing, Inc. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* IMPORTANT NOTE: This file must be included in another file which does + * the following thing for it to work: + * The UNUSUAL_DEV, COMPLIANT_DEV, and USUAL_DEV macros must be defined + * before this file is included. + */ + +/* If you edit this file, please try to keep it sorted first by VendorID, + * then by ProductID. + * + * If you want to add an entry for this file, be sure to include the + * following information: + * - a patch that adds the entry for your device, including your + * email address right above the entry (plus maybe a brief + * explanation of the reason for the entry), + * - a copy of /proc/bus/usb/devices with your device plugged in + * running with this patch. + * Send your submission to either Phil Dibowitz or + * Alan Stern , and don't forget to CC: the + * USB development list and the USB storage list + * + */ + +/* Note: If you add an entry only in order to set the CAPACITY_OK flag, + * use the COMPLIANT_DEV macro instead of UNUSUAL_DEV. This is + * because such entries mark devices which actually work correctly, + * as opposed to devices that do something strangely or wrongly. + */ + +/* In-kernel mode switching is deprecated. Do not add new devices to + * this list for the sole purpose of switching them to a different + * mode. Existing userspace solutions are superior. + * + * New mode switching devices should instead be added to the database + * maintained at http://www.draisberghof.de/usb_modeswitch/ + */ + +#if !defined(CONFIG_USB_STORAGE_SDDR09) && \ + !defined(CONFIG_USB_STORAGE_SDDR09_MODULE) +#define NO_SDDR09 +#endif + +/* patch submitted by Vivian Bregier + */ +UNUSUAL_DEV( 0x03eb, 0x2002, 0x0100, 0x0100, + "ATMEL", + "SND1 Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE), + +/* Reported by Rodolfo Quesada */ +UNUSUAL_DEV( 0x03ee, 0x6906, 0x0003, 0x0003, + "VIA Technologies Inc.", + "Mitsumi multi cardreader", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +UNUSUAL_DEV( 0x03f0, 0x0107, 0x0200, 0x0200, + "HP", + "CD-Writer+", + USB_SC_8070, USB_PR_CB, NULL, 0), + +/* Reported by Ben Efros */ +UNUSUAL_DEV( 0x03f0, 0x070c, 0x0000, 0x0000, + "HP", + "Personal Media Drive", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SANE_SENSE ), + +/* Reported by Grant Grundler + * HP r707 camera in "Disk" mode with 2.00.23 or 2.00.24 firmware. + */ +UNUSUAL_DEV( 0x03f0, 0x4002, 0x0001, 0x0001, + "HP", + "PhotoSmart R707", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_FIX_CAPACITY), + +UNUSUAL_DEV( 0x03f3, 0x0001, 0x0000, 0x9999, + "Adaptec", + "USBConnect 2000", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +/* Reported by Sebastian Kapfer + * and Olaf Hering (different bcd's, same vendor/product) + * for USB floppies that need the SINGLE_LUN enforcement. + */ +UNUSUAL_DEV( 0x0409, 0x0040, 0x0000, 0x9999, + "NEC", + "NEC USB UF000x", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* Patch submitted by Mihnea-Costin Grigore */ +UNUSUAL_DEV( 0x040d, 0x6205, 0x0003, 0x0003, + "VIA Technologies Inc.", + "USB 2.0 Card Reader", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Deduced by Jonathan Woithe + * Entry needed for flags: US_FL_FIX_INQUIRY because initial inquiry message + * always fails and confuses drive. + */ +UNUSUAL_DEV( 0x0411, 0x001c, 0x0113, 0x0113, + "Buffalo", + "DUB-P40G HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* Submitted by Ernestas Vaiciukevicius */ +UNUSUAL_DEV( 0x0419, 0x0100, 0x0100, 0x0100, + "Samsung Info. Systems America, Inc.", + "MP3 Player", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Orgad Shaneh */ +UNUSUAL_DEV( 0x0419, 0xaace, 0x0100, 0x0100, + "Samsung", "MP3 Player", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Christian Leber */ +UNUSUAL_DEV( 0x0419, 0xaaf5, 0x0100, 0x0100, + "TrekStor", + "i.Beat 115 2.0", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_NOT_LOCKABLE ), + +/* Reported by Stefan Werner */ +UNUSUAL_DEV( 0x0419, 0xaaf6, 0x0100, 0x0100, + "TrekStor", + "i.Beat Joy 2.0", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Pete Zaitcev , bz#176584 */ +UNUSUAL_DEV( 0x0420, 0x0001, 0x0100, 0x0100, + "GENERIC", "MP3 PLAYER", /* MyMusix PD-205 on the outside. */ + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Andrew Nayenko + * Updated for new firmware by Phillip Potter */ +UNUSUAL_DEV( 0x0421, 0x0019, 0x0592, 0x0610, + "Nokia", + "Nokia 6288", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* Reported by Mario Rettig */ +UNUSUAL_DEV( 0x0421, 0x042e, 0x0100, 0x0100, + "Nokia", + "Nokia 3250", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_FIX_CAPACITY ), + +/* Reported by */ +UNUSUAL_DEV( 0x0421, 0x0433, 0x0100, 0x0100, + "Nokia", + "E70", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_FIX_CAPACITY ), + +/* Reported by Jon Hart */ +UNUSUAL_DEV( 0x0421, 0x0434, 0x0100, 0x0100, + "Nokia", + "E60", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_IGNORE_RESIDUE ), + +/* Reported by Sumedha Swamy and + * Einar Th. Einarsson */ +UNUSUAL_DEV( 0x0421, 0x0444, 0x0100, 0x0100, + "Nokia", + "N91", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_FIX_CAPACITY ), + +/* Reported by Jiri Slaby and + * Rene C. Castberg */ +UNUSUAL_DEV( 0x0421, 0x0446, 0x0100, 0x0100, + "Nokia", + "N80", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_FIX_CAPACITY ), + +/* Reported by Matthew Bloch */ +UNUSUAL_DEV( 0x0421, 0x044e, 0x0100, 0x0100, + "Nokia", + "E61", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_FIX_CAPACITY ), + +/* Reported by Bardur Arantsson */ +UNUSUAL_DEV( 0x0421, 0x047c, 0x0370, 0x0610, + "Nokia", + "6131", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* Reported by Manuel Osdoba */ +UNUSUAL_DEV( 0x0421, 0x0492, 0x0452, 0x9999, + "Nokia", + "Nokia 6233", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* Reported by Alex Corcoles */ +UNUSUAL_DEV( 0x0421, 0x0495, 0x0370, 0x0370, + "Nokia", + "6234", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* Reported by Daniele Forsi */ +UNUSUAL_DEV( 0x0421, 0x04b9, 0x0350, 0x0350, + "Nokia", + "5300", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* Patch submitted by Victor A. Santos */ +UNUSUAL_DEV( 0x0421, 0x05af, 0x0742, 0x0742, + "Nokia", + "305", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64), + +/* Patch submitted by Mikhail Zolotaryov */ +UNUSUAL_DEV( 0x0421, 0x06aa, 0x1110, 0x1110, + "Nokia", + "502", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +#ifdef NO_SDDR09 +UNUSUAL_DEV( 0x0436, 0x0005, 0x0100, 0x0100, + "Microtech", + "CameraMate", + USB_SC_SCSI, USB_PR_CB, NULL, + US_FL_SINGLE_LUN ), +#endif + +/* Patch submitted by Daniel Drake + * Device reports nonsense bInterfaceProtocol 6 when connected over USB2 */ +UNUSUAL_DEV( 0x0451, 0x5416, 0x0100, 0x0100, + "Neuros Audio", + "USB 2.0 HD 2.5", + USB_SC_DEVICE, USB_PR_BULK, NULL, + US_FL_NEED_OVERRIDE ), + +/* + * Pete Zaitcev , from Patrick C. F. Ernzer, bz#162559. + * The key does not actually break, but it returns zero sense which + * makes our SCSI stack to print confusing messages. + */ +UNUSUAL_DEV( 0x0457, 0x0150, 0x0100, 0x0100, + "USBest Technology", /* sold by Transcend */ + "USB Mass Storage Device", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_NOT_LOCKABLE ), + +/* +* Bohdan Linda +* 1GB USB sticks MyFlash High Speed. I have restricted +* the revision to my model only +*/ +UNUSUAL_DEV( 0x0457, 0x0151, 0x0100, 0x0100, + "USB 2.0", + "Flash Disk", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NOT_LOCKABLE ), + +/* Reported by Tamas Kerecsen + * Obviously the PROM has not been customized by the VAR; + * the Vendor and Product string descriptors are: + * Generic Mass Storage (PROTOTYPE--Remember to change idVendor) + * Generic Manufacturer (PROTOTYPE--Remember to change idVendor) + */ +UNUSUAL_DEV( 0x045e, 0xffff, 0x0000, 0x0000, + "Mitac", + "GPS", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* + * This virtual floppy is found in Sun equipment (x4600, x4200m2, etc.) + * Reported by Pete Zaitcev + * This device chokes on both version of MODE SENSE which we have, so + * use_10_for_ms is not effective, and we use US_FL_NO_WP_DETECT. + */ +UNUSUAL_DEV( 0x046b, 0xff40, 0x0100, 0x0100, + "AMI", + "Virtual Floppy", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_WP_DETECT), + +/* Patch submitted by Philipp Friedrich */ +UNUSUAL_DEV( 0x0482, 0x0100, 0x0100, 0x0100, + "Kyocera", + "Finecam S3x", + USB_SC_8070, USB_PR_CB, NULL, US_FL_FIX_INQUIRY), + +/* Patch submitted by Philipp Friedrich */ +UNUSUAL_DEV( 0x0482, 0x0101, 0x0100, 0x0100, + "Kyocera", + "Finecam S4", + USB_SC_8070, USB_PR_CB, NULL, US_FL_FIX_INQUIRY), + +/* Patch submitted by Stephane Galles */ +UNUSUAL_DEV( 0x0482, 0x0103, 0x0100, 0x0100, + "Kyocera", + "Finecam S5", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_FIX_INQUIRY), + +/* Patch submitted by Jens Taprogge */ +UNUSUAL_DEV( 0x0482, 0x0107, 0x0100, 0x0100, + "Kyocera", + "CONTAX SL300R T*", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_NOT_LOCKABLE), + +/* Reported by Paul Stewart + * This entry is needed because the device reports Sub=ff */ +UNUSUAL_DEV( 0x04a4, 0x0004, 0x0001, 0x0001, + "Hitachi", + "DVD-CAM DZ-MV100A Camcorder", + USB_SC_SCSI, USB_PR_CB, NULL, US_FL_SINGLE_LUN), + +/* BENQ DC5330 + * Reported by Manuel Fombuena and + * Frank Copeland */ +UNUSUAL_DEV( 0x04a5, 0x3010, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "300_CAMERA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Patch for Nikon coolpix 2000 + * Submitted by Fabien Cosse */ +UNUSUAL_DEV( 0x04b0, 0x0301, 0x0010, 0x0010, + "NIKON", + "NIKON DSC E2000", + USB_SC_DEVICE, USB_PR_DEVICE,NULL, + US_FL_NOT_LOCKABLE ), + +/* Reported by Doug Maxey (dwm@austin.ibm.com) */ +UNUSUAL_DEV( 0x04b3, 0x4001, 0x0110, 0x0110, + "IBM", + "IBM RSA2", + USB_SC_DEVICE, USB_PR_CB, NULL, + US_FL_MAX_SECTORS_MIN), + +/* Reported by Simon Levitt + * This entry needs Sub and Proto fields */ +UNUSUAL_DEV( 0x04b8, 0x0601, 0x0100, 0x0100, + "Epson", + "875DC Storage", + USB_SC_SCSI, USB_PR_CB, NULL, US_FL_FIX_INQUIRY), + +/* Reported by Khalid Aziz + * This entry is needed because the device reports Sub=ff */ +UNUSUAL_DEV( 0x04b8, 0x0602, 0x0110, 0x0110, + "Epson", + "785EPX Storage", + USB_SC_SCSI, USB_PR_BULK, NULL, US_FL_SINGLE_LUN), + +/* Not sure who reported this originally but + * Pavel Machek reported that the extra US_FL_SINGLE_LUN + * flag be added */ +UNUSUAL_DEV( 0x04cb, 0x0100, 0x0000, 0x2210, + "Fujifilm", + "FinePix 1400Zoom", + USB_SC_UFI, USB_PR_DEVICE, NULL, US_FL_FIX_INQUIRY | US_FL_SINGLE_LUN), + +/* Reported by Ondrej Zary + * The device reports one sector more and breaks when that sector is accessed + */ +UNUSUAL_DEV( 0x04ce, 0x0002, 0x026c, 0x026c, + "ScanLogic", + "SL11R-IDE", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + +/* Reported by Kriston Fincher + * Patch submitted by Sean Millichamp + * This is to support the Panasonic PalmCam PV-SD4090 + * This entry is needed because the device reports Sub=ff + */ +UNUSUAL_DEV( 0x04da, 0x0901, 0x0100, 0x0200, + "Panasonic", + "LS-120 Camera", + USB_SC_UFI, USB_PR_DEVICE, NULL, 0), + +/* From Yukihiro Nakai, via zaitcev@yahoo.com. + * This is needed for CB instead of CBI */ +UNUSUAL_DEV( 0x04da, 0x0d05, 0x0000, 0x0000, + "Sharp CE-CW05", + "CD-R/RW Drive", + USB_SC_8070, USB_PR_CB, NULL, 0), + +/* Reported by Adriaan Penning */ +UNUSUAL_DEV( 0x04da, 0x2372, 0x0000, 0x9999, + "Panasonic", + "DMC-LCx Camera", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_NOT_LOCKABLE ), + +/* Reported by Simeon Simeonov */ +UNUSUAL_DEV( 0x04da, 0x2373, 0x0000, 0x9999, + "LEICA", + "D-LUX Camera", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_NOT_LOCKABLE ), + +/* Most of the following entries were developed with the help of + * Shuttle/SCM directly. + */ +UNUSUAL_DEV( 0x04e6, 0x0001, 0x0200, 0x0200, + "Matshita", + "LS-120", + USB_SC_8020, USB_PR_CB, NULL, 0), + +UNUSUAL_DEV( 0x04e6, 0x0002, 0x0100, 0x0100, + "Shuttle", + "eUSCSI Bridge", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +#ifdef NO_SDDR09 +UNUSUAL_DEV( 0x04e6, 0x0005, 0x0100, 0x0208, + "SCM Microsystems", + "eUSB CompactFlash Adapter", + USB_SC_SCSI, USB_PR_CB, NULL, + US_FL_SINGLE_LUN), +#endif + +/* Reported by Markus Demleitner */ +UNUSUAL_DEV( 0x04e6, 0x0006, 0x0100, 0x0100, + "SCM Microsystems Inc.", + "eUSB MMC Adapter", + USB_SC_SCSI, USB_PR_CB, NULL, + US_FL_SINGLE_LUN), + +/* Reported by Daniel Nouri */ +UNUSUAL_DEV( 0x04e6, 0x0006, 0x0205, 0x0205, + "Shuttle", + "eUSB MMC Adapter", + USB_SC_SCSI, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN), + +UNUSUAL_DEV( 0x04e6, 0x0007, 0x0100, 0x0200, + "Sony", + "Hifd", + USB_SC_SCSI, USB_PR_CB, NULL, + US_FL_SINGLE_LUN), + +UNUSUAL_DEV( 0x04e6, 0x0009, 0x0200, 0x0200, + "Shuttle", + "eUSB ATA/ATAPI Adapter", + USB_SC_8020, USB_PR_CB, NULL, 0), + +UNUSUAL_DEV( 0x04e6, 0x000a, 0x0200, 0x0200, + "Shuttle", + "eUSB CompactFlash Adapter", + USB_SC_8020, USB_PR_CB, NULL, 0), + +UNUSUAL_DEV( 0x04e6, 0x000B, 0x0100, 0x0100, + "Shuttle", + "eUSCSI Bridge", + USB_SC_SCSI, USB_PR_BULK, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +UNUSUAL_DEV( 0x04e6, 0x000C, 0x0100, 0x0100, + "Shuttle", + "eUSCSI Bridge", + USB_SC_SCSI, USB_PR_BULK, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +UNUSUAL_DEV( 0x04e6, 0x0101, 0x0200, 0x0200, + "Shuttle", + "CD-RW Device", + USB_SC_8020, USB_PR_CB, NULL, 0), + +/* Reported by Dmitry Khlystov */ +UNUSUAL_DEV( 0x04e8, 0x507c, 0x0220, 0x0220, + "Samsung", + "YP-U3", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64), + +/* Reported by Vitaly Kuznetsov */ +UNUSUAL_DEV( 0x04e8, 0x5122, 0x0000, 0x9999, + "Samsung", + "YP-CP3", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 | US_FL_BULK_IGNORE_TAG), + +/* Added by Dmitry Artamonow */ +UNUSUAL_DEV( 0x04e8, 0x5136, 0x0000, 0x9999, + "Samsung", + "YP-Z3", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64), + +/* Entry and supporting patch by Theodore Kilgore . + * Device uses standards-violating 32-byte Bulk Command Block Wrappers and + * reports itself as "Proprietary SCSI Bulk." Cf. device entry 0x084d:0x0011. + */ +UNUSUAL_DEV( 0x04fc, 0x80c2, 0x0100, 0x0100, + "Kobian Mercury", + "Binocam DCB-132", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BULK32), + +/* Reported by Bob Sass -- only rev 1.33 tested */ +UNUSUAL_DEV( 0x050d, 0x0115, 0x0133, 0x0133, + "Belkin", + "USB SCSI Adaptor", + USB_SC_SCSI, USB_PR_BULK, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +/* Iomega Clik! Drive + * Reported by David Chatenay + * The reason this is needed is not fully known. + */ +UNUSUAL_DEV( 0x0525, 0xa140, 0x0100, 0x0100, + "Iomega", + "USB Clik! 40", + USB_SC_8070, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* Added by Alan Stern */ +COMPLIANT_DEV(0x0525, 0xa4a5, 0x0000, 0x9999, + "Linux", + "File-backed Storage Gadget", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_CAPACITY_OK ), + +/* Yakumo Mega Image 37 + * Submitted by Stephan Fuhrmann */ +UNUSUAL_DEV( 0x052b, 0x1801, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "300_CAMERA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Another Yakumo camera. + * Reported by Michele Alzetta */ +UNUSUAL_DEV( 0x052b, 0x1804, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "300_CAMERA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Iacopo Spalletti */ +UNUSUAL_DEV( 0x052b, 0x1807, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "300_CAMERA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Yakumo Mega Image 47 + * Reported by Bjoern Paetzel */ +UNUSUAL_DEV( 0x052b, 0x1905, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "400_CAMERA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Paul Ortyl + * Note that it's similar to the device above, only different prodID */ +UNUSUAL_DEV( 0x052b, 0x1911, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "400_CAMERA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +UNUSUAL_DEV( 0x054c, 0x0010, 0x0106, 0x0450, + "Sony", + "DSC-S30/S70/S75/505V/F505/F707/F717/P8", + USB_SC_SCSI, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN | US_FL_NOT_LOCKABLE | US_FL_NO_WP_DETECT ), + +/* Submitted by Lars Jacob + * This entry is needed because the device reports Sub=ff */ +UNUSUAL_DEV( 0x054c, 0x0010, 0x0500, 0x0610, + "Sony", + "DSC-T1/T5/H5", + USB_SC_8070, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + + +/* Reported by wim@geeks.nl */ +UNUSUAL_DEV( 0x054c, 0x0025, 0x0100, 0x0100, + "Sony", + "Memorystick NW-MS7", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* Submitted by Olaf Hering, SuSE Bugzilla #49049 */ +UNUSUAL_DEV( 0x054c, 0x002c, 0x0501, 0x2000, + "Sony", + "USB Floppy Drive", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +UNUSUAL_DEV( 0x054c, 0x002d, 0x0100, 0x0100, + "Sony", + "Memorystick MSAC-US1", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* Submitted by Klaus Mueller */ +UNUSUAL_DEV( 0x054c, 0x002e, 0x0106, 0x0310, + "Sony", + "Handycam", + USB_SC_SCSI, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* Submitted by Rajesh Kumble Nayak */ +UNUSUAL_DEV( 0x054c, 0x002e, 0x0500, 0x0500, + "Sony", + "Handycam HC-85", + USB_SC_UFI, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +UNUSUAL_DEV( 0x054c, 0x0032, 0x0000, 0x9999, + "Sony", + "Memorystick MSC-U01N", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* Submitted by Michal Mlotek */ +UNUSUAL_DEV( 0x054c, 0x0058, 0x0000, 0x9999, + "Sony", + "PEG N760c Memorystick", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +UNUSUAL_DEV( 0x054c, 0x0069, 0x0000, 0x9999, + "Sony", + "Memorystick MSC-U03", + USB_SC_UFI, USB_PR_CB, NULL, + US_FL_SINGLE_LUN ), + +/* Submitted by Nathan Babb */ +UNUSUAL_DEV( 0x054c, 0x006d, 0x0000, 0x9999, + "Sony", + "PEG Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* Submitted by Frank Engel */ +UNUSUAL_DEV( 0x054c, 0x0099, 0x0000, 0x9999, + "Sony", + "PEG Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* Submitted by Mike Alborn */ +UNUSUAL_DEV( 0x054c, 0x016a, 0x0000, 0x9999, + "Sony", + "PEG Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* Submitted by Ren Bigcren */ +UNUSUAL_DEV( 0x054c, 0x02a5, 0x0100, 0x0100, + "Sony Corp.", + "MicroVault Flash Drive", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_READ_CAPACITY_16 ), + +/* floppy reports multiple luns */ +UNUSUAL_DEV( 0x055d, 0x2020, 0x0000, 0x0210, + "SAMSUNG", + "SFD-321U [FW 0C]", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* We keep this entry to force the transport; firmware 3.00 and later is ok. */ +UNUSUAL_DEV( 0x057b, 0x0000, 0x0000, 0x0299, + "Y-E Data", + "Flashbuster-U", + USB_SC_DEVICE, USB_PR_CB, NULL, + US_FL_SINGLE_LUN), + +/* Reported by Johann Cardon + * This entry is needed only because the device reports + * bInterfaceClass = 0xff (vendor-specific) + */ +UNUSUAL_DEV( 0x057b, 0x0022, 0x0000, 0x9999, + "Y-E Data", + "Silicon Media R/W", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, 0), + +/* Reported by RTE */ +UNUSUAL_DEV( 0x058f, 0x6387, 0x0141, 0x0141, + "JetFlash", + "TS1GJF2A/120", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* Fabrizio Fellini */ +UNUSUAL_DEV( 0x0595, 0x4343, 0x0000, 0x2210, + "Fujifilm", + "Digital Camera EX-20 DSC", + USB_SC_8070, USB_PR_DEVICE, NULL, 0 ), + +/* Reported by Andre Welter + * This antique device predates the release of the Bulk-only Transport + * spec, and if it gets a Get-Max-LUN then it requires the host to do a + * Clear-Halt on the bulk endpoints. The SINGLE_LUN flag will prevent + * us from sending the request. + */ +UNUSUAL_DEV( 0x059b, 0x0001, 0x0100, 0x0100, + "Iomega", + "ZIP 100", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +UNUSUAL_DEV( 0x059b, 0x0040, 0x0100, 0x0100, + "Iomega", + "Jaz USB Adapter", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* Reported by */ +UNUSUAL_DEV( 0x059f, 0x0643, 0x0000, 0x0000, + "LaCie", + "DVD+-RW", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_GO_SLOW ), + +/* Submitted by Joel Bourquard + * Some versions of this device need the SubClass and Protocol overrides + * while others don't. + */ +UNUSUAL_DEV( 0x05ab, 0x0060, 0x1104, 0x1110, + "In-System", + "PyroGate External CD-ROM Enclosure (FCD-523)", + USB_SC_SCSI, USB_PR_BULK, NULL, + US_FL_NEED_OVERRIDE ), + +/* Submitted by Sven Anderson + * There are at least four ProductIDs used for iPods, so I added 0x1202 and + * 0x1204. They just need the US_FL_FIX_CAPACITY. As the bcdDevice appears + * to change with firmware updates, I changed the range to maximum for all + * iPod entries. + */ +UNUSUAL_DEV( 0x05ac, 0x1202, 0x0000, 0x9999, + "Apple", + "iPod", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +/* Reported by Avi Kivity */ +UNUSUAL_DEV( 0x05ac, 0x1203, 0x0000, 0x9999, + "Apple", + "iPod", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +UNUSUAL_DEV( 0x05ac, 0x1204, 0x0000, 0x9999, + "Apple", + "iPod", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_NOT_LOCKABLE ), + +UNUSUAL_DEV( 0x05ac, 0x1205, 0x0000, 0x9999, + "Apple", + "iPod", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +/* + * Reported by Tyson Vinson + * This particular productId is the iPod Nano + */ +UNUSUAL_DEV( 0x05ac, 0x120a, 0x0000, 0x9999, + "Apple", + "iPod", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +/* Reported by Dan Williams + * Option N.V. mobile broadband modems + * Ignore driver CD mode and force into modem mode by default. + */ + +/* Globetrotter HSDPA; mass storage shows up as Qualcomm for vendor */ +UNUSUAL_DEV( 0x05c6, 0x1000, 0x0000, 0x9999, + "Option N.V.", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, option_ms_init, + 0), + +/* Reported by Blake Matheny */ +UNUSUAL_DEV( 0x05dc, 0xb002, 0x0000, 0x0113, + "Lexar", + "USB CF Reader", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* The following two entries are for a Genesys USB to IDE + * converter chip, but it changes its ProductId depending + * on whether or not a disk or an optical device is enclosed + * They were originally reported by Alexander Oltu + * and Peter Marks + * respectively. + * + * US_FL_GO_SLOW and US_FL_MAX_SECTORS_64 added by Phil Dibowitz + * as these flags were made and hard-coded + * special-cases were pulled from scsiglue.c. + */ +UNUSUAL_DEV( 0x05e3, 0x0701, 0x0000, 0xffff, + "Genesys Logic", + "USB to IDE Optical", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_GO_SLOW | US_FL_MAX_SECTORS_64 | US_FL_IGNORE_RESIDUE ), + +UNUSUAL_DEV( 0x05e3, 0x0702, 0x0000, 0xffff, + "Genesys Logic", + "USB to IDE Disk", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_GO_SLOW | US_FL_MAX_SECTORS_64 | US_FL_IGNORE_RESIDUE ), + +/* Reported by Ben Efros */ +UNUSUAL_DEV( 0x05e3, 0x0723, 0x9451, 0x9451, + "Genesys Logic", + "USB to SATA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SANE_SENSE ), + +/* Reported by Hanno Boeck + * Taken from the Lycoris Kernel */ +UNUSUAL_DEV( 0x0636, 0x0003, 0x0000, 0x9999, + "Vivitar", + "Vivicam 35Xx", + USB_SC_SCSI, USB_PR_BULK, NULL, + US_FL_FIX_INQUIRY ), + +UNUSUAL_DEV( 0x0644, 0x0000, 0x0100, 0x0100, + "TEAC", + "Floppy Drive", + USB_SC_UFI, USB_PR_CB, NULL, 0 ), + +/* Reported by Darsen Lu */ +UNUSUAL_DEV( 0x066f, 0x8000, 0x0001, 0x0001, + "SigmaTel", + "USBMSC Audio Player", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +/* Reported by Daniel Kukula */ +UNUSUAL_DEV( 0x067b, 0x1063, 0x0100, 0x0100, + "Prolific Technology, Inc.", + "Prolific Storage Gadget", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BAD_SENSE ), + +/* Reported by Rogerio Brito */ +UNUSUAL_DEV( 0x067b, 0x2317, 0x0001, 0x001, + "Prolific Technology, Inc.", + "Mass Storage Device", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NOT_LOCKABLE ), + +/* Reported by Richard -=[]=- */ +/* Change to bcdDeviceMin (0x0100 to 0x0001) reported by + * Thomas Bartosik */ +UNUSUAL_DEV( 0x067b, 0x2507, 0x0001, 0x0100, + "Prolific Technology Inc.", + "Mass Storage Device", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_GO_SLOW ), + +/* Reported by Alex Butcher */ +UNUSUAL_DEV( 0x067b, 0x3507, 0x0001, 0x0101, + "Prolific Technology Inc.", + "ATAPI-6 Bridge Controller", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_GO_SLOW ), + +/* Submitted by Benny Sjostrand */ +UNUSUAL_DEV( 0x0686, 0x4011, 0x0001, 0x0001, + "Minolta", + "Dimage F300", + USB_SC_SCSI, USB_PR_BULK, NULL, 0 ), + +/* Reported by Miguel A. Fosas */ +UNUSUAL_DEV( 0x0686, 0x4017, 0x0001, 0x0001, + "Minolta", + "DIMAGE E223", + USB_SC_SCSI, USB_PR_DEVICE, NULL, 0 ), + +UNUSUAL_DEV( 0x0693, 0x0005, 0x0100, 0x0100, + "Hagiwara", + "Flashgate", + USB_SC_SCSI, USB_PR_BULK, NULL, 0 ), + +/* Reported by David Hamilton */ +UNUSUAL_DEV( 0x069b, 0x3004, 0x0001, 0x0001, + "Thomson Multimedia Inc.", + "RCA RD1080 MP3 Player", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +/* Reported by Adrian Pilchowiec */ +UNUSUAL_DEV( 0x071b, 0x3203, 0x0000, 0x0000, + "RockChip", + "MP3", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_WP_DETECT | US_FL_MAX_SECTORS_64 | + US_FL_NO_READ_CAPACITY_16), + +/* Reported by Jean-Baptiste Onofre + * Support the following product : + * "Dane-Elec MediaTouch" + */ +UNUSUAL_DEV( 0x071b, 0x32bb, 0x0000, 0x0000, + "RockChip", + "MTP", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_WP_DETECT | US_FL_MAX_SECTORS_64), + +/* Reported by Massimiliano Ghilardi + * This USB MP3/AVI player device fails and disconnects if more than 128 + * sectors (64kB) are read/written in a single command, and may be present + * at least in the following products: + * "Magnex Digital Video Panel DVP 1800" + * "MP4 AIGO 4GB SLOT SD" + * "Teclast TL-C260 MP3" + * "i.Meizu PMP MP3/MP4" + * "Speed MV8 MP4 Audio Player" + */ +UNUSUAL_DEV( 0x071b, 0x3203, 0x0100, 0x0100, + "RockChip", + "ROCK MP3", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64), + +/* Reported by Olivier Blondeau */ +UNUSUAL_DEV( 0x0727, 0x0306, 0x0100, 0x0100, + "ATMEL", + "SND1 Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE), + +/* Submitted by Roman Hodek */ +UNUSUAL_DEV( 0x0781, 0x0001, 0x0200, 0x0200, + "Sandisk", + "ImageMate SDDR-05a", + USB_SC_SCSI, USB_PR_CB, NULL, + US_FL_SINGLE_LUN ), + +UNUSUAL_DEV( 0x0781, 0x0002, 0x0009, 0x0009, + "SanDisk Corporation", + "ImageMate CompactFlash USB", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +UNUSUAL_DEV( 0x0781, 0x0100, 0x0100, 0x0100, + "Sandisk", + "ImageMate SDDR-12", + USB_SC_SCSI, USB_PR_CB, NULL, + US_FL_SINGLE_LUN ), + +/* Reported by Eero Volotinen */ +UNUSUAL_DEV( 0x07ab, 0xfccd, 0x0000, 0x9999, + "Freecom Technologies", + "FHD-Classic", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + +UNUSUAL_DEV( 0x07af, 0x0004, 0x0100, 0x0133, + "Microtech", + "USB-SCSI-DB25", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +UNUSUAL_DEV( 0x07af, 0x0005, 0x0100, 0x0100, + "Microtech", + "USB-SCSI-HD50", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +#ifdef NO_SDDR09 +UNUSUAL_DEV( 0x07af, 0x0006, 0x0100, 0x0100, + "Microtech", + "CameraMate", + USB_SC_SCSI, USB_PR_CB, NULL, + US_FL_SINGLE_LUN ), +#endif + +/* Datafab KECF-USB / Sagatek DCS-CF / Simpletech Flashlink UCF-100 + * Only revision 1.13 tested (same for all of the above devices, + * based on the Datafab DF-UG-07 chip). Needed for US_FL_FIX_INQUIRY. + * Submitted by Marek Michalkiewicz . + * See also http://martin.wilck.bei.t-online.de/#kecf . + */ +UNUSUAL_DEV( 0x07c4, 0xa400, 0x0000, 0xffff, + "Datafab", + "KECF-USB", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY | US_FL_FIX_CAPACITY ), + +/* Reported by Rauch Wolke + * and augmented by binbin (Bugzilla #12882) + */ +UNUSUAL_DEV( 0x07c4, 0xa4a5, 0x0000, 0xffff, + "Simple Tech/Datafab", + "CF+SM Reader", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_MAX_SECTORS_64 ), + +/* Casio QV 2x00/3x00/4000/8000 digital still cameras are not conformant + * to the USB storage specification in two ways: + * - They tell us they are using transport protocol CBI. In reality they + * are using transport protocol CB. + * - They don't like the INQUIRY command. So we must handle this command + * of the SCSI layer ourselves. + * - Some cameras with idProduct=0x1001 and bcdDevice=0x1000 have + * bInterfaceProtocol=0x00 (USB_PR_CBI) while others have 0x01 (USB_PR_CB). + * So don't remove the USB_PR_CB override! + * - Cameras with bcdDevice=0x9009 require the USB_SC_8070 override. + */ +UNUSUAL_DEV( 0x07cf, 0x1001, 0x1000, 0x9999, + "Casio", + "QV DigitalCamera", + USB_SC_8070, USB_PR_CB, NULL, + US_FL_NEED_OVERRIDE | US_FL_FIX_INQUIRY ), + +/* Submitted by Oleksandr Chumachenko */ +UNUSUAL_DEV( 0x07cf, 0x1167, 0x0100, 0x0100, + "Casio", + "EX-N1 DigitalCamera", + USB_SC_8070, USB_PR_DEVICE, NULL, 0), + +/* Submitted by Hartmut Wahl */ +UNUSUAL_DEV( 0x0839, 0x000a, 0x0001, 0x0001, + "Samsung", + "Digimax 410", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY), + +/* Reported by Luciano Rocha */ +UNUSUAL_DEV( 0x0840, 0x0082, 0x0001, 0x0001, + "Argosy", + "Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + +/* Reported and patched by Nguyen Anh Quynh */ +UNUSUAL_DEV( 0x0840, 0x0084, 0x0001, 0x0001, + "Argosy", + "Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + +/* Reported by Martijn Hijdra */ +UNUSUAL_DEV( 0x0840, 0x0085, 0x0001, 0x0001, + "Argosy", + "Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + +/* Entry and supporting patch by Theodore Kilgore . + * Flag will support Bulk devices which use a standards-violating 32-byte + * Command Block Wrapper. Here, the "DC2MEGA" cameras (several brands) with + * Grandtech GT892x chip, which request "Proprietary SCSI Bulk" support. + */ + +UNUSUAL_DEV( 0x084d, 0x0011, 0x0110, 0x0110, + "Grandtech", + "DC2MEGA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BULK32), + +/* Reported by + * The device reports a vendor-specific device class, requiring an + * explicit vendor/product match. + */ +UNUSUAL_DEV( 0x0851, 0x1542, 0x0002, 0x0002, + "MagicPixel", + "FW_Omega2", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, 0), + +/* Andrew Lunn + * PanDigital Digital Picture Frame. Does not like ALLOW_MEDIUM_REMOVAL + * on LUN 4. + * Note: Vend:Prod clash with "Ltd Maxell WS30 Slim Digital Camera" +*/ +UNUSUAL_DEV( 0x0851, 0x1543, 0x0200, 0x0200, + "PanDigital", + "Photo Frame", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NOT_LOCKABLE), + +UNUSUAL_DEV( 0x085a, 0x0026, 0x0100, 0x0133, + "Xircom", + "PortGear USB-SCSI (Mac USB Dock)", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +UNUSUAL_DEV( 0x085a, 0x0028, 0x0100, 0x0133, + "Xircom", + "PortGear USB to SCSI Converter", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +/* Submitted by Jan De Luyck */ +UNUSUAL_DEV( 0x08bd, 0x1100, 0x0000, 0x0000, + "CITIZEN", + "X1DE-USB", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN), + +/* Submitted by Dylan Taft + * US_FL_IGNORE_RESIDUE Needed + */ +UNUSUAL_DEV( 0x08ca, 0x3103, 0x0100, 0x0100, + "AIPTEK", + "Aiptek USB Keychain MP3 Player", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE), + +/* Entry needed for flags. Moreover, all devices with this ID use + * bulk-only transport, but _some_ falsely report Control/Bulk instead. + * One example is "Trumpion Digital Research MYMP3". + * Submitted by Bjoern Brill + */ +UNUSUAL_DEV( 0x090a, 0x1001, 0x0100, 0x0100, + "Trumpion", + "t33520 USB Flash Card Controller", + USB_SC_DEVICE, USB_PR_BULK, NULL, + US_FL_NEED_OVERRIDE ), + +/* Reported by Filippo Bardelli + * The device reports a subclass of RBC, which is wrong. + */ +UNUSUAL_DEV( 0x090a, 0x1050, 0x0100, 0x0100, + "Trumpion Microelectronics, Inc.", + "33520 USB Digital Voice Recorder", + USB_SC_UFI, USB_PR_DEVICE, NULL, + 0), + +/* Trumpion Microelectronics MP3 player (felipe_alfaro@linuxmail.org) */ +UNUSUAL_DEV( 0x090a, 0x1200, 0x0000, 0x9999, + "Trumpion", + "MP3 player", + USB_SC_RBC, USB_PR_BULK, NULL, + 0 ), + +/* aeb */ +UNUSUAL_DEV( 0x090c, 0x1132, 0x0000, 0xffff, + "Feiya", + "5-in-1 Card Reader", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +/* Reported by Paul Hartman + * This card reader returns "Illegal Request, Logical Block Address + * Out of Range" for the first READ(10) after a new card is inserted. + */ +UNUSUAL_DEV( 0x090c, 0x6000, 0x0100, 0x0100, + "Feiya", + "SD/SDHC Card Reader", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_INITIAL_READ10 ), + +/* This Pentax still camera is not conformant + * to the USB storage specification: - + * - It does not like the INQUIRY command. So we must handle this command + * of the SCSI layer ourselves. + * Tested on Rev. 10.00 (0x1000) + * Submitted by James Courtier-Dutton + */ +UNUSUAL_DEV( 0x0a17, 0x0004, 0x1000, 0x1000, + "Pentax", + "Optio 2/3/400", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* These are virtual windows driver CDs, which the zd1211rw driver + * automatically converts into WLAN devices. */ +UNUSUAL_DEV( 0x0ace, 0x2011, 0x0101, 0x0101, + "ZyXEL", + "G-220F USB-WLAN Install", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_DEVICE ), + +UNUSUAL_DEV( 0x0ace, 0x20ff, 0x0101, 0x0101, + "SiteCom", + "WL-117 USB-WLAN Install", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_DEVICE ), + +/* Reported by Dan Williams + * Option N.V. mobile broadband modems + * Ignore driver CD mode and force into modem mode by default. + */ + +/* iCON 225 */ +UNUSUAL_DEV( 0x0af0, 0x6971, 0x0000, 0x9999, + "Option N.V.", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, option_ms_init, + 0), + +/* Reported by F. Aben + * This device (wrongly) has a vendor-specific device descriptor. + * The entry is needed so usb-storage can bind to it's mass-storage + * interface as an interface driver */ +UNUSUAL_DEV( 0x0af0, 0x7401, 0x0000, 0x0000, + "Option", + "GI 0401 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +/* Reported by Jan Dumon + * These devices (wrongly) have a vendor-specific device descriptor. + * These entries are needed so usb-storage can bind to their mass-storage + * interface as an interface driver */ +UNUSUAL_DEV( 0x0af0, 0x7501, 0x0000, 0x0000, + "Option", + "GI 0431 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0x7701, 0x0000, 0x0000, + "Option", + "GI 0451 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0x7706, 0x0000, 0x0000, + "Option", + "GI 0451 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0x7901, 0x0000, 0x0000, + "Option", + "GI 0452 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0x7A01, 0x0000, 0x0000, + "Option", + "GI 0461 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0x7A05, 0x0000, 0x0000, + "Option", + "GI 0461 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0x8300, 0x0000, 0x0000, + "Option", + "GI 033x SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0x8302, 0x0000, 0x0000, + "Option", + "GI 033x SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0x8304, 0x0000, 0x0000, + "Option", + "GI 033x SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0xc100, 0x0000, 0x0000, + "Option", + "GI 070x SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0xd057, 0x0000, 0x0000, + "Option", + "GI 1505 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0xd058, 0x0000, 0x0000, + "Option", + "GI 1509 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0xd157, 0x0000, 0x0000, + "Option", + "GI 1515 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0xd257, 0x0000, 0x0000, + "Option", + "GI 1215 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +UNUSUAL_DEV( 0x0af0, 0xd357, 0x0000, 0x0000, + "Option", + "GI 1505 SD-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0 ), + +/* Reported by Namjae Jeon */ +UNUSUAL_DEV(0x0bc2, 0x2300, 0x0000, 0x9999, + "Seagate", + "Portable HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_WRITE_CACHE), + +/* Reported by Ben Efros */ +UNUSUAL_DEV( 0x0bc2, 0x3010, 0x0000, 0x0000, + "Seagate", + "FreeAgent Pro", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SANE_SENSE ), + +UNUSUAL_DEV( 0x0d49, 0x7310, 0x0000, 0x9999, + "Maxtor", + "USB to SATA", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SANE_SENSE), + +/* + * Pete Zaitcev , bz#164688. + * The device blatantly ignores LUN and returns 1 in GetMaxLUN. + */ +UNUSUAL_DEV( 0x0c45, 0x1060, 0x0100, 0x0100, + "Unknown", + "Unknown", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* Submitted by Joris Struyve */ +UNUSUAL_DEV( 0x0d96, 0x410a, 0x0001, 0xffff, + "Medion", + "MD 7425", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY), + +/* + * Entry for Jenoptik JD 5200z3 + * + * email: car.busse@gmx.de + */ +UNUSUAL_DEV( 0x0d96, 0x5200, 0x0001, 0x0200, + "Jenoptik", + "JD 5200 z3", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_FIX_INQUIRY), + +/* Reported by Jason Johnston */ +UNUSUAL_DEV( 0x0dc4, 0x0073, 0x0000, 0x0000, + "Macpower Technology Co.LTD.", + "USB 2.0 3.5\" DEVICE", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + +/* Reported by Lubomir Blaha + * I _REALLY_ don't know what 3rd, 4th number and all defines mean, but this + * works for me. Can anybody correct these values? (I able to test corrected + * version.) + */ +UNUSUAL_DEV( 0x0dd8, 0x1060, 0x0000, 0xffff, + "Netac", + "USB-CF-Card", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* Reported by Edward Chapman (taken from linux-usb mailing list) + Netac OnlyDisk Mini U2CV2 512MB USB 2.0 Flash Drive */ +UNUSUAL_DEV( 0x0dd8, 0xd202, 0x0000, 0x9999, + "Netac", + "USB Flash Disk", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + + +/* Patch by Stephan Walter + * I don't know why, but it works... */ +UNUSUAL_DEV( 0x0dda, 0x0001, 0x0012, 0x0012, + "WINWARD", + "Music Disk", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Ian McConnell */ +UNUSUAL_DEV( 0x0dda, 0x0301, 0x0012, 0x0012, + "PNP_MP3", + "PNP_MP3 PLAYER", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Jim McCloskey */ +UNUSUAL_DEV( 0x0e21, 0x0520, 0x0100, 0x0100, + "Cowon Systems", + "iAUDIO M5", + USB_SC_DEVICE, USB_PR_BULK, NULL, + US_FL_NEED_OVERRIDE ), + +/* Submitted by Antoine Mairesse */ +UNUSUAL_DEV( 0x0ed1, 0x6660, 0x0100, 0x0300, + "USB", + "Solid state disk", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY ), + +/* Submitted by Daniel Drake + * Reported by dayul on the Gentoo Forums */ +UNUSUAL_DEV( 0x0ea0, 0x2168, 0x0110, 0x0110, + "Ours Technology", + "Flash Disk", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Rastislav Stanik */ +UNUSUAL_DEV( 0x0ea0, 0x6828, 0x0110, 0x0110, + "USB", + "Flash Disk", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Benjamin Schiller + * It is also sold by Easylite as DJ 20 */ +UNUSUAL_DEV( 0x0ed1, 0x7636, 0x0103, 0x0103, + "Typhoon", + "My DJ 1820", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_GO_SLOW | US_FL_MAX_SECTORS_64), + +/* Patch by Leonid Petrov mail at lpetrov.net + * Reported by Robert Spitzenpfeil + * http://www.qbik.ch/usb/devices/showdev.php?id=1705 + * Updated to 103 device by MJ Ray mjr at phonecoop.coop + */ +UNUSUAL_DEV( 0x0f19, 0x0103, 0x0100, 0x0100, + "Oracom Co., Ltd", + "ORC-200M", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* David Kuehling : + * for MP3-Player AVOX WSX-300ER (bought in Japan). Reports lots of SCSI + * errors when trying to write. + */ +UNUSUAL_DEV( 0x0f19, 0x0105, 0x0100, 0x0100, + "C-MEX", + "A-VOX", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Submitted by Nick Holloway */ +UNUSUAL_DEV( 0x0f88, 0x042e, 0x0100, 0x0100, + "VTech", + "Kidizoom", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + +/* Reported by Moritz Moeller-Herrmann */ +UNUSUAL_DEV( 0x0fca, 0x8004, 0x0201, 0x0201, + "Research In Motion", + "BlackBerry Bold 9000", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* Reported by Michael Stattmann */ +UNUSUAL_DEV( 0x0fce, 0xd008, 0x0000, 0x0000, + "Sony Ericsson", + "V800-Vodafone 802", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_WP_DETECT ), + +/* Reported by The Solutor */ +UNUSUAL_DEV( 0x0fce, 0xd0e1, 0x0000, 0x0000, + "Sony Ericsson", + "MD400", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_DEVICE), + +/* Reported by Jan Mate + * and by Soeren Sonnenburg */ +UNUSUAL_DEV( 0x0fce, 0xe030, 0x0000, 0x0000, + "Sony Ericsson", + "P990i", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_IGNORE_RESIDUE ), + +/* Reported by Emmanuel Vasilakis */ +UNUSUAL_DEV( 0x0fce, 0xe031, 0x0000, 0x0000, + "Sony Ericsson", + "M600i", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_FIX_CAPACITY ), + +/* Reported by Ricardo Barberis */ +UNUSUAL_DEV( 0x0fce, 0xe092, 0x0000, 0x0000, + "Sony Ericsson", + "P1i", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Kevin Cernekee + * Tested on hardware version 1.10. + * Entry is needed only for the initializer function override. + * Devices with bcd > 110 seem to not need it while those + * with bcd < 110 appear to need it. + */ +UNUSUAL_DEV( 0x1019, 0x0c55, 0x0000, 0x0110, + "Desknote", + "UCR-61S2B", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_ucr61s2b_init, + 0 ), + +UNUSUAL_DEV( 0x1058, 0x0704, 0x0000, 0x9999, + "Western Digital", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SANE_SENSE), + +/* Reported by Namjae Jeon */ +UNUSUAL_DEV(0x1058, 0x070a, 0x0000, 0x9999, + "Western Digital", + "My Passport HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_WRITE_CACHE), + +/* Reported by Fabio Venturi + * The device reports a vendor-specific bDeviceClass. + */ +UNUSUAL_DEV( 0x10d6, 0x2200, 0x0100, 0x0100, + "Actions Semiconductor", + "Mtp device", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + 0), + +/* Reported by Pascal Terjan + * Ignore driver CD mode and force into modem mode by default. + */ +UNUSUAL_DEV( 0x1186, 0x3e04, 0x0000, 0x0000, + "D-Link", + "USB Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, option_ms_init, US_FL_IGNORE_DEVICE), + +/* Reported by Kevin Lloyd + * Entry is needed for the initializer function override, + * which instructs the device to load as a modem + * device. + */ +UNUSUAL_DEV( 0x1199, 0x0fff, 0x0000, 0x9999, + "Sierra Wireless", + "USB MMC Storage", + USB_SC_DEVICE, USB_PR_DEVICE, sierra_ms_init, + 0), + +/* Reported by Jaco Kroon + * The usb-storage module found on the Digitech GNX4 (and supposedly other + * devices) misbehaves and causes a bunch of invalid I/O errors. + */ +UNUSUAL_DEV( 0x1210, 0x0003, 0x0100, 0x0100, + "Digitech HMG", + "DigiTech Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by fangxiaozhi + * This brings the HUAWEI data card devices into multi-port mode + */ +UNUSUAL_DEV( 0x12d1, 0x1001, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1003, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1004, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1401, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1402, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1403, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1404, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1405, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1406, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1407, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1408, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1409, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x140A, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x140B, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x140C, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x140D, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x140E, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x140F, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1410, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1411, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1412, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1413, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1414, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1415, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1416, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1417, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1418, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1419, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x141A, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x141B, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x141C, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x141D, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x141E, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x141F, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1420, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1421, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1422, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1423, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1424, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1425, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1426, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1427, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1428, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1429, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x142A, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x142B, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x142C, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x142D, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x142E, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x142F, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1430, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1431, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1432, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1433, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1434, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1435, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1436, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1437, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1438, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x1439, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x143A, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x143B, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x143C, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x143D, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x143E, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), +UNUSUAL_DEV( 0x12d1, 0x143F, 0x0000, 0x0000, + "HUAWEI MOBILE", + "Mass Storage", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_huawei_e220_init, + 0), + +/* Reported by Vilius Bilinkevicius */ +UNUSUAL_DEV( 0x1370, 0x6828, 0x0110, 0x0110, + "SWISSBIT", + "Black Silver", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Qinglin Ye */ +UNUSUAL_DEV( 0x13fe, 0x3600, 0x0100, 0x0100, + "Kingston", + "DT 101 G2", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BULK_IGNORE_TAG ), + +/* Reported by Francesco Foresti */ +UNUSUAL_DEV( 0x14cd, 0x6600, 0x0201, 0x0201, + "Super Top", + "IDE DEVICE", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Alexandre Oliva + * JMicron responds to USN and several other SCSI ioctls with a + * residue that causes subsequent I/O requests to fail. */ +UNUSUAL_DEV( 0x152d, 0x2329, 0x0100, 0x0100, + "JMicron", + "USB to ATA/ATAPI Bridge", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_SANE_SENSE ), + +/* Entrega Technologies U1-SC25 (later Xircom PortGear PGSCSI) + * and Mac USB Dock USB-SCSI */ +UNUSUAL_DEV( 0x1645, 0x0007, 0x0100, 0x0133, + "Entrega Technologies", + "USB to SCSI Converter", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +/* Reported by Robert Schedel + * Note: this is a 'super top' device like the above 14cd/6600 device */ +UNUSUAL_DEV( 0x1652, 0x6600, 0x0201, 0x0201, + "Teac", + "HD-35PUK-B", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Oliver Neukum */ +UNUSUAL_DEV( 0x174c, 0x55aa, 0x0100, 0x0100, + "ASMedia", + "AS2105", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NEEDS_CAP16), + +/* Reported by Jesse Feddema */ +UNUSUAL_DEV( 0x177f, 0x0400, 0x0000, 0x0000, + "Yarvik", + "PMP400", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BULK_IGNORE_TAG | US_FL_MAX_SECTORS_64 ), + +UNUSUAL_DEV( 0x1822, 0x0001, 0x0000, 0x9999, + "Ariston Technologies", + "iConnect USB to SCSI adapter", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +/* Reported by Hans de Goede + * These Appotech controllers are found in Picture Frames, they provide a + * (buggy) emulation of a cdrom drive which contains the windows software + * Uploading of pictures happens over the corresponding /dev/sg device. */ +UNUSUAL_DEV( 0x1908, 0x1315, 0x0000, 0x0000, + "BUILDWIN", + "Photo Frame", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BAD_SENSE ), +UNUSUAL_DEV( 0x1908, 0x1320, 0x0000, 0x0000, + "BUILDWIN", + "Photo Frame", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BAD_SENSE ), +UNUSUAL_DEV( 0x1908, 0x3335, 0x0200, 0x0200, + "BUILDWIN", + "Photo Frame", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_READ_DISC_INFO ), + +/* Reported by Sven Geggus + * This encrypted pen drive returns bogus data for the initial READ(10). + */ +UNUSUAL_DEV( 0x1b1c, 0x1ab5, 0x0200, 0x0200, + "Corsair", + "Padlock v2", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_INITIAL_READ10 ), + +/* Patch by Richard Schütz + * This external hard drive enclosure uses a JMicron chip which + * needs the US_FL_IGNORE_RESIDUE flag to work properly. */ +UNUSUAL_DEV( 0x1e68, 0x001b, 0x0000, 0x0000, + "TrekStor GmbH & Co. KG", + "DataStation maxi g.u", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_SANE_SENSE ), + +/* Reported by Jasper Mackenzie */ +UNUSUAL_DEV( 0x1e74, 0x4621, 0x0000, 0x0000, + "Coby Electronics", + "MP3 Player", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BULK_IGNORE_TAG | US_FL_MAX_SECTORS_64 ), + +UNUSUAL_DEV( 0x2116, 0x0320, 0x0001, 0x0001, + "ST", + "2A", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + +/* patch submitted by Davide Perini + * and Renato Perini + */ +UNUSUAL_DEV( 0x22b8, 0x3010, 0x0001, 0x0001, + "Motorola", + "RAZR V3x", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_IGNORE_RESIDUE ), + +/* + * Patch by Constantin Baranov + * Report by Andreas Koenecke. + * Motorola ROKR Z6. + */ +UNUSUAL_DEV( 0x22b8, 0x6426, 0x0101, 0x0101, + "Motorola", + "MSnc.", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_INQUIRY | US_FL_FIX_CAPACITY | US_FL_BULK_IGNORE_TAG), + +/* Reported by Radovan Garabik */ +UNUSUAL_DEV( 0x2735, 0x100b, 0x0000, 0x9999, + "MPIO", + "HS200", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_GO_SLOW ), + +/* Reported by Frederic Marchal + * Mio Moov 330 + */ +UNUSUAL_DEV( 0x3340, 0xffff, 0x0000, 0x0000, + "Mitac", + "Mio DigiWalker USB Sync", + USB_SC_DEVICE,USB_PR_DEVICE,NULL, + US_FL_MAX_SECTORS_64 ), + +/* Reported by Andrey Rahmatullin */ +UNUSUAL_DEV( 0x4102, 0x1020, 0x0100, 0x0100, + "iRiver", + "MP3 T10", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Reported by Sergey Pinaev */ +UNUSUAL_DEV( 0x4102, 0x1059, 0x0000, 0x0000, + "iRiver", + "P7K", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_MAX_SECTORS_64 ), + +/* + * David Härdeman + * The key makes the SCSI stack print confusing (but harmless) messages + */ +UNUSUAL_DEV( 0x4146, 0xba01, 0x0100, 0x0100, + "Iomega", + "Micro Mini 1GB", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_NOT_LOCKABLE ), + +/* + * Nick Bowler + * SCSI stack spams (otherwise harmless) error messages. + */ +UNUSUAL_DEV( 0xc251, 0x4003, 0x0100, 0x0100, + "Keil Software, Inc.", + "V2M MotherBoard", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NOT_LOCKABLE), + +/* Reported by Andrew Simmons */ +UNUSUAL_DEV( 0xed06, 0x4500, 0x0001, 0x0001, + "DataStor", + "USB4500 FW1.04", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_CAPACITY_HEURISTICS), + +/* Reported by Alessio Treglia */ +UNUSUAL_DEV( 0xed10, 0x7636, 0x0001, 0x0001, + "TGE", + "Digital MP3 Audio Player", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_NOT_LOCKABLE ), + +/* Control/Bulk transport for all SubClass values */ +USUAL_DEV(USB_SC_RBC, USB_PR_CB), +USUAL_DEV(USB_SC_8020, USB_PR_CB), +USUAL_DEV(USB_SC_QIC, USB_PR_CB), +USUAL_DEV(USB_SC_UFI, USB_PR_CB), +USUAL_DEV(USB_SC_8070, USB_PR_CB), +USUAL_DEV(USB_SC_SCSI, USB_PR_CB), + +/* Control/Bulk/Interrupt transport for all SubClass values */ +USUAL_DEV(USB_SC_RBC, USB_PR_CBI), +USUAL_DEV(USB_SC_8020, USB_PR_CBI), +USUAL_DEV(USB_SC_QIC, USB_PR_CBI), +USUAL_DEV(USB_SC_UFI, USB_PR_CBI), +USUAL_DEV(USB_SC_8070, USB_PR_CBI), +USUAL_DEV(USB_SC_SCSI, USB_PR_CBI), + +/* Bulk-only transport for all SubClass values */ +USUAL_DEV(USB_SC_RBC, USB_PR_BULK), +USUAL_DEV(USB_SC_8020, USB_PR_BULK), +USUAL_DEV(USB_SC_QIC, USB_PR_BULK), +USUAL_DEV(USB_SC_UFI, USB_PR_BULK), +USUAL_DEV(USB_SC_8070, USB_PR_BULK), +USUAL_DEV(USB_SC_SCSI, USB_PR_BULK), diff --git a/drivers/usb/storage/unusual_ene_ub6250.h b/drivers/usb/storage/unusual_ene_ub6250.h new file mode 100644 index 00000000..5667f5d3 --- /dev/null +++ b/drivers/usb/storage/unusual_ene_ub6250.h @@ -0,0 +1,26 @@ +/* + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_ENE_UB6250) || \ + defined(CONFIG_USB_STORAGE_ENE_UB6250_MODULE) + +UNUSUAL_DEV(0x0cf2, 0x6250, 0x0000, 0x9999, + "ENE", + "ENE UB6250 reader", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, 0), + +#endif /* defined(CONFIG_USB_STORAGE_ENE_UB6250) || ... */ diff --git a/drivers/usb/storage/unusual_freecom.h b/drivers/usb/storage/unusual_freecom.h new file mode 100644 index 00000000..59a26115 --- /dev/null +++ b/drivers/usb/storage/unusual_freecom.h @@ -0,0 +1,26 @@ +/* Unusual Devices File for the Freecom USB/IDE adaptor + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_FREECOM) || \ + defined(CONFIG_USB_STORAGE_FREECOM_MODULE) + +UNUSUAL_DEV( 0x07ab, 0xfc01, 0x0000, 0x9999, + "Freecom", + "USB-IDE", + USB_SC_QIC, USB_PR_FREECOM, init_freecom, 0), + +#endif /* defined(CONFIG_USB_STORAGE_FREECOM) || ... */ diff --git a/drivers/usb/storage/unusual_isd200.h b/drivers/usb/storage/unusual_isd200.h new file mode 100644 index 00000000..14cca0c4 --- /dev/null +++ b/drivers/usb/storage/unusual_isd200.h @@ -0,0 +1,57 @@ +/* Unusual Devices File for In-System Design, Inc. ISD200 ASIC + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_ISD200) || \ + defined(CONFIG_USB_STORAGE_ISD200_MODULE) + +UNUSUAL_DEV( 0x054c, 0x002b, 0x0100, 0x0110, + "Sony", + "Portable USB Harddrive V2", + USB_SC_ISD200, USB_PR_BULK, isd200_Initialization, + 0), + +UNUSUAL_DEV( 0x05ab, 0x0031, 0x0100, 0x0110, + "In-System", + "USB/IDE Bridge (ATA/ATAPI)", + USB_SC_ISD200, USB_PR_BULK, isd200_Initialization, + 0), + +UNUSUAL_DEV( 0x05ab, 0x0301, 0x0100, 0x0110, + "In-System", + "Portable USB Harddrive V2", + USB_SC_ISD200, USB_PR_BULK, isd200_Initialization, + 0), + +UNUSUAL_DEV( 0x05ab, 0x0351, 0x0100, 0x0110, + "In-System", + "Portable USB Harddrive V2", + USB_SC_ISD200, USB_PR_BULK, isd200_Initialization, + 0), + +UNUSUAL_DEV( 0x05ab, 0x5701, 0x0100, 0x0110, + "In-System", + "USB Storage Adapter V2", + USB_SC_ISD200, USB_PR_BULK, isd200_Initialization, + 0), + +UNUSUAL_DEV( 0x0bf6, 0xa001, 0x0100, 0x0110, + "ATI", + "USB Cable 205", + USB_SC_ISD200, USB_PR_BULK, isd200_Initialization, + 0), + +#endif /* defined(CONFIG_USB_STORAGE_ISD200) || ... */ diff --git a/drivers/usb/storage/unusual_jumpshot.h b/drivers/usb/storage/unusual_jumpshot.h new file mode 100644 index 00000000..54be78b5 --- /dev/null +++ b/drivers/usb/storage/unusual_jumpshot.h @@ -0,0 +1,27 @@ +/* Unusual Devices File for the Lexar "Jumpshot" Compact Flash reader + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_JUMPSHOT) || \ + defined(CONFIG_USB_STORAGE_JUMPSHOT_MODULE) + +UNUSUAL_DEV( 0x05dc, 0x0001, 0x0000, 0x0001, + "Lexar", + "Jumpshot USB CF Reader", + USB_SC_SCSI, USB_PR_JUMPSHOT, NULL, + US_FL_NEED_OVERRIDE), + +#endif /* defined(CONFIG_USB_STORAGE_JUMPSHOT) || ... */ diff --git a/drivers/usb/storage/unusual_karma.h b/drivers/usb/storage/unusual_karma.h new file mode 100644 index 00000000..6df03972 --- /dev/null +++ b/drivers/usb/storage/unusual_karma.h @@ -0,0 +1,26 @@ +/* Unusual Devices File for the Rio Karma + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_KARMA) || \ + defined(CONFIG_USB_STORAGE_KARMA_MODULE) + +UNUSUAL_DEV( 0x045a, 0x5210, 0x0101, 0x0101, + "Rio", + "Rio Karma", + USB_SC_SCSI, USB_PR_KARMA, rio_karma_init, 0), + +#endif /* defined(CONFIG_USB_STORAGE_KARMA) || ... */ diff --git a/drivers/usb/storage/unusual_onetouch.h b/drivers/usb/storage/unusual_onetouch.h new file mode 100644 index 00000000..0abb819c --- /dev/null +++ b/drivers/usb/storage/unusual_onetouch.h @@ -0,0 +1,36 @@ +/* Unusual Devices File for the Maxtor OneTouch USB hard drive's button + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_ONETOUCH) || \ + defined(CONFIG_USB_STORAGE_ONETOUCH_MODULE) + +/* Submitted by: Nick Sillik + * Needed for OneTouch extension to usb-storage + */ +UNUSUAL_DEV( 0x0d49, 0x7000, 0x0000, 0x9999, + "Maxtor", + "OneTouch External Harddrive", + USB_SC_DEVICE, USB_PR_DEVICE, onetouch_connect_input, + 0), + +UNUSUAL_DEV( 0x0d49, 0x7010, 0x0000, 0x9999, + "Maxtor", + "OneTouch External Harddrive", + USB_SC_DEVICE, USB_PR_DEVICE, onetouch_connect_input, + 0), + +#endif /* defined(CONFIG_USB_STORAGE_ONETOUCH) || ... */ diff --git a/drivers/usb/storage/unusual_realtek.h b/drivers/usb/storage/unusual_realtek.h new file mode 100644 index 00000000..e41f50c9 --- /dev/null +++ b/drivers/usb/storage/unusual_realtek.h @@ -0,0 +1,41 @@ +/* Driver for Realtek RTS51xx USB card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + * + * Author: + * wwang (wei_wang@realsil.com.cn) + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#if defined(CONFIG_USB_STORAGE_REALTEK) || \ + defined(CONFIG_USB_STORAGE_REALTEK_MODULE) + +UNUSUAL_DEV(0x0bda, 0x0138, 0x0000, 0x9999, + "Realtek", + "USB Card Reader", + USB_SC_DEVICE, USB_PR_DEVICE, init_realtek_cr, 0), + +UNUSUAL_DEV(0x0bda, 0x0158, 0x0000, 0x9999, + "Realtek", + "USB Card Reader", + USB_SC_DEVICE, USB_PR_DEVICE, init_realtek_cr, 0), + +UNUSUAL_DEV(0x0bda, 0x0159, 0x0000, 0x9999, + "Realtek", + "USB Card Reader", + USB_SC_DEVICE, USB_PR_DEVICE, init_realtek_cr, 0), + +#endif /* defined(CONFIG_USB_STORAGE_REALTEK) || ... */ diff --git a/drivers/usb/storage/unusual_sddr09.h b/drivers/usb/storage/unusual_sddr09.h new file mode 100644 index 00000000..59a7e37b --- /dev/null +++ b/drivers/usb/storage/unusual_sddr09.h @@ -0,0 +1,56 @@ +/* Unusual Devices File for SanDisk SDDR-09 SmartMedia reader + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_SDDR09) || \ + defined(CONFIG_USB_STORAGE_SDDR09_MODULE) + +UNUSUAL_DEV( 0x0436, 0x0005, 0x0100, 0x0100, + "Microtech", + "CameraMate (DPCM_USB)", + USB_SC_SCSI, USB_PR_DPCM_USB, NULL, 0), + +UNUSUAL_DEV( 0x04e6, 0x0003, 0x0000, 0x9999, + "Sandisk", + "ImageMate SDDR09", + USB_SC_SCSI, USB_PR_EUSB_SDDR09, usb_stor_sddr09_init, + 0), + +/* This entry is from Andries.Brouwer@cwi.nl */ +UNUSUAL_DEV( 0x04e6, 0x0005, 0x0100, 0x0208, + "SCM Microsystems", + "eUSB SmartMedia / CompactFlash Adapter", + USB_SC_SCSI, USB_PR_DPCM_USB, usb_stor_sddr09_dpcm_init, + 0), + +UNUSUAL_DEV( 0x066b, 0x0105, 0x0100, 0x0100, + "Olympus", + "Camedia MAUSB-2", + USB_SC_SCSI, USB_PR_EUSB_SDDR09, usb_stor_sddr09_init, + 0), + +UNUSUAL_DEV( 0x0781, 0x0200, 0x0000, 0x9999, + "Sandisk", + "ImageMate SDDR-09", + USB_SC_SCSI, USB_PR_EUSB_SDDR09, usb_stor_sddr09_init, + 0), + +UNUSUAL_DEV( 0x07af, 0x0006, 0x0100, 0x0100, + "Microtech", + "CameraMate (DPCM_USB)", + USB_SC_SCSI, USB_PR_DPCM_USB, NULL, 0), + +#endif /* defined(CONFIG_USB_STORAGE_SDDR09) || ... */ diff --git a/drivers/usb/storage/unusual_sddr55.h b/drivers/usb/storage/unusual_sddr55.h new file mode 100644 index 00000000..fcb7e12c --- /dev/null +++ b/drivers/usb/storage/unusual_sddr55.h @@ -0,0 +1,44 @@ +/* Unusual Devices File for SanDisk SDDR-55 SmartMedia reader + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_SDDR55) || \ + defined(CONFIG_USB_STORAGE_SDDR55_MODULE) + +/* Contributed by Peter Waechtler */ +UNUSUAL_DEV( 0x07c4, 0xa103, 0x0000, 0x9999, + "Datafab", + "MDSM-B reader", + USB_SC_SCSI, USB_PR_SDDR55, NULL, + US_FL_FIX_INQUIRY), + +/* SM part - aeb */ +UNUSUAL_DEV( 0x07c4, 0xa109, 0x0000, 0xffff, + "Datafab Systems, Inc.", + "USB to CF + SM Combo (LC1)", + USB_SC_SCSI, USB_PR_SDDR55, NULL, 0), + +UNUSUAL_DEV( 0x0c0b, 0xa109, 0x0000, 0xffff, + "Acomdata", + "SM", + USB_SC_SCSI, USB_PR_SDDR55, NULL, 0), + +UNUSUAL_DEV( 0x55aa, 0xa103, 0x0000, 0x9999, + "Sandisk", + "ImageMate SDDR55", + USB_SC_SCSI, USB_PR_SDDR55, NULL, 0), + +#endif /* defined(CONFIG_USB_STORAGE_SDDR55) || ... */ diff --git a/drivers/usb/storage/unusual_usbat.h b/drivers/usb/storage/unusual_usbat.h new file mode 100644 index 00000000..38e79c4e --- /dev/null +++ b/drivers/usb/storage/unusual_usbat.h @@ -0,0 +1,43 @@ +/* Unusual Devices File for SCM Microsystems (a.k.a. Shuttle) USB-ATAPI cable + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if defined(CONFIG_USB_STORAGE_USBAT) || \ + defined(CONFIG_USB_STORAGE_USBAT_MODULE) + +UNUSUAL_DEV( 0x03f0, 0x0207, 0x0001, 0x0001, + "HP", + "CD-Writer+ 8200e", + USB_SC_8070, USB_PR_USBAT, init_usbat_cd, 0), + +UNUSUAL_DEV( 0x03f0, 0x0307, 0x0001, 0x0001, + "HP", + "CD-Writer+ CD-4e", + USB_SC_8070, USB_PR_USBAT, init_usbat_cd, 0), + +UNUSUAL_DEV( 0x04e6, 0x1010, 0x0000, 0x9999, + "Shuttle/SCM", + "USBAT-02", + USB_SC_SCSI, USB_PR_USBAT, init_usbat_flash, + US_FL_SINGLE_LUN), + +UNUSUAL_DEV( 0x0781, 0x0005, 0x0005, 0x0005, + "Sandisk", + "ImageMate SDDR-05b", + USB_SC_SCSI, USB_PR_USBAT, init_usbat_flash, + US_FL_SINGLE_LUN), + +#endif /* defined(CONFIG_USB_STORAGE_USBAT) || ... */ diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c new file mode 100644 index 00000000..5c4fe074 --- /dev/null +++ b/drivers/usb/storage/usb.c @@ -0,0 +1,1088 @@ +/* Driver for USB Mass Storage compliant devices + * + * Current development and maintenance by: + * (c) 1999-2003 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Developed with the assistance of: + * (c) 2000 David L. Brown, Jr. (usb-storage@davidb.org) + * (c) 2003-2009 Alan Stern (stern@rowland.harvard.edu) + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * usb_device_id support by Adam J. Richter (adam@yggdrasil.com): + * (c) 2000 Yggdrasil Computing, Inc. + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifdef CONFIG_USB_STORAGE_DEBUG +#define DEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "scsiglue.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" +#include "initializers.h" + +#include "sierra_ms.h" +#include "option_ms.h" + +/* Some informational data */ +MODULE_AUTHOR("Matthew Dharm "); +MODULE_DESCRIPTION("USB Mass Storage driver for Linux"); +MODULE_LICENSE("GPL"); + +static unsigned int delay_use = 1; +module_param(delay_use, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(delay_use, "seconds to delay before using a new device"); + +static char quirks[128]; +module_param_string(quirks, quirks, sizeof(quirks), S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(quirks, "supplemental list of device IDs and their quirks"); + + +/* + * The entries in this table correspond, line for line, + * with the entries in usb_storage_usb_ids[], defined in usual-tables.c. + */ + +/* The vendor name should be kept at eight characters or less, and + * the product name should be kept at 16 characters or less. If a device + * has the US_FL_FIX_INQUIRY flag, then the vendor and product names + * normally generated by a device thorugh the INQUIRY response will be + * taken from this list, and this is the reason for the above size + * restriction. However, if the flag is not present, then you + * are free to use as many characters as you like. + */ + +#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +#define COMPLIANT_DEV UNUSUAL_DEV + +#define USUAL_DEV(use_protocol, use_transport) \ +{ \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ +} + +#define UNUSUAL_VENDOR_INTF(idVendor, cl, sc, pr, \ + vendor_name, product_name, use_protocol, use_transport, \ + init_function, Flags) \ +{ \ + .vendorName = vendor_name, \ + .productName = product_name, \ + .useProtocol = use_protocol, \ + .useTransport = use_transport, \ + .initFunction = init_function, \ +} + +static struct us_unusual_dev us_unusual_dev_list[] = { +# include "unusual_devs.h" + { } /* Terminating entry */ +}; + +static struct us_unusual_dev for_dynamic_ids = + USUAL_DEV(USB_SC_SCSI, USB_PR_BULK); + +#undef UNUSUAL_DEV +#undef COMPLIANT_DEV +#undef USUAL_DEV +#undef UNUSUAL_VENDOR_INTF + +#ifdef CONFIG_LOCKDEP + +static struct lock_class_key us_interface_key[USB_MAXINTERFACES]; + +static void us_set_lock_class(struct mutex *mutex, + struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_config *config = udev->actconfig; + int i; + + for (i = 0; i < config->desc.bNumInterfaces; i++) { + if (config->interface[i] == intf) + break; + } + + BUG_ON(i == config->desc.bNumInterfaces); + + lockdep_set_class(mutex, &us_interface_key[i]); +} + +#else + +static void us_set_lock_class(struct mutex *mutex, + struct usb_interface *intf) +{ +} + +#endif + +#ifdef CONFIG_PM /* Minimal support for suspend and resume */ + +int usb_stor_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct us_data *us = usb_get_intfdata(iface); + + /* Wait until no command is running */ + mutex_lock(&us->dev_mutex); + + if (us->suspend_resume_hook) + (us->suspend_resume_hook)(us, US_SUSPEND); + + /* When runtime PM is working, we'll set a flag to indicate + * whether we should autoresume when a SCSI request arrives. */ + + mutex_unlock(&us->dev_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(usb_stor_suspend); + +int usb_stor_resume(struct usb_interface *iface) +{ + struct us_data *us = usb_get_intfdata(iface); + + mutex_lock(&us->dev_mutex); + + if (us->suspend_resume_hook) + (us->suspend_resume_hook)(us, US_RESUME); + + mutex_unlock(&us->dev_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(usb_stor_resume); + +int usb_stor_reset_resume(struct usb_interface *iface) +{ + struct us_data *us = usb_get_intfdata(iface); + + /* Report the reset to the SCSI core */ + usb_stor_report_bus_reset(us); + + /* FIXME: Notify the subdrivers that they need to reinitialize + * the device */ + return 0; +} +EXPORT_SYMBOL_GPL(usb_stor_reset_resume); + +#endif /* CONFIG_PM */ + +/* + * The next two routines get called just before and just after + * a USB port reset, whether from this driver or a different one. + */ + +int usb_stor_pre_reset(struct usb_interface *iface) +{ + struct us_data *us = usb_get_intfdata(iface); + + /* Make sure no command runs during the reset */ + mutex_lock(&us->dev_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(usb_stor_pre_reset); + +int usb_stor_post_reset(struct usb_interface *iface) +{ + struct us_data *us = usb_get_intfdata(iface); + + /* Report the reset to the SCSI core */ + usb_stor_report_bus_reset(us); + + /* FIXME: Notify the subdrivers that they need to reinitialize + * the device */ + + mutex_unlock(&us->dev_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(usb_stor_post_reset); + +/* + * fill_inquiry_response takes an unsigned char array (which must + * be at least 36 characters) and populates the vendor name, + * product name, and revision fields. Then the array is copied + * into the SCSI command's response buffer (oddly enough + * called request_buffer). data_len contains the length of the + * data array, which again must be at least 36. + */ + +void fill_inquiry_response(struct us_data *us, unsigned char *data, + unsigned int data_len) +{ + if (data_len < 36) /* You lose. */ + return; + + memset(data+8, ' ', 28); + if (data[0]&0x20) { /* USB device currently not connected. Return + peripheral qualifier 001b ("...however, the + physical device is not currently connected + to this logical unit") and leave vendor and + product identification empty. ("If the target + does store some of the INQUIRY data on the + device, it may return zeros or ASCII spaces + (20h) in those fields until the data is + available from the device."). */ + } else { + u16 bcdDevice = le16_to_cpu(us->pusb_dev->descriptor.bcdDevice); + int n; + + n = strlen(us->unusual_dev->vendorName); + memcpy(data+8, us->unusual_dev->vendorName, min(8, n)); + n = strlen(us->unusual_dev->productName); + memcpy(data+16, us->unusual_dev->productName, min(16, n)); + + data[32] = 0x30 + ((bcdDevice>>12) & 0x0F); + data[33] = 0x30 + ((bcdDevice>>8) & 0x0F); + data[34] = 0x30 + ((bcdDevice>>4) & 0x0F); + data[35] = 0x30 + ((bcdDevice) & 0x0F); + } + + usb_stor_set_xfer_buf(data, data_len, us->srb); +} +EXPORT_SYMBOL_GPL(fill_inquiry_response); + +static int usb_stor_control_thread(void * __us) +{ + struct us_data *us = (struct us_data *)__us; + struct Scsi_Host *host = us_to_host(us); + + for (;;) { + usb_stor_dbg(us, "*** thread sleeping\n"); + if (wait_for_completion_interruptible(&us->cmnd_ready)) + break; + + usb_stor_dbg(us, "*** thread awakened\n"); + + /* lock the device pointers */ + mutex_lock(&(us->dev_mutex)); + + /* lock access to the state */ + scsi_lock(host); + + /* When we are called with no command pending, we're done */ + if (us->srb == NULL) { + scsi_unlock(host); + mutex_unlock(&us->dev_mutex); + usb_stor_dbg(us, "-- exiting\n"); + break; + } + + /* has the command timed out *already* ? */ + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + us->srb->result = DID_ABORT << 16; + goto SkipForAbort; + } + + scsi_unlock(host); + + /* reject the command if the direction indicator + * is UNKNOWN + */ + if (us->srb->sc_data_direction == DMA_BIDIRECTIONAL) { + usb_stor_dbg(us, "UNKNOWN data direction\n"); + us->srb->result = DID_ERROR << 16; + } + + /* reject if target != 0 or if LUN is higher than + * the maximum known LUN + */ + else if (us->srb->device->id && + !(us->fflags & US_FL_SCM_MULT_TARG)) { + usb_stor_dbg(us, "Bad target number (%d:%d)\n", + us->srb->device->id, us->srb->device->lun); + us->srb->result = DID_BAD_TARGET << 16; + } + + else if (us->srb->device->lun > us->max_lun) { + usb_stor_dbg(us, "Bad LUN (%d:%d)\n", + us->srb->device->id, us->srb->device->lun); + us->srb->result = DID_BAD_TARGET << 16; + } + + /* Handle those devices which need us to fake + * their inquiry data */ + else if ((us->srb->cmnd[0] == INQUIRY) && + (us->fflags & US_FL_FIX_INQUIRY)) { + unsigned char data_ptr[36] = { + 0x00, 0x80, 0x02, 0x02, + 0x1F, 0x00, 0x00, 0x00}; + + usb_stor_dbg(us, "Faking INQUIRY command\n"); + fill_inquiry_response(us, data_ptr, 36); + us->srb->result = SAM_STAT_GOOD; + } + + /* we've got a command, let's do it! */ + else { + US_DEBUG(usb_stor_show_command(us, us->srb)); + us->proto_handler(us->srb, us); + usb_mark_last_busy(us->pusb_dev); + } + + /* lock access to the state */ + scsi_lock(host); + + /* indicate that the command is done */ + if (us->srb->result != DID_ABORT << 16) { + usb_stor_dbg(us, "scsi cmd done, result=0x%x\n", + us->srb->result); + us->srb->scsi_done(us->srb); + } else { +SkipForAbort: + usb_stor_dbg(us, "scsi command aborted\n"); + } + + /* If an abort request was received we need to signal that + * the abort has finished. The proper test for this is + * the TIMED_OUT flag, not srb->result == DID_ABORT, because + * the timeout might have occurred after the command had + * already completed with a different result code. */ + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + complete(&(us->notify)); + + /* Allow USB transfers to resume */ + clear_bit(US_FLIDX_ABORTING, &us->dflags); + clear_bit(US_FLIDX_TIMED_OUT, &us->dflags); + } + + /* finished working on this command */ + us->srb = NULL; + scsi_unlock(host); + + /* unlock the device pointers */ + mutex_unlock(&us->dev_mutex); + } /* for (;;) */ + + /* Wait until we are told to stop */ + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (kthread_should_stop()) + break; + schedule(); + } + __set_current_state(TASK_RUNNING); + return 0; +} + +/*********************************************************************** + * Device probing and disconnecting + ***********************************************************************/ + +/* Associate our private data with the USB device */ +static int associate_dev(struct us_data *us, struct usb_interface *intf) +{ + /* Fill in the device-related fields */ + us->pusb_dev = interface_to_usbdev(intf); + us->pusb_intf = intf; + us->ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + usb_stor_dbg(us, "Vendor: 0x%04x, Product: 0x%04x, Revision: 0x%04x\n", + le16_to_cpu(us->pusb_dev->descriptor.idVendor), + le16_to_cpu(us->pusb_dev->descriptor.idProduct), + le16_to_cpu(us->pusb_dev->descriptor.bcdDevice)); + usb_stor_dbg(us, "Interface Subclass: 0x%02x, Protocol: 0x%02x\n", + intf->cur_altsetting->desc.bInterfaceSubClass, + intf->cur_altsetting->desc.bInterfaceProtocol); + + /* Store our private data in the interface */ + usb_set_intfdata(intf, us); + + /* Allocate the control/setup and DMA-mapped buffers */ + us->cr = kmalloc(sizeof(*us->cr), GFP_KERNEL); + if (!us->cr) + return -ENOMEM; + + us->iobuf = usb_alloc_coherent(us->pusb_dev, US_IOBUF_SIZE, + GFP_KERNEL, &us->iobuf_dma); + if (!us->iobuf) { + usb_stor_dbg(us, "I/O buffer allocation failed\n"); + return -ENOMEM; + } + return 0; +} + +/* Works only for digits and letters, but small and fast */ +#define TOLOWER(x) ((x) | 0x20) + +/* Adjust device flags based on the "quirks=" module parameter */ +static void adjust_quirks(struct us_data *us) +{ + char *p; + u16 vid = le16_to_cpu(us->pusb_dev->descriptor.idVendor); + u16 pid = le16_to_cpu(us->pusb_dev->descriptor.idProduct); + unsigned f = 0; + unsigned int mask = (US_FL_SANE_SENSE | US_FL_BAD_SENSE | + US_FL_FIX_CAPACITY | + US_FL_CAPACITY_HEURISTICS | US_FL_IGNORE_DEVICE | + US_FL_NOT_LOCKABLE | US_FL_MAX_SECTORS_64 | + US_FL_CAPACITY_OK | US_FL_IGNORE_RESIDUE | + US_FL_SINGLE_LUN | US_FL_NO_WP_DETECT | + US_FL_NO_READ_DISC_INFO | US_FL_NO_READ_CAPACITY_16 | + US_FL_INITIAL_READ10 | US_FL_WRITE_CACHE); + + p = quirks; + while (*p) { + /* Each entry consists of VID:PID:flags */ + if (vid == simple_strtoul(p, &p, 16) && + *p == ':' && + pid == simple_strtoul(p+1, &p, 16) && + *p == ':') + break; + + /* Move forward to the next entry */ + while (*p) { + if (*p++ == ',') + break; + } + } + if (!*p) /* No match */ + return; + + /* Collect the flags */ + while (*++p && *p != ',') { + switch (TOLOWER(*p)) { + case 'a': + f |= US_FL_SANE_SENSE; + break; + case 'b': + f |= US_FL_BAD_SENSE; + break; + case 'c': + f |= US_FL_FIX_CAPACITY; + break; + case 'd': + f |= US_FL_NO_READ_DISC_INFO; + break; + case 'e': + f |= US_FL_NO_READ_CAPACITY_16; + break; + case 'h': + f |= US_FL_CAPACITY_HEURISTICS; + break; + case 'i': + f |= US_FL_IGNORE_DEVICE; + break; + case 'l': + f |= US_FL_NOT_LOCKABLE; + break; + case 'm': + f |= US_FL_MAX_SECTORS_64; + break; + case 'n': + f |= US_FL_INITIAL_READ10; + break; + case 'o': + f |= US_FL_CAPACITY_OK; + break; + case 'p': + f |= US_FL_WRITE_CACHE; + break; + case 'r': + f |= US_FL_IGNORE_RESIDUE; + break; + case 's': + f |= US_FL_SINGLE_LUN; + break; + case 'w': + f |= US_FL_NO_WP_DETECT; + break; + /* Ignore unrecognized flag characters */ + } + } + us->fflags = (us->fflags & ~mask) | f; +} + +/* Get the unusual_devs entries and the string descriptors */ +static int get_device_info(struct us_data *us, const struct usb_device_id *id, + struct us_unusual_dev *unusual_dev) +{ + struct usb_device *dev = us->pusb_dev; + struct usb_interface_descriptor *idesc = + &us->pusb_intf->cur_altsetting->desc; + struct device *pdev = &us->pusb_intf->dev; + + /* Store the entries */ + us->unusual_dev = unusual_dev; + us->subclass = (unusual_dev->useProtocol == USB_SC_DEVICE) ? + idesc->bInterfaceSubClass : + unusual_dev->useProtocol; + us->protocol = (unusual_dev->useTransport == USB_PR_DEVICE) ? + idesc->bInterfaceProtocol : + unusual_dev->useTransport; + us->fflags = id->driver_info; + adjust_quirks(us); + + if (us->fflags & US_FL_IGNORE_DEVICE) { + dev_info(pdev, "device ignored\n"); + return -ENODEV; + } + + /* + * This flag is only needed when we're in high-speed, so let's + * disable it if we're in full-speed + */ + if (dev->speed != USB_SPEED_HIGH) + us->fflags &= ~US_FL_GO_SLOW; + + if (us->fflags) + dev_info(pdev, "Quirks match for vid %04x pid %04x: %lx\n", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct), + us->fflags); + + /* Log a message if a non-generic unusual_dev entry contains an + * unnecessary subclass or protocol override. This may stimulate + * reports from users that will help us remove unneeded entries + * from the unusual_devs.h table. + */ + if (id->idVendor || id->idProduct) { + static const char *msgs[3] = { + "an unneeded SubClass entry", + "an unneeded Protocol entry", + "unneeded SubClass and Protocol entries"}; + struct usb_device_descriptor *ddesc = &dev->descriptor; + int msg = -1; + + if (unusual_dev->useProtocol != USB_SC_DEVICE && + us->subclass == idesc->bInterfaceSubClass) + msg += 1; + if (unusual_dev->useTransport != USB_PR_DEVICE && + us->protocol == idesc->bInterfaceProtocol) + msg += 2; + if (msg >= 0 && !(us->fflags & US_FL_NEED_OVERRIDE)) + dev_notice(pdev, "This device " + "(%04x,%04x,%04x S %02x P %02x)" + " has %s in unusual_devs.h (kernel" + " %s)\n" + " Please send a copy of this message to " + " and " + "\n", + le16_to_cpu(ddesc->idVendor), + le16_to_cpu(ddesc->idProduct), + le16_to_cpu(ddesc->bcdDevice), + idesc->bInterfaceSubClass, + idesc->bInterfaceProtocol, + msgs[msg], + utsname()->release); + } + + return 0; +} + +/* Get the transport settings */ +static void get_transport(struct us_data *us) +{ + switch (us->protocol) { + case USB_PR_CB: + us->transport_name = "Control/Bulk"; + us->transport = usb_stor_CB_transport; + us->transport_reset = usb_stor_CB_reset; + us->max_lun = 7; + break; + + case USB_PR_CBI: + us->transport_name = "Control/Bulk/Interrupt"; + us->transport = usb_stor_CB_transport; + us->transport_reset = usb_stor_CB_reset; + us->max_lun = 7; + break; + + case USB_PR_BULK: + us->transport_name = "Bulk"; + us->transport = usb_stor_Bulk_transport; + us->transport_reset = usb_stor_Bulk_reset; + break; + } +} + +/* Get the protocol settings */ +static void get_protocol(struct us_data *us) +{ + switch (us->subclass) { + case USB_SC_RBC: + us->protocol_name = "Reduced Block Commands (RBC)"; + us->proto_handler = usb_stor_transparent_scsi_command; + break; + + case USB_SC_8020: + us->protocol_name = "8020i"; + us->proto_handler = usb_stor_pad12_command; + us->max_lun = 0; + break; + + case USB_SC_QIC: + us->protocol_name = "QIC-157"; + us->proto_handler = usb_stor_pad12_command; + us->max_lun = 0; + break; + + case USB_SC_8070: + us->protocol_name = "8070i"; + us->proto_handler = usb_stor_pad12_command; + us->max_lun = 0; + break; + + case USB_SC_SCSI: + us->protocol_name = "Transparent SCSI"; + us->proto_handler = usb_stor_transparent_scsi_command; + break; + + case USB_SC_UFI: + us->protocol_name = "Uniform Floppy Interface (UFI)"; + us->proto_handler = usb_stor_ufi_command; + break; + } +} + +/* Get the pipe settings */ +static int get_pipes(struct us_data *us) +{ + struct usb_host_interface *altsetting = + us->pusb_intf->cur_altsetting; + int i; + struct usb_endpoint_descriptor *ep; + struct usb_endpoint_descriptor *ep_in = NULL; + struct usb_endpoint_descriptor *ep_out = NULL; + struct usb_endpoint_descriptor *ep_int = NULL; + + /* + * Find the first endpoint of each type we need. + * We are expecting a minimum of 2 endpoints - in and out (bulk). + * An optional interrupt-in is OK (necessary for CBI protocol). + * We will ignore any others. + */ + for (i = 0; i < altsetting->desc.bNumEndpoints; i++) { + ep = &altsetting->endpoint[i].desc; + + if (usb_endpoint_xfer_bulk(ep)) { + if (usb_endpoint_dir_in(ep)) { + if (!ep_in) + ep_in = ep; + } else { + if (!ep_out) + ep_out = ep; + } + } + + else if (usb_endpoint_is_int_in(ep)) { + if (!ep_int) + ep_int = ep; + } + } + + if (!ep_in || !ep_out || (us->protocol == USB_PR_CBI && !ep_int)) { + usb_stor_dbg(us, "Endpoint sanity check failed! Rejecting dev.\n"); + return -EIO; + } + + /* Calculate and store the pipe values */ + us->send_ctrl_pipe = usb_sndctrlpipe(us->pusb_dev, 0); + us->recv_ctrl_pipe = usb_rcvctrlpipe(us->pusb_dev, 0); + us->send_bulk_pipe = usb_sndbulkpipe(us->pusb_dev, + usb_endpoint_num(ep_out)); + us->recv_bulk_pipe = usb_rcvbulkpipe(us->pusb_dev, + usb_endpoint_num(ep_in)); + if (ep_int) { + us->recv_intr_pipe = usb_rcvintpipe(us->pusb_dev, + usb_endpoint_num(ep_int)); + us->ep_bInterval = ep_int->bInterval; + } + return 0; +} + +/* Initialize all the dynamic resources we need */ +static int usb_stor_acquire_resources(struct us_data *us) +{ + int p; + struct task_struct *th; + + us->current_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!us->current_urb) { + usb_stor_dbg(us, "URB allocation failed\n"); + return -ENOMEM; + } + + /* Just before we start our control thread, initialize + * the device if it needs initialization */ + if (us->unusual_dev->initFunction) { + p = us->unusual_dev->initFunction(us); + if (p) + return p; + } + + /* Start up our control thread */ + th = kthread_run(usb_stor_control_thread, us, "usb-storage"); + if (IS_ERR(th)) { + dev_warn(&us->pusb_intf->dev, + "Unable to start control thread\n"); + return PTR_ERR(th); + } + us->ctl_thread = th; + + return 0; +} + +/* Release all our dynamic resources */ +static void usb_stor_release_resources(struct us_data *us) +{ + /* Tell the control thread to exit. The SCSI host must + * already have been removed and the DISCONNECTING flag set + * so that we won't accept any more commands. + */ + usb_stor_dbg(us, "-- sending exit command to thread\n"); + complete(&us->cmnd_ready); + if (us->ctl_thread) + kthread_stop(us->ctl_thread); + + /* Call the destructor routine, if it exists */ + if (us->extra_destructor) { + usb_stor_dbg(us, "-- calling extra_destructor()\n"); + us->extra_destructor(us->extra); + } + + /* Free the extra data and the URB */ + kfree(us->extra); + usb_free_urb(us->current_urb); +} + +/* Dissociate from the USB device */ +static void dissociate_dev(struct us_data *us) +{ + /* Free the buffers */ + kfree(us->cr); + usb_free_coherent(us->pusb_dev, US_IOBUF_SIZE, us->iobuf, us->iobuf_dma); + + /* Remove our private data from the interface */ + usb_set_intfdata(us->pusb_intf, NULL); +} + +/* First stage of disconnect processing: stop SCSI scanning, + * remove the host, and stop accepting new commands + */ +static void quiesce_and_remove_host(struct us_data *us) +{ + struct Scsi_Host *host = us_to_host(us); + + /* If the device is really gone, cut short reset delays */ + if (us->pusb_dev->state == USB_STATE_NOTATTACHED) { + set_bit(US_FLIDX_DISCONNECTING, &us->dflags); + wake_up(&us->delay_wait); + } + + /* Prevent SCSI scanning (if it hasn't started yet) + * or wait for the SCSI-scanning routine to stop. + */ + cancel_delayed_work_sync(&us->scan_dwork); + + /* Balance autopm calls if scanning was cancelled */ + if (test_bit(US_FLIDX_SCAN_PENDING, &us->dflags)) + usb_autopm_put_interface_no_suspend(us->pusb_intf); + + /* Removing the host will perform an orderly shutdown: caches + * synchronized, disks spun down, etc. + */ + scsi_remove_host(host); + + /* Prevent any new commands from being accepted and cut short + * reset delays. + */ + scsi_lock(host); + set_bit(US_FLIDX_DISCONNECTING, &us->dflags); + scsi_unlock(host); + wake_up(&us->delay_wait); +} + +/* Second stage of disconnect processing: deallocate all resources */ +static void release_everything(struct us_data *us) +{ + usb_stor_release_resources(us); + dissociate_dev(us); + + /* Drop our reference to the host; the SCSI core will free it + * (and "us" along with it) when the refcount becomes 0. */ + scsi_host_put(us_to_host(us)); +} + +/* Delayed-work routine to carry out SCSI-device scanning */ +static void usb_stor_scan_dwork(struct work_struct *work) +{ + struct us_data *us = container_of(work, struct us_data, + scan_dwork.work); + struct device *dev = &us->pusb_intf->dev; + + dev_dbg(dev, "starting scan\n"); + + /* For bulk-only devices, determine the max LUN value */ + if (us->protocol == USB_PR_BULK && !(us->fflags & US_FL_SINGLE_LUN)) { + mutex_lock(&us->dev_mutex); + us->max_lun = usb_stor_Bulk_max_lun(us); + mutex_unlock(&us->dev_mutex); + } + scsi_scan_host(us_to_host(us)); + dev_dbg(dev, "scan complete\n"); + + /* Should we unbind if no devices were detected? */ + + usb_autopm_put_interface(us->pusb_intf); + clear_bit(US_FLIDX_SCAN_PENDING, &us->dflags); +} + +static unsigned int usb_stor_sg_tablesize(struct usb_interface *intf) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + + if (usb_dev->bus->sg_tablesize) { + return usb_dev->bus->sg_tablesize; + } + return SG_ALL; +} + +/* First part of general USB mass-storage probing */ +int usb_stor_probe1(struct us_data **pus, + struct usb_interface *intf, + const struct usb_device_id *id, + struct us_unusual_dev *unusual_dev) +{ + struct Scsi_Host *host; + struct us_data *us; + int result; + + dev_info(&intf->dev, "USB Mass Storage device detected\n"); + + /* + * Ask the SCSI layer to allocate a host structure, with extra + * space at the end for our private us_data structure. + */ + host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us)); + if (!host) { + dev_warn(&intf->dev, "Unable to allocate the scsi host\n"); + return -ENOMEM; + } + + /* + * Allow 16-byte CDBs and thus > 2TB + */ + host->max_cmd_len = 16; + host->sg_tablesize = usb_stor_sg_tablesize(intf); + *pus = us = host_to_us(host); + mutex_init(&(us->dev_mutex)); + us_set_lock_class(&us->dev_mutex, intf); + init_completion(&us->cmnd_ready); + init_completion(&(us->notify)); + init_waitqueue_head(&us->delay_wait); + INIT_DELAYED_WORK(&us->scan_dwork, usb_stor_scan_dwork); + + /* Associate the us_data structure with the USB device */ + result = associate_dev(us, intf); + if (result) + goto BadDevice; + + /* Get the unusual_devs entries and the descriptors */ + result = get_device_info(us, id, unusual_dev); + if (result) + goto BadDevice; + + /* Get standard transport and protocol settings */ + get_transport(us); + get_protocol(us); + + /* Give the caller a chance to fill in specialized transport + * or protocol settings. + */ + return 0; + +BadDevice: + usb_stor_dbg(us, "storage_probe() failed\n"); + release_everything(us); + return result; +} +EXPORT_SYMBOL_GPL(usb_stor_probe1); + +/* Second part of general USB mass-storage probing */ +int usb_stor_probe2(struct us_data *us) +{ + int result; + struct device *dev = &us->pusb_intf->dev; + + /* Make sure the transport and protocol have both been set */ + if (!us->transport || !us->proto_handler) { + result = -ENXIO; + goto BadDevice; + } + usb_stor_dbg(us, "Transport: %s\n", us->transport_name); + usb_stor_dbg(us, "Protocol: %s\n", us->protocol_name); + + /* fix for single-lun devices */ + if (us->fflags & US_FL_SINGLE_LUN) + us->max_lun = 0; + + if (!(us->fflags & US_FL_SCM_MULT_TARG)) + us_to_host(us)->max_id = 1; + + /* Find the endpoints and calculate pipe values */ + result = get_pipes(us); + if (result) + goto BadDevice; + + /* + * If the device returns invalid data for the first READ(10) + * command, indicate the command should be retried. + */ + if (us->fflags & US_FL_INITIAL_READ10) + set_bit(US_FLIDX_REDO_READ10, &us->dflags); + + /* Acquire all the other resources and add the host */ + result = usb_stor_acquire_resources(us); + if (result) + goto BadDevice; + snprintf(us->scsi_name, sizeof(us->scsi_name), "usb-storage %s", + dev_name(&us->pusb_intf->dev)); + result = scsi_add_host(us_to_host(us), dev); + if (result) { + dev_warn(dev, + "Unable to add the scsi host\n"); + goto BadDevice; + } + + /* Submit the delayed_work for SCSI-device scanning */ + usb_autopm_get_interface_no_resume(us->pusb_intf); + set_bit(US_FLIDX_SCAN_PENDING, &us->dflags); + + if (delay_use > 0) + dev_dbg(dev, "waiting for device to settle before scanning\n"); + queue_delayed_work(system_freezable_wq, &us->scan_dwork, + delay_use * HZ); + return 0; + + /* We come here if there are any problems */ +BadDevice: + usb_stor_dbg(us, "storage_probe() failed\n"); + release_everything(us); + return result; +} +EXPORT_SYMBOL_GPL(usb_stor_probe2); + +/* Handle a USB mass-storage disconnect */ +void usb_stor_disconnect(struct usb_interface *intf) +{ + struct us_data *us = usb_get_intfdata(intf); + + quiesce_and_remove_host(us); + release_everything(us); +} +EXPORT_SYMBOL_GPL(usb_stor_disconnect); + +/* The main probe routine for standard devices */ +static int storage_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct us_unusual_dev *unusual_dev; + struct us_data *us; + int result; + int size; + + /* + * If the device isn't standard (is handled by a subdriver + * module) then don't accept it. + */ + if (usb_usual_ignore_device(intf)) + return -ENXIO; + + /* + * Call the general probe procedures. + * + * The unusual_dev_list array is parallel to the usb_storage_usb_ids + * table, so we use the index of the id entry to find the + * corresponding unusual_devs entry. + */ + + size = ARRAY_SIZE(us_unusual_dev_list); + if (id >= usb_storage_usb_ids && id < usb_storage_usb_ids + size) { + unusual_dev = (id - usb_storage_usb_ids) + us_unusual_dev_list; + } else { + unusual_dev = &for_dynamic_ids; + + dev_dbg(&intf->dev, "Use Bulk-Only transport with the Transparent SCSI protocol for dynamic id: 0x%04x 0x%04x\n", + id->idVendor, id->idProduct); + } + + result = usb_stor_probe1(&us, intf, id, unusual_dev); + if (result) + return result; + + /* No special transport or protocol settings in the main module */ + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver usb_storage_driver = { + .name = "usb-storage", + .probe = storage_probe, + .disconnect = usb_stor_disconnect, + .suspend = usb_stor_suspend, + .resume = usb_stor_resume, + .reset_resume = usb_stor_reset_resume, + .pre_reset = usb_stor_pre_reset, + .post_reset = usb_stor_post_reset, + .id_table = usb_storage_usb_ids, + .supports_autosuspend = 1, + .soft_unbind = 1, +}; + +module_usb_driver(usb_storage_driver); diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h new file mode 100644 index 00000000..75f70f04 --- /dev/null +++ b/drivers/usb/storage/usb.h @@ -0,0 +1,204 @@ +/* Driver for USB Mass Storage compliant devices + * Main Header File + * + * Current development and maintenance by: + * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _USB_H_ +#define _USB_H_ + +#include +#include +#include +#include +#include +#include +#include + +struct us_data; +struct scsi_cmnd; + +/* + * Unusual device list definitions + */ + +struct us_unusual_dev { + const char* vendorName; + const char* productName; + __u8 useProtocol; + __u8 useTransport; + int (*initFunction)(struct us_data *); +}; + + +/* Dynamic bitflag definitions (us->dflags): used in set_bit() etc. */ +#define US_FLIDX_URB_ACTIVE 0 /* current_urb is in use */ +#define US_FLIDX_SG_ACTIVE 1 /* current_sg is in use */ +#define US_FLIDX_ABORTING 2 /* abort is in progress */ +#define US_FLIDX_DISCONNECTING 3 /* disconnect in progress */ +#define US_FLIDX_RESETTING 4 /* device reset in progress */ +#define US_FLIDX_TIMED_OUT 5 /* SCSI midlayer timed out */ +#define US_FLIDX_SCAN_PENDING 6 /* scanning not yet done */ +#define US_FLIDX_REDO_READ10 7 /* redo READ(10) command */ +#define US_FLIDX_READ10_WORKED 8 /* previous READ(10) succeeded */ + +#define USB_STOR_STRING_LEN 32 + +/* + * We provide a DMA-mapped I/O buffer for use with small USB transfers. + * It turns out that CB[I] needs a 12-byte buffer and Bulk-only needs a + * 31-byte buffer. But Freecom needs a 64-byte buffer, so that's the + * size we'll allocate. + */ + +#define US_IOBUF_SIZE 64 /* Size of the DMA-mapped I/O buffer */ +#define US_SENSE_SIZE 18 /* Size of the autosense data buffer */ + +typedef int (*trans_cmnd)(struct scsi_cmnd *, struct us_data*); +typedef int (*trans_reset)(struct us_data*); +typedef void (*proto_cmnd)(struct scsi_cmnd*, struct us_data*); +typedef void (*extra_data_destructor)(void *); /* extra data destructor */ +typedef void (*pm_hook)(struct us_data *, int); /* power management hook */ + +#define US_SUSPEND 0 +#define US_RESUME 1 + +/* we allocate one of these for every device that we remember */ +struct us_data { + /* The device we're working with + * It's important to note: + * (o) you must hold dev_mutex to change pusb_dev + */ + struct mutex dev_mutex; /* protect pusb_dev */ + struct usb_device *pusb_dev; /* this usb_device */ + struct usb_interface *pusb_intf; /* this interface */ + struct us_unusual_dev *unusual_dev; /* device-filter entry */ + unsigned long fflags; /* fixed flags from filter */ + unsigned long dflags; /* dynamic atomic bitflags */ + unsigned int send_bulk_pipe; /* cached pipe values */ + unsigned int recv_bulk_pipe; + unsigned int send_ctrl_pipe; + unsigned int recv_ctrl_pipe; + unsigned int recv_intr_pipe; + + /* information about the device */ + char *transport_name; + char *protocol_name; + __le32 bcs_signature; + u8 subclass; + u8 protocol; + u8 max_lun; + + u8 ifnum; /* interface number */ + u8 ep_bInterval; /* interrupt interval */ + + /* function pointers for this device */ + trans_cmnd transport; /* transport function */ + trans_reset transport_reset; /* transport device reset */ + proto_cmnd proto_handler; /* protocol handler */ + + /* SCSI interfaces */ + struct scsi_cmnd *srb; /* current srb */ + unsigned int tag; /* current dCBWTag */ + char scsi_name[32]; /* scsi_host name */ + + /* control and bulk communications data */ + struct urb *current_urb; /* USB requests */ + struct usb_ctrlrequest *cr; /* control requests */ + struct usb_sg_request current_sg; /* scatter-gather req. */ + unsigned char *iobuf; /* I/O buffer */ + dma_addr_t iobuf_dma; /* buffer DMA addresses */ + struct task_struct *ctl_thread; /* the control thread */ + + /* mutual exclusion and synchronization structures */ + struct completion cmnd_ready; /* to sleep thread on */ + struct completion notify; /* thread begin/end */ + wait_queue_head_t delay_wait; /* wait during reset */ + struct delayed_work scan_dwork; /* for async scanning */ + + /* subdriver information */ + void *extra; /* Any extra data */ + extra_data_destructor extra_destructor;/* extra data destructor */ +#ifdef CONFIG_PM + pm_hook suspend_resume_hook; +#endif + + /* hacks for READ CAPACITY bug handling */ + int use_last_sector_hacks; + int last_sector_retries; +}; + +/* Convert between us_data and the corresponding Scsi_Host */ +static inline struct Scsi_Host *us_to_host(struct us_data *us) { + return container_of((void *) us, struct Scsi_Host, hostdata); +} +static inline struct us_data *host_to_us(struct Scsi_Host *host) { + return (struct us_data *) host->hostdata; +} + +/* Function to fill an inquiry response. See usb.c for details */ +extern void fill_inquiry_response(struct us_data *us, + unsigned char *data, unsigned int data_len); + +/* The scsi_lock() and scsi_unlock() macros protect the sm_state and the + * single queue element srb for write access */ +#define scsi_unlock(host) spin_unlock_irq(host->host_lock) +#define scsi_lock(host) spin_lock_irq(host->host_lock) + +/* General routines provided by the usb-storage standard core */ +#ifdef CONFIG_PM +extern int usb_stor_suspend(struct usb_interface *iface, pm_message_t message); +extern int usb_stor_resume(struct usb_interface *iface); +extern int usb_stor_reset_resume(struct usb_interface *iface); +#else +#define usb_stor_suspend NULL +#define usb_stor_resume NULL +#define usb_stor_reset_resume NULL +#endif + +extern int usb_stor_pre_reset(struct usb_interface *iface); +extern int usb_stor_post_reset(struct usb_interface *iface); + +extern int usb_stor_probe1(struct us_data **pus, + struct usb_interface *intf, + const struct usb_device_id *id, + struct us_unusual_dev *unusual_dev); +extern int usb_stor_probe2(struct us_data *us); +extern void usb_stor_disconnect(struct usb_interface *intf); + +#endif diff --git a/drivers/usb/storage/usual-tables.c b/drivers/usb/storage/usual-tables.c new file mode 100644 index 00000000..5ef8ce74 --- /dev/null +++ b/drivers/usb/storage/usual-tables.c @@ -0,0 +1,123 @@ +/* Driver for USB Mass Storage devices + * Usual Tables File for usb-storage and libusual + * + * Copyright (C) 2009 Alan Stern (stern@rowland.harvard.edu) + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + + +/* + * The table of devices + */ +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + +#define COMPLIANT_DEV UNUSUAL_DEV + +#define USUAL_DEV(useProto, useTrans) \ +{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans) } + +/* Define the device is matched with Vendor ID and interface descriptors */ +#define UNUSUAL_VENDOR_INTF(id_vendor, cl, sc, pr, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ \ + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO \ + | USB_DEVICE_ID_MATCH_VENDOR, \ + .idVendor = (id_vendor), \ + .bInterfaceClass = (cl), \ + .bInterfaceSubClass = (sc), \ + .bInterfaceProtocol = (pr), \ + .driver_info = (flags) \ +} + +struct usb_device_id usb_storage_usb_ids[] = { +# include "unusual_devs.h" + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usb_storage_usb_ids); + +#undef UNUSUAL_DEV +#undef COMPLIANT_DEV +#undef USUAL_DEV +#undef UNUSUAL_VENDOR_INTF + +/* + * The table of devices to ignore + */ +struct ignore_entry { + u16 vid, pid, bcdmin, bcdmax; +}; + +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ \ + .vid = id_vendor, \ + .pid = id_product, \ + .bcdmin = bcdDeviceMin, \ + .bcdmax = bcdDeviceMax, \ +} + +static struct ignore_entry ignore_ids[] = { +# include "unusual_alauda.h" +# include "unusual_cypress.h" +# include "unusual_datafab.h" +# include "unusual_ene_ub6250.h" +# include "unusual_freecom.h" +# include "unusual_isd200.h" +# include "unusual_jumpshot.h" +# include "unusual_karma.h" +# include "unusual_onetouch.h" +# include "unusual_realtek.h" +# include "unusual_sddr09.h" +# include "unusual_sddr55.h" +# include "unusual_usbat.h" + { } /* Terminating entry */ +}; + +#undef UNUSUAL_DEV + +/* Return an error if a device is in the ignore_ids list */ +int usb_usual_ignore_device(struct usb_interface *intf) +{ + struct usb_device *udev; + unsigned vid, pid, bcd; + struct ignore_entry *p; + + udev = interface_to_usbdev(intf); + vid = le16_to_cpu(udev->descriptor.idVendor); + pid = le16_to_cpu(udev->descriptor.idProduct); + bcd = le16_to_cpu(udev->descriptor.bcdDevice); + + for (p = ignore_ids; p->vid; ++p) { + if (p->vid == vid && p->pid == pid && + p->bcdmin <= bcd && p->bcdmax >= bcd) + return -ENXIO; + } + return 0; +} diff --git a/drivers/usb/usb-common.c b/drivers/usb/usb-common.c new file mode 100644 index 00000000..0db0a919 --- /dev/null +++ b/drivers/usb/usb-common.c @@ -0,0 +1,82 @@ +/* + * Provides code common for host and device side USB. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + * If either host side (ie. CONFIG_USB=y) or device side USB stack + * (ie. CONFIG_USB_GADGET=y) is compiled in the kernel, this module is + * compiled-in as well. Otherwise, if either of the two stacks is + * compiled as module, this file is compiled as module as well. + */ + +#include +#include +#include +#include + +const char *usb_otg_state_string(enum usb_otg_state state) +{ + static const char *const names[] = { + [OTG_STATE_A_IDLE] = "a_idle", + [OTG_STATE_A_WAIT_VRISE] = "a_wait_vrise", + [OTG_STATE_A_WAIT_BCON] = "a_wait_bcon", + [OTG_STATE_A_HOST] = "a_host", + [OTG_STATE_A_SUSPEND] = "a_suspend", + [OTG_STATE_A_PERIPHERAL] = "a_peripheral", + [OTG_STATE_A_WAIT_VFALL] = "a_wait_vfall", + [OTG_STATE_A_VBUS_ERR] = "a_vbus_err", + [OTG_STATE_B_IDLE] = "b_idle", + [OTG_STATE_B_SRP_INIT] = "b_srp_init", + [OTG_STATE_B_PERIPHERAL] = "b_peripheral", + [OTG_STATE_B_WAIT_ACON] = "b_wait_acon", + [OTG_STATE_B_HOST] = "b_host", + }; + + if (state < 0 || state >= ARRAY_SIZE(names)) + return "UNDEFINED"; + + return names[state]; +} +EXPORT_SYMBOL_GPL(usb_otg_state_string); + +const char *usb_speed_string(enum usb_device_speed speed) +{ + static const char *const names[] = { + [USB_SPEED_UNKNOWN] = "UNKNOWN", + [USB_SPEED_LOW] = "low-speed", + [USB_SPEED_FULL] = "full-speed", + [USB_SPEED_HIGH] = "high-speed", + [USB_SPEED_WIRELESS] = "wireless", + [USB_SPEED_SUPER] = "super-speed", + }; + + if (speed < 0 || speed >= ARRAY_SIZE(names)) + speed = USB_SPEED_UNKNOWN; + return names[speed]; +} +EXPORT_SYMBOL_GPL(usb_speed_string); + +const char *usb_state_string(enum usb_device_state state) +{ + static const char *const names[] = { + [USB_STATE_NOTATTACHED] = "not attached", + [USB_STATE_ATTACHED] = "attached", + [USB_STATE_POWERED] = "powered", + [USB_STATE_RECONNECTING] = "reconnecting", + [USB_STATE_UNAUTHENTICATED] = "unauthenticated", + [USB_STATE_DEFAULT] = "default", + [USB_STATE_ADDRESS] = "addresssed", + [USB_STATE_CONFIGURED] = "configured", + [USB_STATE_SUSPENDED] = "suspended", + }; + + if (state < 0 || state >= ARRAY_SIZE(names)) + return "UNKNOWN"; + + return names[state]; +} +EXPORT_SYMBOL_GPL(usb_state_string); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usb-skeleton.c b/drivers/usb/usb-skeleton.c new file mode 100644 index 00000000..7ed3b039 --- /dev/null +++ b/drivers/usb/usb-skeleton.c @@ -0,0 +1,666 @@ +/* + * USB Skeleton driver - 2.2 + * + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + * This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c + * but has been rewritten to be easier to read and use. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Define these values to match your devices */ +#define USB_SKEL_VENDOR_ID 0xfff0 +#define USB_SKEL_PRODUCT_ID 0xfff0 + +/* table of devices that work with this driver */ +static const struct usb_device_id skel_table[] = { + { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, skel_table); + + +/* Get a minor range for your devices from the usb maintainer */ +#define USB_SKEL_MINOR_BASE 192 + +/* our private defines. if this grows any larger, use your own .h file */ +#define MAX_TRANSFER (PAGE_SIZE - 512) +/* MAX_TRANSFER is chosen so that the VM is not stressed by + allocations > PAGE_SIZE and the number of packets in a page + is an integer 512 is the largest possible packet on EHCI */ +#define WRITES_IN_FLIGHT 8 +/* arbitrarily chosen */ + +/* Structure to hold all of our device specific stuff */ +struct usb_skel { + struct usb_device *udev; /* the usb device for this device */ + struct usb_interface *interface; /* the interface for this device */ + struct semaphore limit_sem; /* limiting the number of writes in progress */ + struct usb_anchor submitted; /* in case we need to retract our submissions */ + struct urb *bulk_in_urb; /* the urb to read data with */ + unsigned char *bulk_in_buffer; /* the buffer to receive data */ + size_t bulk_in_size; /* the size of the receive buffer */ + size_t bulk_in_filled; /* number of bytes in the buffer */ + size_t bulk_in_copied; /* already copied to user space */ + __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ + __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */ + int errors; /* the last request tanked */ + bool ongoing_read; /* a read is going on */ + spinlock_t err_lock; /* lock for errors */ + struct kref kref; + struct mutex io_mutex; /* synchronize I/O with disconnect */ + wait_queue_head_t bulk_in_wait; /* to wait for an ongoing read */ +}; +#define to_skel_dev(d) container_of(d, struct usb_skel, kref) + +static struct usb_driver skel_driver; +static void skel_draw_down(struct usb_skel *dev); + +static void skel_delete(struct kref *kref) +{ + struct usb_skel *dev = to_skel_dev(kref); + + usb_free_urb(dev->bulk_in_urb); + usb_put_dev(dev->udev); + kfree(dev->bulk_in_buffer); + kfree(dev); +} + +static int skel_open(struct inode *inode, struct file *file) +{ + struct usb_skel *dev; + struct usb_interface *interface; + int subminor; + int retval = 0; + + subminor = iminor(inode); + + interface = usb_find_interface(&skel_driver, subminor); + if (!interface) { + pr_err("%s - error, can't find device for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + retval = usb_autopm_get_interface(interface); + if (retval) + goto exit; + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* save our object in the file's private structure */ + file->private_data = dev; + +exit: + return retval; +} + +static int skel_release(struct inode *inode, struct file *file) +{ + struct usb_skel *dev; + + dev = file->private_data; + if (dev == NULL) + return -ENODEV; + + /* allow the device to be autosuspended */ + mutex_lock(&dev->io_mutex); + if (dev->interface) + usb_autopm_put_interface(dev->interface); + mutex_unlock(&dev->io_mutex); + + /* decrement the count on our device */ + kref_put(&dev->kref, skel_delete); + return 0; +} + +static int skel_flush(struct file *file, fl_owner_t id) +{ + struct usb_skel *dev; + int res; + + dev = file->private_data; + if (dev == NULL) + return -ENODEV; + + /* wait for io to stop */ + mutex_lock(&dev->io_mutex); + skel_draw_down(dev); + + /* read out errors, leave subsequent opens a clean slate */ + spin_lock_irq(&dev->err_lock); + res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0; + dev->errors = 0; + spin_unlock_irq(&dev->err_lock); + + mutex_unlock(&dev->io_mutex); + + return res; +} + +static void skel_read_bulk_callback(struct urb *urb) +{ + struct usb_skel *dev; + + dev = urb->context; + + spin_lock(&dev->err_lock); + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(&dev->interface->dev, + "%s - nonzero write bulk status received: %d\n", + __func__, urb->status); + + dev->errors = urb->status; + } else { + dev->bulk_in_filled = urb->actual_length; + } + dev->ongoing_read = 0; + spin_unlock(&dev->err_lock); + + wake_up_interruptible(&dev->bulk_in_wait); +} + +static int skel_do_read_io(struct usb_skel *dev, size_t count) +{ + int rv; + + /* prepare a read */ + usb_fill_bulk_urb(dev->bulk_in_urb, + dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->bulk_in_endpointAddr), + dev->bulk_in_buffer, + min(dev->bulk_in_size, count), + skel_read_bulk_callback, + dev); + /* tell everybody to leave the URB alone */ + spin_lock_irq(&dev->err_lock); + dev->ongoing_read = 1; + spin_unlock_irq(&dev->err_lock); + + /* submit bulk in urb, which means no data to deliver */ + dev->bulk_in_filled = 0; + dev->bulk_in_copied = 0; + + /* do it */ + rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL); + if (rv < 0) { + dev_err(&dev->interface->dev, + "%s - failed submitting read urb, error %d\n", + __func__, rv); + rv = (rv == -ENOMEM) ? rv : -EIO; + spin_lock_irq(&dev->err_lock); + dev->ongoing_read = 0; + spin_unlock_irq(&dev->err_lock); + } + + return rv; +} + +static ssize_t skel_read(struct file *file, char *buffer, size_t count, + loff_t *ppos) +{ + struct usb_skel *dev; + int rv; + bool ongoing_io; + + dev = file->private_data; + + /* if we cannot read at all, return EOF */ + if (!dev->bulk_in_urb || !count) + return 0; + + /* no concurrent readers */ + rv = mutex_lock_interruptible(&dev->io_mutex); + if (rv < 0) + return rv; + + if (!dev->interface) { /* disconnect() was called */ + rv = -ENODEV; + goto exit; + } + + /* if IO is under way, we must not touch things */ +retry: + spin_lock_irq(&dev->err_lock); + ongoing_io = dev->ongoing_read; + spin_unlock_irq(&dev->err_lock); + + if (ongoing_io) { + /* nonblocking IO shall not wait */ + if (file->f_flags & O_NONBLOCK) { + rv = -EAGAIN; + goto exit; + } + /* + * IO may take forever + * hence wait in an interruptible state + */ + rv = wait_event_interruptible(dev->bulk_in_wait, (!dev->ongoing_read)); + if (rv < 0) + goto exit; + } + + /* errors must be reported */ + rv = dev->errors; + if (rv < 0) { + /* any error is reported once */ + dev->errors = 0; + /* to preserve notifications about reset */ + rv = (rv == -EPIPE) ? rv : -EIO; + /* report it */ + goto exit; + } + + /* + * if the buffer is filled we may satisfy the read + * else we need to start IO + */ + + if (dev->bulk_in_filled) { + /* we had read data */ + size_t available = dev->bulk_in_filled - dev->bulk_in_copied; + size_t chunk = min(available, count); + + if (!available) { + /* + * all data has been used + * actual IO needs to be done + */ + rv = skel_do_read_io(dev, count); + if (rv < 0) + goto exit; + else + goto retry; + } + /* + * data is available + * chunk tells us how much shall be copied + */ + + if (copy_to_user(buffer, + dev->bulk_in_buffer + dev->bulk_in_copied, + chunk)) + rv = -EFAULT; + else + rv = chunk; + + dev->bulk_in_copied += chunk; + + /* + * if we are asked for more than we have, + * we start IO but don't wait + */ + if (available < count) + skel_do_read_io(dev, count - chunk); + } else { + /* no data in the buffer */ + rv = skel_do_read_io(dev, count); + if (rv < 0) + goto exit; + else if (!(file->f_flags & O_NONBLOCK)) + goto retry; + rv = -EAGAIN; + } +exit: + mutex_unlock(&dev->io_mutex); + return rv; +} + +static void skel_write_bulk_callback(struct urb *urb) +{ + struct usb_skel *dev; + + dev = urb->context; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(&dev->interface->dev, + "%s - nonzero write bulk status received: %d\n", + __func__, urb->status); + + spin_lock(&dev->err_lock); + dev->errors = urb->status; + spin_unlock(&dev->err_lock); + } + + /* free up our allocated buffer */ + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + up(&dev->limit_sem); +} + +static ssize_t skel_write(struct file *file, const char *user_buffer, + size_t count, loff_t *ppos) +{ + struct usb_skel *dev; + int retval = 0; + struct urb *urb = NULL; + char *buf = NULL; + size_t writesize = min(count, (size_t)MAX_TRANSFER); + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* + * limit the number of URBs in flight to stop a user from using up all + * RAM + */ + if (!(file->f_flags & O_NONBLOCK)) { + if (down_interruptible(&dev->limit_sem)) { + retval = -ERESTARTSYS; + goto exit; + } + } else { + if (down_trylock(&dev->limit_sem)) { + retval = -EAGAIN; + goto exit; + } + } + + spin_lock_irq(&dev->err_lock); + retval = dev->errors; + if (retval < 0) { + /* any error is reported once */ + dev->errors = 0; + /* to preserve notifications about reset */ + retval = (retval == -EPIPE) ? retval : -EIO; + } + spin_unlock_irq(&dev->err_lock); + if (retval < 0) + goto error; + + /* create a urb, and a buffer for it, and copy the data to the urb */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + retval = -ENOMEM; + goto error; + } + + buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + goto error; + } + + if (copy_from_user(buf, user_buffer, writesize)) { + retval = -EFAULT; + goto error; + } + + /* this lock makes sure we don't submit URBs to gone devices */ + mutex_lock(&dev->io_mutex); + if (!dev->interface) { /* disconnect() was called */ + mutex_unlock(&dev->io_mutex); + retval = -ENODEV; + goto error; + } + + /* initialize the urb properly */ + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), + buf, writesize, skel_write_bulk_callback, dev); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + usb_anchor_urb(urb, &dev->submitted); + + /* send the data out the bulk port */ + retval = usb_submit_urb(urb, GFP_KERNEL); + mutex_unlock(&dev->io_mutex); + if (retval) { + dev_err(&dev->interface->dev, + "%s - failed submitting write urb, error %d\n", + __func__, retval); + goto error_unanchor; + } + + /* + * release our reference to this urb, the USB core will eventually free + * it entirely + */ + usb_free_urb(urb); + + + return writesize; + +error_unanchor: + usb_unanchor_urb(urb); +error: + if (urb) { + usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma); + usb_free_urb(urb); + } + up(&dev->limit_sem); + +exit: + return retval; +} + +static const struct file_operations skel_fops = { + .owner = THIS_MODULE, + .read = skel_read, + .write = skel_write, + .open = skel_open, + .release = skel_release, + .flush = skel_flush, + .llseek = noop_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver skel_class = { + .name = "skel%d", + .fops = &skel_fops, + .minor_base = USB_SKEL_MINOR_BASE, +}; + +static int skel_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_skel *dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + size_t buffer_size; + int i; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + dev_err(&interface->dev, "Out of memory\n"); + goto error; + } + kref_init(&dev->kref); + sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); + mutex_init(&dev->io_mutex); + spin_lock_init(&dev->err_lock); + init_usb_anchor(&dev->submitted); + init_waitqueue_head(&dev->bulk_in_wait); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* set up the endpoint information */ + /* use only the first bulk-in and bulk-out endpoints */ + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (!dev->bulk_in_endpointAddr && + usb_endpoint_is_bulk_in(endpoint)) { + /* we found a bulk in endpoint */ + buffer_size = usb_endpoint_maxp(endpoint); + dev->bulk_in_size = buffer_size; + dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + dev_err(&interface->dev, + "Could not allocate bulk_in_buffer\n"); + goto error; + } + dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->bulk_in_urb) { + dev_err(&interface->dev, + "Could not allocate bulk_in_urb\n"); + goto error; + } + } + + if (!dev->bulk_out_endpointAddr && + usb_endpoint_is_bulk_out(endpoint)) { + /* we found a bulk out endpoint */ + dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; + } + } + if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { + dev_err(&interface->dev, + "Could not find both bulk-in and bulk-out endpoints\n"); + goto error; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &skel_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&interface->dev, + "Not able to get a minor for this device.\n"); + usb_set_intfdata(interface, NULL); + goto error; + } + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, + "USB Skeleton device now attached to USBSkel-%d", + interface->minor); + return 0; + +error: + if (dev) + /* this frees allocated memory */ + kref_put(&dev->kref, skel_delete); + return retval; +} + +static void skel_disconnect(struct usb_interface *interface) +{ + struct usb_skel *dev; + int minor = interface->minor; + + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + /* give back our minor */ + usb_deregister_dev(interface, &skel_class); + + /* prevent more I/O from starting */ + mutex_lock(&dev->io_mutex); + dev->interface = NULL; + mutex_unlock(&dev->io_mutex); + + usb_kill_anchored_urbs(&dev->submitted); + + /* decrement our usage count */ + kref_put(&dev->kref, skel_delete); + + dev_info(&interface->dev, "USB Skeleton #%d now disconnected", minor); +} + +static void skel_draw_down(struct usb_skel *dev) +{ + int time; + + time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000); + if (!time) + usb_kill_anchored_urbs(&dev->submitted); + usb_kill_urb(dev->bulk_in_urb); +} + +static int skel_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + if (!dev) + return 0; + skel_draw_down(dev); + return 0; +} + +static int skel_resume(struct usb_interface *intf) +{ + return 0; +} + +static int skel_pre_reset(struct usb_interface *intf) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + mutex_lock(&dev->io_mutex); + skel_draw_down(dev); + + return 0; +} + +static int skel_post_reset(struct usb_interface *intf) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + /* we are sure no URBs are active - no locking needed */ + dev->errors = -EPIPE; + mutex_unlock(&dev->io_mutex); + + return 0; +} + +static struct usb_driver skel_driver = { + .name = "skeleton", + .probe = skel_probe, + .disconnect = skel_disconnect, + .suspend = skel_suspend, + .resume = skel_resume, + .pre_reset = skel_pre_reset, + .post_reset = skel_post_reset, + .id_table = skel_table, + .supports_autosuspend = 1, +}; + +module_usb_driver(skel_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig new file mode 100644 index 00000000..0e17b966 --- /dev/null +++ b/drivers/usb/wusbcore/Kconfig @@ -0,0 +1,39 @@ +# +# Wireless USB Core configuration +# +config USB_WUSB + tristate "Enable Wireless USB extensions" + depends on PCI + depends on UWB + select CRYPTO + select CRYPTO_BLKCIPHER + select CRYPTO_CBC + select CRYPTO_MANAGER + select CRYPTO_AES + help + Enable the host-side support for Wireless USB. + + To compile this support select Y (built in). It is safe to + select even if you don't have the hardware. + +config USB_WUSB_CBAF + tristate "Support WUSB Cable Based Association (CBA)" + help + Some WUSB devices support Cable Based Association. It's used to + enable the secure communication between the host and the + device. + + Enable this option if your WUSB device must to be connected + via wired USB before establishing a wireless link. + + It is safe to select even if you don't have a compatible + hardware. + +config USB_WUSB_CBAF_DEBUG + bool "Enable CBA debug messages" + depends on USB_WUSB_CBAF + help + Say Y here if you want the CBA to produce a bunch of debug messages + to the system log. Select this if you are having a problem with + CBA support and want to see more of what is going on. + diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile new file mode 100644 index 00000000..b3bd3130 --- /dev/null +++ b/drivers/usb/wusbcore/Makefile @@ -0,0 +1,25 @@ +ccflags-$(CONFIG_USB_WUSB_CBAF_DEBUG) := -DDEBUG + +obj-$(CONFIG_USB_WUSB) += wusbcore.o +obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o +obj-$(CONFIG_USB_WUSB_CBAF) += wusb-cbaf.o + + +wusbcore-y := \ + crypto.o \ + devconnect.o \ + dev-sysfs.o \ + mmc.o \ + pal.o \ + rh.o \ + reservation.o \ + security.o \ + wusbhc.o + +wusb-cbaf-y := cbaf.o + +wusb-wa-y := \ + wa-hc.o \ + wa-nep.o \ + wa-rpipe.o \ + wa-xfer.o diff --git a/drivers/usb/wusbcore/cbaf.c b/drivers/usb/wusbcore/cbaf.c new file mode 100644 index 00000000..7f78f300 --- /dev/null +++ b/drivers/usb/wusbcore/cbaf.c @@ -0,0 +1,662 @@ +/* + * Wireless USB - Cable Based Association + * + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * WUSB devices have to be paired (associated in WUSB lingo) so + * that they can connect to the system. + * + * One way of pairing is using CBA-Cable Based Association. First + * time you plug the device with a cable, association is done between + * host and device and subsequent times, you can connect wirelessly + * without having to associate again. That's the idea. + * + * This driver does nothing Earth shattering. It just provides an + * interface to chat with the wire-connected device so we can get a + * CDID (device ID) that might have been previously associated to a + * CHID (host ID) and to set up a new triplet + * (connection context), with the CK being the secret, or connection + * key. This is the pairing data. + * + * When a device with the CBA capability connects, the probe routine + * just creates a bunch of sysfs files that a user space enumeration + * manager uses to allow it to connect wirelessly to the system or not. + * + * The process goes like this: + * + * 1. Device plugs, cbaf is loaded, notifications happen. + * + * 2. The connection manager (CM) sees a device with CBAF capability + * (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE). + * + * 3. The CM writes the host name, supported band groups, and the CHID + * (host ID) into the wusb_host_name, wusb_host_band_groups and + * wusb_chid files. These get sent to the device and the CDID (if + * any) for this host is requested. + * + * 4. The CM can verify that the device's supported band groups + * (wusb_device_band_groups) are compatible with the host. + * + * 5. The CM reads the wusb_cdid file. + * + * 6. The CM looks up its database + * + * 6.1 If it has a matching CHID,CDID entry, the device has been + * authorized before (paired) and nothing further needs to be + * done. + * + * 6.2 If the CDID is zero (or the CM doesn't find a matching CDID in + * its database), the device is assumed to be not known. The CM + * may associate the host with device by: writing a randomly + * generated CDID to wusb_cdid and then a random CK to wusb_ck + * (this uploads the new CC to the device). + * + * CMD may choose to prompt the user before associating with a new + * device. + * + * 7. Device is unplugged. + * + * When the device tries to connect wirelessly, it will present its + * CDID to the WUSB host controller. The CM will query the + * database. If the CHID/CDID pair found, it will (with a 4-way + * handshake) challenge the device to demonstrate it has the CK secret + * key (from our database) without actually exchanging it. Once + * satisfied, crypto keys are derived from the CK, the device is + * connected and all communication is encrypted. + * + * References: + * [WUSB-AM] Association Models Supplement to the Certified Wireless + * Universal Serial Bus Specification, version 1.0. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CBA_NAME_LEN 0x40 /* [WUSB-AM] table 4-7 */ + +/* An instance of a Cable-Based-Association-Framework device */ +struct cbaf { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + void *buffer; + size_t buffer_size; + + struct wusb_ckhdid chid; + char host_name[CBA_NAME_LEN]; + u16 host_band_groups; + + struct wusb_ckhdid cdid; + char device_name[CBA_NAME_LEN]; + u16 device_band_groups; + + struct wusb_ckhdid ck; +}; + +/* + * Verify that a CBAF USB-interface has what we need + * + * According to [WUSB-AM], CBA devices should provide at least two + * interfaces: + * - RETRIEVE_HOST_INFO + * - ASSOCIATE + * + * If the device doesn't provide these interfaces, we do not know how + * to deal with it. + */ +static int cbaf_check(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_assoc_info *assoc_info; + struct wusb_cbaf_assoc_request *assoc_request; + size_t assoc_size; + void *itr, *top; + int ar_rhi = 0, ar_assoc = 0; + + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_INFORMATION, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + cbaf->buffer, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot get available association types: %d\n", + result); + return result; + } + + assoc_info = cbaf->buffer; + if (result < sizeof(*assoc_info)) { + dev_err(dev, "Not enough data to decode association info " + "header (%zu vs %zu bytes required)\n", + (size_t)result, sizeof(*assoc_info)); + return result; + } + + assoc_size = le16_to_cpu(assoc_info->Length); + if (result < assoc_size) { + dev_err(dev, "Not enough data to decode association info " + "(%zu vs %zu bytes required)\n", + (size_t)assoc_size, sizeof(*assoc_info)); + return result; + } + /* + * From now on, we just verify, but won't error out unless we + * don't find the AR_TYPE_WUSB_{RETRIEVE_HOST_INFO,ASSOCIATE} + * types. + */ + itr = cbaf->buffer + sizeof(*assoc_info); + top = cbaf->buffer + assoc_size; + dev_dbg(dev, "Found %u association requests (%zu bytes)\n", + assoc_info->NumAssociationRequests, assoc_size); + + while (itr < top) { + u16 ar_type, ar_subtype; + u32 ar_size; + const char *ar_name; + + assoc_request = itr; + + if (top - itr < sizeof(*assoc_request)) { + dev_err(dev, "Not enough data to decode associaton " + "request (%zu vs %zu bytes needed)\n", + top - itr, sizeof(*assoc_request)); + break; + } + + ar_type = le16_to_cpu(assoc_request->AssociationTypeId); + ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId); + ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize); + ar_name = "unknown"; + + switch (ar_type) { + case AR_TYPE_WUSB: + /* Verify we have what is mandated by [WUSB-AM]. */ + switch (ar_subtype) { + case AR_TYPE_WUSB_RETRIEVE_HOST_INFO: + ar_name = "RETRIEVE_HOST_INFO"; + ar_rhi = 1; + break; + case AR_TYPE_WUSB_ASSOCIATE: + /* send assoc data */ + ar_name = "ASSOCIATE"; + ar_assoc = 1; + break; + }; + break; + }; + + dev_dbg(dev, "Association request #%02u: 0x%04x/%04x " + "(%zu bytes): %s\n", + assoc_request->AssociationDataIndex, ar_type, + ar_subtype, (size_t)ar_size, ar_name); + + itr += sizeof(*assoc_request); + } + + if (!ar_rhi) { + dev_err(dev, "Missing RETRIEVE_HOST_INFO association " + "request\n"); + return -EINVAL; + } + if (!ar_assoc) { + dev_err(dev, "Missing ASSOCIATE association request\n"); + return -EINVAL; + } + + return 0; +} + +static const struct wusb_cbaf_host_info cbaf_host_info_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB), + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_RETRIEVE_HOST_INFO), + .CHID_hdr = WUSB_AR_CHID, + .LangID_hdr = WUSB_AR_LangID, + .HostFriendlyName_hdr = WUSB_AR_HostFriendlyName, +}; + +/* Send WUSB host information (CHID and name) to a CBAF device */ +static int cbaf_send_host_info(struct cbaf *cbaf) +{ + struct wusb_cbaf_host_info *hi; + size_t name_len; + size_t hi_size; + + hi = cbaf->buffer; + memset(hi, 0, sizeof(*hi)); + *hi = cbaf_host_info_defaults; + hi->CHID = cbaf->chid; + hi->LangID = 0; /* FIXME: I guess... */ + strlcpy(hi->HostFriendlyName, cbaf->host_name, CBA_NAME_LEN); + name_len = strlen(cbaf->host_name); + hi->HostFriendlyName_hdr.len = cpu_to_le16(name_len); + hi_size = sizeof(*hi) + name_len; + + return usb_control_msg(cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_SET_ASSOCIATION_RESPONSE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0101, + cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + hi, hi_size, 1000 /* FIXME: arbitrary */); +} + +/* + * Get device's information (CDID) associated to CHID + * + * The device will return it's information (CDID, name, bandgroups) + * associated to the CHID we have set before, or 0 CDID and default + * name and bandgroup if no CHID set or unknown. + */ +static int cbaf_cdid_get(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_device_info *di; + size_t needed; + + di = cbaf->buffer; + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_REQUEST, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot request device information: %d\n", result); + return result; + } + + needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length); + if (result < needed) { + dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs " + "%zu bytes needed)\n", (size_t)result, needed); + return -ENOENT; + } + + strlcpy(cbaf->device_name, di->DeviceFriendlyName, CBA_NAME_LEN); + cbaf->cdid = di->CDID; + cbaf->device_band_groups = le16_to_cpu(di->BandGroups); + + return 0; +} + +static ssize_t cbaf_wusb_chid_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_chid[WUSB_CKHDID_STRSIZE]; + + ckhdid_printf(pr_chid, sizeof(pr_chid), &cbaf->chid); + return scnprintf(buf, PAGE_SIZE, "%s\n", pr_chid); +} + +static ssize_t cbaf_wusb_chid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx", + &cbaf->chid.data[0] , &cbaf->chid.data[1], + &cbaf->chid.data[2] , &cbaf->chid.data[3], + &cbaf->chid.data[4] , &cbaf->chid.data[5], + &cbaf->chid.data[6] , &cbaf->chid.data[7], + &cbaf->chid.data[8] , &cbaf->chid.data[9], + &cbaf->chid.data[10], &cbaf->chid.data[11], + &cbaf->chid.data[12], &cbaf->chid.data[13], + &cbaf->chid.data[14], &cbaf->chid.data[15]); + + if (result != 16) + return -EINVAL; + + result = cbaf_send_host_info(cbaf); + if (result < 0) + return result; + result = cbaf_cdid_get(cbaf); + if (result < 0) + return result; + return size; +} +static DEVICE_ATTR(wusb_chid, 0600, cbaf_wusb_chid_show, cbaf_wusb_chid_store); + +static ssize_t cbaf_wusb_host_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->host_name); +} + +static ssize_t cbaf_wusb_host_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, "%63s", cbaf->host_name); + if (result != 1) + return -EINVAL; + + return size; +} +static DEVICE_ATTR(wusb_host_name, 0600, cbaf_wusb_host_name_show, + cbaf_wusb_host_name_store); + +static ssize_t cbaf_wusb_host_band_groups_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->host_band_groups); +} + +static ssize_t cbaf_wusb_host_band_groups_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + u16 band_groups = 0; + + result = sscanf(buf, "%04hx", &band_groups); + if (result != 1) + return -EINVAL; + + cbaf->host_band_groups = band_groups; + + return size; +} + +static DEVICE_ATTR(wusb_host_band_groups, 0600, + cbaf_wusb_host_band_groups_show, + cbaf_wusb_host_band_groups_store); + +static const struct wusb_cbaf_device_info cbaf_device_info_defaults = { + .Length_hdr = WUSB_AR_Length, + .CDID_hdr = WUSB_AR_CDID, + .BandGroups_hdr = WUSB_AR_BandGroups, + .LangID_hdr = WUSB_AR_LangID, + .DeviceFriendlyName_hdr = WUSB_AR_DeviceFriendlyName, +}; + +static ssize_t cbaf_wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_cdid[WUSB_CKHDID_STRSIZE]; + + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid); + return scnprintf(buf, PAGE_SIZE, "%s\n", pr_cdid); +} + +static ssize_t cbaf_wusb_cdid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + struct wusb_ckhdid cdid; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx", + &cdid.data[0] , &cdid.data[1], + &cdid.data[2] , &cdid.data[3], + &cdid.data[4] , &cdid.data[5], + &cdid.data[6] , &cdid.data[7], + &cdid.data[8] , &cdid.data[9], + &cdid.data[10], &cdid.data[11], + &cdid.data[12], &cdid.data[13], + &cdid.data[14], &cdid.data[15]); + if (result != 16) + return -EINVAL; + + cbaf->cdid = cdid; + + return size; +} +static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_show, cbaf_wusb_cdid_store); + +static ssize_t cbaf_wusb_device_band_groups_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->device_band_groups); +} + +static DEVICE_ATTR(wusb_device_band_groups, 0600, + cbaf_wusb_device_band_groups_show, + NULL); + +static ssize_t cbaf_wusb_device_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->device_name); +} +static DEVICE_ATTR(wusb_device_name, 0600, cbaf_wusb_device_name_show, NULL); + +static const struct wusb_cbaf_cc_data cbaf_cc_data_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB), + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_ASSOCIATE), + .Length_hdr = WUSB_AR_Length, + .Length = cpu_to_le32(sizeof(struct wusb_cbaf_cc_data)), + .ConnectionContext_hdr = WUSB_AR_ConnectionContext, + .BandGroups_hdr = WUSB_AR_BandGroups, +}; + +static const struct wusb_cbaf_cc_data_fail cbaf_cc_data_fail_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .Length_hdr = WUSB_AR_Length, + .AssociationStatus_hdr = WUSB_AR_AssociationStatus, +}; + +/* + * Send a new CC to the device. + */ +static int cbaf_cc_upload(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_cc_data *ccd; + char pr_cdid[WUSB_CKHDID_STRSIZE]; + + ccd = cbaf->buffer; + *ccd = cbaf_cc_data_defaults; + ccd->CHID = cbaf->chid; + ccd->CDID = cbaf->cdid; + ccd->CK = cbaf->ck; + ccd->BandGroups = cpu_to_le16(cbaf->host_band_groups); + + dev_dbg(dev, "Trying to upload CC:\n"); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID); + dev_dbg(dev, " CHID %s\n", pr_cdid); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID); + dev_dbg(dev, " CDID %s\n", pr_cdid); + dev_dbg(dev, " Bandgroups 0x%04x\n", cbaf->host_band_groups); + + result = usb_control_msg( + cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_SET_ASSOCIATION_RESPONSE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + ccd, sizeof(*ccd), 1000 /* FIXME: arbitrary */); + + return result; +} + +static ssize_t cbaf_wusb_ck_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx", + &cbaf->ck.data[0] , &cbaf->ck.data[1], + &cbaf->ck.data[2] , &cbaf->ck.data[3], + &cbaf->ck.data[4] , &cbaf->ck.data[5], + &cbaf->ck.data[6] , &cbaf->ck.data[7], + &cbaf->ck.data[8] , &cbaf->ck.data[9], + &cbaf->ck.data[10], &cbaf->ck.data[11], + &cbaf->ck.data[12], &cbaf->ck.data[13], + &cbaf->ck.data[14], &cbaf->ck.data[15]); + if (result != 16) + return -EINVAL; + + result = cbaf_cc_upload(cbaf); + if (result < 0) + return result; + + return size; +} +static DEVICE_ATTR(wusb_ck, 0600, NULL, cbaf_wusb_ck_store); + +static struct attribute *cbaf_dev_attrs[] = { + &dev_attr_wusb_host_name.attr, + &dev_attr_wusb_host_band_groups.attr, + &dev_attr_wusb_chid.attr, + &dev_attr_wusb_cdid.attr, + &dev_attr_wusb_device_name.attr, + &dev_attr_wusb_device_band_groups.attr, + &dev_attr_wusb_ck.attr, + NULL, +}; + +static struct attribute_group cbaf_dev_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = cbaf_dev_attrs, +}; + +static int cbaf_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct cbaf *cbaf; + struct device *dev = &iface->dev; + int result = -ENOMEM; + + cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL); + if (cbaf == NULL) + goto error_kzalloc; + cbaf->buffer = kmalloc(512, GFP_KERNEL); + if (cbaf->buffer == NULL) + goto error_kmalloc_buffer; + + cbaf->buffer_size = 512; + cbaf->usb_dev = usb_get_dev(interface_to_usbdev(iface)); + cbaf->usb_iface = usb_get_intf(iface); + result = cbaf_check(cbaf); + if (result < 0) { + dev_err(dev, "This device is not WUSB-CBAF compliant" + "and is not supported yet.\n"); + goto error_check; + } + + result = sysfs_create_group(&dev->kobj, &cbaf_dev_attr_group); + if (result < 0) { + dev_err(dev, "Can't register sysfs attr group: %d\n", result); + goto error_create_group; + } + usb_set_intfdata(iface, cbaf); + return 0; + +error_create_group: +error_check: + kfree(cbaf->buffer); +error_kmalloc_buffer: + kfree(cbaf); +error_kzalloc: + return result; +} + +static void cbaf_disconnect(struct usb_interface *iface) +{ + struct cbaf *cbaf = usb_get_intfdata(iface); + struct device *dev = &iface->dev; + sysfs_remove_group(&dev->kobj, &cbaf_dev_attr_group); + usb_set_intfdata(iface, NULL); + usb_put_intf(iface); + kfree(cbaf->buffer); + /* paranoia: clean up crypto keys */ + kzfree(cbaf); +} + +static const struct usb_device_id cbaf_id_table[] = { + { USB_INTERFACE_INFO(0xef, 0x03, 0x01), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, cbaf_id_table); + +static struct usb_driver cbaf_driver = { + .name = "wusb-cbaf", + .id_table = cbaf_id_table, + .probe = cbaf_probe, + .disconnect = cbaf_disconnect, +}; + +module_usb_driver(cbaf_driver); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB Cable Based Association"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/crypto.c b/drivers/usb/wusbcore/crypto.c new file mode 100644 index 00000000..7e4bf95f --- /dev/null +++ b/drivers/usb/wusbcore/crypto.c @@ -0,0 +1,518 @@ +/* + * Ultra Wide Band + * AES-128 CCM Encryption + * + * Copyright (C) 2007 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We don't do any encryption here; we use the Linux Kernel's AES-128 + * crypto modules to construct keys and payload blocks in a way + * defined by WUSB1.0[6]. Check the erratas, as typos are are patched + * there. + * + * Thanks a zillion to John Keys for his help and clarifications over + * the designed-by-a-committee text. + * + * So the idea is that there is this basic Pseudo-Random-Function + * defined in WUSB1.0[6.5] which is the core of everything. It works + * by tweaking some blocks, AES crypting them and then xoring + * something else with them (this seems to be called CBC(AES) -- can + * you tell I know jack about crypto?). So we just funnel it into the + * Linux Crypto API. + * + * We leave a crypto test module so we can verify that vectors match, + * every now and then. + * + * Block size: 16 bytes -- AES seems to do things in 'block sizes'. I + * am learning a lot... + * + * Conveniently, some data structures that need to be + * funneled through AES are...16 bytes in size! + */ + +#include +#include +#include +#include +#include +#include +#include + +static int debug_crypto_verify = 0; + +module_param(debug_crypto_verify, int, 0); +MODULE_PARM_DESC(debug_crypto_verify, "verify the key generation algorithms"); + +static void wusb_key_dump(const void *buf, size_t len) +{ + print_hex_dump(KERN_ERR, " ", DUMP_PREFIX_OFFSET, 16, 1, + buf, len, 0); +} + +/* + * Block of data, as understood by AES-CCM + * + * The code assumes this structure is nothing but a 16 byte array + * (packed in a struct to avoid common mess ups that I usually do with + * arrays and enforcing type checking). + */ +struct aes_ccm_block { + u8 data[16]; +} __attribute__((packed)); + +/* + * Counter-mode Blocks (WUSB1.0[6.4]) + * + * According to CCM (or so it seems), for the purpose of calculating + * the MIC, the message is broken in N counter-mode blocks, B0, B1, + * ... BN. + * + * B0 contains flags, the CCM nonce and l(m). + * + * B1 contains l(a), the MAC header, the encryption offset and padding. + * + * If EO is nonzero, additional blocks are built from payload bytes + * until EO is exahusted (FIXME: padding to 16 bytes, I guess). The + * padding is not xmitted. + */ + +/* WUSB1.0[T6.4] */ +struct aes_ccm_b0 { + u8 flags; /* 0x59, per CCM spec */ + struct aes_ccm_nonce ccm_nonce; + __be16 lm; +} __attribute__((packed)); + +/* WUSB1.0[T6.5] */ +struct aes_ccm_b1 { + __be16 la; + u8 mac_header[10]; + __le16 eo; + u8 security_reserved; /* This is always zero */ + u8 padding; /* 0 */ +} __attribute__((packed)); + +/* + * Encryption Blocks (WUSB1.0[6.4.4]) + * + * CCM uses Ax blocks to generate a keystream with which the MIC and + * the message's payload are encoded. A0 always encrypts/decrypts the + * MIC. Ax (x>0) are used for the successive payload blocks. + * + * The x is the counter, and is increased for each block. + */ +struct aes_ccm_a { + u8 flags; /* 0x01, per CCM spec */ + struct aes_ccm_nonce ccm_nonce; + __be16 counter; /* Value of x */ +} __attribute__((packed)); + +static void bytewise_xor(void *_bo, const void *_bi1, const void *_bi2, + size_t size) +{ + u8 *bo = _bo; + const u8 *bi1 = _bi1, *bi2 = _bi2; + size_t itr; + for (itr = 0; itr < size; itr++) + bo[itr] = bi1[itr] ^ bi2[itr]; +} + +/* + * CC-MAC function WUSB1.0[6.5] + * + * Take a data string and produce the encrypted CBC Counter-mode MIC + * + * Note the names for most function arguments are made to (more or + * less) match those used in the pseudo-function definition given in + * WUSB1.0[6.5]. + * + * @tfm_cbc: CBC(AES) blkcipher handle (initialized) + * + * @tfm_aes: AES cipher handle (initialized) + * + * @mic: buffer for placing the computed MIC (Message Integrity + * Code). This is exactly 8 bytes, and we expect the buffer to + * be at least eight bytes in length. + * + * @key: 128 bit symmetric key + * + * @n: CCM nonce + * + * @a: ASCII string, 14 bytes long (I guess zero padded if needed; + * we use exactly 14 bytes). + * + * @b: data stream to be processed; cannot be a global or const local + * (will confuse the scatterlists) + * + * @blen: size of b... + * + * Still not very clear how this is done, but looks like this: we + * create block B0 (as WUSB1.0[6.5] says), then we AES-crypt it with + * @key. We bytewise xor B0 with B1 (1) and AES-crypt that. Then we + * take the payload and divide it in blocks (16 bytes), xor them with + * the previous crypto result (16 bytes) and crypt it, repeat the next + * block with the output of the previous one, rinse wash (I guess this + * is what AES CBC mode means...but I truly have no idea). So we use + * the CBC(AES) blkcipher, that does precisely that. The IV (Initial + * Vector) is 16 bytes and is set to zero, so + * + * See rfc3610. Linux crypto has a CBC implementation, but the + * documentation is scarce, to say the least, and the example code is + * so intricated that is difficult to understand how things work. Most + * of this is guess work -- bite me. + * + * (1) Created as 6.5 says, again, using as l(a) 'Blen + 14', and + * using the 14 bytes of @a to fill up + * b1.{mac_header,e0,security_reserved,padding}. + * + * NOTE: The definition of l(a) in WUSB1.0[6.5] vs the definition of + * l(m) is orthogonal, they bear no relationship, so it is not + * in conflict with the parameter's relation that + * WUSB1.0[6.4.2]) defines. + * + * NOTE: WUSB1.0[A.1]: Host Nonce is missing a nibble? (1e); fixed in + * first errata released on 2005/07. + * + * NOTE: we need to clean IV to zero at each invocation to make sure + * we start with a fresh empty Initial Vector, so that the CBC + * works ok. + * + * NOTE: blen is not aligned to a block size, we'll pad zeros, that's + * what sg[4] is for. Maybe there is a smarter way to do this. + */ +static int wusb_ccm_mac(struct crypto_blkcipher *tfm_cbc, + struct crypto_cipher *tfm_aes, void *mic, + const struct aes_ccm_nonce *n, + const struct aes_ccm_label *a, const void *b, + size_t blen) +{ + int result = 0; + struct blkcipher_desc desc; + struct aes_ccm_b0 b0; + struct aes_ccm_b1 b1; + struct aes_ccm_a ax; + struct scatterlist sg[4], sg_dst; + void *iv, *dst_buf; + size_t ivsize, dst_size; + const u8 bzero[16] = { 0 }; + size_t zero_padding; + + /* + * These checks should be compile time optimized out + * ensure @a fills b1's mac_header and following fields + */ + WARN_ON(sizeof(*a) != sizeof(b1) - sizeof(b1.la)); + WARN_ON(sizeof(b0) != sizeof(struct aes_ccm_block)); + WARN_ON(sizeof(b1) != sizeof(struct aes_ccm_block)); + WARN_ON(sizeof(ax) != sizeof(struct aes_ccm_block)); + + result = -ENOMEM; + zero_padding = sizeof(struct aes_ccm_block) + - blen % sizeof(struct aes_ccm_block); + zero_padding = blen % sizeof(struct aes_ccm_block); + if (zero_padding) + zero_padding = sizeof(struct aes_ccm_block) - zero_padding; + dst_size = blen + sizeof(b0) + sizeof(b1) + zero_padding; + dst_buf = kzalloc(dst_size, GFP_KERNEL); + if (dst_buf == NULL) { + printk(KERN_ERR "E: can't alloc destination buffer\n"); + goto error_dst_buf; + } + + iv = crypto_blkcipher_crt(tfm_cbc)->iv; + ivsize = crypto_blkcipher_ivsize(tfm_cbc); + memset(iv, 0, ivsize); + + /* Setup B0 */ + b0.flags = 0x59; /* Format B0 */ + b0.ccm_nonce = *n; + b0.lm = cpu_to_be16(0); /* WUSB1.0[6.5] sez l(m) is 0 */ + + /* Setup B1 + * + * The WUSB spec is anything but clear! WUSB1.0[6.5] + * says that to initialize B1 from A with 'l(a) = blen + + * 14'--after clarification, it means to use A's contents + * for MAC Header, EO, sec reserved and padding. + */ + b1.la = cpu_to_be16(blen + 14); + memcpy(&b1.mac_header, a, sizeof(*a)); + + sg_init_table(sg, ARRAY_SIZE(sg)); + sg_set_buf(&sg[0], &b0, sizeof(b0)); + sg_set_buf(&sg[1], &b1, sizeof(b1)); + sg_set_buf(&sg[2], b, blen); + /* 0 if well behaved :) */ + sg_set_buf(&sg[3], bzero, zero_padding); + sg_init_one(&sg_dst, dst_buf, dst_size); + + desc.tfm = tfm_cbc; + desc.flags = 0; + result = crypto_blkcipher_encrypt(&desc, &sg_dst, sg, dst_size); + if (result < 0) { + printk(KERN_ERR "E: can't compute CBC-MAC tag (MIC): %d\n", + result); + goto error_cbc_crypt; + } + + /* Now we crypt the MIC Tag (*iv) with Ax -- values per WUSB1.0[6.5] + * The procedure is to AES crypt the A0 block and XOR the MIC + * Tag against it; we only do the first 8 bytes and place it + * directly in the destination buffer. + * + * POS Crypto API: size is assumed to be AES's block size. + * Thanks for documenting it -- tip taken from airo.c + */ + ax.flags = 0x01; /* as per WUSB 1.0 spec */ + ax.ccm_nonce = *n; + ax.counter = 0; + crypto_cipher_encrypt_one(tfm_aes, (void *)&ax, (void *)&ax); + bytewise_xor(mic, &ax, iv, 8); + result = 8; +error_cbc_crypt: + kfree(dst_buf); +error_dst_buf: + return result; +} + +/* + * WUSB Pseudo Random Function (WUSB1.0[6.5]) + * + * @b: buffer to the source data; cannot be a global or const local + * (will confuse the scatterlists) + */ +ssize_t wusb_prf(void *out, size_t out_size, + const u8 key[16], const struct aes_ccm_nonce *_n, + const struct aes_ccm_label *a, + const void *b, size_t blen, size_t len) +{ + ssize_t result, bytes = 0, bitr; + struct aes_ccm_nonce n = *_n; + struct crypto_blkcipher *tfm_cbc; + struct crypto_cipher *tfm_aes; + u64 sfn = 0; + __le64 sfn_le; + + tfm_cbc = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm_cbc)) { + result = PTR_ERR(tfm_cbc); + printk(KERN_ERR "E: can't load CBC(AES): %d\n", (int)result); + goto error_alloc_cbc; + } + result = crypto_blkcipher_setkey(tfm_cbc, key, 16); + if (result < 0) { + printk(KERN_ERR "E: can't set CBC key: %d\n", (int)result); + goto error_setkey_cbc; + } + + tfm_aes = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm_aes)) { + result = PTR_ERR(tfm_aes); + printk(KERN_ERR "E: can't load AES: %d\n", (int)result); + goto error_alloc_aes; + } + result = crypto_cipher_setkey(tfm_aes, key, 16); + if (result < 0) { + printk(KERN_ERR "E: can't set AES key: %d\n", (int)result); + goto error_setkey_aes; + } + + for (bitr = 0; bitr < (len + 63) / 64; bitr++) { + sfn_le = cpu_to_le64(sfn++); + memcpy(&n.sfn, &sfn_le, sizeof(n.sfn)); /* n.sfn++... */ + result = wusb_ccm_mac(tfm_cbc, tfm_aes, out + bytes, + &n, a, b, blen); + if (result < 0) + goto error_ccm_mac; + bytes += result; + } + result = bytes; +error_ccm_mac: +error_setkey_aes: + crypto_free_cipher(tfm_aes); +error_alloc_aes: +error_setkey_cbc: + crypto_free_blkcipher(tfm_cbc); +error_alloc_cbc: + return result; +} + +/* WUSB1.0[A.2] test vectors */ +static const u8 stv_hsmic_key[16] = { + 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d, + 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f +}; + +static const struct aes_ccm_nonce stv_hsmic_n = { + .sfn = { 0 }, + .tkid = { 0x76, 0x98, 0x01, }, + .dest_addr = { .data = { 0xbe, 0x00 } }, + .src_addr = { .data = { 0x76, 0x98 } }, +}; + +/* + * Out-of-band MIC Generation verification code + * + */ +static int wusb_oob_mic_verify(void) +{ + int result; + u8 mic[8]; + /* WUSB1.0[A.2] test vectors + * + * Need to keep it in the local stack as GCC 4.1.3something + * messes up and generates noise. + */ + struct usb_handshake stv_hsmic_hs = { + .bMessageNumber = 2, + .bStatus = 00, + .tTKID = { 0x76, 0x98, 0x01 }, + .bReserved = 00, + .CDID = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f }, + .nonce = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f }, + .MIC = { 0x75, 0x6a, 0x97, 0x51, 0x0c, 0x8c, + 0x14, 0x7b } , + }; + size_t hs_size; + + result = wusb_oob_mic(mic, stv_hsmic_key, &stv_hsmic_n, &stv_hsmic_hs); + if (result < 0) + printk(KERN_ERR "E: WUSB OOB MIC test: failed: %d\n", result); + else if (memcmp(stv_hsmic_hs.MIC, mic, sizeof(mic))) { + printk(KERN_ERR "E: OOB MIC test: " + "mismatch between MIC result and WUSB1.0[A2]\n"); + hs_size = sizeof(stv_hsmic_hs) - sizeof(stv_hsmic_hs.MIC); + printk(KERN_ERR "E: Handshake2 in: (%zu bytes)\n", hs_size); + wusb_key_dump(&stv_hsmic_hs, hs_size); + printk(KERN_ERR "E: CCM Nonce in: (%zu bytes)\n", + sizeof(stv_hsmic_n)); + wusb_key_dump(&stv_hsmic_n, sizeof(stv_hsmic_n)); + printk(KERN_ERR "E: MIC out:\n"); + wusb_key_dump(mic, sizeof(mic)); + printk(KERN_ERR "E: MIC out (from WUSB1.0[A.2]):\n"); + wusb_key_dump(stv_hsmic_hs.MIC, sizeof(stv_hsmic_hs.MIC)); + result = -EINVAL; + } else + result = 0; + return result; +} + +/* + * Test vectors for Key derivation + * + * These come from WUSB1.0[6.5.1], the vectors in WUSB1.0[A.1] + * (errata corrected in 2005/07). + */ +static const u8 stv_key_a1[16] __attribute__ ((__aligned__(4))) = { + 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, + 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f +}; + +static const struct aes_ccm_nonce stv_keydvt_n_a1 = { + .sfn = { 0 }, + .tkid = { 0x76, 0x98, 0x01, }, + .dest_addr = { .data = { 0xbe, 0x00 } }, + .src_addr = { .data = { 0x76, 0x98 } }, +}; + +static const struct wusb_keydvt_out stv_keydvt_out_a1 = { + .kck = { + 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d, + 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f + }, + .ptk = { + 0xc8, 0x70, 0x62, 0x82, 0xb6, 0x7c, 0xe9, 0x06, + 0x7b, 0xc5, 0x25, 0x69, 0xf2, 0x36, 0x61, 0x2d + } +}; + +/* + * Performa a test to make sure we match the vectors defined in + * WUSB1.0[A.1](Errata2006/12) + */ +static int wusb_key_derive_verify(void) +{ + int result = 0; + struct wusb_keydvt_out keydvt_out; + /* These come from WUSB1.0[A.1] + 2006/12 errata + * NOTE: can't make this const or global -- somehow it seems + * the scatterlists for crypto get confused and we get + * bad data. There is no doc on this... */ + struct wusb_keydvt_in stv_keydvt_in_a1 = { + .hnonce = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }, + .dnonce = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f + } + }; + + result = wusb_key_derive(&keydvt_out, stv_key_a1, &stv_keydvt_n_a1, + &stv_keydvt_in_a1); + if (result < 0) + printk(KERN_ERR "E: WUSB key derivation test: " + "derivation failed: %d\n", result); + if (memcmp(&stv_keydvt_out_a1, &keydvt_out, sizeof(keydvt_out))) { + printk(KERN_ERR "E: WUSB key derivation test: " + "mismatch between key derivation result " + "and WUSB1.0[A1] Errata 2006/12\n"); + printk(KERN_ERR "E: keydvt in: key\n"); + wusb_key_dump(stv_key_a1, sizeof(stv_key_a1)); + printk(KERN_ERR "E: keydvt in: nonce\n"); + wusb_key_dump( &stv_keydvt_n_a1, sizeof(stv_keydvt_n_a1)); + printk(KERN_ERR "E: keydvt in: hnonce & dnonce\n"); + wusb_key_dump(&stv_keydvt_in_a1, sizeof(stv_keydvt_in_a1)); + printk(KERN_ERR "E: keydvt out: KCK\n"); + wusb_key_dump(&keydvt_out.kck, sizeof(keydvt_out.kck)); + printk(KERN_ERR "E: keydvt out: PTK\n"); + wusb_key_dump(&keydvt_out.ptk, sizeof(keydvt_out.ptk)); + result = -EINVAL; + } else + result = 0; + return result; +} + +/* + * Initialize crypto system + * + * FIXME: we do nothing now, other than verifying. Later on we'll + * cache the encryption stuff, so that's why we have a separate init. + */ +int wusb_crypto_init(void) +{ + int result; + + if (debug_crypto_verify) { + result = wusb_key_derive_verify(); + if (result < 0) + return result; + return wusb_oob_mic_verify(); + } + return 0; +} + +void wusb_crypto_exit(void) +{ + /* FIXME: free cached crypto transforms */ +} diff --git a/drivers/usb/wusbcore/dev-sysfs.c b/drivers/usb/wusbcore/dev-sysfs.c new file mode 100644 index 00000000..10183457 --- /dev/null +++ b/drivers/usb/wusbcore/dev-sysfs.c @@ -0,0 +1,139 @@ +/* + * WUSB devices + * sysfs bindings + * + * Copyright (C) 2007 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Get them out of the way... + */ + +#include +#include +#include +#include "wusbhc.h" + +static ssize_t wusb_disconnect_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_device *usb_dev; + struct wusbhc *wusbhc; + unsigned command; + u8 port_idx; + + if (sscanf(buf, "%u", &command) != 1) + return -EINVAL; + if (command == 0) + return size; + usb_dev = to_usb_device(dev); + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return -ENODEV; + + mutex_lock(&wusbhc->mutex); + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + __wusbhc_dev_disable(wusbhc, port_idx); + mutex_unlock(&wusbhc->mutex); + wusbhc_put(wusbhc); + return size; +} +static DEVICE_ATTR(wusb_disconnect, 0200, NULL, wusb_disconnect_store); + +static ssize_t wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t result; + struct wusb_dev *wusb_dev; + + wusb_dev = wusb_dev_get_by_usb_dev(to_usb_device(dev)); + if (wusb_dev == NULL) + return -ENODEV; + result = ckhdid_printf(buf, PAGE_SIZE, &wusb_dev->cdid); + strcat(buf, "\n"); + wusb_dev_put(wusb_dev); + return result + 1; +} +static DEVICE_ATTR(wusb_cdid, 0444, wusb_cdid_show, NULL); + +static ssize_t wusb_ck_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int result; + struct usb_device *usb_dev; + struct wusbhc *wusbhc; + struct wusb_ckhdid ck; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &ck.data[0] , &ck.data[1], + &ck.data[2] , &ck.data[3], + &ck.data[4] , &ck.data[5], + &ck.data[6] , &ck.data[7], + &ck.data[8] , &ck.data[9], + &ck.data[10], &ck.data[11], + &ck.data[12], &ck.data[13], + &ck.data[14], &ck.data[15]); + if (result != 16) + return -EINVAL; + + usb_dev = to_usb_device(dev); + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return -ENODEV; + result = wusb_dev_4way_handshake(wusbhc, usb_dev->wusb_dev, &ck); + memset(&ck, 0, sizeof(ck)); + wusbhc_put(wusbhc); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_ck, 0200, NULL, wusb_ck_store); + +static struct attribute *wusb_dev_attrs[] = { + &dev_attr_wusb_disconnect.attr, + &dev_attr_wusb_cdid.attr, + &dev_attr_wusb_ck.attr, + NULL, +}; + +static struct attribute_group wusb_dev_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wusb_dev_attrs, +}; + +int wusb_dev_sysfs_add(struct wusbhc *wusbhc, struct usb_device *usb_dev, + struct wusb_dev *wusb_dev) +{ + int result = sysfs_create_group(&usb_dev->dev.kobj, + &wusb_dev_attr_group); + struct device *dev = &usb_dev->dev; + if (result < 0) + dev_err(dev, "Cannot register WUSB-dev attributes: %d\n", + result); + return result; +} + +void wusb_dev_sysfs_rm(struct wusb_dev *wusb_dev) +{ + struct usb_device *usb_dev = wusb_dev->usb_dev; + if (usb_dev) + sysfs_remove_group(&usb_dev->dev.kobj, &wusb_dev_attr_group); +} diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c new file mode 100644 index 00000000..1d365316 --- /dev/null +++ b/drivers/usb/wusbcore/devconnect.c @@ -0,0 +1,1120 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * Device Connect handling + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * FIXME: this file needs to be broken up, it's grown too big + * + * + * WUSB1.0[7.1, 7.5.1, ] + * + * WUSB device connection is kind of messy. Some background: + * + * When a device wants to connect it scans the UWB radio channels + * looking for a WUSB Channel; a WUSB channel is defined by MMCs + * (Micro Managed Commands or something like that) [see + * Design-overview for more on this] . + * + * So, device scans the radio, finds MMCs and thus a host and checks + * when the next DNTS is. It sends a Device Notification Connect + * (DN_Connect); the host picks it up (through nep.c and notif.c, ends + * up in wusb_devconnect_ack(), which creates a wusb_dev structure in + * wusbhc->port[port_number].wusb_dev), assigns an unauth address + * to the device (this means from 0x80 to 0xfe) and sends, in the MMC + * a Connect Ack Information Element (ConnAck IE). + * + * So now the device now has a WUSB address. From now on, we use + * that to talk to it in the RPipes. + * + * ASSUMPTIONS: + * + * - We use the the as device address the port number where it is + * connected (port 0 doesn't exist). For unauth, it is 128 + that. + * + * ROADMAP: + * + * This file contains the logic for doing that--entry points: + * + * wusb_devconnect_ack() Ack a device until _acked() called. + * Called by notif.c:wusb_handle_dn_connect() + * when a DN_Connect is received. + * + * wusb_devconnect_acked() Ack done, release resources. + * + * wusb_handle_dn_alive() Called by notif.c:wusb_handle_dn() + * for processing a DN_Alive pong from a device. + * + * wusb_handle_dn_disconnect()Called by notif.c:wusb_handle_dn() to + * process a disconenct request from a + * device. + * + * __wusb_dev_disable() Called by rh.c:wusbhc_rh_clear_port_feat() when + * disabling a port. + * + * wusb_devconnect_create() Called when creating the host by + * lc.c:wusbhc_create(). + * + * wusb_devconnect_destroy() Cleanup called removing the host. Called + * by lc.c:wusbhc_destroy(). + * + * Each Wireless USB host maintains a list of DN_Connect requests + * (actually we maintain a list of pending Connect Acks, the + * wusbhc->ca_list). + * + * LIFE CYCLE OF port->wusb_dev + * + * Before the @wusbhc structure put()s the reference it owns for + * port->wusb_dev [and clean the wusb_dev pointer], it needs to + * lock @wusbhc->mutex. + */ + +#include +#include +#include +#include +#include +#include "wusbhc.h" + +static void wusbhc_devconnect_acked_work(struct work_struct *work); + +static void wusb_dev_free(struct wusb_dev *wusb_dev) +{ + if (wusb_dev) { + kfree(wusb_dev->set_gtk_req); + usb_free_urb(wusb_dev->set_gtk_urb); + kfree(wusb_dev); + } +} + +static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc) +{ + struct wusb_dev *wusb_dev; + struct urb *urb; + struct usb_ctrlrequest *req; + + wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL); + if (wusb_dev == NULL) + goto err; + + wusb_dev->wusbhc = wusbhc; + + INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) + goto err; + wusb_dev->set_gtk_urb = urb; + + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (req == NULL) + goto err; + wusb_dev->set_gtk_req = req; + + req->bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req->bRequest = USB_REQ_SET_DESCRIPTOR; + req->wValue = cpu_to_le16(USB_DT_KEY << 8 | wusbhc->gtk_index); + req->wIndex = 0; + req->wLength = cpu_to_le16(wusbhc->gtk.descr.bLength); + + return wusb_dev; +err: + wusb_dev_free(wusb_dev); + return NULL; +} + + +/* + * Using the Connect-Ack list, fill out the @wusbhc Connect-Ack WUSB IE + * properly so that it can be added to the MMC. + * + * We just get the @wusbhc->ca_list and fill out the first four ones or + * less (per-spec WUSB1.0[7.5, before T7-38). If the ConnectAck WUSB + * IE is not allocated, we alloc it. + * + * @wusbhc->mutex must be taken + */ +static void wusbhc_fill_cack_ie(struct wusbhc *wusbhc) +{ + unsigned cnt; + struct wusb_dev *dev_itr; + struct wuie_connect_ack *cack_ie; + + cack_ie = &wusbhc->cack_ie; + cnt = 0; + list_for_each_entry(dev_itr, &wusbhc->cack_list, cack_node) { + cack_ie->blk[cnt].CDID = dev_itr->cdid; + cack_ie->blk[cnt].bDeviceAddress = dev_itr->addr; + if (++cnt >= WUIE_ELT_MAX) + break; + } + cack_ie->hdr.bLength = sizeof(cack_ie->hdr) + + cnt * sizeof(cack_ie->blk[0]); +} + +/* + * Register a new device that wants to connect + * + * A new device wants to connect, so we add it to the Connect-Ack + * list. We give it an address in the unauthorized range (bit 8 set); + * user space will have to drive authorization further on. + * + * @dev_addr: address to use for the device (which is also the port + * number). + * + * @wusbhc->mutex must be taken + */ +static struct wusb_dev *wusbhc_cack_add(struct wusbhc *wusbhc, + struct wusb_dn_connect *dnc, + const char *pr_cdid, u8 port_idx) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + int new_connection = wusb_dn_connect_new_connection(dnc); + u8 dev_addr; + int result; + + /* Is it registered already? */ + list_for_each_entry(wusb_dev, &wusbhc->cack_list, cack_node) + if (!memcmp(&wusb_dev->cdid, &dnc->CDID, + sizeof(wusb_dev->cdid))) + return wusb_dev; + /* We don't have it, create an entry, register it */ + wusb_dev = wusb_dev_alloc(wusbhc); + if (wusb_dev == NULL) + return NULL; + wusb_dev_init(wusb_dev); + wusb_dev->cdid = dnc->CDID; + wusb_dev->port_idx = port_idx; + + /* + * Devices are always available within the cluster reservation + * and since the hardware will take the intersection of the + * per-device availability and the cluster reservation, the + * per-device availability can simply be set to always + * available. + */ + bitmap_fill(wusb_dev->availability.bm, UWB_NUM_MAS); + + /* FIXME: handle reconnects instead of assuming connects are + always new. */ + if (1 && new_connection == 0) + new_connection = 1; + if (new_connection) { + dev_addr = (port_idx + 2) | WUSB_DEV_ADDR_UNAUTH; + + dev_info(dev, "Connecting new WUSB device to address %u, " + "port %u\n", dev_addr, port_idx); + + result = wusb_set_dev_addr(wusbhc, wusb_dev, dev_addr); + if (result < 0) + return NULL; + } + wusb_dev->entry_ts = jiffies; + list_add_tail(&wusb_dev->cack_node, &wusbhc->cack_list); + wusbhc->cack_count++; + wusbhc_fill_cack_ie(wusbhc); + + return wusb_dev; +} + +/* + * Remove a Connect-Ack context entry from the HCs view + * + * @wusbhc->mutex must be taken + */ +static void wusbhc_cack_rm(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + list_del_init(&wusb_dev->cack_node); + wusbhc->cack_count--; + wusbhc_fill_cack_ie(wusbhc); +} + +/* + * @wusbhc->mutex must be taken */ +static +void wusbhc_devconnect_acked(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + wusbhc_cack_rm(wusbhc, wusb_dev); + if (wusbhc->cack_count) + wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr); + else + wusbhc_mmcie_rm(wusbhc, &wusbhc->cack_ie.hdr); +} + +static void wusbhc_devconnect_acked_work(struct work_struct *work) +{ + struct wusb_dev *wusb_dev = container_of(work, struct wusb_dev, + devconnect_acked_work); + struct wusbhc *wusbhc = wusb_dev->wusbhc; + + mutex_lock(&wusbhc->mutex); + wusbhc_devconnect_acked(wusbhc, wusb_dev); + mutex_unlock(&wusbhc->mutex); + + wusb_dev_put(wusb_dev); +} + +/* + * Ack a device for connection + * + * FIXME: docs + * + * @pr_cdid: Printable CDID...hex Use @dnc->cdid for the real deal. + * + * So we get the connect ack IE (may have been allocated already), + * find an empty connect block, an empty virtual port, create an + * address with it (see below), make it an unauth addr [bit 7 set] and + * set the MMC. + * + * Addresses: because WUSB hosts have no downstream hubs, we can do a + * 1:1 mapping between 'port number' and device + * address. This simplifies many things, as during this + * initial connect phase the USB stack has no knoledge of + * the device and hasn't assigned an address yet--we know + * USB's choose_address() will use the same euristics we + * use here, so we can assume which address will be assigned. + * + * USB stack always assigns address 1 to the root hub, so + * to the port number we add 2 (thus virtual port #0 is + * addr #2). + * + * @wusbhc shall be referenced + */ +static +void wusbhc_devconnect_ack(struct wusbhc *wusbhc, struct wusb_dn_connect *dnc, + const char *pr_cdid) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wusb_port *port; + unsigned idx, devnum; + + mutex_lock(&wusbhc->mutex); + + /* Check we are not handling it already */ + for (idx = 0; idx < wusbhc->ports_max; idx++) { + port = wusb_port_by_idx(wusbhc, idx); + if (port->wusb_dev + && memcmp(&dnc->CDID, &port->wusb_dev->cdid, sizeof(dnc->CDID)) == 0) + goto error_unlock; + } + /* Look up those fake ports we have for a free one */ + for (idx = 0; idx < wusbhc->ports_max; idx++) { + port = wusb_port_by_idx(wusbhc, idx); + if ((port->status & USB_PORT_STAT_POWER) + && !(port->status & USB_PORT_STAT_CONNECTION)) + break; + } + if (idx >= wusbhc->ports_max) { + dev_err(dev, "Host controller can't connect more devices " + "(%u already connected); device %s rejected\n", + wusbhc->ports_max, pr_cdid); + /* NOTE: we could send a WUIE_Disconnect here, but we haven't + * event acked, so the device will eventually timeout the + * connection, right? */ + goto error_unlock; + } + + devnum = idx + 2; + + /* Make sure we are using no crypto on that "virtual port" */ + wusbhc->set_ptk(wusbhc, idx, 0, NULL, 0); + + /* Grab a filled in Connect-Ack context, fill out the + * Connect-Ack Wireless USB IE, set the MMC */ + wusb_dev = wusbhc_cack_add(wusbhc, dnc, pr_cdid, idx); + if (wusb_dev == NULL) + goto error_unlock; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr); + if (result < 0) + goto error_unlock; + /* Give the device at least 2ms (WUSB1.0[7.5.1p3]), let's do + * three for a good measure */ + msleep(3); + port->wusb_dev = wusb_dev; + port->status |= USB_PORT_STAT_CONNECTION; + port->change |= USB_PORT_STAT_C_CONNECTION; + /* Now the port status changed to connected; khubd will + * pick the change up and try to reset the port to bring it to + * the enabled state--so this process returns up to the stack + * and it calls back into wusbhc_rh_port_reset(). + */ +error_unlock: + mutex_unlock(&wusbhc->mutex); + return; + +} + +/* + * Disconnect a Wireless USB device from its fake port + * + * Marks the port as disconnected so that khubd can pick up the change + * and drops our knowledge about the device. + * + * Assumes there is a device connected + * + * @port_index: zero based port number + * + * NOTE: @wusbhc->mutex is locked + * + * WARNING: From here it is not very safe to access anything hanging off + * wusb_dev + */ +static void __wusbhc_dev_disconnect(struct wusbhc *wusbhc, + struct wusb_port *port) +{ + struct wusb_dev *wusb_dev = port->wusb_dev; + + port->status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE + | USB_PORT_STAT_SUSPEND | USB_PORT_STAT_RESET + | USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED); + port->change |= USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE; + if (wusb_dev) { + dev_dbg(wusbhc->dev, "disconnecting device from port %d\n", wusb_dev->port_idx); + if (!list_empty(&wusb_dev->cack_node)) + list_del_init(&wusb_dev->cack_node); + /* For the one in cack_add() */ + wusb_dev_put(wusb_dev); + } + port->wusb_dev = NULL; + + /* After a device disconnects, change the GTK (see [WUSB] + * section 6.2.11.2). */ + if (wusbhc->active) + wusbhc_gtk_rekey(wusbhc); + + /* The Wireless USB part has forgotten about the device already; now + * khubd's timer will pick up the disconnection and remove the USB + * device from the system + */ +} + +/* + * Refresh the list of keep alives to emit in the MMC + * + * Some devices don't respond to keep alives unless they've been + * authenticated, so skip unauthenticated devices. + * + * We only publish the first four devices that have a coming timeout + * condition. Then when we are done processing those, we go for the + * next ones. We ignore the ones that have timed out already (they'll + * be purged). + * + * This might cause the first devices to timeout the last devices in + * the port array...FIXME: come up with a better algorithm? + * + * Note we can't do much about MMC's ops errors; we hope next refresh + * will kind of handle it. + * + * NOTE: @wusbhc->mutex is locked + */ +static void __wusbhc_keep_alive(struct wusbhc *wusbhc) +{ + struct device *dev = wusbhc->dev; + unsigned cnt; + struct wusb_dev *wusb_dev; + struct wusb_port *wusb_port; + struct wuie_keep_alive *ie = &wusbhc->keep_alive_ie; + unsigned keep_alives, old_keep_alives; + + old_keep_alives = ie->hdr.bLength - sizeof(ie->hdr); + keep_alives = 0; + for (cnt = 0; + keep_alives < WUIE_ELT_MAX && cnt < wusbhc->ports_max; + cnt++) { + unsigned tt = msecs_to_jiffies(wusbhc->trust_timeout); + + wusb_port = wusb_port_by_idx(wusbhc, cnt); + wusb_dev = wusb_port->wusb_dev; + + if (wusb_dev == NULL) + continue; + if (wusb_dev->usb_dev == NULL || !wusb_dev->usb_dev->authenticated) + continue; + + if (time_after(jiffies, wusb_dev->entry_ts + tt)) { + dev_err(dev, "KEEPALIVE: device %u timed out\n", + wusb_dev->addr); + __wusbhc_dev_disconnect(wusbhc, wusb_port); + } else if (time_after(jiffies, wusb_dev->entry_ts + tt/2)) { + /* Approaching timeout cut out, need to refresh */ + ie->bDeviceAddress[keep_alives++] = wusb_dev->addr; + } + } + if (keep_alives & 0x1) /* pad to even number ([WUSB] section 7.5.9) */ + ie->bDeviceAddress[keep_alives++] = 0x7f; + ie->hdr.bLength = sizeof(ie->hdr) + + keep_alives*sizeof(ie->bDeviceAddress[0]); + if (keep_alives > 0) + wusbhc_mmcie_set(wusbhc, 10, 5, &ie->hdr); + else if (old_keep_alives != 0) + wusbhc_mmcie_rm(wusbhc, &ie->hdr); +} + +/* + * Do a run through all devices checking for timeouts + */ +static void wusbhc_keep_alive_run(struct work_struct *ws) +{ + struct delayed_work *dw = to_delayed_work(ws); + struct wusbhc *wusbhc = container_of(dw, struct wusbhc, keep_alive_timer); + + mutex_lock(&wusbhc->mutex); + __wusbhc_keep_alive(wusbhc); + mutex_unlock(&wusbhc->mutex); + + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + msecs_to_jiffies(wusbhc->trust_timeout / 2)); +} + +/* + * Find the wusb_dev from its device address. + * + * The device can be found directly from the address (see + * wusb_cack_add() for where the device address is set to port_idx + * +2), except when the address is zero. + */ +static struct wusb_dev *wusbhc_find_dev_by_addr(struct wusbhc *wusbhc, u8 addr) +{ + int p; + + if (addr == 0xff) /* unconnected */ + return NULL; + + if (addr > 0) { + int port = (addr & ~0x80) - 2; + if (port < 0 || port >= wusbhc->ports_max) + return NULL; + return wusb_port_by_idx(wusbhc, port)->wusb_dev; + } + + /* Look for the device with address 0. */ + for (p = 0; p < wusbhc->ports_max; p++) { + struct wusb_dev *wusb_dev = wusb_port_by_idx(wusbhc, p)->wusb_dev; + if (wusb_dev && wusb_dev->addr == addr) + return wusb_dev; + } + return NULL; +} + +/* + * Handle a DN_Alive notification (WUSB1.0[7.6.1]) + * + * This just updates the device activity timestamp and then refreshes + * the keep alive IE. + * + * @wusbhc shall be referenced and unlocked + */ +static void wusbhc_handle_dn_alive(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + mutex_lock(&wusbhc->mutex); + wusb_dev->entry_ts = jiffies; + __wusbhc_keep_alive(wusbhc); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Handle a DN_Connect notification (WUSB1.0[7.6.1]) + * + * @wusbhc + * @pkt_hdr + * @size: Size of the buffer where the notification resides; if the + * notification data suggests there should be more data than + * available, an error will be signaled and the whole buffer + * consumed. + * + * @wusbhc->mutex shall be held + */ +static void wusbhc_handle_dn_connect(struct wusbhc *wusbhc, + struct wusb_dn_hdr *dn_hdr, + size_t size) +{ + struct device *dev = wusbhc->dev; + struct wusb_dn_connect *dnc; + char pr_cdid[WUSB_CKHDID_STRSIZE]; + static const char *beacon_behaviour[] = { + "reserved", + "self-beacon", + "directed-beacon", + "no-beacon" + }; + + if (size < sizeof(*dnc)) { + dev_err(dev, "DN CONNECT: short notification (%zu < %zu)\n", + size, sizeof(*dnc)); + return; + } + + dnc = container_of(dn_hdr, struct wusb_dn_connect, hdr); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &dnc->CDID); + dev_info(dev, "DN CONNECT: device %s @ %x (%s) wants to %s\n", + pr_cdid, + wusb_dn_connect_prev_dev_addr(dnc), + beacon_behaviour[wusb_dn_connect_beacon_behavior(dnc)], + wusb_dn_connect_new_connection(dnc) ? "connect" : "reconnect"); + /* ACK the connect */ + wusbhc_devconnect_ack(wusbhc, dnc, pr_cdid); +} + +/* + * Handle a DN_Disconnect notification (WUSB1.0[7.6.1]) + * + * Device is going down -- do the disconnect. + * + * @wusbhc shall be referenced and unlocked + */ +static void wusbhc_handle_dn_disconnect(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + + dev_info(dev, "DN DISCONNECT: device 0x%02x going down\n", wusb_dev->addr); + + mutex_lock(&wusbhc->mutex); + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, wusb_dev->port_idx)); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Handle a Device Notification coming a host + * + * The Device Notification comes from a host (HWA, DWA or WHCI) + * wrapped in a set of headers. Somebody else has peeled off those + * headers for us and we just get one Device Notifications. + * + * Invalid DNs (e.g., too short) are discarded. + * + * @wusbhc shall be referenced + * + * FIXMES: + * - implement priorities as in WUSB1.0[Table 7-55]? + */ +void wusbhc_handle_dn(struct wusbhc *wusbhc, u8 srcaddr, + struct wusb_dn_hdr *dn_hdr, size_t size) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + + if (size < sizeof(struct wusb_dn_hdr)) { + dev_err(dev, "DN data shorter than DN header (%d < %d)\n", + (int)size, (int)sizeof(struct wusb_dn_hdr)); + return; + } + + wusb_dev = wusbhc_find_dev_by_addr(wusbhc, srcaddr); + if (wusb_dev == NULL && dn_hdr->bType != WUSB_DN_CONNECT) { + dev_dbg(dev, "ignoring DN %d from unconnected device %02x\n", + dn_hdr->bType, srcaddr); + return; + } + + switch (dn_hdr->bType) { + case WUSB_DN_CONNECT: + wusbhc_handle_dn_connect(wusbhc, dn_hdr, size); + break; + case WUSB_DN_ALIVE: + wusbhc_handle_dn_alive(wusbhc, wusb_dev); + break; + case WUSB_DN_DISCONNECT: + wusbhc_handle_dn_disconnect(wusbhc, wusb_dev); + break; + case WUSB_DN_MASAVAILCHANGED: + case WUSB_DN_RWAKE: + case WUSB_DN_SLEEP: + /* FIXME: handle these DNs. */ + break; + case WUSB_DN_EPRDY: + /* The hardware handles these. */ + break; + default: + dev_warn(dev, "unknown DN %u (%d octets) from %u\n", + dn_hdr->bType, (int)size, srcaddr); + } +} +EXPORT_SYMBOL_GPL(wusbhc_handle_dn); + +/* + * Disconnect a WUSB device from a the cluster + * + * @wusbhc + * @port Fake port where the device is (wusbhc index, not USB port number). + * + * In Wireless USB, a disconnect is basically telling the device he is + * being disconnected and forgetting about him. + * + * We send the device a Device Disconnect IE (WUSB1.0[7.5.11]) for 100 + * ms and then keep going. + * + * We don't do much in case of error; we always pretend we disabled + * the port and disconnected the device. If physically the request + * didn't get there (many things can fail in the way there), the stack + * will reject the device's communication attempts. + * + * @wusbhc should be refcounted and locked + */ +void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port_idx) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wuie_disconnect *ie; + + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev == NULL) { + /* reset no device? ignore */ + dev_dbg(dev, "DISCONNECT: no device at port %u, ignoring\n", + port_idx); + return; + } + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + + ie = kzalloc(sizeof(*ie), GFP_KERNEL); + if (ie == NULL) + return; + ie->hdr.bLength = sizeof(*ie); + ie->hdr.bIEIdentifier = WUIE_ID_DEVICE_DISCONNECT; + ie->bDeviceAddress = wusb_dev->addr; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &ie->hdr); + if (result < 0) + dev_err(dev, "DISCONNECT: can't set MMC: %d\n", result); + else { + /* At least 6 MMCs, assuming at least 1 MMC per zone. */ + msleep(7*4); + wusbhc_mmcie_rm(wusbhc, &ie->hdr); + } + kfree(ie); +} + +/* + * Walk over the BOS descriptor, verify and grok it + * + * @usb_dev: referenced + * @wusb_dev: referenced and unlocked + * + * The BOS descriptor is defined at WUSB1.0[7.4.1], and it defines a + * "flexible" way to wrap all kinds of descriptors inside an standard + * descriptor (wonder why they didn't use normal descriptors, + * btw). Not like they lack code. + * + * At the end we go to look for the WUSB Device Capabilities + * (WUSB1.0[7.4.1.1]) that is wrapped in a device capability descriptor + * that is part of the BOS descriptor set. That tells us what does the + * device support (dual role, beacon type, UWB PHY rates). + */ +static int wusb_dev_bos_grok(struct usb_device *usb_dev, + struct wusb_dev *wusb_dev, + struct usb_bos_descriptor *bos, size_t desc_size) +{ + ssize_t result; + struct device *dev = &usb_dev->dev; + void *itr, *top; + + /* Walk over BOS capabilities, verify them */ + itr = (void *)bos + sizeof(*bos); + top = itr + desc_size - sizeof(*bos); + while (itr < top) { + struct usb_dev_cap_header *cap_hdr = itr; + size_t cap_size; + u8 cap_type; + if (top - itr < sizeof(*cap_hdr)) { + dev_err(dev, "Device BUG? premature end of BOS header " + "data [offset 0x%02x]: only %zu bytes left\n", + (int)(itr - (void *)bos), top - itr); + result = -ENOSPC; + goto error_bad_cap; + } + cap_size = cap_hdr->bLength; + cap_type = cap_hdr->bDevCapabilityType; + if (cap_size == 0) + break; + if (cap_size > top - itr) { + dev_err(dev, "Device BUG? premature end of BOS data " + "[offset 0x%02x cap %02x %zu bytes]: " + "only %zu bytes left\n", + (int)(itr - (void *)bos), + cap_type, cap_size, top - itr); + result = -EBADF; + goto error_bad_cap; + } + switch (cap_type) { + case USB_CAP_TYPE_WIRELESS_USB: + if (cap_size != sizeof(*wusb_dev->wusb_cap_descr)) + dev_err(dev, "Device BUG? WUSB Capability " + "descriptor is %zu bytes vs %zu " + "needed\n", cap_size, + sizeof(*wusb_dev->wusb_cap_descr)); + else + wusb_dev->wusb_cap_descr = itr; + break; + default: + dev_err(dev, "BUG? Unknown BOS capability 0x%02x " + "(%zu bytes) at offset 0x%02x\n", cap_type, + cap_size, (int)(itr - (void *)bos)); + } + itr += cap_size; + } + result = 0; +error_bad_cap: + return result; +} + +/* + * Add information from the BOS descriptors to the device + * + * @usb_dev: referenced + * @wusb_dev: referenced and unlocked + * + * So what we do is we alloc a space for the BOS descriptor of 64 + * bytes; read the first four bytes which include the wTotalLength + * field (WUSB1.0[T7-26]) and if it fits in those 64 bytes, read the + * whole thing. If not we realloc to that size. + * + * Then we call the groking function, that will fill up + * wusb_dev->wusb_cap_descr, which is what we'll need later on. + */ +static int wusb_dev_bos_add(struct usb_device *usb_dev, + struct wusb_dev *wusb_dev) +{ + ssize_t result; + struct device *dev = &usb_dev->dev; + struct usb_bos_descriptor *bos; + size_t alloc_size = 32, desc_size = 4; + + bos = kmalloc(alloc_size, GFP_KERNEL); + if (bos == NULL) + return -ENOMEM; + result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size); + if (result < 4) { + dev_err(dev, "Can't get BOS descriptor or too short: %zd\n", + result); + goto error_get_descriptor; + } + desc_size = le16_to_cpu(bos->wTotalLength); + if (desc_size >= alloc_size) { + kfree(bos); + alloc_size = desc_size; + bos = kmalloc(alloc_size, GFP_KERNEL); + if (bos == NULL) + return -ENOMEM; + } + result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size); + if (result < 0 || result != desc_size) { + dev_err(dev, "Can't get BOS descriptor or too short (need " + "%zu bytes): %zd\n", desc_size, result); + goto error_get_descriptor; + } + if (result < sizeof(*bos) + || le16_to_cpu(bos->wTotalLength) != desc_size) { + dev_err(dev, "Can't get BOS descriptor or too short (need " + "%zu bytes): %zd\n", desc_size, result); + goto error_get_descriptor; + } + + result = wusb_dev_bos_grok(usb_dev, wusb_dev, bos, result); + if (result < 0) + goto error_bad_bos; + wusb_dev->bos = bos; + return 0; + +error_bad_bos: +error_get_descriptor: + kfree(bos); + wusb_dev->wusb_cap_descr = NULL; + return result; +} + +static void wusb_dev_bos_rm(struct wusb_dev *wusb_dev) +{ + kfree(wusb_dev->bos); + wusb_dev->wusb_cap_descr = NULL; +}; + +/* + * USB stack's device addition Notifier Callback + * + * Called from drivers/usb/core/hub.c when a new device is added; we + * use this hook to perform certain WUSB specific setup work on the + * new device. As well, it is the first time we can connect the + * wusb_dev and the usb_dev. So we note it down in wusb_dev and take a + * reference that we'll drop. + * + * First we need to determine if the device is a WUSB device (else we + * ignore it). For that we use the speed setting (USB_SPEED_WIRELESS) + * [FIXME: maybe we'd need something more definitive]. If so, we track + * it's usb_busd and from there, the WUSB HC. + * + * Because all WUSB HCs are contained in a 'struct wusbhc', voila, we + * get the wusbhc for the device. + * + * We have a reference on @usb_dev (as we are called at the end of its + * enumeration). + * + * NOTE: @usb_dev locked + */ +static void wusb_dev_add_ncb(struct usb_device *usb_dev) +{ + int result = 0; + struct wusb_dev *wusb_dev; + struct wusbhc *wusbhc; + struct device *dev = &usb_dev->dev; + u8 port_idx; + + if (usb_dev->wusb == 0 || usb_dev->devnum == 1) + return; /* skip non wusb and wusb RHs */ + + usb_set_device_state(usb_dev, USB_STATE_UNAUTHENTICATED); + + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + goto error_nodev; + mutex_lock(&wusbhc->mutex); + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev); + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + mutex_unlock(&wusbhc->mutex); + if (wusb_dev == NULL) + goto error_nodev; + wusb_dev->usb_dev = usb_get_dev(usb_dev); + usb_dev->wusb_dev = wusb_dev_get(wusb_dev); + result = wusb_dev_sec_add(wusbhc, usb_dev, wusb_dev); + if (result < 0) { + dev_err(dev, "Cannot enable security: %d\n", result); + goto error_sec_add; + } + /* Now query the device for it's BOS and attach it to wusb_dev */ + result = wusb_dev_bos_add(usb_dev, wusb_dev); + if (result < 0) { + dev_err(dev, "Cannot get BOS descriptors: %d\n", result); + goto error_bos_add; + } + result = wusb_dev_sysfs_add(wusbhc, usb_dev, wusb_dev); + if (result < 0) + goto error_add_sysfs; +out: + wusb_dev_put(wusb_dev); + wusbhc_put(wusbhc); +error_nodev: + return; + + wusb_dev_sysfs_rm(wusb_dev); +error_add_sysfs: + wusb_dev_bos_rm(wusb_dev); +error_bos_add: + wusb_dev_sec_rm(wusb_dev); +error_sec_add: + mutex_lock(&wusbhc->mutex); + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + mutex_unlock(&wusbhc->mutex); + goto out; +} + +/* + * Undo all the steps done at connection by the notifier callback + * + * NOTE: @usb_dev locked + */ +static void wusb_dev_rm_ncb(struct usb_device *usb_dev) +{ + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + + if (usb_dev->wusb == 0 || usb_dev->devnum == 1) + return; /* skip non wusb and wusb RHs */ + + wusb_dev_sysfs_rm(wusb_dev); + wusb_dev_bos_rm(wusb_dev); + wusb_dev_sec_rm(wusb_dev); + wusb_dev->usb_dev = NULL; + usb_dev->wusb_dev = NULL; + wusb_dev_put(wusb_dev); + usb_put_dev(usb_dev); +} + +/* + * Handle notifications from the USB stack (notifier call back) + * + * This is called when the USB stack does a + * usb_{bus,device}_{add,remove}() so we can do WUSB specific + * handling. It is called with [for the case of + * USB_DEVICE_{ADD,REMOVE} with the usb_dev locked. + */ +int wusb_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv) +{ + int result = NOTIFY_OK; + + switch (val) { + case USB_DEVICE_ADD: + wusb_dev_add_ncb(priv); + break; + case USB_DEVICE_REMOVE: + wusb_dev_rm_ncb(priv); + break; + case USB_BUS_ADD: + /* ignore (for now) */ + case USB_BUS_REMOVE: + break; + default: + WARN_ON(1); + result = NOTIFY_BAD; + }; + return result; +} + +/* + * Return a referenced wusb_dev given a @wusbhc and @usb_dev + */ +struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *wusbhc, + struct usb_device *usb_dev) +{ + struct wusb_dev *wusb_dev; + u8 port_idx; + + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + BUG_ON(port_idx > wusbhc->ports_max); + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev != NULL) /* ops, device is gone */ + wusb_dev_get(wusb_dev); + return wusb_dev; +} +EXPORT_SYMBOL_GPL(__wusb_dev_get_by_usb_dev); + +void wusb_dev_destroy(struct kref *_wusb_dev) +{ + struct wusb_dev *wusb_dev = container_of(_wusb_dev, struct wusb_dev, refcnt); + + list_del_init(&wusb_dev->cack_node); + wusb_dev_free(wusb_dev); +} +EXPORT_SYMBOL_GPL(wusb_dev_destroy); + +/* + * Create all the device connect handling infrastructure + * + * This is basically the device info array, Connect Acknowledgement + * (cack) lists, keep-alive timers (and delayed work thread). + */ +int wusbhc_devconnect_create(struct wusbhc *wusbhc) +{ + wusbhc->keep_alive_ie.hdr.bIEIdentifier = WUIE_ID_KEEP_ALIVE; + wusbhc->keep_alive_ie.hdr.bLength = sizeof(wusbhc->keep_alive_ie.hdr); + INIT_DELAYED_WORK(&wusbhc->keep_alive_timer, wusbhc_keep_alive_run); + + wusbhc->cack_ie.hdr.bIEIdentifier = WUIE_ID_CONNECTACK; + wusbhc->cack_ie.hdr.bLength = sizeof(wusbhc->cack_ie.hdr); + INIT_LIST_HEAD(&wusbhc->cack_list); + + return 0; +} + +/* + * Release all resources taken by the devconnect stuff + */ +void wusbhc_devconnect_destroy(struct wusbhc *wusbhc) +{ + /* no op */ +} + +/* + * wusbhc_devconnect_start - start accepting device connections + * @wusbhc: the WUSB HC + * + * Sets the Host Info IE to accept all new connections. + * + * FIXME: This also enables the keep alives but this is not necessary + * until there are connected and authenticated devices. + */ +int wusbhc_devconnect_start(struct wusbhc *wusbhc) +{ + struct device *dev = wusbhc->dev; + struct wuie_host_info *hi; + int result; + + hi = kzalloc(sizeof(*hi), GFP_KERNEL); + if (hi == NULL) + return -ENOMEM; + + hi->hdr.bLength = sizeof(*hi); + hi->hdr.bIEIdentifier = WUIE_ID_HOST_INFO; + hi->attributes = cpu_to_le16((wusbhc->rsv->stream << 3) | WUIE_HI_CAP_ALL); + hi->CHID = wusbhc->chid; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &hi->hdr); + if (result < 0) { + dev_err(dev, "Cannot add Host Info MMCIE: %d\n", result); + goto error_mmcie_set; + } + wusbhc->wuie_host_info = hi; + + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (wusbhc->trust_timeout*CONFIG_HZ)/1000/2); + + return 0; + +error_mmcie_set: + kfree(hi); + return result; +} + +/* + * wusbhc_devconnect_stop - stop managing connected devices + * @wusbhc: the WUSB HC + * + * Disconnects any devices still connected, stops the keep alives and + * removes the Host Info IE. + */ +void wusbhc_devconnect_stop(struct wusbhc *wusbhc) +{ + int i; + + mutex_lock(&wusbhc->mutex); + for (i = 0; i < wusbhc->ports_max; i++) { + if (wusbhc->port[i].wusb_dev) + __wusbhc_dev_disconnect(wusbhc, &wusbhc->port[i]); + } + mutex_unlock(&wusbhc->mutex); + + cancel_delayed_work_sync(&wusbhc->keep_alive_timer); + wusbhc_mmcie_rm(wusbhc, &wusbhc->wuie_host_info->hdr); + kfree(wusbhc->wuie_host_info); + wusbhc->wuie_host_info = NULL; +} + +/* + * wusb_set_dev_addr - set the WUSB device address used by the host + * @wusbhc: the WUSB HC the device is connect to + * @wusb_dev: the WUSB device + * @addr: new device address + */ +int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, u8 addr) +{ + int result; + + wusb_dev->addr = addr; + result = wusbhc->dev_info_set(wusbhc, wusb_dev); + if (result < 0) + dev_err(wusbhc->dev, "device %d: failed to set device " + "address\n", wusb_dev->port_idx); + else + dev_info(wusbhc->dev, "device %d: %s addr %u\n", + wusb_dev->port_idx, + (addr & WUSB_DEV_ADDR_UNAUTH) ? "unauth" : "auth", + wusb_dev->addr); + + return result; +} diff --git a/drivers/usb/wusbcore/mmc.c b/drivers/usb/wusbcore/mmc.c new file mode 100644 index 00000000..b8c72583 --- /dev/null +++ b/drivers/usb/wusbcore/mmc.c @@ -0,0 +1,287 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * MMC (Microscheduled Management Command) handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * WUIEs and MMC IEs...well, they are almost the same at the end. MMC + * IEs are Wireless USB IEs that go into the MMC period...[what is + * that? look in Design-overview.txt]. + * + * + * This is a simple subsystem to keep track of which IEs are being + * sent by the host in the MMC period. + * + * For each WUIE we ask to send, we keep it in an array, so we can + * request its removal later, or replace the content. They are tracked + * by pointer, so be sure to use the same pointer if you want to + * remove it or update the contents. + * + * FIXME: + * - add timers that autoremove intervalled IEs? + */ +#include +#include +#include +#include "wusbhc.h" + +/* Initialize the MMCIEs handling mechanism */ +int wusbhc_mmcie_create(struct wusbhc *wusbhc) +{ + u8 mmcies = wusbhc->mmcies_max; + wusbhc->mmcie = kcalloc(mmcies, sizeof(wusbhc->mmcie[0]), GFP_KERNEL); + if (wusbhc->mmcie == NULL) + return -ENOMEM; + mutex_init(&wusbhc->mmcie_mutex); + return 0; +} + +/* Release resources used by the MMCIEs handling mechanism */ +void wusbhc_mmcie_destroy(struct wusbhc *wusbhc) +{ + kfree(wusbhc->mmcie); +} + +/* + * Add or replace an MMC Wireless USB IE. + * + * @interval: See WUSB1.0[8.5.3.1] + * @repeat_cnt: See WUSB1.0[8.5.3.1] + * @handle: See WUSB1.0[8.5.3.1] + * @wuie: Pointer to the header of the WUSB IE data to add. + * MUST BE allocated in a kmalloc buffer (no stack or + * vmalloc). + * THE CALLER ALWAYS OWNS THE POINTER (we don't free it + * on remove, we just forget about it). + * @returns: 0 if ok, < 0 errno code on error. + * + * Goes over the *whole* @wusbhc->mmcie array looking for (a) the + * first free spot and (b) if @wuie is already in the array (aka: + * transmitted in the MMCs) the spot were it is. + * + * If present, we "overwrite it" (update). + * + * + * NOTE: Need special ordering rules -- see below WUSB1.0 Table 7-38. + * The host uses the handle as the 'sort' index. We + * allocate the last one always for the WUIE_ID_HOST_INFO, and + * the rest, first come first serve in inverse order. + * + * Host software must make sure that it adds the other IEs in + * the right order... the host hardware is responsible for + * placing the WCTA IEs in the right place with the other IEs + * set by host software. + * + * NOTE: we can access wusbhc->wa_descr without locking because it is + * read only. + */ +int wusbhc_mmcie_set(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + struct wuie_hdr *wuie) +{ + int result = -ENOBUFS; + unsigned handle, itr; + + /* Search a handle, taking into account the ordering */ + mutex_lock(&wusbhc->mmcie_mutex); + switch (wuie->bIEIdentifier) { + case WUIE_ID_HOST_INFO: + /* Always last */ + handle = wusbhc->mmcies_max - 1; + break; + case WUIE_ID_ISOCH_DISCARD: + dev_err(wusbhc->dev, "Special ordering case for WUIE ID 0x%x " + "unimplemented\n", wuie->bIEIdentifier); + result = -ENOSYS; + goto error_unlock; + default: + /* search for it or find the last empty slot */ + handle = ~0; + for (itr = 0; itr < wusbhc->mmcies_max - 1; itr++) { + if (wusbhc->mmcie[itr] == wuie) { + handle = itr; + break; + } + if (wusbhc->mmcie[itr] == NULL) + handle = itr; + } + if (handle == ~0) + goto error_unlock; + } + result = (wusbhc->mmcie_add)(wusbhc, interval, repeat_cnt, handle, + wuie); + if (result >= 0) + wusbhc->mmcie[handle] = wuie; +error_unlock: + mutex_unlock(&wusbhc->mmcie_mutex); + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_mmcie_set); + +/* + * Remove an MMC IE previously added with wusbhc_mmcie_set() + * + * @wuie Pointer used to add the WUIE + */ +void wusbhc_mmcie_rm(struct wusbhc *wusbhc, struct wuie_hdr *wuie) +{ + int result; + unsigned handle, itr; + + mutex_lock(&wusbhc->mmcie_mutex); + for (itr = 0; itr < wusbhc->mmcies_max; itr++) { + if (wusbhc->mmcie[itr] == wuie) { + handle = itr; + goto found; + } + } + mutex_unlock(&wusbhc->mmcie_mutex); + return; + +found: + result = (wusbhc->mmcie_rm)(wusbhc, handle); + if (result == 0) + wusbhc->mmcie[itr] = NULL; + mutex_unlock(&wusbhc->mmcie_mutex); +} +EXPORT_SYMBOL_GPL(wusbhc_mmcie_rm); + +static int wusbhc_mmc_start(struct wusbhc *wusbhc) +{ + int ret; + + mutex_lock(&wusbhc->mutex); + ret = wusbhc->start(wusbhc); + if (ret >= 0) + wusbhc->active = 1; + mutex_unlock(&wusbhc->mutex); + + return ret; +} + +static void wusbhc_mmc_stop(struct wusbhc *wusbhc) +{ + mutex_lock(&wusbhc->mutex); + wusbhc->active = 0; + wusbhc->stop(wusbhc, WUSB_CHANNEL_STOP_DELAY_MS); + mutex_unlock(&wusbhc->mutex); +} + +/* + * wusbhc_start - start transmitting MMCs and accepting connections + * @wusbhc: the HC to start + * + * Establishes a cluster reservation, enables device connections, and + * starts MMCs with appropriate DNTS parameters. + */ +int wusbhc_start(struct wusbhc *wusbhc) +{ + int result; + struct device *dev = wusbhc->dev; + + WARN_ON(wusbhc->wuie_host_info != NULL); + + result = wusbhc_rsv_establish(wusbhc); + if (result < 0) { + dev_err(dev, "cannot establish cluster reservation: %d\n", + result); + goto error_rsv_establish; + } + + result = wusbhc_devconnect_start(wusbhc); + if (result < 0) { + dev_err(dev, "error enabling device connections: %d\n", result); + goto error_devconnect_start; + } + + result = wusbhc_sec_start(wusbhc); + if (result < 0) { + dev_err(dev, "error starting security in the HC: %d\n", result); + goto error_sec_start; + } + /* FIXME: the choice of the DNTS parameters is somewhat + * arbitrary */ + result = wusbhc->set_num_dnts(wusbhc, 0, 15); + if (result < 0) { + dev_err(dev, "Cannot set DNTS parameters: %d\n", result); + goto error_set_num_dnts; + } + result = wusbhc_mmc_start(wusbhc); + if (result < 0) { + dev_err(dev, "error starting wusbch: %d\n", result); + goto error_wusbhc_start; + } + + return 0; + +error_wusbhc_start: + wusbhc_sec_stop(wusbhc); +error_set_num_dnts: +error_sec_start: + wusbhc_devconnect_stop(wusbhc); +error_devconnect_start: + wusbhc_rsv_terminate(wusbhc); +error_rsv_establish: + return result; +} + +/* + * wusbhc_stop - stop transmitting MMCs + * @wusbhc: the HC to stop + * + * Stops the WUSB channel and removes the cluster reservation. + */ +void wusbhc_stop(struct wusbhc *wusbhc) +{ + wusbhc_mmc_stop(wusbhc); + wusbhc_sec_stop(wusbhc); + wusbhc_devconnect_stop(wusbhc); + wusbhc_rsv_terminate(wusbhc); +} + +/* + * Set/reset/update a new CHID + * + * Depending on the previous state of the MMCs, start, stop or change + * the sent MMC. This effectively switches the host controller on and + * off (radio wise). + */ +int wusbhc_chid_set(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid) +{ + int result = 0; + + if (memcmp(chid, &wusb_ckhdid_zero, sizeof(*chid)) == 0) + chid = NULL; + + mutex_lock(&wusbhc->mutex); + if (chid) { + if (wusbhc->active) { + mutex_unlock(&wusbhc->mutex); + return -EBUSY; + } + wusbhc->chid = *chid; + } + mutex_unlock(&wusbhc->mutex); + + if (chid) + result = uwb_radio_start(&wusbhc->pal); + else + uwb_radio_stop(&wusbhc->pal); + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_chid_set); diff --git a/drivers/usb/wusbcore/pal.c b/drivers/usb/wusbcore/pal.c new file mode 100644 index 00000000..d0b172c5 --- /dev/null +++ b/drivers/usb/wusbcore/pal.c @@ -0,0 +1,54 @@ +/* + * Wireless USB Host Controller + * UWB Protocol Adaptation Layer (PAL) glue. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "wusbhc.h" + +static void wusbhc_channel_changed(struct uwb_pal *pal, int channel) +{ + struct wusbhc *wusbhc = container_of(pal, struct wusbhc, pal); + + if (channel < 0) + wusbhc_stop(wusbhc); + else + wusbhc_start(wusbhc); +} + +/** + * wusbhc_pal_register - register the WUSB HC as a UWB PAL + * @wusbhc: the WUSB HC + */ +int wusbhc_pal_register(struct wusbhc *wusbhc) +{ + uwb_pal_init(&wusbhc->pal); + + wusbhc->pal.name = "wusbhc"; + wusbhc->pal.device = wusbhc->usb_hcd.self.controller; + wusbhc->pal.rc = wusbhc->uwb_rc; + wusbhc->pal.channel_changed = wusbhc_channel_changed; + + return uwb_pal_register(&wusbhc->pal); +} + +/** + * wusbhc_pal_register - unregister the WUSB HC as a UWB PAL + * @wusbhc: the WUSB HC + */ +void wusbhc_pal_unregister(struct wusbhc *wusbhc) +{ + uwb_pal_unregister(&wusbhc->pal); +} diff --git a/drivers/usb/wusbcore/reservation.c b/drivers/usb/wusbcore/reservation.c new file mode 100644 index 00000000..6f4fafdc --- /dev/null +++ b/drivers/usb/wusbcore/reservation.c @@ -0,0 +1,118 @@ +/* + * WUSB cluster reservation management + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include + +#include "wusbhc.h" + +/* + * WUSB cluster reservations are multicast reservations with the + * broadcast cluster ID (BCID) as the target DevAddr. + * + * FIXME: consider adjusting the reservation depending on what devices + * are attached. + */ + +static int wusbhc_bwa_set(struct wusbhc *wusbhc, u8 stream, + const struct uwb_mas_bm *mas) +{ + if (mas == NULL) + mas = &uwb_mas_bm_zero; + return wusbhc->bwa_set(wusbhc, stream, mas); +} + +/** + * wusbhc_rsv_complete_cb - WUSB HC reservation complete callback + * @rsv: the reservation + * + * Either set or clear the HC's view of the reservation. + * + * FIXME: when a reservation is denied the HC should be stopped. + */ +static void wusbhc_rsv_complete_cb(struct uwb_rsv *rsv) +{ + struct wusbhc *wusbhc = rsv->pal_priv; + struct device *dev = wusbhc->dev; + struct uwb_mas_bm mas; + char buf[72]; + + switch (rsv->state) { + case UWB_RSV_STATE_O_ESTABLISHED: + uwb_rsv_get_usable_mas(rsv, &mas); + bitmap_scnprintf(buf, sizeof(buf), mas.bm, UWB_NUM_MAS); + dev_dbg(dev, "established reservation: %s\n", buf); + wusbhc_bwa_set(wusbhc, rsv->stream, &mas); + break; + case UWB_RSV_STATE_NONE: + dev_dbg(dev, "removed reservation\n"); + wusbhc_bwa_set(wusbhc, 0, NULL); + break; + default: + dev_dbg(dev, "unexpected reservation state: %d\n", rsv->state); + break; + } +} + + +/** + * wusbhc_rsv_establish - establish a reservation for the cluster + * @wusbhc: the WUSB HC requesting a bandwidth reservation + */ +int wusbhc_rsv_establish(struct wusbhc *wusbhc) +{ + struct uwb_rc *rc = wusbhc->uwb_rc; + struct uwb_rsv *rsv; + struct uwb_dev_addr bcid; + int ret; + + rsv = uwb_rsv_create(rc, wusbhc_rsv_complete_cb, wusbhc); + if (rsv == NULL) + return -ENOMEM; + + bcid.data[0] = wusbhc->cluster_id; + bcid.data[1] = 0; + + rsv->target.type = UWB_RSV_TARGET_DEVADDR; + rsv->target.devaddr = bcid; + rsv->type = UWB_DRP_TYPE_PRIVATE; + rsv->max_mas = 256; /* try to get as much as possible */ + rsv->min_mas = 15; /* one MAS per zone */ + rsv->max_interval = 1; /* max latency is one zone */ + rsv->is_multicast = true; + + ret = uwb_rsv_establish(rsv); + if (ret == 0) + wusbhc->rsv = rsv; + else + uwb_rsv_destroy(rsv); + return ret; +} + + +/** + * wusbhc_rsv_terminate - terminate the cluster reservation + * @wusbhc: the WUSB host whose reservation is to be terminated + */ +void wusbhc_rsv_terminate(struct wusbhc *wusbhc) +{ + if (wusbhc->rsv) { + uwb_rsv_terminate(wusbhc->rsv); + uwb_rsv_destroy(wusbhc->rsv); + wusbhc->rsv = NULL; + } +} diff --git a/drivers/usb/wusbcore/rh.c b/drivers/usb/wusbcore/rh.c new file mode 100644 index 00000000..59ff254d --- /dev/null +++ b/drivers/usb/wusbcore/rh.c @@ -0,0 +1,452 @@ +/* + * Wireless USB Host Controller + * Root Hub operations + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We fake a root hub that has fake ports (as many as simultaneous + * devices the Wireless USB Host Controller can deal with). For each + * port we keep an state in @wusbhc->port[index] identical to the one + * specified in the USB2.0[ch11] spec and some extra device + * information that complements the one in 'struct usb_device' (as + * this lacs a hcpriv pointer). + * + * Note this is common to WHCI and HWA host controllers. + * + * Through here we enable most of the state changes that the USB stack + * will use to connect or disconnect devices. We need to do some + * forced adaptation of Wireless USB device states vs. wired: + * + * USB: WUSB: + * + * Port Powered-off port slot n/a + * Powered-on port slot available + * Disconnected port slot available + * Connected port slot assigned device + * device sent DN_Connect + * device was authenticated + * Enabled device is authenticated, transitioned + * from unauth -> auth -> default address + * -> enabled + * Reset disconnect + * Disable disconnect + * + * This maps the standard USB port states with the WUSB device states + * so we can fake ports without having to modify the USB stack. + * + * FIXME: this process will change in the future + * + * + * ENTRY POINTS + * + * Our entry points into here are, as in hcd.c, the USB stack root hub + * ops defined in the usb_hcd struct: + * + * wusbhc_rh_status_data() Provide hub and port status data bitmap + * + * wusbhc_rh_control() Execution of all the major requests + * you can do to a hub (Set|Clear + * features, get descriptors, status, etc). + * + * wusbhc_rh_[suspend|resume]() That + * + * wusbhc_rh_start_port_reset() ??? unimplemented + */ +#include +#include +#include "wusbhc.h" + +/* + * Reset a fake port + * + * Using a Reset Device IE is too heavyweight as it causes the device + * to enter the UnConnected state and leave the cluster, this can mean + * that when the device reconnects it is connected to a different fake + * port. + * + * Instead, reset authenticated devices with a SetAddress(0), followed + * by a SetAddresss(AuthAddr). + * + * For unauthenticated devices just pretend to reset but do nothing. + * If the device initialization continues to fail it will eventually + * time out after TrustTimeout and enter the UnConnected state. + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Supposedly we are the only thread accesing @wusbhc->port; in any + * case, maybe we should move the mutex locking from + * wusbhc_devconnect_auth() to here. + * + * @port_idx refers to the wusbhc's port index, not the USB port number + */ +static int wusbhc_rh_port_reset(struct wusbhc *wusbhc, u8 port_idx) +{ + int result = 0; + struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx); + struct wusb_dev *wusb_dev = port->wusb_dev; + + if (wusb_dev == NULL) + return -ENOTCONN; + + port->status |= USB_PORT_STAT_RESET; + port->change |= USB_PORT_STAT_C_RESET; + + if (wusb_dev->addr & WUSB_DEV_ADDR_UNAUTH) + result = 0; + else + result = wusb_dev_update_address(wusbhc, wusb_dev); + + port->status &= ~USB_PORT_STAT_RESET; + port->status |= USB_PORT_STAT_ENABLE; + port->change |= USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_ENABLE; + + return result; +} + +/* + * Return the hub change status bitmap + * + * The bits in the change status bitmap are cleared when a + * ClearPortFeature request is issued (USB2.0[11.12.3,11.12.4]. + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * WARNING!! This gets called from atomic context; we cannot get the + * mutex--the only race condition we can find is some bit + * changing just after we copy it, which shouldn't be too + * big of a problem [and we can't make it an spinlock + * because other parts need to take it and sleep] . + * + * @usb_hcd is refcounted, so it won't disappear under us + * and before killing a host, the polling of the root hub + * would be stopped anyway. + */ +int wusbhc_rh_status_data(struct usb_hcd *usb_hcd, char *_buf) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + size_t cnt, size; + unsigned long *buf = (unsigned long *) _buf; + + /* WE DON'T LOCK, see comment */ + size = wusbhc->ports_max + 1 /* hub bit */; + size = (size + 8 - 1) / 8; /* round to bytes */ + for (cnt = 0; cnt < wusbhc->ports_max; cnt++) + if (wusb_port_by_idx(wusbhc, cnt)->change) + set_bit(cnt + 1, buf); + else + clear_bit(cnt + 1, buf); + return size; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_status_data); + +/* + * Return the hub's descriptor + * + * NOTE: almost cut and paste from ehci-hub.c + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked + */ +static int wusbhc_rh_get_hub_descr(struct wusbhc *wusbhc, u16 wValue, + u16 wIndex, + struct usb_hub_descriptor *descr, + u16 wLength) +{ + u16 temp = 1 + (wusbhc->ports_max / 8); + u8 length = 7 + 2 * temp; + + if (wLength < length) + return -ENOSPC; + descr->bDescLength = 7 + 2 * temp; + descr->bDescriptorType = 0x29; /* HUB type */ + descr->bNbrPorts = wusbhc->ports_max; + descr->wHubCharacteristics = cpu_to_le16( + 0x00 /* All ports power at once */ + | 0x00 /* not part of compound device */ + | 0x10 /* No overcurrent protection */ + | 0x00 /* 8 FS think time FIXME ?? */ + | 0x00); /* No port indicators */ + descr->bPwrOn2PwrGood = 0; + descr->bHubContrCurrent = 0; + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&descr->u.hs.DeviceRemovable[0], 0, temp); + memset(&descr->u.hs.DeviceRemovable[temp], 0xff, temp); + return 0; +} + +/* + * Clear a hub feature + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Nothing to do, so no locking needed ;) + */ +static int wusbhc_rh_clear_hub_feat(struct wusbhc *wusbhc, u16 feature) +{ + int result; + + switch (feature) { + case C_HUB_LOCAL_POWER: + /* FIXME: maybe plug bit 0 to the power input status, + * if any? + * see wusbhc_rh_get_hub_status() */ + case C_HUB_OVER_CURRENT: + result = 0; + break; + default: + result = -EPIPE; + } + return result; +} + +/* + * Return hub status (it is always zero...) + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Nothing to do, so no locking needed ;) + */ +static int wusbhc_rh_get_hub_status(struct wusbhc *wusbhc, u32 *buf, + u16 wLength) +{ + /* FIXME: maybe plug bit 0 to the power input status (if any)? */ + *buf = 0; + return 0; +} + +/* + * Set a port feature + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_set_port_feat(struct wusbhc *wusbhc, u16 feature, + u8 selector, u8 port_idx) +{ + struct device *dev = wusbhc->dev; + + if (port_idx > wusbhc->ports_max) + return -EINVAL; + + switch (feature) { + /* According to USB2.0[11.24.2.13]p2, these features + * are not required to be implemented. */ + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_RESET: + return 0; + case USB_PORT_FEAT_POWER: + /* No such thing, but we fake it works */ + mutex_lock(&wusbhc->mutex); + wusb_port_by_idx(wusbhc, port_idx)->status |= USB_PORT_STAT_POWER; + mutex_unlock(&wusbhc->mutex); + return 0; + case USB_PORT_FEAT_RESET: + return wusbhc_rh_port_reset(wusbhc, port_idx); + case USB_PORT_FEAT_ENABLE: + case USB_PORT_FEAT_SUSPEND: + dev_err(dev, "(port_idx %d) set feat %d/%d UNIMPLEMENTED\n", + port_idx, feature, selector); + return -ENOSYS; + default: + dev_err(dev, "(port_idx %d) set feat %d/%d UNKNOWN\n", + port_idx, feature, selector); + return -EPIPE; + } + + return 0; +} + +/* + * Clear a port feature... + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_clear_port_feat(struct wusbhc *wusbhc, u16 feature, + u8 selector, u8 port_idx) +{ + int result = 0; + struct device *dev = wusbhc->dev; + + if (port_idx > wusbhc->ports_max) + return -EINVAL; + + mutex_lock(&wusbhc->mutex); + switch (feature) { + case USB_PORT_FEAT_POWER: /* fake port always on */ + /* According to USB2.0[11.24.2.7.1.4], no need to implement? */ + case USB_PORT_FEAT_C_OVER_CURRENT: + break; + case USB_PORT_FEAT_C_RESET: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_RESET; + break; + case USB_PORT_FEAT_C_CONNECTION: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_CONNECTION; + break; + case USB_PORT_FEAT_ENABLE: + __wusbhc_dev_disable(wusbhc, port_idx); + break; + case USB_PORT_FEAT_C_ENABLE: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_ENABLE; + break; + case USB_PORT_FEAT_SUSPEND: + case USB_PORT_FEAT_C_SUSPEND: + dev_err(dev, "(port_idx %d) Clear feat %d/%d UNIMPLEMENTED\n", + port_idx, feature, selector); + result = -ENOSYS; + break; + default: + dev_err(dev, "(port_idx %d) Clear feat %d/%d UNKNOWN\n", + port_idx, feature, selector); + result = -EPIPE; + break; + } + mutex_unlock(&wusbhc->mutex); + + return result; +} + +/* + * Return the port's status + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_get_port_status(struct wusbhc *wusbhc, u16 port_idx, + u32 *_buf, u16 wLength) +{ + __le16 *buf = (__le16 *)_buf; + + if (port_idx > wusbhc->ports_max) + return -EINVAL; + + mutex_lock(&wusbhc->mutex); + buf[0] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->status); + buf[1] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->change); + mutex_unlock(&wusbhc->mutex); + + return 0; +} + +/* + * Entry point for Root Hub operations + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +int wusbhc_rh_control(struct usb_hcd *usb_hcd, u16 reqntype, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + int result = -ENOSYS; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + + switch (reqntype) { + case GetHubDescriptor: + result = wusbhc_rh_get_hub_descr( + wusbhc, wValue, wIndex, + (struct usb_hub_descriptor *) buf, wLength); + break; + case ClearHubFeature: + result = wusbhc_rh_clear_hub_feat(wusbhc, wValue); + break; + case GetHubStatus: + result = wusbhc_rh_get_hub_status(wusbhc, (u32 *)buf, wLength); + break; + + case SetPortFeature: + result = wusbhc_rh_set_port_feat(wusbhc, wValue, wIndex >> 8, + (wIndex & 0xff) - 1); + break; + case ClearPortFeature: + result = wusbhc_rh_clear_port_feat(wusbhc, wValue, wIndex >> 8, + (wIndex & 0xff) - 1); + break; + case GetPortStatus: + result = wusbhc_rh_get_port_status(wusbhc, wIndex - 1, + (u32 *)buf, wLength); + break; + + case SetHubFeature: + default: + dev_err(wusbhc->dev, "%s (%p [%p], %x, %x, %x, %p, %x) " + "UNIMPLEMENTED\n", __func__, usb_hcd, wusbhc, reqntype, + wValue, wIndex, buf, wLength); + /* dump_stack(); */ + result = -ENOSYS; + } + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_control); + +int wusbhc_rh_suspend(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, wusbhc); + /* dump_stack(); */ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_suspend); + +int wusbhc_rh_resume(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, wusbhc); + /* dump_stack(); */ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_resume); + +int wusbhc_rh_start_port_reset(struct usb_hcd *usb_hcd, unsigned port_idx) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p], port_idx %u) UNIMPLEMENTED\n", + __func__, usb_hcd, wusbhc, port_idx); + WARN_ON(1); + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_start_port_reset); + +static void wusb_port_init(struct wusb_port *port) +{ + port->status |= USB_PORT_STAT_HIGH_SPEED; +} + +/* + * Alloc fake port specific fields and status. + */ +int wusbhc_rh_create(struct wusbhc *wusbhc) +{ + int result = -ENOMEM; + size_t port_size, itr; + port_size = wusbhc->ports_max * sizeof(wusbhc->port[0]); + wusbhc->port = kzalloc(port_size, GFP_KERNEL); + if (wusbhc->port == NULL) + goto error_port_alloc; + for (itr = 0; itr < wusbhc->ports_max; itr++) + wusb_port_init(&wusbhc->port[itr]); + result = 0; +error_port_alloc: + return result; +} + +void wusbhc_rh_destroy(struct wusbhc *wusbhc) +{ + kfree(wusbhc->port); +} diff --git a/drivers/usb/wusbcore/security.c b/drivers/usb/wusbcore/security.c new file mode 100644 index 00000000..dd88441c --- /dev/null +++ b/drivers/usb/wusbcore/security.c @@ -0,0 +1,578 @@ +/* + * Wireless USB Host Controller + * Security support: encryption enablement, etc + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ +#include +#include +#include +#include +#include +#include "wusbhc.h" + +static void wusbhc_set_gtk_callback(struct urb *urb); +static void wusbhc_gtk_rekey_done_work(struct work_struct *work); + +int wusbhc_sec_create(struct wusbhc *wusbhc) +{ + wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data); + wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY; + wusbhc->gtk.descr.bReserved = 0; + + wusbhc->gtk_index = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + INIT_WORK(&wusbhc->gtk_rekey_done_work, wusbhc_gtk_rekey_done_work); + + return 0; +} + + +/* Called when the HC is destroyed */ +void wusbhc_sec_destroy(struct wusbhc *wusbhc) +{ +} + + +/** + * wusbhc_next_tkid - generate a new, currently unused, TKID + * @wusbhc: the WUSB host controller + * @wusb_dev: the device whose PTK the TKID is for + * (or NULL for a TKID for a GTK) + * + * The generated TKID consist of two parts: the device's authenicated + * address (or 0 or a GTK); and an incrementing number. This ensures + * that TKIDs cannot be shared between devices and by the time the + * incrementing number wraps around the older TKIDs will no longer be + * in use (a maximum of two keys may be active at any one time). + */ +static u32 wusbhc_next_tkid(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + u32 *tkid; + u32 addr; + + if (wusb_dev == NULL) { + tkid = &wusbhc->gtk_tkid; + addr = 0; + } else { + tkid = &wusb_port_by_idx(wusbhc, wusb_dev->port_idx)->ptk_tkid; + addr = wusb_dev->addr & 0x7f; + } + + *tkid = (addr << 8) | ((*tkid + 1) & 0xff); + + return *tkid; +} + +static void wusbhc_generate_gtk(struct wusbhc *wusbhc) +{ + const size_t key_size = sizeof(wusbhc->gtk.data); + u32 tkid; + + tkid = wusbhc_next_tkid(wusbhc, NULL); + + wusbhc->gtk.descr.tTKID[0] = (tkid >> 0) & 0xff; + wusbhc->gtk.descr.tTKID[1] = (tkid >> 8) & 0xff; + wusbhc->gtk.descr.tTKID[2] = (tkid >> 16) & 0xff; + + get_random_bytes(wusbhc->gtk.descr.bKeyData, key_size); +} + +/** + * wusbhc_sec_start - start the security management process + * @wusbhc: the WUSB host controller + * + * Generate and set an initial GTK on the host controller. + * + * Called when the HC is started. + */ +int wusbhc_sec_start(struct wusbhc *wusbhc) +{ + const size_t key_size = sizeof(wusbhc->gtk.data); + int result; + + wusbhc_generate_gtk(wusbhc); + + result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, + &wusbhc->gtk.descr.bKeyData, key_size); + if (result < 0) + dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n", + result); + + return result; +} + +/** + * wusbhc_sec_stop - stop the security management process + * @wusbhc: the WUSB host controller + * + * Wait for any pending GTK rekeys to stop. + */ +void wusbhc_sec_stop(struct wusbhc *wusbhc) +{ + cancel_work_sync(&wusbhc->gtk_rekey_done_work); +} + + +/** @returns encryption type name */ +const char *wusb_et_name(u8 x) +{ + switch (x) { + case USB_ENC_TYPE_UNSECURE: return "unsecure"; + case USB_ENC_TYPE_WIRED: return "wired"; + case USB_ENC_TYPE_CCM_1: return "CCM-1"; + case USB_ENC_TYPE_RSA_1: return "RSA-1"; + default: return "unknown"; + } +} +EXPORT_SYMBOL_GPL(wusb_et_name); + +/* + * Set the device encryption method + * + * We tell the device which encryption method to use; we do this when + * setting up the device's security. + */ +static int wusb_dev_set_encryption(struct usb_device *usb_dev, int value) +{ + int result; + struct device *dev = &usb_dev->dev; + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + + if (value) { + value = wusb_dev->ccm1_etd.bEncryptionValue; + } else { + /* FIXME: should be wusb_dev->etd[UNSECURE].bEncryptionValue */ + value = 0; + } + /* Set device's */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ENCRYPTION, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + value, 0, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Can't set device's WUSB encryption to " + "%s (value %d): %d\n", + wusb_et_name(wusb_dev->ccm1_etd.bEncryptionType), + wusb_dev->ccm1_etd.bEncryptionValue, result); + return result; +} + +/* + * Set the GTK to be used by a device. + * + * The device must be authenticated. + */ +static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct usb_device *usb_dev = wusb_dev->usb_dev; + + return usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_DT_KEY << 8 | wusbhc->gtk_index, 0, + &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, + 1000); +} + + +/* FIXME: prototype for adding security */ +int wusb_dev_sec_add(struct wusbhc *wusbhc, + struct usb_device *usb_dev, struct wusb_dev *wusb_dev) +{ + int result, bytes, secd_size; + struct device *dev = &usb_dev->dev; + struct usb_security_descriptor *secd, *new_secd; + const struct usb_encryption_descriptor *etd, *ccm1_etd = NULL; + const void *itr, *top; + char buf[64]; + + secd = kmalloc(sizeof(*secd), GFP_KERNEL); + if (secd == NULL) { + result = -ENOMEM; + goto out; + } + + result = usb_get_descriptor(usb_dev, USB_DT_SECURITY, + 0, secd, sizeof(*secd)); + if (result < sizeof(*secd)) { + dev_err(dev, "Can't read security descriptor or " + "not enough data: %d\n", result); + goto out; + } + secd_size = le16_to_cpu(secd->wTotalLength); + new_secd = krealloc(secd, secd_size, GFP_KERNEL); + if (new_secd == NULL) { + dev_err(dev, "Can't allocate space for security descriptors\n"); + goto out; + } + secd = new_secd; + result = usb_get_descriptor(usb_dev, USB_DT_SECURITY, + 0, secd, secd_size); + if (result < secd_size) { + dev_err(dev, "Can't read security descriptor or " + "not enough data: %d\n", result); + goto out; + } + bytes = 0; + itr = &secd[1]; + top = (void *)secd + result; + while (itr < top) { + etd = itr; + if (top - itr < sizeof(*etd)) { + dev_err(dev, "BUG: bad device security descriptor; " + "not enough data (%zu vs %zu bytes left)\n", + top - itr, sizeof(*etd)); + break; + } + if (etd->bLength < sizeof(*etd)) { + dev_err(dev, "BUG: bad device encryption descriptor; " + "descriptor is too short " + "(%u vs %zu needed)\n", + etd->bLength, sizeof(*etd)); + break; + } + itr += etd->bLength; + bytes += snprintf(buf + bytes, sizeof(buf) - bytes, + "%s (0x%02x/%02x) ", + wusb_et_name(etd->bEncryptionType), + etd->bEncryptionValue, etd->bAuthKeyIndex); + if (etd->bEncryptionType == USB_ENC_TYPE_CCM_1) + ccm1_etd = etd; + } + /* This code only supports CCM1 as of now. */ + /* FIXME: user has to choose which sec mode to use? + * In theory we want CCM */ + if (ccm1_etd == NULL) { + dev_err(dev, "WUSB device doesn't support CCM1 encryption, " + "can't use!\n"); + result = -EINVAL; + goto out; + } + wusb_dev->ccm1_etd = *ccm1_etd; + dev_dbg(dev, "supported encryption: %s; using %s (0x%02x/%02x)\n", + buf, wusb_et_name(ccm1_etd->bEncryptionType), + ccm1_etd->bEncryptionValue, ccm1_etd->bAuthKeyIndex); + result = 0; +out: + kfree(secd); + return result; +} + +void wusb_dev_sec_rm(struct wusb_dev *wusb_dev) +{ + /* Nothing so far */ +} + +/** + * Update the address of an unauthenticated WUSB device + * + * Once we have successfully authenticated, we take it to addr0 state + * and then to a normal address. + * + * Before the device's address (as known by it) was usb_dev->devnum | + * 0x80 (unauthenticated address). With this we update it to usb_dev->devnum. + */ +int wusb_dev_update_address(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + int result = -ENOMEM; + struct usb_device *usb_dev = wusb_dev->usb_dev; + struct device *dev = &usb_dev->dev; + u8 new_address = wusb_dev->addr & 0x7F; + + /* Set address 0 */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ADDRESS, 0, + 0, 0, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "auth failed: can't set address 0: %d\n", + result); + goto error_addr0; + } + result = wusb_set_dev_addr(wusbhc, wusb_dev, 0); + if (result < 0) + goto error_addr0; + usb_set_device_state(usb_dev, USB_STATE_DEFAULT); + usb_ep0_reinit(usb_dev); + + /* Set new (authenticated) address. */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ADDRESS, 0, + new_address, 0, NULL, 0, + 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "auth failed: can't set address %u: %d\n", + new_address, result); + goto error_addr; + } + result = wusb_set_dev_addr(wusbhc, wusb_dev, new_address); + if (result < 0) + goto error_addr; + usb_set_device_state(usb_dev, USB_STATE_ADDRESS); + usb_ep0_reinit(usb_dev); + usb_dev->authenticated = 1; +error_addr: +error_addr0: + return result; +} + +/* + * + * + */ +/* FIXME: split and cleanup */ +int wusb_dev_4way_handshake(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, + struct wusb_ckhdid *ck) +{ + int result = -ENOMEM; + struct usb_device *usb_dev = wusb_dev->usb_dev; + struct device *dev = &usb_dev->dev; + u32 tkid; + __le32 tkid_le; + struct usb_handshake *hs; + struct aes_ccm_nonce ccm_n; + u8 mic[8]; + struct wusb_keydvt_in keydvt_in; + struct wusb_keydvt_out keydvt_out; + + hs = kcalloc(3, sizeof(hs[0]), GFP_KERNEL); + if (hs == NULL) { + dev_err(dev, "can't allocate handshake data\n"); + goto error_kzalloc; + } + + /* We need to turn encryption before beginning the 4way + * hshake (WUSB1.0[.3.2.2]) */ + result = wusb_dev_set_encryption(usb_dev, 1); + if (result < 0) + goto error_dev_set_encryption; + + tkid = wusbhc_next_tkid(wusbhc, wusb_dev); + tkid_le = cpu_to_le32(tkid); + + hs[0].bMessageNumber = 1; + hs[0].bStatus = 0; + memcpy(hs[0].tTKID, &tkid_le, sizeof(hs[0].tTKID)); + hs[0].bReserved = 0; + memcpy(hs[0].CDID, &wusb_dev->cdid, sizeof(hs[0].CDID)); + get_random_bytes(&hs[0].nonce, sizeof(hs[0].nonce)); + memset(hs[0].MIC, 0, sizeof(hs[0].MIC)); /* Per WUSB1.0[T7-22] */ + + result = usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_HANDSHAKE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 1, 0, &hs[0], sizeof(hs[0]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake1: request failed: %d\n", result); + goto error_hs1; + } + + /* Handshake 2, from the device -- need to verify fields */ + result = usb_control_msg( + usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_GET_HANDSHAKE, + USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 2, 0, &hs[1], sizeof(hs[1]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake2: request failed: %d\n", result); + goto error_hs2; + } + + result = -EINVAL; + if (hs[1].bMessageNumber != 2) { + dev_err(dev, "Handshake2 failed: bad message number %u\n", + hs[1].bMessageNumber); + goto error_hs2; + } + if (hs[1].bStatus != 0) { + dev_err(dev, "Handshake2 failed: bad status %u\n", + hs[1].bStatus); + goto error_hs2; + } + if (memcmp(hs[0].tTKID, hs[1].tTKID, sizeof(hs[0].tTKID))) { + dev_err(dev, "Handshake2 failed: TKID mismatch " + "(#1 0x%02x%02x%02x vs #2 0x%02x%02x%02x)\n", + hs[0].tTKID[0], hs[0].tTKID[1], hs[0].tTKID[2], + hs[1].tTKID[0], hs[1].tTKID[1], hs[1].tTKID[2]); + goto error_hs2; + } + if (memcmp(hs[0].CDID, hs[1].CDID, sizeof(hs[0].CDID))) { + dev_err(dev, "Handshake2 failed: CDID mismatch\n"); + goto error_hs2; + } + + /* Setup the CCM nonce */ + memset(&ccm_n.sfn, 0, sizeof(ccm_n.sfn)); /* Per WUSB1.0[6.5.2] */ + memcpy(ccm_n.tkid, &tkid_le, sizeof(ccm_n.tkid)); + ccm_n.src_addr = wusbhc->uwb_rc->uwb_dev.dev_addr; + ccm_n.dest_addr.data[0] = wusb_dev->addr; + ccm_n.dest_addr.data[1] = 0; + + /* Derive the KCK and PTK from CK, the CCM, H and D nonces */ + memcpy(keydvt_in.hnonce, hs[0].nonce, sizeof(keydvt_in.hnonce)); + memcpy(keydvt_in.dnonce, hs[1].nonce, sizeof(keydvt_in.dnonce)); + result = wusb_key_derive(&keydvt_out, ck->data, &ccm_n, &keydvt_in); + if (result < 0) { + dev_err(dev, "Handshake2 failed: cannot derive keys: %d\n", + result); + goto error_hs2; + } + + /* Compute MIC and verify it */ + result = wusb_oob_mic(mic, keydvt_out.kck, &ccm_n, &hs[1]); + if (result < 0) { + dev_err(dev, "Handshake2 failed: cannot compute MIC: %d\n", + result); + goto error_hs2; + } + + if (memcmp(hs[1].MIC, mic, sizeof(hs[1].MIC))) { + dev_err(dev, "Handshake2 failed: MIC mismatch\n"); + goto error_hs2; + } + + /* Send Handshake3 */ + hs[2].bMessageNumber = 3; + hs[2].bStatus = 0; + memcpy(hs[2].tTKID, &tkid_le, sizeof(hs[2].tTKID)); + hs[2].bReserved = 0; + memcpy(hs[2].CDID, &wusb_dev->cdid, sizeof(hs[2].CDID)); + memcpy(hs[2].nonce, hs[0].nonce, sizeof(hs[2].nonce)); + result = wusb_oob_mic(hs[2].MIC, keydvt_out.kck, &ccm_n, &hs[2]); + if (result < 0) { + dev_err(dev, "Handshake3 failed: cannot compute MIC: %d\n", + result); + goto error_hs2; + } + + result = usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_HANDSHAKE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 3, 0, &hs[2], sizeof(hs[2]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake3: request failed: %d\n", result); + goto error_hs3; + } + + result = wusbhc->set_ptk(wusbhc, wusb_dev->port_idx, tkid, + keydvt_out.ptk, sizeof(keydvt_out.ptk)); + if (result < 0) + goto error_wusbhc_set_ptk; + + result = wusb_dev_set_gtk(wusbhc, wusb_dev); + if (result < 0) { + dev_err(dev, "Set GTK for device: request failed: %d\n", + result); + goto error_wusbhc_set_gtk; + } + + /* Update the device's address from unauth to auth */ + if (usb_dev->authenticated == 0) { + result = wusb_dev_update_address(wusbhc, wusb_dev); + if (result < 0) + goto error_dev_update_address; + } + result = 0; + dev_info(dev, "device authenticated\n"); + +error_dev_update_address: +error_wusbhc_set_gtk: +error_wusbhc_set_ptk: +error_hs3: +error_hs2: +error_hs1: + memset(hs, 0, 3*sizeof(hs[0])); + memset(&keydvt_out, 0, sizeof(keydvt_out)); + memset(&keydvt_in, 0, sizeof(keydvt_in)); + memset(&ccm_n, 0, sizeof(ccm_n)); + memset(mic, 0, sizeof(mic)); + if (result < 0) + wusb_dev_set_encryption(usb_dev, 0); +error_dev_set_encryption: + kfree(hs); +error_kzalloc: + return result; +} + +/* + * Once all connected and authenticated devices have received the new + * GTK, switch the host to using it. + */ +static void wusbhc_gtk_rekey_done_work(struct work_struct *work) +{ + struct wusbhc *wusbhc = container_of(work, struct wusbhc, gtk_rekey_done_work); + size_t key_size = sizeof(wusbhc->gtk.data); + + mutex_lock(&wusbhc->mutex); + + if (--wusbhc->pending_set_gtks == 0) + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); + + mutex_unlock(&wusbhc->mutex); +} + +static void wusbhc_set_gtk_callback(struct urb *urb) +{ + struct wusbhc *wusbhc = urb->context; + + queue_work(wusbd, &wusbhc->gtk_rekey_done_work); +} + +/** + * wusbhc_gtk_rekey - generate and distribute a new GTK + * @wusbhc: the WUSB host controller + * + * Generate a new GTK and distribute it to all connected and + * authenticated devices. When all devices have the new GTK, the host + * starts using it. + * + * This must be called after every device disconnect (see [WUSB] + * section 6.2.11.2). + */ +void wusbhc_gtk_rekey(struct wusbhc *wusbhc) +{ + static const size_t key_size = sizeof(wusbhc->gtk.data); + int p; + + wusbhc_generate_gtk(wusbhc); + + for (p = 0; p < wusbhc->ports_max; p++) { + struct wusb_dev *wusb_dev; + + wusb_dev = wusbhc->port[p].wusb_dev; + if (!wusb_dev || !wusb_dev->usb_dev || !wusb_dev->usb_dev->authenticated) + continue; + + usb_fill_control_urb(wusb_dev->set_gtk_urb, wusb_dev->usb_dev, + usb_sndctrlpipe(wusb_dev->usb_dev, 0), + (void *)wusb_dev->set_gtk_req, + &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, + wusbhc_set_gtk_callback, wusbhc); + if (usb_submit_urb(wusb_dev->set_gtk_urb, GFP_KERNEL) == 0) + wusbhc->pending_set_gtks++; + } + if (wusbhc->pending_set_gtks == 0) + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); +} diff --git a/drivers/usb/wusbcore/wa-hc.c b/drivers/usb/wusbcore/wa-hc.c new file mode 100644 index 00000000..a09b65eb --- /dev/null +++ b/drivers/usb/wusbcore/wa-hc.c @@ -0,0 +1,99 @@ +/* + * Wire Adapter Host Controller Driver + * Common items to HWA and DWA based HCDs + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ +#include +#include +#include "wusbhc.h" +#include "wa-hc.h" + +/** + * Assumes + * + * wa->usb_dev and wa->usb_iface initialized and refcounted, + * wa->wa_descr initialized. + */ +int wa_create(struct wahc *wa, struct usb_interface *iface) +{ + int result; + struct device *dev = &iface->dev; + + result = wa_rpipes_create(wa); + if (result < 0) + goto error_rpipes_create; + /* Fill up Data Transfer EP pointers */ + wa->dti_epd = &iface->cur_altsetting->endpoint[1].desc; + wa->dto_epd = &iface->cur_altsetting->endpoint[2].desc; + wa->xfer_result_size = usb_endpoint_maxp(wa->dti_epd); + wa->xfer_result = kmalloc(wa->xfer_result_size, GFP_KERNEL); + if (wa->xfer_result == NULL) { + result = -ENOMEM; + goto error_xfer_result_alloc; + } + result = wa_nep_create(wa, iface); + if (result < 0) { + dev_err(dev, "WA-CDS: can't initialize notif endpoint: %d\n", + result); + goto error_nep_create; + } + return 0; + +error_nep_create: + kfree(wa->xfer_result); +error_xfer_result_alloc: + wa_rpipes_destroy(wa); +error_rpipes_create: + return result; +} +EXPORT_SYMBOL_GPL(wa_create); + + +void __wa_destroy(struct wahc *wa) +{ + if (wa->dti_urb) { + usb_kill_urb(wa->dti_urb); + usb_put_urb(wa->dti_urb); + usb_kill_urb(wa->buf_in_urb); + usb_put_urb(wa->buf_in_urb); + } + kfree(wa->xfer_result); + wa_nep_destroy(wa); + wa_rpipes_destroy(wa); +} +EXPORT_SYMBOL_GPL(__wa_destroy); + +/** + * wa_reset_all - reset the WA device + * @wa: the WA to be reset + * + * For HWAs the radio controller and all other PALs are also reset. + */ +void wa_reset_all(struct wahc *wa) +{ + /* FIXME: assuming HWA. */ + wusbhc_reset_all(wa->wusb); +} + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB Wire Adapter core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/wa-hc.h b/drivers/usb/wusbcore/wa-hc.h new file mode 100644 index 00000000..d6bea3e0 --- /dev/null +++ b/drivers/usb/wusbcore/wa-hc.h @@ -0,0 +1,417 @@ +/* + * HWA Host Controller Driver + * Wire Adapter Control/Data Streaming Iface (WUSB1.0[8]) + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver implements a USB Host Controller (struct usb_hcd) for a + * Wireless USB Host Controller based on the Wireless USB 1.0 + * Host-Wire-Adapter specification (in layman terms, a USB-dongle that + * implements a Wireless USB host). + * + * Check out the Design-overview.txt file in the source documentation + * for other details on the implementation. + * + * Main blocks: + * + * driver glue with the driver API, workqueue daemon + * + * lc RC instance life cycle management (create, destroy...) + * + * hcd glue with the USB API Host Controller Interface API. + * + * nep Notification EndPoint managent: collect notifications + * and queue them with the workqueue daemon. + * + * Handle notifications as coming from the NEP. Sends them + * off others to their respective modules (eg: connect, + * disconnect and reset go to devconnect). + * + * rpipe Remote Pipe management; rpipe is what we use to write + * to an endpoint on a WUSB device that is connected to a + * HWA RC. + * + * xfer Transfer management -- this is all the code that gets a + * buffer and pushes it to a device (or viceversa). * + * + * Some day a lot of this code will be shared between this driver and + * the drivers for DWA (xfer, rpipe). + * + * All starts at driver.c:hwahc_probe(), when one of this guys is + * connected. hwahc_disconnect() stops it. + * + * During operation, the main driver is devices connecting or + * disconnecting. They cause the HWA RC to send notifications into + * nep.c:hwahc_nep_cb() that will dispatch them to + * notif.c:wa_notif_dispatch(). From there they will fan to cause + * device connects, disconnects, etc. + * + * Note much of the activity is difficult to follow. For example a + * device connect goes to devconnect, which will cause the "fake" root + * hub port to show a connect and stop there. Then khubd will notice + * and call into the rh.c:hwahc_rc_port_reset() code to authenticate + * the device (and this might require user intervention) and enable + * the port. + * + * We also have a timer workqueue going from devconnect.c that + * schedules in hwahc_devconnect_create(). + * + * The rest of the traffic is in the usual entry points of a USB HCD, + * which are hooked up in driver.c:hwahc_rc_driver, and defined in + * hcd.c. + */ + +#ifndef __HWAHC_INTERNAL_H__ +#define __HWAHC_INTERNAL_H__ + +#include +#include +#include +#include +#include +#include +#include + +struct wusbhc; +struct wahc; +extern void wa_urb_enqueue_run(struct work_struct *ws); + +/** + * RPipe instance + * + * @descr's fields are kept in LE, as we need to send it back and + * forth. + * + * @wa is referenced when set + * + * @segs_available is the number of requests segments that still can + * be submitted to the controller without overloading + * it. It is initialized to descr->wRequests when + * aiming. + * + * A rpipe supports a max of descr->wRequests at the same time; before + * submitting seg_lock has to be taken. If segs_avail > 0, then we can + * submit; if not, we have to queue them. + */ +struct wa_rpipe { + struct kref refcnt; + struct usb_rpipe_descriptor descr; + struct usb_host_endpoint *ep; + struct wahc *wa; + spinlock_t seg_lock; + struct list_head seg_list; + atomic_t segs_available; + u8 buffer[1]; /* For reads/writes on USB */ +}; + + +/** + * Instance of a HWA Host Controller + * + * Except where a more specific lock/mutex applies or atomic, all + * fields protected by @mutex. + * + * @wa_descr Can be accessed without locking because it is in + * the same area where the device descriptors were + * read, so it is guaranteed to exist umodified while + * the device exists. + * + * Endianess has been converted to CPU's. + * + * @nep_* can be accessed without locking as its processing is + * serialized; we submit a NEP URB and it comes to + * hwahc_nep_cb(), which won't issue another URB until it is + * done processing it. + * + * @xfer_list: + * + * List of active transfers to verify existence from a xfer id + * gotten from the xfer result message. Can't use urb->list because + * it goes by endpoint, and we don't know the endpoint at the time + * when we get the xfer result message. We can't really rely on the + * pointer (will have to change for 64 bits) as the xfer id is 32 bits. + * + * @xfer_delayed_list: List of transfers that need to be started + * (with a workqueue, because they were + * submitted from an atomic context). + * + * FIXME: this needs to be layered up: a wusbhc layer (for sharing + * comonalities with WHCI), a wa layer (for sharing + * comonalities with DWA-RC). + */ +struct wahc { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + + /* HC to deliver notifications */ + union { + struct wusbhc *wusb; + struct dwahc *dwa; + }; + + const struct usb_endpoint_descriptor *dto_epd, *dti_epd; + const struct usb_wa_descriptor *wa_descr; + + struct urb *nep_urb; /* Notification EndPoint [lockless] */ + struct edc nep_edc; + void *nep_buffer; + size_t nep_buffer_size; + + atomic_t notifs_queued; + + u16 rpipes; + unsigned long *rpipe_bm; /* rpipe usage bitmap */ + spinlock_t rpipe_bm_lock; /* protect rpipe_bm */ + struct mutex rpipe_mutex; /* assigning resources to endpoints */ + + struct urb *dti_urb; /* URB for reading xfer results */ + struct urb *buf_in_urb; /* URB for reading data in */ + struct edc dti_edc; /* DTI error density counter */ + struct wa_xfer_result *xfer_result; /* real size = dti_ep maxpktsize */ + size_t xfer_result_size; + + s32 status; /* For reading status */ + + struct list_head xfer_list; + struct list_head xfer_delayed_list; + spinlock_t xfer_list_lock; + struct work_struct xfer_work; + atomic_t xfer_id_count; +}; + + +extern int wa_create(struct wahc *wa, struct usb_interface *iface); +extern void __wa_destroy(struct wahc *wa); +void wa_reset_all(struct wahc *wa); + + +/* Miscellaneous constants */ +enum { + /** Max number of EPROTO errors we tolerate on the NEP in a + * period of time */ + HWAHC_EPROTO_MAX = 16, + /** Period of time for EPROTO errors (in jiffies) */ + HWAHC_EPROTO_PERIOD = 4 * HZ, +}; + + +/* Notification endpoint handling */ +extern int wa_nep_create(struct wahc *, struct usb_interface *); +extern void wa_nep_destroy(struct wahc *); + +static inline int wa_nep_arm(struct wahc *wa, gfp_t gfp_mask) +{ + struct urb *urb = wa->nep_urb; + urb->transfer_buffer = wa->nep_buffer; + urb->transfer_buffer_length = wa->nep_buffer_size; + return usb_submit_urb(urb, gfp_mask); +} + +static inline void wa_nep_disarm(struct wahc *wa) +{ + usb_kill_urb(wa->nep_urb); +} + + +/* RPipes */ +static inline void wa_rpipe_init(struct wahc *wa) +{ + spin_lock_init(&wa->rpipe_bm_lock); + mutex_init(&wa->rpipe_mutex); +} + +static inline void wa_init(struct wahc *wa) +{ + edc_init(&wa->nep_edc); + atomic_set(&wa->notifs_queued, 0); + wa_rpipe_init(wa); + edc_init(&wa->dti_edc); + INIT_LIST_HEAD(&wa->xfer_list); + INIT_LIST_HEAD(&wa->xfer_delayed_list); + spin_lock_init(&wa->xfer_list_lock); + INIT_WORK(&wa->xfer_work, wa_urb_enqueue_run); + atomic_set(&wa->xfer_id_count, 1); +} + +/** + * Destroy a pipe (when refcount drops to zero) + * + * Assumes it has been moved to the "QUIESCING" state. + */ +struct wa_xfer; +extern void rpipe_destroy(struct kref *_rpipe); +static inline +void __rpipe_get(struct wa_rpipe *rpipe) +{ + kref_get(&rpipe->refcnt); +} +extern int rpipe_get_by_ep(struct wahc *, struct usb_host_endpoint *, + struct urb *, gfp_t); +static inline void rpipe_put(struct wa_rpipe *rpipe) +{ + kref_put(&rpipe->refcnt, rpipe_destroy); + +} +extern void rpipe_ep_disable(struct wahc *, struct usb_host_endpoint *); +extern int wa_rpipes_create(struct wahc *); +extern void wa_rpipes_destroy(struct wahc *); +static inline void rpipe_avail_dec(struct wa_rpipe *rpipe) +{ + atomic_dec(&rpipe->segs_available); +} + +/** + * Returns true if the rpipe is ready to submit more segments. + */ +static inline int rpipe_avail_inc(struct wa_rpipe *rpipe) +{ + return atomic_inc_return(&rpipe->segs_available) > 0 + && !list_empty(&rpipe->seg_list); +} + + +/* Transferring data */ +extern int wa_urb_enqueue(struct wahc *, struct usb_host_endpoint *, + struct urb *, gfp_t); +extern int wa_urb_dequeue(struct wahc *, struct urb *); +extern void wa_handle_notif_xfer(struct wahc *, struct wa_notif_hdr *); + + +/* Misc + * + * FIXME: Refcounting for the actual @hwahc object is not correct; I + * mean, this should be refcounting on the HCD underneath, but + * it is not. In any case, the semantics for HCD refcounting + * are *weird*...on refcount reaching zero it just frees + * it...no RC specific function is called...unless I miss + * something. + * + * FIXME: has to go away in favour of an 'struct' hcd based sollution + */ +static inline struct wahc *wa_get(struct wahc *wa) +{ + usb_get_intf(wa->usb_iface); + return wa; +} + +static inline void wa_put(struct wahc *wa) +{ + usb_put_intf(wa->usb_iface); +} + + +static inline int __wa_feature(struct wahc *wa, unsigned op, u16 feature) +{ + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + op ? USB_REQ_SET_FEATURE : USB_REQ_CLEAR_FEATURE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + feature, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + + +static inline int __wa_set_feature(struct wahc *wa, u16 feature) +{ + return __wa_feature(wa, 1, feature); +} + + +static inline int __wa_clear_feature(struct wahc *wa, u16 feature) +{ + return __wa_feature(wa, 0, feature); +} + + +/** + * Return the status of a Wire Adapter + * + * @wa: Wire Adapter instance + * @returns < 0 errno code on error, or status bitmap as described + * in WUSB1.0[8.3.1.6]. + * + * NOTE: need malloc, some arches don't take USB from the stack + */ +static inline +s32 __wa_get_status(struct wahc *wa) +{ + s32 result; + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_GET_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + &wa->status, sizeof(wa->status), + 1000 /* FIXME: arbitrary */); + if (result >= 0) + result = wa->status; + return result; +} + + +/** + * Waits until the Wire Adapter's status matches @mask/@value + * + * @wa: Wire Adapter instance. + * @returns < 0 errno code on error, otherwise status. + * + * Loop until the WAs status matches the mask and value (status & mask + * == value). Timeout if it doesn't happen. + * + * FIXME: is there an official specification on how long status + * changes can take? + */ +static inline s32 __wa_wait_status(struct wahc *wa, u32 mask, u32 value) +{ + s32 result; + unsigned loops = 10; + do { + msleep(50); + result = __wa_get_status(wa); + if ((result & mask) == value) + break; + if (loops-- == 0) { + result = -ETIMEDOUT; + break; + } + } while (result >= 0); + return result; +} + + +/** Command @hwahc to stop, @returns 0 if ok, < 0 errno code on error */ +static inline int __wa_stop(struct wahc *wa) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + + result = __wa_clear_feature(wa, WA_ENABLE); + if (result < 0 && result != -ENODEV) { + dev_err(dev, "error commanding HC to stop: %d\n", result); + goto out; + } + result = __wa_wait_status(wa, WA_ENABLE, 0); + if (result < 0 && result != -ENODEV) + dev_err(dev, "error waiting for HC to stop: %d\n", result); +out: + return 0; +} + + +#endif /* #ifndef __HWAHC_INTERNAL_H__ */ diff --git a/drivers/usb/wusbcore/wa-nep.c b/drivers/usb/wusbcore/wa-nep.c new file mode 100644 index 00000000..f67f7f1e --- /dev/null +++ b/drivers/usb/wusbcore/wa-nep.c @@ -0,0 +1,305 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * Notification EndPoint support + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This part takes care of getting the notification from the hw + * only and dispatching through wusbwad into + * wa_notif_dispatch. Handling is done there. + * + * WA notifications are limited in size; most of them are three or + * four bytes long, and the longest is the HWA Device Notification, + * which would not exceed 38 bytes (DNs are limited in payload to 32 + * bytes plus 3 bytes header (WUSB1.0[7.6p2]), plus 3 bytes HWA + * header (WUSB1.0[8.5.4.2]). + * + * It is not clear if more than one Device Notification can be packed + * in a HWA Notification, I assume no because of the wording in + * WUSB1.0[8.5.4.2]. In any case, the bigger any notification could + * get is 256 bytes (as the bLength field is a byte). + * + * So what we do is we have this buffer and read into it; when a + * notification arrives we schedule work to a specific, single thread + * workqueue (so notifications are serialized) and copy the + * notification data. After scheduling the work, we rearm the read from + * the notification endpoint. + * + * Entry points here are: + * + * wa_nep_[create|destroy]() To initialize/release this subsystem + * + * wa_nep_cb() Callback for the notification + * endpoint; when data is ready, this + * does the dispatching. + */ +#include +#include +#include + +#include "wa-hc.h" +#include "wusbhc.h" + +/* Structure for queueing notifications to the workqueue */ +struct wa_notif_work { + struct work_struct work; + struct wahc *wa; + size_t size; + u8 data[]; +}; + +/* + * Process incoming notifications from the WA's Notification EndPoint + * [the wuswad daemon, basically] + * + * @_nw: Pointer to a descriptor which has the pointer to the + * @wa, the size of the buffer and the work queue + * structure (so we can free all when done). + * @returns 0 if ok, < 0 errno code on error. + * + * All notifications follow the same format; they need to start with a + * 'struct wa_notif_hdr' header, so it is easy to parse through + * them. We just break the buffer in individual notifications (the + * standard doesn't say if it can be done or is forbidden, so we are + * cautious) and dispatch each. + * + * So the handling layers are is: + * + * WA specific notification (from NEP) + * Device Notification Received -> wa_handle_notif_dn() + * WUSB Device notification generic handling + * BPST Adjustment -> wa_handle_notif_bpst_adj() + * ... -> ... + * + * @wa has to be referenced + */ +static void wa_notif_dispatch(struct work_struct *ws) +{ + void *itr; + u8 missing = 0; + struct wa_notif_work *nw = container_of(ws, struct wa_notif_work, work); + struct wahc *wa = nw->wa; + struct wa_notif_hdr *notif_hdr; + size_t size; + + struct device *dev = &wa->usb_iface->dev; + +#if 0 + /* FIXME: need to check for this??? */ + if (usb_hcd->state == HC_STATE_QUIESCING) /* Going down? */ + goto out; /* screw it */ +#endif + atomic_dec(&wa->notifs_queued); /* Throttling ctl */ + dev = &wa->usb_iface->dev; + size = nw->size; + itr = nw->data; + + while (size) { + if (size < sizeof(*notif_hdr)) { + missing = sizeof(*notif_hdr) - size; + goto exhausted_buffer; + } + notif_hdr = itr; + if (size < notif_hdr->bLength) + goto exhausted_buffer; + itr += notif_hdr->bLength; + size -= notif_hdr->bLength; + /* Dispatch the notification [don't use itr or size!] */ + switch (notif_hdr->bNotifyType) { + case HWA_NOTIF_DN: { + struct hwa_notif_dn *hwa_dn; + hwa_dn = container_of(notif_hdr, struct hwa_notif_dn, + hdr); + wusbhc_handle_dn(wa->wusb, hwa_dn->bSourceDeviceAddr, + hwa_dn->dndata, + notif_hdr->bLength - sizeof(*hwa_dn)); + break; + } + case WA_NOTIF_TRANSFER: + wa_handle_notif_xfer(wa, notif_hdr); + break; + case DWA_NOTIF_RWAKE: + case DWA_NOTIF_PORTSTATUS: + case HWA_NOTIF_BPST_ADJ: + /* FIXME: unimplemented WA NOTIFs */ + /* fallthru */ + default: + dev_err(dev, "HWA: unknown notification 0x%x, " + "%zu bytes; discarding\n", + notif_hdr->bNotifyType, + (size_t)notif_hdr->bLength); + break; + } + } +out: + wa_put(wa); + kfree(nw); + return; + + /* THIS SHOULD NOT HAPPEN + * + * Buffer exahusted with partial data remaining; just warn and + * discard the data, as this should not happen. + */ +exhausted_buffer: + dev_warn(dev, "HWA: device sent short notification, " + "%d bytes missing; discarding %d bytes.\n", + missing, (int)size); + goto out; +} + +/* + * Deliver incoming WA notifications to the wusbwa workqueue + * + * @wa: Pointer the Wire Adapter Controller Data Streaming + * instance (part of an 'struct usb_hcd'). + * @size: Size of the received buffer + * @returns 0 if ok, < 0 errno code on error. + * + * The input buffer is @wa->nep_buffer, with @size bytes + * (guaranteed to fit in the allocated space, + * @wa->nep_buffer_size). + */ +static int wa_nep_queue(struct wahc *wa, size_t size) +{ + int result = 0; + struct device *dev = &wa->usb_iface->dev; + struct wa_notif_work *nw; + + /* dev_fnstart(dev, "(wa %p, size %zu)\n", wa, size); */ + BUG_ON(size > wa->nep_buffer_size); + if (size == 0) + goto out; + if (atomic_read(&wa->notifs_queued) > 200) { + if (printk_ratelimit()) + dev_err(dev, "Too many notifications queued, " + "throttling back\n"); + goto out; + } + nw = kzalloc(sizeof(*nw) + size, GFP_ATOMIC); + if (nw == NULL) { + if (printk_ratelimit()) + dev_err(dev, "No memory to queue notification\n"); + goto out; + } + INIT_WORK(&nw->work, wa_notif_dispatch); + nw->wa = wa_get(wa); + nw->size = size; + memcpy(nw->data, wa->nep_buffer, size); + atomic_inc(&wa->notifs_queued); /* Throttling ctl */ + queue_work(wusbd, &nw->work); +out: + /* dev_fnend(dev, "(wa %p, size %zu) = result\n", wa, size, result); */ + return result; +} + +/* + * Callback for the notification event endpoint + * + * Check's that everything is fine and then passes the data to be + * queued to the workqueue. + */ +static void wa_nep_cb(struct urb *urb) +{ + int result; + struct wahc *wa = urb->context; + struct device *dev = &wa->usb_iface->dev; + + switch (result = urb->status) { + case 0: + result = wa_nep_queue(wa, urb->actual_length); + if (result < 0) + dev_err(dev, "NEP: unable to process notification(s): " + "%d\n", result); + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: + dev_dbg(dev, "NEP: going down %d\n", urb->status); + goto out; + default: /* On general errors, we retry unless it gets ugly */ + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "NEP: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + goto out; + } + dev_err(dev, "NEP: URB error %d\n", urb->status); + } + result = wa_nep_arm(wa, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "NEP: cannot submit URB: %d\n", result); + wa_reset_all(wa); + } +out: + return; +} + +/* + * Initialize @wa's notification and event's endpoint stuff + * + * This includes the allocating the read buffer, the context ID + * allocation bitmap, the URB and submitting the URB. + */ +int wa_nep_create(struct wahc *wa, struct usb_interface *iface) +{ + int result; + struct usb_endpoint_descriptor *epd; + struct usb_device *usb_dev = interface_to_usbdev(iface); + struct device *dev = &iface->dev; + + edc_init(&wa->nep_edc); + epd = &iface->cur_altsetting->endpoint[0].desc; + wa->nep_buffer_size = 1024; + wa->nep_buffer = kmalloc(wa->nep_buffer_size, GFP_KERNEL); + if (wa->nep_buffer == NULL) { + dev_err(dev, "Unable to allocate notification's read buffer\n"); + goto error_nep_buffer; + } + wa->nep_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->nep_urb == NULL) { + dev_err(dev, "Unable to allocate notification URB\n"); + goto error_urb_alloc; + } + usb_fill_int_urb(wa->nep_urb, usb_dev, + usb_rcvintpipe(usb_dev, epd->bEndpointAddress), + wa->nep_buffer, wa->nep_buffer_size, + wa_nep_cb, wa, epd->bInterval); + result = wa_nep_arm(wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "Cannot submit notification URB: %d\n", result); + goto error_nep_arm; + } + return 0; + +error_nep_arm: + usb_free_urb(wa->nep_urb); +error_urb_alloc: + kfree(wa->nep_buffer); +error_nep_buffer: + return -ENOMEM; +} + +void wa_nep_destroy(struct wahc *wa) +{ + wa_nep_disarm(wa); + usb_free_urb(wa->nep_urb); + kfree(wa->nep_buffer); +} diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c new file mode 100644 index 00000000..f0d546c5 --- /dev/null +++ b/drivers/usb/wusbcore/wa-rpipe.c @@ -0,0 +1,530 @@ +/* + * WUSB Wire Adapter + * rpipe management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * RPIPE + * + * Targeted at different downstream endpoints + * + * Descriptor: use to config the remote pipe. + * + * The number of blocks could be dynamic (wBlocks in descriptor is + * 0)--need to schedule them then. + * + * Each bit in wa->rpipe_bm represents if an rpipe is being used or + * not. Rpipes are represented with a 'struct wa_rpipe' that is + * attached to the hcpriv member of a 'struct usb_host_endpoint'. + * + * When you need to xfer data to an endpoint, you get an rpipe for it + * with wa_ep_rpipe_get(), which gives you a reference to the rpipe + * and keeps a single one (the first one) with the endpoint. When you + * are done transferring, you drop that reference. At the end the + * rpipe is always allocated and bound to the endpoint. There it might + * be recycled when not used. + * + * Addresses: + * + * We use a 1:1 mapping mechanism between port address (0 based + * index, actually) and the address. The USB stack knows about this. + * + * USB Stack port number 4 (1 based) + * WUSB code port index 3 (0 based) + * USB Address 5 (2 based -- 0 is for default, 1 for root hub) + * + * Now, because we don't use the concept as default address exactly + * like the (wired) USB code does, we need to kind of skip it. So we + * never take addresses from the urb->pipe, but from the + * urb->dev->devnum, to make sure that we always have the right + * destination address. + */ +#include +#include +#include +#include +#include + +#include "wusbhc.h" +#include "wa-hc.h" + +static int __rpipe_get_descr(struct wahc *wa, + struct usb_rpipe_descriptor *descr, u16 index) +{ + ssize_t result; + struct device *dev = &wa->usb_iface->dev; + + /* Get the RPIPE descriptor -- we cannot use the usb_get_descriptor() + * function because the arguments are different. + */ + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DT_RPIPE<<8, index, descr, sizeof(*descr), + 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "rpipe %u: get descriptor failed: %d\n", + index, (int)result); + goto error; + } + if (result < sizeof(*descr)) { + dev_err(dev, "rpipe %u: got short descriptor " + "(%zd vs %zd bytes needed)\n", + index, result, sizeof(*descr)); + result = -EINVAL; + goto error; + } + result = 0; + +error: + return result; +} + +/* + * + * The descriptor is assumed to be properly initialized (ie: you got + * it through __rpipe_get_descr()). + */ +static int __rpipe_set_descr(struct wahc *wa, + struct usb_rpipe_descriptor *descr, u16 index) +{ + ssize_t result; + struct device *dev = &wa->usb_iface->dev; + + /* we cannot use the usb_get_descriptor() function because the + * arguments are different. + */ + result = usb_control_msg( + wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DT_RPIPE<<8, index, descr, sizeof(*descr), + HZ / 10); + if (result < 0) { + dev_err(dev, "rpipe %u: set descriptor failed: %d\n", + index, (int)result); + goto error; + } + if (result < sizeof(*descr)) { + dev_err(dev, "rpipe %u: sent short descriptor " + "(%zd vs %zd bytes required)\n", + index, result, sizeof(*descr)); + result = -EINVAL; + goto error; + } + result = 0; + +error: + return result; + +} + +static void rpipe_init(struct wa_rpipe *rpipe) +{ + kref_init(&rpipe->refcnt); + spin_lock_init(&rpipe->seg_lock); + INIT_LIST_HEAD(&rpipe->seg_list); +} + +static unsigned rpipe_get_idx(struct wahc *wa, unsigned rpipe_idx) +{ + unsigned long flags; + + spin_lock_irqsave(&wa->rpipe_bm_lock, flags); + rpipe_idx = find_next_zero_bit(wa->rpipe_bm, wa->rpipes, rpipe_idx); + if (rpipe_idx < wa->rpipes) + set_bit(rpipe_idx, wa->rpipe_bm); + spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags); + + return rpipe_idx; +} + +static void rpipe_put_idx(struct wahc *wa, unsigned rpipe_idx) +{ + unsigned long flags; + + spin_lock_irqsave(&wa->rpipe_bm_lock, flags); + clear_bit(rpipe_idx, wa->rpipe_bm); + spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags); +} + +void rpipe_destroy(struct kref *_rpipe) +{ + struct wa_rpipe *rpipe = container_of(_rpipe, struct wa_rpipe, refcnt); + u8 index = le16_to_cpu(rpipe->descr.wRPipeIndex); + + if (rpipe->ep) + rpipe->ep->hcpriv = NULL; + rpipe_put_idx(rpipe->wa, index); + wa_put(rpipe->wa); + kfree(rpipe); +} +EXPORT_SYMBOL_GPL(rpipe_destroy); + +/* + * Locate an idle rpipe, create an structure for it and return it + * + * @wa is referenced and unlocked + * @crs enum rpipe_attr, required endpoint characteristics + * + * The rpipe can be used only sequentially (not in parallel). + * + * The rpipe is moved into the "ready" state. + */ +static int rpipe_get_idle(struct wa_rpipe **prpipe, struct wahc *wa, u8 crs, + gfp_t gfp) +{ + int result; + unsigned rpipe_idx; + struct wa_rpipe *rpipe; + struct device *dev = &wa->usb_iface->dev; + + rpipe = kzalloc(sizeof(*rpipe), gfp); + if (rpipe == NULL) + return -ENOMEM; + rpipe_init(rpipe); + + /* Look for an idle pipe */ + for (rpipe_idx = 0; rpipe_idx < wa->rpipes; rpipe_idx++) { + rpipe_idx = rpipe_get_idx(wa, rpipe_idx); + if (rpipe_idx >= wa->rpipes) /* no more pipes :( */ + break; + result = __rpipe_get_descr(wa, &rpipe->descr, rpipe_idx); + if (result < 0) + dev_err(dev, "Can't get descriptor for rpipe %u: %d\n", + rpipe_idx, result); + else if ((rpipe->descr.bmCharacteristics & crs) != 0) + goto found; + rpipe_put_idx(wa, rpipe_idx); + } + *prpipe = NULL; + kfree(rpipe); + return -ENXIO; + +found: + set_bit(rpipe_idx, wa->rpipe_bm); + rpipe->wa = wa_get(wa); + *prpipe = rpipe; + return 0; +} + +static int __rpipe_reset(struct wahc *wa, unsigned index) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + + result = usb_control_msg( + wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_RPIPE_RESET, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "rpipe %u: reset failed: %d\n", + index, result); + return result; +} + +/* + * Fake companion descriptor for ep0 + * + * See WUSB1.0[7.4.4], most of this is zero for bulk/int/ctl + */ +static struct usb_wireless_ep_comp_descriptor epc0 = { + .bLength = sizeof(epc0), + .bDescriptorType = USB_DT_WIRELESS_ENDPOINT_COMP, +/* .bMaxBurst = 1, */ + .bMaxSequence = 31, +}; + +/* + * Look for EP companion descriptor + * + * Get there, look for Inara in the endpoint's extra descriptors + */ +static struct usb_wireless_ep_comp_descriptor *rpipe_epc_find( + struct device *dev, struct usb_host_endpoint *ep) +{ + void *itr; + size_t itr_size; + struct usb_descriptor_header *hdr; + struct usb_wireless_ep_comp_descriptor *epcd; + + if (ep->desc.bEndpointAddress == 0) { + epcd = &epc0; + goto out; + } + itr = ep->extra; + itr_size = ep->extralen; + epcd = NULL; + while (itr_size > 0) { + if (itr_size < sizeof(*hdr)) { + dev_err(dev, "HW Bug? ep 0x%02x: extra descriptors " + "at offset %zu: only %zu bytes left\n", + ep->desc.bEndpointAddress, + itr - (void *) ep->extra, itr_size); + break; + } + hdr = itr; + if (hdr->bDescriptorType == USB_DT_WIRELESS_ENDPOINT_COMP) { + epcd = itr; + break; + } + if (hdr->bLength > itr_size) { + dev_err(dev, "HW Bug? ep 0x%02x: extra descriptor " + "at offset %zu (type 0x%02x) " + "length %d but only %zu bytes left\n", + ep->desc.bEndpointAddress, + itr - (void *) ep->extra, hdr->bDescriptorType, + hdr->bLength, itr_size); + break; + } + itr += hdr->bLength; + itr_size -= hdr->bDescriptorType; + } +out: + return epcd; +} + +/* + * Aim an rpipe to its device & endpoint destination + * + * Make sure we change the address to unauthenticathed if the device + * is WUSB and it is not authenticated. + */ +static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa, + struct usb_host_endpoint *ep, struct urb *urb, gfp_t gfp) +{ + int result = -ENOMSG; /* better code for lack of companion? */ + struct device *dev = &wa->usb_iface->dev; + struct usb_device *usb_dev = urb->dev; + struct usb_wireless_ep_comp_descriptor *epcd; + u8 unauth; + + epcd = rpipe_epc_find(dev, ep); + if (epcd == NULL) { + dev_err(dev, "ep 0x%02x: can't find companion descriptor\n", + ep->desc.bEndpointAddress); + goto error; + } + unauth = usb_dev->wusb && !usb_dev->authenticated ? 0x80 : 0; + __rpipe_reset(wa, le16_to_cpu(rpipe->descr.wRPipeIndex)); + atomic_set(&rpipe->segs_available, le16_to_cpu(rpipe->descr.wRequests)); + /* FIXME: block allocation system; request with queuing and timeout */ + /* FIXME: compute so seg_size > ep->maxpktsize */ + rpipe->descr.wBlocks = cpu_to_le16(16); /* given */ + /* ep0 maxpktsize is 0x200 (WUSB1.0[4.8.1]) */ + rpipe->descr.wMaxPacketSize = cpu_to_le16(ep->desc.wMaxPacketSize); + rpipe->descr.bHSHubAddress = 0; /* reserved: zero */ + rpipe->descr.bHSHubPort = wusb_port_no_to_idx(urb->dev->portnum); + /* FIXME: use maximum speed as supported or recommended by device */ + rpipe->descr.bSpeed = usb_pipeendpoint(urb->pipe) == 0 ? + UWB_PHY_RATE_53 : UWB_PHY_RATE_200; + + dev_dbg(dev, "addr %u (0x%02x) rpipe #%u ep# %u speed %d\n", + urb->dev->devnum, urb->dev->devnum | unauth, + le16_to_cpu(rpipe->descr.wRPipeIndex), + usb_pipeendpoint(urb->pipe), rpipe->descr.bSpeed); + + /* see security.c:wusb_update_address() */ + if (unlikely(urb->dev->devnum == 0x80)) + rpipe->descr.bDeviceAddress = 0; + else + rpipe->descr.bDeviceAddress = urb->dev->devnum | unauth; + rpipe->descr.bEndpointAddress = ep->desc.bEndpointAddress; + /* FIXME: bDataSequence */ + rpipe->descr.bDataSequence = 0; + /* FIXME: dwCurrentWindow */ + rpipe->descr.dwCurrentWindow = cpu_to_le32(1); + /* FIXME: bMaxDataSequence */ + rpipe->descr.bMaxDataSequence = epcd->bMaxSequence - 1; + rpipe->descr.bInterval = ep->desc.bInterval; + /* FIXME: bOverTheAirInterval */ + rpipe->descr.bOverTheAirInterval = 0; /* 0 if not isoc */ + /* FIXME: xmit power & preamble blah blah */ + rpipe->descr.bmAttribute = ep->desc.bmAttributes & 0x03; + /* rpipe->descr.bmCharacteristics RO */ + /* FIXME: bmRetryOptions */ + rpipe->descr.bmRetryOptions = 15; + /* FIXME: use for assessing link quality? */ + rpipe->descr.wNumTransactionErrors = 0; + result = __rpipe_set_descr(wa, &rpipe->descr, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + if (result < 0) { + dev_err(dev, "Cannot aim rpipe: %d\n", result); + goto error; + } + result = 0; +error: + return result; +} + +/* + * Check an aimed rpipe to make sure it points to where we want + * + * We use bit 19 of the Linux USB pipe bitmap for unauth vs auth + * space; when it is like that, we or 0x80 to make an unauth address. + */ +static int rpipe_check_aim(const struct wa_rpipe *rpipe, const struct wahc *wa, + const struct usb_host_endpoint *ep, + const struct urb *urb, gfp_t gfp) +{ + int result = 0; /* better code for lack of companion? */ + struct device *dev = &wa->usb_iface->dev; + struct usb_device *usb_dev = urb->dev; + u8 unauth = (usb_dev->wusb && !usb_dev->authenticated) ? 0x80 : 0; + u8 portnum = wusb_port_no_to_idx(urb->dev->portnum); + +#define AIM_CHECK(rdf, val, text) \ + do { \ + if (rpipe->descr.rdf != (val)) { \ + dev_err(dev, \ + "rpipe aim discrepancy: " #rdf " " text "\n", \ + rpipe->descr.rdf, (val)); \ + result = -EINVAL; \ + WARN_ON(1); \ + } \ + } while (0) + AIM_CHECK(wMaxPacketSize, cpu_to_le16(ep->desc.wMaxPacketSize), + "(%u vs %u)"); + AIM_CHECK(bHSHubPort, portnum, "(%u vs %u)"); + AIM_CHECK(bSpeed, usb_pipeendpoint(urb->pipe) == 0 ? + UWB_PHY_RATE_53 : UWB_PHY_RATE_200, + "(%u vs %u)"); + AIM_CHECK(bDeviceAddress, urb->dev->devnum | unauth, "(%u vs %u)"); + AIM_CHECK(bEndpointAddress, ep->desc.bEndpointAddress, "(%u vs %u)"); + AIM_CHECK(bInterval, ep->desc.bInterval, "(%u vs %u)"); + AIM_CHECK(bmAttribute, ep->desc.bmAttributes & 0x03, "(%u vs %u)"); +#undef AIM_CHECK + return result; +} + +#ifndef CONFIG_BUG +#define CONFIG_BUG 0 +#endif + +/* + * Make sure there is an rpipe allocated for an endpoint + * + * If already allocated, we just refcount it; if not, we get an + * idle one, aim it to the right location and take it. + * + * Attaches to ep->hcpriv and rpipe->ep to ep. + */ +int rpipe_get_by_ep(struct wahc *wa, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t gfp) +{ + int result = 0; + struct device *dev = &wa->usb_iface->dev; + struct wa_rpipe *rpipe; + u8 eptype; + + mutex_lock(&wa->rpipe_mutex); + rpipe = ep->hcpriv; + if (rpipe != NULL) { + if (CONFIG_BUG == 1) { + result = rpipe_check_aim(rpipe, wa, ep, urb, gfp); + if (result < 0) + goto error; + } + __rpipe_get(rpipe); + dev_dbg(dev, "ep 0x%02x: reusing rpipe %u\n", + ep->desc.bEndpointAddress, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + } else { + /* hmm, assign idle rpipe, aim it */ + result = -ENOBUFS; + eptype = ep->desc.bmAttributes & 0x03; + result = rpipe_get_idle(&rpipe, wa, 1 << eptype, gfp); + if (result < 0) + goto error; + result = rpipe_aim(rpipe, wa, ep, urb, gfp); + if (result < 0) { + rpipe_put(rpipe); + goto error; + } + ep->hcpriv = rpipe; + rpipe->ep = ep; + __rpipe_get(rpipe); /* for caching into ep->hcpriv */ + dev_dbg(dev, "ep 0x%02x: using rpipe %u\n", + ep->desc.bEndpointAddress, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + } +error: + mutex_unlock(&wa->rpipe_mutex); + return result; +} + +/* + * Allocate the bitmap for each rpipe. + */ +int wa_rpipes_create(struct wahc *wa) +{ + wa->rpipes = wa->wa_descr->wNumRPipes; + wa->rpipe_bm = kzalloc(BITS_TO_LONGS(wa->rpipes)*sizeof(unsigned long), + GFP_KERNEL); + if (wa->rpipe_bm == NULL) + return -ENOMEM; + return 0; +} + +void wa_rpipes_destroy(struct wahc *wa) +{ + struct device *dev = &wa->usb_iface->dev; + + if (!bitmap_empty(wa->rpipe_bm, wa->rpipes)) { + char buf[256]; + WARN_ON(1); + bitmap_scnprintf(buf, sizeof(buf), wa->rpipe_bm, wa->rpipes); + dev_err(dev, "BUG: pipes not released on exit: %s\n", buf); + } + kfree(wa->rpipe_bm); +} + +/* + * Release resources allocated for an endpoint + * + * If there is an associated rpipe to this endpoint, Abort any pending + * transfers and put it. If the rpipe ends up being destroyed, + * __rpipe_destroy() will cleanup ep->hcpriv. + * + * This is called before calling hcd->stop(), so you don't need to do + * anything else in there. + */ +void rpipe_ep_disable(struct wahc *wa, struct usb_host_endpoint *ep) +{ + struct wa_rpipe *rpipe; + + mutex_lock(&wa->rpipe_mutex); + rpipe = ep->hcpriv; + if (rpipe != NULL) { + u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex); + + usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_RPIPE_ABORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); + rpipe_put(rpipe); + } + mutex_unlock(&wa->rpipe_mutex); +} +EXPORT_SYMBOL_GPL(rpipe_ep_disable); diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c new file mode 100644 index 00000000..028fc833 --- /dev/null +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -0,0 +1,1621 @@ +/* + * WUSB Wire Adapter + * Data transfer and URB enqueing + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * How transfers work: get a buffer, break it up in segments (segment + * size is a multiple of the maxpacket size). For each segment issue a + * segment request (struct wa_xfer_*), then send the data buffer if + * out or nothing if in (all over the DTO endpoint). + * + * For each submitted segment request, a notification will come over + * the NEP endpoint and a transfer result (struct xfer_result) will + * arrive in the DTI URB. Read it, get the xfer ID, see if there is + * data coming (inbound transfer), schedule a read and handle it. + * + * Sounds simple, it is a pain to implement. + * + * + * ENTRY POINTS + * + * FIXME + * + * LIFE CYCLE / STATE DIAGRAM + * + * FIXME + * + * THIS CODE IS DISGUSTING + * + * Warned you are; it's my second try and still not happy with it. + * + * NOTES: + * + * - No iso + * + * - Supports DMA xfers, control, bulk and maybe interrupt + * + * - Does not recycle unused rpipes + * + * An rpipe is assigned to an endpoint the first time it is used, + * and then it's there, assigned, until the endpoint is disabled + * (destroyed [{h,d}wahc_op_ep_disable()]. The assignment of the + * rpipe to the endpoint is done under the wa->rpipe_sem semaphore + * (should be a mutex). + * + * Two methods it could be done: + * + * (a) set up a timer every time an rpipe's use count drops to 1 + * (which means unused) or when a transfer ends. Reset the + * timer when a xfer is queued. If the timer expires, release + * the rpipe [see rpipe_ep_disable()]. + * + * (b) when looking for free rpipes to attach [rpipe_get_by_ep()], + * when none are found go over the list, check their endpoint + * and their activity record (if no last-xfer-done-ts in the + * last x seconds) take it + * + * However, due to the fact that we have a set of limited + * resources (max-segments-at-the-same-time per xfer, + * xfers-per-ripe, blocks-per-rpipe, rpipes-per-host), at the end + * we are going to have to rebuild all this based on an scheduler, + * to where we have a list of transactions to do and based on the + * availability of the different required components (blocks, + * rpipes, segment slots, etc), we go scheduling them. Painful. + */ +#include +#include +#include +#include +#include +#include + +#include "wa-hc.h" +#include "wusbhc.h" + +enum { + WA_SEGS_MAX = 255, +}; + +enum wa_seg_status { + WA_SEG_NOTREADY, + WA_SEG_READY, + WA_SEG_DELAYED, + WA_SEG_SUBMITTED, + WA_SEG_PENDING, + WA_SEG_DTI_PENDING, + WA_SEG_DONE, + WA_SEG_ERROR, + WA_SEG_ABORTED, +}; + +static void wa_xfer_delayed_run(struct wa_rpipe *); + +/* + * Life cycle governed by 'struct urb' (the refcount of the struct is + * that of the 'struct urb' and usb_free_urb() would free the whole + * struct). + */ +struct wa_seg { + struct urb urb; + struct urb *dto_urb; /* for data output? */ + struct list_head list_node; /* for rpipe->req_list */ + struct wa_xfer *xfer; /* out xfer */ + u8 index; /* which segment we are */ + enum wa_seg_status status; + ssize_t result; /* bytes xfered or error */ + struct wa_xfer_hdr xfer_hdr; + u8 xfer_extra[]; /* xtra space for xfer_hdr_ctl */ +}; + +static void wa_seg_init(struct wa_seg *seg) +{ + /* usb_init_urb() repeats a lot of work, so we do it here */ + kref_init(&seg->urb.kref); +} + +/* + * Protected by xfer->lock + * + */ +struct wa_xfer { + struct kref refcnt; + struct list_head list_node; + spinlock_t lock; + u32 id; + + struct wahc *wa; /* Wire adapter we are plugged to */ + struct usb_host_endpoint *ep; + struct urb *urb; /* URB we are transferring for */ + struct wa_seg **seg; /* transfer segments */ + u8 segs, segs_submitted, segs_done; + unsigned is_inbound:1; + unsigned is_dma:1; + size_t seg_size; + int result; + + gfp_t gfp; /* allocation mask */ + + struct wusb_dev *wusb_dev; /* for activity timestamps */ +}; + +static inline void wa_xfer_init(struct wa_xfer *xfer) +{ + kref_init(&xfer->refcnt); + INIT_LIST_HEAD(&xfer->list_node); + spin_lock_init(&xfer->lock); +} + +/* + * Destroy a transfer structure + * + * Note that the xfer->seg[index] thingies follow the URB life cycle, + * so we need to put them, not free them. + */ +static void wa_xfer_destroy(struct kref *_xfer) +{ + struct wa_xfer *xfer = container_of(_xfer, struct wa_xfer, refcnt); + if (xfer->seg) { + unsigned cnt; + for (cnt = 0; cnt < xfer->segs; cnt++) { + if (xfer->is_inbound) + usb_put_urb(xfer->seg[cnt]->dto_urb); + usb_put_urb(&xfer->seg[cnt]->urb); + } + } + kfree(xfer); +} + +static void wa_xfer_get(struct wa_xfer *xfer) +{ + kref_get(&xfer->refcnt); +} + +static void wa_xfer_put(struct wa_xfer *xfer) +{ + kref_put(&xfer->refcnt, wa_xfer_destroy); +} + +/* + * xfer is referenced + * + * xfer->lock has to be unlocked + * + * We take xfer->lock for setting the result; this is a barrier + * against drivers/usb/core/hcd.c:unlink1() being called after we call + * usb_hcd_giveback_urb() and wa_urb_dequeue() trying to get a + * reference to the transfer. + */ +static void wa_xfer_giveback(struct wa_xfer *xfer) +{ + unsigned long flags; + + spin_lock_irqsave(&xfer->wa->xfer_list_lock, flags); + list_del_init(&xfer->list_node); + spin_unlock_irqrestore(&xfer->wa->xfer_list_lock, flags); + /* FIXME: segmentation broken -- kills DWA */ + wusbhc_giveback_urb(xfer->wa->wusb, xfer->urb, xfer->result); + wa_put(xfer->wa); + wa_xfer_put(xfer); +} + +/* + * xfer is referenced + * + * xfer->lock has to be unlocked + */ +static void wa_xfer_completion(struct wa_xfer *xfer) +{ + if (xfer->wusb_dev) + wusb_dev_put(xfer->wusb_dev); + rpipe_put(xfer->ep->hcpriv); + wa_xfer_giveback(xfer); +} + +/* + * If transfer is done, wrap it up and return true + * + * xfer->lock has to be locked + */ +static unsigned __wa_xfer_is_done(struct wa_xfer *xfer) +{ + struct device *dev = &xfer->wa->usb_iface->dev; + unsigned result, cnt; + struct wa_seg *seg; + struct urb *urb = xfer->urb; + unsigned found_short = 0; + + result = xfer->segs_done == xfer->segs_submitted; + if (result == 0) + goto out; + urb->actual_length = 0; + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt]; + switch (seg->status) { + case WA_SEG_DONE: + if (found_short && seg->result > 0) { + dev_dbg(dev, "xfer %p#%u: bad short segments (%zu)\n", + xfer, cnt, seg->result); + urb->status = -EINVAL; + goto out; + } + urb->actual_length += seg->result; + if (seg->result < xfer->seg_size + && cnt != xfer->segs-1) + found_short = 1; + dev_dbg(dev, "xfer %p#%u: DONE short %d " + "result %zu urb->actual_length %d\n", + xfer, seg->index, found_short, seg->result, + urb->actual_length); + break; + case WA_SEG_ERROR: + xfer->result = seg->result; + dev_dbg(dev, "xfer %p#%u: ERROR result %zu\n", + xfer, seg->index, seg->result); + goto out; + case WA_SEG_ABORTED: + dev_dbg(dev, "xfer %p#%u ABORTED: result %d\n", + xfer, seg->index, urb->status); + xfer->result = urb->status; + goto out; + default: + dev_warn(dev, "xfer %p#%u: is_done bad state %d\n", + xfer, cnt, seg->status); + xfer->result = -EINVAL; + goto out; + } + } + xfer->result = 0; +out: + return result; +} + +/* + * Initialize a transfer's ID + * + * We need to use a sequential number; if we use the pointer or the + * hash of the pointer, it can repeat over sequential transfers and + * then it will confuse the HWA....wonder why in hell they put a 32 + * bit handle in there then. + */ +static void wa_xfer_id_init(struct wa_xfer *xfer) +{ + xfer->id = atomic_add_return(1, &xfer->wa->xfer_id_count); +} + +/* + * Return the xfer's ID associated with xfer + * + * Need to generate a + */ +static u32 wa_xfer_id(struct wa_xfer *xfer) +{ + return xfer->id; +} + +/* + * Search for a transfer list ID on the HCD's URB list + * + * For 32 bit architectures, we use the pointer itself; for 64 bits, a + * 32-bit hash of the pointer. + * + * @returns NULL if not found. + */ +static struct wa_xfer *wa_xfer_get_by_id(struct wahc *wa, u32 id) +{ + unsigned long flags; + struct wa_xfer *xfer_itr; + spin_lock_irqsave(&wa->xfer_list_lock, flags); + list_for_each_entry(xfer_itr, &wa->xfer_list, list_node) { + if (id == xfer_itr->id) { + wa_xfer_get(xfer_itr); + goto out; + } + } + xfer_itr = NULL; +out: + spin_unlock_irqrestore(&wa->xfer_list_lock, flags); + return xfer_itr; +} + +struct wa_xfer_abort_buffer { + struct urb urb; + struct wa_xfer_abort cmd; +}; + +static void __wa_xfer_abort_cb(struct urb *urb) +{ + struct wa_xfer_abort_buffer *b = urb->context; + usb_put_urb(&b->urb); +} + +/* + * Aborts an ongoing transaction + * + * Assumes the transfer is referenced and locked and in a submitted + * state (mainly that there is an endpoint/rpipe assigned). + * + * The callback (see above) does nothing but freeing up the data by + * putting the URB. Because the URB is allocated at the head of the + * struct, the whole space we allocated is kfreed. + * + * We'll get an 'aborted transaction' xfer result on DTI, that'll + * politely ignore because at this point the transaction has been + * marked as aborted already. + */ +static void __wa_xfer_abort(struct wa_xfer *xfer) +{ + int result; + struct device *dev = &xfer->wa->usb_iface->dev; + struct wa_xfer_abort_buffer *b; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + b = kmalloc(sizeof(*b), GFP_ATOMIC); + if (b == NULL) + goto error_kmalloc; + b->cmd.bLength = sizeof(b->cmd); + b->cmd.bRequestType = WA_XFER_ABORT; + b->cmd.wRPipe = rpipe->descr.wRPipeIndex; + b->cmd.dwTransferID = wa_xfer_id(xfer); + + usb_init_urb(&b->urb); + usb_fill_bulk_urb(&b->urb, xfer->wa->usb_dev, + usb_sndbulkpipe(xfer->wa->usb_dev, + xfer->wa->dto_epd->bEndpointAddress), + &b->cmd, sizeof(b->cmd), __wa_xfer_abort_cb, b); + result = usb_submit_urb(&b->urb, GFP_ATOMIC); + if (result < 0) + goto error_submit; + return; /* callback frees! */ + + +error_submit: + if (printk_ratelimit()) + dev_err(dev, "xfer %p: Can't submit abort request: %d\n", + xfer, result); + kfree(b); +error_kmalloc: + return; + +} + +/* + * + * @returns < 0 on error, transfer segment request size if ok + */ +static ssize_t __wa_xfer_setup_sizes(struct wa_xfer *xfer, + enum wa_xfer_type *pxfer_type) +{ + ssize_t result; + struct device *dev = &xfer->wa->usb_iface->dev; + size_t maxpktsize; + struct urb *urb = xfer->urb; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + switch (rpipe->descr.bmAttribute & 0x3) { + case USB_ENDPOINT_XFER_CONTROL: + *pxfer_type = WA_XFER_TYPE_CTL; + result = sizeof(struct wa_xfer_ctl); + break; + case USB_ENDPOINT_XFER_INT: + case USB_ENDPOINT_XFER_BULK: + *pxfer_type = WA_XFER_TYPE_BI; + result = sizeof(struct wa_xfer_bi); + break; + case USB_ENDPOINT_XFER_ISOC: + dev_err(dev, "FIXME: ISOC not implemented\n"); + result = -ENOSYS; + goto error; + default: + /* never happens */ + BUG(); + result = -EINVAL; /* shut gcc up */ + }; + xfer->is_inbound = urb->pipe & USB_DIR_IN ? 1 : 0; + xfer->is_dma = urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP ? 1 : 0; + xfer->seg_size = le16_to_cpu(rpipe->descr.wBlocks) + * 1 << (xfer->wa->wa_descr->bRPipeBlockSize - 1); + /* Compute the segment size and make sure it is a multiple of + * the maxpktsize (WUSB1.0[8.3.3.1])...not really too much of + * a check (FIXME) */ + maxpktsize = le16_to_cpu(rpipe->descr.wMaxPacketSize); + if (xfer->seg_size < maxpktsize) { + dev_err(dev, "HW BUG? seg_size %zu smaller than maxpktsize " + "%zu\n", xfer->seg_size, maxpktsize); + result = -EINVAL; + goto error; + } + xfer->seg_size = (xfer->seg_size / maxpktsize) * maxpktsize; + xfer->segs = (urb->transfer_buffer_length + xfer->seg_size - 1) + / xfer->seg_size; + if (xfer->segs >= WA_SEGS_MAX) { + dev_err(dev, "BUG? ops, number of segments %d bigger than %d\n", + (int)(urb->transfer_buffer_length / xfer->seg_size), + WA_SEGS_MAX); + result = -EINVAL; + goto error; + } + if (xfer->segs == 0 && *pxfer_type == WA_XFER_TYPE_CTL) + xfer->segs = 1; +error: + return result; +} + +/* Fill in the common request header and xfer-type specific data. */ +static void __wa_xfer_setup_hdr0(struct wa_xfer *xfer, + struct wa_xfer_hdr *xfer_hdr0, + enum wa_xfer_type xfer_type, + size_t xfer_hdr_size) +{ + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + xfer_hdr0 = &xfer->seg[0]->xfer_hdr; + xfer_hdr0->bLength = xfer_hdr_size; + xfer_hdr0->bRequestType = xfer_type; + xfer_hdr0->wRPipe = rpipe->descr.wRPipeIndex; + xfer_hdr0->dwTransferID = wa_xfer_id(xfer); + xfer_hdr0->bTransferSegment = 0; + switch (xfer_type) { + case WA_XFER_TYPE_CTL: { + struct wa_xfer_ctl *xfer_ctl = + container_of(xfer_hdr0, struct wa_xfer_ctl, hdr); + xfer_ctl->bmAttribute = xfer->is_inbound ? 1 : 0; + memcpy(&xfer_ctl->baSetupData, xfer->urb->setup_packet, + sizeof(xfer_ctl->baSetupData)); + break; + } + case WA_XFER_TYPE_BI: + break; + case WA_XFER_TYPE_ISO: + printk(KERN_ERR "FIXME: ISOC not implemented\n"); + default: + BUG(); + }; +} + +/* + * Callback for the OUT data phase of the segment request + * + * Check wa_seg_cb(); most comments also apply here because this + * function does almost the same thing and they work closely + * together. + * + * If the seg request has failed but this DTO phase has succeeded, + * wa_seg_cb() has already failed the segment and moved the + * status to WA_SEG_ERROR, so this will go through 'case 0' and + * effectively do nothing. + */ +static void wa_seg_dto_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned long flags; + unsigned rpipe_ready = 0; + u8 done = 0; + + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + dev_dbg(dev, "xfer %p#%u: data out done (%d bytes)\n", + xfer, seg->index, urb->actual_length); + if (seg->status < WA_SEG_PENDING) + seg->status = WA_SEG_PENDING; + seg->result = urb->actual_length; + spin_unlock_irqrestore(&xfer->lock, flags); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + dev_dbg(dev, "xfer %p#%u: data out error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + if (seg->status != WA_SEG_ERROR) { + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + __wa_xfer_abort(xfer); + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + } + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } +} + +/* + * Callback for the segment request + * + * If successful transition state (unless already transitioned or + * outbound transfer); otherwise, take a note of the error, mark this + * segment done and try completion. + * + * Note we don't access until we are sure that the transfer hasn't + * been cancelled (ECONNRESET, ENOENT), which could mean that + * seg->xfer could be already gone. + * + * We have to check before setting the status to WA_SEG_PENDING + * because sometimes the xfer result callback arrives before this + * callback (geeeeeeze), so it might happen that we are already in + * another state. As well, we don't set it if the transfer is inbound, + * as in that case, wa_seg_dto_cb will do it when the OUT data phase + * finishes. + */ +static void wa_seg_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned long flags; + unsigned rpipe_ready; + u8 done = 0; + + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + dev_dbg(dev, "xfer %p#%u: request done\n", xfer, seg->index); + if (xfer->is_inbound && seg->status < WA_SEG_PENDING) + seg->status = WA_SEG_PENDING; + spin_unlock_irqrestore(&xfer->lock, flags); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: request error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + usb_unlink_urb(seg->dto_urb); + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + __wa_xfer_abort(xfer); + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } +} + +/* + * Allocate the segs array and initialize each of them + * + * The segments are freed by wa_xfer_destroy() when the xfer use count + * drops to zero; however, because each segment is given the same life + * cycle as the USB URB it contains, it is actually freed by + * usb_put_urb() on the contained USB URB (twisted, eh?). + */ +static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size) +{ + int result, cnt; + size_t alloc_size = sizeof(*xfer->seg[0]) + - sizeof(xfer->seg[0]->xfer_hdr) + xfer_hdr_size; + struct usb_device *usb_dev = xfer->wa->usb_dev; + const struct usb_endpoint_descriptor *dto_epd = xfer->wa->dto_epd; + struct wa_seg *seg; + size_t buf_itr, buf_size, buf_itr_size; + + result = -ENOMEM; + xfer->seg = kcalloc(xfer->segs, sizeof(xfer->seg[0]), GFP_ATOMIC); + if (xfer->seg == NULL) + goto error_segs_kzalloc; + buf_itr = 0; + buf_size = xfer->urb->transfer_buffer_length; + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt] = kzalloc(alloc_size, GFP_ATOMIC); + if (seg == NULL) + goto error_seg_kzalloc; + wa_seg_init(seg); + seg->xfer = xfer; + seg->index = cnt; + usb_fill_bulk_urb(&seg->urb, usb_dev, + usb_sndbulkpipe(usb_dev, + dto_epd->bEndpointAddress), + &seg->xfer_hdr, xfer_hdr_size, + wa_seg_cb, seg); + buf_itr_size = buf_size > xfer->seg_size ? + xfer->seg_size : buf_size; + if (xfer->is_inbound == 0 && buf_size > 0) { + seg->dto_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (seg->dto_urb == NULL) + goto error_dto_alloc; + usb_fill_bulk_urb( + seg->dto_urb, usb_dev, + usb_sndbulkpipe(usb_dev, + dto_epd->bEndpointAddress), + NULL, 0, wa_seg_dto_cb, seg); + if (xfer->is_dma) { + seg->dto_urb->transfer_dma = + xfer->urb->transfer_dma + buf_itr; + seg->dto_urb->transfer_flags |= + URB_NO_TRANSFER_DMA_MAP; + } else + seg->dto_urb->transfer_buffer = + xfer->urb->transfer_buffer + buf_itr; + seg->dto_urb->transfer_buffer_length = buf_itr_size; + } + seg->status = WA_SEG_READY; + buf_itr += buf_itr_size; + buf_size -= buf_itr_size; + } + return 0; + +error_dto_alloc: + kfree(xfer->seg[cnt]); + cnt--; +error_seg_kzalloc: + /* use the fact that cnt is left at were it failed */ + for (; cnt >= 0; cnt--) { + if (xfer->seg[cnt] && xfer->is_inbound == 0) + usb_free_urb(xfer->seg[cnt]->dto_urb); + kfree(xfer->seg[cnt]); + } +error_segs_kzalloc: + return result; +} + +/* + * Allocates all the stuff needed to submit a transfer + * + * Breaks the whole data buffer in a list of segments, each one has a + * structure allocated to it and linked in xfer->seg[index] + * + * FIXME: merge setup_segs() and the last part of this function, no + * need to do two for loops when we could run everything in a + * single one + */ +static int __wa_xfer_setup(struct wa_xfer *xfer, struct urb *urb) +{ + int result; + struct device *dev = &xfer->wa->usb_iface->dev; + enum wa_xfer_type xfer_type = 0; /* shut up GCC */ + size_t xfer_hdr_size, cnt, transfer_size; + struct wa_xfer_hdr *xfer_hdr0, *xfer_hdr; + + result = __wa_xfer_setup_sizes(xfer, &xfer_type); + if (result < 0) + goto error_setup_sizes; + xfer_hdr_size = result; + result = __wa_xfer_setup_segs(xfer, xfer_hdr_size); + if (result < 0) { + dev_err(dev, "xfer %p: Failed to allocate %d segments: %d\n", + xfer, xfer->segs, result); + goto error_setup_segs; + } + /* Fill the first header */ + xfer_hdr0 = &xfer->seg[0]->xfer_hdr; + wa_xfer_id_init(xfer); + __wa_xfer_setup_hdr0(xfer, xfer_hdr0, xfer_type, xfer_hdr_size); + + /* Fill remainig headers */ + xfer_hdr = xfer_hdr0; + transfer_size = urb->transfer_buffer_length; + xfer_hdr0->dwTransferLength = transfer_size > xfer->seg_size ? + xfer->seg_size : transfer_size; + transfer_size -= xfer->seg_size; + for (cnt = 1; cnt < xfer->segs; cnt++) { + xfer_hdr = &xfer->seg[cnt]->xfer_hdr; + memcpy(xfer_hdr, xfer_hdr0, xfer_hdr_size); + xfer_hdr->bTransferSegment = cnt; + xfer_hdr->dwTransferLength = transfer_size > xfer->seg_size ? + cpu_to_le32(xfer->seg_size) + : cpu_to_le32(transfer_size); + xfer->seg[cnt]->status = WA_SEG_READY; + transfer_size -= xfer->seg_size; + } + xfer_hdr->bTransferSegment |= 0x80; /* this is the last segment */ + result = 0; +error_setup_segs: +error_setup_sizes: + return result; +} + +/* + * + * + * rpipe->seg_lock is held! + */ +static int __wa_seg_submit(struct wa_rpipe *rpipe, struct wa_xfer *xfer, + struct wa_seg *seg) +{ + int result; + result = usb_submit_urb(&seg->urb, GFP_ATOMIC); + if (result < 0) { + printk(KERN_ERR "xfer %p#%u: REQ submit failed: %d\n", + xfer, seg->index, result); + goto error_seg_submit; + } + if (seg->dto_urb) { + result = usb_submit_urb(seg->dto_urb, GFP_ATOMIC); + if (result < 0) { + printk(KERN_ERR "xfer %p#%u: DTO submit failed: %d\n", + xfer, seg->index, result); + goto error_dto_submit; + } + } + seg->status = WA_SEG_SUBMITTED; + rpipe_avail_dec(rpipe); + return 0; + +error_dto_submit: + usb_unlink_urb(&seg->urb); +error_seg_submit: + seg->status = WA_SEG_ERROR; + seg->result = result; + return result; +} + +/* + * Execute more queued request segments until the maximum concurrent allowed + * + * The ugly unlock/lock sequence on the error path is needed as the + * xfer->lock normally nests the seg_lock and not viceversa. + * + */ +static void wa_xfer_delayed_run(struct wa_rpipe *rpipe) +{ + int result; + struct device *dev = &rpipe->wa->usb_iface->dev; + struct wa_seg *seg; + struct wa_xfer *xfer; + unsigned long flags; + + spin_lock_irqsave(&rpipe->seg_lock, flags); + while (atomic_read(&rpipe->segs_available) > 0 + && !list_empty(&rpipe->seg_list)) { + seg = list_entry(rpipe->seg_list.next, struct wa_seg, + list_node); + list_del(&seg->list_node); + xfer = seg->xfer; + result = __wa_seg_submit(rpipe, xfer, seg); + dev_dbg(dev, "xfer %p#%u submitted from delayed [%d segments available] %d\n", + xfer, seg->index, atomic_read(&rpipe->segs_available), result); + if (unlikely(result < 0)) { + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + spin_lock_irqsave(&xfer->lock, flags); + __wa_xfer_abort(xfer); + xfer->segs_done++; + spin_unlock_irqrestore(&xfer->lock, flags); + spin_lock_irqsave(&rpipe->seg_lock, flags); + } + } + spin_unlock_irqrestore(&rpipe->seg_lock, flags); +} + +/* + * + * xfer->lock is taken + * + * On failure submitting we just stop submitting and return error; + * wa_urb_enqueue_b() will execute the completion path + */ +static int __wa_xfer_submit(struct wa_xfer *xfer) +{ + int result; + struct wahc *wa = xfer->wa; + struct device *dev = &wa->usb_iface->dev; + unsigned cnt; + struct wa_seg *seg; + unsigned long flags; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + size_t maxrequests = le16_to_cpu(rpipe->descr.wRequests); + u8 available; + u8 empty; + + spin_lock_irqsave(&wa->xfer_list_lock, flags); + list_add_tail(&xfer->list_node, &wa->xfer_list); + spin_unlock_irqrestore(&wa->xfer_list_lock, flags); + + BUG_ON(atomic_read(&rpipe->segs_available) > maxrequests); + result = 0; + spin_lock_irqsave(&rpipe->seg_lock, flags); + for (cnt = 0; cnt < xfer->segs; cnt++) { + available = atomic_read(&rpipe->segs_available); + empty = list_empty(&rpipe->seg_list); + seg = xfer->seg[cnt]; + dev_dbg(dev, "xfer %p#%u: available %u empty %u (%s)\n", + xfer, cnt, available, empty, + available == 0 || !empty ? "delayed" : "submitted"); + if (available == 0 || !empty) { + dev_dbg(dev, "xfer %p#%u: delayed\n", xfer, cnt); + seg->status = WA_SEG_DELAYED; + list_add_tail(&seg->list_node, &rpipe->seg_list); + } else { + result = __wa_seg_submit(rpipe, xfer, seg); + if (result < 0) { + __wa_xfer_abort(xfer); + goto error_seg_submit; + } + } + xfer->segs_submitted++; + } +error_seg_submit: + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + return result; +} + +/* + * Second part of a URB/transfer enqueuement + * + * Assumes this comes from wa_urb_enqueue() [maybe through + * wa_urb_enqueue_run()]. At this point: + * + * xfer->wa filled and refcounted + * xfer->ep filled with rpipe refcounted if + * delayed == 0 + * xfer->urb filled and refcounted (this is the case when called + * from wa_urb_enqueue() as we come from usb_submit_urb() + * and when called by wa_urb_enqueue_run(), as we took an + * extra ref dropped by _run() after we return). + * xfer->gfp filled + * + * If we fail at __wa_xfer_submit(), then we just check if we are done + * and if so, we run the completion procedure. However, if we are not + * yet done, we do nothing and wait for the completion handlers from + * the submitted URBs or from the xfer-result path to kick in. If xfer + * result never kicks in, the xfer will timeout from the USB code and + * dequeue() will be called. + */ +static void wa_urb_enqueue_b(struct wa_xfer *xfer) +{ + int result; + unsigned long flags; + struct urb *urb = xfer->urb; + struct wahc *wa = xfer->wa; + struct wusbhc *wusbhc = wa->wusb; + struct wusb_dev *wusb_dev; + unsigned done; + + result = rpipe_get_by_ep(wa, xfer->ep, urb, xfer->gfp); + if (result < 0) + goto error_rpipe_get; + result = -ENODEV; + /* FIXME: segmentation broken -- kills DWA */ + mutex_lock(&wusbhc->mutex); /* get a WUSB dev */ + if (urb->dev == NULL) { + mutex_unlock(&wusbhc->mutex); + goto error_dev_gone; + } + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); + if (wusb_dev == NULL) { + mutex_unlock(&wusbhc->mutex); + goto error_dev_gone; + } + mutex_unlock(&wusbhc->mutex); + + spin_lock_irqsave(&xfer->lock, flags); + xfer->wusb_dev = wusb_dev; + result = urb->status; + if (urb->status != -EINPROGRESS) + goto error_dequeued; + + result = __wa_xfer_setup(xfer, urb); + if (result < 0) + goto error_xfer_setup; + result = __wa_xfer_submit(xfer); + if (result < 0) + goto error_xfer_submit; + spin_unlock_irqrestore(&xfer->lock, flags); + return; + + /* this is basically wa_xfer_completion() broken up wa_xfer_giveback() + * does a wa_xfer_put() that will call wa_xfer_destroy() and clean + * upundo setup(). + */ +error_xfer_setup: +error_dequeued: + spin_unlock_irqrestore(&xfer->lock, flags); + /* FIXME: segmentation broken, kills DWA */ + if (wusb_dev) + wusb_dev_put(wusb_dev); +error_dev_gone: + rpipe_put(xfer->ep->hcpriv); +error_rpipe_get: + xfer->result = result; + wa_xfer_giveback(xfer); + return; + +error_xfer_submit: + done = __wa_xfer_is_done(xfer); + xfer->result = result; + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); +} + +/* + * Execute the delayed transfers in the Wire Adapter @wa + * + * We need to be careful here, as dequeue() could be called in the + * middle. That's why we do the whole thing under the + * wa->xfer_list_lock. If dequeue() jumps in, it first locks urb->lock + * and then checks the list -- so as we would be acquiring in inverse + * order, we just drop the lock once we have the xfer and reacquire it + * later. + */ +void wa_urb_enqueue_run(struct work_struct *ws) +{ + struct wahc *wa = container_of(ws, struct wahc, xfer_work); + struct wa_xfer *xfer, *next; + struct urb *urb; + + spin_lock_irq(&wa->xfer_list_lock); + list_for_each_entry_safe(xfer, next, &wa->xfer_delayed_list, + list_node) { + list_del_init(&xfer->list_node); + spin_unlock_irq(&wa->xfer_list_lock); + + urb = xfer->urb; + wa_urb_enqueue_b(xfer); + usb_put_urb(urb); /* taken when queuing */ + + spin_lock_irq(&wa->xfer_list_lock); + } + spin_unlock_irq(&wa->xfer_list_lock); +} +EXPORT_SYMBOL_GPL(wa_urb_enqueue_run); + +/* + * Submit a transfer to the Wire Adapter in a delayed way + * + * The process of enqueuing involves possible sleeps() [see + * enqueue_b(), for the rpipe_get() and the mutex_lock()]. If we are + * in an atomic section, we defer the enqueue_b() call--else we call direct. + * + * @urb: We own a reference to it done by the HCI Linux USB stack that + * will be given up by calling usb_hcd_giveback_urb() or by + * returning error from this function -> ergo we don't have to + * refcount it. + */ +int wa_urb_enqueue(struct wahc *wa, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t gfp) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + struct wa_xfer *xfer; + unsigned long my_flags; + unsigned cant_sleep = irqs_disabled() | in_atomic(); + + if (urb->transfer_buffer == NULL + && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + && urb->transfer_buffer_length != 0) { + dev_err(dev, "BUG? urb %p: NULL xfer buffer & NODMA\n", urb); + dump_stack(); + } + + result = -ENOMEM; + xfer = kzalloc(sizeof(*xfer), gfp); + if (xfer == NULL) + goto error_kmalloc; + + result = -ENOENT; + if (urb->status != -EINPROGRESS) /* cancelled */ + goto error_dequeued; /* before starting? */ + wa_xfer_init(xfer); + xfer->wa = wa_get(wa); + xfer->urb = urb; + xfer->gfp = gfp; + xfer->ep = ep; + urb->hcpriv = xfer; + + dev_dbg(dev, "xfer %p urb %p pipe 0x%02x [%d bytes] %s %s %s\n", + xfer, urb, urb->pipe, urb->transfer_buffer_length, + urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP ? "dma" : "nodma", + urb->pipe & USB_DIR_IN ? "inbound" : "outbound", + cant_sleep ? "deferred" : "inline"); + + if (cant_sleep) { + usb_get_urb(urb); + spin_lock_irqsave(&wa->xfer_list_lock, my_flags); + list_add_tail(&xfer->list_node, &wa->xfer_delayed_list); + spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags); + queue_work(wusbd, &wa->xfer_work); + } else { + wa_urb_enqueue_b(xfer); + } + return 0; + +error_dequeued: + kfree(xfer); +error_kmalloc: + return result; +} +EXPORT_SYMBOL_GPL(wa_urb_enqueue); + +/* + * Dequeue a URB and make sure uwb_hcd_giveback_urb() [completion + * handler] is called. + * + * Until a transfer goes successfully through wa_urb_enqueue() it + * needs to be dequeued with completion calling; when stuck in delayed + * or before wa_xfer_setup() is called, we need to do completion. + * + * not setup If there is no hcpriv yet, that means that that enqueue + * still had no time to set the xfer up. Because + * urb->status should be other than -EINPROGRESS, + * enqueue() will catch that and bail out. + * + * If the transfer has gone through setup, we just need to clean it + * up. If it has gone through submit(), we have to abort it [with an + * asynch request] and then make sure we cancel each segment. + * + */ +int wa_urb_dequeue(struct wahc *wa, struct urb *urb) +{ + unsigned long flags, flags2; + struct wa_xfer *xfer; + struct wa_seg *seg; + struct wa_rpipe *rpipe; + unsigned cnt; + unsigned rpipe_ready = 0; + + xfer = urb->hcpriv; + if (xfer == NULL) { + /* NOthing setup yet enqueue will see urb->status != + * -EINPROGRESS (by hcd layer) and bail out with + * error, no need to do completion + */ + BUG_ON(urb->status == -EINPROGRESS); + goto out; + } + spin_lock_irqsave(&xfer->lock, flags); + rpipe = xfer->ep->hcpriv; + if (rpipe == NULL) { + pr_debug("%s: xfer id 0x%08X has no RPIPE. %s", + __func__, wa_xfer_id(xfer), + "Probably already aborted.\n" ); + goto out_unlock; + } + /* Check the delayed list -> if there, release and complete */ + spin_lock_irqsave(&wa->xfer_list_lock, flags2); + if (!list_empty(&xfer->list_node) && xfer->seg == NULL) + goto dequeue_delayed; + spin_unlock_irqrestore(&wa->xfer_list_lock, flags2); + if (xfer->seg == NULL) /* still hasn't reached */ + goto out_unlock; /* setup(), enqueue_b() completes */ + /* Ok, the xfer is in flight already, it's been setup and submitted.*/ + __wa_xfer_abort(xfer); + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt]; + switch (seg->status) { + case WA_SEG_NOTREADY: + case WA_SEG_READY: + printk(KERN_ERR "xfer %p#%u: dequeue bad state %u\n", + xfer, cnt, seg->status); + WARN_ON(1); + break; + case WA_SEG_DELAYED: + seg->status = WA_SEG_ABORTED; + spin_lock_irqsave(&rpipe->seg_lock, flags2); + list_del(&seg->list_node); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + spin_unlock_irqrestore(&rpipe->seg_lock, flags2); + break; + case WA_SEG_SUBMITTED: + seg->status = WA_SEG_ABORTED; + usb_unlink_urb(&seg->urb); + if (xfer->is_inbound == 0) + usb_unlink_urb(seg->dto_urb); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_PENDING: + seg->status = WA_SEG_ABORTED; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_DTI_PENDING: + usb_unlink_urb(wa->dti_urb); + seg->status = WA_SEG_ABORTED; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_DONE: + case WA_SEG_ERROR: + case WA_SEG_ABORTED: + break; + } + } + xfer->result = urb->status; /* -ENOENT or -ECONNRESET */ + __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + return 0; + +out_unlock: + spin_unlock_irqrestore(&xfer->lock, flags); +out: + return 0; + +dequeue_delayed: + list_del_init(&xfer->list_node); + spin_unlock_irqrestore(&wa->xfer_list_lock, flags2); + xfer->result = urb->status; + spin_unlock_irqrestore(&xfer->lock, flags); + wa_xfer_giveback(xfer); + usb_put_urb(urb); /* we got a ref in enqueue() */ + return 0; +} +EXPORT_SYMBOL_GPL(wa_urb_dequeue); + +/* + * Translation from WA status codes (WUSB1.0 Table 8.15) to errno + * codes + * + * Positive errno values are internal inconsistencies and should be + * flagged louder. Negative are to be passed up to the user in the + * normal way. + * + * @status: USB WA status code -- high two bits are stripped. + */ +static int wa_xfer_status_to_errno(u8 status) +{ + int errno; + u8 real_status = status; + static int xlat[] = { + [WA_XFER_STATUS_SUCCESS] = 0, + [WA_XFER_STATUS_HALTED] = -EPIPE, + [WA_XFER_STATUS_DATA_BUFFER_ERROR] = -ENOBUFS, + [WA_XFER_STATUS_BABBLE] = -EOVERFLOW, + [WA_XFER_RESERVED] = EINVAL, + [WA_XFER_STATUS_NOT_FOUND] = 0, + [WA_XFER_STATUS_INSUFFICIENT_RESOURCE] = -ENOMEM, + [WA_XFER_STATUS_TRANSACTION_ERROR] = -EILSEQ, + [WA_XFER_STATUS_ABORTED] = -EINTR, + [WA_XFER_STATUS_RPIPE_NOT_READY] = EINVAL, + [WA_XFER_INVALID_FORMAT] = EINVAL, + [WA_XFER_UNEXPECTED_SEGMENT_NUMBER] = EINVAL, + [WA_XFER_STATUS_RPIPE_TYPE_MISMATCH] = EINVAL, + }; + status &= 0x3f; + + if (status == 0) + return 0; + if (status >= ARRAY_SIZE(xlat)) { + printk_ratelimited(KERN_ERR "%s(): BUG? " + "Unknown WA transfer status 0x%02x\n", + __func__, real_status); + return -EINVAL; + } + errno = xlat[status]; + if (unlikely(errno > 0)) { + printk_ratelimited(KERN_ERR "%s(): BUG? " + "Inconsistent WA status: 0x%02x\n", + __func__, real_status); + errno = -errno; + } + return errno; +} + +/* + * Process a xfer result completion message + * + * inbound transfers: need to schedule a DTI read + * + * FIXME: this functio needs to be broken up in parts + */ +static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + unsigned long flags; + u8 seg_idx; + struct wa_seg *seg; + struct wa_rpipe *rpipe; + struct wa_xfer_result *xfer_result = wa->xfer_result; + u8 done = 0; + u8 usb_status; + unsigned rpipe_ready = 0; + + spin_lock_irqsave(&xfer->lock, flags); + seg_idx = xfer_result->bTransferSegment & 0x7f; + if (unlikely(seg_idx >= xfer->segs)) + goto error_bad_seg; + seg = xfer->seg[seg_idx]; + rpipe = xfer->ep->hcpriv; + usb_status = xfer_result->bTransferStatus; + dev_dbg(dev, "xfer %p#%u: bTransferStatus 0x%02x (seg %u)\n", + xfer, seg_idx, usb_status, seg->status); + if (seg->status == WA_SEG_ABORTED + || seg->status == WA_SEG_ERROR) /* already handled */ + goto segment_aborted; + if (seg->status == WA_SEG_SUBMITTED) /* ops, got here */ + seg->status = WA_SEG_PENDING; /* before wa_seg{_dto}_cb() */ + if (seg->status != WA_SEG_PENDING) { + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: Bad segment state %u\n", + xfer, seg_idx, seg->status); + seg->status = WA_SEG_PENDING; /* workaround/"fix" it */ + } + if (usb_status & 0x80) { + seg->result = wa_xfer_status_to_errno(usb_status); + dev_err(dev, "DTI: xfer %p#%u failed (0x%02x)\n", + xfer, seg->index, usb_status); + goto error_complete; + } + /* FIXME: we ignore warnings, tally them for stats */ + if (usb_status & 0x40) /* Warning?... */ + usb_status = 0; /* ... pass */ + if (xfer->is_inbound) { /* IN data phase: read to buffer */ + seg->status = WA_SEG_DTI_PENDING; + BUG_ON(wa->buf_in_urb->status == -EINPROGRESS); + if (xfer->is_dma) { + wa->buf_in_urb->transfer_dma = + xfer->urb->transfer_dma + + seg_idx * xfer->seg_size; + wa->buf_in_urb->transfer_flags + |= URB_NO_TRANSFER_DMA_MAP; + } else { + wa->buf_in_urb->transfer_buffer = + xfer->urb->transfer_buffer + + seg_idx * xfer->seg_size; + wa->buf_in_urb->transfer_flags + &= ~URB_NO_TRANSFER_DMA_MAP; + } + wa->buf_in_urb->transfer_buffer_length = + le32_to_cpu(xfer_result->dwTransferLength); + wa->buf_in_urb->context = seg; + result = usb_submit_urb(wa->buf_in_urb, GFP_ATOMIC); + if (result < 0) + goto error_submit_buf_in; + } else { + /* OUT data phase, complete it -- */ + seg->status = WA_SEG_DONE; + seg->result = le32_to_cpu(xfer_result->dwTransferLength); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + } + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + return; + +error_submit_buf_in: + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: can't submit DTI data phase: %d\n", + xfer, seg_idx, result); + seg->result = result; +error_complete: + seg->status = WA_SEG_ERROR; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + __wa_xfer_abort(xfer); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + return; + +error_bad_seg: + spin_unlock_irqrestore(&xfer->lock, flags); + wa_urb_dequeue(wa, xfer->urb); + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: bad segment\n", xfer, seg_idx); + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + return; + +segment_aborted: + /* nothing to do, as the aborter did the completion */ + spin_unlock_irqrestore(&xfer->lock, flags); +} + +/* + * Callback for the IN data phase + * + * If successful transition state; otherwise, take a note of the + * error, mark this segment done and try completion. + * + * Note we don't access until we are sure that the transfer hasn't + * been cancelled (ECONNRESET, ENOENT), which could mean that + * seg->xfer could be already gone. + */ +static void wa_buf_in_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned rpipe_ready; + unsigned long flags; + u8 done = 0; + + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + dev_dbg(dev, "xfer %p#%u: data in done (%zu bytes)\n", + xfer, seg->index, (size_t)urb->actual_length); + seg->status = WA_SEG_DONE; + seg->result = urb->actual_length; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: data in error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + __wa_xfer_abort(xfer); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } +} + +/* + * Handle an incoming transfer result buffer + * + * Given a transfer result buffer, it completes the transfer (possibly + * scheduling and buffer in read) and then resubmits the DTI URB for a + * new transfer result read. + * + * + * The xfer_result DTI URB state machine + * + * States: OFF | RXR (Read-Xfer-Result) | RBI (Read-Buffer-In) + * + * We start in OFF mode, the first xfer_result notification [through + * wa_handle_notif_xfer()] moves us to RXR by posting the DTI-URB to + * read. + * + * We receive a buffer -- if it is not a xfer_result, we complain and + * repost the DTI-URB. If it is a xfer_result then do the xfer seg + * request accounting. If it is an IN segment, we move to RBI and post + * a BUF-IN-URB to the right buffer. The BUF-IN-URB callback will + * repost the DTI-URB and move to RXR state. if there was no IN + * segment, it will repost the DTI-URB. + * + * We go back to OFF when we detect a ENOENT or ESHUTDOWN (or too many + * errors) in the URBs. + */ +static void wa_xfer_result_cb(struct urb *urb) +{ + int result; + struct wahc *wa = urb->context; + struct device *dev = &wa->usb_iface->dev; + struct wa_xfer_result *xfer_result; + u32 xfer_id; + struct wa_xfer *xfer; + u8 usb_status; + + BUG_ON(wa->dti_urb != urb); + switch (wa->dti_urb->status) { + case 0: + /* We have a xfer result buffer; check it */ + dev_dbg(dev, "DTI: xfer result %d bytes at %p\n", + urb->actual_length, urb->transfer_buffer); + if (wa->dti_urb->actual_length != sizeof(*xfer_result)) { + dev_err(dev, "DTI Error: xfer result--bad size " + "xfer result (%d bytes vs %zu needed)\n", + urb->actual_length, sizeof(*xfer_result)); + break; + } + xfer_result = wa->xfer_result; + if (xfer_result->hdr.bLength != sizeof(*xfer_result)) { + dev_err(dev, "DTI Error: xfer result--" + "bad header length %u\n", + xfer_result->hdr.bLength); + break; + } + if (xfer_result->hdr.bNotifyType != WA_XFER_RESULT) { + dev_err(dev, "DTI Error: xfer result--" + "bad header type 0x%02x\n", + xfer_result->hdr.bNotifyType); + break; + } + usb_status = xfer_result->bTransferStatus & 0x3f; + if (usb_status == WA_XFER_STATUS_NOT_FOUND) + /* taken care of already */ + break; + xfer_id = xfer_result->dwTransferID; + xfer = wa_xfer_get_by_id(wa, xfer_id); + if (xfer == NULL) { + /* FIXME: transaction might have been cancelled */ + dev_err(dev, "DTI Error: xfer result--" + "unknown xfer 0x%08x (status 0x%02x)\n", + xfer_id, usb_status); + break; + } + wa_xfer_result_chew(wa, xfer); + wa_xfer_put(xfer); + break; + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: /* going away! */ + dev_dbg(dev, "DTI: going down! %d\n", urb->status); + goto out; + default: + /* Unknown error */ + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + goto out; + } + if (printk_ratelimit()) + dev_err(dev, "DTI: URB error %d\n", urb->status); + break; + } + /* Resubmit the DTI URB */ + result = usb_submit_urb(wa->dti_urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "DTI Error: Could not submit DTI URB (%d), " + "resetting\n", result); + wa_reset_all(wa); + } +out: + return; +} + +/* + * Transfer complete notification + * + * Called from the notif.c code. We get a notification on EP2 saying + * that some endpoint has some transfer result data available. We are + * about to read it. + * + * To speed up things, we always have a URB reading the DTI URB; we + * don't really set it up and start it until the first xfer complete + * notification arrives, which is what we do here. + * + * Follow up in wa_xfer_result_cb(), as that's where the whole state + * machine starts. + * + * So here we just initialize the DTI URB for reading transfer result + * notifications and also the buffer-in URB, for reading buffers. Then + * we just submit the DTI URB. + * + * @wa shall be referenced + */ +void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + struct wa_notif_xfer *notif_xfer; + const struct usb_endpoint_descriptor *dti_epd = wa->dti_epd; + + notif_xfer = container_of(notif_hdr, struct wa_notif_xfer, hdr); + BUG_ON(notif_hdr->bNotifyType != WA_NOTIF_TRANSFER); + + if ((0x80 | notif_xfer->bEndpoint) != dti_epd->bEndpointAddress) { + /* FIXME: hardcoded limitation, adapt */ + dev_err(dev, "BUG: DTI ep is %u, not %u (hack me)\n", + notif_xfer->bEndpoint, dti_epd->bEndpointAddress); + goto error; + } + if (wa->dti_urb != NULL) /* DTI URB already started */ + goto out; + + wa->dti_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->dti_urb == NULL) { + dev_err(dev, "Can't allocate DTI URB\n"); + goto error_dti_urb_alloc; + } + usb_fill_bulk_urb( + wa->dti_urb, wa->usb_dev, + usb_rcvbulkpipe(wa->usb_dev, 0x80 | notif_xfer->bEndpoint), + wa->xfer_result, wa->xfer_result_size, + wa_xfer_result_cb, wa); + + wa->buf_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->buf_in_urb == NULL) { + dev_err(dev, "Can't allocate BUF-IN URB\n"); + goto error_buf_in_urb_alloc; + } + usb_fill_bulk_urb( + wa->buf_in_urb, wa->usb_dev, + usb_rcvbulkpipe(wa->usb_dev, 0x80 | notif_xfer->bEndpoint), + NULL, 0, wa_buf_in_cb, wa); + result = usb_submit_urb(wa->dti_urb, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "DTI Error: Could not submit DTI URB (%d), " + "resetting\n", result); + goto error_dti_urb_submit; + } +out: + return; + +error_dti_urb_submit: + usb_put_urb(wa->buf_in_urb); +error_buf_in_urb_alloc: + usb_put_urb(wa->dti_urb); + wa->dti_urb = NULL; +error_dti_urb_alloc: +error: + wa_reset_all(wa); +} diff --git a/drivers/usb/wusbcore/wusbhc.c b/drivers/usb/wusbcore/wusbhc.c new file mode 100644 index 00000000..0faca16d --- /dev/null +++ b/drivers/usb/wusbcore/wusbhc.c @@ -0,0 +1,450 @@ +/* + * Wireless USB Host Controller + * sysfs glue, wusbcore module support and life cycle management + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Creation/destruction of wusbhc is split in two parts; that that + * doesn't require the HCD to be added (wusbhc_{create,destroy}) and + * the one that requires (phase B, wusbhc_b_{create,destroy}). + * + * This is so because usb_add_hcd() will start the HC, and thus, all + * the HC specific stuff has to be already initialized (like sysfs + * thingies). + */ +#include +#include +#include "wusbhc.h" + +/** + * Extract the wusbhc that corresponds to a USB Host Controller class device + * + * WARNING! Apply only if @dev is that of a + * wusbhc.usb_hcd.self->class_dev; otherwise, you loose. + */ +static struct wusbhc *usbhc_dev_to_wusbhc(struct device *dev) +{ + struct usb_bus *usb_bus = dev_get_drvdata(dev); + struct usb_hcd *usb_hcd = bus_to_hcd(usb_bus); + return usb_hcd_to_wusbhc(usb_hcd); +} + +/* + * Show & store the current WUSB trust timeout + * + * We don't do locking--it is an 'atomic' value. + * + * The units that we store/show are always MILLISECONDS. However, the + * value of trust_timeout is jiffies. + */ +static ssize_t wusb_trust_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", wusbhc->trust_timeout); +} + +static ssize_t wusb_trust_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + ssize_t result = -ENOSYS; + unsigned trust_timeout; + + result = sscanf(buf, "%u", &trust_timeout); + if (result != 1) { + result = -EINVAL; + goto out; + } + /* FIXME: maybe we should check for range validity? */ + wusbhc->trust_timeout = trust_timeout; + cancel_delayed_work(&wusbhc->keep_alive_timer); + flush_workqueue(wusbd); + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (trust_timeout * CONFIG_HZ)/1000/2); +out: + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_trust_timeout, 0644, wusb_trust_timeout_show, + wusb_trust_timeout_store); + +/* + * Show the current WUSB CHID. + */ +static ssize_t wusb_chid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + const struct wusb_ckhdid *chid; + ssize_t result = 0; + + if (wusbhc->wuie_host_info != NULL) + chid = &wusbhc->wuie_host_info->CHID; + else + chid = &wusb_ckhdid_zero; + + result += ckhdid_printf(buf, PAGE_SIZE, chid); + result += sprintf(buf + result, "\n"); + + return result; +} + +/* + * Store a new CHID. + * + * - Write an all zeros CHID and it will stop the controller + * - Write a non-zero CHID and it will start it. + * + * See wusbhc_chid_set() for more info. + */ +static ssize_t wusb_chid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + struct wusb_ckhdid chid; + ssize_t result; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &chid.data[0] , &chid.data[1] , + &chid.data[2] , &chid.data[3] , + &chid.data[4] , &chid.data[5] , + &chid.data[6] , &chid.data[7] , + &chid.data[8] , &chid.data[9] , + &chid.data[10], &chid.data[11], + &chid.data[12], &chid.data[13], + &chid.data[14], &chid.data[15]); + if (result != 16) { + dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits): " + "%d\n", (int)result); + return -EINVAL; + } + result = wusbhc_chid_set(wusbhc, &chid); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_chid, 0644, wusb_chid_show, wusb_chid_store); + + +static ssize_t wusb_phy_rate_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + + return sprintf(buf, "%d\n", wusbhc->phy_rate); +} + +static ssize_t wusb_phy_rate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + uint8_t phy_rate; + ssize_t result; + + result = sscanf(buf, "%hhu", &phy_rate); + if (result != 1) + return -EINVAL; + if (phy_rate >= UWB_PHY_RATE_INVALID) + return -EINVAL; + + wusbhc->phy_rate = phy_rate; + return size; +} +static DEVICE_ATTR(wusb_phy_rate, 0644, wusb_phy_rate_show, wusb_phy_rate_store); + +/* Group all the WUSBHC attributes */ +static struct attribute *wusbhc_attrs[] = { + &dev_attr_wusb_trust_timeout.attr, + &dev_attr_wusb_chid.attr, + &dev_attr_wusb_phy_rate.attr, + NULL, +}; + +static struct attribute_group wusbhc_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wusbhc_attrs, +}; + +/* + * Create a wusbhc instance + * + * NOTEs: + * + * - assumes *wusbhc has been zeroed and wusbhc->usb_hcd has been + * initialized but not added. + * + * - fill out ports_max, mmcies_max and mmcie_{add,rm} before calling. + * + * - fill out wusbhc->uwb_rc and refcount it before calling + * - fill out the wusbhc->sec_modes array + */ +int wusbhc_create(struct wusbhc *wusbhc) +{ + int result = 0; + + wusbhc->trust_timeout = WUSB_TRUST_TIMEOUT_MS; + wusbhc->phy_rate = UWB_PHY_RATE_INVALID - 1; + + mutex_init(&wusbhc->mutex); + result = wusbhc_mmcie_create(wusbhc); + if (result < 0) + goto error_mmcie_create; + result = wusbhc_devconnect_create(wusbhc); + if (result < 0) + goto error_devconnect_create; + result = wusbhc_rh_create(wusbhc); + if (result < 0) + goto error_rh_create; + result = wusbhc_sec_create(wusbhc); + if (result < 0) + goto error_sec_create; + return 0; + +error_sec_create: + wusbhc_rh_destroy(wusbhc); +error_rh_create: + wusbhc_devconnect_destroy(wusbhc); +error_devconnect_create: + wusbhc_mmcie_destroy(wusbhc); +error_mmcie_create: + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_create); + +static inline struct kobject *wusbhc_kobj(struct wusbhc *wusbhc) +{ + return &wusbhc->usb_hcd.self.controller->kobj; +} + +/* + * Phase B of a wusbhc instance creation + * + * Creates fields that depend on wusbhc->usb_hcd having been + * added. This is where we create the sysfs files in + * /sys/class/usb_host/usb_hostX/. + * + * NOTE: Assumes wusbhc->usb_hcd has been already added by the upper + * layer (hwahc or whci) + */ +int wusbhc_b_create(struct wusbhc *wusbhc) +{ + int result = 0; + struct device *dev = wusbhc->usb_hcd.self.controller; + + result = sysfs_create_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); + if (result < 0) { + dev_err(dev, "Cannot register WUSBHC attributes: %d\n", result); + goto error_create_attr_group; + } + + result = wusbhc_pal_register(wusbhc); + if (result < 0) + goto error_pal_register; + return 0; + +error_pal_register: + sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); +error_create_attr_group: + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_b_create); + +void wusbhc_b_destroy(struct wusbhc *wusbhc) +{ + wusbhc_pal_unregister(wusbhc); + sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); +} +EXPORT_SYMBOL_GPL(wusbhc_b_destroy); + +void wusbhc_destroy(struct wusbhc *wusbhc) +{ + wusbhc_sec_destroy(wusbhc); + wusbhc_rh_destroy(wusbhc); + wusbhc_devconnect_destroy(wusbhc); + wusbhc_mmcie_destroy(wusbhc); +} +EXPORT_SYMBOL_GPL(wusbhc_destroy); + +struct workqueue_struct *wusbd; +EXPORT_SYMBOL_GPL(wusbd); + +/* + * WUSB Cluster ID allocation map + * + * Each WUSB bus in a channel is identified with a Cluster Id in the + * unauth address pace (WUSB1.0[4.3]). We take the range 0xe0 to 0xff + * (that's space for 31 WUSB controllers, as 0xff can't be taken). We + * start taking from 0xff, 0xfe, 0xfd... (hence the += or -= 0xff). + * + * For each one we taken, we pin it in the bitap + */ +#define CLUSTER_IDS 32 +static DECLARE_BITMAP(wusb_cluster_id_table, CLUSTER_IDS); +static DEFINE_SPINLOCK(wusb_cluster_ids_lock); + +/* + * Get a WUSB Cluster ID + * + * Need to release with wusb_cluster_id_put() when done w/ it. + */ +/* FIXME: coordinate with the choose_addres() from the USB stack */ +/* we want to leave the top of the 128 range for cluster addresses and + * the bottom for device addresses (as we map them one on one with + * ports). */ +u8 wusb_cluster_id_get(void) +{ + u8 id; + spin_lock(&wusb_cluster_ids_lock); + id = find_first_zero_bit(wusb_cluster_id_table, CLUSTER_IDS); + if (id >= CLUSTER_IDS) { + id = 0; + goto out; + } + set_bit(id, wusb_cluster_id_table); + id = (u8) 0xff - id; +out: + spin_unlock(&wusb_cluster_ids_lock); + return id; + +} +EXPORT_SYMBOL_GPL(wusb_cluster_id_get); + +/* + * Release a WUSB Cluster ID + * + * Obtained it with wusb_cluster_id_get() + */ +void wusb_cluster_id_put(u8 id) +{ + id = 0xff - id; + BUG_ON(id >= CLUSTER_IDS); + spin_lock(&wusb_cluster_ids_lock); + WARN_ON(!test_bit(id, wusb_cluster_id_table)); + clear_bit(id, wusb_cluster_id_table); + spin_unlock(&wusb_cluster_ids_lock); +} +EXPORT_SYMBOL_GPL(wusb_cluster_id_put); + +/** + * wusbhc_giveback_urb - return an URB to the USB core + * @wusbhc: the host controller the URB is from. + * @urb: the URB. + * @status: the URB's status. + * + * Return an URB to the USB core doing some additional WUSB specific + * processing. + * + * - After a successful transfer, update the trust timeout timestamp + * for the WUSB device. + * + * - [WUSB] sections 4.13 and 7.5.1 specifies the stop retrasmittion + * condition for the WCONNECTACK_IE is that the host has observed + * the associated device responding to a control transfer. + */ +void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, int status) +{ + struct wusb_dev *wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); + + if (status == 0 && wusb_dev) { + wusb_dev->entry_ts = jiffies; + + /* wusbhc_devconnect_acked() can't be called from + atomic context so defer it to a work queue. */ + if (!list_empty(&wusb_dev->cack_node)) + queue_work(wusbd, &wusb_dev->devconnect_acked_work); + else + wusb_dev_put(wusb_dev); + } + + usb_hcd_giveback_urb(&wusbhc->usb_hcd, urb, status); +} +EXPORT_SYMBOL_GPL(wusbhc_giveback_urb); + +/** + * wusbhc_reset_all - reset the HC hardware + * @wusbhc: the host controller to reset. + * + * Request a full hardware reset of the chip. This will also reset + * the radio controller and any other PALs. + */ +void wusbhc_reset_all(struct wusbhc *wusbhc) +{ + uwb_rc_reset_all(wusbhc->uwb_rc); +} +EXPORT_SYMBOL_GPL(wusbhc_reset_all); + +static struct notifier_block wusb_usb_notifier = { + .notifier_call = wusb_usb_ncb, + .priority = INT_MAX /* Need to be called first of all */ +}; + +static int __init wusbcore_init(void) +{ + int result; + result = wusb_crypto_init(); + if (result < 0) + goto error_crypto_init; + /* WQ is singlethread because we need to serialize notifications */ + wusbd = create_singlethread_workqueue("wusbd"); + if (wusbd == NULL) { + result = -ENOMEM; + printk(KERN_ERR "WUSB-core: Cannot create wusbd workqueue\n"); + goto error_wusbd_create; + } + usb_register_notify(&wusb_usb_notifier); + bitmap_zero(wusb_cluster_id_table, CLUSTER_IDS); + set_bit(0, wusb_cluster_id_table); /* reserve Cluster ID 0xff */ + return 0; + +error_wusbd_create: + wusb_crypto_exit(); +error_crypto_init: + return result; + +} +module_init(wusbcore_init); + +static void __exit wusbcore_exit(void) +{ + clear_bit(0, wusb_cluster_id_table); + if (!bitmap_empty(wusb_cluster_id_table, CLUSTER_IDS)) { + char buf[256]; + bitmap_scnprintf(buf, sizeof(buf), wusb_cluster_id_table, + CLUSTER_IDS); + printk(KERN_ERR "BUG: WUSB Cluster IDs not released " + "on exit: %s\n", buf); + WARN_ON(1); + } + usb_unregister_notify(&wusb_usb_notifier); + destroy_workqueue(wusbd); + wusb_crypto_exit(); +} +module_exit(wusbcore_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/wusbhc.h b/drivers/usb/wusbcore/wusbhc.h new file mode 100644 index 00000000..3a2d0916 --- /dev/null +++ b/drivers/usb/wusbcore/wusbhc.h @@ -0,0 +1,496 @@ +/* + * Wireless USB Host Controller + * Common infrastructure for WHCI and HWA WUSB-HC drivers + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver implements parts common to all Wireless USB Host + * Controllers (struct wusbhc, embedding a struct usb_hcd) and is used + * by: + * + * - hwahc: HWA, USB-dongle that implements a Wireless USB host + * controller, (Wireless USB 1.0 Host-Wire-Adapter specification). + * + * - whci: WHCI, a PCI card with a wireless host controller + * (Wireless Host Controller Interface 1.0 specification). + * + * Check out the Design-overview.txt file in the source documentation + * for other details on the implementation. + * + * Main blocks: + * + * rh Root Hub emulation (part of the HCD glue) + * + * devconnect Handle all the issues related to device connection, + * authentication, disconnection, timeout, reseting, + * keepalives, etc. + * + * mmc MMC IE broadcasting handling + * + * A host controller driver just initializes its stuff and as part of + * that, creates a 'struct wusbhc' instance that handles all the + * common WUSB mechanisms. Links in the function ops that are specific + * to it and then registers the host controller. Ready to run. + */ + +#ifndef __WUSBHC_H__ +#define __WUSBHC_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Time from a WUSB channel stop request to the last transmitted MMC. + * + * This needs to be > 4.096 ms in case no MMCs can be transmitted in + * zone 0. + */ +#define WUSB_CHANNEL_STOP_DELAY_MS 8 + +/** + * Wireless USB device + * + * Describe a WUSB device connected to the cluster. This struct + * belongs to the 'struct wusb_port' it is attached to and it is + * responsible for putting and clearing the pointer to it. + * + * Note this "complements" the 'struct usb_device' that the usb_hcd + * keeps for each connected USB device. However, it extends some + * information that is not available (there is no hcpriv ptr in it!) + * *and* most importantly, it's life cycle is different. It is created + * as soon as we get a DN_Connect (connect request notification) from + * the device through the WUSB host controller; the USB stack doesn't + * create the device until we authenticate it. FIXME: this will + * change. + * + * @bos: This is allocated when the BOS descriptors are read from + * the device and freed upon the wusb_dev struct dying. + * @wusb_cap_descr: points into @bos, and has been verified to be size + * safe. + */ +struct wusb_dev { + struct kref refcnt; + struct wusbhc *wusbhc; + struct list_head cack_node; /* Connect-Ack list */ + u8 port_idx; + u8 addr; + u8 beacon_type:4; + struct usb_encryption_descriptor ccm1_etd; + struct wusb_ckhdid cdid; + unsigned long entry_ts; + struct usb_bos_descriptor *bos; + struct usb_wireless_cap_descriptor *wusb_cap_descr; + struct uwb_mas_bm availability; + struct work_struct devconnect_acked_work; + struct urb *set_gtk_urb; + struct usb_ctrlrequest *set_gtk_req; + struct usb_device *usb_dev; +}; + +#define WUSB_DEV_ADDR_UNAUTH 0x80 + +static inline void wusb_dev_init(struct wusb_dev *wusb_dev) +{ + kref_init(&wusb_dev->refcnt); + /* no need to init the cack_node */ +} + +extern void wusb_dev_destroy(struct kref *_wusb_dev); + +static inline struct wusb_dev *wusb_dev_get(struct wusb_dev *wusb_dev) +{ + kref_get(&wusb_dev->refcnt); + return wusb_dev; +} + +static inline void wusb_dev_put(struct wusb_dev *wusb_dev) +{ + kref_put(&wusb_dev->refcnt, wusb_dev_destroy); +} + +/** + * Wireless USB Host Controller root hub "fake" ports + * (state and device information) + * + * Wireless USB is wireless, so there are no ports; but we + * fake'em. Each RC can connect a max of devices at the same time + * (given in the Wireless Adapter descriptor, bNumPorts or WHCI's + * caps), referred to in wusbhc->ports_max. + * + * See rh.c for more information. + * + * The @status and @change use the same bits as in USB2.0[11.24.2.7], + * so we don't have to do much when getting the port's status. + * + * WUSB1.0[7.1], USB2.0[11.24.2.7.1,fig 11-10], + * include/linux/usb_ch9.h (#define USB_PORT_STAT_*) + */ +struct wusb_port { + u16 status; + u16 change; + struct wusb_dev *wusb_dev; /* connected device's info */ + u32 ptk_tkid; +}; + +/** + * WUSB Host Controller specifics + * + * All fields that are common to all Wireless USB controller types + * (HWA and WHCI) are grouped here. Host Controller + * functions/operations that only deal with general Wireless USB HC + * issues use this data type to refer to the host. + * + * @usb_hcd Instantiation of a USB host controller + * (initialized by upper layer [HWA=HC or WHCI]. + * + * @dev Device that implements this; initialized by the + * upper layer (HWA-HC, WHCI...); this device should + * have a refcount. + * + * @trust_timeout After this time without hearing for device + * activity, we consider the device gone and we have to + * re-authenticate. + * + * Can be accessed w/o locking--however, read to a + * local variable then use. + * + * @chid WUSB Cluster Host ID: this is supposed to be a + * unique value that doesn't change across reboots (so + * that your devices do not require re-association). + * + * Read/Write protected by @mutex + * + * @dev_info This array has ports_max elements. It is used to + * give the HC information about the WUSB devices (see + * 'struct wusb_dev_info'). + * + * For HWA we need to allocate it in heap; for WHCI it + * needs to be permanently mapped, so we keep it for + * both and make it easy. Call wusbhc->dev_info_set() + * to update an entry. + * + * @ports_max Number of simultaneous device connections (fake + * ports) this HC will take. Read-only. + * + * @port Array of port status for each fake root port. Guaranteed to + * always be the same length during device existence + * [this allows for some unlocked but referenced reading]. + * + * @mmcies_max Max number of Information Elements this HC can send + * in its MMC. Read-only. + * + * @start Start the WUSB channel. + * + * @stop Stop the WUSB channel after the specified number of + * milliseconds. Channel Stop IEs should be transmitted + * as required by [WUSB] 4.16.2.1. + * + * @mmcie_add HC specific operation (WHCI or HWA) for adding an + * MMCIE. + * + * @mmcie_rm HC specific operation (WHCI or HWA) for removing an + * MMCIE. + * + * @set_ptk: Set the PTK and enable encryption for a device. Or, if + * the supplied key is NULL, disable encryption for that + * device. + * + * @set_gtk: Set the GTK to be used for all future broadcast packets + * (i.e., MMCs). With some hardware, setting the GTK may start + * MMC transmission. + * + * NOTE: + * + * - If wusb_dev->usb_dev is not NULL, then usb_dev is valid + * (wusb_dev has a refcount on it). Likewise, if usb_dev->wusb_dev + * is not NULL, usb_dev->wusb_dev is valid (usb_dev keeps a + * refcount on it). + * + * Most of the times when you need to use it, it will be non-NULL, + * so there is no real need to check for it (wusb_dev will + * disappear before usb_dev). + * + * - The following fields need to be filled out before calling + * wusbhc_create(): ports_max, mmcies_max, mmcie_{add,rm}. + * + * - there is no wusbhc_init() method, we do everything in + * wusbhc_create(). + * + * - Creation is done in two phases, wusbhc_create() and + * wusbhc_create_b(); b are the parts that need to be called after + * calling usb_hcd_add(&wusbhc->usb_hcd). + */ +struct wusbhc { + struct usb_hcd usb_hcd; /* HAS TO BE 1st */ + struct device *dev; + struct uwb_rc *uwb_rc; + struct uwb_pal pal; + + unsigned trust_timeout; /* in jiffies */ + struct wusb_ckhdid chid; + uint8_t phy_rate; + struct wuie_host_info *wuie_host_info; + + struct mutex mutex; /* locks everything else */ + u16 cluster_id; /* Wireless USB Cluster ID */ + struct wusb_port *port; /* Fake port status handling */ + struct wusb_dev_info *dev_info; /* for Set Device Info mgmt */ + u8 ports_max; + unsigned active:1; /* currently xmit'ing MMCs */ + struct wuie_keep_alive keep_alive_ie; /* protected by mutex */ + struct delayed_work keep_alive_timer; + struct list_head cack_list; /* Connect acknowledging */ + size_t cack_count; /* protected by 'mutex' */ + struct wuie_connect_ack cack_ie; + struct uwb_rsv *rsv; /* cluster bandwidth reservation */ + + struct mutex mmcie_mutex; /* MMC WUIE handling */ + struct wuie_hdr **mmcie; /* WUIE array */ + u8 mmcies_max; + /* FIXME: make wusbhc_ops? */ + int (*start)(struct wusbhc *wusbhc); + void (*stop)(struct wusbhc *wusbhc, int delay); + int (*mmcie_add)(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie); + int (*mmcie_rm)(struct wusbhc *wusbhc, u8 handle); + int (*dev_info_set)(struct wusbhc *, struct wusb_dev *wusb_dev); + int (*bwa_set)(struct wusbhc *wusbhc, s8 stream_index, + const struct uwb_mas_bm *); + int (*set_ptk)(struct wusbhc *wusbhc, u8 port_idx, + u32 tkid, const void *key, size_t key_size); + int (*set_gtk)(struct wusbhc *wusbhc, + u32 tkid, const void *key, size_t key_size); + int (*set_num_dnts)(struct wusbhc *wusbhc, u8 interval, u8 slots); + + struct { + struct usb_key_descriptor descr; + u8 data[16]; /* GTK key data */ + } __attribute__((packed)) gtk; + u8 gtk_index; + u32 gtk_tkid; + struct work_struct gtk_rekey_done_work; + int pending_set_gtks; + + struct usb_encryption_descriptor *ccm1_etd; +}; + +#define usb_hcd_to_wusbhc(u) container_of((u), struct wusbhc, usb_hcd) + + +extern int wusbhc_create(struct wusbhc *); +extern int wusbhc_b_create(struct wusbhc *); +extern void wusbhc_b_destroy(struct wusbhc *); +extern void wusbhc_destroy(struct wusbhc *); +extern int wusb_dev_sysfs_add(struct wusbhc *, struct usb_device *, + struct wusb_dev *); +extern void wusb_dev_sysfs_rm(struct wusb_dev *); +extern int wusbhc_sec_create(struct wusbhc *); +extern int wusbhc_sec_start(struct wusbhc *); +extern void wusbhc_sec_stop(struct wusbhc *); +extern void wusbhc_sec_destroy(struct wusbhc *); +extern void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, + int status); +void wusbhc_reset_all(struct wusbhc *wusbhc); + +int wusbhc_pal_register(struct wusbhc *wusbhc); +void wusbhc_pal_unregister(struct wusbhc *wusbhc); + +/* + * Return @usb_dev's @usb_hcd (properly referenced) or NULL if gone + * + * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr) + * + * This is a safe assumption as @usb_dev->bus is referenced all the + * time during the @usb_dev life cycle. + */ +static inline struct usb_hcd *usb_hcd_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct usb_hcd *usb_hcd; + usb_hcd = container_of(usb_dev->bus, struct usb_hcd, self); + return usb_get_hcd(usb_hcd); +} + +/* + * Increment the reference count on a wusbhc. + * + * @wusbhc's life cycle is identical to that of the underlying usb_hcd. + */ +static inline struct wusbhc *wusbhc_get(struct wusbhc *wusbhc) +{ + return usb_get_hcd(&wusbhc->usb_hcd) ? wusbhc : NULL; +} + +/* + * Return the wusbhc associated to a @usb_dev + * + * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr) + * + * @returns: wusbhc for @usb_dev; NULL if the @usb_dev is being torn down. + * WARNING: referenced at the usb_hcd level, unlocked + * + * FIXME: move offline + */ +static inline struct wusbhc *wusbhc_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct wusbhc *wusbhc = NULL; + struct usb_hcd *usb_hcd; + if (usb_dev->devnum > 1 && !usb_dev->wusb) { + /* but root hubs */ + dev_err(&usb_dev->dev, "devnum %d wusb %d\n", usb_dev->devnum, + usb_dev->wusb); + BUG_ON(usb_dev->devnum > 1 && !usb_dev->wusb); + } + usb_hcd = usb_hcd_get_by_usb_dev(usb_dev); + if (usb_hcd == NULL) + return NULL; + BUG_ON(usb_hcd->wireless == 0); + return wusbhc = usb_hcd_to_wusbhc(usb_hcd); +} + + +static inline void wusbhc_put(struct wusbhc *wusbhc) +{ + usb_put_hcd(&wusbhc->usb_hcd); +} + +int wusbhc_start(struct wusbhc *wusbhc); +void wusbhc_stop(struct wusbhc *wusbhc); +extern int wusbhc_chid_set(struct wusbhc *, const struct wusb_ckhdid *); + +/* Device connect handling */ +extern int wusbhc_devconnect_create(struct wusbhc *); +extern void wusbhc_devconnect_destroy(struct wusbhc *); +extern int wusbhc_devconnect_start(struct wusbhc *wusbhc); +extern void wusbhc_devconnect_stop(struct wusbhc *wusbhc); +extern void wusbhc_handle_dn(struct wusbhc *, u8 srcaddr, + struct wusb_dn_hdr *dn_hdr, size_t size); +extern void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port); +extern int wusb_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv); +extern int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, + u8 addr); + +/* Wireless USB fake Root Hub methods */ +extern int wusbhc_rh_create(struct wusbhc *); +extern void wusbhc_rh_destroy(struct wusbhc *); + +extern int wusbhc_rh_status_data(struct usb_hcd *, char *); +extern int wusbhc_rh_control(struct usb_hcd *, u16, u16, u16, char *, u16); +extern int wusbhc_rh_suspend(struct usb_hcd *); +extern int wusbhc_rh_resume(struct usb_hcd *); +extern int wusbhc_rh_start_port_reset(struct usb_hcd *, unsigned); + +/* MMC handling */ +extern int wusbhc_mmcie_create(struct wusbhc *); +extern void wusbhc_mmcie_destroy(struct wusbhc *); +extern int wusbhc_mmcie_set(struct wusbhc *, u8 interval, u8 repeat_cnt, + struct wuie_hdr *); +extern void wusbhc_mmcie_rm(struct wusbhc *, struct wuie_hdr *); + +/* Bandwidth reservation */ +int wusbhc_rsv_establish(struct wusbhc *wusbhc); +void wusbhc_rsv_terminate(struct wusbhc *wusbhc); + +/* + * I've always said + * I wanted a wedding in a church... + * + * but lately I've been thinking about + * the Botanical Gardens. + * + * We could do it by the tulips. + * It'll be beautiful + * + * --Security! + */ +extern int wusb_dev_sec_add(struct wusbhc *, struct usb_device *, + struct wusb_dev *); +extern void wusb_dev_sec_rm(struct wusb_dev *) ; +extern int wusb_dev_4way_handshake(struct wusbhc *, struct wusb_dev *, + struct wusb_ckhdid *ck); +void wusbhc_gtk_rekey(struct wusbhc *wusbhc); +int wusb_dev_update_address(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev); + + +/* WUSB Cluster ID handling */ +extern u8 wusb_cluster_id_get(void); +extern void wusb_cluster_id_put(u8); + +/* + * wusb_port_by_idx - return the port associated to a zero-based port index + * + * NOTE: valid without locking as long as wusbhc is referenced (as the + * number of ports doesn't change). The data pointed to has to + * be verified though :) + */ +static inline struct wusb_port *wusb_port_by_idx(struct wusbhc *wusbhc, + u8 port_idx) +{ + return &wusbhc->port[port_idx]; +} + +/* + * wusb_port_no_to_idx - Convert port number (per usb_dev->portnum) to + * a port_idx. + * + * USB stack USB ports are 1 based!! + * + * NOTE: only valid for WUSB devices!!! + */ +static inline u8 wusb_port_no_to_idx(u8 port_no) +{ + return port_no - 1; +} + +extern struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *, + struct usb_device *); + +/* + * Return a referenced wusb_dev given a @usb_dev + * + * Returns NULL if the usb_dev is being torn down. + * + * FIXME: move offline + */ +static inline +struct wusb_dev *wusb_dev_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct wusbhc *wusbhc; + struct wusb_dev *wusb_dev; + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return NULL; + mutex_lock(&wusbhc->mutex); + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev); + mutex_unlock(&wusbhc->mutex); + wusbhc_put(wusbhc); + return wusb_dev; +} + +/* Misc */ + +extern struct workqueue_struct *wusbd; +#endif /* #ifndef __WUSBHC_H__ */ -- cgit v1.3.1